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 def unindent(self): 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("BzTileID 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("BzTileID frame;\n") self.output("f32 duration;\n") self.unindent() self.output("} AnimationFrame;\n") self.empty_line() def header_guard_start(self): self.content += f"#ifndef {self.name}_H\n" self.content += f"#define {self.name}_H\n" self.content += "\n" def header_guard_stop(self): self.content += f"#endif // {self.name}_H\n" def include(self, header): self.content += f"{self.indention}#include {header}\n" def enum_start(self, name): self.content += f"{self.indention}typedef enum {name} {{\n" self.indent() def enum_stop(self, name): self.unindent() self.content += f"}} {name};\n\n" def enum_list(self, name, enums): self.enum_start(name) for enum in enums: if enum.endswith("_NONE"): self.content += f"{self.indention}{enum} = -1,\n" else: 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(): self.content += f"{self.indention}{enum} = {v},\n" self.enum_stop(name) def output(self, out): self.content += self.indention self.content += out def empty_line(self): self.content += '\n' def only_output(self, out): self.content += out def block_start(self): self.content += f"{{\n" self.indent() def block_end(self): self.unindent() 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, tiles, prefix, anim_prefix=None): self.writer = writer self.prefix = prefix self.all_tiles = tiles self.tiles = group_by_class(tiles) self.enums = [] self.enums.append(self.to_enum("none")) self.enums += sorted([self.to_enum(x) for x in self.tiles.keys()]) self.enums.append(self.to_enum("count")) self.enum_type = f"{prefix.capitalize()}Type" if anim_prefix: self.anim_prefix = anim_prefix self.anim_type = f"{anim_prefix.capitalize()}Type" self.anim_map = defaultdict(lambda: defaultdict(list)) self.animations = [] self.all_tiles = tiles animations = [] for tile in self.all_tiles: if 'animation' not in tile: continue if 'properties' not in tile: continue if 'type' not in tile: continue enum = self.to_enum(tile['type']) props = tile['properties'] anim_type = [prop['value'] for prop in props if prop['name'] == 'animation'][0] anim_type = self.to_anim_enum(anim_type) animations.append(anim_type) self.anim_map[enum][anim_type] = tile['animation'] animations.append(self.to_anim_enum("count")) animations.append(self.to_anim_enum("none")) anim_seen = set() anim_add = anim_seen.add self.animations = [x for x in animations if not (x in anim_seen or anim_add(x))] def to_enum(self, name): return f"{self.prefix.upper()}_{name.upper()}" def to_anim_enum(self, name): return f"{self.anim_prefix.upper()}_{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}(BzTileID 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 BzTileID {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, i32 *outWidth, i32 *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}(BzTileID 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}(BzTileID 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}(BzTileID 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 = [x['duration'] * 0.001 for x in anim] anim_frames = [f"{{{frame}, {format(duration, '.4f')}f}}" 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) {-1, -1.0f};\n") writer.unindent() writer.block_end() writer.block_end() writer.empty_line() def output_anim_enum(self): writer = self.writer writer.enum_list(self.anim_type, self.animations) def output_anim_enum_to_str(self, func_name): writer = self.writer writer.output(f"static const char *{func_name}({self.anim_type} type) ") writer.block_start() writer.output("switch (type) ") writer.block_start() for name in self.animations: writer.output(f"case {name}: return \"{name}\";\n") writer.output(f"default: return NULL;\n") writer.block_end() writer.block_end() writer.empty_line() def output_has_anim(self, func_name): writer = self.writer writer.output(f"static bool {func_name}({self.enum_type} entity, {self.anim_type} type) ") writer.block_start() writer.output("switch (entity) ") writer.block_start() for entity, anims in self.anim_map.items(): writer.output(f"case {entity}:\n") writer.indent() writer.output("switch (type) ") writer.block_start() for anim in anims: writer.output(f"case {anim}:\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.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_anim_sequence(self, func_name): writer = self.writer writer.output(f"static AnimationSequence {func_name}({self.enum_type} entity, {self.anim_type} type) ") writer.block_start() writer.output("switch (entity) ") writer.block_start() for entity, animation_types in self.anim_map.items(): writer.output(f"case {entity}:\n") writer.indent() writer.output("switch (type) ") writer.block_start() for anim_type in animation_types: anim = self.anim_map[entity][anim_type] num_frames = len(anim) anim_id = anim[0]['tileid'] ret = f"(AnimationSequence) {{.startFrame = {anim_id}, .frameCount = {num_frames}}}" writer.output(f"case {anim_type}: return {ret};\n") writer.output("default: break;\n") writer.block_end() writer.unindent() writer.output("default: break;\n") writer.block_end() writer.output("BZ_ASSERT(0);\n") writer.output("return (AnimationSequence) {0, 0};\n") writer.block_end() writer.empty_line() def output_anim_frame(self, func_name): writer = self.writer writer.output( f"static AnimationFrame {func_name}({self.enum_type} entity, {self.anim_type} type, i32 frameIdx) ") writer.block_start() writer.output("switch (entity) ") writer.block_start() for entity, animation_types in self.anim_map.items(): writer.output(f"case {entity}:\n") writer.indent() writer.output("switch (type) ") writer.block_start() for anim_type in animation_types: anim = self.anim_map[entity][anim_type] frames = [str(x['tileid']) for x in anim] durations = [x['duration'] * 0.001 for x in anim] anim_frames = [f"{{{frame}, {format(duration, '0.4f')}f}}" for frame, duration in zip(frames, durations)] ret = f"((AnimationFrame []) {{{', '.join(anim_frames)}}}) [frameIdx]" writer.output(f"case {anim_type}: return {ret};\n") writer.output("default: break;\n") writer.block_end() writer.unindent() writer.output("default: break;\n") writer.block_end() writer.output("BZ_ASSERT(0);\n") writer.output("return (AnimationFrame) {-1, -1.0f};\n") writer.block_end() writer.empty_line() def filter_by_props(self, tiles, prop_names): filtered = [] for tile in tiles: if 'properties' not in tile: continue props = tile['properties'] names = prop_names[:] for prop in props: if prop['name'] in names: names.remove(prop['name']) if len(names) == 0: filtered.append(tile) return filtered def get_property(self, tile, prop_name): if 'properties' not in tile: return None props = tile['properties'] for prop in props: if prop['name'] == prop_name: return prop return None def output_get_text_rect(self, func_name, width, tile_width, tile_height): writer = self.writer writer.output( f"static Rectangle {func_name}(BzTileID tile) ") writer.block_start() writer.output(f"Rectangle base = {{ (tile % {width}) * {tile_width}, " f"(tile / {width}) * {tile_height}, {tile_width}, {tile_height} }};\n") sized_tiles = {} for tile in self.all_tiles: if 'properties' not in tile: continue props = tile['properties'] size_x = 1 size_y = 1 for prop in props: if prop['name'] == 'size_x': size_x = int(prop['value']) if prop['name'] == 'size_y': size_y = int(prop['value']) if size_x == 1 and size_y == 1: continue sized_tiles[tile['id']] = (size_x, size_y) sized_map = defaultdict(list) for k, v in sized_tiles.items(): sized_map[v].append(k) if sized_map: writer.output("switch (tile) ") writer.block_start() for k, v in sized_map.items(): for id in v: writer.output(f"case {id}:\n") writer.indent() x, y = k writer.output(f"base.width *= {x};\n") writer.output(f"base.height *= {y};\n") writer.output("break;\n") writer.unindent() writer.output("default: break;\n") writer.block_end() writer.output("return base;\n") writer.block_end() writer.empty_line() def output_base_index(self, func_name, field): writer = self.writer tiles = self.filter_by_props(self.all_tiles, [field]) field_map = defaultdict(list) for tile in tiles: index = self.get_property(tile, field)['value'] field_map[index].append(tile['id']) sizes = list(map(lambda x: len(x), field_map.values())) assert len(set(sizes)) == 1 indices = list(zip(*field_map.values())) writer.output( f"static BzTileID {func_name}(BzTileID tile) ") writer.block_start() writer.output("switch (tile) ") writer.block_start() for index in indices: for idx in index: writer.output(f"case {idx}:\n") writer.indent() writer.output(f"return {index[0]};\n") writer.unindent() writer.output("default: return tile;\n") writer.block_end() writer.block_end() writer.empty_line() def output_index_tile_offset(self, func_name, field, width, tile_width, tile_height): writer = self.writer tiles = self.filter_by_props(self.all_tiles, [field]) field_map = defaultdict(list) for tile in tiles: index = self.get_property(tile, field)['value'] field_map[index].append(tile['id']) sizes = list(map(lambda x: len(x), field_map.values())) assert len(set(sizes)) == 1 indices = list(zip(*field_map.values())) writer.output( f"static Vector2 {func_name}(BzTileID tile, i32 idx) ") writer.block_start() size = sizes[0] writer.output(f"if (idx < 0 || idx >= {size}) return (Vector2) {{0, 0}};\n") writer.output("switch (tile) ") writer.block_start() def calc_pos(tile): return (tile % width) * tile_width, (tile // width) * tile_height for index in indices: base_idx = index[0] base_pos = calc_pos(base_idx) positions = [(0, 0)] for idx in index[1:]: pos = calc_pos(idx) positions.append((pos[0] - base_pos[0], pos[1] - base_pos[1])) str_positions = list(map(lambda x: f"{{{x[0]}, {x[1]}}}", positions)) ret = f"((Vector2 []) {{{', '.join(str_positions)}}})[idx]" writer.output(f"case {base_idx}: return {ret};\n") writer.output("default: return (Vector2) {0, 0};\n") writer.block_end() writer.block_end() writer.empty_line()