diff --git a/.gitignore b/.gitignore index 3777608d..e093ba4b 100644 --- a/.gitignore +++ b/.gitignore @@ -121,4 +121,4 @@ docker/__pycache__ docker-compose.override.yml -!/tests/TestBitStreams/*.bin +!*Test.bin diff --git a/CMakeLists.txt b/CMakeLists.txt index 34a6cd27..7dfef867 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,15 @@ endif() # Our output dir set(CMAKE_BINARY_DIR ${PROJECT_BINARY_DIR}) + +# TODO make this not have to override the build type directories +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) @@ -239,6 +248,11 @@ set(INCLUDED_DIRECTORIES "thirdparty/recastnavigation" "thirdparty/SQLite" "thirdparty/cpplinq" + + "tests" + "tests/dCommonTests" + "tests/dGameTests" + "tests/dGameTests/dComponentsTests" ) # Add system specfic includes for Apple, Windows and Other Unix OS' (including Linux) @@ -320,8 +334,6 @@ if (UNIX) endif() endif() -add_subdirectory(tests) - # Include all of our binary directories add_subdirectory(dWorldServer) add_subdirectory(dAuthServer) @@ -354,3 +366,7 @@ target_precompile_headers( tinyxml2 PRIVATE "$<$:${PROJECT_SOURCE_DIR}/thirdparty/tinyxml2/tinyxml2.h>" ) + +if (${__enable_testing__} MATCHES "1") + add_subdirectory(tests) +endif() diff --git a/CMakePresets.json b/CMakePresets.json index badb161d..15eb729e 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,131 +1,131 @@ { - "version": 3, - "cmakeMinimumRequired": { - "major": 3, - "minor": 14, - "patch": 0 - }, - "configurePresets": [ - { - "name": "default", - "displayName": "Default configure step", - "description": "Use 'build' dir and Unix makefiles", - "binaryDir": "${sourceDir}/build", - "generator": "Unix Makefiles" + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 14, + "patch": 0 }, - { - "name": "ci-ubuntu-20.04", - "displayName": "CI configure step for Ubuntu", - "description": "Same as default, Used in GitHub actions workflow", - "inherits": "default" - }, - { - "name": "ci-macos-11", - "displayName": "CI configure step for MacOS", - "description": "Same as default, Used in GitHub actions workflow", - "inherits": "default", - "cacheVariables": { - "OPENSSL_ROOT_DIR": "/usr/local/Cellar/openssl@3/3.0.5/" - } - }, - { - "name": "ci-windows-2022", - "displayName": "CI configure step for Windows", - "description": "Set architecture to 64-bit (b/c RakNet)", - "inherits": "default", - "generator": "Visual Studio 17 2022", - "architecture": { - "value": "x64" + "configurePresets": [ + { + "name": "default", + "displayName": "Default configure step", + "description": "Use 'build' dir and Unix makefiles", + "binaryDir": "${sourceDir}/build", + "generator": "Unix Makefiles" }, - "cacheVariables": { - "CMAKE_BUILD_TYPE": "RelWithDebInfo" + { + "name": "ci-ubuntu-20.04", + "displayName": "CI configure step for Ubuntu", + "description": "Same as default, Used in GitHub actions workflow", + "inherits": "default" + }, + { + "name": "ci-macos-11", + "displayName": "CI configure step for MacOS", + "description": "Same as default, Used in GitHub actions workflow", + "inherits": "default", + "cacheVariables": { + "OPENSSL_ROOT_DIR": "/usr/local/Cellar/openssl@3/3.0.5/" + } + }, + { + "name": "ci-windows-2022", + "displayName": "CI configure step for Windows", + "description": "Set architecture to 64-bit (b/c RakNet)", + "inherits": "default", + "generator": "Visual Studio 17 2022", + "architecture": { + "value": "x64" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + }, + { + "name": "windows-default", + "inherits": "ci-windows-2022", + "displayName": "Windows only Configure Settings", + "description": "Sets build and install directories", + "generator": "Ninja", + "architecture": { + "value": "x64", + "strategy": "external" + } } - }, - { - "name": "windows-default", - "inherits": "ci-windows-2022", - "displayName": "Windows only Configure Settings", - "description": "Sets build and install directories", - "generator": "Ninja", - "architecture": { - "value": "x64", - "strategy": "external" - } - } - ], - "buildPresets": [ - { - "name": "default", - "configurePreset": "default", - "displayName": "Default Build", - "description": "Default Build", - "jobs": 2 - }, - { - "name": "ci-windows-2022", - "configurePreset": "ci-windows-2022", - "displayName": "Windows CI Build", - "description": "This preset is used by the CI build on windows", - "configuration": "RelWithDebInfo", - "jobs": 2 - }, - { - "name": "ci-ubuntu-20.04", - "configurePreset": "ci-ubuntu-20.04", - "displayName": "Linux CI Build", - "description": "This preset is used by the CI build on linux", - "jobs": 2 - }, - { - "name": "ci-macos-11", - "configurePreset": "ci-macos-11", - "displayName": "MacOS CI Build", - "description": "This preset is used by the CI build on MacOS", - "jobs": 2 - } - ], - "testPresets": [ - { - "name": "ci-ubuntu-20.04", - "configurePreset": "ci-ubuntu-20.04", - "displayName": "CI Tests on Linux", - "description": "Runs all tests on a linux configuration", - "execution": { + ], + "buildPresets": [ + { + "name": "default", + "configurePreset": "default", + "displayName": "Default Build", + "description": "Default Build", "jobs": 2 }, - "output": { - "outputOnFailure": true - } - }, - { - "name": "ci-macos-11", - "configurePreset": "ci-macos-11", - "displayName": "CI Tests on MacOS", - "description": "Runs all tests on a Mac configuration", - "execution": { + { + "name": "ci-windows-2022", + "configurePreset": "ci-windows-2022", + "displayName": "Windows CI Build", + "description": "This preset is used by the CI build on windows", + "configuration": "RelWithDebInfo", "jobs": 2 }, - "output": { - "outputOnFailure": true + { + "name": "ci-ubuntu-20.04", + "configurePreset": "ci-ubuntu-20.04", + "displayName": "Linux CI Build", + "description": "This preset is used by the CI build on linux", + "jobs": 2 + }, + { + "name": "ci-macos-11", + "configurePreset": "ci-macos-11", + "displayName": "MacOS CI Build", + "description": "This preset is used by the CI build on MacOS", + "jobs": 2 } - }, - { - "name": "ci-windows-2022", - "configurePreset": "ci-windows-2022", - "displayName": "CI Tests on windows", - "description": "Runs all tests on a windows configuration", - "configuration": "RelWithDebInfo", + ], + "testPresets": [ + { + "name": "ci-ubuntu-20.04", + "configurePreset": "ci-ubuntu-20.04", + "displayName": "CI Tests on Linux", + "description": "Runs all tests on a linux configuration", "execution": { "jobs": 2 }, + "output": { + "outputOnFailure": true + } + }, + { + "name": "ci-macos-11", + "configurePreset": "ci-macos-11", + "displayName": "CI Tests on MacOS", + "description": "Runs all tests on a Mac configuration", + "execution": { + "jobs": 2 + }, + "output": { + "outputOnFailure": true + } + }, + { + "name": "ci-windows-2022", + "configurePreset": "ci-windows-2022", + "displayName": "CI Tests on windows", + "description": "Runs all tests on a windows configuration", + "configuration": "RelWithDebInfo", + "execution": { + "jobs": 2 + }, + "output": { + "outputOnFailure": true + }, "filter": { "exclude": { "name": "((example)|(minigzip))+" } - }, - "output": { - "outputOnFailure": true + } } - } - ] -} + ] + } diff --git a/CMakeVariables.txt b/CMakeVariables.txt index e68e826e..94e9cd20 100644 --- a/CMakeVariables.txt +++ b/CMakeVariables.txt @@ -18,3 +18,5 @@ __dynamic=1 # Set __compile_backtrace__ to 1 to compile the backtrace library instead of using system libraries. __maria_db_connector_compile_jobs__=1 # Set to the number of jobs (make -j equivalent) to compile the mariadbconn files with. +__enable_testing__=1 +# When set to 1 and uncommented, compiling and linking testing folders and libraries will be done. diff --git a/README.md b/README.md index 8f9eaf8d..e588d341 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,10 @@ This was done make sure that older and incomplete clients wouldn't produce false If you're using a DLU client you'll have to go into the "CMakeVariables.txt" file and change the NET_VERSION variable to 171023 to match the modified client's version number. +### Enabling testing +While it is highly recommended to enable testing, if you would like to save compilation time, you'll want to comment out the enable_testing variable in CMakeVariables.txt. +It is recommended that after building and if testing is enabled, to run `ctest` and make sure all the tests pass. + ### Using Docker Refer to [Docker.md](/Docker.md). diff --git a/dAuthServer/CMakeLists.txt b/dAuthServer/CMakeLists.txt index 353f2a54..00fa6e7a 100644 --- a/dAuthServer/CMakeLists.txt +++ b/dAuthServer/CMakeLists.txt @@ -1,4 +1,2 @@ -set(DAUTHSERVER_SOURCES "AuthServer.cpp") - -add_executable(AuthServer ${DAUTHSERVER_SOURCES}) +add_executable(AuthServer "AuthServer.cpp") target_link_libraries(AuthServer ${COMMON_LIBRARIES}) diff --git a/dChatServer/CMakeLists.txt b/dChatServer/CMakeLists.txt index 948593fb..9a47803d 100644 --- a/dChatServer/CMakeLists.txt +++ b/dChatServer/CMakeLists.txt @@ -1,6 +1,10 @@ -set(DCHATSERVER_SOURCES "ChatPacketHandler.cpp" - "ChatServer.cpp" - "PlayerContainer.cpp") - -add_executable(ChatServer ${DCHATSERVER_SOURCES}) -target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter) +set(DCHATSERVER_SOURCES + "ChatPacketHandler.cpp" + "PlayerContainer.cpp" +) + +add_executable(ChatServer "ChatServer.cpp") +add_library(dChatServer ${DCHATSERVER_SOURCES}) + +target_link_libraries(dChatServer ${COMMON_LIBRARIES} dChatFilter) +target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer) diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 8b9ae42d..3cf206da 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -138,25 +138,17 @@ void DestroyableComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsIn if (m_IsSmashable) { outBitStream->Write(m_HasBricks); - - if (m_ExplodeFactor != 1.0f) { - outBitStream->Write1(); - outBitStream->Write(m_ExplodeFactor); - } else { - outBitStream->Write0(); - } + outBitStream->Write(m_ExplodeFactor != 1.0f); + if (m_ExplodeFactor != 1.0f) outBitStream->Write(m_ExplodeFactor); } } - m_DirtyHealth = false; } + outBitStream->Write(m_DirtyThreatList || bIsInitialUpdate); if (m_DirtyThreatList || bIsInitialUpdate) { - outBitStream->Write1(); outBitStream->Write(m_HasThreats); m_DirtyThreatList = false; - } else { - outBitStream->Write0(); } } @@ -438,7 +430,6 @@ void DestroyableComponent::AddEnemyFaction(int32_t factionID) { void DestroyableComponent::SetIsSmashable(bool value) { m_DirtyHealth = true; m_IsSmashable = value; - //m_HasBricks = value; } void DestroyableComponent::SetAttacksToBlock(const uint32_t value) { diff --git a/dGame/dComponents/DestroyableComponent.h b/dGame/dComponents/DestroyableComponent.h index a403717b..b8e81b33 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -239,7 +239,7 @@ public: * Sets the multiplier for the explosion that's visible when the bricks fly out when this entity is smashed * @param value the multiplier for the explosion that's visible when the bricks fly out when this entity is smashed */ - void SetExplodeFactor(float value); + void SetExplodeFactor(float value) { m_ExplodeFactor = value; }; /** * Returns the current multiplier for explosions @@ -414,6 +414,14 @@ public: */ void AddOnHitCallback(const std::function& callback); + /** + * Pushes a faction back to the list of factions. + * @param value Faction to add to list. + * + * This method should only be used for testing. Use AddFaction(int32_t, bool) for adding a faction properly. + */ + void AddFactionNoLookup(int32_t faction) { m_FactionIDs.push_back(faction); }; + private: /** * Whether or not the health should be serialized diff --git a/dMasterServer/CMakeLists.txt b/dMasterServer/CMakeLists.txt index 08fc63db..2161681f 100644 --- a/dMasterServer/CMakeLists.txt +++ b/dMasterServer/CMakeLists.txt @@ -1,9 +1,13 @@ -set(DMASTERSERVER_SOURCES "InstanceManager.cpp" - "MasterServer.cpp" - "ObjectIDManager.cpp") - -add_executable(MasterServer ${DMASTERSERVER_SOURCES}) -target_link_libraries(MasterServer ${COMMON_LIBRARIES}) +set(DMASTERSERVER_SOURCES + "InstanceManager.cpp" + "ObjectIDManager.cpp" +) + +add_library(dMasterServer ${DMASTERSERVER_SOURCES}) +add_executable(MasterServer "MasterServer.cpp") + +target_link_libraries(dMasterServer ${COMMON_LIBRARIES}) +target_link_libraries(MasterServer ${COMMON_LIBRARIES} dMasterServer) if(WIN32) add_dependencies(MasterServer WorldServer AuthServer ChatServer) diff --git a/dNet/dServer.cpp b/dNet/dServer.cpp index 4c032f33..c46b156c 100644 --- a/dNet/dServer.cpp +++ b/dNet/dServer.cpp @@ -50,7 +50,6 @@ dServer::dServer(const std::string& ip, int port, int instanceID, int maxConnect mNetIDManager = nullptr; mReplicaManager = nullptr; mServerType = serverType; - //Attempt to start our server here: mIsOkay = Startup(); @@ -193,7 +192,10 @@ bool dServer::Startup() { } void dServer::Shutdown() { - mPeer->Shutdown(1000); + if (mPeer) { + mPeer->Shutdown(1000); + RakNetworkFactory::DestroyRakPeerInterface(mPeer); + } if (mNetIDManager) { delete mNetIDManager; @@ -205,10 +207,9 @@ void dServer::Shutdown() { mReplicaManager = nullptr; } - //RakNetworkFactory::DestroyRakPeerInterface(mPeer); //Not needed, we already called Shutdown ourselves. - if (mServerType != ServerType::Master) { + if (mServerType != ServerType::Master && mMasterPeer) { mMasterPeer->Shutdown(1000); - //RakNetworkFactory::DestroyRakPeerInterface(mMasterPeer); + RakNetworkFactory::DestroyRakPeerInterface(mMasterPeer); } } diff --git a/dNet/dServer.h b/dNet/dServer.h index bd052f86..264932ee 100644 --- a/dNet/dServer.h +++ b/dNet/dServer.h @@ -15,6 +15,8 @@ enum class ServerType : uint32_t { class dServer { public: + // Default constructor should only used for testing! + dServer() {}; dServer(const std::string& ip, int port, int instanceID, int maxConnections, bool isInternal, bool useEncryption, dLogger* logger, const std::string masterIP, int masterPort, ServerType serverType, unsigned int zoneID = 0); ~dServer(); @@ -22,7 +24,7 @@ public: Packet* Receive(); void DeallocatePacket(Packet* packet); void DeallocateMasterPacket(Packet* packet); - void Send(RakNet::BitStream* bitStream, const SystemAddress& sysAddr, bool broadcast); + virtual void Send(RakNet::BitStream* bitStream, const SystemAddress& sysAddr, bool broadcast); void SendToMaster(RakNet::BitStream* bitStream); void Disconnect(const SystemAddress& sysAddr, uint32_t disconNotifyID); @@ -55,10 +57,10 @@ private: bool ConnectToMaster(); private: - dLogger* mLogger; - RakPeerInterface* mPeer; - ReplicaManager* mReplicaManager; - NetworkIDManager* mNetIDManager; + dLogger* mLogger = nullptr; + RakPeerInterface* mPeer = nullptr; + ReplicaManager* mReplicaManager = nullptr; + NetworkIDManager* mNetIDManager = nullptr; SocketDescriptor mSocketDescriptor; std::string mIP; int mPort; @@ -71,7 +73,7 @@ private: bool mMasterConnectionActive; ServerType mServerType; - RakPeerInterface* mMasterPeer; + RakPeerInterface* mMasterPeer = nullptr; SocketDescriptor mMasterSocketDescriptor; SystemAddress mMasterSystemAddress; std::string mMasterIP; diff --git a/dWorldServer/CMakeLists.txt b/dWorldServer/CMakeLists.txt index fcf29838..c616da87 100644 --- a/dWorldServer/CMakeLists.txt +++ b/dWorldServer/CMakeLists.txt @@ -1,6 +1,11 @@ -set(DWORLDSERVER_SOURCES "ObjectIDManager.cpp" - "PerformanceManager.cpp" - "WorldServer.cpp") +set(DWORLDSERVER_SOURCES + "ObjectIDManager.cpp" + "PerformanceManager.cpp" +) + +add_library(dWorldServer ${DWORLDSERVER_SOURCES}) +add_executable(WorldServer "WorldServer.cpp") + +target_link_libraries(dWorldServer ${COMMON_LIBRARIES}) +target_link_libraries(WorldServer ${COMMON_LIBRARIES} dChatFilter dGame dZoneManager dPhysics Detour Recast tinyxml2 dWorldServer dNavigation) -add_executable(WorldServer ${DWORLDSERVER_SOURCES}) -target_link_libraries(WorldServer ${COMMON_LIBRARIES} dChatFilter dGame dZoneManager Detour Recast dPhysics tinyxml2 dNavigation) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fb1ed5ac..9ba75a2f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,34 +1,21 @@ -# create the testing file and list of tests -create_test_sourcelist (Tests - CommonCxxTests.cpp - AMFDeserializeTests.cpp - TestNiPoint3.cpp - TestLDFFormat.cpp - TestEncoding.cpp +message (STATUS "Testing is enabled. Fetching gtest...") +enable_testing() + +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.12.1 ) -# add the executable -add_executable (CommonCxxTests ${Tests}) -target_link_libraries(CommonCxxTests ${COMMON_LIBRARIES}) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -# remove the test driver source file -set (TestsToRun ${Tests}) -remove (TestsToRun CommonCxxTests.cpp) +FetchContent_MakeAvailable(GoogleTest) +include(GoogleTest) -# Copy test files to testing directory -configure_file( - ${CMAKE_SOURCE_DIR}/tests/TestBitStreams/AMFBitStreamTest.bin ${PROJECT_BINARY_DIR}/tests/AMFBitStreamTest.bin - COPYONLY -) +message(STATUS "gtest fetched and is now ready.") -configure_file( - ${CMAKE_SOURCE_DIR}/tests/TestBitStreams/AMFBitStreamUnimplementedTest.bin ${PROJECT_BINARY_DIR}/tests/AMFBitStreamUnimplementedTest.bin - COPYONLY -) - -# Add all the ADD_TEST for each test -foreach (test ${TestsToRun}) - get_filename_component (TName ${test} NAME_WE) - add_test (NAME ${TName} COMMAND CommonCxxTests ${TName}) - set_property(TEST ${TName} PROPERTY ENVIRONMENT CTEST_OUTPUT_ON_FAILURE=1) -endforeach () +# Add the subdirectories +add_subdirectory(dCommonTests) +add_subdirectory(dGameTests) diff --git a/tests/CommonCxxTests.cpp b/tests/CommonCxxTests.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/CommonCxxTests.h b/tests/CommonCxxTests.h deleted file mode 100644 index f1894927..00000000 --- a/tests/CommonCxxTests.h +++ /dev/null @@ -1,4 +0,0 @@ -#include - -#define ASSERT_EQ(a,b) { if (!(a == b)) { printf("Failed assertion: " #a " == " #b " \n in %s:%d\n", __FILE__, __LINE__); return 1; }} -#define ASSERT_NE(a,b) { if (!(a != b)) { printf("Failed assertion: " #a " != " #b " \n in %s:%d\n", __FILE__, __LINE__); return 1; }} diff --git a/tests/TestEncoding.cpp b/tests/TestEncoding.cpp deleted file mode 100644 index c23e2db6..00000000 --- a/tests/TestEncoding.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include - -#include "GeneralUtils.h" -#include "CommonCxxTests.h" - -int TestEncoding(int argc, char** const argv) { - std::string x = "Hello World!"; - std::string_view v(x); - - uint32_t out; - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 'H'); - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 'e'); - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 'l'); - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 'l'); - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 'o'); - ASSERT_EQ(GeneralUtils::_NextUTF8Char(v, out), true); - - x = u8"Frühling"; - v = x; - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'F'); - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'r'); - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'ü'); - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'h'); - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'l'); - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'i'); - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'n'); - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'g'); - ASSERT_EQ(GeneralUtils::_NextUTF8Char(v, out), false); - - x = "中文字"; - v = x; - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'中'); - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'文'); - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'字'); - ASSERT_EQ(GeneralUtils::_NextUTF8Char(v, out), false); - - x = "👨‍⚖️"; - v = x; - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 0x1F468); - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 0x200D); - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 0x2696); - GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 0xFE0F); - ASSERT_EQ(GeneralUtils::_NextUTF8Char(v, out), false); - - ASSERT_EQ(GeneralUtils::UTF8ToUTF16("Hello World!"), u"Hello World!"); - ASSERT_EQ(GeneralUtils::UTF8ToUTF16("Frühling"), u"Frühling"); - ASSERT_EQ(GeneralUtils::UTF8ToUTF16("中文字"), u"中文字"); - ASSERT_EQ(GeneralUtils::UTF8ToUTF16("👨‍⚖️"), u"👨‍⚖️"); - - return 0; -} diff --git a/tests/AMFDeserializeTests.cpp b/tests/dCommonTests/AMFDeserializeTests.cpp similarity index 83% rename from tests/AMFDeserializeTests.cpp rename to tests/dCommonTests/AMFDeserializeTests.cpp index 8d4974a3..a823b0dc 100644 --- a/tests/AMFDeserializeTests.cpp +++ b/tests/dCommonTests/AMFDeserializeTests.cpp @@ -1,52 +1,65 @@ -#include #include -#include #include +#include #include "AMFDeserialize.h" #include "AMFFormat.h" -#include "CommonCxxTests.h" +/** + * Helper method that all tests use to get their respective AMF. + */ std::unique_ptr ReadFromBitStream(RakNet::BitStream* bitStream) { AMFDeserialize deserializer; std::unique_ptr returnValue(deserializer.Read(bitStream)); return returnValue; } -int ReadAMFUndefinedFromBitStream() { - CBITSTREAM; +/** + * @brief Test reading an AMFUndefined value from a BitStream. + */ +TEST(dCommonTests, AMFDeserializeAMFUndefinedTest) { + CBITSTREAM bitStream.Write(0x00); std::unique_ptr res(ReadFromBitStream(&bitStream)); ASSERT_EQ(res->GetValueType(), AMFValueType::AMFUndefined); - return 0; } -int ReadAMFNullFromBitStream() { - CBITSTREAM; +/** + * @brief Test reading an AMFNull value from a BitStream. + * + */ +TEST(dCommonTests, AMFDeserializeAMFNullTest) { + CBITSTREAM bitStream.Write(0x01); std::unique_ptr res(ReadFromBitStream(&bitStream)); ASSERT_EQ(res->GetValueType(), AMFValueType::AMFNull); - return 0; } -int ReadAMFFalseFromBitStream() { - CBITSTREAM; +/** + * @brief Test reading an AMFFalse value from a BitStream. + */ +TEST(dCommonTests, AMFDeserializeAMFFalseTest) { + CBITSTREAM bitStream.Write(0x02); std::unique_ptr res(ReadFromBitStream(&bitStream)); ASSERT_EQ(res->GetValueType(), AMFValueType::AMFFalse); - return 0; } -int ReadAMFTrueFromBitStream() { - CBITSTREAM; +/** + * @brief Test reading an AMFTrue value from a BitStream. + */ +TEST(dCommonTests, AMFDeserializeAMFTrueTest) { + CBITSTREAM bitStream.Write(0x03); std::unique_ptr res(ReadFromBitStream(&bitStream)); ASSERT_EQ(res->GetValueType(), AMFValueType::AMFTrue); - return 0; } -int ReadAMFIntegerFromBitStream() { - CBITSTREAM; +/** + * @brief Test reading an AMFInteger value from a BitStream. + */ +TEST(dCommonTests, AMFDeserializeAMFIntegerTest) { + CBITSTREAM { bitStream.Write(0x04); // 127 == 01111111 @@ -91,21 +104,25 @@ int ReadAMFIntegerFromBitStream() { // Check that 2 byte max can be read correctly ASSERT_EQ(static_cast(res.get())->GetIntegerValue(), 16383); } - return 0; } -int ReadAMFDoubleFromBitStream() { - CBITSTREAM; +/** + * @brief Test reading an AMFDouble value from a BitStream. + */ +TEST(dCommonTests, AMFDeserializeAMFDoubleTest) { + CBITSTREAM bitStream.Write(0x05); bitStream.Write(25346.4f); std::unique_ptr res(ReadFromBitStream(&bitStream)); ASSERT_EQ(res->GetValueType(), AMFValueType::AMFDouble); ASSERT_EQ(static_cast(res.get())->GetDoubleValue(), 25346.4f); - return 0; } -int ReadAMFStringFromBitStream() { - CBITSTREAM; +/** + * @brief Test reading an AMFString value from a BitStream. + */ +TEST(dCommonTests, AMFDeserializeAMFStringTest) { + CBITSTREAM bitStream.Write(0x06); bitStream.Write(0x0F); std::string toWrite = "stateID"; @@ -113,11 +130,13 @@ int ReadAMFStringFromBitStream() { std::unique_ptr res(ReadFromBitStream(&bitStream)); ASSERT_EQ(res->GetValueType(), AMFValueType::AMFString); ASSERT_EQ(static_cast(res.get())->GetStringValue(), "stateID"); - return 0; } -int ReadAMFArrayFromBitStream() { - CBITSTREAM; +/** + * @brief Test reading an AMFArray value from a BitStream. + */ +TEST(dCommonTests, AMFDeserializeAMFArrayTest) { + CBITSTREAM // Test empty AMFArray bitStream.Write(0x09); bitStream.Write(0x01); @@ -149,15 +168,15 @@ int ReadAMFArrayFromBitStream() { ASSERT_EQ(static_cast(res.get())->FindValue("BehaviorID")->GetStringValue(), "10447"); ASSERT_EQ(static_cast(res.get())->GetValueAt(0)->GetStringValue(), "10447"); } - // Test a dense array - return 0; } /** - * This test checks that if we recieve an unimplemented AMFValueType + * @brief This test checks that if we recieve an unimplemented AMFValueType * we correctly throw an error and can actch it. + * */ -int TestUnimplementedAMFValues() { +#pragma message("-- The AMFDeserializeUnimplementedValuesTest causes a known memory leak of 880 bytes since it throws errors! --") +TEST(dCommonTests, AMFDeserializeUnimplementedValuesTest) { std::vector unimplementedValues = { AMFValueType::AMFXMLDoc, AMFValueType::AMFDate, @@ -196,13 +215,15 @@ int TestUnimplementedAMFValues() { } catch (AMFValueType unimplementedValueType) { caughtException = true; } - std::cout << "Testing unimplemented value " << amfValueType << " Did we catch an exception: " << (caughtException ? "YES" : "NO") << std::endl; + ASSERT_EQ(caughtException, true); } - return 0; } -int TestLiveCapture() { +/** + * @brief Test reading a packet capture from live from a BitStream + */ +TEST(dCommonTests, AMFDeserializeLivePacketTest) { std::ifstream testFileStream; testFileStream.open("AMFBitStreamTest.bin", std::ios::binary); @@ -267,9 +288,9 @@ int TestLiveCapture() { auto actionID = firstStrip->FindValue("id"); - ASSERT_EQ(actionID->GetDoubleValue(), 0.0f) + ASSERT_EQ(actionID->GetDoubleValue(), 0.0f); - auto uiArray = firstStrip->FindValue("ui"); + auto uiArray = firstStrip->FindValue("ui"); auto xPos = uiArray->FindValue("x"); auto yPos = uiArray->FindValue("y"); @@ -279,9 +300,9 @@ int TestLiveCapture() { auto stripID = firstStrip->FindValue("id"); - ASSERT_EQ(stripID->GetDoubleValue(), 0.0f) + ASSERT_EQ(stripID->GetDoubleValue(), 0.0f); - auto firstAction = dynamic_cast(actionsInFirstStrip[0]); + auto firstAction = dynamic_cast(actionsInFirstStrip[0]); auto firstType = firstAction->FindValue("Type"); @@ -318,17 +339,17 @@ int TestLiveCapture() { auto thirdDistance = thirdAction->FindValue("Distance"); ASSERT_EQ(thirdDistance->GetDoubleValue(), 25.0f); - - return 0; } -int TestNullStream() { +/** + * @brief Tests that having no BitStream returns a nullptr. + */ +TEST(dCommonTests, AMFDeserializeNullTest) { auto result = ReadFromBitStream(nullptr); ASSERT_EQ(result.get(), nullptr); - return 0; } -int TestBadConversion() { +TEST(dCommonTests, AMFBadConversionTest) { std::ifstream testFileStream; testFileStream.open("AMFBitStreamTest.bin", std::ios::binary); @@ -360,30 +381,6 @@ int TestBadConversion() { // Value is out of bounds ASSERT_EQ(result->GetValueAt(1), nullptr); - - return 0; -} - -int AMFDeserializeTests(int argc, char** const argv) { - std::cout << "Checking that using a null bitstream doesnt cause exception" << std::endl; - if (TestNullStream()) return 1; - std::cout << "passed nullptr test, checking basic tests" << std::endl; - if (ReadAMFUndefinedFromBitStream() != 0) return 1; - if (ReadAMFNullFromBitStream() != 0) return 1; - if (ReadAMFFalseFromBitStream() != 0) return 1; - if (ReadAMFTrueFromBitStream() != 0) return 1; - if (ReadAMFIntegerFromBitStream() != 0) return 1; - if (ReadAMFDoubleFromBitStream() != 0) return 1; - if (ReadAMFStringFromBitStream() != 0) return 1; - if (ReadAMFArrayFromBitStream() != 0) return 1; - std::cout << "Passed basic test, checking live capture" << std::endl; - if (TestLiveCapture() != 0) return 1; - std::cout << "Passed live capture, checking unimplemented amf values" << std::endl; - if (TestUnimplementedAMFValues() != 0) return 1; - std::cout << "Passed unimplemented values, checking poor casting" << std::endl; - if (TestBadConversion() != 0) return 1; - std::cout << "Passed all tests." << std::endl; - return 0; } /** diff --git a/tests/dCommonTests/CMakeLists.txt b/tests/dCommonTests/CMakeLists.txt new file mode 100644 index 00000000..86c00c58 --- /dev/null +++ b/tests/dCommonTests/CMakeLists.txt @@ -0,0 +1,19 @@ +set(DCOMMONTEST_SOURCES + "AMFDeserializeTests.cpp" + "TestLDFFormat.cpp" + "TestNiPoint3.cpp" + "TestEncoding.cpp" +) + +# Set our executable +add_executable(dCommonTests ${DCOMMONTEST_SOURCES}) + +# Link needed libraries +target_link_libraries(dCommonTests ${COMMON_LIBRARIES} GTest::gtest_main) + +# Copy test files to testing directory +add_subdirectory(TestBitStreams) +file(COPY ${TESTBITSTREAMS} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + +# Discover the tests +gtest_discover_tests(dCommonTests) diff --git a/tests/TestBitStreams/AMFBitStreamTest.bin b/tests/dCommonTests/TestBitStreams/AMFBitStreamTest.bin similarity index 100% rename from tests/TestBitStreams/AMFBitStreamTest.bin rename to tests/dCommonTests/TestBitStreams/AMFBitStreamTest.bin diff --git a/tests/TestBitStreams/AMFBitStreamUnimplementedTest.bin b/tests/dCommonTests/TestBitStreams/AMFBitStreamUnimplementedTest.bin similarity index 100% rename from tests/TestBitStreams/AMFBitStreamUnimplementedTest.bin rename to tests/dCommonTests/TestBitStreams/AMFBitStreamUnimplementedTest.bin diff --git a/tests/dCommonTests/TestBitStreams/CMakeLists.txt b/tests/dCommonTests/TestBitStreams/CMakeLists.txt new file mode 100644 index 00000000..93606df6 --- /dev/null +++ b/tests/dCommonTests/TestBitStreams/CMakeLists.txt @@ -0,0 +1,11 @@ +set(TESTBITSTREAMS + "AMFBitStreamTest.bin" + "AMFBitStreamUnimplementedTest.bin" +) + +# Get the folder name and prepend it to the files above +get_filename_component(thisFolderName ${CMAKE_CURRENT_SOURCE_DIR} NAME) +list(TRANSFORM TESTBITSTREAMS PREPEND "${thisFolderName}/") + +# Export our list of files +set(TESTBITSTREAMS ${TESTBITSTREAMS} PARENT_SCOPE) diff --git a/tests/dCommonTests/TestEncoding.cpp b/tests/dCommonTests/TestEncoding.cpp new file mode 100644 index 00000000..c103ccbf --- /dev/null +++ b/tests/dCommonTests/TestEncoding.cpp @@ -0,0 +1,68 @@ +#include +#include +#include + +#include "GeneralUtils.h" + +class EncodingTest : public ::testing::Test { +protected: + std::string originalWord; + std::string_view originalWordSv; + uint32_t out; +}; + +TEST_F(EncodingTest, TestEncodingHello) { + originalWord = "Hello World!"; + originalWordSv = originalWord; + + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'H'); + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'e'); + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'l'); + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'l'); + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'o'); + EXPECT_EQ(GeneralUtils::_NextUTF8Char(originalWordSv, out), true); + + EXPECT_EQ(GeneralUtils::UTF8ToUTF16("Hello World!"), u"Hello World!"); +}; + +TEST_F(EncodingTest, TestEncodingUmlaut) { + originalWord = u8"Frühling"; + originalWordSv = originalWord; + + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'F'); + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'r'); + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'ü'); + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'h'); + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'l'); + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'i'); + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'n'); + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'g'); + EXPECT_EQ(GeneralUtils::_NextUTF8Char(originalWordSv, out), false); + + EXPECT_EQ(GeneralUtils::UTF8ToUTF16("Frühling"), u"Frühling"); +}; + +TEST_F(EncodingTest, TestEncodingChinese) { + originalWord = "中文字"; + originalWordSv = originalWord; + + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'中'); + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'文'); + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'字'); + EXPECT_EQ(GeneralUtils::_NextUTF8Char(originalWordSv, out), false); + + EXPECT_EQ(GeneralUtils::UTF8ToUTF16("中文字"), u"中文字"); +}; + +TEST_F(EncodingTest, TestEncodingEmoji) { + originalWord = "👨‍⚖️"; + originalWordSv = originalWord; + + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x1F468); + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x200D); + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x2696); + GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0xFE0F); + EXPECT_EQ(GeneralUtils::_NextUTF8Char(originalWordSv, out), false); + + EXPECT_EQ(GeneralUtils::UTF8ToUTF16("👨‍⚖️"), u"👨‍⚖️"); +}; diff --git a/tests/TestLDFFormat.cpp b/tests/dCommonTests/TestLDFFormat.cpp similarity index 69% rename from tests/TestLDFFormat.cpp rename to tests/dCommonTests/TestLDFFormat.cpp index a7433e96..647c3cbf 100644 --- a/tests/TestLDFFormat.cpp +++ b/tests/dCommonTests/TestLDFFormat.cpp @@ -1,14 +1,10 @@ #include "LDFFormat.h" -#include "CommonCxxTests.h" +#include /** * @brief Test parsing an LDF value - * - * @param argc Number of command line arguments for this test - * @param argv Command line arguments - * @return 0 on success, non-zero on failure */ -int TestLDFFormat(int argc, char** const argv) { +TEST(dCommonTests, LDFTest) { // Create auto* data = LDFBaseData::DataFromString("KEY=0:VALUE"); @@ -26,6 +22,4 @@ int TestLDFFormat(int argc, char** const argv) { // Cleanup the object delete data; - - return 0; } diff --git a/tests/TestNiPoint3.cpp b/tests/dCommonTests/TestNiPoint3.cpp similarity index 59% rename from tests/TestNiPoint3.cpp rename to tests/dCommonTests/TestNiPoint3.cpp index d1588b8b..33cd51d2 100644 --- a/tests/TestNiPoint3.cpp +++ b/tests/dCommonTests/TestNiPoint3.cpp @@ -1,13 +1,14 @@ -#include +#include #include "NiPoint3.h" -#include "CommonCxxTests.h" -int TestNiPoint3(int argc, char** const argv) { +/** + * @brief Basic test for NiPoint3 functionality + * + */ +TEST(dCommonTests, NiPoint3Test) { // Check that Unitize works ASSERT_EQ(NiPoint3(3, 0, 0).Unitize(), NiPoint3::UNIT_X); // Check what unitize does to a vector of length 0 ASSERT_EQ(NiPoint3::ZERO.Unitize(), NiPoint3::ZERO); - // If we get here, all was successful - return 0; } diff --git a/tests/dGameTests/CMakeLists.txt b/tests/dGameTests/CMakeLists.txt new file mode 100644 index 00000000..68192b3f --- /dev/null +++ b/tests/dGameTests/CMakeLists.txt @@ -0,0 +1,14 @@ +set(DGAMETEST_SOURCES + "GameDependencies.cpp" +) + +add_subdirectory(dComponentsTests) +list(APPEND DGAMETEST_SOURCES ${DCOMPONENTS_TESTS}) + +# Add the executable. Remember to add all tests above this! +add_executable(dGameTests ${DGAMETEST_SOURCES}) + +target_link_libraries(dGameTests ${COMMON_LIBRARIES} GTest::gtest_main dGame dZoneManager dPhysics Detour Recast tinyxml2 dWorldServer dChatFilter dNavigation) + +# Discover the tests +gtest_discover_tests(dGameTests) diff --git a/tests/dGameTests/GameDependencies.cpp b/tests/dGameTests/GameDependencies.cpp new file mode 100644 index 00000000..8a572668 --- /dev/null +++ b/tests/dGameTests/GameDependencies.cpp @@ -0,0 +1,15 @@ +#include "GameDependencies.h" + +namespace Game { + dLogger* logger; + dServer* server; + dZoneManager* zoneManager; + dpWorld* physicsWorld; + dChatFilter* chatFilter; + dConfig* config; + dLocale* locale; + std::mt19937 randomEngine; + RakPeerInterface* chatServer; + AssetManager* assetManager; + SystemAddress chatSysAddr; +} diff --git a/tests/dGameTests/GameDependencies.h b/tests/dGameTests/GameDependencies.h new file mode 100644 index 00000000..593ec0fc --- /dev/null +++ b/tests/dGameTests/GameDependencies.h @@ -0,0 +1,43 @@ +#ifndef __GAMEDEPENDENCIES__H__ +#define __GAMEDEPENDENCIES__H__ + +#include "Game.h" +#include "dLogger.h" +#include "dServer.h" +#include "EntityManager.h" +class dZoneManager; +class AssetManager; +#include + +class dServerMock : public dServer { +public: + dServerMock() {}; + ~dServerMock() {}; + void Send(RakNet::BitStream* bitStream, const SystemAddress& sysAddr, bool broadcast) override {}; +}; + +class GameDependenciesTest : public ::testing::Test { +protected: + void SetUpDependencies() { + info.pos = NiPoint3::ZERO; + info.rot = NiQuaternion::IDENTITY; + info.scale = 1.0f; + info.spawner = nullptr; + info.lot = 999; + Game::logger = new dLogger("./testing.log", true, true); + Game::server = new dServerMock(); + } + + void TearDownDependencies() { + if (Game::server) delete Game::server; + delete EntityManager::Instance(); + if (Game::logger) { + Game::logger->Flush(); + delete Game::logger; + } + } + + EntityInfo info; +}; + +#endif //!__GAMEDEPENDENCIES__H__ diff --git a/tests/dGameTests/dComponentsTests/CMakeLists.txt b/tests/dGameTests/dComponentsTests/CMakeLists.txt new file mode 100644 index 00000000..17e69a2f --- /dev/null +++ b/tests/dGameTests/dComponentsTests/CMakeLists.txt @@ -0,0 +1,10 @@ +set(DCOMPONENTS_TESTS + "DestroyableComponentTests.cpp" +) + +# Get the folder name and prepend it to the files above +get_filename_component(thisFolderName ${CMAKE_CURRENT_SOURCE_DIR} NAME) +list(TRANSFORM DCOMPONENTS_TESTS PREPEND "${thisFolderName}/") + +# Export to parent scope +set(DCOMPONENTS_TESTS ${DCOMPONENTS_TESTS} PARENT_SCOPE) diff --git a/tests/dGameTests/dComponentsTests/DestroyableComponentTests.cpp b/tests/dGameTests/dComponentsTests/DestroyableComponentTests.cpp new file mode 100644 index 00000000..97288909 --- /dev/null +++ b/tests/dGameTests/dComponentsTests/DestroyableComponentTests.cpp @@ -0,0 +1,300 @@ +#include "GameDependencies.h" +#include + +#include "BitStream.h" +#include "DestroyableComponent.h" +#include "Entity.h" + +class DestroyableTest : public GameDependenciesTest { +protected: + Entity* baseEntity; + DestroyableComponent* destroyableComponent; + CBITSTREAM + uint32_t flags = 0; + void SetUp() override { + SetUpDependencies(); + baseEntity = new Entity(15, GameDependenciesTest::info); + destroyableComponent = new DestroyableComponent(baseEntity); + baseEntity->AddComponent(COMPONENT_TYPE_DESTROYABLE, destroyableComponent); + // Initialize some values to be not default + destroyableComponent->SetMaxHealth(12345.0f); + destroyableComponent->SetHealth(23); + destroyableComponent->SetMaxArmor(14.0f); + destroyableComponent->SetArmor(7); + destroyableComponent->SetMaxImagination(14000.0f); + destroyableComponent->SetImagination(6000); + destroyableComponent->SetIsSmashable(true); + destroyableComponent->SetExplodeFactor(1.1f); + destroyableComponent->AddFactionNoLookup(-1); + destroyableComponent->AddFactionNoLookup(6); + } + + void TearDown() override { + delete baseEntity; + TearDownDependencies(); + } +}; + +/** + * Test Construction of a DestroyableComponent + */ +TEST_F(DestroyableTest, DestroyableComponentSerializeConstructionTest) { + destroyableComponent->Serialize(&bitStream, true, flags); + // Assert that the full number of bits are present + ASSERT_EQ(bitStream.GetNumberOfUnreadBits(), 460); + { + // Now read in the full serialized construction BitStream + bool optionStatusImmunityInfo{}; // Values under this option are unused. + bool optionStatsInfo{}; + uint32_t currentHealth{}; + float maxHealth{}; + uint32_t currentArmor{}; + float maxArmor{}; + uint32_t currentImagination{}; + float maxImagination{}; + uint32_t damageAbsorptionPoints{}; + bool hasImmunity{}; + bool isGmImmune{}; + bool isShielded{}; + float actualMaxHealth{}; + float actualMaxArmor{}; + float actualMaxImagination{}; + uint32_t factionsSize{}; + std::vector factions{}; + bool isSmashable{}; + bool isDead{}; + bool isSmashed{}; + bool isModuleAssembly{}; + bool optionExplodeFactor{}; + float explodeFactor{}; + bool optionIsOnThreatList{}; + bool isThreatened{}; + bitStream.Read(optionStatusImmunityInfo); + + bitStream.Read(optionStatsInfo); + bitStream.Read(currentHealth); + bitStream.Read(maxHealth); + bitStream.Read(currentArmor); + bitStream.Read(maxArmor); + bitStream.Read(currentImagination); + bitStream.Read(maxImagination); + bitStream.Read(damageAbsorptionPoints); + bitStream.Read(hasImmunity); + bitStream.Read(isGmImmune); + bitStream.Read(isShielded); + bitStream.Read(actualMaxHealth); + bitStream.Read(actualMaxArmor); + bitStream.Read(actualMaxImagination); + bitStream.Read(factionsSize); + for (uint32_t i = 0; i < factionsSize; i++) { + int32_t factionID{}; + bitStream.Read(factionID); + factions.push_back(factionID); + } + bitStream.Read(isSmashable); // This is an option later and also a flag at this spot + bitStream.Read(isDead); + bitStream.Read(isSmashed); + // if IsSmashable is true, read the next bits. + bitStream.Read(isModuleAssembly); + bitStream.Read(optionExplodeFactor); + bitStream.Read(explodeFactor); + + bitStream.Read(optionIsOnThreatList); + bitStream.Read(isThreatened); + EXPECT_EQ(optionStatusImmunityInfo, false); + + EXPECT_EQ(optionStatsInfo, true); + EXPECT_EQ(currentHealth, 23); + EXPECT_EQ(maxHealth, 12345.0f); + EXPECT_EQ(currentArmor, 7); + EXPECT_EQ(maxArmor, 14.0f); + EXPECT_EQ(currentImagination, 6000); + EXPECT_EQ(maxImagination, 14000.0f); + EXPECT_EQ(damageAbsorptionPoints, 0.0f); + EXPECT_EQ(hasImmunity, false); + EXPECT_EQ(isGmImmune, false); + EXPECT_EQ(isShielded, false); + EXPECT_EQ(actualMaxHealth, 12345.0f); + EXPECT_EQ(actualMaxArmor, 14.0f); + EXPECT_EQ(actualMaxImagination, 14000.0f); + EXPECT_EQ(factionsSize, 2); + EXPECT_NE(std::find(factions.begin(), factions.end(), -1), factions.end()); + EXPECT_NE(std::find(factions.begin(), factions.end(), 6), factions.end()); + EXPECT_EQ(isSmashable, true); + EXPECT_EQ(isDead, false); + EXPECT_EQ(isSmashed, false); + EXPECT_EQ(isSmashable, true); // For the sake of readability with the struct viewers, we will test this twice since its used as an option here, but as a bool above. + EXPECT_EQ(isModuleAssembly, false); + EXPECT_EQ(optionExplodeFactor, true); + EXPECT_EQ(explodeFactor, 1.1f); + + EXPECT_EQ(optionIsOnThreatList, true); + EXPECT_EQ(isThreatened, false); + } + bitStream.Reset(); +} + +/** + * Test serialization of a DestroyableComponent + */ +TEST_F(DestroyableTest, DestroyableComponentSerializeTest) { + bitStream.Reset(); + // Initialize some values to be not default so we can test a full serialization + destroyableComponent->SetMaxHealth(1233.0f); + + // Now we test a serialization for correctness. + destroyableComponent->Serialize(&bitStream, false, flags); + ASSERT_EQ(bitStream.GetNumberOfUnreadBits(), 422); + { + // Now read in the full serialized BitStream + bool optionStatsInfo{}; + uint32_t currentHealth{}; + float maxHealth{}; + uint32_t currentArmor{}; + float maxArmor{}; + uint32_t currentImagination{}; + float maxImagination{}; + uint32_t damageAbsorptionPoints{}; + bool hasImmunity{}; + bool isGmImmune{}; + bool isShielded{}; + float actualMaxHealth{}; + float actualMaxArmor{}; + float actualMaxImagination{}; + uint32_t factionsSize{}; + std::vector factions{}; + bool isSmashable{}; + bool optionIsOnThreatList{}; + bitStream.Read(optionStatsInfo); + bitStream.Read(currentHealth); + bitStream.Read(maxHealth); + bitStream.Read(currentArmor); + bitStream.Read(maxArmor); + bitStream.Read(currentImagination); + bitStream.Read(maxImagination); + bitStream.Read(damageAbsorptionPoints); + bitStream.Read(hasImmunity); + bitStream.Read(isGmImmune); + bitStream.Read(isShielded); + bitStream.Read(actualMaxHealth); + bitStream.Read(actualMaxArmor); + bitStream.Read(actualMaxImagination); + bitStream.Read(factionsSize); + for (uint32_t i = 0; i < factionsSize; i++) { + int32_t factionID{}; + bitStream.Read(factionID); + factions.push_back(factionID); + } + bitStream.Read(isSmashable); + + bitStream.Read(optionIsOnThreatList); + + EXPECT_EQ(optionStatsInfo, true); + EXPECT_EQ(currentHealth, 23); + EXPECT_EQ(maxHealth, 1233.0f); + EXPECT_EQ(currentArmor, 7); + EXPECT_EQ(maxArmor, 14.0f); + EXPECT_EQ(currentImagination, 6000); + EXPECT_EQ(maxImagination, 14000.0f); + EXPECT_EQ(damageAbsorptionPoints, 0.0f); + EXPECT_EQ(hasImmunity, false); + EXPECT_EQ(isGmImmune, false); + EXPECT_EQ(isShielded, false); + EXPECT_EQ(actualMaxHealth, 1233.0f); + EXPECT_EQ(actualMaxArmor, 14.0f); + EXPECT_EQ(actualMaxImagination, 14000.0f); + EXPECT_EQ(factionsSize, 2); + EXPECT_NE(std::find(factions.begin(), factions.end(), -1), factions.end()); + EXPECT_NE(std::find(factions.begin(), factions.end(), 6), factions.end()); + EXPECT_EQ(isSmashable, true); + + EXPECT_EQ(optionIsOnThreatList, false); // Always zero for now on serialization + } +} + +/** + * Test the Damage method of DestroyableComponent + */ +TEST_F(DestroyableTest, DestroyableComponentDamageTest) { + // Do some actions + destroyableComponent->SetMaxHealth(100.0f); + destroyableComponent->SetHealth(100); + destroyableComponent->SetMaxArmor(0.0f); + destroyableComponent->Damage(10, LWOOBJID_EMPTY); + // Check that we take damage + ASSERT_EQ(destroyableComponent->GetHealth(), 90); + // Check that if we have armor, we take the correct amount of damage + destroyableComponent->SetMaxArmor(10.0f); + destroyableComponent->SetArmor(5); + destroyableComponent->Damage(10, LWOOBJID_EMPTY); + ASSERT_EQ(destroyableComponent->GetHealth(), 85); + // Check that if we have damage absorption we take the correct damage + destroyableComponent->SetDamageToAbsorb(10); + destroyableComponent->Damage(9, LWOOBJID_EMPTY); + ASSERT_EQ(destroyableComponent->GetHealth(), 85); + ASSERT_EQ(destroyableComponent->GetDamageToAbsorb(), 1); + destroyableComponent->Damage(6, LWOOBJID_EMPTY); + ASSERT_EQ(destroyableComponent->GetHealth(), 80); + // Check that we take the correct reduced damage if we take reduced damage + destroyableComponent->SetDamageReduction(2); + destroyableComponent->Damage(7, LWOOBJID_EMPTY); + ASSERT_EQ(destroyableComponent->GetHealth(), 75); + destroyableComponent->Damage(2, LWOOBJID_EMPTY); + ASSERT_EQ(destroyableComponent->GetHealth(), 74); + ASSERT_EQ(destroyableComponent->GetDamageReduction(), 2); + destroyableComponent->SetDamageReduction(0); + // Check that blocking works + destroyableComponent->SetAttacksToBlock(1); + destroyableComponent->Damage(UINT32_MAX, LWOOBJID_EMPTY); + ASSERT_EQ(destroyableComponent->GetHealth(), 74); + destroyableComponent->Damage(4, LWOOBJID_EMPTY); + ASSERT_EQ(destroyableComponent->GetHealth(), 70); + // Check that immunity works + destroyableComponent->SetIsImmune(true); + destroyableComponent->Damage(UINT32_MAX, LWOOBJID_EMPTY); + ASSERT_EQ(destroyableComponent->GetHealth(), 70); + ASSERT_TRUE(destroyableComponent->IsImmune()); + destroyableComponent->SetIsImmune(false); + destroyableComponent->SetIsGMImmune(true); + destroyableComponent->Damage(UINT32_MAX, LWOOBJID_EMPTY); + ASSERT_EQ(destroyableComponent->GetHealth(), 70); + ASSERT_TRUE(destroyableComponent->IsImmune()); + destroyableComponent->SetIsGMImmune(false); + // Check knockback immunity + destroyableComponent->SetIsShielded(true); + ASSERT_TRUE(destroyableComponent->IsKnockbackImmune()); + // Finally deal enough damage to kill the Entity + destroyableComponent->Damage(71, LWOOBJID_EMPTY); + ASSERT_EQ(destroyableComponent->GetHealth(), 0); + // Now lets heal some stats + destroyableComponent->Heal(15); + ASSERT_EQ(destroyableComponent->GetHealth(), 15); + destroyableComponent->Heal(15000); + ASSERT_EQ(destroyableComponent->GetHealth(), 100); + destroyableComponent->Repair(10); + ASSERT_EQ(destroyableComponent->GetArmor(), 10); + destroyableComponent->Repair(15000); + ASSERT_EQ(destroyableComponent->GetArmor(), 10); + destroyableComponent->SetMaxImagination(100.0f); + destroyableComponent->SetImagination(0); + destroyableComponent->Imagine(99); + ASSERT_EQ(destroyableComponent->GetImagination(), 99); + destroyableComponent->Imagine(4); + ASSERT_EQ(destroyableComponent->GetImagination(), 100); +} + +TEST_F(DestroyableTest, DestroyableComponentFactionTest) { + ASSERT_TRUE(destroyableComponent->HasFaction(-1)); + ASSERT_TRUE(destroyableComponent->HasFaction(6)); +} + +TEST_F(DestroyableTest, DestroyableComponentValiditiyTest) { + auto* enemyEntity = new Entity(19, info); + auto* enemyDestroyableComponent = new DestroyableComponent(enemyEntity); + enemyEntity->AddComponent(COMPONENT_TYPE_DESTROYABLE, enemyDestroyableComponent); + enemyDestroyableComponent->AddFactionNoLookup(16); + destroyableComponent->AddEnemyFaction(16); + EXPECT_TRUE(destroyableComponent->IsEnemy(enemyEntity)); + EXPECT_FALSE(destroyableComponent->IsFriend(enemyEntity)); + delete enemyEntity; +}