mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
Mechanical `gofmt -w ./internal ./cmd`. Mostly struct-field realignment that had drifted, plus a few trailing-newline fixes. No behaviour change — gofmt is semantics-preserving, and build/vet/test are green either side. Split out from the code-review fixes that follow so those stay reviewable instead of hiding inside a wall of realignment.
332 lines
9.8 KiB
Go
332 lines
9.8 KiB
Go
package plugin
|
|
|
|
import "testing"
|
|
|
|
func TestAdvEquipmentScore_Empty(t *testing.T) {
|
|
score := advEquipmentScore(map[EquipmentSlot]*AdvEquipment{})
|
|
if score != 0 {
|
|
t.Errorf("empty equipment should score 0, got %.2f", score)
|
|
}
|
|
}
|
|
|
|
func TestAdvEquipmentScore_WeaponDoubled(t *testing.T) {
|
|
equip := map[EquipmentSlot]*AdvEquipment{
|
|
SlotWeapon: {Tier: 3, Condition: 100},
|
|
SlotArmor: {Tier: 3, Condition: 100},
|
|
}
|
|
score := advEquipmentScore(equip)
|
|
// Weapon: 3*2=6, Armor: 3
|
|
if score != 9.0 {
|
|
t.Errorf("got %.2f, want 9 (weapon 6 + armor 3)", score)
|
|
}
|
|
}
|
|
|
|
func TestAdvEquipmentScore_LowConditionHalved(t *testing.T) {
|
|
equip := map[EquipmentSlot]*AdvEquipment{
|
|
SlotArmor: {Tier: 4, Condition: 49}, // below 50%
|
|
}
|
|
score := advEquipmentScore(equip)
|
|
// Tier 4, halved = 2
|
|
if score != 2.0 {
|
|
t.Errorf("got %.2f, want 2 (tier 4 halved for low condition)", score)
|
|
}
|
|
}
|
|
|
|
func TestAdvEquipmentScore_FullLoadout(t *testing.T) {
|
|
equip := map[EquipmentSlot]*AdvEquipment{
|
|
SlotWeapon: {Tier: 5, Condition: 100},
|
|
SlotArmor: {Tier: 5, Condition: 100},
|
|
SlotHelmet: {Tier: 5, Condition: 100},
|
|
SlotBoots: {Tier: 5, Condition: 100},
|
|
SlotTool: {Tier: 5, Condition: 100},
|
|
}
|
|
score := advEquipmentScore(equip)
|
|
// Weapon: 5*2=10, others: 5*4=20, total=30
|
|
if score != 30.0 {
|
|
t.Errorf("got %.2f, want 30", score)
|
|
}
|
|
}
|
|
|
|
func TestXpToNextLevel(t *testing.T) {
|
|
// Level 1: 100 + 3 = 103
|
|
if xp := xpToNextLevel(1); xp != 103 {
|
|
t.Errorf("level 1: got %d, want 103", xp)
|
|
}
|
|
// Level 50: 100 + 150 = 250
|
|
if xp := xpToNextLevel(50); xp != 250 {
|
|
t.Errorf("level 50: got %d, want 250", xp)
|
|
}
|
|
// Higher levels should require more XP
|
|
for i := 1; i < 50; i++ {
|
|
if xpToNextLevel(i) >= xpToNextLevel(i+1) {
|
|
t.Errorf("xp for level %d should be less than level %d", i, i+1)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAdvIsEligible_TooLowLevel(t *testing.T) {
|
|
char := &AdventureCharacter{CombatLevel: 5}
|
|
equip := map[EquipmentSlot]*AdvEquipment{
|
|
SlotWeapon: {Tier: 3, Condition: 100},
|
|
}
|
|
loc := &AdvLocation{Activity: AdvActivityDungeon, MinLevel: 20, MinEquipTier: 0}
|
|
bonuses := &AdvBonusSummary{}
|
|
|
|
eligible, _ := advIsEligible(char, equip, loc, bonuses)
|
|
if eligible {
|
|
t.Error("should not be eligible with combat level 5 for min level 20")
|
|
}
|
|
}
|
|
|
|
func TestAdvIsEligible_TooLowEquipTier(t *testing.T) {
|
|
char := &AdventureCharacter{CombatLevel: 30}
|
|
equip := map[EquipmentSlot]*AdvEquipment{
|
|
SlotWeapon: {Tier: 1, Condition: 100},
|
|
SlotArmor: {Tier: 0, Condition: 100}, // this is the bottleneck
|
|
}
|
|
loc := &AdvLocation{Activity: AdvActivityDungeon, MinLevel: 1, MinEquipTier: 1}
|
|
bonuses := &AdvBonusSummary{}
|
|
|
|
eligible, _ := advIsEligible(char, equip, loc, bonuses)
|
|
if eligible {
|
|
t.Error("should not be eligible with min equip tier 0 for requirement 1")
|
|
}
|
|
}
|
|
|
|
func TestAdvIsEligible_PenaltyZone(t *testing.T) {
|
|
char := &AdventureCharacter{CombatLevel: 21} // within 3 of min 20
|
|
equip := map[EquipmentSlot]*AdvEquipment{
|
|
SlotWeapon: {Tier: 2, Condition: 100},
|
|
}
|
|
loc := &AdvLocation{Activity: AdvActivityDungeon, MinLevel: 20, MinEquipTier: 0}
|
|
bonuses := &AdvBonusSummary{}
|
|
|
|
eligible, penalty := advIsEligible(char, equip, loc, bonuses)
|
|
if !eligible {
|
|
t.Error("should be eligible at combat level 21 for min 20")
|
|
}
|
|
if !penalty {
|
|
t.Error("should be in penalty zone (21-20=1, less than 3)")
|
|
}
|
|
}
|
|
|
|
func TestAdvIsEligible_NoPenalty(t *testing.T) {
|
|
char := &AdventureCharacter{CombatLevel: 25}
|
|
equip := map[EquipmentSlot]*AdvEquipment{
|
|
SlotWeapon: {Tier: 2, Condition: 100},
|
|
}
|
|
loc := &AdvLocation{Activity: AdvActivityDungeon, MinLevel: 20, MinEquipTier: 0}
|
|
bonuses := &AdvBonusSummary{}
|
|
|
|
eligible, penalty := advIsEligible(char, equip, loc, bonuses)
|
|
if !eligible {
|
|
t.Error("should be eligible")
|
|
}
|
|
if penalty {
|
|
t.Error("should NOT be in penalty zone (25-20=5, >= 3)")
|
|
}
|
|
}
|
|
|
|
func TestAdvIsEligible_BonusesDontUnlockTiers(t *testing.T) {
|
|
char := &AdventureCharacter{MiningSkill: 1}
|
|
equip := map[EquipmentSlot]*AdvEquipment{}
|
|
loc := &AdvLocation{Activity: AdvActivityMining, MinLevel: 8, MinEquipTier: 0}
|
|
bonuses := &AdvBonusSummary{MiningBonus: 10} // +10 would make effective 11, but base is 1
|
|
|
|
eligible, _ := advIsEligible(char, equip, loc, bonuses)
|
|
if eligible {
|
|
t.Error("bonuses should not unlock tiers — base mining 1 should not access min level 8")
|
|
}
|
|
}
|
|
|
|
func TestAdvIsEligible_BonusesDontAffectPenaltyZone(t *testing.T) {
|
|
char := &AdventureCharacter{CombatLevel: 21} // base 21, min 20 → penalty (21-20=1 < 3)
|
|
equip := map[EquipmentSlot]*AdvEquipment{
|
|
SlotWeapon: {Tier: 2, Condition: 100},
|
|
}
|
|
loc := &AdvLocation{Activity: AdvActivityDungeon, MinLevel: 20, MinEquipTier: 0}
|
|
bonuses := &AdvBonusSummary{CombatBonus: 10} // +10 would make effective 31, but penalty uses base
|
|
|
|
eligible, penalty := advIsEligible(char, equip, loc, bonuses)
|
|
if !eligible {
|
|
t.Error("should be eligible at base combat 21 for min 20")
|
|
}
|
|
if !penalty {
|
|
t.Error("should still be in penalty zone — bonuses don't affect penalty calculation")
|
|
}
|
|
}
|
|
|
|
func TestCalculateAdvProbabilities_SumsTo100(t *testing.T) {
|
|
char := &AdventureCharacter{CombatLevel: 10}
|
|
equip := map[EquipmentSlot]*AdvEquipment{
|
|
SlotWeapon: {Tier: 2, Condition: 100},
|
|
SlotArmor: {Tier: 2, Condition: 100},
|
|
}
|
|
loc := &AdvLocation{
|
|
Activity: AdvActivityDungeon,
|
|
BaseDeathPct: 18,
|
|
EmptyPct: 15,
|
|
MinLevel: 8,
|
|
}
|
|
bonuses := &AdvBonusSummary{}
|
|
|
|
prob := calculateAdvProbabilities(char, equip, loc, bonuses, false)
|
|
total := prob.DeathPct + prob.EmptyPct + prob.SuccessPct + prob.ExceptionalPct
|
|
|
|
if total < 99.9 || total > 100.1 {
|
|
t.Errorf("probabilities should sum to 100, got %.2f (death=%.1f empty=%.1f success=%.1f exceptional=%.1f)",
|
|
total, prob.DeathPct, prob.EmptyPct, prob.SuccessPct, prob.ExceptionalPct)
|
|
}
|
|
}
|
|
|
|
func TestCalculateAdvProbabilities_PenaltyIncreasesRisk(t *testing.T) {
|
|
char := &AdventureCharacter{CombatLevel: 10}
|
|
equip := map[EquipmentSlot]*AdvEquipment{
|
|
SlotWeapon: {Tier: 2, Condition: 100},
|
|
}
|
|
loc := &AdvLocation{
|
|
Activity: AdvActivityDungeon,
|
|
BaseDeathPct: 18,
|
|
EmptyPct: 15,
|
|
MinLevel: 8,
|
|
}
|
|
bonuses := &AdvBonusSummary{}
|
|
|
|
noPenalty := calculateAdvProbabilities(char, equip, loc, bonuses, false)
|
|
withPenalty := calculateAdvProbabilities(char, equip, loc, bonuses, true)
|
|
|
|
if withPenalty.DeathPct <= noPenalty.DeathPct {
|
|
t.Errorf("penalty zone should increase death %% (%.1f vs %.1f)", withPenalty.DeathPct, noPenalty.DeathPct)
|
|
}
|
|
}
|
|
|
|
func TestCalculateAdvProbabilities_BetterGearReducesDeath(t *testing.T) {
|
|
char := &AdventureCharacter{CombatLevel: 10}
|
|
loc := &AdvLocation{
|
|
Activity: AdvActivityDungeon,
|
|
BaseDeathPct: 30,
|
|
EmptyPct: 15,
|
|
MinLevel: 8,
|
|
}
|
|
bonuses := &AdvBonusSummary{}
|
|
|
|
weakGear := map[EquipmentSlot]*AdvEquipment{
|
|
SlotWeapon: {Tier: 1, Condition: 100},
|
|
}
|
|
strongGear := map[EquipmentSlot]*AdvEquipment{
|
|
SlotWeapon: {Tier: 5, Condition: 100},
|
|
SlotArmor: {Tier: 5, Condition: 100},
|
|
SlotHelmet: {Tier: 5, Condition: 100},
|
|
SlotBoots: {Tier: 5, Condition: 100},
|
|
SlotTool: {Tier: 5, Condition: 100},
|
|
}
|
|
|
|
weakProb := calculateAdvProbabilities(char, weakGear, loc, bonuses, false)
|
|
strongProb := calculateAdvProbabilities(char, strongGear, loc, bonuses, false)
|
|
|
|
if strongProb.DeathPct >= weakProb.DeathPct {
|
|
t.Errorf("better gear should reduce death %% (strong=%.1f vs weak=%.1f)", strongProb.DeathPct, weakProb.DeathPct)
|
|
}
|
|
}
|
|
|
|
func TestAdvEffectiveSkill(t *testing.T) {
|
|
char := &AdventureCharacter{
|
|
CombatLevel: 10,
|
|
MiningSkill: 15,
|
|
ForagingSkill: 20,
|
|
}
|
|
bonuses := &AdvBonusSummary{
|
|
CombatBonus: 5,
|
|
MiningBonus: 3,
|
|
ForagingBonus: 0,
|
|
}
|
|
|
|
if s := advEffectiveSkill(char, AdvActivityDungeon, bonuses); s != 15 {
|
|
t.Errorf("dungeon: got %d, want 15 (10+5)", s)
|
|
}
|
|
if s := advEffectiveSkill(char, AdvActivityMining, bonuses); s != 18 {
|
|
t.Errorf("mining: got %d, want 18 (15+3)", s)
|
|
}
|
|
if s := advEffectiveSkill(char, AdvActivityForaging, bonuses); s != 20 {
|
|
t.Errorf("foraging: got %d, want 20 (20+0)", s)
|
|
}
|
|
}
|
|
|
|
func TestAdvParseShopCategory(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
want EquipmentSlot
|
|
}{
|
|
{"weapon", SlotWeapon},
|
|
{"sword", SlotWeapon},
|
|
{"swords", SlotWeapon},
|
|
{"armor", SlotArmor},
|
|
{"armour", SlotArmor},
|
|
{"helmet", SlotHelmet},
|
|
{"helm", SlotHelmet},
|
|
{"boots", SlotBoots},
|
|
{"boot", SlotBoots},
|
|
{"tool", SlotTool},
|
|
{"pickaxe", SlotTool},
|
|
{" WEAPON ", SlotWeapon}, // trimmed + lowered
|
|
{"nonsense", ""},
|
|
{"", ""},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
got := advParseShopCategory(tt.input)
|
|
if got != tt.want {
|
|
t.Errorf("advParseShopCategory(%q) = %q, want %q", tt.input, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSlotEmoji(t *testing.T) {
|
|
if e := slotEmoji(SlotWeapon); e != "⚔️" {
|
|
t.Errorf("weapon emoji: got %q", e)
|
|
}
|
|
if e := slotEmoji(SlotArmor); e != "🛡️" {
|
|
t.Errorf("armor emoji: got %q", e)
|
|
}
|
|
// Default case
|
|
if e := slotEmoji("unknown"); e != "📦" {
|
|
t.Errorf("unknown slot emoji: got %q, want 📦", e)
|
|
}
|
|
}
|
|
|
|
func TestSlotTitle(t *testing.T) {
|
|
if s := slotTitle(SlotWeapon); s != "Weapon" {
|
|
t.Errorf("got %q, want Weapon", s)
|
|
}
|
|
if s := slotTitle(SlotBoots); s != "Boots" {
|
|
t.Errorf("got %q, want Boots", s)
|
|
}
|
|
if s := slotTitle(""); s != "" {
|
|
t.Errorf("empty slot should return empty, got %q", s)
|
|
}
|
|
}
|
|
|
|
func TestComputeAdvBonuses(t *testing.T) {
|
|
treasures := []AdvTreasureBonus{
|
|
{BonusType: "combat_level", BonusValue: 3},
|
|
{BonusType: "mining_skill", BonusValue: 2},
|
|
{BonusType: "all_skills", BonusValue: 1},
|
|
{BonusType: "death_chance", BonusValue: -2.5},
|
|
}
|
|
|
|
bonuses := computeAdvBonuses(treasures, nil, 0, false)
|
|
|
|
if bonuses.CombatBonus != 4 { // 3 + 1 from all_skills
|
|
t.Errorf("CombatBonus: got %d, want 4", bonuses.CombatBonus)
|
|
}
|
|
if bonuses.MiningBonus != 3 { // 2 + 1 from all_skills
|
|
t.Errorf("MiningBonus: got %d, want 3", bonuses.MiningBonus)
|
|
}
|
|
if bonuses.ForagingBonus != 1 { // 1 from all_skills
|
|
t.Errorf("ForagingBonus: got %d, want 1", bonuses.ForagingBonus)
|
|
}
|
|
if bonuses.DeathModifier != -2.5 {
|
|
t.Errorf("DeathModifier: got %f, want -2.5", bonuses.DeathModifier)
|
|
}
|
|
}
|