diff --git a/assets/images/ConveyerDownRight.png b/assets/images/ConveyerDownRight.png new file mode 100644 index 0000000..a0cb785 Binary files /dev/null and b/assets/images/ConveyerDownRight.png differ diff --git a/assets/images/Ramp.png b/assets/images/Ramp.png new file mode 100644 index 0000000..f382536 Binary files /dev/null and b/assets/images/Ramp.png differ diff --git a/assets/images/Teleporter.png b/assets/images/Teleporter.png new file mode 100644 index 0000000..3bbae16 Binary files /dev/null and b/assets/images/Teleporter.png differ diff --git a/assets/images/TeleporterInactive.png b/assets/images/TeleporterInactive.png new file mode 100644 index 0000000..6e7b51f Binary files /dev/null and b/assets/images/TeleporterInactive.png differ diff --git a/generated/all-systems.lua b/generated/all-systems.lua index 2e2451f..7fb93ac 100644 --- a/generated/all-systems.lua +++ b/generated/all-systems.lua @@ -1,3 +1,4 @@ +require("../systems/ballEffects") require("../systems/camera-pan") require("../systems/collision-detection") require("../systems/collision-resolution") @@ -5,5 +6,4 @@ require("../systems/decay") require("../systems/draw") require("../systems/gravity") require("../systems/input") -require("../systems/rounds") require("../systems/velocity") diff --git a/generated/assets.lua b/generated/assets.lua index 2be4bac..c6051b0 100644 --- a/generated/assets.lua +++ b/generated/assets.lua @@ -5,6 +5,10 @@ ---@type love.Texture Ball = love.graphics.newImage("assets/images/Ball.png") +-- luacheck: ignore +---@type love.Texture +ConveyerDownRight = love.graphics.newImage("assets/images/ConveyerDownRight.png") + -- luacheck: ignore ---@type love.Texture Flag = love.graphics.newImage("assets/images/Flag.png") @@ -33,6 +37,10 @@ GolferUp = love.graphics.newImage("assets/images/GolferUp.png") ---@type love.Texture Grass = love.graphics.newImage("assets/images/Grass.png") +-- luacheck: ignore +---@type love.Texture +Ramp = love.graphics.newImage("assets/images/Ramp.png") + -- luacheck: ignore ---@type love.Texture SandTrap = love.graphics.newImage("assets/images/SandTrap.png") diff --git a/main.lua b/main.lua index 154dc39..dbc859b 100644 --- a/main.lua +++ b/main.lua @@ -133,58 +133,7 @@ Scenarios = { 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, - movement = { x = 0, y = 0 }, - }, - }) - replaceAt(yxGrid, 2, 2, { - drawAsSprite = SmallSandTrap, - z = 1, - spriteAfterEffect = SmallSandTrapActivated, - effectsToApply = { - slowsBy = { x = 1, y = 1 }, - movement = { x = 0, y = 0 }, - }, - }) - World:addEntity({ - canBeCollidedBy = CursorMask, - pickedUpOnClick = T.marker, - size = squareSize, - drawAsSprite = GolferRight, - z = 1, - spriteAfterEffect = GolferRightActivated, - effectsToApply = { - movement = { x = 99, y = 0 }, - }, - position = { x = 20, y = height - 80 }, - }) - World:addEntity({ - canBeCollidedBy = CursorMask, - pickedUpOnClick = T.marker, - size = squareSize, - drawAsSprite = GolferDown, - z = 1, - spriteAfterEffect = GolferRightActivated, - effectsToApply = { - movement = { x = 0, y = 99 }, - }, - position = { x = 20 + squareSide, y = height - 80 }, - }) - World:addEntity({ - ballEffects = {}, - drawAsSprite = Ball, - gridPosition = { x = 1, y = 1 }, - z = 2, - }) local startButtonX, startButtonY = StartButton:getDimensions() World:addEntity({ canBeCollidedBy = CursorMask, @@ -199,6 +148,101 @@ Scenarios = { y = height - 50, }, }) + return yxGrid + end, + + firstLevel = function() + local yxGrid = Scenarios.emptyGrid() + local clickables = 0 + ---@param entity table | { effectsToApply: BallEffects } + local function addClickable(entity) + entity.canBeCollidedBy = bit.bor(CursorMask, entity.canBeCollidedBy or 0) + entity.pickedUpOnClick = T.marker + entity.size = squareSize + entity.z = 1 + entity.position = { x = 20 + (clickables * squareSide), y = height - 80 } + + clickables = clickables + 1 + + return World:addEntity(entity) + end + replaceAt(yxGrid, 4, 7, { + drawAsSprite = Flag, + z = 1, + effectsToApply = { + endOfRound = T.marker, + movement = { x = 0, y = 0 }, + }, + }) + replaceAt(yxGrid, 2, 5, { + drawAsSprite = SmallSandTrap, + z = 1, + spriteAfterEffect = SmallSandTrapActivated, + effectsToApply = { + slowsTo = { x = 1, y = 1 }, + --movement = { x = 0, y = 0 }, + }, + }) + replaceAt(yxGrid, 3, 1, { + drawAsSprite = SmallSandTrap, + z = 1, + spriteAfterEffect = SmallSandTrapActivated, + effectsToApply = { + slowsTo = { x = 1, y = 1 }, + --movement = { x = 0, y = 0 }, + }, + }) + addClickable({ + drawAsSprite = GolferRight, + spriteAfterEffect = GolferRightActivated, + effectsToApply = { + movement = { x = 8, y = 0 }, + } + }) + addClickable({ + drawAsSprite = ConveyerDownRight, + spriteAfterEffect = ConveyerDownRight, + effectsToApply = { + movement = { x = 1, y = 1 }, + } + }) + addClickable({ + drawAsSprite = Ramp, + spriteAfterEffect = Ramp, + effectsToApply = { + fliesFor = 1, + slowsBy = { x = 3, y = 3 }, + } + }) + addClickable({ + drawAsSprite = GolferRight, + spriteAfterEffect = GolferRightActivated, + effectsToApply = { + movement = { x = 8, y = 0 }, + }, + }) + addClickable({ + drawAsSprite = GolferDown, + spriteAfterEffect = GolferRightActivated, + effectsToApply = { + movement = { x = 0, y = 6 }, + }, + }) + addClickable({ + drawAsSprite = SmallSandTrap, + spriteAfterEffect = SmallSandTrapActivated, + effectsToApply = { + slowsTo = { x = 1, y = 1 }, + --movement = { x = 0, y = 0 }, + }, + }) + World:addEntity({ + ballEffects = {}, + drawAsSprite = Ball, + gridPosition = { x = 1, y = 1 }, + endAnimating = T.marker, + z = 2, + }) end, } @@ -210,16 +254,29 @@ function love.load() love.graphics.setFont(EtBt7001Z0xa(32)) end +local bail = false + +World:setSystemIndex(LiveForNFrames, 1) + function love.draw() local dt = love.timer.getDelta() + if love.keyboard.isDown("r") then World:clearEntities() currentLevel() + bail = false end - World:setSystemIndex(LiveForNFrames, 1) - World:update(dt, function(_, system) - return not system.isDrawSystem - end) + + if love.keyboard.isDown("f") then + bail = not bail + end + + if not bail then + World:update(dt, function(_, system) + return not system.isDrawSystem + end) + end + World:update(dt, function(_, system) return system.isDrawSystem end) diff --git a/systems/ballEffects.lua b/systems/ballEffects.lua new file mode 100644 index 0000000..20587d7 --- /dev/null +++ b/systems/ballEffects.lua @@ -0,0 +1,159 @@ +local gridElements = filteredSystem("gridElements", { gridPosition = T.XyPair, effectsToApply = T.AnyComponent }) + +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) + +function math.sign(n) + return n > 0 and 1 or n < 0 and -1 or 0 +end + +RoundSystem = filteredSystem("rounds", { roundState = T.str }, function(e) + -- print("roundState: " .. e.roundState) +end) + +---@alias RoundState "active" | "animating" | "end" + +---@param state RoundState +function RoundSystem:remove(state) + for _, round in pairs(self.entities) do + if round.roundState == state then + self.world:removeEntity(round) + end + end +end + +---@param state RoundState +function RoundSystem:hasState(state) + for _, round in pairs(self.entities) do + if round.roundState == state then + return true + end + end + return false +end + +---@param n number +---@param nToSlowBy number +---@return number slowedValue +local function slowBy(n, nToSlowBy) + if math.abs(n) < math.abs(nToSlowBy) then + return 0 + end + return n - (math.sign(n) * nToSlowBy) +end +--- +---@param n number +---@param nToSlowTo number +---@return number slowedValue +local function slowTo(n, nToSlowTo) + if math.abs(n) < math.abs(nToSlowTo) then + return 0 + end + return math.sign(n) * nToSlowTo +end + +---@class BallEffects +---@field slowsTo nil | XyPair +---@field slowsBy nil | XyPair +---@field movement nil | XyPair +---@field fliesFor nil | number + +---@type BallEffects +local BallEffects = {} + +local activeBallEffects = filteredSystem( + "activeBallEffects", + { ballEffects = BallEffects, gridPosition = T.XyPair }, + function(e, _, system) + local gridPosition, effects = e.gridPosition, e.ballEffects + + if effects.fliesFor == nil then + -- 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 + else + effects.fliesFor = effects.fliesFor - 1 + if effects.fliesFor == 0 then + effects.fliesFor = nil + end + end + + -------------------------------------------------------- + -- Apply any effects currently connected to this ball -- + -------------------------------------------------------- + + if effects.movement ~= nil then + if effects.slowsBy ~= nil then + effects.movement.x = slowBy(effects.movement.x, effects.slowsBy.x) + effects.movement.y = slowBy(effects.movement.y, effects.slowsBy.y) + effects.slowsBy = nil + end + if effects.slowsTo ~= nil then + effects.movement.x = slowTo(effects.movement.x, effects.slowsTo.x) + effects.movement.y = slowTo(effects.movement.y, effects.slowsTo.y) + effects.slowsTo = nil + end + + if effects.movement.x == 0 and effects.movement.y == 0 then + effects.movement = nil + else + gridPosition.x = gridPosition.x + math.sign(effects.movement.x) + gridPosition.y = gridPosition.y + math.sign(effects.movement.y) + + effects.movement.x = effects.movement.x - math.sign(effects.movement.x) + effects.movement.y = effects.movement.y - math.sign(effects.movement.y) + 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" }) + system.world:removeEntity(e) + end +) + +function activeBallEffects:preProcess(dt) + if RoundSystem:hasState("animating") then + return tiny.SKIP_PROCESS + end + + if RoundSystem:hasState("active") then + return + end + + return tiny.SKIP_PROCESS +end diff --git a/systems/draw.lua b/systems/draw.lua index d621b48..4861251 100644 --- a/systems/draw.lua +++ b/systems/draw.lua @@ -19,8 +19,43 @@ local function drawSystem(name, shape, process) return system end -filteredSystem("mapGridPositionToRealPosition", { gridPosition = T.XyPair }, function(e, _, system) - e.position = PositionAtGridXy(e.gridPosition.x, e.gridPosition.y) +local mapGridPositions = filteredSystem("mapGridPositionToRealPosition", { gridPosition = T.XyPair }, function(e, dt, system) + local target = PositionAtGridXy(e.gridPosition.x, e.gridPosition.y) + if e.position == nil then + e.position = target + return + end + local speed = 100 + if e.ballEffects and e.ballEffects.movement then + local movement = e.ballEffects.movement + local longTarget = PositionAtGridXy(e.gridPosition.x + movement.x, e.gridPosition.y + movement.y) + local xDiff = e.position.x - longTarget.x + local yDiff = e.position.y - longTarget.y + speed = 30 + math.sqrt((xDiff * xDiff) + (yDiff * yDiff)) + end + local stops = 0 + + local xDiff = target.x - e.position.x + local xChange = dt * speed * math.sign(xDiff) + if math.abs(xDiff) <= math.abs(xChange) then + e.position.x = target.x + stops = stops + 1 + else + e.position.x = e.position.x + xChange + end + + local yDiff = target.y - e.position.y + local yChange = dt * speed * math.sign(yDiff) + if math.abs(yDiff) <= math.abs(yChange) then + e.position.y = target.y + stops = stops + 1 + else + e.position.y = e.position.y + yChange + end + + if e.endAnimating and stops == 2 then + RoundSystem:remove("animating") + end system.world:addEntity(e) end) @@ -71,6 +106,6 @@ drawSystem( 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") + local mode = e.drawAsRectangle.mode or "line" -- (e.highlighted and "fill" or "line") gfx.rectangle(mode, e.position.x, e.position.y, e.drawAsRectangle.size.x, e.drawAsRectangle.size.y) end) diff --git a/systems/input.lua b/systems/input.lua index cde440d..93a18c1 100644 --- a/systems/input.lua +++ b/systems/input.lua @@ -108,7 +108,7 @@ local menuSystem = filteredSystem( pressed = tryShiftMenu(e.below, { "down", "s" }) or pressed pressed = tryShiftMenu(e.above, { "up", "w" }) or pressed - if isDown("return") then + if isDown("return") or isDown("e") then pressed = true system.world:addEntity({ roundState = "active", diff --git a/systems/rounds.lua b/systems/rounds.lua deleted file mode 100644 index b0373c8..0000000 --- a/systems/rounds.lua +++ /dev/null @@ -1,107 +0,0 @@ -local gridElements = filteredSystem("gridElements", { gridPosition = T.XyPair, effectsToApply = T.AnyComponent }) - -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", { roundState = T.str }, function(e) - print("roundState: " .. e.roundState) -end) - -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 - 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