From f1bfd6bc2a84fc44b1a1127d7a5d9e32588122cc Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Fri, 5 Nov 2021 14:14:11 -0400 Subject: [PATCH] [Spells] Implemented SPA 512 SE_Proc_Timer_Modifier, Fixed AA procs not working (#1646) * update for SPA 511 * remove debugs, AA implemented * update * twinprocfix * AA procs added * format update * update * proctimer limits * update * rename function renamed function only check for buffs value > 0, don't need to check for AA's which are negative ID's * pre merge * variable updates * Update spell_effects.cpp * var rename update var name to better represent its function. * updated proc struct added reuse timer * reuse timer to spell procs * updates * debug remove * Update mob.cpp * fix * merge --- common/spdat.cpp | 35 ++++++++ common/spdat.h | 8 +- zone/attack.cpp | 181 +++++++++++++++++++++++++++++++++-------- zone/bonuses.cpp | 70 ++++++++++++++++ zone/client_packet.cpp | 6 +- zone/common.h | 5 ++ zone/mob.cpp | 65 ++++++++++----- zone/mob.h | 15 +++- zone/pets.cpp | 13 +-- zone/spell_effects.cpp | 163 +++++++++++++++++++++++++++++-------- zone/spells.cpp | 17 ++-- 11 files changed, 469 insertions(+), 109 deletions(-) diff --git a/common/spdat.cpp b/common/spdat.cpp index e52da4da1..42b2fe26d 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -1597,3 +1597,38 @@ int32 GetViralSpreadRange(int32 spell_id) { return spells[spell_id].viral_range; } + +uint32 GetProcLimitTimer(int32 spell_id, int proc_type) { + + //This allows for support for effects that may have multiple different proc types and timers. + if (!IsValidSpell(spell_id)) { + return 0; + } + + bool use_next_timer = false; + for (int i = 0; i < EFFECT_COUNT; ++i) { + + if (proc_type == SE_WeaponProc) { + if (spells[spell_id].effect_id[i] == SE_WeaponProc || spells[spell_id].effect_id[i] == SE_AddMeleeProc) { + use_next_timer = true; + } + } + + if (proc_type == SE_RangedProc) { + if (spells[spell_id].effect_id[i] == SE_RangedProc) { + use_next_timer = true; + } + } + + if (proc_type == SE_DefensiveProc) { + if (spells[spell_id].effect_id[i] == SE_DefensiveProc) { + use_next_timer = true; + } + } + + if (use_next_timer && spells[spell_id].effect_id[i] == SE_Proc_Timer_Modifier) { + return spells[spell_id].limit_value[i]; + } + } + return 0; +} diff --git a/common/spdat.h b/common/spdat.h index 9710e1825..fd9413444 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -179,8 +179,11 @@ #define MAX_RESISTABLE_EFFECTS 12 // Number of effects that are typcially checked agianst resists. #define MaxLimitInclude 16 //Number(x 0.5) of focus Limiters that have inclusive checks used when calcing focus effects #define MAX_SKILL_PROCS 4 //Number of spells to check skill procs from. (This is arbitrary) [Single spell can have multiple proc checks] +#define MAX_AA_PROCS 16 //(Actual Proc Amount is MAX_AA_PROCS/4) Number of spells to check AA procs from. (This is arbitrary) #define MAX_SYMPATHETIC_PROCS 10 // Number of sympathetic procs a client can have (This is arbitrary) -#define MAX_FOCUS_PROC_LIMIT_TIMERS 20 //Number of proc limiting timers that can be going at same time (This is arbitrary) +#define MAX_FOCUS_PROC_LIMIT_TIMERS 20 //Number of focus recast timers that can be going at same time (This is arbitrary) +#define MAX_PROC_LIMIT_TIMERS 8 //Number of proc delay timers that can be going at same time, different proc types get their own timer array. (This is arbitrary) + const int Z_AGGRO=10; @@ -1209,7 +1212,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 // implemented, @Fc, On Target, resist modifier, base: amt #define SE_Ff_FocusTimerMin 511 // implemented, @Ff, sets a recast time until focus can be used again, base: 1, limit: time ms, Note: ie. limit to 1 trigger every 1.5 seconds -#define SE_Proc_Timer_Modifier 512 // not implemented - limits procs per amount of a time based on timer value (ie limit to 1 proc every 55 seconds) +#define SE_Proc_Timer_Modifier 512 // implemented - limits procs per amount of a time based on timer value, base: 1, limit: time ms, Note:, ie limit to 1 proc every 55 seconds) //#define SE_Mana_Max_Percent 513 // //#define SE_Endurance_Max_Percent 514 // #define SE_AC_Avoidance_Max_Percent 515 // implemented - stackable avoidance modifier @@ -1514,6 +1517,7 @@ int GetViralMinSpreadTime(int32 spell_id); int GetViralMaxSpreadTime(int32 spell_id); int GetViralSpreadRange(int32 spell_id); bool IsInstrumentModAppliedToSpellEffect(int32 spell_id, int effect); +uint32 GetProcLimitTimer(int32 spell_id, int proc_type); int CalcPetHp(int levelb, int classb, int STA = 75); int GetSpellEffectDescNum(uint16 spell_id); diff --git a/zone/attack.cpp b/zone/attack.cpp index f9f086f7f..28a6f995e 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -3380,17 +3380,37 @@ int32 Mob::ReduceAllDamage(int32 damage) bool Mob::HasProcs() const { - for (int i = 0; i < MAX_PROCS; i++) - if (PermaProcs[i].spellID != SPELL_UNKNOWN || SpellProcs[i].spellID != SPELL_UNKNOWN) + for (int i = 0; i < MAX_PROCS; i++) { + if (PermaProcs[i].spellID != SPELL_UNKNOWN || SpellProcs[i].spellID != SPELL_UNKNOWN) { return true; + } + } + + if (IsClient()) { + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + if (aabonuses.SpellProc[i]) { + return true; + } + } + } return false; } bool Mob::HasDefensiveProcs() const { - for (int i = 0; i < MAX_PROCS; i++) - if (DefensiveProcs[i].spellID != SPELL_UNKNOWN) + for (int i = 0; i < MAX_PROCS; i++) { + if (DefensiveProcs[i].spellID != SPELL_UNKNOWN) { return true; + } + } + + if (IsClient()) { + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + if (aabonuses.DefensiveProc[i]) { + return true; + } + } + } return false; } @@ -3415,9 +3435,19 @@ bool Mob::HasSkillProcSuccess() const bool Mob::HasRangedProcs() const { - for (int i = 0; i < MAX_PROCS; i++) - if (RangedProcs[i].spellID != SPELL_UNKNOWN) + for (int i = 0; i < MAX_PROCS; i++){ + if (RangedProcs[i].spellID != SPELL_UNKNOWN) { return true; + } + } + + if (IsClient()) { + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + if (aabonuses.RangedProc[i]) { + return true; + } + } + } return false; } @@ -4051,33 +4081,61 @@ void Mob::TryDefensiveProc(Mob *on, uint16 hand) { return; } - if (!HasDefensiveProcs()) + if (!HasDefensiveProcs()) { return; + } if (!on->HasDied() && on->GetHP() > 0) { float ProcChance, ProcBonus; on->GetDefensiveProcChances(ProcBonus, ProcChance, hand, this); - if (hand != EQ::invslot::slotPrimary) + if (hand != EQ::invslot::slotPrimary) { ProcChance /= 2; + } int level_penalty = 0; int level_diff = GetLevel() - on->GetLevel(); - if (level_diff > 6)//10% penalty per level if > 6 levels over target. + if (level_diff > 6) {//10% penalty per level if > 6 levels over target. level_penalty = (level_diff - 6) * 10; + } ProcChance -= ProcChance*level_penalty / 100; - if (ProcChance < 0) + if (ProcChance < 0) { return; + } + //Spell Procs and Quest added procs for (int i = 0; i < MAX_PROCS; i++) { if (IsValidSpell(DefensiveProcs[i].spellID)) { - float chance = ProcChance * (static_cast(DefensiveProcs[i].chance) / 100.0f); - if (zone->random.Roll(chance)) { - ExecWeaponProc(nullptr, DefensiveProcs[i].spellID, on); - CheckNumHitsRemaining(NumHit::DefensiveSpellProcs, 0, DefensiveProcs[i].base_spellID); + if (!IsProcLimitTimerActive(DefensiveProcs[i].base_spellID, DefensiveProcs[i].proc_reuse_time, SE_DefensiveProc)) { + float chance = ProcChance * (static_cast(DefensiveProcs[i].chance) / 100.0f); + if (zone->random.Roll(chance)) { + ExecWeaponProc(nullptr, DefensiveProcs[i].spellID, on); + CheckNumHitsRemaining(NumHit::DefensiveSpellProcs, 0, DefensiveProcs[i].base_spellID); + SetProcLimitTimer(DefensiveProcs[i].base_spellID, DefensiveProcs[i].proc_reuse_time, SE_DefensiveProc); + } + } + } + } + + //AA Procs + if (IsClient()){ + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + int32 aa_rank_id = aabonuses.DefensiveProc[i]; + int32 aa_spell_id = aabonuses.DefensiveProc[i + 1]; + int32 aa_proc_chance = 100 + aabonuses.DefensiveProc[i + 2]; + uint32 aa_proc_reuse_timer = aabonuses.DefensiveProc[i + 3]; + + if (aa_rank_id) { + if (!IsProcLimitTimerActive(-aa_rank_id, aa_proc_reuse_timer, SE_DefensiveProc)) { + float chance = ProcChance * (static_cast(aa_proc_chance) / 100.0f); + if (zone->random.Roll(chance) && IsValidSpell(aa_spell_id)) { + ExecWeaponProc(nullptr, aa_spell_id, on); + SetProcLimitTimer(-aa_rank_id, aa_proc_reuse_timer, SE_DefensiveProc); + } + } } } } @@ -4122,7 +4180,9 @@ void Mob::TryWeaponProc(const EQ::ItemInstance* weapon_g, Mob *on, uint16 hand) void Mob::TryWeaponProc(const EQ::ItemInstance *inst, const EQ::ItemData *weapon, Mob *on, uint16 hand) { - + if (!on) { + return; + } if (!weapon) return; uint16 skillinuse = 28; @@ -4206,6 +4266,10 @@ void Mob::TryWeaponProc(const EQ::ItemInstance *inst, const EQ::ItemData *weapon void Mob::TrySpellProc(const EQ::ItemInstance *inst, const EQ::ItemData *weapon, Mob *on, uint16 hand) { + if (!on) { + return; + } + float ProcBonus = static_cast(spellbonuses.SpellProcChance + itembonuses.SpellProcChance + aabonuses.SpellProcChance); float ProcChance = 0.0f; @@ -4239,7 +4303,7 @@ void Mob::TrySpellProc(const EQ::ItemInstance *inst, const EQ::ItemData *weapon, // Not ranged if (!rangedattk) { - // Perma procs (AAs) + // Perma procs (Not used for AA, they are handled below) if (PermaProcs[i].spellID != SPELL_UNKNOWN) { if (zone->random.Roll(PermaProcs[i].chance)) { // TODO: Do these get spell bonus? LogCombat("Permanent proc [{}] procing spell [{}] ([{}] percent chance)", i, PermaProcs[i].spellID, PermaProcs[i].chance); @@ -4256,32 +4320,79 @@ void Mob::TrySpellProc(const EQ::ItemInstance *inst, const EQ::ItemData *weapon, poison_slot=i; continue; // Process the poison proc last per @mackal } - - float chance = ProcChance * (static_cast(SpellProcs[i].chance) / 100.0f); - if (zone->random.Roll(chance)) { - LogCombat("Spell proc [{}] procing spell [{}] ([{}] percent chance)", i, SpellProcs[i].spellID, chance); - SendBeginCast(SpellProcs[i].spellID, 0); - ExecWeaponProc(nullptr, SpellProcs[i].spellID, on, SpellProcs[i].level_override); - CheckNumHitsRemaining(NumHit::OffensiveSpellProcs, 0, - SpellProcs[i].base_spellID); - } - else { - LogCombat("Spell proc [{}] failed to proc [{}] ([{}] percent chance)", i, SpellProcs[i].spellID, chance); + + if (!IsProcLimitTimerActive(SpellProcs[i].base_spellID, SpellProcs[i].proc_reuse_time, SE_WeaponProc)) { + float chance = ProcChance * (static_cast(SpellProcs[i].chance) / 100.0f); + if (zone->random.Roll(chance)) { + LogCombat("Spell proc [{}] procing spell [{}] ([{}] percent chance)", i, SpellProcs[i].spellID, chance); + SendBeginCast(SpellProcs[i].spellID, 0); + ExecWeaponProc(nullptr, SpellProcs[i].spellID, on, SpellProcs[i].level_override); + SetProcLimitTimer(SpellProcs[i].base_spellID, SpellProcs[i].proc_reuse_time, SE_WeaponProc); + CheckNumHitsRemaining(NumHit::OffensiveSpellProcs, 0, SpellProcs[i].base_spellID); + } + else { + LogCombat("Spell proc [{}] failed to proc [{}] ([{}] percent chance)", i, SpellProcs[i].spellID, chance); + } } } } else if (rangedattk) { // ranged only // ranged spell procs (buffs) if (RangedProcs[i].spellID != SPELL_UNKNOWN) { - float chance = ProcChance * (static_cast(RangedProcs[i].chance) / 100.0f); - if (zone->random.Roll(chance)) { - LogCombat("Ranged proc [{}] procing spell [{}] ([{}] percent chance)", i, RangedProcs[i].spellID, chance); - ExecWeaponProc(nullptr, RangedProcs[i].spellID, on); - CheckNumHitsRemaining(NumHit::OffensiveSpellProcs, 0, - RangedProcs[i].base_spellID); + + if (!IsProcLimitTimerActive(RangedProcs[i].base_spellID, RangedProcs[i].proc_reuse_time, SE_RangedProc)) { + float chance = ProcChance * (static_cast(RangedProcs[i].chance) / 100.0f); + if (zone->random.Roll(chance)) { + LogCombat("Ranged proc [{}] procing spell [{}] ([{}] percent chance)", i, RangedProcs[i].spellID, chance); + ExecWeaponProc(nullptr, RangedProcs[i].spellID, on); + CheckNumHitsRemaining(NumHit::OffensiveSpellProcs, 0, RangedProcs[i].base_spellID); + SetProcLimitTimer(RangedProcs[i].base_spellID, RangedProcs[i].proc_reuse_time, SE_RangedProc); + } + else { + LogCombat("Ranged proc [{}] failed to proc [{}] ([{}] percent chance)", i, RangedProcs[i].spellID, chance); + } } - else { - LogCombat("Ranged proc [{}] failed to proc [{}] ([{}] percent chance)", i, RangedProcs[i].spellID, chance); + } + } + } + + //AA Procs + if (IsClient()) { + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + + int32 aa_rank_id = 0; + int32 aa_spell_id = SPELL_UNKNOWN; + int32 aa_proc_chance = 100; + uint32 aa_proc_reuse_timer = 0; + int proc_type = 0; //used to deterimne which timer array is used. + + if (!rangedattk) { + + aa_rank_id = aabonuses.SpellProc[i]; + aa_spell_id = aabonuses.SpellProc[i + 1]; + aa_proc_chance += aabonuses.SpellProc[i + 2]; + aa_proc_reuse_timer = aabonuses.SpellProc[i + 3]; + proc_type = SE_WeaponProc; + } + else { + aa_rank_id = aabonuses.RangedProc[i]; + aa_spell_id = aabonuses.RangedProc[i + 1]; + aa_proc_chance += aabonuses.RangedProc[i + 2]; + aa_proc_reuse_timer = aabonuses.RangedProc[i + 3]; + proc_type = SE_RangedProc; + } + + if (aa_rank_id) { + if (!IsProcLimitTimerActive(-aa_rank_id, aa_proc_reuse_timer, proc_type)) { + float chance = ProcChance * (static_cast(aa_proc_chance) / 100.0f); + if (zone->random.Roll(chance) && IsValidSpell(aa_spell_id)) { + LogCombat("AA proc [{}] procing spell [{}] ([{}] percent chance)", aa_rank_id, aa_spell_id, chance); + ExecWeaponProc(nullptr, aa_spell_id, on); + SetProcLimitTimer(-aa_rank_id, aa_proc_reuse_timer, proc_type); + } + else { + LogCombat("AA proc [{}] failed to proc [{}] ([{}] percent chance)", aa_rank_id, aa_spell_id, chance); + } } } } diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index bdc02009c..e15175cad 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1071,6 +1071,76 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } break; + case SE_WeaponProc: + case SE_AddMeleeProc: + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + if (!newbon->SpellProc[i]) { + newbon->SpellProc[i] = rank.id; //aa rank id + newbon->SpellProc[i + 1] = base_value; //proc spell id + newbon->SpellProc[i + 2] = limit_value; //proc rate modifer + newbon->SpellProc[i + 3] = 0; //Lock out Timer + break; + } + } + break; + + case SE_RangedProc: + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + if (!newbon->RangedProc[i]) { + newbon->RangedProc[i] = rank.id; //aa rank id + newbon->RangedProc[i + 1] = base_value; //proc spell id + newbon->RangedProc[i + 2] = limit_value; //proc rate modifer + newbon->RangedProc[i + 3] = 0; //Lock out Timer + break; + } + } + break; + + case SE_DefensiveProc: + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + if (!newbon->DefensiveProc[i]) { + newbon->DefensiveProc[i] = rank.id; //aa rank id + newbon->DefensiveProc[i + 1] = base_value; //proc spell id + newbon->DefensiveProc[i + 2] = limit_value; //proc rate modifer + newbon->DefensiveProc[i + 3] = 0; //Lock out Timer + break; + } + } + break; + + case SE_Proc_Timer_Modifier: { + /* + AA can multiples of this in a single effect, proc should use the timer + that comes after the respective proc spell effect, thus rank.id will be already set + when this is checked. + */ + + newbon->Proc_Timer_Modifier = true; + + for (int i = 0; i < MAX_AA_PROCS; i += 4) { + if (newbon->SpellProc[i] == rank.id) { + if (!newbon->SpellProc[i + 3]) { + newbon->SpellProc[i + 3] = limit_value;//Lock out Timer + break; + } + } + + if (newbon->RangedProc[i] == rank.id) { + if (!newbon->RangedProc[i + 3]) { + newbon->RangedProc[i + 3] = limit_value;//Lock out Timer + break; + } + } + + if (newbon->DefensiveProc[i] == rank.id) { + if (!newbon->DefensiveProc[i + 3]) { + newbon->DefensiveProc[i + 3] = limit_value;//Lock out Timer + break; + } + } + } + break; + } case SE_CriticalHitChance: { // Bad data or unsupported new skill diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 9273290b3..ab8fa7b0f 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -761,17 +761,17 @@ void Client::CompleteConnect() case SE_AddMeleeProc: case SE_WeaponProc: { - AddProcToWeapon(GetProcID(buffs[j1].spellid, x1), false, 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, buffs[j1].casterlevel); + AddProcToWeapon(GetProcID(buffs[j1].spellid, x1), false, 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, buffs[j1].casterlevel, GetProcLimitTimer(buffs[j1].spellid, SE_WeaponProc)); break; } case SE_DefensiveProc: { - AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid); + AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, GetProcLimitTimer(buffs[j1].spellid, SE_DefensiveProc)); break; } case SE_RangedProc: { - AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid); + AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, GetProcLimitTimer(buffs[j1].spellid, SE_RangedProc)); break; } } diff --git a/zone/common.h b/zone/common.h index 733a4e3fd..85f2d3cbc 100644 --- a/zone/common.h +++ b/zone/common.h @@ -535,6 +535,10 @@ struct StatBonuses { bool LimitToSkill[EQ::skills::HIGHEST_SKILL + 2]; // Determines if we need to search for a skill proc. uint32 SkillProc[MAX_SKILL_PROCS]; // Max number of spells containing skill_procs. uint32 SkillProcSuccess[MAX_SKILL_PROCS]; // Max number of spells containing skill_procs_success. + int32 SpellProc[MAX_AA_PROCS]; // Max number of spells containing melee spell procs. + int32 RangedProc[MAX_AA_PROCS]; // Max number of spells containing ranged spell procs. + int32 DefensiveProc[MAX_AA_PROCS]; // Max number of spells containing defensive spell procs. + bool Proc_Timer_Modifier; // Used to check if this exists, to avoid any further unnncessary checks. uint32 PC_Pet_Rampage[2]; // 0= % chance to rampage, 1=damage modifier uint32 PC_Pet_AE_Rampage[2]; // 0= % chance to AE rampage, 1=damage modifier uint32 PC_Pet_Flurry; // Percent chance flurry from double attack @@ -691,6 +695,7 @@ typedef struct uint16 chance; uint16 base_spellID; int level_override; + uint32 proc_reuse_time; } tProc; diff --git a/zone/mob.cpp b/zone/mob.cpp index 1c5752d9a..0ac852b56 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -284,22 +284,26 @@ Mob::Mob( // clear the proc arrays for (int j = 0; j < MAX_PROCS; j++) { - PermaProcs[j].spellID = SPELL_UNKNOWN; - PermaProcs[j].chance = 0; - PermaProcs[j].base_spellID = SPELL_UNKNOWN; - PermaProcs[j].level_override = -1; - SpellProcs[j].spellID = SPELL_UNKNOWN; - SpellProcs[j].chance = 0; - SpellProcs[j].base_spellID = SPELL_UNKNOWN; - SpellProcs[j].level_override = -1; - DefensiveProcs[j].spellID = SPELL_UNKNOWN; - DefensiveProcs[j].chance = 0; - DefensiveProcs[j].base_spellID = SPELL_UNKNOWN; - DefensiveProcs[j].level_override = -1; - RangedProcs[j].spellID = SPELL_UNKNOWN; - RangedProcs[j].chance = 0; - RangedProcs[j].base_spellID = SPELL_UNKNOWN; - RangedProcs[j].level_override = -1; + PermaProcs[j].spellID = SPELL_UNKNOWN; + PermaProcs[j].chance = 0; + PermaProcs[j].base_spellID = SPELL_UNKNOWN; + PermaProcs[j].level_override = -1; + PermaProcs[j].proc_reuse_time = 0; + SpellProcs[j].spellID = SPELL_UNKNOWN; + SpellProcs[j].chance = 0; + SpellProcs[j].base_spellID = SPELL_UNKNOWN; + SpellProcs[j].proc_reuse_time = 0; + SpellProcs[j].level_override = -1; + DefensiveProcs[j].spellID = SPELL_UNKNOWN; + DefensiveProcs[j].chance = 0; + DefensiveProcs[j].base_spellID = SPELL_UNKNOWN; + DefensiveProcs[j].level_override = -1; + DefensiveProcs[j].proc_reuse_time = 0; + RangedProcs[j].spellID = SPELL_UNKNOWN; + RangedProcs[j].chance = 0; + RangedProcs[j].base_spellID = SPELL_UNKNOWN; + RangedProcs[j].level_override = -1; + RangedProcs[j].proc_reuse_time = 0; } for (int i = EQ::textures::textureBegin; i < EQ::textures::materialCount; i++) { @@ -354,6 +358,15 @@ Mob::Mob( focusproclimit_timer[i].Disable(); } + for (int i = 0; i < MAX_PROC_LIMIT_TIMERS; i++) { + spell_proclimit_spellid[i] = 0; + spell_proclimit_timer[i].Disable(); + ranged_proclimit_spellid[i] = 0; + ranged_proclimit_timer[i].Disable(); + def_proclimit_spellid[i] = 0; + def_proclimit_timer[i].Disable(); + } + memset(&itembonuses, 0, sizeof(StatBonuses)); memset(&spellbonuses, 0, sizeof(StatBonuses)); memset(&aabonuses, 0, sizeof(StatBonuses)); @@ -3167,6 +3180,10 @@ int32 Mob::GetActSpellCasttime(uint16 spell_id, int32 casttime) void Mob::ExecWeaponProc(const EQ::ItemInstance *inst, uint16 spell_id, Mob *on, int level_override) { // Changed proc targets to look up based on the spells goodEffect flag. // This should work for the majority of weapons. + if (!on) { + return; + } + if(spell_id == SPELL_UNKNOWN || on->GetSpecialAbility(NO_HARM_FROM_CLIENT)) { //This is so 65535 doesn't get passed to the client message and to logs because it is not relavant information for debugging. return; @@ -3202,21 +3219,25 @@ void Mob::ExecWeaponProc(const EQ::ItemInstance *inst, uint16 spell_id, Mob *on, bool twinproc = false; int32 twinproc_chance = 0; - if(IsClient()) + if (IsClient()) { twinproc_chance = CastToClient()->GetFocusEffect(focusTwincast, spell_id); + } - if(twinproc_chance && zone->random.Roll(twinproc_chance)) + if (twinproc_chance && zone->random.Roll(twinproc_chance)) { twinproc = true; + } if (IsBeneficialSpell(spell_id) && (!IsNPC() || (IsNPC() && CastToNPC()->GetInnateProcSpellID() != spell_id))) { // NPC innate procs don't take this path ever SpellFinished(spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty, true, level_override); - if(twinproc) - SpellOnTarget(spell_id, this, 0, false, 0, true, level_override); + if (twinproc) { + SpellFinished(spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty, true, level_override); + } } else if(!(on->IsClient() && on->CastToClient()->dead)) { //dont proc on dead clients SpellFinished(spell_id, on, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty, true, level_override); - if(twinproc) - SpellOnTarget(spell_id, on, 0, false, 0, true, level_override); + if (twinproc && (!(on->IsClient() && on->CastToClient()->dead))) { + SpellFinished(spell_id, on, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty, true, level_override); + } } return; } diff --git a/zone/mob.h b/zone/mob.h index 9b8361b9c..ec7a84700 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -726,15 +726,15 @@ public: //Procs void TriggerDefensiveProcs(Mob *on, uint16 hand = EQ::invslot::slotPrimary, bool FromSkillProc = false, int damage = 0); - bool AddRangedProc(uint16 spell_id, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN); + bool AddRangedProc(uint16 spell_id, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN, uint32 proc_reuse_time = 0); bool RemoveRangedProc(uint16 spell_id, bool bAll = false); bool HasRangedProcs() const; - bool AddDefensiveProc(uint16 spell_id, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN); + bool AddDefensiveProc(uint16 spell_id, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN, uint32 proc_reuse_time = 0); bool RemoveDefensiveProc(uint16 spell_id, bool bAll = false); bool HasDefensiveProcs() const; bool HasSkillProcs() const; bool HasSkillProcSuccess() const; - bool AddProcToWeapon(uint16 spell_id, bool bPerma = false, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN, int level_override = -1); + bool AddProcToWeapon(uint16 spell_id, bool bPerma = false, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN, int level_override = -1, uint32 proc_reuse_time = 0); bool RemoveProcFromWeapon(uint16 spell_id, bool bAll = false); bool HasProcs() const; bool IsCombatProc(uint16 spell_id); @@ -861,6 +861,8 @@ public: bool IsFocusProcLimitTimerActive(int32 focus_spell_id); void SetFocusProcLimitTimer(int32 focus_spell_id, uint32 focus_reuse_time); + bool IsProcLimitTimerActive(int32 base_spell_id, uint32 proc_reuse_time, int proc_type); + void SetProcLimitTimer(int32 base_spell_id, uint32 proc_reuse_time, int proc_type); void VirusEffectProcess(); void SpreadVirusEffect(int32 spell_id, uint32 caster_id, int32 buff_tics_remaining); @@ -1469,6 +1471,13 @@ protected: Timer focusproclimit_timer[MAX_FOCUS_PROC_LIMIT_TIMERS]; //SPA 511 int32 focusproclimit_spellid[MAX_FOCUS_PROC_LIMIT_TIMERS]; //SPA 511 + Timer spell_proclimit_timer[MAX_PROC_LIMIT_TIMERS]; //SPA 512 + int32 spell_proclimit_spellid[MAX_PROC_LIMIT_TIMERS]; //SPA 512 + Timer ranged_proclimit_timer[MAX_PROC_LIMIT_TIMERS]; //SPA 512 + int32 ranged_proclimit_spellid[MAX_PROC_LIMIT_TIMERS]; //SPA 512 + Timer def_proclimit_timer[MAX_PROC_LIMIT_TIMERS]; //SPA 512 + int32 def_proclimit_spellid[MAX_PROC_LIMIT_TIMERS]; //SPA 512 + Timer shield_timer; uint32 m_shield_target_id; uint32 m_shielder_id; diff --git a/zone/pets.cpp b/zone/pets.cpp index 4ca6b7752..8aeb1f5f2 100644 --- a/zone/pets.cpp +++ b/zone/pets.cpp @@ -576,14 +576,17 @@ void NPC::SetPetState(SpellBuff_Struct *pet_buffs, uint32 *items) { if (buffs[j1].spellid <= (uint32)SPDAT_RECORDS) { for (int x1=0; x1 < EFFECT_COUNT; x1++) { switch (spells[buffs[j1].spellid].effect_id[x1]) { + case SE_AddMeleeProc: case SE_WeaponProc: // We need to reapply buff based procs // We need to do this here so suspended pets also regain their procs. - if (spells[buffs[j1].spellid].limit_value[x1] == 0) { - AddProcToWeapon(GetProcID(buffs[j1].spellid,x1), false, 100, buffs[j1].spellid); - } else { - AddProcToWeapon(GetProcID(buffs[j1].spellid,x1), false, 100+spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid); - } + AddProcToWeapon(GetProcID(buffs[j1].spellid,x1), false, 100+spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, buffs[j1].casterlevel, GetProcLimitTimer(buffs[j1].spellid, SE_WeaponProc)); + break; + case SE_DefensiveProc: + AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, GetProcLimitTimer(buffs[j1].spellid, SE_DefensiveProc)); + break; + case SE_RangedProc: + AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].limit_value[x1], buffs[j1].spellid, GetProcLimitTimer(buffs[j1].spellid, SE_RangedProc)); break; case SE_Charm: case SE_Rune: diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 44d07f957..163aa18c6 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -1897,11 +1897,27 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Weapon Proc: %s (id %d)", spells[effect_value].name, procid); #endif + AddProcToWeapon(procid, false, 100 + spells[spell_id].limit_value[i], spell_id, caster_level, GetProcLimitTimer(spell_id, SE_WeaponProc)); + break; + } - if(spells[spell_id].limit_value[i] == 0) - AddProcToWeapon(procid, false, 100, spell_id, caster_level); - else - AddProcToWeapon(procid, false, spells[spell_id].limit_value[i]+100, spell_id, caster_level); + case SE_RangedProc: + { + uint16 procid = GetProcID(spell_id, i); +#ifdef SPELL_EFFECT_SPAM + snprintf(effect_desc, _EDLEN, "Ranged Proc: %+i", effect_value); +#endif + AddRangedProc(procid, 100 + spells[spell_id].limit_value[i], spell_id, GetProcLimitTimer(spell_id, SE_RangedProc)); + break; + } + + case SE_DefensiveProc: + { + uint16 procid = GetProcID(spell_id, i); +#ifdef SPELL_EFFECT_SPAM + snprintf(effect_desc, _EDLEN, "Defensive Proc: %s (id %d)", spells[effect_value].name, procid); +#endif + AddDefensiveProc(procid, 100 + spells[spell_id].limit_value[i], spell_id, GetProcLimitTimer(spell_id, SE_DefensiveProc)); break; } @@ -2273,20 +2289,6 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_RangedProc: - { -#ifdef SPELL_EFFECT_SPAM - snprintf(effect_desc, _EDLEN, "Ranged Proc: %+i", effect_value); -#endif - uint16 procid = GetProcID(spell_id, i); - - if(spells[spell_id].limit_value[i] == 0) - AddRangedProc(procid, 100, spell_id); - else - AddRangedProc(procid, spells[spell_id].limit_value[i]+100, spell_id); - break; - } - case SE_Rampage: { #ifdef SPELL_EFFECT_SPAM @@ -2383,21 +2385,6 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_DefensiveProc: - { - uint16 procid = GetProcID(spell_id, i); -#ifdef SPELL_EFFECT_SPAM - snprintf(effect_desc, _EDLEN, "Defensive Proc: %s (id %d)", spells[effect_value].name, procid); -#endif - if(spells[spell_id].limit_value[i] == 0) - AddDefensiveProc(procid, 100,spell_id); - else - AddDefensiveProc(procid, spells[spell_id].limit_value[i]+100,spell_id); - break; - - break; - } - case SE_BardAEDot: { #ifdef SPELL_EFFECT_SPAM @@ -3278,6 +3265,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_Worn_Endurance_Regen_Cap: case SE_Buy_AA_Rank: case SE_Ff_FocusTimerMin: + case SE_Proc_Timer_Modifier: { break; } @@ -8634,7 +8622,7 @@ void Mob::SpreadVirusEffect(int32 spell_id, uint32 caster_id, int32 buff_tics_re bool Mob::IsFocusProcLimitTimerActive(int32 focus_spell_id) { /* - Used with SPA SE_Ff_FocusTimerMin to limit how often a focus effect can be applied. + Used with SPA 511 SE_Ff_FocusTimerMin to limit how often a focus effect can be applied. Ie. Can only have a spell trigger once every 15 seconds, or to be more creative can only have the fire spells received a very high special focused once every 30 seconds. Note, this stores timers for both spell, item and AA related focuses For AA the focus_spell_id @@ -8673,3 +8661,110 @@ void Mob::SetFocusProcLimitTimer(int32 focus_spell_id, uint32 focus_reuse_time) } } } + +bool Mob::IsProcLimitTimerActive(int32 base_spell_id, uint32 proc_reuse_time, int proc_type) { + /* + Used with SPA 512 SE_Proc_Timer_Modifier to limit how often a proc can be cast. + If this effect exists it will prevent the next proc from firing until the timer + defined in SPA 512 is finished. Ie. 1 proc every 55 seconds. + Spell, Ranged, and Defensive procs all have their own timer array, therefore + you can stack multiple different types of effects in the same spell. Make sure + SPA 512 goes directly after each proc you want to have the timer. + */ + if (!proc_reuse_time) { + return false; + } + + for (int i = 0; i < MAX_PROC_LIMIT_TIMERS; i++) { + + if (proc_type == SE_WeaponProc) { + if (spell_proclimit_spellid[i] == base_spell_id) { + if (spell_proclimit_timer[i].Enabled()) { + if (spell_proclimit_timer[i].GetRemainingTime() > 0) { + return true; + } + else { + spell_proclimit_timer[i].Disable(); + spell_proclimit_spellid[i] = 0; + } + } + } + } + else if (proc_type == SE_RangedProc) { + if (ranged_proclimit_spellid[i] == base_spell_id) { + if (ranged_proclimit_timer[i].Enabled()) { + if (ranged_proclimit_timer[i].GetRemainingTime() > 0) { + return true; + } + else { + ranged_proclimit_timer[i].Disable(); + ranged_proclimit_spellid[i] = 0; + } + } + } + } + else if (proc_type == SE_DefensiveProc) { + if (def_proclimit_spellid[i] == base_spell_id) { + if (def_proclimit_timer[i].Enabled()) { + if (def_proclimit_timer[i].GetRemainingTime() > 0) { + return true; + } + else { + def_proclimit_timer[i].Disable(); + def_proclimit_spellid[i] = 0; + } + } + } + } + } + return false; +} + +void Mob::SetProcLimitTimer(int32 base_spell_id, uint32 proc_reuse_time, int proc_type) { + + if (!proc_reuse_time) { + return; + } + + bool is_set = false; + + for (int i = 0; i < MAX_PROC_LIMIT_TIMERS; i++) { + + if (proc_type == SE_WeaponProc) { + if (!spell_proclimit_spellid[i] && !is_set) { + spell_proclimit_spellid[i] = base_spell_id; + spell_proclimit_timer[i].SetTimer(proc_reuse_time); + is_set = true; + } + else if (spell_proclimit_spellid[i] > 0 && !FindBuff(base_spell_id)) { + spell_proclimit_spellid[i] = 0; + spell_proclimit_timer[i].Disable(); + } + } + + if (proc_type == SE_RangedProc) { + if (!ranged_proclimit_spellid[i] && !is_set) { + ranged_proclimit_spellid[i] = base_spell_id; + ranged_proclimit_timer[i].SetTimer(proc_reuse_time); + is_set = true; + } + else if (ranged_proclimit_spellid[i] > 0 && !FindBuff(base_spell_id)) { + ranged_proclimit_spellid[i] = 0; + ranged_proclimit_timer[i].Disable(); + } + } + + if (proc_type == SE_DefensiveProc) { + if (!def_proclimit_spellid[i] && !is_set) { + def_proclimit_spellid[i] = base_spell_id; + def_proclimit_timer[i].SetTimer(proc_reuse_time); + is_set = true; + } + else if (def_proclimit_spellid[i] > 0 && !FindBuff(base_spell_id)) { + def_proclimit_spellid[i] = 0; + def_proclimit_timer[i].Disable(); + } + } + } +} + diff --git a/zone/spells.cpp b/zone/spells.cpp index 6cf9837c9..76769da93 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -5665,7 +5665,7 @@ bool Mob::IsCombatProc(uint16 spell_id) { return false; } -bool Mob::AddProcToWeapon(uint16 spell_id, bool bPerma, uint16 iChance, uint16 base_spell_id, int level_override) { +bool Mob::AddProcToWeapon(uint16 spell_id, bool bPerma, uint16 iChance, uint16 base_spell_id, int level_override, uint32 proc_reuse_time) { if(spell_id == SPELL_UNKNOWN) return(false); @@ -5677,8 +5677,8 @@ bool Mob::AddProcToWeapon(uint16 spell_id, bool bPerma, uint16 iChance, uint16 b PermaProcs[i].chance = iChance; PermaProcs[i].base_spellID = base_spell_id; PermaProcs[i].level_override = level_override; + PermaProcs[i].proc_reuse_time = proc_reuse_time; LogSpells("Added permanent proc spell [{}] with chance [{}] to slot [{}]", spell_id, iChance, i); - return true; } } @@ -5692,6 +5692,7 @@ bool Mob::AddProcToWeapon(uint16 spell_id, bool bPerma, uint16 iChance, uint16 b SpellProcs[i].spellID = spell_id; SpellProcs[i].chance = iChance; SpellProcs[i].level_override = level_override; + SpellProcs[i].proc_reuse_time = proc_reuse_time; Log(Logs::Detail, Logs::Spells, "Replaced poison-granted proc spell %d with chance %d to slot %d", spell_id, iChance, i); return true; } @@ -5708,6 +5709,7 @@ bool Mob::AddProcToWeapon(uint16 spell_id, bool bPerma, uint16 iChance, uint16 b SpellProcs[i].chance = iChance; SpellProcs[i].base_spellID = base_spell_id;; SpellProcs[i].level_override = level_override; + SpellProcs[i].proc_reuse_time = proc_reuse_time; LogSpells("Added [{}]-granted proc spell [{}] with chance [{}] to slot [{}]", (base_spell_id == POISON_PROC) ? "poison" : "spell", spell_id, iChance, i); return true; } @@ -5724,13 +5726,14 @@ bool Mob::RemoveProcFromWeapon(uint16 spell_id, bool bAll) { SpellProcs[i].chance = 0; SpellProcs[i].base_spellID = SPELL_UNKNOWN; SpellProcs[i].level_override = -1; + SpellProcs[i].proc_reuse_time = 0; LogSpells("Removed proc [{}] from slot [{}]", spell_id, i); } } return true; } -bool Mob::AddDefensiveProc(uint16 spell_id, uint16 iChance, uint16 base_spell_id) +bool Mob::AddDefensiveProc(uint16 spell_id, uint16 iChance, uint16 base_spell_id, uint32 proc_reuse_time) { if(spell_id == SPELL_UNKNOWN) return(false); @@ -5741,6 +5744,7 @@ bool Mob::AddDefensiveProc(uint16 spell_id, uint16 iChance, uint16 base_spell_id DefensiveProcs[i].spellID = spell_id; DefensiveProcs[i].chance = iChance; DefensiveProcs[i].base_spellID = base_spell_id; + DefensiveProcs[i].proc_reuse_time = proc_reuse_time; LogSpells("Added spell-granted defensive proc spell [{}] with chance [{}] to slot [{}]", spell_id, iChance, i); return true; } @@ -5756,13 +5760,14 @@ bool Mob::RemoveDefensiveProc(uint16 spell_id, bool bAll) DefensiveProcs[i].spellID = SPELL_UNKNOWN; DefensiveProcs[i].chance = 0; DefensiveProcs[i].base_spellID = SPELL_UNKNOWN; + DefensiveProcs[i].proc_reuse_time = 0; LogSpells("Removed defensive proc [{}] from slot [{}]", spell_id, i); } } return true; } -bool Mob::AddRangedProc(uint16 spell_id, uint16 iChance, uint16 base_spell_id) +bool Mob::AddRangedProc(uint16 spell_id, uint16 iChance, uint16 base_spell_id, uint32 proc_reuse_time) { if(spell_id == SPELL_UNKNOWN) return(false); @@ -5773,6 +5778,7 @@ bool Mob::AddRangedProc(uint16 spell_id, uint16 iChance, uint16 base_spell_id) RangedProcs[i].spellID = spell_id; RangedProcs[i].chance = iChance; RangedProcs[i].base_spellID = base_spell_id; + RangedProcs[i].proc_reuse_time = proc_reuse_time; LogSpells("Added spell-granted ranged proc spell [{}] with chance [{}] to slot [{}]", spell_id, iChance, i); return true; } @@ -5787,7 +5793,8 @@ bool Mob::RemoveRangedProc(uint16 spell_id, bool bAll) if (bAll || RangedProcs[i].spellID == spell_id) { RangedProcs[i].spellID = SPELL_UNKNOWN; RangedProcs[i].chance = 0; - RangedProcs[i].base_spellID = SPELL_UNKNOWN;; + RangedProcs[i].base_spellID = SPELL_UNKNOWN; + RangedProcs[i].proc_reuse_time = 0; LogSpells("Removed ranged proc [{}] from slot [{}]", spell_id, i); } }