Skip to content

Commit b220d9b

Browse files
merge
2 parents 6d1ed89 + 7e5f12d commit b220d9b

11 files changed

Lines changed: 135 additions & 16 deletions

File tree

mgz/common/chat.py

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,66 @@
33
import logging
44
from enum import Enum
55

6+
from mgz.fast import Age
7+
68
LOGGER = logging.getLogger(__name__)
9+
FEUDAL_AGE_MARKERS = [
10+
'봉건 시대',
11+
'Edad Feudal',
12+
'封建时代',
13+
'Feudalzeit',
14+
'Feodal Çağ',
15+
'封建時代',
16+
'Edad Feudal',
17+
'領主の時代',
18+
'Zaman Feudal',
19+
'Età feudale',
20+
'Feudal Age',
21+
'Thời phong kiến',
22+
'सामंतवादी युग',
23+
'Era Feudalna',
24+
'Idade Feudal',
25+
'Âge féodal',
26+
'Феодальная эпоха',
27+
]
28+
CASTLE_AGE_MARKERS = [
29+
'성주 시대',
30+
'Ed. Castillos',
31+
'城堡时代',
32+
'Ritterzeit',
33+
'Kale Çağı',
34+
'城堡時代',
35+
'Edad de los Castillos',
36+
'城主の時代',
37+
'Zaman Kastil',
38+
'Età dei castelli',
39+
'Castle Age',
40+
'Thời lâu đài',
41+
'परिवर्तन युग',
42+
'Era Zamków',
43+
'Idade dos Castelos',
44+
'Âge des châteaux',
45+
'Замковая эпоха',
46+
]
47+
IMPERIAL_AGE_MARKERS = [
48+
'왕정 시대',
49+
'Edad Imperial',
50+
'帝王时代',
51+
'Imperialzeit',
52+
'İmparatorluk Çağı',
53+
'帝王時代',
54+
'Edad Imperial',
55+
'帝王の時代',
56+
'Zaman Empayar',
57+
'Età imperiale',
58+
'Imperial Age',
59+
'Thời đế quốc',
60+
'साम्राज्यवादी युग',
61+
'Era Imperiów',
62+
'Idade Imperial',
63+
'Âge impérial',
64+
'Имперская эпоха',
65+
]
766
AGE_MARKERS = [
867
'advanced to the',
968
'a progressé vers',
@@ -28,6 +87,9 @@
2887
'đã nâng cấp',
2988
'progressé vers',
3089
'wkracza do',
90+
'युग में उन्नत है।', # hi
91+
'telah mara ke', # ms
92+
'geçti', # tr
3193
]
3294
SAVE_MARKERS = [
3395
'Continuar con la partida en vez de guardar y salir',
@@ -86,10 +148,6 @@ def parse_chat(line, encoding, timestamp, players, diplomacy_type=None, originat
86148
if line.find(save_marker) > 0:
87149
data['type'] = Chat.SAVE
88150
return data
89-
for age_marker in AGE_MARKERS:
90-
if line.find(age_marker) > 0:
91-
data['type'] = Chat.AGE
92-
return data
93151
if line.find('Voobly: Ratings provided') > 0:
94152
_parse_ladder(data, line)
95153
elif line.find('Voobly') == 3:
@@ -104,6 +162,17 @@ def parse_chat(line, encoding, timestamp, players, diplomacy_type=None, originat
104162
_parse_json(data, line, diplomacy_type)
105163
else:
106164
_parse_chat(data, line, players, diplomacy_type)
165+
166+
for age_marker in AGE_MARKERS:
167+
if line.find(age_marker) > 0:
168+
data['type'] = Chat.AGE
169+
if any(marker in line for marker in FEUDAL_AGE_MARKERS):
170+
data['age'] = Age.FEUDAL_AGE
171+
if any(marker in line for marker in CASTLE_AGE_MARKERS):
172+
data['age'] = Age.CASTLE_AGE
173+
if any(marker in line for marker in IMPERIAL_AGE_MARKERS):
174+
data['age'] = Age.IMPERIAL_AGE
175+
107176
if not _validate(data, players):
108177
data['type'] = Chat.DISCARD
109178
return data

mgz/fast/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import io
33
import struct
44

5-
from mgz.fast.enums import Operation, Action, Postgame
5+
from mgz.fast.enums import Operation, Action, Postgame, Age
66
from mgz.fast.actions import parse_action_71094
77
from mgz.util import check_flags, unpack
88

mgz/fast/enums.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,10 @@ class Postgame(Enum):
8787
"""Postgame types."""
8888
WORLD_TIME = 1
8989
LEADERBOARDS = 2
90+
91+
class Age(Enum):
92+
"""Age types."""
93+
DARK_AGE = 1
94+
FEUDAL_AGE = 2
95+
CASTLE_AGE = 3
96+
IMPERIAL_AGE = 4

mgz/fast/header.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ def parse_lobby(data, version, save):
175175
data.read(8)
176176
if save >= 64.3:
177177
data.read(16)
178+
if save >= 66.3:
179+
data.read(1)
178180
data.read(8)
179181
if version not in (Version.DE, Version.HD):
180182
data.read(1)
@@ -280,7 +282,9 @@ def parse_scenario(data, num_players, version, save):
280282
map_id, difficulty_id = unpack('<II', data)
281283
remainder = data.read()
282284
if version is Version.DE:
283-
if save >= 64.3:
285+
if save >= 66.3:
286+
settings_version = 4.5
287+
elif save >= 64.3:
284288
settings_version = 4.1
285289
elif save >= 63:
286290
settings_version = 3.9
@@ -406,7 +410,7 @@ def parse_de(data, version, save, skip=False):
406410
if save > 50:
407411
data.read(1)
408412
players = []
409-
for _ in range(num_players if save >= 37 else 8):
413+
for _ in range(num_players if 66.3 > save >= 37 else 8):
410414
data.read(4)
411415
color_id = unpack('<i', data)
412416
data.read(2)
@@ -424,6 +428,8 @@ def parse_de(data, version, save, skip=False):
424428
data.read(1)
425429
ai_name = de_string(data)
426430
name = de_string(data)
431+
if save >= 66.3:
432+
_name2 = de_string(data)
427433
type = unpack('<I', data)
428434
profile_id, number = unpack('<I4xi', data)
429435
if save < 25.22:
@@ -448,7 +454,7 @@ def parse_de(data, version, save, skip=False):
448454
prefer_random=prefer_random == 1
449455
))
450456
data.read(12)
451-
if save >= 37:
457+
if 66.3 > save >= 37:
452458
for _ in range(8 - num_players):
453459
if save >= 61.5:
454460
data.read(4)
@@ -509,6 +515,8 @@ def parse_de(data, version, save, skip=False):
509515
data.read(1)
510516
if save >= 63:
511517
data.read(5)
518+
if save >= 66.3:
519+
data.read(16)
512520
if not skip:
513521
de_string(data)
514522
data.read(8)

mgz/header/de.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@
3434
"ai_civ_name_index"/Byte,
3535
"ai_name"/de_string,
3636
"name"/de_string,
37+
"name2"/If(lambda ctx: find_save_version(ctx) >= 66.3,de_string),
3738
"type"/PlayerTypeEnum(Int32ul),
38-
"profile_id"/Int32ul,
39+
"profile_id"/Int32sl, # -1 if it's an empty slot
3940
"ai_unknown" / Int32sl, # Often 0, sometimes -1 for AI players
4041
"player_number"/Int32sl,
4142
"hd_rm_elo"/If(lambda ctx: find_save_version(ctx) < 25.22, Int32ul),
@@ -106,12 +107,12 @@
106107
"handicap"/If(lambda ctx: find_save_version(ctx) >= 25.06, Flag),
107108
"unk"/If(lambda ctx: find_save_version(ctx) >= 50, Flag),
108109
separator,
109-
"players"/Array(lambda ctx: ctx.num_players if find_save_version(ctx) >= 37 else 8, player),
110+
"players"/Array(lambda ctx: ctx.num_players if find_save_version(ctx) >= 37 and find_save_version(ctx) < 66.3 else 8, player),
110111
Bytes(9),
111112
"fog_of_war"/Flag,
112113
"cheat_notifications"/Flag,
113114
"colored_chat"/Flag,
114-
"empty_slots"/If(lambda ctx: find_save_version(ctx) >= 37, Array(lambda ctx: 8 - ctx.num_players, Struct(
115+
"empty_slots"/If(lambda ctx: find_save_version(ctx) >= 37 and find_save_version(ctx) < 66.3, Array(lambda ctx: 8 - ctx.num_players, Struct(
115116
"unk"/If(lambda ctx: find_save_version(ctx) >= 61.5, Int32ul),
116117
"i0x"/Int32ul,
117118
"i0a"/Int32ul,
@@ -162,6 +163,9 @@
162163
If(lambda ctx: find_save_version(ctx) >= 50, Bytes(8)),
163164
If(lambda ctx: find_save_version(ctx) >= 61.5, Flag),
164165
If(lambda ctx: find_save_version(ctx) >= 63, Bytes(5)),
166+
"unknown_count"/If(lambda ctx: find_save_version(ctx) >= 66.3, Int32ul),
167+
If(lambda ctx: find_save_version(ctx) >= 66.3, Bytes(12)),
168+
If(lambda ctx: find_save_version(ctx) >= 66.3, Array(lambda ctx: ctx.unknown_count, Bytes(4))),
165169
de_string,
166170
Bytes(5),
167171
If(lambda ctx: find_save_version(ctx) >= 13.13, Byte),

mgz/header/lobby.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
If(lambda ctx: find_save_version(ctx) >= 26.16, Bytes(5)),
1616
If(lambda ctx: find_save_version(ctx) >= 37, Bytes(8)),
1717
If(lambda ctx: find_save_version(ctx) >= 64.3, Bytes(16)),
18+
If(lambda ctx: find_save_version(ctx) >= 66.3, Bytes(1)),
1819
Array(8, "teams"/Byte), # team number selected by each player
1920
If(lambda ctx: ctx._.version not in (Version.DE, Version.HD),
2021
Padding(1),
@@ -60,6 +61,7 @@
6061
If(lambda ctx: find_save_version(ctx) >= 26.16, Bytes(4)),
6162
If(lambda ctx: find_save_version(ctx) >= 37, Bytes(4)),
6263
If(lambda ctx: find_save_version(ctx) >= 50, Bytes(1)),
64+
If(lambda ctx: find_save_version(ctx) >= 66.3, Bytes(4)),
6365
)
6466
)
6567
)

mgz/header/objects.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
"pathing_group_len"/Int32ul,
8282
"pathing_group"/Array(lambda ctx: ctx.pathing_group_len, "object_id"/Int32ul)
8383
)),
84+
If(lambda ctx: find_save_version(ctx) >= 66.3, Bytes(2)),
8485
"group_id"/Int32sl,
8586
"roo_already_called"/Byte,
8687
"de_static_unk1"/If(lambda ctx: find_version(ctx) == Version.DE, Bytes(17)),
@@ -127,7 +128,8 @@
127128
If(lambda ctx: ctx.type > 0, Bytes(16))
128129
))
129130
))
130-
))
131+
)),
132+
If(lambda ctx: find_save_version(ctx) >= 66.3, Bytes(4)),
131133
)
132134

