OpenDiablo2/d2core/d2item/diablo2item/item_property.go

374 lines
9.8 KiB
Go

package diablo2item
import (
"math/rand"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats"
)
const (
invalidHeroIndex = -1.0
)
const (
noValue = iota
oneValue
twoValue
threeValue
)
const (
skillTabsPerClass = 3
)
// these come from properties.txt, the types of functions that properties can use to evaluate args
const (
fnNone = iota
fnValuesToStat
fnArmorPercent
fnRepeatPreviousWithMinMax // repeat only with min and max
fnUnused
fnDamageMin
fnDamageMax
fnDamagePercent
fnSpeedRelated
fnRepeatPreviousWithParamMinMax // repeat with param, man, and max
fnClassSkillTab
fnProcs
fnRandomSkill
fnMaxDurability
fnNumSockets
fnStatMin
fnStatMax
fnStatParam
fnTimeRelated
fnChargeRelated
fnIndestructable
fnClassSkills
fnSingleSkill
fnEthereal
fnStateApplyToTarget
)
// PropertyType describes what kind of property this is
type PropertyType int
// Property types
// Not all properties contain stats, some are just used to compute a value
// examples are:
// min/max
// % damage
// indestructable and etheral flags
const (
PropertyComputeStats = iota // for properties that do compute stats
PropertyComputeInteger // for properties that compute an integer value
PropertyComputeBoolean // for properties that compute a boolean
)
const (
fnRandClassSkill = 36
)
// Property is an item property.
type Property struct {
factory *ItemFactory
record *d2records.PropertyRecord
stats []d2stats.Stat
PropertyType PropertyType
// the inputValues that were passed initially when calling `NewProperty`
inputParams []int
// some properties are statless and used only for computing a value
computedInt int
computedBool bool
}
func (p *Property) init() *Property {
p.stats = make([]d2stats.Stat, 0)
// some property functions need to be able to repeat last function
// this is for properties with multiple stats that want to repeat the same
// initialization step with the same min/max params
var lastFnCalled int
var stat d2stats.Stat
for idx := range p.record.Stats {
if p.record.Stats[idx] == nil {
continue
}
stat, lastFnCalled = p.eval(idx, lastFnCalled)
// some property stats don't actually have a stat
// but they have functions on the first stat entry
if stat != nil {
p.stats = append(p.stats, stat)
}
}
return p
}
// eval will attempt to create a stat, and will return the function id that was last run.
// this is because some of the properties have a func index which indicates that it should
// repeat the previous fn with the same parameters, but for a different stat.
func (p *Property) eval(propStatIdx, previousFnID int) (stat d2stats.Stat, funcID int) {
pStatRecord := p.record.Stats[propStatIdx]
iscRecord := p.factory.asset.Records.Item.Stats[pStatRecord.StatCode]
funcID = pStatRecord.FunctionID
switch funcID {
case fnRepeatPreviousWithMinMax, fnRepeatPreviousWithParamMinMax:
funcID = previousFnID
fallthrough
case fnValuesToStat, fnSpeedRelated, fnMaxDurability, fnNumSockets,
fnStatMin, fnStatMax, fnSingleSkill, fnArmorPercent:
p.PropertyType = PropertyComputeStats
stat = p.fnValuesToStat(iscRecord)
case fnDamageMin, fnDamageMax, fnDamagePercent:
p.PropertyType = PropertyComputeInteger
p.computedInt = p.fnComputeInteger()
case fnClassSkillTab:
p.PropertyType = PropertyComputeStats
stat = p.fnClassSkillTab(iscRecord)
case fnProcs:
p.PropertyType = PropertyComputeStats
stat = p.fnProcs(iscRecord)
case fnRandomSkill:
p.PropertyType = PropertyComputeStats
stat = p.fnRandomSkill(iscRecord)
case fnStatParam:
p.PropertyType = PropertyComputeStats
stat = p.fnStatParam(iscRecord)
case fnChargeRelated:
p.PropertyType = PropertyComputeStats
stat = p.fnChargeRelated(iscRecord)
case fnIndestructable, fnEthereal:
p.PropertyType = PropertyComputeBoolean
p.computedBool = p.fnBoolean()
case fnClassSkills:
p.PropertyType = PropertyComputeStats
stat = p.fnClassSkills(pStatRecord, iscRecord)
case fnStateApplyToTarget:
p.PropertyType = PropertyComputeStats
stat = p.fnStateApplyToTarget(iscRecord)
case fnRandClassSkill:
p.PropertyType = PropertyComputeStats
stat = p.fnRandClassSkill(iscRecord)
case fnNone, fnUnused, fnTimeRelated:
default:
}
return stat, funcID
}
// fnValuesToStat Applies a value to a stat, can use SetX parameter.
func (p *Property) fnValuesToStat(iscRecord *d2records.ItemStatCostRecord) d2stats.Stat {
// the only special case to handle for this function is for
// property "color", which corresponds to ISC record "item_lightcolor"
// I'm not yet sure how to handle this special case... it is likely
// and index into one of the colors in colors.txt
var min, max int
var propParam, statValue float64
switch len(p.inputParams) {
case noValue, oneValue:
return nil
case twoValue:
min, max = p.inputParams[0], p.inputParams[1]
case threeValue:
propParam = float64(p.inputParams[0])
min, max = p.inputParams[1], p.inputParams[2]
default:
min, max = p.inputParams[0], p.inputParams[1]
}
if max < min {
min, max = max, min
}
// nolint:gosec // not concerned with crypto-strong randomness
statValue = float64(rand.Intn(max-min+1) + min)
return p.factory.stat.NewStat(iscRecord.Name, statValue, propParam)
}
// fnComputeInteger Dmg-min related ???
func (p *Property) fnComputeInteger() int {
var min, max int
switch len(p.inputParams) {
case noValue, oneValue:
return 0
default:
min, max = p.inputParams[0], p.inputParams[1]
}
// nolint:gosec // not concerned with crypto-strong randomness
statValue := rand.Intn(max-min+1) + min
return statValue
}
// fnClassSkillTab skilltab skill group ???
func (p *Property) fnClassSkillTab(iscRecord *d2records.ItemStatCostRecord) d2stats.Stat {
// from here: https://d2mods.info/forum/kb/viewarticle?a=45
// Amazon
// 0 - Bow & Crossbow
// 1 - Passive & Magic
// 2 - Spear & Javelin
// Sorceress
// 3 - Fire
// 4 - Lightning
// 5 - Cold
// Necromancer
// 6 - Curses
// 7 - Poison & Bone
// 8 - Summoning
// Paladin
// 9 - Offensive Auras
// 10 - Combat Skills
// 11 - Defensive Auras
// Barbarian
// 12 - Masteries
// 13 - Combat Skills
// 14 - Warcries
// Druid
// 15 - Summoning
// 16 - Shapeshifting
// 17 - Elemental
// Assassin
// 18 - Traps
// 19 - Shadow Disciplines
// 20 - Martial Arts
param, min, max := p.inputParams[0], p.inputParams[1], p.inputParams[2]
skillTabIdx := float64(param % skillTabsPerClass)
classIdx := float64(param / skillTabsPerClass)
// nolint:gosec // not concerned with crypto-strong randomness
level := float64(rand.Intn(max-min+1) + min)
return p.factory.stat.NewStat(iscRecord.Name, level, classIdx, skillTabIdx)
}
// fnProcs event-based skills ???
func (p *Property) fnProcs(iscRecord *d2records.ItemStatCostRecord) d2stats.Stat {
var skillID, chance, skillLevel float64
switch len(p.inputParams) {
case noValue, oneValue, twoValue:
return nil
default:
skillID = float64(p.inputParams[0])
chance = float64(p.inputParams[1])
skillLevel = float64(p.inputParams[2])
}
return p.factory.stat.NewStat(iscRecord.Name, chance, skillLevel, skillID)
}
// fnRandomSkill random selection of parameters for parameter-based stat ???
func (p *Property) fnRandomSkill(iscRecord *d2records.ItemStatCostRecord) d2stats.Stat {
var skillLevel, skillID float64
switch len(p.inputParams) {
case noValue, oneValue, twoValue:
return nil
default:
skillLevel = float64(p.inputParams[0])
min, max := p.inputParams[1], p.inputParams[2]
// nolint:gosec // not concerned with crypto-strong randomness
skillID = float64(rand.Intn(max-min+1) + min)
}
return p.factory.stat.NewStat(iscRecord.Name, skillLevel, skillID, invalidHeroIndex)
}
// fnStatParam use param field only
func (p *Property) fnStatParam(iscRecord *d2records.ItemStatCostRecord) d2stats.Stat {
switch len(p.inputParams) {
case noValue:
return nil
default:
val := float64(p.inputParams[0])
return p.factory.stat.NewStat(iscRecord.Name, val)
}
}
// fnChargeRelated Related to charged item.
func (p *Property) fnChargeRelated(iscRecord *d2records.ItemStatCostRecord) d2stats.Stat {
var lvl, skill, charges float64
switch len(p.inputParams) {
case noValue, oneValue, twoValue:
return nil
default:
lvl = float64(p.inputParams[2])
skill = float64(p.inputParams[0])
charges = float64(p.inputParams[1])
return p.factory.stat.NewStat(iscRecord.Name, lvl, skill, charges, charges)
}
}
// fnIndestructable Simple boolean stuff. Use by indestruct.
func (p *Property) fnBoolean() bool {
var min, max int
switch len(p.inputParams) {
case noValue, oneValue:
return false
default:
min, max = p.inputParams[0], p.inputParams[1]
}
// nolint:gosec // not concerned with crypto-strong randomness
statValue := rand.Intn(max-min+1) + min
return statValue > 0
}
// fnClassSkills Add to group of skills, group determined by stat ID, uses ValX parameter.
func (p *Property) fnClassSkills(
propStatRecord *d2records.PropertyStatRecord,
iscRecord *d2records.ItemStatCostRecord,
) d2stats.Stat {
// in order 0..6
// Amazon
// Sorceress
// Necromancer
// Paladin
// Druid
// Assassin
var min, max, classIdx int
switch len(p.inputParams) {
case noValue, oneValue:
return nil
default:
min, max = p.inputParams[0], p.inputParams[1]
}
// nolint:gosec // not concerned with crypto-strong randomness
statValue := rand.Intn(max-min+1) + min
classIdx = propStatRecord.Value
return p.factory.stat.NewStat(iscRecord.Name, float64(statValue), float64(classIdx))
}
// fnStateApplyToTarget property applied to character or target monster ???
func (p *Property) fnStateApplyToTarget(_ *d2records.ItemStatCostRecord) d2stats.Stat {
// https://github.com/OpenDiablo2/OpenDiablo2/issues/818
return nil
}
// fnRandClassSkill property applied to character or target monster ???
func (p *Property) fnRandClassSkill(_ *d2records.ItemStatCostRecord) d2stats.Stat {
return nil
}