Replaces the UtilityBelt over-retail float with the user's actual in-game OD: per-weapon-type MaxDamage/elemVsMon thresholds from Loot.utl. Bows/thrown deferred (null) pending a computed-stat calibration. (L) Dagger MS bucket folded into (L) Dagger (indistinguishable by captured fields). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
78 lines
3 KiB
Go
78 lines
3 KiB
Go
package main
|
|
|
|
import "testing"
|
|
|
|
func TestOD_MeleeTetsubo(t *testing.T) {
|
|
// 2H Cleaving baseline 45, name "Tetsubo", maxdmg 85 → clamp(85-45,1,10)=10.
|
|
raw := map[string]any{"ObjectClass": 1.0, "Name": "Tetsubo",
|
|
"IntValues": map[string]any{"218103840": 41.0, "353": 11.0, "218103842": 85.0}}
|
|
if od, ok := computeOD(raw); !ok || od != 10 {
|
|
t.Fatalf("Tetsubo OD=%v ok=%v want 10", od, ok)
|
|
}
|
|
}
|
|
|
|
func TestOD_MeleeMidTier(t *testing.T) {
|
|
// Heavy Axe baseline 74, name "War Axe", maxdmg 79 → clamp(79-74,1,10)=5.
|
|
raw := map[string]any{"ObjectClass": 1.0, "Name": "Bronze War Axe",
|
|
"IntValues": map[string]any{"218103840": 44.0, "353": 3.0, "218103842": 79.0}}
|
|
if od, ok := computeOD(raw); !ok || od != 5 {
|
|
t.Fatalf("War Axe OD=%v ok=%v want 5", od, ok)
|
|
}
|
|
}
|
|
|
|
func TestOD_MeleeBelowBaselineNull(t *testing.T) {
|
|
// Heavy Axe baseline 74, maxdmg 74 → stat-baseline=0 (<1) → null.
|
|
raw := map[string]any{"ObjectClass": 1.0, "Name": "Bronze War Axe",
|
|
"IntValues": map[string]any{"218103840": 44.0, "353": 3.0, "218103842": 74.0}}
|
|
if _, ok := computeOD(raw); ok {
|
|
t.Fatal("maxdmg == baseline must be null")
|
|
}
|
|
}
|
|
|
|
func TestOD_MeleeNameGateNull(t *testing.T) {
|
|
// Heavy Axe bucket requires name in {Silifi,Lugian,War Axe,Battle}. A skill-44
|
|
// mastery-3 item NOT matching any name and matching no other bucket → null.
|
|
raw := map[string]any{"ObjectClass": 1.0, "Name": "Bronze Frobnicator",
|
|
"IntValues": map[string]any{"218103840": 44.0, "353": 3.0, "218103842": 84.0}}
|
|
if _, ok := computeOD(raw); ok {
|
|
t.Fatal("name not matching the Heavy Axe filter must be null")
|
|
}
|
|
}
|
|
|
|
func TestOD_Crossbow(t *testing.T) {
|
|
// Xbow baseline 77, k839=89 → clamp(89-77,1,10)=10.
|
|
raw := map[string]any{"ObjectClass": 9.0, "Name": "Fire Compound Crossbow",
|
|
"IntValues": map[string]any{"218103840": 47.0, "353": 9.0, "218103839": 89.0}}
|
|
if od, ok := computeOD(raw); !ok || od != 10 {
|
|
t.Fatalf("Xbow OD=%v ok=%v want 10", od, ok)
|
|
}
|
|
}
|
|
|
|
func TestOD_WarCaster(t *testing.T) {
|
|
// War caster: elemVsMon 1.40 → clamp(round((1.40-1.18)*100),1,7)=clamp(22,1,7)=7.
|
|
raw := map[string]any{"ObjectClass": 31.0, "Name": "Diamond Frost Baton",
|
|
"IntValues": map[string]any{"159": 34.0},
|
|
"DoubleValues": map[string]any{"152": 1.40}}
|
|
if od, ok := computeOD(raw); !ok || od != 7 {
|
|
t.Fatalf("War caster OD=%v ok=%v want 7", od, ok)
|
|
}
|
|
}
|
|
|
|
func TestOD_VoidCasterTier(t *testing.T) {
|
|
// Void caster: name must contain "Nether"; elemVsMon 1.22 → round((1.22-1.18)*100)=4.
|
|
raw := map[string]any{"ObjectClass": 31.0, "Name": "Silver Nether Staff",
|
|
"IntValues": map[string]any{"159": 43.0},
|
|
"DoubleValues": map[string]any{"152": 1.22}}
|
|
if od, ok := computeOD(raw); !ok || od != 4 {
|
|
t.Fatalf("Void caster OD=%v ok=%v want 4", od, ok)
|
|
}
|
|
}
|
|
|
|
func TestOD_BowDeferredNull(t *testing.T) {
|
|
// Bows (mastery 8) are deferred → always null in this pass.
|
|
raw := map[string]any{"ObjectClass": 9.0, "Name": "Corsair's Arc",
|
|
"IntValues": map[string]any{"218103840": 47.0, "353": 8.0, "218103839": 70.0}}
|
|
if _, ok := computeOD(raw); ok {
|
|
t.Fatal("bows are deferred → null")
|
|
}
|
|
}
|