From 1736b7501f479723bba9fcfa4435e1b55e499d09 Mon Sep 17 00:00:00 2001 From: Yehonal Date: Sat, 27 Sep 2025 16:07:03 +0200 Subject: refactor(Player): replace visibility detection calls with dedicated methods (#23025) --- apps/test-framework/run-core-tests.sh | 3 + src/server/game/Entities/Player/Player.cpp | 8 +- .../server/game/Commands/GmVisibleCommandTest.cpp | 218 +++++++++++++++++++++ 3 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 src/test/server/game/Commands/GmVisibleCommandTest.cpp diff --git a/apps/test-framework/run-core-tests.sh b/apps/test-framework/run-core-tests.sh index 0f72fd572f..fb558149d7 100644 --- a/apps/test-framework/run-core-tests.sh +++ b/apps/test-framework/run-core-tests.sh @@ -3,6 +3,9 @@ # shellcheck source-path=SCRIPTDIR CURRENT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +# Clean up gcda files to avoid false positives in coverage reports +find var/build/obj -name '*.gcda' -delete + # shellcheck source=../bash_shared/includes.sh source "$CURRENT_PATH/../bash_shared/includes.sh" diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index cc9a820fc7..02adb9ef0c 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -2243,7 +2243,7 @@ void Player::SetGameMaster(bool on) CombatStopWithPets(); SetPhaseMask(uint32(PHASEMASK_ANYWHERE), false); // see and visible in all phases - m_serverSideVisibilityDetect.SetValue(SERVERSIDE_VISIBILITY_GM, GetSession()->GetSecurity()); + SetServerSideVisibilityDetect(SERVERSIDE_VISIBILITY_GM, GetSession()->GetSecurity()); } else { @@ -2279,7 +2279,7 @@ void Player::SetGameMaster(bool on) UpdateArea(m_areaUpdateId); getHostileRefMgr().setOnlineOfflineState(true); - m_serverSideVisibilityDetect.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER); + SetServerSideVisibilityDetect(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER); } UpdateObjectVisibility(); @@ -2293,7 +2293,7 @@ void Player::SetGMVisible(bool on) { RemoveAurasDueToSpell(VISUAL_AURA); m_ExtraFlags &= ~PLAYER_EXTRA_GM_INVISIBLE; - m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER); + SetServerSideVisibility(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER); getHostileRefMgr().setOnlineOfflineState(false); CombatStopWithPets(); @@ -2302,7 +2302,7 @@ void Player::SetGMVisible(bool on) { AddAura(VISUAL_AURA, this); m_ExtraFlags |= PLAYER_EXTRA_GM_INVISIBLE; - m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GM, GetSession()->GetSecurity()); + SetServerSideVisibility(SERVERSIDE_VISIBILITY_GM, GetSession()->GetSecurity()); } } diff --git a/src/test/server/game/Commands/GmVisibleCommandTest.cpp b/src/test/server/game/Commands/GmVisibleCommandTest.cpp new file mode 100644 index 0000000000..8d4512ade5 --- /dev/null +++ b/src/test/server/game/Commands/GmVisibleCommandTest.cpp @@ -0,0 +1,218 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "Player.h" +#include "ScriptMgr.h" +#include "WorldSession.h" +#include "WorldMock.h" +#include "ObjectGuid.h" +#include "ScriptDefines/MiscScript.h" +#include "ScriptDefines/PlayerScript.h" +#include "ScriptDefines/WorldObjectScript.h" +#include "ScriptDefines/UnitScript.h" +#include "ScriptDefines/CommandScript.h" +#include "SharedDefines.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include +#include + +#ifndef TEST_F +#define TEST_F(fixture, name) void fixture##_##name() +#endif + +using namespace testing; + +namespace +{ +class TestVisibilityScript : public PlayerScript +{ +public: + TestVisibilityScript() : PlayerScript("TestVisibilityScript", { PLAYERHOOK_ON_SET_SERVER_SIDE_VISIBILITY }) { } + + void OnPlayerSetServerSideVisibility(Player* player, ServerSideVisibilityType& type, AccountTypes& sec) override + { + ++CallCount; + LastPlayer = player; + LastType = type; + LastSecurity = sec; + } + + static void EnsureRegistered() + { + if (!Instance) + Instance = new TestVisibilityScript(); + } + + static void Reset() + { + CallCount = 0; + LastPlayer = nullptr; + LastType = SERVERSIDE_VISIBILITY_GM; + LastSecurity = SEC_PLAYER; + } + + inline static TestVisibilityScript* Instance = nullptr; + inline static uint32 CallCount = 0; + inline static Player* LastPlayer = nullptr; + inline static ServerSideVisibilityType LastType = SERVERSIDE_VISIBILITY_GM; + inline static AccountTypes LastSecurity = SEC_PLAYER; +}; + +class TestPlayer : public Player +{ +public: + using Player::Player; + + void UpdateObjectVisibility(bool /*forced*/ = true, bool /*fromUpdate*/ = false) override { } + + void ForceInitValues(ObjectGuid::LowType guidLow = 1) + { + Object::_Create(guidLow, uint32(0), HighGuid::Player); + } +}; + +class GmVisibleCommandTest : public ::testing::Test +{ +protected: + void SetUp() override + { + EnsureScriptRegistriesInitialized(); + + TestVisibilityScript::EnsureRegistered(); + + originalWorld = sWorld.release(); + worldMock = new NiceMock(); + sWorld.reset(worldMock); + + static std::string emptyString; + ON_CALL(*worldMock, GetDataPath()).WillByDefault(ReturnRef(emptyString)); + ON_CALL(*worldMock, GetRealmName()).WillByDefault(ReturnRef(emptyString)); + ON_CALL(*worldMock, GetDefaultDbcLocale()).WillByDefault(Return(LOCALE_enUS)); + ON_CALL(*worldMock, getRate(_)).WillByDefault(Return(1.0f)); + ON_CALL(*worldMock, getBoolConfig(_)).WillByDefault(Return(false)); + ON_CALL(*worldMock, getIntConfig(_)).WillByDefault(Return(0)); + ON_CALL(*worldMock, getFloatConfig(_)).WillByDefault(Return(0.0f)); + ON_CALL(*worldMock, GetPlayerSecurityLimit()).WillByDefault(Return(SEC_PLAYER)); + + session = new WorldSession(1, "gm", 0, nullptr, SEC_GAMEMASTER, EXPANSION_WRATH_OF_THE_LICH_KING, + 0, LOCALE_enUS, 0, false, false, 0); + + player = new TestPlayer(session); + player->ForceInitValues(); + session->SetPlayer(player); + player->SetSession(session); + + TestVisibilityScript::Reset(); + } + + void TearDown() override + { + // Intentional leaks of session/player to avoid database access in destructors. + IWorld* currentWorld = sWorld.release(); + delete currentWorld; + worldMock = nullptr; + + sWorld.reset(originalWorld); + originalWorld = nullptr; + session = nullptr; + player = nullptr; + } + + void ExecuteCommand(std::string_view text) + { + if (text == ".gm visible off") + { + ApplyGmVisibleState(false); + } + else if (text == ".gm visible on") + { + ApplyGmVisibleState(true); + } + else + { + FAIL() << "Unsupported test command: " << text; + } + } + + static void EnsureScriptRegistriesInitialized() + { + static bool initialized = false; + if (!initialized) + { + ScriptRegistry::InitEnabledHooksIfNeeded(MISCHOOK_END); + ScriptRegistry::InitEnabledHooksIfNeeded(WORLDOBJECTHOOK_END); + ScriptRegistry::InitEnabledHooksIfNeeded(UNITHOOK_END); + ScriptRegistry::InitEnabledHooksIfNeeded(PLAYERHOOK_END); + ScriptRegistry::InitEnabledHooksIfNeeded(ALLCOMMANDHOOK_END); + initialized = true; + } + } + + IWorld* originalWorld = nullptr; + NiceMock* worldMock = nullptr; + WorldSession* session = nullptr; + TestPlayer* player = nullptr; + +private: + void ApplyGmVisibleState(bool makeVisible) + { + constexpr uint32 VISUAL_AURA = 37800; + + if (makeVisible) + { + player->RemoveAurasDueToSpell(VISUAL_AURA); + player->SetGMVisible(true); + } + else + { + player->AddAura(VISUAL_AURA, player); + player->SetGMVisible(false); + } + + player->UpdateObjectVisibility(); + } +}; + +TEST_F(GmVisibleCommandTest, SetsPlayerInvisibleAndInvokesHook) +{ + ExecuteCommand(".gm visible off"); + + EXPECT_EQ(TestVisibilityScript::CallCount, 1u); + EXPECT_EQ(TestVisibilityScript::LastPlayer, player); + EXPECT_EQ(TestVisibilityScript::LastType, SERVERSIDE_VISIBILITY_GM); + EXPECT_EQ(TestVisibilityScript::LastSecurity, session->GetSecurity()); + EXPECT_EQ(player->m_serverSideVisibility.GetValue(SERVERSIDE_VISIBILITY_GM), uint32(session->GetSecurity())); + EXPECT_FALSE(player->isGMVisible()); +} + +TEST_F(GmVisibleCommandTest, SetsPlayerVisibleAndInvokesHook) +{ + // Ensure the player starts from invisible state to test the opposite transition as well. + ExecuteCommand(".gm visible off"); + TestVisibilityScript::Reset(); + + ExecuteCommand(".gm visible on"); + + EXPECT_EQ(TestVisibilityScript::CallCount, 1u); + EXPECT_EQ(TestVisibilityScript::LastPlayer, player); + EXPECT_EQ(TestVisibilityScript::LastType, SERVERSIDE_VISIBILITY_GM); + EXPECT_EQ(TestVisibilityScript::LastSecurity, SEC_PLAYER); + EXPECT_EQ(player->m_serverSideVisibility.GetValue(SERVERSIDE_VISIBILITY_GM), uint32(SEC_PLAYER)); + EXPECT_TRUE(player->isGMVisible()); +} +} -- cgit v1.2.3