public async Task <SimcSpell> GeneratePlayerSpellAsync(uint playerLevel, uint spellId) { var spellData = await _simcUtilityService.GetRawSpellDataAsync(spellId); var itemSpell = new SimcSpell() { SpellId = spellData.Id, Name = spellData.Name, School = spellData.School, MinRange = spellData.MinRange, MaxRange = spellData.MaxRange, Cooldown = spellData.Cooldown, Gcd = spellData.Gcd, Category = spellData.Category, CategoryCooldown = spellData.CategoryCooldown, Charges = spellData.Charges, ChargeCooldown = spellData.ChargeCooldown, MaxTargets = spellData.MaxTargets, Duration = spellData.Duration, MaxStacks = spellData.MaxStack, ProcChance = spellData.ProcChance, InternalCooldown = spellData.InternalCooldown, Rppm = spellData.Rppm, CastTime = spellData.CastTime }; // Add power costs foreach (var power in spellData.SpellPowers) { itemSpell.PowerCosts.Add(power.AuraId, power.PercentCost); } // Add the RPPM modifiers var rppmModifiers = await _simcUtilityService.GetSpellRppmModifiersAsync(spellData.Id); foreach (var modifier in rppmModifiers) { var newRppmModifier = new SimcSpellRppmModifier() { RppmIsHasted = modifier.ModifierType == RppmModifierType.RPPM_MODIFIER_HASTE, RppmIsSpecModified = modifier.ModifierType == RppmModifierType.RPPM_MODIFIER_SPEC, RppmCoefficient = modifier.Coefficient, RppmSpec = modifier.ModifierType == RppmModifierType.RPPM_MODIFIER_SPEC ? modifier.Type : 0 }; itemSpell.RppmModifiers.Add(newRppmModifier); } // Populate the spell effects foreach (var spellEffect in spellData.Effects) { // Populate the trigger spell if one exists. SimcSpell triggerSpell = null; if (spellEffect.TriggerSpellId > 0) { triggerSpell = await GeneratePlayerSpellAsync(playerLevel, spellEffect.TriggerSpellId); } // Get the scale budget var spellScalingClass = _simcUtilityService.GetScaleClass(spellEffect.ScalingType); double budget = 0; if (spellScalingClass != PlayerScaling.PLAYER_NONE) { // Cap the scaling level if needed if (spellData.MaxScalingLevel > 0) { playerLevel = Math.Min(playerLevel, (uint)spellData.MaxScalingLevel); } var scaleIndex = _simcUtilityService.GetClassId(spellScalingClass); var scaledValue = await _simcUtilityService.GetSpellScalingMultiplierAsync(scaleIndex, (int)playerLevel); budget = scaledValue; } itemSpell.Effects.Add(new SimcSpellEffect() { Id = spellEffect.Id, EffectIndex = spellEffect.EffectIndex, EffectType = spellEffect.EffectType, EffectSubType = spellEffect.EffectSubType, ScalingType = spellEffect.ScalingType, Coefficient = spellEffect.Coefficient, SpCoefficient = spellEffect.SpCoefficient, Delta = spellEffect.Delta, Amplitude = spellEffect.Amplitude, Radius = spellEffect.Radius, RadiusMax = spellEffect.RadiusMax, BaseValue = spellEffect.BaseValue, TriggerSpellId = spellEffect.TriggerSpellId, TriggerSpell = triggerSpell, ScaleBudget = budget }); } // Add the conduit info var conduitRanks = await _simcUtilityService.GetSpellConduitRanksAsync(spellData.Id); foreach (var rank in conduitRanks) { if (itemSpell.ConduitId == 0) { itemSpell.ConduitId = rank.ConduitId; } itemSpell.ConduitRanks.Add(rank.Rank, rank.Value); } return(itemSpell); }
internal async Task <SimcSpell> BuildItemSpellAsync(uint spellId, int itemLevel, ItemQuality itemQuality, InventoryType inventoryType, List <uint> parentSpellIds = null) { // From double spelleffect_data_t::average( const item_t* item ) // Get the item budget from item_database::item_budget // For this we need the item with appropriate item level // and we need the spells maximum scaling level // Then we use the GetScaledModValue() method // Now we update get the budget multiplier using the spells scaling class // If the scaling is -7, apply combat rating multiplier to it // This is done using GetCombatRatingMultiplier() and setting the budget to it // If the scaling is -8, get the props for the items ilvl // set the buget to tbe the props damage replace stat value ... ??? // If the scaling is PLAYER_NONE but the spells flags contains // the spell_attribute::SX_SCALE_ILEVEL value (354U) // Then get random props again and use the damage_secondary property... ??? // Otherwise just use the original budget. // Finally multiply the coefficient of the spell effect against the budget. var spellData = await _simcUtilityService.GetRawSpellDataAsync(spellId); var combatRatingType = _simcUtilityService.GetCombatRatingMultiplierType(inventoryType); var multi = await _simcUtilityService.GetCombatRatingMultiplierAsync(itemLevel, combatRatingType); var itemSpell = new SimcSpell() { SpellId = spellData.Id, Name = spellData.Name, School = spellData.School, //ScalingType = spellData.ScalingType, MinRange = spellData.MinRange, MaxRange = spellData.MaxRange, Cooldown = spellData.Cooldown, Gcd = spellData.Gcd, Category = spellData.Category, CategoryCooldown = spellData.CategoryCooldown, Charges = spellData.Charges, ChargeCooldown = spellData.ChargeCooldown, MaxTargets = spellData.MaxTargets, Duration = spellData.Duration, MaxStacks = spellData.MaxStack, ProcChance = spellData.ProcChance, InternalCooldown = spellData.InternalCooldown, Rppm = spellData.Rppm, CastTime = spellData.CastTime, CombatRatingMultiplier = multi }; // Add power costs foreach (var power in spellData.SpellPowers) { itemSpell.PowerCosts.Add(power.AuraId, power.PercentCost); } // Add the RPPM modifiers var rppmModifiers = await _simcUtilityService.GetSpellRppmModifiersAsync(spellData.Id); foreach (var modifier in rppmModifiers) { var newRppmModifier = new SimcSpellRppmModifier() { SpellId = modifier.SpellId, RppmIsHasted = modifier.ModifierType == RppmModifierType.RPPM_MODIFIER_HASTE, RppmIsSpecModified = modifier.ModifierType == RppmModifierType.RPPM_MODIFIER_SPEC, RppmCoefficient = modifier.Coefficient, RppmSpec = modifier.ModifierType == RppmModifierType.RPPM_MODIFIER_SPEC ? modifier.Type : 0 }; itemSpell.RppmModifiers.Add(newRppmModifier); } // Populate the spell effects if (parentSpellIds == null) { parentSpellIds = new List <uint>(); } parentSpellIds.Add(spellId); foreach (var spellEffect in spellData.Effects) { // Populate the trigger spell if one exists. SimcSpell triggerSpell = null; if (spellEffect.TriggerSpellId > 0) { if (!parentSpellIds.Contains(spellEffect.TriggerSpellId)) { triggerSpell = await BuildItemSpellAsync( spellEffect.TriggerSpellId, itemLevel, itemQuality, inventoryType, parentSpellIds.ToList()); } } // Get the scale budget // simc: spelleffect_data_t::average - spell_data.cpp var budget = await _simcUtilityService.GetItemBudgetAsync(itemLevel, itemQuality, spellData.MaxScalingLevel); var spellScalingClass = _simcUtilityService.GetScaleClass(spellEffect.ScalingType); if (spellScalingClass == PlayerScaling.PLAYER_SPECIAL_SCALE) { // This is some logic that azerite traits used. Seems to be used in some trinket effects too // From azerite_power_t::check_combat_rating_penalty. See #77 foreach (var effect in spellData.Effects) { if (effect.EffectSubType == 189 && // 189 == A_MOD_RATING effect.Coefficient > 0) { _logger?.LogTrace($"Changing scaling type from PLAYER_SPECIAL_SCALE (-1) to PLAYER_SPECIAL_SCALE7 (-7). Spell: {spellData.Id}"); spellScalingClass = PlayerScaling.PLAYER_SPECIAL_SCALE7; break; } } } if (spellScalingClass == PlayerScaling.PLAYER_SPECIAL_SCALE7) { budget *= multi; } else if (spellScalingClass == PlayerScaling.PLAYER_SPECIAL_SCALE8) { var props = await _simcUtilityService.GetRandomPropsAsync(itemLevel); budget = props.DamageReplaceStat; } else if (spellScalingClass == PlayerScaling.PLAYER_NONE || spellScalingClass == PlayerScaling.PLAYER_SPECIAL_SCALE9) { // This is from spelleffect_data_t::average's call to _spell->flags( spell_attribute::SX_SCALE_ILEVEL ) // and from bool flags( spell_attribute attr ) const in spell_data.hpp var ilvlScaleAttribute = 354u; int bit = (int)(ilvlScaleAttribute % 32u); var index = ilvlScaleAttribute / 32u; var mask = 1u << bit; if (spellData.Attributes.Length > index && (spellData.Attributes[index] & mask) != 0) { var props = await _simcUtilityService.GetRandomPropsAsync(itemLevel); budget = props.DamageSecondary; } else { _logger?.LogError($"ilvl scaling from spell flags not yet implemented. Spell: {spellData.Id}"); } } itemSpell.Effects.Add(new SimcSpellEffect() { Id = spellEffect.Id, EffectIndex = spellEffect.EffectIndex, EffectType = spellEffect.EffectType, EffectSubType = spellEffect.EffectSubType, ScalingType = spellEffect.ScalingType, Coefficient = spellEffect.Coefficient, SpCoefficient = spellEffect.SpCoefficient, Delta = spellEffect.Delta, Amplitude = spellEffect.Amplitude, Radius = spellEffect.Radius, RadiusMax = spellEffect.RadiusMax, BaseValue = spellEffect.BaseValue, TriggerSpellId = spellEffect.TriggerSpellId, TriggerSpell = triggerSpell, ScaleBudget = budget }); } return(itemSpell); // stat_buff_t::stat_buff_t // Checks if the effects subtype is A_MOOD_RATING // Then grab the rating then translate value1 to get the stat type // and if its an item, apply the combat rating multi for the item. }