start from scratch
@ -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
|
||||
|
@ -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"
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"automappingRulesFile": "",
|
||||
"commands": [
|
||||
],
|
||||
"extensionsPath": "extensions",
|
||||
"folders": [
|
||||
"."
|
||||
],
|
||||
"propertyTypes": [
|
||||
]
|
||||
}
|
@ -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 = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.9" tiledversion="1.9.0" orientation="orthogonal" renderorder="right-down" width="16" height="9" tilewidth="16" tileheight="16" infinite="0" nextlayerid="5" nextobjectid="13">
|
||||
<editorsettings>
|
||||
<export target="main-menu.lua" format="lua"/>
|
||||
</editorsettings>
|
||||
<tileset firstgid="1" source="../tilesets/roguelike-dungeon.tsx"/>
|
||||
<layer id="1" name="ground" width="16" height="9">
|
||||
<data encoding="csv">
|
||||
68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,
|
||||
68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,
|
||||
68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,
|
||||
68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,
|
||||
68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,
|
||||
68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,
|
||||
68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,
|
||||
68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,
|
||||
68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="3" name="decorations" width="16" height="9">
|
||||
<data encoding="csv">
|
||||
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
|
||||
</data>
|
||||
</layer>
|
||||
<objectgroup id="2" name="metadata">
|
||||
<object id="2" name="Spawnpoint" x="140.167" y="90.1667">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="6" name="title" x="128" y="16" width="96" height="48">
|
||||
<properties>
|
||||
<property name="sprite" value="title"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="7" name="Host" class="button" x="16" y="16" width="96" height="32"/>
|
||||
<object id="8" name="Join" class="button" x="16" y="56" width="96" height="32"/>
|
||||
<object id="9" name="Quit" class="button" x="16" y="96" width="96" height="32"/>
|
||||
<object id="12" name="controls" x="225" y="111.667">
|
||||
<point/>
|
||||
</object>
|
||||
</objectgroup>
|
||||
</map>
|
@ -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 = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.9" tiledversion="1.9.0" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="16" tileheight="16" infinite="0" nextlayerid="5" nextobjectid="4">
|
||||
<editorsettings>
|
||||
<export target="playground.lua" format="lua"/>
|
||||
</editorsettings>
|
||||
<tileset firstgid="1" source="../tilesets/roguelike-dungeon.tsx"/>
|
||||
<layer id="3" name="ground" width="30" height="20">
|
||||
<data encoding="csv">
|
||||
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
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="1" name="walls" width="30" height="20">
|
||||
<data encoding="csv">
|
||||
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
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="4" name="decorations" width="30" height="20">
|
||||
<data encoding="csv">
|
||||
0,0,0,0,0,0,0,0,0,0,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
|
||||
</data>
|
||||
</layer>
|
||||
<objectgroup id="2" name="spawnpoints">
|
||||
<object id="1" x="67.3333" y="173.333">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="2" x="409.333" y="163.333">
|
||||
<point/>
|
||||
</object>
|
||||
</objectgroup>
|
||||
</map>
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 30 KiB |
@ -1,87 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<tileset version="1.9" tiledversion="1.9.0" name="roguelike-dungeon" tilewidth="16" tileheight="16" spacing="1" tilecount="522" columns="29">
|
||||
<editorsettings>
|
||||
<export target="roguelike-dungeon.lua" format="lua"/>
|
||||
</editorsettings>
|
||||
<image source="roguelike-dungeon.png" width="492" height="305"/>
|
||||
<tile id="8">
|
||||
<properties>
|
||||
<property name="collidable" type="bool" value="true"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="9">
|
||||
<properties>
|
||||
<property name="collidable" type="bool" value="true"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="10">
|
||||
<properties>
|
||||
<property name="collidable" type="bool" value="true"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="11">
|
||||
<properties>
|
||||
<property name="collidable" type="bool" value="true"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="12">
|
||||
<properties>
|
||||
<property name="collidable" type="bool" value="true"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="13">
|
||||
<properties>
|
||||
<property name="collidable" type="bool" value="true"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="14">
|
||||
<properties>
|
||||
<property name="collidable" type="bool" value="true"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="15">
|
||||
<properties>
|
||||
<property name="collidable" type="bool" value="true"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="37">
|
||||
<properties>
|
||||
<property name="collidable" type="bool" value="true"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="38">
|
||||
<properties>
|
||||
<property name="collidable" type="bool" value="true"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="39">
|
||||
<properties>
|
||||
<property name="collidable" type="bool" value="true"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="40">
|
||||
<properties>
|
||||
<property name="collidable" type="bool" value="true"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="41">
|
||||
<properties>
|
||||
<property name="collidable" type="bool" value="true"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="42">
|
||||
<properties>
|
||||
<property name="collidable" type="bool" value="true"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="43">
|
||||
<properties>
|
||||
<property name="collidable" type="bool" value="true"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="44">
|
||||
<properties>
|
||||
<property name="collidable" type="bool" value="true"/>
|
||||
</properties>
|
||||
</tile>
|
||||
</tileset>
|
Before Width: | Height: | Size: 20 KiB |
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<tileset version="1.9" tiledversion="1.9.0" name="roguelike-indoors" tilewidth="16" tileheight="16" spacing="1" tilecount="486" columns="27">
|
||||
<image source="roguelike-indoors.png" width="458" height="305"/>
|
||||
</tileset>
|
Before Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 177 B |
Before Width: | Height: | Size: 173 B |
Before Width: | Height: | Size: 181 B |
Before Width: | Height: | Size: 657 B |
Before Width: | Height: | Size: 175 B |
@ -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"}}
|
||||
}
|
@ -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
|
@ -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()
|
@ -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
|
388
src/lib/json.lua
@ -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
|
@ -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
|
@ -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
|
1631
src/lib/sti/init.lua
@ -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
|
@ -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
|
@ -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
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|