[Feature] New SPAs pass 2 (#1459)

* Implemented SPA Duration Pct

Implemented new spell effects
SE_Duration_HP_Pct 			524
SE_Duration_Mana_Pct			525
SE_Duration_Endurance_Pct		526

Consumes 'base1' % of your maximum health/mana/endurance every 6 seconds. 'max' is maximum amount that can be consumed per tic.

Additional Functionality
Can be used as a heal/gain % by setting the base1 value to a positive.

* Implemented SPA Instant Mana/End pct

Fixes for SPA 524-526
Implemented
SE_Instant_Mana_Pct			522
SE_Instant_Endurance_Pct		523

Extracts 'base1' percent of your maximum mana/endurance, or 'max', whichever is lower.

* Implemented: SPA 521 EndAbsorbPctDmg

Implemented
SE_Endurance_Absorb_Pct_Damage 521

Absorb Damage using Endurance: base1 % (base2 End per 1 HP)
Note: Both base1 and base2 need to be divided by 100 for actually value

* Implemented SE_HealthTransfer 509

Implemented
SE_Health_Transfer			509
'life burn'
Consume base2 % of Hit Points to Damage for base % of Hit Points

Can be used for heal
Act of Valor

* Implemented SPA 515,516,518,496

Implemented
SE_AC_Avoidance_Max_Percent 515
SE_AC_Mitigation_Max_Percent	516
SE_Attack_Accuracy_Max_Percent	518
Above are stackable defense and offensive mods

SE_Critical_Melee_Damage_Mod_Max	496 - This is a non stackable melee critical modifier

* Implemented SPA 503 , 505

SE_Melee_Damage_Position_Mod	503
define SE_Damage_Taken_Position_Mod	505

SPA 503 increase/decreases melee damage by percent base1 based on your position base2 0=back 1=front

SPA 504 increase/decreases melee damage taken by percent base1 based on your position base2 0=back 1=front

* Implemented 467,468

Implemented
SE_DS_Mitigation_Amount		467
SE_DS_Mitigation_Percentage	468

Reduce incoming DS by amt or percentage. base1 is value, if a reduction is desired it should be set to negative for both.

* Fixes

Formula fixes

* Update spdat.h

Added spa descriptions.

* Implemented SPA 469, 470

Implemented
SE_Chance_Best_in_Spell_Grp     469  Chance to cast highest scribed spell within a spell group. All base2 spells share roll chance, only 1 cast.

SE_Trigger_Best_in_Spell_Grp	470

Chance to cast highest scribed spell within a spell group. Each spell has own chance.

Additional Changes:
Rewrote TrySpellTrigger function used for SPA 340 since it incorporates SPA 469. Improved code so that chance of spell being triggered should be more accurate statistically.

* Implemented SPA 474, 494

Implemented
 SE_Pet_Crit_Melee_Damage_Pct_Owner	474 - Gives pets a critical melee damage modifier from the owner

SE_Pet_Add_Atk	494 - Gives pet a ATK bonus from the owner

Fixed SE_PetMeleeMitigation 397 - The bonus was not being calculated

* Implemented SPA 465,477,478

Implemented

SE_PC_Pet_AE_Rampage			465
Chance for pet to AE rampage with a damage modifier

SE_Hatelist_To_Top_Index		477
Chance to be put on top of RAMPAGE list

SE_Hatelist_To_Tail_Index		478
Chance to be put on bottom of RAMPAGE list

* Implemented

Implemented

SE_Fearstun 	502
Stun with a max level limit. Normal stun restrictions don't apply. Base1 duration, base2 PC duration, max is level limit

SE_TwinCastBlocker 39
Previously unused spell effect that is now used on live. Simply, if this effect is present in a spell then the spell can not be twin cast.

* Implemented SPA 483

Implemented
Fc_Spell_Damage_Pct_IncomingPC	483
- Focus effect that modifies iby percent incoming spell damage on the target.
Base1= min Base2= max. Final percent is random between max and min each time focus is applied from a spell cast.

Note: Written to stack with similar functioning focus SPA 269 SE_FcSpellVulnerability.

* Implemented SPA 484

Implemented

SE_Fc_Spell_Damage_Amt_IncomingPC	484 // focus effect that modifies incoming spell damage by flat amount. Consider it a debuff that adds damage to incoming spells. Positive value to add additional damage.

* 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.

* Stacking fixes for new effects

Stacking fixes for new effects.

* merge with upstream master

merge and update up spdat.h

* Update spdat.h

* Fix for bolt spell targeting self if target zone/died while casting.

Fix for bolt spell targeting self if target zone/died while casting. Despite the name being "ST_TargetOptional", this target type is reserved for projectile spells which all require a target, thus should be treated like any other targeted spell.
This commit is contained in:
KayenEQ 2021-07-20 11:06:20 -04:00 committed by GitHub
parent 8a2a1b152e
commit 2b74d71ff5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 766 additions and 139 deletions

View File

@ -1218,6 +1218,12 @@ bool IsEffectIgnoredInStacking(int spa)
case SE_LimitUseType:
case SE_GravityEffect:
case 425:
//Spell effects implemented after ROF2, following same pattern, lets assume these should go here.
case SE_Fc_Spell_Damage_Pct_IncomingPC:
case SE_Fc_Spell_Damage_Amt_IncomingPC:
case SE_Ff_CasterClass:
case SE_Ff_Same_Caster:
case SE_Proc_Timer_Modifier:
return true;
default:
return false;

View File

@ -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
@ -278,7 +279,7 @@ enum RESISTTYPE
//Target Type IDs
typedef enum {
/* 01 */ ST_TargetOptional = 0x01,
/* 01 */ ST_TargetOptional = 0x01, //only used for targeted projectile spells
/* 02 */ ST_AEClientV1 = 0x02,
/* 03 */ ST_GroupTeleport = 0x03,
/* 04 */ ST_AECaster = 0x04,
@ -381,7 +382,7 @@ typedef enum {
#define SE_PoisonCounter 36 // implemented
//#define SE_DetectHostile 37 // not used
//#define SE_DetectMagic 38 // not used
//#define SE_DetectPoison 39 // not used
#define SE_TwinCastBlocker 39 // implemented - If present in spell, then the spell can not be twincast.
#define SE_DivineAura 40 // implemented
#define SE_Destroy 41 // implemented - Disintegrate, Banishment of Shadows
#define SE_ShadowStep 42 // implemented
@ -464,7 +465,7 @@ typedef enum {
#define SE_AttackSpeed3 119 // implemented
#define SE_HealRate 120 // implemented - reduces healing by a %
#define SE_ReverseDS 121 // implemented
//#define SE_ReduceSkill 122 // not used
//#define SE_ReduceSkill 122 // not implemented TODO: Now used on live, decreases skills by percent
#define SE_Screech 123 // implemented Spell Blocker(If have buff with value +1 will block any effect with -1)
#define SE_ImprovedDamage 124 // implemented
#define SE_ImprovedHeal 125 // implemented
@ -488,7 +489,7 @@ typedef enum {
#define SE_LimitCastTimeMin 143 // implemented
#define SE_LimitCastTimeMax 144 // implemented (*not used in any known live spell)
#define SE_Teleport2 145 // implemented - Banishment of the Pantheon
//#define SE_ElectricityResist 146 // *not implemented (Lightning Rod: 23233)
//#define SE_ElectricityResist 146 // *not implemented TODO: Now used on live, xyz for teleport spells? also in temp pets?
#define SE_PercentalHeal 147 // implemented
#define SE_StackingCommand_Block 148 // implemented?
#define SE_StackingCommand_Overwrite 149 // implemented?
@ -638,7 +639,7 @@ typedef enum {
#define SE_FrontalStunResist 293 // implemented[AA] - Reduce chance to be stunned from front. -- live descriptions sounds like this isn't limited to frontal anymore
#define SE_CriticalSpellChance 294 // implemented - increase chance to critical hit and critical damage modifier.
//#define SE_ReduceTimerSpecial 295 // not used
#define SE_FcSpellVulnerability 296 // implemented - increase in incoming spell damage
#define SE_FcSpellVulnerability 296 // implemented - increase in incoming spell damage [base1= min dmg base2= max dmg]
#define SE_FcDamageAmtIncoming 297 // implemented - debuff that adds points damage to spells cast on target (focus effect).
#define SE_ChangeHeight 298 // implemented
#define SE_WakeTheDead 299 // implemented
@ -674,7 +675,7 @@ typedef enum {
#define SE_ManaAbsorbPercentDamage 329 // implemented
#define SE_CriticalDamageMob 330 // implemented
#define SE_Salvage 331 // implemented - chance to recover items that would be destroyed in failed tradeskill combine
#define SE_SummonToCorpse 332 // *not implemented AA - Call of the Wild (Druid/Shaman Res spell with no exp)
#define SE_SummonToCorpse 332 // *not implemented AA - Call of the Wild (Druid/Shaman Res spell with no exp) TOOD: implement this.
#define SE_CastOnRuneFadeEffect 333 // implemented
#define SE_BardAEDot 334 // implemented
#define SE_BlockNextSpellFocus 335 // implemented - base1 chance to block next spell ie Puratus (8494)
@ -682,7 +683,7 @@ typedef enum {
#define SE_PercentXPIncrease 337 // implemented
#define SE_SummonAndResAllCorpses 338 // implemented
#define SE_TriggerOnCast 339 // implemented
#define SE_SpellTrigger 340 // implemented - chance to trigger spell
#define SE_SpellTrigger 340 // implemented - chance to trigger spell [Share rolls with 469] All base2 spells share roll chance, only 1 cast.
#define SE_ItemAttackCapIncrease 341 // implemented[AA] - increases the maximum amount of attack you can gain from items.
#define SE_ImmuneFleeing 342 // implemented - stop mob from fleeing
#define SE_InterruptCasting 343 // implemented - % chance to interrupt spells being cast every tic. Cacophony (8272)
@ -739,7 +740,7 @@ typedef enum {
#define SE_FcHealAmtIncoming 394 // implemented - Adds/Removes amount of healing on target by X value with foucs restrictions.
#define SE_FcHealPctCritIncoming 395 // implemented[AA] - Increases chance of having a heal crit when cast on you. [focus limited]
#define SE_FcHealAmtCrit 396 // implemented - Adds a direct healing amount to spells
#define SE_PetMeleeMitigation 397 // implemented[AA] - additional mitigation to your pets. Adds AC.
#define SE_PetMeleeMitigation 397 // implemented[AA] - additional mitigation to your pets. Adds AC
#define SE_SwarmPetDuration 398 // implemented - Affects the duration of swarm pets
#define SE_FcTwincast 399 // implemented - cast 2 spells for every 1
#define SE_HealGroupFromMana 400 // implemented - Drains mana and heals for each point of mana drained
@ -807,28 +808,28 @@ typedef enum {
#define SE_FcDamageAmt2 462 // implemented - Increase spell damage by flat amount (SE_Fc_Damage_Amt2)
//#define SE_Shield_Target 463 //
#define SE_PC_Pet_Rampage 464 // implemented - Base1 % chance to do rampage for base2 % of damage each melee round
//#define SE_PC_Pet_AE_Rampage 465 // Would assume as above but need to confirm.
#define SE_PC_Pet_AE_Rampage 465 // implemented - Base1 % chance to do AE rampage for base2 % of damage each melee round
#define SE_PC_Pet_Flurry_Chance 466 // implemented - Base1 % chance to do flurry from double attack hit.
#define SE_DS_Mitigation_Amount 467 // implemented - Modify incoming damage shield damage by a flat amount
#define SE_DS_Mitigation_Percentage 468 // implemented - Modify incoming damage shield damage by percentage
//#define SE_Chance_Best_in_Spell_Grp 469 //
//#define SE_Trigger_Best_in_Spell Grp 470 //
#define SE_Chance_Best_in_Spell_Grp 469 // implemented - Chance to cast highest scribed spell within a spell group. All base2 spells share roll chance, only 1 cast.
#define SE_Trigger_Best_in_Spell_Grp 470 // implemented - Chance to cast highest scribed spell within a spell group. Each spell has own chance.
//#define SE_Double_Melee_Round 471 //
//#define SE_Buy_AA_Rank 472 //
#define SE_Double_Backstab_Front 473 // implemented - Chance to double backstab from front
//#define SE_Pet_Crit_Melee_Damage_Pct_Owner 474 //
//#define SE_Trigger_Spell_Non_Item 475 //
#define SE_Pet_Crit_Melee_Damage_Pct_Owner 474 // implemenetd - Critical damage mod applied to pets from owner
#define SE_Trigger_Spell_Non_Item 475 // implemented - Trigger spell on cast only if not from item click.
//#define SE_Weapon_Stance 476 //
//#define SE_Hatelist_To_Top_Index 477 //
//#define SE_Hatelist_To_Tail_Index 478 //
#define SE_Hatelist_To_Top_Index 477 // Implemented - Chance to be set to top of rampage list
#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 // Implemented - [FOCUS] 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 //
//#define SE_Fc_Spell_Damage_Amt_IncomingPC 484 //
//#define SE_Ff_CasterClass 485 //
//#define SE_Ff_Same_Caster 486 //
#define SE_Fc_Spell_Damage_Pct_IncomingPC 483 // Implemented - [FOCUS] modifies incoming spell damage by percent
#define SE_Fc_Spell_Damage_Amt_IncomingPC 484 // Implemented - [FOCUS] modifies incoming spell damage by flat amount. Typically adds damage to incoming spells.
#define SE_Ff_CasterClass 485 // Implemented - [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 // Implemented - [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 //
@ -836,7 +837,7 @@ typedef enum {
//#define SE_Ff_ReuseTimeMax 491 //
//#define SE_Ff_Endurance_Min 492 //
//#define SE_Ff_Endurance_Max 493 //
//#define SE_Pet_Add_Atk 494 //
#define SE_Pet_Add_Atk 494 // implemented - Bonus on pet owner which gives their pet increased attack stat
//#define SE_Ff_DurationMax 495 //
#define SE_Critical_Melee_Damage_Mod_Max 496 // implemented - increase or decrease by percent critical damage (not stackable)
//#define SE_Ff_FocusCastProcNoBypass 497 //
@ -844,7 +845,7 @@ typedef enum {
//#define SE_AddExtraAttackPct_1h_Secondary 499 //
//#define SE_Fc_CastTimeMod2 500 //
//#define SE_Fc_CastTimeAmt 501 //
//#define SE_Fearstun 502 //
#define SE_Fearstun 502 // implemented - Stun with a max level limit. Normal stun restrictions don't apply.
#define SE_Melee_Damage_Position_Mod 503 // implemented - modify melee damage by pct if done from Front or Behind
//#define SE_Melee_Damage_Position_Amt 504 //
#define SE_Damage_Taken_Position_Mod 505 // implemented - mitigate melee damage by pct if dmg taken from Front or Behind
@ -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 // implemented - spell trigger limiter used currently with SPA 481, ie. limit to 1 proc every 1.5 seconds (base=1 base2=1500).
//#define SE_Mana_Max_Percent 513 //
//#define SE_Endurance_Max_Percent 514 //
#define SE_AC_Avoidance_Max_Percent 515 // implemented - stackable avoidance modifier

View File

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

View File

@ -274,14 +274,8 @@ int Mob::GetTotalDefense()
// 172 Evasion aka SE_AvoidMeleeChance
evasion_bonus += itembonuses.AvoidMeleeChanceEffect + aabonuses.AvoidMeleeChanceEffect; // item bonus here isn't mod2 avoidance
Mob *owner = nullptr;
if (IsPet())
owner = GetOwner();
else if (IsNPC() && CastToNPC()->GetSwarmOwner())
owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner());
if (owner) // 215 Pet Avoidance % aka SE_PetAvoidance
evasion_bonus += owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance;
// 215 Pet Avoidance % aka SE_PetAvoidance
evasion_bonus += GetPetAvoidanceBonusFromOwner();
// Evasion is a percentage bonus according to AA descriptions
if (evasion_bonus)
@ -823,13 +817,7 @@ int Mob::ACSum(bool skip_caps)
// According to the guild hall Combat Dummies, a level 50 classic EQ mob it should be ~115
// For a 60 PoP mob ~120, 70 OoW ~120
ac += GetAC();
Mob *owner = nullptr;
if (IsPet())
owner = GetOwner();
else if (CastToNPC()->GetSwarmOwner())
owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner());
if (owner)
ac += owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance;
ac += GetPetACBonusFromOwner();
auto spell_aa_ac = aabonuses.AC + spellbonuses.AC;
ac += GetSkill(EQ::skills::SkillDefense) / 5;
if (EQ::ValueWithin(static_cast<int>(GetClass()), NECROMANCER, ENCHANTER))
@ -925,7 +913,7 @@ int Mob::offense(EQ::skills::SkillType skill)
if (stat_bonus >= 75)
offense += (2 * stat_bonus - 150) / 3;
offense += GetATK();
offense += GetATK() + GetPetATKBonusFromOwner();
return offense;
}
@ -4343,7 +4331,7 @@ void Mob::TryPetCriticalHit(Mob *defender, DamageHitInfo &hit)
if (critChance > 0) {
if (zone->random.Roll(critChance)) {
critMod += GetCritDmgMod(hit.skill);
critMod += GetCritDmgMod(hit.skill, owner);
hit.damage_done += 5;
hit.damage_done = (hit.damage_done * critMod) / 100;
@ -5266,15 +5254,22 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac
if (mod > 0)
spec_mod = mod;
if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) {
int spell = spellbonuses.PC_Pet_Rampage[1] + itembonuses.PC_Pet_Rampage[1] + aabonuses.PC_Pet_Rampage[1];
if (spell > spec_mod)
spec_mod = spell;
//SE_PC_Pet_Rampage SPA 464 on pet, damage modifier
int spell_mod = spellbonuses.PC_Pet_Rampage[1] + itembonuses.PC_Pet_Rampage[1] + aabonuses.PC_Pet_Rampage[1];
if (spell_mod > spec_mod)
spec_mod = spell_mod;
}
}
else if (IsSpecialAttack(eSpecialAttacks::AERampage)) {
int mod = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 2);
if (mod > 0)
spec_mod = mod;
if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) {
//SE_PC_Pet_AE_Rampage SPA 465 on pet, damage modifier
int spell_mod = spellbonuses.PC_Pet_AE_Rampage[1] + itembonuses.PC_Pet_AE_Rampage[1] + aabonuses.PC_Pet_AE_Rampage[1];
if (spell_mod > spec_mod)
spec_mod = spell_mod;
}
}
if (spec_mod > 0)
hit.damage_done = (hit.damage_done * spec_mod) / 100;
@ -5625,6 +5620,48 @@ void Mob::DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts)
}
}
int Mob::GetPetAvoidanceBonusFromOwner()
{
Mob *owner = nullptr;
if (IsPet())
owner = GetOwner();
else if (IsNPC() && CastToNPC()->GetSwarmOwner())
owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner());
if (owner)
return owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance;
return 0;
}
int Mob::GetPetACBonusFromOwner()
{
Mob *owner = nullptr;
if (IsPet())
owner = GetOwner();
else if (IsNPC() && CastToNPC()->GetSwarmOwner())
owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner());
if (owner)
return owner->aabonuses.PetMeleeMitigation + owner->spellbonuses.PetMeleeMitigation + owner->itembonuses.PetMeleeMitigation;
return 0;
}
int Mob::GetPetATKBonusFromOwner()
{
Mob *owner = nullptr;
if (IsPet())
owner = GetOwner();
else if (IsNPC() && CastToNPC()->GetSwarmOwner())
owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner());
if (owner)
return owner->aabonuses.Pet_Add_Atk + owner->spellbonuses.Pet_Add_Atk + owner->itembonuses.Pet_Add_Atk;
return 0;
}
bool Mob::GetWasSpawnedInWater() const {
return spawned_in_water;
}

View File

@ -1438,6 +1438,13 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
break;
}
case SE_PC_Pet_AE_Rampage: {
newbon->PC_Pet_AE_Rampage[0] += base1; //Chance to rampage
if (newbon->PC_Pet_AE_Rampage[1] < base2)
newbon->PC_Pet_AE_Rampage[1] = base2; //Damage modifer - take highest
break;
}
case SE_PC_Pet_Flurry_Chance:
newbon->PC_Pet_Flurry += base1; //Chance to Flurry
break;
@ -1546,6 +1553,13 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
newbon->DS_Mitigation_Percentage += base1;
break;
case SE_Pet_Crit_Melee_Damage_Pct_Owner:
newbon->Pet_Crit_Melee_Damage_Pct_Owner += base1;
break;
case SE_Pet_Add_Atk:
newbon->Pet_Add_Atk += base1;
break;
// to do
case SE_PetDiscipline:
@ -3282,6 +3296,13 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
break;
}
case SE_PC_Pet_AE_Rampage: {
new_bonus->PC_Pet_AE_Rampage[0] += effect_value; //Chance to rampage
if (new_bonus->PC_Pet_AE_Rampage[1] < base2)
new_bonus->PC_Pet_AE_Rampage[1] = base2; //Damage modifer - take highest
break;
}
case SE_PC_Pet_Flurry_Chance:
new_bonus->PC_Pet_Flurry += effect_value; //Chance to Flurry
break;
@ -3386,7 +3407,13 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
new_bonus->DS_Mitigation_Percentage += effect_value;
break;
case SE_Pet_Crit_Melee_Damage_Pct_Owner:
new_bonus->Pet_Crit_Melee_Damage_Pct_Owner += effect_value;
break;
case SE_Pet_Add_Atk:
new_bonus->Pet_Add_Atk += effect_value;
break;
//Special custom cases for loading effects on to NPC from 'npc_spels_effects' table
if (IsAISpellEffect) {
@ -3708,6 +3735,8 @@ uint8 Mob::IsFocusEffect(uint16 spell_id,int effect_index, bool AA,uint32 aa_eff
return 0; //This is calculated as an actual bonus
case SE_FcSpellVulnerability:
return focusSpellVulnerability;
case SE_Fc_Spell_Damage_Pct_IncomingPC:
return focusFcSpellDamagePctIncomingPC;
case SE_BlockNextSpellFocus:
//return focusBlockNextSpell;
return 0; //This is calculated as an actual bonus
@ -3725,6 +3754,8 @@ uint8 Mob::IsFocusEffect(uint16 spell_id,int effect_index, bool AA,uint32 aa_eff
return focusFcDamagePctCrit;
case SE_FcDamageAmtIncoming:
return focusFcDamageAmtIncoming;
case SE_Fc_Spell_Damage_Amt_IncomingPC:
return focusFcSpellDamageAmtIncomingPC;
case SE_FcHealAmtIncoming:
return focusFcHealAmtIncoming;
case SE_FcHealPctIncoming:
@ -3739,6 +3770,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:
@ -3747,6 +3780,7 @@ uint8 Mob::IsFocusEffect(uint16 spell_id,int effect_index, bool AA,uint32 aa_eff
return focusFcHealAmt;
case SE_FcHealAmtCrit:
return focusFcHealAmtCrit;
}
return 0;
}
@ -4978,6 +5012,37 @@ void Mob::NegateSpellsBonuses(uint16 spell_id)
aabonuses.DS_Mitigation_Percentage = effect_value;
break;
case SE_Pet_Crit_Melee_Damage_Pct_Owner:
spellbonuses.Pet_Crit_Melee_Damage_Pct_Owner = effect_value;
itembonuses.Pet_Crit_Melee_Damage_Pct_Owner = effect_value;
aabonuses.Pet_Crit_Melee_Damage_Pct_Owner = effect_value;
break;
case SE_Pet_Add_Atk:
spellbonuses.Pet_Add_Atk = effect_value;
itembonuses.Pet_Add_Atk = effect_value;
aabonuses.Pet_Add_Atk = effect_value;
break;
case SE_PC_Pet_Rampage:
spellbonuses.PC_Pet_Rampage[0] = effect_value;
itembonuses.PC_Pet_Rampage[0] = effect_value;
aabonuses.PC_Pet_Rampage[0] = effect_value;
spellbonuses.PC_Pet_Rampage[1] = effect_value;
itembonuses.PC_Pet_Rampage[1] = effect_value;
aabonuses.PC_Pet_Rampage[1] = effect_value;
break;
case SE_PC_Pet_AE_Rampage:
spellbonuses.PC_Pet_AE_Rampage[0] = effect_value;
itembonuses.PC_Pet_AE_Rampage[0] = effect_value;
aabonuses.PC_Pet_AE_Rampage[0] = effect_value;
spellbonuses.PC_Pet_AE_Rampage[1] = effect_value;
itembonuses.PC_Pet_AE_Rampage[1] = effect_value;
aabonuses.PC_Pet_AE_Rampage[1] = effect_value;
break;
case SE_SkillProcSuccess:{
for(int e = 0; e < MAX_SKILL_PROCS; e++)
{

View File

@ -1021,6 +1021,7 @@ public:
int GetNextAvailableSpellBookSlot(int starting_slot = 0);
inline uint32 GetSpellByBookSlot(int book_slot) { return m_pp.spell_book[book_slot]; }
inline bool HasSpellScribed(int spellid) { return FindSpellBookSlotBySpellID(spellid) != -1; }
uint32 GetHighestScribedSpellinSpellGroup(uint32 spell_group);
uint16 GetMaxSkillAfterSpecializationRules(EQ::skills::SkillType skillid, uint16 maxSkill);
void SendPopupToClient(const char *Title, const char *Text, uint32 PopupID = 0, uint32 Buttons = 0, uint32 Duration = 0);
void SendFullPopup(const char *Title, const char *Text, uint32 PopupID = 0, uint32 NegativeID = 0, uint32 Buttons = 0, uint32 Duration = 0, const char *ButtonName0 = 0, const char *ButtonName1 = 0, uint32 SoundControls = 0);

View File

@ -538,6 +538,9 @@ bool Client::Process() {
}
}
if (focus_proc_limit_timer.Check() && !dead)
FocusProcLimitProcess();
if (client_state == CLIENT_KICKED) {
Save();
OnDisconnect(true);

View File

@ -124,6 +124,7 @@ typedef enum { //focus types
focusSpellHateMod,
focusTriggerOnCast,
focusSpellVulnerability,
focusFcSpellDamagePctIncomingPC,
focusTwincast,
focusSympatheticProc,
focusFcDamageAmt,
@ -135,6 +136,8 @@ typedef enum { //focus types
focusBlockNextSpell,
focusFcHealPctIncoming,
focusFcDamageAmtIncoming,
focusFcSpellDamageAmtIncomingPC,
focusFcCastSpellOnLand,
focusFcHealAmtIncoming,
focusFcBaseEffects,
focusIncreaseNumHits,
@ -319,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;
@ -524,6 +529,7 @@ struct StatBonuses {
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.
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
int32 Attack_Accuracy_Max_Percent; // Increase ATK accuracy by percent.
int32 AC_Mitigation_Max_Percent; // Increase AC mitigation by percent
@ -532,7 +538,10 @@ struct StatBonuses {
int32 Melee_Damage_Position_Mod[2]; // base = percent melee damage increase base2 0=back 1=front. [0]Back[1]Front
int32 Double_Backstab_Front; // base = percent chance to double back stab front
int32 DS_Mitigation_Amount; // base = flat amt DS mitigation. Negative value to reduce
int32 DS_Mitigation_Percentage; // base = percent amt of DS mitigation. Negative value to reduce
int32 DS_Mitigation_Percentage; // base = percent amt of DS mitigation. Negative value to reduce
int32 Pet_Crit_Melee_Damage_Pct_Owner; // base = percent mod for pet critcal damage from owner
int32 Pet_Add_Atk; // base = Pet ATK bonus from owner
// AAs
int8 Packrat; //weight reduction for items, 1 point = 10%

View File

@ -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),
@ -3537,57 +3538,82 @@ void Mob::TriggerOnCast(uint32 focus_spell, uint32 spell_id, bool aa_trigger)
}
}
bool Mob::TrySpellTrigger(Mob *target, uint32 spell_id, int effect)
{
if(!target || !IsValidSpell(spell_id))
if (!target || !IsValidSpell(spell_id))
return false;
int spell_trig = 0;
// Count all the percentage chances to trigger for all effects
for(int i = 0; i < EFFECT_COUNT; i++)
{
if (spells[spell_id].effectid[i] == SE_SpellTrigger)
spell_trig += spells[spell_id].base[i];
}
// If all the % add to 100, then only one of the effects can fire but one has to fire.
if (spell_trig == 100)
{
int trig_chance = 100;
for(int i = 0; i < EFFECT_COUNT; i++)
{
if (spells[spell_id].effectid[i] == SE_SpellTrigger)
{
if(zone->random.Int(0, trig_chance) <= spells[spell_id].base[i])
{
// If we trigger an effect then its over.
if (IsValidSpell(spells[spell_id].base2[i])){
SpellFinished(spells[spell_id].base2[i], target, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff);
return true;
}
}
else
{
// Increase the chance to fire for the next effect, if all effects fail, the final effect will fire.
trig_chance -= spells[spell_id].base[i];
}
}
/*The effects SE_SpellTrigger (SPA 340) and SE_Chance_Best_in_Spell_Grp (SPA 469) work as follows, you typically will have 2-3 different spells each with their own
chance to be triggered with all chances equaling up to 100 pct, with only 1 spell out of the group being ultimately cast.
(ie Effect1 trigger spellA with 30% chance, Effect2 triggers spellB with 20% chance, Effect3 triggers spellC with 50% chance).
The following function ensures a stastically accurate chance for each spell to be cast based on their chance values. These effects are also used in spells where there
is only 1 effect using the trigger effect. In those situations we simply roll a chance for that spell to be cast once.
Note: Both SPA 340 and 469 can be in same spell and both cummulative add up to 100 pct chances. SPA469 only difference being the spell cast will
be "best in spell group", instead of a defined spell_id.*/
}
}
// if the chances don't add to 100, then each effect gets a chance to fire, chance for no trigger as well.
else
int chance_array[EFFECT_COUNT] = {};
int total_chance = 0;
int effect_slot = effect;
bool CastSpell = false;
for (int i = 0; i < EFFECT_COUNT; i++)
{
if(zone->random.Int(0, 100) <= spells[spell_id].base[effect])
{
if (IsValidSpell(spells[spell_id].base2[effect])){
SpellFinished(spells[spell_id].base2[effect], target, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].base2[effect]].ResistDiff);
return true; //Only trigger once of these per spell effect.
if (spells[spell_id].effectid[i] == SE_SpellTrigger || spells[spell_id].effectid[i] == SE_Chance_Best_in_Spell_Grp)
total_chance += spells[spell_id].base[i];
}
if (total_chance == 100)
{
int current_chance = 0;
int cummulative_chance = 0;
for (int i = 0; i < EFFECT_COUNT; i++){
//Find spells with SPA 340 and add the cummulative percent chances to the roll array
if ((spells[spell_id].effectid[i] == SE_SpellTrigger) || (spells[spell_id].effectid[i] == SE_Chance_Best_in_Spell_Grp)){
cummulative_chance = current_chance + spells[spell_id].base[i];
chance_array[i] = cummulative_chance;
current_chance = cummulative_chance;
}
}
int random_roll = zone->random.Int(1, 100);
//Determine which spell out of the group of the spells (each with own percent chance out of 100) will be cast based on a single roll.
for (int i = 0; i < EFFECT_COUNT; i++){
if (chance_array[i] != 0 && random_roll <= chance_array[i]) {
effect_slot = i;
CastSpell = true;
break;
}
}
}
//If the chances don't add to 100, then each effect gets a chance to fire, chance for no trigger as well.
else if (zone->random.Roll(spells[spell_id].base[effect])) {
CastSpell = true; //In this case effect_slot is what was passed into function.
}
if (CastSpell) {
if (spells[spell_id].effectid[effect_slot] == SE_SpellTrigger && IsValidSpell(spells[spell_id].base2[effect_slot])) {
SpellFinished(spells[spell_id].base2[effect_slot], target, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].base2[effect_slot]].ResistDiff);
return true;
}
else if (IsClient() & spells[spell_id].effectid[effect_slot] == SE_Chance_Best_in_Spell_Grp) {
uint32 best_spell_id = CastToClient()->GetHighestScribedSpellinSpellGroup(spells[spell_id].base2[effect_slot]);
if (IsValidSpell(best_spell_id)) {
SpellFinished(best_spell_id, target, EQ::spells::CastingSlot::Item, 0, -1, spells[best_spell_id].ResistDiff);
}
return true;//Do nothing if you don't have the any spell in spell group scribed.
}
}
return false;
}
void Mob::TryTriggerOnValueAmount(bool IsHP, bool IsMana, bool IsEndur, bool IsPet)
{
/*
@ -3676,6 +3702,9 @@ void Mob::TryTwincast(Mob *caster, Mob *target, uint32 spell_id)
if(!IsValidSpell(spell_id))
return;
if (IsEffectInSpell(spell_id, SE_TwinCastBlocker))
return;
if(IsClient())
{
int32 focus = CastToClient()->GetFocusEffect(focusTwincast, spell_id);
@ -3739,34 +3768,46 @@ void Mob::TryOnSpellFinished(Mob *caster, Mob *target, uint16 spell_id)
int32 Mob::GetVulnerability(Mob* caster, uint32 spell_id, uint32 ticsremaining)
{
/*
Modifies incoming spell damage by percent, to increase or decrease damage, can be limited to specific resists.
Can be applied through quest function, spell focus or npc_spells_effects table. This function is run on the target of the spell.
*/
if (!IsValidSpell(spell_id))
return 0;
if (!caster)
return 0;
int32 value = 0;
int32 total_mod = 0;
int32 innate_mod = 0;
int32 fc_spell_vulnerability_mod = 0;
int32 fc_spell_damage_pct_incomingPC_mod = 0;
//Apply innate vulnerabilities
//Apply innate vulnerabilities from quest functions and tables
if (Vulnerability_Mod[GetSpellResistType(spell_id)] != 0)
value = Vulnerability_Mod[GetSpellResistType(spell_id)];
innate_mod = Vulnerability_Mod[GetSpellResistType(spell_id)];
else if (Vulnerability_Mod[HIGHEST_RESIST+1] != 0)
value = Vulnerability_Mod[HIGHEST_RESIST+1];
innate_mod = Vulnerability_Mod[HIGHEST_RESIST+1];
//Apply spell derived vulnerabilities
if (spellbonuses.FocusEffects[focusSpellVulnerability]){
//[Apply spell derived vulnerabilities] Step 1: Check this focus effect exists on the mob.
if (spellbonuses.FocusEffects[focusSpellVulnerability]){
int32 tmp_focus = 0;
int tmp_buffslot = -1;
/*
Find all buffs that may contain SPA 296, then find which slot has the highest possible effect. Since the focus can use
a min and max amount value to determine final focus amt. To find the best focus, use only max value if possible. Once the
best is found. Run it again to get the final value randoming between min and max.
*/
int buff_count = GetMaxTotalSlots();
for(int i = 0; i < buff_count; i++) {
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;
@ -3780,21 +3821,61 @@ int32 Mob::GetVulnerability(Mob* caster, uint32 spell_id, uint32 ticsremaining)
tmp_focus = focus;
tmp_buffslot = i;
}
}
}
tmp_focus = caster->CalcFocusEffect(focusSpellVulnerability, buffs[tmp_buffslot].spellid, spell_id);
if (tmp_focus < -99)
tmp_focus = -99;
value += tmp_focus;
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);
}
return value;
if (spellbonuses.FocusEffects[focusFcSpellDamagePctIncomingPC]) {
int32 tmp_focus = 0;
int tmp_buffslot = -1;
/*
Find all buffs that may contain SPA 483, then find which slot has the highest possible effect. Since the focus can use
a min and max amount value to determine final focus amt. To find the best focus, use only max value if possible. Once the
best is found. Run it again to get the final value randoming between min and max.
*/
int buff_count = GetMaxTotalSlots();
for (int i = 0; i < buff_count; i++) {
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, buffs[tmp_buffslot].casterid);
if (!focus)
continue;
if (tmp_focus && focus > tmp_focus) {
tmp_focus = focus;
tmp_buffslot = i;
}
else if (!tmp_focus) {
tmp_focus = focus;
tmp_buffslot = i;
}
}
}
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);
}
total_mod = fc_spell_vulnerability_mod + fc_spell_damage_pct_incomingPC_mod;
//Don't let focus derived mods reduce past 99% mitigation. Quest related can, and for custom functionality if negative will give a healing affect instead of damage.
if (total_mod < -99)
total_mod = -99;
total_mod += innate_mod;
return total_mod;
}
int32 Mob::GetSkillDmgTaken(const EQ::skills::SkillType skill_used, ExtraAttackOptions *opts)
@ -3856,6 +3937,61 @@ int16 Mob::GetHealRate(uint16 spell_id, Mob* caster) {
return heal_rate;
}
void Mob::SetBottomRampageList()
{
auto &mob_list = entity_list.GetCloseMobList(this);
for (auto &e : mob_list) {
auto mob = e.second;
if (!mob) {
continue;
}
if (!mob->GetSpecialAbility(SPECATK_RAMPAGE)) {
continue;
}
if (mob->IsNPC() && mob->CheckAggro(this)) {
for (int i = 0; i < mob->RampageArray.size(); i++) {
// Find this mob in the rampage list
if (this->GetID() == mob->RampageArray[i]) {
//Move to bottom of Rampage List
auto it = mob->RampageArray.begin() + i;
std::rotate(it, it + 1, mob->RampageArray.end());
}
}
}
}
}
void Mob::SetTopRampageList()
{
auto &mob_list = entity_list.GetCloseMobList(this);
for (auto &e : mob_list) {
auto mob = e.second;
if (!mob) {
continue;
}
if (!mob->GetSpecialAbility(SPECATK_RAMPAGE)) {
continue;
}
if (mob->IsNPC() && mob->CheckAggro(this)) {
for (int i = 0; i < mob->RampageArray.size(); i++) {
// Find this mob in the rampage list
if (this->GetID() == mob->RampageArray[i]) {
//Move to Top of Rampage List
auto it = mob->RampageArray.begin() + i;
std::rotate(it, it + 1, mob->RampageArray.end());
std::rotate(mob->RampageArray.rbegin(), mob->RampageArray.rbegin() + 1, mob->RampageArray.rend());
}
}
}
}
}
bool Mob::TryFadeEffect(int slot)
{
if (!buffs[slot].spellid)
@ -4647,7 +4783,7 @@ bool Mob::TrySpellOnDeath()
//in death because the heal will not register before the script kills you.
}
int16 Mob::GetCritDmgMod(uint16 skill)
int16 Mob::GetCritDmgMod(uint16 skill, Mob* owner)
{
int critDmg_mod = 0;
@ -4658,6 +4794,9 @@ int16 Mob::GetCritDmgMod(uint16 skill)
critDmg_mod += itembonuses.CritDmgModNoStack[EQ::skills::HIGHEST_SKILL + 1] + spellbonuses.CritDmgModNoStack[EQ::skills::HIGHEST_SKILL + 1] + aabonuses.CritDmgModNoStack[EQ::skills::HIGHEST_SKILL + 1] +
itembonuses.CritDmgModNoStack[skill] + spellbonuses.CritDmgModNoStack[skill] + aabonuses.CritDmgModNoStack[skill];
if (owner) //Checked in TryPetCriticalHit
critDmg_mod += owner->aabonuses.Pet_Crit_Melee_Damage_Pct_Owner + owner->itembonuses.Pet_Crit_Melee_Damage_Pct_Owner + owner->spellbonuses.Pet_Crit_Melee_Damage_Pct_Owner;
return critDmg_mod;
}

