From 9e3bf9137415da987fd9c41b1f3fa27925cf176c Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Sat, 30 Mar 2024 13:34:03 -0400 Subject: [PATCH] [Spells] Implemented SPA 463 SE_SHIELD_TARGET (#4224) SPA 463 SE_SHIELD_TARGET Live description: "Shields your target, taking a percentage of their damage". Only example spell on live is an NPC who uses it during a raid event "Laurion's Song" expansion. SPA 54492 'Guardian Stance' Described as 100% Melee Shielding Example of mechanic. Base value = 70. Caster puts buff on target. Each melee hit Buff Target takes 70% less damage, Buff Caster receives 30% of the melee damage. Added mechanic to cause buff to fade if target or caster are separated by a distance greater than the casting range of the spell. This allows similar mechanics to the /shield ability, without a range removal mechanic it would be too easy to abuse if put on a player spell. *can not confirm live does this currently Can not be cast on self. --- common/spdat.h | 2 +- zone/attack.cpp | 73 +++++++++++++++++++++++++++++++++++++++++++++--- zone/bonuses.cpp | 19 +++++++++++++ zone/common.h | 3 ++ zone/mob.h | 1 + zone/spells.cpp | 8 ++++++ 6 files changed, 101 insertions(+), 5 deletions(-) diff --git a/common/spdat.h b/common/spdat.h index fc019d3e2..de7a0433a 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -1257,7 +1257,7 @@ typedef enum { #define SE_Ff_Override_NotFocusable 460 // implemented, @Fc, Allow spell to be focused event if flagged with 'not_focusable' in spell table, base: 1 #define SE_ImprovedDamage2 461 // implemented, @Fc, On Caster, spell damage mod pct, base: min pct, limit: max pct #define SE_FcDamageAmt2 462 // implemented, @Fc, On Caster, spell damage mod flat amt, base: amt -//#define SE_Shield_Target 463 // +#define SE_Shield_Target 463 // implemented, Base1 % damage shielded on target #define SE_PC_Pet_Rampage 464 // implemented - Base1 % chance to do rampage for base2 % of damage each melee round #define SE_PC_Pet_AE_Rampage 465 // implemented - Base1 % chance to do AE rampage for base2 % of damage each melee round #define SE_PC_Pet_Flurry_Chance 466 // implemented - Base1 % chance to do flurry from double attack hit. diff --git a/zone/attack.cpp b/zone/attack.cpp index 04ff87dab..034605788 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -6285,9 +6285,24 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac hit.damage_done += (hit.damage_done * pct_damage_reduction / 100) + defender->GetPositionalDmgTakenAmt(this); - if (defender->GetShielderID()) { - DoShieldDamageOnShielder(defender, hit.damage_done, hit.skill); - hit.damage_done -= hit.damage_done * defender->GetShieldTargetMitigation() / 100; //Default shielded takes 50 pct damage + if (defender->GetShielderID() || defender->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT]) { + bool use_shield_ability = true; + //If defender is being shielded by an ability AND has a shield spell effect buff use highest mitigation value. + if ((defender->GetShieldTargetMitigation() && defender->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT]) && + (defender->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT] >= defender->GetShieldTargetMitigation())){ + bool use_shield_ability = false; + } + + //use targeted /shield ability values + if (defender->GetShielderID() && use_shield_ability) { + DoShieldDamageOnShielder(defender, hit.damage_done, hit.skill); + hit.damage_done -= hit.damage_done * defender->GetShieldTargetMitigation() / 100; //Default shielded takes 50 pct damage + } + //use spell effect SPA 463 values + else if (defender->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT]){ + DoShieldDamageOnShielderSpellEffect(defender, hit.damage_done, hit.skill); + hit.damage_done -= hit.damage_done * defender->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT] / 100; + } } CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); @@ -6330,7 +6345,57 @@ void Mob::DoShieldDamageOnShielder(Mob *shield_target, int64 hit_damage_done, EQ hit_damage_done -= hit_damage_done * mitigation / 100; shielder->Damage(this, hit_damage_done, SPELL_UNKNOWN, skillInUse, true, -1, false, m_specialattacks); - shielder->CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); +} + +void Mob::DoShieldDamageOnShielderSpellEffect(Mob* shield_target, int64 hit_damage_done, EQ::skills::SkillType skillInUse) +{ + if (!shield_target) { + return; + } + /* + SPA 463 SE_SHIELD_TARGET + + Live description: "Shields your target, taking a percentage of their damage". + Only example spell on live is an NPC who uses it during a raid event "Laurion's Song" expansion. SPA 54492 'Guardian Stance' Described as 100% Melee Shielding + + Example of mechanic. Base value = 70. Caster puts buff on target. Each melee hit Buff Target takes 70% less damage, Buff Caster receives 30% of the melee damage. + Added mechanic to cause buff to fade if target or caster are seperated by a distance greater than the casting range of the spell. This allows similiar mechanics + to the /shield ability, without a range removal mechanic it would be too easy to abuse if put on a player spell. *can not confirm live does this currently + + Can not be cast on self. + */ + + if (shield_target->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT]) + { + if (shield_target->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_BUFFSLOT] >= 0) + { + Mob *shielder = entity_list.GetMob(shield_target->buffs[shield_target->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_BUFFSLOT]].casterid); + if (!shielder) { + shield_target->BuffFadeBySlot(shield_target->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_BUFFSLOT]); + return; + } + + int shield_spell_id = shield_target->buffs[shield_target->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_BUFFSLOT]].spellid; + if (!IsValidSpell(shield_spell_id)) { + return; + } + + float max_range = spells[shield_spell_id].range; + if (spells[shield_spell_id].aoe_range > max_range) { + max_range = spells[shield_spell_id].aoe_range; + } + max_range += 5.0f; //small buffer in case casted at exactly max range. + + if (shield_target->CalculateDistance(shielder->GetX(), shielder->GetY(), shielder->GetZ()) > max_range) { + shield_target->BuffFadeBySlot(shield_target->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_BUFFSLOT]); + return; + } + + int mitigation = 100 - shield_target->spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT]; + hit_damage_done -= hit_damage_done * mitigation / 100; + shielder->Damage(this, hit_damage_done, SPELL_UNKNOWN, skillInUse, true, -1, false, m_specialattacks); + } + } } void Mob::CommonBreakInvisibleFromCombat() diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index e34913676..05c8760cc 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -3301,6 +3301,15 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } + case SE_Shield_Target: + { + if (new_bonus->ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT] < effect_value) { + new_bonus->ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT] = effect_value; + new_bonus->ShieldTargetSpa[SBIndex::SHIELD_TARGET_BUFFSLOT] = buffslot; + } + break; + } + case SE_TriggerMeleeThreshold: new_bonus->TriggerMeleeThreshold = true; break; @@ -5919,6 +5928,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) if (negate_itembonus) { itembonuses.SkillProcSuccess[e] = effect_value; } if (negate_aabonus) { aabonuses.SkillProcSuccess[e] = effect_value; } } + break; } case SE_SkillProcAttempt: { @@ -5928,6 +5938,15 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id) if (negate_itembonus) { itembonuses.SkillProc[e] = effect_value; } if (negate_aabonus) { aabonuses.SkillProc[e] = effect_value; } } + break; + } + + case SE_Shield_Target: { + if (negate_spellbonus) { + spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_MITIGATION_PERCENT] = effect_value; + spellbonuses.ShieldTargetSpa[SBIndex::SHIELD_TARGET_BUFFSLOT] = effect_value; + } + break; } } } diff --git a/zone/common.h b/zone/common.h index b3b1815ac..547dd6e0d 100644 --- a/zone/common.h +++ b/zone/common.h @@ -512,6 +512,7 @@ struct StatBonuses { uint8 invisibility; // invisibility level uint8 invisibility_verse_undead; // IVU level uint8 invisibility_verse_animal; // IVA level + int32 ShieldTargetSpa[2]; // [0] base = % mitigation amount, [1] buff slot // AAs int32 TrapCircumvention; // reduce chance to trigger a trap. @@ -657,6 +658,8 @@ namespace SBIndex { constexpr uint16 COMBAT_PROC_SPELL_ID = 1; // SPA constexpr uint16 COMBAT_PROC_RATE_MOD = 2; // SPA constexpr uint16 COMBAT_PROC_REUSE_TIMER = 3; // SPA + constexpr uint16 SHIELD_TARGET_MITIGATION_PERCENT = 0; // SPA 463 + constexpr uint16 SHIELD_TARGET_BUFFSLOT = 1; // SPA 463 }; diff --git a/zone/mob.h b/zone/mob.h index 08735706e..1e5ec9548 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -995,6 +995,7 @@ public: inline void SetDualWieldingSameDelayWeapons(int32 val) { dw_same_delay = val; } bool IsTargetedFocusEffect(int focus_type); bool HasPersistDeathIllusion(int32 spell_id); + void DoShieldDamageOnShielderSpellEffect(Mob* shield_target, int64 hit_damage_done, EQ::skills::SkillType skillInUse); bool TryDoubleMeleeRoundEffect(); diff --git a/zone/spells.cpp b/zone/spells.cpp index 036ffde36..6e5a95dac 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -863,6 +863,14 @@ bool Mob::DoCastingChecksOnTarget(bool check_on_casting, int32 spell_id, Mob *sp } } } + /* + Cannot cast shield target on self + */ + if (this == spell_target && IsEffectInSpell(spell_id, SE_Shield_Target)) { + LogSpells("You cannot shield yourself"); + Message(Chat::SpellFailure, "You cannot shield yourself."); + return false; + } /* Cannot cast life tap on self */