Skip to content

Commit ed41078

Browse files
committed
Support FileVersion 8.4
1 parent db81f7b commit ed41078

File tree

4 files changed

+194
-3
lines changed

4 files changed

+194
-3
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ packages = ["src/genieutils"]
77

88
[project]
99
name = "genieutils-py"
10-
version = "0.0.7"
10+
version = "0.0.8"
1111
authors = [
1212
{ name = "SiegeEngineers", email = "[email protected]" },
1313
]

src/genieutils/unit.py

Lines changed: 191 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,11 @@ class Type50(GenieClass):
234234
displayed_range: float
235235
displayed_reload_time: float
236236
blast_damage: float
237+
damage_reflection: float
238+
friendly_fire_damage: float
239+
interrupt_frame: int
240+
garrison_firepower: float
241+
attack_graphic_2: int
237242

238243
@classmethod
239244
def from_bytes(cls, content: ByteHandler) -> 'Type50':
@@ -261,6 +266,17 @@ def from_bytes(cls, content: ByteHandler) -> 'Type50':
261266
displayed_range = content.read_float()
262267
displayed_reload_time = content.read_float()
263268
blast_damage = content.read_float()
269+
damage_reflection = 0.0
270+
friendly_fire_damage = 1.0
271+
interrupt_frame = -1
272+
garrison_firepower = 0.0
273+
attack_graphic_2 = -1
274+
if content.version >= Version.VER_84:
275+
damage_reflection = content.read_float()
276+
friendly_fire_damage = content.read_float()
277+
interrupt_frame = content.read_int_16()
278+
garrison_firepower = content.read_float()
279+
attack_graphic_2 = content.read_int_16()
264280
return cls(
265281
base_armor=base_armor,
266282
attacks=attacks,
@@ -284,10 +300,15 @@ def from_bytes(cls, content: ByteHandler) -> 'Type50':
284300
displayed_range=displayed_range,
285301
displayed_reload_time=displayed_reload_time,
286302
blast_damage=blast_damage,
303+
damage_reflection=damage_reflection,
304+
friendly_fire_damage=friendly_fire_damage,
305+
interrupt_frame=interrupt_frame,
306+
garrison_firepower=garrison_firepower,
307+
attack_graphic_2=attack_graphic_2,
287308
)
288309

