diff --git a/zone/bot.cpp b/zone/bot.cpp index c7300f6f6..940e0ce27 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2272,7 +2272,6 @@ void Bot::AI_Process() // This causes conflicts with default pet handler (bounces between targets) if (NOT_PULLING_BOT && HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) { - // We don't add to hate list here because it's assumed to already be on the list GetPet()->SetTarget(tar); } @@ -2316,7 +2315,7 @@ void Bot::AI_Process() if (GetPullingFlag()) { if (!TargetValidation(tar)) { return; } - if (!DoLosChecks(this, tar)) { + if (!DoLosChecks(tar)) { return; } @@ -3196,7 +3195,7 @@ bool Bot::IsValidTarget( (!Charmed() && tar->GetUltimateOwner()->IsOfClientBotMerc()) || lo_distance > leash_distance || tar_distance > leash_distance || - (!GetAttackingFlag() && !CheckLosCheat(this, tar) && !CheckLosCheat(leash_owner, tar)) || + (!GetAttackingFlag() && !CheckLosCheat(tar) && !leash_owner->CheckLosCheat(tar)) || !IsAttackAllowed(tar) ) { invalid_target_state = true; @@ -9382,6 +9381,10 @@ void Bot::DoItemClick(const EQ::ItemData *item, uint16 slot_id) bool is_casting_bard_song = false; Mob* tar = (GetOwner()->GetTarget() ? GetOwner()->GetTarget() : this); + if (!DoLosChecks(tar)) { + return; + } + if (IsCasting()) { InterruptSpell(); } @@ -10252,7 +10255,7 @@ bool Bot::IsValidMezTarget(Mob* owner, Mob* npc, uint16 spell_id) { return false; } - if (!DoLosChecks(this, npc)) { + if (!DoLosChecks(npc)) { return false; } @@ -11084,7 +11087,7 @@ bool Bot::AttemptAACastSpell(Mob* tar, uint16 spell_id, AA::Rank* rank) { tar = this; } - if (!DoLosChecks(this, tar)) { + if (!DoLosChecks(tar)) { return false; } @@ -11219,7 +11222,7 @@ bool Bot::AttemptForcedCastSpell(Mob* tar, uint16 spell_id, bool is_disc) { return false; } - if (!DoLosChecks(this, tar)) { + if (!DoLosChecks(tar)) { return false; } @@ -11712,7 +11715,7 @@ bool Bot::HasRequiredLoSForPositioning(Mob* tar) { return true; } - if (RequiresLoSForPositioning() && !DoLosChecks(this, tar)) { + if (RequiresLoSForPositioning() && !DoLosChecks(tar)) { return false; } diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 2c05b748f..7575e9c00 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -803,7 +803,7 @@ void helper_send_usage_required_bots(Client *bot_owner, uint16 spell_type) auto& spell_map = bot->GetCommandedSpellTypesMinLevels(); if (spell_map.empty()) { - bot_owner->Message(Chat::Yellow, "No bots are capable of casting this spell type"); //deleteme + bot_owner->Message(Chat::Yellow, "No bots are capable of casting this spell type"); return; } diff --git a/zone/bot_commands/attack.cpp b/zone/bot_commands/attack.cpp index 6b8a929cb..1cb67bf45 100644 --- a/zone/bot_commands/attack.cpp +++ b/zone/bot_commands/attack.cpp @@ -18,7 +18,12 @@ void bot_command_attack(Client *c, const Seperator *sep) if (!target_mob) { - c->Message(Chat::White, "You must an enemy to use this command"); + c->Message(Chat::Yellow, "You must an enemy to use this command"); + return; + } + + if (!c->DoLosChecks(target_mob)) { + c->Message(Chat::Red, "You must have Line of Sight to use this command."); return; } @@ -71,7 +76,7 @@ void bot_command_attack(Client *c, const Seperator *sep) ); } else { c->Message( - Chat::White, + Chat::PetResponse, fmt::format( "{} of your bots are attacking {}.", sbl.size(), diff --git a/zone/bot_commands/click_item.cpp b/zone/bot_commands/click_item.cpp index a101dfbd2..d0ba47b69 100644 --- a/zone/bot_commands/click_item.cpp +++ b/zone/bot_commands/click_item.cpp @@ -47,11 +47,22 @@ void bot_command_click_item(Client* c, const Seperator* sep) sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end()); + Mob* tar = c->GetTarget(); + for (auto my_bot : sbl) { if (my_bot->BotPassiveCheck()) { continue; } + if ( + tar && + tar != c && + tar->GetOwner() != c && + !c->DoLosChecks(tar) + ) { + continue; + } + if (RuleI(Bots, BotsClickItemsMinLvl) > my_bot->GetLevel()) { c->Message(Chat::White, "%s must be level %i to use clickable items.", my_bot->GetCleanName(), RuleI(Bots, BotsClickItemsMinLvl)); continue; diff --git a/zone/bot_commands/precombat.cpp b/zone/bot_commands/precombat.cpp index 86ac7569a..073dccf72 100644 --- a/zone/bot_commands/precombat.cpp +++ b/zone/bot_commands/precombat.cpp @@ -5,15 +5,22 @@ void bot_command_precombat(Client* c, const Seperator* sep) if (helper_command_alias_fail(c, "bot_command_precombat", sep->arg[0], "precombat")) { return; } - if (helper_is_help_or_usage(sep->arg[1])) { + if (helper_is_help_or_usage(sep->arg[1])) { c->Message(Chat::White, "usage: %s ([set | clear])", sep->arg[0]); + return; } if (!c->GetTarget() || !c->IsAttackAllowed(c->GetTarget())) { - c->Message(Chat::White, "This command requires an attackable target."); + + return; + } + + if (!c->DoLosChecks(c->GetTarget())) { + c->Message(Chat::Red, "You must have Line of Sight to use this command."); + return; } diff --git a/zone/bot_commands/pull.cpp b/zone/bot_commands/pull.cpp index e082e05f0..22c27ee25 100644 --- a/zone/bot_commands/pull.cpp +++ b/zone/bot_commands/pull.cpp @@ -48,6 +48,12 @@ void bot_command_pull(Client *c, const Seperator *sep) return; } + if (!c->DoLosChecks(target_mob)) { + c->Message(Chat::Red, "You must have Line of Sight to use this command."); + + return; + } + if (target_mob->IsNPC() && target_mob->GetHateList().size()) { c->Message(Chat::White, "Your current target is already engaged!"); @@ -55,8 +61,8 @@ void bot_command_pull(Client *c, const Seperator *sep) } Bot* bot_puller = nullptr; - for (auto bot_iter : sbl) { + for (auto bot_iter : sbl) { if (bot_iter->GetAppearance() == eaDead || bot_iter->GetBotStance() == Stance::Passive) { continue; } diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 7cffb07c9..17f1d8d81 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -65,7 +65,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ bot_spell.ManaCost = 0; if (BotSpellTypeRequiresLoS(spell_type) && tar != this) { - SetHasLoS(DoLosChecks(this, tar)); + SetHasLoS(DoLosChecks(tar)); } else { SetHasLoS(true); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 27a674b85..22afc4e66 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -11091,10 +11091,17 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) case PET_ATTACK: { if (!target) break; + + if (!DoLosChecks(target)) { + mypet->SayString(this, NOT_LEGAL_TARGET); + break; + } + if (target->IsMezzed()) { MessageString(Chat::NPCQuestSay, CANNOT_WAKE, mypet->GetCleanName(), target->GetCleanName()); break; } + if (mypet->IsFeared()) break; //prevent pet from attacking stuff while feared @@ -11149,8 +11156,15 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) if (mypet->IsFeared()) break; //prevent pet from attacking stuff while feared - if (!GetTarget()) + if (!GetTarget()) { break; + } + + if (!DoLosChecks(GetTarget())) { + mypet->SayString(this, NOT_LEGAL_TARGET); + break; + } + if (GetTarget()->IsMezzed()) { MessageString(Chat::NPCQuestSay, CANNOT_WAKE, mypet->GetCleanName(), GetTarget()->GetCleanName()); break; diff --git a/zone/mob.cpp b/zone/mob.cpp index d0374f07a..2d3013230 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -8679,33 +8679,54 @@ bool Mob::IsInGroupOrRaid(Mob* other, bool same_raid_group) { return group && group == other_group; } -bool Mob::DoLosChecks(Mob* who, Mob* other) { - if (!who->CheckLosFN(other) || !who->CheckWaterLoS(other)) { - if (who->CheckLosCheatExempt(who, other)) { - return true; - } - - if (CheckLosCheat(who, other)) { +bool Mob::DoLosChecks(Mob* other) { + if (!CheckLosFN(other) || !CheckWaterLoS(other)) { + if (CheckLosCheatExempt(other)) { return true; } + return false; + } + if (!CheckLosCheat(other)) { return false; } return true; } -bool Mob::CheckLosCheat(Mob* who, Mob* other) { +bool Mob::CheckLosCheat(Mob* other) { if (RuleB(Map, CheckForLoSCheat)) { for (auto itr : entity_list.GetDoorsList()) { Doors* d = itr.second; - if (d && !d->IsDoorOpen() && (d->GetTriggerType() == 255 || d->GetLockpick() != 0 || d->GetKeyItem() != 0 || d->GetNoKeyring() != 0)) { - if (DistanceNoZ(who->GetPosition(), d->GetPosition()) <= 50) { - auto who_to_door = DistanceNoZ(who->GetPosition(), d->GetPosition()); - auto other_to_door = DistanceNoZ(other->GetPosition(), d->GetPosition()); - auto who_to_other = DistanceNoZ(who->GetPosition(), other->GetPosition()); - auto distance_difference = who_to_other - (who_to_door + other_to_door); + if ( + !d->IsDoorOpen() && + ( + d->GetKeyItem() || + d->GetLockpick() || + d->IsDoorOpen() || + d->IsDoorBlacklisted() || + d->GetNoKeyring() != 0 || + d->GetDoorParam() > 0 + ) + ) { + // If the door is a trigger door, check if the trigger door is open + if (d->GetTriggerDoorID() > 0) { + auto td = entity_list.GetDoorsByDoorID(d->GetTriggerDoorID()); + + if (td) { + if (Strings::RemoveNumbers(d->GetDoorName()) != Strings::RemoveNumbers(td->GetDoorName())) { + continue; + } + } + } + + if (DistanceNoZ(GetPosition(), d->GetPosition()) <= 50) { + auto who_to_door = DistanceNoZ(GetPosition(), d->GetPosition()); + auto other_to_door = DistanceNoZ(other->GetPosition(), d->GetPosition()); + auto who_to_other = DistanceNoZ(GetPosition(), other->GetPosition()); + auto distance_difference = who_to_other - (who_to_door + other_to_door); + if (distance_difference >= (-1 * RuleR(Maps, RangeCheckForLoSCheat)) && distance_difference <= RuleR(Maps, RangeCheckForLoSCheat)) { return false; } @@ -8717,19 +8738,23 @@ bool Mob::CheckLosCheat(Mob* who, Mob* other) { return true; } -bool Mob::CheckLosCheatExempt(Mob* who, Mob* other) { +bool Mob::CheckLosCheatExempt(Mob* other) { if (RuleB(Map, EnableLoSCheatExemptions)) { + /* This is an exmaple of how to configure exemptions for LoS checks. glm::vec4 exempt_check_who; glm::vec4 exempt_check_other; - /* This is an exmaple of how to configure exemptions for LoS checks. - if (zone->GetZoneID() == 222) { //PoEarthB - exempt_check_who.x = 2051; exempt_check_who.y = 407; exempt_check_who.z = -219; //Middle of councilman spawns - //check to be sure the player and the target are in the pit to PoEarthB - //if the player is inside the cove they cannot be higher than the ceiling (no exploiting from uptop) - //otherwise they can pass LoS checks even if they don't have true LoS - if (who->GetZ() <= -171 && other->GetZ() <= -171 && DistanceNoZ(other->GetPosition(), exempt_check_who) <= 800 && DistanceNoZ(who->GetPosition(), exempt_check_who) <= 800) { - return true; - } + + switch (zone->GetZoneID()) { + case POEARTHB: + exempt_check_who.x = 2051; exempt_check_who.y = 407; exempt_check_who.z = -219; //Middle of councilman spawns + //exempt_check_other.x = 1455; exempt_check_other.y = 415; exempt_check_other.z = -242; + //check to be sure the player and the target are outside of the councilman area + //if the player is inside the cove they cannot be higher than the ceiling (no exploiting from uptop) + if (GetZ() <= -171 && other->GetZ() <= -171 && DistanceNoZ(other->GetPosition(), exempt_check_who) <= 800 && DistanceNoZ(GetPosition(), exempt_check_who) <= 800) { + return true; + } + default: + return false; } */ } diff --git a/zone/mob.h b/zone/mob.h index 3fb130142..312311a1d 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -872,9 +872,9 @@ public: static bool CheckLosFN(glm::vec3 posWatcher, float sizeWatcher, glm::vec3 posTarget, float sizeTarget); virtual bool CheckWaterLoS(Mob* m); bool CheckPositioningLosFN(Mob* other, float posX, float posY, float posZ); - bool CheckLosCheat(Mob* who, Mob* other); - bool CheckLosCheatExempt(Mob* who, Mob* other); - bool DoLosChecks(Mob* who, Mob* other); + bool CheckLosCheat(Mob* other); + bool CheckLosCheatExempt(Mob* other); + bool DoLosChecks(Mob* other); bool TargetValidation(Mob* other); inline void SetLastLosState(bool value) { last_los_check = value; } inline bool CheckLastLosState() const { return last_los_check; }