[Spells] Update SPA 339 SE_TriggerOnCast (#1478)

* Recoded SE_TriggerOnCast Focus effect

Recoded SE_TriggerOnCast focus effect to be consistent with how all other focuses are checked. No longer an arbitrary limit as to number of a focus effects of this type you can have.

* new command: resetdisc_timer

usage: #resetdisc_timer [all | timer_id]

* syntax fixes

syntax improvements

* minor fix

changed numhits check

* Update spell_effects.cpp

* added better support for spell procs that don't require target.

* syntax

* Formatting and syntax tweaks

Co-authored-by: Akkadius <akkadius1@gmail.com>
This commit is contained in:
KayenEQ 2021-08-01 14:26:44 -04:00 committed by GitHub
parent 93b0264a8b
commit f01cf74fa6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 174 additions and 111 deletions

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -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))

View File

@ -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);

View File

@ -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))

View File

@ -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);