Major changes and refactor of all code to break things out more and add in more testing

This commit is contained in:
2024-08-21 02:08:46 -04:00
parent dc35137e19
commit c2281e0c19
21 changed files with 974 additions and 331 deletions

View File

@@ -0,0 +1,106 @@
package creatures
import (
"errors"
"fmt"
"github.com/araxiaonline/endgame-item-generator/import/utils"
_ "github.com/go-sql-driver/mysql"
_ "github.com/jmoiron/sqlx"
)
type Boss struct {
Entry int
Name string
ScriptName string `db:"ScriptName"`
ExperienceModifier int `db:"ExperienceModifier"`
}
func (db Database) GetBosses(mapId int) ([]Boss, error) {
if mapId == 0 {
return nil, errors.New("mapId cannot be 0")
}
bosses := []Boss{}
var sql string
// 540 is pre-classic dungeons so XP Multiplier is best way to determine bosses / rare mobs
if mapId < 540 {
sql = `
SELECT ct.entry, ct.name, ct.ScriptName, ct.ExperienceModifier from acore_world.creature c
JOIN acore_world.creature_template ct ON(c.id1 = ct.entry) WHERE map = ? and ExperienceModifier >= 2;
`
} else {
sql = `
SELECT ct.entry, ct.name, ct.ScriptName, ct.ExperienceModifier from acore_world.creature c
JOIN acore_world.creature_template ct ON(c.id1 = ct.entry) WHERE map = ? and ct.ScriptName Like 'boss_%'
`
}
err := db.client.Select(&bosses, sql, mapId)
if err != nil {
return nil, err
}
return bosses, nil
}
func (db Database) GetBossLoot(bossId int) ([]Item, error) {
if bossId == 0 {
return nil, errors.New("bossId cannot be 0")
}
// This will first find items that are not in the reference boss loot table
items := []Item{}
sql := `
SELECT ` + utils.GetItemFields("") + `
from acore_world.item_template
where
entry in
(SELECT item from acore_world.creature_loot_template where entry = ? and GroupId != 0 and Reference = 0)
and Quality > 2
`
udb := db.client.Unsafe()
err := udb.Select(&items, sql, bossId)
if err != nil {
return nil, err
}
// Get all the boss reference items now
var references []int
sql = `
SELECT reference
FROM acore_world.creature_loot_template
WHERE entry = ? AND Reference != 0
`
err = db.client.Select(&references, sql, bossId)
if err != nil {
return nil, fmt.Errorf("failed to get references: %v sql %s", err, sql)
}
if len(references) == 0 {
return items, nil
}
refItems := []Item{}
// For each reference we now need to get the items and add them to the items slice
for _, ref := range references {
sql = `
SELECT ` + utils.GetItemFields("it") + `
FROM acore_world.reference_loot_template rlt
JOIN acore_world.item_template it ON rlt.Item = it.entry
WHERE rlt.Entry = ? and it.Quality > 2 and it.StatsCount > 0
`
err = db.client.Select(&refItems, sql, ref)
if err != nil {
return nil, fmt.Errorf("failed to get ref items: %v sql %s", err, sql)
}
items = append(items, refItems...)
}
return items, nil
}

450
internal/items/item_test.go Normal file
View File

