//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); }
//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; }
// checks if this spell can be targeted bool IsTGBCompatibleSpell(uint16 spell_id) { if (IsValidSpell(spell_id) && (!IsDetrimentalSpell(spell_id) && spells[spell_id].buffduration != 0 && !IsBardSong(spell_id) && !IsEffectInSpell(spell_id, SE_Illusion))) return true; return false; }
int32 Client::GetActSpellDuration(uint16 spell_id, int32 duration) { if (spells[spell_id].not_extendable) return duration; int increase = 100; increase += GetFocusEffect(focusSpellDuration, spell_id); int tic_inc = 0; tic_inc = GetFocusEffect(focusSpellDurByTic, spell_id); // Only need this for clients, since the change was for bard songs, I assume we should keep non bard songs getting +1 // However if its bard or not and is mez, charm or fear, we need to add 1 so that client is in sync if (!(IsShortDurationBuff(spell_id) && IsBardSong(spell_id)) || IsFearSpell(spell_id) || IsCharmSpell(spell_id) || IsMezSpell(spell_id) || IsBlindSpell(spell_id)) tic_inc += 1; return (((duration * increase) / 100) + tic_inc); }
//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; }
//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; }
void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { AA::Rank *rank = zone->GetAlternateAdvancementRank(rank_id); if(!rank) { return; } AA::Ability *ability = rank->base_ability; if(!ability) { return; } if(!IsValidSpell(rank->spell)) { return; } if(!CanUseAlternateAdvancementRank(rank)) { return; } //make sure it is not a passive if(!rank->effects.empty()) { return; } uint32 charges = 0; // We don't have the AA if (!GetAA(rank_id, &charges)) return; //if expendable make sure we have charges if(ability->charges > 0 && charges < 1) return; //check cooldown if(!p_timers.Expired(&database, rank->spell_type + pTimerAAStart, false)) { uint32 aaremain = p_timers.GetRemainingTime(rank->spell_type + pTimerAAStart); uint32 aaremain_hr = aaremain / (60 * 60); uint32 aaremain_min = (aaremain / 60) % 60; uint32 aaremain_sec = aaremain % 60; if(aaremain_hr >= 1) { Message(13, "You can use this ability again in %u hour(s) %u minute(s) %u seconds", aaremain_hr, aaremain_min, aaremain_sec); } else { Message(13, "You can use this ability again in %u minute(s) %u seconds", aaremain_min, aaremain_sec); } return; } //calculate cooldown int cooldown = rank->recast_time - GetAlternateAdvancementCooldownReduction(rank); if(cooldown < 0) { cooldown = 0; } if (!IsCastWhileInvis(rank->spell)) CommonBreakInvisible(); if (spells[rank->spell].sneak && (!hidden || (hidden && (Timer::GetCurrentTime() - tmHidden) < 4000))) { Message_StringID(MT_SpellFailure, SNEAK_RESTRICT); return; } // // Modern clients don't require pet targeted for AA casts that are ST_Pet if (spells[rank->spell].targettype == ST_Pet || spells[rank->spell].targettype == ST_SummonedPet) target_id = GetPetID(); // extra handling for cast_not_standing spells if (!spells[rank->spell].cast_not_standing) { if (GetAppearance() == eaSitting) // we need to stand! SetAppearance(eaStanding, false); if (GetAppearance() != eaStanding) { Message_StringID(MT_SpellFailure, STAND_TO_CAST); return; } } // Bards can cast instant cast AAs while they are casting another song if(spells[rank->spell].cast_time == 0 && GetClass() == BARD && IsBardSong(casting_spell_id)) { if(!SpellFinished(rank->spell, entity_list.GetMob(target_id), EQEmu::spells::CastingSlot::AltAbility, spells[rank->spell].mana, -1, spells[rank->spell].ResistDiff, false)) { return; } ExpendAlternateAdvancementCharge(ability->id); } else { if(!CastSpell(rank->spell, target_id, EQEmu::spells::CastingSlot::AltAbility, -1, -1, 0, -1, rank->spell_type + pTimerAAStart, cooldown, nullptr, rank->id)) { return; } } CastToClient()->GetPTimers().Start(rank->spell_type + pTimerAAStart, cooldown); SendAlternateAdvancementTimer(rank->spell_type, 0, 0); }
void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { AA::Rank *rank = zone->GetAlternateAdvancementRank(rank_id); if(!rank) { return; } AA::Ability *ability = rank->base_ability; if(!ability) { return; } if(!IsValidSpell(rank->spell)) { return; } if(!CanUseAlternateAdvancementRank(rank)) { return; } //make sure it is not a passive if(!rank->effects.empty()) { return; } uint32 charges = 0; // We don't have the AA if (!GetAA(rank_id, &charges)) return; //if expendable make sure we have charges if(ability->charges > 0 && charges < 1) return; //check cooldown if(!p_timers.Expired(&database, rank->spell_type + pTimerAAStart)) { uint32 aaremain = p_timers.GetRemainingTime(rank->spell_type + pTimerAAStart); uint32 aaremain_hr = aaremain / (60 * 60); uint32 aaremain_min = (aaremain / 60) % 60; uint32 aaremain_sec = aaremain % 60; if(aaremain_hr >= 1) { Message(13, "You can use this ability again in %u hour(s) %u minute(s) %u seconds", aaremain_hr, aaremain_min, aaremain_sec); } else { Message(13, "You can use this ability again in %u minute(s) %u seconds", aaremain_min, aaremain_sec); } return; } //calculate cooldown int cooldown = rank->recast_time - GetAlternateAdvancementCooldownReduction(rank); if(cooldown < 0) { cooldown = 0; } if (!IsCastWhileInvis(rank->spell)) CommonBreakInvisible(); // Bards can cast instant cast AAs while they are casting another song if(spells[rank->spell].cast_time == 0 && GetClass() == BARD && IsBardSong(casting_spell_id)) { if(!SpellFinished(rank->spell, entity_list.GetMob(target_id), ALTERNATE_ABILITY_SPELL_SLOT, spells[rank->spell].mana, -1, spells[rank->spell].ResistDiff, false)) { return; } ExpendAlternateAdvancementCharge(ability->id); } else { if(!CastSpell(rank->spell, target_id, ALTERNATE_ABILITY_SPELL_SLOT, -1, -1, 0, -1, rank->spell_type + pTimerAAStart, cooldown, nullptr, rank->id)) { return; } } CastToClient()->GetPTimers().Start(rank->spell_type + pTimerAAStart, cooldown); SendAlternateAdvancementTimer(rank->spell_type, 0, 0); }
uint32 Mob::GetInstrumentMod(uint16 spell_id) const { if (GetClass() != BARD || spells[spell_id].IsDisciplineBuff) // Puretone is Singing but doesn't get any mod return 10; uint32 effectmod = 10; int effectmodcap = 0; bool nocap = false; if (RuleB(Character, UseSpellFileSongCap)) { effectmodcap = spells[spell_id].songcap / 10; // this looks a bit weird, but easiest way I could think to keep both systems working if (effectmodcap == 0) nocap = true; else effectmodcap += 10; } else { effectmodcap = RuleI(Character, BaseInstrumentSoftCap); } // this should never use spell modifiers... // if a spell grants better modifers, they are copied into the item mods // because the spells are supposed to act just like having the intrument. // item mods are in 10ths of percent increases // clickies (Symphony of Battle) that have a song skill don't get AA bonus for some reason // but clickies that are songs (selo's on Composers Greaves) do get AA mod as well switch (spells[spell_id].skill) { case SkillPercussionInstruments: if (itembonuses.percussionMod == 0 && spellbonuses.percussionMod == 0) effectmod = 10; else if (GetSkill(SkillPercussionInstruments) == 0) effectmod = 10; else if (itembonuses.percussionMod > spellbonuses.percussionMod) effectmod = itembonuses.percussionMod; else effectmod = spellbonuses.percussionMod; if (IsBardSong(spell_id)) effectmod += aabonuses.percussionMod; break; case SkillStringedInstruments: if (itembonuses.stringedMod == 0 && spellbonuses.stringedMod == 0) effectmod = 10; else if (GetSkill(SkillStringedInstruments) == 0) effectmod = 10; else if (itembonuses.stringedMod > spellbonuses.stringedMod) effectmod = itembonuses.stringedMod; else effectmod = spellbonuses.stringedMod; if (IsBardSong(spell_id)) effectmod += aabonuses.stringedMod; break; case SkillWindInstruments: if (itembonuses.windMod == 0 && spellbonuses.windMod == 0) effectmod = 10; else if (GetSkill(SkillWindInstruments) == 0) effectmod = 10; else if (itembonuses.windMod > spellbonuses.windMod) effectmod = itembonuses.windMod; else effectmod = spellbonuses.windMod; if (IsBardSong(spell_id)) effectmod += aabonuses.windMod; break; case SkillBrassInstruments: if (itembonuses.brassMod == 0 && spellbonuses.brassMod == 0) effectmod = 10; else if (GetSkill(SkillBrassInstruments) == 0) effectmod = 10; else if (itembonuses.brassMod > spellbonuses.brassMod) effectmod = itembonuses.brassMod; else effectmod = spellbonuses.brassMod; if (IsBardSong(spell_id)) effectmod += aabonuses.brassMod; break; case SkillSinging: if (itembonuses.singingMod == 0 && spellbonuses.singingMod == 0) effectmod = 10; else if (itembonuses.singingMod > spellbonuses.singingMod) effectmod = itembonuses.singingMod; else effectmod = spellbonuses.singingMod; if (IsBardSong(spell_id)) effectmod += aabonuses.singingMod + spellbonuses.Amplification; break; default: effectmod = 10; return effectmod; } if (!RuleB(Character, UseSpellFileSongCap)) effectmodcap += aabonuses.songModCap + spellbonuses.songModCap + itembonuses.songModCap; if (effectmod < 10) effectmod = 10; if (!nocap && effectmod > effectmodcap) // if the cap is calculated to be 0 using new rules, no cap. effectmod = effectmodcap; Log.Out(Logs::Detail, Logs::Spells, "%s::GetInstrumentMod() spell=%d mod=%d modcap=%d\n", GetName(), spell_id, effectmod, effectmodcap); return effectmod; }
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; } } }