chroma_solstice/movement.lua

138 lines
4.2 KiB
Lua
Raw Normal View History

-- 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 softly grabbed by a sticky block, or is rigidly welded to a
-- gluey block.
local Movement = {}
local adjacentDirections = {
{x = 1, y = 0},
{x = -1, y = 0},
{x = 0, y = 1},
{x = 0, y = -1},
}
local function isSticky(entity)
return entity.isSticky and entity:isSticky()
end
local function isPassable(entity)
return entity.canPassOver and entity:canPassOver()
end
local function isGluey(entity)
return entity.isGluey and entity:isGluey()
end
-- Build one rigid component: ordinary pushes are required, and gluey blocks weld
2026-08-11 20:08:58 -04:00
-- 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
table.insert(pendingCells, cell)
end
local nextCell = 1
while nextCell <= #pendingCells do
local cell = pendingCells[nextCell]
nextCell = nextCell + 1
if not isInBounds(cell) then
return false, entities
end
local entity = entityAt(cell)
2026-08-11 20:08:58 -04:00
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
entities[entity] = entity
-- Anything occupying this block's destination must be pushed too.
for _, projectedCell in ipairs(entity:getOccupiedCells(direction)) do
table.insert(pendingCells, projectedCell)
end
-- Gluey blocks make every edge contact part of this rigid component.
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 attached = adjacentEntity and (
isGluey(entity) or isGluey(adjacentEntity)
)
2026-08-11 20:08:58 -04:00
if attached and adjacentEntity ~= entity and adjacentEntity:canMove() and
not (alreadyMoving and alreadyMoving[adjacentEntity]) then
table.insert(pendingCells, adjacentCell)
end
end
end
end
end
end
return true, entities
end
2026-08-11 20:08:58 -04:00
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 on every face: it tries to drag each neighbour
-- along, but simply releases a blocked rigid component without cancelling
-- this entity or another independently pulled branch.
2026-08-11 20:08:58 -04:00
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 softContact = adjacentEntity and (
isSticky(entity) or isSticky(adjacentEntity)
2026-08-11 20:08:58 -04:00
)
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