diff --git a/door.lua b/door.lua index 20a15df..186594a 100644 --- a/door.lua +++ b/door.lua @@ -11,6 +11,7 @@ function Door:initialize(x, y, color) collision = "unmoveable", sprite = loadSprite("art/game/door_" .. color:sub(1, 1) .. ".png"), }) + self.isDoor = true self.locked = true self:refreshSprites() end @@ -27,10 +28,10 @@ function Door:refreshSprites() self.sprites = { [self.doorColor] = sprite } end -function Door:setColors(colors) +function Door:setColors(colors, canClose) if colors[self.doorColor] then if self.locked then self:unlock() end - else + elseif canClose ~= false then if not self.locked then self:lock() end end diff --git a/room.lua b/room.lua index f38e8cc..baf426a 100644 --- a/room.lua +++ b/room.lua @@ -538,10 +538,29 @@ end function Room:resolveSwitchesAndDoors() local setDoorColors = self:switchCheck() for _, door in pairs(self.doors) do - door:setColors(setDoorColors) + door:setColors(setDoorColors, self:doorCanClose(door)) 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) local activate = {} local deactivate = {} diff --git a/tests/test_wire.lua b/tests/test_wire.lua index 8dc5c1c..9dda775 100644 --- a/tests/test_wire.lua +++ b/tests/test_wire.lua @@ -192,6 +192,66 @@ test("an orphaned stub at a door does not block a valid switch run", function() assertTrue(wiredDoor.powered) 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() local function player() local entity = {switched = false} diff --git a/wired_door.lua b/wired_door.lua index 8d30528..b780e14 100644 --- a/wired_door.lua +++ b/wired_door.lua @@ -10,10 +10,10 @@ function WiredDoor:initialize(x, y, color, circuitId) self.wired = true end -function WiredDoor:setPowered(powered) +function WiredDoor:setPowered(powered, canClose) if powered then if self.locked then self:unlock() end - elseif not self.locked then + elseif canClose ~= false and not self.locked then self:lock() end self:refreshSprites() diff --git a/world.lua b/world.lua index de792b6..2d1059a 100644 --- a/world.lua +++ b/world.lua @@ -284,6 +284,28 @@ function World:isSwitchPressed(room, switch) return target ~= nil or (player and player:occupiesCell(switch:getGridPos())) or false 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() self.wireMatrix = {} self.wiredSwitchMatrix = {} @@ -448,7 +470,7 @@ function World:resolveCircuits() doorPowered[door] = powered -- locked/collision is a projection of world state onto the physical -- room entity so the existing movement renderer can consume it. - door:setPowered(powered) + door:setPowered(powered, self:doorCanClose(device.room, door)) end end end