This commit is contained in:
Naruse
2025-04-15 19:36:05 +08:00
parent dd51fb491d
commit ec8972d5d6
121 changed files with 30598 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
import dataclasses
from rail_proto.lib import AvatarSkillTree,EquipRelic
@dataclasses.dataclass
class AvatarManager:
avatar_id: int
level: int
exp: int
promotion: int
rank: int
skills: list[AvatarSkillTree]
equip_id: int = 0
relic_list: dict[int,EquipRelic] = dataclasses.field(default_factory=dict)

View File

@@ -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}")

View File

@@ -0,0 +1,60 @@
from game_server.game.chat.decorators import Command
from game_server.net.session import PlayerSession
from game_server.resource import ResourceManager
from game_server.resource.configdb.equipment_config import EquipmentConfig
from game_server.resource.configdb.relic_config import RelicConfigData
from game_server.resource.configdb.relic_main_affix_config import RelicMainAffixConfigData
from game_server.resource.configdb.relic_sub_affix_config import RelicSubAffixConfigData
from database.inventory.inventory_data import SubAffix
@Command(
prefix="give",
usage="/give",
)
async def execute(session:PlayerSession, item_id, param1=None, param2=None, param3=None, param4=None, param5=None):
try:
sync = False
lightcones = ResourceManager.instance().find_by_index(EquipmentConfig,item_id)
relics = ResourceManager.instance().find_by_index(RelicConfigData,item_id)
if lightcones:
rank = 1
if param1 and param1.startswith("r") and len(param1) == 2:
rank_value = param1[1]
if rank_value.isdigit() and 1 <= int(rank_value) <= 5:
rank = int(rank_value)
elif rank_value.isdigit() and int(rank_value) > 5:
rank = 5
item = session.player.add_lightcone(item_id, rank)
if item:
session.player.inventory_manager[item.unique_id] = item
sync = True
elif relics:
main_stat = 0
main_property = ""
main_affix_group = ResourceManager.instance().find_all_by_index(RelicMainAffixConfigData, relics.MainAffixGroup)
sub_affix_list = []
if param1 and param1.startswith("s") and len(param1) == 2 and param1[1].isdigit():
main_affix = next((affix for affix in main_affix_group if affix.AffixID == int(param1[1])))
if main_affix:
main_stat = main_affix.AffixID
main_property = main_affix.Property
for param in [param2, param3, param4, param5]:
if param and len(param) >= 3 and ":" in param:
parts = param.split(":")
if len(parts) == 2 and all(part.isdigit() for part in parts):
sub_affix_data = ResourceManager.instance().find_all_by_index(RelicSubAffixConfigData, relics.SubAffixGroup)
for affix in sub_affix_data:
if affix.AffixID == int(parts[0]) and affix.Property != main_property:
sub_affix_list.append(SubAffix(id=int(parts[0]), count=int(parts[1]), step=int(parts[1]) * 2))
item = session.player.add_relic(item_id,main_stat,sub_affix_list)
if item:
session.player.inventory_manager[item.unique_id] = item
sync = True
if sync:
await session.notify(session.player.PlayerSyncProto())
return "GIVE"
except Exception as e:
print(e)

View File

@@ -0,0 +1,48 @@
from game_server.game.chat.decorators import Command
from game_server.net.session import PlayerSession
from game_server.resource import ResourceManager
from game_server.resource.configdb.avatar_config import AvatarConfig
from game_server.resource.configdb.equipment_config import EquipmentConfig
from game_server.resource.configdb.relic_config import RelicConfigData
@Command(
prefix="giveall",
usage="/giveall",
)
async def execute(session:PlayerSession, text):
try:
sync = False
if text == "avatars":
avatars = ResourceManager.instance().values(AvatarConfig)
for avatar in avatars:
if avatar.AvatarID == 1224 or avatar.AvatarID >= 7000:
continue
if session.player.avatar_mananger.get(avatar.AvatarID):
continue
data = session.player.add_avatar(avatar.AvatarID)
if data:
session.player.avatar_mananger[data.avatar_id] = data
sync = True
if text == "lightcones":
lightcones = ResourceManager.instance().values(EquipmentConfig)
for lightcone in lightcones:
item = session.player.add_lightcone(lightcone.EquipmentID)
if item:
session.player.inventory_manager[item.unique_id] = item
sync = True
if text == "relics":
relics = ResourceManager.instance().values(RelicConfigData)
for relic in relics:
item = session.player.add_relic(relic.ID)
if item:
session.player.inventory_manager[item.unique_id] = item
sync = True
if sync:
await session.notify(session.player.PlayerSyncProto())
return "GIVEALL"
except Exception as e:
print(e)