133135

@@ -400,6 +402,7 @@
400402
Embedded(base_combat),
401403
"de_pre"/If(lambda ctx: find_version(ctx) == Version.DE and find_save_version(ctx) < 37, Bytes(4)),
402404
"de"/If(lambda ctx: find_version(ctx) == Version.DE, Bytes(14)),
405+
"de_unknown_66_3_1"/If(lambda ctx: find_save_version(ctx) >= 66.3, Bytes(4)),
403406
"de_2"/If(lambda ctx: find_save_version(ctx) >= 26.16, Bytes(16)),
404407
"de_3"/If(lambda ctx: 63 > find_save_version(ctx) >= 26.18, Bytes(1)),
405408
"de_4"/If(lambda ctx: find_save_version(ctx) >= 61.5, Bytes(4)),
@@ -497,6 +500,7 @@
497500
"de_unk_5"/If(lambda ctx: find_save_version(ctx) >= 50.4, Bytes(4)),
498501
"de_unk_6"/If(lambda ctx: find_save_version(ctx) >= 61.5, Bytes(12)),
499502
"de_unk_7"/If(lambda ctx: find_save_version(ctx) >= 64.3, Bytes(4)),
503+
"de_unknown_66_3_2"/If(lambda ctx: find_save_version(ctx) >= 66.3, Bytes(5)),
500504
)
501505

