├── .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 |
--------------------------------------------------------------------------------