aoe2rec-py 0.1.18__cp314-cp314t-musllinux_1_2_armv7l.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
aoe2rec_py/__init__.py
ADDED
|
Binary file
|
aoe2rec_py/summary.py
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
import collections
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
import json
|
|
6
|
+
from typing import BinaryIO
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from typing import override
|
|
10
|
+
except ImportError:
|
|
11
|
+
|
|
12
|
+
def override(f):
|
|
13
|
+
return f
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from aoe2rec_py import aoe2rec_py
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class Chat:
|
|
21
|
+
timestamp: timedelta
|
|
22
|
+
player: str
|
|
23
|
+
message: str
|
|
24
|
+
|
|
25
|
+
@override
|
|
26
|
+
def __str__(self):
|
|
27
|
+
return f"{self.timestamp} - {self.player}: {self.message}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class RecSummary:
|
|
31
|
+
def __init__(self, handle: BinaryIO):
|
|
32
|
+
data = handle.read()
|
|
33
|
+
self._cache = aoe2rec_py.parse_rec(data)
|
|
34
|
+
self.players = {
|
|
35
|
+
player_id + 1: {"resigned": False, "elo": 0, "eapm": 0, **player}
|
|
36
|
+
for player_id, player in enumerate(
|
|
37
|
+
self._cache["zheader"]["game_settings"]["players"]
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
self.duration: float = 0
|
|
41
|
+
self.chats: list[Chat] = []
|
|
42
|
+
|
|
43
|
+
self._parse_operations()
|
|
44
|
+
|
|
45
|
+
def _parse_operations(self):
|
|
46
|
+
eapm_counter = collections.Counter()
|
|
47
|
+
for event in self._cache["operations"]:
|
|
48
|
+
if "Sync" in event:
|
|
49
|
+
self.duration += event["Sync"]["time_increment"]
|
|
50
|
+
if "Chat" in event:
|
|
51
|
+
chat = json.loads(event["Chat"]["text"])
|
|
52
|
+
self.chats.append(
|
|
53
|
+
Chat(
|
|
54
|
+
self.get_duration(),
|
|
55
|
+
self.players[chat["player"]]["name"],
|
|
56
|
+
chat["message"],
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
if "Action" in event:
|
|
60
|
+
actions = event["Action"]["action_data"]
|
|
61
|
+
for action_type, action_data in actions.items():
|
|
62
|
+
if "player_id" in action_data:
|
|
63
|
+
eapm_counter[action_data["player_id"]] += 1
|
|
64
|
+
if action_type == "Resign":
|
|
65
|
+
self.players[action_data["player_id"]]["resigned"] = True
|
|
66
|
+
if "PostGame" in event:
|
|
67
|
+
for block in event["PostGame"]["blocks"]:
|
|
68
|
+
if (
|
|
69
|
+
"Leaderboards" not in block
|
|
70
|
+
or block["Leaderboards"]["num_leaderboards"] < 1
|
|
71
|
+
):
|
|
72
|
+
continue
|
|
73
|
+
for player in block["Leaderboards"]["leaderboards"][0]["players"]:
|
|
74
|
+
if (player["player_number"] + 1) in self.players:
|
|
75
|
+
self.players[player["player_number"] + 1]["elo"] = player[
|
|
76
|
+
"elo"
|
|
77
|
+
]
|
|
78
|
+
total_minutes = self.get_duration().total_seconds() / 60
|
|
79
|
+
for player_id, action_count in eapm_counter.items():
|
|
80
|
+
self.players[player_id]["eapm"] = int(round(action_count / total_minutes))
|
|
81
|
+
|
|
82
|
+
def get_chat(self):
|
|
83
|
+
return self.chats
|
|
84
|
+
|
|
85
|
+
def get_postgame(self):
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
def has_achievements(self):
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
def get_header(self):
|
|
92
|
+
return self._cache["zheader"]
|
|
93
|
+
|
|
94
|
+
def get_start_time(self):
|
|
95
|
+
return self._cache["zheader"]["replay"]["world_time"]
|
|
96
|
+
|
|
97
|
+
def get_duration(self):
|
|
98
|
+
return timedelta(
|
|
99
|
+
milliseconds=self.duration + self._cache["zheader"]["replay"]["world_time"]
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def get_restored(self):
|
|
103
|
+
return self.get_start_time() > 0, self.get_start_time()
|
|
104
|
+
|
|
105
|
+
def get_version(self):
|
|
106
|
+
header = self._cache["zheader"]
|
|
107
|
+
major = header["version_major"]
|
|
108
|
+
minor = header["version_minor"]
|
|
109
|
+
version = float(f"{major}.{minor}")
|
|
110
|
+
return (
|
|
111
|
+
"DE",
|
|
112
|
+
header["game"],
|
|
113
|
+
version,
|
|
114
|
+
self._cache["log_version"],
|
|
115
|
+
header["build"],
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def get_owner(self):
|
|
119
|
+
return self._cache["zheader"]["replay"]["rec_player"]
|
|
120
|
+
|
|
121
|
+
def get_teams(self):
|
|
122
|
+
teams: defaultdict[int, list[int]] = defaultdict(list)
|
|
123
|
+
for player_id, player in self.players.items():
|
|
124
|
+
team_id: int = player["resolved_team_id"]
|
|
125
|
+
if team_id > 1:
|
|
126
|
+
teams[team_id].append(player_id)
|
|
127
|
+
elif team_id == 1:
|
|
128
|
+
teams[player_id + 8].append(player_id)
|
|
129
|
+
return set([frozenset(s) for s in teams.values()])
|
|
130
|
+
|
|
131
|
+
def get_diplomacy(self):
|
|
132
|
+
diplo_type = self._get_diplomacy_type()
|
|
133
|
+
if diplo_type == "FFA":
|
|
134
|
+
return {"type": diplo_type, "team_size": "FFA"}
|
|
135
|
+
team_sizes = [str(len(team)) for team in self.get_teams()]
|
|
136
|
+
return {"type": diplo_type, "team_size": "v".join(team_sizes)}
|
|
137
|
+
|
|
138
|
+
def get_players(self):
|
|
139
|
+
return [
|
|
140
|
+
{
|
|
141
|
+
"name": player["name"],
|
|
142
|
+
"number": player_id,
|
|
143
|
+
"civilization": player["civ_id"],
|
|
144
|
+
"color_id": player["color_id"],
|
|
145
|
+
"human": player["player_type"] == 2,
|
|
146
|
+
"winner": not player["resigned"],
|
|
147
|
+
"user_id": player["profile_id"],
|
|
148
|
+
"position": [
|
|
149
|
+
None,
|
|
150
|
+
None,
|
|
151
|
+
], # TODO: Parse players objects and find starting TC
|
|
152
|
+
"rate_snapshot": player["elo"],
|
|
153
|
+
"prefer_random": player["prefer_random"],
|
|
154
|
+
"eapm": player["eapm"],
|
|
155
|
+
}
|
|
156
|
+
for player_id, player in self.players.items()
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
def get_objects(self):
|
|
160
|
+
raise NotImplementedError()
|
|
161
|
+
|
|
162
|
+
def get_platform(self):
|
|
163
|
+
settings = self._cache["zheader"]["game_settings"]
|
|
164
|
+
guid = settings["guid"]
|
|
165
|
+
guid_str = f"{guid[0]:02x}{guid[1]:02x}{guid[2]:02x}{guid[3]:02x}-{guid[4]:02x}{guid[5]:02x}-{guid[6]:02x}{guid[7]:02x}-{guid[8]:02x}{guid[9]:02x}-{guid[10]:02x}{guid[11]:02x}{guid[12]:02x}{guid[13]:02x}{guid[14]:02x}{guid[15]:02x}"
|
|
166
|
+
return {
|
|
167
|
+
"platform_id": "de",
|
|
168
|
+
"platform_match_id": guid_str,
|
|
169
|
+
"rated": settings["ranked"],
|
|
170
|
+
"lobby_name": settings["lobby_name"],
|
|
171
|
+
"spec_delay": settings["spec_delay"],
|
|
172
|
+
"allow_specs": settings["allow_specs"],
|
|
173
|
+
"private": settings["lobby_visibility"] == 2,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
def _get_diplomacy_type(self):
|
|
177
|
+
n_teams = len(self.get_teams())
|
|
178
|
+
n_players = len(self.players)
|
|
179
|
+
if n_teams == 2 and n_players > 2:
|
|
180
|
+
return "TG"
|
|
181
|
+
if n_players == 2:
|
|
182
|
+
return "1v1"
|
|
183
|
+
if n_teams == n_players or (n_teams == 1 and n_players > 2):
|
|
184
|
+
return "FFA"
|
|
185
|
+
return "Other"
|
|
186
|
+
|
|
187
|
+
def get_settings(self):
|
|
188
|
+
settings = self._cache["zheader"]["game_settings"]
|
|
189
|
+
# TODO: Add missing names from constants in aocref
|
|
190
|
+
return {
|
|
191
|
+
"type": (settings["game_type"], "<Missing>"),
|
|
192
|
+
"difficulty": (settings["difficulty"], "<Missing>"),
|
|
193
|
+
"population_limit": settings["population_limit"],
|
|
194
|
+
"speed": (settings["speed"], "<Missing>"),
|
|
195
|
+
"cheats": settings["cheats"],
|
|
196
|
+
"team_together": settings["team_positions"],
|
|
197
|
+
"all_technologies": settings["all_techs"],
|
|
198
|
+
"lock_speed": settings["lock_speed"],
|
|
199
|
+
"lock_teams": settings["lock_teams"],
|
|
200
|
+
"map_reveal_choice": (settings["reveal_map"], "<Missing>"),
|
|
201
|
+
"diplomacy_type": self._get_diplomacy_type(),
|
|
202
|
+
"starting_resouces": (settings["starting_resources_id"], "<Missing>"),
|
|
203
|
+
"starting_age": (settings["starting_age_id"], "<Missing>"),
|
|
204
|
+
"ending_age": (settings["ending_age_id"], "<Missing>"),
|
|
205
|
+
"victory_condition": (settings["victory_type_id"], "<Missing>"),
|
|
206
|
+
"treaty_length": settings["treaty_length"],
|
|
207
|
+
"multiqueue": True, # Always true for DE
|
|
208
|
+
"hidden_civs": settings["hidden_civs"],
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
def get_file_hash(self):
|
|
212
|
+
raise NotImplementedError()
|
|
213
|
+
|
|
214
|
+
def get_hash(self):
|
|
215
|
+
raise NotImplementedError()
|
|
216
|
+
|
|
217
|
+
def get_encoding(self):
|
|
218
|
+
raise NotImplementedError()
|
|
219
|
+
|
|
220
|
+
def get_language(self):
|
|
221
|
+
raise NotImplementedError()
|
|
222
|
+
|
|
223
|
+
def get_device(self):
|
|
224
|
+
raise NotImplementedError()
|
|
225
|
+
|
|
226
|
+
def get_map(self):
|
|
227
|
+
raise NotImplementedError()
|
|
228
|
+
|
|
229
|
+
def get_dataset(self):
|
|
230
|
+
raise NotImplementedError()
|
|
231
|
+
|
|
232
|
+
def get_completed(self):
|
|
233
|
+
raise NotImplementedError()
|
|
234
|
+
|
|
235
|
+
def get_mirror(self):
|
|
236
|
+
raise NotImplementedError()
|
|
237
|
+
|
|
238
|
+
def get_played(self):
|
|
239
|
+
return datetime.fromtimestamp(
|
|
240
|
+
self._cache["zheader"]["game_settings"]["timestamp"]
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class NotImplementedError(Exception):
|
|
245
|
+
pass
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def test():
|
|
249
|
+
with open("ClickBait_vs_Numerfolt_G1b.aoe2record", "rb") as f:
|
|
250
|
+
return RecSummary(f)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aoe2rec-py
|
|
3
|
+
Version: 0.1.18
|
|
4
|
+
Classifier: Programming Language :: Rust
|
|
5
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
7
|
+
Requires-Dist: pip>=24.3.1
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Project-URL: Homepage, https://github.com/aoe2ct/aoe2rec
|
|
10
|
+
Project-URL: Repository, https://github.com/aoe2ct/aoe2rec/tree/main/crates/aoe2rec-py
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
aoe2rec_py-0.1.18.dist-info/METADATA,sha256=imCpj2UljqDL6ZbGIJ7J3-ISi8gdbFGdWDNWSv7aqbw,431
|
|
2
|
+
aoe2rec_py-0.1.18.dist-info/WHEEL,sha256=50sOPaSYc3bjJpBvsS8-LiMZUpJkJjzNIwaqSNTvTFU,109
|
|
3
|
+
aoe2rec_py.libs/libgcc_s-0366c7ba.so.1,sha256=QjFj5R7pVqB-p92h2JTCmlyfd3UKsVGW_Y_l3SqOAaU,2753157
|
|
4
|
+
aoe2rec_py/__init__.py,sha256=yiy3pW1QnH4wdN-MOezsQ2f5OJUaxLgOCxhVwhyX-Cw,58
|
|
5
|
+
aoe2rec_py/aoe2rec_py.cpython-314t-arm-linux-musleabihf.so,sha256=83LNkcGX6Ja3b0aFbCH32CSwiUpvcKi0TlYg9VPJzpA,1215185
|
|
6
|
+
aoe2rec_py/summary.py,sha256=zx2k-7gJIU1WJXDRZFlMTQX_51cw7mFif5Szc_PpZ1E,8538
|
|
7
|
+
aoe2rec_py-0.1.18.dist-info/RECORD,,
|
|
Binary file
|