feat: Add RPG cog with character and inventory features

Sets up RPG system as a Redbot cog with core mechanics
Implements character creation, class selection, and stat management
Adds action commands for attacking and healing
Provides inventory and shop functionality for item management
Introduces interactive UI menus for user engagement
This commit is contained in:
2025-09-11 17:23:45 -04:00
parent 859ac84b68
commit e0958f6f2a
9 changed files with 756 additions and 0 deletions

126
rpg/actions.py Normal file
View File

@@ -0,0 +1,126 @@
import asyncio
import random
import discord
from .check import Check
from redbot.core import commands, Config
from .inventory import RPGInventory
from typing import Literal
class RPGActions:
"""
Implements the core RPG mechanics and actions.
"""
def __init__(self, rpg_cog):
self.rpg_cog = rpg_cog
self.inventory = RPGInventory(rpg_cog)
async def create_character(self, interaction: discord.Interaction, user, character_name):
"""
Creates a new character for the user.
"""
# Character Name Validation
check = Check(interaction, length=30)
if not check.length_under(interaction.message) or not character_name.isalnum():
return await self.rpg_cog.send_message(interaction, "Invalid character name. Please choose a name between 2 and 30 characters long, containing only letters and numbers.")
# Retrieve the 'characters' group
characters_group = self.rpg_cog.config.member(user).characters
# Then, get all characters within that group (await the .all() call)
existing_characters = await characters_group.all()
# Check for duplicate character names (case-insensitive)
if any(existing_name.lower() == character_name.lower() for existing_name in existing_characters):
return await self.rpg_cog.send_message(interaction, f"You already have a character named '{character_name}'. Choose another name.")
# Retrieve available classes from config
try:
available_classes = await self.rpg_cog.config.guild(user.guild).get_raw("classes", default=[])
except KeyError:
return await self.rpg_cog.send_message(interaction, "No classes have been configured yet. Please contact an admin.")
if not available_classes:
return await self.rpg_cog.send_message(interaction, "No classes are available yet. Please contact an admin.")
# Prompt user to choose a class
class_options = "\n".join([f"{i+1}. {class_name}" for i, class_name in enumerate(available_classes)])
await self.rpg_cog.send_message(interaction, f"Choose a class for your character:\n{class_options}")
def check(m):
return m.author == user and m.channel == interaction.channel and m.content.isdigit() and 1 <= int(m.content) <= len(available_classes)
try:
class_choice = await self.rpg_cog.bot.wait_for("message", check=check, timeout=30.0)
except asyncio.TimeoutError:
return await self.rpg_cog.send_message(interaction, "Class selection timed out. Character creation canceled.")
selected_class = available_classes[int(class_choice.content) - 1]
# Retrieve default stats for the selected class
try:
default_stats = await self.rpg_cog.config.guild(user.guild).get_raw("characters", "classes", selected_class, "stats")
except KeyError:
return await self.rpg_cog.send_message(interaction, f"Default stats for class '{selected_class}' haven't been configured yet. Please contact an admin.")
# Create character data
character_data = {
"name": character_name,
"class": [selected_class], # Store the selected class
"level": 1,
"experience": 0,
"stats": default_stats.copy(),
"inventory": [],
"equipment": {},
"skills": [],
"gold": 0,
"max_health": 100,
"health": 100,
"mana": 50,
"max_mana": 50,
}
# Store character data in config
await characters_group.set_raw(character_name, value=character_data)
await self.rpg_cog.config.member(user).active_character.set(character_name)
await interaction.response.send_message(interaction, f"Character '{character_name}' (Class: {selected_class}) created for {user.mention}!")
async def attack(self, ctx, attacker, target):
"""
Handles the attack action.
"""
# Check if attacker and target have active characters
if not await self._has_active_character(ctx, attacker):
return
if not await self._has_active_character(ctx, target):
return
attacker_data = await self.rpg_cog.config.member(attacker).active_character
target_data = await self.rpg_cog.config.member(target).active_character
# ... (rest of the attack logic)
async def heal(self, ctx, user):
"""
Handles the heal action.
"""
# ... (rest of the heal logic)
# Helper function to check if a user has an active character
async def _has_active_character(self, ctx, user):
active_character = await self.rpg_cog.config.member(user).active_character
if not active_character:
await self.rpg_cog.send_message(ctx, f"{user.mention}, you don't have an active character. Create one using `[p]create_character <name>`.")
return False
return True
async def use_item(self, ctx, user, item_name):
"""
Handles the use of an item from the inventory.
"""
# ... (logic to check if the user has the item and handle its effects)
# Remove the used item from the inventory
await self.inventory.remove_item(ctx, user, item_name)