@@ -0,0 +1,450 @@
package items
import (
"io"
"log"
"math"
"reflect"
"testing"
"golang.org/x/exp/rand"
)
func TestGetPrimaryStat(t *testing.T) {
originalLog := log.Writer()
log.SetOutput(io.Discard)
defer log.SetOutput(originalLog)
tests := []struct {
name string
item Item
wantStat int
wantValue int
expectError bool
}{
{
name: "No primary stat found",
item: Item{
Entry: 1,
Name: "Test Item",
StatType1: ptrInt(1), StatValue1: ptrInt(10),
StatType2: ptrInt(2), StatValue2: ptrInt(20),
StatType3: ptrInt(12), StatValue3: ptrInt(15),
},
wantStat: 0,
wantValue: 0,
expectError: false,
},
{
name: "Primary stat found with higher value",
item: Item{
Entry: 1,
Name: "Test Item",
StatType1: ptrInt(3), StatValue1: ptrInt(10), // Agility
StatType2: ptrInt(4), StatValue2: ptrInt(20), // Strength
StatType3: ptrInt(5), StatValue3: ptrInt(15), // Intellect
},
wantStat: 4, // Strength
wantValue: 20,
expectError: false,
},
{
name: "Primary stat found with lower value",
item: Item{
Entry: 1,
Name: "Test Item",
StatType1: ptrInt(3), StatValue1: ptrInt(30), // Agility
StatType2: ptrInt(4), StatValue2: ptrInt(20), // Strength
StatType3: ptrInt(5), StatValue3: ptrInt(15), // Intellect
},
wantStat: 3, // Agility
wantValue: 30,
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotStat, gotValue, err := tt.item.GetPrimaryStat()
if (err != nil) != tt.expectError {
t.Errorf("GetPrimaryStat() error = %v, expectError %v", err, tt.expectError)
return
}
if gotStat != tt.wantStat {
t.Errorf("GetPrimaryStat() gotStat = %v, want %v", gotStat, tt.wantStat)
}
if gotValue != tt.wantValue {
t.Errorf("GetPrimaryStat() gotValue = %v, want %v", gotValue, tt.wantValue)
}
})
}
}
func TestGetStatList(t *testing.T) {
originalLog := log.Writer()
log.SetOutput(io.Discard)
defer log.SetOutput(originalLog)
tests := []struct {
name string
item Item
want []int
expectError bool
}{
{
name: "No stats available",
item: Item{
Entry: 1,
Name: "Test Item",
StatType1: ptrInt(0), StatValue1: ptrInt(0),
StatType2: ptrInt(0), StatValue2: ptrInt(0),
},
want: []int{},
expectError: false,
},
{
name: "Multiple stats available",
item: Item{
Entry: 1,
Name: "Test Item",
StatType1: ptrInt(3), StatValue1: ptrInt(10), // Agility
StatType2: ptrInt(4), StatValue2: ptrInt(20), // Strength
StatType3: ptrInt(5), StatValue3: ptrInt(15), // Intellect
},
want: []int{3, 4, 5},
expectError: false,
},
{
name: "Stats are ordered correctly",
item: Item{
Entry: 1,
Name: "Test Item",
StatType1: ptrInt(7), StatValue1: ptrInt(10), // Agility
StatType2: ptrInt(4), StatValue2: ptrInt(20), // Strength
StatType3: ptrInt(31), StatValue3: ptrInt(15), // Intellect
},
want: []int{4, 7, 31},
expectError: false,
},
{
name: "Some stats are zero",
item: Item{
Entry: 1,
Name: "Test Item",
StatType1: ptrInt(3), StatValue1: ptrInt(0), // Agility
StatType2: ptrInt(4), StatValue2: ptrInt(20), // Strength
StatType3: ptrInt(5), StatValue3: ptrInt(0), // Intellect
},
want: []int{4},
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.item.GetStatList()
if (err != nil) != tt.expectError {
t.Errorf("GetStatList() error = %v, expectError %v", err, tt.expectError)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetStatList() got = %v, want %v", got, tt.want)
}
})
}
}
func TestGetDPS(t *testing.T) {
tests := []struct {
name string
item Item
wantDPS float64
expectError bool
}{
{
name: "Valid DPS calculation",
item: Item{
MinDmg1: ptrInt(50),
MaxDmg1: ptrInt(70),
Delay: ptrFloat64(3000),
},
wantDPS: 20.00,
expectError: false,
},
{
name: "High damage DPS calculation",
item: Item{
MinDmg1: ptrInt(100),
MaxDmg1: ptrInt(150),
Delay: ptrFloat64(2000),
},
wantDPS: 62.50,
expectError: false,
},
{
name: "Low damage DPS calculation",
item: Item{
MinDmg1: ptrInt(10),
MaxDmg1: ptrInt(15),
Delay: ptrFloat64(1500),
},
wantDPS: 8.33,
expectError: false,
},
{
name: "Missing MinDmg1",
item: Item{
MaxDmg1: ptrInt(70),
Delay: ptrFloat64(3000),
},
wantDPS: 0,
expectError: true,
},
{
name: "Missing MaxDmg1",
item: Item{
MinDmg1: ptrInt(50),
Delay: ptrFloat64(3000),
},
wantDPS: 0,
expectError: true,
},
{
name: "Missing Delay",
item: Item{
MinDmg1: ptrInt(50),
MaxDmg1: ptrInt(70),
},
wantDPS: 0,
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotDPS, err := tt.item.GetDPS()
if (err != nil) != tt.expectError {
t.Errorf("GetDPS() error = %v, expectError %v", err, tt.expectError)
return
}
if !tt.expectError && !almostEqual(gotDPS, tt.wantDPS, 0.01) {
t.Errorf("GetDPS() = %v, want %v", gotDPS, tt.wantDPS)
}
})
}
}
func TestScaleDPS(t *testing.T) {
tests := []struct {
name string
item Item
level int
wantDPSMin float64
wantDPSMax float64
expectError bool
}{
{
name: "Valid Scale DPS calculation",
item: Item{
ItemLevel: ptrInt(60),
Delay: ptrFloat64(3000),
MinDmg1: ptrInt(50),
MaxDmg1: ptrInt(70),
Subclass: ptrInt(4), // One-handed weapon
Quality: ptrInt(3), // Rare
},
level: 70,
wantDPSMin: 53.0, // Expected DPS range due to randomness
wantDPSMax: 107.0,
expectError: false,
},
{
name: "High level Scale DPS calculation",
item: Item{
ItemLevel: ptrInt(80),
Delay: ptrFloat64(2000),
MinDmg1: ptrInt(150),
MaxDmg1: ptrInt(200),
Subclass: ptrInt(17), // Two-handed weapon
Quality: ptrInt(4), // Epic
},
level: 100,
wantDPSMin: 120.0, // Expected DPS range due to randomness
wantDPSMax: 240.0,
expectError: false,
},
{
name: "Low level Scale DPS calculation",
item: Item{
ItemLevel: ptrInt(20),
Delay: ptrFloat64(1000),
MinDmg1: ptrInt(30),
MaxDmg1: ptrInt(50),
Subclass: ptrInt(2), // Ranged weapon
Quality: ptrInt(2), // Uncommon
},
level: 25,
wantDPSMin: 21.0, // Expected DPS range due to randomness
wantDPSMax: 42.0,
expectError: false,
},
{
name: "Missing ItemLevel",
item: Item{
Delay: ptrFloat64(3000),
MinDmg1: ptrInt(50),
MaxDmg1: ptrInt(70),
Subclass: ptrInt(4), // One-handed weapon
Quality: ptrInt(3), // Rare
},
level: 70,
wantDPSMin: 0,
wantDPSMax: 0,
expectError: true,
},
{
name: "Missing Delay",
item: Item{
ItemLevel: ptrInt(60),
MinDmg1: ptrInt(50),
MaxDmg1: ptrInt(70),
Subclass: ptrInt(4), // One-handed weapon
Quality: ptrInt(3), // Rare
},
level: 70,
wantDPSMin: 0,
wantDPSMax: 0,
expectError: true,
},
{
name: "Secondary damage scaling",
item: Item{
ItemLevel: ptrInt(60),
Delay: ptrFloat64(3000),
MinDmg1: ptrInt(50),
MaxDmg1: ptrInt(70),
MinDmg2: ptrInt(25),
MaxDmg2: ptrInt(35),
Subclass: ptrInt(4), // One-handed weapon
Quality: ptrInt(3), // Rare
},
level: 70,
wantDPSMin: 53.0, // Expected DPS range due to randomness
wantDPSMax: 107.0,
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Seed the random number generator for consistent test results
rand.Seed(1)
gotDPS, err := tt.item.ScaleDPS(tt.level)
if (err != nil) != tt.expectError {
t.Errorf("ScaleDPS() error = %v, expectError %v", err, tt.expectError)
return
}
if !tt.expectError && (gotDPS < tt.wantDPSMin || gotDPS > tt.wantDPSMax) {
t.Errorf("ScaleDPS() = %v, want between %v and %v", gotDPS, tt.wantDPSMin, tt.wantDPSMax)
}
})
}
}
func TestGetDpsModifier(t *testing.T) {
tests := []struct {
name string
item Item
wantModifier float64
expectError bool
}{
{
name: "Valid one-handed weapon modifier",
item: Item{
Subclass: ptrInt(4), // One-handed weapon
Quality: ptrInt(3), // Rare
},
wantModifier: 0.64 * 1.38,
expectError: false,
},
{
name: "Valid two-handed weapon modifier",
item: Item{
Subclass: ptrInt(17), // Two-handed weapon
Quality: ptrInt(4), // Epic
},
wantModifier: 0.80 * 1.5,
expectError: false,
},
{
name: "Valid ranged weapon modifier",
item: Item{
Subclass: ptrInt(2), // Ranged weapon
Quality: ptrInt(2), // Uncommon
},
wantModifier: 0.70 * 1.25,
expectError: false,
},
{
name: "Valid wand modifier",
item: Item{
Subclass: ptrInt(19), // Wand
Quality: ptrInt(4), // Epic
},
wantModifier: 0.70 * 1.5,
expectError: false,
},
{
name: "Invalid subclass",
item: Item{
Subclass: ptrInt(99), // Invalid subclass
Quality: ptrInt(3), // Rare
},
wantModifier: 0,
expectError: true,
},
{
name: "Missing subclass",
item: Item{
Quality: ptrInt(3), // Rare
},
wantModifier: 0,
expectError: true,
},
{
name: "Missing quality",
item: Item{
Subclass: ptrInt(4), // One-handed weapon
},
wantModifier: 0,
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotModifier, err := tt.item.GetDpsModifier()
if (err != nil) != tt.expectError {
t.Errorf("GetDpsModifier() error = %v, expectError %v", err, tt.expectError)
return
}
if !tt.expectError && !almostEqual(gotModifier, tt.wantModifier, 0.01) {
t.Errorf("GetDpsModifier() = %v, want %v", gotModifier, tt.wantModifier)
}
})
}
}
// Helper function to return a pointer to an int
func ptrInt(i int) *int {
return &i
}
func ptrFloat64(f float64) *float64 {
return &f
}
func almostEqual(a, b, tolerance float64) bool {
return math.Abs(a-b) <= tolerance
}

