1
0

start from scratch

This commit is contained in:
Rokas Puzonas 2022-11-19 12:15:25 +02:00
parent 0066f8ba0a
commit cc709ba84a
50 changed files with 6 additions and 6948 deletions

View File

@ -1,11 +1,9 @@
function love.conf(t)
t.title = "Love2D project"
t.title = "Dodge Bolt"
t.console = true
t.window.width = 854
t.window.height = 480
t.window.resizable = true
t.modules.joystick = false
end

Binary file not shown.

Binary file not shown.

View File

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

Binary file not shown.

View File

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

View File

@ -1,11 +0,0 @@
{
"automappingRulesFile": "",
"commands": [
],
"extensionsPath": "extensions",
"folders": [
"."
],
"propertyTypes": [
]
}

View File

@ -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 = {}
}
}
}
}
}

View File

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

View File

@ -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 = {}
}
}
}
}
}

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 657 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 B

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -1,18 +1,11 @@
local Gamestate = require("lib.hump.gamestate")
local binser = require("lib.binser")
local Vec = require("lib.brinevector")
pprint = require("lib.pprint")
binser.registerStruct("brinevector",
function(v) return v.x, v.y end,
function(x, y) return Vec(x, y) end
)
function love.load()
love.math.setRandomSeed(love.timer.getTime())
love.keyboard.setKeyRepeat(true)
math.randomseed(love.timer.getTime())
Gamestate.switch(require("states.main-menu"))
Gamestate.switch(require("states.main"))
Gamestate.registerEvents()
end

View File

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

View File

@ -1,56 +1,12 @@
local MainState = {}
local pprint = require("lib.pprint")
local nata = require("lib.nata")
local data = require("data")
function MainState:enter(_, host_socket)
local systems = {
require("systems.physics"),
require("systems.map"),
require("systems.sprite"),
require("systems.player"),
require("systems.bolt"),
require("systems.screen-scaler")
}
if not love.filesystem.isFused() then
table.insert(systems, require("systems.debug"))
end
if host_socket then
table.insert(systems, require("systems.multiplayer"))
end
function MainState:enter()
self.ecs = nata.new{
available_groups = require("groups"),
systems = systems,
data = {
host_socket = host_socket,
map = "data/maps/playground.lua"
}
groups = {},
systems = {},
data = {}
}
self.downscaled_canvas = nil
self:refreshScaler()
self.ecs:on("onMapSwitch", function(map)
self:refreshScaler(map)
end)
-- Spawn in the entity that the local player will be using
local PlayerSystem = self.ecs:getSystem(require("systems.player"))
local player = PlayerSystem:spawnPlayer()
player.controllable = true
end
function MainState:refreshScaler(map)
if not map then
local Map = self.ecs:getSystem(require("systems.map"))
map = Map.map
end
self.screenWidth = map.width * map.tilewidth
self.screenHeight = map.height * map.tileheight
end
function MainState:update(dt)
@ -62,37 +18,12 @@ function MainState:update(dt)
end
end
function MainState:cleanup()
local multiplayer = self.ecs:getSystem(require("systems.multiplayer"))
if multiplayer then
multiplayer:disconnectPeers()
end
end
function MainState:quit()
self:cleanup()
end
function MainState:mousemoved(x, y, dx, dy, istouch)
local ScreenScaler = self.ecs:getSystem(require("systems.screen-scaler"))
if not ScreenScaler:isInBounds(x, y) then return end
x, y = ScreenScaler:getPosition(x, y)
dx = (ScreenScaler.scale or 1) * dx
dy = (ScreenScaler.scale or 1) * dy
self.ecs:emit("mousemoved", x, y, dx, dy, istouch)
end
function MainState:keypressed(...)
self.ecs:emit("keypressed", ...)
end
function MainState:draw()
local ScreenScaler = self.ecs:getSystem(require("systems.screen-scaler"))
ScreenScaler:start(self.screenWidth, self.screenHeight)
self.ecs:emit("draw")
ScreenScaler:finish()
end
return MainState

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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