From cc709ba84ac219d5a7e162c55df0cc992d46bda7 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sat, 19 Nov 2022 12:15:25 +0200 Subject: [PATCH] start from scratch --- src/conf.lua | 4 +- src/data/audio/switch-5.ogg | Bin 7814 -> 0 bytes src/data/audio/switch-7.ogg | Bin 7921 -> 0 bytes src/data/controls.lua | 14 - src/data/fonts/kenney-future.ttf | Bin 34116 -> 0 bytes src/data/init.lua | 119 -- src/data/main.tiled-project | 11 - src/data/maps/main-menu.lua | 179 --- src/data/maps/main-menu.tmx | 49 - src/data/maps/playground.lua | 187 --- src/data/maps/playground.tmx | 87 -- src/data/sprites/bolt-ghost.aseprite | Bin 650 -> 0 bytes src/data/sprites/bolt.aseprite | Bin 857 -> 0 bytes src/data/sprites/player.aseprite | Bin 1328 -> 0 bytes src/data/sprites/title.aseprite | Bin 1661 -> 0 bytes src/data/tilesets/roguelike-dungeon.lua | 128 -- src/data/tilesets/roguelike-dungeon.png | Bin 31160 -> 0 bytes src/data/tilesets/roguelike-dungeon.tsx | 87 -- src/data/tilesets/roguelike-indoors.png | Bin 20867 -> 0 bytes src/data/tilesets/roguelike-indoors.tsx | 4 - src/data/ui/panels/blue-pressed.9.png | Bin 7552 -> 0 bytes src/data/ui/panels/blue.9.png | Bin 8149 -> 0 bytes src/data/ui/prompts/dark/a.png | Bin 177 -> 0 bytes src/data/ui/prompts/dark/d.png | Bin 173 -> 0 bytes src/data/ui/prompts/dark/s.png | Bin 181 -> 0 bytes src/data/ui/prompts/dark/spacebar.png | Bin 657 -> 0 bytes src/data/ui/prompts/dark/w.png | Bin 175 -> 0 bytes src/groups.lua | 14 - src/lib/ase-loader.lua | 374 ------ src/lib/binser.lua | 782 ----------- src/lib/cargo.lua | 104 -- src/lib/json.lua | 388 ------ src/lib/slicy.lua | 497 ------- src/lib/sti/graphics.lua | 132 -- src/lib/sti/init.lua | 1631 ----------------------- src/lib/sti/plugins/box2d.lua | 323 ----- src/lib/sti/plugins/bump.lua | 235 ---- src/lib/sti/utils.lua | 217 --- src/main.lua | 9 +- src/states/main-menu.lua | 165 --- src/states/main.lua | 77 +- src/systems/bolt.lua | 91 -- src/systems/debug.lua | 84 -- src/systems/map.lua | 54 - src/systems/multiplayer.lua | 151 --- src/systems/physics.lua | 176 --- src/systems/player.lua | 261 ---- src/systems/screen-scaler.lua | 106 -- src/systems/sprite.lua | 103 -- src/systems/ui.lua | 111 -- 50 files changed, 6 insertions(+), 6948 deletions(-) delete mode 100644 src/data/audio/switch-5.ogg delete mode 100644 src/data/audio/switch-7.ogg delete mode 100644 src/data/controls.lua delete mode 100644 src/data/fonts/kenney-future.ttf delete mode 100644 src/data/init.lua delete mode 100644 src/data/main.tiled-project delete mode 100644 src/data/maps/main-menu.lua delete mode 100644 src/data/maps/main-menu.tmx delete mode 100644 src/data/maps/playground.lua delete mode 100644 src/data/maps/playground.tmx delete mode 100644 src/data/sprites/bolt-ghost.aseprite delete mode 100644 src/data/sprites/bolt.aseprite delete mode 100644 src/data/sprites/player.aseprite delete mode 100644 src/data/sprites/title.aseprite delete mode 100644 src/data/tilesets/roguelike-dungeon.lua delete mode 100644 src/data/tilesets/roguelike-dungeon.png delete mode 100644 src/data/tilesets/roguelike-dungeon.tsx delete mode 100644 src/data/tilesets/roguelike-indoors.png delete mode 100644 src/data/tilesets/roguelike-indoors.tsx delete mode 100644 src/data/ui/panels/blue-pressed.9.png delete mode 100644 src/data/ui/panels/blue.9.png delete mode 100644 src/data/ui/prompts/dark/a.png delete mode 100644 src/data/ui/prompts/dark/d.png delete mode 100644 src/data/ui/prompts/dark/s.png delete mode 100644 src/data/ui/prompts/dark/spacebar.png delete mode 100644 src/data/ui/prompts/dark/w.png delete mode 100644 src/groups.lua delete mode 100644 src/lib/ase-loader.lua delete mode 100644 src/lib/binser.lua delete mode 100644 src/lib/cargo.lua delete mode 100644 src/lib/json.lua delete mode 100644 src/lib/slicy.lua delete mode 100644 src/lib/sti/graphics.lua delete mode 100644 src/lib/sti/init.lua delete mode 100644 src/lib/sti/plugins/box2d.lua delete mode 100644 src/lib/sti/plugins/bump.lua delete mode 100644 src/lib/sti/utils.lua delete mode 100644 src/states/main-menu.lua delete mode 100644 src/systems/bolt.lua delete mode 100644 src/systems/debug.lua delete mode 100644 src/systems/map.lua delete mode 100644 src/systems/multiplayer.lua delete mode 100644 src/systems/physics.lua delete mode 100644 src/systems/player.lua delete mode 100644 src/systems/screen-scaler.lua delete mode 100644 src/systems/sprite.lua delete mode 100644 src/systems/ui.lua diff --git a/src/conf.lua b/src/conf.lua index e0eb56c..0bd4901 100644 --- a/src/conf.lua +++ b/src/conf.lua @@ -1,11 +1,9 @@ function love.conf(t) - t.title = "Love2D project" + t.title = "Dodge Bolt" t.console = true t.window.width = 854 t.window.height = 480 t.window.resizable = true - - t.modules.joystick = false end diff --git a/src/data/audio/switch-5.ogg b/src/data/audio/switch-5.ogg deleted file mode 100644 index 799e3277c0c8826589216ec5518801293e13d5d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7814 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 a6v#clA4L35zdJgAkN6k{4T$9*<-Y*ZXBEW& diff --git a/src/data/controls.lua b/src/data/controls.lua deleted file mode 100644 index 232a8a7..0000000 --- a/src/data/controls.lua +++ /dev/null @@ -1,14 +0,0 @@ - -return { - move_up = "w", - move_down = "s", - move_left = "a", - move_right = "d", - - aim_up = "up", - aim_down = "down", - aim_left = "left", - aim_right = "right", - - shoot = "space" -} diff --git a/src/data/fonts/kenney-future.ttf b/src/data/fonts/kenney-future.ttf deleted file mode 100644 index 1dbb2dd5f7ca220796c5fae5bcbd4cfe09aa5a77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34116 zcmeHQYm8l2bzXO7{LaJRyq(8!Vuz5%9>+<5xG7ACaS{>=#3oJKG%#b2X9jy7nHl4l zM|CP8#E(iK6``m_h@z;fN?P#)wfVshBcZA)`GLTt0V3g2K>R2OwN#1}@a^}lwb$PJ zJnq~(*n$epx!(8evma}H>sxC-&bfE4r6VGnrIcYQ-g#(r-^i!ee@|rV0hGS)$n5yS zKmFrVZ1KFv$Y-WboIdv9y~}Sy{Z~bvzGixI{OEhPc0Vby?jf{!!!!zp%I-&SUE+G@ z^z8C8pZLUo{{+|1i3}e-F@I!SI{))|kw1GG*VD7(&n$GmBY!QjX(QSfbK|pRVpL?~>%h}g4v3vw6ukV$ww{u(C?CB+g z|8n*W8Ct~Hpa*ez`Fe2$MD{e-z1j|KN<3y<@3YSA%BT{cw}?PUn=h!`B=zbCQpuhE9AGyW9#-= z9{9Je`{)Dn3#S)nrlyyRM<(ayCQldp_TG6{$R0eod~$K}vB{~EC&m{K&d)6$T3$SP zB(}8bO4}kdetdFqX=Z+|xO2~5tKV&nGa)le#dxv2IDT|;c6{-8F@LN$cW8iHD|_mlyVrjvfP!CB?Dl$o%Z6iT_3g#W#yn<4eWF$(a+& z#i^O)X_KmbHXoQ=nwgrLTr7@GE*)8%S)hXbwEgkPBg^xPh2h-Xt}N+BucgtY>G@Nm z_wK#>t~cH@dR*DteM~8PU~v-2KQLK5Fh4tsmc{+c%ZoD;C#kGBG(EmJS=@hO=J;gL zawIf|J#4*aesOB_#LSV&xuwaa(TUT$m&Ql$+Ov1`ASF)BOchU`96xe=W^StZ;N-&e z>BXhu{qwMN_B70XrkI#K`oO8Ktmo@seN=4C-n%c9K4luXOA6!M5nVpPoG{WPxj z$zHir?y8j@l#^(C68$FSF{CNvPheav%0Wn+L){^yMU)EP|J~4@aJ3_ufrJ7`3&l4sMHOdwh_l1 z`YZ=5{^DQG12 z3D`ITjr7$ip~4eX|?@Rx&mPyhL57y-Uzfk1s?E_M^s_B+K;KytdBy+`KBw z+Uz#ZEy3<3$eIWGQMngByc_ZLM!5%9$D!S0Z#TG$3?Nb&YsUEpkSjEHXTc)vCNK8` zHEo|!hTJy=d^xTDWuN=O+l*>qtS2v#k6gO>$7SzPHl~1&y^koyCA`y;35=56$c-a^ zmqz3$d@&=2aRRw1=zSWNjHBmqT+eCA2ho24$T;?h{{7HlKAqKZM{hra@(Hv%3R_R1 z?>mq?0iLF@nhXtnsPjo#hkNm`aM~G*=w0$6S2x=Wi+m2Z^<#YA5RQX~4nHPiQoOo; zsASP_*};3~Rpy6EEh~`a9Y2p_kk@vsW1+Sm#tOVeUW@fpI@JTqpcRaXbpI0VzlQTY zI3LTd|El|MxcUD+N^Ye<)c#SZKh-!-qE^uQKf3>Z{(iRoB@%k61;kD)2?gs)bU@jo{NWrloQERQ&WvHD z4W>P4;B{yPJ4#|b4>1XoS}h3&BcaA=Jqi>C-!x!*Mt^7!RtqIoovI4 z2U_&vkx@)(-U3|x5WRn@B`QbRO#GoS`dGZHyR~!4Qbol}6^MJ?QZDBOUcm;2v*g}L z-iuMK@-!l#r{Yt1-C9-U+Zc&jw7<8^B)EmBM=kB<*-OvosAwZv0RI)pWdoYl!jzh=T7V6Col)K**eYhr09HpJvLKU+N70B}yW;0@E1zV#o zj0_gR=U>BU{0__6hMogI7%4=+LNLJ!yi2 z0S77?@-J=C^FZ3a2H^+k7s`nE=#I4xX zHpPayLv6FSh!QeXN5Ie%OV9vpJRW}e1IX*}gN zMypVV_|+EYX%7yc*J4B&!%5c$*`9egX-hfEvAdx#B9bhxsf@#~LFac-6UQcl0rOnh z*4h!$M~1cHk87Gd7#ARC1v+|+fS%;6FcL@(4vh~o#UpW3xzcuc%jR?zRc6qfsc5kd zV4Dj9(~�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 diff --git a/src/data/init.lua b/src/data/init.lua deleted file mode 100644 index ea08293..0000000 --- a/src/data/init.lua +++ /dev/null @@ -1,119 +0,0 @@ -local cargo = require("lib.cargo") -local aseLoader = require("lib.ase-loader") -local pprint = require("lib.pprint") -local slicy = require("lib.slicy") -local ripple = require("lib.ripple") - -love.graphics.setDefaultFilter("nearest", "nearest") - --- TODO: Maybe add a texture atlas library for packing frame data --- TODO: For production maybe use another type of loader? (https://github.com/elloramir/packer, https://github.com/EngineerSmith/Runtime-TextureAtlas) -local function loadAsepriteSprite(filename) - local ase = aseLoader(filename) - - local LAYER_CHUNK = 0x2004 - local CEL_CHUNK = 0x2005 - local TAG_CHUNK = 0x2018 - local SLICE_CHUNK = 0x2022 - local USER_CHUNK = 0x2020 - - local frames = {} - local tags = {} - local layers = {} - local slices = {} - - love.graphics.push("transform") - love.graphics.origin() - - local width = ase.header.width - local height = ase.header.height - for i, ase_frame in ipairs(ase.header.frames) do - for chunk_i, chunk in ipairs(ase_frame.chunks) do - local user_data = ase_frame.chunks[chunk_i+1] - if user_data and user_data.type ~= USER_CHUNK then - user_data = nil - end - - if chunk.type == CEL_CHUNK then - local cel = chunk.data - local buffer = love.data.decompress("data", "zlib", cel.data) - local data = love.image.newImageData(cel.width, cel.height, "rgba8", buffer) - local image = love.graphics.newImage(data) - local frame = frames[i] - if not frame then - frame = { - image = love.graphics.newCanvas(width, height), - duration = ase_frame.frame_duration / 1000 - } - frame.image:setFilter("nearest", "nearest") - frames[i] = frame - end - - -- you need to draw in a canvas before. - -- frame images can be of different sizes - -- but never bigger than the header width and height - love.graphics.setCanvas(frame.image) - love.graphics.draw(image, cel.x, cel.y) - love.graphics.setCanvas() - elseif chunk.type == TAG_CHUNK then - for j, tag in ipairs(chunk.data.tags) do - -- aseprite use 0 notation to begin - -- but in lua, everthing starts in 1 - tag.to = tag.to + 1 - tag.from = tag.from + 1 - tag.frames = tag.to - tag.from - tags[tag.name] = tag - end - elseif chunk.type == LAYER_CHUNK then - table.insert(layers, chunk.data) - elseif chunk.type == SLICE_CHUNK then - local slice_key = chunk.data.keys[1] - table.insert(slices, { - name = chunk.data.name, - x = slice_key.x, - y = slice_key.y, - width = slice_key.width, - height = slice_key.height, - user_data = user_data and user_data.data.text - }) - end - end - end - - love.graphics.push() - - local sprite = { - width = width, - height = height, - slices = slices, - variants = {} - } - - for _, tag in pairs(tags) do - local variant = {} - sprite.variants[tag.name] = variant - for i=tag.from,tag.to do - table.insert(variant, frames[i]) - end - end - - if not sprite.variants.default then - sprite.variants.default = frames - end - - return sprite -end - -return cargo.init{ - dir = "data", - loaders = { - aseprite = loadAsepriteSprite, - ase = loadAsepriteSprite, - ["9.png"] = function(filename) - return slicy.load(filename) - end, - ogg = function(filename) - return ripple.newSound(love.audio.newSource(filename, 'static')) - end - } -} diff --git a/src/data/main.tiled-project b/src/data/main.tiled-project deleted file mode 100644 index d58954a..0000000 --- a/src/data/main.tiled-project +++ /dev/null @@ -1,11 +0,0 @@ -{ - "automappingRulesFile": "", - "commands": [ - ], - "extensionsPath": "extensions", - "folders": [ - "." - ], - "propertyTypes": [ - ] -} diff --git a/src/data/maps/main-menu.lua b/src/data/maps/main-menu.lua deleted file mode 100644 index 5cb3547..0000000 --- a/src/data/maps/main-menu.lua +++ /dev/null @@ -1,179 +0,0 @@ -return { - version = "1.9", - luaversion = "5.1", - tiledversion = "1.9.0", - class = "", - orientation = "orthogonal", - renderorder = "right-down", - width = 16, - height = 9, - tilewidth = 16, - tileheight = 16, - nextlayerid = 5, - nextobjectid = 13, - properties = {}, - tilesets = { - { - name = "roguelike-dungeon", - firstgid = 1, - filename = "../tilesets/roguelike-dungeon.tsx", - exportfilename = "../tilesets/roguelike-dungeon.lua" - } - }, - layers = { - { - type = "tilelayer", - x = 0, - y = 0, - width = 16, - height = 9, - id = 1, - name = "ground", - class = "", - visible = true, - opacity = 1, - offsetx = 0, - offsety = 0, - parallaxx = 1, - parallaxy = 1, - properties = {}, - encoding = "lua", - data = { - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68 - } - }, - { - type = "tilelayer", - x = 0, - y = 0, - width = 16, - height = 9, - id = 3, - name = "decorations", - class = "", - visible = true, - opacity = 1, - offsetx = 0, - offsety = 0, - parallaxx = 1, - parallaxy = 1, - properties = {}, - encoding = "lua", - data = { - 0, 0, 66, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, - 0, 0, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 66, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 66, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 66, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 119, 119, 119, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 119, 0, 0, 0, 0, 0, 0, 0, 0 - } - }, - { - type = "objectgroup", - draworder = "topdown", - id = 2, - name = "metadata", - class = "", - visible = true, - opacity = 1, - offsetx = 0, - offsety = 0, - parallaxx = 1, - parallaxy = 1, - properties = {}, - objects = { - { - id = 2, - name = "Spawnpoint", - class = "", - shape = "point", - x = 140.167, - y = 90.1667, - width = 0, - height = 0, - rotation = 0, - visible = true, - properties = {} - }, - { - id = 6, - name = "title", - class = "", - shape = "rectangle", - x = 128, - y = 16, - width = 96, - height = 48, - rotation = 0, - visible = true, - properties = { - ["sprite"] = "title" - } - }, - { - id = 7, - name = "Host", - class = "button", - shape = "rectangle", - x = 16, - y = 16, - width = 96, - height = 32, - rotation = 0, - visible = true, - properties = {} - }, - { - id = 8, - name = "Join", - class = "button", - shape = "rectangle", - x = 16, - y = 56, - width = 96, - height = 32, - rotation = 0, - visible = true, - properties = {} - }, - { - id = 9, - name = "Quit", - class = "button", - shape = "rectangle", - x = 16, - y = 96, - width = 96, - height = 32, - rotation = 0, - visible = true, - properties = {} - }, - { - id = 12, - name = "controls", - class = "", - shape = "point", - x = 225, - y = 111.667, - width = 0, - height = 0, - rotation = 0, - visible = true, - properties = {} - } - } - } - } -} diff --git a/src/data/maps/main-menu.tmx b/src/data/maps/main-menu.tmx deleted file mode 100644 index 666792c..0000000 --- a/src/data/maps/main-menu.tmx +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - -68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68, -68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68, -68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68, -68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68, -68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68, -68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68, -68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68, -68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68, -68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68 - - - - -0,0,66,0,66,0,0,0,0,0,0,0,0,96,0,0, -0,0,66,66,66,66,0,0,0,0,0,0,0,0,0,0, -0,0,0,66,66,66,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,66,66,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,66,66,0, -0,0,0,0,0,0,0,0,0,0,0,0,96,0,66,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,119,119,119,0,0,0,0,0,0, -0,0,0,0,0,0,0,119,0,0,0,0,0,0,0,0 - - - - - - - - - - - - - - - - - - - diff --git a/src/data/maps/playground.lua b/src/data/maps/playground.lua deleted file mode 100644 index 32c56ac..0000000 --- a/src/data/maps/playground.lua +++ /dev/null @@ -1,187 +0,0 @@ -return { - version = "1.9", - luaversion = "5.1", - tiledversion = "1.9.0", - class = "", - orientation = "orthogonal", - renderorder = "right-down", - width = 30, - height = 20, - tilewidth = 16, - tileheight = 16, - nextlayerid = 5, - nextobjectid = 4, - properties = {}, - tilesets = { - { - name = "roguelike-dungeon", - firstgid = 1, - filename = "../tilesets/roguelike-dungeon.tsx", - exportfilename = "../tilesets/roguelike-dungeon.lua" - } - }, - layers = { - { - type = "tilelayer", - x = 0, - y = 0, - width = 30, - height = 20, - id = 3, - name = "ground", - class = "", - visible = true, - opacity = 1, - offsetx = 0, - offsety = 0, - parallaxx = 1, - parallaxy = 1, - properties = {}, - encoding = "lua", - data = { - 484, 481, 485, 481, 485, 484, 484, 483, 484, 483, 481, 484, 481, 484, 485, 485, 482, 483, 483, 481, 485, 485, 483, 482, 481, 484, 484, 483, 485, 481, - 483, 481, 482, 481, 484, 484, 483, 481, 483, 485, 485, 482, 483, 482, 482, 484, 481, 481, 485, 485, 484, 481, 485, 483, 481, 485, 483, 481, 484, 482, - 482, 485, 485, 483, 483, 485, 481, 481, 484, 481, 483, 485, 482, 485, 484, 482, 485, 482, 482, 482, 484, 481, 485, 484, 484, 481, 482, 485, 482, 482, - 482, 483, 484, 482, 481, 485, 482, 481, 485, 483, 483, 483, 481, 481, 482, 483, 485, 481, 481, 485, 481, 484, 485, 484, 484, 484, 481, 482, 482, 481, - 485, 484, 484, 483, 483, 484, 485, 483, 482, 482, 484, 484, 484, 482, 482, 485, 485, 483, 483, 485, 484, 481, 484, 485, 481, 482, 485, 482, 482, 485, - 484, 483, 485, 481, 483, 483, 483, 484, 481, 485, 483, 482, 483, 483, 483, 483, 484, 481, 483, 485, 482, 485, 484, 482, 481, 482, 484, 481, 483, 481, - 483, 482, 483, 484, 481, 481, 483, 484, 485, 481, 482, 483, 483, 481, 483, 483, 482, 482, 484, 485, 484, 482, 482, 483, 484, 484, 483, 483, 483, 481, - 485, 482, 484, 482, 484, 482, 484, 484, 483, 481, 483, 484, 483, 481, 482, 481, 482, 485, 483, 482, 483, 481, 482, 481, 481, 484, 481, 482, 481, 481, - 481, 483, 482, 483, 481, 484, 481, 483, 484, 482, 483, 485, 483, 482, 485, 484, 481, 482, 481, 484, 483, 481, 484, 482, 482, 483, 483, 485, 481, 481, - 485, 483, 485, 482, 485, 483, 485, 481, 485, 482, 485, 483, 483, 482, 484, 483, 484, 483, 482, 484, 482, 481, 484, 483, 481, 484, 481, 485, 484, 485, - 483, 485, 483, 482, 483, 484, 484, 482, 484, 483, 483, 483, 484, 481, 483, 483, 483, 483, 481, 482, 482, 482, 481, 481, 483, 483, 481, 481, 484, 482, - 485, 485, 485, 481, 484, 484, 483, 482, 484, 482, 483, 485, 484, 482, 481, 484, 483, 482, 483, 483, 485, 485, 485, 484, 484, 484, 481, 485, 482, 484, - 484, 482, 482, 485, 483, 484, 485, 482, 481, 484, 485, 482, 484, 482, 481, 481, 481, 481, 484, 481, 485, 485, 483, 481, 483, 482, 482, 485, 484, 482, - 482, 481, 483, 485, 485, 485, 481, 483, 484, 481, 482, 483, 485, 483, 482, 485, 481, 485, 485, 483, 482, 484, 482, 483, 482, 485, 483, 482, 485, 481, - 481, 482, 485, 483, 482, 481, 481, 485, 481, 484, 485, 483, 485, 485, 484, 485, 482, 482, 484, 481, 484, 483, 483, 485, 485, 482, 485, 481, 483, 484, - 483, 483, 483, 482, 484, 484, 483, 485, 482, 484, 485, 482, 484, 483, 482, 485, 485, 484, 482, 485, 481, 481, 484, 485, 481, 482, 484, 485, 484, 483, - 483, 484, 481, 482, 482, 483, 485, 483, 483, 482, 481, 483, 483, 485, 484, 483, 484, 482, 485, 482, 485, 481, 485, 483, 485, 485, 481, 484, 483, 484, - 483, 484, 483, 481, 484, 484, 482, 481, 481, 482, 481, 482, 485, 484, 482, 484, 484, 483, 484, 484, 483, 483, 484, 485, 485, 485, 484, 485, 481, 482, - 481, 484, 484, 482, 483, 484, 482, 485, 485, 485, 481, 485, 485, 484, 481, 481, 483, 484, 484, 483, 481, 485, 481, 481, 484, 482, 485, 481, 482, 483, - 483, 485, 482, 481, 485, 483, 483, 482, 485, 483, 481, 484, 484, 485, 484, 484, 485, 484, 483, 483, 481, 483, 484, 484, 484, 485, 482, 483, 481, 482 - } - }, - { - type = "tilelayer", - x = 0, - y = 0, - width = 30, - height = 20, - id = 1, - name = "walls", - class = "", - visible = true, - opacity = 1, - offsetx = 0, - offsety = 0, - parallaxx = 1, - parallaxy = 1, - properties = {}, - encoding = "lua", - data = { - 13, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 45, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 14, - 41, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, - 41, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, - 41, 0, 0, 0, 10, 11, 43, 0, 0, 0, 0, 0, 0, 0, 0, 42, 11, 11, 11, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, - 41, 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, 41, - 41, 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, 41, - 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 11, 12, 0, 0, 0, 0, 41, - 41, 0, 0, 0, 0, 10, 11, 14, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 13, 43, 0, 0, 0, 0, 0, 0, 41, - 41, 0, 0, 0, 0, 0, 0, 42, 14, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 41, - 41, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 41, - 41, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 41, - 41, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 41, - 41, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 41, 0, 0, 0, 9, 0, 0, 0, 42, 14, 0, 0, 0, 0, 0, 0, 41, - 41, 0, 0, 0, 0, 0, 0, 13, 43, 0, 0, 0, 0, 38, 0, 0, 0, 41, 0, 0, 0, 0, 42, 11, 12, 0, 0, 0, 0, 41, - 41, 0, 0, 0, 0, 10, 11, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, - 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, - 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 11, 11, 12, 0, 0, 41, - 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 41, - 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 41, - 42, 11, 11, 11, 11, 11, 11, 11, 11, 11, 16, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 16, 11, 11, 11, 11, 11, 43 - } - }, - { - type = "tilelayer", - x = 0, - y = 0, - width = 30, - height = 20, - id = 4, - name = "decorations", - class = "", - visible = true, - opacity = 1, - offsetx = 0, - offsety = 0, - parallaxx = 1, - parallaxy = 1, - properties = {}, - encoding = "lua", - data = { - 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, 0, - 0, 0, 0, 0, 60, 61, 0, 0, 0, 66, 65, 0, 0, 0, 509, 0, 62, 0, 0, 478, 0, 0, 0, 0, 479, 0, 478, 0, 478, 0, - 0, 0, 480, 60, 0, 0, 0, 0, 0, 65, 65, 65, 480, 0, 0, 0, 505, 505, 505, 505, 505, 505, 505, 0, 0, 480, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 480, 0, 0, 65, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 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, 480, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 66, 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, 66, 66, 66, 0, 0, 0, 0, 0, 478, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 66, 0, 66, 0, 0, 479, 478, 0, 0, 0, 0, 0, 0, - 0, 0, 480, 0, 0, 0, 0, 422, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 478, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 478, 478, 0, 0, 0, 0, 0, - 0, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 480, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 65, 65, 65, 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, 65, 0, 0, 0, 0, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 483, 0, 0, 66, 66, 0, 0, 0, 0, 0, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 478, 479, 505, 505, 505, 505, 505, 505, 0, 0, 0, 0, 0, 0, 0, 65, 65, 65, 480, 0, 0, 0, 0, 451, 0, 0, 0, 0, 0, - 0, 478, 478, 478, 0, 0, 0, 0, 0, 0, 0, 479, 479, 478, 0, 0, 0, 0, 0, 0, 0, 0, 509, 0, 0, 0, 0, 478, 478, 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, 0, 0 - } - }, - { - type = "objectgroup", - draworder = "topdown", - id = 2, - name = "spawnpoints", - class = "", - visible = true, - opacity = 1, - offsetx = 0, - offsety = 0, - parallaxx = 1, - parallaxy = 1, - properties = {}, - objects = { - { - id = 1, - name = "", - class = "", - shape = "point", - x = 67.3333, - y = 173.333, - width = 0, - height = 0, - rotation = 0, - visible = true, - properties = {} - }, - { - id = 2, - name = "", - class = "", - shape = "point", - x = 409.333, - y = 163.333, - width = 0, - height = 0, - rotation = 0, - visible = true, - properties = {} - } - } - } - } -} diff --git a/src/data/maps/playground.tmx b/src/data/maps/playground.tmx deleted file mode 100644 index e3d5b61..0000000 --- a/src/data/maps/playground.tmx +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - -484,481,485,481,485,484,484,483,484,483,481,484,481,484,485,485,482,483,483,481,485,485,483,482,481,484,484,483,485,481, -483,481,482,481,484,484,483,481,483,485,485,482,483,482,482,484,481,481,485,485,484,481,485,483,481,485,483,481,484,482, -482,485,485,483,483,485,481,481,484,481,483,485,482,485,484,482,485,482,482,482,484,481,485,484,484,481,482,485,482,482, -482,483,484,482,481,485,482,481,485,483,483,483,481,481,482,483,485,481,481,485,481,484,485,484,484,484,481,482,482,481, -485,484,484,483,483,484,485,483,482,482,484,484,484,482,482,485,485,483,483,485,484,481,484,485,481,482,485,482,482,485, -484,483,485,481,483,483,483,484,481,485,483,482,483,483,483,483,484,481,483,485,482,485,484,482,481,482,484,481,483,481, -483,482,483,484,481,481,483,484,485,481,482,483,483,481,483,483,482,482,484,485,484,482,482,483,484,484,483,483,483,481, -485,482,484,482,484,482,484,484,483,481,483,484,483,481,482,481,482,485,483,482,483,481,482,481,481,484,481,482,481,481, -481,483,482,483,481,484,481,483,484,482,483,485,483,482,485,484,481,482,481,484,483,481,484,482,482,483,483,485,481,481, -485,483,485,482,485,483,485,481,485,482,485,483,483,482,484,483,484,483,482,484,482,481,484,483,481,484,481,485,484,485, -483,485,483,482,483,484,484,482,484,483,483,483,484,481,483,483,483,483,481,482,482,482,481,481,483,483,481,481,484,482, -485,485,485,481,484,484,483,482,484,482,483,485,484,482,481,484,483,482,483,483,485,485,485,484,484,484,481,485,482,484, -484,482,482,485,483,484,485,482,481,484,485,482,484,482,481,481,481,481,484,481,485,485,483,481,483,482,482,485,484,482, -482,481,483,485,485,485,481,483,484,481,482,483,485,483,482,485,481,485,485,483,482,484,482,483,482,485,483,482,485,481, -481,482,485,483,482,481,481,485,481,484,485,483,485,485,484,485,482,482,484,481,484,483,483,485,485,482,485,481,483,484, -483,483,483,482,484,484,483,485,482,484,485,482,484,483,482,485,485,484,482,485,481,481,484,485,481,482,484,485,484,483, -483,484,481,482,482,483,485,483,483,482,481,483,483,485,484,483,484,482,485,482,485,481,485,483,485,485,481,484,483,484, -483,484,483,481,484,484,482,481,481,482,481,482,485,484,482,484,484,483,484,484,483,483,484,485,485,485,484,485,481,482, -481,484,484,482,483,484,482,485,485,485,481,485,485,484,481,481,483,484,484,483,481,485,481,481,484,482,485,481,482,483, -483,485,482,481,485,483,483,482,485,483,481,484,484,485,484,484,485,484,483,483,481,483,484,484,484,485,482,483,481,482 - - - - -13,11,11,11,11,11,11,11,11,11,11,11,11,11,11,45,11,11,11,11,11,11,11,11,11,11,11,11,11,14, -41,0,0,0,0,0,41,0,0,0,0,0,0,0,0,41,0,0,0,0,0,0,0,0,0,0,0,0,0,41, -41,0,0,0,0,0,41,0,0,0,0,0,0,0,0,41,0,0,0,0,0,0,0,0,0,0,0,0,0,41, -41,0,0,0,10,11,43,0,0,0,0,0,0,0,0,42,11,11,11,12,0,0,0,0,0,0,0,0,0,41, -41,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,41, -41,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,41, -41,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,11,12,0,0,0,0,41, -41,0,0,0,0,10,11,14,0,0,0,0,0,9,0,0,0,0,0,0,0,13,43,0,0,0,0,0,0,41, -41,0,0,0,0,0,0,42,14,0,0,0,0,41,0,0,0,0,0,0,0,41,0,0,0,0,0,0,0,41, -41,0,0,0,0,0,0,0,41,0,0,0,0,41,0,0,0,0,0,0,0,41,0,0,0,0,0,0,0,41, -41,0,0,0,0,0,0,0,41,0,0,0,0,41,0,0,0,0,0,0,0,41,0,0,0,0,0,0,0,41, -41,0,0,0,0,0,0,0,41,0,0,0,0,41,0,0,0,0,0,0,0,41,0,0,0,0,0,0,0,41, -41,0,0,0,0,0,0,0,41,0,0,0,0,41,0,0,0,9,0,0,0,42,14,0,0,0,0,0,0,41, -41,0,0,0,0,0,0,13,43,0,0,0,0,38,0,0,0,41,0,0,0,0,42,11,12,0,0,0,0,41, -41,0,0,0,0,10,11,43,0,0,0,0,0,0,0,0,0,41,0,0,0,0,0,0,0,0,0,0,0,41, -41,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,0,0,0,0,0,0,41, -41,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,11,11,12,0,0,41, -41,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,41,0,0,0,0,0,41, -41,0,0,0,0,0,0,0,0,0,41,0,0,0,0,0,0,0,0,0,0,0,0,41,0,0,0,0,0,41, -42,11,11,11,11,11,11,11,11,11,16,11,11,11,11,11,11,11,11,11,11,11,11,16,11,11,11,11,11,43 - - - - -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,0, -0,0,0,0,60,61,0,0,0,66,65,0,0,0,509,0,62,0,0,478,0,0,0,0,479,0,478,0,478,0, -0,0,480,60,0,0,0,0,0,65,65,65,480,0,0,0,505,505,505,505,505,505,505,0,0,480,0,0,0,0, -0,0,0,0,0,0,0,0,0,65,65,65,0,0,0,0,0,0,0,0,0,0,480,0,0,65,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,66,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,480,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,66,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,66,66,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,66,66,66,0,0,0,0,0,478,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,66,66,0,66,0,0,479,478,0,0,0,0,0,0, -0,0,480,0,0,0,0,422,0,0,0,0,0,0,0,0,0,0,0,0,0,0,478,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,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,0,0,0,0,0,0,0,0,0, -0,65,65,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,478,478,0,0,0,0,0, -0,65,65,65,0,0,0,0,0,0,0,480,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,65,65,65,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,65,0,0,0,0,66,66,66,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,483,0,0,66,66,0,0,0,0,0,65,65,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,478,479,505,505,505,505,505,505,0,0,0,0,0,0,0,65,65,65,480,0,0,0,0,451,0,0,0,0,0, -0,478,478,478,0,0,0,0,0,0,0,479,479,478,0,0,0,0,0,0,0,0,509,0,0,0,0,478,478,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,0,0 - - - - - - - - - - - diff --git a/src/data/sprites/bolt-ghost.aseprite b/src/data/sprites/bolt-ghost.aseprite deleted file mode 100644 index cf7ba3f9a090febaff3a2efab8a42fb0da7549d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 650 zcmcJNPbh<79LJyeTap}P+`-W%GvMf`Skp&@8@~mHDsl;0}_zjpMYDny~If{mqh&}XZFbFNx=HB<~w zSAFndZxOydRm1RI8&q~Iux5S$I{M0?(@_Mibv9_X2cX400Xqk7peLfj?xS0Hc>WGm zN{Gnj7MvWPhJA_xJ6ui}YzV;(s}-KMd*I2~Hk?~%fuTW*s&Ii6?cF5HX5FBWUYKm iUNjwC?%y{Djb-t&A!F1(KUK2!67v>U+k}W!E`I`Uc&DlW diff --git a/src/data/sprites/bolt.aseprite b/src/data/sprites/bolt.aseprite deleted file mode 100644 index 0cfec695e0573627e1cea388ae5d74bd2432e4bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 857 zcmcJNO-K}B7{{M&-SHw~7umw<&Avnu*QIt5cU6KGY^h`rL(5hh6=sZ$KtvQpQV_(F zGKkusC_4nzHR%w9mmoYSf>?(>$deFk(8WI9ezS>+V0P&l=EpnleEfgUyl;b74mDE~GR(?%xMARwPJTV*2G#l2;`L#dUSJ?rjdhFYXh9f8M- zBCx%@435=T!1tFA!iD?o@XOR4`2A}L-d?PQ&dUG{-%P@|T?)X!I7-+r% z&n7=YE2Uv#`ZIjt;3g@S zd$mv?`(%rqtY+j_EX8J(C3fq@Hjiw0#E=%N)9z|BdC#Z*ysTHBaradBU8}j;SIj@T zUgpgmc$RywvNe4)7Ax|ncAs-*zU4j4Peli7qK|(aI25V5T58UEr6T-l`vpF>oc3h) zg}WQJN(fJl>wT6{AWgd}`(A(Q7#*z*grbvk50>`ENI z3{WHqgC-(Eh#t?6!dhsN#9{o2I=hUJ#RW+?Rf2#-JQ6w_85Q>g^JJR-z9Nn4gr@=_ zsq{9mU{elQlPw3^%65RyYtz9%Zw)v<-U)vBm<3*VT>^3=GH|=Y1ghCBV5vF{RODoX zO65^drfmc(O)o(mFMx;c2f=$!zk&ja5dQKoXghrlWH}D3C@Tf)^IJfVLIDQ$>%hk+ zZ-Q+dyTKN-OyF<;Ds=BOCO8C60DZVa9M({VFJxf~O*ldjc2I*Cq+kS{kc%L|0t(1S zIFb>IOavkgQPNNny&POBC=dxLW*VyNX!!=b{NAR>9)>mLp_fGCOGPSr8!S@f7-KX< zx=Ry^uzF*4KVHT7q#}d~kxdfGT5`Ngr}a^rabZQH<#r$2{$SF)i>aE(oxQU9j_gxU zjG@t4Ze`vMvY9KV#xz08&RCVJ|xYZDJ)Xnc%~}cIQS->pJ|q_?>IsF L=Jc2T(<}c5cK)^q diff --git a/src/data/sprites/title.aseprite b/src/data/sprites/title.aseprite deleted file mode 100644 index 2531528653e28c170406f18b21347ff03f1bb486..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1661 zcmcJPdsLEV9LL|ZyljrjwzL8z^TMbjf|hbAT&HZ4I+~$olDSM!j*4mCi(RstHLbK< zQ>kUkv`|x55?+qFvI6gtQBK1WmC8aT)HL>k)3)|Uf9`qD@AE$I!}~tp&+|OzaKHpX zp8RHngd#2o7MX(}$X%#^8;t1(kp*!5AI)Sgf=sK;4XNf3uo%h|Yl!?H?h*8tV6pE< zY(MzL(A6P`9o7xF5k~{E$wXjQ02SE3*AXbczy^*s{Q`VDwgz}^&=<(9a{;>N!~hwQ z1fUL9`?G`XA*Bh9@bJ1~O0dw{Hj)e{^0#*R};11$o4a(pPvS13D;0S_X z2WsF2QeXr+dKaAzEIZ7HgUUM%lvGQ8+q(% zR(W+_jJDo%^Z5b$a!Xp`1TEDs*<08t9w={oTvFEA-|+QF(rSNlM0Ddes}*}MUlH6d zV@7L2IvPUlTXQ;Iy!M=7PPL4Ee!|Vvjlzk)9G_DZTCKHLICMH~rFNYg9-NdZ9F8}2 z+fnjE?j7(|$9j-7-Z29-OAl!;Nt~%rF=C`vBIZ2Eaff{DjMa}TF;>shtugq-H z3{y?A)tw?+i}^B!61}y)-M1=B)y0uK!w(%D3&va(708e8ni<&KA$nCeV&nKGSrz;` zYjEOWN~U?tHwTXC)>GGp&#D?dW7joEdTLM>j!r?jndx3y8{1mWez|C6kd5>OaW+Sb zA1w*h@&oG&1z-0169@W5T^-|$;q4fe`vflcWnjTBzvhxqH>Swht5dR*k4K{kw`$Pp z9hgs!oU^T5%HC9%_I43_kvd*!{@kJBDaI#1dGJYupNdjLlSC!IJe#D7&=R^hZlHZ%vE73Ii(T?XLm^+kiEAY zqgd8%R9+fjCZ*Ye6DB5xCWqgZh#ZgidU}U2dus%xXSYXZYO5N4-;``ibg2x<(iO6KAvha z$d1pBka=*Oh)F)D(;Ne)=~iN{izC@a-k;iQ_! zzeq_Hlyh2cY1y8_Lz=2bOJtb4eaET2TUz&1`f%j6Lh;l{YSS~#zI<6*VdmL2H*W+b Xj6NAEI?K}u$L=&lL=$>q&2Rq&8yt^! diff --git a/src/data/tilesets/roguelike-dungeon.lua b/src/data/tilesets/roguelike-dungeon.lua deleted file mode 100644 index d0476e9..0000000 --- a/src/data/tilesets/roguelike-dungeon.lua +++ /dev/null @@ -1,128 +0,0 @@ -return { - version = "1.9", - luaversion = "5.1", - tiledversion = "1.9.0", - name = "roguelike-dungeon", - class = "", - tilewidth = 16, - tileheight = 16, - spacing = 1, - margin = 0, - columns = 29, - image = "roguelike-dungeon.png", - imagewidth = 492, - imageheight = 305, - objectalignment = "unspecified", - tilerendersize = "tile", - fillmode = "stretch", - tileoffset = { - x = 0, - y = 0 - }, - grid = { - orientation = "orthogonal", - width = 16, - height = 16 - }, - properties = {}, - wangsets = {}, - tilecount = 522, - tiles = { - { - id = 8, - properties = { - ["collidable"] = true - } - }, - { - id = 9, - properties = { - ["collidable"] = true - } - }, - { - id = 10, - properties = { - ["collidable"] = true - } - }, - { - id = 11, - properties = { - ["collidable"] = true - } - }, - { - id = 12, - properties = { - ["collidable"] = true - } - }, - { - id = 13, - properties = { - ["collidable"] = true - } - }, - { - id = 14, - properties = { - ["collidable"] = true - } - }, - { - id = 15, - properties = { - ["collidable"] = true - } - }, - { - id = 37, - properties = { - ["collidable"] = true - } - }, - { - id = 38, - properties = { - ["collidable"] = true - } - }, - { - id = 39, - properties = { - ["collidable"] = true - } - }, - { - id = 40, - properties = { - ["collidable"] = true - } - }, - { - id = 41, - properties = { - ["collidable"] = true - } - }, - { - id = 42, - properties = { - ["collidable"] = true - } - }, - { - id = 43, - properties = { - ["collidable"] = true - } - }, - { - id = 44, - properties = { - ["collidable"] = true - } - } - } -} diff --git a/src/data/tilesets/roguelike-dungeon.png b/src/data/tilesets/roguelike-dungeon.png deleted file mode 100644 index 5d1bc646754251bb592b0ca57a3cedda8b172f47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31160 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 GsQ&|(OMMgo diff --git a/src/data/tilesets/roguelike-dungeon.tsx b/src/data/tilesets/roguelike-dungeon.tsx deleted file mode 100644 index fa54a7f..0000000 --- a/src/data/tilesets/roguelike-dungeon.tsx +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/data/tilesets/roguelike-indoors.png b/src/data/tilesets/roguelike-indoors.png deleted file mode 100644 index c6046fdf22c36fcd71f450d7c76b02bc33f19b07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20867 zcmaHTRX|+ZvTZ|f36?;BAOQjdcb5<>xLf1FJ$P`p;O-FICAbru;O^eITjTN;d!O^} zyFc%v>84q$=3G;%MvWSi5P4Z~bQA&<5D0`W@l`|-1cDg=Ud%`cz&8(nH-I-%kc5bk z@{hEmR*$5&)9EMoR&X|$dv#X2rv7t&df!OoC*Josz4jGrL}Y?UQhswrvrBjU$ zhJ29C-c9mn)^~T=wtLUdMWyyF=R<};_KyhTHdi3~x>=)X_yV96BnfSse)v zr>&h1ndcS}>b-Q8E*DkBNF*pe?clnB_`%veo|YLYNkZD?pS(^^Cd*KT=9nmsP`W#` zQH|qx3bfZKya)AmDy@VD|gIDucs>mZdVlOP*JlWB%6*BAOAq3&(F z44!DkRp8e$z4;6-Q^ebW<^>Qr^WRvj_WC@Mz;G>m?V@N{d4v$sY?){;g-~VcF!Dd~ zaxU*p@9_20hc-#pGaN*;M(FQwm+z^A{)pSE{b4R$$@VCo?X=irkjJ*xxQ(BT11Ak9 z(koB6eVeZOsqNdwD!M^?kT8 zmx~?yXA%?T0jG$lZV+Ff9 z)vv>Y1g0le3S3&s5K0X8zP7q3SBU0Dmz2-4fn(8TwJY;yJ7>EBdd-Hr;+`C(lNE2N z`~+oHQu`6E85hR>^peRvRqvDDzVsp+w?Nw90S{p?{JB#Pq>^VN$9Q*jsrs0?iibeL zrCbM@IQ3cY3NpAkUFY(=QP@r4C+foxl)NPYl|i&kD|xD1Q?)|B`hnx%vV0in4;h!v z@g$)Qk70WY(3_3~M@b`3uY-zsFgGQR>+8d3B=Sh!ru-IjXUo+LotiKL-CIG7MZtp& zSttsYqs%d^(eFxhnKFnXH)FJyvSx#o)}ZUb!m2-UU;UqY!JHLWfxK3ky@rU_kFIGG zmBNk0WFp$-faRMrndRTIV(L+F-oOM#PL8%|X~q@`+D$`UA6qR5U0sjbHuEnpkALN1 zzN0Myq=Pt$>=SQ$iYfU|T zJHb{HA%I}t#wKINtB#n1>LjwTa~-V(FDW)r#+#`bOX+BD%P)kUxk}zi^fJJwBdOE$ z?anKF;1c$|FT(KSVRHefcpRlVtptb|#NGSTB`~0>BjBrB0I^v&emH{NJ_-p5mG3TLaSp%fS`c0c7^4F+kNyASF`?n zS0*_2ZJ4&0CjH5(-C|^X8#x2p4Z}?3^WZz?Z*Vu>Fu#m> zV|vGTo2p98)xK-?0^MP@n4#ml-Hh$@KJ>IeKZYP`GXY}v zH%bBb^L}mEOnsRIF0bQVPkGu9z7kL4)(y_s&haBJ+T03uYgAAukd3xGm_Ob%7$i=1(BYiP^U*mM7|!%}T% z&KAu~8@w}@Iw|LO5q=-P<&2w~Gn|dr-m;P?VF@S2ck-0fR||wSO2;Y)P>V|_B97+Q zf~%|XC71hN6)J&g0ExE@Q_MS<#xrG+OAb^FNf^R}6T%dEhi`kZS_E{!EebP zhJvH0EMvIX=4P|?jKQd>C?PPix(ImbP+DC2qWATW1*mcRV=k)}=!tv}R(Y;Vt7k?> zuI?6)97*Vpho5e{tO*9b#(hn>`b$H>EY>sAq!8m!*9JD;5uCO;NvyN3)kS31Hh(T{ z2Ceh%Vxx}&c_bbT$oP%#bEF4E{nNX|pL=W0dQ^V~C%#-3a#WyiKT5cp#FE#G-j(y0hQJDiYXYOYjt{%a#juEGxT zj0GeR9fWY8Bh2(&#HufE+`L^+Hzev}taO&85jDVqE%TCOrrc0mAI@lU*+(QGC3bb?kLZC%Ja0AK zoIz>~#bzJG>ZqxP5=SvsjO^ppDMS5ohR(z-Ii{Y4l zPP1_PiJM|>8X;1ee$fnrPBfd|FCp$R&7YI0AjYv49|};(Eaaoe-Ko?(;|!OiG5vvw zPJ6obpaAZUMuZsSUOLwJ;c7@!pNtj6`&=_YjGvNRJMqXZbobY3fg5^c2AlYyblG@pP*Y=~zz9~{Zn#HCH)IU5j`T5`7ac;op@^?Mp}KTu1$;T@Lx zm}4BqB1JE$tyN2XfUFyq>e2C!u}vZt>-;xPpZg7omuF%^XW}CQWl$ya;0gL%s!{Mc z)gXiS>r(vZukkr24T%9e$iAdVUw&R}eJCzt(DmxL`_=@E!SpF*m&A^%45S4vn!}ks zN#SlOc?SfFxbLbMcR5fjZ8!RDcgq%*szG!u_k;@iL!#@^c+RKNiW@CPcR-y(d>Y5e zx*BC`|MQ}BM2^>D>EFueJyUfB1A_Ho&=m?we$JZ^kH0f%V38LD0 znu(5mPx#2Za)W2XAn(#V1bry(ky0zJPENikVDnRn`1_quC0-l%^@Mi$yEBJ-{K@#s z8^i^Sy3$x8@cMQA6VHX9vAdampF$)P(7jgP?>%{|rQDu#)HJ3ydt*uQMR) zwe1~!Y3XXYw$+F)JHV{N_uy0nws%;6$K?oWXVJX%A6$~cDj%Bx9oV0{cC}*k*{=;| zGl%`?n2OC;P*eYjXm*_WMt-Bu(>ww~tW&^dNIr>)6*1Isi>f&jPl?n*S$}n02uB}n zYtPg#dW2}sPk(C<`m^);Z6&nB?evJsNqHa7cu5+~ZUedR6)0lS@y+&7Mg){`dfP8P z+5v@EweN%Yeyh#(nnm#}*+jp#qJwX6Y`iYFOi{qHdotJCpAq#j^3JF9d-QPy3gIV-EJ&n$<5ujE72QrFh%;%BnZg!4z{(lki*5X!vhkq!*n} z!pn$P7jCFQYh(ci$0s*c7Z*YB{iG&K?n-7ux0C)AM7t+_{o4!31T?2*18&=C;EiTo z4+@$d?Um6Ja(17pBFFcyD-Ljp`3zn0qAL9Y3K@j+OWc}}E4D6+I8-FzRzekPRg&v$ z?JF6otg=1lo@aCp#kq;*g0FVHKcW2Wkx5lh=E6e_)?B@>H0O(fmugKZ5dKJV82wOb zFD4gC{{wln%f)yC@T$LSsk^Z0XEhaV3HuuwQ)+za;a%MRL4Lg|&ivPWAidmQMnwx- z1ZXj6tC}%F0OBD(eyHr=s}VAtu}D^L*73tQDh(sNGZsP5HCd8I=%N0kZ@87V{}#D#Qbcs^ z;k(%xeW>M9nv)y=)<$1Z9_VYJveFN5XkNg8Omye0t*Y}D!Uw?465(&F^pNA4JwwdX zDsl|VzDBF1AJ#Hi?i(o1O!f1W4l32svK7~+&-kUF`3@I3;7bH<%`sas&j{yj5vv{O z%=pH2z#2}lYWd~rSI3LcD#G!aa7SgvUhcJu&1GL2xsQtiG1i^gPPT-&?Hyd-d~4|! zxs!+{CK{&@GqJoHKZAu_en_04pv?9s93HW~!JF(**Ew_lT9jB%xrstVe7Mv}+lxn# zciCWq2r&*7M!N1XkNb`DI#jO@6AlBo3-HfCScm)YAwi=;f&P7_=G(fm>R+_NDFhr_ z9{!vPszQbhmL*x{WP?meXxRT)&Tpl^D{T}lz6=Z zF-j{!Wd{EJ40_H)JdSEYQzFF2Yc3F9h9d+_Gm^F}d}+Hf=^_Z1O9WFd@%e*)TeUtP8 zhqS-h}?t%WD;GZ zS23b*S+fw_+O1Za7ZX>sVZBM{o+5sW8O8k6Pc(Mbrgaa+CE9zgj@5J zC$E`{HFzFX*uCFIMb61C?WD4zN^W&w0ss`hq__(!J5ANKScIf)wbmE=E*IM>Pd3v6TRD2(At|(&Rq|Gv!PPX1@i}Xc|H36mVZw`)RaYWy?#O zk=Jd(<2l><*BJt!Kf4O_H$P578$3S6`5ImSB$TXx9)3dn3;5UcSIuYOQDT$?g7>oy zmbZcd4hLCMc>wA06CgdNYN$Q-IKn4cV;-odUA|4T6jZlLU!JekVVa;W*1OWFeX$S8;p+6JEu2cGGPips;hm2idRY20^+i-{9 z>j`>bl3^n{OUuhaW4?$@}=?dQqjQ9oktQTs-a@N9F zyHpM^U9Y1PbahscAoB)P^XkXJmZM6ae+M|+i$DMwPlt^lN4yq99jWkS_5;;hjZ8D4 zbb8M9&=anYdj7e3uIk~*R!zm~+3;C-fuJK0vsv8#JxZL&Hq&1UgOp*)q`$q1xOIQg zbTsfvLX#K5*a>xoFFIfsA}y~kk3V~@=k(7|jtZ{ca=vrgXpAqPHSxTsPzI;+)ju5S zySI5v>CGM#TNl=Fh=#q>Uu7YylA?tD2y8}WF?r+F30=eVgjZ_Py&t9~W_)D#?Fk-&tp zXny7R9;f0Z1;Nn_od`1AgcG7bL`^6)`l~toJHb%7Bm-V*rpnMKnj|L-Oxy{JW1*o6 zOD?EWgkPHG&{}0ggD}Kf4MVF-KYe(x7p~?9uo)q>2h)Hd%DFmEcNVLN1M`>EG5KANbV}h&rKRH6sodyX zws#z}-y~Jhd_#U9A&A7DZR&kylhDOO3)Vo7I_wMTf3E{=b<9#2<R&;l66K`GMKRJ&M)oXhDU5)i}&dBMhH4ALeVH!#q6Ts(?@*P1WDPjnEs0@7Ihw z#6;r;&}}7ka><_P%6`Y?+Rdj|O>f0^9MOUNhwH8En+-@ld4hr7K)?u@GYy z1qMFtb%s8v&$pdsYb+1|b{l+W6neX-y4(PwnV7e#>g4enL;33WvpBQy(7V$1tBWt` zzf^ZTD%PzIQgHmaUy`(@r+u-On%ad1mwkURddN13)?DyW4!Afhb&-;jrk<`@ZApGV zR|f_q;pfY={$KnN>yYI2{P-%Kw9l7dv4DWC%i-pc2h4M0x+8M) z_wJrR@{hxt6q2sHos#fWLAMbDP=ZJu${>Mr*4V>4RfMqAipE1`P=$zjCG+1e7Y6Et zOx78;PX-|3oP<2G2WbVI7$7kUPJtGeD7>ZiAkeKLT1E0}7gJFCRdObpk?U*p9%v~p ze;D^e{zS|YwP3lu-H*YTT8ih7WK!UQT%*6>nB~;+1oPcGx=C5CC|vgiPMjE7{k~vH z{eqHpK32IITjsU~i&+MTmFtw~-e^6-yCypI863mvEs}t3yAlQCrJ;yui~jJ`d1gEG zMq5_iKvLyX9#%QfO=4*wmb2A zkiUj((*X%SmKy)L`BIDf?YZK2P0l?~2}ZjWOZ4u2pSDWvW9%cwp5)ZhNnzS3+d-|g z%&)jsTOdw?!jZAsHbbX7j4b3iT^e_@w!Zk0#*o_d4i6?WG7hJ(qsj@~8um1}%#> zWhy@mU+vj6oF+yTj$^`y=*b-BV~b*;SeF67sQ}rG(AVIeNNh~Pt)Ir-Sa8uwI@UDn zY1zxyd~yW%kaBod$xPnrrTs@Qus}CtM>kk1BlK(cV054@YYF6e`WpXVS_a$sr7WTl z`-Z3z!!CW20n^yG(zcLlLb%WBt~85O^2=qEb@ym6JBNtLDexbyutC6Go(GjP)4!|Qt zi|z7ArjjO7NqhcoW}-?k6Zsg%StK5VF&tgdPT}$RXHN;ISfZ`cMOS+T$MbP5ZG2L3 z&;qQ3%1r|Leid*ehCB@Mc5KduQbTY*Ozu7$z@F-AmVx8(w7v`N&6{MtP2fNChrocy zBOWoecjWpQ*(fjK20x~DMogb|;|$=~UfEJIzksk{_E`(um|rD&F-puK_F_a3nj4v< zvyaj#ETSbteS{qT=9bSz4_SNwwn1T+-CYcc7H|2Y!u^2>Ve?DZ=Hr_FY(eNDKm63H z+IXGpydJ-AWdQW~?D>K0Y3KP4uT0sBL0eKUTDKS{->w=YJ;J~Uc^VKIH<>XA!5w~H4MefzFw}?658&fT4QDN zx#%mb8jIWxO;5<;JE7Y6{ zT4@&lb9Zl9VQbz667wlkD}_d`|igfQpH%aW^vHj+NM^~HQdkC!%yazW?vs!wvQ$pT(Ll{?oQ4YiD8U5z_WKdY@$D27BfLorQM2gXbY|rFe zZo%Yv{8tN5cTv6$GA@9M{pq0m^b!pKm>|M(u_ckx;@15zv>TuVX5RmcdnOM>m=^(>YZ_MibDu3%c*Qm;1wvPUatOs`3~Uf zx2No^a;o8}zt0rR<^cjYGjl0ei40aMsvd)F!?llq{Um4uid4@&(8N@=jepy;RLULp zE1F#aHJNn>_1|7DL$Y~$ss3ps;M13!?ZTsX=N&)8QgZ6L&H+ZfRC;^p>IElqVWbNR z9?@|1Y`dOc>43I=&2oK{-mMG%rNAohgn_)MiX8J2Y|~xcZ~o`Y*9a0Tmvfhhb&Lev zq+cN0T?gG)9K!j|07Hz{cV7wv5}d1gUBI@xd|h8}+4mkT<-Td`6h78MpHDy^q3!MX zGC^R)2_gvX{Zhcp_#~6bWliy8$C1yDlZ0bRsvN8ZH;eBgi_;-tM0M&*$@hpWb&+Ig z<#&a6G3&Q(BqkC`vG1DQaYZ{Dy&t6{a}+Fomi}ZU01dku{WuZ-gt7pzsb04JLeG~) zvil!4OX>?r4nhJ)?jq_Qq zN}Piq{l47QXemQVf&VGW_+DsKTrWFkDsHx3Em)YzCzGyRKSi=U7jlKSJ4G0q%HbaYOeNtHs4Mr+toi^ zP^wW)6AT_(SSZ%E-slbi_u-+yUqYcON1-Tn48G=5+~1Jnc%6$MQz)ul;Zxotw~`!V z;m`KL@CNOPq*5>=$K>xIBS@0+$=j^7%+EK93Z=sz|4+%MAm<3eD8OQU)n7RGYWMNG zl%h~fkjqN%W12>jL(%DN+mEwvHW@bh6mJVfcgm3Cyaku@ONnjMsxPG=G!5|AQ95Zshbh{uFKw3@zIQAa4^@cTQZ7 zHfWNF6bqMuiV<$kkO`v_FIpBk& zp&9Zd_vWSn4)I;bd?in{-mONt0!%BVd;}4#PSoIBl~Jgj==m4^cbt>&J_^N2G^u>L zKQ5B4n$)f`ScqwXMDQ|sJ-PGUtog;agUqkaD)tf@&w2^N_u>B(A3oe(LRUFxe3jp zHH7joScjE9=M{;!%%C`y#jM45wCG-RWHPT{;wci6yEmXQ1ijVh=O8R-6ABA@+v~13 zz9{WA#p#O`z_NPO-!VBBpwZ6fkQ%{d)LE)@*qfN>NnBCBSojOd@Vqn2tBtd1h2lPK z+dSp(GkT|Z9G;j6-fJAOP|m?gs0!5L8m)}o8YTCXtNB)bFx_+N3vR(v)v?nmY0tmt+Z2^6uPG2jzUooa)9$y_Nna6!7%yc z^Ko5W-M5SF;Uq5GZ(BBVPjr+dNJp;Ff9fm$TIJ;sn<|$n-2rNQ2*=M8?PWN|Y$8Mu z#jZHOO-PR7s6+zq39>AF_4^m3GGH*c_(|-@Srl8l0-zjTlo8kj$1T>brN}L@QXP4tW(t*5OiD%t7sMw3qIub+`5_D(bE9DIk4kv0g$Lg{pQO7=1!>^$628{ ziyTl?Wao)ClHVl zC9;|}m~YhhRplWkQz2SQ?9b~CM1FE$!P6ThB!u}7Cpb=L#|`(EB|{0OWw3LhZL>9l z4P(J5pMz!{)glz8XNnLj=*^MOMa&koLS^~0jX4lp+&j^ZSQyx{VaHa{k4oO>6qhMs z=x9!1Z~jiFr)kDbcg4KzRw-A*C9P=Mr&)iZBji*vCYBLtN3iny@ov$p=@-qP#;2YN zS9;soE zCM6Eu9hXi89Pck!Ig?AbR;~BRT!n?Ov9tYAc(YNgJ`(|S;~XAaLbq=`k`@x^BM1UH z3F7b<6D;~{lW&xY`H#en*@&ax>TrEiY zq!_Nhf$9QeSMpw$DOovo#xR5Qq4jZ{ias8Me{D!iN_d@6*~bYnUe)VJfj1HO5qeeh zUa9${(y8_7jO1CNIm~*#__Nu>xugY4B-&L;6#Q>4%8OI&G!0YRq?(qAlp%7zWlh}f zjijM2XIta-6>RIXzYX~PVu0FE;#;=DNBM;?tHyVD_Uv_(EIQNUGu)~@Kc*2ajisBC ztoF=%^LJPFO%L9u#=A*1^Z?yx)5(4;Hb@*YJ}7Q)t7geE>5UHI(*Tpj;0nXs>khYR zdXetjXuh1eGYh^DH$2~4%1YkxBvHp(SKtpl|q7xYfwu8-un~lmRc3BQr8ThM=vy;pdV2@=cx8QTK{&;1wOj=}D zzQ$Y(CFGO!Vu_TI)tEk4F^J2@2;#EJGfa9{k>X?vH=0C$du?5_O|fZXM%Or7GqVuDR`mNHcujU{P~#>>VG1P$>z{5-5b*?f z$K+dau>{P5uL@jjRQ~-FUQ0a(!6AD*E^#EaU`e>xoZH<+EMa`jOHH4!?famvx`%)R zTier!_xJVz6T1Br<^7IKLG!fp7jA|IXL^oV17CBL3{aXWMAE&hnD}=d>HW6c!@neC zPKya&wml_OzYe|o@Lkd^b)r#*{f5UOL#G+;GnDl|z)Kg(nxKlptLsT|{|j)sflzs7 z=__~^YbTqfLvp%Cm#D%ml0Lr2Ysrvln6;#*$SjtGWS|!462+ZA)Lb?CuUC?GGvHxyhRb!tG%__#&?2mqy25G2Wq2y9L{s0#FMR1LI6vb zipTU0S6Lv>2m^rn-eifcIYEy<_{Z6F!+ein2JmDXp;RP=4vj1PSD_$p{XU|g4gY9$ zWBDCd->_8l1FU*m){{`Gmzz4eWWoF&+V~W4hYi zhF6lJnaXS4W3D%xR=o*}p3;JCWWYRxx>D?E&rLRyUxHs0ldhcqSsG=G!7HK!XvNk6fmHfeN&q$*uHNvG;K&MpspA-Wk5~Jp`-GV-d-06GF_T_ zpVBhwU^9IXUF(LbT#t|=Z-$$wASM0HhObSjsN;r%}_OtF(A?ih(OR4$FcuT`j8@hfE6$} z#P^w7ePWY{-`u#>xM(^nv_w^0HOnn^)s4`o(7hMmgd)46`njxCo%Q6o_b~n$^A3-? zzSFR;VX9Kpi8$UhDQ4S{c+%uEcoZXW_ulf34x94**rVvt23|u%;Bb&|UOaZ6i-*r~ z+5N?xZ~qt6vFdTVESNa@0)%Hl;k$ar&al8j11y?oLQ?-|VV}VrRc`2~Tdo zo;~T_4x;dthw3zh{lAs;&4Gsb_&~<|L11O*bRkX}CH4-R0f@r&1*~}7xJ7NUpYPNH zTX7w_Roqe**x$mAG1fYK(h3FkXTmJNEF7_UpL=|TxDAb1ST50lZ!)MZcKEU^(*FHu z;u{q$4%ybkbyR^bU8l-Wf!=NOga+)`ka!iVOu$cGM;m3I!;?ddH8}N%hcYjMtuvGe z(bWJ{vQf28b}p6iQh+hn+(;|zx2TpceHYHUN)bjG*?QmcGb zOAislWa>)?Fv!nyD1y;C8E0=8Z3Ky39)R2!5!AxZg#FnCA8C>+@Ask8vmgBdnd8&s zr$pr^lDasGlFj7??gNopS(QHbTZ0@2je1si$$@ykQWth6Cxa%REAI4RtETjIRbv^no8P?%`F#*~E z(TX@s$JVAKm=wGV6Vle}SEpSsrXsgTKf9hF0vuUetTSZW?YI#!QlR0@%Zpr`V|{Gp zs0rIK2nQhY2ms0TYkEy|lCV^5(_D`yt{OlL1xP*+00@zM6q{UF^j>%l(*0r-!|{6& z#C7Nc{mX3vQyqB`jMp=OE9Pz@<^Y`coiaWrqQ0(tJQ^|wfr2WHgc*~=P=rxmLtSDO zv-N9UyyeC3?(OwF{bU6xXow2n?e-iu`5HTEs|72Z)y+k@T~DIr30mQM`(_x0)NCDsy7RPsmw9ni|#JZyxvo7<)h~gM=)s;`w1<&0Z`< z%)`vV$q5!TQ=yw_&L1-q<;V}~r=3g*l^@@>FWuLP1>!#{I+@q$_8H5M>KiULkHS8A z(z`+Y04tY?{yMQKW)A3@pKMB|lE&#Ie`M%=XO~@@*oVQ<{V#s~9Pahh^cqEhG1)i+ zmh)AjH=l{P?F_e`<@lj-7oR^DcEqG2M$@Ci3>=fwyoB+i`g%W-L5FpW-B7U?NeXDp zaX1}4`!TF?w)y4QfVA*dix9dJQ96szgq_?O5lWmt?o>9ZZ!mFrQ*4?0KZhDPm~=E+ zapf;vuZxfOkNz-E*pFnqW{W^TaGJHW*n`{GhyA#MC+rhL$`S^)s7(7xI!E9j3RLCV zleoCHo=Z}pBy~NV9BH1H`2$x6Br+@$>MZ(WjP`$LOfzMPXHrlRdQ%|2KK3fkd2b7f zvqT`fDfWNY4ul#4U_WM|ITm5F-2L=0HLDu*(L6j6SE^pd{wdr;7Rae#n1MeRUeSL3 zb0SMn@Ss#aeAd|`*JFQD(>8a>snyOPVnzTjYeXLOj@PL)J{TBW(r_jR%5nejEU{@} zXD#;OlhBd-XbO*>w&HfaVn|TW(^E$~`4HRdx1lOtGHiyYvnF>rzkt&$LaqF*Vgw>p zXpedj;U6Ary5aB(PZy(Scl(;9G(rTqSiOj?yrA&o&PscUgYlc4eW=9kefGWQjW44? z6a!m~zi%;#a+6Nig!kW;`5PbINw#n0?9#DP7oW|1??&(^RxJJTkq0 zB=N#^p;y!OU?={u=2P5$(*7&}TAMS7?0JR3N7|CWbyBv!Gsr2ug0ncwdzm(>J`-|DA@16sCaC9{iA~aRr`ZRwy*DgiL&`bi3V3gaGZnZM%B9KY>Jx6GtbQ z2w{U{2{^H7>(ayPws~nPPArx{Yn+PcxD+tI04TT=P_dC*g3$b~&vMSY%wmh6uAzak zE|YO}?6zgmQ`g;75P&w+yn{jT_y8MXD@}wxZ9AmrVWd4IbTgxu*JAwr4ZKP;0~TXe ztP60Z^2Z8xXiv4wm!Vm5bep!q&+mG4iM-%>WK$LAR~o!tf$tg1EJ*~JKzdETHc9_; zxWWWn>@8aPfu&rMg$qYcUJB^K#zPn}~_6xJrmy)8*Z+@q7mKxN?} z(1#@P5LDSpW6=mHycOvG{agYkhZ14O%SgD$kjx-LFPX6>Jb3xR`2G|!SYn#YkhJBs z%o>g^T8sOUS0~r@xh?3d!ltz7(93z%hrB~U%Otv&)Aer5Vd}WU!1eC?<46~nfbeIb zL-o1WeC=XuUYk~Z<6^Y!{<8ijP()6cpgrPjy z23-ux68&fJz=nbF6jwfq0wjq>B-{Y@48q?wjfNphG=;21?70fakH$bYrDjG~OR- zTV1jxEhPPq;O6i&zr-0~_-qDCB=#RLj_dxn5e=#W!~;yr0Fb)i%|61m#(hgZ$N z9~0}R6Zo=&XK=Q6@+vUq#sZM zkx5A|004;xPd2!i*jAz%TTYHAE&P71>Fj}z)5`}oS}4t7evq^Ib~EP{Da`v^Hc+FH zkLAdJ$m0uiy;LslIrlV$U7-14%5C!Az<}7QYLiq-Vw|?ZUMOaDr^!gdze~WPs>YS3 zZ_c#WdWr5LrxXA(AZporxSbRH&>gX(?m4wV17zLi&3bjlx+k0}<4!bp60dH#Q*N#6 zV^olLb;Lq2-rGSEkL|67hx5U+e)34=$1STmVj;)M(k2Aps217l9Q*W(9tAN6^x|p- zqvh;2QwkVX{e)!ZP|Lc`0j$4k4lI$op>OKiC~4;iI9YXgZ3HXPU!F2OcF>7*tNp+n z9IsE8>40Bp#v1s%CzH0w0SEeSy@=V;@Hm{Jnumn@=C*w(h~=;kBqV~xPNGdT;@9*sW=w836I?-FTK3$)P$$aHH z&f`_@nI|dQ#Xbx)K*>byHM8Oy@izyNU4VXVIjBuzex>%2>DO4cL&%ZG_@g`46Anj? z3L+lFE1wnYvQXZrcJw*K_G0hIO8iNUjF5%fTwZ4F6?QOw`CWi9 zd%{7F`_Iy;4~e7NZ+4m$IGQE>Y_;^x zLzj*+IjO~qVfBJ;#Pn@6pkYcB#&1iGD-Z(6K4bpxSF6Kyi7OG7~>ze8arlSu|e$qNLyCDYe>N3DzFJjqzgaEW5ccc0~**i=ZPBNRLLcy6zCMzn=rzvVxe#5 z^FR#3X|qFo$mnnttXsS_6q)V30` zY>=<Uu4kWxq~HabAScV!d>Jn@SH- ze-85bIh-Kv#~u8_96@=#6n@XcdgIlTl+RiIE#KNNspthTqC7OQe%^J>2k}cqPmhPK zP#Wc^23r(|#zHhgzL4S-r|gX?z~1o`-h3_oA-&KLARkpDZoc$B@GuKMNGwU{JNB*6 zUt2xZ7;N?u06kgs(_0o3Z}6-C%+c4Q<2m3D`K>|u)S z2S5#p13Y=r38`MjCWn=vWYs)&?3Krd?w67<>^EIq5-|ACvceMifM=_jA*V^@)99&C|EeDT$etgvlj#o9S5sM|Yr-)Me;2r`BN~z3lT|r}J2TOL4++oT&nB190X9 z%%Q1I!1JFURTc`h^NM?Kzb;b6VRj*k%?ibQ#Fw8A-{*NdtTT=7&q6JyLV-grmf_w;xKg ze+bdH8$7GcA0q48USoFWHbU5BREnw*{tMXQGfx65F{2Zv(&y_NRQIrf?k&!IxDM6_ z+4rM1H`59eazlovrz%#ywe~LuAVlc`^d;$SM^DFeY+gxncCHYCJ^=$#Nl@rT zPZ1Gb)8D_M9U}pOMY>s58i@p-J;PFS#*kyo-f=dMeZPHQvhS+$SozluDF(r;+p2}2 zG-&Xj+lrmkSTyPJ2s8?G!cs+r6l?tbsU%+5g7Qp*|H7JoYIyrPy1DBWVz_9zj%=30 znHK;ExLN>8zETh1Aw&}QD6|yT{+ItHvCx+Zjz3VDNjZEzvZ6~(P`gg`iukC(`P3rp zZ=1x%ecTAf?seb&FTdoA%Hksj~B38)WbL_|ty4A-c?-`M|ZrM2^b^HM? zT0twBp&5|wZ1oOHSin_G8fsxv%NhUHVEI3doM$|neHX{I=u&${YSoHORjn39V~;9I zV$as7@fbBqDpfTSNl``At`f6WBek{ms2QUOZdJmqH>F1KT<(6J_3n9dy}mxz|2pS< z&+q)s-~B+R$!5|+owZw(L;Rvp2SLF;x&lk*pL z`xokSU_pq>VriF?SC?9lsc+dom|l+4ibHfyG>+qP$bp1EXSwA8j9L6zf7NR6sw8Ra zH%3@Y_Ed)|_}-=3{9OBf8@Xgq3FvP6nJ6-JP(=#6;#Aok~crtK~!(AsuA9KTDPUJQME)#&no4)2O?xjeNpoL)NYzl~B);Mt)A8?Kneh z|5=4AwLi$*rjw6}LG^ z)|Hd->k&tuDV>~mym*f~FPKkyR({(aO%9NZzKL;D*FU?p8Qb$XS&sew9ED5NY&yi$ zt;VXeXeZmF`iRU;ZjElE+fkA+bi7=+B=4&P3t@{j!E&&^6%AK6yq?hPvLn5aCKhR( z=43QcsG&t-w=y$+B`9H(q_H^3YPb>rzmMfU&VIi|IUV`I8)9+ru7%Cm-Um-le(0-9 zMvr)W%NXDwi+6kU3E*T#1+f!_BId1AEr^z2dr&h-zqDT3>RHZ)nxTgL^CL#KUE=pp zdzoXEncNI+q*nEhq>r0W{=cfBtBG&er+ippcSy<#OnVb{AoG+k<3Wj=*6K-S3AutL z73Bh0Idx=eDuc3o)$^SGZNIZgYuAV?=|!Q5yFcE{oQPD;UT{v7-S)pwA*JzK>b+ z;)yfYE_`e<*YEPqXNDw2>X=jE?9%Oc_D_3?+czkk16s(GP~@SN*iKCFFUiW)I~yX| zbptULQU(e8lO>a(k+(pEDs1+xydrL!p%TB*4(u}k6FjkgmPpNx<_~W;#N%(J zK(_DOK7kos#I7nVoX_8U$c6p_{cm`|dhNLpeM(t(Gn4Xa@D2X}ng0@%wRotFJ~eR> zJKban?x6C( z1|2K2Uc=uc-U8hZ^vqw?QE$BFYue}aLNOn(CaUMpJ+}V!Trkcp_Zr~TEb9UGJhJ0D zs14b9w+&cg*teX%WSDGTItdwut(aV`EAM~i{% zXur)=%%S@mLVphBm0{w&Z#KD$9!|CnMXdCr0Zk5OU8lAAuCi~MXE9h=&pt6B;CiLG znfzh7AWSjHl!5&LV~WBASd}@I^IXW%j*f*CP6)!9cDVC%K~D63d71h6jwFMfq}D)v z<9C(gNyX}0z)t)z3TNf4>b#vyt0d9xQ)0P+|642bV&xxlNL*?R)8i7ksH&*T1Ej{4 zJ_3w$ZY=Cue}+}t#EZO3_Iqx$Pmsu}BZH3fdN9EyOuSkeCT9 zL721;tzuWpn>XCaw|Oq>6VLS4Hx}Ea#!{vNf$I){A`CnZgHSkkQa(Tsy8I}d$pu`0 zfd%AM-lf?sA;3uz<11v|GwQx<+>Z-ptz+B&(^^AZY&@SM_yhn5!RAS`S%WJ87#8xba_TzY3DBy(lq^hj;}hlT8Cf6A7QTAN z9h=$hi7U=pTj>jW5Igorw-5)V>+jr&&{RaOIFE8Eb*oOMGSJR$844Ej=xU5D0glQU zFw-u=*Wxp-d#~(!1cd3IVlAre5o!5O41FPi_EpNZQc~y8J9LtUtaiahW<>c72ST?g z64Ix|78lxV8`OvY#OQ~@jn=vdq5aX}NRJQGHDAatnhbbs8O`;?Y#)Q{{N>=@peIcc zpBY7?He7&xHt)W0EZrYcqHg8dJb)X89?BtX?F__>=kH%r8tz}2xjUfD7;A{7lE3J+ z`Y$-ok@HCkWyY3aaDHuZS2f+NZ)GHv_vIFLq_OE~`1J|U164r*-~f0$^A*N_R37HD zLQSy!H0J7VMWvFKiU5ZjPNGh}UE|APURqT!?&31(W1Z6C5J{vJ2%XQTYe%q5iA-1z zi~Psd3$O?n3^Wso4w~!xj2A8yvmL(1>ZtLqHFy7d4&_Gejf>><6lJRn z%|oa3{y7Y=@~12I6;r@JKL@)5jPHMnFpA)^oIDu~>HjDo{Ld`=^_O0c5Ur2yBdW(f2sS8#$n4L|ei-b&g_nAx2e(3|W;4%~i1EtbgIs909kJ0fu)MxH zQvGp=6vEq{HIBeQzC|&gY?dtVaGzfOQX>PW1&tA0hS^GreE6I#vHjZlL4eZ1!Xr}A zx~Og4r4_R|9UQ>;B#{OCfo@5}hFP$QSZKhH6d`N1pAk67JN+iIK~IQW*#x+K5mDi0 zxjY2xHu#-E+lGuS*lds{*%(<}3a?Jp0p0Ix1i0F)y=TO*kGoE9)vLyP9@~ zq{b$AfMCqYs9H8AOL%Iv#&q$qEo@)h&O2S~>00MmgYW#I*%ZIvdZNy>K~?6xl&`VgUB{4j7j$RESy|rLZa{J%zPNnylVJaU&M;L z(=`2e)FfAAr`R ze6Gt^UTDBHLv(-; zi{(|?0$_VJ0$OIzboGdkW$v0%E>VoB6NA*dX*8w!#u^f6RJoWM6I_+xtq%mbnbEhNk1>T`rTtaD=cMo_9~w=x-=r8QqPia zv9%uBu;0(@KSFp(^Y^47;n9PF^8KV<${|>pW)E$-lw+#KY?9K;{GrHzJ%GD8;Xws> zT@h5{Oj}>^%A0WKSB=&ly|;SjaDt*%-Hmk1(&5j0EGIqON0DaRD#29V~(_ z2ncblqBMp_7=H%Mjg+Z%#hmLUKa*S20+IHZ5p#8yTR?6S|9A+^DpT=LO6JCHYC?8qB~A> zI#|LN&u^6ZKhpbnWDjn?#;;H3>5nuQBcsq=wiJb1VZiMWRRX^b=gOnZ+Uud{r>4)- z#9QoB$875E~C*(f?GcH(_ diff --git a/src/data/tilesets/roguelike-indoors.tsx b/src/data/tilesets/roguelike-indoors.tsx deleted file mode 100644 index f611edd..0000000 --- a/src/data/tilesets/roguelike-indoors.tsx +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/data/ui/panels/blue-pressed.9.png b/src/data/ui/panels/blue-pressed.9.png deleted file mode 100644 index 6ea732747bc290d5b705e6f87ac569a97f897de7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7552 zcmeHMdpML^+aGd1Z{6!z&$=eY!Ol`z zVyy%O0+F`1GIIp4=;fc72-t2gJW&jRNOD7+T)B?qU?@AlkIwJ~pxh8P00npqIt0S& zope7~q7E*9t#kVM@aF-M%-Nuyw_NP1T?h%jE9-x9qIuYB#?6&Z!eWyDP&>_ zUd9OEGA%rIm9FZ?J488qtWRsWH)weIL`k*ua|vhr4viz9k4{U$&05P$B;-R~jpvR; z$865Ms;#`Pj-WnyE@`K)|Jj+83AH`jJFM>0uiRnSD;6=uD|<)t`pTR;whP>D2(OJ8 zA8Id}e}A;>v69J~4=0s+l`bDWO;_dc>zRYl0)>t$fqRF4GHlQ~TI0JV{S+3WcLru5 zQwPzW7)|p((o&MoR<2j00^k$d{ z9_Rj)cjZ{v!N+oW>zmb6kH*;A3io4QLA7$E780GZvoCJVi$$i|6y6zWu8YB7RQOrl zl1VRiYVX^5EhlOJwcIqVF2{q+vfJHa#uz*+=V_<=^wrYKFV5uYv0jGN8LOY35Q#&- z^1f`}azHT*nt7DFJS62Ml&A&c!zva_@4_ao<%1KfwDtYB|-P28v z5%&4WeRqP}sr0Rt#rxbI^f+pp3rTAB5LS_&#J4{8;^JK8I?skE@8&x$C!9wY6!D*OU6jv|cR%mE(^%ib);o-uukUn!ula?6K=Zi9 z%f;K*mY;BSHyI*aN`i-csz0eS#PeB)QEwYh0=l zw)KhFac+H?v*?Ye&)I#w#NJ!cDelUuo)r%713FKiwHvxA^kKNTnBDjSW$7s!5l)?C z7^}BmCY2?}?{ar>df?HuuC!$+e`sq~uioszQIR3#iDQjWF?dskUh@-)eFbSS>a<)v zH@Uuvl58R%6Gw92DZY)o?(~^a6)3eq+&7%tc1!X;^M;d4YLQfM^>#~tw#CD4mDA;W zXOR2^k{rG>li#J6ks(m#?;c4E$gXeKzLnK6@u*%$VT3<*Q~K_J!KNwYb7+S#$yE;! zTi(o{tdHn;GM8^;*^rhc3bzlc(clB!maa3Fy5*JzRY{RGSDx9$-E5h2Vr>alX}R!%OWW>JS=GCgMVV~TS-;r3HTBE` z10_rQyH-!&=fayK1zA!H!jEhge}f@9qhaY%Lr-~|-|wPkVG0HAMt6R( z6PeoDFDW9j>ftUKUFY|wOTMJW_+%cd+kAPirQsPxSn28QX9KHD{3}u$eXVqepMN_L zdEUrdL0aRt4Vz&8R}*Kj>Yg?69ote3gw&32E4dx@WoK)#-U}^_;&|9r$i-q*?CQt_ zXEEfJsJivzQWo}2@yGU#DfX<+f2o`>65AH>)X1)N_*^H!wsY;1%GWQOVk<^><@zKz zLwfB*57gL+njBEvC=jDmPcf=L$=gIN5EM4@I6VkeT=NMczgjKVp08~DZo{*;ZHj}2 z-iO_qv^D}CR7bEyoJpQgUccF_Ea`^M;S8L~Hj1_A-nB^o9K61si%i6(hwb#NxL$g6 zX?efUm?Wa+<>jC+<52iJ0{{GppZUR=JJ>zyAJ+6a>6(rBCabR+JX=|tGNnDR??Dr% zKwQ9?Lw_-e+#fJ5hQHEObHnrch!~BXGsziB7gk&_D39x|=o;)4-=?KP8{2}&DtP(p zrmI2`Bg3kn#*6MA(w~`JyqDz~?cT27i&Dvug$ANjPPDiPCSJe%q4tK7TH&eDn3>3} za&Hfqw8mN`M#2K4tEpzjVYbHnSgD_iy`HL8I<%f5XB%60sK34!IlIPfZvCy6 zri$sTuz||z@Q*To-dl8riB4ilru4is=a!p=+LP)!LX8XqKJ80Fm0%BcJ&8zl{PkgfH0EJK~>;o$kR2G|LO8%Jm zTjYEFvuoQG87q z1)Af1g5-xDW+$RqRrG1=?7mG;#e%bYpm_crtG#O59n4&WrBZTgk{;A~?kj6yaEi6N ztkI!~+j+8%Yc%=_Sfy?r#nwG`5;vkWD#prta+v~&w()vvT;`h`f;K%n8J{>Rr{x=1 zCM+&I(&zL+e~owhe*Zr`8_q_T^s8->H53snUqNsjJYW&U3vN>zxMG_*rRM zZ?rD8y3d9g@!ct4Igfv;_L{A=$k22q>ZEoYFhFSyb+>@HS`M(6^jYaOpGK4 zxUs~?U{24q6gD+V-FtaQ%(+GU3kw%zFt)!7HP(EnXR!G|u|cv;h``Rd+q6;v<`fXA(y(|oRWDguCTab3#bBKc%B=Z;Y)*Kjvc`a< z_9p&d+bsG?>G|;-eLw6VUyN2%oyyc)s;;jupVNHjP-9$K`Q`}jjqtmL3TR+A-3Bq@ z8j9-EnVd4%9Gqc@T6`Hc)x4lB={U{3FjX}qBRhBhgIca<^jW>L&ulc71maxGDyI)G zxYbSBr}hUh_b)a-e3tiJD^uYcV|Ae|y%Q+PeY{^3rYRq{KzASUHV_Sd&ro3az0$}& zXODvwQ#Nf>J@o>6ru@_a+aCFN`gG7$82Mu(Ii-Q^TzmCptOh3B=D2lPnfm!#^=(#7 zyV7o4gunj{6+nHj8rx4zbWN?@&IxrPyjV+67cEgaS@lqmE4!P1O?4>lBQc^eH}2N$w(*hw#6w(FKW;MTH!Yk!eF4{V(AY{a-!nNO zp;iGp%|d*D-Q_e=_LbBVWDkenMWy-m<=O%v5=|+ii>pF@-ME<*W1x;`qxro5>ZS1El%|~c zgR5fKz1w0MIq|V(X)yTqo+Ffgl0^N{W|h80RT05Tqs#QZ=Z*Yl1c5It_mc+Rro6uY zUUR%GS=m4^lz;Tp>*U-CffPNXrE?q8OVmFxBtGfCIb!iVQ8?7^Xrc4%+oL<|ehSA1 zCRGW@$Q(@&1d2kc^tuQ7s=d+{79gav0jcUO4$mQw)jJsAfyk9)OQiZSwaGL;3ZTtn zvcUrr1fp-qW0R=|0WOpRcrjQ8u<6Qb7?eRXfVtpEND|u=@Mc(r1^~N5?VPBg2dTO= znBfiyeI5}6U;@SNEFzAX3_n-mHmE-_5^$P%?SZ%B}5^m08 z1fgL&B%t~MG&<4I%;FmacxM3f=5pCY1R^*%SUVV_?HAyMKL2#(zyRNsbZArr z;0rK8RSxJC^@B@GYm&ns7RwZPF_`QXE0F9TG`S4=pJe?I+w#atI^PEZn*V|OgZA&a zuPB3BBofigj~cifp0$|)Y}r4N=0|1Fh$~$>4It}~$z(Wy1SoJU7LSG#Xm}bNNu^@x zNC2Se;OXB%S+h7?GK&fGzyJDqw)AZmG%Gu9Iz6XF;Pfu40>f`d0L2IG9a)xfO#0`VUzVeO*zz0spUeUvGUGD@zFqU0D=FGWBZ;9C8pq zTZt3I`Z`4QCbPT%@agfbTz{7{{zEC?uy{NLpwr<1jZTJRb&wP|g^nh`b;(p58Kn!L zFj(M6bdDdL8%z!WjJ-e}L9W2^T;U43Wo4qa{-_=74J^+BNElcW@MXdz&L#eE|T z@%N`lL*a3{bP55kL&gJeERBGIlkrFt9H3&5fDQ(%8(rNWgZ{s!h$W(sL@fUQFGUO) zjRq)K8XT{)yr7X-3S1YBq{GQH0-8d=;s6R>=f9ic*Rn_bn4&&n`N{u#-RdL$t+Kx= ze79AAv;S2FZb9IVhWKMk`&Jgqo5$by`L?3|Mh_tLKTiG(@B>p@U11#`eHW)ne0x6ag;Bi@mVq<9rSps`Y8#K-x0k7h0D>n}K zr=!C1PXLmZp$ZB`xz;3e(NQsJc}1nOf>Dv6sKnaL*vWffvbR{e)=1XuiQ6wqM&a9^ z2%)4~6AqT2DO5iNaaToi`9`wPnzSYLUlKCy?s?aiJXMqcn$p7fwI!xO)kZfDs1)Zsh1=m~!MJSSx&ljoe5BqSsjKNc0CARs6tCJQwRhx}XT zi&7n0^X;Mdk=#fHfw$-BdlQ5D5^g+woI%mmKXi5B?VHgHlH(%t-C78RghR1l^bl)v LJF`NQ0}=lMmL&U3 diff --git a/src/data/ui/panels/blue.9.png b/src/data/ui/panels/blue.9.png deleted file mode 100644 index d199a46b2cab7c63c1766615f94b88d2b59b7a31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8149 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 diff --git a/src/data/ui/prompts/dark/a.png b/src/data/ui/prompts/dark/a.png deleted file mode 100644 index 3ae68cab48c28f7ac062362ae287d31a680698a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPF>6_S*0 Sw-*GO#o+1c=d#Wzp$Py+Ycb{k diff --git a/src/data/ui/prompts/dark/d.png b/src/data/ui/prompts/dark/d.png deleted file mode 100644 index c238b13503a1f0ce90601fcd7cbe6bdf1dbf87c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPF7 diff --git a/src/data/ui/prompts/dark/s.png b/src/data/ui/prompts/dark/s.png deleted file mode 100644 index 500206c7e88372b017ca7e0ec7d40ea5aa730e2f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFEX>4Tx04R}tkv&MmKpe$iQ>8^(9PA+CkfDl$g`y&kT7@E12(?114kp)6Xws0R zxHt-~1qVMCs}3&Cx;nTDg5U>;qmz@Oird-(Wz7vovp=l&ewe8FUZPb8jYx?vG-5KnJf zI_G`j2rJ8Z;&bA0gDyz?$aTf#H_j!81)do(GU<8Z2(eh|Vx@~&*-(k6h@*K`qkJLj za-Q=RXSG^q?R)YUh6>usGS_JiBZ);UL4pVcbyQG=g&3_GDJIgipYZSxIev*;3b`s^ zDYDGy0|+FmMa>t+~Cm_i_3FWT>mu4RCM> zjFu>S-Q(TC?%w`A)9&vFonUgfMc42Q00006VoOIv00000008+zyMF)x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru<_HZ94-7~p0+av%02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{003!8L_t(Y$75g^;(!sxaQ2G-Ka?E4ciTcnxCe%shV zf-Dde5XA8K*~>xgXjv(7hIJd(!TC(cl7rpx*xbl8)HCs@2SzVY8+Sg@6|L$(+l r?2gCgMwA#puHi--b98FV4S4_nw7nfHy!i#K00000NkvXXu0mjfxd0Oe diff --git a/src/data/ui/prompts/dark/w.png b/src/data/ui/prompts/dark/w.png deleted file mode 100644 index 46ee0118474dd163808a507837c23146231c9352..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 175 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFgTe~DWM4fJs&iM diff --git a/src/groups.lua b/src/groups.lua deleted file mode 100644 index 4d1c163..0000000 --- a/src/groups.lua +++ /dev/null @@ -1,14 +0,0 @@ -return { - physical = {filter = {"pos", "vel"}}, - player = {filter = { - "pos", "acc", "speed", "bolts" - -- "bolt_count", - -- "bolt_cooldown", "bolt_speed", "bolt_friction" - }}, - controllable_player = {filter = {"controllable"}}, - sprite = {filter = {"pos", "sprite"}}, - image = {filter = {"pos", "image"}}, - bolt = {filter={"pos", "vel", "bolt"}}, - collider = {filter={"collider"}}, - ui_button = {filter={"pos", "size", "text"}} -} diff --git a/src/lib/ase-loader.lua b/src/lib/ase-loader.lua deleted file mode 100644 index 4eae08d..0000000 --- a/src/lib/ase-loader.lua +++ /dev/null @@ -1,374 +0,0 @@ ---[[ -MIT License - -Copyright (c) 2021 Pedro Lucas (github.com/elloramir) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -]]-- - -local File = {} -File.__index = File - -function File.open(filename) - local data, err = love.filesystem.read(filename) - if not data then return nil, err end - - local self = setmetatable({}, File) - self.data = data - self.cursor = 1 - return self -end - -function File:read(size) - local bytes = self.data:sub(self.cursor, self.cursor+size-1) - self.cursor = self.cursor + size - return bytes -end - --- sizes in bytes -local BYTE = 1 -local WORD = 2 -local SHORT = 2 -local DWORD = 4 -local LONG = 4 -local FIXED = 4 - --- parse data/text to number -local function read_num(data, size) - local bytes = data:read(size) - local hex = "" - - for i = size, 1, -1 do - local char = string.sub(bytes, i, i) - hex = hex .. string.format("%02X", string.byte(char)) - end - - return tonumber(hex, 16) -end - --- return a string by it size -local function read_string(data) - local length = read_num(data, WORD) - return data:read(length) -end - -local function grab_header(data) - local header = {} - - header.file_size = read_num(data, DWORD) - header.magic_number = read_num(data, WORD) - - if header.magic_number ~= 0xA5E0 then - error("Not a valid aseprite file") - end - - header.frames_number = read_num(data, WORD) - header.width = read_num(data, WORD) - header.height = read_num(data, WORD) - header.color_depth = read_num(data, WORD) - header.opacity = read_num(data, DWORD) - header.speed = read_num(data, WORD) - - -- skip - read_num(data, DWORD * 2) - - header.palette_entry = read_num(data, BYTE) - - -- skip - read_num(data, BYTE * 3) - - header.number_color = read_num(data, WORD) - header.pixel_width = read_num(data, BYTE) - header.pixel_height = read_num(data, BYTE) - header.grid_x = read_num(data, SHORT) - header.grid_y = read_num(data, SHORT) - header.grid_width = read_num(data, WORD) - header.grid_height = read_num(data, WORD) - - -- skip - read_num(data, BYTE * 84) - - -- to the future - header.frames = {} - - return header -end - -local function grab_frame_header(data) - local frame_header = {} - - frame_header.bytes_size = read_num(data, DWORD) - frame_header.magic_number = read_num(data, WORD) - - if frame_header.magic_number ~= 0xF1FA then - error("Corrupted file") - end - - local old_chunks = read_num(data, WORD) - - frame_header.frame_duration = read_num(data, WORD) - - -- skip - read_num(data, BYTE * 2) - - -- if 0, use old chunks as chunks - local new_chunks = read_num(data, DWORD) - - if new_chunks == 0 then - frame_header.chunks_number = old_chunks - else - frame_header.chunks_number = new_chunks - end - - -- to the future - frame_header.chunks = {} - - return frame_header -end - -local function grab_color_profile(data) - local color_profile = {} - - color_profile.type = read_num(data, WORD) - color_profile.uses_fixed_gama = read_num(data, WORD) - color_profile.fixed_game = read_num(data, FIXED) - - -- skip - read_num(data, BYTE * 8) - - if color_profile.type ~= 1 then - error("No suported color profile, use sRGB") - end - - return color_profile -end - -local function grab_palette(data) - local palette = {} - - palette.entry_size = read_num(data, DWORD) - palette.first_color = read_num(data, DWORD) - palette.last_color = read_num(data, DWORD) - palette.colors = {} - - -- skip - read_num(data, BYTE * 8) - - for i = 1, palette.entry_size do - local has_name = read_num(data, WORD) - - palette.colors[i] = { - color = { - read_num(data, BYTE), - read_num(data, BYTE), - read_num(data, BYTE), - read_num(data, BYTE)}} - - if has_name == 1 then - palette.colors[i].name = read_string(data) - end - end - - return palette -end - -local function grab_old_palette(data) - local palette = {} - - palette.packets = read_num(data, WORD) - palette.colors_packet = {} - - for i = 1, palette.packets do - palette.colors_packet[i] = { - entries = read_num(data, BYTE), - number = read_num(data, BYTE), - colors = {}} - - for j = 1, palette.colors_packet[i].number do - palette.colors_packet[i][j] = { - read_num(data, BYTE), - read_num(data, BYTE), - read_num(data, BYTE)} - end - end - - return palette -end - -local function grab_layer(data) - local layer = {} - - layer.flags = read_num(data, WORD) - layer.type = read_num(data, WORD) - layer.child_level = read_num(data, WORD) - layer.width = read_num(data, WORD) - layer.height = read_num(data, WORD) - layer.blend = read_num(data, WORD) - layer.opacity = read_num(data, BYTE) - - -- skip - read_num(data, BYTE * 3) - - layer.name = read_string(data) - - return layer -end - -local function grab_cel(data, size) - local cel = {} - - cel.layer_index = read_num(data, WORD) - cel.x = read_num(data, WORD) - cel.y = read_num(data, WORD) - cel.opacity_level = read_num(data, BYTE) - cel.type = read_num(data, WORD) - - read_num(data, BYTE * 7) - - if cel.type == 2 then - cel.width = read_num(data, WORD) - cel.height = read_num(data, WORD) - cel.data = data:read(size - 26) - end - - return cel -end - -local function grab_tags(data) - local tags = {} - - tags.number = read_num(data, WORD) - tags.tags = {} - - -- skip - read_num(data, BYTE * 8) - - for i = 1, tags.number do - tags.tags[i] = { - from = read_num(data, WORD), - to = read_num(data, WORD), - direction = read_num(data, BYTE), - extra_byte = read_num(data, BYTE), - color = read_num(data, BYTE * 3), - skip_holder = read_num(data, BYTE * 8), - name = read_string(data)} - end - - return tags -end - -local function grab_slice(data) - local slice = {} - - slice.key_numbers = read_num(data, DWORD) - slice.keys = {} - slice.flags = read_num(data, DWORD) - - -- reserved? - read_num(data, DWORD) - - slice.name = read_string(data) - - for i = 1, slice.key_numbers do - slice.keys[i] = { - frame = read_num(data, DWORD), - x = read_num(data, DWORD), - y = read_num(data, DWORD), - width = read_num(data, DWORD), - height = read_num(data, DWORD)} - - if slice.flags == 1 then - slice.keys[i].center_x = read_num(data, DWORD) - slice.keys[i].center_y = read_num(data, DWORD) - slice.keys[i].center_width = read_num(data, DWORD) - slice.keys[i].center_height = read_num(data, DWORD) - elseif slice.flags == 2 then - slice.keys[i].pivot_x = read_num(data, DWORD) - slice.keys[i].pivot_y = read_num(data, DWORD) - end - end - - return slice -end - -local function grab_user_data(data) - local user_data = {} - - user_data.flags = read_num(data, DWORD) - - if bit.band(user_data.flags, 1) > 0 then - user_data.text = read_string(data) - end - if bit.band(user_data.flags, 2) > 0 then - user_data.colors = read_num(data, BYTE * 4) - end - - return user_data -end - -local function grab_chunk(data) - local chunk = {} - chunk.size = read_num(data, DWORD) - chunk.type = read_num(data, WORD) - - if chunk.type == 0x2007 then - chunk.data = grab_color_profile(data) - elseif chunk.type == 0x2019 then - chunk.data = grab_palette(data) - elseif chunk.type == 0x0004 then - chunk.data = grab_old_palette(data) - elseif chunk.type == 0x2004 then - chunk.data = grab_layer(data) - elseif chunk.type == 0x2005 then - chunk.data = grab_cel(data, chunk.size) - elseif chunk.type == 0x2018 then - chunk.data = grab_tags(data) - elseif chunk.type == 0x2022 then - chunk.data = grab_slice(data) - elseif chunk.type == 0x2020 then - chunk.data = grab_user_data(data) - else - error(("Uknown chunk type: 0x%x"):format(chunk.type)) - end - - return chunk -end - -local function ase_loader(src) - local data = File.open(src) - assert(data, "can't open " .. src) - local ase = {} - - -- parse header - ase.header = grab_header(data) - - -- parse frames - for i = 1, ase.header.frames_number do - ase.header.frames[i] = grab_frame_header(data) - - -- parse frames chunks - for j = 1, ase.header.frames[i].chunks_number do - ase.header.frames[i].chunks[j] = grab_chunk(data) - end - end - - return ase -end - -return ase_loader diff --git a/src/lib/binser.lua b/src/lib/binser.lua deleted file mode 100644 index 22be2f9..0000000 --- a/src/lib/binser.lua +++ /dev/null @@ -1,782 +0,0 @@ --- binser.lua - ---[[ -Copyright (c) 2016-2019 Calvin Rose - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -]] - -local assert = assert -local error = error -local select = select -local pairs = pairs -local getmetatable = getmetatable -local setmetatable = setmetatable -local type = type -local loadstring = loadstring or load -local concat = table.concat -local char = string.char -local byte = string.byte -local format = string.format -local sub = string.sub -local dump = string.dump -local floor = math.floor -local frexp = math.frexp -local unpack = unpack or table.unpack -local ffi = require("ffi") - --- Lua 5.3 frexp polyfill --- From https://github.com/excessive/cpml/blob/master/modules/utils.lua -if not frexp then - local log, abs, floor = math.log, math.abs, math.floor - local log2 = log(2) - frexp = function(x) - if x == 0 then return 0, 0 end - local e = floor(log(abs(x)) / log2 + 1) - return x / 2 ^ e, e - end -end - -local function pack(...) - return {...}, select("#", ...) -end - -local function not_array_index(x, len) - return type(x) ~= "number" or x < 1 or x > len or x ~= floor(x) -end - -local function type_check(x, tp, name) - assert(type(x) == tp, - format("Expected parameter %q to be of type %q.", name, tp)) -end - -local bigIntSupport = false -local isInteger -if math.type then -- Detect Lua 5.3 - local mtype = math.type - bigIntSupport = loadstring[[ - local char = string.char - return function(n) - local nn = n < 0 and -(n + 1) or n - local b1 = nn // 0x100000000000000 - local b2 = nn // 0x1000000000000 % 0x100 - local b3 = nn // 0x10000000000 % 0x100 - local b4 = nn // 0x100000000 % 0x100 - local b5 = nn // 0x1000000 % 0x100 - local b6 = nn // 0x10000 % 0x100 - local b7 = nn // 0x100 % 0x100 - local b8 = nn % 0x100 - if n < 0 then - b1, b2, b3, b4 = 0xFF - b1, 0xFF - b2, 0xFF - b3, 0xFF - b4 - b5, b6, b7, b8 = 0xFF - b5, 0xFF - b6, 0xFF - b7, 0xFF - b8 - end - return char(212, b1, b2, b3, b4, b5, b6, b7, b8) - end]]() - isInteger = function(x) - return mtype(x) == 'integer' - end -else - isInteger = function(x) - return floor(x) == x - end -end - --- Copyright (C) 2012-2015 Francois Perrad. --- number serialization code modified from https://github.com/fperrad/lua-MessagePack --- Encode a number as a big-endian ieee-754 double, big-endian signed 64 bit integer, or a small integer -local function number_to_str(n) - if isInteger(n) then -- int - if n <= 100 and n >= -27 then -- 1 byte, 7 bits of data - return char(n + 27) - elseif n <= 8191 and n >= -8192 then -- 2 bytes, 14 bits of data - n = n + 8192 - return char(128 + (floor(n / 0x100) % 0x100), n % 0x100) - elseif bigIntSupport then - return bigIntSupport(n) - end - end - local sign = 0 - if n < 0.0 then - sign = 0x80 - n = -n - end - local m, e = frexp(n) -- mantissa, exponent - if m ~= m then - return char(203, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) - elseif m == 1/0 then - if sign == 0 then - return char(203, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) - else - return char(203, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) - end - end - e = e + 0x3FE - if e < 1 then -- denormalized numbers - m = m * 2 ^ (52 + e) - e = 0 - else - m = (m * 2 - 1) * 2 ^ 52 - end - return char(203, - sign + floor(e / 0x10), - (e % 0x10) * 0x10 + floor(m / 0x1000000000000), - floor(m / 0x10000000000) % 0x100, - floor(m / 0x100000000) % 0x100, - floor(m / 0x1000000) % 0x100, - floor(m / 0x10000) % 0x100, - floor(m / 0x100) % 0x100, - m % 0x100) -end - --- Copyright (C) 2012-2015 Francois Perrad. --- number deserialization code also modified from https://github.com/fperrad/lua-MessagePack -local function number_from_str(str, index) - local b = byte(str, index) - if not b then error("Expected more bytes of input.") end - if b < 128 then - return b - 27, index + 1 - elseif b < 192 then - local b2 = byte(str, index + 1) - if not b2 then error("Expected more bytes of input.") end - return b2 + 0x100 * (b - 128) - 8192, index + 2 - end - local b1, b2, b3, b4, b5, b6, b7, b8 = byte(str, index + 1, index + 8) - if (not b1) or (not b2) or (not b3) or (not b4) or - (not b5) or (not b6) or (not b7) or (not b8) then - error("Expected more bytes of input.") - end - if b == 212 then - local flip = b1 >= 128 - if flip then -- negative - b1, b2, b3, b4 = 0xFF - b1, 0xFF - b2, 0xFF - b3, 0xFF - b4 - b5, b6, b7, b8 = 0xFF - b5, 0xFF - b6, 0xFF - b7, 0xFF - b8 - end - local n = ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) * - 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8 - if flip then - return (-n) - 1, index + 9 - else - return n, index + 9 - end - end - if b ~= 203 then - error("Expected number") - end - local sign = b1 > 0x7F and -1 or 1 - local e = (b1 % 0x80) * 0x10 + floor(b2 / 0x10) - local m = ((((((b2 % 0x10) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8 - local n - if e == 0 then - if m == 0 then - n = sign * 0.0 - else - n = sign * (m / 2 ^ 52) * 2 ^ -1022 - end - elseif e == 0x7FF then - if m == 0 then - n = sign * (1/0) - else - n = 0.0/0.0 - end - else - n = sign * (1.0 + m / 2 ^ 52) * 2 ^ (e - 0x3FF) - end - return n, index + 9 -end - - -local function newbinser() - - -- unique table key for getting next value - local NEXT = {} - local CTORSTACK = {} - - -- NIL = 202 - -- FLOAT = 203 - -- TRUE = 204 - -- FALSE = 205 - -- STRING = 206 - -- TABLE = 207 - -- REFERENCE = 208 - -- CONSTRUCTOR = 209 - -- FUNCTION = 210 - -- RESOURCE = 211 - -- INT64 = 212 - -- TABLE WITH META = 213 - - local mts = {} - local ids = {} - local serializers = {} - local deserializers = {} - local resources = {} - local resources_by_name = {} - local types = {} - - types["nil"] = function(x, visited, accum) - accum[#accum + 1] = "\202" - end - - function types.number(x, visited, accum) - accum[#accum + 1] = number_to_str(x) - end - - function types.boolean(x, visited, accum) - accum[#accum + 1] = x and "\204" or "\205" - end - - function types.string(x, visited, accum) - local alen = #accum - if visited[x] then - accum[alen + 1] = "\208" - accum[alen + 2] = number_to_str(visited[x]) - else - visited[x] = visited[NEXT] - visited[NEXT] = visited[NEXT] + 1 - accum[alen + 1] = "\206" - accum[alen + 2] = number_to_str(#x) - accum[alen + 3] = x - end - end - - local function check_custom_type(x, visited, accum) - local res = resources[x] - if res then - accum[#accum + 1] = "\211" - types[type(res)](res, visited, accum) - return true - end - local mt = getmetatable(x) - local id = (mt and ids[mt]) or (ffi and type(x) == "cdata" and ids[tostring(ffi.typeof(x))]) - if id then - local constructing = visited[CTORSTACK] - if constructing[x] then - error("Infinite loop in constructor.") - end - constructing[x] = true - accum[#accum + 1] = "\209" - types[type(id)](id, visited, accum) - local args, len = pack(serializers[id](x)) - accum[#accum + 1] = number_to_str(len) - for i = 1, len do - local arg = args[i] - types[type(arg)](arg, visited, accum) - end - visited[x] = visited[NEXT] - visited[NEXT] = visited[NEXT] + 1 - -- We finished constructing - constructing[x] = nil - return true - end - end - - function types.userdata(x, visited, accum) - if visited[x] then - accum[#accum + 1] = "\208" - accum[#accum + 1] = number_to_str(visited[x]) - else - if check_custom_type(x, visited, accum) then return end - error("Cannot serialize this userdata.") - end - end - - function types.table(x, visited, accum) - if visited[x] then - accum[#accum + 1] = "\208" - accum[#accum + 1] = number_to_str(visited[x]) - else - if check_custom_type(x, visited, accum) then return end - visited[x] = visited[NEXT] - visited[NEXT] = visited[NEXT] + 1 - local xlen = #x - local mt = getmetatable(x) - if mt then - accum[#accum + 1] = "\213" - types.table(mt, visited, accum) - else - accum[#accum + 1] = "\207" - end - accum[#accum + 1] = number_to_str(xlen) - for i = 1, xlen do - local v = x[i] - types[type(v)](v, visited, accum) - end - local key_count = 0 - for k in pairs(x) do - if not_array_index(k, xlen) then - key_count = key_count + 1 - end - end - accum[#accum + 1] = number_to_str(key_count) - for k, v in pairs(x) do - if not_array_index(k, xlen) then - types[type(k)](k, visited, accum) - types[type(v)](v, visited, accum) - end - end - end - end - - types["function"] = function(x, visited, accum) - if visited[x] then - accum[#accum + 1] = "\208" - accum[#accum + 1] = number_to_str(visited[x]) - else - if check_custom_type(x, visited, accum) then return end - visited[x] = visited[NEXT] - visited[NEXT] = visited[NEXT] + 1 - local str = dump(x) - accum[#accum + 1] = "\210" - accum[#accum + 1] = number_to_str(#str) - accum[#accum + 1] = str - end - end - - types.cdata = function(x, visited, accum) - if visited[x] then - accum[#accum + 1] = "\208" - accum[#accum + 1] = number_to_str(visited[x]) - else - if check_custom_type(x, visited, accum) then return end - error("Cannot serialize this cdata.") - end - end - - types.thread = function() error("Cannot serialize threads.") end - - local function deserialize_value(str, index, visited) - local t = byte(str, index) - if not t then return nil, index end - if t < 128 then - return t - 27, index + 1 - elseif t < 192 then - local b2 = byte(str, index + 1) - if not b2 then error("Expected more bytes of input.") end - return b2 + 0x100 * (t - 128) - 8192, index + 2 - elseif t == 202 then - return nil, index + 1 - elseif t == 203 or t == 212 then - return number_from_str(str, index) - elseif t == 204 then - return true, index + 1 - elseif t == 205 then - return false, index + 1 - elseif t == 206 then - local length, dataindex = number_from_str(str, index + 1) - local nextindex = dataindex + length - if not (length >= 0) then error("Bad string length") end - if #str < nextindex - 1 then error("Expected more bytes of string") end - local substr = sub(str, dataindex, nextindex - 1) - visited[#visited + 1] = substr - return substr, nextindex - elseif t == 207 or t == 213 then - local mt, count, nextindex - local ret = {} - visited[#visited + 1] = ret - nextindex = index + 1 - if t == 213 then - mt, nextindex = deserialize_value(str, nextindex, visited) - if type(mt) ~= "table" then error("Expected table metatable") end - end - count, nextindex = number_from_str(str, nextindex) - for i = 1, count do - local oldindex = nextindex - ret[i], nextindex = deserialize_value(str, nextindex, visited) - if nextindex == oldindex then error("Expected more bytes of input.") end - end - count, nextindex = number_from_str(str, nextindex) - for i = 1, count do - local k, v - local oldindex = nextindex - k, nextindex = deserialize_value(str, nextindex, visited) - if nextindex == oldindex then error("Expected more bytes of input.") end - oldindex = nextindex - v, nextindex = deserialize_value(str, nextindex, visited) - if nextindex == oldindex then error("Expected more bytes of input.") end - if k == nil then error("Can't have nil table keys") end - ret[k] = v - end - if mt then setmetatable(ret, mt) end - return ret, nextindex - elseif t == 208 then - local ref, nextindex = number_from_str(str, index + 1) - return visited[ref], nextindex - elseif t == 209 then - local count - local name, nextindex = deserialize_value(str, index + 1, visited) - count, nextindex = number_from_str(str, nextindex) - local args = {} - for i = 1, count do - local oldindex = nextindex - args[i], nextindex = deserialize_value(str, nextindex, visited) - if nextindex == oldindex then error("Expected more bytes of input.") end - end - if not name or not deserializers[name] then - error(("Cannot deserialize class '%s'"):format(tostring(name))) - end - local ret = deserializers[name](unpack(args)) - visited[#visited + 1] = ret - return ret, nextindex - elseif t == 210 then - local length, dataindex = number_from_str(str, index + 1) - local nextindex = dataindex + length - if not (length >= 0) then error("Bad string length") end - if #str < nextindex - 1 then error("Expected more bytes of string") end - local ret = loadstring(sub(str, dataindex, nextindex - 1)) - visited[#visited + 1] = ret - return ret, nextindex - elseif t == 211 then - local resname, nextindex = deserialize_value(str, index + 1, visited) - if resname == nil then error("Got nil resource name") end - local res = resources_by_name[resname] - if res == nil then - error(("No resources found for name '%s'"):format(tostring(resname))) - end - return res, nextindex - else - error("Could not deserialize type byte " .. t .. ".") - end - end - - local function serialize(...) - local visited = {[NEXT] = 1, [CTORSTACK] = {}} - local accum = {} - for i = 1, select("#", ...) do - local x = select(i, ...) - types[type(x)](x, visited, accum) - end - return concat(accum) - end - - local function make_file_writer(file) - return setmetatable({}, { - __newindex = function(_, _, v) - file:write(v) - end - }) - end - - local function serialize_to_file(path, mode, ...) - local file, err = io.open(path, mode) - assert(file, err) - local visited = {[NEXT] = 1, [CTORSTACK] = {}} - local accum = make_file_writer(file) - for i = 1, select("#", ...) do - local x = select(i, ...) - types[type(x)](x, visited, accum) - end - -- flush the writer - file:flush() - file:close() - end - - local function writeFile(path, ...) - return serialize_to_file(path, "wb", ...) - end - - local function appendFile(path, ...) - return serialize_to_file(path, "ab", ...) - end - - local function deserialize(str, index) - assert(type(str) == "string", "Expected string to deserialize.") - local vals = {} - index = index or 1 - local visited = {} - local len = 0 - local val - while true do - local nextindex - val, nextindex = deserialize_value(str, index, visited) - if nextindex > index then - len = len + 1 - vals[len] = val - index = nextindex - else - break - end - end - return vals, len - end - - local function deserializeN(str, n, index) - assert(type(str) == "string", "Expected string to deserialize.") - n = n or 1 - assert(type(n) == "number", "Expected a number for parameter n.") - assert(n > 0 and floor(n) == n, "N must be a poitive integer.") - local vals = {} - index = index or 1 - local visited = {} - local len = 0 - local val - while len < n do - local nextindex - val, nextindex = deserialize_value(str, index, visited) - if nextindex > index then - len = len + 1 - vals[len] = val - index = nextindex - else - break - end - end - vals[len + 1] = index - return unpack(vals, 1, n + 1) - end - - local function readFile(path) - local file, err = io.open(path, "rb") - assert(file, err) - local str = file:read("*all") - file:close() - return deserialize(str) - end - - -- Resources - - local function registerResource(resource, name) - type_check(name, "string", "name") - assert(not resources[resource], - "Resource already registered.") - assert(not resources_by_name[name], - format("Resource %q already exists.", name)) - resources_by_name[name] = resource - resources[resource] = name - return resource - end - - local function unregisterResource(name) - type_check(name, "string", "name") - assert(resources_by_name[name], format("Resource %q does not exist.", name)) - local resource = resources_by_name[name] - resources_by_name[name] = nil - resources[resource] = nil - return resource - end - - -- Templating - - local function normalize_template(template) - local ret = {} - for i = 1, #template do - ret[i] = template[i] - end - local non_array_part = {} - -- The non-array part of the template (nested templates) have to be deterministic, so they are sorted. - -- This means that inherently non deterministicly sortable keys (tables, functions) should NOT be used - -- in templates. Looking for way around this. - for k in pairs(template) do - if not_array_index(k, #template) then - non_array_part[#non_array_part + 1] = k - end - end - table.sort(non_array_part) - for i = 1, #non_array_part do - local name = non_array_part[i] - ret[#ret + 1] = {name, normalize_template(template[name])} - end - return ret - end - - local function templatepart_serialize(part, argaccum, x, len) - local extras = {} - local extracount = 0 - for k, v in pairs(x) do - extras[k] = v - extracount = extracount + 1 - end - for i = 1, #part do - local name - if type(part[i]) == "table" then - name = part[i][1] - len = templatepart_serialize(part[i][2], argaccum, x[name], len) - else - name = part[i] - len = len + 1 - argaccum[len] = x[part[i]] - end - if extras[name] ~= nil then - extracount = extracount - 1 - extras[name] = nil - end - end - if extracount > 0 then - argaccum[len + 1] = extras - else - argaccum[len + 1] = nil - end - return len + 1 - end - - local function templatepart_deserialize(ret, part, values, vindex) - for i = 1, #part do - local name = part[i] - if type(name) == "table" then - local newret = {} - ret[name[1]] = newret - vindex = templatepart_deserialize(newret, name[2], values, vindex) - else - ret[name] = values[vindex] - vindex = vindex + 1 - end - end - local extras = values[vindex] - if extras then - for k, v in pairs(extras) do - ret[k] = v - end - end - return vindex + 1 - end - - local function template_serializer_and_deserializer(metatable, template) - return function(x) - local argaccum = {} - local len = templatepart_serialize(template, argaccum, x, 0) - return unpack(argaccum, 1, len) - end, function(...) - local ret = {} - local args = {...} - templatepart_deserialize(ret, template, args, 1) - return setmetatable(ret, metatable) - end - end - - -- Used to serialize classes withh custom serializers and deserializers. - -- If no _serialize or _deserialize (or no _template) value is found in the - -- metatable, then the metatable is registered as a resources. - local function register(metatable, name, serialize, deserialize) - if type(metatable) == "table" then - name = name or metatable.name - serialize = serialize or metatable._serialize - deserialize = deserialize or metatable._deserialize - if (not serialize) or (not deserialize) then - if metatable._template then - -- Register as template - local t = normalize_template(metatable._template) - serialize, deserialize = template_serializer_and_deserializer(metatable, t) - else - -- Register the metatable as a resource. This is semantically - -- similar and more flexible (handles cycles). - registerResource(metatable, name) - return - end - end - elseif type(metatable) == "string" then - name = name or metatable - end - type_check(name, "string", "name") - type_check(serialize, "function", "serialize") - type_check(deserialize, "function", "deserialize") - assert((not ids[metatable]) and (not resources[metatable]), - "Metatable already registered.") - assert((not mts[name]) and (not resources_by_name[name]), - ("Name %q already registered."):format(name)) - mts[name] = metatable - ids[metatable] = name - serializers[name] = serialize - deserializers[name] = deserialize - return metatable - end - - local function unregister(item) - local name, metatable - if type(item) == "string" then -- assume name - name, metatable = item, mts[item] - else -- assume metatable - name, metatable = ids[item], item - end - type_check(name, "string", "name") - mts[name] = nil - if (metatable) then - resources[metatable] = nil - ids[metatable] = nil - end - serializers[name] = nil - deserializers[name] = nil - resources_by_name[name] = nil; - return metatable - end - - local function registerClass(class, name) - name = name or class.name - if class.__instanceDict then -- middleclass - register(class.__instanceDict, name) - else -- assume 30log or similar library - register(class, name) - end - return class - end - - local registerStruct - local unregisterStruct - if ffi then - function registerStruct(name, serialize, deserialize) - type_check(name, "string", "name") - type_check(serialize, "function", "serialize") - type_check(deserialize, "function", "deserialize") - assert((not mts[name]) and (not resources_by_name[name]), - ("Name %q already registered."):format(name)) - local ctype_str = tostring(ffi.typeof(name)) - ids[ctype_str] = name - mts[name] = ctype_str - serializers[name] = serialize - deserializers[name] = deserialize - return name - end - - function unregisterStruct(name) - type_check(name, "string", "name") - local ctype_str = tostring(ffi.typeof(name)) - ids[ctype_str] = nil - mts[name] = nil - serializers[name] = nil - deserializers[name] = nil - return name - end - end - - return { - VERSION = "0.0-8", - -- aliases - s = serialize, - d = deserialize, - dn = deserializeN, - r = readFile, - w = writeFile, - a = appendFile, - - serialize = serialize, - deserialize = deserialize, - deserializeN = deserializeN, - readFile = readFile, - writeFile = writeFile, - appendFile = appendFile, - register = register, - unregister = unregister, - registerResource = registerResource, - unregisterResource = unregisterResource, - registerClass = registerClass, - - registerStruct = registerStruct, - unregisterStruct = unregisterStruct, - - newbinser = newbinser - } -end - -return newbinser() diff --git a/src/lib/cargo.lua b/src/lib/cargo.lua deleted file mode 100644 index 9d777ef..0000000 --- a/src/lib/cargo.lua +++ /dev/null @@ -1,104 +0,0 @@ --- cargo v0.1.1 --- https://github.com/bjornbytes/cargo --- MIT License - -local cargo = {} - -local function merge(target, source, ...) - if not target or not source then return target end - for k, v in pairs(source) do target[k] = v end - return merge(target, ...) -end - -local la, lf, lg = love.audio, love.filesystem, love.graphics - -local function makeSound(path) - local info = lf.getInfo(path, 'file') - return la.newSource(path, (info and info.size and info.size < 5e5) and 'static' or 'stream') -end - -local function makeFont(path) - return function(size) - return lg.newFont(path, size) - end -end - -local function loadFile(path) - return lf.load(path)() -end - -cargo.loaders = { - lua = lf and loadFile, - png = lg and lg.newImage, - jpg = lg and lg.newImage, - dds = lg and lg.newImage, - ogv = lg and lg.newVideo, - glsl = lg and lg.newShader, - mp3 = la and makeSound, - ogg = la and makeSound, - wav = la and makeSound, - flac = la and makeSound, - txt = lf and lf.read, - ttf = lg and makeFont, - otf = lg and makeFont, - fnt = lg and lg.newFont -} - -cargo.processors = {} - -function cargo.init(config) - if type(config) == 'string' then - config = { dir = config } - end - - local loaders = merge({}, cargo.loaders, config.loaders) - local processors = merge({}, cargo.processors, config.processors) - - local init - - local function halp(t, k) - local path = (t._path .. '/' .. k):gsub('^/+', '') - if lf.getInfo(path, 'directory') then - rawset(t, k, init(path)) - return t[k] - else - for extension, loader in pairs(loaders) do - local file = path .. '.' .. extension - local fileInfo = lf.getInfo(file) - if loader and fileInfo then - local asset = loader(file) - rawset(t, k, asset) - for pattern, processor in pairs(processors) do - if file:match(pattern) then - processor(asset, file, t) - end - end - return asset - end - end - end - - return rawget(t, k) - end - - local function __call(t, recurse) - for i, f in ipairs(love.filesystem.getDirectoryItems(t._path)) do - local key = f:gsub('%..-$', '') - halp(t, key) - - if recurse and love.filesystem.getInfo(t._path .. '/' .. f, 'directory') then - t[key](recurse) - end - end - - return t - end - - init = function(path) - return setmetatable({ _path = path }, { __index = halp, __call = __call }) - end - - return init(config.dir) -end - -return cargo diff --git a/src/lib/json.lua b/src/lib/json.lua deleted file mode 100644 index 711ef78..0000000 --- a/src/lib/json.lua +++ /dev/null @@ -1,388 +0,0 @@ --- --- json.lua --- --- Copyright (c) 2020 rxi --- --- Permission is hereby granted, free of charge, to any person obtaining a copy of --- this software and associated documentation files (the "Software"), to deal in --- the Software without restriction, including without limitation the rights to --- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies --- of the Software, and to permit persons to whom the Software is furnished to do --- so, subject to the following conditions: --- --- The above copyright notice and this permission notice shall be included in all --- copies or substantial portions of the Software. --- --- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --- SOFTWARE. --- - -local json = { _version = "0.1.2" } - -------------------------------------------------------------------------------- --- Encode -------------------------------------------------------------------------------- - -local encode - -local escape_char_map = { - [ "\\" ] = "\\", - [ "\"" ] = "\"", - [ "\b" ] = "b", - [ "\f" ] = "f", - [ "\n" ] = "n", - [ "\r" ] = "r", - [ "\t" ] = "t", -} - -local escape_char_map_inv = { [ "/" ] = "/" } -for k, v in pairs(escape_char_map) do - escape_char_map_inv[v] = k -end - - -local function escape_char(c) - return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) -end - - -local function encode_nil(val) - return "null" -end - - -local function encode_table(val, stack) - local res = {} - stack = stack or {} - - -- Circular reference? - if stack[val] then error("circular reference") end - - stack[val] = true - - if rawget(val, 1) ~= nil or next(val) == nil then - -- Treat as array -- check keys are valid and it is not sparse - local n = 0 - for k in pairs(val) do - if type(k) ~= "number" then - error("invalid table: mixed or invalid key types") - end - n = n + 1 - end - if n ~= #val then - error("invalid table: sparse array") - end - -- Encode - for i, v in ipairs(val) do - table.insert(res, encode(v, stack)) - end - stack[val] = nil - return "[" .. table.concat(res, ",") .. "]" - - else - -- Treat as an object - for k, v in pairs(val) do - if type(k) ~= "string" then - error("invalid table: mixed or invalid key types") - end - table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) - end - stack[val] = nil - return "{" .. table.concat(res, ",") .. "}" - end -end - - -local function encode_string(val) - return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' -end - - -local function encode_number(val) - -- Check for NaN, -inf and inf - if val ~= val or val <= -math.huge or val >= math.huge then - error("unexpected number value '" .. tostring(val) .. "'") - end - return string.format("%.14g", val) -end - - -local type_func_map = { - [ "nil" ] = encode_nil, - [ "table" ] = encode_table, - [ "string" ] = encode_string, - [ "number" ] = encode_number, - [ "boolean" ] = tostring, -} - - -encode = function(val, stack) - local t = type(val) - local f = type_func_map[t] - if f then - return f(val, stack) - end - error("unexpected type '" .. t .. "'") -end - - -function json.encode(val) - return ( encode(val) ) -end - - -------------------------------------------------------------------------------- --- Decode -------------------------------------------------------------------------------- - -local parse - -local function create_set(...) - local res = {} - for i = 1, select("#", ...) do - res[ select(i, ...) ] = true - end - return res -end - -local space_chars = create_set(" ", "\t", "\r", "\n") -local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") -local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") -local literals = create_set("true", "false", "null") - -local literal_map = { - [ "true" ] = true, - [ "false" ] = false, - [ "null" ] = nil, -} - - -local function next_char(str, idx, set, negate) - for i = idx, #str do - if set[str:sub(i, i)] ~= negate then - return i - end - end - return #str + 1 -end - - -local function decode_error(str, idx, msg) - local line_count = 1 - local col_count = 1 - for i = 1, idx - 1 do - col_count = col_count + 1 - if str:sub(i, i) == "\n" then - line_count = line_count + 1 - col_count = 1 - end - end - error( string.format("%s at line %d col %d", msg, line_count, col_count) ) -end - - -local function codepoint_to_utf8(n) - -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa - local f = math.floor - if n <= 0x7f then - return string.char(n) - elseif n <= 0x7ff then - return string.char(f(n / 64) + 192, n % 64 + 128) - elseif n <= 0xffff then - return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) - elseif n <= 0x10ffff then - return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, - f(n % 4096 / 64) + 128, n % 64 + 128) - end - error( string.format("invalid unicode codepoint '%x'", n) ) -end - - -local function parse_unicode_escape(s) - local n1 = tonumber( s:sub(1, 4), 16 ) - local n2 = tonumber( s:sub(7, 10), 16 ) - -- Surrogate pair? - if n2 then - return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) - else - return codepoint_to_utf8(n1) - end -end - - -local function parse_string(str, i) - local res = "" - local j = i + 1 - local k = j - - while j <= #str do - local x = str:byte(j) - - if x < 32 then - decode_error(str, j, "control character in string") - - elseif x == 92 then -- `\`: Escape - res = res .. str:sub(k, j - 1) - j = j + 1 - local c = str:sub(j, j) - if c == "u" then - local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) - or str:match("^%x%x%x%x", j + 1) - or decode_error(str, j - 1, "invalid unicode escape in string") - res = res .. parse_unicode_escape(hex) - j = j + #hex - else - if not escape_chars[c] then - decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") - end - res = res .. escape_char_map_inv[c] - end - k = j + 1 - - elseif x == 34 then -- `"`: End of string - res = res .. str:sub(k, j - 1) - return res, j + 1 - end - - j = j + 1 - end - - decode_error(str, i, "expected closing quote for string") -end - - -local function parse_number(str, i) - local x = next_char(str, i, delim_chars) - local s = str:sub(i, x - 1) - local n = tonumber(s) - if not n then - decode_error(str, i, "invalid number '" .. s .. "'") - end - return n, x -end - - -local function parse_literal(str, i) - local x = next_char(str, i, delim_chars) - local word = str:sub(i, x - 1) - if not literals[word] then - decode_error(str, i, "invalid literal '" .. word .. "'") - end - return literal_map[word], x -end - - -local function parse_array(str, i) - local res = {} - local n = 1 - i = i + 1 - while 1 do - local x - i = next_char(str, i, space_chars, true) - -- Empty / end of array? - if str:sub(i, i) == "]" then - i = i + 1 - break - end - -- Read token - x, i = parse(str, i) - res[n] = x - n = n + 1 - -- Next token - i = next_char(str, i, space_chars, true) - local chr = str:sub(i, i) - i = i + 1 - if chr == "]" then break end - if chr ~= "," then decode_error(str, i, "expected ']' or ','") end - end - return res, i -end - - -local function parse_object(str, i) - local res = {} - i = i + 1 - while 1 do - local key, val - i = next_char(str, i, space_chars, true) - -- Empty / end of object? - if str:sub(i, i) == "}" then - i = i + 1 - break - end - -- Read key - if str:sub(i, i) ~= '"' then - decode_error(str, i, "expected string for key") - end - key, i = parse(str, i) - -- Read ':' delimiter - i = next_char(str, i, space_chars, true) - if str:sub(i, i) ~= ":" then - decode_error(str, i, "expected ':' after key") - end - i = next_char(str, i + 1, space_chars, true) - -- Read value - val, i = parse(str, i) - -- Set - res[key] = val - -- Next token - i = next_char(str, i, space_chars, true) - local chr = str:sub(i, i) - i = i + 1 - if chr == "}" then break end - if chr ~= "," then decode_error(str, i, "expected '}' or ','") end - end - return res, i -end - - -local char_func_map = { - [ '"' ] = parse_string, - [ "0" ] = parse_number, - [ "1" ] = parse_number, - [ "2" ] = parse_number, - [ "3" ] = parse_number, - [ "4" ] = parse_number, - [ "5" ] = parse_number, - [ "6" ] = parse_number, - [ "7" ] = parse_number, - [ "8" ] = parse_number, - [ "9" ] = parse_number, - [ "-" ] = parse_number, - [ "t" ] = parse_literal, - [ "f" ] = parse_literal, - [ "n" ] = parse_literal, - [ "[" ] = parse_array, - [ "{" ] = parse_object, -} - - -parse = function(str, idx) - local chr = str:sub(idx, idx) - local f = char_func_map[chr] - if f then - return f(str, idx) - end - decode_error(str, idx, "unexpected character '" .. chr .. "'") -end - - -function json.decode(str) - if type(str) ~= "string" then - error("expected argument of type string, got " .. type(str)) - end - local res, idx = parse(str, next_char(str, 1, space_chars, true)) - idx = next_char(str, idx, space_chars, true) - if idx <= #str then - decode_error(str, idx, "trailing garbage") - end - return res -end - - -return json diff --git a/src/lib/slicy.lua b/src/lib/slicy.lua deleted file mode 100644 index 30f1bb1..0000000 --- a/src/lib/slicy.lua +++ /dev/null @@ -1,497 +0,0 @@ -local M = {} - ---[[ - file format is as follows: - * extension: *.9.png, same as patchy and (afaik) behaves the same - * original author never documented it anywhere - * actual image has a 1 pixel border on all sides - * pixels in the border can either be black with opacity > 0 ("set") or not - * set pixels serve as metadata for how to slice the image into 9 patches - * first and last set pixels on the top and left define the interval for the "edge" portions of the image - * image "edge" will be scaled in 1 dimension to accomodate variable size - * first and last set pixels on the bottom and right define the "content window" - * content window defines inner padding so that content doesn't touch the borders - * can be different from the "edge" definitions - * getContentRegion() returns the content region bounds for a given size -]] - ----@class PatchedImage ----@field patches table ----@field contentPadding table ----@field x number ----@field y number ----@field width number ----@field height number -local PatchedImage = {} - -local PatchedImageMt = {__index = PatchedImage} - -local debugDraw = false -local debugLog = false -local DEBUG_DRAW_SEP_WIDTH = 1 - -local function dbg(...) - if debugLog then - print(...) - end -end - -local function firstBlackPixel(imgData, axis, axisoffset, reversed) - local lim, getpixel - if axis == "row" then - lim = imgData:getWidth() - 1 - getpixel = function(idx) - return imgData:getPixel(idx, axisoffset) - end - elseif axis == "col" then - lim = imgData:getHeight() - 1 - getpixel = function(idx) - return imgData:getPixel(axisoffset, idx) - end - else - return nil, "argument 2: expected either 'row' or 'col', got " .. tostring(axis) - end - dbg("looking for black pixel in axis", axis, axisoffset) - - local startidx, endidx, step - if reversed then - -- start at last valid position, down to 0 - startidx, endidx, step = lim, 0, -1 - else - -- go upwards instead - startidx, endidx, step = 0, lim, 1 - end - - for idx = startidx, endidx, step do - local r, g, b, a = getpixel(idx) - -- black non-transparent pixel - if r + g + b == 0 and a > 0 then - dbg("black pixel found at idx", idx) - return idx - end - end - return nil, "no black pixel found" -end - -local function setCorners(p, rawdata, horizontalEdgeSegment, verticalEdgeSegment) - dbg("slicing corners") - - -- -1 from file format border, -1 from excluding the pixel itself - local brCornerWidth = rawdata:getWidth() - horizontalEdgeSegment[2] - 2 - local brCornerHeight = rawdata:getHeight() - verticalEdgeSegment[2] - 2 - - local tlCorner = love.image.newImageData(horizontalEdgeSegment[1] - 1, verticalEdgeSegment[1] - 1) - tlCorner:paste( - rawdata, - 0, 0, - -- skip metadata column and row - 1, 1, - tlCorner:getDimensions() - ) - p.patches[1][1] = love.graphics.newImage(tlCorner, {}) - tlCorner:release() - dbg("top left corner:", p.patches[1][1]:getDimensions()) - - local trCorner = love.image.newImageData(brCornerWidth, verticalEdgeSegment[1] - 1) - trCorner:paste( - rawdata, - 0, 0, - horizontalEdgeSegment[2] + 1, 1, - trCorner:getDimensions() - ) - p.patches[1][3] = love.graphics.newImage(trCorner, {}) - trCorner:release() - dbg("top right corner:", p.patches[1][3]:getDimensions()) - - local blCorner = love.image.newImageData(horizontalEdgeSegment[1] - 1, brCornerHeight) - blCorner:paste( - rawdata, - 0, 0, - 1, verticalEdgeSegment[2] + 1, - blCorner:getDimensions() - ) - p.patches[3][1] = love.graphics.newImage(blCorner, {}) - blCorner:release() - dbg("bottom left corner:", p.patches[3][1]:getDimensions()) - - local brCorner = love.image.newImageData(brCornerWidth, brCornerHeight) - brCorner:paste( - rawdata, - 0, 0, - horizontalEdgeSegment[2] + 1, verticalEdgeSegment[2] + 1, - brCorner:getDimensions() - ) - p.patches[3][3] = love.graphics.newImage(brCorner, {}) - brCorner:release() - dbg("bottom right corner:", p.patches[3][3]:getDimensions()) -end - -local function setMiddle(p, rawdata, horizontalEdgeSegment, verticalEdgeSegment) - dbg("slicing middle") - local w = horizontalEdgeSegment[2] - horizontalEdgeSegment[1] + 1 - local h = verticalEdgeSegment[2] - verticalEdgeSegment[1] + 1 - local middle = love.image.newImageData(w, h) - middle:paste( - rawdata, - 0, 0, - horizontalEdgeSegment[1], verticalEdgeSegment[1], - w, h - ) - p.patches[2][2] = love.graphics.newImage(middle, {}) - middle:release() - dbg("middle:", p.patches[2][2]:getDimensions()) -end - -local function setEdges(p, rawdata, horizontalEdgeSegment, verticalEdgeSegment) - dbg("slicing edges") - local hlen = horizontalEdgeSegment[2] - horizontalEdgeSegment[1] + 1 - local vlen = verticalEdgeSegment[2] - verticalEdgeSegment[1] + 1 - - local top = love.image.newImageData(hlen, verticalEdgeSegment[1] - 1) - top:paste( - rawdata, - 0, 0, - -- 1 to skip over metadata row - horizontalEdgeSegment[1], 1, - top:getDimensions() - ) - p.patches[1][2] = love.graphics.newImage(top, {}) - top:release() - dbg("top:", p.patches[1][2]:getDimensions()) - - -- -2 because of 2 distinct -1s, see comments in setCorners - local bottom = love.image.newImageData(hlen, rawdata:getHeight() - verticalEdgeSegment[2] - 2) - bottom:paste( - rawdata, - 0, 0, - horizontalEdgeSegment[1], verticalEdgeSegment[2] + 1, - bottom:getDimensions() - ) - p.patches[3][2] = love.graphics.newImage(bottom, {}) - bottom:release() - dbg("bottom:", p.patches[3][2]:getDimensions()) - - local left = love.image.newImageData(horizontalEdgeSegment[1] - 1, vlen) - left:paste( - rawdata, - 0, 0, - 1, verticalEdgeSegment[1], - left:getDimensions() - ) - p.patches[2][1] = love.graphics.newImage(left, {}) - left:release() - dbg("left:", p.patches[2][1]:getDimensions()) - - local right = love.image.newImageData(rawdata:getWidth() - horizontalEdgeSegment[2] - 2, vlen) - right:paste( - rawdata, - 0, 0, - horizontalEdgeSegment[2] + 1, verticalEdgeSegment[1], - right:getDimensions() - ) - p.patches[2][3] = love.graphics.newImage(right, {}) - dbg("right:", p.patches[2][3]:getDimensions()) -end - ----Load a 9-slice image ----@param arg string|love.ImageData filename or raw image data to use for 9-slicing ----@return nil ----@return string -function M.load(arg) - local rawdata - local release = false - local p = {} - - if type(arg) == "string" then - dbg("loading sliced image from:", arg) - - rawdata = love.image.newImageData(arg, {}) - release = true - elseif arg.type and arg:type() == "ImageData" then - rawdata = arg - else - return nil, "expected string or ImageData, got "..tostring(arg) - end - - local horizontalEdgeSegment = { - assert(firstBlackPixel(rawdata, "row", 0, false)), - assert(firstBlackPixel(rawdata, "row", 0, true)) - } - - local verticalEdgeSegment = { - assert(firstBlackPixel(rawdata, "col", 0, false)), - assert(firstBlackPixel(rawdata, "col", 0, true)) - } - - local horizontalContentPadding = { - assert(firstBlackPixel(rawdata, "row", rawdata:getHeight() - 1, false)) - 1, - rawdata:getWidth() - assert(firstBlackPixel(rawdata, "row", rawdata:getHeight() - 1, true)) - 1 - } - - local verticalContentPadding = { - assert(firstBlackPixel(rawdata, "col", rawdata:getWidth() - 1, false)) - 1, - rawdata:getHeight() - assert(firstBlackPixel(rawdata, "col", rawdata:getWidth() - 1, true)) - 1 - } - - -- TODO check for valid value ranges in content padding - - p.contentPadding = { - left = horizontalContentPadding[1], - right = horizontalContentPadding[2], - up = verticalContentPadding[1], - down = verticalContentPadding[2] - } - dbg("padding (u,d,l,r):", p.contentPadding.up, p.contentPadding.down, p.contentPadding.left, p.contentPadding.right) - - p.patches = {{}, {}, {}} - - setCorners(p, rawdata, horizontalEdgeSegment, verticalEdgeSegment) - setMiddle(p, rawdata, horizontalEdgeSegment, verticalEdgeSegment) - setEdges(p, rawdata, horizontalEdgeSegment, verticalEdgeSegment) - - p.x, p.y = 0, 0 - p.width = p.patches[1][1]:getWidth() + p.patches[1][3]:getWidth() - p.height = p.patches[1][1]:getHeight() + p.patches[3][1]:getHeight() - - if release then - rawdata:release() - end - - setmetatable(p, PatchedImageMt) - return p -end - --- THIS REUSES THE IMAGE OBJECTS FROM THE ORIGINAL -function PatchedImage:clone() - local c = {} - - for i = 1, 3 do - c.patches[i] = {} - for j = 1, 3 do - c.patches[i][j] = self.patches[i][j] - end - end - - c.contentPadding = { - left = self.contentPadding.left, - right = self.contentPadding.right, - up = self.contentPadding.up, - down = self.contentPadding.down - } - c.x, c.y = self.x, self.y - c.width, c.height = self.width, self.height - - setmetatable(c, PatchedImageMt) - return c -end - ----Resize image to given dimensions ----@param w number new width ----@param h number new height -function PatchedImage:resize(w, h) - self.width = self:clampWidth(assert(w)) - self.height = self:clampHeight(assert(h)) -end - ----Move image to given position ----@param x number ----@param y number -function PatchedImage:move(x, y) - self.x = assert(x) - self.y = assert(y) -end - -function PatchedImage:getX() - return self.x -end - -function PatchedImage:getY() - return self.y -end - -function PatchedImage:getPosition() - return self.x, self.y -end - -function PatchedImage:getWidth() - return self.width -end - -function PatchedImage:getHeight() - return self.height -end - -function PatchedImage:getDimensions() - return self.width, self.height -end - ----Draws a patch with an optional background debug rect ----@param p love.Image ----@param x number ----@param y number ----@param sx number? ----@param sy number? -local function drawPatch(p, x, y, sx, sy) - sx = sx or 1 - sy = sy or 1 - if debugDraw then - love.graphics.rectangle("fill", x, y, p:getWidth() * sx, p:getHeight() * sy) - end - love.graphics.draw(p, x, y, 0, sx, sy) -end - -function PatchedImage:draw(x, y, w, h) - if x then - self.x = x - else - x = self.x - end - if y then - self.y = y - else - y = self.y - end - if w then - self.width = w - else - w = self.width - end - if h then - self.height = h - else - h = self.height - end - local debugSpacing = debugDraw and DEBUG_DRAW_SEP_WIDTH or 0 - local horizontalEdgeLen = w - self.patches[1][1]:getWidth() - self.patches[1][3]:getWidth() - local verticalEdgeLen = h - self.patches[1][1]:getHeight() - self.patches[3][1]:getHeight() - - horizontalEdgeLen = math.max(horizontalEdgeLen, 0) - verticalEdgeLen = math.max(verticalEdgeLen, 0) - - local horizontalEdgeScale = horizontalEdgeLen / self.patches[1][2]:getWidth() - local verticalEdgeScale = verticalEdgeLen / self.patches[2][1]:getHeight() - - -- middle - drawPatch( - self.patches[2][2], - x + debugSpacing + self.patches[1][1]:getWidth(), - y + debugSpacing + self.patches[1][1]:getHeight(), - horizontalEdgeScale, verticalEdgeScale - ) - - -- edges - -- top - drawPatch( - self.patches[1][2], - x + debugSpacing + self.patches[1][1]:getWidth(), - y, - horizontalEdgeScale, 1 - ) - -- left - drawPatch( - self.patches[2][1], - x, - y + debugSpacing + self.patches[1][1]:getHeight(), - 1, verticalEdgeScale - ) - -- right - drawPatch( - self.patches[2][3], - x + 2*debugSpacing + self.patches[1][1]:getWidth() + horizontalEdgeLen, - y + debugSpacing + self.patches[1][1]:getHeight(), - 1, verticalEdgeScale - ) - -- bottom - drawPatch( - self.patches[3][2], - x + debugSpacing + self.patches[1][1]:getWidth(), - y + 2*debugSpacing + self.patches[1][1]:getHeight() + verticalEdgeLen, - horizontalEdgeScale, 1 - ) - - -- corners - -- top left - drawPatch(self.patches[1][1], x, y) - -- top right - drawPatch( - self.patches[1][3], - x + 2*debugSpacing + self.patches[1][1]:getWidth() + horizontalEdgeLen, - y - ) - -- bottom left - drawPatch( - self.patches[3][1], - x, - y + 2*debugSpacing + self.patches[1][1]:getHeight() + verticalEdgeLen - ) - -- bottom right - drawPatch( - self.patches[3][3], - x + 2*debugSpacing + self.patches[1][1]:getWidth() + horizontalEdgeLen, - y + 2*debugSpacing + self.patches[1][1]:getHeight() + verticalEdgeLen - ) - - if debugDraw then - local rw = 2*debugSpacing - - self.contentPadding.left - self.contentPadding.right - + self.patches[1][1]:getWidth() + horizontalEdgeLen + self.patches[1][3]:getWidth() - local rh = 2*debugSpacing - - self.contentPadding.up - self.contentPadding.down - + self.patches[1][1]:getHeight() + verticalEdgeLen + self.patches[3][1]:getHeight() - - local r, g, b, a = love.graphics.getColor() - local lw = love.graphics.getLineWidth() - love.graphics.setColor(1, 0, 0) - love.graphics.setLineWidth(1) - love.graphics.rectangle( - "line", - x + self.contentPadding.left + 0.5, y + self.contentPadding.up + 0.5, - rw, rh - ) - love.graphics.setColor(r, g, b, a) - love.graphics.setLineWidth(lw) - end -end - -function PatchedImage:getContentWindow(x, y, w, h) - x = x or self.x - y = y or self.y - w = w or self.width - h = h or self.height - - x = x + self.contentPadding.left - y = y + self.contentPadding.up - - w = self:clampWidth(w) - self.contentPadding.left - self.contentPadding.right - h = self:clampHeight(h) - self.contentPadding.up - self.contentPadding.down - - if debugDraw then - w = w + 2*DEBUG_DRAW_SEP_WIDTH - h = h + 2*DEBUG_DRAW_SEP_WIDTH - end - - return x, y, w, h -end - -function PatchedImage:clampWidth(w) - return math.max(w, self.patches[1][1]:getWidth() + self.patches[1][3]:getWidth()) -end - -function PatchedImage:clampHeight(h) - return math.max(h, self.patches[1][1]:getHeight() + self.patches[3][1]:getHeight()) -end - -function M.setDebug(t) - debugDraw = t.draw - debugLog = t.log -end - -function M.isDebugLogging() - return debugLog -end - -function M.isDebugDrawing() - return debugDraw -end - -return M diff --git a/src/lib/sti/graphics.lua b/src/lib/sti/graphics.lua deleted file mode 100644 index 6acf8d6..0000000 --- a/src/lib/sti/graphics.lua +++ /dev/null @@ -1,132 +0,0 @@ -local lg = _G.love.graphics -local graphics = { isCreated = lg and true or false } - -function graphics.newSpriteBatch(...) - if graphics.isCreated then - return lg.newSpriteBatch(...) - end -end - -function graphics.newCanvas(...) - if graphics.isCreated then - return lg.newCanvas(...) - end -end - -function graphics.newImage(...) - if graphics.isCreated then - return lg.newImage(...) - end -end - -function graphics.newQuad(...) - if graphics.isCreated then - return lg.newQuad(...) - end -end - -function graphics.getCanvas(...) - if graphics.isCreated then - return lg.getCanvas(...) - end -end - -function graphics.setCanvas(...) - if graphics.isCreated then - return lg.setCanvas(...) - end -end - -function graphics.clear(...) - if graphics.isCreated then - return lg.clear(...) - end -end - -function graphics.push(...) - if graphics.isCreated then - return lg.push(...) - end -end - -function graphics.origin(...) - if graphics.isCreated then - return lg.origin(...) - end -end - -function graphics.scale(...) - if graphics.isCreated then - return lg.scale(...) - end -end - -function graphics.translate(...) - if graphics.isCreated then - return lg.translate(...) - end -end - -function graphics.pop(...) - if graphics.isCreated then - return lg.pop(...) - end -end - -function graphics.draw(...) - if graphics.isCreated then - return lg.draw(...) - end -end - -function graphics.rectangle(...) - if graphics.isCreated then - return lg.rectangle(...) - end -end - -function graphics.getColor(...) - if graphics.isCreated then - return lg.getColor(...) - end -end - -function graphics.setColor(...) - if graphics.isCreated then - return lg.setColor(...) - end -end - -function graphics.line(...) - if graphics.isCreated then - return lg.line(...) - end -end - -function graphics.polygon(...) - if graphics.isCreated then - return lg.polygon(...) - end -end - -function graphics.points(...) - if graphics.isCreated then - return lg.points(...) - end -end - -function graphics.getWidth() - if graphics.isCreated then - return lg.getWidth() - end - return 0 -end - -function graphics.getHeight() - if graphics.isCreated then - return lg.getHeight() - end - return 0 -end - -return graphics diff --git a/src/lib/sti/init.lua b/src/lib/sti/init.lua deleted file mode 100644 index 8ab07c1..0000000 --- a/src/lib/sti/init.lua +++ /dev/null @@ -1,1631 +0,0 @@ ---- Simple and fast Tiled map loader and renderer. --- @module sti --- @author Landon Manning --- @copyright 2019 --- @license MIT/X11 - -local STI = { - _LICENSE = "MIT/X11", - _URL = "https://github.com/karai17/Simple-Tiled-Implementation", - _VERSION = "1.2.3.0", - _DESCRIPTION = "Simple Tiled Implementation is a Tiled Map Editor library designed for the *awesome* LÖVE framework.", - cache = {} -} -STI.__index = STI - -local love = _G.love -local cwd = (...):gsub('%.init$', '') .. "." -local utils = require(cwd .. "utils") -local ceil = math.ceil -local floor = math.floor -local lg = require(cwd .. "graphics") -local Map = {} -Map.__index = Map - -local DRAW_OBJECT_LAYERS = false - -local function new(map, plugins, ox, oy) - local dir = "" - - if type(map) == "table" then - map = setmetatable(map, Map) - else - -- Check for valid map type - local ext = map:sub(-4, -1) - assert(ext == ".lua", string.format( - "Invalid file type: %s. File must be of type: lua.", - ext - )) - - -- Get directory of map - dir = map:reverse():find("[/\\]") or "" - if dir ~= "" then - dir = map:sub(1, 1 + (#map - dir)) - end - - -- Load map - map = setmetatable(assert(love.filesystem.load(map))(), Map) - - -- Load possibly external tilesets - if map.tilesets then - for i, tileset in ipairs(map.tilesets) do - if tileset.filename then - local filename = utils.format_path(dir .. tileset.filename):gsub("%.tsx$", ".lua") - local external = assert(love.filesystem.load(filename))() - external.image = tileset.filename:gsub("[^/]+$", "") .. external.image - external.filename = filename - external.firstgid = tileset.firstgid - external.name = tileset.name - map.tilesets[i] = external - end - end - end - end - - - map:init(dir, plugins, ox, oy) - - return map -end - ---- Instance a new map. --- @param map Path to the map file or the map table itself --- @param plugins A list of plugins to load --- @param ox Offset of map on the X axis (in pixels) --- @param oy Offset of map on the Y axis (in pixels) --- @return table The loaded Map -function STI.__call(_, map, plugins, ox, oy) - return new(map, plugins, ox, oy) -end - ---- Flush image cache. -function STI:flush() - self.cache = {} -end - ---- Map object - ---- Instance a new map --- @param path Path to the map file --- @param plugins A list of plugins to load --- @param ox Offset of map on the X axis (in pixels) --- @param oy Offset of map on the Y axis (in pixels) -function Map:init(path, plugins, ox, oy) - if type(plugins) == "table" then - self:loadPlugins(plugins) - end - - self:resize() - self.objects = {} - self.tiles = {} - self.tileInstances = {} - self.drawRange = { - sx = 1, - sy = 1, - ex = self.width, - ey = self.height, - } - self.offsetx = ox or 0 - self.offsety = oy or 0 - - self.freeBatchSprites = {} - setmetatable(self.freeBatchSprites, { __mode = 'k' }) - - -- Set tiles, images - local gid = 1 - for i, tileset in ipairs(self.tilesets) do - assert(tileset.image, "STI does not support Tile Collections.\nYou need to create a Texture Atlas.") - - -- Cache images - if lg.isCreated then - local formatted_path = utils.format_path(path .. tileset.image) - - if not STI.cache[formatted_path] then - utils.fix_transparent_color(tileset, formatted_path) - utils.cache_image(STI, formatted_path, tileset.image) - else - tileset.image = STI.cache[formatted_path] - end - end - - gid = self:setTiles(i, tileset, gid) - end - - local layers = {} - for _, layer in ipairs(self.layers) do - self:groupAppendToList(layers, layer) - end - self.layers = layers - - -- Set layers - for _, layer in ipairs(self.layers) do - self:setLayer(layer, path) - end -end - ---- Layers from the group are added to the list --- @param layers List of layers --- @param layer Layer data -function Map:groupAppendToList(layers, layer) - if layer.type == "group" then - for _, groupLayer in pairs(layer.layers) do - groupLayer.name = layer.name .. "." .. groupLayer.name - groupLayer.visible = layer.visible - groupLayer.opacity = layer.opacity * groupLayer.opacity - groupLayer.offsetx = layer.offsetx + groupLayer.offsetx - groupLayer.offsety = layer.offsety + groupLayer.offsety - - for key, property in pairs(layer.properties) do - if groupLayer.properties[key] == nil then - groupLayer.properties[key] = property - end - end - - self:groupAppendToList(layers, groupLayer) - end - else - table.insert(layers, layer) - end -end - ---- Load plugins --- @param plugins A list of plugins to load -function Map:loadPlugins(plugins) - for _, plugin in ipairs(plugins) do - local pluginModulePath = cwd .. 'plugins.' .. plugin - local ok, pluginModule = pcall(require, pluginModulePath) - if ok then - for k, func in pairs(pluginModule) do - if not self[k] then - self[k] = func - end - end - end - end -end - ---- Create Tiles --- @param index Index of the Tileset --- @param tileset Tileset data --- @param gid First Global ID in Tileset --- @return number Next Tileset's first Global ID -function Map:setTiles(index, tileset, gid) - local quad = lg.newQuad - local imageW = tileset.imagewidth - local imageH = tileset.imageheight - local tileW = tileset.tilewidth - local tileH = tileset.tileheight - local margin = tileset.margin - local spacing = tileset.spacing - local w = utils.get_tiles(imageW, tileW, margin, spacing) - local h = utils.get_tiles(imageH, tileH, margin, spacing) - - for y = 1, h do - for x = 1, w do - local id = gid - tileset.firstgid - local quadX = (x - 1) * tileW + margin + (x - 1) * spacing - local quadY = (y - 1) * tileH + margin + (y - 1) * spacing - local type = "" - local properties, terrain, animation, objectGroup - - for _, tile in pairs(tileset.tiles) do - if tile.id == id then - properties = tile.properties - animation = tile.animation - objectGroup = tile.objectGroup - type = tile.type - - if tile.terrain then - terrain = {} - - for i = 1, #tile.terrain do - terrain[i] = tileset.terrains[tile.terrain[i] + 1] - end - end - end - end - - local tile = { - id = id, - gid = gid, - tileset = index, - type = type, - quad = quad( - quadX, quadY, - tileW, tileH, - imageW, imageH - ), - properties = properties or {}, - terrain = terrain, - animation = animation, - objectGroup = objectGroup, - frame = 1, - time = 0, - width = tileW, - height = tileH, - sx = 1, - sy = 1, - r = 0, - offset = tileset.tileoffset, - } - - self.tiles[gid] = tile - gid = gid + 1 - end - end - - return gid -end - ---- Create Layers --- @param layer Layer data --- @param path (Optional) Path to an Image Layer's image -function Map:setLayer(layer, path) - if layer.encoding then - if layer.encoding == "base64" then - assert(require "ffi", "Compressed maps require LuaJIT FFI.\nPlease Switch your interperator to LuaJIT or your Tile Layer Format to \"CSV\".") - local fd = love.data.decode("string", "base64", layer.data) - - if not layer.compression then - layer.data = utils.get_decompressed_data(fd) - else - assert(love.data.decompress, "zlib and gzip compression require LOVE 11.0+.\nPlease set your Tile Layer Format to \"Base64 (uncompressed)\" or \"CSV\".") - - if layer.compression == "zlib" then - local data = love.data.decompress("string", "zlib", fd) - layer.data = utils.get_decompressed_data(data) - end - - if layer.compression == "gzip" then - local data = love.data.decompress("string", "gzip", fd) - layer.data = utils.get_decompressed_data(data) - end - end - end - end - - layer.x = (layer.x or 0) + layer.offsetx + self.offsetx - layer.y = (layer.y or 0) + layer.offsety + self.offsety - layer.update = function() end - - if layer.type == "tilelayer" then - self:setTileData(layer) - self:setSpriteBatches(layer) - layer.draw = function() self:drawTileLayer(layer) end - elseif layer.type == "objectgroup" then - self:setObjectData(layer) - self:setObjectCoordinates(layer) - self:setObjectSpriteBatches(layer) - layer.draw = function() self:drawObjectLayer(layer) end - elseif layer.type == "imagelayer" then - layer.draw = function() self:drawImageLayer(layer) end - - if layer.image ~= "" then - local formatted_path = utils.format_path(path .. layer.image) - if not STI.cache[formatted_path] then - utils.cache_image(STI, formatted_path) - end - - layer.image = STI.cache[formatted_path] - layer.width = layer.image:getWidth() - layer.height = layer.image:getHeight() - end - end - - self.layers[layer.name] = layer -end - ---- Add Tiles to Tile Layer --- @param layer The Tile Layer -function Map:setTileData(layer) - if layer.chunks then - for _, chunk in ipairs(layer.chunks) do - self:setTileData(chunk) - end - return - end - - local i = 1 - local map = {} - - for y = 1, layer.height do - map[y] = {} - for x = 1, layer.width do - local gid = layer.data[i] - - -- NOTE: Empty tiles have a GID of 0 - if gid > 0 then - map[y][x] = self.tiles[gid] or self:setFlippedGID(gid) - end - - i = i + 1 - end - end - - layer.data = map -end - ---- Add Objects to Layer --- @param layer The Object Layer -function Map:setObjectData(layer) - for _, object in ipairs(layer.objects) do - object.layer = layer - self.objects[object.id] = object - end -end - ---- Correct position and orientation of Objects in an Object Layer --- @param layer The Object Layer -function Map:setObjectCoordinates(layer) - for _, object in ipairs(layer.objects) do - local x = layer.x + object.x - local y = layer.y + object.y - local w = object.width - local h = object.height - local cos = math.cos(math.rad(object.rotation)) - local sin = math.sin(math.rad(object.rotation)) - - if object.shape == "rectangle" and not object.gid then - object.rectangle = {} - - local vertices = { - { x=x, y=y }, - { x=x + w, y=y }, - { x=x + w, y=y + h }, - { x=x, y=y + h }, - } - - for _, vertex in ipairs(vertices) do - vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin) - table.insert(object.rectangle, { x = vertex.x, y = vertex.y }) - end - elseif object.shape == "ellipse" then - object.ellipse = {} - local vertices = utils.convert_ellipse_to_polygon(x, y, w, h) - - for _, vertex in ipairs(vertices) do - vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin) - table.insert(object.ellipse, { x = vertex.x, y = vertex.y }) - end - elseif object.shape == "polygon" then - for _, vertex in ipairs(object.polygon) do - vertex.x = vertex.x + x - vertex.y = vertex.y + y - vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin) - end - elseif object.shape == "polyline" then - for _, vertex in ipairs(object.polyline) do - vertex.x = vertex.x + x - vertex.y = vertex.y + y - vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin) - end - end - end -end - ---- Convert tile location to tile instance location --- @param layer Tile layer --- @param tile Tile --- @param x Tile location on X axis (in tiles) --- @param y Tile location on Y axis (in tiles) --- @return number Tile instance location on X axis (in pixels) --- @return number Tile instance location on Y axis (in pixels) -function Map:getLayerTilePosition(layer, tile, x, y) - local tileW = self.tilewidth - local tileH = self.tileheight - local tileX, tileY - - if self.orientation == "orthogonal" then - local tileset = self.tilesets[tile.tileset] - tileX = (x - 1) * tileW + tile.offset.x - tileY = (y - 0) * tileH + tile.offset.y - tileset.tileheight - tileX, tileY = utils.compensate(tile, tileX, tileY, tileW, tileH) - elseif self.orientation == "isometric" then - tileX = (x - y) * (tileW / 2) + tile.offset.x + layer.width * tileW / 2 - self.tilewidth / 2 - tileY = (x + y - 2) * (tileH / 2) + tile.offset.y - else - local sideLen = self.hexsidelength or 0 - if self.staggeraxis == "y" then - if self.staggerindex == "odd" then - if y % 2 == 0 then - tileX = (x - 1) * tileW + tileW / 2 + tile.offset.x - else - tileX = (x - 1) * tileW + tile.offset.x - end - else - if y % 2 == 0 then - tileX = (x - 1) * tileW + tile.offset.x - else - tileX = (x - 1) * tileW + tileW / 2 + tile.offset.x - end - end - - local rowH = tileH - (tileH - sideLen) / 2 - tileY = (y - 1) * rowH + tile.offset.y - else - if self.staggerindex == "odd" then - if x % 2 == 0 then - tileY = (y - 1) * tileH + tileH / 2 + tile.offset.y - else - tileY = (y - 1) * tileH + tile.offset.y - end - else - if x % 2 == 0 then - tileY = (y - 1) * tileH + tile.offset.y - else - tileY = (y - 1) * tileH + tileH / 2 + tile.offset.y - end - end - - local colW = tileW - (tileW - sideLen) / 2 - tileX = (x - 1) * colW + tile.offset.x - end - end - - return tileX, tileY -end - ---- Place new tile instance --- @param layer Tile layer --- @param chunk Layer chunk --- @param tile Tile --- @param number Tile location on X axis (in tiles) --- @param number Tile location on Y axis (in tiles) -function Map:addNewLayerTile(layer, chunk, tile, x, y) - local tileset = tile.tileset - local image = self.tilesets[tile.tileset].image - local batches - local size - - if chunk then - batches = chunk.batches - size = chunk.width * chunk.height - else - batches = layer.batches - size = layer.width * layer.height - end - - batches[tileset] = batches[tileset] or lg.newSpriteBatch(image, size) - - local batch = batches[tileset] - local tileX, tileY = self:getLayerTilePosition(layer, tile, x, y) - - local instance = { - layer = layer, - chunk = chunk, - gid = tile.gid, - x = tileX, - y = tileY, - r = tile.r, - oy = 0 - } - - -- NOTE: STI can run headless so it is not guaranteed that a batch exists. - if batch then - instance.batch = batch - instance.id = batch:add(tile.quad, tileX, tileY, tile.r, tile.sx, tile.sy) - end - - self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {} - table.insert(self.tileInstances[tile.gid], instance) -end - -function Map:set_batches(layer, chunk) - if chunk then - chunk.batches = {} - else - layer.batches = {} - end - - if self.orientation == "orthogonal" or self.orientation == "isometric" then - local offsetX = chunk and chunk.x or 0 - local offsetY = chunk and chunk.y or 0 - - local startX = 1 - local startY = 1 - local endX = chunk and chunk.width or layer.width - local endY = chunk and chunk.height or layer.height - local incrementX = 1 - local incrementY = 1 - - -- Determine order to add tiles to sprite batch - -- Defaults to right-down - if self.renderorder == "right-up" then - startY, endY, incrementY = endY, startY, -1 - elseif self.renderorder == "left-down" then - startX, endX, incrementX = endX, startX, -1 - elseif self.renderorder == "left-up" then - startX, endX, incrementX = endX, startX, -1 - startY, endY, incrementY = endY, startY, -1 - end - - for y = startY, endY, incrementY do - for x = startX, endX, incrementX do - -- NOTE: Cannot short circuit this since it is valid for tile to be assigned nil - local tile - if chunk then - tile = chunk.data[y][x] - else - tile = layer.data[y][x] - end - - if tile then - self:addNewLayerTile(layer, chunk, tile, x + offsetX, y + offsetY) - end - end - end - else - if self.staggeraxis == "y" then - for y = 1, (chunk and chunk.height or layer.height) do - for x = 1, (chunk and chunk.width or layer.width) do - -- NOTE: Cannot short circuit this since it is valid for tile to be assigned nil - local tile - if chunk then - tile = chunk.data[y][x] - else - tile = layer.data[y][x] - end - - if tile then - self:addNewLayerTile(layer, chunk, tile, x, y) - end - end - end - else - local i = 0 - local _x - - if self.staggerindex == "odd" then - _x = 1 - else - _x = 2 - end - - while i < (chunk and chunk.width * chunk.height or layer.width * layer.height) do - for _y = 1, (chunk and chunk.height or layer.height) + 0.5, 0.5 do - local y = floor(_y) - - for x = _x, (chunk and chunk.width or layer.width), 2 do - i = i + 1 - - -- NOTE: Cannot short circuit this since it is valid for tile to be assigned nil - local tile - if chunk then - tile = chunk.data[y][x] - else - tile = layer.data[y][x] - end - - if tile then - self:addNewLayerTile(layer, chunk, tile, x, y) - end - end - - if _x == 1 then - _x = 2 - else - _x = 1 - end - end - end - end - end -end - ---- Batch Tiles in Tile Layer for improved draw speed --- @param layer The Tile Layer -function Map:setSpriteBatches(layer) - if layer.chunks then - for _, chunk in ipairs(layer.chunks) do - self:set_batches(layer, chunk) - end - return - end - - self:set_batches(layer) -end - ---- Batch Tiles in Object Layer for improved draw speed --- @param layer The Object Layer -function Map:setObjectSpriteBatches(layer) - local newBatch = lg.newSpriteBatch - local batches = {} - - if layer.draworder == "topdown" then - table.sort(layer.objects, function(a, b) - return a.y + a.height < b.y + b.height - end) - end - - for _, object in ipairs(layer.objects) do - if object.gid then - local tile = self.tiles[object.gid] or self:setFlippedGID(object.gid) - local tileset = tile.tileset - local image = self.tilesets[tileset].image - - batches[tileset] = batches[tileset] or newBatch(image) - - local sx = object.width / tile.width - local sy = object.height / tile.height - - -- Tiled rotates around bottom left corner, where love2D rotates around top left corner - local ox = 0 - local oy = tile.height - - local batch = batches[tileset] - local tileX = object.x + tile.offset.x - local tileY = object.y + tile.offset.y - local tileR = math.rad(object.rotation) - - -- Compensation for scale/rotation shift - if tile.sx == -1 then - tileX = tileX + object.width - - if tileR ~= 0 then - tileX = tileX - object.width - ox = ox + tile.width - end - end - - if tile.sy == -1 then - tileY = tileY - object.height - - if tileR ~= 0 then - tileY = tileY + object.width - oy = oy - tile.width - end - end - - local instance = { - id = batch:add(tile.quad, tileX, tileY, tileR, tile.sx * sx, tile.sy * sy, ox, oy), - batch = batch, - layer = layer, - gid = tile.gid, - x = tileX, - y = tileY - oy, - r = tileR, - oy = oy - } - - self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {} - table.insert(self.tileInstances[tile.gid], instance) - end - end - - layer.batches = batches -end - ---- Create a Custom Layer to place userdata in (such as player sprites) --- @param name Name of Custom Layer --- @param index Draw order within Layer stack --- @return table Custom Layer -function Map:addCustomLayer(name, index) - index = index or #self.layers + 1 - local layer = { - type = "customlayer", - name = name, - visible = true, - opacity = 1, - properties = {}, - } - - function layer.draw() end - function layer.update() end - - table.insert(self.layers, index, layer) - self.layers[name] = self.layers[index] - - return layer -end - ---- Convert another Layer into a Custom Layer --- @param index Index or name of Layer to convert --- @return table Custom Layer -function Map:convertToCustomLayer(index) - local layer = assert(self.layers[index], "Layer not found: " .. index) - - layer.type = "customlayer" - layer.x = nil - layer.y = nil - layer.width = nil - layer.height = nil - layer.encoding = nil - layer.data = nil - layer.chunks = nil - layer.objects = nil - layer.image = nil - - function layer.draw() end - function layer.update() end - - return layer -end - ---- Remove a Layer from the Layer stack --- @param index Index or name of Layer to remove -function Map:removeLayer(index) - local layer = assert(self.layers[index], "Layer not found: " .. index) - - if type(index) == "string" then - for i, l in ipairs(self.layers) do - if l.name == index then - table.remove(self.layers, i) - self.layers[index] = nil - break - end - end - else - local name = self.layers[index].name - table.remove(self.layers, index) - self.layers[name] = nil - end - - -- Remove layer batches - if layer.batches then - for _, batch in pairs(layer.batches) do - self.freeBatchSprites[batch] = nil - end - end - - -- Remove chunk batches - if layer.chunks then - for _, chunk in ipairs(layer.chunks) do - for _, batch in pairs(chunk.batches) do - self.freeBatchSprites[batch] = nil - end - end - end - - -- Remove tile instances - if layer.type == "tilelayer" then - for _, tiles in pairs(self.tileInstances) do - for i = #tiles, 1, -1 do - local tile = tiles[i] - if tile.layer == layer then - table.remove(tiles, i) - end - end - end - end - - -- Remove objects - if layer.objects then - for i, object in pairs(self.objects) do - if object.layer == layer then - self.objects[i] = nil - end - end - end -end - ---- Animate Tiles and update every Layer --- @param dt Delta Time -function Map:update(dt) - for _, tile in pairs(self.tiles) do - local update = false - - if tile.animation then - tile.time = tile.time + dt * 1000 - - while tile.time > tonumber(tile.animation[tile.frame].duration) do - update = true - tile.time = tile.time - tonumber(tile.animation[tile.frame].duration) - tile.frame = tile.frame + 1 - - if tile.frame > #tile.animation then tile.frame = 1 end - end - - if update and self.tileInstances[tile.gid] then - for _, j in pairs(self.tileInstances[tile.gid]) do - local t = self.tiles[tonumber(tile.animation[tile.frame].tileid) + self.tilesets[tile.tileset].firstgid] - j.batch:set(j.id, t.quad, j.x, j.y, j.r, tile.sx, tile.sy, 0, j.oy) - end - end - end - end - - for _, layer in ipairs(self.layers) do - layer:update(dt) - end -end - ---- Draw every Layer --- @param tx Translate on X --- @param ty Translate on Y --- @param sx Scale on X --- @param sy Scale on Y -function Map:draw(tx, ty, sx, sy) - local current_canvas = lg.getCanvas() - lg.setCanvas(self.canvas) - lg.clear() - - -- Scale map to 1.0 to draw onto canvas, this fixes tearing issues - -- Map is translated to correct position so the right section is drawn - lg.push() - lg.origin() - for _, layer in ipairs(self.layers) do - if layer.visible and layer.opacity > 0 then - self:drawLayer(layer) - end - end - lg.pop() - - lg.push() - lg.translate(math.floor(tx or 0), math.floor(ty or 0)) - lg.scale(sx or 1, sy or sx or 1) - - lg.setCanvas(current_canvas) - lg.draw(self.canvas) - - lg.pop() -end - ---- Draw an individual Layer --- @param layer The Layer to draw -function Map.drawLayer(_, layer) - local r,g,b,a = lg.getColor() - lg.setColor(r, g, b, a * layer.opacity) - layer:draw() - lg.setColor(r,g,b,a) -end - ---- Default draw function for Tile Layers --- @param layer The Tile Layer to draw -function Map:drawTileLayer(layer) - if type(layer) == "string" or type(layer) == "number" then - layer = self.layers[layer] - end - - assert(layer.type == "tilelayer", "Invalid layer type: " .. layer.type .. ". Layer must be of type: tilelayer") - - -- NOTE: This does not take into account any sort of draw range clipping and will always draw every chunk - if layer.chunks then - for _, chunk in ipairs(layer.chunks) do - for _, batch in pairs(chunk.batches) do - lg.draw(batch, 0, 0) - end - end - - return - end - - for _, batch in pairs(layer.batches) do - lg.draw(batch, floor(layer.x), floor(layer.y)) - end -end - ---- Default draw function for Object Layers --- @param layer The Object Layer to draw -function Map:drawObjectLayer(layer) - if not DRAW_OBJECT_LAYERS then return end - - if type(layer) == "string" or type(layer) == "number" then - layer = self.layers[layer] - end - - assert(layer.type == "objectgroup", "Invalid layer type: " .. layer.type .. ". Layer must be of type: objectgroup") - - local line = { 160, 160, 160, 255 * layer.opacity } - local fill = { 160, 160, 160, 255 * layer.opacity * 0.5 } - local r,g,b,a = lg.getColor() - local reset = { r, g, b, a * layer.opacity } - - local function sortVertices(obj) - local vertex = {} - - for _, v in ipairs(obj) do - table.insert(vertex, v.x) - table.insert(vertex, v.y) - end - - return vertex - end - - local function drawShape(obj, shape) - local vertex = sortVertices(obj) - - if shape == "polyline" then - lg.setColor(line) - lg.line(vertex) - return - elseif shape == "polygon" then - lg.setColor(fill) - if not love.math.isConvex(vertex) then - local triangles = love.math.triangulate(vertex) - for _, triangle in ipairs(triangles) do - lg.polygon("fill", triangle) - end - else - lg.polygon("fill", vertex) - end - else - lg.setColor(fill) - lg.polygon("fill", vertex) - end - - lg.setColor(line) - lg.polygon("line", vertex) - end - - for _, object in ipairs(layer.objects) do - if object.visible then - if object.shape == "rectangle" and not object.gid then - drawShape(object.rectangle, "rectangle") - elseif object.shape == "ellipse" then - drawShape(object.ellipse, "ellipse") - elseif object.shape == "polygon" then - drawShape(object.polygon, "polygon") - elseif object.shape == "polyline" then - drawShape(object.polyline, "polyline") - elseif object.shape == "point" then - lg.points(object.x, object.y) - end - end - end - - lg.setColor(reset) - for _, batch in pairs(layer.batches) do - lg.draw(batch, 0, 0) - end - lg.setColor(r,g,b,a) -end - ---- Default draw function for Image Layers --- @param layer The Image Layer to draw -function Map:drawImageLayer(layer) - if type(layer) == "string" or type(layer) == "number" then - layer = self.layers[layer] - end - - assert(layer.type == "imagelayer", "Invalid layer type: " .. layer.type .. ". Layer must be of type: imagelayer") - - if layer.image ~= "" then - lg.draw(layer.image, layer.x, layer.y) - end -end - ---- Resize the drawable area of the Map --- @param w The new width of the drawable area (in pixels) --- @param h The new Height of the drawable area (in pixels) -function Map:resize(w, h) - if lg.isCreated then - w = w or lg.getWidth() - h = h or lg.getHeight() - - self.canvas = lg.newCanvas(w, h) - self.canvas:setFilter("nearest", "nearest") - end -end - ---- Create flipped or rotated Tiles based on bitop flags --- @param gid The flagged Global ID --- @return table Flipped Tile -function Map:setFlippedGID(gid) - local bit31 = 2147483648 - local bit30 = 1073741824 - local bit29 = 536870912 - local flipX = false - local flipY = false - local flipD = false - local realgid = gid - - if realgid >= bit31 then - realgid = realgid - bit31 - flipX = not flipX - end - - if realgid >= bit30 then - realgid = realgid - bit30 - flipY = not flipY - end - - if realgid >= bit29 then - realgid = realgid - bit29 - flipD = not flipD - end - - local tile = self.tiles[realgid] - local data = { - id = tile.id, - gid = gid, - tileset = tile.tileset, - frame = tile.frame, - time = tile.time, - width = tile.width, - height = tile.height, - offset = tile.offset, - quad = tile.quad, - properties = tile.properties, - terrain = tile.terrain, - animation = tile.animation, - sx = tile.sx, - sy = tile.sy, - r = tile.r, - } - - if flipX then - if flipY and flipD then - data.r = math.rad(-90) - data.sy = -1 - elseif flipY then - data.sx = -1 - data.sy = -1 - elseif flipD then - data.r = math.rad(90) - else - data.sx = -1 - end - elseif flipY then - if flipD then - data.r = math.rad(-90) - else - data.sy = -1 - end - elseif flipD then - data.r = math.rad(90) - data.sy = -1 - end - - self.tiles[gid] = data - - return self.tiles[gid] -end - ---- Get custom properties from Layer --- @param layer The Layer --- @return table List of properties -function Map:getLayerProperties(layer) - local l = self.layers[layer] - - if not l then - return {} - end - - return l.properties -end - ---- Get custom properties from Tile --- @param layer The Layer that the Tile belongs to --- @param x The X axis location of the Tile (in tiles) --- @param y The Y axis location of the Tile (in tiles) --- @return table List of properties -function Map:getTileProperties(layer, x, y) - local tile = self.layers[layer].data[y][x] - - if not tile then - return {} - end - - return tile.properties -end - ---- Get custom properties from Object --- @param layer The Layer that the Object belongs to --- @param object The index or name of the Object --- @return table List of properties -function Map:getObjectProperties(layer, object) - local o = self.layers[layer].objects - - if type(object) == "number" then - o = o[object] - else - for _, v in ipairs(o) do - if v.name == object then - o = v - break - end - end - end - - if not o then - return {} - end - - return o.properties -end - ---- Change a tile in a layer to another tile --- @param layer The Layer that the Tile belongs to --- @param x The X axis location of the Tile (in tiles) --- @param y The Y axis location of the Tile (in tiles) --- @param gid The gid of the new tile -function Map:setLayerTile(layer, x, y, gid) - layer = self.layers[layer] - - layer.data[y] = layer.data[y] or {} - local tile = layer.data[y][x] - local instance - if tile then - local tileX, tileY = self:getLayerTilePosition(layer, tile, x, y) - for _, inst in pairs(self.tileInstances[tile.gid]) do - if inst.x == tileX and inst.y == tileY then - instance = inst - break - end - end - end - - if tile == self.tiles[gid] then - return - end - - tile = self.tiles[gid] - - if instance then - self:swapTile(instance, tile) - else - self:addNewLayerTile(layer, tile, x, y) - end - layer.data[y][x] = tile -end - ---- Swap a tile in a spritebatch --- @param instance The current Instance object we want to replace --- @param tile The Tile object we want to use --- @return none -function Map:swapTile(instance, tile) - -- Update sprite batch - if instance.batch then - if tile then - instance.batch:set( - instance.id, - tile.quad, - instance.x, - instance.y, - tile.r, - tile.sx, - tile.sy - ) - else - instance.batch:set( - instance.id, - instance.x, - instance.y, - 0, - 0) - - self.freeBatchSprites[instance.batch] = self.freeBatchSprites[instance.batch] or {} - table.insert(self.freeBatchSprites[instance.batch], instance) - end - end - - -- Remove old tile instance - for i, ins in ipairs(self.tileInstances[instance.gid]) do - if ins.batch == instance.batch and ins.id == instance.id then - table.remove(self.tileInstances[instance.gid], i) - break - end - end - - -- Add new tile instance - if tile then - self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {} - - local freeBatchSprites = self.freeBatchSprites[instance.batch] - local newInstance - if freeBatchSprites and #freeBatchSprites > 0 then - newInstance = freeBatchSprites[#freeBatchSprites] - freeBatchSprites[#freeBatchSprites] = nil - else - newInstance = {} - end - - newInstance.layer = instance.layer - newInstance.batch = instance.batch - newInstance.id = instance.id - newInstance.gid = tile.gid or 0 - newInstance.x = instance.x - newInstance.y = instance.y - newInstance.r = tile.r or 0 - newInstance.oy = tile.r ~= 0 and tile.height or 0 - table.insert(self.tileInstances[tile.gid], newInstance) - end -end - ---- Convert tile location to pixel location --- @param x The X axis location of the point (in tiles) --- @param y The Y axis location of the point (in tiles) --- @return number The X axis location of the point (in pixels) --- @return number The Y axis location of the point (in pixels) -function Map:convertTileToPixel(x,y) - if self.orientation == "orthogonal" then - local tileW = self.tilewidth - local tileH = self.tileheight - return - x * tileW, - y * tileH - elseif self.orientation == "isometric" then - local mapH = self.height - local tileW = self.tilewidth - local tileH = self.tileheight - local offsetX = mapH * tileW / 2 - return - (x - y) * tileW / 2 + offsetX, - (x + y) * tileH / 2 - elseif self.orientation == "staggered" or - self.orientation == "hexagonal" then - local tileW = self.tilewidth - local tileH = self.tileheight - local sideLen = self.hexsidelength or 0 - - if self.staggeraxis == "x" then - return - x * tileW, - ceil(y) * (tileH + sideLen) + (ceil(y) % 2 == 0 and tileH or 0) - else - return - ceil(x) * (tileW + sideLen) + (ceil(x) % 2 == 0 and tileW or 0), - y * tileH - end - end -end - ---- Convert pixel location to tile location --- @param x The X axis location of the point (in pixels) --- @param y The Y axis location of the point (in pixels) --- @return number The X axis location of the point (in tiles) --- @return number The Y axis location of the point (in tiles) -function Map:convertPixelToTile(x, y) - if self.orientation == "orthogonal" then - local tileW = self.tilewidth - local tileH = self.tileheight - return - x / tileW, - y / tileH - elseif self.orientation == "isometric" then - local mapH = self.height - local tileW = self.tilewidth - local tileH = self.tileheight - local offsetX = mapH * tileW / 2 - return - y / tileH + (x - offsetX) / tileW, - y / tileH - (x - offsetX) / tileW - elseif self.orientation == "staggered" then - local staggerX = self.staggeraxis == "x" - local even = self.staggerindex == "even" - - local function topLeft(x, y) - if staggerX then - if ceil(x) % 2 == 1 and even then - return x - 1, y - else - return x - 1, y - 1 - end - else - if ceil(y) % 2 == 1 and even then - return x, y - 1 - else - return x - 1, y - 1 - end - end - end - - local function topRight(x, y) - if staggerX then - if ceil(x) % 2 == 1 and even then - return x + 1, y - else - return x + 1, y - 1 - end - else - if ceil(y) % 2 == 1 and even then - return x + 1, y - 1 - else - return x, y - 1 - end - end - end - - local function bottomLeft(x, y) - if staggerX then - if ceil(x) % 2 == 1 and even then - return x - 1, y + 1 - else - return x - 1, y - end - else - if ceil(y) % 2 == 1 and even then - return x, y + 1 - else - return x - 1, y + 1 - end - end - end - - local function bottomRight(x, y) - if staggerX then - if ceil(x) % 2 == 1 and even then - return x + 1, y + 1 - else - return x + 1, y - end - else - if ceil(y) % 2 == 1 and even then - return x + 1, y + 1 - else - return x, y + 1 - end - end - end - - local tileW = self.tilewidth - local tileH = self.tileheight - - if staggerX then - x = x - (even and tileW / 2 or 0) - else - y = y - (even and tileH / 2 or 0) - end - - local halfH = tileH / 2 - local ratio = tileH / tileW - local referenceX = ceil(x / tileW) - local referenceY = ceil(y / tileH) - local relativeX = x - referenceX * tileW - local relativeY = y - referenceY * tileH - - if (halfH - relativeX * ratio > relativeY) then - return topLeft(referenceX, referenceY) - elseif (-halfH + relativeX * ratio > relativeY) then - return topRight(referenceX, referenceY) - elseif (halfH + relativeX * ratio < relativeY) then - return bottomLeft(referenceX, referenceY) - elseif (halfH * 3 - relativeX * ratio < relativeY) then - return bottomRight(referenceX, referenceY) - end - - return referenceX, referenceY - elseif self.orientation == "hexagonal" then - local staggerX = self.staggeraxis == "x" - local even = self.staggerindex == "even" - local tileW = self.tilewidth - local tileH = self.tileheight - local sideLenX = 0 - local sideLenY = 0 - - local colW = tileW / 2 - local rowH = tileH / 2 - if staggerX then - sideLenX = self.hexsidelength - x = x - (even and tileW or (tileW - sideLenX) / 2) - colW = colW - (colW - sideLenX / 2) / 2 - else - sideLenY = self.hexsidelength - y = y - (even and tileH or (tileH - sideLenY) / 2) - rowH = rowH - (rowH - sideLenY / 2) / 2 - end - - local referenceX = ceil(x) / (colW * 2) - local referenceY = ceil(y) / (rowH * 2) - - -- If in staggered line, then shift reference by 0.5 of other axes - if staggerX then - if (floor(referenceX) % 2 == 0) == even then - referenceY = referenceY - 0.5 - end - else - if (floor(referenceY) % 2 == 0) == even then - referenceX = referenceX - 0.5 - end - end - - local relativeX = x - referenceX * colW * 2 - local relativeY = y - referenceY * rowH * 2 - local centers - - if staggerX then - local left = sideLenX / 2 - local centerX = left + colW - local centerY = tileH / 2 - - centers = { - { x = left, y = centerY }, - { x = centerX, y = centerY - rowH }, - { x = centerX, y = centerY + rowH }, - { x = centerX + colW, y = centerY }, - } - else - local top = sideLenY / 2 - local centerX = tileW / 2 - local centerY = top + rowH - - centers = { - { x = centerX, y = top }, - { x = centerX - colW, y = centerY }, - { x = centerX + colW, y = centerY }, - { x = centerX, y = centerY + rowH } - } - end - - local nearest = 0 - local minDist = math.huge - - local function len2(ax, ay) - return ax * ax + ay * ay - end - - for i = 1, 4 do - local dc = len2(centers[i].x - relativeX, centers[i].y - relativeY) - - if dc < minDist then - minDist = dc - nearest = i - end - end - - local offsetsStaggerX = { - { x = 1, y = 1 }, - { x = 2, y = 0 }, - { x = 2, y = 1 }, - { x = 3, y = 1 }, - } - - local offsetsStaggerY = { - { x = 1, y = 1 }, - { x = 0, y = 2 }, - { x = 1, y = 2 }, - { x = 1, y = 3 }, - } - - local offsets = staggerX and offsetsStaggerX or offsetsStaggerY - - return - referenceX + offsets[nearest].x, - referenceY + offsets[nearest].y - end -end - ---- A list of individual layers indexed both by draw order and name --- @table Map.layers --- @see TileLayer --- @see ObjectLayer --- @see ImageLayer --- @see CustomLayer - ---- A list of individual tiles indexed by Global ID --- @table Map.tiles --- @see Tile --- @see Map.tileInstances - ---- A list of tile instances indexed by Global ID --- @table Map.tileInstances --- @see TileInstance --- @see Tile --- @see Map.tiles - ---- A list of no-longer-used batch sprites, indexed by batch ---@table Map.freeBatchSprites - ---- A list of individual objects indexed by Global ID --- @table Map.objects --- @see Object - ---- @table TileLayer --- @field name The name of the layer --- @field x Position on the X axis (in pixels) --- @field y Position on the Y axis (in pixels) --- @field width Width of layer (in tiles) --- @field height Height of layer (in tiles) --- @field visible Toggle if layer is visible or hidden --- @field opacity Opacity of layer --- @field properties Custom properties --- @field data A tileWo dimensional table filled with individual tiles indexed by [y][x] (in tiles) --- @field update Update function --- @field draw Draw function --- @see Map.layers --- @see Tile - ---- @table ObjectLayer --- @field name The name of the layer --- @field x Position on the X axis (in pixels) --- @field y Position on the Y axis (in pixels) --- @field visible Toggle if layer is visible or hidden --- @field opacity Opacity of layer --- @field properties Custom properties --- @field objects List of objects indexed by draw order --- @field update Update function --- @field draw Draw function --- @see Map.layers --- @see Object - ---- @table ImageLayer --- @field name The name of the layer --- @field x Position on the X axis (in pixels) --- @field y Position on the Y axis (in pixels) --- @field visible Toggle if layer is visible or hidden --- @field opacity Opacity of layer --- @field properties Custom properties --- @field image Image to be drawn --- @field update Update function --- @field draw Draw function --- @see Map.layers - ---- Custom Layers are used to place userdata such as sprites within the draw order of the map. --- @table CustomLayer --- @field name The name of the layer --- @field x Position on the X axis (in pixels) --- @field y Position on the Y axis (in pixels) --- @field visible Toggle if layer is visible or hidden --- @field opacity Opacity of layer --- @field properties Custom properties --- @field update Update function --- @field draw Draw function --- @see Map.layers --- @usage --- -- Create a Custom Layer --- local spriteLayer = map:addCustomLayer("Sprite Layer", 3) --- --- -- Add data to Custom Layer --- spriteLayer.sprites = { --- player = { --- image = lg.newImage("assets/sprites/player.png"), --- x = 64, --- y = 64, --- r = 0, --- } --- } --- --- -- Update callback for Custom Layer --- function spriteLayer:update(dt) --- for _, sprite in pairs(self.sprites) do --- sprite.r = sprite.r + math.rad(90 * dt) --- end --- end --- --- -- Draw callback for Custom Layer --- function spriteLayer:draw() --- for _, sprite in pairs(self.sprites) do --- local x = math.floor(sprite.x) --- local y = math.floor(sprite.y) --- local r = sprite.r --- lg.draw(sprite.image, x, y, r) --- end --- end - ---- @table Tile --- @field id Local ID within Tileset --- @field gid Global ID --- @field tileset Tileset ID --- @field quad Quad object --- @field properties Custom properties --- @field terrain Terrain data --- @field animation Animation data --- @field frame Current animation frame --- @field time Time spent on current animation frame --- @field width Width of tile --- @field height Height of tile --- @field sx Scale value on the X axis --- @field sy Scale value on the Y axis --- @field r Rotation of tile (in radians) --- @field offset Offset drawing position --- @field offset.x Offset value on the X axis --- @field offset.y Offset value on the Y axis --- @see Map.tiles - ---- @table TileInstance --- @field batch Spritebatch the Tile Instance belongs to --- @field id ID within the spritebatch --- @field gid Global ID --- @field x Position on the X axis (in pixels) --- @field y Position on the Y axis (in pixels) --- @see Map.tileInstances --- @see Tile - ---- @table Object --- @field id Global ID --- @field name Name of object (non-unique) --- @field shape Shape of object --- @field x Position of object on X axis (in pixels) --- @field y Position of object on Y axis (in pixels) --- @field width Width of object (in pixels) --- @field height Heigh tof object (in pixels) --- @field rotation Rotation of object (in radians) --- @field visible Toggle if object is visible or hidden --- @field properties Custom properties --- @field ellipse List of verticies of specific shape --- @field rectangle List of verticies of specific shape --- @field polygon List of verticies of specific shape --- @field polyline List of verticies of specific shape --- @see Map.objects - -return setmetatable({}, STI) diff --git a/src/lib/sti/plugins/box2d.lua b/src/lib/sti/plugins/box2d.lua deleted file mode 100644 index c6d4148..0000000 --- a/src/lib/sti/plugins/box2d.lua +++ /dev/null @@ -1,323 +0,0 @@ ---- Box2D plugin for STI --- @module box2d --- @author Landon Manning --- @copyright 2019 --- @license MIT/X11 - -local love = _G.love -local utils = require((...):gsub('plugins.box2d', 'utils')) -local lg = require((...):gsub('plugins.box2d', 'graphics')) - -return { - box2d_LICENSE = "MIT/X11", - box2d_URL = "https://github.com/karai17/Simple-Tiled-Implementation", - box2d_VERSION = "2.3.2.7", - box2d_DESCRIPTION = "Box2D hooks for STI.", - - --- Initialize Box2D physics world. - -- @param world The Box2D world to add objects to. - box2d_init = function(map, world) - assert(love.physics, "To use the Box2D plugin, please enable the love.physics module.") - - local body = love.physics.newBody(world, map.offsetx, map.offsety) - local collision = { - body = body, - } - - local function addObjectToWorld(objshape, vertices, userdata, object) - local shape - - if objshape == "polyline" then - if #vertices == 4 then - shape = love.physics.newEdgeShape(unpack(vertices)) - else - shape = love.physics.newChainShape(false, unpack(vertices)) - end - else - shape = love.physics.newPolygonShape(unpack(vertices)) - end - - local currentBody = body - --dynamic are objects/players etc. - if userdata.properties.dynamic == true then - currentBody = love.physics.newBody(world, map.offsetx, map.offsety, 'dynamic') - -- static means it shouldn't move. Things like walls/ground. - elseif userdata.properties.static == true then - currentBody = love.physics.newBody(world, map.offsetx, map.offsety, 'static') - -- kinematic means that the object is static in the game world but effects other bodies - elseif userdata.properties.kinematic == true then - currentBody = love.physics.newBody(world, map.offsetx, map.offsety, 'kinematic') - end - - local fixture = love.physics.newFixture(currentBody, shape) - fixture:setUserData(userdata) - - -- Set some custom properties from userdata (or use default set by box2d) - fixture:setFriction(userdata.properties.friction or 0.2) - fixture:setRestitution(userdata.properties.restitution or 0.0) - fixture:setSensor(userdata.properties.sensor or false) - fixture:setFilterData( - userdata.properties.categories or 1, - userdata.properties.mask or 65535, - userdata.properties.group or 0 - ) - - local obj = { - object = object, - body = currentBody, - shape = shape, - fixture = fixture, - } - - table.insert(collision, obj) - end - - local function getPolygonVertices(object) - local vertices = {} - for _, vertex in ipairs(object.polygon) do - table.insert(vertices, vertex.x) - table.insert(vertices, vertex.y) - end - - return vertices - end - - local function calculateObjectPosition(object, tile) - local o = { - shape = object.shape, - x = (object.dx or object.x) + map.offsetx, - y = (object.dy or object.y) + map.offsety, - w = object.width, - h = object.height, - polygon = object.polygon or object.polyline or object.ellipse or object.rectangle - } - - local userdata = { - object = o, - properties = object.properties - } - - o.r = object.rotation or 0 - if o.shape == "rectangle" then - local cos = math.cos(math.rad(o.r)) - local sin = math.sin(math.rad(o.r)) - local oy = 0 - - if object.gid then - local tileset = map.tilesets[map.tiles[object.gid].tileset] - local lid = object.gid - tileset.firstgid - local t = {} - - -- This fixes a height issue - o.y = o.y + map.tiles[object.gid].offset.y - oy = o.h - - for _, tt in ipairs(tileset.tiles) do - if tt.id == lid then - t = tt - break - end - end - - if t.objectGroup then - for _, obj in ipairs(t.objectGroup.objects) do - -- Every object in the tile - calculateObjectPosition(obj, object) - end - - return - else - o.w = map.tiles[object.gid].width - o.h = map.tiles[object.gid].height - end - end - - o.polygon = { - { x=o.x+0, y=o.y+0 }, - { x=o.x+o.w, y=o.y+0 }, - { x=o.x+o.w, y=o.y+o.h }, - { x=o.x+0, y=o.y+o.h } - } - - for _, vertex in ipairs(o.polygon) do - vertex.x, vertex.y = utils.rotate_vertex(map, vertex, o.x, o.y, cos, sin, oy) - end - - local vertices = getPolygonVertices(o) - addObjectToWorld(o.shape, vertices, userdata, tile or object) - elseif o.shape == "ellipse" then - if not o.polygon then - o.polygon = utils.convert_ellipse_to_polygon(o.x, o.y, o.w, o.h) - end - local vertices = getPolygonVertices(o) - local triangles = love.math.triangulate(vertices) - - for _, triangle in ipairs(triangles) do - addObjectToWorld(o.shape, triangle, userdata, tile or object) - end - elseif o.shape == "polygon" then - -- Recalculate collision polygons inside tiles - if tile then - local cos = math.cos(math.rad(o.r)) - local sin = math.sin(math.rad(o.r)) - for _, vertex in ipairs(o.polygon) do - vertex.x = vertex.x + o.x - vertex.y = vertex.y + o.y - vertex.x, vertex.y = utils.rotate_vertex(map, vertex, o.x, o.y, cos, sin) - end - end - - local vertices = getPolygonVertices(o) - local triangles = love.math.triangulate(vertices) - - for _, triangle in ipairs(triangles) do - addObjectToWorld(o.shape, triangle, userdata, tile or object) - end - elseif o.shape == "polyline" then - local vertices = getPolygonVertices(o) - addObjectToWorld(o.shape, vertices, userdata, tile or object) - end - end - - for _, tile in pairs(map.tiles) do - if map.tileInstances[tile.gid] then - for _, instance in ipairs(map.tileInstances[tile.gid]) do - -- Every object in every instance of a tile - if tile.objectGroup then - for _, object in ipairs(tile.objectGroup.objects) do - if object.properties.collidable == true then - object = utils.deepCopy(object) - object.dx = instance.x + object.x - object.dy = instance.y + object.y - calculateObjectPosition(object, instance) - end - end - end - - -- Every instance of a tile - if tile.properties.collidable == true then - local object = { - shape = "rectangle", - x = instance.x, - y = instance.y, - width = map.tilewidth, - height = map.tileheight, - properties = tile.properties - } - - calculateObjectPosition(object, instance) - end - end - end - end - - for _, layer in ipairs(map.layers) do - -- Entire layer - if layer.properties.collidable == true then - if layer.type == "tilelayer" then - for gid, tiles in pairs(map.tileInstances) do - local tile = map.tiles[gid] - local tileset = map.tilesets[tile.tileset] - - for _, instance in ipairs(tiles) do - if instance.layer == layer then - local object = { - shape = "rectangle", - x = instance.x, - y = instance.y, - width = tileset.tilewidth, - height = tileset.tileheight, - properties = tile.properties - } - - calculateObjectPosition(object, instance) - end - end - end - elseif layer.type == "objectgroup" then - for _, object in ipairs(layer.objects) do - calculateObjectPosition(object) - end - elseif layer.type == "imagelayer" then - local object = { - shape = "rectangle", - x = layer.x or 0, - y = layer.y or 0, - width = layer.width, - height = layer.height, - properties = layer.properties - } - - calculateObjectPosition(object) - end - end - - -- Individual objects - if layer.type == "objectgroup" then - for _, object in ipairs(layer.objects) do - if object.properties.collidable == true then - calculateObjectPosition(object) - end - end - end - end - - map.box2d_collision = collision - end, - - --- Remove Box2D fixtures and shapes from world. - -- @param index The index or name of the layer being removed - box2d_removeLayer = function(map, index) - local layer = assert(map.layers[index], "Layer not found: " .. index) - local collision = map.box2d_collision - - -- Remove collision objects - for i = #collision, 1, -1 do - local obj = collision[i] - - if obj.object.layer == layer then - obj.fixture:destroy() - table.remove(collision, i) - end - end - end, - - --- Draw Box2D physics world. - -- @param tx Translate on X - -- @param ty Translate on Y - -- @param sx Scale on X - -- @param sy Scale on Y - box2d_draw = function(map, tx, ty, sx, sy) - local collision = map.box2d_collision - - lg.push() - lg.scale(sx or 1, sy or sx or 1) - lg.translate(math.floor(tx or 0), math.floor(ty or 0)) - - for _, obj in ipairs(collision) do - local points = {obj.body:getWorldPoints(obj.shape:getPoints())} - local shape_type = obj.shape:getType() - - if shape_type == "edge" or shape_type == "chain" then - love.graphics.line(points) - elseif shape_type == "polygon" then - love.graphics.polygon("line", points) - else - error("sti box2d plugin does not support "..shape_type.." shapes") - end - end - - lg.pop() - end -} - ---- Custom Properties in Tiled are used to tell this plugin what to do. --- @table Properties --- @field collidable set to true, can be used on any Layer, Tile, or Object --- @field sensor set to true, can be used on any Tile or Object that is also collidable --- @field dynamic set to true, can be used on any Tile or Object --- @field friction can be used to define the friction of any Object --- @field restitution can be used to define the restitution of any Object --- @field categories can be used to set the filter Category of any Object --- @field mask can be used to set the filter Mask of any Object --- @field group can be used to set the filter Group of any Object diff --git a/src/lib/sti/plugins/bump.lua b/src/lib/sti/plugins/bump.lua deleted file mode 100644 index aeb0e81..0000000 --- a/src/lib/sti/plugins/bump.lua +++ /dev/null @@ -1,235 +0,0 @@ ---- Bump.lua plugin for STI --- @module bump.lua --- @author David Serrano (BobbyJones|FrenchFryLord) --- @copyright 2019 --- @license MIT/X11 - -local lg = require((...):gsub('plugins.bump', 'graphics')) - -local function getKeys(t) - local keys = {} - for k in pairs(t) do - table.insert(keys, k) - end - return keys -end - -local bit = require("bit") -local FLIPPED_HORIZONTALLY_FLAG = 0x80000000; -local FLIPPED_VERTICALLY_FLAG = 0x40000000; -local FLIPPED_DIAGONALLY_FLAG = 0x20000000; -local ROTATED_HEXAGONAL_120_FLAG = 0x10000000; -local GID_MASK = bit.bnot(bit.bor(FLIPPED_DIAGONALLY_FLAG, FLIPPED_VERTICALLY_FLAG, FLIPPED_HORIZONTALLY_FLAG, ROTATED_HEXAGONAL_120_FLAG)) - -local function findTileFromTilesets(tilesets, id) - for _, tileset in ipairs(tilesets) do - if tileset.firstgid <= id then - for _, tile in ipairs(tileset.tiles) do - if tileset.firstgid + tile.id == id then - return tile - end - end - end - end -end - -return { - bump_LICENSE = "MIT/X11", - bump_URL = "https://github.com/karai17/Simple-Tiled-Implementation", - bump_VERSION = "3.1.7.1", - bump_DESCRIPTION = "Bump hooks for STI.", - - --- Adds each collidable tile to the Bump world. - -- @param world The Bump world to add objects to. - -- @return collidables table containing the handles to the objects in the Bump world. - bump_init = function(map, world) - local collidables = {} - - for _, gid in ipairs(getKeys(map.tileInstances)) do - local id = bit.band(gid, GID_MASK) - local tile = findTileFromTilesets(map.tilesets, id) - - if tile then - for _, instance in ipairs(map.tileInstances[gid]) do - -- Every object in every instance of a tile - if tile.objectGroup then - for _, object in ipairs(tile.objectGroup.objects) do - if object.properties.collidable == true then - local t = { - name = object.name, - type = object.type, - x = instance.x + map.offsetx + object.x, - y = instance.y + map.offsety + object.y, - width = object.width, - height = object.height, - layer = instance.layer, - properties = object.properties - } - - world:add(t, t.x, t.y, t.width, t.height) - table.insert(collidables, t) - end - end - end - - -- Every instance of a tile - if tile.properties and tile.properties.collidable == true then - local tileProperties = map.tiles[gid] - local x = instance.x + map.offsetx - local y = instance.y + map.offsety - local sx = tileProperties.sx - local sy = tileProperties.sy - - -- Width and height can only be positive in bump, to get around this - -- For negative scaling just move the position back instead - if sx < 1 then - sx = -sx - x = x - map.tilewidth * sx - end - if sy < 1 then - sy = -sy - x = x - map.tileheight * sy - end - - local t = { - x = x, - y = y, - width = map.tilewidth * sx, - height = map.tileheight * sy, - layer = instance.layer, - type = tile.type, - properties = tile.properties - } - - world:add(t, t.x, t.y, t.width, t.height) - table.insert(collidables, t) - end - end - end - end - - for _, layer in ipairs(map.layers) do - -- Entire layer - if layer.properties.collidable == true then - if layer.type == "tilelayer" then - for y, tiles in ipairs(layer.data) do - for x, tile in pairs(tiles) do - - if tile.objectGroup then - for _, object in ipairs(tile.objectGroup.objects) do - if object.properties.collidable == true then - local t = { - name = object.name, - type = object.type, - x = ((x-1) * map.tilewidth + tile.offset.x + map.offsetx) + object.x, - y = ((y-1) * map.tileheight + tile.offset.y + map.offsety) + object.y, - width = object.width, - height = object.height, - layer = layer, - properties = object.properties - } - - world:add(t, t.x, t.y, t.width, t.height) - table.insert(collidables, t) - end - end - end - - - local t = { - x = (x-1) * map.tilewidth + tile.offset.x + map.offsetx, - y = (y-1) * map.tileheight + tile.offset.y + map.offsety, - width = tile.width, - height = tile.height, - layer = layer, - type = tile.type, - properties = tile.properties - } - - world:add(t, t.x, t.y, t.width, t.height) - table.insert(collidables, t) - end - end - elseif layer.type == "imagelayer" then - world:add(layer, layer.x, layer.y, layer.width, layer.height) - table.insert(collidables, layer) - end - end - - -- individual collidable objects in a layer that is not "collidable" - -- or whole collidable objects layer - if layer.type == "objectgroup" then - for _, obj in ipairs(layer.objects) do - if layer.properties.collidable == true or obj.properties.collidable == true then - if obj.shape == "rectangle" then - local t = { - name = obj.name, - type = obj.type, - x = obj.x + map.offsetx, - y = obj.y + map.offsety, - width = obj.width, - height = obj.height, - layer = layer, - properties = obj.properties - } - - if obj.gid then - t.y = t.y - obj.height - end - - world:add(t, t.x, t.y, t.width, t.height) - table.insert(collidables, t) - end -- TODO implement other object shapes? - end - end - end - end - - map.bump_world = world - map.bump_collidables = collidables - end, - - --- Remove layer - -- @param index to layer to be removed - bump_removeLayer = function(map, index) - local layer = assert(map.layers[index], "Layer not found: " .. index) - local collidables = map.bump_collidables - - -- Remove collision objects - for i = #collidables, 1, -1 do - local obj = collidables[i] - - if obj.layer == layer - and ( - layer.properties.collidable == true - or obj.properties.collidable == true - ) then - map.bump_world:remove(obj) - table.remove(collidables, i) - end - end - end, - - --- Draw bump collisions world. - -- @param world bump world holding the tiles geometry - -- @param tx Translate on X - -- @param ty Translate on Y - -- @param sx Scale on X - -- @param sy Scale on Y - bump_draw = function(map, tx, ty, sx, sy) - lg.push() - lg.scale(sx or 1, sy or sx or 1) - lg.translate(math.floor(tx or 0), math.floor(ty or 0)) - - local items = map.bump_world:getItems() - for _, item in ipairs(items) do - lg.rectangle("line", map.bump_world:getRect(item)) - end - - lg.pop() - end -} - ---- Custom Properties in Tiled are used to tell this plugin what to do. --- @table Properties --- @field collidable set to true, can be used on any Layer, Tile, or Object diff --git a/src/lib/sti/utils.lua b/src/lib/sti/utils.lua deleted file mode 100644 index 95e857a..0000000 --- a/src/lib/sti/utils.lua +++ /dev/null @@ -1,217 +0,0 @@ --- Some utility functions that shouldn't be exposed. -local utils = {} - --- https://github.com/stevedonovan/Penlight/blob/master/lua/pl/path.lua#L286 -function utils.format_path(path) - local np_gen1,np_gen2 = '[^SEP]+SEP%.%.SEP?','SEP+%.?SEP' - local np_pat1, np_pat2 = np_gen1:gsub('SEP','/'), np_gen2:gsub('SEP','/') - local k - - repeat -- /./ -> / - path,k = path:gsub(np_pat2,'/',1) - until k == 0 - - repeat -- A/../ -> (empty) - path,k = path:gsub(np_pat1,'',1) - until k == 0 - - if path == '' then path = '.' end - - return path -end - --- Compensation for scale/rotation shift -function utils.compensate(tile, tileX, tileY, tileW, tileH) - local compx = 0 - local compy = 0 - - if tile.sx < 0 then compx = tileW end - if tile.sy < 0 then compy = tileH end - - if tile.r > 0 then - tileX = tileX + tileH - compy - tileY = tileY + tileH + compx - tileW - elseif tile.r < 0 then - tileX = tileX + compy - tileY = tileY - compx + tileH - else - tileX = tileX + compx - tileY = tileY + compy - end - - return tileX, tileY -end - --- Cache images in main STI module -function utils.cache_image(sti, path, image) - image = image or love.graphics.newImage(path) - image:setFilter("nearest", "nearest") - sti.cache[path] = image -end - --- We just don't know. -function utils.get_tiles(imageW, tileW, margin, spacing) - imageW = imageW - margin - local n = 0 - - while imageW >= tileW do - imageW = imageW - tileW - if n ~= 0 then imageW = imageW - spacing end - if imageW >= 0 then n = n + 1 end - end - - return n -end - --- Decompress tile layer data -function utils.get_decompressed_data(data) - local ffi = require "ffi" - local d = {} - local decoded = ffi.cast("uint32_t*", data) - - for i = 0, data:len() / ffi.sizeof("uint32_t") do - table.insert(d, tonumber(decoded[i])) - end - - return d -end - --- Convert a Tiled ellipse object to a LOVE polygon -function utils.convert_ellipse_to_polygon(x, y, w, h, max_segments) - local ceil = math.ceil - local cos = math.cos - local sin = math.sin - - local function calc_segments(segments) - local function vdist(a, b) - local c = { - x = a.x - b.x, - y = a.y - b.y, - } - - return c.x * c.x + c.y * c.y - end - - segments = segments or 64 - local vertices = {} - - local v = { 1, 2, ceil(segments/4-1), ceil(segments/4) } - - local m - if love and love.physics then - m = love.physics.getMeter() - else - m = 32 - end - - for _, i in ipairs(v) do - local angle = (i / segments) * math.pi * 2 - local px = x + w / 2 + cos(angle) * w / 2 - local py = y + h / 2 + sin(angle) * h / 2 - - table.insert(vertices, { x = px / m, y = py / m }) - end - - local dist1 = vdist(vertices[1], vertices[2]) - local dist2 = vdist(vertices[3], vertices[4]) - - -- Box2D threshold - if dist1 < 0.0025 or dist2 < 0.0025 then - return calc_segments(segments-2) - end - - return segments - end - - local segments = calc_segments(max_segments) - local vertices = {} - - table.insert(vertices, { x = x + w / 2, y = y + h / 2 }) - - for i = 0, segments do - local angle = (i / segments) * math.pi * 2 - local px = x + w / 2 + cos(angle) * w / 2 - local py = y + h / 2 + sin(angle) * h / 2 - - table.insert(vertices, { x = px, y = py }) - end - - return vertices -end - -function utils.rotate_vertex(map, vertex, x, y, cos, sin, oy) - if map.orientation == "isometric" then - x, y = utils.convert_isometric_to_screen(map, x, y) - vertex.x, vertex.y = utils.convert_isometric_to_screen(map, vertex.x, vertex.y) - end - - vertex.x = vertex.x - x - vertex.y = vertex.y - y - - return - x + cos * vertex.x - sin * vertex.y, - y + sin * vertex.x + cos * vertex.y - (oy or 0) -end - ---- Project isometric position to cartesian position -function utils.convert_isometric_to_screen(map, x, y) - local mapW = map.width - local tileW = map.tilewidth - local tileH = map.tileheight - local tileX = x / tileH - local tileY = y / tileH - local offsetX = mapW * tileW / 2 - - return - (tileX - tileY) * tileW / 2 + offsetX, - (tileX + tileY) * tileH / 2 -end - -function utils.hex_to_color(hex) - if hex:sub(1, 1) == "#" then - hex = hex:sub(2) - end - - return { - r = tonumber(hex:sub(1, 2), 16) / 255, - g = tonumber(hex:sub(3, 4), 16) / 255, - b = tonumber(hex:sub(5, 6), 16) / 255 - } -end - -function utils.pixel_function(_, _, r, g, b, a) - local mask = utils._TC - - if r == mask.r and - g == mask.g and - b == mask.b then - return r, g, b, 0 - end - - return r, g, b, a -end - -function utils.fix_transparent_color(tileset, path) - local image_data = love.image.newImageData(path) - tileset.image = love.graphics.newImage(image_data) - - if tileset.transparentcolor then - utils._TC = utils.hex_to_color(tileset.transparentcolor) - - image_data:mapPixel(utils.pixel_function) - tileset.image = love.graphics.newImage(image_data) - end -end - -function utils.deepCopy(t) - local copy = {} - for k,v in pairs(t) do - if type(v) == "table" then - v = utils.deepCopy(v) - end - copy[k] = v - end - return copy -end - -return utils diff --git a/src/main.lua b/src/main.lua index 1d2e839..d29f588 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1,18 +1,11 @@ local Gamestate = require("lib.hump.gamestate") -local binser = require("lib.binser") -local Vec = require("lib.brinevector") pprint = require("lib.pprint") -binser.registerStruct("brinevector", - function(v) return v.x, v.y end, - function(x, y) return Vec(x, y) end -) - function love.load() love.math.setRandomSeed(love.timer.getTime()) love.keyboard.setKeyRepeat(true) math.randomseed(love.timer.getTime()) - Gamestate.switch(require("states.main-menu")) + Gamestate.switch(require("states.main")) Gamestate.registerEvents() end diff --git a/src/states/main-menu.lua b/src/states/main-menu.lua deleted file mode 100644 index 3a9d2ba..0000000 --- a/src/states/main-menu.lua +++ /dev/null @@ -1,165 +0,0 @@ -local MainMenu = {} -local Gamestate = require("lib.hump.gamestate") -local http = require("socket.http") -local enet = require("enet") -local nata = require("lib.nata") -local Vec = require("lib.brinevector") -local data = require("data") - -local function getPublicIP() - local ip = http.request("https://ipinfo.io/ip") - return ip -end - -local function splitat(str, char) - local index = str:find(char) - return str:sub(1, index-1), str:sub(index+1) -end - -local function findByName(list, name) - for _, o in ipairs(list) do - if o.name:lower() == name:lower() then - return o - end - end -end - -function MainMenu:init() - local systems = { - require("systems.physics"), - require("systems.bolt"), - require("systems.map"), - require("systems.ui"), - require("systems.player"), - require("systems.sprite"), - require("systems.screen-scaler"), - } - if not love.filesystem.isFused() then - table.insert(systems, require("systems.debug")) - end - - self.ecs = nata.new{ - available_groups = require("groups"), - systems = systems, - data = { - map = "data/maps/main-menu.lua" - } - } - - self.host_socket = enet.host_create("*:0") - - self.peer_socket = nil - self.hosting = false - self.connecting = false - - local map = self.ecs:getSystem(require("systems.map")).map - local pprint = require("lib.pprint") - local metadata_layer = findByName(map.layers, "metadata") - - do - local spawnpoint = findByName(metadata_layer.objects, "spawnpoint") - local PlayerSystem = self.ecs:getSystem(require("systems.player")) - local player = PlayerSystem:spawnPlayer(Vec(spawnpoint.x, spawnpoint.y)) - player.sprite.flip = true - player.controllable = true - end - - do - self.screenWidth = map.width * map.tilewidth - self.screenHeight = map.height * map.tileheight - - local border = 2.5 - local w = self.screenWidth - local h = self.screenHeight - self.ecs:queue{ collider = {0, 0, w, border} } - self.ecs:queue{ collider = {0, 0, border, h} } - self.ecs:queue{ collider = {w-border, 0, w, h} } - self.ecs:queue{ collider = {0, h-border, w, h} } - end - - do - local controls_obj = findByName(metadata_layer.objects, "controls") - local pos = Vec(controls_obj.x, controls_obj.y) - - local prompts = data.ui.prompts.dark - self.ecs:queue{ - pos = pos + Vec(0, -16), - image = prompts["w"], - collider = {-8, -8, 8, 8} - } - self.ecs:queue{ - pos = pos, - image = prompts["a"], - collider = {-8, -8, 8, 8} - } - self.ecs:queue{ - pos = pos + Vec(-16, 0), - image = prompts["s"], - collider = {-8, -8, 8, 8} - } - self.ecs:queue{ - pos = pos + Vec(16, 0), - image = prompts["d"], - collider = {-8, -8, 8, 8} - } - self.ecs:queue{ - pos = pos + Vec(0, 16), - image = prompts["spacebar"], - collider = {-24, -8, 24, 8} - } - end - - do - local host_btn = self.ecs:queue{ - pos = Vec(16, 16), - size = Vec(96, 32), - text = "Host" - } - local join_btn = self.ecs:queue{ - pos = Vec(16, 48+8), - size = Vec(96, 32), - text = "Join" - } - local quit_btn = self.ecs:queue{ - pos = Vec(16, 96), - size = Vec(96, 32), - text = "Quit" - } - self.ecs:on("on_btn_pressed", function(btn) - if btn == quit_btn then - love.event.quit() - elseif btn == host_btn then - Gamestate.switch(require("states.main"), self.host_socket) - end - end) - end -end - -function MainMenu:update(dt) - self.ecs:flush() - self.ecs:emit("update", dt) - local event = self.host_socket:service() - if event and event.type == "connect" then - Gamestate.switch(require("states.main"), self.host_socket) - end -end - -function MainMenu:mousemoved(x, y, dx, dy, istouch) - local ScreenScaler = self.ecs:getSystem(require("systems.screen-scaler")) - if not ScreenScaler:isInBounds(x, y) then return end - - x, y = ScreenScaler:getPosition(x, y) - dx = (ScreenScaler.scale or 1) * dx - dy = (ScreenScaler.scale or 1) * dy - self.ecs:emit("mousemoved", x, y, dx, dy, istouch) -end - -function MainMenu:draw() - local ScreenScaler = self.ecs:getSystem(require("systems.screen-scaler")) - - ScreenScaler:start(self.screenWidth, self.screenHeight) - self.ecs:emit("draw") - ScreenScaler:finish() -end - -return MainMenu diff --git a/src/states/main.lua b/src/states/main.lua index 09c6c8d..05bac68 100644 --- a/src/states/main.lua +++ b/src/states/main.lua @@ -1,56 +1,12 @@ local MainState = {} -local pprint = require("lib.pprint") local nata = require("lib.nata") -local data = require("data") - -function MainState:enter(_, host_socket) - local systems = { - require("systems.physics"), - require("systems.map"), - require("systems.sprite"), - require("systems.player"), - require("systems.bolt"), - require("systems.screen-scaler") - } - - if not love.filesystem.isFused() then - table.insert(systems, require("systems.debug")) - end - - if host_socket then - table.insert(systems, require("systems.multiplayer")) - end +function MainState:enter() self.ecs = nata.new{ - available_groups = require("groups"), - systems = systems, - data = { - host_socket = host_socket, - map = "data/maps/playground.lua" - } + groups = {}, + systems = {}, + data = {} } - - self.downscaled_canvas = nil - self:refreshScaler() - self.ecs:on("onMapSwitch", function(map) - self:refreshScaler(map) - end) - - - -- Spawn in the entity that the local player will be using - local PlayerSystem = self.ecs:getSystem(require("systems.player")) - local player = PlayerSystem:spawnPlayer() - player.controllable = true -end - -function MainState:refreshScaler(map) - if not map then - local Map = self.ecs:getSystem(require("systems.map")) - map = Map.map - end - - self.screenWidth = map.width * map.tilewidth - self.screenHeight = map.height * map.tileheight end function MainState:update(dt) @@ -62,37 +18,12 @@ function MainState:update(dt) end end -function MainState:cleanup() - local multiplayer = self.ecs:getSystem(require("systems.multiplayer")) - if multiplayer then - multiplayer:disconnectPeers() - end -end - -function MainState:quit() - self:cleanup() -end - -function MainState:mousemoved(x, y, dx, dy, istouch) - local ScreenScaler = self.ecs:getSystem(require("systems.screen-scaler")) - if not ScreenScaler:isInBounds(x, y) then return end - - x, y = ScreenScaler:getPosition(x, y) - dx = (ScreenScaler.scale or 1) * dx - dy = (ScreenScaler.scale or 1) * dy - self.ecs:emit("mousemoved", x, y, dx, dy, istouch) -end - function MainState:keypressed(...) self.ecs:emit("keypressed", ...) end function MainState:draw() - local ScreenScaler = self.ecs:getSystem(require("systems.screen-scaler")) - - ScreenScaler:start(self.screenWidth, self.screenHeight) self.ecs:emit("draw") - ScreenScaler:finish() end return MainState diff --git a/src/systems/bolt.lua b/src/systems/bolt.lua deleted file mode 100644 index 2f85fcb..0000000 --- a/src/systems/bolt.lua +++ /dev/null @@ -1,91 +0,0 @@ -local Bolt = {} -local Vec = require("lib.brinevector") - --- BUG: trajectory projection doesn't take into account the bolt collider - -local BOLT_COLLIDER = {-3, -3, 3, 3} - -Bolt.required_groups = {"bolt"} - -function Bolt:update(dt) - for _, bolt in ipairs(self.pool.groups.bolt.entities) do - if bolt.vel.length < 20 and not bolt.pickupable then - bolt.pickupable = true - bolt.sprite.variant = "idle" - bolt.vel.x = 0 - bolt.vel.y = 0 - end - end -end - -function Bolt:projectTrajectory(pos, step, count) - local physics = self.pool:getSystem(require("systems.physics")) - local step_size = step.length - local distance = step_size * (count-1) - local key_points = physics:project(pos, step.normalized, distance) - - local trajectory = {} - - local current_segment = 1 - local current_distance = -step_size - local segment_direction, segment_size - local last_point = key_points[1] - step - do - local diff = key_points[2] - key_points[1] - segment_size = diff.length - segment_direction = diff.normalized - end - - -- For every point that we want to place along our projected line - for _=1, count do - -- First check if the point about to be placed would be outside current segment - while current_distance+step_size > segment_size do - -- Check if there are any unused segments left - if current_segment < (#key_points-1) then - -- Switch next segment - current_distance = current_distance - segment_size - - current_segment = current_segment + 1 - local diff = key_points[current_segment+1] - key_points[current_segment] - segment_size = diff.length - segment_direction = diff.normalized - - -- Adjust where the next point is going to be placed, so it's correct - local to_key_point = (key_points[current_segment]-last_point).length - last_point = key_points[current_segment] - segment_direction * to_key_point - - -- If there are not segments left, just treat the last segment as infinite - else - segment_size = math.huge - end - end - - -- Place point along segment - local point = last_point + segment_direction * step_size - table.insert(trajectory, point) - current_distance = current_distance + step_size - last_point = point - end - - return trajectory -end - -function Bolt:boltShot(player, bolt) - bolt.pickupable = nil - bolt.collider = BOLT_COLLIDER - bolt.bolt = true - bolt.sprite.variant = "active" - bolt.hidden = nil - self.pool:queue(bolt) -end - -function Bolt:storeBolt(player, bolt) - bolt.pickupable = nil - bolt.collider = nil - bolt.bolt = nil - bolt.sprite.variant = "idle" - bolt.hidden = true - self.pool:queue(bolt) -end - -return Bolt diff --git a/src/systems/debug.lua b/src/systems/debug.lua deleted file mode 100644 index c5fdd5a..0000000 --- a/src/systems/debug.lua +++ /dev/null @@ -1,84 +0,0 @@ -local Debug = {} -local rgb = require("helpers.rgb") - -local DRAW_GRID = false -local GRID_COLOR = rgb(30, 30, 30) - -local DRAW_COLLIDERS = false -local COLLIDER_COLOR = rgb(200, 20, 200) - -local DRAW_PING = false - -function Debug:init() - self.current_player = 1 -end - -function Debug:drawColliders() - local physics = self.pool:getSystem(require("systems.physics")) - love.graphics.setColor(COLLIDER_COLOR) - - local bump = physics.bump - local items = bump:getItems() - for _, item in ipairs(items) do - love.graphics.rectangle("line", bump:getRect(item)) - end -end - -function Debug:drawGrid() - local map = self.pool:getSystem(require("systems.map")) - if not map.map then return end - - local scaler = self.pool:getSystem(require("systems.screen-scaler")) - local w, h = scaler:getDimensions() - love.graphics.setColor(GRID_COLOR) - for x=0, w, map.map.tilewidth do - love.graphics.line(x, 0, x, h) - end - for y=0, h, map.map.tileheight do - love.graphics.line(0, y, w, y) - end -end - -function Debug:keypressed(key) - if key == "e" and love.keyboard.isDown("lshift") then - local PlayerSystem = self.pool:getSystem(require("systems.player")) - local player = PlayerSystem:spawnPlayer() - for _, e in ipairs(self.pool.groups.controllable_player.entities) do - e.controllable = false - self.pool:queue(e) - end - - player.controllable = true - self.pool:queue(player) - - self.current_player = #self.pool.groups.player.entities+1 - elseif key == "tab" then - local player_entities = self.pool.groups.player.entities - - player_entities[self.current_player].controllable = false - self.pool:queue(player_entities[self.current_player]) - - self.current_player = self.current_player % #player_entities + 1 - player_entities[self.current_player].controllable = true - self.pool:queue(player_entities[self.current_player]) - end -end - -function Debug:draw() - if DRAW_GRID then - self:drawGrid() - end - if DRAW_COLLIDERS then - self:drawColliders() - end - if DRAW_PING and self.pool.data.host_socket then - local host_socket = self.pool.data.host_socket - local height = love.graphics.getFont():getHeight() - for _, index in ipairs(self.connected_peers) do - local peer = host_socket:get_peer(index) - love.graphics.print(peer:round_trip_time(), 0, (index-1)*height) - end - end -end - -return Debug diff --git a/src/systems/map.lua b/src/systems/map.lua deleted file mode 100644 index 28a5fe6..0000000 --- a/src/systems/map.lua +++ /dev/null @@ -1,54 +0,0 @@ -local Map = {} -local sti = require("lib.sti") -local Vec = require("lib.brinevector") - -function Map:init() - if self.pool.data.map then - self.map = sti(self.pool.data.map, { "bump" }) - - local spriteSystem = self.pool:getSystem(require("systems.sprite")) - if spriteSystem then - for _, layer in ipairs(self.map.layers) do - if layer.type == "objectgroup" then - for _, obj in ipairs(layer.objects) do - local props = obj.properties - if props.sprite then - local x = obj.x + obj.width/2 - local y = obj.y + obj.height/2 - self.pool:queue{ - sprite = { name = props.sprite }, - pos = Vec(x, y) - } - end - end - end - end - end - - - self.pool:emit("onMapSwitch", self.map) - end -end - -function Map:update(dt) - if self.map then - self.map:update(dt) - end -end - -function Map:listSpawnpoints() - local points = {} - for _, object in ipairs(self.map.layers.spawnpoints.objects) do - table.insert(points, Vec(object.x, object.y)) - end - return points -end - -function Map:draw() - if self.map then - love.graphics.setColor(1, 1, 1) - self.map:draw() - end -end - -return Map diff --git a/src/systems/multiplayer.lua b/src/systems/multiplayer.lua deleted file mode 100644 index 19aaffa..0000000 --- a/src/systems/multiplayer.lua +++ /dev/null @@ -1,151 +0,0 @@ -local Multiplayer = {} -local binser = require("lib.binser") -local pprint = require("lib.pprint") -local uid = require("helpers.uid") - -local RATE_LIMIT = 20 - -local CMD = { - SPAWN_PLAYER = 1, - MOVE_PLAYER = 2, - AIM_PLAYER = 3, - PLAYER_SHOT = 4, -} - -local function removeValue(t, v) - for i, vv in ipairs(t) do - if v == vv then - table.remove(t, i) - end - end -end - -function Multiplayer:init() - self.connected_peers = {} - local host_socket = self.pool.data.host_socket - for i=1, host_socket:peer_count() do - local peer = host_socket:get_peer(i) - if peer:state() == "connected" then - self:onPeerConnect(peer) - end - end -end - -function Multiplayer:onPeerConnect(peer) - table.insert(self.connected_peers, peer:index()) -end - -function Multiplayer:onPeerDisconnect(peer) - local peer_index = peer:index() - removeValue(self.connected_peers, peer_index) - for _, player in ipairs(self.pool.groups.player.entities) do - if player.peer_index == peer_index then - self.pool:removeEntity(player) - end - end -end - -function Multiplayer:disconnectPeers() - local host_socket = self.pool.data.host_socket - for _, index in ipairs(self.connected_peers) do - local peer = host_socket:get_peer(index) - peer:disconnect_now() - self:onPeerDisconnect(peer) - end - host_socket:flush() -end - -function Multiplayer:sendToPeer(peer, ...) - peer:send(binser.serialize(...), nil, "unreliable") -end - -function Multiplayer:sendToAllPeers(...) - local payload = binser.serialize(...) - local host_socket = self.pool.data.host_socket - for _, index in ipairs(self.connected_peers) do - local peer = host_socket:get_peer(index) - peer:send(payload, nil, "unreliable") - end -end - -function Multiplayer:update() - local host_socket = self.pool.data.host_socket - local event = host_socket:service() - if event then - if event.type == "connect" then - self:onPeerConnect(event.peer) - elseif event.type == "disconnect" then - self:onPeerDisconnect(event.peer) - elseif event.type == "receive" then - local parameters = binser.deserialize(event.data) - self:handlePeerMessage(event.peer, unpack(parameters)) - end - end -end - -function Multiplayer:getPlayerEntityById(id) - for _, player in ipairs(self.pool.groups.player.entities) do - if player.id == id then - return player - end - end -end - -function Multiplayer:handlePeerMessage(peer, cmd, ...) - if cmd == CMD.SPAWN_PLAYER then - local id = ... - local PlayerSystem = self.pool:getSystem(require("systems.player")) - local player = PlayerSystem:spawnPlayer() - player.id = id - player.peer_index = peer:index() - elseif cmd == CMD.MOVE_PLAYER then - local id, move_dir, pos = ... - local player = self:getPlayerEntityById(id) - - if player and player.peer_index == peer:index() then - player.move_dir = move_dir - player.pos = pos - end - elseif cmd == CMD.AIM_PLAYER then - local id, aim_dir = ... - local player = self:getPlayerEntityById(id) - - if player and player.peer_index == peer:index() then - player.aim_dir = aim_dir - end - elseif cmd == CMD.PLAYER_SHOT then - local id = ... - local player = self:getPlayerEntityById(id) - - if player and player.peer_index == peer:index() then - self.pool:emit("tryShootingBolt", player) - end - else - print("DEBUG INFORMATION:") - print("Message:") - pprint{cmd, ...} - error("Unhandled message from peer") - end -end - -function Multiplayer:addToGroup(group, player) - if group == "player" and not player.peer_index then - player.id = uid() - self:sendToAllPeers(CMD.SPAWN_PLAYER, player.id) - self.pool:queue(player) - end -end - -function Multiplayer:playerMoved(player) - self:sendToAllPeers(CMD.MOVE_PLAYER, player.id, player.move_dir, player.pos) -end - -function Multiplayer:playerAimed(player) - self:sendToAllPeers(CMD.AIM_PLAYER, player.id, player.aim_dir) -end - -function Multiplayer:playerShot(player) - self:sendToAllPeers(CMD.PLAYER_SHOT, player.id) -end - -return Multiplayer diff --git a/src/systems/physics.lua b/src/systems/physics.lua deleted file mode 100644 index 782e2c4..0000000 --- a/src/systems/physics.lua +++ /dev/null @@ -1,176 +0,0 @@ -local Physics = {} -local bump = require("lib.bump") -local pprint = require("lib.pprint") -local Vec = require("lib.brinevector") - --- TODO: Tweak bump world `cellSize` at runtime, when the map switches - -Physics.required_groups = {"physical", "collider"} - -function Physics:init() - self.bump = bump.newWorld() - self.boundCollisionFilter = function(...) - return self:collisionFilter(...) - end -end - -function Physics:onMapSwitch(map) - map:bump_init(self.bump) -end - -local function getColliderBounds(e) - local x = e.collider[1] - local y = e.collider[2] - local w = math.abs(e.collider[3] - e.collider[1]) - local h = math.abs(e.collider[4] - e.collider[2]) - if e.pos then - x = x + e.pos.x - y = y + e.pos.y - end - return x, y, w, h -end - -function Physics:addToGroup(group, e) - if group == "collider" then - self.bump:add(e, getColliderBounds(e)) - end -end - -function Physics:removeFromGroup(group, e) - if group == "collider" then - self.bump:remove(e) - end -end - -function Physics:collisionFilter(entity, other) - if entity.hidden then return end - if other.hidden then return end - - local bolt = entity.bolt or other.bolt - local vel = entity.vel or other.vel - local player = entity.bolts or other.bolts - - if bolt and player then - return "cross" - elseif bolt then - return "bounce" - elseif (vel or vel) and not (entity.bolts and other.bolts) then - return "slide" - end -end - -function Physics:resolveCollisions(e, dt) - local targetPos = e.pos + e.vel * dt - local ox = e.collider[1] - local oy = e.collider[2] - self.bump:update(e, e.pos.x+ox, e.pos.y+oy) - local x, y, cols = self.bump:move(e, targetPos.x+ox, targetPos.y+oy, self.boundCollisionFilter) - - local skip_moving = false - if #cols > 0 then - e.pos.x = x - ox - e.pos.y = y - oy - skip_moving = true - end - - for _, col in ipairs(cols) do - if col.type == "cross" then - local player = col.other.bolts and col.other or e - local bolt = col.other.bolt and col.other or e - if player and bolt and bolt.owner ~= player then - self.pool:emit("hitPlayer", player, bolt) - end - elseif col.type == "bounce" then - -- sx and sy and just number for flipped the direction when something - -- bounces. - -- When `normal.x` is zero, sx = 1, otherwise sx = -1. Same with sy - local sx = 1 - 2*math.abs(col.normal.x) - local sy = 1 - 2*math.abs(col.normal.y) - - e.vel.x = e.vel.x * sx - e.vel.y = e.vel.y * sy - if e.acc then - e.acc.x = e.acc.x * sx - e.acc.y = e.acc.y * sy - end - skip_moving = true - end - end - - return skip_moving -end - -function Physics:update(dt) - for _, e in ipairs(self.pool.groups.physical.entities) do - if e.acc then - e.vel = e.vel + e.acc * dt - end - if e.friction then - e.vel = e.vel * (1 - math.min(e.friction, 1)) ^ dt - end - - local skip_moving = false - if self.pool.groups.collider.hasEntity[e] then - skip_moving = self:resolveCollisions(e, dt) - end - - if not skip_moving then - if e.max_speed then - e.pos = e.pos + e.vel:trim(e.max_speed) * dt - else - e.pos = e.pos + e.vel * dt - end - end - end -end - -function Physics:castRay(from, to) - local items = self.bump:querySegmentWithCoords(from.x, from.y, to.x, to.y, function(item) - return not item.vel - end) - for _, item in ipairs(items) do - if item.nx ~= 0 or item.ny ~= 0 then - local pos = Vec(item.x1, item.y1) - local normal = Vec(item.nx, item.ny) - return pos, normal - end - end -end - -function Physics:project(from, direction, distance) - local key_points = {} - table.insert(key_points, from) - - local distance_traveled = 0 - local position = from - -- While the ray hasen't traveled the required amount - while math.abs(distance_traveled - distance) > 0.01 do - -- Check if it collides with anything - local destination = position + direction * (distance-distance_traveled) - local intersect, normal = self:castRay(position, destination) - local new_position = intersect or destination - -- Update how much the ray has moved in total - distance_traveled = distance_traveled + (position - new_position).length - position = new_position - - -- If it collided change its travel direction - if normal then - -- sx and sy and just number for flipped the direction when something - -- bounces. - -- When `normal.x` is zero, sx = 1, otherwise sx = -1. Same with sy - direction.x = direction.x * (1 - 2*math.abs(normal.x)) - direction.y = direction.y * (1 - 2*math.abs(normal.y)) - end - - -- Save this point where it stopped/collided - table.insert(key_points, new_position) - end - - return key_points -end - -function Physics:queryRect(pos, size, filter) - return self.bump:queryRect(pos.x, pos.y, size.x, size.y, filter) -end - -return Physics diff --git a/src/systems/player.lua b/src/systems/player.lua deleted file mode 100644 index 7bb3f6c..0000000 --- a/src/systems/player.lua +++ /dev/null @@ -1,261 +0,0 @@ -local Player = {} -local data = require("data") -local Vec = require("lib.brinevector") - -local controls = data.controls - -local AIMED_BOLT_DISTANCE = 15 - -local BOLT_AMOUNT = 1 - -local function getDirection(up_key, down_key, left_key, right_key) - local dx = (love.keyboard.isDown(right_key) and 1 or 0) - - (love.keyboard.isDown(left_key) and 1 or 0) - local dy = (love.keyboard.isDown(down_key) and 1 or 0) - - (love.keyboard.isDown(up_key) and 1 or 0) - return Vec(dx, dy).normalized -end - -Player.required_groups = {"player", "controllable_player", "bolt"} - -function Player:getMoveDirection() - return getDirection( - controls.move_up, - controls.move_down, - controls.move_left, - controls.move_right - ) -end - -function Player:getAimDirection(player) - if self.use_mouse_aim then - local ScreenScaler = self.pool:getSystem(require("systems.screen-scaler")) - local dir = Vec(ScreenScaler:getMousePosition()) - player.pos - - return dir.normalized - -- local MAX_DIRECTIONS = 8 - -- local angle_segment = math.pi*2/MAX_DIRECTIONS - -- local new_angle = math.floor(dir.angle/angle_segment+0.5)*angle_segment - -- return Vec(math.cos(new_angle), math.sin(new_angle)) - else - return getDirection( - controls.aim_up, - controls.aim_down, - controls.aim_left, - controls.aim_right - ) - end -end - -function Player:addToGroup(group, e) - if group == "player" then - e.bolt_cooldown_timer = e.bolt_cooldown_timer or 0 - e.aim_dir = e.aim_dir or Vec() - e.move_dir = e.move_dir or Vec() - end -end - -function Player:update(dt) - if love.keyboard.isDown(controls.aim_up, controls.aim_down, controls.aim_left, controls.aim_right) then - self.use_mouse_aim = false - end - - -- Update controls for "my" players - local move_direction = self:getMoveDirection() - for _, e in ipairs(self.pool.groups.controllable_player.entities) do - -- Update where they are aiming/looking - local aim_direction = self:getAimDirection(e) - if e.aim_dir ~= aim_direction then - e.aim_dir = aim_direction - self.pool:emit("playerAimed", e) - end - - -- Update acceleration to make the player move - if e.move_dir ~= move_direction then - e.move_dir = move_direction - self.pool:emit("playerMoved", e) - end - - -- Check if the player tried to shoot a bolt - if love.keyboard.isDown(controls.shoot) then - if self:tryShootingBolt(e) then - self.pool:emit("playerShot", e) - end - end - end - - -- Update each player - for _, e in ipairs(self.pool.groups.player.entities) do - e.acc = e.move_dir * e.speed - - -- Decrease bolt cooldown timer - if e.bolt_cooldown_timer > 0 then - e.bolt_cooldown_timer = math.max(0, e.bolt_cooldown_timer - dt) - end - - -- If the player is nearby a non-moving bolt, pick it up - for _, bolt in ipairs(self.pool.groups.bolt.entities) do - if (e.pos-bolt.pos).length < 20 and bolt.vel.length < 3 then - self.pool:emit("storeBolt", e, bolt) - end - end - - if e.acc.x ~= 0 or e.acc.y ~= 0 then - e.sprite.variant = "walk" - e.sprite.wobble = true - else - e.sprite.variant = "idle" - e.sprite.wobble = false - end - - if e.acc.x < 0 then - e.sprite.flip = true - elseif e.acc.x > 0 then - e.sprite.flip = false - end - - if #e.bolts > 0 then - local bolt = e.bolts[1] - if e.aim_dir.x ~= 0 or e.aim_dir.y ~= 0 then - bolt.hidden = nil - bolt.pos = e.pos + e.aim_dir * AIMED_BOLT_DISTANCE - else - bolt.hidden = true - end - end - end -end - -function Player:mousemoved() - self.use_mouse_aim = true -end - -function Player:tryShootingBolt(player) - if (player.aim_dir.x ~= 0 or player.aim_dir.y ~= 0) and - player.bolt_cooldown_timer == 0 and - #player.bolts > 0 - then - player.bolt_cooldown_timer = player.bolt_cooldown - self.pool:emit("shootBolt", player) - return true - end -end - -function Player:shootBolt(player) - local bolt = table.remove(player.bolts, 1) - if not bolt then return end - - bolt.pos = player.pos + player.aim_dir * AIMED_BOLT_DISTANCE - bolt.vel = player.aim_dir * player.bolt_speed - bolt.owner = player - self.pool:emit("boltShot", player, bolt) -end - -function Player:storeBolt(player, bolt) - bolt.owner = nil - table.insert(player.bolts, bolt) -end - -function Player:hitPlayer(player, bolt) - self:resetMap() -end - -local function createBolt() - return { - pos = Vec(), - vel = Vec(), - acc = Vec(), - sprite = { - name = "bolt", - variant = "idle", - }, - friction = 0.98, - max_speed = 400, - } -end - -function Player:resetMap() - local map = self.pool:getSystem(require("systems.map")) - local spawnpoints = map:listSpawnpoints() - - for _, bolt in ipairs(self.pool.groups.bolt.entities) do - self.pool:removeEntity(bolt) - end - - for i, player in ipairs(self.pool.groups.player.entities) do - for _, bolt in ipairs(player.bolts) do - self.pool:removeEntity(bolt) - end - - local spawnpoint = spawnpoints[(i - 1) % #spawnpoints + 1] - player.pos = spawnpoint - player.bolts = {} - for _=1, BOLT_AMOUNT do - local bolt = self.pool:queue(createBolt()) - self.pool:emit("storeBolt", player, bolt) - end - end -end - -function Player:spawnPlayer(pos) - if not pos then - local map = self.pool:getSystem(require("systems.map")) - local spawnpoints = map:listSpawnpoints() - local players = self.pool.groups.player.entities - pos = spawnpoints[#players % #spawnpoints + 1] - end - - local player = self.pool:queue{ - pos = pos, - vel = Vec(0, 0), - acc = Vec(), - - sprite = { - name = "player", - variant = "walk" - }, - friction = 0.998, - speed = 800, - bolt_speed = 2000, - bolts = {}, - bolt_cooldown = 0.1, - collider = {-5, -5, 4, 8} - } - - for _=1, BOLT_AMOUNT do - local bolt = self.pool:queue(createBolt()) - self.pool:emit("storeBolt", player, bolt) - end - - return player -end - -function Player:draw() - for _, e in ipairs(self.pool.groups.controllable_player.entities) do - if (e.aim_dir.x ~= 0 or e.aim_dir.y ~= 0) and #e.bolts > 0 then - local boltSystem = self.pool:getSystem(require("systems.bolt")) - local pos = e.pos + e.aim_dir * AIMED_BOLT_DISTANCE*0 - local vel = (e.aim_dir * e.bolt_speed).normalized * AIMED_BOLT_DISTANCE*1 - - local point_amount = 8 - local points = boltSystem:projectTrajectory(pos, vel, point_amount+3) - for i=3, point_amount+3-1 do - local ghost = data.sprites["bolt-ghost"] - local point = points[i] - local frame = ghost.variants.default[1] - love.graphics.setColor(1, 1, 1, (point_amount-(i-3))/point_amount) - love.graphics.draw( - frame.image, - point.x, - point.y, - nil, - nil, - nil, - ghost.width/2, ghost.height/2 - ) - end - end - end -end - -return Player diff --git a/src/systems/screen-scaler.lua b/src/systems/screen-scaler.lua deleted file mode 100644 index 9a8290c..0000000 --- a/src/systems/screen-scaler.lua +++ /dev/null @@ -1,106 +0,0 @@ -local ScreenScaler = {} - -function ScreenScaler:hideBorders() - local r, g, b, a = love.graphics.getColor() - love.graphics.setColor(love.graphics.getBackgroundColor()) - local w, h = love.graphics.getDimensions() - - if self.offset_x ~= 0 then - love.graphics.rectangle("fill", 0, 0, self.offset_x, h) - love.graphics.rectangle("fill", w-self.offset_x, 0, self.offset_x, h) - end - - if self.offset_y ~= 0 then - love.graphics.rectangle("fill", 0, 0, w, self.offset_y) - love.graphics.rectangle("fill", 0, h-self.offset_y, w, self.offset_y) - end - - love.graphics.setColor(r, g, b, a) -end - -function ScreenScaler:getPosition(x, y) - return (x - self.offset_x) / self.scale, - (y - self.offset_y) / self.scale -end - -function ScreenScaler:isInBounds(x, y) - if self.scale then - local w, h = love.graphics.getDimensions() - return x >= self.offset_x and - x < w-self.offset_x and - y >= self.offset_y and - y < h-self.offset_y - else - return true - end -end - -function ScreenScaler:getMousePosition() - if self.scale then - return self:getPosition(love.mouse.getPosition()) - else - return love.mouse.getPosition() - end -end - -function ScreenScaler:getDimensions() - if self.current_width and self.current_height then - return self.current_width, self.current_height - else - return love.graphics.getDimensions() - end -end - -function ScreenScaler:overrideScaling(width, height) - local sw, sh = love.graphics.getDimensions() - self.scale = math.min(sw / width, sh / height) - self.offset_x = (sw - width * self.scale)/2 - self.offset_y = (sh - height * self.scale)/2 - self.current_width = width - self.current_height = height -end - -function ScreenScaler:start(p1, p2) - local width, height - if type(p1) == "number" and type(p2) == "number" then - width, height = p1, p2 - self.canvas = nil - elseif p1:typeOf("Canvas") then - self.canvas = p1 - width, height = self.canvas:getDimensions() - else - return - end - - local sw, sh = love.graphics.getDimensions() - self.current_width = width - self.current_height = height - self.scale = math.min(sw / width, sh / height) - self.offset_x = (sw - width * self.scale)/2 - self.offset_y = (sh - height * self.scale)/2 - self.active = true - - love.graphics.push() - if self.canvas then - love.graphics.setCanvas(self.canvas) - love.graphics.clear() - else - love.graphics.translate(self.offset_x, self.offset_y) - love.graphics.scale(self.scale) - end -end - -function ScreenScaler:finish() - love.graphics.pop() - if self.canvas then - love.graphics.setCanvas() - love.graphics.setColor(1, 1, 1) - love.graphics.draw(self.canvas, self.offset_x, self.offset_y, 0, self.scale) - else - self:hideBorders() - end - self.canvas = nil - self.active = false -end - -return ScreenScaler diff --git a/src/systems/sprite.lua b/src/systems/sprite.lua deleted file mode 100644 index ffa604e..0000000 --- a/src/systems/sprite.lua +++ /dev/null @@ -1,103 +0,0 @@ -local data = require("data") -local pprint = require("lib.pprint") -local Vec = require("lib.brinevector") -local Sprite = {} - -Sprite.required_groups = {"sprite", "image"} - -function Sprite:addToGroup(group, e) - if group == "sprite" then - local sprite = data.sprites[e.sprite.name] - assert(sprite, ("Attempt to draw unknown sprite: %s"):format(e.sprite.name)) - - for _, slice in ipairs(sprite.slices) do - if slice.user_data and slice.user_data:find("collider") then - self.pool:queue{ - pos = e.pos - Vec(sprite.width, sprite.height)/2, - collider = { - slice.x, - slice.y, - slice.x + slice.width, - slice.y + slice.height - } - } - end - end - elseif group == "image" then - assert(e.image:typeOf("Drawable"), ("Expected sprite to be a drawable or aseprite sprite, got: %s"):format(e.image)) - end -end - -function Sprite:update(dt) - for _, e in ipairs(self.pool.groups.sprite.entities) do - local sprite = data.sprites[e.sprite.name] - if sprite then - local variant = sprite.variants[e.sprite.variant or "default"] - e.sprite.timer = (e.sprite.timer or 0) + dt - - if variant then - local frame = variant[e.sprite.frame] - if not frame then - frame = variant[1] - end - if e.sprite.timer > frame.duration then - e.sprite.timer = e.sprite.timer % 0.1 - e.sprite.frame = (e.sprite.frame or 1) % #variant + 1 - end - end - end - end -end - -function Sprite:draw() - love.graphics.setColor(1, 1, 1) - for _, e in ipairs(self.pool.groups.sprite.entities) do - local sprite = data.sprites[e.sprite.name] - assert(sprite, ("Attempt to draw unknown sprite: %s"):format(e.sprite.name)) - - if not e.hidden then - local variant = sprite.variants[e.sprite.variant or "default"] - local frame_idx = e.sprite.frame or 1 - if variant and frame_idx then - local rotation = nil - local frame = variant[frame_idx] - if not frame then - frame = variant[1] - end - local sx = 1 - if e.sprite.flip then - sx = -1 - end - - if e.sprite.wobble then - rotation = math.sin(love.timer.getTime()*10)*0.15 - end - - love.graphics.draw( - frame.image, - e.pos.x, - e.pos.y, - rotation, - sx, 1, - sprite.width/2, sprite.height/2 - ) - end - end - end - - for _, e in ipairs(self.pool.groups.image.entities) do - if not e.hidden then - local width, height = e.image:getDimensions() - love.graphics.draw( - e.image, - e.pos.x, - e.pos.y, - 0, - 1, 1, - width/2, height/2 - ) - end - end -end - -return Sprite diff --git a/src/systems/ui.lua b/src/systems/ui.lua deleted file mode 100644 index 5eaf4f2..0000000 --- a/src/systems/ui.lua +++ /dev/null @@ -1,111 +0,0 @@ -local UI = {} -local data = require("data") -local rgb = require("helpers.rgb") - -local font = data.fonts["kenney-future"](16) -local on_press_sound = data.audio["switch-5"] -local on_release_sound = data.audio["switch-7"] - -UI.required_groups = {"ui_button", "player"} - -local BUTTON_TIME = 1.2 - -function UI:addToGroup(group, e) - if group == "ui_button" then - e.pressed_timer = 0 - end -end - -function UI:update(dt) - local physics = self.pool:getSystem(require("systems.physics")) - - for _, btn in ipairs(self.pool.groups.ui_button.entities) do - local collisions = physics:queryRect(btn.pos, btn.size) - local pressed = #collisions > 0 - if btn.pressed then - btn.pressed_timer = math.min(btn.pressed_timer + dt, BUTTON_TIME) - elseif btn.pressed_timer and btn.pressed_timer > 0 then - btn.pressed_timer = math.max(0, btn.pressed_timer - dt * 3) - end - - if btn.pressed_timer == BUTTON_TIME and not btn.event_emitted then - btn.event_emitted = true - self.pool:emit("on_btn_pressed", btn) - elseif btn.pressed_timer < BUTTON_TIME and btn.event_emitted then - btn.event_emitted = nil - self.pool:emit("on_btn_released", btn) - end - - if not btn.pressed and pressed then - on_press_sound:play() - elseif btn.pressed and not pressed then - on_release_sound:play() - end - btn.pressed = pressed - end -end - -local mask_shader = love.graphics.newShader[[ - extern number offset_x; - extern number scale; - extern number threshold; - extern vec4 mask_color; - vec4 effect(vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords) - { - vec4 texturecolor = Texel(tex, texture_coords); - if (screen_coords.x > threshold * scale + offset_x) { - return texturecolor * color; - } else { - return texturecolor * mask_color; - } - } -]] - -function UI:draw() - love.graphics.setFont(font) - local ScreenScaler = self.pool:getSystem(require("systems.screen-scaler")) - - mask_shader:send("mask_color", rgb(10, 250, 20)) - if ScreenScaler.active and not ScreenScaler.canvas then - mask_shader:send("scale", ScreenScaler.scale) - mask_shader:send("offset_x", ScreenScaler.offset_x) - else - mask_shader:send("scale", 1) - mask_shader:send("offset_x", 0) - end - - for _, btn in ipairs(self.pool.groups.ui_button.entities) do - local panel = data.ui.panels["blue"] - local oy = 0 - local r = 0 - if btn.pressed then - panel = data.ui.panels["blue-pressed"] - oy = 2 - r = math.sin(love.timer.getTime()*20)*0.15 - end - - love.graphics.setColor(1, 1, 1) - panel:draw(btn.pos.x, btn.pos.y, btn.size.x, btn.size.y) - local cx, cy, cw, ch = panel:getContentWindow() - - local text_width = font:getWidth(btn.text) - local text_height = font:getHeight(btn.text) - local threshold = math.min(1, (btn.pressed_timer or 0) / BUTTON_TIME) - mask_shader:send("threshold", cx+threshold*text_width + (cw-text_width)/2) - love.graphics.setShader(mask_shader) - love.graphics.printf( - btn.text, - cx+cw/2, - cy+ch/2+oy, - cw, - "left", - r, - 1, 1, - text_width/2, - text_height/2 - ) - love.graphics.setShader() - end -end - -return UI