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 799e327..0000000 Binary files a/src/data/audio/switch-5.ogg and /dev/null differ diff --git a/src/data/audio/switch-7.ogg b/src/data/audio/switch-7.ogg deleted file mode 100644 index 856a60d..0000000 Binary files a/src/data/audio/switch-7.ogg and /dev/null differ 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 1dbb2dd..0000000 Binary files a/src/data/fonts/kenney-future.ttf and /dev/null differ 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 cf7ba3f..0000000 Binary files a/src/data/sprites/bolt-ghost.aseprite and /dev/null differ diff --git a/src/data/sprites/bolt.aseprite b/src/data/sprites/bolt.aseprite deleted file mode 100644 index 0cfec69..0000000 Binary files a/src/data/sprites/bolt.aseprite and /dev/null differ diff --git a/src/data/sprites/player.aseprite b/src/data/sprites/player.aseprite deleted file mode 100644 index 5c3a681..0000000 Binary files a/src/data/sprites/player.aseprite and /dev/null differ diff --git a/src/data/sprites/title.aseprite b/src/data/sprites/title.aseprite deleted file mode 100644 index 2531528..0000000 Binary files a/src/data/sprites/title.aseprite and /dev/null differ 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 5d1bc64..0000000 Binary files a/src/data/tilesets/roguelike-dungeon.png and /dev/null differ 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 c6046fd..0000000 Binary files a/src/data/tilesets/roguelike-indoors.png and /dev/null differ 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 6ea7327..0000000 Binary files a/src/data/ui/panels/blue-pressed.9.png and /dev/null differ diff --git a/src/data/ui/panels/blue.9.png b/src/data/ui/panels/blue.9.png deleted file mode 100644 index d199a46..0000000 Binary files a/src/data/ui/panels/blue.9.png and /dev/null differ diff --git a/src/data/ui/prompts/dark/a.png b/src/data/ui/prompts/dark/a.png deleted file mode 100644 index 3ae68ca..0000000 Binary files a/src/data/ui/prompts/dark/a.png and /dev/null differ diff --git a/src/data/ui/prompts/dark/d.png b/src/data/ui/prompts/dark/d.png deleted file mode 100644 index c238b13..0000000 Binary files a/src/data/ui/prompts/dark/d.png and /dev/null differ diff --git a/src/data/ui/prompts/dark/s.png b/src/data/ui/prompts/dark/s.png deleted file mode 100644 index 500206c..0000000 Binary files a/src/data/ui/prompts/dark/s.png and /dev/null differ diff --git a/src/data/ui/prompts/dark/spacebar.png b/src/data/ui/prompts/dark/spacebar.png deleted file mode 100644 index 40925b2..0000000 Binary files a/src/data/ui/prompts/dark/spacebar.png and /dev/null differ diff --git a/src/data/ui/prompts/dark/w.png b/src/data/ui/prompts/dark/w.png deleted file mode 100644 index 46ee011..0000000 Binary files a/src/data/ui/prompts/dark/w.png and /dev/null differ 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