pyddtas 0.1.0__tar.gz
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-0.1.0/PKG-INFO +7 -0
- pyddtas-0.1.0/README.md +17 -0
- pyddtas-0.1.0/pyproject.toml +21 -0
- pyddtas-0.1.0/setup.cfg +4 -0
- pyddtas-0.1.0/src/pyddtas/core.py +223 -0
- pyddtas-0.1.0/src/pyddtas.egg-info/PKG-INFO +7 -0
- pyddtas-0.1.0/src/pyddtas.egg-info/SOURCES.txt +9 -0
- pyddtas-0.1.0/src/pyddtas.egg-info/dependency_links.txt +1 -0
- pyddtas-0.1.0/src/pyddtas.egg-info/requires.txt +3 -0
- pyddtas-0.1.0/src/pyddtas.egg-info/top_level.txt +1 -0
- pyddtas-0.1.0/test/test.py +4 -0
pyddtas-0.1.0/PKG-INFO
ADDED
pyddtas-0.1.0/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# About
|
|
2
|
+
PyTAS is a pythonic version of parser/writer of .tas files from KRX DDNet client.
|
|
3
|
+
|
|
4
|
+
# Usage
|
|
5
|
+
PyTAS including dataclasses for interacting with .tas format, import all of them using `from pyddtas.core import *`.
|
|
6
|
+
|
|
7
|
+
## PyTAS dataclasses
|
|
8
|
+
> TASFile: .tas file itself
|
|
9
|
+
>> TASData: JSON data from file (info, movements)
|
|
10
|
+
>>> TASInfo: some info about .tas file, like author, description or map info (name, hash)
|
|
11
|
+
>>> TASTick: every tick state such as inputs or position
|
|
12
|
+
>>>> TASInput: client\`s inputs
|
|
13
|
+
>>>> TASPos: client\`s position
|
|
14
|
+
|
|
15
|
+
# Building
|
|
16
|
+
1. Install requirements using `pip install requirements.txt`
|
|
17
|
+
2. Build project with `python -m build`
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyddtas"
|
|
3
|
+
authors = [{name = "nesquikcode", email = "nesquik@nishine.ru"}]
|
|
4
|
+
version = "0.1.0"
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
dependencies = []
|
|
7
|
+
|
|
8
|
+
[project.optional-dependencies]
|
|
9
|
+
fast = [
|
|
10
|
+
"orjson>=3.10"
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[build-system]
|
|
14
|
+
requires = ["setuptools>=80", "wheel"]
|
|
15
|
+
build-backend = "setuptools.build_meta"
|
|
16
|
+
|
|
17
|
+
[tool.setuptools]
|
|
18
|
+
package-dir = {"" = "src"}
|
|
19
|
+
|
|
20
|
+
[tool.setuptools.packages.find]
|
|
21
|
+
where = ["src"]
|
pyddtas-0.1.0/setup.cfg
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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyddtas
|