1# Copyright 2013-2017 the openage authors. See copying.md for legal info.
2
3# TODO pylint: disable=C,R,too-many-lines
4
5from ..dataformat.exportable import Exportable
6from ..dataformat.member_access import READ, READ_EXPORT
7from ..dataformat.members import EnumLookupMember, ContinueReadMember, IncludeMembers, SubdataMember
8
9
10class UnitCommand(Exportable):
11    """
12    also known as "Task" according to ES debug code,
13    this structure is the master for spawn-unit actions.
14    """
15    name_struct        = "unit_command"
16    name_struct_file   = "unit"
17    struct_description = "a command a single unit may receive by script or human."
18
19    data_format = [
20        (READ, "command_used", "int16_t"),                  # Type (0 = Generic, 1 = Tribe)
21        (READ_EXPORT, "id", "int16_t"),                     # Identity
22        (READ, "is_default", "int8_t"),
23        (READ_EXPORT, "type", EnumLookupMember(
24            raw_type    = "int16_t",
25            type_name   = "command_ability",
26            lookup_dict = {
27                # the Action-Types found under RGE namespace:
28                0: "UNUSED",
29                1: "MOVE_TO",
30                2: "FOLLOW",
31                3: "GARRISON",  # also known as "Enter"
32                4: "EXPLORE",
33                5: "GATHER",
34                6: "NATURAL_WONDERS_CHEAT",  # also known as "Graze"
35                7: "COMBAT",       # this is converted to action-type 9 when once instanciated
36                8: "MISSILE",      # for projectiles
37                9: "ATTACK",
38                10: "BIRD",        # flying.
39                11: "PREDATOR",    # scares other animals when hunting
40                12: "TRANSPORT",
41                13: "GUARD",
42                14: "TRANSPORT_OVER_WALL",
43                20: "RUN_AWAY",
44                21: "MAKE",
45                # Action-Types found under TRIBE namespace:
46                101: "BUILD",
47                102: "MAKE_OBJECT",
48                103: "MAKE_TECH",
49                104: "CONVERT",
50                105: "HEAL",
51                106: "REPAIR",
52                107: "CONVERT_AUTO",  # "Artifact": can get auto-converted
53                108: "DISCOVERY",
54                109: "SHOOTING_RANGE_RETREAT",
55                110: "HUNT",
56                111: "TRADE",
57                120: "WONDER_VICTORY_GENERATE",
58                121: "DESELECT_ON_TASK",
59                122: "LOOT",
60                123: "HOUSING",
61                124: "PACK",
62                125: "UNPACK_ATTACK",
63                130: "OFF_MAP_TRADE_0",
64                131: "OFF_MAP_TRADE_1",
65                132: "PICKUP_UNIT",
66                133: "PICKUP_133",
67                134: "PICKUP_134",
68                135: "KIDNAP_UNIT",
69                136: "DEPOSIT_UNIT",
70                149: "SHEAR",
71                150: "REGENERATION",
72                151: "FEITORIA",
73                768: "UNKNOWN_768",
74                1024: "UNKNOWN_1024",
75            },
76        )),
77        (READ_EXPORT, "class_id", "int16_t"),
78        (READ_EXPORT, "unit_id", "int16_t"),
79        (READ_EXPORT, "terrain_id", "int16_t"),
80        (READ_EXPORT, "resource_in", "int16_t"),            # carry resource
81        (READ_EXPORT, "resource_multiplier", "int16_t"),    # resource that multiplies the amount you can gather
82        (READ_EXPORT, "resource_out", "int16_t"),           # drop resource
83        (READ_EXPORT, "unused_resource", "int16_t"),
84        (READ_EXPORT, "work_value1", "float"),              # quantity
85        (READ_EXPORT, "work_value2", "float"),              # execution radius?
86        (READ_EXPORT, "work_range", "float"),
87        (READ, "search_mode", "int8_t"),
88        (READ, "search_time", "float"),
89        (READ, "enable_targeting", "int8_t"),
90        (READ, "combat_level_flag", "int8_t"),
91        (READ, "gather_type", "int16_t"),
92        (READ, "work_mode2", "int16_t"),
93        (READ_EXPORT, "owner_type", EnumLookupMember(
94            # what can be selected as a target for the unit command?
95            raw_type    = "int8_t",
96            type_name   = "selection_type",
97            lookup_dict = {
98                0: "ANY_0",               # select anything
99                1: "OWNED_UNITS",         # your own things
100                2: "NEUTRAL_ENEMY",       # enemy and neutral things (->attack)
101                3: "NOTHING",
102                4: "GAIA_OWNED_ALLY",     # any of gaia, owned or allied things
103                5: "GAYA_NEUTRAL_ENEMY",  # any of gaia, neutral or enemy things
104                6: "NOT_OWNED",           # all things that aren't yours
105                7: "ANY_7",
106            },
107        )),
108        (READ, "carry_check", "int8_t"),                          # TODO: what does it do? right click?
109        (READ, "state_build", "int8_t"),
110        (READ_EXPORT, "move_sprite_id", "int16_t"),               # walking with tool but no resource
111        (READ_EXPORT, "proceed_sprite_id", "int16_t"),            # proceeding resource gathering or attack
112        (READ_EXPORT, "work_sprite_id", "int16_t"),               # actual execution or transformation graphic
113        (READ_EXPORT, "carry_sprite_id", "int16_t"),              # display resources in hands
114        (READ_EXPORT, "resource_gather_sound_id", "int16_t"),     # sound to play when execution starts
115        (READ_EXPORT, "resource_deposit_sound_id", "int16_t"),    # sound to play on resource drop
116    ]
117
118
119class UnitHeader(Exportable):
120    name_struct        = "unit_header"
121    name_struct_file   = "unit"
122    struct_description = "stores a bunch of unit commands."
123
124    data_format = [
125        (READ, "exists", ContinueReadMember("uint8_t")),
126        (READ, "unit_command_count", "uint16_t"),
127        (READ_EXPORT, "unit_commands", SubdataMember(
128            ref_type=UnitCommand,
129            length="unit_command_count",
130        )),
131    ]
132
133
134# Only used in SWGB
135class UnitLine(Exportable):
136    name_struct        = "unit_header"
137    name_struct_file   = "unit"
138    struct_description = "stores a bunch of unit commands."
139
140    data_format = [
141        (READ, "name_length", "uint16_t"),
142        (READ, "name", "char[name_length]"),
143        (READ, "unit_ids_counter", "uint16_t"),
144        (READ, "unit_ids", "int16_t[unit_ids_counter]"),
145    ]
146
147
148class ResourceStorage(Exportable):
149    name_struct        = "resource_storage"
150    name_struct_file   = "unit"
151    struct_description = "determines the resource storage capacity for one unit mode."
152
153    data_format = [
154        (READ, "type", "int16_t"),
155        (READ, "amount", "float"),
156        (READ, "used_mode", EnumLookupMember(
157            raw_type    = "int8_t",
158            type_name   = "resource_handling",
159            lookup_dict = {
160                0: "DECAYABLE",
161                1: "KEEP_AFTER_DEATH",
162                2: "RESET_ON_DEATH_INSTANT",
163                4: "RESET_ON_DEATH_WHEN_COMPLETED",
164            },
165        )),
166    ]
167
168
169class DamageGraphic(Exportable):
170    name_struct        = "damage_graphic"
171    name_struct_file   = "unit"
172    struct_description = "stores one possible unit image that is displayed at a given damage percentage."
173
174    data_format = [
175        (READ_EXPORT, "graphic_id", "int16_t"),
176        (READ_EXPORT, "damage_percent", "int8_t"),
177        (READ, "old_apply_mode", "int8_t"),    # gets overwritten in aoe memory by the real apply_mode:
178        (READ_EXPORT, "apply_mode", EnumLookupMember(
179            raw_type    = "int8_t",
180            type_name   = "damage_draw_type",
181            lookup_dict = {
182                0: "TOP",      # adds graphics on top (e.g. flames)
183                1: "RANDOM",   # adds graphics on top randomly
184                2: "REPLACE",  # replace original graphics (e.g. damaged walls)
185            },
186        )),
187    ]
188
189
190class HitType(Exportable):
191    name_struct        = "hit_type"
192    name_struct_file   = "unit"
193    struct_description = "stores attack amount for a damage type."
194
195    data_format = [
196        (READ, "type_id", EnumLookupMember(
197            raw_type    = "int16_t",
198            type_name   = "hit_class",
199            lookup_dict = {
200                -1: "NONE",
201                0: "UNKNOWN_0",
202                1: "INFANTRY",
203                2: "SHIP_TURTLE",
204                3: "UNITS_PIERCE",
205                4: "UNITS_MELEE",
206                5: "WAR_ELEPHANT",
207                8: "CAVALRY",
208                11: "BUILDINGS_NO_PORT",
209                13: "STONE_DEFENSES",
210                14: "UNKNOWN_14",
211                15: "ARCHERS",
212                16: "SHIPS_CAMELS_SABOTEURS",
213                17: "RAMS",
214                18: "TREES",
215                19: "UNIQUE_UNITS",
216                20: "SIEGE_WEAPONS",
217                21: "BUILDINGS",
218                22: "WALLS_GATES",
219                23: "UNKNOWN_23",
220                24: "BOAR",
221                25: "MONKS",
222                26: "CASTLE",
223                27: "SPEARMEN",
224                28: "CAVALRY_ARCHER",
225                29: "EAGLE_WARRIOR",
226                30: "UNKNOWN_30",
227            },
228        )),
229        (READ, "amount", "int16_t"),
230    ]
231
232
233class ResourceCost(Exportable):
234    name_struct        = "resource_cost"
235    name_struct_file   = "unit"
236    struct_description = "stores cost for one resource for creating the unit."
237
238    data_format = [
239        (READ, "type_id", EnumLookupMember(
240            raw_type = "int16_t",
241            type_name = "resource_types",
242            lookup_dict = {
243                -1: "NONE",
244                0: "FOOD_STORAGE",
245                1: "WOOD_STORAGE",
246                2: "STONE_STORAGE",
247                3: "GOLD_STORAGE",
248                4: "POPULATION_HEADROOM",
249                5: "CONVERSION_RANGE",
250                6: "CURRENT_AGE",
251                7: "OWNED_RELIC_COUNT",
252                8: "TRADE_BONUS",
253                9: "TRADE_GOODS",
254                10: "TRADE_PRODUCTION",
255                11: "POPULATION",           # both current population and population headroom
256                12: "CORPSE_DECAY_TIME",
257                13: "DISCOVERY",
258                14: "RUIN_MONUMENTS_CAPTURED",
259                15: "MEAT_STORAGE",
260                16: "BERRY_STORAGE",
261                17: "FISH_STORAGE",
262                18: "UNKNOWN_18",           # in starwars: power core range
263                19: "TOTAL_UNITS_OWNED",    # or just military ones? used for counting losses
264                20: "UNITS_KILLED",
265                21: "RESEARCHED_TECHNOLOGIES_COUNT",
266                22: "MAP_EXPLORED_PERCENTAGE",
267                23: "CASTLE_AGE_TECH_INDEX",      # default: 102
268                24: "IMPERIAL_AGE_TECH_INDEX",    # default: 103
269                25: "FEUDAL_AGE_TECH_INDEX",      # default: 101
270                26: "ATTACK_WARNING_SOUND",
271                27: "ENABLE_MONK_CONVERSION",
272                28: "ENABLE_BUILDING_CONVERSION",
273                30: "BUILDING_COUNT",              # default: 500
274                31: "FOOD_COUNT",
275                32: "BONUS_POPULATION",
276                33: "MAINTENANCE",
277                34: "FAITH",
278                35: "FAITH_RECHARGE_RATE",  # default: 1.6
279                36: "FARM_FOOD_AMOUNT",     # default: 175
280                37: "CIVILIAN_POPULATION",
281                38: "UNKNOWN_38",           # starwars: shields for bombers/fighters
282                39: "ALL_TECHS_ACHIEVED",   # default: 178
283                40: "MILITARY_POPULATION",  # -> largest army
284                41: "UNITS_CONVERTED",      # monk success count
285                42: "WONDERS_STANDING",
286                43: "BUILDINGS_RAZED",
287                44: "KILL_RATIO",
288                45: "SURVIVAL_TO_FINISH",   # bool
289                46: "TRIBUTE_FEE",          # default: 0.3
290                47: "GOLD_MINING_PRODUCTIVITY",  # default: 1
291                48: "TOWN_CENTER_UNAVAILABLE",  # -> you may build a new one
292                49: "GOLD_COUNTER",
293                50: "REVEAL_ALLY",          # bool, ==cartography discovered
294                51: "HOUSES_COUNT",
295                52: "MONASTERY_COUNT",
296                53: "TRIBUTE_SENT",
297                54: "RUINES_CAPTURED_ALL",  # bool
298                55: "RELICS_CAPTURED_ALL",  # bool
299                56: "ORE_STORAGE",
300                57: "CAPTURED_UNITS",
301                58: "DARK_AGE_TECH_INDEX",  # default: 104
302                59: "TRADE_GOOD_QUALITY",   # default: 1
303                60: "TRADE_MARKET_LEVEL",
304                61: "FORMATIONS",
305                62: "BUILDING_HOUSING_RATE",  # default: 20
306                63: "GATHER_TAX_RATE",        # default: 32000
307                64: "GATHER_ACCUMULATOR",
308                65: "SALVAGE_DECAY_RATE",     # default: 5
309                66: "ALLOW_FORMATION",        # bool, something with age?
310                67: "ALLOW_CONVERSIONS",      # bool
311                68: "HIT_POINTS_KILLED",      # unused
312                69: "KILLED_PLAYER_1",        # bool
313                70: "KILLED_PLAYER_2",        # bool
314                71: "KILLED_PLAYER_3",        # bool
315                72: "KILLED_PLAYER_4",        # bool
316                73: "KILLED_PLAYER_5",        # bool
317                74: "KILLED_PLAYER_6",        # bool
318                75: "KILLED_PLAYER_7",        # bool
319                76: "KILLED_PLAYER_8",        # bool
320                77: "CONVERSION_RESISTANCE",
321                78: "TRADE_VIG_RATE",             # default: 0.3
322                79: "STONE_MINING_PRODUCTIVITY",  # default: 1
323                80: "QUEUED_UNITS",
324                81: "TRAINING_COUNT",
325                82: "START_PACKED_TOWNCENTER",   # or raider, default: 2
326                83: "BOARDING_RECHARGE_RATE",
327                84: "STARTING_VILLAGERS",        # default: 3
328                85: "RESEARCH_COST_MULTIPLIER",
329                86: "RESEARCH_TIME_MULTIPLIER",
330                87: "CONVERT_SHIPS_ALLOWED",     # bool
331                88: "FISH_TRAP_FOOD_AMOUNT",     # default: 700
332                89: "HEALING_RATE_MULTIPLIER",
333                90: "HEALING_RANGE",
334                91: "STARTING_FOOD",
335                92: "STARTING_WOOD",
336                93: "STARTING_STONE",
337                94: "STARTING_GOLD",
338                95: "TOWN_CENTER_PACKING",        # or raider, default: 3
339                96: "BERSERKER_HEAL_TIME",        # in seconds
340                97: "DOMINANT_ANIMAL_DISCOVERY",  # bool, sheep/turkey
341                98: "SCORE_OBJECT_COST",          # object cost summary, economy?
342                99: "SCORE_RESEARCH",
343                100: "RELIC_GOLD_COLLECTED",
344                101: "TRADE_PROFIT",
345                102: "TRIBUTE_P1",
346                103: "TRIBUTE_P2",
347                104: "TRIBUTE_P3",
348                105: "TRIBUTE_P4",
349                106: "TRIBUTE_P5",
350                107: "TRIBUTE_P6",
351                108: "TRIBUTE_P7",
352                109: "TRIBUTE_P8",
353                110: "KILL_SCORE_P1",
354                111: "KILL_SCORE_P2",
355                112: "KILL_SCORE_P3",
356                113: "KILL_SCORE_P4",
357                114: "KILL_SCORE_P5",
358                115: "KILL_SCORE_P6",
359                116: "KILL_SCORE_P7",
360                117: "KILL_SCORE_P8",
361                118: "RAZING_COUNT_P1",
362                119: "RAZING_COUNT_P2",
363                120: "RAZING_COUNT_P3",
364                121: "RAZING_COUNT_P4",
365                122: "RAZING_COUNT_P5",
366                123: "RAZING_COUNT_P6",
367                124: "RAZING_COUNT_P7",
368                125: "RAZING_COUNT_P8",
369                126: "RAZING_SCORE_P1",
370                127: "RAZING_SCORE_P2",
371                128: "RAZING_SCORE_P3",
372                129: "RAZING_SCORE_P4",
373                130: "RAZING_SCORE_P5",
374                131: "RAZING_SCORE_P6",
375                132: "RAZING_SCORE_P7",
376                133: "RAZING_SCORE_P8",
377                134: "STANDING_CASTLES",
378                135: "RAZINGS_HIT_POINTS",
379                136: "KILLS_BY_P1",
380                137: "KILLS_BY_P2",
381                138: "KILLS_BY_P3",
382                139: "KILLS_BY_P4",
383                140: "KILLS_BY_P5",
384                141: "KILLS_BY_P6",
385                142: "KILLS_BY_P7",
386                143: "KILLS_BY_P8",
387                144: "RAZINGS_BY_P1",
388                145: "RAZINGS_BY_P2",
389                146: "RAZINGS_BY_P3",
390                147: "RAZINGS_BY_P4",
391                148: "RAZINGS_BY_P5",
392                149: "RAZINGS_BY_P6",
393                150: "RAZINGS_BY_P7",
394                151: "RAZINGS_BY_P8",
395                152: "LOST_UNITS_SCORE",
396                153: "LOST_BUILDINGS_SCORE",
397                154: "LOST_UNITS",
398                155: "LOST_BUILDINGS",
399                156: "TRIBUTE_FROM_P1",
400                157: "TRIBUTE_FROM_P2",
401                158: "TRIBUTE_FROM_P3",
402                159: "TRIBUTE_FROM_P4",
403                160: "TRIBUTE_FROM_P5",
404                161: "TRIBUTE_FROM_P6",
405                162: "TRIBUTE_FROM_P7",
406                163: "TRIBUTE_FROM_P8",
407                164: "SCORE_UNITS_CURRENT",
408                165: "SCORE_BUILDINGS_CURRENT",         # default: 275
409                166: "COLLECTED_FOOD",
410                167: "COLLECTED_WOOD",
411                168: "COLLECTED_STONE",
412                169: "COLLECTED_GOLD",
413                170: "SCORE_MILITARY",
414                171: "TRIBUTE_RECEIVED",
415                172: "SCORE_RAZINGS",
416                173: "TOTAL_CASTLES",
417                174: "TOTAL_WONDERS",
418                175: "SCORE_ECONOMY_TRIBUTES",
419                176: "CONVERT_ADJUSTMENT_MIN",          # used for resistance against monk conversions
420                177: "CONVERT_ADJUSTMENT_MAX",
421                178: "CONVERT_RESIST_ADJUSTMENT_MIN",
422                179: "CONVERT_RESIST_ADJUSTMENT_MAX",
423                180: "CONVERT_BUILDIN_MIN",             # default: 15
424                181: "CONVERT_BUILDIN_MAX",             # default: 25
425                182: "CONVERT_BUILDIN_CHANCE",          # default: 25
426                183: "REVEAL_ENEMY",
427                184: "SCORE_SOCIETY",                   # wonders, castles
428                185: "SCORE_FOOD",
429                186: "SCORE_WOOD",
430                187: "SCORE_STONE",
431                188: "SCORE_GOLD",
432                189: "CHOPPING_PRODUCTIVITY",           # default: 1
433                190: "FOOD_GATHERING_PRODUCTIVITY",     # default: 1
434                191: "RELIC_GOLD_PRODUCTION_RATE",      # default: 30
435                192: "CONVERTED_UNITS_DIE",             # bool
436                193: "THEOCRACY_ACTIVE",                # bool
437                194: "CRENELLATIONS_ACTIVE",            # bool
438                195: "CONSTRUCTION_RATE_MULTIPLIER",    # except for wonders
439                196: "HUN_WONDER_BONUS",
440                197: "SPIES_DISCOUNT",                  # or atheism_active?
441            }
442        )),
443        (READ, "amount", "int16_t"),
444        (READ, "enabled", "int16_t"),
445    ]
446
447
448class BuildingAnnex(Exportable):
449
450    name_struct        = "building_annex"
451    name_struct_file   = "unit"
452    struct_description = "a possible building annex."
453
454    data_format = [
455        (READ_EXPORT, "unit_id",    "int16_t"),
456        (READ_EXPORT, "misplaced0", "float"),
457        (READ_EXPORT, "misplaced1", "float"),
458    ]
459
460
461class UnitObject(Exportable):
462    """
463    base properties for every unit entry.
464    """
465
466    name_struct        = "unit_object"
467    name_struct_file   = "unit"
468    struct_description = "base properties for all units."
469
470    data_format = [
471        (READ, "name_length", "uint16_t"),
472        (READ_EXPORT, "id0", "int16_t"),
473        (READ_EXPORT, "language_dll_name", "uint16_t"),
474        (READ_EXPORT, "language_dll_creation", "uint16_t"),
475        (READ_EXPORT, "unit_class", EnumLookupMember(
476            raw_type    = "int16_t",
477            type_name   = "unit_classes",
478            lookup_dict = {
479                -1: "NONE",
480                0: "ARCHER",
481                1: "ARTIFACT",
482                2: "TRADE_BOAT",
483                3: "BUILDING",
484                4: "CIVILIAN",
485                5: "SEA_FISH",
486                6: "SOLDIER",
487                7: "BERRY_BUSH",
488                8: "STONE_MINE",
489                9: "PREY_ANIMAL",
490                10: "PREDATOR_ANIMAL",
491                11: "OTHER",
492                12: "CAVALRY",
493                13: "SIEGE_WEAPON",
494                14: "TERRAIN",
495                15: "TREES",
496                16: "UNKNOWN_16",
497                18: "PRIEST",
498                19: "TRADE_CART",
499                20: "TRANSPORT_BOAT",
500                21: "FISHING_BOAT",
501                22: "WAR_BOAT",
502                23: "CONQUISTADOR",
503                27: "WALLS",
504                28: "PHALANX",
505                29: "ANIMAL_DOMESTICATED",
506                30: "FLAGS",
507                32: "GOLD_MINE",
508                33: "SHORE_FISH",
509                34: "CLIFF",
510                35: "PETARD",
511                36: "CAVALRY_ARCHER",
512                37: "DOLPHIN",
513                38: "BIRDS",
514                39: "GATES",
515                40: "PILES",
516                41: "PILES_OF_RESOURCE",
517                42: "RELIC",
518                43: "MONK_WITH_RELIC",
519                44: "HAND_CANNONEER",
520                45: "TWO_HANDED_SWORD",
521                46: "PIKEMAN",
522                47: "SCOUT_CAVALRY",
523                48: "ORE_MINE",
524                49: "FARM",
525                50: "SPEARMAN",
526                51: "PACKED_SIEGE_UNITS",
527                52: "TOWER",
528                53: "BOARDING_BOAT",
529                54: "UNPACKED_SIEGE_UNITS",
530                55: "SCORPION",
531                56: "RAIDER",
532                57: "CAVALRY_RAIDER",
533                58: "SHEEP",
534                59: "KING",
535                61: "HORSE",
536            },
537        )),
538        (READ_EXPORT, "graphic_standing0", "int16_t"),
539        (READ_EXPORT, "graphic_standing1", "int16_t"),
540        (READ_EXPORT, "dying_graphic", "int16_t"),
541        (READ_EXPORT, "undead_graphic", "int16_t"),
542        (READ, "death_mode", "int8_t"),                  # 1 = become `dead_unit_id` (reviving does not make it usable again)
543        (READ_EXPORT, "hit_points", "int16_t"),          # unit health. -1=insta-die
544        (READ, "line_of_sight", "float"),
545        (READ, "garrison_capacity", "int8_t"),           # number of units that can garrison in there
546        (READ_EXPORT, "radius_x", "float"),              # size of the unit
547        (READ_EXPORT, "radius_y", "float"),
548        (READ_EXPORT, "radius_z", "float"),
549        (READ_EXPORT, "train_sound", "int16_t"),
550    ]
551
552    # TODO: Enable conversion for AOE1; replace "damage_sound"
553    # ===========================================================================
554    # if (GameVersion.aoe_1 or GameVersion.aoe_ror) not in game_versions:
555    #     data_format.append((READ_EXPORT, "damage_sound", "int16_t"))
556    # ===========================================================================
557    data_format.append((READ_EXPORT, "damage_sound", "int16_t"))
558
559    data_format.extend([
560        (READ_EXPORT, "dead_unit_id", "int16_t"),        # unit id to become on death
561        (READ, "placement_mode", "int8_t"),              # 0=placable on top of others in scenario editor, 5=can't
562        (READ, "can_be_built_on", "int8_t"),             # 1=no footprints
563        (READ, "icon_id", "int16_t"),                    # frame id of the icon slp (57029) to place on the creation button
564        (READ, "hidden_in_editor", "int8_t"),
565        (READ, "old_portrait_icon_id", "int16_t"),
566        (READ, "enabled", "int8_t"),                     # 0=unlocked by research, 1=insta-available
567    ])
568
569    # TODO: Enable conversion for AOE1; replace "disabled"
570    # ===========================================================================
571    # if (GameVersion.aoe_1 or GameVersion.aoe_ror) not in game_versions:
572    #     data_format.append((READ, "disabled", "int8_t"))
573    # ===========================================================================
574    data_format.append((READ, "disabled", "int8_t"))
575
576    data_format.extend([
577        (READ, "placement_side_terrain0", "int16_t"),    # terrain id that's needed somewhere on the foundation (e.g. dock water)
578        (READ, "placement_side_terrain1", "int16_t"),    # second slot for ^
579        (READ, "placement_terrain0", "int16_t"),         # terrain needed for placement (e.g. dock: water)
580        (READ, "placement_terrain1", "int16_t"),         # alternative terrain needed for placement (e.g. dock: shallows)
581        (READ, "clearance_size_x", "float"),             # minimum space required to allow placement in editor
582        (READ, "clearance_size_y", "float"),
583        (READ_EXPORT, "building_mode", EnumLookupMember(
584            raw_type    = "int8_t",
585            type_name   = "building_modes",
586            lookup_dict = {
587                0: "NON_BUILDING",    # gates, farms, walls, towers
588                2: "TRADE_BUILDING",  # towncenter, port, trade workshop
589                3: "ANY",
590            },
591        )),
592        (READ_EXPORT, "visible_in_fog", EnumLookupMember(
593            raw_type    = "int8_t",
594            type_name   = "fog_visibility",
595            lookup_dict = {
596                0: "INVISIBLE",     # people etc
597                1: "VISIBLE",       # buildings
598                3: "ONLY_IN_FOG",
599            },
600        )),
601        (READ_EXPORT, "terrain_restriction", EnumLookupMember(
602            raw_type    = "int16_t",      # determines on what type of ground the unit can be placed/walk
603            type_name   = "ground_type",  # is actually the id of the terrain_restriction entry!
604            lookup_dict = {
605                -0x01: "NONE",
606                0x00: "ANY",
607                0x01: "SHORELINE",
608                0x02: "WATER",
609                0x03: "WATER_SHIP_0x03",
610                0x04: "FOUNDATION",
611                0x05: "NOWHERE",              # can't place anywhere
612                0x06: "WATER_DOCK",           # shallow water for dock placement
613                0x07: "SOLID",
614                0x08: "NO_ICE_0x08",
615                0x0A: "NO_ICE_0x0A",
616                0x0B: "FOREST",
617                0x0C: "UNKNOWN_0x0C",
618                0x0D: "WATER_0x0D",           # great fish
619                0x0E: "UNKNOWN_0x0E",
620                0x0F: "WATER_SHIP_0x0F",      # transport ship
621                0x10: "GRASS_SHORELINE",      # for gates and walls
622                0x11: "WATER_ANY_0x11",
623                0x12: "UNKNOWN_0x12",
624                0x13: "FISH_NO_ICE",
625                0x14: "WATER_ANY_0x14",
626                0x15: "WATER_SHALLOW",
627            },
628        )),
629        (READ_EXPORT, "fly_mode", "int8_t"),                      # determines whether the unit can fly
630        (READ_EXPORT, "resource_capacity", "int16_t"),
631        (READ_EXPORT, "resource_decay", "float"),                 # when animals rot, their resources decay
632        (READ_EXPORT, "blast_defense_level", EnumLookupMember(
633            # receive blast damage from units that have lower or same blast_attack_level.
634            raw_type    = "int8_t",
635            type_name   = "blast_types",
636            lookup_dict = {
637                0: "UNIT_0",    # projectile, dead, fish, relic, tree, gate, towncenter
638                1: "OTHER",     # 'other' things with multiple rotations
639                2: "BUILDING",  # buildings, gates, walls, towncenter, fishtrap
640                3: "UNIT_3",    # boar, farm, fishingship, villager, tradecart, sheep, turkey, archers, junk, ships, monk, siege
641            }
642        )),
643        (READ_EXPORT, "combat_level", EnumLookupMember(
644            raw_type = "int8_t",
645            type_name = "combat_levels",
646            lookup_dict = {
647                0: "PROJECTILE_DEAD_RESOURCE",
648                1: "BOAR",
649                2: "BUILDING",
650                3: "CIVILIAN",
651                4: "MILITARY",
652                5: "OTHER",
653            }
654        )),
655        (READ_EXPORT, "interaction_mode", EnumLookupMember(
656            # what can be done with this unit?
657            raw_type    = "int8_t",
658            type_name   = "interaction_modes",
659            lookup_dict = {
660                0: "NOTHING_0",
661                1: "NOTHING_1",
662                2: "SELECTABLE",
663                3: "SELECT_ATTACK",
664                4: "SELECT_ATTACK_MOVE",
665                5: "SELECT_MOVE",
666            },
667        )),
668        (READ_EXPORT, "map_draw_level", EnumLookupMember(
669            # how does the unit show up on the minimap?
670            raw_type    = "int8_t",
671            type_name   = "minimap_modes",
672            lookup_dict = {
673                0: "NO_DOT_0",
674                1: "SQUARE_DOT",   # turns white when selected
675                2: "DIAMOND_DOT",  # dito
676                3: "DIAMOND_DOT_KEEPCOLOR",  # doesn't turn white when selected
677                4: "LARGEDOT",   # observable by all players, no attacked-blinking
678                5: "NO_DOT_5",
679                6: "NO_DOT_6",
680                7: "NO_DOT_7",
681                8: "NO_DOT_8",
682                9: "NO_DOT_9",
683                10: "NO_DOT_10",
684            },
685        )),
686        (READ_EXPORT, "unit_level", EnumLookupMember(
687            # selects the available ui command buttons for the unit
688            raw_type    = "int8_t",
689            type_name   = "command_attributes",
690            lookup_dict = {
691                0: "LIVING",                # commands: delete, garrison, stop, attributes: hit points
692                1: "ANIMAL",                # animal
693                2: "NONMILITARY_BULIDING",  # civilian building (build page 1)
694                3: "VILLAGER",              # villager
695                4: "MILITARY_UNIT",         # military unit
696                5: "TRADING_UNIT",          # trading unit
697                6: "MONK_EMPTY",            # monk
698                7: "TRANSPORT_SHIP",        # transport ship
699                8: "RELIC",                 # relic / monk with relic
700                9: "FISHING_SHIP",          # fishing ship
701                10: "MILITARY_BUILDING",    # military building (build page 2)
702                11: "SHIELDED_BUILDING",    # shield building (build page 3)
703                12: "UNKNOWN_12",
704            },
705        )),
706        (READ, "attack_reaction", "float"),
707        (READ_EXPORT, "minimap_color", "int8_t"),        # palette color id for the minimap
708        (READ_EXPORT, "language_dll_help", "int32_t"),   # help text for this unit, stored in the translation dll.
709        (READ_EXPORT, "language_dll_hotkey_text", "int32_t"),
710        (READ, "hot_keys", "int32_t"),                   # language dll dependent (kezb lazouts!)
711        (READ, "reclyclable", "int8_t"),
712        (READ, "enable_auto_gather", "int8_t"),
713        (READ, "doppelgaenger_on_death", "int8_t"),
714        (READ, "resource_gather_drop", "int8_t"),
715    ])
716
717    # TODO: Enable conversion for AOE1, AOK; replace 6 values below
718    # ===========================================================================
719    # if (GameVersion.aoe_1 or GameVersion.aoe_ror) not in game_versions:
720    #     # bit 0 == 1 && val != 7: mask shown behind buildings,
721    #     # bit 0 == 0 && val != {6, 10}: no mask displayed,
722    #     # val == {-1, 7}: in open area mask is partially displayed
723    #     # val == {6, 10}: building, causes mask to appear on units behind it
724    #     data_format.extend([
725    #         (READ, "occlusion_mask", "int8_t"),
726    #         (READ, "obstruction_type", EnumLookupMember(
727    #             # selects the available ui command buttons for the unit
728    #             raw_type    = "int8_t",
729    #             type_name   = "obstruction_types",
730    #             lookup_dict = {
731    #                 0: "PASSABLE",              # farm, gate, dead bodies, town center
732    #                 2: "BUILDING",
733    #                 3: "BERSERK",
734    #                 5: "UNIT",
735    #                 10: "MOUNTAIN",             # mountain (matches occlusion_mask)
736    #             },
737    #         )),
738    #         # There shouldn't be a value here according to genieutils
739    #         # What is this?
740    #         (READ_EXPORT, "selection_shape", "int8_t"),            # 0=square, 1<=round
741    #     ])
742    #
743    #     if GameVersion-age2_aok not in game_versions:
744    #         # bitfield of unit attributes:
745    #         # bit 0: allow garrison,
746    #         # bit 1: don't join formation,
747    #         # bit 2: stealth unit,
748    #         # bit 3: detector unit,
749    #         # bit 4: mechanical unit,
750    #         # bit 5: biological unit,
751    #         # bit 6: self-shielding unit,
752    #         # bit 7: invisible unit
753    #         data_format.extend([
754    #             (READ, "trait", "uint8_t"),
755    #             (READ, "civilisation", "int8_t"),
756    #             (READ, "attribute_piece", "int16_t"),    # leftover from trait+civ variable
757    #         ])
758    # ===========================================================================
759
760    # bit 0 == 1 && val != 7: mask shown behind buildings,
761    # bit 0 == 0 && val != {6, 10}: no mask displayed,
762    # val == {-1, 7}: in open area mask is partially displayed
763    # val == {6, 10}: building, causes mask to appear on units behind it
764    data_format.extend([
765        (READ, "occlusion_mask", "int8_t"),
766        (READ, "obstruction_type", EnumLookupMember(
767            # selects the available ui command buttons for the unit
768            raw_type    = "int8_t",
769            type_name   = "obstruction_types",
770            lookup_dict = {
771                0: "PASSABLE",              # farm, gate, dead bodies, town center
772                2: "BUILDING",
773                3: "BERSERK",
774                5: "UNIT",
775                10: "MOUNTAIN",             # mountain (matches occlusion_mask)
776            },
777        )),
778        # There shouldn't be a value here according to genieutils
779        # What is this?
780        (READ_EXPORT, "selection_shape", "int8_t"),            # 0=square, 1<=round
781
782        # bitfield of unit attributes:
783        # bit 0: allow garrison,
784        # bit 1: don't join formation,
785        # bit 2: stealth unit,
786        # bit 3: detector unit,
787        # bit 4: mechanical unit,
788        # bit 5: biological unit,
789        # bit 6: self-shielding unit,
790        # bit 7: invisible unit
791        (READ, "trait", "uint8_t"),
792        (READ, "civilisation", "int8_t"),
793        (READ, "attribute_piece", "int16_t"),   # leftover from trait+civ variable
794    ])
795    # ===========================================================================
796
797    data_format.extend([
798        (READ_EXPORT, "selection_effect", EnumLookupMember(
799            # things that happen when the unit was selected
800            raw_type = "int8_t",
801            type_name = "selection_effects",
802            lookup_dict = {
803                0: "NONE",
804                1: "HPBAR_ON_OUTLINE_DARK",  # permanent, editor only
805                2: "HPBAR_ON_OUTLINE_NORMAL",
806                3: "HPBAR_OFF_SELECTION_SHADOW",
807                4: "HPBAR_OFF_OUTLINE_NORMAL",
808                5: "HPBAR_ON_5",
809                6: "HPBAR_OFF_6",
810                7: "HPBAR_OFF_7",
811                8: "HPBAR_ON_8",
812                9: "HPBAR_ON_9",
813            },
814        )),
815        (READ, "editor_selection_color", "uint8_t"),  # 0: default, -16: fish trap, farm, 52: deadfarm, OLD-*, 116: flare, whale, dolphin -123: fish
816        (READ_EXPORT, "selection_shape_x", "float"),
817        (READ_EXPORT, "selection_shape_y", "float"),
818        (READ_EXPORT, "selection_shape_z", "float"),
819        (READ_EXPORT, "resource_storage", SubdataMember(
820            ref_type=ResourceStorage,
821            length=3,
822        )),
823        (READ, "damage_graphic_count", "int8_t"),
824        (READ_EXPORT, "damage_graphic", SubdataMember(
825            ref_type=DamageGraphic,
826            length="damage_graphic_count",
827        )),
828        (READ_EXPORT, "sound_selection", "int16_t"),
829        (READ_EXPORT, "sound_dying", "int16_t"),
830        (READ_EXPORT, "old_attack_mode", EnumLookupMember(  # obsolete, as it's copied when converting the unit
831            raw_type = "int8_t",     # things that happen when the unit was selected
832            type_name = "attack_modes",
833            lookup_dict = {
834                0: "NO",         # no attack
835                1: "FOLLOWING",  # by following
836                2: "RUN",        # run when attacked
837                3: "UNKNOWN3",
838                4: "ATTACK",
839            },
840        )),
841
842        (READ, "convert_terrain", "int8_t"),
843        (READ_EXPORT, "name", "char[name_length]"),
844    ])
845
846    # TODO: Enable conversion for SWGB
847    # ===========================================================================
848    # if (GameVersion.swgb_10 or GameVersion.swgb_cc) in game_versions:
849    #     data_format.extend([(READ, "name2_length", "uint16_t"),
850    #                         (READ, "name2", "char[name2_length]"),
851    #                         (READ, "unit_line", "int16_t"),
852    #                         (READ, "min_tech_level", "int8_t"),
853    #     ])
854    # ===========================================================================
855
856    data_format.append((READ_EXPORT, "id1", "int16_t"))
857
858    # TODO: Enable conversion for AOE1; replace "id2"
859    # ===========================================================================
860    # if (GameVersion.aoe_1 or GameVersion.aoe_ror) not in game_versions:
861    #     data_format.append((READ_EXPORT, "id2", "int16_t"))
862    # ===========================================================================
863    data_format.append((READ_EXPORT, "id2", "int16_t"))
864
865
866class TreeUnit(UnitObject):
867    """
868    type_id == 90
869    """
870
871    name_struct        = "tree_unit"
872    name_struct_file   = "unit"
873    struct_description = "just a tree unit."
874
875    data_format = [
876        (READ_EXPORT, None, IncludeMembers(cls=UnitObject)),
877    ]
878
879
880class AnimatedUnit(UnitObject):
881    """
882    type_id >= 20
883    Animated master object
884    """
885
886    name_struct        = "animated_unit"
887    name_struct_file   = "unit"
888    struct_description = "adds speed property to units."
889
890    data_format = [
891        (READ_EXPORT, None, IncludeMembers(cls=UnitObject)),
892        (READ_EXPORT, "speed", "float"),
893    ]
894
895
896class DoppelgangerUnit(AnimatedUnit):
897    """
898    type_id >= 25
899    """
900
901    name_struct        = "doppelganger_unit"
902    name_struct_file   = "unit"
903    struct_description = "weird doppelganger unit thats actually the same as an animated unit."
904
905    data_format = [
906        (READ_EXPORT, None, IncludeMembers(cls=AnimatedUnit)),
907    ]
908
909
910class MovingUnit(DoppelgangerUnit):
911    """
912    type_id >= 30
913    Moving master object
914    """
915
916    name_struct        = "moving_unit"
917    name_struct_file   = "unit"
918    struct_description = "adds walking graphics, rotations and tracking properties to units."
919
920    data_format = [
921        (READ_EXPORT, None, IncludeMembers(cls=DoppelgangerUnit)),
922        (READ_EXPORT, "walking_graphics0", "int16_t"),
923        (READ_EXPORT, "walking_graphics1", "int16_t"),
924        (READ, "turn_speed", "float"),
925        (READ, "old_size_class", "int8_t"),
926        (READ, "trail_unit_id", "int16_t"),             # unit id for the ground traces
927        (READ, "trail_opsions", "uint8_t"),             # ground traces: -1: no tracking present, 2: projectiles with tracking unit
928        (READ, "trail_spacing", "float"),               # ground trace spacing: 0: no tracking, 0.5: trade cart, 0.12: some projectiles, 0.4: other projectiles
929        (READ, "old_move_algorithm", "int8_t"),
930    ]
931
932    # TODO: Enable conversion for AOE1; replace 5 values below
933    # ===========================================================================
934    # if (GameVersion.aoe_1 or GameVersion.aoe_ror) not in game_versions:
935    #     data_format.extend([
936    #         (READ, "turn_radius", "float"),
937    #         (READ, "turn_radius_speed", "float"),
938    #         (READ, "max_yaw_per_sec_moving", "float"),
939    #         (READ, "stationary_yaw_revolution_time", "float"),
940    #         (READ, "max_yaw_per_sec_stationary", "float"),
941    #     ])
942    # ===========================================================================
943    data_format.extend([
944        (READ, "turn_radius", "float"),
945        (READ, "turn_radius_speed", "float"),
946        (READ, "max_yaw_per_sec_moving", "float"),
947        (READ, "stationary_yaw_revolution_time", "float"),
948        (READ, "max_yaw_per_sec_stationary", "float"),
949    ])
950
951
952class ActionUnit(MovingUnit):
953    """
954    type_id >= 40
955    Action master object
956    """
957
958    name_struct        = "action_unit"
959    name_struct_file   = "unit"
960    struct_description = "adds search radius and work properties, as well as movement sounds."
961
962    data_format = [
963        (READ_EXPORT, None, IncludeMembers(cls=MovingUnit)),
964        # callback unit action id when found.
965        # monument and sheep: 107 = enemy convert.
966        # all auto-convertible units: 0, most other units: -1
967        (READ, "default_task_id", "int16_t"),            # e.g. when sheep are discovered
968        (READ, "search_radius", "float"),
969        (READ_EXPORT, "work_rate", "float"),
970        (READ_EXPORT, "drop_site0", "int16_t"),          # unit id where gathered resources shall be delivered to
971        (READ_EXPORT, "drop_site1", "int16_t"),          # alternative unit id
972        (READ_EXPORT, "task_by_group", "int8_t"),        # if a task is not found in the current unit, other units with the same group id are tried.
973                                                         # 1: male villager; 2: female villager; 3+: free slots
974                                                         # basically this creates a "swap group id" where you can place different-graphic units together.
975        (READ_EXPORT, "command_sound_id", "int16_t"),    # sound played when a command is instanciated
976        (READ_EXPORT, "stop_sound_id", "int16_t"),       # sound when the command is done (e.g. unit stops at target position)
977        (READ, "run_pattern", "int8_t"),                 # how animals run around randomly
978    ]
979
980    # TODO: Enable conversion for AOE1
981    # ===========================================================================
982    # if (GameVersion.aoe_1 or GameVersion.aoe_ror) in game_versions:
983    #     data_format.extend([
984    #         (READ_EXPORT, "unit_count", "uint16_t"),
985    #         (READ_EXPORT, "unit_commands", SubdataMember(
986    #             ref_type=UnitCommand,
987    #             length="unit_count",
988    #         )),
989    #     ])
990    # ===========================================================================
991
992
993class ProjectileUnit(ActionUnit):
994    """
995    type_id >= 60
996    Projectile master object
997    """
998
999    name_struct        = "projectile_unit"
1000    name_struct_file   = "unit"
1001    struct_description = "adds attack and armor properties to units."
1002
1003    data_format = [
1004        (READ_EXPORT, None, IncludeMembers(cls=ActionUnit)),
1005    ]
1006
1007    # TODO: Enable conversion for AOE1; replace "default_armor"
1008    # ===========================================================================
1009    # if (GameVersion.aoe_1 or GameVersion.aoe_ror or GameVersion.age2_aok) in game_versions:
1010    #     data_format.append((READ, "default_armor", "uint8_t"))
1011    # else:
1012    #     data_format.append((READ, "default_armor", "int16_t"))
1013    # ===========================================================================
1014    data_format.append((READ, "default_armor", "int16_t"))
1015
1016    data_format.extend([
1017        (READ, "attack_count", "uint16_t"),
1018        (READ, "attacks", SubdataMember(ref_type=HitType, length="attack_count")),
1019        (READ, "armor_count", "uint16_t"),
1020        (READ, "armors", SubdataMember(ref_type=HitType, length="armor_count")),
1021        (READ_EXPORT, "boundary_id", EnumLookupMember(
1022            # the damage received by this unit is multiplied by
1023            # the accessible values on the specified terrain restriction
1024            raw_type    = "int16_t",
1025            type_name   = "boundary_ids",
1026            lookup_dict = {
1027                -1: "NONE",
1028                4: "BUILDING",
1029                6: "DOCK",
1030                10: "WALL",
1031            },
1032        )),
1033        (READ_EXPORT, "weapon_range_max", "float"),
1034        (READ, "blast_range", "float"),
1035        (READ, "attack_speed", "float"),                    # = "reload time"
1036        (READ_EXPORT, "missile_unit_id", "int16_t"),        # which projectile to use?
1037        (READ, "base_hit_chance", "int16_t"),               # probablity of attack hit in percent
1038        (READ, "break_off_combat", "int8_t"),               # = tower mode?; not used anywhere
1039        (READ, "frame_delay", "int16_t"),                   # the frame number at which the missile is fired, = delay
1040        (READ, "weapon_offset", "float[3]"),                # graphics displacement in x, y and z
1041        (READ_EXPORT, "blast_level_offence", EnumLookupMember(
1042            # blasts damage units that have higher or same blast_defense_level
1043            raw_type    = "int8_t",
1044            type_name   = "range_damage_type",
1045            lookup_dict = {
1046                0: "RESOURCES",
1047                1: "TREES",
1048                2: "NEARBY_UNITS",
1049                3: "TARGET_ONLY",
1050                6: "UNKNOWN_6",
1051            },
1052        )),
1053        (READ, "weapon_range_min", "float"),                # minimum range that this projectile requests for display
1054    ])
1055
1056    # TODO: Enable conversion for AOE1; replace "accuracy_dispersion"
1057    # ===========================================================================
1058    # if (GameVersion.aoe_1 or GameVersion.aoe_ror) not in game_versions:
1059    #     data_format.append((READ, "accuracy_dispersion", "float"))
1060    # ===========================================================================
1061    data_format.append((READ, "accuracy_dispersion", "float"))
1062
1063    data_format.extend([
1064        (READ_EXPORT, "fight_sprite_id", "int16_t"),
1065        (READ, "melee_armor_displayed", "int16_t"),
1066        (READ, "attack_displayed", "int16_t"),
1067        (READ, "range_displayed", "float"),
1068        (READ, "reload_time_displayed", "float"),
1069    ])
1070
1071
1072class MissileUnit(ProjectileUnit):
1073    """
1074    type_id == 60
1075    Missile master object
1076    """
1077
1078    name_struct        = "missile_unit"
1079    name_struct_file   = "unit"
1080    struct_description = "adds missile specific unit properties."
1081
1082    data_format = [
1083        (READ_EXPORT, None, IncludeMembers(cls=ProjectileUnit)),
1084        (READ, "projectile_type", "int8_t"),      # 0 = default; 1 = projectile falls vertically to the bottom of the map; 3 = teleporting projectiles
1085        (READ, "smart_mode", "int8_t"),           # "better aiming". tech attribute 19 changes this: 0 = shoot at current pos; 1 = shoot at predicted pos
1086        (READ, "drop_animation_mode", "int8_t"),  # 1 = disappear on hit
1087        (READ, "penetration_mode", "int8_t"),     # 1 = pass through hit object; 0 = stop projectile on hit; (only for graphics, not pass-through damage)
1088        (READ, "area_of_effect_special", "int8_t"),
1089        (READ_EXPORT, "projectile_arc", "float"),
1090    ]
1091
1092
1093class LivingUnit(ProjectileUnit):
1094    """
1095    type_id >= 70
1096    """
1097
1098    name_struct        = "living_unit"
1099    name_struct_file   = "unit"
1100    struct_description = "adds creation location and garrison unit properties."
1101
1102    data_format = [
1103        (READ_EXPORT, None, IncludeMembers(cls=ProjectileUnit)),
1104        (READ_EXPORT, "resource_cost", SubdataMember(
1105            ref_type=ResourceCost,
1106            length=3,
1107        )),
1108        (READ_EXPORT, "creation_time", "int16_t"),         # in seconds
1109        (READ_EXPORT, "creation_location_id", "int16_t"),  # e.g. 118 = villager
1110
1111        # where to place the button with the given icon
1112        # creation page:
1113        # +------------------------+
1114        # | 01 | 02 | 03 | 04 | 05 |
1115        # |----|----|----|----|----|
1116        # | 06 | 07 | 08 | 09 | 10 |
1117        # |----|----|----|----|----|
1118        # | 11 | 12 | 13 | 14 | 15 |
1119        # +------------------------+
1120        #
1121        # additional page (dock):
1122        # +------------------------+
1123        # | 21 | 22 | 23 | 24 | 25 |
1124        # |----|----|----|----|----|
1125        # | 26 | 27 | 28 | 29 | 30 |
1126        # |----|----|----|----|----|
1127        # | 31 | 32 | 33 | 34 | 35 |
1128        # +------------------------+
1129        (READ, "creation_button_id", "int8_t"),
1130    ]
1131
1132    # TODO: Enable conversion for AOE1; replace 13 values below
1133    # ===========================================================================
1134    # if (GameVersion.aoe_1 or GameVersion.aoe_ror) not in game_versions:
1135    #     data_format.extend([(
1136    #         (READ, "rear_attack_modifier", "float"),
1137    #         (READ, "flank_attack_modifier", "float"),
1138    #         (READ_EXPORT, "creatable_type", EnumLookupMember(
1139    #             raw_type    = "int8_t",
1140    #             type_name   = "creatable_types",
1141    #             lookup_dict = {
1142    #                 0: "NONHUMAN",  # building, animal, ship
1143    #                 1: "VILLAGER",  # villager, king
1144    #                 2: "MELEE",     # soldier, siege, predator, trader
1145    #                 3: "MOUNTED",   # camel rider
1146    #                 4: "RELIC",
1147    #                 5: "RANGED_PROJECTILE",  # archer
1148    #                 6: "RANGED_MAGIC",       # monk
1149    #                 21: "TRANSPORT_SHIP",
1150    #             },
1151    #         )),
1152    #         (READ, "hero_mode", "int8_t"),                           # if building: "others" tab in editor, if living unit: "heroes" tab, regenerate health + monk immunity
1153    #         (READ_EXPORT, "garrison_graphic", "int32_t"),            # graphic to display when units are garrisoned
1154    #         (READ, "attack_projectile_count", "float"),              # projectile count when nothing garrisoned, including both normal and duplicated projectiles
1155    #         (READ, "attack_projectile_max_count", "int8_t"),         # total projectiles when fully garrisoned
1156    #         (READ, "attack_projectile_spawning_area_width", "float"),
1157    #         (READ, "attack_projectile_spawning_area_length", "float"),
1158    #         (READ, "attack_projectile_spawning_area_randomness", "float"),  # placement randomness, 0=from single spot, 1=random, 1<less random
1159    #         (READ, "attack_projectile_secondary_unit_id", "int32_t"),       # uses its own attack values
1160    #         (READ, "special_graphic_id", "int32_t"),  # used just before unit reaches its target enemy, configuration:
1161    #         (READ, "special_activation", "int8_t"),  # determines adjacent unit graphics, if 1: building can adapt graphics by adjacent buildings
1162    #                                                 # 0: default: only works when facing the hit angle.
1163    #                                                 # 1: block: activates special graphic when receiving damage and not pursuing the attacker.
1164    #                                                 # while idle, blocking decreases damage taken by 1/3.
1165    #                                                 # also: a wall changes the graphics (when not-an-end piece) because of this.
1166    #                                                 # 2: counter charge: activates special graphic when idle and enemy is near.
1167    #                                                 # while idle, attacks back once on first received hit.
1168    #                                                 # enemy must be unit type 70 and have less than 0.2 max range.
1169    #                                                 # 3: charge: activates special graphic when closer than two tiles to the target.
1170    #                                                 # deals 2X damage on 1st hit
1171    #     ])
1172    # ===========================================================================
1173    data_format.extend([
1174        (READ, "rear_attack_modifier", "float"),
1175        (READ, "flank_attack_modifier", "float"),
1176        (READ_EXPORT, "creatable_type", EnumLookupMember(
1177            raw_type    = "int8_t",
1178            type_name   = "creatable_types",
1179            lookup_dict = {
1180                0: "NONHUMAN",  # building, animal, ship
1181                1: "VILLAGER",  # villager, king
1182                2: "MELEE",     # soldier, siege, predator, trader
1183                3: "MOUNTED",   # camel rider
1184                4: "RELIC",
1185                5: "RANGED_PROJECTILE",  # archer
1186                6: "RANGED_MAGIC",       # monk
1187                21: "TRANSPORT_SHIP",
1188            },
1189        )),
1190        (READ, "hero_mode", "int8_t"),                           # if building: "others" tab in editor, if living unit: "heroes" tab, regenerate health + monk immunity
1191        (READ_EXPORT, "garrison_graphic", "int32_t"),            # graphic to display when units are garrisoned
1192        (READ, "attack_projectile_count", "float"),              # projectile count when nothing garrisoned, including both normal and duplicated projectiles
1193        (READ, "attack_projectile_max_count", "int8_t"),         # total projectiles when fully garrisoned
1194        (READ, "attack_projectile_spawning_area_width", "float"),
1195        (READ, "attack_projectile_spawning_area_length", "float"),
1196        (READ, "attack_projectile_spawning_area_randomness", "float"),  # placement randomness, 0=from single spot, 1=random, 1<less random
1197        (READ, "attack_projectile_secondary_unit_id", "int32_t"),       # uses its own attack values
1198        (READ, "special_graphic_id", "int32_t"),    # used just before unit reaches its target enemy, configuration:
1199        (READ, "special_activation", "int8_t"),     # determines adjacent unit graphics, if 1: building can adapt graphics by adjacent buildings
1200                                                    # 0: default: only works when facing the hit angle.
1201                                                    # 1: block: activates special graphic when receiving damage and not pursuing the attacker.
1202                                                    # while idle, blocking decreases damage taken by 1/3.
1203                                                    # also: a wall changes the graphics (when not-an-end piece) because of this.
1204                                                    # 2: counter charge: activates special graphic when idle and enemy is near.
1205                                                    # while idle, attacks back once on first received hit.
1206                                                    # enemy must be unit type 70 and have less than 0.2 max range.
1207                                                    # 3: charge: activates special graphic when closer than two tiles to the target.
1208                                                    # deals 2X damage on 1st hit
1209    ])
1210    # ===========================================================================
1211
1212    data_format.append((READ, "pierce_armor_displayed", "int16_t"))  # unit stats display of pierce armor
1213
1214
1215class BuildingUnit(LivingUnit):
1216    """
1217    type_id >= 80
1218    """
1219
1220    name_struct        = "building_unit"
1221    name_struct_file   = "unit"
1222    struct_description = "construction graphics and garrison building properties for units."
1223
1224    data_format = [
1225        (READ_EXPORT, None, IncludeMembers(cls=LivingUnit)),
1226        (READ_EXPORT, "construction_graphic_id", "int16_t"),
1227    ]
1228
1229    # TODO: Enable conversion for AOE1; replace "snow_graphic_id"
1230    # ===========================================================================
1231    # if (GameVersion.aoe_1 or GameVersion.aoe_ror or age2_aok) not in game_versions:
1232    #     data_format.append((READ, "snow_graphic_id", "int16_t"))
1233    # ===========================================================================
1234    data_format.append((READ, "snow_graphic_id", "int16_t"))
1235
1236    data_format.extend([
1237        (READ, "adjacent_mode", "int8_t"),            # 1=adjacent units may change the graphics
1238        (READ, "graphics_angle", "int16_t"),
1239        (READ, "disappears_when_built", "int8_t"),
1240        (READ_EXPORT, "stack_unit_id", "int16_t"),    # second building to place directly on top
1241        (READ_EXPORT, "foundation_terrain_id", "int16_t"),  # change underlying terrain to this id when building completed
1242        (READ, "old_overlay_id", "int16_t"),          # deprecated terrain-like structures knowns as "Overlays" from alpha AOE used for roads
1243        (READ, "research_id", "int16_t"),             # research_id to be enabled when building creation
1244    ])
1245
1246    # TODO: Enable conversion for AOE1; replace 5 values below
1247    # ===========================================================================
1248    # if (GameVersion.aoe_1 or GameVersion.aoe_ror) not in game_versions:
1249    #     data_format.extend([
1250    #         (READ, "can_burn", "int8_t"),
1251    #         (READ_EXPORT, "building_annex", SubdataMember(
1252    #             ref_type=BuildingAnnex,
1253    #             length=4
1254    #         )),
1255    #         (READ, "head_unit_id", "int16_t"),            # building at which an annex building is attached to
1256    #         (READ, "transform_unit_id", "int16_t"),       # destination unit id when unit shall transform (e.g. unpack)
1257    #         (READ, "transform_sound_id", "int16_t"),
1258    #     ])
1259    # ===========================================================================
1260    data_format.extend([
1261        (READ, "can_burn", "int8_t"),
1262        (READ_EXPORT, "building_annex", SubdataMember(
1263            ref_type=BuildingAnnex,
1264            length=4
1265        )),
1266        (READ, "head_unit_id", "int16_t"),            # building at which an annex building is attached to
1267        (READ, "transform_unit_id", "int16_t"),       # destination unit id when unit shall transform (e.g. unpack)
1268        (READ, "transform_sound_id", "int16_t"),
1269    ])
1270    # ===========================================================================
1271
1272    data_format.append((READ, "construction_sound_id", "int16_t"))
1273
1274    # TODO: Enable conversion for AOE1; replace 5 values below
1275    # ===========================================================================
1276    # if (GameVersion.aoe_1 or GameVersion.aoe_ror) not in game_versions:
1277    #     data_format.extend([
1278    #         (READ_EXPORT, "garrison_type", EnumLookupMember(
1279    #             raw_type    = "int8_t",
1280    #             type_name   = "garrison_types",
1281    #             lookup_dict = {  # TODO: create bitfield
1282    #                 0x00: "NONE",
1283    #                 0x01: "VILLAGER",
1284    #                 0x02: "INFANTRY",
1285    #                 0x04: "CAVALRY",
1286    #                 0x08: "MONK",
1287    #                 0x0b: "NOCAVALRY",
1288    #                 0x0f: "ALL",
1289    #             },
1290    #         )),
1291    #         (READ, "garrison_heal_rate", "float"),
1292    #         (READ, "garrison_repair_rate", "float"),
1293    #         (READ, "salvage_unit_id", "int16_t"),       # id of the unit used for salvages
1294    #         (READ, "salvage_attributes", "int8_t[6]"),  # list of attributes for salvages (looting table)
1295    #     ])
1296    # ===========================================================================
1297    data_format.extend([
1298        (READ_EXPORT, "garrison_type", EnumLookupMember(
1299            raw_type    = "int8_t",
1300            type_name   = "garrison_types",
1301            lookup_dict = {  # TODO: create bitfield
1302                0x00: "NONE",
1303                0x01: "VILLAGER",
1304                0x02: "INFANTRY",
1305                0x04: "CAVALRY",
1306                0x08: "MONK",
1307                0x0b: "NOCAVALRY",
1308                0x0f: "ALL",
1309            },
1310        )),
1311        (READ, "garrison_heal_rate", "float"),
1312        (READ, "garrison_repair_rate", "float"),
1313        (READ, "salvage_unit_id", "int16_t"),       # id of the unit used for salvages
1314        (READ, "salvage_attributes", "int8_t[6]"),  # list of attributes for salvages (looting table)
1315    ])
1316    # ===========================================================================
1317
1318
1319# unit type id => human readable name
1320# used as member name in the resulting struct
1321unit_type_lookup = {
1322    10: "object",
1323    20: "animated",
1324    25: "doppelganger",
1325    30: "moving",
1326    40: "action",
1327    60: "missile",
1328    70: "living",
1329    80: "building",
1330    90: "tree",
1331}
1332
1333
1334# name => attribute class
1335unit_type_class_lookup = {
1336    "object":         UnitObject,
1337    "animated":       AnimatedUnit,
1338    "doppelganger":   DoppelgangerUnit,
1339    "moving":         MovingUnit,
1340    "action":         ActionUnit,
1341    "missile":        MissileUnit,
1342    "living":         LivingUnit,
1343    "building":       BuildingUnit,
1344    "tree":           TreeUnit,
1345}
1346