diff --git a/tools/database/atlasloot.go b/tools/database/atlasloot.go index 78b0bb4a59..1f76774636 100644 --- a/tools/database/atlasloot.go +++ b/tools/database/atlasloot.go @@ -12,7 +12,7 @@ import ( "github.com/wowsims/mop/tools" ) -func ReadAtlasLootData() *WowDatabase { +func ReadAtlasLootData(dbHelper *DBHelper) *WowDatabase { db := NewWowDatabase() // Read these in reverse order, because some items are listed in multiple expansions @@ -30,7 +30,7 @@ func ReadAtlasLootData() *WowDatabase { readAtlasLootDungeonData(db, proto.Expansion_ExpansionCata, "https://raw.githubusercontent.com/snowflame0/AtlasLootClassic_Cata/main/AtlasLootClassic_DungeonsAndRaids/data-cata.lua") readAtlasLootFactionData(db, "https://raw.githubusercontent.com/snowflame0/AtlasLootClassic_Cata/main/AtlasLootClassic_Factions/data-cata.lua") - readZoneData(db) + readZoneData(db, dbHelper) return db } @@ -312,31 +312,44 @@ func readAtlasLootFactionData(db *WowDatabase, srcUrl string) { // } // } -func readZoneData(db *WowDatabase) { +func readZoneData(db *WowDatabase, dbHelper *DBHelper) { zoneIDs := make([]int32, 0, len(db.Zones)) for zoneID := range db.Zones { zoneIDs = append(zoneIDs, zoneID) } - zoneIDStrs := core.MapSlice(zoneIDs, func(zoneID int32) string { return strconv.Itoa(int(zoneID)) }) - //Todo: This is the only place still needing the tooltip manager - //Remove it - zoneTM := &WowheadTooltipManager{ - TooltipManager{ - FilePath: "", - UrlPattern: "https://nether.wowhead.com/mop-classic/tooltip/zone/%s", - }, + + zoneNames, error := loadZones(dbHelper) + if error != nil { + panic(error) + } + + for _, zoneID := range zoneIDs { + db.Zones[zoneID].Name = zoneNames[zoneID] + } +} + +func loadZones(dbHelper *DBHelper) (map[int32]string, error) { + const query = `SELECT ID, AreaName_lang FROM AreaTable` + + rows, err := dbHelper.db.Query(query) + if err != nil { + return nil, fmt.Errorf("querying drop sources: %w", err) } - zoneTooltips := zoneTM.FetchFromWeb(zoneIDStrs) - - tooltipPattern := regexp.MustCompile(`{"name":"(.*?)",`) - for i, zoneID := range zoneIDs { - tooltip := zoneTooltips[zoneIDStrs[i]] - match := tooltipPattern.FindStringSubmatch(tooltip) - if match == nil { - log.Fatalf("Error parsing zone tooltip %s", tooltip) + defer rows.Close() + + var zoneId int32 + var zoneName string + namesByZone := make(map[int32]string) + for rows.Next() { + e := rows.Scan(&zoneId, &zoneName) + if e != nil { + return nil, e } - db.Zones[zoneID].Name = match[1] + + namesByZone[zoneId] = zoneName } + + return namesByZone, nil } var AtlasLootProfessionIDs = map[int]proto.Profession{ diff --git a/tools/database/gen_db/main.go b/tools/database/gen_db/main.go index abff13016e..f5c2403e19 100644 --- a/tools/database/gen_db/main.go +++ b/tools/database/gen_db/main.go @@ -40,7 +40,13 @@ func main() { inputsDir := fmt.Sprintf("%s/db_inputs", *outDir) if *genAsset == "atlasloot" { - db := database.ReadAtlasLootData() + helper, err := database.NewDBHelper() + if err != nil { + log.Fatalf("failed to initialize database: %v", err) + } + defer helper.Close() + + db := database.ReadAtlasLootData(helper) db.WriteJson(fmt.Sprintf("%s/atlasloot_db.json", inputsDir)) return } else if *genAsset == "reforge-stats" { diff --git a/tools/database/wowhead_tooltips.go b/tools/database/wowhead_tooltips.go deleted file mode 100644 index e94dfd5408..0000000000 --- a/tools/database/wowhead_tooltips.go +++ /dev/null @@ -1,709 +0,0 @@ -package database - -import ( - "encoding/json" - "fmt" - "log" - "regexp" - "strconv" - "strings" - - "github.com/wowsims/mop/sim/core" - "github.com/wowsims/mop/sim/core/proto" - "github.com/wowsims/mop/sim/core/stats" -) - -type WowheadTooltipManager struct { - TooltipManager -} - -func (wtm *WowheadTooltipManager) Read() map[int32]WowheadItemResponse { - strDB := wtm.TooltipManager.Read() - return core.MapMap(strDB, func(id int32, tooltip string) (int32, WowheadItemResponse) { - return id, NewWowheadItemResponse(id, tooltip) - }) -} - -func NewWowheadItemTooltipManager(filePath string) *WowheadTooltipManager { - return &WowheadTooltipManager{ - TooltipManager{ - FilePath: filePath, - UrlPattern: "https://nether.wowhead.com/mop-classic/tooltip/item/%s?lvl=90", - }, - } -} - -func NewWowheadSpellTooltipManager(filePath string) *WowheadTooltipManager { - return &WowheadTooltipManager{ - TooltipManager{ - FilePath: filePath, - UrlPattern: "https://nether.wowhead.com/mop-classic/tooltip/spell/%s", - }, - } -} - -type ItemResponse interface { - GetName() string - GetQuality() int - GetIcon() string - HasBuff() bool - TooltipWithoutSetBonus() string - GetTooltipRegexString(pattern *regexp.Regexp, matchIdx int) string - GetTooltipRegexValue(pattern *regexp.Regexp, matchIdx int) int - GetIntValue(pattern *regexp.Regexp) int - GetStats() stats.Stats - IsEquippable() bool - GetItemLevel() int - GetPhase() int - GetUnique() bool - GetItemType() proto.ItemType - GetArmorType() proto.ArmorType - GetWeaponType() proto.WeaponType - GetHandType() proto.HandType - GetRangedWeaponType() proto.RangedWeaponType - GetWeaponDamage() (float64, float64) - GetWeaponSpeed() float64 - GetGemSockets() []proto.GemColor - GetSocketBonus() stats.Stats - GetSocketColor() proto.GemColor - GetGemStats() stats.Stats - GetItemSetName() string - IsHeroic() bool - GetRequiredProfession() proto.Profession -} - -type WowheadItemResponse struct { - ID int32 - Name string `json:"name"` - Quality int `json:"quality"` - Icon string `json:"icon"` - Tooltip string `json:"tooltip"` - Buff string `json:"buff"` -} - -func NewWowheadItemResponse(id int32, tooltip string) WowheadItemResponse { - response := WowheadItemResponse{} - err := json.Unmarshal([]byte(tooltip), &response) - if err != nil { - fmt.Printf("Failed to decode tooltipBytes: %s\n", tooltip) - panic(err) - } - response.ID = id - return response -} - -func (item WowheadItemResponse) GetName() string { - return item.Name -} -func (item WowheadItemResponse) GetQuality() int { - return item.Quality -} -func (item WowheadItemResponse) GetIcon() string { - return item.Icon -} -func (item WowheadItemResponse) HasBuff() bool { - return item.Buff != "" -} - -func GetRegexStringValue(srcStr string, pattern *regexp.Regexp, matchIdx int) string { - match := pattern.FindStringSubmatch(srcStr) - if match == nil { - return "" - } else { - return match[matchIdx] - } -} -func GetRegexIntValue(srcStr string, pattern *regexp.Regexp, matchIdx int) int { - matchStr := GetRegexStringValue(srcStr, pattern, matchIdx) - matchStr = strings.Replace(matchStr, ",", "", -1) - - val, err := strconv.Atoi(matchStr) - if err != nil { - return 0 - } - - return val -} -func GetBestRegexIntValue(srcStr string, patterns []*regexp.Regexp, matchIdx int) int { - best := 0 - for _, pattern := range patterns { - newVal := GetRegexIntValue(srcStr, pattern, matchIdx) - if newVal > best { - best = newVal - } - } - return best -} - -func (item WowheadItemResponse) TooltipWithoutSetBonus() string { - setIdx := strings.Index(item.Tooltip, "Set : ") - if setIdx == -1 { - return item.Tooltip - } else { - return item.Tooltip[:setIdx] - } -} - -func (item WowheadItemResponse) GetTooltipRegexString(pattern *regexp.Regexp, matchIdx int) string { - return GetRegexStringValue(item.TooltipWithoutSetBonus(), pattern, matchIdx) -} - -func (item WowheadItemResponse) GetTooltipRegexValue(pattern *regexp.Regexp, matchIdx int) int { - return GetRegexIntValue(item.TooltipWithoutSetBonus(), pattern, matchIdx) -} - -func (item WowheadItemResponse) GetIntValue(pattern *regexp.Regexp) int { - return item.GetTooltipRegexValue(pattern, 1) -} - -var expansionRegex = regexp.MustCompile(`(tbc|wotlk|cata|mop)`) - -var armorRegex = regexp.MustCompile(`([0-9]+) Armor`) -var agilityRegex = regexp.MustCompile(`\+([0-9]+) Agility`) -var strengthRegex = regexp.MustCompile(`\+([0-9]+) Strength`) -var intellectRegex = regexp.MustCompile(`\+([0-9]+) Intellect`) -var spiritRegex = regexp.MustCompile(`\+([0-9]+) Spirit`) -var staminaRegex = regexp.MustCompile(`\+([0-9]+) Stamina`) -var spellPowerRegex = regexp.MustCompile(`Increases spell power by ([0-9]{1,3}(,[0-9]{3})*)\.`) -var spellPowerRegex2 = regexp.MustCompile(`Increases spell power by ([0-9]{1,3}(,[0-9]{3})*)\.`) -var masteryRegex = regexp.MustCompile(`([0-9]+)\s*Mastery`) - -/* -// Not sure these exist anymore? -var arcaneSpellPowerRegex = regexp.MustCompile(`Increases Arcane power by ([0-9]+)\.`) -var fireSpellPowerRegex = regexp.MustCompile(`Increases Fire power by ([0-9]+)\.`) -var frostSpellPowerRegex = regexp.MustCompile(`Increases Frost power by ([0-9]+)\.`) -var holySpellPowerRegex = regexp.MustCompile(`Increases Holy power by ([0-9]+)\.`) -var natureSpellPowerRegex = regexp.MustCompile(`Increases Nature power by ([0-9]+)\.`) -var shadowSpellPowerRegex = regexp.MustCompile(`Increases Shadow power by ([0-9]+)\.`) -*/ - -var hitRegex = regexp.MustCompile(`Improves hit rating by ([0-9]+)\.`) -var critRegex = regexp.MustCompile(`Improves critical strike rating by ([0-9]+)\.`) -var hasteRegex = regexp.MustCompile(`Improves haste rating by ([0-9]+)\.`) - -var spellPenetrationRegex = regexp.MustCompile(`Increases your spell penetration by ([0-9]+)\.`) -var mp5Regex = regexp.MustCompile(`Restores ([0-9]+) mana per 5 sec\.`) -var attackPowerRegex = regexp.MustCompile(`Increases attack power by ([0-9]+)\.`) -var attackPowerRegex2 = regexp.MustCompile(`Increases attack power by ([0-9]+)\.`) - -var rangedAttackPowerRegex = regexp.MustCompile(`Increases ranged attack power by ([0-9]+)\.`) -var rangedAttackPowerRegex2 = regexp.MustCompile(`Increases ranged attack power by ([0-9]+)\.`) - -var armorPenetrationRegex = regexp.MustCompile(`Increases armor penetration rating by ([0-9]+)`) -var armorPenetrationRegex2 = regexp.MustCompile(`Increases your armor penetration by ([0-9]+)\.`) - -var expertiseRegex = regexp.MustCompile(`Increases your expertise rating by ([0-9]+)\.`) -var weaponDamageRegex = regexp.MustCompile(`([0-9]+) - ([0-9]+)`) -var weaponDamageRegex2 = regexp.MustCompile(`([0-9]+) Damage`) -var weaponSpeedRegex = regexp.MustCompile(`(([0-9]+).([0-9]+))`) - -var defenseRegex = regexp.MustCompile(`Increases defense rating by ([0-9]+)\.`) -var defenseRegex2 = regexp.MustCompile(`Increases defense rating by ([0-9]+)\.`) -var blockRegex = regexp.MustCompile(`Increases your shield block rating by ([0-9]+)\.`) -var blockRegex2 = regexp.MustCompile(`Increases your shield block rating by ([0-9]+)\.`) -var dodgeRegex = regexp.MustCompile(`Increases your dodge rating by ([0-9]+)\.`) -var dodgeRegex2 = regexp.MustCompile(`Increases your dodge rating by ([0-9]+)\.`) -var parryRegex = regexp.MustCompile(`Increases your parry rating by ([0-9]+)\.`) -var parryRegex2 = regexp.MustCompile(`Increases your parry rating by ([0-9]+)\.`) -var resilienceRegex = regexp.MustCompile(`Improves your resilience rating by ([0-9]+)\.`) -var arcaneResistanceRegex = regexp.MustCompile(`\+([0-9]+) Arcane Resistance`) -var fireResistanceRegex = regexp.MustCompile(`\+([0-9]+) Fire Resistance`) -var frostResistanceRegex = regexp.MustCompile(`\+([0-9]+) Frost Resistance`) -var natureResistanceRegex = regexp.MustCompile(`\+([0-9]+) Nature Resistance`) -var shadowResistanceRegex = regexp.MustCompile(`\+([0-9]+) Shadow Resistance`) -var bonusArmorRegex = regexp.MustCompile(`Has ([0-9]+) bonus armor`) -var bonusArmorRegex2 = regexp.MustCompile(`([\d,\.]+) Bonus Armor`) - -func (item WowheadItemResponse) GetStats() stats.Stats { - sp := float64(item.GetIntValue(spellPowerRegex)) + float64(item.GetIntValue(spellPowerRegex2)) - baseAP := float64(item.GetIntValue(attackPowerRegex)) + float64(item.GetIntValue(attackPowerRegex2)) - armor, bonusArmor := item.GetArmorValues() - return stats.Stats{ - stats.Armor: float64(armor), - stats.BonusArmor: float64(bonusArmor), - stats.Strength: float64(item.GetIntValue(strengthRegex)), - stats.Agility: float64(item.GetIntValue(agilityRegex)), - stats.Stamina: float64(item.GetIntValue(staminaRegex)), - stats.Intellect: float64(item.GetIntValue(intellectRegex)), - stats.Spirit: float64(item.GetIntValue(spiritRegex)), - stats.SpellPower: sp, - stats.HitRating: float64(item.GetIntValue(hitRegex)), - stats.CritRating: float64(item.GetIntValue(critRegex)), - stats.HasteRating: float64(item.GetIntValue(hasteRegex)), - stats.SpellPenetration: float64(item.GetIntValue(spellPenetrationRegex)), - stats.MP5: float64(item.GetIntValue(mp5Regex)), - stats.AttackPower: baseAP, - stats.RangedAttackPower: baseAP + float64(item.GetIntValue(rangedAttackPowerRegex)) + float64(item.GetIntValue(rangedAttackPowerRegex2)), - stats.ExpertiseRating: float64(item.GetIntValue(expertiseRegex)), - stats.DodgeRating: float64(item.GetIntValue(dodgeRegex) + item.GetIntValue(dodgeRegex2)), - stats.ParryRating: float64(item.GetIntValue(parryRegex) + item.GetIntValue(parryRegex2)), - stats.ResilienceRating: float64(item.GetIntValue(resilienceRegex)), - stats.ArcaneResistance: float64(item.GetIntValue(arcaneResistanceRegex)), - stats.FireResistance: float64(item.GetIntValue(fireResistanceRegex)), - stats.FrostResistance: float64(item.GetIntValue(frostResistanceRegex)), - stats.NatureResistance: float64(item.GetIntValue(natureResistanceRegex)), - stats.ShadowResistance: float64(item.GetIntValue(shadowResistanceRegex)), - stats.MasteryRating: float64(item.GetIntValue(masteryRegex)), - } -} - -var patternRegexes = []*regexp.Regexp{ - regexp.MustCompile(`Design:`), - regexp.MustCompile(`Recipe:`), - regexp.MustCompile(`Pattern:`), - regexp.MustCompile(`Plans:`), - regexp.MustCompile(`Schematic:`), -} - -func (item WowheadItemResponse) IsPattern() bool { - for _, pattern := range patternRegexes { - if pattern.MatchString(item.Tooltip) { - return true - } - } - return false -} - -var randomEnchantRegex = regexp.MustCompile(`Random enchantment`) - -func (item WowheadItemResponse) IsRandomEnchant() bool { - return randomEnchantRegex.MatchString(item.Tooltip) -} - -func (item WowheadItemResponse) IsEquippable() bool { - return item.GetItemType() != proto.ItemType_ItemTypeUnknown && - !item.IsPattern() && - item.GetItemLevel() <= 416 -} - -var itemLevelRegex = regexp.MustCompile(`Item Level ([0-9]+)<`) - -func (item WowheadItemResponse) GetItemLevel() int { - return item.GetIntValue(itemLevelRegex) -} - -var phaseRegex = regexp.MustCompile(`Phase ([0-9])`) - -func (item WowheadItemResponse) GetPhase() int { - phase := item.GetIntValue(phaseRegex) - if phase != 0 { - return phase - } - - ilvl := item.GetItemLevel() - if ilvl <= 284 { // TBC items - return 0 - } else { - return 1 - } -} - -var uniqueRegex = regexp.MustCompile(`Unique`) -var jcGemsRegex = regexp.MustCompile(`Jeweler's Gems`) - -func (item WowheadItemResponse) GetUnique() bool { - return uniqueRegex.MatchString(item.Tooltip) && !jcGemsRegex.MatchString(item.Tooltip) -} - -var itemTypePatterns = map[proto.ItemType]*regexp.Regexp{ - proto.ItemType_ItemTypeHead: regexp.MustCompile(`Head`), - proto.ItemType_ItemTypeNeck: regexp.MustCompile(`Neck`), - proto.ItemType_ItemTypeShoulder: regexp.MustCompile(`Shoulder`), - proto.ItemType_ItemTypeBack: regexp.MustCompile(`Back`), - proto.ItemType_ItemTypeChest: regexp.MustCompile(`Chest`), - proto.ItemType_ItemTypeWrist: regexp.MustCompile(`Wrist`), - proto.ItemType_ItemTypeHands: regexp.MustCompile(`Hands`), - proto.ItemType_ItemTypeWaist: regexp.MustCompile(`Waist`), - proto.ItemType_ItemTypeLegs: regexp.MustCompile(`Legs`), - proto.ItemType_ItemTypeFeet: regexp.MustCompile(`Feet`), - proto.ItemType_ItemTypeFinger: regexp.MustCompile(`Finger`), - proto.ItemType_ItemTypeTrinket: regexp.MustCompile(`Trinket`), - proto.ItemType_ItemTypeWeapon: regexp.MustCompile(`((Main Hand)|(Two-Hand)|(One-Hand)|(Off Hand)|(Held In Off-hand)|(Held In Off-Hand))`), - proto.ItemType_ItemTypeRanged: regexp.MustCompile(`(Ranged|Thrown|Relic)`), -} - -func (item WowheadItemResponse) GetItemType() proto.ItemType { - for itemType, pattern := range itemTypePatterns { - if pattern.MatchString(item.Tooltip) { - return itemType - } - } - return proto.ItemType_ItemTypeUnknown -} - -func (item WowheadItemResponse) IsScalableArmorSlot() bool { - // Special case shields as Base Armor - if item.GetWeaponType() == proto.WeaponType_WeaponTypeShield { - return true - } - - itemType := item.GetItemType() - switch itemType { - case - proto.ItemType_ItemTypeNeck, - proto.ItemType_ItemTypeFinger, - proto.ItemType_ItemTypeTrinket, - proto.ItemType_ItemTypeWeapon: - return false - } - return true -} - -func (item WowheadItemResponse) GetArmorValues() (int, int) { - armorValue := item.GetIntValue(armorRegex) - bonusArmorValue1 := item.GetIntValue(bonusArmorRegex) - bonusArmorValue2 := item.GetIntValue(bonusArmorRegex2) - bonusArmorValue := bonusArmorValue1 + bonusArmorValue2 - - if item.IsScalableArmorSlot() { - armorValue -= bonusArmorValue1 - } else if bonusArmorValue2 == 0 { - bonusArmorValue = armorValue - armorValue = 0 - } - - return armorValue, bonusArmorValue -} - -var armorTypePatterns = map[proto.ArmorType]*regexp.Regexp{ - proto.ArmorType_ArmorTypeCloth: regexp.MustCompile(`(?:)?Cloth`), - proto.ArmorType_ArmorTypeLeather: regexp.MustCompile(`(?:)?Leather`), - proto.ArmorType_ArmorTypeMail: regexp.MustCompile(`(?:)?Mail`), - proto.ArmorType_ArmorTypePlate: regexp.MustCompile(`(?:)?Plate`), -} - -func (item WowheadItemResponse) GetArmorType() proto.ArmorType { - for armorType, pattern := range armorTypePatterns { - if pattern.MatchString(item.Tooltip) { - return armorType - } - } - return proto.ArmorType_ArmorTypeUnknown -} - -var weaponTypePatterns = map[proto.WeaponType]*regexp.Regexp{ - proto.WeaponType_WeaponTypeAxe: regexp.MustCompile(`Axe`), - proto.WeaponType_WeaponTypeDagger: regexp.MustCompile(`Dagger`), - proto.WeaponType_WeaponTypeFist: regexp.MustCompile(`Fist Weapon`), - proto.WeaponType_WeaponTypeMace: regexp.MustCompile(`Mace`), - proto.WeaponType_WeaponTypeOffHand: regexp.MustCompile(`Held In Off-hand`), - proto.WeaponType_WeaponTypePolearm: regexp.MustCompile(`Polearm`), - proto.WeaponType_WeaponTypeShield: regexp.MustCompile(`Shield`), - proto.WeaponType_WeaponTypeStaff: regexp.MustCompile(`Staff`), - proto.WeaponType_WeaponTypeSword: regexp.MustCompile(`Sword`), -} - -func (item WowheadItemResponse) GetWeaponType() proto.WeaponType { - for weaponType, pattern := range weaponTypePatterns { - if pattern.MatchString(item.Tooltip) { - return weaponType - } - } - return proto.WeaponType_WeaponTypeUnknown -} - -var handTypePatterns = map[proto.HandType]*regexp.Regexp{ - proto.HandType_HandTypeMainHand: regexp.MustCompile(`Main Hand`), - proto.HandType_HandTypeOneHand: regexp.MustCompile(`One-Hand`), - proto.HandType_HandTypeOffHand: regexp.MustCompile(`((Off Hand)|(Held In Off-hand)|(Held In Off-Hand))`), - proto.HandType_HandTypeTwoHand: regexp.MustCompile(`Two-Hand`), -} - -func (item WowheadItemResponse) GetHandType() proto.HandType { - for handType, pattern := range handTypePatterns { - if pattern.MatchString(item.Tooltip) { - return handType - } - } - return proto.HandType_HandTypeUnknown -} - -var rangedWeaponTypePatterns = map[proto.RangedWeaponType]*regexp.Regexp{ - proto.RangedWeaponType_RangedWeaponTypeBow: regexp.MustCompile(`Bow`), - proto.RangedWeaponType_RangedWeaponTypeCrossbow: regexp.MustCompile(`Crossbow`), - proto.RangedWeaponType_RangedWeaponTypeGun: regexp.MustCompile(`Gun`), - proto.RangedWeaponType_RangedWeaponTypeRelic: regexp.MustCompile(`Relic`), - proto.RangedWeaponType_RangedWeaponTypeThrown: regexp.MustCompile(`Thrown`), - proto.RangedWeaponType_RangedWeaponTypeWand: regexp.MustCompile(`Wand`), -} - -func (item WowheadItemResponse) GetRangedWeaponType() proto.RangedWeaponType { - for rangedWeaponType, pattern := range rangedWeaponTypePatterns { - if pattern.MatchString(item.Tooltip) { - return rangedWeaponType - } - } - return proto.RangedWeaponType_RangedWeaponTypeUnknown -} - -// Returns min/max of weapon damage -func (item WowheadItemResponse) GetWeaponDamage() (float64, float64) { - noCommas := strings.ReplaceAll(item.Tooltip, ",", "") - if matches := weaponDamageRegex.FindStringSubmatch(noCommas); len(matches) > 0 { - min, err := strconv.ParseFloat(matches[1], 64) - if err != nil { - log.Fatalf("Failed to parse weapon damage: %s", err) - } - max, err := strconv.ParseFloat(matches[2], 64) - if err != nil { - log.Fatalf("Failed to parse weapon damage: %s", err) - } - if min > max { - log.Fatalf("Invalid weapon damage for item %s: min = %0.1f, max = %0.1f", item.Name, min, max) - } - return min, max - } else if matches := weaponDamageRegex2.FindStringSubmatch(noCommas); len(matches) > 0 { - val, err := strconv.ParseFloat(matches[1], 64) - if err != nil { - log.Fatalf("Failed to parse weapon damage: %s", err) - } - return val, val - } - return 0, 0 -} - -func (item WowheadItemResponse) GetWeaponSpeed() float64 { - if matches := weaponSpeedRegex.FindStringSubmatch(item.Tooltip); len(matches) > 0 { - speed, err := strconv.ParseFloat(matches[1], 64) - if err != nil { - log.Fatalf("Failed to parse weapon damage: %s", err) - } - return speed - } - return 0 -} - -var gemColorsRegex = regexp.MustCompile("(Meta|Yellow|Blue|Red|Cogwheel|Prismatic) Socket") - -func (item WowheadItemResponse) GetGemSockets() []proto.GemColor { - matches := gemColorsRegex.FindAllStringSubmatch(item.Tooltip, -1) - if matches == nil { - return []proto.GemColor{} - } - - numSockets := len(matches) - gemColors := make([]proto.GemColor, numSockets) - for socketIdx, match := range matches { - gemColorName := "GemColor" + match[1] - gemColors[socketIdx] = proto.GemColor(proto.GemColor_value[gemColorName]) - } - return gemColors -} - -var socketBonusRegex = regexp.MustCompile(`Socket Bonus: (.*?)`) -var strengthSocketBonusRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Strength`)} -var agilitySocketBonusRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Agility`)} -var staminaSocketBonusRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Stamina`)} -var intellectSocketBonusRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Intellect`)} -var spiritSocketBonusRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Spirit`)} -var spellPowerSocketBonusRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Spell Power`)} -var spellHitSocketBonusRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Hit Rating`)} -var spellCritSocketBonusRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Critical Strike Rating`)} -var hasteSocketBonusRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Haste Rating`)} -var mp5SocketBonusRegexes = []*regexp.Regexp{ - regexp.MustCompile(`([0-9]+) Mana per 5 sec`), - regexp.MustCompile(`([0-9]+) mana per 5 sec`), -} -var attackPowerSocketBonusRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Attack Power`)} -var armorPenSocketBonusRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Armor Penetration Rating`)} -var expertiseSocketBonusRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Expertise Rating`)} -var blockSocketBonusRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Block Rating`)} -var dodgeSocketBonusRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Dodge Rating`)} -var parrySocketBonusRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Parry Rating`)} -var resilienceSocketBonusRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Resilience Rating`)} -var masterySocketBonusRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Mastery Rating`)} - -func (item WowheadItemResponse) GetSocketBonus() stats.Stats { - match := socketBonusRegex.FindStringSubmatch(item.Tooltip) - if match == nil { - return stats.Stats{} - } - - bonusStr := match[1] - //fmt.Printf("\n%s\n", bonusStr) - - stats := stats.Stats{ - stats.Strength: float64(GetBestRegexIntValue(bonusStr, strengthSocketBonusRegexes, 1)), - stats.Agility: float64(GetBestRegexIntValue(bonusStr, agilitySocketBonusRegexes, 1)), - stats.Stamina: float64(GetBestRegexIntValue(bonusStr, staminaSocketBonusRegexes, 1)), - stats.Intellect: float64(GetBestRegexIntValue(bonusStr, intellectSocketBonusRegexes, 1)), - stats.Spirit: float64(GetBestRegexIntValue(bonusStr, spiritSocketBonusRegexes, 1)), - stats.HasteRating: float64(GetBestRegexIntValue(bonusStr, hasteSocketBonusRegexes, 1)), - stats.SpellPower: float64(GetBestRegexIntValue(bonusStr, spellPowerSocketBonusRegexes, 1)), - stats.HitRating: float64(GetBestRegexIntValue(bonusStr, spellHitSocketBonusRegexes, 1)), - stats.CritRating: float64(GetBestRegexIntValue(bonusStr, spellCritSocketBonusRegexes, 1)), - stats.MP5: float64(GetBestRegexIntValue(bonusStr, mp5SocketBonusRegexes, 1)), - stats.AttackPower: float64(GetBestRegexIntValue(bonusStr, attackPowerSocketBonusRegexes, 1)), - stats.RangedAttackPower: float64(GetBestRegexIntValue(bonusStr, attackPowerSocketBonusRegexes, 1)), - stats.ExpertiseRating: float64(GetBestRegexIntValue(bonusStr, expertiseSocketBonusRegexes, 1)), - stats.DodgeRating: float64(GetBestRegexIntValue(bonusStr, dodgeSocketBonusRegexes, 1)), - stats.ParryRating: float64(GetBestRegexIntValue(bonusStr, parrySocketBonusRegexes, 1)), - stats.ResilienceRating: float64(GetBestRegexIntValue(bonusStr, resilienceSocketBonusRegexes, 1)), - stats.MasteryRating: float64(GetBestRegexIntValue(bonusStr, masterySocketBonusRegexes, 1)), - } - - return stats -} - -var gemSocketColorPatterns = map[proto.GemColor]*regexp.Regexp{ - proto.GemColor_GemColorMeta: regexp.MustCompile(`Only fits in a meta gem slot\.`), - proto.GemColor_GemColorBlue: regexp.MustCompile(`Matches a Blue ([Ss])ocket\.`), - proto.GemColor_GemColorRed: regexp.MustCompile(`Matches a Red [Ss]ocket\.`), - proto.GemColor_GemColorYellow: regexp.MustCompile(`Matches a Yellow [Ss]ocket\.`), - proto.GemColor_GemColorOrange: regexp.MustCompile(`Matches a ((Yellow)|(Red)) or ((Yellow)|(Red)) [Ss]ocket\.`), - proto.GemColor_GemColorPurple: regexp.MustCompile(`Matches a ((Blue)|(Red)) or ((Blue)|(Red)) [Ss]ocket\.`), - proto.GemColor_GemColorGreen: regexp.MustCompile(`Matches a ((Yellow)|(Blue)) or ((Yellow)|(Blue)) [Ss]ocket\.`), - proto.GemColor_GemColorPrismatic: regexp.MustCompile(`(Matches any [Ss]ocket)|(Matches a Red, Yellow or Blue [Ss]ocket)`), - proto.GemColor_GemColorCogwheel: regexp.MustCompile(`Only fits in a Cogwheel socket.`), -} - -func (item WowheadItemResponse) GetSocketColor() proto.GemColor { - for socketColor, pattern := range gemSocketColorPatterns { - if pattern.MatchString(item.Tooltip) { - return socketColor - } - } - // fmt.Printf("Could not find socket color for gem %s\n", item.Name) - return proto.GemColor_GemColorUnknown -} -func (item WowheadItemResponse) IsGem() bool { - return item.GetSocketColor() != proto.GemColor_GemColorUnknown && - !strings.Contains(item.GetName(), "Design:") -} -func (item WowheadItemResponse) ToItemProto() *proto.UIItem { - weaponDamageMin, weaponDamageMax := item.GetWeaponDamage() - return &proto.UIItem{ - Id: item.ID, - Name: item.GetName(), - Icon: item.GetIcon(), - - Type: item.GetItemType(), - ArmorType: item.GetArmorType(), - WeaponType: item.GetWeaponType(), - HandType: item.GetHandType(), - RangedWeaponType: item.GetRangedWeaponType(), - - Stats: item.GetStats().ToProtoArray(), - GemSockets: item.GetGemSockets(), - SocketBonus: item.GetSocketBonus().ToProtoArray(), - - WeaponDamageMin: weaponDamageMin, - WeaponDamageMax: weaponDamageMax, - WeaponSpeed: item.GetWeaponSpeed(), - - Ilvl: int32(item.GetItemLevel()), - Phase: int32(item.GetPhase()), - Quality: proto.ItemQuality(item.GetQuality()), - Unique: item.GetUnique(), - Heroic: item.IsHeroic(), - - RequiredProfession: item.GetRequiredProfession(), - SetName: item.GetItemSetName(), - } -} -func (item WowheadItemResponse) ToGemProto() *proto.UIGem { - return &proto.UIGem{ - Id: item.ID, - Name: item.GetName(), - Icon: item.GetIcon(), - Color: item.GetSocketColor(), - - Stats: item.GetGemStats().ToProtoArray(), - - Phase: int32(item.GetPhase()), - Quality: proto.ItemQuality(item.GetQuality()), - Unique: item.GetUnique(), - RequiredProfession: item.GetRequiredProfession(), - } -} - -var strengthGemStatRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Strength`), regexp.MustCompile(`\+([0-9]+) (to )?All Stats`)} -var agilityGemStatRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Agility`), regexp.MustCompile(`\+([0-9]+) (to )?All Stats`)} -var staminaGemStatRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Stamina`), regexp.MustCompile(`\+([0-9]+) (to )?All Stats`)} -var intellectGemStatRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Intellect`), regexp.MustCompile(`\+([0-9]+) (to )?All Stats`)} -var spiritGemStatRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Spirit`), regexp.MustCompile(`\+([0-9]+) (to )?All Stats`)} -var spellPowerGemStatRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Spell Power`)} -var hitGemStatRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Hit Rating`)} -var critGemStatRegexes = []*regexp.Regexp{ - regexp.MustCompile(`\+([0-9]+) Crit Rating`), - regexp.MustCompile(`\+([0-9]+) Critical Strike Rating`), - regexp.MustCompile(`\+([0-9]+) Critical`), -} -var hasteGemStatRegexes = []*regexp.Regexp{ - regexp.MustCompile(`\+([0-9]+) Haste Rating`), -} -var armorPenetrationGemStatRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Armor Penetration`)} -var spellPenetrationGemStatRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Spell Penetration`)} -var mp5GemStatRegexes = []*regexp.Regexp{ - regexp.MustCompile(`([0-9]+) Mana per 5 sec`), - regexp.MustCompile(`([0-9]+) mana per 5 sec`), - regexp.MustCompile(`([0-9]+) Mana every 5 seconds`), -} -var attackPowerGemStatRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Attack Power`)} -var expertiseGemStatRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Expertise Rating`)} -var dodgeGemStatRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Dodge Rating`)} -var parryGemStatRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Parry Rating`)} -var resilienceGemStatRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Resilience Rating`)} -var allResistGemStatRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Resist All`)} -var masteryGemStatRegexes = []*regexp.Regexp{regexp.MustCompile(`\+([0-9]+) Mastery Rating`)} //https://www.wowhead.com/mop-classic/spell=101748/zen-elven-peridot - -func (item WowheadItemResponse) GetGemStats() stats.Stats { - stats := stats.Stats{ - stats.Strength: float64(GetBestRegexIntValue(item.Tooltip, strengthGemStatRegexes, 1)), - stats.Agility: float64(GetBestRegexIntValue(item.Tooltip, agilityGemStatRegexes, 1)), - stats.Stamina: float64(GetBestRegexIntValue(item.Tooltip, staminaGemStatRegexes, 1)), - stats.Intellect: float64(GetBestRegexIntValue(item.Tooltip, intellectGemStatRegexes, 1)), - stats.Spirit: float64(GetBestRegexIntValue(item.Tooltip, spiritGemStatRegexes, 1)), - - stats.HitRating: float64(GetBestRegexIntValue(item.Tooltip, hitGemStatRegexes, 1)), - stats.CritRating: float64(GetBestRegexIntValue(item.Tooltip, critGemStatRegexes, 1)), - stats.HasteRating: float64(GetBestRegexIntValue(item.Tooltip, hasteGemStatRegexes, 1)), - stats.MasteryRating: float64(GetBestRegexIntValue(item.Tooltip, masteryGemStatRegexes, 1)), - - stats.SpellPower: float64(GetBestRegexIntValue(item.Tooltip, spellPowerGemStatRegexes, 1)), - stats.AttackPower: float64(GetBestRegexIntValue(item.Tooltip, attackPowerGemStatRegexes, 1)), - stats.RangedAttackPower: float64(GetBestRegexIntValue(item.Tooltip, attackPowerGemStatRegexes, 1)), - stats.SpellPenetration: float64(GetBestRegexIntValue(item.Tooltip, spellPenetrationGemStatRegexes, 1)), - stats.MP5: float64(GetBestRegexIntValue(item.Tooltip, mp5GemStatRegexes, 1)), - stats.ExpertiseRating: float64(GetBestRegexIntValue(item.Tooltip, expertiseGemStatRegexes, 1)), - stats.DodgeRating: float64(GetBestRegexIntValue(item.Tooltip, dodgeGemStatRegexes, 1)), - stats.ParryRating: float64(GetBestRegexIntValue(item.Tooltip, parryGemStatRegexes, 1)), - stats.ResilienceRating: float64(GetBestRegexIntValue(item.Tooltip, resilienceGemStatRegexes, 1)), - stats.ArcaneResistance: float64(GetBestRegexIntValue(item.Tooltip, allResistGemStatRegexes, 1)), - stats.FireResistance: float64(GetBestRegexIntValue(item.Tooltip, allResistGemStatRegexes, 1)), - stats.FrostResistance: float64(GetBestRegexIntValue(item.Tooltip, allResistGemStatRegexes, 1)), - stats.NatureResistance: float64(GetBestRegexIntValue(item.Tooltip, allResistGemStatRegexes, 1)), - stats.ShadowResistance: float64(GetBestRegexIntValue(item.Tooltip, allResistGemStatRegexes, 1)), - } - - return stats -} - -var itemSetNameRegex = regexp.MustCompile(fmt.Sprintf(`([^<]+)<`, expansionRegex)) - -func (item WowheadItemResponse) GetItemSetName() string { - original := item.GetTooltipRegexString(itemSetNameRegex, 4) - - // Strip out the 10/25 man prefixes from set names - withoutTier := strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(original, "Heroes' "), "Valorous "), "Conqueror's "), "Triumphant "), "Sanctified ") - if original != withoutTier { // if we found a tier prefix, return now. - return withoutTier - } - - // Now strip out the season prefix from any pvp set names - withoutPvp := strings.Replace(strings.Replace(strings.Replace(strings.Replace(strings.Replace(strings.Replace(original, "Savage Glad", "Glad", 1), "Hateful Glad", "Glad", 1), "Deadly Glad", "Glad", 1), "Furious Glad", "Glad", 1), "Relentless Glad", "Glad", 1), "Wrathful Glad", "Glad", 1) - return withoutPvp -} - -func (item WowheadItemResponse) IsHeroic() bool { - return strings.Contains(item.Tooltip, "Heroic") -} - -func (item WowheadItemResponse) GetRequiredProfession() proto.Profession { - if jcGemsRegex.MatchString(item.Tooltip) { - return proto.Profession_Jewelcrafting - } - - return proto.Profession_ProfessionUnknown -}