Add spiky box variants

This commit is contained in:
Your Name 2026-08-11 20:08:58 -04:00
parent 761214fe3e
commit c3a2692351
10 changed files with 340 additions and 19 deletions

View file

@ -1,7 +1,7 @@
local BoxVariants = {} local BoxVariants = {}
-- Add directional sticky-puller and all-edge skewer variants for every authored -- Add directional sticky-puller, rigid skewer, and soft all-edge spiky variants
-- moveable box. Both share sprite paths and collision cells with their base. -- for every authored moveable box. All share sprites/collision with their base.
function BoxVariants.addTo(assets) function BoxVariants.addTo(assets)
local baseBoxes = {} local baseBoxes = {}
for name, properties in pairs(assets) do for name, properties in pairs(assets) do
@ -28,6 +28,7 @@ function BoxVariants.addTo(assets)
for _, base in ipairs(baseBoxes) do for _, base in ipairs(baseBoxes) do
addVariant(base, "sticky_", "sticky", "sticky", "goopy") addVariant(base, "sticky_", "sticky", "sticky", "goopy")
addVariant(base, "skewer_", "skewer", "skewer", "skewer") addVariant(base, "skewer_", "skewer", "skewer", "skewer")
addVariant(base, "spiky_", "spiky", "spiky", "spiky")
end end
return assets return assets

View file

@ -16,6 +16,7 @@ function Entity:initialize(t)
self.collision = t.collision --"none", "moveable", "unmoveable" self.collision = t.collision --"none", "moveable", "unmoveable"
self.sticky = t.sticky or false self.sticky = t.sticky or false
self.skewer = t.skewer or false self.skewer = t.skewer or false
self.spiky = t.spiky or false
self.sprite = repSprite self.sprite = repSprite
self.shaAmount = t.sha or 1 self.shaAmount = t.sha or 1
-- onBump is a method (see Entity:onBump) so mixins like Dialogable can -- onBump is a method (see Entity:onBump) so mixins like Dialogable can
@ -144,6 +145,10 @@ function Entity:isSkewer()
return self.skewer return self.skewer
end end
function Entity:isSpiky()
return self.spiky
end
function Entity:canPassOver() function Entity:canPassOver()
if self:getCollision() == "none" then if self:getCollision() == "none" then
return true return true

View file

@ -16,7 +16,7 @@ end
-- Draw two teeth on every exposed cell edge. The art remains synthetic: the -- 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. -- 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}} cellShape = cellShape or {{x = 1, y = 1}}
local cellSize = 16 * scale local cellSize = 16 * scale
local inset = scale local inset = scale
@ -50,7 +50,10 @@ function drawSkewerSprite(sprite, cellShape, x, y, scale)
end end
end 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) love.graphics.draw(sprite, x, y, 0, scale, scale)
end end

46
generic_spiky.lua Normal file
View file

@ -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

View file

@ -364,6 +364,7 @@ local function rebuildAssetSearchButtons()
sprites = sprites, sprites = sprites,
goopy = gameAssets[asset].goopy, goopy = gameAssets[asset].goopy,
skewer = gameAssets[asset].skewer, skewer = gameAssets[asset].skewer,
spiky = gameAssets[asset].spiky,
cells = gameAssets[asset].cells, cells = gameAssets[asset].cells,
name = "asset", name = "asset",
text = asset, text = asset,
@ -685,6 +686,7 @@ function selectFolder(folder)
sprites = sprites, sprites = sprites,
goopy = gameAssets[asset].goopy, goopy = gameAssets[asset].goopy,
skewer = gameAssets[asset].skewer, skewer = gameAssets[asset].skewer,
spiky = gameAssets[asset].spiky,
cells = gameAssets[asset].cells, cells = gameAssets[asset].cells,
name = "asset", name = "asset",
text = asset, text = asset,
@ -759,6 +761,7 @@ function editAsset(asset)
sprites = assetSprites, sprites = assetSprites,
goopy = assetProperties.goopy, goopy = assetProperties.goopy,
skewer = assetProperties.skewer, skewer = assetProperties.skewer,
spiky = assetProperties.spiky,
cells = assetProperties.cells, cells = assetProperties.cells,
x = sb.w / 2 - 18, x = sb.w / 2 - 18,
y = 80, y = 80,

