From 70ee95efc0dfc432bc62c620b76bfdac32de4003 Mon Sep 17 00:00:00 2001 From: Alex King <89047260+Kinglykrab@users.noreply.github.com> Date: Sat, 2 Mar 2024 16:19:31 -0500 Subject: [PATCH] [Quest API] Add Bot Special Attacks for Immune Aggro/Damage (#4108) * [Quest API] Add Bot Special Attacks for Immune Aggro/Damage # Notes - Adds `IMMUNE_AGGRO_BOT` and `IMMUNE_DAMAGE_BOT` for uses in special abilities. * Cleanup * Update attack.cpp --- common/emu_constants.cpp | 2 + common/emu_constants.h | 4 +- zone/aggro.cpp | 40 ++++++++------ zone/attack.cpp | 45 ++++++++++------ zone/entity.cpp | 10 ++-- zone/lua_mob.cpp | 4 +- zone/mob.cpp | 111 ++++++++++++++++++++++++++++++--------- zone/npc.cpp | 2 + 8 files changed, 157 insertions(+), 61 deletions(-) diff --git a/common/emu_constants.cpp b/common/emu_constants.cpp index e85059ecf..395e96158 100644 --- a/common/emu_constants.cpp +++ b/common/emu_constants.cpp @@ -698,6 +698,8 @@ const std::map& EQ::constants::GetSpecialAbilityMap() { IMMUNE_OPEN, "Immune to Open" }, { IMMUNE_ASSASSINATE, "Immune to Assassinate" }, { IMMUNE_HEADSHOT, "Immune to Headshot" }, + { IMMUNE_AGGRO_BOT, "Immune to Bot Aggro" }, + { IMMUNE_DAMAGE_BOT, "Immune to Bot Damage" }, }; return special_ability_map; diff --git a/common/emu_constants.h b/common/emu_constants.h index c9e856858..5f7caeb71 100644 --- a/common/emu_constants.h +++ b/common/emu_constants.h @@ -656,7 +656,9 @@ enum { IMMUNE_OPEN = 53, IMMUNE_ASSASSINATE = 54, IMMUNE_HEADSHOT = 55, - MAX_SPECIAL_ATTACK = 56 + IMMUNE_AGGRO_BOT = 56, + IMMUNE_DAMAGE_BOT = 57, + MAX_SPECIAL_ATTACK = 58 }; diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 1804c20dc..65a1982fd 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -621,28 +621,38 @@ bool Mob::IsAttackAllowed(Mob *target, bool isSpellAttack) // 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; - - if(target->GetSpecialAbility(NO_HARM_FROM_CLIENT)){ + if (!zone->CanDoCombat()) { return false; } - if (target->GetSpecialAbility(IMMUNE_DAMAGE_CLIENT) && IsClient()) + // some special cases + if (!target) { return false; + } - if (target->GetSpecialAbility(IMMUNE_DAMAGE_NPC) && IsNPC()) - return false; + if (this == target) { // you can attack yourself + return true; + } - if (target->IsHorse()) + if (target->GetSpecialAbility(NO_HARM_FROM_CLIENT)) { return false; + } + + if (IsBot() && target->GetSpecialAbility(IMMUNE_DAMAGE_BOT)) { + return false; + } + + if (IsClient() && target->GetSpecialAbility(IMMUNE_DAMAGE_CLIENT)) { + return false; + } + + if (IsNPC() && target->GetSpecialAbility(IMMUNE_DAMAGE_NPC)) { + return false; + } + + if (target->IsHorse()) { + return false; + } // can't damage own pet (applies to everthing) Mob *target_owner = target->GetOwner(); diff --git a/zone/attack.cpp b/zone/attack.cpp index f45f193b9..dcb39ab77 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -3091,26 +3091,37 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo TryTriggerOnCastRequirement(); } - if (IsClient() && !IsAIControlled()) + if (IsClient() && !IsAIControlled()) { return; + } - if (IsFamiliar() || GetSpecialAbility(IMMUNE_AGGRO)) + if (IsFamiliar() || GetSpecialAbility(IMMUNE_AGGRO)) { return; + } - if (GetSpecialAbility(IMMUNE_AGGRO_NPC) && other->IsNPC()) + if (other->IsBot() && GetSpecialAbility(IMMUNE_AGGRO_BOT)) { return; + } - if (GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && other->IsClient()) + if (other->IsClient() && GetSpecialAbility(IMMUNE_AGGRO_CLIENT)) { return; + } - if (IsValidSpell(spell_id) && IsNoDetrimentalSpellAggroSpell(spell_id)) + if (other->IsNPC() && GetSpecialAbility(IMMUNE_AGGRO_NPC)) { return; + } - if (other == myowner) + if (IsValidSpell(spell_id) && IsNoDetrimentalSpellAggroSpell(spell_id)) { return; + } - if (other->GetSpecialAbility(IMMUNE_AGGRO_ON)) + if (other == myowner) { return; + } + + if (other->GetSpecialAbility(IMMUNE_AGGRO_ON)) { + return; + } if (GetSpecialAbility(NPC_TUNNELVISION)) { int tv_mod = GetSpecialAbilityParam(NPC_TUNNELVISION, 0); @@ -3194,8 +3205,9 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo // owner must get on list, but he's not actually gained any hate yet if ( !owner->GetSpecialAbility(IMMUNE_AGGRO) && - !(GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && owner->IsClient()) && - !(GetSpecialAbility(IMMUNE_AGGRO_NPC) && owner->IsNPC()) + !(owner->IsBot() && GetSpecialAbility(IMMUNE_AGGRO_BOT)) && + !(owner->IsClient() && GetSpecialAbility(IMMUNE_AGGRO_CLIENT)) && + !(owner->IsNPC() && GetSpecialAbility(IMMUNE_AGGRO_NPC)) ) { if (owner->IsClient() && !CheckAggro(owner)) { owner->CastToClient()->AddAutoXTarget(this); @@ -3209,8 +3221,9 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo if ( !mypet->IsFamiliar() && !mypet->GetSpecialAbility(IMMUNE_AGGRO) && - !(mypet->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && IsClient()) && - !(mypet->GetSpecialAbility(IMMUNE_AGGRO_NPC) && IsNPC()) + !(IsBot() && mypet->GetSpecialAbility(IMMUNE_AGGRO_BOT)) && + !(IsClient() && mypet->GetSpecialAbility(IMMUNE_AGGRO_CLIENT)) && + !(IsNPC() && mypet->GetSpecialAbility(IMMUNE_AGGRO_NPC)) ) { mypet->hate_list.AddEntToHateList(other, 0, 0, bFrenzy); } @@ -3219,8 +3232,9 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo if ( myowner->IsAIControlled() && !myowner->GetSpecialAbility(IMMUNE_AGGRO) && - !(GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && myowner->IsClient()) && - !(GetSpecialAbility(IMMUNE_AGGRO_NPC) && myowner->IsNPC()) + !(myowner->IsBot() && GetSpecialAbility(IMMUNE_AGGRO_BOT)) && + !(myowner->IsClient() && GetSpecialAbility(IMMUNE_AGGRO_CLIENT)) && + !(myowner->IsNPC() && GetSpecialAbility(IMMUNE_AGGRO_NPC)) ) { myowner->hate_list.AddEntToHateList(other, 0, 0, bFrenzy); } @@ -4060,8 +4074,9 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons !pet->GetSpecialAbility(IMMUNE_AGGRO) && !pet->IsEngaged() && attacker && - !(pet->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && attacker->IsClient()) && - !(pet->GetSpecialAbility(IMMUNE_AGGRO_NPC) && attacker->IsNPC()) && + !(attacker->IsBot() && pet->GetSpecialAbility(IMMUNE_AGGRO_BOT)) && + !(attacker->IsClient() && pet->GetSpecialAbility(IMMUNE_AGGRO_CLIENT)) && + !(attacker->IsNPC() && pet->GetSpecialAbility(IMMUNE_AGGRO_NPC)) && attacker != this && !attacker->IsCorpse() && !pet->IsGHeld() && diff --git a/zone/entity.cpp b/zone/entity.cpp index 24bf38ae7..69b76ec67 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -4379,8 +4379,9 @@ void EntityList::AddTempPetsToHateList(Mob *owner, Mob* other, bool bFrenzy) if (n->GetSwarmInfo()->owner_id == owner->GetID()) { if ( !n->GetSpecialAbility(IMMUNE_AGGRO) && - !(n->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && other->IsClient()) && - !(n->GetSpecialAbility(IMMUNE_AGGRO_NPC) && other->IsNPC()) + !(other->IsBot() && n->GetSpecialAbility(IMMUNE_AGGRO_BOT)) && + !(other->IsClient() && n->GetSpecialAbility(IMMUNE_AGGRO_CLIENT)) && + !(other->IsNPC() && n->GetSpecialAbility(IMMUNE_AGGRO_NPC)) ) { n->hate_list.AddEntToHateList(other, 0, 0, bFrenzy); } @@ -4405,8 +4406,9 @@ void EntityList::AddTempPetsToHateListOnOwnerDamage(Mob *owner, Mob* attacker, i attacker != n && !n->IsEngaged() && !n->GetSpecialAbility(IMMUNE_AGGRO) && - !(n->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && attacker->IsClient()) && - !(n->GetSpecialAbility(IMMUNE_AGGRO_NPC) && attacker->IsNPC()) && + !(attacker->IsBot() && n->GetSpecialAbility(IMMUNE_AGGRO_BOT)) && + !(attacker->IsClient() && n->GetSpecialAbility(IMMUNE_AGGRO_CLIENT)) && + !(attacker->IsNPC() && n->GetSpecialAbility(IMMUNE_AGGRO_NPC)) && !attacker->IsTrap() && !attacker->IsCorpse() ) { diff --git a/zone/lua_mob.cpp b/zone/lua_mob.cpp index 08b0aa6ed..e14118279 100644 --- a/zone/lua_mob.cpp +++ b/zone/lua_mob.cpp @@ -3866,7 +3866,9 @@ luabind::scope lua_register_special_abilities() { luabind::value("modify_avoid_damage", static_cast(MODIFY_AVOID_DAMAGE)), luabind::value("immune_open", static_cast(IMMUNE_OPEN)), luabind::value("immune_assassinate", static_cast(IMMUNE_ASSASSINATE)), - luabind::value("immune_headshot", static_cast(IMMUNE_HEADSHOT)) + luabind::value("immune_headshot", static_cast(IMMUNE_HEADSHOT)), + luabind::value("immune_aggro_bot", static_cast(IMMUNE_AGGRO_BOT)), + luabind::value("immune_damage_bot", static_cast(IMMUNE_DAMAGE_BOT)) )]; } diff --git a/zone/mob.cpp b/zone/mob.cpp index 9780e5e0b..75e6c710e 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -5204,32 +5204,47 @@ int32 Mob::GetActSpellCasttime(uint16 spell_id, int32 casttime) } -void Mob::ExecWeaponProc(const EQ::ItemInstance *inst, uint16 spell_id, Mob *on, int level_override) { +void Mob::ExecWeaponProc(const EQ::ItemInstance* inst, uint16 spell_id, Mob* on, int level_override) +{ // Changed proc targets to look up based on the spells goodEffect flag. // This should work for the majority of weapons. if (!on) { return; } - if(!IsValidSpell(spell_id) || on->GetSpecialAbility(NO_HARM_FROM_CLIENT)) { + if (!IsValidSpell(spell_id) || on->GetSpecialAbility(NO_HARM_FROM_CLIENT)) { //This is so 65535 doesn't get passed to the client message and to logs because it is not relavant information for debugging. return; } - if (on->GetSpecialAbility(IMMUNE_DAMAGE_CLIENT) && IsClient()) + if (IsBot() && on->GetSpecialAbility(IMMUNE_DAMAGE_BOT)) { return; + } - if (on->GetSpecialAbility(IMMUNE_DAMAGE_NPC) && IsNPC()) + if (IsClient() && on->GetSpecialAbility(IMMUNE_DAMAGE_CLIENT)) { return; + } - if (IsNoCast()) + if (IsNPC() && on->GetSpecialAbility(IMMUNE_DAMAGE_NPC)) { return; + } - if(!IsValidSpell(spell_id)) { // Check for a valid spell otherwise it will crash through the function - if(IsClient()){ - Message(0, "Invalid spell proc %u", spell_id); + if (IsNoCast()) { + return; + } + + if (!IsValidSpell(spell_id)) { // Check for a valid spell otherwise it will crash through the function + if (IsClient()) { + Message( + Chat::White, + fmt::format( + "Invalid spell ID for proc {}.", + spell_id + ).c_str() + ); LogSpells("Player [{}] Weapon Procced invalid spell [{}]", GetName(), spell_id); } + return; } @@ -5243,7 +5258,7 @@ void Mob::ExecWeaponProc(const EQ::ItemInstance *inst, uint16 spell_id, Mob *on, return; } - if(inst && IsClient()) { + if (inst && IsClient()) { //const cast is dirty but it would require redoing a ton of interfaces at this point //It should be safe as we don't have any truly const EQ::ItemInstance floating around anywhere. //So we'll live with it for now @@ -5263,30 +5278,76 @@ void Mob::ExecWeaponProc(const EQ::ItemInstance *inst, uint16 spell_id, Mob *on, } } - bool twinproc = false; - int32 twinproc_chance = 0; + bool twin_proc = false; + int32 twin_proc_chance = 0; if (IsClient() || IsBot()) { - twinproc_chance = GetFocusEffect(focusTwincast, spell_id); + twin_proc_chance = GetFocusEffect(focusTwincast, spell_id); } - if (twinproc_chance && zone->random.Roll(twinproc_chance)) { - twinproc = true; + if (twin_proc_chance && zone->random.Roll(twin_proc_chance)) { + twin_proc = true; } - if (IsBeneficialSpell(spell_id) && (!IsNPC() || (IsNPC() && CastToNPC()->GetInnateProcSpellID() != spell_id)) && spells[spell_id].target_type != ST_TargetsTarget) { // NPC innate procs don't take this path ever - SpellFinished(spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty, true, level_override); - if (twinproc) { - SpellFinished(spell_id, this, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty, true, level_override); + if ( + IsBeneficialSpell(spell_id) && + ( + !IsNPC() || + ( + IsNPC() && + CastToNPC()->GetInnateProcSpellID() != spell_id + ) + ) && + spells[spell_id].target_type != ST_TargetsTarget + ) { // NPC innate procs don't take this path ever + SpellFinished( + spell_id, + this, + EQ::spells::CastingSlot::Item, + 0, + -1, + spells[spell_id].resist_difficulty, + true, + level_override + ); + + if (twin_proc) { + SpellFinished( + spell_id, + this, + EQ::spells::CastingSlot::Item, + 0, + -1, + spells[spell_id].resist_difficulty, + true, + level_override + ); + } + } else if (!(on->IsClient() && on->CastToClient()->dead)) { //dont proc on dead clients + SpellFinished( + spell_id, + on, + EQ::spells::CastingSlot::Item, + 0, + -1, + spells[spell_id].resist_difficulty, + true, + level_override + ); + + if (twin_proc && (!(on->IsClient() && on->CastToClient()->dead))) { + SpellFinished( + spell_id, + on, + EQ::spells::CastingSlot::Item, + 0, + -1, + spells[spell_id].resist_difficulty, + true, + level_override + ); } } - else if(!(on->IsClient() && on->CastToClient()->dead)) { //dont proc on dead clients - SpellFinished(spell_id, on, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty, true, level_override); - if (twinproc && (!(on->IsClient() && on->CastToClient()->dead))) { - SpellFinished(spell_id, on, EQ::spells::CastingSlot::Item, 0, -1, spells[spell_id].resist_difficulty, true, level_override); - } - } - return; } uint32 Mob::GetZoneID() const { diff --git a/zone/npc.cpp b/zone/npc.cpp index 4fabc9d92..a4e863457 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -3798,6 +3798,8 @@ void NPC::DescribeSpecialAbilities(Client* c) IMMUNE_OPEN, IMMUNE_ASSASSINATE, IMMUNE_HEADSHOT, + IMMUNE_AGGRO_BOT, + IMMUNE_DAMAGE_BOT }; // These abilities have parameters that need to be parsed out individually