First project-specific commit.
Hardly a game yet, but extremely basic ball movements work!
|
@ -1,4 +1,4 @@
|
|||
std = "lua54+love"
|
||||
stds.project = {
|
||||
globals = {"tiny"},
|
||||
}
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 772 B |
After Width: | Height: | Size: 925 B |
After Width: | Height: | Size: 901 B |
After Width: | Height: | Size: 889 B |
After Width: | Height: | Size: 126 KiB |
After Width: | Height: | Size: 857 B |
After Width: | Height: | Size: 857 B |
After Width: | Height: | Size: 915 B |
After Width: | Height: | Size: 726 B |
|
@ -5,4 +5,5 @@ require("../systems/decay")
|
|||
require("../systems/draw")
|
||||
require("../systems/gravity")
|
||||
require("../systems/input")
|
||||
require("../systems/rounds")
|
||||
require("../systems/velocity")
|
||||
|
|
|
@ -1,11 +1,47 @@
|
|||
-- GENERATED FILE - DO NOT EDIT
|
||||
-- Instead, edit the source file directly: assets.lua2p.
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
Ball = love.graphics.newImage("assets/images/Ball.png")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
Flag = love.graphics.newImage("assets/images/Flag.png")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
GolferRightActivated = love.graphics.newImage("assets/images/GolferRightActivated.png")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
GolferRight = love.graphics.newImage("assets/images/GolferRight.png")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
Grass = love.graphics.newImage("assets/images/Grass.png")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
SandTrap = love.graphics.newImage("assets/images/SandTrap.png")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
SmallSandTrapActivated = love.graphics.newImage("assets/images/SmallSandTrapActivated.png")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
SmallSandTrap = love.graphics.newImage("assets/images/SmallSandTrap.png")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
StartButton = love.graphics.newImage("assets/images/StartButton.png")
|
||||
|
||||
|
||||
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type FontData
|
||||
---@type fun(fontSize: number | nil): love.Font
|
||||
EtBt7001Z0xa = function(fontSize)
|
||||
return love.graphics.newFont("assets/fonts/EtBt7001Z0xa.ttf", fontSize)
|
||||
end
|
||||
|
|
|
@ -25,9 +25,11 @@ function generatedFileWarning()
|
|||
return "-- GENERATED FILE - DO NOT EDIT\n-- Instead, edit the source file directly: assets.lua2p."
|
||||
end)!!(generatedFileWarning())
|
||||
|
||||
!!(dirLookup('assets/images', 'png', 'love.graphics.newImage', 'Image'))
|
||||
!!(dirLookup('assets/sounds', 'wav', 'love.sound.newSoundData', 'SoundData'))
|
||||
!!(dirLookup('assets/music', 'wav', 'love.sound.newSoundData', 'SoundData'))
|
||||
!!(dirLookup('assets/fonts', 'ttf', 'love.graphics.newFont', 'FontData', function(varName, newFunc, file)
|
||||
!!(dirLookup('assets/images', 'png', 'love.graphics.newImage', 'love.Texture'))
|
||||
!!(dirLookup('assets/sounds', 'ogg', 'love.audio.newSource', 'love.Source', function(varName, newFunc, file)
|
||||
return varName .. ' = ' .. newFunc .. '("' .. file .. '", "static")'
|
||||
end))
|
||||
!!(dirLookup('assets/music', 'wav', 'love.sound.newSoundData', 'love.SoundData'))
|
||||
!!(dirLookup('assets/fonts', 'ttf', 'love.graphics.newFont', 'fun(fontSize: number | nil): love.Font', function(varName, newFunc, file)
|
||||
return varName .. ' = function(fontSize)\n return ' .. newFunc .. '("' .. file .. '", fontSize)\nend'
|
||||
end))
|
||||
|
|
2968
lib/luaunit.lua
|
@ -1,7 +1,7 @@
|
|||
#!/bin/sh
|
||||
_=[[
|
||||
_ = [[
|
||||
exec lua "$0" "$@"
|
||||
]]and nil
|
||||
]] and nil
|
||||
--==============================================================
|
||||
--=
|
||||
--= LuaPreprocess command line program
|
||||
|
@ -175,35 +175,35 @@ Handler messages:
|
|||
]]
|
||||
--==============================================================
|
||||
|
||||
|
||||
|
||||
local startTime = os.time()
|
||||
local startTime = os.time()
|
||||
local startClock = os.clock()
|
||||
|
||||
local args = arg
|
||||
|
||||
if not args[0] then error("Expected to run from the Lua interpreter.") end
|
||||
if not args[0] then
|
||||
error("Expected to run from the Lua interpreter.")
|
||||
end
|
||||
local pp = dofile((args[0]:gsub("[^/\\]+$", "preprocess.lua")))
|
||||
|
||||
-- From args:
|
||||
local addLineNumbers = false
|
||||
local addLineNumbers = false
|
||||
local allowBacktickStrings = false
|
||||
local allowJitSyntax = false
|
||||
local canOutputNil = true
|
||||
local customData = nil
|
||||
local fastStrings = false
|
||||
local hasOutputExtension = false
|
||||
local hasOutputPaths = false
|
||||
local isDebug = false
|
||||
local outputExtension = "lua"
|
||||
local outputMeta = false -- flag|path
|
||||
local processingInfoPath = ""
|
||||
local silent = false
|
||||
local validate = true
|
||||
local macroPrefix = ""
|
||||
local macroSuffix = ""
|
||||
local releaseMode = false
|
||||
local maxLogLevel = "trace"
|
||||
local allowJitSyntax = false
|
||||
local canOutputNil = true
|
||||
local customData = nil
|
||||
local fastStrings = false
|
||||
local hasOutputExtension = false
|
||||
local hasOutputPaths = false
|
||||
local isDebug = false
|
||||
local outputExtension = "lua"
|
||||
local outputMeta = false -- flag|path
|
||||
local processingInfoPath = ""
|
||||
local silent = false
|
||||
local validate = true
|
||||
local macroPrefix = ""
|
||||
local macroSuffix = ""
|
||||
local releaseMode = false
|
||||
local maxLogLevel = "trace"
|
||||
local strictMacroArguments = true
|
||||
|
||||
--==============================================================
|
||||
|
@ -212,46 +212,50 @@ local strictMacroArguments = true
|
|||
local F = string.format
|
||||
|
||||
local function formatBytes(n)
|
||||
if n >= 1024*1024*1024 then
|
||||
return F("%.2f GiB", n/(1024*1024*1024))
|
||||
elseif n >= 1024*1024 then
|
||||
return F("%.2f MiB", n/(1024*1024))
|
||||
elseif n >= 1024 then
|
||||
return F("%.2f KiB", n/(1024))
|
||||
elseif n == 1 then
|
||||
return F("1 byte", n)
|
||||
else
|
||||
return F("%d bytes", n)
|
||||
end
|
||||
if n >= 1024 * 1024 * 1024 then
|
||||
return F("%.2f GiB", n / (1024 * 1024 * 1024))
|
||||
elseif n >= 1024 * 1024 then
|
||||
return F("%.2f MiB", n / (1024 * 1024))
|
||||
elseif n >= 1024 then
|
||||
return F("%.2f KiB", n / 1024)
|
||||
elseif n == 1 then
|
||||
return F("1 byte", n)
|
||||
else
|
||||
return F("%d bytes", n)
|
||||
end
|
||||
end
|
||||
|
||||
local function printfNoise(s, ...)
|
||||
print(s:format(...))
|
||||
print(s:format(...))
|
||||
end
|
||||
local function printError(s)
|
||||
io.stderr:write(s, "\n")
|
||||
io.stderr:write(s, "\n")
|
||||
end
|
||||
local function printfError(s, ...)
|
||||
io.stderr:write(s:format(...), "\n")
|
||||
io.stderr:write(s:format(...), "\n")
|
||||
end
|
||||
|
||||
local function errorLine(err)
|
||||
printError(pp.tryToFormatError(err))
|
||||
os.exit(1)
|
||||
printError(pp.tryToFormatError(err))
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
local loadLuaFile = (
|
||||
(_VERSION >= "Lua 5.2" or jit) and function(path, env)
|
||||
return loadfile(path, "bt", env)
|
||||
end
|
||||
or function(path, env)
|
||||
local chunk, err = loadfile(path)
|
||||
if not chunk then return nil, err end
|
||||
(_VERSION >= "Lua 5.2" or jit) and function(path, env)
|
||||
return loadfile(path, "bt", env)
|
||||
end
|
||||
or function(path, env)
|
||||
local chunk, err = loadfile(path)
|
||||
if not chunk then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
if env then setfenv(chunk, env) end
|
||||
if env then
|
||||
setfenv(chunk, env)
|
||||
end
|
||||
|
||||
return chunk
|
||||
end
|
||||
return chunk
|
||||
end
|
||||
)
|
||||
|
||||
--==============================================================
|
||||
|
@ -264,367 +268,339 @@ io.stderr:setvbuf("no")
|
|||
math.randomseed(os.time()) -- In case math.random() is used anywhere.
|
||||
math.random() -- Must kickstart...
|
||||
|
||||
local processOptions = true
|
||||
local processOptions = true
|
||||
local messageHandlerPath = ""
|
||||
local pathsIn = {}
|
||||
local pathsOut = {}
|
||||
local pathsIn = {}
|
||||
local pathsOut = {}
|
||||
|
||||
for _, arg in ipairs(args) do
|
||||
if processOptions and (arg:find"^%-%-?help$" or arg == "/?" or arg:find"^/[Hh][Ee][Ll][Pp]$") then
|
||||
print("LuaPreprocess v"..pp.VERSION)
|
||||
print((help:gsub("\t", " ")))
|
||||
os.exit()
|
||||
if processOptions and (arg:find("^%-%-?help$") or arg == "/?" or arg:find("^/[Hh][Ee][Ll][Pp]$")) then
|
||||
print("LuaPreprocess v" .. pp.VERSION)
|
||||
print((help:gsub("\t", " ")))
|
||||
os.exit()
|
||||
elseif not (processOptions and arg:find("^%-.")) then
|
||||
local paths = (hasOutputPaths and #pathsOut < #pathsIn) and pathsOut or pathsIn
|
||||
table.insert(paths, arg)
|
||||
|
||||
elseif not (processOptions and arg:find"^%-.") then
|
||||
local paths = (hasOutputPaths and #pathsOut < #pathsIn) and pathsOut or pathsIn
|
||||
table.insert(paths, arg)
|
||||
|
||||
if arg == "-" and (not hasOutputPaths or paths == pathsOut) then
|
||||
silent = true
|
||||
end
|
||||
|
||||
elseif arg == "--" then
|
||||
processOptions = false
|
||||
|
||||
elseif arg:find"^%-%-data=" or arg:find"^%-d=" then
|
||||
customData = arg:gsub("^.-=", "")
|
||||
|
||||
elseif arg == "--backtickstrings" then
|
||||
allowBacktickStrings = true
|
||||
|
||||
elseif arg == "--debug" then
|
||||
isDebug = true
|
||||
outputMeta = outputMeta or true
|
||||
|
||||
elseif arg:find"^%-%-handler=" or arg:find"^%-h=" then
|
||||
messageHandlerPath = arg:gsub("^.-=", "")
|
||||
|
||||
elseif arg == "--jitsyntax" then
|
||||
allowJitSyntax = true
|
||||
|
||||
elseif arg == "--linenumbers" then
|
||||
addLineNumbers = true
|
||||
|
||||
elseif arg == "--meta" then
|
||||
outputMeta = true
|
||||
elseif arg:find"^%-%-meta=" then
|
||||
outputMeta = arg:gsub("^.-=", "")
|
||||
|
||||
elseif arg == "--nonil" then
|
||||
canOutputNil = false
|
||||
|
||||
elseif arg == "--novalidate" then
|
||||
validate = false
|
||||
|
||||
elseif arg:find"^%-%-outputextension=" then
|
||||
if hasOutputPaths then
|
||||
errorLine("Cannot specify both --outputextension and --outputpaths")
|
||||
end
|
||||
hasOutputExtension = true
|
||||
outputExtension = arg:gsub("^.-=", "")
|
||||
|
||||
elseif arg == "--outputpaths" or arg == "-o" then
|
||||
if hasOutputExtension then
|
||||
errorLine("Cannot specify both --outputpaths and --outputextension")
|
||||
elseif pathsIn[1] then
|
||||
errorLine(arg.." must appear before any input path.")
|
||||
end
|
||||
hasOutputPaths = true
|
||||
|
||||
elseif arg:find"^%-%-saveinfo=" or arg:find"^%-i=" then
|
||||
processingInfoPath = arg:gsub("^.-=", "")
|
||||
|
||||
elseif arg == "--silent" then
|
||||
silent = true
|
||||
|
||||
elseif arg == "--faststrings" then
|
||||
fastStrings = true
|
||||
|
||||
elseif arg == "--nogc" then
|
||||
collectgarbage("stop")
|
||||
|
||||
elseif arg:find"^%-%-macroprefix=" then
|
||||
macroPrefix = arg:gsub("^.-=", "")
|
||||
|
||||
elseif arg:find"^%-%-macrosuffix=" then
|
||||
macroSuffix = arg:gsub("^.-=", "")
|
||||
|
||||
elseif arg == "--release" then
|
||||
releaseMode = true
|
||||
|
||||
elseif arg:find"^%-%-loglevel=" then
|
||||
maxLogLevel = arg:gsub("^.-=", "")
|
||||
|
||||
elseif arg == "--version" then
|
||||
io.stdout:write(pp.VERSION)
|
||||
os.exit()
|
||||
|
||||
elseif arg == "--nostrictmacroarguments" then
|
||||
strictMacroArguments = false
|
||||
|
||||
else
|
||||
errorLine("Unknown option '"..arg:gsub("=.*", "").."'.")
|
||||
end
|
||||
if arg == "-" and (not hasOutputPaths or paths == pathsOut) then
|
||||
silent = true
|
||||
end
|
||||
elseif arg == "--" then
|
||||
processOptions = false
|
||||
elseif arg:find("^%-%-data=") or arg:find("^%-d=") then
|
||||
customData = arg:gsub("^.-=", "")
|
||||
elseif arg == "--backtickstrings" then
|
||||
allowBacktickStrings = true
|
||||
elseif arg == "--debug" then
|
||||
isDebug = true
|
||||
outputMeta = outputMeta or true
|
||||
elseif arg:find("^%-%-handler=") or arg:find("^%-h=") then
|
||||
messageHandlerPath = arg:gsub("^.-=", "")
|
||||
elseif arg == "--jitsyntax" then
|
||||
allowJitSyntax = true
|
||||
elseif arg == "--linenumbers" then
|
||||
addLineNumbers = true
|
||||
elseif arg == "--meta" then
|
||||
outputMeta = true
|
||||
elseif arg:find("^%-%-meta=") then
|
||||
outputMeta = arg:gsub("^.-=", "")
|
||||
elseif arg == "--nonil" then
|
||||
canOutputNil = false
|
||||
elseif arg == "--novalidate" then
|
||||
validate = false
|
||||
elseif arg:find("^%-%-outputextension=") then
|
||||
if hasOutputPaths then
|
||||
errorLine("Cannot specify both --outputextension and --outputpaths")
|
||||
end
|
||||
hasOutputExtension = true
|
||||
outputExtension = arg:gsub("^.-=", "")
|
||||
elseif arg == "--outputpaths" or arg == "-o" then
|
||||
if hasOutputExtension then
|
||||
errorLine("Cannot specify both --outputpaths and --outputextension")
|
||||
elseif pathsIn[1] then
|
||||
errorLine(arg .. " must appear before any input path.")
|
||||
end
|
||||
hasOutputPaths = true
|
||||
elseif arg:find("^%-%-saveinfo=") or arg:find("^%-i=") then
|
||||
processingInfoPath = arg:gsub("^.-=", "")
|
||||
elseif arg == "--silent" then
|
||||
silent = true
|
||||
elseif arg == "--faststrings" then
|
||||
fastStrings = true
|
||||
elseif arg == "--nogc" then
|
||||
collectgarbage("stop")
|
||||
elseif arg:find("^%-%-macroprefix=") then
|
||||
macroPrefix = arg:gsub("^.-=", "")
|
||||
elseif arg:find("^%-%-macrosuffix=") then
|
||||
macroSuffix = arg:gsub("^.-=", "")
|
||||
elseif arg == "--release" then
|
||||
releaseMode = true
|
||||
elseif arg:find("^%-%-loglevel=") then
|
||||
maxLogLevel = arg:gsub("^.-=", "")
|
||||
elseif arg == "--version" then
|
||||
io.stdout:write(pp.VERSION)
|
||||
os.exit()
|
||||
elseif arg == "--nostrictmacroarguments" then
|
||||
strictMacroArguments = false
|
||||
else
|
||||
errorLine("Unknown option '" .. arg:gsub("=.*", "") .. "'.")
|
||||
end
|
||||
end
|
||||
|
||||
if silent then
|
||||
printfNoise = function()end
|
||||
printfNoise = function() end
|
||||
end
|
||||
|
||||
local header = "= LuaPreprocess v"..pp.VERSION..os.date(", %Y-%m-%d %H:%M:%S =", startTime)
|
||||
local header = "= LuaPreprocess v" .. pp.VERSION .. os.date(", %Y-%m-%d %H:%M:%S =", startTime)
|
||||
printfNoise(("="):rep(#header))
|
||||
printfNoise("%s", header)
|
||||
printfNoise(("="):rep(#header))
|
||||
|
||||
if hasOutputPaths and #pathsOut < #pathsIn then
|
||||
errorLine("Missing output path for "..pathsIn[#pathsIn])
|
||||
errorLine("Missing output path for " .. pathsIn[#pathsIn])
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- Prepare metaEnvironment.
|
||||
pp.metaEnvironment.dataFromCommandLine = customData -- May be nil.
|
||||
|
||||
|
||||
|
||||
-- Load message handler.
|
||||
local messageHandler = nil
|
||||
|
||||
local function hasMessageHandler(message)
|
||||
if not messageHandler then
|
||||
return false
|
||||
|
||||
elseif type(messageHandler) == "function" then
|
||||
return true
|
||||
|
||||
elseif type(messageHandler) == "table" then
|
||||
return messageHandler[message] ~= nil
|
||||
|
||||
else
|
||||
assert(false)
|
||||
end
|
||||
if not messageHandler then
|
||||
return false
|
||||
elseif type(messageHandler) == "function" then
|
||||
return true
|
||||
elseif type(messageHandler) == "table" then
|
||||
return messageHandler[message] ~= nil
|
||||
else
|
||||
assert(false)
|
||||
end
|
||||
end
|
||||
|
||||
local function sendMessage(message, ...)
|
||||
if not messageHandler then
|
||||
return
|
||||
if not messageHandler then
|
||||
return
|
||||
elseif type(messageHandler) == "function" then
|
||||
local returnValues = pp.pack(messageHandler(message, ...))
|
||||
return pp.unpack(returnValues, 1, returnValues.n)
|
||||
elseif type(messageHandler) == "table" then
|
||||
local _messageHandler = messageHandler[message]
|
||||
if not _messageHandler then
|
||||
return
|
||||
end
|
||||
|
||||
elseif type(messageHandler) == "function" then
|
||||
local returnValues = pp.pack(messageHandler(message, ...))
|
||||
return pp.unpack(returnValues, 1, returnValues.n)
|
||||
|
||||
elseif type(messageHandler) == "table" then
|
||||
local _messageHandler = messageHandler[message]
|
||||
if not _messageHandler then return end
|
||||
|
||||
local returnValues = pp.pack(_messageHandler(...))
|
||||
return pp.unpack(returnValues, 1, returnValues.n)
|
||||
|
||||
else
|
||||
assert(false)
|
||||
end
|
||||
local returnValues = pp.pack(_messageHandler(...))
|
||||
return pp.unpack(returnValues, 1, returnValues.n)
|
||||
else
|
||||
assert(false)
|
||||
end
|
||||
end
|
||||
|
||||
if messageHandlerPath ~= "" then
|
||||
-- Make the message handler and the metaprogram share the same environment.
|
||||
-- This way the message handler can easily define globals that the metaprogram uses.
|
||||
local mainChunk, err = loadLuaFile(messageHandlerPath, pp.metaEnvironment)
|
||||
if not mainChunk then
|
||||
errorLine("Could not load message handler...\n"..pp.tryToFormatError(err))
|
||||
end
|
||||
-- Make the message handler and the metaprogram share the same environment.
|
||||
-- This way the message handler can easily define globals that the metaprogram uses.
|
||||
local mainChunk, err = loadLuaFile(messageHandlerPath, pp.metaEnvironment)
|
||||
if not mainChunk then
|
||||
errorLine("Could not load message handler...\n" .. pp.tryToFormatError(err))
|
||||
end
|
||||
|
||||
messageHandler = mainChunk()
|
||||
messageHandler = mainChunk()
|
||||
|
||||
if type(messageHandler) == "function" then
|
||||
-- void
|
||||
elseif type(messageHandler) == "table" then
|
||||
for message, _messageHandler in pairs(messageHandler) do
|
||||
if type(message) ~= "string" then
|
||||
errorLine(messageHandlerPath..": Table of handlers must only contain messages as keys.")
|
||||
elseif type(_messageHandler) ~= "function" then
|
||||
errorLine(messageHandlerPath..": Table of handlers must only contain functions as values.")
|
||||
end
|
||||
end
|
||||
else
|
||||
errorLine(messageHandlerPath..": File did not return a table or a function.")
|
||||
end
|
||||
if type(messageHandler) == "function" then
|
||||
-- void
|
||||
elseif type(messageHandler) == "table" then
|
||||
for message, _messageHandler in pairs(messageHandler) do
|
||||
if type(message) ~= "string" then
|
||||
errorLine(messageHandlerPath .. ": Table of handlers must only contain messages as keys.")
|
||||
elseif type(_messageHandler) ~= "function" then
|
||||
errorLine(messageHandlerPath .. ": Table of handlers must only contain functions as values.")
|
||||
end
|
||||
end
|
||||
else
|
||||
errorLine(messageHandlerPath .. ": File did not return a table or a function.")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- Init stuff.
|
||||
sendMessage("init", pathsIn, (hasOutputPaths and pathsOut or nil)) -- @Incomplete: Use pcall and format error message better?
|
||||
|
||||
if not hasOutputPaths then
|
||||
for i, pathIn in ipairs(pathsIn) do
|
||||
pathsOut[i] = (pathIn == "-") and "-" or pathIn:gsub("%.%w+$", "").."."..outputExtension
|
||||
end
|
||||
for i, pathIn in ipairs(pathsIn) do
|
||||
pathsOut[i] = (pathIn == "-") and "-" or pathIn:gsub("%.%w+$", "") .. "." .. outputExtension
|
||||
end
|
||||
end
|
||||
|
||||
if not pathsIn[1] then
|
||||
errorLine("No path(s) specified.")
|
||||
errorLine("No path(s) specified.")
|
||||
elseif #pathsIn ~= #pathsOut then
|
||||
errorLine(F("Number of input and output paths differ. (%d in, %d out)", #pathsIn, #pathsOut))
|
||||
errorLine(F("Number of input and output paths differ. (%d in, %d out)", #pathsIn, #pathsOut))
|
||||
end
|
||||
|
||||
local pathsSetIn = {}
|
||||
local pathsSetIn = {}
|
||||
local pathsSetOut = {}
|
||||
|
||||
for i = 1, #pathsIn do
|
||||
if pathsSetIn [pathsIn [i]] then errorLine("Duplicate input path: " ..pathsIn [i]) end
|
||||
if pathsSetOut[pathsOut[i]] then errorLine("Duplicate output path: "..pathsOut[i]) end
|
||||
if pathsSetIn[pathsIn[i]] then
|
||||
errorLine("Duplicate input path: " .. pathsIn[i])
|
||||
end
|
||||
if pathsSetOut[pathsOut[i]] then
|
||||
errorLine("Duplicate output path: " .. pathsOut[i])
|
||||
end
|
||||
|
||||
pathsSetIn [pathsIn [i]] = true
|
||||
pathsSetOut[pathsOut[i]] = true
|
||||
pathsSetIn[pathsIn[i]] = true
|
||||
pathsSetOut[pathsOut[i]] = true
|
||||
|
||||
if pathsIn [i] ~= "-" and pathsSetOut[pathsIn [i]] then errorLine("Path is both input and output: "..pathsIn [i]) end
|
||||
if pathsOut[i] ~= "-" and pathsSetIn [pathsOut[i]] then errorLine("Path is both input and output: "..pathsOut[i]) end
|
||||
if pathsIn[i] ~= "-" and pathsSetOut[pathsIn[i]] then
|
||||
errorLine("Path is both input and output: " .. pathsIn[i])
|
||||
end
|
||||
if pathsOut[i] ~= "-" and pathsSetIn[pathsOut[i]] then
|
||||
errorLine("Path is both input and output: " .. pathsOut[i])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- Process files.
|
||||
|
||||
-- :SavedInfo
|
||||
local processingInfo = {
|
||||
date = os.date("%Y-%m-%d %H:%M:%S", startTime),
|
||||
files = {},
|
||||
date = os.date("%Y-%m-%d %H:%M:%S", startTime),
|
||||
files = {},
|
||||
}
|
||||
|
||||
local byteCount = 0
|
||||
local lineCount = 0
|
||||
local byteCount = 0
|
||||
local lineCount = 0
|
||||
local lineCountCode = 0
|
||||
local tokenCount = 0
|
||||
local tokenCount = 0
|
||||
|
||||
for i, pathIn in ipairs(pathsIn) do
|
||||
local startClockForPath = os.clock()
|
||||
printfNoise("Processing '%s'...", pathIn)
|
||||
local startClockForPath = os.clock()
|
||||
printfNoise("Processing '%s'...", pathIn)
|
||||
|
||||
local pathOut = pathsOut[i]
|
||||
local pathMeta = (type(outputMeta) == "string") and outputMeta or pathOut:gsub("%.%w+$", "")..".meta.lua"
|
||||
local pathOut = pathsOut[i]
|
||||
local pathMeta = (type(outputMeta) == "string") and outputMeta or pathOut:gsub("%.%w+$", "") .. ".meta.lua"
|
||||
|
||||
if not outputMeta or pathOut == "-" then
|
||||
pathMeta = nil
|
||||
end
|
||||
if not outputMeta or pathOut == "-" then
|
||||
pathMeta = nil
|
||||
end
|
||||
|
||||
local info, err = pp.processFile{
|
||||
pathIn = pathIn,
|
||||
pathMeta = pathMeta,
|
||||
pathOut = pathOut,
|
||||
local info, err = pp.processFile({
|
||||
pathIn = pathIn,
|
||||
pathMeta = pathMeta,
|
||||
pathOut = pathOut,
|
||||
|
||||
debug = isDebug,
|
||||
addLineNumbers = addLineNumbers,
|
||||
debug = isDebug,
|
||||
addLineNumbers = addLineNumbers,
|
||||
|
||||
backtickStrings = allowBacktickStrings,
|
||||
jitSyntax = allowJitSyntax,
|
||||
canOutputNil = canOutputNil,
|
||||
fastStrings = fastStrings,
|
||||
validate = validate,
|
||||
strictMacroArguments = strictMacroArguments,
|
||||
backtickStrings = allowBacktickStrings,
|
||||
jitSyntax = allowJitSyntax,
|
||||
canOutputNil = canOutputNil,
|
||||
fastStrings = fastStrings,
|
||||
validate = validate,
|
||||
strictMacroArguments = strictMacroArguments,
|
||||
|
||||
macroPrefix = macroPrefix,
|
||||
macroSuffix = macroSuffix,
|
||||
macroPrefix = macroPrefix,
|
||||
macroSuffix = macroSuffix,
|
||||
|
||||
release = releaseMode,
|
||||
logLevel = maxLogLevel,
|
||||
release = releaseMode,
|
||||
logLevel = maxLogLevel,
|
||||
|
||||
onInsert = (hasMessageHandler("insert") or nil) and function(name)
|
||||
local lua = sendMessage("insert", pathIn, name)
|
||||
onInsert = (hasMessageHandler("insert") or nil) and function(name)
|
||||
local lua = sendMessage("insert", pathIn, name)
|
||||
|
||||
-- onInsert() is expected to return a Lua code string and so is the message
|
||||
-- handler. However, if the handler is a single catch-all function we allow
|
||||
-- the message to not be handled and we fall back to the default behavior of
|
||||
-- treating 'name' as a path to a file to be inserted. If we didn't allow this
|
||||
-- then it would be required for the "insert" message to be handled. I think
|
||||
-- it's better if the user can choose whether to handle a message or not!
|
||||
--
|
||||
if lua == nil and type(messageHandler) == "function" then
|
||||
return assert(pp.readFile(name))
|
||||
end
|
||||
-- onInsert() is expected to return a Lua code string and so is the message
|
||||
-- handler. However, if the handler is a single catch-all function we allow
|
||||
-- the message to not be handled and we fall back to the default behavior of
|
||||
-- treating 'name' as a path to a file to be inserted. If we didn't allow this
|
||||
-- then it would be required for the "insert" message to be handled. I think
|
||||
-- it's better if the user can choose whether to handle a message or not!
|
||||
--
|
||||
if lua == nil and type(messageHandler) == "function" then
|
||||
return assert(pp.readFile(name))
|
||||
end
|
||||
|
||||
return lua
|
||||
end,
|
||||
return lua
|
||||
end,
|
||||
|
||||
onBeforeMeta = messageHandler and function(lua)
|
||||
sendMessage("beforemeta", pathIn, lua)
|
||||
end,
|
||||
onBeforeMeta = messageHandler and function(lua)
|
||||
sendMessage("beforemeta", pathIn, lua)
|
||||
end,
|
||||
|
||||
onAfterMeta = messageHandler and function(lua)
|
||||
local luaModified = sendMessage("aftermeta", pathIn, lua)
|
||||
onAfterMeta = messageHandler
|
||||
and function(lua)
|
||||
local luaModified = sendMessage("aftermeta", pathIn, lua)
|
||||
|
||||
if type(luaModified) == "string" then
|
||||
lua = luaModified
|
||||
if type(luaModified) == "string" then
|
||||
lua = luaModified
|
||||
elseif luaModified ~= nil then
|
||||
error(
|
||||
F(
|
||||
"%s: Message handler did not return a string for 'aftermeta'. (Got %s)",
|
||||
messageHandlerPath,
|
||||
type(luaModified)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
elseif luaModified ~= nil then
|
||||
error(F(
|
||||
"%s: Message handler did not return a string for 'aftermeta'. (Got %s)",
|
||||
messageHandlerPath, type(luaModified)
|
||||
))
|
||||
end
|
||||
return lua
|
||||
end,
|
||||
|
||||
return lua
|
||||
end,
|
||||
onDone = messageHandler and function(info)
|
||||
sendMessage("filedone", pathIn, pathOut, info)
|
||||
end,
|
||||
|
||||
onDone = messageHandler and function(info)
|
||||
sendMessage("filedone", pathIn, pathOut, info)
|
||||
end,
|
||||
onError = function(err)
|
||||
xpcall(function()
|
||||
sendMessage("fileerror", pathIn, err)
|
||||
end, function(err)
|
||||
printfError("Additional error in 'fileerror' message handler...\n%s", pp.tryToFormatError(err))
|
||||
end)
|
||||
os.exit(1)
|
||||
end,
|
||||
})
|
||||
assert(info, err) -- The onError() handler above should have been called and we should have exited already.
|
||||
|
||||
onError = function(err)
|
||||
xpcall(function()
|
||||
sendMessage("fileerror", pathIn, err)
|
||||
end, function(err)
|
||||
printfError("Additional error in 'fileerror' message handler...\n%s", pp.tryToFormatError(err))
|
||||
end)
|
||||
os.exit(1)
|
||||
end,
|
||||
}
|
||||
assert(info, err) -- The onError() handler above should have been called and we should have exited already.
|
||||
byteCount = byteCount + info.processedByteCount
|
||||
lineCount = lineCount + info.lineCount
|
||||
lineCountCode = lineCountCode + info.linesOfCode
|
||||
tokenCount = tokenCount + info.tokenCount
|
||||
|
||||
byteCount = byteCount + info.processedByteCount
|
||||
lineCount = lineCount + info.lineCount
|
||||
lineCountCode = lineCountCode + info.linesOfCode
|
||||
tokenCount = tokenCount + info.tokenCount
|
||||
if processingInfoPath ~= "" then
|
||||
-- :SavedInfo
|
||||
table.insert(processingInfo.files, info) -- See 'ProcessInfo' in preprocess.lua for what more 'info' contains.
|
||||
end
|
||||
|
||||
if processingInfoPath ~= "" then
|
||||
|
||||
-- :SavedInfo
|
||||
table.insert(processingInfo.files, info) -- See 'ProcessInfo' in preprocess.lua for what more 'info' contains.
|
||||
|
||||
end
|
||||
|
||||
printfNoise("Processing '%s' successful! (%.3fs)", pathIn, os.clock()-startClockForPath)
|
||||
printfNoise(("-"):rep(#header))
|
||||
printfNoise("Processing '%s' successful! (%.3fs)", pathIn, os.clock() - startClockForPath)
|
||||
printfNoise(("-"):rep(#header))
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- Finalize stuff.
|
||||
if processingInfoPath ~= "" then
|
||||
printfNoise("Saving processing info to '%s'.", processingInfoPath)
|
||||
printfNoise("Saving processing info to '%s'.", processingInfoPath)
|
||||
|
||||
local luaParts = {"return"}
|
||||
assert(pp.serialize(luaParts, processingInfo))
|
||||
local lua = table.concat(luaParts)
|
||||
local luaParts = { "return" }
|
||||
assert(pp.serialize(luaParts, processingInfo))
|
||||
local lua = table.concat(luaParts)
|
||||
|
||||
local file = assert(io.open(processingInfoPath, "wb"))
|
||||
file:write(lua)
|
||||
file:close()
|
||||
local file = assert(io.open(processingInfoPath, "wb"))
|
||||
file:write(lua)
|
||||
file:close()
|
||||
end
|
||||
|
||||
printfNoise(
|
||||
"All done! (%.3fs, %.0f file%s, %.0f LOC, %.0f line%s, %.0f token%s, %s)",
|
||||
os.clock()-startClock,
|
||||
#pathsIn, (#pathsIn == 1) and "" or "s",
|
||||
lineCountCode,
|
||||
lineCount, (lineCount == 1) and "" or "s",
|
||||
tokenCount, (tokenCount == 1) and "" or "s",
|
||||
formatBytes(byteCount)
|
||||
"All done! (%.3fs, %.0f file%s, %.0f LOC, %.0f line%s, %.0f token%s, %s)",
|
||||
os.clock() - startClock,
|
||||
#pathsIn,
|
||||
(#pathsIn == 1) and "" or "s",
|
||||
lineCountCode,
|
||||
lineCount,
|
||||
(lineCount == 1) and "" or "s",
|
||||
tokenCount,
|
||||
(tokenCount == 1) and "" or "s",
|
||||
formatBytes(byteCount)
|
||||
)
|
||||
|
||||
sendMessage("alldone") -- @Incomplete: Use pcall and format error message better?
|
||||
|
||||
|
||||
|
||||
--[[!===========================================================
|
||||
|
||||
Copyright © 2018-2022 Marcus 'ReFreezed' Thunström
|
||||
|
@ -648,4 +624,3 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||
SOFTWARE.
|
||||
|
||||
==============================================================]]
|
||||
|
||||
|
|
5819
lib/preprocess.lua
69
lib/tiny.lua
|
@ -30,7 +30,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
---@field indices table<any, any> field is a table of Entity keys to their indices in the entities list. Most Systems can ignore this.
|
||||
---@field modified boolean indicator for if the System has been modified in the last update. If so, the onModify callback will be called on the System in the next update, if it has one. This is usually managed by tiny-ecs, so users should mostly ignore this, too.
|
||||
|
||||
|
||||
--- @module tiny-ecs
|
||||
-- @author Calvin Rose
|
||||
-- @license MIT
|
||||
|
@ -103,14 +102,13 @@ local filterJoin
|
|||
-- A helper function to filters from string
|
||||
local filterBuildString
|
||||
|
||||
|
||||
local function filterJoinRaw(invert, joining_op, ...)
|
||||
local _args = {...}
|
||||
local _args = { ... }
|
||||
|
||||
return function(system, e)
|
||||
local acc
|
||||
local args = _args
|
||||
if joining_op == 'or' then
|
||||
if joining_op == "or" then
|
||||
acc = false
|
||||
for i = 1, #args do
|
||||
local v = args[i]
|
||||
|
@ -119,7 +117,7 @@ local function filterJoinRaw(invert, joining_op, ...)
|
|||
elseif type(v) == "function" then
|
||||
acc = acc or v(system, e)
|
||||
else
|
||||
error 'Filter token must be a string or a filter function.'
|
||||
error("Filter token must be a string or a filter function.")
|
||||
end
|
||||
end
|
||||
else
|
||||
|
@ -131,7 +129,7 @@ local function filterJoinRaw(invert, joining_op, ...)
|
|||
elseif type(v) == "function" then
|
||||
acc = acc and v(system, e)
|
||||
else
|
||||
error 'Filter token must be a string or a filter function.'
|
||||
error("Filter token must be a string or a filter function.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -146,69 +144,68 @@ local function filterJoinRaw(invert, joining_op, ...)
|
|||
end
|
||||
|
||||
do
|
||||
|
||||
function filterJoin(...)
|
||||
local state, value = pcall(filterJoinRaw, ...)
|
||||
if state then return value else return nil, value end
|
||||
if state then
|
||||
return value
|
||||
else
|
||||
return nil, value
|
||||
end
|
||||
end
|
||||
|
||||
local function buildPart(str)
|
||||
local accum = {}
|
||||
local subParts = {}
|
||||
str = str:gsub('%b()', function(p)
|
||||
str = str:gsub("%b()", function(p)
|
||||
subParts[#subParts + 1] = buildPart(p:sub(2, -2))
|
||||
return ('\255%d'):format(#subParts)
|
||||
return ("\255%d"):format(#subParts)
|
||||
end)
|
||||
for invert, part, sep in str:gmatch('(%!?)([^%|%&%!]+)([%|%&]?)') do
|
||||
if part:match('^\255%d+$') then
|
||||
for invert, part, sep in str:gmatch("(%!?)([^%|%&%!]+)([%|%&]?)") do
|
||||
if part:match("^\255%d+$") then
|
||||
local partIndex = tonumber(part:match(part:sub(2)))
|
||||
accum[#accum + 1] = ('%s(%s)')
|
||||
:format(invert == '' and '' or 'not', subParts[partIndex])
|
||||
accum[#accum + 1] = ("%s(%s)"):format(invert == "" and "" or "not", subParts[partIndex])
|
||||
else
|
||||
accum[#accum + 1] = ("(e[%s] %s nil)")
|
||||
:format(make_safe(part), invert == '' and '~=' or '==')
|
||||
accum[#accum + 1] = ("(e[%s] %s nil)"):format(make_safe(part), invert == "" and "~=" or "==")
|
||||
end
|
||||
if sep ~= '' then
|
||||
accum[#accum + 1] = (sep == '|' and ' or ' or ' and ')
|
||||
if sep ~= "" then
|
||||
accum[#accum + 1] = (sep == "|" and " or " or " and ")
|
||||
end
|
||||
end
|
||||
return table.concat(accum)
|
||||
end
|
||||
|
||||
function filterBuildString(str)
|
||||
local source = ("return function(_, e) return %s end")
|
||||
:format(buildPart(str))
|
||||
local source = ("return function(_, e) return %s end"):format(buildPart(str))
|
||||
local loader, err = loadstring(source)
|
||||
if err then
|
||||
error(err)
|
||||
end
|
||||
return loader()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Makes a Filter that selects Entities with all specified Components and
|
||||
-- Filters.
|
||||
function tiny.requireAll(...)
|
||||
return filterJoin(false, 'and', ...)
|
||||
return filterJoin(false, "and", ...)
|
||||
end
|
||||
|
||||
--- Makes a Filter that selects Entities with at least one of the specified
|
||||
-- Components and Filters.
|
||||
function tiny.requireAny(...)
|
||||
return filterJoin(false, 'or', ...)
|
||||
return filterJoin(false, "or", ...)
|
||||
end
|
||||
|
||||
--- Makes a Filter that rejects Entities with all specified Components and
|
||||
-- Filters, and selects all other Entities.
|
||||
function tiny.rejectAll(...)
|
||||
return filterJoin(true, 'and', ...)
|
||||
return filterJoin(true, "and", ...)
|
||||
end
|
||||
|
||||
--- Makes a Filter that rejects Entities with at least one of the specified
|
||||
-- Components and Filters, and selects all other Entities.
|
||||
function tiny.rejectAny(...)
|
||||
return filterJoin(true, 'or', ...)
|
||||
return filterJoin(true, "or", ...)
|
||||
end
|
||||
|
||||
--- Makes a Filter from a string. Syntax of `pattern` is as follows.
|
||||
|
@ -224,7 +221,11 @@ end
|
|||
-- @param pattern
|
||||
function tiny.filter(pattern)
|
||||
local state, value = pcall(filterBuildString, pattern)
|
||||
if state then return value else return nil, value end
|
||||
if state then
|
||||
return value
|
||||
else
|
||||
return nil, value
|
||||
end
|
||||
end
|
||||
|
||||
--- System functions.
|
||||
|
@ -463,8 +464,7 @@ function tiny.world(...)
|
|||
entities = {},
|
||||
|
||||
-- List of Systems
|
||||
systems = {}
|
||||
|
||||
systems = {},
|
||||
}, worldMetaTable)
|
||||
|
||||
tiny_add(ret, ...)
|
||||
|
@ -673,7 +673,6 @@ end
|
|||
|
||||
-- Adds, removes, and changes Entities that have been marked.
|
||||
function tiny_manageEntities(world)
|
||||
|
||||
local e2r = world.entitiesToRemove
|
||||
local e2c = world.entitiesToChange
|
||||
|
||||
|
@ -793,7 +792,6 @@ end
|
|||
-- Systems. If `filter` is not supplied, all Systems are updated. Put this
|
||||
-- function in your main loop.
|
||||
function tiny.update(world, dt, filter)
|
||||
|
||||
tiny_manageSystems(world)
|
||||
tiny_manageEntities(world)
|
||||
|
||||
|
@ -809,8 +807,7 @@ function tiny.update(world, dt, filter)
|
|||
onModify(system, dt)
|
||||
end
|
||||
local preWrap = system.preWrap
|
||||
if preWrap and
|
||||
((not filter) or filter(world, system)) then
|
||||
if preWrap and ((not filter) or filter(world, system)) then
|
||||
preWrap(system, dt)
|
||||
end
|
||||
end
|
||||
|
@ -853,12 +850,10 @@ function tiny.update(world, dt, filter)
|
|||
for i = 1, #systems do
|
||||
local system = systems[i]
|
||||
local postWrap = system.postWrap
|
||||
if postWrap and system.active and
|
||||
((not filter) or filter(world, system)) then
|
||||
if postWrap and system.active and ((not filter) or filter(world, system)) then
|
||||
postWrap(system, dt)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Removes all Entities from the World.
|
||||
|
@ -924,11 +919,11 @@ worldMetaTable = {
|
|||
clearSystems = tiny.clearSystems,
|
||||
getEntityCount = tiny.getEntityCount,
|
||||
getSystemCount = tiny.getSystemCount,
|
||||
setSystemIndex = tiny.setSystemIndex
|
||||
setSystemIndex = tiny.setSystemIndex,
|
||||
},
|
||||
__tostring = function()
|
||||
return "<tiny-ecs_World>"
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
_G.tiny = tiny
|
||||
|
|
190
main.lua
|
@ -9,25 +9,177 @@ require("generated/filter-types")
|
|||
require("generated/assets")
|
||||
require("generated/all-systems")
|
||||
|
||||
local scenarios = {
|
||||
default = function()
|
||||
-- TODO: Add default entities
|
||||
end,
|
||||
textTestScenario = function()
|
||||
local width, height = love.graphics.getWidth(), love.graphics.getHeight()
|
||||
|
||||
local squareSize = 80
|
||||
local tileSize = math.floor(squareSize * 1.2)
|
||||
local marginSize = 10
|
||||
|
||||
CursorMask = 1
|
||||
BallMask = 2
|
||||
|
||||
local function emptyTile(unhighlightSiblings, gridPosition)
|
||||
local size = { x = squareSize, y = squareSize }
|
||||
return {
|
||||
unhighlightSiblings = unhighlightSiblings,
|
||||
canBeCollidedBy = CursorMask,
|
||||
highlightOnMouseOver = T.marker,
|
||||
size = size,
|
||||
gridPosition = gridPosition,
|
||||
drawAsRectangle = { size = size },
|
||||
}
|
||||
end
|
||||
|
||||
local function replaceAt(grid, y, x, entity)
|
||||
local current = grid[y][x]
|
||||
grid[y][x] = entity
|
||||
|
||||
current.unhighlightSiblings[entity] = true
|
||||
current.unhighlightSiblings[current] = nil
|
||||
entity.unhighlightSiblings = current.unhighlightSiblings
|
||||
|
||||
-- entity.position = current.position
|
||||
entity.gridPosition = current.gridPosition
|
||||
entity.toLeft = current.toLeft
|
||||
entity.toRight = current.toRight
|
||||
entity.above = current.above
|
||||
entity.below = current.below
|
||||
|
||||
current.toLeft.toRight = current.toRight
|
||||
current.toRight.toLeft = current.toLeft
|
||||
current.above.below = current.below
|
||||
current.below.above = current.above
|
||||
|
||||
World:removeEntity(current)
|
||||
World:addEntity(entity)
|
||||
end
|
||||
|
||||
function PositionAtGridXy(x, y)
|
||||
return {
|
||||
x = 20 + ((x - 1) * tileSize),
|
||||
y = 60 + ((y - 1) * tileSize),
|
||||
}
|
||||
end
|
||||
|
||||
Scenarios = {
|
||||
---@return table xyGrid Where grid[y][x] is an Entity
|
||||
emptyGrid = function()
|
||||
local cursorSize = { x = 5, y = 5 }
|
||||
World:addEntity({
|
||||
position = { x = 0, y = 600 },
|
||||
drawAsText = {
|
||||
text = "Hello, world!",
|
||||
style = TextStyle.Inverted,
|
||||
size = cursorSize,
|
||||
moveWithCursor = T.marker,
|
||||
canCollideWith = bit.bor(CursorMask, BallMask),
|
||||
position = { x = -999, y = -999 },
|
||||
highlightsCollided = T.marker,
|
||||
drawAsRectangle = {
|
||||
size = cursorSize,
|
||||
mode = "fill",
|
||||
},
|
||||
})
|
||||
World:addEntity({
|
||||
position = { x = 0, y = 0 },
|
||||
drawAsSprite = Grass,
|
||||
z = 0,
|
||||
})
|
||||
|
||||
-- Temporary storages for connecting grid items
|
||||
local previous
|
||||
local unhighlightSiblings = {}
|
||||
local yxGrid = {}
|
||||
|
||||
local xCount = 8
|
||||
local yCount = 5
|
||||
|
||||
for y = 1, yCount do
|
||||
yxGrid[y] = {}
|
||||
for x = 1, xCount do
|
||||
local current = World:addEntity(emptyTile(unhighlightSiblings, { x = x, y = y }))
|
||||
yxGrid[y][x] = current
|
||||
unhighlightSiblings[current] = true
|
||||
current.toLeft = previous
|
||||
if previous ~= nil then
|
||||
if previous.toRight == nil then
|
||||
previous.toRight = current
|
||||
end
|
||||
else
|
||||
current.canReceiveButtons = T.marker
|
||||
current.highlighted = T.marker
|
||||
end
|
||||
if yxGrid[y - 1] and yxGrid[y - 1][x] then
|
||||
current.above = yxGrid[y - 1][x]
|
||||
yxGrid[y - 1][x].below = current
|
||||
end
|
||||
if y == yCount then
|
||||
-- Connect last row to first row
|
||||
yxGrid[1][x].above = current
|
||||
current.below = yxGrid[1][x]
|
||||
end
|
||||
if x == xCount then
|
||||
-- Connect last entry to first Entry
|
||||
current.toRight = yxGrid[y][1]
|
||||
yxGrid[y][1].toLeft = current
|
||||
end
|
||||
previous = current
|
||||
end
|
||||
end
|
||||
return yxGrid
|
||||
end,
|
||||
|
||||
firstLevel = function()
|
||||
local yxGrid = Scenarios.emptyGrid()
|
||||
replaceAt(yxGrid, 4, 7, {
|
||||
drawAsSprite = Flag,
|
||||
z = 1,
|
||||
effectsToApply = {
|
||||
endOfRound = T.marker,
|
||||
}
|
||||
})
|
||||
replaceAt(yxGrid, 2, 2, {
|
||||
drawAsSprite = SmallSandTrap,
|
||||
z = 1,
|
||||
spriteAfterEffect = SmallSandTrapActivated,
|
||||
effectsToApply = {
|
||||
slowsBy = { x = 1, y = 1 },
|
||||
movement = { x = 0, y = 0, },
|
||||
},
|
||||
})
|
||||
replaceAt(yxGrid, 1, 1, {
|
||||
pickedUpOnClick = T.marker,
|
||||
drawAsSprite = GolferRight,
|
||||
z = 1,
|
||||
spriteAfterEffect = GolferRightActivated,
|
||||
effectsToApply = {
|
||||
movement = { x = 6, y = 0, },
|
||||
},
|
||||
})
|
||||
replaceAt(yxGrid, 1, 7, {
|
||||
pickedUpOnClick = T.marker,
|
||||
drawAsSprite = GolferRight,
|
||||
z = 1,
|
||||
spriteAfterEffect = GolferRightActivated,
|
||||
effectsToApply = {
|
||||
movement = { x = 0, y = 3 },
|
||||
},
|
||||
})
|
||||
World:addEntity({
|
||||
ballEffects = {},
|
||||
drawAsSprite = Ball,
|
||||
gridPosition = { x = 1, y = 1 },
|
||||
z = 2,
|
||||
})
|
||||
-- TODO: Make the start button start
|
||||
World:addEntity({
|
||||
drawAsSprite = StartButton,
|
||||
z = 3,
|
||||
position = {
|
||||
x = width - 120,
|
||||
y = height - 50,
|
||||
},
|
||||
velocity = { x = 240, y = -500 },
|
||||
mass = 1,
|
||||
decayAfterSeconds = 10,
|
||||
})
|
||||
end,
|
||||
}
|
||||
|
||||
scenarios.textTestScenario()
|
||||
Scenarios.firstLevel()
|
||||
|
||||
function love.load()
|
||||
love.graphics.setBackgroundColor(1, 1, 1)
|
||||
|
@ -35,5 +187,15 @@ function love.load()
|
|||
end
|
||||
|
||||
function love.draw()
|
||||
World:update(love.timer.getDelta())
|
||||
local dt = love.timer.getDelta()
|
||||
if love.keyboard.isDown("r") then
|
||||
World:clearEntities()
|
||||
Scenarios.firstLevel()
|
||||
end
|
||||
World:update(dt, function(_, system)
|
||||
return not system.isDrawSystem
|
||||
end)
|
||||
World:update(dt, function(_, system)
|
||||
return system.isDrawSystem
|
||||
end)
|
||||
end
|
||||
|
|
|
@ -1,11 +1,24 @@
|
|||
collidingEntities = filteredSystem("collidingEntitites", {
|
||||
velocity = T.XyPair,
|
||||
position = T.XyPair,
|
||||
size = T.XyPair,
|
||||
canCollideWith = T.BitMask,
|
||||
isSolid = Maybe(T.bool),
|
||||
})
|
||||
|
||||
local function intersects(rect, rectOther)
|
||||
local left = rect.position.x
|
||||
local right = rect.position.x + rect.size.x
|
||||
local top = rect.position.y
|
||||
local bottom = rect.position.y + rect.size.y
|
||||
|
||||
local leftOther = rectOther.position.x
|
||||
local rightOther = rectOther.position.x + rectOther.size.x
|
||||
local topOther = rectOther.position.y
|
||||
local bottomOther = rectOther.position.y + rectOther.size.y
|
||||
|
||||
return leftOther < right and left < rightOther and topOther < bottom and top < bottomOther
|
||||
end
|
||||
|
||||
filteredSystem(
|
||||
"collisionDetection",
|
||||
{ position = T.XyPair, size = T.XyPair, canBeCollidedBy = T.BitMask, isSolid = Maybe(T.bool) },
|
||||
|
@ -18,19 +31,7 @@ filteredSystem(
|
|||
and e.canBeCollidedBy
|
||||
and bit.band(collider.canCollideWith, e.canBeCollidedBy) ~= 0
|
||||
then
|
||||
local colliderTop = collider.position.y
|
||||
local colliderBottom = collider.position.y + collider.size.y
|
||||
local entityTop = e.position.y
|
||||
local entityBottom = entityTop + e.size.y
|
||||
|
||||
local withinY = (entityTop > colliderTop and entityTop < colliderBottom)
|
||||
or (entityBottom > colliderTop and entityBottom < colliderBottom)
|
||||
|
||||
if
|
||||
withinY
|
||||
and collider.position.x < e.position.x + e.size.x
|
||||
and collider.position.x + collider.size.x > e.position.x
|
||||
then
|
||||
if intersects(e, collider) then
|
||||
system.world:addEntity({ collisionBetween = { e, collider } })
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,23 @@
|
|||
filteredSystem("collisionResolution", { collisionBetween = T.Collision }, function(e, _, system)
|
||||
Collisions = filteredSystem("collisionResolution", { collisionBetween = T.Collision }, function(e, _, system)
|
||||
local collidedInto, collider = e.collisionBetween[1], e.collisionBetween[2]
|
||||
if collider.highlightsCollided then
|
||||
if collidedInto.unhighlightSiblings then
|
||||
for sibling in pairs(collidedInto.unhighlightSiblings) do
|
||||
sibling.highlighted = nil
|
||||
sibling.canReceiveButtons = nil
|
||||
system.world:addEntity(sibling)
|
||||
end
|
||||
end
|
||||
if collidedInto.highlightOnMouseOver ~= nil then
|
||||
collidedInto.highlighted = T.marker
|
||||
collidedInto.canReceiveButtons = T.marker
|
||||
system.world:addEntity(collidedInto)
|
||||
end
|
||||
if collidedInto.pickedUpOnClick ~= nil then -- and #HeldByCursor.entities == 0 and love.mouse.isDown(1) then
|
||||
print("Click!")
|
||||
collidedInto.moveWithCursor = T.marker
|
||||
system.world:addEntity(collidedInto)
|
||||
end
|
||||
end
|
||||
system.world:removeEntity(e)
|
||||
end)
|
||||
|
|
|
@ -3,4 +3,4 @@ filteredSystem("decay", { decayAfterSeconds = T.number }, function(e, dt, system
|
|||
if e.decayAfterSeconds <= 0 then
|
||||
system.world:removeEntity(e)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
|
|
@ -1,23 +1,52 @@
|
|||
local floor = math.floor
|
||||
local gfx = love.graphics
|
||||
---
|
||||
---@generic T
|
||||
---@param shape T | fun()
|
||||
---@param process fun(entity: T, dt: number, system: System) | nil
|
||||
---@return System | { entities: T[] }
|
||||
local function drawSystem(name, shape, process)
|
||||
local system = filteredSystem(name, shape, process, function(_, a, b)
|
||||
if a.z ~= nil and b.z ~= nil then
|
||||
return a.z < b.z
|
||||
end
|
||||
if a.z ~= nil then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end)
|
||||
system.isDrawSystem = true
|
||||
return system
|
||||
end
|
||||
|
||||
filteredSystem("drawRectangles", { position = T.XyPair, drawAsRectangle = { size = T.XyPair } }, function(e, _, _)
|
||||
gfx.fillRect(e.position.x, e.position.y, e.drawAsRectangle.size.x, e.drawAsRectangle.size.y)
|
||||
filteredSystem("mapGridPositionToRealPosition", { gridPosition = T.XyPair }, function(e, _, system)
|
||||
e.position = PositionAtGridXy(e.gridPosition.x, e.gridPosition.y)
|
||||
system.world:addEntity(e)
|
||||
end)
|
||||
|
||||
filteredSystem("drawSprites", { position = T.XyPair, drawAsSprite = T.pd_image }, function(e)
|
||||
if e.position.y < Camera.pan.y - 240 or e.position.y > Camera.pan.y + 480 then
|
||||
return
|
||||
local spriteDrawSystem = drawSystem(
|
||||
"drawSprites",
|
||||
{ position = T.XyPair, drawAsSprite = T.Drawable, rotation = Maybe(T.number) },
|
||||
function(e)
|
||||
if not e.drawAsSprite then
|
||||
return
|
||||
end
|
||||
local width, height = e.drawAsSprite:getDimensions()
|
||||
gfx.draw(e.drawAsSprite, e.position.x, e.position.y)
|
||||
end
|
||||
e.drawAsSprite:draw(e.position.x, e.position.y)
|
||||
end)
|
||||
)
|
||||
|
||||
function spriteDrawSystem:preProcess()
|
||||
gfx.setColor(1, 1, 1)
|
||||
end
|
||||
|
||||
local margin = 8
|
||||
|
||||
filteredSystem(
|
||||
drawSystem(
|
||||
"drawText",
|
||||
{ position = T.XyPair, drawAsText = { text = T.str, style = Maybe(T.str), font = Maybe(T.pd_font) } },
|
||||
function(e)
|
||||
local font = gfx.getFont() -- e.drawAsText.font or AshevilleSans14Bold
|
||||
local font = e.font or gfx.getFont() -- e.drawAsText.font or AshevilleSans14Bold
|
||||
local textHeight = font:getHeight()
|
||||
local textWidth = font:getWidth(e.drawAsText.text)
|
||||
|
||||
|
@ -40,3 +69,9 @@ filteredSystem(
|
|||
gfx.print(e.drawAsText.text, bgLeftEdge + margin, bgTopEdge + margin)
|
||||
end
|
||||
)
|
||||
|
||||
drawSystem("drawRectangles", { position = T.XyPair, drawAsRectangle = { size = T.XyPair } }, function(e, _, _)
|
||||
gfx.setColor(1, 1, 1, 0.5)
|
||||
local mode = e.drawAsRectangle.mode or (e.highlighted and "fill" or "line")
|
||||
gfx.rectangle(mode, e.position.x, e.position.y, e.drawAsRectangle.size.x, e.drawAsRectangle.size.y)
|
||||
end)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
local isDown = love.keyboard.isDown
|
||||
|
||||
---@type ButtonState
|
||||
local buttonState = {}
|
||||
|
||||
|
@ -6,8 +8,105 @@ buttonInputSystem = filteredSystem("buttonInput", { canReceiveButtons = T.marker
|
|||
system.world:addEntity(e)
|
||||
end)
|
||||
|
||||
HeldByCursor = filteredSystem("HeldByCursor", { pickedUpOnClick = T.marker, moveWithCursor = T.marker })
|
||||
|
||||
function buttonInputSystem:preProcess()
|
||||
if #self.entities == 0 then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function love.keypressed(key, _, _)
|
||||
buttonState[key] = true
|
||||
end
|
||||
|
||||
function ClearButtonState()
|
||||
for key in pairs(buttonState) do
|
||||
buttonState[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
---@type System
|
||||
local cursorTracking
|
||||
|
||||
local mouseX, mouseY = -9999, -9999
|
||||
local mouseInControl = false
|
||||
|
||||
function love.mousemoved(x, y)
|
||||
mouseInControl = true
|
||||
mouseX, mouseY = x, y
|
||||
end
|
||||
|
||||
local keyDebounceSec = 0.1
|
||||
local delay = 0
|
||||
|
||||
local menuSystem = filteredSystem(
|
||||
"menu",
|
||||
{ canReceiveButtons = T.marker, highlighted = T.marker },
|
||||
function(e, dt, system)
|
||||
if delay > 0 then
|
||||
delay = delay - dt
|
||||
return
|
||||
end
|
||||
|
||||
local function tryShiftMenu(target, keys)
|
||||
if target == nil then
|
||||
return false
|
||||
end
|
||||
for _, key in ipairs(keys) do
|
||||
if isDown(key) then
|
||||
e.highlighted = nil
|
||||
e.canReceiveButtons = nil
|
||||
target.canReceiveButtons = T.marker
|
||||
target.highlighted = T.marker
|
||||
system.world:addEntity(e)
|
||||
system.world:addEntity(target)
|
||||
e = target
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local pressed = tryShiftMenu(e.toRight, { "right", "d" })
|
||||
pressed = tryShiftMenu(e.toLeft, { "left", "a" }) or pressed
|
||||
pressed = tryShiftMenu(e.below, { "down", "s" }) or pressed
|
||||
pressed = tryShiftMenu(e.above, { "up", "w" }) or pressed
|
||||
|
||||
if isDown("return") then
|
||||
pressed = true
|
||||
system.world:addEntity({
|
||||
round = "start",
|
||||
})
|
||||
end
|
||||
|
||||
if pressed then
|
||||
mouseInControl = false
|
||||
mouseX, mouseY = -9999, -9999
|
||||
for _, tracker in pairs(cursorTracking.entities) do
|
||||
tracker.position.x = -9999
|
||||
tracker.position.y = -9999
|
||||
end
|
||||
delay = keyDebounceSec
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
cursorTracking = filteredSystem("cursorTracking", { moveWithCursor = T.marker, position = T.XyPair }, function(e)
|
||||
if mouseInControl then
|
||||
e.position.x = mouseX
|
||||
e.position.y = mouseY
|
||||
end
|
||||
end)
|
||||
|
||||
-- local allHighlighted = filteredSystem("allHighlighted", { highlighted = T.marker })
|
||||
|
||||
function cursorTracking:postProcess()
|
||||
-- if mouseInControl and #Collisions.entities == 0 then
|
||||
-- -- If the cursor is not colliding with anything, wipe all highlighted components
|
||||
-- for _, highlighted in pairs(allHighlighted.entities) do
|
||||
-- highlighted.highlighted = nil
|
||||
-- self.world:addEntity(highlighted)
|
||||
-- end
|
||||
-- end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
local gridElements = filteredSystem("gridElements", { gridPosition = T.XyPair, effectsToApply = T.AnyComponent })
|
||||
|
||||
local roundRunning = false
|
||||
|
||||
filteredSystem("timers", { timerSec = T.number, callback = T.SelfFunction }, function(e, dt, system)
|
||||
e.timerSec = e.timerSec - dt
|
||||
if e.timerSec < 0 then
|
||||
e:callback()
|
||||
system.world:removeEntity(e)
|
||||
end
|
||||
end)
|
||||
|
||||
local function sign(n)
|
||||
return n > 0 and 1 or n < 0 and -1 or 0
|
||||
end
|
||||
|
||||
local roundSystem = filteredSystem("rounds", { round = T.str })
|
||||
|
||||
local activeBallEffects = filteredSystem("activeBallEffects", { ballEffects = T.AnyComponent, gridPosition = T.XyPair }, function(e, dt, system)
|
||||
local roundActive = false
|
||||
for _, state in pairs(roundSystem.entities) do
|
||||
if state.round == "start" then
|
||||
roundActive = true
|
||||
end
|
||||
end
|
||||
if not roundActive then
|
||||
return
|
||||
end
|
||||
local gridPosition, effects = e.gridPosition, e.ballEffects
|
||||
|
||||
-- Search for new effects from the current tile
|
||||
for _, gridElement in pairs(gridElements.entities) do
|
||||
if
|
||||
gridPosition.x == gridElement.gridPosition.x
|
||||
and gridPosition.y == gridElement.gridPosition.y
|
||||
then
|
||||
-- More direct-mutation-y than I'd like,
|
||||
-- but offers a simple way to overwrite existing effects.
|
||||
-- We're "setting InRelations" :D
|
||||
for key, value in pairs(gridElement.effectsToApply) do
|
||||
effects[key] = value
|
||||
end
|
||||
if gridElement.spriteAfterEffect then
|
||||
gridElement.drawAsSprite = gridElement.spriteAfterEffect
|
||||
gridElement.spriteAfterEffect = nil
|
||||
end
|
||||
gridElement.effectsToApply = nil
|
||||
system.world:addEntity(gridElement)
|
||||
end
|
||||
end
|
||||
|
||||
-- Apply any effects currently connected to this ball
|
||||
if effects.movement ~= nil then
|
||||
gridPosition.x = gridPosition.x + sign(effects.movement.x)
|
||||
gridPosition.y = gridPosition.y + sign(effects.movement.y)
|
||||
|
||||
effects.movement.x = effects.movement.x - sign(effects.movement.x)
|
||||
effects.movement.y = effects.movement.y - sign(effects.movement.y)
|
||||
|
||||
if effects.movement.x == 0 and effects.movement.y == 0 then
|
||||
effects.movement = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO: Trigger the round end
|
||||
-- for _, _ in pairs(effects) do
|
||||
-- -- Return if there are any effects left
|
||||
-- return
|
||||
-- end
|
||||
|
||||
-- system.world:addEntity({ round = "end" })
|
||||
end)
|
||||
|
||||
local stepTimeSec = 0.3
|
||||
local stepTimer = stepTimeSec
|
||||
|
||||
function activeBallEffects:preProcess(dt)
|
||||
stepTimer = stepTimer - dt
|
||||
if stepTimer <= 0 then
|
||||
stepTimer = stepTimeSec
|
||||
return
|
||||
end
|
||||
return tiny.SKIP_PROCESS
|
||||
end
|
||||
|
|
@ -13,4 +13,4 @@ filteredSystem("drag", { velocity = T.XyPair, drag = T.number }, function(e, dt,
|
|||
local currentDrag = e.drag * dt
|
||||
e.velocity.x = e.velocity.x - (e.velocity.x * currentDrag * dt)
|
||||
e.velocity.y = e.velocity.y - (e.velocity.y * currentDrag * dt)
|
||||
end)
|
||||
end)
|
||||
|
|
|
@ -39,4 +39,4 @@ if tinyWarnWhenNonDataOnEntities then
|
|||
return valType
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
---@generic T
|
||||
---@param shape T | fun()
|
||||
---@param process fun(entity: T, dt: number, system: System) | nil
|
||||
---@param compare nil | fun(system: System, entityA: T, entityB: T): boolean
|
||||
---@return System | { entities: T[] }
|
||||
function filteredSystem(name, shape, process)
|
||||
function filteredSystem(name, shape, process, compare)
|
||||
assert(type(name) == "string")
|
||||
assert(type(shape) == "table" or type(shape) == "function")
|
||||
assert(process == nil or type(process) == "function")
|
||||
|
||||
local system = tiny.processingSystem()
|
||||
local system = compare and tiny.sortedProcessingSystem() or tiny.processingSystem()
|
||||
system.compare = compare
|
||||
system.name = name
|
||||
if type(shape) == "table" then
|
||||
local keys = {}
|
||||
|
|