742
internal/items/items.go Normal file
View File

@@ -0,0 +1,742 @@
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
}

View File

@@ -0,0 +1,160 @@
package items
import (
"fmt"
)
func GetItemFields(prefix string) string {
pre := ""
if prefix != "" {
pre = prefix + "."
}
return `
` + pre + `entry, ` + pre + `name, ` + pre + `displayid,
quality, ItemLevel, class, subclass, inventoryType,
allowableClass, allowableRace,
armor,material,
requiredSkill, requiredLevel,
dmg_min1, dmg_max1,
dmg_min2,dmg_max2,
dmg_type1, dmg_type2,
delay, sheath, MaxDurability,
statsCount,
stat_type1, stat_value1,
stat_type2, stat_value2,
stat_type3, stat_value3,
stat_type4, stat_value4,
stat_type5, stat_value5,
stat_type6, stat_value6,
stat_type7, stat_value7,
stat_type8, stat_value8,
stat_type9, stat_value9,
stat_type10, stat_value10,
spellid_1, spellid_2, spellid_3,
spelltrigger_1, spelltrigger_2, spelltrigger_3`
}
func ItemToSql(item Item, reqLevel int, difficulty int) string {
entryBump := 20000000
spellBump := 30000000
if difficulty == 4 {
entryBump = 21000000
}
if difficulty == 5 {
entryBump = 22000000
}
if *item.Quality == 4 {
spellBump = 31000000
}
if *item.Quality == 5 {
spellBump = 32000000
}
spells := ""
if len(item.Spells) > 0 {
for i, spell := range item.Spells {
spells += spells.SpellToSql(spell, *item.Quality)
item.UpdateField(fmt.Sprintf("SpellId%v", i), spellBump+spell.ID)
}
}
delete := fmt.Sprintf("DELETE FROM acore_world.item_template WHERE entry = %v;", entryBump+item.Entry)
clone := fmt.Sprintf(`
INSERT INTO acore_world.item_template (
entry, class, subclass, SoundOverrideSubclass, name, displayid, Quality, Flags, FlagsExtra, BuyCount,
BuyPrice, SellPrice, InventoryType, AllowableClass, AllowableRace, ItemLevel, RequiredLevel,
RequiredSkill, RequiredSkillRank, requiredspell, requiredhonorrank, RequiredCityRank,
RequiredReputationFaction, RequiredReputationRank, maxcount, stackable, ContainerSlots, StatsCount,
stat_type1, stat_value1, stat_type2, stat_value2, stat_type3, stat_value3, stat_type4, stat_value4,
stat_type5, stat_value5, stat_type6, stat_value6, stat_type7, stat_value7, stat_type8, stat_value8,
stat_type9, stat_value9, stat_type10, stat_value10, ScalingStatDistribution, ScalingStatValue,
dmg_min1, dmg_max1, dmg_type1, dmg_min2, dmg_max2, dmg_type2, armor, holy_res, fire_res, nature_res,
frost_res, shadow_res, arcane_res, delay, ammo_type, RangedModRange, spellid_1, spelltrigger_1,
spellcharges_1, spellppmRate_1, spellcooldown_1, spellcategory_1, spellcategorycooldown_1, spellid_2,
spelltrigger_2, spellcharges_2, spellppmRate_2, spellcooldown_2, spellcategory_2, spellcategorycooldown_2,
spellid_3, spelltrigger_3, spellcharges_3, spellppmRate_3, spellcooldown_3, spellcategory_3,
spellcategorycooldown_3, spellid_4, spelltrigger_4, spellcharges_4, spellppmRate_4, spellcooldown_4,
spellcategory_4, spellcategorycooldown_4, spellid_5, spelltrigger_5, spellcharges_5, spellppmRate_5,
spellcooldown_5, spellcategory_5, spellcategorycooldown_5, bonding, description, PageText, LanguageID,
PageMaterial, startquest, lockid, Material, sheath, RandomProperty, RandomSuffix, block, itemset,
MaxDurability, area, Map, BagFamily, TotemCategory, socketColor_1, socketContent_1, socketColor_2,
socketContent_2, socketColor_3, socketContent_3, socketBonus, GemProperties, RequiredDisenchantSkill,
ArmorDamageModifier, duration, ItemLimitCategory, HolidayId, ScriptName, DisenchantID, FoodType,
minMoneyLoot, maxMoneyLoot, flagsCustom, VerifiedBuild
)
SELECT
entry + %v, class, subclass, SoundOverrideSubclass, name, displayid, Quality, Flags, FlagsExtra, BuyCount,
BuyPrice, SellPrice, InventoryType, AllowableClass, AllowableRace, ItemLevel, RequiredLevel,
RequiredSkill, RequiredSkillRank, requiredspell, requiredhonorrank, RequiredCityRank,
RequiredReputationFaction, RequiredReputationRank, maxcount, stackable, ContainerSlots, StatsCount,
stat_type1, stat_value1, stat_type2, stat_value2, stat_type3, stat_value3, stat_type4, stat_value4,
stat_type5, stat_value5, stat_type6, stat_value6, stat_type7, stat_value7, stat_type8, stat_value8,
stat_type9, stat_value9, stat_type10, stat_value10, ScalingStatDistribution, ScalingStatValue,
dmg_min1, dmg_max1, dmg_type1, dmg_min2, dmg_max2, dmg_type2, armor, holy_res, fire_res, nature_res,
frost_res, shadow_res, arcane_res, delay, ammo_type, RangedModRange, spellid_1, spelltrigger_1,
spellcharges_1, spellppmRate_1, spellcooldown_1, spellcategory_1, spellcategorycooldown_1, spellid_2,
spelltrigger_2, spellcharges_2, spellppmRate_2, spellcooldown_2, spellcategory_2, spellcategorycooldown_2,
spellid_3, spelltrigger_3, spellcharges_3, spellppmRate_3, spellcooldown_3, spellcategory_3,
spellcategorycooldown_3, spellid_4, spelltrigger_4, spellcharges_4, spellppmRate_4, spellcooldown_4,
spellcategory_4, spellcategorycooldown_4, spellid_5, spelltrigger_5, spellcharges_5, spellppmRate_5,
spellcooldown_5, spellcategory_5, spellcategorycooldown_5, bonding, description, PageText, LanguageID,
PageMaterial, startquest, lockid, Material, sheath, RandomProperty, RandomSuffix, block, itemset,
MaxDurability, area, Map, BagFamily, TotemCategory, socketColor_1, socketContent_1, socketColor_2,
socketContent_2, socketColor_3, socketContent_3, socketBonus, GemProperties, RequiredDisenchantSkill,
ArmorDamageModifier, duration, ItemLimitCategory, HolidayId, ScriptName, DisenchantID, FoodType,
minMoneyLoot, maxMoneyLoot, flagsCustom, VerifiedBuild
FROM acore_world.item_template as src
WHERE src.entry = %v ON DUPLICATE KEY UPDATE entry = src.entry + %v;
`, entryBump, item.Entry, entryBump)
update := fmt.Sprintf(`
UPDATE acore_world.item_template
SET
Quality = %v,
ItemLevel = %v,
RequiredLevel = %v,
dmg_min1 = %v,
dmg_max1 = %v,
dmg_min2 = %v,
dmg_max2 = %v,
StatsCount = %v,
stat_type1 = %v,
stat_value1 = %v,
stat_type2 = %v,
stat_value2 = %v,
stat_type3 = %v,
stat_value3 = %v,
stat_type4 = %v,
stat_value4 = %v,
stat_type5 = %v,
stat_value5 = %v,
stat_type6 = %v,
stat_value6 = %v,
stat_type7 = %v,
stat_value7 = %v,
stat_type8 = %v,
stat_value8 = %v,
stat_type9 = %v,
stat_value9 = %v,
stat_type10 = %v,
stat_value10 = %v,
spellid_1 = %v,
spellid_2 = %v,
spellid_3 = %v,
RequiredDisenchantSkill = %v,
DisenchantID = %v,
SellPrice = FLOOR(100000 + (RAND() * 400001)),
Armor = %v
WHERE entry = %v;
`, *item.Quality, *item.ItemLevel, reqLevel, *item.MinDmg1, *item.MaxDmg1, *item.MinDmg2, *item.MaxDmg2, *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, 375,
68, *item.Armor, entryBump+item.Entry)
return fmt.Sprintf("%s %s \n %s \n %s", spells, delete, clone, update)
}

