diff --git a/cmd/fetch-highlevel-items/main.go b/cmd/fetch-highlevel-items/main.go new file mode 100644 index 0000000..62a6df1 --- /dev/null +++ b/cmd/fetch-highlevel-items/main.go @@ -0,0 +1,114 @@ +package main + +import ( + "database/sql" + "fmt" + "slices" + + "log" + "os" + "strconv" + "strings" + + "github.com/araxiaonline/endgame-item-generator/internal/db/mysql" + "github.com/araxiaonline/endgame-item-generator/internal/items" + "github.com/joho/godotenv" + _ "github.com/mattn/go-sqlite3" +) + +type EndGameItem struct { + Entry int `db:"entry"` + Class int `db:"class"` + Subclass int `db:"subclass"` + StatsList string `db:"stats_list"` +} + +func createItemsTable(db *sql.DB) { + createItems := `CREATE TABLE IF NOT EXISTS items ( + entry int unsigned NOT NULL DEFAULT '0', + class tinyint unsigned NOT NULL DEFAULT '0', + name varchar(250) NOT NULL DEFAULT '', + Quality int unsigned NOT NULL DEFAULT '0', + itemLevel int unsigned NOT NULL DEFAULT '0', + subclass tinyint unsigned NOT NULL DEFAULT '0', + stats_list varchar(250) NOT NULL DEFAULT '', + PRIMARY KEY (entry) + )` + + _, err := db.Exec(createItems) + if err != nil { + log.Fatal(err) + } +} + +func ConvertIntSliceToString(slice []int) string { + sliceStr := make([]string, len(slice)) + for i, v := range slice { + sliceStr[i] = strconv.Itoa(v) + } + + return strings.Join(sliceStr, ",") +} + +func main() { + godotenv.Load("../../.env") + liteDb, err := sql.Open("sqlite3", "./items.db") + if err != nil { + log.Fatal(err) + } + mysqlDb, err := mysql.Connect(&mysql.MySqlConfig{ + Host: os.Getenv("DB_HOST"), + User: os.Getenv("DB_USER"), + Password: os.Getenv("DB_PASSWORD"), + Database: os.Getenv("DB_NAME"), + }) + + if err != nil { + log.Fatal(err) + os.Exit(1) + } + + defer liteDb.Close() + defer mysqlDb.Close() + + // create the items table if it doesnt exist + createItemsTable(liteDb) + + // create the endgames items lookup table locally for items 200 and above + var dbItems []mysql.DbItem + sql := ` + SELECT ` + mysql.GetItemFields("") + ` + from acore_world.item_template + where ItemLevel >= 200 and Quality >= 3 and ItemLevel < 290 + AND name NOT LIKE 'NPC Equip%' and name NOT LIKE 'OLD%' + AND name NOT LIKE '%(test)%' AND name NOT LIKE '%Deprecated%' + AND name NOT LIKE '%Monster - %' + AND ((class = 2 and subclass IN(0,1,2,3,4,5,6,7,8,10,11,12,13,15,16,17,18,19)) or ((class = 4 AND subclass IN (0,1,2,3,4,6)))) + ` + err = mysqlDb.Select(&dbItems, sql) + if err != nil { + log.Printf("Failed to get items: %v", err) + } + + for _, dbItem := range dbItems { + item := items.ItemFromDbItem(dbItem) + var statsList []int + for i := 1; i <= 10; i++ { + val, _ := item.GetField(fmt.Sprintf("StatValue%v", i)) + statId, _ := item.GetField(fmt.Sprintf("StatType%v", i)) + if val != 0 { + statsList = append(statsList, statId) + } + } + slices.Sort(statsList) + statsListStr := ConvertIntSliceToString(statsList) + log.Printf("StatList %s for Item %v", statsListStr, item.Name) + _, err = liteDb.Exec("INSERT OR IGNORE INTO items (entry, class, name, Quality, itemLevel, subclass, stats_list) VALUES (?, ?, ?, ?, ?,?,?)", item.Entry, *item.Class, item.Name, *item.Quality, *item.ItemLevel, *item.Subclass, statsListStr) + if err != nil { + log.Printf("Failed to insert item %v: %v", item.Entry, err) + } + + } + + log.Printf("Items: %v", len(dbItems)) +} diff --git a/prebuilt_sql/ascendant-scaled.sql b/data/ascendant-scaled.sql similarity index 100% rename from prebuilt_sql/ascendant-scaled.sql rename to data/ascendant-scaled.sql diff --git a/data/items.db b/data/items.db new file mode 100644 index 0000000..1d9d1c8 Binary files /dev/null and b/data/items.db differ diff --git a/prebuilt_sql/legend-scaled.sql b/data/legend-scaled.sql similarity index 100% rename from prebuilt_sql/legend-scaled.sql rename to data/legend-scaled.sql diff --git a/prebuilt_sql/mythic-scaled.sql b/data/mythic-scaled.sql similarity index 100% rename from prebuilt_sql/mythic-scaled.sql rename to data/mythic-scaled.sql diff --git a/endgame-item-generator b/endgame-item-generator new file mode 100755 index 0000000..78582ff Binary files /dev/null and b/endgame-item-generator differ diff --git a/internal/config/modifier.go b/internal/config/modifier.go index e8b5b55..a40e0d8 100644 --- a/internal/config/modifier.go +++ b/internal/config/modifier.go @@ -86,7 +86,7 @@ var StatModifiers = map[int]float64{ 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 + 45: 0.50, // 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 diff --git a/internal/creatures/creatures.go b/internal/creatures/creatures.go deleted file mode 100644 index 0b519cd..0000000 --- a/internal/creatures/creatures.go +++ /dev/null @@ -1,8 +0,0 @@ -package creatures - -type Boss struct { - Entry int - Name string - ScriptName string `db:"ScriptName"` - ExperienceModifier int `db:"ExperienceModifier"` -} diff --git a/internal/db/mysql/creatures.go b/internal/db/mysql/creatures.go index 08dc142..982a606 100644 --- a/internal/db/mysql/creatures.go +++ b/internal/db/mysql/creatures.go @@ -3,18 +3,22 @@ package mysql import ( "errors" "fmt" - - "github.com/araxiaonline/endgame-item-generator/internal/creatures" - "github.com/araxiaonline/endgame-item-generator/internal/items" ) -func (db *MySqlDb) GetBosses(mapId int) ([]creatures.Boss, error) { +type Boss struct { + Entry int + Name string + ScriptName string `db:"ScriptName"` + ExperienceModifier int `db:"ExperienceModifier"` +} + +func (db *MySqlDb) GetBosses(mapId int) ([]Boss, error) { if mapId == 0 { return nil, errors.New("mapId cannot be 0") } - bosses := []creatures.Boss{} + bosses := []Boss{} var sql string // 540 is pre-classic dungeons so XP Multiplier is best way to determine bosses / rare mobs @@ -38,13 +42,13 @@ func (db *MySqlDb) GetBosses(mapId int) ([]creatures.Boss, error) { return bosses, nil } -func (db *MySqlDb) GetBossLoot(bossId int) ([]items.Item, error) { +func (db *MySqlDb) GetBossLoot(bossId int) ([]DbItem, 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 := []items.Item{} + items := []DbItem{} fields := GetItemFields("") sql := ` SELECT ` + fields + ` @@ -77,7 +81,7 @@ func (db *MySqlDb) GetBossLoot(bossId int) ([]items.Item, error) { return items, nil } - refItems := []items.Item{} + refItems := []DbItem{} // For each reference we now need to get the items and add them to the items slice for _, ref := range references { diff --git a/internal/db/mysql/items.go b/internal/db/mysql/items.go index e21b309..cfeff82 100644 --- a/internal/db/mysql/items.go +++ b/internal/db/mysql/items.go @@ -2,25 +2,100 @@ package mysql import ( "fmt" - - "github.com/araxiaonline/endgame-item-generator/internal/items" ) -func (db *MySqlDb) GetItem(entry int) (items.Item, error) { +type DbItem 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 *float64 `db:"dmg_min1"` + MaxDmg1 *float64 `db:"dmg_max1"` + MinDmg2 *float64 `db:"dmg_min2"` + MaxDmg2 *float64 `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"` + SocketColor1 *int `db:"socketColor_1"` + SocketContent1 *int `db:"socketContent_1"` + SocketColor2 *int `db:"socketColor_2"` + SocketContent2 *int `db:"socketContent_2"` + SocketColor3 *int `db:"socketColor_3"` + SocketContent3 *int `db:"socketContent_3"` + SocketBonus *int `db:"socketBonus"` + GemProperties *int `db:"GemProperties"` +} + +func (db *MySqlDb) GetItem(entry int) (DbItem, error) { if entry == 0 { - return items.Item{}, fmt.Errorf("entry cannot be 0") + return DbItem{}, fmt.Errorf("entry cannot be 0") } - item := items.Item{} + item := DbItem{} sql := "SELECT " + GetItemFields("") + " FROM item_template WHERE entry = ?" err := db.Get(&item, sql, entry) if err != nil { - return items.Item{}, err + return DbItem{}, err } return item, nil } +func (db *MySqlDb) GetRarePlusItems(limit, offset int) ([]DbItem, error) { + items := []DbItem{} + sql := "SELECT " + GetItemFields("") + " FROM item_template WHERE Quality >= 3 and Quality <= 5 and (class = 2 or class = 4)" + + if limit != 0 && offset != 0 { + sql += fmt.Sprintf("LIMIT %v OFFSET %v", limit, offset) + } + + err := db.Select(&items, sql) + if err != nil { + return []DbItem{}, err + } + + return items, nil +} + func GetItemFields(prefix string) string { pre := "" if prefix != "" { @@ -49,130 +124,7 @@ func GetItemFields(prefix string) string { 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 += 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) + spelltrigger_1, spelltrigger_2, spelltrigger_3, + socketColor_1, socketContent_1, socketColor_2, socketContent_2, socketColor_3, socketContent_3, + socketBonus, GemProperties` } diff --git a/internal/db/mysql/maps.go b/internal/db/mysql/maps.go index e7f8c3d..89d8e77 100644 --- a/internal/db/mysql/maps.go +++ b/internal/db/mysql/maps.go @@ -2,8 +2,6 @@ package mysql import ( "fmt" - - "github.com/araxiaonline/endgame-item-generator/internal/items" ) type Dungeon struct { @@ -104,10 +102,10 @@ func (db *MySqlDb) GetDungeons(expansionId int) ([]Dungeon, error) { } // Gets a list of other rare+ items that drop in a specific instance. -func (db *MySqlDb) GetAddlDungeonDrops(instanceId int) ([]items.Item, error) { +func (db *MySqlDb) GetAddlDungeonDrops(instanceId int) ([]DbItem, error) { - fields := items.GetItemFields("it") - var items []items.Item + fields := GetItemFields("it") + var items []DbItem sql := fmt.Sprintf(` SELECT `+fields+` from diff --git a/internal/db/mysql/mysql.go b/internal/db/mysql/mysql.go index 9cccf9f..1db813d 100644 --- a/internal/db/mysql/mysql.go +++ b/internal/db/mysql/mysql.go @@ -1,6 +1,7 @@ package mysql import ( + "errors" "os" _ "github.com/go-sql-driver/mysql" @@ -41,6 +42,16 @@ func Connect(config *MySqlConfig) (*MySqlDb, error) { return MySql, nil } -func (db *MySqlDb) Close() { - db.Close() +func GetDb() (*MySqlDb, error) { + if MySql == nil { + return nil, errors.New("mysql not connected") + } + + return MySql, nil +} + +func (db *MySqlDb) Close() { + if db.DB != nil { + db.DB.Close() + } } diff --git a/internal/db/mysql/spells.go b/internal/db/mysql/spells.go index 27f0aba..36c7204 100644 --- a/internal/db/mysql/spells.go +++ b/internal/db/mysql/spells.go @@ -3,22 +3,47 @@ package mysql import ( "fmt" "strconv" - - "github.com/araxiaonline/endgame-item-generator/internal/spells" ) -func (db *MySqlDb) GetSpell(id int) (spells.Spell, error) { +type DbSpell 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"` +} + +func (db *MySqlDb) GetSpell(id int) (DbSpell, error) { if id == 0 { - return spells.Spell{}, fmt.Errorf("id cannot be 0") + return DbSpell{}, fmt.Errorf("id cannot be 0") } - spell := spells.Spell{} + spell := DbSpell{} sql := "SELECT " + GetSpellFields() + " FROM `spell_dbc` WHERE ID = ? -- " + strconv.Itoa(id) err := db.Get(&spell, sql, id) if err != nil { - return spells.Spell{}, fmt.Errorf("failed to get spell: %v", err) + return DbSpell{}, fmt.Errorf("failed to get spell: %v", err) } return spell, nil @@ -52,96 +77,3 @@ func GetSpellFields() string { EffectBonusMultiplier_3 ` } - -func SpellToSql(spell spells.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) -} diff --git a/internal/db/sqlite/items.go b/internal/db/sqlite/items.go new file mode 100644 index 0000000..180437b --- /dev/null +++ b/internal/db/sqlite/items.go @@ -0,0 +1,86 @@ +package sqlite + +import ( + "errors" + "fmt" + "log" + "strings" + + "github.com/araxiaonline/endgame-item-generator/internal/db/mysql" +) + +type HighLevelItem struct { + Entry int `db:"entry"` + Class *int `db:"class"` + Name string `db:"name"` + Quality int `db:"Quality"` + ItemLevel int `db:"itemLevel"` + Subclass *int `db:"subclass"` + StatsList string `db:"stats_list"` +} + +func (db *SqlLite) GetItem(entry int) (HighLevelItem, error) { + if entry == 0 { + return HighLevelItem{}, fmt.Errorf("entry cannot be 0") + } + + item := HighLevelItem{} + sql := "SELECT " + mysql.GetItemFields("") + " FROM item_template WHERE entry = ?" + err := db.Get(&item, sql, entry) + if err != nil { + return HighLevelItem{}, err + } + + return item, nil +} + +// This gets a random item that is close in stats type to the lower level items with some randomness +func (db *SqlLite) GetRandItem(class, subclass int, statsList []int, end bool) (HighLevelItem, error) { + rndItem := HighLevelItem{} + var statsTxt string + var err error + var sql string + + // if we have a stats_list try to match by that first, if not then just select a random item from the class and subclass + if len(statsList) == 0 { + sql = "SELECT * FROM items WHERE class = ? and subclass = ? ORDER BY RANDOM() LIMIT 1" + err = db.Get(&rndItem, sql, class, subclass) + } else { + // convert the array of ints to a commas string for lookup + statsTxt = intSliceToString(statsList) + sql = "SELECT * FROM items WHERE class = ? and subclass = ? and stats_list like ? ORDER BY RANDOM() LIMIT 1" + err = db.Get(&rndItem, sql, class, subclass, statsTxt+"%") + } + + if err != nil { + + // if we hit the last check and still no item match then return an error + if end { + msg := fmt.Sprintf("Failed to find a matching item class %v subclass %v statsTxt %v", class, subclass, statsTxt) + return HighLevelItem{}, errors.New(msg) + } + + // if there was not a remove the last stat and try again + if err.Error() == "sql: no rows in result set" { + + if len(statsList) == 0 { + return db.GetRandItem(class, subclass, statsList, true) + } else { + statsList = statsList[:len(statsList)-1] + return db.GetRandItem(class, subclass, statsList, false) + } + + } + + log.Fatalf("Error getting random sql: %v error: %v", sql, err) + return HighLevelItem{}, err + } + + return rndItem, nil +} + +func intSliceToString(slice []int) string { + str := fmt.Sprint(slice) + str = strings.Trim(str, "[]") + return strings.ReplaceAll(str, " ", ",") +} diff --git a/internal/db/sqlite/sqlite.go b/internal/db/sqlite/sqlite.go index 81d3909..268996f 100644 --- a/internal/db/sqlite/sqlite.go +++ b/internal/db/sqlite/sqlite.go @@ -1,23 +1,33 @@ package sqlite import ( - "database/sql" + "github.com/jmoiron/sqlx" _ "github.com/mattn/go-sqlite3" ) type SqlLite struct { - client *sql.DB + *sqlx.DB } +var SqlLiteDb *SqlLite + func Connect(path string) (*SqlLite, error) { - client, err := sql.Open("sqlite3", path) + client, err := sqlx.Open("sqlite3", path) if err != nil { return nil, err } - return &SqlLite{client: client}, nil + + SqlLiteDb = &SqlLite{client} + return SqlLiteDb, nil +} + +func GetDb() (*SqlLite, error) { + return SqlLiteDb, nil } func (db *SqlLite) Close() { - db.client.Close() + if db.DB != nil { + db.DB.Close() + } } diff --git a/internal/items/item_test.go b/internal/items/item_test.go index 482d1f5..6262e57 100644 --- a/internal/items/item_test.go +++ b/internal/items/item_test.go @@ -181,8 +181,8 @@ func TestGetDPS(t *testing.T) { name: "Valid DPS calculation", item: Item{ DbItem: mysql.DbItem{ - MinDmg1: ptrInt(50), - MaxDmg1: ptrInt(70), + MinDmg1: ptrFloat64(50), + MaxDmg1: ptrFloat64(70), Delay: ptrFloat64(3000), }, }, @@ -193,8 +193,8 @@ func TestGetDPS(t *testing.T) { name: "High damage DPS calculation", item: Item{ DbItem: mysql.DbItem{ - MinDmg1: ptrInt(100), - MaxDmg1: ptrInt(150), + MinDmg1: ptrFloat64(100), + MaxDmg1: ptrFloat64(150), Delay: ptrFloat64(2000), }, }, @@ -205,8 +205,8 @@ func TestGetDPS(t *testing.T) { name: "Low damage DPS calculation", item: Item{ DbItem: mysql.DbItem{ - MinDmg1: ptrInt(10), - MaxDmg1: ptrInt(15), + MinDmg1: ptrFloat64(10), + MaxDmg1: ptrFloat64(15), Delay: ptrFloat64(1500), }, }, @@ -217,7 +217,7 @@ func TestGetDPS(t *testing.T) { name: "Missing MinDmg1", item: Item{ DbItem: mysql.DbItem{ - MaxDmg1: ptrInt(70), + MaxDmg1: ptrFloat64(70), Delay: ptrFloat64(3000), }, }, @@ -228,7 +228,7 @@ func TestGetDPS(t *testing.T) { name: "Missing MaxDmg1", item: Item{ DbItem: mysql.DbItem{ - MinDmg1: ptrInt(50), + MinDmg1: ptrFloat64(50), Delay: ptrFloat64(3000), }, }, @@ -239,8 +239,8 @@ func TestGetDPS(t *testing.T) { name: "Missing Delay", item: Item{ DbItem: mysql.DbItem{ - MinDmg1: ptrInt(50), - MaxDmg1: ptrInt(70), + MinDmg1: ptrFloat64(50), + MaxDmg1: ptrFloat64(70), }, }, wantDPS: 0, @@ -277,8 +277,8 @@ func TestScaleDPS(t *testing.T) { DbItem: mysql.DbItem{ ItemLevel: ptrInt(60), Delay: ptrFloat64(3000), - MinDmg1: ptrInt(50), - MaxDmg1: ptrInt(70), + MinDmg1: ptrFloat64(50), + MaxDmg1: ptrFloat64(70), Subclass: ptrInt(4), // One-handed weapon Quality: ptrInt(3), // Rare }, @@ -294,8 +294,8 @@ func TestScaleDPS(t *testing.T) { DbItem: mysql.DbItem{ ItemLevel: ptrInt(80), Delay: ptrFloat64(2000), - MinDmg1: ptrInt(150), - MaxDmg1: ptrInt(200), + MinDmg1: ptrFloat64(150), + MaxDmg1: ptrFloat64(200), Subclass: ptrInt(17), // Two-handed weapon Quality: ptrInt(4), // Epic }, @@ -311,8 +311,8 @@ func TestScaleDPS(t *testing.T) { DbItem: mysql.DbItem{ ItemLevel: ptrInt(20), Delay: ptrFloat64(1000), - MinDmg1: ptrInt(30), - MaxDmg1: ptrInt(50), + MinDmg1: ptrFloat64(30), + MaxDmg1: ptrFloat64(50), Subclass: ptrInt(2), // Ranged weapon Quality: ptrInt(2), // Uncommon }, @@ -327,8 +327,8 @@ func TestScaleDPS(t *testing.T) { item: Item{ DbItem: mysql.DbItem{ Delay: ptrFloat64(3000), - MinDmg1: ptrInt(50), - MaxDmg1: ptrInt(70), + MinDmg1: ptrFloat64(50), + MaxDmg1: ptrFloat64(70), Subclass: ptrInt(4), // One-handed weapon Quality: ptrInt(3), // Rare }, @@ -343,8 +343,8 @@ func TestScaleDPS(t *testing.T) { item: Item{ DbItem: mysql.DbItem{ ItemLevel: ptrInt(60), - MinDmg1: ptrInt(50), - MaxDmg1: ptrInt(70), + MinDmg1: ptrFloat64(50), + MaxDmg1: ptrFloat64(70), Subclass: ptrInt(4), // One-handed weapon Quality: ptrInt(3), // Rare }, @@ -360,10 +360,10 @@ func TestScaleDPS(t *testing.T) { DbItem: mysql.DbItem{ ItemLevel: ptrInt(60), Delay: ptrFloat64(3000), - MinDmg1: ptrInt(50), - MaxDmg1: ptrInt(70), - MinDmg2: ptrInt(25), - MaxDmg2: ptrInt(35), + MinDmg1: ptrFloat64(50), + MaxDmg1: ptrFloat64(70), + MinDmg2: ptrFloat64(25), + MaxDmg2: ptrFloat64(35), Subclass: ptrInt(4), // One-handed weapon Quality: ptrInt(3), // Rare }, diff --git a/internal/items/items.go b/internal/items/items.go index 1274d61..50b1de0 100644 --- a/internal/items/items.go +++ b/internal/items/items.go @@ -33,6 +33,13 @@ type ItemStat struct { AdjValue float64 } +// Create a new item from the database item +func ItemFromDbItem(dbItem mysql.DbItem) Item { + return Item{ + DbItem: dbItem, + } +} + // Get the primary stat for an item (strength, agility, intellect, spirit, stamina) func (item Item) GetPrimaryStat() (int, int, error) { var primaryStat int64 @@ -75,6 +82,31 @@ func (item Item) GetPrimaryStat() (int, int, error) { func (item Item) GetStatList() ([]int, error) { statList := []int{} + + // Also need to get spells that on the item that convert to stats + spells, err := item.GetSpells() + if err != nil { + log.Printf("Failed to get spells for item: %v", err) + return nil, err + } + + for _, spell := range spells { + convStats, err := spell.ConvertToStats() + if err != nil { + log.Printf("Failed to convert spell to stats: %v for spell %v", err, spell.Name) + continue + } + + if len(convStats) == 0 { + continue + } + + for _, convStat := range convStats { + statList = append(statList, convStat.StatType) + } + + } + for i := 1; i < 11; i++ { val, err := item.GetField(fmt.Sprintf("StatValue%v", i)) @@ -190,8 +222,8 @@ func (item *Item) ScaleDPS(level int) (float64, error) { 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)) + minimum2 := ratioMin * float64(minimum) + maximum2 := ratioMax * float64(maximum) item.MinDmg2 = &minimum2 item.MaxDmg2 = &maximum2 @@ -199,14 +231,13 @@ func (item *Item) ScaleDPS(level int) (float64, error) { // 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 + // var min int = int(minimum) + // var max int = int(maximum) + item.MinDmg1 = &minimum + item.MaxDmg1 = &maximum return dps, nil } @@ -225,7 +256,7 @@ func (item Item) GetStatPercents(spellStats []spells.ConvItemStat) map[int]*Item continue } - adjValue := float64(statValue) / config.StatModifiers[int(statType)] + adjValue := float64(statValue) * config.StatModifiers[int(statType)] statBudget += adjValue statMap[int(statType)] = &ItemStat{ Value: int(statValue), @@ -337,6 +368,42 @@ func (item *Item) GetNonStatSpells() ([]spells.Spell, error) { return nonStatSpells, nil } +// Applies status of one item to another overwriting the current stats +func (item *Item) ApplyStats(otherItem Item) (success bool, err error) { + + for i := 1; i < 11; i++ { + statType, err := otherItem.GetField(fmt.Sprintf("StatType%v", i)) + if err != nil { + return false, err + } + + statValue, err := otherItem.GetField(fmt.Sprintf("StatValue%v", i)) + if err != nil { + return false, err + } + + item.UpdateField(fmt.Sprintf("StatType%v", i), statType) + item.UpdateField(fmt.Sprintf("StatValue%v", i), statValue) + } + + if otherItem.SocketColor1 != nil { + item.SocketColor1 = otherItem.SocketColor1 + item.SocketContent1 = otherItem.SocketContent1 + } + + if otherItem.SocketColor2 != nil { + item.SocketColor2 = otherItem.SocketColor2 + item.SocketContent2 = otherItem.SocketContent2 + } + + if otherItem.SocketColor3 != nil { + item.SocketColor3 = otherItem.SocketColor3 + item.SocketContent3 = otherItem.SocketContent3 + } + + return true, 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 @@ -440,7 +507,7 @@ func (item *Item) ScaleItem(itemLevel int, itemQuality int) (bool, error) { item.Spells = []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) + // 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) @@ -455,7 +522,7 @@ func (item *Item) ScaleItem(itemLevel int, itemQuality int) (bool, error) { 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) + // 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 @@ -601,7 +668,7 @@ func (item *Item) addStats(stats map[int]*ItemStat) { // 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)*config.QualityModifiers[itemQuality]*config.InvTypeModifiers[itemType]), 1.7095) * percOfStat + scaledUp := (math.Pow((float64(itemLevel)*config.QualityModifiers[itemQuality]*config.InvTypeModifiers[itemType]), 1.7095) * percOfStat) // leaving modifier off for now but not changing signature in case I need to add it back _ = statModifier @@ -719,6 +786,14 @@ func ItemToSql(item Item, reqLevel int, difficulty int) string { spellid_1 = %v, spellid_2 = %v, spellid_3 = %v, + socketColor_1 = %v, + socketContent_1 = %v, + socketColor_2 = %v, + socketContent_2 = %v, + socketColor_3 = %v, + socketContent_3 = %v, + socketBonus = %v, + GemProperties = %v, RequiredDisenchantSkill = %v, DisenchantID = %v, SellPrice = FLOOR(100000 + (RAND() * 400001)), @@ -727,8 +802,9 @@ func ItemToSql(item Item, reqLevel int, difficulty int) string { `, *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) + *item.StatType9, *item.StatValue9, *item.StatType10, *item.StatValue10, *item.SpellId1, *item.SpellId2, *item.SpellId3, *item.SocketColor1, *item.SocketContent1, + *item.SocketColor2, *item.SocketContent2, *item.SocketColor3, *item.SocketContent3, *item.SocketBonus, *item.GemProperties, + 375, 68, *item.Armor, entryBump+item.Entry) return fmt.Sprintf("%s %s \n %s \n %s", spellList, delete, clone, update) } diff --git a/internal/spells/spells.go b/internal/spells/spells.go index ef5b979..733507d 100644 --- a/internal/spells/spells.go +++ b/internal/spells/spells.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/araxiaonline/endgame-item-generator/internal/config" + "github.com/araxiaonline/endgame-item-generator/internal/db/mysql" "github.com/thoas/go-funk" ) @@ -27,6 +28,7 @@ var SpellAuraEffects = [...]int{ 13, // Modifies Spell Damage Done 15, // Modifies Damage Shield 22, // Modifies Resistance + 29, // Modifies Strength 34, // Modifies HEalth 85, // Modifies Mana Regen 99, // Modifies Attack Power @@ -39,6 +41,7 @@ var SpellAuraEffects = [...]int{ var AuraEffectsStatMap = map[int]int{ 8: 46, 13: 45, + 29: 4, 85: 43, 99: 38, 124: 38, @@ -93,34 +96,10 @@ type SpellEffect struct { 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 + mysql.DbSpell + Scaled bool + ItemSpellSlot int } func calcMaxValue(base int, sides int) int { @@ -225,14 +204,13 @@ func (s Spell) HasAuraEffect() bool { } func AuraEffectCanBeConv(effect int) bool { - statMods := [...]int{8, 13, 22, 34, 85, 99, 124, 135, 189} - return funk.Contains(statMods, effect) + return funk.Contains(SpellAuraEffects, 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) + // log.Printf("effect %v not found in SpellEffectStatMap skipping", effect) return -1 } @@ -251,8 +229,8 @@ func (s Spell) ConvertToStats() ([]ConvItemStat, error) { 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) { @@ -282,14 +260,14 @@ func (s Spell) ConvertToStats() ([]ConvItemStat, error) { // 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) + // 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) - } + // 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) + // log.Printf("StatId: %v Calced: %v", statId, calced) stats = append(stats, ConvItemStat{ StatType: statId, StatValue: calced, @@ -297,6 +275,12 @@ func (s Spell) ConvertToStats() ([]ConvItemStat, error) { }) } + // if len(stats) == 0 { + // // log.Printf("Failed to Convert Spell to Stats: %v AuraEffect1: %v AuraEffect2: %v AuraEffect3: %v", s.Name, s.EffectAura1, s.EffectAura2, s.EffectAura3) + // } else { + // // log.Printf("Converted Spell to Stats: %v AuraEffect1: %v AuraEffect2: %v AuraEffect3: %v", s.Name, s.EffectAura1, s.EffectAura2, s.EffectAura3) + // } + return stats, nil } @@ -447,3 +431,96 @@ func (s *Spell) ScaleSpell(fromItemLevel int, itemLevel int, itemQuality int) (i s.Scaled = true return idBump + s.ID, nil } + +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) +} diff --git a/internal/spells/spells_test.go b/internal/spells/spells_test.go new file mode 100644 index 0000000..6027ba9 --- /dev/null +++ b/internal/spells/spells_test.go @@ -0,0 +1,60 @@ +package spells + +import ( + "testing" + + "github.com/araxiaonline/endgame-item-generator/internal/db/mysql" +) + +func TestCanBeConverted(t *testing.T) { + tests := []struct { + name string + spell Spell + expected bool + }{ + { + name: "Spell with non-aura effect", + spell: Spell{ + DbSpell: mysql.DbSpell{ + Effect1: 1, + }, + }, + expected: false, + }, + { + name: "Spell with aura effect that can be converted", + spell: Spell{ + DbSpell: mysql.DbSpell{ + EffectAura1: 8, + }, + }, + expected: true, + }, + { + name: "Spell with mixed effects", + spell: Spell{ + DbSpell: mysql.DbSpell{ + Effect1: 1, + EffectAura1: 8, + }, + }, + expected: false, + }, + { + name: "Spell with no effects", + spell: Spell{ + DbSpell: mysql.DbSpell{}, + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.spell.CanBeConverted() + if result != tt.expected { + t.Errorf("expected %v, got %v", tt.expected, result) + } + }) + } +} diff --git a/main.go b/main.go index 7ae62bd..2f1aefb 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,10 @@ import ( "log" "os" + "github.com/araxiaonline/endgame-item-generator/internal/db/mysql" + "github.com/araxiaonline/endgame-item-generator/internal/db/sqlite" + "github.com/araxiaonline/endgame-item-generator/internal/items" + _ "github.com/go-sql-driver/mysql" "github.com/joho/godotenv" ) @@ -17,23 +21,98 @@ func main() { // database.models.Connect() debug := flag.Bool("debug", false, "Enable verbose logging inside generator") - // itemLevel := flag.Int("ilvl", 305, "Specify the item level to start scaling from, expansion and difficulty modifiers scale up.") - // difficulty := flag.Int("difficulty", 3, "set the difficulty of the dungeon, defaults to 3 (mythic) 4 (legendary) 5 (ascendant)") + itemLevel := flag.Int("ilvl", 305, "Specify the item level to start scaling from, expansion and difficulty modifiers scale up.") + difficulty := flag.Int("difficulty", 3, "set the difficulty of the dungeon, defaults to 3 (mythic) 4 (legendary) 5 (ascendant)") // levelUp := flag.Bool("levelUp", false, "Boss items require higher +1 level to equip, defaults to false") - // baselevel := flag.Int("baselevel", 80, "set the base level for items to be used, defaults to 80 this is required for levelUp flag") - + baselevel := flag.Int("baselevel", 80, "set the base level for items to be used, defaults to 80 this is required for levelUp flag") flag.Parse() + if difficulty == nil || *difficulty < 3 || *difficulty > 5 { + log.Fatal("difficulty must be between 3-5") + os.Exit(1) + } + + if itemLevel == nil || *itemLevel < 280 { + log.Fatal("item level must be greater than 280") + os.Exit(1) + } + + if baselevel == nil || *baselevel < 0 { + log.Fatal("base level must be greater than 80") + os.Exit(1) + } + if *debug { log.SetOutput(os.Stdout) } else { log.SetOutput(io.Discard) } - // if difficulty == nil || *difficulty < 3 || *difficulty > 5 { - // log.Fatal("difficulty must be between 3-5") - // os.Exit(1) - // } + // Connect to Mysql + mysqlDb, err := mysql.Connect(&mysql.MySqlConfig{ + Host: os.Getenv("DB_HOST"), + User: os.Getenv("DB_USER"), + Password: os.Getenv("DB_PASSWORD"), + Database: os.Getenv("DB_NAME"), + }) + + if err != nil { + log.Fatal(err) + } + + // Connect to SqlList for EndGame Mapping + sqliteDb, err := sqlite.Connect("./data/items.db") + if err != nil { + log.Fatal(err) + } + + rareItems, err := mysqlDb.GetRarePlusItems(0, 0) + if err != nil { + log.Fatal(err) + } + + for itr, dbItem := range rareItems { + item := items.ItemFromDbItem(dbItem) + + statsList, err := item.GetStatList() + if err != nil { + log.Fatal(err) + continue + } + + log.Printf("Item: %v Entry: %v StatsList: %v\n", item.Name, item.Entry, statsList) + rndItem, err := sqliteDb.GetRandItem(*item.Class, *item.Subclass, statsList, false) + if err != nil { + log.Fatal(err) + continue + } + + if rndItem == (sqlite.HighLevelItem{}) { + log.Fatalf("Failed to get random item for %v Entry: %v\n", item.Name, item.Entry) + } + + log.Printf("Random Item: %v Entry: %v\n", rndItem.Name, rndItem.Entry) + + highLevelItem, err := mysqlDb.GetItem(rndItem.Entry) + if err != nil { + log.Fatal(err) + continue + } + + // Print all the status for the item that was copied + log.Printf("Item Name: %v Stat1: %v Stat2: %v Stat3: %v Stat4: %v Stat5: %v Stat6: %v Stat7: %v Stat8: %v \n", + item.Entry, *item.StatValue1, *item.StatValue2, *item.StatValue3, *item.StatValue4, *item.StatValue5, *item.StatValue6, *item.StatValue7, *item.StatValue8) + + item.ApplyStats(items.ItemFromDbItem(highLevelItem)) + + item.ScaleItem(*itemLevel, 4) + log.Printf("Item Name: %v Stat1: %v Stat2: %v Stat3: %v Stat4: %v Stat5: %v Stat6: %v Stat7: %v Stat8: %v \n", + item.Name, *item.StatValue1, *item.StatValue2, *item.StatValue3, *item.StatValue4, *item.StatValue5, *item.StatValue6, *item.StatValue7, *item.StatValue8) + + if itr > 100 { + break + } + } // // main loop // dungeons, err := models.DB.GetDungeons(-1) diff --git a/scripts/localdb/items.db b/scripts/localdb/items.db deleted file mode 100644 index 3fbf719..0000000 Binary files a/scripts/localdb/items.db and /dev/null differ diff --git a/scripts/localdb/populate-endgame.go b/scripts/localdb/populate-endgame.go deleted file mode 100644 index ba023ff..0000000 --- a/scripts/localdb/populate-endgame.go +++ /dev/null @@ -1,103 +0,0 @@ -// package main - -// import ( -// "database/sql" -// "fmt" -// "log" -// "slices" -// "strconv" -// "strings" - -// // "github.com/araxiaonline/endgame-item-generator/internal/models" -// "github.com/araxiaonline/endgame-item-generator/internal/utils" -// "github.com/joho/godotenv" -// _ "github.com/mattn/go-sqlite3" -// ) - -// type EndGameItem struct { -// Entry int `db:"entry"` -// Class int `db:"class"` -// Subclass int `db:"subclass"` -// StatsList string `db:"stats_list"` -// } - -// func createItemsTable(db *sql.DB) { -// createItems := `CREATE TABLE IF NOT EXISTS items ( -// entry int unsigned NOT NULL DEFAULT '0', -// class tinyint unsigned NOT NULL DEFAULT '0', -// name varchar(250) NOT NULL DEFAULT '', -// Quality int unsigned NOT NULL DEFAULT '0', -// itemLevel int unsigned NOT NULL DEFAULT '0', -// subclass tinyint unsigned NOT NULL DEFAULT '0', -// stats_list varchar(250) NOT NULL DEFAULT '', -// PRIMARY KEY (entry) -// )` - -// _, err := db.Exec(createItems) -// if err != nil { -// log.Fatal(err) -// } -// } - -// func ConvertIntSliceToString(slice []int) string { -// sliceStr := make([]string, len(slice)) -// for i, v := range slice { -// sliceStr[i] = strconv.Itoa(v) -// } - -// return strings.Join(sliceStr, ",") -// } - -// func main() { - -// liteDb, err := sql.Open("sqlite3", "./items.db") -// if err != nil { -// log.Fatal(err) -// } -// godotenv.Load("../../.env") -// models.Connect() -// sqlDb := models.DB.Client() - -// defer liteDb.Close() -// defer sqlDb.Close() - -// // create the items table if it doesnt exist -// createItemsTable(liteDb) - -// // create the endgames items lookup table locally for items 200 and above -// var items []models.Item -// sql := ` -// SELECT ` + utils.GetItemFields("") + ` -// from acore_world.item_template -// where ItemLevel >= 200 and Quality >= 3 and ItemLevel < 290 -// AND name NOT LIKE 'NPC Equip%' and name NOT LIKE 'OLD%' -// AND name NOT LIKE '%(test)%' AND name NOT LIKE '%Deprecated%' -// AND name NOT LIKE '%Monster - %' -// AND ((class = 2 and subclass IN(0,1,2,3,4,5,6,7,8,10,11,12,13,15,16,17,18,19)) or ((class = 4 AND subclass IN (1,2,3,4,6)))) -// ` -// err = sqlDb.Select(&items, sql) -// if err != nil { -// log.Printf("Failed to get items: %v", err) -// } - -// for _, item := range items { -// var statsList []int -// for i := 1; i <= 10; i++ { -// val, _ := item.GetField(fmt.Sprintf("StatValue%v", i)) -// statId, _ := item.GetField(fmt.Sprintf("StatType%v", i)) -// if val != 0 { -// statsList = append(statsList, statId) -// } -// } -// slices.Sort(statsList) -// statsListStr := ConvertIntSliceToString(statsList) -// log.Printf("StatList %s for Item %v", statsListStr, item.Name) -// _, err = liteDb.Exec("INSERT OR IGNORE INTO items (entry, class, name, Quality, itemLevel, subclass, stats_list) VALUES (?, ?, ?, ?, ?,?,?)", item.Entry, *item.Class, item.Name, *item.Quality, *item.ItemLevel, *item.Subclass, statsListStr) -// if err != nil { -// log.Printf("Failed to insert item %v: %v", item.Entry, err) -// } - -// } - -// log.Printf("Items: %v", len(items)) -// }