From 20aee8ffae6665eb01eaf4a72954c4bae0312ee9 Mon Sep 17 00:00:00 2001 From: Klemen Plestenjak Date: Wed, 13 Dec 2023 15:32:02 +0100 Subject: [PATCH] Rework scripts to work for single tileset --- scripts/extract_common.py | 263 +++++++++++++++++++++++++++-- scripts/extract_entities.py | 154 ----------------- scripts/extract_tileset.py | 47 ++++-- scripts/extract_tileset_classes.py | 199 ---------------------- 4 files changed, 283 insertions(+), 380 deletions(-) delete mode 100644 scripts/extract_entities.py delete mode 100755 scripts/extract_tileset_classes.py diff --git a/scripts/extract_common.py b/scripts/extract_common.py index 14cc210..a4d538f 100644 --- a/scripts/extract_common.py +++ b/scripts/extract_common.py @@ -1,11 +1,19 @@ +from collections import defaultdict + + class ExtractFileWriter: indent_width = 4 indent_level = 0 indention = "" + content = "" def __init__(self, name): self.name = name.upper() + def to_file(self, path): + with open(path, "w") as f: + f.write(self.content) + def indent(self): self.indent_level += self.indent_width self.indention = " " * self.indent_level @@ -14,51 +22,274 @@ class ExtractFileWriter: self.indent_level -= self.indent_width self.indention = " " * self.indent_level + def output_anim_sequence_struct(self): + self.output("typedef struct AnimationSequence {\n") + self.indent() + self.output("BzTile startFrame;\n") + self.output("i32 frameCount;\n") + self.unindent() + self.output("} AnimationSequence;\n") + self.empty_line() + + def output_anim_frame_struct(self): + self.output("typedef struct AnimationFrame {\n") + self.indent() + self.output("BzTile frame;\n") + self.output("f32 duration;\n") + self.unindent() + self.output("} AnimationFrame;\n") + self.empty_line() + def header_guard_start(self): - print(f"#ifndef {self.name}") - print(f"#define {self.name}") - print() + self.content += f"#ifndef {self.name}_H\n" + self.content += f"#define {self.name}_H\n" + self.content += "\n" def header_guard_stop(self): - print(f"#endif // {self.name}") + self.content += f"#endif // {self.name}_H\n" def include(self, header): - print(f"${self.indention}#include {header}") + self.content += f"{self.indention}#include {header}\n" def enum_start(self, name): - print(f"{self.indention}typedef enum {name} {{") + self.content += f"{self.indention}typedef enum {name} {{\n" self.indent() def enum_stop(self, name): self.unindent() - print(f"}} {name};") - print() + self.content += f"}} {name};\n\n" def enum_list(self, name, enums): self.enum_start(name) for enum in enums: - print(f"{self.indention}{enum},") + self.content += f"{self.indention}{enum},\n" self.enum_stop(name) def enum_dict(self, name, enums): self.enum_start(name) for enum, v in enums.items(): - print(f"{self.indention}{enum} = {v},") + self.content += f"{self.indention}{enum} = {v},\n" self.enum_stop(name) def output(self, out): - print(f"{self.indention}{out}", end="") + self.content += self.indention + self.content += out - def output_line(self): - print() + def empty_line(self): + self.content += '\n' def only_output(self, out): - print(out, end="") + self.content += out def block_start(self): - print(f"{{") + self.content += f"{{\n" self.indent() def block_end(self): self.unindent() - print(f"{self.indention}}}") + self.content += f"{self.indention}}}\n" + + +def group_by_class(tiles): + groups = defaultdict(list) + for tile in tiles: + if 'type' not in tile: + continue + tile_class = tile['type'] + groups[tile_class].append(tile) + return groups + + +class EnumWriter: + def __init__(self, writer: ExtractFileWriter, prefix, tiles): + self.writer = writer + self.prefix = prefix + self.all_tiles = tiles + self.tiles = group_by_class(tiles) + self.enums = [] + self.enums += [self.to_enum(x) for x in self.tiles.keys()] + self.enums.append(self.to_enum("count")) + self.enums.append(self.to_enum("none")) + self.enum_type = f"{prefix.capitalize()}Type" + + def to_enum(self, name): + return f"{self.prefix.upper()}_{name.upper()}" + + def to_anim_enum(self, name): + return f"ANIM_{name.upper()}" + + def output_enum(self): + self.writer.enum_list(self.enum_type, self.enums) + + def output_tile_to_enum(self, func_name): + writer = self.writer + writer.output(f"static {self.enum_type} {func_name}(BzTile tile) ") + writer.block_start() + + writer.output("switch (tile) ") + writer.block_start() + + for key, tiles in self.tiles.items(): + ids = [x['id'] for x in tiles] + for id in ids: + writer.output(f"case {id}:\n") + writer.output(f" return {self.to_enum(key)};\n") + writer.output(f"default:\n") + writer.output(f" return {self.enums[-1]};\n") + + writer.block_end() + writer.block_end() + writer.empty_line() + + def output_enum_to_tile(self, func_name): + writer = self.writer + writer.output(f"static {self.enum_type} {func_name}({self.enum_type} type) ") + writer.block_start() + + writer.output("switch (type) ") + writer.block_start() + + for key, tiles in self.tiles.items(): + ids = sorted([x['id'] for x in tiles]) + writer.output(f"case {self.to_enum(key)}: return {ids[0]};\n") + writer.output(f"default: return -1;\n") + + writer.block_end() + writer.block_end() + writer.empty_line() + + def output_str_to_enum(self, func_name): + writer = self.writer + writer.output(f"static {self.enum_type} {func_name}(const char *str) ") + writer.block_start() + + for name in self.tiles.keys(): + writer.output(f"if (strncmp(\"{name}\", str, {len(name)}) == 0) return {self.to_enum(name)};\n") + writer.output(f"return {self.enums[-1]};\n") + + writer.block_end() + writer.empty_line() + + def output_enum_to_str(self, func_name): + writer = self.writer + writer.output(f"static const char *{func_name}({self.enum_type} type) ") + writer.block_start() + + writer.output("switch (type) ") + writer.block_start() + + for name in self.tiles.keys(): + writer.output(f"case {self.to_enum(name)}: return \"{name}\";\n") + writer.output(f"default: return NULL;\n") + + writer.block_end() + writer.block_end() + writer.empty_line() + + def output_enum_tile_size(self, func_name): + writer = self.writer + writer.output( + f"static {self.enum_type} {func_name}({self.enum_type} type, BzTile *outWidth, BzTile *outHeight) ") + writer.block_start() + + writer.output("switch (type) ") + writer.block_start() + + for key, tiles in self.tiles.items(): + ids = [x['id'] for x in tiles] + w = 1 + h = 1 + for curr, next in zip(ids, ids[1:]): + if next > curr + 1: + h += 1 + elif h == 1: + w += 1 + writer.output(f"case {self.to_enum(key)}:\n") + writer.indent() + writer.output(f"if (outWidth) *outWidth = {w};\n") + writer.output(f"if (outHeight) *outHeight = {h};\n") + writer.output("break;\n") + writer.unindent() + writer.output("default:\n") + writer.indent() + writer.output("if (outWidth) *outWidth = 0;\n") + writer.output("if (outHeight) *outHeight = 0;\n") + writer.output("break;\n") + writer.unindent() + + writer.block_end() + writer.block_end() + writer.empty_line() + + def output_tile_has_anim(self, func_name): + writer = self.writer + writer.output(f"static bool {func_name}(BzTile tile) ") + writer.block_start() + + writer.output("switch (tile) ") + writer.block_start() + ids = sorted([x['id'] for x in self.all_tiles if 'animation' in x]) + for id in ids: + writer.output(f"case {id}:\n") + writer.indent() + writer.output("return true;\n") + writer.unindent() + writer.output("default:\n") + writer.indent() + writer.output("return false;\n") + writer.unindent() + + writer.block_end() + writer.block_end() + writer.empty_line() + + def output_tile_anim_sequence(self, func_name): + writer = self.writer + writer.output(f"static AnimationSequence {func_name}(BzTile tile) ") + writer.block_start() + + writer.output("switch (tile) ") + writer.block_start() + anim_tiles = [x for x in self.all_tiles if 'animation' in x] + for tile in anim_tiles: + anim = tile['animation'] + num_frames = len(anim) + anim_id = min([x['tileid'] for x in anim]) + ret = f"(AnimationSequence) {{.startFrame = {anim_id}, .frameCount = {num_frames}}}" + writer.output(f"case {tile['id']}: return {ret};\n") + writer.output("default:\n") + writer.indent() + writer.output("BZ_ASSERT(0);\n") + writer.output("return (AnimationSequence) {0, 0};\n") + writer.unindent() + + writer.block_end() + writer.block_end() + writer.empty_line() + + def output_tile_anim_frame(self, func_name): + writer = self.writer + writer.output(f"static AnimationFrame {func_name}(BzTile tile, i32 frameIdx) ") + writer.block_start() + + writer.output("switch (tile) ") + writer.block_start() + anim_tiles = [x for x in self.all_tiles if 'animation' in x] + for tile in anim_tiles: + anim = tile['animation'] + # num_frames = len(anim) + frames = [str(x['tileid']) for x in anim] + durations = [str(x['duration']) for x in anim] + + anim_frames = [f"{{{frame}, {duration}}}" for frame, duration in zip(frames, durations)] + + ret = f"((AnimationFrame []) {{{', '.join(anim_frames)}}}) [frameIdx]" + writer.output(f"case {tile['id']}: return {ret};\n") + writer.output("default:\n") + writer.indent() + writer.output("BZ_ASSERT(0);\n") + writer.output("return (AnimationFrame) {0, 0};\n") + writer.unindent() + writer.block_end() + writer.block_end() + writer.empty_line() diff --git a/scripts/extract_entities.py b/scripts/extract_entities.py deleted file mode 100644 index 91a3ecd..0000000 --- a/scripts/extract_entities.py +++ /dev/null @@ -1,154 +0,0 @@ -#!/usr/bin/python -""" -Utility script that extracts entities tileset with animations -""" - -import json -import sys -import os -from collections import defaultdict -from extract_common import * - -raw_entities = defaultdict(list) -entities = defaultdict(list) - -content = open("../assets/entities.tsj").read() -tiles = json.loads(content)["tiles"] - -for tile in tiles: - if "type" not in tile: - continue - raw_entities[tile["type"]].append(tile) - -entity_enum_prefix = "ENTITY_" -animation_enum_prefix = "ANIM_" - -entity_enums = {} -animations = set() -entity_animations = {} - - -def entity_enum(type): - return f"{entity_enum_prefix}{type.upper()}" - - -def animation_enum(value): - value = value.split("_")[0] - return f"{animation_enum_prefix}{value.upper()}" - - -def simple_entity(entity): - type = entity["type"] - id = entity["id"] - entity_enums[entity_enum(type)] = id - - -def animated_entity(entity): - simple_entity(entity[0]) - entity_name = entity_enum(entity[0]["type"]) - cur_animations = defaultdict(list) - for frame in entity: - assert entity_name == entity_enum(frame["type"]) - for prop in frame["properties"]: - if prop["name"] != "animation": - continue - animation_name = animation_enum(prop["value"]) - animations.add(animation_name) - cur_animations[animation_name].append(frame["id"]) - break - entity_animations[entity_name] = cur_animations - - -for k, v in raw_entities.items(): - num_frames = len(v) - if num_frames == 1: - simple_entity(v[0]) - else: - animated_entity(v) - - -writer = ExtractFileWriter("entity_types") -writer.header_guard_start() - -writer.include("") - -writer.output_line() - -# ============================ - -writer.enum_dict("EntityType", entity_enums) -writer.enum_list("AnimationType", animations) - -# ============================ - -writer.output("typedef struct AnimationSequence {\n") -writer.indent() -writer.output("i32 startFrame;\n") -writer.output("i32 frameCount;\n") -writer.unindent() -writer.output("} AnimationSequence;\n") -writer.output_line() - -# ============================ - -# ============================ - -writer.output("static bool entityHasAnimation(EntityType entity, AnimationType anim) ") -writer.block_start() - -writer.output("switch (entity) ") -writer.block_start() - -for entity, anims in entity_animations.items(): - writer.output(f"case {entity}: ") - writer.block_start() - - writer.output("switch (anim) ") - writer.block_start() - for anim in anims: - writer.output(f"case {anim}:") - writer.output_line() - writer.indent() - writer.output("return true;\n") - writer.unindent() - writer.block_end() - - writer.output("break;\n") - writer.block_end() - -writer.block_end() - -writer.output("return false;\n") -writer.block_end() - -writer.output_line() -# ============================ - -writer.output("static AnimationSequence getEntityAnimation(EntityType entity, AnimationType anim) ") -writer.block_start() -writer.output("switch (entity) ") -writer.block_start() -for entity, anims in entity_animations.items(): - writer.output(f"case {entity}: ") - writer.block_start() - - writer.output("switch (anim) ") - writer.block_start() - for anim in anims: - ids = sorted(anims[anim]) - for id0, id1 in zip(ids, ids[1:]): - assert id1 - id0 == 1 - writer.output(f"case {anim}: return (AnimationSequence) {{{min(ids)}, {len(ids)}}};") - writer.output_line() - writer.block_end() - - writer.block_end() -writer.block_end() -writer.output("BZ_ASSERT(0);\n") -writer.block_end() -writer.output_line() - -# ============================ - -writer.header_guard_stop() - diff --git a/scripts/extract_tileset.py b/scripts/extract_tileset.py index 0eb1717..8a143e3 100644 --- a/scripts/extract_tileset.py +++ b/scripts/extract_tileset.py @@ -1,10 +1,9 @@ import json +import os.path from extract_common import * -writer = ExtractFileWriter("") - -writer.include("") +writer = ExtractFileWriter("game_tileset") def extract_by_property(tiles, key): @@ -21,13 +20,39 @@ def extract_by_property(tiles, key): content = open("../rawAssets/game.tsj").read() tiles = json.loads(content)["tiles"] -terrain = extract_by_property(tiles, "terrain") -building = extract_by_property(tiles, "building") -entity = extract_by_property(tiles, "entity") -print(terrain) - - -writer.output(f"// This file was generated by: {__file__}\n\n") - +terrain_tiles = extract_by_property(tiles, "terrain") +building_tiles = extract_by_property(tiles, "building") +entity_tiles = extract_by_property(tiles, "entity") + +writer.header_guard_start() +script_name = os.path.basename(__file__) +writer.output(f"// This file was generated by: {script_name}\n\n") +writer.include("") +writer.include("") + +writer.empty_line() +writer.output_anim_sequence_struct() +writer.output_anim_frame_struct() + +terrain_writer = EnumWriter(writer, "terrain", terrain_tiles) +building_writer = EnumWriter(writer, "building", building_tiles) +entity_writer = EnumWriter(writer, "entity", entity_tiles) + +terrain_writer.output_enum() +terrain_writer.output_tile_has_anim("terrainHasAnimation") +terrain_writer.output_tile_anim_sequence("terrainGetAnimationSequence") +terrain_writer.output_tile_anim_frame("terrainGetAnimationFrame") +writer.empty_line() + +building_writer.output_enum() +building_writer.output_tile_to_enum("getTileBuilding") +building_writer.output_enum_to_tile("getBuildingTile") +building_writer.output_str_to_enum("getBuildingFromStr") +building_writer.output_enum_to_str("getBuildingStr") +building_writer.output_enum_tile_size("getBuildingSize") +writer.empty_line() + +writer.header_guard_stop() +writer.to_file("../game/game_tileset.h") diff --git a/scripts/extract_tileset_classes.py b/scripts/extract_tileset_classes.py deleted file mode 100755 index 5a23071..0000000 --- a/scripts/extract_tileset_classes.py +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/python -""" -Utility script that extracts tiles with the same class and -outputs C enum and functions for them (so there is no string checking). -""" - -import json -import sys -import os -from collections import defaultdict - -types = defaultdict(list) - -path = sys.argv[1] -content = open(path).read() - -tiles = json.loads(content)["tiles"] - -for tile in tiles: - if "type" not in tile: - continue - id = tile["id"] - type = tile["type"] - types[type].append(id) - -enum_type = os.path.basename(path).split(".")[0].capitalize() -enum_name = enum_type.upper() -if sys.argv[2]: - enum_type = sys.argv[2] -fun_name = enum_type -if sys.argv[3]: - fun_name = sys.argv[3] -enum_prefix = enum_name -if sys.argv[4]: - enum_prefix = sys.argv[4] - -indent_level = 0 -indent_offset = 4 - - -def indent(): - return ' ' * indent_level - - -def enum_string(name): - return f"{enum_prefix}_{name.upper()}" - - -# ============================ -header_guard = f"{enum_type.upper()}_H" -print(f"#ifndef {header_guard}") -print(f"#define {header_guard}") -script_name = os.path.basename(__file__) -print(f"// This file was generated by: {script_name}") -print() -print("#include ") -print("#include ") -print() -print() - -# ============================ - -enum_none = enum_string("none") -enum_count = enum_string("count") - -print(f"{indent()}typedef enum {enum_type} {{") -indent_level += indent_offset -print(f"{indent()}{enum_none},") -for enum in types: - print(f"{indent()}{enum_string(enum)},") -print(f"{indent()}{enum_count}") -indent_level -= indent_offset -print(f"{indent()}}} {enum_type};") - -print() -print() - -# ============================ - -print(f"{indent()}static {enum_type} getTile{fun_name}(BzTile tile) {{") -indent_level += indent_offset -print(f"{indent()}switch (tile) {{") -for enum, ids in types.items(): - indent_level += indent_offset - for id in ids: - print(f"{indent()}case {id}:") - - indent_level += indent_offset - print(f"{indent()} return {enum_string(enum)};") - indent_level -= indent_offset - - indent_level -= indent_offset - -indent_level += indent_offset -print(f"{indent()}default:") -indent_level += indent_offset -print(f"{indent()}return {enum_string('none')};") -indent_level -= indent_offset -indent_level -= indent_offset -print(f"{indent()}}}") -indent_level -= indent_offset -print(f"{indent()}}}") - -print() - -# ============================ - -print(f"{indent()}static BzTile get{fun_name}Tile({enum_type} type) {{") -indent_level += indent_offset -print(f"{indent()}switch (type) {{") -indent_level += indent_offset -for type, ids in types.items(): - min_id = min(ids) - print(f"{indent()}case {enum_string(type)}: return {min_id};") - -print(f"{indent()}default: return -1;") -indent_level -= indent_offset -print(f"{indent()}}}") - -indent_level -= indent_offset - -print(f"{indent()}}}") -print() - -# ============================ - -print(f"{indent()}static {enum_type} get{fun_name}FromStr(const char *str) {{") -indent_level += indent_offset - -# trie would be much better -for type in types: - print(f"{indent()}if (strncmp(\"{type}\", str, {len(type)}) == 0) return {enum_string(type)};") -print(f"{indent()}else return {enum_string('none')};") - -indent_level -= indent_offset -print(f"{indent()}}}") - -# ============================ - -print(f"{indent()}static const char *get{fun_name}Str({enum_type} type) {{") -indent_level += indent_offset - -print(f"{indent()}switch (type) {{") -indent_level += indent_offset - -print(f"{indent()}case {enum_string('none')}: return \"none\";") -for type in types: - print(f"{indent()}case {enum_string(type)}: return \"{type}\";") -print(f"{indent()}default: return NULL;") -indent_level -= indent_offset -print(f"{indent()}}}") - -indent_level -= indent_offset -print(f"{indent()}}}") -print() - - -# ============================ - -print(f"{indent()}static void get{fun_name}Size({enum_type} type, BzTile *outWidth, BzTile *outHeight) {{") -indent_level += indent_offset -print(f"{indent()} switch (type) {{") -indent_level += indent_offset -for enum, ids in types.items(): - print(f"{indent()}case {enum_string(enum)}:") - indent_level += indent_offset - w = 1 - h = 1 - for curr, next in zip(ids, ids[1:]): - if next > curr + 1: - h += 1 - elif h == 1: - w += 1 - print(f"{indent()}if (outWidth) *outWidth = {w};") - print(f"{indent()}if (outHeight) *outHeight = {h};") - print(f"{indent()}break;") - indent_level -= indent_offset - -print(f"{indent()}default:") -indent_level += indent_offset -print(f"{indent()}if (outWidth) *outWidth = 0;") -print(f"{indent()}if (outHeight) *outHeight = 0;") -print(f"{indent()}break;") -indent_level -= indent_offset -indent_level -= indent_offset -print(f"{indent()}}}") - - -indent_level -= indent_offset -indent_level -= indent_offset - -indent_level -= indent_offset -print(f"{indent()}}}") -print() - - -# ============================ - -print(f"#endif //{header_guard}") \ No newline at end of file