149
internal/maps/maps.go Normal file
View File

@@ -0,0 +1,149 @@
package maps
import (
"fmt"
"github.com/araxiaonline/endgame-item-generator/utils"
)
type Dungeon struct {
Id int `db:"Id"`
Name string `db:"Name"`
Level int
ExpansionId int `db:"ExpansionId"`
}
// dungeon instance id : avg level
var dungeonLevels = 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
109: 60, // Sunken Temple
129: 33, // Razorfen Downs
70: 40, // Uldaman
329: 60, // Stratholme
229: 60, // Blackrock Spire (Lower)
230: 60, // Blackrock Spire (Upper)
429: 60, // Dire Maul
209: 50, // Zul'Farrak
349: 55, // Maraudon
269: 57, // Temple of Atal'Hakkar
// The Burning Crusade dungeons
540: 70, // Shattered Halls
542: 65, // Hellfire The Blood Furnace
543: 62, // Hellfire Ramparts
545: 64, // Coilfang Steamvaults
546: 65, // Coilfang Reservoir: The Underbog
547: 64, // Coilfang Reservoir: The Underbog
557: 66, // Auchindoun: Mana-Tombs
558: 67, // Auchindoun: Auchenai Crypts
556: 70, // Auchindoun: Sethekk Halls
555: 70, // Auchindoun: Shadow Labyrinth
560: 68, // Caverns of Time: Old Hillsbrad Foothills
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
574: 72, // Utgarde keep
575: 76, // Utgarde Pinnacle
619: 75, // Ahn'kahet: The Old Kingdom
576: 73, // The Nexus
595: 80, // The Culling of Stratholme
600: 76, // Drak'Tharon Keep
601: 75, // Azjol-Nerub
608: 77, // The Violet Hold
604: 78, // Gundrak
599: 78, // Halls of Stone
602: 80, // Halls of Lightning
578: 78, // The Oculus
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 MapName_Lang_enUS 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)
}
for i := range dungeons {
if level, ok := dungeonLevels[dungeons[i].Id]; ok {
dungeons[i].Level = level
}
}
return dungeons, nil
}
func (db Database) GetAddlDungeonDrops(instanceId int) ([]Item, error) {
var items []Item
sql := fmt.Sprintf(`
SELECT `+utils.GetItemFields("it")+`
from
acore_world.map_dbc m
join acore_world.creature c on m.ID = c.map
join acore_world.creature_template ct on c.id1 = ct.entry
left join acore_world.creature_loot_template clt on ct.lootid = clt.Entry
left join reference_loot_template rlt on clt.Reference = rlt.Entry
left join item_template it on rlt.Item = it.entry
WHERE m.ID = %v and Quality >= 3 and it.bonding = 2 and class IN(2,4)
UNION
SELECT `+utils.GetItemFields("it")+`
from
acore_world.map_dbc m
join acore_world.creature c on m.ID = c.map
join acore_world.creature_template ct on c.id1 = ct.entry
left join acore_world.creature_loot_template clt on clt.Entry = ct.Entry
left join item_template it on clt.Item = it.entry
WHERE m.ID = %v and Quality >= 3 and it.bonding = 2 and it.class IN(2,4)
UNION
SELECT `+utils.GetItemFields("it")+`
from
acore_world.map_dbc m
join acore_world.gameobject go on m.ID = go.map
left join acore_world.gameobject_template got on go.id = got.entry
left join acore_world.gameobject_loot_template glt on glt.Entry = got.Data1
left join reference_loot_template rlt on glt.Reference = rlt.Entry
left join item_template it on rlt.Item = it.entry
where m.ID = %v and Quality >=3 and it.bonding IN(1,2) and it.class IN(2,4);
`, instanceId, instanceId, instanceId)
// log.Printf("sql: %s", sql)
err := db.client.Select(&items, sql)
if err != nil {
return nil, fmt.Errorf("failed to get additional dungeon items: %v ", err)
}
return items, nil
}

