From c3a26923517f1abebe46ff459611f9f5df58e89a Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 11 Aug 2026 20:08:58 -0400 Subject: [PATCH] Add spiky box variants --- box_variants.lua | 5 +- entity.lua | 5 + generic_skewer.lua | 5 +- generic_spiky.lua | 46 +++++++++ level_editor/editor.lua | 3 + level_editor/gui.lua | 3 + movement.lua | 82 +++++++++++++--- room.lua | 5 +- tests/test_movement.lua | 203 +++++++++++++++++++++++++++++++++++++++- world.lua | 2 + 10 files changed, 340 insertions(+), 19 deletions(-) create mode 100644 generic_spiky.lua diff --git a/box_variants.lua b/box_variants.lua index ff9fcef..ebce311 100644 --- a/box_variants.lua +++ b/box_variants.lua @@ -1,7 +1,7 @@ local BoxVariants = {} --- Add directional sticky-puller and all-edge skewer variants for every authored --- moveable box. Both share sprite paths and collision cells with their base. +-- Add directional sticky-puller, rigid skewer, and soft all-edge spiky variants +-- for every authored moveable box. All share sprites/collision with their base. function BoxVariants.addTo(assets) local baseBoxes = {} for name, properties in pairs(assets) do @@ -28,6 +28,7 @@ function BoxVariants.addTo(assets) for _, base in ipairs(baseBoxes) do addVariant(base, "sticky_", "sticky", "sticky", "goopy") addVariant(base, "skewer_", "skewer", "skewer", "skewer") + addVariant(base, "spiky_", "spiky", "spiky", "spiky") end return assets diff --git a/entity.lua b/entity.lua index 0766501..e7ebb9b 100644 --- a/entity.lua +++ b/entity.lua @@ -16,6 +16,7 @@ function Entity:initialize(t) self.collision = t.collision --"none", "moveable", "unmoveable" self.sticky = t.sticky or false self.skewer = t.skewer or false + self.spiky = t.spiky or false self.sprite = repSprite self.shaAmount = t.sha or 1 -- onBump is a method (see Entity:onBump) so mixins like Dialogable can @@ -144,6 +145,10 @@ function Entity:isSkewer() return self.skewer end +function Entity:isSpiky() + return self.spiky +end + function Entity:canPassOver() if self:getCollision() == "none" then return true diff --git a/generic_skewer.lua b/generic_skewer.lua index 5cdfc77..19dea5d 100644 --- a/generic_skewer.lua +++ b/generic_skewer.lua @@ -16,7 +16,7 @@ end -- Draw two teeth on every exposed cell edge. The art remains synthetic: the -- underlying box sprite and its collision shape are still the authored base. -function drawSkewerSprite(sprite, cellShape, x, y, scale) +function drawSpikes(cellShape, x, y, scale) cellShape = cellShape or {{x = 1, y = 1}} local cellSize = 16 * scale local inset = scale @@ -50,7 +50,10 @@ function drawSkewerSprite(sprite, cellShape, x, y, scale) end end end +end +function drawSkewerSprite(sprite, cellShape, x, y, scale) + drawSpikes(cellShape, x, y, scale) love.graphics.draw(sprite, x, y, 0, scale, scale) end diff --git a/generic_spiky.lua b/generic_spiky.lua new file mode 100644 index 0000000..73d25be --- /dev/null +++ b/generic_spiky.lua @@ -0,0 +1,46 @@ +require "entity" +require "generic_skewer" + +GenericSpiky = class("GenericSpiky", Entity) + +function drawSpikySprite(sprite, _, x, y, scale) + if drawGoopySprite then + drawGoopySprite(sprite, x, y, scale) + else + love.graphics.draw(sprite, x, y, 0, scale, scale) + end +end + +function GenericSpiky:initialize(x, y, color, sprite, name, cellShape) + Entity.initialize(self, { + name = name, + x = x, + y = y, + color = color, + collision = "moveable", + spiky = true, + sprite = sprite, + cellShape = cellShape + }) +end + +function GenericSpiky:update() + +end + +function GenericSpiky:draw(channel) + local sprite = self.sprites[channel] + if not sprite then return end + love.graphics.setColor(gColor[channel]:set()) + local p = self:getDrawPos() + drawSpikySprite( + sprite, self:getCellShape(), + p.x + self.drawOffset.x * drawScale, + p.y + self.drawOffset.y * drawScale, + drawScale + ) +end + +function GenericSpiky:tick() + +end diff --git a/level_editor/editor.lua b/level_editor/editor.lua index 813a5f0..fbaa7e3 100644 --- a/level_editor/editor.lua +++ b/level_editor/editor.lua @@ -364,6 +364,7 @@ local function rebuildAssetSearchButtons() sprites = sprites, goopy = gameAssets[asset].goopy, skewer = gameAssets[asset].skewer, + spiky = gameAssets[asset].spiky, cells = gameAssets[asset].cells, name = "asset", text = asset, @@ -685,6 +686,7 @@ function selectFolder(folder) sprites = sprites, goopy = gameAssets[asset].goopy, skewer = gameAssets[asset].skewer, + spiky = gameAssets[asset].spiky, cells = gameAssets[asset].cells, name = "asset", text = asset, @@ -759,6 +761,7 @@ function editAsset(asset) sprites = assetSprites, goopy = assetProperties.goopy, skewer = assetProperties.skewer, + spiky = assetProperties.spiky, cells = assetProperties.cells, x = sb.w / 2 - 18, y = 80, diff --git a/level_editor/gui.lua b/level_editor/gui.lua index d60fb80..1008ed7 100644 --- a/level_editor/gui.lua +++ b/level_editor/gui.lua @@ -11,6 +11,7 @@ function GUI:initialize(t) self.text = t.text self.goopy = t.goopy or false self.skewer = t.skewer or false + self.spiky = t.spiky or false self.cells = t.cells if not t.onClic then @@ -106,6 +107,8 @@ function GUI:draw() drawGoopySprite(sprite, box.x, box.y, self.scale) elseif self.skewer and drawSkewerSprite then drawSkewerSprite(sprite, self.cells and self.cells[color], box.x, box.y, self.scale) + elseif self.spiky and drawSpikySprite then + drawSpikySprite(sprite, self.cells and self.cells[color], box.x, box.y, self.scale) else love.graphics.draw(sprite, box.x, box.y, 0, self.scale, self.scale) end diff --git a/movement.lua b/movement.lua index 2fc30dc..b21fe6d 100644 --- a/movement.lua +++ b/movement.lua @@ -1,6 +1,6 @@ -- Finds the complete set of blocks that must move together for one grid step. -- A block joins the set when it is in another block's destination (ordinary --- pushing), is behind a directional sticky puller, or touches a skewer block. +-- pushing), is softly grabbed by sticky/spiky, or touches a rigid skewer. local Movement = {} local adjacentDirections = { @@ -22,7 +22,14 @@ local function isSkewer(entity) return entity.isSkewer and entity:isSkewer() end -function Movement.findMovableGroup(startCells, direction, entityAt, isInBounds) +local function isSpiky(entity) + return entity.isSpiky and entity:isSpiky() +end + +-- Build one rigid component: ordinary pushes are required, and skewers join +-- along every edge. `alreadyMoving` contains cells that will be vacated during +-- this same step, so a later pulled component may move into them. +local function findRigidGroup(startCells, direction, entityAt, isInBounds, alreadyMoving) local entities = {} local pendingCells = {} for _, cell in ipairs(startCells) do @@ -39,7 +46,8 @@ function Movement.findMovableGroup(startCells, direction, entityAt, isInBounds) end local entity = entityAt(cell) - if entity and not isPassable(entity) and not entities[entity] then + if entity and not isPassable(entity) and not entities[entity] and + not (alreadyMoving and alreadyMoving[entity]) then if not entity:canMove() then return false, entities end @@ -51,9 +59,7 @@ function Movement.findMovableGroup(startCells, direction, entityAt, isInBounds) table.insert(pendingCells, projectedCell) end - -- Sticky blocks pull only from the trailing face, like the player. A - -- lateral move therefore shears the contact. Skewers retain the older - -- all-edge behaviour and make everything they touch one rigid group. + -- Skewers make every edge contact part of this rigid component. for _, occupiedCell in ipairs(entity:getOccupiedCells()) do for _, adjacentDirection in ipairs(adjacentDirections) do local adjacentCell = { @@ -62,13 +68,11 @@ function Movement.findMovableGroup(startCells, direction, entityAt, isInBounds) } if isInBounds(adjacentCell) then local adjacentEntity = entityAt(adjacentCell) - local trailing = adjacentDirection.x == -direction.x and - adjacentDirection.y == -direction.y local attached = adjacentEntity and ( - isSkewer(entity) or isSkewer(adjacentEntity) or - (trailing and (isSticky(entity) or isSticky(adjacentEntity))) + isSkewer(entity) or isSkewer(adjacentEntity) ) - if attached and adjacentEntity ~= entity and adjacentEntity:canMove() then + if attached and adjacentEntity ~= entity and adjacentEntity:canMove() and + not (alreadyMoving and alreadyMoving[adjacentEntity]) then table.insert(pendingCells, adjacentCell) end end @@ -80,4 +84,60 @@ function Movement.findMovableGroup(startCells, direction, entityAt, isInBounds) return true, entities end +function Movement.findMovableGroup(startCells, direction, entityAt, isInBounds) + local possible, entities = findRigidGroup( + startCells, direction, entityAt, isInBounds + ) + if not possible then return false, entities end + + local movingQueue = {} + for entity in pairs(entities) do table.insert(movingQueue, entity) end + local attemptedPulls = {} + local nextMover = 1 + + while nextMover <= #movingQueue do + local entity = movingQueue[nextMover] + nextMover = nextMover + 1 + + -- Sticky contact is soft only on the trailing face. Spiky contact is soft + -- on every face. Either kind simply releases a blocked rigid component, + -- without cancelling this entity or another independently pulled branch. + for _, occupiedCell in ipairs(entity:getOccupiedCells()) do + for _, adjacentDirection in ipairs(adjacentDirections) do + local adjacentCell = { + x = occupiedCell.x + adjacentDirection.x, + y = occupiedCell.y + adjacentDirection.y, + } + if isInBounds(adjacentCell) then + local adjacentEntity = entityAt(adjacentCell) + local trailing = adjacentDirection.x == -direction.x and + adjacentDirection.y == -direction.y + local softContact = adjacentEntity and ( + isSpiky(entity) or isSpiky(adjacentEntity) or + (trailing and (isSticky(entity) or isSticky(adjacentEntity))) + ) + if softContact and adjacentEntity ~= entity and + not entities[adjacentEntity] and not attemptedPulls[adjacentEntity] and + adjacentEntity:canMove() then + attemptedPulls[adjacentEntity] = true + local canPull, pulled = findRigidGroup( + {adjacentCell}, direction, entityAt, isInBounds, entities + ) + if canPull then + for pulledEntity in pairs(pulled) do + if not entities[pulledEntity] then + entities[pulledEntity] = pulledEntity + table.insert(movingQueue, pulledEntity) + end + end + end + end + end + end + end + end + + return true, entities +end + return Movement diff --git a/room.lua b/room.lua index 76d301a..ca12edc 100644 --- a/room.lua +++ b/room.lua @@ -12,6 +12,7 @@ require "dialogue_manager" require "generic_moveable" require "generic_sticky" require "generic_skewer" +require "generic_spiky" require "generic_unmoveable" local Movement = require "movement" @@ -465,7 +466,7 @@ end -- Instantiate one object from the flat {class,color,x,y} schema. `class` names an -- art asset, and its .meta declares the behaviour ("door"/"switch"/"player"/ --- "moveable"/"sticky"/"skewer"); anything without a .meta defaults to immoveable. npc is the one +-- "moveable"/"sticky"/"skewer"/"spiky"); anything without a .meta defaults to immoveable. npc is the one -- keyword handled directly since it has no art. function Room:createEntity(class, color, x, y) if class == "npc" then @@ -509,6 +510,8 @@ function Room:createEntity(class, color, x, y) entity = GenericSticky:new(x, y, color, sprite, class, cellShape) elseif behavior == "skewer" then entity = GenericSkewer:new(x, y, color, sprite, class, cellShape) + elseif behavior == "spiky" then + entity = GenericSpiky:new(x, y, color, sprite, class, cellShape) else entity = GenericUnmoveable:new(x, y, color, sprite, cellShape) end diff --git a/tests/test_movement.lua b/tests/test_movement.lua index 5745883..3f0395a 100644 --- a/tests/test_movement.lua +++ b/tests/test_movement.lua @@ -29,17 +29,19 @@ local function assertNotIn(group, entity, message) if group[entity] then error(message or "expected entity outside movement group", 2) end end -local function movable(x, y, sticky, shape, skewer) +local function movable(x, y, sticky, shape, skewer, spiky) local entity = { x = x, y = y, sticky = sticky or false, skewer = skewer or false, + spiky = spiky or false, shape = shape or {{x = 0, y = 0}}, } function entity:canMove() return true end function entity:canPassOver() return false end function entity:isSticky() return self.sticky end function entity:isSkewer() return self.skewer end + function entity:isSpiky() return self.spiky end function entity:bump() end function entity:move(axis, amount) self[axis] = self[axis] + amount end function entity:getGridPos() return {x = self.x, y = self.y} end @@ -100,7 +102,7 @@ test("a trailing sticky block joins a horizontal pull", function() assertIn(group, sticky) end) -test("a sticky block touching the player pulls a regular block behind it", function() +test("a moving sticky block pulls a regular block behind it", function() -- regular, sticky, player; moving right local regular = movable(2, 3) local sticky = movable(3, 3, true) @@ -176,6 +178,36 @@ test("a blocked skewer branch stops its rigid movement group", function() assertFalse(possible) end) +test("a spiky block pulls both orthogonal side blocks", function() + local spiky = movable(4, 4, false, nil, false, true) + local upper = movable(4, 3) + local lower = movable(4, 5) + local possible, group = plan( + {spiky, upper, lower}, {x = 4, y = 4}, {x = 1, y = 0} + ) + + assertTrue(possible) + assertIn(group, spiky) + assertIn(group, upper) + assertIn(group, lower) +end) + +test("a spiky block releases one blocked side while pulling the other", function() + local spiky = movable(4, 4, false, nil, false, true) + local blockedUpper = movable(4, 3) + local freeLower = movable(4, 5) + local blockingWall = wall(5, 3) + local possible, group = plan( + {spiky, blockedUpper, freeLower, blockingWall}, + {x = 4, y = 4}, {x = 1, y = 0} + ) + + assertTrue(possible) + assertIn(group, spiky) + assertNotIn(group, blockedUpper) + assertIn(group, freeLower) +end) + test("ordinary pushing still propagates through regular blocks", function() local first = movable(3, 3) local second = movable(4, 3) @@ -203,7 +235,88 @@ test("multi-cell sticky blocks pull from every trailing occupied cell", function assertIn(group, sticky) end) -test("production sticky and skewer blocks advertise distinct behavior", function() +test("an L-shaped sticky pulls from its trailing face in every direction", function() + local lShape = {{x = 0, y = 0}, {x = 1, y = 0}, {x = 0, y = 1}} + local cases = { + {direction = {x = 1, y = 0}, trailing = {x = 2, y = 3}}, + {direction = {x = -1, y = 0}, trailing = {x = 5, y = 3}}, + {direction = {x = 0, y = 1}, trailing = {x = 3, y = 2}}, + {direction = {x = 0, y = -1}, trailing = {x = 3, y = 5}}, + } + + for _, case in ipairs(cases) do + local stickyL = movable(3, 3, true, lShape) + local trailing = movable(case.trailing.x, case.trailing.y) + local possible, group = plan( + {stickyL, trailing}, {x = 3, y = 3}, case.direction + ) + assertTrue(possible) + assertIn(group, stickyL) + assertIn(group, trailing) + end +end) + +test("an L-shaped sticky can release one blocked pull while completing another", function() + -- Moving right, the two exposed trailing cells touch separate objects. The + -- upper object is pinned against a wall; the lower one is free to follow. + local stickyL = movable(3, 3, true, { + {x = 0, y = 0}, {x = 1, y = 0}, {x = 0, y = 1}, + }) + local pinned = movable(2, 2, false, {{x = 0, y = 0}, {x = 0, y = 1}}) + local free = movable(2, 4) + local blockingWall = wall(3, 2) + local possible, group = plan( + {stickyL, pinned, free, blockingWall}, + {x = 3, y = 3}, {x = 1, y = 0} + ) + + assertTrue(possible) + assertIn(group, stickyL) + assertIn(group, free) + assertNotIn(group, pinned) +end) + +test("a T-shaped sticky pulls independently across a notched trailing face", function() + -- Moving up, three exposed lower faces pull into cells vacated by the T. + local stickyT = movable(3, 3, true, { + {x = 0, y = 0}, {x = 1, y = 0}, {x = 2, y = 0}, + {x = 1, y = 1}, + }) + local trailingLeft = movable(3, 4) + local trailingRight = movable(5, 4) + local trailingStem = movable(4, 5) + local possible, group = plan( + {stickyT, trailingLeft, trailingRight, trailingStem}, + {x = 3, y = 3}, {x = 0, y = -1} + ) + + assertTrue(possible) + assertIn(group, stickyT) + assertIn(group, trailingLeft) + assertIn(group, trailingRight) + assertIn(group, trailingStem) +end) + +test("an L-shaped spiky block grabs moveables across several exposed faces", function() + local spikyL = movable(3, 3, false, { + {x = 0, y = 0}, {x = 1, y = 0}, {x = 0, y = 1}, + }, false, true) + local above = movable(3, 2) + local below = movable(3, 5) + local behind = movable(2, 3) + local possible, group = plan( + {spikyL, above, below, behind}, + {x = 3, y = 3}, {x = 1, y = 0} + ) + + assertTrue(possible) + assertIn(group, spikyL) + assertIn(group, above) + assertIn(group, below) + assertIn(group, behind) +end) + +test("production sticky, skewer, and spiky blocks advertise distinct behavior", function() class = require "libs/middleclass" Color = class("TestColor") function Color:initialize(name) self.name = name end @@ -212,10 +325,12 @@ test("production sticky and skewer blocks advertise distinct behavior", function require "generic_moveable" require "generic_sticky" require "generic_skewer" + require "generic_spiky" local sprite = {getWidth = function() return 16 end, getHeight = function() return 16 end} local regular = GenericMoveable:new(2, 2, "red", sprite, "box") local sticky = GenericSticky:new(3, 2, "red", sprite, "sticky_box") local skewer = GenericSkewer:new(4, 2, "red", sprite, "skewer_box") + local spiky = GenericSpiky:new(5, 2, "red", sprite, "spiky_box") assertTrue(regular:canMove()) assertFalse(regular:isSticky()) @@ -225,6 +340,11 @@ test("production sticky and skewer blocks advertise distinct behavior", function assertTrue(skewer:canMove()) assertFalse(skewer:isSticky()) assertTrue(skewer:isSkewer()) + assertFalse(skewer:isSpiky()) + assertTrue(spiky:canMove()) + assertFalse(spiky:isSticky()) + assertFalse(spiky:isSkewer()) + assertTrue(spiky:isSpiky()) end) local function loadRoomClass() @@ -276,6 +396,47 @@ test("Room applies the sticky-regular-player pull", function() assertTrue(player.x == 5, "player should move one cell right") end) +test("Room pulls sticky out of a pinned concave moveable", function() + loadRoomClass() + -- Before: ___ After: p__ + -- px_ sx_ + -- sm_ _m_ + -- mm_ mm_ + local player = movable(1, 2) + local wall = wall(2, 2) + local sticky = movable(1, 3, true) + local concave = movable(1, 3, false, { + {x = 1, y = 0}, + {x = 0, y = 1}, {x = 1, y = 1}, + }) + local room = roomWith(player, {wall, sticky, concave}) + + room:movePlayer("up") + + assertTrue(player.x == 1 and player.y == 1) + assertTrue(sticky.x == 1 and sticky.y == 2, "sticky should peel away from the concave block") + assertTrue(concave.x == 1 and concave.y == 3, "pinned concave block should stay put") + assertTrue(wall.x == 2 and wall.y == 2) +end) + +test("Room moves an L-shaped sticky and both available trailing pulls", function() + loadRoomClass() + local player = movable(3, 2) + local stickyL = movable(3, 3, true, { + {x = 0, y = 0}, {x = 1, y = 0}, {x = 0, y = 1}, + }) + local trailingLeft = movable(3, 5) + local trailingRight = movable(4, 4) + local room = roomWith(player, {stickyL, trailingLeft, trailingRight}) + + room:movePlayer("up") + + assertTrue(player.x == 3 and player.y == 1) + assertTrue(stickyL.x == 3 and stickyL.y == 2) + assertTrue(trailingLeft.x == 3 and trailingLeft.y == 4) + assertTrue(trailingRight.x == 4 and trailingRight.y == 3) +end) + test("Room shears a lateral sticky contact", function() loadRoomClass() local sticky = movable(3, 2, true) @@ -314,15 +475,36 @@ test("Room never moves a component found by push and pull twice", function() assertTrue(player.x == 5) end) -test("every moveable base box gets a synthetic sticky variant", function() +test("Room pushes spiky onward while a blocked side shears off", function() + loadRoomClass() + local player = movable(3, 4) + local spiky = movable(4, 4, false, nil, false, true) + local blockedUpper = movable(4, 3) + local freeLower = movable(4, 5) + local blockingWall = wall(5, 3) + local room = roomWith(player, {spiky, blockedUpper, freeLower, blockingWall}) + + room:movePlayer("right") + + assertTrue(player.x == 4 and player.y == 4) + assertTrue(spiky.x == 5 and spiky.y == 4) + assertTrue(blockedUpper.x == 4 and blockedUpper.y == 3) + assertTrue(freeLower.x == 5 and freeLower.y == 5) + assertTrue(blockingWall.x == 5 and blockingWall.y == 3) +end) + +test("every moveable base box gets all three synthetic behavior variants", function() local BoxVariants = require "box_variants" local boxSprites = {red = "box_r.png"} local boxCells = {red = {{x = 1, y = 1}}} local shapeSprites = {blue = "box_shape_b.png"} local shapeCells = {blue = {{x = 1, y = 1}, {x = 2, y = 1}}} + local lSprites = {green = "box_l_g.png"} + local lCells = {green = {{x = 1, y = 1}, {x = 1, y = 2}, {x = 2, y = 2}}} local assets = { box = {class = "moveable", sprites = boxSprites, cells = boxCells}, ["box_2x1_11"] = {class = "moveable", sprites = shapeSprites, cells = shapeCells}, + ["box_2x2_10-11"] = {class = "moveable", sprites = lSprites, cells = lCells}, box_statue = {class = "immoveable", sprites = {}}, beer_mug = {class = "moveable", sprites = {}}, } @@ -337,6 +519,8 @@ test("every moveable base box gets a synthetic sticky variant", function() assertTrue(assets.sticky_box.cells == boxCells, "variant should share collision cells") assertTrue(assets["sticky_box_2x1_11"].sprites == shapeSprites) assertTrue(assets["sticky_box_2x1_11"].cells == shapeCells) + assertTrue(assets["sticky_box_2x2_10-11"].sprites == lSprites) + assertTrue(assets["sticky_box_2x2_10-11"].cells == lCells) assertTrue(assets.skewer_box.class == "skewer") assertTrue(assets.skewer_box.directory == "skewer") assertTrue(assets.skewer_box.synthetic) @@ -344,10 +528,21 @@ test("every moveable base box gets a synthetic sticky variant", function() assertTrue(assets.skewer_box.sprites == boxSprites) assertTrue(assets.skewer_box.cells == boxCells) assertTrue(assets["skewer_box_2x1_11"].sprites == shapeSprites) + assertTrue(assets["skewer_box_2x2_10-11"].cells == lCells) + assertTrue(assets.spiky_box.class == "spiky") + assertTrue(assets.spiky_box.directory == "spiky") + assertTrue(assets.spiky_box.synthetic) + assertTrue(assets.spiky_box.spiky) + assertTrue(assets.spiky_box.sprites == boxSprites) + assertTrue(assets.spiky_box.cells == boxCells) + assertTrue(assets["spiky_box_2x1_11"].sprites == shapeSprites) + assertTrue(assets["spiky_box_2x2_10-11"].cells == lCells) assertFalse(assets.sticky_box_statue) assertFalse(assets.sticky_beer_mug) assertFalse(assets.skewer_box_statue) assertFalse(assets.skewer_beer_mug) + assertFalse(assets.spiky_box_statue) + assertFalse(assets.spiky_beer_mug) end) print("movement/block-variant tests passed: " .. passed) diff --git a/world.lua b/world.lua index 061e859..7a4abe7 100644 --- a/world.lua +++ b/world.lua @@ -176,6 +176,8 @@ function World:drawRoomPreview(name, x, y, size) drawGoopySprite(sprite, x + (object.x - 1) * 16 * scale, y + (object.y - 1) * 16 * scale, scale) elseif props.skewer and drawSkewerSprite then drawSkewerSprite(sprite, props.cells and props.cells[color], x + (object.x - 1) * 16 * scale, y + (object.y - 1) * 16 * scale, scale) + elseif props.spiky and drawSpikySprite then + drawSpikySprite(sprite, props.cells and props.cells[color], x + (object.x - 1) * 16 * scale, y + (object.y - 1) * 16 * scale, scale) else love.graphics.draw(sprite, x + (object.x - 1) * 16 * scale, y + (object.y - 1) * 16 * scale, 0, scale, scale) end