eqemu-server/zone/mob.cpp
2013-05-07 18:20:09 -04:00

4695 lines
124 KiB
C++

/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2002 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 "masterentity.h"
#include "../common/spdat.h"
#include "StringIDs.h"
#include "worldserver.h"
#include "QuestParserCollection.h"
#include <sstream>
#include <math.h>
#include <limits.h>
extern EntityList entity_list;
extern Zone* zone;
extern WorldServer worldserver;
Mob::Mob(const char* in_name,
const char* in_lastname,
int32 in_cur_hp,
int32 in_max_hp,
uint8 in_gender,
uint16 in_race,
uint8 in_class,
bodyType in_bodytype,
uint8 in_deity,
uint8 in_level,
uint32 in_npctype_id,
float in_size,
float in_runspeed,
float in_heading,
float in_x_pos,
float in_y_pos,
float in_z_pos,
uint8 in_light,
uint8 in_texture,
uint8 in_helmtexture,
uint16 in_ac,
uint16 in_atk,
uint16 in_str,
uint16 in_sta,
uint16 in_dex,
uint16 in_agi,
uint16 in_int,
uint16 in_wis,
uint16 in_cha,
uint8 in_haircolor,
uint8 in_beardcolor,
uint8 in_eyecolor1, // the eyecolors always seem to be the same, maybe left and right eye?
uint8 in_eyecolor2,
uint8 in_hairstyle,
uint8 in_luclinface,
uint8 in_beard,
uint32 in_drakkin_heritage,
uint32 in_drakkin_tattoo,
uint32 in_drakkin_details,
uint32 in_armor_tint[MAX_MATERIALS],
uint8 in_aa_title,
uint8 in_see_invis, // see through invis/ivu
uint8 in_see_invis_undead,
uint8 in_see_hide,
uint8 in_see_improved_hide,
int32 in_hp_regen,
int32 in_mana_regen,
uint8 in_qglobal,
uint8 in_maxlevel,
uint32 in_scalerate
) :
attack_timer(2000),
attack_dw_timer(2000),
ranged_timer(2000),
tic_timer(6000),
mana_timer(2000),
spellend_timer(0),
rewind_timer(30000), //Timer used for determining amount of time between actual player position updates for /rewind.
stunned_timer(0),
spun_timer(0),
bardsong_timer(6000),
flee_timer(FLEE_CHECK_TIMER),
bindwound_timer(10000),
gravity_timer(1000),
viral_timer(0)
{
targeted = 0;
tar_ndx=0;
tar_vector=0;
tar_vx=0;
tar_vy=0;
tar_vz=0;
tarx=0;
tary=0;
tarz=0;
fear_walkto_x = -999999;
fear_walkto_y = -999999;
fear_walkto_z = -999999;
curfp = false;
AI_Init();
SetMoving(false);
moved=false;
rewind_x = 0; //Stored x_pos for /rewind
rewind_y = 0; //Stored y_pos for /rewind
rewind_z = 0; //Stored z_pos for /rewind
move_tic_count = 0;
_egnode = nullptr;
adverrorinfo = 0;
name[0]=0;
orig_name[0]=0;
clean_name[0]=0;
lastname[0]=0;
if(in_name) {
strn0cpy(name,in_name,64);
strn0cpy(orig_name,in_name,64);
}
if(in_lastname)
strn0cpy(lastname,in_lastname,64);
cur_hp = in_cur_hp;
max_hp = in_max_hp;
base_hp = in_max_hp;
gender = in_gender;
race = in_race;
base_gender = in_gender;
base_race = in_race;
class_ = in_class;
bodytype = in_bodytype;
orig_bodytype = in_bodytype;
deity = in_deity;
level = in_level;
npctype_id = in_npctype_id;
size = in_size;
base_size = size;
runspeed = in_runspeed;
// sanity check
if (runspeed < 0 || runspeed > 20)
runspeed = 1.25f;
heading = in_heading;
x_pos = in_x_pos;
y_pos = in_y_pos;
z_pos = in_z_pos;
light = in_light;
texture = in_texture;
helmtexture = in_helmtexture;
haircolor = in_haircolor;
beardcolor = in_beardcolor;
eyecolor1 = in_eyecolor1;
eyecolor2 = in_eyecolor2;
hairstyle = in_hairstyle;
luclinface = in_luclinface;
beard = in_beard;
drakkin_heritage = in_drakkin_heritage;
drakkin_tattoo = in_drakkin_tattoo;
drakkin_details = in_drakkin_details;
attack_speed= 0;
slow_mitigation= 0;
findable = false;
trackable = true;
if(in_aa_title>0)
aa_title = in_aa_title;
else
aa_title =0xFF;
AC = in_ac;
ATK = in_atk;
STR = in_str;
STA = in_sta;
DEX = in_dex;
AGI = in_agi;
INT = in_int;
WIS = in_wis;
CHA = in_cha;
MR = CR = FR = DR = PR = Corrup = 0;
ExtraHaste = 0;
bEnraged = false;
shield_target = nullptr;
cur_mana = 0;
max_mana = 0;
hp_regen = in_hp_regen;
mana_regen = in_mana_regen;
oocregen = RuleI(NPC, OOCRegen); //default Out of Combat Regen
maxlevel = in_maxlevel;
scalerate = in_scalerate;
invisible = false;
invisible_undead = false;
invisible_animals = false;
sneaking = false;
hidden = false;
improved_hidden = false;
invulnerable = false;
IsFullHP = (cur_hp == max_hp);
qglobal=0;
InitializeBuffSlots();
// clear the proc arrays
int i;
int j;
for (j = 0; j < MAX_PROCS; j++)
{
PermaProcs[j].spellID = SPELL_UNKNOWN;
PermaProcs[j].chance = 0;
PermaProcs[j].base_spellID = SPELL_UNKNOWN;
SpellProcs[j].spellID = SPELL_UNKNOWN;
DefensiveProcs[j].spellID = SPELL_UNKNOWN;
DefensiveProcs[j].chance = 0;
DefensiveProcs[j].base_spellID = SPELL_UNKNOWN;
RangedProcs[j].spellID = SPELL_UNKNOWN;
RangedProcs[j].chance = 0;
RangedProcs[j].base_spellID = SPELL_UNKNOWN;
SkillProcs[j].spellID = SPELL_UNKNOWN;
SkillProcs[j].chance = 0;
SkillProcs[j].base_spellID = SPELL_UNKNOWN;
}
for (i = 0; i < MAX_MATERIALS; i++)
{
if (in_armor_tint)
{
armor_tint[i] = in_armor_tint[i];
}
else
{
armor_tint[i] = 0;
}
}
delta_heading = 0;
delta_x = 0;
delta_y = 0;
delta_z = 0;
animation = 0;
logging_enabled = false;
isgrouped = false;
israidgrouped = false;
islooting = false;
_appearance = eaStanding;
pRunAnimSpeed = 0;
spellend_timer.Disable();
bardsong_timer.Disable();
bardsong = 0;
bardsong_target_id = 0;
casting_spell_id = 0;
casting_spell_timer = 0;
casting_spell_timer_duration = 0;
casting_spell_type = 0;
target = 0;
memset(&itembonuses, 0, sizeof(StatBonuses));
memset(&spellbonuses, 0, sizeof(StatBonuses));
memset(&aabonuses, 0, sizeof(StatBonuses));
spellbonuses.AggroRange = -1;
spellbonuses.AssistRange = -1;
pLastChange = 0;
SetPetID(0);
SetOwnerID(0);
typeofpet = petCharmed; //default to charmed...
petpower = 0;
held = false;
nocast = false;
focused = false;
attacked_count = 0;
mezzed = false;
stunned = false;
silenced = false;
amnesiad = false;
inWater = false;
int m;
for (m = 0; m < MAX_SHIELDERS; m++)
{
shielder[m].shielder_id = 0;
shielder[m].shielder_bonus = 0;
}
for (i=0; i<SPECATK_MAXNUM ; i++) {
SpecAttacks[i] = false;
SpecAttackTimers[i] = 0;
}
destructibleobject = false;
wandertype=0;
pausetype=0;
cur_wp = 0;
cur_wp_x = 0;
cur_wp_y = 0;
cur_wp_z = 0;
cur_wp_pause = 0;
patrol=0;
follow=0;
follow_dist = 100; // Default Distance for Follow
flee_mode = false;
fear_walkto_x = -999999;
fear_walkto_y = -999999;
fear_walkto_z = -999999;
curfp = false;
flee_timer.Start();
permarooted = (runspeed > 0) ? false : true;
movetimercompleted = false;
roamer = false;
rooted = false;
charmed = false;
has_virus = false;
for (i=0; i<MAX_SPELL_TRIGGER*2; i++) {
viral_spells[i] = 0;
}
pStandingPetOrder = SPO_Follow;
see_invis = in_see_invis;
see_invis_undead = in_see_invis_undead != 0;
see_hide = in_see_hide != 0;
see_improved_hide = in_see_improved_hide != 0;
qglobal = in_qglobal != 0;
// Bind wound
bindwound_timer.Disable();
bindwound_target = 0;
trade = new Trade(this);
// hp event
nexthpevent = -1;
nextinchpevent = -1;
TempPets(false);
SetHasRune(false);
SetHasSpellRune(false);
SetHasPartialMeleeRune(false);
SetHasPartialSpellRune(false);
m_hasDeathSaveChance = false;
m_is_running = false;
nimbus_effect1 = 0;
nimbus_effect2 = 0;
nimbus_effect3 = 0;
m_targetable = true;
flymode = FlyMode3;
// Pathing
PathingLOSState = UnknownLOS;
PathingLoopCount = 0;
PathingLastNodeVisited = -1;
PathingLOSCheckTimer = new Timer(RuleI(Pathing, LOSCheckFrequency));
PathingRouteUpdateTimerShort = new Timer(RuleI(Pathing, RouteUpdateFrequencyShort));
PathingRouteUpdateTimerLong = new Timer(RuleI(Pathing, RouteUpdateFrequencyLong));
DistractedFromGrid = false;
PathingTraversedNodes = 0;
hate_list.SetOwner(this);
m_AllowBeneficial = false;
m_DisableMelee = false;
for (int i = 0; i < HIGHEST_SKILL+2; i++) { SkillDmgTaken_Mod[i] = 0; }
for (int i = 0; i < HIGHEST_RESIST+2; i++) { Vulnerability_Mod[i] = 0; }
emoteid = 0;
}
Mob::~Mob()
{
// Our Entity ID is set to 0 in NPC::Death. This leads to mobs hanging around for a while in
// the entity list, even after they have been destroyed. Use our memory pointer to remove the mob
// if our EntityID is 0.
//
if(GetID() > 0)
entity_list.RemoveMob(GetID());
else
entity_list.RemoveMob(this);
AI_Stop();
if (GetPet()) {
if (GetPet()->Charmed())
GetPet()->BuffFadeByEffect(SE_Charm);
else
SetPet(0);
}
for (int i=0; i<SPECATK_MAXNUM ; i++) {
safe_delete(SpecAttackTimers[i]);
}
EQApplicationPacket app;
CreateDespawnPacket(&app, !IsCorpse());
Corpse* corpse = entity_list.GetCorpseByID(GetID());
if(!corpse || (corpse && !corpse->IsPlayerCorpse()))
entity_list.QueueClients(this, &app, true);
entity_list.RemoveFromTargets(this, true);
if(trade) {
Mob *with = trade->With();
if(with && with->IsClient()) {
with->CastToClient()->FinishTrade(with);
with->trade->Reset();
}
delete trade;
}
if(HadTempPets()){
entity_list.DestroyTempPets(this);
}
entity_list.UnMarkNPC(GetID());
safe_delete(PathingLOSCheckTimer);
safe_delete(PathingRouteUpdateTimerShort);
safe_delete(PathingRouteUpdateTimerLong);
UninitializeBuffSlots();
}
uint32 Mob::GetAppearanceValue(EmuAppearance iAppearance) {
switch (iAppearance) {
// 0 standing, 1 sitting, 2 ducking, 3 lieing down, 4 looting
case eaStanding: {
return ANIM_STAND;
}
case eaSitting: {
return ANIM_SIT;
}
case eaCrouching: {
return ANIM_CROUCH;
}
case eaDead: {
return ANIM_DEATH;
}
case eaLooting: {
return ANIM_LOOT;
}
//to shup up compiler:
case _eaMaxAppearance:
break;
}
return(ANIM_STAND);
}
void Mob::SetInvisible(uint8 state)
{
invisible = state;
SendAppearancePacket(AT_Invis, invisible);
// Invis and hide breaks charms
if ((this->GetPetType() == petCharmed) && (invisible || hidden || improved_hidden))
{
Mob* formerpet = this->GetPet();
if(formerpet)
formerpet->BuffFadeByEffect(SE_Charm);
}
}
//check to see if `this` is invisible to `other`
bool Mob::IsInvisible(Mob* other) const
{
if(!other)
return(false);
uint8 SeeInvisBonus = 0;
if (IsClient())
SeeInvisBonus = aabonuses.SeeInvis;
//check regular invisibility
if (invisible && invisible > (other->SeeInvisible()))
return true;
//check invis vs. undead
if (other->GetBodyType() == BT_Undead || other->GetBodyType() == BT_SummonedUndead) {
if(invisible_undead && !other->SeeInvisibleUndead())
return true;
}
//check invis vs. animals...
if (other->GetBodyType() == BT_Animal){
if(invisible_animals && !other->SeeInvisible())
return true;
}
if(hidden){
if(!other->see_hide && !other->see_improved_hide){
return true;
}
}
if(improved_hidden){
if(!other->see_improved_hide){
return true;
}
}
//handle sneaking
if(sneaking) {
if(BehindMob(other, GetX(), GetY()) )
return true;
}
return(false);
}
float Mob::_GetMovementSpeed(int mod) const {
// List of movement speed modifiers, including AAs & spells:
// http://everquest.allakhazam.com/db/item.html?item=1721;page=1;howmany=50#m10822246245352
if (IsRooted())
return 0.0f;
float aa_mod = 0.0f;
float speed_mod = runspeed;
bool has_horse = false;
if (IsClient())
{
if(CastToClient()->GetGMSpeed())
{
speed_mod = 3.125f;
}
else
{
Mob* horse = entity_list.GetMob(CastToClient()->GetHorseId());
if(horse)
{
speed_mod = horse->GetBaseRunspeed();
has_horse = true;
}
}
}
aa_mod += itembonuses.BaseMovementSpeed + spellbonuses.BaseMovementSpeed + aabonuses.BaseMovementSpeed;
int spell_mod = spellbonuses.movementspeed + itembonuses.movementspeed;
int movemod = 0;
if(spell_mod < 0)
{
movemod += spell_mod;
}
else if(spell_mod > (aa_mod))
{
movemod = spell_mod;
}
else
{
movemod = static_cast<int>(aa_mod);
}
if(movemod < -85) //cap it at moving very very slow
movemod = -85;
if (!has_horse && movemod != 0)
speed_mod += (speed_mod * float(movemod) / 100.0f);
if(mod != 0)
speed_mod += (speed_mod * (float)mod / 100.0f);
if(speed_mod <= 0.0f)
return(0.0001f);
//runspeed cap.
if(IsClient())
{
if (speed_mod > 1.58){
uint8 bonus_IncreaseRunSpeedCap = itembonuses.IncreaseRunSpeedCap + spellbonuses.IncreaseRunSpeedCap + aabonuses.IncreaseRunSpeedCap;
if (bonus_IncreaseRunSpeedCap){
speed_mod += float(bonus_IncreaseRunSpeedCap)/100.0f;
if(speed_mod > 1.74)
speed_mod = 1.74;
}
else
speed_mod = 1.58;
}
}
return speed_mod;
}
int32 Mob::CalcMaxMana() {
switch (GetCasterClass()) {
case 'I':
max_mana = (((GetINT()/2)+1) * GetLevel()) + spellbonuses.Mana + itembonuses.Mana;
break;
case 'W':
max_mana = (((GetWIS()/2)+1) * GetLevel()) + spellbonuses.Mana + itembonuses.Mana;
break;
case 'N':
default:
max_mana = 0;
break;
}
if (max_mana < 0) {
max_mana = 0;
}
return max_mana;
}
int32 Mob::CalcMaxHP() {
max_hp = (base_hp + itembonuses.HP + spellbonuses.HP);
max_hp += max_hp * (aabonuses.MaxHPChange + spellbonuses.MaxHPChange + itembonuses.MaxHPChange) / 10000;
return max_hp;
}
int32 Mob::GetItemHPBonuses() {
int32 item_hp = 0;
item_hp = itembonuses.HP;
item_hp += item_hp * itembonuses.MaxHPChange / 10000;
return item_hp;
}
int32 Mob::GetSpellHPBonuses() {
int32 spell_hp = 0;
spell_hp = spellbonuses.HP;
spell_hp += spell_hp * spellbonuses.MaxHPChange / 10000;
return spell_hp;
}
char Mob::GetCasterClass() const {
switch(class_)
{
case CLERIC:
case PALADIN:
case RANGER:
case DRUID:
case SHAMAN:
case BEASTLORD:
case CLERICGM:
case PALADINGM:
case RANGERGM:
case DRUIDGM:
case SHAMANGM:
case BEASTLORDGM:
return 'W';
break;
case SHADOWKNIGHT:
case BARD:
case NECROMANCER:
case WIZARD:
case MAGICIAN:
case ENCHANTER:
case SHADOWKNIGHTGM:
case BARDGM:
case NECROMANCERGM:
case WIZARDGM:
case MAGICIANGM:
case ENCHANTERGM:
return 'I';
break;
default:
return 'N';
break;
}
}
uint8 Mob::GetArchetype() const {
switch(class_)
{
case PALADIN:
case RANGER:
case SHADOWKNIGHT:
case BARD:
case BEASTLORD:
case PALADINGM:
case RANGERGM:
case SHADOWKNIGHTGM:
case BARDGM:
case BEASTLORDGM:
return ARCHETYPE_HYBRID;
break;
case CLERIC:
case DRUID:
case SHAMAN:
case NECROMANCER:
case WIZARD:
case MAGICIAN:
case ENCHANTER:
case CLERICGM:
case DRUIDGM:
case SHAMANGM:
case NECROMANCERGM:
case WIZARDGM:
case MAGICIANGM:
case ENCHANTERGM:
return ARCHETYPE_CASTER;
break;
case WARRIOR:
case MONK:
case ROGUE:
case BERSERKER:
case WARRIORGM:
case MONKGM:
case ROGUEGM:
case BERSERKERGM:
return ARCHETYPE_MELEE;
break;
default:
return ARCHETYPE_HYBRID;
break;
}
}
void Mob::CreateSpawnPacket(EQApplicationPacket* app, Mob* ForWho) {
app->SetOpcode(OP_NewSpawn);
app->size = sizeof(NewSpawn_Struct);
app->pBuffer = new uchar[app->size];
memset(app->pBuffer, 0, app->size);
NewSpawn_Struct* ns = (NewSpawn_Struct*)app->pBuffer;
FillSpawnStruct(ns, ForWho);
}
void Mob::CreateSpawnPacket(EQApplicationPacket* app, NewSpawn_Struct* ns) {
app->SetOpcode(OP_NewSpawn);
app->size = sizeof(NewSpawn_Struct);
app->pBuffer = new uchar[sizeof(NewSpawn_Struct)];
// Copy ns directly into packet
memcpy(app->pBuffer, ns, sizeof(NewSpawn_Struct));
// Custom packet data
NewSpawn_Struct* ns2 = (NewSpawn_Struct*)app->pBuffer;
strcpy(ns2->spawn.name, ns->spawn.name);
/*if (ns->spawn.class_==MERCHANT)
strcpy(ns2->spawn.lastName, "EQEmu Shopkeeper");
else*/ if (ns->spawn.class_==TRIBUTE_MASTER)
strcpy(ns2->spawn.lastName, "Tribute Master");
else if (ns->spawn.class_==ADVENTURERECRUITER)
strcpy(ns2->spawn.lastName, "Adventure Recruiter");
else if (ns->spawn.class_==BANKER)
strcpy(ns2->spawn.lastName, "Banker");
else if (ns->spawn.class_==ADVENTUREMERCHANT)
strcpy(ns->spawn.lastName,"Adventure Merchant");
else if (ns->spawn.class_==WARRIORGM)
strcpy(ns2->spawn.lastName, "GM Warrior");
else if (ns->spawn.class_==PALADINGM)
strcpy(ns2->spawn.lastName, "GM Paladin");
else if (ns->spawn.class_==RANGERGM)
strcpy(ns2->spawn.lastName, "GM Ranger");
else if (ns->spawn.class_==SHADOWKNIGHTGM)
strcpy(ns2->spawn.lastName, "GM Shadowknight");
else if (ns->spawn.class_==DRUIDGM)
strcpy(ns2->spawn.lastName, "GM Druid");
else if (ns->spawn.class_==BARDGM)
strcpy(ns2->spawn.lastName, "GM Bard");
else if (ns->spawn.class_==ROGUEGM)
strcpy(ns2->spawn.lastName, "GM Rogue");
else if (ns->spawn.class_==SHAMANGM)
strcpy(ns2->spawn.lastName, "GM Shaman");
else if (ns->spawn.class_==NECROMANCERGM)
strcpy(ns2->spawn.lastName, "GM Necromancer");
else if (ns->spawn.class_==WIZARDGM)
strcpy(ns2->spawn.lastName, "GM Wizard");
else if (ns->spawn.class_==MAGICIANGM)
strcpy(ns2->spawn.lastName, "GM Magician");
else if (ns->spawn.class_==ENCHANTERGM)
strcpy(ns2->spawn.lastName, "GM Enchanter");
else if (ns->spawn.class_==BEASTLORDGM)
strcpy(ns2->spawn.lastName, "GM Beastlord");
else if (ns->spawn.class_==BERSERKERGM)
strcpy(ns2->spawn.lastName, "GM Berserker");
else
strcpy(ns2->spawn.lastName, ns->spawn.lastName);
memset(&app->pBuffer[sizeof(Spawn_Struct)-7],0xFF,7);
}
void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
{
int i;
strcpy(ns->spawn.name, name);
if(IsClient())
{
strn0cpy(ns->spawn.lastName,lastname,sizeof(ns->spawn.lastName));
}
ns->spawn.heading = FloatToEQ19(heading);
ns->spawn.x = FloatToEQ19(x_pos);//((int32)x_pos)<<3;
ns->spawn.y = FloatToEQ19(y_pos);//((int32)y_pos)<<3;
ns->spawn.z = FloatToEQ19(z_pos);//((int32)z_pos)<<3;
ns->spawn.spawnId = GetID();
ns->spawn.curHp = static_cast<uint8>(GetHPRatio());
ns->spawn.max_hp = 100; //this field needs a better name
ns->spawn.race = race;
ns->spawn.runspeed = runspeed;
ns->spawn.walkspeed = runspeed * 0.5f;
ns->spawn.class_ = class_;
ns->spawn.gender = gender;
ns->spawn.level = level;
ns->spawn.deity = deity;
ns->spawn.animation = 0;
ns->spawn.findable = findable?1:0;
// vesuvias - appearence fix
ns->spawn.light = light;
ns->spawn.showhelm = 1;
ns->spawn.invis = (invisible || hidden) ? 1 : 0; // TODO: load this before spawning players
ns->spawn.NPC = IsClient() ? 0 : 1;
ns->spawn.IsMercenary = IsMerc() ? 1 : 0;
ns->spawn.petOwnerId = ownerid;
ns->spawn.haircolor = haircolor;
ns->spawn.beardcolor = beardcolor;
ns->spawn.eyecolor1 = eyecolor1;
ns->spawn.eyecolor2 = eyecolor2;
ns->spawn.hairstyle = hairstyle;
ns->spawn.face = luclinface;
ns->spawn.beard = beard;
ns->spawn.StandState = GetAppearanceValue(_appearance);
ns->spawn.drakkin_heritage = drakkin_heritage;
ns->spawn.drakkin_tattoo = drakkin_tattoo;
ns->spawn.drakkin_details = drakkin_details;
ns->spawn.equip_chest2 = texture;
// ns->spawn.invis2 = 0xff;//this used to be labeled beard.. if its not FF it will turn
//mob invis
if(helmtexture && helmtexture != 0xFF)
{
ns->spawn.helm=helmtexture;
} else {
ns->spawn.helm = 0;
}
ns->spawn.guildrank = 0xFF;
ns->spawn.size = size;
ns->spawn.bodytype = bodytype;
// The 'flymode' settings have the following effect:
// 0 - Mobs in water sink like a stone to the bottom
// 1 - Same as #flymode 1
// 2 - Same as #flymode 2
// 3 - Mobs in water do not sink. A value of 3 in this field appears to be the default setting for all mobs
// (in water or not) according to 6.2 era packet collects.
if(IsClient())
{
ns->spawn.flymode = FindType(SE_Levitate) ? 2 : 0;
}
else
ns->spawn.flymode = flymode;
ns->spawn.lastName[0] = '\0';
strn0cpy(ns->spawn.lastName, lastname, sizeof(ns->spawn.lastName));
for(i = 0; i < MAX_MATERIALS; i++)
{
ns->spawn.equipment[i] = GetEquipmentMaterial(i);
if (armor_tint[i])
{
ns->spawn.colors[i].color = armor_tint[i];
}
else
{
ns->spawn.colors[i].color = GetEquipmentColor(i);
}
}
memset(ns->spawn.set_to_0xFF, 0xFF, sizeof(ns->spawn.set_to_0xFF));
if(IsNPC() && IsDestructibleObject())
{
ns->spawn.DestructibleObject = true;
// Changing the first string made it vanish, so it has some significance.
if(lastname)
sprintf(ns->spawn.DestructibleModel, lastname);
// Changing the second string made no visible difference
sprintf(ns->spawn.DestructibleName2, "%s", ns->spawn.name);
// Putting a string in the final one that was previously empty had no visible effect.
sprintf(ns->spawn.DestructibleString, "");
// Sets damage appearance level of the object.
ns->spawn.DestructibleAppearance = luclinface; // Was 0x00000000
//ns->spawn.DestructibleAppearance = static_cast<EmuAppearance>(_appearance);
// #appearance 44 1 makes it jump but no visible damage
// #appearance 44 2 makes it look completely broken but still visible
// #appearnace 44 3 makes it jump but not visible difference to 3
// #appearance 44 4 makes it disappear altogether
// #appearance 44 5 makes the client crash.
ns->spawn.DestructibleUnk1 = 0x00000224; // Was 0x000001f5;
// These next 4 are mostly always sequential
// Originally they were 633, 634, 635, 636
// Changing them all to 633 - no visible effect.
// Changing them all to 636 - no visible effect.
// Reversing the order of these four numbers and then using #appearance gain had no visible change.
// Setting these four ids to zero had no visible effect when the catapult spawned, nor when #appearance was used.
ns->spawn.DestructibleID1 = 1968;
ns->spawn.DestructibleID2 = 1969;
ns->spawn.DestructibleID3 = 1970;
ns->spawn.DestructibleID4 = 1971;
// Next one was originally 0x1ce45008, changing it to 0x00000000 made no visible difference
ns->spawn.DestructibleUnk2 = 0x13f79d00;
// Next one was originally 0x1a68fe30, changing it to 0x00000000 made no visible difference
ns->spawn.DestructibleUnk3 = 0x00000000;
// Next one was already 0x00000000
ns->spawn.DestructibleUnk4 = 0x13f79d58;
// Next one was originally 0x005a69ec, changing it to 0x00000000 made no visible difference.
ns->spawn.DestructibleUnk5 = 0x13c55b00;
// Next one was originally 0x1a68fe30, changing it to 0x00000000 made no visible difference.
ns->spawn.DestructibleUnk6 = 0x00128860;
// Next one was originally 0x0059de6d, changing it to 0x00000000 made no visible difference.
ns->spawn.DestructibleUnk7 = 0x005a8f66;
// Next one was originally 0x00000201, changing it to 0x00000000 made no visible difference.
// For the Minohten tents, 0x00000000 had them up in the air, while 0x201 put them on the ground.
// Changing it it 0x00000001 makes the tent sink into the ground.
ns->spawn.DestructibleUnk8 = 0x01; // Needs to be 1 for tents?
ns->spawn.DestructibleUnk9 = 0x00000002; // Needs to be 2 for tents?
ns->spawn.flymode = 0;
}
}
void Mob::CreateDespawnPacket(EQApplicationPacket* app, bool Decay)
{
app->SetOpcode(OP_DeleteSpawn);
app->size = sizeof(DeleteSpawn_Struct);
app->pBuffer = new uchar[app->size];
memset(app->pBuffer, 0, app->size);
DeleteSpawn_Struct* ds = (DeleteSpawn_Struct*)app->pBuffer;
ds->spawn_id = GetID();
// The next field only applies to corpses. If 0, they vanish instantly, otherwise they 'decay'
ds->Decay = Decay ? 1 : 0;
}
void Mob::CreateHPPacket(EQApplicationPacket* app)
{
this->IsFullHP=(cur_hp>=max_hp);
app->SetOpcode(OP_MobHealth);
app->size = sizeof(SpawnHPUpdate_Struct2);
app->pBuffer = new uchar[app->size];
memset(app->pBuffer, 0, sizeof(SpawnHPUpdate_Struct2));
SpawnHPUpdate_Struct2* ds = (SpawnHPUpdate_Struct2*)app->pBuffer;
ds->spawn_id = GetID();
// they don't need to know the real hp
ds->hp = (int)GetHPRatio();
// hp event
if (IsNPC() && (GetNextHPEvent() > 0))
{
if (ds->hp < GetNextHPEvent())
{
char buf[10];
snprintf(buf, 9, "%i", GetNextHPEvent());
buf[9] = '\0';
SetNextHPEvent(-1);
parse->EventNPC(EVENT_HP, CastToNPC(), nullptr, buf, 0);
}
}
if (IsNPC() && (GetNextIncHPEvent() > 0))
{
if (ds->hp > GetNextIncHPEvent())
{
char buf[10];
snprintf(buf, 9, "%i", GetNextIncHPEvent());
buf[9] = '\0';
SetNextIncHPEvent(-1);
parse->EventNPC(EVENT_HP, CastToNPC(), nullptr, buf, 1);
}
}
}
// sends hp update of this mob to people who might care
void Mob::SendHPUpdate()
{
EQApplicationPacket hp_app;
Group *group;
// destructor will free the pBuffer
CreateHPPacket(&hp_app);
#ifdef MANAGE_HP_UPDATES
entity_list.QueueManaged(this, &hp_app, true);
#else
// send to people who have us targeted
entity_list.QueueClientsByTarget(this, &hp_app, false, 0, false, true, BIT_AllClients);
entity_list.QueueClientsByXTarget(this, &hp_app, false);
entity_list.QueueToGroupsForNPCHealthAA(this, &hp_app);
// send to group
if(IsGrouped())
{
group = entity_list.GetGroupByMob(this);
if(group) //not sure why this might be null, but it happens
group->SendHPPacketsFrom(this);
}
if(IsClient()){
Raid *r = entity_list.GetRaidByClient(CastToClient());
if(r){
r->SendHPPacketsFrom(this);
}
}
// send to master
if(GetOwner() && GetOwner()->IsClient())
{
GetOwner()->CastToClient()->QueuePacket(&hp_app, false);
group = entity_list.GetGroupByClient(GetOwner()->CastToClient());
if(group)
group->SendHPPacketsFrom(this);
Raid *r = entity_list.GetRaidByClient(GetOwner()->CastToClient());
if(r)
r->SendHPPacketsFrom(this);
}
// send to pet
if(GetPet() && GetPet()->IsClient())
{
GetPet()->CastToClient()->QueuePacket(&hp_app, false);
}
#endif //MANAGE_HP_PACKETS
// Update the damage state of destructible objects
if(IsNPC() && IsDestructibleObject())
{
if (GetHPRatio() > 74)
{
if (GetAppearance() != eaStanding)
{
SendAppearancePacket(AT_DamageState, eaStanding);
_appearance = eaStanding;
}
}
else if (GetHPRatio() > 49)
{
if (GetAppearance() != eaSitting)
{
SendAppearancePacket(AT_DamageState, eaSitting);
_appearance = eaSitting;
}
}
else if (GetHPRatio() > 24)
{
if (GetAppearance() != eaCrouching)
{
SendAppearancePacket(AT_DamageState, eaCrouching);
_appearance = eaCrouching;
}
}
else if (GetHPRatio() > 0)
{
if (GetAppearance() != eaDead)
{
SendAppearancePacket(AT_DamageState, eaDead);
_appearance = eaDead;
}
}
else if (GetAppearance() != eaLooting)
{
SendAppearancePacket(AT_DamageState, eaLooting);
_appearance = eaLooting;
}
}
// send to self - we need the actual hps here
if(IsClient())
{
EQApplicationPacket* hp_app2 = new EQApplicationPacket(OP_HPUpdate,sizeof(SpawnHPUpdate_Struct));
SpawnHPUpdate_Struct* ds = (SpawnHPUpdate_Struct*)hp_app2->pBuffer;
ds->cur_hp = CastToClient()->GetHP() - itembonuses.HP;
ds->spawn_id = GetID();
ds->max_hp = CastToClient()->GetMaxHP() - itembonuses.HP;
CastToClient()->QueuePacket(hp_app2);
safe_delete(hp_app2);
}
}
// this one just warps the mob to the current location
void Mob::SendPosition()
{
EQApplicationPacket* app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
PlayerPositionUpdateServer_Struct* spu = (PlayerPositionUpdateServer_Struct*)app->pBuffer;
MakeSpawnUpdateNoDelta(spu);
move_tic_count = 0;
entity_list.QueueClients(this, app, true);
safe_delete(app);
}
// this one is for mobs on the move, with deltas - this makes them walk
void Mob::SendPosUpdate(uint8 iSendToSelf) {
EQApplicationPacket* app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
PlayerPositionUpdateServer_Struct* spu = (PlayerPositionUpdateServer_Struct*)app->pBuffer;
MakeSpawnUpdate(spu);
if (iSendToSelf == 2) {
if (this->IsClient())
this->CastToClient()->FastQueuePacket(&app,false);
}
else
{
#ifdef PACKET_UPDATE_MANAGER
entity_list.QueueManaged(this, app, (iSendToSelf==0),false);
#else
if(move_tic_count == RuleI(Zone, NPCPositonUpdateTicCount))
{
entity_list.QueueClients(this, app, (iSendToSelf==0), false);
move_tic_count = 0;
}
else
{
entity_list.QueueCloseClients(this, app, (iSendToSelf==0), 800, nullptr, false);
move_tic_count++;
}
#endif
}
safe_delete(app);
}
// this is for SendPosition()
void Mob::MakeSpawnUpdateNoDelta(PlayerPositionUpdateServer_Struct *spu){
memset(spu,0xff,sizeof(PlayerPositionUpdateServer_Struct));
spu->spawn_id = GetID();
spu->x_pos = FloatToEQ19(x_pos);
spu->y_pos = FloatToEQ19(y_pos);
spu->z_pos = FloatToEQ19(z_pos);
spu->delta_x = NewFloatToEQ13(0);
spu->delta_y = NewFloatToEQ13(0);
spu->delta_z = NewFloatToEQ13(0);
spu->heading = FloatToEQ19(heading);
spu->animation = 0;
spu->delta_heading = NewFloatToEQ13(0);
spu->padding0002 =0;
spu->padding0006 =7;
spu->padding0014 =0x7f;
spu->padding0018 =0x5df27;
}
// this is for SendPosUpdate()
void Mob::MakeSpawnUpdate(PlayerPositionUpdateServer_Struct* spu) {
spu->spawn_id = GetID();
spu->x_pos = FloatToEQ19(x_pos);
spu->y_pos = FloatToEQ19(y_pos);
spu->z_pos = FloatToEQ19(z_pos);
spu->delta_x = NewFloatToEQ13(delta_x);
spu->delta_y = NewFloatToEQ13(delta_y);
spu->delta_z = NewFloatToEQ13(delta_z);
spu->heading = FloatToEQ19(heading);
spu->padding0002 =0;
spu->padding0006 =7;
spu->padding0014 =0x7f;
spu->padding0018 =0x5df27;
if(this->IsClient())
spu->animation = animation;
else
spu->animation = pRunAnimSpeed;//animation;
spu->delta_heading = NewFloatToEQ13(static_cast<float>(delta_heading));
}
void Mob::ShowStats(Client* client)
{
if (IsClient()) {
CastToClient()->SendStatsWindow(client, RuleB(Character, UseNewStatsWindow));
}
else if (IsCorpse()) {
if (IsPlayerCorpse()) {
client->Message(0, " CharID: %i PlayerCorpse: %i", CastToCorpse()->GetCharID(), CastToCorpse()->GetDBID());
}
else {
client->Message(0, " NPCCorpse", GetID());
}
}
else {
client->Message(0, " Level: %i AC: %i Class: %i Size: %1.1f Haste: %i", GetLevel(), GetAC(), GetClass(), GetSize(), GetHaste());
client->Message(0, " HP: %i Max HP: %i",GetHP(), GetMaxHP());
client->Message(0, " Mana: %i Max Mana: %i", GetMana(), GetMaxMana());
client->Message(0, " Total ATK: %i Worn/Spell ATK (Cap %i): %i", GetATK(), RuleI(Character, ItemATKCap), GetATKBonus());
client->Message(0, " STR: %i STA: %i DEX: %i AGI: %i INT: %i WIS: %i CHA: %i", GetSTR(), GetSTA(), GetDEX(), GetAGI(), GetINT(), GetWIS(), GetCHA());
client->Message(0, " MR: %i PR: %i FR: %i CR: %i DR: %i Corruption: %i", GetMR(), GetPR(), GetFR(), GetCR(), GetDR(), GetCorrup());
client->Message(0, " Race: %i BaseRace: %i Texture: %i HelmTexture: %i Gender: %i BaseGender: %i", GetRace(), GetBaseRace(), GetTexture(), GetHelmTexture(), GetGender(), GetBaseGender());
if (client->Admin() >= 100)
client->Message(0, " EntityID: %i PetID: %i OwnerID: %i AIControlled: %i Targetted: %i", GetID(), GetPetID(), GetOwnerID(), IsAIControlled(), targeted);
if (IsNPC()) {
NPC *n = CastToNPC();
uint32 spawngroupid = 0;
if(n->respawn2 != 0)
spawngroupid = n->respawn2->SpawnGroupID();
client->Message(0, " NPCID: %u SpawnGroupID: %u Grid: %i LootTable: %u FactionID: %i SpellsID: %u ", GetNPCTypeID(),spawngroupid, n->GetGrid(), n->GetLoottableID(), n->GetNPCFactionID(), n->GetNPCSpellsID());
client->Message(0, " Accuracy: %i MerchantID: %i EmoteID: %i Runspeed: %f Walkspeed: %f", n->GetAccuracyRating(), n->MerchantType, n->GetEmoteID(), n->GetRunspeed(), n->GetWalkspeed());
n->QueryLoot(client);
}
if (IsAIControlled()) {
client->Message(0, " AggroRange: %1.0f AssistRange: %1.0f", GetAggroRange(), GetAssistRange());
}
}
}
void Mob::DoAnim(const int animnum, int type, bool ackreq, eqFilterType filter) {
EQApplicationPacket* outapp = new EQApplicationPacket(OP_Animation, sizeof(Animation_Struct));
Animation_Struct* anim = (Animation_Struct*)outapp->pBuffer;
anim->spawnid = GetID();
if(type == 0){
anim->action = 10;
anim->value=animnum;
}
else{
anim->action = animnum;
anim->value=type;
}
entity_list.QueueCloseClients(this, outapp, false, 200, 0, ackreq, filter);
safe_delete(outapp);
}
void Mob::ShowBuffs(Client* client) {
if(SPDAT_RECORDS <= 0)
return;
client->Message(0, "Buffs on: %s", this->GetName());
uint32 i;
uint32 buff_count = GetMaxTotalSlots();
for (i=0; i < buff_count; i++) {
if (buffs[i].spellid != SPELL_UNKNOWN) {
if (spells[buffs[i].spellid].buffdurationformula == DF_Permanent)
client->Message(0, " %i: %s: Permanent", i, spells[buffs[i].spellid].name);
else
client->Message(0, " %i: %s: %i tics left", i, spells[buffs[i].spellid].name, buffs[i].ticsremaining);
}
}
if (IsClient()){
client->Message(0, "itembonuses:");
client->Message(0, "Atk:%i Ac:%i HP(%i):%i Mana:%i", itembonuses.ATK, itembonuses.AC, itembonuses.HPRegen, itembonuses.HP, itembonuses.Mana);
client->Message(0, "Str:%i Sta:%i Dex:%i Agi:%i Int:%i Wis:%i Cha:%i",
itembonuses.STR,itembonuses.STA,itembonuses.DEX,itembonuses.AGI,itembonuses.INT,itembonuses.WIS,itembonuses.CHA);
client->Message(0, "SvMagic:%i SvFire:%i SvCold:%i SvPoison:%i SvDisease:%i",
itembonuses.MR,itembonuses.FR,itembonuses.CR,itembonuses.PR,itembonuses.DR);
client->Message(0, "DmgShield:%i Haste:%i", itembonuses.DamageShield, itembonuses.haste );
client->Message(0, "spellbonuses:");
client->Message(0, "Atk:%i Ac:%i HP(%i):%i Mana:%i", spellbonuses.ATK, spellbonuses.AC, spellbonuses.HPRegen, spellbonuses.HP, spellbonuses.Mana);
client->Message(0, "Str:%i Sta:%i Dex:%i Agi:%i Int:%i Wis:%i Cha:%i",
spellbonuses.STR,spellbonuses.STA,spellbonuses.DEX,spellbonuses.AGI,spellbonuses.INT,spellbonuses.WIS,spellbonuses.CHA);
client->Message(0, "SvMagic:%i SvFire:%i SvCold:%i SvPoison:%i SvDisease:%i",
spellbonuses.MR,spellbonuses.FR,spellbonuses.CR,spellbonuses.PR,spellbonuses.DR);
client->Message(0, "DmgShield:%i Haste:%i", spellbonuses.DamageShield, spellbonuses.haste );
}
}
void Mob::ShowBuffList(Client* client) {
if(SPDAT_RECORDS <= 0)
return;
client->Message(0, "Buffs on: %s", this->GetCleanName());
uint32 i;
uint32 buff_count = GetMaxTotalSlots();
for (i=0; i < buff_count; i++) {
if (buffs[i].spellid != SPELL_UNKNOWN) {
if (spells[buffs[i].spellid].buffdurationformula == DF_Permanent)
client->Message(0, " %i: %s: Permanent", i, spells[buffs[i].spellid].name);
else
client->Message(0, " %i: %s: %i tics left", i, spells[buffs[i].spellid].name, buffs[i].ticsremaining);
}
}
}
void Mob::GMMove(float x, float y, float z, float heading, bool SendUpdate) {
Route.clear();
x_pos = x;
y_pos = y;
z_pos = z;
if (heading != 0.01)
this->heading = heading;
if(IsNPC())
CastToNPC()->SaveGuardSpot(true);
if(SendUpdate)
SendPosition();
//SendPosUpdate(1);
#ifdef PACKET_UPDATE_MANAGER
if(IsClient()) {
CastToClient()->GetUpdateManager()->FlushQueues();
}
#endif
}
void Mob::SendIllusionPacket(uint16 in_race, uint8 in_gender, uint8 in_texture, uint8 in_helmtexture, uint8 in_haircolor, uint8 in_beardcolor, uint8 in_eyecolor1, uint8 in_eyecolor2, uint8 in_hairstyle, uint8 in_luclinface, uint8 in_beard, uint8 in_aa_title, uint32 in_drakkin_heritage, uint32 in_drakkin_tattoo, uint32 in_drakkin_details, float in_size) {
uint16 BaseRace = GetBaseRace();
if (in_race == 0) {
this->race = BaseRace;
if (in_gender == 0xFF)
this->gender = GetBaseGender();
else
this->gender = in_gender;
}
else {
this->race = in_race;
if (in_gender == 0xFF) {
uint8 tmp = Mob::GetDefaultGender(this->race, gender);
if (tmp == 2)
gender = 2;
else if (gender == 2 && GetBaseGender() == 2)
gender = tmp;
else if (gender == 2)
gender = GetBaseGender();
}
else
gender = in_gender;
}
if (in_texture == 0xFF) {
if (in_race <= 12 || in_race == 128 || in_race == 130 || in_race == 330 || in_race == 522)
this->texture = 0xFF;
else
this->texture = GetTexture();
}
else
this->texture = in_texture;
if (in_helmtexture == 0xFF) {
if (in_race <= 12 || in_race == 128 || in_race == 130 || in_race == 330 || in_race == 522)
this->helmtexture = 0xFF;
else if (in_texture != 0xFF)
this->helmtexture = in_texture;
else
this->helmtexture = GetHelmTexture();
}
else
this->helmtexture = in_helmtexture;
if (in_haircolor == 0xFF)
this->haircolor = GetHairColor();
else
this->haircolor = in_haircolor;
if (in_beardcolor == 0xFF)
this->beardcolor = GetBeardColor();
else
this->beardcolor = in_beardcolor;
if (in_eyecolor1 == 0xFF)
this->eyecolor1 = GetEyeColor1();
else
this->eyecolor1 = in_eyecolor1;
if (in_eyecolor2 == 0xFF)
this->eyecolor2 = GetEyeColor2();
else
this->eyecolor2 = in_eyecolor2;
if (in_hairstyle == 0xFF)
this->hairstyle = GetHairStyle();
else
this->hairstyle = in_hairstyle;
if (in_luclinface == 0xFF)
this->luclinface = GetLuclinFace();
else
this->luclinface = in_luclinface;
if (in_beard == 0xFF)
this->beard = GetBeard();
else
this->beard = in_beard;
this->aa_title = 0xFF;
if (in_drakkin_heritage == 0xFFFFFFFF)
this->drakkin_heritage = GetDrakkinHeritage();
else
this->drakkin_heritage = in_drakkin_heritage;
if (in_drakkin_tattoo == 0xFFFFFFFF)
this->drakkin_tattoo = GetDrakkinTattoo();
else
this->drakkin_tattoo = in_drakkin_tattoo;
if (in_drakkin_details == 0xFFFFFFFF)
this->drakkin_details = GetDrakkinDetails();
else
this->drakkin_details = in_drakkin_details;
if (in_size == 0xFFFFFFFF)
this->size = GetSize();
else
this->size = in_size;
// Forces the feature information to be pulled from the Player Profile
if (this->IsClient() && in_race == 0) {
this->race = CastToClient()->GetBaseRace();
this->gender = CastToClient()->GetBaseGender();
this->texture = 0xFF;
this->helmtexture = 0xFF;
this->haircolor = CastToClient()->GetBaseHairColor();
this->beardcolor = CastToClient()->GetBaseBeardColor();
this->eyecolor1 = CastToClient()->GetBaseEyeColor();
this->eyecolor2 = CastToClient()->GetBaseEyeColor();
this->hairstyle = CastToClient()->GetBaseHairStyle();
this->luclinface = CastToClient()->GetBaseFace();
this->beard = CastToClient()->GetBaseBeard();
this->aa_title = 0xFF;
this->drakkin_heritage = CastToClient()->GetBaseHeritage();
this->drakkin_tattoo = CastToClient()->GetBaseTattoo();
this->drakkin_details = CastToClient()->GetBaseDetails();
switch(race){
case OGRE:
this->size = 9;
break;
case TROLL:
this->size = 8;
break;
case VAHSHIR:
case BARBARIAN:
this->size = 7;
break;
case HALF_ELF:
case WOOD_ELF:
case DARK_ELF:
case FROGLOK:
this->size = 5;
break;
case DWARF:
this->size = 4;
break;
case HALFLING:
case GNOME:
this->size = 3;
break;
default:
this->size = 6;
break;
}
}
EQApplicationPacket* outapp = new EQApplicationPacket(OP_Illusion, sizeof(Illusion_Struct));
memset(outapp->pBuffer, 0, sizeof(outapp->pBuffer));
Illusion_Struct* is = (Illusion_Struct*) outapp->pBuffer;
is->spawnid = this->GetID();
strcpy(is->charname, GetCleanName());
is->race = this->race;
is->gender = this->gender;
is->texture = this->texture;
is->helmtexture = this->helmtexture;
is->haircolor = this->haircolor;
is->beardcolor = this->beardcolor;
is->beard = this->beard;
is->eyecolor1 = this->eyecolor1;
is->eyecolor2 = this->eyecolor2;
is->hairstyle = this->hairstyle;
is->face = this->luclinface;
//is->aa_title = this->aa_title;
is->drakkin_heritage = this->drakkin_heritage;
is->drakkin_tattoo = this->drakkin_tattoo;
is->drakkin_details = this->drakkin_details;
is->size = this->size;
entity_list.QueueClients(this, outapp);
safe_delete(outapp);
mlog(CLIENT__SPELLS, "Illusion: Race = %i, Gender = %i, Texture = %i, HelmTexture = %i, HairColor = %i, BeardColor = %i, EyeColor1 = %i, EyeColor2 = %i, HairStyle = %i, Face = %i, DrakkinHeritage = %i, DrakkinTattoo = %i, DrakkinDetails = %i, Size = %f",
this->race, this->gender, this->texture, this->helmtexture, this->haircolor, this->beardcolor, this->eyecolor1, this->eyecolor2, this->hairstyle, this->luclinface, this->drakkin_heritage, this->drakkin_tattoo, this->drakkin_details, this->size);
}
uint8 Mob::GetDefaultGender(uint16 in_race, uint8 in_gender) {
//cout << "Gender in: " << (int)in_gender << endl;
if ((in_race > 0 && in_race <= GNOME )
|| in_race == IKSAR || in_race == VAHSHIR || in_race == FROGLOK || in_race == DRAKKIN
|| in_race == 15 || in_race == 50 || in_race == 57 || in_race == 70 || in_race == 98 || in_race == 118) {
if (in_gender >= 2) {
// Female default for PC Races
return 1;
}
else
return in_gender;
}
else if (in_race == 44 || in_race == 52 || in_race == 55 || in_race == 65 || in_race == 67 || in_race == 88 || in_race == 117 || in_race == 127 ||
in_race == 77 || in_race == 78 || in_race == 81 || in_race == 90 || in_race == 92 || in_race == 93 || in_race == 94 || in_race == 106 || in_race == 112 || in_race == 471) {
// Male only races
return 0;
}
else if (in_race == 25 || in_race == 56) {
// Female only races
return 1;
}
else {
// Neutral default for NPC Races
return 2;
}
}
void Mob::SendAppearancePacket(uint32 type, uint32 value, bool WholeZone, bool iIgnoreSelf, Client *specific_target) {
if (!GetID())
return;
EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct));
SpawnAppearance_Struct* appearance = (SpawnAppearance_Struct*)outapp->pBuffer;
appearance->spawn_id = this->GetID();
appearance->type = type;
appearance->parameter = value;
if (WholeZone)
entity_list.QueueClients(this, outapp, iIgnoreSelf);
else if(specific_target != nullptr)
specific_target->QueuePacket(outapp, false, Client::CLIENT_CONNECTED);
else if (this->IsClient())
this->CastToClient()->QueuePacket(outapp, false, Client::CLIENT_CONNECTED);
safe_delete(outapp);
}
void Mob::SendLevelAppearance(){
EQApplicationPacket* outapp = new EQApplicationPacket(OP_LevelAppearance, sizeof(LevelAppearance_Struct));
LevelAppearance_Struct* la = (LevelAppearance_Struct*)outapp->pBuffer;
la->parm1 = 0x4D;
la->parm2 = la->parm1 + 1;
la->parm3 = la->parm2 + 1;
la->parm4 = la->parm3 + 1;
la->parm5 = la->parm4 + 1;
la->spawn_id = GetID();
la->value1a = 1;
la->value2a = 2;
la->value3a = 1;
la->value3b = 1;
la->value4a = 1;
la->value4b = 1;
la->value5a = 2;
entity_list.QueueCloseClients(this,outapp);
safe_delete(outapp);
}
void Mob::SendAppearanceEffect(uint32 parm1, uint32 parm2, uint32 parm3, uint32 parm4, uint32 parm5, Client *specific_target){
EQApplicationPacket* outapp = new EQApplicationPacket(OP_LevelAppearance, sizeof(LevelAppearance_Struct));
LevelAppearance_Struct* la = (LevelAppearance_Struct*)outapp->pBuffer;
la->spawn_id = GetID();
la->parm1 = parm1;
la->parm2 = parm2;
la->parm3 = parm3;
la->parm4 = parm4;
la->parm5 = parm5;
// Note that setting the b values to 0 will disable the related effect from the corresponding parameter.
// Setting the a value appears to have no affect at all.
la->value1a = 1;
la->value1b = 1;
la->value2a = 1;
la->value2b = 1;
la->value3a = 1;
la->value3b = 1;
la->value4a = 1;
la->value4b = 1;
la->value5a = 1;
la->value5b = 1;
if(specific_target == nullptr) {
entity_list.QueueClients(this,outapp);
}
else if (specific_target->IsClient()) {
specific_target->CastToClient()->QueuePacket(outapp, false);
}
safe_delete(outapp);
}
void Mob::SendTargetable(bool on, Client *specific_target) {
EQApplicationPacket* outapp = new EQApplicationPacket(OP_Untargetable, sizeof(Untargetable_Struct));
Untargetable_Struct *ut = (Untargetable_Struct*)outapp->pBuffer;
ut->id = GetID();
ut->targetable_flag = on == true ? 1 : 0;
if(specific_target == nullptr) {
entity_list.QueueClients(this, outapp);
}
else if (specific_target->IsClient()) {
specific_target->CastToClient()->QueuePacket(outapp, false);
}
safe_delete(outapp);
}
void Mob::QuestReward(Client *c, uint32 silver, uint32 gold, uint32 platinum) {
EQApplicationPacket* outapp = new EQApplicationPacket(OP_Sound, sizeof(QuestReward_Struct));
memset(outapp->pBuffer, 0, sizeof(outapp->pBuffer));
QuestReward_Struct* qr = (QuestReward_Struct*) outapp->pBuffer;
qr->from_mob = GetID(); // Entity ID for the from mob name
qr->silver = silver;
qr->gold = gold;
qr->platinum = platinum;
if(c)
c->QueuePacket(outapp, false, Client::CLIENT_CONNECTED);
safe_delete(outapp);
}
void Mob::CameraEffect(uint32 duration, uint32 intensity, Client *c, bool global) {
if(global == true)
{
ServerPacket* pack = new ServerPacket(ServerOP_CameraShake, sizeof(ServerCameraShake_Struct));
memset(pack->pBuffer, 0, sizeof(pack->pBuffer));
ServerCameraShake_Struct* scss = (ServerCameraShake_Struct*) pack->pBuffer;
scss->duration = duration;
scss->intensity = intensity;
worldserver.SendPacket(pack);
safe_delete(pack);
return;
}
EQApplicationPacket* outapp = new EQApplicationPacket(OP_CameraEffect, sizeof(Camera_Struct));
memset(outapp->pBuffer, 0, sizeof(outapp->pBuffer));
Camera_Struct* cs = (Camera_Struct*) outapp->pBuffer;
cs->duration = duration; // Duration in milliseconds
cs->intensity = ((intensity * 6710886) + 1023410176); // Intensity ranges from 1023410176 to 1090519040, so simplify it from 0 to 10.
if(c)
c->QueuePacket(outapp, false, Client::CLIENT_CONNECTED);
else
entity_list.QueueClients(this, outapp);
safe_delete(outapp);
}
void Mob::SendSpellEffect(uint32 effectid, uint32 duration, uint32 finish_delay, bool zone_wide, uint32 unk020, bool perm_effect, Client *c) {
EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpellEffect, sizeof(SpellEffect_Struct));
memset(outapp->pBuffer, 0, sizeof(outapp->pBuffer));
SpellEffect_Struct* se = (SpellEffect_Struct*) outapp->pBuffer;
se->EffectID = effectid; // ID of the Particle Effect
se->EntityID = GetID();
se->EntityID2 = GetID(); // EntityID again
se->Duration = duration; // In Milliseconds
se->FinishDelay = finish_delay; // Seen 0
se->Unknown020 = unk020; // Seen 3000
se->Unknown024 = 1; // Seen 1 for SoD
se->Unknown025 = 1; // Seen 1 for Live
se->Unknown026 = 0; // Seen 1157
if(c)
c->QueuePacket(outapp, false, Client::CLIENT_CONNECTED);
else if(zone_wide)
entity_list.QueueClients(this, outapp);
else
entity_list.QueueCloseClients(this, outapp);
safe_delete(outapp);
if (perm_effect) {
if(!IsNimbusEffectActive(effectid)) {
SetNimbusEffect(effectid);
}
}
}
void Mob::TempName(const char *newname)
{
char temp_name[64];
char old_name[64];
strn0cpy(old_name, GetName(), 64);
if(newname)
strn0cpy(temp_name, newname, 64);
// Reset the name to the original if left null.
if(!newname) {
strn0cpy(temp_name, GetOrigName(), 64);
SetName(temp_name);
//CleanMobName(GetName(), temp_name);
strn0cpy(temp_name, GetCleanName(), 64);
}
// Make the new name unique and set it
strn0cpy(temp_name, entity_list.MakeNameUnique(temp_name), 64);
// Send the new name to all clients
EQApplicationPacket* outapp = new EQApplicationPacket(OP_MobRename, sizeof(MobRename_Struct));
memset(outapp->pBuffer, 0, sizeof(outapp->pBuffer));
MobRename_Struct* mr = (MobRename_Struct*) outapp->pBuffer;
strn0cpy(mr->old_name, old_name, 64);
strn0cpy(mr->old_name_again, old_name, 64);
strn0cpy(mr->new_name, temp_name, 64);
mr->unknown192 = 0;
mr->unknown196 = 1;
entity_list.QueueClients(this, outapp);
safe_delete(outapp);
SetName(temp_name);
}
void Mob::SetTargetable(bool on) {
if(m_targetable != on) {
m_targetable = on;
SendTargetable(on);
}
}
const int32& Mob::SetMana(int32 amount)
{
CalcMaxMana();
int32 mmana = GetMaxMana();
cur_mana = amount < 0 ? 0 : (amount > mmana ? mmana : amount);
/*
if(IsClient())
LogFile->write(EQEMuLog::Debug, "Setting mana for %s to %d (%4.1f%%)", GetName(), amount, GetManaRatio());
*/
return cur_mana;
}
void Mob::SetAppearance(EmuAppearance app, bool iIgnoreSelf) {
if (_appearance != app) {
_appearance = app;
SendAppearancePacket(AT_Anim, GetAppearanceValue(app), true, iIgnoreSelf);
if (this->IsClient() && this->IsAIControlled())
SendAppearancePacket(AT_Anim, ANIM_FREEZE, false, false);
}
}
void Mob::ChangeSize(float in_size = 0, bool bNoRestriction) {
// Size Code
if (!bNoRestriction)
{
if (this->IsClient() || this->petid != 0)
if (in_size < 3.0)
in_size = 3.0;
if (this->IsClient() || this->petid != 0)
if (in_size > 15.0)
in_size = 15.0;
}
if (in_size < 1.0)
in_size = 1.0;
if (in_size > 255.0)
in_size = 255.0;
//End of Size Code
this->size = in_size;
SendAppearancePacket(AT_Size, (uint32) in_size);
}
Mob* Mob::GetOwnerOrSelf() {
if (!GetOwnerID())
return this;
Mob* owner = entity_list.GetMob(this->GetOwnerID());
if (!owner) {
SetOwnerID(0);
return(this);
}
if (owner->GetPetID() == this->GetID()) {
return owner;
}
if(IsNPC() && CastToNPC()->GetSwarmInfo()){
return (CastToNPC()->GetSwarmInfo()->GetOwner());
}
SetOwnerID(0);
return this;
}
Mob* Mob::GetOwner() {
Mob* owner = entity_list.GetMob(this->GetOwnerID());
if (owner && owner->GetPetID() == this->GetID()) {
return owner;
}
if(IsNPC() && CastToNPC()->GetSwarmInfo()){
return (CastToNPC()->GetSwarmInfo()->GetOwner());
}
SetOwnerID(0);
return 0;
}
Mob* Mob::GetUltimateOwner()
{
Mob* Owner = GetOwner();
if(!Owner)
return this;
while(Owner && Owner->HasOwner())
Owner = Owner->GetOwner();
return Owner ? Owner : this;
}
void Mob::SetOwnerID(uint16 NewOwnerID) {
if (NewOwnerID == GetID() && NewOwnerID != 0) // ok, no charming yourself now =p
return;
ownerid = NewOwnerID;
if (ownerid == 0 && this->IsNPC() && this->GetPetType() != petCharmed)
this->Depop();
}
//heko: for backstab
bool Mob::BehindMob(Mob* other, float playerx, float playery) const {
if (!other)
return true; // sure your behind your invisible friend?? (fall thru for sneak)
//see if player is behind mob
float angle, lengthb, vectorx, vectory;
float mobx = -(other->GetX()); // mob xlocation (inverse because eq is confused)
float moby = other->GetY(); // mobylocation
float heading = other->GetHeading(); // mob heading
heading = (heading * 360.0f) / 256.0f; // convert to degrees
if (heading < 270)
heading += 90;
else
heading -= 270;
heading = heading * 3.1415f / 180.0f; // convert to radians
vectorx = mobx + (10.0f * cosf(heading)); // create a vector based on heading
vectory = moby + (10.0f * sinf(heading)); // of mob length 10
//length of mob to player vector
//lengthb = (float)sqrtf(pow((-playerx-mobx),2) + pow((playery-moby),2));
lengthb = (float) sqrtf( ( (-playerx-mobx) * (-playerx-mobx) ) + ( (playery-moby) * (playery-moby) ) );
// calculate dot product to get angle
angle = acosf(((vectorx-mobx)*(-playerx-mobx)+(vectory-moby)*(playery-moby)) / (10 * lengthb));
angle = angle * 180.0f / 3.1415f;
if (angle > 90.0f) //not sure what value to use (90*2=180 degrees is front)
return true;
else
return false;
}
void Mob::SetZone(uint32 zone_id, uint32 instance_id)
{
if(IsClient())
{
CastToClient()->GetPP().zone_id = zone_id;
CastToClient()->GetPP().zoneInstance = instance_id;
}
Save();
}
void Mob::Kill() {
Death(this, 0, SPELL_UNKNOWN, HAND_TO_HAND);
}
void Mob::SetAttackTimer() {
float PermaHaste;
if(GetHaste() > 0)
PermaHaste = 1 / (1 + (float)GetHaste()/100);
else if(GetHaste() < 0)
PermaHaste = 1 * (1 - (float)GetHaste()/100);
else
PermaHaste = 1.0f;
//default value for attack timer in case they have
//an invalid weapon equipped:
attack_timer.SetAtTrigger(4000, true);
Timer* TimerToUse = nullptr;
const Item_Struct* PrimaryWeapon = nullptr;
for (int i=SLOT_RANGE; i<=SLOT_SECONDARY; i++) {
//pick a timer
if (i == SLOT_PRIMARY)
TimerToUse = &attack_timer;
else if (i == SLOT_RANGE)
TimerToUse = &ranged_timer;
else if(i == SLOT_SECONDARY)
TimerToUse = &attack_dw_timer;
else //invalid slot (hands will always hit this)
continue;
const Item_Struct* ItemToUse = nullptr;
//find our item
if (IsClient()) {
ItemInst* ci = CastToClient()->GetInv().GetItem(i);
if (ci)
ItemToUse = ci->GetItem();
} else if(IsNPC())
{
//The code before here was fundementally flawed because equipment[]
//isn't the same as PC inventory and also:
//NPCs don't use weapon speed to dictate how fast they hit anyway.
ItemToUse = nullptr;
}
//special offhand stuff
if(i == SLOT_SECONDARY) {
//if we have a 2H weapon in our main hand, no dual
if(PrimaryWeapon != nullptr) {
if( PrimaryWeapon->ItemClass == ItemClassCommon
&& (PrimaryWeapon->ItemType == ItemType2HS
|| PrimaryWeapon->ItemType == ItemType2HB
|| PrimaryWeapon->ItemType == ItemType2HPierce)) {
attack_dw_timer.Disable();
continue;
}
}
//clients must have the skill to use it...
if(IsClient()) {
//if we cant dual wield, skip it
if (!CanThisClassDualWield()) {
attack_dw_timer.Disable();
continue;
}
} else {
//NPCs get it for free at 13
if(GetLevel() < 13) {
attack_dw_timer.Disable();
continue;
}
}
}
//see if we have a valid weapon
if(ItemToUse != nullptr) {
//check type and damage/delay
if(ItemToUse->ItemClass != ItemClassCommon
|| ItemToUse->Damage == 0
|| ItemToUse->Delay == 0) {
//no weapon
ItemToUse = nullptr;
}
// Check to see if skill is valid
else if((ItemToUse->ItemType > ItemTypeThrowing) && (ItemToUse->ItemType != ItemTypeHand2Hand) && (ItemToUse->ItemType != ItemType2HPierce)) {
//no weapon
ItemToUse = nullptr;
}
}
int16 DelayMod = itembonuses.HundredHands + spellbonuses.HundredHands;
if (DelayMod < -99)
DelayMod = -99;
//if we have no weapon..
if (ItemToUse == nullptr) {
//above checks ensure ranged weapons do not fall into here
// Work out if we're a monk
if ((GetClass() == MONK) || (GetClass() == BEASTLORD)) {
//we are a monk, use special delay
int speed = (int)( (GetMonkHandToHandDelay()*(100+DelayMod)/100)*(100.0f+attack_speed)*PermaHaste);
// 1200 seemed too much, with delay 10 weapons available
if(speed < RuleI(Combat, MinHastedDelay)) //lower bound
speed = RuleI(Combat, MinHastedDelay);
TimerToUse->SetAtTrigger(speed, true); // Hand to hand, delay based on level or epic
} else {
//not a monk... using fist, regular delay
int speed = (int)((36 *(100+DelayMod)/100)*(100.0f+attack_speed)*PermaHaste);
if(speed < RuleI(Combat, MinHastedDelay) && IsClient()) //lower bound
speed = RuleI(Combat, MinHastedDelay);
TimerToUse->SetAtTrigger(speed, true); // Hand to hand, non-monk 2/36
}
} else {
//we have a weapon, use its delay
// Convert weapon delay to timer resolution (milliseconds)
//delay * 100
int speed = (int)((ItemToUse->Delay*(100+DelayMod)/100)*(100.0f+attack_speed)*PermaHaste);
if(speed < RuleI(Combat, MinHastedDelay))
speed = RuleI(Combat, MinHastedDelay);
if(ItemToUse && (ItemToUse->ItemType == ItemTypeBow || ItemToUse->ItemType == ItemTypeThrowing))
{
if(IsClient())
{
float max_quiver = 0;
for(int r = SLOT_PERSONAL_BEGIN; r <= SLOT_PERSONAL_END; r++)
{
const ItemInst *pi = CastToClient()->GetInv().GetItem(r);
if(!pi)
continue;
if(pi->IsType(ItemClassContainer) && pi->GetItem()->BagType == bagTypeQuiver)
{
float temp_wr = ( pi->GetItem()->BagWR / RuleI(Combat, QuiverWRHasteDiv) );
if(temp_wr > max_quiver)
{
max_quiver = temp_wr;
}
}
}
if(max_quiver > 0)
{
float quiver_haste = 1 / (1 + max_quiver / 100);
speed *= quiver_haste;
}
}
}
TimerToUse->SetAtTrigger(speed, true);
}
if(i == SLOT_PRIMARY)
PrimaryWeapon = ItemToUse;
}
}
bool Mob::CanThisClassDualWield(void) const
{
if (!IsClient()) {
return(GetSkill(DUAL_WIELD) > 0);
} else {
const ItemInst* inst = CastToClient()->GetInv().GetItem(SLOT_PRIMARY);
// 2HS, 2HB, or 2HP
if (inst && inst->IsType(ItemClassCommon)) {
const Item_Struct* item = inst->GetItem();
if ((item->ItemType == ItemType2HB) || (item->ItemType == ItemType2HS) || (item->ItemType == ItemType2HPierce))
return false;
} else {
//No weapon in hand... using hand-to-hand...
//only monks and beastlords? can dual wield their fists.
if(class_ != MONK && class_ != MONKGM && class_ != BEASTLORD && class_ != BEASTLORDGM) {
return false;
}
}
return (CastToClient()->HasSkill(DUAL_WIELD)); // No skill = no chance
}
}
bool Mob::CanThisClassDoubleAttack(void) const
{
if(!IsClient()) {
return(GetSkill(DOUBLE_ATTACK) > 0);
} else {
if(aabonuses.GiveDoubleAttack || itembonuses.GiveDoubleAttack || spellbonuses.GiveDoubleAttack) {
return true;
}
return(CastToClient()->HasSkill(DOUBLE_ATTACK));
}
}
bool Mob::IsWarriorClass(void) const
{
switch(GetClass())
{
case WARRIOR:
case WARRIORGM:
case ROGUE:
case ROGUEGM:
case MONK:
case MONKGM:
case PALADIN:
case PALADINGM:
case SHADOWKNIGHT:
case SHADOWKNIGHTGM:
case RANGER:
case RANGERGM:
case BEASTLORD:
case BEASTLORDGM:
case BERSERKER:
case BERSERKERGM:
case BARD:
case BARDGM:
{
return true;
}
default:
{
return false;
}
}
}
bool Mob::CanThisClassParry(void) const
{
if(!IsClient()) {
return(GetSkill(PARRY) > 0);
} else {
return(CastToClient()->HasSkill(PARRY));
}
}
bool Mob::CanThisClassDodge(void) const
{
if(!IsClient()) {
return(GetSkill(DODGE) > 0);
} else {
return(CastToClient()->HasSkill(DODGE));
}
}
bool Mob::CanThisClassRiposte(void) const
{
if(!IsClient()) {
return(GetSkill(RIPOSTE) > 0);
} else {
return(CastToClient()->HasSkill(RIPOSTE));
}
}
bool Mob::CanThisClassBlock(void) const
{
if(!IsClient()) {
return(GetSkill(BLOCKSKILL) > 0);
} else {
return(CastToClient()->HasSkill(BLOCKSKILL));
}
}
float Mob::Dist(const Mob &other) const {
_ZP(Mob_Dist);
float xDiff = other.x_pos - x_pos;
float yDiff = other.y_pos - y_pos;
float zDiff = other.z_pos - z_pos;
return sqrtf( (xDiff * xDiff)
+ (yDiff * yDiff)
+ (zDiff * zDiff) );
}
float Mob::DistNoZ(const Mob &other) const {
_ZP(Mob_DistNoZ);
float xDiff = other.x_pos - x_pos;
float yDiff = other.y_pos - y_pos;
return sqrtf( (xDiff * xDiff)
+ (yDiff * yDiff) );
}
float Mob::DistNoRoot(const Mob &other) const {
_ZP(Mob_DistNoRoot);
float xDiff = other.x_pos - x_pos;
float yDiff = other.y_pos - y_pos;
float zDiff = other.z_pos - z_pos;
return ( (xDiff * xDiff)
+ (yDiff * yDiff)
+ (zDiff * zDiff) );
}
float Mob::DistNoRoot(float x, float y, float z) const {
_ZP(Mob_DistNoRoot);
float xDiff = x - x_pos;
float yDiff = y - y_pos;
float zDiff = z - z_pos;
return ( (xDiff * xDiff)
+ (yDiff * yDiff)
+ (zDiff * zDiff) );
}
float Mob::DistNoRootNoZ(float x, float y) const {
_ZP(Mob_DistNoRoot);
float xDiff = x - x_pos;
float yDiff = y - y_pos;
return ( (xDiff * xDiff)
+ (yDiff * yDiff) );
}
float Mob::DistNoRootNoZ(const Mob &other) const {
_ZP(Mob_DistNoRootNoZ);
float xDiff = other.x_pos - x_pos;
float yDiff = other.y_pos - y_pos;
return ( (xDiff * xDiff) + (yDiff * yDiff) );
}
float Mob::GetReciprocalHeading(Mob* target) {
float Result = 0;
if(target) {
// Convert to radians
float h = (target->GetHeading() / 256.0f) * 6.283184f;
// Calculate the reciprocal heading in radians
Result = h + 3.141592f;
// Convert back to eq heading from radians
Result = (Result / 6.283184f) * 256.0f;
}
return Result;
}
bool Mob::PlotPositionAroundTarget(Mob* target, float &x_dest, float &y_dest, float &z_dest, bool lookForAftArc) {
bool Result = false;
if(target) {
float look_heading = 0;
if(lookForAftArc)
look_heading = GetReciprocalHeading(target);
else
look_heading = target->GetHeading();
// Convert to sony heading to radians
look_heading = (look_heading / 256.0f) * 6.283184f;
float tempX = 0;
float tempY = 0;
float tempZ = 0;
float tempSize = 0;
const float rangeCreepMod = 0.25;
const uint8 maxIterationsAllowed = 4;
uint8 counter = 0;
float rangeReduction= 0;
tempSize = target->GetSize();
rangeReduction = (tempSize * rangeCreepMod);
while(tempSize > 0 && counter != maxIterationsAllowed) {
tempX = GetX() + (tempSize * static_cast<float>(sin(double(look_heading))));
tempY = GetY() + (tempSize * static_cast<float>(cos(double(look_heading))));
tempZ = target->GetZ();
if(!CheckLosFN(tempX, tempY, tempZ, tempSize)) {
tempSize -= rangeReduction;
}
else {
Result = true;
break;
}
counter++;
}
if(!Result) {
// Try to find an attack arc to position at from the opposite direction.
look_heading += (3.141592 / 2);
tempSize = target->GetSize();
counter = 0;
while(tempSize > 0 && counter != maxIterationsAllowed) {
tempX = GetX() + (tempSize * static_cast<float>(sin(double(look_heading))));
tempY = GetY() + (tempSize * static_cast<float>(cos(double(look_heading))));
tempZ = target->GetZ();
if(!CheckLosFN(tempX, tempY, tempZ, tempSize)) {
tempSize -= rangeReduction;
}
else {
Result = true;
break;
}
counter++;
}
}
if(Result) {
x_dest = tempX;
y_dest = tempY;
z_dest = tempZ;
}
}
return Result;
}
bool Mob::HateSummon() {
// check if mob has ability to summon
// 97% is the offical % that summoning starts on live, not 94
// if the mob can summon and is charmed, it can only summon mobs it has LoS to
Mob* mob_owner = nullptr;
if(GetOwnerID())
mob_owner = entity_list.GetMob(GetOwnerID());
if (GetHPRatio() >= 98 || SpecAttacks[SPECATK_SUMMON] == false || !GetTarget() ||
(mob_owner && mob_owner->IsClient() && !CheckLosFN(GetTarget())))
return false;
// now validate the timer
if (!SpecAttackTimers[SPECATK_SUMMON])
{
SpecAttackTimers[SPECATK_SUMMON] = new Timer(6000);
SpecAttackTimers[SPECATK_SUMMON]->Start();
}
// now check the timer
if (!SpecAttackTimers[SPECATK_SUMMON]->Check())
return false;
// get summon target
SetTarget(GetHateTop());
if(target)
{
if (target->IsClient())
target->CastToClient()->Message(15,"You have been summoned!");
entity_list.MessageClose(this, true, 500, 10, "%s says,'You will not evade me, %s!' ", GetCleanName(), target->GetCleanName() );
// RangerDown - GMMove doesn't seem to be working well with players, so use MovePC for them, GMMove for NPC's
if (target->IsClient()) {
target->CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), x_pos, y_pos, z_pos, target->GetHeading(), 0, SummonPC);
}
else {
#ifdef BOTS
if(target && target->IsBot()) {
// set pre summoning info to return to (to get out of melee range for caster)
target->CastToBot()->SetHasBeenSummoned(true);
target->CastToBot()->SetPreSummonX(target->GetX());
target->CastToBot()->SetPreSummonY(target->GetY());
target->CastToBot()->SetPreSummonZ(target->GetZ());
}
#endif //BOTS
target->GMMove(x_pos, y_pos, z_pos, target->GetHeading());
}
return true;
}
return false;
}
void Mob::FaceTarget(Mob* MobToFace) {
Mob* facemob = MobToFace;
if(!facemob) {
if(!GetTarget()) {
return;
}
else {
facemob = GetTarget();
}
}
float oldheading = GetHeading();
float newheading = CalculateHeadingToTarget(facemob->GetX(), facemob->GetY());
if(oldheading != newheading) {
SetHeading(newheading);
if(moving)
SendPosUpdate();
else
{
SendPosition();
}
}
if(IsNPC() && !IsEngaged()) {
CastToNPC()->GetRefaceTimer()->Start(15000);
CastToNPC()->GetRefaceTimer()->Enable();
}
}
bool Mob::RemoveFromHateList(Mob* mob)
{
SetRunAnimSpeed(0);
bool bFound = false;
if(IsEngaged())
{
bFound = hate_list.RemoveEnt(mob);
if(hate_list.IsEmpty())
{
AI_Event_NoLongerEngaged();
zone->DelAggroMob();
}
}
if(GetTarget() == mob)
{
SetTarget(hate_list.GetTop(this));
}
return bFound;
}
void Mob::WipeHateList()
{
if(IsEngaged())
{
AI_Event_NoLongerEngaged();
}
hate_list.Wipe();
}
uint32 Mob::RandomTimer(int min,int max) {
int r = 14000;
if(min != 0 && max != 0 && min < max)
{
r = MakeRandomInt(min, max);
}
return r;
}
uint32 NPC::GetEquipment(uint8 material_slot) const
{
if(material_slot > 8)
return 0;
int invslot = Inventory::CalcSlotFromMaterial(material_slot);
if (invslot == -1)
return 0;
return equipment[invslot];
}
void Mob::SendWearChange(uint8 material_slot)
{
EQApplicationPacket* outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct));
WearChange_Struct* wc = (WearChange_Struct*)outapp->pBuffer;
wc->spawn_id = GetID();
wc->material = GetEquipmentMaterial(material_slot);
wc->elite_material = IsEliteMaterialItem(material_slot);
wc->color.color = GetEquipmentColor(material_slot);
wc->wear_slot_id = material_slot;
entity_list.QueueClients(this, outapp);
safe_delete(outapp);
}
void Mob::SendTextureWC(uint8 slot, uint16 texture, uint32 hero_forge_model, uint32 elite_material, uint32 unknown06, uint32 unknown18)
{
EQApplicationPacket* outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct));
WearChange_Struct* wc = (WearChange_Struct*)outapp->pBuffer;
wc->spawn_id = this->GetID();
wc->material = texture;
if (this->IsClient())
wc->color.color = GetEquipmentColor(slot);
else
wc->color.color = this->GetArmorTint(slot);
wc->wear_slot_id = slot;
wc->unknown06 = unknown06;
wc->elite_material = elite_material;
wc->hero_forge_model = hero_forge_model;
wc->unknown18 = unknown18;
entity_list.QueueClients(this, outapp);
safe_delete(outapp);
}
void Mob::SetSlotTint(uint8 material_slot, uint8 red_tint, uint8 green_tint, uint8 blue_tint)
{
uint32 color;
color = (red_tint & 0xFF) << 16;
color |= (green_tint & 0xFF) << 8;
color |= (blue_tint & 0xFF);
color |= (color) ? (0xFF << 24) : 0;
armor_tint[material_slot] = color;
EQApplicationPacket* outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct));
WearChange_Struct* wc = (WearChange_Struct*)outapp->pBuffer;
wc->spawn_id = this->GetID();
wc->material = GetEquipmentMaterial(material_slot);
wc->color.color = color;
wc->wear_slot_id = material_slot;
entity_list.QueueClients(this, outapp);
safe_delete(outapp);
}
void Mob::WearChange(uint8 material_slot, uint16 texture, uint32 color)
{
armor_tint[material_slot] = color;
EQApplicationPacket* outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct));
WearChange_Struct* wc = (WearChange_Struct*)outapp->pBuffer;
wc->spawn_id = this->GetID();
wc->material = texture;
wc->color.color = color;
wc->wear_slot_id = material_slot;
entity_list.QueueClients(this, outapp);
safe_delete(outapp);
}
int32 Mob::GetEquipmentMaterial(uint8 material_slot) const
{
const Item_Struct *item;
item = database.GetItem(GetEquipment(material_slot));
if(item != 0)
{
if // for primary and secondary we need the model, not the material
(
material_slot == MATERIAL_PRIMARY ||
material_slot == MATERIAL_SECONDARY
)
{
if(strlen(item->IDFile) > 2)
return atoi(&item->IDFile[2]);
else //may as well try this, since were going to 0 anyways
return item->Material;
}
else
{
return item->Material;
}
}
return 0;
}
uint32 Mob::GetEquipmentColor(uint8 material_slot) const
{
const Item_Struct *item;
item = database.GetItem(GetEquipment(material_slot));
if(item != 0)
{
return item->Color;
}
return 0;
}
uint32 Mob::IsEliteMaterialItem(uint8 material_slot) const
{
const Item_Struct *item;
item = database.GetItem(GetEquipment(material_slot));
if(item != 0)
{
return item->EliteMaterial;
}
return 0;
}
// works just like a printf
void Mob::Say(const char *format, ...)
{
char buf[1000];
va_list ap;
va_start(ap, format);
vsnprintf(buf, 1000, format, ap);
va_end(ap);
Mob* talker = this;
if(spellbonuses.VoiceGraft != 0) {
if(spellbonuses.VoiceGraft == GetPetID())
talker = entity_list.GetMob(spellbonuses.VoiceGraft);
else
spellbonuses.VoiceGraft = 0;
}
if(!talker)
talker = this;
entity_list.MessageClose_StringID(talker, false, 200, 10,
GENERIC_SAY, GetCleanName(), buf);
}
//
// solar: this is like the above, but the first parameter is a string id
//
void Mob::Say_StringID(uint32 string_id, const char *message3, const char *message4, const char *message5, const char *message6, const char *message7, const char *message8, const char *message9)
{
char string_id_str[10];
snprintf(string_id_str, 10, "%d", string_id);
entity_list.MessageClose_StringID(this, false, 200, 10,
GENERIC_STRINGID_SAY, GetCleanName(), string_id_str, message3, message4, message5,
message6, message7, message8, message9
);
}
void Mob::Say_StringID(uint32 type, uint32 string_id, const char *message3, const char *message4, const char *message5, const char *message6, const char *message7, const char *message8, const char *message9)
{
char string_id_str[10];
snprintf(string_id_str, 10, "%d", string_id);
entity_list.MessageClose_StringID(this, false, 200, type,
GENERIC_STRINGID_SAY, GetCleanName(), string_id_str, message3, message4, message5,
message6, message7, message8, message9
);
}
void Mob::Shout(const char *format, ...)
{
char buf[1000];
va_list ap;
va_start(ap, format);
vsnprintf(buf, 1000, format, ap);
va_end(ap);
entity_list.Message_StringID(this, false, MT_Shout,
GENERIC_SHOUT, GetCleanName(), buf);
}
void Mob::Emote(const char *format, ...)
{
char buf[1000];
va_list ap;
va_start(ap, format);
vsnprintf(buf, 1000, format, ap);
va_end(ap);
entity_list.MessageClose_StringID(this, false, 200, 10,
GENERIC_EMOTE, GetCleanName(), buf);
}
void Mob::QuestJournalledSay(Client *QuestInitiator, const char *str)
{
entity_list.QuestJournalledSayClose(this, QuestInitiator, 200, GetCleanName(), str);
}
const char *Mob::GetCleanName()
{
if(!strlen(clean_name))
{
CleanMobName(GetName(), clean_name);
}
return clean_name;
}
// hp event
void Mob::SetNextHPEvent( int hpevent )
{
nexthpevent = hpevent;
}
void Mob::SetNextIncHPEvent( int inchpevent )
{
nextinchpevent = inchpevent;
}
//warp for quest function,from sandy
void Mob::Warp( float x, float y, float z )
{
x_pos = x;
y_pos = y;
z_pos = z;
Mob* target = GetTarget();
if ( target ) {
FaceTarget( target );
}
SendPosition();
}
bool Mob::DivineAura() const
{
uint32 l;
uint32 buff_count = GetMaxTotalSlots();
for (l = 0; l < buff_count; l++)
{
if (buffs[l].spellid != SPELL_UNKNOWN)
{
for (int k = 0; k < EFFECT_COUNT; k++)
{
if (spells[buffs[l].spellid].effectid[k] == SE_DivineAura)
{
return true;
}
}
}
}
return false;
}
int16 Mob::GetResist(uint8 type) const
{
if (IsNPC())
{
if (type == 1)
return MR + spellbonuses.MR + itembonuses.MR;
else if (type == 2)
return FR + spellbonuses.FR + itembonuses.FR;
else if (type == 3)
return CR + spellbonuses.CR + itembonuses.CR;
else if (type == 4)
return PR + spellbonuses.PR + itembonuses.PR;
else if (type == 5)
return DR + spellbonuses.DR + itembonuses.DR;
}
else if (IsClient())
{
if (type == 1)
return CastToClient()->GetMR();
else if (type == 2)
return CastToClient()->GetFR();
else if (type == 3)
return CastToClient()->GetCR();
else if (type == 4)
return CastToClient()->GetPR();
else if (type == 5)
return CastToClient()->GetDR();
}
return 25;
}
uint32 Mob::GetLevelHP(uint8 tlevel)
{
//cout<<"Tlevel: "<<(int)tlevel<<endl;
int multiplier = 0;
if (tlevel < 10)
{
multiplier = tlevel*20;
}
else if (tlevel < 20)
{
multiplier = tlevel*25;
}
else if (tlevel < 40)
{
multiplier = tlevel*tlevel*12*((tlevel*2+60)/100)/10;
}
else if (tlevel < 45)
{
multiplier = tlevel*tlevel*15*((tlevel*2+60)/100)/10;
}
else if (tlevel < 50)
{
multiplier = tlevel*tlevel*175*((tlevel*2+60)/100)/100;
}
else
{
multiplier = tlevel*tlevel*2*((tlevel*2+60)/100)*(1+((tlevel-50)*20/10));
}
return multiplier;
}
int32 Mob::GetActSpellCasttime(uint16 spell_id, int32 casttime) {
if (level >= 60 && casttime > 1000)
{
casttime = casttime / 2;
if (casttime < 1000)
casttime = 1000;
} else if (level >= 50 && casttime > 1000) {
int32 cast_deduction = (casttime*(level - 49))/5;
if (cast_deduction > casttime/2)
casttime /= 2;
else
casttime -= cast_deduction;
}
return(casttime);
}
void Mob::ExecWeaponProc(uint16 spell_id, Mob *on) {
// Changed proc targets to look up based on the spells goodEffect flag.
// This should work for the majority of weapons.
if(spell_id == SPELL_UNKNOWN || on->SpecAttacks[NO_HARM_FROM_CLIENT]){ //This is so 65535 doesn't get passed to the client message and to logs because it is not relavant information for debugging.
return;
}
if (IsNoCast())
return;
if(!IsValidSpell(spell_id)){ // Check for a valid spell otherwise it will crash through the function
if(this->IsClient()){
this->Message(0, "Invalid spell proc %u", spell_id);
mlog(CLIENT__SPELLS, "Player %s, Weapon Procced invalid spell %u", this->GetName(), spell_id);
}
return;
}
/*
int twinproc_chance = itembonuses.TwinProc + spellbonuses.TwinProc;
if(IsClient())
twinproc_chance += aabonuses.TwinProc;
*/
bool twinproc = false;
int32 twinproc_chance = 0;
if(IsClient())
twinproc_chance = CastToClient()->GetFocusEffect(focusTwincast, spell_id);
if(twinproc_chance && (MakeRandomInt(0,99) < twinproc_chance))
twinproc = true;
if (IsBeneficialSpell(spell_id)) {
SpellFinished(spell_id, this, 10, 0, -1, spells[spell_id].ResistDiff, true);
if(twinproc)
SpellOnTarget(spell_id, this, false, false, 0, true);
}
else if(!(on->IsClient() && on->CastToClient()->dead)) { //dont proc on dead clients
SpellFinished(spell_id, on, 10, 0, -1, spells[spell_id].ResistDiff, true);
if(twinproc)
SpellOnTarget(spell_id, on, false, false, 0, true);
}
return;
}
uint32 Mob::GetZoneID() const {
return(zone->GetZoneID());
}
int Mob::GetHaste() {
int h = spellbonuses.haste + spellbonuses.hastetype2 + itembonuses.haste;
int cap = 0;
int level = GetLevel();
if(level < 30) { // Rogean: Are these caps correct? Will use for now.
cap = 50;
} else if(level < 50) {
cap = 74;
} else if(level < 55) {
cap = 84;
} else if(level < 60) {
cap = 94;
} else {
cap = RuleI(Character, HasteCap);
}
if(h > cap) h = cap;
h += spellbonuses.hastetype3;
h += ExtraHaste; //GM granted haste.
if (spellbonuses.inhibitmelee){
if (h >= 0)
h -= spellbonuses.inhibitmelee;
else
h -=((100+h)*spellbonuses.inhibitmelee/100);
}
return(h);
}
void Mob::SetTarget(Mob* mob) {
if (target == mob) return;
target = mob;
entity_list.UpdateHoTT(this);
if(IsNPC())
parse->EventNPC(EVENT_TARGET_CHANGE, CastToNPC(), mob, "", 0); //parse->Event(EVENT_TARGET_CHANGE, this->GetNPCTypeID(), 0, this->CastToNPC(), mob);
else if (IsClient())
parse->EventPlayer(EVENT_TARGET_CHANGE, CastToClient(), "", 0); //parse->Event(EVENT_TARGET_CHANGE, 0, "", (NPC*)nullptr, this->CastToClient());
if(IsPet() && GetOwner() && GetOwner()->IsClient())
GetOwner()->CastToClient()->UpdateXTargetType(MyPetTarget, mob);
}
float Mob::FindGroundZ(float new_x, float new_y, float z_offset)
{
float ret = -999999;
if (zone->zonemap != 0)
{
NodeRef pnode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), new_x, new_y );
if (pnode != NODE_NONE)
{
VERTEX me;
me.x = new_x;
me.y = new_y;
me.z = z_pos+z_offset;
VERTEX hit;
FACE *onhit;
float best_z = zone->zonemap->FindBestZ(pnode, me, &hit, &onhit);
if (best_z != -999999)
{
ret = best_z;
}
}
}
return ret;
}
// Copy of above function that isn't protected to be exported to Perl::Mob
float Mob::GetGroundZ(float new_x, float new_y, float z_offset)
{
float ret = -999999;
if (zone->zonemap != 0)
{
NodeRef pnode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), new_x, new_y );
if (pnode != NODE_NONE)
{
VERTEX me;
me.x = new_x;
me.y = new_y;
me.z = z_pos+z_offset;
VERTEX hit;
FACE *onhit;
float best_z = zone->zonemap->FindBestZ(pnode, me, &hit, &onhit);
if (best_z != -999999)
{
ret = best_z;
}
}
}
return ret;
}
//helper function for npc AI; needs to be mob:: cause we need to be able to count buffs on other clients and npcs
int Mob::CountDispellableBuffs()
{
int val = 0;
int buff_count = GetMaxTotalSlots();
for(int x = 0; x < buff_count; x++)
{
if(!IsValidSpell(buffs[x].spellid))
continue;
if(buffs[x].counters)
continue;
if(spells[buffs[x].spellid].goodEffect == 0)
continue;
if(buffs[x].spellid != SPELL_UNKNOWN && spells[buffs[x].spellid].buffdurationformula != DF_Permanent)
val++;
}
return val;
}
// Returns the % that a mob is snared (as a positive value). -1 means not snared
int Mob::GetSnaredAmount()
{
int worst_snare = -1;
int buff_count = GetMaxTotalSlots();
for (int i = 0; i < buff_count; i++)
{
if (!IsValidSpell(buffs[i].spellid))
continue;
for(int j = 0; j < EFFECT_COUNT; j++)
{
if (spells[buffs[i].spellid].effectid[j] == SE_MovementSpeed)
{
int val = CalcSpellEffectValue_formula(spells[buffs[i].spellid].formula[j], spells[buffs[i].spellid].base[j], spells[buffs[i].spellid].max[j], buffs[i].casterlevel, buffs[i].spellid);
//int effect = CalcSpellEffectValue(buffs[i].spellid, spells[buffs[i].spellid].effectid[j], buffs[i].casterlevel);
if (val < 0 && abs(val) > worst_snare)
worst_snare = abs(val);
}
}
}
return worst_snare;
}
void Mob::TriggerDefensiveProcs(const ItemInst* weapon, Mob *on, uint16 hand, int damage)
{
if (!on)
return;
on->TryDefensiveProc(weapon, this, hand, damage);
}
void Mob::SetDeltas(float dx, float dy, float dz, float dh) {
delta_x = dx;
delta_y = dy;
delta_z = dz;
delta_heading = static_cast<int>(dh);
}
bool Mob::HasBuffIcon(Mob *caster, Mob *target, uint16 spell_id)
{
if((caster->CalcBuffDuration(caster, target, spell_id)-1) > 0)
return true;
else
return false;
}
void Mob::SetEntityVariable(const char *id, const char *m_var)
{
std::string n_m_var = m_var;
m_EntityVariables[id] = n_m_var;
}
const char* Mob::GetEntityVariable(const char *id)
{
std::map<std::string, std::string>::iterator iter = m_EntityVariables.find(id);
if(iter != m_EntityVariables.end())
{
return iter->second.c_str();
}
return nullptr;
}
bool Mob::EntityVariableExists(const char *id)
{
std::map<std::string, std::string>::iterator iter = m_EntityVariables.find(id);
if(iter != m_EntityVariables.end())
{
return true;
}
return false;
}
void Mob::SetFlyMode(uint8 flymode)
{
if(IsClient() && flymode >= 0 && flymode < 3)
{
this->SendAppearancePacket(AT_Levitate, flymode);
}
else if(IsNPC() && flymode >= 0 && flymode <= 3)
{
this->SendAppearancePacket(AT_Levitate, flymode);
this->CastToNPC()->SetFlyMode(flymode);
}
}
bool Mob::IsNimbusEffectActive(uint32 nimbus_effect)
{
if(nimbus_effect1 == nimbus_effect || nimbus_effect2 == nimbus_effect || nimbus_effect3 == nimbus_effect)
{
return true;
}
return false;
}
void Mob::SetNimbusEffect(uint32 nimbus_effect)
{
if(nimbus_effect1 == 0)
{
nimbus_effect1 = nimbus_effect;
}
else if(nimbus_effect2 == 0)
{
nimbus_effect2 = nimbus_effect;
}
else
{
nimbus_effect3 = nimbus_effect;
}
}
void Mob::TryTriggerOnCast(uint32 spell_id, bool aa_trigger)
{
if(!IsValidSpell(spell_id))
return;
if (aabonuses.SpellTriggers[0] || spellbonuses.SpellTriggers[0] || itembonuses.SpellTriggers[0]){
for(int i = 0; i < MAX_SPELL_TRIGGER; i++){
if(aabonuses.SpellTriggers[i] && IsClient())
TriggerOnCast(aabonuses.SpellTriggers[i], spell_id,1);
if(spellbonuses.SpellTriggers[i])
TriggerOnCast(spellbonuses.SpellTriggers[i], spell_id,0);
if(itembonuses.SpellTriggers[i])
TriggerOnCast(spellbonuses.SpellTriggers[i], spell_id,0);
}
}
}
void Mob::TriggerOnCast(uint32 focus_spell, uint32 spell_id, bool aa_trigger)
{
if(!IsValidSpell(focus_spell) || !IsValidSpell(spell_id))
return;
uint32 trigger_spell_id = 0;
if (aa_trigger && IsClient()){
//focus_spell = aaid
trigger_spell_id = CastToClient()->CalcAAFocus(focusTriggerOnCast, focus_spell, spell_id);
if(IsValidSpell(trigger_spell_id) && GetTarget())
SpellFinished(trigger_spell_id, GetTarget());
}
else{
trigger_spell_id = CalcFocusEffect(focusTriggerOnCast, focus_spell, spell_id);
if(IsValidSpell(trigger_spell_id) && GetTarget()){
SpellFinished(trigger_spell_id, GetTarget());
CheckHitsRemaining(0, false,false, 0, focus_spell);
}
}
}
void Mob::TrySpellTrigger(Mob *target, uint32 spell_id)
{
if(target == nullptr || !IsValidSpell(spell_id))
{
return;
}
int spell_trig = 0;
// Count all the percentage chances to trigger for all effects
for(int i = 0; i < EFFECT_COUNT; i++)
{
if (spells[spell_id].effectid[i] == SE_SpellTrigger)
spell_trig += spells[spell_id].base[i];
}
// If all the % add to 100, then only one of the effects can fire but one has to fire.
if (spell_trig == 100)
{
int trig_chance = 100;
for(int i = 0; i < EFFECT_COUNT; i++)
{
if (spells[spell_id].effectid[i] == SE_SpellTrigger)
{
if(MakeRandomInt(0, trig_chance) <= spells[spell_id].base[i])
{
// If we trigger an effect then its over.
SpellFinished(spells[spell_id].base2[i], target);
break;
}
else
{
// Increase the chance to fire for the next effect, if all effects fail, the final effect will fire.
trig_chance -= spells[spell_id].base[i];
}
}
}
}
// if the chances don't add to 100, then each effect gets a chance to fire, chance for no trigger as well.
else
{
for(int i = 0; i < EFFECT_COUNT; i++)
{
if (spells[spell_id].effectid[i] == SE_SpellTrigger)
{
if(MakeRandomInt(0, 100) <= spells[spell_id].base[i])
{
SpellFinished(spells[spell_id].base2[i], target);
}
}
}
}
}
void Mob::TryApplyEffect(Mob *target, uint32 spell_id)
{
if(target == nullptr || !IsValidSpell(spell_id))
{
return;
}
for(int i = 0; i < EFFECT_COUNT; i++)
{
if (spells[spell_id].effectid[i] == SE_ApplyEffect)
{
if(MakeRandomInt(0, 100) <= spells[spell_id].base[i])
{
if(target)
SpellFinished(spells[spell_id].base2[i], target);
}
}
}
}
//Twincast Focus effects should stack across different types (Spell, AA - when implemented ect)
void Mob::TryTwincast(Mob *caster, Mob *target, uint32 spell_id)
{
if(!IsValidSpell(spell_id))
return;
if(IsClient())
{
int32 focus = CastToClient()->GetFocusEffect(focusTwincast, spell_id);
if (focus > 0)
{
if(MakeRandomInt(0, 100) <= focus)
{
Message(MT_Spells,"You twincast %s!",spells[spell_id].name);
SpellFinished(spell_id, target);
}
}
}
//Retains function for non clients
else if (spellbonuses.FocusEffects[focusTwincast] || itembonuses.FocusEffects[focusTwincast])
{
int buff_count = GetMaxTotalSlots();
for(int i = 0; i < buff_count; i++)
{
if(IsEffectInSpell(buffs[i].spellid, SE_Twincast))
{
int32 focus = CalcFocusEffect(focusTwincast, buffs[i].spellid, spell_id);
if(focus > 0)
{
if(MakeRandomInt(0, 100) <= focus)
{
SpellFinished(spell_id, target);
}
}
}
}
}
}
int32 Mob::GetVulnerability(int32 damage, Mob *caster, uint32 spell_id, uint32 ticsremaining)
{
if (!caster)
return damage;
//Apply innate vulnerabilities
if (Vulnerability_Mod[GetSpellResistType(spell_id)] != 0)
damage += damage * Vulnerability_Mod[GetSpellResistType(spell_id)] / 100;
else if (Vulnerability_Mod[HIGHEST_RESIST+1] != 0)
damage += damage * Vulnerability_Mod[HIGHEST_RESIST+1] / 100;
//Apply spell derived vulnerabilities
if (spellbonuses.FocusEffects[focusSpellVulnerability]){
int32 tmp_focus = 0;
int tmp_buffslot = -1;
int buff_count = GetMaxTotalSlots();
for(int i = 0; i < buff_count; i++) {
if((IsValidSpell(buffs[i].spellid) && IsEffectInSpell(buffs[i].spellid, SE_SpellVulnerability))){
int32 focus = caster->CalcFocusEffect(focusSpellVulnerability, buffs[i].spellid, spell_id);
if (!focus)
continue;
if (tmp_focus && focus > tmp_focus){
tmp_focus = focus;
tmp_buffslot = i;
}
else if (!tmp_focus){
tmp_focus = focus;
tmp_buffslot = i;
}
}
}
if (tmp_focus < -99)
tmp_focus = -99;
damage += damage * tmp_focus / 100;
if (tmp_buffslot >= 0)
CheckHitsRemaining(tmp_buffslot);
}
return damage;
}
int16 Mob::GetSkillDmgTaken(const SkillType skill_used)
{
int skilldmg_mod = 0;
// All skill dmg mod + Skill specific
skilldmg_mod += itembonuses.SkillDmgTaken[HIGHEST_SKILL+1] + spellbonuses.SkillDmgTaken[HIGHEST_SKILL+1] +
itembonuses.SkillDmgTaken[skill_used] + spellbonuses.SkillDmgTaken[skill_used];
//Innate SetSkillDamgeTaken(skill,value)
if ((SkillDmgTaken_Mod[skill_used]) || (SkillDmgTaken_Mod[HIGHEST_SKILL+1]))
skilldmg_mod += SkillDmgTaken_Mod[skill_used] + SkillDmgTaken_Mod[HIGHEST_SKILL+1];
if(skilldmg_mod < -100)
skilldmg_mod = -100;
if (spellbonuses.SkillDmgTaken[HIGHEST_SKILL+1] || spellbonuses.SkillDmgTaken[skill_used])
CheckHitsRemaining(0, false,false, SE_SkillDamageTaken,0,true,skill_used);
return skilldmg_mod;
}
int16 Mob::GetHealRate(uint16 spell_id)
{
Mob* target = GetTarget();
int16 heal_rate = 0;
if (target){
heal_rate = target->itembonuses.HealRate + target->spellbonuses.HealRate;
if (target->IsClient())
heal_rate += target->CastToClient()->GetFocusEffect(focusHealRate, spell_id);
if(heal_rate < -99)
heal_rate = -99;
}
return heal_rate;
}
int16 Mob::GetCriticalHealRate(uint16 spell_id)
{
Mob* target = GetTarget();
int16 critical_heal_rate = 0;
if (target && target->IsClient())
critical_heal_rate = target->CastToClient()->GetFocusEffect(focusCriticalHealRate, spell_id);
return critical_heal_rate;
}
bool Mob::TryFadeEffect(int slot)
{
if(IsValidSpell(buffs[slot].spellid))
{
for(int i = 0; i < EFFECT_COUNT; i++)
{
if (spells[buffs[slot].spellid].effectid[i] == SE_CastOnWearoff || spells[buffs[slot].spellid].effectid[i] == SE_EffectOnFade)
{
uint16 spell_id = spells[buffs[slot].spellid].base[i];
BuffFadeBySlot(slot);
if(spell_id)
{
if(spell_id == SPELL_UNKNOWN)
return false;
if(IsValidSpell(spell_id))
{
if (IsBeneficialSpell(spell_id)) {
SpellFinished(spell_id, this, 10, 0, -1, spells[spell_id].ResistDiff);
}
else if(!(IsClient() && CastToClient()->dead)) {
SpellFinished(spell_id, this, 10, 0, -1, spells[spell_id].ResistDiff);
}
return true;
}
}
}
}
}
return false;
}
void Mob::TrySympatheticProc(Mob *target, uint32 spell_id)
{
if(target == nullptr || !IsValidSpell(spell_id))
return;
int focus_spell = CastToClient()->GetSympatheticFocusEffect(focusSympatheticProc,spell_id);
if(IsValidSpell(focus_spell)){
int focus_trigger = spells[focus_spell].base2[0];
// For beneficial spells, if the triggered spell is also beneficial then proc it on the target
// if the triggered spell is detrimental, then it will trigger on the caster(ie cursed items)
if(IsBeneficialSpell(spell_id))
{
if(IsBeneficialSpell(focus_trigger))
SpellFinished(focus_trigger, target);
else
SpellFinished(focus_trigger, this);
}
// For detrimental spells, if the triggered spell is beneficial, then it will land on the caster
// if the triggered spell is also detrimental, then it will land on the target
else
{
if(IsBeneficialSpell(focus_trigger))
SpellFinished(focus_trigger, this);
else
SpellFinished(focus_trigger, target);
}
CheckHitsRemaining(0, false,false, 0, focus_spell);
}
}
uint32 Mob::GetItemStat(uint32 itemid, const char *identifier)
{
const ItemInst* inst = database.CreateItem(itemid);
if (!inst)
return 0;
const Item_Struct* item = inst->GetItem();
if (!item)
return 0;
if (!identifier)
return 0;
uint32 stat = 0;
std::string id = identifier;
for(int i = 0; i < id.length(); ++i)
{
id[i] = tolower(id[i]);
}
if (id == "itemclass")
stat = uint32(item->ItemClass);
if (id == "id")
stat = uint32(item->ID);
if (id == "weight")
stat = uint32(item->Weight);
if (id == "norent")
stat = uint32(item->NoRent);
if (id == "nodrop")
stat = uint32(item->NoDrop);
if (id == "size")
stat = uint32(item->Size);
if (id == "slots")
stat = uint32(item->Slots);
if (id == "price")
stat = uint32(item->Price);
if (id == "icon")
stat = uint32(item->Icon);
if (id == "loregroup")
stat = uint32(item->LoreGroup);
if (id == "loreflag")
stat = uint32(item->LoreFlag);
if (id == "pendingloreflag")
stat = uint32(item->PendingLoreFlag);
if (id == "artifactflag")
stat = uint32(item->ArtifactFlag);
if (id == "summonedflag")
stat = uint32(item->SummonedFlag);
if (id == "fvnodrop")
stat = uint32(item->FVNoDrop);
if (id == "favor")
stat = uint32(item->Favor);
if (id == "guildfavor")
stat = uint32(item->GuildFavor);
if (id == "pointtype")
stat = uint32(item->PointType);
if (id == "bagtype")
stat = uint32(item->BagType);
if (id == "bagslots")
stat = uint32(item->BagSlots);
if (id == "bagsize")
stat = uint32(item->BagSize);
if (id == "bagwr")
stat = uint32(item->BagWR);
if (id == "benefitflag")
stat = uint32(item->BenefitFlag);
if (id == "tradeskills")
stat = uint32(item->Tradeskills);
if (id == "cr")
stat = uint32(item->CR);
if (id == "dr")
stat = uint32(item->DR);
if (id == "pr")
stat = uint32(item->PR);
if (id == "mr")
stat = uint32(item->MR);
if (id == "fr")
stat = uint32(item->FR);
if (id == "astr")
stat = uint32(item->AStr);
if (id == "asta")
stat = uint32(item->ASta);
if (id == "aagi")
stat = uint32(item->AAgi);
if (id == "adex")
stat = uint32(item->ADex);
if (id == "acha")
stat = uint32(item->ACha);
if (id == "aint")
stat = uint32(item->AInt);
if (id == "awis")
stat = uint32(item->AWis);
if (id == "hp")
stat = uint32(item->HP);
if (id == "mana")
stat = uint32(item->Mana);
if (id == "ac")
stat = uint32(item->AC);
if (id == "deity")
stat = uint32(item->Deity);
if (id == "skillmodvalue")
stat = uint32(item->SkillModValue);
if (id == "skillmodtype")
stat = uint32(item->SkillModType);
if (id == "banedmgrace")
stat = uint32(item->BaneDmgRace);
if (id == "banedmgamt")
stat = uint32(item->BaneDmgAmt);
if (id == "banedmgbody")
stat = uint32(item->BaneDmgBody);
if (id == "magic")
stat = uint32(item->Magic);
if (id == "casttime_")
stat = uint32(item->CastTime_);
if (id == "reqlevel")
stat = uint32(item->ReqLevel);
if (id == "bardtype")
stat = uint32(item->BardType);
if (id == "bardvalue")
stat = uint32(item->BardValue);
if (id == "light")
stat = uint32(item->Light);
if (id == "delay")
stat = uint32(item->Delay);
if (id == "reclevel")
stat = uint32(item->RecLevel);
if (id == "recskill")
stat = uint32(item->RecSkill);
if (id == "elemdmgtype")
stat = uint32(item->ElemDmgType);
if (id == "elemdmgamt")
stat = uint32(item->ElemDmgAmt);
if (id == "range")
stat = uint32(item->Range);
if (id == "damage")
stat = uint32(item->Damage);
if (id == "color")
stat = uint32(item->Color);
if (id == "classes")
stat = uint32(item->Classes);
if (id == "races")
stat = uint32(item->Races);
if (id == "maxcharges")
stat = uint32(item->MaxCharges);
if (id == "itemtype")
stat = uint32(item->ItemType);
if (id == "material")
stat = uint32(item->Material);
if (id == "casttime")
stat = uint32(item->CastTime);
if (id == "elitematerial")
stat = uint32(item->EliteMaterial);
if (id == "procrate")
stat = uint32(item->ProcRate);
if (id == "combateffects")
stat = uint32(item->CombatEffects);
if (id == "shielding")
stat = uint32(item->Shielding);
if (id == "stunresist")
stat = uint32(item->StunResist);
if (id == "strikethrough")
stat = uint32(item->StrikeThrough);
if (id == "extradmgskill")
stat = uint32(item->ExtraDmgSkill);
if (id == "extradmgamt")
stat = uint32(item->ExtraDmgAmt);
if (id == "spellshield")
stat = uint32(item->SpellShield);
if (id == "avoidance")
stat = uint32(item->Avoidance);
if (id == "accuracy")
stat = uint32(item->Accuracy);
if (id == "charmfileid")
stat = uint32(item->CharmFileID);
if (id == "factionmod1")
stat = uint32(item->FactionMod1);
if (id == "factionmod2")
stat = uint32(item->FactionMod2);
if (id == "factionmod3")
stat = uint32(item->FactionMod3);
if (id == "factionmod4")
stat = uint32(item->FactionMod4);
if (id == "factionamt1")
stat = uint32(item->FactionAmt1);
if (id == "factionamt2")
stat = uint32(item->FactionAmt2);
if (id == "factionamt3")
stat = uint32(item->FactionAmt3);
if (id == "factionamt4")
stat = uint32(item->FactionAmt4);
if (id == "augtype")
stat = uint32(item->AugType);
if (id == "ldontheme")
stat = uint32(item->LDoNTheme);
if (id == "ldonprice")
stat = uint32(item->LDoNPrice);
if (id == "ldonsold")
stat = uint32(item->LDoNSold);
if (id == "banedmgraceamt")
stat = uint32(item->BaneDmgRaceAmt);
if (id == "augrestrict")
stat = uint32(item->AugRestrict);
if (id == "endur")
stat = uint32(item->Endur);
if (id == "dotshielding")
stat = uint32(item->DotShielding);
if (id == "attack")
stat = uint32(item->Attack);
if (id == "regen")
stat = uint32(item->Regen);
if (id == "manaregen")
stat = uint32(item->ManaRegen);
if (id == "enduranceregen")
stat = uint32(item->EnduranceRegen);
if (id == "haste")
stat = uint32(item->Haste);
if (id == "damageshield")
stat = uint32(item->DamageShield);
if (id == "recastdelay")
stat = uint32(item->RecastDelay);
if (id == "recasttype")
stat = uint32(item->RecastType);
if (id == "augdistiller")
stat = uint32(item->AugDistiller);
if (id == "attuneable")
stat = uint32(item->Attuneable);
if (id == "nopet")
stat = uint32(item->NoPet);
if (id == "potionbelt")
stat = uint32(item->PotionBelt);
if (id == "stackable")
stat = uint32(item->Stackable);
if (id == "notransfer")
stat = uint32(item->NoTransfer);
if (id == "questitemflag")
stat = uint32(item->QuestItemFlag);
if (id == "stacksize")
stat = uint32(item->StackSize);
if (id == "potionbeltslots")
stat = uint32(item->PotionBeltSlots);
if (id == "book")
stat = uint32(item->Book);
if (id == "booktype")
stat = uint32(item->BookType);
if (id == "svcorruption")
stat = uint32(item->SVCorruption);
if (id == "purity")
stat = uint32(item->Purity);
if (id == "backstabdmg")
stat = uint32(item->BackstabDmg);
if (id == "dsmitigation")
stat = uint32(item->DSMitigation);
if (id == "heroicstr")
stat = uint32(item->HeroicStr);
if (id == "heroicint")
stat = uint32(item->HeroicInt);
if (id == "heroicwis")
stat = uint32(item->HeroicWis);
if (id == "heroicagi")
stat = uint32(item->HeroicAgi);
if (id == "heroicdex")
stat = uint32(item->HeroicDex);
if (id == "heroicsta")
stat = uint32(item->HeroicSta);
if (id == "heroiccha")
stat = uint32(item->HeroicCha);
if (id == "heroicmr")
stat = uint32(item->HeroicMR);
if (id == "heroicfr")
stat = uint32(item->HeroicFR);
if (id == "heroiccr")
stat = uint32(item->HeroicCR);
if (id == "heroicdr")
stat = uint32(item->HeroicDR);
if (id == "heroicpr")
stat = uint32(item->HeroicPR);
if (id == "heroicsvcorrup")
stat = uint32(item->HeroicSVCorrup);
if (id == "healamt")
stat = uint32(item->HealAmt);
if (id == "spelldmg")
stat = uint32(item->SpellDmg);
if (id == "ldonsellbackrate")
stat = uint32(item->LDoNSellBackRate);
if (id == "scriptfileid")
stat = uint32(item->ScriptFileID);
if (id == "expendablearrow")
stat = uint32(item->ExpendableArrow);
if (id == "clairvoyance")
stat = uint32(item->Clairvoyance);
// Begin Effects
if (id == "clickeffect")
stat = uint32(item->Click.Effect);
if (id == "clicktype")
stat = uint32(item->Click.Type);
if (id == "clicklevel")
stat = uint32(item->Click.Level);
if (id == "clicklevel2")
stat = uint32(item->Click.Level2);
if (id == "proceffect")
stat = uint32(item->Proc.Effect);
if (id == "proctype")
stat = uint32(item->Proc.Type);
if (id == "proclevel")
stat = uint32(item->Proc.Level);
if (id == "proclevel2")
stat = uint32(item->Proc.Level2);
if (id == "worneffect")
stat = uint32(item->Worn.Effect);
if (id == "worntype")
stat = uint32(item->Worn.Type);
if (id == "wornlevel")
stat = uint32(item->Worn.Level);
if (id == "wornlevel2")
stat = uint32(item->Worn.Level2);
if (id == "focuseffect")
stat = uint32(item->Focus.Effect);
if (id == "focustype")
stat = uint32(item->Focus.Type);
if (id == "focuslevel")
stat = uint32(item->Focus.Level);
if (id == "focuslevel2")
stat = uint32(item->Focus.Level2);
if (id == "scrolleffect")
stat = uint32(item->Scroll.Effect);
if (id == "scrolltype")
stat = uint32(item->Scroll.Type);
if (id == "scrolllevel")
stat = uint32(item->Scroll.Level);
if (id == "scrolllevel2")
stat = uint32(item->Scroll.Level2);
safe_delete(inst);
return stat;
}
void Mob::SetGlobal(const char *varname, const char *newvalue, int options, const char *duration, Mob *other) {
int qgZoneid = zone->GetZoneID();
int qgCharid = 0;
int qgNpcid = 0;
if (this->IsNPC())
{
qgNpcid = this->GetNPCTypeID();
}
else if (other && other->IsNPC())
{
qgNpcid = other->GetNPCTypeID();
}
if (this->IsClient())
{
qgCharid = this->CastToClient()->CharacterID();
}
else if (other && other->IsClient())
{
qgCharid = other->CastToClient()->CharacterID();
}
else
{
qgCharid = -qgNpcid; // make char id negative npc id as a fudge
}
if (options < 0 || options > 7)
{
//cerr << "Invalid options for global var " << varname << " using defaults" << endl;
options = 0; // default = 0 (only this npcid,player and zone)
}
else
{
if (options & 1)
qgNpcid=0;
if (options & 2)
qgCharid=0;
if (options & 4)
qgZoneid=0;
}
InsertQuestGlobal(qgCharid, qgNpcid, qgZoneid, varname, newvalue, QGVarDuration(duration));
}
void Mob::TarGlobal(const char *varname, const char *value, const char *duration, int qgNpcid, int qgCharid, int qgZoneid)
{
InsertQuestGlobal(qgCharid, qgNpcid, qgZoneid, varname, value, QGVarDuration(duration));
}
void Mob::DelGlobal(const char *varname) {
// delglobal(varname)
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
int qgZoneid=zone->GetZoneID();
int qgCharid=0;
int qgNpcid=0;
if (this->IsNPC())
{
qgNpcid = this->GetNPCTypeID();
}
if (this->IsClient())
{
qgCharid = this->CastToClient()->CharacterID();
}
else
{
qgCharid = -qgNpcid; // make char id negative npc id as a fudge
}
if (!database.RunQuery(query,
MakeAnyLenString(&query,
"DELETE FROM quest_globals WHERE name='%s'"
" && (npcid=0 || npcid=%i) && (charid=0 || charid=%i) && (zoneid=%i || zoneid=0)",
varname,qgNpcid,qgCharid,qgZoneid),errbuf))
{
//_log(QUESTS, "DelGlobal error deleting %s : %s", varname, errbuf);
}
safe_delete_array(query);
if(zone)
{
ServerPacket* pack = new ServerPacket(ServerOP_QGlobalDelete, sizeof(ServerQGlobalDelete_Struct));
ServerQGlobalDelete_Struct *qgu = (ServerQGlobalDelete_Struct*)pack->pBuffer;
qgu->npc_id = qgNpcid;
qgu->char_id = qgCharid;
qgu->zone_id = qgZoneid;
strcpy(qgu->name, varname);
entity_list.DeleteQGlobal(std::string((char*)qgu->name), qgu->npc_id, qgu->char_id, qgu->zone_id);
zone->DeleteQGlobal(std::string((char*)qgu->name), qgu->npc_id, qgu->char_id, qgu->zone_id);
worldserver.SendPacket(pack);
safe_delete(pack);
}
}
// Inserts global variable into quest_globals table
void Mob::InsertQuestGlobal(int charid, int npcid, int zoneid, const char *varname, const char *varvalue, int duration) {
char *query = 0;
char errbuf[MYSQL_ERRMSG_SIZE];
// Make duration string either "unix_timestamp(now()) + xxx" or "NULL"
stringstream duration_ss;
if (duration == INT_MAX)
{
duration_ss << "NULL";
}
else
{
duration_ss << "unix_timestamp(now()) + " << duration;
}
//NOTE: this should be escaping the contents of arglist
//npcwise a malicious script can arbitrarily alter the DB
uint32 last_id = 0;
if (!database.RunQuery(query, MakeAnyLenString(&query,
"REPLACE INTO quest_globals (charid, npcid, zoneid, name, value, expdate)"
"VALUES (%i, %i, %i, '%s', '%s', %s)",
charid, npcid, zoneid, varname, varvalue, duration_ss.str().c_str()
), errbuf))
{
//_log(QUESTS, "SelGlobal error inserting %s : %s", varname, errbuf);
}
safe_delete_array(query);
if(zone)
{
//first delete our global
ServerPacket* pack = new ServerPacket(ServerOP_QGlobalDelete, sizeof(ServerQGlobalDelete_Struct));
ServerQGlobalDelete_Struct *qgd = (ServerQGlobalDelete_Struct*)pack->pBuffer;
qgd->npc_id = npcid;
qgd->char_id = charid;
qgd->zone_id = zoneid;
qgd->from_zone_id = zone->GetZoneID();
qgd->from_instance_id = zone->GetInstanceID();
strcpy(qgd->name, varname);
entity_list.DeleteQGlobal(std::string((char*)qgd->name), qgd->npc_id, qgd->char_id, qgd->zone_id);
zone->DeleteQGlobal(std::string((char*)qgd->name), qgd->npc_id, qgd->char_id, qgd->zone_id);
worldserver.SendPacket(pack);
safe_delete(pack);
//then create a new one with the new id
pack = new ServerPacket(ServerOP_QGlobalUpdate, sizeof(ServerQGlobalUpdate_Struct));
ServerQGlobalUpdate_Struct *qgu = (ServerQGlobalUpdate_Struct*)pack->pBuffer;
qgu->npc_id = npcid;
qgu->char_id = charid;
qgu->zone_id = zoneid;
if(duration == INT_MAX)
{
qgu->expdate = 0xFFFFFFFF;
}
else
{
qgu->expdate = Timer::GetTimeSeconds() + duration;
}
strcpy((char*)qgu->name, varname);
strcpy((char*)qgu->value, varvalue);
qgu->id = last_id;
qgu->from_zone_id = zone->GetZoneID();
qgu->from_instance_id = zone->GetInstanceID();
QGlobal temp;
temp.npc_id = npcid;
temp.char_id = charid;
temp.zone_id = zoneid;
temp.expdate = qgu->expdate;
temp.name.assign(qgu->name);
temp.value.assign(qgu->value);
entity_list.UpdateQGlobal(qgu->id, temp);
zone->UpdateQGlobal(qgu->id, temp);
worldserver.SendPacket(pack);
safe_delete(pack);
}
}
// Converts duration string to duration value (in seconds)
// Return of INT_MAX indicates infinite duration
int Mob::QGVarDuration(const char *fmt)
{
int duration = 0;
// format: Y#### or D## or H## or M## or S## or T###### or C#######
int len = static_cast<int>(strlen(fmt));
// Default to no duration
if (len < 1)
return 0;
// Set val to value after type character
// e.g., for "M3924", set to 3924
int val = atoi(&fmt[0] + 1);
switch (fmt[0])
{
// Forever
case 'F':
case 'f':
duration = INT_MAX;
break;
// Years
case 'Y':
case 'y':
duration = val * 31556926;
break;
case 'D':
case 'd':
duration = val * 86400;
break;
// Hours
case 'H':
case 'h':
duration = val * 3600;
break;
// Minutes
case 'M':
case 'm':
duration = val * 60;
break;
// Seconds
case 'S':
case 's':
duration = val;
break;
// Invalid
default:
duration = 0;
break;
}
return duration;
}
void Mob::DoKnockback(Mob *caster, uint32 pushback, uint32 pushup)
{
if(IsClient())
{
CastToClient()->SetKnockBackExemption(true);
EQApplicationPacket* outapp_push = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
PlayerPositionUpdateServer_Struct* spu = (PlayerPositionUpdateServer_Struct*)outapp_push->pBuffer;
double look_heading = caster->CalculateHeadingToTarget(GetX(), GetY());
look_heading /= 256;
look_heading *= 360;
if(look_heading > 360)
look_heading -= 360;
//x and y are crossed mkay
double new_x = pushback * sin(double(look_heading * 3.141592 / 180.0));
double new_y = pushback * cos(double(look_heading * 3.141592 / 180.0));
spu->spawn_id = GetID();
spu->x_pos = FloatToEQ19(GetX());
spu->y_pos = FloatToEQ19(GetY());
spu->z_pos = FloatToEQ19(GetZ());
spu->delta_x = NewFloatToEQ13(static_cast<float>(new_x));
spu->delta_y = NewFloatToEQ13(static_cast<float>(new_y));
spu->delta_z = NewFloatToEQ13(static_cast<float>(pushup));
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 = 6;
entity_list.QueueClients(this, outapp_push, true);
CastToClient()->FastQueuePacket(&outapp_push);
}
}
void Mob::TrySpellOnKill(uint8 level, uint16 spell_id)
{
if (spell_id != SPELL_UNKNOWN)
{
if(IsEffectInSpell(spell_id, SE_SpellOnKill2)) {
for (int i = 0; i < EFFECT_COUNT; i++) {
if (spells[spell_id].effectid[i] == SE_SpellOnKill2)
{
if (spells[spell_id].max[i] <= level)
{
if(MakeRandomInt(0,99) < spells[spell_id].base[i])
SpellFinished(spells[spell_id].base2[i], this);
}
}
}
}
}
if (!aabonuses.SpellOnKill[0] && !itembonuses.SpellOnKill[0] && !spellbonuses.SpellOnKill[0])
return;
// Allow to check AA, items and buffs in all cases. Base2 = Spell to fire | Base1 = % chance | Base3 = min level
for(int i = 0; i < MAX_SPELL_TRIGGER*3; i+=3) {
if(aabonuses.SpellOnKill[i] && (level >= aabonuses.SpellOnKill[i + 2])) {
if(MakeRandomInt(0, 99) < static_cast<int>(aabonuses.SpellOnKill[i + 1]))
SpellFinished(aabonuses.SpellOnKill[i], this);
}
if(itembonuses.SpellOnKill[i] && (level >= itembonuses.SpellOnKill[i + 2])){
if(MakeRandomInt(0, 99) < static_cast<int>(itembonuses.SpellOnKill[i + 1]))
SpellFinished(itembonuses.SpellOnKill[i], this);
}
if(spellbonuses.SpellOnKill[i] && (level >= spellbonuses.SpellOnKill[i + 2])) {
if(MakeRandomInt(0, 99) < static_cast<int>(spellbonuses.SpellOnKill[i + 1]))
SpellFinished(spellbonuses.SpellOnKill[i], this);
}
}
}
bool Mob::TrySpellOnDeath()
{
if (IsNPC() && !spellbonuses.SpellOnDeath[0] && !itembonuses.SpellOnDeath[0])
return false;
if (IsClient() && !aabonuses.SpellOnDeath[0] && !spellbonuses.SpellOnDeath[0] && !itembonuses.SpellOnDeath[0])
return false;
for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2) {
if(IsClient() && aabonuses.SpellOnDeath[i]) {
if(MakeRandomInt(0, 99) < static_cast<int>(aabonuses.SpellOnDeath[i + 1])) {
SpellFinished(aabonuses.SpellOnDeath[i], this);
}
}
if(itembonuses.SpellOnDeath[i]) {
if(MakeRandomInt(0, 99) < static_cast<int>(itembonuses.SpellOnDeath[i + 1])) {
SpellFinished(itembonuses.SpellOnDeath[i], this);
}
}
if(spellbonuses.SpellOnDeath[i]) {
if(MakeRandomInt(0, 99) < static_cast<int>(spellbonuses.SpellOnDeath[i + 1])) {
SpellFinished(spellbonuses.SpellOnDeath[i], this);
}
}
}
BuffFadeAll();
return false;
//You should not be able to use this effect and survive (ALWAYS return false),
//attempting to place a heal in these effects will still result
//in death because the heal will not register before the script kills you.
}
int16 Mob::GetCritDmgMob(uint16 skill)
{
int critDmg_mod = 0;
// All skill dmg mod + Skill specific
critDmg_mod += itembonuses.CritDmgMob[HIGHEST_SKILL+1] + spellbonuses.CritDmgMob[HIGHEST_SKILL+1] + aabonuses.CritDmgMob[HIGHEST_SKILL+1] +
itembonuses.CritDmgMob[skill] + spellbonuses.CritDmgMob[skill] + aabonuses.CritDmgMob[skill];
if(critDmg_mod < -100)
critDmg_mod = -100;
return critDmg_mod;
}
void Mob::SetGrouped(bool v)
{
if(v)
{
israidgrouped = false;
}
isgrouped = v;
if(IsClient())
{
parse->EventPlayer(EVENT_GROUP_CHANGE, CastToClient(), "", 0);
if(!v)
CastToClient()->RemoveGroupXTargets();
}
}
void Mob::SetRaidGrouped(bool v)
{
if(v)
{
isgrouped = false;
}
israidgrouped = v;
if(IsClient())
{
parse->EventPlayer(EVENT_GROUP_CHANGE, CastToClient(), "", 0);
}
}
int16 Mob::GetCriticalChanceBonus(uint16 skill)
{
int critical_chance = 0;
// All skills + Skill specific
critical_chance += itembonuses.CriticalHitChance[HIGHEST_SKILL+1] + spellbonuses.CriticalHitChance[HIGHEST_SKILL+1] + aabonuses.CriticalHitChance[HIGHEST_SKILL+1] +
itembonuses.CriticalHitChance[skill] + spellbonuses.CriticalHitChance[skill] + aabonuses.CriticalHitChance[skill];
if(critical_chance < -100)
critical_chance = -100;
return critical_chance;
}
int16 Mob::GetMeleeDamageMod_SE(uint16 skill)
{
int dmg_mod = 0;
// All skill dmg mod + Skill specific
dmg_mod += itembonuses.DamageModifier[HIGHEST_SKILL+1] + spellbonuses.DamageModifier[HIGHEST_SKILL+1] + aabonuses.DamageModifier[HIGHEST_SKILL+1] +
itembonuses.DamageModifier[skill] + spellbonuses.DamageModifier[skill] + aabonuses.DamageModifier[skill];
if(dmg_mod < -100)
dmg_mod = -100;
if (spellbonuses.DamageModifier[HIGHEST_SKILL+1] || spellbonuses.DamageModifier[skill])
CheckHitsRemaining(0, false, false, SE_DamageModifier,0,true,skill);
return dmg_mod;
}
int16 Mob::GetMeleeMinDamageMod_SE(uint16 skill)
{
int dmg_mod = 0;
dmg_mod = itembonuses.MinDamageModifier[skill] + spellbonuses.MinDamageModifier[skill] +
itembonuses.MinDamageModifier[HIGHEST_SKILL+1] + spellbonuses.MinDamageModifier[HIGHEST_SKILL+1];
if(dmg_mod < -100)
dmg_mod = -100;
return dmg_mod;
}
int16 Mob::GetCrippBlowChance()
{
int16 crip_chance = 0;
crip_chance += itembonuses.CrippBlowChance + spellbonuses.CrippBlowChance + aabonuses.CrippBlowChance;
if(crip_chance < 0)
crip_chance = 0;
return crip_chance;
}
int16 Mob::GetSkillReuseTime(uint16 skill)
{
int skill_reduction = this->itembonuses.SkillReuseTime[skill] + this->spellbonuses.SkillReuseTime[skill] + this->aabonuses.SkillReuseTime[skill];
return skill_reduction;
}
int16 Mob::GetSkillDmgAmt(uint16 skill)
{
int skill_dmg = 0;
// All skill dmg(only spells do this) + Skill specific
skill_dmg += spellbonuses.SkillDamageAmount[HIGHEST_SKILL+1] + itembonuses.SkillDamageAmount[HIGHEST_SKILL+1] + aabonuses.SkillDamageAmount[HIGHEST_SKILL+1]
+ itembonuses.SkillDamageAmount[skill] + spellbonuses.SkillDamageAmount[skill] + aabonuses.SkillDamageAmount[skill];
skill_dmg += spellbonuses.SkillDamageAmount2[HIGHEST_SKILL+1] + itembonuses.SkillDamageAmount2[HIGHEST_SKILL+1]
+ itembonuses.SkillDamageAmount2[skill] + spellbonuses.SkillDamageAmount2[skill];
// Deplete the buff if needed
if (spellbonuses.SkillDamageAmount[HIGHEST_SKILL+1] || spellbonuses.SkillDamageAmount[skill])
CheckHitsRemaining(0, false,false, SE_SkillDamageAmount,0,true,skill);
if (spellbonuses.SkillDamageAmount2[HIGHEST_SKILL+1] || spellbonuses.SkillDamageAmount2[skill])
CheckHitsRemaining(0, false,false, SE_SkillDamageAmount2,0,true,skill);
return skill_dmg;
}
bool Mob::TryReflectSpell(uint32 spell_id)
{
if(!GetTarget())
return false;
if(GetTarget()->spellbonuses.reflect_chance)
CheckHitsRemaining(0, false, false, SE_Reflect);
if(MakeRandomInt(0, 99) < (GetTarget()->itembonuses.reflect_chance + GetTarget()->spellbonuses.reflect_chance))
return true;
return false;
}
void Mob::DoGravityEffect()
{
Mob *caster = nullptr;
int away = -1;
float caster_x, caster_y, amount, value, cur_x, my_x, cur_y, my_y, x_vector, y_vector, hypot;
// Set values so we can run through all gravity effects and then apply the culmative move at the end
// instead of many small moves if the mob/client had more than 1 gravity effect on them
cur_x = my_x = GetX();
cur_y = my_y = GetY();
int buff_count = GetMaxTotalSlots();
for (int slot = 0; slot < buff_count; slot++)
{
if (buffs[slot].spellid != SPELL_UNKNOWN && IsEffectInSpell(buffs[slot].spellid, SE_GravityEffect))
{
for (int i = 0; i < EFFECT_COUNT; i++)
{
if(spells[buffs[slot].spellid].effectid[i] == SE_GravityEffect) {
int casterId = buffs[slot].casterid;
if(casterId)
caster = entity_list.GetMob(casterId);
if(!caster || casterId == this->GetID())
continue;
caster_x = caster->GetX();
caster_y = caster->GetY();
value = static_cast<float>(spells[buffs[slot].spellid].base[i]);
if(value == 0)
continue;
if(value > 0)
away = 1;
amount = fabs(value) / (100.0f); // to bring the values in line, arbitarily picked
x_vector = cur_x - caster_x;
y_vector = cur_y - caster_y;
hypot = sqrt(x_vector*x_vector + y_vector*y_vector);
if(hypot <= 5) // dont want to be inside the mob, even though we can, it looks bad
continue;
x_vector /= hypot;
y_vector /= hypot;
cur_x = cur_x + (x_vector * amount * away);
cur_y = cur_y + (y_vector * amount * away);
}
}
}
}
if((fabs(my_x - cur_x) > 0.01) || (fabs(my_y - cur_y) > 0.01)) {
float new_ground = GetGroundZ(cur_x, cur_y);
// If we cant get LoS on our new spot then keep checking up to 5 units up.
if(!CheckLosFN(cur_x, cur_y, new_ground, GetSize())) {
for(float z_adjust = 0.1f; z_adjust < 5; z_adjust += 0.1f) {
if(CheckLosFN(cur_x, cur_y, new_ground+z_adjust, GetSize())) {
new_ground += z_adjust;
break;
}
}
// If we still fail, then lets only use the x portion(ie sliding around a wall)
if(!CheckLosFN(cur_x, my_y, new_ground, GetSize())) {
// If that doesnt work, try the y
if(!CheckLosFN(my_x, cur_y, new_ground, GetSize())) {
// If everything fails, then lets do nothing
return;
}
else {
cur_x = my_x;
}
}
else {
cur_y = my_y;
}
}
if(IsClient())
this->CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), cur_x, cur_y, new_ground, GetHeading()*2); // I know the heading thing is weird(chance of movepc to halve the heading value, too lazy to figure out why atm)
else
this->GMMove(cur_x, cur_y, new_ground, GetHeading());
}
}
void Mob::SpreadVirus(uint16 spell_id, uint16 casterID)
{
int num_targs = spells[spell_id].viral_targets;
Mob* caster = entity_list.GetMob(casterID);
Mob* target = nullptr;
// Only spread in zones without perm buffs
if(!zone->BuffTimersSuspended()) {
for(int i = 0; i < num_targs; i++) {
target = entity_list.GetTargetForVirus(this);
if(target) {
// Only spreads to the uninfected
if(!target->FindBuff(spell_id)) {
if(caster)
caster->SpellOnTarget(spell_id, target);
}
}
}
}
}
void Mob::RemoveNimbusEffect(int effectid)
{
EQApplicationPacket* outapp = new EQApplicationPacket(OP_RemoveNimbusEffect, sizeof(RemoveNimbusEffect_Struct));
RemoveNimbusEffect_Struct* rne = (RemoveNimbusEffect_Struct*)outapp->pBuffer;
rne->spawnid = GetID();
rne->nimbus_effect = effectid;
entity_list.QueueClients(this, outapp);
safe_delete(outapp);
}
bool Mob::IsBoat() const {
return (race == 72 || race == 73 || race == 114 || race == 404 || race == 550 || race == 551 || race == 552);
}
void Mob::SetBodyType(bodyType new_body, bool overwrite_orig) {
bool needs_spawn_packet = false;
if(bodytype == 11 || bodytype >= 65 || new_body == 11 || new_body >= 65) {
needs_spawn_packet = true;
}
if(overwrite_orig) {
orig_bodytype = new_body;
}
bodytype = new_body;
if(needs_spawn_packet) {
EQApplicationPacket* app = new EQApplicationPacket;
CreateDespawnPacket(app, true);
entity_list.QueueClients(this, app);
CreateSpawnPacket(app, this);
entity_list.QueueClients(this, app);
safe_delete(app);
}
}
void Mob::ModSkillDmgTaken(SkillType skill_num, int value)
{
if (skill_num <= HIGHEST_SKILL)
SkillDmgTaken_Mod[skill_num] = value;
else if (skill_num == 255)
SkillDmgTaken_Mod[HIGHEST_SKILL+1] = value;
}
int16 Mob::GetModSkillDmgTaken(const SkillType skill_num)
{
if (skill_num <= HIGHEST_SKILL)
return SkillDmgTaken_Mod[skill_num];
else if (skill_num == 255)
return SkillDmgTaken_Mod[HIGHEST_SKILL+1];
return 0;
}
void Mob::ModVulnerability(uint8 resist, int16 value)
{
if (resist < HIGHEST_RESIST+1)
Vulnerability_Mod[resist] = value;
else if (resist == 255)
Vulnerability_Mod[HIGHEST_RESIST+1] = value;
}
int16 Mob::GetModVulnerability(const uint8 resist)
{
if (resist < HIGHEST_RESIST+1)
return Vulnerability_Mod[resist];
else if (resist == 255)
return Vulnerability_Mod[HIGHEST_RESIST+1];
return 0;
}
void Mob::CastOnCurer(uint32 spell_id)
{
for(int i = 0; i < EFFECT_COUNT; i++)
{
if (spells[spell_id].effectid[i] == SE_CastOnCurer)
{
if(IsValidSpell(spells[spell_id].base[i]))
{
SpellFinished(spells[spell_id].base[i], this);
}
}
}
}
void Mob::CastOnCure(uint32 spell_id)
{
for(int i = 0; i < EFFECT_COUNT; i++)
{
if (spells[spell_id].effectid[i] == SE_CastOnCure)
{
if(IsValidSpell(spells[spell_id].base[i]))
{
SpellFinished(spells[spell_id].base[i], this);
}
}
}
}
void Mob::CastOnNumHitFade(uint32 spell_id)
{
if(!IsValidSpell(spell_id))
return;
uint32 buff_max = GetMaxTotalSlots();
for(int i = 0; i < EFFECT_COUNT; i++)
{
if (spells[spell_id].effectid[i] == SE_CastonNumHitFade)
{
if(IsValidSpell(spells[spell_id].base[i]))
{
SpellFinished(spells[spell_id].base[i], this);
}
}
}
}
int Mob::SlowMitigation(bool slow_msg, Mob *caster, int slow_value)
{
float int_slow_mitigation = slow_mitigation * 100.0f;
if (int_slow_mitigation > 100.0f)
return 0;
if (slow_msg)
{
if (caster && caster->IsClient())
{
if ((int_slow_mitigation > 0.0f) && (int_slow_mitigation < 26.0f))
caster->Message(262, "Your spell was mostly successful");
else if ((int_slow_mitigation > 26.0f) && (int_slow_mitigation < 74.0f))
caster->Message(262, "Your spell was partially successful");
else if ((int_slow_mitigation > 74.0f) && (int_slow_mitigation < 101.0f))
caster->Message(262, "Your spell was slightly successful");
}
return 0;
}
else
{
slow_value -= (slow_value * static_cast<int>(int_slow_mitigation) / 100);
return slow_value;
}
}
uint16 Mob::GetSkillByItemType(int ItemType)
{
switch (ItemType)
{
case ItemType1HS:
return _1H_SLASHING;
case ItemType2HS:
return _2H_SLASHING;
case ItemTypePierce:
return PIERCING;
case ItemType1HB:
return _1H_BLUNT;
case ItemType2HB:
return _2H_BLUNT;
case ItemType2HPierce:
return PIERCING;
case ItemTypeHand2Hand:
return HAND_TO_HAND;
default:
return HAND_TO_HAND;
}
return HAND_TO_HAND;
}
bool Mob::PassLimitToSkill(uint16 spell_id, uint16 skill) {
if (!IsValidSpell(spell_id))
return false;
if (!IsEffectInSpell(spell_id, SE_LimitToSkill))
return false;
for (int i = 0; i < EFFECT_COUNT; i++) {
if (spells[spell_id].effectid[i] == SE_LimitToSkill){
if (spells[spell_id].base[i] == skill){
return true;
}
}
}
return false;
}
// Faction Mods for Alliance type spells
void Mob::AddFactionBonus(uint32 pFactionID,int32 bonus) {
map <uint32, int32> :: const_iterator faction_bonus;
typedef std::pair <uint32, int32> NewFactionBonus;
faction_bonus = faction_bonuses.find(pFactionID);
if(faction_bonus == faction_bonuses.end())
{
faction_bonuses.insert(NewFactionBonus(pFactionID,bonus));
}
else
{
if(faction_bonus->second<bonus)
{
faction_bonuses.erase(pFactionID);
faction_bonuses.insert(NewFactionBonus(pFactionID,bonus));
}
}
}
// Faction Mods from items
void Mob::AddItemFactionBonus(uint32 pFactionID,int32 bonus) {
map <uint32, int32> :: const_iterator faction_bonus;
typedef std::pair <uint32, int32> NewFactionBonus;
faction_bonus = item_faction_bonuses.find(pFactionID);
if(faction_bonus == item_faction_bonuses.end())
{
item_faction_bonuses.insert(NewFactionBonus(pFactionID,bonus));
}
else
{
if((bonus > 0 && faction_bonus->second < bonus) || (bonus < 0 && faction_bonus->second > bonus))
{
item_faction_bonuses.erase(pFactionID);
item_faction_bonuses.insert(NewFactionBonus(pFactionID,bonus));
}
}
}
int32 Mob::GetFactionBonus(uint32 pFactionID) {
map <uint32, int32> :: const_iterator faction_bonus;
faction_bonus = faction_bonuses.find(pFactionID);
if(faction_bonus != faction_bonuses.end())
{
return (*faction_bonus).second;
}
return 0;
}
int32 Mob::GetItemFactionBonus(uint32 pFactionID) {
map <uint32, int32> :: const_iterator faction_bonus;
faction_bonus = item_faction_bonuses.find(pFactionID);
if(faction_bonus != item_faction_bonuses.end())
{
return (*faction_bonus).second;
}
return 0;
}
void Mob::ClearItemFactionBonuses() {
map <uint32, int32> :: iterator itr;
for(itr = item_faction_bonuses.begin(); itr != item_faction_bonuses.end(); itr++)
{
item_faction_bonuses.erase(itr->first);
}
}
FACTION_VALUE Mob::GetSpecialFactionCon(Mob* iOther) {
if (!iOther)
return FACTION_INDIFFERENT;
iOther = iOther->GetOwnerOrSelf();
Mob* self = this->GetOwnerOrSelf();
bool selfAIcontrolled = self->IsAIControlled();
bool iOtherAIControlled = iOther->IsAIControlled();
int selfPrimaryFaction = self->GetPrimaryFaction();
int iOtherPrimaryFaction = iOther->GetPrimaryFaction();
if (selfPrimaryFaction >= 0 && selfAIcontrolled)
return FACTION_INDIFFERENT;
if (iOther->GetPrimaryFaction() >= 0)
return FACTION_INDIFFERENT;
/* special values:
-2 = indiff to player, ally to AI on special values, indiff to AI
-3 = dub to player, ally to AI on special values, indiff to AI
-4 = atk to player, ally to AI on special values, indiff to AI
-5 = indiff to player, indiff to AI
-6 = dub to player, indiff to AI
-7 = atk to player, indiff to AI
-8 = indiff to players, ally to AI on same value, indiff to AI
-9 = dub to players, ally to AI on same value, indiff to AI
-10 = atk to players, ally to AI on same value, indiff to AI
-11 = indiff to players, ally to AI on same value, atk to AI
-12 = dub to players, ally to AI on same value, atk to AI
-13 = atk to players, ally to AI on same value, atk to AI
*/
switch (iOtherPrimaryFaction) {
case -2: // -2 = indiff to player, ally to AI on special values, indiff to AI
if (selfAIcontrolled && iOtherAIControlled)
return FACTION_ALLY;
else
return FACTION_INDIFFERENT;
case -3: // -3 = dub to player, ally to AI on special values, indiff to AI
if (selfAIcontrolled && iOtherAIControlled)
return FACTION_ALLY;
else
return FACTION_DUBIOUS;
case -4: // -4 = atk to player, ally to AI on special values, indiff to AI
if (selfAIcontrolled && iOtherAIControlled)
return FACTION_ALLY;
else
return FACTION_SCOWLS;
case -5: // -5 = indiff to player, indiff to AI
return FACTION_INDIFFERENT;
case -6: // -6 = dub to player, indiff to AI
if (selfAIcontrolled && iOtherAIControlled)
return FACTION_INDIFFERENT;
else
return FACTION_DUBIOUS;
case -7: // -7 = atk to player, indiff to AI
if (selfAIcontrolled && iOtherAIControlled)
return FACTION_INDIFFERENT;
else
return FACTION_SCOWLS;
case -8: // -8 = indiff to players, ally to AI on same value, indiff to AI
if (selfAIcontrolled && iOtherAIControlled) {
if (selfPrimaryFaction == iOtherPrimaryFaction)
return FACTION_ALLY;
else
return FACTION_INDIFFERENT;
}
else
return FACTION_INDIFFERENT;
case -9: // -9 = dub to players, ally to AI on same value, indiff to AI
if (selfAIcontrolled && iOtherAIControlled) {
if (selfPrimaryFaction == iOtherPrimaryFaction)
return FACTION_ALLY;
else
return FACTION_INDIFFERENT;
}
else
return FACTION_DUBIOUS;
case -10: // -10 = atk to players, ally to AI on same value, indiff to AI
if (selfAIcontrolled && iOtherAIControlled) {
if (selfPrimaryFaction == iOtherPrimaryFaction)
return FACTION_ALLY;
else
return FACTION_INDIFFERENT;
}
else
return FACTION_SCOWLS;
case -11: // -11 = indiff to players, ally to AI on same value, atk to AI
if (selfAIcontrolled && iOtherAIControlled) {
if (selfPrimaryFaction == iOtherPrimaryFaction)
return FACTION_ALLY;
else
return FACTION_SCOWLS;
}
else
return FACTION_INDIFFERENT;
case -12: // -12 = dub to players, ally to AI on same value, atk to AI
if (selfAIcontrolled && iOtherAIControlled) {
if (selfPrimaryFaction == iOtherPrimaryFaction)
return FACTION_ALLY;
else
return FACTION_SCOWLS;
}
else
return FACTION_DUBIOUS;
case -13: // -13 = atk to players, ally to AI on same value, atk to AI
if (selfAIcontrolled && iOtherAIControlled) {
if (selfPrimaryFaction == iOtherPrimaryFaction)
return FACTION_ALLY;
else
return FACTION_SCOWLS;
}
else
return FACTION_SCOWLS;
default:
return FACTION_INDIFFERENT;
}
}
bool Mob::HasSpellEffect(int effectid)
{
int i;
uint32 buff_count = GetMaxTotalSlots();
for(i = 0; i < buff_count; i++)
{
if(buffs[i].spellid == SPELL_UNKNOWN) { continue; }
if(IsEffectInSpell(buffs[i].spellid, effectid))
{
return(1);
}
}
return(0);
}