diff --git a/zone/bot.cpp b/zone/bot.cpp
index 5579b4bc5..6d14ce740 100644
--- a/zone/bot.cpp
+++ b/zone/bot.cpp
@@ -6421,32 +6421,52 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
bool taunt_time = taunt_timer.Check();
bool ca_time = classattack_timer.Check(false);
+ bool ma_time = monkattack_timer.Check(false);
bool ka_time = knightattack_timer.Check(false);
- if((taunt_time || ca_time || ka_time) && !IsAttackAllowed(target))
+
+ if (taunt_time) {
+
+ // Bots without this skill shouldn't be 'checking' on this timer..let's just disable it and avoid the extra IsAttackAllowed() checks
+ // Note: this is done here instead of NPC::ctor() because taunt skill can be acquired during level ups (the timer is re-enabled in CalcBotStats())
+ if (!GetSkill(EQEmu::skills::SkillTaunt)) {
+
+ taunt_timer.Disable();
+ return;
+ }
+
+ if (!IsAttackAllowed(target)) {
+ return;
+ }
+ }
+
+ if ((ca_time || ma_time || ka_time) && !IsAttackAllowed(target)) {
return;
+ }
if(ka_time){
- int knightreuse = 1000;
+
switch(GetClass()){
- case SHADOWKNIGHT:
- case SHADOWKNIGHTGM: {
+ case SHADOWKNIGHT: {
CastSpell(SPELL_NPC_HARM_TOUCH, target->GetID());
- knightreuse = (HarmTouchReuseTime * 1000);
+ knightattack_timer.Start(HarmTouchReuseTime * 1000);
+
break;
}
- case PALADIN:
- case PALADINGM: {
+ case PALADIN: {
if(GetHPRatio() < 20) {
CastSpell(SPELL_LAY_ON_HANDS, GetID());
- knightreuse = (LayOnHandsReuseTime * 1000);
+ knightattack_timer.Start(LayOnHandsReuseTime * 1000);
+ }
+ else {
+ knightattack_timer.Start(2000);
}
- else
- knightreuse = 2000;
break;
}
+ default: {
+ break;
+ }
}
- knightattack_timer.Start(knightreuse);
}
if(taunting && target && target->IsNPC() && taunt_time) {
@@ -6457,8 +6477,66 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
}
}
- if(!ca_time)
+ if (ma_time) {
+ switch (GetClass()) {
+ case MONK: {
+ int reuse = (MonkSpecialAttack(target, EQEmu::skills::SkillTigerClaw) - 1);
+
+ // Live AA - Technique of Master Wu
+ int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack;
+
+ if (wuchance) {
+ const int MonkSPA[5] = {
+ EQEmu::skills::SkillFlyingKick,
+ EQEmu::skills::SkillDragonPunch,
+ EQEmu::skills::SkillEagleStrike,
+ EQEmu::skills::SkillTigerClaw,
+ EQEmu::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)) {
+ ++extra;
+ }
+ else {
+ break;
+ }
+ wuchance /= 4;
+ }
+
+ Mob* bo = GetBotOwner();
+ if (bo && bo->IsClient() && bo->CastToClient()->GetBotOption(Client::booMonkWuMessage)) {
+
+ bo->Message(
+ GENERIC_EMOTE,
+ "The spirit of Master Wu fills %s! %s gains %d additional attack(s).",
+ GetCleanName(),
+ GetCleanName(),
+ extra
+ );
+ }
+
+ auto classic = RuleB(Combat, ClassicMasterWu);
+ while (extra) {
+ MonkSpecialAttack(GetTarget(), (classic ? MonkSPA[zone->random.Int(0, 4)] : EQEmu::skills::SkillTigerClaw));
+ --extra;
+ }
+ }
+
+ float HasteModifier = (GetHaste() * 0.01f);
+ monkattack_timer.Start((reuse * 1000) / HasteModifier);
+
+ break;
+ }
+ default:
+ break;;
+ }
+ }
+
+ if (!ca_time) {
return;
+ }
float HasteModifier = (GetHaste() * 0.01f);
uint16 skill_to_use = -1;
@@ -6493,18 +6571,22 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
}
break;
case MONK:
- if(GetLevel() >= 30)
+ if (GetLevel() >= 30) {
skill_to_use = EQEmu::skills::SkillFlyingKick;
- else if(GetLevel() >= 25)
+ }
+ else if (GetLevel() >= 25) {
skill_to_use = EQEmu::skills::SkillDragonPunch;
- else if(GetLevel() >= 20)
+ }
+ else if (GetLevel() >= 20) {
skill_to_use = EQEmu::skills::SkillEagleStrike;
- else if(GetLevel() >= 10)
- skill_to_use = EQEmu::skills::SkillTigerClaw;
- else if(GetLevel() >= 5)
+ }
+ else if (GetLevel() >= 5) {
skill_to_use = EQEmu::skills::SkillRoundKick;
- else
+ }
+ else {
skill_to_use = EQEmu::skills::SkillKick;
+ }
+
break;
case ROGUE:
skill_to_use = EQEmu::skills::SkillBackstab;
@@ -6555,19 +6637,54 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
}
}
- if (skill_to_use == EQEmu::skills::SkillFlyingKick || skill_to_use == EQEmu::skills::SkillDragonPunch || skill_to_use == EQEmu::skills::SkillEagleStrike || skill_to_use == EQEmu::skills::SkillTigerClaw || skill_to_use == EQEmu::skills::SkillRoundKick) {
+ if (
+ skill_to_use == EQEmu::skills::SkillFlyingKick ||
+ skill_to_use == EQEmu::skills::SkillDragonPunch ||
+ skill_to_use == EQEmu::skills::SkillEagleStrike ||
+ skill_to_use == EQEmu::skills::SkillRoundKick
+ ) {
reuse = (MonkSpecialAttack(target, skill_to_use) - 1);
- MonkSpecialAttack(target, skill_to_use);
- uint32 bDoubleSpecialAttack = (itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack);
- if(bDoubleSpecialAttack && (bDoubleSpecialAttack >= 100 || bDoubleSpecialAttack > zone->random.Int(0, 100))) {
- int MonkSPA[5] = { EQEmu::skills::SkillFlyingKick, EQEmu::skills::SkillDragonPunch, EQEmu::skills::SkillEagleStrike, EQEmu::skills::SkillTigerClaw, EQEmu::skills::SkillRoundKick };
- MonkSpecialAttack(target, MonkSPA[zone->random.Int(0, 4)]);
- int TripleChance = 25;
- if (bDoubleSpecialAttack > 100)
- TripleChance += (TripleChance * (100 - bDoubleSpecialAttack) / 100);
+
+ // Live AA - Technique of Master Wu
+ int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack;
- if(TripleChance > zone->random.Int(0,100))
- MonkSpecialAttack(target, MonkSPA[zone->random.Int(0, 4)]);
+ if (wuchance) {
+ const int MonkSPA[5] = {
+ EQEmu::skills::SkillFlyingKick,
+ EQEmu::skills::SkillDragonPunch,
+ EQEmu::skills::SkillEagleStrike,
+ EQEmu::skills::SkillTigerClaw,
+ EQEmu::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)) {
+ ++extra;
+ }
+ else {
+ break;
+ }
+ wuchance /= 4;
+ }
+
+ Mob* bo = GetBotOwner();
+ if (bo && bo->IsClient() && bo->CastToClient()->GetBotOption(Client::booMonkWuMessage)) {
+
+ bo->Message(
+ GENERIC_EMOTE,
+ "The spirit of Master Wu fills %s! %s gains %d additional attack(s).",
+ GetCleanName(),
+ GetCleanName(),
+ extra
+ );
+ }
+
+ auto classic = RuleB(Combat, ClassicMasterWu);
+ while (extra) {
+ MonkSpecialAttack(GetTarget(), (classic ? MonkSPA[zone->random.Int(0, 4)] : skill_to_use));
+ --extra;
+ }
}
reuse *= 1000;
@@ -8966,6 +9083,12 @@ void Bot::CalcBotStats(bool showtext) {
skills[sindex] = database.GetSkillCap(GetClass(), (EQEmu::skills::SkillType)sindex, GetLevel());
}
+ taunt_timer.Start(1000);
+
+ if (GetClass() == MONK && GetLevel() >= 10) {
+ monkattack_timer.Start(1000);
+ }
+
LoadAAs();
GenerateSpecialAttacks();
diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp
index dc9d40aab..9c202fed8 100644
--- a/zone/bot_command.cpp
+++ b/zone/bot_command.cpp
@@ -3928,6 +3928,16 @@ void bot_command_owner_option(Client *c, const Seperator *sep)
"
null | "
"(toggles) | "
""
+ ""
+ "| monkwumessage | "
+ "enable | disable | "
+ "displays monk wu trigger messages | "
+ "
"
+ ""
+ " | "
+ "null | "
+ "(toggles) | "
+ "
"
""
"| current | "
" | "
@@ -4103,6 +4113,22 @@ void bot_command_owner_option(Client *c, const Seperator *sep)
c->Message(m_action, "Bot 'buff counter' is now %s.", (c->GetBotOption(Client::booBuffCounter) == true ? "enabled" : "disabled"));
}
+ else if (!owner_option.compare("monkwumessage")) {
+
+ if (!argument.compare("enable")) {
+ c->SetBotOption(Client::booMonkWuMessage, true);
+ }
+ else if (!argument.compare("disable")) {
+ c->SetBotOption(Client::booMonkWuMessage, false);
+ }
+ else {
+ c->SetBotOption(Client::booMonkWuMessage, !c->GetBotOption(Client::booMonkWuMessage));
+ }
+
+ database.botdb.SaveOwnerOption(c->CharacterID(), Client::booMonkWuMessage, c->GetBotOption(Client::booMonkWuMessage));
+
+ c->Message(m_action, "Bot 'monk wu message' is now %s.", (c->GetBotOption(Client::booMonkWuMessage) == true ? "enabled" : "disabled"));
+ }
else if (!owner_option.compare("current")) {
std::string window_title = "Current Bot Owner Options Settings";
@@ -4112,13 +4138,14 @@ void bot_command_owner_option(Client *c, const Seperator *sep)
"Option ------ | "
"Argument ------- | "
"
"
- "" "| deathmarquee | " "{} | " "
"
- "" "| statsupdate | " "{} | " "
"
- "" "| spawnmessage | " "{} | " "
"
- "" "| spawnmessage | " "{} | " "
"
- "" "| altcombat | " "{} | " "
"
- "" "| autodefend | " "{} | " "
"
- "" "| buffcounter | " "{} | " "
"
+ "" "| deathmarquee | " "{} | " "
"
+ "" "| statsupdate | " "{} | " "
"
+ "" "| spawnmessage | " "{} | " "
"
+ "" "| spawnmessage | " "{} | " "
"
+ "" "| altcombat | " "{} | " "
"
+ "" "| autodefend | " "{} | " "
"
+ "" "| buffcounter | " "{} | " "
"
+ "" "| monkwumessage | " "{} | " "
"
"",
(c->GetBotOption(Client::booDeathMarquee) ? "enabled" : "disabled"),
(c->GetBotOption(Client::booStatsUpdate) ? "enabled" : "disabled"),
@@ -4126,7 +4153,8 @@ void bot_command_owner_option(Client *c, const Seperator *sep)
(c->GetBotOption(Client::booSpawnMessageClassSpecific) ? "class" : "default"),
(RuleB(Bots, AllowOwnerOptionAltCombat) ? (c->GetBotOption(Client::booAltCombat) ? "enabled" : "disabled") : "restricted"),
(RuleB(Bots, AllowOwnerOptionAutoDefend) ? (c->GetBotOption(Client::booAutoDefend) ? "enabled" : "disabled") : "restricted"),
- (c->GetBotOption(Client::booBuffCounter) ? "enabled" : "disabled")
+ (c->GetBotOption(Client::booBuffCounter) ? "enabled" : "disabled"),
+ (c->GetBotOption(Client::booMonkWuMessage) ? "enabled" : "disabled")
);
c->SendPopupToClient(window_title.c_str(), window_text.c_str());
diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp
index 2f6f9c1c6..28b5288e0 100644
--- a/zone/bot_database.cpp
+++ b/zone/bot_database.cpp
@@ -2257,6 +2257,7 @@ bool BotDatabase::SaveOwnerOption(const uint32 owner_id, size_t type, const bool
case Client::booAltCombat:
case Client::booAutoDefend:
case Client::booBuffCounter:
+ case Client::booMonkWuMessage:
{
query = fmt::format(
"REPLACE INTO `bot_owner_options`(`owner_id`, `option_type`, `option_value`) VALUES ('{}', '{}', '{}')",
diff --git a/zone/client.cpp b/zone/client.cpp
index 2398db500..6bd9941f8 100644
--- a/zone/client.cpp
+++ b/zone/client.cpp
@@ -358,6 +358,7 @@ Client::Client(EQStreamInterface* ieqs)
bot_owner_options[booAltCombat] = RuleB(Bots, AllowOwnerOptionAltCombat);
bot_owner_options[booAutoDefend] = RuleB(Bots, AllowOwnerOptionAutoDefend);
bot_owner_options[booBuffCounter] = false;
+ bot_owner_options[booMonkWuMessage] = false;
SetBotPulling(false);
SetBotPrecombat(false);
diff --git a/zone/client.h b/zone/client.h
index 52af842be..58b0d9af8 100644
--- a/zone/client.h
+++ b/zone/client.h
@@ -1646,6 +1646,7 @@ public:
booAltCombat,
booAutoDefend,
booBuffCounter,
+ booMonkWuMessage,
_booCount
};
diff --git a/zone/npc.cpp b/zone/npc.cpp
index 17e3c5957..0b22bab3a 100644
--- a/zone/npc.cpp
+++ b/zone/npc.cpp
@@ -118,6 +118,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
attacked_timer(CombatEventTimer_expire),
swarm_timer(100),
classattack_timer(1000),
+ monkattack_timer(1000),
knightattack_timer(1000),
assist_timer(AIassistcheck_delay),
qglobal_purge_timer(30000),
@@ -307,7 +308,15 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
// some overrides -- really we need to be able to set skills for mobs in the DB
// There are some known low level SHM/BST pets that do not follow this, which supports
// the theory of needing to be able to set skills for each mob separately
- if (!IsBot()) {
+ if (IsBot()) {
+ if (GetClass() != PALADIN && GetClass() != SHADOWKNIGHT) {
+ knightattack_timer.Disable();
+ }
+ else if (GetClass() != MONK || GetLevel() < 10) {
+ monkattack_timer.Disable();
+ }
+ }
+ else {
if (moblevel > 50) {
skills[EQEmu::skills::SkillDoubleAttack] = 250;
skills[EQEmu::skills::SkillDualWield] = 250;
@@ -3233,4 +3242,4 @@ void NPC::RecalculateSkills()
skills[EQEmu::skills::SkillDoubleAttack] = level * 5;
}
}
-}
\ No newline at end of file
+}
diff --git a/zone/npc.h b/zone/npc.h
index e545ffb38..2b8edc9b9 100644
--- a/zone/npc.h
+++ b/zone/npc.h
@@ -499,6 +499,7 @@ protected:
Timer attacked_timer; //running while we are being attacked (damaged)
Timer swarm_timer;
+ Timer monkattack_timer; //additional timer for tiger claw usage
Timer classattack_timer;
Timer knightattack_timer;
Timer assist_timer; //ask for help from nearby mobs
diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp
index 42ec94616..9334e4f6c 100644
--- a/zone/special_attacks.cpp
+++ b/zone/special_attacks.cpp
@@ -379,31 +379,35 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk)
ReuseTime = MonkSpecialAttack(GetTarget(), ca_atk->m_skill) - 1 - skill_reduction;
// Live AA - Technique of Master Wu
- int wuchance =
- itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack;
+ int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack;
+
if (wuchance) {
- const int MonkSPA[5] = {EQEmu::skills::SkillFlyingKick, EQEmu::skills::SkillDragonPunch,
- EQEmu::skills::SkillEagleStrike, EQEmu::skills::SkillTigerClaw,
- EQEmu::skills::SkillRoundKick};
+ const int MonkSPA[5] = {
+ EQEmu::skills::SkillFlyingKick,
+ EQEmu::skills::SkillDragonPunch,
+ EQEmu::skills::SkillEagleStrike,
+ EQEmu::skills::SkillTigerClaw,
+ EQEmu::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))
- extra++;
- else
+ if (zone->random.Roll(wuchance)) {
+ ++extra;
+ }
+ else {
break;
+ }
wuchance /= 4;
}
// They didn't add a string ID for this.
- std::string msg = StringFormat(
- "The spirit of Master Wu fills you! You gain %d additional attack(s).", extra);
+ std::string msg = StringFormat("The spirit of Master Wu fills you! You gain %d additional attack(s).", extra);
// live uses 400 here -- not sure if it's the best for all clients though
SendColoredText(400, msg);
auto classic = RuleB(Combat, ClassicMasterWu);
while (extra) {
- MonkSpecialAttack(GetTarget(),
- classic ? MonkSPA[zone->random.Int(0, 4)] : ca_atk->m_skill);
- extra--;
+ MonkSpecialAttack(GetTarget(), (classic ? MonkSPA[zone->random.Int(0, 4)] : ca_atk->m_skill));
+ --extra;
}
}