mirror of
https://github.com/araxiaonline/wow-item-generator.git
synced 2026-06-13 03:02:22 -04:00
743 lines
21 KiB
Go
743 lines
21 KiB
Go
package items
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"math"
|
|
"math/rand/v2"
|
|
"reflect"
|
|
"slices"
|
|
)
|
|
|
|
/**
|
|
* For details about values of item int values use link below
|
|
* @link https://www.azerothcore.org/wiki/item_template
|
|
*/
|
|
type Item struct {
|
|
Entry int
|
|
Name string
|
|
DisplayId int `db:"displayid"`
|
|
Quality *int
|
|
ItemLevel *int `db:"ItemLevel"`
|
|
Class *int
|
|
Subclass *int
|
|
Armor *int `db:"armor"`
|
|
Material *int `db:"material"`
|
|
InventoryType *int `db:"inventoryType"`
|
|
AllowableClass *int `db:"allowableClass"`
|
|
AllowableRace *int `db:"allowableRace"`
|
|
RequiredSkill *int `db:"requiredSkill"`
|
|
RequiredLevel *int `db:"requiredLevel"`
|
|
Durability *int `db:"MaxDurability"`
|
|
MinDmg1 *int `db:"dmg_min1"`
|
|
MaxDmg1 *int `db:"dmg_max1"`
|
|
MinDmg2 *int `db:"dmg_min2"`
|
|
MaxDmg2 *int `db:"dmg_max2"`
|
|
DmgType1 *int `db:"dmg_type1"`
|
|
DmgType2 *int `db:"dmg_type2"`
|
|
Delay *float64
|
|
Sheath *int
|
|
StatsCount *int `db:"statsCount"`
|
|
StatType1 *int `db:"stat_type1"`
|
|
StatValue1 *int `db:"stat_value1"`
|
|
StatType2 *int `db:"stat_type2"`
|
|
StatValue2 *int `db:"stat_value2"`
|
|
StatType3 *int `db:"stat_type3"`
|
|
StatValue3 *int `db:"stat_value3"`
|
|
StatType4 *int `db:"stat_type4"`
|
|
StatValue4 *int `db:"stat_value4"`
|
|
StatType5 *int `db:"stat_type5"`
|
|
StatValue5 *int `db:"stat_value5"`
|
|
StatType6 *int `db:"stat_type6"`
|
|
StatValue6 *int `db:"stat_value6"`
|
|
StatType7 *int `db:"stat_type7"`
|
|
StatValue7 *int `db:"stat_value7"`
|
|
StatType8 *int `db:"stat_type8"`
|
|
StatValue8 *int `db:"stat_value8"`
|
|
StatType9 *int `db:"stat_type9"`
|
|
StatValue9 *int `db:"stat_value9"`
|
|
StatType10 *int `db:"stat_type10"`
|
|
StatValue10 *int `db:"stat_value10"`
|
|
SpellId1 *int `db:"spellid_1"`
|
|
SpellId2 *int `db:"spellid_2"`
|
|
SpellId3 *int `db:"spellid_3"`
|
|
SpellTrigger1 *int `db:"spelltrigger_1"`
|
|
SpellTrigger2 *int `db:"spelltrigger_2"`
|
|
SpellTrigger3 *int `db:"spelltrigger_3"`
|
|
StatsMap map[int]*ItemStat
|
|
ConvStatCount int
|
|
Spells []Spell
|
|
}
|
|
|
|
// Use for storing item stats for all stats that will be scaled.
|
|
type ItemStat struct {
|
|
Value int
|
|
Percent float64
|
|
Type string
|
|
AdjValue float64
|
|
}
|
|
|
|
var InvTypeModifiers = map[int]float64{
|
|
1: 0.813, // Head
|
|
2: 1.0, // Neck
|
|
3: 0.75, // Shoulder
|
|
5: 1.0, // Chest
|
|
6: 0.562, // Waist
|
|
7: 0.875, // Legs
|
|
8: 0.688, // Feet
|
|
9: 0.437, // Wrists
|
|
10: 0.625, // Hands
|
|
11: 1.0, // Finger
|
|
13: 0.42, // One-Hand (not to confuse with Off-Hand = 22)
|
|
14: 0.56, // Shield (class = armor, not weapon even if in weapon slot)
|
|
15: 0.32, // Ranged (Bows) (see also Ranged right = 26)
|
|
16: 0.56, // Back
|
|
17: 1.0, // Two-Hand
|
|
18: 1.0, // Bag (assuming same as Chest for simplicity)
|
|
19: 1.0, // Tabard (assuming same as Chest for simplicity)
|
|
20: 1.0, // Robe (see also Chest = 5)
|
|
21: 1.0, // Main hand
|
|
22: 0.42, // Off Hand weapons (see also One-Hand = 13)
|
|
23: 0.56, // Held in Off-Hand (class = armor, not weapon even if in weapon slot)
|
|
24: 1.0, // Ammo (assuming same as Chest for simplicity)
|
|
25: 0.32, // Thrown
|
|
26: 0.32, // Ranged right (Wands, Guns) (see also Ranged = 15)
|
|
27: 1.0, // Quiver (assuming same as Chest for simplicity)
|
|
}
|
|
|
|
var QualityModifiers = map[int]float64{
|
|
0: 1.0, // Common
|
|
1: 1.1, // Uncommon
|
|
2: 1.2, // Rare
|
|
3: 1.3, // Epic
|
|
4: 1.5, // Legendary
|
|
5: 1.7, // Artifact
|
|
}
|
|
|
|
var MaterialModifiers = map[int]float64{
|
|
1: 1.2, // Cloth
|
|
2: 2.2, // Leather
|
|
3: 4.75, // Mail
|
|
4: 9.0, // Plate
|
|
6: 20.0, // Plate
|
|
}
|
|
|
|
var StatModifiers = map[int]float64{
|
|
0: 1.0, // ITEM_MOD_MANA
|
|
1: 1.0, // ITEM_MOD_HEALTH
|
|
3: 1.0, // ITEM_MOD_AGILITY
|
|
4: 1.0, // ITEM_MOD_STRENGTH
|
|
5: 1.0, // ITEM_MOD_INTELLECT
|
|
6: 1.0, // ITEM_MOD_SPIRIT
|
|
7: 1.0, // ITEM_MOD_STAMINA
|
|
12: 1.0, // ITEM_MOD_DEFENSE_SKILL_RATING
|
|
13: 1.0, // ITEM_MOD_DODGE_RATING
|
|
14: 1.0, // ITEM_MOD_PARRY_RATING
|
|
15: 1.0, // ITEM_MOD_BLOCK_RATING
|
|
16: 1.0, // ITEM_MOD_HIT_MELEE_RATING
|
|
17: 1.0, // ITEM_MOD_HIT_RANGED_RATING
|
|
18: 1.0, // ITEM_MOD_HIT_SPELL_RATING
|
|
19: 1.0, // ITEM_MOD_CRIT_MELEE_RATING
|
|
20: 1.0, // ITEM_MOD_CRIT_RANGED_RATING
|
|
21: 1.0, // ITEM_MOD_CRIT_SPELL_RATING
|
|
22: 1.0, // ITEM_MOD_HIT_TAKEN_MELEE_RATING
|
|
23: 1.0, // ITEM_MOD_HIT_TAKEN_RANGED_RATING
|
|
24: 1.0, // ITEM_MOD_HIT_TAKEN_SPELL_RATING
|
|
25: 1.0, // ITEM_MOD_CRIT_TAKEN_MELEE_RATING
|
|
26: 1.0, // ITEM_MOD_CRIT_TAKEN_RANGED_RATING
|
|
27: 1.0, // ITEM_MOD_CRIT_TAKEN_SPELL_RATING
|
|
28: 1.0, // ITEM_MOD_HASTE_MELEE_RATING
|
|
29: 1.0, // ITEM_MOD_HASTE_RANGED_RATING
|
|
30: 1.0, // ITEM_MOD_HASTE_SPELL_RATING
|
|
31: 1.0, // ITEM_MOD_HIT_RATING
|
|
32: 1.0, // ITEM_MOD_CRIT_RATING
|
|
33: 1.0, // ITEM_MOD_HIT_TAKEN_RATING
|
|
34: 1.0, // ITEM_MOD_CRIT_TAKEN_RATING
|
|
35: 1.0, // ITEM_MOD_RESILIENCE_RATING
|
|
36: 1.0, // ITEM_MOD_HASTE_RATING
|
|
37: 1.0, // ITEM_MOD_EXPERTISE_RATING
|
|
38: 0.5, // ITEM_MOD_ATTACK_POWER
|
|
39: 0.5, // ITEM_MOD_RANGED_ATTACK_POWER
|
|
40: 0.5, // ITEM_MOD_FERAL_ATTACK_POWER (not used as of 3.3)
|
|
41: 0.5, // ITEM_MOD_SPELL_HEALING_DONE
|
|
42: 0.5, // ITEM_MOD_SPELL_DAMAGE_DONE
|
|
43: 2.5, // ITEM_MOD_MANA_REGENERATION
|
|
44: 1.0, // ITEM_MOD_ARMOR_PENETRATION_RATING
|
|
45: 0.5, // ITEM_MOD_SPELL_POWER
|
|
46: 1.0, // ITEM_MOD_HEALTH_REGEN
|
|
47: 2.0, // ITEM_MOD_SPELL_PENETRATION
|
|
48: 0.65, // ITEM_MOD_BLOCK_VALUE
|
|
}
|
|
|
|
// Get the primary stat for an item (strength, agility, intellect, spirit, stamina)
|
|
func (item Item) GetPrimaryStat() (int, int, error) {
|
|
var primaryStat int64
|
|
var primaryVal int64
|
|
|
|
for i := 1; i < 11; i++ {
|
|
|
|
statType, err := item.GetField(fmt.Sprintf("StatType%v", i))
|
|
if err != nil {
|
|
log.Printf("Failed to get stat type %v for item: %v", i, item.Name)
|
|
continue
|
|
}
|
|
if statType < 3 || statType > 7 {
|
|
continue
|
|
}
|
|
|
|
val, err := item.GetField(fmt.Sprintf("StatValue%v", i))
|
|
|
|
if err != nil {
|
|
log.Printf("Failed to get stat value %v for item: %v", i, item.Name)
|
|
continue
|
|
}
|
|
if val == 0 {
|
|
continue
|
|
}
|
|
|
|
if int64(val) > primaryVal {
|
|
primaryVal = int64(val)
|
|
primaryStat = int64(statType)
|
|
}
|
|
}
|
|
|
|
return int(primaryStat), int(primaryVal), nil
|
|
}
|
|
|
|
/**
|
|
* Get the statIds for anitem as a slice of integers
|
|
* @return []int
|
|
*/
|
|
func (item Item) GetStatList() ([]int, error) {
|
|
|
|
statList := []int{}
|
|
for i := 1; i < 11; i++ {
|
|
val, err := item.GetField(fmt.Sprintf("StatValue%v", i))
|
|
|
|
if err != nil {
|
|
log.Printf("Failed to get stat value %v for item: %v", i, item.Name)
|
|
continue
|
|
}
|
|
if val == 0 {
|
|
continue
|
|
}
|
|
|
|
statId, err := item.GetField(fmt.Sprintf("StatType%v", i))
|
|
if err != nil {
|
|
log.Printf("Failed to get stat type %v for item: %s", i, item.Name)
|
|
continue
|
|
}
|
|
statList = append(statList, statId)
|
|
slices.Sort(statList)
|
|
|
|
}
|
|
return statList, nil
|
|
}
|
|
|
|
func (i Item) GetDpsModifier() (float64, error) {
|
|
if i.Subclass == nil {
|
|
return 0, fmt.Errorf("subclass on the item is not set")
|
|
}
|
|
|
|
if i.Quality == nil {
|
|
return 0, fmt.Errorf("quality is not set")
|
|
}
|
|
|
|
typeModifier := 0.0
|
|
// Is a One-Handed Weapon
|
|
if *i.Subclass == 0 || *i.Subclass == 4 || *i.Subclass == 13 || *i.Subclass == 15 || *i.Subclass == 7 {
|
|
typeModifier = 0.64
|
|
}
|
|
|
|
// Is a Two-Handed Weapon
|
|
if *i.Subclass == 1 || *i.Subclass == 5 || *i.Subclass == 6 || *i.Subclass == 8 || *i.Subclass == 10 || *i.Subclass == 17 {
|
|
typeModifier = 0.80
|
|
}
|
|
|
|
// Ranged Weapons
|
|
if *i.Subclass == 2 || *i.Subclass == 3 || *i.Subclass == 16 || *i.Subclass == 18 {
|
|
typeModifier = 0.70
|
|
}
|
|
|
|
// Wands
|
|
if *i.Subclass == 19 {
|
|
typeModifier = 0.70
|
|
}
|
|
|
|
qualityModifier := 1.0
|
|
|
|
// Add the quality modifier for the DPS calculation
|
|
if *i.Quality == 2 {
|
|
qualityModifier = 1.25
|
|
}
|
|
if *i.Quality == 3 {
|
|
qualityModifier = 1.38
|
|
}
|
|
if *i.Quality == 4 {
|
|
qualityModifier = 1.5
|
|
}
|
|
|
|
if typeModifier == 0 {
|
|
return 0, fmt.Errorf("Item subclass is not a weapon %v", *i.Subclass)
|
|
}
|
|
|
|
return (qualityModifier * typeModifier), nil
|
|
}
|
|
|
|
// Get the current expected DPS of the item bsed on the min and max damage and delay
|
|
func (item Item) GetDPS() (float64, error) {
|
|
|
|
if item.MinDmg1 == nil || item.MaxDmg1 == nil {
|
|
return 0, fmt.Errorf("MinDmg1 or MaxDmg1 is not set")
|
|
}
|
|
|
|
if item.Delay == nil {
|
|
return 0, fmt.Errorf("delay is not set")
|
|
}
|
|
|
|
dps := math.Round((float64(*item.MinDmg1+*item.MaxDmg1)/2.0)/float64(*item.Delay/1000)*100) / 100
|
|
return dps, nil
|
|
}
|
|
|
|
// Scales and items dps damage numbers based on a desired item level.
|
|
func (item *Item) ScaleDPS(level int) (float64, error) {
|
|
if item.ItemLevel == nil {
|
|
return 0, fmt.Errorf("ItemLevel is not set")
|
|
}
|
|
|
|
if item.Delay == nil {
|
|
return 0, fmt.Errorf("delay is not set")
|
|
}
|
|
|
|
modifier, err := item.GetDpsModifier()
|
|
if err != nil {
|
|
log.Fatalf("Error getting DPS modifier: %v", err)
|
|
return 0.0, err
|
|
}
|
|
|
|
dps := modifier * float64(level)
|
|
adjDps := (dps * (*item.Delay / 1000) / 100)
|
|
|
|
//(((Y8*Y4)/100))*((100 - Y5)) Forumula from Weapon Item Genertor
|
|
minimum := adjDps * float64(100-(rand.IntN(15)+22))
|
|
maximum := adjDps * float64(100+(rand.IntN(15)+22))
|
|
|
|
// If the weapon has secondary damage, scale that as well based on the ratio of the primary damage
|
|
if item.MinDmg2 != nil && item.MaxDmg2 != nil {
|
|
ratioMin := float64(*item.MinDmg2) / float64(*item.MinDmg1)
|
|
ratioMax := float64(*item.MaxDmg2) / float64(*item.MaxDmg1)
|
|
minimum2 := int(ratioMin * float64(minimum))
|
|
maximum2 := int(ratioMax * float64(maximum))
|
|
|
|
item.MinDmg2 = &minimum2
|
|
item.MaxDmg2 = &maximum2
|
|
|
|
// In order to balance the original scale of the secondary damage from primary
|
|
minimum = minimum - float64(minimum2)*0.75
|
|
maximum = maximum - float64(maximum2)*0.75
|
|
|
|
}
|
|
|
|
// item.MinDmg1 = &minimum
|
|
var min int = int(minimum)
|
|
var max int = int(maximum)
|
|
item.MinDmg1 = &min
|
|
item.MaxDmg1 = &max
|
|
|
|
return dps, nil
|
|
}
|
|
|
|
func (d Database) GetItem(entry int) (Item, error) {
|
|
if entry == 0 {
|
|
return Item{}, fmt.Errorf("entry cannot be 0")
|
|
}
|
|
|
|
item := Item{}
|
|
sql := "SELECT " + utils.GetItemFields("") + " FROM item_template WHERE entry = ?"
|
|
err := d.client.Get(&item, sql, entry)
|
|
if err != nil {
|
|
return Item{}, err
|
|
}
|
|
|
|
return item, nil
|
|
}
|
|
|
|
// Create a Map of stat percentages based on the current stat and how budgets are caluated
|
|
func (item Item) GetStatPercents(spellStats []ConvItemStat) map[int]*ItemStat {
|
|
|
|
statMap := make(map[int]*ItemStat)
|
|
statBudget := 0.0
|
|
|
|
values := reflect.ValueOf(item)
|
|
for i := 1; i < 11; i++ {
|
|
var statValue = values.FieldByName(fmt.Sprintf("StatValue%v", i)).Elem().Int()
|
|
var statType = values.FieldByName(fmt.Sprintf("StatType%v", i)).Elem().Int()
|
|
if statValue == 0 {
|
|
continue
|
|
}
|
|
|
|
adjValue := float64(statValue) / StatModifiers[int(statType)]
|
|
statBudget += adjValue
|
|
statMap[int(statType)] = &ItemStat{
|
|
Value: int(statValue),
|
|
Percent: 0.0,
|
|
Type: "Item",
|
|
AdjValue: adjValue,
|
|
}
|
|
}
|
|
|
|
// Calculate the total budget for the spell stats if we have some
|
|
for _, spellStat := range spellStats {
|
|
statBudget += float64(spellStat.Budget)
|
|
statMap[spellStat.StatType] = &ItemStat{
|
|
Value: spellStat.StatValue,
|
|
Percent: 0.0,
|
|
Type: "Spell",
|
|
AdjValue: float64(spellStat.Budget),
|
|
}
|
|
}
|
|
|
|
// Combine all stats and calculate percentages for each stat
|
|
for statId, stat := range statMap {
|
|
statMap[statId].Percent = math.Round(float64(stat.AdjValue)/statBudget*100) / 100
|
|
}
|
|
|
|
return statMap
|
|
}
|
|
|
|
// get an array of all the spells set on the item
|
|
func (item *Item) GetSpells() ([]Spell, error) {
|
|
// dont reload for the same item .
|
|
if len(item.Spells) > 0 {
|
|
return item.Spells, nil
|
|
}
|
|
|
|
spells := []Spell{}
|
|
values := reflect.ValueOf(item)
|
|
for i := 1; i < 4; i++ {
|
|
spellId := values.Elem().FieldByName(fmt.Sprintf("SpellId%v", i)).Elem().Int()
|
|
if spellId == 0 {
|
|
continue
|
|
}
|
|
|
|
if spellId == -1 {
|
|
continue
|
|
}
|
|
|
|
spell, err := DB.GetSpell(int(spellId))
|
|
if err != nil {
|
|
log.Printf("failed to get the spell: %v error: %v", spellId, err)
|
|
continue
|
|
}
|
|
|
|
spells = append(spells, spell)
|
|
}
|
|
item.Spells = spells
|
|
return spells, nil
|
|
}
|
|
|
|
func (item *Item) GetNonStatSpells() ([]Spell, error) {
|
|
nonStatSpells := []Spell{}
|
|
for i := 1; i < 4; i++ {
|
|
spellId, err := item.GetField(fmt.Sprintf("SpellId%v", i))
|
|
|
|
if err != nil {
|
|
log.Printf("Failed to get spell id %v", i)
|
|
continue
|
|
}
|
|
|
|
if spellId == 0 {
|
|
continue
|
|
}
|
|
spell, err := DB.GetSpell(spellId)
|
|
if err != nil {
|
|
log.Printf("Failed to get spell %v", spellId)
|
|
continue
|
|
}
|
|
|
|
// Need to handle extended spell casts basically when a spell casts another spell and the base points are there
|
|
// instead of with the item itself.
|
|
// Can just create a new spell with base points, type and remove triggerspell and see what happens?
|
|
// For now just skip anything not in our list.
|
|
if spell.EffectAura1 == 42 || spell.EffectAura2 == 42 || spell.EffectAura3 == 42 {
|
|
continue
|
|
}
|
|
|
|
spell.ItemSpellSlot = i
|
|
nonStatSpells = append(nonStatSpells, spell)
|
|
}
|
|
return nonStatSpells, nil
|
|
}
|
|
|
|
// Stat Formula scaler
|
|
// Ceiling of ((ItemLevel * QualityModifier * ItemTypeModifier)^1.7095 * %ofStats) ^ (1/1.7095)) / StatModifier
|
|
// i.e) Green Strength Helmet (((100 * 1.1 * 1.0)^1.705) * 1)^(1/1.7095) / 1.0 = 110 Strength on item
|
|
func (item *Item) ScaleItem(itemLevel int, itemQuality int) (bool, error) {
|
|
var allSpellStats []ConvItemStat
|
|
if item.ItemLevel == nil {
|
|
return false, errors.New("field itemLevel is not set")
|
|
}
|
|
|
|
if item.Quality == nil {
|
|
return false, errors.New("field quality is not set")
|
|
}
|
|
|
|
fromItemLevel := *item.ItemLevel
|
|
*item.ItemLevel = itemLevel
|
|
|
|
// if an item quality is being forced than use it intead
|
|
if itemQuality != 0 {
|
|
*item.Quality = itemQuality
|
|
}
|
|
|
|
log.Printf("Scaling item %v %v to item level %v and quality %v", item.Name, item.Entry, itemLevel, *item.Quality)
|
|
|
|
// Get all the spell Stats on the item we can convert
|
|
spells, err := item.GetSpells()
|
|
if err != nil {
|
|
log.Printf("Failed to get spells for item: %v", err)
|
|
return false, err
|
|
}
|
|
|
|
for i := 0; i < len(spells); i++ {
|
|
|
|
log.Printf("Spell %v (%v) Effect %v AuraEffect %v Spell Desc: %v basePoints %v", spells[i].Name, spells[i].ID, spells[i].Effect1, spells[i].EffectAura1, spells[i].Description, spells[i].EffectBasePoints1)
|
|
|
|
convStats, err := spells[i].ConvertToStats()
|
|
if err != nil {
|
|
log.Printf("Failed to convert spell to stats: %v for spell %v", err, spells[i].Name)
|
|
continue
|
|
}
|
|
|
|
if len(convStats) != 0 {
|
|
item.UpdateField(fmt.Sprintf("SpellId%v", i+1), 0)
|
|
}
|
|
|
|
allSpellStats = append(allSpellStats, convStats...)
|
|
}
|
|
|
|
allStats := item.GetStatPercents(allSpellStats)
|
|
for statId, stat := range allStats {
|
|
origValue := stat.Value
|
|
|
|
stat.Value = scaleStat(itemLevel, *item.InventoryType, *item.Quality, stat.Percent, StatModifiers[statId])
|
|
|
|
log.Printf(">>>>>> Scaled : StatId: %v Type: %s Orig: %v - New Value: %v Percent: %v", statId, stat.Type, origValue, stat.Value, stat.Percent)
|
|
}
|
|
|
|
item.addStats(allStats)
|
|
*item.StatsCount = len(allStats)
|
|
|
|
// Scale Armor Stats
|
|
if *item.Class == 4 && *item.Armor > 0 {
|
|
preArmor := *item.Armor
|
|
*item.Armor = int(math.Ceil(float64(itemLevel) * QualityModifiers[*item.Quality] * MaterialModifiers[*item.Subclass]))
|
|
|
|
log.Printf("New Armor: %v scaled up from previous armor %v material is %v", *item.Armor, preArmor, *item.Material)
|
|
}
|
|
|
|
// If the item is a weapon scale the DPS
|
|
if *item.Class == 2 && *item.MinDmg1 > 0 {
|
|
predps, err := item.GetDPS()
|
|
if err != nil {
|
|
log.Printf("Failed to get DPS: %v", err)
|
|
}
|
|
|
|
dps, err := item.ScaleDPS(itemLevel)
|
|
if err != nil {
|
|
log.Printf("Failed to scale DPS: %v", err)
|
|
return false, err
|
|
}
|
|
log.Printf("DPS: %.1f scaled up from previous dps %v", dps, predps)
|
|
}
|
|
|
|
item.cleanSpells()
|
|
|
|
// Item is scaled now we have to determine if there are additional spell effects that need scaled.
|
|
// this will be as simple as possible as the effects will just be a percentage of the item stats.
|
|
// This could lead to some OP weapons that will need tuned down later. But for now, we will just scale at a
|
|
// An example of this might on hit do $s1 nature damage over $d seconds. We would just scale the $s1 value
|
|
// based on the formula below. This assumes that Blizzard has already balanced the spell bonus against the
|
|
// stats on the item level and quality. This is a big assumption as the stats are not penalized
|
|
// from having the extra damage. This could really create some unique sought after weapons that exploit this.
|
|
// modified ratio ((s1 / existing iLevel) * newIlevel) * (0.20 Rare or 0.30 Epic or 0.4 for Legendary).
|
|
|
|
otherSpells, err := item.GetNonStatSpells()
|
|
if err != nil {
|
|
log.Printf("failed to get non stat spells: %v", err)
|
|
}
|
|
|
|
log.Printf("\n\n\n -------------------- COUNT OF other spells %v \n\n", len(otherSpells))
|
|
|
|
item.Spells = []Spell{}
|
|
// Spells that can not be scaled into stats must get new spells scaled and created
|
|
for _, spell := range otherSpells {
|
|
log.Printf(" --^^^^^^--------SPELL --- Spell %v (%v) Effect %v AuraEffect %v Spell Desc: %v basePoints %v", spell.Name, spell.ID, spell.Effect1, spell.EffectAura1, spell.Description, spell.EffectBasePoints1)
|
|
newId, err := spell.ScaleSpell(fromItemLevel, itemLevel, *item.Quality)
|
|
if err != nil {
|
|
log.Printf("Failed to scale spell: %v, Spell %v", err, spell.ID)
|
|
continue
|
|
}
|
|
|
|
if newId == 0 {
|
|
log.Printf("Failed to scale spell: %v, Spell %v", err, spell.ID)
|
|
continue
|
|
}
|
|
|
|
item.UpdateField(fmt.Sprintf("SpellId%v", spell.ItemSpellSlot), newId)
|
|
item.Spells = append(item.Spells, spell)
|
|
|
|
log.Printf(" --SCALED---SPELL --- Spell %v (%v) Effect %v AuraEffect %v Spell Desc: %v basePoints %v", spell.Name, spell.ID, spell.Effect1, spell.EffectAura1, spell.Description, spell.EffectBasePoints1)
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
func (item *Item) GetField(fieldName string) (int, error) {
|
|
itemValue := reflect.ValueOf(item).Elem()
|
|
field := itemValue.FieldByName(fieldName)
|
|
if !field.IsValid() {
|
|
return 0, fmt.Errorf("failed to find field %s", fieldName)
|
|
}
|
|
|
|
switch field.Kind() {
|
|
case reflect.Ptr:
|
|
if field.IsNil() {
|
|
return 0, fmt.Errorf("field %s is nil", fieldName)
|
|
}
|
|
return int(field.Elem().Int()), nil
|
|
default:
|
|
return 0, fmt.Errorf("field %s is not a pointer", fieldName)
|
|
}
|
|
}
|
|
|
|
// Updates a dynamic field on the item struct useful for stat replacements or spells
|
|
func (item *Item) UpdateField(fieldName string, value int) {
|
|
itemValue := reflect.ValueOf(item).Elem()
|
|
field := itemValue.FieldByName(fieldName)
|
|
if !field.IsValid() {
|
|
log.Printf("failed to find field %s", fieldName)
|
|
return
|
|
}
|
|
|
|
switch field.Kind() {
|
|
case reflect.Ptr:
|
|
newValue := reflect.ValueOf(&value)
|
|
field.Set(newValue)
|
|
default:
|
|
}
|
|
}
|
|
|
|
func (item *Item) emptyStats() {
|
|
*item.StatType1 = 0
|
|
*item.StatValue1 = 0
|
|
*item.StatType2 = 0
|
|
*item.StatValue2 = 0
|
|
*item.StatType3 = 0
|
|
*item.StatValue3 = 0
|
|
*item.StatType4 = 0
|
|
*item.StatValue4 = 0
|
|
*item.StatType5 = 0
|
|
*item.StatValue5 = 0
|
|
*item.StatType6 = 0
|
|
*item.StatValue6 = 0
|
|
*item.StatType7 = 0
|
|
*item.StatValue7 = 0
|
|
*item.StatType8 = 0
|
|
*item.StatValue8 = 0
|
|
*item.StatType9 = 0
|
|
*item.StatValue9 = 0
|
|
*item.StatType10 = 0
|
|
*item.StatValue10 = 0
|
|
}
|
|
|
|
// Cleans up spells from the item that have been converted to stats and leaves only the ones that are not
|
|
func (item *Item) cleanSpells() {
|
|
for i := 1; i < 3; i++ {
|
|
currentId, err := item.GetField(fmt.Sprintf("SpellId%v", i))
|
|
|
|
log.Printf("Checking spell id %v - value %v", i, currentId)
|
|
if err != nil {
|
|
log.Printf("ERROR: Failed to get spell id %v err: %v", i, err)
|
|
continue
|
|
}
|
|
|
|
// if there no spellId set then check the next one if it is set move it and clear it
|
|
if currentId == 0 {
|
|
nextSpellId, err := item.GetField(fmt.Sprintf("SpellId%v", i+1))
|
|
if err != nil {
|
|
log.Printf("ERROR: Failed to get spell id %v err: %v", i+1, err)
|
|
}
|
|
|
|
if nextSpellId != 0 {
|
|
item.UpdateField(fmt.Sprintf("SpellId%v", i), nextSpellId)
|
|
item.UpdateField(fmt.Sprintf("SpellId%v", i+1), 0)
|
|
log.Printf("Moved spell %v to %v to replace removed spell", nextSpellId, i)
|
|
continue
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func (item *Item) addStats(stats map[int]*ItemStat) {
|
|
item.emptyStats()
|
|
i := 1
|
|
|
|
// itemValue := reflect.ValueOf(item).Elem() // Get value of underlying struct
|
|
|
|
for statId, stat := range stats {
|
|
if i > 10 {
|
|
break
|
|
}
|
|
|
|
statTypeField := fmt.Sprintf("StatType%d", i)
|
|
statValueField := fmt.Sprintf("StatValue%d", i)
|
|
|
|
// MP5 adjustment
|
|
if statId == 43 {
|
|
stat.Value = int(math.Round(float64(stat.Value) * 0.5))
|
|
}
|
|
|
|
if statId == 12 {
|
|
stat.Value = int(math.Round(float64(stat.Value) * 0.5))
|
|
}
|
|
|
|
if statId == 12 {
|
|
stat.Value = int(math.Round(float64(stat.Value) * 0.75))
|
|
}
|
|
|
|
if statId == 13 {
|
|
stat.Value = int(math.Round(float64(stat.Value) * 0.65))
|
|
}
|
|
|
|
if statId == 31 {
|
|
stat.Value = int(math.Round(float64(stat.Value) * 0.55))
|
|
}
|
|
|
|
// Update the item with new stats from scaling
|
|
item.UpdateField(statTypeField, statId)
|
|
item.UpdateField(statValueField, stat.Value)
|
|
|
|
// Get the stats for logging purposes
|
|
// tmpType, _ := item.GetField(statTypeField)
|
|
// tmpStat, _ := item.GetField(statValueField)
|
|
// log.Printf("Updated %s to %v, %s to %v", statTypeField, tmpType, statValueField, tmpStat)
|
|
|
|
i++
|
|
}
|
|
}
|
|
|
|
// Scale formula ((ItemLevel * QualityModifier * ItemTypeModifier)^1.7095 * %ofStats) ^ (1/1.7095)) / StatModifier
|
|
func scaleStat(itemLevel int, itemType int, itemQuality int, percOfStat float64, statModifier float64) int {
|
|
scaledUp := math.Pow((float64(itemLevel)*QualityModifiers[itemQuality]*InvTypeModifiers[itemType]), 1.7095) * percOfStat
|
|
|
|
// leaving modifier off for now but not changing signature in case I need to add it back
|
|
_ = statModifier
|
|
return int(math.Ceil(math.Pow(scaledUp, 1/1.7095))) // normalized
|
|
}
|