Support SRTools web

This commit is contained in:
Naruse
2025-04-17 20:13:39 +08:00
parent 2d6462c321
commit 6208880642
12 changed files with 341 additions and 27 deletions

View File

@@ -11,9 +11,9 @@ from pydantic import BaseModel
from rail_proto.lib import ( from rail_proto.lib import (
Equipment, Equipment,
Relic, Relic,
RelicAffix,
BattleRelic, BattleRelic,
RelicAffix RelicAffix,
PlayerSyncScNotify
) )
items_collection = get_collection("items") items_collection = get_collection("items")
@@ -125,6 +125,18 @@ class InventoryData(BaseDatabaseData):
) )
return proto return proto
def RelicSyncProto(self):
proto = PlayerSyncScNotify(
relic_list=[self.ToProto()]
)
return proto
def LightconeSyncProto(self):
proto = PlayerSyncScNotify(
equipment_list=[self.ToProto()]
)
return proto
def save_item(self): def save_item(self):
item_data = self.model_dump() item_data = self.model_dump()
item_data["uid"] = self.uid item_data["uid"] = self.uid

View File

@@ -10,7 +10,7 @@ from rail_proto.lib import (
PlayerSyncScNotify, PlayerSyncScNotify,
AvatarSync AvatarSync
) )
from pymongo import UpdateOne from pymongo import UpdateOne, DeleteMany
from utils.logger import Error from utils.logger import Error
@@ -82,12 +82,14 @@ class PlayerManager(BaseModel):
return None return None
def add_lineup(self,avatar_ids: list[int],index=0,name=""): def add_lineup(self,avatar_ids: list[int],index=0,name=""):
LineupData( lineup = LineupData(
uid=self.data.uid, uid=self.data.uid,
index=index, index=index,
name=name, name=name,
avatar_list=avatar_ids avatar_list=avatar_ids
).add_lineup() ).add_lineup()
if lineup:
self.lineup_manager[lineup.index] = lineup
def add_lightcone(self,lightcone_id: int, rank=1) -> Optional[InventoryData]: def add_lightcone(self,lightcone_id: int, rank=1) -> Optional[InventoryData]:
item = InventoryData( item = InventoryData(
@@ -110,6 +112,25 @@ class PlayerManager(BaseModel):
return item return item
return None return None
async def unequip_items(self,avatar_id):
avatar_data = self.avatar_manager.get(avatar_id)
if not avatar_data:return
item_data = self.inventory_manager.get(avatar_data.lightcone_id)
if not item_data:return
item_data.equip_avatar = 0
match item_data.main_type:
case 1:
avatar_data.lightcone_id = 0
case 2:
avatar_data.relic_ids[str(item_data.sub_type)] = 0
return
async def remove_items(self,count,unique_id):
item_data = self.inventory_manager.get(unique_id)
if not item_data:return
if count == 0: self.inventory_manager.pop(unique_id)
return
def PlayerSyncProto(self) -> PlayerSyncScNotify: def PlayerSyncProto(self) -> PlayerSyncScNotify:
avatars = [] avatars = []
@@ -135,6 +156,33 @@ class PlayerManager(BaseModel):
basic_info=self.data.ToProto() basic_info=self.data.ToProto()
) )
def SrToolAvatarSync(self):
avatars = []
for avatar_id, avatar in self.avatar_manager.items():
avatars.append(avatar.ToProto())
return PlayerSyncScNotify(
avatar_sync=
AvatarSync(
avatar_list=avatars
)
)
def SrToolItemsSync(self):
equipment = []
for unique_id, item in self.inventory_manager.items():
if item.main_type == 1:
equipment.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=equipment,
relic_list=relics
)
def save_player_data_bulk(self): def save_player_data_bulk(self):
try: try:
player_data = self.data.model_dump() player_data = self.data.model_dump()
@@ -168,11 +216,20 @@ class PlayerManager(BaseModel):
def save_all_items_bulk(self): def save_all_items_bulk(self):
operations = [] operations = []
current_unique_ids = set()
for item in self.inventory_manager.values(): for item in self.inventory_manager.values():
item_data = item.model_dump() item_data = item.model_dump()
item_data["uid"] = item.uid item_data["uid"] = item.uid
query = {"uid": item.uid, "unique_id": item.unique_id} query = {"uid": item.uid, "unique_id": item.unique_id}
operations.append(UpdateOne(query, {"$set": item_data}, upsert=True)) operations.append(UpdateOne(query, {"$set": item_data}, upsert=True))
current_unique_ids.add(item.unique_id)
delete_query = {
"unique_id": {"$nin": list(current_unique_ids)},
"uid": self.data.uid
}
operations.append(DeleteMany(delete_query))
if operations: if operations:
items_collection.bulk_write(operations) items_collection.bulk_write(operations)

