start from scratch
@ -1,11 +1,9 @@
|
|||||||
function love.conf(t)
|
function love.conf(t)
|
||||||
t.title = "Love2D project"
|
t.title = "Dodge Bolt"
|
||||||
|
|
||||||
t.console = true
|
t.console = true
|
||||||
|
|
||||||
t.window.width = 854
|
t.window.width = 854
|
||||||
t.window.height = 480
|
t.window.height = 480
|
||||||
t.window.resizable = true
|
t.window.resizable = true
|
||||||
|
|
||||||
t.modules.joystick = false
|
|
||||||
end
|
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 Gamestate = require("lib.hump.gamestate")
|
||||||
local binser = require("lib.binser")
|
|
||||||
local Vec = require("lib.brinevector")
|
|
||||||
pprint = require("lib.pprint")
|
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()
|
function love.load()
|
||||||
love.math.setRandomSeed(love.timer.getTime())
|
love.math.setRandomSeed(love.timer.getTime())
|
||||||
love.keyboard.setKeyRepeat(true)
|
love.keyboard.setKeyRepeat(true)
|
||||||
math.randomseed(love.timer.getTime())
|
math.randomseed(love.timer.getTime())
|
||||||
|
|
||||||
Gamestate.switch(require("states.main-menu"))
|
Gamestate.switch(require("states.main"))
|
||||||
Gamestate.registerEvents()
|
Gamestate.registerEvents()
|
||||||
end
|
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 MainState = {}
|
||||||
local pprint = require("lib.pprint")
|
|
||||||
local nata = require("lib.nata")
|
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{
|
self.ecs = nata.new{
|
||||||
available_groups = require("groups"),
|
groups = {},
|
||||||
systems = systems,
|
systems = {},
|
||||||
data = {
|
data = {}
|
||||||
host_socket = host_socket,
|
|
||||||
map = "data/maps/playground.lua"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
end
|
||||||
|
|
||||||
function MainState:update(dt)
|
function MainState:update(dt)
|
||||||
@ -62,37 +18,12 @@ function MainState:update(dt)
|
|||||||
end
|
end
|
||||||
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(...)
|
function MainState:keypressed(...)
|
||||||
self.ecs:emit("keypressed", ...)
|
self.ecs:emit("keypressed", ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
function MainState:draw()
|
function MainState:draw()
|
||||||
local ScreenScaler = self.ecs:getSystem(require("systems.screen-scaler"))
|
|
||||||
|
|
||||||
ScreenScaler:start(self.screenWidth, self.screenHeight)
|
|
||||||
self.ecs:emit("draw")
|
self.ecs:emit("draw")
|
||||||
ScreenScaler:finish()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return MainState
|
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
|
|