View File

@ -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,
@ -814,7 +814,7 @@ public:
void CastOnCure(uint32 spell_id);
void CastOnNumHitFade(uint32 spell_id);
void SlowMitigation(Mob* caster);
int16 GetCritDmgMod(uint16 skill);
int16 GetCritDmgMod(uint16 skill, Mob* owner = nullptr);
int16 GetMeleeDamageMod_SE(uint16 skill);
int16 GetMeleeMinDamageMod_SE(uint16 skill);
int16 GetCrippBlowChance();
@ -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);
@ -909,6 +913,9 @@ public:
inline bool IsTempPet() const { return _IsTempPet; }
inline void SetTempPet(bool value) { _IsTempPet = value; }
inline bool IsHorse() { return is_horse; }
int GetPetAvoidanceBonusFromOwner();
int GetPetACBonusFromOwner();
int GetPetATKBonusFromOwner();
inline const bodyType GetBodyType() const { return bodytype; }
inline const bodyType GetOrigBodyType() const { return orig_bodytype; }
@ -966,6 +973,8 @@ public:
bool Rampage(ExtraAttackOptions *opts);
bool AddRampage(Mob*);
void ClearRampage();
void SetBottomRampageList();
void SetTopRampageList();
void AreaRampage(ExtraAttackOptions *opts);
inline bool IsSpecialAttack(eSpecialAttacks in) { return m_specialattacks == in; }
@ -1414,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;