View File

@@ -18,21 +18,17 @@ async def handle(session: PlayerSession, msg: DressAvatarCsReq) -> betterproto.M
target_equipment = session.player.inventory_manager.get(target_avatar.lightcone_id) target_equipment = session.player.inventory_manager.get(target_avatar.lightcone_id)
target_avatar.lightcone_id, previous_avatar.lightcone_id = previous_avatar.lightcone_id, target_avatar.lightcone_id target_avatar.lightcone_id, previous_avatar.lightcone_id = previous_avatar.lightcone_id, target_avatar.lightcone_id
target_equipment.equip_avatar, equipment.equip_avatar = previous_avatar.avatar_id, target_avatar.avatar_id target_equipment.equip_avatar, equipment.equip_avatar = previous_avatar.avatar_id, target_avatar.avatar_id
#target_equipment.save_item()
else: else:
previous_avatar.lightcone_id = 0 previous_avatar.lightcone_id = 0
equipment.equip_avatar = target_avatar.avatar_id equipment.equip_avatar = target_avatar.avatar_id
target_avatar.lightcone_id = equipment.unique_id target_avatar.lightcone_id = equipment.unique_id
#previous_avatar.save_avatar()
else: else:
if target_avatar.lightcone_id > 0: if target_avatar.lightcone_id > 0:
previous_equipment = session.player.inventory_manager.get(target_avatar.lightcone_id) previous_equipment = session.player.inventory_manager.get(target_avatar.lightcone_id)
previous_equipment.equip_avatar = 0 previous_equipment.equip_avatar = 0
#previous_equipment.save_item()
equipment.equip_avatar = target_avatar.avatar_id equipment.equip_avatar = target_avatar.avatar_id
target_avatar.lightcone_id = equipment.unique_id target_avatar.lightcone_id = equipment.unique_id
#equipment.save_item()
#target_avatar.save_avatar() session.pending_notify(session.player.PlayerSyncProto())
await session.notify(session.player.PlayerSyncProto()) return DressAvatarScRsp()
return DressAvatarScRsp(retcode=0)

View File

@@ -33,41 +33,30 @@ async def handle(session: PlayerSession, msg: DressRelicAvatarCsReq) -> betterpr
target_avatar.relic_ids[relic_sub_type] = previous_relic.unique_id target_avatar.relic_ids[relic_sub_type] = previous_relic.unique_id
previous_relic.equip_avatar = target_avatar.avatar_id previous_relic.equip_avatar = target_avatar.avatar_id
#current_relic.save_item()
#previous_relic.save_item()
elif previous_relic_id > 0: elif previous_relic_id > 0:
previous_relic = session.player.inventory_manager.get(previous_relic_id) previous_relic = session.player.inventory_manager.get(previous_relic_id)
if previous_relic: if previous_relic:
previous_relic.equip_avatar = 0 previous_relic.equip_avatar = 0
#previous_relic.save_item()
previous_avatar.relic_ids[relic_sub_type] = 0 previous_avatar.relic_ids[relic_sub_type] = 0
elif current_relic_id > 0: elif current_relic_id > 0:
current_relic = session.player.inventory_manager.get(current_relic_id) current_relic = session.player.inventory_manager.get(current_relic_id)
if current_relic: if current_relic:
current_relic.equip_avatar = 0 current_relic.equip_avatar = 0
#current_relic.save_item()
target_avatar.relic_ids[relic_sub_type] = 0 target_avatar.relic_ids[relic_sub_type] = 0
target_avatar.relic_ids[relic_sub_type] = relic.unique_id target_avatar.relic_ids[relic_sub_type] = relic.unique_id
relic.equip_avatar = target_avatar.avatar_id relic.equip_avatar = target_avatar.avatar_id
#relic.save_item()
#previous_avatar.save_avatar()
else: else:
if current_relic_id > 0: if current_relic_id > 0:
current_relic = session.player.inventory_manager.get(current_relic_id) current_relic = session.player.inventory_manager.get(current_relic_id)
if current_relic: if current_relic:
current_relic.equip_avatar = 0 current_relic.equip_avatar = 0
#current_relic.save_item()
target_avatar.relic_ids[relic_sub_type] = relic.unique_id target_avatar.relic_ids[relic_sub_type] = relic.unique_id
relic.equip_avatar = target_avatar.avatar_id relic.equip_avatar = target_avatar.avatar_id
#relic.save_item()
#target_avatar.save_avatar() session.pending_notify(session.player.PlayerSyncProto())
await session.notify(session.player.PlayerSyncProto()) return DressRelicAvatarScRsp()
return DressRelicAvatarScRsp(retcode=0)

