mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 00:32:40 +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.
97 lines
2.9 KiB
Go
97 lines
2.9 KiB
Go
package plugin
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
|
|
// N1/A3 — every crafting ingredient must be obtainable from some live drop
|
|
// source. Phase R retired the legacy activity loop, which orphaned
|
|
// generateAdvLoot (reachable only from the now-uncalled resolveDungeonAction)
|
|
// and with it every ingredient in the game. rollZoneIngredient reconnects the
|
|
// legacy tables to zone combat; this test is the guard that keeps them
|
|
// reachable.
|
|
|
|
// liveIngredientSources returns every item name a player can obtain from the
|
|
// loot tables zone combat draws on, keyed to the tier it drops at.
|
|
func liveIngredientSources() map[string][]int {
|
|
sources := map[string][]int{}
|
|
for _, act := range advIngredientActivities {
|
|
table := advLootTable(act)
|
|
for tier, defs := range table {
|
|
for _, d := range defs {
|
|
sources[d.Name] = append(sources[d.Name], tier)
|
|
}
|
|
}
|
|
}
|
|
return sources
|
|
}
|
|
|
|
func TestEveryRecipeIngredientHasALiveSource(t *testing.T) {
|
|
sources := liveIngredientSources()
|
|
for _, recipe := range craftingRecipes {
|
|
for _, ing := range recipe.Ingredients {
|
|
if tiers, ok := sources[ing]; !ok || len(tiers) == 0 {
|
|
t.Errorf("recipe %q needs %q, which no live loot table drops",
|
|
recipe.Result, ing)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The drop is keyed by zone tier, so an ingredient that only exists at
|
|
// legacy tier N is only reachable in a tier-N zone. Assert each recipe is
|
|
// completable by someone — i.e. every ingredient sits in tier 1..5.
|
|
func TestIngredientTiersAreReachableByZoneTier(t *testing.T) {
|
|
sources := liveIngredientSources()
|
|
for _, recipe := range craftingRecipes {
|
|
for _, ing := range recipe.Ingredients {
|
|
for _, tier := range sources[ing] {
|
|
if tier < 1 || tier > 5 {
|
|
t.Errorf("%q drops at tier %d, outside the zone tier range",
|
|
ing, tier)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Every zone tier must be able to produce an ingredient, or that tier's
|
|
// players are cut out of crafting entirely.
|
|
func TestRollZoneIngredient_EveryTierCanDrop(t *testing.T) {
|
|
for tier := 1; tier <= 5; tier++ {
|
|
t.Run(fmt.Sprintf("tier%d", tier), func(t *testing.T) {
|
|
got := false
|
|
for i := 0; i < 500 && !got; i++ {
|
|
if item := rollZoneIngredient(tier); item != nil {
|
|
got = true
|
|
if item.Tier != tier {
|
|
t.Errorf("tier %d drop carried tier %d", tier, item.Tier)
|
|
}
|
|
if item.Value <= 0 {
|
|
t.Errorf("%q dropped with value %d", item.Name, item.Value)
|
|
}
|
|
}
|
|
}
|
|
if !got {
|
|
t.Errorf("tier %d never dropped an ingredient in 500 rolls", tier)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJoinLootLines_SkipsEmpties(t *testing.T) {
|
|
if got := joinLootLines("", ""); got != "" {
|
|
t.Errorf("all-empty = %q, want empty", got)
|
|
}
|
|
if got := joinLootLines("a", ""); got != "a" {
|
|
t.Errorf("trailing empty = %q, want %q", got, "a")
|
|
}
|
|
if got := joinLootLines("", "b"); got != "b" {
|
|
t.Errorf("leading empty = %q, want %q", got, "b")
|
|
}
|
|
if got := joinLootLines("a", "b"); got != "a\nb" {
|
|
t.Errorf("both = %q, want %q", got, "a\nb")
|
|
}
|
|
}
|