updates to add ability to scale to higher level items for phase 1 raids

This commit is contained in:
2025-08-06 22:12:32 -04:00
parent b84a9e670c
commit 574525809d
8 changed files with 1459 additions and 47 deletions

View File

@@ -1,6 +1,7 @@
package config
var InvTypeModifiers = map[int]float64{
0: 0.6, // Trinket
1: 0.813, // Head
2: 1.0, // Neck
3: 0.75, // Shoulder
@@ -34,7 +35,7 @@ var QualityModifiers = map[int]float64{
2: 1.0, // UnCommon
3: 1.2, // Rare
4: 1.4, // Epic
5: 1.8, // Legendary
5: 2.0, // Legendary
}
var MaterialModifiers = map[int]float64{
@@ -45,6 +46,15 @@ var MaterialModifiers = map[int]float64{
6: 20.0, // Plate
}
// Modifies stats flat for difficulty of dungeon / raid itself.
var GearTierModifiers = map[int]float64{
1: 1.10,
2: 1.15,
3: 1.20,
4: 1.25,
5: 1.30,
}
var StatModifiers = map[int]float64{
0: 1.0, // ITEM_MOD_MANA
1: 1.0, // ITEM_MOD_HEALTH
@@ -53,10 +63,10 @@ var StatModifiers = map[int]float64{
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
12: 1.5, // 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
15: 0.8, // 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
@@ -72,38 +82,85 @@ var StatModifiers = map[int]float64{
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
31: 2.5, // 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
38: 0.75, // ITEM_MOD_ATTACK_POWER
39: 0.75, // ITEM_MOD_RANGED_ATTACK_POWER
40: 0.75, // ITEM_MOD_FERAL_ATTACK_POWER (not used as of 3.3)
41: 0.75, // ITEM_MOD_SPELL_HEALING_DONE
42: 0.75, // ITEM_MOD_SPELL_DAMAGE_DONE
43: 2.5, // ITEM_MOD_MANA_REGENERATION
44: 1.0, // ITEM_MOD_ARMOR_PENETRATION_RATING
45: 0.50, // ITEM_MOD_SPELL_POWER
45: 0.75, // 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
47: 1.8, // ITEM_MOD_SPELL_PENETRATION
48: 1.5, // ITEM_MOD_BLOCK_VALUE
}
var StatModifierNames = map[int]string{
0: "MANA",
1: "HEALTH",
3: "AGILITY",
4: "STRENGTH",
5: "INTELLECT",
6: "SPIRIT",
7: "STAMINA",
12: "DEFENSE_SKILL_RATING",
13: "DODGE_RATING",
14: "PARRY_RATING",
15: "BLOCK_RATING",
16: "HIT_MELEE_RATING",
17: "HIT_RANGED_RATING",
18: "HIT_SPELL_RATING",
19: "CRIT_MELEE_RATING",
20: "CRIT_RANGED_RATING",
21: "CRIT_SPELL_RATING",
22: "HIT_TAKEN_MELEE_RATING",
23: "HIT_TAKEN_RANGED_RATING",
24: "HIT_TAKEN_SPELL_RATING",
25: "CRIT_TAKEN_MELEE_RATING",
26: "CRIT_TAKEN_RANGED_RATING",
27: "CRIT_TAKEN_SPELL_RATING",
28: "HASTE_MELEE_RATING",
29: "HASTE_RANGED_RATING",
30: "HASTE_SPELL_RATING",
31: "HIT_RATING",
32: "CRIT_RATING",
33: "HIT_TAKEN_RATING",
34: "CRIT_TAKEN_RATING",
35: "RESILIENCE_RATING",
36: "HASTE_RATING",
37: "EXPERTISE_RATING",
38: "ATTACK_POWER",
39: "RANGED_ATTACK_POWER",
40: "FERAL_ATTACK_POWER",
41: "SPELL_HEALING_DONE",
42: "SPELL_DAMAGE_DONE",
43: "MANA_REGENERATION",
44: "ARMOR_PENETRATION_RATING",
45: "SPELL_POWER",
46: "HEALTH_REGEN",
47: "SPELL_PENETRATION",
48: "BLOCK_VALUE",
}
var ScalingFactor = map[int]float64{
0: 1.1, // ITEM_MOD_MANA
1: 1.5, // ITEM_MOD_HEALTH
3: 1.30, // ITEM_MOD_AGILITY
4: 1.30, // ITEM_MOD_STRENGTH
5: 1.30, // ITEM_MOD_INTELLECT
6: 1.30, // ITEM_MOD_SPIRIT
7: 1.75, // ITEM_MOD_STAMINA
12: 1.2, // ITEM_MOD_DEFENSE_SKILL_RATING
3: 1.35, // ITEM_MOD_AGILITY
4: 1.35, // ITEM_MOD_STRENGTH
5: 1.35, // ITEM_MOD_INTELLECT
6: 1.35, // ITEM_MOD_SPIRIT
7: 1.40, // ITEM_MOD_STAMINA
12: 1.3, // ITEM_MOD_DEFENSE_SKILL_RATING
13: 1.15, // ITEM_MOD_DODGE_RATING
14: 1.15, // ITEM_MOD_PARRY_RATING
15: 1.1, // ITEM_MOD_BLOCK_RATING
15: 1.2, // ITEM_MOD_BLOCK_RATING
16: 1.1, // ITEM_MOD_HIT_MELEE_RATING
17: 1.1, // ITEM_MOD_HIT_RANGED_RATING
18: 1.1, // ITEM_MOD_HIT_SPELL_RATING
@@ -126,15 +183,15 @@ var ScalingFactor = map[int]float64{
35: 1.0, // ITEM_MOD_RESILIENCE_RATING
36: 1.25, // ITEM_MOD_HASTE_RATING
37: 0.8, // ITEM_MOD_EXPERTISE_RATING
38: 1.45, // ITEM_MOD_ATTACK_POWER
39: 1.45, // ITEM_MOD_RANGED_ATTACK_POWER
40: 1.45, // ITEM_MOD_FERAL_ATTACK_POWER (not used as of 3.3)
41: 1.4, // ITEM_MOD_SPELL_HEALING_DONE
42: 1.4, // ITEM_MOD_SPELL_DAMAGE_DONE
38: 1.5, // ITEM_MOD_ATTACK_POWER
39: 1.5, // ITEM_MOD_RANGED_ATTACK_POWER
40: 1.5, // ITEM_MOD_FERAL_ATTACK_POWER (not used as of 3.3)
41: 1.5, // ITEM_MOD_SPELL_HEALING_DONE
42: 1.5, // ITEM_MOD_SPELL_DAMAGE_DONE
43: 1.3, // ITEM_MOD_MANA_REGENERATION
44: 1.1, // ITEM_MOD_ARMOR_PENETRATION_RATING
45: 1.6, // ITEM_MOD_SPELL_POWER
45: 1.5, // ITEM_MOD_SPELL_POWER
46: 1.3, // ITEM_MOD_HEALTH_REGEN
47: 1.0, // ITEM_MOD_SPELL_PENETRATION
48: 1.0, // ITEM_MOD_BLOCK_VALUE
48: 1.4, // ITEM_MOD_BLOCK_VALUE
}

View File

@@ -74,6 +74,76 @@ var BossIDs = map[int]bool{
36502: true, // Devourer of Souls (Forge of Souls)
36658: true, // Scourgelord Tyrannus (Pit of Saron)
37226: true, // The Lich King (Halls of Reflection)
// Molten Core
12118: true, // Lucifron
11982: true, // Magmadar
12259: true, // Gehennas
12057: true, // Garr
12264: true, // Shazzrah
12056: true, // Baron Geddon
12098: true, // Sulfuron Harbinger
11988: true, // Golemagg the Incinerator
12018: true, // Majordomo Executus
11502: true, // Ragnaros
// Blackwing Lair
12435: true, // Razorgore the Untamed
13020: true, // Vaelastrasz the Corrupt
12017: true, // Broodlord Lashlayer
11983: true, // Firemaw
14601: true, // Ebonroc
11981: true, // Flamegor
14020: true, // Chromaggus
11583: true, // Nefarian
// Ruins of Ahn'Qiraj
15348: true, // Kurinnaxx
15341: true, // General Rajaxx
15340: true, // Moam
15370: true, // Buru the Gorger
15369: true, // Ayamiss the Hunter
15339: true, // Ossirian the Unscarred
// Temple of Ahn'Qiraj
15263: true, // The Prophet Skeram
15516: true, // Battleguard Sartura
15510: true, // Fankriss the Unyielding
15509: true, // Princess Huhuran
15275: true, // Emperor Vek'lor
15276: true, // Emperor Vek'nilash
15727: true, // C'Thun
// Zul'Gurub
14517: true, // High Priestess Jeklik
14507: true, // High Priest Venoxis
14510: true, // High Priestess Mar'li
14509: true, // High Priest Thekal
14515: true, // High Priestess Arlokk
14834: true, // Hakkar the Soulflayer
11382: true, // Bloodlord Mandokir
11380: true, // Jin'do the Hexxer
15114: true, // Gahz'ranka
15082: true, // Renataki
15083: true, // Grilek
15084: true, // Hazza'rah
15085: true, // Wushoolay
// Karazhan (Full Boss List)
16152: true, // Attumen the Huntsman (Karazhan)
15687: true, // Moroes (Karazhan)
16457: true, // Maiden of Virtue (Karazhan)
17521: true, // The Big Bad Wolf (Karazhan)
18168: true, // The Crone (Karazhan)
17533: true, // Romulo (Karazhan)
17534: true, // Julianne (Karazhan)
15691: true, // The Curator (Karazhan)
15688: true, // Terestian Illhoof (Karazhan)
16524: true, // Shade of Aran (Karazhan)
15689: true, // Netherspite (Karazhan)
16816: true, // Chess Event / Echo of Medivh (Karazhan)
15690: true, // Prince Malchezaar (Karazhan)
17225: true, // Nightbane (Karazhan)
}
func (db *MySqlDb) GetBosses(mapId int) ([]Boss, error) {

View File

@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"log"
"time"
"github.com/araxiaonline/endgame-item-generator/internal/config"
)
@@ -53,6 +54,12 @@ type DbItem struct {
StatValue9 *int `db:"stat_value9"`
StatType10 *int `db:"stat_type10"`
StatValue10 *int `db:"stat_value10"`
HolyRes *int `db:"holy_res"`
FireRes *int `db:"fire_res"`
NatureRes *int `db:"nature_res"`
FrostRes *int `db:"frost_res"`
ShadowRes *int `db:"shadow_res"`
ArcaneRes *int `db:"arcane_res"`
SpellId1 *int `db:"spellid_1"`
SpellId2 *int `db:"spellid_2"`
SpellId3 *int `db:"spellid_3"`
@@ -69,6 +76,39 @@ type DbItem struct {
GemProperties *int `db:"GemProperties"`
}
type DbItemCsv struct {
Entry int `csv:"entry"`
Name string `csv:"name"`
DisplayId int `csv:"displayid"`
Quality *int `csv:"Quality"`
ItemLevel *int `csv:"ItemLevel"`
Class *int `csv:"class"`
Subclass *int `csv:"subclass"`
InventoryType *int `csv:"inventoryType"`
RequiredLevel *int `csv:"requiredLevel"`
StatsCount *int `csv:"statsCount"`
StatType1 *int `csv:"stat_type1"`
StatValue1 *int `csv:"stat_value1"`
StatType2 *int `csv:"stat_type2"`
StatValue2 *int `csv:"stat_value2"`
StatType3 *int `csv:"stat_type3"`
StatValue3 *int `csv:"stat_value3"`
StatType4 *int `csv:"stat_type4"`
StatValue4 *int `csv:"stat_value4"`
StatType5 *int `csv:"stat_type5"`
StatValue5 *int `csv:"stat_value5"`
StatType6 *int `csv:"stat_type6"`
StatValue6 *int `csv:"stat_value6"`
StatType7 *int `csv:"stat_type7"`
StatValue7 *int `csv:"stat_value7"`
StatType8 *int `csv:"stat_type8"`
StatValue8 *int `csv:"stat_value8"`
StatType9 *int `csv:"stat_type9"`
StatValue9 *int `csv:"stat_value9"`
StatType10 *int `csv:"stat_type10"`
StatValue10 *int `csv:"stat_value10"`
}
func (db *MySqlDb) GetItem(entry int) (DbItem, error) {
if entry == 0 {
return DbItem{}, fmt.Errorf("entry cannot be 0")
@@ -137,6 +177,70 @@ func (db *MySqlDb) GetRarePlusItems(limit, offset int) ([]DbItem, error) {
return items, nil
}
func (db *MySqlDb) GetBossMapItems(mapId, limit, offset int) ([]DbItem, error) {
items := []DbItem{}
sql := `SELECT DISTINCT ` + GetItemFields("it") + `
FROM acore_world.creature c
JOIN acore_world.creature_template ct ON c.id1 = ct.entry
JOIN acore_world.map_dbc m ON c.map = m.ID
LEFT JOIN acore_world.creature_loot_template clt ON ct.lootid = clt.Entry
LEFT JOIN acore_world.reference_loot_template rlt ON clt.Reference = rlt.Entry
LEFT JOIN acore_world.item_template it ON rlt.Item = it.entry
WHERE
m.ID = ?
AND ct.rank = 3
-- AND it.StatsCount = 0
AND it.class IN (2, 4) -- Weapons and armor
AND it.bonding IN (1, 2) -- Binds when picked up/equipped
AND it.Quality >= 3 -- Epic and above
`
if limit != 0 && offset != 0 {
sql += fmt.Sprintf("LIMIT %v OFFSET %v", limit, offset)
}
err := db.Select(&items, sql, mapId)
if err != nil {
return []DbItem{}, err
}
return items, nil
}
func (db *MySqlDb) GetRaidPhase1Items(class, subclass, limit, offset int) ([]DbItem, error) {
items := []DbItem{}
sql := `SELECT DISTINCT ` + GetItemFields("it") + `
FROM acore_world.creature c
JOIN acore_world.creature_template ct ON c.id1 = ct.entry
JOIN acore_world.map_dbc m ON c.map = m.ID
LEFT JOIN acore_world.creature_loot_template clt ON ct.lootid = clt.Entry
LEFT JOIN acore_world.reference_loot_template rlt ON clt.Reference = rlt.Entry
LEFT JOIN acore_world.item_template it ON rlt.Item = it.entry
WHERE
m.ID IN (533,615,616)
AND ct.rank = 3
AND it.class = ? -- Weapons and armor
AND it.subclass = ?
AND it.bonding IN (1, 2) -- Binds when picked up/equipped
AND it.Quality >= 3 -- Epic and above
`
if limit != 0 && offset != 0 {
sql += fmt.Sprintf("LIMIT %v OFFSET %v", limit, offset)
}
err := db.Select(&items, sql, class, subclass)
if err != nil {
return []DbItem{}, err
}
return items, nil
}
func GetItemFields(prefix string) string {
pre := ""
if prefix != "" {
@@ -148,6 +252,7 @@ func GetItemFields(prefix string) string {
quality, ItemLevel, class, subclass, inventoryType,
allowableClass, allowableRace,
armor,material,
holy_res, fire_res, nature_res, frost_res, shadow_res, arcane_res,
requiredSkill, requiredLevel,
dmg_min1, dmg_max1,
dmg_min2,dmg_max2,
@@ -169,3 +274,281 @@ func GetItemFields(prefix string) string {
socketColor_1, socketContent_1, socketColor_2, socketContent_2, socketColor_3, socketContent_3,
socketBonus, GemProperties`
}
// This will write an DBItem to the database of the specified table..
// It must match the item_template schema
// CopyItem copies an item from one table to another with an optional new ID
// This uses a temporary table approach to handle copying ALL fields without having to list them
// If newId is 0, it keeps the original ID
func (db *MySqlDb) CopyItem(sourceTable string, destTable string, itemEntry int, newEntry int) error {
// Generate a unique temporary table name using timestamp
tempTableName := fmt.Sprintf("temp_item_copy_%d", time.Now().UnixNano())
// Create a temporary table with the same structure as the source table
sql := fmt.Sprintf("CREATE TEMPORARY TABLE %s LIKE %s", tempTableName, sourceTable)
_, err := db.Exec(sql)
if err != nil {
return fmt.Errorf("failed to create temporary table: %w", err)
}
// Copy the item to the temporary table
sql = fmt.Sprintf("INSERT INTO %s SELECT * FROM %s WHERE entry = %d",
tempTableName, sourceTable, itemEntry)
_, err = db.Exec(sql)
if err != nil {
return fmt.Errorf("failed to copy item to temporary table: %w", err)
}
// If we need to change the entry, update it in the temporary table
if newEntry > 0 && newEntry != itemEntry {
sql = fmt.Sprintf("UPDATE %s SET entry = %d", tempTableName, newEntry)
_, err = db.Exec(sql)
if err != nil {
return fmt.Errorf("failed to update item entry in temporary table: %w", err)
}
}
// Copy from temporary table to destination table
sql = fmt.Sprintf("REPLACE INTO %s SELECT * FROM %s", destTable, tempTableName)
_, err = db.Exec(sql)
if err != nil {
return fmt.Errorf("failed to copy item from temporary table to destination: %w", err)
}
// Drop the temporary table
sql = fmt.Sprintf("DROP TEMPORARY TABLE IF EXISTS %s", tempTableName)
_, err = db.Exec(sql)
if err != nil {
log.Printf("Warning: failed to drop temporary table %s: %v", tempTableName, err)
}
return nil
}
func (db *MySqlDb) WriteItem(table string, item DbItem) error {
// We'll use INSERT ... ON DUPLICATE KEY UPDATE to preserve fields not explicitly set
// First, build the field list for the INSERT part
fields := GetItemFields("")
// Construct the SQL insert statement using the item fields
sql := "INSERT INTO " + table + " (" + fields + ") VALUES (" +
"?, ?, ?, " + // entry, name, displayid
"?, ?, ?, ?, ?, " + // quality, ItemLevel, class, subclass, inventoryType
"?, ?, " + // allowableClass, allowableRace
"?, ?, " + // armor, material
"?, ?, ?, ?, ?, ?, " + // holy_res, fire_res, nature_res, frost_res, shadow_res, arcane_res
"?, ?, " + // requiredSkill, requiredLevel
"?, ?, " + // dmg_min1, dmg_max1
"?, ?, " + // dmg_min2, dmg_max2
"?, ?, " + // dmg_type1, dmg_type2
"?, ?, ?, " + // delay, sheath, MaxDurability
"?, " + // statsCount
"?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " + // stat_type1-5, stat_value1-5
"?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " + // stat_type6-10, stat_value6-10
"?, ?, ?, " + // spellid_1-3
"?, ?, ?, " + // spelltrigger_1-3
"?, ?, ?, ?, ?, ?, " + // socketColor_1-3, socketContent_1-3
"?, ?" + // socketBonus, GemProperties
") ON DUPLICATE KEY UPDATE " +
"name = VALUES(name), " +
"quality = VALUES(quality), " +
"ItemLevel = VALUES(ItemLevel), " +
"requiredLevel = VALUES(requiredLevel), " +
"dmg_min1 = VALUES(dmg_min1), dmg_max1 = VALUES(dmg_max1), " +
"dmg_min2 = VALUES(dmg_min2), dmg_max2 = VALUES(dmg_max2), " +
"dmg_type1 = VALUES(dmg_type1), dmg_type2 = VALUES(dmg_type2), " +
"statsCount = VALUES(statsCount), " +
"stat_type1 = VALUES(stat_type1), stat_value1 = VALUES(stat_value1), " +
"stat_type2 = VALUES(stat_type2), stat_value2 = VALUES(stat_value2), " +
"stat_type3 = VALUES(stat_type3), stat_value3 = VALUES(stat_value3), " +
"stat_type4 = VALUES(stat_type4), stat_value4 = VALUES(stat_value4), " +
"stat_type5 = VALUES(stat_type5), stat_value5 = VALUES(stat_value5), " +
"stat_type6 = VALUES(stat_type6), stat_value6 = VALUES(stat_value6), " +
"stat_type7 = VALUES(stat_type7), stat_value7 = VALUES(stat_value7), " +
"stat_type8 = VALUES(stat_type8), stat_value8 = VALUES(stat_value8), " +
"stat_type9 = VALUES(stat_type9), stat_value9 = VALUES(stat_value9), " +
"stat_type10 = VALUES(stat_type10), stat_value10 = VALUES(stat_value10), " +
"spellid_1 = VALUES(spellid_1), spellid_2 = VALUES(spellid_2), spellid_3 = VALUES(spellid_3)"
// Execute the query with all the item fields as parameters
_, err := db.Exec(sql,
item.Entry, item.Name, item.DisplayId,
item.Quality, item.ItemLevel, item.Class, item.Subclass, item.InventoryType,
item.AllowableClass, item.AllowableRace,
item.Armor, item.Material,
item.HolyRes, item.FireRes, item.NatureRes, item.FrostRes, item.ShadowRes, item.ArcaneRes,
item.RequiredSkill, item.RequiredLevel,
item.MinDmg1, item.MaxDmg1,
item.MinDmg2, item.MaxDmg2,
item.DmgType1, item.DmgType2,
item.Delay, item.Sheath, item.Durability,
item.StatsCount,
item.StatType1, item.StatValue1,
item.StatType2, item.StatValue2,
item.StatType3, item.StatValue3,
item.StatType4, item.StatValue4,
item.StatType5, item.StatValue5,
item.StatType6, item.StatValue6,
item.StatType7, item.StatValue7,
item.StatType8, item.StatValue8,
item.StatType9, item.StatValue9,
item.StatType10, item.StatValue10,
item.SpellId1, item.SpellId2, item.SpellId3,
item.SpellTrigger1, item.SpellTrigger2, item.SpellTrigger3,
item.SocketColor1, item.SocketContent1,
item.SocketColor2, item.SocketContent2,
item.SocketColor3, item.SocketContent3,
item.SocketBonus, item.GemProperties,
)
if err != nil {
log.Printf("Failed to run sql query: %v", sql)
return fmt.Errorf("failed to insert item into %s: %w", table, err)
}
return nil
}
// This will convert a DbItemCsv to a DbItem and return
// It will first look up the original item in the database though first and populate
// the DbItem with the original item's values then override with the values from the csv
func (db *MySqlDb) ConvertCsvToDbItem(csv DbItemCsv) (DbItem, error) {
// Try to find the original item in the database
lookupEntry := csv.Entry - 2000000
item, err := db.GetItem(lookupEntry)
if err != nil {
// Log the error with more details
log.Printf("Error finding original item with entry %d: %v", lookupEntry, err)
item, err = db.GetItem(csv.Entry)
if err != nil {
log.Printf("Error finding item with direct entry %d: %v", csv.Entry, err)
// Create a new item with the CSV data
item = DbItem{
Entry: csv.Entry,
Name: csv.Name,
DisplayId: csv.DisplayId,
}
}
}
// Override values from CSV
item.Name = csv.Name
// Override pointer fields if they exist in CSV
if csv.Quality != nil {
item.Quality = csv.Quality
}
if csv.ItemLevel != nil {
item.ItemLevel = csv.ItemLevel
}
if csv.RequiredLevel != nil {
item.RequiredLevel = csv.RequiredLevel
}
// Handle stats count and stats
if csv.StatsCount != nil {
item.StatsCount = csv.StatsCount
} else if csv.StatType1 != nil {
// Calculate stats count if not provided but stats exist
statsCount := 0
if csv.StatType1 != nil && *csv.StatType1 > 0 {
statsCount++
}
if csv.StatType2 != nil && *csv.StatType2 > 0 {
statsCount++
}
if csv.StatType3 != nil && *csv.StatType3 > 0 {
statsCount++
}
if csv.StatType4 != nil && *csv.StatType4 > 0 {
statsCount++
}
if csv.StatType5 != nil && *csv.StatType5 > 0 {
statsCount++
}
if csv.StatType6 != nil && *csv.StatType6 > 0 {
statsCount++
}
if csv.StatType7 != nil && *csv.StatType7 > 0 {
statsCount++
}
if csv.StatType8 != nil && *csv.StatType8 > 0 {
statsCount++
}
if csv.StatType9 != nil && *csv.StatType9 > 0 {
statsCount++
}
if csv.StatType10 != nil && *csv.StatType10 > 0 {
statsCount++
}
item.StatsCount = &statsCount
}
// Override stat types and values
if csv.StatType1 != nil {
item.StatType1 = csv.StatType1
}
if csv.StatValue1 != nil {
item.StatValue1 = csv.StatValue1
}
if csv.StatType2 != nil {
item.StatType2 = csv.StatType2
}
if csv.StatValue2 != nil {
item.StatValue2 = csv.StatValue2
}
if csv.StatType3 != nil {
item.StatType3 = csv.StatType3
}
if csv.StatValue3 != nil {
item.StatValue3 = csv.StatValue3
}
if csv.StatType4 != nil {
item.StatType4 = csv.StatType4
}
if csv.StatValue4 != nil {
item.StatValue4 = csv.StatValue4
}
if csv.StatType5 != nil {
item.StatType5 = csv.StatType5
}
if csv.StatValue5 != nil {
item.StatValue5 = csv.StatValue5
}
if csv.StatType6 != nil {
item.StatType6 = csv.StatType6
}
if csv.StatValue6 != nil {
item.StatValue6 = csv.StatValue6
}
if csv.StatType7 != nil {
item.StatType7 = csv.StatType7
}
if csv.StatValue7 != nil {
item.StatValue7 = csv.StatValue7
}
if csv.StatType8 != nil {
item.StatType8 = csv.StatType8
}
if csv.StatValue8 != nil {
item.StatValue8 = csv.StatValue8
}
if csv.StatType9 != nil {
item.StatType9 = csv.StatType9
}
if csv.StatValue9 != nil {
item.StatValue9 = csv.StatValue9
}
if csv.StatType10 != nil {
item.StatType10 = csv.StatType10
}
if csv.StatValue10 != nil {
item.StatValue10 = csv.StatValue10
}
return item, nil
}

View File

@@ -72,6 +72,31 @@ var dungeonLevels = map[int]int{
668: 80, // Halls of Reflection
}
var raidLevels = map[int]int{
249: 60, // Onyxia's Lair
409: 60, // Molten Core
469: 60, // Blackwing Lair
309: 60, // Zul'Gurub
531: 60, // Ahn'Qiraj Temple
509: 60, // Ruins of Ahn'Qiraj
532: 70, // Karazhan
548: 70, // Serpentshrine Cavern
550: 70, // Tempest Keep
534: 70, // The Battle for Mount Hyjal
564: 70, // Black Temple
565: 70, // Gruul's Lair
568: 70, // Zul'Aman
580: 70, // Sunwell Plateau
533: 80, // Naxxramas
616: 80, // The Eye of Eternity
615: 80, // The Obsidian Sanctum
624: 80, // Vault of Archavon
603: 80, // Ulduar
649: 80, // Trial of the Crusader
631: 80, // Icecrown Citadel
724: 80, // The Ruby Sanctum
}
func (db *MySqlDb) GetDungeons(expansionId int) ([]Dungeon, error) {
dungeons := []Dungeon{}

View File

@@ -2,6 +2,7 @@ package mysql
import (
"errors"
"fmt"
"os"
_ "github.com/go-sql-driver/mysql"
@@ -35,7 +36,13 @@ func Connect(config *MySqlConfig) (*MySqlDb, error) {
connString := config.User + ":" + config.Password + "@tcp(" + config.Host + ")/" + config.Database
client, err := sqlx.Open("mysql", connString)
if err != nil {
return nil, err
return nil, fmt.Errorf("error opening database connection: %w", err)
}
// Verify the connection is actually working
err = client.Ping()
if err != nil {
return nil, fmt.Errorf("error connecting to database: %w", err)
}
MySql = &MySqlDb{client}

View File

@@ -2,7 +2,9 @@ package mysql
import (
"fmt"
"log"
"strconv"
"time"
)
type DbSpell struct {
@@ -53,6 +55,34 @@ func GetSpellFields() string {
return `
ID,
Name_Lang_enUS,
COALESCE(Description_Lang_enUS, '') as Description_Lang_enUS,
COALESCE(AuraDescription_Lang_enUS, '') as AuraDescription_Lang_enUS,
ProcChance,
SpellLevel,
Effect_1,
Effect_2,
Effect_3,
EffectDieSides_1,
EffectDieSides_2,
EffectDieSides_3,
EffectRealPointsPerLevel_1,
EffectRealPointsPerLevel_2,
EffectRealPointsPerLevel_3,
EffectBasePoints_1,
EffectBasePoints_2,
EffectBasePoints_3,
EffectAura_1,
EffectAura_2,
EffectAura_3,
EffectBonusMultiplier_1,
EffectBonusMultiplier_2,
EffectBonusMultiplier_3
`
}
func GetSpellWriteFields() string {
return `
ID,
Name_Lang_enUS,
Description_Lang_enUS,
AuraDescription_Lang_enUS,
ProcChance,
@@ -77,3 +107,132 @@ func GetSpellFields() string {
EffectBonusMultiplier_3
`
}
// Writes a new upgraded spell to the database for the specified table
func (db *MySqlDb) WriteSpell(table string, spell DbSpell) error {
// Get the field names from GetSpellFields
fields := GetSpellWriteFields()
// Construct the SQL insert statement using the spell fields
sql := "INSERT INTO " + table + " (" + fields + ") VALUES (" +
"?, ?, ?, ?, ?, ?, " + // ID, Name_Lang_enUS, Description_Lang_enUS, AuraDescription_Lang_enUS, ProcChance, SpellLevel
"?, ?, ?, " + // Effect_1, Effect_2, Effect_3
"?, ?, ?, " + // EffectDieSides_1, EffectDieSides_2, EffectDieSides_3
"?, ?, ?, " + // EffectRealPointsPerLevel_1, EffectRealPointsPerLevel_2, EffectRealPointsPerLevel_3
"?, ?, ?, " + // EffectBasePoints_1, EffectBasePoints_2, EffectBasePoints_3
"?, ?, ?, " + // EffectAura_1, EffectAura_2, EffectAura_3
"?, ?, ?" + // EffectBonusMultiplier_1, EffectBonusMultiplier_2, EffectBonusMultiplier_3
") ON DUPLICATE KEY UPDATE " +
"Name_Lang_enUS = VALUES(Name_Lang_enUS), " +
"Description_Lang_enUS = VALUES(Description_Lang_enUS), " +
"AuraDescription_Lang_enUS = VALUES(AuraDescription_Lang_enUS), " +
"ProcChance = VALUES(ProcChance), " +
"SpellLevel = VALUES(SpellLevel), " +
"Effect_1 = VALUES(Effect_1), " +
"Effect_2 = VALUES(Effect_2), " +
"Effect_3 = VALUES(Effect_3), " +
"EffectDieSides_1 = VALUES(EffectDieSides_1), " +
"EffectDieSides_2 = VALUES(EffectDieSides_2), " +
"EffectDieSides_3 = VALUES(EffectDieSides_3), " +
"EffectRealPointsPerLevel_1 = VALUES(EffectRealPointsPerLevel_1), " +
"EffectRealPointsPerLevel_2 = VALUES(EffectRealPointsPerLevel_2), " +
"EffectRealPointsPerLevel_3 = VALUES(EffectRealPointsPerLevel_3), " +
"EffectBasePoints_1 = VALUES(EffectBasePoints_1), " +
"EffectBasePoints_2 = VALUES(EffectBasePoints_2), " +
"EffectBasePoints_3 = VALUES(EffectBasePoints_3), " +
"EffectAura_1 = VALUES(EffectAura_1), " +
"EffectAura_2 = VALUES(EffectAura_2), " +
"EffectAura_3 = VALUES(EffectAura_3), " +
"EffectBonusMultiplier_1 = VALUES(EffectBonusMultiplier_1), " +
"EffectBonusMultiplier_2 = VALUES(EffectBonusMultiplier_2), " +
"EffectBonusMultiplier_3 = VALUES(EffectBonusMultiplier_3)"
// Execute the query with all the spell fields as parameters
_, err := db.Exec(sql,
spell.ID,
spell.Name,
spell.Description,
spell.AuraDescription,
spell.ProcChance,
spell.SpellLevel,
spell.Effect1,
spell.Effect2,
spell.Effect3,
spell.EffectDieSides1,
spell.EffectDieSides2,
spell.EffectDieSides3,
spell.EffectRealPointsPerLevel1,
spell.EffectRealPointsPerLevel2,
spell.EffectRealPointsPerLevel3,
spell.EffectBasePoints1,
spell.EffectBasePoints2,
spell.EffectBasePoints3,
spell.EffectAura1,
spell.EffectAura2,
spell.EffectAura3,
spell.EffectBonusMultiplier1,
spell.EffectBonusMultiplier2,
spell.EffectBonusMultiplier3,
)
if err != nil {
log.Printf("Failed to run sql query: %v", sql)
return fmt.Errorf("failed to insert spell into %s: %w", table, err)
}
return nil
}
// CopySpell copies a spell from one table to another with an optional new ID
// This uses a temporary table approach to handle copying ALL fields without having to list them
// If newId is 0, it keeps the original ID
func (db *MySqlDb) CopySpell(sourceTable string, destTable string, spellId int, newId int) error {
// Generate a unique temporary table name using timestamp
tempTableName := fmt.Sprintf("temp_spell_copy_%d", time.Now().UnixNano())
// Create a temporary table with the same structure as the source table
sql := fmt.Sprintf("CREATE TEMPORARY TABLE %s LIKE %s", tempTableName, sourceTable)
_, err := db.Exec(sql)
if err != nil {
return fmt.Errorf("failed to create temporary table: %w", err)
}
// Copy the spell to the temporary table
sql = fmt.Sprintf("INSERT INTO %s SELECT * FROM %s WHERE ID = %d",
tempTableName, sourceTable, spellId)
_, err = db.Exec(sql)
if err != nil {
return fmt.Errorf("failed to copy spell to temporary table: %w", err)
}
// If we need to change the ID, update it in the temporary table
if newId > 0 && newId != spellId {
sql = fmt.Sprintf("UPDATE %s SET ID = %d", tempTableName, newId)
_, err = db.Exec(sql)
if err != nil {
return fmt.Errorf("failed to update spell ID in temporary table: %w", err)
}
}
// Copy from temporary table to destination table
sql = fmt.Sprintf("INSERT INTO %s SELECT * FROM %s", destTable, tempTableName)
_, err = db.Exec(sql)
if err != nil {
return fmt.Errorf("failed to copy spell from temporary table to destination: %w", err)
}
// Drop the temporary table (MySQL automatically drops temporary tables at the end of the session,
// but it's good practice to clean up explicitly)
sql = fmt.Sprintf("DROP TEMPORARY TABLE IF EXISTS %s", tempTableName)
_, err = db.Exec(sql)
if err != nil {
log.Printf("Warning: failed to drop temporary table %s: %v", tempTableName, err)
}
log.Printf("Successfully copied spell %d to %s", spellId, destTable)
if newId > 0 && newId != spellId {
log.Printf(" with new ID: %d", newId)
}
return nil
}

View File

@@ -60,6 +60,43 @@ func (item *Item) SetDifficulty(difficulty int) {
item.Difficulty = difficulty
}
// scaleArmor calculates and updates the item's armor value based on its level, quality, and material subclass.
// It checks for nil pointers for critical scaling fields and valid map keys before performing calculations.
func (item *Item) ScaleArmor(itemLevel int) {
// Ensure critical pointer fields for scaling are non-nil
// Entry and Name are value types from the embedded DbItem and used for logging.
if item.Class == nil || item.Armor == nil || item.Quality == nil || item.Subclass == nil || item.Material == nil {
log.Printf("Item (Entry: %d, Name: '%s'): Cannot scale armor: one or more required pointer fields (Class, Armor, Quality, Subclass, Material) are nil.", item.Entry, item.Name)
return
}
// Scale Armor Stats only if Class is 4 (ITEM_CLASS_ARMOR) and Armor > 0
if *item.Class == 4 && *item.Armor > 0 {
qualityModifier, qOk := config.QualityModifiers[*item.Quality]
// Assuming item.Subclass is the correct key for MaterialModifiers as per original logic
materialModifier, mOk := config.MaterialModifiers[*item.Subclass]
if qOk && mOk {
// preArmor := *item.Armor
scaledArmorValue := math.Ceil(float64(itemLevel) * qualityModifier * materialModifier)
*item.Armor = int(scaledArmorValue)
// log.Printf("Item (Entry: %d, Name: '%s'): Scaled armor to %d (was %d). ItemLevel: %d, Quality: %d (Mod: %.2f), Subclass for MaterialMod: %d (Mod: %.2f). Actual Material field: %d",
// item.Entry, item.Name, *item.Armor, preArmor, itemLevel, *item.Quality, qualityModifier, *item.Subclass, materialModifier, *item.Material)
} else {
var errorMessages []string
if !qOk {
errorMessages = append(errorMessages, fmt.Sprintf("invalid Quality key: %d", *item.Quality))
}
if !mOk {
errorMessages = append(errorMessages, fmt.Sprintf("invalid Subclass key for MaterialModifier: %d", *item.Subclass))
}
log.Printf("Item (Entry: %d, Name: '%s'): Could not scale armor. Issues: %s. Original Armor: %d",
item.Entry, item.Name, strings.Join(errorMessages, "; "), *item.Armor)
}
}
}
// Get the primary stat for an item (strength, agility, intellect, spirit, stamina)
func (item Item) GetPrimaryStat() (int, int, error) {
var primaryStat int64
@@ -192,7 +229,7 @@ func (i Item) GetDpsModifier() (float64, error) {
return (qualityModifier * typeModifier), nil
}
// Get the current expected DPS of the item bsed on the min and max damage and delay
// Get the current expected DPS of the item based on the min and max damage and delay
func (item Item) GetDPS() (float64, error) {
if item.MinDmg1 == nil || item.MaxDmg1 == nil {
@@ -229,9 +266,13 @@ func (item *Item) ScaleDPS(oldLevel, level int) (float64, error) {
dps := modifier * float64(level) * scalingFactor
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)+28))
// Use deterministic values based on item entry instead of random values
// We'll use the item entry to derive consistent min/max modifiers
minMod := 70 // Default mid-range value (was 100-(rand.IntN(15)+22) which is ~70)
maxMod := 135 // Default mid-range value (was 100+(rand.IntN(15)+28) which is ~135)
minimum := adjDps * float64(minMod)
maximum := adjDps * float64(maxMod)
// If the weapon has secondary damage, scale that as well based on the ratio of the primary damage
if *item.MinDmg2 != 0 && *item.MaxDmg2 != 0 {
@@ -254,9 +295,6 @@ func (item *Item) ScaleDPS(oldLevel, level int) (float64, error) {
minimum = math.Ceil(minimum)
maximum = math.Ceil(maximum)
// item.MinDmg1 = &minimum
// var min int = int(minimum)
// var max int = int(maximum)
item.MinDmg1 = &minimum
item.MaxDmg1 = &maximum
@@ -308,6 +346,35 @@ func (item Item) GetStatPercents(spellStats []spells.ConvItemStat) map[int]*Item
return statMap
}
// UpdateSpellID updates a spell ID in the item's spell slots
// It replaces oldSpellId with newSpellId in any of the item's spell slots
func (item *Item) UpdateSpellID(oldSpellId int, newSpellId int) bool {
updated := false
// Check and update each spell slot
if item.SpellId1 != nil && *item.SpellId1 == oldSpellId {
*item.SpellId1 = newSpellId
updated = true
}
if item.SpellId2 != nil && *item.SpellId2 == oldSpellId {
*item.SpellId2 = newSpellId
updated = true
}
if item.SpellId3 != nil && *item.SpellId3 == oldSpellId {
*item.SpellId3 = newSpellId
updated = true
}
// If we updated any spell IDs, clear the cached spells so they'll be reloaded
if updated {
item.Spells = nil
}
return updated
}
// get an array of all the spells set on the item
func (item *Item) GetSpells() ([]spells.Spell, error) {
// dont reload for the same item .
@@ -511,12 +578,7 @@ func (item *Item) ScaleItem(itemLevel int, itemQuality int) (bool, error) {
*item.StatsCount = len(allStats)
// Scale Armor Stats
if *item.Class == 4 && *item.Armor > 0 {
preArmor := *item.Armor
*item.Armor = int(math.Ceil(float64(itemLevel) * config.QualityModifiers[*item.Quality] * config.MaterialModifiers[*item.Subclass]))
log.Printf("New Armor: %v scaled up from previous armor %v material is %v", *item.Armor, preArmor, *item.Material)
}
item.ScaleArmor(itemLevel)
// If the item is a weapon scale the DPS
if *item.Class == 2 && *item.MinDmg1 > 0 {
@@ -555,20 +617,32 @@ func (item *Item) ScaleItem(itemLevel int, itemQuality int) (bool, error) {
// 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)
// Use ForceScaleSpell instead of ScaleSpell to ensure all spells are scaled properly
// Determine tier based on item level
tier := 1
if itemLevel >= 200 {
tier = 5
} else if itemLevel >= 175 {
tier = 4
} else if itemLevel >= 150 {
tier = 3
} else if itemLevel >= 125 {
tier = 2
}
log.Printf("Scaling spell %v (ID: %v) with tier %d modifier", spell.Name, spell.ID, tier)
err := spell.ForceScaleSpell(fromItemLevel, itemLevel, *item.Quality, tier)
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)
// ForceScaleSpell modifies the spell in place, so we use the original spell ID
item.UpdateField(fmt.Sprintf("SpellId%v", spell.ItemSpellSlot), spell.ID)
item.Spells = append(item.Spells, spell)
// do one last check on all setting StatsCount based on how many stats have been set
// 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)
}
@@ -797,6 +871,271 @@ func correctSpellAttackPower(item *Item, allStats map[int]*ItemStat) {
}
}
/**
* This will determine the class type that would be the user of the item
* Melee Strength Attacker: 1
* Melee Agility Attacker: 2
* Ranged Attacker: 3
* Mage: 4
* Healer: 5
* Tank: 6
* Generic: 7 (Could not determine)
* @return int
**/
func (item *Item) GetClassUserType() int {
// loop over the stats and check if any of them are parry, defense, block
for i := 1; i <= 7; i++ {
statTypeField := fmt.Sprintf("StatType%d", i)
statTypePtr, _ := item.GetField(statTypeField)
// Tanking weapons will have defensive stats on them
if statTypePtr == STAT.ParryRating || statTypePtr == STAT.DefenseSkillRating || statTypePtr == STAT.BlockRating || statTypePtr == STAT.BlockValue {
return 6
}
// Check for a healer stats like MP5 and Spell Healing Done
if statTypePtr == STAT.ManaRegeneration || statTypePtr == STAT.SpellHealingDone {
return 5
}
// Check for a Mage stat if they have spell penetration we know it is a mage
if statTypePtr == STAT.SpellPenetration {
return 4
}
if statTypePtr == STAT.RangedAttackPower || statTypePtr == STAT.CritRangedRating || statTypePtr == STAT.HitRangedRating {
return 3
}
}
// For armor we can use the type to determine the class type
if *item.Class == 4 {
// if the item is cloth its a mage and did not have healer stats just treat as a mage item
if *item.Material == 1 && *item.InventoryType != 16 {
return 4
}
// If it is plate and not a tank then it is a strength melee attack
if *item.Material == 4 {
return 1
}
// If it is mail/leather armor then it is limited to Mage, Agility Fighter
if *item.Material == 2 || *item.Material == 3 {
// check for spellpower, spellcrit, spellhit, intellect
for i := 1; i <= 7; i++ {
statTypeField := fmt.Sprintf("StatType%d", i)
statTypePtr, _ := item.GetField(statTypeField)
if statTypePtr == STAT.SpellPower || statTypePtr == STAT.CritSpellRating ||
statTypePtr == STAT.HitSpellRating || statTypePtr == STAT.Intellect || statTypePtr == STAT.Spirit {
return 4
}
}
return 2
}
}
// Do some weapon checks
if *item.Class == 2 {
// If it is a fist weapon or ranged throwing weapons its agility class type
if *item.Subclass == 13 || *item.Subclass == 16 {
return 2
}
if *item.Subclass == 19 {
return 4
}
// if it is a polearm or spear 17 or 6 and strength then its strength class type
if *item.Subclass == 17 || *item.Subclass == 6 {
for i := 1; i <= 7; i++ {
statTypeField := fmt.Sprintf("StatType%d", i)
statTypePtr, _ := item.GetField(statTypeField)
if statTypePtr == STAT.Strength {
return 1
}
// or attack power
if statTypePtr == STAT.AttackPower {
return 1
}
}
// otherwise check for agility
for i := 1; i <= 7; i++ {
statTypeField := fmt.Sprintf("StatType%d", i)
statTypePtr, _ := item.GetField(statTypeField)
if statTypePtr == STAT.Agility {
return 2
}
}
// last assume it is a healer
return 5
}
if *item.Subclass == 2 || *item.Subclass == 3 || *item.Subclass == 18 {
for i := 1; i <= 7; i++ {
statTypeField := fmt.Sprintf("StatType%d", i)
statTypePtr, _ := item.GetField(statTypeField)
if statTypePtr == STAT.Strength {
return 1
}
}
return 3
}
}
// Most specific cases have been addressed now just use the base stats to make a decision for the remaining
for i := 1; i <= 7; i++ {
statTypeField := fmt.Sprintf("StatType%d", i)
statTypePtr, _ := item.GetField(statTypeField)
if statTypePtr == STAT.Spirit {
return 5
}
}
for i := 1; i <= 7; i++ {
statTypeField := fmt.Sprintf("StatType%d", i)
statTypePtr, _ := item.GetField(statTypeField)
if statTypePtr == STAT.Intellect {
return 4
}
}
for i := 1; i <= 7; i++ {
statTypeField := fmt.Sprintf("StatType%d", i)
statTypePtr, _ := item.GetField(statTypeField)
if statTypePtr == STAT.Strength {
return 1
}
}
for i := 1; i <= 7; i++ {
statTypeField := fmt.Sprintf("StatType%d", i)
statTypePtr, _ := item.GetField(statTypeField)
if statTypePtr == STAT.Agility {
return 2
}
}
// If it is attack power melee haste melee crit or anything else then it is a agility
for i := 1; i <= 7; i++ {
statTypeField := fmt.Sprintf("StatType%d", i)
statTypePtr, _ := item.GetField(statTypeField)
if statTypePtr == STAT.AttackPower || statTypePtr == STAT.HasteMeleeRating || statTypePtr == STAT.CritMeleeRating {
return 7
}
}
// If it is spell power spell crit spell hit then it is a mage
for i := 1; i <= 7; i++ {
statTypeField := fmt.Sprintf("StatType%d", i)
statTypePtr, _ := item.GetField(statTypeField)
if statTypePtr == STAT.SpellPower || statTypePtr == STAT.CritSpellRating || statTypePtr == STAT.HitSpellRating {
return 4
}
}
// If it is ranged attack power ranged haste ranged crit or anything else then it is a ranged
for i := 1; i <= 7; i++ {
statTypeField := fmt.Sprintf("StatType%d", i)
statTypePtr, _ := item.GetField(statTypeField)
if statTypePtr == STAT.RangedAttackPower || statTypePtr == STAT.HasteRangedRating || statTypePtr == STAT.CritRangedRating {
return 3
}
}
return 7
}
func (item *Item) ApplyTierModifiers(optionalTier ...int) {
// Use provided tier or default to 0 if not set
var tier int
if len(optionalTier) > 0 {
tier = optionalTier[0]
} else {
tier = 0
}
// Default tier modifier is 1.0 (no modification)
tierModifier := 1.0
// This is a necessary bonus to catch gear up from previous v2 version
catchUpBonus := 1.5
// If tier is valid (1-5), get the modifier from config
if tier > 0 && tier <= 5 {
if mod, ok := config.GearTierModifiers[tier]; ok {
tierModifier = mod
}
}
// Apply tier modifier to all stats on the item
for i := 1; i <= 10; i++ {
// Get the stat type and value fields using reflection
statTypeField := fmt.Sprintf("StatType%d", i)
statValueField := fmt.Sprintf("StatValue%d", i)
// Get the current values
statTypePtr, err1 := item.GetField(statTypeField)
statValuePtr, err2 := item.GetField(statValueField)
// Skip if any errors or if stat type is 0 or stat value is 0
if err1 != nil || err2 != nil || statTypePtr == 0 || statValuePtr == 0 {
continue
}
// Get the stat modifier (inverse of the cost modifier)
statModifier, ok := config.StatModifiers[statTypePtr]
if !ok {
statModifier = 1.0
}
// Inverse of the stat modifier (e.g., 0.5 cost means 2.0 multiplier)
inverseModifier := 1.0
if statModifier > 0 {
inverseModifier = 1.0 / statModifier
}
// Apply tier modifier and stat modifier
newValue := int(float64(statValuePtr) * tierModifier * inverseModifier * catchUpBonus)
// Update the item's stat value
item.UpdateField(statValueField, newValue)
// We've already updated the field directly with UpdateField above
// No need to update StatsMap as we're focusing on the direct stat values
}
// Apply tier modifier to spells
// spells, err := item.GetSpells()
// if err == nil && len(spells) > 0 {
// for i := range spells {
// // Get the item level
// currentLevel := 0
// if item.ItemLevel != nil {
// currentLevel = *item.ItemLevel
// }
// // Get the item quality
// quality := 2 // Default to uncommon
// if item.Quality != nil {
// quality = *item.Quality
// }
// // Scale spells with the tier modifier
// spells[i].ForceScaleSpell(currentLevel, currentLevel, quality, tier)
// }
// // Update the item's spells
// item.Spells = spells
// }
}
func ItemToSql(item Item, reqLevel int, difficulty int) string {
fmt.Printf("-- Required level: %v\n", reqLevel)

View File

@@ -432,6 +432,342 @@ func (s *Spell) ScaleSpell(fromItemLevel int, itemLevel int, itemQuality int) (i
return idBump + s.ID, nil
}
// ForceScaleSpell is an enhanced version of ScaleSpell that scales ANY spell regardless of effect type.
// It applies quality modifiers and special handling for different effect types, using a progression-based
// scaling curve with diminishing returns for large item level differences.
// The tier parameter (1-5) applies an additional modifier from GearTierModifiers plus a 5% bonus.
// Also updates spell names to reflect new values when applicable.
func (s *Spell) ForceScaleSpell(fromItemLevel int, toItemLevel int, itemQuality int, tier ...int) error {
// Store original base point values before scaling
// Add 1 to each value to account for the way WoW stores spell effect values
// (the displayed value is often BasePoints+1)
originalBasePoints1 := s.EffectBasePoints1 + 1
originalBasePoints2 := s.EffectBasePoints2 + 1
originalBasePoints3 := s.EffectBasePoints3 + 1
// Get quality modifier from config package
qualModifier, exists := config.QualityModifiers[itemQuality]
if !exists {
// Default to 1.0 if quality not found
qualModifier = 1.0
}
if itemQuality == 5 {
qualModifier = 1.2 // Legendary
} else {
qualModifier = 1.0
}
tierModifier := 1.00
// Apply tier modifier if provided
if len(tier) > 0 && tier[0] >= 1 && tier[0] <= 5 {
// Get the tier modifier from config
if mod, exists := config.GearTierModifiers[tier[0]]; exists {
tierModifier *= mod // Apply tier modifier on top of base 5% bonus
}
}
// Apply tier modifier to quality modifier
qualModifier *= tierModifier
// Calculate item level difference and ratio
ilevelDiff := toItemLevel - fromItemLevel
// Use a simple linear scaling based on item level ratio
// This provides a straightforward and predictable scaling that's easy to understand
levelRatio := float64(toItemLevel) / float64(fromItemLevel)
// Effect type categorization
directDamageEffects := [...]int{2, 9, 10} // School damage, % weapon damage, etc.
statBuffEffects := [...]int{35, 29, 99, 124} // Apply stat, add flat stat, etc.
periodEffects := [...]int{6, 77} // Apply aura, periodic trigger
// Scale Effect1
if s.EffectBasePoints1 != 0 {
effectMultiplier := 1.0
// Check for attack power and spell power in the description regardless of effect type
if strings.Contains(s.Description, "attack power") ||
strings.Contains(s.Description, "Attack Power") ||
strings.Contains(s.Description, "spell power") ||
strings.Contains(s.Description, "Spell Power") ||
strings.Contains(s.Description, "healing") ||
strings.Contains(s.Description, "Healing") {
effectMultiplier = 2.0 // Higher multiplier for attack/spell power
}
// Determine effect category and apply appropriate multiplier
if s.Effect1 != 0 {
// Direct damage effects scale more aggressively at higher item levels
if funk.Contains(directDamageEffects, s.Effect1) {
effectMultiplier = 2.5 + (float64(toItemLevel) * 0.1 * 0.005) // +0.5% per every 10 item levels
}
// Flat Base Stat modifier for all other stats.
if funk.Contains(statBuffEffects, s.Effect1) && effectMultiplier < 1.5 {
effectMultiplier = 1.45
}
}
// Special handling for aura effects
if s.EffectAura1 != 0 {
// DOT effects (Aura 3: Periodic Damage)
if s.EffectAura1 == 3 && funk.Contains(periodEffects, s.Effect1) {
effectMultiplier = 2.5 + (float64(toItemLevel) * 0.1 * 0.005)
// Scale DOTs more with higher quality items
if itemQuality >= 5 { // Legendary or higher
effectMultiplier += 0.5
}
}
// HOT effects (Aura 8: Periodic Heal)
if s.EffectAura1 == 8 && funk.Contains(periodEffects, s.Effect1) {
effectMultiplier = 2.5 + (float64(toItemLevel) * 0.1 * 0.005)
// Healing scales slightly higher than damage
}
// Damage Shield effects (Aura 15)
if s.EffectAura1 == 15 && funk.Contains(periodEffects, s.Effect1) {
// Damage shields scale with item level difference
effectMultiplier = 1.5 + (float64(toItemLevel) * 0.1 * 0.005)
if effectMultiplier > 2.5 {
effectMultiplier = 2.5 // Cap at 2.5x
}
}
// Proc chance effects (various auras)
if s.ProcChance > 0 && s.ProcChance < 100 {
// For proc effects, we might want to scale the effect more aggressively
// since they don't happen all the time
procFactor := 100.0 / float64(s.ProcChance) // Inverse of proc chance
// Limit the proc factor to avoid excessive scaling
if procFactor > 2.0 {
procFactor = 2.0
}
effectMultiplier += math.Sqrt(procFactor) // Scale by square root of proc factor
}
}
// Special handling for mana restoration
if s.Effect1 == 30 {
if strings.Contains(s.Description, "Mana") || strings.Contains(s.Description, "mana") {
// Mana effects scale with level but with diminishing returns
effectMultiplier = 1.0 + (math.Log10(float64(ilevelDiff+1)) * 0.3)
}
}
// Apply the scaling with the appropriate multiplier
s.EffectBasePoints1 = int(float64(s.EffectBasePoints1) * levelRatio * qualModifier * effectMultiplier)
}
// Scale Effect2 with similar logic
if s.EffectBasePoints2 != 0 {
effectMultiplier := 1.0
// Check for attack power and spell power in the description regardless of effect type
if strings.Contains(s.Description, "attack power") ||
strings.Contains(s.Description, "Attack Power") ||
strings.Contains(s.Description, "spell power") ||
strings.Contains(s.Description, "Spell Power") ||
strings.Contains(s.Description, "healing") ||
strings.Contains(s.Description, "Healing") {
effectMultiplier = 2.0 // Higher multiplier for attack/spell power
}
// Determine effect category and apply appropriate multiplier
if s.Effect2 != 0 {
// Direct damage effects scale more aggressively at higher item levels
if funk.Contains(directDamageEffects, s.Effect2) {
// Scale damage more aggressively for higher item levels
effectMultiplier = 2.5 + (float64(toItemLevel) * 0.1 * 0.005) // +0.5% per every 10 item levels
}
// Flat Base Stat modifier for all other stats.
if funk.Contains(statBuffEffects, s.Effect2) && effectMultiplier < 1.5 {
effectMultiplier = 1.45
}
}
// Special handling for aura effects
if s.EffectAura2 != 0 {
// DOT effects (Aura 3: Periodic Damage)
if s.EffectAura2 == 3 && funk.Contains(periodEffects, s.Effect2) {
effectMultiplier = 2.5 + (float64(toItemLevel) * 0.1 * 0.005)
// Scale DOTs more with higher quality items
if itemQuality >= 5 { // Legendary or higher
effectMultiplier += 0.5
}
}
// HOT effects (Aura 8: Periodic Heal)
if s.EffectAura2 == 8 && funk.Contains(periodEffects, s.Effect2) {
effectMultiplier = 2.5 + (float64(toItemLevel) * 0.1 * 0.005)
// Healing scales slightly higher than damage
}
// Damage Shield effects (Aura 15)
if s.EffectAura2 == 15 && funk.Contains(periodEffects, s.Effect2) {
// Damage shields scale with item level difference
effectMultiplier = 1.5 + (float64(toItemLevel) * 0.1 * 0.005)
if effectMultiplier > 2.5 {
effectMultiplier = 2.5 // Cap at 2.5x
}
}
// Proc chance effects (various auras)
if s.ProcChance > 0 && s.ProcChance < 100 {
// For proc effects, we might want to scale the effect more aggressively
// since they don't happen all the time
procFactor := 100.0 / float64(s.ProcChance) // Inverse of proc chance
// Limit the proc factor to avoid excessive scaling
if procFactor > 2.0 {
procFactor = 2.0
}
effectMultiplier += math.Sqrt(procFactor) // Scale by square root of proc factor
}
}
// Special handling for mana restoration
if s.Effect2 == 30 {
if strings.Contains(s.Description, "Mana") || strings.Contains(s.Description, "mana") {
// Mana effects scale with level but with diminishing returns
effectMultiplier = 1.0 + (math.Log10(float64(ilevelDiff+1)) * 0.3)
}
}
// Apply the scaling with the appropriate multiplier
s.EffectBasePoints2 = int(float64(s.EffectBasePoints2) * levelRatio * qualModifier * effectMultiplier)
}
// Scale Effect3 with similar logic
if s.EffectBasePoints3 != 0 {
effectMultiplier := 1.0
// Check for attack power and spell power in the description regardless of effect type
if strings.Contains(s.Description, "attack power") ||
strings.Contains(s.Description, "Attack Power") ||
strings.Contains(s.Description, "spell power") ||
strings.Contains(s.Description, "Spell Power") ||
strings.Contains(s.Description, "healing") ||
strings.Contains(s.Description, "Healing") {
effectMultiplier = 2.0 // Higher multiplier for attack/spell power
}
// Determine effect category and apply appropriate multiplier
if s.Effect3 != 0 {
// Direct damage effects scale more aggressively at higher item levels
if funk.Contains(directDamageEffects, s.Effect3) {
// Scale damage more aggressively for higher item levels
effectMultiplier = 2.5 + (float64(toItemLevel) * 0.1 * 0.005) // +0.5% per every 10 item levels
}
// Flat Base Stat modifier for all other stats.
if funk.Contains(statBuffEffects, s.Effect3) && effectMultiplier < 1.5 {
effectMultiplier = 1.45
}
}
// Special handling for aura effects
if s.EffectAura3 != 0 {
// DOT effects (Aura 3: Periodic Damage)
if s.EffectAura3 == 3 && funk.Contains(periodEffects, s.Effect3) {
effectMultiplier = 2.5 + (float64(toItemLevel) * 0.1 * 0.005)
// Scale DOTs more with higher quality items
if itemQuality >= 5 { // Legendary or higher
effectMultiplier += 0.5
}
}
// HOT effects (Aura 8: Periodic Heal)
if s.EffectAura3 == 8 && funk.Contains(periodEffects, s.Effect3) {
effectMultiplier = 2.5 + (float64(toItemLevel) * 0.1 * 0.005)
// Healing scales slightly higher than damage
}
// Damage Shield effects (Aura 15)
if s.EffectAura3 == 15 && funk.Contains(periodEffects, s.Effect3) {
// Damage shields scale with item level difference
effectMultiplier = 1.5 + (float64(toItemLevel) * 0.1 * 0.005)
if effectMultiplier > 2.5 {
effectMultiplier = 2.5 // Cap at 2.5x
}
}
// Proc chance effects (various auras)
if s.ProcChance > 0 && s.ProcChance < 100 {
// For proc effects, we might want to scale the effect more aggressively
// since they don't happen all the time
procFactor := 100.0 / float64(s.ProcChance) // Inverse of proc chance
// Limit the proc factor to avoid excessive scaling
if procFactor > 2.0 {
procFactor = 2.0
}
effectMultiplier += math.Sqrt(procFactor) // Scale by square root of proc factor
}
}
// Special handling for mana restoration
if s.Effect3 == 30 {
if strings.Contains(s.Description, "Mana") || strings.Contains(s.Description, "mana") {
// Mana effects scale with level but with diminishing returns
effectMultiplier = 1.0 + (math.Log10(float64(ilevelDiff+1)) * 0.3)
}
}
// Apply the scaling with the appropriate multiplier
s.EffectBasePoints3 = int(float64(s.EffectBasePoints3) * levelRatio * qualModifier * effectMultiplier)
}
// Update spell name with new values if it contains the old values
// This helps keep the spell name in sync with the actual effect values
newValues := []int{
s.EffectBasePoints1 + 1, // Add 1 to match WoW's display format
s.EffectBasePoints2 + 1,
s.EffectBasePoints3 + 1,
}
// Store the original name before any modifications
originalName := s.Name
// Try different formats of the number that might appear in the name
for i, oldValue := range []int{originalBasePoints1, originalBasePoints2, originalBasePoints3} {
if oldValue <= 1 { // Skip if original value is 0 or 1 (likely not meaningful)
continue
}
newValue := newValues[i]
if newValue <= 1 { // Skip if new value is 0 or 1 (likely not meaningful)
continue
}
// Skip if values are the same (no change needed)
if oldValue == newValue {
continue
}
// Convert both old and new values to strings
oldValueStr := fmt.Sprintf("%d", oldValue)
newValueStr := fmt.Sprintf("%d", newValue)
// Only replace if the old value actually appears in the name
if strings.Contains(s.Name, oldValueStr) {
s.Name = strings.Replace(s.Name, oldValueStr, newValueStr, -1)
//log.Printf("Updated ID %d spell name from '%s' to '%s' (replaced %s with %s)",
// s.ID, originalName, s.Name, oldValueStr, newValueStr)
}
// Also try with a plus sign (e.g., "+15" in name)
oldValueWithPlus := "+" + oldValueStr
newValueWithPlus := "+" + newValueStr
if strings.Contains(s.Name, oldValueWithPlus) {
s.Name = strings.Replace(s.Name, oldValueWithPlus, newValueWithPlus, -1)
log.Printf("Updated spell name from '%s' to '%s' (replaced %s with %s)",
originalName, s.Name, oldValueWithPlus, newValueWithPlus)
}
}
s.Scaled = true
return nil
}
func SpellToSql(spell Spell, quality int) string {
entryBump := 30000000
@@ -514,6 +850,42 @@ func SpellToSql(spell Spell, quality int) string {
MaxTargetLevel, SpellClassSet, SpellClassMask_1, SpellClassMask_2, SpellClassMask_3, MaxTargets, DefenseType, PreventionType, StanceBarOrder,
EffectChainAmplitude_1, EffectChainAmplitude_2, EffectChainAmplitude_3, MinFactionID, MinReputation, RequiredAuraVision, RequiredTotemCategoryID_1,
RequiredTotemCategoryID_2, RequiredAreasID, SchoolMask, RuneCostID, SpellMissileID, PowerDisplayID, EffectBonusMultiplier_1, EffectBonusMultiplier_2,
EffectBonusMultiplier_3, SpellDescriptionVariableID, SpellDifficultyID
) SELECT
ID + %v, Category, DispelType, Mechanic, Attributes, AttributesEx, AttributesEx2, AttributesEx3, AttributesEx4,
AttributesEx5, AttributesEx6, AttributesEx7, ShapeshiftMask, unk_320_2, ShapeshiftExclude, unk_320_3, Targets,
TargetCreatureType, RequiresSpellFocus, FacingCasterFlags, CasterAuraState, TargetAuraState, ExcludeCasterAuraState,
ExcludeTargetAuraState, CasterAuraSpell, TargetAuraSpell, ExcludeCasterAuraSpell, ExcludeTargetAuraSpell, CastingTimeIndex,
RecoveryTime, CategoryRecoveryTime, InterruptFlags, AuraInterruptFlags, ChannelInterruptFlags, ProcTypeMask, ProcChance,
ProcCharges, MaxLevel, BaseLevel, SpellLevel, DurationIndex, PowerType, ManaCost, ManaCostPerLevel, ManaPerSecond,
ManaPerSecondPerLevel, RangeIndex, Speed, ModalNextSpell, CumulativeAura, Totem_1, Totem_2, Reagent_1, Reagent_2, Reagent_3,
Reagent_4, Reagent_5, Reagent_6, Reagent_7, Reagent_8, ReagentCount_1, ReagentCount_2, ReagentCount_3, ReagentCount_4,
ReagentCount_5, ReagentCount_6, ReagentCount_7, ReagentCount_8, EquippedItemClass, EquippedItemSubclass, EquippedItemInvTypes,
Effect_1, Effect_2, Effect_3, EffectDieSides_1, EffectDieSides_2, EffectDieSides_3, EffectRealPointsPerLevel_1,
EffectRealPointsPerLevel_2, EffectRealPointsPerLevel_3, EffectBasePoints_1, EffectBasePoints_2, EffectBasePoints_3,
EffectMechanic_1, EffectMechanic_2, EffectMechanic_3, ImplicitTargetA_1, ImplicitTargetA_2, ImplicitTargetA_3, ImplicitTargetB_1,
ImplicitTargetB_2, ImplicitTargetB_3, EffectRadiusIndex_1, EffectRadiusIndex_2, EffectRadiusIndex_3, EffectAura_1,
EffectAura_2, EffectAura_3, EffectAuraPeriod_1, EffectAuraPeriod_2, EffectAuraPeriod_3, EffectMultipleValue_1, EffectMultipleValue_2,
EffectMultipleValue_3, EffectChainTargets_1, EffectChainTargets_2, EffectChainTargets_3, EffectItemType_1, EffectItemType_2,
EffectItemType_3, EffectMiscValue_1, EffectMiscValue_2, EffectMiscValue_3, EffectMiscValueB_1, EffectMiscValueB_2, EffectMiscValueB_3,
EffectTriggerSpell_1, EffectTriggerSpell_2, EffectTriggerSpell_3, EffectPointsPerCombo_1, EffectPointsPerCombo_2, EffectPointsPerCombo_3,
EffectSpellClassMaskA_1, EffectSpellClassMaskA_2, EffectSpellClassMaskA_3, EffectSpellClassMaskB_1, EffectSpellClassMaskB_2,
EffectSpellClassMaskB_3, EffectSpellClassMaskC_1, EffectSpellClassMaskC_2, EffectSpellClassMaskC_3, SpellVisualID_1, SpellVisualID_2,
SpellIconID, ActiveIconID, SpellPriority, Name_Lang_enUS, Name_Lang_enGB, Name_Lang_koKR, Name_Lang_frFR, Name_Lang_deDE,
Name_Lang_enCN, Name_Lang_zhCN, Name_Lang_enTW, Name_Lang_zhTW, Name_Lang_esES, Name_Lang_esMX, Name_Lang_ruRU, Name_Lang_ptPT,
Name_Lang_ptBR, Name_Lang_itIT, Name_Lang_Unk, Name_Lang_Mask, NameSubtext_Lang_enUS, NameSubtext_Lang_enGB, NameSubtext_Lang_koKR,
NameSubtext_Lang_frFR, NameSubtext_Lang_deDE, NameSubtext_Lang_enCN, NameSubtext_Lang_zhCN, NameSubtext_Lang_enTW, NameSubtext_Lang_zhTW,
NameSubtext_Lang_esES, NameSubtext_Lang_esMX, NameSubtext_Lang_ruRU, NameSubtext_Lang_ptPT, NameSubtext_Lang_ptBR, NameSubtext_Lang_itIT,
NameSubtext_Lang_Unk, NameSubtext_Lang_Mask, Description_Lang_enUS, Description_Lang_enGB, Description_Lang_koKR, Description_Lang_frFR,
Description_Lang_deDE, Description_Lang_enCN, Description_Lang_zhCN, Description_Lang_enTW, Description_Lang_zhTW, Description_Lang_esES,
Description_Lang_esMX, Description_Lang_ruRU, Description_Lang_ptPT, Description_Lang_ptBR, Description_Lang_itIT, Description_Lang_Unk,
Description_Lang_Mask, AuraDescription_Lang_enUS, AuraDescription_Lang_enGB, AuraDescription_Lang_koKR, AuraDescription_Lang_frFR,
AuraDescription_Lang_deDE, AuraDescription_Lang_enCN, AuraDescription_Lang_zhCN, AuraDescription_Lang_enTW, AuraDescription_Lang_zhTW,
AuraDescription_Lang_esES, AuraDescription_Lang_esMX, AuraDescription_Lang_ruRU, AuraDescription_Lang_ptPT, AuraDescription_Lang_ptBR,
AuraDescription_Lang_itIT, AuraDescription_Lang_Unk, AuraDescription_Lang_Mask, ManaCostPct, StartRecoveryCategory, StartRecoveryTime,
MaxTargetLevel, SpellClassSet, SpellClassMask_1, SpellClassMask_2, SpellClassMask_3, MaxTargets, DefenseType, PreventionType, StanceBarOrder,
EffectChainAmplitude_1, EffectChainAmplitude_2, EffectChainAmplitude_3, MinFactionID, MinReputation, RequiredAuraVision, RequiredTotemCategoryID_1,
RequiredTotemCategoryID_2, RequiredAreasID, SchoolMask, RuneCostID, SpellMissileID, PowerDisplayID, EffectBonusMultiplier_1, EffectBonusMultiplier_2,
EffectBonusMultiplier_3, SpellDescriptionVariableID, SpellDifficultyID from acore_world.spell_dbc as src
WHERE src.ID = %v ON DUPLICATE KEY UPDATE ID = src.ID + %v;`, entryBump, spell.ID, entryBump)