Skip to content

Commit 7e51aa7

Browse files
committed
Add README and implement civ technologies
1 parent 7f139d9 commit 7e51aa7

2 files changed

Lines changed: 172 additions & 3 deletions

File tree

scripts/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Scripts
2+
3+
## Import game data
4+
The `import_game_data.py` script will read the information that can be read
5+
from the AoE2DE installation files and patch the `data/datasets/100.json` file
6+
with that information. The script will only print the updated JSON to standard
7+
output, it will not actually modify the file itself.
8+
9+
The script has been written with the help of [uv](https://docs.astral.sh/uv) to
10+
manage depedencies/venv. In brief, to install dependencies, and run the script:
11+
12+
```bash
13+
# Install dependencies. It assumes you are in the scripts directory. This step is optional
14+
scripts $ uv sync --script import_game_data.py
15+
16+
# Run the script
17+
scripts $ uv run import_game_data.py
18+
19+
# To add a dependency, this how you would do it
20+
scripts $ uv add --script import_game_data.py beautifulpackage
21+
```
22+
23+
The script should automatically detect the installation location, both on
24+
Windows and Linux, but assumes it is installed through Steam.

scripts/import_game_data.py

Lines changed: 148 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
import csv
1010
import platform
1111
from pathlib import Path
12+
from typing import Annotated, Literal
1213

1314
import vdf
1415

15-
from pydantic import BaseModel, ConfigDict
16+
from pydantic import BaseModel, ConfigDict, Field
1617

1718

1819
class 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+
61161
class 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

106234
def 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

234363
def 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

238373
def 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

Comments
 (0)