[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
This commit is contained in:
KayenEQ 2021-11-05 14:14:11 -04:00 committed by GitHub
parent 8c95323728
commit f1bfd6bc2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 469 additions and 109 deletions

View File

@ -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;
}

View File

@ -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);

View File

@ -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<float>(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<float>(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<float>(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<float>(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<float>(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<float>(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<float>(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<float>(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<float>(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);
}
}
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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:

View File

@ -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();
}
}
}
}

View File

@ -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);
}
}