first commit
14
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lua",
|
||||
"request": "launch",
|
||||
"name": "Debug",
|
||||
"program": "${workspaceFolder}/main.lua"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
fonts/font.ttf
Normal file
BIN
graphics/PixelUIpack/9-Slice/Ancient/brown.png
Executable file
|
After Width: | Height: | Size: 274 B |
BIN
graphics/PixelUIpack/9-Slice/Ancient/brown_inlay.png
Executable file
|
After Width: | Height: | Size: 222 B |
BIN
graphics/PixelUIpack/9-Slice/Ancient/brown_pressed.png
Executable file
|
After Width: | Height: | Size: 255 B |
BIN
graphics/PixelUIpack/9-Slice/Ancient/grey.png
Executable file
|
After Width: | Height: | Size: 272 B |
BIN
graphics/PixelUIpack/9-Slice/Ancient/grey_inlay.png
Executable file
|
After Width: | Height: | Size: 221 B |
BIN
graphics/PixelUIpack/9-Slice/Ancient/grey_pressed.png
Executable file
|
After Width: | Height: | Size: 254 B |
BIN
graphics/PixelUIpack/9-Slice/Ancient/tan.png
Executable file
|
After Width: | Height: | Size: 274 B |
BIN
graphics/PixelUIpack/9-Slice/Ancient/tan_inlay.png
Executable file
|
After Width: | Height: | Size: 223 B |
BIN
graphics/PixelUIpack/9-Slice/Ancient/tan_pressed.png
Executable file
|
After Width: | Height: | Size: 256 B |
BIN
graphics/PixelUIpack/9-Slice/Ancient/white.png
Executable file
|
After Width: | Height: | Size: 265 B |
BIN
graphics/PixelUIpack/9-Slice/Ancient/white_inlay.png
Executable file
|
After Width: | Height: | Size: 223 B |
BIN
graphics/PixelUIpack/9-Slice/Ancient/white_pressed.png
Executable file
|
After Width: | Height: | Size: 249 B |
BIN
graphics/PixelUIpack/9-Slice/Colored/blue.png
Executable file
|
After Width: | Height: | Size: 230 B |
BIN
graphics/PixelUIpack/9-Slice/Colored/blue_pressed.png
Executable file
|
After Width: | Height: | Size: 214 B |
BIN
graphics/PixelUIpack/9-Slice/Colored/green.png
Executable file
|
After Width: | Height: | Size: 230 B |
BIN
graphics/PixelUIpack/9-Slice/Colored/green_pressed.png
Executable file
|
After Width: | Height: | Size: 214 B |
BIN
graphics/PixelUIpack/9-Slice/Colored/grey.png
Executable file
|
After Width: | Height: | Size: 216 B |
BIN
graphics/PixelUIpack/9-Slice/Colored/grey_pressed.png
Executable file
|
After Width: | Height: | Size: 201 B |
BIN
graphics/PixelUIpack/9-Slice/Colored/red.png
Executable file
|
After Width: | Height: | Size: 230 B |
BIN
graphics/PixelUIpack/9-Slice/Colored/red_pressed.png
Executable file
|
After Width: | Height: | Size: 214 B |
BIN
graphics/PixelUIpack/9-Slice/Colored/yellow.png
Executable file
|
After Width: | Height: | Size: 221 B |
BIN
graphics/PixelUIpack/9-Slice/Colored/yellow_pressed.png
Executable file
|
After Width: | Height: | Size: 206 B |
BIN
graphics/PixelUIpack/9-Slice/Outline/blue.png
Executable file
|
After Width: | Height: | Size: 221 B |
BIN
graphics/PixelUIpack/9-Slice/Outline/blue_pressed.png
Executable file
|
After Width: | Height: | Size: 207 B |
BIN
graphics/PixelUIpack/9-Slice/Outline/green.png
Executable file
|
After Width: | Height: | Size: 221 B |
BIN
graphics/PixelUIpack/9-Slice/Outline/green_pressed.png
Executable file
|
After Width: | Height: | Size: 207 B |
BIN
graphics/PixelUIpack/9-Slice/Outline/red.png
Executable file
|
After Width: | Height: | Size: 221 B |
BIN
graphics/PixelUIpack/9-Slice/Outline/red_pressed.png
Executable file
|
After Width: | Height: | Size: 207 B |
BIN
graphics/PixelUIpack/9-Slice/Outline/yellow.png
Executable file
|
After Width: | Height: | Size: 219 B |
BIN
graphics/PixelUIpack/9-Slice/Outline/yellow_pressed.png
Executable file
|
After Width: | Height: | Size: 203 B |
BIN
graphics/PixelUIpack/9-Slice/list.png
Executable file
|
After Width: | Height: | Size: 213 B |
BIN
graphics/PixelUIpack/9-Slice/space.png
Executable file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
graphics/PixelUIpack/9-Slice/space_inlay.png
Executable file
|
After Width: | Height: | Size: 1019 B |
2
graphics/PixelUIpack/Donate.url
Executable file
@@ -0,0 +1,2 @@
|
||||
[InternetShortcut]
|
||||
URL=http://donate.kenney.nl/
|
||||
2
graphics/PixelUIpack/Facebook.url
Executable file
@@ -0,0 +1,2 @@
|
||||
[InternetShortcut]
|
||||
URL=https://www.facebook.com/KenneyNL
|
||||
23
graphics/PixelUIpack/License.txt
Executable file
@@ -0,0 +1,23 @@
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
Pixel UI pack
|
||||
by Kenney Vleugels for Kenney (www.kenney.nl)
|
||||
with help by Lynn Evers (Twitter: @EversLynn)
|
||||
|
||||
------------------------------
|
||||
|
||||
License (Creative Commons Zero, CC0)
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
You may use these graphics in personal and commercial projects.
|
||||
Credit (Kenney or www.kenney.nl) would be nice but is not mandatory.
|
||||
|
||||
------------------------------
|
||||
|
||||
Donate: http://donate.kenney.nl/
|
||||
Request: http://request.kenney.nl/
|
||||
|
||||
|
||||
###############################################################################
|
||||
BIN
graphics/PixelUIpack/Preview.png
Executable file
|
After Width: | Height: | Size: 102 KiB |
BIN
graphics/PixelUIpack/Spritesheet/UIpackSheet_magenta.png
Executable file
|
After Width: | Height: | Size: 28 KiB |
BIN
graphics/PixelUIpack/Spritesheet/UIpackSheet_transparent.png
Executable file
|
After Width: | Height: | Size: 40 KiB |
6
graphics/PixelUIpack/Spritesheet/spritesheetInfo.txt
Executable file
@@ -0,0 +1,6 @@
|
||||
The tiles are 16 x 16px and have a 2px margin between them.
|
||||
|
||||
---
|
||||
|
||||
TILE SIZE: 16 x 16
|
||||
MARGIN: 2
|
||||
BIN
graphics/entities.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
graphics/entities_numbered.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
graphics/pokemon/aardart-back.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
graphics/pokemon/aardart-front.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
graphics/pokemon/agnite-back.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
graphics/pokemon/agnite-front.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
graphics/pokemon/anoleaf-back.png
Normal file
|
After Width: | Height: | Size: 917 B |
BIN
graphics/pokemon/anoleaf-front.png
Normal file
|
After Width: | Height: | Size: 861 B |
BIN
graphics/pokemon/bamboon-back.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
graphics/pokemon/bamboon-front.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
graphics/pokemon/cardiwing-back.png
Normal file
|
After Width: | Height: | Size: 811 B |
BIN
graphics/pokemon/cardiwing-front.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
graphics/sheet.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
graphics/sheet_numbered.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
100
lib/class.lua
Normal file
@@ -0,0 +1,100 @@
|
||||
-- require('mobdebug').start()
|
||||
|
||||
--[[
|
||||
Copyright (c) 2010-2013 Matthias Richter
|
||||
|
||||
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.
|
||||
|
||||
Except as contained in this notice, the name(s) of the above copyright holders
|
||||
shall not be used in advertising or otherwise to promote the sale, use or
|
||||
other dealings in this Software without prior written authorization.
|
||||
|
||||
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 function include_helper(to, from, seen)
|
||||
if from == nil then
|
||||
return to
|
||||
elseif type(from) ~= 'table' then
|
||||
return from
|
||||
elseif seen[from] then
|
||||
return seen[from]
|
||||
end
|
||||
|
||||
seen[from] = to
|
||||
for k,v in pairs(from) do
|
||||
k = include_helper({}, k, seen) -- keys might also be tables
|
||||
if to[k] == nil then
|
||||
to[k] = include_helper({}, v, seen)
|
||||
end
|
||||
end
|
||||
return to
|
||||
end
|
||||
|
||||
-- deeply copies `other' into `class'. keys in `other' that are already
|
||||
-- defined in `class' are omitted
|
||||
local function include(class, other)
|
||||
return include_helper(class, other, {})
|
||||
end
|
||||
|
||||
-- returns a deep copy of `other'
|
||||
local function clone(other)
|
||||
return setmetatable(include({}, other), getmetatable(other))
|
||||
end
|
||||
|
||||
local function new(class)
|
||||
-- mixins
|
||||
class = class or {} -- class can be nil
|
||||
local inc = class.__includes or {}
|
||||
if getmetatable(inc) then inc = {inc} end
|
||||
|
||||
for _, other in ipairs(inc) do
|
||||
if type(other) == "string" then
|
||||
other = _G[other]
|
||||
end
|
||||
include(class, other)
|
||||
end
|
||||
|
||||
-- class implementation
|
||||
class.__index = class
|
||||
class.init = class.init or class[1] or function() end
|
||||
class.include = class.include or include
|
||||
class.clone = class.clone or clone
|
||||
|
||||
-- constructor call
|
||||
return setmetatable(class, {__call = function(c, ...)
|
||||
local o = setmetatable({}, c)
|
||||
o:init(...)
|
||||
return o
|
||||
end})
|
||||
end
|
||||
|
||||
-- interface for cross class-system compatibility (see https://github.com/bartbes/Class-Commons).
|
||||
if class_commons ~= false and not common then
|
||||
common = {}
|
||||
function common.class(name, prototype, parent)
|
||||
return new{__includes = {prototype, parent}}
|
||||
end
|
||||
function common.instance(class, ...)
|
||||
return class(...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- the module
|
||||
return setmetatable({new = new, include = include, clone = clone},
|
||||
{__call = function(_,...) return new(...) end})
|
||||
15
lib/knife/base.lua
Normal file
@@ -0,0 +1,15 @@
|
||||
return {
|
||||
extend = function (self, subtype)
|
||||
subtype = subtype or {}
|
||||
local meta = { __index = subtype }
|
||||
return setmetatable(subtype, {
|
||||
__index = self,
|
||||
__call = function (self, ...)
|
||||
local instance = setmetatable({}, meta)
|
||||
return instance, instance:constructor(...)
|
||||
end
|
||||
})
|
||||
end,
|
||||
constructor = function () end,
|
||||
}
|
||||
|
||||
78
lib/knife/behavior.lua
Normal file
@@ -0,0 +1,78 @@
|
||||
-- behavior.lua -- a state manager
|
||||
|
||||
-- internal/external api
|
||||
|
||||
local function getCurrentFrame (behavior)
|
||||
return behavior.states[behavior.state][behavior.index]
|
||||
end
|
||||
|
||||
local function advanceFrame (behavior)
|
||||
local nextState = behavior.frame.after
|
||||
local nextIndex = behavior.index + 1
|
||||
local maxIndex = #behavior.states[behavior.state]
|
||||
|
||||
if nextState then
|
||||
behavior.state = nextState
|
||||
nextIndex = 1
|
||||
elseif nextIndex > maxIndex then
|
||||
nextIndex = 1
|
||||
end
|
||||
|
||||
behavior.index = nextIndex
|
||||
behavior.frame = behavior:getCurrentFrame()
|
||||
end
|
||||
|
||||
local function performAction (behavior)
|
||||
local act = behavior.frame.action
|
||||
|
||||
if act then
|
||||
act(behavior, behavior.subject)
|
||||
end
|
||||
end
|
||||
|
||||
-- external api
|
||||
|
||||
local function update (behavior, dt)
|
||||
behavior.elapsed = behavior.elapsed + dt
|
||||
|
||||
while behavior.elapsed >= behavior.frame.duration do
|
||||
behavior.elapsed = behavior.elapsed - behavior.frame.duration
|
||||
behavior:advanceFrame()
|
||||
behavior:performAction()
|
||||
end
|
||||
|
||||
return behavior
|
||||
end
|
||||
|
||||
local function setState (behavior, state, index)
|
||||
behavior.state = state
|
||||
behavior.index = index or 1
|
||||
behavior.elapsed = 0
|
||||
behavior.frame = behavior:getCurrentFrame()
|
||||
behavior:performAction()
|
||||
return behavior
|
||||
end
|
||||
|
||||
-- behavior factory
|
||||
|
||||
return function (states, subject)
|
||||
local behavior = {
|
||||
states = states,
|
||||
subject = subject,
|
||||
elapsed = 0,
|
||||
state = 'default',
|
||||
index = 1,
|
||||
-- internal api
|
||||
getCurrentFrame = getCurrentFrame,
|
||||
advanceFrame = advanceFrame,
|
||||
performAction = performAction,
|
||||
-- external api
|
||||
update = update,
|
||||
setState = setState
|
||||
}
|
||||
|
||||
behavior.frame = behavior:getCurrentFrame()
|
||||
behavior:performAction()
|
||||
|
||||
return behavior
|
||||
end
|
||||
27
lib/knife/bind.lua
Normal file
@@ -0,0 +1,27 @@
|
||||
local loadstring = _G.loadstring or _G.load
|
||||
local tconcat = table.concat
|
||||
|
||||
local helperCache = {}
|
||||
|
||||
local function buildHelper (argCount)
|
||||
if helperCache[argCount] then
|
||||
return helperCache[argCount]
|
||||
end
|
||||
local argList1 = { 'f' }
|
||||
local argList2 = {}
|
||||
for index = 1, argCount do
|
||||
argList1[index + 1] = 'a' .. index
|
||||
argList2[index] = 'a' .. index
|
||||
end
|
||||
argList2[argCount + 1] = '...'
|
||||
local source = 'return function(' .. tconcat(argList1, ', ') ..
|
||||
') return function(...) return f(' .. tconcat(argList2, ', ') ..
|
||||
') end end'
|
||||
local helper = loadstring(source)()
|
||||
helperCache[argCount] = helper
|
||||
return helper
|
||||
end
|
||||
|
||||
return function (func, ...)
|
||||
return buildHelper(select('#', ...))(func, ...)
|
||||
end
|
||||
31
lib/knife/chain.lua
Normal file
@@ -0,0 +1,31 @@
|
||||
local function Invoker (links, index)
|
||||
return function (...)
|
||||
local link = links[index]
|
||||
if not link then
|
||||
return
|
||||
end
|
||||
local continue = Invoker(links, index + 1)
|
||||
local returned = link(continue, ...)
|
||||
if returned then
|
||||
returned(function (_, ...) continue(...) end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return function (...)
|
||||
local links = { ... }
|
||||
|
||||
local function chain (...)
|
||||
if not (...) then
|
||||
return Invoker(links, 1)(select(2, ...))
|
||||
end
|
||||
local offset = #links
|
||||
for index = 1, select('#', ...) do
|
||||
links[offset + index] = select(index, ...)
|
||||
end
|
||||
return chain
|
||||
end
|
||||
|
||||
return chain
|
||||
end
|
||||
|
||||
54
lib/knife/convoke.lua
Normal file
@@ -0,0 +1,54 @@
|
||||
return function (routine)
|
||||
local routines = { routine }
|
||||
local routineIndex = 1
|
||||
local isFinished = false
|
||||
|
||||
local function execute ()
|
||||
local continueCount = 0
|
||||
local run
|
||||
|
||||
local function continue ()
|
||||
continueCount = continueCount + 1
|
||||
return function (...)
|
||||
continueCount = continueCount - 1
|
||||
if continueCount == 0 then
|
||||
return run(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function wait (...)
|
||||
return coroutine.yield(...)
|
||||
end
|
||||
|
||||
local r = coroutine.create(function ()
|
||||
isFinished = false
|
||||
while routineIndex <= #routines do
|
||||
routines[routineIndex](continue, wait)
|
||||
continueCount = 0
|
||||
routineIndex = routineIndex + 1
|
||||
end
|
||||
isFinished = true
|
||||
end)
|
||||
|
||||
run = function (...)
|
||||
return coroutine.resume(r, ...)
|
||||
end
|
||||
|
||||
run()
|
||||
end
|
||||
|
||||
local function appendOrExecute (routine)
|
||||
if routine then
|
||||
routines[#routines + 1] = routine
|
||||
if isFinished then
|
||||
execute()
|
||||
end
|
||||
return appendOrExecute
|
||||
else
|
||||
execute()
|
||||
end
|
||||
end
|
||||
|
||||
return appendOrExecute
|
||||
end
|
||||
111
lib/knife/event.lua
Normal file
@@ -0,0 +1,111 @@
|
||||
-- require('mobdebug').start()
|
||||
|
||||
-- Event module
|
||||
local Event = {}
|
||||
|
||||
-- Event handler registry
|
||||
Event.handlers = {}
|
||||
|
||||
-- Remove an event handler from the registry
|
||||
local function remove (self)
|
||||
if not self.isRegistered then
|
||||
return self
|
||||
end
|
||||
if self.prevHandler then
|
||||
self.prevHandler.nextHandler = self.nextHandler
|
||||
end
|
||||
if self.nextHandler then
|
||||
self.nextHandler.prevHandler = self.prevHandler
|
||||
end
|
||||
if Event.handlers[self.name] == self then
|
||||
Event.handlers[self.name] = self.nextHandler
|
||||
end
|
||||
self.prevHandler = nil
|
||||
self.nextHandler = nil
|
||||
self.isRegistered = false
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- Insert an event handler into the registry
|
||||
local function register (self)
|
||||
if self.isRegistered then
|
||||
return self
|
||||
end
|
||||
self.nextHandler = Event.handlers[self.name]
|
||||
if self.nextHandler then
|
||||
self.nextHandler.prevHandler = self
|
||||
end
|
||||
Event.handlers[self.name] = self
|
||||
self.isRegistered = true
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- Create an event handler
|
||||
local function Handler (name, callback)
|
||||
return {
|
||||
name = name,
|
||||
callback = callback,
|
||||
isRegistered = false,
|
||||
remove = remove,
|
||||
register = register
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- Create and register a new event handler
|
||||
function Event.on (name, callback)
|
||||
return register(Handler(name, callback))
|
||||
end
|
||||
|
||||
-- Dispatch an event
|
||||
function Event.dispatch (name, ...)
|
||||
local handler = Event.handlers[name]
|
||||
|
||||
while handler do
|
||||
if handler.callback(...) == false then
|
||||
return handler
|
||||
end
|
||||
handler = handler.nextHandler
|
||||
end
|
||||
end
|
||||
|
||||
local function isCallable (value)
|
||||
return type(value) == 'function' or
|
||||
getmetatable(value) and getmetatable(value).__call
|
||||
end
|
||||
|
||||
-- Inject a dispatcher into a table.
|
||||
local function hookDispatcher (t, key)
|
||||
local original = t[key]
|
||||
|
||||
if isCallable(original) then
|
||||
t[key] = function (...)
|
||||
original(...)
|
||||
return Event.dispatch(key, ...)
|
||||
end
|
||||
else
|
||||
t[key] = function (...)
|
||||
return Event.dispatch(key, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Inject dispatchers into a table. Examples:
|
||||
-- Event.hook(love.handlers)
|
||||
-- Event.hook(love, { 'load', 'update', 'draw' })
|
||||
function Event.hook (t, keys)
|
||||
if keys then
|
||||
for _, key in ipairs(keys) do
|
||||
hookDispatcher(t, key)
|
||||
end
|
||||
else
|
||||
for key in pairs(t) do
|
||||
hookDispatcher(t, key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Event
|
||||
2
lib/knife/gun.lua
Normal file
@@ -0,0 +1,2 @@
|
||||
-- GUN (Give Up Now)
|
||||
os.exit()
|
||||
77
lib/knife/memoize.lua
Normal file
@@ -0,0 +1,77 @@
|
||||
local loadstring = _G.loadstring or _G.load
|
||||
local weakKeys = { __mode = 'k' }
|
||||
local cache = setmetatable({}, weakKeys)
|
||||
local resultsKey = {}
|
||||
local nilKey = {}
|
||||
|
||||
local function getMetaCall (callable)
|
||||
local meta = getmetatable(callable)
|
||||
return meta and meta.__call
|
||||
end
|
||||
|
||||
local tupleConstructorCache = {}
|
||||
|
||||
local function buildTupleConstructor (n)
|
||||
if tupleConstructorCache[n] then
|
||||
return tupleConstructorCache[n]
|
||||
end
|
||||
local t = {}
|
||||
for i = 1, n do
|
||||
t[i] = "a" .. i
|
||||
end
|
||||
local args = table.concat(t, ',')
|
||||
local ctor = loadstring('return function(' .. args ..
|
||||
') return function() return ' .. args .. ' end end')()
|
||||
tupleConstructorCache[n] = ctor
|
||||
return ctor
|
||||
end
|
||||
|
||||
local function tuple (...)
|
||||
return buildTupleConstructor(select('#', ...))(...)
|
||||
end
|
||||
|
||||
return function (callable)
|
||||
local metaCall = getMetaCall(callable)
|
||||
|
||||
if type(callable) ~= 'function' and not metaCall then
|
||||
error 'Attempted to memoize a non-callable value.'
|
||||
end
|
||||
|
||||
cache[callable] = setmetatable({}, weakKeys)
|
||||
|
||||
local function run (...)
|
||||
local node = cache[callable]
|
||||
local argc = select('#', ...)
|
||||
for i = 1, argc do
|
||||
local key = select(i, ...)
|
||||
if key == nil then
|
||||
key = nilKey
|
||||
end
|
||||
if not node[key] then
|
||||
node[key] = setmetatable({}, weakKeys)
|
||||
end
|
||||
node = node[key]
|
||||
end
|
||||
|
||||
if not node[resultsKey] then
|
||||
node[resultsKey] = tuple(callable(...))
|
||||
end
|
||||
|
||||
return node[resultsKey]()
|
||||
end
|
||||
|
||||
if metaCall then
|
||||
return function (...)
|
||||
local call = getMetaCall(callable)
|
||||
|
||||
if call ~= metaCall then
|
||||
cache[callable] = setmetatable({}, weakKeys)
|
||||
metaCall = call
|
||||
end
|
||||
|
||||
return run(...)
|
||||
end, cache, resultsKey, nilKey
|
||||
end
|
||||
|
||||
return run, cache, resultsKey, nilKey
|
||||
end
|
||||
78
lib/knife/serialize.lua
Normal file
@@ -0,0 +1,78 @@
|
||||
local tinsert, tconcat, infinity = table.insert, table.concat, math.huge
|
||||
|
||||
return function (value)
|
||||
local intro, outro, ready, known = {}, {}, {}, {}
|
||||
local knownCount = 0
|
||||
local writer = {}
|
||||
|
||||
-- get writer delegate for this value's type
|
||||
local function getWriter (value)
|
||||
return writer[type(value)]
|
||||
end
|
||||
|
||||
-- check if a value has a representation yet
|
||||
local function isReady (value)
|
||||
return type(value) ~= 'table' or ready[value]
|
||||
end
|
||||
|
||||
-- serialize tables
|
||||
function writer.table (value)
|
||||
if known[value] then
|
||||
return known[value]
|
||||
end
|
||||
|
||||
knownCount = knownCount + 1
|
||||
local variable = ('v%i'):format(knownCount)
|
||||
known[value] = variable
|
||||
|
||||
local parts = {}
|
||||
for k, v in pairs(value) do
|
||||
local writeKey, writeValue = getWriter(k), getWriter(v)
|
||||
if writeKey and writeValue then
|
||||
local key, value = writeKey(k), writeValue(v)
|
||||
if isReady(k) and isReady(v) then
|
||||
tinsert(parts, ('[%s]=%s'):format(key, value))
|
||||
else
|
||||
tinsert(outro, ('%s[%s]=%s'):format(variable, key, value))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local fields = tconcat(parts, ',')
|
||||
tinsert(intro, ('local %s={%s}'):format(variable, fields))
|
||||
ready[value] = true
|
||||
|
||||
return variable
|
||||
end
|
||||
|
||||
-- preserve sign bit on NaN, since Lua prints "nan" or "-nan"
|
||||
local function writeNan (n)
|
||||
return tostring(n) == tostring(0/0) and '0/0' or '-(0/0)'
|
||||
end
|
||||
|
||||
-- serialize numbers
|
||||
function writer.number (value)
|
||||
return value == infinity and '1/0'
|
||||
or value == -infinity and '-1/0'
|
||||
or value ~= value and writeNan(value)
|
||||
or ('%.17G'):format(value)
|
||||
end
|
||||
|
||||
-- serialize strings
|
||||
function writer.string (value)
|
||||
return ('%q'):format(value)
|
||||
end
|
||||
|
||||
-- serialize booleans
|
||||
writer.boolean = tostring
|
||||
|
||||
-- concatenate array, joined by and terminated with line break
|
||||
local function lines (t)
|
||||
return #t == 0 and '' or tconcat(t, '\n') .. '\n'
|
||||
end
|
||||
|
||||
-- generate serialized result
|
||||
local write = getWriter(value)
|
||||
local result = write and write(value) or 'nil'
|
||||
return lines(intro) .. lines(outro) .. 'return ' .. result
|
||||
end
|
||||
69
lib/knife/system.lua
Normal file
@@ -0,0 +1,69 @@
|
||||
local loadstring = _G.loadstring or _G.load
|
||||
local tconcat = table.concat
|
||||
local type = type
|
||||
|
||||
local function hasSigil (sigil, value)
|
||||
return type(value) == 'string' and sigil:byte() == value:byte()
|
||||
end
|
||||
|
||||
return function (aspects, process)
|
||||
local args = {}
|
||||
local cond = {}
|
||||
local results = {}
|
||||
local localIndex = 0
|
||||
local choicePattern = '([^|]+)'
|
||||
|
||||
local function suppress (aspect, condition)
|
||||
cond[#cond + 1] = 'if nil'
|
||||
for option in aspect:gmatch(choicePattern) do
|
||||
cond[#cond + 1] = condition:format(option)
|
||||
end
|
||||
cond[#cond + 1] = 'then return end'
|
||||
end
|
||||
|
||||
local function supply (aspect, isOptional, isReturned)
|
||||
localIndex = localIndex + 1
|
||||
cond[#cond + 1] = ('local l%d = nil'):format(localIndex)
|
||||
for option in aspect:gmatch(choicePattern) do
|
||||
cond[#cond + 1] = ('or _entity[%q]'):format(option)
|
||||
end
|
||||
if not isOptional then
|
||||
cond[#cond + 1] = ('if not l%d then return end'):format(localIndex)
|
||||
end
|
||||
if isReturned then
|
||||
results[#results + 1] = ('_entity[%q]'):format(aspect)
|
||||
end
|
||||
args[#args + 1] = ('l%d'):format(localIndex)
|
||||
end
|
||||
|
||||
for index = 1, #aspects do
|
||||
local aspect = aspects[index]
|
||||
if hasSigil('_', aspect) then
|
||||
args[#args + 1] = aspect
|
||||
elseif hasSigil('!', aspect) or hasSigil('~', aspect) then
|
||||
suppress(aspect:sub(2), 'or _entity[%q]')
|
||||
elseif hasSigil('-', aspect) then
|
||||
suppress(aspect:sub(2), 'or not _entity[%q]')
|
||||
elseif hasSigil('?', aspect) then
|
||||
supply(aspect:sub(2), true)
|
||||
elseif hasSigil('=', aspect) then
|
||||
supply(aspect:sub(2), false, true)
|
||||
else
|
||||
supply(aspect, false)
|
||||
end
|
||||
end
|
||||
|
||||
local source = ([[
|
||||
local _aspects, _process = ...
|
||||
return function (_entity, ...)
|
||||
%s
|
||||
%s _process(%s ...)
|
||||
return true
|
||||
end]]):format(
|
||||
tconcat(cond, ' '),
|
||||
results[1] and (tconcat(results, ',') .. ' = ') or '',
|
||||
args[1] and (tconcat(args, ', ') .. ', ') or '')
|
||||
|
||||
return loadstring(source)(aspects, process)
|
||||
end
|
||||
|
||||
128
lib/knife/test.lua
Normal file
@@ -0,0 +1,128 @@
|
||||
local test, testAssert, testError
|
||||
|
||||
-- Create a node representing a test section
|
||||
local function createNode (parent, description, process)
|
||||
return setmetatable({
|
||||
parent = parent,
|
||||
description = description,
|
||||
process = process,
|
||||
nodes = {},
|
||||
activeNodeIndex = 1,
|
||||
currentNodeIndex = 0,
|
||||
assert = testAssert,
|
||||
error = testError,
|
||||
}, { __call = test })
|
||||
end
|
||||
|
||||
-- Run a node
|
||||
local function runNode (node)
|
||||
node.currentNodeIndex = 0
|
||||
return node:process()
|
||||
end
|
||||
|
||||
-- Get the root node for a given node
|
||||
local function getRootNode (node)
|
||||
local parent = node.parent
|
||||
return parent and getRootNode(parent) or node
|
||||
end
|
||||
|
||||
-- Update the active child node of the given node
|
||||
local function updateActiveNode (node, description, process)
|
||||
local activeNodeIndex = node.activeNodeIndex
|
||||
local nodes = node.nodes
|
||||
local activeNode = nodes[activeNodeIndex]
|
||||
|
||||
if not activeNode then
|
||||
activeNode = createNode(node, description, process)
|
||||
nodes[activeNodeIndex] = activeNode
|
||||
else
|
||||
activeNode.process = process
|
||||
end
|
||||
|
||||
getRootNode(node).lastActiveLeaf = activeNode
|
||||
|
||||
return activeNode
|
||||
end
|
||||
|
||||
-- Run the active child node of the given node
|
||||
local function runActiveNode (node, description, process)
|
||||
local activeNode = updateActiveNode(node, description, process)
|
||||
return runNode(activeNode)
|
||||
end
|
||||
|
||||
-- Get ancestors of a node, including the node
|
||||
local function getAncestors (node)
|
||||
local ancestors = { node }
|
||||
for ancestor in function () return node.parent end do
|
||||
ancestors[#ancestors + 1] = ancestor
|
||||
node = ancestor
|
||||
end
|
||||
return ancestors
|
||||
end
|
||||
|
||||
-- Print a message describing one execution path in the test scenario
|
||||
local function printScenario (node)
|
||||
local ancestors = getAncestors(node)
|
||||
for i = #ancestors, 1, -1 do
|
||||
io.stderr:write(ancestors[i].description or '')
|
||||
io.stderr:write('\n')
|
||||
end
|
||||
end
|
||||
|
||||
-- Print a message and stop the test scenario when an assertion fails
|
||||
local function failAssert (node, description, message)
|
||||
io.stderr:write(message or '')
|
||||
io.stderr:write('\n\n')
|
||||
printScenario(node)
|
||||
io.stderr:write(description or '')
|
||||
io.stderr:write('\n\n')
|
||||
error(message or '', 2)
|
||||
end
|
||||
|
||||
-- Create a branch node for a test scenario
|
||||
test = function (node, description, process)
|
||||
node.currentNodeIndex = node.currentNodeIndex + 1
|
||||
if node.currentNodeIndex == node.activeNodeIndex then
|
||||
return runActiveNode(node, description, process)
|
||||
end
|
||||
end
|
||||
|
||||
-- Test an assertion
|
||||
testAssert = function (self, value, description)
|
||||
if not value then
|
||||
return failAssert(self, description, 'Test failed: assertion failed')
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
-- Expect function f to fail
|
||||
testError = function (self, f, description)
|
||||
if pcall(f) then
|
||||
return failAssert(self, description, 'Test failed: expected error')
|
||||
end
|
||||
end
|
||||
|
||||
-- Create the root node for a test scenario
|
||||
local function T (description, process)
|
||||
local root = createNode(nil, description, process)
|
||||
|
||||
runNode(root)
|
||||
while root.activeNodeIndex <= #root.nodes do
|
||||
local lastActiveBranch = root.lastActiveLeaf.parent
|
||||
lastActiveBranch.activeNodeIndex = lastActiveBranch.activeNodeIndex + 1
|
||||
runNode(root)
|
||||
end
|
||||
|
||||
return root
|
||||
end
|
||||
|
||||
-- Run any other files passed from CLI.
|
||||
if arg and arg[0] and arg[0]:gmatch('test.lua') then
|
||||
_G.T = T
|
||||
for i = 1, #arg do
|
||||
dofile(arg[i])
|
||||
end
|
||||
_G.T = nil
|
||||
end
|
||||
|
||||
return T
|
||||
246
lib/knife/timer.lua
Normal file
@@ -0,0 +1,246 @@
|
||||
-- require('mobdebug').start()
|
||||
|
||||
local Timer = {}
|
||||
|
||||
-- group management
|
||||
|
||||
local function detach (group, item)
|
||||
local index = item.index
|
||||
|
||||
group[index] = group[#group]
|
||||
group[index].index = index
|
||||
group[#group] = nil
|
||||
item.groupField = nil
|
||||
end
|
||||
|
||||
local function attach (group, item)
|
||||
if item.groupField then
|
||||
detach (item.groupField, item)
|
||||
end
|
||||
|
||||
local index = #group + 1
|
||||
|
||||
item.index = index
|
||||
group[index] = item
|
||||
item.groupField = group
|
||||
item.lastGroup = group
|
||||
end
|
||||
|
||||
-- instance update methods
|
||||
|
||||
local function updateContinuous (self, dt)
|
||||
local cutoff = self.cutoff
|
||||
local elapsed = self.elapsed + dt
|
||||
|
||||
if self:callback(dt) == false or elapsed >= cutoff then
|
||||
if self.finishField then
|
||||
self:finishField(elapsed - cutoff)
|
||||
end
|
||||
self:remove()
|
||||
end
|
||||
|
||||
self.elapsed = elapsed
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local function updateIntermittent (self, dt)
|
||||
local duration = self.delay or self.interval
|
||||
local elapsed = self.elapsed + dt
|
||||
|
||||
while elapsed >= duration do
|
||||
elapsed = elapsed - duration
|
||||
if self.limitField then
|
||||
self.limitField = self.limitField - 1
|
||||
end
|
||||
if self:callback(elapsed) == false
|
||||
or self.delay or self.limitField == 0 then
|
||||
if self.finishField then
|
||||
self:finishField(elapsed)
|
||||
end
|
||||
self:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
self.elapsed = elapsed
|
||||
end
|
||||
|
||||
local function updateTween (self, dt)
|
||||
local elapsed = self.elapsed + dt
|
||||
local plan = self.plan
|
||||
local duration = self.duration
|
||||
|
||||
self.elapsed = elapsed
|
||||
|
||||
if elapsed >= duration then
|
||||
for index = 1, #plan do
|
||||
local task = plan[index]
|
||||
|
||||
task.target[task.key] = task.final
|
||||
end
|
||||
if self.finishField then
|
||||
self:finishField(elapsed - duration)
|
||||
end
|
||||
self:remove()
|
||||
return
|
||||
end
|
||||
|
||||
local ease = self.easeField
|
||||
|
||||
for index = 1, #plan do
|
||||
local task = plan[index]
|
||||
local target, key = task.target, task.key
|
||||
local initial, change = task.initial, task.change
|
||||
|
||||
target[key] = ease(elapsed, initial, change, duration)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- shared instance methods
|
||||
|
||||
local defaultGroup = {}
|
||||
|
||||
local function group (self, group)
|
||||
if not group then
|
||||
group = defaultGroup
|
||||
end
|
||||
|
||||
attach(group, self)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
local function remove (self)
|
||||
if self.groupField then
|
||||
detach(self.groupField, self)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
local function register (self)
|
||||
attach(self.lastGroup, self)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
local function limit (self, limitField)
|
||||
self.limitField = limitField
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
local function finish (self, finishField)
|
||||
self.finishField = finishField
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
local function ease (self, easeField)
|
||||
self.easeField = easeField
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- tweening helper functions
|
||||
|
||||
local function planTween (definition)
|
||||
local plan = {}
|
||||
|
||||
for target, values in pairs(definition) do
|
||||
for key, final in pairs(values) do
|
||||
local initial = target[key]
|
||||
|
||||
plan[#plan + 1] = {
|
||||
target = target,
|
||||
key = key,
|
||||
initial = initial,
|
||||
final = final,
|
||||
change = final - initial,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return plan
|
||||
end
|
||||
|
||||
local function easeLinear (elapsed, initial, change, duration)
|
||||
return change * elapsed / duration + initial
|
||||
end
|
||||
|
||||
-- instance initializer
|
||||
|
||||
local function initialize (timer)
|
||||
timer.elapsed = 0
|
||||
timer.group = group
|
||||
timer.remove = remove
|
||||
timer.register = register
|
||||
|
||||
attach(defaultGroup, timer)
|
||||
|
||||
return timer
|
||||
end
|
||||
|
||||
-- static api
|
||||
|
||||
function Timer.after (delay, callback)
|
||||
return initialize {
|
||||
delay = delay,
|
||||
callback = callback,
|
||||
update = updateIntermittent,
|
||||
}
|
||||
end
|
||||
|
||||
function Timer.every (interval, callback)
|
||||
return initialize {
|
||||
interval = interval,
|
||||
callback = callback,
|
||||
update = updateIntermittent,
|
||||
limit = limit,
|
||||
finish = finish,
|
||||
}
|
||||
end
|
||||
|
||||
function Timer.prior (cutoff, callback)
|
||||
return initialize {
|
||||
cutoff = cutoff,
|
||||
callback = callback,
|
||||
update = updateContinuous,
|
||||
finish = finish,
|
||||
}
|
||||
end
|
||||
|
||||
function Timer.tween (duration, definition)
|
||||
return initialize {
|
||||
duration = duration,
|
||||
plan = planTween(definition),
|
||||
update = updateTween,
|
||||
easeField = easeLinear,
|
||||
ease = ease,
|
||||
finish = finish,
|
||||
}
|
||||
end
|
||||
|
||||
function Timer.update (dt, group)
|
||||
if not group then
|
||||
group = defaultGroup
|
||||
end
|
||||
for index = #group, 1, -1 do
|
||||
group[index]:update(dt)
|
||||
end
|
||||
end
|
||||
|
||||
function Timer.clear (group)
|
||||
if not group then
|
||||
group = defaultGroup
|
||||
end
|
||||
for i = 1, #group do
|
||||
group[i] = nil
|
||||
end
|
||||
end
|
||||
|
||||
Timer.defaultGroup = defaultGroup
|
||||
|
||||
return Timer
|
||||
281
lib/push.lua
Normal file
@@ -0,0 +1,281 @@
|
||||
-- push.lua v0.4
|
||||
|
||||
-- Copyright (c) 2020 Ulysse Ramage
|
||||
-- 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 love11 = love.getVersion() == 11
|
||||
local getDPI = love11 and love.window.getDPIScale or love.window.getPixelScale
|
||||
local windowUpdateMode = love11 and love.window.updateMode or function(width, height, settings)
|
||||
local _, _, flags = love.window.getMode()
|
||||
for k, v in pairs(settings) do flags[k] = v end
|
||||
love.window.setMode(width, height, flags)
|
||||
end
|
||||
|
||||
local push = {
|
||||
|
||||
defaults = {
|
||||
fullscreen = false,
|
||||
resizable = false,
|
||||
pixelperfect = false,
|
||||
highdpi = true,
|
||||
canvas = true,
|
||||
stencil = true
|
||||
}
|
||||
|
||||
}
|
||||
setmetatable(push, push)
|
||||
|
||||
function push:applySettings(settings)
|
||||
for k, v in pairs(settings) do
|
||||
self["_" .. k] = v
|
||||
end
|
||||
end
|
||||
|
||||
function push:resetSettings() return self:applySettings(self.defaults) end
|
||||
|
||||
function push:setupScreen(WWIDTH, WHEIGHT, RWIDTH, RHEIGHT, settings)
|
||||
|
||||
settings = settings or {}
|
||||
|
||||
self._WWIDTH, self._WHEIGHT = WWIDTH, WHEIGHT
|
||||
self._RWIDTH, self._RHEIGHT = RWIDTH, RHEIGHT
|
||||
|
||||
self:applySettings(self.defaults) --set defaults first
|
||||
self:applySettings(settings) --then fill with custom settings
|
||||
|
||||
windowUpdateMode(self._RWIDTH, self._RHEIGHT, {
|
||||
fullscreen = self._fullscreen,
|
||||
resizable = self._resizable,
|
||||
highdpi = self._highdpi
|
||||
})
|
||||
|
||||
self:initValues()
|
||||
|
||||
if self._canvas then
|
||||
self:setupCanvas({ "default" }) --setup canvas
|
||||
end
|
||||
|
||||
self._borderColor = {0, 0, 0}
|
||||
|
||||
self._drawFunctions = {
|
||||
["start"] = self.start,
|
||||
["end"] = self.finish
|
||||
}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function push:setupCanvas(canvases)
|
||||
table.insert(canvases, { name = "_render", private = true }) --final render
|
||||
|
||||
self._canvas = true
|
||||
self.canvases = {}
|
||||
|
||||
for i = 1, #canvases do
|
||||
push:addCanvas(canvases[i])
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
function push:addCanvas(params)
|
||||
table.insert(self.canvases, {
|
||||
name = params.name,
|
||||
private = params.private,
|
||||
shader = params.shader,
|
||||
canvas = love.graphics.newCanvas(self._WWIDTH, self._WHEIGHT),
|
||||
stencil = params.stencil or self._stencil
|
||||
})
|
||||
end
|
||||
|
||||
function push:setCanvas(name)
|
||||
if not self._canvas then return true end
|
||||
local canvasTable = self:getCanvasTable(name)
|
||||
return love.graphics.setCanvas({ canvasTable.canvas, stencil = canvasTable.stencil })
|
||||
end
|
||||
function push:getCanvasTable(name)
|
||||
for i = 1, #self.canvases do
|
||||
if self.canvases[i].name == name then
|
||||
return self.canvases[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
function push:setShader(name, shader)
|
||||
if not shader then
|
||||
self:getCanvasTable("_render").shader = name
|
||||
else
|
||||
self:getCanvasTable(name).shader = shader
|
||||
end
|
||||
end
|
||||
|
||||
function push:initValues()
|
||||
self._PSCALE = (not love11 and self._highdpi) and getDPI() or 1
|
||||
|
||||
self._SCALE = {
|
||||
x = self._RWIDTH/self._WWIDTH * self._PSCALE,
|
||||
y = self._RHEIGHT/self._WHEIGHT * self._PSCALE
|
||||
}
|
||||
|
||||
if self._stretched then --if stretched, no need to apply offset
|
||||
self._OFFSET = {x = 0, y = 0}
|
||||
else
|
||||
local scale = math.min(self._SCALE.x, self._SCALE.y)
|
||||
if self._pixelperfect then scale = math.floor(scale) end
|
||||
|
||||
self._OFFSET = {x = (self._SCALE.x - scale) * (self._WWIDTH/2), y = (self._SCALE.y - scale) * (self._WHEIGHT/2)}
|
||||
self._SCALE.x, self._SCALE.y = scale, scale --apply same scale to X and Y
|
||||
end
|
||||
|
||||
self._GWIDTH = self._RWIDTH * self._PSCALE - self._OFFSET.x * 2
|
||||
self._GHEIGHT = self._RHEIGHT * self._PSCALE - self._OFFSET.y * 2
|
||||
end
|
||||
|
||||
function push:apply(operation, shader)
|
||||
self._drawFunctions[operation](self, shader)
|
||||
end
|
||||
|
||||
function push:start()
|
||||
if self._canvas then
|
||||
love.graphics.push()
|
||||
love.graphics.setCanvas({ self.canvases[1].canvas, stencil = self.canvases[1].stencil })
|
||||
|
||||
else
|
||||
love.graphics.translate(self._OFFSET.x, self._OFFSET.y)
|
||||
love.graphics.setScissor(self._OFFSET.x, self._OFFSET.y, self._WWIDTH*self._SCALE.x, self._WHEIGHT*self._SCALE.y)
|
||||
love.graphics.push()
|
||||
love.graphics.scale(self._SCALE.x, self._SCALE.y)
|
||||
end
|
||||
end
|
||||
|
||||
function push:applyShaders(canvas, shaders)
|
||||
local _shader = love.graphics.getShader()
|
||||
if #shaders <= 1 then
|
||||
love.graphics.setShader(shaders[1])
|
||||
love.graphics.draw(canvas)
|
||||
else
|
||||
local _canvas = love.graphics.getCanvas()
|
||||
|
||||
local _tmp = self:getCanvasTable("_tmp")
|
||||
if not _tmp then --create temp canvas only if needed
|
||||
self:addCanvas({ name = "_tmp", private = true, shader = nil })
|
||||
_tmp = self:getCanvasTable("_tmp")
|
||||
end
|
||||
|
||||
love.graphics.push()
|
||||
love.graphics.origin()
|
||||
local outputCanvas
|
||||
for i = 1, #shaders do
|
||||
local inputCanvas = i % 2 == 1 and canvas or _tmp.canvas
|
||||
outputCanvas = i % 2 == 0 and canvas or _tmp.canvas
|
||||
love.graphics.setCanvas(outputCanvas)
|
||||
love.graphics.clear()
|
||||
love.graphics.setShader(shaders[i])
|
||||
love.graphics.draw(inputCanvas)
|
||||
love.graphics.setCanvas(inputCanvas)
|
||||
end
|
||||
love.graphics.pop()
|
||||
|
||||
love.graphics.setCanvas(_canvas)
|
||||
love.graphics.draw(outputCanvas)
|
||||
end
|
||||
love.graphics.setShader(_shader)
|
||||
end
|
||||
|
||||
function push:finish(shader)
|
||||
love.graphics.setBackgroundColor(unpack(self._borderColor))
|
||||
if self._canvas then
|
||||
local _render = self:getCanvasTable("_render")
|
||||
|
||||
love.graphics.pop()
|
||||
|
||||
local white = love11 and 1 or 255
|
||||
love.graphics.setColor(white, white, white)
|
||||
|
||||
--draw canvas
|
||||
love.graphics.setCanvas(_render.canvas)
|
||||
for i = 1, #self.canvases do --do not draw _render yet
|
||||
local _table = self.canvases[i]
|
||||
if not _table.private then
|
||||
local _canvas = _table.canvas
|
||||
local _shader = _table.shader
|
||||
self:applyShaders(_canvas, type(_shader) == "table" and _shader or { _shader })
|
||||
end
|
||||
end
|
||||
love.graphics.setCanvas()
|
||||
|
||||
--draw render
|
||||
love.graphics.translate(self._OFFSET.x, self._OFFSET.y)
|
||||
local shader = shader or _render.shader
|
||||
love.graphics.push()
|
||||
love.graphics.scale(self._SCALE.x, self._SCALE.y)
|
||||
self:applyShaders(_render.canvas, type(shader) == "table" and shader or { shader })
|
||||
love.graphics.pop()
|
||||
|
||||
--clear canvas
|
||||
for i = 1, #self.canvases do
|
||||
love.graphics.setCanvas(self.canvases[i].canvas)
|
||||
love.graphics.clear()
|
||||
end
|
||||
|
||||
love.graphics.setCanvas()
|
||||
love.graphics.setShader()
|
||||
else
|
||||
love.graphics.pop()
|
||||
love.graphics.setScissor()
|
||||
end
|
||||
end
|
||||
|
||||
function push:setBorderColor(color, g, b)
|
||||
self._borderColor = g and {color, g, b} or color
|
||||
end
|
||||
|
||||
function push:toGame(x, y)
|
||||
x, y = x - self._OFFSET.x, y - self._OFFSET.y
|
||||
local normalX, normalY = x / self._GWIDTH, y / self._GHEIGHT
|
||||
|
||||
x = (x >= 0 and x <= self._WWIDTH * self._SCALE.x) and normalX * self._WWIDTH or nil
|
||||
y = (y >= 0 and y <= self._WHEIGHT * self._SCALE.y) and normalY * self._WHEIGHT or nil
|
||||
|
||||
return x, y
|
||||
end
|
||||
|
||||
function push:toReal(x, y)
|
||||
local realX = self._OFFSET.x + (self._GWIDTH * x)/self._WWIDTH
|
||||
local realY = self._OFFSET.y + (self._GHEIGHT * y)/self._WHEIGHT
|
||||
return realX, realY
|
||||
end
|
||||
|
||||
function push:switchFullscreen(winw, winh)
|
||||
self._fullscreen = not self._fullscreen
|
||||
local windowWidth, windowHeight = love.window.getDesktopDimensions()
|
||||
|
||||
if self._fullscreen then --save windowed dimensions for later
|
||||
self._WINWIDTH, self._WINHEIGHT = self._RWIDTH, self._RHEIGHT
|
||||
elseif not self._WINWIDTH or not self._WINHEIGHT then
|
||||
self._WINWIDTH, self._WINHEIGHT = windowWidth * .5, windowHeight * .5
|
||||
end
|
||||
|
||||
self._RWIDTH = self._fullscreen and windowWidth or winw or self._WINWIDTH
|
||||
self._RHEIGHT = self._fullscreen and windowHeight or winh or self._WINHEIGHT
|
||||
|
||||
self:initValues()
|
||||
|
||||
love.window.setFullscreen(self._fullscreen, "desktop")
|
||||
if not self._fullscreen and (winw or winh) then
|
||||
windowUpdateMode(self._RWIDTH, self._RHEIGHT) --set window dimensions
|
||||
end
|
||||
end
|
||||
|
||||
function push:resize(w, h)
|
||||
if self._highdpi then w, h = w / self._PSCALE, h / self._PSCALE end
|
||||
self._RWIDTH = w
|
||||
self._RHEIGHT = h
|
||||
self:initValues()
|
||||
end
|
||||
|
||||
function push:getWidth() return self._WWIDTH end
|
||||
function push:getHeight() return self._WHEIGHT end
|
||||
function push:getDimensions() return self._WWIDTH, self._WHEIGHT end
|
||||
|
||||
return push
|
||||
78
main.lua
Normal file
@@ -0,0 +1,78 @@
|
||||
--[[
|
||||
GD50
|
||||
|
||||
Author: Angel C Monroy
|
||||
angelchecamonroy@outlook.com
|
||||
|
||||
Credit for art:
|
||||
Buch - https://opengameart.org/users/buch (tile sprites)
|
||||
|
||||
Monster sprites:
|
||||
http://creativecommons.org/licenses/by-sa/4.0/
|
||||
Aardart - Magic-Purple-Hermit
|
||||
https://wiki.tuxemon.org/index.php?title=Magic-Purple-Hermit
|
||||
Agnite - Leo, Sanglorian, and extyrannomon
|
||||
https://wiki.tuxemon.org/index.php?title=Leo
|
||||
https://wiki.tuxemon.org/index.php?title=Sanglorian
|
||||
https://wiki.tuxemon.org/index.php?title=Extyrannomon&action=edit&redlink=1
|
||||
Anoleaf - Spalding004
|
||||
https://wiki.tuxemon.org/index.php?title=Spalding004
|
||||
Bamboon - Mike Bramson
|
||||
mnbramson@gmail.com
|
||||
Cardiwing - Spalding004
|
||||
https://wiki.tuxemon.org/index.php?title=Spalding004
|
||||
|
||||
Credit for music:
|
||||
Field: https://freesound.org/people/Mrthenoronha/sounds/371843/
|
||||
Battle: https://freesound.org/people/Sirkoto51/sounds/414214/
|
||||
]]
|
||||
|
||||
-- require('mobdebug').start()
|
||||
|
||||
require 'src/Dependencies'
|
||||
|
||||
function love.load()
|
||||
love.window.setTitle('BallCatch50')
|
||||
love.graphics.setDefaultFilter('nearest', 'nearest')
|
||||
math.randomseed(os.time())
|
||||
|
||||
push:setupScreen(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT, {
|
||||
fullscreen = false,
|
||||
vsync = false,
|
||||
resizable = false
|
||||
})
|
||||
|
||||
gStateStack = StateStack()
|
||||
gStateStack:push(StartState())
|
||||
|
||||
love.keyboard.keysPressed = {}
|
||||
end
|
||||
|
||||
function love.resize(w, h)
|
||||
push:resize(w, h)
|
||||
end
|
||||
|
||||
function love.keypressed(key)
|
||||
if key == 'escape' then
|
||||
love.event.quit()
|
||||
end
|
||||
|
||||
love.keyboard.keysPressed[key] = true
|
||||
end
|
||||
|
||||
function love.keyboard.wasPressed(key)
|
||||
return love.keyboard.keysPressed[key]
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
Timer.update(dt)
|
||||
gStateStack:update(dt)
|
||||
|
||||
love.keyboard.keysPressed = {}
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
push:start()
|
||||
gStateStack:render()
|
||||
push:finish()
|
||||
end
|
||||
BIN
sounds/battle_music.mp3
Normal file
BIN
sounds/blip.wav
Normal file
BIN
sounds/exp.wav
Normal file
BIN
sounds/field_music.wav
Normal file
BIN
sounds/heal.wav
Normal file
BIN
sounds/hit.wav
Normal file
BIN
sounds/intro.mp3
Normal file
BIN
sounds/intro.ogg
Normal file
BIN
sounds/kill.wav
Normal file
BIN
sounds/levelup.wav
Normal file
BIN
sounds/music.wav
Normal file
BIN
sounds/powerup.wav
Normal file
BIN
sounds/run.wav
Normal file
BIN
sounds/victory.wav
Normal file
57
src/Animation.lua
Normal file
@@ -0,0 +1,57 @@
|
||||
--[[
|
||||
GD50
|
||||
Legend of Zelda
|
||||
|
||||
-- Animation Class --
|
||||
|
||||
Author: Colton Ogden
|
||||
cogden@cs50.harvard.edu
|
||||
]]
|
||||
|
||||
Animation = Class{}
|
||||
|
||||
function Animation:init(def)
|
||||
self.frames = def.frames
|
||||
self.interval = def.interval
|
||||
self.texture = def.texture
|
||||
self.looping = def.looping or true
|
||||
|
||||
self.timer = 0
|
||||
self.currentFrame = 1
|
||||
|
||||
-- used to see if we've seen a whole loop of the animation
|
||||
self.timesPlayed = 0
|
||||
end
|
||||
|
||||
function Animation:refresh()
|
||||
self.timer = 0
|
||||
self.currentFrame = 1
|
||||
self.timesPlayed = 0
|
||||
end
|
||||
|
||||
function Animation:update(dt)
|
||||
-- if not a looping animation and we've played at least once, exit
|
||||
if not self.looping and self.timesPlayed > 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- no need to update if animation is only one frame
|
||||
if #self.frames > 1 then
|
||||
self.timer = self.timer + dt
|
||||
|
||||
if self.timer > self.interval then
|
||||
self.timer = self.timer % self.interval
|
||||
|
||||
self.currentFrame = math.max(1, (self.currentFrame + 1) % (#self.frames + 1))
|
||||
|
||||
-- if we've looped back to the beginning, record
|
||||
if self.currentFrame == 1 then
|
||||
self.timesPlayed = self.timesPlayed + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Animation:getCurrentFrame()
|
||||
return self.frames[self.currentFrame]
|
||||
end
|
||||
69
src/Ball.lua
Normal file
@@ -0,0 +1,69 @@
|
||||
|
||||
Ball = Class{}
|
||||
|
||||
function Ball:init(x, y, dx, dy)
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.width = 8
|
||||
self.height = 8
|
||||
|
||||
-- these variables are for keeping track of our velocity on both the
|
||||
-- X and Y axis, since the ball can move in two dimensions
|
||||
self.dy = dx
|
||||
self.dx = dy
|
||||
self.remove = false
|
||||
self.hitPlayer = false
|
||||
end
|
||||
function Ball:reset()
|
||||
self.x = VIRTUAL_WIDTH / 2 - 2
|
||||
self.y = VIRTUAL_HEIGHT / 2 - 2
|
||||
self.dx = 0
|
||||
self.dy = 0
|
||||
end
|
||||
|
||||
function Ball:update(dt,level)
|
||||
local tempX = self.x + self.dx * dt
|
||||
local tempY = self.y + self.dy * dt
|
||||
|
||||
local trailSeg = level:pointOnEdge(tempX,tempY,level.player.trailSegments)
|
||||
if trailSeg then
|
||||
self.hitPlayer = true
|
||||
gSounds['hit']:play()
|
||||
end
|
||||
|
||||
local i, segment = level:getTouchingSegment(tempX,tempY)
|
||||
if segment then
|
||||
local direction = self:getCollisionDirection(segment, tempX, tempY)
|
||||
if direction == 'up' or direction == 'down' then
|
||||
self.dy = - self.dy
|
||||
elseif direction == 'right' or direction == 'left' then
|
||||
self.dx = -self.dx
|
||||
elseif level:pointOnEdge(tempX,tempY) then
|
||||
self.dx = -self.dx
|
||||
self.dy = -self.dy
|
||||
elseif not level:insideBounds(tempX,tempY) then
|
||||
self.dx = -self.dx
|
||||
self.dy = -self.dy
|
||||
end
|
||||
gSounds['blip']:stop()
|
||||
gSounds['blip']:play()
|
||||
end
|
||||
|
||||
if not (level:insideBounds(tempX,tempY) or level:pointOnEdge(tempX,tempY)) then
|
||||
self.remove = true
|
||||
end
|
||||
|
||||
self.x = self.x + self.dx * dt
|
||||
self.y = self.y + self.dy * dt
|
||||
|
||||
|
||||
end
|
||||
|
||||
function Ball:render()
|
||||
love.graphics.setColor(255,255,255,255)
|
||||
love.graphics.circle('fill', self.x, self.y, self.width/2)
|
||||
end
|
||||
|
||||
function Ball:getCollisionDirection(segment,x,y)
|
||||
return segment:sideOfSegment(x,y)
|
||||
end
|
||||
64
src/Dependencies.lua
Normal file
@@ -0,0 +1,64 @@
|
||||
--
|
||||
-- libraries
|
||||
--
|
||||
|
||||
Class = require 'lib/class'
|
||||
Event = require 'lib/knife.event'
|
||||
push = require 'lib/push'
|
||||
Timer = require 'lib/knife.timer'
|
||||
|
||||
require 'src/Animation'
|
||||
require 'src/constants'
|
||||
require 'src/StateMachine'
|
||||
require 'src/Util'
|
||||
|
||||
require 'src/Level'
|
||||
require 'src/Segment'
|
||||
require 'src/Player'
|
||||
require 'src/Ball'
|
||||
|
||||
require 'src/entity/entity_defs'
|
||||
require 'src/entity/Entity'
|
||||
|
||||
require 'src/states/BaseState'
|
||||
require 'src/states/StateStack'
|
||||
|
||||
require 'src/states/entity/EntityBaseState'
|
||||
require 'src/states/entity/EntityIdleState'
|
||||
require 'src/states/entity/EntityWalkState'
|
||||
require 'src/states/entity/EntityWalkEdgeState'
|
||||
|
||||
require 'src/states/game/StartState'
|
||||
require 'src/states/game/FadeInState'
|
||||
require 'src/states/game/FadeOutState'
|
||||
require 'src/states/game/PlayState'
|
||||
require 'src/states/game/GameOverState'
|
||||
|
||||
gTextures = {
|
||||
['entities'] = love.graphics.newImage('graphics/entities.png')
|
||||
}
|
||||
|
||||
gFrames = {
|
||||
['entities'] = GenerateQuads(gTextures['entities'], 16, 16)
|
||||
}
|
||||
|
||||
gFonts = {
|
||||
['small'] = love.graphics.newFont('fonts/font.ttf', 8),
|
||||
['medium'] = love.graphics.newFont('fonts/font.ttf', 16),
|
||||
['large'] = love.graphics.newFont('fonts/font.ttf', 32)
|
||||
}
|
||||
|
||||
gSounds = {
|
||||
['music'] = love.audio.newSource('sounds/music.wav', 'static'),
|
||||
['field-music'] = love.audio.newSource('sounds/field_music.wav', 'static'),
|
||||
['battle-music'] = love.audio.newSource('sounds/battle_music.mp3', 'static'),
|
||||
['blip'] = love.audio.newSource('sounds/blip.wav', 'static'),
|
||||
['powerup'] = love.audio.newSource('sounds/powerup.wav', 'static'),
|
||||
['hit'] = love.audio.newSource('sounds/hit.wav', 'static'),
|
||||
['run'] = love.audio.newSource('sounds/run.wav', 'static'),
|
||||
['heal'] = love.audio.newSource('sounds/heal.wav', 'static'),
|
||||
['exp'] = love.audio.newSource('sounds/exp.wav', 'static'),
|
||||
['levelup'] = love.audio.newSource('sounds/levelup.wav', 'static'),
|
||||
['victory-music'] = love.audio.newSource('sounds/victory.wav', 'static'),
|
||||
['intro-music'] = love.audio.newSource('sounds/intro.mp3', 'static')
|
||||
}
|
||||
728
src/Level.lua
Normal file
@@ -0,0 +1,728 @@
|
||||
|
||||
-- require('mobdebug').start()
|
||||
|
||||
Level = Class{}
|
||||
|
||||
function Level:init()
|
||||
|
||||
self.segments = self:createLevel()
|
||||
self.polygon = self:createPolygon()
|
||||
self.player = {}
|
||||
|
||||
end
|
||||
|
||||
function Level:update(dt)
|
||||
end
|
||||
|
||||
function Level:render()
|
||||
self:renderOuterSegments()
|
||||
end
|
||||
|
||||
function Level:renderOuterSegments()
|
||||
for k,segment in pairs(self.segments) do
|
||||
segment:render()
|
||||
end
|
||||
end
|
||||
|
||||
function Level:createLevel()
|
||||
local level = {
|
||||
[1] = Segment({LEVEL_RENDER_OFFSET,LEVEL_RENDER_OFFSET_TOP},{VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, LEVEL_RENDER_OFFSET_TOP},.5,'down'),
|
||||
[2] = Segment({VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, LEVEL_RENDER_OFFSET_TOP},{VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},.5,'left'),
|
||||
[3] = Segment({VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},{LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},.5,'up'),
|
||||
[4] = Segment({LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},{LEVEL_RENDER_OFFSET,LEVEL_RENDER_OFFSET_TOP},.5,'right')
|
||||
}
|
||||
-- local level = {
|
||||
-- [1] = Segment({LEVEL_RENDER_OFFSET,LEVEL_RENDER_OFFSET_TOP},{VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, LEVEL_RENDER_OFFSET_TOP},.5),
|
||||
-- [2] = Segment({VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, LEVEL_RENDER_OFFSET_TOP},{VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},.5),
|
||||
-- [3] = Segment({VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},{VIRTUAL_WIDTH / 2 + 10, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},.5),
|
||||
-- [4] = Segment({VIRTUAL_WIDTH / 2 + 10, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},{VIRTUAL_WIDTH / 2 + 10, VIRTUAL_HEIGHT / 2},.5),
|
||||
-- [5] = Segment({VIRTUAL_WIDTH / 2 + 10, VIRTUAL_HEIGHT / 2},{LEVEL_RENDER_OFFSET,VIRTUAL_HEIGHT / 2},.5),
|
||||
-- [6] = Segment({LEVEL_RENDER_OFFSET,VIRTUAL_HEIGHT / 2},{LEVEL_RENDER_OFFSET,LEVEL_RENDER_OFFSET_TOP},.5)
|
||||
-- }
|
||||
return level
|
||||
end
|
||||
|
||||
function Level:insideBounds2(x,y)
|
||||
local shape = love.physics.newPolygonShape(self.polygonPoints)
|
||||
return shape:testPoint(x,y)
|
||||
end
|
||||
|
||||
function Level:insideBounds(x,y, segments)
|
||||
if not segments then
|
||||
segments = self.segments
|
||||
end
|
||||
|
||||
local up,down,left,right = false
|
||||
local upSeg, downSeg, leftSeg, rightSeg = nil
|
||||
|
||||
-- find closest segments
|
||||
for i, segment in pairs(segments) do
|
||||
-- check raycast on y axis
|
||||
if segment.vertical then
|
||||
if y <= math.max(segment.firstPointY, segment.secondPointY) and
|
||||
y >= math.min(segment.firstPointY, segment.secondPointY) then
|
||||
if x <= segment.firstPointX then
|
||||
if not rightSeg then
|
||||
rightSeg = segment
|
||||
elseif math.abs(segment.firstPointX - x) < math.abs(rightSeg.firstPointX - x) then
|
||||
rightSeg = segment
|
||||
end
|
||||
else
|
||||
if not leftSeg then
|
||||
leftSeg = segment
|
||||
elseif math.abs(segment.firstPointX - x) < math.abs(leftSeg.firstPointX - x) then
|
||||
leftSeg = segment
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if segment.horizontal then
|
||||
if x <= math.max(segment.firstPointX, segment.secondPointX) and
|
||||
x >= math.min(segment.firstPointX, segment.secondPointX) then
|
||||
if y <= segment.firstPointY then
|
||||
if not downSeg then
|
||||
downSeg = segment
|
||||
elseif math.abs(segment.firstPointY - y) < math.abs(downSeg.firstPointY - y) then
|
||||
downSeg = segment
|
||||
end
|
||||
else
|
||||
if not upSeg then
|
||||
upSeg = segment
|
||||
elseif math.abs(segment.firstPointY - y) < math.abs(upSeg.firstPointY - y) then
|
||||
upSeg = segment
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if not rightSeg or not leftSeg or not upSeg or not downSeg then
|
||||
return false
|
||||
end
|
||||
|
||||
if rightSeg.face ~= 'left' or leftSeg.face ~= 'right' or upSeg.face ~= 'down' or downSeg.face ~= 'up' then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Level:insideBounds3(x,y)
|
||||
x = math.floor(x)
|
||||
y = math.floor(y)
|
||||
local oddNodes = false
|
||||
local j = #self.polygon
|
||||
local polygon = self.polygon
|
||||
for i = 1, #polygon do
|
||||
if (polygon[i].y < y and polygon[j].y >= y or polygon[j].y < y and polygon[i].y >= y) then
|
||||
if (polygon[i].x + ( y - polygon[i].y ) / (polygon[j].y - polygon[i].y) * (polygon[j].x - polygon[i].x) < x) then
|
||||
oddNodes = not oddNodes
|
||||
end
|
||||
end
|
||||
j = i
|
||||
end
|
||||
return oddNodes
|
||||
end
|
||||
|
||||
function Level:createPolygon()
|
||||
local polygon = {}
|
||||
local polygonPoints = {}
|
||||
local j = 1
|
||||
for i,segment in ipairs(self.segments) do
|
||||
polygon[i] = {}
|
||||
polygon[i+1] = {}
|
||||
polygon[i].x, polygon[i].y, polygon[i+1].x, polygon[i+1].y = segment:segmentToPoints()
|
||||
polygonPoints[j] = polygon[i].x
|
||||
polygonPoints[j+1] = polygon[i].y
|
||||
polygonPoints[j+2] = polygon[i+1].x
|
||||
polygonPoints[j+3] = polygon[i+1].y
|
||||
j = j + 4
|
||||
i = i + 1
|
||||
end
|
||||
self.polygonPoints = polygonPoints
|
||||
return polygon
|
||||
end
|
||||
|
||||
function Level:pointOnEdge(x,y,segments)
|
||||
if not segments then
|
||||
segments = self.segments
|
||||
end
|
||||
local margin = 2
|
||||
for i,segment in pairs(segments) do
|
||||
if segment.vertical then --vertical line
|
||||
if x >= segment.firstPointX - margin and x <= segment.firstPointX + margin and
|
||||
y <= math.max(segment.firstPointY, segment.secondPointY) and
|
||||
y >= math.min(segment.firstPointY, segment.secondPointY)
|
||||
then
|
||||
return true
|
||||
end
|
||||
elseif segment.horizontal then --horizontal line
|
||||
if y >= segment.firstPointY - margin and y <= segment.firstPointY + margin and
|
||||
x <= math.max(segment.firstPointX, segment.secondPointX) and
|
||||
x >= math.min(segment.firstPointX, segment.secondPointX)
|
||||
then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function Level:getTouchingSegment(x,y)
|
||||
local margin = 2
|
||||
for i,segment in pairs(self.segments) do
|
||||
if segment.vertical then --vertical line
|
||||
if x >= segment.firstPointX - margin and x <= segment.firstPointX + margin and
|
||||
y <= math.max(segment.firstPointY, segment.secondPointY) and
|
||||
y >= math.min(segment.firstPointY, segment.secondPointY)
|
||||
then
|
||||
return i, segment
|
||||
end
|
||||
elseif segment.horizontal then --horizontal line
|
||||
if y >= segment.firstPointY - margin and y <= segment.firstPointY + margin and
|
||||
x <= math.max(segment.firstPointX, segment.secondPointX) and
|
||||
x >= math.min(segment.firstPointX, segment.secondPointX)
|
||||
then
|
||||
return i, segment
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function Level:cutLevel()
|
||||
|
||||
local newSegs = self.player.trailSegments
|
||||
local startSegi, startSeg = self.player.trailStartSegment[1],self.player.trailStartSegment[2]
|
||||
local endSegi, endSeg = self.player.trailFinishSegment[1],self.player.trailFinishSegment[2]
|
||||
local first = 0
|
||||
local last = 0
|
||||
local firstFace = ''
|
||||
|
||||
--check if it is same start and finish segment
|
||||
if startSegi == endSegi then
|
||||
-- print("cutlevel same start and end segment: "..tostring(startSegi)..' - '..tostring(endSegi))
|
||||
local s = {}
|
||||
-- split the start/end segment
|
||||
local new = {}
|
||||
local split = self.segments[startSegi]:copy()
|
||||
|
||||
newSegs[1]:joinPerpendicular(split)
|
||||
newSegs[#newSegs]:joinPerpendicular(split)
|
||||
-- print("-- joined perpendicular start and end segment from new segments")
|
||||
newSegs[1]:debug()
|
||||
newSegs[#newSegs]:debug()
|
||||
local previous = #self.segments
|
||||
if startSegi > 1 and startSegi < #self.segments then
|
||||
previous = startSegi - 1
|
||||
end
|
||||
local part1, part2, temp = {}
|
||||
local inOrder = false
|
||||
local j,k = 1,1
|
||||
local insertedNewSegs = false
|
||||
-- print("-- Create new segments table")
|
||||
-- create the new set of segments
|
||||
while k <= #self.segments do
|
||||
-- print(" segment to be inserted "..tostring(k))
|
||||
if k == startSegi and not insertedNewSegs then
|
||||
-- print("Reached the segment being cut")
|
||||
-- print_r(self.segments[k])
|
||||
-- print("split the segment")
|
||||
part1, temp, part2 = self.segments[k]:splitInThreeWithSegments(newSegs[1],newSegs[#newSegs])
|
||||
-- print("part1")
|
||||
part1:debug()
|
||||
-- print("part2")
|
||||
part2:debug()
|
||||
|
||||
-- print("insert first part")
|
||||
new[j] = part1
|
||||
-- print_r(new)
|
||||
j = j + 1
|
||||
-- insert new segs
|
||||
-- print("-- insert new segments into new set")
|
||||
for i, segment in pairs(newSegs) do
|
||||
if i + 1 < #newSegs then
|
||||
newSegs[i+1]:joinPerpendicular(segment)
|
||||
end
|
||||
new[j] = segment:copy()
|
||||
j = j + 1
|
||||
end
|
||||
insertedNewSegments = true
|
||||
new[j] = part2
|
||||
j = j + 1
|
||||
-- print_r(new)
|
||||
-- print("-- finished inserting new segments and parts")
|
||||
else
|
||||
new[j] = self.segments[k]:copy()
|
||||
j = j + 1
|
||||
end
|
||||
k = k + 1
|
||||
end
|
||||
|
||||
-- print("order segments in cutlevel same start and end segment")
|
||||
new = self:orderSegments(new)
|
||||
-- print_r(new)
|
||||
-- print("calculate faces")
|
||||
self:calculateSegmentFaces(new,new[1].face)
|
||||
-- print_r(new)
|
||||
self.segments = new
|
||||
else
|
||||
-- create two set of segments, one to one side of the cut, one to the other
|
||||
local board1 = {}
|
||||
local board2 = {}
|
||||
|
||||
-- create first board
|
||||
local k = 1
|
||||
local j = 1
|
||||
local last = #self.segments
|
||||
local insertedNewSegments = false
|
||||
local savedStart,savedEnd = {}
|
||||
local board2StartSegi,board2EndSegi = 0
|
||||
-- create first board
|
||||
while k <= #self.segments do
|
||||
-- print("-----start Loop for "..tostring(k).."-----")
|
||||
if k == startSegi and not insertedNewSegments then
|
||||
board2StartSegi = k
|
||||
-- print("-- this segment is the start segment and not inserted new segments yet")
|
||||
-- print("newsegs[1] is joined perpendicular to k segment:")
|
||||
newSegs[1]:debug()
|
||||
newSegs[1]:joinPerpendicular(self.segments[k])
|
||||
newSegs[1]:debug()
|
||||
-- print("newSegs[1] is joined -- finished")
|
||||
local startPart1, startPart2 = self.segments[k]:splitInTwoWithSegment(newSegs[1])
|
||||
-- print("-- Segment k split into 2 segments by newsegs[1]: ")
|
||||
startPart1:debug()
|
||||
startPart2:debug()
|
||||
|
||||
if self.segments[last]:endEqualsStartOf(self.segments[k]) or
|
||||
self.segments[last]:startEqualsStartOf(self.segments[k]) then -- last one is linked to the start of this one
|
||||
--keep first part of segment
|
||||
-- print("-- keep first part of the k segment")
|
||||
self.segments[k] = startPart1
|
||||
savedStart = startPart2
|
||||
else -- keep second part of the segment (which is the first one touching the last segment)
|
||||
-- print("-- keep second part of the k segment")
|
||||
self.segments[k] = startPart2
|
||||
savedStart = startPart1
|
||||
end
|
||||
|
||||
board1[j] = self.segments[k]
|
||||
j = j + 1
|
||||
|
||||
-- print("-- before checking for inserted new segments - Board1")
|
||||
-- print_r(board1)
|
||||
if not insertedNewSegments then
|
||||
k = endSegi -- skip to last segment to cut and insert it as well
|
||||
last = k - 1
|
||||
board2EndSegi = k
|
||||
newSegs[#newSegs]:joinPerpendicular(self.segments[k])
|
||||
|
||||
-- print("-- not inserted items, proceed to insert in order new segments")
|
||||
for i, newseg in ipairs(newSegs) do
|
||||
board1[j] = newseg:copy()
|
||||
j = j + 1
|
||||
end
|
||||
-- print_r(board1)
|
||||
|
||||
local endPart1, endPart2 = self.segments[k]:splitInTwoWithSegment(newSegs[#newSegs])
|
||||
-- print("-- proceed to insert the second split segment")
|
||||
if self.segments[last]:endEqualsEndOf(self.segments[k]) or
|
||||
self.segments[last]:startEqualsEndOf(self.segments[k]) then -- next one is linked to the start of this one
|
||||
self.segments[k] = endPart1
|
||||
savedEnd = endPart2
|
||||
-- print("-- keep first part of the k segment")
|
||||
else
|
||||
self.segments[k] = endPart2
|
||||
savedEnd = endPart1
|
||||
-- print("-- keep second part of the k segment")
|
||||
end
|
||||
|
||||
board1[j] = self.segments[k]:copy()
|
||||
j = j + 1
|
||||
-- print_r(board1)
|
||||
insertedNewSegments = true
|
||||
end
|
||||
elseif k == endSegi and not insertedNewSegments then
|
||||
board2StartSegi = k
|
||||
-- print("-- this segment is the end segment and not inserted new segments yet")
|
||||
-- print("newsegs[#newSegs] is joined perpendicular to k segment:")
|
||||
newSegs[#newSegs]:debug()
|
||||
newSegs[#newSegs]:joinPerpendicular(self.segments[k])
|
||||
newSegs[#newSegs]:debug()
|
||||
-- print("newSegs[#newSegs] is joined -- finished")
|
||||
local endPart1, endPart2 = self.segments[k]:splitInTwoWithSegment(newSegs[#newSegs])
|
||||
-- print("-- Segment k split into 2 segments by newsegs[#newSegs]: ")
|
||||
endPart1:debug()
|
||||
endPart2:debug()
|
||||
|
||||
if self.segments[last]:endEqualsStartOf(self.segments[k]) or
|
||||
self.segments[last]:startEqualsStartOf(self.segments[k]) then -- last one is linked to the start of this one
|
||||
-- keep first part of segment
|
||||
self.segments[k] = endPart1
|
||||
savedEnd = endPart2
|
||||
else -- keep second part of the segment (which is the first one touching the last segment)
|
||||
self.segments[k] = endPart2
|
||||
savedEnd = endPart1
|
||||
end
|
||||
|
||||
board1[j] = self.segments[k]
|
||||
j = j + 1
|
||||
|
||||
if not insertedNewSegments then
|
||||
k = startSegi -- skip to last segment to cut and insert it as well
|
||||
last = k - 1
|
||||
board2EndSegi = k
|
||||
newSegs[1]:joinPerpendicular(self.segments[k])
|
||||
-- print("-- not inserted items, proceed to insert in reverse order new segments")
|
||||
|
||||
local i = #newSegs
|
||||
while i >= 1 do
|
||||
board1[j] = newSegs[i]:copy()
|
||||
board1[j]:switchDirection()
|
||||
j = j + 1
|
||||
i = i - 1
|
||||
end
|
||||
-- print_r(board1)
|
||||
|
||||
local startPart1, startPart2 = self.segments[k]:splitInTwoWithSegment(newSegs[1])
|
||||
-- print("-- Segment k split into 2 segments by newsegs[1]: ")
|
||||
startPart1:debug()
|
||||
startPart2:debug()
|
||||
|
||||
if self.segments[last]:endEqualsEndOf(self.segments[k]) or
|
||||
self.segments[last]:startEqualsEndOf(self.segments[k]) then -- next one is linked to the start of this one
|
||||
self.segments[k] = startPart1
|
||||
savedstart = startPart2
|
||||
else
|
||||
self.segments[k] = startPart2
|
||||
savedStart= startPart1
|
||||
end
|
||||
|
||||
board1[j] = self.segments[k]:copy()
|
||||
j = j + 1
|
||||
-- print_r(board1)
|
||||
insertedNewSegments = true
|
||||
end
|
||||
|
||||
elseif k ~= startSegi and k ~= endSegi then
|
||||
board1[j] = self.segments[k]:copy()
|
||||
j = j + 1
|
||||
end
|
||||
|
||||
last = k
|
||||
k = k + 1
|
||||
end
|
||||
-- print("order segments in cutlevel board1")
|
||||
board1 = self:orderSegments(board1)
|
||||
self:calculateSegmentFaces(board1,board1[1].face)
|
||||
-- print("PREPARE BOARD 1 -- FINAL")
|
||||
-- print_r(board1)
|
||||
|
||||
-- create second board
|
||||
-- print('---- Create Second board from '..tostring(board2StartSegi)..' to '..tostring(board2EndSegi))
|
||||
j = 1
|
||||
if board2StartSegi < board2EndSegi then
|
||||
board2[j] = savedStart
|
||||
j = j + 1
|
||||
-- print_r(board2)
|
||||
|
||||
-- print('-- inserted first segment, proceed with skipped segments in first board, start < end')
|
||||
k = board2StartSegi + 1
|
||||
|
||||
while k < board2EndSegi do
|
||||
board2[j] = self.segments[k]:copy()
|
||||
k = k + 1
|
||||
j = j + 1
|
||||
end
|
||||
-- print_r(board2)
|
||||
-- print('-- inserted original skipped segments')
|
||||
board2[j] = savedEnd
|
||||
-- print('-- inserted saved end segment')
|
||||
-- print_r(board2)
|
||||
j = j + 1
|
||||
-- insert new segments in order
|
||||
-- print("-- proceed to insert in order new segments")
|
||||
for i, newseg in ipairs(newSegs) do
|
||||
board2[j] = newseg:copy()
|
||||
j = j + 1
|
||||
end
|
||||
-- print_r(board2)
|
||||
|
||||
else
|
||||
-- print('-- inserted first segment, proceed with skipped segments in first board, end < start')
|
||||
board2[j] = savedEnd
|
||||
j = j + 1
|
||||
-- print('inserted saved end')
|
||||
-- print_r(board2)
|
||||
|
||||
k = board2EndSegi + 1
|
||||
-- print('-- insert the skipped segments')
|
||||
while k < board2StartSegi do
|
||||
board2[j] = self.segments[k]:copy()
|
||||
k = k + 1
|
||||
j = j + 1
|
||||
end
|
||||
-- print_r(board2)
|
||||
-- print('insert the saved start segment')
|
||||
-- insert new segments reverse order
|
||||
board2[j] = savedStart
|
||||
j = j + 1
|
||||
-- print_r(board2)
|
||||
-- print('-- insert the new segments in reverse order')
|
||||
local i = #newSegs
|
||||
while i >= 1 do
|
||||
board2[j] = newSegs[i]:copy()
|
||||
board2[j]:switchDirection()
|
||||
j = j + 1
|
||||
i = i - 1
|
||||
end
|
||||
-- print('-- inserted new segments, final board2 before ordering')
|
||||
-- print_r(board2)
|
||||
end
|
||||
|
||||
-- print("order segments in cutlevel board2")
|
||||
board2 = self:orderSegments(board2)
|
||||
self:calculateSegmentFaces(board2,board2[1].face)
|
||||
-- print("PREPARE BOARD 2 -- FINAL")
|
||||
-- print_r(board2)
|
||||
|
||||
if self:containsBalls(board1) == 0 then
|
||||
self.segments = board2
|
||||
elseif self:containsBalls(board2) == 0 then
|
||||
self.segments = board1
|
||||
else
|
||||
self.segments = self:getPerimeter(board1) > self:getPerimeter(board2) and board1 or board2
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function Level:orderSegments(segs)
|
||||
--returns the list of segments in the level linked to each other in order and perpendicular to each other
|
||||
local new = {}
|
||||
-- -- print('----- order Segments : ')
|
||||
-- print_s(segs)
|
||||
|
||||
new[1] = segs[1]
|
||||
table.remove(segs,1)
|
||||
|
||||
local found = false
|
||||
local k = #segs
|
||||
local i = 1
|
||||
while i <= k do
|
||||
-- -- print('----- segment i = '..tostring(i) .. '#segs '..tostring(#segs))
|
||||
-- print_s(new)
|
||||
-- -- print(' --new search')
|
||||
-- print_s(segs)
|
||||
for j, s in ipairs(segs) do
|
||||
-- -- print("test if j is next segment to i, j = "..tostring(j))
|
||||
if new[i]:endEqualsStartOf(s) then
|
||||
found = true
|
||||
|
||||
-- -- print("new[i] end equals start of ")
|
||||
-- s:debug()
|
||||
if new[i].vertical == s.vertical and new[i].horizontal == s.horizontal then
|
||||
-- -- print("join contiguous "..tostring(i).. 'with'..tostring(j))
|
||||
new[i]:joinContiguous(s)
|
||||
table.remove(segs,j)
|
||||
i = i - 1
|
||||
k = k - 1
|
||||
break
|
||||
else
|
||||
-- -- print("join perpendicular "..tostring(i).. 'with'..tostring(j))
|
||||
s:joinPerpendicular(new[i])
|
||||
new[i+1] = s
|
||||
table.remove(segs,j)
|
||||
break
|
||||
end
|
||||
elseif new[i]:endEqualsEndOf(s) then
|
||||
found = true
|
||||
|
||||
-- -- print("new[i] end equals end of ")
|
||||
-- s:debug()
|
||||
s:switchDirection()
|
||||
if new[i].vertical == s.vertical and new[i].horizontal == s.horizontal then
|
||||
-- -- print("join contiguous "..tostring(i).. 'with'..tostring(j))
|
||||
new[i]:joinContiguous(s)
|
||||
table.remove(segs,j)
|
||||
i = i - 1
|
||||
k = k - 1
|
||||
break
|
||||
else
|
||||
-- -- print("join perpendicular "..tostring(i).. 'with'..tostring(j))
|
||||
s:joinPerpendicular(new[i])
|
||||
new[i+1] = s
|
||||
table.remove(segs,j)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local fi, fs = 0,{}
|
||||
|
||||
if not found then
|
||||
-- -- print("search didnt yield any segment")
|
||||
local margin = 2
|
||||
while not found and margin < 5 do
|
||||
fi, fs = self:findNearestSegment(new[i].secondPointX,new[i].secondPointY,segs,margin)
|
||||
|
||||
if not fs then
|
||||
fi, fs = self:findNearestSegment(new[i].firstPointX,new[i].firstPointY,segs,margin)
|
||||
|
||||
if not fs then
|
||||
margin = margin + 1
|
||||
else
|
||||
found = true
|
||||
end
|
||||
else
|
||||
found = true
|
||||
end
|
||||
end
|
||||
|
||||
if found then
|
||||
if new[i].vertical == fs.vertical then
|
||||
-- -- print("join contiguous "..tostring(i).. 'with'..tostring(fi))
|
||||
new[i]:joinContiguous(fs)
|
||||
table.remove(segs,fi)
|
||||
i = i - 1
|
||||
k = k - 1
|
||||
else
|
||||
-- -- print("join perpendicular "..tostring(i).. 'with'..tostring(fi))
|
||||
fs:joinPerpendicular(new[i])
|
||||
new[i+1] = fs
|
||||
table.remove(segs,fi)
|
||||
end
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
-- print("ERROR -- order segments does not find next segment to ")
|
||||
new[i]:debug("number: " ..tostring(i))
|
||||
break
|
||||
else
|
||||
found = false
|
||||
end
|
||||
|
||||
i = i + 1
|
||||
end
|
||||
-- -- print("finished ordering: #new " ..tostring(#new))
|
||||
return new
|
||||
end
|
||||
|
||||
function Level:calculateSegmentFaces(segments, firstFace)
|
||||
-- start segment, end segment, list of segments to calculate faces for (when cutting the map)
|
||||
local face = firstFace
|
||||
for k, s in ipairs(segments) do
|
||||
s.face = face
|
||||
if k + 1 <= #segments then
|
||||
if face == 'up' then
|
||||
if s.direction == 'right' then
|
||||
if segments[k+1].direction == 'up' then
|
||||
face = 'left'
|
||||
else
|
||||
face = 'right'
|
||||
end
|
||||
else
|
||||
if segments[k+1].direction == 'up' then
|
||||
face = 'right'
|
||||
else
|
||||
face = 'left'
|
||||
end
|
||||
end
|
||||
elseif face == 'down' then
|
||||
if s.direction == 'right' then
|
||||
if segments[k+1].direction == 'up' then
|
||||
face = 'right'
|
||||
else
|
||||
face = 'left'
|
||||
end
|
||||
else
|
||||
if segments[k+1].direction == 'up' then
|
||||
face = 'left'
|
||||
else
|
||||
face = 'right'
|
||||
end
|
||||
end
|
||||
elseif face == 'right' then
|
||||
if s.direction == 'up' then
|
||||
if segments[k+1].direction == 'right' then
|
||||
face = 'down'
|
||||
else
|
||||
face = 'up'
|
||||
end
|
||||
else
|
||||
if segments[k+1].direction == 'right' then
|
||||
face = 'up'
|
||||
else
|
||||
face = 'down'
|
||||
end
|
||||
end
|
||||
elseif face == 'left' then
|
||||
if s.direction == 'up' then
|
||||
if segments[k+1].direction == 'right' then
|
||||
face = 'up'
|
||||
else
|
||||
face = 'down'
|
||||
end
|
||||
else
|
||||
if segments[k+1].direction == 'right' then
|
||||
face = 'down'
|
||||
else
|
||||
face = 'up'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Level:getPerimeter(segments)
|
||||
local p = 0
|
||||
if not segments then
|
||||
segments = self.segments
|
||||
end
|
||||
for k, s in pairs(segments) do
|
||||
p = p + s:length()
|
||||
end
|
||||
return p
|
||||
end
|
||||
|
||||
function Level:findNearestSegment(x,y,segments,margin)
|
||||
if not segments then
|
||||
segments = self.segments
|
||||
end
|
||||
if not margin then
|
||||
margin = 2
|
||||
end
|
||||
-- find a touching segment within margins
|
||||
for i,segment in pairs(segments) do
|
||||
if segment.vertical then --vertical line
|
||||
if x >= segment.firstPointX - margin and x <= segment.firstPointX + margin and
|
||||
y <= math.max(segment.firstPointY, segment.secondPointY) and
|
||||
y >= math.min(segment.firstPointY, segment.secondPointY)
|
||||
then
|
||||
return i, segment
|
||||
end
|
||||
elseif segment.horizontal then --horizontal line
|
||||
if y >= segment.firstPointY - margin and y <= segment.firstPointY + margin and
|
||||
x <= math.max(segment.firstPointX, segment.secondPointX) and
|
||||
x >= math.min(segment.firstPointX, segment.secondPointX)
|
||||
then
|
||||
return i, segment
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
function Level:containsBalls(segments)
|
||||
-- returns numbere of balls inside the bounds passed in or self.segments
|
||||
if not segments then
|
||||
segments = self.segments
|
||||
end
|
||||
local counter = 0
|
||||
|
||||
for k,b in pairs(self.balls) do
|
||||
if self:insideBounds(b.x + 4, b.y + 4,segments) or self:pointOnEdge(b.x+4, b.y+4, seegments) then
|
||||
counter = counter + 1
|
||||
end
|
||||
end
|
||||
return counter
|
||||
end
|
||||
213
src/Player.lua
Normal file
@@ -0,0 +1,213 @@
|
||||
|
||||
-- require('mobdebug').start()
|
||||
|
||||
Player = Class{__includes = Entity}
|
||||
|
||||
function Player:init(def, level )
|
||||
Entity.init(self,def)
|
||||
|
||||
self.x = 1
|
||||
self.y = LEVEL_RENDER_OFFSET_TOP - TILE_SIZE / 2 + 1
|
||||
|
||||
self.trail = {}
|
||||
self.level = level
|
||||
self.trailSegments = {}
|
||||
self.trailStartSegment = {}
|
||||
self.trailFinishSegment = {}
|
||||
self.score = 0
|
||||
self.multiplier = 1
|
||||
|
||||
Event.on('on-edge',function()
|
||||
self:onEdge()
|
||||
-- self:playerDebug("after on-edge event ")
|
||||
end)
|
||||
|
||||
Event.on('walking',function()
|
||||
self:onWalking()
|
||||
-- self:playerDebug("after walking event ")
|
||||
end)
|
||||
|
||||
Event.on('change-direction', function()
|
||||
self:onChangeDirection()
|
||||
-- self:playerDebug("after change-direction event ")
|
||||
end)
|
||||
|
||||
Event.on('back-to-wall', function ()
|
||||
self:onBackToWall()
|
||||
-- self:playerDebug("after back-to-wall event ")
|
||||
end)
|
||||
end
|
||||
|
||||
function Player:changeState(state)
|
||||
Entity.changeState(self,state)
|
||||
end
|
||||
|
||||
function Player:update(dt)
|
||||
Entity.update(self, dt)
|
||||
|
||||
end
|
||||
|
||||
function Player:createAnimations(animations)
|
||||
return Entity.createAnimations(self,animations)
|
||||
end
|
||||
|
||||
function Player:changeAnimation(name)
|
||||
Entity.changeAnimation(self,name)
|
||||
end
|
||||
|
||||
function Player:render()
|
||||
Entity.render(self)
|
||||
self:renderTrailSegments()
|
||||
|
||||
-- love.graphics.setColor(255, 0, 255, 255)
|
||||
-- love.graphics.rectangle('line', self.x, self.y, self.width, self.height)
|
||||
-- love.graphics.setColor(255, 255, 255, 255)
|
||||
end
|
||||
|
||||
function Player:renderTrailSegments()
|
||||
for i,segment in pairs (self.trailSegments) do
|
||||
segment:render()
|
||||
-- print_r(segment)
|
||||
end
|
||||
end
|
||||
|
||||
function Player:insideBounds()
|
||||
return self.level:insideBounds(self.x + TILE_SIZE / 2, self.y + TILE_SIZE / 2)
|
||||
end
|
||||
|
||||
function Player:onEdge()
|
||||
self.trail = {}
|
||||
self.trailSegments = {}
|
||||
self.trailFinishSegment = {}
|
||||
self.trailStartSegment = {}
|
||||
self.trailStartSegment[1], self.trailStartSegment[2] = self.level:getTouchingSegment(self.x + TILE_SIZE / 2, self.y + TILE_SIZE / 2)
|
||||
table.insert(self.trail,
|
||||
{['x'] = self.x + self.width/2,['y'] = self.y + self.height/2}
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
function Player:onWalking()
|
||||
if #self.trail > 1 then
|
||||
table.remove(self.trail)
|
||||
table.remove(self.trailSegments)
|
||||
end
|
||||
table.insert(self.trail,
|
||||
{['x'] = self.x + self.width/2,['y'] = self.y + self.height/2}
|
||||
)
|
||||
if #self.trail == 2 then
|
||||
table.insert(self.trailSegments,
|
||||
Segment(
|
||||
{self.trail[1].x, self.trail[1].y},
|
||||
{self.trail[2].x, self.trail[2].y},
|
||||
.5,'',{r=250,g=150,b=150}
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function Player:onChangeDirection()
|
||||
if self:insideBounds() then
|
||||
if #self.trail > 1 then
|
||||
table.remove(self.trail)
|
||||
table.remove(self.trailSegments)
|
||||
end
|
||||
|
||||
local k = #self.trailSegments
|
||||
local j = #self.trail + 1
|
||||
|
||||
table.insert(self.trail,
|
||||
{['x'] = self.x + self.width/2,['y'] = self.y + self.height/2}
|
||||
)
|
||||
if #self.trail == 2 then
|
||||
|
||||
table.insert(self.trailSegments,
|
||||
Segment(
|
||||
{self.trail[1].x, self.trail[1].y},
|
||||
{self.trail[2].x, self.trail[2].y},
|
||||
.5,'',{r=250,g=150,b=150}
|
||||
)
|
||||
)
|
||||
end
|
||||
self.trail = {}
|
||||
table.insert(self.trail,
|
||||
{['x'] = self.x + self.width/2,['y'] = self.y + self.height/2}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function Player:onBackToWall()
|
||||
if self:onTheEdge() and #self.trail > 0 then
|
||||
self.trailFinishSegment[1], self.trailFinishSegment[2] = self.level:getTouchingSegment(self.x + TILE_SIZE / 2, self.y + TILE_SIZE / 2)
|
||||
local k = #self.trailSegments
|
||||
local j = #self.trail + 1
|
||||
if #self.trail > 1 then
|
||||
table.remove(self.trail)
|
||||
end
|
||||
table.insert(self.trail,
|
||||
{['x'] = self.x + self.width/2,['y'] = self.y + self.height/2}
|
||||
)
|
||||
if #self.trail == 2 then
|
||||
table.remove(self.trailSegments)
|
||||
table.insert(self.trailSegments,
|
||||
Segment(
|
||||
{self.trail[1].x, self.trail[1].y},
|
||||
{self.trail[2].x, self.trail[2].y},
|
||||
.5,'',{r=250,g=150,b=150}
|
||||
)
|
||||
)
|
||||
end
|
||||
self:resetTrail()
|
||||
end
|
||||
end
|
||||
|
||||
function Player:reset()
|
||||
self.x = 1
|
||||
self.y = LEVEL_RENDER_OFFSET_TOP - TILE_SIZE / 2 + 1
|
||||
self.trail = {}
|
||||
self.trailSegments = {}
|
||||
self.trailStartSegment = {}
|
||||
self.trailFinishSegment = {}
|
||||
end
|
||||
|
||||
function Player:resetTrail()
|
||||
self:playerDebug('Player:resetTrail')
|
||||
if #self.trailStartSegment > 0 and #self.trailFinishSegment > 0 then
|
||||
self.level:cutLevel()
|
||||
end
|
||||
self.trail = {}
|
||||
self.trailStartSegment = {}
|
||||
self.trailFinishSegment = {}
|
||||
self.trailSegments = {}
|
||||
end
|
||||
|
||||
function Player:onTheEdge()
|
||||
return self.level:pointOnEdge(self.x + TILE_SIZE / 2, self.y + TILE_SIZE / 2)
|
||||
end
|
||||
|
||||
function Player:playerDebug(msg)
|
||||
-- print('------------------------------------')
|
||||
if msg then
|
||||
-- print(msg)
|
||||
end
|
||||
-- print("Player Position x,y = " ..tostring(self.x)..','..tostring(self.y))
|
||||
-- print("Player Center x,y = " ..tostring(self.x+8)..','..tostring(self.y+8))
|
||||
-- print("Player direction : "..self.direction .. ' previous: '..self.previousDirection)
|
||||
-- print('......................')
|
||||
-- print("Player trail points: ")
|
||||
-- print_r(self.trail)
|
||||
-- print('......................')
|
||||
-- print("Player segments: ")
|
||||
-- print_s(self.trailSegments)
|
||||
-- print('......................')
|
||||
-- print("Trail Start")
|
||||
if self.trailStartSegment[2] ~= nil then
|
||||
self.trailStartSegment[2]:debug(tostring(self.trailStartSegment[1]))
|
||||
end
|
||||
-- print('......................')
|
||||
-- print("Trail Finish")
|
||||
if self.trailFinishSegment[2] ~= nil then
|
||||
self.trailFinishSegment[2]:debug(tostring(self.trailFinishSegment[1]))
|
||||
end
|
||||
-- print('------------------------------------')
|
||||
end
|
||||
294
src/Segment.lua
Normal file
@@ -0,0 +1,294 @@
|
||||
-- require('mobdebug').start()
|
||||
|
||||
Segment = Class{}
|
||||
|
||||
local symbols = {['up'] = 207, ['down'] = 209, ['right'] = 199, ['left'] = 182}
|
||||
|
||||
function Segment:init(firstPoint, secondPoint,lineWidth, face, color)
|
||||
self.firstPointX = firstPoint[1]
|
||||
self.firstPointY = firstPoint[2]
|
||||
self.secondPointX = secondPoint[1]
|
||||
self.secondPointY = secondPoint[2]
|
||||
self.lineWidth = lineWidth or 3
|
||||
self.vertical = self.firstPointX == self.secondPointX
|
||||
self.horizontal = self.firstPointY == self.secondPointY
|
||||
self.face = face or ''
|
||||
self.direction = self:calculateDirection()
|
||||
self.color = color or {r=255,g=255,b=255}
|
||||
end
|
||||
|
||||
function Segment:render()
|
||||
love.graphics.setColor(self.color.r,self.color.g, self.color.b,255)
|
||||
love.graphics.setLineWidth(self.lineWidth)
|
||||
love.graphics.line(self.firstPointX,self.firstPointY,self.secondPointX, self.secondPointY)
|
||||
|
||||
love.graphics.setColor(255,255, 255, 255)
|
||||
|
||||
-- debug
|
||||
-- love.graphics.setFont(gFonts['small'])
|
||||
-- love.graphics.printf(self.face,self.firstPointX + 2, self.firstPointY + 2 / 2,25,'left' )
|
||||
|
||||
end
|
||||
|
||||
function Segment:segmentToPoints()
|
||||
return self.firstPointX, self.firstPointY, self.secondPointX, self.secondPointY
|
||||
end
|
||||
|
||||
function Segment:length()
|
||||
return math.sqrt( (self.secondPointX - self.firstPointX) * (self.secondPointX - self.firstPointX) + (self.secondPointY - self.firstPointY) * (self.secondPointY - self.firstPointY) )
|
||||
end
|
||||
|
||||
function Segment:sideOfSegment(x,y)
|
||||
if y < self.firstPointY and y < self.secondPointY then -- above
|
||||
return 'up'
|
||||
end
|
||||
if y > self.firstPointY and y > self.secondPointY then -- below
|
||||
return 'down'
|
||||
end
|
||||
if x < self.firstPointX and x < self.secondPointX then -- left
|
||||
return 'left'
|
||||
end
|
||||
if x > self.firstPointX and x > self.secondPointX then -- right
|
||||
return 'right'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function Segment:debug(msg)
|
||||
if not msg then
|
||||
msg = ''
|
||||
end
|
||||
-- print("Segment:debug ".. msg .." ...............")
|
||||
-- print("FirstPoint: "..tostring(self.firstPointX)..','..tostring(self.firstPointY))
|
||||
-- print("SecondPoint: "..tostring(self.secondPointX)..','..tostring(self.secondPointY))
|
||||
-- print("face: "..self.face.. ' - Direction: '..self.direction)
|
||||
end
|
||||
|
||||
function Segment:copy()
|
||||
local copy = {}
|
||||
copy.firstPointX = self.firstPointX
|
||||
copy.firstPointY = self.firstPointY
|
||||
copy.secondPointX = self.secondPointX
|
||||
copy.secondPointY = self.secondPointY
|
||||
copy.lineWidth = self.lineWidth
|
||||
copy.face = self.face
|
||||
return Segment({copy.firstPointX,copy.firstPointY},{copy.secondPointX,copy.secondPointY},copy.lineWidth, copy.face)
|
||||
end
|
||||
|
||||
function Segment:endEqualsStartOf(s)
|
||||
-- segments are linked at the end of this one
|
||||
if self.secondPointX == s.firstPointX and
|
||||
self.secondPointY == s.firstPointY then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
function Segment:endEqualsEndOf(s)
|
||||
-- segments are linked at the end of this one
|
||||
if self.secondPointX == s.secondPointX and self.secondPointY == s.secondPointY then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function Segment:startEqualsEndOf(s)
|
||||
-- segments are linked at the beginning of this one
|
||||
if self.firstPointX == s.secondPointX and self.firstPointY == s.secondPointY then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
function Segment:startEqualsStartOf(s)
|
||||
-- segments are linked at the beginning of this one
|
||||
if self.firstPointX == s.firstPointX and self.firstPointY == s.PointY then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function Segment:calculateDirection()
|
||||
local dir = ''
|
||||
|
||||
if self.vertical then
|
||||
if self.firstPointY < self.secondPointY then
|
||||
dir = 'down'
|
||||
else
|
||||
dir = 'up'
|
||||
end
|
||||
else
|
||||
if self.firstPointX < self.secondPointX then
|
||||
dir = 'right'
|
||||
else
|
||||
dir = 'left'
|
||||
end
|
||||
end
|
||||
return dir
|
||||
end
|
||||
|
||||
function Segment:splitInTwoWithPoint(point) -- number
|
||||
local s1, s2 = {}
|
||||
if point > 0 then
|
||||
if self.vertical then
|
||||
s1 = Segment(
|
||||
{self.firstPointX, self.firstPointY},
|
||||
{self.secondPointX, point},
|
||||
self.lineWidth, self.face
|
||||
)
|
||||
s2 = Segment(
|
||||
{self.firstPointX, point},
|
||||
{self.secondPointX,self.secondPointY},
|
||||
self.lineWidth, self.face
|
||||
)
|
||||
else
|
||||
s1 = Segment(
|
||||
{self.firstPointX, self.firstPointY},
|
||||
{point, self.secondPointY},
|
||||
self.lineWidth, self.face
|
||||
)
|
||||
s2 = Segment(
|
||||
{point, self.firstPointY},
|
||||
{self.secondPointX,self.secondPointY},
|
||||
self.lineWidth, self.face
|
||||
)
|
||||
end
|
||||
else
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
return s1, s2
|
||||
end
|
||||
|
||||
function Segment:splitInTwoWithSegment(segment)
|
||||
local point = 0
|
||||
if self.vertical then
|
||||
if self.firstPointY == segment.firstPointY then
|
||||
point = segment.firstPointY
|
||||
else
|
||||
point = segment.secondPointY
|
||||
end
|
||||
elseif self.horizontal then
|
||||
if self.firstPointX == segment.firstPointX then
|
||||
point = segment.firstPointX
|
||||
else
|
||||
point = segment.secondPointX
|
||||
end
|
||||
end
|
||||
|
||||
return self:splitInTwoWithPoint(point)
|
||||
end
|
||||
|
||||
function Segment:joinPerpendicular(s)
|
||||
-- changes the self, not the parameter
|
||||
if s.vertical then
|
||||
local sx = s.firstPointX
|
||||
if math.abs(self.firstPointX - sx) <
|
||||
math.abs(self.secondPointX - sx) then
|
||||
self.firstPointX = sx
|
||||
else
|
||||
self.secondPointX = sx
|
||||
end
|
||||
else
|
||||
local sy = s.firstPointY
|
||||
if math.abs(self.firstPointY - sy) <
|
||||
math.abs(self.secondPointY - sy) then
|
||||
self.firstPointY = sy
|
||||
else
|
||||
self.secondPointY = sy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Segment:switchDirection()
|
||||
local temp = {}
|
||||
temp.firstPointX = self.firstPointX
|
||||
temp.firstPointY = self.firstPointY
|
||||
self.firstPointX = self.secondPointX
|
||||
self.firstPointY = self.secondPointY
|
||||
self.secondPointX = temp.firstPointX
|
||||
self.secondPointY = temp.firstPointY
|
||||
self:notDirection()
|
||||
end
|
||||
|
||||
function Segment:notDirection()
|
||||
if self.direction == 'down' then
|
||||
self.direction = 'up'
|
||||
elseif self.direction == 'up' then
|
||||
self.direction = 'down'
|
||||
elseif self.direction == 'right' then
|
||||
self.direction = 'left'
|
||||
elseif self.direction == 'left' then
|
||||
self.direction = 'right'
|
||||
end
|
||||
end
|
||||
|
||||
function Segment:notFace()
|
||||
if self.face == 'up' then
|
||||
self.face = 'down'
|
||||
elseif self.face == 'down' then
|
||||
self.face = 'up'
|
||||
elseif self.face == 'right' then
|
||||
self.face = 'left'
|
||||
elseif self.face == 'left' then
|
||||
self.face = 'right'
|
||||
end
|
||||
end
|
||||
|
||||
function Segment:joinContiguous(s)
|
||||
-- second segment becomes part of first one
|
||||
if self.vertical then
|
||||
self.secondPointY = s.secondPointY
|
||||
else
|
||||
self.secondPointX = s.secondPointX
|
||||
end
|
||||
end
|
||||
|
||||
function Segment:compare(s)
|
||||
-- returns >0 if self is to the right or below
|
||||
-- returns <0 if self is to the left or above
|
||||
-- returns 0 if both are same point
|
||||
if self.vertical and s.vertical then
|
||||
if self.firstPointX == s.firstPointX then
|
||||
return 0
|
||||
else
|
||||
return self.firstPointX - s.firstPointX
|
||||
end
|
||||
elseif self.horizontal and s.horizontal then
|
||||
if self.firstPointY == s.firstPointY then
|
||||
return 0
|
||||
else
|
||||
return self.firstPointY - s.firstPointY
|
||||
end
|
||||
else
|
||||
return (self.firstPointX + self.firstPointY +
|
||||
self.secondPointX + self.secondPointY) - (
|
||||
s.firstPointX + s.firstPointY + s.secondPointX
|
||||
+s.secondPointY)
|
||||
end
|
||||
end
|
||||
|
||||
function Segment:splitInThreeWithSegments(s1,s2)
|
||||
local p1, p2, p3, t = {}
|
||||
if self.direction == 'down' or self.direction == 'right' then
|
||||
if s1:compare(s2) < 0 then
|
||||
p1, t = self:splitInTwoWithSegment(s1)
|
||||
p2, p3 = t:splitInTwoWithSegment(s2)
|
||||
else
|
||||
p1, t = self:splitInTwoWithSegment(s2)
|
||||
p2, p3 = t:splitInTwoWithSegment(s1)
|
||||
end
|
||||
else
|
||||
if s1:compare(s2) < 0 then
|
||||
p1, t = self:splitInTwoWithSegment(s2)
|
||||
p2, p3 = t:splitInTwoWithSegment(s1)
|
||||
else
|
||||
p1, t = self:splitInTwoWithSegment(s1)
|
||||
p2, p3 = t:splitInTwoWithSegment(s2)
|
||||
end
|
||||
end
|
||||
|
||||
return p1,p2,p3
|
||||
end
|
||||
|
||||
function Segment:insert(list)
|
||||
|
||||
end
|
||||
37
src/StateMachine.lua
Normal file
@@ -0,0 +1,37 @@
|
||||
-- require('mobdebug').start()
|
||||
|
||||
StateMachine = Class{}
|
||||
|
||||
function StateMachine:init(states)
|
||||
self.empty = {
|
||||
render = function() end,
|
||||
update = function() end,
|
||||
processAI = function() end,
|
||||
enter = function() end,
|
||||
exit = function() end
|
||||
}
|
||||
self.states = states or {} -- [name] -> [function that returns states]
|
||||
self.current = self.empty
|
||||
end
|
||||
|
||||
function StateMachine:change(stateName, enterParams)
|
||||
assert(self.states[stateName]) -- state must exist!
|
||||
self.current:exit()
|
||||
self.current = self.states[stateName]()
|
||||
self.current:enter(enterParams)
|
||||
end
|
||||
|
||||
function StateMachine:update(dt)
|
||||
self.current:update(dt)
|
||||
end
|
||||
|
||||
function StateMachine:render()
|
||||
self.current:render()
|
||||
end
|
||||
|
||||
--[[
|
||||
Used for states that can be controlled by the AI to influence update logic.
|
||||
]]
|
||||
function StateMachine:processAI(params, dt)
|
||||
self.current:processAI(params, dt)
|
||||
end
|
||||
73
src/Util.lua
Normal file
@@ -0,0 +1,73 @@
|
||||
|
||||
-- require('mobdebug').start()
|
||||
--[[
|
||||
Given an "atlas" (a texture with multiple sprites), as well as a
|
||||
width and a height for the tiles therein, split the texture into
|
||||
all of the quads by simply dividing it evenly.
|
||||
]]
|
||||
function GenerateQuads(atlas, tilewidth, tileheight)
|
||||
local sheetWidth = atlas:getWidth() / tilewidth
|
||||
local sheetHeight = atlas:getHeight() / tileheight
|
||||
|
||||
local sheetCounter = 1
|
||||
local spritesheet = {}
|
||||
|
||||
for y = 0, sheetHeight - 1 do
|
||||
for x = 0, sheetWidth - 1 do
|
||||
spritesheet[sheetCounter] =
|
||||
love.graphics.newQuad(x * tilewidth, y * tileheight, tilewidth,
|
||||
tileheight, atlas:getDimensions())
|
||||
sheetCounter = sheetCounter + 1
|
||||
end
|
||||
end
|
||||
|
||||
return spritesheet
|
||||
end
|
||||
|
||||
--[[
|
||||
Recursive table printing function.
|
||||
https://coronalabs.com/blog/2014/09/02/tutorial-printing-table-contents/
|
||||
]]
|
||||
function print_r ( t )
|
||||
local print_r_cache={}
|
||||
local function sub_print_r(t,indent)
|
||||
if (print_r_cache[tostring(t)]) then
|
||||
print(indent.."*"..tostring(t))
|
||||
else
|
||||
print_r_cache[tostring(t)]=true
|
||||
if (type(t)=="table") then
|
||||
for pos,val in pairs(t) do
|
||||
if (type(val)=="table") then
|
||||
print(indent.."["..pos.."] => "..tostring(t).." {")
|
||||
sub_print_r(val,indent..string.rep(" ",string.len(pos)+8))
|
||||
print(indent..string.rep(" ",string.len(pos)+6).."}")
|
||||
elseif (type(val)=="string") then
|
||||
print(indent.."["..pos..'] => "'..val..'"')
|
||||
else
|
||||
print(indent.."["..pos.."] => "..tostring(val))
|
||||
end
|
||||
end
|
||||
else
|
||||
print(indent..tostring(t))
|
||||
end
|
||||
end
|
||||
end
|
||||
if (type(t)=="table") then
|
||||
print(tostring(t).." {")
|
||||
sub_print_r(t," ")
|
||||
print("}")
|
||||
else
|
||||
sub_print_r(t," ")
|
||||
end
|
||||
print()
|
||||
end
|
||||
|
||||
function math.round(x,precision)
|
||||
return x + precision - (x + precision) % 1
|
||||
end
|
||||
|
||||
function print_s(segments)
|
||||
for k, s in ipairs(segments) do
|
||||
s:debug(tostring(k))
|
||||
end
|
||||
end
|
||||
12
src/constants.lua
Normal file
@@ -0,0 +1,12 @@
|
||||
-- require('mobdebug').start()
|
||||
|
||||
VIRTUAL_WIDTH = 384
|
||||
VIRTUAL_HEIGHT = 216
|
||||
|
||||
WINDOW_WIDTH = 1280
|
||||
WINDOW_HEIGHT = 720
|
||||
|
||||
LEVEL_RENDER_OFFSET_TOP = 30
|
||||
LEVEL_RENDER_OFFSET = 10
|
||||
|
||||
TILE_SIZE = 16
|
||||
57
src/entity/Entity.lua
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
Entity = Class{}
|
||||
|
||||
function Entity:init(def)
|
||||
self.direction = 'down'
|
||||
self.previousDirection = ''
|
||||
self.walkingSpeed = def.walkingSpeed
|
||||
self.animations = self:createAnimations(def.animations)
|
||||
|
||||
self.currentAnimation = self.animations['idle-down']
|
||||
|
||||
self.stateMachine = StateMachine {
|
||||
['walk'] = function() return EntityWalkState(self) end,
|
||||
['edge'] = function() return EntityWalkEdgeState(self) end,
|
||||
['idle'] = function() return EntityIdleState(self) end
|
||||
}
|
||||
self.stateMachine:change('idle')
|
||||
self.width = def.width
|
||||
self.height = def.height
|
||||
|
||||
end
|
||||
|
||||
function Entity:changeState(name)
|
||||
self.stateMachine:change(name)
|
||||
-- printChangeState to "..name)
|
||||
end
|
||||
|
||||
function Entity:changeAnimation(name)
|
||||
self.currentAnimation = self.animations[name]
|
||||
end
|
||||
|
||||
function Entity:createAnimations(animations)
|
||||
local animationsReturned = {}
|
||||
|
||||
for k, animationDef in pairs(animations) do
|
||||
animationsReturned[k] = Animation {
|
||||
texture = animationDef.texture or 'entities',
|
||||
frames = animationDef.frames,
|
||||
interval = animationDef.interval
|
||||
}
|
||||
end
|
||||
|
||||
return animationsReturned
|
||||
end
|
||||
|
||||
function Entity:processAI(params, dt)
|
||||
self.stateMachine:processAI(params, dt)
|
||||
end
|
||||
|
||||
function Entity:update(dt)
|
||||
self.currentAnimation:update(dt)
|
||||
self.stateMachine:update(dt)
|
||||
end
|
||||
|
||||
function Entity:render()
|
||||
self.stateMachine:render()
|
||||
end
|
||||
47
src/entity/entity_defs.lua
Normal file
@@ -0,0 +1,47 @@
|
||||
|
||||
|
||||
ENTITY_DEFS = {
|
||||
['player'] = {
|
||||
width = 16,
|
||||
height = 16,
|
||||
walkingSpeed = 100,
|
||||
animations = {
|
||||
['walk-left'] = {
|
||||
frames = {67, 68, 69},
|
||||
interval = 0.15,
|
||||
texture = 'entities'
|
||||
},
|
||||
['walk-right'] = {
|
||||
frames = {79, 80, 81},
|
||||
interval = 0.15,
|
||||
texture = 'entities'
|
||||
},
|
||||
['walk-down'] = {
|
||||
frames = {55, 56, 57},
|
||||
interval = 0.15,
|
||||
texture = 'entities'
|
||||
},
|
||||
['walk-up'] = {
|
||||
frames = {91, 92, 93},
|
||||
interval = 0.15,
|
||||
texture = 'entities'
|
||||
},
|
||||
['idle-left'] = {
|
||||
frames = {67},
|
||||
texture = 'entities'
|
||||
},
|
||||
['idle-right'] = {
|
||||
frames = {79},
|
||||
texture = 'entities'
|
||||
},
|
||||
['idle-down'] = {
|
||||
frames = {55},
|
||||
texture = 'entities'
|
||||
},
|
||||
['idle-up'] = {
|
||||
frames = {91},
|
||||
texture = 'entities'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/states/BaseState.lua
Normal file
@@ -0,0 +1,9 @@
|
||||
-- require('mobdebug').start()
|
||||
|
||||
BaseState = Class{}
|
||||
|
||||
function BaseState:init() end
|
||||
function BaseState:enter() end
|
||||
function BaseState:exit() end
|
||||
function BaseState:update(dt) end
|
||||
function BaseState:render() end
|
||||
36
src/states/StateStack.lua
Normal file
@@ -0,0 +1,36 @@
|
||||
-- require('mobdebug').start()
|
||||
|
||||
StateStack = Class{}
|
||||
|
||||
function StateStack:init()
|
||||
self.states = {}
|
||||
end
|
||||
|
||||
function StateStack:update(dt)
|
||||
self.states[#self.states]:update(dt)
|
||||
end
|
||||
|
||||
function StateStack:processAI(params, dt)
|
||||
self.states[#self.states]:processAI(params, dt)
|
||||
end
|
||||
|
||||
function StateStack:render()
|
||||
for i, state in ipairs(self.states) do
|
||||
state:render()
|
||||
end
|
||||
end
|
||||
|
||||
function StateStack:clear()
|
||||
self.states = {}
|
||||
end
|
||||
|
||||
function StateStack:push(state)
|
||||
table.insert(self.states, state)
|
||||
state:enter()
|
||||
end
|
||||
|
||||
function StateStack:pop()
|
||||
-- print("pop state: " .. self.states[#self.states].name)
|
||||
self.states[#self.states]:exit()
|
||||
table.remove(self.states)
|
||||
end
|
||||
18
src/states/entity/EntityBaseState.lua
Normal file
@@ -0,0 +1,18 @@
|
||||
-- require('mobdebug').start()
|
||||
|
||||
EntityBaseState = Class{}
|
||||
|
||||
function EntityBaseState:init(entity)
|
||||
self.entity = entity
|
||||
end
|
||||
|
||||
function EntityBaseState:update(dt) end
|
||||
function EntityBaseState:enter() end
|
||||
function EntityBaseState:exit() end
|
||||
function EntityBaseState:processAI(params, dt) end
|
||||
|
||||
function EntityBaseState:render()
|
||||
local anim = self.entity.currentAnimation
|
||||
love.graphics.draw(gTextures[anim.texture], gFrames[anim.texture][anim:getCurrentFrame()],
|
||||
math.floor(self.entity.x), math.floor(self.entity.y))
|
||||
end
|
||||