engine-psx/tools/object_design.org
2024-09-30 23:27:58 -03:00

59 KiB
Raw Permalink Blame History

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

  1. 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.
  2. Since PyTMX does not calculate GIDs with flip information properly, we have to parse the XML by hand and do all the dirty work.
  3. 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;