From cf6cfdbd3bb97dbb461a4d7a57a93cccba5b0682 Mon Sep 17 00:00:00 2001 From: Midorica Date: Wed, 31 May 2023 21:58:30 -0400 Subject: [PATCH] adding partial synthetize system for SAO --- titles/sao/base.py | 71 +++++++++++++++++++ titles/sao/handlers/base.py | 137 ++++++++++++++++++++++++++++++++++++ titles/sao/schema/item.py | 37 +++++++++- titles/sao/schema/static.py | 8 +++ 4 files changed, 252 insertions(+), 1 deletion(-) diff --git a/titles/sao/base.py b/titles/sao/base.py index 543f488..ac1b69c 100644 --- a/titles/sao/base.py +++ b/titles/sao/base.py @@ -244,6 +244,77 @@ class SaoBase: resp = SaoCheckProfileCardUsedRewardResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1) return resp.make() + def handle_c816(self, request: Any) -> bytes: # not fully done yet + #custom/synthesize_enhancement_equipment + req = bytes.fromhex(request)[24:] + + req_struct = Struct( + Padding(20), + "ticket_id" / Bytes(1), # needs to be parsed as an int + Padding(1), + "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id + "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string + "origin_user_equipment_id_size" / Rebuild(Int32ub, len_(this.origin_user_equipment_id) * 2), # calculates the length of the origin_user_equipment_id + "origin_user_equipment_id" / PaddedString(this.origin_user_equipment_id_size, "utf_16_le"), # origin_user_equipment_id is a (zero) padded string + Padding(3), + "material_common_reward_user_data_list_length" / Rebuild(Int8ub, len_(this.material_common_reward_user_data_list)), # material_common_reward_user_data_list is a byte, + "material_common_reward_user_data_list" / Array(this.material_common_reward_user_data_list_length, Struct( + "common_reward_type" / Int16ub, # team_no is a byte + "user_common_reward_id_size" / Rebuild(Int32ub, len_(this.user_common_reward_id) * 2), # calculates the length of the user_common_reward_id + "user_common_reward_id" / PaddedString(this.user_common_reward_id_size, "utf_16_le"), # user_common_reward_id is a (zero) padded string + )), + ) + + req_data = req_struct.parse(req) + + user_id = req_data.user_id + synthesize_equipment_data = self.game_data.item.get_user_equipment(req_data.user_id, req_data.origin_user_equipment_id) + + for i in range(0,req_data.material_common_reward_user_data_list_length): + + itemList = self.game_data.static.get_item_id(req_data.material_common_reward_user_data_list[i].user_common_reward_id) + heroList = self.game_data.static.get_hero_id(req_data.material_common_reward_user_data_list[i].user_common_reward_id) + equipmentList = self.game_data.static.get_equipment_id(req_data.material_common_reward_user_data_list[i].user_common_reward_id) + + if itemList: + equipment_exp = 2000 + int(synthesize_equipment_data["enhancement_exp"]) + # Then delete the used item, function for items progression is not done yet... + + if equipmentList: + equipment_data = self.game_data.item.get_user_equipment(req_data.user_id, req_data.material_common_reward_user_data_list[i].user_common_reward_id) + equipment_exp = int(equipment_data["enhancement_exp"]) + int(synthesize_equipment_data["enhancement_exp"]) + self.game_data.item.remove_equipment(req_data.user_id, req_data.material_common_reward_user_data_list[i].user_common_reward_id) + + if heroList: + hero_data = self.game_data.item.get_hero_log(req_data.user_id, req_data.material_common_reward_user_data_list[i].user_common_reward_id) + equipment_exp = int(hero_data["log_exp"]) + int(synthesize_equipment_data["enhancement_exp"]) + self.game_data.item.remove_hero_log(req_data.user_id, req_data.material_common_reward_user_data_list[i].user_common_reward_id) + + self.game_data.item.put_equipment_data(req_data.user_id, int(req_data.origin_user_equipment_id), synthesize_equipment_data["enhancement_value"], equipment_exp, 0, 0, 0) + + profile = self.game_data.profile.get_profile(req_data.user_id) + new_col = int(profile["own_col"]) - 100 + + # Update profile + + self.game_data.profile.put_profile( + req_data.user_id, + profile["user_type"], + profile["nick_name"], + profile["rank_num"], + profile["rank_exp"], + new_col, + profile["own_vp"], + profile["own_yui_medal"], + profile["setting_title_id"] + ) + + # Load the item again to push to the response handler + synthesize_equipment_data = self.game_data.item.get_user_equipment(req_data.user_id, req_data.origin_user_equipment_id) + + resp = SaoSynthesizeEnhancementEquipmentResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, synthesize_equipment_data) + return resp.make() + def handle_c806(self, request: Any) -> bytes: #custom/change_party req = bytes.fromhex(request)[24:] diff --git a/titles/sao/handlers/base.py b/titles/sao/handlers/base.py index 8ed8ba0..4949143 100644 --- a/titles/sao/handlers/base.py +++ b/titles/sao/handlers/base.py @@ -1773,5 +1773,142 @@ class SaoCheckProfileCardUsedRewardResponse(SaoBaseResponse): )) + self.length = len(resp_data) + return super().make() + resp_data + +class SaoSynthesizeEnhancementEquipment(SaoBaseRequest): + def __init__(self, data: bytes) -> None: + super().__init__(data) + +class SaoSynthesizeEnhancementEquipmentResponse(SaoBaseResponse): + def __init__(self, cmd, synthesize_equipment_data) -> None: + super().__init__(cmd) + self.result = 1 + equipment_level = 0 + + # Calculate level based off experience and the CSV list + with open(r'titles/sao/data/EquipmentLevel.csv') as csv_file: + csv_reader = csv.reader(csv_file, delimiter=',') + line_count = 0 + data = [] + rowf = False + for row in csv_reader: + if rowf==False: + rowf=True + else: + data.append(row) + + exp = synthesize_equipment_data[4] + + for e in range(0,len(data)): + if exp>=int(data[e][1]) and exp bytes: + + after_equipment_user_data_struct = Struct( + "user_equipment_id_size" / Int32ub, # big endian + "user_equipment_id" / Int16ul[9], #string + "equipment_id" / Int32ub, #int + "enhancement_value" / Int16ub, #short + "max_enhancement_value_extended_num" / Int16ub, #short + "enhancement_exp" / Int32ub, #int + "possible_awakening_flag" / Int8ul, # result is either 0 or 1 + "awakening_stage" / Int16ub, #short + "awakening_exp" / Int32ub, #int + "property1_property_id" / Int32ub, + "property1_value1" / Int32ub, + "property1_value2" / Int32ub, + "property2_property_id" / Int32ub, + "property2_value1" / Int32ub, + "property2_value2" / Int32ub, + "property3_property_id" / Int32ub, + "property3_value1" / Int32ub, + "property3_value2" / Int32ub, + "property4_property_id" / Int32ub, + "property4_value1" / Int32ub, + "property4_value2" / Int32ub, + "converted_card_num" / Int16ub, + "shop_purchase_flag" / Int8ul, # result is either 0 or 1 + "protect_flag" / Int8ul, # result is either 0 or 1 + "get_date_size" / Int32ub, # big endian + "get_date" / Int16ul[len(self.get_date)], + ) + + # create a resp struct + resp_struct = Struct( + "result" / Int8ul, # result is either 0 or 1 + "after_equipment_user_data_size" / Rebuild(Int32ub, len_(this.after_equipment_user_data)), # big endian + "after_equipment_user_data" / Array(this.after_equipment_user_data_size, after_equipment_user_data_struct), + ) + + resp_data = resp_struct.parse(resp_struct.build(dict( + result=self.result, + after_equipment_user_data_size=0, + after_equipment_user_data=[], + ))) + + synthesize_equipment_data = dict( + user_equipment_id_size=len(self.user_equipment_id) * 2, + user_equipment_id=[ord(x) for x in self.user_equipment_id], + equipment_id=self.equipment_id, + enhancement_value=self.enhancement_value, + max_enhancement_value_extended_num=self.max_enhancement_value_extended_num, + enhancement_exp=self.enhancement_exp, + possible_awakening_flag=self.possible_awakening_flag, + awakening_stage=self.awakening_stage, + awakening_exp=self.awakening_exp, + property1_property_id=self.property1_property_id, + property1_value1=self.property1_value1, + property1_value2=self.property1_value2, + property2_property_id=self.property2_property_id, + property2_value1=self.property2_value1, + property2_value2=self.property2_value2, + property3_property_id=self.property3_property_id, + property3_value1=self.property3_value1, + property3_value2=self.property3_value2, + property4_property_id=self.property4_property_id, + property4_value1=self.property4_value1, + property4_value2=self.property4_value2, + converted_card_num=self.converted_card_num, + shop_purchase_flag=self.shop_purchase_flag, + protect_flag=self.protect_flag, + get_date_size=len(self.get_date) * 2, + get_date=[ord(x) for x in self.get_date], + + ) + + resp_data.after_equipment_user_data.append(synthesize_equipment_data) + + # finally, rebuild the resp_data + resp_data = resp_struct.build(resp_data) + self.length = len(resp_data) return super().make() + resp_data \ No newline at end of file diff --git a/titles/sao/schema/item.py b/titles/sao/schema/item.py index e1cb207..0e3d86c 100644 --- a/titles/sao/schema/item.py +++ b/titles/sao/schema/item.py @@ -192,6 +192,14 @@ class SaoItemData(BaseData): return None return result.lastrowid + + def get_user_equipment(self, user_id: int, equipment_id: int) -> Optional[Dict]: + sql = equipment_data.select(equipment_data.c.user == user_id and equipment_data.c.equipment_id == equipment_id) + + result = self.execute(sql) + if result is None: + return None + return result.fetchone() def get_user_equipments( self, user_id: int @@ -274,4 +282,31 @@ class SaoItemData(BaseData): result = self.execute(sql) if result is None: return None - return result.fetchone() \ No newline at end of file + return result.fetchone() + + def remove_hero_log(self, user_id: int, user_hero_log_id: int) -> None: + sql = hero_log_data.delete( + and_( + hero_log_data.c.user == user_id, + hero_log_data.c.user_hero_log_id == user_hero_log_id, + ) + ) + + result = self.execute(sql) + if result is None: + self.logger.error( + f"{__name__} failed to remove hero log! profile: {user_id}, user_hero_log_id: {user_hero_log_id}" + ) + return None + + def remove_equipment(self, user_id: int, equipment_id: int) -> None: + sql = equipment_data.delete( + and_(equipment_data.c.user == user_id, equipment_data.c.equipment_id == equipment_id) + ) + + result = self.execute(sql) + if result is None: + self.logger.error( + f"{__name__} failed to remove equipment! profile: {user_id}, equipment_id: {equipment_id}" + ) + return None \ No newline at end of file diff --git a/titles/sao/schema/static.py b/titles/sao/schema/static.py index 2635b5f..7323fc8 100644 --- a/titles/sao/schema/static.py +++ b/titles/sao/schema/static.py @@ -281,6 +281,14 @@ class SaoStaticData(BaseData): if result is None: return None return [list[2] for list in result.fetchall()] + + def get_item_id(self, itemId: int) -> Optional[Dict]: + sql = item.select(item.c.itemId == itemId) + + result = self.execute(sql) + if result is None: + return None + return result.fetchone() def get_item_ids(self, version: int, enabled: bool) -> Optional[List[Dict]]: sql = item.select(item.c.version == version and item.c.enabled == enabled).order_by(