99import csv
1010import platform
1111from pathlib import Path
12+ from typing import Annotated , Literal
1213
1314import vdf
1415
15- from pydantic import BaseModel , ConfigDict
16+ from pydantic import BaseModel , ConfigDict , Field
1617
1718
1819class DEMap (BaseModel ):
@@ -58,6 +59,105 @@ class DEMaps(BaseModel):
5859 map_list : list [DEMap ]
5960
6061
62+ class DECivBuilding (BaseModel ):
63+ model_config = ConfigDict (strict = True , extra = "forbid" )
64+
65+ age_id : Annotated [int , Field (alias = "Age ID" )]
66+ building_id : Annotated [int , Field (alias = "Building ID" )]
67+ building_new_column : Annotated [bool , Field (alias = "Building in new column" )]
68+ building_upgraded_from_id : Annotated [int , Field (alias = "Building upgraded from ID" )]
69+ draw_node_type : Annotated [str , Field (alias = "Draw Node Type" )]
70+ help_string_id : Annotated [int , Field (alias = "Help String ID" )]
71+ link_id : Annotated [int , Field (alias = "Link ID" )]
72+ link_node_type : Annotated [str , Field (alias = "Link Node Type" )]
73+ name : Annotated [str , Field (alias = "Name" )]
74+ name_string_id : Annotated [int , Field (alias = "Name String ID" )]
75+ node_id : Annotated [int , Field (alias = "Node ID" )]
76+ node_status : Annotated [str , Field (alias = "Node Status" )]
77+ node_type : Annotated [str , Field (alias = "Node Type" )]
78+ picture_index : Annotated [int , Field (alias = "Picture Index" )]
79+ prerequisite_ids : Annotated [list [int ], Field (alias = "Prerequisite IDs" )]
80+ prerequisite_types : Annotated [list [str ], Field (alias = "Prerequisite Types" )]
81+ trigger_tech_id : Annotated [int , Field (alias = "Trigger Tech ID" )]
82+ use_type : Annotated [str , Field (alias = "Use Type" )]
83+
84+
85+ class DECivUnit (BaseModel ):
86+ model_config = ConfigDict (strict = True , extra = "forbid" )
87+
88+ age_id : Annotated [int , Field (alias = "Age ID" )]
89+ building_id : Annotated [int , Field (alias = "Building ID" )]
90+ building_new_column : Annotated [bool , Field (alias = "Building in new column" )] = False
91+ building_upgraded_from_id : Annotated [
92+ int , Field (alias = "Building upgraded from ID" )
93+ ] = - 1
94+ draw_node_type : Annotated [str , Field (alias = "Draw Node Type" )]
95+ help_string_id : Annotated [int , Field (alias = "Help String ID" )]
96+ link_id : Annotated [int , Field (alias = "Link ID" )]
97+ link_node_type : Annotated [str , Field (alias = "Link Node Type" )]
98+ name : Annotated [str , Field (alias = "Name" )]
99+ name_string_id : Annotated [int , Field (alias = "Name String ID" )]
100+ node_id : Annotated [int , Field (alias = "Node ID" )]
101+ node_status : Annotated [str , Field (alias = "Node Status" )]
102+ node_type : Annotated [str , Field (alias = "Node Type" )]
103+ picture_index : Annotated [int , Field (alias = "Picture Index" )]
104+ prerequisite_ids : Annotated [list [int ], Field (alias = "Prerequisite IDs" )]
105+ prerequisite_types : Annotated [list [str ], Field (alias = "Prerequisite Types" )]
106+ trigger_tech_id : Annotated [int , Field (alias = "Trigger Tech ID" )]
107+ use_type : Annotated [str , Field (alias = "Use Type" )]
108+
109+
110+ class DECivTechTree (BaseModel ):
111+ model_config = ConfigDict (strict = True , extra = "forbid" )
112+
113+ civ_id : str
114+ civ_techs_buildings : list [DECivBuilding ]
115+ civ_techs_units : list [DECivUnit ]
116+
117+
118+ class DECivTechTrees (BaseModel ):
119+ model_config = ConfigDict (strict = True , extra = "forbid" )
120+
121+ civs : list [DECivTechTree ]
122+
123+
124+ class UnitStringIds (BaseModel ):
125+ model_config = ConfigDict (strict = True , extra = "forbid" )
126+
127+ name : int
128+ description : int
129+
130+
131+ class DECivilization (BaseModel ):
132+ model_config = ConfigDict (strict = True , extra = "forbid" )
133+
134+ internal_name : str
135+ tech_tree_name : str
136+ data_name : str
137+ hud_style : str
138+ tech_tree_image_path : Path | None = None
139+ emblem_image_path : Path | None = None
140+ unique_unit_image_paths : Annotated [list [Path ], Field (default_factory = list [Path ])]
141+ name_string_id : int
142+ computer_name_string_table_offset : int = - 1
143+ unique_tech_id_1 : int = - 1
144+ unique_tech_id_2 : int = - 1
145+ unique_unit_line : int = - 1
146+ unique_unit_upgrade_id : int = - 1
147+ unique_unit_id : int = - 1
148+ elite_unique_unit_id : int = - 1
149+ unique_unit_string_ids : Annotated [
150+ list [UnitStringIds ], Field (default_factory = list [UnitStringIds ])
151+ ]
152+ era : Literal ["base" ] | Literal ["antiquity" ]
153+
154+
155+ class DECivilizations (BaseModel ):
156+ model_config = ConfigDict (strict = True , extra = "forbid" )
157+
158+ civilization_list : list [DECivilization ]
159+
160+
61161class AoCDatasetMeta (BaseModel ):
62162 model_config = ConfigDict (strict = True , extra = "forbid" )
63163
@@ -102,6 +202,34 @@ def with_maps(self, maps: DEMaps, key_values: dict[str, str]):
102202 )
103203 return new_dataset
104204
205+ def with_civ_techs (self , civs : list [DECivTechTree ], key_values : dict [str , str ]):
206+ new_techs = {
207+ ** self .technologies ,
208+ ** {
209+ str (unit .trigger_tech_id ): key_values .get (
210+ str (unit .name_string_id ), unit .name
211+ )
212+ for civ in civs
213+ for unit in civ .civ_techs_units
214+ if unit .trigger_tech_id != - 1
215+ },
216+ ** {
217+ str (unit .node_id ): key_values .get (str (unit .name_string_id ), unit .name )
218+ for civ in civs
219+ for unit in civ .civ_techs_units
220+ if unit .node_type == "Research"
221+ },
222+ }
223+ new_dataset = self .model_copy (
224+ update = {
225+ "technologies" : {
226+ str (tech_name ): new_techs [str (tech_name )]
227+ for tech_name in sorted ([int (k ) for k in new_techs .keys ()])
228+ }
229+ }
230+ )
231+ return new_dataset
232+
105233
106234def get_steam_path () -> Path :
107235 if platform .system () == "Windows" :
@@ -179,6 +307,7 @@ def read_english_strings(resources_dir: Path) -> dict[str, str]:
179307 key_values = {}
180308 with strings_file .open ("rt" , newline = "\r \n " ) as file :
181309 for line in file :
310+ line = line .replace ("\\ n" , " " )
182311 if line .lstrip ().startswith ("//" ) or line .strip () == "" :
183312 continue
184313 if "//" in line :
@@ -226,13 +355,19 @@ def read_english_strings(resources_dir: Path) -> dict[str, str]:
226355 raise Exception (
227356 f"Key { key } is not unique: { value = } - { key_values [key ]= } "
228357 )
229- key_values [key ] = value .strip ().replace ('"' , "" )
358+ key_values [key ] = value .strip ().replace ('"' , "" ). replace ( " \n " , " " )
230359
231360 return key_values
232361
233362
234363def read_civ_tech_tree (resources_dir : Path ):
364+ civ_json = resources_dir / "_common" / "dat" / "civTechTrees.json"
365+ return DECivTechTrees .model_validate_json (civ_json .read_text (encoding = "utf8" ))
366+
367+
368+ def read_civilizations (resources_dir : Path ):
235369 civ_json = resources_dir / "_common" / "dat" / "civilizations.json"
370+ return DECivilizations .model_validate_json (civ_json .read_text (encoding = "utf8" ))
236371
237372
238373def read_maps (resources_dir : Path ):
@@ -251,7 +386,17 @@ def main() -> None:
251386 resources_dir = game_dir / "resources"
252387 key_values = read_english_strings (resources_dir )
253388 maps = read_maps (resources_dir )
254- new_dataset = dataset .with_maps (maps , key_values )
389+ civs = read_civilizations (resources_dir )
390+ tech_tree = read_civ_tech_tree (resources_dir )
391+
392+ aoe2_civs = {
393+ civ .tech_tree_name for civ in civs .civilization_list if civ .era == "base"
394+ }
395+ filtered_civs = [civ for civ in tech_tree .civs if civ .civ_id in aoe2_civs ]
396+ new_dataset = dataset .with_maps (maps , key_values ).with_civ_techs (
397+ filtered_civs , key_values
398+ )
399+
255400 print (new_dataset .model_dump_json (indent = 2 ))
256401
257402
0 commit comments