diff --git a/main.go b/main.go index 80903f7..2f47a24 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,11 @@ package main import ( + "flag" "fmt" + "io" "log" + "os" "github.com/araxiaonline/endgame-item-generator/models" _ "github.com/go-sql-driver/mysql" @@ -15,9 +18,18 @@ func main() { godotenv.Load() models.Connect() + debug := flag.Bool("debug", false, "Enable verbose logging inside generator") + flag.Parse() + + if *debug { + log.SetOutput(os.Stdout) + } else { + log.SetOutput(io.Discard) + } + bosses, err := models.DB.GetBosses(229) if err != nil { - log.Fatal(err) + log.Fatal("failed to get bosses") } for _, boss := range bosses { @@ -30,30 +42,7 @@ func main() { for _, item := range items { - fmt.Printf("\nItem %v Entry: %v ItemLevel %v \n", item.Name, item.Entry, *item.ItemLevel) - // item.GetStatPercents() - - // if *item.SpellId1 != 0 { - // spell, err := models.DB.GetSpell(*item.SpellId1) - // if err != nil { - // log.Printf("failed to get the spell: %v error: %v", *item.SpellId1, err) - // } - - // log.Printf("Spell %v Spell Effects 1: %v 2: %v, 3: %v \n", spell.Name, spell.Effect1, spell.Effect2, spell.Effect3) - // log.Printf("Spell Aura 1: %v 2: %v, 3: %v \n", spell.EffectAura1, spell.EffectAura2, spell.EffectAura3) - - // convStats, err := spell.ConvertToStats() - // if err != nil { - // log.Printf("Failed to convert spell to stats: %v", err) - // } - - // scaleItemStats := item.GetStatPercents(convStats) - // for statId, stat := range scaleItemStats { - // log.Printf("StatId: %v Type: %s Value: %v Percent: %v", statId, stat.Type, stat.Value, stat.Percent) - // } - // log.Printf("Scaled Spell Stats: %v\n", convStats) - - // } + log.Printf("\nItem %v Entry: %v ItemLevel %v \n", item.Name, item.Entry, *item.ItemLevel) _, error := item.ScaleItem(320, 3) fmt.Print(ItemToSql(item, 80, 3)) @@ -65,18 +54,9 @@ func main() { if err != nil { log.Fatal(err) } - // fmt.Println(stat, value) } } - // iLevel := 219 - // qual := 3 - // delay := 2.60 - // sub := 0 - // myItem := models.Item{Name: "Hypnotic Blade", ItemLevel: &iLevel, Quality: &qual, Delay: &delay, Subclass: &sub} - // dps, err := myItem.ScaleDPS() - - // log.Printf("Item %s DPS: %.1f", myItem.Name, dps) defer models.DB.Close() } diff --git a/models/items.go b/models/items.go index 4269101..1f08a6e 100644 --- a/models/items.go +++ b/models/items.go @@ -71,6 +71,14 @@ type Item struct { 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 @@ -163,13 +171,6 @@ var StatModifiers = map[int]float64{ 48: 0.65, // ITEM_MOD_BLOCK_VALUE } -type ItemStat struct { - Value int - Percent float64 - Type string - AdjValue float64 -} - func (item Item) GetPrimaryStat() (int, int, error) { var primaryStat int64 var primaryVal int64 @@ -488,7 +489,7 @@ func (item *Item) ScaleItem(itemLevel int, itemQuality int) (bool, error) { log.Printf("DPS: %.1f scaled up from previous dps %v", dps, predps) } - item.CleanSpells() + 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. @@ -515,87 +516,6 @@ func (item *Item) ScaleItem(itemLevel int, itemQuality int) (bool, error) { } -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 -} - -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) - - // 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++ - } -} - -// Cleans up spells from the item that have been converted to stats and leaves only the ones that are not -func (item *Item) CleanSpells() { - spells, err := item.GetSpells() - if err != nil { - log.Printf("Failed to get spells for item: %v", err) - return - } - - if len(spells) == 0 { - return - } - - for i := 1; i < 4; i++ { - for _, spell := range spells { - currentId, err := item.GetField(fmt.Sprintf("SpellId%v", i)) - if err != nil { - log.Printf("ERROR: Failed to get spell id %v err: %v", i, err) - continue - } - if currentId == 0 { - continue - } - - if currentId == spell.ID { - item.UpdateField(fmt.Sprintf("SpellId%v", i), 0) - log.Printf("Removed spell %v from spellSlot: %v", spell.Name, fmt.Sprintf("SpellId%v", i)) - } - } - } -} - func (item *Item) GetField(fieldName string) (int, error) { itemValue := reflect.ValueOf(item).Elem() field := itemValue.FieldByName(fieldName) @@ -633,6 +553,87 @@ func (item *Item) UpdateField(fieldName string, value int) { } } +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() { + spells, err := item.GetSpells() + if err != nil { + log.Printf("Failed to get spells for item: %v", err) + return + } + + if len(spells) == 0 { + return + } + + for i := 1; i < 4; i++ { + for _, spell := range spells { + currentId, err := item.GetField(fmt.Sprintf("SpellId%v", i)) + if err != nil { + log.Printf("ERROR: Failed to get spell id %v err: %v", i, err) + continue + } + if currentId == 0 { + continue + } + + if currentId == spell.ID { + item.UpdateField(fmt.Sprintf("SpellId%v", i), 0) + log.Printf("Removed spell %v from spellSlot: %v", spell.Name, fmt.Sprintf("SpellId%v", i)) + } + } + } +} + +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) + + // 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 diff --git a/models/maps.go b/models/maps.go new file mode 100644 index 0000000..fe3959d --- /dev/null +++ b/models/maps.go @@ -0,0 +1,94 @@ +package models + +import "fmt" + +type Dungeon struct { + Id int `db:"Id"` + Name string `db:"Name"` + Level int + ExpansionId int `db:"ExpansionId"` +} + +var dungeons = map[int]int{ + // Classic WoW dungeons + 389: 18, // Ragefire Chasm + 43: 25, // Wailing Caverns + 36: 23, // The Deadmines + 33: 30, // Shadowfang Keep + 34: 30, // The Stockade + 48: 32, // Blackfathom Deeps + 90: 38, // Gnomeregan + 47: 40, // Razorfen Kraul + 189: 45, // Scarlet Monastery (Graveyard) + 289: 60, // Scholomance + 329: 60, // Stratholme + 229: 60, // Blackrock Spire (Lower) + 230: 60, // Blackrock Spire (Upper) + 429: 60, // Dire Maul + 209: 54, // Zul'Farrak + 349: 55, // Maraudon + 269: 57, // Temple of Atal'Hakkar + // The Burning Crusade dungeons + 540: 62, // Hellfire Citadel: Hellfire Ramparts + 542: 63, // Hellfire Citadel: The Blood Furnace + 547: 64, // Coilfang Reservoir: The Slave Pens + 546: 65, // Coilfang Reservoir: The Underbog + 557: 66, // Auchindoun: Mana-Tombs + 558: 67, // Auchindoun: Auchenai Crypts + 560: 68, // Caverns of Time: Old Hillsbrad Foothills + 556: 69, // Auchindoun: Sethekk Halls + 545: 70, // Coilfang Reservoir: The Steamvault + 555: 70, // Auchindoun: Shadow Labyrinth + 543: 70, // Hellfire Citadel: The Shattered Halls + 553: 70, // Tempest Keep: The Botanica + 554: 70, // Tempest Keep: The Mechanar + 552: 70, // Tempest Keep: The Arcatraz + 585: 70, // Magisters' Terrace + // Wrath of the Lich King dungeons + 70: 72, // Utgarde Keep + 129: 74, // Azjol-Nerub + 619: 75, // Ahn'kahet: The Old Kingdom + 576: 73, // The Nexus + 600: 76, // Drak'Tharon Keep + 608: 77, // The Violet Hold + 604: 78, // Gundrak + 599: 79, // Halls of Stone + 602: 80, // Halls of Lightning + 578: 79, // The Oculus + 575: 80, // Utgarde Pinnacle + 595: 80, // The Culling of Stratholme + 650: 80, // Trial of the Champion + 632: 80, // The Forge of Souls + 658: 80, // Pit of Saron + 668: 80, // Halls of Reflection +} + +func (db Database) GetDungeons(expansionId int) ([]Dungeon, error) { + dungeons := []Dungeon{} + + sql := ` + SELECT ID as Id, MapName_Lang_enUS as Name, ExpansionID as ExpansionId + FROM map_dbc + WHERE InstanceType = 1 AND Name NOT LIKE '%unused%'; + ` + var err error + if expansionId != -1 { + sql = sql + "AND ExpansionID = ?" + err = db.client.Select(&dungeons, sql, expansionId) + } else { + err = db.client.Select(&dungeons, sql) + } + + if err != nil { + return nil, fmt.Errorf("failed to get dungeons %v", err) + } + + return dungeons, nil +} + +func addLevels(dungeons []Dungeon) { + for _, dungeon := range dungeons { + + dungeon.Level = 60 + } +} diff --git a/models/spells.go b/models/spells.go index d778361..bbb044d 100644 --- a/models/spells.go +++ b/models/spells.go @@ -388,13 +388,14 @@ func parseStatDesc(desc string) int { return 0 } -// Scales a spell effect +// Scales a spell effect, means creating a new spell with the same effect but scaled to a new item level, then passing +// back the new spellId, In order to be predictable I will use 30000000 for rare, 31000000 for epic, 32000000 for legendary // 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). -func (s *Spell) ScaleSpell(itemLevel int, itemQuality int) error { +func (s *Spell) ScaleSpell(itemLevel int, itemQuality int) (int, error) { qualModifier := map[int]float64{ 3: 0.20, @@ -402,15 +403,26 @@ func (s *Spell) ScaleSpell(itemLevel int, itemQuality int) error { 5: 0.40, } + idBump := 30000000 + if itemQuality == 4 { + idBump = 31000000 + } + if itemQuality == 5 { + idBump = 32000000 + } + // direct damage types dd := [...]int{2, 9, 10} + didScale := false // Causes direct damage if s.Effect1 != 0 && funk.Contains(dd, s.Effect1) { s.EffectBasePoints1 = int(float64(s.EffectBasePoints1) / float64(s.SpellLevel) * float64(itemLevel) * qualModifier[itemQuality]) + didScale = true } if s.Effect2 != 0 && funk.Contains(dd, s.Effect1) { s.EffectBasePoints2 = int(float64(s.EffectBasePoints2) / float64(s.SpellLevel) * float64(itemLevel) * qualModifier[itemQuality]) + didScale = true } // Restores a Power / Mana @@ -418,26 +430,34 @@ func (s *Spell) ScaleSpell(itemLevel int, itemQuality int) error { // skip anyhing else that is not mana as they are flat values if strings.Contains(s.Description, "Mana") { s.EffectBasePoints1 = int(float64(s.EffectBasePoints1) / float64(s.SpellLevel) * float64(itemLevel) * qualModifier[itemQuality] * 0.75) + didScale = true } } // Scales a stat buff if s.Effect1 != 0 && s.Effect1 == 35 { s.EffectBasePoints1 = int(float64(s.EffectBasePoints1) / float64(s.SpellLevel) * float64(itemLevel) * qualModifier[itemQuality]) + didScale = true } if s.Effect1 != 0 && s.Effect2 == 35 { s.EffectBasePoints2 = int(float64(s.EffectBasePoints2) / float64(s.SpellLevel) * float64(itemLevel) * qualModifier[itemQuality]) + didScale = true } // Handle special aura effects if s.EffectAura1 != 0 && s.EffectAura1 == 3 && s.Effect1 == 6 { s.EffectBasePoints1 = int(float64(s.EffectBasePoints1) / float64(s.SpellLevel) * float64(itemLevel) * qualModifier[itemQuality]) + didScale = true } // Damage Shield Increase Scale due to HP curve if s.EffectAura1 != 0 && s.EffectAura1 == 15 && s.Effect1 == 6 { s.EffectBasePoints1 = int(float64(s.EffectBasePoints1) / float64(s.SpellLevel) * float64(itemLevel) * qualModifier[itemQuality] * 1.50) + didScale = true } - return nil + if !didScale { + return 0, fmt.Errorf("did not qualify to be scaled in ScaleSpell %v (%v)", s.Name, s.ID) + } + return idBump + s.ID, nil }