mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-11-19 10:56:07 -05:00
fc87b2be7a
* Remove weapons, armor, misc, itemCommon, itemTyps datadict singletons - removed loader calls from d2app - removed the HeroObjects singleton from `d2core/d2inventory` - added an InventoryItemFactory in d2inventory - package-level functions that use data records are now methods of the InventoryItemFactory - renamed ItemGenerator in d2item to ItemFactory - package-level functions that use records are now methods of ItemFactory - d2map.MapEntityFactory now has an item factory instance for creating items - fixed a bug in unique item record loader where it loaded an empty record - added a PlayerStateFactory for creating a player state (uses the asset manager) - updated the test inventory/equipment code in d2player to handle errors from the ItemFactory - character select and character creation screens have a player state and inventory item factory - updated item tests to use the item factory * minor edit * Removed d2datadict.Experience singleton added a HeroStatsFactory, much like the other factories. The factory gets an asset manager reference in order to use data records. * removed d2datadict.AutoMagic singleton * removed d2datadict.AutoMap singleton * removed d2datadict.BodyLocations singleton * removed d2datadict.Books singleton * Removed singletons for level records - removed loader calls in d2app - changed type references from d2datadict to d2records - added a `MapGenerator` in d2mapgen which uses thew asset manager and map engine - package-level map generation functions are now MapGenerator methods - `d2datadict.GetLevelDetails(id int)` is now a method of the RecordManager * remove SkillCalc and MissileCalc singletons * Removed CharStats and ItemStatCost singletons - added an ItemStatFactory which uses the asset manager to create stats - package-level functions for stats in d2item are now StatFactory methods - changed type references from d2datadict to d2records - `d2player.GetAllPlayerStates` is now a method of the `PlayerStateFactory` * Removed DkillDesc and Skills singletons from d2datadict - removed loader calls from d2app - diablo2stats.Stat instances are given a reference to the factory for doing record lookups * update the stats test to use mock a asset manager and stat factory * fixed diablo2stats tests and diablo2item tests * removed CompCodes singleton from d2datadict * remove cubemain singleton from d2datadict * removed DifficultyLevels singleton from d2datadict * removed ElemTypes singleton from d2datadict * removed events.go loader from d2datadict (was unused) * removed Gems singleton from d2datadict * removed Hireling and Inventory singletons from d2datadict * removed MagicPrefix and MagicSuffix singletons from d2datadict * removed ItemRatios singleton from d2datadict * removed Missiles singleton from d2datadict * removed MonModes singleton * Removed all monster and npc singletons from d2datadict - MapStamp instances now get a reference to their factory for doing record lookups * removed SoundEntry and SoundEnviron singletons from d2datadict
415 lines
11 KiB
Go
415 lines
11 KiB
Go
package diablo2stats
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
|
)
|
|
|
|
const (
|
|
errStr string = "incorrect description string format for stat"
|
|
errFmt string = "%v:\n\tDescFnID: %v\n\tKey: %v\n\tVal: %+v\n\texpected: %v\n\tgot: %v\n\n"
|
|
)
|
|
|
|
var itemStatCosts = map[string]*d2records.ItemStatCostRecord{
|
|
"strength": {
|
|
Name: "strength",
|
|
DescFnID: 1,
|
|
DescVal: int(descValPrefix),
|
|
DescStrPos: "to Strength",
|
|
DescStrNeg: "to Strength",
|
|
},
|
|
"dexterity": {
|
|
Name: "dexterity",
|
|
DescFnID: 1,
|
|
DescVal: int(descValPrefix),
|
|
DescStrPos: "to Dexterity",
|
|
DescStrNeg: "to Dexterity",
|
|
},
|
|
"vitality": {
|
|
Name: "vitality",
|
|
DescFnID: 1,
|
|
DescVal: int(descValPrefix),
|
|
DescStrPos: "to Vitality",
|
|
DescStrNeg: "to Vitality",
|
|
},
|
|
"energy": {
|
|
Name: "energy",
|
|
DescFnID: 1,
|
|
DescVal: int(descValPrefix),
|
|
DescStrPos: "to Energy",
|
|
DescStrNeg: "to Energy",
|
|
},
|
|
"hpregen": {
|
|
Name: "hpregen",
|
|
DescFnID: 1,
|
|
DescVal: int(descValPostfix),
|
|
DescStrPos: "Replenish Life",
|
|
DescStrNeg: "Drain Life",
|
|
},
|
|
"toblock": {
|
|
Name: "toblock",
|
|
DescFnID: 2,
|
|
DescVal: int(descValPrefix),
|
|
DescStrPos: "Increased Chance of Blocking",
|
|
DescStrNeg: "Increased Chance of Blocking",
|
|
},
|
|
"item_absorblight_percent": {
|
|
Name: "item_absorblight_percent",
|
|
DescFnID: 2,
|
|
DescVal: int(descValPostfix),
|
|
DescStrPos: "Lightning Absorb",
|
|
DescStrNeg: "Lightning Absorb",
|
|
},
|
|
"item_restinpeace": {
|
|
Name: "item_restinpeace",
|
|
DescFnID: 3,
|
|
DescVal: int(descValHide),
|
|
DescStrPos: "Slain Monsters Rest in Peace",
|
|
DescStrNeg: "Slain Monsters Rest in Peace",
|
|
},
|
|
"normal_damage_reduction": {
|
|
Name: "normal_damage_reduction",
|
|
DescFnID: 3,
|
|
DescVal: int(descValPostfix),
|
|
DescStrPos: "Damage Reduced by",
|
|
DescStrNeg: "Damage Reduced by",
|
|
},
|
|
"poisonresist": {
|
|
Name: "poisonresist",
|
|
DescFnID: 4,
|
|
DescVal: int(descValPostfix),
|
|
DescStrPos: "Poison Resist",
|
|
DescStrNeg: "Poison Resist",
|
|
},
|
|
"item_fastermovevelocity": {
|
|
Name: "item_fastermovevelocity",
|
|
DescFnID: 4,
|
|
DescVal: int(descValPrefix),
|
|
DescStrPos: "Faster Run/Walk",
|
|
DescStrNeg: "Faster Run/Walk",
|
|
},
|
|
"item_howl": {
|
|
Name: "item_howl",
|
|
DescFnID: 5,
|
|
DescVal: int(descValPostfix),
|
|
DescStrPos: "Hit Causes Monster to Flee",
|
|
DescStrNeg: "Hit Causes Monster to Flee",
|
|
},
|
|
"item_hp_perlevel": {
|
|
Name: "item_hp_perlevel",
|
|
DescFnID: 6,
|
|
DescVal: int(descValPrefix),
|
|
DescStrPos: "to Life",
|
|
DescStrNeg: "to Life",
|
|
DescStr2: "(Based on Character Level)",
|
|
},
|
|
"item_resist_ltng_perlevel": {
|
|
Name: "item_resist_ltng_perlevel",
|
|
DescFnID: 7,
|
|
DescVal: int(descValPostfix),
|
|
DescStrPos: "Lightning Resist",
|
|
DescStrNeg: "Lightning Resist",
|
|
DescStr2: "(Based on Character Level)",
|
|
},
|
|
"item_find_magic_perlevel": {
|
|
Name: "item_find_magic_perlevel",
|
|
DescFnID: 7,
|
|
DescVal: int(descValPrefix),
|
|
DescStrPos: "Better Chance of Getting Magic Items",
|
|
DescStrNeg: "Better Chance of Getting Magic Items",
|
|
DescStr2: "(Based on Character Level)",
|
|
},
|
|
"item_armorpercent_perlevel": {
|
|
Name: "item_armorpercent_perlevel",
|
|
DescFnID: 8,
|
|
DescVal: int(descValPrefix),
|
|
DescStrPos: "Enhanced Defense",
|
|
DescStrNeg: "Enhanced Defense",
|
|
DescStr2: "(Based on Character Level)",
|
|
},
|
|
"item_regenstamina_perlevel": {
|
|
Name: "item_regenstamina_perlevel",
|
|
DescFnID: 8,
|
|
DescVal: int(descValPostfix),
|
|
DescStrPos: "Heal Stamina Plus",
|
|
DescStrNeg: "Heal Stamina Plus",
|
|
DescStr2: "(Based on Character Level)",
|
|
},
|
|
"item_thorns_perlevel": {
|
|
Name: "item_thorns_perlevel",
|
|
DescFnID: 9,
|
|
DescVal: int(descValPostfix),
|
|
DescStrPos: "Attacker Takes Damage of",
|
|
DescStrNeg: "Attacker Takes Damage of",
|
|
DescStr2: "(Based on Character Level)",
|
|
},
|
|
"item_replenish_durability": {
|
|
Name: "item_replenish_durability",
|
|
DescFnID: 11,
|
|
DescVal: int(descValPrefix),
|
|
DescStrPos: "Repairs %v durability per second",
|
|
DescStrNeg: "Repairs %v durability per second",
|
|
DescStr2: "",
|
|
},
|
|
"item_stupidity": {
|
|
Name: "item_stupidity",
|
|
DescFnID: 12,
|
|
DescVal: int(descValPostfix),
|
|
DescStrPos: "Hit Blinds Target",
|
|
DescStrNeg: "Hit Blinds Target",
|
|
},
|
|
"item_addclassskills": {
|
|
Name: "item_addclassskills",
|
|
DescFnID: 13,
|
|
DescVal: int(descValPrefix),
|
|
},
|
|
"item_addskill_tab": {
|
|
Name: "item_addskill_tab",
|
|
DescFnID: 14,
|
|
DescVal: int(descValPrefix),
|
|
},
|
|
"item_skillonattack": {
|
|
Name: "item_skillonattack",
|
|
DescFnID: 15,
|
|
DescVal: int(descValPrefix),
|
|
DescStrPos: "%d%% Chance to cast level %d %s on attack",
|
|
DescStrNeg: "%d%% Chance to cast level %d %s on attack",
|
|
},
|
|
"item_aura": {
|
|
Name: "item_aura",
|
|
DescFnID: 16,
|
|
DescVal: int(descValPrefix),
|
|
DescStrPos: "Level %d %s Aura When Equipped",
|
|
DescStrNeg: "Level %d %s Aura When Equipped",
|
|
},
|
|
"item_fractionaltargetac": {
|
|
Name: "item_fractionaltargetac",
|
|
DescFnID: 20,
|
|
DescVal: int(descValPrefix),
|
|
DescStrPos: "Target Defense",
|
|
DescStrNeg: "Target Defense",
|
|
},
|
|
"attack_vs_montype": {
|
|
Name: "item_fractionaltargetac",
|
|
DescFnID: 22,
|
|
DescVal: int(descValPrefix),
|
|
DescStrPos: "to Attack Rating versus",
|
|
DescStrNeg: "to Attack Rating versus",
|
|
},
|
|
"item_reanimate": {
|
|
Name: "item_reanimate",
|
|
DescFnID: 23,
|
|
DescVal: int(descValPostfix),
|
|
DescStrPos: "Reanimate as:",
|
|
DescStrNeg: "Reanimate as:",
|
|
},
|
|
"item_charged_skill": {
|
|
Name: "item_charged_skill",
|
|
DescFnID: 24,
|
|
DescVal: int(descValPostfix),
|
|
DescStrPos: "(%d/%d Charges)",
|
|
DescStrNeg: "(%d/%d Charges)",
|
|
},
|
|
"item_singleskill": {
|
|
Name: "item_singleskill",
|
|
DescFnID: 27,
|
|
DescVal: int(descValPostfix),
|
|
DescStrPos: "(%d/%d Charges)",
|
|
DescStrNeg: "(%d/%d Charges)",
|
|
},
|
|
"item_nonclassskill": {
|
|
Name: "item_nonclassskill",
|
|
DescFnID: 28,
|
|
DescVal: int(descValPostfix),
|
|
DescStrPos: "(%d/%d Charges)",
|
|
DescStrNeg: "(%d/%d Charges)",
|
|
},
|
|
}
|
|
|
|
var skillDetails = map[int]*d2records.SkillRecord{
|
|
37: {Skill: "Warmth"},
|
|
64: {Skill: "Frozen Orb"},
|
|
}
|
|
|
|
var monStats = map[string]*d2records.MonStatsRecord{
|
|
"Specter": {NameString: "Specter", ID: 40},
|
|
}
|
|
|
|
var charStats = map[d2enum.Hero]*d2records.CharStatsRecord{
|
|
d2enum.HeroPaladin: {
|
|
Class: d2enum.HeroPaladin,
|
|
SkillStrAll: "to Paladin Skill Levels",
|
|
SkillStrClassOnly: "(Paladin Only)",
|
|
SkillStrTab: [3]string{
|
|
"+%d to Combat Skills",
|
|
"+%d to Offensive Auras",
|
|
"+%d to Defensive Auras",
|
|
},
|
|
},
|
|
}
|
|
var testAssetManager2 *d2asset.AssetManager
|
|
|
|
var testStatFactory2 *StatFactory
|
|
|
|
func TestSetup_Stat(t *testing.T) {
|
|
testAssetManager2 = &d2asset.AssetManager{}
|
|
testAssetManager2.Records = &d2records.RecordManager{}
|
|
|
|
testStatFactory2, _ = NewStatFactory(testAssetManager2)
|
|
|
|
testAssetManager2.Records.Item.Stats = itemStatCosts
|
|
testAssetManager2.Records.Character.Stats = charStats
|
|
testAssetManager2.Records.Skill.Details = skillDetails
|
|
testAssetManager2.Records.Monster.Stats = monStats
|
|
}
|
|
|
|
func TestStat_Clone(t *testing.T) {
|
|
s1 := testStatFactory2.NewStat("strength", 5)
|
|
s2 := s1.Clone()
|
|
|
|
// make sure the stats are distinct
|
|
if &s1 == &s2 {
|
|
t.Errorf("stats share the same pointer %d == %d", &s1, &s2)
|
|
}
|
|
|
|
// make sure the stat values are unique
|
|
vs1, vs2 := s1.Values(), s2.Values()
|
|
if &vs1 == &vs2 {
|
|
t.Errorf("stat values share the same pointer %d == %d", &s1, &s2)
|
|
}
|
|
|
|
s2.Values()[0].SetInt(6)
|
|
v1, v2 := s1.Values()[0].Int(), s2.Values()[0].Int()
|
|
|
|
// make sure the value ranges are distinct
|
|
if v1 == v2 {
|
|
t.Errorf("clones should not share stat values")
|
|
}
|
|
}
|
|
|
|
func TestStat_Descriptions(t *testing.T) {
|
|
tests := []struct {
|
|
recordKey string
|
|
vals []float64
|
|
expect string
|
|
}{
|
|
// DescFn1
|
|
{"strength", []float64{31}, "+31 to Strength"},
|
|
{"hpregen", []float64{20}, "Replenish Life +20"},
|
|
{"hpregen", []float64{-8}, "Drain Life -8"},
|
|
|
|
// DescFn2
|
|
{"toblock", []float64{16}, "+16% Increased Chance of Blocking"},
|
|
{"item_absorblight_percent", []float64{10}, "Lightning Absorb +10%"},
|
|
|
|
// DescFn3
|
|
{"normal_damage_reduction", []float64{25}, "Damage Reduced by 25"},
|
|
{"item_restinpeace", []float64{25}, "Slain Monsters Rest in Peace"},
|
|
|
|
// DescFn4
|
|
{"poisonresist", []float64{25}, "Poison Resist +25%"},
|
|
{"item_fastermovevelocity", []float64{25}, "+25% Faster Run/Walk"},
|
|
|
|
// DescFn5
|
|
{"item_howl", []float64{25}, "Hit Causes Monster to Flee 25%"},
|
|
|
|
// DescFn6
|
|
{"item_hp_perlevel", []float64{25}, "+25 to Life (Based on Character Level)"},
|
|
|
|
// DescFn7
|
|
{"item_resist_ltng_perlevel", []float64{25},
|
|
"Lightning Resist +25% (Based on Character Level)"},
|
|
{"item_find_magic_perlevel", []float64{25}, "+25% Better Chance of Getting Magic Items (" +
|
|
"Based on Character Level)"},
|
|
|
|
// DescFn8
|
|
{"item_armorpercent_perlevel", []float64{25},
|
|
"+25% Enhanced Defense (Based on Character Level)"},
|
|
{"item_regenstamina_perlevel", []float64{25},
|
|
"Heal Stamina Plus +25% (Based on Character Level)"},
|
|
|
|
// DescFn9
|
|
{"item_thorns_perlevel", []float64{25}, "Attacker Takes Damage of 25 (" +
|
|
"Based on Character Level)"},
|
|
|
|
// DescFn11
|
|
{"item_replenish_durability", []float64{2}, "Repairs 2 durability per second"},
|
|
|
|
// DescFn12
|
|
{"item_stupidity", []float64{5}, "Hit Blinds Target +5"},
|
|
|
|
// DescFn13
|
|
{"item_addclassskills", []float64{5, 3}, "+5 to Paladin Skill Levels"},
|
|
|
|
// DescFn14
|
|
{"item_addskill_tab", []float64{5, 3, 0}, "+5 to Combat Skills (Paladin Only)"},
|
|
{"item_addskill_tab", []float64{5, 3, 1}, "+5 to Offensive Auras (Paladin Only)"},
|
|
{"item_addskill_tab", []float64{5, 3, 2}, "+5 to Defensive Auras (Paladin Only)"},
|
|
|
|
// DescFn15
|
|
{"item_skillonattack", []float64{5, 7, 64},
|
|
"5% Chance to cast level 7 Frozen Orb on attack"},
|
|
|
|
// DescFn16
|
|
{"item_aura", []float64{3, 37}, "Level 3 Warmth Aura When Equipped"},
|
|
|
|
// DescFn20
|
|
{"item_fractionaltargetac", []float64{-25}, "-25% Target Defense"},
|
|
|
|
// DescFn22
|
|
{"attack_vs_montype", []float64{25, 40}, "25% to Attack Rating versus Specter"},
|
|
|
|
// DescFn23
|
|
{"item_reanimate", []float64{25, 40}, "25% Reanimate as: Specter"},
|
|
|
|
// DescFn24
|
|
{"item_charged_skill", []float64{25, 64, 20, 19}, "Level 25 Frozen Orb (19/20 Charges)"},
|
|
|
|
// DescFn27
|
|
{"item_singleskill", []float64{25, 64, 3}, "+25 to Frozen Orb (Paladin Only)"},
|
|
|
|
// DescFn28
|
|
{"item_nonclassskill", []float64{25, 64}, "+25 to Frozen Orb"},
|
|
}
|
|
|
|
for idx := range tests {
|
|
test := tests[idx]
|
|
key := test.recordKey
|
|
record := itemStatCosts[key]
|
|
expect := test.expect
|
|
stat := testStatFactory2.NewStat(key, test.vals...)
|
|
|
|
if got := stat.String(); got != expect {
|
|
t.Errorf(errFmt, errStr, record.DescFnID, test.recordKey, test.vals, expect, got)
|
|
} else {
|
|
success := "[Desc Func %d][%s %+v] %s"
|
|
success = fmt.Sprintf(success, record.DescFnID, record.Name, test.vals, got)
|
|
fmt.Println(success)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDiablo2Stat_Combine(t *testing.T) {
|
|
a := testStatFactory2.NewStat("item_nonclassskill", 25, 64) // "+25 to Frozen Orb"
|
|
b := testStatFactory2.NewStat("item_nonclassskill", 5, 64) // "+5 to Frozen Orb"
|
|
|
|
c, err := a.Combine(b)
|
|
|
|
if err != nil || c.String() != "+30 to Frozen Orb" {
|
|
t.Errorf("stats combination failed\r%s", err)
|
|
}
|
|
|
|
d := testStatFactory2.NewStat("item_nonclassskill", 5, 37) // "+5 to Warmth"
|
|
_, err = c.Combine(d)
|
|
|
|
if err == nil {
|
|
t.Error("stats were combined when they should not have been.")
|
|
}
|
|
}
|