aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXander Swan <no email>2025-12-23 20:58:32 -0500
committerXander Swan <no email>2025-12-23 20:58:32 -0500
commit2838358993ade2ac1b770d675af9126749074e8f (patch)
treeb423ac832dd86cec1863c725fd2957eddd83c28f
parentce4d64bd41937d7dff18ca607122188dc338d696 (diff)
Fix lotsa memory leaks mhm
-rw-r--r--editor/editor.odin32
-rwxr-xr-xeditor/run.sh3
-rw-r--r--res/room_begin.tmj51
-rw-r--r--res/tileset.tsj125
-rw-r--r--res/tilesets.pngbin0 -> 2904 bytes
-rw-r--r--res/tilesets.qoibin3135 -> 2998 bytes
-rw-r--r--res/world.tiled-project14
-rw-r--r--res/world.tiled-session26
-rwxr-xr-xrun.sh2
-rw-r--r--src/draw/draw.odin11
-rw-r--r--src/main.odin57
-rw-r--r--src/phys/world.odin3
-rw-r--r--src/player.odin2
-rw-r--r--src/tiled/json.odin208
-rw-r--r--src/tiled/tiled.odin585
15 files changed, 1066 insertions, 53 deletions
diff --git a/editor/editor.odin b/editor/editor.odin
deleted file mode 100644
index f508514..0000000
--- a/editor/editor.odin
+++ /dev/null
@@ -1,32 +0,0 @@
-package editor
-
-import rl "vendor:raylib"
-import mu "vendor:microui"
-
-state := struct{
- muctx: mu.Context
-}
-
-main :: proc() {
- rl.InitWindow(800, 400, "Editor")
- defer rl.CloseWindow()
-
- ctx := &state.muctx
- mu.init(ctx)
-
- ctx.text_width = mu.default_atlas_text_width
- ctx.text_height = mu.default_atlas_text_height
-
- atlas_texture = rl.LoadRenderTexture(int(mu.DEFAULT_ATLAS_WIDTH), int(mu.DEFAULT_ATLAS_HEIGHT))
- defer rl.UnloadRenderTexture(state.atlas_texture)
-
- for rl.WindowShouldClose() {
- mu.begin(ctx)
- mu.label(ctx, "Test")
- mu.end(ctx)
-
- rl.BeginDrawing()
-
- rl.EndDrawing()
- }
-}
diff --git a/editor/run.sh b/editor/run.sh
deleted file mode 100755
index 5c479e9..0000000
--- a/editor/run.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#! /bin/bash
-
-odin run editor -vet -disallow-do -o:speed
diff --git a/res/room_begin.tmj b/res/room_begin.tmj
new file mode 100644
index 0000000..1a294fc
--- /dev/null
+++ b/res/room_begin.tmj
@@ -0,0 +1,51 @@
+{ "compressionlevel":-1,
+ "height":20,
+ "infinite":false,
+ "layers":[
+ {
+ "data":[40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 27, 31, 31, 19, 19, 28,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 21, 31, 31, 31, 11, 0, 0, 0, 0, 16,
+ 40, 40, 40, 40, 40, 40, 40, 40, 27, 31, 19, 31, 19, 19, 19, 31, 19, 31, 19, 19, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 40, 40, 40, 40, 21, 31, 31, 31, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 40, 40, 21, 19, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5,
+ 40, 21, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7,
+ 27, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7,
+ 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33,
+ 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33,
+ 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7,
+ 32, 0, 0, 0, 0, 0, 0, 5, 9, 20, 9, 20, 20, 20, 18, 20, 20, 18, 18, 9, 18, 9, 18, 6, 0, 0, 0, 0, 0, 33,
+ 32, 0, 0, 0, 0, 0, 0, 7, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 8, 0, 0, 0, 0, 0, 7,
+ 11, 0, 0, 0, 0, 0, 0, 7, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 38, 9, 9, 42, 0, 0, 7,
+ 0, 0, 0, 5, 9, 20, 9, 44, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 21, 19, 19, 17, 0, 0, 33,
+ 0, 0, 0, 7, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 32, 0, 0, 0, 0, 0, 33,
+ 20, 9, 18, 39, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 27, 17, 0, 41, 18, 20, 20, 44,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 27, 17, 0, 0, 33, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 8, 0, 41, 20, 44, 40, 40, 40, 40],
+ "height":20,
+ "id":1,
+ "name":"Tile Layer 1",
+ "opacity":1,
+ "type":"tilelayer",
+ "visible":true,
+ "width":30,
+ "x":0,
+ "y":0
+ }],
+ "nextlayerid":2,
+ "nextobjectid":1,
+ "orientation":"orthogonal",
+ "renderorder":"right-down",
+ "tiledversion":"1.11.2",
+ "tileheight":16,
+ "tilesets":[
+ {
+ "firstgid":1,
+ "source":"tileset.tsj"
+ }],
+ "tilewidth":16,
+ "type":"map",
+ "version":"1.10",
+ "width":30
+}
diff --git a/res/tileset.tsj b/res/tileset.tsj
new file mode 100644
index 0000000..2f717e9
--- /dev/null
+++ b/res/tileset.tsj
@@ -0,0 +1,125 @@
+{ "backgroundcolor":"#444444",
+ "columns":11,
+ "image":"tilesets.png",
+ "imageheight":64,
+ "imagewidth":176,
+ "margin":0,
+ "name":"tileset",
+ "spacing":0,
+ "tilecount":44,
+ "tiledversion":"1.11.2",
+ "tileheight":16,
+ "tilewidth":16,
+ "type":"tileset",
+ "version":"1.10",
+ "wangsets":[
+ {
+ "colors":[
+ {
+ "color":"#ff0000",
+ "name":"",
+ "probability":1,
+ "tile":-1
+ }],
+ "name":"Carrabassett",
+ "tile":-1,
+ "type":"corner",
+ "wangtiles":[
+ {
+ "tileid":4,
+ "wangid":[0, 0, 0, 1, 0, 0, 0, 0]
+ },
+ {
+ "tileid":5,
+ "wangid":[0, 0, 0, 0, 0, 1, 0, 0]
+ },
+ {
+ "tileid":6,
+ "wangid":[0, 1, 0, 1, 0, 0, 0, 0]
+ },
+ {
+ "tileid":7,
+ "wangid":[0, 0, 0, 0, 0, 1, 0, 1]
+ },
+ {
+ "tileid":8,
+ "wangid":[0, 0, 0, 1, 0, 1, 0, 0]
+ },
+ {
+ "tileid":9,
+ "wangid":[0, 1, 0, 0, 0, 0, 0, 0]
+ },
+ {
+ "tileid":15,
+ "wangid":[0, 1, 0, 0, 0, 0, 0, 0]
+ },
+ {
+ "tileid":16,
+ "wangid":[0, 0, 0, 0, 0, 0, 0, 1]
+ },
+ {
+ "tileid":17,
+ "wangid":[0, 0, 0, 1, 0, 1, 0, 0]
+ },
+ {
+ "tileid":18,
+ "wangid":[0, 1, 0, 0, 0, 0, 0, 1]
+ },
+ {
+ "tileid":19,
+ "wangid":[0, 0, 0, 1, 0, 1, 0, 0]
+ },
+ {
+ "tileid":20,
+ "wangid":[0, 1, 0, 0, 0, 1, 0, 1]
+ },
+ {
+ "tileid":26,
+ "wangid":[0, 1, 0, 0, 0, 1, 0, 1]
+ },
+ {
+ "tileid":27,
+ "wangid":[0, 1, 0, 1, 0, 0, 0, 1]
+ },
+ {
+ "tileid":28,
+ "wangid":[0, 1, 0, 0, 0, 1, 0, 0]
+ },
+ {
+ "tileid":29,
+ "wangid":[0, 0, 0, 1, 0, 0, 0, 1]
+ },
+ {
+ "tileid":30,
+ "wangid":[0, 1, 0, 0, 0, 0, 0, 1]
+ },
+ {
+ "tileid":31,
+ "wangid":[0, 0, 0, 0, 0, 1, 0, 1]
+ },
+ {
+ "tileid":37,
+ "wangid":[0, 0, 0, 1, 0, 1, 0, 1]
+ },
+ {
+ "tileid":38,
+ "wangid":[0, 1, 0, 1, 0, 1, 0, 0]
+ },
+ {
+ "tileid":39,
+ "wangid":[0, 1, 0, 1, 0, 1, 0, 1]
+ },
+ {
+ "tileid":40,
+ "wangid":[0, 0, 0, 1, 0, 0, 0, 0]
+ },
+ {
+ "tileid":41,
+ "wangid":[0, 0, 0, 0, 0, 1, 0, 0]
+ },
+ {
+ "tileid":42,
+ "wangid":[0, 0, 0, 1, 0, 1, 0, 1]
+ }]
+ }]
+} \ No newline at end of file
diff --git a/res/tilesets.png b/res/tilesets.png
new file mode 100644
index 0000000..4a431fe
--- /dev/null
+++ b/res/tilesets.png
Binary files differ
diff --git a/res/tilesets.qoi b/res/tilesets.qoi
index 9bd31c9..ecc869f 100644
--- a/res/tilesets.qoi
+++ b/res/tilesets.qoi
Binary files differ
diff --git a/res/world.tiled-project b/res/world.tiled-project
new file mode 100644
index 0000000..d0eb592
--- /dev/null
+++ b/res/world.tiled-project
@@ -0,0 +1,14 @@
+{
+ "automappingRulesFile": "",
+ "commands": [
+ ],
+ "compatibilityVersion": 1100,
+ "extensionsPath": "extensions",
+ "folders": [
+ "."
+ ],
+ "properties": [
+ ],
+ "propertyTypes": [
+ ]
+}
diff --git a/res/world.tiled-session b/res/world.tiled-session
new file mode 100644
index 0000000..42585f3
--- /dev/null
+++ b/res/world.tiled-session
@@ -0,0 +1,26 @@
+{
+ "Map/SizeTest": {
+ "height": 4300,
+ "width": 2
+ },
+ "activeFile": "",
+ "expandedProjectPaths": [
+ ],
+ "fileStates": {
+ },
+ "last.imagePath": "/home/xswan/demonchime/res",
+ "map.lastUsedFormat": "json",
+ "map.tileHeight": 16,
+ "map.tileWidth": 16,
+ "openFiles": [
+ ],
+ "project": "world.tiled-project",
+ "property.type": "int",
+ "recentFiles": [
+ ],
+ "tileset.lastUsedFormat": "json",
+ "tileset.tileSize": {
+ "height": 16,
+ "width": 16
+ }
+}
diff --git a/run.sh b/run.sh
index 0c786f7..eb1410a 100755
--- a/run.sh
+++ b/run.sh
@@ -1,2 +1,2 @@
sokol-shdc -i src/draw/default.glsl -o src/draw/default_shader.odin -l glsl430:metal_macos:hlsl5 -f sokol_odin
-odin run src -disallow-do -debug -sanitize:address -show-timings
+odin run src -disallow-do -debug -show-timings
diff --git a/src/draw/draw.odin b/src/draw/draw.odin
index 6761b36..3e80d42 100644
--- a/src/draw/draw.odin
+++ b/src/draw/draw.odin
@@ -18,7 +18,7 @@ Mat4 :: matrix[4, 4]f32
Color :: [4]f32
SCREEN_WIDTH :: 480
-SCREEN_HEIGHT :: 360
+SCREEN_HEIGHT :: 320
renderer: struct {
screen: rl.RenderTexture2D,
@@ -98,10 +98,13 @@ texture_quad :: proc(
rotation: f32 = 0,
scale := Vec2{1, 1},
) {
- // assert(quad.size.x > 0 && quad.size.y > 0)
-
quad := quad
- quad.size *= scale
+ if scale.x < 0 {
+ quad.size.x *= -1
+ }
+ if scale.y < 0 {
+ quad.size.y *= -1
+ }
rl.DrawTexturePro(
img,
diff --git a/src/main.odin b/src/main.odin
index 9a6c09a..6b04e02 100644
--- a/src/main.odin
+++ b/src/main.odin
@@ -8,11 +8,13 @@ import "base:runtime"
import "core:log"
import "core:fmt"
+import "core:mem"
import rl "vendor:raylib"
import "draw"
import "phys"
+import "tiled"
Vec2 :: [2]f32
@@ -24,6 +26,8 @@ Rect :: struct {
state: struct {
player: Player,
platform_list: Entity_List(Platform),
+
+ room: tiled.Map,
}
init :: proc() {
@@ -33,6 +37,12 @@ init :: proc() {
init_player(&state.player)
+ room, err := tiled.load_map("res/room_begin.tmj")
+ if err != .NONE {
+ log.errorf("Error loading map (%v)", err)
+ }
+ state.room = room
+
make_platform(Rect{
start = {50, 50},
size = {64, 20}
@@ -41,7 +51,7 @@ init :: proc() {
i := f32(20)
for i < draw.SCREEN_WIDTH - 20 - 64 {
make_platform(Rect{
- start = {i, 340},
+ start = {i, draw.SCREEN_HEIGHT - 20},
size = {64, 20}
})
i += 65
@@ -52,14 +62,6 @@ init :: proc() {
size = {20, 20}
})
make_platform(Rect{
- start = {240, 280},
- size = {40, 20}
- })
- make_platform(Rect{
- start = {260, 260},
- size = {20, 20}
- })
- make_platform(Rect{
start = {300, 220},
size = {40, 20}
})
@@ -90,23 +92,54 @@ frame :: proc() {
draw.new_frame()
+ draw_platforms()
+ draw_player(state.player)
+
+ tiled.draw_map(state.room)
+
fps_text := fmt.caprintf(
"FPS: %v", rl.GetFPS(),
allocator = context.temp_allocator,
)
rl.DrawText(fps_text, 5, 5, 8, rl.GREEN)
- draw_platforms()
- draw_player(state.player)
-
draw.end_frame()
}
cleanup :: proc() {
+ delete_player(&state.player)
delete_entity_list(state.platform_list)
+ tiled.delete_map(state.room)
+ phys.destroy_world()
}
main :: proc() {
+ when ODIN_DEBUG {
+ track: mem.Tracking_Allocator
+ mem.tracking_allocator_init(&track, context.allocator)
+ context.allocator = mem.tracking_allocator(&track)
+
+ defer {
+ if len(track.allocation_map) > 0 {
+ fmt.eprintf("=== %v allocations not freed: ===\n", len(track.allocation_map))
+ total := 0
+ for _, entry in track.allocation_map {
+ fmt.eprintf(
+ "- %v bytes @ %v (%v, mode %v)\n",
+ entry.size,
+ entry.location,
+ entry.err,
+ entry.mode,
+ )
+ total += entry.size
+ }
+
+ fmt.eprintf("=== a total of %v bytes unfreed ===\n", total)
+ }
+ mem.tracking_allocator_destroy(&track)
+ }
+ }
+
logger := log.create_console_logger()
context.logger = logger
defer log.destroy_console_logger(logger)
diff --git a/src/phys/world.odin b/src/phys/world.odin
index 3154a0b..66479e8 100644
--- a/src/phys/world.odin
+++ b/src/phys/world.odin
@@ -21,6 +21,9 @@ destroy_world :: proc() {
for bin in world.bins {
delete(bin)
}
+ delete(world.handles)
+ delete(world.unused_handles)
+ delete(world.bodies)
}
@(private="file")
diff --git a/src/player.odin b/src/player.odin
index 6c34d14..80672a9 100644
--- a/src/player.odin
+++ b/src/player.odin
@@ -47,7 +47,7 @@ init_player :: proc(p: ^Player) {
}
}
-deinit_player :: proc(p: ^Player) {
+delete_player :: proc(p: ^Player) {
phys.remove_body(p.body_handle)
draw.destroy_sprite(p.sprite)
diff --git a/src/tiled/json.odin b/src/tiled/json.odin
new file mode 100644
index 0000000..874430e
--- /dev/null
+++ b/src/tiled/json.odin
@@ -0,0 +1,208 @@
+package tiled
+
+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_Tile_Set,
+}
+
+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: i32,
+ 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_Tile_Set :: 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_Tile_Set),
+ object: Json_Object,
+}
+
+Json_Property :: struct {
+ name: string,
+ type: string,
+ propertytype: string,
+ value: any,
+}
+
+Json_Point :: struct {
+ x: f64,
+ y: f64,
+}
diff --git a/src/tiled/tiled.odin b/src/tiled/tiled.odin
new file mode 100644
index 0000000..e1627a5
--- /dev/null
+++ b/src/tiled/tiled.odin
@@ -0,0 +1,585 @@
+package tiled
+
+import os "core:os/os2"
+import mem "core:mem"
+import strings "core:strings"
+import "core:path/filepath"
+import "core:log"
+import "core:encoding/json"
+
+import rl "vendor:raylib"
+
+Color :: [4]f32
+
+Error :: enum {
+ NONE = 0,
+ COULD_NOT_LOAD,
+ NON_ORTHOGONAL_NOT_SUPPORTED,
+ INFINITE_NOT_SUPPORTED,
+ ELLIPSE_OBJ_NOT_SUPPORTED,
+ POLYGON_OBJ_NOT_SUPPORTED,
+ POLYLINE_OBJ_NOT_SUPPORTED,
+ TEXT_OBJ_NOT_SUPPORTED,
+}
+
+Axis :: enum {
+ X,
+ Y,
+}
+
+Properties :: map[string]any
+
+Map :: struct {
+ // Currently, only orthogonal maps are allowed. Any non-orthogonal maps
+ // throw an error.
+ // Infinite maps will also throw an error for now.
+
+ background_color: Color,
+ class: string,
+
+ width: i32,
+ height: i32,
+
+ layers: []Layer,
+
+ next_layer_id: i32,
+ next_object_id: i32,
+
+ parallax_origin_x: f64,
+ parallax_origin_y: f64,
+
+ properties: Properties,
+
+ // stagger_axis: Axis,
+ // stagger_offset: i32, // To replace stagger index.
+
+ tile_width: i32,
+ tile_height: i32,
+
+ tile_sets: []Tile_Set,
+
+ tiles: [dynamic]Tile,
+}
+
+Layer :: struct {
+ // Layers that aren't visible are entirely ignored.
+ name: string,
+ properties: Properties,
+
+ parallax_x: f64,
+ parallax_y: f64,
+
+ tint: Color,
+ id: i32,
+
+ layer: union #no_nil {
+ Tile_Layer,
+ Image_Layer,
+ Object_Layer,
+ }
+}
+
+Tile_Layer :: struct {
+ // Data will automatically become uncompressed.
+ width: i32,
+ height: i32,
+ data: []i32,
+}
+
+Image_Layer :: struct {
+ texture: rl.Texture2D,
+ transparent_color: Color,
+ repeat_x: bool,
+ repeat_y: bool,
+}
+
+Object_Layer :: []Object
+
+Object_Type :: enum {
+ POINT,
+ TILE,
+ RECT,
+}
+
+Object :: struct {
+ // For now, anything that *isn't* one of these is ignored and
+ // a warning is thrown:
+ // - Point
+ // - Tile
+ // - Rectangle
+ //
+ // Objects that aren't visible will be ignored entirely.
+
+ type: Object_Type,
+ name: string,
+ class: string,
+ properties: Properties,
+ position: [2]f32,
+ size: [2]f32,
+ tile_id: i32,
+}
+
+Tile_Set :: struct {
+ // For now, any autotiling information is ignored.
+
+ properties: Properties,
+
+ rows: i32,
+ columns: i32,
+
+ first_gid: i32,
+
+ texture: rl.Texture2D,
+ margin: i32,
+ spacing: i32,
+
+ tile_count: i32,
+ tile_width: i32,
+ tile_height: i32,
+ tile_offset: [2]i32, // in pixels
+
+ tiles: []Tile,
+}
+
+Tile :: struct {
+ // For now, animations are ignored. Support in the future is somewhat likely.
+ // And similar to above, autotiling information is ignored.
+
+ is_image_collection: bool,
+
+ tile_set: i32, // index for the tile set in Map
+
+ id: i32,
+ gid: i32,
+
+ texture: Maybe(rl.Texture2D), // If the tile is part of an image collection.
+
+ // Where on the texture is this tile
+ src_start: [2]i32,
+ src_size: [2]i32,
+
+ object_group: Layer, // Object layer for collisions.
+
+ properties: Properties,
+}
+
+load_map :: proc(path: string) -> (Map, Error) {
+ defer free_all(context.temp_allocator)
+
+ jmap_text, read_err := os.read_entire_file(path, context.temp_allocator)
+ if read_err != nil {
+ log.errorf("Failed to read file %v (%v)", path, read_err)
+ return {}, .COULD_NOT_LOAD
+ }
+
+ jmap: Json_Map
+ unmarshal_err := json.unmarshal(
+ jmap_text,
+ &jmap,
+ allocator = context.temp_allocator,
+ )
+ if unmarshal_err != nil {
+ log.errorf("Failed to unmarshal file %v (%v)", path, unmarshal_err)
+ return {}, .COULD_NOT_LOAD
+ }
+
+ log.info(jmap)
+
+ tmap: Map
+ err := convert_json_map(jmap, &tmap, path)
+ if err != .NONE {
+ return {}, err
+ }
+ return tmap, .NONE
+}
+
+delete_map :: proc(tmap: Map) {
+ delete(tmap.class)
+ delete(tmap.properties)
+
+ for layer in tmap.layers {
+ delete_layer(layer)
+ }
+ delete(tmap.layers)
+
+ for tile_set in tmap.tile_sets {
+ delete(tile_set.properties)
+ delete(tile_set.tiles) // all tiles are deleted later
+ rl.UnloadTexture(tile_set.texture)
+ }
+ delete(tmap.tile_sets)
+
+ for tile in tmap.tiles {
+ delete(tile.properties)
+
+ if tile.texture != nil {
+ rl.UnloadTexture(tile.texture.(rl.Texture2D))
+ }
+
+ delete_layer(tile.object_group)
+ }
+ delete(tmap.tiles)
+}
+
+@(private)
+delete_layer :: proc(layer: Layer) {
+ delete(layer.name)
+ delete(layer.properties)
+
+ switch l in layer.layer {
+ case Tile_Layer:
+ delete(l.data)
+ case Image_Layer:
+ rl.UnloadTexture(l.texture)
+ case Object_Layer:
+ for obj in l {
+ delete(obj.name)
+ delete(obj.class)
+ delete(obj.properties)
+ }
+ delete(l)
+ }
+}
+
+@(private)
+convert_json_color :: proc(jcolor: string) -> Color {
+ return Color{1, 1, 1, 1}
+}
+
+@(private)
+convert_json_properties :: proc(jprops: []Json_Property) -> Properties {
+ props := make(Properties)
+
+ for jprop in jprops {
+ props[jprop.name] = jprop.value
+ }
+
+ return props
+}
+
+@(private)
+convert_json_map :: proc(jmap: Json_Map, tmap: ^Map, path: string) -> Error {
+ // ensure the map is orthogonal
+ if strings.compare(jmap.orientation, "orthogonal") != 0 {
+ return .NON_ORTHOGONAL_NOT_SUPPORTED
+ }
+
+ // ensure the map is not infinite
+ if jmap.infinite {
+ return .INFINITE_NOT_SUPPORTED
+ }
+
+ tmap.background_color = convert_json_color(jmap.backgroundcolor)
+ tmap.class = strings.clone(jmap.class)
+
+ tmap.width = jmap.width
+ tmap.height = jmap.height
+
+ tmap.tile_width = jmap.tilewidth
+ tmap.tile_height = jmap.tileheight
+
+ tmap.next_layer_id = jmap.nextlayerid
+ tmap.next_object_id = jmap.nextobjectid
+
+ tmap.parallax_origin_x = jmap.parallaxoriginx
+ tmap.parallax_origin_y = jmap.parallaxoriginy
+
+ tmap.properties = convert_json_properties(jmap.properties)
+
+ // TODO: flatten groups
+ layer_count := len(jmap.layers)
+ for jlayer in jmap.layers {
+ layer_count += len(jlayer.layers) // take into consideration groups
+ }
+
+ tmap.layers = make([]Layer, layer_count)
+ current_index := 0
+
+ for jlayer in jmap.layers {
+ if !jlayer.visible {
+ continue // ignore hidden layers
+ }
+
+ // FIXME: Some duplicated code down here
+ if strings.compare(jlayer.type, "group") == 0 {
+ for jlayer2 in jlayer.layers {
+ if !jlayer2.visible {
+ continue // ignore hidden layers
+ }
+
+ layer, err := convert_json_layer(jlayer2)
+ if err != .NONE {
+ return err
+ }
+ tmap.layers[current_index] = layer
+ current_index += 1
+ }
+ continue
+ }
+
+ layer, err := convert_json_layer(jlayer)
+ if err != .NONE {
+ return err
+ }
+ tmap.layers[current_index] = layer
+ current_index += 1
+ }
+
+ tmap.tile_sets = make([]Tile_Set, len(jmap.tilesets))
+
+ res_dir := filepath.dir(path, allocator = context.temp_allocator)
+
+ for jtile_set, i in jmap.tilesets {
+ tile_set, err := convert_json_tile_set(tmap, res_dir, jtile_set)
+ if err != .NONE {
+ return err
+ }
+ tmap.tile_sets[i] = tile_set
+
+ for j in int(tile_set.first_gid)..<len(tmap.tiles) {
+ tile_set.tiles[j].tile_set = i32(i)
+ }
+ }
+
+ return .NONE
+}
+
+draw_map :: proc(tmap: Map) {
+ for layer in tmap.layers {
+ tile_layer, ok := layer.layer.(Tile_Layer)
+ if !ok {
+ continue
+ }
+
+ x: i32 = 0
+ y: i32 = 0
+
+ for cell in tile_layer.data {
+ if cell != 0 {
+ tile := tmap.tiles[cell - 1]
+ tile_set := tmap.tile_sets[tile.tile_set]
+
+ rl.DrawTexturePro(
+ tile_set.texture,
+ rl.Rectangle{
+ x = f32(tile.src_start.x),
+ y = f32(tile.src_start.y),
+ width = f32(tile.src_size.x),
+ height = f32(tile.src_size.y),
+ },
+ rl.Rectangle{
+ x = f32(x),
+ y = f32(y),
+ width = f32(tile.src_size.x),
+ height = f32(tile.src_size.y),
+ },
+ {0, 0},
+ 0,
+ rl.WHITE,
+ )
+ }
+
+ x += tmap.tile_width
+ if x > (tmap.width - 1) * tmap.tile_width {
+ x = 0
+ y += tmap.tile_height
+ }
+ }
+ }
+}
+
+@(private)
+load_image :: proc(filename: string, res_dir: string) -> rl.Texture2D {
+ path := strings.concatenate({res_dir, "/", filename})
+ defer delete(path)
+
+ byte_path := make([]u8, len(path) + 1, context.temp_allocator)
+ copy(byte_path, path)
+ byte_path[len(path)] = 0
+ return rl.LoadTexture(transmute(cstring)&byte_path[0])
+}
+
+@(private)
+convert_json_layer :: proc(jlayer: Json_Layer) -> (Layer, Error) {
+ // TODO: uncompress tile data
+
+ layer: Layer
+
+ layer.name = strings.clone(jlayer.name)
+
+ layer.parallax_x = jlayer.parallaxx
+ layer.parallax_y = jlayer.parallaxy
+
+ layer.id = jlayer.id
+
+ layer.tint = convert_json_color(jlayer.tintcolor)
+
+ layer.properties = convert_json_properties(jlayer.properties)
+
+ switch jlayer.type {
+ case "tilelayer":
+ tile_layer := Tile_Layer{
+ width = jlayer.width,
+ height = jlayer.height,
+ }
+
+ data := jlayer.data.([]i32)
+ tile_layer.data = make([]i32, len(data))
+ copy(tile_layer.data, data)
+
+ layer.layer = tile_layer
+
+ case "objectgroup":
+ obj_layer := make(Object_Layer, len(jlayer.objects))
+
+ for jobj, i in jlayer.objects {
+ if !jobj.visible {
+ continue // ignore hidden objects
+ }
+
+ obj, err := convert_json_object(jobj)
+ if err != .NONE {
+ return {}, err
+ }
+ obj_layer[i] = obj
+ }
+
+ layer.layer = obj_layer
+
+ case "imagelayer":
+ image_layer := Image_Layer{
+ texture = load_image(jlayer.image, "res"),
+ transparent_color = convert_json_color(jlayer.transparentcolor),
+ repeat_x = jlayer.repeatx,
+ repeat_y = jlayer.repeaty,
+ }
+
+ layer.layer = image_layer
+
+ case "group":
+ // groups are handled elsewhere
+ }
+
+ return layer, .NONE
+}
+
+@(private)
+convert_json_object :: proc(jobj: Json_Object) -> (Object, Error) {
+ obj: Object
+
+ if jobj.ellipse {
+ return {}, .ELLIPSE_OBJ_NOT_SUPPORTED
+ }
+ if jobj.polygon != nil {
+ return {}, .POLYGON_OBJ_NOT_SUPPORTED
+ }
+ if jobj.polyline != nil {
+ return {}, .POLYLINE_OBJ_NOT_SUPPORTED
+ }
+ if jobj.text != nil {
+ return {}, .TEXT_OBJ_NOT_SUPPORTED
+ }
+
+ obj.position = {
+ f32(jobj.x),
+ f32(jobj.y),
+ }
+ obj.size = {
+ f32(jobj.width),
+ f32(jobj.height),
+ }
+ obj.tile_id = jobj.gid
+
+ obj.name = strings.clone(jobj.name)
+ obj.class = strings.clone(jobj.type)
+ obj.properties = convert_json_properties(jobj.properties)
+
+ return obj, .NONE
+}
+
+@(private)
+convert_json_tile_set :: proc(
+ tmap: ^Map,
+ res_dir: string,
+ jtile_set: Json_Tile_Set,
+) -> (Tile_Set, Error) {
+ if len(jtile_set.source) > 0 {
+ // Tileset links somewhere else
+ path := strings.concatenate({res_dir, "/", jtile_set.source})
+ defer delete(path)
+
+ data, err := os.read_entire_file(path, allocator = context.temp_allocator)
+ if err != nil {
+ log.errorf("Could not load external tile set '%v'", path)
+ return {}, .COULD_NOT_LOAD
+ }
+
+ new_jtile_set: Json_Tile_Set
+ marshal_err := json.unmarshal(
+ data,
+ &new_jtile_set,
+ allocator = context.temp_allocator,
+ )
+ if marshal_err != nil {
+ log.errorf("Could not unmarshal external tile set '%v'", path)
+ return {}, .COULD_NOT_LOAD
+ }
+
+ return convert_json_tile_set(tmap, res_dir, new_jtile_set)
+ }
+
+ tile_set: Tile_Set
+
+ tile_set.rows = jtile_set.rows
+ tile_set.columns = jtile_set.columns
+
+ tile_set.first_gid = jtile_set.firstgid
+
+ tile_set.texture = load_image(jtile_set.image, res_dir)
+ tile_set.margin = jtile_set.margin
+ tile_set.spacing = jtile_set.spacing
+
+ tile_set.tile_count = jtile_set.tilecount
+ tile_set.tile_width = jtile_set.tilewidth
+ tile_set.tile_height = jtile_set.tileheight
+ tile_set.tile_offset = {
+ jtile_set.tileoffset.x,
+ jtile_set.tileoffset.y,
+ }
+
+ tile_set.properties = convert_json_properties(jtile_set.properties)
+
+ tile_set.tiles = make([]Tile, jtile_set.tilecount)
+
+ // Ignore margin and spacing for now
+ // TODO: account for margin and spacing
+ tile_x := tile_set.texture.width / tile_set.tile_width
+ tile_y := tile_set.texture.height / tile_set.tile_height
+
+ current_index := 0
+ for y in 0..<tile_y {
+ for x in 0..<tile_x {
+ tile := &tile_set.tiles[current_index]
+
+ tile.id = i32(current_index)
+
+ tile.src_start = {
+ x * tile_set.tile_width,
+ y * tile_set.tile_height,
+ }
+ tile.src_size = {
+ tile_set.tile_width,
+ tile_set.tile_height,
+ }
+
+ tile.gid = i32(len(tmap.tiles))
+ append(&tmap.tiles, tile^)
+
+ current_index += 1
+ }
+ }
+
+ // TODO: here, go through jtile_set.tiles and load in more tile data
+
+ return tile_set, .NONE
+}