@@ -196,6 +196,102 @@ def _write_resources_ts(
196196 print (f"Written resources to { path } " )
197197
198198
199+ def _to_good_key (english_name : str ) -> str :
200+ """Convert an English display name to a GOOD key (PascalCase, no punctuation).
201+
202+ Examples:
203+ Gladiator's Finale -> GladiatorsFinale
204+ Spirit Locket of Boreas -> SpiritLocketOfBoreas
205+ "The Catch" -> TheCatch
206+ """
207+ name = english_name .replace ('"' , "" ).replace ("'" , "" )
208+ words = re .split (r"[^A-Za-z0-9]+" , name )
209+ return "" .join (w .capitalize () for w in words if w )
210+
211+
212+ _DEDUPE_PREFIXES = ("traveler_" , "manekin_" , "manekina_" )
213+
214+
215+ def generate_good_keys_json (project_root : str ) -> None :
216+ """Generate public/good/game-data.json with GOOD-format keys and Chinese names."""
217+ game_dir = os .path .join (project_root , "src" , "data" , "game" )
218+ out_path = os .path .join (project_root , "public" , "good" , "game-data.json" )
219+ os .makedirs (os .path .dirname (out_path ), exist_ok = True )
220+
221+ # --- Characters ---
222+ chars = []
223+ seen_base : set [str ] = set ()
224+ for rarity in (5 , 4 ):
225+ with open (os .path .join (game_dir , f"character_{ rarity } _en.json" ), encoding = "utf-8" ) as f :
226+ en = json .load (f )
227+ with open (os .path .join (game_dir , f"character_{ rarity } _zh.json" ), encoding = "utf-8" ) as f :
228+ zh = json .load (f )
229+ for key in en :
230+ is_variant = any (key .startswith (p ) and key != p .rstrip ("_" ) for p in _DEDUPE_PREFIXES )
231+ base = key .rsplit ("_" , 1 )[0 ] if is_variant else key
232+ if base in seen_base :
233+ continue
234+ seen_base .add (base )
235+ chars .append (
236+ {
237+ "id" : _to_good_key (en [key ]["name" ]),
238+ "rarity" : rarity ,
239+ "names" : {"zh" : zh [key ]["name" ] if key in zh else en [key ]["name" ]},
240+ }
241+ )
242+ chars .sort (key = lambda c : c ["id" ])
243+
244+ # --- Weapons ---
245+ with open (os .path .join (game_dir , "weapon_en.json" ), encoding = "utf-8" ) as f :
246+ w_en = json .load (f )
247+ with open (os .path .join (game_dir , "weapon_zh.json" ), encoding = "utf-8" ) as f :
248+ w_zh = json .load (f )
249+ with open (os .path .join (game_dir , "weapon_stats.json" ), encoding = "utf-8" ) as f :
250+ w_stats = json .load (f )
251+
252+ weapons = []
253+ for key in w_en :
254+ if key not in w_stats or w_stats [key ]["rarity" ] < 3 :
255+ continue
256+ weapons .append (
257+ {
258+ "id" : _to_good_key (w_en [key ]["name" ]),
259+ "rarity" : w_stats [key ]["rarity" ],
260+ "type" : w_stats [key ]["type" ].lower (),
261+ "names" : {"zh" : w_zh [key ]["name" ] if key in w_zh else w_en [key ]["name" ]},
262+ }
263+ )
264+ weapons .sort (key = lambda w : w ["id" ])
265+
266+ # --- Artifact Sets ---
267+ with open (os .path .join (game_dir , "artifact_en.json" ), encoding = "utf-8" ) as f :
268+ a_en = json .load (f )
269+ with open (os .path .join (game_dir , "artifact_zh.json" ), encoding = "utf-8" ) as f :
270+ a_zh = json .load (f )
271+
272+ artifact_sets = []
273+ for key in a_en :
274+ rarity = a_en [key ].get ("rarity" )
275+ if rarity is None or rarity < 4 :
276+ continue
277+ artifact_sets .append (
278+ {
279+ "id" : _to_good_key (a_en [key ]["name" ]),
280+ "rarity" : rarity ,
281+ "names" : {"zh" : a_zh [key ]["name" ] if key in a_zh else a_en [key ]["name" ]},
282+ }
283+ )
284+ artifact_sets .sort (key = lambda s : s ["id" ])
285+
286+ # --- Write ---
287+ result = {"characters" : chars , "weapons" : weapons , "artifactSets" : artifact_sets }
288+ with open (out_path , "w" , encoding = "utf-8" ) as f :
289+ json .dump (result , f , ensure_ascii = False , separators = ("," , ":" ))
290+
291+ print (f"Written { out_path } " )
292+ print (f" { len (chars )} characters, { len (weapons )} weapons, { len (artifact_sets )} artifact sets" )
293+
294+
199295def _write_i18n_game_ts (data_dir : str , i18n_data : dict [str , dict [str , Any ]]) -> None :
200296 """Write src/data/i18n-game.ts with names-only for weapons/artifacts."""
201297 path = os .path .join (data_dir , "i18n-game.ts" )
@@ -427,6 +523,7 @@ def main():
427523 parser .add_argument ("--artifact" , action = "store_true" , help = "Update artifact data" )
428524 parser .add_argument ("--half-set" , action = "store_true" , help = "Recompute half sets only" )
429525 parser .add_argument ("--enka" , action = "store_true" , help = "Generate Enka ID maps" )
526+ parser .add_argument ("--good-keys" , action = "store_true" , help = "Generate GOOD-format keys JSON" )
430527 parser .add_argument (
431528 "--details" ,
432529 action = "store_true" ,
@@ -440,7 +537,14 @@ def main():
440537 args = parser .parse_args ()
441538
442539 # Default to all if no flags provided
443- if not (args .character or args .weapon or args .artifact or args .half_set or args .enka ):
540+ if not (
541+ args .character
542+ or args .weapon
543+ or args .artifact
544+ or args .half_set
545+ or args .enka
546+ or args .good_keys
547+ ):
444548 args .character = True
445549 args .weapon = True
446550 args .artifact = True
@@ -618,6 +722,11 @@ def main():
618722 print ("=== [5/5] Enka Map Generation ===" )
619723 enka .run ()
620724
725+ # 6. GOOD Keys JSON
726+ if args .good_keys :
727+ print ("=== GOOD Keys JSON ===" )
728+ generate_good_keys_json (project_root )
729+
621730
622731if __name__ == "__main__" :
623732 main ()
0 commit comments