peppi-py-vladfi 0.9.0__cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.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.
peppi_py/__init__.py ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env python
2
+ from dataclasses import dataclass
3
+ from ._peppi import read_peppi as _read_peppi, read_slippi as _read_slippi
4
+ from .game import End, Game, Start
5
+ from .parse import dc_from_json, frames_from_sa, RollbackMode
6
+
7
+ def read_peppi(path: str, skip_frames: bool=False) -> Game:
8
+ g = _read_peppi(path, skip_frames)
9
+ return Game(dc_from_json(Start, g.start), g.end and dc_from_json(End, g.end), g.metadata, frames_from_sa(g.frames))
10
+
11
+ def read_slippi(path: str, skip_frames: bool=False, rollback_mode: RollbackMode=RollbackMode.ALL) -> Game:
12
+ g = _read_slippi(path, skip_frames)
13
+ return Game(
14
+ dc_from_json(Start, g.start),
15
+ g.end and dc_from_json(End, g.end),
16
+ g.metadata,
17
+ frames_from_sa(g.frames, rollback_mode=rollback_mode)
18
+ )
Binary file
peppi_py/frame.py ADDED
@@ -0,0 +1,135 @@
1
+ from dataclasses import dataclass
2
+ from enum import IntEnum
3
+
4
+ from pyarrow.lib import (
5
+ Int8Array, Int16Array, Int32Array, Int64Array,
6
+ UInt8Array, UInt16Array, UInt32Array, UInt64Array,
7
+ FloatArray, DoubleArray,
8
+ ListArray,
9
+ )
10
+ from .util import _repr
11
+
12
+ @dataclass(slots=True)
13
+ class End:
14
+ __repr__ = _repr
15
+ latest_finalized_frame: Int32Array | None = None
16
+
17
+ @dataclass(slots=True)
18
+ class Position:
19
+ __repr__ = _repr
20
+ x: FloatArray
21
+ y: FloatArray
22
+
23
+ @dataclass(slots=True)
24
+ class Start:
25
+ __repr__ = _repr
26
+ random_seed: UInt32Array
27
+ scene_frame_counter: UInt32Array | None = None
28
+
29
+ @dataclass(slots=True)
30
+ class TriggersPhysical:
31
+ __repr__ = _repr
32
+ l: FloatArray
33
+ r: FloatArray
34
+
35
+ @dataclass(slots=True)
36
+ class Velocities:
37
+ __repr__ = _repr
38
+ self_x_air: FloatArray
39
+ self_y: FloatArray
40
+ knockback_x: FloatArray
41
+ knockback_y: FloatArray
42
+ self_x_ground: FloatArray
43
+
44
+ @dataclass(slots=True)
45
+ class Velocity:
46
+ __repr__ = _repr
47
+ x: FloatArray
48
+ y: FloatArray
49
+
50
+ @dataclass(slots=True)
51
+ class Item:
52
+ __repr__ = _repr
53
+ type: UInt16Array
54
+ state: UInt8Array
55
+ direction: FloatArray
56
+ velocity: Velocity
57
+ position: Position
58
+ damage: UInt16Array
59
+ timer: FloatArray
60
+ id: UInt32Array
61
+ misc: tuple[UInt8Array, UInt8Array, UInt8Array, UInt8Array] | None = None
62
+ owner: Int8Array | None = None
63
+ instance_id: UInt16Array | None = None
64
+
65
+ @dataclass(slots=True)
66
+ class Post:
67
+ __repr__ = _repr
68
+ character: UInt8Array
69
+ state: UInt16Array
70
+ position: Position
71
+ direction: FloatArray
72
+ percent: FloatArray
73
+ shield: FloatArray
74
+ last_attack_landed: UInt8Array
75
+ combo_count: UInt8Array
76
+ last_hit_by: UInt8Array
77
+ stocks: UInt8Array
78
+ state_age: FloatArray | None = None
79
+ state_flags: tuple[UInt8Array, UInt8Array, UInt8Array, UInt8Array, UInt8Array] | None = None
80
+ misc_as: FloatArray | None = None
81
+ airborne: UInt8Array | None = None
82
+ ground: UInt16Array | None = None
83
+ jumps: UInt8Array | None = None
84
+ l_cancel: UInt8Array | None = None
85
+ hurtbox_state: UInt8Array | None = None
86
+ velocities: Velocities | None = None
87
+ hitlag: FloatArray | None = None
88
+ animation_index: UInt32Array | None = None
89
+
90
+ @dataclass(slots=True)
91
+ class Pre:
92
+ __repr__ = _repr
93
+ random_seed: UInt32Array
94
+ state: UInt16Array
95
+ position: Position
96
+ direction: FloatArray
97
+ joystick: Position
98
+ cstick: Position
99
+ triggers: FloatArray
100
+ buttons: UInt32Array
101
+ buttons_physical: UInt16Array
102
+ triggers_physical: TriggersPhysical
103
+ raw_analog_x: Int8Array | None = None
104
+ percent: FloatArray | None = None
105
+ raw_analog_y: Int8Array | None = None
106
+
107
+ @dataclass(slots=True)
108
+ class Data:
109
+ __repr__ = _repr
110
+ pre: Pre
111
+ post: Post
112
+
113
+ @dataclass(slots=True)
114
+ class PortData:
115
+ __repr__ = _repr
116
+ leader: Data
117
+ follower: Data | None = None
118
+
119
+ class FodPlatform(IntEnum):
120
+ RIGHT = 0
121
+ LEFT = 1
122
+
123
+ @dataclass(slots=True)
124
+ class FodPlatformMove:
125
+ __repr__ = _repr
126
+ platform: ListArray
127
+ height: ListArray
128
+
129
+ @dataclass(slots=True)
130
+ class Frame:
131
+ __repr__ = _repr
132
+ id: object
133
+ ports: tuple[PortData]
134
+ items: Item | None = None
135
+ fod_platforms: FodPlatformMove | None = None
peppi_py/game.py ADDED
@@ -0,0 +1,132 @@
1
+ from dataclasses import dataclass
2
+ from enum import IntEnum
3
+ from .frame import Frame
4
+ from .util import _repr
5
+
6
+ class Port(IntEnum):
7
+ P1 = 0
8
+ P2 = 1
9
+ P3 = 2
10
+ P4 = 3
11
+
12
+ class PlayerType(IntEnum):
13
+ HUMAN = 0
14
+ CPU = 1
15
+ DEMO = 2
16
+
17
+ class Language(IntEnum):
18
+ JAPANESE = 0
19
+ ENGLISH = 1
20
+
21
+ class DashBack(IntEnum):
22
+ UCF = 1
23
+ ARDUINO = 2
24
+
25
+ class ShieldDrop(IntEnum):
26
+ UCF = 1
27
+ ARDUINO = 2
28
+
29
+ class EndMethod(IntEnum):
30
+ UNRESOLVED = 0
31
+ TIME = 1
32
+ GAME = 2
33
+ RESOLVED = 3
34
+ NO_CONTEST = 7
35
+
36
+ @dataclass(slots=True)
37
+ class Scene:
38
+ __repr__ = _repr
39
+ major: int
40
+ minor: int
41
+
42
+ @dataclass(slots=True)
43
+ class Match:
44
+ __repr__ = _repr
45
+ id: str
46
+ game: int
47
+ tiebreaker: int
48
+
49
+ @dataclass(slots=True)
50
+ class Slippi:
51
+ __repr__ = _repr
52
+ version: tuple[int, int, int]
53
+
54
+ @dataclass(slots=True)
55
+ class Netplay:
56
+ __repr__ = _repr
57
+ name: str
58
+ code: str
59
+ suid: str | None = None
60
+
61
+ @dataclass(slots=True)
62
+ class Team:
63
+ __repr__ = _repr
64
+ color: int
65
+ shade: int
66
+
67
+ @dataclass(slots=True)
68
+ class Ucf:
69
+ __repr__ = _repr
70
+ dash_back: DashBack | None
71
+ shield_drop: ShieldDrop | None
72
+
73
+ @dataclass(slots=True)
74
+ class Player:
75
+ __repr__ = _repr
76
+ port: Port
77
+ character: int
78
+ type: PlayerType
79
+ stocks: int
80
+ costume: int
81
+ team: Team | None
82
+ handicap: int
83
+ bitfield: int
84
+ cpu_level: int | None
85
+ offense_ratio: float
86
+ defense_ratio: float
87
+ model_scale: float
88
+ ucf: Ucf | None = None
89
+ name_tag: str | None = None
90
+ netplay: Netplay | None = None
91
+
92
+ @dataclass(slots=True)
93
+ class Start:
94
+ __repr__ = _repr
95
+ slippi: Slippi
96
+ bitfield: tuple[int, int, int, int]
97
+ is_raining_bombs: bool
98
+ is_teams: bool
99
+ item_spawn_frequency: int
100
+ self_destruct_score: int
101
+ stage: int
102
+ timer: int
103
+ item_spawn_bitfield: tuple[int, int, int, int, int]
104
+ damage_ratio: float
105
+ players: tuple[Player, ...]
106
+ random_seed: int
107
+ is_pal: bool | None = None
108
+ is_frozen_ps: bool | None = None
109
+ scene: Scene | None = None
110
+ language: Language | None = None
111
+ match: Match | None = None
112
+
113
+ @dataclass(slots=True)
114
+ class PlayerEnd:
115
+ __repr__ = _repr
116
+ port: Port
117
+ placement: int
118
+
119
+ @dataclass(slots=True)
120
+ class End:
121
+ __repr__ = _repr
122
+ method: EndMethod
123
+ lras_initiator: Port | None = None
124
+ players: tuple[PlayerEnd, ...] | None = None
125
+
126
+ @dataclass(slots=True)
127
+ class Game:
128
+ __repr__ = _repr
129
+ start: Start
130
+ end: End
131
+ metadata: dict
132
+ frames: Frame | None
peppi_py/parse.py ADDED
@@ -0,0 +1,196 @@
1
+ import types, typing
2
+ import pyarrow
3
+ import dataclasses as dc
4
+ import functools
5
+ from inflection import underscore
6
+ from enum import Enum
7
+ from .frame import Data, Frame, PortData, Item
8
+ from .frame import FodPlatform, FodPlatformMove
9
+
10
+ T = typing.TypeVar('T')
11
+
12
+ def _repr(x):
13
+ if isinstance(x, pyarrow.Array):
14
+ s = ', '.join(repr(v.as_py()) for v in x[:3])
15
+ if len(x) > 3:
16
+ s += ', ...'
17
+ return f'[{s}]'
18
+ elif isinstance(x, tuple):
19
+ s = ', '.join(_repr(v) for v in x)
20
+ return f'({s})'
21
+ elif dc.is_dataclass(x):
22
+ s = ', '.join(f'{f.name}={_repr(getattr(x, f.name))}' for f in dc.fields(type(x)))
23
+ return f'{type(x).__name__}({s})'
24
+ else:
25
+ return repr(x)
26
+
27
+ get_origin = functools.cache(typing.get_origin)
28
+ is_dataclass = functools.cache(dc.is_dataclass)
29
+ dc_fields = functools.cache(dc.fields)
30
+ get_args = functools.cache(typing.get_args)
31
+
32
+ @functools.cache
33
+ def unwrap_union(cls):
34
+ if get_origin(cls) is types.UnionType:
35
+ return get_args(cls)[0]
36
+ else:
37
+ return cls
38
+
39
+ def field_from_sa(cls: type[T], arr: pyarrow.Array | None) -> T | pyarrow.Array | None:
40
+ if arr is None:
41
+ return None
42
+ cls = unwrap_union(cls)
43
+ if is_dataclass(cls):
44
+ return dc_from_sa(cls, arr)
45
+ elif get_origin(cls) is tuple:
46
+ return tuple_from_sa(cls, arr)
47
+ else:
48
+ return arr
49
+
50
+ def arr_field(arr, dc_field: dc.Field):
51
+ try:
52
+ return arr.field(dc_field.name)
53
+ except KeyError:
54
+ if dc_field.default is dc.MISSING:
55
+ raise
56
+ else:
57
+ return dc_field.default
58
+
59
+ def dc_from_sa(cls: type[T], arr: pyarrow.StructArray) -> T:
60
+ return cls(*(field_from_sa(f.type, arr_field(arr, f)) for f in dc_fields(cls)))
61
+
62
+ def tuple_from_sa(cls: type[tuple], arr: pyarrow.Array) -> tuple:
63
+ return cls((field_from_sa(t, arr.field(str(idx))) for (idx, t) in enumerate(get_args(cls))))
64
+
65
+ @functools.cache
66
+ def unwrap_optional(cls: type) -> type | None:
67
+ if get_origin(cls) is not types.UnionType:
68
+ return cls
69
+
70
+ args = get_args(cls)
71
+ assert len(args) == 2
72
+ assert args[1] is types.NoneType
73
+ return args[0]
74
+
75
+ # Generic recursion on dataclasses
76
+ def map_dc(cls: type[T], fn: typing.Callable, *xs: T) -> T:
77
+ unwrapped_cls = unwrap_optional(cls)
78
+ if unwrapped_cls is not None:
79
+ # Optional fields might be missing; if so, don't recurse.
80
+ for x in xs:
81
+ if x is None:
82
+ return None
83
+ cls = unwrapped_cls
84
+
85
+ if is_dataclass(cls):
86
+ return cls(*(
87
+ map_dc(f.type, fn, *(getattr(x, f.name) for x in xs))
88
+ for f in dc_fields(cls)
89
+ ))
90
+ elif get_origin(cls) is tuple:
91
+ return cls(
92
+ map_dc(t, fn, *(x[idx] for x in xs))
93
+ for (idx, t) in enumerate(get_args(cls))
94
+ )
95
+ else:
96
+ return fn(*xs)
97
+
98
+ def dc_from_la(cls: type[T], la: pyarrow.ListArray) -> T:
99
+ """Converts ListArray of Structs into dataclass of ListArrays."""
100
+ dc_sa = dc_from_sa(cls, la.values)
101
+ return map_dc(cls, lambda arr: pyarrow.ListArray.from_arrays(la.offsets, arr), dc_sa)
102
+
103
+
104
+ class RollbackMode(Enum):
105
+ ALL = 'all' # All frames in the replay.
106
+ FIRST = 'first' # Only the first frame, as seen by the player
107
+ LAST = 'last' # Only the finalized frames; the "true" frame sequence.
108
+
109
+ def frames_from_sa(
110
+ arrow_frames,
111
+ rollback_mode: RollbackMode = RollbackMode.ALL,
112
+ ) -> typing.Optional[Frame]:
113
+ if arrow_frames is None:
114
+ return None
115
+
116
+ # Handle rollback mode
117
+ if rollback_mode is RollbackMode.FIRST:
118
+ index = arrow_frames.field('id').tolist()
119
+ first_indices = []
120
+ next_idx = index[0]
121
+ assert next_idx == -123
122
+ for i, idx in enumerate(index):
123
+ if idx == next_idx:
124
+ first_indices.append(i)
125
+ next_idx += 1
126
+ arrow_frames = arrow_frames.take(first_indices)
127
+ elif rollback_mode is RollbackMode.LAST:
128
+ index = arrow_frames.field('id').tolist()
129
+ next_idx = index[-1]
130
+ last_indices = []
131
+ for i, idx in reversed(list(enumerate(index))):
132
+ if idx == next_idx:
133
+ last_indices.append(i)
134
+ next_idx -= 1
135
+ assert next_idx == -124
136
+ last_indices.reverse()
137
+ arrow_frames = arrow_frames.take(last_indices)
138
+
139
+ ports = []
140
+ port_arrays = arrow_frames.field('ports')
141
+ for p in ('P1', 'P2', 'P3', 'P4'):
142
+ try: port = port_arrays.field(p)
143
+ except KeyError: continue
144
+ leader = dc_from_sa(Data, port.field('leader'))
145
+ try: follower = dc_from_sa(Data, port.field('follower'))
146
+ except KeyError: follower = None
147
+ ports.append(PortData(leader, follower))
148
+
149
+ # Extract items if available
150
+ items = None
151
+ try:
152
+ items_array = arrow_frames.field('item')
153
+ items = dc_from_la(Item, items_array)
154
+ except KeyError:
155
+ pass
156
+
157
+ # Extract FoD platforms if available
158
+ fod_platforms: FodPlatformMove | None = None
159
+ try:
160
+ fod_platform_array = arrow_frames.field('fod_platform')
161
+ fod_platforms = dc_from_la(FodPlatformMove, fod_platform_array)
162
+ except KeyError:
163
+ pass
164
+
165
+ return Frame(
166
+ id=arrow_frames.field('id'),
167
+ ports=tuple(ports),
168
+ items=items,
169
+ fod_platforms=fod_platforms
170
+ )
171
+
172
+ def field_from_json(cls, json):
173
+ if json is None:
174
+ return None
175
+ cls = unwrap_union(cls)
176
+ if dc.is_dataclass(cls):
177
+ return dc_from_json(cls, json)
178
+ elif typing.get_origin(cls) is tuple:
179
+ return tuple_from_json(cls, json)
180
+ elif issubclass(cls, Enum):
181
+ return cls[underscore(json).upper()]
182
+ else:
183
+ return json
184
+
185
+ def dc_from_json(cls, json):
186
+ return cls(*(field_from_json(f.type, json.get(f.name)) for f in dc.fields(cls)))
187
+
188
+ def tuple_from_json(cls, json):
189
+ child_cls = typing.get_args(cls)[0]
190
+
191
+ if type(json) is dict:
192
+ items = json.items()
193
+ else:
194
+ items = enumerate(json)
195
+
196
+ return tuple((field_from_json(child_cls, val) for (idx, val) in items))
peppi_py/util.py ADDED
@@ -0,0 +1,17 @@
1
+ import pyarrow
2
+ import dataclasses as dc
3
+
4
+ def _repr(self):
5
+ if isinstance(self, pyarrow.Array):
6
+ s = ', '.join(repr(v.as_py()) for v in self[:3])
7
+ if len(self) > 3:
8
+ s += ', ...'
9
+ return f'[{s}]'
10
+ elif isinstance(self, tuple):
11
+ s = ', '.join(_repr(v) for v in self)
12
+ return f'({s})'
13
+ elif dc.is_dataclass(self):
14
+ s = ', '.join(f'{f.name}={_repr(getattr(self, f.name))}' for f in dc.fields(type(self)))
15
+ return f'{type(self).__name__}({s})'
16
+ else:
17
+ return repr(self)
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: peppi-py-vladfi
3
+ Version: 0.9.0
4
+ Requires-Dist: pyarrow~=21.0
5
+ Requires-Dist: inflection~=0.5
6
+ Requires-Python: >=3.10
@@ -0,0 +1,9 @@
1
+ peppi_py/__init__.py,sha256=4to3yVoNHnsV1W6kQzCnXn1njIUJdbLL9oYZstq52G8,748
2
+ peppi_py/_peppi.abi3.so,sha256=x6VMDJA_xWqpD3vtuuDI2Obrz8fWJiOo-L5vEWjp_1o,2440648
3
+ peppi_py/frame.py,sha256=l1f0nyvWPTCo5ZDmFg5gYiNTum8nE8J5fI9xMnRto4k,2942
4
+ peppi_py/game.py,sha256=3oWv15Ik96mJ0glTEa0z2PxRzjvJtcWhWu8hJshjFmk,2222
5
+ peppi_py/parse.py,sha256=veTt3er79VeVmT4qedX_zM_Mq7p_CC1OMGhiG_E1k2w,5332
6
+ peppi_py/util.py,sha256=z6Q_NUCOwlpmpvQggkbQv1RNXX52Q7Nis0apXKb4_XU,475
7
+ peppi_py_vladfi-0.9.0.dist-info/METADATA,sha256=-PtlNUXkZC4uPC1h8bsBAK1BazmD6JwdPbxJE5vYSmQ,143
8
+ peppi_py_vladfi-0.9.0.dist-info/WHEEL,sha256=GOcqNI272zbCryxmLtJmciY1wvncsOuTwTM3MFIoyvQ,145
9
+ peppi_py_vladfi-0.9.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.11.5)
3
+ Root-Is-Purelib: false
4
+ Tag: cp310-abi3-manylinux_2_17_x86_64
5
+ Tag: cp310-abi3-manylinux2014_x86_64