import sys import json import shared TEMPLATE = """ // generated code! be nice #include #include "sys/sys.h" sys_maptile map_{{map_name}}_data[{{width * height}}] = { {{ tiles|join(",") }} }; sys_map map_{{map_name}} = { .tiles=map_{{map_name}}_data, .width={{width}}, .height={{height}}, }; {% for entity_type in entity_types %} void map_{{map_name}}_{{entity_type}}_create(sys_i32 x, sys_i32 y); {% endfor %} {% for entity_name, field_name, c_type, render_mode in entity_fields %} {% if render_mode == "scalar" %} void map_{{map_name}}_{{entity_name}}_set_{{field_name}}({{c_type}} value); {% elif render_mode == "array" %} void map_{{map_name}}_{{entity_name}}_add_{{field_name}}({{c_type}} value); {% else %} wtf; // {{ render_mode }} {% endif %} {% endfor %} void map_{{map_name}}_create_entities() { {% for entity in entities %} map_{{map_name}}_{{entity.type}}_create({{entity.x}}, {{entity.y}}); {% for field in entity.fields %} {% if field.renderMode == "scalar" %} map_{{map_name}}_{{entity.type}}_set_{{field.name}}({{field.value|safe}}); {% elif field.renderMode == "array" %} {% for s in field.value %} map_{{map_name}}_{{entity.type}}_add_{{field.name}}({{s|safe}}); {% endfor %} {% endif %} {% endfor %} {% endfor %} } """.lstrip() def main(map_name, fname_ldtk, fname_c): width, height, tiles, entities, entity_types, entity_fields = load_mapdata(fname_ldtk) print(entity_fields) with open(fname_c, "wt") as output: output.write( shared.templates.from_string(TEMPLATE).render( map_name=map_name, tiles=tiles, width=width, height=height, entities=entities, entity_types=entity_types, entity_fields=entity_fields, ) ) def load_mapdata(fname_ldtk): sparse_tiles = {} entities = [] entity_types = set() entity_fields = set() with open(fname_ldtk, "rt") as f: data = json.load(f) for level in data["levels"]: level_x = level["worldX"] // 8 level_y = level["worldY"] // 8 for layer in level["layerInstances"]: if layer["__identifier"] == "conceptual": # for right now I use the vague layer to assign tiles for tile in layer["autoLayerTiles"]: x, y = tile["px"] x //= 8 y //= 8 ix = tile["t"] sparse_tiles[level_x + x, level_y + y] = ix if layer["__identifier"] == "entities": for e in layer["entityInstances"]: # TODO: Other fields? entity = { "type": e["__identifier"], "x": e["__worldX"] // 8, "y": e["__worldY"] // 8, "fields": [] } for f in e["fieldInstances"]: field = { "name": f["__identifier"], } field["value"], field["renderMode"], rendered_type = format_field_value(f["__type"], f["__value"]) entity["fields"].append(field) entity_fields.add(( entity["type"], field["name"], rendered_type, field["renderMode"] )) entities.append(entity) entity_types.add(entity["type"]) x_min = 0 y_min = 0 assert not any(x for (x, _) in sparse_tiles if x < x_min), "level can't be left of (0, 0)" assert not any(y for (_, y) in sparse_tiles if y < y_min), "level can't be up from (0, 0)" width = max(x for (x, _) in sparse_tiles) + 1 height = max(y for (_, y) in sparse_tiles) + 1 dense_tiles = [] for y in range(height): for x in range(width): k = (x, y) if k in sparse_tiles: dense_tiles.append(sparse_tiles[k]) else: dense_tiles.append(255) return width, height, dense_tiles, entities, entity_types, entity_fields def format_field_value(ty, val): if ty == "Bool": return "true" if val else "false", "scalar", "bool" elif ty == "Int": return str(val), "scalar", "sys_i32" elif ty == "String": if val is None: return "NULL", "scalar", "const char*" return json.dumps(val), "scalar" # this is close enough to being right in C elif ty == "Array": return [format_field_value("Int", i)[0] for i in val], "array", "sys_i32" elif ty == "Array": return [format_field_value("String", i)[0] for i in val], "array", "const char*" else: assert False, f"unknown type: {ty}" def annot_xy(lst, w, h): assert len(lst) == w * h for y in range(h): for x in range(w): yield x, y, lst[y * w + x] if __name__ == "__main__": assert len(sys.argv) == 4, \ "there must be three args (map name, src ldtk, out c)" main(sys.argv[1], sys.argv[2], sys.argv[3])