package assets_gen import os "core:os/os2" import "core:fmt" import "core:encoding/json" import "core:path/filepath" import "core:strings" import "core:math/bits" import "core:strconv" Rect :: struct { start: [2]f32, size: [2]f32, } Json_Map :: struct { backgroundcolor: string, class: string, compressionlevel: i32, width: i32, height: i32, infinite: bool, layers: []Json_Layer, nextlayerid: i32, nextobjectid: i32, orientation: string, parallaxoriginx: f64, parallaxoriginy: f64, properties: []Json_Property, renderorder: string, staggeraxis: string, staggerindex: string, tilewidth: i32, tileheight: i32, tilesets: []Json_Tileset, } Json_Layer :: struct { class: string, compression: string, data: union { []i32, string, }, draworder: string, encoding: string, id: i32, image: string, image_width: i32, image_height: i32, layers: []Json_Layer, // locked: bool, name: string, objects: []Json_Object, offsetx: f64, offsety: f64, opacity: f32, parallaxx: f64, parallaxy: f64, properties: []Json_Property, repeatx: bool, repeaty: bool, tintcolor: string, transparentcolor: string, type: string, visible: bool, x: i32, y: i32, width: i32, height: i32, } Json_Object :: struct { ellipse: bool, gid: u32, width: f64, height: f64, id: i32, name: string, point: bool, polygon: []Json_Point, polyline: []Json_Point, properties: []Json_Property, rotation: f64, template: string, text: Maybe(struct { fontfamily: string, color: string, text: string, halign: string, valign: string, pixelsize: i32, wrap: bool, underline: bool, bold: bool, italic: bool, kerning: bool, strikeout: bool, }), type: string, visible: bool, x: f64, y: f64, } Json_Tileset :: struct { background_color: string, class: string, rows: i32, columns: i32, fillmode: string, firstgid: i32, grid: Maybe(Json_Grid), image: string, imagewidth: i32, imageheight: i32, margin: i32, name: string, objectalignment: string, properties: []Json_Property, source: string, spacing: i32, terrains: []Json_Terrain, tilecount: i32, tilewidth: i32, tileheight: i32, tileoffset: Json_Tile_Offset, tilerendersize: string, tiles: []Json_Tile, transformations: Json_Transformations, transparentcolor: string, type: string, wangsets: []Json_Wang_Set, } Json_Grid :: struct { width: i32, height: i32, orientation: string, } Json_Tile_Offset :: struct { x: i32, y: i32, } Json_Transformations :: struct { hflip: bool, vflip: bool, rotate: bool, preferuntransformed: bool, } Json_Tile :: struct { animation: []Json_Frame, id: i32, image: string, imagewidth: i32, imageheight: i32, x: i32, y: i32, width: i32, height: i32, objectgroup: Json_Layer, probability: f64, properties: []Json_Property, terrain: []i32, type: string, } Json_Frame :: struct { duration: i32, tileid: i32, } Json_Terrain :: struct { name: string, properties: []Json_Property, tile: i32, } Json_Wang_Set :: struct { class: string, colors: []Json_Wang_Color, name: string, properties: []Json_Property, tile: i32, type: string, wangtiles: []Json_Wang_Tile, } Json_Wang_Color :: struct { class: string, color: string, name: string, probability: f64, properties: []Json_Property, tile: i32, } Json_Wang_Tile :: struct { tileid: i32, wangid: []u8, } Json_Object_Template :: struct { tileset: Maybe(Json_Tileset), object: Json_Object, } Json_Property :: struct { name: string, type: string, propertytype: string, value: union{ bool, int, string, f32, }, } Json_Point :: struct { x: f64, y: f64, } Json_World_Room :: struct { file_name: string `json:"fileName"`, x: i32, y: i32, width: i32, height: i32, } Json_World :: struct { maps: []Json_World_Room, } Gids :: struct { first: u32, last: u32, } tileset_gids: map[string]Gids load_json_color :: proc(hex_str: string) -> [4]f32 { if len(hex_str) == 0 { return {0, 0, 0, 1} } hex, hex_ok := strconv.parse_u64(hex_str[1:], 16) if !hex_ok { return {0, 0, 0, 1} } a := u8((hex >> 24) & 0xFF) r := u8((hex >> 16) & 0xFF) g := u8((hex >> 8) & 0xFF) b := u8(hex & 0xFF) if len(hex_str[1:]) == 2*3 { // If there wasn't an alpha channel included, assume max a = bits.U8_MAX } return { f32(r) / bits.U8_MAX, f32(g) / bits.U8_MAX, f32(b) / bits.U8_MAX, f32(a) / bits.U8_MAX, } } load_json_world :: proc(path: string) { json_text, read_err := os.read_entire_file(path, context.temp_allocator) if read_err != nil { die("Could not load world %v (%v)", path, read_err) } json_dat: Json_World unmarshal_err := json.unmarshal( json_text, &json_dat, allocator = context.temp_allocator, ) if unmarshal_err != nil { die("Could not parse world %v (%v)", path, unmarshal_err) } for room in json_dat.maps { room_name := strings.to_ada_case( filepath.stem(room.file_name), allocator = context.temp_allocator, ) line := fmt.aprintf( "{{id=.%v, x=%w, y=%w, width=%w, height=%w}}", room_name, room.x, room.y, room.width, room.height, ) append(&world, line) } } create_tile :: proc( image_enum: string, rect: Rect, collisions: []Rect, ) -> string { line := fmt.aprintf( "{{image=.%v, rect=%w, id=%w, collisions=%w}}", image_enum, rect, len(tiles), collisions, ) append(&tiles, line) return line } load_json_imageset :: proc(path: string, json_dat: Json_Tileset) { last_id: i32 = 0 for tile in json_dat.tiles { image_enum := strings.to_ada_case( filepath.stem(tile.image), context.temp_allocator, ) collisions := make([dynamic]Rect, context.temp_allocator) for object in tile.objectgroup.objects { append( &collisions, Rect{ start = {f32(object.x), f32(object.y)}, size = {f32(object.width), f32(object.height)} }, ) } // Fill in any gaps with dummy tiles // TODO: Find a solution that doesn't involve filling my exe with zeroed // data for i in 1..<(tile.id - last_id) { append(&tiles, "") } create_tile( image_enum, Rect{ start = {0, 0}, size = {f32(tile.imagewidth), f32(tile.imageheight)}, }, collisions[:], ) last_id = tile.id } } load_json_tileset :: proc(path: string) { if filepath.stem(path) in loaded_tilesets { return } loaded_tilesets[filepath.stem(path)] = {} json_text, read_err := os.read_entire_file(path, context.temp_allocator) if read_err != nil { die("Could not load tileset %v (%v)", path, read_err) } json_dat: Json_Tileset unmarshal_err := json.unmarshal( json_text, &json_dat, allocator = context.temp_allocator, ) if unmarshal_err != nil { die("Failed to parse tileset %v (%v)", path, unmarshal_err) } first_gid := u32(len(tiles)) tileset_gids[filepath.stem(path)] = { first = first_gid, last = u32(len(tiles) + int(json_dat.tilecount)), } // No image found; this is a image set if json_dat.imagewidth == 0 && json_dat.imageheight == 0 && json_dat.image == "" { load_json_imageset(path, json_dat) gids := &tileset_gids[filepath.stem(path)] gids.last = u32(len(tiles)) return } image_enum := strings.to_ada_case( filepath.stem(json_dat.image), allocator = context.temp_allocator, ) collisions := make([][dynamic]Rect, json_dat.tilecount) defer { for collision in collisions { delete(collision) } delete(collisions) } for tile in json_dat.tiles { collision := &collisions[tile.id] for object in tile.objectgroup.objects { append( collision, Rect{ start = {f32(object.x), f32(object.y)}, size = {f32(object.width), f32(object.height)} } ) } } tiles_x := json_dat.imagewidth / json_dat.tilewidth tiles_y := json_dat.imageheight / json_dat.tileheight tile_size := [2]f32{f32(json_dat.tilewidth), f32(json_dat.tileheight)} for y in 0.. string { cwd, cwd_err := os.get_working_directory(context.temp_allocator) assert(cwd_err == nil) room_dir, room_dir_err := filepath.rel( cwd, filepath.dir(room_path, context.temp_allocator), context.temp_allocator, ) assert(room_dir_err == nil) pos: [2]f32 size: [2]f32 tile_id: Maybe(u32) obj_type := obj.type if obj.type != "" else "prop" type_name := strings.to_ada_case(obj_type) properties: Properties defer delete(properties) if obj.template != "" { rel, err := filepath.join( {room_dir, obj.template}, context.temp_allocator, ) assert(err == nil) template_dir := filepath.dir(rel, context.temp_allocator) json_text, read_err := os.read_entire_file(rel, context.temp_allocator) if read_err != nil { die("Could not load template %v (%v)", rel, read_err) } template: Json_Object_Template unmarshal_err := json.unmarshal( json_text, &template, allocator = context.temp_allocator, ) tobj := template.object if obj.type == "" && tobj.type != "" { delete(type_name) obj_type := tobj.type if tobj.type != "" else "prop" type_name = strings.to_ada_case(obj_type) } pos = {f32(tobj.x), f32(tobj.y)} size = {f32(tobj.width), f32(tobj.height)} load_properties(&properties, tobj, template_dir) if tobj.gid != 0 { flip_flags := (tobj.gid & 0x80000000) >> 28 if flip_flags & 0b1000 != 0 { size.x *= -1 } if flip_flags & 0b0100 != 0 { size.y *= -1 } tiled_id := (tobj.gid << 4) >> 4 tile_id = tiled_to_real_gid[i32(tiled_id)] } } pos = [2]f32{f32(obj.x), f32(obj.y)} size = [2]f32{f32(obj.width), f32(obj.height)} load_properties(&properties, obj, room_dir) if obj.gid != 0 { flip_flags := (obj.gid & 0x80000000) >> 28 if flip_flags & 0b1000 != 0 { size.x *= -1 } if flip_flags & 0b0100 != 0 { size.y *= -1 } tiled_id := (obj.gid << 4) >> 4 tile_id = tiled_to_real_gid[i32(tiled_id)] } // `object_type_names` now owns `type_name`; freed in main if type_name in object_type_names { key, _ := delete_key(&object_type_names, type_name) delete(key) } object_type_names[type_name] = {} return fmt.tprintf( "{{type = .%v, tile_id = %v, pos = %w, size = %w, parallax = %w, properties = %w}}", type_name, tile_id, pos, size, parallax, properties, ) } load_json_room :: proc(path: string, file: ^os.File) { json_text, read_err := os.read_entire_file(file, context.temp_allocator) if read_err != nil { die("Could not load room %v (%v)", path, read_err) } json_dat: Json_Map unmarshal_err := json.unmarshal( json_text, &json_dat, allocator = context.temp_allocator, ) if unmarshal_err != nil { die("Failed to parse room %v (%v)", path, unmarshal_err) } width := json_dat.width height := json_dat.height tile_width := json_dat.tilewidth tile_height := json_dat.tileheight color := load_json_color(json_dat.backgroundcolor) tiled_to_real_gid: map[i32]u32 defer delete(tiled_to_real_gid) // map tiled's map-based GIDs to my actually global GIDs id: i32 = 1 for tileset in json_dat.tilesets { tileset_name := filepath.stem(tileset.source) gid, exists := tileset_gids[tileset_name] if !exists { load_json_tileset(strings.concatenate({ filepath.dir(path, context.temp_allocator), "/", tileset.source, })) gid = tileset_gids[tileset_name] } for i in gid.first..