mirror of
https://github.com/EQEmu/Server.git
synced 2026-03-10 18:32:24 +00:00
Added ElixirSpellCache, #elixircheck
This commit is contained in:
parent
c23fc4ade7
commit
3e8796bb4c
@ -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());
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
2523
zone/botspellsai.cpp
2523
zone/botspellsai.cpp
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
476
zone/elixir.cpp
476
zone/elixir.cpp
@ -13,12 +13,12 @@ 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();
|
||||||
int hpCurrent = GetHP();
|
int hpCurrent = GetHP();
|
||||||
int hpMax = GetMaxHP();
|
int hpMax = GetMaxHP();
|
||||||
int endCurrent = GetEndurance();
|
int endCurrent = GetEndurance();
|
||||||
int endMax = GetMaxEndurance();
|
int endMax = GetMaxEndurance();
|
||||||
|
|
||||||
@ -38,33 +38,38 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
if (aeMinimum < 1) aeMinimum = 1;
|
if (aeMinimum < 1) aeMinimum = 1;
|
||||||
if (aeMinimum > 99) aeMinimum = 99;
|
if (aeMinimum > 99) aeMinimum = 99;
|
||||||
|
|
||||||
if (IsCorpse()) return ELIXIR_CANNOT_CAST_BAD_STATE;
|
if (IsCorpse()) return ELIXIR_CANNOT_CAST_BAD_STATE;
|
||||||
|
|
||||||
bool isHeal = false;
|
bool isHeal = false;
|
||||||
bool isDebuff = false;
|
bool isDebuff = false;
|
||||||
bool isBuff = false;
|
bool isBuff = false;
|
||||||
bool isLifetap = false;
|
bool isLifetap = false;
|
||||||
bool isMana = false;
|
bool isMana = false;
|
||||||
bool isCharm = false;
|
bool isCharm = false;
|
||||||
bool isSnare = false;
|
bool isSnare = false;
|
||||||
bool isSow = false;
|
bool isSow = false;
|
||||||
bool isTaunt = false;
|
bool isTaunt = false;
|
||||||
bool isSingleTargetSpell = false;
|
bool isSingleTargetSpell = false;
|
||||||
bool isPetSummon = false;
|
bool isPetSummon = false;
|
||||||
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;
|
||||||
@ -75,42 +80,42 @@ 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();
|
||||||
|
|
||||||
for (int i = 0; i < EFFECT_COUNT; i++) {
|
for (int i = 0; i < EFFECT_COUNT; i++) {
|
||||||
if (IsBlankSpellEffect(spellID, i)) continue;
|
if (IsBlankSpellEffect(spellID, i)) continue;
|
||||||
|
|
||||||
int attr = spDat.effect_id[i];
|
int attr = spDat.effect_id[i];
|
||||||
int base = spDat.base_value[i];
|
int base = spDat.base_value[i];
|
||||||
int base2 = spDat.limit_value[i];
|
int base2 = spDat.limit_value[i];
|
||||||
int max = spDat.max_value[i];
|
int max = spDat.max_value[i];
|
||||||
int calc = spDat.formula[i];
|
int calc = spDat.formula[i];
|
||||||
|
|
||||||
if (attr == SE_CurrentHP) { //0
|
if (attr == SE_CurrentHP) { //0
|
||||||
if (max > 0) { //Heal / HoT
|
if (max > 0) { //Heal / HoT
|
||||||
if (ticks < 5 && base > 0) { //regen
|
if (ticks < 5 && base > 0) { //regen
|
||||||
isHeal = true;
|
isHeal = true;
|
||||||
healAmount = base;
|
healAmount = base;
|
||||||
}
|
}
|
||||||
if (ticks > 0) {
|
if (ticks > 0) {
|
||||||
isBuff = true;
|
isBuff = true;
|
||||||
}
|
}
|
||||||
if (category == 114) { //taps like touch of zlandicar
|
if (category == 114) { //taps like touch of zlandicar
|
||||||
isLifetap = true;
|
isLifetap = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (base < 0 && damageAmount == 0) {
|
if (base < 0 && damageAmount == 0) {
|
||||||
damageAmount = -base;
|
damageAmount = -base;
|
||||||
}
|
}
|
||||||
if (max < 0) { //Nuke / DoT
|
if (max < 0) { //Nuke / DoT
|
||||||
damageAmount = -max;
|
damageAmount = -max;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attr == SE_ArmorClass || // 1 ac
|
if (attr == SE_ArmorClass || // 1 ac
|
||||||
attr == SE_ATK || //2 attack
|
attr == SE_ATK || //2 attack
|
||||||
attr == SE_STR || //4 str
|
attr == SE_STR || //4 str
|
||||||
attr == SE_DEX || //5 dex
|
attr == SE_DEX || //5 dex
|
||||||
attr == SE_AGI || //6 agi
|
attr == SE_AGI || //6 agi
|
||||||
@ -124,9 +129,9 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
if (base < 0) { //-stat
|
if (base < 0) { //-stat
|
||||||
isDebuff = true;
|
isDebuff = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attr == SE_MovementSpeed) { //3
|
if (attr == SE_MovementSpeed) { //3
|
||||||
if (base > 0) { //+Movement
|
if (base > 0) { //+Movement
|
||||||
isBuff = true;
|
isBuff = true;
|
||||||
isSow = true;
|
isSow = true;
|
||||||
@ -137,7 +142,7 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attr == SE_CHA) { //10 CHA
|
if (attr == SE_CHA) { //10 CHA
|
||||||
if (base > 0 && base < 254) { //+CHA
|
if (base > 0 && base < 254) { //+CHA
|
||||||
isBuff = true;
|
isBuff = true;
|
||||||
}
|
}
|
||||||
@ -155,7 +160,7 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attr == SE_CurrentMana) { //15 Mana
|
if (attr == SE_CurrentMana) { //15 Mana
|
||||||
isMana = true;
|
isMana = true;
|
||||||
}
|
}
|
||||||
if (attr == SE_Lull) { // pacify (lull)
|
if (attr == SE_Lull) { // pacify (lull)
|
||||||
@ -175,7 +180,7 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
isTransport = true;
|
isTransport = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attr == SE_ChangeFrenzyRad) { //30 frenzy radius reduction (lull)
|
if (attr == SE_ChangeFrenzyRad) { //30 frenzy radius reduction (lull)
|
||||||
isLull = true;
|
isLull = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +199,7 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
if (attr == SE_Teleport) { //83 Transport
|
if (attr == SE_Teleport) { //83 Transport
|
||||||
isTransport = true;
|
isTransport = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attr == SE_Harmony) { //86 reaction radius reduction (lull)
|
if (attr == SE_Harmony) { //86 reaction radius reduction (lull)
|
||||||
isLull = true;
|
isLull = true;
|
||||||
}
|
}
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
@ -213,7 +223,7 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
if (attr == SE_SkillAttack) { //193 skill attack
|
if (attr == SE_SkillAttack) { //193 skill attack
|
||||||
skillID = spDat.skill;
|
skillID = spDat.skill;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subcategory == 43 && ticks < 10) { //Health
|
if (subcategory == 43 && ticks < 10) { //Health
|
||||||
isHeal = true;
|
isHeal = 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:
|
||||||
@ -243,7 +255,7 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
if (targettype == ST_GroupTeleport) { //3 Group v1
|
if (targettype == ST_GroupTeleport) { //3 Group v1
|
||||||
isGroupSpell = true;
|
isGroupSpell = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isGroupSpell && IsBardSong(spellID)) {
|
if (isGroupSpell && IsBardSong(spellID)) {
|
||||||
isBardSong = true;
|
isBardSong = true;
|
||||||
}
|
}
|
||||||
@ -304,28 +316,28 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
if (targetBodyType != BT_Animal) return ELIXIR_INVALID_TARGET_BODYTYPE;
|
if (targetBodyType != BT_Animal) return ELIXIR_INVALID_TARGET_BODYTYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targettype == ST_Undead ) { //10 Undead
|
if (targettype == ST_Undead) { //10 Undead
|
||||||
if (target == nullptr) return ELIXIR_NO_TARGET;
|
if (target == nullptr) return ELIXIR_NO_TARGET;
|
||||||
if (targetBodyType != BT_Undead) return ELIXIR_INVALID_TARGET_BODYTYPE;
|
if (targetBodyType != BT_Undead) return ELIXIR_INVALID_TARGET_BODYTYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targettype == ST_Summoned) { //11 Summoned
|
if (targettype == ST_Summoned) { //11 Summoned
|
||||||
if (target == nullptr) return ELIXIR_NO_TARGET;
|
if (target == nullptr) return ELIXIR_NO_TARGET;
|
||||||
if (targetBodyType != BT_Summoned) return ELIXIR_INVALID_TARGET_BODYTYPE;
|
if (targetBodyType != BT_Summoned) return ELIXIR_INVALID_TARGET_BODYTYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targettype == ST_Plant) { //Plant
|
if (targettype == ST_Plant) { //Plant
|
||||||
if (target == nullptr) return ELIXIR_NO_TARGET;
|
if (target == nullptr) return ELIXIR_NO_TARGET;
|
||||||
if (targetBodyType != BT_Plant) return ELIXIR_INVALID_TARGET_BODYTYPE;
|
if (targetBodyType != BT_Plant) return ELIXIR_INVALID_TARGET_BODYTYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
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)) {
|
||||||
@ -351,9 +363,9 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recourseID > 0) { //recourse buff attached
|
if (recourseID > 0) { //recourse buff attached
|
||||||
const SPDat_Spell_Struct &spDatRecourse = spells[recourseID];
|
const SPDat_Spell_Struct& spDatRecourse = spells[recourseID];
|
||||||
if (spDatRecourse.buff_duration > 0) {
|
if (spDatRecourse.buff_duration > 0) {
|
||||||
buffCount = GetMaxTotalSlots();
|
buffCount = GetMaxTotalSlots();
|
||||||
for (uint32 i = 0; i < buffCount; i++) {
|
for (uint32 i = 0; i < buffCount; i++) {
|
||||||
@ -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,14 +409,31 @@ 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()) {
|
||||||
return ELIXIR_ZONETYPE_FAIL;
|
return ELIXIR_ZONETYPE_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
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];
|
||||||
@ -412,7 +441,7 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
if (IsDetrimentalSpell(buff.spellid) && buff.ticsremaining > 0 && !DetrimentalSpellAllowsRest(buff.spellid)) {
|
if (IsDetrimentalSpell(buff.spellid) && buff.ticsremaining > 0 && !DetrimentalSpellAllowsRest(buff.spellid)) {
|
||||||
return ELIXIR_CANNOT_USE_IN_COMBAT;
|
return ELIXIR_CANNOT_USE_IN_COMBAT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsDisciplineBuff(spellID)) {
|
if (IsDisciplineBuff(spellID)) {
|
||||||
@ -434,7 +463,7 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
|
|
||||||
if (isPetSummon && HasPet()) return ELIXIR_ALREADY_HAVE_PET;
|
if (isPetSummon && HasPet()) return ELIXIR_ALREADY_HAVE_PET;
|
||||||
|
|
||||||
|
|
||||||
if (targettype == ST_Pet && isHeal) {
|
if (targettype == ST_Pet && isHeal) {
|
||||||
if (!HasPet()) {
|
if (!HasPet()) {
|
||||||
return ELIXIR_NO_PET;
|
return ELIXIR_NO_PET;
|
||||||
@ -445,11 +474,11 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffCount = GetPet()->GetMaxTotalSlots();
|
buffCount = GetPet()->GetMaxTotalSlots();
|
||||||
for (uint32 i = 0; i < buffCount; i++) {
|
for (uint32 i = 0; i < buffCount; i++) {
|
||||||
auto buff = GetPet()->buffs[i];
|
auto buff = GetPet()->buffs[i];
|
||||||
@ -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,64 +516,22 @@ 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;
|
||||||
float sqDistance = spDat.aoe_range * spDat.aoe_range;
|
// figure out who is lowest HP party member
|
||||||
for (int i = 0; i < MAX_GROUP_MEMBERS; i++) {
|
if (GetHPRatio() <= healPercent) {
|
||||||
if (!grp) break;
|
if (ticks == 0) { // instant heal, just apply
|
||||||
if (!grp->members[i]) continue;
|
*outMob = this;
|
||||||
if (grp->members[i]->GetHPRatio() > healPercent) continue;
|
healIDPercent = GetHPRatio();
|
||||||
if (sqDistance > 0 && DistanceSquaredNoZ(target->GetPosition(), grp->members[i]->GetPosition()) > sqDistance) continue;
|
|
||||||
groupHealCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (groupHealCount < aeMinimum) {
|
|
||||||
return ELIXIR_NOT_NEEDED;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if ((targettype == ST_Self || isGroupSpell) && IsBeneficialSpell(spellID)) { //self/group beneficial spell
|
|
||||||
if (IsEngaged() && !isBardSong) {
|
|
||||||
return ELIXIR_CANNOT_USE_IN_COMBAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ticks > 0) {
|
|
||||||
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.ticsremaining < 2) 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) return 0;
|
else { // it's a heal buff, check if player already has it
|
||||||
if (!isGroupSpell) return ELIXIR_NOT_NEEDED;
|
bool isBuffNeeded = true;
|
||||||
|
buffCount = GetMaxTotalSlots();
|
||||||
isBuffNeeded = true;
|
|
||||||
for (int i = 0; i < MAX_GROUP_MEMBERS; i++) {
|
|
||||||
|
|
||||||
if (!grp) break;
|
|
||||||
if (!grp->members[i]) continue;
|
|
||||||
buffCount = grp->members[i]->GetMaxTotalSlots();
|
|
||||||
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;
|
||||||
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.spellid == spellID) {
|
if (buff.spellid == spellID) {
|
||||||
isBuffNeeded = false;
|
isBuffNeeded = false;
|
||||||
break;
|
break;
|
||||||
@ -555,90 +542,129 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isBuffNeeded) continue;
|
if (isBuffNeeded) {
|
||||||
outMob = grp->members[i];
|
*outMob = this;
|
||||||
return 1;
|
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;
|
||||||
|
|
||||||
|
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 (isGroupSpell && groupHealCount < aeMinimum) {
|
||||||
return ELIXIR_NOT_NEEDED;
|
return ELIXIR_NOT_NEEDED;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
if (!*outMob) {
|
||||||
|
return ELIXIR_NOT_NEEDED;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targettype == ST_Target || IsBeneficialSpell(spellID)) { //single target beneficial spell
|
|
||||||
if (isHeal) { //heal logic
|
if ((targettype == ST_Self || isGroupSpell) && isBeneficial) { //self/group beneficial spell
|
||||||
int healIDPercent = 100;
|
if (isGroupHated && !isBardSong) {
|
||||||
// figure out who is lowest HP party member
|
return ELIXIR_CANNOT_USE_IN_COMBAT;
|
||||||
if (GetHPRatio() <= healPercent) {
|
}
|
||||||
if (ticks == 0) { // instant heal, just apply
|
|
||||||
outMob = this;
|
if (ticks <= 4 && GetClass() != BARD) { //don't bother with short duration buffs
|
||||||
healIDPercent = GetHPRatio();
|
return ELIXIR_NOT_NEEDED;
|
||||||
} else { // it's a heal buff, check if player already has it
|
}
|
||||||
bool isBuffNeeded = true;
|
|
||||||
for (uint32 i = 0; i < buffCount; i++) {
|
if (ticks == 0) {
|
||||||
auto buff = buffs[i];
|
return 0;
|
||||||
if (buff.spellid == SPELL_UNKNOWN) continue;
|
}
|
||||||
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
|
bool isBuffNeeded = true;
|
||||||
if (buff.spellid == spellID) {
|
|
||||||
isBuffNeeded = false;
|
buffCount = GetMaxTotalSlots();
|
||||||
break;
|
for (uint32 i = 0; i < buffCount; i++) {
|
||||||
}
|
auto buff = buffs[i];
|
||||||
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
if (buff.spellid == SPELL_UNKNOWN) continue;
|
||||||
if (stackResult == -1) {
|
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
|
||||||
isBuffNeeded = false;
|
if (buff.ticsremaining < 2) continue;
|
||||||
break;
|
if (buff.spellid == spellID) {
|
||||||
}
|
isBuffNeeded = false;
|
||||||
}
|
break;
|
||||||
if (isBuffNeeded) {
|
}
|
||||||
outMob = this;
|
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||||
healIDPercent = GetHPRatio();
|
if (stackResult == -1) {
|
||||||
}
|
isBuffNeeded = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isBuffNeeded) return 0;
|
||||||
|
if (!isGroupSpell) return ELIXIR_NOT_NEEDED;
|
||||||
|
|
||||||
|
isBuffNeeded = true;
|
||||||
|
for (int i = 0; i < MAX_GROUP_MEMBERS; i++) {
|
||||||
|
|
||||||
|
if (!grp) break;
|
||||||
|
if (!grp->members[i]) continue;
|
||||||
|
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.ticsremaining < 2) 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) continue;
|
||||||
for (int i = 0; i < MAX_GROUP_MEMBERS; i++) {
|
*outMob = grp->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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
return ELIXIR_NOT_NEEDED;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targettype == ST_Target && isBeneficial && damageAmount == 0) { //single target beneficial spell
|
||||||
|
|
||||||
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -677,10 +706,11 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
if (!grp) break;
|
if (!grp) break;
|
||||||
if (!grp->members[i]) continue;
|
if (!grp->members[i]) continue;
|
||||||
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) {
|
||||||
@ -688,7 +718,7 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||||
if (stackResult == -1) {
|
if (stackResult == -1) {
|
||||||
isBuffNeeded = false;
|
isBuffNeeded = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -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,13 +770,13 @@ 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)) {
|
||||||
return ELIXIR_OUT_OF_RANGE;
|
return ELIXIR_OUT_OF_RANGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
int targetCount = 0;
|
int targetCount = 0;
|
||||||
float sqDistance = spDat.aoe_range * spDat.aoe_range;
|
float sqDistance = spDat.aoe_range * spDat.aoe_range;
|
||||||
auto hates = GetHateList();
|
auto hates = GetHateList();
|
||||||
@ -746,17 +792,21 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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;
|
||||||
@ -791,7 +845,7 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ELIXIR_UNHANDLED_SPELL;
|
return ELIXIR_UNHANDLED_SPELL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -801,4 +855,4 @@ int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
|||||||
if (!raid->members[i].member) continue;
|
if (!raid->members[i].member) continue;
|
||||||
//raid->members[i].GroupNumber == gid
|
//raid->members[i].GroupNumber == gid
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|||||||
3074
zone/merc.cpp
3074
zone/merc.cpp
File diff suppressed because it is too large
Load Diff
@ -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; }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user