mirror of
https://github.com/luksamuk/engine-psx.git
synced 2025-04-28 21:38:02 +03:00
Progress on object placement layout
This commit is contained in:
parent
93c4b9fb6c
commit
e28ede29a7
5 changed files with 138 additions and 14 deletions
|
@ -9,7 +9,7 @@ from pprint import pp
|
||||||
def main():
|
def main():
|
||||||
map_src = realpath(sys.argv[1])
|
map_src = realpath(sys.argv[1])
|
||||||
obj_defs, obj_places = parse_map(map_src)
|
obj_defs, obj_places = parse_map(map_src)
|
||||||
pp(obj_places)
|
# pp(obj_places)
|
||||||
for key, d in obj_defs.items():
|
for key, d in obj_defs.items():
|
||||||
d.write()
|
d.write()
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,28 @@
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
import typing
|
import typing
|
||||||
from ctypes import c_ubyte, c_byte, c_short, c_ushort, c_int
|
from ctypes import c_ubyte, c_byte, c_short, c_ushort, c_int
|
||||||
|
from enum import Enum
|
||||||
# from enum import Enum
|
|
||||||
|
|
||||||
c_short = c_short.__ctype_be__
|
c_short = c_short.__ctype_be__
|
||||||
c_ushort = c_ushort.__ctype_be__
|
c_ushort = c_ushort.__ctype_be__
|
||||||
c_int = c_int.__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
|
# OBJECT TABLE DEFINITION (.OTN) LAYOUT
|
||||||
# - is_level_specific (u8)
|
# - is_level_specific (u8)
|
||||||
# - num_classes (u16)
|
# - num_classes (u16)
|
||||||
|
@ -82,12 +96,13 @@ MaybeObjectFragment = ObjectFragment | None
|
||||||
@dataclass
|
@dataclass
|
||||||
class ObjectData:
|
class ObjectData:
|
||||||
id: int = -1
|
id: int = -1
|
||||||
|
gid: int = -1
|
||||||
name: str = ""
|
name: str = ""
|
||||||
animations: [ObjectAnimation] = field(default_factory=list)
|
animations: [ObjectAnimation] = field(default_factory=list)
|
||||||
fragment: MaybeObjectFragment = None
|
fragment: MaybeObjectFragment = None
|
||||||
|
|
||||||
def write_to(self, f):
|
def write_to(self, f):
|
||||||
f.write(c_ubyte(self.id))
|
f.write(c_byte(self.id))
|
||||||
f.write(c_ubyte(int(self.fragment is not None)))
|
f.write(c_ubyte(int(self.fragment is not None)))
|
||||||
f.write(c_ushort(len(self.animations)))
|
f.write(c_ushort(len(self.animations)))
|
||||||
for animation in self.animations:
|
for animation in self.animations:
|
||||||
|
@ -100,11 +115,19 @@ class ObjectData:
|
||||||
@dataclass
|
@dataclass
|
||||||
class ObjectMap:
|
class ObjectMap:
|
||||||
is_level_specific: bool = False
|
is_level_specific: bool = False
|
||||||
|
name: str = ""
|
||||||
out: str = ""
|
out: str = ""
|
||||||
firstgid: int = 0
|
firstgid: int = 0
|
||||||
num_objs: int = 0
|
num_objs: int = 0
|
||||||
object_types: typing.Dict[int, ObjectData] = field(default_factory=dict)
|
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):
|
def write(self):
|
||||||
with open(self.out, "wb") as f:
|
with open(self.out, "wb") as f:
|
||||||
self.write_to(f)
|
self.write_to(f)
|
||||||
|
@ -116,10 +139,20 @@ class ObjectMap:
|
||||||
print(f"Writing object class id {t.id} ({t.name})...")
|
print(f"Writing object class id {t.id} ({t.name})...")
|
||||||
t.write_to(f)
|
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
|
# OBJECT MAP PLACEMENT (.OMP) LAYOUT
|
||||||
# - Type / ID (u8)
|
# - is_level_specific (u8)
|
||||||
|
# - Type / ID (s8)
|
||||||
# - Flip Mask (u8)
|
# - Flip Mask (u8)
|
||||||
|
# - has_properties (u8)
|
||||||
# - vx (s32)
|
# - vx (s32)
|
||||||
# - vy (s32)
|
# - vy (s32)
|
||||||
# - Properties (exists depending on Type)
|
# - Properties (exists depending on Type)
|
||||||
|
@ -127,13 +160,38 @@ class ObjectMap:
|
||||||
# - kind (u8)
|
# - kind (u8)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MonitorProperties:
|
||||||
|
kind: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
ObjectProperties = MonitorProperties | None
|
||||||
|
|
||||||
|
|
||||||
# Root for the .OMP datatype
|
# Root for the .OMP datatype
|
||||||
@dataclass
|
@dataclass
|
||||||
class ObjectPlacement:
|
class ObjectPlacement:
|
||||||
|
is_level_specific: bool = False
|
||||||
otype: int = 0
|
otype: int = 0
|
||||||
x: int = 0
|
x: int = 0
|
||||||
y: int = 0
|
y: int = 0
|
||||||
flipx: bool = False
|
flipx: bool = False
|
||||||
flipy: bool = False
|
flipy: bool = False
|
||||||
rotcw: bool = False
|
rotcw: bool = False # clockwise rotation
|
||||||
rotct: bool = False
|
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
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
import toml
|
import toml
|
||||||
from os.path import realpath, dirname, splitext
|
from os.path import realpath, dirname, splitext
|
||||||
|
from pprint import pp
|
||||||
|
|
||||||
from datatypes import *
|
from datatypes import *
|
||||||
|
|
||||||
|
@ -47,14 +48,21 @@ def parse_tileset(firstgid: int, set_src: str) -> (ObjectMap, str):
|
||||||
od = ObjectData()
|
od = ObjectData()
|
||||||
od.id = obj_id
|
od.id = obj_id
|
||||||
od.name = (str(tile["type"]) if tile else "none").lower()
|
od.name = (str(tile["type"]) if tile else "none").lower()
|
||||||
|
gid = int(tile["id"]) + firstgid
|
||||||
extra = extra_data.get(od.name)
|
extra = extra_data.get(od.name)
|
||||||
|
|
||||||
# If this is a dummy object (e.g. rows of rings), we don't
|
# If this is a dummy object (e.g. rows of rings), we don't
|
||||||
# need to register it
|
# need to register it
|
||||||
|
# TODO: These dummy objects will be needed somewhere else!
|
||||||
|
# We actually need to register them, yes! But somewhere
|
||||||
|
# else.
|
||||||
if extra and extra.get("dummy", False):
|
if extra and extra.get("dummy", False):
|
||||||
o.num_objs -= 1
|
o.num_objs -= 1
|
||||||
|
o.obj_mapping[gid] = DummyObjectId.get(od.name).value
|
||||||
continue
|
continue
|
||||||
# If not a dummy, increase sequential id for next object
|
|
||||||
|
# If this isn't a dummy object, increase object ID.
|
||||||
|
# ID's are sequential only for non-dummy objects.
|
||||||
obj_id += 1
|
obj_id += 1
|
||||||
|
|
||||||
# Get tile collision
|
# Get tile collision
|
||||||
|
@ -78,9 +86,8 @@ def parse_tileset(firstgid: int, set_src: str) -> (ObjectMap, str):
|
||||||
# for p in points
|
# for p in points
|
||||||
# ]
|
# ]
|
||||||
# collision["points"] = points
|
# collision["points"] = points
|
||||||
# Get other tile data
|
|
||||||
|
|
||||||
idx = i + firstgid
|
o.obj_mapping[gid] = gid - firstgid
|
||||||
|
|
||||||
# Append TOML data
|
# Append TOML data
|
||||||
if extra:
|
if extra:
|
||||||
|
@ -103,7 +110,7 @@ def parse_tileset(firstgid: int, set_src: str) -> (ObjectMap, str):
|
||||||
# # TODO: append collision
|
# # TODO: append collision
|
||||||
# pass
|
# pass
|
||||||
|
|
||||||
o.object_types[idx] = od
|
o.object_types[gid] = od
|
||||||
else:
|
else:
|
||||||
o.num_objs -= 1
|
o.num_objs -= 1
|
||||||
|
|
||||||
|
@ -111,9 +118,61 @@ def parse_tileset(firstgid: int, set_src: str) -> (ObjectMap, str):
|
||||||
o.out = splitext(set_src)[0] + ".OTD"
|
o.out = splitext(set_src)[0] + ".OTD"
|
||||||
o.is_level_specific = ts["name"] != "objects_common"
|
o.is_level_specific = ts["name"] != "objects_common"
|
||||||
o.num_objs = len(o.object_types)
|
o.num_objs = len(o.object_types)
|
||||||
|
o.name = ts["name"]
|
||||||
return (o, ts["name"])
|
return (o, ts["name"])
|
||||||
|
|
||||||
|
|
||||||
|
def parse_object_group(
|
||||||
|
tilesets: typing.Dict[str, ObjectMap], objgroup
|
||||||
|
) -> [ObjectPlacement]:
|
||||||
|
is_level_specific = False
|
||||||
|
current_ts = None
|
||||||
|
placements = []
|
||||||
|
|
||||||
|
objects = objgroup.find_all("object", [])
|
||||||
|
if not objects:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Get first object's gid.
|
||||||
|
first_obj = sorted(objects, key=lambda x: int(x.get("gid")))[0]
|
||||||
|
first_obj_gid = int(first_obj.get("gid"))
|
||||||
|
print(f"First object gid: {first_obj_gid}")
|
||||||
|
|
||||||
|
# Identify if this is from common objects tileset or from level-specific tileset.
|
||||||
|
for key, ts in tilesets.items():
|
||||||
|
print(f"First gid: {ts.firstgid}")
|
||||||
|
result = ts.get_is_specific_if_from_this_map(first_obj_gid)
|
||||||
|
if result is not None:
|
||||||
|
current_ts = ts
|
||||||
|
is_level_specific = result
|
||||||
|
|
||||||
|
# If the tileset was not found... DON'T GO BEYOND THIS POINT!
|
||||||
|
assert current_ts is not None, "Object was not found in any tilesets!"
|
||||||
|
|
||||||
|
# Iterate over placements
|
||||||
|
for obj in objects:
|
||||||
|
p = ObjectPlacement()
|
||||||
|
p.is_level_specific = is_level_specific
|
||||||
|
p.otype = current_ts.get_otype_from_gid(int(obj.get("gid")))
|
||||||
|
p.x = int(float(obj.get("x")))
|
||||||
|
p.y = int(float(obj.get("y")))
|
||||||
|
p.flipx = False # TODO
|
||||||
|
p.flipy = False # TODO
|
||||||
|
p.rotcw = int(float(obj.get("rotation", 0))) == 90
|
||||||
|
p.rotccw = int(float(obj.get("rotation", 0))) == -90
|
||||||
|
props = obj.find("properties")
|
||||||
|
if props:
|
||||||
|
# TODO: Check type, build type-specific property, etc
|
||||||
|
pass
|
||||||
|
print(
|
||||||
|
f"Object type {current_ts.object_types[p.otype + current_ts.firstgid].name if p.otype >= 0 else 'DUMMY'}"
|
||||||
|
)
|
||||||
|
pp(p)
|
||||||
|
placements.append(p)
|
||||||
|
|
||||||
|
return placements
|
||||||
|
|
||||||
|
|
||||||
def parse_map(map_src: str) -> (typing.Dict[str, ObjectMap], [ObjectPlacement]):
|
def parse_map(map_src: str) -> (typing.Dict[str, ObjectMap], [ObjectPlacement]):
|
||||||
map = None
|
map = None
|
||||||
with open(map_src) as f:
|
with open(map_src) as f:
|
||||||
|
@ -132,5 +191,9 @@ def parse_map(map_src: str) -> (typing.Dict[str, ObjectMap], [ObjectPlacement]):
|
||||||
objmaps[ts_name] = loaded_set
|
objmaps[ts_name] = loaded_set
|
||||||
|
|
||||||
# Retrieve objects placement
|
# Retrieve objects placement
|
||||||
|
layergroup = map.find(name="group", attrs={"name": "OBJECTS"})
|
||||||
|
objgroups = layergroup.find_all("objectgroup")
|
||||||
|
for objgroup in objgroups:
|
||||||
|
placements += parse_object_group(objmaps, objgroup)
|
||||||
|
|
||||||
return (objmaps, placements)
|
return (objmaps, placements)
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// -*- mode: c; -*-
|
// -*- mode: c; -*-
|
||||||
|
|
||||||
enum CommonId: u8 {
|
enum CommonId: s8 {
|
||||||
|
RING_3V = -2,
|
||||||
|
RING_3H = -1,
|
||||||
|
|
||||||
RING = 0,
|
RING = 0,
|
||||||
MONITOR = 1,
|
MONITOR = 1,
|
||||||
SPIKES = 2,
|
SPIKES = 2,
|
||||||
|
|
|
@ -131,7 +131,7 @@ Frames are an array with the following arguments in order:
|
||||||
- [optional, default False] Flip_Y
|
- [optional, default False] Flip_Y
|
||||||
|
|
||||||
|
|
||||||
#+begin_src toml :tangle ../assets/levels/COMMON/objects_common.toml
|
#+begin_src toml
|
||||||
[ring]
|
[ring]
|
||||||
|
|
||||||
[[ring.animations]]
|
[[ring.animations]]
|
||||||
|
@ -421,7 +421,7 @@ dummy = true
|
||||||
|
|
||||||
** Parse test
|
** Parse test
|
||||||
|
|
||||||
#+begin_src python :results output
|
#+begin_src python :results output :eval never
|
||||||
import toml
|
import toml
|
||||||
from pprint import pp
|
from pprint import pp
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue