mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
5391 lines
137 KiB
C++
5391 lines
137 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/debug.h"
|
|
#include "../common/spdat.h"
|
|
#include "masterentity.h"
|
|
#include "../common/packet_dump.h"
|
|
#include "../common/moremath.h"
|
|
#include "../common/Item.h"
|
|
#include "worldserver.h"
|
|
#include "../common/skills.h"
|
|
#include "../common/bodytypes.h"
|
|
#include "../common/classes.h"
|
|
#include "../common/rulesys.h"
|
|
#include <math.h>
|
|
#include <assert.h>
|
|
#ifndef WIN32
|
|
#include <stdlib.h>
|
|
#include "../common/unix.h"
|
|
#endif
|
|
|
|
#include "StringIDs.h"
|
|
#include "QuestParserCollection.h"
|
|
|
|
extern Zone* zone;
|
|
extern volatile bool ZoneLoaded;
|
|
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)
|
|
{
|
|
_ZP(Mob_SpellEffect);
|
|
|
|
int caster_level, buffslot, effect, effect_value, i;
|
|
ItemInst *SummonedItem=NULL;
|
|
#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];
|
|
|
|
bool c_override = false;
|
|
if(caster && caster->IsClient() && GetCastedSpellInvSlot() > 0)
|
|
{
|
|
const ItemInst* 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
|
|
caster_level = caster ? caster->GetCasterLevel(spell_id) : GetCasterLevel(spell_id);
|
|
}
|
|
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-1) > 0)
|
|
{
|
|
buffslot = AddBuff(caster, spell_id, durat, caster_level);
|
|
if(buffslot == -1) // stacking failure
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
buffslot = -2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if((CalcBuffDuration(caster,this,spell_id)-1) > 0){
|
|
if(IsEffectInSpell(spell_id, SE_BindSight))
|
|
{
|
|
if(caster)
|
|
{
|
|
buffslot = caster->AddBuff(caster, spell_id);
|
|
}
|
|
else
|
|
buffslot = -1;
|
|
}
|
|
else
|
|
{
|
|
buffslot = AddBuff(caster, spell_id);
|
|
}
|
|
if(buffslot == -1) // stacking failure
|
|
return false;
|
|
} else {
|
|
buffslot = -2; //represents not a buff I guess
|
|
}
|
|
}
|
|
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
Message(0, "You are affected by spell '%s' (id %d)", spell.name, spell_id);
|
|
if(buffslot >= 0)
|
|
{
|
|
Message(0, "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].numhits = 0;
|
|
|
|
if(IsClient() && CastToClient()->GetClientVersionBit() & BIT_UnderfootAndLater)
|
|
{
|
|
EQApplicationPacket *outapp = MakeBuffsPacket(false);
|
|
CastToClient()->FastQueuePacket(&outapp);
|
|
}
|
|
}
|
|
|
|
if(IsNPC())
|
|
{
|
|
if(parse->SpellHasQuestSub(spell_id, "EVENT_SPELL_EFFECT_NPC"))
|
|
{
|
|
parse->EventSpell(EVENT_SPELL_EFFECT_NPC, CastToNPC(), NULL, spell_id, caster ? caster->GetID() : 0);
|
|
CalcBonuses();
|
|
return true;
|
|
}
|
|
}
|
|
else if(IsClient())
|
|
{
|
|
if(parse->SpellHasQuestSub(spell_id, "EVENT_SPELL_EFFECT_CLIENT"))
|
|
{
|
|
parse->EventSpell(EVENT_SPELL_EFFECT_CLIENT, NULL, CastToClient(), spell_id, caster ? caster->GetID() : 0);
|
|
CalcBonuses();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if(spells[spell_id].viral_targets > 0) {
|
|
if(!viral_timer.Enabled())
|
|
viral_timer.Start(1000);
|
|
|
|
has_virus = true;
|
|
for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2)
|
|
{
|
|
if(!viral_spells[i])
|
|
{
|
|
viral_spells[i] = spell_id;
|
|
viral_spells[i+1] = caster->GetID();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(spells[spell_id].numhits > 0 && buffslot >= 0){
|
|
|
|
int numhit = spells[spell_id].numhits;
|
|
|
|
if (caster && caster->IsClient())
|
|
numhit += caster->CastToClient()->GetFocusEffect(focusIncreaseNumHits, spell_id);
|
|
buffs[buffslot].numhits = numhit;
|
|
}
|
|
|
|
// iterate through the effects in the spell
|
|
for (i = 0; i < EFFECT_COUNT; i++)
|
|
{
|
|
if(IsBlankSpellEffect(spell_id, i))
|
|
continue;
|
|
|
|
effect = spell.effectid[i];
|
|
effect_value = CalcSpellEffectValue(spell_id, i, caster_level, caster ? caster : this);
|
|
|
|
if(spell_id == SPELL_LAY_ON_HANDS && caster && caster->GetAA(aaImprovedLayOnHands))
|
|
effect_value = GetMaxHP();
|
|
|
|
#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)
|
|
break;
|
|
|
|
// for offensive spells check if we have a spell rune on
|
|
int32 dmg = effect_value;
|
|
if(dmg < 0)
|
|
{
|
|
// take partial damage into account
|
|
dmg = (int32) (dmg * partial / 100);
|
|
|
|
//handles AAs and what not...
|
|
if(caster)
|
|
{
|
|
dmg = GetVulnerability(dmg, caster, spell_id, 0);
|
|
dmg -= GetAdditionalDamage(caster, spell_id);
|
|
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);
|
|
|
|
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
|
|
|
|
int32 dmg = effect_value;
|
|
if (spell_id == 2751 && caster) //Manaburn
|
|
{
|
|
dmg = caster->GetMana()*-3;
|
|
caster->SetMana(0);
|
|
} else if (spell_id == 2755 && caster) //Lifeburn
|
|
{
|
|
dmg = caster->GetHP()*-15/10;
|
|
caster->SetHP(1);
|
|
if(caster->IsClient()){
|
|
caster->CastToClient()->SetFeigned(true);
|
|
caster->SendAppearancePacket(AT_Anim, 115);
|
|
}
|
|
}
|
|
|
|
//do any AAs apply to these spells?
|
|
if(dmg < 0) {
|
|
dmg = -dmg;
|
|
Damage(caster, dmg, spell_id, spell.skill, false, buffslot, false);
|
|
} else {
|
|
HealDamage(dmg, caster);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_PercentalHeal:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Percental Heal: %+i (%d%% max)", spell.max[i], effect_value);
|
|
#endif
|
|
//im not 100% sure about this implementation.
|
|
//the spell value forumula dosent work for these... at least spell 3232 anyways
|
|
int32 val = spell.max[i];
|
|
|
|
if(caster)
|
|
val = caster->GetActSpellHealing(spell_id, val);
|
|
|
|
int32 mhp = GetMaxHP();
|
|
int32 cap = mhp * spell.base[i] / 100;
|
|
|
|
if(cap < val)
|
|
val = cap;
|
|
|
|
if(val > 0)
|
|
HealDamage(val, caster);
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_CompleteHeal:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Complete Heal");
|
|
#endif
|
|
//make sure they are not allready affected by this...
|
|
//I think that is the point of making this a buff.
|
|
//this is in the wrong spot, it should be in the immune
|
|
//section so the buff timer does not get refreshed!
|
|
|
|
int i;
|
|
bool inuse = false;
|
|
uint32 buff_count = GetMaxTotalSlots();
|
|
for(i = 0; i < buff_count; i++) {
|
|
if(buffs[i].spellid == spell_id && i != buffslot) {
|
|
Message(0, "You must wait before you can be affected by this spell again.");
|
|
inuse = true;
|
|
break;
|
|
}
|
|
}
|
|
if(inuse)
|
|
break;
|
|
|
|
Heal();
|
|
break;
|
|
}
|
|
|
|
case SE_CurrentMana:
|
|
{
|
|
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);
|
|
caster->SetMana(caster->GetMana() + abs(effect_value));
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
caster->Message(0, "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);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_CurrentManaOnce:
|
|
{
|
|
#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[1], spell.base[0],
|
|
spell.base[2], spell.base[3]
|
|
);
|
|
#endif
|
|
if(IsClient())
|
|
{
|
|
|
|
if(caster)
|
|
CastToClient()->SendOPTranslocateConfirm(caster, spell_id);
|
|
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Succor:
|
|
{
|
|
float x, y, z, heading;
|
|
const char *target_zone;
|
|
|
|
x = spell.base[1];
|
|
y = spell.base[0];
|
|
z = spell.base[2];
|
|
heading = spell.base[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())
|
|
{
|
|
// 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
|
|
LogFile->write(EQEMuLog::Debug, "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
|
|
LogFile->write(EQEMuLog::Debug, "Succor/Evacuation Spell To Another Zone.");
|
|
#endif
|
|
if(IsClient())
|
|
CastToClient()->MovePC(target_zone, x, y, z, heading);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case SE_YetAnotherGate: //Shin: Used on Teleport Bind.
|
|
case SE_Teleport: // gates, rings, circles, etc
|
|
case SE_Teleport2:
|
|
{
|
|
float x, y, z, heading;
|
|
const char *target_zone;
|
|
|
|
x = spell.base[1];
|
|
y = spell.base[0];
|
|
z = spell.base[2];
|
|
heading = spell.base[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_YetAnotherGate && caster->IsClient())
|
|
{ //Shin: Teleport Bind uses caster's bind point
|
|
x = caster->CastToClient()->GetBindX();
|
|
y = caster->CastToClient()->GetBindY();
|
|
z = caster->CastToClient()->GetBindZ();
|
|
heading = caster->CastToClient()->GetBindHeading();
|
|
//target_zone = caster->CastToClient()->GetBindZoneId(); target_zone doesn't work due to const char
|
|
CastToClient()->MovePC(caster->CastToClient()->GetBindZoneID(), 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_Invisibility:
|
|
case SE_Invisibility2:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Invisibility");
|
|
#endif
|
|
SetInvisible(spell.base[i]);
|
|
break;
|
|
}
|
|
|
|
case SE_InvisVsAnimals:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Invisibility to Animals");
|
|
#endif
|
|
invisible_animals = true;
|
|
break;
|
|
}
|
|
|
|
case SE_InvisVsUndead2:
|
|
case SE_InvisVsUndead:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Invisibility to Undead");
|
|
#endif
|
|
invisible_undead = true;
|
|
break;
|
|
}
|
|
case SE_SeeInvis:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "See Invisible");
|
|
#endif
|
|
see_invis = spell.base[i];
|
|
break;
|
|
}
|
|
|
|
case SE_FleshToBone:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Flesh To Bone");
|
|
#endif
|
|
if(IsClient()){
|
|
ItemInst* transI = CastToClient()->GetInv().GetItem(SLOT_CURSOR);
|
|
if(transI && transI->IsType(ItemClassCommon) && transI->IsStackable()){
|
|
uint32 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(SLOT_CURSOR, fcharges, true);
|
|
CastToClient()->SummonItem(13073, fcharges);
|
|
}
|
|
else{
|
|
Message(13, "You can only transmute flesh to bone.");
|
|
}
|
|
}
|
|
else{
|
|
Message(13, "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.
|
|
uint32 group_id_caster = 0;
|
|
uint32 time = spell.base[i]*10;
|
|
if(caster->IsClient())
|
|
{
|
|
if(caster->IsGrouped())
|
|
{
|
|
group_id_caster = GetGroup()->GetID();
|
|
}
|
|
else if(caster->IsRaidGrouped())
|
|
{
|
|
group_id_caster = (GetRaid()->GetGroup(CastToClient()) == 0xFFFF) ? 0 : (GetRaid()->GetGroup(CastToClient()) + 1);
|
|
}
|
|
}
|
|
if(group_id_caster){
|
|
Group *g = entity_list.GetGroupByID(group_id_caster);
|
|
uint32 time = spell.base[i]*10;
|
|
if(g){
|
|
for(int gi=0; gi < 6; gi++){
|
|
if(g->members[gi] && g->members[gi]->IsClient())
|
|
{
|
|
g->members[gi]->CastToClient()->EnableAAEffect(aaEffectWarcry , time);
|
|
if (g->members[gi]->GetID() != caster->GetID())
|
|
g->members[gi]->Message(13, "You hear the war cry.");
|
|
else
|
|
Message(13, "You let loose a fierce war cry.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
else{
|
|
CastToClient()->EnableAAEffect(aaEffectWarcry , time);
|
|
Message(13, "You let loose a fierce war cry.");
|
|
}
|
|
|
|
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(),spell.base[0]);
|
|
}
|
|
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[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.
|
|
if(SpecAttacks[UNSTUNABLE] ||
|
|
((GetLevel() > max_level)
|
|
&& caster && (!caster->IsNPC() || (caster->IsNPC() && !RuleB(Spells, NPCIgnoreBaseImmunity)))))
|
|
{
|
|
caster->Message_StringID(MT_SpellFailure, IMMUNE_STUN);
|
|
}
|
|
else
|
|
{
|
|
int stun_resist = itembonuses.StunResist+spellbonuses.StunResist;
|
|
if(IsClient())
|
|
stun_resist += aabonuses.StunResist;
|
|
|
|
if(stun_resist <= 0 || MakeRandomInt(0,99) >= stun_resist)
|
|
{
|
|
mlog(COMBAT__HITS, "Stunned. We had %d percent resist chance.", stun_resist);
|
|
Stun(effect_value);
|
|
}
|
|
else {
|
|
if(IsClient())
|
|
Message_StringID(MT_Stun, SHAKE_OFF_STUN);
|
|
|
|
mlog(COMBAT__HITS, "Stun Resisted. We had %d 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[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.RemoveFromTargets(this);
|
|
WipeHateList();
|
|
|
|
if (IsClient() && caster->IsClient()) {
|
|
caster->Message(0, "Unable to cast charm on a fellow player.");
|
|
BuffFadeByEffect(SE_Charm);
|
|
break;
|
|
} else if(IsCorpse()) {
|
|
caster->Message(0, "Unable to cast charm on a corpse.");
|
|
BuffFadeByEffect(SE_Charm);
|
|
break;
|
|
} else if(caster->GetPet() != NULL && caster->IsClient()) {
|
|
caster->Message(0, "You cannot charm something when you already have a pet.");
|
|
BuffFadeByEffect(SE_Charm);
|
|
break;
|
|
} else if(GetOwner()) {
|
|
caster->Message(0, "You cannot charm someone else's pet!");
|
|
BuffFadeByEffect(SE_Charm);
|
|
break;
|
|
}
|
|
|
|
Mob *my_pet = GetPet();
|
|
if(my_pet)
|
|
{
|
|
my_pet->Kill();
|
|
}
|
|
|
|
caster->SetPet(this);
|
|
SetOwnerID(caster->GetID());
|
|
SetPetOrder(SPO_Follow);
|
|
|
|
if(caster->IsClient()){
|
|
EQApplicationPacket *app = new EQApplicationPacket(OP_Charm, sizeof(Charm_Struct));
|
|
Charm_Struct *ps = (Charm_Struct*)app->pBuffer;
|
|
ps->owner_id = caster->GetID();
|
|
ps->pet_id = this->GetID();
|
|
ps->command = 1;
|
|
entity_list.QueueClients(this, app);
|
|
safe_delete(app);
|
|
SendPetBuffsToClient();
|
|
SendAppearancePacket(AT_Pet, caster->GetID(), true, true);
|
|
}
|
|
|
|
if (IsClient())
|
|
{
|
|
AI_Start();
|
|
SendAppearancePacket(14, 100, true, true);
|
|
} else if(IsNPC()) {
|
|
CastToNPC()->SetPetSpellID(0); //not a pet spell.
|
|
}
|
|
|
|
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].powerful_flag == -1)
|
|
bBreak = true;
|
|
|
|
if (!bBreak)
|
|
{
|
|
int resistMod = partial + (GetCHA()/25);
|
|
resistMod = resistMod > 100 ? 100 : resistMod;
|
|
buffs[buffslot].ticsremaining = resistMod * buffs[buffslot].ticsremaining / 100;
|
|
}
|
|
|
|
if(IsClient())
|
|
{
|
|
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())
|
|
{
|
|
CastToClient()->SetSenseExemption(true);
|
|
|
|
if(CastToClient()->GetClientVersionBit() & BIT_SoDAndLater)
|
|
{
|
|
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);
|
|
|
|
if(ClosestMob)
|
|
{
|
|
Message_StringID(MT_Spells, MessageID);
|
|
SetHeading(CalculateHeadingToTarget(ClosestMob->GetX(), ClosestMob->GetY()));
|
|
SetTarget(ClosestMob);
|
|
CastToClient()->SendTargetCommand(ClosestMob->GetID());
|
|
SendPosUpdate(2);
|
|
}
|
|
else
|
|
Message_StringID(clientMessageError, SENSE_NOTHING);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Fear:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Fear: %+i", effect_value);
|
|
#endif
|
|
//use resistance value for duration...
|
|
buffs[buffslot].ticsremaining = ((buffs[buffslot].ticsremaining * partial) / 100);
|
|
|
|
if(IsClient())
|
|
{
|
|
if(buffs[buffslot].ticsremaining > RuleI(Character, MaxFearDurationForPlayerCharacter))
|
|
buffs[buffslot].ticsremaining = RuleI(Character, MaxFearDurationForPlayerCharacter);
|
|
}
|
|
|
|
if(RuleB(Combat, EnableFearPathing)){
|
|
if(IsClient())
|
|
{
|
|
AI_Start();
|
|
animation = GetRunspeed() * 21; //set our animation to match our speed about
|
|
}
|
|
|
|
CalculateNewFearpoint();
|
|
if(curfp)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Stun(buffs[buffslot].ticsremaining * 6000 - (6000 - tic_timer.GetRemainingTime()));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_BindAffinity:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Bind Affinity");
|
|
#endif
|
|
if (IsClient())
|
|
{
|
|
if(CastToClient()->GetGM() || RuleB(Character, BindAnywhere))
|
|
{
|
|
EQApplicationPacket *action_packet = new EQApplicationPacket(OP_Action, sizeof(Action_Struct));
|
|
Action_Struct* action = (Action_Struct*) action_packet->pBuffer;
|
|
EQApplicationPacket *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->sequence = (GetHeading() * 12345 / 2);
|
|
action->type = 231;
|
|
action->spell = spell_id;
|
|
action->buff_unknown = 4;
|
|
|
|
cd->target = action->target;
|
|
cd->source = action->source;
|
|
cd->type = action->type;
|
|
cd->spellid = action->spell;
|
|
cd->sequence = action->sequence;
|
|
|
|
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();
|
|
Save();
|
|
safe_delete(action_packet);
|
|
safe_delete(message_packet);
|
|
}
|
|
else
|
|
{
|
|
if(!zone->CanBind())
|
|
{
|
|
Message_StringID(MT_SpellFailure, CANNOT_BIND);
|
|
break;
|
|
}
|
|
if(!zone->IsCity())
|
|
{
|
|
if(caster != this)
|
|
{
|
|
Message_StringID(MT_SpellFailure, CANNOT_BIND);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
EQApplicationPacket *action_packet = new EQApplicationPacket(OP_Action, sizeof(Action_Struct));
|
|
Action_Struct* action = (Action_Struct*) action_packet->pBuffer;
|
|
EQApplicationPacket *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->sequence = (GetHeading() * 12345 / 2);
|
|
action->type = 231;
|
|
action->spell = spell_id;
|
|
action->buff_unknown = 4;
|
|
|
|
cd->target = action->target;
|
|
cd->source = action->source;
|
|
cd->type = action->type;
|
|
cd->spellid = action->spell;
|
|
cd->sequence = action->sequence;
|
|
|
|
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();
|
|
Save();
|
|
safe_delete(action_packet);
|
|
safe_delete(message_packet);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EQApplicationPacket *action_packet = new EQApplicationPacket(OP_Action, sizeof(Action_Struct));
|
|
Action_Struct* action = (Action_Struct*) action_packet->pBuffer;
|
|
EQApplicationPacket *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->sequence = (GetHeading() * 12345 / 2);
|
|
action->type = 231;
|
|
action->spell = spell_id;
|
|
action->buff_unknown = 4;
|
|
|
|
cd->target = action->target;
|
|
cd->source = action->source;
|
|
cd->type = action->type;
|
|
cd->spellid = action->spell;
|
|
cd->sequence = action->sequence;
|
|
|
|
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();
|
|
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)
|
|
Gate();
|
|
break;
|
|
}
|
|
|
|
case SE_CancelMagic:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Cancel Magic: %d", effect_value);
|
|
#endif
|
|
if(SpecAttacks[UNDISPELLABLE]){
|
|
caster->Message_StringID(MT_SpellFailure, SPELL_NO_EFFECT, spells[spell_id].name);
|
|
break;
|
|
}
|
|
|
|
uint32 buff_count = GetMaxTotalSlots();
|
|
for(int slot = 0; slot < buff_count; slot++) {
|
|
if( buffs[slot].spellid != SPELL_UNKNOWN &&
|
|
spells[buffs[slot].spellid].buffdurationformula != DF_Permanent &&
|
|
spells[buffs[slot].spellid].dispel_flag < 1 &&
|
|
!IsDiscipline(buffs[slot].spellid))
|
|
{
|
|
BuffFadeBySlot(slot);
|
|
slot = buff_count;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_DispelDetrimental:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Dispel Detrimental: %d", effect_value);
|
|
#endif
|
|
if(SpecAttacks[UNDISPELLABLE]){
|
|
caster->Message_StringID(MT_SpellFailure, SPELL_NO_EFFECT, spells[spell_id].name);
|
|
break;
|
|
}
|
|
|
|
uint32 buff_count = GetMaxTotalSlots();
|
|
for(int slot = 0; slot < buff_count; slot++) {
|
|
if (buffs[slot].spellid != SPELL_UNKNOWN &&
|
|
spells[buffs[slot].spellid].buffdurationformula != DF_Permanent &&
|
|
IsDetrimentalSpell(buffs[slot].spellid) &&
|
|
spells[buffs[slot].spellid].dispel_flag < 1)
|
|
{
|
|
BuffFadeBySlot(slot);
|
|
slot = buff_count;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_DispelBeneficial:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Dispel Beneficial: %d", effect_value);
|
|
#endif
|
|
if(SpecAttacks[UNDISPELLABLE]){
|
|
caster->Message_StringID(MT_SpellFailure, SPELL_NO_EFFECT, spells[spell_id].name);
|
|
break;
|
|
}
|
|
|
|
uint32 buff_count = GetMaxTotalSlots();
|
|
for(int slot = 0; slot < buff_count; slot++) {
|
|
if (buffs[slot].spellid != SPELL_UNKNOWN &&
|
|
spells[buffs[slot].spellid].buffdurationformula != DF_Permanent &&
|
|
IsBeneficialSpell(buffs[slot].spellid) &&
|
|
spells[buffs[slot].spellid].dispel_flag < 1)
|
|
{
|
|
BuffFadeBySlot(slot);
|
|
slot = buff_count;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Mez:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Mesmerize");
|
|
#endif
|
|
Mesmerize();
|
|
break;
|
|
}
|
|
|
|
case SE_SummonItem:
|
|
{
|
|
const Item_Struct *item = database.GetItem(spell.base[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[i]);
|
|
#endif
|
|
if(!item)
|
|
{
|
|
Message(13, "Unable to summon item %d. Item not found.", spell.base[i]);
|
|
}
|
|
else if(IsClient()){
|
|
Client *c=CastToClient();
|
|
if (c->CheckLoreConflict(item)) {
|
|
c->DuplicateLoreMessage(spell.base[i]);
|
|
} else {
|
|
int charges;
|
|
if (spell.formula[i] < 100)
|
|
{
|
|
charges = spell.formula[i];
|
|
}
|
|
else // variable charges
|
|
{
|
|
charges = CalcSpellEffectValue_formula(spell.formula[i], 0, 20, caster_level, spell_id);
|
|
}
|
|
charges = (spell.formula[i] < 100) ? charges : (charges > 20) ? 20 : (spell.max[i] < 1) ? item->MaxCharges : spell.max[i];
|
|
if (SummonedItem) {
|
|
c->PushItemOnCursor(*SummonedItem);
|
|
c->SendItemPacket(SLOT_CURSOR, SummonedItem, ItemPacketSummonItem);
|
|
safe_delete(SummonedItem);
|
|
}
|
|
SummonedItem=database.CreateItem(spell.base[i],charges);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case SE_SummonItemIntoBag:
|
|
{
|
|
const Item_Struct *item = database.GetItem(spell.base[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[i]);
|
|
#endif
|
|
uint8 slot;
|
|
|
|
if (!SummonedItem || !SummonedItem->IsType(ItemClassContainer)) {
|
|
if(caster) caster->Message(13,"SE_SummonItemIntoBag but no bag has been summoned!");
|
|
} else if ((slot=SummonedItem->FirstOpenSlot())==0xff) {
|
|
if(caster) caster->Message(13,"SE_SummonItemIntoBag but no room in summoned bag!");
|
|
} else if (IsClient()) {
|
|
if (CastToClient()->CheckLoreConflict(item)) {
|
|
CastToClient()->DuplicateLoreMessage(spell.base[i]);
|
|
} else {
|
|
int charges;
|
|
if (spell.formula[i] < 100)
|
|
{
|
|
charges = spell.formula[i];
|
|
}
|
|
else // variable charges
|
|
{
|
|
charges = CalcSpellEffectValue_formula(spell.formula[i], 0, 20, caster_level, spell_id);
|
|
}
|
|
charges = charges < 1 ? 1 : (charges > 20 ? 20 : charges);
|
|
ItemInst *SubItem=database.CreateItem(spell.base[i],charges);
|
|
if (SubItem!=NULL) {
|
|
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())
|
|
{
|
|
Message_StringID(MT_Shout, ONLY_ONE_PET);
|
|
}
|
|
else
|
|
{
|
|
MakePet(spell_id, spell.teleport_zone);
|
|
}
|
|
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].buffduration; // 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();
|
|
}
|
|
// solar: 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
|
|
if (spells[spell_id].base[i] == 1)
|
|
BuffFadeByEffect(SE_Blind);
|
|
// solar: handled by client
|
|
// TODO: blind flag?
|
|
break;
|
|
}
|
|
|
|
case SE_Rune:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Melee Absorb Rune: %+i", effect_value);
|
|
#endif
|
|
effect_value = ApplySpellEffectiveness(caster, spell_id, effect_value);
|
|
buffs[buffslot].melee_rune = effect_value;
|
|
SetHasRune(true);
|
|
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;
|
|
SetHasSpellRune(true);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_MitigateMeleeDamage:
|
|
{
|
|
buffs[buffslot].melee_rune = GetPartialMeleeRuneAmount(spell_id);
|
|
SetHasPartialMeleeRune(true);
|
|
break;
|
|
}
|
|
|
|
case SE_MitigateSpellDamage:
|
|
{
|
|
buffs[buffslot].magic_rune = GetPartialMagicRuneAmount(spell_id);
|
|
SetHasPartialSpellRune(true);
|
|
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.
|
|
SendAppearancePacket(AT_Levitate, 2, true, true);
|
|
break;
|
|
}
|
|
|
|
case SE_Illusion:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Illusion: race %d", effect_value);
|
|
#endif
|
|
// Gender Illusions
|
|
if(spell.base[i] == -1) {
|
|
// Specific Gender Illusions
|
|
if(spell_id == 1732 || spell_id == 1731) {
|
|
int specific_gender = -1;
|
|
// Male
|
|
if(spell_id == 1732)
|
|
specific_gender = 0;
|
|
// Female
|
|
else if (spell_id == 1731)
|
|
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 {
|
|
SendIllusionPacket
|
|
(
|
|
spell.base[i],
|
|
Mob::GetDefaultGender(spell.base[i], GetGender()),
|
|
spell.base2[i]
|
|
);
|
|
if(spell.base[i] == OGRE){
|
|
SendAppearancePacket(AT_Size, 9);
|
|
}
|
|
else if(spell.base[i] == TROLL){
|
|
SendAppearancePacket(AT_Size, 8);
|
|
}
|
|
else if(spell.base[i] == VAHSHIR || spell.base[i] == BARBARIAN){
|
|
SendAppearancePacket(AT_Size, 7);
|
|
}
|
|
else if(spell.base[i] == HALF_ELF || spell.base[i] == WOOD_ELF || spell.base[i] == DARK_ELF || spell.base[i] == FROGLOK){
|
|
SendAppearancePacket(AT_Size, 5);
|
|
}
|
|
else if(spell.base[i] == DWARF){
|
|
SendAppearancePacket(AT_Size, 4);
|
|
}
|
|
else if(spell.base[i] == HALFLING || spell.base[i] == GNOME){
|
|
SendAppearancePacket(AT_Size, 3);
|
|
}
|
|
else if(spell.base[i] == WOLF) {
|
|
SendAppearancePacket(AT_Size, 2);
|
|
}
|
|
else{
|
|
SendAppearancePacket(AT_Size, 6);
|
|
}
|
|
}
|
|
for(int x = 0; x < 7; x++){
|
|
SendWearChange(x);
|
|
}
|
|
if(caster && caster->GetAA(aaPermanentIllusion))
|
|
buffs[buffslot].persistant_buff = 1;
|
|
else
|
|
buffs[buffslot].persistant_buff = 0;
|
|
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, caster->GetTarget()->GetSize());
|
|
for(int x = 0; x < 7; x++){
|
|
caster->SendWearChange(x);
|
|
}
|
|
}
|
|
}
|
|
|
|
case SE_WipeHateList:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Memory Blur: %d", effect_value);
|
|
#endif
|
|
int wipechance = spells[spell_id].base[i];
|
|
if(MakeRandomInt(0, 100) < wipechance)
|
|
{
|
|
if(IsAIControlled())
|
|
{
|
|
WipeHateList();
|
|
}
|
|
Message(13, "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
|
|
// solar: the spinning is handled by the client
|
|
int max_level = spells[spell_id].max[i];
|
|
if(max_level == 0)
|
|
max_level = RuleI(Spells, BaseImmunityLevel); // Default max is 55 level limit
|
|
|
|
// NPCs ignore level limits in their spells
|
|
if(SpecAttacks[UNSTUNABLE] ||
|
|
((GetLevel() > max_level)
|
|
&& caster && (!caster->IsNPC() || (caster->IsNPC() && !RuleB(Spells, NPCIgnoreBaseImmunity)))))
|
|
{
|
|
caster->Message_StringID(MT_Shout, IMMUNE_STUN);
|
|
}
|
|
else
|
|
{
|
|
// solar: 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;
|
|
caster->TemporaryPets(spell_id, NULL, eye_name, duration);
|
|
}
|
|
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
|
|
)
|
|
{
|
|
int lvlmod = 4;
|
|
if(caster->IsClient() && caster->CastToClient()->GetAA(aaImprovedReclaimEnergy))
|
|
lvlmod = 8; //this is an unconfirmed number, I made it up
|
|
if(caster->IsClient() && caster->CastToClient()->GetAA(aaImprovedReclaimEnergy2))
|
|
lvlmod = 8; //this is an unconfirmed number, I made it up
|
|
caster->SetMana(caster->GetMana()+(GetLevel()*lvlmod));
|
|
|
|
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
|
|
//todo, look up spell ID in DB
|
|
if(spell_id == 2488) //Dook- Lifeburn fix
|
|
break;
|
|
|
|
if(IsClient())
|
|
CastToClient()->SetFeigned(true);
|
|
break;
|
|
}
|
|
|
|
case SE_Sentinel:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Sentinel");
|
|
#endif
|
|
if(caster)
|
|
{
|
|
if(caster == this)
|
|
{
|
|
Message_StringID(MT_Spells,
|
|
SENTINEL_TRIG_YOU);
|
|
}
|
|
else
|
|
{
|
|
caster->Message_StringID(MT_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()->GetClientVersionBit() & BIT_SoDAndLater))
|
|
CastToClient()->LocateCorpse();
|
|
|
|
break;
|
|
}
|
|
|
|
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)
|
|
mlog(SPELLS__REZ, " corpse being rezzed using spell %i by %s",
|
|
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
|
|
ChangeSize(GetSize() * (effect_value / 100.0));
|
|
break;
|
|
}
|
|
|
|
case SE_Root:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Root: %+i", effect_value);
|
|
#endif
|
|
rooted = true;
|
|
rooted_mod = 0;
|
|
|
|
if (caster){
|
|
rooted_mod = 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
|
|
{
|
|
CastToClient()->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 = 0;
|
|
if(this->GetTarget())
|
|
TargetClient = this->GetTarget()->CastToClient();
|
|
else
|
|
TargetClient = this->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(13, "Your target must be a group member for this spell.");
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
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(13, "Your target must be a group member for this spell.");
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if(TargetClient != this->CastToClient()) {
|
|
Message(13, "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) {
|
|
Corpse *corpse = entity_list.GetCorpseByOwner(TargetClient);
|
|
if(corpse) {
|
|
if(TargetClient == this->CastToClient())
|
|
Message_StringID(4, SUMMONING_CORPSE, TargetClient->CastToMob()->GetCleanName());
|
|
else
|
|
Message_StringID(4, SUMMONING_CORPSE_OTHER, TargetClient->CastToMob()->GetCleanName());
|
|
|
|
corpse->Summon(CastToClient(), true, true);
|
|
}
|
|
else {
|
|
// No corpse found in the zone
|
|
Message_StringID(4, CORPSE_CANT_SENSE);
|
|
}
|
|
}
|
|
else {
|
|
Message_StringID(4, TARGET_NOT_FOUND);
|
|
LogFile->write(EQEMuLog::Error, "%s attempted to cast spell id %u with spell effect SE_SummonCorpse, 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
|
|
|
|
if(spells[spell_id].base2[i] == 0)
|
|
AddProcToWeapon(procid, false, 100);
|
|
else
|
|
AddProcToWeapon(procid, false, spells[spell_id].base2[i]+100);
|
|
break;
|
|
}
|
|
|
|
case SE_SkillProc2:
|
|
case SE_SkillProc:
|
|
{
|
|
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
|
|
if(spells[spell_id].base2[i] == 0)
|
|
AddSkillProc(procid, 100, spell_id);
|
|
else
|
|
AddSkillProc(procid, spells[spell_id].base2[i]+100, spell_id);
|
|
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].numhits = effect_value;
|
|
break;
|
|
}
|
|
case SE_AppraiseLDonChest:
|
|
{
|
|
if(IsNPC())
|
|
{
|
|
int check = spell.max[0];
|
|
int target = spell.targettype;
|
|
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[0];
|
|
int target = spell.targettype;
|
|
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[0];
|
|
int target = spell.targettype;
|
|
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;
|
|
uint32 buff_count = GetMaxTotalSlots();
|
|
for (int j=0; j < buff_count; j++) {
|
|
if (buffs[j].spellid >= (uint16)SPDAT_RECORDS)
|
|
continue;
|
|
if (CalculatePoisonCounters(buffs[j].spellid) == 0)
|
|
continue;
|
|
if (effect_value >= buffs[j].counters) {
|
|
if (caster)
|
|
caster->Message(MT_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;
|
|
uint32 buff_count = GetMaxTotalSlots();
|
|
for (int j=0; j < buff_count; j++) {
|
|
if (buffs[j].spellid >= (uint16)SPDAT_RECORDS)
|
|
continue;
|
|
if (CalculateDiseaseCounters(buffs[j].spellid) == 0)
|
|
continue;
|
|
if (effect_value >= buffs[j].counters)
|
|
{
|
|
if (caster)
|
|
caster->Message(MT_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;
|
|
uint32 buff_count = GetMaxTotalSlots();
|
|
for (int j=0; j < buff_count; j++) {
|
|
if (buffs[j].spellid >= (uint16)SPDAT_RECORDS)
|
|
continue;
|
|
if (CalculateCurseCounters(buffs[j].spellid) == 0)
|
|
continue;
|
|
if (effect_value >= buffs[j].counters)
|
|
{
|
|
if (caster)
|
|
caster->Message(MT_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;
|
|
uint32 buff_count = GetMaxTotalSlots();
|
|
for (int j=0; j < buff_count; j++) {
|
|
if (buffs[j].spellid >= (uint16)SPDAT_RECORDS)
|
|
continue;
|
|
if (CalculateCorruptionCounters(buffs[j].spellid) == 0)
|
|
continue;
|
|
if (effect_value >= buffs[j].counters) {
|
|
if (caster)
|
|
caster->Message(MT_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();
|
|
else
|
|
Message(13, "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
|
|
double toss_amt = (double)spells[spell_id].base[i];
|
|
if(toss_amt < 0)
|
|
toss_amt = -toss_amt;
|
|
|
|
if(IsNPC())
|
|
{
|
|
Stun(toss_amt);
|
|
}
|
|
toss_amt = sqrt(toss_amt)-2.0;
|
|
|
|
if(toss_amt < 0.0)
|
|
toss_amt = 0.0;
|
|
|
|
if(toss_amt > 20.0)
|
|
toss_amt = 20.0;
|
|
|
|
if(IsClient())
|
|
{
|
|
CastToClient()->SetKnockBackExemption(true);
|
|
}
|
|
|
|
double look_heading = GetHeading();
|
|
look_heading /= 256;
|
|
look_heading *= 360;
|
|
look_heading += 180;
|
|
if(look_heading > 360)
|
|
look_heading -= 360;
|
|
|
|
//x and y are crossed mkay
|
|
double new_x = spells[spell_id].pushback * sin(double(look_heading * 3.141592 / 180.0));
|
|
double new_y = spells[spell_id].pushback * cos(double(look_heading * 3.141592 / 180.0));
|
|
|
|
EQApplicationPacket* outapp_push = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
|
|
PlayerPositionUpdateServer_Struct* spu = (PlayerPositionUpdateServer_Struct*)outapp_push->pBuffer;
|
|
|
|
spu->spawn_id = GetID();
|
|
spu->x_pos = FloatToEQ19(GetX());
|
|
spu->y_pos = FloatToEQ19(GetY());
|
|
spu->z_pos = FloatToEQ19(GetZ());
|
|
spu->delta_x = NewFloatToEQ13(new_x);
|
|
spu->delta_y = NewFloatToEQ13(new_y);
|
|
spu->delta_z = NewFloatToEQ13(toss_amt);
|
|
spu->heading = FloatToEQ19(GetHeading());
|
|
spu->padding0002 =0;
|
|
spu->padding0006 =7;
|
|
spu->padding0014 =0x7f;
|
|
spu->padding0018 =0x5df27;
|
|
spu->animation = 0;
|
|
spu->delta_heading = NewFloatToEQ13(0);
|
|
outapp_push->priority = 5;
|
|
entity_list.QueueClients(this, outapp_push, true);
|
|
if(IsClient())
|
|
CastToClient()->FastQueuePacket(&outapp_push);
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_StopRain:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Stop Rain");
|
|
#endif
|
|
zone->zone_weather = 0;
|
|
zone->weatherSend();
|
|
break;
|
|
}
|
|
|
|
case SE_Sacrifice:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Sacrifice");
|
|
#endif
|
|
if(!IsClient() || !caster->IsClient()){
|
|
break;
|
|
}
|
|
CastToClient()->SacrificeConfirm(caster->CastToClient());
|
|
break;
|
|
}
|
|
|
|
case SE_SummonPC:
|
|
{
|
|
if(IsClient()){
|
|
CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), caster->GetX(), caster->GetY(), caster->GetZ(), caster->GetHeading(), 2, SummonPC);
|
|
Message(15, "You have been summoned!");
|
|
entity_list.ClearAggro(this);
|
|
}
|
|
else
|
|
caster->Message(13, "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
|
|
// solar: this is cast on self, not on the pet
|
|
if(GetPet() && GetPet()->IsNPC())
|
|
{
|
|
GetPet()->CastToNPC()->GMMove(GetX(), GetY(), GetZ(), GetHeading());
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_StackingCommand_Block:
|
|
case SE_StackingCommand_Overwrite:
|
|
{
|
|
// solar: these are special effects used by the buff stuff
|
|
break;
|
|
}
|
|
|
|
|
|
case SE_TemporaryPets: //Dook- swarms and wards:
|
|
{
|
|
// EverHood - 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, NULL);
|
|
break;
|
|
}
|
|
|
|
case SE_FadingMemories: //Dook- escape etc
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Fading Memories");
|
|
#endif
|
|
entity_list.RemoveFromTargets(caster);
|
|
SetInvisible(1);
|
|
break;
|
|
}
|
|
|
|
case SE_RangedProc:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Ranged Proc: %+i", effect_value);
|
|
#endif
|
|
uint16 procid = GetProcID(spell_id, i);
|
|
|
|
if(spells[spell_id].base2[i] == 0)
|
|
AddRangedProc(procid, 100, spell_id);
|
|
else
|
|
AddRangedProc(procid, spells[spell_id].base2[i]+100, spell_id);
|
|
break;
|
|
}
|
|
|
|
case SE_Rampage:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Rampage");
|
|
#endif
|
|
if(caster)
|
|
entity_list.AEAttack(caster, 30, 13, 0, true); // on live wars dont get a duration ramp, its a one shot deal
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_AEMelee:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Duration Rampage");
|
|
#endif
|
|
if (caster && caster->IsClient()) { // will tidy this up later so that NPCs can duration ramp from spells too
|
|
CastToClient()->DurationRampage(effect_value*12);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_AETaunt://Dook- slapped it in the spell effect so client does the animations
|
|
{ // and incase there are similar spells we havent found yet
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "AE Taunt");
|
|
#endif
|
|
if(caster && caster->IsClient())
|
|
entity_list.AETaunt(caster->CastToClient());
|
|
break;
|
|
}
|
|
|
|
case SE_SkillAttack:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Skill Attack");
|
|
#endif
|
|
/*Kayen:
|
|
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)
|
|
*/
|
|
int16 focus = 0;
|
|
|
|
if(caster->IsClient())
|
|
focus = caster->CastToClient()->GetFocusEffect(focusSpellEffectiveness, spell_id);
|
|
|
|
switch(spells[spell_id].skill)
|
|
{
|
|
case THROWING:
|
|
caster->DoThrowingAttackDmg(this, NULL, NULL, spells[spell_id].base[i],spells[spell_id].base2[i], focus);
|
|
break;
|
|
|
|
case ARCHERY:
|
|
caster->DoArcheryAttackDmg(this, NULL, NULL, spells[spell_id].base[i],spells[spell_id].base2[i],focus);
|
|
break;
|
|
|
|
default:
|
|
caster->DoMeleeSkillAttackDmg(this, spells[spell_id].base[i], spells[spell_id].skill, spells[spell_id].base2[i], focus);
|
|
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->IsClient()){
|
|
//this spell doesn't appear to actually contain the information on duration inside of it oddly
|
|
int dur = 60;
|
|
if(spell_id == 3269)
|
|
dur += 15;
|
|
else if(spell_id == 3270)
|
|
dur += 30;
|
|
|
|
caster->WakeTheDead(spell_id, caster->GetTarget(), dur);
|
|
}
|
|
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[i];
|
|
int pet_duration = spells[spell_id].max[i];
|
|
caster->CastToClient()->Doppelganger(spell_id, this, pet_name, pet_count, pet_duration);
|
|
}
|
|
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
|
|
if(spells[spell_id].base2[i] == 0)
|
|
AddDefensiveProc(procid, 100,spell_id);
|
|
else
|
|
AddDefensiveProc(procid, spells[spell_id].base2[i]+100,spell_id);
|
|
break;
|
|
|
|
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;
|
|
|
|
// for offensive spells check if we have a spell rune on
|
|
int32 dmg = effect_value;
|
|
if(dmg < 0)
|
|
{
|
|
// take partial damage into account
|
|
dmg = (int32) (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);
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
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[i], gid);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Group *g = entity_list.GetGroupByClient(caster->CastToClient());
|
|
|
|
if(!g)
|
|
break;
|
|
|
|
g->BalanceHP(spell.base[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[i], gid);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Group *g = entity_list.GetGroupByClient(caster->CastToClient());
|
|
|
|
if(!g)
|
|
break;
|
|
|
|
g->BalanceMana(spell.base[i]);
|
|
break;
|
|
}
|
|
|
|
case SE_DeathSave: {
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Death Save: %+i", effect_value);
|
|
#endif
|
|
uint8 BonusChance = 0;
|
|
if(caster) {
|
|
|
|
BonusChance = caster->aabonuses.UnfailingDivinity +
|
|
caster->itembonuses.UnfailingDivinity +
|
|
caster->spellbonuses.UnfailingDivinity;
|
|
}
|
|
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
//snprintf(effect_desc, _EDLEN, "Death Save Chance: %+i", SuccessChance);
|
|
#endif
|
|
//buffs[buffslot].deathSaveSuccessChance = SuccessChance;
|
|
//buffs[buffslot].deathsaveCasterAARank = caster->GetAA(aaUnfailingDivinity);
|
|
buffs[buffslot].deathsaveCasterAARank = BonusChance;
|
|
//SetDeathSaveChance(true);
|
|
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_SummonAndResAllCorpses:
|
|
{
|
|
if(IsClient())
|
|
CastToClient()->SummonAndRezzAllCorpses();
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_GateToHomeCity:
|
|
{
|
|
if(IsClient())
|
|
CastToClient()->GoToBind(4);
|
|
break;
|
|
}
|
|
|
|
case SE_SuspendMinion:
|
|
case SE_SuspendPet:
|
|
{
|
|
if(IsClient())
|
|
CastToClient()->SuspendMinion();
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_Forceful_Rejuv:
|
|
{
|
|
if(IsClient()) {
|
|
for(unsigned int i =0 ; i < MAX_PP_MEMSPELL; ++i) {
|
|
if(IsValidSpell(CastToClient()->m_pp.mem_spells[i])) {
|
|
CastToClient()->m_pp.spellSlotRefresh[i] = 1;
|
|
CastToClient()->GetPTimers().Clear(&database, (pTimerSpellStart + CastToClient()->m_pp.mem_spells[i]));
|
|
}
|
|
}
|
|
SetMana(GetMana());
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_HealGroupFromMana: {
|
|
if(!caster)
|
|
break;
|
|
|
|
if(!caster->IsClient())
|
|
break;
|
|
|
|
uint32 max_mana = spell.base[i];
|
|
int ratio = spell.base2[i];
|
|
uint32 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);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Group *g = entity_list.GetGroupByClient(caster->CastToClient());
|
|
|
|
if(!g){
|
|
caster->HealDamage(heal_amt);
|
|
break;
|
|
}
|
|
|
|
g->HealGroup(heal_amt, caster);
|
|
break;
|
|
}
|
|
|
|
case SE_ManaDrainWithDmg:
|
|
{
|
|
int mana_damage = 0;
|
|
int32 mana_to_use = GetMana() - spell.base[i];
|
|
if(mana_to_use > -1) {
|
|
SetMana(GetMana() - spell.base[i]);
|
|
// we take full dmg(-10 to make the damage the right sign)
|
|
mana_damage = spell.base[i] / -10 * spell.base2[i];
|
|
Damage(caster, mana_damage, spell_id, spell.skill, false, i, true);
|
|
}
|
|
else {
|
|
mana_damage = GetMana() / -10 * spell.base2[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[i];
|
|
if(end_to_use > -1) {
|
|
CastToClient()->SetEndurance(CastToClient()->GetEndurance() - spell.base[i]);
|
|
// we take full dmg(-10 to make the damage the right sign)
|
|
end_damage = spell.base[i] / -10 * spell.base2[i];
|
|
Damage(caster, end_damage, spell_id, spell.skill, false, i, true);
|
|
}
|
|
else {
|
|
end_damage = CastToClient()->GetEndurance() / -10 * spell.base2[i];
|
|
CastToClient()->SetEndurance(0);
|
|
Damage(caster, end_damage, spell_id, spell.skill, false, i, true);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_SetBodyType:
|
|
{
|
|
SetBodyType((bodyType)spell.base[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[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()*2);
|
|
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:
|
|
{
|
|
uint32 max_mana = spell.base[i];
|
|
int ratio = spell.base2[i];
|
|
int32 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);
|
|
}
|
|
|
|
if(IsDetrimentalSpell(spell_id)) {
|
|
dmg = -dmg;
|
|
Damage(caster, dmg, spell_id, spell.skill, false, buffslot, false);
|
|
} else {
|
|
HealDamage(dmg, caster);
|
|
}
|
|
}
|
|
}
|
|
|
|
case SE_Taunt:
|
|
{
|
|
if (IsNPC())
|
|
caster->Taunt(this->CastToNPC(), false, spell.base[i]);
|
|
}
|
|
|
|
case SE_Purify:
|
|
{
|
|
/*
|
|
Guessing as to exactly how this effect works.
|
|
All spells that utilize it 'remove all determental effects'
|
|
with a value of (20). Lets assume the value determines
|
|
how many determental effects can be removed.
|
|
*/
|
|
uint32 buff_count = GetMaxTotalSlots();
|
|
int FadeCount = 0;
|
|
|
|
for (int j = 0; j <= buff_count; j++) {
|
|
if(buffs[j].spellid != SPELL_UNKNOWN) {
|
|
if((FadeCount <= spell.base[i]) && IsDetrimentalSpell(buffs[j].spellid)){
|
|
BuffFadeBySlot(j, false);
|
|
FadeCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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_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_SingingSkill:
|
|
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_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_Hate2:
|
|
case SE_Identify:
|
|
case SE_Calm:
|
|
case SE_ReduceHate:
|
|
case SE_SpellDamageShield:
|
|
case SE_ReverseDS:
|
|
case SE_DamageShield:
|
|
case SE_TrueNorth:
|
|
case SE_WaterBreathing:
|
|
case SE_MovementSpeed:
|
|
case SE_HealOverTime:
|
|
case SE_PercentXPIncrease:
|
|
case SE_DivineSave:
|
|
case SE_Accuracy:
|
|
case SE_Flurry:
|
|
case SE_AttackSpeed:
|
|
case SE_AttackSpeed2:
|
|
case SE_AttackSpeed3:
|
|
case SE_AttackSpeed4:
|
|
case SE_ImprovedDamage:
|
|
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_LimitCastTime:
|
|
case SE_LimitManaCost:
|
|
case SE_CombatSkills:
|
|
case SE_SpellDurationIncByTic:
|
|
case SE_TriggerOnCast:
|
|
case SE_HealRate:
|
|
case SE_SkillDamageTaken:
|
|
case SE_SpellVulnerability:
|
|
case SE_SpellTrigger:
|
|
case SE_ApplyEffect:
|
|
case SE_Twincast:
|
|
case SE_DelayDeath:
|
|
case SE_InterruptCasting:
|
|
case SE_ImprovedSpellEffect:
|
|
case SE_BossSpellTrigger:
|
|
case SE_CastOnWearoff:
|
|
case SE_EffectOnFade:
|
|
case SE_MaxHPChange:
|
|
case SE_SympatheticProc:
|
|
case SE_SpellDamage:
|
|
case SE_CriticalSpellChance:
|
|
case SE_SpellCritChance:
|
|
case SE_SpellCritDmgIncrease:
|
|
case SE_DotCritDmgIncrease:
|
|
case SE_CriticalHealChance:
|
|
case SE_CriticalHealOverTime:
|
|
case SE_CriticalDoTChance:
|
|
case SE_SpellOnKill:
|
|
case SE_SpellOnKill2:
|
|
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_FF_Damage_Amount:
|
|
case SE_AdditionalHeal:
|
|
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_ImprovedDamage2:
|
|
case SE_AdditionalHeal2:
|
|
case SE_HealRate2:
|
|
case SE_CriticalHealChance2:
|
|
case SE_CriticalHealOverTime2:
|
|
case SE_Empathy:
|
|
case SE_LimitSpellSkill:
|
|
case SE_MitigateDamageShield:
|
|
case SE_IncreaseSpellPower:
|
|
case SE_LimitClass:
|
|
case SE_LimitExcludeSkill:
|
|
case SE_BlockBehind:
|
|
case SE_ShieldBlock:
|
|
case SE_PetCriticalHit:
|
|
case SE_SlayUndead:
|
|
case SE_GiveDoubleAttack:
|
|
case SE_StrikeThrough2:
|
|
case SE_SecondaryDmgInc:
|
|
case SE_ArcheryDamageModifier:
|
|
case SE_ConsumeProjectile:
|
|
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_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_CriticalHealRate:
|
|
case SE_IncreaseNumHits:
|
|
{
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
snprintf(effect_desc, _EDLEN, "Unknown Effect ID %d", effect);
|
|
#else
|
|
Message(0, "Unknown spell effect %d in spell %s (id %d)", effect, spell.name, spell_id);
|
|
#endif
|
|
}
|
|
}
|
|
#ifdef SPELL_EFFECT_SPAM
|
|
Message(0, ". . . 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(SLOT_CURSOR, SummonedItem, ItemPacketSummonItem);
|
|
safe_delete(SummonedItem);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int Mob::CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level, Mob *caster, int ticsremaining)
|
|
{
|
|
int formula, base, max, effect_value;
|
|
|
|
if
|
|
(
|
|
!IsValidSpell(spell_id) ||
|
|
effect_id < 0 ||
|
|
effect_id >= EFFECT_COUNT
|
|
)
|
|
return 0;
|
|
|
|
formula = spells[spell_id].formula[effect_id];
|
|
base = spells[spell_id].base[effect_id];
|
|
max = spells[spell_id].max[effect_id];
|
|
|
|
if(IsBlankSpellEffect(spell_id, effect_id))
|
|
return 0;
|
|
|
|
effect_value = CalcSpellEffectValue_formula(formula, base, max, caster_level, spell_id, ticsremaining);
|
|
|
|
if(caster && IsBardSong(spell_id) &&
|
|
(spells[spell_id].effectid[effect_id] != SE_AttackSpeed) &&
|
|
(spells[spell_id].effectid[effect_id] != SE_AttackSpeed2) &&
|
|
(spells[spell_id].effectid[effect_id] != SE_AttackSpeed3) &&
|
|
(spells[spell_id].effectid[effect_id] != SE_Lull) &&
|
|
(spells[spell_id].effectid[effect_id] != SE_ChangeFrenzyRad) &&
|
|
(spells[spell_id].effectid[effect_id] != SE_Harmony) &&
|
|
(spells[spell_id].effectid[effect_id] != SE_CurrentMana)&&
|
|
(spells[spell_id].effectid[effect_id] != SE_ManaRegen_v2))
|
|
{
|
|
|
|
int oval = effect_value;
|
|
int mod = caster->GetInstrumentMod(spell_id);
|
|
mod = ApplySpellEffectiveness(caster, spell_id, mod, true);
|
|
effect_value = effect_value * mod / 10;
|
|
mlog(SPELLS__BARDS, "Effect value %d altered with bard modifier of %d to yeild %d", oval, mod, effect_value);
|
|
}
|
|
|
|
return(effect_value);
|
|
}
|
|
|
|
// solar: generic formula calculations
|
|
int Mob::CalcSpellEffectValue_formula(int formula, int base, int max, int caster_level, uint16 spell_id, int ticsremaining)
|
|
{
|
|
/*
|
|
neotokyo: 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
|
|
*/
|
|
|
|
int result = 0, updownsign = 1, ubase = base;
|
|
if(ubase < 0)
|
|
ubase = 0 - ubase;
|
|
|
|
// solar: 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 < base && max != 0)
|
|
{
|
|
// values are calculated down
|
|
updownsign = -1;
|
|
}
|
|
else
|
|
{
|
|
// values are calculated up
|
|
updownsign = 1;
|
|
}
|
|
|
|
mlog(SPELLS__EFFECT_VALUES, "CSEV: spell %d, formula %d, base %d, max %d, lvl %d. Up/Down %d",
|
|
spell_id, formula, base, max, caster_level, updownsign);
|
|
|
|
switch(formula)
|
|
{
|
|
case 60: //used in stun spells..?
|
|
case 70:
|
|
result = ubase/100; break;
|
|
case 0:
|
|
case 100: // solar: confirmed 2/6/04
|
|
result = ubase; break;
|
|
case 101: // solar: confirmed 2/6/04
|
|
result = updownsign * (ubase + (caster_level / 2)); break;
|
|
case 102: // solar: confirmed 2/6/04
|
|
result = updownsign * (ubase + caster_level); break;
|
|
case 103: // solar: confirmed 2/6/04
|
|
result = updownsign * (ubase + (caster_level * 2)); break;
|
|
case 104: // solar: confirmed 2/6/04
|
|
result = updownsign * (ubase + (caster_level * 3)); break;
|
|
case 105: // solar: confirmed 2/6/04
|
|
result = updownsign * (ubase + (caster_level * 4)); break;
|
|
|
|
case 107:
|
|
//Used on Reckless Strength, I think it should decay over time
|
|
result = updownsign * (ubase + (caster_level / 2)); break;
|
|
case 108:
|
|
result = updownsign * (ubase + (caster_level / 3)); break;
|
|
case 109: // solar: confirmed 2/6/04
|
|
result = updownsign * (ubase + (caster_level / 4)); break;
|
|
|
|
case 110: // solar: confirmed 2/6/04
|
|
//is there a reason we dont use updownsign here???
|
|
result = ubase + (caster_level / 5); break;
|
|
|
|
case 111:
|
|
result = updownsign * (ubase + 6 * (caster_level - GetMinLevel(spell_id))); break;
|
|
case 112:
|
|
result = updownsign * (ubase + 8 * (caster_level - GetMinLevel(spell_id))); break;
|
|
case 113:
|
|
result = updownsign * (ubase + 10 * (caster_level - GetMinLevel(spell_id))); break;
|
|
case 114:
|
|
result = updownsign * (ubase + 15 * (caster_level - GetMinLevel(spell_id))); break;
|
|
|
|
//these formula were updated according to lucy 10/16/04
|
|
case 115: // solar: this is only in symbol of transal
|
|
result = ubase + 6 * (caster_level - GetMinLevel(spell_id)); break;
|
|
case 116: // solar: this is only in symbol of ryltan
|
|
result = ubase + 8 * (caster_level - GetMinLevel(spell_id)); break;
|
|
case 117: // solar: this is only in symbol of pinzarn
|
|
result = ubase + 12 * (caster_level - GetMinLevel(spell_id)); break;
|
|
case 118: // solar: used in naltron and a few others
|
|
result = ubase + 20 * (caster_level - GetMinLevel(spell_id)); break;
|
|
|
|
case 119: // solar: confirmed 2/6/04
|
|
result = ubase + (caster_level / 8); break;
|
|
case 121: // solar: corrected 2/6/04
|
|
result = ubase + (caster_level / 3); break;
|
|
case 122:
|
|
{
|
|
// May need to account for duration focus effects
|
|
int ticdif = spells[spell_id].buffduration - (ticsremaining - 1);
|
|
if(ticdif < 0)
|
|
ticdif = 0;
|
|
|
|
result = updownsign * (ubase - (12 * ticdif));
|
|
break;
|
|
}
|
|
case 123: // solar: added 2/6/04
|
|
result = MakeRandomInt(ubase, abs(max));
|
|
break;
|
|
|
|
//these are used in stacking effects... formula unknown
|
|
case 201:
|
|
case 203:
|
|
result = max;
|
|
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
|
|
int ticdif = spells[spell_id].buffduration - (ticsremaining - 1);
|
|
if(ticdif < 0)
|
|
ticdif = 0;
|
|
|
|
result = updownsign * (ubase - ((formula - 1000) * ticdif));
|
|
}
|
|
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
|
|
LogFile->write(EQEMuLog::Debug, "Unknown spell effect value forumula %d", formula);
|
|
}
|
|
}
|
|
|
|
int oresult = result;
|
|
|
|
// now check result against the allowed maximum
|
|
if (max != 0)
|
|
{
|
|
if (updownsign == 1)
|
|
{
|
|
if (result > max)
|
|
result = max;
|
|
}
|
|
else
|
|
{
|
|
if (result < max)
|
|
result = max;
|
|
}
|
|
}
|
|
|
|
// if base is less than zero, then the result need to be negative too
|
|
if (base < 0 && result > 0)
|
|
result *= -1;
|
|
|
|
mlog(SPELLS__EFFECT_VALUES, "Result: %d (orig %d), cap %d %s", result, oresult, max, (base < 0 && result > 0)?"Inverted due to negative base":"");
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
void Mob::BuffProcess()
|
|
{
|
|
uint32 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].spellid, buffs[buffs_i].ticsremaining, buffs[buffs_i].casterlevel, 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;
|
|
|
|
if(spells[buffs[buffs_i].spellid].buffdurationformula != DF_Permanent)
|
|
{
|
|
if(!zone->BuffTimersSuspended() || IsDetrimentalSpell(buffs[buffs_i].spellid))
|
|
{
|
|
--buffs[buffs_i].ticsremaining;
|
|
|
|
if (buffs[buffs_i].ticsremaining == 0) {
|
|
if (!IsShortDurationBuff(buffs[buffs_i].spellid) ||
|
|
IsFearSpell(buffs[buffs_i].spellid) ||
|
|
IsCharmSpell(buffs[buffs_i].spellid) ||
|
|
IsMezSpell(buffs[buffs_i].spellid) ||
|
|
IsBlindSpell(buffs[buffs_i].spellid))
|
|
{
|
|
mlog(SPELLS__BUFFS, "Buff %d in slot %d has expired. Fading.", buffs[buffs_i].spellid, buffs_i);
|
|
BuffFadeBySlot(buffs_i);
|
|
}
|
|
}
|
|
else if (buffs[buffs_i].ticsremaining < 0)
|
|
{
|
|
mlog(SPELLS__BUFFS, "Buff %d in slot %d has expired. Fading.", buffs[buffs_i].spellid, buffs_i);
|
|
BuffFadeBySlot(buffs_i);
|
|
}
|
|
else
|
|
{
|
|
mlog(SPELLS__BUFFS, "Buff %d in slot %d has %d tics remaining.", buffs[buffs_i].spellid, buffs_i, buffs[buffs_i].ticsremaining);
|
|
}
|
|
}
|
|
else if(IsClient() && !(CastToClient()->GetClientVersionBit() & BIT_SoFAndLater))
|
|
{
|
|
buffs[buffs_i].UpdateClient = true;
|
|
}
|
|
}
|
|
|
|
if(IsClient())
|
|
{
|
|
if(buffs[buffs_i].UpdateClient == true)
|
|
{
|
|
CastToClient()->SendBuffDurationPacket(buffs[buffs_i].spellid, buffs[buffs_i].ticsremaining, buffs[buffs_i].casterlevel);
|
|
buffs[buffs_i].UpdateClient = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mob::DoBuffTic(uint16 spell_id, uint32 ticsremaining, uint8 caster_level, Mob* caster) {
|
|
_ZP(Mob_DoBuffTic);
|
|
|
|
int effect, effect_value;
|
|
|
|
if(!IsValidSpell(spell_id))
|
|
return;
|
|
|
|
const SPDat_Spell_Struct &spell = spells[spell_id];
|
|
|
|
if (spell_id == SPELL_UNKNOWN)
|
|
return;
|
|
|
|
if(IsNPC())
|
|
{
|
|
if(parse->SpellHasQuestSub(spell_id, "EVENT_SPELL_EFFECT_BUFF_TIC_NPC"))
|
|
{
|
|
parse->EventSpell(EVENT_SPELL_EFFECT_BUFF_TIC_NPC, CastToNPC(), NULL, spell_id, caster ? caster->GetID() : 0);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(parse->SpellHasQuestSub(spell_id, "EVENT_SPELL_EFFECT_BUFF_TIC_CLIENT"))
|
|
{
|
|
parse->EventSpell(EVENT_SPELL_EFFECT_BUFF_TIC_CLIENT, NULL, CastToClient(), spell_id, caster ? caster->GetID() : 0);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check for non buff spell effects to fade
|
|
// AE melee effects
|
|
if(IsClient())
|
|
CastToClient()->CheckAAEffect(aaEffectRampage);
|
|
|
|
for (int i=0; i < EFFECT_COUNT; i++)
|
|
{
|
|
if(IsBlankSpellEffect(spell_id, i))
|
|
continue;
|
|
|
|
effect = spell.effectid[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:
|
|
{
|
|
effect_value = CalcSpellEffectValue(spell_id, i, caster_level, caster, ticsremaining);
|
|
//Handle client cast DOTs here.
|
|
if (caster && caster->IsClient() && IsDetrimentalSpell(spell_id) && effect_value < 0) {
|
|
effect_value = GetVulnerability(effect_value, caster, spell_id, ticsremaining);
|
|
effect_value = caster->CastToClient()->GetActDoTDamage(spell_id, effect_value);
|
|
|
|
if (!caster->CastToClient()->GetFeigned())
|
|
AddToHateList(caster, -effect_value);
|
|
}
|
|
|
|
if(effect_value < 0)
|
|
{
|
|
if(caster)
|
|
{
|
|
if(!caster->IsClient()){
|
|
effect_value = GetVulnerability(effect_value, caster, spell_id, ticsremaining);
|
|
if (!IsClient()) //Allow NPC's to generate hate if casted on other NPC's.
|
|
AddToHateList(caster, -effect_value);
|
|
}
|
|
|
|
if(caster->IsNPC())
|
|
effect_value = caster->CastToNPC()->GetActSpellDamage(spell_id, effect_value);
|
|
}
|
|
|
|
effect_value = -effect_value;
|
|
Damage(caster, effect_value, spell_id, spell.skill, false, i, true);
|
|
} else if(effect_value > 0) {
|
|
// Regen spell...
|
|
// handled with bonuses
|
|
}
|
|
break;
|
|
}
|
|
case SE_HealOverTime:
|
|
{
|
|
effect_value = CalcSpellEffectValue(spell_id, i, caster_level);
|
|
if(caster)
|
|
effect_value = caster->GetActSpellHealing(spell_id, effect_value);
|
|
effect_value += effect_value * (itembonuses.HealRate + spellbonuses.HealRate) / 100;
|
|
HealDamage(effect_value, caster);
|
|
//healing aggro would go here; removed for now
|
|
break;
|
|
}
|
|
|
|
case SE_CurrentEndurance: {
|
|
// Handled with bonuses
|
|
break;
|
|
}
|
|
|
|
case SE_BardAEDot:
|
|
{
|
|
effect_value = CalcSpellEffectValue(spell_id, i, caster_level, caster);
|
|
|
|
if (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, spell_id, 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_Hate2:{
|
|
effect_value = CalcSpellEffectValue(spell_id, i, caster_level);
|
|
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{
|
|
int32 newhate = GetHateAmount(caster) + effect_value;
|
|
if (newhate < 1) {
|
|
SetHate(caster,1);
|
|
} else {
|
|
SetHate(caster,newhate);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Charm: {
|
|
if (!caster || !PassCharismaCheck(caster, this, spell_id)) {
|
|
BuffFadeByEffect(SE_Charm);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_Root: {
|
|
float SpellEffectiveness = ResistSpell(spells[spell_id].resisttype, spell_id, caster);
|
|
if(SpellEffectiveness < 25) {
|
|
BuffFadeByEffect(SE_Root);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_Hunger: {
|
|
// this procedure gets called 7 times for every once that the stamina update occurs so we add 1/7 of the subtraction.
|
|
// It's far from perfect, but works without any unnecessary buff checks to bog down the server.
|
|
if(IsClient()) {
|
|
CastToClient()->m_pp.hunger_level += 5;
|
|
CastToClient()->m_pp.thirst_level += 5;
|
|
}
|
|
break;
|
|
}
|
|
case SE_Invisibility:
|
|
case SE_InvisVsAnimals:
|
|
case SE_InvisVsUndead:
|
|
{
|
|
if(ticsremaining > 3)
|
|
{
|
|
if(!IsBardSong(spell_id))
|
|
{
|
|
double break_chance = 2.0;
|
|
if(caster)
|
|
{
|
|
break_chance -= (2 * (((double)caster->GetSkill(DIVINATION) + ((double)caster->GetLevel() * 3.0)) / 650.0));
|
|
}
|
|
else
|
|
{
|
|
break_chance -= (2 * (((double)GetSkill(DIVINATION) + ((double)GetLevel() * 3.0)) / 650.0));
|
|
}
|
|
|
|
if(MakeRandomFloat(0.0, 100.0) < break_chance)
|
|
{
|
|
BuffModifyDurationBySpellID(spell_id, 3);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case SE_Invisibility2:
|
|
case SE_InvisVsUndead2:
|
|
{
|
|
if(ticsremaining <= 3 && ticsremaining > 1)
|
|
{
|
|
Message_StringID(MT_Spells, INVIS_BEGIN_BREAK);
|
|
}
|
|
break;
|
|
}
|
|
case SE_InterruptCasting:
|
|
{
|
|
if(IsCasting())
|
|
{
|
|
if(MakeRandomInt(0, 100) <= spells[spell_id].base[i])
|
|
{
|
|
InterruptSpell();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
// These effects always trigger when they fade.
|
|
case SE_ImprovedSpellEffect:
|
|
case SE_BossSpellTrigger:
|
|
case SE_CastOnWearoff:
|
|
{
|
|
if (ticsremaining == 1)
|
|
{
|
|
SpellOnTarget(spells[spell_id].base[i], this);
|
|
}
|
|
break;
|
|
}
|
|
case SE_LocateCorpse:
|
|
{
|
|
// This is handled by the client prior to SoD.
|
|
|
|
if(IsClient() && (CastToClient()->GetClientVersionBit() & BIT_SoDAndLater))
|
|
CastToClient()->LocateCorpse();
|
|
}
|
|
case SE_TotalHP:
|
|
{
|
|
if (spell.formula[i] > 1000 && spell.formula[i] < 1999)
|
|
{
|
|
// These formulas can affect Max HP each tick
|
|
// Maybe there is a more efficient way to recalculate this for just Max HP each tic...
|
|
//CalcBonuses();
|
|
CalcSpellBonuses(&spellbonuses);
|
|
CalcMaxHP();
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
// do we need to do anyting here?
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// solar: 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);
|
|
|
|
mlog(SPELLS__BUFFS, "Fading buff %d from slot %d", buffs[slot].spellid, slot);
|
|
|
|
if(spells[buffs[slot].spellid].viral_targets > 0) {
|
|
bool last_virus = true;
|
|
for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2)
|
|
{
|
|
if(viral_spells[i] && viral_spells[i] != buffs[slot].spellid)
|
|
{
|
|
// If we have a virus that doesn't match this one then don't stop the viral timer
|
|
last_virus = false;
|
|
}
|
|
}
|
|
// This is the last virus on us so lets stop timer
|
|
if(last_virus) {
|
|
viral_timer.Disable();
|
|
has_virus = false;
|
|
}
|
|
}
|
|
|
|
for (int i=0; i < EFFECT_COUNT; i++)
|
|
{
|
|
if(IsBlankSpellEffect(buffs[slot].spellid, i))
|
|
continue;
|
|
|
|
switch (spells[buffs[slot].spellid].effectid[i])
|
|
{
|
|
case SE_AddMeleeProc:
|
|
case SE_WeaponProc:
|
|
{
|
|
uint16 procid = GetProcID(buffs[slot].spellid, i);
|
|
RemoveProcFromWeapon(procid, false);
|
|
break;
|
|
}
|
|
|
|
case SE_SkillProc2:
|
|
case SE_SkillProc:
|
|
{
|
|
uint16 procid = GetProcID(buffs[slot].spellid, i);
|
|
RemoveSkillProc(procid);
|
|
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(this->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 = 0; x < 7; x++){
|
|
SendWearChange(x);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Levitate:
|
|
{
|
|
if (!AffectedBySpellExcludingSlot(slot, SE_Levitate))
|
|
SendAppearancePacket(AT_Levitate, 0);
|
|
break;
|
|
}
|
|
|
|
case SE_Invisibility2:
|
|
case SE_Invisibility:
|
|
{
|
|
SetInvisible(0);
|
|
break;
|
|
}
|
|
|
|
case SE_InvisVsUndead2:
|
|
case SE_InvisVsUndead:
|
|
{
|
|
invisible_undead = false; // Mongrel: No longer IVU
|
|
break;
|
|
}
|
|
|
|
case SE_InvisVsAnimals:
|
|
{
|
|
invisible_animals = false;
|
|
break;
|
|
}
|
|
|
|
case SE_SeeInvis:
|
|
{
|
|
see_invis = 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
|
|
this->mezzed = false;
|
|
break;
|
|
}
|
|
|
|
case SE_Charm:
|
|
{
|
|
if(IsNPC())
|
|
{
|
|
CastToNPC()->RestoreGuardSpotCharm();
|
|
SendAppearancePacket(AT_Pet, 0, true, true);
|
|
}
|
|
|
|
Mob* tempmob = GetOwner();
|
|
SetOwnerID(0);
|
|
if(tempmob)
|
|
{
|
|
tempmob->SetPet(0);
|
|
}
|
|
if (IsAIControlled())
|
|
{
|
|
// clear the hate list of the mobs
|
|
entity_list.ReplaceWithTarget(this, tempmob);
|
|
WipeHateList();
|
|
if(tempmob)
|
|
AddToHateList(tempmob, 1, 0);
|
|
SendAppearancePacket(AT_Anim, ANIM_STAND);
|
|
}
|
|
if(tempmob && tempmob->IsClient())
|
|
{
|
|
EQApplicationPacket *app = new EQApplicationPacket(OP_Charm, sizeof(Charm_Struct));
|
|
Charm_Struct *ps = (Charm_Struct*)app->pBuffer;
|
|
ps->owner_id = tempmob->GetID();
|
|
ps->pet_id = this->GetID();
|
|
ps->command = 0;
|
|
entity_list.QueueClients(this, app);
|
|
safe_delete(app);
|
|
}
|
|
if(IsClient())
|
|
{
|
|
InterruptSpell();
|
|
if (this->CastToClient()->IsLD())
|
|
AI_Start(CLIENT_LD_TIMEOUT);
|
|
else
|
|
{
|
|
bool feared = FindType(SE_Fear);
|
|
if(!feared)
|
|
AI_Stop();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_Root:
|
|
{
|
|
rooted = false;
|
|
rooted_mod = 0;
|
|
break;
|
|
}
|
|
|
|
case SE_Fear:
|
|
{
|
|
if(RuleB(Combat, EnableFearPathing)){
|
|
if(IsClient())
|
|
{
|
|
bool charmed = FindType(SE_Charm);
|
|
if(!charmed)
|
|
AI_Stop();
|
|
}
|
|
|
|
if(curfp) {
|
|
curfp = false;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UnStun();
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_ImmuneFleeing:
|
|
{
|
|
if(RuleB(Combat, EnableFearPathing)){
|
|
if(flee_mode) {
|
|
curfp = true;
|
|
CheckFlee();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
case SE_BindSight:
|
|
{
|
|
if(IsClient())
|
|
{
|
|
CastToClient()->SetBindSightTarget(NULL);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_SetBodyType:
|
|
{
|
|
SetBodyType(GetOrigBodyType(), false);
|
|
break;
|
|
}
|
|
|
|
case SE_MovementSpeed:
|
|
{
|
|
if(IsClient())
|
|
{
|
|
Client *my_c = CastToClient();
|
|
uint32 cur_time = Timer::GetCurrentTime();
|
|
if((cur_time - my_c->m_TimeSinceLastPositionCheck) > 1000)
|
|
{
|
|
float speed = (my_c->m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - my_c->m_TimeSinceLastPositionCheck);
|
|
float runs = my_c->GetRunspeed();
|
|
if(speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor)))
|
|
{
|
|
if(!my_c->GetGMSpeed() && (runs >= my_c->GetBaseRunspeed() || (speed > (my_c->GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor)))))
|
|
{
|
|
printf("%s %i moving too fast! moved: %.2f in %ims, speed %.2f\n", __FILE__, __LINE__,
|
|
my_c->m_DistanceSinceLastPositionCheck, (cur_time - my_c->m_TimeSinceLastPositionCheck), speed);
|
|
if(my_c->IsShadowStepExempted())
|
|
{
|
|
if(my_c->m_DistanceSinceLastPositionCheck > 800)
|
|
{
|
|
my_c->CheatDetected(MQWarpShadowStep, my_c->GetX(), my_c->GetY(), my_c->GetZ());
|
|
}
|
|
}
|
|
else if(my_c->IsKnockBackExempted())
|
|
{
|
|
//still potential to trigger this if you're knocked back off a
|
|
//HUGE fall that takes > 2.5 seconds
|
|
if(speed > 30.0f)
|
|
{
|
|
my_c->CheatDetected(MQWarpKnockBack, my_c->GetX(), my_c->GetY(), my_c->GetZ());
|
|
}
|
|
}
|
|
else if(!my_c->IsPortExempted())
|
|
{
|
|
if(!my_c->IsMQExemptedArea(zone->GetZoneID(), my_c->GetX(), my_c->GetY(), my_c->GetZ()))
|
|
{
|
|
if(speed > (runs * 2 * RuleR(Zone, MQWarpDetectionDistanceFactor)))
|
|
{
|
|
my_c->m_TimeSinceLastPositionCheck = cur_time;
|
|
my_c->m_DistanceSinceLastPositionCheck = 0.0f;
|
|
my_c->CheatDetected(MQWarp, my_c->GetX(), my_c->GetY(), my_c->GetZ());
|
|
//my_c->Death(my_c, 10000000, SPELL_UNKNOWN, _1H_BLUNT);
|
|
}
|
|
else
|
|
{
|
|
my_c->CheatDetected(MQWarpLight, my_c->GetX(), my_c->GetY(), my_c->GetZ());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
my_c->m_TimeSinceLastPositionCheck = cur_time;
|
|
my_c->m_DistanceSinceLastPositionCheck = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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->Message_StringID(MT_WornOff, SPELL_WORN_OFF_OF,
|
|
spells[buffs[slot].spellid].name, GetCleanName());
|
|
}
|
|
}
|
|
|
|
buffs[slot].spellid = SPELL_UNKNOWN;
|
|
if(IsPet() && GetOwner() && GetOwner()->IsClient()) {
|
|
SendPetBuffsToClient();
|
|
}
|
|
if((IsClient() && !CastToClient()->GetPVP()) || (IsPet() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()) ||
|
|
(IsMerc() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()))
|
|
{
|
|
EQApplicationPacket *outapp = MakeBuffsPacket();
|
|
|
|
entity_list.QueueClientsByTarget(this, outapp, false, NULL, true, false, BIT_SoDAndLater);
|
|
if(GetTarget() == this) {
|
|
CastToClient()->QueuePacket(outapp);
|
|
}
|
|
|
|
safe_delete(outapp);
|
|
}
|
|
|
|
if(IsClient() && CastToClient()->GetClientVersionBit() & BIT_UnderfootAndLater)
|
|
{
|
|
EQApplicationPacket *outapp = MakeBuffsPacket(false);
|
|
CastToClient()->FastQueuePacket(&outapp);
|
|
}
|
|
|
|
if (iRecalcBonuses)
|
|
CalcBonuses();
|
|
}
|
|
|
|
/* No longer used.
|
|
int16 Client::CalcAAFocusEffect(focusType type, uint16 focus_spell, uint16 spell_id)
|
|
{
|
|
uint32 slots = 0;
|
|
uint32 aa_AA = 0;
|
|
uint32 aa_value = 0;
|
|
|
|
int32 value = 0;
|
|
// Iterate through all of the client's AAs
|
|
for (int i = 0; i < MAX_PP_AA_ARRAY; i++)
|
|
{
|
|
aa_AA = this->aa[i]->AA;
|
|
aa_value = this->aa[i]->value;
|
|
if (aa_AA > 0 || aa_value > 0)
|
|
{
|
|
slots = zone->GetTotalAALevels(aa_AA);
|
|
if (slots > 0)
|
|
for(int j = 1;j <= slots; j++)
|
|
{
|
|
switch (aa_effects[aa_AA][j].skill_id)
|
|
{
|
|
case SE_TriggerOnCast:
|
|
// If focus_spell matches the spell listed in the DB, load these restrictions
|
|
if(type == focusTriggerOnCast && focus_spell == aa_effects[aa_AA][j].base1)
|
|
value = CalcAAFocus(type, aa_AA, spell_id);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
*/
|
|
|
|
|
|
int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id)
|
|
{
|
|
const SPDat_Spell_Struct &spell = spells[spell_id];
|
|
|
|
int16 value = 0;
|
|
int lvlModifier = 100;
|
|
int spell_level = 0;
|
|
int lvldiff = 0;
|
|
bool LimitSpellSkill = false;
|
|
bool SpellSkill_Found = false;
|
|
uint32 effect = 0;
|
|
int32 base1 = 0;
|
|
int32 base2 = 0;
|
|
uint32 slot = 0;
|
|
|
|
bool LimitFound = false;
|
|
int FocusCount = 0;
|
|
|
|
std::map<uint32, std::map<uint32, AA_Ability> >::const_iterator find_iter = aa_effects.find(aa_ID);
|
|
if(find_iter == aa_effects.end())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
for (map<uint32, AA_Ability>::const_iterator iter = aa_effects[aa_ID].begin(); iter != aa_effects[aa_ID].end(); ++iter)
|
|
{
|
|
effect = iter->second.skill_id;
|
|
base1 = iter->second.base1;
|
|
base2 = iter->second.base2;
|
|
slot = iter->second.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 effects, we will reset the limit check
|
|
//when the next valid focus effect is found.
|
|
if (IsFocusEffect(0, 0, true,effect) || (effect == SE_TriggerOnCast)){
|
|
FocusCount++;
|
|
//If limit found on prior check next, else end loop.
|
|
if (FocusCount > 1){
|
|
if (LimitFound){
|
|
value = 0;
|
|
LimitFound = false;
|
|
}
|
|
|
|
else{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
switch (effect)
|
|
{
|
|
case SE_Blank:
|
|
break;
|
|
|
|
//Handle Focus Limits
|
|
case SE_LimitResist:
|
|
if(base1)
|
|
{
|
|
if(spell.resisttype != base1)
|
|
LimitFound = true;
|
|
}
|
|
break;
|
|
case SE_LimitInstant:
|
|
if(spell.buffduration)
|
|
LimitFound = true;
|
|
break;
|
|
case SE_LimitMaxLevel:
|
|
spell_level = spell.classes[(GetClass()%16) - 1];
|
|
lvldiff = spell_level - base1;
|
|
//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(base2 > 0)
|
|
{
|
|
lvlModifier -= base2*lvldiff;
|
|
if(lvlModifier < 1)
|
|
LimitFound = true;
|
|
}
|
|
else {
|
|
LimitFound = true;
|
|
}
|
|
}
|
|
break;
|
|
case SE_LimitMinLevel:
|
|
if((spell.classes[(GetClass()%16) - 1]) < base1)
|
|
LimitFound = true;
|
|
break;
|
|
case SE_LimitCastTime:
|
|
if (spell.cast_time < base1)
|
|
LimitFound = true;
|
|
break;
|
|
case SE_LimitSpell:
|
|
// Exclude spell(any but this)
|
|
if(base1 < 0) {
|
|
if (spell_id == (base1*-1))
|
|
LimitFound = true;
|
|
}
|
|
else {
|
|
// Include Spell(only this)
|
|
if (spell_id != base1)
|
|
LimitFound = true;
|
|
}
|
|
break;
|
|
case SE_LimitMinDur:
|
|
if (base1 > CalcBuffDuration_formula(GetLevel(), spell.buffdurationformula, spell.buffduration))
|
|
LimitFound = true;
|
|
break;
|
|
case SE_LimitEffect:
|
|
// Exclude effect(any but this)
|
|
if(base1 < 0) {
|
|
if(IsEffectInSpell(spell_id,(base1*-1)))
|
|
LimitFound = true;
|
|
}
|
|
else {
|
|
// Include effect(only this)
|
|
if(!IsEffectInSpell(spell_id,base1))
|
|
LimitFound = true;
|
|
}
|
|
break;
|
|
case SE_LimitSpellType:
|
|
switch(base1)
|
|
{
|
|
case 0:
|
|
if (!IsDetrimentalSpell(spell_id))
|
|
LimitFound = true;
|
|
break;
|
|
case 1:
|
|
if (!IsBeneficialSpell(spell_id))
|
|
LimitFound = true;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SE_LimitManaCost:
|
|
if(spell.mana < base1)
|
|
LimitFound = true;
|
|
break;
|
|
|
|
case SE_LimitTarget:
|
|
// Exclude
|
|
if(base1 < 0){
|
|
if(-base1 == spell.targettype)
|
|
LimitFound = true;
|
|
}
|
|
// Include
|
|
else {
|
|
if(base1 != spell.targettype)
|
|
LimitFound = true;
|
|
}
|
|
break;
|
|
|
|
case SE_CombatSkills:
|
|
// 1 is for disciplines only
|
|
if(base1 == 1 && !IsDiscipline(spell_id))
|
|
LimitFound = true;
|
|
// 0 is spells only
|
|
else if(base1 == 0 && IsDiscipline(spell_id))
|
|
LimitFound = true;
|
|
break;
|
|
|
|
case SE_LimitSpellGroup:
|
|
if(base1 > 0 && base1 != spell.spellgroup)
|
|
LimitFound = true;
|
|
else if(base1 < 0 && base1 == spell.spellgroup)
|
|
LimitFound = true;
|
|
break;
|
|
|
|
|
|
case SE_LimitSpellSkill:
|
|
LimitSpellSkill = true;
|
|
if(base1 == spell.skill)
|
|
SpellSkill_Found = true;
|
|
break;
|
|
|
|
case SE_LimitExcludeSkill:{
|
|
int16 spell_skill = spell.skill * -1;
|
|
if(base1 == spell_skill)
|
|
LimitFound = 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(base1, GetClass()))
|
|
LimitFound = true;
|
|
break;
|
|
|
|
|
|
//Handle Focus Effects
|
|
case SE_ImprovedDamage:
|
|
if (type == focusImprovedDamage && base1 > value)
|
|
value = base1;
|
|
break;
|
|
|
|
case SE_ImprovedHeal:
|
|
if (type == focusImprovedHeal && base1 > value)
|
|
value = base1;
|
|
break;
|
|
|
|
case SE_ReduceManaCost:
|
|
if (type == focusManaCost )
|
|
value = base1;
|
|
break;
|
|
|
|
case SE_IncreaseSpellHaste:
|
|
if (type == focusSpellHaste && base1 > value)
|
|
value = base1;
|
|
break;
|
|
|
|
case SE_IncreaseSpellDuration:
|
|
if (type == focusSpellDuration && base1 > value)
|
|
value = base1;
|
|
break;
|
|
|
|
case SE_SpellDurationIncByTic:
|
|
if (type == focusSpellDurByTic && base1 > value)
|
|
value = base1;
|
|
break;
|
|
|
|
case SE_SwarmPetDuration:
|
|
if (type == focusSwarmPetDuration && base1 > value)
|
|
value = base1;
|
|
break;
|
|
|
|
case SE_IncreaseRange:
|
|
if (type == focusRange && base1 > value)
|
|
value = base1;
|
|
break;
|
|
|
|
case SE_ReduceReagentCost:
|
|
if (type == focusReagentCost && base1 > value)
|
|
value = base1;
|
|
break;
|
|
|
|
case SE_PetPowerIncrease:
|
|
if (type == focusPetPower && base1 > value)
|
|
value = base1;
|
|
break;
|
|
|
|
case SE_SpellResistReduction:
|
|
if (type == focusResistRate && base1 > value)
|
|
value = base1;
|
|
break;
|
|
|
|
case SE_SpellHateMod:
|
|
if (type == focusSpellHateMod)
|
|
{
|
|
if(value != 0)
|
|
{
|
|
if(value > 0)
|
|
{
|
|
if(base1 > value)
|
|
{
|
|
value = base1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(base1 < value)
|
|
{
|
|
value = base1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
value = base1;
|
|
}
|
|
break;
|
|
|
|
case SE_ReduceReuseTimer:
|
|
{
|
|
if(type == focusReduceRecastTime)
|
|
value = base1 / 1000;
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_TriggerOnCast:
|
|
{
|
|
if(type == focusTriggerOnCast)
|
|
{
|
|
if(MakeRandomInt(0, 100) <= base1){
|
|
value = base2;
|
|
}
|
|
|
|
else{
|
|
value = 0;
|
|
LimitFound = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SE_SpellVulnerability:
|
|
{
|
|
if(type == focusSpellVulnerability)
|
|
{
|
|
value = base1;
|
|
}
|
|
break;
|
|
}
|
|
case SE_BlockNextSpellFocus:
|
|
{
|
|
if(type == focusBlockNextSpell)
|
|
{
|
|
if(MakeRandomInt(1, 100) <= base1)
|
|
value = 1;
|
|
}
|
|
break;
|
|
}
|
|
case SE_Twincast:
|
|
{
|
|
if(type == focusTwincast)
|
|
{
|
|
value = base1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
case SE_SympatheticProc:
|
|
{
|
|
if(type == focusSympatheticProc)
|
|
{
|
|
float ProcChance, ProcBonus;
|
|
int16 ProcRateMod = base1; //Baseline is 100 for most Sympathetic foci
|
|
int32 cast_time = GetActSpellCasttime(spell_id, spells[spell_id].cast_time);
|
|
GetSympatheticProcChances(ProcBonus, ProcChance, cast_time, ProcRateMod);
|
|
|
|
if(MakeRandomFloat(0, 1) <= ProcChance)
|
|
value = focus_id;
|
|
|
|
else
|
|
value = 0;
|
|
}
|
|
break;
|
|
}
|
|
*/
|
|
case SE_SpellDamage:
|
|
{
|
|
if(type == focusSpellDamage)
|
|
value = base1;
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_FF_Damage_Amount:
|
|
{
|
|
if(type == focusFF_Damage_Amount)
|
|
value = base1;
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_Empathy:
|
|
{
|
|
if(type == focusAdditionalDamage)
|
|
value = base1;
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_CriticalHealRate:
|
|
{
|
|
if (type == focusCriticalHealRate)
|
|
value = base1;
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_AdditionalHeal:
|
|
{
|
|
if(type == focusAdditionalHeal)
|
|
value = base1;
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_AdditionalHeal2:
|
|
{
|
|
if(type == focusAdditionalHeal2)
|
|
value = base1;
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_HealRate2:
|
|
{
|
|
if(type == focusHealRate)
|
|
value = base1;
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_IncreaseSpellPower:
|
|
{
|
|
if (type == focusSpellEffectiveness)
|
|
value = base1;
|
|
|
|
break;
|
|
}
|
|
case SE_ImprovedDamage2:
|
|
{
|
|
if(type == focusImprovedDamage2)
|
|
value = base1;
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_IncreaseNumHits:
|
|
{
|
|
if(type == focusIncreaseNumHits)
|
|
value = base1;
|
|
|
|
break;
|
|
}
|
|
|
|
//Check for spell skill limits.
|
|
if ((LimitSpellSkill) && (!SpellSkill_Found))
|
|
return 0;
|
|
|
|
}
|
|
}
|
|
|
|
if (LimitFound){
|
|
return 0;
|
|
}
|
|
|
|
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
|
|
int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, bool best_focus) {
|
|
|
|
if(!IsValidSpell(focus_id) || !IsValidSpell(spell_id))
|
|
return 0;
|
|
|
|
const SPDat_Spell_Struct &focus_spell = spells[focus_id];
|
|
const SPDat_Spell_Struct &spell = spells[spell_id];
|
|
|
|
int16 value = 0;
|
|
int lvlModifier = 100;
|
|
int spell_level = 0;
|
|
int lvldiff = 0;
|
|
bool LimitSpellSkill = false;
|
|
bool SpellSkill_Found = false;
|
|
|
|
for (int i = 0; i < EFFECT_COUNT; i++) {
|
|
|
|
switch (focus_spell.effectid[i]) {
|
|
case SE_Blank:
|
|
break;
|
|
//check limits
|
|
|
|
case SE_LimitResist:{
|
|
if(focus_spell.base[i]){
|
|
if(spell.resisttype != focus_spell.base[i])
|
|
return(0);
|
|
}
|
|
break;
|
|
}
|
|
case SE_LimitInstant:{
|
|
if(spell.buffduration)
|
|
return(0);
|
|
break;
|
|
}
|
|
|
|
case SE_LimitMaxLevel:{
|
|
if (IsNPC())
|
|
break;
|
|
spell_level = spell.classes[(GetClass()%16) - 1];
|
|
lvldiff = spell_level - focus_spell.base[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.base2[i] > 0)
|
|
{
|
|
lvlModifier -= focus_spell.base2[i]*lvldiff;
|
|
if(lvlModifier < 1)
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SE_LimitMinLevel:
|
|
if (IsNPC())
|
|
break;
|
|
if (spell.classes[(GetClass()%16) - 1] < focus_spell.base[i])
|
|
return(0);
|
|
break;
|
|
|
|
case SE_LimitCastTime:
|
|
if (spells[spell_id].cast_time < (uint16)focus_spell.base[i])
|
|
return(0);
|
|
break;
|
|
|
|
case SE_LimitSpell:
|
|
if(focus_spell.base[i] < 0) { //exclude spell
|
|
if (spell_id == (focus_spell.base[i]*-1))
|
|
return(0);
|
|
} else {
|
|
//this makes the assumption that only one spell can be explicitly included...
|
|
if (spell_id != focus_spell.base[i])
|
|
return(0);
|
|
}
|
|
break;
|
|
|
|
case SE_LimitMinDur:
|
|
if (focus_spell.base[i] > CalcBuffDuration_formula(GetLevel(), spell.buffdurationformula, spell.buffduration))
|
|
return(0);
|
|
break;
|
|
|
|
case SE_LimitEffect:
|
|
if(focus_spell.base[i] < 0){
|
|
if(IsEffectInSpell(spell_id,focus_spell.base[i])){ //we limit this effect, can't have
|
|
return 0;
|
|
}
|
|
}
|
|
else{
|
|
if(focus_spell.base[i] == SE_SummonPet) //summoning haste special case
|
|
{ //must have one of the three pet effects to qualify
|
|
if(!IsEffectInSpell(spell_id, SE_SummonPet) &&
|
|
!IsEffectInSpell(spell_id, SE_NecPet) &&
|
|
!IsEffectInSpell(spell_id, SE_SummonBSTPet))
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
else if(!IsEffectInSpell(spell_id,focus_spell.base[i])){ //we limit this effect, must have
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
|
|
case SE_LimitSpellType:
|
|
switch( focus_spell.base[i] )
|
|
{
|
|
case 0:
|
|
if (!IsDetrimentalSpell(spell_id))
|
|
return 0;
|
|
break;
|
|
case 1:
|
|
if (!IsBeneficialSpell(spell_id))
|
|
return 0;
|
|
break;
|
|
default:
|
|
LogFile->write(EQEMuLog::Normal, "CalcFocusEffect: unknown limit spelltype %d", focus_spell.base[i]);
|
|
}
|
|
break;
|
|
|
|
case SE_LimitManaCost:
|
|
if(spell.mana < focus_spell.base[i])
|
|
return 0;
|
|
break;
|
|
|
|
case SE_LimitTarget:
|
|
// Exclude
|
|
if((focus_spell.base[i] < 0) && -focus_spell.base[i] == spell.targettype)
|
|
return 0;
|
|
// Include
|
|
else if (focus_spell.base[i] > 0 && focus_spell.base[i] != spell.targettype)
|
|
return 0;
|
|
|
|
break;
|
|
|
|
case SE_CombatSkills:
|
|
// 1 is for disciplines only
|
|
if(focus_spell.base[i] == 1 && !IsDiscipline(spell_id))
|
|
return 0;
|
|
// 0 is for spells only
|
|
else if(focus_spell.base[i] == 0 && IsDiscipline(spell_id))
|
|
return 0;
|
|
break;
|
|
|
|
case SE_LimitSpellGroup:
|
|
if(focus_spell.base[i] > 0 && focus_spell.base[i] != spell.spellgroup)
|
|
return 0;
|
|
else if(focus_spell.base[i] < 0 && focus_spell.base[i] == spell.spellgroup)
|
|
return 0;
|
|
break;
|
|
|
|
case SE_LimitSpellSkill:
|
|
LimitSpellSkill = true;
|
|
if(focus_spell.base[i] == spell.skill)
|
|
SpellSkill_Found = true;
|
|
break;
|
|
|
|
case SE_LimitExcludeSkill:{
|
|
int16 spell_skill = spell.skill * -1;
|
|
if(focus_spell.base[i] == spell_skill)
|
|
return 0;
|
|
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[i], GetClass()))
|
|
return 0;
|
|
break;
|
|
|
|
//handle effects
|
|
case SE_ImprovedDamage:
|
|
// No Spell used this, its handled by different spell effect IDs.
|
|
if (type == focusImprovedDamage) {
|
|
// This is used to determine which focus should be used for the random calculation
|
|
if(best_focus) {
|
|
// If the spell contains a value in the base2 field then that is the max value
|
|
if (focus_spell.base2[i] != 0) {
|
|
value = focus_spell.base2[i];
|
|
}
|
|
// If the spell does not contain a base2 value, then its a straight non random value
|
|
else {
|
|
value = focus_spell.base[i];
|
|
}
|
|
}
|
|
// Actual focus calculation starts here
|
|
else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) {
|
|
value = focus_spell.base[i];
|
|
}
|
|
else {
|
|
value = MakeRandomInt(focus_spell.base[i], focus_spell.base2[i]);
|
|
}
|
|
}
|
|
break;
|
|
case SE_ImprovedHeal:
|
|
if (type == focusImprovedHeal) {
|
|
if(best_focus) {
|
|
if (focus_spell.base2[i] != 0) {
|
|
value = focus_spell.base2[i];
|
|
}
|
|
else {
|
|
value = focus_spell.base[i];
|
|
}
|
|
}
|
|
else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) {
|
|
value = focus_spell.base[i];
|
|
}
|
|
else {
|
|
value = MakeRandomInt(focus_spell.base[i], focus_spell.base2[i]);
|
|
}
|
|
}
|
|
break;
|
|
case SE_ReduceManaCost:
|
|
if (type == focusManaCost) {
|
|
if(best_focus) {
|
|
if (focus_spell.base2[i] != 0) {
|
|
value = focus_spell.base2[i];
|
|
}
|
|
else {
|
|
value = focus_spell.base[i];
|
|
}
|
|
}
|
|
else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) {
|
|
value = focus_spell.base[i];
|
|
}
|
|
else {
|
|
value = MakeRandomInt(focus_spell.base[i], focus_spell.base2[i]);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SE_IncreaseSpellHaste:
|
|
if (type == focusSpellHaste && focus_spell.base[i] > value)
|
|
{
|
|
value = focus_spell.base[i];
|
|
}
|
|
break;
|
|
case SE_IncreaseSpellDuration:
|
|
if (type == focusSpellDuration && focus_spell.base[i] > value)
|
|
{
|
|
value = focus_spell.base[i];
|
|
}
|
|
break;
|
|
case SE_SpellDurationIncByTic:
|
|
if (type == focusSpellDurByTic && focus_spell.base[i] > value)
|
|
{
|
|
value = focus_spell.base[i];
|
|
}
|
|
break;
|
|
case SE_SwarmPetDuration:
|
|
if (type == focusSwarmPetDuration && focus_spell.base[i] > value)
|
|
{
|
|
value = focus_spell.base[i];
|
|
}
|
|
break;
|
|
case SE_IncreaseRange:
|
|
if (type == focusRange && focus_spell.base[i] > value)
|
|
{
|
|
value = focus_spell.base[i];
|
|
}
|
|
break;
|
|
case SE_ReduceReagentCost:
|
|
if (type == focusReagentCost && focus_spell.base[i] > value)
|
|
{
|
|
value = focus_spell.base[i];
|
|
}
|
|
break;
|
|
case SE_PetPowerIncrease:
|
|
if (type == focusPetPower && focus_spell.base[i] > value)
|
|
{
|
|
value = focus_spell.base[i];
|
|
}
|
|
break;
|
|
case SE_SpellResistReduction:
|
|
if (type == focusResistRate && focus_spell.base[i] > value)
|
|
{
|
|
value = focus_spell.base[i];
|
|
}
|
|
break;
|
|
case SE_SpellHateMod:
|
|
if (type == focusSpellHateMod)
|
|
{
|
|
if(value != 0)
|
|
{
|
|
if(value > 0)
|
|
{
|
|
if(focus_spell.base[i] > value)
|
|
{
|
|
value = focus_spell.base[i];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(focus_spell.base[i] < value)
|
|
{
|
|
value = focus_spell.base[i];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
value = focus_spell.base[i];
|
|
}
|
|
break;
|
|
|
|
case SE_ReduceReuseTimer:
|
|
{
|
|
if(type == focusReduceRecastTime)
|
|
value = focus_spell.base[i] / 1000;
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_TriggerOnCast:
|
|
{
|
|
if(type == focusTriggerOnCast)
|
|
|
|
if(MakeRandomInt(0, 100) <= focus_spell.base[i])
|
|
value = focus_spell.base2[i];
|
|
|
|
else
|
|
value = 0;
|
|
|
|
break;
|
|
}
|
|
case SE_SpellVulnerability:
|
|
{
|
|
if(type == focusSpellVulnerability)
|
|
{
|
|
value = focus_spell.base[i];
|
|
}
|
|
break;
|
|
}
|
|
case SE_BlockNextSpellFocus:
|
|
{
|
|
if(type == focusBlockNextSpell)
|
|
{
|
|
if(MakeRandomInt(1, 100) <= focus_spell.base[i])
|
|
value = 1;
|
|
}
|
|
break;
|
|
}
|
|
case SE_Twincast:
|
|
{
|
|
if(type == focusTwincast)
|
|
{
|
|
value = focus_spell.base[i];
|
|
}
|
|
break;
|
|
}
|
|
case SE_SympatheticProc:
|
|
{
|
|
if(type == focusSympatheticProc)
|
|
{
|
|
float ProcChance, ProcBonus;
|
|
int16 ProcRateMod = focus_spell.base[i]; //Baseline is 100 for most Sympathetic foci
|
|
int32 cast_time = GetActSpellCasttime(spell_id, spells[spell_id].cast_time);
|
|
GetSympatheticProcChances(ProcBonus, ProcChance, cast_time, ProcRateMod);
|
|
|
|
if(MakeRandomFloat(0, 1) <= ProcChance)
|
|
value = focus_id;
|
|
|
|
else
|
|
value = 0;
|
|
}
|
|
break;
|
|
}
|
|
case SE_SpellDamage:
|
|
{
|
|
if(type == focusSpellDamage)
|
|
value = focus_spell.base[i];
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_FF_Damage_Amount:
|
|
{
|
|
if(type == focusFF_Damage_Amount)
|
|
value = focus_spell.base[i];
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_Empathy:
|
|
{
|
|
if(type == focusAdditionalDamage)
|
|
value = focus_spell.base[i];
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_CriticalHealRate:
|
|
{
|
|
if (type == focusCriticalHealRate)
|
|
value = focus_spell.base[i];
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_AdditionalHeal:
|
|
{
|
|
if(type == focusAdditionalHeal)
|
|
value = focus_spell.base[i];
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_AdditionalHeal2:
|
|
{
|
|
if(type == focusAdditionalHeal2)
|
|
value = focus_spell.base[i];
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_HealRate2:
|
|
{
|
|
if(type == focusHealRate)
|
|
value = focus_spell.base[i];
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_IncreaseSpellPower:
|
|
{
|
|
if (type == focusSpellEffectiveness)
|
|
value = focus_spell.base[i];
|
|
|
|
break;
|
|
}
|
|
case SE_ImprovedDamage2:
|
|
{
|
|
if(type == focusImprovedDamage2)
|
|
value = focus_spell.base[i];
|
|
|
|
break;
|
|
}
|
|
|
|
case SE_IncreaseNumHits:
|
|
{
|
|
if(type == focusIncreaseNumHits)
|
|
value = focus_spell.base[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:
|
|
LogFile->write(EQEMuLog::Normal, "CalcFocusEffect: unknown effectid %d", focus_spell.effectid[i]);
|
|
#endif
|
|
}
|
|
}
|
|
//Check for spell skill limits.
|
|
if ((LimitSpellSkill) && (!SpellSkill_Found))
|
|
return 0;
|
|
|
|
return(value*lvlModifier/100);
|
|
}
|
|
|
|
int16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) {
|
|
|
|
if (IsBardSong(spell_id))
|
|
return 0;
|
|
|
|
uint16 proc_spellid = 0;
|
|
uint8 SizeProcList = 0;
|
|
uint8 MAX_SYMPATHETIC = 10;
|
|
|
|
vector<int> SympatheticProcList;
|
|
|
|
//item focus
|
|
if (itembonuses.FocusEffects[type]){
|
|
|
|
const Item_Struct* TempItem = 0;
|
|
|
|
for(int x=0; x<=21; x++)
|
|
{
|
|
if (SizeProcList > MAX_SYMPATHETIC)
|
|
continue;
|
|
|
|
TempItem = NULL;
|
|
ItemInst* ins = GetInv().GetItem(x);
|
|
if (!ins)
|
|
continue;
|
|
TempItem = ins->GetItem();
|
|
if (TempItem && TempItem->Focus.Effect > 0 && TempItem->Focus.Effect != SPELL_UNKNOWN) {
|
|
|
|
proc_spellid = CalcFocusEffect(type, TempItem->Focus.Effect, spell_id);
|
|
|
|
if (proc_spellid > 0)
|
|
{
|
|
SympatheticProcList.push_back(proc_spellid);
|
|
SizeProcList = SympatheticProcList.size();
|
|
}
|
|
}
|
|
|
|
for(int y = 0; y < MAX_AUGMENT_SLOTS; ++y)
|
|
{
|
|
if (SizeProcList > MAX_SYMPATHETIC)
|
|
continue;
|
|
|
|
ItemInst *aug = NULL;
|
|
aug = ins->GetAugment(y);
|
|
if(aug)
|
|
{
|
|
const Item_Struct* TempItemAug = aug->GetItem();
|
|
if (TempItemAug && TempItemAug->Focus.Effect > 0 && TempItemAug->Focus.Effect != SPELL_UNKNOWN) {
|
|
|
|
proc_spellid = CalcFocusEffect(type, TempItemAug->Focus.Effect, spell_id);
|
|
|
|
if (proc_spellid > 0)
|
|
{
|
|
SympatheticProcList.push_back(proc_spellid);
|
|
SizeProcList = SympatheticProcList.size();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Spell Focus
|
|
if (spellbonuses.FocusEffects[type]){
|
|
int buff_slot = 0;
|
|
uint16 focusspellid = 0;
|
|
uint32 buff_max = GetMaxTotalSlots();
|
|
for (buff_slot = 0; buff_slot < buff_max; buff_slot++) {
|
|
|
|
if (SizeProcList > MAX_SYMPATHETIC)
|
|
continue;
|
|
|
|
focusspellid = buffs[buff_slot].spellid;
|
|
if (focusspellid == 0 || focusspellid >= SPDAT_RECORDS)
|
|
continue;
|
|
|
|
proc_spellid = CalcFocusEffect(type, focusspellid, spell_id);
|
|
|
|
if (proc_spellid > 0)
|
|
{
|
|
SympatheticProcList.push_back(proc_spellid);
|
|
SizeProcList = SympatheticProcList.size();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SizeProcList > 0)
|
|
{
|
|
uint8 random = MakeRandomInt(0, SizeProcList-1);
|
|
int FinalSympatheticProc = SympatheticProcList[random];
|
|
SympatheticProcList.clear();
|
|
return FinalSympatheticProc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int16 Client::GetFocusEffect(focusType type, uint16 spell_id) {
|
|
|
|
if (IsBardSong(spell_id) && type != focusSpellEffectiveness)
|
|
return 0;
|
|
|
|
int16 realTotal = 0;
|
|
int16 realTotal2 = 0;
|
|
int16 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((type == focusManaCost || type == focusImprovedHeal || type == focusImprovedDamage)
|
|
&& RuleB(Spells, LiveLikeFocusEffects))
|
|
{
|
|
rand_effectiveness = true;
|
|
}
|
|
|
|
//Check if item focus effect exists for the client.
|
|
if (itembonuses.FocusEffects[type]){
|
|
|
|
const Item_Struct* TempItem = 0;
|
|
const Item_Struct* UsedItem = 0;
|
|
uint16 UsedFocusID = 0;
|
|
int16 Total = 0;
|
|
int16 focus_max = 0;
|
|
int16 focus_max_real = 0;
|
|
|
|
//item focus
|
|
for(int x=0; x<=21; x++)
|
|
{
|
|
TempItem = NULL;
|
|
ItemInst* 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 = 0; y < MAX_AUGMENT_SLOTS; ++y)
|
|
{
|
|
ItemInst *aug = NULL;
|
|
aug = ins->GetAugment(y);
|
|
if(aug)
|
|
{
|
|
const Item_Struct* 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Tribute Focus
|
|
for(int x = TRIBUTE_SLOT_START; x < (TRIBUTE_SLOT_START + MAX_PLAYER_TRIBUTES); ++x)
|
|
{
|
|
TempItem = NULL;
|
|
ItemInst* 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 (realTotal != 0 && UsedItem)
|
|
Message_StringID(MT_Spells, BEGINS_TO_GLOW, UsedItem->Name);
|
|
}
|
|
|
|
//Check if spell focus effect exists for the client.
|
|
if (spellbonuses.FocusEffects[type]){
|
|
|
|
//Spell Focus
|
|
int16 Total2 = 0;
|
|
int16 focus_max2 = 0;
|
|
int16 focus_max_real2 = 0;
|
|
|
|
int buff_tracker = -1;
|
|
int buff_slot = 0;
|
|
uint16 focusspellid = 0;
|
|
uint16 focusspell_tracker = 0;
|
|
uint32 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);
|
|
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);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(focusspell_tracker && rand_effectiveness && focus_max_real2 != 0)
|
|
realTotal2 = CalcFocusEffect(type, focusspell_tracker, spell_id);
|
|
|
|
// For effects like gift of mana that only fire once, save the spellid into an array that consists of all available buff slots.
|
|
if(buff_tracker >= 0 && buffs[buff_tracker].numhits > 0) {
|
|
m_spellHitsLeft[buff_tracker] = focusspell_tracker;
|
|
}
|
|
}
|
|
|
|
|
|
// AA Focus
|
|
if (aabonuses.FocusEffects[type]){
|
|
|
|
int16 Total3 = 0;
|
|
uint32 slots = 0;
|
|
uint32 aa_AA = 0;
|
|
uint32 aa_value = 0;
|
|
|
|
for (int i = 0; i < MAX_PP_AA_ARRAY; i++)
|
|
{
|
|
aa_AA = this->aa[i]->AA;
|
|
aa_value = this->aa[i]->value;
|
|
if (aa_AA < 1 || aa_value < 1)
|
|
continue;
|
|
|
|
Total3 = CalcAAFocus(type, aa_AA, spell_id);
|
|
if (Total3 > 0 && realTotal3 >= 0 && Total3 > realTotal3) {
|
|
realTotal3 = Total3;
|
|
}
|
|
else if (Total3 < 0 && Total3 < realTotal3) {
|
|
realTotal3 = Total3;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(type == focusReagentCost && IsSummonPetSpell(spell_id) && GetAA(aaElementalPact))
|
|
return 100;
|
|
|
|
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.
|
|
|
|
return realTotal + realTotal2 + realTotal3;
|
|
}
|
|
|
|
bool Mob::CheckHitsRemaining(uint32 buff_slot, bool when_spell_done, bool negate, uint16 type, uint16 spell_id,bool use_skill,uint16 skill)
|
|
{
|
|
bool bDepleted = false;
|
|
//Effects: Cast: SE_ResistSpellChance, SE_Reflect, SE_SpellDamageShield
|
|
//Effects: Attack: SE_MeleeLifetap : SE_DamageShield, SE_AvoidMeleeChance, SE_SkillProc
|
|
//Effects: Skill: SE_DamageModifier, SE_SkillDamageTaken, SE_SkillDamageAmount, SE_HitChance
|
|
//For spell buffs that are limited typically when you are attacked or are subject to an attack/cast and we do not know the buff slot.
|
|
if (type){
|
|
uint32 buff_max = GetMaxTotalSlots();
|
|
for(uint32 d = 0; d < buff_max; d++) {
|
|
if((buffs[d].spellid != SPELL_UNKNOWN) && (buffs[d].numhits > 0) && IsEffectInSpell(buffs[d].spellid, type)){
|
|
if (!use_skill){
|
|
if(--buffs[d].numhits == 0) {
|
|
if(!TryFadeEffect(d)){
|
|
CastOnNumHitFade(buffs[d].spellid);
|
|
BuffFadeBySlot(d, true);
|
|
}
|
|
}
|
|
}
|
|
else{
|
|
bDepleted = false;
|
|
for (int j = 0; j < EFFECT_COUNT; j++) {
|
|
if (bDepleted)
|
|
continue;
|
|
if ((buffs[d].spellid != SPELL_UNKNOWN) && (spells[buffs[d].spellid].effectid[j] == type)) {
|
|
if(spells[buffs[d].spellid].base2[j] == -1 || spells[buffs[d].spellid].base2[j] == skill) {
|
|
bDepleted = true;
|
|
if(--buffs[d].numhits == 0) {
|
|
if(!TryFadeEffect(d)){
|
|
CastOnNumHitFade(buffs[d].spellid);
|
|
BuffFadeBySlot(d, true);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// For spell buffs that are limited by the number of times it can successfully trigger a spell.
|
|
// Effects: SE_TriggerOnCast, SE_SympatheticProc,SE_DefensiveProc, SE_SkillProc, SE_RangedProc
|
|
if(spell_id){
|
|
uint32 buff_count = GetMaxTotalSlots();
|
|
for(uint32 d = 0; d < buff_count; d++){
|
|
if((buffs[d].spellid != SPELL_UNKNOWN) && (buffs[d].numhits > 0) && buffs[d].spellid == spell_id){
|
|
if(--buffs[d].numhits == 0) {
|
|
if(!TryFadeEffect(d)){
|
|
CastOnNumHitFade(buffs[d].spellid);
|
|
BuffFadeBySlot(d, true);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// For focusTypes that limit the number of spell casts it will effect.
|
|
// Effect: Focus effects ie SE_ImprovedDamage ect
|
|
if(when_spell_done) {
|
|
uint32 buff_max = GetMaxTotalSlots();
|
|
// Go through all possible saved spells with limited hits, the place in the array is the same as the buff slot
|
|
for(int d = 0; d < buff_max; d++) {
|
|
if(!m_spellHitsLeft[d])
|
|
continue;
|
|
// Double check to make sure the saved spell matches the buff in that slot
|
|
if (m_spellHitsLeft[d] == buffs[d].spellid) {
|
|
if(buffs[d].numhits > 1) {
|
|
buffs[d].numhits--;
|
|
return true;
|
|
}
|
|
else {
|
|
if(!TryFadeEffect(d))
|
|
BuffFadeBySlot(d, true);
|
|
CastOnNumHitFade(m_spellHitsLeft[d]);
|
|
m_spellHitsLeft[d] = 0;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// For lowering numhits when we already know the effects buff_slot
|
|
// Effects: SE_SpellVulnerability,SE_MitigateMeleeDamage,SE_NegateAttacks,SE_MitigateSpellDamage,SE_ManaAbsorbPercentDamage
|
|
if(spells[buffs[buff_slot].spellid].numhits > 0 || negate) {
|
|
if(buffs[buff_slot].numhits > 1) {
|
|
buffs[buff_slot].numhits--;
|
|
return true;
|
|
}
|
|
else if(!TryFadeEffect(buff_slot)) {
|
|
CastOnNumHitFade(buffs[buff_slot].spellid);
|
|
BuffFadeBySlot(buff_slot, true);
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//for some stupid reason SK procs return theirs one base off...
|
|
uint16 Mob::GetProcID(uint16 spell_id, uint8 effect_index) {
|
|
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[effect_index] + 1);
|
|
}
|
|
else{
|
|
return(spells[spell_id].base[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[0] + itembonuses.DivineSaveChance[0] + spellbonuses.DivineSaveChance[0];
|
|
if (SuccessChance && MakeRandomInt(0, 100) <= SuccessChance)
|
|
{
|
|
SetHP(1);
|
|
|
|
uint16 EffectsToTry[] =
|
|
{
|
|
aabonuses.DivineSaveChance[1],
|
|
itembonuses.DivineSaveChance[1],
|
|
spellbonuses.DivineSaveChance[1]
|
|
};
|
|
//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(4789, 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[0]){
|
|
|
|
int SuccessChance = 0;
|
|
int buffSlot = spellbonuses.DeathSave[1];
|
|
uint8 UD_HealMod = buffs[buffSlot].deathsaveCasterAARank; //Contains value of UD heal modifier.
|
|
uint32 HealAmt = 300; //Death Pact max Heal
|
|
|
|
if(buffSlot >= 0){
|
|
|
|
SuccessChance = ( (GetCHA() * (RuleI(Spells, DeathSaveCharismaMod))) + 1) / 10; //(CHA Mod Default = 3)
|
|
|
|
if (SuccessChance > 95)
|
|
SuccessChance = 95;
|
|
|
|
if(SuccessChance >= MakeRandomInt(0, 100)) {
|
|
|
|
if(spellbonuses.DeathSave[0] == 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[3] && (GetLevel() >= spellbonuses.DeathSave[2]))
|
|
HealAmt += spellbonuses.DeathSave[3];
|
|
|
|
if ((GetMaxHP() - GetHP()) < HealAmt)
|
|
HealAmt = GetMaxHP() - GetHP();
|
|
|
|
SetHP((GetHP()+HealAmt));
|
|
Message(263, "The gods have healed you for %i points of damage.", HealAmt);
|
|
|
|
if(spellbonuses.DeathSave[0] == 2)
|
|
entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, DIVINE_INTERVENTION, GetCleanName());
|
|
else
|
|
entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, 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(SuccessChance >= MakeRandomInt(0, 100)) {
|
|
|
|
if(spellbonuses.DeathSave[0] == 2)
|
|
HealAmt = RuleI(Spells, DivineInterventionHeal);
|
|
|
|
//Check if bonus Heal amount can be applied ([3] Bonus Heal [2] Level limit)
|
|
if (spellbonuses.DeathSave[3] && (GetLevel() >= spellbonuses.DeathSave[2]))
|
|
HealAmt += spellbonuses.DeathSave[3];
|
|
|
|
HealAmt = HealAmt*UD_HealMod/100;
|
|
|
|
if ((GetMaxHP() - GetHP()) < HealAmt)
|
|
HealAmt = GetMaxHP() - GetHP();
|
|
|
|
SetHP((GetHP()+HealAmt));
|
|
Message(263, "The gods have healed you for %i points of damage.", HealAmt);
|
|
|
|
if(spellbonuses.DeathSave[0] == 2)
|
|
entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, DIVINE_INTERVENTION, GetCleanName());
|
|
else
|
|
entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, DEATH_PACT, GetCleanName());
|
|
|
|
SendHPUpdate();
|
|
BuffFadeBySlot(buffSlot);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Mob::AffectedBySpellExcludingSlot(int slot, int effect)
|
|
{
|
|
for (int i = 0; i <= EFFECT_COUNT; i++)
|
|
{
|
|
if (i == slot)
|
|
continue;
|
|
|
|
if (IsEffectInSpell(buffs[i].spellid, effect))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
float Mob::GetSympatheticProcChances(float &ProcBonus, float &ProcChance, int32 cast_time, int16 ProcRateMod) {
|
|
|
|
ProcBonus = spellbonuses.SpellProcChance + itembonuses.SpellProcChance;
|
|
ProcChance = 0;
|
|
|
|
if(cast_time > 0)
|
|
{
|
|
ProcChance = ((float)cast_time * RuleR(Casting, AvgSpellProcsPerMinute) / 60000.0f);
|
|
ProcChance = ProcChance * (float)(ProcRateMod/100);
|
|
ProcChance = ProcChance+(ProcChance*ProcBonus/100);
|
|
}
|
|
return ProcChance;
|
|
}
|
|
|
|
bool Mob::DoHPToManaCovert(uint16 mana_cost)
|
|
{
|
|
if (spellbonuses.HPToManaConvert){
|
|
int hp_cost = spellbonuses.HPToManaConvert * mana_cost / 100;
|
|
if(hp_cost) {
|
|
SetHP(GetHP()-hp_cost);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int32 Mob::GetAdditionalDamage(Mob *caster, uint32 spell_id, bool use_skill, uint16 skill )
|
|
{
|
|
//Used to check focus derived from SE_Empathy which adds direct damage to Spells or Skill based attacks.
|
|
int32 dmg = 0;
|
|
bool limit_exists = false;
|
|
bool skill_found = false;
|
|
|
|
if (!caster)
|
|
return 0;
|
|
|
|
if (spellbonuses.FocusEffects[focusAdditionalDamage]){
|
|
uint32 buff_count = GetMaxTotalSlots();
|
|
for(int i = 0; i < buff_count; i++){
|
|
|
|
if( (IsValidSpell(buffs[i].spellid) && (IsEffectInSpell(buffs[i].spellid, SE_Empathy))) ){
|
|
|
|
if (use_skill){
|
|
int32 temp_dmg = 0;
|
|
for (int e = 0; e < EFFECT_COUNT; e++) {
|
|
|
|
if (spells[buffs[i].spellid].effectid[e] == SE_Empathy){
|
|
temp_dmg += spells[buffs[i].spellid].base[e];
|
|
continue;
|
|
}
|
|
|
|
if (!skill_found){
|
|
if ((spells[buffs[i].spellid].effectid[e] == SE_LimitToSkill) ||
|
|
(spells[buffs[i].spellid].effectid[e] == SE_LimitSpellSkill)){
|
|
limit_exists = true;
|
|
|
|
if (spells[buffs[i].spellid].base[e] == skill)
|
|
skill_found = true;
|
|
}
|
|
}
|
|
}
|
|
if ((!limit_exists) || (limit_exists && skill_found)){
|
|
dmg += temp_dmg;
|
|
CheckHitsRemaining(i);
|
|
}
|
|
}
|
|
|
|
else{
|
|
int32 focus = caster->CalcFocusEffect(focusAdditionalDamage, buffs[i].spellid, spell_id);
|
|
if(focus){
|
|
dmg += focus;
|
|
CheckHitsRemaining(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return dmg;
|
|
}
|
|
|
|
int32 Mob::ApplySpellEffectiveness(Mob* caster, int16 spell_id, int32 value, bool IsBard) {
|
|
|
|
//Kayen 9-17-12: This is likely causing crashes, disabled till can resolve.
|
|
if (IsBard)
|
|
return value;
|
|
|
|
if (!caster)
|
|
return value;
|
|
|
|
if (caster->IsClient()){
|
|
int16 focus = caster->CastToClient()->GetFocusEffect(focusSpellEffectiveness, spell_id);
|
|
|
|
if (IsBard)
|
|
value += focus;
|
|
|
|
else
|
|
value += value*focus/100;
|
|
}
|
|
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[0] && !spellbonuses.SEResist[0] && !itembonuses.SEResist[0])
|
|
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].effectid[i] == aabonuses.SEResist[d]){
|
|
resist_chance += aabonuses.SEResist[d+1];
|
|
found = true;
|
|
}
|
|
|
|
if (spells[spell_id].effectid[i] == itembonuses.SEResist[d]){
|
|
resist_chance += itembonuses.SEResist[d+1];
|
|
found = true;
|
|
}
|
|
|
|
|
|
if (spells[spell_id].effectid[i] == spellbonuses.SEResist[d]){
|
|
resist_chance += spellbonuses.SEResist[d+1];
|
|
found = true;
|
|
}
|
|
|
|
if (found)
|
|
continue;
|
|
}
|
|
}
|
|
return resist_chance;
|
|
}
|
|
|