View File

@ -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<float>(GetSpecialAbilityParam(TETHER, 0));
@ -1218,16 +1221,15 @@ void Mob::AI_Process() {
}
}
//SE_PC_Pet_Rampage SPA 464 on pet, chance modifier
if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) {
if (spellbonuses.PC_Pet_Rampage[0] || itembonuses.PC_Pet_Rampage[0] ||
aabonuses.PC_Pet_Rampage[0]) {
int chance = spellbonuses.PC_Pet_Rampage[0] + itembonuses.PC_Pet_Rampage[0] +
aabonuses.PC_Pet_Rampage[0];
if (zone->random.Roll(chance)) {
Rampage(nullptr);
}
int chance = spellbonuses.PC_Pet_Rampage[0] + itembonuses.PC_Pet_Rampage[0] + aabonuses.PC_Pet_Rampage[0];
if (chance && zone->random.Roll(chance)) {
Rampage(nullptr);
}
}
if (GetSpecialAbility(SPECATK_RAMPAGE) && !specialed) {
int rampage_chance = GetSpecialAbilityParam(SPECATK_RAMPAGE, 0);
@ -1263,6 +1265,14 @@ void Mob::AI_Process() {
}
}
//SE_PC_Pet_Rampage SPA 465 on pet, chance modifier
if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) {
int chance = spellbonuses.PC_Pet_AE_Rampage[0] + itembonuses.PC_Pet_AE_Rampage[0] + aabonuses.PC_Pet_AE_Rampage[0];
if (chance && zone->random.Roll(chance)) {
Rampage(nullptr);
}
}
if (GetSpecialAbility(SPECATK_AREA_RAMPAGE) && !specialed) {
int rampage_chance = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 0);
rampage_chance = rampage_chance > 0 ? rampage_chance : 20;

