test: cover tests.biomesForDepthCyclesThroughTheCatalogInOrder
This commit is contained in:
1 parent
2c6fae4f44
commit
865b04ef93
1 file changed
+173
+173
@@ -6703,6 +6703,179 @@ end
|
|||||||
-- Biomes: a fixed, seed-independent theme per depth (contrast with
|
-- Biomes: a fixed, seed-independent theme per depth (contrast with
|
||||||
-- Atmosphere's own dice-rolled weather above), read by the builder's biome
|
-- Atmosphere's own dice-rolled weather above), read by the builder's biome
|
||||||
-- pass to occasionally reinforce a floor with one more native monster or
|
-- pass to occasionally reinforce a floor with one more native monster or
|
||||||
|
-- bloom a small pocket of its own hazard kind.
|
||||||
|
|
||||||
|
function tests.biomesForDepthCyclesThroughTheCatalogInOrder()
|
||||||
|
for i, biome in ipairs(Biomes.CATALOG) do
|
||||||
|
eq(Biomes.forDepth(i), biome, "depth " .. i .. " maps to the catalog's own entry in order")
|
||||||
|
end
|
||||||
|
eq(Biomes.forDepth(#Biomes.CATALOG + 1), Biomes.CATALOG[1],
|
||||||
|
"depth wraps back to the first biome once the catalog is exhausted")
|
||||||
|
eq(Biomes.forDepth(3 * #Biomes.CATALOG), Biomes.CATALOG[#Biomes.CATALOG],
|
||||||
|
"depth keeps cycling any number of catalog lengths deep")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.biomesDescribeNamesTheBiomeAndItsAmbientText()
|
||||||
|
eq(Biomes.describe(Biomes.byKey.warrens), "the mossy warrens: damp stone and skittering claws",
|
||||||
|
"describe joins a biome's own name and ambient line")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.biomesCatalogEveryEntryNamesARealFactionAndHazardKind()
|
||||||
|
local realFactions = {}
|
||||||
|
for _, faction in pairs(Factions) do
|
||||||
|
if type(faction) == "string" then realFactions[faction] = true end
|
||||||
|
end
|
||||||
|
for _, biome in ipairs(Biomes.CATALOG) do
|
||||||
|
ok(realFactions[biome.favoredFaction], biome.key .. " names a real faction")
|
||||||
|
ok(biome.hazardKind == Hazards.FIRE or biome.hazardKind == Hazards.GAS,
|
||||||
|
biome.key .. " names a real hazard kind")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.biomesNativesAmongFiltersToTheFavoredFactionAndExcludesRangedSpecs()
|
||||||
|
local crypts = Biomes.byKey.crypts
|
||||||
|
local deepRoster = Monsters.forDepth(6)
|
||||||
|
local natives = Biomes.nativesAmong(crypts, deepRoster)
|
||||||
|
ok(#natives > 0, "crypts finds at least one native among a deep enough roster")
|
||||||
|
for _, spec in ipairs(natives) do
|
||||||
|
eq(spec.faction, Factions.UNDEAD, "every native belongs to the biome's own faction")
|
||||||
|
ok(spec.ai ~= "ranged", "no native reinforcement is ever a ranged specialist")
|
||||||
|
end
|
||||||
|
local shallowRoster = Monsters.forDepth(1)
|
||||||
|
eq(#Biomes.nativesAmong(crypts, shallowRoster), 0,
|
||||||
|
"no undead have surfaced yet this shallow, so crypts finds no native at depth one")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.builderLevelsCarryTheBiomeMatchingTheirDepthRegardlessOfSeed()
|
||||||
|
for depth = 1, 8 do
|
||||||
|
local expected = Biomes.forDepth(depth).key
|
||||||
|
for seed = 1, 3 do
|
||||||
|
local level = Builder.build(depth, Rng.new(seed), {})
|
||||||
|
eq(level.biome, expected, "depth " .. depth .. " always carries its own fixed biome no matter the seed")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.builderOccasionallyReinforcesAFloorWithADormantNativeMonster()
|
||||||
|
local level = Builder.build(1, Rng.new(7), {})
|
||||||
|
ok(#level.entities > 7, "this seed's entrance floor carries one more monster than the ordinary roster ever places")
|
||||||
|
local reinforcement = level.entities[#level.entities]
|
||||||
|
eq(reinforcement.faction, Factions.VERMIN, "the extra monster belongs to the entrance floor's own biome")
|
||||||
|
eq(reinforcement.awareness, Stealth.DORMANT, "the reinforcement starts dormant like every other wild monster")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.builderOccasionallyBloomsAHazardMatchingTheFloorSBiome()
|
||||||
|
local warrens = Builder.build(1, Rng.new(7), {})
|
||||||
|
eq(warrens.hazards[1].kind, Hazards.GAS, "a gas-favoring biome only ever blooms gas at generation")
|
||||||
|
local warcamp = Builder.build(4, Rng.new(11), {})
|
||||||
|
eq(warcamp.hazards[1].kind, Hazards.FIRE, "a fire-favoring biome only ever blooms fire at generation")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.biomeFieldSurvivesSerialization()
|
||||||
|
local level = arena(21, 11)
|
||||||
|
level.biome = "crypts"
|
||||||
|
local restored = Level.fromData(level:toData())
|
||||||
|
eq(restored.biome, "crypts", "a floor's biome survives a round trip")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Stealth: monster alertness (asleep/hunting/searching), sneak attacks.
|
||||||
|
|
||||||
|
local function wallSplitArena()
|
||||||
|
local map = Map.new(21, 11)
|
||||||
|
for y = 2, 10 do for x = 2, 20 do map:set(x, y, Map.FLOOR) end end
|
||||||
|
for y = 2, 10 do map:set(11, y, Map.WALL) end
|
||||||
|
return Level.new({
|
||||||
|
depth = 1, generator = "arena", map = map,
|
||||||
|
entities = {}, floorItems = {}, traps = {}, lights = {}, dark = false, explored = {},
|
||||||
|
upX = 2, upY = 2, downX = 20, downY = 10,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.stealthHandBuiltMonsterDefaultsToAlertWhenAwarenessIsUnset()
|
||||||
|
local orc = Monsters.create(Monsters.byKey.orc, 1, 5, 5, 1)
|
||||||
|
eq(orc.awareness, nil, "a monster built directly carries no awareness field at all")
|
||||||
|
ok(not Stealth.isUnaware(orc), "an unset awareness field is treated as already alert, not asleep")
|
||||||
|
eq(Stealth.sneakMultiplier(orc), 1, "no sneak bonus applies against an already-alert target")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.builderPlacesWildDepthOneMonstersAsleepByDefault()
|
||||||
|
local game = Game.new(9)
|
||||||
|
ok(#game.level.entities > 0, "the opening floor spawns at least one wild monster")
|
||||||
|
for _, entity in ipairs(game.level.entities) do
|
||||||
|
eq(entity.awareness, Stealth.DORMANT, "every builder-placed wild monster on the opening floor starts asleep")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.stealthBossesStartFullyAlertRatherThanDormant()
|
||||||
|
local warlord = Bosses.create(Bosses.byKey.warlord, 1, 5, 5)
|
||||||
|
ok(not Stealth.isUnaware(warlord), "a named boss is never asleep, unlike a wild monster")
|
||||||
|
eq(warlord.awareness, Stealth.HUNTING, "a boss starts directly in the hunting state")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.stealthSpawnedMinionsStartHuntingRatherThanDormant()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local minion = game:spawnMinion("zombie", 10, 6)
|
||||||
|
eq(minion.awareness, Stealth.HUNTING, "a minion summoned or split mid-fight is already aware, never asleep")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.stealthDormantMonsterWakesImmediatelyWhenThePlayerStandsNextToIt()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local orc = Monsters.create(Monsters.byKey.orc, 1, 4, 6, 1)
|
||||||
|
orc.awareness = Stealth.DORMANT
|
||||||
|
game.level.entities = { orc }
|
||||||
|
local hpBefore = game.player.hp
|
||||||
|
game:takeMonsterTurn(orc)
|
||||||
|
eq(orc.awareness, Stealth.HUNTING, "a monster right next to the player always notices it")
|
||||||
|
ok(game.player.hp < hpBefore, "the freshly woken monster still gets to act on the very same turn")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.stealthDormantMonsterBeyondItsWakeRadiusNeverStirs()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local orc = Monsters.create(Monsters.byKey.orc, 1, 19, 6, 1)
|
||||||
|
orc.awareness = Stealth.DORMANT
|
||||||
|
game.level.entities = { orc }
|
||||||
|
for _ = 1, 10 do
|
||||||
|
game:takeMonsterTurn(orc)
|
||||||
|
end
|
||||||
|
eq(orc.awareness, Stealth.DORMANT, "a monster far outside its wake radius stays asleep no matter how many turns pass")
|
||||||
|
eq(orc.x, 19, "an asleep monster that never wakes does not move")
|
||||||
|
eq(orc.y, 6, "an asleep monster that never wakes does not move either")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.stealthDormantMonsterWithinRangeEventuallyWakesOnASuccessfulRoll()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local orc = Monsters.create(Monsters.byKey.orc, 1, 6, 6, 1)
|
||||||
|
orc.awareness = Stealth.DORMANT
|
||||||
|
game.level.entities = { orc }
|
||||||
|
local woke = false
|
||||||
|
for _ = 1, 20 do
|
||||||
|
game:takeMonsterTurn(orc)
|
||||||
|
if orc.awareness ~= Stealth.DORMANT then woke = true; break end
|
||||||
|
end
|
||||||
|
ok(woke, "a monster well within its wake radius eventually notices the player across enough turns")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.stealthMeleeAttackOnADormantMonsterDealsATripledSneakBonus()
|
||||||
|
local sneakGame = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local sneakOrc = Monsters.create(Monsters.byKey.orc, 1, 4, 6, 1)
|
||||||
|
sneakOrc.awareness = Stealth.DORMANT
|
||||||
|
sneakGame.level.entities = { sneakOrc }
|
||||||
|
local before = sneakOrc.hp
|
||||||
|
sneakGame:playerAttack(sneakOrc)
|
||||||
|
local sneakDamage = before - sneakOrc.hp
|
||||||
|
eq(sneakOrc.awareness, Stealth.HUNTING, "landing the blow wakes the target immediately")
|
||||||
|
ok(contains(table.concat(sneakGame.log:recent(6), "\n"), "unaware"), "the log calls out the sneak attack")
|
||||||
|
|
||||||
|
local plainGame = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local plainOrc = Monsters.create(Monsters.byKey.orc, 1, 4, 6, 1)
|
||||||
|
plainGame.level.entities = { plainOrc }
|
||||||
|
local plainBefore = plainOrc.hp
|
||||||
|
plainGame:playerAttack(plainOrc)
|
||||||
|
local plainDamage = plainBefore - plainOrc.hp
|
||||||
|
eq(sneakDamage, plainDamage * 3, "a sneak attack deals exactly triple the damage of the same hit thrown while already noticed")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.stealthHuntingMonsterLosesTrackAndSwitchesToSearching()
|
||||||
end
|
end
|
||||||
|
|
||||||
local order = {
|
local order = {
|
||||||
|
|||||||
Reference in new issue
Block a user