Prevent doors closing through occupants
This commit is contained in:
parent
bf09a8844a
commit
9731202739
5 changed files with 108 additions and 6 deletions
5
door.lua
5
door.lua
|
|
@ -11,6 +11,7 @@ function Door:initialize(x, y, color)
|
||||||
collision = "unmoveable",
|
collision = "unmoveable",
|
||||||
sprite = loadSprite("art/game/door_" .. color:sub(1, 1) .. ".png"),
|
sprite = loadSprite("art/game/door_" .. color:sub(1, 1) .. ".png"),
|
||||||
})
|
})
|
||||||
|
self.isDoor = true
|
||||||
self.locked = true
|
self.locked = true
|
||||||
self:refreshSprites()
|
self:refreshSprites()
|
||||||
end
|
end
|
||||||
|
|
@ -27,10 +28,10 @@ function Door:refreshSprites()
|
||||||
self.sprites = { [self.doorColor] = sprite }
|
self.sprites = { [self.doorColor] = sprite }
|
||||||
end
|
end
|
||||||
|
|
||||||
function Door:setColors(colors)
|
function Door:setColors(colors, canClose)
|
||||||
if colors[self.doorColor] then
|
if colors[self.doorColor] then
|
||||||
if self.locked then self:unlock() end
|
if self.locked then self:unlock() end
|
||||||
else
|
elseif canClose ~= false then
|
||||||
if not self.locked then self:lock() end
|
if not self.locked then self:lock() end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
21
room.lua
21
room.lua
|
|
@ -538,10 +538,29 @@ end
|
||||||
function Room:resolveSwitchesAndDoors()
|
function Room:resolveSwitchesAndDoors()
|
||||||
local setDoorColors = self:switchCheck()
|
local setDoorColors = self:switchCheck()
|
||||||
for _, door in pairs(self.doors) do
|
for _, door in pairs(self.doors) do
|
||||||
door:setColors(setDoorColors)
|
door:setColors(setDoorColors, self:doorCanClose(door))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Match World:doorCanClose for ordinary room-local doors. All colour layers
|
||||||
|
-- count, while other layers of the same doorway do not obstruct each other.
|
||||||
|
function Room:doorCanClose(door)
|
||||||
|
for _, cell in ipairs(door:getOccupiedCells()) do
|
||||||
|
for _, player in pairs(self.player or {}) do
|
||||||
|
if player:occupiesCell(cell) then return false end
|
||||||
|
end
|
||||||
|
for _, entities in pairs(self.collideables or {}) do
|
||||||
|
for _, entity in ipairs(entities) do
|
||||||
|
if entity ~= door and not entity.isDoor and entity:occupiesCell(cell) and
|
||||||
|
not (entity.canPassOver and entity:canPassOver()) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
function Room:playerJoinCheck(previousPositions, newPositions)
|
function Room:playerJoinCheck(previousPositions, newPositions)
|
||||||
local activate = {}
|
local activate = {}
|
||||||
local deactivate = {}
|
local deactivate = {}
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,66 @@ test("an orphaned stub at a door does not block a valid switch run", function()
|
||||||
assertTrue(wiredDoor.powered)
|
assertTrue(wiredDoor.powered)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
test("real wired and regular doors stay open while any player or collidable blocks the doorway", function()
|
||||||
|
local oldLoadSprite = loadSprite
|
||||||
|
loadSprite = function()
|
||||||
|
return {getWidth = function() return 16 end, getHeight = function() return 16 end}
|
||||||
|
end
|
||||||
|
require "color"
|
||||||
|
require "wired_door"
|
||||||
|
|
||||||
|
local function occupant(x, y)
|
||||||
|
local entity = {x = x, y = y}
|
||||||
|
function entity:occupiesCell(cell) return self.x == cell.x and self.y == cell.y end
|
||||||
|
function entity:canPassOver() return false end
|
||||||
|
return entity
|
||||||
|
end
|
||||||
|
|
||||||
|
local wired = WiredDoor:new(2, 2, "blue", "circuit_a")
|
||||||
|
local ordinary = Door:new(4, 2, "red")
|
||||||
|
local room = {
|
||||||
|
player = {green = occupant(2, 2)},
|
||||||
|
collideables = {
|
||||||
|
red = {ordinary}, green = {occupant(4, 2)}, blue = {wired},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
local world = setmetatable({}, {__index = World})
|
||||||
|
|
||||||
|
wired:setPowered(true)
|
||||||
|
assertFalse(wired.locked, "wired door did not open")
|
||||||
|
assertFalse(world:doorCanClose(room, wired), "green player did not block blue wired door")
|
||||||
|
wired:setPowered(false, world:doorCanClose(room, wired))
|
||||||
|
assertFalse(wired.locked, "wired door shut on player")
|
||||||
|
|
||||||
|
ordinary:setColors({red = true})
|
||||||
|
assertFalse(ordinary.locked, "regular door did not open")
|
||||||
|
assertFalse(world:doorCanClose(room, ordinary), "green collidable did not block red regular door")
|
||||||
|
ordinary:setColors({red = false}, world:doorCanClose(room, ordinary))
|
||||||
|
assertFalse(ordinary.locked, "regular door shut on collidable")
|
||||||
|
|
||||||
|
room.player.green.x, room.player.green.y = 1, 1
|
||||||
|
room.collideables.green[1].x, room.collideables.green[1].y = 1, 1
|
||||||
|
wired:setPowered(false, world:doorCanClose(room, wired))
|
||||||
|
ordinary:setColors({red = false}, world:doorCanClose(room, ordinary))
|
||||||
|
assertTrue(wired.locked, "wired door did not close once clear")
|
||||||
|
assertTrue(ordinary.locked, "regular door did not close once clear")
|
||||||
|
loadSprite = oldLoadSprite
|
||||||
|
end)
|
||||||
|
|
||||||
|
test("circuit resolution passes doorway occupancy to a wired door", function()
|
||||||
|
local world, roomA, _, wiredDoor = makeWorld()
|
||||||
|
local player = {x = 1, y = 1}
|
||||||
|
function player:occupiesCell(cell) return self.x == cell.x and self.y == cell.y end
|
||||||
|
roomA.player.green = player
|
||||||
|
function wiredDoor:setPowered(powered, canClose)
|
||||||
|
self.powered, self.canClose = powered, canClose
|
||||||
|
end
|
||||||
|
|
||||||
|
world:resolveCircuits()
|
||||||
|
assertFalse(wiredDoor.powered, "unpressed switch unexpectedly powered door")
|
||||||
|
assertFalse(wiredDoor.canClose, "resolver omitted player doorway obstruction")
|
||||||
|
end)
|
||||||
|
|
||||||
test("selecting a parked colour changes to its room", function()
|
test("selecting a parked colour changes to its room", function()
|
||||||
local function player()
|
local function player()
|
||||||
local entity = {switched = false}
|
local entity = {switched = false}
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,10 @@ function WiredDoor:initialize(x, y, color, circuitId)
|
||||||
self.wired = true
|
self.wired = true
|
||||||
end
|
end
|
||||||
|
|
||||||
function WiredDoor:setPowered(powered)
|
function WiredDoor:setPowered(powered, canClose)
|
||||||
if powered then
|
if powered then
|
||||||
if self.locked then self:unlock() end
|
if self.locked then self:unlock() end
|
||||||
elseif not self.locked then
|
elseif canClose ~= false and not self.locked then
|
||||||
self:lock()
|
self:lock()
|
||||||
end
|
end
|
||||||
self:refreshSprites()
|
self:refreshSprites()
|
||||||
|
|
|
||||||
24
world.lua
24
world.lua
|
|
@ -284,6 +284,28 @@ function World:isSwitchPressed(room, switch)
|
||||||
return target ~= nil or (player and player:occupiesCell(switch:getGridPos())) or false
|
return target ~= nil or (player and player:occupiesCell(switch:getGridPos())) or false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Check every rendered/colliding colour layer. A red or green player/box can
|
||||||
|
-- visibly occupy a blue door cell, so checking only the door's own layer lets
|
||||||
|
-- wired doors close through an apparent occupant. Other door layers at the
|
||||||
|
-- same location are intentionally ignored: they are one layered doorway.
|
||||||
|
function World:doorCanClose(room, door)
|
||||||
|
local cells = door.getOccupiedCells and door:getOccupiedCells() or {door:getGridPos()}
|
||||||
|
for _, cell in ipairs(cells) do
|
||||||
|
for _, player in pairs(room.player or {}) do
|
||||||
|
if player:occupiesCell(cell) then return false end
|
||||||
|
end
|
||||||
|
for _, entities in pairs(room.collideables or {}) do
|
||||||
|
for _, entity in ipairs(entities) do
|
||||||
|
if entity ~= door and not entity.isDoor and entity:occupiesCell(cell) and
|
||||||
|
not (entity.canPassOver and entity:canPassOver()) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
function World:rebuildCircuitMatrices()
|
function World:rebuildCircuitMatrices()
|
||||||
self.wireMatrix = {}
|
self.wireMatrix = {}
|
||||||
self.wiredSwitchMatrix = {}
|
self.wiredSwitchMatrix = {}
|
||||||
|
|
@ -448,7 +470,7 @@ function World:resolveCircuits()
|
||||||
doorPowered[door] = powered
|
doorPowered[door] = powered
|
||||||
-- locked/collision is a projection of world state onto the physical
|
-- locked/collision is a projection of world state onto the physical
|
||||||
-- room entity so the existing movement renderer can consume it.
|
-- room entity so the existing movement renderer can consume it.
|
||||||
door:setPowered(powered)
|
door:setPowered(powered, self:doorCanClose(device.room, door))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue