diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e6a2cf7..f705edf3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,12 @@ project(Darkflame HOMEPAGE_URL "https://github.com/DarkflameUniverse/DarkflameServer" LANGUAGES C CXX ) + +# check if the path to the source directory contains a space +if("${CMAKE_SOURCE_DIR}" MATCHES " ") + message(FATAL_ERROR "The server cannot build in the path (" ${CMAKE_SOURCE_DIR} ") because it contains a space. Please move the server to a path without spaces.") +endif() + include(CTest) set(CMAKE_C_STANDARD 99) diff --git a/CMakeVariables.txt b/CMakeVariables.txt index d9430d9d..4ded5f59 100644 --- a/CMakeVariables.txt +++ b/CMakeVariables.txt @@ -1,6 +1,6 @@ -PROJECT_VERSION_MAJOR=1 -PROJECT_VERSION_MINOR=1 -PROJECT_VERSION_PATCH=1 +PROJECT_VERSION_MAJOR=2 +PROJECT_VERSION_MINOR=3 +PROJECT_VERSION_PATCH=0 # Debugging # Set DYNAMIC to 1 to enable the -rdynamic flag for the linker, yielding some symbols in crashlogs. diff --git a/Dockerfile b/Dockerfile index efb82b42..9086cf17 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,7 +31,7 @@ COPY --from=build /app/build/*Server /app/ # Necessary suplimentary files COPY --from=build /app/build/*.ini /app/configs/ -COPY --from=build /app/build/vanity/*.* /app/vanity/* +COPY --from=build /app/build/vanity/*.* /app/vanity/ COPY --from=build /app/build/navmeshes /app/navmeshes COPY --from=build /app/build/migrations /app/migrations COPY --from=build /app/build/*.dcf /app/ @@ -39,7 +39,7 @@ COPY --from=build /app/build/*.dcf /app/ # backup of config and vanity files to copy to the host incase # of a mount clobbering the copy from above COPY --from=build /app/build/*.ini /app/default-configs/ -COPY --from=build /app/build/vanity/*.* /app/default-vanity/* +COPY --from=build /app/build/vanity/*.* /app/default-vanity/ # needed as the container runs with the root user # and therefore sudo doesn't exist diff --git a/dChatServer/ChatPacketHandler.cpp b/dChatServer/ChatPacketHandler.cpp index d37777b6..82cea018 100644 --- a/dChatServer/ChatPacketHandler.cpp +++ b/dChatServer/ChatPacketHandler.cpp @@ -18,6 +18,7 @@ #include "eGameMessageType.h" #include "StringifiedEnum.h" #include "eGameMasterLevel.h" +#include "ChatPackets.h" void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) { //Get from the packet which player we want to do something with: @@ -354,6 +355,67 @@ void ChatPacketHandler::HandleGMLevelUpdate(Packet* packet) { inStream.Read(player.gmLevel); } + +void ChatPacketHandler::HandleWho(Packet* packet) { + CINSTREAM_SKIP_HEADER; + FindPlayerRequest request; + request.Deserialize(inStream); + + const auto& sender = Game::playerContainer.GetPlayerData(request.requestor); + if (!sender) return; + + const auto& player = Game::playerContainer.GetPlayerData(request.playerName.GetAsString()); + bool online = player; + + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); + bitStream.Write(request.requestor); + + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::WHO_RESPONSE); + bitStream.Write(online); + bitStream.Write(player.zoneID.GetMapID()); + bitStream.Write(player.zoneID.GetInstanceID()); + bitStream.Write(player.zoneID.GetCloneID()); + bitStream.Write(request.playerName); + + SystemAddress sysAddr = sender.sysAddr; + SEND_PACKET; +} + +void ChatPacketHandler::HandleShowAll(Packet* packet) { + CINSTREAM_SKIP_HEADER; + ShowAllRequest request; + request.Deserialize(inStream); + + const auto& sender = Game::playerContainer.GetPlayerData(request.requestor); + if (!sender) return; + + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); + bitStream.Write(request.requestor); + + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::SHOW_ALL_RESPONSE); + bitStream.Write(!request.displayZoneData && !request.displayIndividualPlayers); + bitStream.Write(Game::playerContainer.GetPlayerCount()); + bitStream.Write(Game::playerContainer.GetSimCount()); + bitStream.Write(request.displayIndividualPlayers); + bitStream.Write(request.displayZoneData); + if (request.displayZoneData || request.displayIndividualPlayers){ + for (auto& [playerID, playerData ]: Game::playerContainer.GetAllPlayers()){ + if (!playerData) continue; + bitStream.Write(0); // structure packing + if (request.displayIndividualPlayers) bitStream.Write(LUWString(playerData.playerName)); + if (request.displayZoneData) { + bitStream.Write(playerData.zoneID.GetMapID()); + bitStream.Write(playerData.zoneID.GetInstanceID()); + bitStream.Write(playerData.zoneID.GetCloneID()); + } + } + } + SystemAddress sysAddr = sender.sysAddr; + SEND_PACKET; +} + // the structure the client uses to send this packet is shared in many chat messages // that are sent to the server. Because of this, there are large gaps of unused data in chat messages void ChatPacketHandler::HandleChatMessage(Packet* packet) { diff --git a/dChatServer/ChatPacketHandler.h b/dChatServer/ChatPacketHandler.h index 847fc899..def9c9b9 100644 --- a/dChatServer/ChatPacketHandler.h +++ b/dChatServer/ChatPacketHandler.h @@ -50,6 +50,8 @@ namespace ChatPacketHandler { void HandleFriendResponse(Packet* packet); void HandleRemoveFriend(Packet* packet); void HandleGMLevelUpdate(Packet* packet); + void HandleWho(Packet* packet); + void HandleShowAll(Packet* packet); void HandleChatMessage(Packet* packet); void HandlePrivateChatMessage(Packet* packet); diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index cc938c3c..d6e99230 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -179,6 +179,7 @@ int main(int argc, char** argv) { } void HandlePacket(Packet* packet) { + if (packet->length < 1) return; if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { LOG("A server has disconnected, erasing their connected players from the list."); } else if (packet->data[0] == ID_NEW_INCOMING_CONNECTION) { @@ -289,7 +290,11 @@ void HandlePacket(Packet* packet) { Game::playerContainer.RemovePlayer(packet); break; case eChatMessageType::WHO: + ChatPacketHandler::HandleWho(packet); + break; case eChatMessageType::SHOW_ALL: + ChatPacketHandler::HandleShowAll(packet); + break; case eChatMessageType::USER_CHANNEL_CHAT_MESSAGE: case eChatMessageType::WORLD_DISCONNECT_REQUEST: case eChatMessageType::WORLD_PROXIMITY_RESPONSE: diff --git a/dChatServer/PlayerContainer.cpp b/dChatServer/PlayerContainer.cpp index 57b3f233..f279195e 100644 --- a/dChatServer/PlayerContainer.cpp +++ b/dChatServer/PlayerContainer.cpp @@ -36,19 +36,23 @@ void PlayerContainer::InsertPlayer(Packet* packet) { data.playerID = playerId; uint32_t len; - inStream.Read(len); + if (!inStream.Read(len)) return; - for (int i = 0; i < len; i++) { - char character; inStream.Read(character); - data.playerName += character; + if (len > 33) { + LOG("Received a really long player name, probably a fake packet %i.", len); + return; } - inStream.Read(data.zoneID); - inStream.Read(data.muteExpire); - inStream.Read(data.gmLevel); + data.playerName.resize(len); + inStream.ReadAlignedBytes(reinterpret_cast(data.playerName.data()), len); + + if (!inStream.Read(data.zoneID)) return; + if (!inStream.Read(data.muteExpire)) return; + if (!inStream.Read(data.gmLevel)) return; data.sysAddr = packet->systemAddress; m_Names[data.playerID] = GeneralUtils::UTF8ToUTF16(data.playerName); + m_PlayerCount++; LOG("Added user: %s (%llu), zone: %i", data.playerName.c_str(), data.playerID, data.zoneID.GetMapID()); @@ -87,6 +91,7 @@ void PlayerContainer::RemovePlayer(Packet* packet) { } } + m_PlayerCount--; LOG("Removed user: %llu", playerID); m_Players.erase(playerID); @@ -120,6 +125,11 @@ void PlayerContainer::CreateTeamServer(Packet* packet) { size_t membersSize = 0; inStream.Read(membersSize); + if (membersSize >= 4) { + LOG("Tried to create a team with more than 4 players"); + return; + } + std::vector members; members.reserve(membersSize); diff --git a/dChatServer/PlayerContainer.h b/dChatServer/PlayerContainer.h index 3f2d783a..9a17f927 100644 --- a/dChatServer/PlayerContainer.h +++ b/dChatServer/PlayerContainer.h @@ -71,6 +71,9 @@ public: const PlayerData& GetPlayerData(const std::string& playerName); PlayerData& GetPlayerDataMutable(const LWOOBJID& playerID); PlayerData& GetPlayerDataMutable(const std::string& playerName); + uint32_t GetPlayerCount() { return m_PlayerCount; }; + uint32_t GetSimCount() { return m_SimCount; }; + const std::map& GetAllPlayers() { return m_Players; }; TeamData* CreateLocalTeam(std::vector members); TeamData* CreateTeam(LWOOBJID leader, bool local = false); @@ -93,5 +96,7 @@ private: std::unordered_map m_Names; uint32_t m_MaxNumberOfBestFriends = 5; uint32_t m_MaxNumberOfFriends = 50; + uint32_t m_PlayerCount = 0; + uint32_t m_SimCount = 0; }; diff --git a/dCommon/Diagnostics.cpp b/dCommon/Diagnostics.cpp index 2ed27fef..9f129194 100644 --- a/dCommon/Diagnostics.cpp +++ b/dCommon/Diagnostics.cpp @@ -201,7 +201,7 @@ void OnTerminate() { } void MakeBacktrace() { - struct sigaction sigact; + struct sigaction sigact{}; sigact.sa_sigaction = CritErrHdlr; sigact.sa_flags = SA_RESTART | SA_SIGINFO; diff --git a/dCommon/GeneralUtils.cpp b/dCommon/GeneralUtils.cpp index 159cc127..52287904 100644 --- a/dCommon/GeneralUtils.cpp +++ b/dCommon/GeneralUtils.cpp @@ -8,23 +8,23 @@ #include template -inline size_t MinSize(size_t size, const std::basic_string_view& string) { - if (size == size_t(-1) || size > string.size()) { +static inline size_t MinSize(const size_t size, const std::basic_string_view string) { + if (size == SIZE_MAX || size > string.size()) { return string.size(); } else { return size; } } -inline bool IsLeadSurrogate(char16_t c) { +inline bool IsLeadSurrogate(const char16_t c) { return (0xD800 <= c) && (c <= 0xDBFF); } -inline bool IsTrailSurrogate(char16_t c) { +inline bool IsTrailSurrogate(const char16_t c) { return (0xDC00 <= c) && (c <= 0xDFFF); } -inline void PushUTF8CodePoint(std::string& ret, char32_t cp) { +inline void PushUTF8CodePoint(std::string& ret, const char32_t cp) { if (cp <= 0x007F) { ret.push_back(static_cast(cp)); } else if (cp <= 0x07FF) { @@ -46,16 +46,16 @@ inline void PushUTF8CodePoint(std::string& ret, char32_t cp) { constexpr const char16_t REPLACEMENT_CHARACTER = 0xFFFD; -bool _IsSuffixChar(uint8_t c) { +bool static _IsSuffixChar(const uint8_t c) { return (c & 0xC0) == 0x80; } -bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) { - size_t rem = slice.length(); +bool GeneralUtils::details::_NextUTF8Char(std::string_view& slice, uint32_t& out) { + const size_t rem = slice.length(); if (slice.empty()) return false; const uint8_t* bytes = reinterpret_cast(&slice.front()); if (rem > 0) { - uint8_t first = bytes[0]; + const uint8_t first = bytes[0]; if (first < 0x80) { // 1 byte character out = static_cast(first & 0x7F); slice.remove_prefix(1); @@ -64,7 +64,7 @@ bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) { // middle byte, not valid at start, fall through } else if (first < 0xE0) { // two byte character if (rem > 1) { - uint8_t second = bytes[1]; + const uint8_t second = bytes[1]; if (_IsSuffixChar(second)) { out = (static_cast(first & 0x1F) << 6) + static_cast(second & 0x3F); @@ -74,8 +74,8 @@ bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) { } } else if (first < 0xF0) { // three byte character if (rem > 2) { - uint8_t second = bytes[1]; - uint8_t third = bytes[2]; + const uint8_t second = bytes[1]; + const uint8_t third = bytes[2]; if (_IsSuffixChar(second) && _IsSuffixChar(third)) { out = (static_cast(first & 0x0F) << 12) + (static_cast(second & 0x3F) << 6) @@ -86,9 +86,9 @@ bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) { } } else if (first < 0xF8) { // four byte character if (rem > 3) { - uint8_t second = bytes[1]; - uint8_t third = bytes[2]; - uint8_t fourth = bytes[3]; + const uint8_t second = bytes[1]; + const uint8_t third = bytes[2]; + const uint8_t fourth = bytes[3]; if (_IsSuffixChar(second) && _IsSuffixChar(third) && _IsSuffixChar(fourth)) { out = (static_cast(first & 0x07) << 18) + (static_cast(second & 0x3F) << 12) @@ -107,7 +107,7 @@ bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) { } /// See -bool PushUTF16CodePoint(std::u16string& output, uint32_t U, size_t size) { +bool PushUTF16CodePoint(std::u16string& output, const uint32_t U, const size_t size) { if (output.length() >= size) return false; if (U < 0x10000) { // If U < 0x10000, encode U as a 16-bit unsigned integer and terminate. @@ -120,7 +120,7 @@ bool PushUTF16CodePoint(std::u16string& output, uint32_t U, size_t size) { // Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF, // U' must be less than or equal to 0xFFFFF. That is, U' can be // represented in 20 bits. - uint32_t Ut = U - 0x10000; + const uint32_t Ut = U - 0x10000; // Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and // 0xDC00, respectively. These integers each have 10 bits free to @@ -141,25 +141,25 @@ bool PushUTF16CodePoint(std::u16string& output, uint32_t U, size_t size) { } else return false; } -std::u16string GeneralUtils::UTF8ToUTF16(const std::string_view& string, size_t size) { - size_t newSize = MinSize(size, string); +std::u16string GeneralUtils::UTF8ToUTF16(const std::string_view string, const size_t size) { + const size_t newSize = MinSize(size, string); std::u16string output; output.reserve(newSize); std::string_view iterator = string; uint32_t c; - while (_NextUTF8Char(iterator, c) && PushUTF16CodePoint(output, c, size)) {} + while (details::_NextUTF8Char(iterator, c) && PushUTF16CodePoint(output, c, size)) {} return output; } //! Converts an std::string (ASCII) to UCS-2 / UTF-16 -std::u16string GeneralUtils::ASCIIToUTF16(const std::string_view& string, size_t size) { - size_t newSize = MinSize(size, string); +std::u16string GeneralUtils::ASCIIToUTF16(const std::string_view string, const size_t size) { + const size_t newSize = MinSize(size, string); std::u16string ret; ret.reserve(newSize); - for (size_t i = 0; i < newSize; i++) { - char c = string[i]; + for (size_t i = 0; i < newSize; ++i) { + const char c = string[i]; // Note: both 7-bit ascii characters and REPLACEMENT_CHARACTER fit in one char16_t ret.push_back((c > 0 && c <= 127) ? static_cast(c) : REPLACEMENT_CHARACTER); } @@ -169,18 +169,18 @@ std::u16string GeneralUtils::ASCIIToUTF16(const std::string_view& string, size_t //! Converts a (potentially-ill-formed) UTF-16 string to UTF-8 //! See: -std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view& string, size_t size) { - size_t newSize = MinSize(size, string); +std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view string, const size_t size) { + const size_t newSize = MinSize(size, string); std::string ret; ret.reserve(newSize); - for (size_t i = 0; i < newSize; i++) { - char16_t u = string[i]; + for (size_t i = 0; i < newSize; ++i) { + const char16_t u = string[i]; if (IsLeadSurrogate(u) && (i + 1) < newSize) { - char16_t next = string[i + 1]; + const char16_t next = string[i + 1]; if (IsTrailSurrogate(next)) { i += 1; - char32_t cp = 0x10000 + const char32_t cp = 0x10000 + ((static_cast(u) - 0xD800) << 10) + (static_cast(next) - 0xDC00); PushUTF8CodePoint(ret, cp); @@ -195,40 +195,40 @@ std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view& string, size_t return ret; } -bool GeneralUtils::CaseInsensitiveStringCompare(const std::string& a, const std::string& b) { +bool GeneralUtils::CaseInsensitiveStringCompare(const std::string_view a, const std::string_view b) { return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { return tolower(a) == tolower(b); }); } // MARK: Bits //! Sets a specific bit in a signed 64-bit integer -int64_t GeneralUtils::SetBit(int64_t value, uint32_t index) { +int64_t GeneralUtils::SetBit(int64_t value, const uint32_t index) { return value |= 1ULL << index; } //! Clears a specific bit in a signed 64-bit integer -int64_t GeneralUtils::ClearBit(int64_t value, uint32_t index) { +int64_t GeneralUtils::ClearBit(int64_t value, const uint32_t index) { return value &= ~(1ULL << index); } //! Checks a specific bit in a signed 64-bit integer -bool GeneralUtils::CheckBit(int64_t value, uint32_t index) { +bool GeneralUtils::CheckBit(int64_t value, const uint32_t index) { return value & (1ULL << index); } -bool GeneralUtils::ReplaceInString(std::string& str, const std::string& from, const std::string& to) { - size_t start_pos = str.find(from); +bool GeneralUtils::ReplaceInString(std::string& str, const std::string_view from, const std::string_view to) { + const size_t start_pos = str.find(from); if (start_pos == std::string::npos) return false; str.replace(start_pos, from.length(), to); return true; } -std::vector GeneralUtils::SplitString(std::wstring& str, wchar_t delimiter) { +std::vector GeneralUtils::SplitString(const std::wstring_view str, const wchar_t delimiter) { std::vector vector = std::vector(); std::wstring current; - for (const auto& c : str) { + for (const wchar_t c : str) { if (c == delimiter) { vector.push_back(current); current = L""; @@ -237,15 +237,15 @@ std::vector GeneralUtils::SplitString(std::wstring& str, wchar_t d } } - vector.push_back(current); + vector.push_back(std::move(current)); return vector; } -std::vector GeneralUtils::SplitString(const std::u16string& str, char16_t delimiter) { +std::vector GeneralUtils::SplitString(const std::u16string_view str, const char16_t delimiter) { std::vector vector = std::vector(); std::u16string current; - for (const auto& c : str) { + for (const char16_t c : str) { if (c == delimiter) { vector.push_back(current); current = u""; @@ -254,17 +254,15 @@ std::vector GeneralUtils::SplitString(const std::u16string& str, } } - vector.push_back(current); + vector.push_back(std::move(current)); return vector; } -std::vector GeneralUtils::SplitString(const std::string& str, char delimiter) { +std::vector GeneralUtils::SplitString(const std::string_view str, const char delimiter) { std::vector vector = std::vector(); std::string current = ""; - for (size_t i = 0; i < str.length(); i++) { - char c = str[i]; - + for (const char c : str) { if (c == delimiter) { vector.push_back(current); current = ""; @@ -273,8 +271,7 @@ std::vector GeneralUtils::SplitString(const std::string& str, char } } - vector.push_back(current); - + vector.push_back(std::move(current)); return vector; } @@ -283,7 +280,7 @@ std::u16string GeneralUtils::ReadWString(RakNet::BitStream& inStream) { inStream.Read(length); std::u16string string; - for (auto i = 0; i < length; i++) { + for (uint32_t i = 0; i < length; ++i) { uint16_t c; inStream.Read(c); string.push_back(c); @@ -292,29 +289,29 @@ std::u16string GeneralUtils::ReadWString(RakNet::BitStream& inStream) { return string; } -std::vector GeneralUtils::GetSqlFileNamesFromFolder(const std::string& folder) { +std::vector GeneralUtils::GetSqlFileNamesFromFolder(const std::string_view folder) { // Because we dont know how large the initial number before the first _ is we need to make it a map like so. - std::map filenames{}; - for (auto& t : std::filesystem::directory_iterator(folder)) { - auto filename = t.path().filename().string(); - auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0)); - filenames.insert(std::make_pair(index, filename)); + std::map filenames{}; + for (const auto& t : std::filesystem::directory_iterator(folder)) { + auto filename = t.path().filename().string(); + const auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0)); + filenames.emplace(index, std::move(filename)); } // Now sort the map by the oldest migration. std::vector sortedFiles{}; - auto fileIterator = filenames.begin(); - std::map::iterator oldest = filenames.begin(); + auto fileIterator = filenames.cbegin(); + auto oldest = filenames.cbegin(); while (!filenames.empty()) { - if (fileIterator == filenames.end()) { + if (fileIterator == filenames.cend()) { sortedFiles.push_back(oldest->second); filenames.erase(oldest); - fileIterator = filenames.begin(); - oldest = filenames.begin(); + fileIterator = filenames.cbegin(); + oldest = filenames.cbegin(); continue; } if (oldest->first > fileIterator->first) oldest = fileIterator; - fileIterator++; + ++fileIterator; } return sortedFiles; diff --git a/dCommon/GeneralUtils.h b/dCommon/GeneralUtils.h index 35c0b895..0a8d15c1 100644 --- a/dCommon/GeneralUtils.h +++ b/dCommon/GeneralUtils.h @@ -3,17 +3,18 @@ // C++ #include #include -#include #include +#include +#include +#include +#include +#include #include #include -#include -#include #include -#include + #include "BitStream.h" #include "NiPoint3.h" - #include "dPlatforms.h" #include "Game.h" #include "Logger.h" @@ -32,29 +33,31 @@ namespace GeneralUtils { //! Converts a plain ASCII string to a UTF-16 string /*! \param string The string to convert - \param size A size to trim the string to. Default is -1 (No trimming) + \param size A size to trim the string to. Default is SIZE_MAX (No trimming) \return An UTF-16 representation of the string */ - std::u16string ASCIIToUTF16(const std::string_view& string, size_t size = -1); + std::u16string ASCIIToUTF16(const std::string_view string, const size_t size = SIZE_MAX); //! Converts a UTF-8 String to a UTF-16 string /*! \param string The string to convert - \param size A size to trim the string to. Default is -1 (No trimming) + \param size A size to trim the string to. Default is SIZE_MAX (No trimming) \return An UTF-16 representation of the string */ - std::u16string UTF8ToUTF16(const std::string_view& string, size_t size = -1); + std::u16string UTF8ToUTF16(const std::string_view string, const size_t size = SIZE_MAX); - //! Internal, do not use - bool _NextUTF8Char(std::string_view& slice, uint32_t& out); + namespace details { + //! Internal, do not use + bool _NextUTF8Char(std::string_view& slice, uint32_t& out); + } //! Converts a UTF-16 string to a UTF-8 string /*! \param string The string to convert - \param size A size to trim the string to. Default is -1 (No trimming) + \param size A size to trim the string to. Default is SIZE_MAX (No trimming) \return An UTF-8 representation of the string */ - std::string UTF16ToWTF8(const std::u16string_view& string, size_t size = -1); + std::string UTF16ToWTF8(const std::u16string_view string, const size_t size = SIZE_MAX); /** * Compares two basic strings but does so ignoring case sensitivity @@ -62,7 +65,7 @@ namespace GeneralUtils { * \param b the second string to compare against the first string * @return if the two strings are equal */ - bool CaseInsensitiveStringCompare(const std::string& a, const std::string& b); + bool CaseInsensitiveStringCompare(const std::string_view a, const std::string_view b); // MARK: Bits @@ -70,9 +73,9 @@ namespace GeneralUtils { //! Sets a bit on a numerical value template - inline void SetBit(T& value, eObjectBits bits) { + inline void SetBit(T& value, const eObjectBits bits) { static_assert(std::is_arithmetic::value, "Not an arithmetic type"); - auto index = static_cast(bits); + const auto index = static_cast(bits); if (index > (sizeof(T) * 8) - 1) { return; } @@ -82,9 +85,9 @@ namespace GeneralUtils { //! Clears a bit on a numerical value template - inline void ClearBit(T& value, eObjectBits bits) { + inline void ClearBit(T& value, const eObjectBits bits) { static_assert(std::is_arithmetic::value, "Not an arithmetic type"); - auto index = static_cast(bits); + const auto index = static_cast(bits); if (index > (sizeof(T) * 8 - 1)) { return; } @@ -97,14 +100,14 @@ namespace GeneralUtils { \param value The value to set the bit for \param index The index of the bit */ - int64_t SetBit(int64_t value, uint32_t index); + int64_t SetBit(int64_t value, const uint32_t index); //! Clears a specific bit in a signed 64-bit integer /*! \param value The value to clear the bit from \param index The index of the bit */ - int64_t ClearBit(int64_t value, uint32_t index); + int64_t ClearBit(int64_t value, const uint32_t index); //! Checks a specific bit in a signed 64-bit integer /*! @@ -112,19 +115,19 @@ namespace GeneralUtils { \param index The index of the bit \return Whether or not the bit is set */ - bool CheckBit(int64_t value, uint32_t index); + bool CheckBit(int64_t value, const uint32_t index); - bool ReplaceInString(std::string& str, const std::string& from, const std::string& to); + bool ReplaceInString(std::string& str, const std::string_view from, const std::string_view to); std::u16string ReadWString(RakNet::BitStream& inStream); - std::vector SplitString(std::wstring& str, wchar_t delimiter); + std::vector SplitString(const std::wstring_view str, const wchar_t delimiter); - std::vector SplitString(const std::u16string& str, char16_t delimiter); + std::vector SplitString(const std::u16string_view str, const char16_t delimiter); - std::vector SplitString(const std::string& str, char delimiter); + std::vector SplitString(const std::string_view str, const char delimiter); - std::vector GetSqlFileNamesFromFolder(const std::string& folder); + std::vector GetSqlFileNamesFromFolder(const std::string_view folder); // Concept constraining to enum types template @@ -144,7 +147,7 @@ namespace GeneralUtils { // If a boolean, present an alias to an intermediate integral type for parsing template requires std::same_as - struct numeric_parse { using type = uint32_t; }; + struct numeric_parse { using type = uint8_t; }; // Shorthand type alias template @@ -205,7 +208,7 @@ namespace GeneralUtils { * @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters */ template - [[nodiscard]] std::optional TryParse(const std::string& strX, const std::string& strY, const std::string& strZ) { + [[nodiscard]] std::optional TryParse(const std::string_view strX, const std::string_view strY, const std::string_view strZ) { const auto x = TryParse(strX); if (!x) return std::nullopt; @@ -217,17 +220,17 @@ namespace GeneralUtils { } /** - * The TryParse overload for handling NiPoint3 by passingn a reference to a vector of three strings - * @param str The string vector representing the X, Y, and Xcoordinates + * The TryParse overload for handling NiPoint3 by passing a span of three strings + * @param str The string vector representing the X, Y, and Z coordinates * @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters */ template - [[nodiscard]] std::optional TryParse(const std::vector& str) { + [[nodiscard]] std::optional TryParse(const std::span str) { return (str.size() == 3) ? TryParse(str[0], str[1], str[2]) : std::nullopt; } template - std::u16string to_u16string(T value) { + std::u16string to_u16string(const T value) { return GeneralUtils::ASCIIToUTF16(std::to_string(value)); } @@ -246,7 +249,7 @@ namespace GeneralUtils { \param max The maximum to generate to */ template - inline T GenerateRandomNumber(std::size_t min, std::size_t max) { + inline T GenerateRandomNumber(const std::size_t min, const std::size_t max) { // Make sure it is a numeric type static_assert(std::is_arithmetic::value, "Not an arithmetic type"); @@ -273,10 +276,10 @@ namespace GeneralUtils { // on Windows we need to undef these or else they conflict with our numeric limits calls // DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS - #ifdef _WIN32 - #undef min - #undef max - #endif +#ifdef _WIN32 +#undef min +#undef max +#endif template inline T GenerateRandomNumber() { diff --git a/dCommon/LDFFormat.h b/dCommon/LDFFormat.h index 2cd9156c..054ddb42 100644 --- a/dCommon/LDFFormat.h +++ b/dCommon/LDFFormat.h @@ -31,22 +31,22 @@ public: virtual ~LDFBaseData() {} - virtual void WriteToPacket(RakNet::BitStream& packet) = 0; + virtual void WriteToPacket(RakNet::BitStream& packet) const = 0; - virtual const std::u16string& GetKey() = 0; + virtual const std::u16string& GetKey() const = 0; - virtual eLDFType GetValueType() = 0; + virtual eLDFType GetValueType() const = 0; /** Gets a string from the key/value pair * @param includeKey Whether or not to include the key in the data * @param includeTypeId Whether or not to include the type id in the data * @return The string representation of the data */ - virtual std::string GetString(bool includeKey = true, bool includeTypeId = true) = 0; + virtual std::string GetString(bool includeKey = true, bool includeTypeId = true) const = 0; - virtual std::string GetValueAsString() = 0; + virtual std::string GetValueAsString() const = 0; - virtual LDFBaseData* Copy() = 0; + virtual LDFBaseData* Copy() const = 0; /** * Given an input string, return the data as a LDF key. @@ -62,7 +62,7 @@ private: T value; //! Writes the key to the packet - void WriteKey(RakNet::BitStream& packet) { + void WriteKey(RakNet::BitStream& packet) const { packet.Write(this->key.length() * sizeof(uint16_t)); for (uint32_t i = 0; i < this->key.length(); ++i) { packet.Write(this->key[i]); @@ -70,7 +70,7 @@ private: } //! Writes the value to the packet - void WriteValue(RakNet::BitStream& packet) { + void WriteValue(RakNet::BitStream& packet) const { packet.Write(this->GetValueType()); packet.Write(this->value); } @@ -90,7 +90,7 @@ public: /*! \return The value */ - const T& GetValue(void) { return this->value; } + const T& GetValue(void) const { return this->value; } //! Sets the value /*! @@ -102,13 +102,13 @@ public: /*! \return The value string */ - std::string GetValueString(void) { return ""; } + std::string GetValueString(void) const { return ""; } //! Writes the data to a packet /*! \param packet The packet */ - void WriteToPacket(RakNet::BitStream& packet) override { + void WriteToPacket(RakNet::BitStream& packet) const override { this->WriteKey(packet); this->WriteValue(packet); } @@ -117,13 +117,13 @@ public: /*! \return The key */ - const std::u16string& GetKey(void) override { return this->key; } + const std::u16string& GetKey(void) const override { return this->key; } //! Gets the LDF Type /*! \return The LDF value type */ - eLDFType GetValueType(void) override { return LDF_TYPE_UNKNOWN; } + eLDFType GetValueType(void) const override { return LDF_TYPE_UNKNOWN; } //! Gets the string data /*! @@ -131,7 +131,7 @@ public: \param includeTypeId Whether or not to include the type id in the data \return The string representation of the data */ - std::string GetString(const bool includeKey = true, const bool includeTypeId = true) override { + std::string GetString(const bool includeKey = true, const bool includeTypeId = true) const override { if (GetValueType() == -1) { return GeneralUtils::UTF16ToWTF8(this->key) + "=-1:"; } @@ -154,11 +154,11 @@ public: return stream.str(); } - std::string GetValueAsString() override { + std::string GetValueAsString() const override { return this->GetValueString(); } - LDFBaseData* Copy() override { + LDFBaseData* Copy() const override { return new LDFData(key, value); } @@ -166,19 +166,19 @@ public: }; // LDF Types -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_UTF_16; }; -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_S32; }; -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_FLOAT; }; -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_DOUBLE; }; -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_U32; }; -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_BOOLEAN; }; -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_U64; }; -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_OBJID; }; -template<> inline eLDFType LDFData::GetValueType(void) { return LDF_TYPE_UTF_8; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_UTF_16; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_S32; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_FLOAT; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_DOUBLE; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_U32; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_BOOLEAN; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_U64; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_OBJID; }; +template<> inline eLDFType LDFData::GetValueType(void) const { return LDF_TYPE_UTF_8; }; // The specialized version for std::u16string (UTF-16) template<> -inline void LDFData::WriteValue(RakNet::BitStream& packet) { +inline void LDFData::WriteValue(RakNet::BitStream& packet) const { packet.Write(this->GetValueType()); packet.Write(this->value.length()); @@ -189,7 +189,7 @@ inline void LDFData::WriteValue(RakNet::BitStream& packet) { // The specialized version for bool template<> -inline void LDFData::WriteValue(RakNet::BitStream& packet) { +inline void LDFData::WriteValue(RakNet::BitStream& packet) const { packet.Write(this->GetValueType()); packet.Write(this->value); @@ -197,7 +197,7 @@ inline void LDFData::WriteValue(RakNet::BitStream& packet) { // The specialized version for std::string (UTF-8) template<> -inline void LDFData::WriteValue(RakNet::BitStream& packet) { +inline void LDFData::WriteValue(RakNet::BitStream& packet) const { packet.Write(this->GetValueType()); packet.Write(this->value.length()); @@ -206,18 +206,18 @@ inline void LDFData::WriteValue(RakNet::BitStream& packet) { } } -template<> inline std::string LDFData::GetValueString() { +template<> inline std::string LDFData::GetValueString() const { return GeneralUtils::UTF16ToWTF8(this->value, this->value.size()); } -template<> inline std::string LDFData::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData::GetValueString() { return std::to_string(this->value); } +template<> inline std::string LDFData::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData::GetValueString() const { return std::to_string(this->value); } -template<> inline std::string LDFData::GetValueString() { return this->value; } +template<> inline std::string LDFData::GetValueString() const { return this->value; } #endif //!__LDFFORMAT__H__ diff --git a/dCommon/dConfig.cpp b/dCommon/dConfig.cpp index 56a43848..bed274b0 100644 --- a/dCommon/dConfig.cpp +++ b/dCommon/dConfig.cpp @@ -1,6 +1,7 @@ #include "dConfig.h" #include +#include #include "BinaryPathFinder.h" #include "GeneralUtils.h" diff --git a/dCommon/dEnums/eInventoryType.h b/dCommon/dEnums/eInventoryType.h index 12573aa4..1c6688b2 100644 --- a/dCommon/dEnums/eInventoryType.h +++ b/dCommon/dEnums/eInventoryType.h @@ -4,6 +4,9 @@ #define __EINVENTORYTYPE__H__ #include + +#include "magic_enum.hpp" + static const uint8_t NUMBER_OF_INVENTORIES = 17; /** * Represents the different types of inventories an entity may have @@ -56,4 +59,10 @@ public: }; }; +template <> +struct magic_enum::customize::enum_range { + static constexpr int min = 0; + static constexpr int max = 16; +}; + #endif //!__EINVENTORYTYPE__H__ diff --git a/dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h b/dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h new file mode 100644 index 00000000..b99687d0 --- /dev/null +++ b/dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h @@ -0,0 +1,21 @@ +#ifndef __EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__ +#define __EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__ + +#include + +enum class eReponseMoveItemBetweenInventoryTypeCode : int32_t { + SUCCESS, + FAIL_GENERIC, + FAIL_INV_FULL, + FAIL_ITEM_NOT_FOUND, + FAIL_CANT_MOVE_TO_THAT_INV_TYPE, + FAIL_NOT_NEAR_BANK, + FAIL_CANT_SWAP_ITEMS, + FAIL_SOURCE_TYPE, + FAIL_WRONG_DEST_TYPE, + FAIL_SWAP_DEST_TYPE, + FAIL_CANT_MOVE_THINKING_HAT, + FAIL_DISMOUNT_BEFORE_MOVING +}; + +#endif //!__EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__ diff --git a/dDatabase/CDClientDatabase/CDClientManager.cpp b/dDatabase/CDClientDatabase/CDClientManager.cpp index 0e05c0b8..6ecfb0ad 100644 --- a/dDatabase/CDClientDatabase/CDClientManager.cpp +++ b/dDatabase/CDClientDatabase/CDClientManager.cpp @@ -25,6 +25,7 @@ #include "CDScriptComponentTable.h" #include "CDSkillBehaviorTable.h" #include "CDZoneTableTable.h" +#include "CDTamingBuildPuzzleTable.h" #include "CDVendorComponentTable.h" #include "CDActivitiesTable.h" #include "CDPackageComponentTable.h" @@ -41,8 +42,6 @@ #include "CDRewardCodesTable.h" #include "CDPetComponentTable.h" -#include - #ifndef CDCLIENT_CACHE_ALL // Uncomment this to cache the full cdclient database into memory. This will make the server load faster, but will use more memory. // A vanilla CDClient takes about 46MB of memory + the regular world data. @@ -55,13 +54,6 @@ #define CDCLIENT_DONT_CACHE_TABLE(x) #endif -class CDClientConnectionException : public std::exception { -public: - virtual const char* what() const throw() { - return "CDClientDatabase is not connected!"; - } -}; - // Using a macro to reduce repetitive code and issues from copy and paste. // As a note, ## in a macro is used to concatenate two tokens together. @@ -108,11 +100,14 @@ DEFINE_TABLE_STORAGE(CDRewardCodesTable); DEFINE_TABLE_STORAGE(CDRewardsTable); DEFINE_TABLE_STORAGE(CDScriptComponentTable); DEFINE_TABLE_STORAGE(CDSkillBehaviorTable); +DEFINE_TABLE_STORAGE(CDTamingBuildPuzzleTable); DEFINE_TABLE_STORAGE(CDVendorComponentTable); DEFINE_TABLE_STORAGE(CDZoneTableTable); void CDClientManager::LoadValuesFromDatabase() { - if (!CDClientDatabase::isConnected) throw CDClientConnectionException(); + if (!CDClientDatabase::isConnected) { + throw std::runtime_error{ "CDClientDatabase is not connected!" }; + } CDActivityRewardsTable::Instance().LoadValuesFromDatabase(); CDActivitiesTable::Instance().LoadValuesFromDatabase(); @@ -152,6 +147,7 @@ void CDClientManager::LoadValuesFromDatabase() { CDRewardsTable::Instance().LoadValuesFromDatabase(); CDScriptComponentTable::Instance().LoadValuesFromDatabase(); CDSkillBehaviorTable::Instance().LoadValuesFromDatabase(); + CDTamingBuildPuzzleTable::Instance().LoadValuesFromDatabase(); CDVendorComponentTable::Instance().LoadValuesFromDatabase(); CDZoneTableTable::Instance().LoadValuesFromDatabase(); } diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDPetComponentTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDPetComponentTable.cpp index f3371ecb..80c10112 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDPetComponentTable.cpp +++ b/dDatabase/CDClientDatabase/CDClientTables/CDPetComponentTable.cpp @@ -50,7 +50,7 @@ void CDPetComponentTable::LoadValuesFromDatabase() { } void CDPetComponentTable::LoadValuesFromDefaults() { - GetEntriesMutable().insert(std::make_pair(defaultEntry.id, defaultEntry)); + GetEntriesMutable().emplace(defaultEntry.id, defaultEntry); } CDPetComponent& CDPetComponentTable::GetByID(const uint32_t componentID) { diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.cpp new file mode 100644 index 00000000..c2301b33 --- /dev/null +++ b/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.cpp @@ -0,0 +1,35 @@ +#include "CDTamingBuildPuzzleTable.h" + +void CDTamingBuildPuzzleTable::LoadValuesFromDatabase() { + // First, get the size of the table + uint32_t size = 0; + auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM TamingBuildPuzzles"); + while (!tableSize.eof()) { + size = tableSize.getIntField(0, 0); + tableSize.nextRow(); + } + + // Reserve the size + auto& entries = GetEntriesMutable(); + entries.reserve(size); + + // Now get the data + auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM TamingBuildPuzzles"); + while (!tableData.eof()) { + const auto lot = static_cast(tableData.getIntField("NPCLot", LOT_NULL)); + entries.emplace(lot, CDTamingBuildPuzzle{ + .puzzleModelLot = lot, + .validPieces{ tableData.getStringField("ValidPiecesLXF") }, + .timeLimit = static_cast(tableData.getFloatField("Timelimit", 30.0f)), + .numValidPieces = tableData.getIntField("NumValidPieces", 6), + .imaginationCost = tableData.getIntField("imagCostPerBuild", 10) + }); + tableData.nextRow(); + } +} + +const CDTamingBuildPuzzle* CDTamingBuildPuzzleTable::GetByLOT(const LOT lot) const { + const auto& entries = GetEntries(); + const auto itr = entries.find(lot); + return itr != entries.cend() ? &itr->second : nullptr; +} diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.h new file mode 100644 index 00000000..acbd65bf --- /dev/null +++ b/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.h @@ -0,0 +1,60 @@ +#pragma once +#include "CDTable.h" + +/** + * Information for the minigame to be completed + */ +struct CDTamingBuildPuzzle { + UNUSED_COLUMN(uint32_t id = 0;) + + // The LOT of the object that is to be created + LOT puzzleModelLot = LOT_NULL; + + // The LOT of the NPC + UNUSED_COLUMN(LOT npcLot = LOT_NULL;) + + // The .lxfml file that contains the bricks required to build the model + std::string validPieces{}; + + // The .lxfml file that contains the bricks NOT required to build the model + UNUSED_COLUMN(std::string invalidPieces{};) + + // Difficulty value + UNUSED_COLUMN(int32_t difficulty = 1;) + + // The time limit to complete the build + float timeLimit = 30.0f; + + // The number of pieces required to complete the minigame + int32_t numValidPieces = 6; + + // Number of valid pieces + UNUSED_COLUMN(int32_t totalNumPieces = 16;) + + // Model name + UNUSED_COLUMN(std::string modelName{};) + + // The .lxfml file that contains the full model + UNUSED_COLUMN(std::string fullModel{};) + + // The duration of the pet taming minigame + UNUSED_COLUMN(float duration = 45.0f;) + + // The imagination cost for the tamer to start the minigame + int32_t imaginationCost = 10; +}; + +class CDTamingBuildPuzzleTable : public CDTable> { +public: + /** + * Load values from the CD client database + */ + void LoadValuesFromDatabase(); + + /** + * Gets the pet ability table corresponding to the pet LOT + * @returns A pointer to the corresponding table, or nullptr if one cannot be found + */ + [[nodiscard]] + const CDTamingBuildPuzzle* GetByLOT(const LOT lot) const; +}; diff --git a/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt b/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt index af401db2..f4551646 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt +++ b/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt @@ -36,5 +36,6 @@ set(DDATABASE_CDCLIENTDATABASE_CDCLIENTTABLES_SOURCES "CDActivitiesTable.cpp" "CDRewardsTable.cpp" "CDScriptComponentTable.cpp" "CDSkillBehaviorTable.cpp" + "CDTamingBuildPuzzleTable.cpp" "CDVendorComponentTable.cpp" "CDZoneTableTable.cpp" PARENT_SCOPE) diff --git a/dDatabase/GameDatabase/GameDatabase.h b/dDatabase/GameDatabase/GameDatabase.h index bcd8550b..f52c8c4e 100644 --- a/dDatabase/GameDatabase/GameDatabase.h +++ b/dDatabase/GameDatabase/GameDatabase.h @@ -23,6 +23,7 @@ #include "IActivityLog.h" #include "IIgnoreList.h" #include "IAccountsRewardCodes.h" +#include "IBehaviors.h" namespace sql { class Statement; @@ -40,7 +41,8 @@ class GameDatabase : public IMail, public ICommandLog, public IPlayerCheatDetections, public IBugReports, public IPropertyContents, public IProperty, public IPetNames, public ICharXml, public IMigrationHistory, public IUgc, public IFriends, public ICharInfo, - public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList { + public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList, + public IBehaviors { public: virtual ~GameDatabase() = default; // TODO: These should be made private. diff --git a/dDatabase/GameDatabase/ITables/IAccounts.h b/dDatabase/GameDatabase/ITables/IAccounts.h index 3f27dda6..a0377f4b 100644 --- a/dDatabase/GameDatabase/ITables/IAccounts.h +++ b/dDatabase/GameDatabase/ITables/IAccounts.h @@ -33,6 +33,9 @@ public: // Add a new account to the database. virtual void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) = 0; + + // Update the GameMaster level of an account. + virtual void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) = 0; }; #endif //!__IACCOUNTS__H__ diff --git a/dDatabase/GameDatabase/ITables/IBehaviors.h b/dDatabase/GameDatabase/ITables/IBehaviors.h new file mode 100644 index 00000000..531167e2 --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IBehaviors.h @@ -0,0 +1,22 @@ +#ifndef IBEHAVIORS_H +#define IBEHAVIORS_H + +#include + +#include "dCommonVars.h" + +class IBehaviors { +public: + struct Info { + int32_t behaviorId{}; + uint32_t characterId{}; + std::string behaviorInfo; + }; + + // This Add also takes care of updating if it exists. + virtual void AddBehavior(const Info& info) = 0; + virtual std::string GetBehavior(const int32_t behaviorId) = 0; + virtual void RemoveBehavior(const int32_t behaviorId) = 0; +}; + +#endif //!IBEHAVIORS_H diff --git a/dDatabase/GameDatabase/ITables/IPropertyContents.h b/dDatabase/GameDatabase/ITables/IPropertyContents.h index c862ca94..0d8d8b5c 100644 --- a/dDatabase/GameDatabase/ITables/IPropertyContents.h +++ b/dDatabase/GameDatabase/ITables/IPropertyContents.h @@ -1,6 +1,7 @@ #ifndef __IPROPERTIESCONTENTS__H__ #define __IPROPERTIESCONTENTS__H__ +#include #include #include @@ -16,6 +17,7 @@ public: LWOOBJID id{}; LOT lot{}; uint32_t ugcId{}; + std::array behaviors{}; }; // Inserts a new UGC model into the database. @@ -32,7 +34,7 @@ public: virtual void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) = 0; // Update the model position and rotation for the given property id. - virtual void UpdateModelPositionRotation(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation) = 0; + virtual void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array, 5>& behaviors) = 0; // Remove the model for the given property id. virtual void RemoveModel(const LWOOBJID& modelId) = 0; diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp b/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp index 259c3866..20e92677 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp @@ -4,6 +4,7 @@ #include "Game.h" #include "dConfig.h" #include "Logger.h" +#include "dPlatforms.h" namespace { std::string databaseName; @@ -39,14 +40,13 @@ void MySQLDatabase::Connect() { properties["autoReconnect"] = "true"; databaseName = Game::config->GetValue("mysql_database").c_str(); - // `connect(const Properties& props)` segfaults in windows debug, but // `connect(const SQLString& host, const SQLString& user, const SQLString& pwd)` doesn't handle pipes/unix sockets correctly - if (properties.find("localSocket") != properties.end() || properties.find("pipe") != properties.end()) { - con = driver->connect(properties); - } else { +#if defined(DARKFLAME_PLATFORM_WIN32) && defined(_DEBUG) con = driver->connect(properties["hostName"].c_str(), properties["user"].c_str(), properties["password"].c_str()); - } +#else + con = driver->connect(properties); +#endif con->setSchema(databaseName.c_str()); } diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index 836ab56c..a3019bea 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -74,7 +74,7 @@ public: std::vector GetPropertyModels(const LWOOBJID& propertyId) override; void RemoveUnreferencedUgcModels() override; void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override; - void UpdateModelPositionRotation(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation) override; + void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array, 5>& behaviors) override; void RemoveModel(const LWOOBJID& modelId) override; void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override; void InsertNewBugReport(const IBugReports::Info& info) override; @@ -108,6 +108,10 @@ public: std::vector GetIgnoreList(const uint32_t playerId) override; void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override; std::vector GetRewardCodesByAccountID(const uint32_t account_id) override; + void AddBehavior(const IBehaviors::Info& info) override; + std::string GetBehavior(const int32_t behaviorId) override; + void RemoveBehavior(const int32_t characterId) override; + void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override; private: // Generic query functions that can be used for any query. diff --git a/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp b/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp index 801f444d..9e9812f3 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp @@ -35,3 +35,7 @@ void MySQLDatabase::UpdateAccountPassword(const uint32_t accountId, const std::s void MySQLDatabase::InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) { ExecuteInsert("INSERT INTO accounts (name, password, gm_level) VALUES (?, ?, ?);", username, bcryptpassword, static_cast(eGameMasterLevel::OPERATOR)); } + +void MySQLDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) { + ExecuteUpdate("UPDATE accounts SET gm_level = ? WHERE id = ?;", static_cast(gmLevel), accountId); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/Behaviors.cpp b/dDatabase/GameDatabase/MySQL/Tables/Behaviors.cpp new file mode 100644 index 00000000..f4647865 --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/Behaviors.cpp @@ -0,0 +1,19 @@ +#include "IBehaviors.h" + +#include "MySQLDatabase.h" + +void MySQLDatabase::AddBehavior(const IBehaviors::Info& info) { + ExecuteInsert( + "INSERT INTO behaviors (behavior_info, character_id, behavior_id) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE behavior_info = ?", + info.behaviorInfo, info.characterId, info.behaviorId, info.behaviorInfo + ); +} + +void MySQLDatabase::RemoveBehavior(const int32_t behaviorId) { + ExecuteDelete("DELETE FROM behaviors WHERE behavior_id = ?", behaviorId); +} + +std::string MySQLDatabase::GetBehavior(const int32_t behaviorId) { + auto result = ExecuteSelect("SELECT behavior_info FROM behaviors WHERE behavior_id = ?", behaviorId); + return result->next() ? result->getString("behavior_info").c_str() : ""; +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt b/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt index 9f0e7baa..47cd220e 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt +++ b/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt @@ -2,6 +2,7 @@ set(DDATABASES_DATABASES_MYSQL_TABLES_SOURCES "Accounts.cpp" "AccountsRewardCodes.cpp" "ActivityLog.cpp" + "Behaviors.cpp" "BugReports.cpp" "CharInfo.cpp" "CharXml.cpp" diff --git a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp index dba82d56..05998785 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp @@ -1,7 +1,10 @@ #include "MySQLDatabase.h" std::vector MySQLDatabase::GetPropertyModels(const LWOOBJID& propertyId) { - auto result = ExecuteSelect("SELECT id, lot, x, y, z, rx, ry, rz, rw, ugc_id FROM properties_contents WHERE property_id = ?;", propertyId); + auto result = ExecuteSelect( + "SELECT id, lot, x, y, z, rx, ry, rz, rw, ugc_id, " + "behavior_1, behavior_2, behavior_3, behavior_4, behavior_5 " + "FROM properties_contents WHERE property_id = ?;", propertyId); std::vector toReturn; toReturn.reserve(result->rowsCount()); @@ -17,6 +20,12 @@ std::vector MySQLDatabase::GetPropertyModels(const LWO model.rotation.y = result->getFloat("ry"); model.rotation.z = result->getFloat("rz"); model.ugcId = result->getUInt64("ugc_id"); + model.behaviors[0] = result->getInt("behavior_1"); + model.behaviors[1] = result->getInt("behavior_2"); + model.behaviors[2] = result->getInt("behavior_3"); + model.behaviors[3] = result->getInt("behavior_4"); + model.behaviors[4] = result->getInt("behavior_5"); + toReturn.push_back(std::move(model)); } return toReturn; @@ -32,21 +41,23 @@ void MySQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IPr model.id, propertyId, model.ugcId == 0 ? std::nullopt : std::optional(model.ugcId), static_cast(model.lot), model.position.x, model.position.y, model.position.z, model.rotation.x, model.rotation.y, model.rotation.z, model.rotation.w, name, "", // Model description. TODO implement this. - 0, // behavior 1. TODO implement this. - 0, // behavior 2. TODO implement this. - 0, // behavior 3. TODO implement this. - 0, // behavior 4. TODO implement this. - 0 // behavior 5. TODO implement this. + model.behaviors[0], // behavior 1 + model.behaviors[1], // behavior 2 + model.behaviors[2], // behavior 3 + model.behaviors[3], // behavior 4 + model.behaviors[4] // behavior 5 ); } catch (sql::SQLException& e) { LOG("Error inserting new property model: %s", e.what()); } } -void MySQLDatabase::UpdateModelPositionRotation(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation) { +void MySQLDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array, 5>& behaviors) { ExecuteUpdate( - "UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ? WHERE id = ?;", - position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, propertyId); + "UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ?, " + "behavior_1 = ?, behavior_2 = ?, behavior_3 = ?, behavior_4 = ?, behavior_5 = ? WHERE id = ?;", + position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, + behaviors[0].first, behaviors[1].first, behaviors[2].first, behaviors[3].first, behaviors[4].first, propertyId); } void MySQLDatabase::RemoveModel(const LWOOBJID& modelId) { diff --git a/dGame/Character.cpp b/dGame/Character.cpp index 59a67462..57a951d9 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -27,6 +27,8 @@ Character::Character(uint32_t id, User* parentUser) { m_ID = id; m_ParentUser = parentUser; m_OurEntity = nullptr; + m_GMLevel = eGameMasterLevel::CIVILIAN; + m_PermissionMap = static_cast(0); } Character::~Character() { diff --git a/dGame/Character.h b/dGame/Character.h index 77f286f0..7a83325b 100644 --- a/dGame/Character.h +++ b/dGame/Character.h @@ -464,22 +464,22 @@ private: /** * The ID of this character. First 32 bits of the ObjectID. */ - uint32_t m_ID; + uint32_t m_ID{}; /** * The 64-bit unique ID used in the game. */ - LWOOBJID m_ObjectID; + LWOOBJID m_ObjectID{ LWOOBJID_EMPTY }; /** * The user that owns this character. */ - User* m_ParentUser; + User* m_ParentUser{}; /** * If the character is in game, this is the entity that it represents, else nullptr. */ - Entity* m_OurEntity; + Entity* m_OurEntity{}; /** * 0-9, the Game Master level of this character. @@ -506,17 +506,17 @@ private: /** * Whether the custom name of this character is rejected */ - bool m_NameRejected; + bool m_NameRejected{}; /** * The current amount of coins of this character */ - int64_t m_Coins; + int64_t m_Coins{}; /** * Whether the character is building */ - bool m_BuildMode; + bool m_BuildMode{}; /** * The items equipped by the character on world load @@ -583,7 +583,7 @@ private: /** * The ID of the properties of this character */ - uint32_t m_PropertyCloneID; + uint32_t m_PropertyCloneID{}; /** * The XML data for this character, stored as string @@ -613,7 +613,7 @@ private: /** * The last time this character logged in */ - uint64_t m_LastLogin; + uint64_t m_LastLogin{}; /** * The gameplay flags this character has (not just true values) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 00ad8471..6699c595 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -225,7 +225,7 @@ void Entity::Initialize() { AddComponent(simplePhysicsComponentID); - AddComponent(); + AddComponent()->LoadBehaviors(); AddComponent(); @@ -649,7 +649,7 @@ void Entity::Initialize() { } if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MODEL, -1) != -1 && !GetComponent()) { - AddComponent(); + AddComponent()->LoadBehaviors(); if (!HasComponent(eReplicaComponentType::DESTROYABLE)) { auto* destroyableComponent = AddComponent(); destroyableComponent->SetHealth(1); @@ -1534,7 +1534,7 @@ void Entity::Kill(Entity* murderer, const eKillType killType) { bool waitForDeathAnimation = false; if (destroyableComponent) { - waitForDeathAnimation = destroyableComponent->GetDeathBehavior() == 0 && killType != eKillType::SILENT; + waitForDeathAnimation = !destroyableComponent->GetIsSmashable() && destroyableComponent->GetDeathBehavior() == 0 && killType != eKillType::SILENT; } // Live waited a hard coded 12 seconds for death animations of type 0 before networking destruction! @@ -1840,6 +1840,12 @@ const NiPoint3& Entity::GetPosition() const { return vehicel->GetPosition(); } + auto* rigidBodyPhantomPhysicsComponent = GetComponent(); + + if (rigidBodyPhantomPhysicsComponent != nullptr) { + return rigidBodyPhantomPhysicsComponent->GetPosition(); + } + return NiPoint3Constant::ZERO; } @@ -1868,6 +1874,12 @@ const NiQuaternion& Entity::GetRotation() const { return vehicel->GetRotation(); } + auto* rigidBodyPhantomPhysicsComponent = GetComponent(); + + if (rigidBodyPhantomPhysicsComponent != nullptr) { + return rigidBodyPhantomPhysicsComponent->GetRotation(); + } + return NiQuaternionConstant::IDENTITY; } @@ -1896,6 +1908,12 @@ void Entity::SetPosition(const NiPoint3& position) { vehicel->SetPosition(position); } + auto* rigidBodyPhantomPhysicsComponent = GetComponent(); + + if (rigidBodyPhantomPhysicsComponent != nullptr) { + rigidBodyPhantomPhysicsComponent->SetPosition(position); + } + Game::entityManager->SerializeEntity(this); } @@ -1924,6 +1942,12 @@ void Entity::SetRotation(const NiQuaternion& rotation) { vehicel->SetRotation(rotation); } + auto* rigidBodyPhantomPhysicsComponent = GetComponent(); + + if (rigidBodyPhantomPhysicsComponent != nullptr) { + rigidBodyPhantomPhysicsComponent->SetRotation(rotation); + } + Game::entityManager->SerializeEntity(this); } diff --git a/dGame/User.cpp b/dGame/User.cpp index 0b2c3c3f..806d4611 100644 --- a/dGame/User.cpp +++ b/dGame/User.cpp @@ -12,6 +12,7 @@ User::User(const SystemAddress& sysAddr, const std::string& username, const std: m_AccountID = 0; m_Username = ""; m_SessionKey = ""; + m_MuteExpire = 0; m_MaxGMLevel = eGameMasterLevel::CIVILIAN; //The max GM level this account can assign to it's characters m_LastCharID = 0; diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index da7e9e23..8579d882 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -536,13 +536,13 @@ void UserManager::LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID uint32_t FindCharShirtID(uint32_t shirtColor, uint32_t shirtStyle) { try { auto stmt = CDClientDatabase::CreatePreppedStmt( - "select obj.id from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == ? AND icc.color1 == ? AND icc.decal == ?" + "select obj.id as objectId from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == ? AND icc.color1 == ? AND icc.decal == ?" ); stmt.bind(1, "character create shirt"); stmt.bind(2, static_cast(shirtColor)); stmt.bind(3, static_cast(shirtStyle)); auto tableData = stmt.execQuery(); - auto shirtLOT = tableData.getIntField(0, 4069); + auto shirtLOT = tableData.getIntField("objectId", 4069); tableData.finalize(); return shirtLOT; } catch (const std::exception& ex) { @@ -555,12 +555,12 @@ uint32_t FindCharShirtID(uint32_t shirtColor, uint32_t shirtStyle) { uint32_t FindCharPantsID(uint32_t pantsColor) { try { auto stmt = CDClientDatabase::CreatePreppedStmt( - "select obj.id from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == ? AND icc.color1 == ?" + "select obj.id as objectId from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == ? AND icc.color1 == ?" ); stmt.bind(1, "cc pants"); stmt.bind(2, static_cast(pantsColor)); auto tableData = stmt.execQuery(); - auto pantsLOT = tableData.getIntField(0, 2508); + auto pantsLOT = tableData.getIntField("objectId", 2508); tableData.finalize(); return pantsLOT; } catch (const std::exception& ex) { diff --git a/dGame/dBehaviors/Behavior.cpp b/dGame/dBehaviors/Behavior.cpp index 4d57a9df..7cefd4dd 100644 --- a/dGame/dBehaviors/Behavior.cpp +++ b/dGame/dBehaviors/Behavior.cpp @@ -377,10 +377,10 @@ void Behavior::PlayFx(std::u16string type, const LWOOBJID target, const LWOOBJID return; } - const auto name = std::string(result.getStringField(0)); + const auto name = std::string(result.getStringField("effectName")); if (type.empty()) { - const auto typeResult = result.getStringField(1); + const auto typeResult = result.getStringField("effectType"); type = GeneralUtils::ASCIIToUTF16(typeResult); diff --git a/dGame/dBehaviors/BehaviorContext.cpp b/dGame/dBehaviors/BehaviorContext.cpp index 5ca335b1..95c4c84c 100644 --- a/dGame/dBehaviors/BehaviorContext.cpp +++ b/dGame/dBehaviors/BehaviorContext.cpp @@ -105,7 +105,7 @@ void BehaviorContext::ExecuteUpdates() { this->scheduledUpdates.clear(); } -void BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bitStream) { +bool BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bitStream) { BehaviorSyncEntry entry; auto found = false; @@ -128,7 +128,7 @@ void BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bit if (!found) { LOG("Failed to find behavior sync entry with sync id (%i)!", syncId); - return; + return false; } auto* behavior = entry.behavior; @@ -137,10 +137,11 @@ void BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bit if (behavior == nullptr) { LOG("Invalid behavior for sync id (%i)!", syncId); - return; + return false; } behavior->Sync(this, bitStream, branch); + return true; } @@ -198,6 +199,26 @@ void BehaviorContext::UpdatePlayerSyncs(float deltaTime) { i++; continue; } + + if (this->skillUId != 0 && !clientInitalized) { + EchoSyncSkill echo; + echo.bDone = true; + echo.uiSkillHandle = this->skillUId; + echo.uiBehaviorHandle = entry.handle; + + RakNet::BitStream bitStream{}; + entry.behavior->SyncCalculation(this, bitStream, entry.branchContext); + + echo.sBitStream.assign(reinterpret_cast(bitStream.GetData()), bitStream.GetNumberOfBytesUsed()); + + RakNet::BitStream message; + BitStreamUtils::WriteHeader(message, eConnectionType::CLIENT, eClientMessageType::GAME_MSG); + message.Write(this->originator); + echo.Serialize(message); + + Game::server->Send(message, UNASSIGNED_SYSTEM_ADDRESS, true); + } + this->syncEntries.erase(this->syncEntries.begin() + i); } } @@ -224,6 +245,16 @@ bool BehaviorContext::CalculateUpdate(const float deltaTime) { for (auto i = 0u; i < this->syncEntries.size(); ++i) { auto entry = this->syncEntries.at(i); + if (entry.behavior->m_templateId == BehaviorTemplate::ATTACK_DELAY) { + auto* self = Game::entityManager->GetEntity(originator); + if (self) { + auto* destroyableComponent = self->GetComponent(); + if (destroyableComponent && destroyableComponent->GetHealth() <= 0) { + continue; + } + } + } + if (entry.time > 0) { entry.time -= deltaTime; @@ -333,7 +364,7 @@ void BehaviorContext::FilterTargets(std::vector& targets, std::forward_ } // handle targeting the caster - if (candidate == caster){ + if (candidate == caster) { // if we aren't targeting self, erase, otherise increment and continue if (!targetSelf) index = targets.erase(index); else index++; @@ -356,24 +387,24 @@ void BehaviorContext::FilterTargets(std::vector& targets, std::forward_ } // if they are dead, then earse and continue - if (candidateDestroyableComponent->GetIsDead()){ + if (candidateDestroyableComponent->GetIsDead()) { index = targets.erase(index); continue; } // if their faction is explicitly included, increment and continue auto candidateFactions = candidateDestroyableComponent->GetFactionIDs(); - if (CheckFactionList(includeFactionList, candidateFactions)){ + if (CheckFactionList(includeFactionList, candidateFactions)) { index++; continue; } // check if they are a team member - if (targetTeam){ + if (targetTeam) { auto* team = TeamManager::Instance()->GetTeam(this->caster); - if (team){ + if (team) { // if we find a team member keep it and continue to skip enemy checks - if(std::find(team->members.begin(), team->members.end(), candidate->GetObjectID()) != team->members.end()){ + if (std::find(team->members.begin(), team->members.end(), candidate->GetObjectID()) != team->members.end()) { index++; continue; } @@ -419,8 +450,8 @@ bool BehaviorContext::CheckTargetingRequirements(const Entity* target) const { // returns true if any of the object factions are in the faction list bool BehaviorContext::CheckFactionList(std::forward_list& factionList, std::vector& objectsFactions) const { if (factionList.empty() || objectsFactions.empty()) return false; - for (auto faction : factionList){ - if(std::find(objectsFactions.begin(), objectsFactions.end(), faction) != objectsFactions.end()) return true; + for (auto faction : factionList) { + if (std::find(objectsFactions.begin(), objectsFactions.end(), faction) != objectsFactions.end()) return true; } return false; } diff --git a/dGame/dBehaviors/BehaviorContext.h b/dGame/dBehaviors/BehaviorContext.h index 3e6c0b1d..4922f736 100644 --- a/dGame/dBehaviors/BehaviorContext.h +++ b/dGame/dBehaviors/BehaviorContext.h @@ -93,7 +93,7 @@ struct BehaviorContext void ExecuteUpdates(); - void SyncBehavior(uint32_t syncId, RakNet::BitStream& bitStream); + bool SyncBehavior(uint32_t syncId, RakNet::BitStream& bitStream); void Update(float deltaTime); diff --git a/dGame/dBehaviors/DamageAbsorptionBehavior.cpp b/dGame/dBehaviors/DamageAbsorptionBehavior.cpp index f657c8fd..e5711f4d 100644 --- a/dGame/dBehaviors/DamageAbsorptionBehavior.cpp +++ b/dGame/dBehaviors/DamageAbsorptionBehavior.cpp @@ -27,6 +27,8 @@ void DamageAbsorptionBehavior::Handle(BehaviorContext* context, RakNet::BitStrea destroyable->SetIsShielded(true); context->RegisterTimerBehavior(this, branch, target->GetObjectID()); + + Game::entityManager->SerializeEntity(target); } void DamageAbsorptionBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bitStream, BehaviorBranchContext branch) { @@ -52,7 +54,13 @@ void DamageAbsorptionBehavior::Timer(BehaviorContext* context, BehaviorBranchCon const auto toRemove = std::min(present, this->m_absorbAmount); - destroyable->SetDamageToAbsorb(present - toRemove); + const auto remaining = present - toRemove; + + destroyable->SetDamageToAbsorb(remaining); + + destroyable->SetIsShielded(remaining > 0); + + Game::entityManager->SerializeEntity(target); } void DamageAbsorptionBehavior::Load() { diff --git a/dGame/dBehaviors/SwitchMultipleBehavior.cpp b/dGame/dBehaviors/SwitchMultipleBehavior.cpp index 92c9a8de..00d639d5 100644 --- a/dGame/dBehaviors/SwitchMultipleBehavior.cpp +++ b/dGame/dBehaviors/SwitchMultipleBehavior.cpp @@ -47,11 +47,11 @@ void SwitchMultipleBehavior::Load() { auto result = query.execQuery(); while (!result.eof()) { - const auto behavior_id = static_cast(result.getFloatField(1)); + const auto behavior_id = static_cast(result.getFloatField("behavior")); auto* behavior = CreateBehavior(behavior_id); - auto value = result.getFloatField(2); + auto value = result.getFloatField("value"); this->m_behaviors.emplace_back(value, behavior); diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index 0c2a796c..bfb0bbfa 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -29,7 +29,8 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): Component(parent) { m_Target = LWOOBJID_EMPTY; - SetAiState(AiState::spawn); + m_DirtyStateOrTarget = true; + m_State = AiState::spawn; m_Timer = 1.0f; m_StartPosition = parent->GetPosition(); m_MovementAI = nullptr; @@ -45,20 +46,20 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): auto componentResult = componentQuery.execQuery(); if (!componentResult.eof()) { - if (!componentResult.fieldIsNull(0)) - m_AggroRadius = componentResult.getFloatField(0); + if (!componentResult.fieldIsNull("aggroRadius")) + m_AggroRadius = componentResult.getFloatField("aggroRadius"); - if (!componentResult.fieldIsNull(1)) - m_TetherSpeed = componentResult.getFloatField(1); + if (!componentResult.fieldIsNull("tetherSpeed")) + m_TetherSpeed = componentResult.getFloatField("tetherSpeed"); - if (!componentResult.fieldIsNull(2)) - m_PursuitSpeed = componentResult.getFloatField(2); + if (!componentResult.fieldIsNull("pursuitSpeed")) + m_PursuitSpeed = componentResult.getFloatField("pursuitSpeed"); - if (!componentResult.fieldIsNull(3)) - m_SoftTetherRadius = componentResult.getFloatField(3); + if (!componentResult.fieldIsNull("softTetherRadius")) + m_SoftTetherRadius = componentResult.getFloatField("softTetherRadius"); - if (!componentResult.fieldIsNull(4)) - m_HardTetherRadius = componentResult.getFloatField(4); + if (!componentResult.fieldIsNull("hardTetherRadius")) + m_HardTetherRadius = componentResult.getFloatField("hardTetherRadius"); } componentResult.finalize(); @@ -82,11 +83,11 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): auto result = skillQuery.execQuery(); while (!result.eof()) { - const auto skillId = static_cast(result.getIntField(0)); + const auto skillId = static_cast(result.getIntField("skillID")); - const auto abilityCooldown = static_cast(result.getFloatField(1)); + const auto abilityCooldown = static_cast(result.getFloatField("cooldown")); - const auto behaviorId = static_cast(result.getIntField(2)); + const auto behaviorId = static_cast(result.getIntField("behaviorID")); auto* behavior = Behavior::CreateBehavior(behaviorId); diff --git a/dGame/dComponents/BuffComponent.cpp b/dGame/dComponents/BuffComponent.cpp index 44c88ccb..2c940647 100644 --- a/dGame/dComponents/BuffComponent.cpp +++ b/dGame/dComponents/BuffComponent.cpp @@ -450,7 +450,7 @@ const std::vector& BuffComponent::GetBuffParameters(int32_t buffI param.value = result.getFloatField("NumberValue"); param.effectId = result.getIntField("EffectID"); - if (!result.fieldIsNull(3)) { + if (!result.fieldIsNull("StringValue")) { std::istringstream stream(result.getStringField("StringValue")); std::string token; diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 476235f9..8ec1e4c9 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -389,9 +389,9 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore if (result.eof()) return; - if (result.fieldIsNull(0)) return; + if (result.fieldIsNull("enemyList")) return; - const auto* list_string = result.getStringField(0); + const auto* list_string = result.getStringField("enemyList"); std::stringstream ss(list_string); std::string token; diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 8af7fb34..d6883e17 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -37,6 +37,9 @@ #include "CDScriptComponentTable.h" #include "CDObjectSkillsTable.h" #include "CDSkillBehaviorTable.h" +#include "StringifiedEnum.h" + +#include InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) { this->m_Dirty = true; @@ -492,6 +495,11 @@ void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) { return; } + auto* const groups = inventoryElement->FirstChildElement("grps"); + if (groups) { + LoadGroupXml(*groups); + } + m_Consumable = inventoryElement->IntAttribute("csl", LOT_NULL); auto* bag = bags->FirstChildElement(); @@ -558,19 +566,9 @@ void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) { itemElement->QueryAttribute("parent", &parent); // End custom xml - std::vector config; + auto* item = new Item(id, lot, inventory, slot, count, bound, {}, parent, subKey); - auto* extraInfo = itemElement->FirstChildElement("x"); - - if (extraInfo) { - std::string modInfo = extraInfo->Attribute("ma"); - - LDFBaseData* moduleAssembly = new LDFData(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(modInfo.substr(2, modInfo.size() - 1))); - - config.push_back(moduleAssembly); - } - - const auto* item = new Item(id, lot, inventory, slot, count, bound, config, parent, subKey); + item->LoadConfigXml(*itemElement); if (equipped) { const auto info = Inventory::FindItemComponent(lot); @@ -640,6 +638,15 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) { bags->LinkEndChild(bag); } + auto* groups = inventoryElement->FirstChildElement("grps"); + if (groups) { + groups->DeleteChildren(); + } else { + groups = inventoryElement->InsertNewChildElement("grps"); + } + + UpdateGroupXml(*groups); + auto* items = inventoryElement->FirstChildElement("items"); if (items == nullptr) { @@ -676,17 +683,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) { itemElement->SetAttribute("parent", item->GetParent()); // End custom xml - for (auto* data : item->GetConfig()) { - if (data->GetKey() != u"assemblyPartLOTs") { - continue; - } - - auto* extraInfo = document.NewElement("x"); - - extraInfo->SetAttribute("ma", data->GetString(false).c_str()); - - itemElement->LinkEndChild(extraInfo); - } + item->SaveConfigXml(*itemElement); bagElement->LinkEndChild(itemElement); } @@ -895,8 +892,6 @@ void InventoryComponent::UnEquipItem(Item* item) { RemoveSlot(item->GetInfo().equipLocation); - PurgeProxies(item); - UnequipScripts(item); Game::entityManager->SerializeEntity(m_Parent); @@ -906,6 +901,8 @@ void InventoryComponent::UnEquipItem(Item* item) { PropertyManagementComponent::Instance()->GetParent()->OnZonePropertyModelRemovedWhileEquipped(m_Parent); Game::zoneManager->GetZoneControlObject()->OnZonePropertyModelRemovedWhileEquipped(m_Parent); } + + PurgeProxies(item); } @@ -1094,7 +1091,7 @@ void InventoryComponent::CheckItemSet(const LOT lot) { auto result = query.execQuery(); while (!result.eof()) { - const auto id = result.getIntField(0); + const auto id = result.getIntField("setID"); bool found = false; @@ -1525,10 +1522,10 @@ void InventoryComponent::PurgeProxies(Item* item) { const auto root = item->GetParent(); if (root != LWOOBJID_EMPTY) { - item = FindItemById(root); + Item* itemRoot = FindItemById(root); - if (item != nullptr) { - UnEquipItem(item); + if (itemRoot != nullptr) { + UnEquipItem(itemRoot); } return; @@ -1600,18 +1597,18 @@ void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument& document) { } -bool InventoryComponent::SetSkill(int32_t slot, uint32_t skillId){ +bool InventoryComponent::SetSkill(int32_t slot, uint32_t skillId) { BehaviorSlot behaviorSlot = BehaviorSlot::Invalid; - if (slot == 1 ) behaviorSlot = BehaviorSlot::Primary; - else if (slot == 2 ) behaviorSlot = BehaviorSlot::Offhand; - else if (slot == 3 ) behaviorSlot = BehaviorSlot::Neck; - else if (slot == 4 ) behaviorSlot = BehaviorSlot::Head; - else if (slot == 5 ) behaviorSlot = BehaviorSlot::Consumable; + if (slot == 1) behaviorSlot = BehaviorSlot::Primary; + else if (slot == 2) behaviorSlot = BehaviorSlot::Offhand; + else if (slot == 3) behaviorSlot = BehaviorSlot::Neck; + else if (slot == 4) behaviorSlot = BehaviorSlot::Head; + else if (slot == 5) behaviorSlot = BehaviorSlot::Consumable; else return false; return SetSkill(behaviorSlot, skillId); } -bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId){ +bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId) { if (skillId == 0) return false; const auto index = m_Skills.find(slot); if (index != m_Skills.end()) { @@ -1624,3 +1621,109 @@ bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId){ return true; } +void InventoryComponent::UpdateGroup(const GroupUpdate& groupUpdate) { + if (groupUpdate.groupId.empty()) return; + + if (groupUpdate.inventory != eInventoryType::BRICKS && groupUpdate.inventory != eInventoryType::MODELS) { + LOG("Invalid inventory type for grouping %s", StringifiedEnum::ToString(groupUpdate.inventory).data()); + return; + } + + auto& groups = m_Groups[groupUpdate.inventory]; + auto groupItr = std::ranges::find_if(groups, [&groupUpdate](const Group& group) { + return group.groupId == groupUpdate.groupId; + }); + + if (groupUpdate.command != GroupUpdateCommand::ADD && groupItr == groups.end()) { + LOG("Group %s not found in inventory %s. Cannot process command.", groupUpdate.groupId.c_str(), StringifiedEnum::ToString(groupUpdate.inventory).data()); + return; + } + + if (groupUpdate.command == GroupUpdateCommand::ADD && groups.size() >= MaximumGroupCount) { + LOG("Cannot add group to inventory %s. Maximum group count reached.", StringifiedEnum::ToString(groupUpdate.inventory).data()); + return; + } + + switch (groupUpdate.command) { + case GroupUpdateCommand::ADD: { + auto& group = groups.emplace_back(); + group.groupId = groupUpdate.groupId; + group.groupName = groupUpdate.groupName; + break; + } + case GroupUpdateCommand::ADD_LOT: { + groupItr->lots.insert(groupUpdate.lot); + break; + } + case GroupUpdateCommand::REMOVE: { + groups.erase(groupItr); + break; + } + case GroupUpdateCommand::REMOVE_LOT: { + groupItr->lots.erase(groupUpdate.lot); + break; + } + case GroupUpdateCommand::MODIFY: { + groupItr->groupName = groupUpdate.groupName; + break; + } + default: { + LOG("Invalid group update command %i", groupUpdate.command); + break; + } + } +} + +void InventoryComponent::UpdateGroupXml(tinyxml2::XMLElement& groups) const { + for (const auto& [inventory, groupsData] : m_Groups) { + for (const auto& group : groupsData) { + auto* const groupElement = groups.InsertNewChildElement("grp"); + + groupElement->SetAttribute("id", group.groupId.c_str()); + groupElement->SetAttribute("n", group.groupName.c_str()); + groupElement->SetAttribute("t", static_cast(inventory)); + groupElement->SetAttribute("u", 0); + std::ostringstream lots; + bool first = true; + for (const auto lot : group.lots) { + if (!first) lots << ' '; + first = false; + + lots << lot; + } + groupElement->SetAttribute("l", lots.str().c_str()); + } + } +} + +void InventoryComponent::LoadGroupXml(const tinyxml2::XMLElement& groups) { + auto* groupElement = groups.FirstChildElement("grp"); + + while (groupElement) { + const char* groupId = nullptr; + const char* groupName = nullptr; + const char* lots = nullptr; + uint32_t inventory = eInventoryType::INVALID; + + groupElement->QueryStringAttribute("id", &groupId); + groupElement->QueryStringAttribute("n", &groupName); + groupElement->QueryStringAttribute("l", &lots); + groupElement->QueryAttribute("t", &inventory); + + if (!groupId || !groupName || !lots) { + LOG("Failed to load group from xml id %i name %i lots %i", + groupId == nullptr, groupName == nullptr, lots == nullptr); + } else { + auto& group = m_Groups[static_cast(inventory)].emplace_back(); + group.groupId = groupId; + group.groupName = groupName; + + for (const auto& lotStr : GeneralUtils::SplitString(lots, ' ')) { + auto lot = GeneralUtils::TryParse(lotStr); + if (lot) group.lots.insert(*lot); + } + } + + groupElement = groupElement->NextSiblingElement("grp"); + } +} diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index a1eb14d1..28158ab5 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -37,6 +37,35 @@ enum class eItemType : int32_t; */ class InventoryComponent final : public Component { public: + struct Group { + // Generated ID for the group. The ID is sent by the client and has the format user_group + Math.random() * UINT_MAX. + std::string groupId; + // Custom name assigned by the user. + std::string groupName; + // All the lots the user has in the group. + std::set lots; + }; + + enum class GroupUpdateCommand { + ADD, + ADD_LOT, + MODIFY, + REMOVE, + REMOVE_LOT, + }; + + // Based on the command, certain fields will be used or not used. + // for example, ADD_LOT wont use groupName, MODIFY wont use lots, etc. + struct GroupUpdate { + std::string groupId; + std::string groupName; + LOT lot; + eInventoryType inventory; + GroupUpdateCommand command; + }; + + static constexpr uint32_t MaximumGroupCount = 50; + static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::INVENTORY; InventoryComponent(Entity* parent); @@ -367,14 +396,23 @@ public: */ void UnequipScripts(Item* unequippedItem); - std::map GetSkills(){ return m_Skills; }; + std::map GetSkills() { return m_Skills; }; bool SetSkill(int slot, uint32_t skillId); bool SetSkill(BehaviorSlot slot, uint32_t skillId); + void UpdateGroup(const GroupUpdate& groupUpdate); + void RemoveGroup(const std::string& groupId); + ~InventoryComponent() override; private: + /** + * The key is the inventory the group belongs to, the value maps' key is the id for the group. + * This is only used for bricks and model inventories. + */ + std::map> m_Groups{ { eInventoryType::BRICKS, {} }, { eInventoryType::MODELS, {} } }; + /** * All the inventory this entity possesses */ @@ -477,6 +515,9 @@ private: * @param document the xml doc to load from */ void UpdatePetXml(tinyxml2::XMLDocument& document); + + void LoadGroupXml(const tinyxml2::XMLElement& groups); + void UpdateGroupXml(tinyxml2::XMLElement& groups) const; }; #endif diff --git a/dGame/dComponents/MissionComponent.cpp b/dGame/dComponents/MissionComponent.cpp index 1eb02e57..0760d8e4 100644 --- a/dGame/dComponents/MissionComponent.cpp +++ b/dGame/dComponents/MissionComponent.cpp @@ -466,8 +466,8 @@ bool MissionComponent::RequiresItem(const LOT lot) { return false; } - if (!result.fieldIsNull(0)) { - const auto type = std::string(result.getStringField(0)); + if (!result.fieldIsNull("type")) { + const auto type = std::string(result.getStringField("type")); result.finalize(); diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index 75f2a019..91680987 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -6,6 +6,9 @@ #include "BehaviorStates.h" #include "ControlBehaviorMsgs.h" +#include "tinyxml2.h" + +#include "Database.h" ModelComponent::ModelComponent(Entity* parent) : Component(parent) { m_OriginalPosition = m_Parent->GetDefaultPosition(); @@ -14,6 +17,33 @@ ModelComponent::ModelComponent(Entity* parent) : Component(parent) { m_userModelID = m_Parent->GetVarAs(u"userModelID"); } +void ModelComponent::LoadBehaviors() { + auto behaviors = GeneralUtils::SplitString(m_Parent->GetVar(u"userModelBehaviors"), ','); + for (const auto& behavior : behaviors) { + if (behavior.empty()) continue; + + const auto behaviorId = GeneralUtils::TryParse(behavior); + if (!behaviorId.has_value() || behaviorId.value() == 0) continue; + + LOG_DEBUG("Loading behavior %d", behaviorId.value()); + auto& inserted = m_Behaviors.emplace_back(); + inserted.SetBehaviorId(*behaviorId); + + const auto behaviorStr = Database::Get()->GetBehavior(behaviorId.value()); + + tinyxml2::XMLDocument behaviorXml; + auto res = behaviorXml.Parse(behaviorStr.c_str(), behaviorStr.size()); + LOG_DEBUG("Behavior %i %d: %s", res, behaviorId.value(), behaviorStr.c_str()); + + const auto* const behaviorRoot = behaviorXml.FirstChildElement("Behavior"); + if (!behaviorRoot) { + LOG("Failed to load behavior %d due to missing behavior root", behaviorId.value()); + continue; + } + inserted.Deserialize(*behaviorRoot); + } +} + void ModelComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { // ItemComponent Serialization. Pets do not get this serialization. if (!m_Parent->HasComponent(eReplicaComponentType::PET)) { @@ -72,3 +102,23 @@ void ModelComponent::MoveToInventory(MoveToInventoryMessage& msg) { m_Behaviors.erase(m_Behaviors.begin() + msg.GetBehaviorIndex()); // TODO move to the inventory } + +std::array, 5> ModelComponent::GetBehaviorsForSave() const { + std::array, 5> toReturn{}; + for (auto i = 0; i < m_Behaviors.size(); i++) { + const auto& behavior = m_Behaviors.at(i); + if (behavior.GetBehaviorId() == -1) continue; + auto& [id, behaviorData] = toReturn[i]; + id = behavior.GetBehaviorId(); + + tinyxml2::XMLDocument doc; + auto* root = doc.NewElement("Behavior"); + behavior.Serialize(*root); + doc.InsertFirstChild(root); + + tinyxml2::XMLPrinter printer(0, true, 0); + doc.Print(&printer); + behaviorData = printer.CStr(); + } + return toReturn; +} diff --git a/dGame/dComponents/ModelComponent.h b/dGame/dComponents/ModelComponent.h index dc6810eb..9e23eafb 100644 --- a/dGame/dComponents/ModelComponent.h +++ b/dGame/dComponents/ModelComponent.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "dCommonVars.h" @@ -28,6 +29,8 @@ public: ModelComponent(Entity* parent); + void LoadBehaviors(); + void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; /** @@ -109,6 +112,8 @@ public: void VerifyBehaviors(); + std::array, 5> GetBehaviorsForSave() const; + private: /** * The behaviors of the model diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index a6bb00fb..2f0df185 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -2,6 +2,7 @@ #include "GameMessages.h" #include "BrickDatabase.h" #include "CDClientDatabase.h" +#include "CDTamingBuildPuzzleTable.h" #include "ChatPackets.h" #include "EntityManager.h" #include "Character.h" @@ -31,8 +32,9 @@ #include "eGameMasterLevel.h" #include "eMissionState.h" #include "dNavMesh.h" +#include "eGameActivity.h" +#include "eStateChangeType.h" -std::unordered_map PetComponent::buildCache{}; std::unordered_map PetComponent::currentActivities{}; std::unordered_map PetComponent::activePets{}; @@ -40,7 +42,7 @@ std::unordered_map PetComponent::activePets{}; * Maps all the pet lots to a flag indicating that the player has caught it. All basic pets have been guessed by ObjID * while the faction ones could be checked using their respective missions. */ -std::map PetComponent::petFlags = { +const std::map PetComponent::petFlags{ { 3050, 801 }, // Elephant { 3054, 803 }, // Cat { 3195, 806 }, // Triceratops @@ -87,7 +89,6 @@ PetComponent::PetComponent(Entity* parentEntity, uint32_t componentId) : Compone m_StartPosition = NiPoint3Constant::ZERO; m_MovementAI = nullptr; m_TresureTime = 0; - m_Preconditions = nullptr; std::string checkPreconditions = GeneralUtils::UTF16ToWTF8(parentEntity->GetVar(u"CheckPrecondition")); @@ -152,96 +153,53 @@ void PetComponent::OnUse(Entity* originator) { m_Tamer = LWOOBJID_EMPTY; } - auto* inventoryComponent = originator->GetComponent(); - + auto* const inventoryComponent = originator->GetComponent(); if (inventoryComponent == nullptr) { return; } - if (m_Preconditions != nullptr && !m_Preconditions->Check(originator, true)) { + if (m_Preconditions.has_value() && !m_Preconditions->Check(originator, true)) { return; } - auto* movementAIComponent = m_Parent->GetComponent(); - + auto* const movementAIComponent = m_Parent->GetComponent(); if (movementAIComponent != nullptr) { movementAIComponent->Stop(); } inventoryComponent->DespawnPet(); - const auto& cached = buildCache.find(m_Parent->GetLOT()); - int32_t imaginationCost = 0; - - std::string buildFile; - - if (cached == buildCache.end()) { - auto query = CDClientDatabase::CreatePreppedStmt( - "SELECT ValidPiecesLXF, PuzzleModelLot, Timelimit, NumValidPieces, imagCostPerBuild FROM TamingBuildPuzzles WHERE NPCLot = ?;"); - query.bind(1, static_cast(m_Parent->GetLOT())); - - auto result = query.execQuery(); - - if (result.eof()) { - ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to find the puzzle minigame for this pet."); - - return; - } - - if (result.fieldIsNull(0)) { - result.finalize(); - - return; - } - - buildFile = std::string(result.getStringField(0)); - - PetPuzzleData data; - data.buildFile = buildFile; - data.puzzleModelLot = result.getIntField(1); - data.timeLimit = result.getFloatField(2); - data.numValidPieces = result.getIntField(3); - data.imaginationCost = result.getIntField(4); - if (data.timeLimit <= 0) data.timeLimit = 60; - imaginationCost = data.imaginationCost; - - buildCache[m_Parent->GetLOT()] = data; - - result.finalize(); - } else { - buildFile = cached->second.buildFile; - imaginationCost = cached->second.imaginationCost; + const auto* const entry = CDClientManager::GetTable()->GetByLOT(m_Parent->GetLOT()); + if (!entry) { + ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to find the puzzle minigame for this pet."); + return; } - auto* destroyableComponent = originator->GetComponent(); - + const auto* const destroyableComponent = originator->GetComponent(); if (destroyableComponent == nullptr) { return; } - auto imagination = destroyableComponent->GetImagination(); - - if (imagination < imaginationCost) { + const auto imagination = destroyableComponent->GetImagination(); + if (imagination < entry->imaginationCost) { return; } - const auto& bricks = BrickDatabase::GetBricks(buildFile); - + const auto& bricks = BrickDatabase::GetBricks(entry->validPieces); if (bricks.empty()) { ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to load the puzzle minigame for this pet."); - LOG("Couldn't find %s for minigame!", buildFile.c_str()); + LOG("Couldn't find %s for minigame!", entry->validPieces.c_str()); return; } - auto petPosition = m_Parent->GetPosition(); + const auto petPosition = m_Parent->GetPosition(); - auto originatorPosition = originator->GetPosition(); + const auto originatorPosition = originator->GetPosition(); m_Parent->SetRotation(NiQuaternion::LookAt(petPosition, originatorPosition)); float interactionDistance = m_Parent->GetVar(u"interaction_distance"); - if (interactionDistance <= 0) { interactionDistance = 15; } @@ -254,24 +212,23 @@ void PetComponent::OnUse(Entity* originator) { if (dpWorld::IsLoaded()) { NiPoint3 attempt = petPosition + forward * interactionDistance; - float y = dpWorld::GetNavMesh()->GetHeightAtPoint(attempt); + NiPoint3 nearestPoint = dpWorld::GetNavMesh()->NearestPoint(attempt); - while (std::abs(y - petPosition.y) > 4 && interactionDistance > 10) { + while (std::abs(nearestPoint.y - petPosition.y) > 4 && interactionDistance > 10) { const NiPoint3 forward = m_Parent->GetRotation().GetForwardVector(); attempt = originatorPosition + forward * interactionDistance; - y = dpWorld::GetNavMesh()->GetHeightAtPoint(attempt); + nearestPoint = dpWorld::GetNavMesh()->NearestPoint(attempt); interactionDistance -= 0.5f; } - position = attempt; + position = nearestPoint; } else { position = petPosition + forward * interactionDistance; } - auto rotation = NiQuaternion::LookAt(position, petPosition); GameMessages::SendNotifyPetTamingMinigame( @@ -290,11 +247,11 @@ void PetComponent::OnUse(Entity* originator) { m_Parent->GetObjectID(), LWOOBJID_EMPTY, originator->GetObjectID(), - true, + false, ePetTamingNotifyType::BEGIN, - petPosition, - position, - rotation, + NiPoint3Constant::ZERO, + NiPoint3Constant::ZERO, + NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f), UNASSIGNED_SYSTEM_ADDRESS ); @@ -302,11 +259,18 @@ void PetComponent::OnUse(Entity* originator) { m_Tamer = originator->GetObjectID(); SetStatus(5); + Game::entityManager->SerializeEntity(m_Parent); currentActivities.insert_or_assign(m_Tamer, m_Parent->GetObjectID()); // Notify the start of a pet taming minigame m_Parent->GetScript()->OnNotifyPetTamingMinigame(m_Parent, originator, ePetTamingNotifyType::BEGIN); + + auto* characterComponent = originator->GetComponent(); + if (characterComponent != nullptr) { + characterComponent->SetCurrentActivity(eGameActivity::PET_TAMING); + Game::entityManager->SerializeEntity(originator); + } } void PetComponent::Update(float deltaTime) { @@ -477,9 +441,8 @@ void PetComponent::TryBuild(uint32_t numBricks, bool clientFailed) { return; } - const auto& cached = buildCache.find(m_Parent->GetLOT()); - - if (cached == buildCache.end()) return; + const auto* const entry = CDClientManager::GetTable()->GetByLOT(m_Parent->GetLOT()); + if (!entry) return; auto* destroyableComponent = tamer->GetComponent(); @@ -487,14 +450,14 @@ void PetComponent::TryBuild(uint32_t numBricks, bool clientFailed) { auto imagination = destroyableComponent->GetImagination(); - imagination -= cached->second.imaginationCost; + imagination -= entry->imaginationCost; destroyableComponent->SetImagination(imagination); Game::entityManager->SerializeEntity(tamer); if (clientFailed) { - if (imagination < cached->second.imaginationCost) { + if (imagination < entry->imaginationCost) { ClientFailTamingMinigame(); } } else { @@ -517,17 +480,14 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) { return; } - const auto& cached = buildCache.find(m_Parent->GetLOT()); - - if (cached == buildCache.end()) { - return; - } + const auto* const entry = CDClientManager::GetTable()->GetByLOT(m_Parent->GetLOT()); + if (!entry) return; GameMessages::SendPlayFXEffect(tamer, -1, u"petceleb", "", LWOOBJID_EMPTY, 1, 1, true); RenderComponent::PlayAnimation(tamer, u"rebuild-celebrate"); EntityInfo info{}; - info.lot = cached->second.puzzleModelLot; + info.lot = entry->puzzleModelLot; info.pos = position; info.rot = NiQuaternionConstant::IDENTITY; info.spawnerID = tamer->GetObjectID(); @@ -675,6 +635,11 @@ void PetComponent::RequestSetPetName(std::u16string name) { UNASSIGNED_SYSTEM_ADDRESS ); + auto* characterComponent = tamer->GetComponent(); + if (characterComponent != nullptr) { + characterComponent->SetCurrentActivity(eGameActivity::NONE); + Game::entityManager->SerializeEntity(tamer); + } GameMessages::SendTerminateInteraction(m_Tamer, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID()); auto* modelEntity = Game::entityManager->GetEntity(m_ModelId); @@ -714,6 +679,11 @@ void PetComponent::ClientExitTamingMinigame(bool voluntaryExit) { UNASSIGNED_SYSTEM_ADDRESS ); + auto* characterComponent = tamer->GetComponent(); + if (characterComponent != nullptr) { + characterComponent->SetCurrentActivity(eGameActivity::NONE); + Game::entityManager->SerializeEntity(tamer); + } GameMessages::SendNotifyTamingModelLoadedOnServer(m_Tamer, tamer->GetSystemAddress()); GameMessages::SendTerminateInteraction(m_Tamer, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID()); @@ -731,13 +701,10 @@ void PetComponent::ClientExitTamingMinigame(bool voluntaryExit) { } void PetComponent::StartTimer() { - const auto& cached = buildCache.find(m_Parent->GetLOT()); + const auto* const entry = CDClientManager::GetTable()->GetByLOT(m_Parent->GetLOT()); + if (!entry) return; - if (cached == buildCache.end()) { - return; - } - - m_Timer = cached->second.timeLimit; + m_Timer = entry->timeLimit; } void PetComponent::ClientFailTamingMinigame() { @@ -763,6 +730,11 @@ void PetComponent::ClientFailTamingMinigame() { UNASSIGNED_SYSTEM_ADDRESS ); + auto* characterComponent = tamer->GetComponent(); + if (characterComponent != nullptr) { + characterComponent->SetCurrentActivity(eGameActivity::NONE); + Game::entityManager->SerializeEntity(tamer); + } GameMessages::SendNotifyTamingModelLoadedOnServer(m_Tamer, tamer->GetSystemAddress()); GameMessages::SendTerminateInteraction(m_Tamer, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID()); @@ -823,8 +795,6 @@ void PetComponent::Wander() { } void PetComponent::Activate(Item* item, bool registerPet, bool fromTaming) { - AddDrainImaginationTimer(item, fromTaming); - m_ItemId = item->GetId(); m_DatabaseId = item->GetSubKey(); @@ -835,6 +805,7 @@ void PetComponent::Activate(Item* item, bool registerPet, bool fromTaming) { inventoryComponent->DespawnPet(); m_Owner = inventoryComponent->GetParent()->GetObjectID(); + AddDrainImaginationTimer(fromTaming); auto* owner = GetOwner(); @@ -887,17 +858,14 @@ void PetComponent::Activate(Item* item, bool registerPet, bool fromTaming) { } } -void PetComponent::AddDrainImaginationTimer(Item* item, bool fromTaming) { +void PetComponent::AddDrainImaginationTimer(bool fromTaming) { if (Game::config->GetValue("pets_take_imagination") != "1") return; - auto playerInventory = item->GetInventory(); - if (!playerInventory) return; - - auto playerInventoryComponent = playerInventory->GetComponent(); - if (!playerInventoryComponent) return; - - auto playerEntity = playerInventoryComponent->GetParent(); - if (!playerEntity) return; + auto* playerEntity = Game::entityManager->GetEntity(m_Owner); + if (!playerEntity) { + LOG("owner was null or didnt exist!"); + return; + } auto playerDestroyableComponent = playerEntity->GetComponent(); if (!playerDestroyableComponent) return; @@ -906,12 +874,16 @@ void PetComponent::AddDrainImaginationTimer(Item* item, bool fromTaming) { if (!fromTaming) playerDestroyableComponent->Imagine(-1); // Set this to a variable so when this is called back from the player the timer doesn't fire off. - m_Parent->AddCallbackTimer(m_PetInfo.imaginationDrainRate, [playerDestroyableComponent, this, item]() { - if (!playerDestroyableComponent) { - LOG("No petComponent and/or no playerDestroyableComponent"); + m_Parent->AddCallbackTimer(m_PetInfo.imaginationDrainRate, [this]() { + const auto* owner = Game::entityManager->GetEntity(m_Owner); + if (!owner) { + LOG("owner was null or didnt exist!"); return; } + const auto* playerDestroyableComponent = owner->GetComponent(); + if (!playerDestroyableComponent) return; + // If we are out of imagination despawn the pet. if (playerDestroyableComponent->GetImagination() == 0) { this->Deactivate(); @@ -921,15 +893,13 @@ void PetComponent::AddDrainImaginationTimer(Item* item, bool fromTaming) { GameMessages::SendUseItemRequirementsResponse(playerEntity->GetObjectID(), playerEntity->GetSystemAddress(), eUseItemResponse::NoImaginationForPet); } - this->AddDrainImaginationTimer(item); + this->AddDrainImaginationTimer(); }); } void PetComponent::Deactivate() { GameMessages::SendPlayFXEffect(m_Parent->GetObjectID(), -1, u"despawn", "", LWOOBJID_EMPTY, 1, 1, true); - GameMessages::SendMarkInventoryItemAsActive(m_Owner, false, eUnequippableActiveType::PET, m_ItemId, GetOwner()->GetSystemAddress()); - activePets.erase(m_Owner); m_Parent->Kill(); @@ -938,6 +908,8 @@ void PetComponent::Deactivate() { if (owner == nullptr) return; + GameMessages::SendMarkInventoryItemAsActive(m_Owner, false, eUnequippableActiveType::PET, m_ItemId, owner->GetSystemAddress()); + GameMessages::SendAddPetToPlayer(m_Owner, 0, u"", LWOOBJID_EMPTY, LOT_NULL, owner->GetSystemAddress()); GameMessages::SendRegisterPetID(m_Owner, LWOOBJID_EMPTY, owner->GetSystemAddress()); @@ -1062,6 +1034,7 @@ Entity* PetComponent::GetParentEntity() const { } PetComponent::~PetComponent() { + m_Owner = LWOOBJID_EMPTY; } void PetComponent::SetPetNameForModeration(const std::string& petName) { @@ -1086,6 +1059,6 @@ void PetComponent::LoadPetNameFromModeration() { } } -void PetComponent::SetPreconditions(std::string& preconditions) { - m_Preconditions = new PreconditionExpression(preconditions); +void PetComponent::SetPreconditions(const std::string& preconditions) { + m_Preconditions = std::make_optional(preconditions); } diff --git a/dGame/dComponents/PetComponent.h b/dGame/dComponents/PetComponent.h index f4198cae..f02a9d30 100644 --- a/dGame/dComponents/PetComponent.h +++ b/dGame/dComponents/PetComponent.h @@ -165,7 +165,7 @@ public: * Sets preconditions for the pet that need to be met before it can be tamed * @param conditions the preconditions to set */ - void SetPreconditions(std::string& conditions); + void SetPreconditions(const std::string& conditions); /** * Returns the entity that this component belongs to @@ -205,7 +205,7 @@ public: * * @param item The item that represents this pet in the inventory. */ - void AddDrainImaginationTimer(Item* item, bool fromTaming = false); + void AddDrainImaginationTimer(bool fromTaming = false); private: @@ -250,15 +250,10 @@ private: */ static std::unordered_map currentActivities; - /** - * Cache of all the minigames and their information from the database - */ - static std::unordered_map buildCache; - /** * Flags that indicate that a player has tamed a pet, indexed by the LOT of the pet */ - static std::map petFlags; + static const std::map petFlags; /** * The ID of the component in the pet component table @@ -349,7 +344,7 @@ private: /** * Preconditions that need to be met before an entity can tame this pet */ - PreconditionExpression* m_Preconditions; + std::optional m_Preconditions{}; /** * Pet information loaded from the CDClientDatabase diff --git a/dGame/dComponents/PhantomPhysicsComponent.cpp b/dGame/dComponents/PhantomPhysicsComponent.cpp index ba0c2495..95fed36e 100644 --- a/dGame/dComponents/PhantomPhysicsComponent.cpp +++ b/dGame/dComponents/PhantomPhysicsComponent.cpp @@ -47,7 +47,7 @@ PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent) : PhysicsCompon m_Direction = NiPoint3(); // * m_DirectionalMultiplier if (m_Parent->GetVar(u"create_physics")) { - CreatePhysics(); + m_dpEntity = CreatePhysicsLnv(m_Scale, ComponentType); } if (m_Parent->GetVar(u"respawnVol")) { @@ -89,105 +89,9 @@ PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent) : PhysicsCompon m_RespawnRot = m_Rotation; } - /* - for (LDFBaseData* data : settings) { - if (data) { - if (data->GetKey() == u"create_physics") { - if (bool(std::stoi(data->GetValueAsString()))) { - CreatePhysics(settings); - } - } - - if (data->GetKey() == u"respawnVol") { - if (bool(std::stoi(data->GetValueAsString()))) { - m_IsRespawnVolume = true; - } - } - - if (m_IsRespawnVolume) { - if (data->GetKey() == u"rspPos") { - //Joy, we get to split strings! - std::stringstream test(data->GetValueAsString()); - std::string segment; - std::vector seglist; - - while (std::getline(test, segment, '\x1f')) { - seglist.push_back(segment); - } - - m_RespawnPos = NiPoint3(std::stof(seglist[0]), std::stof(seglist[1]), std::stof(seglist[2])); - } - - if (data->GetKey() == u"rspRot") { - //Joy, we get to split strings! - std::stringstream test(data->GetValueAsString()); - std::string segment; - std::vector seglist; - - while (std::getline(test, segment, '\x1f')) { - seglist.push_back(segment); - } - - m_RespawnRot = NiQuaternion(std::stof(seglist[0]), std::stof(seglist[1]), std::stof(seglist[2]), std::stof(seglist[3])); - } - } - - if (m_Parent->GetLOT() == 4945) // HF - RespawnPoints - { - m_IsRespawnVolume = true; - m_RespawnPos = m_Position; - m_RespawnRot = m_Rotation; - } - } - } - */ - - if (!m_HasCreatedPhysics) { - CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable(); - auto componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), eReplicaComponentType::PHANTOM_PHYSICS); - - CDPhysicsComponentTable* physComp = CDClientManager::GetTable(); - - if (physComp == nullptr) return; - - auto* info = physComp->GetByID(componentID); - if (info == nullptr || info->physicsAsset == "" || info->physicsAsset == "NO_PHYSICS") return; - - //temp test - if (info->physicsAsset == "miscellaneous\\misc_phys_10x1x5.hkx") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 10.0f, 5.0f, 1.0f); - } else if (info->physicsAsset == "miscellaneous\\misc_phys_640x640.hkx") { - // TODO Fix physics simulation to do simulation at high velocities due to bullet through paper problem... - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 1638.4f, 13.521004f * 2.0f, 1638.4f); - - // Move this down by 13.521004 units so it is still effectively at the same height as before - m_Position = m_Position - NiPoint3Constant::UNIT_Y * 13.521004f; - } else if (info->physicsAsset == "env\\trigger_wall_tall.hkx") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 10.0f, 25.0f, 1.0f); - } else if (info->physicsAsset == "env\\env_gen_placeholderphysics.hkx") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 20.0f, 20.0f, 20.0f); - } else if (info->physicsAsset == "env\\POI_trigger_wall.hkx") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 1.0f, 12.5f, 20.0f); // Not sure what the real size is - } else if (info->physicsAsset == "env\\NG_NinjaGo\\env_ng_gen_gate_chamber_puzzle_ceiling_tile_falling_phantom.hkx") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 18.0f, 5.0f, 15.0f); - m_Position += m_Rotation.GetForwardVector() * 7.5f; - } else if (info->physicsAsset == "env\\NG_NinjaGo\\ng_flamejet_brick_phantom.HKX") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 1.0f, 1.0f, 12.0f); - m_Position += m_Rotation.GetForwardVector() * 6.0f; - } else if (info->physicsAsset == "env\\Ring_Trigger.hkx") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 6.0f, 6.0f, 6.0f); - } else if (info->physicsAsset == "env\\vfx_propertyImaginationBall.hkx") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 4.5f); - } else if (info->physicsAsset == "env\\env_won_fv_gas-blocking-volume.hkx") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 390.496826f, 111.467964f, 600.821534f, true); - m_Position.y -= (111.467964f * m_Scale) / 2; - } else { - // LOG_DEBUG("This one is supposed to have %s", info->physicsAsset.c_str()); - - //add fallback cube: - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 2.0f, 2.0f, 2.0f); - } - + if (!m_dpEntity) { + m_dpEntity = CreatePhysicsEntity(ComponentType); + if (!m_dpEntity) return; m_dpEntity->SetScale(m_Scale); m_dpEntity->SetRotation(m_Rotation); m_dpEntity->SetPosition(m_Position); @@ -201,69 +105,6 @@ PhantomPhysicsComponent::~PhantomPhysicsComponent() { } } -void PhantomPhysicsComponent::CreatePhysics() { - unsigned char alpha; - unsigned char red; - unsigned char green; - unsigned char blue; - int type = -1; - float x = 0.0f; - float y = 0.0f; - float z = 0.0f; - float width = 0.0f; //aka "radius" - float height = 0.0f; - - if (m_Parent->HasVar(u"primitiveModelType")) { - type = m_Parent->GetVar(u"primitiveModelType"); - x = m_Parent->GetVar(u"primitiveModelValueX"); - y = m_Parent->GetVar(u"primitiveModelValueY"); - z = m_Parent->GetVar(u"primitiveModelValueZ"); - } else { - CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable(); - auto componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), eReplicaComponentType::PHANTOM_PHYSICS); - - CDPhysicsComponentTable* physComp = CDClientManager::GetTable(); - - if (physComp == nullptr) return; - - auto info = physComp->GetByID(componentID); - - if (info == nullptr) return; - - type = info->pcShapeType; - width = info->playerRadius; - height = info->playerHeight; - } - - switch (type) { - case 1: { //Make a new box shape - NiPoint3 boxSize(x, y, z); - if (x == 0.0f) { - //LU has some weird values, so I think it's best to scale them down a bit - if (height < 0.5f) height = 2.0f; - if (width < 0.5f) width = 2.0f; - - //Scale them: - width = width * m_Scale; - height = height * m_Scale; - - boxSize = NiPoint3(width, height, width); - } - - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), boxSize); - break; - } - } - - if (!m_dpEntity) return; - - m_dpEntity->SetPosition({ m_Position.x, m_Position.y - (height / 2), m_Position.z }); - - dpWorld::AddEntity(m_dpEntity); - - m_HasCreatedPhysics = true; -} - void PhantomPhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { PhysicsComponent::Serialize(outBitStream, bIsInitialUpdate); @@ -308,8 +149,9 @@ void ApplyCollisionEffect(const LWOOBJID& target, const ePhysicsEffectType effec controllablePhysicsComponent->SetGravityScale(effectScale); GameMessages::SendSetGravityScale(target, effectScale, targetEntity->GetSystemAddress()); } + break; } - // The other types are not handled by the server + case ePhysicsEffectType::ATTRACT: case ePhysicsEffectType::FRICTION: case ePhysicsEffectType::PUSH: @@ -317,6 +159,7 @@ void ApplyCollisionEffect(const LWOOBJID& target, const ePhysicsEffectType effec default: break; } + // The other types are not handled by the server and are here to handle all cases of the enum. } void PhantomPhysicsComponent::Update(float deltaTime) { @@ -356,24 +199,12 @@ void PhantomPhysicsComponent::SetDirection(const NiPoint3& pos) { m_IsDirectional = true; } -void PhantomPhysicsComponent::SpawnVertices() { - if (!m_dpEntity) return; - - LOG("%llu", m_Parent->GetObjectID()); - auto box = static_cast(m_dpEntity->GetShape()); - for (auto vert : box->GetVertices()) { - LOG("%f, %f, %f", vert.x, vert.y, vert.z); - - EntityInfo info; - info.lot = 33; - info.pos = vert; - info.spawner = nullptr; - info.spawnerID = m_Parent->GetObjectID(); - info.spawnerNodeID = 0; - - Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr); - Game::entityManager->ConstructEntity(newEntity); +void PhantomPhysicsComponent::SpawnVertices() const { + if (!m_dpEntity) { + LOG("No dpEntity to spawn vertices for %llu:%i", m_Parent->GetObjectID(), m_Parent->GetLOT()); + return; } + PhysicsComponent::SpawnVertices(m_dpEntity); } void PhantomPhysicsComponent::SetDirectionalMultiplier(float mul) { diff --git a/dGame/dComponents/PhantomPhysicsComponent.h b/dGame/dComponents/PhantomPhysicsComponent.h index 1aae9527..89cfb857 100644 --- a/dGame/dComponents/PhantomPhysicsComponent.h +++ b/dGame/dComponents/PhantomPhysicsComponent.h @@ -18,6 +18,7 @@ class LDFBaseData; class Entity; class dpEntity; enum class ePhysicsEffectType : uint32_t ; +enum class eReplicaComponentType : uint32_t; /** * Allows the creation of phantom physics for an entity: a physics object that is generally invisible but can be @@ -34,11 +35,6 @@ public: void Update(float deltaTime) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; - /** - * Creates the physics shape for this entity based on LDF data - */ - void CreatePhysics(); - /** * Sets the direction this physics object is pointed at * @param pos the direction to set @@ -109,7 +105,7 @@ public: /** * Spawns an object at each of the vertices for debugging purposes */ - void SpawnVertices(); + void SpawnVertices() const; /** * Legacy stuff no clue what this does @@ -166,11 +162,6 @@ private: */ dpEntity* m_dpEntity; - /** - * Whether or not the physics object has been created yet - */ - bool m_HasCreatedPhysics = false; - /** * Whether or not this physics object represents an object that updates the respawn pos of an entity that crosses it */ diff --git a/dGame/dComponents/PhysicsComponent.cpp b/dGame/dComponents/PhysicsComponent.cpp index 3a84c4ce..4a250a6a 100644 --- a/dGame/dComponents/PhysicsComponent.cpp +++ b/dGame/dComponents/PhysicsComponent.cpp @@ -1,5 +1,19 @@ #include "PhysicsComponent.h" +#include "eReplicaComponentType.h" +#include "NiPoint3.h" +#include "NiQuaternion.h" + +#include "CDComponentsRegistryTable.h" +#include "CDPhysicsComponentTable.h" + +#include "dpEntity.h" +#include "dpWorld.h" +#include "dpShapeBox.h" +#include "dpShapeSphere.h" + +#include "EntityInfo.h" + PhysicsComponent::PhysicsComponent(Entity* parent) : Component(parent) { m_Position = NiPoint3Constant::ZERO; m_Rotation = NiQuaternionConstant::IDENTITY; @@ -19,3 +33,190 @@ void PhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitia if (!bIsInitialUpdate) m_DirtyPosition = false; } } + +dpEntity* PhysicsComponent::CreatePhysicsEntity(eReplicaComponentType type) { + CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable(); + auto componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), type); + + CDPhysicsComponentTable* physComp = CDClientManager::GetTable(); + + if (physComp == nullptr) return nullptr; + + auto* info = physComp->GetByID(componentID); + if (info == nullptr || info->physicsAsset == "" || info->physicsAsset == "NO_PHYSICS") return nullptr; + + dpEntity* toReturn; + if (info->physicsAsset == "miscellaneous\\misc_phys_10x1x5.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 10.0f, 5.0f, 1.0f); + } else if (info->physicsAsset == "miscellaneous\\misc_phys_640x640.hkx") { + // TODO Fix physics simulation to do simulation at high velocities due to bullet through paper problem... + toReturn = new dpEntity(m_Parent->GetObjectID(), 1638.4f, 13.521004f * 2.0f, 1638.4f); + + // Move this down by 13.521004 units so it is still effectively at the same height as before + m_Position = m_Position - NiPoint3Constant::UNIT_Y * 13.521004f; + } else if (info->physicsAsset == "env\\trigger_wall_tall.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 10.0f, 25.0f, 1.0f); + } else if (info->physicsAsset == "env\\env_gen_placeholderphysics.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 20.0f, 20.0f, 20.0f); + } else if (info->physicsAsset == "env\\POI_trigger_wall.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 1.0f, 12.5f, 20.0f); // Not sure what the real size is + } else if (info->physicsAsset == "env\\NG_NinjaGo\\env_ng_gen_gate_chamber_puzzle_ceiling_tile_falling_phantom.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 18.0f, 5.0f, 15.0f); + m_Position += m_Rotation.GetForwardVector() * 7.5f; + } else if (info->physicsAsset == "env\\NG_NinjaGo\\ng_flamejet_brick_phantom.HKX") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 1.0f, 1.0f, 12.0f); + m_Position += m_Rotation.GetForwardVector() * 6.0f; + } else if (info->physicsAsset == "env\\Ring_Trigger.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 6.0f, 6.0f, 6.0f); + } else if (info->physicsAsset == "env\\vfx_propertyImaginationBall.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 4.5f); + } else if (info->physicsAsset == "env\\env_won_fv_gas-blocking-volume.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 390.496826f, 111.467964f, 600.821534f, true); + m_Position.y -= (111.467964f * m_Parent->GetDefaultScale()) / 2; + } else { + // LOG_DEBUG("This one is supposed to have %s", info->physicsAsset.c_str()); + + //add fallback cube: + toReturn = new dpEntity(m_Parent->GetObjectID(), 2.0f, 2.0f, 2.0f); + } + return toReturn; +} + +dpEntity* PhysicsComponent::CreatePhysicsLnv(const float scale, const eReplicaComponentType type) const { + int pcShapeType = -1; + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + float width = 0.0f; //aka "radius" + float height = 0.0f; + dpEntity* toReturn = nullptr; + + if (m_Parent->HasVar(u"primitiveModelType")) { + pcShapeType = m_Parent->GetVar(u"primitiveModelType"); + x = m_Parent->GetVar(u"primitiveModelValueX"); + y = m_Parent->GetVar(u"primitiveModelValueY"); + z = m_Parent->GetVar(u"primitiveModelValueZ"); + } else { + CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable(); + auto componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), type); + + CDPhysicsComponentTable* physComp = CDClientManager::GetTable(); + + if (physComp == nullptr) return nullptr; + + auto info = physComp->GetByID(componentID); + + if (info == nullptr) return nullptr; + + pcShapeType = info->pcShapeType; + width = info->playerRadius; + height = info->playerHeight; + } + + switch (pcShapeType) { + case 0: { // HKX type + break; + } + case 1: { //Make a new box shape + NiPoint3 boxSize(x, y, z); + if (x == 0.0f) { + //LU has some weird values, so I think it's best to scale them down a bit + if (height < 0.5f) height = 2.0f; + if (width < 0.5f) width = 2.0f; + + //Scale them: + width = width * scale; + height = height * scale; + + boxSize = NiPoint3(width, height, width); + } + + toReturn = new dpEntity(m_Parent->GetObjectID(), boxSize); + + toReturn->SetPosition({ m_Position.x, m_Position.y - (height / 2), m_Position.z }); + break; + } + case 2: { //Make a new cylinder shape + break; + } + case 3: { //Make a new sphere shape + auto [x, y, z] = m_Position; + toReturn = new dpEntity(m_Parent->GetObjectID(), width); + toReturn->SetPosition({ x, y, z }); + break; + } + case 4: { //Make a new capsule shape + break; + } + } + + if (toReturn) dpWorld::AddEntity(toReturn); + + return toReturn; +} + +void PhysicsComponent::SpawnVertices(dpEntity* entity) const { + if (!entity) return; + + LOG("Spawning vertices for %llu", m_Parent->GetObjectID()); + EntityInfo info; + info.lot = 33; + info.spawner = nullptr; + info.spawnerID = m_Parent->GetObjectID(); + info.spawnerNodeID = 0; + + // These don't use overloaded methods as dPhysics does not link with dGame at the moment. + auto box = dynamic_cast(entity->GetShape()); + if (box) { + for (auto vert : box->GetVertices()) { + LOG("Vertex at %f, %f, %f", vert.x, vert.y, vert.z); + + info.pos = vert; + Entity* newEntity = Game::entityManager->CreateEntity(info); + Game::entityManager->ConstructEntity(newEntity); + } + } + auto sphere = dynamic_cast(entity->GetShape()); + if (sphere) { + auto [x, y, z] = entity->GetPosition(); // Use shapes position instead of the parent's position in case it's different + float plusX = x + sphere->GetRadius(); + float minusX = x - sphere->GetRadius(); + float plusY = y + sphere->GetRadius(); + float minusY = y - sphere->GetRadius(); + float plusZ = z + sphere->GetRadius(); + float minusZ = z - sphere->GetRadius(); + + auto radius = sphere->GetRadius(); + LOG("Radius: %f", radius); + LOG("Plus Vertices %f %f %f", plusX, plusY, plusZ); + LOG("Minus Vertices %f %f %f", minusX, minusY, minusZ); + + info.pos = NiPoint3{ x, plusY, z }; + Entity* newEntity = Game::entityManager->CreateEntity(info); + Game::entityManager->ConstructEntity(newEntity); + + info.pos = NiPoint3{ x, minusY, z }; + newEntity = Game::entityManager->CreateEntity(info); + Game::entityManager->ConstructEntity(newEntity); + + info.pos = NiPoint3{ plusX, y, z }; + newEntity = Game::entityManager->CreateEntity(info); + Game::entityManager->ConstructEntity(newEntity); + + info.pos = NiPoint3{ minusX, y, z }; + newEntity = Game::entityManager->CreateEntity(info); + Game::entityManager->ConstructEntity(newEntity); + + info.pos = NiPoint3{ x, y, plusZ }; + newEntity = Game::entityManager->CreateEntity(info); + Game::entityManager->ConstructEntity(newEntity); + + info.pos = NiPoint3{ x, y, minusZ }; + newEntity = Game::entityManager->CreateEntity(info); + Game::entityManager->ConstructEntity(newEntity); + + info.pos = NiPoint3{ x, y, z }; + newEntity = Game::entityManager->CreateEntity(info); + Game::entityManager->ConstructEntity(newEntity); + } +} diff --git a/dGame/dComponents/PhysicsComponent.h b/dGame/dComponents/PhysicsComponent.h index 71f52e54..4bf0828a 100644 --- a/dGame/dComponents/PhysicsComponent.h +++ b/dGame/dComponents/PhysicsComponent.h @@ -9,6 +9,10 @@ namespace Raknet { class BitStream; }; +enum class eReplicaComponentType : uint32_t; + +class dpEntity; + class PhysicsComponent : public Component { public: PhysicsComponent(Entity* parent); @@ -22,6 +26,12 @@ public: const NiQuaternion& GetRotation() const { return m_Rotation; } virtual void SetRotation(const NiQuaternion& rot) { if (m_Rotation == rot) return; m_Rotation = rot; m_DirtyPosition = true; } protected: + dpEntity* CreatePhysicsEntity(eReplicaComponentType type); + + dpEntity* CreatePhysicsLnv(const float scale, const eReplicaComponentType type) const; + + void SpawnVertices(dpEntity* entity) const; + NiPoint3 m_Position; NiQuaternion m_Rotation; diff --git a/dGame/dComponents/PossessableComponent.cpp b/dGame/dComponents/PossessableComponent.cpp index ae5b05b3..e0e8c8ad 100644 --- a/dGame/dComponents/PossessableComponent.cpp +++ b/dGame/dComponents/PossessableComponent.cpp @@ -18,8 +18,8 @@ PossessableComponent::PossessableComponent(Entity* parent, uint32_t componentId) // Should a result not exist for this default to attached visible if (!result.eof()) { - m_PossessionType = static_cast(result.getIntField(0, 1)); // Default to Attached Visible - m_DepossessOnHit = static_cast(result.getIntField(1, 0)); + m_PossessionType = static_cast(result.getIntField("possessionType", 1)); // Default to Attached Visible + m_DepossessOnHit = static_cast(result.getIntField("depossessOnHit", 0)); } else { m_PossessionType = ePossessionType::ATTACHED_VISIBLE; m_DepossessOnHit = false; diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index 95a6f3e0..d7be22ac 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -21,9 +21,11 @@ #include "eObjectBits.h" #include "CharacterComponent.h" #include "PlayerManager.h" +#include "ModelComponent.h" #include #include "CppScripts.h" +#include PropertyManagementComponent* PropertyManagementComponent::instance = nullptr; @@ -49,11 +51,11 @@ PropertyManagementComponent::PropertyManagementComponent(Entity* parent) : Compo auto result = query.execQuery(); - if (result.eof() || result.fieldIsNull(0)) { + if (result.eof() || result.fieldIsNull("id")) { return; } - templateId = result.getIntField(0); + templateId = result.getIntField("id"); auto propertyInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId); @@ -105,7 +107,7 @@ std::vector PropertyManagementComponent::GetPaths() const { std::vector points; - std::istringstream stream(result.getStringField(0)); + std::istringstream stream(result.getStringField("path")); std::string token; while (std::getline(stream, token, ' ')) { @@ -593,6 +595,20 @@ void PropertyManagementComponent::Load() { settings.push_back(new LDFData(u"componentWhitelist", 1)); } + std::ostringstream userModelBehavior; + bool firstAdded = false; + for (auto behavior : databaseModel.behaviors) { + if (behavior < 0) { + LOG("Invalid behavior ID: %d, removing behavior reference from model", behavior); + behavior = 0; + } + if (firstAdded) userModelBehavior << ","; + userModelBehavior << behavior; + firstAdded = true; + } + + settings.push_back(new LDFData(u"userModelBehaviors", userModelBehavior.str())); + node->config = settings; const auto spawnerId = Game::zoneManager->MakeSpawner(info); @@ -610,6 +626,12 @@ void PropertyManagementComponent::Save() { return; } + const auto* const owner = GetOwner(); + if (!owner) return; + + const auto* const character = owner->GetCharacter(); + if (!character) return; + auto present = Database::Get()->GetPropertyModels(propertyId); std::vector modelIds; @@ -624,6 +646,20 @@ void PropertyManagementComponent::Save() { if (entity == nullptr) { continue; } + auto* modelComponent = entity->GetComponent(); + if (!modelComponent) continue; + const auto modelBehaviors = modelComponent->GetBehaviorsForSave(); + + // save the behaviors of the model + for (const auto& [behaviorId, behaviorStr] : modelBehaviors) { + if (behaviorStr.empty() || behaviorId == -1 || behaviorId == 0) continue; + IBehaviors::Info info { + .behaviorId = behaviorId, + .characterId = character->GetID(), + .behaviorInfo = behaviorStr + }; + Database::Get()->AddBehavior(info); + } const auto position = entity->GetPosition(); const auto rotation = entity->GetRotation(); @@ -635,10 +671,13 @@ void PropertyManagementComponent::Save() { model.position = position; model.rotation = rotation; model.ugcId = 0; + for (auto i = 0; i < model.behaviors.size(); i++) { + model.behaviors[i] = modelBehaviors[i].first; + } Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_" + std::to_string(model.lot) + "_name"); } else { - Database::Get()->UpdateModelPositionRotation(id, position, rotation); + Database::Get()->UpdateModel(id, position, rotation, modelBehaviors); } } diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index d7e01f94..ab6d5d17 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -25,6 +25,7 @@ #include "LeaderboardManager.h" #include "dZoneManager.h" #include "CDActivitiesTable.h" +#include "eStateChangeType.h" #include #ifndef M_PI @@ -44,7 +45,6 @@ RacingControlComponent::RacingControlComponent(Entity* parent) m_LoadedPlayers = 0; m_LoadTimer = 0; m_Finished = 0; - m_StartTime = 0; m_EmptyTimer = 0; m_SoloRacing = Game::config->GetValue("solo_racing") == "1"; @@ -77,6 +77,9 @@ void RacingControlComponent::OnPlayerLoaded(Entity* player) { m_LoadedPlayers++; + // not live accurate to stun the player but prevents them from using skills during the race that are not meant to be used. + GameMessages::SendSetStunned(player->GetObjectID(), eStateChangeType::PUSH, player->GetSystemAddress(), LWOOBJID_EMPTY, true, true, true, true, true, true, true, true, true); + LOG("Loading player %i", m_LoadedPlayers); m_LobbyPlayers.push_back(player->GetObjectID()); @@ -394,25 +397,6 @@ void RacingControlComponent::HandleMessageBoxResponse(Entity* player, int32_t bu GameMessages::SendNotifyRacingClient( m_Parent->GetObjectID(), 2, 0, LWOOBJID_EMPTY, u"", player->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); - - auto* missionComponent = player->GetComponent(); - - if (missionComponent == nullptr) return; - - missionComponent->Progress(eMissionTaskType::RACING, 0, static_cast(eRacingTaskParam::COMPETED_IN_RACE)); // Progress task for competing in a race - missionComponent->Progress(eMissionTaskType::RACING, data->smashedTimes, static_cast(eRacingTaskParam::SAFE_DRIVER)); // Finish a race without being smashed. - - // If solo racing is enabled OR if there are 3 players in the race, progress placement tasks. - if (m_SoloRacing || m_LoadedPlayers > 2) { - missionComponent->Progress(eMissionTaskType::RACING, data->finished, static_cast(eRacingTaskParam::FINISH_WITH_PLACEMENT)); // Finish in 1st place on a race - if (data->finished == 1) { - missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast(eRacingTaskParam::FIRST_PLACE_MULTIPLE_TRACKS)); // Finish in 1st place on multiple tracks. - missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast(eRacingTaskParam::WIN_RACE_IN_WORLD)); // Finished first place in specific world. - } - if (data->finished == m_LoadedPlayers) { - missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast(eRacingTaskParam::LAST_PLACE_FINISH)); // Finished first place in specific world. - } - } } else if ((id == "ACT_RACE_EXIT_THE_RACE?" || id == "Exit") && button == m_ActivityExitConfirm) { auto* vehicle = Game::entityManager->GetEntity(data->vehicleID); @@ -442,9 +426,9 @@ void RacingControlComponent::Serialize(RakNet::BitStream& outBitStream, bool bIs outBitStream.Write(player.playerID); outBitStream.Write(player.data[0]); - if (player.finished != 0) outBitStream.Write(player.raceTime); + if (player.finished != 0) outBitStream.Write(player.raceTime.count() / 1000.0f); else outBitStream.Write(player.data[1]); - if (player.finished != 0) outBitStream.Write(player.bestLapTime); + if (player.finished != 0) outBitStream.Write(player.bestLapTime.count() / 1000.0f); else outBitStream.Write(player.data[2]); if (player.finished == 1) outBitStream.Write(1.0f); else outBitStream.Write(player.data[3]); @@ -505,8 +489,8 @@ void RacingControlComponent::Serialize(RakNet::BitStream& outBitStream, bool bIs if (player.finished == 0) continue; outBitStream.Write1(); // Has more data outBitStream.Write(player.playerID); - outBitStream.Write(player.bestLapTime); - outBitStream.Write(player.raceTime); + outBitStream.Write(player.bestLapTime.count() / 1000.0f); + outBitStream.Write(player.raceTime.count() / 1000.0f); } outBitStream.Write0(); // No more data @@ -736,7 +720,7 @@ void RacingControlComponent::Update(float deltaTime) { Game::entityManager->SerializeEntity(m_Parent); - m_StartTime = std::time(nullptr); + m_StartTime = std::chrono::high_resolution_clock::now(); } m_StartTimer += deltaTime; @@ -817,52 +801,68 @@ void RacingControlComponent::Update(float deltaTime) { // Some offset up to make they don't fall through the terrain on a // respawn, seems to fix itself to the track anyhow - player.respawnPosition = position + NiPoint3Constant::UNIT_Y * 5; - player.respawnRotation = vehicle->GetRotation(); + if (waypoint.racing.isResetNode) { + player.respawnPosition = position + NiPoint3Constant::UNIT_Y * 5; + player.respawnRotation = vehicle->GetRotation(); + } player.respawnIndex = respawnIndex; // Reached the start point, lapped if (respawnIndex == 0) { - time_t lapTime = std::time(nullptr) - (player.lap == 0 ? m_StartTime : player.lapTime); + const auto now = std::chrono::high_resolution_clock::now(); + const auto lapTime = std::chrono::duration_cast(now - (player.lap == 0 ? m_StartTime : player.lapTime)); // Cheating check - if (lapTime < 40) { + if (lapTime.count() < 40000) { continue; } - player.lap++; + player.lapTime = now; - player.lapTime = std::time(nullptr); - - if (player.bestLapTime == 0 || player.bestLapTime > lapTime) { + if (player.bestLapTime > lapTime || player.lap == 0) { player.bestLapTime = lapTime; LOG("Best lap time (%llu)", lapTime); } + player.lap++; + auto* missionComponent = playerEntity->GetComponent(); if (missionComponent != nullptr) { // Progress lap time tasks - missionComponent->Progress(eMissionTaskType::RACING, (lapTime) * 1000, static_cast(eRacingTaskParam::LAP_TIME)); + missionComponent->Progress(eMissionTaskType::RACING, lapTime.count(), static_cast(eRacingTaskParam::LAP_TIME)); if (player.lap == 3) { m_Finished++; player.finished = m_Finished; - const auto raceTime = - (std::time(nullptr) - m_StartTime); + const auto raceTime = std::chrono::duration_cast(now - m_StartTime); player.raceTime = raceTime; - LOG("Completed time %llu, %llu", - raceTime, raceTime * 1000); + LOG("Completed time %llums %fs", raceTime.count(), raceTime.count() / 1000.0f); - LeaderboardManager::SaveScore(playerEntity->GetObjectID(), m_ActivityID, static_cast(player.raceTime), static_cast(player.bestLapTime), static_cast(player.finished == 1)); + LeaderboardManager::SaveScore(playerEntity->GetObjectID(), m_ActivityID, static_cast(player.raceTime.count()) / 1000, static_cast(player.bestLapTime.count()) / 1000, static_cast(player.finished == 1)); // Entire race time - missionComponent->Progress(eMissionTaskType::RACING, (raceTime) * 1000, static_cast(eRacingTaskParam::TOTAL_TRACK_TIME)); + missionComponent->Progress(eMissionTaskType::RACING, player.raceTime.count(), static_cast(eRacingTaskParam::TOTAL_TRACK_TIME)); + + missionComponent->Progress(eMissionTaskType::RACING, 0, static_cast(eRacingTaskParam::COMPETED_IN_RACE)); // Progress task for competing in a race + missionComponent->Progress(eMissionTaskType::RACING, player.smashedTimes, static_cast(eRacingTaskParam::SAFE_DRIVER)); // Finish a race without being smashed. + + // If solo racing is enabled OR if there are 3 players in the race, progress placement tasks. + if (m_SoloRacing || m_RacingPlayers.size() > 2) { + missionComponent->Progress(eMissionTaskType::RACING, player.finished, static_cast(eRacingTaskParam::FINISH_WITH_PLACEMENT)); // Finish in 1st place on a race + if (player.finished == 1) { + missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast(eRacingTaskParam::FIRST_PLACE_MULTIPLE_TRACKS)); // Finish in 1st place on multiple tracks. + missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast(eRacingTaskParam::WIN_RACE_IN_WORLD)); // Finished first place in specific world. + } + if (player.finished == m_RacingPlayers.size()) { + missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast(eRacingTaskParam::LAST_PLACE_FINISH)); // Finished first place in specific world. + } + } auto* characterComponent = playerEntity->GetComponent(); if (characterComponent != nullptr) { @@ -871,8 +871,8 @@ void RacingControlComponent::Update(float deltaTime) { } } - LOG("Lapped (%i) in (%llu)", player.lap, - lapTime); + LOG("Lapped (%i) in (%llums %fs)", player.lap, + lapTime.count(), lapTime.count() / 1000.0f); } LOG("Reached point (%i)/(%i)", player.respawnIndex, diff --git a/dGame/dComponents/RacingControlComponent.h b/dGame/dComponents/RacingControlComponent.h index 790459e3..43c7e158 100644 --- a/dGame/dComponents/RacingControlComponent.h +++ b/dGame/dComponents/RacingControlComponent.h @@ -8,6 +8,7 @@ #include "Entity.h" #include "Component.h" #include "eReplicaComponentType.h" +#include /** * Information for each player in the race @@ -72,12 +73,12 @@ struct RacingPlayerInfo { /** * The fastest lap time of the player */ - time_t bestLapTime = 0; + std::chrono::milliseconds bestLapTime; /** * The current lap time of the player */ - time_t lapTime = 0; + std::chrono::high_resolution_clock::time_point lapTime; /** * The number of times this player smashed their car @@ -97,7 +98,7 @@ struct RacingPlayerInfo { /** * Unused */ - time_t raceTime = 0; + std::chrono::milliseconds raceTime; }; /** @@ -231,7 +232,7 @@ private: /** * The time the race was started */ - time_t m_StartTime; + std::chrono::high_resolution_clock::time_point m_StartTime; /** * Timer for tracking how long a player was alone in this race diff --git a/dGame/dComponents/RenderComponent.cpp b/dGame/dComponents/RenderComponent.cpp index 2067ef2a..cc5974a1 100644 --- a/dGame/dComponents/RenderComponent.cpp +++ b/dGame/dComponents/RenderComponent.cpp @@ -117,7 +117,7 @@ void RenderComponent::PlayEffect(const int32_t effectId, const std::u16string& e auto result = query.execQuery(); - if (result.eof() || result.fieldIsNull(0)) { + if (result.eof() || result.fieldIsNull("animation_length")) { result.finalize(); m_DurationCache[effectId] = 0; @@ -127,7 +127,7 @@ void RenderComponent::PlayEffect(const int32_t effectId, const std::u16string& e return; } - effect.time = static_cast(result.getFloatField(0)); + effect.time = static_cast(result.getFloatField("animation_length")); result.finalize(); diff --git a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp index 30faa688..df81aab3 100644 --- a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp +++ b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp @@ -1,16 +1,57 @@ -/* - * Darkflame Universe - * Copyright 2023 - */ +// Darkflame Universe +// Copyright 2024 #include "RigidbodyPhantomPhysicsComponent.h" #include "Entity.h" +#include "dpEntity.h" +#include "CDComponentsRegistryTable.h" +#include "CDPhysicsComponentTable.h" +#include "dpWorld.h" +#include "dpShapeBox.h" +#include "dpShapeSphere.h" +#include"EntityInfo.h" + RigidbodyPhantomPhysicsComponent::RigidbodyPhantomPhysicsComponent(Entity* parent) : PhysicsComponent(parent) { m_Position = m_Parent->GetDefaultPosition(); m_Rotation = m_Parent->GetDefaultRotation(); + m_Scale = m_Parent->GetDefaultScale(); + + if (m_Parent->GetVar(u"create_physics")) { + m_dpEntity = CreatePhysicsLnv(m_Scale, ComponentType); + if (!m_dpEntity) { + m_dpEntity = CreatePhysicsEntity(ComponentType); + if (!m_dpEntity) return; + m_dpEntity->SetScale(m_Scale); + m_dpEntity->SetRotation(m_Rotation); + m_dpEntity->SetPosition(m_Position); + dpWorld::AddEntity(m_dpEntity); + } + } } void RigidbodyPhantomPhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { PhysicsComponent::Serialize(outBitStream, bIsInitialUpdate); } + +void RigidbodyPhantomPhysicsComponent::Update(const float deltaTime) { + if (!m_dpEntity) return; + + //Process enter events + for (const auto id : m_dpEntity->GetNewObjects()) { + m_Parent->OnCollisionPhantom(id); + } + + //Process exit events + for (const auto id : m_dpEntity->GetRemovedObjects()) { + m_Parent->OnCollisionLeavePhantom(id); + } +} + +void RigidbodyPhantomPhysicsComponent::SpawnVertices() const { + if (!m_dpEntity) { + LOG("No dpEntity to spawn vertices for %llu:%i", m_Parent->GetObjectID(), m_Parent->GetLOT()); + return; + } + PhysicsComponent::SpawnVertices(m_dpEntity); +} diff --git a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h index 09820f8e..11595ec0 100644 --- a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h +++ b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h @@ -1,10 +1,8 @@ -/* - * Darkflame Universe - * Copyright 2023 - */ +// Darkflame Universe +// Copyright 2024 -#ifndef __RIGIDBODYPHANTOMPHYSICS_H__ -#define __RIGIDBODYPHANTOMPHYSICS_H__ +#ifndef RIGIDBODYPHANTOMPHYSICS_H +#define RIGIDBODYPHANTOMPHYSICS_H #include "BitStream.h" #include "dCommonVars.h" @@ -13,6 +11,8 @@ #include "PhysicsComponent.h" #include "eReplicaComponentType.h" +class dpEntity; + /** * Component that handles rigid bodies that can be interacted with, mostly client-side rendered. An example is the * bananas that fall from trees in GF. @@ -23,7 +23,15 @@ public: RigidbodyPhantomPhysicsComponent(Entity* parent); + void Update(const float deltaTime) override; + void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; + + void SpawnVertices() const; +private: + float m_Scale{}; + + dpEntity* m_dpEntity{}; }; -#endif // __RIGIDBODYPHANTOMPHYSICS_H__ +#endif // RIGIDBODYPHANTOMPHYSICS_H diff --git a/dGame/dComponents/RocketLaunchpadControlComponent.cpp b/dGame/dComponents/RocketLaunchpadControlComponent.cpp index 2bc4deec..c0c90581 100644 --- a/dGame/dComponents/RocketLaunchpadControlComponent.cpp +++ b/dGame/dComponents/RocketLaunchpadControlComponent.cpp @@ -27,12 +27,12 @@ RocketLaunchpadControlComponent::RocketLaunchpadControlComponent(Entity* parent, auto result = query.execQuery(); - if (!result.eof() && !result.fieldIsNull(0)) { - m_TargetZone = result.getIntField(0); - m_DefaultZone = result.getIntField(1); - m_TargetScene = result.getStringField(2); - m_AltPrecondition = new PreconditionExpression(result.getStringField(3)); - m_AltLandingScene = result.getStringField(4); + if (!result.eof() && !result.fieldIsNull("targetZone")) { + m_TargetZone = result.getIntField("targetZone"); + m_DefaultZone = result.getIntField("defaultZoneID"); + m_TargetScene = result.getStringField("targetScene"); + m_AltPrecondition = new PreconditionExpression(result.getStringField("altLandingPrecondition")); + m_AltLandingScene = result.getStringField("altLandingSpawnPointName"); } result.finalize(); diff --git a/dGame/dComponents/SkillComponent.cpp b/dGame/dComponents/SkillComponent.cpp index 8460032c..58b13dc5 100644 --- a/dGame/dComponents/SkillComponent.cpp +++ b/dGame/dComponents/SkillComponent.cpp @@ -38,7 +38,7 @@ bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t s context->skillID = skillID; - this->m_managedBehaviors.insert_or_assign(skillUid, context); + this->m_managedBehaviors.insert({ skillUid, context }); auto* behavior = Behavior::CreateBehavior(behaviorId); @@ -52,17 +52,24 @@ bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t s } void SkillComponent::SyncPlayerSkill(const uint32_t skillUid, const uint32_t syncId, RakNet::BitStream& bitStream) { - const auto index = this->m_managedBehaviors.find(skillUid); + const auto index = this->m_managedBehaviors.equal_range(skillUid); - if (index == this->m_managedBehaviors.end()) { + if (index.first == this->m_managedBehaviors.end()) { LOG("Failed to find skill with uid (%i)!", skillUid, syncId); return; } - auto* context = index->second; + bool foundSyncId = false; + for (auto it = index.first; it != index.second && !foundSyncId; ++it) { + const auto& context = it->second; - context->SyncBehavior(syncId, bitStream); + foundSyncId = context->SyncBehavior(syncId, bitStream); + } + + if (!foundSyncId) { + LOG("Failed to find sync id (%i) for skill with uid (%i)!", syncId, skillUid); + } } @@ -99,7 +106,7 @@ void SkillComponent::SyncPlayerProjectile(const LWOOBJID projectileId, RakNet::B return; } - const auto behavior_id = static_cast(result.getIntField(0)); + const auto behavior_id = static_cast(result.getIntField("behaviorID")); result.finalize(); @@ -138,7 +145,7 @@ void SkillComponent::Update(const float deltaTime) { for (const auto& pair : this->m_managedBehaviors) pair.second->UpdatePlayerSyncs(deltaTime); } - std::map keep{}; + std::multimap keep{}; for (const auto& pair : this->m_managedBehaviors) { auto* context = pair.second; @@ -176,7 +183,7 @@ void SkillComponent::Update(const float deltaTime) { } } - keep.insert_or_assign(pair.first, context); + keep.insert({ pair.first, context }); } this->m_managedBehaviors = keep; @@ -285,7 +292,7 @@ SkillExecutionResult SkillComponent::CalculateBehavior( return { false, 0 }; } - this->m_managedBehaviors.insert_or_assign(context->skillUId, context); + this->m_managedBehaviors.insert({ context->skillUId, context }); if (!clientInitalized) { // Echo start skill @@ -425,7 +432,7 @@ void SkillComponent::SyncProjectileCalculation(const ProjectileSyncEntry& entry) return; } - const auto behaviorId = static_cast(result.getIntField(0)); + const auto behaviorId = static_cast(result.getIntField("behaviorID")); result.finalize(); diff --git a/dGame/dComponents/SkillComponent.h b/dGame/dComponents/SkillComponent.h index 24d92148..f74b4411 100644 --- a/dGame/dComponents/SkillComponent.h +++ b/dGame/dComponents/SkillComponent.h @@ -188,7 +188,7 @@ private: /** * All of the active skills mapped by their unique ID. */ - std::map m_managedBehaviors; + std::multimap m_managedBehaviors; /** * All active projectiles. diff --git a/dGame/dComponents/VendorComponent.cpp b/dGame/dComponents/VendorComponent.cpp index abe11ea5..b9286d25 100644 --- a/dGame/dComponents/VendorComponent.cpp +++ b/dGame/dComponents/VendorComponent.cpp @@ -76,8 +76,8 @@ void VendorComponent::RefreshInventory(bool isCreation) { if (vendorItems.empty()) break; auto randomItemIndex = GeneralUtils::GenerateRandomNumber(0, vendorItems.size() - 1); const auto& randomItem = vendorItems.at(randomItemIndex); - vendorItems.erase(vendorItems.begin() + randomItemIndex); if (SetupItem(randomItem.itemid)) m_Inventory.push_back(SoldItem(randomItem.itemid, randomItem.sortPriority)); + vendorItems.erase(vendorItems.begin() + randomItemIndex); } } } diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index d2432e36..76fabce2 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -685,6 +685,13 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System case eGameMessageType::REQUEST_VENDOR_STATUS_UPDATE: GameMessages::SendVendorStatusUpdate(entity, sysAddr, true); break; + case eGameMessageType::UPDATE_INVENTORY_GROUP: + GameMessages::HandleUpdateInventoryGroup(inStream, entity, sysAddr); + break; + case eGameMessageType::UPDATE_INVENTORY_GROUP_CONTENTS: + GameMessages::HandleUpdateInventoryGroupContents(inStream, entity, sysAddr); + break; + default: LOG_DEBUG("Received Unknown GM with ID: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data()); break; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 74946179..2fba411b 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -99,9 +99,11 @@ #include "ActivityManager.h" #include "PlayerManager.h" #include "eVendorTransactionResult.h" +#include "eReponseMoveItemBetweenInventoryTypeCode.h" #include "CDComponentsRegistryTable.h" #include "CDObjectsTable.h" +#include "eItemType.h" void GameMessages::SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender) { CBITSTREAM; @@ -369,8 +371,8 @@ void GameMessages::SendPlatformResync(Entity* entity, const SystemAddress& sysAd const auto lot = entity->GetLOT(); - if (lot == 12341 || lot == 5027 || lot == 5028 || lot == 14335 || lot == 14447 || lot == 14449) { - iDesiredWaypointIndex = 0; + if (lot == 12341 || lot == 5027 || lot == 5028 || lot == 14335 || lot == 14447 || lot == 14449 || lot == 11306 || lot == 11308) { + iDesiredWaypointIndex = (lot == 11306 || lot == 11308) ? 1 : 0; iIndex = 0; nextIndex = 0; bStopAtDesiredWaypoint = true; @@ -412,7 +414,8 @@ void GameMessages::SendPlatformResync(Entity* entity, const SystemAddress& sysAd bitStream.Write(qUnexpectedRotation.w); } - SEND_PACKET_BROADCAST; + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; } void GameMessages::SendRestoreToPostLoadStats(Entity* entity, const SystemAddress& sysAddr) { @@ -4563,16 +4566,31 @@ void GameMessages::HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream& if (inStream.ReadBit()) inStream.Read(itemLOT); if (invTypeDst == invTypeSrc) { + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC); return; } auto* inventoryComponent = entity->GetComponent(); - if (inventoryComponent != nullptr) { + if (inventoryComponent) { if (itemID != LWOOBJID_EMPTY) { auto* item = inventoryComponent->FindItemById(itemID); - if (!item) return; + if (!item) { + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_ITEM_NOT_FOUND); + return; + } + + if (item->GetLot() == 6086) { // Thinking hat + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_CANT_MOVE_THINKING_HAT); + return; + } + + auto* destInv = inventoryComponent->GetInventory(invTypeDst); + if (destInv && destInv->GetEmptySlots() == 0) { + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_INV_FULL); + return; + } // Despawn the pet if we are moving that pet to the vault. auto* petComponent = PetComponent::GetActivePet(entity->GetObjectID()); @@ -4581,10 +4599,32 @@ void GameMessages::HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream& } inventoryComponent->MoveItemToInventory(item, invTypeDst, iStackCount, showFlyingLoot, false, false, destSlot); + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::SUCCESS); } + } else { + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC); } } +void GameMessages::SendResponseMoveItemBetweenInventoryTypes(LWOOBJID objectId, const SystemAddress& sysAddr, eInventoryType inventoryTypeDestination, eInventoryType inventoryTypeSource, eReponseMoveItemBetweenInventoryTypeCode response) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(eGameMessageType::RESPONSE_MOVE_ITEM_BETWEEN_INVENTORY_TYPES); + + bitStream.Write(inventoryTypeDestination != eInventoryType::ITEMS); + if (inventoryTypeDestination != eInventoryType::ITEMS) bitStream.Write(inventoryTypeDestination); + + bitStream.Write(inventoryTypeSource != eInventoryType::ITEMS); + if (inventoryTypeSource != eInventoryType::ITEMS) bitStream.Write(inventoryTypeSource); + + bitStream.Write(response != eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC); + if (response != eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC) bitStream.Write(response); + + SEND_PACKET; +} + void GameMessages::SendShowActivityCountdown(LWOOBJID objectId, bool bPlayAdditionalSound, bool bPlayCountdownSound, std::u16string sndName, int32_t stateToPlaySoundOn, const SystemAddress& sysAddr) { CBITSTREAM; @@ -5351,17 +5391,18 @@ void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream& inStream, En iStackCount = std::min(item->GetCount(), iStackCount); if (bConfirmed) { - if (eInvType == eInventoryType::MODELS) { + const auto itemType = static_cast(item->GetInfo().itemType); + if (itemType == eItemType::MODEL || itemType == eItemType::LOOT_MODEL) { item->DisassembleModel(iStackCount); } - + auto lot = item->GetLot(); item->SetCount(item->GetCount() - iStackCount, true); Game::entityManager->SerializeEntity(entity); auto* missionComponent = entity->GetComponent(); if (missionComponent != nullptr) { - missionComponent->Progress(eMissionTaskType::GATHER, item->GetLot(), LWOOBJID_EMPTY, "", -iStackCount); + missionComponent->Progress(eMissionTaskType::GATHER, lot, LWOOBJID_EMPTY, "", -iStackCount); } } } @@ -6209,3 +6250,81 @@ void GameMessages::SendSlashCommandFeedbackText(Entity* entity, std::u16string t auto sysAddr = entity->GetSystemAddress(); SEND_PACKET; } + +void GameMessages::HandleUpdateInventoryGroup(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { + std::string action; + std::u16string groupName; + InventoryComponent::GroupUpdate groupUpdate; + bool locked{}; // All groups are locked by default + + uint32_t size{}; + if (!inStream.Read(size)) return; + action.resize(size); + if (!inStream.Read(action.data(), size)) return; + + if (!inStream.Read(size)) return; + groupUpdate.groupId.resize(size); + if (!inStream.Read(groupUpdate.groupId.data(), size)) return; + + if (!inStream.Read(size)) return; + groupName.resize(size); + if (!inStream.Read(reinterpret_cast(groupName.data()), size * 2)) return; + + if (!inStream.Read(groupUpdate.inventory)) return; + if (!inStream.Read(locked)) return; + + groupUpdate.groupName = GeneralUtils::UTF16ToWTF8(groupName); + + if (action == "ADD") groupUpdate.command = InventoryComponent::GroupUpdateCommand::ADD; + else if (action == "MODIFY") groupUpdate.command = InventoryComponent::GroupUpdateCommand::MODIFY; + else if (action == "REMOVE") groupUpdate.command = InventoryComponent::GroupUpdateCommand::REMOVE; + else { + LOG("Invalid action %s", action.c_str()); + return; + } + + auto* inventoryComponent = entity->GetComponent(); + if (inventoryComponent) inventoryComponent->UpdateGroup(groupUpdate); +} + +void GameMessages::HandleUpdateInventoryGroupContents(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { + std::string action; + InventoryComponent::GroupUpdate groupUpdate; + + uint32_t size{}; + if (!inStream.Read(size)) return; + action.resize(size); + if (!inStream.Read(action.data(), size)) return; + + if (action == "ADD") groupUpdate.command = InventoryComponent::GroupUpdateCommand::ADD_LOT; + else if (action == "REMOVE") groupUpdate.command = InventoryComponent::GroupUpdateCommand::REMOVE_LOT; + else { + LOG("Invalid action %s", action.c_str()); + return; + } + + if (!inStream.Read(size)) return; + groupUpdate.groupId.resize(size); + if (!inStream.Read(groupUpdate.groupId.data(), size)) return; + + if (!inStream.Read(groupUpdate.inventory)) return; + if (!inStream.Read(groupUpdate.lot)) return; + + auto* inventoryComponent = entity->GetComponent(); + if (inventoryComponent) inventoryComponent->UpdateGroup(groupUpdate); +} + +void GameMessages::SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(entity->GetObjectID()); + bitStream.Write(eGameMessageType::FORCE_CAMERA_TARGET_CYCLE); + bitStream.Write(bForceCycling); + bitStream.Write(cyclingMode != eCameraTargetCyclingMode::ALLOW_CYCLE_TEAMMATES); + if (cyclingMode != eCameraTargetCyclingMode::ALLOW_CYCLE_TEAMMATES) bitStream.Write(cyclingMode); + bitStream.Write(optionalTargetID); + + auto sysAddr = entity->GetSystemAddress(); + SEND_PACKET; +} diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index b842710e..090fcd4b 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -39,6 +39,12 @@ enum class eQuickBuildFailReason : uint32_t; enum class eQuickBuildState : uint32_t; enum class BehaviorSlot : int32_t; enum class eVendorTransactionResult : uint32_t; +enum class eReponseMoveItemBetweenInventoryTypeCode : int32_t; + +enum class eCameraTargetCyclingMode : int32_t { + ALLOW_CYCLE_TEAMMATES, + DISALLOW_CYCLING +}; namespace GameMessages { class PropertyDataMessage; @@ -589,6 +595,7 @@ namespace GameMessages { //NT: void HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); + void SendResponseMoveItemBetweenInventoryTypes(LWOOBJID objectId, const SystemAddress& sysAddr, eInventoryType inventoryTypeDestination, eInventoryType inventoryTypeSource, eReponseMoveItemBetweenInventoryTypeCode response); void SendShowActivityCountdown(LWOOBJID objectId, bool bPlayAdditionalSound, bool bPlayCountdownSound, std::u16string sndName, int32_t stateToPlaySoundOn, const SystemAddress& sysAddr); @@ -666,6 +673,10 @@ namespace GameMessages { void HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity); void SendSlashCommandFeedbackText(Entity* entity, std::u16string text); + + void HandleUpdateInventoryGroup(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); + void HandleUpdateInventoryGroupContents(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); + void SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID); }; #endif // GAMEMESSAGES_H diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index d3f15315..32603761 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -27,6 +27,23 @@ #include "CDComponentsRegistryTable.h" #include "CDPackageComponentTable.h" +namespace { + const std::map ExtraSettingAbbreviations = { + { "assemblyPartLOTs", "ma" }, + { "blueprintID", "b" }, + { "userModelID", "ui" }, + { "userModelName", "un" }, + { "userModelDesc", "ud" }, + { "userModelHasBhvr", "ub" }, + { "userModelBehaviors", "ubh" }, + { "userModelBehaviorSourceID", "ubs" }, + { "userModelPhysicsType", "up" }, + { "userModelMod", "um" }, + { "userModelOpt", "uo" }, + { "reforgedLOT", "rl" }, + }; +} + Item::Item(const LWOOBJID id, const LOT lot, Inventory* inventory, const uint32_t slot, const uint32_t count, const bool bound, const std::vector& config, const LWOOBJID parent, LWOOBJID subKey, eLootSourceType lootSourceType) { if (!Inventory::IsValidItem(lot)) { return; @@ -122,6 +139,10 @@ uint32_t Item::GetSlot() const { return slot; } +std::vector Item::GetConfig() const { + return config; +} + std::vector& Item::GetConfig() { return config; } @@ -251,7 +272,7 @@ bool Item::Consume() { auto skills = skillsTable->Query([this](const CDObjectSkills entry) { return entry.objectTemplate == static_cast(lot); - }); + }); auto success = false; @@ -405,18 +426,18 @@ void Item::DisassembleModel(uint32_t numToDismantle) { auto result = query.execQuery(); - if (result.eof() || result.fieldIsNull(0)) { + if (result.eof() || result.fieldIsNull("render_asset")) { return; } - std::string renderAsset = std::string(result.getStringField(0)); + std::string renderAsset = std::string(result.getStringField("render_asset")); // normalize path slashes for (auto& c : renderAsset) { if (c == '\\') c = '/'; } - std::string lxfmlFolderName = std::string(result.getStringField(1)); + std::string lxfmlFolderName = std::string(result.getStringField("LXFMLFolder")); if (!lxfmlFolderName.empty()) lxfmlFolderName.insert(0, "/"); std::vector renderAssetSplit = GeneralUtils::SplitString(renderAsset, '/'); @@ -515,3 +536,35 @@ Item::~Item() { config.clear(); } + +void Item::SaveConfigXml(tinyxml2::XMLElement& i) const { + tinyxml2::XMLElement* x = nullptr; + + for (const auto* config : this->config) { + const auto& key = GeneralUtils::UTF16ToWTF8(config->GetKey()); + const auto saveKey = ExtraSettingAbbreviations.find(key); + if (saveKey == ExtraSettingAbbreviations.end()) { + continue; + } + + if (!x) { + x = i.InsertNewChildElement("x"); + } + + const auto dataToSave = config->GetString(false); + x->SetAttribute(saveKey->second.c_str(), dataToSave.c_str()); + } +} + +void Item::LoadConfigXml(const tinyxml2::XMLElement& i) { + const auto* x = i.FirstChildElement("x"); + if (!x) return; + + for (const auto& pair : ExtraSettingAbbreviations) { + const auto* data = x->Attribute(pair.second.c_str()); + if (!data) continue; + + const auto value = pair.first + "=" + data; + config.push_back(LDFBaseData::DataFromString(value)); + } +} diff --git a/dGame/dInventory/Item.h b/dGame/dInventory/Item.h index 04d05d7c..72ff264c 100644 --- a/dGame/dInventory/Item.h +++ b/dGame/dInventory/Item.h @@ -9,6 +9,10 @@ #include "eInventoryType.h" #include "eLootSourceType.h" +namespace tinyxml2 { + class XMLElement; +}; + /** * An item that can be stored in an inventory and optionally consumed or equipped * TODO: ideally this should be a component @@ -116,6 +120,12 @@ public: */ std::vector& GetConfig(); + /** + * Returns current config info for this item, e.g. for rockets + * @return current config info for this item + */ + std::vector GetConfig() const; + /** * Returns the database info for this item * @return the database info for this item @@ -214,6 +224,10 @@ public: */ void RemoveFromInventory(); + void SaveConfigXml(tinyxml2::XMLElement& i) const; + + void LoadConfigXml(const tinyxml2::XMLElement& i); + private: /** * The object ID of this item diff --git a/dGame/dInventory/ItemSet.cpp b/dGame/dInventory/ItemSet.cpp index 1d086786..d8d320ed 100644 --- a/dGame/dInventory/ItemSet.cpp +++ b/dGame/dInventory/ItemSet.cpp @@ -8,10 +8,13 @@ #include "MissionComponent.h" #include "eMissionTaskType.h" #include +#include #include "CDSkillBehaviorTable.h" ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) { + using namespace std::string_view_literals; + this->m_ID = id; this->m_InventoryComponent = inventoryComponent; @@ -27,14 +30,16 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) { return; } - for (auto i = 0; i < 5; ++i) { - if (result.fieldIsNull(i)) { + constexpr std::array rowNames = { "skillSetWith2"sv, "skillSetWith3"sv, "skillSetWith4"sv, "skillSetWith5"sv, "skillSetWith6"sv }; + for (auto i = 0; i < rowNames.size(); ++i) { + const auto rowName = rowNames[i]; + if (result.fieldIsNull(rowName.data())) { continue; } auto skillQuery = CDClientDatabase::CreatePreppedStmt( "SELECT SkillID FROM ItemSetSkills WHERE SkillSetID = ?;"); - skillQuery.bind(1, result.getIntField(i)); + skillQuery.bind(1, result.getIntField(rowName.data())); auto skillResult = skillQuery.execQuery(); @@ -43,13 +48,13 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) { } while (!skillResult.eof()) { - if (skillResult.fieldIsNull(0)) { + if (skillResult.fieldIsNull("SkillID")) { skillResult.nextRow(); continue; } - const auto skillId = skillResult.getIntField(0); + const auto skillId = skillResult.getIntField("SkillID"); switch (i) { case 0: @@ -75,7 +80,7 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) { } } - std::string ids = result.getStringField(5); + std::string ids = result.getStringField("itemIDs"); ids.erase(std::remove_if(ids.begin(), ids.end(), ::isspace), ids.end()); diff --git a/dGame/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index c2ed2a42..2a841e39 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -454,6 +454,16 @@ void Mission::YieldRewards() { } } + // Even with no repeatable column, reputation is repeatable + if (info.reward_reputation > 0) { + missionComponent->Progress(eMissionTaskType::EARN_REPUTATION, 0, LWOOBJID_EMPTY, "", info.reward_reputation); + auto* const character = entity->GetComponent(); + if (character) { + character->SetReputation(character->GetReputation() + info.reward_reputation); + GameMessages::SendUpdateReputation(entity->GetObjectID(), character->GetReputation(), entity->GetSystemAddress()); + } + } + if (m_Completions > 0) { std::vector> items; @@ -532,15 +542,6 @@ void Mission::YieldRewards() { modelInventory->SetSize(modelInventory->GetSize() + info.reward_bankinventory); } - if (info.reward_reputation > 0) { - missionComponent->Progress(eMissionTaskType::EARN_REPUTATION, 0, 0L, "", info.reward_reputation); - auto character = entity->GetComponent(); - if (character) { - character->SetReputation(character->GetReputation() + info.reward_reputation); - GameMessages::SendUpdateReputation(entity->GetObjectID(), character->GetReputation(), entity->GetSystemAddress()); - } - } - if (info.reward_maxhealth > 0) { destroyableComponent->SetMaxHealth(destroyableComponent->GetMaxHealth() + static_cast(info.reward_maxhealth), true); } diff --git a/dGame/dMission/MissionTask.cpp b/dGame/dMission/MissionTask.cpp index 2fe9bc9f..aa2b9bca 100644 --- a/dGame/dMission/MissionTask.cpp +++ b/dGame/dMission/MissionTask.cpp @@ -186,7 +186,7 @@ void MissionTask::Progress(int32_t value, LWOOBJID associate, const std::string& if (count < 0) { if (mission->IsMission() && type == eMissionTaskType::GATHER && InAllTargets(value)) { - if (parameters.size() > 0 && (parameters[0] & 1) != 0) { + if (parameters.size() > 0 && (parameters[0] & 4) != 0) { return; } diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp index 3e62a2d7..6a21be9b 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp @@ -1,6 +1,8 @@ #include "Action.h" #include "Amf3.h" +#include "tinyxml2.h" + Action::Action(const AMFArrayValue& arguments) { for (const auto& [paramName, paramValue] : arguments.GetAssociative()) { if (paramName == "Type") { @@ -32,3 +34,46 @@ void Action::SendBehaviorBlocksToClient(AMFArrayValue& args) const { actionArgs->Insert(m_ValueParameterName, m_ValueParameterDouble); } } + +void Action::Serialize(tinyxml2::XMLElement& action) const { + action.SetAttribute("Type", m_Type.c_str()); + + if (m_ValueParameterName.empty()) return; + + action.SetAttribute("ValueParameterName", m_ValueParameterName.c_str()); + + if (m_ValueParameterName == "Message") { + action.SetAttribute("Value", m_ValueParameterString.c_str()); + } else { + action.SetAttribute("Value", m_ValueParameterDouble); + } +} + +void Action::Deserialize(const tinyxml2::XMLElement& action) { + const char* type = nullptr; + action.QueryAttribute("Type", &type); + if (!type) { + LOG("No type found for an action?"); + return; + } + + m_Type = type; + + const char* valueParameterName = nullptr; + action.QueryAttribute("ValueParameterName", &valueParameterName); + if (valueParameterName) { + m_ValueParameterName = valueParameterName; + + if (m_ValueParameterName == "Message") { + const char* value = nullptr; + action.QueryAttribute("Value", &value); + if (value) { + m_ValueParameterString = value; + } else { + LOG("No value found for an action message?"); + } + } else { + action.QueryDoubleAttribute("Value", &m_ValueParameterDouble); + } + } +} diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h index 988e616c..8146e08d 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h @@ -3,6 +3,10 @@ #include +namespace tinyxml2 { + class XMLElement; +}; + class AMFArrayValue; /** @@ -20,6 +24,8 @@ public: void SendBehaviorBlocksToClient(AMFArrayValue& args) const; + void Serialize(tinyxml2::XMLElement& action) const; + void Deserialize(const tinyxml2::XMLElement& action); private: double m_ValueParameterDouble{ 0.0 }; std::string m_Type{ "" }; diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp index 56dc43ff..ae153a5f 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp @@ -1,6 +1,7 @@ #include "StripUiPosition.h" #include "Amf3.h" +#include "tinyxml2.h" StripUiPosition::StripUiPosition(const AMFArrayValue& arguments, const std::string& uiKeyName) { const auto* const uiArray = arguments.GetArray(uiKeyName); @@ -21,3 +22,13 @@ void StripUiPosition::SendBehaviorBlocksToClient(AMFArrayValue& args) const { uiArgs->Insert("x", m_XPosition); uiArgs->Insert("y", m_YPosition); } + +void StripUiPosition::Serialize(tinyxml2::XMLElement& position) const { + position.SetAttribute("x", m_XPosition); + position.SetAttribute("y", m_YPosition); +} + +void StripUiPosition::Deserialize(const tinyxml2::XMLElement& position) { + position.QueryDoubleAttribute("x", &m_XPosition); + position.QueryDoubleAttribute("y", &m_YPosition); +} diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h index f202210d..47501ff7 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h @@ -3,6 +3,10 @@ class AMFArrayValue; +namespace tinyxml2 { + class XMLElement; +} + /** * @brief The position of the first Action in a Strip * @@ -15,6 +19,8 @@ public: [[nodiscard]] double GetX() const noexcept { return m_XPosition; } [[nodiscard]] double GetY() const noexcept { return m_YPosition; } + void Serialize(tinyxml2::XMLElement& position) const; + void Deserialize(const tinyxml2::XMLElement& position); private: double m_XPosition{ 0.0 }; double m_YPosition{ 0.0 }; diff --git a/dGame/dPropertyBehaviors/PropertyBehavior.cpp b/dGame/dPropertyBehaviors/PropertyBehavior.cpp index 423751c4..5bdb5827 100644 --- a/dGame/dPropertyBehaviors/PropertyBehavior.cpp +++ b/dGame/dPropertyBehaviors/PropertyBehavior.cpp @@ -3,6 +3,7 @@ #include "Amf3.h" #include "BehaviorStates.h" #include "ControlBehaviorMsgs.h" +#include "tinyxml2.h" PropertyBehavior::PropertyBehavior() { m_LastEditedState = BehaviorState::HOME_STATE; @@ -124,3 +125,31 @@ void PropertyBehavior::SendBehaviorBlocksToClient(AMFArrayValue& args) const { // TODO Serialize the execution state of the behavior } + +void PropertyBehavior::Serialize(tinyxml2::XMLElement& behavior) const { + behavior.SetAttribute("id", m_BehaviorId); + behavior.SetAttribute("name", m_Name.c_str()); + behavior.SetAttribute("isLocked", isLocked); + behavior.SetAttribute("isLoot", isLoot); + + for (const auto& [stateId, state] : m_States) { + if (state.IsEmpty()) continue; + auto* const stateElement = behavior.InsertNewChildElement("State"); + stateElement->SetAttribute("id", static_cast(stateId)); + state.Serialize(*stateElement); + } +} + + +void PropertyBehavior::Deserialize(const tinyxml2::XMLElement& behavior) { + m_Name = behavior.Attribute("name"); + behavior.QueryBoolAttribute("isLocked", &isLocked); + behavior.QueryBoolAttribute("isLoot", &isLoot); + + for (const auto* stateElement = behavior.FirstChildElement("State"); stateElement; stateElement = stateElement->NextSiblingElement("State")) { + int32_t stateId = -1; + stateElement->QueryIntAttribute("id", &stateId); + if (stateId < 0 || stateId > 5) continue; + m_States[static_cast(stateId)].Deserialize(*stateElement); + } +} diff --git a/dGame/dPropertyBehaviors/PropertyBehavior.h b/dGame/dPropertyBehaviors/PropertyBehavior.h index c9cb4b98..01eb1968 100644 --- a/dGame/dPropertyBehaviors/PropertyBehavior.h +++ b/dGame/dPropertyBehaviors/PropertyBehavior.h @@ -3,6 +3,10 @@ #include "State.h" +namespace tinyxml2 { + class XMLElement; +} + enum class BehaviorState : uint32_t; class AMFArrayValue; @@ -25,6 +29,8 @@ public: [[nodiscard]] int32_t GetBehaviorId() const noexcept { return m_BehaviorId; } void SetBehaviorId(int32_t id) noexcept { m_BehaviorId = id; } + void Serialize(tinyxml2::XMLElement& behavior) const; + void Deserialize(const tinyxml2::XMLElement& behavior); private: // The states this behavior has. diff --git a/dGame/dPropertyBehaviors/State.cpp b/dGame/dPropertyBehaviors/State.cpp index 0c8a11d9..1fb072c1 100644 --- a/dGame/dPropertyBehaviors/State.cpp +++ b/dGame/dPropertyBehaviors/State.cpp @@ -2,6 +2,7 @@ #include "Amf3.h" #include "ControlBehaviorMsgs.h" +#include "tinyxml2.h" template <> void State::HandleMsg(AddStripMessage& msg) { @@ -134,4 +135,20 @@ void State::SendBehaviorBlocksToClient(AMFArrayValue& args) const { strip.SendBehaviorBlocksToClient(*stripArgs); } -}; +} + +void State::Serialize(tinyxml2::XMLElement& state) const { + for (const auto& strip : m_Strips) { + if (strip.IsEmpty()) continue; + + auto* const stripElement = state.InsertNewChildElement("Strip"); + strip.Serialize(*stripElement); + } +} + +void State::Deserialize(const tinyxml2::XMLElement& state) { + for (const auto* stripElement = state.FirstChildElement("Strip"); stripElement; stripElement = stripElement->NextSiblingElement("Strip")) { + auto& strip = m_Strips.emplace_back(); + strip.Deserialize(*stripElement); + } +} diff --git a/dGame/dPropertyBehaviors/State.h b/dGame/dPropertyBehaviors/State.h index f0425763..3e8c827f 100644 --- a/dGame/dPropertyBehaviors/State.h +++ b/dGame/dPropertyBehaviors/State.h @@ -3,6 +3,10 @@ #include "Strip.h" +namespace tinyxml2 { + class XMLElement; +} + class AMFArrayValue; class State { @@ -13,6 +17,8 @@ public: void SendBehaviorBlocksToClient(AMFArrayValue& args) const; bool IsEmpty() const; + void Serialize(tinyxml2::XMLElement& state) const; + void Deserialize(const tinyxml2::XMLElement& state); private: std::vector m_Strips; }; diff --git a/dGame/dPropertyBehaviors/Strip.cpp b/dGame/dPropertyBehaviors/Strip.cpp index 0f459e46..d6dc050b 100644 --- a/dGame/dPropertyBehaviors/Strip.cpp +++ b/dGame/dPropertyBehaviors/Strip.cpp @@ -2,6 +2,7 @@ #include "Amf3.h" #include "ControlBehaviorMsgs.h" +#include "tinyxml2.h" template <> void Strip::HandleMsg(AddStripMessage& msg) { @@ -83,4 +84,25 @@ void Strip::SendBehaviorBlocksToClient(AMFArrayValue& args) const { for (const auto& action : m_Actions) { action.SendBehaviorBlocksToClient(*actions); } -}; +} + +void Strip::Serialize(tinyxml2::XMLElement& strip) const { + auto* const positionElement = strip.InsertNewChildElement("Position"); + m_Position.Serialize(*positionElement); + for (const auto& action : m_Actions) { + auto* const actionElement = strip.InsertNewChildElement("Action"); + action.Serialize(*actionElement); + } +} + +void Strip::Deserialize(const tinyxml2::XMLElement& strip) { + const auto* positionElement = strip.FirstChildElement("Position"); + if (positionElement) { + m_Position.Deserialize(*positionElement); + } + + for (const auto* actionElement = strip.FirstChildElement("Action"); actionElement; actionElement = actionElement->NextSiblingElement("Action")) { + auto& action = m_Actions.emplace_back(); + action.Deserialize(*actionElement); + } +} diff --git a/dGame/dPropertyBehaviors/Strip.h b/dGame/dPropertyBehaviors/Strip.h index 107fee11..8fd7d0fe 100644 --- a/dGame/dPropertyBehaviors/Strip.h +++ b/dGame/dPropertyBehaviors/Strip.h @@ -6,6 +6,10 @@ #include +namespace tinyxml2 { + class XMLElement; +} + class AMFArrayValue; class Strip { @@ -16,6 +20,8 @@ public: void SendBehaviorBlocksToClient(AMFArrayValue& args) const; bool IsEmpty() const noexcept { return m_Actions.empty(); } + void Serialize(tinyxml2::XMLElement& strip) const; + void Deserialize(const tinyxml2::XMLElement& strip); private: std::vector m_Actions; StripUiPosition m_Position; diff --git a/dGame/dUtilities/CheatDetection.cpp b/dGame/dUtilities/CheatDetection.cpp index a87157a1..504b3d53 100644 --- a/dGame/dUtilities/CheatDetection.cpp +++ b/dGame/dUtilities/CheatDetection.cpp @@ -130,12 +130,6 @@ bool CheatDetection::VerifyLwoobjidIsSender(const LWOOBJID& id, const SystemAddr // This will be true if the player does not possess the entity they are trying to send a packet as. // or if the user does not own the character they are trying to send a packet as. - if (invalidPacket) { - va_list args; - va_start(args, messageIfNotSender); - LogAndSaveFailedAntiCheatCheck(id, sysAddr, checkType, messageIfNotSender, args); - va_end(args); - } return !invalidPacket; } diff --git a/dGame/dUtilities/Mail.cpp b/dGame/dUtilities/Mail.cpp index a610c3a7..677728a6 100644 --- a/dGame/dUtilities/Mail.cpp +++ b/dGame/dUtilities/Mail.cpp @@ -94,35 +94,6 @@ void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, LWOOBJ SendNotification(sysAddr, 1); //Show the "one new mail" message } -//Because we need it: -std::string ReadWStringAsString(RakNet::BitStream& bitStream, uint32_t size) { - std::string toReturn = ""; - uint8_t buffer; - bool isFinishedReading = false; - - for (uint32_t i = 0; i < size; ++i) { - bitStream.Read(buffer); - if (!isFinishedReading) toReturn.push_back(buffer); - if (buffer == '\0') isFinishedReading = true; //so we don't continue to read garbage as part of the string. - bitStream.Read(buffer); //Read the null term - } - - return toReturn; -} - -void WriteStringAsWString(RakNet::BitStream& bitStream, std::string str, uint32_t size) { - uint32_t sizeToFill = size - str.size(); - - for (uint32_t i = 0; i < str.size(); ++i) { - bitStream.Write(str[i]); - bitStream.Write(uint8_t(0)); - } - - for (uint32_t i = 0; i < sizeToFill; ++i) { - bitStream.Write(uint16_t(0)); - } -} - void Mail::HandleMailStuff(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* entity) { int mailStuffID = 0; packet.Read(mailStuffID); @@ -176,11 +147,20 @@ void Mail::HandleSendMail(RakNet::BitStream& packet, const SystemAddress& sysAdd return; } - std::string subject = ReadWStringAsString(packet, 50); - std::string body = ReadWStringAsString(packet, 400); - std::string recipient = ReadWStringAsString(packet, 32); + LUWString subjectRead(50); + packet.Read(subjectRead); + + LUWString bodyRead(400); + packet.Read(bodyRead); + + LUWString recipientRead(32); + packet.Read(recipientRead); + + const std::string subject = subjectRead.GetAsString(); + const std::string body = bodyRead.GetAsString(); + //Cleanse recipient: - recipient = std::regex_replace(recipient, std::regex("[^0-9a-zA-Z]+"), ""); + const std::string recipient = std::regex_replace(recipientRead.GetAsString(), std::regex("[^0-9a-zA-Z]+"), ""); uint64_t unknown64 = 0; LWOOBJID attachmentID; @@ -267,40 +247,44 @@ void Mail::HandleDataRequest(RakNet::BitStream& packet, const SystemAddress& sys RakNet::BitStream bitStream; BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::MAIL); bitStream.Write(int(MailMessageID::MailData)); - bitStream.Write(int(0)); + bitStream.Write(int(0)); // throttled - bitStream.Write(playerMail.size()); + bitStream.Write(playerMail.size()); // size bitStream.Write(0); for (const auto& mail : playerMail) { bitStream.Write(mail.id); //MailID - WriteStringAsWString(bitStream, mail.subject.c_str(), 50); //subject - WriteStringAsWString(bitStream, mail.body.c_str(), 400); //body - WriteStringAsWString(bitStream, mail.senderUsername.c_str(), 32); //sender + const LUWString subject(mail.subject, 50); + bitStream.Write(subject); //subject + const LUWString body(mail.body, 400); + bitStream.Write(body); //body + const LUWString sender(mail.senderUsername, 32); + bitStream.Write(sender); //sender + bitStream.Write(uint32_t(0)); // packing - bitStream.Write(uint32_t(0)); - bitStream.Write(uint64_t(0)); + bitStream.Write(uint64_t(0)); // attachedCurrency bitStream.Write(mail.itemID); //Attachment ID LOT lot = mail.itemLOT; if (lot <= 0) bitStream.Write(LOT(-1)); else bitStream.Write(lot); - bitStream.Write(uint32_t(0)); + bitStream.Write(uint32_t(0)); // packing - bitStream.Write(mail.itemSubkey); //Attachment subKey - bitStream.Write(mail.itemCount); //Attachment count + bitStream.Write(mail.itemSubkey); // Attachment subKey - bitStream.Write(uint32_t(0)); - bitStream.Write(uint16_t(0)); + bitStream.Write(mail.itemCount); // Attachment count + bitStream.Write(uint8_t(0)); // subject type (used for auction) + bitStream.Write(uint8_t(0)); // packing + bitStream.Write(uint32_t(0)); // packing - bitStream.Write(mail.timeSent); //time sent (twice?) - bitStream.Write(mail.timeSent); + bitStream.Write(mail.timeSent); // expiration date + bitStream.Write(mail.timeSent);// send date bitStream.Write(mail.wasRead); //was read - bitStream.Write(uint8_t(0)); - bitStream.Write(uint16_t(0)); - bitStream.Write(uint32_t(0)); + bitStream.Write(uint8_t(0)); // isLocalized + bitStream.Write(uint16_t(0)); // packing + bitStream.Write(uint32_t(0)); // packing } Game::server->Send(bitStream, sysAddr, false); diff --git a/dGame/dUtilities/Preconditions.cpp b/dGame/dUtilities/Preconditions.cpp index bd855962..a0aac27e 100644 --- a/dGame/dUtilities/Preconditions.cpp +++ b/dGame/dUtilities/Preconditions.cpp @@ -26,17 +26,18 @@ Precondition::Precondition(const uint32_t condition) { if (result.eof()) { this->type = PreconditionType::ItemEquipped; this->count = 1; - this->values = { 0 }; + this->values.clear(); + this->values.push_back(0); LOG("Failed to find precondition of id (%i)!", condition); return; } - this->type = static_cast(result.fieldIsNull(0) ? 0 : result.getIntField(0)); + this->type = static_cast(result.fieldIsNull("type") ? 0 : result.getIntField("type")); - if (!result.fieldIsNull(1)) { - std::istringstream stream(result.getStringField(1)); + if (!result.fieldIsNull("targetLOT")) { + std::istringstream stream(result.getStringField("targetLOT")); std::string token; while (std::getline(stream, token, ',')) { @@ -45,7 +46,7 @@ Precondition::Precondition(const uint32_t condition) { } } - this->count = result.fieldIsNull(2) ? 1 : result.getIntField(2); + this->count = result.fieldIsNull("targetCount") ? 1 : result.getIntField("targetCount"); result.finalize(); } @@ -138,21 +139,20 @@ bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluat case PreconditionType::DoesNotHaveItem: return inventoryComponent->IsEquipped(value) < count; case PreconditionType::HasAchievement: - mission = missionComponent->GetMission(value); - - return mission == nullptr || mission->GetMissionState() >= eMissionState::COMPLETE; + if (missionComponent == nullptr) return false; + return missionComponent->GetMissionState(value) >= eMissionState::COMPLETE; case PreconditionType::MissionAvailable: - mission = missionComponent->GetMission(value); - - return mission == nullptr || mission->GetMissionState() >= eMissionState::AVAILABLE; + if (missionComponent == nullptr) return false; + return missionComponent->GetMissionState(value) == eMissionState::AVAILABLE || missionComponent->GetMissionState(value) == eMissionState::COMPLETE_AVAILABLE; case PreconditionType::OnMission: - mission = missionComponent->GetMission(value); - - return mission == nullptr || mission->GetMissionState() >= eMissionState::ACTIVE; + if (missionComponent == nullptr) return false; + return missionComponent->GetMissionState(value) == eMissionState::ACTIVE || + missionComponent->GetMissionState(value) == eMissionState::COMPLETE_ACTIVE || + missionComponent->GetMissionState(value) == eMissionState::READY_TO_COMPLETE || + missionComponent->GetMissionState(value) == eMissionState::COMPLETE_READY_TO_COMPLETE; case PreconditionType::MissionComplete: - mission = missionComponent->GetMission(value); - - return mission == nullptr ? false : mission->GetMissionState() >= eMissionState::COMPLETE; + if (missionComponent == nullptr) return false; + return missionComponent->GetMissionState(value) >= eMissionState::COMPLETE; case PreconditionType::PetDeployed: return false; // TODO case PreconditionType::HasFlag: diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index bd7d5f28..ca4e03d4 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -8,6 +8,7 @@ #include "SlashCommandHandler.h" #include +#include #include "DEVGMCommands.h" #include "GMGreaterThanZeroCommands.h" @@ -19,7 +20,7 @@ #include "dServer.h" namespace { - std::vector CommandInfos; + std::map CommandInfos; std::map RegisteredCommands; } @@ -30,7 +31,6 @@ void SlashCommandHandler::RegisterCommand(Command command) { } for (const auto& alias : command.aliases) { - LOG_DEBUG("Registering command %s", alias.c_str()); auto [_, success] = RegisteredCommands.try_emplace(alias, command); // Don't allow duplicate commands if (!success) { @@ -38,9 +38,8 @@ void SlashCommandHandler::RegisterCommand(Command command) { continue; } } - - CommandInfos.push_back(command); -}; + CommandInfos[command.aliases[0]] = command; +} void SlashCommandHandler::HandleChatCommand(const std::u16string& chat, Entity* entity, const SystemAddress& sysAddr) { auto input = GeneralUtils::UTF16ToWTF8(chat); @@ -61,11 +60,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& chat, Entity* if (commandHandle.requiredLevel > eGameMasterLevel::CIVILIAN) Database::Get()->InsertSlashCommandUsage(entity->GetObjectID(), input); commandHandle.handle(entity, sysAddr, args); } else if (entity->GetGMLevel() != eGameMasterLevel::CIVILIAN) { - // We don't need to tell civilians they aren't high enough level error = "You are not high enough GM level to use \"" + command + "\""; } } else if (entity->GetGMLevel() == eGameMasterLevel::CIVILIAN) { - // We don't need to tell civilians commands don't exist error = "Command " + command + " does not exist!"; } @@ -74,41 +71,76 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& chat, Entity* } } -// This commands in here so we can access the CommandInfos to display info void GMZeroCommands::Help(Entity* entity, const SystemAddress& sysAddr, const std::string args) { std::ostringstream feedback; - if (args.empty()) { - feedback << "----- Commands -----"; - for (const auto& command : CommandInfos) { - // TODO: Limit displaying commands based on GM level they require - if (command.requiredLevel > entity->GetGMLevel()) continue; - LOG("Help command: %s", command.aliases[0].c_str()); - feedback << "\n/" << command.aliases[0] << ": " << command.help; + constexpr size_t pageSize = 10; + + std::string trimmedArgs = args; + trimmedArgs.erase(trimmedArgs.begin(), std::find_if_not(trimmedArgs.begin(), trimmedArgs.end(), [](unsigned char ch) { + return std::isspace(ch); + })); + trimmedArgs.erase(std::find_if_not(trimmedArgs.rbegin(), trimmedArgs.rend(), [](unsigned char ch) { + return std::isspace(ch); + }).base(), trimmedArgs.end()); + + std::optional parsedPage = GeneralUtils::TryParse(trimmedArgs); + if (trimmedArgs.empty() || parsedPage.has_value()) { + size_t page = parsedPage.value_or(1); + + std::map accessibleCommands; + for (const auto& [commandName, command] : CommandInfos) { + if (command.requiredLevel <= entity->GetGMLevel()) { + accessibleCommands.emplace(commandName, command); + } } - } else { - bool foundCommand = false; - for (const auto& command : CommandInfos) { - if (std::ranges::find(command.aliases, args) == command.aliases.end()) continue; - if (entity->GetGMLevel() < command.requiredLevel) break; - foundCommand = true; - feedback << "----- " << command.aliases.at(0) << " -----\n"; - // info can be a localizable string - feedback << command.info; - if (command.aliases.size() == 1) break; + size_t totalPages = (accessibleCommands.size() + pageSize - 1) / pageSize; - feedback << "\nAliases: "; - for (size_t i = 0; i < command.aliases.size(); i++) { + if (page < 1 || page > totalPages) { + feedback << "Invalid page number. Total pages: " << totalPages; + GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedback.str())); + return; + } + + auto it = accessibleCommands.begin(); + std::advance(it, (page - 1) * pageSize); + size_t endIdx = std::min(page * pageSize, accessibleCommands.size()); + + feedback << "----- Commands (Page " << page << " of " << totalPages << ") -----"; + for (size_t i = (page - 1) * pageSize; i < endIdx; ++i, ++it) { + feedback << "\n/" << it->first << ": " << it->second.help; + } + + const auto feedbackStr = feedback.str(); + if (!feedbackStr.empty()) { + GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr)); + } + return; + } + + auto it = std::ranges::find_if(CommandInfos, [&trimmedArgs](const auto& pair) { + return std::ranges::find(pair.second.aliases, trimmedArgs) != pair.second.aliases.end(); + }); + + if (it != CommandInfos.end() && entity->GetGMLevel() >= it->second.requiredLevel) { + const auto& command = it->second; + feedback << "----- " << it->first << " Info -----\n"; + feedback << command.info << "\n"; + if (command.aliases.size() > 1) { + feedback << "Aliases: "; + for (size_t i = 0; i < command.aliases.size(); ++i) { if (i > 0) feedback << ", "; feedback << command.aliases[i]; } } - - // Let GameMasters know if the command doesn't exist - if (!foundCommand && entity->GetGMLevel() > eGameMasterLevel::CIVILIAN) feedback << "Command " << std::quoted(args) << " does not exist!"; + } else { + feedback << "Command not found."; } + const auto feedbackStr = feedback.str(); - if (!feedbackStr.empty()) GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr)); + if (!feedbackStr.empty()) { + GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr)); + } } void SlashCommandHandler::SendAnnouncement(const std::string& title, const std::string& message) { @@ -878,11 +910,38 @@ void SlashCommandHandler::Startup() { }; RegisterCommand(TitleCommand); + Command ShowAllCommand{ + .help = "Show all online players across World Servers", + .info = "Usage: /showall (displayZoneData: Default 1) (displayIndividualPlayers: Default 1)", + .aliases = { "showall" }, + .handle = GMGreaterThanZeroCommands::ShowAll, + .requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR + }; + RegisterCommand(ShowAllCommand); + + Command FindPlayerCommand{ + .help = "Find the World Server a player is in if they are online", + .info = "Find the World Server a player is in if they are online", + .aliases = { "findplayer" }, + .handle = GMGreaterThanZeroCommands::FindPlayer, + .requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR + }; + RegisterCommand(FindPlayerCommand); + + Command SpectateCommand{ + .help = "Spectate a player", + .info = "Specify a player name to spectate. They must be in the same world as you. Leave blank to stop spectating", + .aliases = { "spectate", "follow" }, + .handle = GMGreaterThanZeroCommands::Spectate, + .requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR + }; + RegisterCommand(SpectateCommand); // Register GM Zero Commands + Command HelpCommand{ .help = "Display command info", - .info = "If a command is given, display detailed info on that command. Otherwise display a list of commands with short desctiptions.", + .info = "If a command is given, display detailed info on that command. Otherwise display a list of commands with short descriptions.", .aliases = { "help", "h"}, .handle = GMZeroCommands::Help, .requiredLevel = eGameMasterLevel::CIVILIAN @@ -997,4 +1056,383 @@ void SlashCommandHandler::Startup() { }; RegisterCommand(InstanceInfoCommand); + //Commands that are handled by the client + + Command faqCommand{ + .help = "Show the LU FAQ Page", + .info = "Show the LU FAQ Page", + .aliases = {"faq","faqs"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(faqCommand); + + Command teamChatCommand{ + .help = "Send a message to your teammates.", + .info = "Send a message to your teammates.", + .aliases = {"team","t"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(teamChatCommand); + + Command showStoreCommand{ + .help = "Show the LEGO shop page.", + .info = "Show the LEGO shop page.", + .aliases = {"shop","store"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(showStoreCommand); + + Command minigamesCommand{ + .help = "Show the LEGO minigames page!", + .info = "Show the LEGO minigames page!", + .aliases = {"minigames"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(minigamesCommand); + + Command forumsCommand{ + .help = "Show the LU Forums!", + .info = "Show the LU Forums!", + .aliases = {"forums"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(forumsCommand); + + Command exitGameCommand{ + .help = "Exit to desktop", + .info = "Exit to desktop", + .aliases = {"exit","quit"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(exitGameCommand); + + Command thumbsUpCommand{ + .help = "Oh, yeah!", + .info = "Oh, yeah!", + .aliases = {"thumb","thumbs","thumbsup"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(thumbsUpCommand); + + Command victoryCommand{ + .help = "Victory!", + .info = "Victory!", + .aliases = {"victory!"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(victoryCommand); + + Command backflipCommand{ + .help = "Do a flip!", + .info = "Do a flip!", + .aliases = {"backflip"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(backflipCommand); + + Command clapCommand{ + .help = "A round of applause!", + .info = "A round of applause!", + .aliases = {"clap"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(clapCommand); + + Command logoutCharacterCommand{ + .help = "Returns you to the character select screen.", + .info = "Returns you to the character select screen.", + .aliases = {"camp","logoutcharacter"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(logoutCharacterCommand); + + Command sayCommand{ + .help = "Say something outloud so that everyone can hear you", + .info = "Say something outloud so that everyone can hear you", + .aliases = {"s","say"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(sayCommand); + + Command whisperCommand{ + .help = "Send a private message to another player.", + .info = "Send a private message to another player.", + .aliases = {"tell","w","whisper"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(whisperCommand); + + Command locationCommand{ + .help = "Output your current location on the map to the chat box.", + .info = "Output your current location on the map to the chat box.", + .aliases = {"loc","locate","location"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(locationCommand); + + Command logoutCommand{ + .help = "Returns you to the login screen.", + .info = "Returns you to the login screen.", + .aliases = {"logout","logoutaccount"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(logoutCommand); + + Command shrugCommand{ + .help = "I dunno...", + .info = "I dunno...", + .aliases = {"shrug"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(shrugCommand); + + Command leaveTeamCommand{ + .help = "Leave your current team.", + .info = "Leave your current team.", + .aliases = {"leave","leaveteam","teamleave","tleave"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(leaveTeamCommand); + + Command teamLootTypeCommand{ + .help = "[rr|ffa] Set the loot for your current team (round-robin/free for all).", + .info = "[rr|ffa] Set the loot for your current team (round-robin/free for all).", + .aliases = {"setloot","teamsetloot","tloot","tsetloot"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(teamLootTypeCommand); + + Command removeFriendCommand{ + .help = "[name] Removes a player from your friends list.", + .info = "[name] Removes a player from your friends list.", + .aliases = {"removefriend"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(removeFriendCommand); + + Command yesCommand{ + .help = "Aye aye, captain!", + .info = "Aye aye, captain!", + .aliases = {"yes"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(yesCommand); + + Command teamInviteCommand{ + .help = "[name] Invite a player to your team.", + .info = "[name] Invite a player to your team.", + .aliases = {"invite","inviteteam","teaminvite","tinvite"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(teamInviteCommand); + + Command danceCommand{ + .help = "Dance 'til you can't dance no more.", + .info = "Dance 'til you can't dance no more.", + .aliases = {"dance"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(danceCommand); + + Command sighCommand{ + .help = "Another day, another brick.", + .info = "Another day, another brick.", + .aliases = {"sigh"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(sighCommand); + + Command recommendedOptionsCommand{ + .help = "Sets the recommended performance options in the cfg file", + .info = "Sets the recommended performance options in the cfg file", + .aliases = {"recommendedperfoptions"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(recommendedOptionsCommand); + + Command setTeamLeaderCommand{ + .help = "[name] Set the leader for your current team.", + .info = "[name] Set the leader for your current team.", + .aliases = {"leader","setleader","teamsetleader","tleader","tsetleader"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(setTeamLeaderCommand); + + Command cringeCommand{ + .help = "I don't even want to talk about it...", + .info = "I don't even want to talk about it...", + .aliases = {"cringe"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(cringeCommand); + + Command talkCommand{ + .help = "Jibber Jabber", + .info = "Jibber Jabber", + .aliases = {"talk"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(talkCommand); + + Command cancelQueueCommand{ + .help = "Cancel Your position in the queue if you are in one.", + .info = "Cancel Your position in the queue if you are in one.", + .aliases = {"cancelqueue"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(cancelQueueCommand); + + Command lowPerformanceCommand{ + .help = "Sets the default low-spec performance options in the cfg file", + .info = "Sets the default low-spec performance options in the cfg file", + .aliases = {"perfoptionslow"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(lowPerformanceCommand); + + Command kickFromTeamCommand{ + .help = "[name] Kick a player from your current team.", + .info = "[name] Kick a player from your current team.", + .aliases = {"kick","kickplayer","teamkickplayer","tkick","tkickplayer"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(kickFromTeamCommand); + + Command thanksCommand{ + .help = "Express your gratitude for another.", + .info = "Express your gratitude for another.", + .aliases = {"thanks"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(thanksCommand); + + Command waveCommand{ + .help = "Wave to other players.", + .info = "Wave to other players.", + .aliases = {"wave"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(waveCommand); + + Command whyCommand{ + .help = "Why|!?!!", + .info = "Why|!?!!", + .aliases = {"why"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(whyCommand); + + Command midPerformanceCommand{ + .help = "Sets the default medium-spec performance options in the cfg file", + .info = "Sets the default medium-spec performance options in the cfg file", + .aliases = {"perfoptionsmid"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(midPerformanceCommand); + + Command highPerformanceCommand{ + .help = "Sets the default high-spec performance options in the cfg file", + .info = "Sets the default high-spec performance options in the cfg file", + .aliases = {"perfoptionshigh"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(highPerformanceCommand); + + Command gaspCommand{ + .help = "Oh my goodness!", + .info = "Oh my goodness!", + .aliases = {"gasp"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(gaspCommand); + + Command ignoreCommand{ + .help = "[name] Add a player to your ignore list.", + .info = "[name] Add a player to your ignore list.", + .aliases = {"addignore"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(ignoreCommand); + + Command addFriendCommand{ + .help = "[name] Add a player to your friends list.", + .info = "[name] Add a player to your friends list.", + .aliases = {"addfriend"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(addFriendCommand); + + Command cryCommand{ + .help = "Show everyone your 'Aw' face.", + .info = "Show everyone your 'Aw' face.", + .aliases = {"cry"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(cryCommand); + + Command giggleCommand{ + .help = "A good little chuckle", + .info = "A good little chuckle", + .aliases = {"giggle"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(giggleCommand); + + Command saluteCommand{ + .help = "For those about to build...", + .info = "For those about to build...", + .aliases = {"salute"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(saluteCommand); + + Command removeIgnoreCommand{ + .help = "[name] Removes a player from your ignore list.", + .info = "[name] Removes a player from your ignore list.", + .aliases = {"removeIgnore"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(removeIgnoreCommand); } diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index b8d6b9f3..00add608 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -41,6 +41,7 @@ #include "ScriptedActivityComponent.h" #include "SkillComponent.h" #include "TriggerComponent.h" +#include "RigidbodyPhantomPhysicsComponent.h" // Enums #include "eGameMasterLevel.h" @@ -704,7 +705,7 @@ namespace DEVGMCommands { auto tables = query.execQuery(); while (!tables.eof()) { - std::string message = std::to_string(tables.getIntField(0)) + " - " + tables.getStringField(1); + std::string message = std::to_string(tables.getIntField("id")) + " - " + tables.getStringField("name"); ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(message, message.size())); tables.nextRow(); } @@ -1129,8 +1130,13 @@ namespace DEVGMCommands { void SpawnPhysicsVerts(Entity* entity, const SystemAddress& sysAddr, const std::string args) { //Go tell physics to spawn all the vertices: auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PHANTOM_PHYSICS); - for (auto en : entities) { - auto phys = static_cast(en->GetComponent(eReplicaComponentType::PHANTOM_PHYSICS)); + for (const auto* en : entities) { + const auto* phys = static_cast(en->GetComponent(eReplicaComponentType::PHANTOM_PHYSICS)); + if (phys) + phys->SpawnVertices(); + } + for (const auto* en : Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS)) { + const auto* phys = en->GetComponent(); if (phys) phys->SpawnVertices(); } diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.h b/dGame/dUtilities/SlashCommands/DEVGMCommands.h index 149348a1..e03fd4de 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.h +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.h @@ -1,3 +1,6 @@ +#ifndef DEVGMCOMMANDS_H +#define DEVGMCOMMANDS_H + namespace DEVGMCommands { void SetGMLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args); void ToggleNameplate(Entity* entity, const SystemAddress& sysAddr, const std::string args); @@ -70,4 +73,6 @@ namespace DEVGMCommands { void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args); void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args); void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args); -} \ No newline at end of file +} + +#endif //!DEVGMCOMMANDS_H diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp index d0aa66aa..b9eaf7bf 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp @@ -287,4 +287,54 @@ namespace GMGreaterThanZeroCommands { std::string name = entity->GetCharacter()->GetName() + " - " + args; GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS); } + + void ShowAll(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + bool displayZoneData = true; + bool displayIndividualPlayers = true; + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + if (!splitArgs.empty() && !splitArgs.at(0).empty()) displayZoneData = splitArgs.at(0) == "1"; + if (splitArgs.size() > 1) displayIndividualPlayers = splitArgs.at(1) == "1"; + + ShowAllRequest request { + .requestor = entity->GetObjectID(), + .displayZoneData = displayZoneData, + .displayIndividualPlayers = displayIndividualPlayers + }; + + CBITSTREAM; + request.Serialize(bitStream); + Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); + } + + void FindPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (args.empty()) { + GameMessages::SendSlashCommandFeedbackText(entity, u"No player Given"); + return; + } + + FindPlayerRequest request { + .requestor = entity->GetObjectID(), + .playerName = LUWString(args) + }; + + CBITSTREAM; + request.Serialize(bitStream); + Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); + } + + void Spectate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (args.empty()) { + GameMessages::SendForceCameraTargetCycle(entity, false, eCameraTargetCyclingMode::DISALLOW_CYCLING, entity->GetObjectID()); + return; + } + + auto player = PlayerManager::GetPlayer(args); + if (!player) { + GameMessages::SendSlashCommandFeedbackText(entity, u"Player not found"); + return; + } + GameMessages::SendSlashCommandFeedbackText(entity, u"Spectating Player"); + GameMessages::SendForceCameraTargetCycle(entity, false, eCameraTargetCyclingMode::DISALLOW_CYCLING, player->GetObjectID()); + } } diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h index 4b03441f..c278fc0a 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h @@ -1,3 +1,6 @@ +#ifndef GMGREATERTHANZEROCOMMANDS_H +#define GMGREATERTHANZEROCOMMANDS_H + namespace GMGreaterThanZeroCommands { void Kick(Entity* entity, const SystemAddress& sysAddr, const std::string args); void MailItem(Entity* entity, const SystemAddress& sysAddr, const std::string args); @@ -10,4 +13,9 @@ namespace GMGreaterThanZeroCommands { void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args); void SetName(Entity* entity, const SystemAddress& sysAddr, const std::string args); void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args); -} \ No newline at end of file + void ShowAll(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void FindPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Spectate(Entity* entity, const SystemAddress& sysAddr, const std::string args); +} + +#endif //!GMGREATERTHANZEROCOMMANDS_H diff --git a/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp index f183d5ea..6c9811c2 100644 --- a/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp +++ b/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp @@ -224,5 +224,9 @@ namespace GMZeroCommands { ChatPackets::SendSystemMessage(sysAddr, u"Map: " + (GeneralUtils::to_u16string(zoneId.GetMapID())) + u"\nClone: " + (GeneralUtils::to_u16string(zoneId.GetCloneID())) + u"\nInstance: " + (GeneralUtils::to_u16string(zoneId.GetInstanceID()))); } + + //For client side commands + void ClientHandled(Entity* entity, const SystemAddress& sysAddr, const std::string args) {} + }; diff --git a/dGame/dUtilities/SlashCommands/GMZeroCommands.h b/dGame/dUtilities/SlashCommands/GMZeroCommands.h index 29aa8f36..d3f6753d 100644 --- a/dGame/dUtilities/SlashCommands/GMZeroCommands.h +++ b/dGame/dUtilities/SlashCommands/GMZeroCommands.h @@ -1,3 +1,6 @@ +#ifndef GMZEROCOMMANDS_H +#define GMZEROCOMMANDS_H + namespace GMZeroCommands { void Help(Entity* entity, const SystemAddress& sysAddr, const std::string args); void Credits(Entity* entity, const SystemAddress& sysAddr, const std::string args); @@ -12,4 +15,7 @@ namespace GMZeroCommands { void LeaveZone(Entity* entity, const SystemAddress& sysAddr, const std::string args); void Resurrect(Entity* entity, const SystemAddress& sysAddr, const std::string args); void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ClientHandled(Entity* entity, const SystemAddress& sysAddr, const std::string args); } + +#endif //!GMZEROCOMMANDS_H diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index 4f288747..8ae9246d 100644 --- a/dGame/dUtilities/VanityUtilities.cpp +++ b/dGame/dUtilities/VanityUtilities.cpp @@ -285,7 +285,6 @@ void ParseXml(const std::string& file) { } if (zoneID.value() != currentZoneID) { - LOG_DEBUG("Skipping (%s) %i location because it is in %i and not the current zone (%i)", name, lot, zoneID.value(), currentZoneID); continue; } diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 3d5f4aff..05955a85 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -40,6 +40,8 @@ #include "BitStreamUtils.h" #include "Start.h" #include "Server.h" +#include "CDZoneTableTable.h" +#include "eGameMasterLevel.h" namespace Game { Logger* logger = nullptr; @@ -186,15 +188,20 @@ int main(int argc, char** argv) { std::cout << "Enter a username: "; std::cin >> username; + const auto checkIsAdmin = []() { + std::string admin; + std::cout << "What level of privilege should this account have? Please enter a number between 0 (Player) and 9 (Admin) inclusive. No entry will default to 0." << std::endl; + std::cin >> admin; + return admin; + }; + auto accountId = Database::Get()->GetAccountInfo(username); - if (accountId) { + if (accountId && accountId->id != 0) { LOG("Account with name \"%s\" already exists", username.c_str()); std::cout << "Do you want to change the password of that account? [y/n]?"; std::string prompt = ""; std::cin >> prompt; if (prompt == "y" || prompt == "yes") { - if (accountId->id == 0) return EXIT_FAILURE; - //Read the password from the console without echoing it. #ifdef __linux__ //This function is obsolete, but it only meant to be used by the @@ -219,6 +226,20 @@ int main(int argc, char** argv) { } else { LOG("Account \"%s\" was not updated.", username.c_str()); } + + std::cout << "Update admin privileges? [y/n]? "; + std::string admin; + std::cin >> admin; + bool updateAdmin = admin == "y" || admin == "yes"; + if (updateAdmin) { + auto gmLevel = GeneralUtils::TryParse(checkIsAdmin()).value_or(0); + if (gmLevel > 9 || gmLevel < 0) { + LOG("Invalid admin level. Defaulting to 0"); + gmLevel = 0; + } + Database::Get()->UpdateAccountGmLevel(accountId->id, static_cast(gmLevel)); + } + return EXIT_SUCCESS; } @@ -249,6 +270,17 @@ int main(int argc, char** argv) { } LOG("Account created successfully!"); + + accountId = Database::Get()->GetAccountInfo(username); + if (accountId) { + auto gmLevel = GeneralUtils::TryParse(checkIsAdmin()).value_or(0); + if (gmLevel > 9 || gmLevel < 0) { + LOG("Invalid admin level. Defaulting to 0"); + gmLevel = 0; + } + Database::Get()->UpdateAccountGmLevel(accountId->id, static_cast(gmLevel)); + } + return EXIT_SUCCESS; } @@ -277,6 +309,17 @@ int main(int argc, char** argv) { PersistentIDManager::Initialize(); Game::im = new InstanceManager(Game::logger, Game::server->GetIP()); + //Get CDClient initial information + try { + CDClientManager::LoadValuesFromDatabase(); + } catch (CppSQLite3Exception& e) { + LOG("Failed to initialize CDServer SQLite Database"); + LOG("May be caused by corrupted file: %s", (Game::assetManager->GetResPath() / "CDServer.sqlite").string().c_str()); + LOG("Error: %s", e.errorMessage()); + LOG("Error Code: %i", e.errorCode()); + return EXIT_FAILURE; + } + //Depending on the config, start up servers: if (Game::config->GetValue("prestart_servers") != "0") { StartChatServer(); @@ -382,6 +425,7 @@ int main(int argc, char** argv) { } void HandlePacket(Packet* packet) { + if (packet->length < 1) return; if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION) { LOG("A server has disconnected"); @@ -545,7 +589,7 @@ void HandlePacket(Packet* packet) { inStream.Read(sessionKey); LUString username; inStream.Read(username); - + for (auto it : activeSessions) { if (it.second == username.string) { activeSessions.erase(it.first); diff --git a/dNavigation/dNavMesh.cpp b/dNavigation/dNavMesh.cpp index f49dd31e..d9584b00 100644 --- a/dNavigation/dNavMesh.cpp +++ b/dNavigation/dNavMesh.cpp @@ -112,6 +112,31 @@ void dNavMesh::LoadNavmesh() { m_NavMesh = mesh; } +NiPoint3 dNavMesh::NearestPoint(const NiPoint3& location, const float halfExtent) const { + NiPoint3 toReturn = location; + if (m_NavMesh != nullptr) { + float pos[3]; + pos[0] = location.x; + pos[1] = location.y; + pos[2] = location.z; + + dtPolyRef nearestRef = 0; + float polyPickExt[3] = { halfExtent, halfExtent, halfExtent }; + float nearestPoint[3] = { 0.0f, 0.0f, 0.0f }; + dtQueryFilter filter{}; + + auto hasPoly = m_NavQuery->findNearestPoly(pos, polyPickExt, &filter, &nearestRef, nearestPoint); + if (hasPoly != DT_SUCCESS) { + toReturn = location; + } else { + toReturn.x = nearestPoint[0]; + toReturn.y = nearestPoint[1]; + toReturn.z = nearestPoint[2]; + } + } + return toReturn; +} + float dNavMesh::GetHeightAtPoint(const NiPoint3& location, const float halfExtentsHeight) const { if (m_NavMesh == nullptr) { return location.y; diff --git a/dNavigation/dNavMesh.h b/dNavigation/dNavMesh.h index 8a55c649..60e07e7c 100644 --- a/dNavigation/dNavMesh.h +++ b/dNavigation/dNavMesh.h @@ -21,7 +21,7 @@ public: /** * Get the height at a point - * + * * @param location The location to check for height at. This is the center of the search area. * @param halfExtentsHeight The half extents height of the search area. This is the distance from the center to the top and bottom of the search area. * The larger the value of halfExtentsHeight is, the larger the performance cost of the query. @@ -29,7 +29,7 @@ public: */ float GetHeightAtPoint(const NiPoint3& location, const float halfExtentsHeight = 32.0f) const; std::vector GetPath(const NiPoint3& startPos, const NiPoint3& endPos, float speed = 10.0f); - + NiPoint3 NearestPoint(const NiPoint3& location, const float halfExtent = 32.0f) const; bool IsNavmeshLoaded() { return m_NavMesh != nullptr; } private: diff --git a/dNet/AuthPackets.cpp b/dNet/AuthPackets.cpp index a2bf731c..715188e8 100644 --- a/dNet/AuthPackets.cpp +++ b/dNet/AuthPackets.cpp @@ -82,7 +82,7 @@ void AuthPackets::SendHandshake(dServer* server, const SystemAddress& sysAddr, c if (serverType == ServerType::Auth) bitStream.Write(ServiceId::Auth); else if (serverType == ServerType::World) bitStream.Write(ServiceId::World); else bitStream.Write(ServiceId::General); - bitStream.Write(215523405360); + bitStream.Write(215523470896); server->Send(bitStream, sysAddr, false); } diff --git a/dNet/BitStreamUtils.h b/dNet/BitStreamUtils.h index 1322ec95..33fde564 100644 --- a/dNet/BitStreamUtils.h +++ b/dNet/BitStreamUtils.h @@ -5,6 +5,7 @@ #include "MessageIdentifiers.h" #include "BitStream.h" #include +#include enum class eConnectionType : uint16_t; diff --git a/dNet/ChatPackets.cpp b/dNet/ChatPackets.cpp index d0354659..6c9b36b7 100644 --- a/dNet/ChatPackets.cpp +++ b/dNet/ChatPackets.cpp @@ -12,6 +12,30 @@ #include "eConnectionType.h" #include "eChatMessageType.h" +void ShowAllRequest::Serialize(RakNet::BitStream& bitStream) { + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::SHOW_ALL); + bitStream.Write(this->requestor); + bitStream.Write(this->displayZoneData); + bitStream.Write(this->displayIndividualPlayers); +} + +void ShowAllRequest::Deserialize(RakNet::BitStream& inStream) { + inStream.Read(this->requestor); + inStream.Read(this->displayZoneData); + inStream.Read(this->displayIndividualPlayers); +} + +void FindPlayerRequest::Serialize(RakNet::BitStream& bitStream) { + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WHO); + bitStream.Write(this->requestor); + bitStream.Write(this->playerName); +} + +void FindPlayerRequest::Deserialize(RakNet::BitStream& inStream) { + inStream.Read(this->requestor); + inStream.Read(this->playerName); +} + void ChatPackets::SendChatMessage(const SystemAddress& sysAddr, char chatChannel, const std::string& senderName, LWOOBJID playerObjectID, bool senderMythran, const std::u16string& message) { CBITSTREAM; BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GENERAL_CHAT_MESSAGE); diff --git a/dNet/ChatPackets.h b/dNet/ChatPackets.h index 8f6de175..c33d00dd 100644 --- a/dNet/ChatPackets.h +++ b/dNet/ChatPackets.h @@ -11,6 +11,21 @@ struct SystemAddress; #include #include "dCommonVars.h" +struct ShowAllRequest{ + LWOOBJID requestor = LWOOBJID_EMPTY; + bool displayZoneData = true; + bool displayIndividualPlayers = true; + void Serialize(RakNet::BitStream& bitStream); + void Deserialize(RakNet::BitStream& inStream); +}; + +struct FindPlayerRequest{ + LWOOBJID requestor = LWOOBJID_EMPTY; + LUWString playerName; + void Serialize(RakNet::BitStream& bitStream); + void Deserialize(RakNet::BitStream& inStream); +}; + namespace ChatPackets { void SendChatMessage(const SystemAddress& sysAddr, char chatChannel, const std::string& senderName, LWOOBJID playerObjectID, bool senderMythran, const std::u16string& message); void SendSystemMessage(const SystemAddress& sysAddr, const std::u16string& message, bool broadcast = false); diff --git a/dNet/WorldPackets.cpp b/dNet/WorldPackets.cpp index 1c2b8dec..44dd687e 100644 --- a/dNet/WorldPackets.cpp +++ b/dNet/WorldPackets.cpp @@ -12,6 +12,15 @@ #include +void HTTPMonitorInfo::Serialize(RakNet::BitStream &bitStream) const { + bitStream.Write(port); + bitStream.Write(openWeb); + bitStream.Write(supportsSum); + bitStream.Write(supportsDetail); + bitStream.Write(supportsWho); + bitStream.Write(supportsObjects); +} + void WorldPackets::SendLoadStaticZone(const SystemAddress& sysAddr, float x, float y, float z, uint32_t checksum, LWOZONEID zone) { RakNet::BitStream bitStream; BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::LOAD_STATIC_ZONE); @@ -160,3 +169,18 @@ void WorldPackets::SendGMLevelChange(const SystemAddress& sysAddr, bool success, SEND_PACKET; } + +void WorldPackets::SendHTTPMonitorInfo(const SystemAddress& sysAddr, const HTTPMonitorInfo& info) { + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::HTTP_MONITOR_INFO_RESPONSE); + info.Serialize(bitStream); + SEND_PACKET; +} + +void WorldPackets::SendDebugOuput(const SystemAddress& sysAddr, const std::string& data){ + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::DEBUG_OUTPUT); + bitStream.Write(data.size()); + bitStream.Write(data); + SEND_PACKET; +} diff --git a/dNet/WorldPackets.h b/dNet/WorldPackets.h index 0d5de079..0081623e 100644 --- a/dNet/WorldPackets.h +++ b/dNet/WorldPackets.h @@ -10,6 +10,19 @@ struct SystemAddress; enum class eGameMasterLevel : uint8_t; enum class eCharacterCreationResponse : uint8_t; enum class eRenameResponse : uint8_t; +namespace RakNet { + class BitStream; +}; + +struct HTTPMonitorInfo { + uint16_t port = 80; + bool openWeb = false; + bool supportsSum = false; + bool supportsDetail = false; + bool supportsWho = false; + bool supportsObjects = false; + void Serialize(RakNet::BitStream &bitstream) const; +}; namespace WorldPackets { void SendLoadStaticZone(const SystemAddress& sysAddr, float x, float y, float z, uint32_t checksum, LWOZONEID zone); @@ -21,6 +34,8 @@ namespace WorldPackets { void SendCreateCharacter(const SystemAddress& sysAddr, int64_t reputation, LWOOBJID player, const std::string& xmlData, const std::u16string& username, eGameMasterLevel gm); void SendChatModerationResponse(const SystemAddress& sysAddr, bool requestAccepted, uint32_t requestID, const std::string& receiver, std::vector> unacceptedItems); void SendGMLevelChange(const SystemAddress& sysAddr, bool success, eGameMasterLevel highestLevel, eGameMasterLevel prevLevel, eGameMasterLevel newLevel); + void SendHTTPMonitorInfo(const SystemAddress& sysAddr, const HTTPMonitorInfo& info); + void SendDebugOuput(const SystemAddress& sysAddr, const std::string& data); } #endif // WORLDPACKETS_H diff --git a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp index 40b248f5..b64bb7a8 100644 --- a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp +++ b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp @@ -15,6 +15,7 @@ #include "SkillComponent.h" #include "eReplicaComponentType.h" #include "RenderComponent.h" +#include "PlayerManager.h" #include @@ -53,11 +54,13 @@ void BossSpiderQueenEnemyServer::OnStartup(Entity* self) { void BossSpiderQueenEnemyServer::OnDie(Entity* self, Entity* killer) { if (Game::zoneManager->GetZoneID().GetMapID() == instanceZoneID && killer) { - auto* missionComponent = killer->GetComponent(); - if (missionComponent == nullptr) - return; + for (const auto& player : PlayerManager::GetAllPlayers()) { + auto* missionComponent = player->GetComponent(); + if (missionComponent == nullptr) + return; - missionComponent->CompleteMission(instanceMissionID); + missionComponent->CompleteMission(instanceMissionID); + } } // There is suppose to be a 0.1 second delay here but that may be admitted? diff --git a/dScripts/02_server/Map/FV/Racing/CMakeLists.txt b/dScripts/02_server/Map/FV/Racing/CMakeLists.txt index 89536b67..9b2108c8 100644 --- a/dScripts/02_server/Map/FV/Racing/CMakeLists.txt +++ b/dScripts/02_server/Map/FV/Racing/CMakeLists.txt @@ -1,3 +1,6 @@ set(DSCRIPTS_SOURCES_02_SERVER_MAP_FV_RACING + "RaceFireballs.cpp" "RaceMaelstromGeiser.cpp" + "RaceShipLapColumnsServer.cpp" + "FvRacingColumns.cpp" PARENT_SCOPE) diff --git a/dScripts/02_server/Map/FV/Racing/FvRacingColumns.cpp b/dScripts/02_server/Map/FV/Racing/FvRacingColumns.cpp new file mode 100644 index 00000000..a3e3dd4a --- /dev/null +++ b/dScripts/02_server/Map/FV/Racing/FvRacingColumns.cpp @@ -0,0 +1,15 @@ +#include "FvRacingColumns.h" +#include "MovingPlatformComponent.h" + +void FvRacingColumns::OnStartup(Entity* self) { + auto* movingPlatformComponent = self->GetComponent(); + if (!movingPlatformComponent) return; + + movingPlatformComponent->StopPathing(); + movingPlatformComponent->SetSerialized(true); + int32_t pathStart = 0; + if (self->HasVar(u"attached_path_start")) { + pathStart = self->GetVar(u"attached_path_start"); + } + movingPlatformComponent->WarpToWaypoint(pathStart); +} diff --git a/dScripts/02_server/Map/FV/Racing/FvRacingColumns.h b/dScripts/02_server/Map/FV/Racing/FvRacingColumns.h new file mode 100644 index 00000000..f4555693 --- /dev/null +++ b/dScripts/02_server/Map/FV/Racing/FvRacingColumns.h @@ -0,0 +1,6 @@ +#include "CppScripts.h" + +class FvRacingColumns : public CppScripts::Script { +public: + void OnStartup(Entity* self) override; +}; diff --git a/dScripts/02_server/Map/FV/Racing/RaceFireballs.cpp b/dScripts/02_server/Map/FV/Racing/RaceFireballs.cpp new file mode 100644 index 00000000..fb0f1272 --- /dev/null +++ b/dScripts/02_server/Map/FV/Racing/RaceFireballs.cpp @@ -0,0 +1,15 @@ +#include "RaceFireballs.h" +#include "SkillComponent.h" + +void RaceFireballs::OnStartup(Entity* self) { + self->AddTimer("fire", GeneralUtils::GenerateRandomNumber(3.0f, 10.0f)); +} + +void RaceFireballs::OnTimerDone(Entity* self, std::string timerName) { + if (timerName == "fire") { + auto* skillComponent = self->GetComponent(); + if (skillComponent) skillComponent->CastSkill(894); + self->AddTimer("fire", GeneralUtils::GenerateRandomNumber(3.0f, 10.0f)); + + } +} diff --git a/dScripts/02_server/Map/FV/Racing/RaceFireballs.h b/dScripts/02_server/Map/FV/Racing/RaceFireballs.h new file mode 100644 index 00000000..e96286ae --- /dev/null +++ b/dScripts/02_server/Map/FV/Racing/RaceFireballs.h @@ -0,0 +1,9 @@ +#pragma once +#include "CppScripts.h" + +class RaceFireballs : public CppScripts::Script +{ +public: + void OnStartup(Entity* self) override; + void OnTimerDone(Entity* self, std::string timerName) override; +}; diff --git a/dScripts/02_server/Map/FV/Racing/RaceShipLapColumnsServer.cpp b/dScripts/02_server/Map/FV/Racing/RaceShipLapColumnsServer.cpp new file mode 100644 index 00000000..c0112b6a --- /dev/null +++ b/dScripts/02_server/Map/FV/Racing/RaceShipLapColumnsServer.cpp @@ -0,0 +1,47 @@ +#include "RaceShipLapColumnsServer.h" + +#include "RacingControlComponent.h" +#include "MovingPlatformComponent.h" + +void RaceShipLapColumnsServer::OnStartup(Entity* self) { + self->SetVar(u"Lap2Complete", false); + self->SetVar(u"Lap3Complete", false); +} + +void SetMovingToWaypoint(const int32_t waypointIndex, const std::string group) { + const auto entities = Game::entityManager->GetEntitiesInGroup(group); + if (entities.empty()) return; + + auto* entity = entities[0]; + entity->SetIsGhostingCandidate(false); + + auto* movingPlatfromComponent = entity->GetComponent(); + if (!movingPlatfromComponent) return; + + movingPlatfromComponent->SetSerialized(true); + movingPlatfromComponent->GotoWaypoint(waypointIndex); + Game::entityManager->SerializeEntity(entity); +} + +void RaceShipLapColumnsServer::OnCollisionPhantom(Entity* self, Entity* target) { + if (!target) return; + + const auto racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); + if (racingControllers.empty()) return; + + auto* racingControlComponent = racingControllers[0]->GetComponent(); + if (!racingControlComponent) return; + + const auto* player = racingControlComponent->GetPlayerData(target->GetObjectID()); + if (!player) return; + + if (player->lap == 1 && !self->GetVar(u"Lap2Complete")) { + self->SetVar(u"Lap2Complete", true); + SetMovingToWaypoint(1, "Lap2Column"); + SetMovingToWaypoint(0, "Lap2Ramp"); + } else if (player->lap == 2 && !self->GetVar(u"Lap3Complete")) { + self->SetVar(u"Lap3Complete", true); + SetMovingToWaypoint(1, "Lap3Column"); + SetMovingToWaypoint(0, "Lap3Ramp"); + } +} diff --git a/dScripts/02_server/Map/FV/Racing/RaceShipLapColumnsServer.h b/dScripts/02_server/Map/FV/Racing/RaceShipLapColumnsServer.h new file mode 100644 index 00000000..b8a26825 --- /dev/null +++ b/dScripts/02_server/Map/FV/Racing/RaceShipLapColumnsServer.h @@ -0,0 +1,8 @@ +#pragma once +#include "CppScripts.h" + +class RaceShipLapColumnsServer : public CppScripts::Script { +public: + void OnStartup(Entity* self) override; + void OnCollisionPhantom(Entity* self, Entity* target) override; +}; diff --git a/dScripts/02_server/Objects/AgSurvivalBuffStation.cpp b/dScripts/02_server/Objects/AgSurvivalBuffStation.cpp index 04d9711c..1a7cac11 100644 --- a/dScripts/02_server/Objects/AgSurvivalBuffStation.cpp +++ b/dScripts/02_server/Objects/AgSurvivalBuffStation.cpp @@ -56,10 +56,6 @@ void AgSurvivalBuffStation::OnTimerDone(Entity* self, std::string timerName) { auto member = Game::entityManager->GetEntity(memberID); if (member != nullptr && !member->GetIsDead()) { GameMessages::SendDropClientLoot(member, self->GetObjectID(), powerupToDrop, 0, self->GetPosition()); - } else { - // If player left the team or left early erase them from the team variable. - team.erase(std::find(team.begin(), team.end(), memberID)); - self->SetVar>(u"BuilderTeam", team); } } } diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 9018c3f4..ed0de2ba 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -14,6 +14,7 @@ #include "AgShipPlayerDeathTrigger.h" #include "AgShipPlayerShockServer.h" #include "AgSpaceStuff.h" +#include "AgShipShake.h" #include "AgImagSmashable.h" #include "NpcNpSpacemanBob.h" #include "StoryBoxInteractServer.h" @@ -154,6 +155,11 @@ #include "FvBounceOverWall.h" #include "FvFong.h" #include "FvMaelstromGeyser.h" +#include "FvRaceDragon.h" +#include "FvRacePillarABCServer.h" +#include "FvRacePillarDServer.h" +#include "RaceFireballs.h" +#include "RaceShipLapColumnsServer.h" // FB Scripts #include "AgJetEffectServer.h" @@ -179,6 +185,7 @@ #include "RaceMaelstromGeiser.h" #include "FvRaceSmashEggImagineServer.h" #include "RaceSmashServer.h" +#include "FvRacingColumns.h" // NT Scripts #include "NtSentinelWalkwayServer.h" @@ -321,650 +328,391 @@ #include "LupGenericInteract.h" #include "WblRobotCitizen.h" +#include +#include +#include + namespace { - // This is in the translation unit instead of the header to prevent wierd linker errors - InvalidScript* const InvalidToReturn = new InvalidScript(); - std::map m_Scripts; + // This is in the translation unit instead of the header to prevent weird linker errors + InvalidScript InvalidToReturn; + std::map g_Scripts; + std::map> scriptLoader = { + + //VE / AG + { "scripts\\ai\\AG\\L_AG_SHIP_PLAYER_DEATH_TRIGGER.lua", []() { return new AgShipPlayerDeathTrigger(); } }, + {"scripts\\ai\\NP\\L_NPC_NP_SPACEMAN_BOB.lua", []() { return new NpcNpSpacemanBob(); } }, + {"scripts\\ai\\AG\\L_AG_SPACE_STUFF.lua", []() { return new AgSpaceStuff();} }, + {"scripts\\ai\\AG\\L_AG_SHIP_SHAKE.lua", []() { return new AgShipShake();}}, + {"scripts\\ai\\AG\\L_AG_SHIP_PLAYER_SHOCK_SERVER.lua", []() { return new AgShipPlayerShockServer();} }, + {"scripts\\ai\\AG\\L_AG_IMAG_SMASHABLE.lua", []() { return new AgImagSmashable();} }, + {"scripts\\02_server\\Map\\General\\L_STORY_BOX_INTERACT_SERVER.lua", []() { return new StoryBoxInteractServer();} }, + {"scripts\\02_server\\Map\\General\\L_BINOCULARS.lua", []() { return new Binoculars();} }, + {"scripts\\ai\\WILD\\L_ALL_CRATE_CHICKEN.lua", []() { return new AllCrateChicken();} }, + // Broken? (below) + {"scripts\\ai\\NS\\WH\\L_ROCKHYDRANT_SMASHABLE.lua", []() { return new RockHydrantSmashable();} }, + {"scripts\\02_server\\Map\\SS\\L_SS_MODULAR_BUILD_SERVER.lua", []() { return new SsModularBuildServer();} }, + {"scripts\\02_server\\Map\\Property\\AG_Small\\L_ZONE_AG_PROPERTY.lua", []() { return new ZoneAgProperty();} }, + // this is done in Entity.cpp, not needed for our implementation (below) + {"scripts\\02_server\\Map\\General\\L_POI_MISSION.lua", []() { return new InvalidScript();} }, + {"scripts\\02_server\\Map\\General\\L_TOUCH_MISSION_UPDATE_SERVER.lua", []() { return new TouchMissionUpdateServer();} }, + {"scripts\\ai\\AG\\L_ACT_SHARK_PLAYER_DEATH_TRIGGER.lua", []() { return new ActSharkPlayerDeathTrigger();} }, + {"scripts\\02_server\\Enemy\\General\\L_BASE_ENEMY_MECH.lua", []() { return new BaseEnemyMech();} }, + {"scripts\\zone\\AG\\L_ZONE_AG_SURVIVAL.lua", []() { return new ZoneAgSurvival();} }, + {"scripts\\02_server\\Objects\\L_BUFF_STATION_SERVER.lua", []() { return new AgSurvivalBuffStation();} }, + {"scripts\\ai\\AG\\L_AG_BUS_DOOR.lua", []() { return new AgBusDoor();} }, + {"scripts\\02_server\\Equipment\\L_MAESTROM_EXTRACTICATOR_SERVER.lua", []() { return new MaestromExtracticatorServer();} }, + {"scripts\\02_server\\Map\\AG\\L_AG_CAGED_BRICKS_SERVER.lua", []() { return new AgCagedBricksServer();} }, + {"scripts\\02_server\\Map\\AG\\L_NPC_WISP_SERVER.lua", []() { return new NpcWispServer();} }, + {"scripts\\02_server\\Map\\AG\\L_NPC_EPSILON_SERVER.lua", []() { return new NpcEpsilonServer();} }, + {"scripts\\ai\\AG\\L_AG_TURRET.lua", []() {return new AgTurret();}}, + {"scripts\\ai\\AG\\L_AG_TURRET_FOR_SHIP.lua", []() { return new AgTurret();}}, + {"scripts\\02_server\\Map\\AG\\L_AG_LASER_SENSOR_SERVER.lua", []() {return new AgLaserSensorServer();}}, + {"scripts\\02_server\\Map\\AG\\L_AG_MONUMENT_LASER_SERVER.lua", []() {return new AgMonumentLaserServer();}}, + {"scripts\\ai\\AG\\L_AG_FANS.lua", []() {return new AgFans();}}, + {"scripts\\02_server\\Map\\AG\\L_AG_MONUMENT_BIRDS.lua", []() {return new AgMonumentBirds();}}, + {"scripts\\02_server\\Map\\AG\\L_REMOVE_RENTAL_GEAR.lua", []() {return new RemoveRentalGear();}}, + {"scripts\\02_server\\Map\\AG\\L_NPC_NJ_ASSISTANT_SERVER.lua", []() {return new NpcNjAssistantServer();}}, + {"scripts\\ai\\AG\\L_AG_SALUTING_NPCS.lua", []() {return new AgSalutingNpcs();}}, + {"scripts\\ai\\AG\\L_AG_JET_EFFECT_SERVER.lua", []() {return new AgJetEffectServer();}}, + {"scripts\\02_server\\Enemy\\AG\\L_BOSS_SPIDER_QUEEN_ENEMY_SERVER.lua", []() {return new BossSpiderQueenEnemyServer();}}, + {"scripts\\02_server\\Map\\Property\\AG_Small\\L_ENEMY_SPIDER_SPAWNER.lua", []() {return new EnemySpiderSpawner();}}, + {"scripts/02_server/Map/Property/AG_Small/L_ENEMY_SPIDER_SPAWNER.lua", []() {return new EnemySpiderSpawner();}}, + {"scripts\\ai\\AG\\L_AG_QB_Elevator.lua", []() {return new AgQbElevator();}}, + {"scripts\\ai\\PROPERTY\\AG\\L_AG_PROP_GUARD.lua", []() {return new AgPropGuard();}}, + {"scripts\\02_server\\Map\\AG\\L_AG_BUGSPRAYER.lua", []() {return new AgBugsprayer();}}, + {"scripts\\02_server\\Map\\AG\\L_NPC_AG_COURSE_STARTER.lua", []() {return new NpcAgCourseStarter();}}, + {"scripts\\02_server\\Map\\AG\\L__AG_MONUMENT_RACE_GOAL.lua", []() {return new AgMonumentRaceGoal();}}, + {"scripts\\02_server\\Map\\AG\\L__AG_MONUMENT_RACE_CANCEL.lua", []() {return new AgMonumentRaceCancel();}}, + {"scripts\\02_server\\Map\\AG_Spider_Queen\\L_ZONE_AG_SPIDER_QUEEN.lua", []() {return new ZoneAgSpiderQueen();}}, + {"scripts\\02_server\\Map\\AG_Spider_Queen\\L_SPIDER_BOSS_TREASURE_CHEST_SERVER.lua", []() {return new SpiderBossTreasureChestServer();}}, + {"scripts\\02_server\\Map\\AG\\L_NPC_COWBOY_SERVER.lua", []() {return new NpcCowboyServer();}}, + {"scripts\\02_server\\Map\\Property\\AG_Med\\L_ZONE_AG_MED_PROPERTY.lua", []() {return new ZoneAgMedProperty();}}, + {"scripts\\ai\\AG\\L_AG_STROMBIE_PROPERTY.lua", []() {return new AgStromlingProperty();}}, + {"scripts\\ai\\AG\\L_AG_DARKLING_MECH.lua", []() {return new BaseEnemyMech();}}, + {"scripts\\ai\\AG\\L_AG_DARK_SPIDERLING.lua", []() {return new AgDarkSpiderling();}}, + {"scripts\\ai\\PROPERTY\\L_PROP_GUARDS.lua", []() {return new AgPropguards();}}, + {"scripts\\ai\\PROPERTY\\L_PROPERTY_FX_DAMAGE.lua", []() {return new PropertyFXDamage();}}, + {"scripts\\02_server\\Map\\AG\\L_NPC_PIRATE_SERVER.lua", []() {return new NpcPirateServer();}}, + {"scripts\\ai\\AG\\L_AG_PICNIC_BLANKET.lua", []() {return new AgPicnicBlanket();}}, + {"scripts\\02_server\\Map\\Property\\L_PROPERTY_BANK_INTERACT_SERVER.lua", []() {return new PropertyBankInteract();}}, + {"scripts\\02_server\\Enemy\\VE\\L_VE_MECH.lua", []() {return new VeMech();}}, + {"scripts\\02_server\\Map\\VE\\L_MISSION_CONSOLE_SERVER.lua", []() {return new VeMissionConsole();}}, + {"scripts\\02_server\\Map\\VE\\L_EPSILON_SERVER.lua", []() {return new VeEpsilonServer();}}, + + //NS + {"scripts\\ai\\NS\\L_NS_MODULAR_BUILD.lua", []() {return new NsModularBuild();}}, + {"scripts\\ai\\NS\\L_NS_GET_FACTION_MISSION_SERVER.lua", []() {return new NsGetFactionMissionServer();}}, + {"scripts\\ai\\NS\\L_NS_QB_IMAGINATION_STATUE.lua", []() {return new NsQbImaginationStatue();}}, + {"scripts\\02_server\\Map\\NS\\CONCERT_CHOICEBUILD_MANAGER_SERVER.lua", []() {return new NsConcertChoiceBuildManager();}}, + {"scripts\\ai\\NS\\L_NS_CONCERT_CHOICEBUILD.lua", []() {return new NsConcertChoiceBuild();}}, + {"scripts\\ai\\NS\\L_NS_CONCERT_QUICKBUILD.lua", []() {return new NsConcertQuickBuild();}}, + {"scripts\\ai\\AG\\L_AG_STAGE_PLATFORMS.lua", []() {return new AgStagePlatforms();}}, + {"scripts\\ai\\NS\\L_NS_CONCERT_INSTRUMENT_QB.lua", []() {return new NsConcertInstrument();}}, + {"scripts\\ai\\NS\\L_NS_JONNY_FLAG_MISSION_SERVER.lua", []() {return new NsJohnnyMissionServer();}}, + {"scripts\\02_server\\Objects\\L_STINKY_FISH_TARGET.lua", []() {return new StinkyFishTarget();}}, + {"scripts\\zone\\PROPERTY\\NS\\L_ZONE_NS_PROPERTY.lua", []() {return new ZoneNsProperty();}}, + {"scripts\\02_server\\Map\\Property\\NS_Med\\L_ZONE_NS_MED_PROPERTY.lua", []() {return new ZoneNsMedProperty();}}, + {"scripts\\02_server\\Map\\NS\\L_NS_TOKEN_CONSOLE_SERVER.lua", []() {return new NsTokenConsoleServer();}}, + {"scripts\\02_server\\Map\\NS\\L_NS_LUP_TELEPORT.lua", []() {return new NsLupTeleport();}}, + {"scripts\\02_server\\Map\\NS\\Waves\\L_ZONE_NS_WAVES.lua", []() {return new ZoneNsWaves();}}, + {"scripts\\02_server\\Enemy\\Waves\\L_WAVES_BOSS_HAMMERLING_ENEMY_SERVER.lua", []() {return new WaveBossHammerling();}}, + {"scripts\\02_server\\Enemy\\Waves\\L_WAVES_BOSS_APE_ENEMY_SERVER.lua", []() {return new WaveBossApe();}}, + {"scripts\\02_server\\Enemy\\Waves\\L_WAVES_BOSS_DARK_SPIDERLING_ENEMY_SERVER.lua", []() {return new WaveBossSpiderling();}}, + {"scripts\\02_server\\Enemy\\Waves\\L_WAVES_BOSS_HORESEMEN_ENEMY_SERVER.lua", []() {return new WaveBossHorsemen();}}, + {"scripts\\02_server\\Minigame\\General\\L_MINIGAME_TREASURE_CHEST_SERVER.lua", []() {return new MinigameTreasureChestServer();}}, + {"scripts\\02_server\\Map\\NS\\L_NS_LEGO_CLUB_DOOR.lua", []() {return new NsLegoClubDoor();}}, + {"scripts/ai/NS/L_CL_RING.lua", []() {return new ClRing();}}, + {"scripts\\ai\\WILD\\L_WILD_AMBIENTS.lua", []() {return new WildAmbients();}}, + {"scripts\\ai\\NS\\NS_PP_01\\L_NS_PP_01_TELEPORT.lua", []() {return new PropertyDeathPlane();}}, + {"scripts\\02_server\\Map\\General\\L_QB_SPAWNER.lua", []() {return new QbSpawner();}}, + {"scripts\\ai\\AG\\L_AG_QB_Wall.lua", []() {return new AgQbWall();}}, + + //GF + {"scripts\\02_server\\Map\\GF\\L_GF_TORCH.lua", []() {return new GfTikiTorch();}}, + {"scripts\\ai\\GF\\L_SPECIAL_FIREPIT.lua", []() {return new GfCampfire();}}, + {"scripts\\ai\\GF\\L_GF_ORGAN.lua", []() {return new GfOrgan();}}, + {"scripts\\ai\\GF\\L_GF_BANANA.lua", []() {return new GfBanana();}}, + {"scripts\\ai\\GF\\L_GF_BANANA_CLUSTER.lua", []() {return new GfBananaCluster();}}, + {"scripts/ai/GF/L_GF_JAILKEEP_MISSION.lua", []() {return new GfJailkeepMission();}}, + {"scripts\\ai\\GF\\L_TRIGGER_AMBUSH.lua", []() {return new TriggerAmbush();}}, + {"scripts\\02_server\\Map\\GF\\L_GF_CAPTAINS_CANNON.lua", []() {return new GfCaptainsCannon();}}, + {"scripts\\02_server\\Map\\GF\\L_MAST_TELEPORT.lua", []() {return new MastTeleport();}}, + {"scripts\\ai\\GF\\L_GF_JAIL_WALLS.lua", []() {return new GfJailWalls();}}, + {"scripts\\02_server\\Map\\General\\L_QB_ENEMY_STUNNER.lua", []() {return new QbEnemyStunner();}}, + //Technically also used once in AG (below) + {"scripts\\ai\\GF\\L_GF_PET_DIG_BUILD.lua", []() {return new PetDigBuild();}}, + {"scripts\\02_server\\Map\\GF\\L_SPAWN_LION_SERVER.lua", []() {return new SpawnLionServer();}}, + {"scripts\\02_server\\Enemy\\General\\L_BASE_ENEMY_APE.lua", []() {return new BaseEnemyApe();}}, + {"scripts\\02_server\\Enemy\\General\\L_GF_APE_SMASHING_QB.lua", []() {return new GfApeSmashingQB();}}, + {"scripts\\zone\\PROPERTY\\GF\\L_ZONE_GF_PROPERTY.lua", []() {return new ZoneGfProperty();}}, + {"scripts\\ai\\GF\\L_GF_ARCHWAY.lua", []() {return new GfArchway();}}, + {"scripts\\ai\\GF\\L_GF_MAELSTROM_GEYSER.lua", []() {return new GfMaelstromGeyser();}}, + {"scripts\\ai\\GF\\L_PIRATE_REP.lua", []() {return new PirateRep();}}, + {"scripts\\ai\\GF\\L_GF_PARROT_CRASH.lua", []() {return new GfParrotCrash();}}, + + //SG + {"scripts\\ai\\MINIGAME\\SG_GF\\SERVER\\SG_CANNON.lua", []() {return new SGCannon();}}, + {"scripts\\ai\\MINIGAME\\SG_GF\\L_ZONE_SG_SERVER.lua", []() {return new ZoneSGServer();}}, + + //PR + {"scripts\\client\\ai\\PR\\L_PR_WHISTLE.lua", []() {return new PrWhistle();}}, + {"scripts\\02_server\\Map\\PR\\L_PR_SEAGULL_FLY.lua", []() {return new PrSeagullFly();}}, + {"scripts\\ai\\PETS\\L_HYDRANT_SMASHABLE.lua", []() {return new HydrantSmashable();}}, + {"scripts\\02_server\\map\\PR\\L_HYDRANT_BROKEN.lua", []() {return new HydrantBroken();}}, + {"scripts\\02_server\\Map\\General\\PET_DIG_SERVER.lua", []() {return new PetDigServer();}}, + {"scripts\\02_server\\Map\\AM\\L_SKELETON_DRAGON_PET_DIG_SERVER.lua", []() {return new PetDigServer();}}, + //{"scripts\\02_server\\Map\\AM\\L_SKELETON_DRAGON_PET_DIG_SERVER.lua", [](){return new PetDigServer();}}, + {"scripts\\client\\ai\\PR\\L_CRAB_SERVER.lua", []() {return new CrabServer();}}, + {"scripts\\02_server\\Pets\\L_PET_FROM_DIG_SERVER.lua", []() {return new PetFromDigServer();}}, + {"scripts\\02_server\\Pets\\L_PET_FROM_OBJECT_SERVER.lua", []() {return new PetFromObjectServer();}}, + {"scripts\\02_server\\Pets\\L_DAMAGING_PET.lua", []() {return new DamagingPets();}}, + {"scripts\\02_server\\Map\\PR\\L_SPAWN_GRYPHON_SERVER.lua", []() {return new SpawnGryphonServer();}}, + + //FV + {"scripts\\02_server\\Map\\FV\\L_ACT_CANDLE.lua", []() {return new FvCandle();}}, + {"scripts\\02_server\\Map\\FV\\L_ENEMY_RONIN_SPAWNER.lua", []() {return new EnemyRoninSpawner();}}, + {"scripts\\02_server\\Enemy\\FV\\L_FV_MAELSTROM_CAVALRY.lua", []() {return new FvMaelstromCavalry();}}, + {"scripts\\ai\\FV\\L_ACT_NINJA_TURRET_1.lua", []() {return new ActNinjaTurret();}}, + {"scripts\\02_server\\Map\\FV\\L_FV_HORSEMEN_TRIGGER.lua", []() {return new FvHorsemenTrigger();}}, + {"scripts\\ai\\FV\\L_FV_FLYING_CREVICE_DRAGON.lua", []() {return new FvFlyingCreviceDragon();}}, + {"scripts\\02_server\\Enemy\\FV\\L_FV_MAELSTROM_DRAGON.lua", []() {return new FvMaelstromDragon();}}, + {"scripts\\ai\\FV\\L_FV_DRAGON_SMASHING_GOLEM_QB.lua", []() {return new FvDragonSmashingGolemQb();}}, + {"scripts\\02_server\\Enemy\\General\\L_TREASURE_CHEST_DRAGON_SERVER.lua", []() {return new TreasureChestDragonServer();}}, + {"scripts\\ai\\GENERAL\\L_INSTANCE_EXIT_TRANSFER_PLAYER_TO_LAST_NON_INSTANCE.lua", []() {return new InstanceExitTransferPlayerToLastNonInstance();}}, + {"scripts\\ai\\FV\\L_NPC_FREE_GF_NINJAS.lua", []() {return new FvFreeGfNinjas();}}, + {"scripts\\ai\\FV\\L_FV_PANDA_SPAWNER_SERVER.lua", []() {return new FvPandaSpawnerServer();}}, + {"scripts\\ai\\FV\\L_FV_PANDA_SERVER.lua", []() {return new FvPandaServer();}}, + {"scripts\\zone\\PROPERTY\\FV\\L_ZONE_FV_PROPERTY.lua", []() {return new ZoneFvProperty();}}, + {"scripts\\ai\\FV\\L_FV_BRICK_PUZZLE_SERVER.lua", []() {return new FvBrickPuzzleServer();}}, + {"scripts\\ai\\FV\\L_FV_CONSOLE_LEFT_QUICKBUILD.lua", []() {return new FvConsoleLeftQuickbuild();}}, + {"scripts\\ai\\FV\\L_FV_CONSOLE_RIGHT_QUICKBUILD.lua", []() {return new FvConsoleRightQuickbuild();}}, + {"scripts\\ai\\FV\\L_FV_FACILITY_BRICK.lua", []() {return new FvFacilityBrick();}}, + {"scripts\\ai\\FV\\L_FV_FACILITY_PIPES.lua", []() {return new FvFacilityPipes();}}, + {"scripts\\02_server\\Map\\FV\\L_IMG_BRICK_CONSOLE_QB.lua", []() {return new ImgBrickConsoleQB();}}, + {"scripts\\ai\\FV\\L_ACT_PARADOX_PIPE_FIX.lua", []() {return new ActParadoxPipeFix();}}, + {"scripts\\ai\\FV\\L_FV_NINJA_GUARDS.lua", []() {return new FvNinjaGuard();}}, + {"scripts\\ai\\FV\\L_ACT_PASS_THROUGH_WALL.lua", []() {return new FvPassThroughWall();}}, + {"scripts\\ai\\FV\\L_ACT_BOUNCE_OVER_WALL.lua", []() {return new FvBounceOverWall();}}, + {"scripts\\02_server\\Map\\FV\\L_NPC_FONG.lua", []() {return new FvFong();}}, + {"scripts\\ai\\FV\\L_FV_MAELSTROM_GEYSER.lua", []() {return new FvMaelstromGeyser();}}, + {"scripts\\02_server\\Map\\FV\\Racing\\RACE_SHIP_LAP_COLUMNS_SERVER.lua", []() {return new RaceShipLapColumnsServer();}}, + + //yes we know the lap numbers dont match the file name or anim. Thats what they desgined it as. + {"scripts\\ai\\RACING\\OBJECTS\\FV_RACE_DRAGON_LAP1_SERVER.lua", []() {return new FvRaceDragon("lap_01", 2);}}, + {"scripts\\ai\\RACING\\OBJECTS\\FV_RACE_DRAGON_LAP2_SERVER.lua", []() {return new FvRaceDragon("lap_02", 0);}}, + {"scripts\\ai\\RACING\\OBJECTS\\FV_RACE_DRAGON_LAP3_SERVER.lua", []() {return new FvRaceDragon("lap_03", 1);}}, + {"scripts\\ai\\RACING\\OBJECTS\\FV_RACE_PILLAR_ABC_SERVER.lua", []() {return new FvRacePillarABCServer();}}, + {"scripts\\ai\\RACING\\OBJECTS\\FV_RACE_PILLAR_D_SERVER.lua", []() {return new FvRacePillarDServer();}}, + {"scripts\\02_server\\Map\\FV\\Racing\\RACE_FIREBALLS.lua", []() {return new RaceFireballs();}}, + + //Misc. + {"scripts\\02_server\\Map\\General\\L_EXPLODING_ASSET.lua", []() {return new ExplodingAsset();}}, + {"scripts\\02_server\\Map\\General\\L_WISHING_WELL_SERVER.lua", []() {return new WishingWellServer();}}, + {"scripts\\ai\\ACT\\L_ACT_PLAYER_DEATH_TRIGGER.lua", []() {return new ActPlayerDeathTrigger();}}, + {"scripts\\02_server\\Map\\General\\L_GROWING_FLOWER_SERVER.lua", []() {return new GrowingFlower();}}, + {"scripts\\02_server\\Map\\General\\L_TOKEN_CONSOLE_SERVER.lua", []() {return new TokenConsoleServer();}}, + {"scripts\\ai\\ACT\\FootRace\\L_ACT_BASE_FOOT_RACE.lua", []() {return new BaseFootRaceManager();}}, + {"scripts\\02_server\\Map\\General\\L_PROP_PLATFORM.lua", []() {return new PropertyPlatform();}}, + {"scripts\\02_server\\Map\\VE\\L_VE_BRICKSAMPLE_SERVER.lua", []() {return new VeBricksampleServer();}}, + {"scripts\\02_server\\Map\\General\\L_MAIL_BOX_SERVER.lua", []() {return new MailBoxServer();}}, + {"scripts\\ai\\ACT\\L_ACT_MINE.lua", []() {return new ActMine();}}, + {"scripts\\02_server\\Map\\AM\\L_WANDERING_VENDOR.lua", []() {return new WanderingVendor();}}, + + //Racing + {"scripts\\ai\\RACING\\OBJECTS\\RACE_IMAGINE_CRATE_SERVER.lua", []() {return new RaceImagineCrateServer();}}, + {"scripts\\ai\\ACT\\L_ACT_VEHICLE_DEATH_TRIGGER.lua", []() {return new ActVehicleDeathTrigger();}}, + {"scripts\\ai\\RACING\\OBJECTS\\RACE_IMAGINE_POWERUP.lua", []() {return new RaceImaginePowerup();}}, + {"scripts\\02_server\\Map\\FV\\Racing\\RACE_MAELSTROM_GEISER.lua", []() {return new RaceMaelstromGeiser();}}, + {"scripts\\ai\\RACING\\OBJECTS\\FV_RACE_SMASH_EGG_IMAGINE_SERVER.lua", []() {return new FvRaceSmashEggImagineServer();}}, + {"scripts\\02_server\\Map\\FV\\Racing\\FV_RACING_COLUMNS.lua", []() {return new FvRacingColumns();}}, + {"scripts\\ai\\RACING\\OBJECTS\\RACE_SMASH_SERVER.lua", []() {return new RaceSmashServer();}}, + + //NT + {"scripts\\02_server\\Map\\NT\\L_NT_SENTINELWALKWAY_SERVER.lua", []() {return new NtSentinelWalkwayServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_PARADOXTELE_SERVER.lua", []() {return new NtParadoxTeleServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_DARKITECT_REVEAL_SERVER.lua", []() {return new NtDarkitectRevealServer();}}, + {"scripts\\02_server\\Map\\General\\L_BANK_INTERACT_SERVER.lua", []() {return new BankInteractServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_VENTURESPEEDPAD_SERVER.lua", []() {return new NtVentureSpeedPadServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_VENTURE_CANNON_SERVER.lua", []() {return new NtVentureCannonServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_COMBAT_CHALLENGE_SERVER.lua", []() {return new NtCombatChallengeServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_COMBAT_CHALLENGE_DUMMY.lua", []() {return new NtCombatChallengeDummy();}}, + {"scripts\\02_server\\Map\\NT\\\\L_NT_COMBAT_EXPLODING_TARGET.lua", []() {return new NtCombatChallengeExplodingDummy();}}, + {"scripts\\02_server\\Map\\General\\L_BASE_INTERACT_DROP_LOOT_SERVER.lua", []() {return new BaseInteractDropLootServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_ASSEMBLYTUBE_SERVER.lua", []() {return new NtAssemblyTubeServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_PARADOX_PANEL_SERVER.lua", []() {return new NtParadoxPanelServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_IMAG_BEAM_BUFFER.lua", []() {return new NtImagBeamBuffer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_BEAM_IMAGINATION_COLLECTORS.lua", []() {return new NtBeamImaginationCollectors();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_DIRT_CLOUD_SERVER.lua", []() {return new NtDirtCloudServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_CONSOLE_TELEPORT_SERVER.lua", []() {return new NtConsoleTeleportServer();}}, + {"scripts\\02_server\\Map\\NT\\L_SPAWN_STEGO_SERVER.lua", []() {return new SpawnStegoServer();}}, + {"scripts\\02_server\\Map\\NT\\L_SPAWN_SABERCAT_SERVER.lua", []() {return new SpawnSaberCatServer();}}, + {"scripts\\02_server\\Map\\NT\\L_SPAWN_SHRAKE_SERVER.lua", []() {return new SpawnShrakeServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_DUKE_SERVER.lua", []() {return new NtDukeServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_HAEL_SERVER.lua", []() {return new NtHaelServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_FACTION_SPY_SERVER.lua", []() {return new NtFactionSpyServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_OVERBUILD_SERVER.lua", []() {return new NtOverbuildServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_VANDA_SERVER.lua", []() {return new NtVandaServer();}}, + {"scripts\\02_server\\Map\\General\\L_FORCE_VOLUME_SERVER.lua", []() {return new ForceVolumeServer();}}, + {"scripts\\02_server\\Map\\General\\L_FRICTION_VOLUME_SERVER.lua", []() {return new FrictionVolumeServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_XRAY_SERVER.lua", []() {return new NtXRayServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_SLEEPING_GUARD.lua", []() {return new NtSleepingGuard();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_IMAGIMETER_VISIBILITY_SERVER.lua", []() {return new NTImagimeterVisibility();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_PIPE_VISIBILITY_SERVER.lua", []() {return new NTPipeVisibilityServer();}}, + {"scripts\\ai\\MINIGAME\\Objects\\MINIGAME_BLUE_MARK.lua", []() {return new MinigameBlueMark();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_NAOMI_BREADCRUMB_SERVER.lua", []() {return new NtNaomiBreadcrumbServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_NAOMI_DIRT_SERVER.lua", []() {return new NTNaomiDirtServer();}}, + + //AM Crux + {"scripts\\02_server\\Map\\AM\\L_AM_CONSOLE_TELEPORT_SERVER.lua", []() {return new AmConsoleTeleportServer();}}, + {"scripts\\02_server\\Map\\AM\\L_RANDOM_SPAWNER_FIN.lua", []() {return new RandomSpawnerFin();}}, + {"scripts\\02_server\\Map\\AM\\L_RANDOM_SPAWNER_PIT.lua", []() {return new RandomSpawnerPit();}}, + {"scripts\\02_server\\Map\\AM\\L_RANDOM_SPAWNER_STR.lua", []() {return new RandomSpawnerStr();}}, + {"scripts\\02_server\\Map\\AM\\L_RANDOM_SPAWNER_ZIP.lua", []() {return new RandomSpawnerZip();}}, + {"scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_MECH.lua", []() {return new AmDarklingMech();}}, + {"scripts\\02_server\\Map\\AM\\L_BRIDGE.lua", []() {return new AmBridge();}}, + {"scripts\\02_server\\Map\\AM\\L_DRAW_BRIDGE.lua", []() {return new AmDrawBridge();}}, + {"scripts\\02_server\\Map\\AM\\L_SHIELD_GENERATOR.lua", []() {return new AmShieldGenerator();}}, + {"scripts\\02_server\\Map\\AM\\L_SHIELD_GENERATOR_QUICKBUILD.lua", []() {return new AmShieldGeneratorQuickbuild();}}, + {"scripts\\02_server\\Map\\AM\\L_DROPSHIP_COMPUTER.lua", []() {return new AmDropshipComputer();}}, + {"scripts\\02_server\\Map\\AM\\L_SCROLL_READER_SERVER.lua", []() {return new AmScrollReaderServer();}}, + {"scripts\\02_server\\Map\\AM\\L_TEMPLE_SKILL_VOLUME.lua", []() {return new AmTemplateSkillVolume();}}, + {"scripts\\02_server\\Enemy\\General\\L_ENEMY_NJ_BUFF.lua", []() {return new EnemyNjBuff();}}, + {"scripts\\02_server\\Enemy\\AM\\L_AM_SKELETON_ENGINEER.lua", []() {return new AmSkeletonEngineer();}}, + {"scripts\\02_server\\Map\\AM\\L_SKULLKIN_DRILL.lua", []() {return new AmSkullkinDrill();}}, + {"scripts\\02_server\\Map\\AM\\L_SKULLKIN_DRILL_STAND.lua", []() {return new AmSkullkinDrillStand();}}, + {"scripts\\02_server\\Map\\AM\\L_SKULLKIN_TOWER.lua", []() {return new AmSkullkinTower();}}, + {"scripts\\02_server\\Enemy\\AM\\L_AM_NAMED_DARKLING_DRAGON.lua", []() {return new AmDarklingDragon();}}, + {"scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_DRAGON.lua", []() {return new AmDarklingDragon();}}, + {"scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_APE.lua", []() {return new BaseEnemyApe();}}, + {"scripts\\02_server\\Map\\AM\\L_BLUE_X.lua", []() {return new AmBlueX();}}, + {"scripts\\02_server\\Map\\AM\\L_TEAPOT_SERVER.lua", []() {return new AmTeapotServer();}}, + + //Ninjago + {"scripts\\02_server\\Map\\njhub\\L_GARMADON_CELEBRATION_SERVER.lua", []() {return new NjGarmadonCelebration();}}, + {"scripts\\02_server\\Map\\njhub\\L_WU_NPC.lua", []() {return new NjWuNPC();}}, + {"scripts\\02_server\\Map\\njhub\\L_SCROLL_CHEST_SERVER.lua", []() {return new NjScrollChestServer();}}, + {"scripts\\02_server\\Map\\njhub\\L_COLE_NPC.lua", []() {return new NjColeNPC();}}, + {"scripts\\02_server\\Map\\njhub\\L_JAY_MISSION_ITEMS.lua", []() {return new NjJayMissionItems();}}, + {"scripts\\02_server\\Map\\njhub\\L_NPC_MISSION_SPINJITZU_SERVER.lua", []() {return new NjNPCMissionSpinjitzuServer();}}, + {"scripts\\02_server\\Map\\njhub\\L_ENEMY_SKELETON_SPAWNER.lua", []() {return new EnemySkeletonSpawner();}}, + {"scripts\\02_server\\Map\\General\\L_NJ_RAIL_SWITCH.lua", []() {return new NjRailSwitch();}}, + {"scripts\\02_server\\Map\\General\\Ninjago\\L_RAIL_ACTIVATORS_SERVER.lua", []() {return new NjRailActivatorsServer();}}, + {"scripts\\02_server\\Map\\General\\Ninjago\\L_RAIL_POST_SERVER.lua", []() {return new NjRailPostServer();}}, + {"scripts\\02_server\\Map\\General\\Ninjago\\L_ICE_RAIL_ACTIVATOR_SERVER.lua", []() {return new NjIceRailActivator();}}, + {"scripts\\02_server\\Map\\njhub\\L_FALLING_TILE.lua", []() {return new FallingTile();}}, + {"scripts\\02_server\\Enemy\\General\\L_ENEMY_NJ_BUFF_STUN_IMMUNITY.lua", []() {return new EnemyNjBuff();}}, + {"scripts\\02_server\\Map\\njhub\\L_IMAGINATION_SHRINE_SERVER.lua", []() {return new ImaginationShrineServer();}}, + {"scripts\\02_server\\Map\\njhub\\L_LIEUTENANT.lua", []() {return new Lieutenant();}}, + {"scripts\\02_server\\Map\\njhub\\L_RAIN_OF_ARROWS.lua", []() {return new RainOfArrows();}}, + {"scripts\\02_server\\Map\\njhub\\L_CAVE_PRISON_CAGE.lua", []() {return new CavePrisonCage();}}, + {"scripts\\02_server\\Map\\njhub\\boss_instance\\L_MONASTERY_BOSS_INSTANCE_SERVER.lua", []() {return new NjMonastryBossInstance();}}, + {"scripts\\02_server\\Map\\njhub\\L_CATAPULT_BOUNCER_SERVER.lua", []() {return new CatapultBouncerServer();}}, + {"scripts\\02_server\\Map\\njhub\\L_CATAPULT_BASE_SERVER.lua", []() {return new CatapultBaseServer();}}, + {"scripts\\02_server\\Map\\General\\Ninjago\\L_NJHUB_LAVA_PLAYER_DEATH_TRIGGER.lua", []() {return new NjhubLavaPlayerDeathTrigger();}}, + {"scripts\\02_server\\Map\\njhub\\L_MON_CORE_NOOK_DOORS.lua", []() {return new MonCoreNookDoors();}}, + {"scripts\\02_server\\Map\\njhub\\L_MON_CORE_SMASHABLE_DOORS.lua", []() {return new MonCoreSmashableDoors();}}, + {"scripts\\02_server\\Map\\njhub\\L_MON_CORE_SMASHABLE_DOORS.lua", []() {return new MonCoreSmashableDoors();}}, + {"scripts\\02_server\\Map\\njhub\\L_FLAME_JET_SERVER.lua", []() {return new FlameJetServer();}}, + {"scripts\\02_server\\Map\\njhub\\L_BURNING_TILE.lua", []() {return new BurningTile();}}, + {"scripts\\02_server\\Map\\njhub\\L_SPAWN_EARTH_PET_SERVER.lua", []() {return new NjEarthDragonPetServer();}}, + {"scripts\\02_server\\Map\\njhub\\L_EARTH_PET_SERVER.lua", []() {return new NjEarthPetServer();}}, + {"scripts\\02_server\\Map\\njhub\\L_DRAGON_EMBLEM_CHEST_SERVER.lua", []() {return new NjDragonEmblemChestServer();}}, + {"scripts\\02_server\\Map\\njhub\\L_NYA_MISSION_ITEMS.lua", []() {return new NjNyaMissionitems();}}, + + //DLU + {"scripts\\02_server\\DLU\\DLUVanityTeleportingObject.lua", []() {return new DLUVanityTeleportingObject();}}, + + //Survival Minigame + {"scripts\\02_server\\Enemy\\Survival\\L_AG_SURVIVAL_STROMBIE.lua", []() {return new AgSurvivalStromling();}}, + {"scripts\\02_server\\Enemy\\Survival\\L_AG_SURVIVAL_DARKLING_MECH.lua", []() {return new AgSurvivalMech();}}, + {"scripts\\02_server\\Enemy\\Survival\\L_AG_SURVIVAL_DARK_SPIDERLING.lua", []() {return new AgSurvivalSpiderling();}}, + + //Scripted Equipment + {"scripts\\EquipmentScripts\\Sunflower.lua", []() {return new Sunflower();}}, + {"scripts/EquipmentScripts/AnvilOfArmor.lua", []() {return new AnvilOfArmor();}}, + {"scripts/EquipmentScripts/FountainOfImagination.lua", []() {return new FountainOfImagination();}}, + {"scripts/EquipmentScripts/CauldronOfLife.lua", []() {return new CauldronOfLife();}}, + {"scripts\\02_server\\Equipment\\L_BOOTYDIG_SERVER.lua", []() {return new BootyDigServer();}}, + {"scripts\\EquipmentScripts\\PersonalFortress.lua", []() {return new PersonalFortress();}}, + {"scripts\\02_server\\Map\\General\\L_PROPERTY_DEVICE.lua", []() {return new PropertyDevice();}}, + {"scripts\\02_server\\Map\\General\\L_IMAG_BACKPACK_HEALS_SERVER.lua", []() {return new ImaginationBackpackHealServer();}}, + {"scripts\\ai\\GENERAL\\L_LEGO_DIE_ROLL.lua", []() {return new LegoDieRoll();}}, + {"scripts\\EquipmentScripts\\BuccaneerValiantShip.lua", []() {return new BuccaneerValiantShip();}}, + {"scripts\\EquipmentScripts\\FireFirstSkillonStartup.lua", []() {return new FireFirstSkillonStartup();}}, + {"scripts\\equipmenttriggers\\gempack.lua", []() {return new GemPack();}}, + {"scripts\\equipmenttriggers\\shardarmor.lua", []() {return new ShardArmor();}}, + {"scripts\\equipmenttriggers\\coilbackpack.lua", []() {return new TeslaPack();}}, + {"scripts\\EquipmentScripts\\stunImmunity.lua", []() {return new StunImmunity();}}, + + //FB + {"scripts\\ai\\NS\\WH\\L_ROCKHYDRANT_BROKEN.lua", []() {return new RockHydrantBroken();}}, + {"scripts\\ai\\NS\\L_NS_WH_FANS.lua", []() {return new WhFans();}}, + + //WBL + {"scripts\\zone\\LUPs\\WBL_generic_zone.lua", []() {return new WblGenericZone();}}, + + //Alpha + {"scripts\\ai\\FV\\L_TRIGGER_GAS.lua", []() {return new TriggerGas();}}, + {"scripts\\ai\\FV\\L_ACT_NINJA_SENSEI.lua", []() {return new ActNinjaSensei();}}, + + //Pickups + {"scripts\\ai\\SPEC\\L_SPECIAL_1_BRONZE-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(1);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_1_GOLD-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(10000);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_1_SILVER-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(100);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_10_BRONZE-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(10);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_10_GOLD-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(100000);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_10_SILVER-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(1000);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_25_BRONZE-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(25);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_25_GOLD-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(250000);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_25_SILVER-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(2500);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_IMAGINE-POWERUP-SPAWNER.lua", []() {return new SpecialPowerupSpawner(13);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_IMAGINE-POWERUP-SPAWNER-2PT.lua", []() {return new SpecialPowerupSpawner(129);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_LIFE-POWERUP-SPAWNER.lua", []() {return new SpecialPowerupSpawner(5);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_ARMOR-POWERUP-SPAWNER.lua", []() {return new SpecialPowerupSpawner(747);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_SPEED_BUFF_SPAWNER.lua", []() {return new SpecialSpeedBuffSpawner();}}, + + //Wild + {"scripts\\ai\\WILD\\L_WILD_GF_RAT.lua", []() {return new WildAndScared();}}, + {"scripts\\ai\\WILD\\L_WILD_GF_SNAIL.lua", []() {return new WildAndScared();}}, + {"scripts\\ai\\WILD\\L_WILD_GF_GLOWBUG.lua", []() {return new WildGfGlowbug();}}, + {"scripts\\ai\\WILD\\L_WILD_AMBIENT_CRAB.lua", []() {return new WildAmbientCrab();}}, + {"scripts\\ai\\WILD\\L_WILD_PANTS.lua", []() {return new WildPants();}}, + {"scripts\\ai\\WILD\\L_WILD_NINJA_BRICKS.lua", []() {return new WildNinjaBricks();}}, + {"scripts\\ai\\WILD\\L_WILD_NINJA_STUDENT.lua", []() {return new WildNinjaStudent();}}, + {"scripts\\ai\\WILD\\L_WILD_NINJA_SENSEI.lua", []() {return new WildNinjaSensei();}}, + {"scripts\\ai\\WILD\\L_LUP_generic_interact.lua", []() {return new LupGenericInteract();}}, + {"scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_RobotCitizenBlue.lua", []() {return new WblRobotCitizen();}}, + {"scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_RobotCitizenGreen.lua", []() {return new WblRobotCitizen();}}, + {"scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_RobotCitizenOrange.lua", []() {return new WblRobotCitizen();}}, + {"scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_RobotCitizenRed.lua", []() {return new WblRobotCitizen();}}, + {"scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_RobotCitizenYellow.lua", []() {return new WblRobotCitizen();}}, + + }; }; CppScripts::Script* const CppScripts::GetScript(Entity* parent, const std::string& scriptName) { - auto itr = m_Scripts.find(scriptName); - if (itr != m_Scripts.end()) { + auto itr = g_Scripts.find(scriptName); + if (itr != g_Scripts.end()) { return itr->second; } - Script* script = InvalidToReturn; + const auto itrTernary = scriptLoader.find(scriptName); + Script* script = itrTernary != scriptLoader.cend() ? itrTernary->second() : &InvalidToReturn; - //VE / AG: - if (scriptName == "scripts\\ai\\AG\\L_AG_SHIP_PLAYER_DEATH_TRIGGER.lua") - script = new AgShipPlayerDeathTrigger(); - else if (scriptName == "scripts\\ai\\NP\\L_NPC_NP_SPACEMAN_BOB.lua") - script = new NpcNpSpacemanBob(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_SPACE_STUFF.lua") // Broken, will (sometimes) display all animations at once on initial login - script = new AgSpaceStuff(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_SHIP_PLAYER_SHOCK_SERVER.lua") - script = new AgShipPlayerShockServer(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_IMAG_SMASHABLE.lua") - script = new AgImagSmashable(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_STORY_BOX_INTERACT_SERVER.lua") - script = new StoryBoxInteractServer(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_BINOCULARS.lua") - script = new Binoculars(); - else if (scriptName == "scripts\\ai\\WILD\\L_ALL_CRATE_CHICKEN.lua") - script = new AllCrateChicken(); - else if (scriptName == "scripts\\ai\\NS\\WH\\L_ROCKHYDRANT_SMASHABLE.lua") - script = new RockHydrantSmashable(); // Broken? - else if (scriptName == "scripts\\02_server\\Map\\SS\\L_SS_MODULAR_BUILD_SERVER.lua") - script = new SsModularBuildServer(); - else if (scriptName == "scripts\\02_server\\Map\\Property\\AG_Small\\L_ZONE_AG_PROPERTY.lua") - script = new ZoneAgProperty(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_POI_MISSION.lua") - script = new InvalidScript(); // this is done in Entity.cpp, not needed for our implementation - else if (scriptName == "scripts\\02_server\\Map\\General\\L_TOUCH_MISSION_UPDATE_SERVER.lua") - script = new TouchMissionUpdateServer(); - else if (scriptName == "scripts\\ai\\AG\\L_ACT_SHARK_PLAYER_DEATH_TRIGGER.lua") - script = new ActSharkPlayerDeathTrigger(); - else if (scriptName == "scripts\\02_server\\Enemy\\General\\L_BASE_ENEMY_MECH.lua") - script = new BaseEnemyMech(); - else if (scriptName == "scripts\\zone\\AG\\L_ZONE_AG_SURVIVAL.lua") - script = new ZoneAgSurvival(); - else if (scriptName == "scripts\\02_server\\Objects\\L_BUFF_STATION_SERVER.lua") - script = new AgSurvivalBuffStation(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_BUS_DOOR.lua") - script = new AgBusDoor(); - else if (scriptName == "scripts\\02_server\\Equipment\\L_MAESTROM_EXTRACTICATOR_SERVER.lua") - script = new MaestromExtracticatorServer(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_AG_CAGED_BRICKS_SERVER.lua") - script = new AgCagedBricksServer(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_NPC_WISP_SERVER.lua") - script = new NpcWispServer(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_NPC_EPSILON_SERVER.lua") - script = new NpcEpsilonServer(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_TURRET.lua" || scriptName == "scripts\\ai\\AG\\L_AG_TURRET_FOR_SHIP.lua") - script = new AgTurret(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_AG_LASER_SENSOR_SERVER.lua") - script = new AgLaserSensorServer(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_AG_MONUMENT_LASER_SERVER.lua") - script = new AgMonumentLaserServer(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_FANS.lua") - script = new AgFans(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_AG_MONUMENT_BIRDS.lua") - script = new AgMonumentBirds(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_REMOVE_RENTAL_GEAR.lua") - script = new RemoveRentalGear(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_NPC_NJ_ASSISTANT_SERVER.lua") - script = new NpcNjAssistantServer(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_SALUTING_NPCS.lua") - script = new AgSalutingNpcs(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_JET_EFFECT_SERVER.lua") - script = new AgJetEffectServer(); - else if (scriptName == "scripts\\02_server\\Enemy\\AG\\L_BOSS_SPIDER_QUEEN_ENEMY_SERVER.lua") - script = new BossSpiderQueenEnemyServer(); - else if (scriptName == "scripts\\02_server\\Map\\Property\\AG_Small\\L_ENEMY_SPIDER_SPAWNER.lua") - script = new EnemySpiderSpawner(); - else if (scriptName == "scripts/02_server/Map/Property/AG_Small/L_ENEMY_SPIDER_SPAWNER.lua") - script = new EnemySpiderSpawner(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_QB_Elevator.lua") - script = new AgQbElevator(); - else if (scriptName == "scripts\\ai\\PROPERTY\\AG\\L_AG_PROP_GUARD.lua") - script = new AgPropGuard(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_AG_BUGSPRAYER.lua") - script = new AgBugsprayer(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_NPC_AG_COURSE_STARTER.lua") - script = new NpcAgCourseStarter(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L__AG_MONUMENT_RACE_GOAL.lua") - script = new AgMonumentRaceGoal(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L__AG_MONUMENT_RACE_CANCEL.lua") - script = new AgMonumentRaceCancel(); - else if (scriptName == "scripts\\02_server\\Map\\AG_Spider_Queen\\L_ZONE_AG_SPIDER_QUEEN.lua") - script = new ZoneAgSpiderQueen(); - else if (scriptName == "scripts\\02_server\\Map\\AG_Spider_Queen\\L_SPIDER_BOSS_TREASURE_CHEST_SERVER.lua") - script = new SpiderBossTreasureChestServer(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_NPC_COWBOY_SERVER.lua") - script = new NpcCowboyServer(); - else if (scriptName == "scripts\\02_server\\Map\\Property\\AG_Med\\L_ZONE_AG_MED_PROPERTY.lua") - script = new ZoneAgMedProperty(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_STROMBIE_PROPERTY.lua") - script = new AgStromlingProperty(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_DARKLING_MECH.lua") - script = new BaseEnemyMech(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_DARK_SPIDERLING.lua") - script = new AgDarkSpiderling(); - else if (scriptName == "scripts\\ai\\PROPERTY\\L_PROP_GUARDS.lua") - script = new AgPropguards(); - else if (scriptName == "scripts\\ai\\PROPERTY\\L_PROPERTY_FX_DAMAGE.lua") - script = new PropertyFXDamage(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_NPC_PIRATE_SERVER.lua") - script = new NpcPirateServer(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_PICNIC_BLANKET.lua") - script = new AgPicnicBlanket(); - else if (scriptName == "scripts\\02_server\\Map\\Property\\L_PROPERTY_BANK_INTERACT_SERVER.lua") - script = new PropertyBankInteract(); - else if (scriptName == "scripts\\02_server\\Enemy\\VE\\L_VE_MECH.lua") - script = new VeMech(); - else if (scriptName == "scripts\\02_server\\Map\\VE\\L_MISSION_CONSOLE_SERVER.lua") - script = new VeMissionConsole(); - else if (scriptName == "scripts\\02_server\\Map\\VE\\L_EPSILON_SERVER.lua") - script = new VeEpsilonServer(); - // Win32 thinks this if chain is too long, let's cut it up and serve it as a three course meal - //NS: - if (scriptName == "scripts\\ai\\NS\\L_NS_MODULAR_BUILD.lua") - script = new NsModularBuild(); - else if (scriptName == "scripts\\ai\\NS\\L_NS_GET_FACTION_MISSION_SERVER.lua") - script = new NsGetFactionMissionServer(); - else if (scriptName == "scripts\\ai\\NS\\L_NS_QB_IMAGINATION_STATUE.lua") - script = new NsQbImaginationStatue(); - else if (scriptName == "scripts\\02_server\\Map\\NS\\CONCERT_CHOICEBUILD_MANAGER_SERVER.lua") - script = new NsConcertChoiceBuildManager(); - else if (scriptName == "scripts\\ai\\NS\\L_NS_CONCERT_CHOICEBUILD.lua") - script = new NsConcertChoiceBuild(); - else if (scriptName == "scripts\\ai\\NS\\L_NS_CONCERT_QUICKBUILD.lua") - script = new NsConcertQuickBuild(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_STAGE_PLATFORMS.lua") - script = new AgStagePlatforms(); - else if (scriptName == "scripts\\ai\\NS\\L_NS_CONCERT_INSTRUMENT_QB.lua") - script = new NsConcertInstrument(); - else if (scriptName == "scripts\\ai\\NS\\L_NS_JONNY_FLAG_MISSION_SERVER.lua") - script = new NsJohnnyMissionServer(); - else if (scriptName == "scripts\\02_server\\Objects\\L_STINKY_FISH_TARGET.lua") - script = new StinkyFishTarget(); - else if (scriptName == "scripts\\zone\\PROPERTY\\NS\\L_ZONE_NS_PROPERTY.lua") - script = new ZoneNsProperty(); - else if (scriptName == "scripts\\02_server\\Map\\Property\\NS_Med\\L_ZONE_NS_MED_PROPERTY.lua") - script = new ZoneNsMedProperty(); - else if (scriptName == "scripts\\02_server\\Map\\NS\\L_NS_TOKEN_CONSOLE_SERVER.lua") - script = new NsTokenConsoleServer(); - else if (scriptName == "scripts\\02_server\\Map\\NS\\L_NS_LUP_TELEPORT.lua") - script = new NsLupTeleport(); - else if (scriptName == "scripts\\02_server\\Map\\NS\\Waves\\L_ZONE_NS_WAVES.lua") - script = new ZoneNsWaves(); - else if (scriptName == "scripts\\02_server\\Enemy\\Waves\\L_WAVES_BOSS_HAMMERLING_ENEMY_SERVER.lua") - script = new WaveBossHammerling(); - else if (scriptName == "scripts\\02_server\\Enemy\\Waves\\L_WAVES_BOSS_APE_ENEMY_SERVER.lua") - script = new WaveBossApe(); - else if (scriptName == "scripts\\02_server\\Enemy\\Waves\\L_WAVES_BOSS_DARK_SPIDERLING_ENEMY_SERVER.lua") - script = new WaveBossSpiderling(); - else if (scriptName == "scripts\\02_server\\Enemy\\Waves\\L_WAVES_BOSS_HORESEMEN_ENEMY_SERVER.lua") - script = new WaveBossHorsemen(); - else if (scriptName == "scripts\\02_server\\Minigame\\General\\L_MINIGAME_TREASURE_CHEST_SERVER.lua") - script = new MinigameTreasureChestServer(); - else if (scriptName == "scripts\\02_server\\Map\\NS\\L_NS_LEGO_CLUB_DOOR.lua") - script = new NsLegoClubDoor(); - else if (scriptName == "scripts/ai/NS/L_CL_RING.lua") - script = new ClRing(); - else if (scriptName == "scripts\\ai\\WILD\\L_WILD_AMBIENTS.lua") - script = new WildAmbients(); - else if (scriptName == "scripts\\ai\\NS\\NS_PP_01\\L_NS_PP_01_TELEPORT.lua") - script = new PropertyDeathPlane(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_QB_SPAWNER.lua") - script = new QbSpawner(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_QB_Wall.lua") - script = new AgQbWall(); - - //GF: - else if (scriptName == "scripts\\02_server\\Map\\GF\\L_GF_TORCH.lua") - script = new GfTikiTorch(); - else if (scriptName == "scripts\\ai\\GF\\L_SPECIAL_FIREPIT.lua") - script = new GfCampfire(); - else if (scriptName == "scripts\\ai\\GF\\L_GF_ORGAN.lua") - script = new GfOrgan(); - else if (scriptName == "scripts\\ai\\GF\\L_GF_BANANA.lua") - script = new GfBanana(); - else if (scriptName == "scripts\\ai\\GF\\L_GF_BANANA_CLUSTER.lua") - script = new GfBananaCluster(); - else if (scriptName == "scripts/ai/GF/L_GF_JAILKEEP_MISSION.lua") - script = new GfJailkeepMission(); - else if (scriptName == "scripts\\ai\\GF\\L_TRIGGER_AMBUSH.lua") - script = new TriggerAmbush(); - else if (scriptName == "scripts\\02_server\\Map\\GF\\L_GF_CAPTAINS_CANNON.lua") - script = new GfCaptainsCannon(); - else if (scriptName == "scripts\\02_server\\Map\\GF\\L_MAST_TELEPORT.lua") - script = new MastTeleport(); - else if (scriptName == "scripts\\ai\\GF\\L_GF_JAIL_WALLS.lua") - script = new GfJailWalls(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_QB_ENEMY_STUNNER.lua") - script = new QbEnemyStunner(); - else if (scriptName == "scripts\\ai\\GF\\L_GF_PET_DIG_BUILD.lua") - script = new PetDigBuild(); // Technically also used once in AG - else if (scriptName == "scripts\\02_server\\Map\\GF\\L_SPAWN_LION_SERVER.lua") - script = new SpawnLionServer(); - else if (scriptName == "scripts\\02_server\\Enemy\\General\\L_BASE_ENEMY_APE.lua") - script = new BaseEnemyApe(); - else if (scriptName == "scripts\\02_server\\Enemy\\General\\L_GF_APE_SMASHING_QB.lua") - script = new GfApeSmashingQB(); - else if (scriptName == "scripts\\zone\\PROPERTY\\GF\\L_ZONE_GF_PROPERTY.lua") - script = new ZoneGfProperty(); - else if (scriptName == "scripts\\ai\\GF\\L_GF_ARCHWAY.lua") - script = new GfArchway(); - else if (scriptName == "scripts\\ai\\GF\\L_GF_MAELSTROM_GEYSER.lua") - script = new GfMaelstromGeyser(); - else if (scriptName == "scripts\\ai\\GF\\L_PIRATE_REP.lua") - script = new PirateRep(); - else if (scriptName == "scripts\\ai\\GF\\L_GF_PARROT_CRASH.lua") - script = new GfParrotCrash(); - - // SG - else if (scriptName == "scripts\\ai\\MINIGAME\\SG_GF\\SERVER\\SG_CANNON.lua") - script = new SGCannon(); - else if (scriptName == "scripts\\ai\\MINIGAME\\SG_GF\\L_ZONE_SG_SERVER.lua") - script = new ZoneSGServer(); - - //PR: - else if (scriptName == "scripts\\client\\ai\\PR\\L_PR_WHISTLE.lua") - script = new PrWhistle(); - if (scriptName == "scripts\\02_server\\Map\\PR\\L_PR_SEAGULL_FLY.lua") - script = new PrSeagullFly(); - else if (scriptName == "scripts\\ai\\PETS\\L_HYDRANT_SMASHABLE.lua") - script = new HydrantSmashable(); - else if (scriptName == "scripts\\02_server\\map\\PR\\L_HYDRANT_BROKEN.lua") - script = new HydrantBroken(); - else if (scriptName == "scripts\\02_server\\Map\\General\\PET_DIG_SERVER.lua" || scriptName == "scripts\\02_server\\Map\\AM\\L_SKELETON_DRAGON_PET_DIG_SERVER.lua") - script = new PetDigServer(); - else if (scriptName == "scripts\\client\\ai\\PR\\L_CRAB_SERVER.lua") - script = new CrabServer(); - else if (scriptName == "scripts\\02_server\\Pets\\L_PET_FROM_DIG_SERVER.lua") - script = new PetFromDigServer(); - else if (scriptName == "scripts\\02_server\\Pets\\L_PET_FROM_OBJECT_SERVER.lua") - script = new PetFromObjectServer(); - else if (scriptName == "scripts\\02_server\\Pets\\L_DAMAGING_PET.lua") - script = new DamagingPets(); - else if (scriptName == "scripts\\02_server\\Map\\PR\\L_SPAWN_GRYPHON_SERVER.lua") - script = new SpawnGryphonServer(); - - //FV Scripts: - else if (scriptName == "scripts\\02_server\\Map\\FV\\L_ACT_CANDLE.lua") - script = new FvCandle(); - else if (scriptName == "scripts\\02_server\\Map\\FV\\L_ENEMY_RONIN_SPAWNER.lua") - script = new EnemyRoninSpawner(); - else if (scriptName == "scripts\\02_server\\Enemy\\FV\\L_FV_MAELSTROM_CAVALRY.lua") - script = new FvMaelstromCavalry(); - else if (scriptName == "scripts\\ai\\FV\\L_ACT_NINJA_TURRET_1.lua") - script = new ActNinjaTurret(); - else if (scriptName == "scripts\\02_server\\Map\\FV\\L_FV_HORSEMEN_TRIGGER.lua") - script = new FvHorsemenTrigger(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_FLYING_CREVICE_DRAGON.lua") - script = new FvFlyingCreviceDragon(); - else if (scriptName == "scripts\\02_server\\Enemy\\FV\\L_FV_MAELSTROM_DRAGON.lua") - script = new FvMaelstromDragon(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_DRAGON_SMASHING_GOLEM_QB.lua") - script = new FvDragonSmashingGolemQb(); - else if (scriptName == "scripts\\02_server\\Enemy\\General\\L_TREASURE_CHEST_DRAGON_SERVER.lua") - script = new TreasureChestDragonServer(); - else if (scriptName == "scripts\\ai\\GENERAL\\L_INSTANCE_EXIT_TRANSFER_PLAYER_TO_LAST_NON_INSTANCE.lua") - script = new InstanceExitTransferPlayerToLastNonInstance(); - else if (scriptName == "scripts\\ai\\FV\\L_NPC_FREE_GF_NINJAS.lua") - script = new FvFreeGfNinjas(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_PANDA_SPAWNER_SERVER.lua") - script = new FvPandaSpawnerServer(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_PANDA_SERVER.lua") - script = new FvPandaServer(); - else if (scriptName == "scripts\\zone\\PROPERTY\\FV\\L_ZONE_FV_PROPERTY.lua") - script = new ZoneFvProperty(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_BRICK_PUZZLE_SERVER.lua") - script = new FvBrickPuzzleServer(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_CONSOLE_LEFT_QUICKBUILD.lua") - script = new FvConsoleLeftQuickbuild(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_CONSOLE_RIGHT_QUICKBUILD.lua") - script = new FvConsoleRightQuickbuild(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_FACILITY_BRICK.lua") - script = new FvFacilityBrick(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_FACILITY_PIPES.lua") - script = new FvFacilityPipes(); - else if (scriptName == "scripts\\02_server\\Map\\FV\\L_IMG_BRICK_CONSOLE_QB.lua") - script = new ImgBrickConsoleQB(); - else if (scriptName == "scripts\\ai\\FV\\L_ACT_PARADOX_PIPE_FIX.lua") - script = new ActParadoxPipeFix(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_NINJA_GUARDS.lua") - script = new FvNinjaGuard(); - else if (scriptName == "scripts\\ai\\FV\\L_ACT_PASS_THROUGH_WALL.lua") - script = new FvPassThroughWall(); - else if (scriptName == "scripts\\ai\\FV\\L_ACT_BOUNCE_OVER_WALL.lua") - script = new FvBounceOverWall(); - else if (scriptName == "scripts\\02_server\\Map\\FV\\L_NPC_FONG.lua") - script = new FvFong(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_MAELSTROM_GEYSER.lua") { - script = new FvMaelstromGeyser(); - } - - //Misc: - if (scriptName == "scripts\\02_server\\Map\\General\\L_EXPLODING_ASSET.lua") - script = new ExplodingAsset(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_WISHING_WELL_SERVER.lua") - script = new WishingWellServer(); - else if (scriptName == "scripts\\ai\\ACT\\L_ACT_PLAYER_DEATH_TRIGGER.lua") - script = new ActPlayerDeathTrigger(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_GROWING_FLOWER_SERVER.lua") - script = new GrowingFlower(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_TOKEN_CONSOLE_SERVER.lua") - script = new TokenConsoleServer(); - else if (scriptName == "scripts\\ai\\ACT\\FootRace\\L_ACT_BASE_FOOT_RACE.lua") - script = new BaseFootRaceManager(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_PROP_PLATFORM.lua") - script = new PropertyPlatform(); - else if (scriptName == "scripts\\02_server\\Map\\VE\\L_VE_BRICKSAMPLE_SERVER.lua") - script = new VeBricksampleServer(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_MAIL_BOX_SERVER.lua") - script = new MailBoxServer(); - else if (scriptName == "scripts\\ai\\ACT\\L_ACT_MINE.lua") - script = new ActMine(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_WANDERING_VENDOR.lua") - script = new WanderingVendor(); - - //Racing: - else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\RACE_IMAGINE_CRATE_SERVER.lua") - script = new RaceImagineCrateServer(); - else if (scriptName == "scripts\\ai\\ACT\\L_ACT_VEHICLE_DEATH_TRIGGER.lua") - script = new ActVehicleDeathTrigger(); - else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\RACE_IMAGINE_POWERUP.lua") - script = new RaceImaginePowerup(); - else if (scriptName == "scripts\\02_server\\Map\\FV\\Racing\\RACE_MAELSTROM_GEISER.lua") - script = new RaceMaelstromGeiser(); - else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_SMASH_EGG_IMAGINE_SERVER.lua") - script = new FvRaceSmashEggImagineServer(); - else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\RACE_SMASH_SERVER.lua") - script = new RaceSmashServer(); - - //NT: - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_SENTINELWALKWAY_SERVER.lua") - script = new NtSentinelWalkwayServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_PARADOXTELE_SERVER.lua") - script = new NtParadoxTeleServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_DARKITECT_REVEAL_SERVER.lua") - script = new NtDarkitectRevealServer(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_BANK_INTERACT_SERVER.lua") - script = new BankInteractServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_VENTURESPEEDPAD_SERVER.lua") - script = new NtVentureSpeedPadServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_VENTURE_CANNON_SERVER.lua") - script = new NtVentureCannonServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_COMBAT_CHALLENGE_SERVER.lua") - script = new NtCombatChallengeServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_COMBAT_CHALLENGE_DUMMY.lua") - script = new NtCombatChallengeDummy(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\\\L_NT_COMBAT_EXPLODING_TARGET.lua") - script = new NtCombatChallengeExplodingDummy(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_BASE_INTERACT_DROP_LOOT_SERVER.lua") - script = new BaseInteractDropLootServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_ASSEMBLYTUBE_SERVER.lua") - script = new NtAssemblyTubeServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_PARADOX_PANEL_SERVER.lua") - script = new NtParadoxPanelServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_IMAG_BEAM_BUFFER.lua") - script = new NtImagBeamBuffer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_BEAM_IMAGINATION_COLLECTORS.lua") - script = new NtBeamImaginationCollectors(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_DIRT_CLOUD_SERVER.lua") - script = new NtDirtCloudServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_CONSOLE_TELEPORT_SERVER.lua") - script = new NtConsoleTeleportServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_SPAWN_STEGO_SERVER.lua") - script = new SpawnStegoServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_SPAWN_SABERCAT_SERVER.lua") - script = new SpawnSaberCatServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_SPAWN_SHRAKE_SERVER.lua") - script = new SpawnShrakeServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_DUKE_SERVER.lua") - script = new NtDukeServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_HAEL_SERVER.lua") - script = new NtHaelServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_FACTION_SPY_SERVER.lua") - script = new NtFactionSpyServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_OVERBUILD_SERVER.lua") - script = new NtOverbuildServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_VANDA_SERVER.lua") - script = new NtVandaServer(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_FORCE_VOLUME_SERVER.lua") - script = new ForceVolumeServer(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_FRICTION_VOLUME_SERVER.lua") - script = new FrictionVolumeServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_XRAY_SERVER.lua") - script = new NtXRayServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_SLEEPING_GUARD.lua") - script = new NtSleepingGuard(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_IMAGIMETER_VISIBILITY_SERVER.lua") - script = new NTImagimeterVisibility(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_PIPE_VISIBILITY_SERVER.lua") - script = new NTPipeVisibilityServer(); - else if (scriptName == "scripts\\ai\\MINIGAME\\Objects\\MINIGAME_BLUE_MARK.lua") - script = new MinigameBlueMark(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_NAOMI_BREADCRUMB_SERVER.lua") - script = new NtNaomiBreadcrumbServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_NAOMI_DIRT_SERVER.lua") - script = new NTNaomiDirtServer(); - - //AM: - if (scriptName == "scripts\\02_server\\Map\\AM\\L_AM_CONSOLE_TELEPORT_SERVER.lua") - script = new AmConsoleTeleportServer(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_RANDOM_SPAWNER_FIN.lua") - script = new RandomSpawnerFin(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_RANDOM_SPAWNER_PIT.lua") - script = new RandomSpawnerPit(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_RANDOM_SPAWNER_STR.lua") - script = new RandomSpawnerStr(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_RANDOM_SPAWNER_ZIP.lua") - script = new RandomSpawnerZip(); - else if (scriptName == "scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_MECH.lua") - script = new AmDarklingMech(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_BRIDGE.lua") - script = new AmBridge(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_DRAW_BRIDGE.lua") - script = new AmDrawBridge(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_SHIELD_GENERATOR.lua") - script = new AmShieldGenerator(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_SHIELD_GENERATOR_QUICKBUILD.lua") - script = new AmShieldGeneratorQuickbuild(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_DROPSHIP_COMPUTER.lua") - script = new AmDropshipComputer(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_SCROLL_READER_SERVER.lua") - script = new AmScrollReaderServer(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_TEMPLE_SKILL_VOLUME.lua") - script = new AmTemplateSkillVolume(); - else if (scriptName == "scripts\\02_server\\Enemy\\General\\L_ENEMY_NJ_BUFF.lua") - script = new EnemyNjBuff(); - else if (scriptName == "scripts\\02_server\\Enemy\\AM\\L_AM_SKELETON_ENGINEER.lua") - script = new AmSkeletonEngineer(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_SKULLKIN_DRILL.lua") - script = new AmSkullkinDrill(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_SKULLKIN_DRILL_STAND.lua") - script = new AmSkullkinDrillStand(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_SKULLKIN_TOWER.lua") - script = new AmSkullkinTower(); - else if (scriptName == "scripts\\02_server\\Enemy\\AM\\L_AM_NAMED_DARKLING_DRAGON.lua") - script = new AmDarklingDragon(); - else if (scriptName == "scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_DRAGON.lua") - script = new AmDarklingDragon(); - else if (scriptName == "scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_APE.lua") - script = new BaseEnemyApe(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_BLUE_X.lua") - script = new AmBlueX(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_TEAPOT_SERVER.lua") - script = new AmTeapotServer(); - - // Ninjago - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_GARMADON_CELEBRATION_SERVER.lua") - script = new NjGarmadonCelebration(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_WU_NPC.lua") - script = new NjWuNPC(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_SCROLL_CHEST_SERVER.lua") - script = new NjScrollChestServer(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_COLE_NPC.lua") - script = new NjColeNPC(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_JAY_MISSION_ITEMS.lua") - script = new NjJayMissionItems(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_NPC_MISSION_SPINJITZU_SERVER.lua") - script = new NjNPCMissionSpinjitzuServer(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_ENEMY_SKELETON_SPAWNER.lua") - script = new EnemySkeletonSpawner(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_NJ_RAIL_SWITCH.lua") - script = new NjRailSwitch(); - else if (scriptName == "scripts\\02_server\\Map\\General\\Ninjago\\L_RAIL_ACTIVATORS_SERVER.lua") - script = new NjRailActivatorsServer(); - else if (scriptName == "scripts\\02_server\\Map\\General\\Ninjago\\L_RAIL_POST_SERVER.lua") - script = new NjRailPostServer(); - else if (scriptName == "scripts\\02_server\\Map\\General\\Ninjago\\L_ICE_RAIL_ACTIVATOR_SERVER.lua") - script = new NjIceRailActivator(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_FALLING_TILE.lua") - script = new FallingTile(); - else if (scriptName == "scripts\\02_server\\Enemy\\General\\L_ENEMY_NJ_BUFF_STUN_IMMUNITY.lua") - script = new EnemyNjBuff(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_IMAGINATION_SHRINE_SERVER.lua") - script = new ImaginationShrineServer(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_LIEUTENANT.lua") - script = new Lieutenant(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_RAIN_OF_ARROWS.lua") - script = new RainOfArrows(); - if (scriptName == "scripts\\02_server\\Map\\njhub\\L_CAVE_PRISON_CAGE.lua") - script = new CavePrisonCage(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\boss_instance\\L_MONASTERY_BOSS_INSTANCE_SERVER.lua") - script = new NjMonastryBossInstance(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_CATAPULT_BOUNCER_SERVER.lua") - script = new CatapultBouncerServer(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_CATAPULT_BASE_SERVER.lua") - script = new CatapultBaseServer(); - else if (scriptName == "scripts\\02_server\\Map\\General\\Ninjago\\L_NJHUB_LAVA_PLAYER_DEATH_TRIGGER.lua") - script = new NjhubLavaPlayerDeathTrigger(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_MON_CORE_NOOK_DOORS.lua") - script = new MonCoreNookDoors(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_MON_CORE_SMASHABLE_DOORS.lua") - script = new MonCoreSmashableDoors(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_FLAME_JET_SERVER.lua") - script = new FlameJetServer(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_BURNING_TILE.lua") - script = new BurningTile(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_SPAWN_EARTH_PET_SERVER.lua") - script = new NjEarthDragonPetServer(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_EARTH_PET_SERVER.lua") - script = new NjEarthPetServer(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_DRAGON_EMBLEM_CHEST_SERVER.lua") - script = new NjDragonEmblemChestServer(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_NYA_MISSION_ITEMS.lua") - script = new NjNyaMissionitems(); - - //DLU: - else if (scriptName == "scripts\\02_server\\DLU\\DLUVanityTeleportingObject.lua") - script = new DLUVanityTeleportingObject(); - - // Survival minigame - else if (scriptName == "scripts\\02_server\\Enemy\\Survival\\L_AG_SURVIVAL_STROMBIE.lua") - script = new AgSurvivalStromling(); - else if (scriptName == "scripts\\02_server\\Enemy\\Survival\\L_AG_SURVIVAL_DARKLING_MECH.lua") - script = new AgSurvivalMech(); - else if (scriptName == "scripts\\02_server\\Enemy\\Survival\\L_AG_SURVIVAL_DARK_SPIDERLING.lua") - script = new AgSurvivalSpiderling(); - - // Scripted equipment - else if (scriptName == "scripts\\EquipmentScripts\\Sunflower.lua") - script = new Sunflower(); - else if (scriptName == "scripts/EquipmentScripts/AnvilOfArmor.lua") - script = new AnvilOfArmor(); - else if (scriptName == "scripts/EquipmentScripts/FountainOfImagination.lua") - script = new FountainOfImagination(); - else if (scriptName == "scripts/EquipmentScripts/CauldronOfLife.lua") - script = new CauldronOfLife(); - else if (scriptName == "scripts\\02_server\\Equipment\\L_BOOTYDIG_SERVER.lua") - script = new BootyDigServer(); - else if (scriptName == "scripts\\EquipmentScripts\\PersonalFortress.lua") - script = new PersonalFortress(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_PROPERTY_DEVICE.lua") - script = new PropertyDevice(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_IMAG_BACKPACK_HEALS_SERVER.lua") - script = new ImaginationBackpackHealServer(); - else if (scriptName == "scripts\\ai\\GENERAL\\L_LEGO_DIE_ROLL.lua") - script = new LegoDieRoll(); - else if (scriptName == "scripts\\EquipmentScripts\\BuccaneerValiantShip.lua") - script = new BuccaneerValiantShip(); - else if (scriptName == "scripts\\EquipmentScripts\\FireFirstSkillonStartup.lua") - script = new FireFirstSkillonStartup(); - else if (scriptName == "scripts\\equipmenttriggers\\gempack.lua") - script = new GemPack(); - else if (scriptName == "scripts\\equipmenttriggers\\shardarmor.lua") - script = new ShardArmor(); - else if (scriptName == "scripts\\equipmenttriggers\\coilbackpack.lua") - script = new TeslaPack(); - else if (scriptName == "scripts\\EquipmentScripts\\stunImmunity.lua") - script = new StunImmunity(); - - // FB - else if (scriptName == "scripts\\ai\\NS\\WH\\L_ROCKHYDRANT_BROKEN.lua") - script = new RockHydrantBroken(); - else if (scriptName == "scripts\\ai\\NS\\L_NS_WH_FANS.lua") - script = new WhFans(); - - // WBL - else if (scriptName == "scripts\\zone\\LUPs\\WBL_generic_zone.lua") - script = new WblGenericZone(); - - // Alpha - if (scriptName == "scripts\\ai\\FV\\L_TRIGGER_GAS.lua") - script = new TriggerGas(); - else if (scriptName == "scripts\\ai\\FV\\L_ACT_NINJA_SENSEI.lua") - script = new ActNinjaSensei(); - - // pickups - if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_1_BRONZE-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(1); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_1_GOLD-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(10000); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_1_SILVER-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(100); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_10_BRONZE-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(10); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_10_GOLD-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(100000); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_10_SILVER-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(1000); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_25_BRONZE-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(25); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_25_GOLD-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(250000); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_25_SILVER-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(2500); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_IMAGINE-POWERUP-SPAWNER.lua") - script = new SpecialPowerupSpawner(13); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_IMAGINE-POWERUP-SPAWNER-2PT.lua") - script = new SpecialPowerupSpawner(129); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_LIFE-POWERUP-SPAWNER.lua") - script = new SpecialPowerupSpawner(5); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_ARMOR-POWERUP-SPAWNER.lua") - script = new SpecialPowerupSpawner(747); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_SPEED_BUFF_SPAWNER.lua") - script = new SpecialSpeedBuffSpawner(); - - // Wild - if (scriptName == "scripts\\ai\\WILD\\L_WILD_GF_RAT.lua" || scriptName == "scripts\\ai\\WILD\\L_WILD_GF_SNAIL.lua") - script = new WildAndScared(); - else if (scriptName == "scripts\\ai\\WILD\\L_WILD_GF_GLOWBUG.lua") - script = new WildGfGlowbug(); - else if (scriptName == "scripts\\ai\\WILD\\L_WILD_AMBIENT_CRAB.lua") - script = new WildAmbientCrab(); - else if (scriptName == "scripts\\ai\\WILD\\L_WILD_PANTS.lua") - script = new WildPants(); - else if (scriptName == "scripts\\ai\\WILD\\L_WILD_NINJA_BRICKS.lua") - script = new WildNinjaBricks(); - else if (scriptName == "scripts\\ai\\WILD\\L_WILD_NINJA_STUDENT.lua") - script = new WildNinjaStudent(); - else if (scriptName == "scripts\\ai\\WILD\\L_WILD_NINJA_SENSEI.lua") - script = new WildNinjaSensei(); - else if (scriptName == "scripts\\ai\\WILD\\L_LUP_generic_interact.lua") - script = new LupGenericInteract(); - else if (scriptName.rfind("scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_RobotCitizen", 0) == 0) - script = new WblRobotCitizen(); - - // handle invalid script reporting if the path is greater than zero and it's not an ignored script - // information not really needed for sys admins but is for developers - else if (script == InvalidToReturn) { + if (script == &InvalidToReturn) { if ((scriptName.length() > 0) && !((scriptName == "scripts\\02_server\\Enemy\\General\\L_SUSPEND_LUA_AI.lua") || (scriptName == "scripts\\02_server\\Enemy\\General\\L_BASE_ENEMY_SPIDERLING.lua") || - (scriptName =="scripts\\ai\\FV\\L_ACT_NINJA_STUDENT.lua") || + (scriptName == "scripts\\ai\\FV\\L_ACT_NINJA_STUDENT.lua") || (scriptName == "scripts\\ai\\WILD\\L_WILD_GF_FROG.lua") || - (scriptName == "scripts\\empty.lua") + (scriptName == "scripts\\empty.lua") || + (scriptName == "scripts\\ai\\AG\\L_AG_SENTINEL_GUARD.lua") )) LOG_DEBUG("LOT %i attempted to load CppScript for '%s', but returned InvalidScript.", parent->GetLOT(), scriptName.c_str()); } - m_Scripts[scriptName] = script; + g_Scripts[scriptName] = script; return script; } CppScripts::Script* const CppScripts::GetInvalidScript() { - return InvalidToReturn; + return &InvalidToReturn; } diff --git a/dScripts/ai/AG/AgShipShake.cpp b/dScripts/ai/AG/AgShipShake.cpp new file mode 100644 index 00000000..7dd2323a --- /dev/null +++ b/dScripts/ai/AG/AgShipShake.cpp @@ -0,0 +1,81 @@ +#include "AgShipShake.h" +#include "EntityInfo.h" +#include "GeneralUtils.h" +#include "GameMessages.h" +#include "EntityManager.h" +#include "RenderComponent.h" +#include "Entity.h" + +void AgShipShake::OnStartup(Entity* self) { + EntityInfo info{}; + + info.pos = { -418, 585, -30 }; + info.lot = 33; + info.spawnerID = self->GetObjectID(); + + auto* ref = Game::entityManager->CreateEntity(info); + + Game::entityManager->ConstructEntity(ref); + + self->SetVar(u"ShakeObject", ref->GetObjectID()); + + self->AddTimer("ShipShakeIdle", 2.0f); + self->SetVar(u"RandomTime", 10); +} + +void AgShipShake::OnTimerDone(Entity* self, std::string timerName) { + auto* shipFxObject = GetEntityInGroup(ShipFX); + auto* shipFxObject2 = GetEntityInGroup(ShipFX2); + auto* debrisObject = GetEntityInGroup(DebrisFX); + if (timerName == "ShipShakeIdle") { + auto* ref = Game::entityManager->GetEntity(self->GetVar(u"ShakeObject")); + + const auto randomTime = self->GetVar(u"RandomTime"); + auto time = GeneralUtils::GenerateRandomNumber(0, randomTime + 1); + + if (time < randomTime / 2) { + time += randomTime / 2; + } + + self->AddTimer("ShipShakeIdle", static_cast(time)); + + if (ref) + GameMessages::SendPlayEmbeddedEffectOnAllClientsNearObject(ref, FXName, ref->GetObjectID(), 500.0f); + + + if (debrisObject) + GameMessages::SendPlayFXEffect(debrisObject, -1, u"DebrisFall", "Debris", LWOOBJID_EMPTY, 1.0f, 1.0f, true); + + const auto randomFx = GeneralUtils::GenerateRandomNumber(0, 3); + + if (shipFxObject) { + std::string effectType = "shipboom" + std::to_string(randomFx); + GameMessages::SendPlayFXEffect(shipFxObject, 559, GeneralUtils::ASCIIToUTF16(effectType), "FX", LWOOBJID_EMPTY, 1.0f, 1.0f, true); + } + + self->AddTimer("ShipShakeExplode", 5.0f); + + if (shipFxObject2) + RenderComponent::PlayAnimation(shipFxObject2, u"explosion"); + } else if (timerName == "ShipShakeExplode") { + if (shipFxObject) + RenderComponent::PlayAnimation(shipFxObject, u"idle"); + if (shipFxObject2) + RenderComponent::PlayAnimation(shipFxObject2, u"idle"); + } +} + +Entity* AgShipShake::GetEntityInGroup(const std::string& group) { + auto entities = Game::entityManager->GetEntitiesInGroup(group); + Entity* en = nullptr; + + for (auto entity : entities) { + if (entity) { + en = entity; + break; + } + } + + return en; +} + diff --git a/dScripts/ai/AG/AgShipShake.h b/dScripts/ai/AG/AgShipShake.h new file mode 100644 index 00000000..4cc26f96 --- /dev/null +++ b/dScripts/ai/AG/AgShipShake.h @@ -0,0 +1,16 @@ +#pragma once +#include "CppScripts.h" + +class AgShipShake : public CppScripts::Script { +public: + void OnStartup(Entity* self) override; + void OnTimerDone(Entity* self, std::string timerName) override; + + std::string DebrisFX = "DebrisFX"; + std::string ShipFX = "ShipFX"; + std::string ShipFX2 = "ShipFX2"; + std::u16string FXName = u"camshake-bridge"; + +private: + Entity* GetEntityInGroup(const std::string& group); +}; diff --git a/dScripts/ai/AG/AgSpaceStuff.cpp b/dScripts/ai/AG/AgSpaceStuff.cpp index 130d4354..dedbd08c 100644 --- a/dScripts/ai/AG/AgSpaceStuff.cpp +++ b/dScripts/ai/AG/AgSpaceStuff.cpp @@ -8,21 +8,6 @@ void AgSpaceStuff::OnStartup(Entity* self) { self->AddTimer("FloaterScale", 5.0f); - - EntityInfo info{}; - - info.pos = { -418, 585, -30 }; - info.lot = 33; - info.spawnerID = self->GetObjectID(); - - auto* ref = Game::entityManager->CreateEntity(info); - - Game::entityManager->ConstructEntity(ref); - - self->SetVar(u"ShakeObject", ref->GetObjectID()); - - self->AddTimer("ShipShakeIdle", 2.0f); - self->SetVar(u"RandomTime", 10); } void AgSpaceStuff::OnTimerDone(Entity* self, std::string timerName) { @@ -37,70 +22,5 @@ void AgSpaceStuff::OnTimerDone(Entity* self, std::string timerName) { RenderComponent::PlayAnimation(self, u"path_0" + (GeneralUtils::to_u16string(pathType))); self->AddTimer("FloaterScale", randTime); - } else if (timerName == "ShipShakeExplode") { - DoShake(self, true); - } else if (timerName == "ShipShakeIdle") { - DoShake(self, false); } } - -void AgSpaceStuff::DoShake(Entity* self, bool explodeIdle) { - - if (!explodeIdle) { - auto* ref = Game::entityManager->GetEntity(self->GetVar(u"ShakeObject")); - - const auto randomTime = self->GetVar(u"RandomTime"); - auto time = GeneralUtils::GenerateRandomNumber(0, randomTime + 1); - - if (time < randomTime / 2) { - time += randomTime / 2; - } - - self->AddTimer("ShipShakeIdle", static_cast(time)); - - if (ref) - GameMessages::SendPlayEmbeddedEffectOnAllClientsNearObject(ref, FXName, ref->GetObjectID(), 500.0f); - - auto* debrisObject = GetEntityInGroup(DebrisFX); - - if (debrisObject) - GameMessages::SendPlayFXEffect(debrisObject, -1, u"DebrisFall", "Debris", LWOOBJID_EMPTY, 1.0f, 1.0f, true); - - const auto randomFx = GeneralUtils::GenerateRandomNumber(0, 3); - - auto* shipFxObject = GetEntityInGroup(ShipFX); - if (shipFxObject) { - std::string effectType = "shipboom" + std::to_string(randomFx); - GameMessages::SendPlayFXEffect(shipFxObject, 559, GeneralUtils::ASCIIToUTF16(effectType), "FX", LWOOBJID_EMPTY, 1.0f, 1.0f, true); - } - - self->AddTimer("ShipShakeExplode", 5.0f); - - auto* shipFxObject2 = GetEntityInGroup(ShipFX2); - if (shipFxObject2) - RenderComponent::PlayAnimation(shipFxObject2, u"explosion"); - } else { - auto* shipFxObject = GetEntityInGroup(ShipFX); - auto* shipFxObject2 = GetEntityInGroup(ShipFX2); - - if (shipFxObject) - RenderComponent::PlayAnimation(shipFxObject, u"idle"); - - if (shipFxObject2) - RenderComponent::PlayAnimation(shipFxObject2, u"idle"); - } -} - -Entity* AgSpaceStuff::GetEntityInGroup(const std::string& group) { - auto entities = Game::entityManager->GetEntitiesInGroup(group); - Entity* en = nullptr; - - for (auto entity : entities) { - if (entity) { - en = entity; - break; - } - } - - return en; -} diff --git a/dScripts/ai/AG/AgSpaceStuff.h b/dScripts/ai/AG/AgSpaceStuff.h index 8d816691..568f2baf 100644 --- a/dScripts/ai/AG/AgSpaceStuff.h +++ b/dScripts/ai/AG/AgSpaceStuff.h @@ -5,14 +5,5 @@ class AgSpaceStuff : public CppScripts::Script { public: void OnStartup(Entity* self); void OnTimerDone(Entity* self, std::string timerName); - void DoShake(Entity* self, bool explodeIdle); - - std::string DebrisFX = "DebrisFX"; - std::string ShipFX = "ShipFX"; - std::string ShipFX2 = "ShipFX2"; - std::u16string FXName = u"camshake-bridge"; - -private: - Entity* GetEntityInGroup(const std::string& group); }; diff --git a/dScripts/ai/AG/CMakeLists.txt b/dScripts/ai/AG/CMakeLists.txt index e74aac78..101f86fd 100644 --- a/dScripts/ai/AG/CMakeLists.txt +++ b/dScripts/ai/AG/CMakeLists.txt @@ -1,6 +1,7 @@ set(DSCRIPTS_SOURCES_AI_AG "AgShipPlayerDeathTrigger.cpp" "AgSpaceStuff.cpp" + "AgShipShake.cpp" "AgShipPlayerShockServer.cpp" "AgImagSmashable.cpp" "ActSharkPlayerDeathTrigger.cpp" diff --git a/dScripts/ai/RACING/OBJECTS/CMakeLists.txt b/dScripts/ai/RACING/OBJECTS/CMakeLists.txt index 4ef427d5..83f4b8b3 100644 --- a/dScripts/ai/RACING/OBJECTS/CMakeLists.txt +++ b/dScripts/ai/RACING/OBJECTS/CMakeLists.txt @@ -1,6 +1,10 @@ set(DSCRIPTS_SOURCES_AI_RACING_OBJECTS "RaceImagineCrateServer.cpp" "RaceImaginePowerup.cpp" + "FvRaceDragon.cpp" + "FvRacePillarServer.cpp" + "FvRacePillarABCServer.cpp" + "FvRacePillarDServer.cpp" "FvRaceSmashEggImagineServer.cpp" "RaceSmashServer.cpp" PARENT_SCOPE) diff --git a/dScripts/ai/RACING/OBJECTS/FvRaceDragon.cpp b/dScripts/ai/RACING/OBJECTS/FvRaceDragon.cpp new file mode 100644 index 00000000..cde7b809 --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/FvRaceDragon.cpp @@ -0,0 +1,30 @@ +#include "FvRaceDragon.h" +#include "RenderComponent.h" +#include "RacingControlComponent.h" + +void FvRaceDragon::OnCollisionPhantom(Entity* self, Entity* target) { + if (!target) return; + + const auto racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); + if (racingControllers.empty()) return; + + auto* racingControlComponent = racingControllers[0]->GetComponent(); + if (!racingControlComponent) return; + + const auto* player = racingControlComponent->GetPlayerData(target->GetObjectID()); + if (!player) return; + + if (player->lap != m_Lap) return; + + const auto dragons = Game::entityManager->GetEntitiesInGroup("dragon"); + for (const auto& dragon : dragons) { + if (!dragon || dragon->GetLOT() != this->m_Dragon) continue; + + auto* renderComponent = dragon->GetComponent(); + if (!renderComponent) continue; + + renderComponent->PlayAnimation(dragon, m_LapAnimName); + + } + Game::entityManager->DestroyEntity(self); +} diff --git a/dScripts/ai/RACING/OBJECTS/FvRaceDragon.h b/dScripts/ai/RACING/OBJECTS/FvRaceDragon.h new file mode 100644 index 00000000..0a0320c3 --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/FvRaceDragon.h @@ -0,0 +1,15 @@ +#pragma once +#include "CppScripts.h" + +#include +#include + +class FvRaceDragon : public CppScripts::Script { +public: + FvRaceDragon(const std::string_view lapAnimName, const int32_t lap) : m_LapAnimName(lapAnimName), m_Lap(lap) {} +private: + void OnCollisionPhantom(Entity* self, Entity* target) override; + const std::string m_LapAnimName; + const int32_t m_Lap; + const LOT m_Dragon = 11898; +}; diff --git a/dScripts/ai/RACING/OBJECTS/FvRacePillarABCServer.cpp b/dScripts/ai/RACING/OBJECTS/FvRacePillarABCServer.cpp new file mode 100644 index 00000000..7023fecc --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/FvRacePillarABCServer.cpp @@ -0,0 +1,34 @@ +#include "FvRacePillarABCServer.h" +#include "RenderComponent.h" +#include "RacingControlComponent.h" + +void FvRacePillarABCServer::OnCollisionPhantom(Entity* self, Entity* target) { + if (!target) return; + + const auto racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); + if (racingControllers.empty()) return; + + auto* racingControlComponent = racingControllers[0]->GetComponent(); + if (!racingControlComponent) return; + + const auto* player = racingControlComponent->GetPlayerData(target->GetObjectID()); + if (!player || player->lap != 1) return; + + PlayAnimation("crumble", "pillars", m_PillarA); + PlayAnimation("roar", "dragon", m_Dragon); + + self->AddTimer("PillarBFall", 2.5f); + self->AddTimer("PillarCFall", 3.7f); + self->AddTimer("DeleteObject", 3.8f); +} + +void FvRacePillarABCServer::OnTimerDone(Entity* self, std::string timerName) { + if (timerName == "PillarBFall") { + PlayAnimation("crumble", "pillars", m_PillarB); + } else if (timerName == "PillarCFall") { + PlayAnimation("crumble", "pillars", m_PillarC); + } else if (timerName == "DeleteObject") { + Game::entityManager->DestroyEntity(self); + } +} + diff --git a/dScripts/ai/RACING/OBJECTS/FvRacePillarABCServer.h b/dScripts/ai/RACING/OBJECTS/FvRacePillarABCServer.h new file mode 100644 index 00000000..9059fbeb --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/FvRacePillarABCServer.h @@ -0,0 +1,13 @@ +#pragma once +#include "CppScripts.h" +#include "FvRacePillarServer.h" + +class FvRacePillarABCServer : public FvRacePillarServer { + void OnCollisionPhantom(Entity* self, Entity* target) override; + void OnTimerDone(Entity* self, std::string timerName) override; +private: + const LOT m_PillarA = 11946; + const LOT m_PillarB = 11947; + const LOT m_PillarC = 11948; + const LOT m_Dragon = 11898; +}; diff --git a/dScripts/ai/RACING/OBJECTS/FvRacePillarDServer.cpp b/dScripts/ai/RACING/OBJECTS/FvRacePillarDServer.cpp new file mode 100644 index 00000000..b119352e --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/FvRacePillarDServer.cpp @@ -0,0 +1,21 @@ +#include "FvRacePillarDServer.h" +#include "RenderComponent.h" +#include "RacingControlComponent.h" + +void FvRacePillarDServer::OnCollisionPhantom(Entity* self, Entity* target) { + if (!target) return; + + const auto racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); + if (racingControllers.empty()) return; + + auto* racingControlComponent = racingControllers[0]->GetComponent(); + if (!racingControlComponent) return; + + const auto* player = racingControlComponent->GetPlayerData(target->GetObjectID()); + if (!player) return; + + if (player->lap == 2) { + PlayAnimation("crumble", "pillars", m_PillarD); + PlayAnimation("roar", "dragon", m_Dragon); + } +} diff --git a/dScripts/ai/RACING/OBJECTS/FvRacePillarDServer.h b/dScripts/ai/RACING/OBJECTS/FvRacePillarDServer.h new file mode 100644 index 00000000..e8d21567 --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/FvRacePillarDServer.h @@ -0,0 +1,10 @@ +#pragma once +#include "CppScripts.h" +#include "FvRacePillarServer.h" + +class FvRacePillarDServer : public FvRacePillarServer { + void OnCollisionPhantom(Entity* self, Entity* target) override; +private: + const LOT m_PillarD = 11949; + const LOT m_Dragon = 11898; +}; diff --git a/dScripts/ai/RACING/OBJECTS/FvRacePillarServer.cpp b/dScripts/ai/RACING/OBJECTS/FvRacePillarServer.cpp new file mode 100644 index 00000000..c89cf2a2 --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/FvRacePillarServer.cpp @@ -0,0 +1,15 @@ +#include "FvRacePillarServer.h" + +#include "Game.h" +#include "EntityManager.h" +#include "RenderComponent.h" + +void FvRacePillarServer::PlayAnimation(const std::string animName, const std::string group, const LOT lot) { + const auto entities = Game::entityManager->GetEntitiesInGroup(group); + for (const auto& entity : entities) { + if (!entity || entity->GetLOT() != lot) continue; + auto* renderComponent = entity->GetComponent(); + if (!renderComponent) continue; + renderComponent->PlayAnimation(entity, animName); + } +} diff --git a/dScripts/ai/RACING/OBJECTS/FvRacePillarServer.h b/dScripts/ai/RACING/OBJECTS/FvRacePillarServer.h new file mode 100644 index 00000000..9249177a --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/FvRacePillarServer.h @@ -0,0 +1,12 @@ +#ifndef FVRACEPILLARSERVER__H +#define FVRACEPILLARSERVER__H + +#include "CppScripts.h" + +class FvRacePillarServer : public virtual CppScripts::Script { +protected: + // Plays an animation on all entities in a group with a specific LOT + void PlayAnimation(const std::string animName, const std::string group, const LOT lot); +}; + +#endif // FVRACEPILLARSERVER__H diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index b6954ca2..e07104d6 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -282,24 +282,22 @@ int main(int argc, char** argv) { } const int32_t bufferSize = 1024; - MD5* md5 = new MD5(); + MD5 md5; char fileStreamBuffer[1024] = {}; while (!fileStream.eof()) { memset(fileStreamBuffer, 0, bufferSize); fileStream.read(fileStreamBuffer, bufferSize); - md5->update(fileStreamBuffer, fileStream.gcount()); + md5.update(fileStreamBuffer, fileStream.gcount()); } fileStream.close(); const char* nullTerminateBuffer = "\0"; - md5->update(nullTerminateBuffer, 1); // null terminate the data - md5->finalize(); - databaseChecksum = md5->hexdigest(); - - delete md5; + md5.update(nullTerminateBuffer, 1); // null terminate the data + md5.finalize(); + databaseChecksum = md5.hexdigest(); LOG("FDB Checksum calculated as: %s", databaseChecksum.c_str()); } @@ -361,7 +359,7 @@ int main(int argc, char** argv) { //Warning if we ran slow if (deltaTime > currentFrameDelta) { - LOG("We're running behind, dT: %f > %f (framerate %i)", deltaTime, currentFrameDelta, currentFramerate); + LOG("We're running behind, dT: %f > %i (framerate %i)", deltaTime, currentFrameDelta, currentFramerate); } //Check if we're still connected to master: @@ -388,14 +386,14 @@ int main(int argc, char** argv) { //In world we'd update our other systems here. if (zoneID != 0 && deltaTime > 0.0f) { - Metrics::StartMeasurement(MetricVariable::Physics); - dpWorld::StepWorld(deltaTime); - Metrics::EndMeasurement(MetricVariable::Physics); - Metrics::StartMeasurement(MetricVariable::UpdateEntities); Game::entityManager->UpdateEntities(deltaTime); Metrics::EndMeasurement(MetricVariable::UpdateEntities); + Metrics::StartMeasurement(MetricVariable::Physics); + dpWorld::StepWorld(deltaTime); + Metrics::EndMeasurement(MetricVariable::Physics); + Metrics::StartMeasurement(MetricVariable::Ghosting); if (std::chrono::duration(currentTime - ghostingLastTime).count() >= 1.0f) { Game::entityManager->UpdateGhosting(); @@ -531,6 +529,7 @@ int main(int argc, char** argv) { } void HandlePacketChat(Packet* packet) { + if (packet->length < 1) return; if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { LOG("Lost our connection to chat, zone(%i), instance(%i)", Game::server->GetZoneID(), Game::server->GetInstanceID()); @@ -544,7 +543,7 @@ void HandlePacketChat(Packet* packet) { chatConnected = true; } - if (packet->data[0] == ID_USER_PACKET_ENUM) { + if (packet->data[0] == ID_USER_PACKET_ENUM && packet->length >= 4) { if (static_cast(packet->data[1]) == eConnectionType::CHAT) { switch (static_cast(packet->data[3])) { case eChatMessageType::WORLD_ROUTE_PACKET: { @@ -559,8 +558,9 @@ void HandlePacketChat(Packet* packet) { //Write our stream outwards: CBITSTREAM; - for (BitSize_t i = 0; i < inStream.GetNumberOfBytesUsed(); i++) { - bitStream.Write(packet->data[i + 16]); //16 bytes == header + playerID to skip + unsigned char data; + while (inStream.Read(data)) { + bitStream.Write(data); } SEND_PACKET; //send routed packet to player @@ -661,7 +661,7 @@ void HandlePacketChat(Packet* packet) { } void HandleMasterPacket(Packet* packet) { - + if (packet->length < 2) return; if (static_cast(packet->data[1]) != eConnectionType::MASTER || packet->length < 4) return; switch (static_cast(packet->data[3])) { case eMasterMessageType::REQUEST_PERSISTENT_ID_RESPONSE: { @@ -787,6 +787,7 @@ void HandleMasterPacket(Packet* packet) { } void HandlePacket(Packet* packet) { + if (packet->length < 1) return; if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { auto user = UserManager::Instance()->GetUser(packet->systemAddress); if (!user) return; @@ -1000,7 +1001,6 @@ void HandlePacket(Packet* packet) { case eWorldMessageType::CHARACTER_DELETE_REQUEST: { UserManager::Instance()->DeleteCharacter(packet->systemAddress, packet); - UserManager::Instance()->RequestCharacterList(packet->systemAddress); break; } @@ -1209,8 +1209,8 @@ void HandlePacket(Packet* packet) { //Now write the rest of the data: auto data = inStream.GetData(); - for (uint32_t i = 0; i < size; ++i) { - bitStream.Write(data[i + 23]); + for (uint32_t i = 23; i - 23 < size && i < packet->length; ++i) { + bitStream.Write(data[i]); } Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE_ORDERED, 0, Game::chatSysAddr, false); diff --git a/docker-compose.yml b/docker-compose.yml index a7954718..8f5a3d09 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: - darkflame image: mariadb:latest volumes: - - ${DB_DATA_DIR:-./db/data}:/var/lib/mysql + - ${DB_DATA_DIR:-db-data}:/var/lib/mysql environment: - MARIADB_RANDOM_ROOT_PASSWORD=1 - MARIADB_USER=${MARIADB_USER:-darkflame} @@ -79,3 +79,6 @@ services: networks: darkflame: + +volumes: + db-data: diff --git a/migrations/dlu/15_behavior_owner.sql b/migrations/dlu/15_behavior_owner.sql new file mode 100644 index 00000000..53e76f82 --- /dev/null +++ b/migrations/dlu/15_behavior_owner.sql @@ -0,0 +1,3 @@ +ALTER TABLE behaviors ADD COLUMN character_id BIGINT NOT NULL DEFAULT 0; +ALTER TABLE behaviors ADD COLUMN behavior_id BIGINT NOT NULL PRIMARY KEY; +ALTER TABLE behaviors DROP COLUMN id; diff --git a/migrations/dlu/16_big_behaviors.sql b/migrations/dlu/16_big_behaviors.sql new file mode 100644 index 00000000..12edc0bc --- /dev/null +++ b/migrations/dlu/16_big_behaviors.sql @@ -0,0 +1 @@ +ALTER TABLE behaviors MODIFY behavior_info LONGTEXT DEFAULT NULL; diff --git a/resources/navmeshes.zip b/resources/navmeshes.zip index 95948656..cfa6539b 100644 Binary files a/resources/navmeshes.zip and b/resources/navmeshes.zip differ diff --git a/tests/dCommonTests/TestEncoding.cpp b/tests/dCommonTests/TestEncoding.cpp index 54ae03d3..0286d284 100644 --- a/tests/dCommonTests/TestEncoding.cpp +++ b/tests/dCommonTests/TestEncoding.cpp @@ -15,12 +15,12 @@ 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); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'H'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'e'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'l'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'l'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'o'); + EXPECT_EQ(GeneralUtils::details::_NextUTF8Char(originalWordSv, out), true); EXPECT_EQ(GeneralUtils::UTF8ToUTF16("Hello World!"), u"Hello World!"); }; @@ -29,15 +29,15 @@ TEST_F(EncodingTest, TestEncodingUmlaut) { originalWord = reinterpret_cast(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); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'F'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'r'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'ü'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'h'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'l'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'i'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'n'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'g'); + EXPECT_EQ(GeneralUtils::details::_NextUTF8Char(originalWordSv, out), false); EXPECT_EQ(GeneralUtils::UTF8ToUTF16("Frühling"), u"Frühling"); }; @@ -46,10 +46,10 @@ 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); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'中'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'文'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'字'); + EXPECT_EQ(GeneralUtils::details::_NextUTF8Char(originalWordSv, out), false); EXPECT_EQ(GeneralUtils::UTF8ToUTF16("中文字"), u"中文字"); }; @@ -58,11 +58,11 @@ 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); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x1F468); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x200D); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x2696); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0xFE0F); + EXPECT_EQ(GeneralUtils::details::_NextUTF8Char(originalWordSv, out), false); EXPECT_EQ(GeneralUtils::UTF8ToUTF16("👨‍⚖️"), u"👨‍⚖️"); }; diff --git a/vanity/atm.xml b/vanity/atm.xml index 863498ed..c03a59ce 100644 --- a/vanity/atm.xml +++ b/vanity/atm.xml @@ -27,6 +27,8 @@ + + diff --git a/versions.txt b/versions.txt index 1dc48d9c..fa7ea86c 100644 --- a/versions.txt +++ b/versions.txt @@ -1,3 +1,7 @@ +2.3 - Dragonmaw functional, new slash command system, vanity system overhaul +2.2 - Code cleanup and QoL fixes +2.1 - Bug and crash fixes +2.0 - Whole lot of fixed bugs and implemented features 1.0 - Final cleanup and bug fixing for public release 0.9 - Includes BBB without the need for a UGC server, cannon cove minigame, and bug fixes. 0.8 - Added Ninjago! and it's various features + frakjaw minigame. AG survival now works. @@ -7,4 +11,4 @@ 0.4 - Added Havok to replace Bullet, Instancing, Quickbuilds, rockets, and a ton more fixes and additions. 0.3 - FrostBurgh, Snowdrift and Snowman's Land testing version. Includes bodged systems. 0.2 - Transfer to VS2019 & Bullet -0.1 - Initial transfer from NixLU, up until BehaviorManager inclusion \ No newline at end of file +0.1 - Initial transfer from NixLU, up until BehaviorManager inclusion