View File

@@ -0,0 +1,53 @@
from utils.logger import Info
from game_server.game.chat.decorators import command_registry
from game_server.net.session import PlayerSession
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: PlayerSession, 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()

View File

@@ -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

View File

@@ -0,0 +1,10 @@
from enum import Enum
class RelicTypeEnum(Enum):
Unknown = 0
HEAD = 1
HAND = 2
BODY = 3
FOOT = 4
NECK = 5
OBJECT = 6

View File

@@ -0,0 +1,12 @@
from enum import Enum
class GameModeTypeEnum(Enum):
Unknown = 0
Town = 1
Maze = 2
Train = 3
Challenge = 4
Rogue = 5
Raid = 6
AetherDivide = 7
TrialActivity = 8

View File

@@ -0,0 +1,57 @@
import random
from pydantic import BaseModel
from game_server.resource import ResourceManager
from game_server.resource.configdb.relic_config import RelicConfigData
from game_server.resource.configdb.relic_main_affix_config import RelicMainAffixConfigData
from game_server.resource.configdb.relic_sub_affix_config import RelicSubAffixConfigData
class SubAffix(BaseModel):
id: int
count: int
step: int
class MainAffix(BaseModel):
AffixID: int
Property: str
class RelicManager(BaseModel):
def GetRandomRelicMainAffix(self, itemid) -> MainAffix:
config = ResourceManager.instance().find_by_index(RelicConfigData, itemid)
if not config:
return MainAffix(AffixID=0, Property=None)
affixes = [
affix
for affix in ResourceManager.instance().values(RelicMainAffixConfigData)
if config.MainAffixGroup == affix.GroupID
]
if not affixes:
return MainAffix(AffixID=0, Property=None)
selected_affix = random.choice(affixes)
return MainAffix(AffixID=selected_affix.AffixID, Property=selected_affix.Property)
def GetRandomRelicSubAffix(self, itemid: int, property: str, loop: int, affix_list: list[SubAffix] = []) -> list[SubAffix]:
config = ResourceManager.instance().find_by_index(RelicConfigData, itemid)
if not config:
return []
affix_ids_in_list = {affix.id for affix in affix_list}
affixes = [
affix
for affix in ResourceManager.instance().values(RelicSubAffixConfigData)
if config.SubAffixGroup == affix.GroupID and affix.Property != property and affix.AffixID not in affix_ids_in_list
]
selected_affixes = random.sample(affixes, 4 - len(affix_ids_in_list))
sub_affix_list = []
for affix in selected_affixes:
count = random.randint(1, loop)
step = count * 2
sub_affix_list.append(SubAffix(id=affix.AffixID, count=count, step=step))
return sub_affix_list

View File

@@ -0,0 +1,22 @@
from pydantic import BaseModel
from rail_proto.lib import MotionInfo,Vector
class Motion(BaseModel):
x: float
y: float
z: float
rotY: float
def ToProto(self) -> MotionInfo:
return MotionInfo(
pos=Vector(
x=int(self.x*1000),
y=int(self.y*1000),
z=int(self.z*1000)
),
rot=Vector(
x=0,
y=int(self.rotY*1000),
z=0
)
)

View File

