mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 16:42:41 +00:00
Adds tier3TrapCatalog (Antimagic Field, Necrotic Miasma, Symbol of Death, Planar Rift) per design doc §6 Tier 3-5 table. Tiers 3, 4, and 5 share this pool — the doc lumps them together. Antimagic Field uses a new SuppressesMagic flag (no damage, narrative suppression beat); the other three deliver dice damage and skip the unmodelled secondary effects (max-HP debuff, drop-to-0, teleport) for a later phase. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
539 lines
16 KiB
Go
539 lines
16 KiB
Go
package plugin
|
||
|
||
import (
|
||
"hash/fnv"
|
||
"math/rand/v2"
|
||
"strings"
|
||
"testing"
|
||
|
||
"maunium.net/go/mautrix/id"
|
||
)
|
||
|
||
func newDeterministicRNG(t *testing.T, seed string) *rand.Rand {
|
||
t.Helper()
|
||
h := fnv.New64a()
|
||
h.Write([]byte(seed))
|
||
return rand.New(rand.NewPCG(h.Sum64(), 0xC0FFEE))
|
||
}
|
||
|
||
// Phase 11 D1e — combat / trap / boss resolution tests.
|
||
//
|
||
// These exercise the pure helpers in dnd_zone_combat.go (deterministic
|
||
// enemy pick, coin-pattern parsing, loot rolling) and the integrated
|
||
// !zone advance path (trap nick + boss combat with a beefed-up char).
|
||
|
||
func TestPickZoneEnemy_NonElite_RespectsRoster(t *testing.T) {
|
||
zone, _ := getZone(ZoneGoblinWarrens)
|
||
seen := map[string]bool{}
|
||
for room := 0; room < 32; room++ {
|
||
m, ok := pickZoneEnemy(zone, "deterministic-run", room, false)
|
||
if !ok {
|
||
t.Fatalf("pickZoneEnemy returned !ok at room %d", room)
|
||
}
|
||
// Non-elite picks must never include elite-flagged entries.
|
||
for _, e := range zone.Enemies {
|
||
if e.IsElite && e.BestiaryID == m.ID {
|
||
t.Errorf("non-elite pick returned elite roster entry: %s", m.ID)
|
||
}
|
||
}
|
||
seen[m.ID] = true
|
||
}
|
||
if len(seen) < 2 {
|
||
t.Errorf("expected variety across 32 rooms, only saw %d unique enemies: %v", len(seen), seen)
|
||
}
|
||
}
|
||
|
||
func TestPickZoneEnemy_Elite_FiltersToEliteRoster(t *testing.T) {
|
||
zone, _ := getZone(ZoneGoblinWarrens)
|
||
for room := 0; room < 16; room++ {
|
||
m, ok := pickZoneEnemy(zone, "elite-run", room, true)
|
||
if !ok {
|
||
t.Fatalf("elite pickZoneEnemy returned !ok at room %d", room)
|
||
}
|
||
// Confirm the picked monster's ID is one of the roster's elite-flagged entries.
|
||
isElite := false
|
||
for _, e := range zone.Enemies {
|
||
if e.BestiaryID == m.ID && e.IsElite {
|
||
isElite = true
|
||
break
|
||
}
|
||
}
|
||
if !isElite {
|
||
t.Errorf("elite pick picked non-elite roster entry: %s", m.ID)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestPickZoneEnemy_Deterministic(t *testing.T) {
|
||
zone, _ := getZone(ZoneCryptValdris)
|
||
a, _ := pickZoneEnemy(zone, "stable", 3, false)
|
||
b, _ := pickZoneEnemy(zone, "stable", 3, false)
|
||
if a.ID != b.ID {
|
||
t.Errorf("deterministic pick diverged: %s vs %s", a.ID, b.ID)
|
||
}
|
||
}
|
||
|
||
func TestRollCoinPattern_Parses(t *testing.T) {
|
||
rng := newDeterministicRNG(t, "coins")
|
||
v := rollCoinPattern("coins_2d10x5", rng)
|
||
// 2d10×5: min 10, max 100.
|
||
if v < 10 || v > 100 {
|
||
t.Errorf("2d10x5 = %d, out of [10,100]", v)
|
||
}
|
||
}
|
||
|
||
func TestRollCoinPattern_BadInputFallsBack(t *testing.T) {
|
||
rng := newDeterministicRNG(t, "bad-coins")
|
||
if got := rollCoinPattern("coins_garbage", rng); got != 25 {
|
||
t.Errorf("bad coin pattern fallback = %d, want 25", got)
|
||
}
|
||
}
|
||
|
||
func TestZoneCombatXP_LossFraction(t *testing.T) {
|
||
loss := zoneCombatXP(CombatResult{PlayerWon: false}, 1.0, 1)
|
||
win := zoneCombatXP(CombatResult{PlayerWon: true}, 1.0, 1)
|
||
if loss >= win {
|
||
t.Errorf("loss (%d) should be < win (%d)", loss, win)
|
||
}
|
||
}
|
||
|
||
func TestZoneCombatXP_NearDeathBonus(t *testing.T) {
|
||
plain := zoneCombatXP(CombatResult{PlayerWon: true}, 1.0, 1)
|
||
nd := zoneCombatXP(CombatResult{PlayerWon: true, NearDeath: true}, 1.0, 1)
|
||
if nd <= plain {
|
||
t.Errorf("near-death (%d) should beat plain win (%d)", nd, plain)
|
||
}
|
||
}
|
||
|
||
func TestZoneCombatXP_TierFloor(t *testing.T) {
|
||
// CR 0.25 at T5 should hit the tier floor, not 25 raw.
|
||
got := zoneCombatXP(CombatResult{PlayerWon: true}, 0.25, 5)
|
||
if got <= int(0.25*100) {
|
||
t.Errorf("expected tier floor to lift CR 0.25 at T5 above 25, got %d", got)
|
||
}
|
||
}
|
||
|
||
func TestTitleCaseUnderscored(t *testing.T) {
|
||
cases := map[string]string{
|
||
"wpn_handaxe_+1": "Wpn Handaxe +1",
|
||
"valdris_phylactery": "Valdris Phylactery",
|
||
"": "",
|
||
"single": "Single",
|
||
}
|
||
for in, want := range cases {
|
||
if got := titleCaseUnderscored(in); got != want {
|
||
t.Errorf("titleCaseUnderscored(%q) = %q, want %q", in, got, want)
|
||
}
|
||
}
|
||
}
|
||
|
||
// failedDetect — fabricated SkillCheckResult that always reads as a
|
||
// missed detection. Used by trap-room tests to avoid d20 flake.
|
||
func failedDetect(skill DnDSkill, dc int) SkillCheckResult {
|
||
return SkillCheckResult{Skill: skill, DC: dc, Roll: 5, Mod: 0, Total: 5, Success: false}
|
||
}
|
||
|
||
// passedDetect — fabricated successful detection.
|
||
func passedDetect(skill DnDSkill, dc int) SkillCheckResult {
|
||
return SkillCheckResult{Skill: skill, DC: dc, Roll: 18, Mod: 0, Total: 18, Success: true}
|
||
}
|
||
|
||
func TestZoneTrapRoom_PitDamagesAndPersists(t *testing.T) {
|
||
setupAuditTestDB(t)
|
||
uid := id.UserID("@zone-trap-pit:example")
|
||
zoneCmdTestCharacter(t, uid, 1)
|
||
defer cleanupZoneRuns(uid)
|
||
|
||
run, err := startZoneRun(uid, ZoneGoblinWarrens, 1, nil)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
zone, _ := getZone(ZoneGoblinWarrens)
|
||
dndChar, _ := LoadDnDCharacter(uid)
|
||
|
||
pit := tier1TrapCatalog[0]
|
||
if pit.Kind != TrapPit {
|
||
t.Fatalf("expected pit at index 0, got %s", pit.Kind)
|
||
}
|
||
|
||
p := &AdventurePlugin{}
|
||
dmg, narration := p.applyTrapEffectWithDetect(uid, run, zone, dndChar, pit,
|
||
failedDetect(pit.DetectSkill, pit.DetectDC))
|
||
if dmg <= 0 {
|
||
t.Errorf("pit trap dealt no damage on failed detect: %d", dmg)
|
||
}
|
||
if dmg > 12 {
|
||
t.Errorf("pit trap exceeded 2d6 max: %d", dmg)
|
||
}
|
||
if !strings.Contains(narration, "HP") {
|
||
t.Errorf("trap narration missing HP line: %q", narration)
|
||
}
|
||
c, _ := LoadDnDCharacter(uid)
|
||
if c.HPCurrent >= c.HPMax {
|
||
t.Errorf("trap did not persist HP loss: cur=%d max=%d", c.HPCurrent, c.HPMax)
|
||
}
|
||
}
|
||
|
||
func TestZoneTrapRoom_DetectedSpotsHarmless(t *testing.T) {
|
||
setupAuditTestDB(t)
|
||
uid := id.UserID("@zone-trap-spot:example")
|
||
zoneCmdTestCharacter(t, uid, 1)
|
||
defer cleanupZoneRuns(uid)
|
||
|
||
run, _ := startZoneRun(uid, ZoneGoblinWarrens, 1, nil)
|
||
zone, _ := getZone(ZoneGoblinWarrens)
|
||
dndChar, _ := LoadDnDCharacter(uid)
|
||
startHP := dndChar.HPCurrent
|
||
|
||
pit := tier1TrapCatalog[0]
|
||
p := &AdventurePlugin{}
|
||
dmg, narration := p.applyTrapEffectWithDetect(uid, run, zone, dndChar, pit,
|
||
passedDetect(pit.DetectSkill, pit.DetectDC))
|
||
if dmg != 0 {
|
||
t.Errorf("detected trap should deal 0 damage, got %d", dmg)
|
||
}
|
||
if !strings.Contains(narration, "spotted") {
|
||
t.Errorf("detected trap narration missing 'spotted': %q", narration)
|
||
}
|
||
c, _ := LoadDnDCharacter(uid)
|
||
if c.HPCurrent != startHP {
|
||
t.Errorf("detected trap should not persist HP change: cur=%d, want=%d", c.HPCurrent, startHP)
|
||
}
|
||
}
|
||
|
||
func TestZoneTrapRoom_TripwireZeroDamageButAlerts(t *testing.T) {
|
||
setupAuditTestDB(t)
|
||
uid := id.UserID("@zone-trap-tripwire:example")
|
||
zoneCmdTestCharacter(t, uid, 1)
|
||
defer cleanupZoneRuns(uid)
|
||
|
||
run, _ := startZoneRun(uid, ZoneGoblinWarrens, 1, nil)
|
||
zone, _ := getZone(ZoneGoblinWarrens)
|
||
dndChar, _ := LoadDnDCharacter(uid)
|
||
startHP := dndChar.HPCurrent
|
||
|
||
var tripwire zoneTrapDef
|
||
for _, tr := range tier1TrapCatalog {
|
||
if tr.Kind == TrapTripwireAlarm {
|
||
tripwire = tr
|
||
break
|
||
}
|
||
}
|
||
if tripwire.Kind != TrapTripwireAlarm {
|
||
t.Fatal("tripwire not present in catalog")
|
||
}
|
||
|
||
p := &AdventurePlugin{}
|
||
dmg, narration := p.applyTrapEffectWithDetect(uid, run, zone, dndChar, tripwire,
|
||
failedDetect(tripwire.DetectSkill, tripwire.DetectDC))
|
||
if dmg != 0 {
|
||
t.Errorf("tripwire should deal 0 damage even on fail, got %d", dmg)
|
||
}
|
||
if !strings.Contains(narration, "Tripwire") {
|
||
t.Errorf("tripwire narration missing trap name: %q", narration)
|
||
}
|
||
c, _ := LoadDnDCharacter(uid)
|
||
if c.HPCurrent != startHP {
|
||
t.Errorf("tripwire should not change HP: cur=%d, want=%d", c.HPCurrent, startHP)
|
||
}
|
||
}
|
||
|
||
func TestZoneTrapRoom_DoesNotKO(t *testing.T) {
|
||
setupAuditTestDB(t)
|
||
uid := id.UserID("@zone-trap-low-hp:example")
|
||
zoneCmdTestCharacter(t, uid, 1)
|
||
defer cleanupZoneRuns(uid)
|
||
|
||
c, _ := LoadDnDCharacter(uid)
|
||
c.HPCurrent = 2
|
||
if err := SaveDnDCharacter(c); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
run, _ := startZoneRun(uid, ZoneCryptValdris, 1, nil)
|
||
zone, _ := getZone(ZoneCryptValdris)
|
||
dndChar, _ := LoadDnDCharacter(uid)
|
||
|
||
pit := tier1TrapCatalog[0]
|
||
p := &AdventurePlugin{}
|
||
_, _ = p.applyTrapEffectWithDetect(uid, run, zone, dndChar, pit,
|
||
failedDetect(pit.DetectSkill, pit.DetectDC))
|
||
c, _ = LoadDnDCharacter(uid)
|
||
if c.HPCurrent < 1 {
|
||
t.Errorf("trap KO'd a player below 1 HP: %d", c.HPCurrent)
|
||
}
|
||
}
|
||
|
||
func TestPickZoneTrap_DeterministicAndTier1Only(t *testing.T) {
|
||
zone, _ := getZone(ZoneGoblinWarrens)
|
||
a, ok := pickZoneTrap(zone, "fixed-run", 3)
|
||
if !ok {
|
||
t.Fatal("pickZoneTrap returned !ok for tier 1 zone")
|
||
}
|
||
b, _ := pickZoneTrap(zone, "fixed-run", 3)
|
||
if a.Kind != b.Kind {
|
||
t.Errorf("pickZoneTrap not deterministic: %s vs %s", a.Kind, b.Kind)
|
||
}
|
||
if a.Tier != ZoneTierBeginner {
|
||
t.Errorf("tier 1 zone picked tier %d trap", a.Tier)
|
||
}
|
||
}
|
||
|
||
func TestPickZoneTrap_Tier2UsesTier2Catalog(t *testing.T) {
|
||
zone, ok := getZone(ZoneForestShadows)
|
||
if !ok {
|
||
t.Fatal("forest_shadows zone missing")
|
||
}
|
||
if zone.Tier != ZoneTierApprentice {
|
||
t.Fatalf("forest_shadows expected tier 2, got %d", zone.Tier)
|
||
}
|
||
seen := map[ZoneTrapKind]bool{}
|
||
for room := 0; room < 64; room++ {
|
||
tr, ok := pickZoneTrap(zone, "t2-run", room)
|
||
if !ok {
|
||
t.Fatalf("pickZoneTrap !ok at room %d", room)
|
||
}
|
||
if tr.Tier != ZoneTierApprentice {
|
||
t.Errorf("tier 2 zone picked tier %d trap (%s)", tr.Tier, tr.Kind)
|
||
}
|
||
seen[tr.Kind] = true
|
||
}
|
||
if len(seen) < 2 {
|
||
t.Errorf("expected variety across 64 rooms in tier 2, only saw %d kinds: %v", len(seen), seen)
|
||
}
|
||
}
|
||
|
||
func TestRollTrapDamage_GlyphOfWardingIn5d8Range(t *testing.T) {
|
||
var glyph zoneTrapDef
|
||
for _, tr := range tier2TrapCatalog {
|
||
if tr.Kind == TrapGlyphOfWarding {
|
||
glyph = tr
|
||
break
|
||
}
|
||
}
|
||
if glyph.Kind != TrapGlyphOfWarding {
|
||
t.Fatal("glyph of warding not in tier 2 catalog")
|
||
}
|
||
for room := 0; room < 64; room++ {
|
||
dmg := rollTrapDamage(glyph, "glyph-run", room)
|
||
if dmg < 5 || dmg > 40 {
|
||
t.Errorf("glyph damage out of 5..40 range at room %d: %d", room, dmg)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestTier2TrapCatalog_AllValid(t *testing.T) {
|
||
if len(tier2TrapCatalog) < 3 {
|
||
t.Fatalf("tier 2 catalog should have at least 3 traps, got %d", len(tier2TrapCatalog))
|
||
}
|
||
for _, tr := range tier2TrapCatalog {
|
||
if tr.Tier != ZoneTierApprentice {
|
||
t.Errorf("trap %s tagged tier %d, expected 2", tr.Kind, tr.Tier)
|
||
}
|
||
if tr.Display == "" || tr.Trigger == "" {
|
||
t.Errorf("trap %s missing display/trigger text", tr.Kind)
|
||
}
|
||
if !tr.AlertsEnemies && (tr.DamageDiceN == 0 || tr.DamageDiceD == 0) {
|
||
t.Errorf("trap %s deals no damage and is not an alarm", tr.Kind)
|
||
}
|
||
if tr.DetectDC <= 12 {
|
||
t.Errorf("trap %s detect DC %d looks tier 1; tier 2 should be 13+", tr.Kind, tr.DetectDC)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestPickZoneTrap_VariesAcrossRooms(t *testing.T) {
|
||
zone, _ := getZone(ZoneGoblinWarrens)
|
||
seen := map[ZoneTrapKind]bool{}
|
||
for room := 0; room < 64; room++ {
|
||
tr, ok := pickZoneTrap(zone, "varied-run", room)
|
||
if !ok {
|
||
t.Fatalf("pickZoneTrap !ok at room %d", room)
|
||
}
|
||
seen[tr.Kind] = true
|
||
}
|
||
if len(seen) < 2 {
|
||
t.Errorf("expected variety across 64 rooms, only saw %d kinds: %v", len(seen), seen)
|
||
}
|
||
}
|
||
|
||
func TestRollTrapDamage_PoisonDartIn2d4Range(t *testing.T) {
|
||
var dart zoneTrapDef
|
||
for _, tr := range tier1TrapCatalog {
|
||
if tr.Kind == TrapPoisonDart {
|
||
dart = tr
|
||
break
|
||
}
|
||
}
|
||
if dart.Kind != TrapPoisonDart {
|
||
t.Fatal("poison dart not in catalog")
|
||
}
|
||
for room := 0; room < 64; room++ {
|
||
dmg := rollTrapDamage(dart, "dart-run", room)
|
||
if dmg < 2 || dmg > 8 { // 2d4 range
|
||
t.Errorf("poison dart damage out of 2..8 range at room %d: %d", room, dmg)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestRollTrapDamage_TripwireZero(t *testing.T) {
|
||
var tripwire zoneTrapDef
|
||
for _, tr := range tier1TrapCatalog {
|
||
if tr.Kind == TrapTripwireAlarm {
|
||
tripwire = tr
|
||
break
|
||
}
|
||
}
|
||
if dmg := rollTrapDamage(tripwire, "any-run", 0); dmg != 0 {
|
||
t.Errorf("tripwire damage should be 0, got %d", dmg)
|
||
}
|
||
}
|
||
|
||
// ── Phase 11 D4b — Tier 3 trap catalog ──────────────────────────────────────
|
||
|
||
func TestPickZoneTrap_Tier3UsesTier3Catalog(t *testing.T) {
|
||
zone, ok := getZone(ZoneManorBlackspire)
|
||
if !ok {
|
||
t.Fatal("manor_blackspire zone missing")
|
||
}
|
||
if zone.Tier != ZoneTierJourneyman {
|
||
t.Fatalf("manor_blackspire expected tier 3, got %d", zone.Tier)
|
||
}
|
||
seen := map[ZoneTrapKind]bool{}
|
||
for room := 0; room < 64; room++ {
|
||
tr, ok := pickZoneTrap(zone, "t3-run", room)
|
||
if !ok {
|
||
t.Fatalf("pickZoneTrap !ok at room %d", room)
|
||
}
|
||
if tr.Tier != ZoneTierJourneyman {
|
||
t.Errorf("tier 3 zone picked tier %d trap (%s)", tr.Tier, tr.Kind)
|
||
}
|
||
seen[tr.Kind] = true
|
||
}
|
||
if len(seen) < 2 {
|
||
t.Errorf("expected variety across 64 rooms in tier 3, only saw %d kinds: %v", len(seen), seen)
|
||
}
|
||
}
|
||
|
||
func TestTier3TrapCatalog_AllValid(t *testing.T) {
|
||
if len(tier3TrapCatalog) < 4 {
|
||
t.Fatalf("tier 3 catalog should have at least 4 traps, got %d", len(tier3TrapCatalog))
|
||
}
|
||
for _, tr := range tier3TrapCatalog {
|
||
if tr.Tier != ZoneTierJourneyman {
|
||
t.Errorf("trap %s tagged tier %d, expected 3", tr.Kind, tr.Tier)
|
||
}
|
||
if tr.Display == "" || tr.Trigger == "" {
|
||
t.Errorf("trap %s missing display/trigger text", tr.Kind)
|
||
}
|
||
nonDamaging := tr.AlertsEnemies || tr.SuppressesMagic
|
||
if !nonDamaging && (tr.DamageDiceN == 0 || tr.DamageDiceD == 0) {
|
||
t.Errorf("trap %s deals no damage and is not alarm/suppression", tr.Kind)
|
||
}
|
||
if tr.DetectDC < 16 {
|
||
t.Errorf("trap %s detect DC %d looks tier 2; tier 3 should be 16+", tr.Kind, tr.DetectDC)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestRollTrapDamage_NecroticMiasmaIn6d6Range(t *testing.T) {
|
||
var miasma zoneTrapDef
|
||
for _, tr := range tier3TrapCatalog {
|
||
if tr.Kind == TrapNecroticMiasma {
|
||
miasma = tr
|
||
break
|
||
}
|
||
}
|
||
if miasma.Kind != TrapNecroticMiasma {
|
||
t.Fatal("necrotic miasma not in tier 3 catalog")
|
||
}
|
||
for room := 0; room < 64; room++ {
|
||
dmg := rollTrapDamage(miasma, "miasma-run", room)
|
||
if dmg < 6 || dmg > 36 { // 6d6 range
|
||
t.Errorf("miasma damage out of 6..36 range at room %d: %d", room, dmg)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestRollTrapDamage_AntimagicFieldZero(t *testing.T) {
|
||
var anti zoneTrapDef
|
||
for _, tr := range tier3TrapCatalog {
|
||
if tr.Kind == TrapAntimagicField {
|
||
anti = tr
|
||
break
|
||
}
|
||
}
|
||
if anti.Kind != TrapAntimagicField {
|
||
t.Fatal("antimagic field not in tier 3 catalog")
|
||
}
|
||
if dmg := rollTrapDamage(anti, "any-run", 0); dmg != 0 {
|
||
t.Errorf("antimagic field should deal 0 damage, got %d", dmg)
|
||
}
|
||
}
|
||
|
||
func TestZoneTrapRoom_AntimagicFieldNoHPLoss(t *testing.T) {
|
||
setupAuditTestDB(t)
|
||
uid := id.UserID("@zone-trap-antimagic:example")
|
||
zoneCmdTestCharacter(t, uid, 5)
|
||
defer cleanupZoneRuns(uid)
|
||
|
||
run, err := startZoneRun(uid, ZoneManorBlackspire, 5, nil)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
zone, _ := getZone(ZoneManorBlackspire)
|
||
dndChar, _ := LoadDnDCharacter(uid)
|
||
startHP := dndChar.HPCurrent
|
||
|
||
var anti zoneTrapDef
|
||
for _, tr := range tier3TrapCatalog {
|
||
if tr.Kind == TrapAntimagicField {
|
||
anti = tr
|
||
break
|
||
}
|
||
}
|
||
|
||
p := &AdventurePlugin{}
|
||
dmg, narration := p.applyTrapEffectWithDetect(uid, run, zone, dndChar, anti,
|
||
failedDetect(anti.DetectSkill, anti.DetectDC))
|
||
if dmg != 0 {
|
||
t.Errorf("antimagic field should deal 0 damage even on fail, got %d", dmg)
|
||
}
|
||
if !strings.Contains(narration, "Antimagic") {
|
||
t.Errorf("antimagic narration missing trap name: %q", narration)
|
||
}
|
||
c, _ := LoadDnDCharacter(uid)
|
||
if c.HPCurrent != startHP {
|
||
t.Errorf("antimagic field should not change HP: cur=%d, want=%d", c.HPCurrent, startHP)
|
||
}
|
||
}
|
||
|
||
func TestRollZoneLoot_BossLootDrops(t *testing.T) {
|
||
setupAuditTestDB(t)
|
||
uid := id.UserID("@zone-loot:example")
|
||
zoneCmdTestCharacter(t, uid, 1)
|
||
defer cleanupZoneRuns(uid)
|
||
|
||
run, _ := startZoneRun(uid, ZoneCryptValdris, 1, nil)
|
||
zone, _ := getZone(ZoneCryptValdris)
|
||
|
||
p := &AdventurePlugin{}
|
||
granted := p.rollZoneLoot(uid, run, zone)
|
||
// Crypt of Valdris has UniqueAlways "valdris_phylactery_shard" + always-drop coins.
|
||
foundShard := false
|
||
foundCoins := false
|
||
for _, id := range granted {
|
||
if id == "valdris_phylactery_shard" {
|
||
foundShard = true
|
||
}
|
||
if strings.HasPrefix(id, "coins_") {
|
||
foundCoins = true
|
||
}
|
||
}
|
||
if !foundShard {
|
||
t.Errorf("UniqueAlways quest item missing from drops: %v", granted)
|
||
}
|
||
if !foundCoins {
|
||
t.Errorf("100%%-drop coins missing from drops: %v", granted)
|
||
}
|
||
}
|