59 KiB
- Object indices
- Level object list
- Object extra data
- Parsing objects from .TMX on Python
- Description of types
- Object state
Object indices
Object indices are files containing object information.
Common objects are objects that can be found on any level and are always ready to be loaded.
Level objects are objects that are level-specific.
The only big difference between common and level objects is the location of their texture and CLUTs on VRAM. Plus, it could be possible to load new texture information atop other objects; in this case, these objects should be loaded atop the level object textures.
For example, if a boss needs space in VRAM to be drawn and we have the opportunity to load its data, just override the level objects on VRAM. This would in theory corrupt the graphics of level objects, but not if the boss is the last "non-common" thing seen in a level. :)
Object data table
This is a text file that should be handwritten to give information on object animations.
- Object ID
- Number of animations
-
Array of animations
- Number of frames
- Loopback frame (0 for infinite loop, -1 for no loop)
- Is this the destruction animation?
- Destroy at end?
-
Array of frames
- u coord (u8)
- v coord (u8)
- width (u8)
- height (u8)
-
flip (u8, bitwise)
- 0x1: FLIP_X
- 0x2: FLIP_Y
- delay (in frames, 0 is minimum)
Common object index
REQUIREMENTS: Object tileset (.tsx); Common object data table (custom!!!)
OBJECTS SHOULD BE LISTED IN ORDER, STARTING AT INDEX 0.
Level object index
REQUIREMENTS: Object tileset (.tsx); Level object data table (custom!!!)
OBJECTS SHOULD BE LISTED IN ORDER, STARTING AT INDEX 0.
Level object list
REQUIREMENTS: Level map (.tmx); Object tileset (.tsx)
- Number of objects (u32)
-
Array of objects
- Object list (u8 – 0 for common, 1 for level…)
- ID (u8 – first index 0)
- quadrant_x (u8? u16?)
- quadrant_y (u8? u16?)
- relative_x (s16)
- relative_y (s16)
-
flip (u8; bitwise)
- 0: None
- 1: FLIP_X
- 2: FLIP_Y
- 3: ROTATE_CW (clockwise 90deg rotation)
- 4: ROTATE_CCW (counterclockwise 90deg rotation)
- (operations are bitwise, e.g. FLIP_X | FLIP_Y is possible)
-
flags (u32; bitwise – optional?)
- Flag 31 is reserved as a "destroyed?" property
Considerations
-
Rotation should be tested before flipping.
- ROTATE_CW is set iff "rotation" = 90.
- ROTATE_CCW is set iff "rotation" = -90.
- If ROTATE_CW || ROTATE_CCW, FLIP_X and FLIP_Y are ignored.
- Flipping and rotation should alter object hitboxes.
Specialized objects
Ring / Ring3h / Ring3v
- Ring3h and Ring3v are simply object spawners for Ring
- flip: completely ignored
-
flags: enumeration for state
- 0x0: UNTOUCHED
- 0x1: COLLECTED
Monitor
- flip: completely ignored
-
flags: enumeration for monitor kind
- 0x0: NONE
- 0x1: BROKEN
- 0x2: RING
- 0x3: 1UP
- 0x4: SHIELD
- 0x5: SHOES
- 0x6: INVINCIBLE
- 0x7: SUPER
Spring (horizontal)
- flip: FLIP_X Is ignored
Spring (diagonal)
- flip: ROTATE_CW and ROTATE_CCW are ignored
Checkpoint
- flip: completely ignored
-
flags: enumeration for state
- 0x0: UNTOUCHED
- 0x1: ACTIVATING
- 0x2: ACTIVATED
Object extra data
Frames are an array with the following arguments in order:
- U coord
- V coord
- Width
- Height
- [optional, default False] Flip_X
- [optional, default False] Flip_Y
[ring]
[[ring.animations]]
name = "idle"
id = 0
# Frames: u, v, w, h
frames = [
[16, 0, 16, 16],
[32, 0, 16, 16],
[48, 0, 8, 16],
[56, 0, 16, 16],
]
loopback = 0
[[ring.animations]]
name = "destroyed"
id = 1
frames = [
[72, 0, 16, 16],
[88, 0, 16, 16],
[104, 0, 16, 16],
[120, 0, 16, 16],
]
loopback = -1
# ============
[monitor]
[[monitor.animations]]
name = "normal"
id = 0
frames = [[136, 0, 32, 32]]
loopback = 0
[[monitor.animations]]
name = "destroyed"
id = 1
frames = [[224, 32, 32, 16]]
loopback = 0
# ------------
[monitor.fragment]
offset = [0, -10]
[[monitor.fragment.animations]]
id = 0
name = "none"
frames = [
[168, 0, 16, 16],
[184, 0, 16, 16],
[200, 0, 16, 16],
]
loopback = 0
[[monitor.fragment.animations]]
id = 1
name = "ring"
frames = [
[168, 0, 16, 16],
[216, 0, 16, 16],
]
loopback = 0
[[monitor.fragment.animations]]
id = 2
name = "speedshoes"
frames = [
[168, 0, 16, 16],
[232, 0, 16, 16],
]
loopback = 0
[[monitor.fragment.animations]]
id = 3
name = "shield"
frames = [
[168, 0, 16, 16],
[168, 16, 16, 16],
]
loopback = 0
[[monitor.fragment.animations]]
id = 4
name = "invincibility"
frames = [
[168, 0, 16, 16],
[184, 16, 16, 16],
]
loopback = 0
[[monitor.fragment.animations]]
id = 5
name = "1up"
frames = [
[168, 0, 16, 16],
[200, 16, 16, 16],
]
loopback = 0
[[monitor.fragment.animations]]
id = 6
name = "super"
frames = [
[168, 0, 16, 16],
[216, 16, 16, 16],
]
loopback = 0
# ============
[spikes]
[[spikes.animations]]
name = "none"
id = 0
frames = [[0, 16, 32, 32]]
loopback = 0
# ============
[checkpoint]
[[checkpoint.animations]]
name = "none"
id = 0
frames = [[32, 32, 16, 48]]
loopback = 0
# ------------
[checkpoint.fragment]
offset = [0, -16]
[[checkpoint.fragment.animations]]
name = "inactive"
id = 0
frames = [[32, 16, 16, 16]]
loopback = 0
[[checkpoint.fragment.animations]]
name = "active"
id = 1
frames = [
[0, 48, 16, 16],
[16, 48, 16, 16],
]
loopback = 0
# ============
[spring_yellow]
[[spring_yellow.animations]]
name = "idle"
id = 0
frames = [[80, 16, 32, 16]]
loopback = 0
[[spring_yellow.animations]]
name = "bounce"
id = 1
frames = [
[80, 32, 32, 8],
[80, 40, 32, 40],
]
loopback = -1
# ============
[spring_red]
[[spring_red.animations]]
name = "idle"
id = 0
frames = [[48, 16, 32, 16]]
loopback = 0
[[spring_red.animations]]
name = "bounce"
id = 1
frames = [
[48, 32, 32, 8],
[48, 40, 32, 40],
]
loopback = -1
# ============
[spring_yellow_diagonal]
[[spring_yellow_diagonal.animations]]
name = "idle"
id = 0
frames = [[112, 80, 32, 32]]
loopback = 0
[[spring_yellow_diagonal.animations]]
name = "bounce"
id = 1
frames = [
[144, 80, 32, 32],
[176, 80, 48, 48],
]
loopback = -1
# ============
[spring_red_diagonal]
[[spring_red_diagonal.animations]]
name = "idle"
id = 0
frames = [[112, 32, 32, 32]]
loopback = 0
[[spring_red_diagonal.animations]]
name = "bounce"
id = 1
frames = [
[144, 32, 32, 32],
[176, 32, 48, 48],
]
loopback = -1
# ============
[goal_sign]
[[goal_sign.animations]]
name = "none"
id = 0
frames = [[0, 80, 48, 56]]
loopback = 0
[[goal_sign.animations]]
name = "spin"
id = 1
frames = [
# x, y, w, h, flip_x, flip_y
[0, 80, 48, 56, 0, 0],
[48, 80, 32, 56, 0, 0],
[80, 80, 32, 56, 0, 0],
[48, 80, 32, 56, 1, 0],
[0, 136, 48, 56, 0, 0],
[48, 80, 32, 56, 0, 0],
[80, 80, 32, 56, 0, 0],
[48, 80, 32, 56, 1, 0],
]
loopback = 0
[[goal_sign.animations]]
name = "win_sonic"
id = 2
frames = [[0, 136, 48, 56]]
loopback = 0
# ============
[switch]
[[switch.animations]]
name = "inactive"
id = 0
frames = [[112, 64, 32, 16]]
loopback = 0
[[switch.animations]]
name = "active"
id = 1
frames = [[144, 72, 32, 8]]
loopback = 0
# ============
# Dummy objects
# ============
[ring_3h]
dummy = true
[ring_3v]
dummy = true
Parse test
import toml
from pprint import pp
cfg = None
with open("../assets/levels/COMMON/objects_common.toml") as f:
cfg = toml.load(f)
pp(cfg)
{'ring': {'animations': [{'name': 'idle', 'id': 0, 'frames': [[16, 0, 16, 16], [32, 0, 16, 16], [48, 0, 8, 16], [56, 0, 16, 16]], 'loopback': 0}, {'name': 'destroyed', 'id': 1, 'frames': [[72, 0, 16, 16], [88, 0, 16, 16], [104, 0, 16, 16], [120, 0, 16, 16]], 'loopback': -1}]}, 'monitor': {'animations': [{'name': 'normal', 'id': 0, 'frames': [[136, 0, 32, 32]], 'loopback': 0}, {'name': 'destroyed', 'id': 1, 'frames': [[224, 32, 32, 16]], 'loopback': 0}], 'fragment': {'offset': [0, -10], 'animations': [{'id': 0, 'name': 'none', 'frames': [[168, 0, 16, 16], [184, 0, 16, 16], [200, 0, 16, 16]], 'loopback': 0}, {'id': 1, 'name': 'ring', 'frames': [[168, 0, 16, 16], [216, 0, 16, 16]], 'loopback': 0}, {'id': 2, 'name': 'speedshoes', 'frames': [[168, 0, 16, 16], [232, 0, 16, 16]], 'loopback': 0}, {'id': 3, 'name': 'shield', 'frames': [[168, 0, 16, 16], [168, 16, 16, 16]], 'loopback': 0}, {'id': 4, 'name': 'invincibility', 'frames': [[168, 0, 16, 16], [184, 16, 16, 16]], 'loopback': 0}, {'id': 5, 'name': '1up', 'frames': [[168, 0, 16, 16], [200, 16, 16, 16]], 'loopback': 0}, {'id': 6, 'name': 'super', 'frames': [[168, 0, 16, 16], [216, 16, 16, 16]], 'loopback': 0}]}}, 'spikes': {'animations': [{'name': 'none', 'id': 0, 'frames': [[0, 16, 32, 32]], 'loopback': 0}]}, 'checkpoint': {'animations': [{'name': 'none', 'id': 0, 'frames': [[32, 32, 16, 48]], 'loopback': 0}], 'fragment': {'offset': [0, -16], 'animations': [{'name': 'inactive', 'id': 0, 'frames': [[32, 16, 16, 16]], 'loopback': 0}, {'name': 'active', 'id': 1, 'frames': [[0, 48, 16, 16], [16, 48, 16, 16]], 'loopback': 0}]}}, 'spring_yellow': {'animations': [{'name': 'idle', 'id': 0, 'frames': [[80, 16, 32, 16]], 'loopback': 0}, {'name': 'bounce', 'id': 1, 'frames': [[80, 32, 32, 8], [80, 40, 32, 40]], 'loopback': -1}]}, 'spring_red': {'animations': [{'name': 'idle', 'id': 0, 'frames': [[48, 16, 32, 16]], 'loopback': 0}, {'name': 'bounce', 'id': 1, 'frames': [[48, 32, 32, 8], [48, 40, 32, 40]], 'loopback': -1}]}, 'spring_yellow_diagonal': {'animations': [{'name': 'idle', 'id': 0, 'frames': [[112, 80, 32, 32]], 'loopback': 0}, {'name': 'bounce', 'id': 1, 'frames': [[144, 80, 32, 32], [176, 80, 48, 48]], 'loopback': -1}]}, 'spring_red_diagonal': {'animations': [{'name': 'idle', 'id': 0, 'frames': [[112, 32, 32, 32]], 'loopback': 0}, {'name': 'bounce', 'id': 1, 'frames': [[144, 32, 32, 32], [176, 32, 48, 48]], 'loopback': -1}]}, 'goal_sign': {'animations': [{'name': 'none', 'id': 0, 'frames': [[0, 80, 48, 56]], 'loopback': 0}, {'name': 'spin', 'id': 1, 'frames': [[0, 80, 48, 56, 0, 0], [48, 80, 32, 56, 0, 0], [80, 80, 32, 56, 0, 0], [48, 80, 32, 56, 1, 0], [0, 136, 48, 56, 0, 0], [48, 80, 32, 56, 0, 0], [80, 80, 32, 56, 0, 0], [48, 80, 32, 56, 1, 0]], 'loopback': 0}, {'name': 'win_sonic', 'id': 2, 'frames': [[0, 136, 48, 56]], 'loopback': 0}]}, 'switch': {'animations': [{'name': 'inactive', 'id': 0, 'frames': [[112, 64, 32, 16]], 'loopback': 0}, {'name': 'active', 'id': 1, 'frames': [[144, 72, 32, 8]], 'loopback': 0}]}, 'ring_3h': {'dummy': True}, 'ring_3v': {'dummy': True}}
Parsing objects from .TMX on Python
- First of all, GIDs are VERY IMPORTANT! GIDs store FLIP_X on bit 31 and
FLIP_Y on bit 30. In the end, we have to clear a GID with:
gid & ~(0b1111 << 29)
. See this link. - Since PyTMX does not calculate GIDs with flip information properly, we have to parse the XML by hand and do all the dirty work.
- We need to fetch extra information on tile objects by hand as well, by
opening the associated
.tsx
file, so we need to keep a relative path.
INFORMATION THAT SHOULD BE LOADED FROM TOML:
- num_animations
-
animations (list of Animation)
- id
- num_frames
- frames (x, y, width, height, flip_x, flip_y)
- loopback index
-
fragment (if existing)
- offset (relative x, relative y)
-
animations (list of Animation)
- id
- num_frames
- frames (x, y, width, height, flip_x, flip_y)
- loopback index
OUTPUT TYPES:
- Object table definition:
*.OTD
- Object map placement:
*.OMP
import os
from bs4 import BeautifulSoup
from pprint import pp
import toml
from ctypes import c_ubyte, c_byte, c_short, c_ushort, c_int
c_short = c_short.__ctype_be__
c_ushort = c_ushort.__ctype_be__
c_int = c_int.__ctype_be__
# Constant object table for exporting properties.
# Only object described here will be exported. Furthermore,
# if an object described here was not described, you'll get
# an error.
COMMON_OBJECT_TABLE = {
0: "ring",
1: "monitor",
2: "spikes",
3: "checkpoint",
4: "spring_yellow",
5: "spring_red",
6: "spring_diagonal_yellow",
7: "spring_diagonal_red",
8: "goal_sign",
9: "switch",
}
map_src = os.path.realpath("../assets/levels/R2/Z1.tmx")
map = None
tilesets = {}
with open(map_src) as f:
map = BeautifulSoup(f, "xml")
sets = map.find_all("tileset")
sets = [x for x in map.find_all("tileset") if x["source"].find("128") == -1]
for tsx in sets:
set_src = os.path.realpath(os.path.dirname(map_src) + "/" + tsx["source"])
with open(set_src) as tsf:
# TOML data
toml_src = os.path.splitext(set_src)[0] + ".toml"
extra_data = toml.load(toml_src)
ts = BeautifulSoup(tsf, "xml")
ts = ts.find("tileset")
tiles = ts.find_all("tile")
firstgid = int(tsx["firstgid"])
num_objs = int(ts["tilecount"])
classes = {}
for i in range(int(ts["tilecount"])):
collision = None
tile = next((x for x in tiles if x["id"] == f"{i}"), None)
if tile:
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
class_name = (tile["type"] if tile else "none").lower()
idx = i + firstgid
classes[idx] = {
"name": class_name,
}
# Append TOML data
extra = extra_data.get(class_name)
if extra:
animations = extra["animations"]
animations.sort(key=lambda x: x.get("id"))
classes[idx]["animations"] = [{
"frames": data["frames"],
"loopback": data["loopback"],
} for data in animations]
if extra.get("fragment"):
frag_animations = extra["fragment"]["animations"]
frag_animations.sort(key=lambda x: x.get("id", 999))
classes[idx]["fragment"] = {
"offset": extra["fragment"]["offset"],
"animations": [{
"frames": data["frames"],
"loopback": data["loopback"],
} for data in frag_animations]
}
if collision:
classes[i + firstgid]["collision"] = collision
else:
num_objs -= 1
objset = {
"out": os.path.splitext(set_src)[0] + ".OTD",
"firstgid": firstgid,
"num_objs": int(ts["tilecount"]),
"object_types": classes,
}
tilesets[ts["name"]] = objset
print("Tileset objects:")
pp(tilesets)
print("====\n")
# Get object group
objgroup = map.find(name="group", attrs={"name": "OBJECTS"})
# Get common objects
objects_common_tags = (
objgroup.find(
name="objectgroup", attrs={"name": "COMMON"}
).find_all("object")
)
# This should be a function
objects_common = []
expected_types = tilesets["objects_common"]["object_types"]
for tag in objects_common_tags:
gid = int(tag["gid"])
# We NEED to clear the four MSB of the GID.
# These guys hold flip information.
# (Why the heck would someone store this here????)
objtype = expected_types[gid & ~(0b1111 << 29)]["name"]
props = None
rotation = tag.get("rotation")
if objtype == "monitor":
props = {}
objprops = tag.find("properties")
kind = objprops.find(attrs={"name": "Kind"})
props["kind"] = kind["value"].lower()
obj = {
"type": objtype,
"x": int(float(tag["x"])),
"y": int(float(tag["y"])),
"width": int(float(tag["width"])),
"height": int(float(tag["height"])),
"flip_x": gid & (1 << 31) != 0,
"flip_y": gid & (1 << 30) != 0,
"rotate_cw": (int(float(rotation)) == 90 if rotation else False),
"rotate_ccw": (int(float(rotation)) == -90 if rotation else False),
}
if props:
obj["properties"] = props
objects_common.append(obj)
print("Map objects:")
pp(objects_common)
# OBJECT TABLE DEFINITION (.OTN) LAYOUT
# - Type / ID (u8)
# - 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)
# Exporting object table definitions (.OTDs)
# for table_name, table in tilesets.items():
# with open(table["out"], "wb") as otd:
# if table_name == "objects_common":
# # In this case, iterate over known common objects
# # and export them
# for obj_idx, obj_name in COMMON_OBJECT_TABLE.items():
# gid = table["firstgid"] + obj_idx
# odef = table["object_types"][gid]
# pass
# pass
# 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)
Tileset objects: {'objects_common': {'out': '/home/alchemist/git/engine-psx/assets/levels/COMMON/objects_common.OTD', 'firstgid': 113, 'num_objs': 16, 'object_types': {113: {'name': 'ring', 'animations': [{'frames': [[16, 0, 16, 16], [32, 0, 16, 16], [48, 0, 8, 16], [56, 0, 16, 16]], 'loopback': 0}, {'frames': [[72, 0, 16, 16], [88, 0, 16, 16], [104, 0, 16, 16], [120, 0, 16, 16]], 'loopback': -1}]}, 114: {'name': 'monitor', 'animations': [{'frames': [[136, 0, 32, 32]], 'loopback': 0}, {'frames': [[224, 32, 32, 16]], 'loopback': 0}], 'fragment': {'offset': [8, 6], 'animations': [{'frames': [[168, 0, 16, 16], [184, 0, 16, 16], [200, 0, 16, 16]], 'loopback': 0}, {'frames': [[168, 0, 16, 16], [216, 0, 16, 16]], 'loopback': 0}, {'frames': [[168, 0, 16, 16], [232, 0, 16, 16]], 'loopback': 0}, {'frames': [[168, 0, 16, 16], [168, 16, 16, 16]], 'loopback': 0}, {'frames': [[168, 0, 16, 16], [184, 16, 16, 16]], 'loopback': 0}, {'frames': [[168, 0, 16, 16], [200, 16, 16, 16]], 'loopback': 0}, {'frames': [[168, 0, 16, 16], [216, 16, 16, 16]], 'loopback': 0}]}, 'collision': {'type': 'rect', 'x': 16, 'y': 34, 'width': 30, 'height': 30}}, 115: {'name': 'spikes', 'animations': [{'frames': [[0, 16, 32, 32]], 'loopback': 0}], 'collision': {'type': 'rect', 'x': 16, 'y': 32, 'width': 32, 'height': 32}}, 116: {'name': 'checkpoint', 'animations': [{'frames': [[32, 32, 16, 48]], 'loopback': 0}], 'fragment': {'offset': [0, -16], 'animations': [{'frames': [[32, 16, 16, 16]], 'loopback': 0}, {'frames': [[0, 48, 16, 16], [16, 48, 16, 16]], 'loopback': 0}]}}, 117: {'name': 'spring_yellow', 'animations': [{'frames': [[80, 16, 32, 16]], 'loopback': 0}, {'frames': [[80, 32, 32, 8], [80, 40, 32, 40]], 'loopback': -1}], 'collision': {'type': 'rect', 'x': 16, 'y': 48, 'width': 32, 'height': 16}}, 118: {'name': 'spring_red', 'animations': [{'frames': [[48, 16, 32, 16]], 'loopback': 0}, {'frames': [[48, 32, 32, 8], [48, 40, 32, 40]], 'loopback': -1}], 'collision': {'type': 'rect', 'x': 16, 'y': 48, 'width': 32, 'height': 16}}, 119: {'name': 'spring_yellow_diagonal', 'animations': [{'frames': [[112, 80, 32, 32]], 'loopback': 0}, {'frames': [[144, 80, 32, 32], [176, 80, 48, 48]], 'loopback': -1}], 'collision': {'type': 'polygon', 'points': [[0, 0], [9, 0], [31, 22], [31, 32], [0, 32]]}}, 120: {'name': 'spring_red_diagonal', 'animations': [{'frames': [[112, 32, 32, 32]], 'loopback': 0}, {'frames': [[144, 32, 32, 32], [176, 32, 48, 48]], 'loopback': -1}], 'collision': {'type': 'polygon', 'points': [[0, 0], [9, 0], [31, 22], [31, 32], [0, 32]]}}, 121: {'name': 'ring_3h'}, 122: {'name': 'switch', 'animations': [{'frames': [[112, 64, 32, 16]], 'loopback': 0}, {'frames': [[144, 72, 32, 8]], 'loopback': 0}], 'collision': {'type': 'rect', 'x': 16, 'y': 48, 'width': 32, 'height': 16}}, 123: {'name': 'ring_3v'}, 124: {'name': 'goal_sign', 'animations': [{'frames': [[0, 80, 48, 56]], 'loopback': 0}, {'frames': [[0, 80, 48, 56, 0, 0], [48, 80, 32, 56, 0, 0], [80, 80, 32, 56, 0, 0], [48, 80, 32, 56, 1, 0], [0, 136, 48, 56, 0, 0], [48, 80, 32, 56, 0, 0], [80, 80, 32, 56, 0, 0], [48, 80, 32, 56, 1, 0]], 'loopback': 0}, {'frames': [[0, 136, 48, 56]], 'loopback': 0}]}}}} ==== Map objects: [{'type': 'ring_3h', 'x': 398, 'y': 287, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'monitor', 'x': 1742, 'y': 225, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False, 'properties': {'kind': 'ring'}}, {'type': 'monitor', 'x': 4866, 'y': 453, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False, 'properties': {'kind': '1up'}}, {'type': 'monitor', 'x': 5123, 'y': 768, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False, 'properties': {'kind': 'ring'}}, {'type': 'spring_red', 'x': 5519, 'y': 900, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 681, 'y': 287, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 1308, 'y': 242, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 3166, 'y': 180, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 3231, 'y': 322, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 2864, 'y': 260, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 2956, 'y': 260, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 1875, 'y': 220, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 1999, 'y': 220, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 1850, 'y': 326, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 1925, 'y': 326, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 3916, 'y': 387, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 4065, 'y': 388, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 4289, 'y': 453, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 4764, 'y': 453, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'spring_yellow_diagonal', 'x': 3056, 'y': 768, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring', 'x': 3206, 'y': 622, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 3206, 'y': 646, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 3181, 'y': 673, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring', 'x': 3230, 'y': 673, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring', 'x': 3254, 'y': 673, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'monitor', 'x': 3055, 'y': 324, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False, 'properties': {'kind': 'ring'}}, {'type': 'spring_yellow_diagonal', 'x': 6256, 'y': 928, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 6361, 'y': 883, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'monitor', 'x': 6888, 'y': 640, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False, 'properties': {'kind': 'ring'}}, {'type': 'monitor', 'x': 6919, 'y': 640, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False, 'properties': {'kind': 'ring'}}, {'type': 'monitor', 'x': 6951, 'y': 640, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False, 'properties': {'kind': 'ring'}}, {'type': 'monitor', 'x': 6983, 'y': 640, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False, 'properties': {'kind': '1up'}}, {'type': 'ring_3h', 'x': 5920, 'y': 638, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 6042, 'y': 638, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 6240, 'y': 688, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 6415, 'y': 656, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 6625, 'y': 688, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 6470, 'y': 1155, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 6598, 'y': 1155, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 6755, 'y': 1152, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'spring_red_diagonal', 'x': 5040, 'y': 672, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'spring_yellow', 'x': 5087, 'y': 768, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'monitor', 'x': 6016, 'y': 896, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False, 'properties': {'kind': 'ring'}}, {'type': 'spikes', 'x': 3189, 'y': 768, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'spikes', 'x': 3221, 'y': 768, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 2208, 'y': 220, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 2526, 'y': 386, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 2643, 'y': 383, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'monitor', 'x': 3465, 'y': 256, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False, 'properties': {'kind': 'shield'}}, {'type': 'ring_3h', 'x': 4615, 'y': 964, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 4824, 'y': 964, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3h', 'x': 5599, 'y': 736, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'spring_yellow', 'x': 4766, 'y': 353, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3v', 'x': 4765, 'y': 258, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3v', 'x': 4765, 'y': 186, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3v', 'x': 4744, 'y': 200, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3v', 'x': 4787, 'y': 200, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'ring_3v', 'x': 4502, 'y': 577, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}, {'type': 'spring_yellow', 'x': 5696, 'y': 978, 'width': 64, 'height': 64, 'flip_x': False, 'flip_y': False, 'rotate_cw': True, 'rotate_ccw': False}, {'type': 'spring_red_diagonal', 'x': 4880, 'y': 223, 'width': 64, 'height': 64, 'flip_x': True, 'flip_y': False, 'rotate_cw': False, 'rotate_ccw': False}]
Description of types
For brevity of detailing, the types will be described as C structs directly.
Object indices
#define OBJ_IDX_RING 0
#define OBJ_IDX_MONITOR 1
#define OBJ_IDX_SPIKES 2
#define OBJ_IDX_CHECKPOINT 3
#define OBJ_IDX_SPRING_Y 4
#define OBJ_IDX_SPRING_R 5
#define OBJ_IDX_SPRING_Y_D 6
#define OBJ_IDX_SPRING_R_D 7
#define OBJ_IDX_GOAL_SIGN 8
#define OBJ_IDX_SWITCH 9
#define OBJ_TABLE_COMMON__SIZE (OBJ_IDX_SWITCH+1)
#define OBJ_IDX_RINGS_3H -1
#define OBJ_IDX_RINGS_3V -2
Special flags
Definitions for bitwise flip flags:
#define MASK_FLIP_FLIPX 0x1
#define MASK_FLIP_FLIPY 0x2
#define MASK_FLIP_ROTCW 0x4 // Rotated clockwise
#define MASK_FLIP_ROTCT 0x8 // Rotated counterclockwise
Definitions for monitor kind:
#define MONITOR_KIND_NONE 0
#define MONITOR_KIND_RING 1
#define MONITOR_KIND_SPEEDSHOES 2
#define MONITOR_KIND_SHIELD 3
#define MONITOR_KIND_INVINCIBILITY 4
#define MONITOR_KIND_1UP 5
#define MONITOR_KIND_SUPER 6
Object table definitions
typedef struct {
uint8_t u0, v0;
uint8_t w, h;
uint8_t flipmask;
} obj_animation_frame;
typedef struct {
obj_animation_frame *frames;
uint16_t num_frames;
int8_t loopback_frame;
} obj_animation;
typedef struct {
int16_t offsetx, offsety;
obj_animation *animations;
uint16_t num_animations;
} obj_fragment;
typedef struct {
obj_animation *animations; // TOML data
obj_fragment *fragment; // TOML data
uint16_t num_animations; // TOML data
// TODO: Collision
} obj_basicdata;
obj_basicdata obj_table[OBJ_TABLE_COMMON__SIZE];
Properties: Monitor
typedef struct {
uint8_t kind;
} obj_properties_monitor;
Object state
Once spawned, objects are created from initial state and manipulated as needed.
typedef struct {
uint8_t type;
uint8_t animation;
uint8_t frame;
uint8_t flipmask;
VECTOR pos;
void *props;
} obj_state;