-- 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. 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 isSkewer(entity) return entity.isSkewer and entity:isSkewer() end function Movement.findMovableGroup(startCells, direction, entityAt, isInBounds) 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) if entity and not isPassable(entity) and not entities[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 -- 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. 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 attached = adjacentEntity and ( isSkewer(entity) or isSkewer(adjacentEntity) or (trailing and (isSticky(entity) or isSticky(adjacentEntity))) ) if attached and adjacentEntity ~= entity and adjacentEntity:canMove() then table.insert(pendingCells, adjacentCell) end end end end end end return true, entities end return Movement