mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 12:41:30 +00:00
* [Bots] Cleanup and remove preprocessors. - Removes every `#ifdef BOTS` we have and locks bots behind `Bots:AllowBots` rule. - Bot updates are now done by default similar to regular database updates. - Modify `CMakeLists.txt`, `.drone.yml`, and `BUILD.md` to match the removal of `EQEMU_ENABLE_BOTS`. * Cleanup - Add SQL for enabling bots for servers with bots. - Add message that tells players/operators bots are disabled. * Suggested changes. * Bot injection stuff * Change SQL to bot SQL. * Tweaks * Remove `is_bot` * Update version.h * Update main.cpp * Update database.cpp * Fix name availability crash * Remove bots from update script Co-authored-by: Akkadius <akkadius1@gmail.com>
10413 lines
303 KiB
C++
10413 lines
303 KiB
C++
/* EQEMu: Everquest Server Emulator
|
|
Copyright (C) 2001-2004 EQEMu Development Team (http://eqemu.org)
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY except by those people which sell it, which
|
|
are required to give you total support for your newly bought product;
|
|
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "../common/eqemu_logsys.h"
|
|
#include "../common/bodytypes.h"
|
|
#include "../common/classes.h"
|
|
#include "../common/global_define.h"
|
|
#include "../common/item_instance.h"
|
|
#include "../common/rulesys.h"
|
|
#include "../common/skills.h"
|
|
#include "../common/spdat.h"
|
|
#include "../common/data_verification.h"
|
|
#include "../common/misc_functions.h"
|
|
|
|
#include "quest_parser_collection.h"
|
|
#include "lua_parser.h"
|
|
#include "string_ids.h"
|
|
#include "worldserver.h"
|
|
|
|
#include <math.h>
|
|
|
|
#ifndef WIN32
|
|
#include <stdlib.h>
|
|
#include "../common/unix.h"
|
|
#endif
|
|
|
|
|
|
extern Zone* zone;
|
|
extern volatile bool is_zone_loaded;
|
|
extern WorldServer worldserver;
|
|
|
|
|
|
// the spell can still fail here, if the buff can't stack
|
|
// in this case false will be returned, true otherwise
|
|
bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_override, int reflect_effectiveness, int32 duration_override, bool disable_buff_overwrite)
|
|
{
|
|
int caster_level, buffslot, effect, effect_value, i;
|
|
EQ::ItemInstance *SummonedItem=nullptr;
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
#define _EDLEN 200
|
|
char effect_desc[_EDLEN];
|
|
#endif
|
|
|
|
if(!IsValidSpell(spell_id))
|
|
return false;
|
|
|
|
const SPDat_Spell_Struct &spell = spells[spell_id];
|
|
|
|
if (spell.disallow_sit && IsBuffSpell(spell_id) && IsClient() && (CastToClient()->IsSitting() || CastToClient()->GetHorseId() != 0))
|
|
return false;
|
|
|
|
bool CanMemoryBlurFromMez = true;
|
|
if (IsMezzed()) { //Check for special memory blur behavior when on mez, this needs to be before buff override.
|
|
CanMemoryBlurFromMez = false;
|
|
}
|
|
|
|
bool c_override = false;
|
|
if (caster && caster->IsClient() && GetCastedSpellInvSlot() > 0) {
|
|
const EQ::ItemInstance *inst = caster->CastToClient()->GetInv().GetItem(GetCastedSpellInvSlot());
|
|
if (inst) {
|
|
if (inst->GetItem()->Click.Level > 0) {
|
|
caster_level = inst->GetItem()->Click.Level;
|
|
c_override = true;
|
|
} else {
|
|
caster_level = caster ? caster->GetCasterLevel(spell_id) : GetCasterLevel(spell_id);
|
|
}
|
|
} else if (level_override > 0) {
|
|
caster_level = level_override;
|
|
c_override = true;
|
|
} else {
|
|
caster_level = caster ? caster->GetCasterLevel(spell_id) : GetCasterLevel(spell_id);
|
|
}
|
|
} else if (level_override > 0) {
|
|
caster_level = level_override;
|
|
c_override = true;
|
|
} else {
|
|
caster_level = caster ? caster->GetCasterLevel(spell_id) : GetCasterLevel(spell_id);
|
|
}
|
|
|
|
if(c_override)
|
|
{
|
|
int durat = CalcBuffDuration(caster, this, spell_id, caster_level);
|
|
if(durat) // negatives are perma buffs
|
|
{
|
|
buffslot = AddBuff(caster, spell_id, durat, caster_level);
|
|
if(buffslot == -1) // stacking failure
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
buffslot = -2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(IsBuffSpell(spell_id)){
|
|
if(IsEffectInSpell(spell_id, SE_BindSight))
|
|
{
|
|
if(caster)
|
|
{
|
|
buffslot = caster->AddBuff(caster, spell_id);
|
|
}
|
|
else
|
|
buffslot = -1;
|
|
}
|
|
else
|
|
{
|
|
buffslot = AddBuff(caster, spell_id, duration_override, -1, disable_buff_overwrite);
|
|
}
|
|
if(buffslot == -1) // stacking failure
|
|
return false;
|
|
} else {
|
|
buffslot = -2; //represents not a buff I guess
|
|
}
|
|
}
|
|
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
Message(Chat::White, "You are affected by spell '%s' (id %d)", spell.name, spell_id);
|
|
if(buffslot >= 0)
|
|
{
|
|
Message(Chat::White, "Buff slot: %d Duration: %d tics", buffslot, buffs[buffslot].ticsremaining);
|
|
}
|
|
#endif
|
|
|
|
if(buffslot >= 0)
|
|
{
|
|
buffs[buffslot].melee_rune = 0;
|
|
buffs[buffslot].magic_rune = 0;
|
|
buffs[buffslot].hit_number = 0;
|
|
|
|
if (spells[spell_id].hit_number > 0) {
|
|
|
|
int numhit = spells[spell_id].hit_number;
|
|
|
|
numhit += numhit * caster->GetFocusEffect(focusFcLimitUse, spell_id) / 100;
|
|
numhit += caster->GetFocusEffect(focusIncreaseNumHits, spell_id);
|
|
buffs[buffslot].hit_number = numhit;
|
|
}
|
|
|
|
if (spells[spell_id].endurance_upkeep > 0)
|
|
SetEndurUpkeep(true);
|
|
|
|
if (IsClient() && CastToClient()->ClientVersionBit() & EQ::versions::maskUFAndLater)
|
|
{
|
|
EQApplicationPacket *outapp = MakeBuffsPacket(false);
|
|
CastToClient()->FastQueuePacket(&outapp);
|
|
}
|
|
}
|
|
|
|
std::string export_string = fmt::format(
|
|
"{} {} {} {}",
|
|
caster ? caster->GetID() : 0,
|
|
buffslot >= 0 ? buffs[buffslot].ticsremaining : 0,
|
|
caster ? caster->GetLevel() : 0,
|
|
buffslot
|
|
);
|
|
|
|
if (IsClient()) {
|
|
if (parse->EventSpell(EVENT_SPELL_EFFECT_CLIENT, nullptr, CastToClient(), spell_id, export_string, 0) != 0) {
|
|
CalcBonuses();
|
|
return true;
|
|
}
|
|
} else if (IsNPC()) {
|
|
if (parse->EventSpell(EVENT_SPELL_EFFECT_NPC, this, nullptr, spell_id, export_string, 0) != 0) {
|
|
CalcBonuses();
|
|
return true;
|
|
}
|
|
} else if (IsBot()) {
|
|
if (parse->EventSpell(EVENT_SPELL_EFFECT_BOT, this, nullptr, spell_id, export_string, 0) != 0) {
|
|
CalcBonuses();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if(IsVirusSpell(spell_id)) {
|
|
|
|
if (!viral_timer.Enabled()) {
|
|
viral_timer.Start(1000);
|
|
}
|
|
buffs[buffslot].virus_spread_time = zone->random.Int(GetViralMinSpreadTime(spell_id), GetViralMaxSpreadTime(spell_id));
|
|
}
|
|
|
|
|
|
if (!IsPowerDistModSpell(spell_id))
|
|
SetSpellPowerDistanceMod(0);
|
|
|
|
bool spell_trigger_cast_complete = false; //Used with SE_Spell_Trigger and SE_Chance_Best_in_Spell_Grp, true when spell has been triggered.
|
|
|
|
// if buff slot, use instrument mod there, otherwise calc it
|
|
uint32 instrument_mod = buffslot > -1 ? buffs[buffslot].instrument_mod : caster ? caster->GetInstrumentMod(spell_id) : 10;
|
|
|
|
// iterate through the effects in the spell
|
|
for (i = 0; i < EFFECT_COUNT; i++)
|
|
{
|
|
if(IsBlankSpellEffect(spell_id, i))
|
|
continue;
|
|
|
|
effect = spell.effect_id[i];
|
|
effect_value = CalcSpellEffectValue(spell_id, i, caster_level, instrument_mod, caster ? caster : this);
|
|
|
|
if(spell_id == SPELL_LAY_ON_HANDS && caster && caster->GetAA(aaImprovedLayOnHands))
|
|
effect_value = GetMaxHP();
|
|
|
|
if (GetSpellPowerDistanceMod())
|
|
effect_value = effect_value*GetSpellPowerDistanceMod()/100;
|
|
|
|
//Prevents effect from being applied
|
|
if (spellbonuses.NegateEffects) {
|
|
if (effect != SE_NegateSpellEffect && NegateSpellEffect(spell_id, effect)) {
|
|
if (caster) {
|
|
caster->Message(Chat::Red, "Part or all of this spell has lost its effectiveness."); //Placeholder msg, until live one is obtained.
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
effect_desc[0] = 0;
|
|
#endif
|
|
switch(effect)
|
|
{
|
|
case SE_CurrentHP: // nukes, heals; also regen/dot if a buff
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Current Hitpoints: %+i", effect_value);
|
|
#endif
|
|
// SE_CurrentHP is calculated at first tick if its a dot/buff
|
|
if (buffslot >= 0) {
|
|
//This is here so dots with hit counters tic down on initial cast.
|
|
if (effect_value < 0) {
|
|
caster->GetActDoTDamage(spell_id, effect_value, this, false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (spells[spell_id].limit_value[i] && !PassCastRestriction(spells[spell_id].limit_value[i])) {
|
|
break; //no messages are given on live if this fails.
|
|
}
|
|
|
|
// for offensive spells check if we have a spell rune on
|
|
int64 dmg = effect_value;
|
|
if(dmg < 0)
|
|
{
|
|
|
|
// take partial damage into account
|
|
dmg = (int64) (dmg * partial / 100);
|
|
|
|
//handles AAs and what not...
|
|
if(caster) {
|
|
if (reflect_effectiveness) {
|
|
dmg = caster->GetActReflectedSpellDamage(spell_id, (int64)(spells[spell_id].base_value[i] * partial / 100), reflect_effectiveness);
|
|
}
|
|
else {
|
|
dmg = caster->GetActSpellDamage(spell_id, dmg, this);
|
|
}
|
|
caster->ResourceTap(-dmg, spell_id);
|
|
}
|
|
|
|
if (dmg <= 0) {
|
|
dmg = -dmg;
|
|
Damage(caster, dmg, spell_id, spell.skill, false, buffslot, false);
|
|
}
|
|
//handles custom situation where quest function mitigation put high enough to allow damage to heal.
|
|
else {
|
|
HealDamage(dmg, caster);
|
|
}
|
|
}
|
|
else if(dmg > 0) {
|
|
//healing spell...
|
|
|
|
if(caster)
|
|
dmg = caster->GetActSpellHealing(spell_id, dmg, this);
|
|
|
|
HealDamage(dmg, caster);
|
|
}
|
|
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Current Hitpoints: %+i actual: %+i", effect_value, dmg);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case SE_CurrentHPOnce: // used in buffs usually, see Courage
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Current Hitpoints Once: %+i", effect_value);
|
|
#endif
|
|
if (spells[spell_id].limit_value[i] && !PassCastRestriction(spells[spell_id].limit_value[i])) {
|
|
break; //no messages are given on live if this fails.
|
|
}
|
|
|
|
// hack fix for client health not reflecting server value
|
|
last_hp = 0;
|
|
|
|
int64 dmg = effect_value;
|
|
|
|
//hardcoded for manaburn and life burn
|
|
if (spell_id == SPELL_MANA_BURN || spell_id == SPELL_LIFE_BURN)
|
|
{
|
|
if (spell_id == SPELL_MANA_BURN && caster) //Manaburn
|
|
{
|
|
dmg = caster->GetMana()*-3;
|
|
caster->SetMana(0);
|
|
}
|
|
else if (spell_id == SPELL_LIFE_BURN && caster) //Lifeburn
|
|
{
|
|
dmg = caster->GetHP(); // just your current HP
|
|
caster->SetHP(dmg / 4); // 2003 patch notes say ~ 1/4 HP. Should this be 1/4 your current HP or do 3/4 max HP dmg? Can it kill you?
|
|
dmg = -dmg;
|
|
}
|
|
|
|
if (dmg < 0) {
|
|
dmg = -dmg;
|
|
Damage(caster, dmg, spell_id, spell.skill, false, buffslot, false);
|
|
}
|
|
else {
|
|
HealDamage(dmg, caster);
|
|
}
|
|
break;
|
|
}
|
|
//normal effects
|
|
else {
|
|
if (dmg < 0){
|
|
dmg = (int64)(dmg * partial / 100);
|
|
|
|
if (caster) {
|
|
if (reflect_effectiveness) {
|
|
dmg = caster->GetActReflectedSpellDamage(spell_id, (int64)(spells[spell_id].base_value[i] * partial / 100), reflect_effectiveness);
|
|
}
|
|
else {
|
|
dmg = caster->GetActSpellDamage(spell_id, dmg, this);
|
|
}
|
|
caster->ResourceTap(-dmg, spell_id);
|
|
}
|
|
|
|
if (dmg <= 0) {
|
|
dmg = -dmg;
|
|
Damage(caster, dmg, spell_id, spell.skill, false, buffslot, false);
|
|
}
|
|
else {
|
|
HealDamage(dmg, caster);
|
|
}
|
|
}
|
|
else if (dmg > 0) {
|
|
//do not apply focus/critical to buff spells
|
|
if (caster && !IsEffectInSpell(spell_id, SE_TotalHP)) {
|
|
dmg = caster->GetActSpellHealing(spell_id, dmg, this);
|
|
}
|
|
HealDamage(dmg, caster);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_PercentalHeal:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Percental Heal: %+i (%d%% max)", spell.max_value[i], effect_value);
|
|
#endif
|
|
int64 val = GetMaxHP() * spell.base_value[i] / 100;
|
|
|
|
//This effect can also do damage by percent.
|
|
if (val < 0) {
|
|
|
|
if (spell.max_value[i] && -val > spell.max_value[i])
|
|
val = -spell.max_value[i];
|
|
|
|
if (caster)
|
|
val = caster->GetActSpellDamage(spell_id, val, this);
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
if (spell.max_value[i] && val > spell.max_value[i])
|
|
val = spell.max_value[i];
|
|
|
|
if(caster)
|
|
val = caster->GetActSpellHealing(spell_id, val, this);
|
|
}
|
|
|
|
if (val < 0)
|
|
Damage(caster, -val, spell_id, spell.skill, false, buffslot, false);
|
|
else
|
|
HealDamage(val, caster);
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_CompleteHeal:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Complete Heal");
|
|
#endif
|
|
int64 val = 7500 * effect_value;
|
|
if (caster) {
|
|
val = caster->GetActSpellHealing(spell_id, val, this);
|
|
}
|
|
if (val > 0) {
|
|
HealDamage(val, caster);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_CurrentMana:
|
|
{
|
|
// Bards don't get mana from effects, good or bad.
|
|
if(GetClass() == BARD)
|
|
break;
|
|
if(IsManaTapSpell(spell_id)) {
|
|
if(GetCasterClass() != 'N') {
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Current Mana: %+i", effect_value);
|
|
#endif
|
|
SetMana(GetMana() + effect_value);
|
|
if (caster)
|
|
caster->SetMana(caster->GetMana() + std::abs(effect_value));
|
|
|
|
if (effect_value < 0)
|
|
TryTriggerOnCastRequirement();
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
if (caster)
|
|
caster->Message(Chat::White, "You have gained %+i mana!", effect_value);
|
|
#endif
|
|
}
|
|
}
|
|
else {
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Current Mana: %+i", effect_value);
|
|
#endif
|
|
if (buffslot >= 0)
|
|
break;
|
|
|
|
SetMana(GetMana() + effect_value);
|
|
if (effect_value < 0)
|
|
TryTriggerOnCastRequirement();
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_CurrentManaOnce:
|
|
{
|
|
// Bards don't get mana from effects, good or bad.
|
|
if(GetClass() == BARD)
|
|
break;
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Current Mana Once: %+i", effect_value);
|
|
#endif
|
|
SetMana(GetMana() + effect_value);
|
|
break;
|
|
}
|
|
|
|
case SE_Translocate:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Translocate: %s %d %d %d heading %d",
|
|
spell.teleport_zone, spell.base_value[1], spell.base_value[0],
|
|
spell.base_value[2], spell.base_value[3]
|
|
);
|
|
#endif
|
|
if(IsClient())
|
|
{
|
|
|
|
if(caster)
|
|
CastToClient()->SendOPTranslocateConfirm(caster, spell_id);
|
|
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Succor:
|
|
{
|
|
|
|
float x, y, z, heading;
|
|
const char *target_zone = nullptr;
|
|
|
|
x = static_cast<float>(spell.base_value[1]);
|
|
y = static_cast<float>(spell.base_value[0]);
|
|
z = static_cast<float>(spell.base_value[2]);
|
|
heading = static_cast<float>(spell.base_value[3]);
|
|
|
|
if(!strcmp(spell.teleport_zone, "same"))
|
|
{
|
|
target_zone = 0;
|
|
}
|
|
else
|
|
{
|
|
target_zone = spell.teleport_zone;
|
|
if(IsNPC() && target_zone != zone->GetShortName()){
|
|
if(!GetOwner()){
|
|
CastToNPC()->Depop();
|
|
break;
|
|
}else{
|
|
if(!GetOwner()->IsClient())
|
|
CastToNPC()->Depop();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(IsClient())
|
|
{
|
|
if(zone->random.Roll(RuleI(Spells, SuccorFailChance))) { //2% Fail chance by default
|
|
|
|
if(IsClient()) {
|
|
CastToClient()->MessageString(Chat::SpellFailure,SUCCOR_FAIL);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Below are the spellid's for known evac/succor spells that send player
|
|
// to the current zone's safe points.
|
|
|
|
// Succor = 1567
|
|
// Lesser Succor = 2183
|
|
// Evacuate = 1628
|
|
// Lesser Evacuate = 2184
|
|
// Decession = 2558
|
|
// Greater Decession = 3244
|
|
// Egress = 1566
|
|
|
|
if(!target_zone) {
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
LogDebug("Succor/Evacuation Spell In Same Zone");
|
|
#endif
|
|
if(IsClient())
|
|
CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), x, y, z, heading, 0, EvacToSafeCoords);
|
|
else
|
|
GMMove(x, y, z, heading);
|
|
}
|
|
else {
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
LogDebug("Succor/Evacuation Spell To Another Zone");
|
|
#endif
|
|
if(IsClient())
|
|
CastToClient()->MovePC(target_zone, x, y, z, heading);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case SE_GateCastersBindpoint: // Used on Teleport Bind.
|
|
case SE_Teleport: // gates, rings, circles, etc
|
|
case SE_Teleport2:
|
|
{
|
|
float x, y, z, heading;
|
|
const char *target_zone = nullptr;
|
|
|
|
x = static_cast<float>(spell.base_value[1]);
|
|
y = static_cast<float>(spell.base_value[0]);
|
|
z = static_cast<float>(spell.base_value[2]);
|
|
heading = static_cast<float>(spell.base_value[3]);
|
|
|
|
if(!strcmp(spell.teleport_zone, "same"))
|
|
{
|
|
target_zone = 0;
|
|
}
|
|
else
|
|
{
|
|
target_zone = spell.teleport_zone;
|
|
|
|
if(IsNPC() && target_zone != zone->GetShortName()){
|
|
if(!GetOwner()){
|
|
CastToNPC()->Depop();
|
|
break;
|
|
}else{
|
|
if(!GetOwner()->IsClient())
|
|
CastToNPC()->Depop();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (effect == SE_GateCastersBindpoint && caster && caster->IsClient())
|
|
{ // Teleport Bind uses caster's bind point
|
|
int index = spells[spell_id].base_value[i] - 1;
|
|
if (index < 0 || index > 4)
|
|
index = 0;
|
|
x = caster->CastToClient()->GetBindX(index);
|
|
y = caster->CastToClient()->GetBindY(index);
|
|
z = caster->CastToClient()->GetBindZ(index);
|
|
heading = caster->CastToClient()->GetBindHeading(index);
|
|
//target_zone = caster->CastToClient()->GetBindZoneId(); target_zone doesn't work due to const char
|
|
CastToClient()->MovePC(caster->CastToClient()->GetBindZoneID(index), 0, x, y, z, heading);
|
|
break;
|
|
}
|
|
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
const char *efstr = "Teleport";
|
|
if(effect == SE_Teleport)
|
|
efstr = "Teleport v1";
|
|
else if(effect == SE_Teleport2)
|
|
efstr = "Teleport v2";
|
|
else if(effect == SE_Succor)
|
|
efstr = "Succor";
|
|
|
|
snprintf(effect_desc, _EDLEN,
|
|
"%s: %0.2f, %0.2f, %0.2f heading %0.2f in %s",
|
|
efstr, x, y, z, heading, target_zone ? target_zone : "same zone"
|
|
);
|
|
#endif
|
|
if(IsClient())
|
|
{
|
|
if(!target_zone)
|
|
CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), x, y, z, heading);
|
|
else
|
|
CastToClient()->MovePC(target_zone, x, y, z, heading);
|
|
}
|
|
else{
|
|
if(!target_zone)
|
|
GMMove(x, y, z, heading);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_FleshToBone:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Flesh To Bone");
|
|
#endif
|
|
if(IsClient()){
|
|
EQ::ItemInstance* transI = CastToClient()->GetInv().GetItem(EQ::invslot::slotCursor);
|
|
if (transI && transI->IsClassCommon() && transI->IsStackable()){
|
|
int16 fcharges = transI->GetCharges();
|
|
//Does it sound like meat... maybe should check if it looks like meat too...
|
|
if(strstr(transI->GetItem()->Name, "meat") ||
|
|
strstr(transI->GetItem()->Name, "Meat") ||
|
|
strstr(transI->GetItem()->Name, "flesh") ||
|
|
strstr(transI->GetItem()->Name, "Flesh") ||
|
|
strstr(transI->GetItem()->Name, "parts") ||
|
|
strstr(transI->GetItem()->Name, "Parts")){
|
|
CastToClient()->DeleteItemInInventory(EQ::invslot::slotCursor, fcharges, true);
|
|
CastToClient()->SummonItem(13073, fcharges);
|
|
}
|
|
else{
|
|
Message(Chat::Red, "You can only transmute flesh to bone.");
|
|
}
|
|
} else{
|
|
Message(Chat::Red, "You can only transmute flesh to bone.");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_GroupFearImmunity:{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Group Fear Immunity");
|
|
#endif
|
|
//Added client messages to give some indication this effect is active.
|
|
// Is there a message generated? Too disgusted by raids.
|
|
uint32 time = spell.base_value[i] * 10 * 1000;
|
|
if (caster && caster->IsClient()) {
|
|
if (caster->IsGrouped()) {
|
|
auto group = caster->GetGroup();
|
|
for (int i = 0; i < 6; ++i)
|
|
if (group->members[i])
|
|
group->members[i]->aa_timers[aaTimerWarcry].Start(time);
|
|
} else if (caster->IsRaidGrouped()) {
|
|
auto raid = caster->GetRaid();
|
|
uint32 gid = raid->GetGroup(caster->CastToClient());
|
|
if (gid < 12)
|
|
for (int i = 0; i < MAX_RAID_MEMBERS; ++i)
|
|
if (raid->members[i].member && raid->members[i].GroupNumber == gid)
|
|
raid->members[i].member->aa_timers[aaTimerWarcry].Start(time);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_AddFaction:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Faction Mod: %+i", effect_value);
|
|
#endif
|
|
// EverHood
|
|
if(caster && GetPrimaryFaction()>0) {
|
|
caster->AddFactionBonus(GetPrimaryFaction(),effect_value);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Stun:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Stun: %d msec", effect_value);
|
|
#endif
|
|
//Typically we check for immunities else where but since stun immunities are different and only
|
|
//Block the stun part and not the whole spell, we do it here, also do the message here so we wont get the message on a resist
|
|
int max_level = spell.max_value[i];
|
|
//max_level of 0 means we assume a default of 55.
|
|
if (max_level == 0)
|
|
max_level = RuleI(Spells, BaseImmunityLevel);
|
|
// NPCs get to ignore max_level for their spells.
|
|
// Ignore if spell is beneficial (ex. Harvest)
|
|
if (IsDetrimentalSpell(spell.id) && (GetSpecialAbility(UNSTUNABLE) ||
|
|
((GetLevel() > max_level) && caster && (!caster->IsNPC() ||
|
|
(caster->IsNPC() && !RuleB(Spells, NPCIgnoreBaseImmunity))))))
|
|
{
|
|
if (caster)
|
|
caster->MessageString(Chat::SpellFailure, IMMUNE_STUN);
|
|
} else {
|
|
int stun_resist = itembonuses.StunResist+spellbonuses.StunResist;
|
|
if (IsClient() || IsBot()) {
|
|
stun_resist += aabonuses.StunResist;
|
|
}
|
|
|
|
if (stun_resist <= 0 || zone->random.Int(0,99) >= stun_resist) {
|
|
LogCombat("Stunned. We had [{}] percent resist chance", stun_resist);
|
|
|
|
if (caster && (caster->IsClient() || caster->IsBot())) {
|
|
effect_value += effect_value*caster->GetFocusEffect(focusFcStunTimeMod, spell_id)/100;
|
|
}
|
|
|
|
Stun(effect_value);
|
|
} else {
|
|
if (IsClient())
|
|
MessageString(Chat::Stun, SHAKE_OFF_STUN);
|
|
|
|
LogCombat("Stun Resisted. We had [{}] percent resist chance", stun_resist);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Charm:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Charm: %+i (up to lvl %d)", effect_value, spell.max_value[i]);
|
|
#endif
|
|
|
|
if (!caster) { // can't be someone's pet unless we know who that someone is
|
|
break;
|
|
}
|
|
|
|
if (IsNPC()) {
|
|
CastToNPC()->SaveGuardSpotCharm();
|
|
}
|
|
InterruptSpell();
|
|
entity_list.RemoveDebuffs(this);
|
|
entity_list.RemoveFromHateLists(this);
|
|
WipeHateList();
|
|
|
|
Mob *my_pet = GetPet();
|
|
if(my_pet)
|
|
{
|
|
my_pet->Kill();
|
|
}
|
|
|
|
caster->SetPet(this);
|
|
SetOwnerID(caster->GetID());
|
|
SetPetOrder(SPO_Follow);
|
|
SetAppearance(eaStanding);
|
|
// Client has saved previous pet sit/stand - make all new pets
|
|
// stand on charm.
|
|
if (caster->IsClient()) {
|
|
caster->CastToClient()->SetPetCommandState(PET_BUTTON_SIT,0);
|
|
}
|
|
|
|
SetPetType(petCharmed);
|
|
|
|
if(caster->IsClient()){
|
|
auto app = new EQApplicationPacket(OP_Charm, sizeof(Charm_Struct));
|
|
Charm_Struct *ps = (Charm_Struct*)app->pBuffer;
|
|
ps->owner_id = caster->GetID();
|
|
ps->pet_id = GetID();
|
|
ps->command = 1;
|
|
entity_list.QueueClients(this, app);
|
|
safe_delete(app);
|
|
SendPetBuffsToClient();
|
|
SendAppearancePacket(AT_Pet, caster->GetID(), true, true);
|
|
}
|
|
|
|
if (IsClient())
|
|
{
|
|
CastToClient()->AI_Start();
|
|
} else if(IsNPC()) {
|
|
CastToNPC()->SetPetSpellID(0); //not a pet spell.
|
|
CastToNPC()->ModifyStatsOnCharm(false);
|
|
}
|
|
|
|
bool bBreak = false;
|
|
|
|
// define spells with fixed duration
|
|
// charm spells with -1 in field 209 are all of fixed duration, so lets use that instead of spell_ids
|
|
if(spells[spell_id].no_resist)
|
|
bBreak = true;
|
|
|
|
if (!bBreak)
|
|
{
|
|
int resistMod = static_cast<int>(partial) + (GetCHA()/25);
|
|
resistMod = resistMod > 100 ? 100 : resistMod;
|
|
buffs[buffslot].ticsremaining = resistMod * buffs[buffslot].ticsremaining / 100;
|
|
}
|
|
|
|
if (IsClient() || IsBot())
|
|
{
|
|
if(buffs[buffslot].ticsremaining > RuleI(Character, MaxCharmDurationForPlayerCharacter))
|
|
buffs[buffslot].ticsremaining = RuleI(Character, MaxCharmDurationForPlayerCharacter);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
case SE_SenseDead:
|
|
case SE_SenseSummoned:
|
|
case SE_SenseAnimals:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Sense Target: %+i", effect_value);
|
|
#endif
|
|
if(IsClient())
|
|
{
|
|
if (CastToClient()->ClientVersionBit() & EQ::versions::maskSoDAndLater)
|
|
{
|
|
bodyType bt = BT_Undead;
|
|
|
|
int MessageID = SENSE_UNDEAD;
|
|
|
|
if(effect == SE_SenseSummoned)
|
|
{
|
|
bt = BT_Summoned;
|
|
MessageID = SENSE_SUMMONED;
|
|
}
|
|
else if(effect == SE_SenseAnimals)
|
|
{
|
|
bt = BT_Animal;
|
|
MessageID = SENSE_ANIMAL;
|
|
}
|
|
|
|
Mob *ClosestMob = entity_list.GetClosestMobByBodyType(this, bt, true);
|
|
|
|
if(ClosestMob)
|
|
{
|
|
MessageString(Chat::Spells, MessageID);
|
|
SetHeading(CalculateHeadingToTarget(ClosestMob->GetX(), ClosestMob->GetY()));
|
|
SetTarget(ClosestMob);
|
|
CastToClient()->SendTargetCommand(ClosestMob->GetID());
|
|
SentPositionPacket(0.0f, 0.0f, 0.0f, 0.0f, 0, true);
|
|
}
|
|
else
|
|
MessageString(Chat::Red, SENSE_NOTHING);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Fear:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Fear: %+i", effect_value);
|
|
#endif
|
|
if (IsClient() || IsBot())
|
|
{
|
|
if (buffs[buffslot].ticsremaining > RuleI(Character, MaxFearDurationForPlayerCharacter)) {
|
|
buffs[buffslot].ticsremaining = RuleI(Character, MaxFearDurationForPlayerCharacter);
|
|
}
|
|
}
|
|
|
|
|
|
if (RuleB(Combat, EnableFearPathing)) {
|
|
if (IsClient())
|
|
{
|
|
CastToClient()->AI_Start();
|
|
}
|
|
|
|
CalculateNewFearpoint();
|
|
if (currently_fleeing)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Stun(buffs[buffslot].ticsremaining * 6000 - (6000 - tic_timer.GetRemainingTime()));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_BindAffinity: //TO DO: Add support for secondary and tertiary gate abilities
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Bind Affinity");
|
|
#endif
|
|
if (IsClient())
|
|
{
|
|
if(CastToClient()->GetGM() || RuleB(Character, BindAnywhere))
|
|
{
|
|
auto action_packet =
|
|
new EQApplicationPacket(OP_Action, sizeof(Action_Struct));
|
|
Action_Struct* action = (Action_Struct*) action_packet->pBuffer;
|
|
auto message_packet =
|
|
new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct));
|
|
CombatDamage_Struct *cd = (CombatDamage_Struct *)message_packet->pBuffer;
|
|
|
|
action->target = GetID();
|
|
action->source = caster ? caster->GetID() : GetID();
|
|
action->level = 65;
|
|
action->instrument_mod = 10;
|
|
action->hit_heading = GetHeading();
|
|
action->type = 231;
|
|
action->spell = spell_id;
|
|
action->effect_flag = 4;
|
|
|
|
cd->target = action->target;
|
|
cd->source = action->source;
|
|
cd->type = action->type;
|
|
cd->spellid = action->spell;
|
|
cd->hit_heading = action->hit_heading;
|
|
|
|
CastToClient()->QueuePacket(action_packet);
|
|
if(caster && caster->IsClient() && caster != this)
|
|
caster->CastToClient()->QueuePacket(action_packet);
|
|
|
|
CastToClient()->QueuePacket(message_packet);
|
|
if(caster && caster->IsClient() && caster != this)
|
|
caster->CastToClient()->QueuePacket(message_packet);
|
|
|
|
CastToClient()->SetBindPoint(spells[spell_id].base_value[i] - 1);
|
|
Save();
|
|
safe_delete(action_packet);
|
|
safe_delete(message_packet);
|
|
}
|
|
else
|
|
{
|
|
if(!zone->CanBind())
|
|
{
|
|
MessageString(Chat::SpellFailure, CANNOT_BIND);
|
|
break;
|
|
}
|
|
if(!zone->IsCity())
|
|
{
|
|
if(caster != this)
|
|
{
|
|
MessageString(Chat::SpellFailure, CANNOT_BIND);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
auto action_packet = new EQApplicationPacket(
|
|
OP_Action, sizeof(Action_Struct));
|
|
Action_Struct* action = (Action_Struct*) action_packet->pBuffer;
|
|
auto message_packet = new EQApplicationPacket(
|
|
OP_Damage, sizeof(CombatDamage_Struct));
|
|
CombatDamage_Struct *cd = (CombatDamage_Struct *)message_packet->pBuffer;
|
|
|
|
action->target = GetID();
|
|
action->source = caster ? caster->GetID() : GetID();
|
|
action->level = 65;
|
|
action->instrument_mod = 10;
|
|
action->hit_heading = GetHeading();
|
|
action->type = 231;
|
|
action->spell = spell_id;
|
|
action->effect_flag = 4;
|
|
|
|
cd->target = action->target;
|
|
cd->source = action->source;
|
|
cd->type = action->type;
|
|
cd->spellid = action->spell;
|
|
cd->hit_heading = action->hit_heading;
|
|
|
|
CastToClient()->QueuePacket(action_packet);
|
|
if(caster->IsClient() && caster != this)
|
|
caster->CastToClient()->QueuePacket(action_packet);
|
|
|
|
CastToClient()->QueuePacket(message_packet);
|
|
if(caster->IsClient() && caster != this)
|
|
caster->CastToClient()->QueuePacket(message_packet);
|
|
|
|
CastToClient()->SetBindPoint(spells[spell_id].base_value[i] - 1);
|
|
Save();
|
|
safe_delete(action_packet);
|
|
safe_delete(message_packet);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto action_packet =
|
|
new EQApplicationPacket(OP_Action, sizeof(Action_Struct));
|
|
Action_Struct* action = (Action_Struct*) action_packet->pBuffer;
|
|
auto message_packet = new EQApplicationPacket(
|
|
OP_Damage, sizeof(CombatDamage_Struct));
|
|
CombatDamage_Struct *cd = (CombatDamage_Struct *)message_packet->pBuffer;
|
|
|
|
action->target = GetID();
|
|
action->source = caster ? caster->GetID() : GetID();
|
|
action->level = 65;
|
|
action->instrument_mod = 10;
|
|
action->hit_heading = GetHeading();
|
|
action->type = 231;
|
|
action->spell = spell_id;
|
|
action->effect_flag = 4;
|
|
|
|
cd->target = action->target;
|
|
cd->source = action->source;
|
|
cd->type = action->type;
|
|
cd->spellid = action->spell;
|
|
cd->hit_heading = action->hit_heading;
|
|
|
|
CastToClient()->QueuePacket(action_packet);
|
|
if(caster->IsClient() && caster != this)
|
|
caster->CastToClient()->QueuePacket(action_packet);
|
|
|
|
CastToClient()->QueuePacket(message_packet);
|
|
if(caster->IsClient() && caster != this)
|
|
caster->CastToClient()->QueuePacket(message_packet);
|
|
|
|
CastToClient()->SetBindPoint(spells[spell_id].base_value[i] - 1);
|
|
Save();
|
|
safe_delete(action_packet);
|
|
safe_delete(message_packet);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Gate:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Gate");
|
|
#endif
|
|
if(!spellbonuses.AntiGate){
|
|
|
|
if(zone->random.Roll(effect_value))
|
|
Gate(spells[spell_id].limit_value[i] - 1);
|
|
else if (caster)
|
|
caster->MessageString(Chat::SpellFailure,GATE_FAIL);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_CancelMagic:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Cancel Magic: %d", effect_value);
|
|
#endif
|
|
if(GetSpecialAbility(UNDISPELLABLE)){
|
|
if (caster)
|
|
caster->MessageString(Chat::SpellFailure, SPELL_NO_EFFECT, spells[spell_id].name);
|
|
break;
|
|
}
|
|
int buff_count = GetMaxTotalSlots();
|
|
for(int slot = 0; slot < buff_count; slot++) {
|
|
if( buffs[slot].spellid != SPELL_UNKNOWN &&
|
|
spells[buffs[slot].spellid].dispel_flag == 0 &&
|
|
!IsDiscipline(buffs[slot].spellid))
|
|
{
|
|
if (caster && TryDispel(caster->GetCasterLevel(spell_id), buffs[slot].casterlevel, effect_value)){
|
|
BuffFadeBySlot(slot);
|
|
slot = buff_count;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_DispelDetrimental:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Dispel Detrimental: %d", effect_value);
|
|
#endif
|
|
if(GetSpecialAbility(UNDISPELLABLE)){
|
|
if (caster)
|
|
caster->MessageString(Chat::SpellFailure, SPELL_NO_EFFECT, spells[spell_id].name);
|
|
break;
|
|
}
|
|
/*
|
|
TODO: Parsing shows there is no level modifier. However, a consistent -2% modifer was
|
|
found on spell with value 950 (95% spells would have 7% failure rates).
|
|
Further investigation is needed. ~ Kayen
|
|
*/
|
|
int chance = spells[spell_id].base_value[i];
|
|
int buff_count = GetMaxTotalSlots();
|
|
for(int slot = 0; slot < buff_count; slot++) {
|
|
if (buffs[slot].spellid != SPELL_UNKNOWN &&
|
|
IsDetrimentalSpell(buffs[slot].spellid) &&
|
|
spells[buffs[slot].spellid].dispel_flag == 0)
|
|
{
|
|
if (zone->random.Int(1, 1000) <= chance){
|
|
BuffFadeBySlot(slot);
|
|
slot = buff_count;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_DispelBeneficial:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Dispel Beneficial: %d", effect_value);
|
|
#endif
|
|
if(GetSpecialAbility(UNDISPELLABLE)){
|
|
if (caster)
|
|
caster->MessageString(Chat::SpellFailure, SPELL_NO_EFFECT, spells[spell_id].name);
|
|
break;
|
|
}
|
|
|
|
int chance = spells[spell_id].base_value[i];
|
|
int buff_count = GetMaxTotalSlots();
|
|
for(int slot = 0; slot < buff_count; slot++) {
|
|
if (buffs[slot].spellid != SPELL_UNKNOWN &&
|
|
IsBeneficialSpell(buffs[slot].spellid) &&
|
|
spells[buffs[slot].spellid].dispel_flag == 0)
|
|
{
|
|
if (zone->random.Int(1, 1000) <= chance) {
|
|
BuffFadeBySlot(slot);
|
|
slot = buff_count;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Purify:
|
|
{
|
|
//Attempt to remove up to base amount of detrimental effects (excluding charm, fear, resurrection, and revival sickness).
|
|
int purify_count = spells[spell_id].base_value[i];
|
|
if (purify_count > GetMaxTotalSlots()) {
|
|
purify_count = GetMaxTotalSlots();
|
|
}
|
|
|
|
for(int slot = 0; slot < purify_count; slot++) {
|
|
if (IsValidSpell(buffs[slot].spellid) && IsDetrimentalSpell(buffs[slot].spellid)){
|
|
|
|
if (!IsEffectInSpell(buffs[slot].spellid, SE_Charm) &&
|
|
!IsEffectInSpell(buffs[slot].spellid, SE_Fear) &&
|
|
buffs[slot].spellid != SPELL_RESURRECTION_SICKNESS &&
|
|
buffs[slot].spellid != SPELL_RESURRECTION_SICKNESS2 &&
|
|
buffs[slot].spellid != SPELL_RESURRECTION_SICKNESS3 &&
|
|
buffs[slot].spellid != SPELL_RESURRECTION_SICKNESS4 &&
|
|
buffs[slot].spellid != SPELL_REVIVAL_SICKNESS)
|
|
{
|
|
BuffFadeBySlot(slot);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Mez:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Mesmerize");
|
|
#endif
|
|
Mesmerize();
|
|
break;
|
|
}
|
|
|
|
case SE_SummonItem:
|
|
{
|
|
const EQ::ItemData *item = database.GetItem(spell.base_value[i]);
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
const char *itemname = item ? item->Name : "*Unknown Item*";
|
|
snprintf(effect_desc, _EDLEN, "Summon Item: %s (id %d)", itemname, spell.base_value[i]);
|
|
#endif
|
|
if (!item) {
|
|
Message(Chat::Red, "Unable to summon item %d. Item not found.", spell.base_value[i]);
|
|
} else if (IsClient()) {
|
|
Client *c = CastToClient();
|
|
if (c->CheckLoreConflict(item)) {
|
|
c->DuplicateLoreMessage(spell.base_value[i]);
|
|
} else {
|
|
int charges;
|
|
if (item->Stackable)
|
|
charges = (spell.formula[i] > item->StackSize) ? item->StackSize : spell.formula[i];
|
|
else if (item->MaxCharges) // mod rods etc
|
|
charges = item->MaxCharges;
|
|
else
|
|
charges = 1;
|
|
|
|
if (charges < 1)
|
|
charges = 1;
|
|
|
|
if (SummonedItem) {
|
|
c->PushItemOnCursor(*SummonedItem);
|
|
c->SendItemPacket(EQ::invslot::slotCursor, SummonedItem, ItemPacketLimbo);
|
|
safe_delete(SummonedItem);
|
|
}
|
|
SummonedItem = database.CreateItem(spell.base_value[i], charges);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case SE_SummonItemIntoBag:
|
|
{
|
|
const EQ::ItemData *item = database.GetItem(spell.base_value[i]);
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
const char *itemname = item ? item->Name : "*Unknown Item*";
|
|
snprintf(effect_desc, _EDLEN, "Summon Item In Bag: %s (id %d)", itemname, spell.base_value[i]);
|
|
#endif
|
|
uint8 slot;
|
|
|
|
if (!SummonedItem || !SummonedItem->IsClassBag()) {
|
|
if (caster)
|
|
caster->Message(Chat::Red, "SE_SummonItemIntoBag but no bag has been summoned!");
|
|
} else if ((slot = SummonedItem->FirstOpenSlot()) == 0xff) {
|
|
if (caster)
|
|
caster->Message(Chat::Red, "SE_SummonItemIntoBag but no room in summoned bag!");
|
|
} else if (IsClient()) {
|
|
if (CastToClient()->CheckLoreConflict(item)) {
|
|
CastToClient()->DuplicateLoreMessage(spell.base_value[i]);
|
|
} else {
|
|
int charges;
|
|
|
|
if (item->Stackable)
|
|
charges = (spell.formula[i] > item->StackSize) ? item->StackSize : spell.formula[i];
|
|
else if (item->MaxCharges) // mod rods, not sure if there are actual examples of this for IntoBag
|
|
charges = item->MaxCharges;
|
|
else
|
|
charges = 1;
|
|
|
|
if (charges < 1)
|
|
charges = 1;
|
|
|
|
EQ::ItemInstance *SubItem = database.CreateItem(spell.base_value[i], charges);
|
|
if (SubItem != nullptr) {
|
|
SummonedItem->PutItem(slot, *SubItem);
|
|
safe_delete(SubItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_SummonBSTPet:
|
|
case SE_NecPet:
|
|
case SE_SummonPet:
|
|
case SE_Familiar:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Summon %s: %s", (effect==SE_Familiar)?"Familiar":"Pet", spell.teleport_zone);
|
|
#endif
|
|
if(GetPet())
|
|
{
|
|
MessageString(Chat::Shout, ONLY_ONE_PET);
|
|
}
|
|
else
|
|
{
|
|
MakePet(spell_id, spell.teleport_zone);
|
|
// TODO: we need to sync the states for these clients ...
|
|
// Will fix buttons for now
|
|
Mob *pet=GetPet();
|
|
if (IsClient() && pet) {
|
|
auto c = CastToClient();
|
|
if (c->ClientVersionBit() & EQ::versions::maskUFAndLater) {
|
|
c->SetPetCommandState(PET_BUTTON_SIT, 0);
|
|
c->SetPetCommandState(PET_BUTTON_STOP, 0);
|
|
c->SetPetCommandState(PET_BUTTON_REGROUP, 0);
|
|
c->SetPetCommandState(PET_BUTTON_FOLLOW, 1);
|
|
c->SetPetCommandState(PET_BUTTON_GUARD, 0);
|
|
// Creating pet from spell - taunt always false
|
|
// If suspended pet - that will be restore there
|
|
// If logging in, client will send toggle
|
|
c->SetPetCommandState(PET_BUTTON_HOLD, 0);
|
|
c->SetPetCommandState(PET_BUTTON_GHOLD, 0);
|
|
c->SetPetCommandState(PET_BUTTON_FOCUS, 0);
|
|
c->SetPetCommandState(PET_BUTTON_SPELLHOLD, 0);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_DivineAura:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Invulnerability");
|
|
#endif
|
|
if(spell_id==4789) // Touch of the Divine - Divine Save
|
|
buffs[buffslot].ticsremaining = spells[spell_id].buff_duration; // Prevent focus/aa buff extension
|
|
|
|
SetInvul(true);
|
|
break;
|
|
}
|
|
|
|
case SE_ShadowStep:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Shadow Step: %d", effect_value);
|
|
#endif
|
|
if(IsNPC()) // see Song of Highsun - sends mob home
|
|
{
|
|
Gate();
|
|
}
|
|
// shadow step is handled by client already, nothing required
|
|
break;
|
|
}
|
|
|
|
case SE_Blind:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Blind: %+i", effect_value);
|
|
#endif
|
|
// 'cure blind'
|
|
if (BeneficialSpell(spell_id) && spells[spell_id].buff_duration == 0) {
|
|
int buff_count = GetMaxBuffSlots();
|
|
for (int slot = 0; slot < buff_count; slot++) {
|
|
if (buffs[slot].spellid != SPELL_UNKNOWN && IsEffectInSpell(buffs[slot].spellid, SE_Blind)) {
|
|
if (caster && TryDispel(caster->GetCasterLevel(spell_id), buffs[slot].casterlevel, 1)) {
|
|
BuffFadeBySlot(slot);
|
|
slot = buff_count;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (!IsClient()) {
|
|
CalculateNewFearpoint();
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Rune:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Melee Absorb Rune: %+i", effect_value);
|
|
#endif
|
|
buffs[buffslot].melee_rune = effect_value;
|
|
break;
|
|
}
|
|
|
|
case SE_AbsorbMagicAtt:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Spell Absorb Rune: %+i", effect_value);
|
|
#endif
|
|
if(effect_value > 0)
|
|
buffs[buffslot].magic_rune = effect_value;
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_MitigateMeleeDamage:
|
|
{
|
|
buffs[buffslot].melee_rune = spells[spell_id].max_value[i];
|
|
break;
|
|
}
|
|
|
|
case SE_MeleeThresholdGuard:
|
|
{
|
|
buffs[buffslot].melee_rune = spells[spell_id].max_value[i];
|
|
break;
|
|
}
|
|
|
|
case SE_SpellThresholdGuard:
|
|
{
|
|
buffs[buffslot].magic_rune = spells[spell_id].max_value[i];
|
|
break;
|
|
}
|
|
|
|
case SE_MitigateSpellDamage:
|
|
{
|
|
buffs[buffslot].magic_rune = spells[spell_id].max_value[i];
|
|
break;
|
|
}
|
|
|
|
case SE_MitigateDotDamage:
|
|
{
|
|
buffs[buffslot].dot_rune = spells[spell_id].max_value[i];
|
|
break;
|
|
}
|
|
|
|
case SE_DistanceRemoval:
|
|
{
|
|
buffs[buffslot].caston_x = int(GetX());
|
|
buffs[buffslot].caston_y = int(GetY());
|
|
buffs[buffslot].caston_z = int(GetZ());
|
|
break;
|
|
}
|
|
|
|
case SE_Levitate:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Levitate");
|
|
#endif
|
|
//this sends the levitate packet to everybody else
|
|
//who does not otherwise receive the buff packet.
|
|
if (spells[spell_id].limit_value[i] == 1) {
|
|
SendAppearancePacket(AT_Levitate, EQ::constants::GravityBehavior::LevitateWhileRunning, true, true);
|
|
}
|
|
else {
|
|
SendAppearancePacket(AT_Levitate, EQ::constants::GravityBehavior::Levitating, true, true);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_DeathSave: {
|
|
|
|
int16 mod = 0;
|
|
|
|
if(caster) {
|
|
mod = caster->aabonuses.UnfailingDivinity +
|
|
caster->itembonuses.UnfailingDivinity +
|
|
caster->spellbonuses.UnfailingDivinity;
|
|
}
|
|
|
|
buffs[buffslot].ExtraDIChance = mod;
|
|
break;
|
|
}
|
|
|
|
case SE_Illusion:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Illusion: race %d", effect_value);
|
|
#endif
|
|
ApplySpellEffectIllusion(spell_id, caster, buffslot, spells[spell_id].base_value[i], spells[spell_id].limit_value[i], spells[spell_id].max_value[i]);
|
|
break;
|
|
}
|
|
|
|
case SE_IllusionCopy:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Illusion Copy");
|
|
#endif
|
|
if(caster && caster->GetTarget()){
|
|
SendIllusionPacket
|
|
(
|
|
caster->GetTarget()->GetRace(),
|
|
caster->GetTarget()->GetGender(),
|
|
caster->GetTarget()->GetTexture()
|
|
);
|
|
caster->SendAppearancePacket(AT_Size, static_cast<uint32>(caster->GetTarget()->GetSize()));
|
|
|
|
for (int x = EQ::textures::textureBegin; x <= EQ::textures::LastTintableTexture; x++)
|
|
caster->SendWearChange(x);
|
|
}
|
|
}
|
|
|
|
case SE_WipeHateList:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Memory Blur: %d", effect_value);
|
|
#endif
|
|
//Memory blur component of Mez spells is not checked again if Mez is recast on a target that is already mezed
|
|
if (!CanMemoryBlurFromMez && IsEffectInSpell(spell_id, SE_Mez)) {
|
|
break;
|
|
}
|
|
|
|
int wipechance = 0;
|
|
|
|
if (caster) {
|
|
wipechance = caster->GetMemoryBlurChance(effect_value);
|
|
}
|
|
|
|
if(zone->random.Roll(wipechance))
|
|
{
|
|
if(IsAIControlled())
|
|
{
|
|
WipeHateList();
|
|
}
|
|
Message(Chat::Red, "Your mind fogs. Who are my friends? Who are my enemies?... it was all so clear a moment ago...");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_SpinTarget:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Spin: %d", effect_value);
|
|
#endif
|
|
// the spinning is handled by the client
|
|
int max_level = spells[spell_id].max_value[i];
|
|
if(max_level == 0)
|
|
max_level = RuleI(Spells, BaseImmunityLevel); // Default max is 55 level limit
|
|
|
|
// NPCs ignore level limits in their spells
|
|
if(GetSpecialAbility(UNSTUNABLE) ||
|
|
((GetLevel() > max_level)
|
|
&& caster && (!caster->IsNPC() || (caster->IsNPC() && !RuleB(Spells, NPCIgnoreBaseImmunity)))))
|
|
{
|
|
if (caster)
|
|
caster->MessageString(Chat::Shout, IMMUNE_STUN);
|
|
}
|
|
else
|
|
{
|
|
// the spinning is handled by the client
|
|
// Stun duration is based on the effect_value, not the buff duration(alot don't have buffs)
|
|
Stun(effect_value);
|
|
if(!IsClient()) {
|
|
Spin();
|
|
spun_timer.Start(100); // spins alittle every 100 ms
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_EyeOfZomm:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Eye of Zomm");
|
|
#endif
|
|
if(caster && caster->IsClient()) {
|
|
char eye_name[64];
|
|
snprintf(eye_name, sizeof(eye_name), "Eye_of_%s", caster->GetCleanName());
|
|
int duration = CalcBuffDuration(caster, this, spell_id) * 6;
|
|
uint16 eye_id=0;
|
|
caster->TemporaryPets(spell_id, nullptr, eye_name, duration, false, false, &eye_id);
|
|
if (eye_id != 0) {
|
|
caster->CastToClient()->SetControlledMobId(eye_id);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_ReclaimPet:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Reclaim Pet");
|
|
#endif
|
|
if
|
|
(
|
|
IsNPC() &&
|
|
GetOwnerID() && // I'm a pet
|
|
caster && // there's a caster
|
|
caster->GetID() == GetOwnerID() && // and it's my master
|
|
GetPetType() != petCharmed
|
|
)
|
|
{
|
|
uint16 pet_spellid = CastToNPC()->GetPetSpellID();
|
|
uint16 pet_ActSpellCost = caster->GetActSpellCost(pet_spellid, spells[pet_spellid].mana);
|
|
int16 ImprovedReclaimMod = caster->spellbonuses.ImprovedReclaimEnergy +
|
|
caster->itembonuses.ImprovedReclaimEnergy +
|
|
caster->aabonuses.ImprovedReclaimEnergy;
|
|
|
|
if (!ImprovedReclaimMod)
|
|
ImprovedReclaimMod = 75; //Reclaim Energy default is 75% of actual mana cost
|
|
|
|
pet_ActSpellCost = pet_ActSpellCost*ImprovedReclaimMod/100;
|
|
|
|
caster->SetMana(caster->GetMana() + pet_ActSpellCost);
|
|
|
|
if(caster->IsClient())
|
|
caster->CastToClient()->SetPet(0);
|
|
|
|
SetOwnerID(0); // this will kill the pet
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_BindSight:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Bind Sight");
|
|
#endif
|
|
if(caster && caster->IsClient())
|
|
{
|
|
caster->CastToClient()->SetBindSightTarget(this);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_FeignDeath:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Feign Death");
|
|
#endif
|
|
if(spell_id == SPELL_LIFEBURN) //Dook- Lifeburn fix
|
|
break;
|
|
|
|
if(IsClient()) {
|
|
CastToClient()->SetHorseId(0); // dismount if have horse
|
|
|
|
if (zone->random.Int(0, 99) > spells[spell_id].base_value[i]) {
|
|
SetFeigned(false);
|
|
entity_list.MessageCloseString(this, false, 200, 10, STRING_FEIGNFAILED, GetName());
|
|
} else {
|
|
SetFeigned(true);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Sentinel:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Sentinel");
|
|
#endif
|
|
if(caster)
|
|
{
|
|
if(caster == this)
|
|
{
|
|
MessageString(Chat::Spells,
|
|
SENTINEL_TRIG_YOU);
|
|
}
|
|
else
|
|
{
|
|
caster->MessageString(Chat::Spells,
|
|
SENTINEL_TRIG_OTHER, GetCleanName());
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_LocateCorpse:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Locate Corpse");
|
|
#endif
|
|
// This is handled by the client prior to SoD.
|
|
//
|
|
if (IsClient() && (CastToClient()->ClientVersionBit() & EQ::versions::maskSoDAndLater))
|
|
CastToClient()->LocateCorpse();
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_SummonToCorpse:
|
|
case SE_Revive:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Revive"); // heh the corpse won't see this
|
|
#endif
|
|
if (IsCorpse() && CastToCorpse()->IsPlayerCorpse()) {
|
|
|
|
if(caster)
|
|
LogSpells("corpse being rezzed using spell [{}] by [{}]",
|
|
spell_id, caster->GetName());
|
|
|
|
CastToCorpse()->CastRezz(spell_id, caster);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_ModelSize:
|
|
case SE_ChangeHeight:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Model Size: %d%%", effect_value);
|
|
#endif
|
|
if (effect_value && effect_value != 100) {
|
|
// Only allow 2 size changes from Base Size
|
|
float modifyAmount = (static_cast<float>(effect_value) / 100.0f);
|
|
float maxModAmount = GetBaseSize() * modifyAmount * modifyAmount;
|
|
if ((GetSize() <= GetBaseSize() && GetSize() > maxModAmount) ||
|
|
(GetSize() >= GetBaseSize() && GetSize() < maxModAmount) ||
|
|
(GetSize() <= GetBaseSize() && maxModAmount > 1.0f) ||
|
|
(GetSize() >= GetBaseSize() && maxModAmount < 1.0f))
|
|
{
|
|
ChangeSize(GetSize() * modifyAmount);
|
|
}
|
|
}
|
|
//Only applies to SPA 89, max value also likely does something, but unknown.
|
|
else if (effect == SE_ModelSize && spells[spell_id].limit_value[i]) {
|
|
ChangeSize(spells[spell_id].limit_value[i]);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_Root:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Root: %+i", effect_value);
|
|
#endif
|
|
rooted = true;
|
|
|
|
if (caster){
|
|
buffs[buffslot].RootBreakChance = caster->aabonuses.RootBreakChance +
|
|
caster->itembonuses.RootBreakChance +
|
|
caster->spellbonuses.RootBreakChance;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_SummonHorse:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Summon Mount: %s", spell.teleport_zone);
|
|
#endif
|
|
if(IsClient()) // NPCs can't ride
|
|
{
|
|
Client *client = CastToClient();
|
|
|
|
// Prevent Feigned players from summoning horses and riding away to freedom.
|
|
client->SetFeigned(false);
|
|
client->Stand();
|
|
client->SummonHorse(spell_id);
|
|
}
|
|
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_SummonCorpse:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Summon Corpse: %d", effect_value);
|
|
#endif
|
|
// can only summon corpses of clients
|
|
if(!IsNPC()) {
|
|
Client* TargetClient = nullptr;
|
|
if(GetTarget())
|
|
TargetClient = GetTarget()->CastToClient();
|
|
else
|
|
TargetClient = CastToClient();
|
|
|
|
// We now have a valid target for this spell. Either the caster himself or a targetted player. Lets see if the target is in the group.
|
|
Group* group = entity_list.GetGroupByClient(TargetClient);
|
|
if(group) {
|
|
if(!group->IsGroupMember(TargetClient)) {
|
|
Message(Chat::Red, "Your target must be a group member for this spell.");
|
|
break;
|
|
}
|
|
}
|
|
else if (caster) {
|
|
Raid *r = entity_list.GetRaidByClient(caster->CastToClient());
|
|
if(r)
|
|
{
|
|
uint32 gid = 0xFFFFFFFF;
|
|
gid = r->GetGroup(caster->GetName());
|
|
if(gid < 11)
|
|
{
|
|
if(r->GetGroup(TargetClient->GetName()) != gid) {
|
|
Message(Chat::Red, "Your target must be a group member for this spell.");
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if(TargetClient != CastToClient()) {
|
|
Message(Chat::Red, "Your target must be a group member for this spell.");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now we should either be casting this on self or its being cast on a valid group member
|
|
if(TargetClient) {
|
|
|
|
if (TargetClient->GetLevel() <= effect_value){
|
|
|
|
Corpse *corpse = entity_list.GetCorpseByOwner(TargetClient);
|
|
if(corpse) {
|
|
if(TargetClient == CastToClient())
|
|
MessageString(Chat::LightBlue, SUMMONING_CORPSE, TargetClient->CastToMob()->GetCleanName());
|
|
else
|
|
MessageString(Chat::LightBlue, SUMMONING_CORPSE_OTHER, TargetClient->CastToMob()->GetCleanName());
|
|
|
|
corpse->Summon(CastToClient(), true, true);
|
|
}
|
|
else {
|
|
// No corpse found in the zone
|
|
MessageString(Chat::LightBlue, CORPSE_CANT_SENSE);
|
|
}
|
|
}
|
|
else if (caster) {
|
|
char level[4];
|
|
ConvertArray(effect_value, level);
|
|
caster->MessageString(Chat::SpellFailure,
|
|
SPELL_LEVEL_REQ, level);
|
|
}
|
|
}
|
|
else {
|
|
MessageString(Chat::LightBlue, TARGET_NOT_FOUND);
|
|
LogError("[{}] attempted to cast spell id [{}] with spell effect SE_SummonCorpse, but could not cast target into a Client object", GetCleanName(), spell_id);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case SE_SummonCorpseZone:
|
|
{
|
|
if (IsClient()) {
|
|
Client* client_target = CastToClient();
|
|
if (client_target->IsGrouped()) {
|
|
Group* group = client_target->GetGroup();
|
|
if (!group->IsGroupMember(caster)) {
|
|
if (caster != this) {
|
|
caster->MessageString(Chat::Red, SUMMON_ONLY_GROUP_CORPSE);
|
|
break;
|
|
}
|
|
}
|
|
} else if (caster) {
|
|
if (caster->IsRaidGrouped()) {
|
|
Raid *raid = caster->GetRaid();
|
|
uint32 group_id = raid->GetGroup(caster->GetName());
|
|
if (group_id > 0 && group_id < MAX_RAID_GROUPS) {
|
|
if (raid->GetGroup(client_target->GetName()) != group_id) {
|
|
caster->MessageString(Chat::Red, SUMMON_ONLY_GROUP_CORPSE);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if (caster != this) {
|
|
caster->MessageString(Chat::Red, SUMMON_ONLY_GROUP_CORPSE);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (client_target) {
|
|
if (database.CountCharacterCorpses(client_target->CharacterID()) == 0) {
|
|
if (caster == this) {
|
|
Message(Chat::Yellow, "You have no corpses to summon.");
|
|
} else {
|
|
caster->Message(Chat::Yellow, "%s has no corpses to summon.", client_target->GetCleanName());
|
|
}
|
|
} else {
|
|
if (caster == this) {
|
|
Message(Chat::Spells, "Summoning your corpses.");
|
|
} else {
|
|
caster->MessageString(Chat::Spells, SUMMONING_CORPSE_ZONE, client_target->GetCleanName());
|
|
}
|
|
client_target->SummonAllCorpses(client_target->GetPosition());
|
|
}
|
|
} else {
|
|
MessageString(Chat::Spells, TARGET_NOT_FOUND);
|
|
LogError("[{}] attempted to cast spell id [{}] with spell effect SE_SummonCorpseZone, but could not cast target into a Client object", GetCleanName(), spell_id);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SE_AddMeleeProc:
|
|
case SE_WeaponProc:
|
|
{
|
|
uint16 procid = GetProcID(spell_id, i);
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Weapon Proc: %s (id %d)", spells[effect_value].name, procid);
|
|
#endif
|
|
AddProcToWeapon(procid, false, 100 + spells[spell_id].limit_value[i], spell_id, caster_level, GetProcLimitTimer(spell_id, ProcType::MELEE_PROC));
|
|
break;
|
|
}
|
|
|
|
case SE_RangedProc:
|
|
{
|
|
uint16 procid = GetProcID(spell_id, i);
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Ranged Proc: %+i", effect_value);
|
|
#endif
|
|
AddRangedProc(procid, 100 + spells[spell_id].limit_value[i], spell_id, GetProcLimitTimer(spell_id, ProcType::RANGED_PROC));
|
|
break;
|
|
}
|
|
|
|
case SE_DefensiveProc:
|
|
{
|
|
uint16 procid = GetProcID(spell_id, i);
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Defensive Proc: %s (id %d)", spells[effect_value].name, procid);
|
|
#endif
|
|
AddDefensiveProc(procid, 100 + spells[spell_id].limit_value[i], spell_id, GetProcLimitTimer(spell_id, ProcType::DEFENSIVE_PROC));
|
|
break;
|
|
}
|
|
|
|
case SE_NegateAttacks:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Melee Negate Attack Rune: %+i", effect_value);
|
|
#endif
|
|
if(buffslot >= 0)
|
|
buffs[buffslot].hit_number = effect_value;
|
|
break;
|
|
}
|
|
case SE_AppraiseLDonChest:
|
|
{
|
|
if(IsNPC())
|
|
{
|
|
int check = spell.max_value[0];
|
|
int target = spell.target_type;
|
|
if(target == ST_LDoNChest_Cursed)
|
|
{
|
|
if(caster && caster->IsClient())
|
|
{
|
|
caster->CastToClient()->HandleLDoNSenseTraps(CastToNPC(), check, LDoNTypeCursed);
|
|
}
|
|
}
|
|
else if(target == ST_Target)
|
|
{
|
|
if(caster && caster->IsClient())
|
|
{
|
|
caster->CastToClient()->HandleLDoNSenseTraps(CastToNPC(), check, LDoNTypeMagical);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_DisarmLDoNTrap:
|
|
{
|
|
if(IsNPC())
|
|
{
|
|
int check = spell.max_value[0];
|
|
int target = spell.target_type;
|
|
if(target == ST_LDoNChest_Cursed)
|
|
{
|
|
if(caster && caster->IsClient())
|
|
{
|
|
caster->CastToClient()->HandleLDoNDisarm(CastToNPC(), check, LDoNTypeCursed);
|
|
}
|
|
}
|
|
else if(target == ST_Target)
|
|
{
|
|
if(caster && caster->IsClient())
|
|
{
|
|
caster->CastToClient()->HandleLDoNDisarm(CastToNPC(), check, LDoNTypeMagical);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_UnlockLDoNChest:
|
|
{
|
|
if(IsNPC())
|
|
{
|
|
int check = spell.max_value[0];
|
|
int target = spell.target_type;
|
|
if(target == ST_LDoNChest_Cursed)
|
|
{
|
|
if(caster && caster->IsClient())
|
|
{
|
|
caster->CastToClient()->HandleLDoNPickLock(CastToNPC(), check, LDoNTypeCursed);
|
|
}
|
|
}
|
|
else if(target == ST_Target)
|
|
{
|
|
if(caster && caster->IsClient())
|
|
{
|
|
caster->CastToClient()->HandleLDoNPickLock(CastToNPC(), check, LDoNTypeMagical);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Lull:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Lull");
|
|
#endif
|
|
// TODO: check vs. CHA when harmony effect failed, if caster is to be added to hatelist
|
|
break;
|
|
}
|
|
|
|
case SE_PoisonCounter:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Poison Counter: %+i", effect_value);
|
|
#endif
|
|
if (effect_value < 0)
|
|
{
|
|
effect_value = 0 - effect_value;
|
|
int buff_count = GetMaxTotalSlots();
|
|
for (int j=0; j < buff_count; j++) {
|
|
if (!IsValidSpell(buffs[j].spellid))
|
|
continue;
|
|
if (CalculatePoisonCounters(buffs[j].spellid) == 0)
|
|
continue;
|
|
if (effect_value >= static_cast<int>(buffs[j].counters)) {
|
|
if (caster) {
|
|
caster->Message(Chat::Spells,"You have cured your target of %s!",spells[buffs[j].spellid].name);
|
|
caster->CastOnCurer(buffs[j].spellid);
|
|
CastOnCure(buffs[j].spellid);
|
|
}
|
|
effect_value -= buffs[j].counters;
|
|
buffs[j].counters = 0;
|
|
BuffFadeBySlot(j);
|
|
} else {
|
|
buffs[j].counters -= effect_value;
|
|
effect_value = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_DiseaseCounter:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Disease Counter: %+i", effect_value);
|
|
#endif
|
|
if (effect_value < 0)
|
|
{
|
|
effect_value = 0 - effect_value;
|
|
int buff_count = GetMaxTotalSlots();
|
|
for (int j=0; j < buff_count; j++) {
|
|
if (!IsValidSpell(buffs[j].spellid))
|
|
continue;
|
|
if (CalculateDiseaseCounters(buffs[j].spellid) == 0)
|
|
continue;
|
|
if (effect_value >= static_cast<int>(buffs[j].counters))
|
|
{
|
|
if (caster) {
|
|
caster->Message(Chat::Spells,"You have cured your target of %s!",spells[buffs[j].spellid].name);
|
|
caster->CastOnCurer(buffs[j].spellid);
|
|
CastOnCure(buffs[j].spellid);
|
|
}
|
|
effect_value -= buffs[j].counters;
|
|
buffs[j].counters = 0;
|
|
BuffFadeBySlot(j);
|
|
}
|
|
else
|
|
{
|
|
buffs[j].counters -= effect_value;
|
|
effect_value = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_CurseCounter:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Curse Counter: %+i", effect_value);
|
|
#endif
|
|
if (effect_value < 0)
|
|
{
|
|
effect_value = 0 - effect_value;
|
|
int buff_count = GetMaxTotalSlots();
|
|
for (int j=0; j < buff_count; j++) {
|
|
if (!IsValidSpell(buffs[j].spellid))
|
|
continue;
|
|
if (CalculateCurseCounters(buffs[j].spellid) == 0)
|
|
continue;
|
|
if (effect_value >= static_cast<int>(buffs[j].counters))
|
|
{
|
|
if (caster) {
|
|
caster->Message(Chat::Spells,"You have cured your target of %s!",spells[buffs[j].spellid].name);
|
|
caster->CastOnCurer(buffs[j].spellid);
|
|
CastOnCure(buffs[j].spellid);
|
|
}
|
|
effect_value -= buffs[j].counters;
|
|
buffs[j].counters = 0;
|
|
BuffFadeBySlot(j);
|
|
}
|
|
else
|
|
{
|
|
buffs[j].counters -= effect_value;
|
|
effect_value = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_CorruptionCounter:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Corruption Counter: %+i", effect_value);
|
|
#endif
|
|
if (effect_value < 0)
|
|
{
|
|
effect_value = -effect_value;
|
|
int buff_count = GetMaxTotalSlots();
|
|
for (int j=0; j < buff_count; j++) {
|
|
if (!IsValidSpell(buffs[j].spellid))
|
|
continue;
|
|
if (CalculateCorruptionCounters(buffs[j].spellid) == 0)
|
|
continue;
|
|
if (effect_value >= static_cast<int>(buffs[j].counters)) {
|
|
if (caster) {
|
|
caster->Message(Chat::Spells,"You have cured your target of %s!",spells[buffs[j].spellid].name);
|
|
caster->CastOnCurer(buffs[j].spellid);
|
|
CastOnCure(buffs[j].spellid);
|
|
}
|
|
effect_value -= buffs[j].counters;
|
|
buffs[j].counters = 0;
|
|
BuffFadeBySlot(j);
|
|
} else {
|
|
buffs[j].counters -= effect_value;
|
|
effect_value = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Destroy:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Destroy");
|
|
#endif
|
|
if(IsNPC()) {
|
|
if(GetLevel() <= 52)
|
|
CastToNPC()->Depop(true);
|
|
else
|
|
Message(Chat::Red, "Your target is too high level to be affected by this spell.");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_TossUp:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Toss Up: %d", effect_value);
|
|
#endif
|
|
if (IsNPC()) {
|
|
Damage(caster, std::abs(effect_value), spell_id, spell.skill, false, buffslot, false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_StopRain:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Stop Rain");
|
|
#endif
|
|
zone->zone_weather = EQ::constants::WeatherTypes::None;
|
|
zone->weather_intensity = 0;
|
|
zone->weatherSend();
|
|
break;
|
|
}
|
|
|
|
case SE_Sacrifice:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Sacrifice");
|
|
#endif
|
|
if(!caster || !IsClient() || !caster->IsClient()){
|
|
break;
|
|
}
|
|
CastToClient()->SacrificeConfirm(caster->CastToClient());
|
|
break;
|
|
}
|
|
|
|
case SE_SummonPC:
|
|
{
|
|
if (!caster) {
|
|
break;
|
|
}
|
|
|
|
if (IsClient()) {
|
|
if (caster->IsClient()) {
|
|
if (!entity_list.IsInSameGroupOrRaidGroup(caster->CastToClient(), CastToClient())) {
|
|
caster->MessageString(Chat::SpellFailure, TARGET_GROUP_MEMBER);
|
|
break;
|
|
}
|
|
|
|
// clear aggro when summoned in zone and further than aggro clear distance rule.
|
|
if (RuleR(Spells, CallOfTheHeroAggroClearDist) == 0 || caster->CalculateDistance(GetX(), GetY(), GetZ()) >= RuleR(Spells, CallOfTheHeroAggroClearDist)) {
|
|
entity_list.ClearAggro(this);
|
|
}
|
|
}
|
|
|
|
CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), caster->GetX(),
|
|
caster->GetY(), caster->GetZ(), caster->GetHeading(), 2,
|
|
SummonPC);
|
|
MessageString(Chat::Spells, PLAYER_SUMMONED);
|
|
} else {
|
|
caster->Message(Chat::Red, "This spell can only be cast on players.");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Silence:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Silence");
|
|
#endif
|
|
Silence(true);
|
|
break;
|
|
}
|
|
|
|
case SE_Amnesia:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Amnesia");
|
|
#endif
|
|
Amnesia(true);
|
|
break;
|
|
}
|
|
|
|
case SE_CallPet:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Call Pet");
|
|
#endif
|
|
// this is cast on self, not on the pet
|
|
Mob *casters_pet = GetPet();
|
|
if(casters_pet && casters_pet->IsNPC()){
|
|
casters_pet->CastToNPC()->GMMove(GetX(), GetY(), GetZ(), GetHeading());
|
|
if (!casters_pet->GetTarget()) {
|
|
casters_pet->StopNavigation();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_StackingCommand_Block:
|
|
case SE_StackingCommand_Overwrite:
|
|
{
|
|
// these are special effects used by the buff stuff
|
|
break;
|
|
}
|
|
|
|
|
|
case SE_TemporaryPets: //Dook- swarms and wards:
|
|
{
|
|
if (!caster)
|
|
break;
|
|
// this makes necro epic 1.5/2.0 proc work properly
|
|
if((spell_id != 6882) && (spell_id != 6884)) // Chaotic Jester/Steadfast Servant
|
|
{
|
|
char pet_name[64];
|
|
snprintf(pet_name, sizeof(pet_name), "%s`s pet", caster->GetCleanName());
|
|
caster->TemporaryPets(spell_id, this, pet_name);
|
|
}
|
|
else
|
|
caster->TemporaryPets(spell_id, this, nullptr);
|
|
break;
|
|
}
|
|
|
|
case SE_FadingMemories: //Dook- escape etc
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Fading Memories");
|
|
#endif
|
|
int max_level = 0;
|
|
|
|
if (RuleB(Spells, UseFadingMemoriesMaxLevel)) {
|
|
//handle ROF2 era where limit value determines max level
|
|
if (spells[spell_id].limit_value[i]) {
|
|
max_level = spells[spell_id].limit_value[i];
|
|
}
|
|
//handle modern client era where max value determines max level or range above client.
|
|
else if (spells[spell_id].max_value[i]) {
|
|
if (spells[spell_id].max_value[i] >= 1000) {
|
|
max_level = 1000 - spells[spell_id].max_value[i];
|
|
}
|
|
else {
|
|
max_level = GetLevel() + spells[spell_id].max_value[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
if(zone->random.Roll(spells[spell_id].base_value[i])) {
|
|
if (IsClient()) {
|
|
int pre_aggro_count = CastToClient()->GetAggroCount();
|
|
entity_list.RemoveFromTargetsFadingMemories(this, true, max_level);
|
|
SetInvisible(Invisibility::Invisible);
|
|
int post_aggro_count = CastToClient()->GetAggroCount();
|
|
if (RuleB(Spells, UseFadingMemoriesMaxLevel)) {
|
|
if (pre_aggro_count == post_aggro_count) {
|
|
Message(Chat::SpellFailure, "You failed to escape from all your opponents.");
|
|
break;
|
|
}
|
|
else if (post_aggro_count) {
|
|
Message(Chat::SpellFailure, "You failed to escape from combat but you evade some of your opponents.");
|
|
break;
|
|
}
|
|
}
|
|
MessageString(Chat::Skills, ESCAPE);
|
|
}
|
|
else{
|
|
entity_list.RemoveFromTargets(caster);
|
|
SetInvisible(Invisibility::Invisible);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Rampage:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Rampage");
|
|
#endif
|
|
//defulat live range is 40, with 1 attack per round, no hit count limit
|
|
float rampage_range = 40;
|
|
if (spells[spell_id].aoe_range) {
|
|
rampage_range = spells[spell_id].aoe_range; //added for expanded functionality
|
|
}
|
|
int attack_count = spells[spell_id].base_value[i]; //added for expanded functionality
|
|
int hit_count = spells[spell_id].limit_value[i]; //added for expanded functionality
|
|
if (caster) {
|
|
entity_list.AEAttack(caster, rampage_range, EQ::invslot::slotPrimary, hit_count, true, attack_count); // on live wars dont get a duration ramp, its a one shot deal
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_AEMelee:
|
|
{
|
|
//old aa
|
|
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Duration Rampage");
|
|
#endif
|
|
aa_timers[aaTimerRampage].Start(effect_value * 10 * 1000); // Live bug, was suppose to be 1 second per value
|
|
break;
|
|
}
|
|
|
|
case SE_AETaunt:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "AE Taunt");
|
|
#endif
|
|
if(caster && caster->IsClient()){
|
|
//Live AE Taunt range is hardcoded at 40 (Spells for AE taunt all use zero range) Target type should be self only.
|
|
float range = 40;
|
|
if (spells[spell_id].max_value[i])//custom support if you want to alter range of AE Taunt.
|
|
range = spells[spell_id].max_value[i];
|
|
|
|
entity_list.AETaunt(caster->CastToClient(), range, spells[spell_id].base_value[i]);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_SkillAttack:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Skill Attack");
|
|
#endif
|
|
/*
|
|
Weapon Damage = spells[spell_id].base[i]
|
|
Chance to Hit Bonus = spells[spell_id].base2[i]
|
|
???? = spells[spell_id].max[i] - MOST of the effects have this value.
|
|
*Max is lower value then Weapon base, possibly min hit vs Weapon Damage range ie. MakeRandInt(max,base)
|
|
*/
|
|
int ReuseTime = spells[spell_id].recast_time + spells[spell_id].recovery_time;
|
|
if (!caster) {
|
|
break;
|
|
}
|
|
|
|
switch(spells[spell_id].skill) {
|
|
case EQ::skills::SkillThrowing:
|
|
caster->DoThrowingAttackDmg(this, nullptr, nullptr, spells[spell_id].base_value[i],spells[spell_id].limit_value[i], 0, ReuseTime, 0, 0, 4.0f, true);
|
|
break;
|
|
case EQ::skills::SkillArchery:
|
|
caster->DoArcheryAttackDmg(this, nullptr, nullptr, spells[spell_id].base_value[i],spells[spell_id].limit_value[i], 0, ReuseTime, 0, 0, nullptr, 0, 4.0f, true);
|
|
break;
|
|
default:
|
|
caster->DoMeleeSkillAttackDmg(this, spells[spell_id].base_value[i], spells[spell_id].skill, spells[spell_id].limit_value[i], 0, false, ReuseTime);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_WakeTheDead:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Wake The Dead");
|
|
#endif
|
|
//meh dupe issue with npc casting this
|
|
if(caster && caster->IsClient()){
|
|
int dur = spells[spell_id].max_value[i];
|
|
if (!dur) {
|
|
dur = 60;
|
|
}
|
|
|
|
Mob* m_target = caster->GetTarget();
|
|
if (m_target) {
|
|
entity_list.TryWakeTheDead(caster, m_target, spell_id, 250, dur, 1);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_ArmyOfTheDead:
|
|
{
|
|
if (caster && caster->IsClient()) {
|
|
int dur = spells[spell_id].max_value[i];
|
|
if (!dur) {
|
|
dur = 60;
|
|
}
|
|
|
|
int amount = spells[spell_id].base_value[i];
|
|
if (!amount) {
|
|
amount = 1;
|
|
}
|
|
|
|
Mob* m_target = caster->GetTarget();
|
|
if (m_target) {
|
|
entity_list.TryWakeTheDead(caster, m_target, spell_id, 250, dur, amount);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Doppelganger:
|
|
{
|
|
if(caster && caster->IsClient()) {
|
|
char pet_name[64];
|
|
snprintf(pet_name, sizeof(pet_name), "%s`s doppelganger", caster->GetCleanName());
|
|
int pet_count = spells[spell_id].base_value[i];
|
|
int pet_duration = spells[spell_id].max_value[i];
|
|
caster->CastToClient()->Doppelganger(spell_id, this, pet_name, pet_count, pet_duration);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_BardAEDot:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Bard AE Dot: %+i", effect_value);
|
|
#endif
|
|
// SE_CurrentHP is calculated at first tick if its a dot/buff
|
|
if (buffslot >= 0)
|
|
break;
|
|
//This effect does no damage if target is moving.
|
|
if (!RuleB(Spells, PreNerfBardAEDoT) && IsMoving())
|
|
break;
|
|
|
|
// for offensive spells check if we have a spell rune on
|
|
int64 dmg = effect_value;
|
|
if(dmg < 0)
|
|
{
|
|
// take partial damage into account
|
|
dmg = (int64) (dmg * partial / 100);
|
|
|
|
//handles AAs and what not...
|
|
//need a bard version of this prolly...
|
|
//if(caster)
|
|
// dmg = caster->GetActSpellDamage(spell_id, dmg);
|
|
|
|
dmg = -dmg;
|
|
Damage(caster, dmg, spell_id, spell.skill, false, buffslot, false);
|
|
} else if(dmg > 0) {
|
|
//healing spell...
|
|
if(caster)
|
|
dmg = caster->GetActSpellHealing(spell_id, dmg, this);
|
|
HealDamage(dmg, caster);
|
|
}
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Current Hitpoints: %+i actual: %+i", effect_value, dmg);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case SE_CurrentEndurance: {
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Current Endurance: %+i", effect_value);
|
|
#endif
|
|
if(IsClient()) {
|
|
CastToClient()->SetEndurance(CastToClient()->GetEndurance() + effect_value);
|
|
if (effect_value < 0) {
|
|
TryTriggerOnCastRequirement();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_CurrentEnduranceOnce:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Current Endurance Once: %+i", effect_value);
|
|
#endif
|
|
|
|
if (IsClient()) {
|
|
CastToClient()->SetEndurance(CastToClient()->GetEndurance() + effect_value);
|
|
if (effect_value < 0) {
|
|
TryTriggerOnCastRequirement();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_BalanceHP: {
|
|
if(!caster)
|
|
break;
|
|
|
|
if(!caster->IsClient())
|
|
break;
|
|
|
|
Raid *r = entity_list.GetRaidByClient(caster->CastToClient());
|
|
if(r)
|
|
{
|
|
uint32 gid = 0xFFFFFFFF;
|
|
gid = r->GetGroup(caster->GetName());
|
|
if(gid < 11)
|
|
{
|
|
r->BalanceHP(spell.base_value[i], gid, spell.range, caster, spell.limit_value[i]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Group *g = entity_list.GetGroupByClient(caster->CastToClient());
|
|
|
|
if(!g)
|
|
break;
|
|
|
|
g->BalanceHP(spell.base_value[i], spell.range, caster, spell.limit_value[i]);
|
|
break;
|
|
}
|
|
|
|
case SE_BalanceMana: {
|
|
if(!caster)
|
|
break;
|
|
|
|
if(!caster->IsClient())
|
|
break;
|
|
|
|
Raid *r = entity_list.GetRaidByClient(caster->CastToClient());
|
|
if(r)
|
|
{
|
|
uint32 gid = 0xFFFFFFFF;
|
|
gid = r->GetGroup(caster->GetName());
|
|
if(gid < 11)
|
|
{
|
|
r->BalanceMana(spell.base_value[i], gid, spell.range, caster, spell.limit_value[i]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Group *g = entity_list.GetGroupByClient(caster->CastToClient());
|
|
|
|
if(!g)
|
|
break;
|
|
|
|
g->BalanceMana(spell.base_value[i], spell.range, caster, spell.limit_value[i]);
|
|
break;
|
|
}
|
|
|
|
case SE_SummonAndResAllCorpses:
|
|
{
|
|
if(IsClient())
|
|
CastToClient()->SummonAndRezzAllCorpses();
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_GateToHomeCity:
|
|
{
|
|
if(IsClient())
|
|
CastToClient()->GoToBind(4);
|
|
break;
|
|
}
|
|
|
|
case SE_SuspendPet:
|
|
{
|
|
if(IsClient())
|
|
CastToClient()->SuspendMinion(spell.base_value[i]);
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_FcTimerRefresh:
|
|
{
|
|
if(IsClient()) {
|
|
for (unsigned int i = 0; i < EQ::spells::SPELL_GEM_COUNT; ++i) {
|
|
if(IsValidSpell(CastToClient()->m_pp.mem_spells[i])) {
|
|
if (CalcFocusEffect(focusFcTimerRefresh, spell_id, CastToClient()->m_pp.mem_spells[i])){
|
|
CastToClient()->m_pp.spellSlotRefresh[i] = 1;
|
|
CastToClient()->GetPTimers().Clear(&database, (pTimerSpellStart + CastToClient()->m_pp.mem_spells[i]));
|
|
if (!CastToClient()->IsLinkedSpellReuseTimerReady(spells[CastToClient()->m_pp.mem_spells[i]].timer_id)) {
|
|
CastToClient()->GetPTimers().Clear(&database, (pTimerLinkedSpellReuseStart + spells[CastToClient()->m_pp.mem_spells[i]].timer_id));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SetMana(GetMana());
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_FcTimerLockout: {
|
|
if (IsClient()) {
|
|
for (unsigned int mem_spell : CastToClient()->m_pp.mem_spells) {
|
|
if (IsValidSpell(mem_spell)) {
|
|
int32 new_recast_timer = CalcFocusEffect(
|
|
focusFcTimerLockout,
|
|
spell_id,
|
|
mem_spell
|
|
);
|
|
if (new_recast_timer) {
|
|
bool apply_recast_timer = true;
|
|
if (IsCasting() && casting_spell_id == mem_spell) {
|
|
apply_recast_timer = false;
|
|
}
|
|
if (apply_recast_timer) {
|
|
new_recast_timer = new_recast_timer / 1000;
|
|
CastToClient()->GetPTimers().Start(
|
|
pTimerSpellStart + mem_spell,
|
|
static_cast<uint32>(new_recast_timer)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SetMana(GetMana());
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_HealGroupFromMana: {
|
|
if(!caster)
|
|
break;
|
|
|
|
if(!caster->IsClient())
|
|
break;
|
|
|
|
int32 max_mana = spell.base_value[i];
|
|
int ratio = spell.limit_value[i];
|
|
uint64 heal_amt = 0;
|
|
|
|
if (caster->GetMana() <= max_mana){
|
|
heal_amt = ratio*caster->GetMana()/10;
|
|
caster->SetMana(0);
|
|
}
|
|
|
|
else {
|
|
heal_amt = ratio*max_mana/10;
|
|
caster->SetMana(caster->GetMana() - max_mana);
|
|
}
|
|
|
|
Raid *r = entity_list.GetRaidByClient(caster->CastToClient());
|
|
if(r)
|
|
{
|
|
uint32 gid = 0xFFFFFFFF;
|
|
gid = r->GetGroup(caster->GetName());
|
|
if(gid < 11)
|
|
{
|
|
r->HealGroup(heal_amt,caster, gid, spell.range);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Group *g = entity_list.GetGroupByClient(caster->CastToClient());
|
|
|
|
if(!g){
|
|
caster->HealDamage(heal_amt);
|
|
break;
|
|
}
|
|
|
|
g->HealGroup(heal_amt, caster, spell.range);
|
|
break;
|
|
}
|
|
|
|
case SE_ManaDrainWithDmg:
|
|
{
|
|
int mana_damage = 0;
|
|
int32 mana_to_use = GetMana() - spell.base_value[i];
|
|
if(mana_to_use > -1) {
|
|
SetMana(GetMana() - spell.base_value[i]);
|
|
TryTriggerOnCastRequirement();
|
|
// we take full dmg(-10 to make the damage the right sign)
|
|
mana_damage = spell.base_value[i] / -10 * spell.limit_value[i];
|
|
Damage(caster, mana_damage, spell_id, spell.skill, false, i, true);
|
|
}
|
|
else {
|
|
mana_damage = GetMana() / -10 * spell.limit_value[i];
|
|
SetMana(0);
|
|
Damage(caster, mana_damage, spell_id, spell.skill, false, i, true);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_EndDrainWithDmg:
|
|
{
|
|
if(IsClient()) {
|
|
int end_damage = 0;
|
|
int32 end_to_use = CastToClient()->GetEndurance() - spell.base_value[i];
|
|
if(end_to_use > -1) {
|
|
CastToClient()->SetEndurance(CastToClient()->GetEndurance() - spell.base_value[i]);
|
|
TryTriggerOnCastRequirement();
|
|
// we take full dmg(-10 to make the damage the right sign)
|
|
end_damage = spell.base_value[i] / -10 * spell.limit_value[i];
|
|
Damage(caster, end_damage, spell_id, spell.skill, false, i, true);
|
|
}
|
|
else {
|
|
end_damage = CastToClient()->GetEndurance() / -10 * spell.limit_value[i];
|
|
CastToClient()->SetEndurance(0);
|
|
Damage(caster, end_damage, spell_id, spell.skill, false, i, true);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_SetBodyType:
|
|
{
|
|
SetBodyType((bodyType)spell.base_value[i], false);
|
|
break;
|
|
}
|
|
|
|
case SE_Leap:
|
|
{
|
|
// These effects remove lev and only work a certain distance away.
|
|
BuffFadeByEffect(SE_Levitate);
|
|
if (caster && caster->GetTarget()) {
|
|
float my_x = caster->GetX();
|
|
float my_y = caster->GetY();
|
|
float my_z = caster->GetZ();
|
|
float target_x = GetX();
|
|
float target_y = GetY();
|
|
float target_z = GetZ();
|
|
if ((CalculateDistance(my_x, my_y, my_z) > 10) &&
|
|
(CalculateDistance(my_x, my_y, my_z) < 75) &&
|
|
(caster->CheckLosFN(caster->GetTarget())))
|
|
{
|
|
float value, x_vector, y_vector, hypot;
|
|
|
|
value = (float)spell.base_value[i]; // distance away from target
|
|
|
|
x_vector = target_x - my_x;
|
|
y_vector = target_y - my_y;
|
|
hypot = sqrt(x_vector*x_vector + y_vector*y_vector);
|
|
|
|
x_vector /= hypot;
|
|
y_vector /= hypot;
|
|
|
|
my_x = target_x - (x_vector * value);
|
|
my_y = target_y - (y_vector * value);
|
|
|
|
float new_ground = GetGroundZ(my_x, my_y);
|
|
|
|
if(caster->IsClient())
|
|
caster->CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), my_x, my_y, new_ground, GetHeading());
|
|
else
|
|
caster->GMMove(my_x, my_y, new_ground, GetHeading());
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SE_VoiceGraft:
|
|
{
|
|
if(caster && caster->GetPet())
|
|
caster->spellbonuses.VoiceGraft = caster->GetPetID();
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_ManaBurn:
|
|
{
|
|
int32 max_mana = spell.base_value[i];
|
|
int ratio = spell.limit_value[i];
|
|
int64 dmg = 0;
|
|
|
|
if (caster){
|
|
if (caster->GetMana() <= max_mana){
|
|
dmg = ratio*caster->GetMana()/10;
|
|
caster->SetMana(0);
|
|
}
|
|
|
|
else {
|
|
dmg = ratio*max_mana/10;
|
|
caster->SetMana(caster->GetMana() - max_mana);
|
|
TryTriggerOnCastRequirement();
|
|
}
|
|
|
|
if(IsDetrimentalSpell(spell_id)) {
|
|
dmg = -dmg;
|
|
Damage(caster, dmg, spell_id, spell.skill, false, buffslot, false);
|
|
} else {
|
|
HealDamage(dmg, caster);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_Taunt:
|
|
{
|
|
if (caster && IsNPC()){
|
|
caster->Taunt(CastToNPC(), false, spell.base_value[i], true, spell.limit_value[i]);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_AttackSpeed:
|
|
if (spell.base_value[i] < 100)
|
|
SlowMitigation(caster);
|
|
break;
|
|
|
|
case SE_AttackSpeed2:
|
|
if (spell.base_value[i] < 100)
|
|
SlowMitigation(caster);
|
|
break;
|
|
|
|
case SE_AttackSpeed3:
|
|
if (spell.base_value[i] < 0)
|
|
SlowMitigation(caster);
|
|
break;
|
|
|
|
case SE_AttackSpeed4:
|
|
SlowMitigation(caster);
|
|
break;
|
|
|
|
case SE_AddHatePct:
|
|
{
|
|
if (IsNPC()){
|
|
int64 new_hate = CastToNPC()->GetHateAmount(caster) * (100 + spell.base_value[i]) / 100;
|
|
if (new_hate <= 0)
|
|
new_hate = 1;
|
|
|
|
CastToNPC()->SetHateAmountOnEnt(caster, new_hate);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Hate:{
|
|
|
|
if (buffslot >= 0)
|
|
break;
|
|
|
|
if(caster){
|
|
if(effect_value > 0){
|
|
if(caster){
|
|
if(caster->IsClient() && !caster->CastToClient()->GetFeigned())
|
|
AddToHateList(caster, effect_value);
|
|
else if(!caster->IsClient())
|
|
AddToHateList(caster, effect_value);
|
|
}
|
|
}else{
|
|
int64 newhate = GetHateAmount(caster) + effect_value;
|
|
if (newhate < 1)
|
|
SetHateAmountOnEnt(caster,1);
|
|
else
|
|
SetHateAmountOnEnt(caster,newhate);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_InterruptCasting:{
|
|
if (buffslot >= 0)
|
|
break;
|
|
|
|
if(!spells[spell_id].uninterruptable && IsCasting() && zone->random.Roll(spells[spell_id].base_value[i]))
|
|
InterruptSpell();
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_MassGroupBuff:{
|
|
|
|
SetMGB(true);
|
|
MessageString(Chat::Disciplines, MGB_STRING);
|
|
break;
|
|
}
|
|
|
|
case SE_IllusionOther: {
|
|
SetProjectIllusion(true);
|
|
Message(Chat::NPCQuestSay, "The power of your next illusion spell will flow to your grouped target in your place.");
|
|
break;
|
|
}
|
|
|
|
case SE_ApplyEffect: {
|
|
|
|
if (caster && IsValidSpell(spells[spell_id].limit_value[i])){
|
|
if(zone->random.Roll(spells[spell_id].base_value[i]))
|
|
caster->SpellFinished(spells[spell_id].limit_value[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].limit_value[i]].resist_difficulty);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_SpellTrigger: {
|
|
|
|
if (!spell_trigger_cast_complete) {
|
|
if (caster && caster->TrySpellTrigger(this, spell_id, i))
|
|
spell_trigger_cast_complete = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
case SE_Instant_Mana_Pct: {
|
|
effect_value = spells[spell_id].base_value[i];
|
|
int64 amt = std::abs(GetMaxMana() * effect_value / 10000);
|
|
if (spells[spell_id].max_value[i] && amt > spells[spell_id].max_value[i])
|
|
amt = spells[spell_id].max_value[i];
|
|
|
|
if (effect_value < 0) {
|
|
SetMana(GetMana() - amt);
|
|
}
|
|
else {
|
|
SetMana(GetMana() + amt);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Instant_Endurance_Pct: {
|
|
effect_value = spells[spell_id].base_value[i];
|
|
if (IsClient()) {
|
|
int32 amt = std::abs(CastToClient()->GetMaxEndurance() * effect_value / 10000);
|
|
if (spells[spell_id].max_value[i] && amt > spells[spell_id].max_value[i])
|
|
amt = spells[spell_id].max_value[i];
|
|
|
|
if (effect_value < 0) {
|
|
CastToClient()->SetEndurance(CastToClient()->GetEndurance() - amt);
|
|
}
|
|
else {
|
|
CastToClient()->SetEndurance(CastToClient()->GetEndurance() + amt);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
/*
|
|
Calc for base1 is found in ApplyHealthTransferDamage() due to needing to account for AOE functionality
|
|
since effect can potentially kill caster.
|
|
*/
|
|
case SE_Health_Transfer: {
|
|
effect_value = spells[spell_id].limit_value[i];
|
|
int64 amt = std::abs(caster->GetMaxHP() * effect_value / 1000);
|
|
|
|
if (effect_value < 0) {
|
|
Damage(caster, amt, spell_id, spell.skill, false, buffslot, false);
|
|
}
|
|
else {
|
|
HealDamage(amt, caster);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Chance_Best_in_Spell_Grp: {
|
|
if (!spell_trigger_cast_complete) {
|
|
if (caster && caster->TrySpellTrigger(this, spell_id, i))
|
|
spell_trigger_cast_complete = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Trigger_Best_in_Spell_Grp: {
|
|
|
|
if (caster && !caster->IsClient())
|
|
break;
|
|
|
|
if (zone->random.Roll(spells[spell_id].base_value[i])) {
|
|
uint32 best_spell_id = caster->CastToClient()->GetHighestScribedSpellinSpellGroup(spells[spell_id].limit_value[i]);
|
|
|
|
if (caster && IsValidSpell(best_spell_id))
|
|
caster->SpellFinished(best_spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[best_spell_id].resist_difficulty);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Trigger_Spell_Non_Item: {
|
|
//Only trigger if not from item
|
|
if (caster && caster->IsClient() && GetCastedSpellInvSlot() > 0)
|
|
break;
|
|
|
|
if (zone->random.Roll(spells[spell_id].base_value[i]) && IsValidSpell(spells[spell_id].limit_value[i]))
|
|
caster->SpellFinished(spells[spell_id].limit_value[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[spell_id].limit_value[i]].resist_difficulty);
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_Hatelist_To_Tail_Index: {
|
|
if (caster && zone->random.Roll(spells[spell_id].base_value[i]))
|
|
caster->SetBottomRampageList();
|
|
break;
|
|
}
|
|
|
|
case SE_Hatelist_To_Top_Index: {
|
|
if (caster && zone->random.Roll(spells[spell_id].base_value[i]))
|
|
caster->SetTopRampageList();
|
|
break;
|
|
}
|
|
|
|
case SE_Fearstun: {
|
|
//Normal 'stun' restrictions do not apply. base1=duration, base2=PC duration, max =lv restrict
|
|
if (!caster)
|
|
break;
|
|
|
|
if (IsNPC() && GetSpecialAbility(UNSTUNABLE)) {
|
|
caster->MessageString(Chat::SpellFailure, IMMUNE_STUN);
|
|
break;
|
|
}
|
|
|
|
if (IsNPC() && GetSpecialAbility(UNFEARABLE)) {
|
|
caster->MessageString(Chat::SpellFailure, IMMUNE_FEAR);
|
|
break;
|
|
}
|
|
int max_level = 0;
|
|
if (spells[spell_id].max_value[i] >= 1000) {
|
|
max_level = spells[spell_id].max_value[i] - 1000;
|
|
}
|
|
else {
|
|
max_level = caster->GetLevel() + spells[spell_id].max_value[i];
|
|
}
|
|
|
|
if (spells[spell_id].max_value[i] == 0 || GetLevel() <= max_level) {
|
|
if (IsClient() && spells[spell_id].limit_value[i])
|
|
Stun(spells[spell_id].limit_value[i]);
|
|
else
|
|
Stun(spells[spell_id].base_value[i]);
|
|
}
|
|
else
|
|
caster->MessageString(Chat::SpellFailure, FEAR_TOO_HIGH);
|
|
break;
|
|
}
|
|
|
|
case SE_PetShield: {
|
|
if (IsPet()) {
|
|
Mob* petowner = GetOwner();
|
|
if (petowner) {
|
|
int shield_duration = spells[spell_id].base_value[i] * 12 * 1000;
|
|
int shield_target_mitigation = spells[spell_id].limit_value[i] ? spells[spell_id].limit_value[i] : 50;
|
|
int shielder_mitigation = spells[spell_id].max_value[i] ? spells[spell_id].limit_value[i] : 50;
|
|
ShieldAbility(petowner->GetID(), 25, shield_duration, shield_target_mitigation, shielder_mitigation);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
case SE_Weapon_Stance: {
|
|
if (IsClient()) {
|
|
CastToClient()->ApplyWeaponsStance();
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_HealOverTime: {
|
|
//This is here so buffs with hit counters tic down on initial cast.
|
|
caster->GetActSpellHealing(spell_id, effect_value, nullptr, false);
|
|
break;
|
|
}
|
|
|
|
case SE_PersistentEffect:
|
|
MakeAura(spell_id);
|
|
break;
|
|
|
|
// Handled Elsewhere
|
|
case SE_ImmuneFleeing:
|
|
case SE_NegateSpellEffect:
|
|
case SE_Knockdown: // handled by client
|
|
case SE_ShadowStepDirectional: // handled by client
|
|
case SE_SpellOnDeath:
|
|
case SE_BlockNextSpellFocus:
|
|
case SE_ReduceReuseTimer:
|
|
case SE_SwarmPetDuration:
|
|
case SE_LimitHPPercent:
|
|
case SE_LimitManaPercent:
|
|
case SE_LimitEndPercent:
|
|
case SE_ExtraAttackChance:
|
|
case SE_ProcChance:
|
|
case SE_StunResist:
|
|
case SE_MinDamageModifier:
|
|
case SE_DamageModifier:
|
|
case SE_DamageModifier2:
|
|
case SE_HitChance:
|
|
case SE_MeleeSkillCheck:
|
|
case SE_HundredHands:
|
|
case SE_ResistFearChance:
|
|
case SE_ResistSpellChance:
|
|
case SE_AllInstrumentMod:
|
|
case SE_MeleeLifetap:
|
|
case SE_DoubleAttackChance:
|
|
case SE_TripleAttackChance:
|
|
case SE_DualWieldChance:
|
|
case SE_ParryChance:
|
|
case SE_DodgeChance:
|
|
case SE_RiposteChance:
|
|
case SE_AvoidMeleeChance:
|
|
case SE_CrippBlowChance:
|
|
case SE_CriticalHitChance:
|
|
case SE_MeleeMitigation:
|
|
case SE_Reflect:
|
|
case SE_Screech:
|
|
case SE_Amplification:
|
|
case SE_MagicWeapon:
|
|
case SE_Hunger:
|
|
case SE_MagnifyVision:
|
|
case SE_Lycanthropy:
|
|
case SE_NegateIfCombat:
|
|
case SE_CastingLevel:
|
|
case SE_CastingLevel2:
|
|
case SE_RaiseStatCap:
|
|
case SE_ResistAll:
|
|
case SE_ResistMagic:
|
|
case SE_ResistDisease:
|
|
case SE_ResistPoison:
|
|
case SE_ResistCold:
|
|
case SE_ResistFire:
|
|
case SE_AllStats:
|
|
case SE_MakeDrunk:
|
|
case SE_CHA:
|
|
case SE_WIS:
|
|
case SE_INT:
|
|
case SE_STA:
|
|
case SE_AGI:
|
|
case SE_DEX:
|
|
case SE_STR:
|
|
case SE_ATK:
|
|
case SE_ArmorClass:
|
|
case SE_EndurancePool:
|
|
case SE_Stamina:
|
|
case SE_UltraVision:
|
|
case SE_InfraVision:
|
|
case SE_ManaPool:
|
|
case SE_TotalHP:
|
|
case SE_ChangeFrenzyRad:
|
|
case SE_Harmony:
|
|
case SE_ChangeAggro:
|
|
case SE_Identify:
|
|
case SE_InstantHate:
|
|
case SE_ReduceHate:
|
|
case SE_SpellDamageShield:
|
|
case SE_ReverseDS:
|
|
case SE_DamageShield:
|
|
case SE_TrueNorth:
|
|
case SE_WaterBreathing:
|
|
case SE_MovementSpeed:
|
|
case SE_PercentXPIncrease:
|
|
case SE_DivineSave:
|
|
case SE_Accuracy:
|
|
case SE_Flurry:
|
|
case SE_ImprovedDamage:
|
|
case SE_ImprovedDamage2:
|
|
case SE_ImprovedHeal:
|
|
case SE_IncreaseSpellHaste:
|
|
case SE_IncreaseSpellDuration:
|
|
case SE_IncreaseRange:
|
|
case SE_SpellHateMod:
|
|
case SE_ReduceReagentCost:
|
|
case SE_ReduceManaCost:
|
|
case SE_LimitMaxLevel:
|
|
case SE_LimitResist:
|
|
case SE_LimitTarget:
|
|
case SE_LimitEffect:
|
|
case SE_LimitSpellType:
|
|
case SE_LimitSpell:
|
|
case SE_LimitMinDur:
|
|
case SE_LimitInstant:
|
|
case SE_LimitMinLevel:
|
|
case SE_LimitCastTimeMin:
|
|
case SE_LimitManaMin:
|
|
case SE_LimitCombatSkills:
|
|
case SE_SpellDurationIncByTic:
|
|
case SE_TriggerOnCast:
|
|
case SE_HealRate:
|
|
case SE_SkillDamageTaken:
|
|
case SE_FcSpellVulnerability:
|
|
case SE_Fc_Spell_Damage_Pct_IncomingPC:
|
|
case SE_Fc_Spell_Damage_Amt_IncomingPC:
|
|
case SE_FcTwincast:
|
|
case SE_DelayDeath:
|
|
case SE_CastOnFadeEffect:
|
|
case SE_CastOnFadeEffectNPC:
|
|
case SE_CastOnFadeEffectAlways:
|
|
case SE_CastOnRuneFadeEffect:
|
|
case SE_MaxHPChange:
|
|
case SE_SympatheticProc:
|
|
case SE_FcDamageAmt:
|
|
case SE_FcDamageAmt2:
|
|
case SE_CriticalSpellChance:
|
|
case SE_SpellCritChance:
|
|
case SE_SpellCritDmgIncrease:
|
|
case SE_DotCritDmgIncrease:
|
|
case SE_CriticalHealChance:
|
|
case SE_CriticalHealOverTime:
|
|
case SE_CriticalDoTChance:
|
|
case SE_ProcOnKillShot:
|
|
case SE_ProcOnSpellKillShot:
|
|
case SE_CriticalDamageMob:
|
|
case SE_LimitSpellGroup:
|
|
case SE_ResistCorruption:
|
|
case SE_ReduceSkillTimer:
|
|
case SE_HPToMana:
|
|
case SE_ManaAbsorbPercentDamage:
|
|
case SE_SkillDamageAmount:
|
|
case SE_SkillDamageAmount2:
|
|
case SE_GravityEffect:
|
|
case SE_IncreaseBlockChance:
|
|
case SE_AntiGate:
|
|
case SE_Fearless:
|
|
case SE_FcDamageAmtCrit:
|
|
case SE_FcHealAmtCrit:
|
|
case SE_CastOnCurer:
|
|
case SE_CastOnCure:
|
|
case SE_CastonNumHitFade:
|
|
case SE_LimitToSkill:
|
|
case SE_SpellProcChance:
|
|
case SE_CharmBreakChance:
|
|
case SE_BardSongRange:
|
|
case SE_ACv2:
|
|
case SE_ManaRegen_v2:
|
|
case SE_FcDamagePctCrit:
|
|
case SE_FcHealAmt:
|
|
case SE_FcHealPctIncoming:
|
|
case SE_CriticalHealDecay:
|
|
case SE_CriticalRegenDecay:
|
|
case SE_FcDamageAmtIncoming:
|
|
case SE_LimitCastingSkill:
|
|
case SE_MitigateDamageShield:
|
|
case SE_FcBaseEffects:
|
|
case SE_LimitClass:
|
|
case SE_BlockBehind:
|
|
case SE_ShieldBlock:
|
|
case SE_PetCriticalHit:
|
|
case SE_SlayUndead:
|
|
case SE_GiveDoubleAttack:
|
|
case SE_StrikeThrough:
|
|
case SE_StrikeThrough2:
|
|
case SE_SecondaryDmgInc:
|
|
case SE_ArcheryDamageModifier:
|
|
case SE_ConsumeProjectile:
|
|
case SE_ForageAdditionalItems:
|
|
case SE_Salvage:
|
|
case SE_FrontalBackstabChance:
|
|
case SE_FrontalBackstabMinDmg:
|
|
case SE_TripleBackstab:
|
|
case SE_DoubleSpecialAttack:
|
|
case SE_IncreaseRunSpeedCap:
|
|
case SE_BaseMovementSpeed:
|
|
case SE_FrontalStunResist:
|
|
case SE_ImprovedBindWound:
|
|
case SE_MaxBindWound:
|
|
case SE_CombatStability:
|
|
case SE_AddSingingMod:
|
|
case SE_SongModCap:
|
|
case SE_HeadShot:
|
|
case SE_HeadShotLevel:
|
|
case SE_PetAvoidance:
|
|
case SE_GiveDoubleRiposte:
|
|
case SE_Ambidexterity:
|
|
case SE_PetMaxHP:
|
|
case SE_PetFlurry:
|
|
case SE_MasteryofPast:
|
|
case SE_GivePetGroupTarget:
|
|
case SE_RootBreakChance:
|
|
case SE_UnfailingDivinity:
|
|
case SE_ChannelChanceSpells:
|
|
case SE_ChannelChanceItems:
|
|
case SE_FcHealPctCritIncoming:
|
|
case SE_FcIncreaseNumHits:
|
|
case SE_CastonFocusEffect:
|
|
case SE_FcHealAmtIncoming:
|
|
case SE_LimitManaMax:
|
|
case SE_DoubleRangedAttack:
|
|
case SE_ShieldEquipDmgMod:
|
|
case SE_TriggerOnReqTarget:
|
|
case SE_LimitRace:
|
|
case SE_FcLimitUse:
|
|
case SE_FcMute:
|
|
case SE_LimitUseType:
|
|
case SE_FcStunTimeMod:
|
|
case SE_StunBashChance:
|
|
case SE_IncreaseChanceMemwipe:
|
|
case SE_CriticalMend:
|
|
case SE_LimitCastTimeMax:
|
|
case SE_TriggerOnReqCaster:
|
|
case SE_FrenziedDevastation:
|
|
case SE_AStacker:
|
|
case SE_BStacker:
|
|
case SE_CStacker:
|
|
case SE_DStacker:
|
|
case SE_DoubleRiposte:
|
|
case SE_Berserk:
|
|
case SE_Vampirism:
|
|
case SE_Metabolism:
|
|
case SE_FinishingBlow:
|
|
case SE_FinishingBlowLvl:
|
|
case SE_Assassinate:
|
|
case SE_AssassinateLevel:
|
|
case SE_FactionModPct:
|
|
case SE_LimitSpellClass:
|
|
case SE_Sanctuary:
|
|
case SE_PetMeleeMitigation:
|
|
case SE_SkillProcAttempt:
|
|
case SE_SkillProcSuccess:
|
|
case SE_SpellResistReduction:
|
|
case SE_Duration_HP_Pct:
|
|
case SE_Duration_Mana_Pct:
|
|
case SE_Duration_Endurance_Pct:
|
|
case SE_Endurance_Absorb_Pct_Damage:
|
|
case SE_AC_Mitigation_Max_Percent:
|
|
case SE_AC_Avoidance_Max_Percent:
|
|
case SE_Attack_Accuracy_Max_Percent:
|
|
case SE_Critical_Melee_Damage_Mod_Max:
|
|
case SE_Melee_Damage_Position_Mod:
|
|
case SE_Damage_Taken_Position_Mod:
|
|
case SE_Melee_Damage_Position_Amt:
|
|
case SE_Damage_Taken_Position_Amt:
|
|
case SE_DS_Mitigation_Amount:
|
|
case SE_DS_Mitigation_Percentage:
|
|
case SE_Double_Backstab_Front:
|
|
case SE_Pet_Crit_Melee_Damage_Pct_Owner:
|
|
case SE_Pet_Add_Atk:
|
|
case SE_TwinCastBlocker:
|
|
case SE_Fc_Cast_Spell_On_Land:
|
|
case SE_Ff_CasterClass:
|
|
case SE_Ff_Same_Caster:
|
|
case SE_Fc_ResistIncoming:
|
|
case SE_Fc_Amplify_Amt:
|
|
case SE_Fc_Amplify_Mod:
|
|
case SE_Fc_CastTimeAmt:
|
|
case SE_Fc_CastTimeMod2:
|
|
case SE_Ff_DurationMax:
|
|
case SE_Ff_Endurance_Max:
|
|
case SE_Ff_Endurance_Min:
|
|
case SE_Ff_ReuseTimeMin:
|
|
case SE_Ff_ReuseTimeMax:
|
|
case SE_Ff_Value_Min:
|
|
case SE_Ff_Value_Max:
|
|
case SE_AddExtraAttackPct_1h_Primary:
|
|
case SE_AddExtraAttackPct_1h_Secondary:
|
|
case SE_Double_Melee_Round:
|
|
case SE_Skill_Base_Damage_Mod:
|
|
case SE_Worn_Endurance_Regen_Cap:
|
|
case SE_Buy_AA_Rank:
|
|
case SE_Ff_FocusTimerMin:
|
|
case SE_Proc_Timer_Modifier:
|
|
case SE_FFItemClass:
|
|
case SE_SpellEffectResistChance:
|
|
case SE_SeeInvis:
|
|
case SE_Invisibility:
|
|
case SE_Invisibility2:
|
|
case SE_InvisVsAnimals:
|
|
case SE_ImprovedInvisAnimals:
|
|
case SE_InvisVsUndead:
|
|
case SE_InvisVsUndead2:
|
|
{
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Unknown Effect ID %d", effect);
|
|
#else
|
|
Message(Chat::White, "Unknown spell effect %d in spell %s (id %d)", effect, spell.name, spell_id);
|
|
#endif
|
|
}
|
|
}
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
Message(Chat::White, ". . . Effect #%i: %s", i + 1, (effect_desc && effect_desc[0]) ? effect_desc : "Unknown");
|
|
#endif
|
|
}
|
|
|
|
CalcBonuses();
|
|
|
|
if (SummonedItem) {
|
|
Client *c=CastToClient();
|
|
c->PushItemOnCursor(*SummonedItem);
|
|
c->SendItemPacket(EQ::invslot::slotCursor, SummonedItem, ItemPacketLimbo);
|
|
safe_delete(SummonedItem);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int64 Mob::CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level, uint32 instrument_mod, Mob *caster,
|
|
int ticsremaining, uint16 caster_id)
|
|
{
|
|
if (!IsValidSpell(spell_id) || effect_id < 0 || effect_id >= EFFECT_COUNT)
|
|
return 0;
|
|
|
|
int formula = spells[spell_id].formula[effect_id];
|
|
int base_value = spells[spell_id].base_value[effect_id];
|
|
int max_value = spells[spell_id].max_value[effect_id];
|
|
int effect_value = 0;
|
|
int oval = 0;
|
|
|
|
if (IsBlankSpellEffect(spell_id, effect_id))
|
|
return 0;
|
|
|
|
effect_value = CalcSpellEffectValue_formula(formula, base_value, max_value, caster_level, spell_id, ticsremaining);
|
|
|
|
// this doesn't actually need to be a song to get mods, just the right skill
|
|
if (EQ::skills::IsBardInstrumentSkill(spells[spell_id].skill)
|
|
&& IsInstrumentModAppliedToSpellEffect(spell_id, spells[spell_id].effect_id[effect_id])) {
|
|
oval = effect_value;
|
|
effect_value = effect_value * static_cast<int>(instrument_mod) / 10;
|
|
LogSpells("Effect value [{}] altered with bard modifier of [{}] to yeild [{}]",
|
|
oval, instrument_mod, effect_value);
|
|
}
|
|
/*
|
|
SPA 413 SE_FcBaseEffects, modifies base value of a spell effect after formula calcultion, but before other focuses.
|
|
This is applied to non-Bards in Mob::GetInstrumentMod
|
|
Like bard modifiers, this is sent in the action_struct using action->instrument_mod (which is a base effect modifier)
|
|
|
|
Issue: value sent with action->instrument_mod needs to be 10 or higher. Therefore lowest possible percent chance would be 11 (calculated to 10%)
|
|
there are modern spells that use less than 10% but we send as a uint where lowest value has to be 10, where it should be a float for current clients.
|
|
Though not ideal, at the moment for spells that are instant effects, the action packet doesn't matter and we will calculate the actual percent here correctly.
|
|
Logic here is, caster_id is only sent from ApplySpellBonuses. Thus if it is a buff a long as the base effects is set to over 10% and at +10% intervals
|
|
it will focus the base value correctly.
|
|
|
|
*/
|
|
|
|
/*
|
|
Calculate base effects modifier for casters who are not bards.
|
|
*/
|
|
|
|
//This is checked from Mob::SpellEffects and applied to instant spells and runes.
|
|
if (caster && caster->GetClass() != BARD && caster->HasBaseEffectFocus()) {
|
|
|
|
oval = effect_value;
|
|
int mod = caster->GetFocusEffect(focusFcBaseEffects, spell_id);
|
|
effect_value += effect_value * mod / 100;
|
|
|
|
LogSpells("Instant Effect value [{}] altered with base effects modifier of [{}] to yeild [{}]",
|
|
oval, mod, effect_value);
|
|
}
|
|
//This is checked from Mob::ApplySpellBonuses, applied to buffs that receive bonuses. See above, must be in 10% intervals to work.
|
|
else if (caster_id && instrument_mod > 10) {
|
|
|
|
Mob* buff_caster = entity_list.GetMob(caster_id);//If targeted bard song needed to confirm caster is not bard.
|
|
if (buff_caster && buff_caster->GetClass() != BARD) {
|
|
oval = effect_value;
|
|
effect_value = effect_value * static_cast<int>(instrument_mod) / 10;
|
|
|
|
LogSpells("Bonus Effect value [{}] altered with base effects modifier of [{}] to yeild [{}]",
|
|
oval, instrument_mod, effect_value);
|
|
}
|
|
}
|
|
|
|
effect_value = mod_effect_value(effect_value, spell_id, spells[spell_id].effect_id[effect_id], caster, caster_id);
|
|
|
|
return effect_value;
|
|
}
|
|
|
|
// generic formula calculations
|
|
int64 Mob::CalcSpellEffectValue_formula(uint32 formula, int64 base_value, int64 max_value, int caster_level, uint16 spell_id, int ticsremaining)
|
|
{
|
|
#ifdef LUA_EQEMU
|
|
int64 lua_ret = 0;
|
|
bool ignoreDefault = false;
|
|
lua_ret = LuaParser::Instance()->CalcSpellEffectValue_formula(this, formula, base_value, max_value, caster_level, spell_id, ticsremaining, ignoreDefault);
|
|
|
|
if (ignoreDefault) {
|
|
return lua_ret;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
i need those formulas checked!!!!
|
|
|
|
0 = base
|
|
1 - 99 = base + level * formulaID
|
|
100 = base
|
|
101 = base + level / 2
|
|
102 = base + level
|
|
103 = base + level * 2
|
|
104 = base + level * 3
|
|
105 = base + level * 4
|
|
106 ? base + level * 5
|
|
107 ? min + level / 2
|
|
108 = min + level / 3
|
|
109 = min + level / 4
|
|
110 = min + level / 5
|
|
119 ? min + level / 8
|
|
121 ? min + level / 4
|
|
122 = splurt
|
|
123 ?
|
|
203 = stacking issues ? max
|
|
205 = stacking issues ? 105
|
|
|
|
|
|
0x77 = min + level / 8
|
|
*/
|
|
|
|
int64 result = 0, updownsign = 1, ubase = base_value;
|
|
if(ubase < 0)
|
|
ubase = 0 - ubase;
|
|
|
|
// this updown thing might look messed up but if you look at the
|
|
// spells it actually looks like some have a positive base and max where
|
|
// the max is actually less than the base, hence they grow downward
|
|
/*
|
|
This seems to mainly catch spells where both base and max are negative.
|
|
Strangely, damage spells have a negative base and positive max, but
|
|
snare has both of them negative, yet their range should work the same:
|
|
(meaning they both start at a negative value and the value gets lower)
|
|
*/
|
|
if (max_value < base_value && max_value != 0)
|
|
{
|
|
// values are calculated down
|
|
updownsign = -1;
|
|
}
|
|
else
|
|
{
|
|
// values are calculated up
|
|
updownsign = 1;
|
|
}
|
|
|
|
LogSpells("spell [{}] formula [{}] base [{}] max [{}] lvl [{}] Up/Down [{}]",
|
|
spell_id, formula, base_value, max_value, caster_level, updownsign);
|
|
|
|
switch(formula)
|
|
{
|
|
case 60: //used in stun spells..?
|
|
case 70:
|
|
result = ubase/100; break;
|
|
case 0:
|
|
case 100: // confirmed 2/6/04
|
|
result = ubase; break;
|
|
case 101: // confirmed 2/6/04
|
|
result = updownsign * (ubase + (caster_level / 2)); break;
|
|
case 102: // confirmed 2/6/04
|
|
result = updownsign * (ubase + caster_level); break;
|
|
case 103: // confirmed 2/6/04
|
|
result = updownsign * (ubase + (caster_level * 2)); break;
|
|
case 104: // confirmed 2/6/04
|
|
result = updownsign * (ubase + (caster_level * 3)); break;
|
|
case 105: // confirmed 2/6/04
|
|
result = updownsign * (ubase + (caster_level * 4)); break;
|
|
|
|
case 107:
|
|
{
|
|
int ticdif = CalcBuffDuration_formula(caster_level, spells[spell_id].buff_duration_formula, spells[spell_id].buff_duration) - std::max((ticsremaining - 1), 0);
|
|
if (ticdif < 0)
|
|
ticdif = 0;
|
|
result = updownsign * (ubase - ticdif);
|
|
degenerating_effects = true;
|
|
break;
|
|
}
|
|
case 108:
|
|
{
|
|
int ticdif = CalcBuffDuration_formula(caster_level, spells[spell_id].buff_duration_formula, spells[spell_id].buff_duration) - std::max((ticsremaining - 1), 0);
|
|
if (ticdif < 0)
|
|
ticdif = 0;
|
|
result = updownsign * (ubase - (2 * ticdif));
|
|
degenerating_effects = true;
|
|
break;
|
|
}
|
|
case 109: // confirmed 2/6/04
|
|
result = updownsign * (ubase + (caster_level / 4)); break;
|
|
|
|
case 110: // confirmed 2/6/04
|
|
//is there a reason we dont use updownsign here???
|
|
result = ubase + (caster_level / 6);
|
|
break;
|
|
|
|
case 111:
|
|
result = updownsign * (ubase + 6 * (caster_level - 16));
|
|
break;
|
|
case 112:
|
|
result = updownsign * (ubase + 8 * (caster_level - 24));
|
|
break;
|
|
case 113:
|
|
result = updownsign * (ubase + 10 * (caster_level - 34));
|
|
break;
|
|
case 114:
|
|
result = updownsign * (ubase + 15 * (caster_level - 44));
|
|
break;
|
|
|
|
case 115: // this is only in symbol of transal
|
|
result = ubase;
|
|
if (caster_level > 15)
|
|
result += 7 * (caster_level - 15);
|
|
break;
|
|
case 116: // this is only in symbol of ryltan
|
|
result = ubase;
|
|
if (caster_level > 24)
|
|
result += 10 * (caster_level - 24);
|
|
break;
|
|
case 117: // this is only in symbol of pinzarn
|
|
result = ubase;
|
|
if (caster_level > 34)
|
|
result += 13 * (caster_level - 34);
|
|
break;
|
|
case 118: // used in naltron and a few others
|
|
result = ubase;
|
|
if (caster_level > 44)
|
|
result += 20 * (caster_level - 44);
|
|
break;
|
|
|
|
case 119: // confirmed 2/6/04
|
|
result = ubase + (caster_level / 8);
|
|
break;
|
|
case 120:
|
|
{
|
|
int ticdif = CalcBuffDuration_formula(caster_level, spells[spell_id].buff_duration_formula, spells[spell_id].buff_duration) - std::max((ticsremaining - 1), 0);
|
|
if (ticdif < 0)
|
|
ticdif = 0;
|
|
result = updownsign * (ubase - (5 * ticdif));
|
|
degenerating_effects = true;
|
|
break;
|
|
}
|
|
case 121: // corrected 2/6/04
|
|
result = ubase + (caster_level / 3); break;
|
|
case 122:
|
|
{
|
|
int ticdif = CalcBuffDuration_formula(caster_level, spells[spell_id].buff_duration_formula, spells[spell_id].buff_duration) - std::max((ticsremaining - 1), 0);
|
|
if(ticdif < 0)
|
|
ticdif = 0;
|
|
|
|
result = updownsign * (ubase - (12 * ticdif));
|
|
degenerating_effects = true;
|
|
break;
|
|
}
|
|
case 123: // added 2/6/04
|
|
result = zone->random.Int(ubase, std::abs(max_value));
|
|
break;
|
|
|
|
case 124: // check sign
|
|
result = ubase;
|
|
if (caster_level > 50)
|
|
result += updownsign * (caster_level - 50);
|
|
break;
|
|
|
|
case 125: // check sign
|
|
result = ubase;
|
|
if (caster_level > 50)
|
|
result += updownsign * 2 * (caster_level - 50);
|
|
break;
|
|
|
|
case 126: // check sign
|
|
result = ubase;
|
|
if (caster_level > 50)
|
|
result += updownsign * 3 * (caster_level - 50);
|
|
break;
|
|
|
|
case 127: // check sign
|
|
result = ubase;
|
|
if (caster_level > 50)
|
|
result += updownsign * 4 * (caster_level - 50);
|
|
break;
|
|
|
|
case 128: // check sign
|
|
result = ubase;
|
|
if (caster_level > 50)
|
|
result += updownsign * 5 * (caster_level - 50);
|
|
break;
|
|
|
|
case 129: // check sign
|
|
result = ubase;
|
|
if (caster_level > 50)
|
|
result += updownsign * 10 * (caster_level - 50);
|
|
break;
|
|
|
|
case 130: // check sign
|
|
result = ubase;
|
|
if (caster_level > 50)
|
|
result += updownsign * 15 * (caster_level - 50);
|
|
break;
|
|
|
|
case 131: // check sign
|
|
result = ubase;
|
|
if (caster_level > 50)
|
|
result += updownsign * 20 * (caster_level - 50);
|
|
break;
|
|
|
|
case 132: // check sign
|
|
result = ubase;
|
|
if (caster_level > 50)
|
|
result += updownsign * 25 * (caster_level - 50);
|
|
break;
|
|
|
|
case 137: // used in berserker AA desperation
|
|
result = ubase - static_cast<int>((ubase * (GetHPRatio() / 100.0f)));
|
|
break;
|
|
|
|
case 138: { // unused on live?
|
|
int64 maxhps = GetMaxHP() / 2;
|
|
if (GetHP() <= maxhps)
|
|
result = -(ubase * GetHP() / maxhps);
|
|
else
|
|
result = -ubase;
|
|
break;
|
|
}
|
|
|
|
case 139: // check sign
|
|
result = ubase + (caster_level > 30 ? (caster_level - 30) / 2 : 0);
|
|
break;
|
|
|
|
case 140: // check sign
|
|
result = ubase + (caster_level > 30 ? caster_level - 30 : 0);
|
|
break;
|
|
|
|
case 141: // check sign
|
|
result = ubase + (caster_level > 30 ? (3 * caster_level - 90) / 2 : 0);
|
|
break;
|
|
|
|
case 142: // check sign
|
|
result = ubase + (caster_level > 30 ? 2 * caster_level - 60 : 0);
|
|
break;
|
|
|
|
case 143: // check sign
|
|
result = ubase + (3 * caster_level / 4);
|
|
break;
|
|
|
|
//these are used in stacking effects... formula unknown
|
|
case 201:
|
|
case 203:
|
|
result = max_value;
|
|
break;
|
|
default:
|
|
{
|
|
if (formula < 100)
|
|
result = ubase + (caster_level * formula);
|
|
else if((formula > 1000) && (formula < 1999))
|
|
{
|
|
// These work like splurt, accept instead of being hard coded to 12, it is formula - 1000.
|
|
// Formula 1999 seems to have a slightly different effect, so is not included here
|
|
int64 ticdif = CalcBuffDuration_formula(caster_level, spells[spell_id].buff_duration_formula, spells[spell_id].buff_duration) - std::max((ticsremaining - 1), 0);
|
|
if(ticdif < 0)
|
|
ticdif = 0;
|
|
|
|
result = updownsign * (ubase - ((formula - 1000) * ticdif));
|
|
degenerating_effects = true;
|
|
}
|
|
else if((formula >= 2000) && (formula <= 2650))
|
|
{
|
|
// Source: http://crucible.samanna.net/viewtopic.php?f=38&t=6259
|
|
result = ubase * (caster_level * (formula - 2000) + 1);
|
|
}
|
|
else
|
|
LogDebug("Unknown spell effect value forumula [{}]", formula);
|
|
}
|
|
}
|
|
|
|
int64 oresult = result;
|
|
|
|
// now check result against the allowed maximum
|
|
if (max_value != 0)
|
|
{
|
|
if (updownsign == 1)
|
|
{
|
|
if (result > max_value)
|
|
result = max_value;
|
|
}
|
|
else
|
|
{
|
|
if (result < max_value)
|
|
result = max_value;
|
|
}
|
|
}
|
|
|
|
// if base is less than zero, then the result need to be negative too
|
|
if (base_value < 0 && result > 0)
|
|
result *= -1;
|
|
|
|
LogSpells("Result: [{}] (orig [{}]) cap [{}] [{}]", result, oresult, max_value, (base_value < 0 && result > 0)?"Inverted due to negative base":"");
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
void Mob::BuffProcess()
|
|
{
|
|
int buff_count = GetMaxTotalSlots();
|
|
|
|
for (int buffs_i = 0; buffs_i < buff_count; ++buffs_i)
|
|
{
|
|
if (buffs[buffs_i].spellid != SPELL_UNKNOWN)
|
|
{
|
|
DoBuffTic(buffs[buffs_i], buffs_i, entity_list.GetMob(buffs[buffs_i].casterid));
|
|
// If the Mob died during DoBuffTic, then the buff we are currently processing will have been removed
|
|
if(buffs[buffs_i].spellid == SPELL_UNKNOWN)
|
|
continue;
|
|
|
|
// DF_Permanent uses -1 DF_Aura uses -4 but we need to check negatives for some spells for some reason?
|
|
if (spells[buffs[buffs_i].spellid].buff_duration_formula != DF_Permanent &&
|
|
spells[buffs[buffs_i].spellid].buff_duration_formula != DF_Aura &&
|
|
buffs[buffs_i].ticsremaining != PERMANENT_BUFF_DURATION) {
|
|
if(!zone->BuffTimersSuspended() || !IsSuspendableSpell(buffs[buffs_i].spellid))
|
|
{
|
|
--buffs[buffs_i].ticsremaining;
|
|
|
|
if (buffs[buffs_i].ticsremaining < 0) {
|
|
LogSpells("Buff [{}] in slot [{}] has expired. Fading", buffs[buffs_i].spellid, buffs_i);
|
|
BuffFadeBySlot(buffs_i);
|
|
}
|
|
else
|
|
{
|
|
LogSpells("Buff [{}] in slot [{}] has [{}] tics remaining", buffs[buffs_i].spellid, buffs_i, buffs[buffs_i].ticsremaining);
|
|
}
|
|
}
|
|
else if (IsClient() && !(CastToClient()->ClientVersionBit() & EQ::versions::maskSoFAndLater))
|
|
{
|
|
buffs[buffs_i].UpdateClient = true;
|
|
}
|
|
}
|
|
|
|
if(IsClient())
|
|
{
|
|
if(buffs[buffs_i].UpdateClient == true)
|
|
{
|
|
CastToClient()->SendBuffDurationPacket(buffs[buffs_i], buffs_i);
|
|
// Hack to get UF to play nicer, RoF seems fine without it
|
|
if (CastToClient()->ClientVersion() == EQ::versions::ClientVersion::UF && buffs[buffs_i].hit_number > 0)
|
|
CastToClient()->SendBuffNumHitPacket(buffs[buffs_i], buffs_i);
|
|
buffs[buffs_i].UpdateClient = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster)
|
|
{
|
|
int effect, effect_value;
|
|
|
|
if (!IsValidSpell(buff.spellid))
|
|
return;
|
|
|
|
const SPDat_Spell_Struct &spell = spells[buff.spellid];
|
|
|
|
std::string export_string = fmt::format(
|
|
"{} {} {} {}",
|
|
caster ? caster->GetID() : 0,
|
|
buffs[slot].ticsremaining,
|
|
caster ? caster->GetLevel() : 0,
|
|
slot
|
|
);
|
|
|
|
if (IsClient()) {
|
|
if (parse->EventSpell(EVENT_SPELL_EFFECT_BUFF_TIC_CLIENT, nullptr, CastToClient(), buff.spellid, export_string, 0) != 0) {
|
|
return;
|
|
}
|
|
} else if (IsNPC()) {
|
|
if (parse->EventSpell(EVENT_SPELL_EFFECT_BUFF_TIC_NPC, this, nullptr, buff.spellid, export_string, 0) != 0) {
|
|
return;
|
|
}
|
|
} else if (IsBot()) {
|
|
if (parse->EventSpell(EVENT_SPELL_EFFECT_BUFF_TIC_BOT, this, nullptr, buff.spellid, export_string, 0) != 0) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < EFFECT_COUNT; i++) {
|
|
if (IsBlankSpellEffect(buff.spellid, i))
|
|
continue;
|
|
|
|
effect = spell.effect_id[i];
|
|
// I copied the calculation into each case which needed it instead of
|
|
// doing it every time up here, since most buff effects dont need it
|
|
|
|
switch (effect) {
|
|
case SE_CurrentHP: {
|
|
if (spells[buff.spellid].limit_value[i] && !PassCastRestriction(spells[buff.spellid].limit_value[i])) {
|
|
break;
|
|
}
|
|
|
|
effect_value = CalcSpellEffectValue(buff.spellid, i, buff.casterlevel, buff.instrument_mod,
|
|
caster, buff.ticsremaining);
|
|
// Handle client cast DOTs here.
|
|
if (caster && effect_value < 0) {
|
|
|
|
if (IsDetrimentalSpell(buff.spellid)) {
|
|
if (caster->IsClient()) {
|
|
if (!caster->CastToClient()->GetFeigned())
|
|
AddToHateList(caster, -effect_value);
|
|
} else if (!IsClient()) // Allow NPC's to generate hate if casted on other
|
|
// NPC's.
|
|
AddToHateList(caster, -effect_value);
|
|
}
|
|
|
|
effect_value = caster->GetActDoTDamage(buff.spellid, effect_value, this);
|
|
|
|
caster->ResourceTap(-effect_value, buff.spellid);
|
|
effect_value = -effect_value;
|
|
Damage(caster, effect_value, buff.spellid, spell.skill, false, i, true);
|
|
} else if (effect_value > 0) {
|
|
// Regen spell...
|
|
// handled with bonuses
|
|
}
|
|
break;
|
|
}
|
|
case SE_HealOverTime: {
|
|
effect_value = CalcSpellEffectValue(buff.spellid, i, buff.casterlevel, buff.instrument_mod);
|
|
if (caster) {
|
|
effect_value = caster->GetActSpellHealing(buff.spellid, effect_value, nullptr, true);
|
|
}
|
|
|
|
HealDamage(effect_value, caster, buff.spellid);
|
|
// healing aggro would go here; removed for now
|
|
break;
|
|
}
|
|
|
|
case SE_CurrentEndurance: {
|
|
// Handled with bonuses
|
|
break;
|
|
}
|
|
|
|
case SE_BardAEDot: {
|
|
effect_value =
|
|
CalcSpellEffectValue(buff.spellid, i, buff.casterlevel, buff.instrument_mod, caster);
|
|
|
|
if ((!RuleB(Spells, PreNerfBardAEDoT) && IsMoving()) || invulnerable ||
|
|
/*effect_value > 0 ||*/ DivineAura())
|
|
break;
|
|
|
|
if (effect_value < 0) {
|
|
effect_value = -effect_value;
|
|
if (caster) {
|
|
if (caster->IsClient() && !caster->CastToClient()->GetFeigned()) {
|
|
AddToHateList(caster, effect_value);
|
|
} else if (!caster->IsClient())
|
|
AddToHateList(caster, effect_value);
|
|
}
|
|
Damage(caster, effect_value, buff.spellid, spell.skill, false, i, true);
|
|
} else if (effect_value > 0) {
|
|
// healing spell...
|
|
HealDamage(effect_value, caster);
|
|
// healing aggro would go here; removed for now
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Hate: {
|
|
effect_value = CalcSpellEffectValue(buff.spellid, i, buff.casterlevel, buff.instrument_mod);
|
|
if (caster) {
|
|
if (effect_value > 0) {
|
|
if (caster) {
|
|
if (caster->IsClient() && !caster->CastToClient()->GetFeigned()) {
|
|
AddToHateList(caster, effect_value);
|
|
} else if (!caster->IsClient())
|
|
AddToHateList(caster, effect_value);
|
|
}
|
|
} else {
|
|
int64 newhate = GetHateAmount(caster) + effect_value;
|
|
if (newhate < 1) {
|
|
SetHateAmountOnEnt(caster, 1);
|
|
} else {
|
|
SetHateAmountOnEnt(caster, newhate);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_WipeHateList: {
|
|
if (IsMezSpell(buff.spellid)) {
|
|
break;
|
|
}
|
|
|
|
int wipechance = 0;
|
|
|
|
if (caster) {
|
|
wipechance = caster->GetMemoryBlurChance(effect_value);
|
|
}
|
|
|
|
if (zone->random.Roll(wipechance)) {
|
|
if (IsAIControlled()) {
|
|
WipeHateList();
|
|
}
|
|
Message(Chat::Red, "Your mind fogs. Who are my friends? Who are my enemies?... it was all so "
|
|
"clear a moment ago...");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Charm: {
|
|
if (!caster || !PassCharismaCheck(caster, buff.spellid)) {
|
|
BuffFadeByEffect(SE_Charm);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_Root: {
|
|
/* Root formula derived from extensive personal live parses - Kayen
|
|
ROOT has a 70% chance to do a resist check to break.
|
|
*/
|
|
|
|
if (zone->random.Roll(RuleI(Spells, RootBreakCheckChance))) {
|
|
float resist_check =
|
|
ResistSpell(spells[buff.spellid].resist_type, buff.spellid, caster, 0, 0, 0, 0, true);
|
|
|
|
if (resist_check == 100)
|
|
break;
|
|
else if (!TryFadeEffect(slot))
|
|
BuffFadeBySlot(slot);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_Fear: {
|
|
if (zone->random.Roll(RuleI(Spells, FearBreakCheckChance))) {
|
|
float resist_check = ResistSpell(spells[buff.spellid].resist_type, buff.spellid, caster,0,0,true);
|
|
|
|
if (resist_check == 100)
|
|
break;
|
|
else if (!TryFadeEffect(slot))
|
|
BuffFadeBySlot(slot);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_Invisibility:
|
|
case SE_InvisVsAnimals:
|
|
case SE_InvisVsUndead: {
|
|
if (buff.ticsremaining > 3) {
|
|
if (!IsBardSong(buff.spellid)) {
|
|
double break_chance = 2.0;
|
|
if (caster) {
|
|
break_chance -= (2 * (((double)caster->GetSkill(EQ::skills::SkillDivination) +
|
|
((double)caster->GetLevel() * 3.0)) /
|
|
650.0));
|
|
} else {
|
|
break_chance -=
|
|
(2 *
|
|
(((double)GetSkill(EQ::skills::SkillDivination) + ((double)GetLevel() * 3.0)) /
|
|
650.0));
|
|
}
|
|
|
|
if (zone->random.Real(0.0, 100.0) < break_chance) {
|
|
BuffModifyDurationBySpellID(buff.spellid, 3);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case SE_ImprovedInvisAnimals:
|
|
case SE_Invisibility2:
|
|
case SE_InvisVsUndead2: {
|
|
if (!IsBardSong(buff.spellid)) {
|
|
if (buff.ticsremaining <= 3 && buff.ticsremaining > 1) {
|
|
MessageString(Chat::Spells, INVIS_BEGIN_BREAK);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SE_InterruptCasting: {
|
|
if (IsCasting()) {
|
|
const auto &spell = spells[casting_spell_id];
|
|
if (!IgnoreCastingRestriction(spell.id) && zone->random.Roll(spells[buff.spellid].base_value[i])) {
|
|
InterruptSpell();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
// These effects always trigger when they fade.
|
|
// Should we have this triggered from else where?
|
|
case SE_CastOnFadeEffect:
|
|
case SE_CastOnFadeEffectNPC:
|
|
case SE_CastOnFadeEffectAlways: {
|
|
if (buff.ticsremaining == 0) {
|
|
SpellFinished(spells[buff.spellid].base_value[i], this, EQ::spells::CastingSlot::Item, 0, -1, spells[spells[buff.spellid].base_value[i]].resist_difficulty);
|
|
}
|
|
break;
|
|
}
|
|
case SE_LocateCorpse: {
|
|
// This is handled by the client prior to SoD.
|
|
|
|
if (IsClient() && (CastToClient()->ClientVersionBit() & EQ::versions::maskSoDAndLater))
|
|
CastToClient()->LocateCorpse();
|
|
}
|
|
|
|
case SE_DistanceRemoval: {
|
|
if (spellbonuses.DistanceRemoval) {
|
|
|
|
int distance =
|
|
((int(GetX()) - buff.caston_x) * (int(GetX()) - buff.caston_x)) +
|
|
((int(GetY()) - buff.caston_y) * (int(GetY()) - buff.caston_y)) +
|
|
((int(GetZ()) - buff.caston_z) * (int(GetZ()) - buff.caston_z));
|
|
|
|
if (distance > (spells[buff.spellid].base_value[i] * spells[buff.spellid].base_value[i])) {
|
|
|
|
if (!TryFadeEffect(slot))
|
|
BuffFadeBySlot(slot, true);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
case SE_AddHateOverTimePct: {
|
|
if (IsNPC()) {
|
|
int64 new_hate = CastToNPC()->GetHateAmount(caster) * (100 + spell.base_value[i]) / 100;
|
|
if (new_hate <= 0)
|
|
new_hate = 1;
|
|
|
|
CastToNPC()->SetHateAmountOnEnt(caster, new_hate);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Duration_HP_Pct: {
|
|
effect_value = spells[buff.spellid].base_value[i];
|
|
int64 amt = std::abs(GetMaxHP() * effect_value / 100);
|
|
if (spells[buff.spellid].max_value[i] && amt > spells[buff.spellid].max_value[i])
|
|
amt = spells[buff.spellid].max_value[i];
|
|
|
|
if (effect_value < 0) {
|
|
Damage(this, amt, 0, EQ::skills::SkillEvocation, false);
|
|
}
|
|
else {
|
|
HealDamage(amt);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Duration_Mana_Pct: {
|
|
effect_value = spells[buff.spellid].base_value[i];
|
|
int32 amt = std::abs(GetMaxMana() * effect_value / 100);
|
|
if (spells[buff.spellid].max_value[i] && amt > spells[buff.spellid].max_value[i])
|
|
amt = spells[buff.spellid].max_value[i];
|
|
|
|
if (effect_value < 0) {
|
|
|
|
SetMana(GetMana() - amt);
|
|
}
|
|
else {
|
|
SetMana(GetMana() + amt);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Duration_Endurance_Pct: {
|
|
effect_value = spells[buff.spellid].base_value[i];
|
|
|
|
if (IsClient()) {
|
|
int32 amt = std::abs(CastToClient()->GetMaxEndurance() * effect_value / 100);
|
|
if (spells[buff.spellid].max_value[i] && amt > spells[buff.spellid].max_value[i])
|
|
amt = spells[buff.spellid].max_value[i];
|
|
|
|
if (effect_value < 0) {
|
|
CastToClient()->SetEndurance(CastToClient()->GetEndurance() - amt);
|
|
}
|
|
else {
|
|
CastToClient()->SetEndurance(CastToClient()->GetEndurance() + amt);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
// do we need to do anyting here?
|
|
}
|
|
}
|
|
if (!IsValidSpell(buff.spellid)) // if we faded we're no longer valid!
|
|
break;
|
|
}
|
|
|
|
/* Is this the best place for this?
|
|
* Ideally we would only recalc spell bonuses
|
|
* but we would also have to call all the Calc functions like Max HP
|
|
* so lets just call the main CalcBonuses
|
|
*/
|
|
if (degenerating_effects)
|
|
CalcBonuses();
|
|
}
|
|
|
|
// removes the buff in the buff slot 'slot'
|
|
void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses)
|
|
{
|
|
if(slot < 0 || slot > GetMaxTotalSlots())
|
|
return;
|
|
|
|
if(!IsValidSpell(buffs[slot].spellid))
|
|
return;
|
|
|
|
if (IsClient() && !CastToClient()->IsDead())
|
|
CastToClient()->MakeBuffFadePacket(buffs[slot].spellid, slot);
|
|
|
|
LogSpells("Fading buff [{}] from slot [{}]", buffs[slot].spellid, slot);
|
|
|
|
std::string export_string = fmt::format(
|
|
"{} {} {} {}",
|
|
buffs[slot].casterid,
|
|
buffs[slot].ticsremaining,
|
|
buffs[slot].casterlevel,
|
|
slot
|
|
);
|
|
|
|
if (IsClient()) {
|
|
if (parse->EventSpell(EVENT_SPELL_FADE, nullptr, CastToClient(), buffs[slot].spellid, export_string, 0) != 0) {
|
|
return;
|
|
}
|
|
} else if (IsNPC()) {
|
|
if (parse->EventSpell(EVENT_SPELL_FADE, this, nullptr, buffs[slot].spellid, export_string, 0) != 0) {
|
|
return;
|
|
}
|
|
} else if (IsBot()) {
|
|
if (parse->EventSpell(EVENT_SPELL_FADE, this, nullptr, buffs[slot].spellid, export_string, 0) != 0) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (int i=0; i < EFFECT_COUNT; i++)
|
|
{
|
|
if(IsBlankSpellEffect(buffs[slot].spellid, i))
|
|
continue;
|
|
|
|
switch (spells[buffs[slot].spellid].effect_id[i])
|
|
{
|
|
case SE_AddMeleeProc:
|
|
case SE_WeaponProc:
|
|
{
|
|
uint16 procid = GetProcID(buffs[slot].spellid, i);
|
|
RemoveProcFromWeapon(procid, false);
|
|
break;
|
|
}
|
|
|
|
case SE_DefensiveProc:
|
|
{
|
|
uint16 procid = GetProcID(buffs[slot].spellid, i);
|
|
RemoveDefensiveProc(procid);
|
|
break;
|
|
}
|
|
|
|
case SE_RangedProc:
|
|
{
|
|
uint16 procid = GetProcID(buffs[slot].spellid, i);
|
|
RemoveRangedProc(procid);
|
|
break;
|
|
}
|
|
|
|
case SE_SummonHorse:
|
|
{
|
|
if(IsClient())
|
|
{
|
|
/*Mob* horse = entity_list.GetMob(CastToClient()->GetHorseId());
|
|
if (horse) horse->Depop();
|
|
CastToClient()->SetHasMount(false);*/
|
|
CastToClient()->SetHorseId(0);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_IllusionCopy:
|
|
case SE_Illusion:
|
|
{
|
|
SendIllusionPacket(0, GetBaseGender());
|
|
if (GetRace() == OGRE) {
|
|
SendAppearancePacket(AT_Size, 9);
|
|
}
|
|
else if (GetRace() == TROLL) {
|
|
SendAppearancePacket(AT_Size, 8);
|
|
}
|
|
else if (GetRace() == VAHSHIR || GetRace() == FROGLOK || GetRace() == BARBARIAN) {
|
|
SendAppearancePacket(AT_Size, 7);
|
|
}
|
|
else if (GetRace() == HALF_ELF || GetRace() == WOOD_ELF || GetRace() == DARK_ELF) {
|
|
SendAppearancePacket(AT_Size, 5);
|
|
}
|
|
else if (GetRace() == DWARF) {
|
|
SendAppearancePacket(AT_Size, 4);
|
|
}
|
|
else if (GetRace() == HALFLING || GetRace() == GNOME) {
|
|
SendAppearancePacket(AT_Size, 3);
|
|
}
|
|
else {
|
|
SendAppearancePacket(AT_Size, 6);
|
|
}
|
|
|
|
for (int x = EQ::textures::textureBegin; x <= EQ::textures::LastTintableTexture; x++) {
|
|
SendWearChange(x);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_Levitate:
|
|
{
|
|
if (!AffectedBySpellExcludingSlot(slot, SE_Levitate))
|
|
SendAppearancePacket(AT_Levitate, 0);
|
|
break;
|
|
}
|
|
|
|
case SE_Silence:
|
|
{
|
|
Silence(false);
|
|
break;
|
|
}
|
|
|
|
case SE_Amnesia:
|
|
{
|
|
Amnesia(false);
|
|
break;
|
|
}
|
|
|
|
case SE_DivineAura:
|
|
{
|
|
SetInvul(false);
|
|
break;
|
|
}
|
|
|
|
case SE_Rune:
|
|
{
|
|
buffs[slot].melee_rune = 0;
|
|
break;
|
|
}
|
|
|
|
case SE_AbsorbMagicAtt:
|
|
{
|
|
buffs[slot].magic_rune = 0;
|
|
break;
|
|
}
|
|
|
|
case SE_Familiar:
|
|
{
|
|
Mob *mypet = GetPet();
|
|
if (mypet){
|
|
if(mypet->IsNPC())
|
|
mypet->CastToNPC()->Depop();
|
|
SetPetID(0);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Mez:
|
|
{
|
|
SendAppearancePacket(AT_Anim, ANIM_STAND); // unfreeze
|
|
mezzed = false;
|
|
break;
|
|
}
|
|
|
|
case SE_Charm:
|
|
{
|
|
if(IsNPC())
|
|
{
|
|
CastToNPC()->RestoreGuardSpotCharm();
|
|
CastToNPC()->ModifyStatsOnCharm(true);
|
|
}
|
|
|
|
SendAppearancePacket(AT_Pet, 0, true, true);
|
|
Mob* owner = GetOwner();
|
|
SetOwnerID(0);
|
|
SetPetType(petNone);
|
|
SetHeld(false);
|
|
SetGHeld(false);
|
|
SetNoCast(false);
|
|
SetFocused(false);
|
|
SetPetStop(false);
|
|
SetPetRegroup(false);
|
|
if(owner)
|
|
{
|
|
owner->SetPet(0);
|
|
}
|
|
if (IsAIControlled())
|
|
{
|
|
//Remove damage over time effects on charmed pet and those applied by charmed pet.
|
|
if (RuleB(Spells, PreventFactionWarOnCharmBreak)) {
|
|
for (auto mob : hate_list.GetHateList()) {
|
|
auto tar = mob->entity_on_hatelist;
|
|
if (tar) {
|
|
if (tar->IsCasting()) {
|
|
tar->InterruptSpell(tar->CastingSpellID());
|
|
}
|
|
uint32 buff_count = tar->GetMaxTotalSlots();
|
|
for (unsigned int j = 0; j < buff_count; j++) {
|
|
if (IsValidSpell(tar->GetBuffs()[j].spellid)) {
|
|
auto spell = spells[tar->GetBuffs()[j].spellid];
|
|
if (spell.good_effect == 0 && IsEffectInSpell(spell.id, SE_CurrentHP) && tar->GetBuffs()[j].casterid == GetID()) {
|
|
tar->BuffFadeBySpellID(spell.id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (IsCasting()) {
|
|
InterruptSpell(CastingSpellID());
|
|
}
|
|
uint32 buff_count = GetMaxTotalSlots();
|
|
for (unsigned int j = 0; j < buff_count; j++) {
|
|
if (IsValidSpell(GetBuffs()[j].spellid )) {
|
|
auto spell = spells[GetBuffs()[j].spellid];
|
|
if (spell.good_effect == 0 && IsEffectInSpell(spell.id, SE_CurrentHP)) {
|
|
BuffFadeBySpellID(spell.id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// clear the hate list of the mobs
|
|
entity_list.ReplaceWithTarget(this, owner);
|
|
WipeHateList();
|
|
if (owner) {
|
|
AddToHateList(owner, 1, 0);
|
|
}
|
|
//If owner dead, briefly setting Immmune Aggro while hatelists wipe for both pet and targets is needed to ensure no reaggroing.
|
|
else if (IsNPC()){
|
|
bool immune_aggro = GetSpecialAbility(IMMUNE_AGGRO); //check if already immune aggro
|
|
SetSpecialAbility(IMMUNE_AGGRO, 1);
|
|
WipeHateList();
|
|
if (IsCasting()) {
|
|
InterruptSpell(CastingSpellID());
|
|
}
|
|
entity_list.RemoveFromHateLists(this);
|
|
//If NPC targeting charmed pet are in process of casting on it after it is removed from hatelist, stop the cast to prevent reaggroing.
|
|
Mob *current_npc = nullptr;
|
|
for (auto &it : entity_list.GetNPCList()) {
|
|
current_npc = it.second;
|
|
|
|
if (current_npc && current_npc->IsCasting() && current_npc->GetTarget() == this) {
|
|
current_npc->InterruptSpell(current_npc->CastingSpellID());
|
|
}
|
|
}
|
|
|
|
if (!immune_aggro) {
|
|
SetSpecialAbility(IMMUNE_AGGRO, 0);
|
|
}
|
|
}
|
|
SendAppearancePacket(AT_Anim, ANIM_STAND);
|
|
}
|
|
if(owner && owner->IsClient())
|
|
{
|
|
auto app = new EQApplicationPacket(OP_Charm, sizeof(Charm_Struct));
|
|
Charm_Struct *ps = (Charm_Struct*)app->pBuffer;
|
|
ps->owner_id = owner->GetID();
|
|
ps->pet_id = GetID();
|
|
ps->command = 0;
|
|
entity_list.QueueClients(this, app);
|
|
safe_delete(app);
|
|
}
|
|
if(IsClient())
|
|
{
|
|
InterruptSpell();
|
|
if (CastToClient()->IsLD())
|
|
CastToClient()->AI_Start(CLIENT_LD_TIMEOUT);
|
|
else
|
|
{
|
|
bool feared = FindType(SE_Fear);
|
|
if(!feared)
|
|
CastToClient()->AI_Stop();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Root:
|
|
{
|
|
buffs[slot].RootBreakChance = 0;
|
|
rooted = false;
|
|
break;
|
|
}
|
|
|
|
case SE_Blind:
|
|
if (currently_fleeing && !FindType(SE_Fear))
|
|
currently_fleeing = false;
|
|
break;
|
|
|
|
case SE_Fear:
|
|
{
|
|
if(RuleB(Combat, EnableFearPathing)){
|
|
if(IsClient())
|
|
{
|
|
bool charmed = FindType(SE_Charm);
|
|
if(!charmed)
|
|
CastToClient()->AI_Stop();
|
|
}
|
|
|
|
if(currently_fleeing) {
|
|
currently_fleeing = false;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UnStun();
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_ImmuneFleeing: {
|
|
if (RuleB(Combat, EnableFearPathing)) {
|
|
if (flee_mode) {
|
|
currently_fleeing = true;
|
|
CheckFlee();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
case SE_BindSight:
|
|
{
|
|
if(IsClient())
|
|
{
|
|
CastToClient()->SetBindSightTarget(nullptr);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_SetBodyType:
|
|
{
|
|
SetBodyType(GetOrigBodyType(), false);
|
|
break;
|
|
}
|
|
|
|
case SE_AlterNPCLevel:
|
|
{
|
|
if (IsNPC())
|
|
SetLevel(GetOrigLevel());
|
|
break;
|
|
}
|
|
|
|
case SE_EyeOfZomm:
|
|
{
|
|
if (IsClient())
|
|
{
|
|
NPC* tmp_eye_of_zomm = entity_list.GetNPCByID(CastToClient()->GetControlledMobId());
|
|
//On live there is about a 6 second delay before it despawns once new one spawns.
|
|
if (tmp_eye_of_zomm) {
|
|
tmp_eye_of_zomm->GetSwarmInfo()->duration->Disable();
|
|
tmp_eye_of_zomm->GetSwarmInfo()->duration->Start(6000);
|
|
tmp_eye_of_zomm->DisableSwarmTimer();
|
|
tmp_eye_of_zomm->StartSwarmTimer(6000);
|
|
}
|
|
CastToClient()->SetControlledMobId(0);
|
|
}
|
|
}
|
|
|
|
case SE_Weapon_Stance:
|
|
{
|
|
/*
|
|
If we click off the spell buff (or fades naturally) giving us
|
|
Weapon Stance effects it should remove all associated buff.
|
|
*/
|
|
if (weaponstance.spellbonus_buff_spell_id) {
|
|
BuffFadeBySpellID(weaponstance.spellbonus_buff_spell_id);
|
|
}
|
|
weaponstance.spellbonus_enabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// notify caster (or their master) of buff that it's worn off
|
|
Mob *p = entity_list.GetMob(buffs[slot].casterid);
|
|
if (p && p != this && !IsBardSong(buffs[slot].spellid))
|
|
{
|
|
Mob *notify = p;
|
|
if(p->IsPet())
|
|
notify = p->GetOwner();
|
|
if(p) {
|
|
notify->MessageString(Chat::SpellWornOff, SPELL_WORN_OFF_OF,
|
|
spells[buffs[slot].spellid].name, GetCleanName());
|
|
}
|
|
}
|
|
|
|
if (HasNumhits()){
|
|
|
|
uint32 buff_max = GetMaxTotalSlots();
|
|
bool found_numhits = false;
|
|
|
|
for(uint32 d = 0; d < buff_max; d++) {
|
|
|
|
if(IsValidSpell(buffs[d].spellid) && (buffs[d].hit_number > 0)) {
|
|
Numhits(true);
|
|
found_numhits = true;
|
|
}
|
|
}
|
|
|
|
if (!found_numhits)
|
|
Numhits(false);
|
|
}
|
|
|
|
if (spells[buffs[slot].spellid].nimbus_effect > 0)
|
|
RemoveNimbusEffect(spells[buffs[slot].spellid].nimbus_effect);
|
|
|
|
buffs[slot].spellid = SPELL_UNKNOWN;
|
|
if(IsPet() && GetOwner() && GetOwner()->IsClient()) {
|
|
SendPetBuffsToClient();
|
|
}
|
|
if((IsClient() && !CastToClient()->GetPVP()) ||
|
|
(IsPet() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()) ||
|
|
(IsBot() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()) ||
|
|
(IsMerc() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()))
|
|
{
|
|
EQApplicationPacket *outapp = MakeBuffsPacket();
|
|
|
|
entity_list.QueueClientsByTarget(this, outapp, false, nullptr, true, false, EQ::versions::maskSoDAndLater);
|
|
if(IsClient() && GetTarget() == this) {
|
|
CastToClient()->QueuePacket(outapp);
|
|
}
|
|
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
if (IsNPC()) {
|
|
EQApplicationPacket *outapp = MakeBuffsPacket();
|
|
entity_list.QueueClientsByTarget(this, outapp, false, nullptr, true, false, EQ::versions::maskSoDAndLater, true);
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
if (IsClient() && CastToClient()->ClientVersionBit() & EQ::versions::maskUFAndLater)
|
|
{
|
|
EQApplicationPacket *outapp = MakeBuffsPacket(false);
|
|
CastToClient()->FastQueuePacket(&outapp);
|
|
}
|
|
|
|
// we will eventually call CalcBonuses() even if we skip it right here, so should correct itself if we still have them
|
|
degenerating_effects = false;
|
|
if (iRecalcBonuses)
|
|
CalcBonuses();
|
|
}
|
|
|
|
int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id)
|
|
{
|
|
const SPDat_Spell_Struct &spell = spells[spell_id];
|
|
|
|
bool not_focusable = spells[spell_id].not_focusable;
|
|
|
|
int64 value = 0;
|
|
int lvlModifier = 100;
|
|
int spell_level = 0;
|
|
int lvldiff = 0;
|
|
uint32 effect = 0;
|
|
int32 base_value = 0;
|
|
int32 limit_value = 0;
|
|
uint32 slot = 0;
|
|
|
|
int index_id = -1;
|
|
uint32 focus_reuse_time = 0;
|
|
|
|
bool is_from_item_click = false;
|
|
bool try_apply_to_item_click = false;
|
|
bool has_item_limit_check = false;
|
|
|
|
if (casting_spell_inventory_slot && casting_spell_inventory_slot != -1) {
|
|
is_from_item_click = true;
|
|
}
|
|
|
|
bool LimitFailure = false;
|
|
bool LimitInclude[MaxLimitInclude] = {false};
|
|
/* Certain limits require only one of several Include conditions to be true. Ie. Add damage to fire OR ice
|
|
spells.
|
|
0/1 SE_LimitResist
|
|
2/3 SE_LimitSpell
|
|
4/5 SE_LimitEffect
|
|
6/7 SE_LimitTarget
|
|
8/9 SE_LimitSpellGroup:
|
|
10/11 SE_LimitCastingSkill:
|
|
12/13 SE_LimitSpellClass:
|
|
14/15 SE_LimitSpellSubClass:
|
|
16/17 SE_FFItemCLass:
|
|
Remember: Update MaxLimitInclude in spdat.h if adding new limits that require Includes
|
|
*/
|
|
|
|
int FocusCount = 0;
|
|
|
|
for (const auto &e : rank.effects) {
|
|
effect = e.effect_id;
|
|
base_value = e.base_value;
|
|
limit_value = e.limit_value;
|
|
slot = e.slot;
|
|
|
|
/*
|
|
AA Foci's can contain multiple focus effects within the same AA.
|
|
To handle this we will not automatically return zero if a limit is found.
|
|
Instead if limit is found and multiple focus effects, we will reset the limit check
|
|
when the next valid focus effect is found.
|
|
*/
|
|
|
|
if (IsFocusEffect(0, 0, true, effect)) {
|
|
FocusCount++;
|
|
// If limit found on prior check next, else end loop.
|
|
if (FocusCount > 1) {
|
|
|
|
for (int i = 0; i < MaxLimitInclude; i += 2) {
|
|
if (LimitInclude[i] && !LimitInclude[i + 1]) {
|
|
LimitFailure = true;
|
|
}
|
|
}
|
|
|
|
if (LimitFailure) {
|
|
value = 0;
|
|
LimitFailure = false;
|
|
|
|
for (bool & l : LimitInclude) {
|
|
l = false; // Reset array
|
|
}
|
|
}
|
|
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (effect) {
|
|
case SE_Blank:
|
|
break;
|
|
|
|
// Handle Focus Limits
|
|
case SE_LimitResist:
|
|
if (base_value < 0) {
|
|
if (spell.resist_type == -base_value) { // Exclude
|
|
LimitFailure = true;
|
|
}
|
|
}
|
|
else {
|
|
LimitInclude[IncludeExistsSELimitResist] = true;
|
|
if (spell.resist_type == base_value) { // Include
|
|
LimitInclude[IncludeFoundSELimitResist] = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_LimitInstant:
|
|
if (base_value == 1 && spell.buff_duration) { // Fail if not instant
|
|
LimitFailure = true;
|
|
}
|
|
if (base_value == 0 && (spell.buff_duration == 0)) { // Fail if instant
|
|
LimitFailure = true;
|
|
}
|
|
|
|
break;
|
|
|
|
case SE_LimitMaxLevel:
|
|
spell_level = spell.classes[(GetClass() % 17) - 1];
|
|
lvldiff = spell_level - base_value;
|
|
// every level over cap reduces the effect by base2 percent unless from a clicky when
|
|
// ItemCastsUseFocus is true
|
|
if (lvldiff > 0 && (spell_level <= RuleI(Character, MaxLevel) || RuleB(Character, ItemCastsUseFocus) == false)) {
|
|
if (limit_value > 0) {
|
|
lvlModifier -= limit_value * lvldiff;
|
|
if (lvlModifier < 1) {
|
|
LimitFailure = true;
|
|
}
|
|
}
|
|
else {
|
|
LimitFailure = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_LimitMinLevel:
|
|
if ((spell.classes[(GetClass() % 17) - 1]) < base_value) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
|
|
case SE_LimitCastTimeMin:
|
|
if (static_cast<int32>(spell.cast_time) < base_value) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
|
|
case SE_LimitCastTimeMax:
|
|
if (static_cast<int32>(spell.cast_time) > base_value) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
|
|
case SE_LimitSpell:
|
|
if (base_value < 0) { // Exclude
|
|
if (spell_id == -base_value) {
|
|
LimitFailure = true;
|
|
}
|
|
}
|
|
else {
|
|
LimitInclude[IncludeExistsSELimitSpell] = true;
|
|
if (spell_id == base_value) { // Include
|
|
LimitInclude[IncludeFoundSELimitSpell] = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_LimitMinDur:
|
|
if (base_value > CalcBuffDuration_formula(GetLevel(), spell.buff_duration_formula, spell.buff_duration)) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
|
|
case SE_LimitEffect:
|
|
if (base_value < 0) {
|
|
if (IsEffectInSpell(spell_id, -base_value)) { // Exclude
|
|
LimitFailure = true;
|
|
}
|
|
}
|
|
else {
|
|
LimitInclude[IncludeExistsSELimitEffect] = true;
|
|
// they use 33 here for all classes ... unsure if the type check is really needed
|
|
if (base_value == SE_SummonPet && type == focusReagentCost) {
|
|
if (IsSummonPetSpell(spell_id) || IsSummonSkeletonSpell(spell_id)) {
|
|
LimitInclude[IncludeFoundSELimitEffect] = true;
|
|
}
|
|
}
|
|
else {
|
|
if (IsEffectInSpell(spell_id, base_value)) { // Include
|
|
LimitInclude[IncludeFoundSELimitEffect] = true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_LimitSpellType:
|
|
switch (base_value) {
|
|
case 0:
|
|
if (!IsDetrimentalSpell(spell_id)) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
case 1:
|
|
if (!IsBeneficialSpell(spell_id)) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SE_LimitManaMin:
|
|
if (spell.mana < base_value) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
|
|
case SE_LimitManaMax:
|
|
if (spell.mana > base_value) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
|
|
case SE_LimitTarget:
|
|
if (base_value < 0) {
|
|
if (-base_value == spell.target_type) { // Exclude
|
|
LimitFailure = true;
|
|
}
|
|
}
|
|
else {
|
|
LimitInclude[IncludeExistsSELimitTarget] = true;
|
|
if (base_value == spell.target_type) { // Include
|
|
LimitInclude[IncludeFoundSELimitTarget] = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_LimitCombatSkills:
|
|
if (base_value == 0 && (IsCombatSkill(spell_id) || IsCombatProc(spell_id))) { // Exclude Discs / Procs
|
|
LimitFailure = true;
|
|
}
|
|
else if (base_value == 1 && (!IsCombatSkill(spell_id) || !IsCombatProc(spell_id))) { // Exclude Spells
|
|
LimitFailure = true;
|
|
}
|
|
|
|
break;
|
|
|
|
case SE_LimitSpellGroup:
|
|
if (base_value < 0) {
|
|
if (-base_value == spell.spell_group) { // Exclude
|
|
LimitFailure = true;
|
|
}
|
|
}
|
|
else {
|
|
LimitInclude[IncludeExistsSELimitSpellGroup] = true;
|
|
if (base_value == spell.spell_group) { // Include
|
|
LimitInclude[IncludeFoundSELimitSpellGroup] = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_LimitCastingSkill:
|
|
if (base_value < 0) {
|
|
if (-base_value == spell.skill) {
|
|
LimitFailure = true;
|
|
}
|
|
}
|
|
else {
|
|
LimitInclude[IncludeExistsSELimitCastingSkill] = true;
|
|
if (base_value == spell.skill) {
|
|
LimitInclude[IncludeFoundSELimitCastingSkill] = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_LimitSpellClass:
|
|
if (base_value < 0) { // Exclude
|
|
if (-base_value == spell.spell_class) {
|
|
LimitFailure = true;
|
|
}
|
|
}
|
|
else {
|
|
LimitInclude[IncludeExistsSELimitSpellClass] = true;
|
|
if (base_value == spell.spell_class) { // Include
|
|
LimitInclude[IncludeFoundSELimitSpellClass] = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_LimitSpellSubclass:
|
|
if (base_value < 0) { // Exclude
|
|
if (-base_value == spell.spell_subclass) {
|
|
LimitFailure = true;
|
|
}
|
|
}
|
|
else {
|
|
LimitInclude[IncludeExistsSELimitSpellSubclass] = true;
|
|
if (base_value == spell.spell_subclass) { // Include
|
|
LimitInclude[IncludeFoundSELimitSpellSubclass] = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_LimitClass:
|
|
// Do not use this limit more then once per spell. If multiple class, treat value like items
|
|
// would.
|
|
if (!PassLimitClass(base_value, GetClass())) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
|
|
case SE_LimitRace:
|
|
if (base_value != GetRace()) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
|
|
case SE_LimitUseMin:
|
|
if (base_value > spell.hit_number) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
|
|
case SE_LimitUseType:
|
|
if (base_value != spell.hit_number_type) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_DurationMax:
|
|
if (base_value > spell.buff_duration) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_Endurance_Min:
|
|
if (spell.endurance_cost < base_value) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_Endurance_Max:
|
|
if (spell.endurance_cost > base_value) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_ReuseTimeMin:
|
|
if (spell.recast_time < base_value) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_ReuseTimeMax:
|
|
if (spell.recast_time > base_value) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_Value_Min:
|
|
index_id = GetSpellEffectIndex(spell_id, limit_value);
|
|
if (index_id >= 0 && spell.base_value[index_id] < base_value) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_Value_Max:
|
|
index_id = GetSpellEffectIndex(spell_id, limit_value);
|
|
if (index_id >= 0 && spell.base_value[index_id] > base_value) {
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_Override_NotFocusable:
|
|
if (base_value == 1) {
|
|
not_focusable = false;
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_FocusTimerMin:
|
|
if (IsFocusProcLimitTimerActive(-rank.id)) {
|
|
LimitFailure = true;
|
|
}
|
|
else {
|
|
focus_reuse_time = limit_value;
|
|
}
|
|
break;
|
|
|
|
case SE_FFItemClass:
|
|
has_item_limit_check = true;
|
|
if (casting_spell_inventory_slot && casting_spell_inventory_slot != -1) {
|
|
if (IsClient() && casting_spell_slot == EQ::spells::CastingSlot::Item && casting_spell_inventory_slot != 0xFFFFFFFF) {
|
|
auto item = CastToClient()->GetInv().GetItem(casting_spell_inventory_slot);
|
|
if (item && item->GetItem()) {
|
|
//If ItemType set to < -1, then we will exclude either all Subtypes (-1000), or specific items by ItemType, SubType or Slot. See above for rules.
|
|
if (base_value < -1) { //Excludes
|
|
bool exclude_this_item = true;
|
|
int tmp_itemtype = (item->GetItem()->ItemType + 100) * -1;
|
|
//ItemType (if set to -1000, ignore and exclude any ItemType)
|
|
if (base_value < -1 && base_value != -1000) {
|
|
if (base_value != tmp_itemtype) {
|
|
exclude_this_item = false;
|
|
}
|
|
}
|
|
//SubType (if set to -1, ignore and exclude all SubTypes)
|
|
if (limit_value >= 0) {
|
|
if (limit_value != item->GetItem()->SubType) {
|
|
exclude_this_item = false;
|
|
}
|
|
}
|
|
if (exclude_this_item) {
|
|
LimitFailure = true;
|
|
}
|
|
}
|
|
else {//Includes
|
|
LimitInclude[IncludeExistsSEFFItemClass] = true;
|
|
bool include_this_item = true;
|
|
//ItemType (if set to -1, ignore and include any ItemType)
|
|
if (base_value >= 0) {
|
|
if (base_value != item->GetItem()->ItemType) {
|
|
include_this_item = false;
|
|
}
|
|
}
|
|
//SubType (if set to -1, ignore and include any SubType)
|
|
if (limit_value >= 0) {
|
|
if (limit_value != item->GetItem()->SubType) {
|
|
include_this_item = false;
|
|
}
|
|
}
|
|
if (include_this_item) {
|
|
LimitInclude[IncludeFoundSEFFItemClass] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//If this is checking that focus can only be cast from an item, then if its not cast from item fail.
|
|
else if (base_value >= -1) {
|
|
LimitFailure = true;
|
|
}
|
|
//If we are checking to exclude items from a focus then do not fail unless the above check fails.
|
|
break;
|
|
|
|
/* These are not applicable to AA's because there is never a 'caster' of the 'buff' with the focus effect.
|
|
case SE_Ff_Same_Caster:
|
|
case SE_Ff_CasterClass:
|
|
*/
|
|
|
|
// Handle Focus Effects
|
|
case SE_ImprovedDamage:
|
|
if (type == focusImprovedDamage && base_value > value) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_ImprovedDamage2:
|
|
if (type == focusImprovedDamage2 && base_value > value) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_Fc_Amplify_Mod:
|
|
if (type == focusFcAmplifyMod && base_value > value) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_ImprovedHeal:
|
|
if (type == focusImprovedHeal && base_value > value) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_ReduceManaCost:
|
|
if (type == focusManaCost) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_IncreaseSpellHaste:
|
|
if (type == focusSpellHaste && base_value > value) {
|
|
value = base_value;
|
|
try_apply_to_item_click = is_from_item_click ? true : false;
|
|
}
|
|
break;
|
|
|
|
case SE_Fc_CastTimeMod2:
|
|
if (type == focusFcCastTimeMod2 && base_value > value) {
|
|
value = base_value;
|
|
try_apply_to_item_click = is_from_item_click ? true : false;
|
|
}
|
|
break;
|
|
|
|
case SE_Fc_CastTimeAmt:
|
|
if (type == focusFcCastTimeAmt && base_value > value) {
|
|
value = base_value;
|
|
try_apply_to_item_click = is_from_item_click ? true : false;
|
|
}
|
|
break;
|
|
|
|
case SE_IncreaseSpellDuration:
|
|
if (type == focusSpellDuration && base_value > value) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_SpellDurationIncByTic:
|
|
if (type == focusSpellDurByTic && base_value > value) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_SwarmPetDuration:
|
|
if (type == focusSwarmPetDuration && base_value > value) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_IncreaseRange:
|
|
if (type == focusRange && base_value > value) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_ReduceReagentCost:
|
|
if (type == focusReagentCost && base_value > value) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_PetPowerIncrease:
|
|
if (type == focusPetPower && base_value > value) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_SpellResistReduction:
|
|
if (type == focusResistRate && base_value > value) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_Fc_ResistIncoming:
|
|
if (type == focusFcResistIncoming && base_value > value) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_SpellHateMod:
|
|
if (type == focusSpellHateMod) {
|
|
if (value != 0) {
|
|
if (value > 0) {
|
|
if (base_value > value) {
|
|
value = base_value;
|
|
}
|
|
}
|
|
else {
|
|
if (base_value < value) {
|
|
value = base_value;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
value = base_value;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_ReduceReuseTimer:
|
|
if (type == focusReduceRecastTime) {
|
|
value = base_value / 1000;
|
|
try_apply_to_item_click = is_from_item_click ? true : false;
|
|
}
|
|
break;
|
|
|
|
case SE_TriggerOnCast:
|
|
if (type == focusTriggerOnCast) {
|
|
if (zone->random.Roll(base_value)) {
|
|
value = limit_value;
|
|
}
|
|
else {
|
|
value = 0;
|
|
LimitFailure = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_FcSpellVulnerability:
|
|
if (type == focusSpellVulnerability) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_Fc_Spell_Damage_Pct_IncomingPC:
|
|
if (type == focusFcSpellDamagePctIncomingPC) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_BlockNextSpellFocus:
|
|
if (type == focusBlockNextSpell) {
|
|
if (zone->random.Roll(base_value)) {
|
|
value = 1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_FcTwincast:
|
|
if (type == focusTwincast && !IsEffectInSpell(spell_id, SE_TwinCastBlocker)) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
// Note if using these as AA, make sure this is first focus used.
|
|
case SE_SympatheticProc:
|
|
if (type == focusSympatheticProc) {
|
|
value = limit_value;
|
|
}
|
|
break;
|
|
|
|
case SE_FcDamageAmt:
|
|
if (type == focusFcDamageAmt) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_FcDamageAmt2:
|
|
if (type == focusFcDamageAmt2) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_Fc_Amplify_Amt:
|
|
if (type == focusFcAmplifyAmt) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_FcDamageAmtCrit:
|
|
if (type == focusFcDamageAmtCrit) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_FcDamageAmtIncoming:
|
|
if (type == focusFcDamageAmtIncoming) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_Fc_Spell_Damage_Amt_IncomingPC:
|
|
if (type == focusFcSpellDamageAmtIncomingPC) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_FcHealAmtIncoming:
|
|
if (type == focusFcHealAmtIncoming) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_FcHealPctCritIncoming:
|
|
if (type == focusFcHealPctCritIncoming) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_FcHealAmtCrit:
|
|
if (type == focusFcHealAmtCrit) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_FcHealAmt:
|
|
if (type == focusFcHealAmt) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_FcHealPctIncoming:
|
|
if (type == focusFcHealPctIncoming) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_FcBaseEffects:
|
|
if (type == focusFcBaseEffects) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_FcDamagePctCrit:
|
|
if (type == focusFcDamagePctCrit) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_FcIncreaseNumHits:
|
|
if (type == focusIncreaseNumHits) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_FcLimitUse:
|
|
if (type == focusFcLimitUse) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_FcMute:
|
|
if (type == focusFcMute) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_FcStunTimeMod:
|
|
if (type == focusFcStunTimeMod) {
|
|
value = base_value;
|
|
}
|
|
break;
|
|
|
|
case SE_Fc_Cast_Spell_On_Land:
|
|
if (type == focusFcCastSpellOnLand) {
|
|
if (zone->random.Roll(base_value)) {
|
|
value = limit_value;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int e = 0; e < MaxLimitInclude; e += 2) {
|
|
if (LimitInclude[e] && !LimitInclude[e + 1]) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (try_apply_to_item_click && !has_item_limit_check) {
|
|
return 0;
|
|
}
|
|
|
|
if (LimitFailure) {
|
|
return 0;
|
|
}
|
|
|
|
if (not_focusable) {
|
|
return 0;
|
|
}
|
|
|
|
if (focus_reuse_time) {
|
|
SetFocusProcLimitTimer(-rank.id, focus_reuse_time);
|
|
}
|
|
|
|
return (value * lvlModifier / 100);
|
|
}
|
|
|
|
//given an item/spell's focus ID and the spell being cast, determine the focus ammount, if any
|
|
//assumes that spell_id is not a bard spell and that both ids are valid spell ids
|
|
int64 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, bool best_focus, uint16 casterid, Mob *caster)
|
|
{
|
|
/*
|
|
'this' is always the caster of the spell_id, most foci check for effects on the caster, however some check for effects on the target.
|
|
'casterid' is the casterid of the caster of spell_id, used when spell_id is cast on a target with a focus effect that is checked by incoming spell.
|
|
*/
|
|
if (!IsValidSpell(focus_id) || !IsValidSpell(spell_id)) {
|
|
return 0;
|
|
}
|
|
|
|
// No further checks if spell_id no_focusable, unless spell focus_id contains an override limiter.
|
|
if (spells[spell_id].not_focusable && !IsEffectInSpell(focus_id, SE_Ff_Override_NotFocusable)) {
|
|
return 0;
|
|
}
|
|
|
|
const SPDat_Spell_Struct &focus_spell = spells[focus_id];
|
|
const SPDat_Spell_Struct &spell = spells[spell_id];
|
|
|
|
int64 value = 0;
|
|
int lvlModifier = 100;
|
|
int spell_level = 0;
|
|
int lvldiff = 0;
|
|
uint32 Caston_spell_id = 0;
|
|
int index_id = -1;
|
|
uint32 focus_reuse_time = 0; //If this is set and all limits pass, start timer at end of script.
|
|
|
|
bool is_from_item_click = false;
|
|
bool try_apply_to_item_click = false;
|
|
bool has_item_limit_check = false;
|
|
|
|
if (casting_spell_inventory_slot && casting_spell_inventory_slot != -1) {
|
|
is_from_item_click = true;
|
|
}
|
|
|
|
|
|
bool LimitInclude[MaxLimitInclude] = {false};
|
|
/* Certain limits require only one of several Include conditions to be true. Determined by limits being negative or positive
|
|
Ie. Add damage to fire OR ice spells. If positive we 'Include', by checking each limit of same type to look for match until found. Opposed to
|
|
just 'Excluding', where if set to negative, if we find that match then focus fails, ie Add damage to all spells BUT Fire.
|
|
0/1 SE_LimitResist
|
|
2/3 SE_LimitSpell
|
|
4/5 SE_LimitEffect
|
|
6/7 SE_LimitTarget
|
|
8/9 SE_LimitSpellGroup:
|
|
10/11 SE_LimitCastingSkill:
|
|
12/13 SE_LimitSpellClass:
|
|
14/15 SE_LimitSpellSubClass:
|
|
16/17 SE_FFItemCLass:
|
|
Remember: Update MaxLimitInclude in spdat.h if adding new limits that require Includes
|
|
*/
|
|
|
|
for (int i = 0; i < EFFECT_COUNT; i++) {
|
|
|
|
switch (focus_spell.effect_id[i]) {
|
|
|
|
case SE_Blank:
|
|
break;
|
|
|
|
case SE_LimitResist:
|
|
if (focus_spell.base_value[i] < 0) {
|
|
if (spell.resist_type == -focus_spell.base_value[i]) { // Exclude
|
|
return 0;
|
|
}
|
|
}
|
|
else {
|
|
LimitInclude[IncludeExistsSELimitResist] = true;
|
|
if (spell.resist_type == focus_spell.base_value[i]) { // Include
|
|
LimitInclude[IncludeFoundSELimitResist] = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_LimitInstant:
|
|
if (focus_spell.base_value[i] == 1 && spell.buff_duration) { // Fail if not instant
|
|
return 0;
|
|
}
|
|
if (focus_spell.base_value[i] == 0 && (spell.buff_duration == 0)) { // Fail if instant
|
|
return 0;
|
|
}
|
|
|
|
break;
|
|
|
|
case SE_LimitMaxLevel:
|
|
if (IsNPC()) {
|
|
break;
|
|
}
|
|
spell_level = spell.classes[(GetClass() % 17) - 1];
|
|
lvldiff = spell_level - focus_spell.base_value[i];
|
|
// every level over cap reduces the effect by focus_spell.base2[i] percent unless from a clicky
|
|
// when ItemCastsUseFocus is true
|
|
if (lvldiff > 0 && (spell_level <= RuleI(Character, MaxLevel) ||
|
|
RuleB(Character, ItemCastsUseFocus) == false)) {
|
|
if (focus_spell.limit_value[i] > 0) {
|
|
lvlModifier -= focus_spell.limit_value[i] * lvldiff;
|
|
if (lvlModifier < 1) {
|
|
return 0;
|
|
}
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_LimitMinLevel:
|
|
if (IsNPC()) {
|
|
break;
|
|
}
|
|
if (spell.classes[(GetClass() % 17) - 1] < focus_spell.base_value[i]) {
|
|
return (0);
|
|
}
|
|
break;
|
|
|
|
case SE_LimitCastTimeMin:
|
|
if (spells[spell_id].cast_time < (uint16) focus_spell.base_value[i]) {
|
|
return (0);
|
|
}
|
|
break;
|
|
|
|
case SE_LimitCastTimeMax:
|
|
if (spells[spell_id].cast_time > (uint16) focus_spell.base_value[i]) {
|
|
return (0);
|
|
}
|
|
break;
|
|
|
|
case SE_LimitSpell:
|
|
if (focus_spell.base_value[i] < 0) { // Exclude
|
|
if (spell_id == -focus_spell.base_value[i]) {
|
|
return (0);
|
|
}
|
|
}
|
|
else {
|
|
LimitInclude[IncludeExistsSELimitSpell] = true;
|
|
if (spell_id == focus_spell.base_value[i]) { // Include
|
|
LimitInclude[IncludeFoundSELimitSpell] = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_LimitMinDur:
|
|
if (focus_spell.base_value[i] >
|
|
CalcBuffDuration_formula(GetLevel(), spell.buff_duration_formula, spell.buff_duration)) {
|
|
return (0);
|
|
}
|
|
break;
|
|
|
|
case SE_LimitEffect:
|
|
if (focus_spell.base_value[i] < 0) {
|
|
if (IsEffectInSpell(spell_id, -focus_spell.base_value[i])) { // Exclude
|
|
return 0;
|
|
}
|
|
}
|
|
else {
|
|
LimitInclude[IncludeExistsSELimitEffect] = true;
|
|
if (IsEffectInSpell(spell_id, focus_spell.base_value[i])) { // Include
|
|
LimitInclude[IncludeFoundSELimitEffect] = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_LimitSpellType:
|
|
switch (focus_spell.base_value[i]) {
|
|
case 0:
|
|
if (!IsDetrimentalSpell(spell_id)) {
|
|
return 0;
|
|
}
|
|
break;
|
|
case 1:
|
|
if (!IsBeneficialSpell(spell_id)) {
|
|
return 0;
|
|
}
|
|
break;
|
|
default:
|
|
LogInfo("unknown limit spelltype [{}]", focus_spell.base_value[i]);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SE_LimitManaMin:
|
|
if (spell.mana < focus_spell.base_value[i]) {
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case SE_LimitManaMax:
|
|
if (spell.mana > focus_spell.base_value[i]) {
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case SE_LimitTarget:
|
|
if (focus_spell.base_value[i] < 0) {
|
|
if (-focus_spell.base_value[i] == spell.target_type) { // Exclude
|
|
return 0;
|
|
}
|
|
}
|
|
else {
|
|
LimitInclude[IncludeExistsSELimitTarget] = true;
|
|
if (focus_spell.base_value[i] == spell.target_type) { // Include
|
|
LimitInclude[IncludeFoundSELimitTarget] = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_LimitCombatSkills:
|
|
if (focus_spell.base_value[i] == 0 &&
|
|
(IsCombatSkill(spell_id) || IsCombatProc(spell_id))) { // Exclude Discs / Procs
|
|
return 0;
|
|
}
|
|
else if (focus_spell.base_value[i] == 1 && (!IsCombatSkill(spell_id) || !IsCombatProc(spell_id))) { // Exclude Spells
|
|
return 0;
|
|
}
|
|
|
|
break;
|
|
|
|
case SE_LimitSpellGroup:
|
|
if (focus_spell.base_value[i] < 0) {
|
|
if (-focus_spell.base_value[i] == spell.spell_group) { // Exclude
|
|
return 0;
|
|
}
|
|
}
|
|
else {
|
|
LimitInclude[IncludeExistsSELimitSpellGroup] = true;
|
|
if (focus_spell.base_value[i] == spell.spell_group) { // Include
|
|
LimitInclude[IncludeFoundSELimitSpellGroup] = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_LimitCastingSkill:
|
|
if (focus_spell.base_value[i] < 0) {
|
|
if (-focus_spell.base_value[i] == spell.skill) {
|
|
return 0;
|
|
}
|
|
}
|
|
else {
|
|
LimitInclude[IncludeExistsSELimitCastingSkill] = true;
|
|
if (focus_spell.base_value[i] == spell.skill) {
|
|
LimitInclude[IncludeFoundSELimitCastingSkill] = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_LimitClass:
|
|
// Do not use this limit more then once per spell. If multiple class, treat value like items
|
|
// would.
|
|
if (!PassLimitClass(focus_spell.base_value[i], GetClass())) {
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case SE_LimitRace:
|
|
if (focus_spell.base_value[i] != GetRace()) {
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case SE_LimitUseMin:
|
|
if (focus_spell.base_value[i] > spell.hit_number) {
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case SE_LimitUseType:
|
|
if (focus_spell.base_value[i] != spell.hit_number_type) {
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case SE_CastonFocusEffect:
|
|
if (focus_spell.base_value[i] > 0) {
|
|
Caston_spell_id = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_LimitSpellClass:
|
|
if (focus_spell.base_value[i] < 0) { // Exclude
|
|
if (-focus_spell.base_value[i] == spell.spell_class) {
|
|
return 0;
|
|
}
|
|
}
|
|
else {
|
|
LimitInclude[IncludeExistsSELimitSpellClass] = true;
|
|
if (focus_spell.base_value[i] == spell.spell_class) { // Include
|
|
LimitInclude[IncludeFoundSELimitSpellClass] = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_LimitSpellSubclass:
|
|
if (focus_spell.base_value[i] < 0) { // Exclude
|
|
if (-focus_spell.base_value[i] == spell.spell_subclass) {
|
|
return 0;
|
|
}
|
|
}
|
|
else {
|
|
LimitInclude[IncludeExistsSELimitSpellSubclass] = true;
|
|
if (focus_spell.base_value[i] == spell.spell_subclass) { // Include
|
|
LimitInclude[IncludeFoundSELimitSpellSubclass] = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_Same_Caster://hmm do i need to pass casterid from buff slot here
|
|
if (focus_spell.base_value[i] == 0) {
|
|
if (caster && casterid == caster->GetID()) {
|
|
return 0;
|
|
}//Mob casting is same as target, fail if you are casting on yourself.
|
|
}
|
|
else if (focus_spell.base_value[i] == 1) {
|
|
if (caster && casterid != caster->GetID()) {
|
|
return 0;
|
|
}//Mob casting is not same as target, fail if you are not casting on yourself.
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_CasterClass: {
|
|
|
|
// Do not use this limit more then once per spell. If multiple class, treat value like items would.
|
|
if (caster && !PassLimitClass(focus_spell.base_value[i], caster->GetClass())) {
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Ff_DurationMax:
|
|
if (focus_spell.base_value[i] > spell.buff_duration) {
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_Endurance_Min:
|
|
if (spell.endurance_cost < focus_spell.base_value[i]) {
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_Endurance_Max:
|
|
if (spell.endurance_cost > focus_spell.base_value[i]) {
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_ReuseTimeMin:
|
|
if (spell.recast_time < focus_spell.base_value[i]) {
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_ReuseTimeMax:
|
|
if (spell.recast_time > focus_spell.base_value[i]) {
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_Value_Min:
|
|
index_id = GetSpellEffectIndex(spell_id, focus_spell.limit_value[i]);
|
|
if (index_id >= 0 && spell.base_value[index_id] < focus_spell.base_value[i]) {
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_Value_Max:
|
|
index_id = GetSpellEffectIndex(spell_id, focus_spell.limit_value[i]);
|
|
if (index_id >= 0 && spell.base_value[index_id] > focus_spell.base_value[i]) {
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case SE_Ff_FocusTimerMin:
|
|
if (IsFocusProcLimitTimerActive(focus_spell.id)) {
|
|
return 0;
|
|
}
|
|
else {
|
|
focus_reuse_time = focus_spell.limit_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_FFItemClass:
|
|
|
|
/*
|
|
Limits focuses to check if cast from item clicks. Can be used to INCLUDE or EXCLUDE items by ItemType and/or SubType and/or Slots
|
|
Not used on live, going on information we have plus implemented as broadly as possible to allow all possible options.
|
|
base = item table field 'ItemType' Limit = item table field 'SubType' Max = item table field 'Slots' (this is slot bitmask)
|
|
|
|
When including: Setting base, limit, max respectively to -1 will cause it to ignore that check, letting any type or slot ect be used.
|
|
|
|
Special rules for excluding. base value needs to be negative < -1, if excluding all ItemTypes set to -1000.
|
|
For SubType and Slots set using same rules above as for includes. Ie. -1 for all, positive for specifics
|
|
To exclude a specific ItemType we have to do some math. The exclude value will be the negative value of (ItemType + 100).
|
|
If ItemType = 10, then SET ItemType= -110 to exclude. If its ItemType 0, then SET ItemType= -100 to exclude ect. Not ideal but it works.
|
|
|
|
Usage example: [INCLUDE] Only focus spell if from click cast and is a 'defense armor' item type=10 [base= 10, limit= -1, max= -1]
|
|
Usage example: [INCLUDE] Only focus spell if from click cast and is from helmet slot' slots= 4 [base= -1, limit= -1, max= 4]
|
|
Usage example: [EXCLUDE] Do not focus spell if it is from an item click. [base= -1000, limit= -1, max= -1]
|
|
Usage example: [EXCLUDE] Do not focus spell if it is from an item click from a helmet slot. [base= -1000, limit= -1, max= 4]
|
|
Usage example: [EXCLUDE] Do not focus spell if it is from an item click and is a 'defense armor' item type=10. [base= -110, limit= -1, max= -1]
|
|
|
|
Note: You can apply multiple includes or excludes to a single focus spell, using multiple SPA 415 limits in the spell. Ie. Check for clicks from ItemType 10 or 11.
|
|
|
|
*/
|
|
has_item_limit_check = true;
|
|
if (casting_spell_inventory_slot && casting_spell_inventory_slot != -1) {
|
|
if (IsClient() && casting_spell_slot == EQ::spells::CastingSlot::Item && casting_spell_inventory_slot != 0xFFFFFFFF) {
|
|
auto item = CastToClient()->GetInv().GetItem(casting_spell_inventory_slot);
|
|
if (item && item->GetItem()) {
|
|
//If ItemType set to < -1, then we will exclude either all Subtypes (-1000), or specific items by ItemType, SubType or Slot. See above for rules.
|
|
if (focus_spell.base_value[i] < -1) { //Excludes
|
|
bool exclude_this_item = true;
|
|
int tmp_itemtype = (item->GetItem()->ItemType + 100) * -1;
|
|
//ItemType (if set to -1000, ignore and exclude any ItemType)
|
|
if (focus_spell.base_value[i] < -1 && focus_spell.base_value[i] != -1000) {
|
|
if (focus_spell.base_value[i] != tmp_itemtype) {
|
|
exclude_this_item = false;
|
|
}
|
|
}
|
|
//SubType (if set to -1, ignore and exclude all SubTypes)
|
|
if (focus_spell.limit_value[i] >= 0) {
|
|
if (focus_spell.limit_value[i] != item->GetItem()->SubType) {
|
|
exclude_this_item = false;
|
|
}
|
|
}
|
|
//item slot bitmask (if set to -1, ignore and exclude all SubTypes)
|
|
if (focus_spell.max_value[i] >= 0) {
|
|
if (focus_spell.max_value[i] != item->GetItem()->Slots) {
|
|
exclude_this_item = false;
|
|
}
|
|
}
|
|
if (exclude_this_item) {
|
|
return 0;
|
|
}
|
|
}
|
|
else {//Includes
|
|
LimitInclude[IncludeExistsSEFFItemClass] = true;
|
|
bool include_this_item = true;
|
|
//ItemType (if set to -1, ignore and include any ItemType)
|
|
if (focus_spell.base_value[i] >= 0) {
|
|
if (focus_spell.base_value[i] != item->GetItem()->ItemType) {
|
|
include_this_item = false;
|
|
}
|
|
}
|
|
//SubType (if set to -1, ignore and include any SubType)
|
|
if (focus_spell.limit_value[i] >= 0) {
|
|
if (focus_spell.limit_value[i] != item->GetItem()->SubType) {
|
|
include_this_item = false;
|
|
}
|
|
}
|
|
//item slot bitmask (if set to -1, ignore and include any slot)
|
|
if (focus_spell.max_value[i] >= 0) {
|
|
if (focus_spell.max_value[i] != item->GetItem()->Slots) {
|
|
include_this_item = false;
|
|
}
|
|
}
|
|
|
|
if (include_this_item) {
|
|
LimitInclude[IncludeFoundSEFFItemClass] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//If this is checking that focus can only be cast from an item, then if its not cast from item fail.
|
|
else if (focus_spell.base_value[i] >= -1) {
|
|
return 0;
|
|
}
|
|
//If we are checking to exclude items from a focus then do not fail unless the above check fails.
|
|
break;
|
|
|
|
// handle effects
|
|
case SE_ImprovedDamage:
|
|
if (type == focusImprovedDamage) {
|
|
value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus);
|
|
}
|
|
break;
|
|
|
|
case SE_ImprovedDamage2:
|
|
if (type == focusImprovedDamage2) {
|
|
value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus);
|
|
}
|
|
break;
|
|
|
|
case SE_Fc_Amplify_Mod:
|
|
if (type == focusFcAmplifyMod && focus_spell.base_value[i] > value) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_ImprovedHeal:
|
|
if (type == focusImprovedHeal) {
|
|
value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus);
|
|
}
|
|
break;
|
|
|
|
case SE_ReduceManaCost:
|
|
if (type == focusManaCost) {
|
|
value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus);
|
|
}
|
|
break;
|
|
|
|
case SE_IncreaseSpellHaste:
|
|
if (type == focusSpellHaste && focus_spell.base_value[i] > value) {
|
|
value = focus_spell.base_value[i];
|
|
try_apply_to_item_click = is_from_item_click ? true : false;
|
|
}
|
|
break;
|
|
|
|
case SE_Fc_CastTimeMod2:
|
|
if (type == focusFcCastTimeMod2 && focus_spell.base_value[i] > value) {
|
|
value = focus_spell.base_value[i];
|
|
try_apply_to_item_click = is_from_item_click ? true : false;
|
|
}
|
|
break;
|
|
|
|
case SE_Fc_CastTimeAmt:
|
|
if (type == focusFcCastTimeAmt && focus_spell.base_value[i] > value) {
|
|
value = focus_spell.base_value[i];
|
|
try_apply_to_item_click = is_from_item_click ? true : false;
|
|
}
|
|
break;
|
|
|
|
case SE_IncreaseSpellDuration:
|
|
if (type == focusSpellDuration && focus_spell.base_value[i] > value) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_SpellDurationIncByTic:
|
|
if (type == focusSpellDurByTic && focus_spell.base_value[i] > value) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_SwarmPetDuration:
|
|
if (type == focusSwarmPetDuration && focus_spell.base_value[i] > value) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_IncreaseRange:
|
|
if (type == focusRange && focus_spell.base_value[i] > value) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_ReduceReagentCost:
|
|
if (type == focusReagentCost) {
|
|
value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus);
|
|
}
|
|
break;
|
|
|
|
case SE_PetPowerIncrease:
|
|
if (type == focusPetPower && focus_spell.base_value[i] > value) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_SpellResistReduction:
|
|
if (type == focusResistRate) {
|
|
value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus);
|
|
}
|
|
break;
|
|
|
|
case SE_Fc_ResistIncoming:
|
|
if (type == focusFcResistIncoming && focus_spell.base_value[i] > value) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_SpellHateMod:
|
|
if (type == focusSpellHateMod) {
|
|
value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus);
|
|
}
|
|
break;
|
|
|
|
case SE_ReduceReuseTimer:
|
|
if (type == focusReduceRecastTime) {
|
|
value = focus_spell.base_value[i] / 1000;
|
|
try_apply_to_item_click = is_from_item_click ? true : false;
|
|
}
|
|
break;
|
|
|
|
case SE_TriggerOnCast:
|
|
if (type == focusTriggerOnCast) {
|
|
if (zone->random.Roll(focus_spell.base_value[i])) {
|
|
value = focus_spell.limit_value[i];
|
|
}
|
|
else {
|
|
value = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_BlockNextSpellFocus:
|
|
if (type == focusBlockNextSpell) {
|
|
if (zone->random.Roll(focus_spell.base_value[i])) {
|
|
value = 1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_SympatheticProc:
|
|
if (type == focusSympatheticProc) {
|
|
value = focus_id;
|
|
}
|
|
break;
|
|
|
|
case SE_FcSpellVulnerability:
|
|
if (type == focusSpellVulnerability) {
|
|
value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus);
|
|
}
|
|
break;
|
|
|
|
case SE_Fc_Spell_Damage_Pct_IncomingPC:
|
|
if (type == focusFcSpellDamagePctIncomingPC) {
|
|
value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus);
|
|
}
|
|
break;
|
|
|
|
case SE_FcTwincast:
|
|
if (type == focusTwincast && !IsEffectInSpell(spell_id, SE_TwinCastBlocker)) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_FcDamageAmt:
|
|
if (type == focusFcDamageAmt) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_FcDamageAmt2:
|
|
if (type == focusFcDamageAmt2) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_Fc_Amplify_Amt:
|
|
if (type == focusFcAmplifyAmt) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_FcDamageAmtCrit:
|
|
if (type == focusFcDamageAmtCrit) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_FcDamageAmtIncoming:
|
|
if (type == focusFcDamageAmtIncoming) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_Fc_Spell_Damage_Amt_IncomingPC:
|
|
if (type == focusFcSpellDamageAmtIncomingPC) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_FcHealAmtIncoming:
|
|
if (type == focusFcHealAmtIncoming) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_FcDamagePctCrit:
|
|
if (type == focusFcDamagePctCrit) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_FcHealPctCritIncoming:
|
|
if (type == focusFcHealPctCritIncoming) {
|
|
value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus);
|
|
}
|
|
break;
|
|
|
|
case SE_FcHealAmtCrit:
|
|
if (type == focusFcHealAmtCrit) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_FcHealAmt:
|
|
if (type == focusFcHealAmt) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_FcHealPctIncoming:
|
|
if (type == focusFcHealPctIncoming) {
|
|
value = GetFocusRandomEffectivenessValue(focus_spell.base_value[i], focus_spell.limit_value[i], best_focus);
|
|
}
|
|
break;
|
|
|
|
case SE_FcBaseEffects:
|
|
if (type == focusFcBaseEffects) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_FcIncreaseNumHits:
|
|
if (type == focusIncreaseNumHits) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_FcLimitUse:
|
|
if (type == focusFcLimitUse) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_FcMute:
|
|
if (type == focusFcMute) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_FcStunTimeMod:
|
|
if (type == focusFcStunTimeMod) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_FcTimerRefresh:
|
|
if (type == focusFcTimerRefresh) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_FcTimerLockout:
|
|
if (type == focusFcTimerLockout) {
|
|
value = focus_spell.base_value[i];
|
|
}
|
|
break;
|
|
|
|
case SE_Fc_Cast_Spell_On_Land:
|
|
if (type == focusFcCastSpellOnLand) {
|
|
if (zone->random.Roll(focus_spell.base_value[i])) {
|
|
value = focus_spell.limit_value[i];
|
|
}
|
|
break;
|
|
}
|
|
|
|
#if EQDEBUG >= 6
|
|
// this spits up a lot of garbage when calculating spell focuses
|
|
// since they have all kinds of extra effects on them.
|
|
default:
|
|
LogInfo("unknown effectid [{}]",
|
|
focus_spell.effect_id[i]);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
for (int e = 0; e < MaxLimitInclude; e += 2) {
|
|
if (LimitInclude[e] && !LimitInclude[e + 1]) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
For item click cast/recast focus modifiers. Only use if SPA 415 exists.
|
|
This is an item click but does not have SPA 415 limiter. Fail here.
|
|
*/
|
|
|
|
if (try_apply_to_item_click && !has_item_limit_check) {
|
|
return 0;
|
|
}
|
|
|
|
if (Caston_spell_id) {
|
|
if (IsValidSpell(Caston_spell_id) && (Caston_spell_id != spell_id)) {
|
|
SpellFinished(
|
|
Caston_spell_id,
|
|
this,
|
|
EQ::spells::CastingSlot::Item,
|
|
0,
|
|
-1,
|
|
spells[Caston_spell_id].resist_difficulty
|
|
);
|
|
}
|
|
}
|
|
|
|
if (focus_reuse_time) {
|
|
SetFocusProcLimitTimer(focus_spell.id, focus_reuse_time);
|
|
}
|
|
|
|
return (value * lvlModifier / 100);
|
|
}
|
|
|
|
void Mob::TryTriggerOnCastFocusEffect(focusType type, uint16 spell_id)
|
|
{
|
|
if (IsBardSong(spell_id)) {
|
|
return;
|
|
}
|
|
|
|
if (!IsValidSpell(spell_id)) {
|
|
return;
|
|
}
|
|
|
|
int32 focus_spell_id = 0;
|
|
int32 proc_spellid = 0;
|
|
|
|
// item focus
|
|
if (IsClient() && itembonuses.FocusEffects[type]) {
|
|
const EQ::ItemData *temp_item = nullptr;
|
|
|
|
for (int x = EQ::invslot::EQUIPMENT_BEGIN; x <= EQ::invslot::EQUIPMENT_END; x++) {
|
|
temp_item = nullptr;
|
|
EQ::ItemInstance *ins = CastToClient()->GetInv().GetItem(x);
|
|
if (!ins) {
|
|
continue;
|
|
}
|
|
temp_item = ins->GetItem();
|
|
if (temp_item && temp_item->Focus.Effect > 0 && IsValidSpell(temp_item->Focus.Effect)) {
|
|
focus_spell_id = temp_item->Focus.Effect;
|
|
if (!IsEffectInSpell(focus_spell_id, SE_TriggerOnCast)) {
|
|
continue;
|
|
}
|
|
|
|
proc_spellid = CalcFocusEffect(type, focus_spell_id, spell_id);
|
|
if (proc_spellid) {
|
|
TryTriggerOnCastProc(focus_spell_id, spell_id, proc_spellid);
|
|
}
|
|
}
|
|
|
|
for (int y = EQ::invaug::SOCKET_BEGIN; y <= EQ::invaug::SOCKET_END; ++y) {
|
|
EQ::ItemInstance *aug = ins->GetAugment(y);
|
|
if (aug) {
|
|
const EQ::ItemData *temp_item_aug = aug->GetItem();
|
|
if (temp_item_aug && temp_item_aug->Focus.Effect > 0 && IsValidSpell(temp_item_aug->Focus.Effect)) {
|
|
focus_spell_id = temp_item_aug->Focus.Effect;
|
|
|
|
if (!IsEffectInSpell(focus_spell_id, SE_TriggerOnCast)) {
|
|
continue;
|
|
}
|
|
|
|
proc_spellid = CalcFocusEffect(type, focus_spell_id, spell_id);
|
|
if (proc_spellid) {
|
|
TryTriggerOnCastProc(focus_spell_id, spell_id, proc_spellid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Spell Focus
|
|
if (spellbonuses.FocusEffects[type]) {
|
|
int buff_slot = 0;
|
|
for (buff_slot = 0; buff_slot < GetMaxTotalSlots(); buff_slot++) {
|
|
focus_spell_id = buffs[buff_slot].spellid;
|
|
if (!IsValidSpell(focus_spell_id)) {
|
|
continue;
|
|
}
|
|
|
|
if (!IsEffectInSpell(focus_spell_id, SE_TriggerOnCast)) {
|
|
continue;
|
|
}
|
|
|
|
proc_spellid = CalcFocusEffect(type, focus_spell_id, spell_id);
|
|
if (proc_spellid) {
|
|
TryTriggerOnCastProc(focus_spell_id, spell_id, proc_spellid);
|
|
CheckNumHitsRemaining(NumHit::MatchingSpells, buff_slot);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only use one of this focus per AA effect.
|
|
if (IsClient() && aabonuses.FocusEffects[type]) {
|
|
for (const auto &aa : aa_ranks) {
|
|
auto ability_rank = zone->GetAlternateAdvancementAbilityAndRank(aa.first, aa.second.first);
|
|
auto ability = ability_rank.first;
|
|
auto rank = ability_rank.second;
|
|
|
|
if (!ability) {
|
|
continue;
|
|
}
|
|
|
|
if (rank->effects.empty()) {
|
|
continue;
|
|
}
|
|
|
|
proc_spellid = CastToClient()->CalcAAFocus(type, *rank, spell_id);
|
|
if (proc_spellid) {
|
|
TryTriggerOnCastProc(0, spell_id, proc_spellid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Mob::TryTriggerOnCastProc(uint16 focusspellid, uint16 spell_id, uint16 proc_spellid)
|
|
{
|
|
// We confirm spell_id and focuspellid are valid before passing into this.
|
|
if (IsValidSpell(proc_spellid) && spell_id != focusspellid && spell_id != proc_spellid) {
|
|
Mob* proc_target = GetTarget();
|
|
if (proc_target) {
|
|
SpellFinished(proc_spellid, proc_target, EQ::spells::CastingSlot::Item, 0, -1, spells[proc_spellid].resist_difficulty);
|
|
return true;
|
|
}
|
|
// Edge cases where proc spell does not require a target such as PBAE, allows proc to still occur even if target potentially dead. Live spells exist with PBAE procs.
|
|
else if (!SpellRequiresTarget(proc_spellid)) {
|
|
SpellFinished(proc_spellid, this, EQ::spells::CastingSlot::Item, 0, -1, spells[proc_spellid].resist_difficulty);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
uint16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) {
|
|
|
|
if (IsBardSong(spell_id))
|
|
return 0;
|
|
|
|
uint16 proc_spellid = 0;
|
|
float ProcChance = 0.0f;
|
|
|
|
std::vector<int> SympatheticProcList;
|
|
|
|
//item focus
|
|
if (itembonuses.FocusEffects[type]){
|
|
|
|
const EQ::ItemData* TempItem = nullptr;
|
|
|
|
for (int x = EQ::invslot::EQUIPMENT_BEGIN; x <= EQ::invslot::EQUIPMENT_END; x++)
|
|
{
|
|
if (SympatheticProcList.size() > MAX_SYMPATHETIC_PROCS)
|
|
continue;
|
|
|
|
TempItem = nullptr;
|
|
EQ::ItemInstance* ins = GetInv().GetItem(x);
|
|
if (!ins)
|
|
continue;
|
|
TempItem = ins->GetItem();
|
|
if (TempItem && TempItem->Focus.Effect > 0 && IsValidSpell(TempItem->Focus.Effect)) {
|
|
|
|
proc_spellid = CalcFocusEffect(type, TempItem->Focus.Effect, spell_id);
|
|
if (IsValidSpell(proc_spellid)){
|
|
ProcChance = GetSympatheticProcChances(spell_id, spells[TempItem->Focus.Effect].base_value[0], TempItem->ProcRate);
|
|
if(zone->random.Roll(ProcChance))
|
|
SympatheticProcList.push_back(proc_spellid);
|
|
}
|
|
}
|
|
|
|
for (int y = EQ::invaug::SOCKET_BEGIN; y <= EQ::invaug::SOCKET_END; ++y)
|
|
{
|
|
if (SympatheticProcList.size() > MAX_SYMPATHETIC_PROCS)
|
|
continue;
|
|
|
|
EQ::ItemInstance *aug = nullptr;
|
|
aug = ins->GetAugment(y);
|
|
if(aug)
|
|
{
|
|
const EQ::ItemData* TempItemAug = aug->GetItem();
|
|
if (TempItemAug && TempItemAug->Focus.Effect > 0 && IsValidSpell(TempItemAug->Focus.Effect)) {
|
|
proc_spellid = CalcFocusEffect(type, TempItemAug->Focus.Effect, spell_id);
|
|
if (IsValidSpell(proc_spellid)){
|
|
ProcChance = GetSympatheticProcChances(spell_id, spells[TempItemAug->Focus.Effect].base_value[0], TempItemAug->ProcRate);
|
|
if(zone->random.Roll(ProcChance))
|
|
SympatheticProcList.push_back(proc_spellid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Spell Focus
|
|
if (spellbonuses.FocusEffects[type]){
|
|
int buff_slot = 0;
|
|
uint16 focusspellid = 0;
|
|
int buff_max = GetMaxTotalSlots();
|
|
for (buff_slot = 0; buff_slot < buff_max; buff_slot++) {
|
|
|
|
if (SympatheticProcList.size() > MAX_SYMPATHETIC_PROCS)
|
|
continue;
|
|
|
|
focusspellid = buffs[buff_slot].spellid;
|
|
if (!IsValidSpell(focusspellid))
|
|
continue;
|
|
|
|
proc_spellid = CalcFocusEffect(type, focusspellid, spell_id);
|
|
|
|
if (IsValidSpell(proc_spellid)){
|
|
|
|
ProcChance = GetSympatheticProcChances(spell_id, GetSympatheticSpellProcRate(proc_spellid));
|
|
if(zone->random.Roll(ProcChance))
|
|
SympatheticProcList.push_back(proc_spellid);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*Note: At present, ff designing custom AA to have a sympathetic proc effect, only use one focus
|
|
effect within the aa_effects data for each AA*[No live AA's use this effect to my knowledge]*/
|
|
if (aabonuses.FocusEffects[type]) {
|
|
for (const auto &aa : aa_ranks) {
|
|
if (SympatheticProcList.size() > MAX_SYMPATHETIC_PROCS)
|
|
break;
|
|
|
|
auto ability_rank = zone->GetAlternateAdvancementAbilityAndRank(aa.first, aa.second.first);
|
|
auto ability = ability_rank.first;
|
|
auto rank = ability_rank.second;
|
|
|
|
if(!ability) {
|
|
continue;
|
|
}
|
|
|
|
if (rank->effects.empty())
|
|
continue;
|
|
|
|
proc_spellid = CalcAAFocus(type, *rank, spell_id);
|
|
if (IsValidSpell(proc_spellid)) {
|
|
ProcChance = GetSympatheticProcChances(spell_id, rank->effects[0].base_value);
|
|
if (zone->random.Roll(ProcChance))
|
|
SympatheticProcList.push_back(proc_spellid);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!SympatheticProcList.empty())
|
|
{
|
|
uint8 random = zone->random.Int(0, SympatheticProcList.size()-1);
|
|
int FinalSympatheticProc = SympatheticProcList[random];
|
|
SympatheticProcList.clear();
|
|
return FinalSympatheticProc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int64 Mob::GetFocusEffect(focusType type, uint16 spell_id, Mob *caster, bool from_buff_tic)
|
|
{
|
|
if (IsBardSong(spell_id) && type != focusFcBaseEffects && type != focusSpellDuration && type != focusReduceRecastTime) {
|
|
return 0;
|
|
}
|
|
|
|
int64 realTotal = 0;
|
|
int64 realTotal2 = 0;
|
|
int64 realTotal3 = 0;
|
|
|
|
bool rand_effectiveness = false;
|
|
|
|
//Improved Healing, Damage & Mana Reduction are handled differently in that some are random percentages
|
|
//In these cases we need to find the most powerful effect, so that each piece of gear wont get its own chance
|
|
if (RuleB(Spells, LiveLikeFocusEffects) && CanFocusUseRandomEffectivenessByType(type)) {
|
|
rand_effectiveness = true;
|
|
}
|
|
|
|
//Check if item focus effect exists for the mob.
|
|
if (itembonuses.FocusEffects[type]) {
|
|
|
|
const EQ::ItemData* TempItem = nullptr;
|
|
const EQ::ItemData* UsedItem = nullptr;
|
|
uint16 UsedFocusID = 0;
|
|
int32 Total = 0;
|
|
int32 focus_max = 0;
|
|
int32 focus_max_real = 0;
|
|
|
|
//item focus
|
|
for (int x = EQ::invslot::EQUIPMENT_BEGIN; x <= EQ::invslot::EQUIPMENT_END; x++) {
|
|
TempItem = nullptr;
|
|
EQ::ItemInstance* ins = GetInv().GetItem(x);
|
|
if (!ins) {
|
|
continue;
|
|
}
|
|
|
|
TempItem = ins->GetItem();
|
|
if (TempItem && TempItem->Focus.Effect > 0 && TempItem->Focus.Effect != SPELL_UNKNOWN) {
|
|
if(rand_effectiveness) {
|
|
focus_max = CalcFocusEffect(type, TempItem->Focus.Effect, spell_id, true);
|
|
if (focus_max > 0 && focus_max_real >= 0 && focus_max > focus_max_real) {
|
|
focus_max_real = focus_max;
|
|
UsedItem = TempItem;
|
|
UsedFocusID = TempItem->Focus.Effect;
|
|
} else if (focus_max < 0 && focus_max < focus_max_real) {
|
|
focus_max_real = focus_max;
|
|
UsedItem = TempItem;
|
|
UsedFocusID = TempItem->Focus.Effect;
|
|
}
|
|
}
|
|
else {
|
|
Total = CalcFocusEffect(type, TempItem->Focus.Effect, spell_id);
|
|
if (Total > 0 && realTotal >= 0 && Total > realTotal) {
|
|
realTotal = Total;
|
|
UsedItem = TempItem;
|
|
UsedFocusID = TempItem->Focus.Effect;
|
|
} else if (Total < 0 && Total < realTotal) {
|
|
realTotal = Total;
|
|
UsedItem = TempItem;
|
|
UsedFocusID = TempItem->Focus.Effect;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int y = EQ::invaug::SOCKET_BEGIN; y <= EQ::invaug::SOCKET_END; ++y) {
|
|
EQ::ItemInstance *aug = nullptr;
|
|
aug = ins->GetAugment(y);
|
|
if (aug) {
|
|
const EQ::ItemData* TempItemAug = aug->GetItem();
|
|
if (TempItemAug && TempItemAug->Focus.Effect > 0 && TempItemAug->Focus.Effect != SPELL_UNKNOWN) {
|
|
if(rand_effectiveness) {
|
|
focus_max = CalcFocusEffect(type, TempItemAug->Focus.Effect, spell_id, true);
|
|
if (focus_max > 0 && focus_max_real >= 0 && focus_max > focus_max_real) {
|
|
focus_max_real = focus_max;
|
|
UsedItem = TempItem;
|
|
UsedFocusID = TempItemAug->Focus.Effect;
|
|
} else if (focus_max < 0 && focus_max < focus_max_real) {
|
|
focus_max_real = focus_max;
|
|
UsedItem = TempItem;
|
|
UsedFocusID = TempItemAug->Focus.Effect;
|
|
}
|
|
}
|
|
else {
|
|
Total = CalcFocusEffect(type, TempItemAug->Focus.Effect, spell_id);
|
|
if (Total > 0 && realTotal >= 0 && Total > realTotal) {
|
|
realTotal = Total;
|
|
UsedItem = TempItem;
|
|
UsedFocusID = TempItemAug->Focus.Effect;
|
|
} else if (Total < 0 && Total < realTotal) {
|
|
realTotal = Total;
|
|
UsedItem = TempItem;
|
|
UsedFocusID = TempItemAug->Focus.Effect;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsClient()) {
|
|
//Client Tribute Focus
|
|
for (int x = EQ::invslot::TRIBUTE_BEGIN; x <= EQ::invslot::TRIBUTE_END; ++x) {
|
|
TempItem = nullptr;
|
|
EQ::ItemInstance* ins = GetInv().GetItem(x);
|
|
if (!ins) {
|
|
continue;
|
|
}
|
|
|
|
TempItem = ins->GetItem();
|
|
if (TempItem && TempItem->Focus.Effect > 0 && TempItem->Focus.Effect != SPELL_UNKNOWN) {
|
|
if (rand_effectiveness) {
|
|
focus_max = CalcFocusEffect(type, TempItem->Focus.Effect, spell_id, true);
|
|
if (focus_max > 0 && focus_max_real >= 0 && focus_max > focus_max_real) {
|
|
focus_max_real = focus_max;
|
|
UsedItem = TempItem;
|
|
UsedFocusID = TempItem->Focus.Effect;
|
|
} else if (focus_max < 0 && focus_max < focus_max_real) {
|
|
focus_max_real = focus_max;
|
|
UsedItem = TempItem;
|
|
UsedFocusID = TempItem->Focus.Effect;
|
|
}
|
|
}
|
|
else {
|
|
Total = CalcFocusEffect(type, TempItem->Focus.Effect, spell_id);
|
|
if (Total > 0 && realTotal >= 0 && Total > realTotal) {
|
|
realTotal = Total;
|
|
UsedItem = TempItem;
|
|
UsedFocusID = TempItem->Focus.Effect;
|
|
}
|
|
else if (Total < 0 && Total < realTotal) {
|
|
realTotal = Total;
|
|
UsedItem = TempItem;
|
|
UsedFocusID = TempItem->Focus.Effect;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (UsedItem && rand_effectiveness && focus_max_real != 0) {
|
|
realTotal = CalcFocusEffect(type, UsedFocusID, spell_id);
|
|
}
|
|
|
|
if ((rand_effectiveness && UsedItem) || (realTotal != 0 && UsedItem)) {
|
|
// there are a crap ton more of these, I was able to verify these ones though
|
|
// the RNG effective ones appear to have a different message for failing to focus
|
|
uint32 string_id = BEGINS_TO_GLOW; // this is really just clicky message ...
|
|
switch (type) {
|
|
case focusSpellHaste:
|
|
string_id = SHIMMERS_BRIEFLY;
|
|
break;
|
|
case focusManaCost: // this might be GROWS_DIM for fail
|
|
string_id = FLICKERS_PALE_LIGHT;
|
|
break;
|
|
case focusSpellDuration:
|
|
string_id = SPARKLES;
|
|
break;
|
|
case focusImprovedDamage:
|
|
case focusImprovedDamage2:
|
|
if (realTotal)
|
|
string_id = ALIVE_WITH_POWER;
|
|
else
|
|
string_id = SEEMS_DRAINED;
|
|
break;
|
|
case focusRange:
|
|
string_id = PULSES_WITH_LIGHT;
|
|
break;
|
|
case focusSpellHateMod: // GLOWS_RED for increasing hate
|
|
string_id = GLOWS_BLUE;
|
|
break;
|
|
case focusImprovedHeal:
|
|
if (realTotal)
|
|
string_id = FEEDS_WITH_POWER;
|
|
else
|
|
string_id = POWER_DRAIN_INTO;
|
|
break;
|
|
case focusReagentCost: // this might be GROWS_DIM for fail as well ...
|
|
string_id = BEGINS_TO_SHINE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
MessageString(Chat::FocusEffect, string_id, UsedItem->Name);
|
|
}
|
|
}
|
|
|
|
//Check if spell focus effect exists for the mob.
|
|
if (spellbonuses.FocusEffects[type]) {
|
|
|
|
//Spell Focus
|
|
int32 Total2 = 0;
|
|
int32 focus_max2 = 0;
|
|
int32 focus_max_real2 = 0;
|
|
|
|
int buff_tracker = -1;
|
|
int buff_slot = 0;
|
|
int32 focusspellid = 0;
|
|
int32 focusspell_tracker = 0;
|
|
int buff_max = GetMaxTotalSlots();
|
|
for (buff_slot = 0; buff_slot < buff_max; buff_slot++) {
|
|
focusspellid = buffs[buff_slot].spellid;
|
|
if (focusspellid == 0 || focusspellid >= SPDAT_RECORDS) {
|
|
continue;
|
|
}
|
|
|
|
if (rand_effectiveness) {
|
|
focus_max2 = CalcFocusEffect(type, focusspellid, spell_id, true, buffs[buff_slot].casterid, caster);
|
|
if (focus_max2 > 0 && focus_max_real2 >= 0 && focus_max2 > focus_max_real2) {
|
|
focus_max_real2 = focus_max2;
|
|
buff_tracker = buff_slot;
|
|
focusspell_tracker = focusspellid;
|
|
} else if (focus_max2 < 0 && focus_max2 < focus_max_real2) {
|
|
focus_max_real2 = focus_max2;
|
|
buff_tracker = buff_slot;
|
|
focusspell_tracker = focusspellid;
|
|
}
|
|
}
|
|
else {
|
|
Total2 = CalcFocusEffect(type, focusspellid, spell_id, false, buffs[buff_slot].casterid, caster);
|
|
if (Total2 > 0 && realTotal2 >= 0 && Total2 > realTotal2) {
|
|
realTotal2 = Total2;
|
|
buff_tracker = buff_slot;
|
|
focusspell_tracker = focusspellid;
|
|
} else if (Total2 < 0 && Total2 < realTotal2) {
|
|
realTotal2 = Total2;
|
|
buff_tracker = buff_slot;
|
|
focusspell_tracker = focusspellid;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint16 original_caster_id = 0;
|
|
if (buff_tracker >= 0 && buffs[buff_tracker].casterid > 0) {
|
|
original_caster_id = buffs[buff_tracker].casterid;
|
|
}
|
|
|
|
if (focusspell_tracker && rand_effectiveness && focus_max_real2 != 0) {
|
|
realTotal2 = CalcFocusEffect(type, focusspell_tracker, spell_id, false, original_caster_id, caster);
|
|
}
|
|
|
|
if (!from_buff_tic && buff_tracker >= 0 && buffs[buff_tracker].hit_number > 0) {
|
|
CheckNumHitsRemaining(NumHit::MatchingSpells, buff_tracker);
|
|
}
|
|
}
|
|
|
|
// AA Focus
|
|
if (aabonuses.FocusEffects[type]) {
|
|
|
|
int32 Total3 = 0;
|
|
|
|
for (const auto &aa : aa_ranks) {
|
|
auto ability_rank = zone->GetAlternateAdvancementAbilityAndRank(aa.first, aa.second.first);
|
|
auto ability = ability_rank.first;
|
|
auto rank = ability_rank.second;
|
|
|
|
if (!ability) {
|
|
continue;
|
|
}
|
|
|
|
if (rank->effects.empty()) {
|
|
continue;
|
|
}
|
|
|
|
Total3 = CalcAAFocus(type, *rank, spell_id);
|
|
if (Total3 > 0 && realTotal3 >= 0 && Total3 > realTotal3) {
|
|
realTotal3 = Total3;
|
|
}
|
|
else if (Total3 < 0 && Total3 < realTotal3) {
|
|
realTotal3 = Total3;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (type == focusReagentCost && (IsEffectInSpell(spell_id, SE_SummonItem) || IsSacrificeSpell(spell_id))) {
|
|
return 0;
|
|
}
|
|
//Summon Spells that require reagents are typically imbue type spells, enchant metal, sacrifice and shouldn't be affected
|
|
//by reagent conservation for obvious reasons.
|
|
|
|
//Non-Live like feature to allow for an additive focus bonus to be applied from foci that are placed in worn slot. (No limit checks)
|
|
int32 worneffect_bonus = 0;
|
|
if (RuleB(Spells, UseAdditiveFocusFromWornSlot)) {
|
|
worneffect_bonus = itembonuses.FocusEffectsWorn[type];
|
|
}
|
|
|
|
return realTotal + realTotal2 + realTotal3 + worneffect_bonus;
|
|
}
|
|
|
|
int64 NPC::GetFocusEffect(focusType type, uint16 spell_id, Mob* caster, bool from_buff_tic) {
|
|
|
|
int64 realTotal = 0;
|
|
int64 realTotal2 = 0;
|
|
|
|
bool rand_effectiveness = false;
|
|
|
|
//Improved Healing, Damage & Mana Reduction are handled differently in that some are random percentages
|
|
//In these cases we need to find the most powerful effect, so that each piece of gear wont get its own chance
|
|
if (RuleB(Spells, LiveLikeFocusEffects) && CanFocusUseRandomEffectivenessByType(type)) {
|
|
rand_effectiveness = true;
|
|
}
|
|
|
|
if (RuleB(Spells, NPC_UseFocusFromItems) && itembonuses.FocusEffects[type]){
|
|
|
|
const EQ::ItemData* TempItem = nullptr;
|
|
const EQ::ItemData* UsedItem = nullptr;
|
|
uint16 UsedFocusID = 0;
|
|
int64 Total = 0;
|
|
int64 focus_max = 0;
|
|
int64 focus_max_real = 0;
|
|
|
|
//item focus
|
|
for (int i = EQ::invslot::EQUIPMENT_BEGIN; i <= EQ::invslot::EQUIPMENT_END; i++){
|
|
const EQ::ItemData *cur = database.GetItem(equipment[i]);
|
|
|
|
if(!cur)
|
|
continue;
|
|
|
|
TempItem = cur;
|
|
|
|
if (TempItem && TempItem->Focus.Effect > 0 && TempItem->Focus.Effect != SPELL_UNKNOWN) {
|
|
if(rand_effectiveness) {
|
|
focus_max = CalcFocusEffect(type, TempItem->Focus.Effect, spell_id, true);
|
|
if (focus_max > 0 && focus_max_real >= 0 && focus_max > focus_max_real) {
|
|
focus_max_real = focus_max;
|
|
UsedItem = TempItem;
|
|
UsedFocusID = TempItem->Focus.Effect;
|
|
} else if (focus_max < 0 && focus_max < focus_max_real) {
|
|
focus_max_real = focus_max;
|
|
UsedItem = TempItem;
|
|
UsedFocusID = TempItem->Focus.Effect;
|
|
}
|
|
}
|
|
else {
|
|
Total = CalcFocusEffect(type, TempItem->Focus.Effect, spell_id);
|
|
if (Total > 0 && realTotal >= 0 && Total > realTotal) {
|
|
realTotal = Total;
|
|
UsedItem = TempItem;
|
|
UsedFocusID = TempItem->Focus.Effect;
|
|
} else if (Total < 0 && Total < realTotal) {
|
|
realTotal = Total;
|
|
UsedItem = TempItem;
|
|
UsedFocusID = TempItem->Focus.Effect;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(UsedItem && rand_effectiveness && focus_max_real != 0)
|
|
realTotal = CalcFocusEffect(type, UsedFocusID, spell_id);
|
|
}
|
|
|
|
if ((RuleB(Spells, NPC_UseFocusFromSpells) || IsTargetedFocusEffect(type)) && spellbonuses.FocusEffects[type]){
|
|
|
|
//Spell Focus
|
|
int64 Total2 = 0;
|
|
int64 focus_max2 = 0;
|
|
int64 focus_max_real2 = 0;
|
|
|
|
int buff_tracker = -1;
|
|
int buff_slot = 0;
|
|
int64 focusspellid = 0;
|
|
int64 focusspell_tracker = 0;
|
|
int buff_max = GetMaxTotalSlots();
|
|
for (buff_slot = 0; buff_slot < buff_max; buff_slot++) {
|
|
focusspellid = buffs[buff_slot].spellid;
|
|
if (focusspellid == 0 || focusspellid >= SPDAT_RECORDS)
|
|
continue;
|
|
|
|
if(rand_effectiveness) {
|
|
focus_max2 = CalcFocusEffect(type, focusspellid, spell_id, true, buffs[buff_slot].casterid, caster);
|
|
if (focus_max2 > 0 && focus_max_real2 >= 0 && focus_max2 > focus_max_real2) {
|
|
focus_max_real2 = focus_max2;
|
|
buff_tracker = buff_slot;
|
|
focusspell_tracker = focusspellid;
|
|
} else if (focus_max2 < 0 && focus_max2 < focus_max_real2) {
|
|
focus_max_real2 = focus_max2;
|
|
buff_tracker = buff_slot;
|
|
focusspell_tracker = focusspellid;
|
|
}
|
|
}
|
|
else {
|
|
Total2 = CalcFocusEffect(type, focusspellid, spell_id, false, buffs[buff_slot].casterid, caster);
|
|
if (Total2 > 0 && realTotal2 >= 0 && Total2 > realTotal2) {
|
|
realTotal2 = Total2;
|
|
buff_tracker = buff_slot;
|
|
focusspell_tracker = focusspellid;
|
|
} else if (Total2 < 0 && Total2 < realTotal2) {
|
|
realTotal2 = Total2;
|
|
buff_tracker = buff_slot;
|
|
focusspell_tracker = focusspellid;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint16 original_caster_id = 0;
|
|
if (buff_tracker >= 0 && buffs[buff_tracker].casterid > 0) {
|
|
original_caster_id = buffs[buff_tracker].casterid;
|
|
}
|
|
|
|
if (focusspell_tracker && rand_effectiveness && focus_max_real2 != 0) {
|
|
realTotal2 = CalcFocusEffect(type, focusspell_tracker, spell_id, false, original_caster_id, caster);
|
|
}
|
|
|
|
if(!from_buff_tic && buff_tracker >= 0 && buffs[buff_tracker].hit_number > 0) {
|
|
CheckNumHitsRemaining(NumHit::MatchingSpells, buff_tracker);
|
|
}
|
|
}
|
|
|
|
return realTotal + realTotal2;
|
|
}
|
|
|
|
void Mob::CheckNumHitsRemaining(NumHit type, int32 buff_slot, uint16 spell_id)
|
|
{
|
|
/*
|
|
Field 175 = numhits type
|
|
1: [Incoming Hit Attempts] (323=SE_DefensiveProc, 172=SE_AvoidMeleeChance, 1=SE_ArmorClass, 40=SE_DivineAura)
|
|
2: [Outgoing Hit Attempts] (185=SE_DamageModifer, 184=SE_HitChance)
|
|
3: [Incoming Spells] (180=SE_ResistSpellChance, 296=SE_FcSpellVulnerability) //Note: Determinetal spells only unless proven otherwise
|
|
4: [Outgoing Spells]
|
|
5: [Outgoing Hit Successes] (220=SE_SkillDamageAmount, 178=SE_MeleeLifetap, 121=SE_ReverseDS, ?373=SE_CastOnFadeEffectAlways)
|
|
6: [Incoming Hit Successes] (59=SE_DamageShield, 197=SE_SkillDamageTaken, 162=define SE_MitigateMeleeDamage)
|
|
7: [Matching Spells] *When focus is triggered (focus effects)
|
|
8: [Incoming Hits or Spells] (329=SE_ManaAbsorbPercentDamage)
|
|
9: [Reflected Spells] If successful
|
|
10: [Defensive Procs] Only count down buff of the proc that executed
|
|
11: [Melee Procs] Only count down the buff of the proc that executed
|
|
*/
|
|
|
|
if (!HasNumhits())
|
|
return;
|
|
|
|
bool bDepleted = false;
|
|
int buff_max = GetMaxTotalSlots();
|
|
|
|
std::string buff_name;
|
|
size_t buff_counter = 0;
|
|
bool buff_update = false;
|
|
|
|
//Spell specific procs [Type 7,10,11]
|
|
if (IsValidSpell(spell_id)) {
|
|
for (int d = 0; d < buff_max; d++) {
|
|
if (buffs[d].spellid == spell_id && buffs[d].hit_number > 0 &&
|
|
spells[buffs[d].spellid].hit_number_type == static_cast<int>(type)) {
|
|
|
|
buff_name = spells[buffs[d].spellid].name;
|
|
buff_counter = (buffs[d].hit_number - 1);
|
|
buff_update = true;
|
|
|
|
if (--buffs[d].hit_number == 0) {
|
|
CastOnNumHitFade(buffs[d].spellid);
|
|
if (!TryFadeEffect(d))
|
|
BuffFadeBySlot(d, true);
|
|
} else if (IsClient()) { // still have numhits and client, update
|
|
CastToClient()->SendBuffNumHitPacket(buffs[d], d);
|
|
}
|
|
}
|
|
}
|
|
} else if (type == NumHit::MatchingSpells) {
|
|
if (buff_slot >= 0) {
|
|
if (--buffs[buff_slot].hit_number == 0) {
|
|
buff_name = spells[buffs[buff_slot].spellid].name;
|
|
buff_counter = (buffs[buff_slot].hit_number - 1);
|
|
buff_update = true;
|
|
|
|
CastOnNumHitFade(buffs[buff_slot].spellid);
|
|
if (!TryFadeEffect(buff_slot))
|
|
BuffFadeBySlot(buff_slot , true);
|
|
} else if (IsClient()) { // still have numhits and client, update
|
|
CastToClient()->SendBuffNumHitPacket(buffs[buff_slot], buff_slot);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (int d = 0; d < buff_max; d++) {
|
|
if (IsValidSpell(buffs[d].spellid) && buffs[d].hit_number > 0 &&
|
|
spells[buffs[d].spellid].hit_number_type == static_cast<int>(type)) {
|
|
|
|
buff_name = spells[buffs[d].spellid].name;
|
|
buff_counter = (buffs[d].hit_number - 1);
|
|
buff_update = true;
|
|
|
|
if (--buffs[d].hit_number == 0) {
|
|
CastOnNumHitFade(buffs[d].spellid);
|
|
if (!TryFadeEffect(d))
|
|
BuffFadeBySlot(d, true);
|
|
} else if (IsClient()) { // still have numhits and client, update
|
|
CastToClient()->SendBuffNumHitPacket(buffs[d], d);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IsBot() && buff_update) {
|
|
auto bot_owner = entity_list.GetBotOwnerByBotEntityID(GetID());
|
|
if (bot_owner && bot_owner->GetBotOption(Client::booBuffCounter)) {
|
|
bot_owner->CastToClient()->SendMarqueeMessage(
|
|
Chat::Yellow,
|
|
510,
|
|
0,
|
|
1000,
|
|
3000,
|
|
StringFormat(
|
|
"%s has [%u] hit%s remaining on '%s'",
|
|
GetCleanName(),
|
|
buff_counter,
|
|
(buff_counter == 1 ? "" : "s"),
|
|
buff_name.c_str()
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
//for some stupid reason SK procs return theirs one base off...
|
|
uint16 Mob::GetProcID(uint16 spell_id, uint8 effect_index)
|
|
{
|
|
if (!RuleB(Spells, SHDProcIDOffByOne)) // UF+ spell files
|
|
return spells[spell_id].base_value[effect_index];
|
|
|
|
// We should actually just be checking if the mob is SHD, but to not force
|
|
// custom servers to create new spells, we will still do this
|
|
bool sk = false;
|
|
bool other = false;
|
|
for (int x = 0; x < 16; x++) {
|
|
if (x == 4) {
|
|
if (spells[spell_id].classes[4] < 255)
|
|
sk = true;
|
|
} else {
|
|
if (spells[spell_id].classes[x] < 255)
|
|
other = true;
|
|
}
|
|
}
|
|
|
|
if (sk && !other)
|
|
return spells[spell_id].base_value[effect_index] + 1;
|
|
else
|
|
return spells[spell_id].base_value[effect_index];
|
|
}
|
|
|
|
bool Mob::TryDivineSave()
|
|
{
|
|
/*
|
|
How Touch of the Divine AA works:
|
|
-Gives chance to avoid death when client is killed.
|
|
-Chance is determined by the sum of AA/item/spell chances.
|
|
-If the chance is met a divine aura like effect 'Touch of the Divine' is applied to the client removing detrimental spell effects.
|
|
-If desired, additional spells can be triggered from the AA/item/spell effect, generally a heal.
|
|
*/
|
|
|
|
int32 SuccessChance = aabonuses.DivineSaveChance[SBIndex::DIVINE_SAVE_CHANCE] + itembonuses.DivineSaveChance[SBIndex::DIVINE_SAVE_CHANCE] + spellbonuses.DivineSaveChance[SBIndex::DIVINE_SAVE_CHANCE];
|
|
if (SuccessChance && zone->random.Roll(SuccessChance))
|
|
{
|
|
SetHP(1);
|
|
|
|
int32 EffectsToTry[] =
|
|
{
|
|
aabonuses.DivineSaveChance[SBIndex::DIVINE_SAVE_SPELL_TRIGGER_ID],
|
|
itembonuses.DivineSaveChance[SBIndex::DIVINE_SAVE_SPELL_TRIGGER_ID],
|
|
spellbonuses.DivineSaveChance[SBIndex::DIVINE_SAVE_SPELL_TRIGGER_ID]
|
|
};
|
|
//Fade the divine save effect here after saving the old effects off.
|
|
//That way, if desired, the effect could apply SE_DivineSave again.
|
|
BuffFadeByEffect(SE_DivineSave);
|
|
for(size_t i = 0; i < ( sizeof(EffectsToTry) / sizeof(EffectsToTry[0]) ); ++i)
|
|
{
|
|
if( EffectsToTry[i] )
|
|
{
|
|
SpellOnTarget(EffectsToTry[i], this);
|
|
}
|
|
}
|
|
|
|
SpellOnTarget(SPELL_TOUCH_OF_THE_DIVINE, this); //Touch of the Divine=4789, an Invulnerability/HoT/Purify effect
|
|
SendHPUpdate();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Mob::TryDeathSave() {
|
|
|
|
/*
|
|
How Death Save works:
|
|
-Chance for Death Save to fire is the same for Death Pact/Divine Intervention
|
|
-Base value of these determines amount healed (1=partial(300HP), 2='full (8000HP)) HARD CODED
|
|
-Charisma of client who has the effect determines fire rate, parses show this clearly with ratio used.
|
|
-Unfailing Divinity AA - Allows for a chance to give a heal for a percentage of the orginal value
|
|
when your innate chance fails and removes the buff. The spell effect for Unfailing Divinity gives
|
|
the a value of a heal modifier of the base effects heal.
|
|
Ie. Divine Intervention is 8000 HP Max UD1=20, therefore heal is 8000*20/100
|
|
-No evidence of chance rate increasing between UD1-3, numbers indicate it uses same CHA rate as first DI.
|
|
-In later expansions this SE_DeathSave was given a level limit and a heal value in its effect data.
|
|
*/
|
|
|
|
if (spellbonuses.DeathSave[SBIndex::DEATH_SAVE_TYPE]){
|
|
|
|
int SuccessChance = 0;
|
|
int buffSlot = spellbonuses.DeathSave[SBIndex::DEATH_SAVE_BUFFSLOT];
|
|
int32 UD_HealMod = 0;
|
|
int64 HealAmt = 300; //Death Pact max Heal
|
|
|
|
if(buffSlot >= 0){
|
|
|
|
UD_HealMod = buffs[buffSlot].ExtraDIChance;
|
|
|
|
SuccessChance = ( (GetCHA() * (RuleI(Spells, DeathSaveCharismaMod))) + 1) / 10; //(CHA Mod Default = 3)
|
|
|
|
if (SuccessChance > 95)
|
|
SuccessChance = 95;
|
|
|
|
if(zone->random.Roll(SuccessChance)) {
|
|
|
|
if(spellbonuses.DeathSave[SBIndex::DEATH_SAVE_TYPE] == 2)
|
|
HealAmt = RuleI(Spells, DivineInterventionHeal); //8000HP is how much LIVE Divine Intervention max heals
|
|
|
|
//Check if bonus Heal amount can be applied ([3] Bonus Heal [2] Level limit)
|
|
if (spellbonuses.DeathSave[SBIndex::DEATH_SAVE_HEAL_AMT] && (GetLevel() >= spellbonuses.DeathSave[SBIndex::DEATH_SAVE_MIN_LEVEL_FOR_HEAL]))
|
|
HealAmt += spellbonuses.DeathSave[SBIndex::DEATH_SAVE_HEAL_AMT];
|
|
|
|
if ((GetMaxHP() - GetHP()) < HealAmt)
|
|
HealAmt = GetMaxHP() - GetHP();
|
|
|
|
SetHP((GetHP()+HealAmt));
|
|
Message(Chat::Emote, "The gods have healed you for %i points of damage.", HealAmt);
|
|
|
|
if(spellbonuses.DeathSave[SBIndex::DEATH_SAVE_TYPE] == 2)
|
|
entity_list.MessageCloseString(
|
|
this,
|
|
false,
|
|
200,
|
|
Chat::MeleeCrit,
|
|
DIVINE_INTERVENTION,
|
|
GetCleanName());
|
|
else
|
|
entity_list.MessageCloseString(this, false, 200, Chat::MeleeCrit, DEATH_PACT, GetCleanName());
|
|
|
|
SendHPUpdate();
|
|
BuffFadeBySlot(buffSlot);
|
|
return true;
|
|
}
|
|
else if (UD_HealMod) {
|
|
|
|
SuccessChance = ((GetCHA() * (RuleI(Spells, DeathSaveCharismaMod))) + 1) / 10;
|
|
|
|
if (SuccessChance > 95)
|
|
SuccessChance = 95;
|
|
|
|
if(zone->random.Roll(SuccessChance)) {
|
|
|
|
if(spellbonuses.DeathSave[SBIndex::DEATH_SAVE_TYPE] == 2)
|
|
HealAmt = RuleI(Spells, DivineInterventionHeal);
|
|
|
|
//Check if bonus Heal amount can be applied ([3] Bonus Heal [2] Level limit)
|
|
if (spellbonuses.DeathSave[SBIndex::DEATH_SAVE_HEAL_AMT] && (GetLevel() >= spellbonuses.DeathSave[SBIndex::DEATH_SAVE_MIN_LEVEL_FOR_HEAL]))
|
|
HealAmt += spellbonuses.DeathSave[SBIndex::DEATH_SAVE_HEAL_AMT];
|
|
|
|
HealAmt = HealAmt*UD_HealMod/100;
|
|
|
|
if ((GetMaxHP() - GetHP()) < HealAmt)
|
|
HealAmt = GetMaxHP() - GetHP();
|
|
|
|
SetHP((GetHP()+HealAmt));
|
|
Message(Chat::Emote, "The gods have healed you for %i points of damage.", HealAmt);
|
|
|
|
if(spellbonuses.DeathSave[SBIndex::DEATH_SAVE_TYPE] == 2)
|
|
entity_list.MessageCloseString(
|
|
this,
|
|
false,
|
|
200,
|
|
Chat::MeleeCrit,
|
|
DIVINE_INTERVENTION,
|
|
GetCleanName());
|
|
else
|
|
entity_list.MessageCloseString(this, false, 200, Chat::MeleeCrit, DEATH_PACT, GetCleanName());
|
|
|
|
SendHPUpdate();
|
|
BuffFadeBySlot(buffSlot);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
BuffFadeBySlot(buffSlot);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Mob::AffectedBySpellExcludingSlot(int slot, int effect)
|
|
{
|
|
int buff_count = GetMaxTotalSlots();
|
|
for (int i = 0; i < buff_count; i++)
|
|
{
|
|
if (i == slot)
|
|
continue;
|
|
|
|
if (IsEffectInSpell(buffs[i].spellid, effect))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
float Mob::GetSympatheticProcChances(uint16 spell_id, int16 ProcRateMod, int32 ItemProcRate) {
|
|
|
|
float ProcChance = 0.0f;
|
|
int32 total_cast_time = 0;
|
|
float cast_time_mod = 0.0f;
|
|
ProcRateMod -= 100;
|
|
|
|
|
|
if (spells[spell_id].recast_time >= spells[spell_id].recovery_time)
|
|
total_cast_time = spells[spell_id].recast_time + spells[spell_id].cast_time;
|
|
else
|
|
total_cast_time = spells[spell_id].recovery_time + spells[spell_id].cast_time;
|
|
|
|
if (total_cast_time > 0 && total_cast_time <= 2500)
|
|
cast_time_mod = 0.25f;
|
|
else if (total_cast_time > 2500 && total_cast_time < 7000)
|
|
cast_time_mod = 0.167f*((static_cast<float>(total_cast_time) - 1000.0f)/1000.0f);
|
|
else
|
|
cast_time_mod = static_cast<float>(total_cast_time) / 7000.0f;
|
|
|
|
ProcChance = (RuleR(Casting, AvgSpellProcsPerMinute)/100.0f) * (static_cast<float>(100.0f + ProcRateMod) / 10.0f)
|
|
* cast_time_mod * (static_cast<float>(100.0f + ItemProcRate)/100.0f);
|
|
|
|
return ProcChance;
|
|
}
|
|
|
|
int16 Mob::GetSympatheticSpellProcRate(uint16 spell_id)
|
|
{
|
|
for (int i = 0; i < EFFECT_COUNT; i++){
|
|
if (spells[spell_id].effect_id[i] == SE_SympatheticProc)
|
|
return spells[spell_id].base_value[i];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint16 Mob::GetSympatheticSpellProcID(uint16 spell_id)
|
|
{
|
|
for (int i = 0; i < EFFECT_COUNT; i++){
|
|
if (spells[spell_id].effect_id[i] == SE_SympatheticProc)
|
|
return spells[spell_id].limit_value[i];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool Mob::DoHPToManaCovert(int32 mana_cost)
|
|
{
|
|
if (spellbonuses.HPToManaConvert){
|
|
int64 hp_cost = spellbonuses.HPToManaConvert * mana_cost / 100;
|
|
if(hp_cost) {
|
|
SetHP(GetHP()-hp_cost);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int64 Mob::GetFcDamageAmtIncoming(Mob *caster, int32 spell_id, bool from_buff_tic)
|
|
{
|
|
//THIS is target of spell cast
|
|
int64 dmg = 0;
|
|
dmg += GetFocusEffect(focusFcDamageAmtIncoming, spell_id, caster, from_buff_tic); //SPA 297 SE_FcDamageAmtIncoming
|
|
dmg += GetFocusEffect(focusFcSpellDamageAmtIncomingPC, spell_id, caster, from_buff_tic); //SPA 484 SE_Fc_Spell_Damage_Amt_IncomingPC
|
|
return dmg;
|
|
}
|
|
|
|
int64 Mob::GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spell_id) {
|
|
|
|
//**** This can be removed when bot healing focus code is updated ****
|
|
|
|
/*
|
|
This is a general function for calculating best focus effect values for focus effects that exist on targets but modify incoming spells.
|
|
Should be used when checking for foci that can exist on clients or npcs ect.
|
|
Example: When your target has a focus limited buff that increases amount of healing on them.
|
|
*/
|
|
|
|
if (!caster) {
|
|
return 0;
|
|
}
|
|
|
|
int64 value = 0;
|
|
|
|
if (spellbonuses.FocusEffects[type]){
|
|
|
|
int32 tmp_focus = 0;
|
|
int tmp_buffslot = -1;
|
|
|
|
int buff_count = GetMaxTotalSlots();
|
|
for(int i = 0; i < buff_count; i++) {
|
|
|
|
if((IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, effect))){
|
|
|
|
int64 focus = caster->CalcFocusEffect(type, buffs[i].spellid, spell_id);
|
|
|
|
if (!focus) {
|
|
continue;
|
|
}
|
|
|
|
if (tmp_focus && focus > tmp_focus){
|
|
tmp_focus = focus;
|
|
tmp_buffslot = i;
|
|
}
|
|
|
|
else if (!tmp_focus){
|
|
tmp_focus = focus;
|
|
tmp_buffslot = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
value = tmp_focus;
|
|
|
|
if (tmp_buffslot >= 0)
|
|
CheckNumHitsRemaining(NumHit::MatchingSpells, tmp_buffslot);
|
|
}
|
|
|
|
|
|
return value;
|
|
}
|
|
|
|
bool Mob::PassLimitClass(uint32 Classes_, uint16 Class_)
|
|
{
|
|
//The class value for SE_LimitClass is +1 to its equivelent value in item dbase
|
|
//Example Bard on items is '128' while Bard on SE_LimitClass is '256', keep this in mind if making custom spells.
|
|
if (Class_ > 16)
|
|
return false;
|
|
|
|
Class_ += 1;
|
|
for (int CurrentClass = 1; CurrentClass <= PLAYER_CLASS_COUNT; ++CurrentClass){
|
|
if (Classes_ % 2 == 1){
|
|
if (CurrentClass == Class_)
|
|
return true;
|
|
}
|
|
Classes_ >>= 1;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
uint16 Mob::GetSpellEffectResistChance(uint16 spell_id)
|
|
{
|
|
|
|
if(!IsValidSpell(spell_id))
|
|
return 0;
|
|
|
|
if (!aabonuses.SEResist[1] && !spellbonuses.SEResist[1] && !itembonuses.SEResist[1])
|
|
return 0;
|
|
|
|
uint16 resist_chance = 0;
|
|
|
|
for(int i = 0; i < EFFECT_COUNT; ++i)
|
|
{
|
|
bool found = false;
|
|
|
|
for(int d = 0; d < MAX_RESISTABLE_EFFECTS*2; d+=2)
|
|
{
|
|
if (spells[spell_id].effect_id[i] == aabonuses.SEResist[d]){
|
|
resist_chance += aabonuses.SEResist[d+1];
|
|
found = true;
|
|
}
|
|
|
|
if (spells[spell_id].effect_id[i] == itembonuses.SEResist[d]){
|
|
resist_chance += itembonuses.SEResist[d+1];
|
|
found = true;
|
|
}
|
|
|
|
|
|
if (spells[spell_id].effect_id[i] == spellbonuses.SEResist[d]){
|
|
resist_chance += spellbonuses.SEResist[d+1];
|
|
found = true;
|
|
}
|
|
|
|
if (found)
|
|
continue;
|
|
}
|
|
}
|
|
return resist_chance;
|
|
}
|
|
|
|
bool Mob::TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier){
|
|
|
|
//Dispels - Check level of caster agianst buffs level (level of the caster who cast the buff)
|
|
//Effect value of dispels are treated as a level modifier.
|
|
//Values for scaling were obtain from live parses, best estimates.
|
|
|
|
caster_level += level_modifier - 1;
|
|
int dispel_chance = 32; //Baseline chance if no level difference and no modifier
|
|
int level_diff = caster_level - buff_level;
|
|
|
|
if (level_diff > 0)
|
|
dispel_chance += level_diff * 7;
|
|
|
|
else if (level_diff < 0)
|
|
dispel_chance += level_diff * 2;
|
|
|
|
if (dispel_chance >= 100)
|
|
return true;
|
|
|
|
else if (dispel_chance < 10)
|
|
dispel_chance = 10;
|
|
|
|
if (zone->random.Roll(dispel_chance))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool Mob::ImprovedTaunt(){
|
|
|
|
if (spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_MAX_LVL]){
|
|
|
|
if (GetLevel() > spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_MAX_LVL])
|
|
return false;
|
|
|
|
if (spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_BUFFSLOT] >= 0){
|
|
|
|
target = entity_list.GetMob(buffs[spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_BUFFSLOT]].casterid);
|
|
|
|
if (target){
|
|
SetTarget(target);
|
|
return true;
|
|
}
|
|
else {
|
|
if(!TryFadeEffect(spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_BUFFSLOT]))
|
|
BuffFadeBySlot(spellbonuses.ImprovedTaunt[SBIndex::IMPROVED_TAUNT_BUFFSLOT], true); //If caster killed removed effect.
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool Mob::PassCastRestriction(int value)
|
|
{
|
|
/*
|
|
Restriction ID corresponds to the type 39 value in dstr_us on live clients (2021). See enum SpellRestriction for full list.
|
|
Modern client will give a message corresponding the type 39 field in the dstr_us for many of these effects upon failure.
|
|
|
|
Use with spell_news table field 'CastRestriction' which limits targets by restrictions below and 'caster_requirement' (field220)
|
|
which limits caster by restrictions below.
|
|
These restrictions also apply to direct damage,dot, heal spells using SPA 0 or SPA 79 by placing a restriction id in the LIMIT field.
|
|
|
|
Note: (ID 221 - 249) For effect seen in mage spell 'Shock of Many' which increases damage based on number of pets on targets hatelist. The way it is implemented
|
|
works for how our ROF2 spell file handles the effect where each slot fires individually, while on live it only takes the highest
|
|
value. In the future the way check is done will need to be adjusted to check a defined range instead of just great than.
|
|
*/
|
|
|
|
if (value <= 0) {
|
|
return true;
|
|
}
|
|
|
|
switch(value)
|
|
{
|
|
case 1:
|
|
return true;
|
|
break;
|
|
|
|
case IS_NOT_ON_HORSE:
|
|
if ((IsClient() && !CastToClient()->GetHorseId()) || IsBot() || IsMerc()) {
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case IS_ANIMAL_OR_HUMANOID:
|
|
if ((GetBodyType() == BT_Animal) || (GetBodyType() == BT_Humanoid))
|
|
return true;
|
|
break;
|
|
|
|
case IS_DRAGON:
|
|
if (GetBodyType() == BT_Dragon || GetBodyType() == BT_VeliousDragon || GetBodyType() == BT_Dragon3)
|
|
return true;
|
|
break;
|
|
|
|
case IS_ANIMAL_OR_INSECT:
|
|
if ((GetBodyType() == BT_Animal) || (GetBodyType() == BT_Insect))
|
|
return true;
|
|
break;
|
|
|
|
case IS_BODY_TYPE_MISC:
|
|
if ((GetBodyType() == BT_Humanoid) || (GetBodyType() == BT_Lycanthrope) || (GetBodyType() == BT_Giant) ||
|
|
(GetBodyType() == BT_RaidGiant) || (GetBodyType() == BT_RaidColdain) || (GetBodyType() == BT_Animal)||
|
|
(GetBodyType() == BT_Construct) || (GetBodyType() == BT_Dragon) || (GetBodyType() == BT_Insect)||
|
|
(GetBodyType() == BT_VeliousDragon) || (GetBodyType() == BT_Muramite) || (GetBodyType() == BT_Magical))
|
|
return true;
|
|
break;
|
|
|
|
case IS_BODY_TYPE_MISC2:
|
|
if ((GetBodyType() == BT_Humanoid) || (GetBodyType() == BT_Lycanthrope) || (GetBodyType() == BT_Giant) ||
|
|
(GetBodyType() == BT_RaidGiant) || (GetBodyType() == BT_RaidColdain) || (GetBodyType() == BT_Animal) ||
|
|
(GetBodyType() == BT_Insect))
|
|
return true;
|
|
break;
|
|
|
|
case IS_PLANT:
|
|
if (GetBodyType() == BT_Plant)
|
|
return true;
|
|
break;
|
|
|
|
case IS_GIANT:
|
|
if (GetBodyType() == BT_Giant)
|
|
return true;
|
|
break;
|
|
|
|
case IS_NOT_ANIMAL_OR_HUMANOID:
|
|
if ((GetBodyType() != BT_Animal) || (GetBodyType() != BT_Humanoid))
|
|
return true;
|
|
break;
|
|
|
|
case IS_BIXIE:
|
|
case IS_BIXIE2:
|
|
if ((GetRace() == RT_BIXIE) ||(GetRace() == RT_BIXIE_2))
|
|
return true;
|
|
break;
|
|
|
|
case IS_HARPY:
|
|
if ((GetRace() == RT_HARPY) ||(GetRace() == RT_HARPY_2))
|
|
return true;
|
|
break;
|
|
|
|
case IS_GNOLL:
|
|
if ((GetRace() == RT_GNOLL) || (GetRace() == RT_GNOLL_2) || (GetRace() == RT_GNOLL_3))
|
|
return true;
|
|
break;
|
|
|
|
case IS_SPORALI:
|
|
if ((GetRace() == RT_SPORALI) ||(GetRace() == RT_FUNGUSMAN))
|
|
return true;
|
|
break;
|
|
|
|
case IS_KOBOLD:
|
|
if ((GetRace() == RT_KOBOLD) ||(GetRace() == RT_KOBOLD_2))
|
|
return true;
|
|
break;
|
|
|
|
case IS_FROSTCRYPT_SHADE:
|
|
if (GetRace() == RT_GIANT_SHADE)
|
|
return true;
|
|
break;
|
|
|
|
case IS_DRAKKIN:
|
|
if (GetRace() == RT_DRAKKIN)
|
|
return true;
|
|
break;
|
|
|
|
case IS_UNDEAD_OR_VALDEHOLM_GIANT:
|
|
if (GetBodyType() == BT_Undead || GetRace() == RT_GIANT_12 || GetRace() == RT_GIANT_13)
|
|
return true;
|
|
break;
|
|
|
|
case IS_ANIMAL_OR_PLANT:
|
|
if ((GetBodyType() == BT_Animal) || (GetBodyType() == BT_Plant))
|
|
return true;
|
|
break;
|
|
|
|
case IS_SUMMONED:
|
|
if (GetBodyType() == BT_Summoned)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_WIZARD:
|
|
case IS_WIZARD_USED_ON_MAGE_FIRE_PET:
|
|
if (GetClass() == WIZARD)
|
|
return true;
|
|
break;
|
|
|
|
case IS_UNDEAD:
|
|
if (GetBodyType() == BT_Undead)
|
|
return true;
|
|
break;
|
|
|
|
case IS_NOT_UNDEAD_OR_SUMMONED_OR_VAMPIRE:
|
|
if ((GetBodyType() != BT_Undead) && (GetBodyType() != BT_Summoned) && (GetBodyType() != BT_Vampire))
|
|
return true;
|
|
break;
|
|
|
|
case IS_FAE_OR_PIXIE:
|
|
if ((GetRace() == RT_PIXIE) || (GetRace() == RT_FAY_DRAKE))
|
|
return true;
|
|
break;
|
|
|
|
case IS_HUMANOID:
|
|
if (GetBodyType() == BT_Humanoid)
|
|
return true;
|
|
break;
|
|
|
|
case IS_UNDEAD_AND_HP_LESS_THAN_10_PCT:
|
|
if ((GetBodyType() == BT_Undead) && (GetHPRatio() < 10))
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLOCKWORK_AND_HP_LESS_THAN_45_PCT:
|
|
if ((GetRace() == RT_GNOMEWORK || GetRace() == RACE_CLOCKWORK_GNOME_88) && (GetHPRatio() < 45))
|
|
return true;
|
|
break;
|
|
|
|
case IS_WISP_AND_HP_LESS_THAN_10_PCT:
|
|
if ((GetRace() == RT_WILL_O_WISP) && (GetHPRatio() < 10))
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_MELEE_THAT_CAN_BASH_OR_KICK_EXCEPT_BARD:
|
|
if ((GetClass() != BARD) && (GetClass() != ROGUE) && IsFighterClass(GetClass()))
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_PURE_MELEE:
|
|
if (GetClass() == ROGUE || GetClass() == WARRIOR || GetClass() == BERSERKER || GetClass() == MONK)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_PURE_CASTER:
|
|
if (IsINTCasterClass(GetClass()))
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_HYBRID_CLASS:
|
|
if (IsHybridClass(GetClass()))
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_WARRIOR:
|
|
if (GetClass() == WARRIOR)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_CLERIC:
|
|
if (GetClass() == CLERIC)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_PALADIN:
|
|
if (GetClass() == PALADIN)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_RANGER:
|
|
if (GetClass() == RANGER)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_SHADOWKNIGHT:
|
|
if (GetClass() == SHADOWKNIGHT)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_DRUID:
|
|
if (GetClass() == DRUID)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_MONK:
|
|
if (GetClass() == MONK)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_BARD2:
|
|
case IS_CLASS_BARD:
|
|
if (GetClass() == BARD)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_ROGUE:
|
|
if (GetClass() == ROGUE)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_SHAMAN:
|
|
if (GetClass() == SHAMAN)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_NECRO:
|
|
if (GetClass() == NECROMANCER)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_MAGE:
|
|
if (GetClass() == MAGICIAN)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_ENCHANTER:
|
|
if (GetClass() == ENCHANTER)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_BEASTLORD:
|
|
if (GetClass() == BEASTLORD)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_BERSERKER:
|
|
if (GetClass() == BERSERKER)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_CLR_SHM_DRU:
|
|
if (IsWISCasterClass(GetClass()))
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_NOT_WAR_PAL_SK:
|
|
if ((GetClass() != WARRIOR) && (GetClass() != PALADIN) && (GetClass() != SHADOWKNIGHT))
|
|
return true;
|
|
break;
|
|
|
|
case IS_LEVEL_UNDER_100:
|
|
if (GetLevel() < 100)
|
|
return true;
|
|
break;
|
|
|
|
case IS_NOT_RAID_BOSS:
|
|
if (!IsRaidTarget())
|
|
return true;
|
|
break;
|
|
|
|
case IS_RAID_BOSS:
|
|
if (IsRaidTarget())
|
|
return true;
|
|
break;
|
|
|
|
case FRENZIED_BURNOUT_ACTIVE:
|
|
if (HasBuffWithSpellGroup(SPELLGROUP_FRENZIED_BURNOUT))
|
|
return true;
|
|
break;
|
|
|
|
case FRENZIED_BURNOUT_NOT_ACTIVE:
|
|
if (!HasBuffWithSpellGroup(SPELLGROUP_FRENZIED_BURNOUT))
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_ABOVE_75_PCT:
|
|
if (GetHPRatio() > 75)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_LESS_THAN_20_PCT:
|
|
if (GetHPRatio() <= 20)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_LESS_THAN_50_PCT:
|
|
if (GetHPRatio() <= 50)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_LESS_THAN_75_PCT:
|
|
if (GetHPRatio() <= 75)
|
|
return true;
|
|
break;
|
|
|
|
case IS_NOT_IN_COMBAT:
|
|
if (!IsEngaged())
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_LESS_THAN_35_PCT:
|
|
if (GetHPRatio() <= 35)
|
|
return true;
|
|
break;
|
|
|
|
case HAS_BETWEEN_1_TO_2_PETS_ON_HATELIST: {
|
|
int count = hate_list.GetSummonedPetCountOnHateList();
|
|
if (count >= 1 && count <= 2) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case HAS_BETWEEN_3_TO_5_PETS_ON_HATELIST: {
|
|
int count = hate_list.GetSummonedPetCountOnHateList();
|
|
if (count >= 3 && count <= 5) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case HAS_BETWEEN_6_TO_9_PETS_ON_HATELIST: {
|
|
int count = hate_list.GetSummonedPetCountOnHateList();
|
|
if (count >= 6 && count <= 9) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case HAS_BETWEEN_10_TO_14_PETS_ON_HATELIST: {
|
|
int count = hate_list.GetSummonedPetCountOnHateList();
|
|
if (count >= 10 && count <= 14) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case HAS_MORE_THAN_14_PETS_ON_HATELIST: {
|
|
int count = hate_list.GetSummonedPetCountOnHateList();
|
|
if (count > 14) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IS_CLASS_CHAIN_OR_PLATE:
|
|
if ((GetClass() == WARRIOR) || (GetClass() == BARD) || (GetClass() == SHADOWKNIGHT) || (GetClass() == PALADIN) || (GetClass() == CLERIC)
|
|
|| (GetClass() == RANGER) || (GetClass() == SHAMAN) || (GetClass() == ROGUE) || (GetClass() == BERSERKER)) {
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_5_AND_9_PCT:
|
|
if (GetHPRatio() >= 5 && GetHPRatio() <= 9)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_10_AND_14_PCT:
|
|
if (GetHPRatio() >= 10 && GetHPRatio() <= 14)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_15_AND_19_PCT:
|
|
if (GetHPRatio() >= 15 && GetHPRatio() <= 19)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_20_AND_24_PCT:
|
|
if (GetHPRatio() >= 20 && GetHPRatio() <= 24)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_25_AND_29_PCT:
|
|
if (GetHPRatio() >= 25 && GetHPRatio() <= 29)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_30_AND_34_PCT:
|
|
if (GetHPRatio() >= 30 && GetHPRatio() <= 34)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_35_AND_39_PCT:
|
|
if (GetHPRatio() >= 35 && GetHPRatio() <= 39)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_40_AND_44_PCT:
|
|
if (GetHPRatio() >= 40 && GetHPRatio() <= 44)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_45_AND_49_PCT:
|
|
if (GetHPRatio() >= 45 && GetHPRatio() <= 49)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_50_AND_54_PCT:
|
|
if (GetHPRatio() >= 50 && GetHPRatio() <= 54)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_55_AND_59_PCT:
|
|
if (GetHPRatio() >= 55 && GetHPRatio() <= 59)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_5_AND_15_PCT:
|
|
if (GetHPRatio() >= 5 && GetHPRatio() <= 15)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_15_AND_25_PCT:
|
|
if (GetHPRatio() >= 15 && GetHPRatio() <= 25)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_1_AND_25_PCT:
|
|
if (GetHPRatio() <= 25)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_25_AND_35_PCT:
|
|
if (GetHPRatio() > 25 && GetHPRatio() <= 35)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_35_AND_45_PCT:
|
|
if (GetHPRatio() > 35 && GetHPRatio() <= 45)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_45_AND_55_PCT:
|
|
if (GetHPRatio() > 45 && GetHPRatio() <= 55)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_55_AND_65_PCT:
|
|
if (GetHPRatio() > 55 && GetHPRatio() <= 65)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_65_AND_75_PCT:
|
|
if (GetHPRatio() > 65 && GetHPRatio() <= 75)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_75_AND_85_PCT:
|
|
if (GetHPRatio() > 75 && GetHPRatio() <= 85)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BETWEEN_85_AND_95_PCT:
|
|
if (GetHPRatio() > 85 && GetHPRatio() <= 95)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_ABOVE_45_PCT:
|
|
if (GetHPRatio() > 45)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_ABOVE_55_PCT:
|
|
if (GetHPRatio() > 55)
|
|
return true;
|
|
break;
|
|
|
|
case IS_MANA_ABOVE_10_PCT:
|
|
if (GetManaRatio() > 10)
|
|
return true;
|
|
break;
|
|
|
|
case IS_ENDURANCE_BELOW_40_PCT:
|
|
if (IsClient() && CastToClient()->GetEndurancePercent() <= 40)
|
|
return true;
|
|
break;
|
|
|
|
case IS_MANA_BELOW_40_PCT:
|
|
if (GetManaRatio() <= 40)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_ABOVE_20_PCT:
|
|
if (GetHPRatio() > 20)
|
|
return true;
|
|
break;
|
|
|
|
case IS_NOT_UNDEAD_OR_SUMMONED:
|
|
if ((GetBodyType() != BT_Undead) && (GetBodyType() != BT_Summoned))
|
|
return true;
|
|
break;
|
|
|
|
case IS_NOT_PLANT:
|
|
if (GetBodyType() != BT_Plant)
|
|
return true;
|
|
break;
|
|
|
|
case IS_NOT_CLIENT:
|
|
if (!IsClient())
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLIENT:
|
|
if (IsClient())
|
|
return true;
|
|
break;
|
|
|
|
case IS_LEVEL_ABOVE_42_AND_IS_CLIENT:
|
|
if (IsClient() && GetLevel() > 42)
|
|
return true;
|
|
break;
|
|
|
|
case IS_TREANT:
|
|
if (GetRace() == RT_TREANT || GetRace() == RT_TREANT_2 || GetRace() == RT_TREANT_3)
|
|
return true;
|
|
break;
|
|
|
|
case IS_SCARECROW:
|
|
if (GetRace() == RT_SCARECROW || GetRace() == RT_SCARECROW_2)
|
|
return true;
|
|
break;
|
|
|
|
case IS_VAMPIRE_OR_UNDEAD_OR_UNDEADPET:
|
|
if (GetBodyType() == BT_Vampire || GetBodyType() == BT_Undead || GetBodyType() == BT_SummonedUndead)
|
|
return true;
|
|
break;
|
|
|
|
case IS_NOT_VAMPIRE_OR_UNDEAD:
|
|
if (GetBodyType() != BT_Vampire && GetBodyType() != BT_Undead && GetBodyType() != BT_SummonedUndead)
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_KNIGHT_HYBRID_MELEE:
|
|
if (IsHybridClass(GetClass()) || IsNonSpellFighterClass(GetClass()))
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLASS_WARRIOR_CASTER_PRIEST:
|
|
if (IsCasterClass(GetClass()) || GetClass() == WARRIOR)
|
|
return true;
|
|
break;
|
|
|
|
case IS_END_BELOW_21_PCT:
|
|
if (IsClient() && CastToClient()->GetEndurancePercent() <= 21)
|
|
return true;
|
|
break;
|
|
|
|
case IS_END_BELOW_25_PCT:
|
|
if (IsClient() && CastToClient()->GetEndurancePercent() <= 25)
|
|
return true;
|
|
break;
|
|
|
|
case IS_END_BELOW_29_PCT:
|
|
if (IsClient() && CastToClient()->GetEndurancePercent() <= 29)
|
|
return true;
|
|
break;
|
|
|
|
case IS_REGULAR_SERVER:
|
|
return true; // todo implement progression flag assume not progression for now
|
|
break;
|
|
|
|
case IS_PROGRESSION_SERVER:
|
|
return false; // todo implement progression flag assume not progression for now
|
|
break;
|
|
|
|
case IS_GOD_EXPANSION_UNLOCKED:
|
|
return true; // todo implement progression flag assume not progression for now, this one is a check if GoD is live
|
|
break;
|
|
|
|
case IS_HUMANOID_LEVEL_84_MAX:
|
|
if (GetBodyType() == BT_Humanoid && GetLevel() <= 84)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HUMANOID_LEVEL_86_MAX:
|
|
if (GetBodyType() == BT_Humanoid && GetLevel() <= 86)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HUMANOID_LEVEL_88_MAX:
|
|
if (GetBodyType() == BT_Humanoid && GetLevel() <= 88)
|
|
return true;
|
|
break;
|
|
|
|
case IS_LEVEL_90_MAX:
|
|
if (GetLevel() <= 90)
|
|
return true;
|
|
break;
|
|
|
|
case IS_LEVEL_92_MAX:
|
|
if (GetLevel() <= 92)
|
|
return true;
|
|
break;
|
|
|
|
case IS_LEVEL_94_MAX:
|
|
if (GetLevel() <= 94)
|
|
return true;
|
|
break;
|
|
|
|
case IS_LEVEL_95_MAX:
|
|
if (GetLevel() <= 95)
|
|
return true;
|
|
break;
|
|
|
|
case IS_LEVEL_97_MAX:
|
|
if (GetLevel() <= 97)
|
|
return true;
|
|
break;
|
|
|
|
case IS_LEVEL_99_MAX:
|
|
if (GetLevel() <= 99)
|
|
return true;
|
|
break;
|
|
|
|
case IS_LEVEL_100_MAX:
|
|
if (GetLevel() <= 100)
|
|
return true;
|
|
break;
|
|
|
|
case IS_LEVEL_102_MAX:
|
|
if (GetLevel() <= 102)
|
|
return true;
|
|
break;
|
|
|
|
case IS_LEVEL_104_MAX:
|
|
if (GetLevel() <= 104)
|
|
return true;
|
|
break;
|
|
case IS_LEVEL_105_MAX:
|
|
if (GetLevel() <= 105)
|
|
return true;
|
|
break;
|
|
|
|
case IS_LEVEL_107_MAX:
|
|
if (GetLevel() <= 107)
|
|
return true;
|
|
break;
|
|
|
|
case IS_LEVEL_109_MAX:
|
|
if (GetLevel() <= 109)
|
|
return true;
|
|
break;
|
|
case IS_LEVEL_110_MAX:
|
|
if (GetLevel() <= 110)
|
|
return true;
|
|
break;
|
|
|
|
case IS_LEVEL_112_MAX:
|
|
if (GetLevel() <= 112)
|
|
return true;
|
|
break;
|
|
|
|
case IS_LEVEL_114_MAX:
|
|
if (GetLevel() <= 114)
|
|
return true;
|
|
break;
|
|
|
|
case IS_BETWEEN_LEVEL_1_AND_75:
|
|
if (GetLevel() >= 1 && GetLevel() <= 75)
|
|
return true;
|
|
break;
|
|
|
|
case IS_BETWEEN_LEVEL_76_AND_85:
|
|
if (GetLevel() >= 76 && GetLevel() <= 85)
|
|
return true;
|
|
break;
|
|
|
|
case IS_BETWEEN_LEVEL_86_AND_95:
|
|
if (GetLevel() >= 86 && GetLevel() <= 95)
|
|
return true;
|
|
break;
|
|
|
|
case IS_BETWEEN_LEVEL_96_AND_105:
|
|
if (GetLevel() >= 96 && GetLevel() <= 105)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_LESS_THAN_80_PCT:
|
|
if (GetHPRatio() < 80)
|
|
return true;
|
|
break;
|
|
|
|
case IS_LEVEL_ABOVE_34:
|
|
if (GetLevel() < 34)
|
|
return true;
|
|
break;
|
|
|
|
case HAS_NO_MANA_BURN_BUFF: {
|
|
bool has_effect = false;
|
|
for (int i = 0; i < GetMaxTotalSlots(); i++) {
|
|
if (IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, SE_ManaBurn)) {
|
|
has_effect = true;
|
|
}
|
|
}
|
|
if (!has_effect) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IS_CLIENT_AND_MALE_PLATE_USER:
|
|
if (IsClient() && GetGender() == MALE && IsPlateClass(GetClass()))
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLEINT_AND_MALE_DRUID_ENCHANTER_MAGICIAN_NECROANCER_SHAMAN_OR_WIZARD:
|
|
if (IsClient() && GetGender() == MALE && (IsCasterClass(GetClass()) && GetClass() != CLERIC))
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLIENT_AND_MALE_BEASTLORD_BERSERKER_MONK_RANGER_OR_ROGUE:
|
|
if (IsClient() && GetGender() == MALE &&
|
|
(GetClass() == BEASTLORD || GetClass() == BERSERKER || GetClass() == MONK || GetClass() == RANGER || GetClass() == ROGUE))
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLIENT_AND_FEMALE_PLATE_USER:
|
|
if (IsClient() && GetGender() == FEMALE && IsPlateClass(GetClass()))
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLIENT_AND_FEMALE_DRUID_ENCHANTER_MAGICIAN_NECROANCER_SHAMAN_OR_WIZARD:
|
|
if (IsClient() && GetGender() == FEMALE && (IsCasterClass(GetClass()) && GetClass() != CLERIC))
|
|
return true;
|
|
break;
|
|
|
|
case IS_CLIENT_AND_FEMALE_BEASTLORD_BERSERKER_MONK_RANGER_OR_ROGUE:
|
|
if (IsClient() && GetGender() == FEMALE &&
|
|
(GetClass() == BEASTLORD || GetClass() == BERSERKER || GetClass() == MONK || GetClass() == RANGER || GetClass() == ROGUE))
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_ABOVE_50_PCT:
|
|
if (GetHPRatio() > 50)
|
|
return true;
|
|
break;
|
|
|
|
case IS_HP_BELOW_50_PCT:
|
|
if (GetHPRatio() <= 50)
|
|
return true;
|
|
break;
|
|
|
|
case IS_OFF_HAND_EQUIPED:
|
|
if (HasShieldEquiped() || CanThisClassDualWield())
|
|
return true;
|
|
break;
|
|
|
|
case IS_MANA_BELOW_20_PCT:
|
|
if (GetManaRatio() <= 20)
|
|
return true;
|
|
break;
|
|
|
|
case IS_MANA_ABOVE_50_PCT:
|
|
if (GetManaRatio() >= 50)
|
|
return true;
|
|
break;
|
|
|
|
case IS_SUMMONED_OR_UNDEAD:
|
|
if (GetBodyType() == BT_Summoned || GetBodyType() == BT_Undead)
|
|
return true;
|
|
break;
|
|
|
|
|
|
case IS_CLASS_CASTER_PRIEST:
|
|
if (IsCasterClass(GetClass()))
|
|
return true;
|
|
break;
|
|
|
|
case IS_END_OR_MANA_ABOVE_20_PCT: {
|
|
if (IsNonSpellFighterClass(GetClass()) && CastToClient()->GetEndurancePercent() >= 20) {
|
|
return true;
|
|
}
|
|
else if (!IsNonSpellFighterClass(GetClass()) && GetManaRatio() >= 20) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case IS_END_OR_MANA_BELOW_10_PCT: {
|
|
if (IsNonSpellFighterClass(GetClass()) && CastToClient()->GetEndurancePercent() <= 10) {
|
|
return true;
|
|
}
|
|
else if (!IsNonSpellFighterClass(GetClass()) && GetManaRatio() <= 10) {
|
|
return true;
|
|
}
|
|
else if (IsHybridClass(GetClass()) && CastToClient()->GetEndurancePercent() <= 10) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case IS_END_OR_MANA_BELOW_30_PCT:
|
|
case IS_END_OR_MANA_BELOW_30_PCT2: {
|
|
if (IsNonSpellFighterClass(GetClass()) && CastToClient()->GetEndurancePercent() <= 30) {
|
|
return true;
|
|
}
|
|
else if (!IsNonSpellFighterClass(GetClass()) && GetManaRatio() <= 30) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IS_NOT_CLASS_BARD:
|
|
if (GetClass() != BARD)
|
|
return true;
|
|
break;
|
|
|
|
case HAS_NO_PACT_OF_FATE_RECOURSE_BUFF:
|
|
if (!FindBuff(SPELL_PACT_OF_HATE_RECOURSE))
|
|
return true;
|
|
break;
|
|
|
|
case HAS_NO_ROGUES_FURY_BUFF:
|
|
if (!HasBuffWithSpellGroup(SPELLGROUP_ROGUES_FURY))
|
|
return true;
|
|
break;
|
|
|
|
case HAS_NO_ILLUSIONS_OF_GRANDEUR_BUFF:
|
|
if (!HasBuffWithSpellGroup(SPELLGROUP_ILLUSION_OF_GRANDEUR))
|
|
return true;
|
|
break;
|
|
|
|
case HAS_NO_HARMONIOUS_PRECISION_BUFF:
|
|
if (!HasBuffWithSpellGroup(SPELLGROUP_HARMONIOUS_PRECISION))
|
|
return true;
|
|
break;
|
|
|
|
case HAS_NO_HARMONIOUS_EXPANSE_BUFF:
|
|
if (!HasBuffWithSpellGroup(SPELLGROUP_HARMONIOUS_EXPANSE))
|
|
return true;
|
|
break;
|
|
|
|
case HAS_NO_FURIOUS_RAMPAGE_BUFF:
|
|
if (!HasBuffWithSpellGroup(SPELLGROUP_FURIOUS_RAMPAGE))
|
|
return true;
|
|
break;
|
|
|
|
case HAS_NO_SHROUD_OF_PRAYER_BUFF:
|
|
if (!HasBuffWithSpellGroup(SPELLGROUP_SHROUD_OF_PRAYER))
|
|
return true;
|
|
break;
|
|
|
|
case HAS_INCENDIARY_OOZE_BUFF:
|
|
if (FindBuff(SPELL_INCENDIARY_OOZE_BUFF))
|
|
return true;
|
|
break;
|
|
|
|
//Not handled, just allow them to pass for now.
|
|
case UNKNOWN_3:
|
|
case HAS_CRYSTALLIZED_FLAME_BUFF:
|
|
case UNKNOWN_199:
|
|
case UNKNOWN_TOO_MUCH_HP_410:
|
|
case UNKNOWN_TOO_MUCH_HP_411:
|
|
case HAS_TBL_ESIANTI_ACCESS:
|
|
case HAS_ITEM_CLOCKWORK_SCRAPS:
|
|
case IN_TWO_HANDED_STANCE:
|
|
case IN_DUAL_WIELD_HANDED_STANCE:
|
|
case IN_SHIELD_STANCE:
|
|
case NOT_IN_TWO_HANDED_STANCE:
|
|
case NOT_IN_DUAL_WIELD_HANDED_STANCE:
|
|
case NOT_IN_SHIELD_STANCE:
|
|
case DISABLED_UNTIL_EXPANSION_ROK:
|
|
case DISABLED_UNTIL_EXPANSION_SOV:
|
|
case DISABLED_UNTIL_EXPANSION_SOL:
|
|
case DISABLED_UNTIL_EXPANSION_POP:
|
|
case DISABLED_UNTIL_EXPANSION_LOY:
|
|
case DISABLED_UNTIL_EXPANSION_LDON:
|
|
case DISABLED_UNTIL_EXPANSION_GOD:
|
|
case DISABLED_UNTIL_EXPANSION_OOW:
|
|
case DISABLED_UNTIL_EXPANSION_DON:
|
|
case DISABLED_UNTIL_EXPANSION_DOD:
|
|
case DISABLED_UNTIL_EXPANSION_POR:
|
|
case DISABLED_UNTIL_EXPANSION_TSS:
|
|
case DISABLED_UNTIL_EXPANSION_TBS:
|
|
case DISABLED_UNTIL_EXPANSION_SOF:
|
|
case DISABLED_UNTIL_EXPANSION_SOD:
|
|
case DISABLED_UNTIL_EXPANSION_UF:
|
|
case DISABLED_UNTIL_EXPANSION_HOT:
|
|
case DISABLED_UNTIL_EXPANSION_VOA:
|
|
case DISABLED_UNTIL_EXPANSION_ROF:
|
|
case DISABLED_UNTIL_EXPANSION_COF:
|
|
case DISABLED_UNTIL_EXPANSION_TDS:
|
|
case DISABLED_UNTIL_EXPANSION_TBM:
|
|
case DISABLED_UNTIL_EXPANSION_EOK:
|
|
case DISABLED_UNTIL_EXPANSION_ROS:
|
|
case DISABLED_UNTIL_EXPANSION_TBL:
|
|
case DISABLED_UNTIL_EXPANSION_TOV:
|
|
case DISABLED_UNTIL_EXPANSION_COV:
|
|
case HAS_TRAVELED_TO_STRATOS:
|
|
case HAS_TRAVELED_TO_AALISHAI:
|
|
case HAS_TRAVELED_TO_MEARATS:
|
|
case COMPLETED_ACHIEVEMENT_LEGENDARY_ANSWERER:
|
|
case NOT_COMPLETED_ACHIEVEMENT_LEGENDARY_ANSWERER:
|
|
case HAS_WEAPONSTANCE_DEFENSIVE_PROFICIENCY:
|
|
case HAS_WEAPONSTANCE_TWO_HAND_PROFICIENCY:
|
|
case HAS_WEAPONSTANCE_DUAL_WEILD_PROFICIENCY:
|
|
case UNKNOWN_812:
|
|
case UNKNOWN_814:
|
|
case UNKNOWN_822:
|
|
case UNKNOWN_840:
|
|
case UNKNOWN_841:
|
|
case UNKNOWN_99999:
|
|
return true;
|
|
break;
|
|
}
|
|
|
|
if (value >= HAS_AT_LEAST_1_PET_ON_HATELIST && value <= HAS_AT_LEAST_20_PETS_ON_HATELIST) {
|
|
int count = hate_list.GetSummonedPetCountOnHateList();
|
|
int minium_amount_of_pets_needed = (1 + value) - HAS_AT_LEAST_1_PET_ON_HATELIST;
|
|
|
|
if (count >= minium_amount_of_pets_needed) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (value >= IS_HP_BELOW_5_PCT && value <= IS_HP_BELOW_95_PCT) {
|
|
int hp_below_amt = 5 * ((1 + value) - IS_HP_BELOW_5_PCT);
|
|
if (GetHPRatio() <= hp_below_amt) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (value >= IS_BODY_TYPE_UNDEFINED && value <= IS_BODY_TYPE_MURAMITE){
|
|
if (GetBodyType() == (value - IS_BODY_TYPE_UNDEFINED))
|
|
return true;
|
|
}
|
|
|
|
//Limit to Race. *Not implemented on live, too much potential not to give an option here.
|
|
if (value >= IS_RACE_FIRST_CUSTOM && value <= IS_RACE_LAST_CUSTOM){
|
|
if (GetRace() == (value - IS_RACE_FIRST_CUSTOM))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Mob::SendCastRestrictionMessage(int requirement_id, bool target_requirement, bool is_discipline) {
|
|
|
|
if (!IsClient()) {
|
|
return;
|
|
}
|
|
/*
|
|
Most of these are the live messages that modern clients give. Current live dbstr_us type 39
|
|
Having these messages display greatly enhances the useabllity of these fields. (CastRestriction=target, caster_requirement_id=caster)
|
|
If target_requirement is false then use the caster requirement message.
|
|
Added support for different messages for certain high yield restrictions based on if
|
|
target or caster requirements.
|
|
|
|
*/
|
|
|
|
std::string msg = "";
|
|
|
|
if (target_requirement) {
|
|
msg = "Your target does not meet the spell requirements. ";
|
|
}
|
|
else {
|
|
if (is_discipline) {
|
|
msg = "Your combat ability is interrupted. ";
|
|
}
|
|
else {
|
|
msg = "Your spell is interrupted. ";
|
|
}
|
|
}
|
|
|
|
switch (requirement_id)
|
|
{
|
|
case IS_ANIMAL_OR_HUMANOID:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on animals or humanoid creatures.", msg).c_str());
|
|
break;
|
|
case IS_DRAGON:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on dragons.", msg).c_str());
|
|
break;
|
|
case IS_ANIMAL_OR_INSECT:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on animals or insects.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_MISC:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on humanoids, lycanthropes, giants, Kael Drakkel giants, Coldain, animals, insects, constructs, dragons, Skyshrine dragons, Muramites, or creatures constructed from magic.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_MISC2:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on humanoids, lycanthropes, giants, Kael Drakkel giants, Coldain, animals, or insects.", msg).c_str());
|
|
break;
|
|
case IS_PLANT:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on plants.", msg).c_str());
|
|
break;
|
|
case IS_GIANT:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on animals.", msg).c_str());
|
|
break;
|
|
case IS_NOT_ANIMAL_OR_HUMANOID:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on targets that are neither animals or humanoid.", msg).c_str());
|
|
break;
|
|
case IS_BIXIE:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on bixies.", msg).c_str());
|
|
break;
|
|
case IS_HARPY:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on harpies.", msg).c_str());
|
|
break;
|
|
case IS_GNOLL:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on gnolls.", msg).c_str());
|
|
break;
|
|
case IS_SPORALI:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on fungusoids.", msg).c_str());
|
|
break;
|
|
case IS_KOBOLD:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on kobolds.", msg).c_str());
|
|
break;
|
|
case IS_FROSTCRYPT_SHADE:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on undead creatures or the Shades of Frostcrypt.", msg).c_str());
|
|
break;
|
|
case IS_DRAKKIN:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on Drakkin.", msg).c_str());
|
|
break;
|
|
case IS_UNDEAD_OR_VALDEHOLM_GIANT:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on undead creatures or the inhabitants of Valdeholm.", msg).c_str());
|
|
break;
|
|
case IS_ANIMAL_OR_PLANT:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on plants or animals.", msg).c_str());
|
|
break;
|
|
case IS_SUMMONED:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on constructs, elementals, or summoned elemental minions.", msg).c_str());
|
|
break;
|
|
case IS_WIZARD_USED_ON_MAGE_FIRE_PET:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on wizards.", msg).c_str());
|
|
break;
|
|
case IS_UNDEAD:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on undead.", msg).c_str());
|
|
break;
|
|
case IS_NOT_UNDEAD_OR_SUMMONED_OR_VAMPIRE:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on creatures that are not undead, constructs, elementals, or vampires.", msg).c_str());
|
|
break;
|
|
case IS_FAE_OR_PIXIE:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on Fae or pixies.", msg).c_str());
|
|
break;
|
|
case IS_HUMANOID:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on humanoids.", msg).c_str());
|
|
break;
|
|
case IS_UNDEAD_AND_HP_LESS_THAN_10_PCT:
|
|
Message(Chat::Red, fmt::format("{} The Essence Extractor whirrs but does not light up.", msg).c_str());
|
|
break;
|
|
case IS_CLOCKWORK_AND_HP_LESS_THAN_45_PCT:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on clockwork gnomes.", msg).c_str());
|
|
break;
|
|
case IS_WISP_AND_HP_LESS_THAN_10_PCT:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on wisps at or below 10 pct of their maximum HP.", msg).c_str());
|
|
break;
|
|
case IS_CLASS_MELEE_THAT_CAN_BASH_OR_KICK_EXCEPT_BARD:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on non-bard targets that can bash or kick.", msg).c_str());
|
|
break;
|
|
case IS_CLASS_PURE_MELEE:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect melee classes (warriors, monks, rogues, and berserkers).", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by melee classes (warriors, monks, rogues, and berserkers).", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_PURE_CASTER:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect pure caster classes (necromancers, wizards, magicians, and enchanters).", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by pure caster classes (necromancers, wizards, magicians, and enchanters).", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_HYBRID_CLASS:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect hybrid classes (paladins, rangers, shadow knights, bards, and beastlords).", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by hybrid classes (paladins, rangers, shadow knights, bards, and beastlords).", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_WARRIOR:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect Warriors.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Warriors.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_CLERIC:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect Clerics.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Clerics.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_PALADIN:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect Paladins.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Paladins.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_RANGER:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect Warriors.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Warriors.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_SHADOWKNIGHT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect Shadow Knights.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Shadow Knights.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_DRUID:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect Druids.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Druids.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_MONK:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect Monks.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Monks.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_BARD:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect Bards.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Bards..", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_ROGUE:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect Rogues.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Rogues.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_SHAMAN:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect Shamans.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Shamans.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_NECRO:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect Necromancers.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Necromancers.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_WIZARD:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect Wizards.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Wizards.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_MAGE:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect Magicians.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Magicians.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_ENCHANTER:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect Enchanters.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Enchanters.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_BEASTLORD:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect Beastlords.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Beastlords.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_BERSERKER:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect Berserkers.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Berserkers.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_CLR_SHM_DRU:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect priest classes (clerics, druids, and shaman).", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by priest classes (clerics, druids, and shaman).", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_CLASS_NOT_WAR_PAL_SK:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will not affect Warriors, Paladins, or Shadow Knights.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Warriors, Paladins, or Shadow Knights.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_LEVEL_UNDER_100:
|
|
Message(Chat::Red, fmt::format("{} This spell will not affect any target over level 100.", msg).c_str());
|
|
break;
|
|
case IS_NOT_RAID_BOSS:
|
|
Message(Chat::Red, fmt::format("{} This spell will not affect raid bosses.", msg).c_str());
|
|
break;
|
|
case IS_RAID_BOSS:
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect raid bosses.", msg).c_str());
|
|
break;
|
|
case FRENZIED_BURNOUT_ACTIVE:
|
|
Message(Chat::Red, fmt::format("{} This spell will only cast if you have Frenzied Burnout active.", msg).c_str());
|
|
break;
|
|
case FRENZIED_BURNOUT_NOT_ACTIVE:
|
|
Message(Chat::Red, fmt::format("{} This spell will only cast if you do not have Frenzied Burnout active.", msg).c_str());
|
|
break;
|
|
case IS_HP_ABOVE_75_PCT:
|
|
Message(Chat::Red, fmt::format("{} Your taret's HP must be at or above 75 pct of its maximum.", msg).c_str());
|
|
break;
|
|
case IS_HP_LESS_THAN_20_PCT:
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 20 pct of its maximum or below.", msg).c_str());
|
|
break;
|
|
case IS_HP_LESS_THAN_50_PCT:
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 50 pct of its maximum or below.", msg).c_str());
|
|
break;
|
|
case IS_HP_LESS_THAN_75_PCT:
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 75 pct of its maximum or below.", msg).c_str());
|
|
break;
|
|
case IS_NOT_IN_COMBAT:
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect creatures that are not in combat.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_1_PET_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 1 pet on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_2_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 2 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_3_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 3 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_4_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 4 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_5_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 5 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_6_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 6 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_7_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 7 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_8_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 8 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_9_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 9 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_10_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 10 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_11_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 11 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_12_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 12 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_13_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 13 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_14_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 14 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_15_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 15 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_16_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 16 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_17_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 17 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_18_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 18 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_19_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 19 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_AT_LEAST_20_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have least 20 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case IS_HP_LESS_THAN_35_PCT:
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 35 pct of its maximum or below.", msg).c_str());
|
|
break;
|
|
case HAS_BETWEEN_1_TO_2_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have between 1 and 2 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_BETWEEN_3_TO_5_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have between 3 and 5 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_BETWEEN_6_TO_9_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have between 6 and 9 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_BETWEEN_10_TO_14_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have between 10 and 14 pets on their hate list.", msg).c_str());
|
|
break;
|
|
case HAS_MORE_THAN_14_PETS_ON_HATELIST:
|
|
Message(Chat::Red, fmt::format("{} Your target must have between 15 or more pets on their hate list.", msg).c_str());
|
|
break;
|
|
case IS_CLASS_CHAIN_OR_PLATE:
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect plate or chain wearing classes.", msg).c_str());
|
|
break;
|
|
case IS_HP_BETWEEN_5_AND_9_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 5 pct and 9 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 5 pct and 9 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_10_AND_14_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 10 pct and 14 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 10 pct and 14 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_15_AND_19_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 15 pct and 19 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 15 pct and 19 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_20_AND_24_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 20 pct and 54 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 20 pct and 24 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_25_AND_29_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 25 pct and 29 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 25 pct and 29 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_30_AND_34_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 30 pct and 34 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 30 pct and 34 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_35_AND_39_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 35 pct and 39 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 35 pct and 39 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_40_AND_44_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 40 pct and 44 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 40 pct and 44 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_45_AND_49_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 45 pct and 49 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 45 pct and 49 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_50_AND_54_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 50 pct and 54 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 50 pct and 54 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_55_AND_59_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 55 pct and 59 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 55 pct and 59 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_5_AND_15_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 5 pct and 15 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 5 pct and 15 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_15_AND_25_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 15 pct and 25 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 15 pct and 25 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_1_AND_25_PCT:
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 25 pct of its maximum or below.", msg).c_str());
|
|
break;
|
|
case IS_HP_BETWEEN_25_AND_35_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 25 pct and 35 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 25 pct and 35 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_35_AND_45_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 35 pct and 45 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 35 pct and 45 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_45_AND_55_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 45 pct and 55 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 45 pct and 55 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_55_AND_65_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 55 pct and 65 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 55 pct and 65 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_65_AND_75_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 65 pct and 75 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 65 pct and 75 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_75_AND_85_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 75 pct and 85 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 75 pct and 85 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BETWEEN_85_AND_95_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be between 85 pct and 95 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be between 85 pct and 95 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_ABOVE_45_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at least 45 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at least 45 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_ABOVE_55_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at least 55 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at least 55 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case UNKNOWN_TOO_MUCH_HP_410:
|
|
Message(Chat::Red, fmt::format("{} Your target has too much HP to be affected by this spell.", msg).c_str());
|
|
break;
|
|
case UNKNOWN_TOO_MUCH_HP_411:
|
|
Message(Chat::Red, fmt::format("{} Your target has too much HP to be affected by this spell.", msg).c_str());
|
|
break;
|
|
case IS_HP_ABOVE_99_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at or above 99 pct of its maximum.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or above 99 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_MANA_ABOVE_10_PCT:
|
|
Message(Chat::Red, fmt::format("{} You must have at least 10 pct of your maximum mana available to cast this spell.", msg).c_str());
|
|
break;
|
|
case IS_HP_BELOW_5_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 5 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 5 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_10_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 10 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 10 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_15_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 15 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 15 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_20_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 20 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 20 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_25_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 25 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 25 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_30_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 30 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 30 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_35_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 35 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 35 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_40_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 40 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 40 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_45_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 45 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 45 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_50_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 50 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 50 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_55_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 55 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 55 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_60_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 60 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 60 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_65_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 65 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 65 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_70_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 70 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 70 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_75_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 75 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 75 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_80_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 80 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 80 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_85_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 85 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 85 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_90_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 90 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 90 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_HP_BELOW_95_PCT:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 95 pct of its maximum or below.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 95 pct of your maximum HP.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_ENDURANCE_BELOW_40_PCT:
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 40 pct of your maximum endurance.", msg).c_str());
|
|
break;
|
|
case IS_MANA_BELOW_40_PCT:
|
|
Message(Chat::Red, fmt::format("{} This spells requires you to be at or below 40 pct of your maximum mana.", msg).c_str());
|
|
break;
|
|
case IS_HP_ABOVE_20_PCT:
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at least 21 pct of its maximum.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_UNDEFINED:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on creatures with an undefined body type.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_HUMANOID:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on humanoid creatures.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_WEREWOLF:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on lycanthrope creatures.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_UNDEAD:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on undead creatures.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_GIANTS:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on giants.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_CONSTRUCTS:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on constructs.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_EXTRAPLANAR:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on extraplanar creatures.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_MAGICAL_CREATURE:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on creatures constructed from magic.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_UNDEADPET:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on animated undead servants.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_KAELGIANT:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on the Giants of Kael Drakkal.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_COLDAIN:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on Coldain Dwarves.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_VAMPIRE:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on vampires.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_ATEN_HA_RA:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on Aten Ha Ra.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_GREATER_AHKEVANS:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on Greater Ahkevans.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_KHATI_SHA:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on Khati Sha.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_LORD_INQUISITOR_SERU:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on Lord Inquisitor Seru.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_GRIEG_VENEFICUS:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on Grieg Veneficus.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_FROM_PLANE_OF_WAR:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on creatures from the Plane of War.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_LUGGALD:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on Luggalds.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_ANIMAL:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on animals.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_INSECT:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on insects.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_MONSTER:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on monsters.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_ELEMENTAL:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on elemental creatures.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_PLANT:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on plants.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_DRAGON2:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on dragons.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_SUMMONED_ELEMENTAL:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on summoned elementals.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_DRAGON_OF_TOV:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on Dragons of Veeshan's Temple.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_FAMILIAR:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on familiars.", msg).c_str());
|
|
break;
|
|
case IS_BODY_TYPE_MURAMITE:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on Muramites.", msg).c_str());
|
|
break;
|
|
case IS_NOT_UNDEAD_OR_SUMMONED:
|
|
Message(Chat::Red, fmt::format("{} This spell will only targets that are not undead or summoned.", msg).c_str());
|
|
break;
|
|
case IS_NOT_PLANT:
|
|
Message(Chat::Red, fmt::format("{} This spell will not affect plants.", msg).c_str());
|
|
break;
|
|
case IS_NOT_CLIENT:
|
|
Message(Chat::Red, fmt::format("{} This spell will not work on adventurers.", msg).c_str());
|
|
break;
|
|
case IS_CLIENT:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on adventurers.", msg).c_str());
|
|
break;
|
|
case IS_LEVEL_ABOVE_42_AND_IS_CLIENT:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on level 43 or higher adventurers.", msg).c_str());
|
|
break;
|
|
case IS_TREANT:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on treants.", msg).c_str());
|
|
break;
|
|
case IS_BIXIE2:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on bixies.", msg).c_str());
|
|
break;
|
|
case IS_SCARECROW:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on scarecrows.", msg).c_str());
|
|
break;
|
|
case IS_VAMPIRE_OR_UNDEAD_OR_UNDEADPET:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on vampires, undead, or animated undead creatures.", msg).c_str());
|
|
break;
|
|
case IS_NOT_VAMPIRE_OR_UNDEAD:
|
|
Message(Chat::Red, fmt::format("{} This spell will not work on vampires or undead creatures.", msg).c_str());
|
|
break;
|
|
case IS_CLASS_KNIGHT_HYBRID_MELEE:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on knights, hybrids, or melee classes.", msg).c_str());
|
|
break;
|
|
case IS_CLASS_WARRIOR_CASTER_PRIEST:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on warriors, casters, or priests.", msg).c_str());
|
|
break;
|
|
case IS_END_BELOW_21_PCT:
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 21 pct of your maximum endurance.", msg).c_str());
|
|
break;
|
|
case IS_END_BELOW_25_PCT:
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 25 pct of your maximum endurance.", msg).c_str());
|
|
break;
|
|
case IS_END_BELOW_29_PCT:
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 29 pct of your maximum endurance.", msg).c_str());
|
|
break;
|
|
case IS_HUMANOID_LEVEL_84_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on humanoids up to level 84.", msg).c_str());
|
|
break;
|
|
case IS_HUMANOID_LEVEL_86_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on humanoids up to level 86.", msg).c_str());
|
|
break;
|
|
case IS_HUMANOID_LEVEL_88_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on humanoids up to level 88.", msg).c_str());
|
|
break;
|
|
case HAS_CRYSTALLIZED_FLAME_BUFF:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on targets afflicted by Crystallized Flame.", msg).c_str());
|
|
break;
|
|
case HAS_INCENDIARY_OOZE_BUFF:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on targets afflicted by Incendiary Ooze.", msg).c_str());
|
|
break;
|
|
case IS_LEVEL_90_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will ony work targets level up to level 90.", msg).c_str());
|
|
break;
|
|
case IS_LEVEL_92_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will ony work targets level up to level 92.", msg).c_str());
|
|
break;
|
|
case IS_LEVEL_94_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will ony work targets level up to level 94.", msg).c_str());
|
|
break;
|
|
case IS_LEVEL_95_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will ony work targets level up to level 95.", msg).c_str());
|
|
break;
|
|
case IS_LEVEL_97_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will ony work targets level up to level 97.", msg).c_str());
|
|
break;
|
|
case IS_LEVEL_99_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will ony work targets level up to level 99.", msg).c_str());
|
|
break;
|
|
case IS_LEVEL_100_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will ony work targets level up to level 100.", msg).c_str());
|
|
break;
|
|
case IS_LEVEL_102_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will ony work targets level up to level 102.", msg).c_str());
|
|
break;
|
|
case IS_LEVEL_104_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will ony work targets level up to level 104.", msg).c_str());
|
|
break;
|
|
case IS_LEVEL_105_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will ony work targets level up to level 105.", msg).c_str());
|
|
break;
|
|
case IS_LEVEL_107_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will ony work targets level up to level 107.", msg).c_str());
|
|
break;
|
|
case IS_LEVEL_109_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will ony work targets level up to level 109.", msg).c_str());
|
|
break;
|
|
case IS_LEVEL_110_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will ony work targets level up to level 110.", msg).c_str());
|
|
break;
|
|
case IS_LEVEL_112_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will ony work targets level up to level 112.", msg).c_str());
|
|
break;
|
|
case IS_LEVEL_114_MAX:
|
|
Message(Chat::Red, fmt::format("{} This spell will ony work targets level up to level 114.", msg).c_str());
|
|
break;
|
|
case HAS_TBL_ESIANTI_ACCESS:
|
|
Message(Chat::Red, fmt::format("{} This spell will only transport adventurers who have gained access to Esianti: Palace of the Winds.", msg).c_str());
|
|
break;
|
|
case IS_BETWEEN_LEVEL_1_AND_75:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on targets between level 1 and 75.", msg).c_str());
|
|
break;
|
|
case IS_BETWEEN_LEVEL_76_AND_85:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on targets between level 76 and 85.", msg).c_str());
|
|
break;
|
|
case IS_BETWEEN_LEVEL_86_AND_95:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on targets between level 86 and 95.", msg).c_str());
|
|
break;
|
|
case IS_BETWEEN_LEVEL_96_AND_105:
|
|
Message(Chat::Red, fmt::format("{} This spell will only work on targets between level 96 and 105.", msg).c_str());
|
|
break;
|
|
case IS_HP_LESS_THAN_80_PCT:
|
|
Message(Chat::Red, fmt::format("{} Your target's HP must be at 80 pct of its maximum or below.", msg).c_str());
|
|
break;
|
|
case IS_LEVEL_ABOVE_34:
|
|
Message(Chat::Red, fmt::format("{} Your target must be level 35 or higher.", msg).c_str());
|
|
break;
|
|
case IN_TWO_HANDED_STANCE:
|
|
Message(Chat::Red, fmt::format("{} You must be in your two-handed stance to use this ability.", msg).c_str());
|
|
break;
|
|
case IN_DUAL_WIELD_HANDED_STANCE:
|
|
Message(Chat::Red, fmt::format("{} You must be in your dual-wielding stance to use this ability.", msg).c_str());
|
|
break;
|
|
case IN_SHIELD_STANCE:
|
|
Message(Chat::Red, fmt::format("{} You must be in your shield stance to use this ability.", msg).c_str());
|
|
break;
|
|
case NOT_IN_TWO_HANDED_STANCE:
|
|
Message(Chat::Red, fmt::format("{} You may not use this ability if you are in your two-handed stance.", msg).c_str());
|
|
break;
|
|
case NOT_IN_DUAL_WIELD_HANDED_STANCE:
|
|
Message(Chat::Red, fmt::format("{} You may not use this ability if you are in your dual-wielding stance.", msg).c_str());
|
|
break;
|
|
case NOT_IN_SHIELD_STANCE:
|
|
Message(Chat::Red, fmt::format("{} You may not use this ability if you are in your shield stance.", msg).c_str());
|
|
break;
|
|
case LEVEL_46_MAX:
|
|
Message(Chat::Red, fmt::format("{} Your target must be level 46 or below.", msg).c_str());
|
|
break;
|
|
case HAS_NO_MANA_BURN_BUFF:
|
|
Message(Chat::Red, fmt::format("{} This spell will not take hold until the effects of the previous Mana Burn have expired.", msg).c_str());
|
|
break;
|
|
case IS_CLIENT_AND_MALE_PLATE_USER:
|
|
Message(Chat::Red, fmt::format("{} Your target wouldn't look right as that Jann.", msg).c_str());
|
|
break;
|
|
case IS_CLEINT_AND_MALE_DRUID_ENCHANTER_MAGICIAN_NECROANCER_SHAMAN_OR_WIZARD:
|
|
Message(Chat::Red, fmt::format("{} Your target wouldn't look right as that Jann.", msg).c_str());
|
|
break;
|
|
case IS_CLIENT_AND_MALE_BEASTLORD_BERSERKER_MONK_RANGER_OR_ROGUE:
|
|
Message(Chat::Red, fmt::format("{} Your target wouldn't look right as that Jann.", msg).c_str());
|
|
break;
|
|
case IS_CLIENT_AND_FEMALE_PLATE_USER:
|
|
Message(Chat::Red, fmt::format("{} Your target wouldn't look right as that Jann.", msg).c_str());
|
|
break;
|
|
case IS_CLIENT_AND_FEMALE_DRUID_ENCHANTER_MAGICIAN_NECROANCER_SHAMAN_OR_WIZARD:
|
|
Message(Chat::Red, fmt::format("{} Your target wouldn't look right as that Jann.", msg).c_str());
|
|
break;
|
|
case IS_CLIENT_AND_FEMALE_BEASTLORD_BERSERKER_MONK_RANGER_OR_ROGUE:
|
|
Message(Chat::Red, fmt::format("{} Your target wouldn't look right as that Jann.", msg).c_str());
|
|
break;
|
|
case HAS_TRAVELED_TO_STRATOS:
|
|
Message(Chat::Red, fmt::format("{} You must travel to Stratos at least once before wishing to go there.", msg).c_str());
|
|
break;
|
|
case HAS_TRAVELED_TO_AALISHAI:
|
|
Message(Chat::Red, fmt::format("{} You must travel to Aalishai at least once before wishing to go there.", msg).c_str());
|
|
break;
|
|
case HAS_TRAVELED_TO_MEARATS:
|
|
Message(Chat::Red, fmt::format("{} You must travel to Mearatas at least once before wishing to go there.", msg).c_str());
|
|
break;
|
|
case IS_HP_ABOVE_50_PCT:
|
|
Message(Chat::Red, fmt::format("{} This target must be above 50 pct of its maximum hit points.", msg).c_str());
|
|
break;
|
|
case IS_HP_UNDER_50_PCT:
|
|
Message(Chat::Red, fmt::format("{} This target must be at oe below 50 pct of its maximum hit points.", msg).c_str());
|
|
break;
|
|
case IS_OFF_HAND_EQUIPED:
|
|
Message(Chat::Red, fmt::format("{} You must be wielding a weapon or shield in your offhand to use this ability.", msg).c_str());
|
|
break;
|
|
case HAS_NO_PACT_OF_FATE_RECOURSE_BUFF:
|
|
Message(Chat::Red, fmt::format("{} This spell will not work while Pact of Fate Recourse is active.", msg).c_str());
|
|
break;
|
|
case HAS_NO_SHROUD_OF_PRAYER_BUFF:
|
|
Message(Chat::Red, fmt::format("{} Your target cannot receive another Quiet Prayer this soon.", msg).c_str());
|
|
break;
|
|
case IS_MANA_BELOW_20_PCT:
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or below 20 pct of your maximum mana.", msg).c_str());
|
|
break;
|
|
case IS_MANA_ABOVE_50_PCT:
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be at or above 50 pct of your maximum mana.", msg).c_str());
|
|
break;
|
|
case COMPLETED_ACHIEVEMENT_LEGENDARY_ANSWERER:
|
|
Message(Chat::Red, fmt::format("{} You have completed Legendary Answerer.", msg).c_str());
|
|
break;
|
|
case HAS_NO_ROGUES_FURY_BUFF:
|
|
Message(Chat::Red, fmt::format("{} This spell will not affect anyone that currently has Rogue's Fury active.", msg).c_str());
|
|
break;
|
|
case NOT_COMPLETED_ACHIEVEMENT_LEGENDARY_ANSWERER:
|
|
Message(Chat::Red, fmt::format("{} You must complete Legendary Answerer.", msg).c_str());
|
|
break;
|
|
case IS_SUMMONED_OR_UNDEAD:
|
|
Message(Chat::Red, fmt::format("{} This spell can only be used on summoned or undead.", msg).c_str());
|
|
break;
|
|
case IS_CLASS_CASTER_PRIEST:
|
|
Message(Chat::Red, fmt::format("{} This ability requires you to be a caster or priest.", msg).c_str());
|
|
break;
|
|
case IS_END_OR_MANA_ABOVE_20_PCT:
|
|
Message(Chat::Red, fmt::format("{} You must have at least 20 pct of your maximum mana and endurance to use this ability.", msg).c_str());
|
|
break;
|
|
case IS_END_OR_MANA_BELOW_30_PCT:
|
|
Message(Chat::Red, fmt::format("{} Your target already has 30 pct or more of their maximum mana or endurance.", msg).c_str());
|
|
break;
|
|
case IS_CLASS_BARD2:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell will only affect Bards.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can only be used by Bards.", msg).c_str());
|
|
}
|
|
break;
|
|
case IS_NOT_CLASS_BARD:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, fmt::format("{} This spell can not affect Bards.", msg).c_str());
|
|
}
|
|
else {
|
|
Message(Chat::Red, fmt::format("{} This ability can not be used by Bards.", msg).c_str());
|
|
}
|
|
break;
|
|
case HAS_NO_FURIOUS_RAMPAGE_BUFF:
|
|
Message(Chat::Red, fmt::format("{} This ability cannot be activated while Furious Rampage is active.", msg).c_str());
|
|
break;
|
|
case IS_END_OR_MANA_BELOW_30_PCT2:
|
|
Message(Chat::Red, fmt::format("{} You can only perform this solo if you have less than 30 pct mana or endurance.", msg).c_str());
|
|
break;
|
|
case HAS_NO_HARMONIOUS_PRECISION_BUFF:
|
|
Message(Chat::Red, fmt::format("{} This spell will not work if you have the Harmonious Precision line active.", msg).c_str());
|
|
break;
|
|
case HAS_NO_HARMONIOUS_EXPANSE_BUFF:
|
|
Message(Chat::Red, fmt::format("{} This spell will not work if you have the Harmonious Expanse line active.", msg).c_str());
|
|
break;
|
|
default:
|
|
if (target_requirement) {
|
|
Message(Chat::Red, "Your target does not meet the spell requirements.");
|
|
}
|
|
else {
|
|
Message(Chat::Red, "Your spell would not take hold on your target.");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed) {
|
|
|
|
/*For mage 'Bolt' line and other various spells.
|
|
-This is mostly accurate for how the modern clients handle this effect.
|
|
-It was changed at some point to use an actual projectile as done here (opposed to a particle effect in classic)
|
|
-There is no LOS check to prevent a bolt from being cast. If you don't have LOS your bolt simply goes into whatever barrier
|
|
and you lose your mana. If there is LOS the bolt will lock onto your target and the damage is applied when it hits the target.
|
|
-If your target moves the bolt moves with it in any direction or angle (consistent with other projectiles).
|
|
-The way this is written once a bolt is cast a the distance from the initial cast to the target repeatedly
|
|
check and if target is moving recalculates at what predicted time the bolt should hit that target in client_process
|
|
When bolt hits its predicted point the damage is then done to target.
|
|
Note: Projectile speed of 1 takes 3 seconds to go 100 distance units. Calculations are based on this constant.
|
|
Live Bolt speed: Projectile speed of X takes 5 seconds to go 300 distance units.
|
|
Pending Implementation: What this code can not do is prevent damage if the bolt hits a barrier after passing the initial LOS check
|
|
because the target has moved while the bolt is in motion. (it is rare to actual get this to occur on live in normal game play)
|
|
*/
|
|
|
|
if (!spell_target)
|
|
return false;
|
|
|
|
uint8 anim = spells[spell_id].casting_animation;
|
|
int slot = -1;
|
|
|
|
//Make sure there is an avialable bolt to be cast.
|
|
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) {
|
|
if (ProjectileAtk[i].target_id == 0) {
|
|
slot = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (slot < 0)
|
|
return false;
|
|
|
|
float arc = 0.0f;
|
|
float distance_mod = 0.0f;
|
|
|
|
if (CheckLosFN(spell_target)) {
|
|
|
|
float distance = spell_target->CalculateDistance(GetX(), GetY(), GetZ());
|
|
|
|
/*
|
|
New Distance Mod constant (7/25/21 update), modifier is needed to adjust slower speeds to have correct impact times at short distances.
|
|
We use archery 4.0 speed as a baseline for the forumla. At speed 1.5 at 50 pct distance mod is needed, where as speed 4.0 there is no modifer.
|
|
Therefore, we derive out our modifer as follows. distance_mod = (speed - 4) * ((50 - 0)/(1.5-4)). The ratio there is -20.0f. distance_mod = (speed - 4) * -20.0f
|
|
For distances >125 we use different modifier, this was all meticulously tested by eye to get the best possible outcome for projectile impact times. Not perfect though.
|
|
*/
|
|
|
|
if (distance <= 125.0f) {
|
|
distance_mod = (speed - 4.0f) * -20.0f;
|
|
distance += distance * distance_mod / 100.0f;
|
|
}
|
|
else if (distance > 125.0f && distance <= 200.0f)
|
|
distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length
|
|
|
|
else if (distance > 200.0f) {
|
|
arc = 50.0f - ((distance - 200.0f) * 0.266f); //Arc angle gets drastically larger if >200 distance, lets lower it down gradually for better effect.
|
|
arc = std::max(arc, 20.0f); //No lower than 20 arc
|
|
distance = distance * 1.30f; //Add 30% to base distance if over 200 range to tighten up hit timing.
|
|
distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length
|
|
}
|
|
|
|
float hit = 1200.0f + (10 * distance / speed);
|
|
|
|
ProjectileAtk[slot].increment = 1;
|
|
ProjectileAtk[slot].hit_increment = static_cast<uint16>(hit); //This projected hit time if target does NOT MOVE
|
|
ProjectileAtk[slot].target_id = spell_target->GetID();
|
|
ProjectileAtk[slot].wpn_dmg = spell_id; //Store spell_id in weapon damage field
|
|
ProjectileAtk[slot].origin_x = GetX();
|
|
ProjectileAtk[slot].origin_y = GetY();
|
|
ProjectileAtk[slot].origin_z = GetZ();
|
|
ProjectileAtk[slot].skill = EQ::skills::SkillConjuration;
|
|
ProjectileAtk[slot].speed_mod = speed;
|
|
|
|
SetProjectileAttack(true);
|
|
}
|
|
|
|
//This will use the correct graphic as defined in the player_1 field of spells_new table. Found in UF+ spell files.
|
|
if (RuleB(Spells, UseLiveSpellProjectileGFX)) {
|
|
ProjectileAnimation(spell_target, 0, false, speed, 0.0f, 0.0f, arc, spells[spell_id].player_1);
|
|
}
|
|
//This allows limited support for server using older spell files that do not contain data for bolt graphics.
|
|
else {
|
|
//Only use fire graphic for fire spells.
|
|
if (spells[spell_id].resist_type == RESIST_FIRE) {
|
|
|
|
if (IsClient()) {
|
|
if (CastToClient()->ClientVersionBit() <= 4) //Titanium needs alternate graphic.
|
|
ProjectileAnimation(spell_target, (RuleI(Spells, FRProjectileItem_Titanium)), false, speed, 0.0f, 0.0f, arc);
|
|
else
|
|
ProjectileAnimation(spell_target, (RuleI(Spells, FRProjectileItem_SOF)), false, speed, 0.0f, 0.0f, arc);
|
|
}
|
|
|
|
else
|
|
ProjectileAnimation(spell_target, (RuleI(Spells, FRProjectileItem_NPC)), false, speed, 0.0f, 0.0f, arc);
|
|
}
|
|
//Default to an arrow if not using a mage bolt (Use up to date spell file and enable above rules for best results)
|
|
else
|
|
ProjectileAnimation(spell_target, 0, 1, speed, 0.0f, 0.0f, arc);
|
|
}
|
|
|
|
if (spells[spell_id].casting_animation == 64)
|
|
anim = 44; //Corrects for animation error.
|
|
|
|
DoAnim(anim, 0, true, IsClient() ? FilterPCSpells : FilterNPCSpells); //Override the default projectile animation.
|
|
return true;
|
|
}
|
|
|
|
void Mob::ResourceTap(int64 damage, uint16 spellid)
|
|
{
|
|
//'this' = caster
|
|
if (!IsValidSpell(spellid))
|
|
return;
|
|
|
|
for (int i = 0; i < EFFECT_COUNT; i++) {
|
|
if (spells[spellid].effect_id[i] == SE_ResourceTap) {
|
|
damage = (damage * spells[spellid].base_value[i]) / 1000;
|
|
|
|
if (damage) {
|
|
if (spells[spellid].max_value[i] && (damage > spells[spellid].max_value[i]))
|
|
damage = spells[spellid].max_value[i];
|
|
|
|
if (spells[spellid].limit_value[i] == 0) { // HP Tap
|
|
if (damage > 0)
|
|
HealDamage(damage);
|
|
else
|
|
Damage(this, -damage, 0, EQ::skills::SkillEvocation, false);
|
|
}
|
|
|
|
if (spells[spellid].limit_value[i] == 1) // Mana Tap
|
|
SetMana(GetMana() + damage);
|
|
|
|
if (spells[spellid].limit_value[i] == 2 && IsClient()) // Endurance Tap
|
|
CastToClient()->SetEndurance(CastToClient()->GetEndurance() + damage);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mob::TryTriggerThreshHold(int64 damage, int effect_id, Mob* attacker){
|
|
|
|
if (damage <= 0)
|
|
return;
|
|
|
|
if ((SE_TriggerMeleeThreshold == effect_id) && !spellbonuses.TriggerMeleeThreshold )
|
|
return;
|
|
else if ((SE_TriggerSpellThreshold == effect_id) && !spellbonuses.TriggerSpellThreshold)
|
|
return;
|
|
|
|
int buff_count = GetMaxTotalSlots();
|
|
|
|
for(int slot = 0; slot < buff_count; slot++) {
|
|
|
|
if(IsValidSpell(buffs[slot].spellid)){
|
|
|
|
for(int i = 0; i < EFFECT_COUNT; i++){
|
|
|
|
if (spells[buffs[slot].spellid].effect_id[i] == effect_id){
|
|
|
|
uint16 spell_id = spells[buffs[slot].spellid].base_value[i];
|
|
|
|
if (damage > spells[buffs[slot].spellid].limit_value[i]){
|
|
|
|
BuffFadeBySlot(slot);
|
|
|
|
if (IsValidSpell(spell_id)) {
|
|
|
|
if (IsBeneficialSpell(spell_id))
|
|
SpellFinished(spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty);
|
|
|
|
else if(attacker)
|
|
SpellFinished(spell_id, attacker, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mob::CastSpellOnLand(Mob* caster, int32 spell_id)
|
|
{
|
|
/*
|
|
This function checks for incoming spells on a mob, if they meet the criteria for focus SE_Fc_Cast_Spell_on_Land then
|
|
a new spell will be cast by THIS mob as specified by the focus effect. Note: Chance to cast the spell is determined in
|
|
the CalcFocusEffect function if not 100pct.
|
|
ApplyFocusProcLimiter() function checks for SE_Proc_Timer_Modifier which allows for limiting how often a spell from effect can be triggered
|
|
for example, if set to base=1 and base2= 1500, then for everyone 1 successful trigger, you will be unable to trigger again for 1.5 seconds.
|
|
|
|
Live only has this focus in buffs/debuffs that can be placed on a target. TODO: Will consider adding support for it as AA and Item.
|
|
*/
|
|
if (!caster)
|
|
return;
|
|
|
|
int32 trigger_spell_id = 0;
|
|
|
|
//Step 1: Check this focus effect exists on the mob.
|
|
if (spellbonuses.FocusEffects[focusFcCastSpellOnLand]) {
|
|
|
|
int buff_count = GetMaxTotalSlots();
|
|
for (int i = 0; i < buff_count; i++) {
|
|
|
|
if ((IsValidSpell(buffs[i].spellid) && (buffs[i].spellid != spell_id) && IsEffectInSpell(buffs[i].spellid, SE_Fc_Cast_Spell_On_Land))) {
|
|
|
|
//Step 2: Check if we pass all focus limiters and focus chance roll
|
|
trigger_spell_id = CalcFocusEffect(focusFcCastSpellOnLand, buffs[i].spellid, spell_id, false, buffs[i].casterid, caster);
|
|
|
|
if (IsValidSpell(trigger_spell_id) && (trigger_spell_id != spell_id)) {
|
|
|
|
//Step 3: Cast spells
|
|
if (IsBeneficialSpell(trigger_spell_id)) {
|
|
SpellFinished(trigger_spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[trigger_spell_id].resist_difficulty);
|
|
}
|
|
else {
|
|
Mob* current_target = GetTarget();
|
|
//For now don't let players cast detrimental effects on themselves if they are targeting themselves. Need to confirm behavior.
|
|
if (current_target && current_target->GetID() != GetID())
|
|
SpellFinished(trigger_spell_id, current_target, EQ::spells::CastingSlot::Item, 0, -1, spells[trigger_spell_id].resist_difficulty);
|
|
}
|
|
}
|
|
if (i >= 0)
|
|
CheckNumHitsRemaining(NumHit::MatchingSpells, i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mob::CalcSpellPowerDistanceMod(uint16 spell_id, float range, Mob* caster)
|
|
{
|
|
if (IsPowerDistModSpell(spell_id)){
|
|
|
|
float distance = 0;
|
|
|
|
if (caster && !range)
|
|
distance = caster->CalculateDistance(GetX(), GetY(), GetZ());
|
|
else
|
|
distance = sqrt(range);
|
|
|
|
distance = EQ::Clamp(distance, spells[spell_id].min_distance, spells[spell_id].max_distance);
|
|
|
|
float dm_range = spells[spell_id].max_distance - spells[spell_id].min_distance;
|
|
float dm_mod_interval = spells[spell_id].max_distance_mod - spells[spell_id].min_distance_mod;
|
|
float dist_from_min = distance - spells[spell_id].min_distance;
|
|
float mod = spells[spell_id].min_distance_mod + (dist_from_min * (dm_mod_interval/dm_range));
|
|
mod *= 100.0f;
|
|
|
|
SetSpellPowerDistanceMod(static_cast<int>(mod));
|
|
}
|
|
}
|
|
|
|
void Mob::BreakInvisibleSpells()
|
|
{
|
|
if(invisible) {
|
|
nobuff_invisible = 0;
|
|
BuffFadeByEffect(SE_Invisibility);
|
|
BuffFadeByEffect(SE_Invisibility2);
|
|
}
|
|
if(invisible_undead) {
|
|
ZeroInvisibleVars(InvisType::T_INVISIBLE_VERSE_UNDEAD);
|
|
BuffFadeByEffect(SE_InvisVsUndead);
|
|
BuffFadeByEffect(SE_InvisVsUndead2);
|
|
}
|
|
if(invisible_animals){
|
|
ZeroInvisibleVars(InvisType::T_INVISIBLE_VERSE_ANIMAL);
|
|
BuffFadeByEffect(SE_ImprovedInvisAnimals);
|
|
BuffFadeByEffect(SE_InvisVsAnimals);
|
|
}
|
|
}
|
|
|
|
void Client::BreakSneakWhenCastOn(Mob *caster, bool IsResisted)
|
|
{
|
|
bool IsCastersTarget = false; // Chance to avoid only applies to AOE spells when not targeted.
|
|
if (hidden || improved_hidden) {
|
|
if (caster) {
|
|
Mob *spell_target = caster->GetTarget();
|
|
if (spell_target && spell_target == this) {
|
|
IsCastersTarget = true;
|
|
}
|
|
}
|
|
if (!IsCastersTarget) {
|
|
int chance = spellbonuses.NoBreakAESneak + itembonuses.NoBreakAESneak + aabonuses.NoBreakAESneak;
|
|
if (IsResisted) {
|
|
chance *= 2;
|
|
}
|
|
if (chance && zone->random.Roll(chance)) {
|
|
return; // Do not drop Sneak/Hide
|
|
}
|
|
}
|
|
CancelSneakHide();
|
|
}
|
|
}
|
|
|
|
void Client::BreakFeignDeathWhenCastOn(bool IsResisted)
|
|
{
|
|
if(GetFeigned()){
|
|
|
|
int chance = spellbonuses.FeignedCastOnChance + itembonuses.FeignedCastOnChance + aabonuses.FeignedCastOnChance;
|
|
|
|
if (IsResisted)
|
|
chance *= 2;
|
|
|
|
if(chance && (zone->random.Roll(chance))){
|
|
MessageString(Chat::SpellFailure,FD_CAST_ON_NO_BREAK);
|
|
return;
|
|
}
|
|
|
|
SetFeigned(false);
|
|
MessageString(Chat::SpellFailure,FD_CAST_ON);
|
|
}
|
|
}
|
|
|
|
bool Mob::HarmonySpellLevelCheck(int32 spell_id, Mob *target)
|
|
{
|
|
//'this' = caster of spell
|
|
if (!target) {
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < EFFECT_COUNT; i++) {
|
|
// not important to check limit on SE_Lull as it doesnt have one and if the other components won't land, then SE_Lull wont either
|
|
if (spells[spell_id].effect_id[i] == SE_ChangeFrenzyRad || spells[spell_id].effect_id[i] == SE_Harmony) {
|
|
if ((spells[spell_id].max_value[i] != 0 && target->GetLevel() > spells[spell_id].max_value[i]) || target->GetSpecialAbility(IMMUNE_PACIFY)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Mob::PassCharmTargetRestriction(Mob *target) {
|
|
|
|
//Level restriction check should not go here.
|
|
if (!target) {
|
|
return false;
|
|
}
|
|
|
|
if (target->IsClient() && IsClient()) {
|
|
MessageString(Chat::Red, CANNOT_AFFECT_PC);
|
|
LogSpells("Spell casting canceled: Can not cast charm on a client.");
|
|
return false;
|
|
}
|
|
else if (target->IsCorpse()) {
|
|
LogSpells("Spell casting canceled: Can not cast charm on a corpse.");
|
|
return false;
|
|
}
|
|
else if (GetPet() && IsClient()) {
|
|
MessageString(Chat::Red, ONLY_ONE_PET);
|
|
LogSpells("Spell casting canceled: Can not cast charm if you have a pet.");
|
|
return false;
|
|
}
|
|
else if (target->GetOwner()) {
|
|
MessageString(Chat::Red, CANNOT_CHARM);
|
|
LogSpells("Spell casting canceled: Can not cast charm on a pet.");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Mob::PassLimitToSkill(EQ::skills::SkillType skill, int32 spell_id, int proc_type, int aa_id)
|
|
{
|
|
/*
|
|
Check if SE_AddMeleProc or SE_RangedProc have a skill limiter. Passes automatically if no skill limiters present.
|
|
*/
|
|
int32 proc_type_spaid = 0;
|
|
if (proc_type == ProcType::MELEE_PROC) {
|
|
proc_type_spaid = SE_AddMeleeProc;
|
|
}
|
|
if (proc_type == ProcType::RANGED_PROC) {
|
|
proc_type_spaid = SE_RangedProc;
|
|
}
|
|
|
|
bool match_proc_type = false;
|
|
bool has_limit_check = false;
|
|
|
|
if (!aa_id && spellbonuses.LimitToSkill[EQ::skills::HIGHEST_SKILL + 2]) {
|
|
|
|
if (spell_id == SPELL_UNKNOWN) {
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < EFFECT_COUNT; i++) {
|
|
if (spells[spell_id].effect_id[i] == proc_type_spaid) {
|
|
match_proc_type = true;
|
|
}
|
|
if (match_proc_type && spells[spell_id].effect_id[i] == SE_LimitToSkill && spells[spell_id].base_value[i] <= EQ::skills::HIGHEST_SKILL) {
|
|
|
|
has_limit_check = true;
|
|
if (spells[spell_id].base_value[i] == skill) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (aabonuses.LimitToSkill[EQ::skills::HIGHEST_SKILL + 2]) {
|
|
|
|
int rank_id = 1;
|
|
AA::Rank *rank = zone->GetAlternateAdvancementRank(aa_id);
|
|
|
|
if (!rank) {
|
|
return true;
|
|
}
|
|
|
|
AA::Ability *ability_in = rank->base_ability;
|
|
if (!ability_in) {
|
|
return true;
|
|
}
|
|
|
|
for (auto &aa : aa_ranks) {
|
|
auto ability_rank = zone->GetAlternateAdvancementAbilityAndRank(aa.first, aa.second.first);
|
|
auto ability = ability_rank.first;
|
|
auto rank = ability_rank.second;
|
|
|
|
if (!ability) {
|
|
continue;
|
|
}
|
|
|
|
for (auto &effect : rank->effects) {
|
|
if (effect.effect_id == proc_type_spaid) {
|
|
match_proc_type = true;
|
|
}
|
|
|
|
if (match_proc_type && effect.effect_id == SE_LimitToSkill && effect.base_value <= EQ::skills::HIGHEST_SKILL) {
|
|
has_limit_check = true;
|
|
if (effect.base_value == skill) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (has_limit_check) {
|
|
return false; //Limit was found, but not matched, fail.
|
|
}
|
|
else {
|
|
return true; //No limit is present, automatically pass.
|
|
}
|
|
}
|
|
|
|
bool Mob::CanFocusUseRandomEffectivenessByType(focusType type)
|
|
{
|
|
switch (type) {
|
|
case focusImprovedDamage:
|
|
case focusImprovedDamage2:
|
|
case focusImprovedHeal:
|
|
case focusManaCost:
|
|
case focusResistRate:
|
|
case focusFcDamagePctCrit:
|
|
case focusReagentCost:
|
|
case focusSpellHateMod:
|
|
case focusSpellVulnerability:
|
|
case focusFcSpellDamagePctIncomingPC:
|
|
case focusFcHealPctIncoming:
|
|
case focusFcHealPctCritIncoming:
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int Mob::GetFocusRandomEffectivenessValue(int focus_base, int focus_base2, bool best_focus)
|
|
{
|
|
int64 value = 0;
|
|
// This is used to determine which focus should be used for the random calculation
|
|
if (best_focus) {
|
|
// If the spell does not contain a base2 value, then its a straight non random
|
|
value = focus_base;
|
|
// If the spell contains a value in the base2 field then that is the max value
|
|
if (focus_base2 != 0) {
|
|
value = focus_base2;
|
|
}
|
|
return value;
|
|
}
|
|
else if (focus_base2 == 0 || focus_base == focus_base2) { // Actual focus calculation starts here
|
|
return focus_base;
|
|
}
|
|
|
|
return zone->random.Int(focus_base, focus_base2);
|
|
}
|
|
|
|
bool Mob::NegateSpellEffect(uint16 spell_id, int effect_id)
|
|
{
|
|
/*
|
|
This works for most effects, anything handled purely by the client will bypass this (ie Gate, Shadowstep)
|
|
Seen with resurrection effects, likely blocks the client from accepting a ressurection request. *Not implement at this time.
|
|
*/
|
|
|
|
for (int i = 0; i < GetMaxTotalSlots(); i++) {
|
|
//Check for any buffs containing NegateEffect
|
|
if (IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, SE_NegateSpellEffect) && spell_id != buffs[i].spellid) {
|
|
//Match each of the negate effects with the current spell effect, if found, that effect will not be applied.
|
|
for (int j = 0; j < EFFECT_COUNT; j++)
|
|
{
|
|
if (spells[buffs[i].spellid].effect_id[j] == SE_NegateSpellEffect &&
|
|
spells[buffs[i].spellid].limit_value[j] == effect_id &&
|
|
(spells[buffs[i].spellid].base_value[j] == NEGATE_SPA_ALL_BONUSES ||
|
|
spells[buffs[i].spellid].base_value[j] == NEGATE_SPA_SPELLBONUS ||
|
|
spells[buffs[i].spellid].base_value[j] == NEGATE_SPA_SPELLBONUS_AND_ITEMBONUS ||
|
|
spells[buffs[i].spellid].base_value[j] == NEGATE_SPA_SPELLBONUS_AND_AABONUS)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int Mob::GetMemoryBlurChance(int base_chance)
|
|
{
|
|
/*
|
|
Memory Blur mechanic for SPA 62
|
|
Chance formula is effect chance + charisma modifer + caster level modifier
|
|
Effect chance is base value of spell
|
|
Charisma modifier is CHA/10 = %, with MAX of 15% (thus 150 cha gives you max bonus)
|
|
Caster level modifier. +100% if caster < level 17 which scales down to 25% at > 53. **
|
|
(Yes the above gets worse as you level. Behavior was confirmed on live.)
|
|
Memory blur is applied to mez on initial cast using same formula. However, recasting on a target that
|
|
is already mezed will not give a chance to memory blur. The blur is not checked on buff ticks.
|
|
|
|
SPA 242 SE_IncreaseChanceMemwipe modifies the final chance after all bonuses are applied.
|
|
This is also applied to memory blur from mez spells.
|
|
|
|
this = caster
|
|
*/
|
|
int cha_mod = int(GetCHA() / 10);
|
|
cha_mod = std::min(cha_mod, 15);
|
|
|
|
int lvl_mod = 0;
|
|
if (GetLevel() < 17) {
|
|
lvl_mod = 100;
|
|
}
|
|
else if (GetLevel() > 53) {
|
|
lvl_mod = 25;
|
|
}
|
|
else {
|
|
lvl_mod = 100 + ((GetLevel() - 16)*-2);//Derived from above range of values.**
|
|
}
|
|
|
|
int chance = cha_mod + lvl_mod + base_chance;
|
|
|
|
int chance_mod = spellbonuses.IncreaseChanceMemwipe + itembonuses.IncreaseChanceMemwipe + aabonuses.IncreaseChanceMemwipe;
|
|
|
|
chance += chance * chance_mod / 100;
|
|
return chance;
|
|
}
|
|
|
|
void Mob::VirusEffectProcess()
|
|
{
|
|
/*
|
|
Virus Mechanics
|
|
To qualify as a virus effect buff, all of the following spell table need to be set. (At some point will correct names)
|
|
viral_targets = MIN_SPREAD_TIME
|
|
viral_timer = MAX_SPREAD_TIME
|
|
viral_range = SPREAD_RADIUS
|
|
Once a buff with a viral effect is applied, a 1000 ms timer will begin.
|
|
The time at which the virus will attempt to spread is determined by a random value between MIN_SPREAD_TIME and MAX_SPREAD_TIME
|
|
Each time the virus attempts to spread the next time interval will be chosen at random again.
|
|
If a spreader finds a target for viral buff, when the viral buff spreads the duration on the new target will be the time remaining on the spreaders buff.
|
|
Spreaders DOES NOT need LOS to spread. There is no max amount of targets the virus can spread to.
|
|
When the spreader no longer has any viral buffs the timer stops.
|
|
The current code supports spreading for both detrimental and beneficial spells.
|
|
*/
|
|
|
|
// Only spread in zones without perm buffs
|
|
if (zone->BuffTimersSuspended()) {
|
|
viral_timer.Disable();
|
|
return;
|
|
}
|
|
|
|
bool stop_timer = true;
|
|
for (int buffs_i = 0; buffs_i < GetMaxTotalSlots(); ++buffs_i)
|
|
{
|
|
if (IsValidSpell(buffs[buffs_i].spellid) && IsVirusSpell(buffs[buffs_i].spellid))
|
|
{
|
|
if (buffs[buffs_i].virus_spread_time > 0) {
|
|
buffs[buffs_i].virus_spread_time -= 1;
|
|
stop_timer = false;
|
|
}
|
|
|
|
if (buffs[buffs_i].virus_spread_time <= 0) {
|
|
buffs[buffs_i].virus_spread_time = zone->random.Int(GetViralMinSpreadTime(buffs[buffs_i].spellid), GetViralMaxSpreadTime(buffs[buffs_i].spellid));
|
|
SpreadVirusEffect(buffs[buffs_i].spellid, buffs[buffs_i].casterid, buffs[buffs_i].ticsremaining);
|
|
stop_timer = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (stop_timer) {
|
|
viral_timer.Disable();
|
|
}
|
|
}
|
|
|
|
void Mob::SpreadVirusEffect(int32 spell_id, uint32 caster_id, int32 buff_tics_remaining)
|
|
{
|
|
Mob *caster = entity_list.GetMob(caster_id);
|
|
|
|
if (!caster) {
|
|
return;
|
|
}
|
|
|
|
std::vector<Mob *> targets_in_range = entity_list.GetTargetsForVirusEffect(
|
|
this,
|
|
caster,
|
|
GetViralSpreadRange(spell_id),
|
|
spells[spell_id].pcnpc_only_flag,
|
|
spell_id
|
|
);
|
|
|
|
for (auto &mob: targets_in_range) {
|
|
if (!mob) {
|
|
continue;
|
|
}
|
|
|
|
if (!mob->FindBuff(spell_id)) {
|
|
if (caster) {
|
|
if (buff_tics_remaining) {
|
|
//When virus is spread, the buff on new target is applied with the amount of time remaining on the spreaders buff.
|
|
caster->SpellOnTarget(spell_id, mob, 0, false, 0, false, -1, buff_tics_remaining);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Mob::IsFocusProcLimitTimerActive(int32 focus_spell_id) {
|
|
/*
|
|
Used with SPA 511 SE_Ff_FocusTimerMin to limit how often a focus effect can be applied.
|
|
Ie. Can only have a spell trigger once every 15 seconds, or to be more creative can only
|
|
have the fire spells received a very high special focused once every 30 seconds.
|
|
Note, this stores timers for both spell, item and AA related focuses For AA the focus_spell_id
|
|
is saved as the the negative value of the rank.id (to avoid conflicting with spell_ids)
|
|
*/
|
|
for (int i = 0; i < MAX_FOCUS_PROC_LIMIT_TIMERS; i++) {
|
|
if (focusproclimit_spellid[i] == focus_spell_id) {
|
|
if (focusproclimit_timer[i].Enabled()) {
|
|
if (focusproclimit_timer[i].GetRemainingTime() > 0) {
|
|
return true;
|
|
}
|
|
else {
|
|
focusproclimit_timer[i].Disable();
|
|
focusproclimit_spellid[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Mob::SetFocusProcLimitTimer(int32 focus_spell_id, uint32 focus_reuse_time) {
|
|
|
|
bool is_set = false;
|
|
|
|
for (int i = 0; i < MAX_FOCUS_PROC_LIMIT_TIMERS; i++) {
|
|
if (!focusproclimit_spellid[i] && !is_set) {
|
|
focusproclimit_spellid[i] = focus_spell_id;
|
|
focusproclimit_timer[i].SetTimer(focus_reuse_time);
|
|
is_set = true;
|
|
}
|
|
//Remove old temporary focus if was from a buff you no longer have.
|
|
else if (focusproclimit_spellid[i] > 0 && !FindBuff(focus_spell_id)) {
|
|
focusproclimit_spellid[i] = 0;
|
|
focusproclimit_timer[i].Disable();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Mob::IsProcLimitTimerActive(int32 base_spell_id, uint32 proc_reuse_time, int proc_type) {
|
|
/*
|
|
Used with SPA 512 SE_Proc_Timer_Modifier to limit how often a proc can be cast.
|
|
If this effect exists it will prevent the next proc from firing until the timer
|
|
defined in SPA 512 is finished. Ie. 1 proc every 55 seconds.
|
|
Spell, Ranged, and Defensive procs all have their own timer array, therefore
|
|
you can stack multiple different types of effects in the same spell. Make sure
|
|
SPA 512 goes directly after each proc you want to have the timer.
|
|
*/
|
|
if (!proc_reuse_time) {
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < MAX_PROC_LIMIT_TIMERS; i++) {
|
|
|
|
if (proc_type == ProcType::MELEE_PROC) {
|
|
if (spell_proclimit_spellid[i] == base_spell_id) {
|
|
if (spell_proclimit_timer[i].Enabled()) {
|
|
if (spell_proclimit_timer[i].GetRemainingTime() > 0) {
|
|
return true;
|
|
}
|
|
else {
|
|
spell_proclimit_timer[i].Disable();
|
|
spell_proclimit_spellid[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (proc_type == ProcType::RANGED_PROC) {
|
|
if (ranged_proclimit_spellid[i] == base_spell_id) {
|
|
if (ranged_proclimit_timer[i].Enabled()) {
|
|
if (ranged_proclimit_timer[i].GetRemainingTime() > 0) {
|
|
return true;
|
|
}
|
|
else {
|
|
ranged_proclimit_timer[i].Disable();
|
|
ranged_proclimit_spellid[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (proc_type == ProcType::DEFENSIVE_PROC) {
|
|
if (def_proclimit_spellid[i] == base_spell_id) {
|
|
if (def_proclimit_timer[i].Enabled()) {
|
|
if (def_proclimit_timer[i].GetRemainingTime() > 0) {
|
|
return true;
|
|
}
|
|
else {
|
|
def_proclimit_timer[i].Disable();
|
|
def_proclimit_spellid[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Mob::SetProcLimitTimer(int32 base_spell_id, uint32 proc_reuse_time, int proc_type) {
|
|
|
|
if (!proc_reuse_time) {
|
|
return;
|
|
}
|
|
|
|
bool is_set = false;
|
|
|
|
for (int i = 0; i < MAX_PROC_LIMIT_TIMERS; i++) {
|
|
|
|
if (proc_type == ProcType::MELEE_PROC) {
|
|
if (!spell_proclimit_spellid[i] && !is_set) {
|
|
spell_proclimit_spellid[i] = base_spell_id;
|
|
spell_proclimit_timer[i].SetTimer(proc_reuse_time);
|
|
is_set = true;
|
|
}
|
|
else if (spell_proclimit_spellid[i] > 0 && !FindBuff(base_spell_id)) {
|
|
spell_proclimit_spellid[i] = 0;
|
|
spell_proclimit_timer[i].Disable();
|
|
}
|
|
}
|
|
|
|
if (proc_type == ProcType::RANGED_PROC) {
|
|
if (!ranged_proclimit_spellid[i] && !is_set) {
|
|
ranged_proclimit_spellid[i] = base_spell_id;
|
|
ranged_proclimit_timer[i].SetTimer(proc_reuse_time);
|
|
is_set = true;
|
|
}
|
|
else if (ranged_proclimit_spellid[i] > 0 && !FindBuff(base_spell_id)) {
|
|
ranged_proclimit_spellid[i] = 0;
|
|
ranged_proclimit_timer[i].Disable();
|
|
}
|
|
}
|
|
|
|
if (proc_type == ProcType::DEFENSIVE_PROC) {
|
|
if (!def_proclimit_spellid[i] && !is_set) {
|
|
def_proclimit_spellid[i] = base_spell_id;
|
|
def_proclimit_timer[i].SetTimer(proc_reuse_time);
|
|
is_set = true;
|
|
}
|
|
else if (def_proclimit_spellid[i] > 0 && !FindBuff(base_spell_id)) {
|
|
def_proclimit_spellid[i] = 0;
|
|
def_proclimit_timer[i].Disable();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mob::SendIllusionWearChange(Client* c) {
|
|
|
|
/*
|
|
We send this to client on Client::CompleteConnect() to properly update textures of
|
|
other mobs in zone with illusions on them.
|
|
*/
|
|
if (!c) {
|
|
return;
|
|
}
|
|
|
|
if (!spellbonuses.Illusion && !itembonuses.Illusion && !aabonuses.Illusion) {
|
|
return;
|
|
}
|
|
|
|
for (int x = EQ::textures::textureBegin; x <= EQ::textures::LastTintableTexture; x++) {
|
|
SendWearChange(x, c);
|
|
}
|
|
}
|
|
|
|
void Mob::ApplyIllusionToCorpse(int32 spell_id, Corpse* new_corpse) {
|
|
|
|
//Transfers most illusions over to the corpse upon death
|
|
if (!IsValidSpell(spell_id)) {
|
|
return;
|
|
}
|
|
|
|
if (!new_corpse) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < EFFECT_COUNT; i++){
|
|
if (spells[spell_id].effect_id[i] == SE_Illusion) {
|
|
new_corpse->ApplySpellEffectIllusion(spell_id, nullptr, -1, spells[spell_id].base_value[i], spells[spell_id].limit_value[i], spells[spell_id].max_value[i]);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mob::ApplySpellEffectIllusion(int32 spell_id, Mob *caster, int buffslot, int base, int limit, int max)
|
|
{
|
|
// Gender Illusions
|
|
if (base == -1) {
|
|
// Specific Gender Illusions
|
|
if (spell_id == SPELL_ILLUSION_MALE || spell_id == SPELL_ILLUSION_FEMALE) {
|
|
int specific_gender = -1;
|
|
// Male
|
|
if (spell_id == SPELL_ILLUSION_MALE)
|
|
specific_gender = 0;
|
|
// Female
|
|
else if (spell_id == SPELL_ILLUSION_FEMALE)
|
|
specific_gender = 1;
|
|
if (specific_gender > -1) {
|
|
if (caster && caster->GetTarget()) {
|
|
SendIllusionPacket
|
|
(
|
|
caster->GetTarget()->GetBaseRace(),
|
|
specific_gender,
|
|
caster->GetTarget()->GetTexture()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
// Change Gender Illusions
|
|
else {
|
|
if (caster && caster->GetTarget()) {
|
|
int opposite_gender = 0;
|
|
if (caster->GetTarget()->GetGender() == 0)
|
|
opposite_gender = 1;
|
|
|
|
SendIllusionPacket
|
|
(
|
|
caster->GetTarget()->GetRace(),
|
|
opposite_gender,
|
|
caster->GetTarget()->GetTexture()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
// Racial Illusions
|
|
else {
|
|
auto gender_id = (
|
|
max > 0 &&
|
|
(
|
|
max != 3 ||
|
|
limit == 0
|
|
) ?
|
|
(max - 1) :
|
|
Mob::GetDefaultGender(base, GetGender())
|
|
);
|
|
|
|
auto race_size = GetRaceGenderDefaultHeight(
|
|
base,
|
|
gender_id
|
|
);
|
|
|
|
if (base != RACE_ELEMENTAL_75) {
|
|
if (max > 0) {
|
|
if (limit == 0) {
|
|
SendIllusionPacket(
|
|
base,
|
|
gender_id
|
|
);
|
|
}
|
|
else {
|
|
if (max != 3) {
|
|
SendIllusionPacket(
|
|
base,
|
|
gender_id,
|
|
limit,
|
|
max
|
|
);
|
|
}
|
|
else {
|
|
SendIllusionPacket(
|
|
base,
|
|
gender_id,
|
|
limit,
|
|
limit
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
SendIllusionPacket(
|
|
base,
|
|
gender_id,
|
|
limit,
|
|
max
|
|
);
|
|
}
|
|
|
|
}
|
|
else {
|
|
SendIllusionPacket(
|
|
base,
|
|
gender_id,
|
|
limit
|
|
);
|
|
}
|
|
SendAppearancePacket(AT_Size, race_size);
|
|
}
|
|
|
|
for (int x = EQ::textures::textureBegin; x <= EQ::textures::LastTintableTexture; x++) {
|
|
SendWearChange(x);
|
|
}
|
|
|
|
if (buffslot != -1) {
|
|
if (
|
|
caster == this &&
|
|
spell_id != SPELL_MINOR_ILLUSION &&
|
|
spell_id != SPELL_ILLUSION_TREE &&
|
|
(
|
|
spellbonuses.IllusionPersistence ||
|
|
aabonuses.IllusionPersistence ||
|
|
itembonuses.IllusionPersistence ||
|
|
RuleB(Spells, IllusionsAlwaysPersist)
|
|
)
|
|
) {
|
|
buffs[buffslot].persistant_buff = 1;
|
|
}
|
|
else {
|
|
buffs[buffslot].persistant_buff = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Mob::HasPersistDeathIllusion(int32 spell_id) {
|
|
if (
|
|
spellbonuses.IllusionPersistence > 1 ||
|
|
aabonuses.IllusionPersistence > 1 ||
|
|
itembonuses.IllusionPersistence > 1 ||
|
|
RuleB(Spells, IllusionsAlwaysPersist)
|
|
) {
|
|
if (
|
|
spell_id != SPELL_MINOR_ILLUSION &&
|
|
spell_id != SPELL_ILLUSION_TREE &&
|
|
IsEffectInSpell(spell_id, SE_Illusion) &&
|
|
IsBeneficialSpell(spell_id)
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Mob::SetBuffDuration(int spell_id, int duration) {
|
|
|
|
/*
|
|
Will refresh the buff with specified spell_id to the specified duration
|
|
If spell is -1, then all spells will be set to the specified duration
|
|
If duration 0, then will set duration to buffs normal max duration.
|
|
*/
|
|
|
|
bool adjust_all_buffs = false;
|
|
|
|
if (spell_id == -1) {
|
|
adjust_all_buffs = true;
|
|
}
|
|
|
|
if (!adjust_all_buffs && !IsValidSpell(spell_id)){
|
|
return;
|
|
}
|
|
|
|
if (duration < -1) {
|
|
duration = PERMANENT_BUFF_DURATION;
|
|
}
|
|
|
|
int buff_count = GetMaxTotalSlots();
|
|
for (int slot = 0; slot < buff_count; slot++) {
|
|
|
|
if (!adjust_all_buffs) {
|
|
if (buffs[slot].spellid != SPELL_UNKNOWN && buffs[slot].spellid == spell_id) {
|
|
SpellOnTarget(buffs[slot].spellid, this, 0, false, 0, false, -1, duration, true);
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
if (buffs[slot].spellid != SPELL_UNKNOWN) {
|
|
SpellOnTarget(buffs[slot].spellid, this, 0, false, 0, false, -1, duration, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mob::ApplySpellBuff(int spell_id, int duration)
|
|
{
|
|
/*
|
|
Used for quest command to apply a new buff with custom duration.
|
|
Duration set to 0 will apply with normal duration.
|
|
*/
|
|
if (!IsValidSpell(spell_id)) {
|
|
return;
|
|
}
|
|
|
|
if (!spells[spell_id].buff_duration) {
|
|
return;
|
|
}
|
|
|
|
if (duration <= -1) {
|
|
duration = PERMANENT_BUFF_DURATION;
|
|
}
|
|
|
|
SpellOnTarget(spell_id, this, 0, false, 0, false, -1, duration);
|
|
}
|
|
|
|
int Mob::GetBuffStatValueBySpell(int32 spell_id, const char* stat_identifier)
|
|
{
|
|
if (!IsValidSpell(spell_id)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!stat_identifier) {
|
|
return 0;
|
|
}
|
|
|
|
std::string id = Strings::ToLower(stat_identifier);
|
|
|
|
int buff_count = GetMaxTotalSlots();
|
|
for (int slot = 0; slot < buff_count; slot++) {
|
|
if (buffs[slot].spellid != SPELL_UNKNOWN && buffs[slot].spellid == spell_id) {
|
|
return GetBuffStatValueBySlot(slot, stat_identifier);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int Mob::GetBuffStatValueBySlot(uint8 slot, const char* stat_identifier)
|
|
{
|
|
if (slot > GetMaxTotalSlots()) {
|
|
return 0;
|
|
}
|
|
|
|
if (!stat_identifier) {
|
|
return 0;
|
|
}
|
|
|
|
std::string id = Strings::ToLower(stat_identifier);
|
|
|
|
if (id == "caster_level") { return buffs[slot].casterlevel; }
|
|
else if (id == "spell_id") { return buffs[slot].spellid; }
|
|
else if (id == "caster_id") { return buffs[slot].spellid;; }
|
|
else if (id == "ticsremaining") { return buffs[slot].ticsremaining; }
|
|
else if (id == "counters") { return buffs[slot].counters; }
|
|
else if (id == "hit_number") { return buffs[slot].hit_number; }
|
|
else if (id == "melee_rune") { return buffs[slot].melee_rune; }
|
|
else if (id == "magic_rune") { return buffs[slot].magic_rune; }
|
|
else if (id == "dot_rune") { return buffs[slot].dot_rune; }
|
|
else if (id == "caston_x") { return buffs[slot].caston_x; }
|
|
else if (id == "caston_y") { return buffs[slot].caston_y; }
|
|
else if (id == "caston_z") { return buffs[slot].caston_z; }
|
|
else if (id == "instrument_mod") { return buffs[slot].instrument_mod; }
|
|
else if (id == "persistant_buff") { return buffs[slot].persistant_buff; }
|
|
else if (id == "client") { return buffs[slot].client; }
|
|
else if (id == "extra_di_chance") { return buffs[slot].ExtraDIChance; }
|
|
else if (id == "root_break_chance") { return buffs[slot].RootBreakChance; }
|
|
else if (id == "virus_spread_time") { return buffs[slot].virus_spread_time; }
|
|
return 0;
|
|
}
|