diff --git a/common/spdat.cpp b/common/spdat.cpp index 6c6e7dcd7..132b74523 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -1282,3 +1282,14 @@ const char* GetSpellName(uint16 spell_id) return spells[spell_id].name; } +bool SpellRequiresTarget(int spell_id) +{ + if (spells[spell_id].targettype == ST_AEClientV1 || + spells[spell_id].targettype == ST_Self || + spells[spell_id].targettype == ST_AECaster || + spells[spell_id].targettype == ST_Ring || + spells[spell_id].targettype == ST_Beam) { + return false; + } + return true; +} diff --git a/common/spdat.h b/common/spdat.h index c41c22347..ea8524f82 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -1151,6 +1151,7 @@ bool IsStackableDot(uint16 spell_id); bool IsBardOnlyStackEffect(int effect); bool IsCastWhileInvis(uint16 spell_id); bool IsEffectIgnoredInStacking(int spa); +bool SpellRequiresTarget(int targettype); int CalcPetHp(int levelb, int classb, int STA = 75); int GetSpellEffectDescNum(uint16 spell_id); diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 92bd537b9..f1106fdf5 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1065,19 +1065,6 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } break; - case SE_TriggerOnCast: - - for (int i = 0; i < MAX_SPELL_TRIGGER; i++) { - if (newbon->SpellTriggers[i] == rank.id) - break; - - if (!newbon->SpellTriggers[i]) { - // Save the 'rank.id' of each triggerable effect to an array - newbon->SpellTriggers[i] = rank.id; - break; - } - } - break; case SE_CriticalHitChance: { // Bad data or unsupported new skill @@ -2538,19 +2525,6 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } - case SE_TriggerOnCast: - { - for(int e = 0; e < MAX_SPELL_TRIGGER; e++) - { - if(!new_bonus->SpellTriggers[e]) - { - new_bonus->SpellTriggers[e] = spell_id; - break; - } - } - break; - } - case SE_SpellCritChance: new_bonus->CriticalSpellChance += effect_value; break; @@ -3816,8 +3790,7 @@ uint8 Mob::IsFocusEffect(uint16 spell_id,int effect_index, bool AA,uint32 aa_eff case SE_ReduceReuseTimer: return focusReduceRecastTime; case SE_TriggerOnCast: - //return focusTriggerOnCast; - return 0; //This is calculated as an actual bonus + return focusTriggerOnCast; case SE_FcSpellVulnerability: return focusSpellVulnerability; case SE_Fc_Spell_Damage_Pct_IncomingPC: @@ -4433,17 +4406,6 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) break; } - case SE_TriggerOnCast: - { - for(int e = 0; e < MAX_SPELL_TRIGGER; e++) - { - spellbonuses.SpellTriggers[e] = effect_value; - aabonuses.SpellTriggers[e] = effect_value; - itembonuses.SpellTriggers[e] = effect_value; - } - break; - } - case SE_SpellCritChance: spellbonuses.CriticalSpellChance = effect_value; aabonuses.CriticalSpellChance = effect_value; diff --git a/zone/command.cpp b/zone/command.cpp index a02da9ab1..665b2098b 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -359,6 +359,7 @@ int command_init(void) command_add("repop", "[delay] - Repop the zone with optional delay", 100, command_repop) || command_add("resetaa", "- Resets a Player's AA in their profile and refunds spent AA's to unspent, may disconnect player.", 200, command_resetaa) || command_add("resetaa_timer", "Command to reset AA cooldown timers.", 200, command_resetaa_timer) || + command_add("resetdisc_timer", "Command to reset all discipline cooldown timers.", 200, command_resetdisc_timer) || command_add("revoke", "[charname] [1/0] - Makes charname unable to talk on OOC", 200, command_revoke) || command_add("roambox", "Manages roambox settings for an NPC", 200, command_roambox) || command_add("rules", "(subcommand) - Manage server rules", 250, command_rules) || @@ -8121,31 +8122,31 @@ void command_summonitem(Client *c, const Seperator *sep) ).c_str() ); } - + if (arguments >= 2 && sep->IsNumber(2)) { charges = atoi(sep->arg[2]); } - + if (arguments >= 3 && sep->IsNumber(3)) { augment_one = atoi(sep->arg[3]); } - + if (arguments >= 4 && sep->IsNumber(4)) { augment_two = atoi(sep->arg[4]); } - + if (arguments >= 5 && sep->IsNumber(5)) { augment_three = atoi(sep->arg[5]); } - + if (arguments >= 6 && sep->IsNumber(6)) { augment_four = atoi(sep->arg[6]); } - + if (arguments >= 7 && sep->IsNumber(7)) { augment_five = atoi(sep->arg[7]); } - + if (arguments == 8 && sep->IsNumber(8)) { augment_six = atoi(sep->arg[8]); } @@ -8189,7 +8190,7 @@ void command_giveitem(Client *c, const Seperator *sep) c->Message(Chat::Red, "Usage: #giveitem [item id | link] [charges] [augment_one_id] [augment_two_id] [augment_three_id] [augment_four_id] [augment_five_id] [augment_six_id] (Charges are optional.)"); return; } - + Client *client_target = c->GetTarget()->CastToClient(); uint8 item_status = 0; uint8 current_status = c->Admin(); @@ -8197,7 +8198,7 @@ void command_giveitem(Client *c, const Seperator *sep) if (item) { item_status = item->MinStatus; } - + if (item_status > current_status) { c->Message( Chat::White, @@ -8209,23 +8210,23 @@ void command_giveitem(Client *c, const Seperator *sep) ); return; } - + if (arguments >= 2 && sep->IsNumber(2)) { charges = atoi(sep->arg[2]); } - + if (arguments >= 3 && sep->IsNumber(3)) { augment_one = atoi(sep->arg[3]); } - + if (arguments >= 4 && sep->IsNumber(4)) { augment_two = atoi(sep->arg[4]); } - + if (arguments >= 5 && sep->IsNumber(5)) { augment_three = atoi(sep->arg[5]); } - + if (arguments >= 6 && sep->IsNumber(6)) { augment_four = atoi(sep->arg[6]); } @@ -13725,6 +13726,27 @@ void command_resetaa_timer(Client *c, const Seperator *sep) { } } +void command_resetdisc_timer(Client *c, const Seperator *sep) +{ + Client *target = c->GetTarget()->CastToClient(); + if (!c->GetTarget() || !c->GetTarget()->IsClient()) { + target = c; + } + + if (sep->IsNumber(1)) { + int timer_id = atoi(sep->arg[1]); + c->Message(Chat::White, "Reset of disc timer %i for %s", timer_id, c->GetName()); + c->ResetDisciplineTimer(timer_id); + } + else if (!strcasecmp(sep->arg[1], "all")) { + c->Message(Chat::White, "Reset all disc timers for %s", c->GetName()); + c->ResetAllDisciplineTimers(); + } + else { + c->Message(Chat::White, "usage: #resetdisc_timer [all | timer_id]"); + } +} + void command_reloadaa(Client *c, const Seperator *sep) { c->Message(Chat::White, "Reloading Alternate Advancement Data..."); zone->LoadAlternateAdvancement(); diff --git a/zone/command.h b/zone/command.h index bc9d06ff8..cb3f5ea04 100644 --- a/zone/command.h +++ b/zone/command.h @@ -256,6 +256,7 @@ void command_reloadzps(Client *c, const Seperator *sep); void command_repop(Client *c, const Seperator *sep); void command_resetaa(Client* c,const Seperator *sep); void command_resetaa_timer(Client *c, const Seperator *sep); +void command_resetdisc_timer(Client *c, const Seperator *sep); void command_revoke(Client *c, const Seperator *sep); void command_roambox(Client *c, const Seperator *sep); void command_rules(Client *c, const Seperator *sep); diff --git a/zone/mob.cpp b/zone/mob.cpp index 60d8d8839..ec4c719a9 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -3488,59 +3488,6 @@ void Mob::SetNimbusEffect(uint32 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 - auto rank = zone->GetAlternateAdvancementRank(focus_spell); - if (rank) - trigger_spell_id = CastToClient()->CalcAAFocus(focusTriggerOnCast, *rank, spell_id); - - if (IsValidSpell(trigger_spell_id) && GetTarget()) - SpellFinished(trigger_spell_id, GetTarget(), EQ::spells::CastingSlot::Item, 0, -1, - spells[trigger_spell_id].ResistDiff); - } - - else { - trigger_spell_id = CalcFocusEffect(focusTriggerOnCast, focus_spell, spell_id); - - if (IsValidSpell(trigger_spell_id) && GetTarget()) { - SpellFinished(trigger_spell_id, GetTarget(), EQ::spells::CastingSlot::Item, 0, -1, - spells[trigger_spell_id].ResistDiff); - CheckNumHitsRemaining(NumHit::MatchingSpells, -1, focus_spell); - } - } -} - - - - bool Mob::TrySpellTrigger(Mob *target, uint32 spell_id, int effect) { if (!target || !IsValidSpell(spell_id)) diff --git a/zone/mob.h b/zone/mob.h index 6bc9b8a8f..128c99f55 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -791,8 +791,8 @@ public: bool TryDeathSave(); bool TryDivineSave(); void DoBuffWearOffEffect(uint32 index); - void TryTriggerOnCast(uint32 spell_id, bool aa_trigger); - void TriggerOnCast(uint32 focus_spell, uint32 spell_id, bool aa_trigger); + void TryTriggerOnCastFocusEffect(focusType type, uint16 spell_id); + bool TryTriggerOnCastProc(uint16 focusspellid, uint16 spell_id, uint16 proc_spellid); bool TrySpellTrigger(Mob *target, uint32 spell_id, int effect); void TryTriggerOnValueAmount(bool IsHP = false, bool IsMana = false, bool IsEndur = false, bool IsPet = false); void TryTwincast(Mob *caster, Mob *target, uint32 spell_id); diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 037db0a95..d947d6845 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -4463,7 +4463,7 @@ int32 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) when the next valid focus effect is found. */ - if (IsFocusEffect(0, 0, true, effect) || (effect == SE_TriggerOnCast)) { + if (IsFocusEffect(0, 0, true, effect)) { FocusCount++; // If limit found on prior check next, else end loop. if (FocusCount > 1) { @@ -5504,6 +5504,125 @@ int32 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo return (value * lvlModifier / 100); } +void Mob::TryTriggerOnCastFocusEffect(focusType type, uint16 spell_id) +{ + if (IsBardSong(spell_id)) { + return; + } + + if (!IsValidSpell(spell_id)) { + return; + } + + int32 focus_spell_id = 0; + int32 proc_spellid = 0; + + // item focus + if (IsClient() && itembonuses.FocusEffects[type]) { + const EQ::ItemData *temp_item = nullptr; + + for (int x = EQ::invslot::EQUIPMENT_BEGIN; x <= EQ::invslot::EQUIPMENT_END; x++) { + temp_item = nullptr; + EQ::ItemInstance *ins = CastToClient()->GetInv().GetItem(x); + if (!ins) { + continue; + } + temp_item = ins->GetItem(); + if (temp_item && temp_item->Focus.Effect > 0 && IsValidSpell(temp_item->Focus.Effect)) { + focus_spell_id = temp_item->Focus.Effect; + if (!IsEffectInSpell(focus_spell_id, SE_TriggerOnCast)) { + continue; + } + + proc_spellid = CalcFocusEffect(type, focus_spell_id, spell_id); + if (proc_spellid) { + TryTriggerOnCastProc(focus_spell_id, spell_id, proc_spellid); + } + } + + for (int y = EQ::invaug::SOCKET_BEGIN; y <= EQ::invaug::SOCKET_END; ++y) { + EQ::ItemInstance *aug = ins->GetAugment(y); + if (aug) { + const EQ::ItemData *temp_item_aug = aug->GetItem(); + if (temp_item_aug && temp_item_aug->Focus.Effect > 0 && IsValidSpell(temp_item_aug->Focus.Effect)) { + focus_spell_id = temp_item_aug->Focus.Effect; + + if (!IsEffectInSpell(focus_spell_id, SE_TriggerOnCast)) { + continue; + } + + proc_spellid = CalcFocusEffect(type, focus_spell_id, spell_id); + if (proc_spellid) { + TryTriggerOnCastProc(focus_spell_id, spell_id, proc_spellid); + } + } + } + } + } + } + + // Spell Focus + if (spellbonuses.FocusEffects[type]) { + int buff_slot = 0; + for (buff_slot = 0; buff_slot < GetMaxTotalSlots(); buff_slot++) { + focus_spell_id = buffs[buff_slot].spellid; + if (!IsValidSpell(focus_spell_id)) { + continue; + } + + if (!IsEffectInSpell(focus_spell_id, SE_TriggerOnCast)) { + continue; + } + + proc_spellid = CalcFocusEffect(type, focus_spell_id, spell_id); + if (proc_spellid) { + TryTriggerOnCastProc(focus_spell_id, spell_id, proc_spellid); + CheckNumHitsRemaining(NumHit::MatchingSpells, buff_slot); + } + } + } + + // Only use of this focus per AA effect. + if (IsClient() && aabonuses.FocusEffects[type]) { + for (const auto &aa : aa_ranks) { + auto ability_rank = zone->GetAlternateAdvancementAbilityAndRank(aa.first, aa.second.first); + auto ability = ability_rank.first; + auto rank = ability_rank.second; + + if (!ability) { + continue; + } + + if (rank->effects.empty()) { + continue; + } + + proc_spellid = CastToClient()->CalcAAFocus(type, *rank, spell_id); + if (proc_spellid) { + TryTriggerOnCastProc(0, spell_id, proc_spellid); + } + } + } +} + +bool Mob::TryTriggerOnCastProc(uint16 focusspellid, uint16 spell_id, uint16 proc_spellid) +{ + // We confirm spell_id and focuspellid are valid before passing into this. + if (IsValidSpell(proc_spellid) && spell_id != focusspellid && spell_id != proc_spellid) { + Mob* proc_target = GetTarget(); + if (proc_target) { + SpellFinished(proc_spellid, proc_target, EQ::spells::CastingSlot::Item, 0, -1, spells[proc_spellid].ResistDiff); + return true; + } + // Edge cases where proc spell does not require a target such as PBAE, allows proc to still occur even if target potentially dead. Live spells exist with PBAE procs. + else if (!SpellRequiresTarget(proc_spellid)) { + SpellFinished(proc_spellid, this, EQ::spells::CastingSlot::Item, 0, -1, spells[proc_spellid].ResistDiff); + return true; + } + } + return false; +} + uint16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { if (IsBardSong(spell_id)) diff --git a/zone/spells.cpp b/zone/spells.cpp index 72ef70e40..7b7d894e3 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -1384,11 +1384,11 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo TrySympatheticProc(target, spell_id); } - TryOnSpellFinished(this, target, spell_id); + TryOnSpellFinished(this, target, spell_id); //Use for effects that should be checked after SpellFinished is completed. TryTwincast(this, target, spell_id); - TryTriggerOnCast(spell_id, 0); + TryTriggerOnCastFocusEffect(focusTriggerOnCast, spell_id); if(DeleteChargeFromSlot >= 0) CastToClient()->DeleteItemInInventory(DeleteChargeFromSlot, 1, true);