View File

@@ -0,0 +1,33 @@
import betterproto
from game_server.net.session import PlayerSession
from rail_proto.lib import (
GetBigDataAllRecommendCsReq,
GetBigDataAllRecommendScRsp,
PIIIPHEFDJO,
MKJALMKMPGL,
OFNGPLJKLOJ,
KNNFPFKCABE
)
async def handle(session: PlayerSession, msg: GetBigDataAllRecommendCsReq) -> betterproto.Message:
unk1 = PIIIPHEFDJO()
unk2 = MKJALMKMPGL()
list1 = []
for _,avatar_data in session.player.avatar_manager.items():
id = avatar_data.avatar_id
list1.append(id)
unk2unk2 = OFNGPLJKLOJ()
unk2unk2.avatar_id = id
unk2.bfdmginboib.append(unk2unk2)
unk1.apfecoopnkn.append(
KNNFPFKCABE(
avatar_id_list=list1
)
)
return GetBigDataAllRecommendScRsp(
big_data_recommend_type=msg.big_data_recommend_type,
dklbnbdpmpo=unk1,
pfopjpjkklk=unk2,
)

View File

@@ -0,0 +1,13 @@
import betterproto
from game_server.net.session import PlayerSession
from rail_proto.lib import (
GetBigDataRecommendCsReq,
GetBigDataRecommendScRsp
)
async def handle(session: PlayerSession, msg: GetBigDataRecommendCsReq) -> betterproto.Message:
return GetBigDataRecommendScRsp(
big_data_recommend_type=msg.big_data_recommend_type,
has_recommand=True,
equip_avatar=msg.equip_avatar
)

View File

@@ -1,4 +1,6 @@
import asyncio import asyncio
from threading import Thread
from game_server.srtools.server import run_flask
from game_server.net.gateway import KCPGateway from game_server.net.gateway import KCPGateway
from utils.config import Config from utils.config import Config
from game_server.resource import ResourceManager from game_server.resource import ResourceManager
@@ -6,7 +8,7 @@ from game_server.resource import ResourceManager
def fn_main(): def fn_main():
try: try:
ResourceManager.instance().load_resources() ResourceManager.instance().load_resources()
Thread(target=run_flask, args=(Config.SRToolsServer.IP, Config.SRToolsServer.Port), daemon=True).start()
asyncio.run(KCPGateway.new(Config.GameServer.IP, Config.GameServer.Port)) asyncio.run(KCPGateway.new(Config.GameServer.IP, Config.GameServer.Port))
except Exception as e: except Exception as e:
print(f"Error: {e}") print(f"Error: {e}")

View File

