From d0e069f4f8846d42edc5c29fca47b03f69464700 Mon Sep 17 00:00:00 2001 From: Fryguy Date: Tue, 9 Jan 2024 05:48:45 -0500 Subject: [PATCH] [Bug Fix] Rampage Number of Hits Limit (#3929) * [Bug Fix] Rampage Number of Hits Limit Rampage should Hit 1-2 times (Primary / Secondary) should not be Triple/Quadable * requested name convention changes --- zone/attack.cpp | 27 +++++++++++++++------------ zone/mob.h | 13 +++++++------ zone/mob_ai.cpp | 24 ++++++++++++++++-------- 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index 7cfacded0..f40e7f396 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -6446,10 +6446,11 @@ bool Client::CheckDualWield() return zone->random.Int(1, 375) <= chance; } -void Mob::DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts) +void Mob::DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts, bool rampage) { - if (!target) + if (!target) { return; + } if (RuleB(Combat, UseLiveCombatRounds)) { // A "quad" on live really is just a successful dual wield where both double attack @@ -6459,8 +6460,9 @@ void Mob::DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts) Attack(target, EQ::invslot::slotPrimary, false, false, false, opts); if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { int chance = spellbonuses.PC_Pet_Flurry + itembonuses.PC_Pet_Flurry + aabonuses.PC_Pet_Flurry; - if (chance && zone->random.Roll(chance)) + if (chance && zone->random.Roll(chance)) { Flurry(nullptr); + } } } return; @@ -6468,16 +6470,14 @@ void Mob::DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts) if (IsNPC()) { int16 n_atk = CastToNPC()->GetNumberOfAttacks(); - if (n_atk <= 1) { + if (n_atk <= 1 || rampage) { Attack(target, EQ::invslot::slotPrimary, false, false, false, opts); - } - else { + } else { for (int i = 0; i < n_atk; ++i) { Attack(target, EQ::invslot::slotPrimary, false, false, false, opts); } } - } - else { + } else { Attack(target, EQ::invslot::slotPrimary, false, false, false, opts); } @@ -6503,10 +6503,12 @@ void Mob::DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts) } } -void Mob::DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts) +void Mob::DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts, bool rampage) { - if (!target) + if (!target) { return; + } + // Mobs will only dual wield w/ the flag or have a secondary weapon // For now, SPECATK_QUAD means innate DW when Combat:UseLiveCombatRounds is true if ((GetSpecialAbility(SPECATK_INNATE_DW) || @@ -6514,13 +6516,14 @@ void Mob::DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts) GetEquippedItemFromTextureSlot(EQ::textures::weaponSecondary) != 0) { if (CheckDualWield()) { Attack(target, EQ::invslot::slotSecondary, false, false, false, opts); - if (CanThisClassDoubleAttack() && GetLevel() > 35 && CheckDoubleAttack()) { + if (CanThisClassDoubleAttack() && GetLevel() > 35 && CheckDoubleAttack() && !rampage) { Attack(target, EQ::invslot::slotSecondary, false, false, false, opts); if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { int chance = spellbonuses.PC_Pet_Flurry + itembonuses.PC_Pet_Flurry + aabonuses.PC_Pet_Flurry; - if (chance && zone->random.Roll(chance)) + if (chance && zone->random.Roll(chance)) { Flurry(nullptr); + } } } } diff --git a/zone/mob.h b/zone/mob.h index 039cec200..f92edf00f 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -260,16 +260,17 @@ public: void CommonOutgoingHitSuccess(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *opts = nullptr); bool HasDied(); virtual bool CheckDualWield(); - void DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr); - void DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr); + void DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr, bool rampage = false); + void DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr, bool rampage = false); virtual bool CheckDoubleAttack(); // inline process for places where we need to do them outside of the AI_Process - void ProcessAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr) + void ProcessAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr, bool rampage = false) { if (target) { - DoMainHandAttackRounds(target, opts); - if (CanThisClassDualWield()) - DoOffHandAttackRounds(target, opts); + DoMainHandAttackRounds(target, opts, rampage); + if (CanThisClassDualWield()) { + DoOffHandAttackRounds(target, opts, rampage); + } } return; } diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index ad9c927f3..ff48dffe9 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -2098,16 +2098,22 @@ bool Mob::Rampage(ExtraAttackOptions *opts) } else { entity_list.MessageCloseString(this, true, 200, Chat::NPCRampage, NPC_RAMPAGE, GetCleanName()); } + int rampage_targets = GetSpecialAbilityParam(SPECATK_RAMPAGE, 1); - if (rampage_targets == 0) // if set to 0 or not set in the DB + + if (rampage_targets == 0) { // if set to 0 or not set in the DB rampage_targets = RuleI(Combat, DefaultRampageTargets); - if (rampage_targets > RuleI(Combat, MaxRampageTargets)) + } + + if (rampage_targets > RuleI(Combat, MaxRampageTargets)) { rampage_targets = RuleI(Combat, MaxRampageTargets); + } m_specialattacks = eSpecialAttacks::Rampage; for (int i = 0; i < RampageArray.size(); i++) { - if (index_hit >= rampage_targets) + if (index_hit >= rampage_targets) { break; + } // range is important Mob *m_target = entity_list.GetMob(RampageArray[i]); if (m_target) { @@ -2122,7 +2128,7 @@ bool Mob::Rampage(ExtraAttackOptions *opts) } if (DistanceSquaredNoZ(GetPosition(), m_target->GetPosition()) <= NPC_RAMPAGE_RANGE2) { - ProcessAttackRounds(m_target, opts); + ProcessAttackRounds(m_target, opts, true); index_hit++; } } @@ -2130,20 +2136,22 @@ bool Mob::Rampage(ExtraAttackOptions *opts) if (RuleB(Combat, RampageHitsTarget)) { if (index_hit < rampage_targets) - ProcessAttackRounds(GetTarget(), opts); + ProcessAttackRounds(GetTarget(), opts, true); } else { // let's do correct behavior here, if they set above rule we can assume they want non-live like behavior if (index_hit < rampage_targets) { // so we go over in reverse order and skip range check // lets do it this way to still support non-live-like >1 rampage targets // likely live is just a fall through of the last valid mob for (auto i = RampageArray.crbegin(); i != RampageArray.crend(); ++i) { - if (index_hit >= rampage_targets) + if (index_hit >= rampage_targets) { break; + } auto m_target = entity_list.GetMob(*i); if (m_target) { - if (m_target == GetTarget()) + if (m_target == GetTarget()) { continue; - ProcessAttackRounds(m_target, opts); + } + ProcessAttackRounds(m_target, opts, true); index_hit++; } }