madlysimple 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.
@@ -0,0 +1,23 @@
1
+ from .extended_stream import LevelStream
2
+
3
+ from .classes import RWClass
4
+ from .actorparams import ActorParamBlock, CProtoActorParams, SharedActorParams
5
+ from .cprotoactor import CProtoActor
6
+ from .cameradata import CameraData, CameraDataOffset
7
+ from .levelhub import LevelHub
8
+
9
+ from .prefabs import Prefab
10
+
11
+ __all__ = [
12
+ "RWClass",
13
+ "ActorParamBlock",
14
+ "CProtoActorParams",
15
+ "SharedActorParams",
16
+ "CProtoActor",
17
+ "CameraData",
18
+ "CameraDataOffset",
19
+ "LevelHub",
20
+ "Prefab",
21
+ # --
22
+ "LevelStream", # LevelStream.load(fp)
23
+ ]
@@ -0,0 +1,367 @@
1
+ """The actor parameter record, as write-through attribute views.
2
+
3
+ `CProtoActor` attribute 1 is one 272-byte blob that the engine copies
4
+ straight into the live actor object. It is really two records glued
5
+ together:
6
+
7
+ +0x00..0x3f CProtoActor-only prefix -> CProtoActorParams
8
+ +0x40..0x10f shared 208-byte record -> SharedActorParams
9
+
10
+ The second half is byte-for-byte the same record `CTFBModel` attribute 1
11
+ carries on its own (there at +0x00), which is why it is its own class --
12
+ point `SharedActorParams` at a model with ``base=0`` and the same field
13
+ names work.
14
+
15
+ Both classes read and write through to the entity, exactly like the
16
+ CameraData properties do: nothing is cached, so an edit lands on the very
17
+ entity the stream holds.
18
+
19
+ actor.params.activationRange = 300.0
20
+ actor.params.noShadow = True
21
+ actor.sharedParams.update(health=100, weight=20.0)
22
+
23
+ Field names and offsets come from the engine's own ``::actor`` script
24
+ descriptor table -- see the `madagascar-actor-param-block` memory note.
25
+ """
26
+
27
+ import struct
28
+ from typing import Any, ClassVar, overload, override
29
+
30
+ from madformats.streamfuncs import RW_sf_CreateEntity
31
+
32
+
33
+ class _Field:
34
+ """One scalar at a fixed offset inside a parameter block.
35
+
36
+ A descriptor rather than a property so the whole layout reads as a
37
+ table -- offset, name and doc on one line each.
38
+ """
39
+
40
+ _fmt: ClassVar[str] = "<I"
41
+
42
+ def __init__(self, offset: int, doc: str = "", readOnly: bool = False) -> None:
43
+ self.offset = offset
44
+ self.readOnly = readOnly
45
+ self.name = ""
46
+ self.__doc__ = doc
47
+
48
+ def __set_name__(self, owner: type, name: str) -> None:
49
+ self.name = name
50
+
51
+ def _coerce(self, value: Any) -> Any:
52
+ return int(value)
53
+
54
+ @overload
55
+ def __get__(self, instance: None, owner: type | None = None) -> "_Field": ...
56
+
57
+ @overload
58
+ def __get__(
59
+ self, instance: "ActorParamBlock", owner: type | None = None
60
+ ) -> Any: ...
61
+
62
+ def __get__(self, instance: "ActorParamBlock | None", owner: type | None = None):
63
+ if instance is None:
64
+ return self
65
+ return instance.readValue(self.offset, self._fmt)
66
+
67
+ def __set__(self, instance: "ActorParamBlock", value: Any) -> None:
68
+ if self.readOnly:
69
+ raise AttributeError(
70
+ f"{type(instance).__name__}.{self.name} is read-only "
71
+ "(the engine never reads it back, changing it only risks "
72
+ "breaking the record)"
73
+ )
74
+ instance.writeValue(self.offset, self._fmt, self._coerce(value))
75
+
76
+
77
+ class _FloatField(_Field):
78
+ _fmt: ClassVar[str] = "<f"
79
+
80
+ @override
81
+ def _coerce(self, value: Any) -> float:
82
+ return float(value)
83
+
84
+
85
+ class _UIntField(_Field):
86
+ _fmt: ClassVar[str] = "<I"
87
+
88
+
89
+ class _IntField(_Field):
90
+ _fmt: ClassVar[str] = "<i"
91
+
92
+
93
+ class _FlagField(_Field):
94
+ """One bit of the flags bitmask, exposed as a bool."""
95
+
96
+ _fmt: ClassVar[str] = "<I"
97
+
98
+ def __init__(self, offset: int, bit: int, doc: str = "") -> None:
99
+ super().__init__(offset, doc)
100
+ self.bit = bit
101
+
102
+ @overload
103
+ def __get__(self, instance: None, owner: type | None = None) -> "_FlagField": ...
104
+
105
+ @overload
106
+ def __get__(
107
+ self, instance: "ActorParamBlock", owner: type | None = None
108
+ ) -> bool: ...
109
+
110
+ @override
111
+ def __get__(self, instance: "ActorParamBlock | None", owner: type | None = None):
112
+ if instance is None:
113
+ return self
114
+ return bool((instance.readValue(self.offset, self._fmt) >> self.bit) & 1)
115
+
116
+ @override
117
+ def __set__(self, instance: "ActorParamBlock", value: Any) -> None:
118
+ flags: int = instance.readValue(self.offset, self._fmt)
119
+ if value:
120
+ flags |= 1 << self.bit
121
+ else:
122
+ flags &= ~(1 << self.bit) & 0xFFFFFFFF
123
+ instance.writeValue(self.offset, self._fmt, flags)
124
+
125
+
126
+ class ActorParamBlock:
127
+ """Shared machinery: a fixed slice of one entity attribute.
128
+
129
+ Subclasses declare their fields as `_Field` descriptors with offsets
130
+ relative to `base`, so the same field table can be pointed at the same
131
+ record wherever it is embedded.
132
+ """
133
+
134
+ __slots__ = ("_base", "_command", "_className", "_owner")
135
+
136
+ SIZE: ClassVar[int] = 0
137
+
138
+ def __init__(
139
+ self,
140
+ owner: RW_sf_CreateEntity,
141
+ className: str = "CProtoActor",
142
+ command: int = 1,
143
+ base: int = 0,
144
+ ) -> None:
145
+ self._owner = owner
146
+ self._className = className
147
+ self._command = command
148
+ self._base = base
149
+
150
+ @property
151
+ def base(self) -> int:
152
+ """Byte offset of this record inside the attribute payload."""
153
+ return self._base
154
+
155
+ def asBytes(self) -> bytes:
156
+ """Detached copy of just this record's bytes."""
157
+ blob = self._owner.getAttribute(self._className, self._command).data
158
+ return blob[self._base : self._base + self.SIZE]
159
+
160
+ def readValue(self, offset: int, fmt: str) -> Any:
161
+ """One struct-format value at `offset` inside this record."""
162
+ blob = self._owner.getAttribute(self._className, self._command).data
163
+ at = self._base + offset
164
+ if at + struct.calcsize(fmt) > len(blob):
165
+ raise ValueError(
166
+ f"{self._className} attribute {self._command} is only "
167
+ f"{len(blob)} bytes, too short to hold offset 0x{at:x}"
168
+ )
169
+ return struct.unpack_from(fmt, blob, at)[0]
170
+
171
+ def writeValue(self, offset: int, fmt: str, value: Any) -> None:
172
+ """Overwrite one struct-format value at `offset`, leaving the rest alone."""
173
+ attr = self._owner.getAttribute(self._className, self._command)
174
+ at = self._base + offset
175
+ if at + struct.calcsize(fmt) > len(attr.data):
176
+ raise ValueError(
177
+ f"{self._className} attribute {self._command} is only "
178
+ f"{len(attr.data)} bytes, too short to hold offset 0x{at:x}"
179
+ )
180
+ data = bytearray(attr.data)
181
+ struct.pack_into(fmt, data, at, value)
182
+ attr.data = bytes(data)
183
+
184
+ @classmethod
185
+ def fields(cls) -> dict[str, _Field]:
186
+ """Every declared field, in layout order."""
187
+ found: dict[str, _Field] = {}
188
+ for klass in reversed(cls.__mro__):
189
+ for name, value in vars(klass).items():
190
+ if isinstance(value, _Field):
191
+ found[name] = value
192
+ return found
193
+
194
+ def asDict(self) -> dict[str, Any]:
195
+ return {name: getattr(self, name) for name in self.fields()}
196
+
197
+ def update(self, **values: Any) -> None:
198
+ """Set several fields at once, rejecting names that don't exist."""
199
+ known = self.fields()
200
+ for name, value in values.items():
201
+ if name not in known:
202
+ raise AttributeError(f"{type(self).__name__} has no field {name!r}")
203
+ setattr(self, name, value)
204
+
205
+ @override
206
+ def __repr__(self) -> str:
207
+ body = ",\n".join(f" {n}={v!r}" for n, v in self.asDict().items())
208
+ return f"{type(self).__name__}(\n{body}\n)"
209
+
210
+
211
+ class CProtoActorParams(ActorParamBlock):
212
+ """The CProtoActor-only prefix -- blob +0x00..0x3f, actor +0x1ac.
213
+
214
+ Everything here is about *being an actor placed in a level*: when the
215
+ actor wakes up, and how the editor/radar draws it. The stats live next
216
+ door in `SharedActorParams`.
217
+ """
218
+
219
+ SIZE: ClassVar[int] = 0x40
220
+
221
+ def __init__(
222
+ self,
223
+ owner: RW_sf_CreateEntity,
224
+ className: str = "CProtoActor",
225
+ command: int = 1,
226
+ base: int = 0x00,
227
+ ) -> None:
228
+ super().__init__(owner, className, command, base)
229
+
230
+ version = _UIntField(0x00, "Record format version -- 2 everywhere.", readOnly=True)
231
+ flags = _UIntField(0x04, "Raw flags bitmask; see the bool fields below.")
232
+ activationRange = _FloatField(
233
+ 0x08, "Distance at which the actor wakes up and starts ticking. 0 = always on."
234
+ )
235
+ deactivationRange = _FloatField(
236
+ 0x0C, "Distance at which it sleeps again. 9999/10000 = never deactivate."
237
+ )
238
+ coneColor = _UIntField(0x10, "Palette index (0/3/4/8) for the debug vision cone.")
239
+ unknown0x14 = _UIntField(0x14, "1 on 5727/5730 actors. No reader found.")
240
+ blipColor = _UIntField(0x18, "Palette index (0/2) for the radar dot.")
241
+ activationRangeFadePercent = _UIntField(
242
+ 0x1C, "Fade-in fraction of the activation range. 0 in all shipped data."
243
+ )
244
+
245
+ solidCollision = _FlagField(0x04, 0, "Actor blocks other actors.")
246
+ wallClimber = _FlagField(0x04, 1, "Actor can climb walls.")
247
+ wallCollision = _FlagField(0x04, 2, "Actor collides with walls.")
248
+ noShadow = _FlagField(0x04, 3, "Shadow renderer skips this actor.")
249
+ visibleOnRadar = _FlagField(0x04, 4, "Drawn on the radar using blipColor.")
250
+ cutSceneIgnore = _FlagField(
251
+ 0x04, 5, "Excluded from normal cut-scene processing outside game mode 3."
252
+ )
253
+ ignoresGroundColor = _FlagField(
254
+ 0x04, 6, "Skips ground-tint lighting. Never set in shipped data."
255
+ )
256
+ tiltsWithGround = _FlagField(0x04, 7, "Pitches/rolls to match the ground normal.")
257
+ meshCollider = _FlagField(
258
+ 0x04, 8, "Collide against the mesh instead of the box (needs actor+0x2c8)."
259
+ )
260
+ trianglesCollide = _FlagField(
261
+ 0x04, 9, "Per-triangle collision (needs actor+0x2c8)."
262
+ )
263
+ unknownBit10 = _FlagField(0x04, 10, "Set on 16 actors; no accessor, no reader.")
264
+ rwClumpFlag0x1000 = _FlagField(
265
+ 0x04, 11, "Model-instance ctor applies RW clump flag 0x1000 when set."
266
+ )
267
+
268
+
269
+ class SharedActorParams(ActorParamBlock):
270
+ """The 208-byte record shared with CTFBModel -- the actor's stats.
271
+
272
+ On a `CProtoActor` this sits at blob +0x40 (the default); on a
273
+ `CTFBModel` it is the whole of attribute 1, so build it as
274
+ ``SharedActorParams(model, "CTFBModel", 1, 0)``.
275
+ """
276
+
277
+ SIZE: ClassVar[int] = 0xD0
278
+
279
+ def __init__(
280
+ self,
281
+ owner: RW_sf_CreateEntity,
282
+ className: str = "CProtoActor",
283
+ command: int = 1,
284
+ base: int = 0x40,
285
+ ) -> None:
286
+ super().__init__(owner, className, command, base)
287
+
288
+ version = _UIntField(0x00, "Record format version -- 2 everywhere.", readOnly=True)
289
+
290
+ # --- jumping / gravity -------------------------------------------------
291
+ jumpCoastFrames = _UIntField(
292
+ 0x04, "Frames the jump floats after takeoff before gravity takes over."
293
+ )
294
+ jumpDelay = _UIntField(0x08, "Windup frames between the jump input and takeoff.")
295
+ initialJumpVelocity = _FloatField(0x2C, "Launch speed applied at takeoff.")
296
+ upTopSpeed = _FloatField(0x30, "Terminal upward speed.")
297
+ upAcceleration = _FloatField(0x34, "Upward acceleration while rising.")
298
+ downAcceleration = _FloatField(0x38, "Gravity -- downward acceleration.")
299
+ downTopSpeed = _FloatField(0x3C, "Terminal falling speed.")
300
+ bounceRestitution = _FloatField(0x40, "Elasticity on landing. 0 on nearly all.")
301
+
302
+ # --- core stats --------------------------------------------------------
303
+ health = _UIntField(0x0C, "Hit points. 30 on most actors. Script get/set.")
304
+ moveType = _UIntField(0x10, "Locomotion mode enum (0/1/2/4).")
305
+ weight = _FloatField(0x44, "Mass for physics / knockback / push resolution.")
306
+ id = _IntField(0x98, "Designer-assigned numeric id; -1 = unset. Script get/set.")
307
+
308
+ # --- ground movement ---------------------------------------------------
309
+ groundTopSpeed = _FloatField(
310
+ 0x14, "Max ground speed, and the upper edge of the fast-move anim band."
311
+ )
312
+ groundTurnRate = _FloatField(
313
+ 0x18, "Max ground turn rate, deg/s; also drives the lean anims."
314
+ )
315
+ groundAcceleration = _FloatField(0x48, "How fast it reaches groundTopSpeed.")
316
+ groundDeceleration = _FloatField(0x4C, "How fast it stops on the ground.")
317
+
318
+ # --- air movement ------------------------------------------------------
319
+ airTopSpeed = _FloatField(0x50, "Horizontal speed cap while airborne.")
320
+ airAcceleration = _FloatField(0x54, "Horizontal air control, accelerating.")
321
+ airDeceleration = _FloatField(0x58, "Horizontal air control, slowing.")
322
+ airTurnRate = _FloatField(0x74, "Turn rate while airborne, deg/s.")
323
+
324
+ # --- vision cone -------------------------------------------------------
325
+ coneAngle = _FloatField(0x1C, "Half-angle of the vision / attention cone.")
326
+ coneLength = _FloatField(0x20, "Range of that cone.")
327
+ coneSweepOffset = _FloatField(0x24, "Angular offset while the cone sweeps.")
328
+ unknown0x28 = _FloatField(0x28, "10 or 0, sits with the cone fields. No reader.")
329
+
330
+ # --- collision box (feet; the engine scales by 0.5 * 12) ---------------
331
+ hExtent = _FloatField(0x5C, "Collision-box height, in feet.")
332
+ wExtent = _FloatField(0x60, "Collision-box width, in feet.")
333
+ lExtent = _FloatField(0x64, "Collision-box length, in feet.")
334
+ xOffset = _FloatField(0x68, "Box centre offset.")
335
+ yOffset = _FloatField(0x6C, "Box centre offset.")
336
+ zOffset = _FloatField(
337
+ 0x70, "Vertical box offset -- usually hExtent/2, so the base sits on the origin."
338
+ )
339
+
340
+ # --- locomotion animation rate bands -----------------------------------
341
+ # FUN_004235b0 picks a band from ground speed, then lerps min->max
342
+ # across it and divides by 100 to get the clip playback rate.
343
+ slowestToSlowSpeed = _FloatField(
344
+ 0x78, "Speed at which slowest-move gives way to slow-move."
345
+ )
346
+ slowToFastSpeed = _FloatField(
347
+ 0x7C, "Speed at which slow-move gives way to fast-move."
348
+ )
349
+ slowestMoveRateMin = _IntField(0x80, "Slowest-move playback rate x100, band bottom.")
350
+ slowestMoveRateMax = _IntField(0x84, "Slowest-move playback rate x100, band top.")
351
+ slowMoveRateMin = _IntField(0x88, "Slow-move playback rate x100, band bottom.")
352
+ slowMoveRateMax = _IntField(0x8C, "Slow-move playback rate x100, band top.")
353
+ fastMoveRateMin = _IntField(0x90, "Fast-move playback rate x100, band bottom.")
354
+ fastMoveRateMax = _IntField(0x94, "Fast-move playback rate x100, band top.")
355
+
356
+ # --- dead editor defaults ---------------------------------------------
357
+ unused50 = _FloatField(0xB0, "50 on every actor and model. No reader.", readOnly=True)
358
+ unused100 = _FloatField(0xB4, "100 on every actor and model. No reader.", readOnly=True)
359
+ unused150 = _FloatField(0xB8, "150 on every actor and model. No reader.", readOnly=True)
360
+
361
+ def checkAlignment(self) -> bool:
362
+ """True if the dead 50/100/150 triple is where it should be.
363
+
364
+ Nothing reads those three floats, which is exactly what makes them a
365
+ cheap sanity check that `base` points at a real shared record.
366
+ """
367
+ return (self.unused50, self.unused100, self.unused150) == (50.0, 100.0, 150.0)
@@ -0,0 +1,213 @@
1
+ import struct
2
+ from collections.abc import Sequence
3
+ from typing import cast, overload, override
4
+
5
+ from madformats.lib.parser import Parser
6
+ from madformats.streamfuncs import RW_sf_CreateEntity
7
+ from dataclasses import dataclass
8
+
9
+
10
+ def _packVector3(value: Sequence[float]) -> bytes:
11
+ values = tuple(value)
12
+ if len(values) != 3:
13
+ raise ValueError(f"Expected 3 offsets, got {len(values)}")
14
+ return struct.pack("<3f", *values)
15
+
16
+
17
+ class CameraDataOffset(Sequence[float]):
18
+ """Write-through view of a 3-float CameraData offset attribute.
19
+
20
+ Reads like the tuple the getters used to return -- unpacking, indexing,
21
+ iteration and comparison against a tuple all still work -- but assigning to
22
+ an element rewrites the attribute on the entity:
23
+
24
+ cam.lookFromOffset[1] = 12.0
25
+ cam.lookAtOffset.heading = 90.0
26
+ """
27
+
28
+ __slots__ = ("_command", "_owner")
29
+
30
+ def __init__(self, owner: "CameraData", command: int) -> None:
31
+ self._owner = owner
32
+ self._command = command
33
+
34
+ def _values(self) -> tuple[float, float, float]:
35
+ cmdp = Parser(self._owner.getAttribute("CameraData", self._command).data)
36
+ return (cmdp.readFloat(), cmdp.readFloat(), cmdp.readFloat())
37
+
38
+ def asTuple(self) -> tuple[float, float, float]:
39
+ """Detached copy, for when you want a value that will not follow edits."""
40
+ return self._values()
41
+
42
+ @override
43
+ def __len__(self) -> int:
44
+ return 3
45
+
46
+ @overload
47
+ def __getitem__(self, index: int) -> float: ...
48
+
49
+ @overload
50
+ def __getitem__(self, index: slice) -> tuple[float, ...]: ...
51
+
52
+ @override
53
+ def __getitem__(self, index: int | slice) -> float | tuple[float, ...]:
54
+ return self._values()[index]
55
+
56
+ def __setitem__(self, index: int, value: float) -> None:
57
+ values = list(self._values())
58
+ values[index] = float(value) # IndexError on out of range, as with a list
59
+ self._owner.setAttribute("CameraData", self._command, _packVector3(values))
60
+
61
+ @property
62
+ def horizontal(self) -> float:
63
+ return self._values()[0]
64
+
65
+ @horizontal.setter
66
+ def horizontal(self, value: float) -> None:
67
+ self[0] = value
68
+
69
+ @property
70
+ def vertical(self) -> float:
71
+ return self._values()[1]
72
+
73
+ @vertical.setter
74
+ def vertical(self, value: float) -> None:
75
+ self[1] = value
76
+
77
+ @property
78
+ def heading(self) -> float:
79
+ return self._values()[2]
80
+
81
+ @heading.setter
82
+ def heading(self, value: float) -> None:
83
+ self[2] = value
84
+
85
+ @override
86
+ def __eq__(self, other: object) -> bool:
87
+ if isinstance(other, CameraDataOffset):
88
+ return self._values() == other._values()
89
+ if isinstance(other, (tuple, list)):
90
+ values = cast(Sequence[object], other)
91
+ return len(values) == 3 and self._values() == tuple(values)
92
+ return NotImplemented
93
+
94
+ @override
95
+ def __repr__(self) -> str:
96
+ return repr(self._values())
97
+
98
+
99
+ class CameraDataOffsetAttribute:
100
+ """One 3-float offset command, exposed as an attribute of CameraData.
101
+
102
+ A descriptor rather than a property because reads and writes have different
103
+ types: reading hands back a write-through CameraDataOffset, writing accepts
104
+ any 3-element sequence.
105
+ """
106
+
107
+ def __init__(self, command: int, doc: str) -> None:
108
+ self._command = command
109
+ self.__doc__ = doc
110
+
111
+ @overload
112
+ def __get__(
113
+ self, instance: None, owner: type | None = None
114
+ ) -> "CameraDataOffsetAttribute": ...
115
+
116
+ @overload
117
+ def __get__(
118
+ self, instance: "CameraData", owner: type | None = None
119
+ ) -> CameraDataOffset: ...
120
+
121
+ def __get__(
122
+ self, instance: "CameraData | None", owner: type | None = None
123
+ ) -> "CameraDataOffset | CameraDataOffsetAttribute":
124
+ if instance is None:
125
+ return self
126
+ return CameraDataOffset(instance, self._command)
127
+
128
+ def __set__(self, instance: "CameraData", value: Sequence[float]) -> None:
129
+ instance.setAttribute("CameraData", self._command, _packVector3(value))
130
+
131
+
132
+ @dataclass
133
+ class CameraData(RW_sf_CreateEntity):
134
+ @classmethod
135
+ def from_entity(cls, entity: RW_sf_CreateEntity) -> "CameraData":
136
+ # Re-tag in place instead of copying, so writes through the properties
137
+ # land on the very entity the stream holds.
138
+ entity.__class__ = cls
139
+ camd = cast(CameraData, entity)
140
+ return camd
141
+
142
+ def _setString(self, command: int, value: str) -> None:
143
+ self.getAttribute("CameraData", command).setString(value)
144
+
145
+ def _setFloat(self, command: int, value: float) -> None:
146
+ self.setAttribute("CameraData", command, struct.pack("<f", value))
147
+
148
+ def _setBool(self, command: int, value: bool) -> None:
149
+ self.setAttribute("CameraData", command, struct.pack("<i", int(value)))
150
+
151
+ @property
152
+ def cameraDataName(self) -> str:
153
+ return self.getAttribute("CameraData", 0).asString()
154
+
155
+ @cameraDataName.setter
156
+ def cameraDataName(self, value: str) -> None:
157
+ self._setString(0, value)
158
+
159
+ @property
160
+ def lookFromTarget(self) -> str:
161
+ return self.getAttribute("CameraData", 1).asString()
162
+
163
+ @lookFromTarget.setter
164
+ def lookFromTarget(self, value: str) -> None:
165
+ self._setString(1, value)
166
+
167
+ lookFromOffset = CameraDataOffsetAttribute(2, "(horizontal,vertical,heading) offsets")
168
+
169
+ @property
170
+ def lookFromHeadingRelative(self) -> bool:
171
+ cmdp = Parser(self.getAttribute("CameraData", 3).data)
172
+ return cmdp.readBool()
173
+
174
+ @lookFromHeadingRelative.setter
175
+ def lookFromHeadingRelative(self, value: bool) -> None:
176
+ self._setBool(3, value)
177
+
178
+ @property
179
+ def lookAtTarget(self) -> str:
180
+ return self.getAttribute("CameraData", 4).asString()
181
+
182
+ @lookAtTarget.setter
183
+ def lookAtTarget(self, value: str) -> None:
184
+ self._setString(4, value)
185
+
186
+ lookAtOffset = CameraDataOffsetAttribute(5, "(horizontal,vertical,heading) offsets")
187
+
188
+ @property
189
+ def lookAtHeadingRelative(self) -> bool:
190
+ cmdp = Parser(self.getAttribute("CameraData", 6).data)
191
+ return cmdp.readBool()
192
+
193
+ @lookAtHeadingRelative.setter
194
+ def lookAtHeadingRelative(self, value: bool) -> None:
195
+ self._setBool(6, value)
196
+
197
+ @property
198
+ def fov(self) -> float:
199
+ cmdp = Parser(self.getAttribute("CameraData", 7).data)
200
+ return cmdp.readFloat()
201
+
202
+ @fov.setter
203
+ def fov(self, value: float) -> None:
204
+ self._setFloat(7, value)
205
+
206
+ @property
207
+ def laziness(self) -> bool:
208
+ cmdp = Parser(self.getAttribute("CameraData", 8).data)
209
+ return cmdp.readBool()
210
+
211
+ @laziness.setter
212
+ def laziness(self, value: bool) -> None:
213
+ self._setBool(8, value)
madlysimple/classes.py ADDED
@@ -0,0 +1,24 @@
1
+ from enum import Enum
2
+
3
+
4
+ class RWClass(str, Enum):
5
+ CSystemCommands = "CSystemCommands"
6
+ CTFBCommand = "CTFBCommand"
7
+ CProtoActor = "CProtoActor"
8
+ CAttributeHandler = "CAttributeHandler"
9
+ LevelHub = "LevelHub"
10
+ CTFBWorld = "CTFBWorld"
11
+ CTFBModel = "CTFBModel"
12
+ SpriteManager = "SpriteManager"
13
+ SpriteObject = "SpriteObject"
14
+ AssetHub = "AssetHub"
15
+ CTFBSound = "CTFBSound"
16
+ CameraData = "CameraData"
17
+ CFXPartSpray = "CFXPartSpray"
18
+ CFXColorLight = "CFXColorLight"
19
+ GameCamera = "GameCamera"
20
+ CDirectorsCamera = "CDirectorsCamera"
21
+ CFXMotionBlur = "CFXMotionBlur"
22
+ RadarRender = "RadarRender"
23
+ TFBShadowCamera = "TFBShadowCamera"
24
+ ShadowRender = "ShadowRender"