test: cover tests.gameSpringTrapConfuseEffectAppliesToAnyVictimNotJus...
This commit is contained in:
1 parent
9601bf2949
commit
62e694d522
1 file changed
+150
+150
@@ -785,6 +785,137 @@ function tests.traps()
|
|||||||
poisonGame.level.traps = { { x = 4, y = 6, effect = "poison", revealed = false } }
|
poisonGame.level.traps = { { x = 4, y = 6, effect = "poison", revealed = false } }
|
||||||
poisonGame:movePlayer(1, 0)
|
poisonGame:movePlayer(1, 0)
|
||||||
eq(Status.has(poisonGame.player, Status.POISON), true, "a poison trap poisons the stepper")
|
eq(Status.has(poisonGame.player, Status.POISON), true, "a poison trap poisons the stepper")
|
||||||
|
|
||||||
|
local confuseGame = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
confuseGame.level.traps = { { x = 4, y = 6, effect = "confuse", revealed = false } }
|
||||||
|
confuseGame:movePlayer(1, 0)
|
||||||
|
eq(Status.has(confuseGame.player, Status.CONFUSE), true, "a confuse trap confuses the stepper")
|
||||||
|
eq(confuseGame.level.traps[1].revealed, true, "a sprung confuse trap becomes revealed too")
|
||||||
|
|
||||||
|
local blindGame = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
blindGame.level.traps = { { x = 4, y = 6, effect = "blind", revealed = false } }
|
||||||
|
blindGame:movePlayer(1, 0)
|
||||||
|
eq(Status.has(blindGame.player, Status.BLIND), true, "a blind trap blinds the stepper")
|
||||||
|
eq(blindGame.level.traps[1].revealed, true, "a sprung blind trap becomes revealed too")
|
||||||
|
eq(blindGame:effectiveRadius(), 1, "the freshly blinded stepper's own sight radius is clamped straight away")
|
||||||
|
|
||||||
|
local stunGame = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
stunGame.level.traps = { { x = 4, y = 6, effect = "stun", revealed = false } }
|
||||||
|
stunGame:movePlayer(1, 0)
|
||||||
|
eq(Status.has(stunGame.player, Status.STUN), true, "a stun trap stuns the stepper")
|
||||||
|
eq(stunGame.level.traps[1].revealed, true, "a sprung stun trap becomes revealed too")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Game:springTrap is written to work against whatever `victim` it is handed,
|
||||||
|
-- not just self.player - the same generic shape a builder-placed trap needs
|
||||||
|
-- to also harm a monster that blunders into a player-planted trap kit.
|
||||||
|
function tests.gameSpringTrapConfuseEffectAppliesToAnyVictimNotJustThePlayer()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local rat = Monsters.create(Monsters.byKey.rat, 1, 10, 6, 1)
|
||||||
|
game.level.entities = { rat }
|
||||||
|
local trap = { x = 10, y = 6, effect = "confuse", revealed = false }
|
||||||
|
game:springTrap(trap, rat)
|
||||||
|
eq(Status.has(rat, Status.CONFUSE), true, "a confuse trap sprung on a monster confuses that monster")
|
||||||
|
eq(Status.has(game.player, Status.CONFUSE), false, "the player is untouched by a trap sprung on someone else")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.gameSpringTrapBlindEffectAppliesToAnyVictimNotJustThePlayer()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local rat = Monsters.create(Monsters.byKey.rat, 1, 10, 6, 1)
|
||||||
|
game.level.entities = { rat }
|
||||||
|
local trap = { x = 10, y = 6, effect = "blind", revealed = false }
|
||||||
|
game:springTrap(trap, rat)
|
||||||
|
eq(Status.has(rat, Status.BLIND), true, "a blind trap sprung on a monster blinds that monster")
|
||||||
|
eq(Status.has(game.player, Status.BLIND), false, "the player is untouched by a trap sprung on someone else")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- STUN previously had the opposite gap from BLIND (see abilities.lua's
|
||||||
|
-- tryShriek comment): a player's crafted weapon could already stun a
|
||||||
|
-- monster, but nothing on the environment/monster side could ever stun the
|
||||||
|
-- player. A trap is a fitting, low-risk source for that missing direction -
|
||||||
|
-- rare and avoidable/disarmable, unlike say an elite's on-hit rider firing
|
||||||
|
-- on every single blow it lands.
|
||||||
|
function tests.gameSpringTrapStunEffectAppliesToAnyVictimNotJustThePlayer()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local rat = Monsters.create(Monsters.byKey.rat, 1, 10, 6, 1)
|
||||||
|
game.level.entities = { rat }
|
||||||
|
local trap = { x = 10, y = 6, effect = "stun", revealed = false }
|
||||||
|
game:springTrap(trap, rat)
|
||||||
|
eq(Status.has(rat, Status.STUN), true, "a stun trap sprung on a monster stuns that monster")
|
||||||
|
eq(Status.has(game.player, Status.STUN), false, "the player is untouched by a trap sprung on someone else")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- A confused player's every attempted move rolls the same override chance a
|
||||||
|
-- confused monster's Ai.act does (status.lua's shared CONFUSE_NUMERATOR/
|
||||||
|
-- DENOMINATOR): in the middle of an open arena every one of the 8 compass
|
||||||
|
-- directions leads to open floor, so either outcome always still moves the
|
||||||
|
-- player somewhere and always spends the turn.
|
||||||
|
function tests.gameMovePlayerConfusionEitherFollowsTheChosenDirectionOrStumblesElsewhere()
|
||||||
|
local sawStraight, sawStumble = false, false
|
||||||
|
for seed = 1, 200 do
|
||||||
|
local game = stagedGame(seed, arena(21, 11), 10, 6)
|
||||||
|
Status.apply(game.player, Status.CONFUSE, 5, 1)
|
||||||
|
local consumesTurn = game:movePlayer(1, 0)
|
||||||
|
ok(consumesTurn, "a confused player's attempted move always spends the turn on open floor")
|
||||||
|
if game.player.x == 11 and game.player.y == 6 then
|
||||||
|
sawStraight = true
|
||||||
|
else
|
||||||
|
sawStumble = true
|
||||||
|
end
|
||||||
|
if sawStraight and sawStumble then break end
|
||||||
|
end
|
||||||
|
ok(sawStraight, "confusion sometimes lets the chosen direction through untouched")
|
||||||
|
ok(sawStumble, "confusion sometimes overrides the chosen direction")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The turn-spending contract that makes confusion different from an ordinary
|
||||||
|
-- rejected move: bumping a wall on purpose spends nothing (see
|
||||||
|
-- tests.doorsAndKeys's locked-door case and moveHandler's own "you cannot go
|
||||||
|
-- that way"), but a confused player who stumbles into a wall instead of their
|
||||||
|
-- own chosen direction still spent the turn on the stumble itself.
|
||||||
|
function tests.gameMovePlayerConfusionStumbleIntoAWallStillSpendsTheTurn()
|
||||||
|
local level = arena(21, 11)
|
||||||
|
-- Wall off every neighbour of (10, 6) except due east, so any redirect
|
||||||
|
-- other than the player's own chosen direction bumps a wall in place.
|
||||||
|
for _, cell in ipairs({ { 9, 5 }, { 10, 5 }, { 11, 5 }, { 9, 6 }, { 9, 7 }, { 10, 7 }, { 11, 7 } }) do
|
||||||
|
level.map:set(cell[1], cell[2], Tiles.WALL)
|
||||||
|
end
|
||||||
|
local sawBlockedStumble = false
|
||||||
|
for seed = 1, 500 do
|
||||||
|
local game = stagedGame(seed, level, 10, 6)
|
||||||
|
Status.apply(game.player, Status.CONFUSE, 5, 1)
|
||||||
|
local consumesTurn = game:movePlayer(1, 0)
|
||||||
|
ok(consumesTurn, "a confused move, straight or stumbled into this walled-off pocket, always spends the turn")
|
||||||
|
if game.player.x == 10 and game.player.y == 6 then
|
||||||
|
sawBlockedStumble = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ok(sawBlockedStumble, "at least one seed in range stumbled into a wall instead of moving east")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Without Status.CONFUSE present at all, Game:movePlayer never consults the
|
||||||
|
-- rng for a redirect - the roll/pick machinery added for confusion must be a
|
||||||
|
-- true no-op for every unconfused move the engine already relied on.
|
||||||
|
function tests.gameMovePlayerWithoutConfusionNeverStumbles()
|
||||||
|
for seed = 1, 20 do
|
||||||
|
local game = stagedGame(seed, arena(21, 11), 10, 6)
|
||||||
|
game:movePlayer(1, 0)
|
||||||
|
eq(game.player.x, 11, "an unconfused move always lands exactly where it was aimed")
|
||||||
|
eq(game.player.y, 6, "an unconfused move never drifts off its own row")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Game:allyActsConfused is the companion/hireling/thrall counterpart of the
|
||||||
|
-- confusion machinery above - unlike Game:movePlayer and Ai.act, it is never
|
||||||
|
-- called at all unless a caller opts in, so an entity without Status.CONFUSE
|
||||||
|
-- must bail out before it ever touches the rng (companionsAct*/mercenaryAct*/
|
||||||
|
-- reanimationAct* below cover the roll actually firing).
|
||||||
|
function tests.gameAllyActsConfusedReturnsFalseImmediatelyWithoutTheStatus()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||||
|
local golem = Companions.summon(game, "golem")
|
||||||
|
local rollsBefore = game.rng.state
|
||||||
|
eq(game:allyActsConfused(golem), false, "an ally without Status.CONFUSE never triggers a stumble")
|
||||||
|
eq(game.rng.state, rollsBefore, "checking an unconfused ally never advances the rng")
|
||||||
end
|
end
|
||||||
|
|
||||||
function tests.lightAndDark()
|
function tests.lightAndDark()
|
||||||
@@ -816,6 +947,19 @@ function tests.identification()
|
|||||||
eq(Identify.label(a, healing), "potion of healing", "identifying reveals the true name")
|
eq(Identify.label(a, healing), "potion of healing", "identifying reveals the true name")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function tests.identifyGivesEveryPotionTypeItsOwnDistinctUnidentifiedLook()
|
||||||
|
local state = Identify.new(Rng.new(3))
|
||||||
|
local seen = {}
|
||||||
|
for _, entry in ipairs(Items.potionTypes) do
|
||||||
|
local label = Identify.label(state, Items.potionOf(entry.effect))
|
||||||
|
ok(seen[label] == nil, "no two potion types share an unidentified look (collided on " .. label .. ")")
|
||||||
|
seen[label] = true
|
||||||
|
end
|
||||||
|
local count = 0
|
||||||
|
for _ in pairs(seen) do count = count + 1 end
|
||||||
|
eq(count, #Items.potionTypes, "every potion type, including confuse, got its own look")
|
||||||
|
end
|
||||||
|
|
||||||
function tests.effects()
|
function tests.effects()
|
||||||
local game = Game.new(31)
|
local game = Game.new(31)
|
||||||
game.player.hp = 10
|
game.player.hp = 10
|
||||||
@@ -824,6 +968,12 @@ function tests.effects()
|
|||||||
Effects.apply("poison", game, game.player, { power = 3 })
|
Effects.apply("poison", game, game.player, { power = 3 })
|
||||||
eq(Status.has(game.player, Status.POISON), true, "poison effect afflicts the target")
|
eq(Status.has(game.player, Status.POISON), true, "poison effect afflicts the target")
|
||||||
|
|
||||||
|
Effects.apply("confuse", game, game.player, {})
|
||||||
|
eq(Status.has(game.player, Status.CONFUSE), true, "confuse effect afflicts the drinker just like poison")
|
||||||
|
|
||||||
|
Effects.apply("blind", game, game.player, {})
|
||||||
|
eq(Status.has(game.player, Status.BLIND), true, "blind effect afflicts the drinker just like confuse")
|
||||||
|
|
||||||
Effects.apply("mapping", game, game.player, {})
|
Effects.apply("mapping", game, game.player, {})
|
||||||
local map = game.level.map
|
local map = game.level.map
|
||||||
local exploredAll = true
|
local exploredAll = true
|
||||||
|
|||||||
Reference in new issue
Block a user