[Feature] Add Extra Kick Classes (#3613)

* [Feature] Add Extra Kick Classes

# Notes
- Allows operators to add extra classes to the "Kick" skill.
- Without this only Warrior, Ranger, Monk, Beastlord, and Berserker could kick.

* Remove gotos.
This commit is contained in:
Alex King 2023-10-11 14:33:23 -04:00 committed by GitHub
parent 4fc3c27715
commit 833fa55fdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 179 additions and 107 deletions

View File

@ -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, 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, 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_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_END()
RULE_CATEGORY(NPC) RULE_CATEGORY(NPC)

View File

@ -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 // We should probably refactor this to take the struct not the packet
void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk)
{ {
if (!GetTarget()) if (!GetTarget()) {
return; return;
}
// make sure were actually able to use such an attack. (Bards can throw while casting. ~Kayen confirmed on live 1/22) // 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; return;
}
pTimerType timer = pTimerCombatAbility; pTimerType timer = pTimerCombatAbility;
// RoF2+ Tiger Claw is unlinked from other monk skills, if they ever do that for other classes there will need // 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 // 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; timer = pTimerCombatAbility2;
}
bool CanBypassSkillCheck = false; bool bypass_skill_check = false;
if (ca_atk->m_skill == EQ::skills::SkillBash) { // SLAM - Bash without a shield equipped if (ca_atk->m_skill == EQ::skills::SkillBash) { // SLAM - Bash without a shield equipped
switch (GetRace()) switch (GetRace()) {
{
case OGRE: case OGRE:
case TROLL: case TROLL:
case BARBARIAN: case BARBARIAN:
CanBypassSkillCheck = true; bypass_skill_check = true;
default: default:
break; break;
} }
} }
/* Check to see if actually have skill */ // Check to see if actually have skill
if (!MaxSkill(static_cast<EQ::skills::SkillType>(ca_atk->m_skill)) && !CanBypassSkillCheck) if (!MaxSkill(static_cast<EQ::skills::SkillType>(ca_atk->m_skill)) && !bypass_skill_check) {
return; return;
}
if (GetTarget()->GetID() != ca_atk->m_target) if (GetTarget()->GetID() != ca_atk->m_target) { // invalid packet.
return; // invalid packet.
if (!IsAttackAllowed(GetTarget()))
return; return;
}
if (!IsAttackAllowed(GetTarget())) {
return;
}
// These two are not subject to the combat ability timer, as they // These two are not subject to the combat ability timer, as they
// allready do their checking in conjunction with the attack timer // 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) { if (ca_atk->m_skill == EQ::skills::SkillThrowing) {
SetAttackTimer(); SetAttackTimer();
ThrowingAttack(GetTarget()); ThrowingAttack(GetTarget());
if (CheckDoubleRangedAttack())
if (CheckDoubleRangedAttack()) {
ThrowingAttack(GetTarget(), true); ThrowingAttack(GetTarget(), true);
}
return; return;
} }
// ranged attack (archery) // ranged attack (archery)
if (ca_atk->m_skill == EQ::skills::SkillArchery) { if (ca_atk->m_skill == EQ::skills::SkillArchery) {
SetAttackTimer(); SetAttackTimer();
RangedAttack(GetTarget()); RangedAttack(GetTarget());
if (CheckDoubleRangedAttack())
if (CheckDoubleRangedAttack()) {
RangedAttack(GetTarget(), true); RangedAttack(GetTarget(), true);
}
return; 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 // check range for all these abilities, they are all close combat stuff
if (!CombatRange(GetTarget())) if (!CombatRange(GetTarget())) {
return; return;
}
if (!p_timers.Expired(&database, timer, false)) { if (!p_timers.Expired(&database, timer, false)) {
Message(Chat::Red, "Ability recovery time not yet met."); Message(Chat::Red, "Ability recovery time not yet met.");
return; return;
} }
int ReuseTime = 0; int reuse_time = 0;
int ClientHaste = GetHaste(); int haste = GetHaste();
int HasteMod = 0; int haste_modifier = 0;
if (ClientHaste >= 0) if (haste >= 0) {
HasteMod = (10000 / (100 + ClientHaste)); //+100% haste = 2x as many attacks haste_modifier = (10000 / (100 + haste)); //+100% haste = 2x as many attacks
else } else {
HasteMod = (100 - ClientHaste); //-100% haste = 1/2 as many attacks 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 (
// not sure what the '100' indicates..if ->m_atk is not used as 'slot' reference, then change SlotRange above back to '11' ca_atk->m_atk == 100 &&
if (ca_atk->m_atk == 100 && ca_atk->m_skill == EQ::skills::SkillBash
ca_atk->m_skill == EQ::skills::SkillBash) { // SLAM - Bash without a shield equipped ) { // SLAM - Bash without a shield equipped
if (GetTarget() != this) { if (GetTarget() != this) {
CheckIncreaseSkill(EQ::skills::SkillBash, GetTarget(), 10); CheckIncreaseSkill(EQ::skills::SkillBash, GetTarget(), 10);
DoAnim(animTailRake, 0, false); DoAnim(animTailRake, 0, false);
int32 ht = 0; int hate_override = 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());
ReuseTime = BashReuseTime - 1 - skill_reduction; if (
ReuseTime = (ReuseTime * HasteMod) / 100; GetWeaponDamage(GetTarget(), GetInv().GetItem(EQ::invslot::slotSecondary)) <= 0 &&
DoSpecialAttackDamage(GetTarget(), EQ::skills::SkillBash, dmg, 0, ht, ReuseTime); GetWeaponDamage(GetTarget(), GetInv().GetItem(EQ::invslot::slotShoulders)) <= 0
if (ReuseTime > 0) ) {
p_timers.Start(timer, ReuseTime); 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; return;
} }
if (ca_atk->m_atk == 100 && ca_atk->m_skill == EQ::skills::SkillFrenzy) { 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); CheckIncreaseSkill(EQ::skills::SkillFrenzy, GetTarget(), 10);
int AtkRounds = 1;
int32 max_dmg = GetBaseSkillDamage(EQ::skills::SkillFrenzy, GetTarget());
DoAnim(anim1HWeapon, 0, false); DoAnim(anim1HWeapon, 0, false);
if (GetClass() == BERSERKER) { if (GetClass() == BERSERKER) {
int chance = GetLevel() * 2 + GetSkill(EQ::skills::SkillFrenzy); int chance = GetLevel() * 2 + GetSkill(EQ::skills::SkillFrenzy);
if (zone->random.Roll0(450) < chance)
AtkRounds++; if (zone->random.Roll0(450) < chance) {
if (zone->random.Roll0(450) < chance) attack_rounds++;
AtkRounds++;
} }
ReuseTime = FrenzyReuseTime - 1 - skill_reduction; if (zone->random.Roll0(450) < chance) {
ReuseTime = (ReuseTime * HasteMod) / 100; attack_rounds++;
}
}
auto primary_in_use = GetInv().GetItem(EQ::invslot::slotPrimary); reuse_time = FrenzyReuseTime - 1 - skill_reduction;
reuse_time = (reuse_time * haste_modifier) / 100;
const EQ::ItemInstance* primary_in_use = GetInv().GetItem(EQ::invslot::slotPrimary);
if (primary_in_use && GetWeaponDamage(GetTarget(), primary_in_use) <= 0) { if (primary_in_use && GetWeaponDamage(GetTarget(), primary_in_use) <= 0) {
max_dmg = DMG_INVULNERABLE; max_dmg = DMG_INVULNERABLE;
} }
while (AtkRounds > 0) { while (attack_rounds > 0) {
if (GetTarget()) if (GetTarget()) {
DoSpecialAttackDamage(GetTarget(), EQ::skills::SkillFrenzy, max_dmg, 0, max_dmg, ReuseTime); DoSpecialAttackDamage(GetTarget(), EQ::skills::SkillFrenzy, max_dmg, 0, max_dmg, reuse_time);
AtkRounds--; }
attack_rounds--;
}
if (reuse_time) {
p_timers.Start(timer, reuse_time);
} }
if (ReuseTime > 0)
p_timers.Start(timer, ReuseTime);
return; return;
} }
switch (GetClass()) { const uint8 class_id = GetClass();
case BERSERKER:
case WARRIOR: // Warrior, Ranger, Monk, Beastlord, and Berserker can kick always
case RANGER: const uint32 allowed_kick_classes = RuleI(Combat, ExtraAllowedKickClassesBitmask);
case BEASTLORD:
if (ca_atk->m_atk != 100 || ca_atk->m_skill != EQ::skills::SkillKick) const bool can_use_kick = (
break; 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) { if (GetTarget() != this) {
CheckIncreaseSkill(EQ::skills::SkillKick, GetTarget(), 10); CheckIncreaseSkill(EQ::skills::SkillKick, GetTarget(), 10);
DoAnim(animKick, 0, false); DoAnim(animKick, 0, false);
int32 ht = 0; int hate_override = 0;
if (GetWeaponDamage(GetTarget(), GetInv().GetItem(EQ::invslot::slotFeet)) <= 0) if (GetWeaponDamage(GetTarget(), GetInv().GetItem(EQ::invslot::slotFeet)) <= 0) {
dmg = -5; damage = -5;
else } else {
ht = dmg = GetBaseSkillDamage(EQ::skills::SkillKick, GetTarget()); hate_override = damage = GetBaseSkillDamage(EQ::skills::SkillKick, GetTarget());
ReuseTime = KickReuseTime - 1 - skill_reduction;
DoSpecialAttackDamage(GetTarget(), EQ::skills::SkillKick, dmg, 0, ht, ReuseTime);
} }
break;
case MONK: { reuse_time = KickReuseTime - 1 - skill_reduction;
ReuseTime = MonkSpecialAttack(GetTarget(), ca_atk->m_skill) - 1 - skill_reduction; DoSpecialAttackDamage(GetTarget(), EQ::skills::SkillKick, damage, 0, hate_override, reuse_time);
found_skill = true;
}
}
if (class_id == MONK) {
reuse_time = MonkSpecialAttack(GetTarget(), ca_atk->m_skill) - 1 - skill_reduction;
// Live AA - Technique of Master Wu // 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) { if (wu_chance) {
const int MonkSPA[5] = { const int monk_special_attacks[5] = {
EQ::skills::SkillFlyingKick, EQ::skills::SkillFlyingKick,
EQ::skills::SkillDragonPunch, EQ::skills::SkillDragonPunch,
EQ::skills::SkillEagleStrike, EQ::skills::SkillEagleStrike,
EQ::skills::SkillTigerClaw, EQ::skills::SkillTigerClaw,
EQ::skills::SkillRoundKick EQ::skills::SkillRoundKick
}; };
int extra = 0; int extra = 0;
// always 1/4 of the double attack chance, 25% at rank 5 (100/4) // always 1/4 of the double attack chance, 25% at rank 5 (100/4)
while (wuchance > 0) { while (wu_chance > 0) {
if (zone->random.Roll(wuchance)) { if (zone->random.Roll(wu_chance)) {
++extra; ++extra;
} } else {
else {
break; break;
} }
wuchance /= 4;
wu_chance /= 4;
} }
if (extra) { if (extra) {
SendColoredText( SendColoredText(
400, 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) { 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; --extra;
} }
} }
if (ReuseTime < 100) { if (reuse_time < 100) {
// hackish... but we return a huge reuse time if this is an // hackish... but we return a huge reuse time if this is an
// invalid skill, otherwise, we can safely assume it is a // invalid skill, otherwise, we can safely assume it is a
// valid monk skill and just cast it to a SkillType // 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;
} }
ReuseTime = (ReuseTime * HasteMod) / 100; found_skill = true;
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);
} }
} }