43
internal/pkg/db/mysql.go Normal file
View File

@@ -0,0 +1,43 @@
package db
import (
"os"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
type MySql struct {
client *sqlx.DB
}
type MySqlConfig struct {
Host string
User string
Password string
Database string
}
func ConnectMySql(config *MySqlConfig) (*MySql, error) {
if config == nil {
config = &MySqlConfig{
Host: os.Getenv("DB_HOST"),
User: os.Getenv("DB_USER"),
Password: os.Getenv("DB_PASSWORD"),
Database: os.Getenv("DB_NAME"),
}
}
connString := config.User + ":" + config.Password + "@tcp(" + config.Host + ")/" + config.Database
client, err := sqlx.Open("mysql", connString)
if err != nil {
return nil, err
}
return &MySql{client: client}, nil
}
func (db *MySql) Close() {
db.client.Close()
}

23
internal/pkg/db/sqlite.go Normal file
View File

@@ -0,0 +1,23 @@
package db
import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
)
type SqlLite struct {
client *sql.DB
}
func ConnectSqlLite(path string) (*SqlLite, error) {
client, err := sql.Open("sqlite3", path)
if err != nil {
return nil, err
}
return &SqlLite{client: client}, nil
}
func (db *SqlLite) Close() {
db.client.Close()
}

