diff options
| author | iamcheeseman <[hidden email]> | 2026-01-12 20:51:28 -0500 |
|---|---|---|
| committer | iamcheeseman <[hidden email]> | 2026-01-12 20:51:28 -0500 |
| commit | 7fb83578b99aa224f7545f4118a46e84b58a9295 (patch) | |
| tree | 515e294ac7de167c4501cc0a8d375b13213faf36 | |
| parent | 2b3a3ea9f4bc902b1b357fd149952d4570b25bf3 (diff) | |
NEW ASSET SYSTEM WOOOOOOOOHOOOOOOOOOOOOOOOOOOOOOOOOO
| -rwxr-xr-x | build.sh | 4 | ||||
| -rw-r--r-- | res/img/player.ase | bin | 5482 -> 5482 bytes | |||
| -rwxr-xr-x | run_debug.sh | 2 | ||||
| -rw-r--r--[-rwxr-xr-x] | src/assets.odin | 106 | ||||
| -rw-r--r-- | src/draw/sprite.odin | 94 | ||||
| -rw-r--r-- | src/main.odin | 15 | ||||
| -rw-r--r-- | src/platform.odin | 18 | ||||
| -rw-r--r-- | src/player.odin | 50 | ||||
| -rw-r--r-- | src/resources.odin | 61 | ||||
| -rw-r--r-- | src/sprite.odin | 106 | ||||
| -rw-r--r-- | src/tiled/json.odin | 208 | ||||
| -rw-r--r-- | src/tiled/tiled.odin | 628 | ||||
| -rw-r--r-- | src/tiled/world.odin | 88 | ||||
| -rw-r--r-- | src/world.odin | 87 | ||||
| -rw-r--r-- | tools/compile_assets/loaders.odin | 97 | ||||
| -rw-r--r-- | tools/compile_assets/main.odin | 129 | ||||
| -rw-r--r-- | tools/compile_assets/tiled.odin | 447 |
17 files changed, 969 insertions, 1171 deletions
@@ -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 build src -vet -disallow-do -o:speed +odin run ./tools/compile_assets -- res src/assets.odin && +odin build src -disallow-do -o:speed $1 diff --git a/res/img/player.ase b/res/img/player.ase Binary files differindex 47a7dfa..5aaf62e 100644 --- a/res/img/player.ase +++ b/res/img/player.ase diff --git a/run_debug.sh b/run_debug.sh index c56ad41..c1cb61d 100755 --- a/run_debug.sh +++ b/run_debug.sh @@ -1 +1 @@ -./run.sh "-debug -sanitize:address" +./run.sh "-sanitize:address" diff --git a/src/assets.odin b/src/assets.odin index e74dcf8..b2222bb 100755..100644 --- a/src/assets.odin +++ b/src/assets.odin @@ -15,11 +15,12 @@ Image_Id :: enum { Animation_Id :: enum { PLAYER, + NONE, } -Map_Id :: enum { - ROOM_BEGIN_1, +Room_Id :: enum { ROOM_BEGIN, + ROOM_BEGIN_1, } Tileset_Id :: enum { @@ -29,50 +30,87 @@ Tileset_Id :: enum { Resource_Id :: union { Image_Id, Animation_Id, - Map_Id, + Room_Id, Tileset_Id, } -images: [Image_Id]Image_Resource -animations: [Animation_Id]Animation_Resource -maps: [Map_Id]Map_Resource -tilesets: [Tileset_Id]Tileset_Resource +images: [Image_Id]Image_Resource = { + .TILESETS = {data = #load("../.compiled-res/tilesets.qoi"), anim = .NONE}, + .PLAYER = {data = #load("../.compiled-res/player-sheet.qoi"), anim = .PLAYER}, +} -path_to_id: map[string]Resource_Id +animations: [Animation_Id]Animation_Resource = { + .PLAYER = {frame_count = 23, frame_durations = {100, 100, 100, 100, 100, 100, 75, 75, 75, 75, 75, 75, 75, 75, 100, 100, 100, 100, 100, 100, 100, 100, 100}, tags = {"idle"={from = 0, to = 5}, "jump_trans"={from = 16, to = 16}, "run"={from = 6, to = 13}, "jump_down"={from = 17, to = 18}, "jump_up"={from = 14, to = 15}, "sleep"={from = 19, to = 22}}}, + .NONE = {frame_count=1, frame_durations={100}, tags={}}, +} -load_resources :: proc() { - load_images() - load_anims() - load_maps() - load_tilesets() +rooms: [Room_Id]Room_Resource = { + .ROOM_BEGIN = {width=30, height=20, tile_width=16, tile_height=16, layers={{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, 7, 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, 7, 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, 16, 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, 0, 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, 0, 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, 41, 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, 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, 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, 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, 7, 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, 18, 18, 9, 39, 0, 0, 0, 5, 9, 20, 9, 44, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 0, 0, 0, 7, 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, 20, 9, 18, 39, 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, 40, 40}}, objects={}, background_image=nil}, + .ROOM_BEGIN_1 = {width=30, height=20, tile_width=16, tile_height=16, layers={{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, 21, 19, 19, 19, 19, 31, 31, 28, 40, 40, 40, 40, 40, 40, 40, 27, 31, 19, 31, 31, 31, 19, 19, 31, 31, 31, 31, 19, 19, 19, 17, 0, 0, 0, 0, 0, 0, 10, 28, 40, 40, 21, 19, 31, 31, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 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, 40, 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, 7, 40, 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, 7, 40, 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, 7, 40, 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, 7, 40, 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, 7, 40, 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, 7, 40, 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, 7, 40, 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, 7, 40, 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, 7, 40, 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, 16, 27, 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, 0, 0, 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, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 9, 20, 20, 20, 18, 18, 18, 20, 8, 0, 0, 0, 0, 0, 0, 5, 18, 9, 9, 20, 20, 20, 18, 9, 18, 9, 18, 18, 9, 39, 40, 40, 40, 40, 40, 40, 40, 40, 43, 20, 20, 9, 20, 9, 9, 39, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40}}, objects={}, background_image=nil}, +} - // Allow conversion from paths to a resource id, since it's a better way to - // reference resources in other resources (JSON is a good example). - path_to_id["res/tileset.tsj"] = Tileset_Id.TILESET - path_to_id["res/img/player.ase"] = Image_Id.PLAYER - path_to_id["res/img/tilesets.png"] = Image_Id.TILESETS - path_to_id["res/room_begin.tmj"] = Map_Id.ROOM_BEGIN - path_to_id["res/room_begin_1.tmj"] = Map_Id.ROOM_BEGIN_1 +tilesets: [Tileset_Id]Tileset_Resource = { + .TILESET = {tiles={1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44}, image=.TILESETS}, } -@(private="file") -load_images :: proc() { - images[.TILESETS] = {data = #load("/home/xswan/demonchime/.compiled-res/tilesets.qoi")} - images[.PLAYER] = {data = #load("/home/xswan/demonchime/.compiled-res/player-sheet.qoi")} +tiles: []Tile_Resource = { + {tileset=.TILESET, rect=Rect{start = {0, 0}, size = {16, 16}}, id=1, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {16, 0}, size = {16, 16}}, id=2, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {32, 0}, size = {16, 16}}, id=3, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {48, 0}, size = {16, 16}}, id=4, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {64, 0}, size = {16, 16}}, id=5, collisions={Rect{start = {8, 8}, size = {8, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {80, 0}, size = {16, 16}}, id=6, collisions={Rect{start = {0, 8}, size = {8, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {96, 0}, size = {16, 16}}, id=7, collisions={Rect{start = {8, 0}, size = {8, 16}}}}, + {tileset=.TILESET, rect=Rect{start = {112, 0}, size = {16, 16}}, id=8, collisions={Rect{start = {0, 0}, size = {8, 16}}}}, + {tileset=.TILESET, rect=Rect{start = {128, 0}, size = {16, 16}}, id=9, collisions={Rect{start = {0, 8}, size = {16, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {144, 0}, size = {16, 16}}, id=10, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {160, 0}, size = {16, 16}}, id=11, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {0, 16}, size = {16, 16}}, id=12, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {16, 16}, size = {16, 16}}, id=13, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {32, 16}, size = {16, 16}}, id=14, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {48, 16}, size = {16, 16}}, id=15, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {64, 16}, size = {16, 16}}, id=16, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {80, 16}, size = {16, 16}}, id=17, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {96, 16}, size = {16, 16}}, id=18, collisions={Rect{start = {0, 8}, size = {16, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {112, 16}, size = {16, 16}}, id=19, collisions={Rect{start = {0, 0}, size = {16, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {128, 16}, size = {16, 16}}, id=20, collisions={Rect{start = {0, 8}, size = {16, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {144, 16}, size = {16, 16}}, id=21, collisions={Rect{start = {8, 0}, size = {8, 8}}, Rect{start = {0, 8}, size = {8, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {160, 16}, size = {16, 16}}, id=22, collisions={Rect{start = {8, 8}, size = {8, 8}}, Rect{start = {0, 0}, size = {8, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {0, 32}, size = {16, 16}}, id=23, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {16, 32}, size = {16, 16}}, id=24, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {32, 32}, size = {16, 16}}, id=25, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {48, 32}, size = {16, 16}}, id=26, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {64, 32}, size = {16, 16}}, id=27, collisions={Rect{start = {8, 0}, size = {8, 8}}, Rect{start = {0, 8}, size = {8, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {80, 32}, size = {16, 16}}, id=28, collisions={Rect{start = {8, 8}, size = {8, 8}}, Rect{start = {0, 0}, size = {8, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {96, 32}, size = {16, 16}}, id=29, collisions={Rect{start = {0, 8}, size = {8, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {112, 32}, size = {16, 16}}, id=30, collisions={Rect{start = {8, 8}, size = {8, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {128, 32}, size = {16, 16}}, id=31, collisions={Rect{start = {0, 0}, size = {16, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {144, 32}, size = {16, 16}}, id=32, collisions={Rect{start = {0, 0}, size = {8, 16}}}}, + {tileset=.TILESET, rect=Rect{start = {160, 32}, size = {16, 16}}, id=33, collisions={Rect{start = {8, 0}, size = {8, 16}}}}, + {tileset=.TILESET, rect=Rect{start = {0, 48}, size = {16, 16}}, id=34, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {16, 48}, size = {16, 16}}, id=35, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {32, 48}, size = {16, 16}}, id=36, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {48, 48}, size = {16, 16}}, id=37, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {64, 48}, size = {16, 16}}, id=38, collisions={Rect{start = {0, 8}, size = {16, 8}}, Rect{start = {0, 0}, size = {8, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {80, 48}, size = {16, 16}}, id=39, collisions={Rect{start = {0, 8}, size = {16, 8}}, Rect{start = {8, 0}, size = {8, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {96, 48}, size = {16, 16}}, id=40, collisions={}}, + {tileset=.TILESET, rect=Rect{start = {112, 48}, size = {16, 16}}, id=41, collisions={Rect{start = {8, 8}, size = {8, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {128, 48}, size = {16, 16}}, id=42, collisions={Rect{start = {0, 8}, size = {8, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {144, 48}, size = {16, 16}}, id=43, collisions={Rect{start = {0, 8}, size = {16, 8}}, Rect{start = {0, 0}, size = {8, 8}}}}, + {tileset=.TILESET, rect=Rect{start = {160, 48}, size = {16, 16}}, id=44, collisions={Rect{start = {0, 8}, size = {16, 8}}, Rect{start = {8, 0}, size = {8, 8}}}}, } -@(private="file") -load_anims :: proc() { - animations[.PLAYER] = {frame_count = 23, frame_durations = {100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100}, tags = {"jump_trans"={from = 16, to = 16}, "jump_down"={from = 17, to = 18}, "idle"={from = 0, to = 5}, "sleep"={from = 19, to = 22}, "jump_up"={from = 14, to = 15}, "run"={from = 6, to = 13}}} +world: []Room_Position_Resource = { + {id=.ROOM_BEGIN_1, x=-528, y=-64, width=480, height=320}, + {id=.ROOM_BEGIN, x=-48, y=-64, width=480, height=320}, } -@(private="file") -load_maps :: proc() { - maps[.ROOM_BEGIN_1] = #load("/home/xswan/demonchime/res/room_begin_1.tmj") - maps[.ROOM_BEGIN] = #load("/home/xswan/demonchime/res/room_begin.tmj") +path_to_id: map[string]Resource_Id = { + "res/img/player.ase" = Image_Id.PLAYER, + "res/img/tilesets.png" = Image_Id.TILESETS, + "res/room_begin.tmj" = Room_Id.ROOM_BEGIN, + "res/room_begin_1.tmj" = Room_Id.ROOM_BEGIN_1, } -@(private="file") -load_tilesets :: proc() { - tilesets[.TILESET] = #load("/home/xswan/demonchime/res/tileset.tsj") +load_resources :: proc() { } diff --git a/src/draw/sprite.odin b/src/draw/sprite.odin index ebe2e08..3a90dfe 100644 --- a/src/draw/sprite.odin +++ b/src/draw/sprite.odin @@ -4,97 +4,3 @@ import "core:log" import rl "vendor:raylib" -Sprite :: struct { - image: rl.Texture2D, - anim: Animation, - active_anim: u32, - frame_time: f32, - current_frame: i32, - - width: i32, - height: i32, - - pos: Vec2, - offset: Vec2, - rotation: f32, - scale: Vec2, - - just_finished_loop: bool, -} - -init_sprite :: proc( - sprite: ^Sprite, - path: string, - anim: Animation = {}, -) -> bool { - byte_path := make([]u8, len(path) + 1, context.temp_allocator) - copy(byte_path, path) - byte_path[len(path)] = 0 - - sprite.image = rl.LoadTexture(transmute(cstring)&byte_path[0]) - sprite.anim = anim - - sprite.width = i32(int(sprite.image.width) / len(sprite.anim.frames)) - sprite.height = i32(sprite.image.height) - - sprite.scale = Vec2{1, 1} - - log.debugf( - "loaded sprite '%v' %vx%v (%vx%v)", - path, - sprite.width, sprite.height, - sprite.image.width, sprite.image.height, - ) - - return true -} - -destroy_sprite :: proc(sprite: Sprite) { - rl.UnloadTexture(sprite.image) -} - -set_sprite_active_tag :: proc(sprite: ^Sprite, tag_name: string) { - sprite.active_anim = sprite.anim.frame_tags_indices[tag_name] -} - -update_sprite :: proc(sprite: ^Sprite, dt: f32) { - sprite.just_finished_loop = false - - sprite.frame_time += dt - - tag := sprite.anim.frame_tags[sprite.active_anim] - frame := sprite.anim.frames[sprite.current_frame] - duration := f32(frame.duration) / 1000 - - if sprite.frame_time > duration { - sprite.frame_time -= duration - sprite.current_frame += 1 - } - - if sprite.current_frame < tag.from { - sprite.current_frame = tag.to - sprite.just_finished_loop = true - } - if sprite.current_frame > tag.to { - sprite.current_frame = tag.from - sprite.just_finished_loop = true - } -} - -sprite :: proc( - sprite: Sprite, -) { - frame := sprite.anim.frames[sprite.current_frame] - - texture_quad( - sprite.image, - Rect { - Vec2{f32(frame.rect.x), f32(frame.rect.y)}, - Vec2{f32(frame.rect.w), f32(frame.rect.h)}, - }, - sprite.pos, - sprite.offset, - sprite.rotation, - sprite.scale, - ) -} diff --git a/src/main.odin b/src/main.odin index 508f97c..bed9861 100644 --- a/src/main.odin +++ b/src/main.odin @@ -9,12 +9,13 @@ import "base:runtime" import "core:log" import "core:fmt" import "core:mem" +import os "core:os/os2" import rl "vendor:raylib" import "draw" import "phys" -import "tiled" +// import "tiled" Vec2 :: [2]f32 @@ -55,6 +56,11 @@ logger: log.Logger // } // } +die :: proc(msg: string, args: ..any, #any_int exit_code := 1) { + log.fatalf(msg, args) + os.exit(exit_code) +} + init :: proc() { state.camera.zoom = 1 // state.camera.offset = {draw.SCREEN_WIDTH/2, draw.SCREEN_HEIGHT/2} @@ -71,8 +77,7 @@ init :: proc() { // } // state.room = room - tiled.load_world("res/map.world") - tiled.open_new_room_at({0, 0}) + open_room(.ROOM_BEGIN) setup_map_collisions() } @@ -85,7 +90,7 @@ frame :: proc() { rl.BeginMode2D(state.camera) draw_player() - tiled.draw_map(tiled.current_room.tmap) + draw_room(current_room.id) // draw_platforms() rl.EndMode2D() @@ -102,7 +107,6 @@ frame :: proc() { cleanup :: proc() { delete_player() delete_entity_list(state.platform_list) - tiled.delete_world() phys.destroy_world() } @@ -159,3 +163,4 @@ main :: proc() { cleanup() } + diff --git a/src/platform.odin b/src/platform.odin index bc0e103..a071468 100644 --- a/src/platform.odin +++ b/src/platform.odin @@ -2,7 +2,6 @@ package demonchime import "draw" import "phys" -import "tiled" Platform :: struct { handle: Entity_Handle, @@ -40,20 +39,17 @@ setup_map_collisions :: proc() { make_map_collisions :: proc( x: i32, y: i32, - tile: tiled.Tile, - tile_set: tiled.Tile_Set, + tile_id: u32, ) { - objs, is_object_layer := tile.object_group.layer.(tiled.Object_Layer) - if !is_object_layer { - return - } + tile := tiles[tile_id] - for obj in objs { + for coll in tile.collisions { make_platform({ - Vec2{f32(x), f32(y)} + obj.position, - obj.size, + coll.start + {f32(x), f32(y)}, + coll.size, }) } } - tiled.iterate_map_tiles(tiled.current_room.tmap, make_map_collisions) + + iterate_room_tiles(current_room.id, make_map_collisions) } diff --git a/src/player.odin b/src/player.odin index de5a66b..e7fd214 100644 --- a/src/player.odin +++ b/src/player.odin @@ -5,9 +5,7 @@ import "core:math" import rl "vendor:raylib" -import "draw" import "phys" -import "tiled" PLAYER_SPEED :: 100 PLAYER_ACCEL :: 10 @@ -29,8 +27,7 @@ Player_State :: enum { // there will only ever be one player, so just make it a global :) player: struct { body_handle: phys.Body_Handle, - anim: draw.Animation, - sprite: draw.Sprite, + sprite: Sprite, jump_buffer: f32, coyote_time: f32, @@ -45,13 +42,6 @@ player: struct { } init_player :: proc() { - anim_ok := draw.init_anim_data(&player.anim, "res/player.json") - if !anim_ok { - log.warn("could not load animation") - rl.CloseWindow() - return - } - handle, body := phys.make_body( phys.Rect{{-4, 17}, {8, 16}}, ) @@ -59,7 +49,7 @@ init_player :: proc() { phys.set_body_position(handle, Vec2{50, 100}) - draw.init_sprite(&player.sprite, player.anim.image_path, player.anim) + init_sprite(&player.sprite, .PLAYER) player.sprite.offset = Vec2{ math.floor(f32(player.sprite.width / 2)), @@ -70,8 +60,7 @@ init_player :: proc() { delete_player :: proc() { phys.remove_body(player.body_handle) - draw.destroy_sprite(player.sprite) - draw.delete_anim_data(player.anim) + destroy_sprite(player.sprite) } @(private="file") @@ -93,10 +82,10 @@ default_state :: proc(dt: f32) { input := get_input_dir() if input != 0 { - draw.set_sprite_active_tag(&player.sprite, "run") + set_sprite_active_tag(&player.sprite, "run") player.sprite.scale.x = math.sign(input) } else { - draw.set_sprite_active_tag(&player.sprite, "idle") + set_sprite_active_tag(&player.sprite, "idle") } if is_keybind_just_down(actions.jump) { @@ -116,11 +105,11 @@ default_state :: proc(dt: f32) { } else { switch body.vel.y { case -math.INF_F32..<-50: - draw.set_sprite_active_tag(&player.sprite, "jump_up") + set_sprite_active_tag(&player.sprite, "jump_up") case 50..<math.INF_F32: - draw.set_sprite_active_tag(&player.sprite, "jump_down") + set_sprite_active_tag(&player.sprite, "jump_down") case: - draw.set_sprite_active_tag(&player.sprite, "jump_trans") + set_sprite_active_tag(&player.sprite, "jump_trans") } } @@ -176,6 +165,7 @@ dash_state :: proc(dt: f32) { } } +@(private="file") exit_dash :: proc() { player.state = .DEFAULT @@ -189,21 +179,21 @@ exit_dash :: proc() { change_rooms :: proc() { body := phys.get_body(player.body_handle) - width := f32(tiled.current_room.room.width) - height := f32(tiled.current_room.room.height) + width := f32(current_room.width) + height := f32(current_room.height) if body.pos.x < 0 || body.pos.x > width \ || body.pos.y < 0 || body.pos.y > height { - prev_room_pos := [2]f32{ - f32(tiled.current_room.room.x), - f32(tiled.current_room.room.y), + prev_room_pos := Vec2{ + f32(current_room.x), + f32(current_room.y), } - changed := tiled.open_new_room_at({i32(body.pos.x), i32(body.pos.y)}) + changed := open_room_at({i32(body.pos.x), i32(body.pos.y)}) if changed { - new_room_pos := [2]f32{ - f32(tiled.current_room.room.x), - f32(tiled.current_room.room.y), + new_room_pos := Vec2{ + f32(current_room.x), + f32(current_room.y), } diff := prev_room_pos - new_room_pos @@ -226,7 +216,7 @@ update_player :: proc(dt: f32) { body := phys.get_body(player.body_handle) player.sprite.pos = body.pos - draw.update_sprite(&player.sprite, dt) + update_sprite(&player.sprite, dt) player.dash_cooldown -= dt player.jump_buffer -= dt @@ -236,5 +226,5 @@ update_player :: proc(dt: f32) { } draw_player :: proc() { - draw.sprite(player.sprite) + draw_sprite(player.sprite) } diff --git a/src/resources.odin b/src/resources.odin index 6120812..799ba75 100644 --- a/src/resources.odin +++ b/src/resources.odin @@ -19,9 +19,68 @@ Animation_Resource :: struct { tags: map[string]Tag_Resource, } -Map_Resource :: struct { +Object_Resource :: struct { + pos: Vec2, + parallax: Vec2, + properties: map[string]any, +} + +Room_Resource :: struct { + width: i32, + height: i32, + + tile_width: i32, + tile_height: i32, + + layers: [][]u32, + + objects: []Object_Resource, + + background_image: Maybe(Image_Id), +} + +Tile_Resource :: struct { + tileset: Tileset_Id, + rect: Rect, + + id: u32, + + collisions: []Rect, } Tileset_Resource :: struct { + tiles: []u32, + image: Image_Id, +} + +Room_Position_Resource :: struct { + id: Room_Id, + x: i32, + y: i32, + width: i32, + height: i32, } +get_image :: proc(id: Image_Id) -> rl.Texture2D { + img_res := &images[id] + + if !rl.IsTextureValid(img_res.texture) { + rl_img := rl.LoadImageFromMemory( + ".qoi", + raw_data(img_res.data), + i32(len(img_res.data)), + ) + if !rl.IsImageValid(rl_img) { + die("Could not load asset %v", id) + } + + img_res.texture = rl.LoadTextureFromImage(rl_img); + rl.UnloadImage(rl_img) + } + + return img_res.texture +} + +get_room :: proc(id: Room_Id) -> Room_Resource { + return rooms[id] +} diff --git a/src/sprite.odin b/src/sprite.odin new file mode 100644 index 0000000..af2fdf9 --- /dev/null +++ b/src/sprite.odin @@ -0,0 +1,106 @@ +package demonchime + +import "core:log" + +import rl "vendor:raylib" + +import "draw" + +Sprite :: struct { + image: rl.Texture2D, + anim_id: Animation_Id, + current_tag: Tag_Resource, + frame_time: f32, + current_frame: i32, + + width: i32, + height: i32, + + pos: Vec2, + offset: Vec2, + rotation: f32, + scale: Vec2, + + just_finished_loop: bool, +} + +init_sprite :: proc( + sprite: ^Sprite, + image_id: Image_Id, + anim_id: Maybe(Animation_Id) = nil, +) -> bool { + sprite.image = get_image(image_id) + + switch id in anim_id { + case Animation_Id: + sprite.anim_id = id + case nil: + sprite.anim_id = images[image_id].anim + } + + anim := animations[sprite.anim_id] + + sprite.width = i32(f32(sprite.image.width) / f32(anim.frame_count)) + sprite.height = i32(sprite.image.height) + + log.debug(sprite.width, anim.frame_count, sprite.anim_id) + + sprite.scale = Vec2{1, 1} + + return true +} + +destroy_sprite :: proc(sprite: Sprite) { + rl.UnloadTexture(sprite.image) +} + +set_sprite_active_tag :: proc(sprite: ^Sprite, tag_name: string) { + anim := animations[sprite.anim_id] + sprite.current_tag = anim.tags[tag_name] +} + +update_sprite :: proc(sprite: ^Sprite, dt: f32) { + sprite.just_finished_loop = false + + sprite.frame_time += dt + + anim := animations[sprite.anim_id] + + duration := f32(anim.frame_durations[sprite.current_frame]) / 1000 + + if sprite.frame_time > duration { + sprite.frame_time -= duration + sprite.current_frame += 1 + } + + tag := &sprite.current_tag + + if sprite.current_frame < tag.from { + sprite.current_frame = tag.to + sprite.just_finished_loop = true + } + if sprite.current_frame > tag.to { + sprite.current_frame = tag.from + sprite.just_finished_loop = true + } +} + +draw_sprite :: proc( + sprite: Sprite, +) { + rect := draw.Rect{ + {f32(sprite.width * sprite.current_frame), 0}, + {f32(sprite.width), f32(sprite.height)}, + } + + draw.texture_quad( + sprite.image, + rect, + sprite.pos, + sprite.offset, + sprite.rotation, + sprite.scale, + ) + + // log.debug(rect) +} diff --git a/src/tiled/json.odin b/src/tiled/json.odin deleted file mode 100644 index 874430e..0000000 --- a/src/tiled/json.odin +++ /dev/null @@ -1,208 +0,0 @@ -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 deleted file mode 100644 index d84c296..0000000 --- a/src/tiled/tiled.odin +++ /dev/null @@ -1,628 +0,0 @@ -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: []i32, // array of GIDs -} - -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.debug(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 0..<len(tmap.tiles) { - tmap.tiles[int(tile_set.first_gid) + j].tile_set = i32(i) - } - } - - return .NONE -} - -Map_Iterate_Callback :: proc(i32, i32, Tile, Tile_Set) - -iterate_map_tiles :: proc(tmap: Map, callback: Map_Iterate_Callback) { - for layer in tmap.layers { - tile_layer, ok := layer.layer.(Tile_Layer) - if !ok { - continue - } - - x: i32 = 0 - y: i32 = 0 - - for cell, i in tile_layer.data { - if cell > 0 { - tile := tmap.tiles[cell - 1] - tile_set := tmap.tile_sets[tile.tile_set] - callback(x, y, tile, tile_set) - } - - x += tmap.tile_width - if x > (tmap.width - 1) * tmap.tile_width { - x = 0 - y += tmap.tile_height - } - } - } -} - -test_cells :: 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, i in tile_layer.data { - if cell > 0 { - log.debug(i, [2]i32{x, y}, cell) - } - - x += tmap.tile_width - if x > (tmap.width - 1) * tmap.tile_width { - x = 0 - y += tmap.tile_height - } - } - } -} - -draw_map :: proc(tmap: Map) { - draw_tile :: proc(x: i32, y: i32, tile: Tile, tile_set: 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, - ) - } - - iterate_map_tiles(tmap, draw_tile) -} - -@(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([]i32, 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 - - 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, - } - - current_index += 1 - - tile.gid = i32(len(tmap.tiles)) - append(&tmap.tiles, tile) - - tile_set.tiles[current_index - 1] = tile.gid - } - } - - for jtile in jtile_set.tiles { - ttile := &tmap.tiles[tile_set.tiles[jtile.id]] - object_group, err := convert_json_layer(jtile.objectgroup) - if err != .NONE { - return {}, err - } - ttile.object_group = object_group - ttile.properties = convert_json_properties(jtile.properties) - } - - return tile_set, .NONE -} diff --git a/src/tiled/world.odin b/src/tiled/world.odin deleted file mode 100644 index 0a13716..0000000 --- a/src/tiled/world.odin +++ /dev/null @@ -1,88 +0,0 @@ -package tiled - -import os "core:os/os2" -import "core:log" -import "core:encoding/json" -import "core:path/filepath" -import "core:strings" - -World_Room :: struct { - file_name: string `json:"fileName"`, - x: i32, - y: i32, - width: i32, - height: i32, -} - -res_dir: string -world: []World_Room -current_room: struct { - tmap: Map, - using room: World_Room, -} - -load_world :: proc(path: string) -> bool { - world_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 false - } - - jworld: struct { - maps: []World_Room, - } - unmarshal_err := json.unmarshal( - world_text, - &jworld, - ) - if unmarshal_err != nil { - log.errorf("Failed to unmarshal file %v (%v)", path, unmarshal_err) - return false - } - - world = jworld.maps - res_dir = filepath.dir(path) - - return true -} - -delete_world :: proc() { - for room in world { - delete(room.file_name) - } - - delete_map(current_room.tmap) - delete(world) - delete(res_dir) -} - -open_new_room_at :: proc(pos: [2]i32) -> bool { - pos := pos - pos += {current_room.room.x, current_room.room.y} - - log.debug("trying to change room...", pos) - for room in world { - if strings.compare(room.file_name, current_room.room.file_name) == 0 { - continue - } - - if pos.x >= room.x && pos.x <= room.x + room.width \ - && pos.y >= room.y && pos.y <= room.y + room.height { - delete_map(current_room.tmap) - - path := strings.concatenate( - {res_dir, "/", room.file_name}, - allocator = context.temp_allocator, - ) - new_map, err := load_map(path) - if err != .NONE { - log.error("could not load new room") - return false - } - current_room.tmap = new_map - current_room.room = room - return true - } - } - return false -} diff --git a/src/world.odin b/src/world.odin new file mode 100644 index 0000000..d0e6c74 --- /dev/null +++ b/src/world.odin @@ -0,0 +1,87 @@ +package demonchime + +import "core:log" + +import rl "vendor:raylib" + +current_room: Room_Position_Resource + +open_room_at :: proc(pos: [2]i32) -> bool { + pos := pos + pos += {current_room.x, current_room.y} + + log.debug("trying to change room...", pos) + for room_pos in world { + if room_pos.id == current_room.id { + continue + } + + if pos.x >= room_pos.x && pos.x <= room_pos.x + room_pos.width \ + && pos.y >= room_pos.y && pos.y <= room_pos.y + room_pos.height { + current_room = room_pos + return true + } + } + return false +} + +open_room :: proc(id: Room_Id) -> bool { + for room_pos in world { + if room_pos.id == id { + current_room = room_pos + return true + } + } + + return false +} + +draw_room :: proc(id: Room_Id) { + draw_tile :: proc(x: i32, y: i32, tile_id: u32) { + tile := tiles[tile_id] + tileset := tilesets[tile.tileset] + rl.DrawTexturePro( + get_image(tileset.image), + rl.Rectangle{ + x = f32(tile.rect.start.x), + y = f32(tile.rect.start.y), + width = f32(tile.rect.size.x), + height = f32(tile.rect.size.y), + }, + rl.Rectangle{ + x = f32(x), + y = f32(y), + width = f32(tile.rect.size.x), + height = f32(tile.rect.size.y), + }, + {0, 0}, + 0, + rl.WHITE, + ) + } + + iterate_room_tiles(id, draw_tile) +} + +Map_Iterate_Callback :: proc(i32, i32, u32) + +iterate_room_tiles :: proc(id: Room_Id, callback: Map_Iterate_Callback) { + room := get_room(id) + + for layer in room.layers { + x: i32 = 0 + y: i32 = 0 + + for cell, i in layer { + if cell > 0 { + callback(x, y, cell - 1) + } + + x += room.tile_width + if x > (room.width - 1) * room.tile_width { + x = 0 + y += room.tile_height + } + } + } +} diff --git a/tools/compile_assets/loaders.odin b/tools/compile_assets/loaders.odin index 136ba07..648fe49 100644 --- a/tools/compile_assets/loaders.odin +++ b/tools/compile_assets/loaders.odin @@ -11,20 +11,35 @@ import "core:image/qoi" import ase "aseprite" import aseutil "aseprite/utils" -load_map :: proc(path: string, _: ^os.File, _: ^os.File) { - line := fmt.aprintf("#load(\"%v\")", path) - maps[filepath.stem(path)] = line - paths_to_res_type[path] = "Map_Id" +load_world :: proc(path: string, file: ^os.File, _: ^os.File) { + load_json_world(path) +} + +load_room :: proc(path: string, file: ^os.File, _: ^os.File) { + load_json_room(path, file) + paths_to_res_type[path] = "Room_Id" } load_tileset :: proc(path: string, _: ^os.File, _: ^os.File) { - line := fmt.aprintf("#load(\"%v\")", path) - tilesets[filepath.stem(path)] = line - paths_to_res_type[path] = "Tileset_Id" + load_json_tileset(path) } load_qoi :: proc(path: string, qoi: ^os.File, output: ^os.File) { - line := fmt.aprintf("{{data = #load(%w)}}", path) + rel_path, rel_err := filepath.rel( + output_dir, + path, + context.temp_allocator, + ) + if rel_err != nil { + die( + "Could not find relative path from %v to %v (%v)", + output_dir, + path, + rel_err, + ) + } + + line := fmt.aprintf("{{data = #load(%w), anim = .NONE}}", rel_path) images[filepath.stem(path)] = line paths_to_res_type[path] = "Image_Id" @@ -60,22 +75,39 @@ load_png :: proc(path: string, png_file: ^os.File, output: ^os.File) { die("Could not convert PNG to QOI (%v)", qoi_err) } - absolute_path, abs_ok := filepath.abs( + abs_path, found_abs := filepath.abs( compiled_path, - allocator = context.temp_allocator, + context.temp_allocator ) - if !abs_ok { - die("Could not find absolute path to a compiled file (%v)", compiled_path) + if !found_abs { + die("Could not find absolute path for %v", compiled_path) } - line := fmt.aprintf("{{data = #load(%w)}}", absolute_path) + rel_path, rel_err := filepath.rel( + output_dir, + abs_path, + context.temp_allocator, + ) + if rel_err != nil { + die( + "Could not find relative path from %v to %v (%v)", + output_dir, + compiled_path, + rel_err, + ) + } + + line := fmt.aprintf("{{data = #load(%w), anim = .NONE}}", rel_path) images[filepath.stem(path)] = line paths_to_res_type[path] = "Image_Id" } @(private="file") -load_sprite_sheet :: proc(path: string, doc: ^ase.Document) { +load_sprite_sheet :: proc( + path: string, + doc: ^ase.Document +) { sprite_sheet, ss_err := aseutil.create_sprite_sheet(doc, { size = {int(doc.header.width), int(doc.header.height)}, count = int(doc.header.frames), @@ -89,8 +121,6 @@ load_sprite_sheet :: proc(path: string, doc: ^ase.Document) { } defer aseutil.destroy(sprite_sheet) - fmt.println("loaded ss") - pixels := make( []image.RGBA_Pixel, sprite_sheet.width * sprite_sheet.height, @@ -126,15 +156,34 @@ load_sprite_sheet :: proc(path: string, doc: ^ase.Document) { die("Could not save spritesheet %v (%v)", path, qoi_err) } - absolute_path, abs_ok := filepath.abs( + abs_path, found_abs := filepath.abs( compiled_path, - allocator = context.temp_allocator, + context.temp_allocator + ) + if !found_abs { + die("Could not find absolute path for %v", compiled_path) + } + + rel_path, rel_err := filepath.rel( + output_dir, + abs_path, + context.temp_allocator, ) - if !abs_ok { - die("Could not find absolute path to a compiled file (%v)", compiled_path) + if rel_err != nil { + die( + "Could not find relative path from %v to %v (%v)", + output_dir, + compiled_path, + rel_err, + ) } - line := fmt.aprintf("{{data = #load(%w)}}", absolute_path) + anim_name := strings.to_screaming_snake_case( + filepath.stem(path), + allocator = context.temp_allocator, + ) + + line := fmt.aprintf("{{data = #load(%w), anim = .%v}}", rel_path, anim_name) images[filepath.stem(path)] = line paths_to_res_type[path] = "Image_Id" } @@ -191,9 +240,9 @@ load_ase :: proc(path: string, ase_file: ^os.File, output: ^os.File) { die("Could not unmarshal aseprite file %v (%v)", path, unmarshal_err) } - // Load sprite sheet - load_sprite_sheet(path, &doc) - // Load animation load_animation(path, &doc) + + // Load sprite sheet + load_sprite_sheet(path, &doc) } diff --git a/tools/compile_assets/main.odin b/tools/compile_assets/main.odin index 60c8a8c..68c5625 100644 --- a/tools/compile_assets/main.odin +++ b/tools/compile_assets/main.odin @@ -27,8 +27,8 @@ Image_Id :: enum { Animation_Id :: enum { <anim-enum>} -Map_Id :: enum { -<map-enum>} +Room_Id :: enum { +<room-enum>} Tileset_Id :: enum { <tileset-enum>} @@ -36,49 +36,46 @@ Tileset_Id :: enum { Resource_Id :: union { Image_Id, Animation_Id, - Map_Id, + Room_Id, Tileset_Id, } -images: [Image_Id]Image_Resource -animations: [Animation_Id]Animation_Resource -maps: [Map_Id]Map_Resource -tilesets: [Tileset_Id]Tileset_Resource +images: [Image_Id]Image_Resource = { +<image-load>} -path_to_id: map[string]Resource_Id +animations: [Animation_Id]Animation_Resource = { +<anim-load>} -load_resources :: proc() { - load_images() - load_anims() - load_maps() - load_tilesets() +rooms: [Room_Id]Room_Resource = { +<room-load>} - // Allow conversion from paths to a resource id, since it's a better way to - // reference resources in other resources (JSON is a good example). -<resource-paths>} +tilesets: [Tileset_Id]Tileset_Resource = { +<tileset-load>} -@(private="file") -load_images :: proc() { -<image-load>} +tiles: []Tile_Resource = { +<tiles-load>} -@(private="file") -load_anims :: proc() { -<anim-load>} +world: []Room_Position_Resource = { +<room-positions>} -@(private="file") -load_maps :: proc() { -<maps-load>} +path_to_id: map[string]Resource_Id = { +<resource-paths>} -@(private="file") -load_tilesets :: proc() { -<tileset-load>} +load_resources :: proc() { +} ` images: map[string]string animations: map[string]string -maps: map[string]string +rooms: map[string]string + +tiles: [dynamic]string tilesets: map[string]string +world: [dynamic]string + +output_dir: string + paths_to_res_type: map[string]string die :: proc(msg: string, args: ..any, exit_code := 1) { @@ -168,13 +165,11 @@ create_loads :: proc( for element in elements { load = strings.concatenate({ load, - " ", - map_name, - "[.", + " .", strings.to_upper_snake_case(element, context.temp_allocator), - "] = ", + " = ", elements[element], - "\n", + ",\n", }, allocator = context.temp_allocator) } return set_placeholder(content, placeholder, load) @@ -199,9 +194,29 @@ main :: proc() { print_help() } + // add dummy + append(&tiles, "") + + // Need to copy, cause you can't just free executable memory, you fool + no_anim_line := "{frame_count=1, frame_durations={100}, tags={}}" + no_anim_line_owned := make([]u8, len(no_anim_line)) + copy_from_string(no_anim_line_owned, no_anim_line) + animations["NONE"] = string(no_anim_line_owned) + input_dir := os.args[1] output_file_path := os.args[2] + abs_output_path, found_abs := filepath.abs( + output_file_path, + context.temp_allocator + ) + if !found_abs { + die("Could not find absolute path to output file") + } + + output_dir = filepath.dir(abs_output_path) + defer delete(output_dir) + output_file, open_err := os.open(output_file_path, {.Write, .Create, .Trunc}) if open_err != nil { die("Could not create output file '%v' (%v)", output_file_path, open_err) @@ -233,8 +248,8 @@ main :: proc() { loaders: map[string]Asset_Loader defer delete(loaders) - loaders["tmj"] = load_map - // loaders["world"] = load_json + loaders["tmj"] = load_room + loaders["world"] = load_world loaders["tsj"] = load_tileset loaders["qoi"] = load_qoi loaders["png"] = load_png @@ -248,7 +263,7 @@ main :: proc() { loader, has := &loaders[ext] if !has { fmt.printfln( - "%-25v Skipped\tNo loader for '%v'", + "%-25v Skipped\tNo loader for '.%v'", file.name, ext, ) @@ -262,7 +277,6 @@ main :: proc() { } defer os.close(f) - fmt.printfln("%-25v Loading...", file.name) loader^(file.fullpath, f, output_file) fmt.printfln("%-25v Loaded", file.name) @@ -276,14 +290,39 @@ main :: proc() { content = create_enum(content, "<image-enum>", images) content = create_enum(content, "<anim-enum>", animations) - content = create_enum(content, "<map-enum>", maps) + content = create_enum(content, "<room-enum>", rooms) content = create_enum(content, "<tileset-enum>", tilesets) content = create_loads(content, "images", "<image-load>", images) content = create_loads(content, "animations", "<anim-load>", animations) - content = create_loads(content, "maps", "<maps-load>", maps) + content = create_loads(content, "rooms", "<room-load>", rooms) content = create_loads(content, "tilesets", "<tileset-load>", tilesets) + tile_loads := "" + for tile, i in tiles { + if i == 0 { + continue + } + tile_loads = strings.concatenate({ + tile_loads, + " ", + tile, + ",\n", + }, allocator = context.temp_allocator) + } + content = set_placeholder(content, "<tiles-load>", tile_loads) + + room_pos_loads := "" + for room_pos, i in world { + room_pos_loads = strings.concatenate({ + room_pos_loads, + " ", + room_pos, + ",\n", + }, allocator = context.temp_allocator) + } + content = set_placeholder(content, "<room-positions>", room_pos_loads) + res_paths := "" cwd, _ := os.get_working_directory(context.temp_allocator) @@ -300,13 +339,13 @@ main :: proc() { rel_path, _ := filepath.rel(cwd, file.fullpath, context.temp_allocator) res_paths = strings.concatenate({ res_paths, - " path_to_id[\"", + " \"", rel_path, - "\"] = ", + "\" = ", res_type, ".", res_name, - "\n", + ",\n", }, allocator = context.temp_allocator) } content = set_placeholder(content, "<resource-paths>", res_paths) @@ -319,8 +358,8 @@ main :: proc() { for anim in animations { delete(animations[anim]) } - for tmap in maps { - delete(maps[tmap]) + for room in rooms { + delete(rooms[room]) } free_all(context.temp_allocator) diff --git a/tools/compile_assets/tiled.odin b/tools/compile_assets/tiled.odin new file mode 100644 index 0000000..7752273 --- /dev/null +++ b/tools/compile_assets/tiled.odin @@ -0,0 +1,447 @@ +package assets_gen + +import os "core:os/os2" +import "core:fmt" +import "core:encoding/json" +import "core:path/filepath" +import "core:strings" + +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: 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_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: any, +} + +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, +} + +gids: map[string]Gids + +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_screaming_snake_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( + tileset_enum: string, + rect: Rect, + collisions: []Rect, +) -> string { + line := fmt.aprintf( + "{{tileset=.%v, rect=%w, id=%w, collisions=%w}}", + tileset_enum, + rect, + len(tiles), + collisions, + ) + append(&tiles, line) + return line +} + +load_json_tileset :: proc(path: string) { + 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) + } + + tileset_enum := strings.to_screaming_snake_case( + filepath.stem(path), + allocator = context.temp_allocator, + ) + + image_enum := strings.to_screaming_snake_case( + filepath.stem(json_dat.image), + allocator = context.temp_allocator, + ) + + tiles_x := json_dat.imagewidth / json_dat.tilewidth + tiles_y := json_dat.imageheight / json_dat.tileheight + + local_tiles := make([dynamic]string, allocator = context.temp_allocator) + + tile_size: [2]f32 = {f32(json_dat.tilewidth), f32(json_dat.tileheight)} + + first_gid := u32(len(tiles)) + + gids[filepath.stem(path)] = { + first = first_gid, + last = u32(len(tiles) + int(json_dat.tilecount)), + } + + 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)} + } + ) + } + } + + for y in 0..<tiles_y { + for x in 0..<tiles_x { + append(&local_tiles, create_tile( + tileset_enum, + Rect{ + start = {f32(x * json_dat.tilewidth), f32(y * json_dat.tileheight)}, + size = tile_size, + }, + collisions[y * tiles_x + x][:], + )) + } + } + + gids := make([]u32, len(local_tiles)) + for tile, i in local_tiles { + gids[i] = first_gid + u32(i) + } + + line := fmt.aprintf("{{tiles=%w, image=.%v}}", gids, image_enum) + tilesets[filepath.stem(path)] = line +} + +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 + + 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 := gids[tileset_name] + if !exists { + load_json_tileset(strings.concatenate({ + filepath.dir(path), + "/", + tileset.source, + })) + gid = gids[tileset_name] + } + + for i in gid.first..<gid.last { + tiled_to_real_gid[id] = i + id += 1 + } + } + + layers: [dynamic][]u32 + defer delete(layers) + + for layer in json_dat.layers { + if strings.compare(layer.type, "tilelayer") != 0 { + continue + } + + json_layer_dat, ok := layer.data.([]i32) + if !ok { + continue + } + + data := make([]u32, len(json_layer_dat), allocator = context.temp_allocator) + + for cell, i in json_layer_dat { + data[i] = tiled_to_real_gid[cell] + } + + append(&layers, data) + } + + line := fmt.aprintf( + "{{width=%v, height=%v, tile_width=%v, tile_height=%v, layers=%w, objects={{}}, background_image=nil}}", + width, height, + tile_width, tile_height, + layers[:], + ) + rooms[filepath.stem(path)] = line +} |
