From ca0ae3cb98aa8e56176cb7398928cea83c31c5b2 Mon Sep 17 00:00:00 2001 From: Paul Coene Date: Sun, 15 Jan 2023 16:30:42 -0500 Subject: [PATCH] [Rule] Add rule for NPC Level Based Buff Restrictions. (#2708) * [Rule] Add rule for NPC Level Based Buff Restrictions. * Erroneous whitespace * Change prefix on log message * Added log message when GM casts --- common/ruletypes.h | 1 + zone/client.h | 1 - zone/mob.h | 2 +- zone/pets.cpp | 4 +- zone/pets.h | 2 +- zone/spells.cpp | 96 ++++++++++++++++++++++++++++------------------ 6 files changed, 63 insertions(+), 43 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index bed6fbb9e..082e6f263 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -426,6 +426,7 @@ RULE_BOOL(Spells, BuffsFadeOnDeath, true, "Disable to keep buffs from fading on RULE_BOOL(Spells, IllusionsAlwaysPersist, false, "Allows Illusions to persist beyond death and zoning always.") RULE_BOOL(Spells, UseItemCastMessage, false, "Enable to use the \"item begins to glow\" messages when casting from an item.") RULE_BOOL(Spells, TargetsTargetRequiresCombatRange, true, "Disable to remove combat range requirement from Target's Target Spell Target Type") +RULE_BOOL(Spells, NPCBuffLevelRestrictions, false, "Impose BuffLevelRestrictions on NPCs if true") RULE_CATEGORY_END() RULE_CATEGORY(Combat) diff --git a/zone/client.h b/zone/client.h index ec872f657..24ead68da 100644 --- a/zone/client.h +++ b/zone/client.h @@ -560,7 +560,6 @@ public: inline virtual int32 GetDelayDeath() const { return aabonuses.DelayDeath + spellbonuses.DelayDeath + itembonuses.DelayDeath + 11; } virtual bool CheckFizzle(uint16 spell_id); - virtual bool CheckSpellLevelRestriction(uint16 spell_id); virtual int GetCurrentBuffSlots() const; virtual int GetCurrentSongSlots() const; virtual int GetCurrentDiscSlots() const { return 1; } diff --git a/zone/mob.h b/zone/mob.h index 57408b970..3704dbc02 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -373,7 +373,7 @@ public: bool DoCastingChecksZoneRestrictions(bool check_on_casting, int32 spell_id); bool DoCastingChecksOnTarget(bool check_on_casting, int32 spell_id, Mob* spell_target); virtual bool CheckFizzle(uint16 spell_id); - virtual bool CheckSpellLevelRestriction(uint16 spell_id); + virtual bool CheckSpellLevelRestriction(Mob *caster, uint16 spell_id); virtual bool IsImmuneToSpell(uint16 spell_id, Mob *caster); virtual float GetAOERange(uint16 spell_id); void InterruptSpell(uint16 spellid = SPELL_UNKNOWN); diff --git a/zone/pets.cpp b/zone/pets.cpp index 54d143066..1a71d942f 100644 --- a/zone/pets.cpp +++ b/zone/pets.cpp @@ -735,11 +735,11 @@ bool ZoneDatabase::GetBasePetItems(int32 equipmentset, uint32 *items) { return true; } -bool Pet::CheckSpellLevelRestriction(uint16 spell_id) +bool Pet::CheckSpellLevelRestriction(Mob *caster, uint16 spell_id) { auto owner = GetOwner(); if (owner) - return owner->CheckSpellLevelRestriction(spell_id); + return owner->CheckSpellLevelRestriction(caster, spell_id); return true; } diff --git a/zone/pets.h b/zone/pets.h index 1b9811149..f8ab50eca 100644 --- a/zone/pets.h +++ b/zone/pets.h @@ -7,7 +7,7 @@ struct NPCType; class Pet : public NPC { public: Pet(NPCType *type_data, Mob *owner, PetType type, uint16 spell_id, int16 power); - virtual bool CheckSpellLevelRestriction(uint16 spell_id); + virtual bool CheckSpellLevelRestriction(Mob *caster, uint16 spell_id); }; diff --git a/zone/spells.cpp b/zone/spells.cpp index 9805ccece..6fe8afeed 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -734,10 +734,6 @@ bool Mob::DoCastingChecksOnTarget(bool check_on_casting, int32 spell_id, Mob *sp */ bool ignore_on_casting = false; - bool ignore_if_npc_or_gm = false; - if (!IsClient() || (IsClient() && CastToClient()->GetGM())) { - ignore_if_npc_or_gm = true; - } if (check_on_casting) { if (spells[spell_id].target_type == ST_AEClientV1 || @@ -803,15 +799,9 @@ bool Mob::DoCastingChecksOnTarget(bool check_on_casting, int32 spell_id, Mob *sp /* Prevent buffs from being cast on targets who don't meet level restriction */ - if (!ignore_if_npc_or_gm && RuleB(Spells, BuffLevelRestrictions)) { - // casting_spell_targetid is guaranteed to be what we went, check for ST_Self for now should work though - if (spells[spell_id].target_type != ST_Self && !spell_target->CheckSpellLevelRestriction(spell_id)) { - LogSpells("Spell [{}] failed: recipient did not meet the level restrictions", spell_id); - if (!IsBardSong(spell_id)) { - MessageString(Chat::SpellFailure, SPELL_TOO_POWERFUL); - } - return false; - } + + if (!spell_target->CheckSpellLevelRestriction(this, spell_id)) { + return false; } /* Prevents buff from being cast based on tareget ing PC OR NPC (1 = PCs, 2 = NPCs) @@ -1161,6 +1151,9 @@ void Mob::InterruptSpell(uint16 message, uint16 color, uint16 spellid) } } + LogSpells("Interrupt: casting_spell_id [{}] casting_spell_slot [{}]", + casting_spell_id, (int) casting_spell_slot); + if(casting_spell_id && IsNPC()) { CastToNPC()->AI_Event_SpellCastFinished(false, static_cast(casting_spell_slot)); } @@ -1275,8 +1268,9 @@ void Mob::StopCastSpell(int32 spell_id, bool send_spellbar_enable) -AA that fail at CastSpell because they never get timer set. -Instant cast items that fail at CastSpell because they never get timer set. */ - if (casting_spell_id && IsNPC()) { - CastToNPC()->AI_Event_SpellCastFinished(false, static_cast(casting_spell_slot)); + // Often called before spell_id and slot are set. For NPCs always update AI + if (IsNPC()) { + CastToNPC()->AI_Event_SpellCastFinished(false, 1); } if (send_spellbar_enable) { @@ -3210,29 +3204,60 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, // spells 1-50: no restrictons // 51-65: SpellLevel/2+15 // 66+ Group Spells 62, Single Target 61 -bool Mob::CheckSpellLevelRestriction(uint16 spell_id) +bool Mob::CheckSpellLevelRestriction(Mob *caster, uint16 spell_id) { - return true; -} + bool check_for_restrictions = false; + bool can_cast = true; -bool Client::CheckSpellLevelRestriction(uint16 spell_id) -{ - int SpellLevel = GetMinLevel(spell_id); + if (!caster) { + LogSpells("[CheckSpellLevelRestriction] No caster"); + return false; + } - // Only check for beneficial buffs - if (IsBuffSpell(spell_id) && IsBeneficialSpell(spell_id)) { - if (SpellLevel > 65) { - if (IsGroupSpell(spell_id) && GetLevel() < 62) - return false; - else if (GetLevel() < 61) - return false; - } else if (SpellLevel > 50) { // 51-65 - if (GetLevel() < (SpellLevel / 2 + 15)) - return false; + if (caster->IsClient() && caster->CastToClient()->GetGM()) { + LogSpells("[CheckSpellLevelRestriction] GM casting - No restrictions"); + return true; + } + + // NON GM clients might be restricted by rule setting + if (caster->IsClient()) { + if (RuleB(Spells, BuffLevelRestrictions)) { + check_for_restrictions = true; + } + } + // NPCS might be restricted by rule setting + else if (RuleB(Spells, NPCBuffLevelRestrictions)) { + check_for_restrictions = true; + } + + if (check_for_restrictions) { + int spell_level = GetMinLevel(spell_id); + + // Only check for beneficial buffs + if (IsBuffSpell(spell_id) && IsBeneficialSpell(spell_id)) { + if (spell_level > 65) { + if (IsGroupSpell(spell_id) && GetLevel() < 62) { + can_cast = false; + } + else if (GetLevel() < 61) { + can_cast = false; + } + } else if (spell_level > 50) { // 51-65 + if (GetLevel() < (spell_level / 2 + 15)) { + can_cast = false; + } + } } } - return true; + if (!can_cast) { + LogSpells("Spell [{}] failed: recipient did not meet the level restrictions", spell_id); + if (!IsBardSong(spell_id)) { + caster->MessageString(Chat::SpellFailure, SPELL_TOO_POWERFUL); + } + } + + return can_cast; } uint32 Mob::GetFirstBuffSlot(bool disc, bool song) @@ -4175,12 +4200,7 @@ bool Mob::SpellOnTarget( } // make sure spelltar is high enough level for the buff - if (RuleB(Spells, BuffLevelRestrictions) && !spelltar->CheckSpellLevelRestriction(spell_id)) { - LogSpells("Spell [{}] failed: recipient did not meet the level restrictions", spell_id); - if (!IsBardSong(spell_id)) { - MessageString(Chat::SpellFailure, SPELL_TOO_POWERFUL); - } - + if (!spelltar->CheckSpellLevelRestriction(this, spell_id)) { safe_delete(action_packet); return false; }