pyddtas 0.1.0__py3-none-any.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.
- pyddtas/core.py +223 -0
- pyddtas-0.1.0.dist-info/METADATA +7 -0
- pyddtas-0.1.0.dist-info/RECORD +5 -0
- pyddtas-0.1.0.dist-info/WHEEL +5 -0
- pyddtas-0.1.0.dist-info/top_level.txt +1 -0
pyddtas/core.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
from ctypes import sizeof
|
|
2
|
+
from typing import Type, Literal
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import zlib
|
|
5
|
+
|
|
6
|
+
try: import orjson as json
|
|
7
|
+
except: import json
|
|
8
|
+
|
|
9
|
+
# Dataclass format:
|
|
10
|
+
# someParam: type # exact json key in TAS file
|
|
11
|
+
#
|
|
12
|
+
# if exact key not provided, its literally like class parameter
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class TASInput:
|
|
16
|
+
direction: int # m_Direction
|
|
17
|
+
fire: int # m_Fire
|
|
18
|
+
hook: int # m_Hook
|
|
19
|
+
jump: int # m_Jump
|
|
20
|
+
nextWeapon: int # m_NextWeapon
|
|
21
|
+
playerFlags: int # m_PlayerFlags
|
|
22
|
+
prevWeapon: int # m_PrevWeapon
|
|
23
|
+
targetX: int # m_TargetX
|
|
24
|
+
targetY: int # m_TargetY
|
|
25
|
+
wantedWeapon: int # m_WantedWeapon
|
|
26
|
+
|
|
27
|
+
def todict(self):
|
|
28
|
+
return {
|
|
29
|
+
"m_Direction": self.direction,
|
|
30
|
+
"m_Fire": self.fire,
|
|
31
|
+
"m_Hook": self.hook,
|
|
32
|
+
"m_Jump": self.jump,
|
|
33
|
+
"m_NextWeapon": self.nextWeapon,
|
|
34
|
+
"m_PlayerFlags": self.playerFlags,
|
|
35
|
+
"m_PrevWeapon": self.prevWeapon,
|
|
36
|
+
"m_TargetX": self.targetX,
|
|
37
|
+
"m_TargetY": self.targetY,
|
|
38
|
+
"m_WantedWeapon": self.wantedWeapon
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
def tojson(self, indent: int | str | None = None):
|
|
42
|
+
return json.dumps(self.todict(), indent=indent)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def fromdict(cls: Type[TASInput], obj: dict, errors: Literal['show', 'ignore'] = "show"):
|
|
46
|
+
if errors == "ignore": getVal = lambda key, default: obj.get(key, default)
|
|
47
|
+
else: getVal = lambda key, default: obj[key]
|
|
48
|
+
|
|
49
|
+
return cls(
|
|
50
|
+
direction=getVal("m_Direction", 0),
|
|
51
|
+
fire=getVal("m_Fire", 0),
|
|
52
|
+
hook=getVal("m_Hook", 0),
|
|
53
|
+
jump=getVal("m_Jump", 0),
|
|
54
|
+
nextWeapon=getVal("m_NextWeapon", 0),
|
|
55
|
+
playerFlags=getVal("m_PlayerFlags", 0),
|
|
56
|
+
prevWeapon=getVal("m_PrevWeapon", 0),
|
|
57
|
+
targetX=getVal("m_TargetX", 0),
|
|
58
|
+
targetY=getVal("m_TargetY", 0),
|
|
59
|
+
wantedWeapon=getVal("m_WantedWeapon", 0),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def fromjson(cls: Type[TASInput], obj: str | bytes, errors: Literal['show', 'ignore'] = "show"):
|
|
64
|
+
return cls.fromdict(
|
|
65
|
+
json.loads(obj),
|
|
66
|
+
errors
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class TASPos:
|
|
71
|
+
x: int
|
|
72
|
+
y: int
|
|
73
|
+
|
|
74
|
+
def todict(self):
|
|
75
|
+
return {
|
|
76
|
+
"x": self.x,
|
|
77
|
+
"y": self.y
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
def tojson(self, indent: int | str | None = None):
|
|
81
|
+
return json.dumps(self.todict(), indent=indent)
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def fromdict(cls: Type[TASPos], obj: dict, errors: Literal['show', 'ignore'] = "show"):
|
|
85
|
+
if errors == "ignore": getVal = lambda key, default: obj.get(key, default)
|
|
86
|
+
else: getVal = lambda key, default: obj[key]
|
|
87
|
+
|
|
88
|
+
return cls(
|
|
89
|
+
x=getVal("x", 0),
|
|
90
|
+
y=getVal("y", 0)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def fromjson(cls: Type[TASPos], obj: str | bytes, errors: Literal['show', 'ignore'] = "show"):
|
|
95
|
+
return cls.fromdict(
|
|
96
|
+
json.loads(obj),
|
|
97
|
+
errors
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class TASTick:
|
|
102
|
+
input: TASInput
|
|
103
|
+
pos: TASPos
|
|
104
|
+
|
|
105
|
+
def todict(self):
|
|
106
|
+
return {
|
|
107
|
+
"input": self.input.todict(),
|
|
108
|
+
"pos": self.pos.todict()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
def tojson(self, indent: int | str | None = None):
|
|
112
|
+
return json.dumps(self.todict(), indent=indent)
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def fromdict(cls: Type[TASTick], obj: dict, errors: Literal['show', 'ignore'] = "show"):
|
|
116
|
+
if errors == "ignore": getVal = lambda key, default: obj.get(key, default)
|
|
117
|
+
else: getVal = lambda key, default: obj[key]
|
|
118
|
+
|
|
119
|
+
return cls(
|
|
120
|
+
input=TASInput.fromdict(getVal("input", {})),
|
|
121
|
+
pos=TASPos.fromdict(getVal("pos", {}))
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def fromjson(cls: Type[TASTick], obj: str | bytes, errors: Literal['show', 'ignore'] = "show"):
|
|
126
|
+
return cls.fromdict(
|
|
127
|
+
json.loads(obj),
|
|
128
|
+
errors
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
@dataclass
|
|
132
|
+
class TASInfo:
|
|
133
|
+
author: str
|
|
134
|
+
date_modified: str
|
|
135
|
+
description: str
|
|
136
|
+
map: str
|
|
137
|
+
map_name: str | None = None # has no key in TAS file, PyTAS value
|
|
138
|
+
map_hash: str | None = None # has no key in TAS file, PyTAS value
|
|
139
|
+
|
|
140
|
+
def todict(self):
|
|
141
|
+
return {
|
|
142
|
+
"author": self.author,
|
|
143
|
+
"date_modified": self.date_modified,
|
|
144
|
+
"description": self.description,
|
|
145
|
+
"map": self.map
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
def tojson(self, indent: int | str | None = None):
|
|
149
|
+
return json.dumps(self.todict(), indent=indent)
|
|
150
|
+
|
|
151
|
+
@classmethod
|
|
152
|
+
def fromdict(cls: Type[TASInfo], obj: dict, errors: Literal['show', 'ignore'] = "show"):
|
|
153
|
+
if errors == "ignore": getVal = lambda key, default: obj.get(key, default)
|
|
154
|
+
else: getVal = lambda key, default: obj[key]
|
|
155
|
+
|
|
156
|
+
return cls(
|
|
157
|
+
author=getVal("author", ""),
|
|
158
|
+
date_modified=getVal("date_modified", ""),
|
|
159
|
+
description=getVal("description", ""),
|
|
160
|
+
map=getVal("map", ""),
|
|
161
|
+
map_name=getVal("map", "").split("_")[0] if len(getVal("map", "").split("_")) == 2 else None,
|
|
162
|
+
map_hash=getVal("map", "").split("_")[1] if len(getVal("map", "").split("_")) == 2 else None
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def fromjson(cls: Type[TASInfo], obj: str | bytes, errors: Literal['show', 'ignore'] = "show"):
|
|
167
|
+
return cls.fromdict(
|
|
168
|
+
json.loads(obj),
|
|
169
|
+
errors
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
@dataclass
|
|
173
|
+
class TASData:
|
|
174
|
+
info: TASInfo
|
|
175
|
+
replay_dummy: list[TASTick]
|
|
176
|
+
replay_local: list[TASTick]
|
|
177
|
+
|
|
178
|
+
def todict(self):
|
|
179
|
+
return {
|
|
180
|
+
"info": self.info.todict(),
|
|
181
|
+
"replay_dummy": [x.todict() for x in self.replay_dummy],
|
|
182
|
+
"replay_local": [x.todict() for x in self.replay_local]
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
def tojson(self, indent: int | str | None = None):
|
|
186
|
+
return json.dumps(self.todict(), indent=indent)
|
|
187
|
+
|
|
188
|
+
@classmethod
|
|
189
|
+
def fromdict(cls: Type[TASData], obj: dict, errors: Literal['show', 'ignore'] = "show"):
|
|
190
|
+
if errors == "ignore": getVal = lambda key, default: obj.get(key, default)
|
|
191
|
+
else: getVal = lambda key, default: obj[key]
|
|
192
|
+
|
|
193
|
+
return cls(
|
|
194
|
+
info=TASInfo.fromdict(getVal("info", {})),
|
|
195
|
+
replay_dummy=[TASTick.fromdict(x) for x in getVal("replay_dummy", [])],
|
|
196
|
+
replay_local=[TASTick.fromdict(x) for x in getVal("replay_local", [])]
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
@classmethod
|
|
200
|
+
def fromjson(cls: Type[TASData], obj: str | bytes, errors: Literal['show', 'ignore'] = "show"):
|
|
201
|
+
return cls.fromdict(
|
|
202
|
+
json.loads(obj),
|
|
203
|
+
errors
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
@dataclass
|
|
207
|
+
class TASFile:
|
|
208
|
+
tas_data: TASData
|
|
209
|
+
|
|
210
|
+
def tobytes(self):
|
|
211
|
+
data = self.tas_data.tojson().encode("utf-8")
|
|
212
|
+
uncsize = len(data).to_bytes(4, "little")
|
|
213
|
+
compressed = zlib.compress(data)
|
|
214
|
+
return uncsize + compressed
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def frombytes(cls: Type[TASFile], data: bytes, ignore_size: bool = False):
|
|
218
|
+
uncsize = int.from_bytes(data[:4], "little")
|
|
219
|
+
data = zlib.decompress(data[4:])
|
|
220
|
+
if len(data) != uncsize and not ignore_size: raise ValueError("Uncompressed size from header is not equal to real uncompressed size. (damaged file)")
|
|
221
|
+
return cls(
|
|
222
|
+
TASData.fromdict(json.loads(data))
|
|
223
|
+
)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
pyddtas/core.py,sha256=PzwJRycTIBoVai_nSrLA7Z4V5UdEUOUbjYJE-gGyUac,7439
|
|
2
|
+
pyddtas-0.1.0.dist-info/METADATA,sha256=YFznqhxGaz15wOJVfnkDHbMg90pbMtuw7P8n5geaXLA,195
|
|
3
|
+
pyddtas-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
4
|
+
pyddtas-0.1.0.dist-info/top_level.txt,sha256=G6PfuX0HldtGmzyi4DN109Ndm44WqUuuuP5Xt2QWHl0,8
|
|
5
|
+
pyddtas-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyddtas
|