diff --git a/Source/Framework/Constants/ItemConst.cs b/Source/Framework/Constants/ItemConst.cs index 56fe47ebe..b986e2153 100644 --- a/Source/Framework/Constants/ItemConst.cs +++ b/Source/Framework/Constants/ItemConst.cs @@ -347,8 +347,10 @@ namespace Framework.Constants RelicType = 17, OverrideRequiredLevel = 18, AzeriteTierUnlockSet = 19, + ScrappingLootId = 20, OverrideCanDisenchant = 21, - OverrideCanScrap = 22 + OverrideCanScrap = 22, + ItemEffectId = 23, } public enum ItemContext : byte diff --git a/Source/Game/Entities/Item/Item.cs b/Source/Game/Entities/Item/Item.cs index d5bd3fa17..0c7c7d961 100644 --- a/Source/Game/Entities/Item/Item.cs +++ b/Source/Game/Entities/Item/Item.cs @@ -70,10 +70,8 @@ namespace Game.Entities SetDurability(itemProto.MaxDurability); for (int i = 0; i < itemProto.Effects.Count; ++i) - { - if (i < 5) - SetSpellCharges(i, itemProto.Effects[i].Charges); - } + if (itemProto.Effects[i].LegacySlotIndex < 5) + SetSpellCharges(itemProto.Effects[i].LegacySlotIndex, itemProto.Effects[i].Charges); SetExpiration(itemProto.GetDuration()); SetCreatePlayedTime(0); @@ -162,7 +160,7 @@ namespace Game.Entities stmt.AddValue(++index, (uint)m_itemData.Expiration); StringBuilder ss = new StringBuilder(); - for (byte i = 0; i < ItemConst.MaxSpells; ++i) + for (byte i = 0; i < m_itemData.SpellCharges.GetSize() && i < _bonusData.EffectCount; ++i) ss.AppendFormat("{0} ", GetSpellCharges(i)); stmt.AddValue(++index, ss.ToString()); @@ -423,16 +421,6 @@ namespace Game.Entities need_save = true; } - var tokens = new StringArray(fields.Read(6), ' '); - if (tokens.Length == ItemConst.MaxProtoSpells) - { - for (byte i = 0; i < ItemConst.MaxProtoSpells; ++i) - { - if (int.TryParse(tokens[i], out int value)) - SetSpellCharges(i, value); - } - } - SetItemFlags((ItemFieldFlags)itemFlags); uint durability = fields.Read(10); @@ -466,6 +454,14 @@ namespace Game.Entities } SetBonuses(bonusListIDs); + // load charges after bonuses, they can add more item effects + var tokens = new StringArray(fields.Read(6), ' '); + for (byte i = 0; i < m_itemData.SpellCharges.GetSize() && i < _bonusData.EffectCount && i < tokens.Length; ++i) + { + if (int.TryParse(tokens[i], out int value)) + SetSpellCharges(i, value); + } + SetModifier(ItemModifier.TransmogAppearanceAllSpecs, fields.Read(19)); SetModifier(ItemModifier.TransmogAppearanceSpec1, fields.Read(20)); SetModifier(ItemModifier.TransmogAppearanceSpec2, fields.Read(21)); @@ -505,14 +501,14 @@ namespace Game.Entities // Enchants must be loaded after all other bonus/scaling data var enchantmentTokens = new StringArray(fields.Read(8), ' '); - if (enchantmentTokens.Length == (int)EnchantmentSlot.Max * (int)EnchantmentOffset.Max) + if (enchantmentTokens.Length == (int)EnchantmentSlot.Max * 3) { for (int i = 0; i < (int)EnchantmentSlot.Max; ++i) { ItemEnchantment enchantmentField = m_itemData.ModifyValue(m_itemData.Enchantment, i); - SetUpdateFieldValue(enchantmentField.ModifyValue(enchantmentField.ID), uint.Parse(enchantmentTokens[i * (int)EnchantmentOffset.Max + 0])); - SetUpdateFieldValue(enchantmentField.ModifyValue(enchantmentField.Duration), uint.Parse(enchantmentTokens[i * (int)EnchantmentOffset.Max + 1])); - SetUpdateFieldValue(enchantmentField.ModifyValue(enchantmentField.Charges), short.Parse(enchantmentTokens[i * (int)EnchantmentOffset.Max + 2])); + SetUpdateFieldValue(enchantmentField.ModifyValue(enchantmentField.ID), uint.Parse(enchantmentTokens[i * 3 + 0])); + SetUpdateFieldValue(enchantmentField.ModifyValue(enchantmentField.Duration), uint.Parse(enchantmentTokens[i * 3 + 1])); + SetUpdateFieldValue(enchantmentField.ModifyValue(enchantmentField.Charges), short.Parse(enchantmentTokens[i * 3 + 2])); } } @@ -2527,6 +2523,13 @@ namespace Game.Entities public ObjectGuid GetChildItem() { return m_childItem; } public void SetChildItem(ObjectGuid childItem) { m_childItem = childItem; } + public ItemEffectRecord[] GetEffects() { return _bonusData.Effects[0.._bonusData.EffectCount]; } + public ItemEffectRecord GetEffect(int i) + { + Cypher.Assert(i < _bonusData.EffectCount, $"Attempted to get effect at index {i} but item has only {_bonusData.EffectCount} effects!"); + return _bonusData.Effects[i]; + } + //Static public static bool ItemCanGoIntoBag(ItemTemplate pProto, ItemTemplate pBagProto) { @@ -2657,14 +2660,6 @@ namespace Game.Entities public uint count; } - public enum EnchantmentOffset - { - Id = 0, - Duration = 1, - Charges = 2, // now here not only charges, but something new in wotlk - Max = 3 - } - public class ItemSetEffect { public uint ItemSetID; @@ -2713,6 +2708,13 @@ namespace Game.Entities if (azeriteEmpoweredItem != null) AzeriteTierUnlockSetId = azeriteEmpoweredItem.AzeriteTierUnlockSetID; + EffectCount = 0; + foreach (ItemEffectRecord itemEffect in proto.Effects) + Effects[EffectCount++] = itemEffect; + + for (int i = EffectCount; i < Effects.Length; ++i) + Effects[i] = null; + CanDisenchant = !proto.GetFlags().HasAnyFlag(ItemFlags.NoDisenchant); CanScrap = proto.GetFlags4().HasAnyFlag(ItemFlags4.Scrapable); @@ -2835,6 +2837,11 @@ namespace Game.Entities case ItemBonusType.OverrideCanScrap: CanScrap = values[0] != 0; break; + case ItemBonusType.ItemEffectId: + ItemEffectRecord itemEffect = CliDB.ItemEffectStorage.LookupByKey(values[0]); + if (itemEffect != null) + Effects[EffectCount++] = itemEffect; + break; } } @@ -2858,6 +2865,8 @@ namespace Game.Entities public int RequiredLevelOverride; public uint AzeriteTierUnlockSetId; public uint Suffix; + public ItemEffectRecord[] Effects = new ItemEffectRecord[13]; + public int EffectCount; public bool CanDisenchant; public bool CanScrap; public bool HasFixedLevel; diff --git a/Source/Game/Entities/Player/Player.Items.cs b/Source/Game/Entities/Player/Player.Items.cs index f6f640ea2..85255940f 100644 --- a/Source/Game/Entities/Player/Player.Items.cs +++ b/Source/Game/Entities/Player/Player.Items.cs @@ -4265,24 +4265,14 @@ namespace Game.Entities if (item == null) return; - ItemTemplate proto = item.GetTemplate(); - if (proto == null) - return; - - for (byte i = 0; i < proto.Effects.Count; ++i) + foreach (ItemEffectRecord effectData in item.GetEffects()) { - var spellData = proto.Effects[i]; - - // no spell - if (spellData.SpellID == 0) - continue; - // wrong triggering type - if (apply && spellData.TriggerType != ItemSpelltriggerType.OnEquip) + if (apply && effectData.TriggerType != ItemSpelltriggerType.OnEquip) continue; // check if it is valid spell - SpellInfo spellproto = Global.SpellMgr.GetSpellInfo((uint)spellData.SpellID); + SpellInfo spellproto = Global.SpellMgr.GetSpellInfo((uint)effectData.SpellID); if (spellproto == null) continue; @@ -4290,7 +4280,7 @@ namespace Game.Entities && Global.DB2Mgr.GetHeirloomByItemId(item.GetEntry()) != null) continue; - if (spellData.ChrSpecializationID != 0 && spellData.ChrSpecializationID != GetPrimarySpecialization()) + if (effectData.ChrSpecializationID != 0 && effectData.ChrSpecializationID != GetPrimarySpecialization()) continue; ApplyEquipSpell(spellproto, item, apply, formChange); @@ -4340,15 +4330,12 @@ namespace Game.Entities void ApplyEquipCooldown(Item pItem) { - ItemTemplate proto = pItem.GetTemplate(); - if (proto.GetFlags().HasAnyFlag(ItemFlags.NoEquipCooldown)) + if (pItem.GetTemplate().GetFlags().HasAnyFlag(ItemFlags.NoEquipCooldown)) return; DateTime now = GameTime.GetGameTimeSteadyPoint(); - for (byte i = 0; i < proto.Effects.Count; ++i) + foreach (ItemEffectRecord effectData in pItem.GetEffects()) { - var effectData = proto.Effects[i]; - // apply proc cooldown to equip auras if we have any if (effectData.TriggerType == ItemSpelltriggerType.OnEquip) { diff --git a/Source/Game/Entities/Player/Player.Spells.cs b/Source/Game/Entities/Player/Player.Spells.cs index 7d53cfa10..42c4838cf 100644 --- a/Source/Game/Entities/Player/Player.Spells.cs +++ b/Source/Game/Entities/Player/Player.Spells.cs @@ -1499,19 +1499,18 @@ namespace Game.Entities public void CastItemUseSpell(Item item, SpellCastTargets targets, ObjectGuid castCount, uint[] misc) { - ItemTemplate proto = item.GetTemplate(); // special learning case - if (proto.Effects.Count >= 2) + if (item.GetBonus().EffectCount >= 2) { - if (proto.Effects[0].SpellID == 483 || proto.Effects[0].SpellID == 55884) + if (item.GetEffect(0).SpellID == 483 || item.GetEffect(0).SpellID == 55884) { - uint learn_spell_id = (uint)proto.Effects[0].SpellID; - uint learning_spell_id = (uint)proto.Effects[1].SpellID; + uint learn_spell_id = (uint)item.GetEffect(0).SpellID; + uint learning_spell_id = (uint)item.GetEffect(1).SpellID; SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(learn_spell_id); if (spellInfo == null) { - Log.outError(LogFilter.Player, "Player.CastItemUseSpell: Item (Entry: {0}) in have wrong spell id {1}, ignoring ", proto.GetId(), learn_spell_id); + Log.outError(LogFilter.Player, "Player.CastItemUseSpell: Item (Entry: {0}) in have wrong spell id {1}, ignoring ", item.GetEntry(), learn_spell_id); SendEquipError(InventoryResult.InternalBagError, item); return; } @@ -1532,22 +1531,16 @@ namespace Game.Entities } // item spells casted at use - for (byte i = 0; i < proto.Effects.Count; ++i) + foreach (ItemEffectRecord effectData in item.GetEffects()) { - var spellData = proto.Effects[i]; - - // no spell - if (spellData.SpellID == 0) - continue; - // wrong triggering type - if (spellData.TriggerType != ItemSpelltriggerType.OnUse) + if (effectData.TriggerType != ItemSpelltriggerType.OnUse) continue; - SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo((uint)spellData.SpellID); + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo((uint)effectData.SpellID); if (spellInfo == null) { - Log.outError(LogFilter.Player, "Player.CastItemUseSpell: Item (Entry: {0}) in have wrong spell id {1}, ignoring", proto.GetId(), spellData.SpellID); + Log.outError(LogFilter.Player, "Player.CastItemUseSpell: Item (Entry: {0}) in have wrong spell id {1}, ignoring", item.GetEntry(), effectData.SpellID); continue; } @@ -1791,13 +1784,12 @@ namespace Game.Entities void ApplyItemObtainSpells(Item item, bool apply) { - ItemTemplate itemTemplate = item.GetTemplate(); - for (byte i = 0; i < itemTemplate.Effects.Count; ++i) + foreach (ItemEffectRecord effect in item.GetEffects()) { - if (itemTemplate.Effects[i].TriggerType != ItemSpelltriggerType.OnObtain) // On obtain trigger + if (effect.TriggerType != ItemSpelltriggerType.OnObtain) // On obtain trigger continue; - int spellId = itemTemplate.Effects[i].SpellID; + int spellId = effect.SpellID; if (spellId <= 0) continue; @@ -3331,22 +3323,16 @@ namespace Game.Entities bool canTrigger = damageInfo.GetHitMask().HasAnyFlag(ProcFlagsHit.Normal | ProcFlagsHit.Critical | ProcFlagsHit.Absorb); if (canTrigger) { - for (byte i = 0; i < proto.Effects.Count; ++i) + foreach (ItemEffectRecord effectData in item.GetEffects()) { - var spellData = proto.Effects[i]; - - // no spell - if (spellData.SpellID == 0) - continue; - // wrong triggering type - if (spellData.TriggerType != ItemSpelltriggerType.ChanceOnHit) + if (effectData.TriggerType != ItemSpelltriggerType.ChanceOnHit) continue; - SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo((uint)spellData.SpellID); + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo((uint)effectData.SpellID); if (spellInfo == null) { - Log.outError(LogFilter.Player, "WORLD: unknown Item spellid {0}", spellData.SpellID); + Log.outError(LogFilter.Player, "WORLD: unknown Item spellid {0}", effectData.SpellID); continue; } diff --git a/Source/Game/Handlers/ItemHandler.cs b/Source/Game/Handlers/ItemHandler.cs index bbba4f9b3..c6f478ec4 100644 --- a/Source/Game/Handlers/ItemHandler.cs +++ b/Source/Game/Handlers/ItemHandler.cs @@ -1052,10 +1052,10 @@ namespace Game if (!item) return; - if (item.GetTemplate().Effects.Count < 2) + if (item.GetBonus().EffectCount < 2) return; - uint spellToLearn = (uint)item.GetTemplate().Effects[1].SpellID; + uint spellToLearn = (uint)item.GetEffect(1).SpellID; var entry = Global.SpellMgr.GetBattlePetSpecies(spellToLearn); if (entry != null) diff --git a/Source/Game/Handlers/SpellHandler.cs b/Source/Game/Handlers/SpellHandler.cs index 223db94bd..75a72f76b 100644 --- a/Source/Game/Handlers/SpellHandler.cs +++ b/Source/Game/Handlers/SpellHandler.cs @@ -90,9 +90,9 @@ namespace Game if (user.IsInCombat()) { - for (int i = 0; i < proto.Effects.Count; ++i) + foreach (ItemEffectRecord effect in item.GetEffects()) { - SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo((uint)proto.Effects[i].SpellID); + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo((uint)effect.SpellID); if (spellInfo != null) { if (!spellInfo.CanBeUsedInCombat()) diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index cac0a9d10..d892e9929 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -4051,34 +4051,34 @@ namespace Game.Spells bool expendable = false; bool withoutCharges = false; - for (int i = 0; i < proto.Effects.Count && i < 5; ++i) + foreach (ItemEffectRecord itemEffect in m_CastItem.GetEffects()) { - if (proto.Effects[i].SpellID != 0) + if (itemEffect.LegacySlotIndex >= m_CastItem.m_itemData.SpellCharges.GetSize()) + continue; + + // item has limited charges + if (itemEffect.Charges != 0) { - // item has limited charges - if (proto.Effects[i].Charges != 0) + if (itemEffect.Charges < 0) + expendable = true; + + int charges = m_CastItem.GetSpellCharges(itemEffect.LegacySlotIndex); + + // item has charges left + if (charges != 0) { - if (proto.Effects[i].Charges < 0) - expendable = true; + if (charges > 0) + --charges; + else + ++charges; - int charges = m_CastItem.GetSpellCharges(i); - - // item has charges left - if (charges != 0) - { - if (charges > 0) - --charges; - else - ++charges; - - if (proto.GetMaxStackSize() == 1) - m_CastItem.SetSpellCharges(i, charges); - m_CastItem.SetState(ItemUpdateState.Changed, m_caster.ToPlayer()); - } - - // all charges used - withoutCharges = (charges == 0); + if (proto.GetMaxStackSize() == 1) + m_CastItem.SetSpellCharges(itemEffect.LegacySlotIndex, charges); + m_CastItem.SetState(ItemUpdateState.Changed, m_caster.ToPlayer()); } + + // all charges used + withoutCharges = (charges == 0); } } @@ -4215,10 +4215,8 @@ namespace Game.Spells if (!m_caster.IsTypeId(TypeId.Player)) return; - ItemTemplate castItemTemplate = m_CastItem?.GetTemplate(); - // do not take reagents for these item casts - if (castItemTemplate != null && Convert.ToBoolean(castItemTemplate.GetFlags() & ItemFlags.NoReagentCost)) + if (m_CastItem != null && m_CastItem.GetTemplate().GetFlags().HasAnyFlag(ItemFlags.NoReagentCost)) return; Player p_caster = m_caster.ToPlayer(); @@ -4234,13 +4232,16 @@ namespace Game.Spells uint itemcount = m_spellInfo.ReagentCount[x]; // if CastItem is also spell reagent - if (castItemTemplate != null && castItemTemplate.GetId() == itemid) + if (m_CastItem != null && m_CastItem.GetEntry() == itemid) { - for (int s = 0; s < castItemTemplate.Effects.Count && s < 5; ++s) + foreach (ItemEffectRecord itemEffect in m_CastItem.GetEffects()) { + if (itemEffect.LegacySlotIndex >= m_CastItem.m_itemData.SpellCharges.GetSize()) + continue; + // CastItem will be used up and does not count as reagent - int charges = m_CastItem.GetSpellCharges(s); - if (castItemTemplate.Effects[s].Charges < 0 && Math.Abs(charges) < 2) + int charges = m_CastItem.GetSpellCharges(itemEffect.LegacySlotIndex); + if (itemEffect.Charges < 0 && Math.Abs(charges) < 2) { ++itemcount; break; @@ -5826,9 +5827,9 @@ namespace Game.Spells if (proto == null) return SpellCastResult.ItemNotReady; - for (int i = 0; i < proto.Effects.Count && i < 5; ++i) - if (proto.Effects[i].Charges > 0) - if (m_CastItem.GetSpellCharges(i) == 0) + foreach (ItemEffectRecord itemEffect in m_CastItem.GetEffects()) + if (itemEffect.LegacySlotIndex < m_CastItem.m_itemData.SpellCharges.GetSize() && itemEffect.Charges != 0) + if (m_CastItem.GetSpellCharges(itemEffect.LegacySlotIndex) == 0) return SpellCastResult.NoChargesRemain; // consumable cast item checks @@ -5931,11 +5932,15 @@ namespace Game.Spells ItemTemplate proto = m_CastItem.GetTemplate(); if (proto == null) return SpellCastResult.ItemNotReady; - for (int s = 0; s < proto.Effects.Count && s < 5; ++s) + + foreach (ItemEffectRecord itemEffect in m_CastItem.GetEffects()) { + if (itemEffect.LegacySlotIndex >= m_CastItem.m_itemData.SpellCharges.GetSize()) + continue; + // CastItem will be used up and does not count as reagent - int charges = m_CastItem.GetSpellCharges(s); - if (proto.Effects[s].Charges < 0 && Math.Abs(charges) < 2) + int charges = m_CastItem.GetSpellCharges(itemEffect.LegacySlotIndex); + if (itemEffect.Charges < 0 && Math.Abs(charges) < 2) { ++itemcount; break; @@ -6060,10 +6065,9 @@ namespace Game.Spells return SpellCastResult.Lowlevel; bool isItemUsable = false; - ItemTemplate proto = targetItem.GetTemplate(); - for (byte e = 0; e < proto.Effects.Count; ++e) + foreach (ItemEffectRecord itemEffect in targetItem.GetEffects()) { - if (proto.Effects[e].SpellID != 0 && proto.Effects[e].TriggerType == ItemSpelltriggerType.OnUse) + if (itemEffect.SpellID != 0 && itemEffect.TriggerType == ItemSpelltriggerType.OnUse) { isItemUsable = true; break; @@ -6238,8 +6242,8 @@ namespace Game.Spells Item item = player.GetItemByEntry(itemId); if (item != null) { - for (int x = 0; x < proto.Effects.Count && x < 5; ++x) - if (proto.Effects[x].Charges != 0 && item.GetSpellCharges(x) == proto.Effects[x].Charges) + foreach (ItemEffectRecord itemEffect in item.GetEffects()) + if (itemEffect.LegacySlotIndex <= item.m_itemData.SpellCharges.GetSize() && itemEffect.Charges != 0 && item.GetSpellCharges(itemEffect.LegacySlotIndex) == itemEffect.Charges) return SpellCastResult.ItemAtMaxCharges; } break; diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs index ee2e19291..2d1983061 100644 --- a/Source/Game/Spells/SpellEffects.cs +++ b/Source/Game/Spells/SpellEffects.cs @@ -5275,9 +5275,9 @@ namespace Game.Spells Item item = player.GetItemByEntry(effectInfo.ItemType); if (item != null) { - ItemTemplate proto = item.GetTemplate(); - for (int x = 0; x < proto.Effects.Count && x < 5; ++x) - item.SetSpellCharges(x, proto.Effects[x].Charges); + foreach (ItemEffectRecord itemEffect in item.GetEffects()) + if (itemEffect.LegacySlotIndex <= item.m_itemData.SpellCharges.GetSize()) + item.SetSpellCharges(itemEffect.LegacySlotIndex, itemEffect.Charges); item.SetState(ItemUpdateState.Changed, player); }