673 lines
27 KiB
Python
673 lines
27 KiB
Python
import discord
|
|
from discord.ext import commands
|
|
from discord import app_commands # Required for choices/autocomplete
|
|
import datetime
|
|
import random
|
|
import logging
|
|
from typing import Optional, List
|
|
|
|
# Import database functions from the sibling module
|
|
from . import database
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
# --- Job Definitions ---
|
|
# Store job details centrally for easier management
|
|
JOB_DEFINITIONS = {
|
|
"miner": {
|
|
"name": "Miner",
|
|
"description": "Mine for ores and gems.",
|
|
"command": "/mine",
|
|
"cooldown": datetime.timedelta(hours=1),
|
|
"base_currency": (15, 30), # Min/Max currency per action
|
|
"base_xp": 15,
|
|
"drops": { # Item Key: Chance (0.0 to 1.0)
|
|
"raw_iron": 0.6,
|
|
"coal": 0.4,
|
|
"shiny_gem": 0.05, # Lower chance for rarer items
|
|
},
|
|
"level_bonus": { # Applied per level
|
|
"currency_increase": 1, # Add +1 to min/max currency range per level
|
|
"rare_find_increase": 0.005, # Increase shiny_gem chance by 0.5% per level
|
|
},
|
|
},
|
|
"fisher": {
|
|
"name": "Fisher",
|
|
"description": "Catch fish and maybe find treasure.",
|
|
"command": "/fish",
|
|
"cooldown": datetime.timedelta(minutes=45),
|
|
"base_currency": (5, 15),
|
|
"base_xp": 10,
|
|
"drops": {
|
|
"common_fish": 0.8, # High chance for common
|
|
"rare_fish": 0.15,
|
|
"treasure_chest": 0.02,
|
|
},
|
|
"level_bonus": {
|
|
"currency_increase": 0.5, # Smaller increase
|
|
"rare_find_increase": 0.003, # Increase rare_fish/treasure chance
|
|
},
|
|
},
|
|
"crafter": {
|
|
"name": "Crafter",
|
|
"description": "Use materials to craft valuable items.",
|
|
"command": "/craft",
|
|
"cooldown": datetime.timedelta(minutes=15), # Cooldown per craft action
|
|
"base_currency": (0, 0), # No direct currency
|
|
"base_xp": 20, # Higher XP for crafting
|
|
"recipes": { # Output Item Key: {Input Item Key: Quantity Required}
|
|
"iron_ingot": {"raw_iron": 2, "coal": 1},
|
|
"basic_tool": {"iron_ingot": 3},
|
|
},
|
|
"level_bonus": {
|
|
"unlock_recipe_level": {"basic_tool": 5}, # Level required to unlock recipe
|
|
# Could add reduced material cost later
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
# Helper function to format time delta
|
|
def format_timedelta(delta: datetime.timedelta) -> str:
|
|
"""Formats a timedelta into a human-readable string (e.g., 1h 30m 15s)."""
|
|
total_seconds = int(delta.total_seconds())
|
|
if total_seconds < 0:
|
|
return "now"
|
|
hours, remainder = divmod(total_seconds, 3600)
|
|
minutes, seconds = divmod(remainder, 60)
|
|
parts = []
|
|
if hours > 0:
|
|
parts.append(f"{hours}h")
|
|
if minutes > 0:
|
|
parts.append(f"{minutes}m")
|
|
if seconds > 0 or not parts: # Show seconds if it's the only unit or > 0
|
|
parts.append(f"{seconds}s")
|
|
return " ".join(parts)
|
|
|
|
|
|
class JobsCommands(commands.Cog):
|
|
"""Cog containing job-related economy commands."""
|
|
|
|
def __init__(self, bot: commands.Bot):
|
|
self.bot = bot
|
|
|
|
# --- Job Management Commands ---
|
|
|
|
@commands.hybrid_command(name="jobs", description="List available jobs.")
|
|
async def list_jobs(self, ctx: commands.Context):
|
|
"""Displays available jobs and their basic information."""
|
|
embed = discord.Embed(title="Available Jobs", color=discord.Color.blue())
|
|
description = "Choose a job to specialize your earning potential!\n\n"
|
|
for key, details in JOB_DEFINITIONS.items():
|
|
description += f"**{details['name']} (`{key}`)**\n"
|
|
description += f"- {details['description']}\n"
|
|
description += f"- Command: `{details['command']}`\n"
|
|
description += f"- Cooldown: {format_timedelta(details['cooldown'])}\n\n"
|
|
embed.description = description
|
|
embed.set_footer(text="Use /choosejob <job_name> to select a job.")
|
|
await ctx.send(embed=embed)
|
|
|
|
@commands.hybrid_command(name="myjob", description="Show your current job status.")
|
|
async def my_job(self, ctx: commands.Context):
|
|
"""Displays the user's current job, level, and XP."""
|
|
user_id = ctx.author.id
|
|
job_info = await database.get_user_job(user_id)
|
|
|
|
if not job_info or not job_info.get("name"):
|
|
embed = discord.Embed(
|
|
description="❌ You don't currently have a job. Use `/jobs` to see available options and `/choosejob <job_name>` to pick one.",
|
|
color=discord.Color.orange(),
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
job_key = job_info["name"]
|
|
level = job_info["level"]
|
|
xp = job_info["xp"]
|
|
job_details = JOB_DEFINITIONS.get(job_key)
|
|
|
|
if not job_details:
|
|
embed = discord.Embed(
|
|
description=f"❌ Error: Your job '{job_key}' is not recognized. Please contact an admin.",
|
|
color=discord.Color.red(),
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
log.error(f"User {user_id} has unrecognized job '{job_key}' in database.")
|
|
return
|
|
|
|
xp_needed = level * 100 # Matches logic in database.py
|
|
embed = discord.Embed(
|
|
title=f"{ctx.author.display_name}'s Job: {job_details['name']}",
|
|
color=discord.Color.green(),
|
|
)
|
|
embed.add_field(name="Level", value=level, inline=True)
|
|
embed.add_field(name="XP", value=f"{xp} / {xp_needed}", inline=True)
|
|
|
|
# Cooldown check
|
|
last_action = job_info.get("last_action")
|
|
cooldown = job_details["cooldown"]
|
|
if last_action:
|
|
now_utc = datetime.datetime.now(datetime.timezone.utc)
|
|
time_since = now_utc - last_action
|
|
if time_since < cooldown:
|
|
time_left = cooldown - time_since
|
|
embed.add_field(
|
|
name="Cooldown",
|
|
value=f"Ready in: {format_timedelta(time_left)}",
|
|
inline=False,
|
|
)
|
|
else:
|
|
embed.add_field(name="Cooldown", value="Ready!", inline=False)
|
|
else:
|
|
embed.add_field(name="Cooldown", value="Ready!", inline=False)
|
|
|
|
embed.set_footer(
|
|
text=f"Use {job_details['command']} to perform your job action."
|
|
)
|
|
await ctx.send(embed=embed)
|
|
|
|
# Autocomplete for choosejob and leavejob
|
|
async def job_autocomplete(
|
|
self, interaction: discord.Interaction, current: str
|
|
) -> List[app_commands.Choice[str]]:
|
|
return [
|
|
app_commands.Choice(name=details["name"], value=key)
|
|
for key, details in JOB_DEFINITIONS.items()
|
|
if current.lower() in key.lower()
|
|
or current.lower() in details["name"].lower()
|
|
][
|
|
:25
|
|
] # Limit to 25 choices
|
|
|
|
@commands.hybrid_command(name="choosejob", description="Select a job to pursue.")
|
|
@app_commands.autocomplete(job_name=job_autocomplete)
|
|
async def choose_job(self, ctx: commands.Context, job_name: str):
|
|
"""Sets the user's active job."""
|
|
user_id = ctx.author.id
|
|
job_key = job_name.lower()
|
|
|
|
if job_key not in JOB_DEFINITIONS:
|
|
embed = discord.Embed(
|
|
description=f"❌ Invalid job name '{job_name}'. Use `/jobs` to see available options.",
|
|
color=discord.Color.red(),
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
current_job_info = await database.get_user_job(user_id)
|
|
if current_job_info and current_job_info.get("name") == job_key:
|
|
embed = discord.Embed(
|
|
description=f"✅ You are already a {JOB_DEFINITIONS[job_key]['name']}.",
|
|
color=discord.Color.blue(),
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
# Implement switching cost/cooldown here if desired
|
|
# For now, allow free switching, resetting progress
|
|
await database.set_user_job(user_id, job_key)
|
|
embed = discord.Embed(
|
|
title="Job Changed!",
|
|
description=f"💼 Congratulations! You are now a **{JOB_DEFINITIONS[job_key]['name']}**.",
|
|
color=discord.Color.green(),
|
|
)
|
|
embed.set_footer(text="Your previous job progress (if any) has been reset.")
|
|
await ctx.send(embed=embed)
|
|
|
|
@commands.hybrid_command(name="leavejob", description="Leave your current job.")
|
|
async def leave_job(self, ctx: commands.Context):
|
|
"""Abandons the user's current job, resetting progress."""
|
|
user_id = ctx.author.id
|
|
current_job_info = await database.get_user_job(user_id)
|
|
|
|
if not current_job_info or not current_job_info.get("name"):
|
|
embed = discord.Embed(
|
|
description="❌ You don't have a job to leave.",
|
|
color=discord.Color.orange(),
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
job_key = current_job_info["name"]
|
|
job_name = JOB_DEFINITIONS.get(job_key, {}).get("name", "Unknown Job")
|
|
|
|
await database.set_user_job(user_id, None) # Set job to NULL
|
|
embed = discord.Embed(
|
|
title="Job Left",
|
|
description=f"🗑️ You have left your job as a **{job_name}**.",
|
|
color=discord.Color.orange(),
|
|
)
|
|
embed.set_footer(
|
|
text="Your level and XP for this job have been reset. You can choose a new job with /choosejob."
|
|
)
|
|
await ctx.send(embed=embed)
|
|
|
|
# --- Job Action Commands ---
|
|
|
|
async def _handle_job_action(self, ctx: commands.Context, job_key: str):
|
|
"""Internal handler for job actions to check cooldowns, grant rewards, etc."""
|
|
user_id = ctx.author.id
|
|
job_info = await database.get_user_job(user_id)
|
|
|
|
# 1. Check if user has the correct job
|
|
if not job_info or job_info.get("name") != job_key:
|
|
correct_job_info = await database.get_user_job(user_id)
|
|
if correct_job_info and correct_job_info.get("name"):
|
|
correct_job_details = JOB_DEFINITIONS.get(correct_job_info["name"])
|
|
embed = discord.Embed(
|
|
description=f"❌ You need to be a {JOB_DEFINITIONS[job_key]['name']} to use this command. Your current job is {correct_job_details['name']}. Use `{correct_job_details['command']}` instead, or change jobs with `/choosejob`.",
|
|
color=discord.Color.red(),
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
else:
|
|
embed = discord.Embed(
|
|
description=f"❌ You need to be a {JOB_DEFINITIONS[job_key]['name']} to use this command. You don't have a job. Use `/choosejob {job_key}` first.",
|
|
color=discord.Color.red(),
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
return None # Indicate failure
|
|
|
|
job_details = JOB_DEFINITIONS[job_key]
|
|
level = job_info["level"]
|
|
|
|
# 2. Check Cooldown
|
|
last_action = job_info.get("last_action")
|
|
cooldown = job_details["cooldown"]
|
|
if last_action:
|
|
now_utc = datetime.datetime.now(datetime.timezone.utc)
|
|
time_since = now_utc - last_action
|
|
if time_since < cooldown:
|
|
time_left = cooldown - time_since
|
|
embed = discord.Embed(
|
|
description=f"🕒 You need to wait **{format_timedelta(time_left)}** before you can {job_key} again.",
|
|
color=discord.Color.orange(),
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
return None # Indicate failure
|
|
|
|
# 3. Set Cooldown Immediately
|
|
await database.set_job_cooldown(user_id)
|
|
|
|
# 4. Calculate Rewards
|
|
level_bonus = job_details.get("level_bonus", {})
|
|
currency_bonus = level * level_bonus.get("currency_increase", 0)
|
|
min_curr, max_curr = job_details["base_currency"]
|
|
currency_earned = random.randint(
|
|
int(min_curr + currency_bonus), int(max_curr + currency_bonus)
|
|
)
|
|
|
|
items_found = {}
|
|
if "drops" in job_details:
|
|
rare_find_bonus = level * level_bonus.get("rare_find_increase", 0)
|
|
for item_key, base_chance in job_details["drops"].items():
|
|
# Apply level bonus to specific rare items if configured (e.g., gems for miner)
|
|
current_chance = base_chance
|
|
if item_key == "shiny_gem" and job_key == "miner":
|
|
current_chance += rare_find_bonus
|
|
elif (
|
|
item_key == "rare_fish" or item_key == "treasure_chest"
|
|
) and job_key == "fisher":
|
|
current_chance += rare_find_bonus
|
|
|
|
if random.random() < current_chance:
|
|
items_found[item_key] = items_found.get(item_key, 0) + 1
|
|
|
|
# 5. Grant Rewards (DB updates)
|
|
if currency_earned > 0:
|
|
await database.update_balance(user_id, currency_earned)
|
|
for item_key, quantity in items_found.items():
|
|
await database.add_item_to_inventory(user_id, item_key, quantity)
|
|
|
|
# 6. Grant XP & Handle Level Up
|
|
xp_earned = job_details["base_xp"] # Could add level bonus to XP later
|
|
new_level, new_xp, did_level_up = await database.add_job_xp(user_id, xp_earned)
|
|
|
|
# 7. Construct Response Message
|
|
response_parts = []
|
|
if currency_earned > 0:
|
|
response_parts.append(f"earned **${currency_earned:,}**")
|
|
if items_found:
|
|
item_strings = []
|
|
for item_key, quantity in items_found.items():
|
|
item_details = await database.get_item_details(item_key)
|
|
item_name = item_details["name"] if item_details else item_key
|
|
item_strings.append(f"{quantity}x **{item_name}**")
|
|
response_parts.append(f"found {', '.join(item_strings)}")
|
|
|
|
response_parts.append(f"gained **{xp_earned} XP**")
|
|
|
|
action_verb = job_key.capitalize() # "Mine", "Fish"
|
|
message = (
|
|
f"⛏️ You {action_verb} and {', '.join(response_parts)}." # Default message
|
|
)
|
|
|
|
# Customize message based on job
|
|
if job_key == "miner":
|
|
message = f"⛏️ You mined and {', '.join(response_parts)}."
|
|
elif job_key == "fisher":
|
|
message = f"🎣 You fished and {', '.join(response_parts)}."
|
|
# Crafter handled separately
|
|
|
|
if did_level_up:
|
|
message += f"\n**Congratulations! You reached Level {new_level} in {job_details['name']}!** 🎉"
|
|
|
|
current_balance = await database.get_balance(user_id)
|
|
message += f"\nYour current balance is **${current_balance:,}**."
|
|
|
|
return message # Indicate success and return message
|
|
|
|
@commands.hybrid_command(
|
|
name="mine", description="Mine for ores and gems (Miner job)."
|
|
)
|
|
async def mine(self, ctx: commands.Context):
|
|
"""Performs the Miner job action."""
|
|
result_message = await self._handle_job_action(ctx, "miner")
|
|
if result_message:
|
|
embed = discord.Embed(
|
|
title="Mining Results",
|
|
description=result_message,
|
|
color=discord.Color.dark_grey(),
|
|
)
|
|
await ctx.send(embed=embed)
|
|
|
|
@commands.hybrid_command(
|
|
name="fish", description="Catch fish and maybe find treasure (Fisher job)."
|
|
)
|
|
async def fish(self, ctx: commands.Context):
|
|
"""Performs the Fisher job action."""
|
|
result_message = await self._handle_job_action(ctx, "fisher")
|
|
if result_message:
|
|
embed = discord.Embed(
|
|
title="Fishing Results",
|
|
description=result_message,
|
|
color=discord.Color.blue(),
|
|
)
|
|
await ctx.send(embed=embed)
|
|
|
|
# --- Crafter Specific ---
|
|
async def craft_autocomplete(
|
|
self, interaction: discord.Interaction, current: str
|
|
) -> List[app_commands.Choice[str]]:
|
|
user_id = interaction.user.id
|
|
job_info = await database.get_user_job(user_id)
|
|
choices = []
|
|
if job_info and job_info.get("name") == "crafter":
|
|
crafter_details = JOB_DEFINITIONS["crafter"]
|
|
level = job_info["level"]
|
|
for item_key, recipe in crafter_details.get("recipes", {}).items():
|
|
# Check level requirement
|
|
required_level = (
|
|
crafter_details.get("level_bonus", {})
|
|
.get("unlock_recipe_level", {})
|
|
.get(item_key, 1)
|
|
)
|
|
if level < required_level:
|
|
continue
|
|
|
|
item_details = await database.get_item_details(item_key)
|
|
item_name = item_details["name"] if item_details else item_key
|
|
if (
|
|
current.lower() in item_key.lower()
|
|
or current.lower() in item_name.lower()
|
|
):
|
|
choices.append(app_commands.Choice(name=item_name, value=item_key))
|
|
return choices[:25]
|
|
|
|
@commands.hybrid_command(
|
|
name="craft", description="Craft items using materials (Crafter job)."
|
|
)
|
|
@app_commands.autocomplete(item_to_craft=craft_autocomplete)
|
|
async def craft(self, ctx: commands.Context, item_to_craft: str):
|
|
"""Performs the Crafter job action."""
|
|
user_id = ctx.author.id
|
|
job_key = "crafter"
|
|
job_info = await database.get_user_job(user_id)
|
|
|
|
# 1. Check if user has the correct job
|
|
if not job_info or job_info.get("name") != job_key:
|
|
embed = discord.Embed(
|
|
description="❌ You need to be a Crafter to use this command. Use `/choosejob crafter` first.",
|
|
color=discord.Color.red(),
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
job_details = JOB_DEFINITIONS[job_key]
|
|
level = job_info["level"]
|
|
recipe_key = item_to_craft.lower()
|
|
|
|
# 2. Check if recipe exists
|
|
recipes = job_details.get("recipes", {})
|
|
if recipe_key not in recipes:
|
|
embed = discord.Embed(
|
|
description=f"❌ Unknown recipe: '{item_to_craft}'. Check available recipes.",
|
|
color=discord.Color.red(),
|
|
) # TODO: Add /recipes command?
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
# 3. Check Level Requirement
|
|
required_level = (
|
|
job_details.get("level_bonus", {})
|
|
.get("unlock_recipe_level", {})
|
|
.get(recipe_key, 1)
|
|
)
|
|
if level < required_level:
|
|
embed = discord.Embed(
|
|
description=f"❌ You need to be Level {required_level} to craft this item. You are currently Level {level}.",
|
|
color=discord.Color.red(),
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
# 4. Check Cooldown
|
|
last_action = job_info.get("last_action")
|
|
cooldown = job_details["cooldown"]
|
|
if last_action:
|
|
now_utc = datetime.datetime.now(datetime.timezone.utc)
|
|
time_since = now_utc - last_action
|
|
if time_since < cooldown:
|
|
time_left = cooldown - time_since
|
|
embed = discord.Embed(
|
|
description=f"🕒 You need to wait **{format_timedelta(time_left)}** before you can craft again.",
|
|
color=discord.Color.orange(),
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
# 5. Check Materials
|
|
required_materials = recipes[recipe_key]
|
|
inventory = await database.get_inventory(user_id)
|
|
inventory_map = {item["key"]: item["quantity"] for item in inventory}
|
|
missing_materials = []
|
|
can_craft = True
|
|
for mat_key, mat_qty in required_materials.items():
|
|
if inventory_map.get(mat_key, 0) < mat_qty:
|
|
can_craft = False
|
|
mat_details = await database.get_item_details(mat_key)
|
|
mat_name = mat_details["name"] if mat_details else mat_key
|
|
missing_materials.append(
|
|
f"{mat_qty - inventory_map.get(mat_key, 0)}x {mat_name}"
|
|
)
|
|
|
|
if not can_craft:
|
|
embed = discord.Embed(
|
|
title="Missing Materials",
|
|
description=f"❌ You don't have the required materials. You still need: {', '.join(missing_materials)}.",
|
|
color=discord.Color.red(),
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
# 6. Set Cooldown Immediately
|
|
await database.set_job_cooldown(user_id)
|
|
|
|
# 7. Consume Materials & Grant Item
|
|
success = True
|
|
for mat_key, mat_qty in required_materials.items():
|
|
if not await database.remove_item_from_inventory(user_id, mat_key, mat_qty):
|
|
success = False
|
|
log.error(
|
|
f"Failed to remove material {mat_key} x{mat_qty} for user {user_id} during crafting, despite check."
|
|
)
|
|
embed = discord.Embed(
|
|
description="❌ An error occurred while consuming materials. Please try again.",
|
|
color=discord.Color.red(),
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
# Should ideally revert cooldown here, but that's complex.
|
|
return
|
|
|
|
if success:
|
|
await database.add_item_to_inventory(user_id, recipe_key, 1)
|
|
|
|
# 8. Grant XP & Handle Level Up
|
|
xp_earned = job_details["base_xp"]
|
|
new_level, new_xp, did_level_up = await database.add_job_xp(
|
|
user_id, xp_earned
|
|
)
|
|
|
|
# 9. Construct Response
|
|
crafted_item_details = await database.get_item_details(recipe_key)
|
|
crafted_item_details = await database.get_item_details(recipe_key)
|
|
crafted_item_name = (
|
|
crafted_item_details["name"] if crafted_item_details else recipe_key
|
|
)
|
|
embed = discord.Embed(
|
|
title="Crafting Successful!",
|
|
description=f"🛠️ You successfully crafted 1x **{crafted_item_name}** and gained **{xp_earned} XP**.",
|
|
color=discord.Color.purple(), # Use a different color for crafting
|
|
)
|
|
|
|
if did_level_up:
|
|
embed.add_field(
|
|
name="Level Up!",
|
|
value=f"**Congratulations! You reached Level {new_level} in {job_details['name']}!** 🎉",
|
|
inline=False,
|
|
)
|
|
|
|
await ctx.send(embed=embed)
|
|
|
|
# --- Inventory Commands ---
|
|
|
|
@commands.hybrid_command(
|
|
name="inventory", aliases=["inv"], description="View your items."
|
|
)
|
|
async def inventory(self, ctx: commands.Context):
|
|
"""Displays the items in the user's inventory."""
|
|
user_id = ctx.author.id
|
|
inventory_items = await database.get_inventory(user_id)
|
|
|
|
if not inventory_items:
|
|
embed = discord.Embed(
|
|
description="🗑️ Your inventory is empty.", color=discord.Color.orange()
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
embed = discord.Embed(
|
|
title=f"{ctx.author.display_name}'s Inventory 🎒",
|
|
color=discord.Color.orange(),
|
|
)
|
|
description = ""
|
|
for item in inventory_items:
|
|
sell_info = (
|
|
f" (Sell: ${item['sell_price']:,})" if item["sell_price"] > 0 else ""
|
|
)
|
|
description += f"- **{item['name']}** x{item['quantity']}{sell_info}\n"
|
|
if item["description"]:
|
|
description += (
|
|
f" *({item['description']})*\n" # Add description if available
|
|
)
|
|
|
|
# Handle potential description length limit
|
|
if len(description) > 4000: # Embed description limit is 4096
|
|
description = (
|
|
description[:4000] + "\n... (Inventory too large to display fully)"
|
|
)
|
|
|
|
embed.description = description
|
|
await ctx.send(embed=embed)
|
|
|
|
# Autocomplete for sell command
|
|
async def inventory_autocomplete(
|
|
self, interaction: discord.Interaction, current: str
|
|
) -> List[app_commands.Choice[str]]:
|
|
user_id = interaction.user.id
|
|
inventory = await database.get_inventory(user_id)
|
|
return [
|
|
app_commands.Choice(
|
|
name=f"{item['name']} (Have: {item['quantity']})", value=item["key"]
|
|
)
|
|
for item in inventory
|
|
if item["sell_price"] > 0
|
|
and (
|
|
current.lower() in item["key"].lower()
|
|
or current.lower() in item["name"].lower()
|
|
)
|
|
][:25]
|
|
|
|
@commands.hybrid_command(name="sell", description="Sell items from your inventory.")
|
|
@app_commands.autocomplete(item_key=inventory_autocomplete)
|
|
async def sell(
|
|
self, ctx: commands.Context, item_key: str, quantity: Optional[int] = 1
|
|
):
|
|
"""Sells a specified quantity of an item from the inventory."""
|
|
user_id = ctx.author.id
|
|
|
|
if quantity <= 0:
|
|
embed = discord.Embed(
|
|
description="❌ Please enter a positive quantity to sell.",
|
|
color=discord.Color.red(),
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
item_details = await database.get_item_details(item_key)
|
|
if not item_details:
|
|
embed = discord.Embed(
|
|
description=f"❌ Invalid item key '{item_key}'. Check your `/inventory`.",
|
|
color=discord.Color.red(),
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
if item_details["sell_price"] <= 0:
|
|
embed = discord.Embed(
|
|
description=f"❌ You cannot sell **{item_details['name']}**.",
|
|
color=discord.Color.red(),
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
# Try to remove items first
|
|
removed = await database.remove_item_from_inventory(user_id, item_key, quantity)
|
|
|
|
if not removed:
|
|
# Get current quantity to show in error message
|
|
inventory = await database.get_inventory(user_id)
|
|
current_quantity = 0
|
|
for item in inventory:
|
|
if item["key"] == item_key:
|
|
current_quantity = item["quantity"]
|
|
break
|
|
embed = discord.Embed(
|
|
description=f"❌ You don't have {quantity}x **{item_details['name']}** to sell. You only have {current_quantity}.",
|
|
color=discord.Color.red(),
|
|
)
|
|
await ctx.send(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
# Grant money if removal was successful
|
|
total_earnings = item_details["sell_price"] * quantity
|
|
await database.update_balance(user_id, total_earnings)
|
|
|
|
current_balance = await database.get_balance(user_id)
|
|
embed = discord.Embed(
|
|
title="Item Sold!",
|
|
description=f"💰 You sold {quantity}x **{item_details['name']}** for **${total_earnings:,}**.",
|
|
color=discord.Color.green(),
|
|
)
|
|
embed.add_field(name="New Balance", value=f"${current_balance:,}", inline=False)
|
|
await ctx.send(embed=embed)
|