289310
def to_bytes(self, version: Version) -> bytes:
290-
return b''.join([
311+
value = b''.join([
291312
self.write_int_16(self.base_armor),
292313
self.write_int_16(len(self.attacks)),
293314
self.write_class_array(self.attacks, version),
@@ -313,6 +334,15 @@ def to_bytes(self, version: Version) -> bytes:
313334
self.write_float(self.displayed_reload_time),
314335
self.write_float(self.blast_damage),
315336
])
337+
if version >= Version.VER_84:
338+
value += b''.join([
339+
self.write_float(self.damage_reflection),
340+
self.write_float(self.friendly_fire_damage),
341+
self.write_int_16(self.interrupt_frame),
342+
self.write_float(self.garrison_firepower),
343+
self.write_int_16(self.attack_graphic_2),
344+
])
345+
return value
316346

317347

318348
PROJECTILE_FORMAT = '<bbbbbf'
@@ -379,6 +409,8 @@ def to_bytes(self, version: Version) -> bytes:
379409

380410
CREATABLE_FORMAT = '<hhbffbblhhhffhhffffbfffllbh'
381411
CREATABLE_FORMAT_LENGTH = 77
412+
CREATABLE_FORMAT_84 = '<hhbffbblhhhhffhhhlbfhllhffffbfffllbh'
413+
CREATABLE_FORMAT_LENGTH_84 = struct.calcsize(CREATABLE_FORMAT_84)
382414

383415
@dataclass(slots=True)
384416
class Creatable(GenieClass):
@@ -394,10 +426,19 @@ class Creatable(GenieClass):
394426
spawning_graphic: int
395427
upgrade_graphic: int
396428
hero_glow_graphic: int
429+
idle_attack_graphic: int
397430
max_charge: float
398431
recharge_rate: float
399432
charge_event: int
400433
charge_type: int
434+
charge_target: int
435+
charge_projectile_unit: int
436+
attack_priority: int
437+
invulnerability_level: float
438+
button_icon_id: int
439+
button_short_tooltip_id: int
440+
button_extended_tooltip_id: int
441+
button_hotkey_action: int
401442
min_conversion_time_mod: float
402443
max_conversion_time_mod: float
403444
conversion_chance_mod: float
@@ -411,6 +452,12 @@ class Creatable(GenieClass):
411452

412453
@classmethod
413454
def from_bytes(cls, content: ByteHandler) -> 'Creatable':
455+
if content.version >= Version.VER_84:
456+
return cls.from_bytes_84(content)
457+
return cls.from_bytes_78(content)
458+
459+
@classmethod
460+
def from_bytes_78(cls, content: ByteHandler) -> 'Creatable':
414461
resource_costs = content.read_class_array_3(ResourceCost)
415462
train_time, \
416463
train_location_id, \
@@ -439,6 +486,96 @@ def from_bytes(cls, content: ByteHandler) -> 'Creatable':
439486
special_graphic, \
440487
special_ability, \
441488
displayed_pierce_armor = struct.unpack(CREATABLE_FORMAT, content.consume_range(CREATABLE_FORMAT_LENGTH))
489+
idle_attack_graphic = -1
490+
charge_target = 0
491+
charge_projectile_unit = -1
492+
attack_priority = 0
493+
invulnerability_level = 0
494+
button_icon_id = -1
495+
button_short_tooltip_id = -1
496+
button_extended_tooltip_id = -1
497+
button_hotkey_action = -1
498+
return cls(
499+
resource_costs=resource_costs,
500+
train_time=train_time,
501+
train_location_id=train_location_id,
502+
button_id=button_id,
503+
rear_attack_modifier=rear_attack_modifier,
504+
flank_attack_modifier=flank_attack_modifier,
505+
creatable_type=creatable_type,
506+
hero_mode=hero_mode,
507+
garrison_graphic=garrison_graphic,
508+
spawning_graphic=spawning_graphic,
509+
upgrade_graphic=upgrade_graphic,
510+
hero_glow_graphic=hero_glow_graphic,
511+
idle_attack_graphic=idle_attack_graphic,
512+
max_charge=max_charge,
513+
recharge_rate=recharge_rate,
514+
charge_event=charge_event,
515+
charge_type=charge_type,
516+
charge_target=charge_target,
517+
charge_projectile_unit=charge_projectile_unit,
518+
attack_priority=attack_priority,
519+
invulnerability_level=invulnerability_level,
520+
button_icon_id=button_icon_id,
521+
button_short_tooltip_id=button_short_tooltip_id,
522+
button_extended_tooltip_id=button_extended_tooltip_id,
523+
button_hotkey_action=button_hotkey_action,
524+
min_conversion_time_mod=min_conversion_time_mod,
525+
max_conversion_time_mod=max_conversion_time_mod,
526+
conversion_chance_mod=conversion_chance_mod,
527+
total_projectiles=total_projectiles,
528+
max_total_projectiles=max_total_projectiles,
529+
projectile_spawning_area=(
530+
projectile_spawning_area_1,
531+
projectile_spawning_area_2,
532+
projectile_spawning_area_3),
533+
secondary_projectile_unit=secondary_projectile_unit,
534+
special_graphic=special_graphic,
535+
special_ability=special_ability,
536+
displayed_pierce_armor=displayed_pierce_armor,
537+
)
538+
539+
@classmethod
540+
def from_bytes_84(cls, content: ByteHandler) -> 'Creatable':
541+
resource_costs = content.read_class_array_3(ResourceCost)
542+
train_time, \
543+
train_location_id, \
544+
button_id, \
545+
rear_attack_modifier, \
546+
flank_attack_modifier, \
547+
creatable_type, \
548+
hero_mode, \
549+
garrison_graphic, \
550+
spawning_graphic, \
551+
upgrade_graphic, \
552+
hero_glow_graphic, \
553+
idle_attack_graphic, \
554+
max_charge, \
555+
recharge_rate, \
556+
charge_event, \
557+
charge_type, \
558+
charge_target, \
559+
charge_projectile_unit, \
560+
attack_priority, \
561+
invulnerability_level, \
562+
button_icon_id, \
563+
button_short_tooltip_id, \
564+
button_extended_tooltip_id, \
565+
button_hotkey_action, \
566+
min_conversion_time_mod, \
567+
max_conversion_time_mod, \
568+
conversion_chance_mod, \
569+
total_projectiles, \
570+
max_total_projectiles, \
571+
projectile_spawning_area_1, \
572+
projectile_spawning_area_2, \
573+
projectile_spawning_area_3, \
574+
secondary_projectile_unit, \
575+
special_graphic, \
576+
special_ability, \
577+
displayed_pierce_armor = struct.unpack(CREATABLE_FORMAT_84,
578+
content.consume_range(CREATABLE_FORMAT_LENGTH_84))
442579
return cls(
443580
resource_costs=resource_costs,
444581
train_time=train_time,
@@ -452,10 +589,19 @@ def from_bytes(cls, content: ByteHandler) -> 'Creatable':
452589
spawning_graphic=spawning_graphic,
453590
upgrade_graphic=upgrade_graphic,
454591
hero_glow_graphic=hero_glow_graphic,
592+
idle_attack_graphic=idle_attack_graphic,
455593
max_charge=max_charge,
456594
recharge_rate=recharge_rate,
457595
charge_event=charge_event,
458596
charge_type=charge_type,
597+
charge_target=charge_target,
598+
charge_projectile_unit=charge_projectile_unit,
599+
attack_priority=attack_priority,
600+
invulnerability_level=invulnerability_level,
601+
button_icon_id=button_icon_id,
602+
button_short_tooltip_id=button_short_tooltip_id,
603+
button_extended_tooltip_id=button_extended_tooltip_id,
604+
button_hotkey_action=button_hotkey_action,
459605
min_conversion_time_mod=min_conversion_time_mod,
460606
max_conversion_time_mod=max_conversion_time_mod,
461607
conversion_chance_mod=conversion_chance_mod,
@@ -472,6 +618,11 @@ def from_bytes(cls, content: ByteHandler) -> 'Creatable':
472618
)
473619

474620
def to_bytes(self, version: Version) -> bytes:
621+
if version >= Version.VER_84:
622+
return self.to_bytes_84(version)
623+
return self.to_bytes_78(version)
624+
625+
def to_bytes_78(self, version: Version) -> bytes:
475626
return self.write_class_array(self.resource_costs, version) + \
476627
struct.pack(CREATABLE_FORMAT,
477628
self.train_time,
@@ -501,6 +652,45 @@ def to_bytes(self, version: Version) -> bytes:
501652
self.displayed_pierce_armor,
502653
)
503654

655+
def to_bytes_84(self, version: Version) -> bytes:
656+
return self.write_class_array(self.resource_costs, version) + \
657+
struct.pack(CREATABLE_FORMAT_84,
658+
self.train_time,
659+
self.train_location_id,
660+
self.button_id,
661+
self.rear_attack_modifier,
662+
self.flank_attack_modifier,
663+
self.creatable_type,
664+
self.hero_mode,
665+
self.garrison_graphic,
666+
self.spawning_graphic,
667+
self.upgrade_graphic,
668+
self.hero_glow_graphic,
669+
self.idle_attack_graphic,
670+
self.max_charge,
671+
self.recharge_rate,
672+
self.charge_event,
673+
self.charge_type,
674+
self.charge_target,
675+
self.charge_projectile_unit,
676+
self.attack_priority,
677+
self.invulnerability_level,
678+
self.button_icon_id,
679+
self.button_short_tooltip_id,
680+
self.button_extended_tooltip_id,
681+
self.button_hotkey_action,
682+
self.min_conversion_time_mod,
683+
self.max_conversion_time_mod,
684+
self.conversion_chance_mod,
685+
self.total_projectiles,
686+
self.max_total_projectiles,
687+
*self.projectile_spawning_area,
688+
self.secondary_projectile_unit,
689+
self.special_graphic,
690+
self.special_ability,
691+
self.displayed_pierce_armor,
692+
)
693+
504694

505695
BUILDING_ANNEX_FORMAT = '<hff'
506696
BUILDING_ANNEX_FORMAT_LENGTH = 10

src/genieutils/versions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class Version(Enum):
1111
VER_76 = 'VER 7.6'
1212
VER_77 = 'VER 7.7'
1313
VER_78 = 'VER 7.8'
14+
VER_84 = 'VER 8.4'
1415

1516
def __lt__(self, other: 'Version'):
1617
return self.value < other.value

tests/test_dat_compatibility.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def test_compatibility_with_dat_files(self, datfile: Path):
1818
version, data = self.get_version(datfile)
1919
print(datfile)
2020
print(version)
21-
if version in ('VER 7.8', 'VER 7.7'):
21+
if version in ('VER 8.4', 'VER 7.8', 'VER 7.7'):
2222
byte_handler = ByteHandler(memoryview(data))
2323
content = DatFile.from_bytes(byte_handler)
2424
re_encoded = content.to_bytes()

0 commit comments

Comments
 (0)