├── .gitattributes ├── .gitignore ├── PythonScripts ├── 7day-signin.py ├── _FileUpload.py ├── _SharecfgLookup.py ├── _UpdateAugmentConverter.py ├── _UpdateEquipConverter.py ├── _UpdateEquipWikidata.py ├── _UpdateShipsConverter.py ├── _UpdateSkinsWikidata.py ├── augment.py ├── data │ └── static │ │ ├── item_name_convert.json │ │ ├── settings_default.toml │ │ └── shipid_overrides.json ├── equip.py ├── equipskin.py ├── furniture.py ├── lib │ ├── Constants.py │ ├── Utility.py │ ├── WikiConstants.py │ ├── WikiHelper.py │ ├── __init__.py │ ├── api.py │ ├── apiclasses.py │ ├── apimodules.py │ ├── converter │ │ ├── __init__.py │ │ ├── augments.py │ │ ├── augments_updater.py │ │ ├── equips.py │ │ ├── equips_updater.py │ │ ├── ships.py │ │ ├── ships_updater.py │ │ ├── skins.py │ │ └── skins_updater.py │ ├── settings.py │ └── sharecfgmodules.py ├── map.py ├── milestone.py ├── quotes.py ├── ship.py ├── shop.py ├── skins.py ├── story.py └── templates │ ├── Equipment.json │ ├── EventShop.json │ ├── Juustagram.json │ ├── Map.json │ ├── Mission.json │ ├── MissionDouble.json │ ├── Ship.json │ ├── ShipQuote.json │ ├── ShipQuoteEN.json │ ├── ShipSkin.json │ ├── ShipSkin0.json │ ├── Story.json │ └── Template.json ├── README.md ├── lua_convert.py ├── lua_converter.py ├── lua_converter_v2.py ├── serializer.lua └── serializer2.lua /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .vscode 3 | Src* 4 | -------------------------------------------------------------------------------- /PythonScripts/7day-signin.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser 2 | 3 | from lib import ALJsonAPI, Client, WikiHelper, Utility 4 | from lib.apiclasses import Award, ShipReward 5 | 6 | 7 | def award_to_display(award: Award, wikifier: WikiHelper.Wikifier, client: Client) -> str: 8 | awardable = award.load_first(wikifier.api, client) 9 | wiki_awardable = wikifier.wikify_awardable(awardable) 10 | 11 | awardable_name = wiki_awardable.name 12 | # add link brackets for ship rewards 13 | if isinstance(awardable, ShipReward): 14 | awardable_name = f"[[{awardable_name}]]" 15 | 16 | wiki_display = WikiHelper.simple_template("Display", 17 | [wiki_awardable.filelink, wiki_awardable.rarity.label, f"{award.amount}x "+awardable_name]) 18 | return wiki_display 19 | 20 | def seven_day_signin(loginid: int, wikifier: WikiHelper.Wikifier, client: Client = None): 21 | client = [client] or Client 22 | 23 | activity_7_day_sign = wikifier.api.get_sharecfgmodule("activity_7_day_sign") 24 | signin_data = activity_7_day_sign.load_first(loginid, client) 25 | if not signin_data: 26 | client_names = ', '.join([c.name for c in client]) 27 | raise ValueError(f"loginid {loginid} does not exist for clients '{client_names}'.") 28 | 29 | award_displays = [award_to_display(award, wikifier, client) for award in signin_data.front_drops] 30 | return '\n'.join(award_displays) 31 | 32 | def main(): 33 | parser = ArgumentParser() 34 | parser.add_argument("-c", "--client", choices=Client.__members__, default = "EN", 35 | help="client to gather information from (default: EN)") 36 | parser.add_argument('loginid', metavar='INDEX', type=int, nargs=1, 37 | help='an index from sharecfg/activity_7_day_sign') 38 | args = parser.parse_args() 39 | 40 | client = Client[args.client] 41 | api = ALJsonAPI() 42 | wikifier = WikiHelper.Wikifier(api) 43 | 44 | wikitext = seven_day_signin(args.loginid[0], wikifier, client) 45 | Utility.output(wikitext) 46 | 47 | if __name__ == "__main__": 48 | main() 49 | -------------------------------------------------------------------------------- /PythonScripts/_FileUpload.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from pathlib import Path 3 | from enum import Enum, auto 4 | from mwclient import APIError 5 | 6 | from lib import WikiHelper 7 | 8 | 9 | class UploadResult(Enum): 10 | SUCCESS = auto() 11 | FAILURE = auto() 12 | FAILURE_SAME = auto() 13 | WARNING_EXIST = auto() 14 | WARNING_DELETED = auto() 15 | WARNING_SIMILAR_NAME = auto() 16 | 17 | 18 | def do_upload(wikiclient: WikiHelper.WikiClient, *args, **kwargs) -> UploadResult: 19 | try: 20 | result = wikiclient.execute(wikiclient.mwclient.upload, *args, **kwargs) 21 | except APIError as error: 22 | # handle additional duplicate error otherwise raise the exception again 23 | if error.code == 'fileexists-no-change': 24 | print('Is duplicate, skipping.') 25 | return UploadResult.FAILURE_SAME 26 | raise error 27 | 28 | # handle other return type 29 | if 'upload' in result: 30 | result = result['upload'] 31 | 32 | if result: 33 | if result['result'] == 'Success': 34 | print('Done.') 35 | return UploadResult.SUCCESS 36 | 37 | if result['result'] == 'Warning': 38 | if 'exists' in result['warnings']: 39 | if not (('no-change' in result['warnings']) or ('duplicate-version' in result['warnings'])): 40 | return UploadResult.WARNING_EXIST 41 | print("Warning (exists):") 42 | print(result) 43 | elif 'was-deleted' in result['warnings']: 44 | print("Waning: File was previously deleted.") 45 | i = input("Retry uploading? (y/n): ") 46 | if i.lower() == 'y': 47 | return UploadResult.WARNING_DELETED 48 | elif 'page-exists' in result['warnings']: 49 | print("Waning: File page already exists, but has no file.") 50 | i = input("Retry uploading? (y/n): ") 51 | if i.lower() == 'y': 52 | return UploadResult.WARNING_EXIST 53 | elif 'exists-normalized' in result['warnings']: 54 | return UploadResult.WARNING_SIMILAR_NAME 55 | else: 56 | print("Warning:") 57 | print(result) 58 | 59 | else: 60 | print('Failed with Unknown Error:') 61 | print(result) 62 | 63 | else: 64 | print('Failed to upload.') 65 | return UploadResult.FAILURE 66 | 67 | def upload_file(wikiclient: WikiHelper.WikiClient, filepath: Path): 68 | print(f'Uploading {filepath.name}... ', end='') 69 | result = do_upload(wikiclient, open(filepath, 'rb'), filename=filepath.name) 70 | while True: 71 | if result in (UploadResult.SUCCESS, UploadResult.FAILURE, UploadResult.FAILURE_SAME): 72 | break 73 | if result in (UploadResult.WARNING_EXIST, UploadResult.WARNING_DELETED, UploadResult.WARNING_SIMILAR_NAME): 74 | result = do_upload(wikiclient, open(filepath, 'rb'), filename=filepath.name, ignore=True) 75 | 76 | 77 | def main(): 78 | wikiclient = WikiHelper.WikiClient().login() 79 | args = sys.argv[1:] 80 | 81 | for arg in args: 82 | argpath = Path(arg) 83 | if not argpath.exists(): 84 | print(f"Input File {arg} does not exist.") 85 | continue 86 | 87 | if argpath.is_dir(): 88 | for fp in argpath.rglob("*"): 89 | upload_file(wikiclient, fp) 90 | else: 91 | upload_file(wikiclient, argpath) 92 | 93 | if __name__ == "__main__": 94 | main() 95 | -------------------------------------------------------------------------------- /PythonScripts/_SharecfgLookup.py: -------------------------------------------------------------------------------- 1 | from lib import Client, ALJsonAPI 2 | 3 | 4 | def main(): 5 | api = ALJsonAPI() 6 | 7 | while True: 8 | module = None 9 | modulename = input("Module: ") 10 | 11 | try: 12 | module = api.get_sharecfgmodule(modulename) 13 | except: pass 14 | if not module: 15 | try: 16 | module = api.get_apimodule(modulename) 17 | except: pass 18 | 19 | if module: break 20 | else: print("FAILED: Module does not exist.") 21 | 22 | while True: 23 | dataid = input("ID: ") 24 | client = input("Client: ") 25 | 26 | try: 27 | if client: 28 | client = Client[client] 29 | print(module.load_client(dataid, client)._json) 30 | else: 31 | print(module.load_first(dataid, Client)._json) 32 | except: 33 | print("FAILED: Maybe the module does not have this id?") 34 | 35 | if __name__ == "__main__": 36 | main() 37 | -------------------------------------------------------------------------------- /PythonScripts/_UpdateAugmentConverter.py: -------------------------------------------------------------------------------- 1 | from lib import ALJsonAPI, Constants 2 | from lib.converter import augments_updater 3 | 4 | 5 | def main(): 6 | api = ALJsonAPI() 7 | augmentcache_fp = Constants.AUGMENT_CONVERT_CACHE_PATH 8 | augments_updater.update_converter(augmentcache_fp, api) 9 | 10 | if __name__ == "__main__": 11 | main() 12 | -------------------------------------------------------------------------------- /PythonScripts/_UpdateEquipConverter.py: -------------------------------------------------------------------------------- 1 | from lib import ALJsonAPI, Constants 2 | from lib.converter import equips_updater 3 | 4 | 5 | def main(): 6 | api = ALJsonAPI() 7 | equipcache_fp = Constants.EQUIP_CONVERT_CACHE_PATH 8 | wikicache_fp = Constants.EQUIP_WIKIDATA_PATH 9 | equips_updater.update_converter(equipcache_fp, wikicache_fp, api) 10 | 11 | if __name__ == "__main__": 12 | main() 13 | -------------------------------------------------------------------------------- /PythonScripts/_UpdateEquipWikidata.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from lib import Constants 4 | from lib.WikiHelper import WikiClient 5 | 6 | 7 | def request_equips(wikiclient: WikiClient, offset=0): 8 | result = wikiclient.execute(wikiclient.mwclient.api, 9 | action="cargoquery", 10 | format="json", 11 | limit=500, 12 | tables="equipment", 13 | fields="equipment.Name,equipment.BaseID", 14 | where="equipment.Type != 'Augment Module'", 15 | offset=offset) 16 | return result['cargoquery'] 17 | 18 | def download_equip_data(): 19 | wikiclient = WikiClient().login() 20 | all_entires = [] 21 | offset = 0 22 | while True: 23 | entries = request_equips(wikiclient, offset) 24 | all_entires += entries 25 | if len(entries) == 500: 26 | offset += 500 27 | else: 28 | return all_entires 29 | 30 | 31 | def main(): 32 | cache_fp = Constants.EQUIP_WIKIDATA_PATH 33 | equipdata = download_equip_data() 34 | with open(cache_fp, "w", encoding="utf8") as file: 35 | json.dump(equipdata, file) 36 | 37 | if __name__ == "__main__": 38 | main() 39 | -------------------------------------------------------------------------------- /PythonScripts/_UpdateShipsConverter.py: -------------------------------------------------------------------------------- 1 | from lib import ALJsonAPI, Constants 2 | from lib.converter import ships_updater 3 | 4 | 5 | def main(): 6 | api = ALJsonAPI() 7 | convert_fp = Constants.SHIPID_CONVERT_CACHE_PATH 8 | override_fp = Constants.SHIPID_CONVERT_OVERRIDE_PATH 9 | ships_updater.update_converter(convert_fp, override_fp, api) 10 | 11 | if __name__ == "__main__": 12 | main() 13 | -------------------------------------------------------------------------------- /PythonScripts/_UpdateSkinsWikidata.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from lib import Constants 4 | from lib.WikiHelper import WikiClient 5 | 6 | 7 | def request_skins(wikiclient: WikiClient, offset=0): 8 | result = wikiclient.execute(wikiclient.mwclient.api, 9 | action="cargoquery", 10 | format="json", 11 | limit=500, 12 | tables="ship_skins", 13 | fields="ship_skins.ShipName,ship_skins.SkinID,ship_skins.SkinCategory", 14 | order_by='"cargo__ship_skins"."ShipName","cargo__ship_skins"."SkinID"', 15 | offset=offset) 16 | return result['cargoquery'] 17 | 18 | def download_skin_data(): 19 | wikiclient = WikiClient().login() 20 | all_entires = [] 21 | offset = 0 22 | while True: 23 | entries = request_skins(wikiclient, offset) 24 | all_entires += entries 25 | if len(entries) == 500: 26 | offset += 500 27 | else: 28 | return all_entires 29 | 30 | 31 | def main(): 32 | cache_fp = Constants.SKIN_WIKIDATA_PATH 33 | equipdata = download_skin_data() 34 | with open(cache_fp, "w", encoding="utf8") as file: 35 | json.dump(equipdata, file) 36 | 37 | if __name__ == "__main__": 38 | main() 39 | -------------------------------------------------------------------------------- /PythonScripts/augment.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser 2 | from difflib import get_close_matches 3 | from collections.abc import Iterable 4 | 5 | from lib import ALJsonAPI, Client, WikiHelper, Utility 6 | from lib.Constants import ShipType as ship_types 7 | 8 | 9 | attributes = { 10 | 'durability': ['Health', 'HealthMax', 'HealthOPS'], 11 | 'cannon': ['Firepower', 'FPMax', 'FPOPS'], 12 | 'torpedo': ['Torpedo', 'TorpMax', 'TorpOPS'], 13 | 'antiaircraft': ['AA', 'AAMax', 'AAOPS'], 14 | 'air': ['Aviation', 'AvMax', 'AvOPS'], 15 | 'reload': ['Reload', 'ReloadMax', 'ReloadOPS'], 16 | 'hit': ['Acc', 'AccMax', 'AccOPS'], 17 | 'dodge': ['Evasion', 'EvasionMax', 'EvasionOPS'], 18 | 'speed': ['Spd', 'SpdMax', 'SpdOPS'], 19 | 'luck': ['Luck', 'LuckMax', 'LuckOPS'], 20 | 'antisub': ['ASW', 'ASWMax', 'ASWOPS'], 21 | 'oxygen': ['Oxygen', 'OxygenMax', 'OxygenOPS'] 22 | } 23 | 24 | 25 | 26 | def getGameData(augment, api: ALJsonAPI, clients: Iterable[Client]): 27 | augment_data_statistics = api.get_sharecfgmodule("spweapon_data_statistics") 28 | augment_data = {} 29 | if type(augment) != int: 30 | augment_id = augment.id 31 | augment_data['Name'] = augment.wikiname 32 | augment_data['Image'] = augment.icon 33 | else: 34 | augment_stats = augment_data_statistics.load_first(augment, clients) 35 | augment_id = augment 36 | augment_data['Name'] = augment_stats.name 37 | augment_data['Image'] = "Augment " + augment_stats.icon 38 | augment_data['BaseID'] = augment_id 39 | 40 | for client in [Client.CN, Client.JP]: 41 | if augment0 := augment_data_statistics.load_client(f"{augment_id}", client): 42 | augment_data[client.name+'Name'] = api.replace_namecode(augment0.name, client) 43 | 44 | augment_stats = augment_data_statistics.load_first(augment_id, clients) 45 | augment_stats_up = augment_data_statistics.load_first(augment_id+10, clients) 46 | if not augment_stats: 47 | raise KeyError(f"Augment ID not found: {augment_id}") 48 | augment_data['Type'] = "Augment Module" 49 | augment_data['Stars'] = augment_stats.get('rarity') + 1 50 | augment_data['Nationality'] = 'Universal' 51 | augment_data['Tech'] = f"T{augment_stats.tech}" 52 | 53 | 54 | i = 1 55 | while stat := augment_stats.get(f"attribute_{i}"): 56 | augment_data[attributes[stat][0]] = f"{augment_stats.get(f'value_{i}')} + {augment_stats.get(f'value_{i}_random')}" 57 | if augment_stats_up.get(f'value_{i}'): 58 | augment_data[attributes[stat][1]] = f"{augment_stats_up.get(f'value_{i}')} + {augment_stats.get(f'value_{i}_random')}" 59 | i += 1 60 | 61 | 62 | if not (usability := augment_stats.usability): 63 | aug_type_data = api.get_sharecfgmodule('spweapon_type') 64 | aug_type = aug_type_data.load_first(augment_stats.type,clients) 65 | #usability = [1,2,3,4,5,6,7,8,10,12,13,17,18,19,22] 66 | usability = aug_type.ship_type 67 | for i in usability: 68 | ship_type = ship_types.from_id(i) 69 | if unique_ship := augment_stats.get('unique'): 70 | augment_data[ship_type.templatename] = 2 71 | augment_data[ship_type.templatename + 'Note'] = api.ship_converter.get_shipname(unique_ship) + ' only' 72 | else: 73 | augment_data[ship_type.templatename] = 1 74 | 75 | augment_data['DropLocation'] = '[[Augmentation]]' 76 | 77 | 78 | 79 | 80 | skill_data_template = api.get_sharecfgmodule("skill_data_template") 81 | skill_desc_template = api.get_sharecfgmodule("skill_world_display") 82 | 83 | 84 | skill_id = augment_stats.effect_id 85 | skill_data = skill_data_template.load_first(skill_id, clients) 86 | skill_desc = skill_desc_template.load_first(skill_id, clients) 87 | name = skill_data.name 88 | 89 | desc_list = [] 90 | for j in [skill_data,skill_desc]: 91 | if j: 92 | desc = j['desc'] 93 | desc_add = j['desc_add'] 94 | for k, desc_add_item in enumerate(desc_add): 95 | desc = desc.replace('$'+str(k+1), desc_add_item[0][0]+' ('+desc_add_item[skill_data['max_level']-1][0]+')') 96 | desc_list.append(desc.replace('.0%','%')) 97 | effect = "'''{}''': {}{}".format(name,desc_list[0] if desc_list else "","
[Operation Siren only] "+desc_list[1] if len(desc_list)>1 else "") 98 | 99 | augment_data['Notes'] = effect 100 | 101 | 102 | if augment_stats.get('skill_upgrade'): 103 | skill_up = augment_stats_up.skill_upgrade[0] 104 | old_skill_data = skill_data_template.load_first(skill_up[0], clients) 105 | new_skill_data = skill_data_template.load_first(skill_up[1], clients) 106 | new_skill_desc = skill_desc_template.load_first(skill_up[1], clients) 107 | old_name = old_skill_data.name 108 | new_name = new_skill_data.name 109 | 110 | desc_list = [] 111 | for j in [new_skill_data,new_skill_desc]: 112 | if j: 113 | desc = j['desc'] 114 | desc_add = j['desc_add'] 115 | for k, desc_add_item in enumerate(desc_add): 116 | desc = desc.replace('$'+str(k+1), desc_add_item[0][0]+' ('+desc_add_item[new_skill_data['max_level']-1][0]+')') 117 | desc_list.append(desc.replace('.0%','%')) 118 | effect = "

