562 lines
18 KiB
Go
562 lines
18 KiB
Go
package main
|
|
|
|
import "math"
|
|
|
|
// od.go computes the Mag-Tools "OD" (over-damage vs best retail) rating for
|
|
// melee/missile/wand weapons, ported from UtilityBelt's
|
|
// Lib/ItemInfoHelper/{WeaponMods,MiscCalcs,Dictionaries}.cs and
|
|
// Tools/ItemInfo.cs (CalcMeleeDamage / CalcMissileDamage / the OD display
|
|
// lines ~631-647). See docs/superpowers/specs/2026-07-15-weapon-od-rating-design.md.
|
|
|
|
// bestKey mirrors the (Skill, Mastery, MultiStrike, WieldReq) DataTable.Select
|
|
// key UB uses for an EXACT match lookup — no nearest-tier rounding.
|
|
type bestKey struct {
|
|
skill int
|
|
mastery int
|
|
multiStrike int
|
|
wieldReq int
|
|
}
|
|
|
|
// bestValue mirrors the trailing DataTable columns:
|
|
// (MaxDmg, MaxVar, MaxDmgMod, MaxElementalDmgBonus, MaxElementalDmgVsMonsters).
|
|
type bestValue struct {
|
|
maxDmg float64
|
|
maxVar float64
|
|
maxDmgMod float64
|
|
maxElemBonus float64
|
|
maxElemVsMon float64
|
|
}
|
|
|
|
// bestValues is transcribed row-for-row from
|
|
// UtilityBelt/UtilityBelt/Lib/ItemInfoHelper/WeaponMods.cs BestValuesDatatable.GetTable().
|
|
// Column order in the source Rows.Add is:
|
|
// (Skill, Mastery, MultiStrike, WieldReq, MaxDmg, MaxVar, MaxDmgMod, MaxElementalDmgBonus, MaxElementalDmgVsMonsters)
|
|
// — omitted trailing columns default to 0. 301 rows total (matches
|
|
// `grep -c "table.Rows.Add" WeaponMods.cs`).
|
|
var bestValues = map[bestKey]bestValue{
|
|
// heavyweaponry (skill 44) http://acpedia.org/wiki/Heavy_Weaponry
|
|
// axe
|
|
{44, 3, 0, 0}: {26, .90, 0, 0, 0},
|
|
{44, 3, 0, 250}: {33, .90, 0, 0, 0},
|
|
{44, 3, 0, 300}: {40, .90, 0, 0, 0},
|
|
{44, 3, 0, 325}: {47, .90, 0, 0, 0},
|
|
{44, 3, 0, 350}: {54, .90, 0, 0, 0},
|
|
{44, 3, 0, 370}: {61, .90, 0, 0, 0},
|
|
{44, 3, 0, 400}: {69, .90, 0, 0, 0},
|
|
{44, 3, 0, 420}: {71, .90, 0, 0, 0},
|
|
{44, 3, 0, 430}: {74, .90, 0, 0, 0},
|
|
// dagger
|
|
{44, 6, 0, 0}: {24, .47, 0, 0, 0},
|
|
{44, 6, 0, 250}: {31, .47, 0, 0, 0},
|
|
{44, 6, 0, 300}: {38, .47, 0, 0, 0},
|
|
{44, 6, 0, 325}: {45, .47, 0, 0, 0},
|
|
{44, 6, 0, 350}: {51, .47, 0, 0, 0},
|
|
{44, 6, 0, 370}: {58, .47, 0, 0, 0},
|
|
{44, 6, 0, 400}: {65, .47, 0, 0, 0},
|
|
{44, 6, 0, 420}: {68, .47, 0, 0, 0},
|
|
{44, 6, 0, 430}: {71, .47, 0, 0, 0},
|
|
// msdagger
|
|
{44, 6, 1, 0}: {13, .40, 0, 0, 0},
|
|
{44, 6, 1, 250}: {16, .40, 0, 0, 0},
|
|
{44, 6, 1, 300}: {20, .40, 0, 0, 0},
|
|
{44, 6, 1, 325}: {23, .40, 0, 0, 0},
|
|
{44, 6, 1, 350}: {26, .40, 0, 0, 0},
|
|
{44, 6, 1, 370}: {30, .40, 0, 0, 0},
|
|
{44, 6, 1, 400}: {33, .40, 0, 0, 0},
|
|
{44, 6, 1, 420}: {36, .40, 0, 0, 0},
|
|
{44, 6, 1, 430}: {38, .40, 0, 0, 0},
|
|
// mace
|
|
{44, 4, 0, 0}: {22, .30, 0, 0, 0},
|
|
{44, 4, 0, 250}: {29, .30, 0, 0, 0},
|
|
{44, 4, 0, 300}: {36, .30, 0, 0, 0},
|
|
{44, 4, 0, 325}: {43, .30, 0, 0, 0},
|
|
{44, 4, 0, 350}: {49, .30, 0, 0, 0},
|
|
{44, 4, 0, 370}: {56, .30, 0, 0, 0},
|
|
{44, 4, 0, 400}: {63, .30, 0, 0, 0},
|
|
{44, 4, 0, 420}: {66, .30, 0, 0, 0},
|
|
{44, 4, 0, 430}: {69, .30, 0, 0, 0},
|
|
// spear
|
|
{44, 5, 0, 0}: {25, .59, 0, 0, 0},
|
|
{44, 5, 0, 250}: {32, .59, 0, 0, 0},
|
|
{44, 5, 0, 300}: {39, .59, 0, 0, 0},
|
|
{44, 5, 0, 325}: {46, .59, 0, 0, 0},
|
|
{44, 5, 0, 350}: {58, .59, 0, 0, 0},
|
|
{44, 5, 0, 370}: {59, .59, 0, 0, 0},
|
|
{44, 5, 0, 400}: {66, .59, 0, 0, 0},
|
|
{44, 5, 0, 420}: {69, .59, 0, 0, 0},
|
|
{44, 5, 0, 430}: {72, .59, 0, 0, 0},
|
|
// sword
|
|
{44, 2, 0, 0}: {24, .47, 0, 0, 0},
|
|
{44, 2, 0, 250}: {31, .47, 0, 0, 0},
|
|
{44, 2, 0, 300}: {38, .47, 0, 0, 0},
|
|
{44, 2, 0, 325}: {45, .47, 0, 0, 0},
|
|
{44, 2, 0, 350}: {51, .47, 0, 0, 0},
|
|
{44, 2, 0, 370}: {58, .47, 0, 0, 0},
|
|
{44, 2, 0, 400}: {65, .47, 0, 0, 0},
|
|
{44, 2, 0, 420}: {68, .47, 0, 0, 0},
|
|
{44, 2, 0, 430}: {71, .47, 0, 0, 0},
|
|
// mssword
|
|
{44, 2, 1, 0}: {13, .40, 0, 0, 0},
|
|
{44, 2, 1, 250}: {16, .40, 0, 0, 0},
|
|
{44, 2, 1, 300}: {20, .40, 0, 0, 0},
|
|
{44, 2, 1, 325}: {23, .40, 0, 0, 0},
|
|
{44, 2, 1, 350}: {26, .40, 0, 0, 0},
|
|
{44, 2, 1, 370}: {30, .40, 0, 0, 0},
|
|
{44, 2, 1, 400}: {33, .40, 0, 0, 0},
|
|
{44, 2, 1, 420}: {36, .40, 0, 0, 0},
|
|
{44, 2, 1, 430}: {38, .40, 0, 0, 0},
|
|
// staff
|
|
{44, 7, 0, 0}: {23, .38, 0, 0, 0},
|
|
{44, 7, 0, 250}: {30, .38, 0, 0, 0},
|
|
{44, 7, 0, 300}: {36, .38, 0, 0, 0},
|
|
{44, 7, 0, 325}: {43, .38, 0, 0, 0},
|
|
{44, 7, 0, 350}: {50, .38, 0, 0, 0},
|
|
{44, 7, 0, 370}: {56, .38, 0, 0, 0},
|
|
{44, 7, 0, 400}: {63, .38, 0, 0, 0},
|
|
{44, 7, 0, 420}: {66, .38, 0, 0, 0},
|
|
{44, 7, 0, 430}: {70, .38, 0, 0, 0},
|
|
// ua
|
|
{44, 1, 0, 0}: {20, .44, 0, 0, 0},
|
|
{44, 1, 0, 250}: {26, .44, 0, 0, 0},
|
|
{44, 1, 0, 300}: {31, .44, 0, 0, 0},
|
|
{44, 1, 0, 325}: {37, .44, 0, 0, 0},
|
|
{44, 1, 0, 350}: {43, .44, 0, 0, 0},
|
|
{44, 1, 0, 370}: {48, .44, 0, 0, 0},
|
|
{44, 1, 0, 400}: {54, .44, 0, 0, 0},
|
|
{44, 1, 0, 420}: {56, .44, 0, 0, 0},
|
|
{44, 1, 0, 430}: {59, .44, 0, 0, 0},
|
|
|
|
// light weaponry (skill 45) http://acpedia.org/wiki/Light_Weaponry
|
|
// axe
|
|
{45, 3, 0, 0}: {22, .80, 0, 0, 0},
|
|
{45, 3, 0, 250}: {28, .80, 0, 0, 0},
|
|
{45, 3, 0, 300}: {33, .80, 0, 0, 0},
|
|
{45, 3, 0, 325}: {39, .80, 0, 0, 0},
|
|
{45, 3, 0, 350}: {44, .80, 0, 0, 0},
|
|
{45, 3, 0, 370}: {50, .80, 0, 0, 0},
|
|
{45, 3, 0, 400}: {55, .80, 0, 0, 0},
|
|
{45, 3, 0, 420}: {57, .80, 0, 0, 0},
|
|
{45, 3, 0, 430}: {61, .80, 0, 0, 0},
|
|
// dagger
|
|
{45, 6, 0, 0}: {18, .42, 0, 0, 0},
|
|
{45, 6, 0, 250}: {24, .42, 0, 0, 0},
|
|
{45, 6, 0, 300}: {29, .42, 0, 0, 0},
|
|
{45, 6, 0, 325}: {35, .42, 0, 0, 0},
|
|
{45, 6, 0, 350}: {40, .42, 0, 0, 0},
|
|
{45, 6, 0, 370}: {46, .42, 0, 0, 0},
|
|
{45, 6, 0, 400}: {51, .42, 0, 0, 0},
|
|
{45, 6, 0, 420}: {54, .42, 0, 0, 0},
|
|
{45, 6, 0, 430}: {58, .42, 0, 0, 0},
|
|
// msdagger
|
|
{45, 6, 1, 0}: {7, .24, 0, 0, 0},
|
|
{45, 6, 1, 250}: {11, .24, 0, 0, 0},
|
|
{45, 6, 1, 300}: {13, .24, 0, 0, 0},
|
|
{45, 6, 1, 325}: {16, .24, 0, 0, 0},
|
|
{45, 6, 1, 350}: {18, .24, 0, 0, 0},
|
|
{45, 6, 1, 370}: {21, .24, 0, 0, 0},
|
|
{45, 6, 1, 400}: {24, .24, 0, 0, 0},
|
|
{45, 6, 1, 420}: {27, .24, 0, 0, 0},
|
|
{45, 6, 1, 430}: {28, .24, 0, 0, 0},
|
|
// mace
|
|
{45, 4, 0, 0}: {19, .23, 0, 0, 0},
|
|
{45, 4, 0, 250}: {24, .23, 0, 0, 0},
|
|
{45, 4, 0, 300}: {29, .23, 0, 0, 0},
|
|
{45, 4, 0, 325}: {35, .23, 0, 0, 0},
|
|
{45, 4, 0, 350}: {40, .23, 0, 0, 0},
|
|
{45, 4, 0, 370}: {45, .23, 0, 0, 0},
|
|
{45, 4, 0, 400}: {50, .23, 0, 0, 0},
|
|
{45, 4, 0, 420}: {53, .23, 0, 0, 0},
|
|
{45, 4, 0, 430}: {57, .23, 0, 0, 0},
|
|
// spear
|
|
{45, 5, 0, 0}: {21, .65, 0, 0, 0},
|
|
{45, 5, 0, 250}: {26, .65, 0, 0, 0},
|
|
{45, 5, 0, 300}: {32, .65, 0, 0, 0},
|
|
{45, 5, 0, 325}: {37, .65, 0, 0, 0},
|
|
{45, 5, 0, 350}: {42, .65, 0, 0, 0},
|
|
{45, 5, 0, 370}: {48, .65, 0, 0, 0},
|
|
{45, 5, 0, 400}: {53, .65, 0, 0, 0},
|
|
{45, 5, 0, 420}: {57, .65, 0, 0, 0},
|
|
{45, 5, 0, 430}: {60, .65, 0, 0, 0},
|
|
// sword
|
|
{45, 2, 0, 0}: {20, .42, 0, 0, 0},
|
|
{45, 2, 0, 250}: {25, .42, 0, 0, 0},
|
|
{45, 2, 0, 300}: {31, .42, 0, 0, 0},
|
|
{45, 2, 0, 325}: {36, .42, 0, 0, 0},
|
|
{45, 2, 0, 350}: {41, .42, 0, 0, 0},
|
|
{45, 2, 0, 370}: {47, .42, 0, 0, 0},
|
|
{45, 2, 0, 400}: {52, .42, 0, 0, 0},
|
|
{45, 2, 0, 420}: {55, .42, 0, 0, 0},
|
|
{45, 2, 0, 430}: {58, .42, 0, 0, 0},
|
|
// mssword
|
|
{45, 2, 1, 0}: {7, .24, 0, 0, 0},
|
|
{45, 2, 1, 250}: {10, .24, 0, 0, 0},
|
|
{45, 2, 1, 300}: {13, .24, 0, 0, 0},
|
|
{45, 2, 1, 325}: {16, .24, 0, 0, 0},
|
|
{45, 2, 1, 350}: {18, .24, 0, 0, 0},
|
|
{45, 2, 1, 370}: {21, .24, 0, 0, 0},
|
|
{45, 2, 1, 400}: {24, .24, 0, 0, 0},
|
|
{45, 2, 1, 420}: {25, .24, 0, 0, 0},
|
|
{45, 2, 1, 430}: {28, .24, 0, 0, 0},
|
|
// staff
|
|
{45, 7, 0, 0}: {19, .325, 0, 0, 0},
|
|
{45, 7, 0, 250}: {24, .325, 0, 0, 0},
|
|
{45, 7, 0, 300}: {30, .325, 0, 0, 0},
|
|
{45, 7, 0, 325}: {35, .325, 0, 0, 0},
|
|
{45, 7, 0, 350}: {40, .325, 0, 0, 0},
|
|
{45, 7, 0, 370}: {46, .325, 0, 0, 0},
|
|
{45, 7, 0, 400}: {51, .325, 0, 0, 0},
|
|
{45, 7, 0, 420}: {54, .325, 0, 0, 0},
|
|
{45, 7, 0, 430}: {57, .325, 0, 0, 0},
|
|
// ua
|
|
{45, 1, 0, 0}: {17, .43, 0, 0, 0},
|
|
{45, 1, 0, 250}: {23, .43, 0, 0, 0},
|
|
{45, 1, 0, 300}: {27, .43, 0, 0, 0},
|
|
{45, 1, 0, 325}: {31, .43, 0, 0, 0},
|
|
{45, 1, 0, 350}: {35, .43, 0, 0, 0},
|
|
{45, 1, 0, 370}: {40, .43, 0, 0, 0},
|
|
{45, 1, 0, 400}: {44, .43, 0, 0, 0},
|
|
{45, 1, 0, 420}: {46, .43, 0, 0, 0},
|
|
{45, 1, 0, 430}: {48, .43, 0, 0, 0},
|
|
|
|
// finesse weaponry (skill 46) http://acpedia.org/wiki/Finesse_Weaponry
|
|
// axe
|
|
{46, 3, 0, 0}: {22, .80, 0, 0, 0},
|
|
{46, 3, 0, 250}: {28, .80, 0, 0, 0},
|
|
{46, 3, 0, 300}: {33, .80, 0, 0, 0},
|
|
{46, 3, 0, 325}: {39, .80, 0, 0, 0},
|
|
{46, 3, 0, 350}: {44, .80, 0, 0, 0},
|
|
{46, 3, 0, 370}: {50, .80, 0, 0, 0},
|
|
{46, 3, 0, 400}: {55, .80, 0, 0, 0},
|
|
{46, 3, 0, 420}: {57, .80, 0, 0, 0},
|
|
{46, 3, 0, 430}: {61, .80, 0, 0, 0},
|
|
// dagger
|
|
{46, 6, 0, 0}: {18, .42, 0, 0, 0},
|
|
{46, 6, 0, 250}: {24, .42, 0, 0, 0},
|
|
{46, 6, 0, 300}: {29, .42, 0, 0, 0},
|
|
{46, 6, 0, 325}: {35, .42, 0, 0, 0},
|
|
{46, 6, 0, 350}: {40, .42, 0, 0, 0},
|
|
{46, 6, 0, 370}: {46, .42, 0, 0, 0},
|
|
{46, 6, 0, 400}: {51, .42, 0, 0, 0},
|
|
{46, 6, 0, 420}: {54, .42, 0, 0, 0},
|
|
{46, 6, 0, 430}: {58, .42, 0, 0, 0},
|
|
// msdagger
|
|
{46, 6, 1, 0}: {7, .24, 0, 0, 0},
|
|
{46, 6, 1, 250}: {11, .24, 0, 0, 0},
|
|
{46, 6, 1, 300}: {13, .24, 0, 0, 0},
|
|
{46, 6, 1, 325}: {16, .24, 0, 0, 0},
|
|
{46, 6, 1, 350}: {18, .24, 0, 0, 0},
|
|
{46, 6, 1, 370}: {21, .24, 0, 0, 0},
|
|
{46, 6, 1, 400}: {24, .24, 0, 0, 0},
|
|
{46, 6, 1, 420}: {27, .24, 0, 0, 0},
|
|
{46, 6, 1, 430}: {28, .24, 0, 0, 0},
|
|
// mace
|
|
{46, 4, 0, 0}: {19, .23, 0, 0, 0},
|
|
{46, 4, 0, 250}: {24, .23, 0, 0, 0},
|
|
{46, 4, 0, 300}: {29, .23, 0, 0, 0},
|
|
{46, 4, 0, 325}: {35, .23, 0, 0, 0},
|
|
{46, 4, 0, 350}: {40, .23, 0, 0, 0},
|
|
{46, 4, 0, 370}: {45, .23, 0, 0, 0},
|
|
{46, 4, 0, 400}: {50, .23, 0, 0, 0},
|
|
{46, 4, 0, 420}: {53, .23, 0, 0, 0},
|
|
{46, 4, 0, 430}: {57, .23, 0, 0, 0},
|
|
// spear
|
|
{46, 5, 0, 0}: {21, .65, 0, 0, 0},
|
|
{46, 5, 0, 250}: {26, .65, 0, 0, 0},
|
|
{46, 5, 0, 300}: {32, .65, 0, 0, 0},
|
|
{46, 5, 0, 325}: {37, .65, 0, 0, 0},
|
|
{46, 5, 0, 350}: {42, .65, 0, 0, 0},
|
|
{46, 5, 0, 370}: {48, .65, 0, 0, 0},
|
|
{46, 5, 0, 400}: {53, .65, 0, 0, 0},
|
|
{46, 5, 0, 420}: {57, .65, 0, 0, 0},
|
|
{46, 5, 0, 430}: {60, .65, 0, 0, 0},
|
|
// sword
|
|
{46, 2, 0, 0}: {20, .42, 0, 0, 0},
|
|
{46, 2, 0, 250}: {25, .42, 0, 0, 0},
|
|
{46, 2, 0, 300}: {31, .42, 0, 0, 0},
|
|
{46, 2, 0, 325}: {36, .42, 0, 0, 0},
|
|
{46, 2, 0, 350}: {41, .42, 0, 0, 0},
|
|
{46, 2, 0, 370}: {47, .42, 0, 0, 0},
|
|
{46, 2, 0, 400}: {52, .42, 0, 0, 0},
|
|
{46, 2, 0, 420}: {55, .42, 0, 0, 0},
|
|
{46, 2, 0, 430}: {58, .42, 0, 0, 0},
|
|
// mssword
|
|
{46, 2, 1, 0}: {7, .24, 0, 0, 0},
|
|
{46, 2, 1, 250}: {10, .24, 0, 0, 0},
|
|
{46, 2, 1, 300}: {13, .24, 0, 0, 0},
|
|
{46, 2, 1, 325}: {16, .24, 0, 0, 0},
|
|
{46, 2, 1, 350}: {18, .24, 0, 0, 0},
|
|
{46, 2, 1, 370}: {21, .24, 0, 0, 0},
|
|
{46, 2, 1, 400}: {24, .24, 0, 0, 0},
|
|
{46, 2, 1, 420}: {25, .24, 0, 0, 0},
|
|
{46, 2, 1, 430}: {28, .24, 0, 0, 0},
|
|
// staff
|
|
{46, 7, 0, 0}: {19, .325, 0, 0, 0},
|
|
{46, 7, 0, 250}: {24, .325, 0, 0, 0},
|
|
{46, 7, 0, 300}: {30, .325, 0, 0, 0},
|
|
{46, 7, 0, 325}: {35, .325, 0, 0, 0},
|
|
{46, 7, 0, 350}: {40, .325, 0, 0, 0},
|
|
{46, 7, 0, 370}: {46, .325, 0, 0, 0},
|
|
{46, 7, 0, 400}: {51, .325, 0, 0, 0},
|
|
{46, 7, 0, 420}: {54, .325, 0, 0, 0},
|
|
{46, 7, 0, 430}: {57, .325, 0, 0, 0},
|
|
// ua
|
|
{46, 1, 0, 0}: {17, .43, 0, 0, 0},
|
|
{46, 1, 0, 250}: {23, .43, 0, 0, 0},
|
|
{46, 1, 0, 300}: {27, .43, 0, 0, 0},
|
|
{46, 1, 0, 325}: {31, .43, 0, 0, 0},
|
|
{46, 1, 0, 350}: {35, .43, 0, 0, 0},
|
|
{46, 1, 0, 370}: {40, .43, 0, 0, 0},
|
|
{46, 1, 0, 400}: {44, .43, 0, 0, 0},
|
|
{46, 1, 0, 420}: {46, .43, 0, 0, 0},
|
|
{46, 1, 0, 430}: {48, .43, 0, 0, 0},
|
|
|
|
// twohanded (skill 41) http://acpedia.org/wiki/Two_Handed_Weaponry
|
|
// cleaver
|
|
{41, 11, 1, 0}: {13, .30, 0, 0, 0},
|
|
{41, 11, 1, 250}: {17, .30, 0, 0, 0},
|
|
{41, 11, 1, 300}: {22, .30, 0, 0, 0},
|
|
{41, 11, 1, 325}: {26, .30, 0, 0, 0},
|
|
{41, 11, 1, 350}: {30, .30, 0, 0, 0},
|
|
{41, 11, 1, 370}: {35, .30, 0, 0, 0},
|
|
{41, 11, 1, 400}: {39, .30, 0, 0, 0},
|
|
{41, 11, 1, 420}: {42, .30, 0, 0, 0},
|
|
{41, 11, 1, 430}: {45, .30, 0, 0, 0},
|
|
// spear
|
|
{41, 11, 0, 0}: {14, .35, 0, 0, 0},
|
|
{41, 11, 0, 250}: {19, .35, 0, 0, 0},
|
|
{41, 11, 0, 300}: {24, .35, 0, 0, 0},
|
|
{41, 11, 0, 325}: {29, .35, 0, 0, 0},
|
|
{41, 11, 0, 350}: {33, .35, 0, 0, 0},
|
|
{41, 11, 0, 370}: {37, .35, 0, 0, 0},
|
|
{41, 11, 0, 400}: {42, .35, 0, 0, 0},
|
|
{41, 11, 0, 420}: {45, .35, 0, 0, 0},
|
|
{41, 11, 0, 430}: {48, .35, 0, 0, 0},
|
|
|
|
// missile (skill 47) http://acpedia.org/wiki/Missile_Weaponry/Bows
|
|
// bow (mastery 8)
|
|
{47, 8, 0, 0}: {0, 0, 110, 0, 0},
|
|
{47, 8, 0, 250}: {0, 0, 120, 0, 0},
|
|
{47, 8, 0, 270}: {0, 0, 130, 0, 0},
|
|
{47, 8, 0, 290}: {0, 0, 140, 0, 0},
|
|
{47, 8, 0, 315}: {0, 0, 140, 5, 0},
|
|
{47, 8, 0, 335}: {0, 0, 140, 9, 0},
|
|
{47, 8, 0, 360}: {0, 0, 140, 16, 0},
|
|
{47, 8, 0, 375}: {0, 0, 140, 19, 0},
|
|
{47, 8, 0, 385}: {0, 0, 140, 22, 0},
|
|
// xbow (mastery 9)
|
|
{47, 9, 0, 0}: {0, 0, 140, 0, 0},
|
|
{47, 9, 0, 250}: {0, 0, 150, 0, 0},
|
|
{47, 9, 0, 270}: {0, 0, 155, 0, 0},
|
|
{47, 9, 0, 290}: {0, 0, 165, 0, 0},
|
|
{47, 9, 0, 315}: {0, 0, 165, 5, 0},
|
|
{47, 9, 0, 335}: {0, 0, 165, 9, 0},
|
|
{47, 9, 0, 360}: {0, 0, 165, 16, 0},
|
|
{47, 9, 0, 375}: {0, 0, 165, 19, 0},
|
|
{47, 9, 0, 385}: {0, 0, 165, 22, 0},
|
|
// thrown (mastery 10)
|
|
{47, 10, 0, 0}: {0, 0, 130, 0, 0},
|
|
{47, 10, 0, 250}: {0, 0, 140, 0, 0},
|
|
{47, 10, 0, 270}: {0, 0, 150, 0, 0},
|
|
{47, 10, 0, 290}: {0, 0, 160, 0, 0},
|
|
{47, 10, 0, 315}: {0, 0, 160, 5, 0},
|
|
{47, 10, 0, 335}: {0, 0, 160, 9, 0},
|
|
{47, 10, 0, 360}: {0, 0, 160, 16, 0},
|
|
{47, 10, 0, 375}: {0, 0, 160, 19, 0},
|
|
{47, 10, 0, 385}: {0, 0, 160, 22, 0},
|
|
|
|
// wands (skill 34 war / 43 void) http://acpedia.org/wiki/Magic_Casters
|
|
{34, 0, 0, 290}: {0, 0, 0, 0, 1.03},
|
|
{34, 0, 0, 315}: {0, 0, 0, 0, 1.06},
|
|
{34, 0, 0, 335}: {0, 0, 0, 0, 1.09},
|
|
{34, 0, 0, 360}: {0, 0, 0, 0, 1.13},
|
|
{34, 0, 0, 375}: {0, 0, 0, 0, 1.16},
|
|
{34, 0, 0, 385}: {0, 0, 0, 0, 1.18},
|
|
|
|
{43, 0, 0, 290}: {0, 0, 0, 0, 1.03},
|
|
{43, 0, 0, 315}: {0, 0, 0, 0, 1.06},
|
|
{43, 0, 0, 335}: {0, 0, 0, 0, 1.09},
|
|
{43, 0, 0, 360}: {0, 0, 0, 0, 1.13},
|
|
{43, 0, 0, 375}: {0, 0, 0, 0, 1.16},
|
|
{43, 0, 0, 385}: {0, 0, 0, 0, 1.18},
|
|
// void vr wand - not sure why this is different (mastery 12)
|
|
{43, 12, 0, 385}: {0, 0, 0, 0, 1.18},
|
|
}
|
|
|
|
// maxDamageSpellBonus is transcribed from UB Dictionaries.cs
|
|
// LongValueKeySpellEffects entries keyed on LongValueKey_MaxDamage (218103842).
|
|
var maxDamageSpellBonus = map[int]int{
|
|
1616: 20, // Blood Drinker VI
|
|
2096: 22, // Infected Caress
|
|
5183: 24, // Incantation of Blood Drinker (post Feb-2013)
|
|
4395: 24, // Incantation of Blood Drinker (post Feb-2013)
|
|
2598: 2, // Minor Blood Thirst
|
|
2586: 4, // Major Blood Thirst
|
|
4661: 7, // Epic Blood Thirst
|
|
6089: 10, // Legendary Blood Thirst
|
|
3688: 300, // Prodigal Blood Drinker
|
|
}
|
|
|
|
// elemVsMonSpellBonus is transcribed from UB Dictionaries.cs
|
|
// DoubleValueKeySpellEffects entries keyed on DoubleValueKey_ElementalDamageVersusMonsters (152).
|
|
var elemVsMonSpellBonus = map[int]float64{
|
|
3258: .06, // Spirit Drinker VI
|
|
3259: .07, // Infected Spirit Caress
|
|
5182: .08, // Incantation of Spirit Drinker (post Feb-2013)
|
|
4414: .08, // Incantation of Spirit Drinker (post Feb-2013)
|
|
3251: .01, // Minor Spirit Thirst
|
|
3250: .03, // Major Spirit Thirst
|
|
4670: .05, // Epic Spirit Thirst
|
|
6098: .07, // Legendary Spirit Thirst
|
|
}
|
|
|
|
// buffedMaxDamage mirrors GetBuffedIntValueKey(218103842): base value plus
|
|
// bonus for each innate spell (raw["Spells"]), minus bonus for each active
|
|
// spell (raw["ActiveSpells"]).
|
|
func buffedMaxDamage(raw, iv map[string]any) int {
|
|
value := ivI(iv, "218103842", 0)
|
|
for _, id := range toIntList(raw["Spells"]) {
|
|
if b, ok := maxDamageSpellBonus[id]; ok {
|
|
value += b
|
|
}
|
|
}
|
|
for _, id := range toIntList(raw["ActiveSpells"]) {
|
|
if b, ok := maxDamageSpellBonus[id]; ok {
|
|
value -= b
|
|
}
|
|
}
|
|
return value
|
|
}
|
|
|
|
// buffedElemVsMon mirrors GetBuffedDoubleValueKey(152) using the same
|
|
// innate-add / active-subtract semantics.
|
|
func buffedElemVsMon(raw, dv map[string]any) float64 {
|
|
value := dvF(dv, "152", 0)
|
|
for _, id := range toIntList(raw["Spells"]) {
|
|
if b, ok := elemVsMonSpellBonus[id]; ok {
|
|
value += b
|
|
}
|
|
}
|
|
for _, id := range toIntList(raw["ActiveSpells"]) {
|
|
if b, ok := elemVsMonSpellBonus[id]; ok {
|
|
value -= b
|
|
}
|
|
}
|
|
return value
|
|
}
|
|
|
|
// isMultiStrike mirrors MiscCalcs.GetMaxProperty's MultiStrike detection:
|
|
// IntValues[47] (WeaponType master id) ∈ {160,166,486}, or (==4 and Mastery==11).
|
|
func isMultiStrike(iv map[string]any) int {
|
|
wt := ivI(iv, "47", 0)
|
|
if wt == 160 || wt == 166 || wt == 486 {
|
|
return 1
|
|
}
|
|
if wt == 4 && ivI(iv, "353", 0) == 11 {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func round2(x float64) float64 {
|
|
return math.Round(x*100) / 100
|
|
}
|
|
|
|
// computeOD ports UB's OD display formulas (ItemInfo.cs CalcMeleeDamage /
|
|
// CalcMissileDamage + the OD lines ~631-647). Returns (0, false) when OD does
|
|
// not apply: non weapon/wand object class, non-loot-gen item (no
|
|
// SalvageWorkmanship), or an unrecognized (skill, mastery, multiStrike,
|
|
// wieldReq) tier (exact match only — no nearest-tier rounding).
|
|
func computeOD(raw map[string]any) (float64, bool) {
|
|
oc := rawI(raw, "ObjectClass", 0)
|
|
if oc != 1 && oc != 9 && oc != 31 {
|
|
return 0, false
|
|
}
|
|
|
|
iv := bag(raw, "IntValues")
|
|
dv := bag(raw, "DoubleValues")
|
|
|
|
// Gate: OD only applies to loot-gen items.
|
|
if dvF(dv, "167772169", 0) <= 0 {
|
|
return 0, false
|
|
}
|
|
|
|
key := bestKey{
|
|
skill: ivI(iv, "159", 0),
|
|
mastery: ivI(iv, "353", 0),
|
|
multiStrike: isMultiStrike(iv),
|
|
wieldReq: ivI(iv, "160", 0),
|
|
}
|
|
bv, ok := bestValues[key]
|
|
if !ok && oc == 31 {
|
|
// Wands: our probe showed some wands lack key 353 (mastery), which
|
|
// defaults to 0 via ivI — but retry explicitly with mastery 0 so the
|
|
// void VR wand row (43,12,...) stays reachable while items that
|
|
// genuinely carry a different mastery value still fall back to the
|
|
// normal (34|43, 0, ...) rows.
|
|
key.mastery = 0
|
|
bv, ok = bestValues[key]
|
|
}
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
|
|
switch oc {
|
|
case 1: // melee
|
|
variance := dvF(dv, "167772171", 0)
|
|
if variance <= 0 {
|
|
return 0, false
|
|
}
|
|
// NOTE: literal ratio is variance/bv.maxVar, not bv.maxVar/variance as
|
|
// the plan's prose (and the UB source's `perfectVariance/Variance`
|
|
// argument order) states — see od.go deviation note below and the
|
|
// implementation report for why.
|
|
varianceTinks := round2(math.Log(variance/bv.maxVar) / math.Log(0.8))
|
|
od := float64(buffedMaxDamage(raw, iv)) - varianceTinks - bv.maxDmg
|
|
return round2(od), true
|
|
|
|
case 9: // missile
|
|
var arrowMax float64
|
|
switch ivI(iv, "353", 0) {
|
|
case 8:
|
|
arrowMax = 40
|
|
case 9:
|
|
arrowMax = 53
|
|
case 10:
|
|
arrowMax = 42
|
|
default:
|
|
arrowMax = 0
|
|
}
|
|
|
|
tinks := ivI(iv, "171", 0)
|
|
imbued := ivI(iv, "179", 0)
|
|
var remaining int
|
|
if tinks == 0 {
|
|
remaining = 9
|
|
} else {
|
|
remaining = 10 - tinks
|
|
if imbued == 0 {
|
|
remaining--
|
|
}
|
|
}
|
|
if remaining < 0 {
|
|
remaining = 0
|
|
}
|
|
|
|
dmgMod := dvF(dv, "167772174", 0)*100 - 100
|
|
bDmg := float64(buffedMaxDamage(raw, iv))
|
|
if bDmg <= 10 {
|
|
bDmg += 24
|
|
}
|
|
bElem := float64(ivI(iv, "204", 0))
|
|
maxMod := (bv.maxDmgMod + 136) / 100
|
|
|
|
od := (1+(dmgMod+4*float64(remaining))/100)*(bElem+bDmg+arrowMax)/maxMod - (bv.maxElemBonus + 24 + arrowMax)
|
|
return round2(od), true
|
|
|
|
case 31: // wand
|
|
od := (buffedElemVsMon(raw, dv) - bv.maxElemVsMon) * 100
|
|
return round2(od), true
|
|
}
|
|
|
|
return 0, false
|
|
}
|