View file

@ -11,6 +11,7 @@ function GUI:initialize(t)
self.text = t.text self.text = t.text
self.goopy = t.goopy or false self.goopy = t.goopy or false
self.skewer = t.skewer or false self.skewer = t.skewer or false
self.spiky = t.spiky or false
self.cells = t.cells self.cells = t.cells
if not t.onClic then if not t.onClic then
@ -106,6 +107,8 @@ function GUI:draw()
drawGoopySprite(sprite, box.x, box.y, self.scale) drawGoopySprite(sprite, box.x, box.y, self.scale)
elseif self.skewer and drawSkewerSprite then elseif self.skewer and drawSkewerSprite then
drawSkewerSprite(sprite, self.cells and self.cells[color], box.x, box.y, self.scale) 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 else
love.graphics.draw(sprite, box.x, box.y, 0, self.scale, self.scale) love.graphics.draw(sprite, box.x, box.y, 0, self.scale, self.scale)
end end

View file

@ -1,6 +1,6 @@
-- Finds the complete set of blocks that must move together for one grid step. -- 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 -- 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 Movement = {}
local adjacentDirections = { local adjacentDirections = {
@ -22,7 +22,14 @@ local function isSkewer(entity)
return entity.isSkewer and entity:isSkewer() return entity.isSkewer and entity:isSkewer()
end 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 entities = {}
local pendingCells = {} local pendingCells = {}
for _, cell in ipairs(startCells) do for _, cell in ipairs(startCells) do
@ -39,7 +46,8 @@ function Movement.findMovableGroup(startCells, direction, entityAt, isInBounds)
end end
local entity = entityAt(cell) 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 if not entity:canMove() then
return false, entities return false, entities
end end
@ -51,9 +59,7 @@ function Movement.findMovableGroup(startCells, direction, entityAt, isInBounds)
table.insert(pendingCells, projectedCell) table.insert(pendingCells, projectedCell)
end end
-- Sticky blocks pull only from the trailing face, like the player. A -- Skewers make every edge contact part of this rigid component.
-- lateral move therefore shears the contact. Skewers retain the older
-- all-edge behaviour and make everything they touch one rigid group.
for _, occupiedCell in ipairs(entity:getOccupiedCells()) do for _, occupiedCell in ipairs(entity:getOccupiedCells()) do
for _, adjacentDirection in ipairs(adjacentDirections) do for _, adjacentDirection in ipairs(adjacentDirections) do
local adjacentCell = { local adjacentCell = {
@ -62,13 +68,11 @@ function Movement.findMovableGroup(startCells, direction, entityAt, isInBounds)
} }
if isInBounds(adjacentCell) then if isInBounds(adjacentCell) then
local adjacentEntity = entityAt(adjacentCell) local adjacentEntity = entityAt(adjacentCell)
local trailing = adjacentDirection.x == -direction.x and
adjacentDirection.y == -direction.y
local attached = adjacentEntity and ( local attached = adjacentEntity and (
isSkewer(entity) or isSkewer(adjacentEntity) or isSkewer(entity) or isSkewer(adjacentEntity)
(trailing and (isSticky(entity) or isSticky(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) table.insert(pendingCells, adjacentCell)
end end
end end
@ -80,4 +84,60 @@ function Movement.findMovableGroup(startCells, direction, entityAt, isInBounds)
return true, entities return true, entities
end 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 return Movement

View file

@ -12,6 +12,7 @@ require "dialogue_manager"
require "generic_moveable" require "generic_moveable"
require "generic_sticky" require "generic_sticky"
require "generic_skewer" require "generic_skewer"
require "generic_spiky"
require "generic_unmoveable" require "generic_unmoveable"
local Movement = require "movement" local Movement = require "movement"
@ -465,7 +466,7 @@ end
-- Instantiate one object from the flat {class,color,x,y} schema. `class` names an -- 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"/ -- 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. -- keyword handled directly since it has no art.
function Room:createEntity(class, color, x, y) function Room:createEntity(class, color, x, y)
if class == "npc" then if class == "npc" then
@ -509,6 +510,8 @@ function Room:createEntity(class, color, x, y)
entity = GenericSticky:new(x, y, color, sprite, class, cellShape) entity = GenericSticky:new(x, y, color, sprite, class, cellShape)
elseif behavior == "skewer" then elseif behavior == "skewer" then
entity = GenericSkewer:new(x, y, color, sprite, class, cellShape) entity = GenericSkewer:new(x, y, color, sprite, class, cellShape)
elseif behavior == "spiky" then
entity = GenericSpiky:new(x, y, color, sprite, class, cellShape)
else else
entity = GenericUnmoveable:new(x, y, color, sprite, cellShape) entity = GenericUnmoveable:new(x, y, color, sprite, cellShape)
end end

View file

@ -29,17 +29,19 @@ local function assertNotIn(group, entity, message)
if group[entity] then error(message or "expected entity outside movement group", 2) end if group[entity] then error(message or "expected entity outside movement group", 2) end
end end
local function movable(x, y, sticky, shape, skewer) local function movable(x, y, sticky, shape, skewer, spiky)
local entity = { local entity = {
x = x, y = y, x = x, y = y,
sticky = sticky or false, sticky = sticky or false,
skewer = skewer or false, skewer = skewer or false,
spiky = spiky or false,
shape = shape or {{x = 0, y = 0}}, shape = shape or {{x = 0, y = 0}},
} }
function entity:canMove() return true end function entity:canMove() return true end
function entity:canPassOver() return false end function entity:canPassOver() return false end
function entity:isSticky() return self.sticky end function entity:isSticky() return self.sticky end
function entity:isSkewer() return self.skewer end function entity:isSkewer() return self.skewer end
function entity:isSpiky() return self.spiky end
function entity:bump() end function entity:bump() end
function entity:move(axis, amount) self[axis] = self[axis] + amount end function entity:move(axis, amount) self[axis] = self[axis] + amount end
function entity:getGridPos() return {x = self.x, y = self.y} 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) assertIn(group, sticky)
end) 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 -- regular, sticky, player; moving right
local regular = movable(2, 3) local regular = movable(2, 3)
local sticky = movable(3, 3, true) local sticky = movable(3, 3, true)
@ -176,6 +178,36 @@ test("a blocked skewer branch stops its rigid movement group", function()
assertFalse(possible) assertFalse(possible)
end) 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() test("ordinary pushing still propagates through regular blocks", function()
local first = movable(3, 3) local first = movable(3, 3)
local second = movable(4, 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) assertIn(group, sticky)
end) 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" class = require "libs/middleclass"
Color = class("TestColor") Color = class("TestColor")
function Color:initialize(name) self.name = name end 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_moveable"
require "generic_sticky" require "generic_sticky"
require "generic_skewer" require "generic_skewer"
require "generic_spiky"
local sprite = {getWidth = function() return 16 end, getHeight = function() return 16 end} local sprite = {getWidth = function() return 16 end, getHeight = function() return 16 end}
local regular = GenericMoveable:new(2, 2, "red", sprite, "box") local regular = GenericMoveable:new(2, 2, "red", sprite, "box")
local sticky = GenericSticky:new(3, 2, "red", sprite, "sticky_box") local sticky = GenericSticky:new(3, 2, "red", sprite, "sticky_box")
local skewer = GenericSkewer:new(4, 2, "red", sprite, "skewer_box") local skewer = GenericSkewer:new(4, 2, "red", sprite, "skewer_box")
local spiky = GenericSpiky:new(5, 2, "red", sprite, "spiky_box")
assertTrue(regular:canMove()) assertTrue(regular:canMove())
assertFalse(regular:isSticky()) assertFalse(regular:isSticky())
@ -225,6 +340,11 @@ test("production sticky and skewer blocks advertise distinct behavior", function
assertTrue(skewer:canMove()) assertTrue(skewer:canMove())
assertFalse(skewer:isSticky()) assertFalse(skewer:isSticky())
assertTrue(skewer:isSkewer()) assertTrue(skewer:isSkewer())
assertFalse(skewer:isSpiky())
assertTrue(spiky:canMove())
assertFalse(spiky:isSticky())
assertFalse(spiky:isSkewer())
assertTrue(spiky:isSpiky())
end) end)
local function loadRoomClass() 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") assertTrue(player.x == 5, "player should move one cell right")
end) 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() test("Room shears a lateral sticky contact", function()
loadRoomClass() loadRoomClass()
local sticky = movable(3, 2, true) 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) assertTrue(player.x == 5)
end) 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 BoxVariants = require "box_variants"
local boxSprites = {red = "box_r.png"} local boxSprites = {red = "box_r.png"}
local boxCells = {red = {{x = 1, y = 1}}} local boxCells = {red = {{x = 1, y = 1}}}
local shapeSprites = {blue = "box_shape_b.png"} local shapeSprites = {blue = "box_shape_b.png"}
local shapeCells = {blue = {{x = 1, y = 1}, {x = 2, y = 1}}} 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 = { local assets = {
box = {class = "moveable", sprites = boxSprites, cells = boxCells}, box = {class = "moveable", sprites = boxSprites, cells = boxCells},
["box_2x1_11"] = {class = "moveable", sprites = shapeSprites, cells = shapeCells}, ["box_2x1_11"] = {class = "moveable", sprites = shapeSprites, cells = shapeCells},
["box_2x2_10-11"] = {class = "moveable", sprites = lSprites, cells = lCells},
box_statue = {class = "immoveable", sprites = {}}, box_statue = {class = "immoveable", sprites = {}},
beer_mug = {class = "moveable", 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.cells == boxCells, "variant should share collision cells")
assertTrue(assets["sticky_box_2x1_11"].sprites == shapeSprites) assertTrue(assets["sticky_box_2x1_11"].sprites == shapeSprites)
assertTrue(assets["sticky_box_2x1_11"].cells == shapeCells) 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.class == "skewer")
assertTrue(assets.skewer_box.directory == "skewer") assertTrue(assets.skewer_box.directory == "skewer")
assertTrue(assets.skewer_box.synthetic) 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.sprites == boxSprites)
assertTrue(assets.skewer_box.cells == boxCells) assertTrue(assets.skewer_box.cells == boxCells)
assertTrue(assets["skewer_box_2x1_11"].sprites == shapeSprites) 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_box_statue)
assertFalse(assets.sticky_beer_mug) assertFalse(assets.sticky_beer_mug)
assertFalse(assets.skewer_box_statue) assertFalse(assets.skewer_box_statue)
assertFalse(assets.skewer_beer_mug) assertFalse(assets.skewer_beer_mug)
assertFalse(assets.spiky_box_statue)
assertFalse(assets.spiky_beer_mug)
end) end)
print("movement/block-variant tests passed: " .. passed) print("movement/block-variant tests passed: " .. passed)

View file

@ -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) drawGoopySprite(sprite, x + (object.x - 1) * 16 * scale, y + (object.y - 1) * 16 * scale, scale)
elseif props.skewer and drawSkewerSprite then 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) 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 else
love.graphics.draw(sprite, x + (object.x - 1) * 16 * scale, y + (object.y - 1) * 16 * scale, 0, scale, scale) love.graphics.draw(sprite, x + (object.x - 1) * 16 * scale, y + (object.y - 1) * 16 * scale, 0, scale, scale)
end end