mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-13 14:41:28 +00:00
Added Elixir
This commit is contained in:
parent
3b628c1ace
commit
c23fc4ade7
@ -188,6 +188,9 @@ RULE_INT(Mercs, AggroRadiusPuller, 25, "Determines the distance from which a mer
|
||||
RULE_INT(Mercs, ResurrectRadius, 50, "Determines the distance from which a healer merc will attempt to resurrect a group member's corpse")
|
||||
RULE_INT(Mercs, ScaleRate, 100, "Merc scale factor")
|
||||
RULE_BOOL(Mercs, AllowMercSuspendInCombat, true, "Allow merc suspend in combat")
|
||||
RULE_BOOL(Mercs, IsMercsElixirEnabled, false, "Override AI with elixir logic")
|
||||
RULE_INT(Mercs, MercsElixirHealPercent, 90, "Heal allies at this percent health")
|
||||
RULE_INT(Mercs, MercsElixirAEMinimum, 3, "AE Minimum to trigger AE spells (heals and nukes)")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Guild)
|
||||
@ -599,6 +602,9 @@ RULE_BOOL(Bots, OldRaceRezEffects, false, "Older clients had ID 757 for races wi
|
||||
RULE_BOOL(Bots, ResurrectionSickness, true, "Use Resurrection Sickness based on Resurrection spell cast, set to false to disable Resurrection Sickness.")
|
||||
RULE_INT(Bots, OldResurrectionSicknessSpell, 757, "757 is Default Old Resurrection Sickness Spell")
|
||||
RULE_INT(Bots, ResurrectionSicknessSpell, 756, "756 is Default Resurrection Sickness Spell")
|
||||
RULE_BOOL(Bots, IsBotsElixirEnabled, false, "Override AI with elixir logic")
|
||||
RULE_INT(Bots, BotsElixirHealPercent, 90, "Heal allies at this percent health")
|
||||
RULE_INT(Bots, BotsElixirAEMinimum, 3, "AE Minimum to trigger AE spells (heals and nukes)")
|
||||
RULE_CATEGORY_END()
|
||||
#endif
|
||||
|
||||
|
||||
@ -26,6 +26,7 @@ SET(zone_sources
|
||||
dialogue_window.cpp
|
||||
dynamic_zone.cpp
|
||||
effects.cpp
|
||||
elixir.cpp
|
||||
embparser.cpp
|
||||
embparser_api.cpp
|
||||
embperl.cpp
|
||||
@ -187,6 +188,7 @@ SET(zone_headers
|
||||
doors.h
|
||||
dialogue_window.h
|
||||
dynamic_zone.h
|
||||
elixir.h
|
||||
embparser.h
|
||||
embperl.h
|
||||
embxs.h
|
||||
|
||||
@ -321,6 +321,8 @@ public:
|
||||
void SetStopMeleeLevel(uint8 level);
|
||||
void SetGuardMode();
|
||||
void SetHoldMode();
|
||||
bool ElixirAIDetermineSpellToCast();
|
||||
bool ElixirAITryCastSpell(BotSpell botSpell, bool isHeal = false);
|
||||
|
||||
// Mob AI Virtual Override Methods
|
||||
virtual void AI_Process();
|
||||
|
||||
@ -1134,6 +1134,14 @@ bool Bot::AI_PursueCastCheck() {
|
||||
|
||||
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.
|
||||
|
||||
if (RuleB(Bots, IsBotsElixirEnabled)) {
|
||||
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 false;
|
||||
}
|
||||
|
||||
LogAI("Bot Engaged (pursuing) autocast check triggered. Trying to cast offensive spells");
|
||||
|
||||
if(!AICastSpell(GetTarget(), 100, SpellType_Snare)) {
|
||||
@ -1166,6 +1174,14 @@ bool Bot::AI_IdleCastCheck() {
|
||||
#endif
|
||||
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.
|
||||
|
||||
if (RuleB(Bots, IsBotsElixirEnabled)) {
|
||||
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 false;
|
||||
}
|
||||
|
||||
bool pre_combat = false;
|
||||
Client* test_against = nullptr;
|
||||
|
||||
@ -1308,6 +1324,13 @@ bool Bot::AI_EngagedCastCheck() {
|
||||
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 (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 false;
|
||||
}
|
||||
|
||||
uint8 botClass = GetClass();
|
||||
EQ::constants::StanceType botStance = GetBotStance();
|
||||
@ -2674,4 +2697,209 @@ uint8 Bot::GetChanceToCastBySpellType(uint32 spellType)
|
||||
return database.botdb.GetSpellCastingChance(spell_type_index, class_index, stance_index, type_index);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ElixirAIDetermineSpellToCast is called during AI bot logics
|
||||
// It determines by class which spell to cast
|
||||
bool Bot::ElixirAIDetermineSpellToCast() {
|
||||
BotSpell selectedBotSpell;
|
||||
int8 spellAIResult;
|
||||
Group *grp = GetGroup();
|
||||
|
||||
if (GetClass() == WARRIOR || GetClass() == SHADOWKNIGHT || GetClass() == PALADIN) {
|
||||
|
||||
/*if(CheckAETaunt()) {
|
||||
selectedBotSpell = GetBestBotSpellForAETaunt(this);
|
||||
if (selectedBotSpell.SpellId > 0 && ElixirAITryCastSpell(selectedBotSpell)) {
|
||||
Log(Logs::General, Logs::Botenaries, "%s AE Taunting.", GetName());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(CheckTaunt()) {
|
||||
selectedBotSpell = GetBestBotSpellForTaunt(this);
|
||||
if (selectedBotSpell.SpellId > 0 && ElixirAITryCastSpell(selectedBotSpell)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
switch (GetClass()) {
|
||||
case CLERIC:
|
||||
case PALADIN:
|
||||
case RANGER:
|
||||
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;
|
||||
// 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
|
||||
// Once valid, it will cast on returned mob candidate
|
||||
bool Bot::ElixirAITryCastSpell(BotSpell botSpell, bool isHeal) {
|
||||
auto spellID = botSpell.SpellId;
|
||||
if (spellID == 0) return false;
|
||||
|
||||
Mob* outMob;
|
||||
auto spellAIResult = ElixirCastSpellCheck(spellID, outMob);
|
||||
|
||||
if (spellAIResult < 0) return false;
|
||||
|
||||
if (spellAIResult == 0) {
|
||||
AIDoSpellCast(botSpell.SpellIndex, GetTarget(), -1);
|
||||
if (GetTarget() == this) return true;
|
||||
if (isHeal) BotGroupSay(this, "Casting %s on %s.", spells[spellID].name, GetTarget()->GetCleanName());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (outMob == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AIDoSpellCast(botSpell.SpellIndex, outMob, -1);
|
||||
if (outMob == this) return true;
|
||||
if (isHeal) BotGroupSay(this, "Casting %s on %s.", spells[spellID].name, outMob->GetCleanName());
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
804
zone/elixir.cpp
Normal file
804
zone/elixir.cpp
Normal file
@ -0,0 +1,804 @@
|
||||
#include "../common/rulesys.h"
|
||||
#include "../common/global_define.h"
|
||||
#include "../common/eqemu_logsys.h"
|
||||
|
||||
#include "mob.h"
|
||||
#include "elixir.h"
|
||||
#include "zone.h"
|
||||
#include "groups.h"
|
||||
|
||||
extern Zone* zone;
|
||||
|
||||
// ElixirCastSpell determines if a spell can be casted by Mob.
|
||||
// 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 1 is returned, outMob is set to the suggested mob entity
|
||||
int8 Mob::ElixirCastSpellCheck(uint16 spellID, Mob *outMob)
|
||||
{
|
||||
int manaCurrent = GetMana();
|
||||
int manaMax = GetMaxMana();
|
||||
int hpCurrent = GetHP();
|
||||
int hpMax = GetMaxHP();
|
||||
int endCurrent = GetEndurance();
|
||||
int endMax = GetMaxEndurance();
|
||||
|
||||
int healPercent = 90;
|
||||
if (IsMerc()) healPercent = RuleI(Mercs, MercsElixirHealPercent);
|
||||
#ifdef BOTS
|
||||
if (IsBot()) healPercent = RuleI(Bots, BotsElixirHealPercent);
|
||||
#endif
|
||||
if (healPercent < 1) healPercent = 1;
|
||||
if (healPercent > 99) healPercent = 99;
|
||||
|
||||
int aeMinimum = 3;
|
||||
if (IsMerc()) aeMinimum = RuleI(Mercs, MercsElixirAEMinimum);
|
||||
#ifdef BOTS
|
||||
if (IsBot()) aeMinimum = RuleI(Bots, BotsElixirAEMinimum);
|
||||
#endif
|
||||
if (aeMinimum < 1) aeMinimum = 1;
|
||||
if (aeMinimum > 99) aeMinimum = 99;
|
||||
|
||||
if (IsCorpse()) return ELIXIR_CANNOT_CAST_BAD_STATE;
|
||||
|
||||
bool isHeal = false;
|
||||
bool isDebuff = false;
|
||||
bool isBuff = false;
|
||||
bool isLifetap = false;
|
||||
bool isMana = false;
|
||||
bool isCharm = false;
|
||||
bool isSnare = false;
|
||||
bool isSow = false;
|
||||
bool isTaunt = false;
|
||||
bool isSingleTargetSpell = false;
|
||||
bool isPetSummon = false;
|
||||
bool isTransport = false;
|
||||
bool isGroupSpell = false;
|
||||
bool isBardSong = false;
|
||||
bool isMez = false;
|
||||
bool isLull = false;
|
||||
long stunDuration = 0;
|
||||
long damageAmount = 0;
|
||||
long healAmount = 0;
|
||||
|
||||
int skillID;
|
||||
|
||||
bodyType targetBodyType = BT_NoTarget2;
|
||||
|
||||
const SPDat_Spell_Struct &spDat = spells[spellID];
|
||||
|
||||
int spellgroup = spDat.type_description_id;
|
||||
uint32 ticks = spDat.buff_duration;
|
||||
int targets = spDat.aoe_max_targets;
|
||||
SpellTargetType targettype = spDat.target_type;
|
||||
EQ::skills::SkillType skill = spDat.skill;
|
||||
uint16 recourseID = spDat.recourse_link;
|
||||
int category = spDat.spell_category;
|
||||
int subcategory = spDat.effect_description_id;
|
||||
|
||||
uint32 buffCount;
|
||||
Group *grp = GetGroup();
|
||||
Raid *raid = GetRaid();
|
||||
|
||||
for (int i = 0; i < EFFECT_COUNT; i++) {
|
||||
if (IsBlankSpellEffect(spellID, i)) continue;
|
||||
|
||||
int attr = spDat.effect_id[i];
|
||||
int base = spDat.base_value[i];
|
||||
int base2 = spDat.limit_value[i];
|
||||
int max = spDat.max_value[i];
|
||||
int calc = spDat.formula[i];
|
||||
|
||||
if (attr == SE_CurrentHP) { //0
|
||||
if (max > 0) { //Heal / HoT
|
||||
if (ticks < 5 && base > 0) { //regen
|
||||
isHeal = true;
|
||||
healAmount = base;
|
||||
}
|
||||
if (ticks > 0) {
|
||||
isBuff = true;
|
||||
}
|
||||
if (category == 114) { //taps like touch of zlandicar
|
||||
isLifetap = true;
|
||||
}
|
||||
}
|
||||
if (base < 0 && damageAmount == 0) {
|
||||
damageAmount = -base;
|
||||
}
|
||||
if (max < 0) { //Nuke / DoT
|
||||
damageAmount = -max;
|
||||
}
|
||||
}
|
||||
|
||||
if (attr == SE_ArmorClass || // 1 ac
|
||||
attr == SE_ATK || //2 attack
|
||||
attr == SE_STR || //4 str
|
||||
attr == SE_DEX || //5 dex
|
||||
attr == SE_AGI || //6 agi
|
||||
attr == SE_STA || //7 sta
|
||||
attr == SE_INT || //8 int
|
||||
attr == SE_WIS //9 wis
|
||||
) {
|
||||
if (base > 0) { //+stat
|
||||
isBuff = true;
|
||||
}
|
||||
if (base < 0) { //-stat
|
||||
isDebuff = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (attr == SE_MovementSpeed) { //3
|
||||
if (base > 0) { //+Movement
|
||||
isBuff = true;
|
||||
isSow = true;
|
||||
}
|
||||
if (base < 0) { //-Movement
|
||||
isDebuff = true;
|
||||
isSnare = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (attr == SE_CHA) { //10 CHA
|
||||
if (base > 0 && base < 254) { //+CHA
|
||||
isBuff = true;
|
||||
}
|
||||
if (base < 0) { //-CHA
|
||||
isDebuff = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (attr == SE_AttackSpeed) { //11 attackspeed
|
||||
if (base > 0) { //+Haste
|
||||
isBuff = true;
|
||||
}
|
||||
if (base < 0) { //-Haste
|
||||
isDebuff = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (attr == SE_CurrentMana) { //15 Mana
|
||||
isMana = true;
|
||||
}
|
||||
if (attr == SE_Lull) { // pacify (lull)
|
||||
isLull = true;
|
||||
}
|
||||
if (attr == SE_Stun) { //21 stun
|
||||
stunDuration = base2;
|
||||
if (targettype == ST_AEClientV1 || targettype == ST_AECaster) { // 2 or
|
||||
isSingleTargetSpell = true; //hack to make ae stuns work
|
||||
}
|
||||
}
|
||||
if (attr == SE_Charm) { //23 charm
|
||||
isCharm = true;
|
||||
}
|
||||
|
||||
if (attr == SE_Gate) { //26 Gate
|
||||
isTransport = true;
|
||||
}
|
||||
|
||||
if (attr == SE_ChangeFrenzyRad) { //30 frenzy radius reduction (lull)
|
||||
isLull = true;
|
||||
}
|
||||
|
||||
if (attr == SE_Mez) { //31 Mesmerization
|
||||
isMez = true;
|
||||
}
|
||||
|
||||
if (attr == SE_SummonPet) { //33 Summon Elemental Pet
|
||||
isPetSummon = true;
|
||||
}
|
||||
|
||||
if (attr == SE_NecPet) { //71 Summon Skeleton Pet
|
||||
isPetSummon = true;
|
||||
}
|
||||
|
||||
if (attr == SE_Teleport) { //83 Transport
|
||||
isTransport = true;
|
||||
}
|
||||
|
||||
if (attr == SE_Harmony) { //86 reaction radius reduction (lull)
|
||||
isLull = true;
|
||||
}
|
||||
if (attr == SE_Succor) { //88 Evac
|
||||
isTransport = true;
|
||||
}
|
||||
|
||||
if (attr == SE_Familiar) { //108 Summon Familiar
|
||||
isPetSummon = true;
|
||||
}
|
||||
|
||||
if (attr == SE_Hate) { //192 taunt
|
||||
isTaunt = true;
|
||||
}
|
||||
|
||||
if (attr == SE_SkillAttack) { //193 skill attack
|
||||
skillID = spDat.skill;
|
||||
}
|
||||
}
|
||||
|
||||
if (subcategory == 43 && ticks < 10) { //Health
|
||||
isHeal = true;
|
||||
}
|
||||
|
||||
if (category == 126) { //Taps
|
||||
if (subcategory == 43) { //Health
|
||||
isLifetap = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
//TODO TargetTypes:
|
||||
case 40: return "AE PC v2";
|
||||
case 25: return "AE Summoned";
|
||||
case 24: return "AE Undead";
|
||||
case 20: return "Targetted AE Tap";
|
||||
case 8: return "Targetted AE";
|
||||
case 2: return "AE PC v1";
|
||||
case 1: return "Line of Sight";
|
||||
*/
|
||||
|
||||
|
||||
if (targettype == ST_Group) { // 41 Group v2
|
||||
isGroupSpell = true;
|
||||
}
|
||||
if (targettype == ST_GroupTeleport) { //3 Group v1
|
||||
isGroupSpell = true;
|
||||
}
|
||||
|
||||
if (isGroupSpell && IsBardSong(spellID)) {
|
||||
isBardSong = true;
|
||||
}
|
||||
|
||||
if (targettype == ST_Target) { //5 Single
|
||||
isSingleTargetSpell = true;
|
||||
}
|
||||
|
||||
if (GetTarget()) {
|
||||
targetBodyType = target->bodytype;
|
||||
}
|
||||
|
||||
if (targettype == ST_Animal) { //9 Animal
|
||||
isSingleTargetSpell = true;
|
||||
}
|
||||
|
||||
if (targettype == ST_Undead) { //10 Undead
|
||||
isSingleTargetSpell = true;
|
||||
}
|
||||
|
||||
if (targettype == ST_Summoned) { //11 Summoned
|
||||
isSingleTargetSpell = true;
|
||||
}
|
||||
|
||||
if (targettype == ST_Tap) { //13 Lifetap
|
||||
isSingleTargetSpell = true;
|
||||
}
|
||||
|
||||
if (targettype == ST_Pet) { //14 Pet
|
||||
isSingleTargetSpell = true;
|
||||
}
|
||||
|
||||
if (targettype == ST_Corpse) { //15 Corpse
|
||||
isSingleTargetSpell = true;
|
||||
}
|
||||
|
||||
if (targettype == ST_Plant) { //16 Plant
|
||||
isSingleTargetSpell = true;
|
||||
}
|
||||
|
||||
if (targettype == ST_Giant) { //17 Uber Giants
|
||||
isSingleTargetSpell = true;
|
||||
}
|
||||
|
||||
if (targettype == ST_Dragon) { //18 Uber Dragons
|
||||
isSingleTargetSpell = true;
|
||||
}
|
||||
|
||||
if (spDat.mana > 0 && manaCurrent < GetActSpellCost(spellID, spDat.mana)) return ELIXIR_NOT_ENOUGH_MANA;
|
||||
if (spDat.endurance_cost > 0 && endCurrent < spDat.endurance_cost) return ELIXIR_NOT_ENOUGH_ENDURANCE;
|
||||
|
||||
if (isLull) return ELIXIR_LULL_IGNORED;
|
||||
if (isMez) return ELIXIR_MEZ_IGNORED;
|
||||
if (isCharm) return ELIXIR_CHARM_IGNORED;
|
||||
|
||||
if (targettype == ST_Animal) { //16 Animal
|
||||
if (target == nullptr) return ELIXIR_NO_TARGET;
|
||||
if (targetBodyType != BT_Animal) return ELIXIR_INVALID_TARGET_BODYTYPE;
|
||||
}
|
||||
|
||||
if (targettype == ST_Undead ) { //10 Undead
|
||||
if (target == nullptr) return ELIXIR_NO_TARGET;
|
||||
if (targetBodyType != BT_Undead) return ELIXIR_INVALID_TARGET_BODYTYPE;
|
||||
}
|
||||
|
||||
if (targettype == ST_Summoned) { //11 Summoned
|
||||
if (target == nullptr) return ELIXIR_NO_TARGET;
|
||||
if (targetBodyType != BT_Summoned) return ELIXIR_INVALID_TARGET_BODYTYPE;
|
||||
}
|
||||
|
||||
if (targettype == ST_Plant) { //Plant
|
||||
if (target == nullptr) return ELIXIR_NO_TARGET;
|
||||
if (targetBodyType != BT_Plant) return ELIXIR_INVALID_TARGET_BODYTYPE;
|
||||
}
|
||||
|
||||
if (isTransport) return ELIXIR_TRANSPORT_IGNORED;
|
||||
|
||||
if (spDat.npc_no_los == 0 && target && isSingleTargetSpell && CheckLosFN(target)) return ELIXIR_NOT_LINE_OF_SIGHT;
|
||||
|
||||
for (int i = 0; i < 4; i++) { // Reagent check
|
||||
if (spDat.component[i] == 0) continue;
|
||||
if (spDat.component_count[i] == -1) continue;
|
||||
if (IsMerc()) continue; //mercs don't have inventory nor require reagents
|
||||
#ifdef BOTS
|
||||
if (IsBot()) continue; //bots don't have inventory nor require reagents
|
||||
#endif
|
||||
return ELIXIR_COMPONENT_REQUIRED;
|
||||
//TODO: teach elixir how to check inventory for component in cases it's a client calling this function
|
||||
}
|
||||
|
||||
//TODO: CasterRequirement logic
|
||||
//DWORD ReqID = pSpell->CasterRequirementID;
|
||||
//if (ReqID == 518 && SpawnPctHPs(pChar->pSpawn) > 89) return "not < 90% hp";
|
||||
|
||||
|
||||
if (skillID == EQ::skills::SkillBackstab) { // 8 backstab
|
||||
if (!target) {
|
||||
return ELIXIR_NO_TARGET;
|
||||
}
|
||||
if (!BehindMob(target)) {
|
||||
return ELIXIR_NOT_NEEDED;
|
||||
}
|
||||
if (!IsWithinSpellRange(target, spDat.range, spellID)) {
|
||||
return ELIXIR_OUT_OF_RANGE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (recourseID > 0) { //recourse buff attached
|
||||
const SPDat_Spell_Struct &spDatRecourse = spells[recourseID];
|
||||
if (spDatRecourse.buff_duration > 0) {
|
||||
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 == recourseID) {
|
||||
return ELIXIR_ALREADY_HAVE_BUFF;
|
||||
}
|
||||
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, recourseID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||
if (stackResult == -1) {
|
||||
return ELIXIR_ALREADY_HAVE_BUFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ticks > 0 && !IsBeneficialSpell(spellID) && targettype == ST_Target) { // debuff
|
||||
if (!target) {
|
||||
return ELIXIR_NO_TARGET;
|
||||
}
|
||||
|
||||
buffCount = target->GetMaxTotalSlots();
|
||||
for (uint32 i = 0; i < buffCount; i++) {
|
||||
auto buff = target->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) {
|
||||
return ELIXIR_ALREADY_HAVE_BUFF;
|
||||
}
|
||||
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||
if (stackResult == -1) {
|
||||
return ELIXIR_ALREADY_HAVE_BUFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (spDat.zone_type == 1 && !zone->CanCastOutdoor()) {
|
||||
return ELIXIR_ZONETYPE_FAIL;
|
||||
}
|
||||
|
||||
//TODO: zone_type 2 check (can't cast outdoors indoor only)
|
||||
|
||||
if (IsEffectInSpell(spellID, SE_Levitate) && !zone->CanLevitate()) {
|
||||
return ELIXIR_ZONETYPE_FAIL;
|
||||
}
|
||||
|
||||
if (!spDat.can_cast_in_combat) {
|
||||
if (IsEngaged()) return ELIXIR_CANNOT_USE_IN_COMBAT;
|
||||
buffCount = target->GetMaxTotalSlots();
|
||||
for (uint32 i = 0; i < buffCount; i++) {
|
||||
auto buff = target->buffs[i];
|
||||
if (buff.spellid == SPELL_UNKNOWN) continue;
|
||||
if (IsDetrimentalSpell(buff.spellid) && buff.ticsremaining > 0 && !DetrimentalSpellAllowsRest(buff.spellid)) {
|
||||
return ELIXIR_CANNOT_USE_IN_COMBAT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IsDisciplineBuff(spellID)) {
|
||||
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) {
|
||||
return ELIXIR_ALREADY_HAVE_BUFF;
|
||||
}
|
||||
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||
if (stackResult == -1) {
|
||||
return ELIXIR_ALREADY_HAVE_BUFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isPetSummon && HasPet()) return ELIXIR_ALREADY_HAVE_PET;
|
||||
|
||||
|
||||
if (targettype == ST_Pet && isHeal) {
|
||||
if (!HasPet()) {
|
||||
return ELIXIR_NO_PET;
|
||||
}
|
||||
if (GetPet()->GetHPRatio() <= healPercent) {
|
||||
return 0;
|
||||
}
|
||||
return ELIXIR_NOT_NEEDED;
|
||||
}
|
||||
|
||||
if (isBuff && targettype == ST_Pet && IsBeneficialSpell(spellID)) {
|
||||
if (!HasPet()) {
|
||||
return ELIXIR_NO_PET;
|
||||
}
|
||||
|
||||
buffCount = GetPet()->GetMaxTotalSlots();
|
||||
for (uint32 i = 0; i < buffCount; i++) {
|
||||
auto buff = GetPet()->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) {
|
||||
return ELIXIR_ALREADY_HAVE_BUFF;
|
||||
}
|
||||
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||
if (stackResult == -1) {
|
||||
return ELIXIR_ALREADY_HAVE_BUFF;
|
||||
}
|
||||
if (!IsWithinSpellRange(GetPet(), spDat.range, spellID)) {
|
||||
return ELIXIR_OUT_OF_RANGE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (isMana && targettype == ST_Self && ticks <= 0 && !isPetSummon) { // self only regen, like harvest, canni
|
||||
if (stunDuration > 0 && IsEngaged()) {
|
||||
return ELIXIR_CANNOT_USE_IN_COMBAT;
|
||||
}
|
||||
|
||||
if (GetManaRatio() > 50) {
|
||||
return ELIXIR_NOT_NEEDED;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (isLifetap && GetHPRatio() <= healPercent) {
|
||||
//TODO: check if it's a group recourse lifetap regen so necros can bond heal group
|
||||
return ELIXIR_NOT_NEEDED;
|
||||
}
|
||||
|
||||
|
||||
if (isGroupSpell && isHeal && ticks == 0) { //self/group instant heals
|
||||
int groupHealCount = 0;
|
||||
|
||||
float sqDistance = spDat.aoe_range * spDat.aoe_range;
|
||||
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 (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;
|
||||
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 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) continue;
|
||||
outMob = grp->members[i];
|
||||
return 1;
|
||||
}
|
||||
return ELIXIR_NOT_NEEDED;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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
|
||||
bool isCombatBuff = false;
|
||||
|
||||
if (IsEngaged() && !isCombatBuff) {
|
||||
return ELIXIR_NOT_NEEDED;
|
||||
}
|
||||
|
||||
// for the time being, any beneficial non-heal single target spells without a duration are skipped
|
||||
// later, we need to add in things like necro mana flow, etc
|
||||
if (ticks == 0) {
|
||||
return ELIXIR_NOT_NEEDED;
|
||||
}
|
||||
|
||||
// always self buff first
|
||||
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;
|
||||
}
|
||||
// TODO: Immune Check
|
||||
}
|
||||
if (isBuffNeeded) {
|
||||
if (target && target->GetID() == GetID()) {
|
||||
return 0;
|
||||
}
|
||||
outMob = this;
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_GROUP_MEMBERS; i++) {
|
||||
if (!grp) break;
|
||||
if (!grp->members[i]) continue;
|
||||
if (!IsWithinSpellRange(grp->members[i], spDat.range, spellID)) continue;
|
||||
|
||||
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) {
|
||||
if (target && target->GetID() == grp->members[i]->GetID()) {
|
||||
return 0;
|
||||
}
|
||||
outMob = grp->members[i];
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ELIXIR_NOT_NEEDED;
|
||||
}
|
||||
|
||||
if (damageAmount > 0 && (targettype == ST_AEClientV1 || targettype == ST_AreaClientOnly || targettype == ST_AECaster)) { // PB AE DD
|
||||
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;
|
||||
targetCount++;
|
||||
}
|
||||
if (targetCount < aeMinimum) {
|
||||
return ELIXIR_NOT_NEEDED;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if (damageAmount > 0 && (targettype == ST_TargetAETap || targettype == ST_AETarget)) { // Target AE DD
|
||||
if (!target) {
|
||||
return ELIXIR_NO_TARGET;
|
||||
}
|
||||
if (!IsWithinSpellRange(target, spDat.range, spellID)) {
|
||||
return ELIXIR_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
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(target->GetPosition(), iter->entity_on_hatelist->GetPosition()) > sqDistance) continue;
|
||||
targetCount++;
|
||||
}
|
||||
if (targetCount < aeMinimum) {
|
||||
return ELIXIR_NOT_NEEDED;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if (targettype == ST_Target || !IsBeneficialSpell(spellID)) { // single target detrimental spell
|
||||
if (!hate_list.IsEntOnHateList(target)) {
|
||||
return ELIXIR_NO_TARGET;
|
||||
}
|
||||
|
||||
if (target->GetHPRatio() <= 0) {
|
||||
return ELIXIR_NOT_NEEDED;
|
||||
}
|
||||
|
||||
if (!IsWithinSpellRange(GetPet(), spDat.range, spellID)) {
|
||||
return ELIXIR_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
if (target->IsMezzed()) {
|
||||
return ELIXIR_NOT_NEEDED;
|
||||
}
|
||||
|
||||
if (!IsAttackAllowed(target)) {
|
||||
return ELIXIR_NOT_NEEDED;
|
||||
}
|
||||
|
||||
if (ticks == 0) {
|
||||
if (!target) {
|
||||
return ELIXIR_NO_TARGET;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (uint32 i = 0; i < buffCount; i++) {
|
||||
auto buff = target->buffs[i];
|
||||
if (buff.spellid == SPELL_UNKNOWN) continue;
|
||||
if (spells[buff.spellid].buff_duration_formula == DF_Permanent) continue;
|
||||
if (buff.spellid == spellID) {
|
||||
return ELIXIR_NOT_NEEDED;
|
||||
}
|
||||
int stackResult = CheckStackConflict(buff.spellid, buff.casterlevel, spellID, GetLevel(), entity_list.GetMobID(buff.casterid), this, i);
|
||||
if (stackResult == -1) {
|
||||
return ELIXIR_NOT_NEEDED;
|
||||
}
|
||||
// TODO: Immune Check
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ELIXIR_UNHANDLED_SPELL;
|
||||
}
|
||||
|
||||
/*
|
||||
for (int i = 0; i < MAX_RAID_MEMBERS; i++) {
|
||||
if (!raid) break;
|
||||
if (!raid->members[i]) continue;
|
||||
if (!raid->members[i].member) continue;
|
||||
//raid->members[i].GroupNumber == gid
|
||||
}
|
||||
*/
|
||||
21
zone/elixir.h
Normal file
21
zone/elixir.h
Normal file
@ -0,0 +1,21 @@
|
||||
enum ElixirError {
|
||||
ELIXIR_UNHANDLED_SPELL = -1,
|
||||
ELIXIR_CANNOT_CAST_BAD_STATE = -2,
|
||||
ELIXIR_NOT_ENOUGH_MANA = -3,
|
||||
ELIXIR_LULL_IGNORED = -4,
|
||||
ELIXIR_MEZ_IGNORED = -5,
|
||||
ELIXIR_CHARM_IGNORED = -6,
|
||||
ELIXIR_NO_TARGET = -7,
|
||||
ELIXIR_INVALID_TARGET_BODYTYPE = -8,
|
||||
ELIXIR_TRANSPORT_IGNORED = -9,
|
||||
ELIXIR_NOT_LINE_OF_SIGHT = -10,
|
||||
ELIXIR_COMPONENT_REQUIRED = -11,
|
||||
ELIXIR_ALREADY_HAVE_BUFF = -12,
|
||||
ELIXIR_ZONETYPE_FAIL = -13,
|
||||
ELIXIR_CANNOT_USE_IN_COMBAT = -14,
|
||||
ELIXIR_NOT_ENOUGH_ENDURANCE = -15,
|
||||
ELIXIR_ALREADY_HAVE_PET = -16,
|
||||
ELIXIR_OUT_OF_RANGE = -17,
|
||||
ELIXIR_NO_PET = -18,
|
||||
ELIXIR_NOT_NEEDED = -19,
|
||||
};
|
||||
156
zone/merc.cpp
156
zone/merc.cpp
@ -2020,6 +2020,10 @@ bool Merc::AICastSpell(int8 iChance, uint32 iSpellTypes) {
|
||||
if(!AI_HasSpells())
|
||||
return false;
|
||||
|
||||
if (RuleB(Mercs, IsMercsElixirEnabled)) {
|
||||
return ElixirAIDetermineSpellToCast();
|
||||
}
|
||||
|
||||
if (iChance < 100) {
|
||||
if (zone->random.Int(0, 100) > iChance){
|
||||
return false;
|
||||
@ -6362,3 +6366,155 @@ uint32 Merc::CalcUpkeepCost(uint32 templateID , uint8 level, uint8 currency_type
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
|
||||
// ElixirAIDetermineSpellToCast is called during Merc::AICastSpell and overrides normal logic
|
||||
// It determines by class which spell to cast
|
||||
bool Merc::ElixirAIDetermineSpellToCast() {
|
||||
MercSpell selectedMercSpell;
|
||||
int8 spellAIResult;
|
||||
Group *grp = GetGroup();
|
||||
|
||||
switch (GetClass()) {
|
||||
case HEALER:
|
||||
selectedMercSpell = GetBestMercSpellForGroupHeal(this);
|
||||
if (ElixirAITryCastSpell(selectedMercSpell, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
selectedMercSpell = GetBestMercSpellForHealOverTime(this);
|
||||
if (ElixirAITryCastSpell(selectedMercSpell, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
selectedMercSpell = GetBestMercSpellForFastHeal(this);
|
||||
if (ElixirAITryCastSpell(selectedMercSpell, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
selectedMercSpell = GetBestMercSpellForRegularSingleTargetHeal(this);
|
||||
if (ElixirAITryCastSpell(selectedMercSpell, 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;
|
||||
selectedMercSpell = GetBestMercSpellForCure(this, grp->members[i]);
|
||||
if (ElixirAITryCastSpell(selectedMercSpell, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (GetManaRatio() > 50) { // healers only offensive or buff at > 50% mana
|
||||
selectedMercSpell = GetBestMercSpellForStun(this);
|
||||
if (ElixirAITryCastSpell(selectedMercSpell)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
selectedMercSpell = GetBestMercSpellForNuke(this);
|
||||
if (ElixirAITryCastSpell(selectedMercSpell)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto buffSpells = GetMercSpellsBySpellType(this, SpellType_Buff);
|
||||
for (auto buffSpell : buffSpells) {
|
||||
if (!ElixirAITryCastSpell(selectedMercSpell)) continue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case MELEEDPS:
|
||||
if (GetTarget() && HasOrMayGetAggro()) {
|
||||
selectedMercSpell = GetFirstMercSpellBySpellType(this, SpellType_Escape);
|
||||
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
selectedMercSpell = GetFirstMercSpellBySpellType(this, SpellType_Nuke);
|
||||
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
selectedMercSpell = GetFirstMercSpellBySpellType(this, SpellType_InCombatBuff);
|
||||
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case TANK:
|
||||
if(CheckAETaunt()) {
|
||||
selectedMercSpell = GetBestMercSpellForAETaunt(this);
|
||||
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||
Log(Logs::General, Logs::Mercenaries, "%s AE Taunting.", GetName());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(CheckTaunt()) {
|
||||
selectedMercSpell = GetBestMercSpellForTaunt(this);
|
||||
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
selectedMercSpell = GetBestMercSpellForHate(this);
|
||||
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
selectedMercSpell = GetFirstMercSpellBySpellType(this, SpellType_Nuke);
|
||||
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
selectedMercSpell = GetFirstMercSpellBySpellType(this, SpellType_InCombatBuff);
|
||||
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case CASTERDPS:
|
||||
if (GetTarget() && HasOrMayGetAggro()) {
|
||||
selectedMercSpell = GetFirstMercSpellBySpellType(this, SpellType_Escape);
|
||||
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
selectedMercSpell = GetFirstMercSpellBySpellType(this, SpellType_Nuke);
|
||||
if (selectedMercSpell.spellid > 0 && ElixirAITryCastSpell(selectedMercSpell)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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
|
||||
bool Merc::ElixirAITryCastSpell(MercSpell mercSpell, bool isHeal) {
|
||||
auto spellID = mercSpell.spellid;
|
||||
if (spellID == 0) return false;
|
||||
|
||||
Mob* outMob;
|
||||
auto spellAIResult = ElixirCastSpellCheck(spellID, outMob);
|
||||
|
||||
if (spellAIResult < 0) return false;
|
||||
|
||||
if (spellAIResult == 0) {
|
||||
AIDoSpellCast(spellID, GetTarget(), -1);
|
||||
if (GetTarget() == this) return true;
|
||||
if (isHeal) MercGroupSay(this, "Casting %s on %s.", spells[spellID].name, GetTarget()->GetCleanName());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (outMob == nullptr) {
|
||||
return false;
|
||||
}
|
||||
AIDoSpellCast(spellID, outMob, -1);
|
||||
if (outMob == this) return true;
|
||||
if (isHeal) MercGroupSay(this, "Casting %s on %s.", spells[spellID].name, outMob->GetCleanName());
|
||||
return true;
|
||||
}
|
||||
@ -178,6 +178,8 @@ public:
|
||||
bool CheckAETaunt();
|
||||
bool CheckConfidence();
|
||||
bool TryHide();
|
||||
bool ElixirAIDetermineSpellToCast();
|
||||
bool ElixirAITryCastSpell(MercSpell mercSpell, bool isHeal = false);
|
||||
|
||||
// stat functions
|
||||
virtual void ScaleStats(int scalepercent, bool setmax = false);
|
||||
|
||||
@ -355,6 +355,7 @@ public:
|
||||
void BeamDirectional(uint16 spell_id, int16 resist_adjust);
|
||||
void ConeDirectional(uint16 spell_id, int16 resist_adjust);
|
||||
void TryOnSpellFinished(Mob *caster, Mob *target, uint16 spell_id);
|
||||
bool IsWithinSpellRange(Mob *target, float spellRange, uint16 spellID);
|
||||
|
||||
//Buff
|
||||
void BuffProcess();
|
||||
@ -854,7 +855,7 @@ public:
|
||||
inline bool HasBaseEffectFocus() const { return (spellbonuses.FocusEffects[focusFcBaseEffects] || aabonuses.FocusEffects[focusFcBaseEffects] || itembonuses.FocusEffects[focusFcBaseEffects]); }
|
||||
int32 GetDualWieldingSameDelayWeapons() const { return dw_same_delay; }
|
||||
inline void SetDualWieldingSameDelayWeapons(int32 val) { dw_same_delay = val; }
|
||||
|
||||
int8 ElixirCastSpellCheck(uint16 spellID, Mob* outMob);
|
||||
bool TryDoubleMeleeRoundEffect();
|
||||
bool GetUseDoubleMeleeRoundDmgBonus() const { return use_double_melee_round_dmg_bonus; }
|
||||
inline void SetUseDoubleMeleeRoundDmgBonus(bool val) { use_double_melee_round_dmg_bonus = val; }
|
||||
|
||||
@ -6327,3 +6327,24 @@ int Client::GetNextAvailableDisciplineSlot(int starting_slot) {
|
||||
|
||||
return -1; // Return -1 if No Slots open
|
||||
}
|
||||
|
||||
// IsWithinRange returns true if target is within range of spell ID casted by mob
|
||||
bool Mob::IsWithinSpellRange(Mob *target, float spellRange, uint16 spellID) {
|
||||
float range = GetActSpellRange(spellID, spellRange, false);
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target->GetID() == GetID()) return true;
|
||||
|
||||
float dist2 = DistanceSquared(GetPosition(), target->GetPosition());
|
||||
float range2 = spellRange * spellRange;
|
||||
float min_range2 = spells[spellID].min_range * spells[spellID].min_range;
|
||||
if(dist2 > range2) {
|
||||
return false;
|
||||
}
|
||||
if (dist2 < min_range2){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user