From bafd5c4cb2ac9f85431ba2c20aad8792f1ae4acc Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Fri, 16 Jul 2021 00:11:06 -0400 Subject: [PATCH] Implemented SPA 481, 485,486,512 Implemented SE_Fc_Cast_Spell_On_Land 481 Focus effect that is checked when a spell is cast on a target, if target has this focus effect and all limiting criteria are met, then the target will cast a spell as specified by the focus. Can be given a roll chance for success. Base1=Chance, Base2=Spellid Note: This spell has a huge amount of potential applications. See 'Alliance' type spells on live. (ie live spell 50247) Implemented associated focus limits seen in live spells. SE_Ff_CasterClass 485 - Caster of spell on target with a focus effect that is checked by incoming spells must be specified class or classes. SE_Ff_Same_Caster 486 -Caster of spell on target with a focus effect that is checked by incoming spells 0=Must be different caster 1=Must be same caster The following is an associated effect seen with SPA 481 SE_Proc_Timer_Modifier 512 This provides a way to rate limit the amount of spell triggers generated by SPA 481. For example after 1 successful spell trigger no additional spells can be triggered for 1.5 seconds. Ie. Base=1 and Base2 1500. Written in a flexible format to allow scaling of multiple different buffs with this effect at same time. --- common/spdat.h | 9 +- zone/aggro.cpp | 1 + zone/bonuses.cpp | 2 + zone/client_process.cpp | 3 + zone/common.h | 3 + zone/mob.cpp | 9 +- zone/mob.h | 7 +- zone/mob_ai.cpp | 3 + zone/spell_effects.cpp | 231 ++++++++++++++++++++++++++++++++++++---- zone/spells.cpp | 6 ++ zone/zonedb.cpp | 2 + 11 files changed, 244 insertions(+), 32 deletions(-) diff --git a/common/spdat.h b/common/spdat.h index e93cb1121..ad0c3a9ee 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -165,6 +165,7 @@ #define MAX_SYMPATHETIC_PROCS 10 // Number of sympathetic procs a client can have (This is arbitrary) + const int Z_AGGRO=10; const uint32 MobAISpellRange=100; // max range of buffs @@ -823,12 +824,12 @@ typedef enum { #define SE_Hatelist_To_Tail_Index 478 // Implemented - Chance to be set to bottom of rampage list //#define SE_Ff_Value_Min 479 // //#define SE_Ff_Value_Max 480 // -//#define SE_Fc_Cast_Spell_On_Land 481 // +#define SE_Fc_Cast_Spell_On_Land 481 // [FOCUS EFFECT] Spells cast on target with this Focus Effect will have chance to cause a Spell to be cast if limits met. //#define SE_Skill_Base_Damage_Mod 482 // #define SE_Fc_Spell_Damage_Pct_IncomingPC 483 // Implemented - modifies incoming spell damage by percent #define SE_Fc_Spell_Damage_Amt_IncomingPC 484 // Implemented - modifies incoming spell damage by flat amount. Typically adds damage to incoming spells. -//#define SE_Ff_CasterClass 485 // -//#define SE_Ff_Same_Caster 486 // +#define SE_Ff_CasterClass 485 // [FOCUS LIMIT] Caster of spell on target with a focus effect that is checked by incoming spells must be specified class. +#define SE_Ff_Same_Caster 486 // [FOCUS LIMIT] Caster of spell on target with a focus effect that is checked by incoming spells 0=Must be different caster 1=Must be same caster //#define SE_Extend_Tradeskill_Cap 487 // //#define SE_Defender_Melee_Force_Pct_PC 488 // //#define SE_Worn_Endurance_Regen_Cap 489 // @@ -854,7 +855,7 @@ typedef enum { #define SE_Health_Transfer 509 // implemented - exchange health for damage or healing on a target. ie Lifeburn/Act of Valor //#define SE_Fc_ResistIncoming 510 // //#define SE_Ff_FocusTimerMin 511 // -//#define SE_Proc_Timer_Modifier 512 // +#define SE_Proc_Timer_Modifier 512 // //#define SE_Mana_Max_Percent 513 // //#define SE_Endurance_Max_Percent 514 // #define SE_AC_Avoidance_Max_Percent 515 // implemented - stackable avoidance modifier diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 5829bc3a3..a3f52fa55 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -1018,6 +1018,7 @@ int32 Mob::CheckAggroAmount(uint16 spell_id, Mob *target, bool isproc) case SE_Mez: case SE_Charm: case SE_Fear: + case SE_Fearstun: AggroAmount += default_aggro; break; case SE_Root: diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index ec9ddafb1..ef49f4da2 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -3771,6 +3771,8 @@ uint8 Mob::IsFocusEffect(uint16 spell_id,int effect_index, bool AA,uint32 aa_eff return focusFcMute; case SE_FcTimerRefresh: return focusFcTimerRefresh; + case SE_Fc_Cast_Spell_On_Land: + return focusFcCastSpellOnLand; case SE_FcStunTimeMod: return focusFcStunTimeMod; case SE_FcHealPctCritIncoming: diff --git a/zone/client_process.cpp b/zone/client_process.cpp index a78095a30..d805be19a 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -538,6 +538,9 @@ bool Client::Process() { } } + if (focus_proc_limit_timer.Check() && !dead) + FocusProcLimitProcess(); + if (client_state == CLIENT_KICKED) { Save(); OnDisconnect(true); diff --git a/zone/common.h b/zone/common.h index c95a750db..f7db01dd6 100644 --- a/zone/common.h +++ b/zone/common.h @@ -137,6 +137,7 @@ typedef enum { //focus types focusFcHealPctIncoming, focusFcDamageAmtIncoming, focusFcSpellDamageAmtIncomingPC, + focusFcCastSpellOnLand, focusFcHealAmtIncoming, focusFcBaseEffects, focusIncreaseNumHits, @@ -321,6 +322,8 @@ struct Buffs_Struct { int32 ExtraDIChance; int16 RootBreakChance; //Not saved to dbase uint32 instrument_mod; + int16 focusproclimit_time; //timer to limit number of procs from focus effects + int16 focusproclimit_procamt; //amount of procs that can be cast before timer limiter is set bool persistant_buff; bool client; //True if the caster is a client bool UpdateClient; diff --git a/zone/mob.cpp b/zone/mob.cpp index 4a7dbb320..7e69441f1 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -102,6 +102,7 @@ Mob::Mob( ranged_timer(2000), tic_timer(6000), mana_timer(2000), + focus_proc_limit_timer(250), spellend_timer(0), rewind_timer(30000), bindwound_timer(10000), @@ -3806,7 +3807,7 @@ int32 Mob::GetVulnerability(Mob* caster, uint32 spell_id, uint32 ticsremaining) if((IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, SE_FcSpellVulnerability))){ - int32 focus = caster->CalcFocusEffect(focusSpellVulnerability, buffs[i].spellid, spell_id, true); + int32 focus = caster->CalcFocusEffect(focusSpellVulnerability, buffs[i].spellid, spell_id, true, buffs[tmp_buffslot].casterid); if (!focus) continue; @@ -3823,7 +3824,7 @@ int32 Mob::GetVulnerability(Mob* caster, uint32 spell_id, uint32 ticsremaining) } } - fc_spell_vulnerability_mod = caster->CalcFocusEffect(focusSpellVulnerability, buffs[tmp_buffslot].spellid, spell_id); + fc_spell_vulnerability_mod = caster->CalcFocusEffect(focusSpellVulnerability, buffs[tmp_buffslot].spellid, spell_id, false, buffs[tmp_buffslot].casterid); if (tmp_buffslot >= 0) CheckNumHitsRemaining(NumHit::MatchingSpells, tmp_buffslot); @@ -3844,7 +3845,7 @@ int32 Mob::GetVulnerability(Mob* caster, uint32 spell_id, uint32 ticsremaining) if ((IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, SE_Fc_Spell_Damage_Pct_IncomingPC))) { - int32 focus = caster->CalcFocusEffect(focusFcSpellDamagePctIncomingPC, buffs[i].spellid, spell_id, true); + int32 focus = caster->CalcFocusEffect(focusFcSpellDamagePctIncomingPC, buffs[i].spellid, spell_id, true, buffs[tmp_buffslot].casterid); if (!focus) continue; @@ -3861,7 +3862,7 @@ int32 Mob::GetVulnerability(Mob* caster, uint32 spell_id, uint32 ticsremaining) } } - fc_spell_damage_pct_incomingPC_mod = caster->CalcFocusEffect(focusFcSpellDamagePctIncomingPC, buffs[tmp_buffslot].spellid, spell_id); + fc_spell_damage_pct_incomingPC_mod = caster->CalcFocusEffect(focusFcSpellDamagePctIncomingPC, buffs[tmp_buffslot].spellid, spell_id, false, buffs[tmp_buffslot].casterid); if (tmp_buffslot >= 0) CheckNumHitsRemaining(NumHit::MatchingSpells, tmp_buffslot); diff --git a/zone/mob.h b/zone/mob.h index 64425112a..4e051d76f 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -770,7 +770,7 @@ public: void QuestJournalledSay(Client *QuestInitiator, const char *str, Journal::Options &opts); int32 GetItemStat(uint32 itemid, const char *identifier); - int16 CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, bool best_focus=false); + int16 CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, bool best_focus=false, uint16 casterid=0); uint8 IsFocusEffect(uint16 spellid, int effect_index, bool AA=false,uint32 aa_effect=0); void SendIllusionPacket(uint16 in_race, uint8 in_gender = 0xFF, uint8 in_texture = 0xFF, uint8 in_helmtexture = 0xFF, uint8 in_haircolor = 0xFF, uint8 in_beardcolor = 0xFF, uint8 in_eyecolor1 = 0xFF, uint8 in_eyecolor2 = 0xFF, @@ -837,6 +837,10 @@ public: inline int16 GetSpellPowerDistanceMod() const { return SpellPowerDistanceMod; }; inline void SetSpellPowerDistanceMod(int16 value) { SpellPowerDistanceMod = value; }; int32 GetSpellStat(uint32 spell_id, const char *identifier, uint8 slot = 0); + + void CastSpellOnLand(Mob* caster, uint32 spell_id); + void FocusProcLimitProcess(); + bool ApplyFocusProcLimiter(uint32 spell_id, int buffslot = -1); void ModSkillDmgTaken(EQ::skills::SkillType skill_num, int value); int16 GetModSkillDmgTaken(const EQ::skills::SkillType skill_num); @@ -1419,6 +1423,7 @@ protected: int16 slow_mitigation; // Allows for a slow mitigation (100 = 100%, 50% = 50%) Timer tic_timer; Timer mana_timer; + Timer focus_proc_limit_timer; //spell casting vars Timer spellend_timer; diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 3791ed3f9..3766a2546 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -1125,6 +1125,9 @@ void Mob::AI_Process() { ProjectileAttack(); + if (focus_proc_limit_timer.Check()) + FocusProcLimitProcess(); + auto npcSpawnPoint = CastToNPC()->GetSpawnPoint(); if (GetSpecialAbility(TETHER)) { float tether_range = static_cast(GetSpecialAbilityParam(TETHER, 0)); diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 9eadeef14..5006f7783 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -2934,17 +2934,22 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - if (GetLevel() <= spells[spell_id].max[i]) { - if (IsClient()) + if (spells[spell_id].max[i] == 0 || GetLevel() <= spells[spell_id].max[i]) { + if (IsClient() && spells[spell_id].base2[i]) Stun(spells[spell_id].base2[i]); else - Stun(effect_value); + Stun(spells[spell_id].base[i]); } else caster->MessageString(Chat::SpellFailure, FEAR_TOO_HIGH); break; } - + + case SE_Proc_Timer_Modifier:{ + buffs[buffslot].focusproclimit_procamt = spells[spell_id].base[i]; //Set max amount of procs before lockout timer + break; + } + case SE_PersistentEffect: MakeAura(spell_id); break; @@ -3206,6 +3211,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_Pet_Crit_Melee_Damage_Pct_Owner: case SE_Pet_Add_Atk: case SE_TwinCastBlocker: + case SE_Fc_Cast_Spell_On_Land: + case SE_Ff_CasterClass: + case SE_Ff_Same_Caster: { break; } @@ -4481,13 +4489,14 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) case SE_Blank: break; - // Handle Focus Limits + // Handle Focus Limits case SE_LimitResist: if (base1 < 0) { if (spell.resisttype == -base1) // Exclude LimitFailure = true; - } else { + } + else { LimitInclude[0] = true; if (spell.resisttype == base1) // Include LimitInclude[1] = true; @@ -4508,12 +4517,13 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) // every level over cap reduces the effect by base2 percent unless from a clicky when // ItemCastsUseFocus is true if (lvldiff > 0 && (spell_level <= RuleI(Character, MaxLevel) || - RuleB(Character, ItemCastsUseFocus) == false)) { + RuleB(Character, ItemCastsUseFocus) == false)) { if (base2 > 0) { lvlModifier -= base2 * lvldiff; if (lvlModifier < 1) LimitFailure = true; - } else + } + else LimitFailure = true; } break; @@ -4537,7 +4547,8 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) if (base1 < 0) { // Exclude if (spell_id == -base1) LimitFailure = true; - } else { + } + else { LimitInclude[2] = true; if (spell_id == base1) // Include LimitInclude[3] = true; @@ -4554,13 +4565,15 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) if (base1 < 0) { if (IsEffectInSpell(spell_id, -base1)) // Exclude LimitFailure = true; - } else { + } + else { LimitInclude[4] = true; // they use 33 here for all classes ... unsure if the type check is really needed if (base1 == SE_SummonPet && type == focusReagentCost) { if (IsSummonPetSpell(spell_id) || IsSummonSkeletonSpell(spell_id)) LimitInclude[5] = true; - } else { + } + else { if (IsEffectInSpell(spell_id, base1)) // Include LimitInclude[5] = true; } @@ -4594,7 +4607,8 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) if (base1 < 0) { if (-base1 == spell.targettype) // Exclude LimitFailure = true; - } else { + } + else { LimitInclude[6] = true; if (base1 == spell.targettype) // Include LimitInclude[7] = true; @@ -4613,7 +4627,8 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) if (base1 < 0) { if (-base1 == spell.spellgroup) // Exclude LimitFailure = true; - } else { + } + else { LimitInclude[8] = true; if (base1 == spell.spellgroup) // Include LimitInclude[9] = true; @@ -4624,7 +4639,8 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) if (base1 < 0) { if (-base1 == spell.skill) LimitFailure = true; - } else { + } + else { LimitInclude[10] = true; if (base1 == spell.skill) LimitInclude[11] = true; @@ -4635,7 +4651,8 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) if (base1 < 0) { // Exclude if (CheckSpellCategory(spell_id, base1, SE_LimitSpellClass)) return (0); - } else { + } + else { LimitInclude[12] = true; if (CheckSpellCategory(spell_id, base1, SE_LimitSpellClass)) // Include LimitInclude[13] = true; @@ -4646,7 +4663,8 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) if (base1 < 0) { // Exclude if (CheckSpellCategory(spell_id, base1, SE_LimitSpellSubclass)) return (0); - } else { + } + else { LimitInclude[14] = true; if (CheckSpellCategory(spell_id, base1, SE_LimitSpellSubclass)) // Include LimitInclude[15] = true; @@ -4675,7 +4693,7 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) LimitFailure = true; break; - // Handle Focus Effects + // Handle Focus Effects case SE_ImprovedDamage: if (type == focusImprovedDamage && base1 > value) value = base1; @@ -4742,11 +4760,13 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) if (value > 0) { if (base1 > value) value = base1; - } else { + } + else { if (base1 < value) value = base1; } - } else + } + else value = base1; } break; @@ -4760,7 +4780,8 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) if (type == focusTriggerOnCast) { if (zone->random.Roll(base1)) { value = base2; - } else { + } + else { value = 0; LimitFailure = true; } @@ -4771,7 +4792,7 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) if (type == focusSpellVulnerability) value = base1; break; - + case SE_Fc_Spell_Damage_Pct_IncomingPC: if (type == focusFcSpellDamagePctIncomingPC) value = base1; @@ -4789,7 +4810,7 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) value = base1; break; - // Note if using these as AA, make sure this is first focus used. + // Note if using these as AA, make sure this is first focus used. case SE_SympatheticProc: if (type == focusSympatheticProc) value = base2; @@ -4874,6 +4895,14 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) if (type == focusFcStunTimeMod) value = base1; break; + + case SE_Fc_Cast_Spell_On_Land: + if (type == focusFcCastSpellOnLand) { + if (zone->random.Roll(base1)) { + value = base2; + } + break; + } } } @@ -4890,8 +4919,13 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) //given an item/spell's focus ID and the spell being cast, determine the focus ammount, if any //assumes that spell_id is not a bard spell and that both ids are valid spell ids -int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, bool best_focus) +int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, bool best_focus, uint16 casterid) { + /* + 'this' is always the caster of the spell_id, most foci check for effects on the caster, however some check for effects on the target. + 'casterid' is the casterid of the caster of spell_id, used when spell_id is cast on a target with a focus effect that is checked by incoming spell. + */ + if (!IsValidSpell(focus_id) || !IsValidSpell(spell_id)) return 0; @@ -5125,6 +5159,23 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo } break; + case SE_Ff_Same_Caster://hmm do i need to pass casterid from buff slot here + if (focus_spell.base[i] == 0) { + if (casterid == GetID()) + return 0;//Mob casting is same as target, fail if you are casting on yourself. + } + else if (focus_spell.base[i] == 1) { + if (casterid != GetID()) + return 0;//Mob casting is not same as target, fail if you are not casting on yourself. + } + break; + + case SE_Ff_CasterClass: + // Do not use this limit more then once per spell. If multiple class, treat value like items would. + if (!PassLimitClass(focus_spell.base[i], GetClass())) + return 0; + break; + // handle effects case SE_ImprovedDamage: if (type == focusImprovedDamage) { @@ -5414,6 +5465,14 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo value = focus_spell.base[i]; break; + case SE_Fc_Cast_Spell_On_Land: + if (type == focusFcCastSpellOnLand) { + if (zone->random.Roll(focus_spell.base[i])) { + value = focus_spell.base2[i]; + } + break; + } + #if EQDEBUG >= 6 // this spits up a lot of garbage when calculating spell focuses // since they have all kinds of extra effects on them. @@ -7089,6 +7148,132 @@ void Mob::TryTriggerThreshHold(int32 damage, int effect_id, Mob* attacker){ } } +void Mob::CastSpellOnLand(Mob* caster, uint32 spell_id) +{ + /* + This function checks for incoming spells on a mob, if they meet the criteria for focus SE_Fc_Cast_Spell_on_Land then + a new spell will be cast by THIS mob as specified by the focus effect. Note: Chance to cast the spell is determined in + the CalcFocusEffect function if not 100pct. + ApplyFocusProcLimiter() function checks for SE_Proc_Timer_Modifier which allows for limiting how often a spell from effect can be triggered + for example, if set to base=1 and base2= 1500, then for everyone 1 successful trigger, you will be unable to trigger again for 1.5 seconds. + */ + if (!caster) + return; + + uint32 trigger_spell_id = 0; + + //Step 1: Check this focus effect exists on the mob. + if (spellbonuses.FocusEffects[focusFcCastSpellOnLand]) { + + int buff_count = GetMaxTotalSlots(); + for (int i = 0; i < buff_count; i++) { + + if ((IsValidSpell(buffs[i].spellid) && (buffs[i].spellid != spell_id) && IsEffectInSpell(buffs[i].spellid, SE_Fc_Cast_Spell_On_Land))) { + + //Step 2: Check if we pass all focus limiters and focus chance roll + trigger_spell_id = caster->CalcFocusEffect(focusFcCastSpellOnLand, buffs[i].spellid, spell_id, false, buffs[i].casterid); + + if (IsValidSpell(trigger_spell_id) && (trigger_spell_id != spell_id)) { + + //Step 3: Check if SE_Proc_Time_Modifier is present and if so apply it. + if (ApplyFocusProcLimiter(buffs[i].spellid, i)) { + + //Step 4: Cast spells + if (IsBeneficialSpell(trigger_spell_id)) { + SpellFinished(trigger_spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[trigger_spell_id].ResistDiff); + } + else { + Mob* current_target = GetTarget(); + //For now don't let players cast detrimental effects on themselves if they are targeting themselves. Need to confirm behavior. + if (current_target && current_target->GetID() != GetID()) + SpellFinished(trigger_spell_id, current_target, EQ::spells::CastingSlot::Item, 0, -1, spells[trigger_spell_id].ResistDiff); + } + } + + if (i >= 0) + CheckNumHitsRemaining(NumHit::MatchingSpells, i); + } + } + } + } +} + +bool Mob::ApplyFocusProcLimiter(uint32 spell_id, int buffslot) +{ + if (buffslot < 0) + return false; + + //Do not allow spell cast if timer is active. + if (buffs[buffslot].focusproclimit_time > 0) + return false; + + /* + SE_Proc_Timer_Modifier + base1= amount of total procs allowed until lock out timer is triggered + base2= lock out timer, which prevents any more procs + This system allows easy scaling for multiple different buffs with same effects each having seperate active individual timer checks. + */ + + if (IsValidSpell(spell_id)) { + + for (int i = 0; i < EFFECT_COUNT; i++) { + + //Step 1: Find which slot the spell effect is in. + if (spells[spell_id].effectid[i] == SE_Proc_Timer_Modifier) { + + //Step 2: Check if you still have procs left to trigger, and if so reduce available procs + if (buffs[buffslot].focusproclimit_procamt > 0) { + --buffs[buffslot].focusproclimit_procamt; //Reduce total amount of triggers possible. + } + + //Step 3: If you used all the procs in the time frame then set proc amount back to max + if (buffs[buffslot].focusproclimit_procamt == 0 && spells[spell_id].base[i] > 0) { + buffs[buffslot].focusproclimit_procamt = spells[spell_id].base[i];//reset to max + + //Step 4: Check if timer exists on this spell, and then set it, and activiate global timer if not active + if (buffs[buffslot].focusproclimit_time ==0 && spells[spell_id].base2[i] > 0) { + buffs[buffslot].focusproclimit_time = spells[spell_id].base2[i];//set time + + //Step 5: If timer is not already running, then start it. + if (!focus_proc_limit_timer.Enabled()) { + focus_proc_limit_timer.Start(250); + } + + return true; + } + } + } + } + } + return true; +} + +void Mob::FocusProcLimitProcess() +{ + /* + Fast 250 ms uinversal timer for checking Focus effects that have a proc rate limiter set in actual time. + */ + bool stop_timer = true; + int buff_count = GetMaxTotalSlots(); + for (int buffs_i = 0; buffs_i < buff_count; ++buffs_i) + { + if (IsValidSpell(buffs[buffs_i].spellid)) + { + if (buffs[buffs_i].focusproclimit_time > 0) { + buffs[buffs_i].focusproclimit_time -= 250; + stop_timer = false; + } + + if (buffs[buffs_i].focusproclimit_time < 0) + buffs[buffs_i].focusproclimit_time = 0; + } + } + + if (stop_timer) { + focus_proc_limit_timer.Disable(); + } +} + bool Mob::CheckSpellCategory(uint16 spell_id, int category_id, int effect_id){ if (!IsValidSpell(spell_id) || !category_id) diff --git a/zone/spells.cpp b/zone/spells.cpp index 1a1cd9a9c..2962ee81f 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -3350,6 +3350,8 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid buffs[emptyslot].dot_rune = 0; buffs[emptyslot].ExtraDIChance = 0; buffs[emptyslot].RootBreakChance = 0; + buffs[emptyslot].focusproclimit_time = 0; + buffs[emptyslot].focusproclimit_procamt = 0; buffs[emptyslot].instrument_mod = caster ? caster->GetInstrumentMod(spell_id) : 10; if (level_override > 0) { @@ -3972,6 +3974,10 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r return false; } + //Check SE_Fc_Cast_Spell_On_Land SPA 481 on target, if hit by this spell and Conditions are Met then target will cast the specified spell. + if (spelltar) + spelltar->CastSpellOnLand(this, spell_id); + if (IsValidSpell(spells[spell_id].RecourseLink) && spells[spell_id].RecourseLink != spell_id) SpellFinished(spells[spell_id].RecourseLink, this, CastingSlot::Item, 0, -1, spells[spells[spell_id].RecourseLink].ResistDiff); diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 34d437a90..9c493a915 100755 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -3666,6 +3666,8 @@ void ZoneDatabase::LoadBuffs(Client *client) buffs[slot_id].caston_z = caston_z; buffs[slot_id].ExtraDIChance = ExtraDIChance; buffs[slot_id].RootBreakChance = 0; + buffs[slot_id].focusproclimit_time = 0; + buffs[slot_id].focusproclimit_procamt = 0; buffs[slot_id].UpdateClient = false; buffs[slot_id].instrument_mod = instrument_mod; }