@@ -5,6 +5,7 @@ from game_server.net.packet import NetOperation
from game_server.net.session import PlayerSession from game_server.net.session import PlayerSession
from utils.logger import Info,Warn,Debug from utils.logger import Info,Warn,Debug
from database.mongodb import get_database from database.mongodb import get_database
from game_server.srtools.gateway import set_gateway_instance
class KCPGateway(asyncio.DatagramProtocol): class KCPGateway(asyncio.DatagramProtocol):
@@ -17,6 +18,12 @@ class KCPGateway(asyncio.DatagramProtocol):
self.timeout_check_interval = 5 self.timeout_check_interval = 5
self.save_duration = 60 self.save_duration = 60
def get_active_connection(self,uid) -> PlayerSession:
for key, session in self.sessions.items():
if session.player.data.uid == uid:
return session
return None
async def check_sessions_timeout(self): async def check_sessions_timeout(self):
while self.running: while self.running:
for conv_id, session in list(self.sessions.items()): for conv_id, session in list(self.sessions.items()):
@@ -134,6 +141,8 @@ class KCPGateway(asyncio.DatagramProtocol):
lambda: KCPGateway(db), local_addr=(host, port) lambda: KCPGateway(db), local_addr=(host, port)
) )
set_gateway_instance(protocol)
try: try:
await protocol.shutdown_event.wait() await protocol.shutdown_event.wait()
except asyncio.CancelledError: except asyncio.CancelledError:

View File

@@ -0,0 +1,8 @@
gateway_instance = None
def set_gateway_instance(instance):
global gateway_instance
gateway_instance = instance
def get_gateway_instance():
return gateway_instance

View File

@@ -0,0 +1,74 @@
from pydantic import BaseModel
from typing import Optional
class SRToolData(BaseModel):
class RelicSubAffixData(BaseModel):
sub_affix_id: int
count: int
step: int
class RelicData(BaseModel):
level: int
relic_id: int
relic_set_id: int
main_affix_id: int
sub_affixes: Optional[list["SRToolData.RelicSubAffixData"]] = None
internal_uid: int
equip_avatar: int
class LightconeData(BaseModel):
level: int
internal_uid: int
equip_avatar: int
item_id: int
rank: int
promotion: int
class AvatarData(BaseModel):
avatar_id: int
data: Optional["SRToolData.AvatarInnerData"] = None
level: int
promotion: int
techniques: Optional[list[int]] = []
sp_value: int
sp_max: int
class AvatarInnerData(BaseModel):
rank: int
skills: dict[str,int]
class DynamicKey(BaseModel):
key: Optional[str] = ""
value: int
class BlessingData(BaseModel):
level: int
id: int
dynamic_key: Optional["SRToolData.DynamicKey"] = None
class MonsterData(BaseModel):
level: int
monster_id: int
amount: int
class BattleConfigData(BaseModel):
battle_type: Optional[str] = ""
blessings: Optional[list["SRToolData.BlessingData"]] = None
custom_stats: Optional[list["SRToolData.RelicSubAffixData"]] = None
cycle_count: int
monsters: Optional[list["SRToolData.MonsterData"]] = None
path_resonance_id: int
stage_id: int
relics: Optional[list[RelicData]] = None
lightcones: Optional[list[LightconeData]] = None
avatars: Optional[dict[int,AvatarData]] = {}
class SRToolDataReq(BaseModel):
data: Optional[SRToolData] = None
username: Optional[str] = ""
password: Optional[str] = ""
class SRToolDataRsp(BaseModel):
status: int
message: Optional[str] = ""

View File

