diff --git a/common/ruletypes.h b/common/ruletypes.h index 4acd79dbd..6dec50fa3 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -410,6 +410,7 @@ RULE_INT(Spells, ClericInnateHealFocus, 5, "Clerics on live get a 5 pct innate h RULE_BOOL(Spells, DOTsScaleWithSpellDmg, false, "Allow SpellDmg stat to affect DoT spells") RULE_BOOL(Spells, HOTsScaleWithHealAmt, false, "Allow HealAmt stat to affect HoT spells") RULE_BOOL(Spells, CompoundLifetapHeals, true, "True: Lifetap heals calculate damage bonuses and then heal bonuses. False: Lifetaps heal using the amount damaged to mob.") +RULE_BOOL(Spells, UseFadingMemoriesMaxLevel, false, "Enables to limit field in spell data to set the max level that over which an NPC will ignore fading memories effect and not lose aggro.") RULE_CATEGORY_END() RULE_CATEGORY(Combat) diff --git a/common/spdat.h b/common/spdat.h index 7e0582f91..caa80ce54 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -916,8 +916,8 @@ typedef enum { #define SE_EndurancePool 190 // implemented #define SE_Amnesia 191 // implemented - Silence vs Melee Effect #define SE_Hate 192 // implemented - Instant and hate over time. -#define SE_SkillAttack 193 // implemented -#define SE_FadingMemories 194 // implemented +#define SE_SkillAttack 193 // implemented, +#define SE_FadingMemories 194 // implemented, @Aggro, Remove from hate lists and make invisible. Can set max level of NPCs that can be affected. base: success chance, limit: max level (ROF2), max: max level (modern client), Note: Support for max level requires Rule (Spells, UseFadingMemoriesMaxLevel) to be true. If used from limit field, then it set as the level, ie. max level of 75 would use limit value of 75. If set from max field, max level 75 would use max value of 1075, if you want to set it so it checks a level range above the spell target then for it to only work on mobs 5 levels or below you set max value to 5. #define SE_StunResist 195 // implemented #define SE_StrikeThrough 196 // implemented #define SE_SkillDamageTaken 197 // implemented diff --git a/zone/client.h b/zone/client.h index 3b3b0ac84..5cbc203ac 100644 --- a/zone/client.h +++ b/zone/client.h @@ -980,7 +980,7 @@ public: //bool DecreaseByType(uint32 type, uint8 amt); bool DecreaseByID(uint32 type, int16 quantity); uint8 SlotConvert2(uint8 slot); //Maybe not needed. - void Escape(); //AA Escape + void Escape(); //keep or quest function void DisenchantSummonedBags(bool client_update = true); void RemoveNoRent(bool client_update = true); void RemoveDuplicateLore(bool client_update = true); diff --git a/zone/common.h b/zone/common.h index 795391ad3..31299d3b4 100644 --- a/zone/common.h +++ b/zone/common.h @@ -209,7 +209,8 @@ enum { IMMUNE_AGGRO_CLIENT = 49, IMMUNE_AGGRO_NPC = 50, MODIFY_AVOID_DAMAGE = 51, //Modify by percent the NPCs chance to riposte, block, parry or dodge individually, or for all skills - MAX_SPECIAL_ATTACK = 52 + IMMUNE_FADING_MEMORIES = 52, + MAX_SPECIAL_ATTACK = 53 }; typedef enum { //fear states diff --git a/zone/entity.cpp b/zone/entity.cpp index ad9da930d..0aa2aba78 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -1514,7 +1514,7 @@ void EntityList::RemoveFromTargets(Mob *mob, bool RemoveFromXTargets) if (!m) continue; - if (RemoveFromXTargets) { + if (RemoveFromXTargets && mob) { if (m->IsClient() && (mob->CheckAggro(m) || mob->IsOnFeignMemory(m))) m->CastToClient()->RemoveXTarget(mob, false); // FadingMemories calls this function passing the client. @@ -1526,6 +1526,32 @@ void EntityList::RemoveFromTargets(Mob *mob, bool RemoveFromXTargets) } } +void EntityList::RemoveFromTargetsFadingMemories(Mob *spell_target, bool RemoveFromXTargets, uint32 max_level) +{ + for (auto &e : mob_list) { + auto &mob = e.second; + + if (!mob) { + continue; + } + + if (max_level && mob->GetLevel() > max_level) + continue; + + if (mob->GetSpecialAbility(IMMUNE_FADING_MEMORIES)) + continue; + + if (RemoveFromXTargets && spell_target) { + if (mob->IsClient() && (spell_target->CheckAggro(mob) || spell_target->IsOnFeignMemory(mob))) + mob->CastToClient()->RemoveXTarget(spell_target, false); + else if (spell_target->IsClient() && (mob->CheckAggro(spell_target) || mob->IsOnFeignMemory(spell_target))) + spell_target->CastToClient()->RemoveXTarget(mob, false); + } + + mob->RemoveFromHateList(spell_target); + } +} + void EntityList::RemoveFromXTargets(Mob *mob) { auto it = client_list.begin(); diff --git a/zone/entity.h b/zone/entity.h index a743a18b0..bc44cb2c7 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -389,6 +389,7 @@ public: void ExpeditionWarning(uint32 minutes_left); void RemoveFromTargets(Mob* mob, bool RemoveFromXTargets = false); + void RemoveFromTargetsFadingMemories(Mob* spell_target, bool RemoveFromXTargets = false, uint32 max_level = 0); void RemoveFromXTargets(Mob* mob); void RemoveFromAutoXTargets(Mob* mob); void ReplaceWithTarget(Mob* pOldMob, Mob*pNewTarget); diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 71f98f40f..f3701f316 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -2208,9 +2208,41 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Fading Memories"); #endif + int max_level = 0; + + if (RuleB(Spells, UseFadingMemoriesMaxLevel)) { + //handle ROF2 era where limit value determines max level + if (spells[spell_id].limit_value[i]) { + max_level = spells[spell_id].limit_value[i]; + } + //handle modern client era where max value determines max level or range above client. + else if (spells[spell_id].max_value[i]) { + if (spells[spell_id].max_value[i] >= 1000) { + max_level = 1000 - spells[spell_id].max_value[i]; + } + else { + max_level = GetLevel() + spells[spell_id].max_value[i]; + } + } + } + if(zone->random.Roll(spells[spell_id].base_value[i])) { if (IsClient()) { - CastToClient()->Escape(); + int pre_aggro_count = CastToClient()->GetAggroCount(); + entity_list.RemoveFromTargetsFadingMemories(this, true, max_level); + SetInvisible(Invisibility::Invisible); + int post_aggro_count = CastToClient()->GetAggroCount(); + if (RuleB(Spells, UseFadingMemoriesMaxLevel)) { + if (pre_aggro_count == post_aggro_count) { + Message(Chat::SpellFailure, "You failed to escape from all your opponents."); + break; + } + else if (post_aggro_count) { + Message(Chat::SpellFailure, "You failed to escape from combat but you evade some of your opponents."); + break; + } + } + MessageString(Chat::Skills, ESCAPE); } else{ entity_list.RemoveFromTargets(caster);