mirror of
https://github.com/luksamuk/engine-psx.git
synced 2025-04-28 21:38:02 +03:00
197 lines
5 KiB
Python
197 lines
5 KiB
Python
from dataclasses import dataclass, field
|
|
import typing
|
|
from ctypes import c_ubyte, c_byte, c_short, c_ushort, c_int
|
|
from enum import Enum
|
|
|
|
c_short = c_short.__ctype_be__
|
|
c_ushort = c_ushort.__ctype_be__
|
|
c_int = c_int.__ctype_be__
|
|
|
|
|
|
class DummyObjectId(Enum):
|
|
RING_3H = -1
|
|
RING_3V = -2
|
|
|
|
@staticmethod
|
|
def get(name: str) -> int:
|
|
switch = {
|
|
"ring_3h": DummyObjectId.RING_3H,
|
|
"ring_3v": DummyObjectId.RING_3V,
|
|
}
|
|
result = switch.get(name.lower())
|
|
assert result is not None, f"Unknown dummy object {name}"
|
|
return result
|
|
|
|
|
|
# OBJECT TABLE DEFINITION (.OTN) LAYOUT
|
|
# - is_level_specific (u8)
|
|
# - num_classes (u16)
|
|
# - Classes of Objects:
|
|
# - id (u8) {types are sequential but id is used for auto-suficient parsing}
|
|
# - has_fragment (u8)
|
|
# - num_animations (u16)
|
|
# - Animations:
|
|
# - num_frames (u16)
|
|
# - loopback_frame (s8)
|
|
# - Frames:
|
|
# - u0 (u8)
|
|
# - v0 (u8)
|
|
# - width (u8)
|
|
# - height (u8)
|
|
# - flipmask (u8)
|
|
# - Fragment: (single, exists depending on Type)
|
|
# - offsetx (s16)
|
|
# - offsety (s16)
|
|
# - num_animations (u16)
|
|
# - Fragment Animations: (see Animations above)
|
|
|
|
|
|
@dataclass
|
|
class Frame:
|
|
u0: int = 0
|
|
v0: int = 0
|
|
width: int = 0
|
|
height: int = 0
|
|
flipx: bool = False
|
|
flipy: bool = False
|
|
|
|
def write_to(self, f):
|
|
flipmask = ((1 << 0) if self.flipx else 0) | ((1 << 1) if self.flipy else 0)
|
|
f.write(c_ubyte(self.u0))
|
|
f.write(c_ubyte(self.v0))
|
|
f.write(c_ubyte(self.width))
|
|
f.write(c_ubyte(self.height))
|
|
f.write(c_ubyte(flipmask))
|
|
|
|
|
|
@dataclass
|
|
class ObjectAnimation:
|
|
frames: [Frame] = field(default_factory=list)
|
|
loopback: int = 0
|
|
|
|
def write_to(self, f):
|
|
f.write(c_ushort(len(self.frames)))
|
|
f.write(c_byte(self.loopback))
|
|
for frame in self.frames:
|
|
frame.write_to(f)
|
|
|
|
|
|
@dataclass
|
|
class ObjectFragment:
|
|
offsetx: int = 0
|
|
offsety: int = 0
|
|
animations: [ObjectAnimation] = field(default_factory=list)
|
|
|
|
def write_to(self, f):
|
|
f.write(c_short(self.offsetx))
|
|
f.write(c_short(self.offsety))
|
|
f.write(c_ushort(len(self.animations)))
|
|
for animation in self.animations:
|
|
animation.write_to(f)
|
|
|
|
|
|
MaybeObjectFragment = ObjectFragment | None
|
|
|
|
|
|
@dataclass
|
|
class ObjectData:
|
|
id: int = -1
|
|
gid: int = -1
|
|
name: str = ""
|
|
animations: [ObjectAnimation] = field(default_factory=list)
|
|
fragment: MaybeObjectFragment = None
|
|
|
|
def write_to(self, f):
|
|
f.write(c_byte(self.id))
|
|
f.write(c_ubyte(int(self.fragment is not None)))
|
|
f.write(c_ushort(len(self.animations)))
|
|
for animation in self.animations:
|
|
animation.write_to(f)
|
|
if self.fragment:
|
|
self.fragment.write_to(f)
|
|
|
|
|
|
# Root for the .OTN data type
|
|
@dataclass
|
|
class ObjectMap:
|
|
is_level_specific: bool = False
|
|
name: str = ""
|
|
out: str = ""
|
|
firstgid: int = 0
|
|
num_objs: int = 0
|
|
object_types: typing.Dict[int, ObjectData] = field(default_factory=dict)
|
|
|
|
# Mapping of dummy objects (gid -> actual id)
|
|
obj_mapping: typing.Dict[int, int] = field(default_factory=dict)
|
|
|
|
def get_otype_from_gid(self, gid: int) -> int:
|
|
gid = gid & ~(0b1111 << 29)
|
|
return self.obj_mapping[gid]
|
|
|
|
def write(self):
|
|
with open(self.out, "wb") as f:
|
|
self.write_to(f)
|
|
|
|
def write_to(self, f):
|
|
f.write(c_ubyte(int(self.is_level_specific)))
|
|
f.write(c_ushort(self.num_objs))
|
|
for key, t in self.object_types.items():
|
|
print(f"Writing object class id {t.id} ({t.name})...")
|
|
t.write_to(f)
|
|
|
|
# I don't have a better name for this. Sorry
|
|
def get_is_specific_if_from_this_map(self, dirty_gid: int) -> bool | None:
|
|
# Clean GID
|
|
gid = dirty_gid & ~(0b1111 << 29)
|
|
if gid >= self.firstgid:
|
|
return self.is_level_specific
|
|
return None
|
|
|
|
|
|
# OBJECT MAP PLACEMENT (.OMP) LAYOUT
|
|
# - is_level_specific (u8)
|
|
# - Type / ID (s8)
|
|
# - Flip Mask (u8)
|
|
# - has_properties (u8)
|
|
# - vx (s32)
|
|
# - vy (s32)
|
|
# - Properties (exists depending on Type)
|
|
# * Properties layout for monitor (id = 1):
|
|
# - kind (u8)
|
|
|
|
|
|
@dataclass
|
|
class MonitorProperties:
|
|
kind: str = ""
|
|
|
|
|
|
ObjectProperties = MonitorProperties | None
|
|
|
|
|
|
# Root for the .OMP datatype
|
|
@dataclass
|
|
class ObjectPlacement:
|
|
is_level_specific: bool = False
|
|
otype: int = 0
|
|
x: int = 0
|
|
y: int = 0
|
|
flipx: bool = False
|
|
flipy: bool = False
|
|
rotcw: bool = False # clockwise rotation
|
|
rotct: bool = False # counterclockwise rotation
|
|
properties: ObjectProperties = None
|
|
|
|
def write_to(self, f):
|
|
flipmask = (
|
|
((1 << 0) if self.flipx else 0)
|
|
| ((1 << 1) if self.flipy else 0)
|
|
| ((1 << 2) if self.rotcw else 0)
|
|
| ((1 << 3) if self.rotct else 0)
|
|
)
|
|
|
|
f.write(c_ubyte(int(self.is_level_specific)))
|
|
f.write(c_byte(self.otype))
|
|
f.write(c_int(self.x))
|
|
f.write(c_int(self.y))
|
|
f.write(c_ubyte(flipmask))
|
|
# TODO: Properties
|