View File

@ -197,7 +197,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
if (!IsPowerDistModSpell(spell_id))
SetSpellPowerDistanceMod(0);
bool SE_SpellTrigger_HasCast = false;
bool spell_trigger_cast_complete = false; //Used with SE_Spell_Trigger and SE_Chance_Best_in_Spell_Grp, true when spell has been triggered.
// if buff slot, use instrument mod there, otherwise calc it
uint32 instrument_mod = buffslot > -1 ? buffs[buffslot].instrument_mod : caster ? caster->GetInstrumentMod(spell_id) : 10;
@ -2822,9 +2822,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
case SE_SpellTrigger: {
if (!SE_SpellTrigger_HasCast) {
if (!spell_trigger_cast_complete) {
if (caster && caster->TrySpellTrigger(this, spell_id, i))
SE_SpellTrigger_HasCast = true;
spell_trigger_cast_complete = true;
}
break;
}
@ -2873,8 +2873,83 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
HealDamage(amt, caster);
break;
}
case SE_Chance_Best_in_Spell_Grp: {
if (!spell_trigger_cast_complete) {
if (caster && caster->TrySpellTrigger(this, spell_id, i))
spell_trigger_cast_complete = true;
}
break;
}
case SE_Trigger_Best_in_Spell_Grp: {
if (caster && !caster->IsClient())
break;
if (zone->random.Roll(spells[spell_id].base[i])) {
uint32 best_spell_id = caster->CastToClient()->GetHighestScribedSpellinSpellGroup(spells[spell_id].base2[i]);
if (caster && IsValidSpell(best_spell_id))
caster->SpellFinished(best_spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[best_spell_id].ResistDiff);
}
break;
}
case SE_Trigger_Spell_Non_Item: {
//Only trigger if not from item
if (caster && caster->IsClient() && GetCastedSpellInvSlot() > 0)
break;
if (zone->random.Roll(spells[spell_id].base[i]) && IsValidSpell(spells[spell_id].base2[i]))
caster->SpellFinished(spells[spell_id].base2[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff);
break;
}
case SE_Hatelist_To_Tail_Index: {
if (caster && zone->random.Roll(spells[spell_id].base[i]))
caster->SetBottomRampageList();
break;
}
case SE_Hatelist_To_Top_Index: {
if (caster && zone->random.Roll(spells[spell_id].base[i]))
caster->SetTopRampageList();
break;
}
case SE_Fearstun: {
//Normal 'stun' restrictions do not apply. base1=duration, base2=PC duration, max =lv restrict
if (!caster)
break;
if (IsNPC() && GetSpecialAbility(UNSTUNABLE)) {
caster->MessageString(Chat::SpellFailure, IMMUNE_STUN);
break;
}
if (IsNPC() && GetSpecialAbility(UNFEARABLE)) {
caster->MessageString(Chat::SpellFailure, IMMUNE_FEAR);
break;
}
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(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;
@ -2990,6 +3065,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
case SE_HealRate:
case SE_SkillDamageTaken:
case SE_FcSpellVulnerability:
case SE_Fc_Spell_Damage_Pct_IncomingPC:
case SE_Fc_Spell_Damage_Amt_IncomingPC:
case SE_FcTwincast:
case SE_DelayDeath:
case SE_CastOnFadeEffect:
@ -3131,6 +3208,12 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
case SE_DS_Mitigation_Amount:
case SE_DS_Mitigation_Percentage:
case SE_Double_Backstab_Front:
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;
}
@ -4406,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;
@ -4433,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;
@ -4462,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;
@ -4479,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;
}
@ -4519,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;
@ -4538,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;
@ -4549,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;
@ -4560,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;
@ -4571,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;
@ -4600,7 +4693,12 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id)
LimitFailure = true;
break;
// Handle Focus Effects
/* These are not applicable to AA's because there is never a 'caster' of the 'buff' with the focus effect.
case SE_Ff_Same_Caster:
case SE_Ff_CasterClass:
*/
// Handle Focus Effects
case SE_ImprovedDamage:
if (type == focusImprovedDamage && base1 > value)
value = base1;
@ -4667,11 +4765,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;
@ -4685,7 +4785,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;
}
@ -4697,6 +4798,11 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id)
value = base1;
break;
case SE_Fc_Spell_Damage_Pct_IncomingPC:
if (type == focusFcSpellDamagePctIncomingPC)
value = base1;
break;
case SE_BlockNextSpellFocus:
if (type == focusBlockNextSpell) {
if (zone->random.Roll(base1))
@ -4709,7 +4815,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;
@ -4735,6 +4841,11 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id)
value = base1;
break;
case SE_Fc_Spell_Damage_Amt_IncomingPC:
if (type == focusFcSpellDamageAmtIncomingPC)
value = base1;
break;
case SE_FcHealAmtIncoming:
if (type == focusFcHealAmtIncoming)
value = base1;
@ -4789,6 +4900,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;
}
}
}
@ -4805,8 +4924,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;
@ -5040,6 +5164,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) {
@ -5211,13 +5352,30 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo
if (type == focusSpellVulnerability) {
if (best_focus) {
if (focus_spell.base2[i] != 0)
value = focus_spell.base2[i];
value = focus_spell.base2[i]; //max damage
else
value = focus_spell.base[i];
value = focus_spell.base[i]; //min damage
} else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) {
value = focus_spell.base[i];
value = focus_spell.base[i]; //If no max damage set, then default to min damage
} else {
value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]);
value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); //else random for value
}
}
break;
case SE_Fc_Spell_Damage_Pct_IncomingPC:
if (type == focusFcSpellDamagePctIncomingPC) {
if (best_focus) {
if (focus_spell.base2[i] != 0)
value = focus_spell.base2[i]; //max damage
else
value = focus_spell.base[i]; //min damage
}
else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) {
value = focus_spell.base[i]; //If no max damage set, then default to min damage
}
else {
value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); //else random for value
}
}
break;
@ -5247,6 +5405,11 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo
value = focus_spell.base[i];
break;
case SE_Fc_Spell_Damage_Amt_IncomingPC:
if (type == focusFcSpellDamageAmtIncomingPC)
value = focus_spell.base[i];
break;
case SE_FcHealAmtIncoming:
if (type == focusFcHealAmtIncoming)
value = focus_spell.base[i];
@ -5307,6 +5470,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.
@ -6232,6 +6403,7 @@ bool Mob::DoHPToManaCovert(uint16 mana_cost)
int32 Mob::GetFcDamageAmtIncoming(Mob *caster, uint32 spell_id, bool use_skill, uint16 skill )
{
//Used to check focus derived from SE_FcDamageAmtIncoming which adds direct damage to Spells or Skill based attacks.
//Used to check focus derived from SE_Fc_Spell_Damage_Amt_IncomingPC which adds direct damage to Spells.
int32 dmg = 0;
bool limit_exists = false;
bool skill_found = false;
@ -6280,6 +6452,20 @@ int32 Mob::GetFcDamageAmtIncoming(Mob *caster, uint32 spell_id, bool use_skill,
}
}
}
if (spellbonuses.FocusEffects[focusFcSpellDamageAmtIncomingPC]) {
int buff_count = GetMaxTotalSlots();
for (int i = 0; i < buff_count; i++) {
if ((IsValidSpell(buffs[i].spellid) && (IsEffectInSpell(buffs[i].spellid, SE_FcDamageAmtIncoming)))) {
int32 focus = caster->CalcFocusEffect(focusFcSpellDamageAmtIncomingPC, buffs[i].spellid, spell_id);
if (focus) {
dmg += focus;
CheckNumHitsRemaining(NumHit::MatchingSpells, i);
}
}
}
}
return dmg;
}
@ -6967,6 +7153,133 @@ 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.
Live only has this focus in buffs/debuffs that can be placed on a target. TODO: Will consider adding support for it as AA and Item.
*/
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, should be set to at least 1 in any spell for the effect to function.
base2= lock out timer, which prevents any more procs set in ms 1500 = 1.5 seconds
This system allows easy scaling for multiple different buffs with same effects each having seperate active individual timer checks. Ie.
*/
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)