''Replaces {} at +10 with {}''
{}{}".format(old_name,new_name,desc_list[0] if desc_list else "","
[Operation Siren only] "+desc_list[1] if len(desc_list)>1 else "") 119 | 120 | augment_data['Notes'] += effect 121 | 122 | 123 | return augment_data 124 | 125 | 126 | 127 | 128 | 129 | 130 | def main(): 131 | parser = ArgumentParser() 132 | parser.add_argument("-c", "--clients", choices=Client.__members__, default = ['EN'], nargs = '+', 133 | help="clients to gather information from (default: EN)") 134 | group = parser.add_mutually_exclusive_group() 135 | group.add_argument("-n", "--name", action="store_true",help="interprets arg as name/id of the Augment to get info for") 136 | group.add_argument("-s", "--ship", action="store_true",help="interprets arg as name/id of a ship") 137 | parser.add_argument("arg",help="argument to get info for") 138 | args = parser.parse_args() 139 | 140 | clients = [ Client[c] for c in args.clients ] 141 | api = ALJsonAPI() 142 | if args.name: 143 | augment = api.augment_converter.from_wikiname(args.arg) 144 | if not augment: 145 | if args.arg.isdigit(): 146 | augment_id = int(args.arg) 147 | augment = api.augment_converter.from_augmentid(augment_id) 148 | if not augment: 149 | print("Warning: ID not found in wiki data, attempting to get data from json!") 150 | augment = augment_id 151 | else: 152 | if m := get_close_matches(args.arg, api.augment_converter.wikiname_to_data.keys(), 1): 153 | print(f'"{args.arg}" is not a valid Augment name, assuming you meant "{m[0]}"!') 154 | augment = api.augment_converter.from_wikiname(m[0]) 155 | if not augment: 156 | raise Exception('Augment cannot be found! (Something major must be wrong with the Augment Converter)') 157 | else: 158 | e = f'"{args.arg}" is not a valid Augment name.' 159 | raise ValueError(e) 160 | elif args.ship: 161 | if type(args.arg) != str: 162 | try: 163 | groupid = int(args.arg) 164 | except: 165 | raise TypeError(f'Expected string or int, got {type(args.arg)} at {args.arg}') 166 | else: groupid = api.ship_converter.get_groupid(args.arg) 167 | if not groupid: 168 | if args.arg.isdigit(): 169 | groupid = int(args.arg) 170 | else: 171 | if m := get_close_matches(args.arg, api.ship_converter.ship_to_id.keys(), 1): 172 | e = f'"{args.arg}" is not a valid ship name, did you mean {m[0]}?' 173 | else: 174 | e = f'"{args.arg}" is not a valid ship name.' 175 | raise ValueError(e) 176 | augment = api.augment_converter.from_shipid(groupid) 177 | if not augment: print(f"Ship {args.arg} has no unique augment") 178 | template_data_game = getGameData(augment, api, clients) 179 | equip_template = WikiHelper.MultilineTemplate("Equipment") 180 | wikitext = equip_template.fill(template_data_game) 181 | Utility.output(wikitext) 182 | #print(equip.id) 183 | 184 | if __name__ == "__main__": 185 | main() 186 | -------------------------------------------------------------------------------- /PythonScripts/data/static/item_name_convert.json: -------------------------------------------------------------------------------- 1 | { 2 | "T1 Royal Tech Pack": "T1Box", 3 | "T2 Royal Tech Pack": "T2Box", 4 | "T3 Royal Tech Pack": "T3Box", 5 | "T4 Royal Tech Pack": "T4Box", 6 | "T5 Royal Tech Pack": "T5Box", 7 | "T1 Eagle Tech Pack": "T1Box", 8 | "T2 Eagle Tech Pack": "T2Box", 9 | "T3 Eagle Tech Pack": "T3Box", 10 | "T4 Eagle Tech Pack": "T4Box", 11 | "T5 Eagle Tech Pack": "T5Box", 12 | "T1 Sakura Tech Pack": "T1Box", 13 | "T2 Sakura Tech Pack": "T2Box", 14 | "T3 Sakura Tech Pack": "T3Box", 15 | "T4 Sakura Tech Pack": "T4Box", 16 | "T5 Sakura Tech Pack": "T5Box", 17 | "T1 Ironblood Tech Pack": "T1Box", 18 | "T2 Ironblood Tech Pack": "T2Box", 19 | "T3 Ironblood Tech Pack": "T3Box", 20 | "T4 Ironblood Tech Pack": "T4Box", 21 | "T5 Ironblood Tech Pack": "T5Box", 22 | "Random T1 Tech Pack": "T1Box", 23 | "Random T2 Tech Pack": "T2Box", 24 | "Random T3 Tech Pack": "T3Box", 25 | "Random T4 Tech Pack": "T4Box", 26 | "Random T5 Tech Pack": "T5Box", 27 | 28 | "T1 Random Skill Book": "UnknownT1Book", 29 | "T2 Random Skill Book": "UnknownT2Book", 30 | "T3 Random Skill Book": "UnknownT3Book", 31 | "T1 Offensive Skill Book": "OffenseT1TB", 32 | "T2 Offensive Skill Book": "OffenseT2TB", 33 | "T3 Offensive Skill Book": "OffenseT3TB", 34 | "T1 Support Skill Book": "SupportT1TB", 35 | "T2 Support Skill Book": "SupportT2TB", 36 | "T3 Support Skill Book": "SupportT3TB", 37 | "T1 Defensive Skill Book": "DefenseT1TB", 38 | "T2 Defensive Skill Book": "DefenseT2TB", 39 | "T3 Defensive Skill Book": "DefenseT3TB", 40 | 41 | "T1 Random Retrofit Blueprint": "UnknownT1BP", 42 | "T2 Random Retrofit Blueprint": "UnknownT2BP", 43 | "T3 Random Retrofit Blueprint": "UnknownT3BP", 44 | "T1 Destroyer Retrofit Blueprint": "DestroyerT1BP", 45 | "T2 Destroyer Retrofit Blueprint": "DestroyerT2BP", 46 | "T3 Destroyer Retrofit Blueprint": "DestroyerT3BP", 47 | "T1 Cruiser Retrofit Blueprint": "CruiserT1BP", 48 | "T2 Cruiser Retrofit Blueprint": "CruiserT2BP", 49 | "T3 Cruiser Retrofit Blueprint": "CruiserT3BP", 50 | "T1 Battleship Retrofit Blueprint": "BattleshipT1BP", 51 | "T2 Battleship Retrofit Blueprint": "BattleshipT2BP", 52 | "T3 Battleship Retrofit Blueprint": "BattleshipT3BP", 53 | "T1 Carrier Retrofit Blueprint": "CarrierT1BP", 54 | "T2 Carrier Retrofit Blueprint": "CarrierT2BP", 55 | "T3 Carrier Retrofit Blueprint": "CarrierT3BP", 56 | 57 | "T1 Random Gear Part": "UnknownT1Plate", 58 | "T2 Random Gear Part": "UnknownT2Plate", 59 | "T3 Random Gear Part": "UnknownT3Plate", 60 | "T1 General Part": "AuxT1Plate", 61 | "T2 General Part": "AuxT2Plate", 62 | "T3 General Part": "AuxT3Plate", 63 | "T1 Main Gun Part": "GunT1Plate", 64 | "T2 Main Gun Part": "GunT2Plate", 65 | "T3 Main Gun Part": "GunT3Plate", 66 | "T1 Torpedo Part": "TorpT1Plate", 67 | "T2 Torpedo Part": "TorpT2Plate", 68 | "T3 Torpedo Part": "TorpT3Plate", 69 | "T1 Anti-Air Gun Part": "AAT1Plate", 70 | "T2 Anti-Air Gun Part": "AAT2Plate", 71 | "T3 Anti-Air Gun Part": "AAT3Plate", 72 | "T1 Aircraft Part": "PlaneT1Plate", 73 | "T2 Aircraft Part": "PlaneT2Plate", 74 | "T3 Aircraft Part": "PlaneT3Plate", 75 | 76 | "Strengthening Unit S1": "AllSUnit", 77 | "Strengthening Unit S2": "AllSUnit2", 78 | "Special Strengthening Unit S2": "AllSUnit2", 79 | "Strengthening Unit S3": "AllSUnit3", 80 | "Special Strengthening Unit S3": "AllSUnit3", 81 | "Strengthening Unit S4": "AllSUnit4", 82 | "Special Strengthening Unit S4": "AllSUnit4", 83 | "Random Blueprint": "UnknownSUnit", 84 | 85 | "Blueprint - Neptune": "HMS_NeptuneSUnit", 86 | "Blueprint - Monarch": "MonarchSUnit", 87 | "Blueprint - Ibuki": "IbukiSUnit", 88 | "Blueprint - Izumo": "IzumoSUnit", 89 | "Blueprint - Roon": "RoonSUnit", 90 | "Blueprint - Saint Louis": "Saint LouisSUnit", 91 | "Blueprint - Seattle": "SeattleSUnit", 92 | "Blueprint - Georgia": "GeorgiaSUnit", 93 | "Blueprint - Kitakaze": "KitakazeSUnit", 94 | "Blueprint - Azuma": "AzumaSUnit", 95 | "Blueprint - Friedrich der Große": "Friedrich der GroßeSUnit", 96 | "Blueprint - Gascogne": "GascogneSUnit", 97 | "Blueprint - Cheshire": "CheshireSUnit", 98 | "Blueprint - Drake": "DrakeSUnit", 99 | "Blueprint - Mainz": "MainzSUnit", 100 | "Blueprint - Odin": "OdinSUnit", 101 | "Blueprint - Champagne": "ChampagneSUnit", 102 | 103 | "Cognitive Chips": "Moduleicon", 104 | "Cognitive Datapack": "Cognitive Datapack", 105 | "Cognitive Array": "Cognitive Array", 106 | "Coins": "Coinicon", 107 | "Oil": "Oilicon", 108 | "Core Data": "Core Data", 109 | "Wisdom Cube": "cube", 110 | "Decor Tokens": "Furniture coin", 111 | 112 | "Rare Cat Box": "Cat Box Rare", 113 | "Elite Cat Box": "Cat Box Elite", 114 | "Super Rare Cat Box": "Cat Box Super Rare", 115 | 116 | "Oxy-cola": "Food1", 117 | "Candy Cane": "FoodChristmas", 118 | 119 | "Sakura Amulets": "SakuraPt", 120 | "Miniature Crowns": "CrownPt", 121 | "The Iron Blood Coat of Arms": "IronbloodPt", 122 | "Universe Badge": "UniverseBadgePt", 123 | "Gear Skin Box (Shining Stars)": "appearanceboxjichang", 124 | "Venus Points": "DOAPt", 125 | "Gear Skin Box (Venus Vacation)": "appearanceboxdoa", 126 | "Arcana Cube": "CubePt", 127 | "Symbol of Renaissance": "RenaissancePt", 128 | "Involuted Seal": "InvolutedSealPt", 129 | "Silver Wing Badge": "SilverWingPt", 130 | "Intel Pt": "IntelPt" 131 | } -------------------------------------------------------------------------------- /PythonScripts/data/static/settings_default.toml: -------------------------------------------------------------------------------- 1 | # path to the json data folder 2 | source_json_path = "../SrcJson" 3 | 4 | # which json data repository is used 5 | # "AzurLaneTools" for https://github.com/AzurLaneTools/AzurLaneData 6 | # "nobbyfix" for https://github.com/nobbyfix/AzurLaneSourceJson 7 | jsonloader_variant = "AzurLaneTools" 8 | -------------------------------------------------------------------------------- /PythonScripts/data/static/shipid_overrides.json: -------------------------------------------------------------------------------- 1 | { 2 | "1010001": "Neptune (Neptunia)", 3 | "1060003": "Kasumi (Venus Vacation)", 4 | "30507": "Kaga (Battleship)", 5 | "1050007": "Ookami Mio", 6 | "70202": "Pamiat Merkuria", 7 | "10224": "Cleveland µ", 8 | "20225": "Sheffield µ", 9 | "20232": "Enterprise (Royal Navy)", 10 | "30710": "Akagi µ", 11 | "40307": "Admiral Hipper µ", 12 | "90503": "Gascogne µ", 13 | "40308": "Roon µ", 14 | "10325": "Baltimore µ", 15 | "10805": "Albacore µ", 16 | "20228": "Dido µ", 17 | "20711": "Illustrious µ", 18 | "30711": "Taihou µ", 19 | "70105": "Tashkent µ", 20 | "90112": "Le Malin µ", 21 | "1020001": "22", 22 | "1020002": "33", 23 | "1030001": "Kuon", 24 | "1030002": "Nekone", 25 | "1030003": "Rurutie", 26 | "1030004": "Uruuru", 27 | "1030005": "Saraana", 28 | "1030006": "Fumiruiru", 29 | "1100005": "Fubuki (Senran Kagura)" 30 | } -------------------------------------------------------------------------------- /PythonScripts/equipskin.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser 2 | 3 | from lib import ALJsonAPI, Client, WikiHelper, Utility 4 | from lib.apiclasses import BackyardTheme 5 | 6 | 7 | EQUIPMENTTYPE_USAGE = { 8 | 1: '{{DD}} DD Main Gun', 9 | 2: '{{CL}} CL Main Gun', 10 | 3: '{{CA}} CA Main Gun', 11 | 4: '{{BB}} BB Main Gun', 12 | 5: '{{Torpedo}} Torpedo', 13 | # 6: 'AA Gun', 14 | 7: '{{CV}} Fighter', 15 | 8: '{{CV}} Torpedo Bomber', 16 | 9: '{{CV}} Dive Bomber', 17 | # 10: 'Auxiliary', 18 | # 11: None, 19 | 12: '{{CV}} Seaplane', 20 | 13: '{{Torpedo}} Sub Torpedo', 21 | # 14: 'Auxiliary', 22 | 15: '{{CV}} ASW Plane', 23 | # 17: 'Helicopter' 24 | } 25 | 26 | 27 | api = ALJsonAPI() 28 | 29 | def equipment_skin(client, eqid): 30 | equip_skin_template = api.get_sharecfgmodule('equip_skin_template') 31 | eqskin = equip_skin_template.load_client(eqid, client) 32 | if not eqskin: raise ValueError(f'Equipment skinid {eqid} does not exist.') 33 | 34 | name = eqskin['name'].strip() 35 | icon = eqskin['icon'] 36 | desc = eqskin['desc'].strip() 37 | usages = [EQUIPMENTTYPE_USAGE[eqtype] for eqtype in eqskin['equip_type'] if eqtype in EQUIPMENTTYPE_USAGE] 38 | return WikiHelper.simple_template('EquipSkinRow', [name, icon, desc, '
'.join(usages)]) 39 | 40 | def equipment_theme_skinlist(client, skinids: list): 41 | theme_skins = [equipment_skin(client, eqskinid) for eqskinid in skinids] 42 | return '\n'.join(theme_skins) 43 | 44 | def equipment_theme(client, theme: BackyardTheme) -> str: 45 | skinlist = equipment_theme_skinlist(client, theme['ids']) 46 | return WikiHelper.simple_template('EquipSkinHeader', [theme.name])+'\n'+skinlist+'\n|}' 47 | 48 | def get_theme_from_id(client: Client, themeid: str | int) -> BackyardTheme: 49 | equip_skin_theme_template = api.get_sharecfgmodule('equip_skin_theme_template') 50 | theme = equip_skin_theme_template.load_client(themeid, client) 51 | if not theme: 52 | raise ValueError(f'Equipment theme {themeid} does not exist.') 53 | return theme 54 | 55 | def get_theme_from_name(client: Client, themename: str) -> BackyardTheme: 56 | equip_skin_theme_template = api.get_sharecfgmodule('equip_skin_theme_template') 57 | for theme in equip_skin_theme_template.all_client(client): 58 | if theme.name == themename: 59 | return theme 60 | raise ValueError(f"Equipment theme with name '{themename}' does not exist.") 61 | 62 | def main(): 63 | parser = ArgumentParser() 64 | parser.add_argument('-i', '--themeids', type=int, nargs='*', default=[], 65 | help='a list of indexes from sharecfg/equip_skin_theme_template') 66 | parser.add_argument('-n', '--themenames', type=str, nargs='*', default=[], 67 | help='a list of names of themes from sharecfg/equip_skin_theme_template') 68 | parser.add_argument('-c', '--client', required=True, help='client to gather information from') 69 | args = parser.parse_args() 70 | 71 | client = Client[args.client] 72 | 73 | themes = [get_theme_from_id(client, themeid) for themeid in args.themeids] 74 | themes.extend([get_theme_from_name(client, themename) for themename in args.themenames]) 75 | 76 | formatted_themes = [equipment_theme(client, theme) for theme in themes] 77 | Utility.output('\n'.join(formatted_themes)) 78 | 79 | if __name__ == "__main__": 80 | main() 81 | -------------------------------------------------------------------------------- /PythonScripts/furniture.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | from argparse import ArgumentParser 3 | from collections.abc import Iterable 4 | 5 | from lib import ALJsonAPI, Client, WikiHelper, Utility 6 | from lib.apiclasses import Furniture 7 | 8 | 9 | FURNITURE_TYPE = { 10 | 1: "Wallpaper", # TYPE_WALLPAPER 11 | 2: "Furniture", # TYPE_FURNITURE 12 | 3: "Decoration", # TYPE_DECORATE 13 | 4: "Floor", # TYPE_FLOORPAPER 14 | 5: "Floor Item", # TYPE_MAT 15 | 6: "Wall Decoration", # TYPE_WALL 16 | 7: "Special", # TYPE_COLLECTION 17 | 8: "Stage", # TYPE_STAGE 18 | 9: "Arch", # TYPE_ARCH 19 | 10: "Special", # TYPE_WALL_MAT 20 | 11: "Moving Object", # TYPE_MOVEABLE 21 | 12: "Transport", # TYPE_TRANSPORT 22 | 13: "Special" # TYPE_RANDOM_CONTROLLER 23 | } 24 | 25 | INTERACTION = { 26 | "sit": "sit", 27 | "wash": "bath", 28 | "sleep": "sleep", 29 | "dance": "dance", 30 | "stand2": "stand", 31 | "victory": "stand", 32 | "yun": "stand", 33 | "attack": "stand", 34 | } 35 | 36 | NUM_TEXT = ["One", "Two", "Three", "Four", "Five", "Six", "Seven"] 37 | def convert_shipgirl_amount(num: int) -> str: 38 | text = NUM_TEXT[num-1] + " shipgirl" 39 | if num > 1: text += "s" 40 | return text 41 | 42 | 43 | def furniture_item_template(furn: Furniture): 44 | # optional shop data that needs to be checked if the field is there 45 | price_coin = furn.dorm_icon_price if "dorm_icon_price" in furn else "" 46 | price_gem = furn.gem_price if "dorm_icon_price" in furn else "" 47 | 48 | if size := furn.size or "": 49 | size = f"{size[0]}x{size[1]}" 50 | 51 | interaction = "" 52 | if "interAction" in furn: 53 | interaction_counter = Counter() 54 | for action_data in furn.interAction: 55 | interaction_counter[action_data[0]] += 1 56 | 57 | interaction = [] 58 | for actionname, amount in interaction_counter.most_common(): 59 | interaction.append(f"{convert_shipgirl_amount(amount)} can {INTERACTION[actionname]} here.") 60 | interaction = "
".join(interaction) 61 | 62 | params = [furn.name, furn.icon, furn.describe, furn.rarity.label, FURNITURE_TYPE[furn.type], price_coin, price_gem, 63 | furn.comfortable or "", size, furn.count, interaction] 64 | return WikiHelper.simple_template("FurnitureRow", params) 65 | 66 | class FurnitureQuery: 67 | api: ALJsonAPI 68 | 69 | def __init__(self, api: ALJsonAPI) -> None: 70 | self.api = api 71 | 72 | def get_theme(self, themeid: int, clients: Iterable[Client]): 73 | # determine client list, because all api calls use load_first 74 | backyard_theme_template = self.api.get_sharecfgmodule("backyard_theme_template") 75 | theme = backyard_theme_template.load_first(themeid, clients) 76 | if not theme: 77 | raise ValueError(f"There is no theme with id {themeid}.") 78 | 79 | wikitext = [ 80 | f"== {theme.name} ==", 81 | f"*'''Description:''' ''{theme.desc}''", 82 | WikiHelper.simple_template("FurnitureTableHeader", [f"FurnIcon_{theme.icon}.png"]), 83 | ] 84 | 85 | # add all furniture directly referenced from the set 86 | theme_items = {} 87 | for furniture_ref in theme.furniture: 88 | furnitem = furniture_ref.load_first(self.api, clients) 89 | theme_items[furnitem] = None 90 | 91 | # search for more furniture items that are not directly referenced 92 | # these are usually gem-only items 93 | furniture_module = self.api.get_apimodule("furniture") 94 | for furnitem in furniture_module.load_all(clients): 95 | if furnitem.themeId == theme.id: 96 | theme_items[furnitem] = None 97 | 98 | # convert all items to wikitext 99 | for furnitem in theme_items: 100 | wikitext.append(furniture_item_template(furnitem)) 101 | 102 | wikitext.append("|}") 103 | return "\n".join(wikitext) 104 | 105 | def get_themeid_from_name(self, name: str, clients: Iterable[Client]) -> int: 106 | backyard_theme_template = self.api.get_sharecfgmodule("backyard_theme_template") 107 | for theme in backyard_theme_template.load_all(clients): 108 | if theme.name == name: 109 | return theme.id 110 | 111 | 112 | def main(): 113 | parser = ArgumentParser() 114 | parser.add_argument("-i", "--id", type=int, 115 | help="an index from sharecfg/backyard_theme_template") 116 | parser.add_argument("-n", "--name", type=str, 117 | help="a theme name from sharecfg/backyard_theme_template") 118 | parser.add_argument("-c", "--client", nargs='*', choices=Client.__members__, 119 | help="client to gather information from") 120 | args = parser.parse_args() 121 | 122 | clients = Client 123 | if args.client: 124 | clients = [Client[c] for c in args.client] 125 | 126 | jsonapi = ALJsonAPI() 127 | fquery = FurnitureQuery(jsonapi) 128 | if themename := args.name: 129 | themeid = fquery.get_themeid_from_name(themename, clients) 130 | else: 131 | themeid = args.id 132 | 133 | print(themeid) 134 | 135 | result = fquery.get_theme(themeid, clients) 136 | Utility.output(result) 137 | 138 | if __name__ == "__main__": 139 | main() 140 | -------------------------------------------------------------------------------- /PythonScripts/lib/Constants.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from enum import Enum 3 | 4 | 5 | ### Constant Filepaths ### 6 | # static files (only manual changes) 7 | ITEMNAME_OVERRIDES_PATH = Path("data", "static", "item_name_convert.json") 8 | SHIPID_CONVERT_OVERRIDE_PATH = Path("data", "static", "shipid_overrides.json") 9 | 10 | # dynamic files (generated files) 11 | SHIPID_CONVERT_CACHE_PATH = Path("data", "dynamic", "shipid_convert.json") 12 | EQUIP_CONVERT_CACHE_PATH = Path("data", "dynamic", "equip_convert.json") 13 | EQUIP_WIKIDATA_PATH = Path("data", "dynamic", "equip_wikinames.json") 14 | SKIN_WIKIDATA_PATH = Path("data", "dynamic", "skin_wikidata.json") 15 | AUGMENT_CONVERT_CACHE_PATH = Path("data", "dynamic", "augment_convert.json") 16 | 17 | 18 | class Rarity(Enum): 19 | """ 20 | Enum class that allows conversion of rarity values between the game data and wiki. 21 | """ 22 | __num2member_map__: dict[int, "Rarity"] = {} 23 | 24 | rarity: int 25 | """Rarity id used in the game.""" 26 | label: str 27 | """Full rarity name used on the wiki.""" 28 | letter: str 29 | """Single letter used on the wiki in some templates.""" 30 | 31 | NORMAL0 = (0, "Normal", "E") 32 | NORMAL = (1, "Normal", "E") 33 | RARE = (2, "Rare", "B") 34 | ELITE = (3, "Elite", "P") 35 | SUPER_RARE = (4, "Super Rare", "G") 36 | PRIORITY = (4, "Priority", "G") 37 | ULTRA_RARE = (5, "Ultra Rare", "R") 38 | LEGENDARY = (5, "Legendary", "R") 39 | DECISIVE = (5, "Decisive", "R") 40 | VALENTINE_GIFT = (8, "Normal", "E") 41 | GIFT_OTHER = (9, "Super Rare", "G") 42 | UNKNOWN18 = (17, "???", "?") 43 | 44 | def __init__(self, rarity, label, letter) -> None: 45 | # add attributes to enum objects 46 | self.rarity = rarity 47 | self.label = label 48 | self.letter = letter 49 | # add enum objects to member maps 50 | if rarity not in self.__num2member_map__: 51 | self.__num2member_map__[rarity] = self 52 | 53 | def __str__(self) -> str: 54 | return self.label 55 | 56 | @classmethod 57 | def from_id(cls, rarity_num: int, is_research: bool = False) -> "Rarity" | None: 58 | """ 59 | Returns a `Rarity` member with *rarity_num* matching it's `rarity` attribute. 60 | Returns `None` if no match exists. 61 | 62 | For `rarity_num=4`, `Rarity.SUPER_RARE` will be returned over `Rarity.PRIORITY`. 63 | For `rarity_num=5`, `Rarity.ULTRA_RARE` will be returned over `Rarity.LEGENDARY` and `Rarity.DECISIVE`. 64 | For `rarity_num=7`, `Rarity.SUPER_RARE` will be returned, as there is only a single item (likely in error). 65 | 66 | If *is_research* is set True, `Rarity.PRIORITY` and `Rarity.DECISIVE` will be prioritised. 67 | """ 68 | if is_research: 69 | if rarity := {4: Rarity.PRIORITY, 5: Rarity.DECISIVE}.get(rarity_num): 70 | return rarity 71 | if rarity_num == 7: 72 | return Rarity.SUPER_RARE 73 | return cls.__num2member_map__.get(rarity_num) 74 | 75 | 76 | # from /model/const/nation.lua#Nation2Name 77 | class Nation(Enum): 78 | """ 79 | Enum class that allows conversion of nation values between the game data and wiki. 80 | """ 81 | id: int 82 | """Nation id used in the game.""" 83 | label: str 84 | """Name of the nation used on the wiki.""" 85 | 86 | UNIVERSAL = (0, "Universal") 87 | EAGLE_UNION = (1, "Eagle Union") 88 | ROYAL_NAVY = (2, "Royal Navy") 89 | SAKURA_EMPIRE = (3, "Sakura Empire") 90 | IRON_BLOOD = (4, "Iron Blood") 91 | DRAGON_EMPERY = (5, "Dragon Empery") 92 | SARDEGNA_EMPIRE = (6, "Sardegna Empire") 93 | NORTHERN_PARLIAMENT = (7, "Northern Parliament") 94 | IRIS_LIBRE = (8, "Iris Libre") 95 | VICHYA_DOMINION = (9, "Vichya Dominion") 96 | IRIS_ORTHODOXY = (10, "Iris Orthodoxy") 97 | TULIPA = (11, "Kingdom of Tulipa") 98 | TEMPESTA = (96, "Tempesta") 99 | META = (97, "META") 100 | UNIVERSAL2 = (98, "Universal") 101 | SIREN = (99, "Siren") 102 | NEPTUNIA = (101, "Neptunia") 103 | BILIBILI = (102, "Bilibili") 104 | UTAWARERUMONO = (103, "Utawarerumono") 105 | KIZUNA_AI = (104, "Kizuna AI") 106 | HOLOLIVE = (105, "Hololive") 107 | VENUS_VACATION = (106, "Venus Vacation") 108 | IDOLMASTER = (107, "The Idolmaster") 109 | SSSS = (108, "SSSS") 110 | ATELIER_RYZA = (109, "Atelier Ryza") 111 | SENRAN_KAGURA = (110, "Senran Kagura") 112 | TO_LOVE_RU = (111, "To LOVE-Ru") 113 | 114 | def __new__(cls, nation_id, label): 115 | obj = object.__new__(cls) 116 | obj._value_ = nation_id 117 | obj.label = label 118 | return obj 119 | 120 | def __str__(self) -> str: 121 | return self.label 122 | 123 | @property 124 | def id(self) -> int: 125 | """Nation id used in the game.""" 126 | return self.value 127 | 128 | @classmethod 129 | def from_id(cls, nation_id: int) -> "Nation" | None: 130 | """ 131 | Returns a `Nation` member with *nation_id* matching it's `id` attribute. 132 | Returns `None` if no match exists. 133 | """ 134 | return cls._value2member_map_.get(nation_id) 135 | 136 | 137 | class Attribute(Enum): 138 | """ 139 | Enum class that allows conversion of ship attributes between the game data and wiki. 140 | """ 141 | pos: int 142 | """The position of the attribute in the ship statistics array.""" 143 | wiki_param_name: str 144 | """The name of the parameter on wiki templates used for the attribute.""" 145 | wiki_template_name: str 146 | """The name of the template displaying the attribute icon on the wiki.""" 147 | 148 | DURABILITY = (0, "Health", "Health") 149 | CANNON = (1, "Fire", "Firepower") 150 | TORPEDO = (2, "Torp", "Torpedo") 151 | ANTIAIRCRAFT = (3, "AA", "AA") 152 | AIR = (4, "Air", "Aviation") 153 | RELOAD = (5, "Reload", "Reload") 154 | ARMOR = (6, "Armor_Debug", "Armor") 155 | HIT = (7, "Acc", "Accuracy") 156 | DODGE = (8, "Evade", "Evasion") 157 | SPEED = (9, "Speed", "Speed") 158 | LUCK = (10, "Luck", "Luck") 159 | ANTISUB = (11, "ASW", "ASW") 160 | 161 | def __init__(self, pos, param_name, template_name) -> None: 162 | # add attributes to enum objects 163 | self.pos = pos 164 | self.wiki_param_name = param_name 165 | self.wiki_template_name = template_name 166 | 167 | def __str__(self) -> str: 168 | return self.name 169 | 170 | 171 | # from /model/const/shiptype.lua 172 | class ShipType(Enum): 173 | """ 174 | Enum class that allows conversion of shiptype values between the game data and wiki. 175 | """ 176 | __id2member_map__: dict[int, "ShipType"] = {} 177 | __name2member_map__: dict[str, "ShipType"] = {} 178 | __fullname2member_map__: dict[str, "ShipType"] = {} 179 | 180 | id: int 181 | """The ID of the shiptype as used in the game.""" 182 | typename: str 183 | """The full name of the shiptype as used on the wiki.""" 184 | categoryname: str 185 | """The full name of the shiptypes category as used on the wiki""" 186 | templatename: str 187 | """The name of the template to display the shiptype icon on the wiki.""" 188 | typetext: str 189 | """The text displaying the abbreviation of the shiptype or multiple types for shiptype bundles.""" 190 | 191 | DD = (1, "Destroyer", "Destroyers", "DD", "DD") 192 | CL = (2, "Light Cruiser", "Light cruisers", "CL", "CL") 193 | CA = (3, "Heavy Cruiser", "Heavy cruisers", "CA", "CA") 194 | BC = (4, "Battlecruiser", "Battlecruisers", "BC", "BC") 195 | BB = (5, "Battleship", "Battleships", "BB", "BB") 196 | CVL = (6, "Light Aircraft Carrier", "Light aircraft carriers", "CVL", "CVL") 197 | CV = (7, "Aircraft Carrier", "Aircraft carriers", "CV", "CV") 198 | SS = (8, "Submarine", "Submarines", "SS", "SS") 199 | CAV = (9, "Aviation Cruiser", "Aviation cruisers", "CAV", "CAV") 200 | BBV = (10, "Aviation Battleship", "Aviation battleships", "BBV", "BBV") 201 | CT = (11, "Torpedo Cruiser", "Torpedo cruisers", "CT", "CT") 202 | AR = (12, "Repair Ship", "Repair ships", "AR", "AR") 203 | BM = (13, "Monitor", "Monitors", "BM", "BM") 204 | SSV = (17, "Submarine Carrier", "Submarine carriers", "SSV", "SSV") 205 | CB = (18, "Large Cruiser", "Large cruisers", "CB", "CB") 206 | AE = (19, "Munition Ship", "Munition ships", "AE", "AE") 207 | DDG_V = (20, "DDG", "Guided-missile destroyers", "DDG", "DDG") 208 | DDG_M = (21, "DDG", "Guided-missile destroyers", "DDG", "DDG") 209 | IX_S = (22, "Sailing Frigate (Submarine)", "Sailing Frigate", "IXs", "IX") 210 | IX_V = (23, "Sailing Frigate (Vanguard)", "Sailing Frigate", "IXv", "IX") 211 | IX_M = (24, "Sailing Frigate (Main)", "Sailing Frigate", "IXm", "IX") 212 | ZHAN = (-1, "", "", "BC", "BC or BB") 213 | HANG = (-1, "", "", "CVL", "CV or CVL") 214 | QIAN = (-1, "", "", "SS", "SS or SSV or IX") 215 | ZHONG = (-1, "", "", "CB", "CB or CA") 216 | FANQIAN = (-1, "", "", "DD", "DD or DDG or CL") 217 | QUZHU = (-1, "", "", "DD", "DD or DDG") 218 | FEGNFAN = (-1, "", "", "IXs", "IX") 219 | 220 | def __init__(self, typeid, typename, catname, templatename, typetext): 221 | # add attributes to enum objects 222 | self.id = typeid 223 | self.typename = typename 224 | self.categoryname = catname 225 | self.templatename = templatename 226 | self.typetext = typetext 227 | 228 | # add enum objects to member maps 229 | self.__name2member_map__[self.name.lower()] = self 230 | if typeid != -1: 231 | self.__id2member_map__[typeid] = self 232 | if typename != "": 233 | self.__fullname2member_map__[typename.lower()] = self 234 | 235 | @classmethod 236 | def from_id(cls, type_id: int) -> "ShipType" | None: 237 | """ 238 | Returns a `ShipType` member with *type_id* matching it's `id` attribute. 239 | Returns `None` if no match exists. 240 | """ 241 | return cls.__id2member_map__.get(type_id) 242 | 243 | @classmethod 244 | def from_type(cls, type_name: str) -> "ShipType" | None: 245 | """ 246 | Returns a `ShipType` member with *type_name* matching it's `name` attribute, ignoring capitalization. 247 | Returns `None` if no match exists. 248 | """ 249 | return cls.__name2member_map__.get(type_name) 250 | 251 | @classmethod 252 | def from_name(cls, type_name: str) -> "ShipType" | None: 253 | """ 254 | Returns a `ShipType` member with *type_name* matching it's `typename` attribute. 255 | Returns `None` if no match exists. 256 | """ 257 | return cls.__fullname2member_map__.get(type_name) 258 | 259 | 260 | class Armor(Enum): 261 | __label2member_map__: dict[str, "Armor"] = {} 262 | id: int 263 | """ID of the armor type as used in the game.""" 264 | label: str 265 | """Name of the armor type.""" 266 | 267 | LIGHT = (1, "Light") 268 | MEDIUM = (2, "Medium") 269 | HEAVY = (3, "Heavy") 270 | 271 | def __new__(cls, armor_id, label): 272 | obj = object.__new__(cls) 273 | obj._value_ = armor_id 274 | return obj 275 | 276 | def __init__(self, armor_id, label) -> None: 277 | # add attributes to enum objects 278 | self.label = label 279 | # add enum objects to member maps 280 | self.__label2member_map__[label] = self 281 | 282 | def __str__(self) -> str: 283 | return self.label 284 | 285 | @property 286 | def id(self) -> int: 287 | """ 288 | ID of the armor type as used in the game. 289 | """ 290 | return self.value 291 | 292 | @classmethod 293 | def from_id(cls, armor_id: int) -> "Armor" | None: 294 | """ 295 | Returns an Armor member with matching *armor_id* if match exists, otherwise None. 296 | """ 297 | return cls._value2member_map_.get(armor_id) 298 | 299 | @classmethod 300 | def from_label(cls, armor_label: str) -> "Armor" | None: 301 | """ 302 | Returns an Armor member with matching *armor_label* if match exists, otherwise None. 303 | """ 304 | return cls.__label2member_map__.get(armor_label) 305 | -------------------------------------------------------------------------------- /PythonScripts/lib/Utility.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from os import PathLike 3 | from typing import Any, MutableSequence 4 | from pathlib import Path 5 | 6 | 7 | def rreplace(s: str, old: str, new: str, occurrence: int) -> str: 8 | """ 9 | reverse replacement for strings 10 | """ 11 | li = s.rsplit(old, occurrence) 12 | return new.join(li) 13 | 14 | def mkdir(directory: Path) -> None: 15 | """ 16 | faster than simply calling os.makedirs with exists_ok=True 17 | if the same directory gets checked even after its creation 18 | """ 19 | if not directory.exists(): 20 | directory.mkdir(parents=True, exist_ok=True) 21 | 22 | def mkdirf(file: Path) -> None: 23 | """ 24 | faster than simply calling os.makedirs with exists_ok=True 25 | if the same directory gets checked even after its creation 26 | """ 27 | mkdir(file.parent) 28 | 29 | def output(text: str, filepath: PathLike | None = None) -> None: 30 | """ 31 | Tries to print the output to console, if it fails write it to the specified file in the output directory. 32 | """ 33 | try: 34 | print(text) 35 | except UnicodeEncodeError: 36 | if filepath: 37 | fpath = Path(filepath) 38 | else: 39 | fpath = Path("output", Path(sys.argv[0]).stem + ".wikitext") 40 | 41 | mkdirf(fpath) 42 | with open(fpath, 'w', encoding='utf8') as f: 43 | f.write(text) 44 | print("Failed to print to console, output has been written into output directory.") 45 | 46 | def dict_args_query(dict_input: dict, *args) -> Any: 47 | """ 48 | Recursively searches a dict for the given list of keys. 49 | """ 50 | for arg in args: 51 | if isinstance(dict_input, dict): 52 | dict_input = dict_input.get(arg) 53 | if not dict_input: return 54 | return dict_input 55 | 56 | def pop_zeros(items: MutableSequence) -> MutableSequence: 57 | while items and items[-1] == 0: 58 | del items[-1] 59 | return items 60 | -------------------------------------------------------------------------------- /PythonScripts/lib/WikiConstants.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | from . import Constants 5 | 6 | 7 | # filepath for wikiclient settings 8 | WIKICLIENT_SETTINGS_PATH = Path("data", "static", "wiki_settings.json") 9 | 10 | # convert item names to wiki filenames 11 | itemname_convertpath = Constants.ITEMNAME_OVERRIDES_PATH 12 | with open(itemname_convertpath, 'r') as file: 13 | ITEMFILENAMES = json.load(file) 14 | 15 | #TODO: rewrite to primarily use icon name instead of item name 16 | # enables to easier derive certain icons without knowing the award type 17 | def item_filename(name: str) -> str: 18 | return ITEMFILENAMES.get(name) 19 | 20 | 21 | # replaces certain phrases in item names for wiki purposes 22 | ITEMNAME_REPLACE = { 23 | 'Mystery': 'Random', 24 | 'Series ': 'S', 25 | 'General Blueprint -': 'Strengthening Unit', 26 | } 27 | 28 | def item_name(name: str) -> str: 29 | for match in ITEMNAME_REPLACE: 30 | if match in name: name = name.replace(match, ITEMNAME_REPLACE[match]) 31 | return name 32 | 33 | 34 | # return page links for certain items 35 | def item_link(name: str) -> str | None: 36 | if not name: return '' 37 | if 'Strengthening Unit' in name: return 'Research' 38 | if name in ['Cognitive Chips', 'Cognitive Array']: return 'Dockyard#Cognitive_Awakening' 39 | if name == 'Oxy-cola': return 'Living_Area#Refilling_supplies' 40 | if name.endswith('Cat Box'): return 'Meowfficer#Cat_Lodge' 41 | if name.startswith('T'): 42 | if name.endswith('Tech Pack'): return 'Equipment#Equipment_Boxes' 43 | if name.endswith('Part'): return 'Equipment#Upgrade_(Enhance)' 44 | 45 | 46 | # return filled templates that will display the icon of the item 47 | def item_template(name: str) -> str: 48 | raise NotImplementedError() 49 | -------------------------------------------------------------------------------- /PythonScripts/lib/WikiHelper.py: -------------------------------------------------------------------------------- 1 | import re, json, time 2 | from pathlib import Path 3 | from typing import Any, Callable 4 | from dataclasses import dataclass, field 5 | import mwclient 6 | from mwclient import APIError 7 | 8 | from . import ALJsonAPI, WikiConstants 9 | from .apiclasses import Awardable, EquipStat, Item, ShipReward, Furniture 10 | 11 | 12 | class WikiClient(): 13 | def __init__(self, execution_delay: float = 1.5, settings_path: Path = WikiConstants.WIKICLIENT_SETTINGS_PATH): 14 | self.settings_path = settings_path 15 | self.execution_delay = execution_delay 16 | self.last_execute_time = 0.0 17 | 18 | ### INIT MWCLIENT ### 19 | print('reading wiki settings ...') 20 | if settings_path.exists(): 21 | settings = self.load_settings() 22 | print('Loaded settings.') 23 | else: 24 | print(f'{settings_path} not found. Creating new file...') 25 | settings = { 26 | "username": "", 27 | "password": "", 28 | "url": input("Wiki Url: "), 29 | "useragent": input("Useragent: "), 30 | } 31 | self.save_settings(settings) 32 | print('Settings file created.') 33 | self.mwclient = mwclient.Site(settings['url'], clients_useragent=settings['useragent']) 34 | self.settings = settings 35 | self.logged_in = False 36 | 37 | def load_settings(self) -> dict: 38 | with open(self.settings_path, 'r', encoding='utf8') as settings_file: 39 | return json.load(settings_file) 40 | 41 | def save_settings(self, settings: dict) -> None: 42 | with open(self.settings_path, 'w', encoding='utf8') as settings_file: 43 | json.dump(settings, settings_file) 44 | 45 | 46 | def login(self) -> "WikiClient": 47 | if self.logged_in: return self 48 | 49 | if not self.settings['username']: 50 | print("You can leave the username empty, but it will prompt to input one during the next run.") 51 | if username := input("Username: "): 52 | self.settings['username'] = username 53 | self.settings['password'] = input("Password: ") 54 | self.save_settings(self.settings) 55 | 56 | # log into wiki 57 | print('Logging into mediawiki...') 58 | self.mwclient.login(self.settings['username'], self.settings['password']) 59 | print('Logged in.') 60 | return self 61 | 62 | def execute(self, func: Callable, *args, **kwargs): 63 | delta_last_execute = time.time() - self.last_execute_time 64 | if delta_last_execute < self.execution_delay: 65 | time.sleep(delta_last_execute) 66 | 67 | try: 68 | result = func(*args, **kwargs) 69 | except APIError as error: 70 | if error.code == "ratelimited": 71 | exec_delay_saved = self.execution_delay 72 | self.execution_delay = (self.execution_delay+5)*2 73 | result = self.execute(func, *args, **kwargs) 74 | self.execution_delay = exec_delay_saved 75 | else: 76 | raise error 77 | 78 | self.last_execute_time = time.time() 79 | return result 80 | 81 | 82 | def simple_template(name: str, params: list) -> str: 83 | params.insert(0, name) 84 | wikitext = '|'.join([(str(param) if param is not None else '') for param in params]) 85 | return '{{'+wikitext.rstrip('|')+'}}' 86 | 87 | 88 | class MultilineTemplate(): 89 | def __init__(self, template: str): 90 | with open('templates/'+template+'.json', 'r') as jfile: 91 | jsontemplate = json.load(jfile) 92 | self.template_name = jsontemplate["template_name"] 93 | self.sections = jsontemplate['template_sections'] 94 | self.behavior = jsontemplate['behavior'] 95 | self.default_behavior = self.behavior['default'] 96 | self.prefer_wiki_params = jsontemplate['prefer-wiki-data'] 97 | 98 | def _param(self, key, val) -> str: 99 | if val is None: val = '' 100 | return f' | {key} = {str(val)}' 101 | 102 | def _fill_section(self, section, content, wiki_content) -> str | None: 103 | # add all template parameter of current section 104 | section_params = [] 105 | for param in section.get('params', []): 106 | value = content.get(param) 107 | if param in self.prefer_wiki_params: 108 | value = wiki_content.get(param) 109 | 110 | p_behavior = self.behavior.get(param, self.default_behavior) 111 | p_behav_type = p_behavior['type'] 112 | if p_behav_type == 'keep': 113 | section_params.append(self._param(param, value)) 114 | elif p_behav_type == 'remove_empty': 115 | if not (value is None or value == ''): 116 | section_params.append(self._param(param, value)) 117 | elif p_behav_type == 'dependency': 118 | dependency_type = p_behavior['dependency']['type'] 119 | dependend_on = p_behavior['dependency']['dependent_params'] 120 | if sum([param in content for param in dependend_on]) == 0: continue 121 | if dependency_type == 'keep_empty': 122 | if not value is None: 123 | section_params.append(self._param(param, value)) 124 | elif dependency_type == 'remove_empty': 125 | if not (value is None or value == ''): 126 | section_params.append(self._param(param, value)) 127 | elif dependency_type == 'keep_always': 128 | section_params.append(self._param(param, value)) 129 | else: raise NotImplementedError(f'Unknown dependency behavior type {dependency_type}') 130 | else: raise NotImplementedError(f'Unknown behavior type {p_behav_type}') 131 | 132 | # recursively fill all subsections 133 | sub_wikitexts = [] 134 | for subsection in section.get('sections', []): 135 | sub_wikitext = self._fill_section(subsection, content, wiki_content) 136 | if sub_wikitext: 137 | sub_wikitexts.append(sub_wikitext) 138 | 139 | # compile section_wikitext if any parameters were added from this section 140 | # section_wikitext consists of the comment before params, then the block of params, each on a new line 141 | if section_params: 142 | sub_wikitexts.insert(0, '\n'.join(section_params)) 143 | if sub_wikitexts: 144 | comment = section['comment'] 145 | comment = comment and comment+'\n' 146 | return comment+'\n\n'.join(sub_wikitexts).rstrip('\n') 147 | 148 | def fill(self, content: dict[str, Any], wiki_content: dict[str, Any] | None = None) -> str: 149 | filled_sections = self._fill_section(self.sections, content, wiki_content or {}) 150 | 151 | # add all sections with one empty line spacing between them to result wikitext 152 | wikitext = "{{"+self.template_name+'\n'+filled_sections+'\n}}' 153 | return wikitext 154 | 155 | COMMENT_REGEX = re.compile(r'') 156 | COMMENT_PART = re.compile(r'(.*?)-->|", 8 | "params": ["Name", "AltNames", "CNName", "JPName", "KRName", "ENName", "Image", "BaseID", "Type", "Stars", "Nationality", "Tech"] 9 | }, 10 | { 11 | "comment": "", 12 | "params": ["Health", "HealthMax", "HealthOPS", "Torpedo", "TorpMax", "TorpOPS", "Firepower", "FPMax", "FPOPS", "Aviation", "AvMax", "AvOPS", "Evasion", "EvasionMax", "EvasionOPS", "ASW", "ASWMax", "ASWOPS", "Oxygen", "OxygenMax", "OxygenOPS", "AA", "AAMax", "AAOPS", "Acc", "AccMax", "AccOPS", "Spd", "SpdMax", "SpdOPS", "Luck", "LuckMax", "LuckOPS", "Reload", "ReloadMax", "ReloadOPS"] 13 | }, 14 | { 15 | "comment": "", 16 | "sections": [ 17 | { 18 | "comment": "", 19 | "params": ["Damage", "DamageMax", "DamageOPS", "RoF", "RoFMax", "RoFOPS", "Int", "IntMax", "IntOPS", "Number", "Angle", "WepRangeMin", "WepRange", "ProjRange", "ProjSpeed", "Spread", "FiringSpread", "PatternSpread", "Coef", "CoefMax", "ArmorModL", "ArmorModM", "ArmorModH", "PlaneHP", "PlaneHPMax", "PlaneHPOPS", "OpSiren", "Shells", "Salvoes", "VolleyTime", "Characteristic", "PlaneSpeed", "CrashDamage", "DodgeLimit", "PlaneDodge"] 20 | }, 21 | { 22 | "comment": "", 23 | "params": ["Ammo", "AoE", "Weapons"] 24 | } 25 | ] 26 | }, 27 | { 28 | "comment": "", 29 | "sections": [ 30 | { 31 | "comment": "", 32 | "params": ["UseOverride", "DD", "DDNote", "CL", "CLNote", "CA", "CANote", "CB", "CBNote", "BB", "BBNote", "BC", "BCNote", "BM", "BMNote", "BBV", "BBVNote", "CV", "CVNote", "CVL", "CVLNote", "AR", "ARNote", "AE", "AENote", "SS", "SSNote", "SSV", "SSVNote", "IX", "IXNote", "IXs", "IXsNote", "IXv", "IXvNote", "IXm", "IXmNote"] 33 | } 34 | ] 35 | }, 36 | { 37 | "comment": "", 38 | "sections": [ 39 | { 40 | "comment": "", 41 | "params": ["DropLocation", "BulletPattern", "Notes", "Research", "LabFrom", "LabTo"] 42 | } 43 | ] 44 | } 45 | ] 46 | }, 47 | "behavior": { 48 | "default": { 49 | "type": "remove_empty" 50 | }, 51 | "Name": { 52 | "type": "keep" 53 | }, 54 | "CNName": { 55 | "type": "keep" 56 | }, 57 | "JPName": { 58 | "type": "keep" 59 | }, 60 | "Image": { 61 | "type": "keep" 62 | }, 63 | "BaseID": { 64 | "type": "keep" 65 | }, 66 | "Type": { 67 | "type": "keep" 68 | }, 69 | "Stars": { 70 | "type": "keep" 71 | }, 72 | "Nationality": { 73 | "type": "keep" 74 | }, 75 | "Tech": { 76 | "type": "keep" 77 | }, 78 | "DamageMax": { 79 | "type": "dependency", 80 | "dependency": { 81 | "type": "keep_empty", 82 | "dependent_params": ["Damage"] 83 | } 84 | }, 85 | "RoF": { 86 | "type": "remove_empty" 87 | }, 88 | "RoFMax": { 89 | "type": "remove_empty" 90 | }, 91 | "Spread": { 92 | "type": "remove_empty" 93 | }, 94 | "Angle": { 95 | "type": "remove_empty" 96 | }, 97 | "WepRange": { 98 | "type": "remove_empty" 99 | }, 100 | "ProjRange": { 101 | "type": "remove_empty" 102 | }, 103 | "Shells": { 104 | "type": "remove_empty" 105 | }, 106 | "Salvoes": { 107 | "type": "remove_empty" 108 | }, 109 | "Characteristic": { 110 | "type": "remove_empty" 111 | }, 112 | "VolleyTime": { 113 | "type": "remove_empty" 114 | }, 115 | "Coef": { 116 | "type": "remove_empty" 117 | }, 118 | "CoefMax": { 119 | "type": "remove_empty" 120 | }, 121 | "ArmorModL": { 122 | "type": "remove_empty" 123 | }, 124 | "ArmorModM": { 125 | "type": "remove_empty" 126 | }, 127 | "ArmorModH": { 128 | "type": "remove_empty" 129 | }, 130 | "Ammo": { 131 | "type": "remove_empty" 132 | }, 133 | "DropLocation": { 134 | "type": "keep" 135 | }, 136 | "Notes": { 137 | "type": "keep" 138 | } 139 | }, 140 | "prefer-wiki-data": [] 141 | } -------------------------------------------------------------------------------- /PythonScripts/templates/EventShop.json: -------------------------------------------------------------------------------- 1 | { 2 | "template_name": "EventShop", 3 | "template_sections": { 4 | "comment": "", 5 | "sections": [ 6 | { 7 | "comment": "", 8 | "params": ["ShopName", "TotalPt", "PtImage"] 9 | }, 10 | { 11 | "comment": "", 12 | "params": ["ColorScheme", "Image", "Size", "Top", "Left"] 13 | }, 14 | { 15 | "comment": "", 16 | "params": ["Items"] 17 | } 18 | ] 19 | }, 20 | "behavior": { 21 | "default": { 22 | "type": "keep" 23 | } 24 | }, 25 | "prefer-wiki-data": [] 26 | } -------------------------------------------------------------------------------- /PythonScripts/templates/Juustagram.json: -------------------------------------------------------------------------------- 1 | { 2 | "template_name": "Juustagram", 3 | "template_sections": { 4 | "comment": "", 5 | "sections": [ 6 | { 7 | "comment": "", 8 | "params": ["pict", "post"] 9 | } 10 | ] 11 | }, 12 | "behavior": { 13 | "default": { 14 | "type": "keep" 15 | } 16 | }, 17 | "prefer-wiki-data": [] 18 | } -------------------------------------------------------------------------------- /PythonScripts/templates/Map.json: -------------------------------------------------------------------------------- 1 | { 2 | "template_name": "Map", 3 | "template_sections": { 4 | "comment": "", 5 | "sections": [ 6 | { 7 | "comment": "", 8 | "params": ["ctID", "Title", "Introduction", "Requirements", "ClearReward", "3StarReward"] 9 | }, 10 | { 11 | "comment": "", 12 | "params": ["MobLevel", "SirenLevel", "MobExp1", "MobExp2", "MobExp3", "MobExpSiren", "MobSpawnOrder", "EliteSpawnOrder", "SirenSpawnOrder", "Boss", "BossLevel", "BossExp"] 13 | }, 14 | { 15 | "comment": "", 16 | "params": ["BossBattleReq", "BossBattleClear", "Star1", "Star2", "Star3", "AvoidRequire", "SuggestedAirSupremacy", "ActualAirSupremacy", "OilCapMob", "OilCapBoss", "OilCapSub"] 17 | }, 18 | { 19 | "comment": "", 20 | "params": ["Fleet1", "Fleet2", "StatRestrictions"] 21 | }, 22 | { 23 | "comment": "", 24 | "params": ["MapDrops", "EquipmentDrops", "ShipDrops", "PreviewFile", "NodeMap", "Notes"] 25 | } 26 | ] 27 | }, 28 | "behavior": { 29 | "default": { 30 | "type": "remove_empty" 31 | } 32 | }, 33 | "prefer-wiki-data": [] 34 | } -------------------------------------------------------------------------------- /PythonScripts/templates/Mission.json: -------------------------------------------------------------------------------- 1 | { 2 | "template_name": "Mission", 3 | "template_sections": { 4 | "comment": "", 5 | "sections": [ 6 | { 7 | "comment": "", 8 | "params": ["descen", "reward", "notes"] 9 | } 10 | ] 11 | }, 12 | "behavior": { 13 | "default": { 14 | "type": "remove_empty" 15 | } 16 | }, 17 | "prefer-wiki-data": [] 18 | } -------------------------------------------------------------------------------- /PythonScripts/templates/MissionDouble.json: -------------------------------------------------------------------------------- 1 | { 2 | "template_name": "MissionDouble", 3 | "template_sections": { 4 | "comment": "", 5 | "sections": [ 6 | { 7 | "comment": "", 8 | "params": ["descen1", "descen2", "reward1", "reward2", "notes"] 9 | } 10 | ] 11 | }, 12 | "behavior": { 13 | "default": { 14 | "type": "remove_empty" 15 | } 16 | }, 17 | "prefer-wiki-data": [] 18 | } -------------------------------------------------------------------------------- /PythonScripts/templates/ShipQuote.json: -------------------------------------------------------------------------------- 1 | { 2 | "template_name": "ShipQuote", 3 | "template_sections": { 4 | "comment": "", 5 | "sections": [ 6 | { 7 | "comment": "", 8 | "params": ["Region", "Voice", "Skin"] 9 | }, 10 | { 11 | "comment": "", 12 | "params": ["ShipDescription", "ShipDescriptionTL", "ShipDescriptionNote"] 13 | }, 14 | { 15 | "comment": "", 16 | "params": ["SelfIntro", "SelfIntroTL", "SelfIntroNote"] 17 | }, 18 | { 19 | "comment": "", 20 | "params": ["Acquisition", "AcquisitionTL", "AcquisitionNote"] 21 | }, 22 | { 23 | "comment": "", 24 | "params": ["Login", "LoginTL", "LoginNote"] 25 | }, 26 | { 27 | "comment": "", 28 | "params": ["Details", "DetailsTL", "DetailsNote"] 29 | }, 30 | { 31 | "comment": "", 32 | "params": ["SecretaryIdle1", "SecretaryIdle1TL", "SecretaryIdle1Note"] 33 | }, 34 | { 35 | "comment": "", 36 | "params": ["SecretaryIdle2", "SecretaryIdle2TL", "SecretaryIdle2Note"] 37 | }, 38 | { 39 | "comment": "", 40 | "params": ["SecretaryIdle3", "SecretaryIdle3TL", "SecretaryIdle3Note"] 41 | }, 42 | { 43 | "comment": "", 44 | "params": ["SecretaryIdle4", "SecretaryIdle4TL", "SecretaryIdle4Note"] 45 | }, 46 | { 47 | "comment": "", 48 | "params": ["SecretaryIdle5", "SecretaryIdle5TL", "SecretaryIdle5Note"] 49 | }, 50 | { 51 | "comment": "", 52 | "params": ["SecretaryIdle6", "SecretaryIdle6TL", "SecretaryIdle6Note"] 53 | }, 54 | { 55 | "comment": "", 56 | "params": ["SecretaryIdle7", "SecretaryIdle7TL", "SecretaryIdle7Note"] 57 | }, 58 | { 59 | "comment": "", 60 | "params": ["SecretaryIdle8", "SecretaryIdle8TL", "SecretaryIdle8Note"] 61 | }, 62 | { 63 | "comment": "", 64 | "params": ["SecretaryTouch", "SecretaryTouchTL", "SecretaryTouchNote"] 65 | }, 66 | { 67 | "comment": "", 68 | "params": ["SpecialTouch", "SpecialTouchTL", "SpecialTouchNote"] 69 | }, 70 | { 71 | "comment": "", 72 | "params": ["Headpat", "HeadpatTL", "HeadpatNote"] 73 | }, 74 | { 75 | "comment": "", 76 | "params": ["Task", "TaskTL", "TaskNote"] 77 | }, 78 | { 79 | "comment": "", 80 | "params": ["TaskComplete", "TaskCompleteTL", "TaskCompleteNote"] 81 | }, 82 | { 83 | "comment": "", 84 | "params": ["Mail", "MailTL", "MailNote"] 85 | }, 86 | { 87 | "comment": "", 88 | "params": ["MissionFinished", "MissionFinishedTL", "MissionFinishedNote"] 89 | }, 90 | { 91 | "comment": "", 92 | "params": ["Commission", "CommissionTL", "CommissionNote"] 93 | }, 94 | { 95 | "comment": "", 96 | "params": ["Strengthening", "StrengtheningTL", "StrengtheningNote"] 97 | }, 98 | { 99 | "comment": "", 100 | "params": ["MissionStart", "MissionStartTL", "MissionStartNote"] 101 | }, 102 | { 103 | "comment": "", 104 | "params": ["MVP", "MVPTL", "MVPNote"] 105 | }, 106 | { 107 | "comment": "", 108 | "params": ["Defeat", "DefeatTL", "DefeatNote"] 109 | }, 110 | { 111 | "comment": "", 112 | "params": ["SkillActivation", "SkillActivationTL", "SkillActivationNote"] 113 | }, 114 | { 115 | "comment": "", 116 | "params": ["LowHP", "LowHPTL", "LowHPNote"] 117 | }, 118 | { 119 | "comment": "", 120 | "params": ["AffinityDisappointed", "AffinityDisappointedTL", "AffinityDisappointedNote"] 121 | }, 122 | { 123 | "comment": "", 124 | "params": ["AffinityStranger", "AffinityStrangerTL", "AffinityStrangerNote"] 125 | }, 126 | { 127 | "comment": "", 128 | "params": ["AffinityFriendly", "AffinityFriendlyTL", "AffinityFriendlyNote"] 129 | }, 130 | { 131 | "comment": "", 132 | "params": ["AffinityLike", "AffinityLikeTL", "AffinityLikeNote"] 133 | }, 134 | { 135 | "comment": "", 136 | "params": ["AffinityLove", "AffinityLoveTL", "AffinityLoveNote"] 137 | }, 138 | { 139 | "comment": "", 140 | "params": ["Pledge", "PledgeTL", "PledgeNote"] 141 | }, 142 | { 143 | "comment": "", 144 | "params": ["Additional1", "Additional1TL", "Additional1Note"] 145 | }, 146 | { 147 | "comment": "", 148 | "params": ["Additional2", "Additional2TL", "Additional2Note"] 149 | }, 150 | { 151 | "comment": "", 152 | "params": ["Additional3", "Additional3TL", "Additional3Note"] 153 | }, 154 | { 155 | "comment": "", 156 | "params": ["Additional4", "Additional4TL", "Additional4Note"] 157 | }, 158 | { 159 | "comment": "", 160 | "params": ["Additional5", "Additional5TL", "Additional5Note"] 161 | }, 162 | { 163 | "comment": "", 164 | "params": ["Additional6", "Additional6TL", "Additional6Note"] 165 | }, 166 | { 167 | "comment": "", 168 | "params": ["Additional7", "Additional7TL", "Additional7Note"] 169 | }, 170 | { 171 | "comment": "", 172 | "params": ["Additional8", "Additional8TL", "Additional8Note"] 173 | }, 174 | { 175 | "comment": "", 176 | "params": ["Additional9", "Additional9TL", "Additional9Note"] 177 | }, 178 | { 179 | "comment": "", 180 | "params": ["Additional10", "Additional10TL", "Additional10Note"] 181 | }, 182 | { 183 | "comment": "", 184 | "params": ["Additional11", "Additional11TL", "Additional11Note"] 185 | }, 186 | { 187 | "comment": "", 188 | "params": ["Additional12", "Additional12TL", "Additional12Note"] 189 | }, 190 | { 191 | "comment": "", 192 | "params": ["Valentine18", "Valentine18TL", "Valentine18Note"] 193 | }, 194 | { 195 | "comment": "", 196 | "params": ["Valentine19", "Valentine19TL", "Valentine19Note"] 197 | }, 198 | { 199 | "comment": "", 200 | "params": ["Valentine20", "Valentine20TL", "Valentine20Note"] 201 | } 202 | ] 203 | }, 204 | "behavior": { 205 | "default": { 206 | "type": "remove_empty" 207 | }, 208 | "Region": { 209 | "type": "keep" 210 | }, 211 | "Skin": { 212 | "type": "keep" 213 | }, 214 | "ShipDescriptionTL": { 215 | "type": "dependency", 216 | "dependency": { 217 | "type": "keep_always", 218 | "dependent_params": ["ShipDescription"] 219 | } 220 | }, 221 | "ShipDescriptionNote": { 222 | "type": "dependency", 223 | "dependency": { 224 | "type": "keep_always", 225 | "dependent_params": ["ShipDescription"] 226 | } 227 | }, 228 | "SelfIntroTL": { 229 | "type": "dependency", 230 | "dependency": { 231 | "type": "keep_always", 232 | "dependent_params": ["SelfIntro"] 233 | } 234 | }, 235 | "SelfIntroNote": { 236 | "type": "dependency", 237 | "dependency": { 238 | "type": "keep_always", 239 | "dependent_params": ["SelfIntro"] 240 | } 241 | }, 242 | "AcquisitionTL": { 243 | "type": "dependency", 244 | "dependency": { 245 | "type": "keep_always", 246 | "dependent_params": ["Acquisition"] 247 | } 248 | }, 249 | "AcquisitionNote": { 250 | "type": "dependency", 251 | "dependency": { 252 | "type": "keep_always", 253 | "dependent_params": ["Acquisition"] 254 | } 255 | }, 256 | "LoginTL": { 257 | "type": "dependency", 258 | "dependency": { 259 | "type": "keep_always", 260 | "dependent_params": ["Login"] 261 | } 262 | }, 263 | "LoginNote": { 264 | "type": "dependency", 265 | "dependency": { 266 | "type": "keep_always", 267 | "dependent_params": ["Login"] 268 | } 269 | }, 270 | "DetailsTL": { 271 | "type": "dependency", 272 | "dependency": { 273 | "type": "keep_always", 274 | "dependent_params": ["Details"] 275 | } 276 | }, 277 | "DetailsNote": { 278 | "type": "dependency", 279 | "dependency": { 280 | "type": "keep_always", 281 | "dependent_params": ["Details"] 282 | } 283 | }, 284 | "SecretaryIdle1TL": { 285 | "type": "dependency", 286 | "dependency": { 287 | "type": "keep_always", 288 | "dependent_params": ["SecretaryIdle1"] 289 | } 290 | }, 291 | "SecretaryIdle1Note": { 292 | "type": "dependency", 293 | "dependency": { 294 | "type": "keep_always", 295 | "dependent_params": ["SecretaryIdle1"] 296 | } 297 | }, 298 | "SecretaryIdle2TL": { 299 | "type": "dependency", 300 | "dependency": { 301 | "type": "keep_always", 302 | "dependent_params": ["SecretaryIdle2"] 303 | } 304 | }, 305 | "SecretaryIdle2Note": { 306 | "type": "dependency", 307 | "dependency": { 308 | "type": "keep_always", 309 | "dependent_params": ["SecretaryIdle2"] 310 | } 311 | }, 312 | "SecretaryIdle3TL": { 313 | "type": "dependency", 314 | "dependency": { 315 | "type": "keep_always", 316 | "dependent_params": ["SecretaryIdle3"] 317 | } 318 | }, 319 | "SecretaryIdle3Note": { 320 | "type": "dependency", 321 | "dependency": { 322 | "type": "keep_always", 323 | "dependent_params": ["SecretaryIdle3"] 324 | } 325 | }, 326 | "SecretaryIdle4TL": { 327 | "type": "dependency", 328 | "dependency": { 329 | "type": "keep_always", 330 | "dependent_params": ["SecretaryIdle4"] 331 | } 332 | }, 333 | "SecretaryIdle4Note": { 334 | "type": "dependency", 335 | "dependency": { 336 | "type": "keep_always", 337 | "dependent_params": ["SecretaryIdle4"] 338 | } 339 | }, 340 | "SecretaryIdle5TL": { 341 | "type": "dependency", 342 | "dependency": { 343 | "type": "keep_always", 344 | "dependent_params": ["SecretaryIdle5"] 345 | } 346 | }, 347 | "SecretaryIdle5Note": { 348 | "type": "dependency", 349 | "dependency": { 350 | "type": "keep_always", 351 | "dependent_params": ["SecretaryIdle5"] 352 | } 353 | }, 354 | "SecretaryIdle6TL": { 355 | "type": "dependency", 356 | "dependency": { 357 | "type": "keep_always", 358 | "dependent_params": ["SecretaryIdle6"] 359 | } 360 | }, 361 | "SecretaryIdle6Note": { 362 | "type": "dependency", 363 | "dependency": { 364 | "type": "keep_always", 365 | "dependent_params": ["SecretaryIdle6"] 366 | } 367 | }, 368 | "SecretaryIdle7TL": { 369 | "type": "dependency", 370 | "dependency": { 371 | "type": "keep_always", 372 | "dependent_params": ["SecretaryIdle7"] 373 | } 374 | }, 375 | "SecretaryIdle7Note": { 376 | "type": "dependency", 377 | "dependency": { 378 | "type": "keep_always", 379 | "dependent_params": ["SecretaryIdle7"] 380 | } 381 | }, 382 | "SecretaryIdle8TL": { 383 | "type": "dependency", 384 | "dependency": { 385 | "type": "keep_always", 386 | "dependent_params": ["SecretaryIdle8"] 387 | } 388 | }, 389 | "SecretaryIdle8Note": { 390 | "type": "dependency", 391 | "dependency": { 392 | "type": "keep_always", 393 | "dependent_params": ["SecretaryIdle8"] 394 | } 395 | }, 396 | "SecretaryTouchTL": { 397 | "type": "dependency", 398 | "dependency": { 399 | "type": "keep_always", 400 | "dependent_params": ["SecretaryTouch"] 401 | } 402 | }, 403 | "SecretaryTouchNote": { 404 | "type": "dependency", 405 | "dependency": { 406 | "type": "keep_always", 407 | "dependent_params": ["SecretaryTouch"] 408 | } 409 | }, 410 | "SpecialTouchTL": { 411 | "type": "dependency", 412 | "dependency": { 413 | "type": "keep_always", 414 | "dependent_params": ["SpecialTouch"] 415 | } 416 | }, 417 | "SpecialTouchNote": { 418 | "type": "dependency", 419 | "dependency": { 420 | "type": "keep_always", 421 | "dependent_params": ["SpecialTouch"] 422 | } 423 | }, 424 | "HeadpatTL": { 425 | "type": "dependency", 426 | "dependency": { 427 | "type": "keep_always", 428 | "dependent_params": ["Headpat"] 429 | } 430 | }, 431 | "HeadpatNote": { 432 | "type": "dependency", 433 | "dependency": { 434 | "type": "keep_always", 435 | "dependent_params": ["Headpat"] 436 | } 437 | }, 438 | "TaskTL": { 439 | "type": "dependency", 440 | "dependency": { 441 | "type": "keep_always", 442 | "dependent_params": ["Task"] 443 | } 444 | }, 445 | "TaskNote": { 446 | "type": "dependency", 447 | "dependency": { 448 | "type": "keep_always", 449 | "dependent_params": ["Task"] 450 | } 451 | }, 452 | "TaskCompleteTL": { 453 | "type": "dependency", 454 | "dependency": { 455 | "type": "keep_always", 456 | "dependent_params": ["TaskComplete"] 457 | } 458 | }, 459 | "TaskCompleteNote": { 460 | "type": "dependency", 461 | "dependency": { 462 | "type": "keep_always", 463 | "dependent_params": ["TaskComplete"] 464 | } 465 | }, 466 | "MailTL": { 467 | "type": "dependency", 468 | "dependency": { 469 | "type": "keep_always", 470 | "dependent_params": ["Mail"] 471 | } 472 | }, 473 | "MailNote": { 474 | "type": "dependency", 475 | "dependency": { 476 | "type": "keep_always", 477 | "dependent_params": ["Mail"] 478 | } 479 | }, 480 | "MissionFinishedTL": { 481 | "type": "dependency", 482 | "dependency": { 483 | "type": "keep_always", 484 | "dependent_params": ["MissionFinished"] 485 | } 486 | }, 487 | "MissionFinishedNote": { 488 | "type": "dependency", 489 | "dependency": { 490 | "type": "keep_always", 491 | "dependent_params": ["MissionFinished"] 492 | } 493 | }, 494 | "CommissionTL": { 495 | "type": "dependency", 496 | "dependency": { 497 | "type": "keep_always", 498 | "dependent_params": ["Commission"] 499 | } 500 | }, 501 | "CommissionNote": { 502 | "type": "dependency", 503 | "dependency": { 504 | "type": "keep_always", 505 | "dependent_params": ["Commission"] 506 | } 507 | }, 508 | "StrengtheningTL": { 509 | "type": "dependency", 510 | "dependency": { 511 | "type": "keep_always", 512 | "dependent_params": ["Strengthening"] 513 | } 514 | }, 515 | "StrengtheningNote": { 516 | "type": "dependency", 517 | "dependency": { 518 | "type": "keep_always", 519 | "dependent_params": ["Strengthening"] 520 | } 521 | }, 522 | "MissionStartTL": { 523 | "type": "dependency", 524 | "dependency": { 525 | "type": "keep_always", 526 | "dependent_params": ["MissionStart"] 527 | } 528 | }, 529 | "MissionStartNote": { 530 | "type": "dependency", 531 | "dependency": { 532 | "type": "keep_always", 533 | "dependent_params": ["MissionStart"] 534 | } 535 | }, 536 | "MVPTL": { 537 | "type": "dependency", 538 | "dependency": { 539 | "type": "keep_always", 540 | "dependent_params": ["MVP"] 541 | } 542 | }, 543 | "MVPNote": { 544 | "type": "dependency", 545 | "dependency": { 546 | "type": "keep_always", 547 | "dependent_params": ["MVP"] 548 | } 549 | }, 550 | "DefeatTL": { 551 | "type": "dependency", 552 | "dependency": { 553 | "type": "keep_always", 554 | "dependent_params": ["Defeat"] 555 | } 556 | }, 557 | "DefeatNote": { 558 | "type": "dependency", 559 | "dependency": { 560 | "type": "keep_always", 561 | "dependent_params": ["Defeat"] 562 | } 563 | }, 564 | "SkillActivationTL": { 565 | "type": "dependency", 566 | "dependency": { 567 | "type": "keep_always", 568 | "dependent_params": ["SkillActivation"] 569 | } 570 | }, 571 | "SkillActivationNote": { 572 | "type": "dependency", 573 | "dependency": { 574 | "type": "keep_always", 575 | "dependent_params": ["SkillActivation"] 576 | } 577 | }, 578 | "LowHPTL": { 579 | "type": "dependency", 580 | "dependency": { 581 | "type": "keep_always", 582 | "dependent_params": ["LowHP"] 583 | } 584 | }, 585 | "LowHPNote": { 586 | "type": "dependency", 587 | "dependency": { 588 | "type": "keep_always", 589 | "dependent_params": ["LowHP"] 590 | } 591 | }, 592 | "AffinityDisappointedTL": { 593 | "type": "dependency", 594 | "dependency": { 595 | "type": "keep_always", 596 | "dependent_params": ["AffinityDisappointed"] 597 | } 598 | }, 599 | "AffinityDisappointedNote": { 600 | "type": "dependency", 601 | "dependency": { 602 | "type": "keep_always", 603 | "dependent_params": ["AffinityDisappointed"] 604 | } 605 | }, 606 | "AffinityStrangerTL": { 607 | "type": "dependency", 608 | "dependency": { 609 | "type": "keep_always", 610 | "dependent_params": ["AffinityStranger"] 611 | } 612 | }, 613 | "AffinityStrangerNote": { 614 | "type": "dependency", 615 | "dependency": { 616 | "type": "keep_always", 617 | "dependent_params": ["AffinityStranger"] 618 | } 619 | }, 620 | "AffinityFriendlyTL": { 621 | "type": "dependency", 622 | "dependency": { 623 | "type": "keep_always", 624 | "dependent_params": ["AffinityFriendly"] 625 | } 626 | }, 627 | "AffinityFriendlyNote": { 628 | "type": "dependency", 629 | "dependency": { 630 | "type": "keep_always", 631 | "dependent_params": ["AffinityFriendly"] 632 | } 633 | }, 634 | "AffinityLikeTL": { 635 | "type": "dependency", 636 | "dependency": { 637 | "type": "keep_always", 638 | "dependent_params": ["AffinityLike"] 639 | } 640 | }, 641 | "AffinityLikeNote": { 642 | "type": "dependency", 643 | "dependency": { 644 | "type": "keep_always", 645 | "dependent_params": ["AffinityLike"] 646 | } 647 | }, 648 | "AffinityLoveTL": { 649 | "type": "dependency", 650 | "dependency": { 651 | "type": "keep_always", 652 | "dependent_params": ["AffinityLove"] 653 | } 654 | }, 655 | "AffinityLoveNote": { 656 | "type": "dependency", 657 | "dependency": { 658 | "type": "keep_always", 659 | "dependent_params": ["AffinityLove"] 660 | } 661 | }, 662 | "PledgeTL": { 663 | "type": "dependency", 664 | "dependency": { 665 | "type": "keep_always", 666 | "dependent_params": ["Pledge"] 667 | } 668 | }, 669 | "PledgeNote": { 670 | "type": "dependency", 671 | "dependency": { 672 | "type": "keep_always", 673 | "dependent_params": ["Pledge"] 674 | } 675 | }, 676 | "Additional1TL": { 677 | "type": "dependency", 678 | "dependency": { 679 | "type": "keep_always", 680 | "dependent_params": ["Additional1"] 681 | } 682 | }, 683 | "Additional1Note": { 684 | "type": "dependency", 685 | "dependency": { 686 | "type": "keep_always", 687 | "dependent_params": ["Additional1"] 688 | } 689 | }, 690 | "Additional2TL": { 691 | "type": "dependency", 692 | "dependency": { 693 | "type": "keep_always", 694 | "dependent_params": ["Additional2"] 695 | } 696 | }, 697 | "Additional2Note": { 698 | "type": "dependency", 699 | "dependency": { 700 | "type": "keep_always", 701 | "dependent_params": ["Additional2"] 702 | } 703 | }, 704 | "Additional3TL": { 705 | "type": "dependency", 706 | "dependency": { 707 | "type": "keep_always", 708 | "dependent_params": ["Additional3"] 709 | } 710 | }, 711 | "Additional3Note": { 712 | "type": "dependency", 713 | "dependency": { 714 | "type": "keep_always", 715 | "dependent_params": ["Additional3"] 716 | } 717 | }, 718 | "Additional4TL": { 719 | "type": "dependency", 720 | "dependency": { 721 | "type": "keep_always", 722 | "dependent_params": ["Additional4"] 723 | } 724 | }, 725 | "Additional4Note": { 726 | "type": "dependency", 727 | "dependency": { 728 | "type": "keep_always", 729 | "dependent_params": ["Additional4"] 730 | } 731 | }, 732 | "Additional5TL": { 733 | "type": "dependency", 734 | "dependency": { 735 | "type": "keep_always", 736 | "dependent_params": ["Additional5"] 737 | } 738 | }, 739 | "Additional5Note": { 740 | "type": "dependency", 741 | "dependency": { 742 | "type": "keep_always", 743 | "dependent_params": ["Additional5"] 744 | } 745 | }, 746 | "Additional6TL": { 747 | "type": "dependency", 748 | "dependency": { 749 | "type": "keep_always", 750 | "dependent_params": ["Additional6"] 751 | } 752 | }, 753 | "Additional6Note": { 754 | "type": "dependency", 755 | "dependency": { 756 | "type": "keep_always", 757 | "dependent_params": ["Additional6"] 758 | } 759 | }, 760 | "Additional7TL": { 761 | "type": "dependency", 762 | "dependency": { 763 | "type": "keep_always", 764 | "dependent_params": ["Additional7"] 765 | } 766 | }, 767 | "Additional7Note": { 768 | "type": "dependency", 769 | "dependency": { 770 | "type": "keep_always", 771 | "dependent_params": ["Additional7"] 772 | } 773 | }, 774 | "Additional8TL": { 775 | "type": "dependency", 776 | "dependency": { 777 | "type": "keep_always", 778 | "dependent_params": ["Additional8"] 779 | } 780 | }, 781 | "Additional8Note": { 782 | "type": "dependency", 783 | "dependency": { 784 | "type": "keep_always", 785 | "dependent_params": ["Additional8"] 786 | } 787 | }, 788 | "Additional9TL": { 789 | "type": "dependency", 790 | "dependency": { 791 | "type": "keep_always", 792 | "dependent_params": ["Additional9"] 793 | } 794 | }, 795 | "Additional9Note": { 796 | "type": "dependency", 797 | "dependency": { 798 | "type": "keep_always", 799 | "dependent_params": ["Additional9"] 800 | } 801 | }, 802 | "Additional10TL": { 803 | "type": "dependency", 804 | "dependency": { 805 | "type": "keep_always", 806 | "dependent_params": ["Additional10"] 807 | } 808 | }, 809 | "Additional10Note": { 810 | "type": "dependency", 811 | "dependency": { 812 | "type": "keep_always", 813 | "dependent_params": ["Additional10"] 814 | } 815 | }, 816 | "Additional11TL": { 817 | "type": "dependency", 818 | "dependency": { 819 | "type": "keep_always", 820 | "dependent_params": ["Additional11"] 821 | } 822 | }, 823 | "Additional11Note": { 824 | "type": "dependency", 825 | "dependency": { 826 | "type": "keep_always", 827 | "dependent_params": ["Additional11"] 828 | } 829 | }, 830 | "Additional12TL": { 831 | "type": "dependency", 832 | "dependency": { 833 | "type": "keep_always", 834 | "dependent_params": ["Additional12"] 835 | } 836 | }, 837 | "Additional12Note": { 838 | "type": "dependency", 839 | "dependency": { 840 | "type": "keep_always", 841 | "dependent_params": ["Additional12"] 842 | } 843 | }, 844 | "Valentine18TL": { 845 | "type": "dependency", 846 | "dependency": { 847 | "type": "keep_always", 848 | "dependent_params": ["Valentine18"] 849 | } 850 | }, 851 | "Valentine18Note": { 852 | "type": "dependency", 853 | "dependency": { 854 | "type": "keep_always", 855 | "dependent_params": ["Valentine18"] 856 | } 857 | }, 858 | "Valentine19TL": { 859 | "type": "dependency", 860 | "dependency": { 861 | "type": "keep_always", 862 | "dependent_params": ["Valentine19"] 863 | } 864 | }, 865 | "Valentine19Note": { 866 | "type": "dependency", 867 | "dependency": { 868 | "type": "keep_always", 869 | "dependent_params": ["Valentine19"] 870 | } 871 | }, 872 | "Valentine20TL": { 873 | "type": "dependency", 874 | "dependency": { 875 | "type": "keep_always", 876 | "dependent_params": ["Valentine20"] 877 | } 878 | }, 879 | "Valentine20Note": { 880 | "type": "dependency", 881 | "dependency": { 882 | "type": "keep_always", 883 | "dependent_params": ["Valentine20"] 884 | } 885 | } 886 | }, 887 | "prefer-wiki-data": [] 888 | } -------------------------------------------------------------------------------- /PythonScripts/templates/ShipQuoteEN.json: -------------------------------------------------------------------------------- 1 | { 2 | "template_name": "ShipQuote", 3 | "template_sections": { 4 | "comment": "", 5 | "sections": [ 6 | { 7 | "comment": "", 8 | "params": ["Region", "Voice", "Skin"] 9 | }, 10 | { 11 | "comment": "", 12 | "params": ["ShipDescription", "ShipDescriptionNote"] 13 | }, 14 | { 15 | "comment": "", 16 | "params": ["SelfIntro", "SelfIntroNote"] 17 | }, 18 | { 19 | "comment": "", 20 | "params": ["Acquisition", "AcquisitionNote"] 21 | }, 22 | { 23 | "comment": "", 24 | "params": ["Login", "LoginNote"] 25 | }, 26 | { 27 | "comment": "", 28 | "params": ["Details", "DetailsNote"] 29 | }, 30 | { 31 | "comment": "", 32 | "params": ["SecretaryIdle1", "SecretaryIdle1Note"] 33 | }, 34 | { 35 | "comment": "", 36 | "params": ["SecretaryIdle2", "SecretaryIdle2Note"] 37 | }, 38 | { 39 | "comment": "", 40 | "params": ["SecretaryIdle3", "SecretaryIdle3Note"] 41 | }, 42 | { 43 | "comment": "", 44 | "params": ["SecretaryIdle4", "SecretaryIdle4Note"] 45 | }, 46 | { 47 | "comment": "", 48 | "params": ["SecretaryIdle5", "SecretaryIdle5Note"] 49 | }, 50 | { 51 | "comment": "", 52 | "params": ["SecretaryIdle6", "SecretaryIdle6Note"] 53 | }, 54 | { 55 | "comment": "", 56 | "params": ["SecretaryIdle7", "SecretaryIdle7Note"] 57 | }, 58 | { 59 | "comment": "", 60 | "params": ["SecretaryIdle8", "SecretaryIdle8Note"] 61 | }, 62 | { 63 | "comment": "", 64 | "params": ["SecretaryTouch", "SecretaryTouchNote"] 65 | }, 66 | { 67 | "comment": "", 68 | "params": ["SpecialTouch", "SpecialTouchNote"] 69 | }, 70 | { 71 | "comment": "", 72 | "params": ["Headpat", "HeadpatNote"] 73 | }, 74 | { 75 | "comment": "", 76 | "params": ["Task", "TaskNote"] 77 | }, 78 | { 79 | "comment": "", 80 | "params": ["TaskComplete", "TaskCompleteNote"] 81 | }, 82 | { 83 | "comment": "", 84 | "params": ["Mail", "MailNote"] 85 | }, 86 | { 87 | "comment": "", 88 | "params": ["MissionFinished", "MissionFinishedNote"] 89 | }, 90 | { 91 | "comment": "", 92 | "params": ["Commission", "CommissionNote"] 93 | }, 94 | { 95 | "comment": "", 96 | "params": ["Strengthening", "StrengtheningNote"] 97 | }, 98 | { 99 | "comment": "", 100 | "params": ["MissionStart", "MissionStartNote"] 101 | }, 102 | { 103 | "comment": "", 104 | "params": ["MVP", "MVPNote"] 105 | }, 106 | { 107 | "comment": "", 108 | "params": ["Defeat", "DefeatNote"] 109 | }, 110 | { 111 | "comment": "", 112 | "params": ["SkillActivation", "SkillActivationNote"] 113 | }, 114 | { 115 | "comment": "", 116 | "params": ["LowHP", "LowHPNote"] 117 | }, 118 | { 119 | "comment": "", 120 | "params": ["AffinityDisappointed", "AffinityDisappointedNote"] 121 | }, 122 | { 123 | "comment": "", 124 | "params": ["AffinityStranger", "AffinityStrangerNote"] 125 | }, 126 | { 127 | "comment": "", 128 | "params": ["AffinityFriendly", "AffinityFriendlyNote"] 129 | }, 130 | { 131 | "comment": "", 132 | "params": ["AffinityLike", "AffinityLikeNote"] 133 | }, 134 | { 135 | "comment": "", 136 | "params": ["AffinityLove", "AffinityLoveNote"] 137 | }, 138 | { 139 | "comment": "", 140 | "params": ["Pledge", "PledgeNote"] 141 | }, 142 | { 143 | "comment": "", 144 | "params": ["Additional1", "Additional1Note"] 145 | }, 146 | { 147 | "comment": "", 148 | "params": ["Additional2", "Additional2Note"] 149 | }, 150 | { 151 | "comment": "", 152 | "params": ["Additional3", "Additional3Note"] 153 | }, 154 | { 155 | "comment": "", 156 | "params": ["Additional4", "Additional4Note"] 157 | }, 158 | { 159 | "comment": "", 160 | "params": ["Additional5", "Additional5Note"] 161 | }, 162 | { 163 | "comment": "", 164 | "params": ["Additional6", "Additional6Note"] 165 | }, 166 | { 167 | "comment": "", 168 | "params": ["Additional7", "Additional7Note"] 169 | }, 170 | { 171 | "comment": "", 172 | "params": ["Additional8", "Additional8Note"] 173 | }, 174 | { 175 | "comment": "", 176 | "params": ["Additional9", "Additional9Note"] 177 | }, 178 | { 179 | "comment": "", 180 | "params": ["Additional10", "Additional10Note"] 181 | }, 182 | { 183 | "comment": "", 184 | "params": ["Additional11", "Additional11Note"] 185 | }, 186 | { 187 | "comment": "", 188 | "params": ["Additional12", "Additional12Note"] 189 | }, 190 | { 191 | "comment": "", 192 | "params": ["Valentine18", "Valentine18Note"] 193 | }, 194 | { 195 | "comment": "", 196 | "params": ["Valentine19", "Valentine19Note"] 197 | }, 198 | { 199 | "comment": "", 200 | "params": ["Valentine20", "Valentine20Note"] 201 | } 202 | ] 203 | }, 204 | "behavior": { 205 | "default": { 206 | "type": "remove_empty" 207 | }, 208 | "Region": { 209 | "type": "keep" 210 | }, 211 | "Skin": { 212 | "type": "keep" 213 | }, 214 | "ShipDescriptionNote": { 215 | "type": "dependency", 216 | "dependency": { 217 | "type": "keep_always", 218 | "dependent_params": ["ShipDescription"] 219 | } 220 | }, 221 | "SelfIntroNote": { 222 | "type": "dependency", 223 | "dependency": { 224 | "type": "keep_always", 225 | "dependent_params": ["SelfIntro"] 226 | } 227 | }, 228 | "AcquisitionNote": { 229 | "type": "dependency", 230 | "dependency": { 231 | "type": "keep_always", 232 | "dependent_params": ["Acquisition"] 233 | } 234 | }, 235 | "LoginNote": { 236 | "type": "dependency", 237 | "dependency": { 238 | "type": "keep_always", 239 | "dependent_params": ["Login"] 240 | } 241 | }, 242 | "DetailsNote": { 243 | "type": "dependency", 244 | "dependency": { 245 | "type": "keep_always", 246 | "dependent_params": ["Details"] 247 | } 248 | }, 249 | "SecretaryIdle1Note": { 250 | "type": "dependency", 251 | "dependency": { 252 | "type": "keep_always", 253 | "dependent_params": ["SecretaryIdle1"] 254 | } 255 | }, 256 | "SecretaryIdle2Note": { 257 | "type": "dependency", 258 | "dependency": { 259 | "type": "keep_always", 260 | "dependent_params": ["SecretaryIdle2"] 261 | } 262 | }, 263 | "SecretaryIdle3Note": { 264 | "type": "dependency", 265 | "dependency": { 266 | "type": "keep_always", 267 | "dependent_params": ["SecretaryIdle3"] 268 | } 269 | }, 270 | "SecretaryIdle4Note": { 271 | "type": "dependency", 272 | "dependency": { 273 | "type": "keep_always", 274 | "dependent_params": ["SecretaryIdle4"] 275 | } 276 | }, 277 | "SecretaryIdle5Note": { 278 | "type": "dependency", 279 | "dependency": { 280 | "type": "keep_always", 281 | "dependent_params": ["SecretaryIdle5"] 282 | } 283 | }, 284 | "SecretaryIdle6Note": { 285 | "type": "dependency", 286 | "dependency": { 287 | "type": "keep_always", 288 | "dependent_params": ["SecretaryIdle6"] 289 | } 290 | }, 291 | "SecretaryIdle7Note": { 292 | "type": "dependency", 293 | "dependency": { 294 | "type": "keep_always", 295 | "dependent_params": ["SecretaryIdle7"] 296 | } 297 | }, 298 | "SecretaryIdle8Note": { 299 | "type": "dependency", 300 | "dependency": { 301 | "type": "keep_always", 302 | "dependent_params": ["SecretaryIdle8"] 303 | } 304 | }, 305 | "SecretaryTouchNote": { 306 | "type": "dependency", 307 | "dependency": { 308 | "type": "keep_always", 309 | "dependent_params": ["SecretaryTouch"] 310 | } 311 | }, 312 | "SpecialTouchNote": { 313 | "type": "dependency", 314 | "dependency": { 315 | "type": "keep_always", 316 | "dependent_params": ["SpecialTouch"] 317 | } 318 | }, 319 | "HeadpatNote": { 320 | "type": "dependency", 321 | "dependency": { 322 | "type": "keep_always", 323 | "dependent_params": ["Headpat"] 324 | } 325 | }, 326 | "TaskNote": { 327 | "type": "dependency", 328 | "dependency": { 329 | "type": "keep_always", 330 | "dependent_params": ["Task"] 331 | } 332 | }, 333 | "TaskCompleteNote": { 334 | "type": "dependency", 335 | "dependency": { 336 | "type": "keep_always", 337 | "dependent_params": ["TaskComplete"] 338 | } 339 | }, 340 | "MailNote": { 341 | "type": "dependency", 342 | "dependency": { 343 | "type": "keep_always", 344 | "dependent_params": ["Mail"] 345 | } 346 | }, 347 | "MissionFinishedNote": { 348 | "type": "dependency", 349 | "dependency": { 350 | "type": "keep_always", 351 | "dependent_params": ["MissionFinished"] 352 | } 353 | }, 354 | "CommissionNote": { 355 | "type": "dependency", 356 | "dependency": { 357 | "type": "keep_always", 358 | "dependent_params": ["Commission"] 359 | } 360 | }, 361 | "StrengtheningNote": { 362 | "type": "dependency", 363 | "dependency": { 364 | "type": "keep_always", 365 | "dependent_params": ["Strengthening"] 366 | } 367 | }, 368 | "MissionStartNote": { 369 | "type": "dependency", 370 | "dependency": { 371 | "type": "keep_always", 372 | "dependent_params": ["MissionStart"] 373 | } 374 | }, 375 | "MVPNote": { 376 | "type": "dependency", 377 | "dependency": { 378 | "type": "keep_always", 379 | "dependent_params": ["MVP"] 380 | } 381 | }, 382 | "DefeatNote": { 383 | "type": "dependency", 384 | "dependency": { 385 | "type": "keep_always", 386 | "dependent_params": ["Defeat"] 387 | } 388 | }, 389 | "SkillActivationNote": { 390 | "type": "dependency", 391 | "dependency": { 392 | "type": "keep_always", 393 | "dependent_params": ["SkillActivation"] 394 | } 395 | }, 396 | "LowHPNote": { 397 | "type": "dependency", 398 | "dependency": { 399 | "type": "keep_always", 400 | "dependent_params": ["LowHP"] 401 | } 402 | }, 403 | "AffinityDisappointedNote": { 404 | "type": "dependency", 405 | "dependency": { 406 | "type": "keep_always", 407 | "dependent_params": ["AffinityDisappointed"] 408 | } 409 | }, 410 | "AffinityStrangerNote": { 411 | "type": "dependency", 412 | "dependency": { 413 | "type": "keep_always", 414 | "dependent_params": ["AffinityStranger"] 415 | } 416 | }, 417 | "AffinityFriendlyNote": { 418 | "type": "dependency", 419 | "dependency": { 420 | "type": "keep_always", 421 | "dependent_params": ["AffinityFriendly"] 422 | } 423 | }, 424 | "AffinityLikeNote": { 425 | "type": "dependency", 426 | "dependency": { 427 | "type": "keep_always", 428 | "dependent_params": ["AffinityLike"] 429 | } 430 | }, 431 | "AffinityLoveNote": { 432 | "type": "dependency", 433 | "dependency": { 434 | "type": "keep_always", 435 | "dependent_params": ["AffinityLove"] 436 | } 437 | }, 438 | "PledgeNote": { 439 | "type": "dependency", 440 | "dependency": { 441 | "type": "keep_always", 442 | "dependent_params": ["Pledge"] 443 | } 444 | }, 445 | "Additional1Note": { 446 | "type": "dependency", 447 | "dependency": { 448 | "type": "keep_always", 449 | "dependent_params": ["Additional1"] 450 | } 451 | }, 452 | "Additional2Note": { 453 | "type": "dependency", 454 | "dependency": { 455 | "type": "keep_always", 456 | "dependent_params": ["Additional2"] 457 | } 458 | }, 459 | "Additional3Note": { 460 | "type": "dependency", 461 | "dependency": { 462 | "type": "keep_always", 463 | "dependent_params": ["Additional3"] 464 | } 465 | }, 466 | "Additional4Note": { 467 | "type": "dependency", 468 | "dependency": { 469 | "type": "keep_always", 470 | "dependent_params": ["Additional4"] 471 | } 472 | }, 473 | "Additional5Note": { 474 | "type": "dependency", 475 | "dependency": { 476 | "type": "keep_always", 477 | "dependent_params": ["Additional5"] 478 | } 479 | }, 480 | "Additional6Note": { 481 | "type": "dependency", 482 | "dependency": { 483 | "type": "keep_always", 484 | "dependent_params": ["Additional6"] 485 | } 486 | }, 487 | "Additional7Note": { 488 | "type": "dependency", 489 | "dependency": { 490 | "type": "keep_always", 491 | "dependent_params": ["Additional7"] 492 | } 493 | }, 494 | "Additional8Note": { 495 | "type": "dependency", 496 | "dependency": { 497 | "type": "keep_always", 498 | "dependent_params": ["Additional8"] 499 | } 500 | }, 501 | "Additional9Note": { 502 | "type": "dependency", 503 | "dependency": { 504 | "type": "keep_always", 505 | "dependent_params": ["Additional9"] 506 | } 507 | }, 508 | "Additional10Note": { 509 | "type": "dependency", 510 | "dependency": { 511 | "type": "keep_always", 512 | "dependent_params": ["Additional10"] 513 | } 514 | }, 515 | "Additional11Note": { 516 | "type": "dependency", 517 | "dependency": { 518 | "type": "keep_always", 519 | "dependent_params": ["Additional11"] 520 | } 521 | }, 522 | "Additional12Note": { 523 | "type": "dependency", 524 | "dependency": { 525 | "type": "keep_always", 526 | "dependent_params": ["Additional12"] 527 | } 528 | }, 529 | "Valentine18Note": { 530 | "type": "dependency", 531 | "dependency": { 532 | "type": "keep_always", 533 | "dependent_params": ["Valentine18"] 534 | } 535 | }, 536 | "Valentine19Note": { 537 | "type": "dependency", 538 | "dependency": { 539 | "type": "keep_always", 540 | "dependent_params": ["Valentine19"] 541 | } 542 | }, 543 | "Valentine20Note": { 544 | "type": "dependency", 545 | "dependency": { 546 | "type": "keep_always", 547 | "dependent_params": ["Valentine20"] 548 | } 549 | } 550 | }, 551 | "prefer-wiki-data": [] 552 | } -------------------------------------------------------------------------------- /PythonScripts/templates/ShipSkin.json: -------------------------------------------------------------------------------- 1 | { 2 | "template_name": "ShipSkin", 3 | "template_sections": { 4 | "comment": "", 5 | "sections": [ 6 | { 7 | "comment": "", 8 | "params": ["NoTabber", "ShipName", "SkinID", "SkinName", "SkinNameTL", "SkinNameEN", "SkinNameCN", "SkinNameCNTL", "SkinNameJP", "SkinNameJPTL"] 9 | }, 10 | { 11 | "comment": "", 12 | "params": ["SkinCategory", "CategoryOverride", "Background", "SpecialBackground", "BGImage", "TabType1", "SkinCategory1", "TabName1", "TabType2", "SkinCategory2", "TabName2", "TabType3", "SkinCategory3", "TabName3", "TabType4", "SkinCategory4", "TabName4", "TabType5", "SkinCategory5", "TabName5"] 13 | }, 14 | { 15 | "comment": "", 16 | "params": ["Cost", "LimitedEN", "LimitedCN", "LimitedJP", "Live2D", "Music"] 17 | }, 18 | { 19 | "comment": "", 20 | "params": ["EventName", "EventPage", "ActivityName", "EventCurrency"] 21 | } 22 | ] 23 | }, 24 | "behavior": { 25 | "default": { 26 | "type": "remove_empty" 27 | }, 28 | "SkinID": { 29 | "type": "keep" 30 | }, 31 | "SkinCategory": { 32 | "type": "keep" 33 | }, 34 | "EventPage": { 35 | "type": "dependency", 36 | "dependency": { 37 | "type": "keep_empty", 38 | "dependent_params": ["EventName"] 39 | } 40 | } 41 | }, 42 | "prefer-wiki-data": [] 43 | } -------------------------------------------------------------------------------- /PythonScripts/templates/ShipSkin0.json: -------------------------------------------------------------------------------- 1 | { 2 | "template_name": "ShipSkin", 3 | "template_sections": { 4 | "comment": "", 5 | "sections": [ 6 | { 7 | "comment": "", 8 | "params": ["NoTabber", "ShipName", "SkinID", "SpecialBackground", "BGImage", "TabType1", "SkinCategory1", "TabName1", "Left1", "TabType2", "SkinCategory2", "TabName2", "Left2", "TabType3", "SkinCategory3", "TabName3", "Left3", "TabType4", "SkinCategory4", "TabName4", "Left4", "TabType5", "SkinCategory5", "TabName5", "Left5", "Live2D", "Music"] 9 | } 10 | ] 11 | }, 12 | "behavior": { 13 | "default": { 14 | "type": "remove_empty" 15 | }, 16 | "SkinID": { 17 | "type": "keep" 18 | } 19 | }, 20 | "prefer-wiki-data": [] 21 | } -------------------------------------------------------------------------------- /PythonScripts/templates/Story.json: -------------------------------------------------------------------------------- 1 | { 2 | "template_name": "Story", 3 | "template_sections": { 4 | "comment": "", 5 | "sections": [ 6 | { 7 | "comment": "", 8 | "params": ["Title", "Unlock", "Language"] 9 | } 10 | ] 11 | }, 12 | "behavior": { 13 | "default": { 14 | "type": "keep" 15 | } 16 | }, 17 | "prefer-wiki-data": [] 18 | } -------------------------------------------------------------------------------- /PythonScripts/templates/Template.json: -------------------------------------------------------------------------------- 1 | { 2 | "template_name": "", 3 | "template_sections": { 4 | "comment": "", 5 | "sections": [ 6 | { 7 | "comment": "", 8 | "sections": [ 9 | { 10 | "comment": "", 11 | "params": [] 12 | } 13 | ] 14 | } 15 | ] 16 | }, 17 | "behavior": { 18 | "default": { 19 | "type": "keep" 20 | }, 21 | "SomeParam": { 22 | "type": "dependency", 23 | "dependency": { 24 | "type": "keep_empty", 25 | "dependent_params": [] 26 | } 27 | } 28 | }, 29 | "prefer-wiki-data": [] 30 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AzurLaneTools 2 | → *the download and extraction tool has been moved to the new [Asset Downloader](https://github.com/nobbyfix/AzurLane-AssetDownloader) repo* 3 | 4 | A collection of tools i use to work with AzurLane game data. 5 | Feel free to create pull requests and issues to let me know about problems or just to improve ugly code. 6 | 7 | ## Additional Dependencies 8 | Lua 5.1 (with cjson) 9 | Python 3.11 (or higher) 10 | -------------------------------------------------------------------------------- /lua_convert.py: -------------------------------------------------------------------------------- 1 | import json 2 | import subprocess 3 | from enum import Enum 4 | from pathlib import Path 5 | 6 | 7 | class Client(Enum): 8 | locale_code: str 9 | 10 | def __new__(cls, value, locale): 11 | obj = object.__new__(cls) 12 | obj._value_ = value 13 | obj.locale_code = locale 14 | return obj 15 | 16 | EN = (1, "en-US") 17 | CN = (2, "zh-CN") 18 | JP = (3, "ja-JP") 19 | KR = (4, "ko-KR") 20 | TW = (5, "zh-TW") 21 | 22 | @staticmethod 23 | def from_locale(locale): 24 | for client in Client: 25 | if client.locale_code == locale: 26 | return client 27 | 28 | 29 | def dump_json(target_path, content): 30 | with open(target_path, "w", encoding="utf8") as f: 31 | json.dump(content, f, indent=2, ensure_ascii=False) 32 | 33 | 34 | def convert_lua(filepath: Path, savedest: Path): 35 | if "sharecfg" in filepath.parts: 36 | # if file is also modified to contain function blocks, remove them 37 | modified_file = False 38 | with open(filepath, "r", encoding="utf8") as f: 39 | content = f.read() 40 | if "function ()" in content and "end()" in content: 41 | content_replaced = content.replace("function ()", "").replace("end()", "") 42 | with open(filepath, "w", encoding="utf8") as f: 43 | f.write(content_replaced) 44 | modified_file = True 45 | 46 | # convert the file 47 | try: 48 | result = subprocess.check_output(["lua", "serializer.lua", 49 | str(filepath.with_suffix("")), filepath.stem], stderr=subprocess.DEVNULL) 50 | except: return 51 | finally: 52 | # revert the file, even if it fails to convert them 53 | if modified_file: 54 | with open(filepath, "w", encoding="utf8") as f: 55 | f.write(content) 56 | 57 | # convert non-sharecfg files with other lua serializer script 58 | else: 59 | try: 60 | result = subprocess.check_output(["lua", "serializer2.lua", 61 | str(filepath.with_suffix(""))], stderr=subprocess.DEVNULL) 62 | except: return 63 | 64 | json_string = result.decode("utf8") 65 | if json_string.startswith("null"): 66 | return 67 | 68 | # parse json, if its empty (or an empty structure), skip 69 | if not (json_data := json.loads(json_string)): 70 | return 71 | 72 | savedest.parent.mkdir(parents=True, exist_ok=True) 73 | dump_json(savedest, json_data) -------------------------------------------------------------------------------- /lua_converter.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | from pathlib import Path 3 | import multiprocessing as mp 4 | 5 | from lua_convert import Client, convert_lua 6 | 7 | 8 | directory_destinations = [ 9 | (Path("sharecfg"), "sharecfg"), 10 | (Path("gamecfg", "buff"), "buff"), 11 | (Path("gamecfg", "dungeon"), "dungeon"), 12 | (Path("gamecfg", "skill"), "skill"), 13 | (Path("gamecfg", "story"), "story"), 14 | (Path("gamecfg", "backyardtheme"), "backyardtheme"), 15 | (Path("gamecfg", "guide"), "guide"), 16 | ] 17 | 18 | 19 | def clear_json_files(client: Client): 20 | client_json_dir = Path("SrcJson", client.name) 21 | shutil.rmtree(client_json_dir, ignore_errors=True) 22 | 23 | def convert_all_files(client: Client): 24 | with mp.Pool(processes=mp.cpu_count()) as pool: 25 | for DIR_FROM, DIR_TO in directory_destinations: 26 | # jp has different folder for story files 27 | if DIR_FROM.name == "story" and client.name == "JP": 28 | DIR_FROM = DIR_FROM.with_name(DIR_FROM.name+"jp") 29 | 30 | # set path constants 31 | CONVERT_FROM = Path("Src", client.locale_code, DIR_FROM) 32 | CONVERT_TO = Path("SrcJson", client.name, DIR_TO) 33 | 34 | # find all files inside the folder 35 | for file in CONVERT_FROM.rglob("*.lua"): 36 | target = Path(CONVERT_TO, file.relative_to(CONVERT_FROM).with_suffix(".json")) 37 | pool.apply_async(convert_lua, (file, target,)) 38 | pool.close() 39 | pool.join() 40 | 41 | def reconvert_all_files(client: Client): 42 | clear_json_files(client) 43 | convert_all_files(client) 44 | 45 | 46 | def main(): 47 | client_input = input("Type the game version to convert: ") 48 | if not client_input in Client.__members__: 49 | print(f"Unknown client {client_input}, aborting.") 50 | return 51 | client = Client[client_input] 52 | reconvert_all_files(client) 53 | 54 | if __name__ == "__main__": 55 | main() 56 | -------------------------------------------------------------------------------- /lua_converter_v2.py: -------------------------------------------------------------------------------- 1 | import re 2 | from pathlib import Path 3 | import multiprocessing as mp 4 | from argparse import ArgumentParser 5 | from git import Repo 6 | 7 | from lua_convert import Client, convert_lua 8 | 9 | 10 | ALLOWED_GAMECFG_FOLDERS = {"buff", "dungeon", "skill", "story", "storyjp", "backyardtheme", "guide"} 11 | 12 | LUA_REPO_NAME = "Src" 13 | JSON_REPO_NAME = "SrcJson" 14 | REGEX = re.compile(r"\[(..-..)\] AZ: (\d+\.\d+\.\d+)") 15 | 16 | 17 | class SrcRepo(Repo): 18 | def pull(self): 19 | """ 20 | Pulls from origin and returns old commit 21 | 22 | :return: the old commit before pulling 23 | """ 24 | print("Pulling new changes from origin...") 25 | pullinfo = self.remotes.origin.pull()[0] 26 | oldcommit = pullinfo.old_commit 27 | if oldcommit is None: print("No new commits available.") 28 | else: print("New commits have been pulled.") 29 | return pullinfo.old_commit 30 | 31 | def pull_and_get_changes(self, override_old_commit=None): 32 | old_commit = self.pull() 33 | 34 | if override_old_commit is not None: old_commit = self.commit(override_old_commit) 35 | if old_commit is None: return 36 | 37 | commits = [] 38 | for commit in self.iter_commits(): 39 | if commit.hexsha == old_commit.hexsha: 40 | break 41 | commits.append(commit) 42 | 43 | for commit in reversed(commits): 44 | changes = self.changes_from_commit(commit) 45 | if changes: yield changes 46 | 47 | def changes_from_commit(self, commit): 48 | """ 49 | :return: the tuple (client, version, files) 50 | where client is a string, version is a string and files a list 51 | 52 | or None, if the commit is not a valid commit 53 | """ 54 | re_result = REGEX.findall(commit.message) 55 | if re_result: 56 | diff_index = commit.parents[0].diff(commit) 57 | files = [diff.b_path for diff in diff_index if diff.change_type in ["A", "M"]] 58 | return *re_result[0], files 59 | 60 | 61 | def is_allowed_gamecfg(path: Path): 62 | return bool(ALLOWED_GAMECFG_FOLDERS.intersection(path.parts)) 63 | 64 | def normalize_gamecfg_paths(path: Path): 65 | if "storyjp" in path.parts: 66 | return Path(str(path).replace("storyjp", "story")) 67 | return path 68 | 69 | 70 | def convert_new_files(override_commit: str = None): 71 | repo = SrcRepo(LUA_REPO_NAME) 72 | repo_json = SrcRepo(JSON_REPO_NAME) 73 | 74 | for client, version, files in repo.pull_and_get_changes(override_commit): 75 | client = Client.from_locale(client) 76 | 77 | with mp.Pool(processes=mp.cpu_count()) as pool: 78 | for filepath in files: 79 | filepath = Path(filepath) 80 | index = 1 81 | if "gamecfg" in filepath.parts: 82 | if not is_allowed_gamecfg(filepath): 83 | continue 84 | index += 1 85 | elif "sharecfg" not in filepath.parts: 86 | continue 87 | # else is sharecfg file 88 | path2 = filepath.relative_to(*filepath.parts[:index]) 89 | 90 | lua_require = Path(LUA_REPO_NAME, filepath) 91 | json_destination = normalize_gamecfg_paths( Path(JSON_REPO_NAME, client.name, path2.with_suffix(".json")) ) 92 | pool.apply_async(convert_lua, (lua_require, json_destination,)) 93 | 94 | # explicitly join the pool 95 | # since the pool only receives async tasks, this waits for their completion 96 | pool.close() 97 | pool.join() 98 | 99 | repo_json.git.add(client.name) # adds only all files inside the current clients directory 100 | repo_json.git.commit("-m", f"{client.name} {version}", "--allow-empty") 101 | repo_json.remotes.origin.push() 102 | 103 | 104 | def main(): 105 | parser = ArgumentParser() 106 | parser.add_argument("-c", "--commit-sha", type=str, help="commit sha from which to start converting") 107 | args = parser.parse_args() 108 | 109 | if sha := args.commit_sha: 110 | convert_new_files(sha) 111 | else: 112 | convert_new_files() 113 | 114 | if __name__ == "__main__": 115 | main() 116 | -------------------------------------------------------------------------------- /serializer.lua: -------------------------------------------------------------------------------- 1 | package.path = package.path .. ";?.lua" 2 | local cjson = require("cjson") 3 | cjson.encode_sparse_array(true) 4 | 5 | uv0 = {} 6 | pg = uv0 7 | require(arg[1]) 8 | 9 | print(cjson.encode(pg[arg[2]])) -------------------------------------------------------------------------------- /serializer2.lua: -------------------------------------------------------------------------------- 1 | package.path = package.path .. ";?.lua" 2 | local cjson = require("cjson") 3 | cjson.encode_sparse_array(true) 4 | 5 | function Vector3(a, b, c) 6 | return {a, b, c} 7 | end 8 | 9 | ltable = require(arg[1]) 10 | print(cjson.encode(ltable)) 11 | --------------------------------------------------------------------------------