mirror of
https://github.com/araxiaonline/wow-item-generator.git
synced 2026-06-13 03:02:22 -04:00
bug: fixes to items and adds and new template for item gen.
This commit is contained in:
@@ -415,6 +415,11 @@ func (item Item) GetDPS() (float64, error) {
|
||||
|
||||
// Scales and items dps damage numbers based on a desired item level.
|
||||
func (item *Item) ScaleDPS(oldLevel, level int) (float64, error) {
|
||||
return item.ScaleDPSWithPhase(oldLevel, level, 0)
|
||||
}
|
||||
|
||||
// ScaleDPSWithPhase scales DPS with phase-based modifiers
|
||||
func (item *Item) ScaleDPSWithPhase(oldLevel, level, phase int) (float64, error) {
|
||||
if item.ItemLevel == nil {
|
||||
return 0, fmt.Errorf("ItemLevel is not set")
|
||||
}
|
||||
@@ -432,7 +437,23 @@ func (item *Item) ScaleDPS(oldLevel, level int) (float64, error) {
|
||||
scalingFactor := math.Pow(float64(level)/float64(oldLevel), 1.012)
|
||||
|
||||
dps := modifier * float64(level) * scalingFactor
|
||||
adjDps := (dps * (*item.Delay / 1000) / 100)
|
||||
|
||||
// Remove variance for weapons to let phase scaling work properly
|
||||
varianceMultiplier := 1.0
|
||||
|
||||
// Add phase-based DPS modifier (higher phases = higher DPS)
|
||||
// Phase 0: base DPS, Phase 1: -15%, Phase 2: -10%, Phase 3: -5%, Phase 4: base, Phase 5: +5%
|
||||
phaseMultiplier := 1.0
|
||||
if phase > 0 {
|
||||
// Start from -20% at phase 1 and increase by +5% per phase
|
||||
phaseBonus := -0.20 + (float64(phase) * 0.05)
|
||||
phaseMultiplier = 1.0 + phaseBonus
|
||||
}
|
||||
|
||||
adjDps := (dps * (*item.Delay / 1000) / 100) * varianceMultiplier * phaseMultiplier
|
||||
|
||||
log.Printf("DPS DEBUG: phase=%d, phaseMultiplier=%.3f, baseDPS=%.3f, adjDps=%.3f",
|
||||
phase, phaseMultiplier, (dps * (*item.Delay / 1000) / 100), adjDps)
|
||||
|
||||
// Use deterministic values based on item entry instead of random values
|
||||
// We'll use the item entry to derive consistent min/max modifiers
|
||||
@@ -672,6 +693,10 @@ func (item *Item) ApplyStats(otherItem Item) (success bool, err error) {
|
||||
// Ceiling of ((ItemLevel * QualityModifier * ItemTypeModifier)^1.7095 * %ofStats) ^ (1/1.7095)) / StatModifier
|
||||
// i.e) Green Strength Helmet (((100 * 1.1 * 1.0)^1.705) * 1)^(1/1.7095) / 1.0 = 110 Strength on item
|
||||
func (item *Item) ScaleItem(itemLevel int, itemQuality int) (bool, error) {
|
||||
return item.ScaleItemWithPhase(itemLevel, itemQuality, 0)
|
||||
}
|
||||
|
||||
func (item *Item) ScaleItemWithPhase(itemLevel int, itemQuality int, phase int) (bool, error) {
|
||||
var allSpellStats []spells.ConvItemStat
|
||||
if item.ItemLevel == nil {
|
||||
return false, errors.New("field itemLevel is not set")
|
||||
@@ -755,7 +780,7 @@ func (item *Item) ScaleItem(itemLevel int, itemQuality int) (bool, error) {
|
||||
log.Printf("Failed to get DPS: %v", err)
|
||||
}
|
||||
|
||||
dps, err := item.ScaleDPS(fromItemLevel, itemLevel)
|
||||
dps, err := item.ScaleDPSWithPhase(fromItemLevel, itemLevel, phase)
|
||||
if err != nil {
|
||||
log.Printf("Failed to scale DPS: %v", err)
|
||||
return false, err
|
||||
@@ -1324,12 +1349,16 @@ func (item *Item) ApplyTierModifiers(optionalTier ...int) {
|
||||
tier = 0
|
||||
}
|
||||
|
||||
// Default tier modifier is 1.0 (no modification)
|
||||
tierModifier := 1.0
|
||||
|
||||
// This is a necessary bonus to catch gear up from previous v2 version
|
||||
catchUpBonus := 1.5
|
||||
|
||||
item.ApplyTierModifiersWithCatchup(tier, catchUpBonus)
|
||||
}
|
||||
|
||||
func (item *Item) ApplyTierModifiersWithCatchup(tier int, catchUpBonus float64) {
|
||||
// Default tier modifier is 1.0 (no modification)
|
||||
tierModifier := 1.0
|
||||
|
||||
// If tier is valid (1-5), get the modifier from config
|
||||
if tier > 0 && tier <= 5 {
|
||||
if mod, ok := config.GearTierModifiers[tier]; ok {
|
||||
@@ -1370,7 +1399,7 @@ func (item *Item) ApplyTierModifiers(optionalTier ...int) {
|
||||
// Apply tier modifier and stat modifier
|
||||
newValue := int(float64(statValuePtr) * tierModifier * inverseModifier * catchUpBonus)
|
||||
|
||||
fmt.Printf("DEBUG: Stat %d changed from %d to %d (tier=%.2f, inverse=%.2f, catchup=%.2f)\n",
|
||||
fmt.Printf("DEBUG: Stat %d changed from %d to %d (tier=%.2f, inverse=%.2f, catchup=%.2f)\n",
|
||||
i, statValuePtr, newValue, tierModifier, inverseModifier, catchUpBonus)
|
||||
|
||||
// Update the item's stat value
|
||||
|
||||
262
internal/items/templates.go
Normal file
262
internal/items/templates.go
Normal file
@@ -0,0 +1,262 @@
|
||||
package items
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
// StatTemplate represents a template for generating item stats
|
||||
type StatTemplate struct {
|
||||
Name string
|
||||
RequiredStats []StatEntry // Stats that are always present
|
||||
OptionalStats []StatEntry // Stats that may be present with variance
|
||||
MaxOptional int // Maximum number of optional stats to add
|
||||
}
|
||||
|
||||
// StatEntry represents a stat type and value range
|
||||
type StatEntry struct {
|
||||
StatType int
|
||||
BaseValue int
|
||||
ValueRange int // Random variance range (0 to ValueRange-1)
|
||||
Multiplier float64 // Multiplier for base stat value
|
||||
}
|
||||
|
||||
// StatTemplateManager handles stat template application
|
||||
type StatTemplateManager struct {
|
||||
debug bool
|
||||
}
|
||||
|
||||
// NewStatTemplateManager creates a new stat template manager
|
||||
func NewStatTemplateManager(debug bool) *StatTemplateManager {
|
||||
return &StatTemplateManager{debug: debug}
|
||||
}
|
||||
|
||||
// ApplySimpleStatTemplate applies a basic stat template to an item for phase 0 scaling
|
||||
func (stm *StatTemplateManager) ApplySimpleStatTemplate(item *Item) {
|
||||
if stm.debug {
|
||||
log.Printf("Applying simple stat template for %s (Class: %d, Subclass: %d)",
|
||||
item.Name, *item.Class, *item.Subclass)
|
||||
}
|
||||
|
||||
// Clear existing stats
|
||||
stm.clearItemStats(item)
|
||||
|
||||
// Get base stat value with variance
|
||||
baseStatValue := 6 + rand.Intn(5) // Random between 6-10 for variance
|
||||
|
||||
// Apply template based on item type
|
||||
if *item.Class == 2 { // Weapons
|
||||
stm.applyWeaponTemplate(item, baseStatValue)
|
||||
} else if *item.Class == 4 { // Armor
|
||||
stm.applyArmorTemplate(item, baseStatValue)
|
||||
}
|
||||
|
||||
// Update stats count
|
||||
stm.updateStatsCount(item)
|
||||
|
||||
if stm.debug {
|
||||
statsCount, _ := item.GetField("StatsCount")
|
||||
log.Printf("Applied simple template: %d stats for %s", statsCount, item.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// clearItemStats clears all existing stats on an item
|
||||
func (stm *StatTemplateManager) clearItemStats(item *Item) {
|
||||
for i := 1; i <= 10; i++ {
|
||||
item.UpdateField(fmt.Sprintf("StatType%d", i), 0)
|
||||
item.UpdateField(fmt.Sprintf("StatValue%d", i), 0)
|
||||
}
|
||||
}
|
||||
|
||||
// applyWeaponTemplate applies weapon-specific stat templates
|
||||
func (stm *StatTemplateManager) applyWeaponTemplate(item *Item, baseStatValue int) {
|
||||
classType := item.GetClassUserType()
|
||||
|
||||
// Determine weapon type
|
||||
isPhysicalWeapon := stm.isPhysicalWeapon(*item.Subclass, classType)
|
||||
isTankWeapon := (*item.Subclass == 6) // Shield
|
||||
|
||||
if stm.debug {
|
||||
log.Printf("Weapon %s (subclass %d, classType %d) determined as physical: %t",
|
||||
item.Name, *item.Subclass, classType, isPhysicalWeapon)
|
||||
}
|
||||
|
||||
if isPhysicalWeapon {
|
||||
stm.applyPhysicalWeaponTemplate(item, baseStatValue, isTankWeapon)
|
||||
} else {
|
||||
stm.applyCasterWeaponTemplate(item, baseStatValue, isTankWeapon)
|
||||
}
|
||||
}
|
||||
|
||||
// isPhysicalWeapon determines if a weapon should use physical stats
|
||||
func (stm *StatTemplateManager) isPhysicalWeapon(subclass, classType int) bool {
|
||||
// Physical weapon subclasses
|
||||
physicalSubclasses := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 13, 15, 16, 17, 18}
|
||||
for _, sc := range physicalSubclasses {
|
||||
if subclass == sc {
|
||||
// Special case: daggers can be physical or caster
|
||||
if subclass == 15 { // Dagger
|
||||
// If classType is generic (7), assume physical for daggers
|
||||
return classType == 7 || classType == 1 || classType == 2
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Caster weapon subclasses
|
||||
casterSubclasses := []int{10, 19, 20} // Staff, Wand, Fishing Pole
|
||||
for _, sc := range casterSubclasses {
|
||||
if subclass == sc {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return false // Default to caster if unknown
|
||||
}
|
||||
|
||||
// applyPhysicalWeaponTemplate applies physical DPS weapon stats
|
||||
func (stm *StatTemplateManager) applyPhysicalWeaponTemplate(item *Item, baseStatValue int, isTankWeapon bool) {
|
||||
statSlot := 1
|
||||
|
||||
// Primary stat (ALWAYS present) - choose based on weapon type
|
||||
primaryStat := 3 // Agility default
|
||||
|
||||
// Weapon-specific stat assignment
|
||||
if *item.Subclass == 15 { // Dagger - ALWAYS Agility
|
||||
primaryStat = 3 // Agility
|
||||
} else if *item.Subclass == 0 || *item.Subclass == 1 || *item.Subclass == 8 { // Axe, Sword, Two-handed Sword
|
||||
// These can be Strength or Agility, with slight preference for Strength
|
||||
if rand.Intn(4) < 3 { // 75% chance for Strength
|
||||
primaryStat = 4 // Strength
|
||||
} else {
|
||||
primaryStat = 3 // Agility
|
||||
}
|
||||
} else if *item.Subclass == 2 || *item.Subclass == 3 || *item.Subclass == 18 { // Bow, Gun, Crossbow
|
||||
primaryStat = 3 // Agility (ranged weapons)
|
||||
} else {
|
||||
// Other weapons - random choice
|
||||
if rand.Intn(2) == 0 {
|
||||
primaryStat = 3 // Agility
|
||||
} else {
|
||||
primaryStat = 4 // Strength
|
||||
}
|
||||
}
|
||||
|
||||
*item.StatType1 = primaryStat
|
||||
*item.StatValue1 = baseStatValue + 3 + rand.Intn(2) // 3-4 bonus, consistent
|
||||
statSlot++
|
||||
|
||||
// Attack Power (ALWAYS present for physical weapons)
|
||||
*item.StatType2 = 38 // Attack Power
|
||||
*item.StatValue2 = baseStatValue*4 + rand.Intn(2) // Consistent high value with small variance
|
||||
statSlot++
|
||||
|
||||
// Secondary stats (VARY these) - choose 1-3 randomly
|
||||
availableSecondaries := []int{32, 36, 31, 44} // Crit, Haste, Hit, Armor Pen
|
||||
stm.addRandomSecondaryStats(item, availableSecondaries, baseStatValue, &statSlot, 1+rand.Intn(3))
|
||||
|
||||
// Only add stamina for tank weapons
|
||||
if isTankWeapon && statSlot <= 5 {
|
||||
item.UpdateField(fmt.Sprintf("StatType%d", statSlot), 7) // Stamina
|
||||
item.UpdateField(fmt.Sprintf("StatValue%d", statSlot), baseStatValue+8+rand.Intn(3)) // 8-10 bonus
|
||||
}
|
||||
}
|
||||
|
||||
// applyCasterWeaponTemplate applies caster weapon stats
|
||||
func (stm *StatTemplateManager) applyCasterWeaponTemplate(item *Item, baseStatValue int, isTankWeapon bool) {
|
||||
statSlot := 1
|
||||
|
||||
// Intellect (ALWAYS present for casters)
|
||||
*item.StatType1 = 5 // Intellect
|
||||
*item.StatValue1 = baseStatValue + 3 + rand.Intn(2) // 3-4 bonus, consistent
|
||||
statSlot++
|
||||
|
||||
// Spell Power (ALWAYS present for caster weapons)
|
||||
*item.StatType2 = 45 // Spell Power
|
||||
*item.StatValue2 = baseStatValue*3 + rand.Intn(2) // Consistent high value with small variance
|
||||
statSlot++
|
||||
|
||||
// Secondary stats (VARY these) - choose 1-3 randomly
|
||||
availableSecondaries := []int{32, 36, 31, 43, 6} // Crit, Haste, Hit, MP5, Spirit
|
||||
stm.addRandomSecondaryStats(item, availableSecondaries, baseStatValue, &statSlot, 1+rand.Intn(3))
|
||||
|
||||
// Only add stamina for tank weapons (rare for casters)
|
||||
if isTankWeapon && statSlot <= 5 {
|
||||
item.UpdateField(fmt.Sprintf("StatType%d", statSlot), 7) // Stamina
|
||||
item.UpdateField(fmt.Sprintf("StatValue%d", statSlot), baseStatValue+8+rand.Intn(3)) // 8-10 bonus
|
||||
}
|
||||
}
|
||||
|
||||
// applyArmorTemplate applies armor-specific stat templates
|
||||
func (stm *StatTemplateManager) applyArmorTemplate(item *Item, baseStatValue int) {
|
||||
if *item.Subclass == 0 { // Trinkets
|
||||
stm.applyTrinketTemplate(item, baseStatValue)
|
||||
} else {
|
||||
stm.applyGenericArmorTemplate(item, baseStatValue)
|
||||
}
|
||||
}
|
||||
|
||||
// applyTrinketTemplate applies trinket stats (minimal stats, power from spells)
|
||||
func (stm *StatTemplateManager) applyTrinketTemplate(item *Item, baseStatValue int) {
|
||||
*item.StatType1 = 7 // Stamina
|
||||
*item.StatValue1 = baseStatValue + 3 + rand.Intn(3) // 3-5 bonus with variance
|
||||
}
|
||||
|
||||
// applyGenericArmorTemplate applies generic armor stats
|
||||
func (stm *StatTemplateManager) applyGenericArmorTemplate(item *Item, baseStatValue int) {
|
||||
classType := item.GetClassUserType()
|
||||
statSlot := 1
|
||||
|
||||
// Stamina (ALWAYS present on armor)
|
||||
*item.StatType1 = 7 // Stamina
|
||||
*item.StatValue1 = baseStatValue + 4 + rand.Intn(2) // 4-5 bonus, consistent
|
||||
statSlot++
|
||||
|
||||
// Primary stat (ALWAYS present) based on class type
|
||||
primaryStat := 5 // Default to Intellect
|
||||
if classType == 1 { // Strength
|
||||
primaryStat = 4 // Strength
|
||||
} else if classType == 2 { // Agility
|
||||
primaryStat = 3 // Agility
|
||||
} else if classType == 7 { // Generic/Unknown - random choice
|
||||
primaryStats := []int{3, 4, 5} // Agi, Str, Int
|
||||
primaryStat = primaryStats[rand.Intn(len(primaryStats))]
|
||||
}
|
||||
// classType 3,4,5,6 (casters) get Intellect by default
|
||||
|
||||
*item.StatType2 = primaryStat
|
||||
*item.StatValue2 = baseStatValue + 2 + rand.Intn(2) // 2-3 bonus, consistent
|
||||
statSlot++
|
||||
|
||||
// Secondary stats (VARY these) - choose 0-2 randomly
|
||||
availableSecondaries := []int{32, 36, 31, 6} // Crit, Haste, Hit, Spirit
|
||||
stm.addRandomSecondaryStats(item, availableSecondaries, baseStatValue-3, &statSlot, rand.Intn(3))
|
||||
}
|
||||
|
||||
// addRandomSecondaryStats adds random secondary stats to an item
|
||||
func (stm *StatTemplateManager) addRandomSecondaryStats(item *Item, availableStats []int, baseValue int, statSlot *int, count int) {
|
||||
// Shuffle available stats
|
||||
rand.Shuffle(len(availableStats), func(i, j int) {
|
||||
availableStats[i], availableStats[j] = availableStats[j], availableStats[i]
|
||||
})
|
||||
|
||||
// Add the specified number of secondary stats
|
||||
for i := 0; i < count && i < len(availableStats) && *statSlot <= 5; i++ {
|
||||
item.UpdateField(fmt.Sprintf("StatType%d", *statSlot), availableStats[i])
|
||||
item.UpdateField(fmt.Sprintf("StatValue%d", *statSlot), baseValue+rand.Intn(4)) // 0 to +3 variance
|
||||
(*statSlot)++
|
||||
}
|
||||
}
|
||||
|
||||
// updateStatsCount updates the StatsCount field based on non-zero stats
|
||||
func (stm *StatTemplateManager) updateStatsCount(item *Item) {
|
||||
statsCount := 0
|
||||
for i := 1; i <= 10; i++ {
|
||||
statType, _ := item.GetField(fmt.Sprintf("StatType%d", i))
|
||||
if statType > 0 {
|
||||
statsCount++
|
||||
}
|
||||
}
|
||||
*item.StatsCount = statsCount
|
||||
}
|
||||
@@ -483,80 +483,95 @@ func (s *Spell) ForceScaleSpell(fromItemLevel int, toItemLevel int, itemQuality
|
||||
|
||||
// Scale Effect1
|
||||
if s.EffectBasePoints1 != 0 {
|
||||
effectMultiplier := 1.0
|
||||
// Skip scaling for percentage-based effects (buffs/debuffs)
|
||||
// Look for percentage indicators in spell description
|
||||
skipScaling := strings.Contains(s.Description, "$s1%") ||
|
||||
strings.Contains(s.Description, "$s1\\%") ||
|
||||
strings.Contains(s.Description, "by $s1%") ||
|
||||
// Common percentage-based aura effects that should not scale
|
||||
(s.EffectAura1 != 0 && (
|
||||
s.EffectAura1 == 33 || // Modify Movement Speed
|
||||
s.EffectAura1 == 31 || // Modify Stat (percentage)
|
||||
s.EffectAura1 == 52 || // Modify Damage Done (percentage)
|
||||
s.EffectAura1 == 79 || // Modify Resistance (percentage)
|
||||
s.EffectAura1 == 137)) // Modify Total Stat Percentage
|
||||
|
||||
// Check for attack power and spell power in the description regardless of effect type
|
||||
if strings.Contains(s.Description, "attack power") ||
|
||||
strings.Contains(s.Description, "Attack Power") ||
|
||||
strings.Contains(s.Description, "spell power") ||
|
||||
strings.Contains(s.Description, "Spell Power") ||
|
||||
strings.Contains(s.Description, "healing") ||
|
||||
strings.Contains(s.Description, "Healing") {
|
||||
effectMultiplier = 2.0 // Higher multiplier for attack/spell power
|
||||
}
|
||||
if !skipScaling {
|
||||
effectMultiplier := 1.0
|
||||
|
||||
// Determine effect category and apply appropriate multiplier
|
||||
if s.Effect1 != 0 {
|
||||
// Direct damage effects scale more aggressively at higher item levels
|
||||
if funk.Contains(directDamageEffects, s.Effect1) {
|
||||
effectMultiplier = 2.5 + (float64(toItemLevel) * 0.1 * 0.005) // +0.5% per every 10 item levels
|
||||
// Check for attack power and spell power in the description regardless of effect type
|
||||
if strings.Contains(s.Description, "attack power") ||
|
||||
strings.Contains(s.Description, "Attack Power") ||
|
||||
strings.Contains(s.Description, "spell power") ||
|
||||
strings.Contains(s.Description, "Spell Power") ||
|
||||
strings.Contains(s.Description, "healing") ||
|
||||
strings.Contains(s.Description, "Healing") {
|
||||
effectMultiplier = 2.0 // Higher multiplier for attack/spell power
|
||||
}
|
||||
|
||||
// Flat Base Stat modifier for all other stats.
|
||||
if funk.Contains(statBuffEffects, s.Effect1) && effectMultiplier < 1.5 {
|
||||
effectMultiplier = 1.45
|
||||
}
|
||||
}
|
||||
// Determine effect category and apply appropriate multiplier
|
||||
if s.Effect1 != 0 {
|
||||
// Direct damage effects scale more aggressively at higher item levels
|
||||
if funk.Contains(directDamageEffects, s.Effect1) {
|
||||
effectMultiplier = 2.5 + (float64(toItemLevel) * 0.1 * 0.005) // +0.5% per every 10 item levels
|
||||
}
|
||||
|
||||
// Special handling for aura effects
|
||||
if s.EffectAura1 != 0 {
|
||||
// DOT effects (Aura 3: Periodic Damage)
|
||||
if s.EffectAura1 == 3 && funk.Contains(periodEffects, s.Effect1) {
|
||||
effectMultiplier = 2.5 + (float64(toItemLevel) * 0.1 * 0.005)
|
||||
|
||||
// Scale DOTs more with higher quality items
|
||||
if itemQuality >= 5 { // Legendary or higher
|
||||
effectMultiplier += 0.5
|
||||
// Flat Base Stat modifier for all other stats.
|
||||
if funk.Contains(statBuffEffects, s.Effect1) && effectMultiplier < 1.5 {
|
||||
effectMultiplier = 1.45
|
||||
}
|
||||
}
|
||||
|
||||
// HOT effects (Aura 8: Periodic Heal)
|
||||
if s.EffectAura1 == 8 && funk.Contains(periodEffects, s.Effect1) {
|
||||
effectMultiplier = 2.5 + (float64(toItemLevel) * 0.1 * 0.005)
|
||||
// Healing scales slightly higher than damage
|
||||
}
|
||||
// Special handling for aura effects
|
||||
if s.EffectAura1 != 0 {
|
||||
// DOT effects (Aura 3: Periodic Damage)
|
||||
if s.EffectAura1 == 3 && funk.Contains(periodEffects, s.Effect1) {
|
||||
effectMultiplier = 2.5 + (float64(toItemLevel) * 0.1 * 0.005)
|
||||
|
||||
// Damage Shield effects (Aura 15)
|
||||
if s.EffectAura1 == 15 && funk.Contains(periodEffects, s.Effect1) {
|
||||
// Damage shields scale with item level difference
|
||||
effectMultiplier = 1.5 + (float64(toItemLevel) * 0.1 * 0.005)
|
||||
if effectMultiplier > 2.5 {
|
||||
effectMultiplier = 2.5 // Cap at 2.5x
|
||||
// Scale DOTs more with higher quality items
|
||||
if itemQuality >= 5 { // Legendary or higher
|
||||
effectMultiplier += 0.5
|
||||
}
|
||||
}
|
||||
|
||||
// HOT effects (Aura 8: Periodic Heal)
|
||||
if s.EffectAura1 == 8 && funk.Contains(periodEffects, s.Effect1) {
|
||||
effectMultiplier = 2.5 + (float64(toItemLevel) * 0.1 * 0.005)
|
||||
// Healing scales slightly higher than damage
|
||||
}
|
||||
|
||||
// Damage Shield effects (Aura 15)
|
||||
if s.EffectAura1 == 15 && funk.Contains(periodEffects, s.Effect1) {
|
||||
// Damage shields scale with item level difference
|
||||
effectMultiplier = 1.5 + (float64(toItemLevel) * 0.1 * 0.005)
|
||||
if effectMultiplier > 2.5 {
|
||||
effectMultiplier = 2.5 // Cap at 2.5x
|
||||
}
|
||||
}
|
||||
|
||||
// Proc chance effects (various auras)
|
||||
if s.ProcChance > 0 && s.ProcChance < 100 {
|
||||
// For proc effects, we might want to scale the effect more aggressively
|
||||
// since they don't happen all the time
|
||||
procFactor := 100.0 / float64(s.ProcChance) // Inverse of proc chance
|
||||
// Limit the proc factor to avoid excessive scaling
|
||||
if procFactor > 2.0 {
|
||||
procFactor = 2.0
|
||||
}
|
||||
effectMultiplier += math.Sqrt(procFactor) // Scale by square root of proc factor
|
||||
}
|
||||
}
|
||||
|
||||
// Proc chance effects (various auras)
|
||||
if s.ProcChance > 0 && s.ProcChance < 100 {
|
||||
// For proc effects, we might want to scale the effect more aggressively
|
||||
// since they don't happen all the time
|
||||
procFactor := 100.0 / float64(s.ProcChance) // Inverse of proc chance
|
||||
// Limit the proc factor to avoid excessive scaling
|
||||
if procFactor > 2.0 {
|
||||
procFactor = 2.0
|
||||
// Special handling for mana restoration
|
||||
if s.Effect1 == 30 {
|
||||
if strings.Contains(s.Description, "Mana") || strings.Contains(s.Description, "mana") {
|
||||
// Mana effects scale with level but with diminishing returns
|
||||
effectMultiplier = 1.0 + (math.Log10(float64(ilevelDiff+1)) * 0.3)
|
||||
}
|
||||
effectMultiplier += math.Sqrt(procFactor) // Scale by square root of proc factor
|
||||
}
|
||||
// Apply the scaling with the appropriate multiplier
|
||||
s.EffectBasePoints1 = int(float64(s.EffectBasePoints1) * levelRatio * qualModifier * effectMultiplier)
|
||||
}
|
||||
|
||||
// Special handling for mana restoration
|
||||
if s.Effect1 == 30 {
|
||||
if strings.Contains(s.Description, "Mana") || strings.Contains(s.Description, "mana") {
|
||||
// Mana effects scale with level but with diminishing returns
|
||||
effectMultiplier = 1.0 + (math.Log10(float64(ilevelDiff+1)) * 0.3)
|
||||
}
|
||||
}
|
||||
// Apply the scaling with the appropriate multiplier
|
||||
s.EffectBasePoints1 = int(float64(s.EffectBasePoints1) * levelRatio * qualModifier * effectMultiplier)
|
||||
}
|
||||
|
||||
// Scale Effect2 with similar logic
|
||||
@@ -851,43 +866,7 @@ func SpellToSql(spell Spell, quality int) string {
|
||||
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)
|
||||
from spell_dbc as src WHERE src.ID = %v;`, entryBump, spell.ID)
|
||||
|
||||
update := fmt.Sprintf(`
|
||||
UPDATE acore_world.spell_dbc
|
||||
|
||||
349
internal/spells/thematic.go
Normal file
349
internal/spells/thematic.go
Normal file
@@ -0,0 +1,349 @@
|
||||
package spells
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/araxiaonline/endgame-item-generator/internal/db/mysql"
|
||||
)
|
||||
|
||||
// SpellTheme represents different magical themes for spell assignment
|
||||
type SpellTheme int
|
||||
|
||||
const (
|
||||
ThemeNone SpellTheme = iota
|
||||
ThemeFrost
|
||||
ThemeFire
|
||||
ThemeNature
|
||||
ThemeShadow
|
||||
ThemeArcane
|
||||
ThemeHoly
|
||||
)
|
||||
|
||||
// String returns the string representation of a SpellTheme
|
||||
func (t SpellTheme) String() string {
|
||||
switch t {
|
||||
case ThemeFrost:
|
||||
return "Frost"
|
||||
case ThemeFire:
|
||||
return "Fire"
|
||||
case ThemeNature:
|
||||
return "Nature"
|
||||
case ThemeShadow:
|
||||
return "Shadow"
|
||||
case ThemeArcane:
|
||||
return "Arcane"
|
||||
case ThemeHoly:
|
||||
return "Holy"
|
||||
default:
|
||||
return "None"
|
||||
}
|
||||
}
|
||||
|
||||
// ThematicSpellAssigner handles assignment of thematic spells to items
|
||||
type ThematicSpellAssigner struct {
|
||||
db *mysql.MySqlDb
|
||||
}
|
||||
|
||||
// NewThematicSpellAssigner creates a new thematic spell assigner
|
||||
func NewThematicSpellAssigner(db *mysql.MySqlDb) *ThematicSpellAssigner {
|
||||
return &ThematicSpellAssigner{db: db}
|
||||
}
|
||||
|
||||
// ShouldHaveProc determines if an item should have a proc spell based on its properties
|
||||
func (tsa *ThematicSpellAssigner) ShouldHaveProc(itemClass, itemSubclass, itemLevel, quality int, itemName string) bool {
|
||||
log.Printf("DEBUG: ShouldHaveProc - Class: %d, Subclass: %d, Level: %d, Quality: %d, Name: %s",
|
||||
itemClass, itemSubclass, itemLevel, quality, itemName)
|
||||
|
||||
// Weapons should have procs
|
||||
if itemClass == 2 {
|
||||
log.Printf("DEBUG: Item is a weapon (class 2)")
|
||||
// Higher quality items more likely to have procs
|
||||
if quality >= 3 && itemLevel >= 40 {
|
||||
log.Printf("DEBUG: Weapon qualifies - quality %d >= 3 and level %d >= 40", quality, itemLevel)
|
||||
return true
|
||||
} else {
|
||||
log.Printf("DEBUG: Weapon does not qualify - quality %d < 3 or level %d < 40", quality, itemLevel)
|
||||
}
|
||||
}
|
||||
|
||||
// Trinkets should have procs
|
||||
if itemClass == 4 && itemSubclass == 0 {
|
||||
log.Printf("DEBUG: Item is a trinket - qualifies for proc")
|
||||
return true
|
||||
}
|
||||
|
||||
// Jewelry with thematic names
|
||||
if itemClass == 4 && (itemSubclass == 1 || itemSubclass == 2) { // Neck, Ring
|
||||
theme := tsa.DetectThemeFromName(itemName)
|
||||
if theme != ThemeNone {
|
||||
log.Printf("DEBUG: Jewelry with theme %s - qualifies for proc", theme.String())
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("DEBUG: Item does not qualify for proc")
|
||||
return false
|
||||
}
|
||||
|
||||
// DetectThemeFromName analyzes item name and description to determine magical theme
|
||||
func (tsa *ThematicSpellAssigner) DetectThemeFromName(itemName string) SpellTheme {
|
||||
name := strings.ToLower(itemName)
|
||||
|
||||
// Frost theme keywords - spells like Frostbolt, Frost Nova, Ice Lance
|
||||
frostKeywords := []string{
|
||||
"frost", "cold", "ice", "frozen", "chill", "winter", "arctic", "glacial",
|
||||
"coldrage", "frostbite", "icicle", "blizzard", "freeze", "shiver", "crystal",
|
||||
"permafrost", "hoarfrost", "rimefang", "icecrown", "northrend", "wintergrasp",
|
||||
"frostmourne", "sindragosa", "arthas", "lich", "undead", "scourge",
|
||||
}
|
||||
|
||||
// Fire theme keywords - spells like Fireball, Flame Strike, Pyroblast
|
||||
fireKeywords := []string{
|
||||
"fire", "flame", "burn", "ember", "inferno", "molten", "volcanic",
|
||||
"blazing", "scorching", "flaming", "ignite", "pyre", "magma", "lava",
|
||||
"sulfuron", "ragnaros", "flamewaker", "firelands", "phoenix", "immolate",
|
||||
"conflagrate", "incinerate", "combustion", "pyroblast", "scorch",
|
||||
}
|
||||
|
||||
// Nature theme keywords - spells like Lightning Bolt, Earth Shock, Entangling Roots
|
||||
natureKeywords := []string{
|
||||
"nature", "earth", "storm", "lightning", "thunder", "wind", "leaf",
|
||||
"thorn", "root", "growth", "poison", "venom", "toxic", "acid", "druid",
|
||||
"shaman", "elemental", "totem", "spirit", "wolf", "bear", "cat", "tree",
|
||||
"hurricane", "typhoon", "earthquake", "rockbiter", "windfury", "stormstrike",
|
||||
"maelstrom", "chain", "healing", "rejuvenation", "regrowth", "lifebloom",
|
||||
}
|
||||
|
||||
// Shadow theme keywords - spells like Shadow Bolt, Drain Life, Fear
|
||||
shadowKeywords := []string{
|
||||
"shadow", "dark", "void", "curse", "doom", "death", "soul", "spirit",
|
||||
"nightmare", "corruption", "plague", "blight", "necro", "warlock", "priest",
|
||||
"undead", "bone", "skull", "grave", "tomb", "crypt", "wraith", "specter",
|
||||
"banshee", "lich", "drain", "siphon", "vampiric", "life", "mana", "burn",
|
||||
"fear", "horror", "terror", "agony", "torment", "suffering", "pain",
|
||||
}
|
||||
|
||||
// Arcane theme keywords - spells like Arcane Missiles, Polymorph, Counterspell
|
||||
arcaneKeywords := []string{
|
||||
"arcane", "magic", "mystic", "enchant", "spell", "mana", "ethereal",
|
||||
"astral", "cosmic", "temporal", "dimensional", "mage", "wizard", "sorcerer",
|
||||
"intellect", "wisdom", "knowledge", "study", "tome", "grimoire", "staff",
|
||||
"orb", "crystal", "gem", "power", "energy", "force", "teleport", "portal",
|
||||
"polymorph", "counterspell", "dispel", "silence", "interrupt",
|
||||
}
|
||||
|
||||
// Holy theme keywords - spells like Heal, Flash of Light, Consecration
|
||||
holyKeywords := []string{
|
||||
"holy", "divine", "blessed", "sacred", "light", "radiant", "celestial",
|
||||
"righteous", "pure", "sanctified", "hallowed", "paladin", "priest", "cleric",
|
||||
"angel", "seraph", "cherub", "heaven", "paradise", "salvation", "redemption",
|
||||
"consecration", "blessing", "grace", "mercy", "faith", "devotion", "prayer",
|
||||
"heal", "cure", "restore", "renew", "resurrect", "revive", "sanctuary",
|
||||
}
|
||||
|
||||
// Check each theme
|
||||
for _, keyword := range frostKeywords {
|
||||
if strings.Contains(name, keyword) {
|
||||
return ThemeFrost
|
||||
}
|
||||
}
|
||||
|
||||
for _, keyword := range fireKeywords {
|
||||
if strings.Contains(name, keyword) {
|
||||
return ThemeFire
|
||||
}
|
||||
}
|
||||
|
||||
for _, keyword := range natureKeywords {
|
||||
if strings.Contains(name, keyword) {
|
||||
return ThemeNature
|
||||
}
|
||||
}
|
||||
|
||||
for _, keyword := range shadowKeywords {
|
||||
if strings.Contains(name, keyword) {
|
||||
return ThemeShadow
|
||||
}
|
||||
}
|
||||
|
||||
for _, keyword := range arcaneKeywords {
|
||||
if strings.Contains(name, keyword) {
|
||||
return ThemeArcane
|
||||
}
|
||||
}
|
||||
|
||||
for _, keyword := range holyKeywords {
|
||||
if strings.Contains(name, keyword) {
|
||||
return ThemeHoly
|
||||
}
|
||||
}
|
||||
|
||||
return ThemeNone
|
||||
}
|
||||
|
||||
// FindThematicSpell finds an appropriate spell for the given theme
|
||||
func (tsa *ThematicSpellAssigner) FindThematicSpell(theme SpellTheme, itemClass, itemSubclass int) (int, error) {
|
||||
log.Printf("Finding thematic spell for theme: %s, class: %d, subclass: %d", theme.String(), itemClass, itemSubclass)
|
||||
|
||||
// First, try to find existing scaled spells (31000000+)
|
||||
scaledSpellId, err := tsa.findScaledThematicSpell(theme, itemClass, itemSubclass)
|
||||
if err == nil && scaledSpellId > 0 {
|
||||
log.Printf("Found existing scaled spell: %d for theme %s", scaledSpellId, theme.String())
|
||||
return scaledSpellId, nil
|
||||
}
|
||||
|
||||
// If no scaled spell found, look in spell_dbc for base spells
|
||||
baseSpellId, err := tsa.findBaseThematicSpell(theme, itemClass, itemSubclass)
|
||||
if err == nil && baseSpellId > 0 {
|
||||
log.Printf("Found base spell: %d for theme %s", baseSpellId, theme.String())
|
||||
return baseSpellId, nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("no suitable spell found for theme %s", theme.String())
|
||||
}
|
||||
|
||||
// findScaledThematicSpell looks for existing scaled spells (31000000+) that match the theme
|
||||
func (tsa *ThematicSpellAssigner) findScaledThematicSpell(theme SpellTheme, itemClass, itemSubclass int) (int, error) {
|
||||
// Define spell pools for each theme (existing scaled spells starting from 31000000)
|
||||
// These would be spells that have already been scaled by the system
|
||||
scaledSpellPools := map[SpellTheme][]int{
|
||||
ThemeFrost: {
|
||||
// Start with high-priority frost spells for weapons/trinkets
|
||||
31013439, // Scaled Frostbolt (damage + slow)
|
||||
31012737, // Scaled Frost Armor (defensive)
|
||||
31021401, // Scaled Frost Nova (AoE slow)
|
||||
},
|
||||
ThemeFire: {
|
||||
// Fire damage spells
|
||||
31013140, // Scaled Fireball (direct damage)
|
||||
31008413, // Scaled Fire Nova (AoE damage)
|
||||
31011564, // Scaled Flame Strike (ground effect)
|
||||
},
|
||||
ThemeNature: {
|
||||
// Nature/Lightning spells
|
||||
31000421, // Scaled Lightning Bolt (instant damage)
|
||||
31008050, // Scaled Earth Shock (damage + interrupt)
|
||||
31010414, // Scaled Entangling Roots (root effect)
|
||||
},
|
||||
ThemeShadow: {
|
||||
// Shadow/Dark magic spells
|
||||
31000980, // Scaled Shadow Bolt (shadow damage)
|
||||
31007648, // Scaled Drain Life (damage + heal)
|
||||
31011671, // Scaled Shadow Word: Pain (DoT)
|
||||
},
|
||||
ThemeArcane: {
|
||||
// Arcane magic spells
|
||||
31005143, // Scaled Arcane Missiles (channeled damage)
|
||||
31001953, // Scaled Blink (teleport)
|
||||
31000118, // Scaled Polymorph (crowd control)
|
||||
},
|
||||
ThemeHoly: {
|
||||
// Holy/Light spells
|
||||
31000139, // Scaled Heal (direct healing)
|
||||
31010060, // Scaled Flash of Light (fast heal)
|
||||
31001244, // Scaled Divine Favor (buff)
|
||||
},
|
||||
}
|
||||
|
||||
spells, exists := scaledSpellPools[theme]
|
||||
if !exists || len(spells) == 0 {
|
||||
return 0, fmt.Errorf("no scaled spells available for theme %s", theme.String())
|
||||
}
|
||||
|
||||
// For weapons, prefer damage spells; for trinkets/jewelry, prefer utility
|
||||
if itemClass == 2 { // Weapons
|
||||
// Return first damage spell (they're ordered by preference)
|
||||
return spells[0], nil
|
||||
}
|
||||
|
||||
// For trinkets/jewelry, prefer utility spells (later in list)
|
||||
if len(spells) > 1 {
|
||||
return spells[len(spells)-1], nil
|
||||
}
|
||||
|
||||
return spells[0], nil
|
||||
}
|
||||
|
||||
// findBaseThematicSpell looks in spell_dbc for base spells that match the theme
|
||||
func (tsa *ThematicSpellAssigner) findBaseThematicSpell(theme SpellTheme, itemClass, itemSubclass int) (int, error) {
|
||||
// Define base spell pools for each theme
|
||||
baseSpellPools := map[SpellTheme][]int{
|
||||
ThemeFrost: {
|
||||
13439, // Frostbolt
|
||||
21401, // Frost Nova
|
||||
12737, // Frost Armor
|
||||
11113, // Blast Wave (Frost)
|
||||
},
|
||||
ThemeFire: {
|
||||
13140, // Fireball
|
||||
8413, // Fire Nova
|
||||
11564, // Flame Strike
|
||||
11129, // Combustion
|
||||
},
|
||||
ThemeNature: {
|
||||
421, // Lightning Bolt
|
||||
8050, // Earth Shock
|
||||
10414, // Entangling Roots
|
||||
16689, // Nature's Grasp
|
||||
},
|
||||
ThemeShadow: {
|
||||
980, // Shadow Bolt
|
||||
7648, // Drain Life
|
||||
11671, // Shadow Word: Pain
|
||||
18265, // Siphon Soul
|
||||
},
|
||||
ThemeArcane: {
|
||||
5143, // Arcane Missiles
|
||||
1953, // Blink
|
||||
118, // Polymorph
|
||||
12051, // Evocation
|
||||
},
|
||||
ThemeHoly: {
|
||||
139, // Heal
|
||||
1244, // Divine Favor
|
||||
10060, // Flash of Light
|
||||
10308, // Hammer of Justice
|
||||
},
|
||||
}
|
||||
|
||||
spells, exists := baseSpellPools[theme]
|
||||
if !exists || len(spells) == 0 {
|
||||
return 0, fmt.Errorf("no base spells available for theme %s", theme.String())
|
||||
}
|
||||
|
||||
// Verify spell exists in database
|
||||
for _, spellId := range spells {
|
||||
_, err := tsa.db.GetSpell(spellId)
|
||||
if err == nil {
|
||||
return spellId, nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("no valid base spells found for theme %s", theme.String())
|
||||
}
|
||||
|
||||
// AssignThematicSpell assigns a thematic spell to an item if appropriate
|
||||
func (tsa *ThematicSpellAssigner) AssignThematicSpell(itemEntry, itemClass, itemSubclass, itemLevel, quality int, itemName string) (int, error) {
|
||||
// Check if item should have a proc
|
||||
if !tsa.ShouldHaveProc(itemClass, itemSubclass, itemLevel, quality, itemName) {
|
||||
return 0, fmt.Errorf("item does not qualify for proc assignment")
|
||||
}
|
||||
|
||||
// Detect theme from name
|
||||
theme := tsa.DetectThemeFromName(itemName)
|
||||
if theme == ThemeNone {
|
||||
// For weapons without clear theme, don't assign a spell
|
||||
return 0, fmt.Errorf("no clear theme detected for item: %s", itemName)
|
||||
}
|
||||
|
||||
// Find appropriate spell
|
||||
spellId, err := tsa.FindThematicSpell(theme, itemClass, itemSubclass)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to find thematic spell: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Assigned spell %d (theme: %s) to item %s (%d)", spellId, theme.String(), itemName, itemEntry)
|
||||
return spellId, nil
|
||||
}
|
||||
Reference in New Issue
Block a user