DataDictionary loader for ItemStatCost (#333)

* adding rules for swap files to .gitignore

* main, d2common: load Magic/Rare/Unique Affix

* d2common: item affixes only

removed Rare/Unique Prefix/Suffix as those are related to monsters, not items.

* removed debug print from item_affix.go

* changed item affix type names for clarity, removed debug print from data_dictionary

* d2common: item affix datadict and records

Item Affixes are defined in `/data/global/excel/Magic{Prefix,Suffix}.txt`
Rare and Unique Pre/Suffixes seem to be for monsters, not items.

d2common: item affixes only

removed Rare/Unique Prefix/Suffix as those are related to monsters, not items.

removed debug print from item_affix.go

changed item affix type names for clarity, removed debug print from data_dictionary

* reverting to pre-allocating memory for parsing txt lines

* removing the rest of the rare/unique definitions

* removing the rest of the rare/unique definitions

* adding ItemStatCost data dict loader
This commit is contained in:
dk 2020-06-13 16:52:22 -07:00 committed by GitHub
parent 80877380af
commit a25e42518d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 702 additions and 1 deletions

4
.gitignore vendored
View File

@ -8,4 +8,6 @@
/OpenDiablo2.exe
/OpenDiablo2
**/*.pprof
tags
*.swp
.*.swp
tags

View File

@ -0,0 +1,320 @@
package d2datadict
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"log"
)
// refer to https://d2mods.info/forum/kb/viewarticle?a=448
type ItemStatCostRecord struct {
Name string
Index int
Signed bool // whether the stat is signed
KeepZero bool // prevent from going negative (assume only client side)
// path_d2.mpq version doesnt have Ranged columne, excluding for now
// Ranged bool // game attempts to keep stat in a range, like strength >-1
MinAccr int // minimum ranged value
UpdateAnimRate bool // when altered, forces speed handler to adjust speed
SendOther bool // whether to send to other clients
SendBits int // #bits to send in stat update
SendParam int // #bits to send in stat update
Saved bool // whether this stat is saved in .d2s files
SavedSigned bool // whether the stat is saved as signed/unsigned
SavedBits int // #bits allocated to the value in .d2s file
SaveBits int // #bits saved to .d2s files, max == 2^SaveBits-1
SaveAdd int // how large the negative range is (lowers max, as well)
SaveParamBits int // #param bits are saved (safe value is 17)
Encode EncodingType // how the stat is encoded in .d2s files
CallbackEnabled bool // whether callback fn is called if value changes
// these two fields control additional cost on items
// cost * (1 + value * multiply / 1024)) + add (...)
CostAdd int
CostMultiply int
// CostDivide // exists in txt, but division hardcoded to 1024
// if divide is used, could we do (?):
// cost * (1 + value * multiply / divide)) + add (...)
ValShift int // controls how stat is stored in .d2s
// so that you can save `+1` instead of `+256`
OperatorType OperatorType
OpParam int
OpBase string
OpStat1 string
OpStat2 string
OpStat3 string
Direct bool // whether is temporary or permanent
MaxStat string // if Direct true, will not exceed val of MaxStat
ItemSpecific bool // prevents stacking with an existing stat on item
// like when socketing a jewel
DamageRelated bool // prevents stacking of stats while dual wielding
EventID1 d2enum.ItemEventType
EventID2 d2enum.ItemEventType
EventFuncID1 d2enum.ItemEventFuncID
EventFuncID2 d2enum.ItemEventFuncID
DescPriority int // determines order when displayed
DescFnID d2enum.DescFuncID
DescFn interface{} // the sprintf func
// Controls whenever and if so in what way the stat value is shown
// 0 = doesn't show the value of the stat
// 1 = shows the value of the stat infront of the description
// 2 = shows the value of the stat after the description.
DescVal int
DescStrPos string // string used when val is positive
DescStrNeg string
DescStr2 string // additional string used by some string funcs
// when stats in the same group have the same value they use the
// group func for desc (they need to be in the same affix)
DescGroup int
DescGroupFuncID d2enum.DescFuncID
DescGroupFn interface{} // group sprintf func
DescGroupVal int
DescGroupStrPos string // string used when val is positive
DescGroupStrNeg string
DescGroupStr2 string // additional string used by some string funcs
// Stay far away from this column unless you really know what you're
// doing and / or work for Blizzard, this column is used during bin-file
// creation to generate a cache regulating the op-stat stuff and other
// things, changing it can be futile, it works like the constants column
// in MonUMod.txt and has no other relation to ItemStatCost.txt, the first
// stat in the file simply must have this set or else you may break the
// entire op stuff.
Stuff string // ? TODO ?
}
type EncodingType int
const (
// TODO: determine other encoding types.
// didn't see anything about how this stuff is encoded, or the types...
EncodeDefault = EncodingType(iota)
)
type OperatorType int // for dynamic properties
const (
// just adds the stat to the unit directly
OpDefault = OperatorType(iota)
// adds opstat.base * statvalue / 100 to the opstat.
Op1
// adds (statvalue * basevalue) / (2 ^ param) to the opstat
// this does not work properly with any stat other then level because of the
// way this is updated, it is only refreshed when you re-equip the item,
// your character is saved or you level up, similar to passive skills, just
// because it looks like it works in the item description
// does not mean it does, the game just recalculates the information in the
// description every frame, while the values remain unchanged serverside.
Op2
// this is a percentage based version of op #2
// look at op #2 for information about the formula behind it, just
// remember the stat is increased by a percentage rather then by adding
// an integer.
Op3
// this works the same way op #2 works, however the stat bonus is
// added to the item and not to the player (so that +defense per level
// properly adds the defense to the armor and not to the character
// directly!)
Op4
// this works like op #4 but is percentage based, it is used for percentage
// based increase of stats that are found on the item itself, and not stats
// that are found on the character.
Op5
// like for op #7, however this adds a plain bonus to the stat, and just
// like #7 it also doesn't work so I won't bother to explain the arithmetic
// behind it either.
Op6
// this is used to increase a stat based on the current daytime of the game
// world by a percentage, there is no need to explain the arithmetics
// behind it because frankly enough it just doesn't work serverside, it
// only updates clientside so this op is essentially useless.
Op7
// hardcoded to work only with maxmana, this will apply the proper amount
// of mana to your character based on CharStats.txt for the amount of energy
// the stat added (doesn't work for non characters)
Op8
// hardcoded to work only with maxhp and maxstamina, this will apply the
// proper amount of maxhp and maxstamina to your character based on
// CharStats.txt for the amount of vitality the stat added (doesn't work
// for non characters)
Op9
// doesn't do anything, this has no switch case in the op function.
Op10
// adds opstat.base * statvalue / 100 similar to 1 and 13, the code just
// does a few more checks
Op11
// doesn't do anything, this has no switch case in the op function.
Op12
// adds opstat.base * statvalue / 100 to the value of opstat, this is
// useable only on items it will not apply the bonus to other unit types
// (this is why it is used for +% durability, +% level requirement,
// +% damage, +% defense ).
Op13
)
/* column names from path_d2.mpq/data/global/excel/ItemStatCost.txt
Stat
ID
Send Other
Signed
Send Bits
Send Param Bits
UpdateAnimRate
Saved
CSvSigned
CSvBits
CSvParam
fCallback
fMin
MinAccr
Encode
Add
Multiply
Divide
ValShift
1.09-Save Bits
1.09-Save Add
Save Bits
Save Add
Save Param Bits
keepzero
op
op param
op base
op stat1
op stat2
op stat3
direct
maxstat
itemspecific
damagerelated
itemevent1
itemeventfunc1
itemevent2
itemeventfunc2
descpriority
descfunc
descval
descstrpos
descstrneg
descstr2
dgrp
dgrpfunc
dgrpval
dgrpstrpos
dgrpstrneg
dgrpstr2
stuff
*eol
*/
var ItemStatCosts map[string]*ItemStatCostRecord
func LoadItemStatCosts(file []byte) {
ItemStatCosts = make(map[string]*ItemStatCostRecord, 0)
d := d2common.LoadDataDictionary(string(file))
r := make([]*ItemStatCostRecord, 0)
for idx, _ := range d.Data {
record := &ItemStatCostRecord{
Name: d.GetString("Stat", idx),
Index: d.GetNumber("ID", idx),
Signed: d.GetNumber("Signed", idx) > 0,
KeepZero: d.GetNumber("keepzero", idx) > 0,
// Ranged: d.GetNumber("Ranged", idx) > 0,
MinAccr: d.GetNumber("MinAccr", idx),
UpdateAnimRate: d.GetNumber("UpdateAnimRate", idx) > 0,
SendOther: d.GetNumber("Send Other", idx) > 0,
SendBits: d.GetNumber("Send Bits", idx),
SendParam: d.GetNumber("Send Param Bits", idx),
Saved: d.GetNumber("CSvBits", idx) > 0,
SavedSigned: d.GetNumber("CSvSigned", idx) > 0,
SavedBits: d.GetNumber("CSvBits", idx),
SaveBits: d.GetNumber("Save Bits", idx),
SaveAdd: d.GetNumber("Save Add", idx),
SaveParamBits: d.GetNumber("Save Param Bits", idx),
Encode: EncodingType(d.GetNumber("Encode", idx)),
CallbackEnabled: d.GetNumber("fCallback", idx) > 0,
CostAdd: d.GetNumber("Add", idx),
CostMultiply: d.GetNumber("Multiply", idx),
ValShift: d.GetNumber("ValShift", idx),
OperatorType: OperatorType(d.GetNumber("op", idx)),
OpParam: d.GetNumber("op param", idx),
OpBase: d.GetString("op base", idx),
OpStat1: d.GetString("op stat1", idx),
OpStat2: d.GetString("op stat2", idx),
OpStat3: d.GetString("op stat3", idx),
Direct: d.GetNumber("direct", idx) > 0,
MaxStat: d.GetString("maxstat", idx),
ItemSpecific: d.GetNumber("itemspecific", idx) > 0,
DamageRelated: d.GetNumber("damagerelated", idx) > 0,
EventID1: d2enum.GetItemEventType(d.GetString("itemevent1", idx)),
EventID2: d2enum.GetItemEventType(d.GetString("itemevent2", idx)),
EventFuncID1: d2enum.GetItemEventFuncID(d.GetNumber("itemeventfunc1", idx)),
EventFuncID2: d2enum.GetItemEventFuncID(d.GetNumber("itemeventfunc2", idx)),
DescPriority: d.GetNumber("descpriority", idx),
DescFnID: d2enum.DescFuncID(d.GetNumber("descfunc", idx)),
DescFn: d2enum.GetDescFunction(d2enum.DescFuncID(d.GetNumber("descfunc", idx))),
DescVal: d.GetNumber("descval", idx),
DescStrPos: d.GetString("descstrpos", idx),
DescStrNeg: d.GetString("descstrneg", idx),
DescStr2: d.GetString("descstr2", idx),
DescGroup: d.GetNumber("dgrp", idx),
DescGroupFuncID: d2enum.DescFuncID(d.GetNumber("dgrpfunc", idx)),
DescGroupFn: d2enum.GetDescFunction(d2enum.DescFuncID(d.GetNumber("dgrpfunc", idx))),
DescGroupVal: d.GetNumber("dgrpval", idx),
DescGroupStrPos: d.GetString("dgrpstrpos", idx),
DescGroupStrNeg: d.GetString("dgrpstrneg", idx),
DescGroupStr2: d.GetString("dgrpstr2", idx),
Stuff: d.GetString("stuff", idx),
}
r = append(r, record)
}
log.Printf("Loaded %d ItemStatCost records", len(r))
}

View File

@ -0,0 +1,194 @@
package d2enum
import (
"fmt"
)
type DescFuncID int
func Format1(value float64, string1 string) string {
// +[value] [string1]
return fmt.Sprintf("+%f %s", value, string1)
}
func Format2(value float64, string1 string) string {
// [value]% [string1]
return fmt.Sprintf("%f%% %s", value, string1)
}
func Format3(value float64, string1 string) string {
// [value] [string1]
return fmt.Sprintf("%f %s", value, string1)
}
func Format4(value float64, string1 string) string {
// +[value]% [string1]
return fmt.Sprintf("+%f%% %s", value, string1)
}
func Format5(value float64, string1 string) string {
// [value*100/128]% [string1]
return fmt.Sprintf("%f%% %s", (value*100.0)/128.0, string1)
}
func Format6(value float64, string1, string2 string) string {
// +[value] [string1] [string2]
return fmt.Sprintf("+%f %s %s", value, string1, string2)
}
func Format7(value float64, string1, string2 string) string {
// [value]% [string1] [string2]
return fmt.Sprintf("%f%% %s %s", value, string1, string2)
}
func Format8(value float64, string1, string2 string) string {
// +[value]% [string1] [string2]
return fmt.Sprintf("+%f%% %s %s", value, string1, string2)
}
func Format9(value float64, string1, string2 string) string {
// [value] [string1] [string2]
return fmt.Sprintf("%f %s %s", value, string1, string2)
}
func Format10(value float64, string1, string2 string) string {
// [value*100/128]% [string1] [string2]
return fmt.Sprintf("%f%% %s %s", (value*100.0)/128.0, string1, string2)
}
func Format11(value float64) string {
// Repairs 1 Durability In [100 / value] Seconds
return fmt.Sprintf("Repairs 1 Durability In %.0f Seconds", 100.0/value)
}
func Format12(value float64, string1 string) string {
// +[value] [string1]
return fmt.Sprintf("+%f %s", value, string1)
}
func Format13(value float64, class string) string {
// +[value] to [class] Skill Levels
return fmt.Sprintf("+%.0f to %s Skill Levels", value, class)
}
func Format14(value float64, skilltab, class string) string {
// +[value] to [skilltab] Skill Levels ([class] Only)
fmtStr := "+%.0f to %s Skill Levels (%s Only)"
return fmt.Sprintf(fmtStr, value, skilltab, class)
}
func Format15(value float64, slvl int, skill, event string) string {
// [value]% chance to cast [slvl] [skill] on [event]
fmtStr := "%.0f%% chance to cast %d %s on %s"
return fmt.Sprintf(fmtStr, value, slvl, skill, event)
}
func Format16(slvl int, skill string) string {
// Level [sLvl] [skill] Aura When Equipped
return fmt.Sprintf("Level %d %s Aura When Equipped", slvl, skill)
}
func Format17(value float64, string1 string, time int) string {
// [value] [string1] (Increases near [time])
return fmt.Sprintf("%f %s (Increases near %d)", value, string1, time)
}
func Format18(value float64, string1 string, time int) string {
// [value]% [string1] (Increases near [time])
return fmt.Sprintf("%f%% %s (Increases near %d)", value, string1, time)
}
func Format19(value float64, string1 string) string {
// this is used by stats that use Blizzard's sprintf implementation
// (if you don't know what that is, it won't be of interest to you
// eitherway I guess), look at how prismatic is setup, the string is
// the format that gets passed to their sprintf spinoff.
return "" // TODO
}
func Format20(value float64, string1 string) string {
// [value * -1]% [string1]
return fmt.Sprintf("%f%% %s", value*-1.0, string1)
}
func Format21(value float64, string1 string) string {
// [value * -1] [string1]
return fmt.Sprintf("%f %s", value*-1.0, string1)
}
func Format22(value float64, string1, montype string) string {
// [value]% [string1] [montype]
return fmt.Sprintf("%f%% %s %s", value, string1, montype)
}
func Format23(value float64, string1 string) string {
// (warning: this is bugged in vanilla and doesn't work properly
// see CE forum)
return "" // TODO
}
func Format24(value float64, string1, monster string) string {
// [value]% [string1] [monster]
return fmt.Sprintf("%f%% %s %s", value, string1, monster)
}
func Format25(slvl float64, skill string, charges, maxCharges int) string {
// Level [slvl] [skill] ([charges]/[maxCharges] Charges)
fmtStr := "Level %.0f %s (%d/%d Charges)"
return fmt.Sprintf(fmtStr, slvl, skill, charges, maxCharges)
}
func Format26(value float64, string1 string) string {
// not used by vanilla, present in the code but I didn't test it yet
return "" // TODO
}
func Format27(value float64, string1 string) string {
// not used by vanilla, present in the code but I didn't test it yet
return "" // TODO
}
func Format28(value float64, skill, class string) string {
// +[value] to [skill] ([class] Only)
return fmt.Sprintf("+%f to %s (%s Only)", value, skill, class)
}
func Format29(value float64, skill string) string {
// +[value] to [skill]
return fmt.Sprintf("+%.0f to %s", value, skill)
}
func GetDescFunction(n DescFuncID) interface{} {
m := map[DescFuncID]interface{}{
DescFuncID(0): Format1,
DescFuncID(1): Format2,
DescFuncID(2): Format3,
DescFuncID(3): Format4,
DescFuncID(4): Format5,
DescFuncID(5): Format6,
DescFuncID(6): Format7,
DescFuncID(7): Format8,
DescFuncID(8): Format9,
DescFuncID(9): Format10,
DescFuncID(10): Format11,
DescFuncID(11): Format12,
DescFuncID(12): Format13,
DescFuncID(13): Format14,
DescFuncID(14): Format15,
DescFuncID(15): Format16,
DescFuncID(16): Format17,
DescFuncID(17): Format18,
DescFuncID(18): Format19,
DescFuncID(19): Format20,
DescFuncID(20): Format21,
DescFuncID(21): Format22,
DescFuncID(22): Format23,
DescFuncID(23): Format24,
DescFuncID(24): Format25,
DescFuncID(25): Format26,
DescFuncID(26): Format27,
DescFuncID(27): Format28,
DescFuncID(28): Format29,
}
return m[n]
}

View File

@ -0,0 +1,143 @@
package d2enum
type ItemEventFuncID int
const (
// shoots a missile at the owner of a missile that has just hit you
// (Chilling Armor uses this)
ReflectMissile = ItemEventFuncID(iota)
// freezes the attacker for a set duration the attacker
// (Frozen Armor uses this)
FreezeAttacker
// does cold damage to and chills the attacker (Shiver Armor uses this)
FreezeChillAttacker
// % of damage taken is done to the attacker
// (Iron Maiden, thorns uses a hardcoded stat)
ReflectPercentDamage
// % of damage done added to life, bypassing the targets resistance
// (used by Life Tap)
DamageDealtToHealth
// attacker takes physical damage of #
AttackerTakesPhysical
// knocks the target back
Knockback
// induces fear in the target making it run away
InduceFear
// applies Dim Vision to the target (it casts the actual curse on the
// monster)
BlindTarget
// attacker takes lightning damage of #
AttackerTakesLightning
// attacker takes fire damage of #
AttackerTakesFire
// attacker takes cold damage of #
AttackerTakesCold
// % damage taken is added to mana
DamageTakenToMana
// freezes the target
FreezeTarget
// causes the target to bleed and lose life (negative life regen)
OpenWounds
// crushing blow against the target
CrushingBlow
// mana after killing a monster
ManaOnKillMonster
// life after killing a demon
LifeOnKillDemon
// slows the target
SlowTarget
// casts a skill against the defender
CastSkillAgainstDefender
// casts a skill against the attacker
CastSkillAgainstAttacker
// absorbs physical damage taken (used by Bone Armor)
AbsorbPhysical
// transfers damage done from the summon to the owner (used by Blood Golem)
TakeSummonDamage
// used by Energy Shield to absorb damage and shift it from life to mana
ManaAbsorbsDamage
// absorbs elemental damage taken (used by Cyclone Armor)
AbsorbElementalDamage
// transfers damage taken from the summon to the owner (used by Blood Golem)
TakeSummonDamage2
// used to slow the attacker if he hits a unit that has the slow target stat
// (used by Clay Golem)
TargetSlowsTarget
// life after killing a monster
LifeOnKillMonster
// destroys the corpse of a killed monster (rest in peace effect)
RestInPeace
// cast a skill when the event occurs, without a target
CastSkillWithoutTarget
// reanimate the target as the specified monster
ReanimateTargetAsMonster
)
func GetItemEventFuncID(n int) ItemEventFuncID {
m := map[int]ItemEventFuncID{
0: ReflectMissile,
1: FreezeAttacker,
2: FreezeChillAttacker,
3: ReflectPercentDamage,
4: DamageDealtToHealth,
5: AttackerTakesPhysical,
6: Knockback,
7: InduceFear,
8: BlindTarget,
9: AttackerTakesLightning,
10: AttackerTakesFire,
11: AttackerTakesCold,
12: DamageTakenToMana,
13: FreezeTarget,
14: OpenWounds,
15: CrushingBlow,
16: ManaOnKillMonster,
17: LifeOnKillDemon,
18: SlowTarget,
19: CastSkillAgainstDefender,
20: CastSkillAgainstAttacker,
21: AbsorbPhysical,
22: TakeSummonDamage,
23: ManaAbsorbsDamage,
24: AbsorbElementalDamage,
25: TakeSummonDamage2,
26: TargetSlowsTarget,
27: LifeOnKillMonster,
28: RestInPeace,
29: CastSkillWithoutTarget,
30: ReanimateTargetAsMonster,
}
return m[n]
}
//? do i need to do this ? //go:generate stringer -linecomment -type AnimationMode

View File

@ -0,0 +1,39 @@
package d2enum
// used in ItemStatCost
type ItemEventType int
const (
HitByMissile = ItemEventType(iota) // hit By a Missile
DamagedInMelee // Damaged in Melee
DamagedByMissile // Damaged By Missile
AttackedInMelee // melee Attack atttempt
DoActive // do active state skill
DoMeleeDamage // do damage in melee
DoMissileDamage // do missile damage
DoMeleeAttack // do melee attack
DoMissileAttack // do missile attack
Kill // killed something
Killed // killed By something
AbsorbDamage // dealt damage
LevelUp // gain a level
)
func GetItemEventType(s string) ItemEventType {
strLookupTable := map[string]ItemEventType{
"HitByMissile": HitByMissile,
"DamagedInMelee": DamagedInMelee,
"DamagedByMissile": DamagedByMissile,
"AttackedInMelee": AttackedInMelee,
"DoActive": DoActive,
"DoMeleeDamage": DoMeleeDamage,
"DoMissileDamage": DoMissileDamage,
"DoMeleeAttack": DoMeleeAttack,
"DoMissileAttack": DoMissileAttack,
"Kill": Kill,
"Killed": Killed,
"AbsorbDamage": AbsorbDamage,
"LevelUp": LevelUp,
}
return strLookupTable[s]
}

View File

@ -174,6 +174,7 @@ const (
LevelDetails = "/data/global/excel/Levels.bin"
ObjectDetails = "/data/global/excel/Objects.txt"
SoundSettings = "/data/global/excel/Sounds.txt"
ItemStatCost = "/data/global/excel/ItemStatCost.txt"
// --- Animations ---

View File

@ -389,6 +389,8 @@ func loadDataDict() error {
{d2resource.MonStats, d2datadict.LoadMonStats},
{d2resource.MagicPrefix, d2datadict.LoadMagicPrefix},
{d2resource.MagicSuffix, d2datadict.LoadMagicSuffix},
{d2resource.ItemStatCost, d2datadict.LoadItemStatCosts},
}
for _, entry := range entries {