@@ -0,0 +1,119 @@
import logging
from flask import Flask, request, jsonify
from flask_cors import CORS
import traceback
import asyncio
from database.inventory.inventory_data import SubAffix
from game_server.game.player.player_manager import PlayerManager
from game_server.net.session import PlayerSession
from game_server.srtools.models.srtools_data import SRToolDataRsp,SRToolDataReq,SRToolData
from database.account.account_data import find_account_by_name
from utils.logger import Info
from game_server.srtools.gateway import get_gateway_instance
flask_app = Flask(__name__)
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
CORS(flask_app, resources={r"/*": {"origins": "https://srtools.pages.dev"}})
async def ClearInventory(conn: PlayerSession):
# un equip all lightcones and relic after that remove it
for _, items in list(conn.player.inventory_manager.items()):
if items.equip_avatar != 0 and items.main_type in (1, 2):
await conn.player.unequip_items(items.equip_avatar)
if items.main_type == 1:
await conn.notify(items.LightconeSyncProto())
if items.main_type == 2:
await conn.notify(items.RelicSyncProto())
await conn.player.remove_items(0, items.unique_id)
async def AddAllData(player: PlayerManager, data: SRToolData):
for key, avatar_data in data.avatars.items():
avatar = player.avatar_manager.get(avatar_data.avatar_id)
if avatar_data.avatar_id >= 8000:continue
if not avatar:
avatar = player.add_avatar(avatar_data.avatar_id)
if avatar:
player.avatar_manager[avatar_data.avatar_id] = avatar
avatar.level = avatar_data.level
avatar.promotion = avatar_data.promotion
avatar.rank = avatar_data.data.rank if avatar_data.data else 0
avatar.skills = avatar_data.data.skills
for lightcone in data.lightcones:
if lightcone.equip_avatar > 8000:continue
lightcone_data = player.add_lightcone(
lightcone.item_id,
lightcone.rank
)
if lightcone_data:
if lightcone.equip_avatar > 0:
player.avatar_manager[lightcone.equip_avatar].lightcone_id = lightcone_data.unique_id
lightcone_data.equip_avatar = lightcone.equip_avatar
player.inventory_manager[lightcone_data.unique_id] = lightcone_data
for relic in data.relics:
if relic.equip_avatar > 8000:continue
relic_data = player.add_relic(
relic_id=relic.relic_id,
main_affix=relic.main_affix_id,
sub_affix=[
SubAffix(
id=affix.sub_affix_id,
count=affix.count,
step=affix.step
)
for affix in relic.sub_affixes
]
)
if relic_data:
if relic.equip_avatar > 0:
player.avatar_manager[relic.equip_avatar].relic_ids[str(relic_data.sub_type)] = relic_data.unique_id
relic_data.equip_avatar = relic.equip_avatar
player.inventory_manager[relic_data.unique_id] = relic_data
@flask_app.route("/srtools", methods=["POST"])
async def srtools_handler():
body = request.get_json()
try:
req = SRToolDataReq(**body)
except Exception:
return jsonify({"message": "Invalid input", "status": 400}), 400
rsp = SRToolDataRsp(message="OK", status=200)
account = find_account_by_name(req.username)
if not account:
rsp.message = "Invalid account"
rsp.status = 400
return jsonify(rsp.model_dump()), 401
if req.data is None:
return jsonify(rsp.model_dump()), 200
gateway = get_gateway_instance()
conn : PlayerSession = gateway.get_active_connection(account.id)
if not conn:
rsp.message = "Player Not Online"
rsp.status = 401
return jsonify(rsp.model_dump()), 401
try:
await ClearInventory(conn)
await AddAllData(conn.player,req.data)
conn.pending_notify(conn.player.SrToolItemsSync())
conn.pending_notify(conn.player.SrToolAvatarSync())
except:
rsp.message = "Internal Server Error"
rsp.status = 500
print(traceback.print_exc())
return jsonify(rsp.model_dump()), 500
return jsonify(rsp.model_dump()), 200
def run_flask(host, port):
Info(f"SRTools Server Starting on {host}:{port}")
flask_app.run(host=host, port=port)

View File

@@ -15,12 +15,14 @@ class ConfigData:
LogLevel: str LogLevel: str
GameServer: ServerConfig GameServer: ServerConfig
SDKServer: ServerConfig SDKServer: ServerConfig
SRToolsServer: ServerConfig
RegionName: str RegionName: str
def write_default_config(): def write_default_config():
config = ConfigData( config = ConfigData(
LogLevel="INFO", LogLevel="INFO",
GameServer=ServerConfig(IP="127.0.0.1", Port=23301), GameServer=ServerConfig(IP="127.0.0.1", Port=23301),
SDKServer=ServerConfig(IP="127.0.0.1", Port=21000), SDKServer=ServerConfig(IP="127.0.0.1", Port=21000),
SRToolsServer=ServerConfig(IP="127.0.0.1", Port=25000),
RegionName="NeonSR", RegionName="NeonSR",
) )
with open("Config.json", "w") as f: with open("Config.json", "w") as f: