Add placements file

This commit is contained in:
Lucas S. Vieira 2024-09-30 23:27:15 -03:00
parent e28ede29a7
commit 7a857049b3
5 changed files with 122 additions and 33 deletions

BIN
assets/levels/R2/Z1.OMP Normal file

Binary file not shown.

View file

@ -9,9 +9,9 @@ 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)
for key, d in obj_defs.items(): for key, d in obj_defs.items():
d.write() d.write()
obj_places.write()
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -13,7 +13,7 @@ class DummyObjectId(Enum):
RING_3V = -2 RING_3V = -2
@staticmethod @staticmethod
def get(name: str) -> int: def get(name):
switch = { switch = {
"ring_3h": DummyObjectId.RING_3H, "ring_3h": DummyObjectId.RING_3H,
"ring_3v": DummyObjectId.RING_3V, "ring_3v": DummyObjectId.RING_3V,
@ -23,6 +23,37 @@ class DummyObjectId(Enum):
return result return result
class ObjectId(Enum):
RING = 0
MONITOR = 1
SPIKES = 2
CHECKPOINT = 3
SPRING_YELLOW = 4
SPRING_RED = 5
SPRING_YELLOW_DIAGONAL = 6
SPRING_RED_DIAGONAL = 7
GOAL_SIGN = 8
SWITCH = 9
@staticmethod
def get(name):
switch = {
"ring": ObjectId.RING,
"monitor": ObjectId.MONITOR,
"spikes": ObjectId.SPIKES,
"checkpoint": ObjectId.CHECKPOINT,
"spring_yellow": ObjectId.SPRING_YELLOW,
"spring_red": ObjectId.SPRING_RED,
"spring_yellow_diagonal": ObjectId.SPRING_YELLOW_DIAGONAL,
"spring_red_diagonal": ObjectId.SPRING_RED_DIAGONAL,
"goal_sign": ObjectId.GOAL_SIGN,
"switch": ObjectId.SWITCH,
}
result = switch.get(name.lower())
assert result is not None, f"Unknown common 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)
@ -148,27 +179,45 @@ class ObjectMap:
return None return None
# OBJECT MAP PLACEMENT (.OMP) LAYOUT # =======================================
# - is_level_specific (u8)
# - Type / ID (s8)
# - Flip Mask (u8) class MonitorKind(Enum):
# - has_properties (u8) NONE = 0
# - vx (s32) RING = 1
# - vy (s32) SPEEDSHOES = 2
# - Properties (exists depending on Type) SHIELD = 3
# * Properties layout for monitor (id = 1): INVINCIBILITY = 4
# - kind (u8) LIFE = 5
SUPER = 6
@staticmethod
def get(name):
switch = {
"NONE": MonitorKind.NONE,
"RING": MonitorKind.RING,
"SPEEDSHOES": MonitorKind.SPEEDSHOES,
"SHIELD": MonitorKind.SHIELD,
"INVINCIBILITY": MonitorKind.INVINCIBILITY,
"1UP": MonitorKind.LIFE,
"SUPER": MonitorKind.SUPER,
}
result = switch.get(name.upper())
assert result is not None, f"Unknown monitor kind {name}"
return result
@dataclass @dataclass
class MonitorProperties: class MonitorProperties:
kind: str = "" kind: int = 0
def write_to(self, f):
f.write(c_ubyte(self.kind))
ObjectProperties = MonitorProperties | None ObjectProperties = MonitorProperties | None
# Root for the .OMP datatype
@dataclass @dataclass
class ObjectPlacement: class ObjectPlacement:
is_level_specific: bool = False is_level_specific: bool = False
@ -194,4 +243,37 @@ class ObjectPlacement:
f.write(c_int(self.x)) f.write(c_int(self.x))
f.write(c_int(self.y)) f.write(c_int(self.y))
f.write(c_ubyte(flipmask)) f.write(c_ubyte(flipmask))
# TODO: Properties if self.properties is not None:
self.properties.write_to(f)
# OBJECT MAP PLACEMENT (.OMP) LAYOUT
# - num_objects (u16)
# - Array of object placements:
# - 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)
# Root for the .OMP datatype
@dataclass
class ObjectLevelLayout:
out: str = ""
placements: [ObjectPlacement] = field(default_factory=list)
def write_to(self, f):
f.write(c_ushort(len(self.placements)))
for p in self.placements:
description = DummyObjectId(p.otype) if p.otype < 0 else ObjectId(p.otype)
print(f"Writing placement for object ID {p.otype} ({description})...")
p.write_to(f)
def write(self):
with open(self.out, "wb") as f:
self.write_to(f)

View file

@ -36,6 +36,8 @@ def parse_tileset(firstgid: int, set_src: str) -> (ObjectMap, str):
ts = ts.find("tileset") ts = ts.find("tileset")
o.is_level_specific = ts["name"] != "objects_common"
tiles = ts.find_all("tile") tiles = ts.find_all("tile")
o.num_objs = int(ts["tilecount"]) o.num_objs = int(ts["tilecount"])
# "classes" becomes an entry on dict o.object_types. # "classes" becomes an entry on dict o.object_types.
@ -87,7 +89,9 @@ def parse_tileset(firstgid: int, set_src: str) -> (ObjectMap, str):
# ] # ]
# collision["points"] = points # collision["points"] = points
o.obj_mapping[gid] = gid - firstgid o.obj_mapping[gid] = (
(gid - firstgid) if o.is_level_specific else ObjectId.get(od.name).value
)
# Append TOML data # Append TOML data
if extra: if extra:
@ -116,7 +120,6 @@ def parse_tileset(firstgid: int, set_src: str) -> (ObjectMap, str):
o.firstgid = firstgid o.firstgid = firstgid
o.out = splitext(set_src)[0] + ".OTD" o.out = splitext(set_src)[0] + ".OTD"
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"] o.name = ts["name"]
return (o, ts["name"]) return (o, ts["name"])
@ -136,11 +139,9 @@ def parse_object_group(
# Get first object's gid. # Get first object's gid.
first_obj = sorted(objects, key=lambda x: int(x.get("gid")))[0] first_obj = sorted(objects, key=lambda x: int(x.get("gid")))[0]
first_obj_gid = int(first_obj.get("gid")) 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. # Identify if this is from common objects tileset or from level-specific tileset.
for key, ts in tilesets.items(): for key, ts in tilesets.items():
print(f"First gid: {ts.firstgid}")
result = ts.get_is_specific_if_from_this_map(first_obj_gid) result = ts.get_is_specific_if_from_this_map(first_obj_gid)
if result is not None: if result is not None:
current_ts = ts current_ts = ts
@ -153,32 +154,38 @@ def parse_object_group(
for obj in objects: for obj in objects:
p = ObjectPlacement() p = ObjectPlacement()
p.is_level_specific = is_level_specific p.is_level_specific = is_level_specific
p.otype = current_ts.get_otype_from_gid(int(obj.get("gid"))) gid = int(obj.get("gid"))
p.otype = current_ts.get_otype_from_gid(gid)
p.x = int(float(obj.get("x"))) p.x = int(float(obj.get("x")))
p.y = int(float(obj.get("y"))) p.y = int(float(obj.get("y")))
p.flipx = False # TODO p.flipx = bool(gid & (1 << 31))
p.flipy = False # TODO p.flipy = bool(gid & (1 << 30))
p.rotcw = int(float(obj.get("rotation", 0))) == 90 p.rotcw = int(float(obj.get("rotation", 0))) == 90
p.rotccw = int(float(obj.get("rotation", 0))) == -90 p.rotccw = int(float(obj.get("rotation", 0))) == -90
props = obj.find("properties") props = obj.find("properties")
if props: if props:
# TODO: Check type, build type-specific property, etc if p.otype == ObjectId.MONITOR.value:
prop = props.find("property")
m = MonitorProperties()
m.kind = MonitorKind.get(prop.get("value")).value
p.properties = m
pass pass
print( # print(
f"Object type {current_ts.object_types[p.otype + current_ts.firstgid].name if p.otype >= 0 else 'DUMMY'}" # f"Object type {current_ts.object_types[p.otype + current_ts.firstgid].name if p.otype >= 0 else 'DUMMY'}"
) # )
pp(p) # pp(p)
placements.append(p) placements.append(p)
return placements return placements
def parse_map(map_src: str) -> (typing.Dict[str, ObjectMap], [ObjectPlacement]): def parse_map(map_src: str) -> (typing.Dict[str, ObjectMap], ObjectLevelLayout):
map = None map = None
with open(map_src) as f: with open(map_src) as f:
map = BeautifulSoup(f, "xml") map = BeautifulSoup(f, "xml")
objmaps = {} objmaps = {}
placements = [] layout = ObjectLevelLayout()
layout.out = realpath(splitext(map_src)[0] + ".OMP")
# Get all tilesets that are not 128x128. # Get all tilesets that are not 128x128.
# Depends on tileset name. # Depends on tileset name.
@ -194,6 +201,6 @@ def parse_map(map_src: str) -> (typing.Dict[str, ObjectMap], [ObjectPlacement]):
layergroup = map.find(name="group", attrs={"name": "OBJECTS"}) layergroup = map.find(name="group", attrs={"name": "OBJECTS"})
objgroups = layergroup.find_all("objectgroup") objgroups = layergroup.find_all("objectgroup")
for objgroup in objgroups: for objgroup in objgroups:
placements += parse_object_group(objmaps, objgroup) layout.placements += parse_object_group(objmaps, objgroup)
return (objmaps, placements) return (objmaps, layout)

View file

@ -1701,8 +1701,8 @@ For brevity of detailing, the types will be described as C structs directly.
#define OBJ_IDX_CHECKPOINT 3 #define OBJ_IDX_CHECKPOINT 3
#define OBJ_IDX_SPRING_Y 4 #define OBJ_IDX_SPRING_Y 4
#define OBJ_IDX_SPRING_R 5 #define OBJ_IDX_SPRING_R 5
#define OBJ_IDX_SPRING_D_Y 6 #define OBJ_IDX_SPRING_Y_D 6
#define OBJ_IDX_SPRING_D_R 7 #define OBJ_IDX_SPRING_R_D 7
#define OBJ_IDX_GOAL_SIGN 8 #define OBJ_IDX_GOAL_SIGN 8
#define OBJ_IDX_SWITCH 9 #define OBJ_IDX_SWITCH 9