465
internal/spells/spells.go Normal file
View File

@@ -0,0 +1,465 @@
package spells
import (
"fmt"
"log"
"math"
"strconv"
"strings"
"github.com/thoas/go-funk"
)
// list of spell effects that require scaling
var SpellEffects = [...]int{
2, // School Damage
6, // AppyAura
9, // HealthLeech
10, // Heal
30, // Restores Mana
35, // Apply Area Aura
}
// list of spell aura effects that require scaling
var SpellAuraEffects = [...]int{
3, // DOT
8, // HOT
13, // Modifies Spell Damage Done
15, // Modifies Damage Shield
22, // Modifies Resistance
34, // Modifies HEalth
85, // Modifies Mana Regen
99, // Modifies Attack Power
124, // Modifies Range Attack Power
135, // Modifies Healing Done
189, // Modifies Critical Strike
}
var AuraEffectsStatMap = map[int]int{
8: 46,
13: 45,
85: 43,
99: 38,
124: 38,
135: 45,
}
// Usually in EffectMiscValueA to describe what the Aura modifies
var SpellModifiers = [...]int{
0, // damage
1, // duration
2, // threat
3, // effect1
4, // charges
5, // range
6, // radius
7, // crit chance
8, // all effects
9, // No pushback
10, // Cast Time
11, // CD
12, // effect2
13, // ignore armor
14, // cost
15, // crit damage bonus
16, // resist miss chance
17, // jump targets
18, // Chance of success
19, // Amplitude
20, // Dmg multiplier
21, // GCD
22, // DoT
23, // effect3
24, // bonus multiplier
26, // PPM
27, // value multiplier
28, // resist dispel chance
29, // crit damage bonus 2
}
// result of a stat conversion from spell to raw stats on item
type ConvItemStat struct {
StatType int
StatValue int
Budget int
}
// Spell Effect with max value for effect storage
type SpellEffect struct {
Effect int
BasePoints int
DieSides int
CalculatedMax int
}
// DB Mapping from spell_dbc
type Spell struct {
ID int `db:"ID"`
Name string `db:"Name_Lang_enUS"`
Description string `db:"Description_Lang_enUS"`
AuraDescription string `db:"AuraDescription_Lang_enUS"`
ProcChance int `db:"ProcChance"`
SpellLevel int `db:"SpellLevel"`
Effect1 int `db:"Effect_1"`
Effect2 int `db:"Effect_2"`
Effect3 int `db:"Effect_3"`
EffectDieSides1 int `db:"EffectDieSides_1"`
EffectDieSides2 int `db:"EffectDieSides_2"`
EffectDieSides3 int `db:"EffectDieSides_3"`
EffectRealPointsPerLevel1 int `db:"EffectRealPointsPerLevel_1"`
EffectRealPointsPerLevel2 int `db:"EffectRealPointsPerLevel_2"`
EffectRealPointsPerLevel3 int `db:"EffectRealPointsPerLevel_3"`
EffectBasePoints1 int `db:"EffectBasePoints_1"`
EffectBasePoints2 int `db:"EffectBasePoints_2"`
EffectBasePoints3 int `db:"EffectBasePoints_3"`
EffectAura1 int `db:"EffectAura_1"`
EffectAura2 int `db:"EffectAura_2"`
EffectAura3 int `db:"EffectAura_3"`
EffectBonusMultiplier1 int `db:"EffectBonusMultiplier_1"`
EffectBonusMultiplier2 int `db:"EffectBonusMultiplier_2"`
EffectBonusMultiplier3 int `db:"EffectBonusMultiplier_3"`
ItemSpellSlot int
Scaled bool
}
func (db Database) GetSpell(id int) (Spell, error) {
if id == 0 {
return Spell{}, fmt.Errorf("id cannot be 0")
}
spell := Spell{}
sql := "SELECT " + GetSpellFields() + " FROM `spell_dbc` WHERE ID = ? -- " + strconv.Itoa(id)
err := db.client.Get(&spell, sql, id)
if err != nil {
return Spell{}, fmt.Errorf("failed to get spell: %v", err)
}
return spell, nil
}
func calcMaxValue(base int, sides int) int {
if base < 0 {
return base - sides
}
return base + sides
}
// get a List of the spell effects (not auras) that need to be scaled
func (s Spell) GetSpellEffects() []SpellEffect {
effects := make([]SpellEffect, 0)
effects = append(effects, SpellEffect{
Effect: s.Effect1,
BasePoints: s.EffectBasePoints1,
DieSides: s.EffectDieSides1,
CalculatedMax: calcMaxValue(s.EffectBasePoints1, s.EffectDieSides1),
})
effects = append(effects, SpellEffect{
Effect: s.Effect2,
BasePoints: s.EffectBasePoints2,
DieSides: s.EffectDieSides2,
CalculatedMax: calcMaxValue(s.EffectBasePoints2, s.EffectDieSides2),
})
effects = append(effects, SpellEffect{
Effect: s.Effect3,
BasePoints: s.EffectBasePoints3,
DieSides: s.EffectDieSides3,
CalculatedMax: calcMaxValue(s.EffectBasePoints3, s.EffectDieSides3),
})
return effects
}
// Get he effects and calculate the max value for the a
func (s Spell) GetAuraEffects() []SpellEffect {
effects := make([]SpellEffect, 0)
effects = append(effects, SpellEffect{
Effect: s.EffectAura1,
BasePoints: s.EffectBasePoints1,
DieSides: s.EffectDieSides1,
CalculatedMax: calcMaxValue(s.EffectBasePoints1, s.EffectDieSides1),
})
effects = append(effects, SpellEffect{
Effect: s.EffectAura2,
BasePoints: s.EffectBasePoints2,
DieSides: s.EffectDieSides2,
CalculatedMax: calcMaxValue(s.EffectBasePoints2, s.EffectDieSides2),
})
effects = append(effects, SpellEffect{
Effect: s.EffectAura3,
BasePoints: s.EffectBasePoints3,
DieSides: s.EffectDieSides3,
CalculatedMax: calcMaxValue(s.EffectBasePoints3, s.EffectDieSides3),
})
return effects
}
// this spell effect has stats or effects that need to be scaled
func (s Spell) SpellEffectsNeedsScaled() bool {
if s.Effect1 == 0 {
return false
}
needsScaled := false
effects := s.GetSpellEffects()
for _, e := range effects {
if !funk.Contains(SpellEffects, e.Effect) || e.Effect == 6 {
continue
}
needsScaled = true
}
return needsScaled
}
// this aura effect has stats or effects that need to be scaled
func (s Spell) AuraEffectNeedsScaled() bool {
if s.EffectAura1 == 0 {
return false
}
for _, effect := range SpellAuraEffects {
if s.EffectAura1 == effect || s.EffectAura2 == effect || s.EffectAura3 == effect {
return true
}
}
return false
}
func (s Spell) HasAuraEffect() bool {
return s.EffectAura1 != 0 || s.EffectAura2 != 0 || s.EffectAura3 != 0
}
func AuraEffectCanBeConv(effect int) bool {
statMods := [...]int{8, 13, 22, 34, 85, 99, 124, 135, 189}
return funk.Contains(statMods, effect)
}
// Lookup details about the effect and return the stat type -1 indicates not found
func convertAuraEffect(effect int) int {
if !funk.Contains(AuraEffectsStatMap, effect) {
log.Printf("effect %v not found in SpellEffectStatMap skipping", effect)
return -1
}
return AuraEffectsStatMap[effect]
}
// Converts spell buffs to item stats making it easier to convert and normalize
func (s Spell) ConvertToStats() ([]ConvItemStat, error) {
stats := []ConvItemStat{}
if s.Effect1 == 0 && s.EffectAura1 == 0 {
return stats, fmt.Errorf("spell does not have an effect1 or auraEffect1")
}
effects := s.GetAuraEffects()
if s.ID == 9397 {
log.Printf("Spell: %v AuraEffect1: %v AuraEffect2: %v AuraEffect3: %v", s.Name, s.EffectAura1, s.EffectAura2, s.EffectAura3)
}
var seen []int
for _, e := range effects {
if !AuraEffectCanBeConv(e.Effect) {
continue
}
statId := convertAuraEffect(e.Effect)
if statId == -1 {
continue
}
if funk.Contains(seen, statId) {
continue
}
// keep track if we have already seen this stat so we do not duplicate
// Wotlk changed everything to spell power so might as well do the same in
// scaling process.
seen = append(seen, statId)
statMod := float64(StatModifiers[statId])
stats = append(stats, ConvItemStat{
StatType: statId,
StatValue: e.CalculatedMax,
Budget: int(math.Abs(math.Ceil(float64(e.CalculatedMax) * statMod))),
})
}
// Handle special stat case where 189 is catch all for crit, dodge, parry, hit, haste, expertise
if s.Effect1 != 0 && s.Effect1 == 6 && (s.EffectAura1 == 189 || s.EffectAura1 == 123) {
log.Printf("Special case for spell aura effect: %v", s.Description)
statId := parseStatDesc(s.Description)
if statId == 0 {
log.Printf("Could not determine stat for spell aura effect description: %v", s.Name)
}
calced := calcMaxValue(s.EffectBasePoints1, s.EffectDieSides1)
log.Printf("StatId: %v Calced: %v", statId, calced)
stats = append(stats, ConvItemStat{
StatType: statId,
StatValue: calced,
Budget: int(math.Abs(math.Ceil(float64(calced) * float64(StatModifiers[statId])))),
})
}
return stats, nil
}
// This spell can be converted fully into a stat and not needed on the item
func (s Spell) CanBeConverted() bool {
// if there are any spell effects that are not aura effects, then it can be converted
effects := s.GetSpellEffects()
for _, e := range effects {
if e.Effect != 0 && e.Effect != 6 {
return false
}
}
// Unfortunately if there are any mixed effects for auras, it is too difficult to split those so just
// bail out
auras := s.GetAuraEffects()
auraFlag := false
for _, a := range auras {
if a.Effect == 0 {
continue
}
if AuraEffectCanBeConv(a.Effect) {
return true
}
}
return auraFlag
}
// based on the description determine the stat to
func parseStatDesc(desc string) int {
if strings.Contains(desc, "critical strike") {
return 32
}
if strings.Contains(desc, "dodge") {
return 13
}
if strings.Contains(desc, "parry") {
return 14
}
if strings.Contains(desc, "hit rating") {
return 31
}
if strings.Contains(desc, "haste rating") {
return 36
}
if strings.Contains(desc, "expertise rating") {
return 37
}
if strings.Contains(desc, "defense rating") {
return 12
}
if strings.Contains(desc, "block rating") {
return 15
}
if strings.Contains(desc, "armor penetration") {
return 44
}
if strings.Contains(desc, "spell penetration") {
return 47
}
return 0
}
// 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(fromItemLevel int, itemLevel int, itemQuality int) (int, error) {
s.Scaled = false
qualModifier := map[int]float64{
3: 1.20,
4: 1.30,
5: 1.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(fromItemLevel) * float64(itemLevel) * qualModifier[itemQuality] * 2.5)
didScale = true
}
if s.Effect2 != 0 && funk.Contains(dd, s.Effect1) {
s.EffectBasePoints2 = int(float64(s.EffectBasePoints2) / float64(fromItemLevel) * float64(itemLevel) * qualModifier[itemQuality] * 2.5)
didScale = true
}
// Restores a Power / Mana
if s.Effect1 != 0 && s.Effect1 == 30 {
// skip anyhing else that is not mana as they are flat values
if strings.Contains(s.Description, "Mana") || strings.Contains(s.Description, "mana") {
s.EffectBasePoints1 = int(float64(s.EffectBasePoints1) / float64(fromItemLevel) * float64(itemLevel) * qualModifier[itemQuality])
didScale = true
}
}
// Scales a stat buff
if s.Effect1 != 0 && s.Effect1 == 35 {
s.EffectBasePoints1 = int(float64(s.EffectBasePoints1) / float64(fromItemLevel) * float64(itemLevel) * qualModifier[itemQuality])
didScale = true
}
if s.Effect1 != 0 && s.Effect2 == 35 {
s.EffectBasePoints2 = int(float64(s.EffectBasePoints2) / float64(fromItemLevel) * 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(fromItemLevel) * float64(itemLevel) * qualModifier[itemQuality] * 2)
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(fromItemLevel) * float64(itemLevel) * qualModifier[itemQuality] * 1.50)
didScale = true
}
if !didScale {
return 0, fmt.Errorf("did not qualify to be scaled in ScaleSpell %v (%v)", s.Name, s.ID)
}
s.Scaled = true
return idBump + s.ID, nil
}

View File

@@ -0,0 +1,127 @@
package spells
import (
"fmt"
)
func GetSpellFields() string {
return `
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
`
}
func SpellToSql(spell Spell, quality int) string {
entryBump := 30000000
if quality == 4 {
entryBump = 31000000
}
if quality == 5 {
entryBump = 32000000
}
insert := fmt.Sprintf(`
INSERT IGNORE INTO acore_world.spell_dbc (
ID, 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
) 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)
update := fmt.Sprintf(`
UPDATE acore_world.spell_dbc
SET EffectBasePoints_1 = %v, EffectBasePoints_2 = %v
WHERE ID = %v;`, spell.EffectBasePoints1, spell.EffectBasePoints2, entryBump+spell.ID)
return fmt.Sprintf("\n %s \n %s \n", insert, update)
}

View File

@@ -0,0 +1 @@
package utils