diff --git a/game_server/__init__.py b/game_server/__init__.py index 7d99013..bef76e9 100644 --- a/game_server/__init__.py +++ b/game_server/__init__.py @@ -1,5 +1,7 @@ from game_server.net.gateway import Gateway +from game_server.game.chat.command_handler import handler class GameServer: def main(self, ServerIp, GameServerPort): + handler.load_commands() Gateway(ServerIp, GameServerPort) diff --git a/game_server/game/chat/__init__.py b/game_server/game/chat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/game_server/game/chat/command/__init__.py b/game_server/game/chat/command/__init__.py new file mode 100644 index 0000000..f509ebf --- /dev/null +++ b/game_server/game/chat/command/__init__.py @@ -0,0 +1,16 @@ +import importlib +import os +import sys + + +folder = "game_server/game/chat/command" +sys.path.append(os.path.dirname(folder)) + +for filename in os.listdir(folder): + if filename.endswith(".py") and filename != "__init__.py": + module_name = filename[:-3] + module_path = f"game_server.game.chat.command.{module_name}" + try: + importlib.import_module(module_path) + except Exception as e: + print(f"Error importing module '{module_path}': {e}") diff --git a/game_server/game/chat/command/max_skill.py b/game_server/game/chat/command/max_skill.py new file mode 100644 index 0000000..25bb723 --- /dev/null +++ b/game_server/game/chat/command/max_skill.py @@ -0,0 +1,48 @@ +from database import mongo +from lib.proto import GetAvatarDataReq, AvatarSubSkill +from game_server.net.session import Session +from game_server.resource import ResourceManager +from game_server.game.enum.data_type import DataType +from game_server.game.chat.decorators import Command +from game_server.resource.configdb.avatar_sub_skill_data import AvatarSubSkillData + +@Command( + prefix="maxskill", + usage="/maxskill {avatar_id | all} - Maximize the skills of a specific avatar. Use 'all' to maximize the skills of all avatars.", +) +async def execute(session: Session, avatar_id): + desc = getattr(execute, "usage", "No description available.") + + if avatar_id == "all": + for avatar in session.player.avatars.values(): + await max_out_skills_for_avatar(avatar, session) + return "All avatars' skills have been maxed out!" + + if not avatar_id.isdigit(): + return f"Usage: {desc}" + + avatar_id = int(avatar_id) + + avatar = session.player.avatars.get(avatar_id) + if not avatar: + return f"Avatar with ID {avatar_id} does not exist." + + await max_out_skills_for_avatar(avatar, session) + + return f"All skills for avatar {avatar_id} have been maxed out!" + +async def max_out_skills_for_avatar(avatar, session:Session): + resource_manager = ResourceManager.instance() + for skill_id, skill in avatar.skill_lists.items(): + sub_skills = [ + data for data in resource_manager.values(AvatarSubSkillData) + if data.skillId == skill_id + ] + for sub_skill_data in sub_skills: + skill.sub_skill_lists[sub_skill_data.avatarSubSkillId] = AvatarSubSkill( + sub_skill_id=sub_skill_data.avatarSubSkillId, + level=sub_skill_data.maxLv + ) + + mongo.save(session, DataType.AVATAR, [avatar.avatar_id]) + await session.process_packet(session.create_packet(GetAvatarDataReq(avatar_id_list=[avatar.avatar_id]))) diff --git a/game_server/game/chat/command/rank.py b/game_server/game/chat/command/rank.py new file mode 100644 index 0000000..2c099a2 --- /dev/null +++ b/game_server/game/chat/command/rank.py @@ -0,0 +1,46 @@ +from game_server.game.enum.data_type import DataType +from game_server.game.chat.decorators import Command +from game_server.net.session import Session +from database import mongo +from lib.proto import GetAvatarDataReq + +@Command( + prefix="rank", + usage="/rank {avatar_id | all} {rank} - Edit the rank of a specific avatar or use 'all' to update the rank of all avatars.", +) +async def execute(session: Session, avatar_id, rank="1"): + desc = getattr(execute, "usage", "No description available.") + + if not rank.isdigit(): + return f"Usage: {desc}" + + if avatar_id == "all": + for avatar in session.player.avatars.values(): + await update_avatar_rank(avatar, rank, session) + return f"Updated rank for all avatars to {rank}." + + if not avatar_id.isdigit(): + return f"Usage: {desc}" + + avatar_id = int(avatar_id) + + avatar = session.player.avatars.get(avatar_id) + if not avatar: + return f"Avatar with ID {avatar_id} does not exist." + + await update_avatar_rank(avatar, rank, session) + + return f"Updated avatar {avatar_id}'s rank to {rank}." + +async def update_avatar_rank(avatar, rank, session:Session): + rank = int(rank) + if rank < 1: + rank = 1 + if rank > 5: + rank = 5 + + avatar.star = rank + + mongo.save(session, DataType.AVATAR, [avatar.avatar_id]) + + await session.process_packet(session.create_packet(GetAvatarDataReq(avatar_id_list=[avatar.avatar_id]))) diff --git a/game_server/game/chat/command_handler.py b/game_server/game/chat/command_handler.py new file mode 100644 index 0000000..803ec72 --- /dev/null +++ b/game_server/game/chat/command_handler.py @@ -0,0 +1,53 @@ +from utils.logger import Info +from game_server.game.chat.decorators import command_registry +from game_server.net.session import Session +import game_server.game.chat.command # noqa: F401 + + +class CommandHandler: + def load_commands(self): + registered_commands = ", ".join( + item.prefix for item in command_registry.values() if not item.is_alias + ) + + Info( + f"[BOOT] [CommandHandler] Registered {len(command_registry)} game commands => {registered_commands}" + ) + + def parse_command(self, content: str): + content = content.lstrip("/") + parts = content.split(maxsplit=1) + if len(parts) < 2: + return parts[0], "" + + return parts[0], parts[1] + + def print_help(self): + result = "Available commands:\n" + for index, (_, func) in enumerate(command_registry.items()): + result += f"{index+1}) {func.usage}\n\n" + return result + + async def handle_command(self, session: Session, content: str): + if content == "/help": + return self.print_help() + + command_label, args = self.parse_command(content) + command_func = command_registry.get(command_label) + + if command_func is not None: + func_args_cnt = command_func.__code__.co_argcount - 1 + args_list = args.split()[:func_args_cnt] + + if args_list and args_list[0] == "help": + return f"Usage: {command_func.usage}" + + try: + return await command_func(session, *args_list) + except TypeError: + return f"Usage: {command_func.usage}" + + return None + + +handler = CommandHandler() diff --git a/game_server/game/chat/decorators.py b/game_server/game/chat/decorators.py new file mode 100644 index 0000000..52a19c2 --- /dev/null +++ b/game_server/game/chat/decorators.py @@ -0,0 +1,21 @@ +from typing import Dict, Type + + +command_registry: Dict[str, Type] = {} + + +def Command(prefix: str, usage: str, aliases: list = list()): + def decorator(func): + func.usage = usage + func.prefix = prefix + func.is_alias = False + command_registry[prefix] = func + + # Register alias if exist + for alias in aliases: + func.is_alias = True + command_registry[alias] = func + + return func + + return decorator diff --git a/game_server/packet/handlers/SendChatMsgNotify.py b/game_server/packet/handlers/SendChatMsgNotify.py new file mode 100644 index 0000000..21264af --- /dev/null +++ b/game_server/packet/handlers/SendChatMsgNotify.py @@ -0,0 +1,68 @@ +import betterproto +from utils.time import get_unix_in_seconds +from game_server.net.session import Session +from game_server.game.chat.command_handler import handler +from lib.proto import ( + SendChatMsgNotify, + RecvChatMsgNotify, + ChatMsg, + ChatMsgSensitiveCheckResult, + ChatMsgMsgChannel, + ChatMsgContent, + ChatMsgItem +) + + +async def handle(session: Session, msg: SendChatMsgNotify) -> betterproto.Message: + string_msg=[msg.msg_str for msg in msg.chat_msg.content.items if msg.msg_str != None][0] + msgs=[ + ChatMsg( + uid=session.player.uid, + nickname=session.player.name, + time=get_unix_in_seconds(), + msg=string_msg, + channel=ChatMsgMsgChannel.WORLD.value, + avatar_id=session.player.assistant_avatar_id, + dress_id=session.player.avatars.get(session.player.assistant_avatar_id).dress_id, + frame_id=session.player.head_frame, + custom_head_id=session.player.head_photo, + check_result=ChatMsgSensitiveCheckResult( + rewrite_text=string_msg + ), + content=ChatMsgContent( + items=[ + ChatMsgItem( + msg_str=string_msg + ) + ] + ) + ) + ] + text = await handler.handle_command(session, string_msg) + if text: + msgs.append( + ChatMsg( + uid=0, + nickname="Ai-Chan", + time=get_unix_in_seconds(), + msg=text, + channel=ChatMsgMsgChannel.WORLD.value, + avatar_id=3201, + dress_id=593201, + frame_id=200001, + custom_head_id=161080, + check_result=ChatMsgSensitiveCheckResult( + rewrite_text=text + ), + content=ChatMsgContent( + items=[ + ChatMsgItem( + msg_str=text + ) + ] + ) + ) + ) + return RecvChatMsgNotify( + chat_msg_list=msgs + )