Added ElixirSpellCache, #elixircheck

This commit is contained in:
Xackery 2021-11-16 17:03:42 -08:00
parent c23fc4ade7
commit 3e8796bb4c
8 changed files with 3166 additions and 3013 deletions

View File

@ -8821,6 +8821,10 @@ void Bot::CalcBotStats(bool showtext) {
CalcBonuses(); CalcBonuses();
AI_AddNPCSpells(this->GetBotSpellID()); AI_AddNPCSpells(this->GetBotSpellID());
if (RuleB(Bot, IsBotsElixirEnabled)) {
isElixirSpellCacheBuilt = false;
ElixirSpellCacheRefresh();
}
if(showtext) { if(showtext) {
GetBotOwner()->Message(Chat::Yellow, "%s has been updated.", GetCleanName()); GetBotOwner()->Message(Chat::Yellow, "%s has been updated.", GetCleanName());

View File

@ -322,7 +322,7 @@ public:
void SetGuardMode(); void SetGuardMode();
void SetHoldMode(); void SetHoldMode();
bool ElixirAIDetermineSpellToCast(); bool ElixirAIDetermineSpellToCast();
bool ElixirAITryCastSpell(BotSpell botSpell, bool isHeal = false); bool ElixirAITryCastSpell(uint16 spellID, bool isHeal = false);
// Mob AI Virtual Override Methods // Mob AI Virtual Override Methods
virtual void AI_Process(); virtual void AI_Process();
@ -608,6 +608,7 @@ protected:
virtual int32 CalcBotAAFocus(focusType type, uint32 aa_ID, uint32 points, uint16 spell_id); virtual int32 CalcBotAAFocus(focusType type, uint32 aa_ID, uint32 points, uint16 spell_id);
virtual void PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* client); virtual void PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* client);
virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0); virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0);
virtual bool AIElixirDoSpellCast(uint16 spellID, Mob* tar, int32 mana_cost);
BotCastingRoles& GetCastingRoles() { return m_CastingRoles; } BotCastingRoles& GetCastingRoles() { return m_CastingRoles; }
void SetGroupHealer(bool flag = true) { m_CastingRoles.GroupHealer = flag; } void SetGroupHealer(bool flag = true) { m_CastingRoles.GroupHealer = flag; }
@ -657,6 +658,8 @@ private:
int32 max_end; int32 max_end;
int32 end_regen; int32 end_regen;
uint32 timers[MaxTimer]; uint32 timers[MaxTimer];
bool isElixirSpellCacheBuilt;
int elixirCacheSpells[15]{ -1,-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
Timer m_evade_timer; // can be moved to pTimers at some point Timer m_evade_timer; // can be moved to pTimers at some point
Timer m_alt_combat_hate_timer; Timer m_alt_combat_hate_timer;
@ -716,6 +719,7 @@ private:
void SetAttackingFlag(bool flag = true) { m_attacking_flag = flag; } void SetAttackingFlag(bool flag = true) { m_attacking_flag = flag; }
void SetPullingFlag(bool flag = true) { m_pulling_flag = flag; } void SetPullingFlag(bool flag = true) { m_pulling_flag = flag; }
void SetReturningFlag(bool flag = true) { m_returning_flag = flag; } void SetReturningFlag(bool flag = true) { m_returning_flag = flag; }
void ElixirSpellCacheRefresh();
// Private "Inventory" Methods // Private "Inventory" Methods
void GetBotItems(EQ::InventoryProfile &inv, std::string* errorMessage); void GetBotItems(EQ::InventoryProfile &inv, std::string* errorMessage);

View File

@ -89,7 +89,8 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
if (!addMob) { if (!addMob) {
//Say("!addMob."); //Say("!addMob.");
break;} break;
}
if (!(!addMob->IsImmuneToSpell(botSpell.SpellId, this) && addMob->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) if (!(!addMob->IsImmuneToSpell(botSpell.SpellId, this) && addMob->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))
break; break;
@ -1058,7 +1059,6 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore) { bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore) {
bool result = false; bool result = false;
// manacost has special values, -1 is no mana cost, -2 is instant cast (no mana) // manacost has special values, -1 is no mana cost, -2 is instant cast (no mana)
int32 manaCost = mana_cost; int32 manaCost = mana_cost;
@ -1070,6 +1070,8 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
int32 extraMana = 0; int32 extraMana = 0;
int32 hasMana = GetMana(); int32 hasMana = GetMana();
Log(Logs::General, Logs::Mercenaries, "Attempting to cast %s on %s", spells[AIspells[i].spellid].name, tar == nullptr ? "no target" : tar->GetCleanName());
// Allow bots to cast buff spells even if they are out of mana // Allow bots to cast buff spells even if they are out of mana
if (RuleB(Bots, FinishBuffing)) { if (RuleB(Bots, FinishBuffing)) {
if (manaCost > hasMana) { if (manaCost > hasMana) {
@ -1085,7 +1087,8 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
if (AIspells[i].type & SpellType_Escape) { if (AIspells[i].type & SpellType_Escape) {
dist2 = 0; dist2 = 0;
} else }
else
dist2 = DistanceSquared(m_Position, tar->GetPosition()); dist2 = DistanceSquared(m_Position, tar->GetPosition());
if (((((spells[AIspells[i].spellid].target_type == ST_GroupTeleport && AIspells[i].type == 2) if (((((spells[AIspells[i].spellid].target_type == ST_GroupTeleport && AIspells[i].type == 2)
@ -1132,15 +1135,15 @@ bool Bot::AI_PursueCastCheck() {
if (AIautocastspell_timer->Check(false)) { if (AIautocastspell_timer->Check(false)) {
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.
if (RuleB(Bots, IsBotsElixirEnabled)) { if (RuleB(Bots, IsBotsElixirEnabled)) {
AIautocastspell_timer->Start(3000, false); // avg human response is much less than 5 seconds..even for non-combat situations...
if (ElixirAIDetermineSpellToCast()) { if (ElixirAIDetermineSpellToCast()) {
AIautocastspell_timer->Start(RandomTimer(500, 2000), false); // avg human response is much less than 5 seconds..even for non-combat situations...
return true; return true;
} }
return false; return false;
} }
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.
LogAI("Bot Engaged (pursuing) autocast check triggered. Trying to cast offensive spells"); LogAI("Bot Engaged (pursuing) autocast check triggered. Trying to cast offensive spells");
@ -1172,15 +1175,16 @@ bool Bot::AI_IdleCastCheck() {
#if BotAI_DEBUG_Spells >= 25 #if BotAI_DEBUG_Spells >= 25
LogAI("Bot Non-Engaged autocast check triggered: [{}]", this->GetCleanName()); LogAI("Bot Non-Engaged autocast check triggered: [{}]", this->GetCleanName());
#endif #endif
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.
if (RuleB(Bots, IsBotsElixirEnabled)) { if (RuleB(Bots, IsBotsElixirEnabled)) {
AIautocastspell_timer->Start(3000, false); // avg human response is much less than 5 seconds..even for non-combat situations...
if (ElixirAIDetermineSpellToCast()) { if (ElixirAIDetermineSpellToCast()) {
AIautocastspell_timer->Start(RandomTimer(500, 2000), false); // avg human response is much less than 5 seconds..even for non-combat situations...
return true; return true;
} }
return false; return false;
} }
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.
bool pre_combat = false; bool pre_combat = false;
Client* test_against = nullptr; Client* test_against = nullptr;
@ -1323,14 +1327,14 @@ bool Bot::AI_EngagedCastCheck() {
if (GetTarget() && AIautocastspell_timer->Check(false)) { if (GetTarget() && AIautocastspell_timer->Check(false)) {
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.
if (RuleB(Bots, IsBotsElixirEnabled)) { if (RuleB(Bots, IsBotsElixirEnabled)) {
AIautocastspell_timer->Start(3000, false); // avg human response is much less than 5 seconds..even for non-combat situations...
if (ElixirAIDetermineSpellToCast()) { if (ElixirAIDetermineSpellToCast()) {
AIautocastspell_timer->Start(RandomTimer(500, 2000), false); // avg human response is much less than 5 seconds..even for non-combat situations...
return true; return true;
} }
return false; return false;
} }
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.
uint8 botClass = GetClass(); uint8 botClass = GetClass();
EQ::constants::StanceType botStance = GetBotStance(); EQ::constants::StanceType botStance = GetBotStance();
@ -2702,194 +2706,135 @@ uint8 Bot::GetChanceToCastBySpellType(uint32 spellType)
// ElixirAIDetermineSpellToCast is called during AI bot logics // ElixirAIDetermineSpellToCast is called during AI bot logics
// It determines by class which spell to cast // It determines by class which spell to cast
bool Bot::ElixirAIDetermineSpellToCast() { bool Bot::ElixirAIDetermineSpellToCast() {
BotSpell selectedBotSpell;
int8 spellAIResult; int8 spellAIResult;
Group* grp = GetGroup(); Group* grp = GetGroup();
if (GetClass() == WARRIOR || GetClass() == SHADOWKNIGHT || GetClass() == PALADIN) { ElixirSpellCacheRefresh();
/*if(CheckAETaunt()) { BotSpell botSpell{};
selectedBotSpell = GetBestBotSpellForAETaunt(this); for (int i = 0; i < 15; i++) {
if (selectedBotSpell.SpellId > 0 && ElixirAITryCastSpell(selectedBotSpell)) { if (elixirCacheSpells[i] == -1) continue;
Log(Logs::General, Logs::Botenaries, "%s AE Taunting.", GetName());
if (ElixirAITryCastSpell(elixirCacheSpells[i], true)) {
return true; return true;
} }
} }
return false;
}
if(CheckTaunt()) { void Bot::ElixirSpellCacheRefresh() {
selectedBotSpell = GetBestBotSpellForTaunt(this); if (isElixirSpellCacheBuilt) return;
isElixirSpellCacheBuilt = true;
for (int i = 0; i < 15; i++) {
elixirCacheSpells[i] = -1;
}
// 0 is group heal all classes
// 1 ae nukes all classes
// 2 evade spells
// 3 slows all classes, hot for dru/clr/shm
//
// 4 shm/clr/dru/pal single target heal
// 5 nukes all classes
// 6 all non-priests single target heal
// 7 hot for everyone
// 10 hp buffs
// 11 ac buffs
// 12 haste buffs
// 13
for (int spellID = 1; spellID < SPDAT_RECORDS; spellID++) {
int spellSlot = -1;
if (GetSpellLevel(spellID, GetClass()) <= 0) continue;
if (GetSpellLevel(spellID, GetClass()) > GetLevel()) continue;
if (spellID == 13) {
if (true) {
}
}
if (spells[spellID].target_type == ST_Target && IsEffectInSpell(spellID, SE_CurrentHP) && spellSlot == -1 && spells[spellID].base_value > 0) {
if (GetClass() == CLERIC || GetClass() == DRUID || GetClass() == SHAMAN || GetClass() == PALADIN) spellSlot = 4;
else spellSlot = 6;
}
if (IsEffectInSpell(spellID, SE_HealOverTime) && spellSlot == -1) {
if (GetClass() == CLERIC || GetClass() == DRUID || GetClass() == SHAMAN || GetClass() == PALADIN) spellSlot = 3;
else spellSlot = 7;
}
if (IsRegularGroupHealSpell(spellID) && spellSlot == -1) {
if (GetClass() == CLERIC || GetClass() == DRUID || GetClass() == SHAMAN || GetClass() == PALADIN) spellSlot = 0;
else spellSlot = 0;
}
if (IsAENukeSpell(spellID) && spellSlot == -1) {
spellSlot = 1;
}
if (IsSlowSpell(spellID) && spellSlot == -1) {
spellSlot = 3;
}
if (IsPureNukeSpell(spellID) && spellSlot == -1) {
if (GetClass() == CLERIC || GetClass() == DRUID || GetClass() == SHAMAN || GetClass() == PALADIN) {
if (GetManaRatio() > 90) {
spellSlot = 5;
}
}
else {
spellSlot = 5;
}
}
/*
//TODO: escape spell slot 2
if (GetTarget() && HasOrMayGetAggro()) {
selectedBotSpell = GetFirstBotSpellBySpellType(this, SpellType_Escape);
if (selectedBotSpell.SpellId > 0 && ElixirAITryCastSpell(selectedBotSpell)) { if (selectedBotSpell.SpellId > 0 && ElixirAITryCastSpell(selectedBotSpell)) {
return true; return true;
} }
} }
*/ */
if (IsBuffSpell(spellID) && spellSlot == -1) {
spellSlot = 10;
}
if (IsHasteSpell(spellID) && spellSlot == -1) {
spellSlot = 12;
}
if (spellSlot == -1) continue;
if (elixirCacheSpells[spellSlot] == -1) {
elixirCacheSpells[spellSlot] = spellID;
continue;
} }
switch (GetClass()) { if (GetSpellLevel(spellID, GetClass()) < GetSpellLevel(elixirCacheSpells[spellSlot], GetClass())) {
case CLERIC: continue;
case PALADIN:
case RANGER:
selectedBotSpell = GetBestBotSpellForGroupHeal(this);
if (ElixirAITryCastSpell(selectedBotSpell, true)) {
return true;
} }
elixirCacheSpells[spellSlot] = spellID;
selectedBotSpell = GetBestBotSpellForHealOverTime(this);
if (ElixirAITryCastSpell(selectedBotSpell, true)) {
return true;
} }
selectedBotSpell = GetBestBotSpellForFastHeal(this);
if (ElixirAITryCastSpell(selectedBotSpell, true)) {
return true;
}
selectedBotSpell = GetBestBotSpellForRegularSingleTargetHeal(this);
if (ElixirAITryCastSpell(selectedBotSpell, true)) {
return true;
}
for (int i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (!grp) break;
if (!grp->members[i]) continue;
if (!grp->members[i]->qglobal) continue;
if(!GetNeedsCured(grp->members[i])) continue;
if (grp->members[i]->DontCureMeBefore() > Timer::GetCurrentTime()) continue;
selectedBotSpell = GetBestBotSpellForCure(this, grp->members[i]);
if (ElixirAITryCastSpell(selectedBotSpell, true)) {
return true;
}
}
if (GetManaRatio() > 50) { // healers only offensive or buff at > 50% mana
selectedBotSpell = GetBestBotSpellForNukeByTargetType(this, ST_Target);
if (ElixirAITryCastSpell(selectedBotSpell)) {
return true;
}
auto buffSpells = GetBotSpellsBySpellType(this, SpellType_Buff);
for (auto buffSpell : buffSpells) {
if (!ElixirAITryCastSpell(selectedBotSpell)) continue;
return true;
}
}
return false;
// Pets class will first cast their pet, then buffs
case DRUID:
case MAGICIAN:
case SHADOWKNIGHT:
case SHAMAN:
case NECROMANCER:
case ENCHANTER:
case BEASTLORD:
selectedBotSpell = GetBestBotSpellForGroupHeal(this);
if (ElixirAITryCastSpell(selectedBotSpell, true)) {
return true;
}
selectedBotSpell = GetBestBotSpellForHealOverTime(this);
if (ElixirAITryCastSpell(selectedBotSpell, true)) {
return true;
}
selectedBotSpell = GetBestBotSpellForFastHeal(this);
if (ElixirAITryCastSpell(selectedBotSpell, true)) {
return true;
}
selectedBotSpell = GetBestBotSpellForRegularSingleTargetHeal(this);
if (ElixirAITryCastSpell(selectedBotSpell, true)) {
return true;
}
for (int i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (!grp) break;
if (!grp->members[i]) continue;
if (!grp->members[i]->qglobal) continue;
if(!GetNeedsCured(grp->members[i])) continue;
if (grp->members[i]->DontCureMeBefore() > Timer::GetCurrentTime()) continue;
selectedBotSpell = GetBestBotSpellForCure(this, grp->members[i]);
if (ElixirAITryCastSpell(selectedBotSpell, true)) {
return true;
}
}
if (GetManaRatio() > 50) { // healers only offensive or buff at > 50% mana
selectedBotSpell = GetBestBotSpellForNukeByTargetType(this, ST_Target);
if (ElixirAITryCastSpell(selectedBotSpell)) {
return true;
}
auto buffSpells = GetBotSpellsBySpellType(this, SpellType_Buff);
for (auto buffSpell : buffSpells) {
if (!ElixirAITryCastSpell(selectedBotSpell)) continue;
return true;
}
}
return false;
case WIZARD: // This can eventually be move into the BEASTLORD case handler once pre-combat is fully implemented
if (GetTarget() && HasOrMayGetAggro()) {
selectedBotSpell = GetFirstBotSpellBySpellType(this, SpellType_Escape);
if (selectedBotSpell.SpellId > 0 && ElixirAITryCastSpell(selectedBotSpell)) {
return true;
}
}
selectedBotSpell = GetFirstBotSpellBySpellType(this, SpellType_Nuke);
if (selectedBotSpell.SpellId > 0 && ElixirAITryCastSpell(selectedBotSpell)) {
return true;
}
return false;
case BARD:
if (GetTarget() && HasOrMayGetAggro()) {
selectedBotSpell = GetFirstBotSpellBySpellType(this, SpellType_PreCombatBuffSong);
if (selectedBotSpell.SpellId > 0 && ElixirAITryCastSpell(selectedBotSpell)) {
return true;
}
}
selectedBotSpell = GetFirstBotSpellBySpellType(this, SpellType_InCombatBuffSong);
if (selectedBotSpell.SpellId > 0 && ElixirAITryCastSpell(selectedBotSpell)) {
return true;
}
return false;
default:
if (GetTarget() && HasOrMayGetAggro()) {
selectedBotSpell = GetFirstBotSpellBySpellType(this, SpellType_Escape);
if (selectedBotSpell.SpellId > 0 && ElixirAITryCastSpell(selectedBotSpell)) {
return true;
}
}
selectedBotSpell = GetFirstBotSpellBySpellType(this, SpellType_Nuke);
if (selectedBotSpell.SpellId > 0 && ElixirAITryCastSpell(selectedBotSpell)) {
return true;
}
selectedBotSpell = GetFirstBotSpellBySpellType(this, SpellType_InCombatBuff);
if (selectedBotSpell.SpellId > 0 && ElixirAITryCastSpell(selectedBotSpell)) {
return true;
}
return false;
}
return false;
} }
// ElixirAITryCastSpell takes a provided spell id and does a spell check to determine if the spell is valid // ElixirAITryCastSpell takes a provided spell id and does a spell check to determine if the spell is valid
// Once valid, it will cast on returned mob candidate // Once valid, it will cast on returned mob candidate
bool Bot::ElixirAITryCastSpell(BotSpell botSpell, bool isHeal) { bool Bot::ElixirAITryCastSpell(uint16 spellID, bool isHeal) {
auto spellID = botSpell.SpellId;
if (spellID == 0) return false; if (spellID == 0) return false;
Mob* outMob; Mob* outMob = nullptr;
auto spellAIResult = ElixirCastSpellCheck(spellID, outMob); auto spellAIResult = ElixirCastSpellCheck(spellID, &outMob);
if (spellAIResult < 0) return false; if (spellAIResult < 0) return false;
if (spellAIResult == 0) { if (spellAIResult == 0) {
AIDoSpellCast(botSpell.SpellIndex, GetTarget(), -1);
if (outMob == nullptr && GetTarget() != nullptr) outMob = GetTarget();
if (outMob == nullptr) outMob = this;
AIElixirDoSpellCast(spellID, outMob, -1);
if (GetTarget() == this) return true; if (GetTarget() == this) return true;
if (isHeal) BotGroupSay(this, "Casting %s on %s.", spells[spellID].name, GetTarget()->GetCleanName()); BotGroupSay(this, "Casting %s on %s.", spells[spellID].name, outMob->GetCleanName());
return true; return true;
} }
@ -2897,9 +2842,53 @@ bool Bot::ElixirAITryCastSpell(BotSpell botSpell, bool isHeal) {
return false; return false;
} }
AIDoSpellCast(botSpell.SpellIndex, outMob, -1); AIElixirDoSpellCast(spellID, outMob, -1);
if (outMob == this) return true; if (outMob == this) return true;
if (isHeal) BotGroupSay(this, "Casting %s on %s.", spells[spellID].name, outMob->GetCleanName()); BotGroupSay(this, "Casting %s on %s.", spells[spellID].name, outMob->GetCleanName());
return true; return true;
} }
bool Bot::AIElixirDoSpellCast(uint16 spellID, Mob* tar, int32 mana_cost) {
// manacost has special values, -1 is no mana cost, -2 is instant cast (no mana)
int32 manaCost = mana_cost;
if (manaCost == -1)
manaCost = spells[spellID].mana;
else if (manaCost == -2)
manaCost = 0;
int32 extraMana = 0;
int32 hasMana = GetMana();
Log(Logs::General, Logs::Mercenaries, "Attempting to cast %s on %s", spells[spellID].name, tar == nullptr ? "no target" : tar->GetCleanName());
// Allow bots to cast buff spells even if they are out of mana
if (RuleB(Bots, FinishBuffing)) {
if (manaCost > hasMana) {
//TODO: fix FinishBuffing no mana cost system
if (false) {
extraMana = manaCost - hasMana;
SetMana(manaCost);
}
}
}
bool result = NPC::CastSpell(spellID, tar->GetID(), EQ::spells::CastingSlot::Gem2, spells[spellID].mana == -2 ? 0 : -1, mana_cost, 0, -1, -1, 0, 0);
if (IsCasting() && IsSitting()) Stand();
// if the spell wasn't casted, then take back any extra mana that was given to the bot to cast that spell
if (!result) {
SetMana(hasMana);
extraMana = false;
return result;
}
//AIspells[i].time_cancast = Timer::GetCurrentTime() + spells[AIspells[i].spellid].recast_time;
if (spells[spellID].timer_id > 0) {
SetSpellRecastTimer(spells[spellID].timer_id, spells[spellID].recast_time);
}
return result;
}
#endif #endif

View File

@ -80,6 +80,7 @@
#include "../common/shared_tasks.h" #include "../common/shared_tasks.h"
#include "gm_commands/door_manipulation.h" #include "gm_commands/door_manipulation.h"
#include "../common/languages.h" #include "../common/languages.h"
#include "elixir.h"
extern QueryServ* QServ; extern QueryServ* QServ;
extern WorldServer worldserver; extern WorldServer worldserver;
@ -209,6 +210,7 @@ int command_init(void)
command_add("dz", "Manage expeditions and dynamic zone instances", 80, command_dz) || command_add("dz", "Manage expeditions and dynamic zone instances", 80, command_dz) ||
command_add("dzkickplayers", "Removes all players from current expedition. (/kickplayers alternative for pre-RoF clients)", 0, command_dzkickplayers) || command_add("dzkickplayers", "Removes all players from current expedition. (/kickplayers alternative for pre-RoF clients)", 0, command_dzkickplayers) ||
command_add("editmassrespawn", "[name-search] [second-value] - Mass (Zone wide) NPC respawn timer editing command", 100, command_editmassrespawn) || command_add("editmassrespawn", "[name-search] [second-value] - Mass (Zone wide) NPC respawn timer editing command", 100, command_editmassrespawn) ||
command_add("elixircheck", "[spellid] - Check how elixir AI works with provided spell id", 100, command_elixircheck) ||
command_add("emote", "['name'/'world'/'zone'] [type] [message] - Send an emote message", 80, command_emote) || command_add("emote", "['name'/'world'/'zone'] [type] [message] - Send an emote message", 80, command_emote) ||
command_add("emotesearch", "Searches NPC Emotes", 80, command_emotesearch) || command_add("emotesearch", "Searches NPC Emotes", 80, command_emotesearch) ||
command_add("emoteview", "Lists all NPC Emotes", 80, command_emoteview) || command_add("emoteview", "Lists all NPC Emotes", 80, command_emoteview) ||
@ -15900,6 +15902,97 @@ void command_emptyinventory(Client *c, const Seperator *sep)
} }
} }
void command_elixircheck(Client* c, const Seperator* sep)
{
if (sep->arg[1][0] == 0) {
c->Message(Chat::White, "Usage: #elixircheck [spellid]");
return;
}
auto spellid = atoi(sep->arg[1]);
if (spellid < 1) {
c->Message(Chat::Red, "Invalid spell id: %d", spellid);
return;
}
Mob* outMob = nullptr;
const SPDat_Spell_Struct& spDat = spells[spellid];
auto result = c->ElixirCastSpellCheck(spellid, &outMob);
if (result < 0) {
switch (result) {
case ELIXIR_UNHANDLED_SPELL:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (UNHANDLED_SPELL)", spDat.name, result);
return;
case ELIXIR_CANNOT_CAST_BAD_STATE:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (CANNOT_CAST_BAD_STATE)", spDat.name, result);
return;
case ELIXIR_NOT_ENOUGH_MANA:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (NOT_ENOUGH_MANA)", spDat.name, result);
return;
case ELIXIR_LULL_IGNORED:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (LULL_IGNORED)", spDat.name, result);
return;
case ELIXIR_MEZ_IGNORED:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (MEZ_IGNORED)", spDat.name, result);
return;
case ELIXIR_CHARM_IGNORED:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (CHARM_IGNORED)", spDat.name, result);
return;
case ELIXIR_NO_TARGET:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (NO_TARGET)", spDat.name, result);
return;
case ELIXIR_INVALID_TARGET_BODYTYPE:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (INVALID_TARGET_BODYTYPE)", spDat.name, result);
return;
case ELIXIR_TRANSPORT_IGNORED:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (TRANSPORT_IGNORED)", spDat.name, result);
return;
case ELIXIR_NOT_LINE_OF_SIGHT:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (NOT_LINE_OF_SIGHT)", spDat.name, result);
return;
case ELIXIR_COMPONENT_REQUIRED:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (COMPONENT_REQUIRED)", spDat.name, result);
return;
case ELIXIR_ALREADY_HAVE_BUFF:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (ALREADY_HAVE_BUFF)", spDat.name, result);
return;
case ELIXIR_ZONETYPE_FAIL:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (ZONETYPE_FAIL)", spDat.name, result);
return;
case ELIXIR_CANNOT_USE_IN_COMBAT:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (CANNOT_USE_IN_COMBAT)", spDat.name, result);
return;
case ELIXIR_NOT_ENOUGH_ENDURANCE:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (NOT_ENOUGH_ENDURANCE)", spDat.name, result);
return;
case ELIXIR_ALREADY_HAVE_PET:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (ALREADY_HAVE_PET)", spDat.name, result);
return;
case ELIXIR_OUT_OF_RANGE:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (OUT_OF_RANGE)", spDat.name, result);
return;
case ELIXIR_NO_PET:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (NO_PET)", spDat.name, result);
return;
case ELIXIR_NOT_NEEDED:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (NOT_NEEDED)", spDat.name, result);
return;
default:
c->Message(Chat::Red, "Elixir AI failed to cast %s due to error %d (UNKNOWN)", spDat.name, result);
return;
}
}
if (result == 0) {
c->Message(Chat::White, "Elixir AI would return OK to cast %s", spDat.name);
return;
}
if (result == 1) {
c->Message(Chat::White, "Elixir AI would return OK if target changes to %s to cast %s", outMob->GetCleanName(), spDat.name);
}
}
// All new code added to command.cpp should be BEFORE this comment line. Do no append code to this file below the BOTS code block. // All new code added to command.cpp should be BEFORE this comment line. Do no append code to this file below the BOTS code block.
#ifdef BOTS #ifdef BOTS
#include "bot_command.h" #include "bot_command.h"

View File

@ -88,6 +88,7 @@ void command_dye(Client *c, const Seperator *sep);
void command_dz(Client *c, const Seperator *sep); void command_dz(Client *c, const Seperator *sep);
void command_dzkickplayers(Client *c, const Seperator *sep); void command_dzkickplayers(Client *c, const Seperator *sep);
void command_editmassrespawn(Client* c, const Seperator* sep); void command_editmassrespawn(Client* c, const Seperator* sep);
void command_elixircheck(Client* c, const Seperator* sep);
void command_emote(Client *c, const Seperator *sep); void command_emote(Client *c, const Seperator *sep);
void command_emotesearch(Client* c, const Seperator *sep); void command_emotesearch(Client* c, const Seperator *sep);
void command_emoteview(Client* c, const Seperator *sep); void command_emoteview(Client* c, const Seperator *sep);

View File

@ -13,7 +13,7 @@ extern Zone* zone;
// If 0 is returned, spell is valid and no target changing is required // If 0 is returned, spell is valid and no target changing is required
// If a negative value is returned, an error occured. See elixir.h ELIXIR_ prefix const error lookup of reasons // If a negative value is returned, an error occured. See elixir.h ELIXIR_ prefix const error lookup of reasons
// If 1 is returned, outMob is set to the suggested mob entity // If 1 is returned, outMob is set to the suggested mob entity
int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob) int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob** outMob)
{ {
int manaCurrent = GetMana(); int manaCurrent = GetMana();
int manaMax = GetMaxMana(); int manaMax = GetMaxMana();
@ -54,18 +54,23 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
bool isTransport = false; bool isTransport = false;
bool isGroupSpell = false; bool isGroupSpell = false;
bool isBardSong = false; bool isBardSong = false;
bool isBeneficial = false;
bool isStun = false;
bool isMez = false; bool isMez = false;
bool isLull = false; bool isLull = false;
long stunDuration = 0; long stunDuration = 0;
long damageAmount = 0; long damageAmount = 0;
long healAmount = 0; long healAmount = 0;
bool isGroupHated = false;
int skillID; int skillID = 0;
bodyType targetBodyType = BT_NoTarget2; bodyType targetBodyType = BT_NoTarget2;
const SPDat_Spell_Struct& spDat = spells[spellID]; const SPDat_Spell_Struct& spDat = spells[spellID];
Log(Logs::General, Logs::Mercenaries, "%s checking %s vs %s", GetName(), spDat.name, target == nullptr ? "no target" : target->GetCleanName());
int spellgroup = spDat.type_description_id; int spellgroup = spDat.type_description_id;
uint32 ticks = spDat.buff_duration; uint32 ticks = spDat.buff_duration;
int targets = spDat.aoe_max_targets; int targets = spDat.aoe_max_targets;
@ -75,7 +80,7 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
int category = spDat.spell_category; int category = spDat.spell_category;
int subcategory = spDat.effect_description_id; int subcategory = spDat.effect_description_id;
uint32 buffCount; uint32 buffCount = 0;
Group* grp = GetGroup(); Group* grp = GetGroup();
Raid* raid = GetRaid(); Raid* raid = GetRaid();
@ -202,6 +207,11 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
isTransport = true; isTransport = true;
} }
if (attr == SE_HealOverTime) { //100 Heal over times
isHeal = true;
}
if (attr == SE_Familiar) { //108 Summon Familiar if (attr == SE_Familiar) { //108 Summon Familiar
isPetSummon = true; isPetSummon = true;
} }
@ -224,6 +234,8 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
isLifetap = true; isLifetap = true;
} }
} }
isBeneficial = IsBeneficialSpell(spellID);
isStun = IsStunSpell(spellID);
/* /*
//TODO TargetTypes: //TODO TargetTypes:
@ -321,11 +333,11 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
if (isTransport) return ELIXIR_TRANSPORT_IGNORED; if (isTransport) return ELIXIR_TRANSPORT_IGNORED;
if (spDat.npc_no_los == 0 && target && isSingleTargetSpell && CheckLosFN(target)) return ELIXIR_NOT_LINE_OF_SIGHT; if (spDat.npc_no_los == 0 && target && isSingleTargetSpell && !isBeneficial && !CheckLosFN(target)) return ELIXIR_NOT_LINE_OF_SIGHT;
for (int i = 0; i < 4; i++) { // Reagent check for (int i = 0; i < 4; i++) { // Reagent check
if (spDat.component[i] == 0) continue; if (spDat.component[i] < 1) continue;
if (spDat.component_count[i] == -1) continue; if (spDat.component_count[i] < 1) continue;
if (IsMerc()) continue; //mercs don't have inventory nor require reagents if (IsMerc()) continue; //mercs don't have inventory nor require reagents
#ifdef BOTS #ifdef BOTS
if (IsBot()) continue; //bots don't have inventory nor require reagents if (IsBot()) continue; //bots don't have inventory nor require reagents
@ -340,7 +352,7 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
if (skillID == EQ::skills::SkillBackstab) { // 8 backstab if (skillID == EQ::skills::SkillBackstab) { // 8 backstab
if (!target) { if (target == nullptr) {
return ELIXIR_NO_TARGET; return ELIXIR_NO_TARGET;
} }
if (!BehindMob(target)) { if (!BehindMob(target)) {
@ -372,8 +384,8 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
} }
} }
if (ticks > 0 && !IsBeneficialSpell(spellID) && targettype == ST_Target) { // debuff if (ticks > 0 && !isBeneficial && targettype == ST_Target) { // debuff
if (!target) { if (target == nullptr) {
return ELIXIR_NO_TARGET; return ELIXIR_NO_TARGET;
} }
@ -397,6 +409,18 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
return ELIXIR_ZONETYPE_FAIL; return ELIXIR_ZONETYPE_FAIL;
} }
if (target) { //do aggro check
if (target->GetHateAmount(this) > 0) isGroupHated = true;
if (!isGroupHated) {
for (int i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (!grp) break;
if (!grp->members[i]) continue;
if (target->GetHateAmount(grp->members[i]) == 0) continue;
isGroupHated = true;
break;
}
}
}
//TODO: zone_type 2 check (can't cast outdoors indoor only) //TODO: zone_type 2 check (can't cast outdoors indoor only)
if (IsEffectInSpell(spellID, SE_Levitate) && !zone->CanLevitate()) { if (IsEffectInSpell(spellID, SE_Levitate) && !zone->CanLevitate()) {
@ -404,7 +428,12 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
} }
if (!spDat.can_cast_in_combat) { if (!spDat.can_cast_in_combat) {
if (IsEngaged()) return ELIXIR_CANNOT_USE_IN_COMBAT; if (IsEngaged()) {
return ELIXIR_CANNOT_USE_IN_COMBAT;
}
if (target == nullptr) {
return ELIXIR_NO_TARGET;
}
buffCount = target->GetMaxTotalSlots(); buffCount = target->GetMaxTotalSlots();
for (uint32 i = 0; i < buffCount; i++) { for (uint32 i = 0; i < buffCount; i++) {
auto buff = target->buffs[i]; auto buff = target->buffs[i];
@ -445,7 +474,7 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
return ELIXIR_NOT_NEEDED; return ELIXIR_NOT_NEEDED;
} }
if (isBuff && targettype == ST_Pet && IsBeneficialSpell(spellID)) { if (isBuff && targettype == ST_Pet && isBeneficial) {
if (!HasPet()) { if (!HasPet()) {
return ELIXIR_NO_PET; return ELIXIR_NO_PET;
} }
@ -471,7 +500,7 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
} }
if (isMana && targettype == ST_Self && ticks <= 0 && !isPetSummon) { // self only regen, like harvest, canni if (isMana && targettype == ST_Self && ticks <= 0 && !isPetSummon) { // self only regen, like harvest, canni
if (stunDuration > 0 && IsEngaged()) { if (stunDuration > 0 && isGroupHated) {
return ELIXIR_CANNOT_USE_IN_COMBAT; return ELIXIR_CANNOT_USE_IN_COMBAT;
} }
@ -487,32 +516,99 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
return ELIXIR_NOT_NEEDED; return ELIXIR_NOT_NEEDED;
} }
if (isHeal) { //heal logic
if (isGroupSpell && isHeal && ticks == 0) { //self/group instant heals
int groupHealCount = 0; int groupHealCount = 0;
int healIDPercent = 100;
// figure out who is lowest HP party member
if (GetHPRatio() <= healPercent) {
if (ticks == 0) { // instant heal, just apply
*outMob = this;
healIDPercent = GetHPRatio();
}
else { // it's a heal buff, check if player already has it
bool isBuffNeeded = true;
buffCount = GetMaxTotalSlots();
for (uint32 i = 0; i < buffCount; i++) {
auto buff = buffs[i];
if (buff.spellid == SPELL_UNKNOWN) continue;
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
if (buff.spellid == spellID) {
isBuffNeeded = false;
break;
}
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
if (stackResult == -1) {
isBuffNeeded = false;
break;
}
}
if (isBuffNeeded) {
*outMob = this;
healIDPercent = GetHPRatio();
}
}
}
float sqDistance = spDat.aoe_range * spDat.aoe_range;
for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { for (int i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (!grp) break; if (!grp) break;
if (!grp->members[i]) continue; if (!grp->members[i]) continue;
if (grp->members[i]->GetHPRatio() > healPercent) continue; if (grp->members[i]->GetHPRatio() > healPercent) continue;
if (sqDistance > 0 && DistanceSquaredNoZ(target->GetPosition(), grp->members[i]->GetPosition()) > sqDistance) continue; if (grp->members[i]->GetHPRatio() > healIDPercent) continue;
if (!IsWithinSpellRange(grp->members[i], spDat.range, spellID)) continue;
groupHealCount++; groupHealCount++;
if (ticks == 0) { // instant heal, just apply
*outMob = grp->members[i];
healIDPercent = grp->members[i]->GetHPRatio();
}
else { // it's a heal buff, check if player already has it
bool isBuffNeeded = true;
buffCount = grp->members[i]->GetMaxTotalSlots();
for (uint32 j = 0; j < buffCount; j++) {
auto buff = grp->members[i]->buffs[j];
if (buff.spellid == SPELL_UNKNOWN) continue;
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
if (buff.spellid == spellID) {
isBuffNeeded = false;
break;
}
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
if (stackResult == -1) {
isBuffNeeded = false;
break;
}
// TODO: Immune Check
}
if (isBuffNeeded) {
*outMob = grp->members[i];
healIDPercent = GetHPRatio();
}
}
} }
if (groupHealCount < aeMinimum) { if (isGroupSpell && groupHealCount < aeMinimum) {
return ELIXIR_NOT_NEEDED; return ELIXIR_NOT_NEEDED;
} }
return 0;
if (!*outMob) {
return ELIXIR_NOT_NEEDED;
}
return 1;
} }
if ((targettype == ST_Self || isGroupSpell) && IsBeneficialSpell(spellID)) { //self/group beneficial spell if ((targettype == ST_Self || isGroupSpell) && isBeneficial) { //self/group beneficial spell
if (IsEngaged() && !isBardSong) { if (isGroupHated && !isBardSong) {
return ELIXIR_CANNOT_USE_IN_COMBAT; return ELIXIR_CANNOT_USE_IN_COMBAT;
} }
if (ticks > 0) { if (ticks <= 4 && GetClass() != BARD) { //don't bother with short duration buffs
return ELIXIR_NOT_NEEDED;
}
if (ticks == 0) {
return 0;
}
bool isBuffNeeded = true; bool isBuffNeeded = true;
buffCount = GetMaxTotalSlots(); buffCount = GetMaxTotalSlots();
@ -540,8 +636,8 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
if (!grp) break; if (!grp) break;
if (!grp->members[i]) continue; if (!grp->members[i]) continue;
buffCount = grp->members[i]->GetMaxTotalSlots(); buffCount = grp->members[i]->GetMaxTotalSlots();
for (uint32 i = 0; i < buffCount; i++) { for (uint32 j = 0; j < buffCount; j++) {
auto buff = buffs[i]; auto buff = grp->members[i]->buffs[j];
if (buff.spellid == SPELL_UNKNOWN) continue; if (buff.spellid == SPELL_UNKNOWN) continue;
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue; if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
if (buff.ticsremaining < 2) continue; if (buff.ticsremaining < 2) continue;
@ -556,89 +652,19 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
} }
} }
if (!isBuffNeeded) continue; if (!isBuffNeeded) continue;
outMob = grp->members[i]; *outMob = grp->members[i];
return 1; return 1;
} }
return ELIXIR_NOT_NEEDED; return ELIXIR_NOT_NEEDED;
} }
return 0; if (targettype == ST_Target && isBeneficial && damageAmount == 0) { //single target beneficial spell
}
if (targettype == ST_Target || IsBeneficialSpell(spellID)) { //single target beneficial spell
if (isHeal) { //heal logic
int healIDPercent = 100;
// figure out who is lowest HP party member
if (GetHPRatio() <= healPercent) {
if (ticks == 0) { // instant heal, just apply
outMob = this;
healIDPercent = GetHPRatio();
} else { // it's a heal buff, check if player already has it
bool isBuffNeeded = true;
for (uint32 i = 0; i < buffCount; i++) {
auto buff = buffs[i];
if (buff.spellid == SPELL_UNKNOWN) continue;
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
if (buff.spellid == spellID) {
isBuffNeeded = false;
break;
}
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
if (stackResult == -1) {
isBuffNeeded = false;
break;
}
}
if (isBuffNeeded) {
outMob = this;
healIDPercent = GetHPRatio();
}
}
}
for (int i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (!grp) break;
if (!grp->members[i]) continue;
if (grp->members[i]->GetHPRatio() > healPercent) continue;
if (grp->members[i]->GetHPRatio() > healIDPercent) continue;
if (!IsWithinSpellRange(grp->members[i], spDat.range, spellID)) continue;
if (ticks == 0) { // instant heal, just apply
outMob = grp->members[i];
healIDPercent = grp->members[i]->GetHPRatio();
} else { // it's a heal buff, check if player already has it
bool isBuffNeeded = true;
for (uint32 i = 0; i < buffCount; i++) {
auto buff = grp->members[i]->buffs[i];
if (buff.spellid == SPELL_UNKNOWN) continue;
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
if (buff.spellid == spellID) {
isBuffNeeded = false;
break;
}
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
if (stackResult == -1) {
isBuffNeeded = false;
break;
}
// TODO: Immune Check
}
if (isBuffNeeded) {
outMob = grp->members[i];
healIDPercent = GetHPRatio();
}
}
}
if (!outMob) {
return ELIXIR_NOT_NEEDED;
}
return 1;
}
// TODO: add exceptions for combat buffs situation // TODO: add exceptions for combat buffs situation
bool isCombatBuff = false; bool isCombatBuff = false;
if (IsEngaged() && !isCombatBuff) { if (isGroupHated && !isCombatBuff) {
return ELIXIR_NOT_NEEDED; return ELIXIR_NOT_NEEDED;
} }
@ -647,9 +673,12 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
if (ticks == 0) { if (ticks == 0) {
return ELIXIR_NOT_NEEDED; return ELIXIR_NOT_NEEDED;
} }
if (ticks <= 4 && GetClass() != BARD) { //don't bother with short duration buffs
return ELIXIR_NOT_NEEDED;
}
// always self buff first // always self buff first
bool isBuffNeeded = true; bool isBuffNeeded = true;
buffCount = GetMaxBuffSlots();
for (uint32 i = 0; i < buffCount; i++) { for (uint32 i = 0; i < buffCount; i++) {
auto buff = buffs[i]; auto buff = buffs[i];
if (buff.spellid == SPELL_UNKNOWN) continue; if (buff.spellid == SPELL_UNKNOWN) continue;
@ -669,7 +698,7 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
if (target && target->GetID() == GetID()) { if (target && target->GetID() == GetID()) {
return 0; return 0;
} }
outMob = this; *outMob = this;
return 1; return 1;
} }
@ -679,8 +708,9 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
if (!IsWithinSpellRange(grp->members[i], spDat.range, spellID)) continue; if (!IsWithinSpellRange(grp->members[i], spDat.range, spellID)) continue;
bool isBuffNeeded = true; bool isBuffNeeded = true;
for (uint32 i = 0; i < buffCount; i++) { buffCount = grp->members[i]->GetMaxTotalSlots();
auto buff = grp->members[i]->buffs[i]; for (uint32 j = 0; j < buffCount; j++) {
auto buff = grp->members[i]->buffs[j];
if (buff.spellid == SPELL_UNKNOWN) continue; if (buff.spellid == SPELL_UNKNOWN) continue;
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue; if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
if (buff.spellid == spellID) { if (buff.spellid == spellID) {
@ -698,7 +728,7 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
if (target && target->GetID() == grp->members[i]->GetID()) { if (target && target->GetID() == grp->members[i]->GetID()) {
return 0; return 0;
} }
outMob = grp->members[i]; *outMob = grp->members[i];
return 1; return 1;
} }
} }
@ -706,6 +736,22 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
return ELIXIR_NOT_NEEDED; return ELIXIR_NOT_NEEDED;
} }
if (isStun && (targettype == ST_AEClientV1 || targettype == ST_AreaClientOnly || targettype == ST_AECaster)) { // PB AE stun
int targetCount = 0;
float sqDistance = spDat.aoe_range * spDat.aoe_range;
auto hates = GetHateList();
auto iter = hates.begin();
for (auto iter : hates) {
if (sqDistance > 0 && DistanceSquaredNoZ(GetPosition(), iter->entity_on_hatelist->GetPosition()) > sqDistance) continue;
if (iter->entity_on_hatelist->IsStunned()) continue;
targetCount++;
}
if (targetCount < aeMinimum) {
return ELIXIR_NOT_NEEDED;
}
return 0;
}
if (damageAmount > 0 && (targettype == ST_AEClientV1 || targettype == ST_AreaClientOnly || targettype == ST_AECaster)) { // PB AE DD if (damageAmount > 0 && (targettype == ST_AEClientV1 || targettype == ST_AreaClientOnly || targettype == ST_AECaster)) { // PB AE DD
int targetCount = 0; int targetCount = 0;
float sqDistance = spDat.aoe_range * spDat.aoe_range; float sqDistance = spDat.aoe_range * spDat.aoe_range;
@ -724,7 +770,7 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
if (damageAmount > 0 && (targettype == ST_TargetAETap || targettype == ST_AETarget)) { // Target AE DD if (damageAmount > 0 && (targettype == ST_TargetAETap || targettype == ST_AETarget)) { // Target AE DD
if (!target) { if (target == nullptr) {
return ELIXIR_NO_TARGET; return ELIXIR_NO_TARGET;
} }
if (!IsWithinSpellRange(target, spDat.range, spellID)) { if (!IsWithinSpellRange(target, spDat.range, spellID)) {
@ -747,16 +793,20 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
} }
if (targettype == ST_Target || !IsBeneficialSpell(spellID)) { // single target detrimental spell if (targettype == ST_Target && !isBeneficial) { // single target detrimental spell
if (!hate_list.IsEntOnHateList(target)) { if (target == nullptr) {
return ELIXIR_NO_TARGET; return ELIXIR_NO_TARGET;
} }
if (!isGroupHated) {
return ELIXIR_NOT_NEEDED;
}
if (target->GetHPRatio() <= 0) { if (target->GetHPRatio() <= 0) {
return ELIXIR_NOT_NEEDED; return ELIXIR_NOT_NEEDED;
} }
if (!IsWithinSpellRange(GetPet(), spDat.range, spellID)) { if (!IsWithinSpellRange(target, spDat.range, spellID)) {
return ELIXIR_OUT_OF_RANGE; return ELIXIR_OUT_OF_RANGE;
} }
@ -768,13 +818,17 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
return ELIXIR_NOT_NEEDED; return ELIXIR_NOT_NEEDED;
} }
if (ticks == 0) { if (isStun) {
if (!target) { if (target->IsStunned()) {
return ELIXIR_NO_TARGET; return ELIXIR_NOT_NEEDED;
} }
return 0; return 0;
} }
if (ticks == 0) {
return 0;
}
buffCount = target->GetMaxTotalSlots();
for (uint32 i = 0; i < buffCount; i++) { for (uint32 i = 0; i < buffCount; i++) {
auto buff = target->buffs[i]; auto buff = target->buffs[i];
if (buff.spellid == SPELL_UNKNOWN) continue; if (buff.spellid == SPELL_UNKNOWN) continue;

View File

@ -1128,7 +1128,8 @@ void Merc::DoEnduranceUpkeep() {
if ((upkeep + upkeep_sum) > GetEndurance()) { if ((upkeep + upkeep_sum) > GetEndurance()) {
//they do not have enough to keep this one going. //they do not have enough to keep this one going.
BuffFadeBySlot(buffs_i); BuffFadeBySlot(buffs_i);
} else { }
else {
upkeep_sum += upkeep; upkeep_sum += upkeep;
} }
} }
@ -1781,7 +1782,8 @@ void Merc::AI_Start(int32 iMoveDelay) {
if (merc_spells.empty()) { if (merc_spells.empty()) {
AIautocastspell_timer->SetTimer(1000); AIautocastspell_timer->SetTimer(1000);
AIautocastspell_timer->Disable(); AIautocastspell_timer->Disable();
} else { }
else {
AIautocastspell_timer->SetTimer(750); AIautocastspell_timer->SetTimer(750);
AIautocastspell_timer->Start(RandomTimer(0, 2000), false); AIautocastspell_timer->Start(RandomTimer(0, 2000), false);
} }
@ -1979,7 +1981,8 @@ bool Merc::AIDoSpellCast(uint16 spellid, Mob* tar, int32 mana_cost, uint32* oDon
if (mercSpell.type & SpellType_Escape) { if (mercSpell.type & SpellType_Escape) {
dist2 = 0; dist2 = 0;
} else }
else
dist2 = DistanceSquared(m_Position, tar->GetPosition()); dist2 = DistanceSquared(m_Position, tar->GetPosition());
if (((((spells[spellid].target_type == ST_GroupTeleport && mercSpell.type == SpellType_Heal) if (((((spells[spellid].target_type == ST_GroupTeleport && mercSpell.type == SpellType_Heal)
@ -2595,7 +2598,8 @@ int32 Merc::GetFocusEffect(focusType type, uint16 spell_id) {
focus_max_real = focus_max; focus_max_real = focus_max;
UsedItem = TempItem; UsedItem = TempItem;
UsedFocusID = TempItem->Focus.Effect; UsedFocusID = TempItem->Focus.Effect;
} else if (focus_max < 0 && focus_max < focus_max_real) { }
else if (focus_max < 0 && focus_max < focus_max_real) {
focus_max_real = focus_max; focus_max_real = focus_max;
UsedItem = TempItem; UsedItem = TempItem;
UsedFocusID = TempItem->Focus.Effect; UsedFocusID = TempItem->Focus.Effect;
@ -2607,7 +2611,8 @@ int32 Merc::GetFocusEffect(focusType type, uint16 spell_id) {
realTotal = Total; realTotal = Total;
UsedItem = TempItem; UsedItem = TempItem;
UsedFocusID = TempItem->Focus.Effect; UsedFocusID = TempItem->Focus.Effect;
} else if (Total < 0 && Total < realTotal) { }
else if (Total < 0 && Total < realTotal) {
realTotal = Total; realTotal = Total;
UsedItem = TempItem; UsedItem = TempItem;
UsedFocusID = TempItem->Focus.Effect; UsedFocusID = TempItem->Focus.Effect;
@ -2647,7 +2652,8 @@ int32 Merc::GetFocusEffect(focusType type, uint16 spell_id) {
focus_max_real2 = focus_max2; focus_max_real2 = focus_max2;
buff_tracker = buff_slot; buff_tracker = buff_slot;
focusspell_tracker = focusspellid; focusspell_tracker = focusspellid;
} else if (focus_max2 < 0 && focus_max2 < focus_max_real2) { }
else if (focus_max2 < 0 && focus_max2 < focus_max_real2) {
focus_max_real2 = focus_max2; focus_max_real2 = focus_max2;
buff_tracker = buff_slot; buff_tracker = buff_slot;
focusspell_tracker = focusspellid; focusspell_tracker = focusspellid;
@ -2659,7 +2665,8 @@ int32 Merc::GetFocusEffect(focusType type, uint16 spell_id) {
realTotal2 = Total2; realTotal2 = Total2;
buff_tracker = buff_slot; buff_tracker = buff_slot;
focusspell_tracker = focusspellid; focusspell_tracker = focusspellid;
} else if (Total2 < 0 && Total2 < realTotal2) { }
else if (Total2 < 0 && Total2 < realTotal2) {
realTotal2 = Total2; realTotal2 = Total2;
buff_tracker = buff_slot; buff_tracker = buff_slot;
focusspell_tracker = focusspellid; focusspell_tracker = focusspellid;
@ -4021,7 +4028,8 @@ bool Merc::UseDiscipline(int32 spell_id, int32 target) {
if (GetEndurance() > spell.endurance_cost) { if (GetEndurance() > spell.endurance_cost) {
SetEndurance(GetEndurance() - spell.endurance_cost); SetEndurance(GetEndurance() - spell.endurance_cost);
} else { }
else {
//too fatigued to use this skill right now. //too fatigued to use this skill right now.
return(false); return(false);
} }
@ -6498,8 +6506,8 @@ bool Merc::ElixirAITryCastSpell(MercSpell mercSpell, bool isHeal) {
auto spellID = mercSpell.spellid; auto spellID = mercSpell.spellid;
if (spellID == 0) return false; if (spellID == 0) return false;
Mob* outMob; Mob* outMob = nullptr;
auto spellAIResult = ElixirCastSpellCheck(spellID, outMob); auto spellAIResult = ElixirCastSpellCheck(spellID, &outMob);
if (spellAIResult < 0) return false; if (spellAIResult < 0) return false;

View File

@ -855,7 +855,7 @@ public:
inline bool HasBaseEffectFocus() const { return (spellbonuses.FocusEffects[focusFcBaseEffects] || aabonuses.FocusEffects[focusFcBaseEffects] || itembonuses.FocusEffects[focusFcBaseEffects]); } inline bool HasBaseEffectFocus() const { return (spellbonuses.FocusEffects[focusFcBaseEffects] || aabonuses.FocusEffects[focusFcBaseEffects] || itembonuses.FocusEffects[focusFcBaseEffects]); }
int32 GetDualWieldingSameDelayWeapons() const { return dw_same_delay; } int32 GetDualWieldingSameDelayWeapons() const { return dw_same_delay; }
inline void SetDualWieldingSameDelayWeapons(int32 val) { dw_same_delay = val; } inline void SetDualWieldingSameDelayWeapons(int32 val) { dw_same_delay = val; }
int8 ElixirCastSpellCheck(uint16 spellID, Mob* outMob); int8 ElixirCastSpellCheck(uint16 spellID, Mob** outMob);
bool TryDoubleMeleeRoundEffect(); bool TryDoubleMeleeRoundEffect();
bool GetUseDoubleMeleeRoundDmgBonus() const { return use_double_melee_round_dmg_bonus; } bool GetUseDoubleMeleeRoundDmgBonus() const { return use_double_melee_round_dmg_bonus; }
inline void SetUseDoubleMeleeRoundDmgBonus(bool val) { use_double_melee_round_dmg_bonus = val; } inline void SetUseDoubleMeleeRoundDmgBonus(bool val) { use_double_melee_round_dmg_bonus = val; }