class ItemTextHandler(): "A class that loads item text and tracks encountered items" def __init__(self): self._item_text = load_text("common/text/steam/item") self.encountered = OrderedSet() def name_for(self, item_id: int): self.encountered.add(item_id) return self._item_text[item_id * 2] def description_for(self, item_id: int): self.encountered.add(item_id) return self._item_text[item_id * 2 + 1] def text_for(self, item_id: int): self.encountered.add(item_id) return (self._item_text[item_id * 2], self._item_text[item_id * 2 + 1])
def __init__(self): self._item_text = load_text("common/text/steam/item") self.encountered = OrderedSet()
def update_armor(mhdata, item_updater: ItemUpdater): "Populates and updates armor information using the armorset_base as a source of truth" armor_series = load_armor_series() # Get number of times armor can be upgraded by rarity level. # Unk7 is max level pre-augment, Unk8 is max post-augment # Thanks to the MHWorld Modders for the above info rarity_upgrades = {} for entry in load_schema(arm_up.ArmUp, "common/equip/arm_upgrade.arm_up").entries: rarity_upgrades[entry.index + 1] = (entry.unk7 - 1, entry.unk8 - 1) print("Binary armor data loaded") print("Writing list of armorset names (in order) to artifacts") artifacts.write_names_artifact( 'setnames.txt', [s.name['en'] for s in armor_series.values()]) # Will store results. Language lookup and validation will be in english new_armorset_map = DataMap(languages="en", start_id=mhdata.armorset_map.max_id + 1) new_armor_map = DataMap(languages="en", start_id=mhdata.armor_map.max_id + 1) new_armorset_bonus_map = DataMap(languages="en") # Temporary storage for later processes all_set_skill_ids = OrderedSet() item_text_handler = ItemTextHandler() skill_text_handler = SkillTextHandler() armor_data_by_name = {} print( "Updating set data, keyed by the existing names in armorset_base.csv") for armorset_entry in mhdata.armorset_map.values(): armorseries_data = armor_series.get(armorset_entry.name('en')) if not armorseries_data: print( f"Armor series {armorset_entry.name('en')} doesn't exist in binary, skipping" ) new_armorset_map.insert(armorset_entry) continue new_entry = { **armorset_entry, 'name': armorseries_data.name, 'rank': armorseries_data.rank } first_armor = armorseries_data.armors[0].binary if first_armor.set_skill1_lvl > 0: skill_id = first_armor.set_skill1 all_set_skill_ids.add(skill_id) new_entry['bonus'] = skill_text_handler.get_skilltree_name( skill_id)['en'] for part in cfg.armor_parts: armor = armorseries_data.get_part(part) if armor: armor_data_by_name[armor.name['en']] = armor new_entry[part] = armor.name['en'] else: new_entry[part] = None new_armorset_map.insert(new_entry) print("Armorset entries updated") print("Updating armor") for armorset_entry in new_armorset_map.values(): # Handle armor pieces for part, armor_name in datafn.iter_armorset_pieces(armorset_entry): existing_armor = mhdata.armor_map.entry_of('en', armor_name) armor_data = armor_data_by_name.get(armor_name, None) if not armor_data: print( f"Failed to find binary armor data for {armor_name}, maintaining existing data" ) new_armor_map.insert(existing_armor) continue armor_binary = armor_data.binary rarity = armor_binary.rarity + 1 # Initial new armor data new_data = { 'name': armor_data.name, 'rarity': rarity, 'type': part, 'gender': gender_list[armor_binary.gender], 'slot_1': armor_binary.gem_slot1_lvl, 'slot_2': armor_binary.gem_slot2_lvl, 'slot_3': armor_binary.gem_slot3_lvl, 'defense_base': armor_binary.defense, 'defense_max': armor_binary.defense + rarity_upgrades[rarity][0] * 2, 'defense_augment_max': armor_binary.defense + rarity_upgrades[rarity][1] * 2, 'defense_fire': armor_binary.fire_res, 'defense_water': armor_binary.water_res, 'defense_thunder': armor_binary.thunder_res, 'defense_ice': armor_binary.ice_res, 'defense_dragon': armor_binary.dragon_res, 'skills': {}, 'craft': {} } if existing_armor: new_data['id'] = existing_armor.id # Add skills to new armor data for i in range(1, 2 + 1): skill_lvl = getattr(armor_binary, f"skill{i}_lvl") if skill_lvl != 0: skill_id = getattr(armor_binary, f"skill{i}") name_en = skill_text_handler.get_skilltree_name( skill_id)['en'] new_data['skills'][f'skill{i}_name'] = name_en new_data['skills'][f'skill{i}_pts'] = skill_lvl else: new_data['skills'][f'skill{i}_name'] = None new_data['skills'][f'skill{i}_pts'] = None # Add recipe to new armor data. Also track the encounter. recipe_binary = armor_data.recipe new_data['craft'] = convert_recipe(item_text_handler, recipe_binary) # Add new data to new armor map new_armor_map.insert(new_data) # Process set skills. As we don't currently understand the set -> skill map, we only translate # We pull the already established set skill name from existing CSV for bonus_entry in mhdata.armorset_bonus_map.values(): skilltree = skill_text_handler.get_skilltree(bonus_entry.name('en')) name_dict = skill_text_handler.get_skilltree_name(skilltree.index) new_armorset_bonus_map.insert({**bonus_entry, 'name': name_dict}) # Write new data writer = create_writer() writer.save_base_map_csv( "armors/armorset_base.csv", new_armorset_map, schema=schema.ArmorSetSchema(), translation_filename="armors/armorset_base_translations.csv") writer.save_base_map_csv( "armors/armor_base.csv", new_armor_map, schema=schema.ArmorBaseSchema(), translation_filename="armors/armor_base_translations.csv") writer.save_data_csv("armors/armor_skills_ext.csv", new_armor_map, key="skills") writer.save_data_csv("armors/armor_craft_ext.csv", new_armor_map, key="craft") writer.save_base_map_csv( "armors/armorset_bonus_base.csv", new_armorset_bonus_map, schema=schema.ArmorSetBonus(), translation_filename="armors/armorset_bonus_base_translations.csv") print("Armor files updated\n") item_updater.add_missing_items(item_text_handler.encountered)
def update_items(item_updater: ItemUpdater, *, mhdata=None): if not mhdata: mhdata = load_data() print("Existing Data loaded. Using to expand item list") new_item_map = DataMap(languages='en', start_id=mhdata.item_map.max_id + 1) unlinked_item_names = OrderedSet() # used to track dupes to throw proper errors updated_names = set() # First pass. Iterate over existing ingame items and merge with existing data for entry in item_updater.item_data: name_dict, description_dict = item_updater.name_and_description_for( entry.id, track=False) existing_item = mhdata.item_map.entry_of('en', name_dict['en']) is_encountered = entry.id in item_updater.encountered_item_ids if not is_encountered and not existing_item: unlinked_item_names.add(name_dict['en']) continue if name_dict['en'] in updated_names: raise Exception(f"Duplicate item {name_dict['en']}") updated_names.add(name_dict['en']) # note: we omit buy price as items may have a buy price even if not sold. # We only care about the buy price of BUYABLE items new_data = { 'name': name_dict, 'description': description_dict, 'rarity': entry.rarity + 1, 'sell_price': None, 'points': None } is_ez = entry.flags.ez is_account = entry.type == 'endemic' is_tradein = "(Trade-in Item)" in description_dict['en'] is_appraisal = entry.flags.appraisal sell_value = entry.sell_price if entry.sell_price != 0 else None if is_account: new_data['points'] = sell_value else: new_data['sell_price'] = sell_value if name_dict['en'] == 'Normal Ammo 1': new_data['category'] = 'hidden' elif is_ez: new_data['category'] = 'misc' new_data['subcategory'] = 'trade' if is_tradein else 'supply' elif is_account: new_data['category'] = 'misc' new_data['subcategory'] = 'trade' if is_tradein else 'account' elif is_appraisal or ('Appraised after investigation' in description_dict['en']): new_data['category'] = 'misc' new_data['subcategory'] = 'appraisal' new_data['sell_price'] = None # why does this have values? else: new_data['category'] = entry.type new_data['subcategory'] = 'trade' if is_tradein else None # Whether we show carry limit at all is based on item type. # Materials are basically infinite carry infinite_carry = new_data['category'] == 'material' new_data[ 'carry_limit'] = None if infinite_carry else entry.carry_limit if existing_item: new_item_map.insert({**existing_item, **new_data}) else: new_item_map.insert(new_data) # Second pass, add old entries that are not in the new one for old_entry in mhdata.item_map.values(): if old_entry.name('en') not in new_item_map.names('en'): new_item_map.insert(old_entry) # Third pass. Items need to be reordered based on type unsorted_item_map = new_item_map # store reference to former map def filter_category(category, subcategory=None): "helper that returns items and then removes from unsorted item map" results = [] for item in unsorted_item_map.values(): if item['category'] == category and item[ 'subcategory'] == subcategory: results.append(item) for result in results: del unsorted_item_map[result.id] return results normal_ammo_1 = unsorted_item_map.entry_of("en", "Normal Ammo 1") # start the before-mentioned third pass by creating a new map based off the old one new_item_map = DataMap(languages="en") new_item_map.extend(filter_category('item')) new_item_map.extend(filter_category('material')) new_item_map.extend(filter_category('material', 'trade')) if normal_ammo_1: new_item_map.insert(normal_ammo_1) new_item_map.extend(filter_category('ammo')) new_item_map.extend(filter_category('misc', 'appraisal')) new_item_map.extend(filter_category('misc', 'account')) new_item_map.extend(filter_category('misc', 'supply')) # Write out data writer = create_writer() writer.save_base_map_csv( "items/item_base.csv", new_item_map, schema=schema.ItemSchema(), translation_filename="items/item_base_translations.csv", translation_extra=['description']) # Write out artifact data print("Writing unlinked item names to artifacts") artifacts.write_names_artifact('items_unlinked.txt', unlinked_item_names) print("Writing all items and ids") artifact_data = [{ 'id': i.id, 'name': i.name['en'] } for i in item_updater.data] artifacts.write_dicts_artifact('items_ids.csv', artifact_data) print("Item files updated")
def update_armor(): "Populates and updates armor information using the armorset_base as a source of truth" armor_text = load_text("common/text/steam/armor") armorset_text = load_text("common/text/steam/armor_series") # Parses binary armor data, mapped by the english name armor_data = {} for armor_entry in load_schema(am_dat.AmDat, "common/equip/armor.am_dat").entries: if armor_entry.gender == 0: continue if armor_entry.order == 0: continue name_en = armor_text[armor_entry.gmd_name_index]['en'] armor_data[name_en] = armor_entry # Parses craft data, mapped by the binary armor id armor_craft_data = {} for craft_entry in load_schema(eq_crt.EqCrt, "common/equip/armor.eq_crt").entries: armor_craft_data[craft_entry.equip_id] = craft_entry # Get number of times armor can be upgraded by rarity level. # Unk7 is max level pre-augment, Unk8 is max post-augment # Thanks to the MHWorld Modders for the above info rarity_upgrades = {} for entry in load_schema(arm_up.ArmUp, "common/equip/arm_upgrade.arm_up").entries: rarity_upgrades[entry.index + 1] = (entry.unk7 - 1, entry.unk8 - 1) print("Binary data loaded") mhdata = load_data() print( "Existing Data loaded. Using existing armorset data to drive new armor data." ) # Will store results. Language lookup and validation will be in english new_armor_map = DataMap(languages="en") new_armorset_bonus_map = DataMap(languages="en") # Temporary storage for later processes all_set_skill_ids = OrderedSet() item_text_handler = ItemTextHandler() skill_text_handler = SkillTextHandler() print("Populating armor data, keyed by the armorset data") next_armor_id = mhdata.armor_map.max_id + 1 for armorset in mhdata.armorset_map.values(): # Handle armor pieces for part, armor_name in datafn.iter_armorset_pieces(armorset): existing_armor = mhdata.armor_map.entry_of('en', armor_name) armor_binary = armor_data.get(armor_name) if not armor_binary: raise Exception( f"Failed to find binary armor data for {armor_name}") if armor_binary.set_skill1_lvl > 0: all_set_skill_ids.add(armor_binary.set_skill1) rarity = armor_binary.rarity + 1 name_dict = armor_text[armor_binary.gmd_name_index] # Initial new armor data new_data = { 'name': name_dict, # Override for translation support! 'rarity': rarity, 'type': part, 'gender': gender_list[armor_binary.gender], 'slot_1': armor_binary.gem_slot1_lvl, 'slot_2': armor_binary.gem_slot2_lvl, 'slot_3': armor_binary.gem_slot3_lvl, 'defense_base': armor_binary.defense, 'defense_max': armor_binary.defense + rarity_upgrades[rarity][0] * 2, 'defense_augment_max': armor_binary.defense + rarity_upgrades[rarity][1] * 2, 'defense_fire': armor_binary.fire_res, 'defense_water': armor_binary.water_res, 'defense_thunder': armor_binary.thunder_res, 'defense_ice': armor_binary.ice_res, 'defense_dragon': armor_binary.dragon_res, 'skills': {}, 'craft': {} } # Add skills to new armor data for i in range(1, 2 + 1): skill_lvl = getattr(armor_binary, f"skill{i}_lvl") if skill_lvl != 0: skill_id = getattr(armor_binary, f"skill{i}") name_en = skill_text_handler.get_skilltree_name( skill_id)['en'] new_data['skills'][f'skill{i}_name'] = name_en new_data['skills'][f'skill{i}_pts'] = skill_lvl else: new_data['skills'][f'skill{i}_name'] = None new_data['skills'][f'skill{i}_pts'] = None # Add recipe to new armor data. Also track the encounter. recipe_binary = armor_craft_data[armor_binary.id] new_data['craft'] = convert_recipe(item_text_handler, recipe_binary) armor_entry = None if not existing_armor: print( f"Entry for {armor_name} not in armor map, creating new entry" ) armor_entry = new_armor_map.add_entry(next_armor_id, new_data) next_armor_id += 1 else: armor_entry = new_armor_map.add_entry(existing_armor.id, { **existing_armor, **new_data }) # Process set skills. As we don't currently understand the set -> skill map, we only translate # We pull the already established set skill name from existing CSV for bonus_entry in mhdata.armorset_bonus_map.values(): skilltree = skill_text_handler.get_skilltree(bonus_entry.name('en')) name_dict = skill_text_handler.get_skilltree_name(skilltree.index) new_armorset_bonus_map.insert({**bonus_entry, 'name': name_dict}) # Write new data writer = create_writer() writer.save_base_map_csv( "armors/armor_base.csv", new_armor_map, schema=schema.ArmorBaseSchema(), translation_filename="armors/armor_base_translations.csv") writer.save_data_csv("armors/armor_skills_ext.csv", new_armor_map, key="skills") writer.save_data_csv("armors/armor_craft_ext.csv", new_armor_map, key="craft") writer.save_base_map_csv( "armors/armorset_bonus_base.csv", new_armorset_bonus_map, schema=schema.ArmorSetBonus(), translation_filename="armors/armorset_bonus_base_translations.csv") print("Armor files updated\n") add_missing_items(item_text_handler.encountered, mhdata=mhdata)