diff --git a/common/ruletypes.h b/common/ruletypes.h index 2dab1b0cb..be7f03b2c 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -530,6 +530,7 @@ RULE_BOOL(Combat, BackstabIgnoresElemental, false, "Enable or disable Elemental RULE_BOOL(Combat, BackstabIgnoresBane, false, "Enable or disable Bane weapon damage affecting backstab damage, false by default.") RULE_BOOL(Combat, SummonMeleeRange, true, "Enable or disable summoning of a player when already in melee range of the summoner.") RULE_BOOL(Combat, WaterMatchRequiredForAutoFireLoS, true, "Enable/Disable the requirement of both the attacker/victim being both in or out of water for AutoFire LoS to pass.") +RULE_INT(Combat, ExtraAllowedKickClassesBitmask, 0, "Bitmask for allowing extra classes beyond Warrior, Ranger, Beastlord, and Berserker to kick, No Extra Classes (0) by default") RULE_CATEGORY_END() RULE_CATEGORY(NPC) diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index fd48b49d7..bcfe43c15 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -281,41 +281,54 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQ::skills::SkillType skill, int32 bas // We should probably refactor this to take the struct not the packet void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) { - if (!GetTarget()) + if (!GetTarget()) { return; + } + // make sure were actually able to use such an attack. (Bards can throw while casting. ~Kayen confirmed on live 1/22) - if ((spellend_timer.Enabled() && GetClass() != BARD)|| IsFeared() || IsStunned() || IsMezzed() || DivineAura() || dead) + if ( + (spellend_timer.Enabled() && GetClass() != BARD) || + IsFeared() || + IsStunned() || + IsMezzed() || + DivineAura() || + dead + ) { return; + } pTimerType timer = pTimerCombatAbility; // RoF2+ Tiger Claw is unlinked from other monk skills, if they ever do that for other classes there will need // to be more checks here - if (ClientVersion() >= EQ::versions::ClientVersion::RoF2 && ca_atk->m_skill == EQ::skills::SkillTigerClaw) + if (ClientVersion() >= EQ::versions::ClientVersion::RoF2 && ca_atk->m_skill == EQ::skills::SkillTigerClaw) { timer = pTimerCombatAbility2; + } - bool CanBypassSkillCheck = false; + bool bypass_skill_check = false; if (ca_atk->m_skill == EQ::skills::SkillBash) { // SLAM - Bash without a shield equipped - switch (GetRace()) - { - case OGRE: - case TROLL: - case BARBARIAN: - CanBypassSkillCheck = true; - default: - break; + switch (GetRace()) { + case OGRE: + case TROLL: + case BARBARIAN: + bypass_skill_check = true; + default: + break; } } - /* Check to see if actually have skill */ - if (!MaxSkill(static_cast(ca_atk->m_skill)) && !CanBypassSkillCheck) + // Check to see if actually have skill + if (!MaxSkill(static_cast(ca_atk->m_skill)) && !bypass_skill_check) { return; + } - if (GetTarget()->GetID() != ca_atk->m_target) - return; // invalid packet. - - if (!IsAttackAllowed(GetTarget())) + if (GetTarget()->GetID() != ca_atk->m_target) { // invalid packet. return; + } + + if (!IsAttackAllowed(GetTarget())) { + return; + } // These two are not subject to the combat ability timer, as they // allready do their checking in conjunction with the attack timer @@ -324,146 +337,194 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) if (ca_atk->m_skill == EQ::skills::SkillThrowing) { SetAttackTimer(); ThrowingAttack(GetTarget()); - if (CheckDoubleRangedAttack()) + + if (CheckDoubleRangedAttack()) { ThrowingAttack(GetTarget(), true); + } + return; } + // ranged attack (archery) if (ca_atk->m_skill == EQ::skills::SkillArchery) { SetAttackTimer(); RangedAttack(GetTarget()); - if (CheckDoubleRangedAttack()) + + if (CheckDoubleRangedAttack()) { RangedAttack(GetTarget(), true); + } + return; } - // could we return here? Im not sure is m_atk 11 is used for real specials } // check range for all these abilities, they are all close combat stuff - if (!CombatRange(GetTarget())) + if (!CombatRange(GetTarget())) { return; + } if (!p_timers.Expired(&database, timer, false)) { Message(Chat::Red, "Ability recovery time not yet met."); return; } - int ReuseTime = 0; - int ClientHaste = GetHaste(); - int HasteMod = 0; + int reuse_time = 0; + int haste = GetHaste(); + int haste_modifier = 0; - if (ClientHaste >= 0) - HasteMod = (10000 / (100 + ClientHaste)); //+100% haste = 2x as many attacks - else - HasteMod = (100 - ClientHaste); //-100% haste = 1/2 as many attacks + if (haste >= 0) { + haste_modifier = (10000 / (100 + haste)); //+100% haste = 2x as many attacks + } else { + haste_modifier = (100 - haste); //-100% haste = 1/2 as many attacks + } - int64 dmg = 0; + int64 damage = 0; + int16 skill_reduction = GetSkillReuseTime(ca_atk->m_skill); - int32 skill_reduction = GetSkillReuseTime(ca_atk->m_skill); - - // not sure what the '100' indicates..if ->m_atk is not used as 'slot' reference, then change SlotRange above back to '11' - if (ca_atk->m_atk == 100 && - ca_atk->m_skill == EQ::skills::SkillBash) { // SLAM - Bash without a shield equipped + // not sure what the '100' indicates, if ->m_atk is not used as 'slot' reference, then change SlotRange above back to '11' + if ( + ca_atk->m_atk == 100 && + ca_atk->m_skill == EQ::skills::SkillBash + ) { // SLAM - Bash without a shield equipped if (GetTarget() != this) { - CheckIncreaseSkill(EQ::skills::SkillBash, GetTarget(), 10); DoAnim(animTailRake, 0, false); - int32 ht = 0; - if (GetWeaponDamage(GetTarget(), GetInv().GetItem(EQ::invslot::slotSecondary)) <= 0 && - GetWeaponDamage(GetTarget(), GetInv().GetItem(EQ::invslot::slotShoulders)) <= 0) - dmg = -5; - else - ht = dmg = GetBaseSkillDamage(EQ::skills::SkillBash, GetTarget()); + int hate_override = 0; - ReuseTime = BashReuseTime - 1 - skill_reduction; - ReuseTime = (ReuseTime * HasteMod) / 100; - DoSpecialAttackDamage(GetTarget(), EQ::skills::SkillBash, dmg, 0, ht, ReuseTime); - if (ReuseTime > 0) - p_timers.Start(timer, ReuseTime); + if ( + GetWeaponDamage(GetTarget(), GetInv().GetItem(EQ::invslot::slotSecondary)) <= 0 && + GetWeaponDamage(GetTarget(), GetInv().GetItem(EQ::invslot::slotShoulders)) <= 0 + ) { + damage = -5; + } else { + hate_override = damage = GetBaseSkillDamage(EQ::skills::SkillBash, GetTarget()); + } + + reuse_time = BashReuseTime - 1 - skill_reduction; + reuse_time = (reuse_time * haste_modifier) / 100; + DoSpecialAttackDamage(GetTarget(), EQ::skills::SkillBash, damage, 0, hate_override, reuse_time); + + if (reuse_time) { + p_timers.Start(timer, reuse_time); + } } + return; } if (ca_atk->m_atk == 100 && ca_atk->m_skill == EQ::skills::SkillFrenzy) { + int attack_rounds = 1; + int max_dmg = GetBaseSkillDamage(EQ::skills::SkillFrenzy, GetTarget()); + CheckIncreaseSkill(EQ::skills::SkillFrenzy, GetTarget(), 10); - int AtkRounds = 1; - int32 max_dmg = GetBaseSkillDamage(EQ::skills::SkillFrenzy, GetTarget()); DoAnim(anim1HWeapon, 0, false); if (GetClass() == BERSERKER) { int chance = GetLevel() * 2 + GetSkill(EQ::skills::SkillFrenzy); - if (zone->random.Roll0(450) < chance) - AtkRounds++; - if (zone->random.Roll0(450) < chance) - AtkRounds++; + + if (zone->random.Roll0(450) < chance) { + attack_rounds++; + } + + if (zone->random.Roll0(450) < chance) { + attack_rounds++; + } } - ReuseTime = FrenzyReuseTime - 1 - skill_reduction; - ReuseTime = (ReuseTime * HasteMod) / 100; + reuse_time = FrenzyReuseTime - 1 - skill_reduction; + reuse_time = (reuse_time * haste_modifier) / 100; - auto primary_in_use = GetInv().GetItem(EQ::invslot::slotPrimary); + const EQ::ItemInstance* primary_in_use = GetInv().GetItem(EQ::invslot::slotPrimary); if (primary_in_use && GetWeaponDamage(GetTarget(), primary_in_use) <= 0) { max_dmg = DMG_INVULNERABLE; } - while (AtkRounds > 0) { - if (GetTarget()) - DoSpecialAttackDamage(GetTarget(), EQ::skills::SkillFrenzy, max_dmg, 0, max_dmg, ReuseTime); - AtkRounds--; + while (attack_rounds > 0) { + if (GetTarget()) { + DoSpecialAttackDamage(GetTarget(), EQ::skills::SkillFrenzy, max_dmg, 0, max_dmg, reuse_time); + } + + attack_rounds--; + } + + if (reuse_time) { + p_timers.Start(timer, reuse_time); } - if (ReuseTime > 0) - p_timers.Start(timer, ReuseTime); return; } - switch (GetClass()) { - case BERSERKER: - case WARRIOR: - case RANGER: - case BEASTLORD: - if (ca_atk->m_atk != 100 || ca_atk->m_skill != EQ::skills::SkillKick) - break; + const uint8 class_id = GetClass(); + + // Warrior, Ranger, Monk, Beastlord, and Berserker can kick always + const uint32 allowed_kick_classes = RuleI(Combat, ExtraAllowedKickClassesBitmask); + + const bool can_use_kick = ( + class_id == WARRIOR || + class_id == RANGER || + class_id == MONK || + class_id == BEASTLORD || + class_id == BERSERKER || + allowed_kick_classes & GetPlayerClassBit(class_id) + ); + + bool found_skill = false; + + if ( + ca_atk->m_atk == 100 && + ca_atk->m_skill == EQ::skills::SkillKick && + can_use_kick + ) { if (GetTarget() != this) { CheckIncreaseSkill(EQ::skills::SkillKick, GetTarget(), 10); DoAnim(animKick, 0, false); - int32 ht = 0; - if (GetWeaponDamage(GetTarget(), GetInv().GetItem(EQ::invslot::slotFeet)) <= 0) - dmg = -5; - else - ht = dmg = GetBaseSkillDamage(EQ::skills::SkillKick, GetTarget()); + int hate_override = 0; + if (GetWeaponDamage(GetTarget(), GetInv().GetItem(EQ::invslot::slotFeet)) <= 0) { + damage = -5; + } else { + hate_override = damage = GetBaseSkillDamage(EQ::skills::SkillKick, GetTarget()); + } - ReuseTime = KickReuseTime - 1 - skill_reduction; - DoSpecialAttackDamage(GetTarget(), EQ::skills::SkillKick, dmg, 0, ht, ReuseTime); + reuse_time = KickReuseTime - 1 - skill_reduction; + DoSpecialAttackDamage(GetTarget(), EQ::skills::SkillKick, damage, 0, hate_override, reuse_time); + + found_skill = true; } - break; - case MONK: { - ReuseTime = MonkSpecialAttack(GetTarget(), ca_atk->m_skill) - 1 - skill_reduction; + } + + if (class_id == MONK) { + reuse_time = MonkSpecialAttack(GetTarget(), ca_atk->m_skill) - 1 - skill_reduction; // Live AA - Technique of Master Wu - int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack; + int wu_chance = ( + itembonuses.DoubleSpecialAttack + + spellbonuses.DoubleSpecialAttack + + aabonuses.DoubleSpecialAttack + ); - if (wuchance) { - const int MonkSPA[5] = { + if (wu_chance) { + const int monk_special_attacks[5] = { EQ::skills::SkillFlyingKick, EQ::skills::SkillDragonPunch, EQ::skills::SkillEagleStrike, EQ::skills::SkillTigerClaw, EQ::skills::SkillRoundKick }; + int extra = 0; // always 1/4 of the double attack chance, 25% at rank 5 (100/4) - while (wuchance > 0) { - if (zone->random.Roll(wuchance)) { + while (wu_chance > 0) { + if (zone->random.Roll(wu_chance)) { ++extra; - } - else { + } else { break; } - wuchance /= 4; + + wu_chance /= 4; } + if (extra) { SendColoredText( 400, @@ -474,37 +535,47 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) ) ); } - auto classic = RuleB(Combat, ClassicMasterWu); + + const bool is_classic_master_wu = RuleB(Combat, ClassicMasterWu); while (extra) { - MonkSpecialAttack(GetTarget(), (classic ? MonkSPA[zone->random.Int(0, 4)] : ca_atk->m_skill)); + MonkSpecialAttack( + GetTarget(), + (is_classic_master_wu ? monk_special_attacks[zone->random.Int(0, 4)] : ca_atk->m_skill) + ); --extra; } } - if (ReuseTime < 100) { + if (reuse_time < 100) { // hackish... but we return a huge reuse time if this is an // invalid skill, otherwise, we can safely assume it is a // valid monk skill and just cast it to a SkillType - CheckIncreaseSkill((EQ::skills::SkillType)ca_atk->m_skill, GetTarget(), 10); + CheckIncreaseSkill((EQ::skills::SkillType) ca_atk->m_skill, GetTarget(), 10); } - break; - } - case ROGUE: { - if (ca_atk->m_atk != 100 || ca_atk->m_skill != EQ::skills::SkillBackstab) - break; - ReuseTime = BackstabReuseTime-1 - skill_reduction; - TryBackstab(GetTarget(), ReuseTime); - break; - } - default: - //they have no abilities... wtf? make em wait a bit - ReuseTime = 9 - skill_reduction; - break; + + found_skill = true; } - ReuseTime = (ReuseTime * HasteMod) / 100; - if (ReuseTime > 0) { - p_timers.Start(timer, ReuseTime); + if ( + ca_atk->m_atk == 100 && + ca_atk->m_skill == EQ::skills::SkillBackstab && + class_id == ROGUE + ) { + reuse_time = BackstabReuseTime - 1 - skill_reduction; + TryBackstab(GetTarget(), reuse_time); + found_skill = true; + } + + if (!found_skill) { + reuse_time = 9 - skill_reduction; + } + + reuse_time = (reuse_time * haste_modifier) / 100; + + reuse_time = EQ::Clamp(reuse_time, 0, reuse_time); + + if (reuse_time) { + p_timers.Start(timer, reuse_time); } }