mirror of
https://github.com/luksamuk/engine-psx.git
synced 2025-04-28 13:28:02 +03:00
Initial refactor of COOKOBJ tool
This commit is contained in:
parent
fa8285268a
commit
7b0ec1a6c9
7 changed files with 277 additions and 0 deletions
BIN
assets/levels/COMMON/objects_common.OTD
Normal file
BIN
assets/levels/COMMON/objects_common.OTD
Normal file
Binary file not shown.
1
tools/cookobj/.gitignore
vendored
Normal file
1
tools/cookobj/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
__pycache__/
|
18
tools/cookobj/cookobj.py
Executable file
18
tools/cookobj/cookobj.py
Executable file
|
@ -0,0 +1,18 @@
|
|||
#!/bin/env python
|
||||
|
||||
import sys
|
||||
from parsers import parse_map
|
||||
from os.path import realpath
|
||||
from pprint import pp
|
||||
|
||||
|
||||
def main():
|
||||
map_src = realpath(sys.argv[1])
|
||||
obj_defs, obj_places = parse_map(map_src)
|
||||
pp(obj_places)
|
||||
for key, d in obj_defs.items():
|
||||
d.write()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
131
tools/cookobj/datatypes.py
Normal file
131
tools/cookobj/datatypes.py
Normal file
|
@ -0,0 +1,131 @@
|
|||
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__
|
||||
|
||||
|
||||
# OBJECT TABLE DEFINITION (.OTN) LAYOUT
|
||||
# - num_classes (u16)
|
||||
# - Classes of Objects:
|
||||
# (Note: we don't write the ID since the ID is sequential)
|
||||
# - num_animations (u16)
|
||||
# - Animations:
|
||||
# - num_frames (u16)
|
||||
# - loopback_frame (u8)
|
||||
# - 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_ubyte(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_ubyte(len(self.animations)))
|
||||
for animation in self.animations:
|
||||
animation.write_to(f)
|
||||
|
||||
|
||||
MaybeObjectFragment = ObjectFragment | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ObjectData:
|
||||
name: str = ""
|
||||
animations: [ObjectAnimation] = field(default_factory=list)
|
||||
fragment: MaybeObjectFragment = None
|
||||
|
||||
def write_to(self, f):
|
||||
f.write(c_ubyte(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:
|
||||
out: str = ""
|
||||
firstgid: int = 0
|
||||
num_objs: int = 0
|
||||
object_types: typing.Dict[int, ObjectData] = field(default_factory=dict)
|
||||
|
||||
def write(self):
|
||||
with open(self.out, "wb") as f:
|
||||
self.write_to(f)
|
||||
|
||||
def write_to(self, f):
|
||||
f.write(c_ushort(self.num_objs))
|
||||
for key, t in self.object_types.items():
|
||||
t.write_to(f)
|
||||
|
||||
|
||||
# OBJECT MAP PLACEMENT (.OMP) LAYOUT
|
||||
# - Type / ID (u8)
|
||||
# - Flip Mask (u8)
|
||||
# - vx (s32)
|
||||
# - vy (s32)
|
||||
# - Properties (exists depending on Type)
|
||||
# * Properties layout for monitor (id = 1):
|
||||
# - kind (u8)
|
||||
|
||||
|
||||
# Root for the .OMP datatype
|
||||
@dataclass
|
||||
class ObjectPlacement:
|
||||
otype: int = 0
|
||||
x: int = 0
|
||||
y: int = 0
|
||||
flipx: bool = False
|
||||
flipy: bool = False
|
||||
rotcw: bool = False
|
||||
rotct: bool = False
|
122
tools/cookobj/parsers.py
Normal file
122
tools/cookobj/parsers.py
Normal file
|
@ -0,0 +1,122 @@
|
|||
from bs4 import BeautifulSoup
|
||||
import toml
|
||||
from os.path import realpath, dirname, splitext
|
||||
|
||||
from datatypes import *
|
||||
|
||||
|
||||
def _parse_animation(data) -> ObjectAnimation:
|
||||
anim = ObjectAnimation()
|
||||
anim.loopback = data["loopback"]
|
||||
for fr in data["frames"]:
|
||||
frame = Frame()
|
||||
fr_get = lambda i: (fr[i] == 1) if i < len(fr) else False
|
||||
# u0, v0, width, height, flipx, flipy
|
||||
frame.u0 = fr[0]
|
||||
frame.v0 = fr[1]
|
||||
frame.width = fr[2]
|
||||
frame.height = fr[3]
|
||||
frame.flipx = fr_get(4)
|
||||
frame.flipy = fr_get(5)
|
||||
anim.frames.append(frame)
|
||||
return anim
|
||||
|
||||
|
||||
def parse_tileset(firstgid: int, set_src: str) -> (ObjectMap, str):
|
||||
toml_src = splitext(set_src)[0] + ".toml"
|
||||
ts = None
|
||||
extra_data = None
|
||||
o = ObjectMap()
|
||||
|
||||
# Load tileset and extra .toml file data
|
||||
with open(set_src) as f:
|
||||
ts = BeautifulSoup(f, "xml")
|
||||
extra_data = toml.load(toml_src)
|
||||
|
||||
ts = ts.find("tileset")
|
||||
|
||||
tiles = ts.find_all("tile")
|
||||
o.num_objs = int(ts["tilecount"])
|
||||
od = ObjectData()
|
||||
# "classes" becomes an entry on dict o.object_types.
|
||||
# Emplace "od" there under a proper gid
|
||||
for i in range(int(ts["tilecount"])):
|
||||
collision = None
|
||||
tile = next((x for x in tiles if x["id"] == f"{i}"), None)
|
||||
if tile:
|
||||
# Get tile collision
|
||||
# collisions = tile.find("objectgroup")
|
||||
# if collisions:
|
||||
# collisions = collisions.find_all("object", [])
|
||||
# if collisions[0].get("width"):
|
||||
# collision = {}
|
||||
# collision["type"] = "rect"
|
||||
# collision["x"] = int(collisions[0].get("x"))
|
||||
# collision["y"] = int(collisions[0].get("y"))
|
||||
# collision["width"] = int(collisions[0].get("width"))
|
||||
# collision["height"] = int(collisions[0].get("height"))
|
||||
# else:
|
||||
# collision = {}
|
||||
# collision["type"] = "polygon"
|
||||
# poly = collisions[0].find("polygon")
|
||||
# points = poly.get("points").split()
|
||||
# points = [
|
||||
# [int(float(p.split(",")[0])), int(float(p.split(",")[1]))]
|
||||
# for p in points
|
||||
# ]
|
||||
# collision["points"] = points
|
||||
# Get other tile data
|
||||
od.name = (tile["type"] if tile else "none").lower()
|
||||
idx = i + firstgid
|
||||
|
||||
# Append TOML data
|
||||
extra = extra_data.get(od.name)
|
||||
if extra:
|
||||
animations = extra["animations"]
|
||||
animations.sort(key=lambda x: x.get("id"))
|
||||
for data in animations:
|
||||
od.animations.append(_parse_animation(data))
|
||||
frag = extra.get("fragment")
|
||||
if frag:
|
||||
od.fragment = ObjectFragment()
|
||||
offset = frag["offset"]
|
||||
od.fragment.offsetx = offset[0]
|
||||
od.fragment.offsety = offset[1]
|
||||
frag_animations = frag["animations"]
|
||||
frag_animations.sort(key=lambda x: x.get("id"))
|
||||
for data in animations:
|
||||
od.fragment.animations.append(_parse_animation(data))
|
||||
|
||||
# if collision:
|
||||
# # TODO: append collision
|
||||
# pass
|
||||
|
||||
o.object_types[idx] = od
|
||||
else:
|
||||
o.num_objs -= 1
|
||||
|
||||
o.firstgid = firstgid
|
||||
o.out = splitext(set_src)[0] + ".OTD"
|
||||
return (o, ts["name"])
|
||||
|
||||
|
||||
def parse_map(map_src: str) -> (typing.Dict[str, ObjectMap], [ObjectPlacement]):
|
||||
map = None
|
||||
with open(map_src) as f:
|
||||
map = BeautifulSoup(f, "xml")
|
||||
objmaps = {}
|
||||
placements = []
|
||||
|
||||
# Get all tilesets that are not 128x128.
|
||||
# Depends on tileset name.
|
||||
# TODO: Perharps use a non-zero firstgid as parameter?
|
||||
tilesets = [t for t in map.find_all("tileset") if t["source"].find("128") == -1]
|
||||
|
||||
for tileset in tilesets:
|
||||
tileset_src = realpath(dirname(map_src) + "/" + tileset["source"])
|
||||
loaded_set, ts_name = parse_tileset(int(tileset["firstgid"]), tileset_src)
|
||||
objmaps[ts_name] = loaded_set
|
||||
|
||||
# Retrieve objects placement
|
||||
|
||||
return (objmaps, placements)
|
|
@ -1,3 +1,5 @@
|
|||
// -*- mode: c; -*-
|
||||
|
||||
#include <type/color.pat>
|
||||
|
||||
enum PolygonType: u8 {
|
||||
|
|
3
tools/layouts/otd.hexpat
Normal file
3
tools/layouts/otd.hexpat
Normal file
|
@ -0,0 +1,3 @@
|
|||
// -*- mode: c; -*-
|
||||
|
||||
// TODO: Write here the definition for .OTD files!!!
|
Loading…
Add table
Add a link
Reference in a new issue