@@ -0,0 +1,171 @@
from pydantic import BaseModel
from database.player.player_data import PlayerData,players_collection
from database.avatar.avatar_data import AvatarData,get_all_avatars_by_uid,avatars_collection
from database.lineup.lineup_data import LineupData,get_all_lineup_by_uid,lineups_collection
from database.inventory.inventory_data import InventoryData,get_all_items_by_uid,items_collection
from game_server.game.scene.scene_manager import SceneManager
from typing import Optional
from rail_proto.lib import (
PlayerSyncScNotify,
AvatarSync
)
from pymongo import UpdateOne
from utils.logger import Error
class PlayerManager(BaseModel):
data : PlayerData = PlayerData()
avatar_mananger: dict[int,AvatarData] = {}
lineup_manager: dict[int,LineupData] = {}
inventory_manager: dict[int,InventoryData] = {}
scene_manager: SceneManager = None
def init_default(self):
self.add_all_avatars()
self.add_all_lineups()
self.add_all_items()
self.scene_manager = SceneManager(
entry_id=self.data.entry_id
)
def save_all(self):
self.save_player_data_bulk()
self.save_all_avatars_bulk()
self.save_all_lineups_bulk()
self.save_all_items_bulk()
def add_all_avatars(self):
avatars = get_all_avatars_by_uid(uid=self.data.uid)
if not avatars:
avatar = AvatarData(
uid=self.data.uid,
avatar_id=self.data.cur_basic_type
).add_avatar()
self.avatar_mananger[self.data.cur_basic_type] = avatar
for avatar in avatars:
self.avatar_mananger[avatar.avatar_id] = avatar
def add_all_lineups(self):
lineups = get_all_lineup_by_uid(uid=self.data.uid)
if not lineups:
lineup = LineupData(
uid=self.data.uid,
index=0,
name="",
avatar_list=[self.data.cur_basic_type]
).add_lineup()
self.lineup_manager[lineup.index] = lineup
for lineup in lineups:
self.lineup_manager[lineup.index] = lineup
def add_all_items(self):
items = get_all_items_by_uid(uid=self.data.uid)
for item in items:
self.inventory_manager[item.unique_id] = item
def add_avatar(self,avatar_id:int) -> Optional[AvatarData]:
avatar = self.avatar_mananger.get(avatar_id)
if avatar:
return None
avatar = AvatarData(uid=self.data.uid,avatar_id=avatar_id).add_avatar()
if avatar:
return avatar
return None
def add_lineup(self,avatar_ids: list[int],index=0,name=""):
LineupData(
uid=self.data.uid,
index=index,
name=name,
avatar_list=avatar_ids
).add_lineup()
def add_lightcone(self,lightcone_id: int, rank=1) -> Optional[InventoryData]:
item = InventoryData(
uid=self.data.uid,
item_id=lightcone_id,
rank=rank
).add_lightcone()
if item:
return item
return None
def add_relic(self,relic_id: int, main_affix=0, sub_affix=[]) -> Optional[InventoryData]:
item = InventoryData(
uid=self.data.uid,
item_id=relic_id,
main_affix=main_affix,
sub_affix=sub_affix
).add_relic()
if item:
return item
return None
def PlayerSyncProto(self) -> PlayerSyncScNotify:
avatars = []
for avatar_id,avatar in self.avatar_mananger.items():
avatars.append(avatar.ToProto())
lightcones = []
for unique_id,item in self.inventory_manager.items():
if item.main_type == 1:
lightcones.append(item.ToProto())
relics = []
for unique_id,item in self.inventory_manager.items():
if item.main_type == 2:
relics.append(item.ToProto())
return PlayerSyncScNotify(
equipment_list=lightcones,
relic_list=relics,
avatar_sync=
AvatarSync(
avatar_list=avatars
),
basic_info=self.data.ToProto()
)
def save_player_data_bulk(self):
try:
player_data = self.data.model_dump()
player_data["uid"] = self.data.uid
query = {"uid": self.data.uid}
players_collection.update_one(query, {"$set": player_data}, upsert=True)
except Exception as e:
Error(f"Error save player data: {e}")
def save_all_avatars_bulk(self):
operations = []
for avatar in self.avatar_mananger.values():
avatar_data = avatar.model_dump()
avatar_data["uid"] = avatar.uid
query = {"uid": avatar.uid, "avatar_id": avatar.avatar_id}
operations.append(UpdateOne(query, {"$set": avatar_data}, upsert=True))
if operations:
avatars_collection.bulk_write(operations)
def save_all_lineups_bulk(self):
operations = []
for lineup in self.lineup_manager.values():
lineup_data = lineup.model_dump()
lineup_data["uid"] = lineup.uid
query = {"uid": lineup.uid, "index": lineup.index}
operations.append(UpdateOne(query, {"$set": lineup_data}, upsert=True))
if operations:
lineups_collection.bulk_write(operations)
def save_all_items_bulk(self):
operations = []
for item in self.inventory_manager.values():
item_data = item.model_dump()
item_data["uid"] = item.uid
query = {"uid": item.uid, "unique_id": item.unique_id}
operations.append(UpdateOne(query, {"$set": item_data}, upsert=True))
if operations:
items_collection.bulk_write(operations)

View File

