From 444d688ad2c44605147596bc290715403ae57228 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Sat, 29 Mar 2025 16:01:31 -0500 Subject: [PATCH] [Bots] Line of Sight and Mez optimizations and cleanup (#4746) * [Bots] Line of Sight and Mez optimizations and cleanup - Renames `Map:CheckForLoSCheat` to `Map:CheckForDoorLoSCheat` to better reflect what it does. - Renames `Map:RangeCheckForLoSCheat` to `Map:RangeCheckForDoorLoSCheat` to better reflect what it does. - Adds the rule `Pets:PetsRequireLoS` to determine whether or not commanded pet attacks require an addition layer of LoS checks for edge-cases. - Adds the rule `Bots:BotsRequireLoS` to determine whether or not bots require LoS to `^attack`, `^pull` and `^precombat`. - Adds the rule `Map:ZonesToCheckDoorCheat` to control what if any zones will be checked.. - Corrects, removes and adds LoS checks where necessary. - Improves door checking logic for locked or triggered doors that could be blocking LoS. - Cleans up false positives for door cheat checks. - Adds `drawbox` option to `#door` command. This will spawn points at the center and each corner of the door's "box". It will also spawn points at your and your target's location. - Improves Mez and AE Mez logic - Adds more details to the rule `Bots:EpicPetSpellName` * Remove leftover debugging * Change return to continue for GetFirstIncomingMobToMez checks * Move mez chance fail to beginning of cast process --- common/ruletypes.h | 14 ++- common/spdat.cpp | 12 -- common/spdat.h | 2 +- common/spdat_bot.cpp | 12 ++ zone/bot.cpp | 17 ++- zone/bot_commands/attack.cpp | 2 +- zone/bot_commands/cast.cpp | 9 +- zone/bot_commands/depart.cpp | 6 +- zone/bot_commands/precombat.cpp | 2 +- zone/bot_commands/pull.cpp | 2 +- zone/botspellsai.cpp | 156 ++++++++++++++----------- zone/client_packet.cpp | 4 +- zone/doors.cpp | 68 +++++++++++ zone/doors.h | 1 + zone/gm_commands/door_manipulation.cpp | 27 +++++ zone/mob.cpp | 82 ++++++------- zone/mob.h | 2 +- 17 files changed, 265 insertions(+), 153 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 456af6bcd..b2a5594b0 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -291,6 +291,7 @@ RULE_BOOL(Pets, ClientPetsUseOwnerNameInLastName, true, "Disable this to keep cl RULE_BOOL(Pets, CanTakeNoDrop, false, "Setting whether anyone can give no-drop items to pets") RULE_INT(Pets, PetTauntRange, 150, "Range at which a pet will taunt targets.") RULE_BOOL(Pets, AlwaysAllowPetRename, false, "Enable this option to allow /changepetname to work without enabling a pet name change via scripts.") +RULE_BOOL(Pets, PetsRequireLoS, false, "Whether or not pets require line of sight to be told to attack their target") RULE_CATEGORY_END() RULE_CATEGORY(GM) @@ -390,9 +391,10 @@ RULE_BOOL(Map, MobZVisualDebug, false, "Displays spell effects determining wheth RULE_BOOL(Map, MobPathingVisualDebug, false, "Displays nodes in pathing points in realtime to help with visual debugging") RULE_REAL(Map, FixPathingZMaxDeltaSendTo, 20, "At runtime in SendTo: maximum change in Z to allow the BestZ code to apply") RULE_INT(Map, FindBestZHeightAdjust, 1, "Adds this to the current Z before seeking the best Z position") -RULE_BOOL(Map, CheckForLoSCheat, false, "Runs predefined zone checks to check for LoS cheating through doors and such.") -RULE_BOOL(Map, EnableLoSCheatExemptions, false, "Enables exemptions for the LoS Cheat check.") -RULE_REAL(Map, RangeCheckForLoSCheat, 20.0, "Default 20.0. Range to check if one is within range of a door.") +RULE_BOOL(Map, CheckForDoorLoSCheat, true, "Runs LoS checks to prevent cheating through doors.") +RULE_BOOL(Map, EnableLoSCheatExemptions, false, "Enables exemptions for the LoS Cheat check. Must modify source to create these.") +RULE_REAL(Map, RangeCheckForDoorLoSCheat, 250.0, "Default 250.0. Range to check if a door is blocking LoS from the target.") +RULE_STRING(Map, ZonesToCheckDoorCheat, "89,103", "Zones that will check for the door LoS cheat. You can leave it blank to disable, 'all' to check all zones or use a comma-delimited list of zones. Default Sebilis & Chardok") RULE_CATEGORY_END() RULE_CATEGORY(Pathing) @@ -816,6 +818,7 @@ RULE_INT(Bots, PercentChanceToCastDispel, 75, "The chance for a bot to attempt t RULE_INT(Bots, PercentChanceToCastInCombatBuff, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") RULE_INT(Bots, PercentChanceToCastHateLine, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") RULE_INT(Bots, PercentChanceToCastMez, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") +RULE_INT(Bots, PercentChanceToCastAEMez, 40, "The chance for a bot to attempt to cast the given spell type in combat. Default 40%.") RULE_INT(Bots, PercentChanceToCastSlow, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") RULE_INT(Bots, PercentChanceToCastDebuff, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") RULE_INT(Bots, PercentChanceToCastCure, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.") @@ -827,8 +830,6 @@ RULE_INT(Bots, MinDelayBetweenInCombatCastAttempts, 500, "The minimum delay in m RULE_INT(Bots, MaxDelayBetweenInCombatCastAttempts, 2000, "The maximum delay in milliseconds between cast attempts while in-combat. This is rolled between the min and max. Default 2500ms.") RULE_INT(Bots, MinDelayBetweenOutCombatCastAttempts, 1000, "The minimum delay in milliseconds between cast attempts while out-of-combat. This is rolled between the min and max. Default 1000ms.") RULE_INT(Bots, MaxDelayBetweenOutCombatCastAttempts, 2500, "The maximum delay in milliseconds between cast attempts while out-of-combat. This is rolled between the min and max. Default 2500ms.") -RULE_INT(Bots, MezChance, 60, "60 Default. Chance for a bot to attempt to Mez a target after validating it is eligible.") -RULE_INT(Bots, AEMezChance, 35, "35 Default. Chance for a bot to attempt to AE Mez targets after validating they are eligible.") RULE_INT(Bots, MezSuccessDelay, 2500, "2500 (2.5 sec) Default. Delay between successful Mez attempts.") RULE_INT(Bots, AEMezSuccessDelay, 5000, "5000 (5 sec) Default. Delay between successful AEMez attempts.") RULE_INT(Bots, MezFailDelay, 1250, "1250 (1.25 sec) Default. Delay between failed Mez attempts.") @@ -845,7 +846,7 @@ RULE_BOOL(Bots, AllowCastAAs, true, "If enabled, players can use ^cast aa to cas RULE_BOOL(Bots, AllowMagicianEpicPet, false, "If enabled, magician bots can summon their epic pets following the rules AllowMagicianEpicPetLevel") RULE_INT(Bots, AllowMagicianEpicPetLevel, 50, "If AllowMagicianEpicPet is enabled, bots can start using their epic pets at this level") RULE_INT(Bots, RequiredMagicianEpicPetItemID, 28034, "If AllowMagicianEpicPet is enabled and this is set, bots will be required to have this item ID equipped to cast their epic. Takes in to account AllowMagicianEpicPetLevel as well. Set to 0 to disable requirement") -RULE_STRING(Bots, EpicPetSpellName, "", "'teleport_zone' in the spell to be cast for epic pets. This must be in their spell list to cast.") +RULE_STRING(Bots, EpicPetSpellName, "", "'teleport_zone' in the spell to be cast for epic pets. This must be in their usable spell list to cast. Empty uses Manifest Elements - 'SumMageMultiElement'") RULE_INT(Bots, ReclaimEnergySpellID, 331, "Spell ID for reclaim energy when using ^petsettype. Default 331") RULE_BOOL(Bots, UseSpellPulling, true, "If enabled bots will use a spell to pull when within range. Uses PullSpellID.") RULE_INT(Bots, PullSpellID, 5225, "Default 5225 - Throw Stone. Spell that will be cast to pull by bots") @@ -903,6 +904,7 @@ RULE_STRING(Bots, ZonesWithForcedSpawnLimits, "", "Comma-delimited list of zones RULE_STRING(Bots, ZoneForcedSpawnLimits, "", "Comma-delimited list of forced spawn limits for zones.") RULE_INT(Bots, AICastSpellTypeDelay, 100, "Delay in milliseconds between AI cast attempts for each spell type. Default 100ms") RULE_INT(Bots, AICastSpellTypeHeldDelay, 2500, "Delay in milliseconds between AI cast attempts for each spell type that is held or disabled. Default 2500ms (2.5s)") +RULE_BOOL(Bots, BotsRequireLoS, true, "Whether or not bots require line of sight to be told to attack their target") RULE_CATEGORY_END() RULE_CATEGORY(Chat) diff --git a/common/spdat.cpp b/common/spdat.cpp index 8a6512dfa..7631d2b98 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -2808,18 +2808,6 @@ bool IsLichSpell(uint16 spell_id) ); } -bool IsValidSpellAndLoS(uint32 spell_id, bool has_los) { - if (!IsValidSpell(spell_id)) { - return false; - } - - if (!has_los && IsTargetRequiredForSpell(spell_id)) { - return false; - } - - return true; -} - bool IsInstantHealSpell(uint32 spell_id) { if (!IsValidSpell(spell_id)) { return false; diff --git a/common/spdat.h b/common/spdat.h index 9f4e787ff..afc00f4dd 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -918,6 +918,7 @@ bool IsPullingBotSpellType(uint16 spell_type); uint16 GetCorrectBotSpellType(uint16 spell_type, uint16 spell_id); uint16 GetPetBotSpellType(uint16 spell_type); bool IsBotBuffSpellType(uint16 spell_type); +bool BotRequiresLoSToCast(uint16 spell_type, uint16 spell_id); // These should not be used to determine spell category.. // They are a graphical affects (effects?) index only @@ -1815,7 +1816,6 @@ bool IsEffectInSpell(uint16 spell_id, int effect_id); uint16 GetSpellTriggerSpellID(uint16 spell_id, int effect_id); bool IsBlankSpellEffect(uint16 spell_id, int effect_index); bool IsValidSpell(uint32 spell_id); -bool IsValidSpellAndLoS(uint32 spell_id, bool has_los = true); bool IsSummonSpell(uint16 spell_id); bool IsDamageSpell(uint16 spell_id); bool IsAnyDamageSpell(uint16 spell_id); diff --git a/common/spdat_bot.cpp b/common/spdat_bot.cpp index 6627d3674..5feef7970 100644 --- a/common/spdat_bot.cpp +++ b/common/spdat_bot.cpp @@ -484,3 +484,15 @@ bool IsBotBuffSpellType(uint16 spell_type) { return false; } + +bool BotRequiresLoSToCast(uint16 spell_type, uint16 spell_id) { + if (!BotSpellTypeRequiresTarget(spell_type)) { + return false; + } + + if (!IsTargetRequiredForSpell(spell_id)) { + return false; + } + + return true; +} diff --git a/zone/bot.cpp b/zone/bot.cpp index bbe473b9d..b0bb7ca6d 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2314,7 +2314,7 @@ void Bot::AI_Process() if (PULLING_BOT || RETURNING_BOT) { if (!TargetValidation(tar)) { return; } - if (!DoLosChecks(tar)) { + if (RuleB(Bots, BotsRequireLoS) && !HasLoS()) { return; } @@ -3175,14 +3175,17 @@ bool Bot::IsValidTarget( return false; } + SetHasLoS(DoLosChecks(tar)); + bool invalid_target_state = false; + if (HOLDING || !tar->IsNPC() || (tar->IsMezzed() && !HasBotAttackFlag(tar)) || (!Charmed() && tar->GetUltimateOwner()->IsOfClientBotMerc()) || lo_distance > leash_distance || tar_distance > leash_distance || - (!GetAttackingFlag() && !CheckLosCheat(tar) && !leash_owner->CheckLosCheat(tar)) || + (!GetAttackingFlag() && !HasLoS()) || !IsAttackAllowed(tar) ) { invalid_target_state = true; @@ -11413,7 +11416,7 @@ bool Bot::AttemptForcedCastSpell(Mob* tar, uint16 spell_id, bool is_disc) { return false; } - if (!DoLosChecks(tar)) { + if (!HasLoS() && !DoLosChecks(tar)) { return false; } @@ -12197,7 +12200,7 @@ bool Bot::HasRequiredLoSForPositioning(Mob* tar) { return true; } - if (RequiresLoSForPositioning() && !DoLosChecks(tar)) { + if (RequiresLoSForPositioning() && !HasLoS()) { return false; } @@ -12212,10 +12215,6 @@ bool Bot::HasValidAETarget(Bot* caster, uint16 spell_id, uint16 spell_type, Mob* for (auto& close_mob : caster->m_close_mobs) { Mob* m = close_mob.second; - if (tar == m) { - continue; - } - switch (spell_type) { case BotSpellTypes::AELull: if (m->GetSpecialAbility(SpecialAbility::PacifyImmunity)) { @@ -12307,8 +12306,6 @@ bool Bot::HasValidAETarget(Bot* caster, uint16 spell_id, uint16 spell_type, Mob* return false; } - SetHasLoS(true); - return true; } diff --git a/zone/bot_commands/attack.cpp b/zone/bot_commands/attack.cpp index 9a2d454c5..f91902d4c 100644 --- a/zone/bot_commands/attack.cpp +++ b/zone/bot_commands/attack.cpp @@ -22,7 +22,7 @@ void bot_command_attack(Client *c, const Seperator *sep) return; } - if (!c->DoLosChecks(target_mob)) { + if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(target_mob)) { c->Message(Chat::Red, "You must have Line of Sight to use this command."); return; } diff --git a/zone/bot_commands/cast.cpp b/zone/bot_commands/cast.cpp index f2f812caa..19368992c 100644 --- a/zone/bot_commands/cast.cpp +++ b/zone/bot_commands/cast.cpp @@ -525,6 +525,9 @@ void bot_command_cast(Client* c, const Seperator* sep) continue; } + bool requires_los = !(IsAnyHealSpell(spell_id) && !IsPBAESpell(spell_id)); + bot_iter->SetHasLoS(requires_los ? bot_iter->DoLosChecks(new_tar) : true); + if (!bot_iter->AttemptAACastSpell(tar, spell_id, rank)) { continue; } @@ -543,6 +546,9 @@ void bot_command_cast(Client* c, const Seperator* sep) tar = bot_iter; } + bool los_required = bot_iter != tar && !IsAnyHealSpell(chosen_spell_id) && !IsPBAESpell(chosen_spell_id); + bot_iter->SetHasLoS(los_required ? bot_iter->DoLosChecks(new_tar) : true); + if (bot_iter->AttemptForcedCastSpell(tar, chosen_spell_id)) { if (!first_found) { first_found = bot_iter; @@ -556,7 +562,8 @@ void bot_command_cast(Client* c, const Seperator* sep) } else { bot_iter->SetCommandedSpell(true); - + bot_iter->SetHasLoS(BotSpellTypeRequiresLoS(spell_type) ? bot_iter->DoLosChecks(new_tar) : true); + if (bot_iter->AICastSpell(new_tar, 100, spell_type, sub_target_type, sub_type)) { if (!first_found) { first_found = bot_iter; diff --git a/zone/bot_commands/depart.cpp b/zone/bot_commands/depart.cpp index dd0c12bfd..0a560e59c 100644 --- a/zone/bot_commands/depart.cpp +++ b/zone/bot_commands/depart.cpp @@ -197,7 +197,11 @@ void bot_command_depart(Client* c, const Seperator* sep) bot_iter->SetCommandedSpell(true); - if (!IsValidSpellAndLoS(itr->SpellId, bot_iter->HasLoS())) { + if (!IsValidSpell(itr->SpellId)) { + continue; + } + + if (BotRequiresLoSToCast(BotSpellTypes::Teleport, itr->SpellId) && !bot_iter->HasLoS()) { continue; } diff --git a/zone/bot_commands/precombat.cpp b/zone/bot_commands/precombat.cpp index 073dccf72..a059e8eb1 100644 --- a/zone/bot_commands/precombat.cpp +++ b/zone/bot_commands/precombat.cpp @@ -18,7 +18,7 @@ void bot_command_precombat(Client* c, const Seperator* sep) return; } - if (!c->DoLosChecks(c->GetTarget())) { + if (RuleB(Bots, BotsRequireLoS) && !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 093cf9bc3..127ec14ab 100644 --- a/zone/bot_commands/pull.cpp +++ b/zone/bot_commands/pull.cpp @@ -48,7 +48,7 @@ void bot_command_pull(Client *c, const Seperator *sep) return; } - if (!c->DoLosChecks(target_mob)) { + if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(target_mob)) { c->Message(Chat::Red, "You must have Line of Sight to use this command."); return; diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 2dd8c608c..60dda0d71 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -41,7 +41,10 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ return false; } - if (chance < 100 && zone->random.Int(0, 100) > chance) { + if ( + !IsCommandedSpell() && + zone->random.Int(0, 100) > chance + ) { return false; } @@ -61,10 +64,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ bot_spell.SpellIndex = 0; bot_spell.ManaCost = 0; - if (BotSpellTypeRequiresLoS(spell_type) && tar != this) { - SetHasLoS(DoLosChecks(tar)); - } - else { + if (!BotSpellTypeRequiresLoS(spell_type) || tar == this) { SetHasLoS(true); } @@ -218,8 +218,11 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ std::vector bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, (IsAEBotSpellType(spell_type) || sub_target_type == CommandedSubTypes::AETarget), sub_target_type, sub_type); for (const auto& s : bot_spell_list) { + if (!IsValidSpell(s.SpellId)) { + continue; + } - if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) { + if (BotRequiresLoSToCast(spell_type, s.SpellId) && !HasLoS()) { continue; } @@ -273,7 +276,11 @@ bool Bot::BotCastMez(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel std::vector bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, IsAEBotSpellType(spell_type)); for (const auto& s : bot_spell_list) { - if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) { + if (!IsValidSpell(s.SpellId)) { + continue; + } + + if (BotRequiresLoSToCast(spell_type, s.SpellId) && !HasLoS()) { continue; } @@ -281,7 +288,7 @@ bool Bot::BotCastMez(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel Mob* add_mob = GetFirstIncomingMobToMez(this, s.SpellId, spell_type, IsAEBotSpellType(spell_type)); if (!add_mob) { - return false; + continue; } tar = add_mob; @@ -327,7 +334,11 @@ bool Bot::BotCastCure(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spe bot_spell = GetBestBotSpellForCure(this, tar, spell_type); - if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) { + if (!IsValidSpell(bot_spell.SpellId)) { + return false; + } + + if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) { return false; } @@ -397,7 +408,11 @@ bool Bot::BotCastPet(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel bot_spell = GetFirstBotSpellBySpellType(this, spell_type); } - if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) { + if (!IsValidSpell(bot_spell.SpellId)) { + return false; + } + + if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) { return false; } @@ -418,6 +433,10 @@ bool Bot::BotCastPet(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel } bool Bot::BotCastNuke(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spell_type) { + if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) { + return false; + } + if (spell_type == BotSpellTypes::Stun || spell_type == BotSpellTypes::AEStun) { uint8 stun_chance = (tar->IsCasting() ? RuleI(Bots, StunCastChanceIfCasting) : RuleI(Bots, StunCastChanceNormal)); @@ -434,25 +453,25 @@ bool Bot::BotCastNuke(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spe ) { bot_spell = GetBestBotSpellForStunByTargetType(this, ST_TargetOptional, spell_type, IsAEBotSpellType(spell_type), tar); } - - if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) { + + if (!IsValidSpell(bot_spell.SpellId)) { return false; } } - if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) { + if (!IsValidSpell(bot_spell.SpellId)) { bot_spell = GetBestBotSpellForNukeByBodyType(this, tar->GetBodyType(), spell_type, IsAEBotSpellType(spell_type), tar); } - if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS()) && spell_type == BotSpellTypes::Nuke && bot_class == Class::Wizard) { + if (spell_type == BotSpellTypes::Nuke && bot_class == Class::Wizard && !IsValidSpell(bot_spell.SpellId)) { bot_spell = GetBestBotWizardNukeSpellByTargetResists(this, tar, spell_type); } - if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) { + if (!IsValidSpell(bot_spell.SpellId)) { std::vector bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, IsAEBotSpellType(spell_type)); for (const auto& s : bot_spell_list) { - if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) { + if (!IsValidSpell(s.SpellId)) { continue; } @@ -910,7 +929,11 @@ std::list Bot::GetBotSpellsForSpellEffect(Bot* caster, uint16 spell_ty const std::vector& bot_spell_list = caster->BotGetSpellsByType(spell_type); for (int i = bot_spell_list.size() - 1; i >= 0; i--) { - if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) { + if (!IsValidSpell(bot_spell_list[i].spellid)) { + continue; + } + + if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) { continue; } @@ -948,7 +971,11 @@ std::list Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* caster, ui const std::vector& bot_spell_list = caster->BotGetSpellsByType(spell_type); for (int i = bot_spell_list.size() - 1; i >= 0; i--) { - if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) { + if (!IsValidSpell(bot_spell_list[i].spellid)) { + continue; + } + + if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) { continue; } @@ -989,7 +1016,11 @@ std::list Bot::GetBotSpellsBySpellType(Bot* caster, uint16 spell_type) const std::vector& bot_spell_list = caster->BotGetSpellsByType(spell_type); for (int i = bot_spell_list.size() - 1; i >= 0; i--) { - if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) { + if (!IsValidSpell(bot_spell_list[i].spellid)) { + continue; + } + + if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) { continue; } @@ -1018,7 +1049,11 @@ std::vector Bot::GetPrioritizedBotSpellsBySpellType(Bot* cas const std::vector& bot_spell_list = caster->BotGetSpellsByType(spell_type); for (int i = bot_spell_list.size() - 1; i >= 0; i--) { - if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) { + if (!IsValidSpell(bot_spell_list[i].spellid)) { + continue; + } + + if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) { continue; } @@ -1105,7 +1140,11 @@ BotSpell Bot::GetFirstBotSpellBySpellType(Bot* caster, uint16 spell_type) { const std::vector& bot_spell_list = caster->BotGetSpellsByType(spell_type); for (int i = bot_spell_list.size() - 1; i >= 0; i--) { - if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) { + if (!IsValidSpell(bot_spell_list[i].spellid)) { + continue; + } + + if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) { continue; } @@ -1137,7 +1176,6 @@ BotSpell Bot::GetBestBotSpellForVeryFastHeal(Bot* caster, Mob* tar, uint16 spell std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP); for (auto bot_spell_list_itr : bot_spell_list) { - // Assuming all the spells have been loaded into this list by level and in descending order if ( IsVeryFastHealSpell(bot_spell_list_itr.SpellId) && caster->CastChecks(bot_spell_list_itr.SpellId, tar, spell_type)) { result.SpellId = bot_spell_list_itr.SpellId; @@ -1163,7 +1201,6 @@ BotSpell Bot::GetBestBotSpellForFastHeal(Bot* caster, Mob* tar, uint16 spell_typ std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP); for (auto bot_spell_list_itr : bot_spell_list) { - // Assuming all the spells have been loaded into this list by level and in descending order if (IsFastHealSpell(bot_spell_list_itr.SpellId) && caster->CastChecks(bot_spell_list_itr.SpellId, tar, spell_type)) { result.SpellId = bot_spell_list_itr.SpellId; result.SpellIndex = bot_spell_list_itr.SpellIndex; @@ -1188,7 +1225,6 @@ BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* caster, Mob* tar, uint16 spell std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_HealOverTime); for (auto bot_spell_list_itr : bot_spell_list) { - // Assuming all the spells have been loaded into this list by level and in descending order if (IsHealOverTimeSpell(bot_spell_list_itr.SpellId) && caster->CastChecks(bot_spell_list_itr.SpellId, tar, spell_type)) { result.SpellId = bot_spell_list_itr.SpellId; result.SpellIndex = bot_spell_list_itr.SpellIndex; @@ -1245,7 +1281,6 @@ BotSpell Bot::GetBestBotSpellForRegularSingleTargetHeal(Bot* caster, Mob* tar, u std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP); for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { - // Assuming all the spells have been loaded into this list by level and in descending order if (IsRegularSingleTargetHealSpell(bot_spell_list_itr->SpellId) && caster->CastChecks(bot_spell_list_itr->SpellId, tar, spell_type)) { result.SpellId = bot_spell_list_itr->SpellId; result.SpellIndex = bot_spell_list_itr->SpellIndex; @@ -1270,7 +1305,6 @@ BotSpell Bot::GetFirstBotSpellForSingleTargetHeal(Bot* caster, Mob* tar, uint16 std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP); for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { - // Assuming all the spells have been loaded into this list by level and in descending order if (IsRegularSingleTargetHealSpell(bot_spell_list_itr->SpellId) && caster->CastChecks(bot_spell_list_itr->SpellId, tar, spell_type)) { result.SpellId = bot_spell_list_itr->SpellId; result.SpellIndex = bot_spell_list_itr->SpellIndex; @@ -1300,7 +1334,6 @@ BotSpell Bot::GetBestBotSpellForGroupHeal(Bot* caster, Mob* tar, uint16 spell_ty int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type); for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { - // Assuming all the spells have been loaded into this list by level and in descending order if (IsRegularGroupHealSpell(bot_spell_list_itr->SpellId)) { uint16 spell_id = bot_spell_list_itr->SpellId; @@ -1339,7 +1372,6 @@ BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* caster, Mob* tar, uint16 int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type); for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { - // Assuming all the spells have been loaded into this list by level and in descending order if (IsGroupHealOverTimeSpell(bot_spell_list_itr->SpellId)) { uint16 spell_id = bot_spell_list_itr->SpellId; @@ -1378,7 +1410,6 @@ BotSpell Bot::GetBestBotSpellForGroupCompleteHeal(Bot* caster, Mob* tar, uint16 int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type); for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { - // Assuming all the spells have been loaded into this list by level and in descending order if (IsGroupCompleteHealSpell(bot_spell_list_itr->SpellId)) { uint16 spell_id = bot_spell_list_itr->SpellId; @@ -1412,7 +1443,6 @@ BotSpell Bot::GetBestBotSpellForMez(Bot* caster, uint16 spell_type) { std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Mez); for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { - // Assuming all the spells have been loaded into this list by level and in descending order if ( IsMesmerizeSpell(bot_spell_list_itr->SpellId) && caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId) @@ -1435,11 +1465,10 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ if (caster && caster->GetOwner()) { int spell_range = caster->GetActSpellRange(spell_id, spells[spell_id].range); int spell_ae_range = caster->GetAOERange(spell_id); - int buff_count = 0; + bool is_pbae_spell = IsPBAESpell(spell_id); NPC* npc = nullptr; for (auto& close_mob : caster->m_close_mobs) { - buff_count = 0; npc = close_mob.second->CastToNPC(); if (!npc) { @@ -1450,29 +1479,29 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ continue; } + if (is_pbae_spell) { + if (spell_ae_range < Distance(caster->GetPosition(), npc->GetPosition())) { + continue; + } + } + else { + if (spell_range < Distance(caster->GetPosition(), npc->GetPosition())) { + continue; + } + } + if (AE) { int target_count = 0; for (auto& close_mob : caster->m_close_mobs) { Mob* m = close_mob.second; - if (npc == m) { - continue; - } - if (!caster->IsValidMezTarget(caster->GetOwner(), m, spell_id)) { continue; } - if (IsPBAESpell(spell_id)) { - if (spell_ae_range < Distance(caster->GetPosition(), m->GetPosition())) { - continue; - } - } - else { - if (spell_range < Distance(m->GetPosition(), npc->GetPosition())) { - continue; - } + if (spell_ae_range < Distance(npc->GetPosition(), m->GetPosition())) { + continue; } if (caster->CastChecks(spell_id, m, spell_type, true, true)) { @@ -1488,11 +1517,6 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ continue; } - if (zone->random.Int(1, 100) < RuleI(Bots, AEMezChance)) { - caster->SetSpellTypeRecastTimer(spell_type, RuleI(Bots, MezFailDelay)); - return result; - } - result = npc; } else { @@ -1504,18 +1528,10 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ continue; } - if (zone->random.Int(1, 100) < RuleI(Bots, MezChance)) { - caster->SetSpellTypeRecastTimer(spell_type, RuleI(Bots, MezAEFailDelay)); - - return result; - } - result = npc; } if (result) { - caster->SetHasLoS(true); - return result; } } @@ -1536,7 +1552,6 @@ BotSpell Bot::GetBestBotMagicianPetSpell(Bot* caster, uint16 spell_type) { std::string pet_type = GetBotMagicianPetType(caster); for(std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { - // Assuming all the spells have been loaded into this list by level and in descending order if ( IsSummonPetSpell(bot_spell_list_itr->SpellId) && caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId) && @@ -1718,7 +1733,6 @@ BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* caster, SpellTargetType ta std::list bot_spell_list = GetBotSpellsForSpellEffectAndTargetType(caster, spell_type, SE_CurrentHP, target_type); for(std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { - // Assuming all the spells have been loaded into this list by level and in descending order if (IsPureNukeSpell(bot_spell_list_itr->SpellId) || IsDamageSpell(bot_spell_list_itr->SpellId)) { if (!AE && IsAnyAESpell(bot_spell_list_itr->SpellId) && !IsGroupSpell(bot_spell_list_itr->SpellId)) { continue; @@ -1731,7 +1745,6 @@ BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* caster, SpellTargetType ta continue; } - if ( caster->IsCommandedSpell() || !AE || @@ -1768,7 +1781,6 @@ BotSpell Bot::GetBestBotSpellForStunByTargetType(Bot* caster, SpellTargetType ta for(std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { - // Assuming all the spells have been loaded into this list by level and in descending order if (IsStunSpell(bot_spell_list_itr->SpellId)) { if (!AE && IsAnyAESpell(bot_spell_list_itr->SpellId) && !IsGroupSpell(bot_spell_list_itr->SpellId)) { continue; @@ -1832,7 +1844,6 @@ BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* caster, Mob* target, bool spell_selected = false; for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { - // Assuming all the spells have been loaded into this list by level and in descending order if (!caster->IsValidSpellRange(bot_spell_list_itr->SpellId, target)) { continue; } @@ -1889,8 +1900,6 @@ BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* caster, Mob* target, if (!spell_selected) { for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { - // Assuming all the spells have been loaded into this list by level and in descending order - if (caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId)) { if (caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type)) { spell_selected = true; @@ -1928,7 +1937,11 @@ BotSpell Bot::GetDebuffBotSpell(Bot* caster, Mob *tar, uint16 spell_type) { const std::vector& bot_spell_list = caster->BotGetSpellsByType(spell_type); for (int i = bot_spell_list.size() - 1; i >= 0; i--) { - if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) { + if (!IsValidSpell(bot_spell_list[i].spellid)) { + continue; + } + + if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) { continue; } @@ -1974,7 +1987,11 @@ BotSpell Bot::GetBestBotSpellForResistDebuff(Bot* caster, Mob *tar, uint16 spell const std::vector& bot_spell_list = caster->BotGetSpellsByType(spell_type); for (int i = bot_spell_list.size() - 1; i >= 0; i--) { - if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) { + if (!IsValidSpell(bot_spell_list[i].spellid)) { + continue; + } + + if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) { continue; } @@ -2093,7 +2110,6 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spell_type) case BotSpellTypes::AERains: case BotSpellTypes::AEStun: case BotSpellTypes::AESnare: - case BotSpellTypes::AEMez: case BotSpellTypes::AESlow: case BotSpellTypes::AEDebuff: case BotSpellTypes::AEFear: @@ -2159,6 +2175,8 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spell_type) case BotSpellTypes::PetVeryFastHeals: case BotSpellTypes::PetHoTHeals: return RuleI(Bots, PercentChanceToCastHeal); + case BotSpellTypes::AEMez: + return RuleI(Bots, PercentChanceToCastAEMez); default: return RuleI(Bots, PercentChanceToCastOtherType); } @@ -2823,7 +2841,6 @@ BotSpell Bot::GetBestBotSpellForRez(Bot* caster, Mob* target, uint16 spell_type) std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Revive); for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { - // Assuming all the spells have been loaded into this list by level and in descending order if ( IsResurrectSpell(bot_spell_list_itr->SpellId) && caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId) @@ -2851,7 +2868,6 @@ BotSpell Bot::GetBestBotSpellForCharm(Bot* caster, Mob* target, uint16 spell_typ std::list bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Charm); for (std::list::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) { - // Assuming all the spells have been loaded into this list by level and in descending order if ( IsCharmSpell(bot_spell_list_itr->SpellId) && caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type) diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 29633bd22..2ec433b36 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -11066,7 +11066,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) if (!target) break; - if (RuleB(Map, CheckForLoSCheat) && (!DoLosChecks(target) || !CheckLosCheat(target))) { + if (RuleB(Pets, PetsRequireLoS) && !DoLosChecks(target)) { mypet->SayString(this, NOT_LEGAL_TARGET); break; } @@ -11134,7 +11134,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) break; } - if (RuleB(Map, CheckForLoSCheat) && (!DoLosChecks(GetTarget()) || !CheckLosCheat(GetTarget()))) { + if (RuleB(Pets, PetsRequireLoS) && !DoLosChecks(GetTarget())) { mypet->SayString(this, NOT_LEGAL_TARGET); break; } diff --git a/zone/doors.cpp b/zone/doors.cpp index 32baf2ee5..075688bb9 100644 --- a/zone/doors.cpp +++ b/zone/doors.cpp @@ -35,6 +35,9 @@ #include +#include +#include + #define OPEN_DOOR 0x02 #define CLOSE_DOOR 0x03 #define OPEN_INVDOOR 0x03 @@ -970,3 +973,68 @@ bool Doors::GetIsDoorBlacklisted() bool Doors::IsDoorBlacklisted() { return m_is_blacklisted_to_open; } + +bool Doors::IsDoorBetween(glm::vec4 loc_a, glm::vec4 loc_c, uint16 door_size, float door_depth, bool draw_box) { + glm::vec4 door_loc = GetPosition(); + float half_size = door_size * 0.5f; + float half_depth = door_depth * 0.5f; + float normalized_heading = std::fmod(door_loc.w, 512.0f); + float heading_radians = normalized_heading * (std::numbers::pi / 256.0f); + glm::mat4 door_rotation = glm::rotate(glm::mat4(1.0f), -heading_radians, glm::vec3(0.0f, 0.0f, 1.0f)); + glm::vec3 box_corner_one = glm::vec3(door_size, -half_depth, 0.0f); + glm::vec3 box_corner_two = glm::vec3(-door_size, -half_depth, 0.0f); + glm::vec3 box_corner_three = glm::vec3(-door_size, half_depth, 0.0f); + glm::vec3 box_corner_four = glm::vec3(door_size, half_depth, 0.0f); + glm::vec3 door_center_offset = glm::vec3(-(door_size * 0.75f), half_depth * 0.5f, 0.0f); + glm::vec3 door_center = glm::vec3(door_loc) + glm::vec3(door_rotation * glm::vec4(door_center_offset, 1.0f)); + glm::mat4 transform = glm::translate(glm::mat4(1.0f), door_center) * door_rotation; + + box_corner_one = glm::vec3(transform * glm::vec4(box_corner_one, 1.0f)); + box_corner_two = glm::vec3(transform * glm::vec4(box_corner_two, 1.0f)); + box_corner_three = glm::vec3(transform * glm::vec4(box_corner_three, 1.0f)); + box_corner_four = glm::vec3(transform * glm::vec4(box_corner_four, 1.0f)); + + if (draw_box) { + NPC::SpawnZonePointNodeNPC("loc_a", loc_a); + NPC::SpawnZonePointNodeNPC("door_anchor", door_loc); + NPC::SpawnZonePointNodeNPC("loc_c", loc_c); + NPC::SpawnZonePointNodeNPC("box_corner_one", glm::vec4(box_corner_one.x, box_corner_one.y, box_corner_one.z, 0)); + NPC::SpawnZonePointNodeNPC("box_corner_two", glm::vec4(box_corner_two.x, box_corner_two.y, box_corner_two.z, 0)); + NPC::SpawnZonePointNodeNPC("box_corner_three", glm::vec4(box_corner_three.x, box_corner_three.y, box_corner_three.z, 0)); + NPC::SpawnZonePointNodeNPC("box_corner_four", glm::vec4(box_corner_four.x, box_corner_four.y, box_corner_four.z, 0)); + NPC::SpawnZonePointNodeNPC("box_center", glm::vec4(door_center.x, door_center.y, door_center.z, 0)); + } + + // Check if LoS intersects box + auto intersects_box = [](const glm::vec3& a, const glm::vec3& b, const glm::vec3& p1, const glm::vec3& p2) { + glm::vec3 ab = b - a; + glm::vec3 p1p2 = p2 - p1; + + glm::vec3 cross = glm::cross(ab, p1p2); + float cross_magnitude_squared = glm::dot(cross, cross); + + if (cross_magnitude_squared < 1e-6f) { + return false; // Lines are parallel or coincident + } + + float t = glm::dot(glm::cross(p1 - a, p1p2), cross) / cross_magnitude_squared; + float u = glm::dot(glm::cross(p1 - a, ab), cross) / cross_magnitude_squared; + + return (t >= 0.0f && t <= 1.0f && u >= 0.0f && u <= 1.0f); + }; + + // Check intersection with each edge of the door bounding box + glm::vec3 loc_a_vec3(loc_a.x, loc_a.y, loc_a.z); + glm::vec3 loc_c_vec3(loc_c.x, loc_c.y, loc_c.z); + + if ( + intersects_box(loc_a_vec3, loc_c_vec3, box_corner_one, box_corner_two) || + intersects_box(loc_a_vec3, loc_c_vec3, box_corner_two, box_corner_three) || + intersects_box(loc_a_vec3, loc_c_vec3, box_corner_three, box_corner_four) || + intersects_box(loc_a_vec3, loc_c_vec3, box_corner_four, box_corner_one) + ) { + return true; + } + + return false; +} diff --git a/zone/doors.h b/zone/doors.h index b93aed657..f600a280b 100644 --- a/zone/doors.h +++ b/zone/doors.h @@ -76,6 +76,7 @@ public: bool IsDestinationZoneSame() const; bool IsDoorBlacklisted(); + bool IsDoorBetween(glm::vec4 loc_a, glm::vec4 loc_c, uint16 door_size = 15, float door_depth = 5.0f, bool draw_box = false); const char* GetDoorZone() const { return m_zone_name; } diff --git a/zone/gm_commands/door_manipulation.cpp b/zone/gm_commands/door_manipulation.cpp index 4f136798f..614cc4484 100644 --- a/zone/gm_commands/door_manipulation.cpp +++ b/zone/gm_commands/door_manipulation.cpp @@ -35,6 +35,26 @@ void DoorManipulation::CommandHandler(Client *c, const Seperator *sep) ); } + if (arg1 == "drawbox") { + Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId()); + + if (door) { + uint16 door_size = 15; + float door_depth = 5.0f; + + if (sep->IsNumber(2) && atof(sep->arg[2]) > 0) { + door_size = atof(sep->arg[2]); + } + + if (sep->IsNumber(3) && atof(sep->arg[3]) > 0) { + door_depth = atof(sep->arg[3]); + } + + + door->IsDoorBetween(c->GetPosition(), (c->GetTarget() ? c->GetTarget()->GetPosition() : c->GetPosition()), door_size, door_depth, true); + } + } + // edit menu if (arg1 == "edit") { Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId()); @@ -544,6 +564,13 @@ void DoorManipulation::CommandHandler(Client *c, const Seperator *sep) c->Message(Chat::White, "#door setinvertstate [0|1] | Sets selected door invert state"); c->Message(Chat::White, "#door setincline | Sets selected door incline"); c->Message(Chat::White, "#door opentype | Sets selected door opentype"); + c->Message( + Chat::White, + fmt::format( + "{} | Draws a box for the door, default size = 15, depth = 5 if none defined", + Saylink::Silent("#door drawbox") + ).c_str() + ); c->Message( Chat::White, fmt::format( diff --git a/zone/mob.cpp b/zone/mob.cpp index 30f9c8690..16978d3e3 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -8675,56 +8675,54 @@ bool Mob::IsInGroupOrRaid(Mob* other, bool same_raid_group) { bool Mob::DoLosChecks(Mob* other) { if (!CheckLosFN(other) || !CheckWaterLoS(other)) { - if (CheckLosCheatExempt(other)) { + if (RuleB(Map, EnableLoSCheatExemptions) && CheckLosCheatExempt(other)) { return true; } return false; } - if (!CheckLosCheat(other)) { + if (RuleB(Map, CheckForDoorLoSCheat) && !CheckDoorLoSCheat(other)) { return false; } return true; } -bool Mob::CheckLosCheat(Mob* other) { - if (RuleB(Map, CheckForLoSCheat)) { - for (auto itr : entity_list.GetDoorsList()) { - Doors* d = itr.second; +bool Mob::CheckDoorLoSCheat(Mob* other) { + if (!other->IsOfClientBotMerc() && other->CastToNPC()->IsOnHatelist(this)) { + return true; + } + + const std::string& zones_to_check = RuleS(Map, ZonesToCheckDoorCheat); + + if (zones_to_check.empty()) { + return true; + } + + const auto& v = Strings::Split(zones_to_check, ","); + + if (zones_to_check == "all" || std::find(v.begin(), v.end(), std::to_string(zone->GetZoneID())) != v.end()) { + for (auto itr: entity_list.GetDoorsList()) { + Doors *d = itr.second; if ( !d->IsDoorOpen() && ( d->GetKeyItem() || d->GetLockpick() || - d->IsDoorOpen() || d->IsDoorBlacklisted() || - d->GetNoKeyring() != 0 || - d->GetDoorParam() > 0 + d->GetNoKeyring() != 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()); + ) { + float distance = Distance(m_Position, d->GetPosition()); - if (td) { - if (Strings::RemoveNumbers(d->GetDoorName()) != Strings::RemoveNumbers(td->GetDoorName())) { - continue; - } - } + if (distance > RuleR(Map, RangeCheckForDoorLoSCheat) || !CheckLosFN(d->GetX(), d->GetY(), d->GetZ(), GetSize())) { + 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; - } + if (d->IsDoorBetween(GetPosition(), other->GetPosition(), d->GetSize())) { + return false; } } } @@ -8733,26 +8731,18 @@ bool Mob::CheckLosCheat(Mob* other) { return true; } -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; +bool Mob::CheckLosCheatExempt(Mob* other) { + glm::vec4 exempt_check_who; - 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; - } - */ + switch (zone->GetZoneID()) { + case Zones::POEARTHB: + exempt_check_who.x = 2053; exempt_check_who.y = 408; exempt_check_who.z = -219; //Middle of councilman spawns + //if the player is inside the cove they cannot be higher than the ceiling (no exploiting from uptop) --- 800 from center of council to furthest corner in cove + if (GetZ() <= -171 && other->GetZ() <= -171 && DistanceNoZ(other->GetPosition(), exempt_check_who) <= 800 && DistanceNoZ(GetPosition(), exempt_check_who) <= 800) { + return true; + } + default: + return false; } return false; diff --git a/zone/mob.h b/zone/mob.h index 37a6ecf37..070372222 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -798,7 +798,7 @@ 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* other); //door skipping checks for LoS + bool CheckDoorLoSCheat(Mob* other); //door skipping checks for LoS bool CheckLosCheatExempt(Mob* other); //exemptions to bypass los bool DoLosChecks(Mob* other); inline void SetLastLosState(bool value) { last_los_check = value; }