test: cover tests.builderOccasionallyPlacesAMimicFromDepthTwo
This commit is contained in:
1 parent
6dc9d144d2
commit
232a447692
1 file changed
+173
+173
@@ -7224,6 +7224,179 @@ function tests.gameKillingARevealedMimicGrantsItsFixedEssenceReward()
|
||||
"felling a mimic grants essence scaled to its own fixed xp reward, not a plain monster's")
|
||||
end
|
||||
|
||||
function tests.builderOccasionallyPlacesAMimicFromDepthTwo()
|
||||
local found
|
||||
for seed = 1, 20 do
|
||||
local level = Builder.build(2, Rng.new(seed), {})
|
||||
for _, ground in ipairs(level.floorItems) do
|
||||
if ground.item.mimic then found = ground.item end
|
||||
end
|
||||
if found then break end
|
||||
end
|
||||
ok(found ~= nil, "a depth-2 floor eventually places a disguised mimic across a handful of seeds")
|
||||
end
|
||||
|
||||
function tests.mimicsNeverAppearOnTheEntranceFloor()
|
||||
for seed = 1, 6 do
|
||||
local level = Builder.build(1, Rng.new(seed), {})
|
||||
for _, ground in ipairs(level.floorItems) do
|
||||
ok(not ground.item.mimic, "no mimic appears on the entrance floor")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function tests.mimicFieldSurvivesSerialization()
|
||||
local mimic = Mimics.reveal(1, 4, 6, 1)
|
||||
local restored = Entity.fromData(mimic:toData())
|
||||
eq(restored.mimic, true, "a revealed mimic's tag survives a save/load round trip")
|
||||
eq(restored.glyph, "M", "a revealed mimic's glyph survives a save/load round trip too")
|
||||
end
|
||||
|
||||
function tests.cursesRollRespectsItsOneInEightChanceAcrossSeedsAndKnownSlots()
|
||||
local hit, miss = false, false
|
||||
for seed = 1, 60 do
|
||||
local curse = Curses.roll(Rng.new(seed), "weapon")
|
||||
if curse then hit = true else miss = true end
|
||||
if hit and miss then break end
|
||||
end
|
||||
ok(hit, "a weapon curse fires on at least one seed in the first 60")
|
||||
ok(miss, "a weapon curse also misses on at least one seed in the first 60")
|
||||
eq(Curses.roll(Rng.new(1), "amulet"), nil, "an unsupported slot never rolls a curse")
|
||||
end
|
||||
|
||||
function tests.cursesAfflictFoldsThePenaltyIntoTheItemAndKeepsItHidden()
|
||||
local weapon = Items.weapon("dagger", 5)
|
||||
Curses.afflict(weapon, Curses.pool.weapon[1])
|
||||
eq(weapon.power, 3, "a weakness curse folds its penalty directly into the weapon's power")
|
||||
eq(weapon.cursed, true, "the item is marked cursed")
|
||||
eq(weapon.curseRevealed, false, "the curse starts hidden")
|
||||
eq(weapon.name, "dagger", "an unrevealed curse never touches the item's displayed name")
|
||||
|
||||
local bow = Items.bow("short bow", 2, 5)
|
||||
Curses.afflict(bow, Curses.pool.bow[1])
|
||||
eq(bow.power, 1, "a faulty curse also taxes a bow's power")
|
||||
eq(bow.range, 3, "a faulty curse shortens a bow's range")
|
||||
|
||||
local ring = Items.ring("ring of power", 1, 1)
|
||||
Curses.afflict(ring, Curses.pool.ring[1])
|
||||
eq(ring.attack, 1, "an affliction curse leaves a ring's own stats untouched")
|
||||
eq(ring.curseDrain, 1, "an affliction curse instead sets a per-turn drain")
|
||||
end
|
||||
|
||||
function tests.cursesAfflictNeverDropsPowerOrRangeBelowItsFloor()
|
||||
local dagger = Items.weapon("dagger", 1)
|
||||
Curses.afflict(dagger, { key = "test", powerPenalty = 5 })
|
||||
eq(dagger.power, 0, "power never drops below zero even with an oversized penalty")
|
||||
|
||||
local bow = Items.bow("short bow", 2, 1)
|
||||
Curses.afflict(bow, { key = "test", rangePenalty = 5 })
|
||||
eq(bow.range, 1, "range never drops below one even with an oversized penalty")
|
||||
end
|
||||
|
||||
function tests.cursesIsCursedAndIsRevealedReadEmptyOrCleanGearSafely()
|
||||
eq(Curses.isCursed(nil), false, "a nil slot is never cursed")
|
||||
local clean = Items.weapon("sword", 4)
|
||||
eq(Curses.isCursed(clean), false, "a plain item is never cursed")
|
||||
eq(Curses.isRevealed(clean), false, "a plain item has nothing to reveal")
|
||||
Curses.afflict(clean, Curses.pool.weapon[1])
|
||||
eq(Curses.isCursed(clean), true, "afflict marks the item cursed")
|
||||
eq(Curses.isRevealed(clean), false, "afflict alone does not reveal the curse")
|
||||
Curses.reveal(clean)
|
||||
eq(Curses.isRevealed(clean), true, "reveal flips the item to known")
|
||||
Curses.reveal(nil)
|
||||
ok(true, "revealing a nil item is a safe no-op")
|
||||
end
|
||||
|
||||
function tests.cursesIsBoundInChecksASpecificEquippedSlot()
|
||||
local entity = { weapon = Items.weapon("sword", 4), armor = nil }
|
||||
eq(Curses.isBoundIn(entity, "weapon"), false, "a clean equipped weapon is not bound")
|
||||
eq(Curses.isBoundIn(entity, "armor"), false, "an empty slot is never bound")
|
||||
Curses.afflict(entity.weapon, Curses.pool.weapon[1])
|
||||
eq(Curses.isBoundIn(entity, "weapon"), true, "a cursed equipped weapon reads as bound")
|
||||
end
|
||||
|
||||
function tests.cursesTryReleaseRevealsAndRefusesABoundSlotButPassesACleanOne()
|
||||
local entity = { weapon = Items.weapon("sword", 4) }
|
||||
eq(Curses.tryRelease(entity, "weapon"), true, "a clean weapon releases without resistance")
|
||||
eq(Curses.tryRelease(entity, "armor"), true, "an empty slot always releases")
|
||||
Curses.afflict(entity.weapon, Curses.pool.weapon[1])
|
||||
eq(Curses.tryRelease(entity, "weapon"), false, "a bound weapon refuses to release")
|
||||
eq(Curses.isRevealed(entity.weapon), true, "the failed release reveals the curse")
|
||||
end
|
||||
|
||||
function tests.cursesCleanseUndoesThePenaltyAndClearsEveryField()
|
||||
local weapon = Items.weapon("dagger", 5)
|
||||
Curses.afflict(weapon, Curses.pool.weapon[1])
|
||||
eq(Curses.cleanse(weapon), true, "cleansing a cursed weapon reports success")
|
||||
eq(weapon.power, 5, "cleansing restores the weapon's original power")
|
||||
eq(weapon.cursed, nil, "cleansing clears the cursed flag")
|
||||
eq(weapon.curseKind, nil, "cleansing clears the curse kind")
|
||||
eq(weapon.curseRevealed, nil, "cleansing clears the revealed flag")
|
||||
eq(weapon.cursePenalty, nil, "cleansing clears the stored penalty")
|
||||
eq(Curses.cleanse(weapon), false, "cleansing an already-clean item is a no-op")
|
||||
|
||||
local ring = Items.ring("ring of power", 0, 0)
|
||||
Curses.afflict(ring, Curses.pool.ring[1])
|
||||
Curses.cleanse(ring)
|
||||
eq(ring.curseDrain, nil, "cleansing a ring also drops its drain")
|
||||
end
|
||||
|
||||
function tests.cursesCleanseWornLiftsEveryCursedSlotAndCountsThem()
|
||||
local entity = {
|
||||
weapon = Curses.afflict(Items.weapon("dagger", 5), Curses.pool.weapon[1]),
|
||||
armor = Items.armor("leather armor", 1),
|
||||
ring = Curses.afflict(Items.ring("ring of power", 0, 0), Curses.pool.ring[1]),
|
||||
}
|
||||
eq(Curses.cleanseWorn(entity), 2, "cleanseWorn reports exactly the cursed pieces it lifted")
|
||||
eq(Curses.isCursed(entity.weapon), false, "the cursed weapon is clean afterward")
|
||||
eq(Curses.isCursed(entity.ring), false, "the cursed ring is clean afterward")
|
||||
eq(Curses.cleanseWorn(entity), 0, "a second pass finds nothing left to cleanse")
|
||||
end
|
||||
|
||||
function tests.cursesRevealFirstUnknownFindsEarliestSlotAndAdvances()
|
||||
local entity = {
|
||||
weapon = Curses.afflict(Items.weapon("dagger", 5), Curses.pool.weapon[1]),
|
||||
armor = Curses.afflict(Items.armor("leather armor", 1), Curses.pool.armor[1]),
|
||||
}
|
||||
local first = Curses.revealFirstUnknown(entity)
|
||||
eq(first, entity.weapon, "weapon slot order comes before armor")
|
||||
eq(Curses.isRevealed(entity.weapon), true, "the found curse is now revealed")
|
||||
local second = Curses.revealFirstUnknown(entity)
|
||||
eq(second, entity.armor, "the next call advances to the still-hidden armor curse")
|
||||
eq(Curses.revealFirstUnknown(entity), nil, "nothing left hidden once both are revealed")
|
||||
end
|
||||
|
||||
function tests.cursesDrainAmountOnlyReadsACursedRing()
|
||||
local entity = {}
|
||||
eq(Curses.drainAmount(entity), 0, "no ring means no drain")
|
||||
entity.weapon = Curses.afflict(Items.weapon("dagger", 5), Curses.pool.weapon[1])
|
||||
eq(Curses.drainAmount(entity), 0, "a cursed weapon never drains hp")
|
||||
entity.ring = Items.ring("ring of power", 0, 0)
|
||||
eq(Curses.drainAmount(entity), 0, "a clean ring drains nothing")
|
||||
Curses.afflict(entity.ring, Curses.pool.ring[1])
|
||||
eq(Curses.drainAmount(entity), 1, "a cursed ring's affliction drains its fixed amount")
|
||||
end
|
||||
|
||||
function tests.cursesLabelHidesUntilRevealedThenPrefixes()
|
||||
local weapon = Items.weapon("dagger", 5)
|
||||
eq(Curses.label(weapon, "dagger"), "dagger", "a clean item's label is untouched")
|
||||
Curses.afflict(weapon, Curses.pool.weapon[1])
|
||||
eq(Curses.label(weapon, "dagger"), "dagger", "an unrevealed curse still hides behind the plain label")
|
||||
Curses.reveal(weapon)
|
||||
eq(Curses.label(weapon, "dagger"), "cursed dagger", "a revealed curse prefixes the label")
|
||||
end
|
||||
|
||||
function tests.gameEquippingOverABoundCursedWeaponFailsAndRevealsTheCurse()
|
||||
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||
game.player.weapon = Curses.afflict(Items.weapon("dagger", 2), Curses.pool.weapon[1])
|
||||
game.player.inventory = { Items.weapon("sword", 4) }
|
||||
local before = game.player.weapon
|
||||
eq(game:useItem(1), false, "equipping a new weapon over a bound one fails")
|
||||
ok(game.player.weapon == before, "the bound weapon stays equipped")
|
||||
eq(Curses.isRevealed(before), true, "the failed swap reveals the curse")
|
||||
eq(#game.player.inventory, 1, "the replacement weapon stays in the pack, never spent")
|
||||
end
|
||||
|
||||
local order = {
|
||||
"rngDeterminism", "mapDeterminism", "tilesRegistry", "fovKnownScene", "losSightLine",
|
||||
"astarPathing", "combatResolution", "itemsAndEquipment", "affixes", "statusEffects",
|
||||
|
||||
Reference in new issue
Block a user