eqemu-server/zone/elixir.cpp
2021-11-15 20:55:40 -08:00

804 lines
22 KiB
C++

#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
}
*/