diff --git a/assets/images/GolferDown.png b/assets/images/GolferDown.png new file mode 100644 index 0000000..ba9aacc Binary files /dev/null and b/assets/images/GolferDown.png differ diff --git a/assets/images/GolferLeft.png b/assets/images/GolferLeft.png new file mode 100644 index 0000000..e40011a Binary files /dev/null and b/assets/images/GolferLeft.png differ diff --git a/assets/images/GolferRight.png b/assets/images/GolferRight.png index 3950f6f..b9bd254 100644 Binary files a/assets/images/GolferRight.png and b/assets/images/GolferRight.png differ diff --git a/assets/images/GolferUp.png b/assets/images/GolferUp.png new file mode 100644 index 0000000..7f9a68f Binary files /dev/null and b/assets/images/GolferUp.png differ diff --git a/generated/assets.lua b/generated/assets.lua index deb0942..2be4bac 100644 --- a/generated/assets.lua +++ b/generated/assets.lua @@ -9,6 +9,14 @@ Ball = love.graphics.newImage("assets/images/Ball.png") ---@type love.Texture Flag = love.graphics.newImage("assets/images/Flag.png") +-- luacheck: ignore +---@type love.Texture +GolferDown = love.graphics.newImage("assets/images/GolferDown.png") + +-- luacheck: ignore +---@type love.Texture +GolferLeft = love.graphics.newImage("assets/images/GolferLeft.png") + -- luacheck: ignore ---@type love.Texture GolferRightActivated = love.graphics.newImage("assets/images/GolferRightActivated.png") @@ -17,6 +25,10 @@ GolferRightActivated = love.graphics.newImage("assets/images/GolferRightActivate ---@type love.Texture GolferRight = love.graphics.newImage("assets/images/GolferRight.png") +-- luacheck: ignore +---@type love.Texture +GolferUp = love.graphics.newImage("assets/images/GolferUp.png") + -- luacheck: ignore ---@type love.Texture Grass = love.graphics.newImage("assets/images/Grass.png") diff --git a/lib/inspect.lua b/lib/inspect.lua new file mode 100644 index 0000000..e6cec1c --- /dev/null +++ b/lib/inspect.lua @@ -0,0 +1,367 @@ +local _tl_compat +if (tonumber((_VERSION or ""):match("[%d.]*$")) or 0) < 5.3 then + local p, m = pcall(require, "compat53.module") + if p then + _tl_compat = m + end +end +local math = _tl_compat and _tl_compat.math or math +local string = _tl_compat and _tl_compat.string or string +local table = _tl_compat and _tl_compat.table or table +local inspect = { Options = {} } + +inspect._VERSION = "inspect.lua 3.1.0" +inspect._URL = "http://github.com/kikito/inspect.lua" +inspect._DESCRIPTION = "human-readable representations of tables" +inspect._LICENSE = [[ + MIT LICENSE + + Copyright (c) 2022 Enrique GarcĂ­a Cota + + 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. +]] +inspect.KEY = setmetatable({}, { + __tostring = function() + return "inspect.KEY" + end, +}) +inspect.METATABLE = setmetatable({}, { + __tostring = function() + return "inspect.METATABLE" + end, +}) + +local tostring = tostring +local rep = string.rep +local match = string.match +local char = string.char +local gsub = string.gsub +local fmt = string.format + +local _rawget +if rawget then + _rawget = rawget +else + _rawget = function(t, k) + return t[k] + end +end + +local function rawpairs(t) + return next, t, nil +end + +local function smartQuote(str) + if match(str, '"') and not match(str, "'") then + return "'" .. str .. "'" + end + return '"' .. gsub(str, '"', '\\"') .. '"' +end + +local shortControlCharEscapes = { + ["\a"] = "\\a", + ["\b"] = "\\b", + ["\f"] = "\\f", + ["\n"] = "\\n", + ["\r"] = "\\r", + ["\t"] = "\\t", + ["\v"] = "\\v", + ["\127"] = "\\127", +} +local longControlCharEscapes = { ["\127"] = "\127" } +for i = 0, 31 do + local ch = char(i) + if not shortControlCharEscapes[ch] then + shortControlCharEscapes[ch] = "\\" .. i + longControlCharEscapes[ch] = fmt("\\%03d", i) + end +end + +local function escape(str) + return (gsub(gsub(gsub(str, "\\", "\\\\"), "(%c)%f[0-9]", longControlCharEscapes), "%c", shortControlCharEscapes)) +end + +local luaKeywords = { + ["and"] = true, + ["break"] = true, + ["do"] = true, + ["else"] = true, + ["elseif"] = true, + ["end"] = true, + ["false"] = true, + ["for"] = true, + ["function"] = true, + ["goto"] = true, + ["if"] = true, + ["in"] = true, + ["local"] = true, + ["nil"] = true, + ["not"] = true, + ["or"] = true, + ["repeat"] = true, + ["return"] = true, + ["then"] = true, + ["true"] = true, + ["until"] = true, + ["while"] = true, +} + +local function isIdentifier(str) + return type(str) == "string" and not not str:match("^[_%a][_%a%d]*$") and not luaKeywords[str] +end + +local flr = math.floor +local function isSequenceKey(k, sequenceLength) + return type(k) == "number" and flr(k) == k and 1 <= k and k <= sequenceLength +end + +local defaultTypeOrders = { + ["number"] = 1, + ["boolean"] = 2, + ["string"] = 3, + ["table"] = 4, + ["function"] = 5, + ["userdata"] = 6, + ["thread"] = 7, +} + +local function sortKeys(a, b) + local ta, tb = type(a), type(b) + + if ta == tb and (ta == "string" or ta == "number") then + return a < b + end + + local dta = defaultTypeOrders[ta] or 100 + local dtb = defaultTypeOrders[tb] or 100 + + return dta == dtb and ta < tb or dta < dtb +end + +local function getKeys(t) + local seqLen = 1 + while _rawget(t, seqLen) ~= nil do + seqLen = seqLen + 1 + end + seqLen = seqLen - 1 + + local keys, keysLen = {}, 0 + for k in rawpairs(t) do + if not isSequenceKey(k, seqLen) then + keysLen = keysLen + 1 + keys[keysLen] = k + end + end + table.sort(keys, sortKeys) + return keys, keysLen, seqLen +end + +local function countCycles(x, cycles) + if type(x) == "table" then + if cycles[x] then + cycles[x] = cycles[x] + 1 + else + cycles[x] = 1 + for k, v in rawpairs(x) do + countCycles(k, cycles) + countCycles(v, cycles) + end + countCycles(getmetatable(x), cycles) + end + end +end + +local function makePath(path, a, b) + local newPath = {} + local len = #path + for i = 1, len do + newPath[i] = path[i] + end + + newPath[len + 1] = a + newPath[len + 2] = b + + return newPath +end + +local function processRecursive(process, item, path, visited) + if item == nil then + return nil + end + if visited[item] then + return visited[item] + end + + local processed = process(item, path) + if type(processed) == "table" then + local processedCopy = {} + visited[item] = processedCopy + local processedKey + + for k, v in rawpairs(processed) do + processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) + if processedKey ~= nil then + processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) + end + end + + local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) + if type(mt) ~= "table" then + mt = nil + end + setmetatable(processedCopy, mt) + processed = processedCopy + end + return processed +end + +local function puts(buf, str) + buf.n = buf.n + 1 + buf[buf.n] = str +end + +local Inspector = {} + +local Inspector_mt = { __index = Inspector } + +local function tabify(inspector) + puts(inspector.buf, inspector.newline .. rep(inspector.indent, inspector.level)) +end + +function Inspector:getId(v) + local id = self.ids[v] + local ids = self.ids + if not id then + local tv = type(v) + id = (ids[tv] or 0) + 1 + ids[v], ids[tv] = id, id + end + return tostring(id) +end + +function Inspector:putValue(v) + local buf = self.buf + local tv = type(v) + if tv == "string" then + puts(buf, smartQuote(escape(v))) + elseif tv == "number" or tv == "boolean" or tv == "nil" or tv == "cdata" or tv == "ctype" then + puts(buf, tostring(v)) + elseif tv == "table" and not self.ids[v] then + local t = v + + if t == inspect.KEY or t == inspect.METATABLE then + puts(buf, tostring(t)) + elseif self.level >= self.depth then + puts(buf, "{...}") + else + if self.cycles[t] > 1 then + puts(buf, fmt("<%d>", self:getId(t))) + end + + local keys, keysLen, seqLen = getKeys(t) + + puts(buf, "{") + self.level = self.level + 1 + + for i = 1, seqLen + keysLen do + if i > 1 then + puts(buf, ",") + end + if i <= seqLen then + puts(buf, " ") + self:putValue(t[i]) + else + local k = keys[i - seqLen] + tabify(self) + if isIdentifier(k) then + puts(buf, k) + else + puts(buf, "[") + self:putValue(k) + puts(buf, "]") + end + puts(buf, " = ") + self:putValue(t[k]) + end + end + + local mt = getmetatable(t) + if type(mt) == "table" then + if seqLen + keysLen > 0 then + puts(buf, ",") + end + tabify(self) + puts(buf, " = ") + self:putValue(mt) + end + + self.level = self.level - 1 + + if keysLen > 0 or type(mt) == "table" then + tabify(self) + elseif seqLen > 0 then + puts(buf, " ") + end + + puts(buf, "}") + end + else + puts(buf, fmt("<%s %d>", tv, self:getId(v))) + end +end + +function inspect.inspect(root, options) + options = options or {} + + local depth = options.depth or math.huge + local newline = options.newline or "\n" + local indent = options.indent or " " + local process = options.process + + if process then + root = processRecursive(process, root, {}, {}) + end + + local cycles = {} + countCycles(root, cycles) + + local inspector = setmetatable({ + buf = { n = 0 }, + ids = {}, + cycles = cycles, + depth = depth, + level = 0, + newline = newline, + indent = indent, + }, Inspector_mt) + + inspector:putValue(root) + + return table.concat(inspector.buf) +end + +setmetatable(inspect, { + __call = function(_, root, options) + return inspect.inspect(root, options) + end, +}) + +_G.Inspect = inspect +return inspect diff --git a/lib/luaunit.lua b/lib/luaunit.lua index fa8ed9b..ec265e8 100644 --- a/lib/luaunit.lua +++ b/lib/luaunit.lua @@ -609,9 +609,8 @@ function M.adjust_err_msg_with_iter(err_msg, iter_msg) end if - (err_msg:find(M.SKIP_PREFIX) == 1) or ( - err_msg:match("(" .. RE_FILE_LINE .. ")" .. M.SKIP_PREFIX .. ".*") ~= nil - ) + (err_msg:find(M.SKIP_PREFIX) == 1) + or (err_msg:match("(" .. RE_FILE_LINE .. ")" .. M.SKIP_PREFIX .. ".*") ~= nil) then -- substitute prefix by iteration message err_msg = err_msg:gsub(".*" .. M.SKIP_PREFIX, iter_msg, 1) diff --git a/lib/preprocess.lua b/lib/preprocess.lua index 54ca5fb..4788d33 100644 --- a/lib/preprocess.lua +++ b/lib/preprocess.lua @@ -1495,9 +1495,10 @@ local unpack = (_VERSION >= "Lua 5.2") and table.unpack or _G.unpack --[[local]] loadLuaString = ( - (_VERSION >= "Lua 5.2" or jit) and function(lua, chunkName, env) - return load(lua, chunkName, "bt", env) - end + (_VERSION >= "Lua 5.2" or jit) + and function(lua, chunkName, env) + return load(lua, chunkName, "bt", env) + end or function(lua, chunkName, env) local chunk, err = loadstring(lua, chunkName) if not chunk then @@ -2811,20 +2812,26 @@ local function doEarlyExpansions(tokensToExpand, stats) outTokens, newTokenAt({ type = "pp_entry", value = "!!", representation = "!!", double = true }, ppSymbolTok) ) - tableInsert(outTokens, newTokenAt({ - type = "punctuation", - value = "(", - representation = "(", - }, ppSymbolTok)) + tableInsert( + outTokens, + newTokenAt({ + type = "punctuation", + value = "(", + representation = "(", + }, ppSymbolTok) + ) tableInsert( outTokens, newTokenAt({ type = "identifier", value = "__EVAL", representation = "__EVAL" }, ppSymbolTok) ) - tableInsert(outTokens, newTokenAt({ - type = "punctuation", - value = "(", - representation = "(", - }, ppSymbolTok)) + tableInsert( + outTokens, + newTokenAt({ + type = "punctuation", + value = "(", + representation = "(", + }, ppSymbolTok) + ) tableInsert( outTokens, newTokenAt( @@ -2832,16 +2839,22 @@ local function doEarlyExpansions(tokensToExpand, stats) ppSymbolTok ) ) - tableInsert(outTokens, newTokenAt({ - type = "punctuation", - value = ")", - representation = ")", - }, ppSymbolTok)) - tableInsert(outTokens, newTokenAt({ - type = "punctuation", - value = ")", - representation = ")", - }, ppSymbolTok)) + tableInsert( + outTokens, + newTokenAt({ + type = "punctuation", + value = ")", + representation = ")", + }, ppSymbolTok) + ) + tableInsert( + outTokens, + newTokenAt({ + type = "punctuation", + value = ")", + representation = ")", + }, ppSymbolTok) + ) -- Anything else. else diff --git a/lib/tiny.lua b/lib/tiny.lua index 6e932fa..960920a 100644 --- a/lib/tiny.lua +++ b/lib/tiny.lua @@ -29,11 +29,14 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---@field index number is the System's index in the World. Lower indexed Systems are processed before higher indices. The index is a read only field; to set the index, use tiny.setSystemIndex(world, system). ---@field indices table 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. +---@field preProcess nil | fun(self, dt: number): nil | table +---@field postProcess nil | fun(self, dt: number) +---@field isDrawSystem nil | boolean --- @module tiny-ecs --- @author Calvin Rose --- @license MIT --- @copyright 2016 +--- @author Calvin Rose +--- @license MIT +--- @copyright 2016 local tiny = {} -- Local versions of standard lua functions diff --git a/main.lua b/main.lua index 8f64446..154dc39 100644 --- a/main.lua +++ b/main.lua @@ -1,5 +1,6 @@ require("tiny-debug") tiny = require("lib/tiny") +require("lib/inspect") require("utils") require("tiny-tools") @@ -11,22 +12,22 @@ require("generated/all-systems") local width, height = love.graphics.getWidth(), love.graphics.getHeight() -local squareSize = 80 -local tileSize = math.floor(squareSize * 1.2) +local squareSide = 80 +local tileSize = math.floor(squareSide * 1.2) local marginSize = 10 +local squareSize = { x = squareSide, y = squareSide } 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, + size = squareSize, gridPosition = gridPosition, - drawAsRectangle = { size = size }, + drawAsRectangle = { size = squareSize }, } end @@ -34,6 +35,8 @@ local function replaceAt(grid, y, x, entity) local current = grid[y][x] grid[y][x] = entity + current.highlighted = nil + entity.parentGrid = grid current.unhighlightSiblings[entity] = true current.unhighlightSiblings[current] = nil entity.unhighlightSiblings = current.unhighlightSiblings @@ -50,10 +53,15 @@ local function replaceAt(grid, y, x, entity) current.above.below = current.below current.below.above = current.above + -- print("replacement: " .. Inspect(entity, { depth = 1 })) World:removeEntity(current) World:addEntity(entity) end +function Replace(toReplace, replacement) + replaceAt(toReplace.parentGrid, toReplace.gridPosition.y, toReplace.gridPosition.x, replacement) +end + function PositionAtGridXy(x, y) return { x = 20 + ((x - 1) * tileSize), @@ -68,13 +76,14 @@ Scenarios = { World:addEntity({ size = cursorSize, moveWithCursor = T.marker, + isCursor = T.marker, canCollideWith = bit.bor(CursorMask, BallMask), position = { x = -999, y = -999 }, highlightsCollided = T.marker, - drawAsRectangle = { - size = cursorSize, - mode = "fill", - }, + -- drawAsRectangle = { + -- size = cursorSize, + -- mode = "fill", + -- }, }) World:addEntity({ position = { x = 0, y = 0 }, @@ -85,6 +94,7 @@ Scenarios = { -- Temporary storages for connecting grid items local previous local unhighlightSiblings = {} + local yxGrid = {} local xCount = 8 @@ -94,6 +104,7 @@ Scenarios = { yxGrid[y] = {} for x = 1, xCount do local current = World:addEntity(emptyTile(unhighlightSiblings, { x = x, y = y })) + current.parentGrid = yxGrid yxGrid[y][x] = current unhighlightSiblings[current] = true current.toLeft = previous @@ -132,7 +143,8 @@ Scenarios = { z = 1, effectsToApply = { endOfRound = T.marker, - } + movement = { x = 0, y = 0 }, + }, }) replaceAt(yxGrid, 2, 2, { drawAsSprite = SmallSandTrap, @@ -140,26 +152,32 @@ Scenarios = { spriteAfterEffect = SmallSandTrapActivated, effectsToApply = { slowsBy = { x = 1, y = 1 }, - movement = { x = 0, y = 0, }, + movement = { x = 0, y = 0 }, }, }) - replaceAt(yxGrid, 1, 1, { + World:addEntity({ + canBeCollidedBy = CursorMask, pickedUpOnClick = T.marker, + size = squareSize, drawAsSprite = GolferRight, z = 1, spriteAfterEffect = GolferRightActivated, effectsToApply = { - movement = { x = 6, y = 0, }, + movement = { x = 99, y = 0 }, }, + position = { x = 20, y = height - 80 }, }) - replaceAt(yxGrid, 1, 7, { + World:addEntity({ + canBeCollidedBy = CursorMask, pickedUpOnClick = T.marker, - drawAsSprite = GolferRight, + size = squareSize, + drawAsSprite = GolferDown, z = 1, spriteAfterEffect = GolferRightActivated, effectsToApply = { - movement = { x = 0, y = 3 }, + movement = { x = 0, y = 99 }, }, + position = { x = 20 + squareSide, y = height - 80 }, }) World:addEntity({ ballEffects = {}, @@ -167,10 +185,15 @@ Scenarios = { gridPosition = { x = 1, y = 1 }, z = 2, }) - -- TODO: Make the start button start + local startButtonX, startButtonY = StartButton:getDimensions() World:addEntity({ + canBeCollidedBy = CursorMask, drawAsSprite = StartButton, + addOnActivate = { + { roundState = "active" }, + }, z = 3, + size = { x = startButtonX, y = startButtonY }, position = { x = width - 120, y = height - 50, @@ -179,7 +202,8 @@ Scenarios = { end, } -Scenarios.firstLevel() +local currentLevel = Scenarios.firstLevel +currentLevel() function love.load() love.graphics.setBackgroundColor(1, 1, 1) @@ -190,8 +214,9 @@ function love.draw() local dt = love.timer.getDelta() if love.keyboard.isDown("r") then World:clearEntities() - Scenarios.firstLevel() + currentLevel() end + World:setSystemIndex(LiveForNFrames, 1) World:update(dt, function(_, system) return not system.isDrawSystem end) diff --git a/systems/collision-resolution.lua b/systems/collision-resolution.lua index 8ae0c32..3303f9d 100644 --- a/systems/collision-resolution.lua +++ b/systems/collision-resolution.lua @@ -1,3 +1,27 @@ +filteredSystem("activated", { activated = T.Entity }, function(e, _, system) + local activated = e.activated + if activated.addOnActivate then + for _, toAdd in ipairs(activated.addOnActivate) do + system.world:addEntity(toAdd) + end + end + if activated.pickedUpOnClick and #HeldByCursor.entities == 0 then + activated.placedOnClick = T.marker + activated.moveWithCursor = T.marker + activated.canBeCollidedBy = 0 + system.world:addEntity(activated) + elseif activated.gridPosition then + for _, held in pairs(HeldByCursor.entities) do + held.placedOnClick = nil + held.moveWithCursor = nil + held.highlighted = nil + held.canBeCollidedBy = CursorMask + Replace(activated, held) + end + end + system.world:removeEntity(e) +end) + Collisions = filteredSystem("collisionResolution", { collisionBetween = T.Collision }, function(e, _, system) local collidedInto, collider = e.collisionBetween[1], e.collisionBetween[2] if collider.highlightsCollided then @@ -13,10 +37,11 @@ Collisions = filteredSystem("collisionResolution", { collisionBetween = T.Collis 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) + if collidedInto.gridPosition then + --print("ready to place " .. #HeldByCursor.entities .. " entities") + end + if MouseJustPressed(1, true) and collider.isCursor then + system.world:addEntity({ activated = collidedInto }) end end system.world:removeEntity(e) diff --git a/systems/draw.lua b/systems/draw.lua index d8cd5ef..d621b48 100644 --- a/systems/draw.lua +++ b/systems/draw.lua @@ -31,7 +31,6 @@ local spriteDrawSystem = drawSystem( if not e.drawAsSprite then return end - local width, height = e.drawAsSprite:getDimensions() gfx.draw(e.drawAsSprite, e.position.x, e.position.y) end ) @@ -63,7 +62,7 @@ drawSystem( gfx.rectangle("fill", bgLeftEdge, bgTopEdge, bgWidth, bgHeight) gfx.setColor(0, 0, 0) - gfx.drawRect("line", bgLeftEdge, bgTopEdge, bgWidth, bgHeight) + gfx.rectangle("line", bgLeftEdge, bgTopEdge, bgWidth, bgHeight) end gfx.print(e.drawAsText.text, bgLeftEdge + margin, bgTopEdge + margin) diff --git a/systems/input.lua b/systems/input.lua index 17cb0a9..cde440d 100644 --- a/systems/input.lua +++ b/systems/input.lua @@ -8,8 +8,30 @@ buttonInputSystem = filteredSystem("buttonInput", { canReceiveButtons = T.marker system.world:addEntity(e) end) +Mouse = filteredSystem("Mouse", { mouseButtonPress = { position = T.XyPair, button = T.number } }) + +function MouseJustPressed(button, clear) + for _, event in pairs(Mouse.entities) do + if event.mouseButtonPress and event.mouseButtonPress.button == button then + if clear then + event.mouseButtonPress = nil + World:removeEntity(event) + end + return true + end + end + return false +end + HeldByCursor = filteredSystem("HeldByCursor", { pickedUpOnClick = T.marker, moveWithCursor = T.marker }) +LiveForNFrames = filteredSystem("liveForNFrames", { liveForNFrames = T.number }, function(e, _, system) + e.liveForNFrames = e.liveForNFrames - 1 + if e.liveForNFrames <= 0 then + system.world:removeEntity(e) + end +end) + function buttonInputSystem:preProcess() if #self.entities == 0 then return @@ -37,6 +59,19 @@ function love.mousemoved(x, y) mouseX, mouseY = x, y end +function love.mousepressed(x, y, button) + World:addEntity({ + mouseButtonPress = { + position = { + x = x, + y = y, + }, + button = button, + }, + liveForNFrames = 1, + }) +end + local keyDebounceSec = 0.1 local delay = 0 @@ -76,7 +111,7 @@ local menuSystem = filteredSystem( if isDown("return") then pressed = true system.world:addEntity({ - round = "start", + roundState = "active", }) end @@ -94,8 +129,13 @@ local menuSystem = filteredSystem( cursorTracking = filteredSystem("cursorTracking", { moveWithCursor = T.marker, position = T.XyPair }, function(e) if mouseInControl then - e.position.x = mouseX - e.position.y = mouseY + local offsetX, offsetY = 0, 0 + if e.size then + offsetX = e.size.x / 2 + offsetY = e.size.y / 2 + end + e.position.x = mouseX - offsetX + e.position.y = mouseY - offsetY end end) diff --git a/systems/rounds.lua b/systems/rounds.lua index ef640fd..b0373c8 100644 --- a/systems/rounds.lua +++ b/systems/rounds.lua @@ -1,7 +1,5 @@ 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 @@ -14,72 +12,96 @@ 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" }) +local roundSystem = filteredSystem("rounds", { roundState = T.str }, function(e) + print("roundState: " .. e.roundState) end) -local stepTimeSec = 0.3 +function roundSystem:preProcess() + if #self.entities ~= 0 then + print("#states: " .. #self.entities) + end +end + +local activeBallEffects = filteredSystem( + "activeBallEffects", + { ballEffects = T.AnyComponent, gridPosition = T.XyPair }, + function(e, dt, system) + 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 + + if effects.endOfRound ~= nil then + effects.endOfRound = nil + system.world:addEntity({ roundState = "end" }) + system.world:removeEntity(e) + print("End of round") + return + end + + for _, _ in pairs(effects) do + -- Return if there are any effects left + print("Setting roundState to animating...") + --system.world:addEntity({ roundState = "animating" }) + --system.world:removeEntity(e) + return + end + + print("End of round") + system.world:addEntity({ roundState = "end" }) + end +) + +local stepTimeSec = 0.1 local stepTimer = stepTimeSec function activeBallEffects:preProcess(dt) stepTimer = stepTimer - dt if stepTimer <= 0 then stepTimer = stepTimeSec - return + else + return tiny.SKIP_PROCESS end + + -- for _, round in pairs(roundSystem.entities) do + -- if round.roundState == "animating" then + -- return tiny.SKIP_PROCESS + -- end + -- end + + for _, round in pairs(roundSystem.entities) do + if round.roundState == "active" then + return + end + end + return tiny.SKIP_PROCESS end - diff --git a/tiny-types.lua b/tiny-types.lua index d81e914..205f198 100644 --- a/tiny-types.lua +++ b/tiny-types.lua @@ -1,12 +1,18 @@ ----@meta +---@meta tiny-ecs ---@class World World = {} function World:add(...) end +---@generic T : Entity +---@param entity T +---@return T function World:addEntity(entity) end +---@generic T : System +---@param system T +---@return T function World:addSystem(system) end function World:remove(...) end @@ -15,17 +21,28 @@ function World:removeEntity(entity) end function World:removeSystem(system) end +--- Manages Entities and Systems marked for deletion or addition. Call this +--- before modifying Systems and Entities outside of a call to `tiny.update`. +--- Do not call this within a call to `tiny.update`. function World:refresh() end ---@param dt number -function World:update(dt) end +---@param systemFilter nil | fun(world: World, system: System): boolean +function World:update(dt, systemFilter) end +--- Removes all Entities from the World. function World:clearEntities() end +--- Removes all Systems from the World. function World:clearSystems() end +--- Gets number of Entities in the World. function World:getEntityCount() end +--- Gets number of Systems in World. function World:getSystemCount() end -function World:setSystemIndex() end +--- Sets the index of a System in the World, and returns the old index. Changes +--- the order in which they Systems processed, because lower indexed Systems are +--- processed first. Returns the old system.index. +function World:setSystemIndex(world, system, index) end