void TempSummon::UnSummon(uint32 msTime) { if (msTime) { ForcedUnsummonDelayEvent* pEvent = new ForcedUnsummonDelayEvent(*this); m_Events.AddEvent(pEvent, m_Events.CalculateTime(msTime)); return; } //ASSERT(!IsPet()); if (IsPet()) { ((Pet*)this)->Remove(PET_SAVE_NOT_IN_SLOT); ASSERT(!IsInWorld()); return; } Unit* owner = GetSummoner(); if (owner && owner->GetTypeId() == TYPEID_UNIT && owner->ToCreature()->IsAIEnabled) owner->ToCreature()->AI()->SummonedCreatureDespawn(this); //npcbot if (GetIAmABot() || GetIAmABotsPet()) { //TC_LOG_ERROR("entities.player", "TempSummon::UnSummon(): Trying to unsummon Bot %s (guidLow: %u owner: %s)", GetName().c_str(), GetGUIDLow(), GetBotOwner()->GetName().c_str()); if (IsTempBot()) AI()->JustDied(NULL); return; } //end npcbots AddObjectToRemoveList(); }
//healing and buffing aggro int32 Mob::CheckHealAggroAmount(uint16 spell_id, Mob *target, uint32 heal_possible) { int32 AggroAmount = 0; auto target_level = target ? target->GetLevel() : 1; bool ignore_default_buff = false; // rune/hot don't use the default 9, HP buffs that heal (virtue) do use the default for (int o = 0; o < EFFECT_COUNT; o++) { switch (spells[spell_id].effectid[o]) { case SE_CurrentHP: { if (heal_possible == 0) { AggroAmount += 1; break; } // hate based on base healing power of the spell int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], GetLevel(), spell_id); if (val > 0) { if (heal_possible < val) val = heal_possible; // capped to amount healed val = 2 * val / 3; // 3:2 ratio if (target_level > 50 && val > 1500) val = 1500; // target 51+ seems ~1500 else if (target_level <= 50 && val > 800) val = 800; // per live patch notes, capped to 800 } AggroAmount += std::max(val, 1); break; } case SE_Rune: AggroAmount += CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], GetLevel(), spell_id) * 2; ignore_default_buff = true; break; case SE_HealOverTime: AggroAmount += 10; ignore_default_buff = true; break; default: break; } } if (GetOwner() && IsPet()) AggroAmount = AggroAmount * RuleI(Aggro, PetSpellAggroMod) / 100; if (!ignore_default_buff && IsBuffSpell(spell_id) && IsBeneficialSpell(spell_id)) AggroAmount = IsBardSong(spell_id) ? 2 : 9; if (AggroAmount > 0) { int HateMod = RuleI(Aggro, SpellAggroMod); HateMod += GetFocusEffect(focusSpellHateMod, spell_id); //Live AA - Spell casting subtlety HateMod += aabonuses.hatemod + spellbonuses.hatemod + itembonuses.hatemod; AggroAmount = (AggroAmount * HateMod) / 100; } return std::max(0, AggroAmount); }
void TempSummon::UnSummon(uint32 msTime) { if (msTime) { ForcedUnsummonDelayEvent* pEvent = new ForcedUnsummonDelayEvent(*this); m_Events.AddEvent(pEvent, m_Events.CalculateTime(msTime)); return; } // Dont allow to call this function twice (possible) if (m_type == TEMPSUMMON_DESPAWNED) return; SetTempSummonType(TEMPSUMMON_DESPAWNED); //ASSERT(!IsPet()); if (IsPet()) { ((Pet*)this)->Remove(PET_SAVE_NOT_IN_SLOT); ASSERT(!IsInWorld()); return; } Unit* owner = GetSummoner(); if (owner && owner->GetTypeId() == TYPEID_UNIT && owner->ToCreature()->IsAIEnabled) owner->ToCreature()->AI()->SummonedCreatureDespawn(this); AddObjectToRemoveList(); }
void Guardian::UpdateAttackPowerAndDamage(bool ranged) { if (ranged) return; float val = 0.0f; float bonusAP = 0.0f; UnitMods unitMod = UNIT_MOD_ATTACK_POWER; if (GetEntry() == 416) // imp's attack power val = GetStat(STAT_STRENGTH) - 10.0f; else val = 2 * GetStat(STAT_STRENGTH) - 20.0f; Unit* owner = GetOwner(); if (owner && owner->GetTypeId() == TYPEID_PLAYER) { if (IsHunterPet()) //hunter pets benefit from owner's attack power { bonusAP = owner->GetTotalAttackPowerValue(RANGED_ATTACK) * 0.22f; SetBonusDamage(int32(owner->GetTotalAttackPowerValue(RANGED_ATTACK) * 0.125f)); } //demons benefit from warlocks shadow or fire damage else if (IsPet() && owner->getClass() == CLASS_WARLOCK) { int32 fire = int32(owner->GetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS + SPELL_SCHOOL_FIRE)) - owner->GetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG + SPELL_SCHOOL_FIRE); int32 shadow = int32(owner->GetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS + SPELL_SCHOOL_SHADOW)) - owner->GetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG + SPELL_SCHOOL_SHADOW); int32 maximum = (fire > shadow) ? fire : shadow; if (maximum < 0) maximum = 0; SetBonusDamage(int32(maximum * 0.15f)); bonusAP = maximum * 0.57f; } //water elementals benefit from mage's frost damage else if (GetEntry() == 510 && owner->getClass() == CLASS_MAGE) { int32 frost = int32(owner->GetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS + SPELL_SCHOOL_FROST)) - owner->GetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG + SPELL_SCHOOL_FROST); if (frost < 0) frost = 0; SetBonusDamage(int32(frost * 0.4f)); } } SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, val + bonusAP); //in BASE_VALUE of UNIT_MOD_ATTACK_POWER for creatures we store data of meleeattackpower field in DB float base_attPower = GetModifierValue(unitMod, BASE_VALUE) * GetModifierValue(unitMod, BASE_PCT); float attPowerMod = GetModifierValue(unitMod, TOTAL_VALUE); float attPowerMultiplier = GetModifierValue(unitMod, TOTAL_PCT) - 1.0f; //UNIT_FIELD_(RANGED)_ATTACK_POWER field SetInt32Value(UNIT_FIELD_ATTACK_POWER, (int32)base_attPower); //UNIT_FIELD_(RANGED)_ATTACK_POWER_MODS field SetInt32Value(UNIT_FIELD_ATTACK_POWER_MODS, (int32)attPowerMod); //UNIT_FIELD_(RANGED)_ATTACK_POWER_MULTIPLIER field SetFloatValue(UNIT_FIELD_ATTACK_POWER_MULTIPLIER, attPowerMultiplier); //automatically update weapon damage after attack power modification UpdateDamagePhysical(BASE_ATTACK); }
bool Mob::CalculateNewPosition2(float x, float y, float z, float speed, bool checkZ) { if(IsNPC() || IsClient() || IsPet()) { pRunAnimSpeed = (int8)(speed*NPC_RUNANIM_RATIO); speed *= NPC_SPEED_MULTIPLIER; } return MakeNewPositionAndSendUpdate(x, y, z, speed, checkZ); }
//healing and buffing aggro int32 Mob::CheckHealAggroAmount(uint16 spellid, uint32 heal_possible) { uint16 spell_id = spellid; int32 AggroAmount = 0; for (int o = 0; o < EFFECT_COUNT; o++) { switch(spells[spell_id].effectid[o]) { case SE_CurrentHP: { AggroAmount += spells[spell_id].mana; break; } case SE_Rune: { AggroAmount += CalcSpellEffectValue_formula(spells[spell_id].formula[0], spells[spell_id].base[0], spells[spell_id].max[o], this->GetLevel(), spellid) * 2; break; } case SE_HealOverTime:{ AggroAmount += CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], this->GetLevel(), spell_id); break; } default:{ break; } } } if (IsBardSong(spell_id)) AggroAmount = AggroAmount * RuleI(Aggro, SongAggroMod) / 100; if (GetOwner() && IsPet()) AggroAmount = AggroAmount * RuleI(Aggro, PetSpellAggroMod) / 100; if(AggroAmount > 0) { int HateMod = RuleI(Aggro, SpellAggroMod); if(IsClient()) { HateMod += CastToClient()->GetFocusEffect(focusSpellHateMod, spell_id); } //Live AA - Spell casting subtlety HateMod += aabonuses.hatemod + spellbonuses.hatemod + itembonuses.hatemod; AggroAmount = (AggroAmount * HateMod) / 100; //made up number probably scales a bit differently on live but it seems like it will be close enough //every time you cast on live you get a certain amount of "this is a spell" aggro //confirmed by EQ devs to be 100 exactly at level 85. From their wording it doesn't seem like it's affected //by hate modifiers either. //AggroAmount += (slevel*slevel/72); // Moved Below } if(AggroAmount < 0) return 0; else return AggroAmount; }
std::string ObjectGuid::ToString() const { std::ostringstream str; str << "GUID Full: 0x" << std::hex << std::setw(16) << std::setfill('0') << _guid << std::dec; str << " Type: " << GetTypeName(); if (HasEntry()) str << (IsPet() ? " Pet number: " : " Entry: ") << GetEntry() << " "; str << " Low: " << GetCounter(); return str.str(); }
void Guardian::UpdateResistances(uint32 school) { if (school > SPELL_SCHOOL_NORMAL) { float value = GetTotalAuraModValue(UnitMods(UNIT_MOD_RESISTANCE_START + school)); // hunter and warlock pets gain 40% of owner's resistance if (IsPet()) value += float(CalculatePct(m_owner->GetResistance(SpellSchools(school)), 40)); SetResistance(SpellSchools(school), int32(value)); } else UpdateArmor(); }
void Guardian::UpdateResistances(uint32 school) { if (school > SPELL_SCHOOL_NORMAL) { float value = GetTotalAuraModValue(UnitMods(UNIT_MOD_RESISTANCE_START + school)); Unit* owner = GetOwner(); // hunter and warlock pets gain 40% of owner's resistance if (owner && (IsHunterPet() || (IsPet() && owner->getClass() == CLASS_WARLOCK))) value += float(owner->GetResistance(SpellSchools(school))) * 0.4f; SetResistance(SpellSchools(school), int32(value)); } else UpdateArmor(); }
void TempSummon::InitStats(uint32 duration) { ASSERT(!IsPet()); m_timer = duration; m_lifetime = duration; if (m_type == TEMPSUMMON_MANUAL_DESPAWN) m_type = (duration == 0) ? TEMPSUMMON_DEAD_DESPAWN : TEMPSUMMON_TIMED_DESPAWN; Unit* owner = GetSummoner(); if (owner) { if (IsTrigger() && m_spells[0]) { setFaction(owner->getFaction()); SetLevel(owner->getLevel()); if (owner->GetTypeId() == TYPEID_PLAYER) m_ControlledByPlayer = true; } if (owner->GetTypeId() == TYPEID_PLAYER) m_CreatedByPlayer = true; } if (!m_Properties) return; if (owner) { if (uint32 slot = m_Properties->Slot) { if (owner->m_SummonSlot[slot] && owner->m_SummonSlot[slot] != GetGUID()) { Creature* oldSummon = GetMap()->GetCreature(owner->m_SummonSlot[slot]); if (oldSummon && oldSummon->IsSummon()) oldSummon->ToTempSummon()->UnSummon(); } owner->m_SummonSlot[slot] = GetGUID(); } } if (m_Properties->Faction) setFaction(m_Properties->Faction); else if (IsVehicle() && owner) // properties should be vehicle setFaction(owner->getFaction()); }
bool Client::UseDiscipline(uint8 disc_id) { // Dont let client waste a reuse timer if they can't use the disc if (IsStunned() || IsFeared() || IsMezzed() || IsAmnesiad() || IsPet()) { return(false); } //Check the disc timer uint32 remain = p_timers.GetRemainingTime(pTimerDisciplineReuseStart); if(remain > 0 && !GetGM()) { char val1[20]= {0}; char val2[20]= {0}; Message_StringID(CC_User_Disciplines, DISCIPLINE_CANUSEIN, ConvertArray((remain)/60,val1), ConvertArray(remain%60,val2)); return(false); } bool active = disc_ability_timer.Enabled(); if(active) { Message(CC_User_Disciplines, "You must wait before using this discipline."); //find correct message return(false); } //can we use the disc? the client checks this for us, but we should also confirm server side. uint8 level_to_use = DisciplineUseLevel(disc_id); if(level_to_use > GetLevel() || level_to_use == 0) { Message_StringID(CC_User_Disciplines, DISC_LEVEL_USE_ERROR); return(false); } // Disciplines with no ability timer (ashenhand, silentfist, thunderkick, and unholyaura) will remain on the player until they either // use the skill the disc affects successfully, camp/zone, or attempt to use another disc. If we're here, clear that disc so they can // cast a new one. if(GetActiveDisc() != 0) { Log.Out(Logs::General, Logs::Discs, "Clearing disc %d so that disc %d can be cast.", GetActiveDisc(), disc_id); FadeDisc(); } //cast the disc if(CastDiscipline(disc_id, level_to_use)) return(true); else return(false); }
std::string ObjectGuid::GetString() const { std::ostringstream str; str << GetTypeName(); if (IsPlayer()) { std::string name; if (sObjectMgr.GetPlayerNameByGUID(*this, name)) str << " " << name; } str << " ("; if (HasEntry()) str << (IsPet() ? "Petnumber: " : "Entry: ") << GetEntry() << " "; str << "Guid: " << GetCounter() << ")"; return str.str(); }
bool Guardian::UpdateStats(Stats stat) { if (stat > STAT_SPIRIT) return false; // value = ((base_value * base_pct) + total_value) * total_pct float value = GetTotalStatValue(stat); Unit* owner = GetOwner(); if (stat == STAT_STAMINA) { if (owner && (IsHunterPet() || owner->getClass() == CLASS_WARLOCK)) value += float(owner->GetStat(stat)) * 0.3f; } //warlock's and mage's pets gain 30% of owner's intellect else if (stat == STAT_INTELLECT && IsPet()) { if (owner && (owner->getClass() == CLASS_WARLOCK || owner->getClass() == CLASS_MAGE)) value += float(owner->GetStat(stat)) * 0.3f; } SetStat(stat, int32(value)); switch (stat) { case STAT_STRENGTH: UpdateAttackPowerAndDamage(); break; case STAT_AGILITY: UpdateArmor(); break; case STAT_STAMINA: UpdateMaxHealth(); break; case STAT_INTELLECT: UpdateMaxPower(POWER_MANA); break; case STAT_SPIRIT: default: break; } return true; }
void Guardian::UpdateArmor() { float value = 0.0f; float bonus_armor = 0.0f; UnitMods unitMod = UNIT_MOD_ARMOR; Unit* owner = GetOwner(); // hunter and warlock pets gain 35% of owner's armor value if (owner && (IsHunterPet() || (IsPet() && owner->getClass() == CLASS_WARLOCK))) bonus_armor = 0.35f * float(owner->GetArmor()); value = GetModifierValue(unitMod, BASE_VALUE); value *= GetModifierValue(unitMod, BASE_PCT); value += GetStat(STAT_AGILITY) * 2.0f; value += GetModifierValue(unitMod, TOTAL_VALUE) + bonus_armor; value *= GetModifierValue(unitMod, TOTAL_PCT); SetArmor(int32(value)); }
void Guardian::UpdateArmor() { float value = 0.0f; float bonus_armor = 0.0f; UnitMods unitMod = UNIT_MOD_ARMOR; // hunter pets gain 35% of owner's armor value, warlock pets gain 100% of owner's armor if (IsHunterPet()) bonus_armor = float(CalculatePct(m_owner->GetArmor(), 70)); else if (IsPet()) bonus_armor = m_owner->GetArmor(); value = GetModifierValue(unitMod, BASE_VALUE); value *= GetModifierValue(unitMod, BASE_PCT); value += GetModifierValue(unitMod, TOTAL_VALUE) + bonus_armor; value *= GetModifierValue(unitMod, TOTAL_PCT); SetArmor(int32(value)); }
void Creature::RemoveFromWorld(bool addrespawnevent, bool free_guid) { RemoveAllAuras(); if(IsPet()) /* Is a pet: IsPet() actually returns false on a pet? o_X */ { if(IsInWorld()) Unit::RemoveFromWorld(true); SafeDelete(); return; } if(IsInWorld()) { uint32 delay = 0; if(addrespawnevent && proto && proto->RespawnTime > 0) delay = proto->RespawnTime; Despawn(0, delay); } }
void TempSummon::UnSummon(uint32 msTime) { if (msTime) { ForcedUnsummonDelayEvent* pEvent = new ForcedUnsummonDelayEvent(*this); m_Events.AddEvent(pEvent, m_Events.CalculateTime(msTime)); return; } //ASSERT(!IsPet()); if (IsPet()) { ((Pet*)this)->Remove(PET_SAVE_NOT_IN_SLOT); ASSERT(!IsInWorld()); return; } Unit* owner = GetSummoner(); if (owner && owner->GetTypeId() == TYPEID_UNIT && owner->ToCreature()->IsAIEnabled) owner->ToCreature()->AI()->SummonedCreatureDespawn(this); AddObjectToRemoveList(); }
//offensive spell aggro int32 Mob::CheckAggroAmount(uint16 spell_id, Mob *target, bool isproc) { if (NoDetrimentalSpellAggro(spell_id)) return 0; int32 AggroAmount = 0; int32 nonModifiedAggro = 0; uint16 slevel = GetLevel(); bool dispel = false; bool on_hatelist = target ? target->CheckAggro(this) : false; int proc_cap = RuleI(Aggro, MaxScalingProcAggro); int hate_cap = isproc && proc_cap != -1 ? proc_cap : 1200; int32 target_hp = target ? target->GetMaxHP() : 18000; // default to max int32 default_aggro = 25; if (target_hp >= 18000) // max default_aggro = hate_cap; else if (target_hp >= 390) // min, 390 is the first number with int division that is 26 default_aggro = target_hp / 15; for (int o = 0; o < EFFECT_COUNT; o++) { switch (spells[spell_id].effectid[o]) { case SE_CurrentHPOnce: case SE_CurrentHP: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if(val < 0) AggroAmount -= val; break; } case SE_MovementSpeed: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount += default_aggro; break; } case SE_AttackSpeed: case SE_AttackSpeed2: case SE_AttackSpeed3: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 100) AggroAmount += default_aggro; break; } case SE_Stun: case SE_Blind: case SE_Mez: case SE_Charm: case SE_Fear: AggroAmount += default_aggro; break; case SE_Root: AggroAmount += 10; break; case SE_ACv2: case SE_ArmorClass: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount += default_aggro; break; } case SE_ATK: case SE_ResistMagic: case SE_ResistFire: case SE_ResistCold: case SE_ResistPoison: case SE_ResistDisease: case SE_STR: case SE_STA: case SE_DEX: case SE_AGI: case SE_INT: case SE_WIS: case SE_CHA: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount += 10; break; } case SE_ResistAll: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount += 50; break; } case SE_AllStats: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount += 70; break; } case SE_BardAEDot: AggroAmount += 10; break; case SE_SpinTarget: case SE_Amnesia: case SE_Silence: case SE_Destroy: AggroAmount += default_aggro; break; // unsure -- leave them this for now case SE_Harmony: case SE_CastingLevel: case SE_MeleeMitigation: case SE_CriticalHitChance: case SE_AvoidMeleeChance: case SE_RiposteChance: case SE_DodgeChance: case SE_ParryChance: case SE_DualWieldChance: case SE_DoubleAttackChance: case SE_MeleeSkillCheck: case SE_HitChance: case SE_DamageModifier: case SE_MinDamageModifier: case SE_IncreaseBlockChance: case SE_Accuracy: case SE_DamageShield: case SE_SpellDamageShield: case SE_ReverseDS: { AggroAmount += slevel * 2; break; } // unsure -- leave them this for now case SE_CurrentMana: case SE_ManaRegen_v2: case SE_ManaPool: case SE_CurrentEndurance: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount -= val * 2; break; } case SE_CancelMagic: case SE_DispelDetrimental: dispel = true; break; case SE_ReduceHate: case SE_InstantHate: nonModifiedAggro = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); break; } } if (IsBardSong(spell_id) && AggroAmount > 40) AggroAmount = 40; // bard songs seem to cap to 40 for most of their spells? if (dispel && target && target->GetHateAmount(this) < 100) AggroAmount += 50; if (spells[spell_id].HateAdded > 0) // overrides the hate (ex. tash) AggroAmount = spells[spell_id].HateAdded; if (GetOwner() && IsPet()) AggroAmount = AggroAmount * RuleI(Aggro, PetSpellAggroMod) / 100; // hate focus ignored on first action for some reason if (!on_hatelist && AggroAmount > 0) { int HateMod = RuleI(Aggro, SpellAggroMod); HateMod += GetFocusEffect(focusSpellHateMod, spell_id); AggroAmount = (AggroAmount * HateMod) / 100; } // initial aggro gets a bonus 100 besides for dispel or hate override // We add this 100 in AddToHateList so we need to account for the oddities here if (dispel && spells[spell_id].HateAdded > 0 && !on_hatelist) AggroAmount -= 100; return AggroAmount + spells[spell_id].bonushate + nonModifiedAggro; }
bool Minion::IsGuardianPet() const { return IsPet() || (m_Properties && m_Properties->Category == SUMMON_CATEGORY_PET); }
bool Client::UseDiscipline(uint32 spell_id, uint32 target) { // Dont let client waste a reuse timer if they can't use the disc if (IsStunned() || IsFeared() || IsMezzed() || IsAmnesiad() || IsPet()) { return(false); } //make sure we have the spell... int r; for(r = 0; r < MAX_PP_DISCIPLINES; r++) { if(m_pp.disciplines.values[r] == spell_id) break; } if(r == MAX_PP_DISCIPLINES) return(false); //not found. //Check the disc timer pTimerType DiscTimer = pTimerDisciplineReuseStart + spells[spell_id].EndurTimerIndex; if(!p_timers.Expired(&database, DiscTimer)) { /*char val1[20]={0};*/ //unused /*char val2[20]={0};*/ //unused uint32 remain = p_timers.GetRemainingTime(DiscTimer); //Message_StringID(0, DISCIPLINE_CANUSEIN, ConvertArray((remain)/60,val1), ConvertArray(remain%60,val2)); Message(0, "You can use this discipline in %d minutes %d seconds.", ((remain)/60), (remain%60)); return(false); } //make sure we can use it.. if(!IsValidSpell(spell_id)) { Message(13, "This tome contains invalid knowledge."); return(false); } //can we use the spell? const SPDat_Spell_Struct &spell = spells[spell_id]; uint8 level_to_use = spell.classes[GetClass() - 1]; if(level_to_use == 255) { Message(13, "Your class cannot learn from this tome."); //should summon them a new one... return(false); } if(level_to_use > GetLevel()) { Message_StringID(13, DISC_LEVEL_USE_ERROR); //should summon them a new one... return(false); } if(GetEndurance() > spell.EndurCost) { SetEndurance(GetEndurance() - spell.EndurCost); } else { Message(11, "You are too fatigued to use this skill right now."); return(false); } if(spell.recast_time > 0) { uint32 reduced_recast = spell.recast_time / 1000; reduced_recast -= CastToClient()->GetFocusEffect(focusReduceRecastTime, spell_id); if(reduced_recast < 0) reduced_recast = 0; CastSpell(spell_id, target, DISCIPLINE_SPELL_SLOT, -1, -1, 0, -1, (uint32)DiscTimer, reduced_recast); if(spells[spell_id].EndurTimerIndex < MAX_DISCIPLINE_TIMERS) { EQApplicationPacket *outapp = new EQApplicationPacket(OP_DisciplineTimer, sizeof(DisciplineTimer_Struct)); DisciplineTimer_Struct *dts = (DisciplineTimer_Struct *)outapp->pBuffer; dts->TimerID = spells[spell_id].EndurTimerIndex; dts->Duration = reduced_recast; QueuePacket(outapp); safe_delete(outapp); } } else { CastSpell(spell_id, target, DISCIPLINE_SPELL_SLOT); } return(true); }
void Aura::ProcessOnGroupMembersPets(Mob *owner) { auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline std::set<int> delayed_remove; bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter // This type can either live on the pet (level 55/70 MAG aura) or on the pet owner (level 85 MAG aura) auto group_member = owner->GetOwnerOrSelf(); if (group_member->IsRaidGrouped() && group_member->IsClient()) { // currently raids are just client, but safety check auto raid = group_member->GetRaid(); if (raid == nullptr) { // well shit owner->RemoveAura(GetID(), false, true); return; } auto group_id = raid->GetGroup(group_member->CastToClient()); // some lambdas so the for loop is less horrible ... auto verify_raid_client_pet = [&raid, &group_id, &group_member, this](Mob *m) { auto idx = raid->GetPlayerIndex(m->GetOwner()->CastToClient()); if (m->GetOwner()->GetID() == group_member->GetID()) { return DistanceSquared(GetPosition(), m->GetPosition()) <= distance; } else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) { return false; } else if (DistanceSquared(GetPosition(), m->GetPosition()) > distance) { return false; } return true; }; auto verify_raid_client_swarm = [&raid, &group_id, &group_member, this](NPC *n) { auto owner = entity_list.GetMob(n->GetSwarmOwner()); if (owner == nullptr) return false; auto idx = raid->GetPlayerIndex(owner->CastToClient()); if (owner->GetID() == group_member->GetID()) { return DistanceSquared(GetPosition(), n->GetPosition()) <= distance; } else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) { return false; } else if (DistanceSquared(GetPosition(), n->GetPosition()) > distance) { return false; } return true; }; for (auto &e : mob_list) { auto mob = e.second; // step 1: check if we're already managing this NPC's buff auto it = casted_on.find(mob->GetID()); if (it != casted_on.end()) { // verify still good! if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner()) { if (!verify_raid_client_pet(mob)) delayed_remove.insert(mob->GetID()); } else if (mob->IsNPC() && mob->IsPetOwnerClient()) { auto npc = mob->CastToNPC(); if (!verify_raid_client_swarm(npc)) delayed_remove.insert(mob->GetID()); } } else { // we're not on it! if (mob->IsClient()) { continue; // never hit client } else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner() && verify_raid_client_pet(mob)) { casted_on.insert(mob->GetID()); if (is_buff) SpellFinished(spell_id, mob); } else if (mob->IsNPC() && mob->IsPetOwnerClient()) { auto npc = mob->CastToNPC(); if (verify_raid_client_swarm(npc)) { casted_on.insert(mob->GetID()); if (is_buff) SpellFinished(spell_id, mob); } } } } } else if (group_member->IsGrouped()) { auto group = group_member->GetGroup(); if (group == nullptr) { // uh oh owner->RemoveAura(GetID(), false, true); return; } // lambdas to make for loop less ugly auto verify_group_pet = [&group, this](Mob *m) { auto owner = m->GetOwner(); if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), m->GetPosition()) <= distance) return true; return false; }; auto verify_group_swarm = [&group, this](NPC *n) { auto owner = entity_list.GetMob(n->GetSwarmOwner()); if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), n->GetPosition()) <= distance) return true; return false; }; for (auto &e : mob_list) { auto mob = e.second; auto it = casted_on.find(mob->GetID()); if (it != casted_on.end()) { // make sure we're still valid if (mob->IsPet()) { if (!verify_group_pet(mob)) delayed_remove.insert(mob->GetID()); } else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo()) { if (!verify_group_swarm(mob->CastToNPC())) delayed_remove.insert(mob->GetID()); } } else { // not on, check if we should be! if (mob->IsClient()) { continue; } else if (mob->IsPet() && verify_group_pet(mob)) { casted_on.insert(mob->GetID()); if (is_buff) SpellFinished(spell_id, mob); } else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo() && verify_group_swarm(mob->CastToNPC())) { casted_on.insert(mob->GetID()); if (is_buff) SpellFinished(spell_id, mob); } } } } else { auto verify_solo = [&group_member, this](Mob *m) { if (m->IsPet() && m->GetOwnerID() == group_member->GetID()) return true; else if (m->IsNPC() && m->CastToNPC()->GetSwarmOwner() == group_member->GetID()) return true; else return false; }; for (auto &e : mob_list) { auto mob = e.second; auto it = casted_on.find(mob->GetID()); bool good = verify_solo(mob); if (it != casted_on.end()) { // make sure still valid if (!good || DistanceSquared(GetPosition(), mob->GetPosition()) > distance) { delayed_remove.insert(mob->GetID()); } } else if (good && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) { casted_on.insert(mob->GetID()); if (is_buff) SpellFinished(spell_id, mob); } } } for (auto &e : delayed_remove) { auto mob = entity_list.GetMob(e); if (mob != nullptr && is_buff) // some auras cast instant spells so no need to remove mob->BuffFadeBySpellIDAndCaster(spell_id, GetID()); casted_on.erase(e); } // so if we have a cast timer and our set isn't empty and timer is disabled we need to enable it if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty()) cast_timer.Start(); if (!cast_timer.Enabled() || !cast_timer.Check()) return; // some auras have to recast (DRU for example, non-buff too) for (auto &e : casted_on) { auto mob = entity_list.GetMob(e); if (mob != nullptr) SpellFinished(spell_id, mob); } }
void Guardian::UpdateAttackPowerAndDamage(bool ranged) { if (ranged) return; float val = 0.0f; float bonusAP = 0.0f; UnitMods unitMod = UNIT_MOD_ATTACK_POWER; if (GetEntry() == ENTRY_IMP) // imp's attack power val = GetStat(STAT_STRENGTH) - 10.0f; else val = 2 * GetStat(STAT_STRENGTH) - 20.0f; Unit* owner = GetOwner(); if (owner && owner->GetTypeId() == TYPEID_PLAYER) { if (IsHunterPet()) //hunter pets benefit from owner's attack power { float mod = 1.0f; //Hunter contribution modifier bonusAP = owner->GetTotalAttackPowerValue(RANGED_ATTACK) * 0.22f * mod; SetBonusDamage(int32(owner->GetTotalAttackPowerValue(RANGED_ATTACK) * 0.1287f * mod)); } else if (IsPetGhoul()) //ghouls benefit from deathknight's attack power (may be summon pet or not) { bonusAP = owner->GetTotalAttackPowerValue(BASE_ATTACK) * 0.22f; SetBonusDamage(int32(owner->GetTotalAttackPowerValue(BASE_ATTACK) * 0.1287f)); } else if (IsSpiritWolf()) //wolf benefit from shaman's attack power { float dmg_multiplier = 0.31f; if (m_owner->GetAuraEffect(63271, 0)) // Glyph of Feral Spirit dmg_multiplier = 0.61f; bonusAP = owner->GetTotalAttackPowerValue(BASE_ATTACK) * dmg_multiplier; SetBonusDamage(int32(owner->GetTotalAttackPowerValue(BASE_ATTACK) * dmg_multiplier)); } //demons benefit from warlocks shadow or fire damage else if (IsPet()) { int32 fire = int32(owner->GetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS + SPELL_SCHOOL_FIRE)) + owner->GetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG + SPELL_SCHOOL_FIRE); int32 shadow = int32(owner->GetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS + SPELL_SCHOOL_SHADOW)) + owner->GetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG + SPELL_SCHOOL_SHADOW); int32 maximum = (fire > shadow) ? fire : shadow; if (maximum < 0) maximum = 0; SetBonusDamage(int32(maximum * 0.15f)); bonusAP = maximum * 0.57f; } //water elementals benefit from mage's frost damage else if (GetEntry() == ENTRY_WATER_ELEMENTAL) { int32 frost = int32(owner->GetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS + SPELL_SCHOOL_FROST)) + owner->GetUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG + SPELL_SCHOOL_FROST); if (frost < 0) frost = 0; SetBonusDamage(int32(frost * 0.4f)); } } SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, val + bonusAP); //in BASE_VALUE of UNIT_MOD_ATTACK_POWER for creatures we store data of meleeattackpower field in DB float base_attPower = GetModifierValue(unitMod, BASE_VALUE) * GetModifierValue(unitMod, BASE_PCT); float attPowerMultiplier = GetModifierValue(unitMod, TOTAL_PCT) - 1.0f; //UNIT_FIELD_(RANGED)_ATTACK_POWER field SetInt32Value(UNIT_FIELD_ATTACK_POWER, (int32)base_attPower); //UNIT_FIELD_(RANGED)_ATTACK_POWER_MULTIPLIER field SetFloatValue(UNIT_FIELD_ATTACK_POWER_MULTIPLIER, attPowerMultiplier); //automatically update weapon damage after attack power modification UpdateDamagePhysical(BASE_ATTACK); }
void Client::ActivateAA(aaID activate){ if (activate < 0 || activate >= aaHighestID) return; if (IsStunned() || IsFeared() || IsMezzed() || IsSilenced() || IsPet() || IsSitting() || GetFeigned()) return; int AATimerID = GetAATimerID(activate); SendAA_Struct* aa2 = nullptr; aaID aaid = activate; uint8 activate_val = GetAA(activate); //this wasn't taking into acct multi tiered act talents before... if (activate_val == 0){ aa2 = zone->FindAA(activate); if (!aa2){ int i; int a; for (i = 1; i<MAX_AA_ACTION_RANKS; i++){ a = activate - i; if (a <= 0) break; aa2 = zone->FindAA(a); if (aa2 != nullptr) break; } } if (aa2){ aaid = (aaID)aa2->id; activate_val = GetAA(aa2->id); } } if (activate_val == 0){ return; } if (aa2) { if (aa2->account_time_required) { if ((Timer::GetTimeSeconds() + account_creation) < aa2->account_time_required) { return; } } } if (!p_timers.Expired(&database, AATimerID + pTimerAAStart)) { uint32 aaremain = p_timers.GetRemainingTime(AATimerID + pTimerAAStart); uint32 aaremain_hr = aaremain / (60 * 60); uint32 aaremain_min = (aaremain / 60) % 60; uint32 aaremain_sec = aaremain % 60; if (aa2) { if (aaremain_hr >= 1) //1 hour or more Message(CC_Red, "You can use the ability %s again in %u hour(s) %u minute(s) %u seconds", aa2->name, aaremain_hr, aaremain_min, aaremain_sec); else //less than an hour Message(CC_Red, "You can use the ability %s again in %u minute(s) %u seconds", aa2->name, aaremain_min, aaremain_sec); } else { if (aaremain_hr >= 1) //1 hour or more Message(CC_Red, "You can use this ability again in %u hour(s) %u minute(s) %u seconds", aaremain_hr, aaremain_min, aaremain_sec); else //less than an hour Message(CC_Red, "You can use this ability again in %u minute(s) %u seconds", aaremain_min, aaremain_sec); } return; } if (activate_val > MAX_AA_ACTION_RANKS) activate_val = MAX_AA_ACTION_RANKS; activate_val--; //to get array index. //get our current node, now that the indices are well bounded const AA_DBAction *caa = &AA_Actions[aaid][activate_val]; if ((aaid == aaImprovedHarmTouch || aaid == aaLeechTouch) && !p_timers.Expired(&database, pTimerHarmTouch)){ Message(CC_Red, "Ability recovery time not yet met."); return; } //everything should be configured out now uint16 target_id = 0; //figure out our target switch (caa->target) { case aaTargetUser: case aaTargetGroup: target_id = GetID(); break; case aaTargetCurrent: case aaTargetCurrentGroup: if (GetTarget() == nullptr) { Message_StringID(MT_DefaultText, AA_NO_TARGET); //You must first select a target for this ability! p_timers.Clear(&database, AATimerID + pTimerAAStart); return; } target_id = GetTarget()->GetID(); break; case aaTargetPet: if (GetPet() == nullptr) { Message(0, "A pet is required for this skill."); return; } target_id = GetPetID(); break; } //handle non-spell action if (caa->action != aaActionNone) { if (caa->mana_cost > 0) { if (GetMana() < caa->mana_cost) { Message_StringID(CC_Red, INSUFFICIENT_MANA); return; } SetMana(GetMana() - caa->mana_cost); } if (caa->reuse_time > 0) { uint32 timer_base = CalcAAReuseTimer(caa); if (activate == aaImprovedHarmTouch || activate == aaLeechTouch) { p_timers.Start(pTimerHarmTouch, HarmTouchReuseTime); } p_timers.Start(AATimerID + pTimerAAStart, timer_base); SendAATimer(activate, static_cast<uint32>(time(nullptr)), static_cast<uint32>(time(nullptr))); } HandleAAAction(aaid); } //cast the spell, if we have one if (caa->spell_id > 0 && caa->spell_id < SPDAT_RECORDS) { if (caa->reuse_time > 0) { uint32 timer_base = CalcAAReuseTimer(caa); SendAATimer(activate, static_cast<uint32>(time(nullptr)), static_cast<uint32>(time(nullptr))); p_timers.Start(AATimerID + pTimerAAStart, timer_base); if (activate == aaImprovedHarmTouch || activate == aaLeechTouch) { p_timers.Start(pTimerHarmTouch, HarmTouchReuseTime); } // Bards can cast instant cast AAs while they are casting another song if (spells[caa->spell_id].cast_time == 0 && GetClass() == BARD && IsBardSong(casting_spell_id)) { if (!SpellFinished(caa->spell_id, entity_list.GetMob(target_id), 10, -1, -1, spells[caa->spell_id].ResistDiff, false)) { //Reset on failed cast SendAATimer(activate, 0, 0xFFFFFF); Message_StringID(CC_Yellow, ABILITY_FAILED); p_timers.Clear(&database, AATimerID + pTimerAAStart); return; } } else { if (!CastSpell(caa->spell_id, target_id, USE_ITEM_SPELL_SLOT, -1, -1, 0, -1, AATimerID + pTimerAAStart, timer_base, 1)) { //Reset on failed cast SendAATimer(activate, 0, 0xFFFFFF); Message_StringID(CC_Yellow, ABILITY_FAILED); p_timers.Clear(&database, AATimerID + pTimerAAStart); return; } } } else { if (!CastSpell(caa->spell_id, target_id)) return; } } }
Mob* EntityList::AICheckCloseAggro(Mob* sender, float iAggroRange, float iAssistRange) { if (!sender || !sender->IsNPC()) return(nullptr); #ifdef REVERSE_AGGRO //with reverse aggro, npc->client is checked elsewhere, no need to check again auto it = npc_list.begin(); while (it != npc_list.end()) { #else auto it = mob_list.begin(); while (it != mob_list.end()) { #endif Mob *mob = it->second; if (sender->CheckWillAggro(mob)) return mob; ++it; } //LogFile->write(EQEMuLog::Debug, "Check aggro for %s no target.", sender->GetName()); return nullptr; } int EntityList::GetHatedCount(Mob *attacker, Mob *exclude) { // Return a list of how many non-feared, non-mezzed, non-green mobs, within aggro range, hate *attacker if (!attacker) return 0; int Count = 0; for (auto it = npc_list.begin(); it != npc_list.end(); ++it) { NPC *mob = it->second; if (!mob || (mob == exclude)) continue; if (!mob->IsEngaged()) continue; if (mob->IsFeared() || mob->IsMezzed()) continue; if (attacker->GetLevelCon(mob->GetLevel()) == CON_GREEN) continue; if (!mob->CheckAggro(attacker)) continue; float AggroRange = mob->GetAggroRange(); // Square it because we will be using DistNoRoot AggroRange *= AggroRange; if (mob->DistNoRoot(*attacker) > AggroRange) continue; Count++; } return Count; } int EntityList::GetHatedCountByFaction(Mob *attacker, Mob *exclude) { // Return a list of how many non-feared, non-mezzed, non-green mobs, within aggro range, hate *attacker if (!attacker) return 0; int Count = 0; for (auto it = npc_list.begin(); it != npc_list.end(); ++it) { NPC *mob = it->second; if (!mob || (mob == exclude)) continue; if (!mob->IsEngaged()) continue; if (mob->IsFeared() || mob->IsMezzed()) continue; //if (attacker->GetLevelCon(mob->GetLevel()) == CON_GREEN) // continue; if (!mob->CheckAggro(attacker)) continue; float AggroRange = mob->GetAggroRange(); // Square it because we will be using DistNoRoot AggroRange *= AggroRange; if (mob->DistNoRoot(*attacker) > AggroRange) continue; // If exclude doesn't have a faction, check for buddies based on race. if(exclude->GetPrimaryFaction() != 0) { if (mob->GetPrimaryFaction() != exclude->GetPrimaryFaction()) continue; } else { if (mob->GetBaseRace() != exclude->GetBaseRace()) continue; } Count++; } return Count; } void EntityList::AIYellForHelp(Mob* sender, Mob* attacker) { if(!sender || !attacker) return; if (sender->GetPrimaryFaction() == 0 ) return; // well, if we dont have a faction set, we're gonna be indiff to everybody for (auto it = npc_list.begin(); it != npc_list.end(); ++it) { NPC *mob = it->second; if (!mob) continue; float r = mob->GetAssistRange(); r = r * r; if ( mob != sender && mob != attacker // && !mob->IsCorpse() // && mob->IsAIControlled() && mob->GetPrimaryFaction() != 0 && mob->DistNoRoot(*sender) <= r && !mob->IsEngaged() && ((!mob->IsPet()) || (mob->IsPet() && mob->GetOwner() && !mob->GetOwner()->IsClient())) // If we're a pet we don't react to any calls for help if our owner is a client ) { //if they are in range, make sure we are not green... //then jump in if they are our friend if(attacker->GetLevelCon(mob->GetLevel()) != CON_GREEN) { // AK Style pet pulling. if(attacker->IsPet() && mob->GetLevelCon(attacker->GetLevel()) == CON_GREEN) { return; } bool useprimfaction = false; if(mob->GetPrimaryFaction() == sender->CastToNPC()->GetPrimaryFaction()) { const NPCFactionList *cf = database.GetNPCFactionEntry(mob->GetNPCFactionID()); if(cf){ if(cf->assistprimaryfaction != 0) useprimfaction = true; } } if(useprimfaction || sender->GetReverseFactionCon(mob) <= FACTION_AMIABLE ) { //attacking someone on same faction, or a friend //Father Nitwit: make sure we can see them. if(mob->CheckLosFN(sender)) { #if (EQDEBUG>=11) LogFile->write(EQEMuLog::Debug, "AIYellForHelp(\"%s\",\"%s\") %s attacking %s Dist %f Z %f", sender->GetName(), attacker->GetName(), mob->GetName(), attacker->GetName(), mob->DistNoRoot(*sender), fabs(sender->GetZ()+mob->GetZ())); #endif mob->AddToHateList(attacker, 1, 0, false); } } } } } } /* solar: returns false if attack should not be allowed I try to list every type of conflict that's possible here, so it's easy to see how the decision is made. Yea, it could be condensed and made faster, but I'm doing it this way to make it readable and easy to modify */ bool Mob::IsAttackAllowed(Mob *target, bool isSpellAttack, int16 spellid) { Mob *mob1, *mob2, *tempmob; Client *c1, *c2, *becomenpc; // NPC *npc1, *npc2; int reverse; if(!zone->CanDoCombat()) return false; // some special cases if(!target) return false; if(this == target) // you can attack yourself return true; //Lifetap on Eye of Zomm. if(target->IsNPC() && target->GetRace() == EYE_OF_ZOMM && IsLifetapSpell(spellid)) return true; if(target->GetSpecialAbility(NO_HARM_FROM_CLIENT)){ return false; } // Pets cant attack mezed mobs if(IsPet() && GetOwner()->IsClient() && target->IsMezzed()) { return false; } // can't damage own pet (applies to everthing) Mob *target_owner = target->GetOwner(); Mob *our_owner = GetOwner(); if(target_owner && target_owner == this) return false; else if(our_owner && our_owner == target) return false; // invalidate for swarm pets for later on if their owner is a corpse if (IsNPC() && CastToNPC()->GetSwarmInfo() && our_owner && our_owner->IsCorpse() && !our_owner->IsPlayerCorpse()) our_owner = nullptr; if (target->IsNPC() && target->CastToNPC()->GetSwarmInfo() && target_owner && target_owner->IsCorpse() && !target_owner->IsPlayerCorpse()) target_owner = nullptr; //cannot hurt untargetable mobs bodyType bt = target->GetBodyType(); if(bt == BT_NoTarget || bt == BT_NoTarget2) { if (RuleB(Pets, UnTargetableSwarmPet)) { if (target->IsNPC()) { if (!target->CastToNPC()->GetSwarmOwner()) { return(false); } } else { return(false); } } else { return(false); } } if(!isSpellAttack) { if(GetClass() == LDON_TREASURE) { return false; } } // solar: the format here is a matrix of mob type vs mob type. // redundant ones are omitted and the reverse is tried if it falls through. // first figure out if we're pets. we always look at the master's flags. // no need to compare pets to anything mob1 = our_owner ? our_owner : this; mob2 = target_owner ? target_owner : target; reverse = 0; do { if(_CLIENT(mob1)) { if(_CLIENT(mob2)) // client vs client { c1 = mob1->CastToClient(); c2 = mob2->CastToClient(); if // if both are pvp they can fight ( c1->GetPVP() && c2->GetPVP() ) return true; else if // if they're dueling they can go at it ( c1->IsDueling() && c2->IsDueling() && c1->GetDuelTarget() == c2->GetID() && c2->GetDuelTarget() == c1->GetID() ) return true; else return false; } else if(_NPC(mob2)) // client vs npc { return true; } else if(_BECOMENPC(mob2)) // client vs becomenpc { c1 = mob1->CastToClient(); becomenpc = mob2->CastToClient(); if(c1->GetLevel() > becomenpc->GetBecomeNPCLevel()) return false; else return true; } else if(_CLIENTCORPSE(mob2)) // client vs client corpse { return false; } else if(_NPCCORPSE(mob2)) // client vs npc corpse { return false; } } else if(_NPC(mob1)) { if(_NPC(mob2)) // npc vs npc { /* this says that an NPC can NEVER attack a faction ally... this is stupid... somebody else should check this rule if they want to enforce it, this just says 'can they possibly fight based on their type', in which case, the answer is yes. */ /* npc1 = mob1->CastToNPC(); npc2 = mob2->CastToNPC(); if ( npc1->GetPrimaryFaction() != 0 && npc2->GetPrimaryFaction() != 0 && ( npc1->GetPrimaryFaction() == npc2->GetPrimaryFaction() || npc1->IsFactionListAlly(npc2->GetPrimaryFaction()) ) ) return false; else */ return true; } else if(_BECOMENPC(mob2)) // npc vs becomenpc { return true; } else if(_CLIENTCORPSE(mob2)) // npc vs client corpse { return false; } else if(_NPCCORPSE(mob2)) // npc vs npc corpse { return false; } } else if(_BECOMENPC(mob1)) { if(_BECOMENPC(mob2)) // becomenpc vs becomenpc { return true; } else if(_CLIENTCORPSE(mob2)) // becomenpc vs client corpse { return false; } else if(_NPCCORPSE(mob2)) // becomenpc vs npc corpse { return false; } } else if(_CLIENTCORPSE(mob1)) { if(_CLIENTCORPSE(mob2)) // client corpse vs client corpse { return false; } else if(_NPCCORPSE(mob2)) // client corpse vs npc corpse { return false; } } else if(_NPCCORPSE(mob1)) { if(_NPCCORPSE(mob2)) // npc corpse vs npc corpse { return false; } } // we fell through, now we swap the 2 mobs and run through again once more tempmob = mob1; mob1 = mob2; mob2 = tempmob; } while( reverse++ == 0 ); LogFile->write(EQEMuLog::Debug, "Mob::IsAttackAllowed: don't have a rule for this - %s vs %s\n", this->GetName(), target->GetName()); return false; }
//offensive spell aggro int32 Mob::CheckAggroAmount(uint16 spell_id, bool isproc) { int32 AggroAmount = 0; int32 nonModifiedAggro = 0; uint16 slevel = GetLevel(); for (int o = 0; o < EFFECT_COUNT; o++) { switch (spells[spell_id].effectid[o]) { case SE_CurrentHPOnce: case SE_CurrentHP: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if(val < 0) AggroAmount -= val; break; } case SE_MovementSpeed: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount += (2 + ((slevel * slevel) / 8)); break; } case SE_AttackSpeed: case SE_AttackSpeed2: case SE_AttackSpeed3: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 100) AggroAmount += (5 + ((slevel * slevel) / 5)); break; } case SE_Stun: { int val = (5 + ((slevel * slevel) / 6)); if (isproc && RuleI(Aggro,MaxStunProcAggro) > -1 && (val > RuleI(Aggro,MaxStunProcAggro))) val = RuleI(Aggro,MaxStunProcAggro); AggroAmount += val; break; } case SE_Blind: { AggroAmount += (5 + ((slevel * slevel) / 6)); break; } case SE_Mez: { AggroAmount += (5 + ((slevel * slevel) / 5)); break; } case SE_Charm: { AggroAmount += (5 + ((slevel * slevel) / 5)); break; } case SE_Root: { AggroAmount += (2 + ((slevel * slevel) / 8)); break; } case SE_Fear: { AggroAmount += (5 + ((slevel * slevel) / 6)); break; } case SE_ATK: case SE_ACv2: case SE_ArmorClass: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount -= val * 2; break; } case SE_ResistMagic: case SE_ResistFire: case SE_ResistCold: case SE_ResistPoison: case SE_ResistDisease: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount -= val * 3; break; } case SE_ResistAll: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount -= val * 6; break; } case SE_STR: case SE_STA: case SE_DEX: case SE_AGI: case SE_INT: case SE_WIS: case SE_CHA: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount -= val * 2; break; } case SE_AllStats: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount -= val * 6; break; } case SE_BardAEDot: { AggroAmount += slevel * 2; break; } case SE_SpinTarget: { AggroAmount += (5 + ((slevel * slevel) / 5)); break; } case SE_Amnesia: case SE_Silence: { AggroAmount += slevel * 2; break; } case SE_Destroy: { AggroAmount += slevel * 2; break; } case SE_Harmony: case SE_CastingLevel: case SE_MeleeMitigation: case SE_CriticalHitChance: case SE_AvoidMeleeChance: case SE_RiposteChance: case SE_DodgeChance: case SE_ParryChance: case SE_DualWieldChance: case SE_DoubleAttackChance: case SE_MeleeSkillCheck: case SE_HitChance: case SE_IncreaseArchery: case SE_DamageModifier: case SE_MinDamageModifier: case SE_IncreaseBlockChance: case SE_Accuracy: case SE_DamageShield: case SE_SpellDamageShield: case SE_ReverseDS: { AggroAmount += slevel * 2; break; } case SE_CurrentMana: case SE_ManaRegen_v2: case SE_ManaPool: case SE_CurrentEndurance: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount -= val * 2; break; } case SE_CancelMagic: case SE_DispelDetrimental: { AggroAmount += slevel; break; } case SE_ReduceHate: case SE_InstantHate: { nonModifiedAggro = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); break; } } } if (IsAEDurationSpell(spell_id)) AggroAmount /= 2; if (spells[spell_id].HateAdded > 0) AggroAmount = spells[spell_id].HateAdded; if (IsBardSong(spell_id)) AggroAmount = AggroAmount * RuleI(Aggro, SongAggroMod) / 100; if (GetOwner() && IsPet()) AggroAmount = AggroAmount * RuleI(Aggro, PetSpellAggroMod) / 100; if (AggroAmount > 0) { int HateMod = RuleI(Aggro, SpellAggroMod); if (IsClient()) HateMod += CastToClient()->GetFocusEffect(focusSpellHateMod, spell_id); AggroAmount = (AggroAmount * HateMod) / 100; //made up number probably scales a bit differently on live but it seems like it will be close enough //every time you cast on live you get a certain amount of "this is a spell" aggro //confirmed by EQ devs to be 100 exactly at level 85. From their wording it doesn't seem like it's affected //by hate modifiers either. //AggroAmount += (slevel*slevel/72); // Saved so I can reimplement it; // this should only be on the spell to aggro the npc not every spell } return AggroAmount + spells[spell_id].bonushate + nonModifiedAggro; }