mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 16:42:41 +00:00
Treasure, masterwork, and consumable drops each had zero call sites: they only ever fired from the legacy daily activity loop, which adventure.go now intercepts with a deprecation DM. Hook all three to the zone-combat seam. A1 - treasure: rollAdvTreasureDropDetailed takes a weight, applied to the base rate. Boss x4, elite x2, standard x1, plus one x1 roll on zone clear. Near-miss DMs now fire only for weighted moments; at x1 on autopilot they'd land on ~3% of every kill. A2 - masterwork: the catalog is keyed to mining/fishing/foraging, so the dungeon lookup returned nil and the hook would have been a silent no-op. masterworkDefForZone rolls across all three slot lines at the zone's tier. Flavor now follows loc.Activity rather than the item's catalog line, with a new dungeon pool - a crypt boss must not narrate a pickaxe striking ore. A3 - consumables + ingredients: the audit found more than the four named ingredients were stranded. generateAdvLoot is reachable only from resolveDungeonAction, which has no callers, so all four legacy loot tables were dead and every one of the 12 recipes was uncraftable. rollZoneIngredient draws from those tables directly at the zone's tier, reviving them wholesale rather than re-keying 24 ingredients into the per-zone slates.
82 lines
2.6 KiB
Go
82 lines
2.6 KiB
Go
package plugin
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"gogobee/internal/db"
|
|
)
|
|
|
|
// N1/A1 — treasure drops were orphaned by the Phase R transition and now
|
|
// hang off zone combat. The weight is what makes that safe: expedition
|
|
// combat rolls far more often than the one-a-day legacy activity the base
|
|
// rates were tuned for, so only boss/elite moments get a multiplier.
|
|
|
|
func TestAdvTreasureDropRate_ScalesWithWeight(t *testing.T) {
|
|
// A roll under the (weighted) rate reaches the duplicate check, which
|
|
// reads the treasure table.
|
|
if err := db.Init(t.TempDir()); err != nil {
|
|
t.Fatalf("db.Init: %v", err)
|
|
}
|
|
const user = "@weight:example.org"
|
|
base := advTreasureDropRates[1]
|
|
|
|
cases := []struct {
|
|
name string
|
|
weight float64
|
|
want float64
|
|
}{
|
|
{"standard kill", advTreasureWeightStandard, base},
|
|
{"elite doubles", advTreasureWeightElite, base * 2},
|
|
{"boss quadruples", advTreasureWeightBoss, base * 4},
|
|
{"zone clear is one bonus roll", advTreasureWeightZoneClear, base},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
_, _, rate := rollAdvTreasureDropDetailed(1, user, 0, c.weight)
|
|
if rate != c.want {
|
|
t.Fatalf("rate for weight %v = %v, want %v", c.weight, rate, c.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAdvTreasureDropRate_ZeroWeightNeverDrops(t *testing.T) {
|
|
for i := 0; i < 200; i++ {
|
|
drop, _, rate := rollAdvTreasureDropDetailed(1, "@zero:example.org", 0, 0)
|
|
if drop != nil || rate != 0 {
|
|
t.Fatalf("weight 0 produced drop=%v rate=%v", drop, rate)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A forced roll must actually yield a treasure — this is the "the seam is
|
|
// live again" assertion. A weight above 1/rate drives the effective rate
|
|
// past 1.0, so every roll lands on the drop path.
|
|
func TestAdvTreasureDropDetailed_ForcedRollGrantsTreasure(t *testing.T) {
|
|
if err := db.Init(t.TempDir()); err != nil {
|
|
t.Fatalf("db.Init: %v", err)
|
|
}
|
|
|
|
drop, _, _ := rollAdvTreasureDropDetailed(1, "@forced:example.org", 0, 1000)
|
|
if drop == nil || drop.Def == nil {
|
|
t.Fatal("forced roll produced no treasure")
|
|
}
|
|
if drop.Def.Tier != 1 {
|
|
t.Fatalf("tier-1 roll produced a tier-%d treasure", drop.Def.Tier)
|
|
}
|
|
}
|
|
|
|
// The treasure and masterwork systems predate zones and speak AdvLocation.
|
|
func TestAdvLocForZone(t *testing.T) {
|
|
loc := advLocForZone(ZoneGoblinWarrens)
|
|
if loc.Activity != AdvActivityDungeon {
|
|
t.Errorf("activity = %q, want dungeon", loc.Activity)
|
|
}
|
|
if want := zoneTierFromID(ZoneGoblinWarrens); loc.Tier != want {
|
|
t.Errorf("tier = %d, want %d", loc.Tier, want)
|
|
}
|
|
if loc.Name == "" || loc.Name == string(ZoneGoblinWarrens) {
|
|
t.Errorf("name = %q, want the zone's display name", loc.Name)
|
|
}
|
|
}
|