@@ -0,0 +1,165 @@
from pydantic import BaseModel
from game_server.game.enums.scene.game_mode_type import GameModeTypeEnum
from game_server.resource import ResourceManager
from game_server.resource.configdb.maze_plane import MazePlaneData
from game_server.resource.configdb.map_entrance import MapEntranceData
from game_server.game.motion.motion_info import Motion
from rail_proto.lib import (
SceneEntityGroupInfo,
SceneInfo,
SceneEntityInfo,
SceneEntityGroupInfo,
ScenePropInfo,
SceneNpcInfo,
SceneNpcMonsterInfo,
SceneActorInfo,
AvatarType
)
class SceneManager(BaseModel):
entry_id: int
plane_id: int = 0
floor_id: int = 0
game_mode_type: int = 0
world_id: int = 0
teleport_id: int = 0
entity_group_list: list[SceneEntityGroupInfo] = []
entities: dict[int,SceneEntityInfo] = {}
battle_monster: list[SceneEntityInfo] = []
def ToProto(self,session) -> SceneInfo:
prop_entity_id = 10
npc_entity_id = 10_000
monster_entity_id = 20_000
entrance = ResourceManager.instance().find_by_index(MapEntranceData, self.entry_id)
maze_plane = ResourceManager.instance().find_by_index(MazePlaneData, entrance.PlaneID)
floor_infos = entrance.floor_infos.get(self.entry_id)
proto = SceneInfo(
entry_id=self.entry_id,
plane_id=entrance.PlaneID,
floor_id=entrance.FloorID,
)
proto.game_mode_type = GameModeTypeEnum[maze_plane.PlaneType].value
if maze_plane.WorldID == 100:
maze_plane.WorldID = 401
proto.world_id = maze_plane.WorldID
loaded_npc = [13199,13321,1313,13227,13190,13191,13192,13141,13246,13407 ,13223,13152]
loaded_group = []
groups = []
for group_id,group in floor_infos.groups.items():
if group.LoadSide == "Client":
continue
# if group_id in loaded_group:
# continue
# if group_id > 150 and group_id < 200:
# continue
# if group_id > 300:
# continue
if group_id in groups:
continue
groups.append(group_id)
group_info = SceneEntityGroupInfo(
group_id=group_id
)
for prop in group.PropList:
prop_entity_id += 1
prop_entity_info = SceneEntityInfo(
inst_id=prop.ID,
group_id=group_id,
motion=Motion(
x=prop.PosX,
y=prop.PosY,
z=prop.PosZ,
rotY=prop.RotY
).ToProto(),
prop=ScenePropInfo(
prop_id=prop.PropID,
prop_state=8 if prop.MappingInfoID > 0 else (1 if prop.State == 0 else prop.State)
),
entity_id=prop_entity_id
)
group_info.entity_list.append(prop_entity_info)
if self.teleport_id > 0 and self.teleport_id == prop.MappingInfoID:
for anchor in group.AnchorList:
if group.id == prop.AnchorGroupID and anchor.ID == prop.AnchorID:
session.player.data.pos.x = int(anchor.PosX)
session.player.data.pos.y = int(anchor.PosY)
session.player.data.pos.z = int(anchor.PosZ)
session.player.data.rot.y = int(anchor.RotY)
for npc in group.NPCList:
if npc.NPCID in loaded_npc:
continue
loaded_npc.append(npc.NPCID)
npc_entity_id += 1
npc_entity_info = SceneEntityInfo(
inst_id=npc.ID,
group_id=group_id,
entity_id=npc_entity_id,
motion=Motion(
x=npc.PosX,
y=npc.PosY,
z=npc.PosZ,
rotY=npc.RotY
).ToProto(),
npc=SceneNpcInfo(
npc_id=npc.NPCID
)
)
group_info.entity_list.append(npc_entity_info)
for monster in group.MonsterList:
monster_entity_id += 1
monster_entity_info = SceneEntityInfo(
inst_id=monster.ID,
group_id=group_id,
entity_id=monster_entity_id,
motion=Motion(
x=monster.PosX,
y=monster.PosY,
z=monster.PosZ,
rotY=monster.RotY
).ToProto(),
npc_monster=SceneNpcMonsterInfo(
monster_id=monster.NPCMonsterID,
event_id=monster.EventID,
world_level=6
)
)
group_info.entity_list.append(monster_entity_info)
session.player.scene_manager.entities[monster_entity_id] = monster_entity_info
proto.entity_group_list.append(group_info)
player_pos = Motion(
x=session.player.data.pos.x,
y=session.player.data.pos.y,
z=session.player.data.pos.z,
rotY=session.player.data.rot.y
).ToProto()
player_group = SceneEntityGroupInfo(state=0,group_id=0)
for avatar_id in session.player.lineup_manager.get(session.player.data.cur_lineup).avatar_list:
player_entity = SceneEntityInfo(
inst_id=0,
entity_id=avatar_id << 20,
motion=player_pos,
actor=SceneActorInfo(
avatar_type=AvatarType.AVATAR_FORMAL_TYPE.value,
base_avatar_id=avatar_id,
uid=session.player.data.uid,
)
)
player_group.entity_list.append(player_entity)
proto.entity_group_list.append(player_group)
session.player.data.entry_id = self.entry_id
session.player.data.plane_id = entrance.PlaneID
session.player.data.floor_id = entrance.FloorID
return proto