502506

mgz/header/scenario.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@
2222
"active"/Int32ul,
2323
"human"/Int32ul,
2424
"civilization"/Int32ul,
25+
"civ_repeat"/If(lambda ctx: find_save_version(ctx) >= 13.34, Int32ul),
2526
"constant"/Int32ul, # 0x04 0x00 0x00 0x00
2627
)),
2728
Padding(5),
2829
"elapsed_time"/Float32l,
2930
"scenario_filename"/PascalString(lengthfield="scenario_filename_length"/Int16ul),
3031
If(lambda ctx: ctx._._.version == Version.DE, Struct(
3132
Padding(64),
32-
If(lambda ctx: find_save_version(ctx) >= 13.34, Padding(64))
33+
# If(lambda ctx: find_save_version(ctx) >= 13.34, Padding(64)) 4*16 = 64
3334
))
3435
)
3536

@@ -152,10 +153,11 @@
152153
If(lambda ctx: 61.5 > find_save_version(ctx) >= 37, Find(struct.pack('<d', 3.5), None)),
153154
If(lambda ctx: 63 > find_save_version(ctx) >= 61.5, Find(struct.pack('<d', 3.6), None)),
154155
If(lambda ctx: 64.3 > find_save_version(ctx) >= 63, Find(struct.pack('<d', 3.9), None)),
155-
If(lambda ctx: find_save_version(ctx) >= 64.3, Find(struct.pack('<d', 4.1), None)),
156+
If(lambda ctx: 66.3 > find_save_version(ctx) >= 64.3, Find(struct.pack('<d', 4.1), None)),
157+
If(lambda ctx: find_save_version(ctx) >= 66.3, Find(struct.pack('<d', 4.5), None)),
156158
),
157159
"end_of_game_settings"/Find(b'\x9a\x99\x99\x99\x99\x99\xf9\\x3f', None),
158-
)
160+
),
159161
)
160162