View File

@ -406,8 +406,7 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot,
spell.targettype == ST_Self ||
spell.targettype == ST_AECaster ||
spell.targettype == ST_Ring ||
spell.targettype == ST_Beam ||
spell.targettype == ST_TargetOptional) && target_id == 0)
spell.targettype == ST_Beam) && target_id == 0)
{
LogSpells("Spell [{}] auto-targeted the caster. Group? [{}], target type [{}]", spell_id, IsGroupSpell(spell_id), spell.targettype);
target_id = GetID();
@ -1585,8 +1584,12 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce
case ST_TargetOptional:
{
if(!spell_target)
spell_target = this;
if (!spell_target)
{
LogSpells("Spell [{}] canceled: invalid target (normal)", spell_id);
MessageString(Chat::Red, SPELL_NEED_TAR);
return false; // can't cast these unless we have a target
}
CastAction = SingleTarget;
break;
}
@ -3350,6 +3353,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 +3977,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);
@ -5251,6 +5260,27 @@ int Client::FindSpellBookSlotBySpellID(uint16 spellid) {
return -1; //default
}
uint32 Client::GetHighestScribedSpellinSpellGroup(uint32 spell_group)
{
//Typical live spells follow 1/5/10 rank value for actual ranks 1/2/3, but this can technically be set as anything.
int highest_rank = 0; //highest ranked found in spellgroup
uint32 highest_spell_id = 0; //spell_id of the highest ranked spell you have scribed in that spell rank.
for (int i = 0; i < EQ::spells::SPELLBOOK_SIZE; i++) {
if (IsValidSpell(m_pp.spell_book[i])) {
if (spells[m_pp.spell_book[i]].spellgroup == spell_group) {
if (highest_rank < spells[m_pp.spell_book[i]].rank) {
highest_rank = spells[m_pp.spell_book[i]].rank;
highest_spell_id = m_pp.spell_book[i];
}
}
}
}
return highest_spell_id;
}
bool Client::SpellGlobalCheck(uint16 spell_id, uint32 char_id) {
std::string spell_global_name;
int spell_global_value;

View File

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