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 @@
-
-
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 @@
-
-
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