161163

mgz/model/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ def parse_match(handle):
234234
resigned = []
235235
actions = []
236236
viewlocks = []
237+
uptimes = []
237238
eapm = collections.Counter()
238239
last_viewlock = None
239240
while True:
@@ -269,6 +270,14 @@ def parse_match(handle):
269270
players[chat['player_number']]
270271
))
271272
inputs.add_chat(chats[-1])
273+
if chat['type'] == ChatEnum.AGE:
274+
uptimes.append(
275+
Uptime(
276+
timedelta(milliseconds=chat['timestamp'] + data['map']['restore_time']),
277+
chat['age'],
278+
players.get(chat['player_number']),
279+
)
280+
)
272281
elif op_type is fast.Operation.ACTION:
273282
action_type, action_data = op_data
274283
action = Action(timedelta(milliseconds=timestamp), action_type, action_data)
@@ -375,7 +384,8 @@ def parse_match(handle):
375384
data['de']['visibility_id'] == 2 if data['version'] is Version.DE else None,
376385
get_hash(data),
377386
actions,
378-
inputs.inputs
387+
inputs.inputs,
388+
uptimes
379389
)
380390

381391

mgz/model/definitions.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from dataclasses import dataclass
44
from datetime import timedelta, datetime
55
from mgz.fast import Action as ActionEnum
6+
from mgz.fast import Age as AgeEnum
67
from mgz.util import Version
78

89

@@ -151,6 +152,17 @@ class Chat:
151152
def __repr__(self):
152153
return f'[{self.timestamp}] {self.player}: {self.message}'
153154

155+
@dataclass
156+
class Uptime:
157+
"""Represents an advanced to age event."""
158+
159+
timestamp: timedelta
160+
age: AgeEnum
161+
player: Player
162+
163+
def __repr__(self):
164+
return f'[{self.timestamp}] {self.player} -> {self.age}'
165+
154166

155167
@dataclass
156168
class Match:
@@ -202,3 +214,4 @@ class Match:
202214
hash: str
203215
actions: list
204216
inputs: list
217+
uptimes: list

0 commit comments

Comments
 (0)