diff --git a/common/ruletypes.h b/common/ruletypes.h index d0bedd05d..53f65b4c4 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -446,6 +446,7 @@ RULE_BOOL(Spells, UseItemCastMessage, false, "Enable to use the \"item begins to RULE_BOOL(Spells, TargetsTargetRequiresCombatRange, true, "Disable to remove combat range requirement from Target's Target Spell Target Type") RULE_BOOL(Spells, NPCBuffLevelRestrictions, false, "Impose BuffLevelRestrictions on NPCs if true") RULE_INT(Spells, ResurrectionEffectBlock, 2, "0 = allow overwrites/rule disabled. If set to 1 = Block all buffs that would overwrite Resurrection Effects. If set to 2 = Will not overwrite Resurrection Effects, instead moves new buff to an empty slot if available. Default is 2.") +RULE_BOOL(Spells, WaterMatchRequiredForLoS, true, "Enable/Disable the requirement of both the attacker/victim being both in or out of water for spells LoS to pass.") RULE_CATEGORY_END() RULE_CATEGORY(Combat) @@ -519,6 +520,7 @@ RULE_BOOL(Combat, EnableWarriorShielding, true, "Enable or disable Warrior Shiel RULE_BOOL(Combat, BackstabIgnoresElemental, false, "Enable or disable Elemental weapon damage affecting backstab damage, false by default.") RULE_BOOL(Combat, BackstabIgnoresBane, false, "Enable or disable Bane weapon damage affecting backstab damage, false by default.") RULE_BOOL(Combat, SummonMeleeRange, true, "Enable or disable summoning of a player when already in melee range of the summoner.") +RULE_BOOL(Combat, WaterMatchRequiredForAutoFireLoS, true, "Enable/Disable the requirement of both the attacker/victim being both in or out of water for AutoFire LoS to pass.") RULE_CATEGORY_END() RULE_CATEGORY(NPC) diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 9a0417d6b..17fbe4321 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -262,7 +262,7 @@ bool Bot::BotCastDebuff(Mob* tar, uint8 botLevel, BotSpell& botSpell, bool check bool casted_spell = false; if ((tar->GetHPRatio() <= 99.0f) && (tar->GetHPRatio() > 20.0f)) { - if (!checked_los && !CheckLosFN(tar)) { + if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { return casted_spell; } @@ -296,7 +296,7 @@ bool Bot::BotCastSlow(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpe bool casted_spell = false; if (tar->GetHPRatio() <= 99.0f) { - if (!checked_los && !CheckLosFN(tar)) { + if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { return casted_spell; } @@ -387,7 +387,7 @@ bool Bot::BotCastDOT(Mob* tar, uint8 botLevel, const BotSpell& botSpell, const b bool casted_spell = false; if ((tar->GetHPRatio() <= 98.0f) && (tar->DontDotMeBefore() < Timer::GetCurrentTime()) && (tar->GetHPRatio() > 15.0f)) { - if (!checked_los && !CheckLosFN(tar)) { + if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { return casted_spell; } @@ -470,7 +470,7 @@ bool Bot::BotCastDOT(Mob* tar, uint8 botLevel, const BotSpell& botSpell, const b bool Bot::BotCastSnare(Mob* tar, uint8 botLevel, BotSpell& botSpell, const bool& checked_los, uint32 iSpellTypes) { bool casted_spell = false; if (tar->DontSnareMeBefore() < Timer::GetCurrentTime()) { - if (!checked_los && !CheckLosFN(tar)) { + if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { return casted_spell; } @@ -501,7 +501,7 @@ bool Bot::BotCastSnare(Mob* tar, uint8 botLevel, BotSpell& botSpell, const bool& bool Bot::BotCastLifetap(Mob* tar, uint8 botLevel, BotSpell& botSpell, const bool& checked_los, uint32 iSpellTypes) { bool casted_spell = false; if (GetHPRatio() < 90.0f) { - if (!checked_los && !CheckLosFN(tar)) { + if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { return casted_spell; } @@ -714,7 +714,7 @@ bool Bot::BotCastDispel(Mob* tar, BotSpell& botSpell, uint32 iSpellTypes, const bool casted_spell = false; if (tar->GetHPRatio() > 95.0f) { - if (!checked_los && !CheckLosFN(tar)) { + if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { return casted_spell; } @@ -737,7 +737,7 @@ bool Bot::BotCastNuke(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpe bool casted_spell = false; if ((tar->GetHPRatio() <= 95.0f) || ((botClass == BARD) || (botClass == SHAMAN) || (botClass == ENCHANTER) || (botClass == PALADIN) || (botClass == SHADOWKNIGHT) || (botClass == WARRIOR))) { - if (!checked_los && !CheckLosFN(tar)) { + if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { return casted_spell; } @@ -965,7 +965,7 @@ bool Bot::BotCastBuff(Mob* tar, uint8 botLevel, uint8 botClass) { bool Bot::BotCastRoot(Mob* tar, uint8 botLevel, uint32 iSpellTypes, BotSpell& botSpell, const bool& checked_los) { bool casted_spell = false; if (!tar->IsRooted() && tar->DontRootMeBefore() < Timer::GetCurrentTime()) { - if (!checked_los && !CheckLosFN(tar)) { + if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { return casted_spell; } @@ -1189,7 +1189,7 @@ bool Bot::BotCastHeal(Mob* tar, uint8 botLevel, uint8 botClass, BotSpell& botSpe bool Bot::BotCastMez(Mob* tar, uint8 botLevel, bool checked_los, BotSpell& botSpell, Raid* raid) { bool casted_spell = false; - if (!checked_los && !CheckLosFN(tar)) { + if (!checked_los && (!CheckLosFN(tar) || !CheckWaterLoS(tar))) { return false; } diff --git a/zone/client.h b/zone/client.h index 98ddd9c87..60ce67718 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1503,6 +1503,8 @@ public: bool GroupFollow(Client* inviter); inline bool GetRunMode() const { return runmode; } + virtual bool CheckWaterAutoFireLoS(Mob* m); + void SendReloadCommandMessages(); void SendItemRecastTimer(int32 recast_type, uint32 recast_delay = 0, bool in_ignore_casting_requirement = false); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 2c9e2e84a..1019c645f 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -51,6 +51,7 @@ #include "zone.h" #include "zonedb.h" #include "../common/events/player_event_logs.h" +#include "water_map.h" extern QueryServ* QServ; extern Zone* zone; @@ -336,7 +337,7 @@ bool Client::Process() { if (ranged_timer.Check(false)) { if (GetTarget() && (GetTarget()->IsNPC() || GetTarget()->IsClient()) && IsAttackAllowed(GetTarget())) { if (GetTarget()->InFrontMob(this, GetTarget()->GetX(), GetTarget()->GetY())) { - if (CheckLosFN(GetTarget())) { + if (CheckLosFN(GetTarget()) && CheckWaterAutoFireLoS(GetTarget())) { //client has built in los check, but auto fire does not.. done last. RangedAttack(GetTarget()); if (CheckDoubleRangedAttack()) @@ -356,7 +357,7 @@ bool Client::Process() { if (ranged_timer.Check(false)) { if (GetTarget() && (GetTarget()->IsNPC() || GetTarget()->IsClient()) && IsAttackAllowed(GetTarget())) { if (GetTarget()->InFrontMob(this, GetTarget()->GetX(), GetTarget()->GetY())) { - if (CheckLosFN(GetTarget())) { + if (CheckLosFN(GetTarget()) && CheckWaterAutoFireLoS(GetTarget())) { //client has built in los check, but auto fire does not.. done last. ThrowingAttack(GetTarget()); } @@ -2401,3 +2402,18 @@ void Client::SendGuildLFGuildStatus() worldserver.SendPacket(pack); safe_delete(pack); } + +bool Client::CheckWaterAutoFireLoS(Mob* m) +{ + if ( + !RuleB(Combat, WaterMatchRequiredForAutoFireLoS) || + !zone->watermap + ) { + return true; + } + + return ( + zone->watermap->InLiquid(GetPosition()) && + zone->watermap->InLiquid(m->GetPosition()) + ); +} diff --git a/zone/mob.h b/zone/mob.h index e802857bc..1762d913d 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -753,6 +753,7 @@ public: bool CheckLosFN(Mob* other); bool CheckLosFN(float posX, float posY, float posZ, float mobSize); static bool CheckLosFN(glm::vec3 posWatcher, float sizeWatcher, glm::vec3 posTarget, float sizeTarget); + virtual bool CheckWaterLoS(Mob* m); inline void SetLastLosState(bool value) { last_los_check = value; } inline bool CheckLastLosState() const { return last_los_check; } std::string GetMobDescription(); diff --git a/zone/spells.cpp b/zone/spells.cpp index 0acd34d50..d266cdbf5 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -101,7 +101,7 @@ Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) #include "mob_movement_manager.h" #include "client.h" #include "mob.h" - +#include "water_map.h" extern Zone* zone; extern volatile bool is_zone_loaded; @@ -2356,7 +2356,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in } // check line of sight to target if it's a detrimental spell - if(!spells[spell_id].npc_no_los && spell_target && IsDetrimentalSpell(spell_id) && !CheckLosFN(spell_target) && !IsHarmonySpell(spell_id) && spells[spell_id].target_type != ST_TargetOptional) + if (!spells[spell_id].npc_no_los && spell_target && IsDetrimentalSpell(spell_id) && (!CheckLosFN(spell_target) || !CheckWaterLoS(spell_target)) && !IsHarmonySpell(spell_id) && spells[spell_id].target_type != ST_TargetOptional) { LogSpells("Spell [{}]: cannot see target [{}]", spell_id, spell_target->GetName()); MessageString(Chat::Red,CANT_SEE_TARGET); @@ -7100,3 +7100,18 @@ const CombatRecord &Mob::GetCombatRecord() const { return m_combat_record; } + +bool Mob::CheckWaterLoS(Mob* m) +{ + if ( + !RuleB(Spells, WaterMatchRequiredForLoS) || + !zone->watermap + ) { + return true; + } + + return ( + zone->watermap->InLiquid(GetPosition()) && + zone->watermap->InLiquid(m->GetPosition()) + ); +}