diff --git a/CMakeLists.txt b/CMakeLists.txt index ae6455b0..aa517182 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,7 +73,8 @@ if(UNIX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -O2 -fPIC") elseif(MSVC) # Skip warning for invalid conversion from size_t to uint32_t for all targets below for now - add_compile_options("/wd4267" "/utf-8") + # Also disable non-portable MSVC volatile behavior + add_compile_options("/wd4267" "/utf-8" "/volatile:iso") elseif(WIN32) add_compile_definitions(_CRT_SECURE_NO_WARNINGS) endif() @@ -103,7 +104,7 @@ make_directory(${CMAKE_BINARY_DIR}/resServer) make_directory(${CMAKE_BINARY_DIR}/logs) # Copy resource files on first build -set(RESOURCE_FILES "sharedconfig.ini" "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini" "blacklist.dcf") +set(RESOURCE_FILES "sharedconfig.ini" "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini" "blocklist.dcf") message(STATUS "Checking resource file integrity") include(Utils) diff --git a/dChatFilter/dChatFilter.cpp b/dChatFilter/dChatFilter.cpp index 6e81db3b..844e3411 100644 --- a/dChatFilter/dChatFilter.cpp +++ b/dChatFilter/dChatFilter.cpp @@ -27,8 +27,8 @@ dChatFilter::dChatFilter(const std::string& filepath, bool dontGenerateDCF) { ExportWordlistToDCF(filepath + ".dcf", true); } - if (BinaryIO::DoesFileExist("blacklist.dcf")) { - ReadWordlistDCF("blacklist.dcf", false); + if (BinaryIO::DoesFileExist("blocklist.dcf")) { + ReadWordlistDCF("blocklist.dcf", false); } //Read player names that are ok as well: @@ -44,20 +44,20 @@ dChatFilter::~dChatFilter() { m_DeniedWords.clear(); } -void dChatFilter::ReadWordlistPlaintext(const std::string& filepath, bool whiteList) { +void dChatFilter::ReadWordlistPlaintext(const std::string& filepath, bool allowList) { std::ifstream file(filepath); if (file) { std::string line; while (std::getline(file, line)) { line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); std::transform(line.begin(), line.end(), line.begin(), ::tolower); //Transform to lowercase - if (whiteList) m_ApprovedWords.push_back(CalculateHash(line)); + if (allowList) m_ApprovedWords.push_back(CalculateHash(line)); else m_DeniedWords.push_back(CalculateHash(line)); } } } -bool dChatFilter::ReadWordlistDCF(const std::string& filepath, bool whiteList) { +bool dChatFilter::ReadWordlistDCF(const std::string& filepath, bool allowList) { std::ifstream file(filepath, std::ios::binary); if (file) { fileHeader hdr; @@ -70,13 +70,13 @@ bool dChatFilter::ReadWordlistDCF(const std::string& filepath, bool whiteList) { if (hdr.formatVersion == formatVersion) { size_t wordsToRead = 0; BinaryIO::BinaryRead(file, wordsToRead); - if (whiteList) m_ApprovedWords.reserve(wordsToRead); + if (allowList) m_ApprovedWords.reserve(wordsToRead); else m_DeniedWords.reserve(wordsToRead); size_t word = 0; for (size_t i = 0; i < wordsToRead; ++i) { BinaryIO::BinaryRead(file, word); - if (whiteList) m_ApprovedWords.push_back(word); + if (allowList) m_ApprovedWords.push_back(word); else m_DeniedWords.push_back(word); } @@ -90,14 +90,14 @@ bool dChatFilter::ReadWordlistDCF(const std::string& filepath, bool whiteList) { return false; } -void dChatFilter::ExportWordlistToDCF(const std::string& filepath, bool whiteList) { +void dChatFilter::ExportWordlistToDCF(const std::string& filepath, bool allowList) { std::ofstream file(filepath, std::ios::binary | std::ios_base::out); if (file) { BinaryIO::BinaryWrite(file, uint32_t(dChatFilterDCF::header)); BinaryIO::BinaryWrite(file, uint32_t(dChatFilterDCF::formatVersion)); - BinaryIO::BinaryWrite(file, size_t(whiteList ? m_ApprovedWords.size() : m_DeniedWords.size())); + BinaryIO::BinaryWrite(file, size_t(allowList ? m_ApprovedWords.size() : m_DeniedWords.size())); - for (size_t word : whiteList ? m_ApprovedWords : m_DeniedWords) { + for (size_t word : allowList ? m_ApprovedWords : m_DeniedWords) { BinaryIO::BinaryWrite(file, word); } @@ -105,10 +105,10 @@ void dChatFilter::ExportWordlistToDCF(const std::string& filepath, bool whiteLis } } -std::vector> dChatFilter::IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool whiteList) { +std::vector> dChatFilter::IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList) { if (gmLevel > eGameMasterLevel::FORUM_MODERATOR) return { }; //If anything but a forum mod, return true. if (message.empty()) return { }; - if (!whiteList && m_DeniedWords.empty()) return { { 0, message.length() } }; + if (!allowList && m_DeniedWords.empty()) return { { 0, message.length() } }; std::stringstream sMessage(message); std::string segment; @@ -126,16 +126,16 @@ std::vector> dChatFilter::IsSentenceOkay(const std:: size_t hash = CalculateHash(segment); - if (std::find(m_UserUnapprovedWordCache.begin(), m_UserUnapprovedWordCache.end(), hash) != m_UserUnapprovedWordCache.end() && whiteList) { + if (std::find(m_UserUnapprovedWordCache.begin(), m_UserUnapprovedWordCache.end(), hash) != m_UserUnapprovedWordCache.end() && allowList) { listOfBadSegments.emplace_back(position, originalSegment.length()); } - if (std::find(m_ApprovedWords.begin(), m_ApprovedWords.end(), hash) == m_ApprovedWords.end() && whiteList) { + if (std::find(m_ApprovedWords.begin(), m_ApprovedWords.end(), hash) == m_ApprovedWords.end() && allowList) { m_UserUnapprovedWordCache.push_back(hash); listOfBadSegments.emplace_back(position, originalSegment.length()); } - if (std::find(m_DeniedWords.begin(), m_DeniedWords.end(), hash) != m_DeniedWords.end() && !whiteList) { + if (std::find(m_DeniedWords.begin(), m_DeniedWords.end(), hash) != m_DeniedWords.end() && !allowList) { m_UserUnapprovedWordCache.push_back(hash); listOfBadSegments.emplace_back(position, originalSegment.length()); } diff --git a/dChatFilter/dChatFilter.h b/dChatFilter/dChatFilter.h index d00525ce..0f1e49ba 100644 --- a/dChatFilter/dChatFilter.h +++ b/dChatFilter/dChatFilter.h @@ -21,10 +21,10 @@ public: dChatFilter(const std::string& filepath, bool dontGenerateDCF); ~dChatFilter(); - void ReadWordlistPlaintext(const std::string& filepath, bool whiteList); - bool ReadWordlistDCF(const std::string& filepath, bool whiteList); - void ExportWordlistToDCF(const std::string& filepath, bool whiteList); - std::vector> IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool whiteList = true); + void ReadWordlistPlaintext(const std::string& filepath, bool allowList); + bool ReadWordlistDCF(const std::string& filepath, bool allowList); + void ExportWordlistToDCF(const std::string& filepath, bool allowList); + std::vector> IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList = true); private: bool m_DontGenerateDCF; diff --git a/dChatServer/ChatIgnoreList.cpp b/dChatServer/ChatIgnoreList.cpp index f0c55eb0..6dfbd7fc 100644 --- a/dChatServer/ChatIgnoreList.cpp +++ b/dChatServer/ChatIgnoreList.cpp @@ -1,6 +1,6 @@ #include "ChatIgnoreList.h" #include "PlayerContainer.h" -#include "eChatInternalMessageType.h" +#include "eChatMessageType.h" #include "BitStreamUtils.h" #include "Game.h" #include "Logger.h" @@ -13,7 +13,7 @@ // The only thing not auto-handled is instance activities force joining the team on the server. void WriteOutgoingReplyHeader(RakNet::BitStream& bitStream, const LWOOBJID& receivingPlayer, const ChatIgnoreList::Response type) { - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); bitStream.Write(receivingPlayer); //portion that will get routed: diff --git a/dChatServer/ChatPacketHandler.cpp b/dChatServer/ChatPacketHandler.cpp index 5e2e58d7..82cea018 100644 --- a/dChatServer/ChatPacketHandler.cpp +++ b/dChatServer/ChatPacketHandler.cpp @@ -14,11 +14,11 @@ #include "eObjectBits.h" #include "eConnectionType.h" #include "eChatMessageType.h" -#include "eChatInternalMessageType.h" #include "eClientMessageType.h" #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: @@ -60,7 +60,7 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) { //Now, we need to send the friendlist to the server they came from: CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); bitStream.Write(playerID); //portion that will get routed: @@ -355,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) { @@ -454,7 +515,7 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) { void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode) { CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); bitStream.Write(routeTo.playerID); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::PRIVATE_CHAT_MESSAGE); @@ -696,7 +757,7 @@ void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) { void ChatPacketHandler::SendTeamInvite(const PlayerData& receiver, const PlayerData& sender) { CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); bitStream.Write(receiver.playerID); //portion that will get routed: @@ -711,7 +772,7 @@ void ChatPacketHandler::SendTeamInvite(const PlayerData& receiver, const PlayerD void ChatPacketHandler::SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName) { CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); bitStream.Write(receiver.playerID); //portion that will get routed: @@ -738,7 +799,7 @@ void ChatPacketHandler::SendTeamInviteConfirm(const PlayerData& receiver, bool b void ChatPacketHandler::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName) { CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); bitStream.Write(receiver.playerID); //portion that will get routed: @@ -763,7 +824,7 @@ void ChatPacketHandler::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64L void ChatPacketHandler::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID) { CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); bitStream.Write(receiver.playerID); //portion that will get routed: @@ -780,7 +841,7 @@ void ChatPacketHandler::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i void ChatPacketHandler::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID) { CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); bitStream.Write(receiver.playerID); //portion that will get routed: @@ -809,7 +870,7 @@ void ChatPacketHandler::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFr void ChatPacketHandler::SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName) { CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); bitStream.Write(receiver.playerID); //portion that will get routed: @@ -835,7 +896,7 @@ void ChatPacketHandler::SendTeamRemovePlayer(const PlayerData& receiver, bool bD void ChatPacketHandler::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) { CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); bitStream.Write(receiver.playerID); //portion that will get routed: @@ -869,7 +930,7 @@ void ChatPacketHandler::SendFriendUpdate(const PlayerData& friendData, const Pla [bool] - is FTP*/ CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); bitStream.Write(friendData.playerID); //portion that will get routed: @@ -906,7 +967,7 @@ void ChatPacketHandler::SendFriendRequest(const PlayerData& receiver, const Play } CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); bitStream.Write(receiver.playerID); //portion that will get routed: @@ -920,7 +981,7 @@ void ChatPacketHandler::SendFriendRequest(const PlayerData& receiver, const Play void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const PlayerData& sender, eAddFriendResponseType responseCode, uint8_t isBestFriendsAlready, uint8_t isBestFriendRequest) { CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); bitStream.Write(receiver.playerID); // Portion that will get routed: @@ -943,7 +1004,7 @@ void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const Pla void ChatPacketHandler::SendRemoveFriend(const PlayerData& receiver, std::string& personToRemove, bool isSuccessful) { CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); bitStream.Write(receiver.playerID); //portion that will get routed: 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 84104726..81b6ddef 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -17,7 +17,6 @@ #include "PlayerContainer.h" #include "ChatPacketHandler.h" #include "eChatMessageType.h" -#include "eChatInternalMessageType.h" #include "eWorldMessageType.h" #include "ChatIgnoreList.h" #include "StringifiedEnum.h" @@ -182,47 +181,29 @@ int main(int argc, char** argv) { void HandlePacket(Packet* packet) { 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."); - } - - if (packet->data[0] == ID_NEW_INCOMING_CONNECTION) { + } else if (packet->data[0] == ID_NEW_INCOMING_CONNECTION) { LOG("A server is connecting, awaiting user list."); - } + } else if (packet->length < 4 || packet->data[0] != ID_USER_PACKET_ENUM) return; // Nothing left to process or not the right packet type - if (packet->length < 4) return; // Nothing left to process. Need 4 bytes to continue. + CINSTREAM; + inStream.SetReadOffset(BYTES_TO_BITS(1)); - if (static_cast(packet->data[1]) == eConnectionType::CHAT_INTERNAL) { - switch (static_cast(packet->data[3])) { - case eChatInternalMessageType::PLAYER_ADDED_NOTIFICATION: - Game::playerContainer.InsertPlayer(packet); - break; + eConnectionType connection; + eChatMessageType chatMessageID; - case eChatInternalMessageType::PLAYER_REMOVED_NOTIFICATION: - Game::playerContainer.RemovePlayer(packet); - break; - - case eChatInternalMessageType::MUTE_UPDATE: + inStream.Read(connection); + if (connection != eConnectionType::CHAT) return; + inStream.Read(chatMessageID); + + switch (chatMessageID) { + case eChatMessageType::GM_MUTE: Game::playerContainer.MuteUpdate(packet); break; - case eChatInternalMessageType::CREATE_TEAM: + case eChatMessageType::CREATE_TEAM: Game::playerContainer.CreateTeamServer(packet); break; - case eChatInternalMessageType::ANNOUNCEMENT: { - //we just forward this packet to every connected server - CINSTREAM; - Game::server->Send(inStream, packet->systemAddress, true); //send to everyone except origin - break; - } - - default: - LOG("Unknown CHAT_INTERNAL id: %i", int(packet->data[3])); - } - } - - if (static_cast(packet->data[1]) == eConnectionType::CHAT) { - eChatMessageType chat_message_type = static_cast(packet->data[3]); - switch (chat_message_type) { case eChatMessageType::GET_FRIENDS_LIST: ChatPacketHandler::HandleFriendlistRequest(packet); break; @@ -296,6 +277,23 @@ void HandlePacket(Packet* packet) { ChatPacketHandler::HandleGMLevelUpdate(packet); break; case eChatMessageType::LOGIN_SESSION_NOTIFY: + Game::playerContainer.InsertPlayer(packet); + break; + case eChatMessageType::GM_ANNOUNCE:{ + // we just forward this packet to every connected server + inStream.ResetReadPointer(); + Game::server->Send(inStream, packet->systemAddress, true); // send to everyone except origin + } + break; + case eChatMessageType::UNEXPECTED_DISCONNECT: + 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: @@ -308,7 +306,6 @@ void HandlePacket(Packet* packet) { case eChatMessageType::GUILD_KICK: case eChatMessageType::GUILD_GET_STATUS: case eChatMessageType::GUILD_GET_ALL: - case eChatMessageType::SHOW_ALL: case eChatMessageType::BLUEPRINT_MODERATED: case eChatMessageType::BLUEPRINT_MODEL_READY: case eChatMessageType::PROPERTY_READY_FOR_APPROVAL: @@ -323,7 +320,6 @@ void HandlePacket(Packet* packet) { case eChatMessageType::CSR_REQUEST: case eChatMessageType::CSR_REPLY: case eChatMessageType::GM_KICK: - case eChatMessageType::GM_ANNOUNCE: case eChatMessageType::WORLD_ROUTE_PACKET: case eChatMessageType::GET_ZONE_POPULATIONS: case eChatMessageType::REQUEST_MINIMUM_CHAT_MODE: @@ -332,33 +328,18 @@ void HandlePacket(Packet* packet) { case eChatMessageType::UGCMANIFEST_REPORT_DONE_FILE: case eChatMessageType::UGCMANIFEST_REPORT_DONE_BLUEPRINT: case eChatMessageType::UGCC_REQUEST: - case eChatMessageType::WHO: case eChatMessageType::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE: case eChatMessageType::ACHIEVEMENT_NOTIFY: case eChatMessageType::GM_CLOSE_PRIVATE_CHAT_WINDOW: - case eChatMessageType::UNEXPECTED_DISCONNECT: case eChatMessageType::PLAYER_READY: case eChatMessageType::GET_DONATION_TOTAL: case eChatMessageType::UPDATE_DONATION: case eChatMessageType::PRG_CSR_COMMAND: case eChatMessageType::HEARTBEAT_REQUEST_FROM_WORLD: case eChatMessageType::UPDATE_FREE_TRIAL_STATUS: - LOG("Unhandled CHAT Message id: %s (%i)", StringifiedEnum::ToString(chat_message_type).data(), chat_message_type); + LOG("Unhandled CHAT Message id: %s (%i)", StringifiedEnum::ToString(chatMessageID).data(), chatMessageID); break; default: - LOG("Unknown CHAT Message id: %i", chat_message_type); - } - } - - if (static_cast(packet->data[1]) == eConnectionType::WORLD) { - switch (static_cast(packet->data[3])) { - case eWorldMessageType::ROUTE_PACKET: { - LOG("Routing packet from world"); - break; - } - - default: - LOG("Unknown World id: %i", int(packet->data[3])); - } + LOG("Unknown CHAT Message id: %i", chatMessageID); } } diff --git a/dChatServer/PlayerContainer.cpp b/dChatServer/PlayerContainer.cpp index 7feff763..17e2cd1a 100644 --- a/dChatServer/PlayerContainer.cpp +++ b/dChatServer/PlayerContainer.cpp @@ -9,9 +9,9 @@ #include "BitStreamUtils.h" #include "Database.h" #include "eConnectionType.h" -#include "eChatInternalMessageType.h" #include "ChatPackets.h" #include "dConfig.h" +#include "eChatMessageType.h" void PlayerContainer::Initialize() { m_MaxNumberOfBestFriends = @@ -49,6 +49,7 @@ void PlayerContainer::InsertPlayer(Packet* packet) { 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 +88,7 @@ void PlayerContainer::RemovePlayer(Packet* packet) { } } + m_PlayerCount--; LOG("Removed user: %llu", playerID); m_Players.erase(playerID); @@ -145,7 +147,7 @@ void PlayerContainer::CreateTeamServer(Packet* packet) { void PlayerContainer::BroadcastMuteUpdate(LWOOBJID player, time_t time) { CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::MUTE_UPDATE); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GM_MUTE); bitStream.Write(player); bitStream.Write(time); @@ -352,7 +354,7 @@ void PlayerContainer::TeamStatusUpdate(TeamData* team) { void PlayerContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) { CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::TEAM_UPDATE); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::TEAM_GET_STATUS); bitStream.Write(team->teamID); bitStream.Write(deleteTeam); 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 46c17e43..2ed27fef 100644 --- a/dCommon/Diagnostics.cpp +++ b/dCommon/Diagnostics.cpp @@ -120,6 +120,8 @@ void CatchUnhandled(int sig) { if (eptr) std::rethrow_exception(eptr); } catch(const std::exception& e) { LOG("Caught exception: '%s'", e.what()); + } catch (...) { + LOG("Caught unknown exception."); } #ifndef INCLUDE_BACKTRACE diff --git a/dCommon/GeneralUtils.cpp b/dCommon/GeneralUtils.cpp index 27ebfb2c..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,35 +289,35 @@ 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; } -#ifdef DARKFLAME_PLATFORM_MACOS +#if !(__GNUC__ >= 11 || _MSC_VER >= 1924) // MacOS floating-point parse function specializations namespace GeneralUtils::details { diff --git a/dCommon/GeneralUtils.h b/dCommon/GeneralUtils.h index d502f55a..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 @@ -156,8 +159,9 @@ namespace GeneralUtils { * @returns An std::optional containing the desired value if it is equivalent to the string */ template - [[nodiscard]] std::optional TryParse(const std::string_view str) { + [[nodiscard]] std::optional TryParse(std::string_view str) { numeric_parse_t result; + while (!str.empty() && std::isspace(str.front())) str.remove_prefix(1); const char* const strEnd = str.data() + str.size(); const auto [parseEnd, ec] = std::from_chars(str.data(), strEnd, result); @@ -181,8 +185,10 @@ namespace GeneralUtils { * @returns An std::optional containing the desired value if it is equivalent to the string */ template - [[nodiscard]] std::optional TryParse(const std::string_view str) noexcept + [[nodiscard]] std::optional TryParse(std::string_view str) noexcept try { + while (!str.empty() && std::isspace(str.front())) str.remove_prefix(1); + size_t parseNum; const T result = details::_parse(str, parseNum); const bool isParsed = str.length() == parseNum; @@ -202,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; @@ -214,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)); } @@ -243,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"); @@ -270,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/dEnums/eChatInternalMessageType.h b/dCommon/dEnums/eChatInternalMessageType.h deleted file mode 100644 index d3b7020b..00000000 --- a/dCommon/dEnums/eChatInternalMessageType.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef __ECHATINTERNALMESSAGETYPE__H__ -#define __ECHATINTERNALMESSAGETYPE__H__ - -#include - -enum eChatInternalMessageType : uint32_t { - PLAYER_ADDED_NOTIFICATION = 0, - PLAYER_REMOVED_NOTIFICATION, - ADD_FRIEND, - ADD_BEST_FRIEND, - ADD_TO_TEAM, - ADD_BLOCK, - REMOVE_FRIEND, - REMOVE_BLOCK, - REMOVE_FROM_TEAM, - DELETE_TEAM, - REPORT, - PRIVATE_CHAT, - PRIVATE_CHAT_RESPONSE, - ANNOUNCEMENT, - MAIL_COUNT_UPDATE, - MAIL_SEND_NOTIFY, - REQUEST_USER_LIST, - FRIEND_LIST, - ROUTE_TO_PLAYER, - TEAM_UPDATE, - MUTE_UPDATE, - CREATE_TEAM, -}; - -#endif //!__ECHATINTERNALMESSAGETYPE__H__ diff --git a/dCommon/dEnums/eChatMessageType.h b/dCommon/dEnums/eChatMessageType.h index 52895ba3..38cd86de 100644 --- a/dCommon/dEnums/eChatMessageType.h +++ b/dCommon/dEnums/eChatMessageType.h @@ -72,7 +72,9 @@ enum class eChatMessageType :uint32_t { UPDATE_DONATION, PRG_CSR_COMMAND, HEARTBEAT_REQUEST_FROM_WORLD, - UPDATE_FREE_TRIAL_STATUS + UPDATE_FREE_TRIAL_STATUS, + // CUSTOM DLU MESSAGE ID FOR INTERNAL USE + CREATE_TEAM, }; #endif //!__ECHATMESSAGETYPE__H__ diff --git a/dCommon/dEnums/eConnectionType.h b/dCommon/dEnums/eConnectionType.h index ce1ff90c..406110a9 100644 --- a/dCommon/dEnums/eConnectionType.h +++ b/dCommon/dEnums/eConnectionType.h @@ -5,8 +5,7 @@ enum class eConnectionType : uint16_t { SERVER = 0, AUTH, CHAT, - CHAT_INTERNAL, - WORLD, + WORLD = 4, CLIENT, MASTER }; diff --git a/dCommon/dEnums/eGameMessageType.h b/dCommon/dEnums/eGameMessageType.h index e3fc22b6..8e6980d6 100644 --- a/dCommon/dEnums/eGameMessageType.h +++ b/dCommon/dEnums/eGameMessageType.h @@ -790,9 +790,10 @@ enum class eGameMessageType : uint16_t { GET_MISSION_TYPE_STATES = 853, GET_TIME_PLAYED = 854, SET_MISSION_VIEWED = 855, - SLASH_COMMAND_TEXT_FEEDBACK = 856, - HANDLE_SLASH_COMMAND_KORE_DEBUGGER = 857, + HKX_VEHICLE_LOADED = 856, + SLASH_COMMAND_TEXT_FEEDBACK = 857, BROADCAST_TEXT_TO_CHATBOX = 858, + HANDLE_SLASH_COMMAND_KORE_DEBUGGER = 859, OPEN_PROPERTY_MANAGEMENT = 860, OPEN_PROPERTY_VENDOR = 861, VOTE_ON_PROPERTY = 862, diff --git a/dCommon/dEnums/eWorldMessageType.h b/dCommon/dEnums/eWorldMessageType.h index bfaa110b..92081055 100644 --- a/dCommon/dEnums/eWorldMessageType.h +++ b/dCommon/dEnums/eWorldMessageType.h @@ -29,8 +29,8 @@ enum class eWorldMessageType : uint32_t { ROUTE_PACKET, // Social? POSITION_UPDATE, MAIL, - WORD_CHECK, // Whitelist word check - STRING_CHECK, // Whitelist string check + WORD_CHECK, // AllowList word check + STRING_CHECK, // AllowList string check GET_PLAYERS_IN_ZONE, REQUEST_UGC_MANIFEST_INFO, BLUEPRINT_GET_ALL_DATA_REQUEST, diff --git a/dGame/Character.cpp b/dGame/Character.cpp index eab7583f..59a67462 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -27,12 +27,9 @@ Character::Character(uint32_t id, User* parentUser) { m_ID = id; m_ParentUser = parentUser; m_OurEntity = nullptr; - m_Doc = nullptr; } Character::~Character() { - if (m_Doc) delete m_Doc; - m_Doc = nullptr; m_OurEntity = nullptr; m_ParentUser = nullptr; } @@ -55,8 +52,6 @@ void Character::UpdateInfoFromDatabase() { m_ZoneInstanceID = 0; //These values don't really matter, these are only used on the char select screen and seem unused. m_ZoneCloneID = 0; - m_Doc = nullptr; - //Quickly and dirtly parse the xmlData to get the info we need: DoQuickXMLDataParse(); @@ -70,18 +65,13 @@ void Character::UpdateInfoFromDatabase() { } void Character::UpdateFromDatabase() { - if (m_Doc) delete m_Doc; UpdateInfoFromDatabase(); } void Character::DoQuickXMLDataParse() { if (m_XMLData.size() == 0) return; - delete m_Doc; - m_Doc = new tinyxml2::XMLDocument(); - if (!m_Doc) return; - - if (m_Doc->Parse(m_XMLData.c_str(), m_XMLData.size()) == 0) { + if (m_Doc.Parse(m_XMLData.c_str(), m_XMLData.size()) == 0) { LOG("Loaded xmlData for character %s (%i)!", m_Name.c_str(), m_ID); } else { LOG("Failed to load xmlData!"); @@ -89,7 +79,7 @@ void Character::DoQuickXMLDataParse() { return; } - tinyxml2::XMLElement* mf = m_Doc->FirstChildElement("obj")->FirstChildElement("mf"); + tinyxml2::XMLElement* mf = m_Doc.FirstChildElement("obj")->FirstChildElement("mf"); if (!mf) { LOG("Failed to find mf tag!"); return; @@ -108,7 +98,7 @@ void Character::DoQuickXMLDataParse() { mf->QueryAttribute("ess", &m_Eyes); mf->QueryAttribute("ms", &m_Mouth); - tinyxml2::XMLElement* inv = m_Doc->FirstChildElement("obj")->FirstChildElement("inv"); + tinyxml2::XMLElement* inv = m_Doc.FirstChildElement("obj")->FirstChildElement("inv"); if (!inv) { LOG("Char has no inv!"); return; @@ -141,7 +131,7 @@ void Character::DoQuickXMLDataParse() { } - tinyxml2::XMLElement* character = m_Doc->FirstChildElement("obj")->FirstChildElement("char"); + tinyxml2::XMLElement* character = m_Doc.FirstChildElement("obj")->FirstChildElement("char"); if (character) { character->QueryAttribute("cc", &m_Coins); int32_t gm_level = 0; @@ -205,7 +195,7 @@ void Character::DoQuickXMLDataParse() { character->QueryAttribute("lzrw", &m_OriginalRotation.w); } - auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag"); + auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag"); if (flags) { auto* currentChild = flags->FirstChildElement(); while (currentChild) { @@ -239,12 +229,10 @@ void Character::SetBuildMode(bool buildMode) { } void Character::SaveXMLToDatabase() { - if (!m_Doc) return; - //For metrics, we'll record the time it took to save: auto start = std::chrono::system_clock::now(); - tinyxml2::XMLElement* character = m_Doc->FirstChildElement("obj")->FirstChildElement("char"); + tinyxml2::XMLElement* character = m_Doc.FirstChildElement("obj")->FirstChildElement("char"); if (character) { character->SetAttribute("gm", static_cast(m_GMLevel)); character->SetAttribute("cc", m_Coins); @@ -266,11 +254,11 @@ void Character::SaveXMLToDatabase() { } auto emotes = character->FirstChildElement("ue"); - if (!emotes) emotes = m_Doc->NewElement("ue"); + if (!emotes) emotes = m_Doc.NewElement("ue"); emotes->DeleteChildren(); for (int emoteID : m_UnlockedEmotes) { - auto emote = m_Doc->NewElement("e"); + auto emote = m_Doc.NewElement("e"); emote->SetAttribute("id", emoteID); emotes->LinkEndChild(emote); @@ -280,15 +268,15 @@ void Character::SaveXMLToDatabase() { } //Export our flags: - auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag"); + auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag"); if (!flags) { - flags = m_Doc->NewElement("flag"); //Create a flags tag if we don't have one - m_Doc->FirstChildElement("obj")->LinkEndChild(flags); //Link it to the obj tag so we can find next time + flags = m_Doc.NewElement("flag"); //Create a flags tag if we don't have one + m_Doc.FirstChildElement("obj")->LinkEndChild(flags); //Link it to the obj tag so we can find next time } flags->DeleteChildren(); //Clear it if we have anything, so that we can fill it up again without dupes for (std::pair flag : m_PlayerFlags) { - auto* f = m_Doc->NewElement("f"); + auto* f = m_Doc.NewElement("f"); f->SetAttribute("id", flag.first); //Because of the joy that is tinyxml2, it doesn't offer a function to set a uint64 as an attribute. @@ -301,7 +289,7 @@ void Character::SaveXMLToDatabase() { // Prevents the news feed from showing up on world transfers if (GetPlayerFlag(ePlayerFlag::IS_NEWS_SCREEN_VISIBLE)) { - auto* s = m_Doc->NewElement("s"); + auto* s = m_Doc.NewElement("s"); s->SetAttribute("si", ePlayerFlag::IS_NEWS_SCREEN_VISIBLE); flags->LinkEndChild(s); } @@ -326,7 +314,7 @@ void Character::SaveXMLToDatabase() { void Character::SetIsNewLogin() { // If we dont have a flag element, then we cannot have a s element as a child of flag. - auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag"); + auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag"); if (!flags) return; auto* currentChild = flags->FirstChildElement(); @@ -344,7 +332,7 @@ void Character::SetIsNewLogin() { void Character::WriteToDatabase() { //Dump our xml into m_XMLData: tinyxml2::XMLPrinter printer(0, true, 0); - m_Doc->Print(&printer); + m_Doc.Print(&printer); //Finally, save to db: Database::Get()->UpdateCharacterXml(m_ID, printer.CStr()); @@ -421,15 +409,15 @@ void Character::SetRetroactiveFlags() { void Character::SaveXmlRespawnCheckpoints() { //Export our respawn points: - auto* points = m_Doc->FirstChildElement("obj")->FirstChildElement("res"); + auto* points = m_Doc.FirstChildElement("obj")->FirstChildElement("res"); if (!points) { - points = m_Doc->NewElement("res"); - m_Doc->FirstChildElement("obj")->LinkEndChild(points); + points = m_Doc.NewElement("res"); + m_Doc.FirstChildElement("obj")->LinkEndChild(points); } points->DeleteChildren(); for (const auto& point : m_WorldRespawnCheckpoints) { - auto* r = m_Doc->NewElement("r"); + auto* r = m_Doc.NewElement("r"); r->SetAttribute("w", point.first); r->SetAttribute("x", point.second.x); @@ -443,7 +431,7 @@ void Character::SaveXmlRespawnCheckpoints() { void Character::LoadXmlRespawnCheckpoints() { m_WorldRespawnCheckpoints.clear(); - auto* points = m_Doc->FirstChildElement("obj")->FirstChildElement("res"); + auto* points = m_Doc.FirstChildElement("obj")->FirstChildElement("res"); if (!points) { return; } diff --git a/dGame/Character.h b/dGame/Character.h index b994fb61..77f286f0 100644 --- a/dGame/Character.h +++ b/dGame/Character.h @@ -37,7 +37,7 @@ public: void LoadXmlRespawnCheckpoints(); const std::string& GetXMLData() const { return m_XMLData; } - tinyxml2::XMLDocument* GetXMLDoc() const { return m_Doc; } + const tinyxml2::XMLDocument& GetXMLDoc() const { return m_Doc; } /** * Out of abundance of safety and clarity of what this saves, this is its own function. @@ -623,7 +623,7 @@ private: /** * The character XML belonging to this character */ - tinyxml2::XMLDocument* m_Doc; + tinyxml2::XMLDocument m_Doc; /** * Title of an announcement this character made (reserved for GMs) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 90763b4c..00ad8471 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -476,8 +476,7 @@ void Entity::Initialize() { } if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::INVENTORY) > 0 || m_Character) { - auto* xmlDoc = m_Character ? m_Character->GetXMLDoc() : nullptr; - AddComponent(xmlDoc); + AddComponent(); } // if this component exists, then we initialize it. it's value is always 0 if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MULTI_ZONE_ENTRANCE, -1) != -1) { @@ -1244,7 +1243,7 @@ void Entity::WriteComponents(RakNet::BitStream& outBitStream, eReplicaPacketType outBitStream.Write0(); } -void Entity::UpdateXMLDoc(tinyxml2::XMLDocument* doc) { +void Entity::UpdateXMLDoc(tinyxml2::XMLDocument& doc) { //This function should only ever be called from within Character, meaning doc should always exist when this is called. //Naturally, we don't include any non-player components in this update function. @@ -1635,10 +1634,8 @@ void Entity::PickupItem(const LWOOBJID& objectID) { CDObjectSkillsTable* skillsTable = CDClientManager::GetTable(); std::vector skills = skillsTable->Query([=](CDObjectSkills entry) {return (entry.objectTemplate == p.second.lot); }); for (CDObjectSkills skill : skills) { - CDSkillBehaviorTable* skillBehTable = CDClientManager::GetTable(); - auto* skillComponent = GetComponent(); - if (skillComponent) skillComponent->CastSkill(skill.skillID, GetObjectID(), GetObjectID()); + if (skillComponent) skillComponent->CastSkill(skill.skillID, GetObjectID(), GetObjectID(), skill.castOnType, NiQuaternion(0, 0, 0, 0)); auto* missionComponent = GetComponent(); diff --git a/dGame/Entity.h b/dGame/Entity.h index 740d7c92..ffdcb713 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -174,7 +174,7 @@ public: void WriteBaseReplicaData(RakNet::BitStream& outBitStream, eReplicaPacketType packetType); void WriteComponents(RakNet::BitStream& outBitStream, eReplicaPacketType packetType); - void UpdateXMLDoc(tinyxml2::XMLDocument* doc); + void UpdateXMLDoc(tinyxml2::XMLDocument& doc); void Update(float deltaTime); // Events diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index 0fde2eb6..8579d882 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -26,7 +26,7 @@ #include "eCharacterCreationResponse.h" #include "eRenameResponse.h" #include "eConnectionType.h" -#include "eChatInternalMessageType.h" +#include "eChatMessageType.h" #include "BitStreamUtils.h" #include "CheatDetection.h" @@ -83,7 +83,7 @@ void UserManager::Initialize() { auto chatListStream = Game::assetManager->GetFile("chatplus_en_us.txt"); if (!chatListStream) { LOG("Failed to load %s", (Game::assetManager->GetResPath() / "chatplus_en_us.txt").string().c_str()); - throw std::runtime_error("Aborting initialization due to missing chat whitelist file."); + throw std::runtime_error("Aborting initialization due to missing chat allowlist file."); } while (std::getline(chatListStream, line, '\n')) { @@ -422,7 +422,7 @@ void UserManager::DeleteCharacter(const SystemAddress& sysAddr, Packet* packet) Database::Get()->DeleteCharacter(charID); CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::PLAYER_REMOVED_NOTIFICATION); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::UNEXPECTED_DISCONNECT); bitStream.Write(objectID); Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); @@ -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/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/ActivityComponent.cpp b/dGame/dComponents/ActivityComponent.cpp index ce82abe0..1b2fc338 100644 --- a/dGame/dComponents/ActivityComponent.cpp +++ b/dGame/dComponents/ActivityComponent.cpp @@ -21,7 +21,7 @@ #include "eMissionTaskType.h" #include "eMatchUpdate.h" #include "eConnectionType.h" -#include "eChatInternalMessageType.h" +#include "eChatMessageType.h" #include "CDCurrencyTableTable.h" #include "CDActivityRewardsTable.h" @@ -501,7 +501,7 @@ void ActivityInstance::StartZone() { // only make a team if we have more than one participant if (participants.size() > 1) { CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::CREATE_TEAM); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::CREATE_TEAM); bitStream.Write(leader->GetObjectID()); bitStream.Write(m_Participants.size()); diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index 2bd2b6f1..60dceeef 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -45,20 +45,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 +82,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); @@ -150,13 +150,13 @@ void BaseCombatAIComponent::Update(const float deltaTime) { m_dpEntityEnemy->SetPosition(m_Parent->GetPosition()); //Process enter events - for (auto en : m_dpEntity->GetNewObjects()) { - m_Parent->OnCollisionPhantom(en->GetObjectID()); + for (const auto id : m_dpEntity->GetNewObjects()) { + m_Parent->OnCollisionPhantom(id); } //Process exit events - for (auto en : m_dpEntity->GetRemovedObjects()) { - m_Parent->OnCollisionLeavePhantom(en->GetObjectID()); + for (const auto id : m_dpEntity->GetRemovedObjects()) { + m_Parent->OnCollisionLeavePhantom(id); } // Check if we should stop the tether effect diff --git a/dGame/dComponents/BuffComponent.cpp b/dGame/dComponents/BuffComponent.cpp index 8b76e423..2c940647 100644 --- a/dGame/dComponents/BuffComponent.cpp +++ b/dGame/dComponents/BuffComponent.cpp @@ -326,9 +326,9 @@ Entity* BuffComponent::GetParent() const { return m_Parent; } -void BuffComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { +void BuffComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { // Load buffs - auto* dest = doc->FirstChildElement("obj")->FirstChildElement("dest"); + auto* dest = doc.FirstChildElement("obj")->FirstChildElement("dest"); // Make sure we have a clean buff element. auto* buffElement = dest->FirstChildElement("buff"); @@ -386,15 +386,15 @@ void BuffComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { } } -void BuffComponent::UpdateXml(tinyxml2::XMLDocument* doc) { +void BuffComponent::UpdateXml(tinyxml2::XMLDocument& doc) { // Save buffs - auto* dest = doc->FirstChildElement("obj")->FirstChildElement("dest"); + auto* dest = doc.FirstChildElement("obj")->FirstChildElement("dest"); // Make sure we have a clean buff element. auto* buffElement = dest->FirstChildElement("buff"); if (buffElement == nullptr) { - buffElement = doc->NewElement("buff"); + buffElement = doc.NewElement("buff"); dest->LinkEndChild(buffElement); } else { @@ -402,7 +402,7 @@ void BuffComponent::UpdateXml(tinyxml2::XMLDocument* doc) { } for (const auto& [id, buff] : m_Buffs) { - auto* buffEntry = doc->NewElement("b"); + auto* buffEntry = doc.NewElement("b"); // TODO: change this if to if (buff.cancelOnZone || buff.cancelOnLogout) handling at some point. No current way to differentiate between zone transfer and logout. if (buff.cancelOnZone) continue; @@ -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/BuffComponent.h b/dGame/dComponents/BuffComponent.h index df3c6a78..507e53a0 100644 --- a/dGame/dComponents/BuffComponent.h +++ b/dGame/dComponents/BuffComponent.h @@ -57,9 +57,9 @@ public: Entity* GetParent() const; - void LoadFromXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/CharacterComponent.cpp b/dGame/dComponents/CharacterComponent.cpp index 3eafd924..d706af9c 100644 --- a/dGame/dComponents/CharacterComponent.cpp +++ b/dGame/dComponents/CharacterComponent.cpp @@ -186,9 +186,9 @@ void CharacterComponent::SetGMLevel(eGameMasterLevel gmlevel) { m_GMLevel = gmlevel; } -void CharacterComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { +void CharacterComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { - tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char"); + auto* character = doc.FirstChildElement("obj")->FirstChildElement("char"); if (!character) { LOG("Failed to find char tag while loading XML!"); return; @@ -299,8 +299,8 @@ void CharacterComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { } } -void CharacterComponent::UpdateXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* minifig = doc->FirstChildElement("obj")->FirstChildElement("mf"); +void CharacterComponent::UpdateXml(tinyxml2::XMLDocument& doc) { + tinyxml2::XMLElement* minifig = doc.FirstChildElement("obj")->FirstChildElement("mf"); if (!minifig) { LOG("Failed to find mf tag while updating XML!"); return; @@ -320,7 +320,7 @@ void CharacterComponent::UpdateXml(tinyxml2::XMLDocument* doc) { // done with minifig - tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char"); + tinyxml2::XMLElement* character = doc.FirstChildElement("obj")->FirstChildElement("char"); if (!character) { LOG("Failed to find char tag while updating XML!"); return; @@ -338,11 +338,11 @@ void CharacterComponent::UpdateXml(tinyxml2::XMLDocument* doc) { // Set the zone statistics of the form ... auto zoneStatistics = character->FirstChildElement("zs"); - if (!zoneStatistics) zoneStatistics = doc->NewElement("zs"); + if (!zoneStatistics) zoneStatistics = doc.NewElement("zs"); zoneStatistics->DeleteChildren(); for (auto pair : m_ZoneStatistics) { - auto zoneStatistic = doc->NewElement("s"); + auto zoneStatistic = doc.NewElement("s"); zoneStatistic->SetAttribute("map", pair.first); zoneStatistic->SetAttribute("ac", pair.second.m_AchievementsCollected); diff --git a/dGame/dComponents/CharacterComponent.h b/dGame/dComponents/CharacterComponent.h index aa5c2e29..7e63b0bd 100644 --- a/dGame/dComponents/CharacterComponent.h +++ b/dGame/dComponents/CharacterComponent.h @@ -70,8 +70,8 @@ public: CharacterComponent(Entity* parent, Character* character, const SystemAddress& systemAddress); ~CharacterComponent() override; - void LoadFromXml(tinyxml2::XMLDocument* doc) override; - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/Component.cpp b/dGame/dComponents/Component.cpp index 705c44a7..8f38fb61 100644 --- a/dGame/dComponents/Component.cpp +++ b/dGame/dComponents/Component.cpp @@ -21,11 +21,11 @@ void Component::OnUse(Entity* originator) { } -void Component::UpdateXml(tinyxml2::XMLDocument* doc) { +void Component::UpdateXml(tinyxml2::XMLDocument& doc) { } -void Component::LoadFromXml(tinyxml2::XMLDocument* doc) { +void Component::LoadFromXml(const tinyxml2::XMLDocument& doc) { } diff --git a/dGame/dComponents/Component.h b/dGame/dComponents/Component.h index 062924f7..160565fb 100644 --- a/dGame/dComponents/Component.h +++ b/dGame/dComponents/Component.h @@ -34,13 +34,13 @@ public: * Save data from this componennt to character XML * @param doc the document to write data to */ - virtual void UpdateXml(tinyxml2::XMLDocument* doc); + virtual void UpdateXml(tinyxml2::XMLDocument& doc); /** * Load base data for this component from character XML * @param doc the document to read data from */ - virtual void LoadFromXml(tinyxml2::XMLDocument* doc); + virtual void LoadFromXml(const tinyxml2::XMLDocument& doc); virtual void Serialize(RakNet::BitStream& outBitStream, bool isConstruction); diff --git a/dGame/dComponents/ControllablePhysicsComponent.cpp b/dGame/dComponents/ControllablePhysicsComponent.cpp index dccbe915..18e4b19d 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.cpp +++ b/dGame/dComponents/ControllablePhysicsComponent.cpp @@ -158,8 +158,8 @@ void ControllablePhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bo } } -void ControllablePhysicsComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char"); +void ControllablePhysicsComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { + auto* character = doc.FirstChildElement("obj")->FirstChildElement("char"); if (!character) { LOG("Failed to find char tag!"); return; @@ -178,8 +178,8 @@ void ControllablePhysicsComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { m_DirtyPosition = true; } -void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char"); +void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument& doc) { + tinyxml2::XMLElement* character = doc.FirstChildElement("obj")->FirstChildElement("char"); if (!character) { LOG("Failed to find char tag while updating XML!"); return; diff --git a/dGame/dComponents/ControllablePhysicsComponent.h b/dGame/dComponents/ControllablePhysicsComponent.h index 8834128a..6309b8fc 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.h +++ b/dGame/dComponents/ControllablePhysicsComponent.h @@ -28,8 +28,8 @@ public: void Update(float deltaTime) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; - void LoadFromXml(tinyxml2::XMLDocument* doc) override; - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; /** * Sets the position of this entity, also ensures this update is serialized next tick. diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 1b9b3285..8ec1e4c9 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -185,8 +185,8 @@ void DestroyableComponent::Update(float deltaTime) { m_DamageCooldownTimer -= deltaTime; } -void DestroyableComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* dest = doc->FirstChildElement("obj")->FirstChildElement("dest"); +void DestroyableComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { + auto* dest = doc.FirstChildElement("obj")->FirstChildElement("dest"); if (!dest) { LOG("Failed to find dest tag!"); return; @@ -207,8 +207,8 @@ void DestroyableComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { m_DirtyHealth = true; } -void DestroyableComponent::UpdateXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* dest = doc->FirstChildElement("obj")->FirstChildElement("dest"); +void DestroyableComponent::UpdateXml(tinyxml2::XMLDocument& doc) { + tinyxml2::XMLElement* dest = doc.FirstChildElement("obj")->FirstChildElement("dest"); if (!dest) { LOG("Failed to find dest tag!"); return; @@ -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/DestroyableComponent.h b/dGame/dComponents/DestroyableComponent.h index 85a4f941..56f30103 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -26,8 +26,8 @@ public: void Update(float deltaTime) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; - void LoadFromXml(tinyxml2::XMLDocument* doc) override; - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; /** * Initializes the component using a different LOT diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index ecca0d28..af52c675 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -38,7 +38,7 @@ #include "CDObjectSkillsTable.h" #include "CDSkillBehaviorTable.h" -InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* document) : Component(parent) { +InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) { this->m_Dirty = true; this->m_Equipped = {}; this->m_Pushed = {}; @@ -48,7 +48,8 @@ InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* do const auto lot = parent->GetLOT(); if (lot == 1) { - LoadXml(document); + auto* character = m_Parent->GetCharacter(); + if (character) LoadXml(character->GetXMLDoc()); CheckProxyIntegrity(); @@ -472,10 +473,10 @@ bool InventoryComponent::HasSpaceForLoot(const std::unordered_map& return true; } -void InventoryComponent::LoadXml(tinyxml2::XMLDocument* document) { +void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) { LoadPetXml(document); - auto* inventoryElement = document->FirstChildElement("obj")->FirstChildElement("inv"); + auto* inventoryElement = document.FirstChildElement("obj")->FirstChildElement("inv"); if (inventoryElement == nullptr) { LOG("Failed to find 'inv' xml element!"); @@ -594,10 +595,10 @@ void InventoryComponent::LoadXml(tinyxml2::XMLDocument* document) { } } -void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { +void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) { UpdatePetXml(document); - auto* inventoryElement = document->FirstChildElement("obj")->FirstChildElement("inv"); + auto* inventoryElement = document.FirstChildElement("obj")->FirstChildElement("inv"); if (inventoryElement == nullptr) { LOG("Failed to find 'inv' xml element!"); @@ -631,7 +632,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { bags->DeleteChildren(); for (const auto* inventory : inventoriesToSave) { - auto* bag = document->NewElement("b"); + auto* bag = document.NewElement("b"); bag->SetAttribute("t", inventory->GetType()); bag->SetAttribute("m", static_cast(inventory->GetSize())); @@ -654,14 +655,14 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { continue; } - auto* bagElement = document->NewElement("in"); + auto* bagElement = document.NewElement("in"); bagElement->SetAttribute("t", inventory->GetType()); for (const auto& pair : inventory->GetItems()) { auto* item = pair.second; - auto* itemElement = document->NewElement("i"); + auto* itemElement = document.NewElement("i"); itemElement->SetAttribute("l", item->GetLot()); itemElement->SetAttribute("id", item->GetId()); @@ -680,7 +681,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { continue; } - auto* extraInfo = document->NewElement("x"); + auto* extraInfo = document.NewElement("x"); extraInfo->SetAttribute("ma", data->GetString(false).c_str()); @@ -1093,7 +1094,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; @@ -1542,8 +1543,8 @@ void InventoryComponent::PurgeProxies(Item* item) { } } -void InventoryComponent::LoadPetXml(tinyxml2::XMLDocument* document) { - auto* petInventoryElement = document->FirstChildElement("obj")->FirstChildElement("pet"); +void InventoryComponent::LoadPetXml(const tinyxml2::XMLDocument& document) { + auto* petInventoryElement = document.FirstChildElement("obj")->FirstChildElement("pet"); if (petInventoryElement == nullptr) { m_Pets.clear(); @@ -1574,19 +1575,19 @@ void InventoryComponent::LoadPetXml(tinyxml2::XMLDocument* document) { } } -void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument* document) { - auto* petInventoryElement = document->FirstChildElement("obj")->FirstChildElement("pet"); +void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument& document) { + auto* petInventoryElement = document.FirstChildElement("obj")->FirstChildElement("pet"); if (petInventoryElement == nullptr) { - petInventoryElement = document->NewElement("pet"); + petInventoryElement = document.NewElement("pet"); - document->FirstChildElement("obj")->LinkEndChild(petInventoryElement); + document.FirstChildElement("obj")->LinkEndChild(petInventoryElement); } petInventoryElement->DeleteChildren(); for (const auto& pet : m_Pets) { - auto* petElement = document->NewElement("p"); + auto* petElement = document.NewElement("p"); petElement->SetAttribute("id", pet.first); petElement->SetAttribute("l", pet.second.lot); diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index 8f58a523..a1eb14d1 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -38,12 +38,12 @@ enum class eItemType : int32_t; class InventoryComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::INVENTORY; - explicit InventoryComponent(Entity* parent, tinyxml2::XMLDocument* document = nullptr); + InventoryComponent(Entity* parent); void Update(float deltaTime) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; - void LoadXml(tinyxml2::XMLDocument* document); - void UpdateXml(tinyxml2::XMLDocument* document) override; + void LoadXml(const tinyxml2::XMLDocument& document); + void UpdateXml(tinyxml2::XMLDocument& document) override; /** * Returns an inventory of the specified type, if it exists @@ -470,13 +470,13 @@ private: * Saves all the pet information stored in inventory items to the database * @param document the xml doc to save to */ - void LoadPetXml(tinyxml2::XMLDocument* document); + void LoadPetXml(const tinyxml2::XMLDocument& document); /** * Loads all the pet information from an xml doc into items * @param document the xml doc to load from */ - void UpdatePetXml(tinyxml2::XMLDocument* document); + void UpdatePetXml(tinyxml2::XMLDocument& document); }; #endif diff --git a/dGame/dComponents/LevelProgressionComponent.cpp b/dGame/dComponents/LevelProgressionComponent.cpp index 2d3d5144..a6801a40 100644 --- a/dGame/dComponents/LevelProgressionComponent.cpp +++ b/dGame/dComponents/LevelProgressionComponent.cpp @@ -13,8 +13,8 @@ LevelProgressionComponent::LevelProgressionComponent(Entity* parent) : Component m_CharacterVersion = eCharacterVersion::LIVE; } -void LevelProgressionComponent::UpdateXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* level = doc->FirstChildElement("obj")->FirstChildElement("lvl"); +void LevelProgressionComponent::UpdateXml(tinyxml2::XMLDocument& doc) { + tinyxml2::XMLElement* level = doc.FirstChildElement("obj")->FirstChildElement("lvl"); if (!level) { LOG("Failed to find lvl tag while updating XML!"); return; @@ -24,8 +24,8 @@ void LevelProgressionComponent::UpdateXml(tinyxml2::XMLDocument* doc) { level->SetAttribute("cv", static_cast(m_CharacterVersion)); } -void LevelProgressionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* level = doc->FirstChildElement("obj")->FirstChildElement("lvl"); +void LevelProgressionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { + auto* level = doc.FirstChildElement("obj")->FirstChildElement("lvl"); if (!level) { LOG("Failed to find lvl tag while loading XML!"); return; diff --git a/dGame/dComponents/LevelProgressionComponent.h b/dGame/dComponents/LevelProgressionComponent.h index a27039f3..e9981ab6 100644 --- a/dGame/dComponents/LevelProgressionComponent.h +++ b/dGame/dComponents/LevelProgressionComponent.h @@ -27,13 +27,13 @@ public: * Save data from this componennt to character XML * @param doc the document to write data to */ - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; /** * Load base data for this component from character XML * @param doc the document to read data from */ - void LoadFromXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; /** * Gets the current level of the entity diff --git a/dGame/dComponents/MissionComponent.cpp b/dGame/dComponents/MissionComponent.cpp index 151fcf2f..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(); @@ -504,10 +504,8 @@ bool MissionComponent::RequiresItem(const LOT lot) { } -void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { - if (doc == nullptr) return; - - auto* mis = doc->FirstChildElement("obj")->FirstChildElement("mis"); +void MissionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { + auto* mis = doc.FirstChildElement("obj")->FirstChildElement("mis"); if (mis == nullptr) return; @@ -523,7 +521,7 @@ void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { auto* mission = new Mission(this, missionId); - mission->LoadFromXml(doneM); + mission->LoadFromXml(*doneM); doneM = doneM->NextSiblingElement(); @@ -540,7 +538,7 @@ void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { auto* mission = new Mission(this, missionId); - mission->LoadFromXml(currentM); + mission->LoadFromXml(*currentM); if (currentM->QueryAttribute("o", &missionOrder) == tinyxml2::XML_SUCCESS && mission->IsMission()) { mission->SetUniqueMissionOrderID(missionOrder); @@ -554,25 +552,23 @@ void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { } -void MissionComponent::UpdateXml(tinyxml2::XMLDocument* doc) { - if (doc == nullptr) return; - +void MissionComponent::UpdateXml(tinyxml2::XMLDocument& doc) { auto shouldInsertMis = false; - auto* obj = doc->FirstChildElement("obj"); + auto* obj = doc.FirstChildElement("obj"); auto* mis = obj->FirstChildElement("mis"); if (mis == nullptr) { - mis = doc->NewElement("mis"); + mis = doc.NewElement("mis"); shouldInsertMis = true; } mis->DeleteChildren(); - auto* done = doc->NewElement("done"); - auto* cur = doc->NewElement("cur"); + auto* done = doc.NewElement("done"); + auto* cur = doc.NewElement("cur"); for (const auto& pair : m_Missions) { auto* mission = pair.second; @@ -580,10 +576,10 @@ void MissionComponent::UpdateXml(tinyxml2::XMLDocument* doc) { if (mission) { const auto complete = mission->IsComplete(); - auto* m = doc->NewElement("m"); + auto* m = doc.NewElement("m"); if (complete) { - mission->UpdateXml(m); + mission->UpdateXml(*m); done->LinkEndChild(m); @@ -591,7 +587,7 @@ void MissionComponent::UpdateXml(tinyxml2::XMLDocument* doc) { } if (mission->IsMission()) m->SetAttribute("o", mission->GetUniqueMissionOrderID()); - mission->UpdateXml(m); + mission->UpdateXml(*m); cur->LinkEndChild(m); } diff --git a/dGame/dComponents/MissionComponent.h b/dGame/dComponents/MissionComponent.h index 866f1650..a01794f0 100644 --- a/dGame/dComponents/MissionComponent.h +++ b/dGame/dComponents/MissionComponent.h @@ -31,8 +31,8 @@ public: explicit MissionComponent(Entity* parent); ~MissionComponent() override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate, unsigned int& flags); - void LoadFromXml(tinyxml2::XMLDocument* doc) override; - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; /** * Returns all the missions for this entity, mapped by mission ID diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 27349c06..6010b4e6 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -215,20 +215,20 @@ void PetComponent::OnUse(Entity* originator) { return; } - if (result.fieldIsNull(0)) { + if (result.fieldIsNull("ValidPiecesLXF")) { result.finalize(); return; } - buildFile = std::string(result.getStringField(0)); + buildFile = std::string(result.getStringField("ValidPiecesLXF")); 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); + data.puzzleModelLot = result.getIntField("PuzzleModelLot"); + data.timeLimit = result.getFloatField("Timelimit"); + data.numValidPieces = result.getIntField("NumValidPieces"); + data.imaginationCost = result.getIntField("imagCostPerBuild"); if (data.timeLimit <= 0) data.timeLimit = 60; imaginationCost = data.imaginationCost; diff --git a/dGame/dComponents/PhantomPhysicsComponent.cpp b/dGame/dComponents/PhantomPhysicsComponent.cpp index 276184b1..ba0c2495 100644 --- a/dGame/dComponents/PhantomPhysicsComponent.cpp +++ b/dGame/dComponents/PhantomPhysicsComponent.cpp @@ -187,7 +187,7 @@ PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent) : PhysicsCompon //add fallback cube: m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 2.0f, 2.0f, 2.0f); } - + m_dpEntity->SetScale(m_Scale); m_dpEntity->SetRotation(m_Rotation); m_dpEntity->SetPosition(m_Position); @@ -323,14 +323,13 @@ void PhantomPhysicsComponent::Update(float deltaTime) { if (!m_dpEntity) return; //Process enter events - for (auto en : m_dpEntity->GetNewObjects()) { - if (!en) continue; - ApplyCollisionEffect(en->GetObjectID(), m_EffectType, m_DirectionalMultiplier); - m_Parent->OnCollisionPhantom(en->GetObjectID()); + for (const auto id : m_dpEntity->GetNewObjects()) { + ApplyCollisionEffect(id, m_EffectType, m_DirectionalMultiplier); + m_Parent->OnCollisionPhantom(id); //If we are a respawn volume, inform the client: if (m_IsRespawnVolume) { - auto entity = Game::entityManager->GetEntity(en->GetObjectID()); + auto* const entity = Game::entityManager->GetEntity(id); if (entity) { GameMessages::SendPlayerReachedRespawnCheckpoint(entity, m_RespawnPos, m_RespawnRot); @@ -341,10 +340,9 @@ void PhantomPhysicsComponent::Update(float deltaTime) { } //Process exit events - for (auto en : m_dpEntity->GetRemovedObjects()) { - if (!en) continue; - ApplyCollisionEffect(en->GetObjectID(), m_EffectType, 1.0f); - m_Parent->OnCollisionLeavePhantom(en->GetObjectID()); + for (const auto id : m_dpEntity->GetRemovedObjects()) { + ApplyCollisionEffect(id, m_EffectType, 1.0f); + m_Parent->OnCollisionLeavePhantom(id); } } 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 2acc6a5d..ad96097b 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -49,11 +49,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 +105,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, ' ')) { @@ -352,16 +352,11 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N auto* spawner = Game::zoneManager->GetSpawner(spawnerId); - auto ldfModelBehavior = new LDFData(u"modelBehaviors", 0); - auto userModelID = new LDFData(u"userModelID", info.spawnerID); - auto modelType = new LDFData(u"modelType", 2); - auto propertyObjectID = new LDFData(u"propertyObjectID", true); - auto componentWhitelist = new LDFData(u"componentWhitelist", 1); - info.nodes[0]->config.push_back(componentWhitelist); - info.nodes[0]->config.push_back(ldfModelBehavior); - info.nodes[0]->config.push_back(modelType); - info.nodes[0]->config.push_back(propertyObjectID); - info.nodes[0]->config.push_back(userModelID); + info.nodes[0]->config.push_back(new LDFData(u"modelBehaviors", 0)); + info.nodes[0]->config.push_back(new LDFData(u"userModelID", info.spawnerID)); + info.nodes[0]->config.push_back(new LDFData(u"modelType", 2)); + info.nodes[0]->config.push_back(new LDFData(u"propertyObjectID", true)); + info.nodes[0]->config.push_back(new LDFData(u"componentWhitelist", 1)); auto* model = spawner->Spawn(); @@ -585,29 +580,17 @@ void PropertyManagementComponent::Load() { GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER); GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT); - LDFBaseData* ldfBlueprintID = new LDFData(u"blueprintid", blueprintID); - LDFBaseData* componentWhitelist = new LDFData(u"componentWhitelist", 1); - LDFBaseData* modelType = new LDFData(u"modelType", 2); - LDFBaseData* propertyObjectID = new LDFData(u"propertyObjectID", true); - LDFBaseData* userModelID = new LDFData(u"userModelID", databaseModel.id); - - settings.push_back(ldfBlueprintID); - settings.push_back(componentWhitelist); - settings.push_back(modelType); - settings.push_back(propertyObjectID); - settings.push_back(userModelID); + settings.push_back(new LDFData(u"blueprintid", blueprintID)); + settings.push_back(new LDFData(u"componentWhitelist", 1)); + settings.push_back(new LDFData(u"modelType", 2)); + settings.push_back(new LDFData(u"propertyObjectID", true)); + settings.push_back(new LDFData(u"userModelID", databaseModel.id)); } else { - auto modelType = new LDFData(u"modelType", 2); - auto userModelID = new LDFData(u"userModelID", databaseModel.id); - auto ldfModelBehavior = new LDFData(u"modelBehaviors", 0); - auto propertyObjectID = new LDFData(u"propertyObjectID", true); - auto componentWhitelist = new LDFData(u"componentWhitelist", 1); - - settings.push_back(componentWhitelist); - settings.push_back(ldfModelBehavior); - settings.push_back(modelType); - settings.push_back(propertyObjectID); - settings.push_back(userModelID); + settings.push_back(new LDFData(u"modelType", 2)); + settings.push_back(new LDFData(u"userModelID", databaseModel.id)); + settings.push_back(new LDFData(u"modelBehaviors", 0)); + settings.push_back(new LDFData(u"propertyObjectID", true)); + settings.push_back(new LDFData(u"componentWhitelist", 1)); } node->config = settings; diff --git a/dGame/dComponents/ProximityMonitorComponent.cpp b/dGame/dComponents/ProximityMonitorComponent.cpp index 9ab3f1db..3338dd43 100644 --- a/dGame/dComponents/ProximityMonitorComponent.cpp +++ b/dGame/dComponents/ProximityMonitorComponent.cpp @@ -5,7 +5,7 @@ #include "EntityManager.h" #include "SimplePhysicsComponent.h" -const std::map ProximityMonitorComponent::m_EmptyObjectMap = {}; +const std::unordered_set ProximityMonitorComponent::m_EmptyObjectSet = {}; ProximityMonitorComponent::ProximityMonitorComponent(Entity* parent, int radiusSmall, int radiusLarge) : Component(parent) { if (radiusSmall != -1 && radiusLarge != -1) { @@ -38,26 +38,26 @@ void ProximityMonitorComponent::SetProximityRadius(dpEntity* entity, const std:: m_ProximitiesData.insert(std::make_pair(name, entity)); } -const std::map& ProximityMonitorComponent::GetProximityObjects(const std::string& name) { - const auto& iter = m_ProximitiesData.find(name); +const std::unordered_set& ProximityMonitorComponent::GetProximityObjects(const std::string& name) { + const auto iter = m_ProximitiesData.find(name); - if (iter == m_ProximitiesData.end()) { - return m_EmptyObjectMap; + if (iter == m_ProximitiesData.cend()) { + return m_EmptyObjectSet; } return iter->second->GetCurrentlyCollidingObjects(); } bool ProximityMonitorComponent::IsInProximity(const std::string& name, LWOOBJID objectID) { - const auto& iter = m_ProximitiesData.find(name); + const auto iter = m_ProximitiesData.find(name); - if (iter == m_ProximitiesData.end()) { + if (iter == m_ProximitiesData.cend()) { return false; } - const auto& collitions = iter->second->GetCurrentlyCollidingObjects(); + const auto& collisions = iter->second->GetCurrentlyCollidingObjects(); - return collitions.find(objectID) != collitions.end(); + return collisions.contains(objectID); } void ProximityMonitorComponent::Update(float deltaTime) { @@ -66,13 +66,13 @@ void ProximityMonitorComponent::Update(float deltaTime) { prox.second->SetPosition(m_Parent->GetPosition()); //Process enter events - for (auto* en : prox.second->GetNewObjects()) { - m_Parent->OnCollisionProximity(en->GetObjectID(), prox.first, "ENTER"); + for (const auto id : prox.second->GetNewObjects()) { + m_Parent->OnCollisionProximity(id, prox.first, "ENTER"); } //Process exit events - for (auto* en : prox.second->GetRemovedObjects()) { - m_Parent->OnCollisionProximity(en->GetObjectID(), prox.first, "LEAVE"); + for (const auto id : prox.second->GetRemovedObjects()) { + m_Parent->OnCollisionProximity(id, prox.first, "LEAVE"); } } } diff --git a/dGame/dComponents/ProximityMonitorComponent.h b/dGame/dComponents/ProximityMonitorComponent.h index 512b2848..e80f1b5b 100644 --- a/dGame/dComponents/ProximityMonitorComponent.h +++ b/dGame/dComponents/ProximityMonitorComponent.h @@ -6,6 +6,8 @@ #ifndef PROXIMITYMONITORCOMPONENT_H #define PROXIMITYMONITORCOMPONENT_H +#include + #include "BitStream.h" #include "Entity.h" #include "dpWorld.h" @@ -42,9 +44,9 @@ public: /** * Returns the last of entities that are used to check proximity, given a name * @param name the proximity name to retrieve physics objects for - * @return a map of physics entities for this name, indexed by object ID + * @return a set of physics entity object IDs for this name */ - const std::map& GetProximityObjects(const std::string& name); + const std::unordered_set& GetProximityObjects(const std::string& name); /** * Checks if the passed object is in proximity of the named proximity sensor @@ -70,7 +72,7 @@ private: /** * Default value for the proximity data */ - static const std::map m_EmptyObjectMap; + static const std::unordered_set m_EmptyObjectSet; }; #endif // PROXIMITYMONITORCOMPONENT_H 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/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 2e6edacd..d926ff2c 100644 --- a/dGame/dComponents/SkillComponent.cpp +++ b/dGame/dComponents/SkillComponent.cpp @@ -99,7 +99,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(); @@ -227,7 +227,7 @@ void SkillComponent::RegisterCalculatedProjectile(const LWOOBJID projectileId, B this->m_managedProjectiles.push_back(entry); } -bool SkillComponent::CastSkill(const uint32_t skillId, LWOOBJID target, const LWOOBJID optionalOriginatorID) { +bool SkillComponent::CastSkill(const uint32_t skillId, LWOOBJID target, const LWOOBJID optionalOriginatorID, const int32_t castType, const NiQuaternion rotationOverride) { uint32_t behaviorId = -1; // try to find it via the cache const auto& pair = m_skillBehaviorCache.find(skillId); @@ -247,11 +247,19 @@ bool SkillComponent::CastSkill(const uint32_t skillId, LWOOBJID target, const LW return false; } - return CalculateBehavior(skillId, behaviorId, target, false, false, optionalOriginatorID).success; + return CalculateBehavior(skillId, behaviorId, target, false, false, optionalOriginatorID, castType, rotationOverride).success; } -SkillExecutionResult SkillComponent::CalculateBehavior(const uint32_t skillId, const uint32_t behaviorId, const LWOOBJID target, const bool ignoreTarget, const bool clientInitalized, const LWOOBJID originatorOverride) { +SkillExecutionResult SkillComponent::CalculateBehavior( + const uint32_t skillId, + const uint32_t behaviorId, + const LWOOBJID target, + const bool ignoreTarget, + const bool clientInitalized, + const LWOOBJID originatorOverride, + const int32_t castType, + const NiQuaternion rotationOverride) { RakNet::BitStream bitStream{}; auto* behavior = Behavior::CreateBehavior(behaviorId); @@ -283,7 +291,7 @@ SkillExecutionResult SkillComponent::CalculateBehavior(const uint32_t skillId, c // Echo start skill EchoStartSkill start; - start.iCastType = 0; + start.iCastType = castType; start.skillID = skillId; start.uiSkillHandle = context->skillUId; start.optionalOriginatorID = context->originator; @@ -294,6 +302,10 @@ SkillExecutionResult SkillComponent::CalculateBehavior(const uint32_t skillId, c if (originator != nullptr) { start.originatorRot = originator->GetRotation(); } + + if (rotationOverride != NiQuaternionConstant::IDENTITY) { + start.originatorRot = rotationOverride; + } //start.optionalTargetID = target; start.sBitStream.assign(reinterpret_cast(bitStream.GetData()), bitStream.GetNumberOfBytesUsed()); @@ -413,7 +425,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(); @@ -464,7 +476,7 @@ void SkillComponent::HandleUnCast(const uint32_t behaviorId, const LWOOBJID targ behavior->UnCast(&context, { target }); } -SkillComponent::SkillComponent(Entity* parent): Component(parent) { +SkillComponent::SkillComponent(Entity* parent) : Component(parent) { this->m_skillUid = 0; } diff --git a/dGame/dComponents/SkillComponent.h b/dGame/dComponents/SkillComponent.h index 2acae5d7..24d92148 100644 --- a/dGame/dComponents/SkillComponent.h +++ b/dGame/dComponents/SkillComponent.h @@ -127,7 +127,7 @@ public: * @param optionalOriginatorID change the originator of the skill * @return if the case succeeded */ - bool CastSkill(const uint32_t skillId, LWOOBJID target = LWOOBJID_EMPTY, const LWOOBJID optionalOriginatorID = LWOOBJID_EMPTY); + bool CastSkill(const uint32_t skillId, LWOOBJID target = LWOOBJID_EMPTY, const LWOOBJID optionalOriginatorID = LWOOBJID_EMPTY, const int32_t castType = 0, const NiQuaternion rotationOverride = NiQuaternionConstant::IDENTITY); /** * Initializes a server-side skill calculation. @@ -139,7 +139,7 @@ public: * @param originatorOverride an override for the originator of the skill calculation * @return the result of the skill calculation */ - SkillExecutionResult CalculateBehavior(uint32_t skillId, uint32_t behaviorId, LWOOBJID target, bool ignoreTarget = false, bool clientInitalized = false, LWOOBJID originatorOverride = LWOOBJID_EMPTY); + SkillExecutionResult CalculateBehavior(uint32_t skillId, uint32_t behaviorId, LWOOBJID target, bool ignoreTarget = false, bool clientInitalized = false, LWOOBJID originatorOverride = LWOOBJID_EMPTY, const int32_t castType = 0, const NiQuaternion rotationOverride = NiQuaternionConstant::IDENTITY); /** * Register a server-side projectile. diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 53a77777..05a6af84 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -2676,17 +2676,11 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent info.spawnerID = entity->GetObjectID(); info.spawnerNodeID = 0; - LDFBaseData* ldfBlueprintID = new LDFData(u"blueprintid", blueprintID); - LDFBaseData* componentWhitelist = new LDFData(u"componentWhitelist", 1); - LDFBaseData* modelType = new LDFData(u"modelType", 2); - LDFBaseData* propertyObjectID = new LDFData(u"propertyObjectID", true); - LDFBaseData* userModelID = new LDFData(u"userModelID", newIDL); - - info.settings.push_back(ldfBlueprintID); - info.settings.push_back(componentWhitelist); - info.settings.push_back(modelType); - info.settings.push_back(propertyObjectID); - info.settings.push_back(userModelID); + info.settings.push_back(new LDFData(u"blueprintid", blueprintID)); + info.settings.push_back(new LDFData(u"componentWhitelist", 1)); + info.settings.push_back(new LDFData(u"modelType", 2)); + info.settings.push_back(new LDFData(u"propertyObjectID", true)); + info.settings.push_back(new LDFData(u"userModelID", newIDL)); Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr); if (newEntity) { @@ -4706,7 +4700,7 @@ void GameMessages::HandleBuyFromVendor(RakNet::BitStream& inStream, Entity* enti if (!user) return; Entity* player = Game::entityManager->GetEntity(user->GetLoggedInChar()); if (!player) return; - + // handle buying normal items auto* vendorComponent = entity->GetComponent(); if (vendorComponent) { @@ -5336,7 +5330,7 @@ void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream& inStream, En bool iLootTypeSourceIsDefault = false; LWOOBJID iLootTypeSource = LWOOBJID_EMPTY; bool iObjIDIsDefault = false; - LWOOBJID iObjID; + LWOOBJID iObjID = LWOOBJID_EMPTY; bool iObjTemplateIsDefault = false; LOT iObjTemplate = LOT_NULL; bool iRequestingObjIDIsDefault = false; @@ -6243,3 +6237,15 @@ void GameMessages::HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Ent if (!characterComponent) return; characterComponent->SetCurrentInteracting(LWOOBJID_EMPTY); } + +void GameMessages::SendSlashCommandFeedbackText(Entity* entity, std::u16string text) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(entity->GetObjectID()); + bitStream.Write(eGameMessageType::SLASH_COMMAND_TEXT_FEEDBACK); + bitStream.Write(text.size()); + bitStream.Write(text); + auto sysAddr = entity->GetSystemAddress(); + SEND_PACKET; +} diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 4806adba..c0c71b96 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -685,6 +685,8 @@ namespace GameMessages { void HandleRemoveDonationItem(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); void HandleConfirmDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity); void HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity); + + void SendSlashCommandFeedbackText(Entity* entity, std::u16string text); }; #endif // GAMEMESSAGES_H diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index d0acef4d..1589d964 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -405,18 +405,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, '/'); 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 4ed80bf3..c2ed2a42 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -65,24 +65,24 @@ Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) { } } -void Mission::LoadFromXml(tinyxml2::XMLElement* element) { +void Mission::LoadFromXml(const tinyxml2::XMLElement& element) { // Start custom XML - if (element->Attribute("state") != nullptr) { - m_State = static_cast(std::stoul(element->Attribute("state"))); + if (element.Attribute("state") != nullptr) { + m_State = static_cast(std::stoul(element.Attribute("state"))); } // End custom XML - if (element->Attribute("cct") != nullptr) { - m_Completions = std::stoul(element->Attribute("cct")); + if (element.Attribute("cct") != nullptr) { + m_Completions = std::stoul(element.Attribute("cct")); - m_Timestamp = std::stoul(element->Attribute("cts")); + m_Timestamp = std::stoul(element.Attribute("cts")); if (IsComplete()) { return; } } - auto* task = element->FirstChildElement(); + auto* task = element.FirstChildElement(); auto index = 0U; @@ -132,19 +132,19 @@ void Mission::LoadFromXml(tinyxml2::XMLElement* element) { } } -void Mission::UpdateXml(tinyxml2::XMLElement* element) { +void Mission::UpdateXml(tinyxml2::XMLElement& element) { // Start custom XML - element->SetAttribute("state", static_cast(m_State)); + element.SetAttribute("state", static_cast(m_State)); // End custom XML - element->DeleteChildren(); + element.DeleteChildren(); - element->SetAttribute("id", static_cast(info.id)); + element.SetAttribute("id", static_cast(info.id)); if (m_Completions > 0) { - element->SetAttribute("cct", static_cast(m_Completions)); + element.SetAttribute("cct", static_cast(m_Completions)); - element->SetAttribute("cts", static_cast(m_Timestamp)); + element.SetAttribute("cts", static_cast(m_Timestamp)); if (IsComplete()) { return; @@ -155,27 +155,27 @@ void Mission::UpdateXml(tinyxml2::XMLElement* element) { if (task->GetType() == eMissionTaskType::COLLECTION || task->GetType() == eMissionTaskType::VISIT_PROPERTY) { - auto* child = element->GetDocument()->NewElement("sv"); + auto* child = element.GetDocument()->NewElement("sv"); child->SetAttribute("v", static_cast(task->GetProgress())); - element->LinkEndChild(child); + element.LinkEndChild(child); for (auto unique : task->GetUnique()) { - auto* uniqueElement = element->GetDocument()->NewElement("sv"); + auto* uniqueElement = element.GetDocument()->NewElement("sv"); uniqueElement->SetAttribute("v", static_cast(unique)); - element->LinkEndChild(uniqueElement); + element.LinkEndChild(uniqueElement); } break; } - auto* child = element->GetDocument()->NewElement("sv"); + auto* child = element.GetDocument()->NewElement("sv"); child->SetAttribute("v", static_cast(task->GetProgress())); - element->LinkEndChild(child); + element.LinkEndChild(child); } } diff --git a/dGame/dMission/Mission.h b/dGame/dMission/Mission.h index d8c104e8..74b8d352 100644 --- a/dGame/dMission/Mission.h +++ b/dGame/dMission/Mission.h @@ -28,8 +28,8 @@ public: Mission(MissionComponent* missionComponent, uint32_t missionId); ~Mission(); - void LoadFromXml(tinyxml2::XMLElement* element); - void UpdateXml(tinyxml2::XMLElement* element); + void LoadFromXml(const tinyxml2::XMLElement& element); + void UpdateXml(tinyxml2::XMLElement& element); /** * Returns the ID of this mission diff --git a/dGame/dUtilities/BrickDatabase.cpp b/dGame/dUtilities/BrickDatabase.cpp index 7b447b84..61a7341d 100644 --- a/dGame/dUtilities/BrickDatabase.cpp +++ b/dGame/dUtilities/BrickDatabase.cpp @@ -29,15 +29,14 @@ const BrickList& BrickDatabase::GetBricks(const LxfmlPath& lxfmlPath) { return emptyCache; } - auto* doc = new tinyxml2::XMLDocument(); - if (doc->Parse(data.str().c_str(), data.str().size()) != 0) { - delete doc; + tinyxml2::XMLDocument doc; + if (doc.Parse(data.str().c_str(), data.str().size()) != 0) { return emptyCache; } BrickList parts; - auto* lxfml = doc->FirstChildElement("LXFML"); + auto* lxfml = doc.FirstChildElement("LXFML"); auto* bricks = lxfml->FirstChildElement("Bricks"); std::string searchTerm = "Brick"; @@ -86,7 +85,5 @@ const BrickList& BrickDatabase::GetBricks(const LxfmlPath& lxfmlPath) { m_Cache[lxfmlPath] = parts; - delete doc; - return m_Cache[lxfmlPath]; } diff --git a/dGame/dUtilities/CMakeLists.txt b/dGame/dUtilities/CMakeLists.txt index 1e54b59b..2202fddc 100644 --- a/dGame/dUtilities/CMakeLists.txt +++ b/dGame/dUtilities/CMakeLists.txt @@ -8,9 +8,15 @@ set(DGAME_DUTILITIES_SOURCES "BrickDatabase.cpp" "SlashCommandHandler.cpp" "VanityUtilities.cpp") +add_subdirectory(SlashCommands) + +foreach(file ${DGAME_DUTILITIES_SLASHCOMMANDS}) + set(DGAME_DUTILITIES_SOURCES ${DGAME_DUTILITIES_SOURCES} "SlashCommands/${file}") +endforeach() + add_library(dUtilities OBJECT ${DGAME_DUTILITIES_SOURCES}) target_precompile_headers(dUtilities REUSE_FROM dGameBase) -target_include_directories(dUtilities PUBLIC "." +target_include_directories(dUtilities PUBLIC "." "SlashCommands" PRIVATE "${PROJECT_SOURCE_DIR}/dGame/dComponents" "${PROJECT_SOURCE_DIR}/dGame/dInventory" # transitive via PossessableComponent.h diff --git a/dGame/dUtilities/Preconditions.cpp b/dGame/dUtilities/Preconditions.cpp index bd855962..25e8b773 100644 --- a/dGame/dUtilities/Preconditions.cpp +++ b/dGame/dUtilities/Preconditions.cpp @@ -33,10 +33,10 @@ Precondition::Precondition(const uint32_t 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 +45,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(); } diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 817df797..bf7e9eb4 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -1,2253 +1,114 @@ /* * Darkflame Universe - * Copyright 2018 + * Copyright 2024 */ + + #include "SlashCommandHandler.h" -#include -#include -#include -#include -#include "dZoneManager.h" +#include -#include /* defines FILENAME_MAX */ -#ifdef _WIN32 -#include -#define GetCurrentDir _getcwd -#else -#include -#define GetCurrentDir getcwd -#endif +#include "DEVGMCommands.h" +#include "GMGreaterThanZeroCommands.h" +#include "GMZeroCommands.h" -#include "Metrics.hpp" - -#include "User.h" -#include "UserManager.h" -#include "BitStream.h" -#include "dCommonVars.h" -#include "GeneralUtils.h" -#include "Entity.h" -#include "EntityManager.h" -#include "Logger.h" -#include "WorldPackets.h" -#include "GameMessages.h" -#include "CDClientDatabase.h" -#include "ZoneInstanceManager.h" -#include "ControllablePhysicsComponent.h" -#include "NiPoint3.h" -#include "NiQuaternion.h" -#include "ChatPackets.h" -#include "InventoryComponent.h" -#include "Game.h" -#include "CharacterComponent.h" -#include "Database.h" -#include "DestroyableComponent.h" -#include "dServer.h" -#include "MissionComponent.h" -#include "Mail.h" -#include "PetComponent.h" -#include "dpWorld.h" -#include "Item.h" -#include "PropertyManagementComponent.h" -#include "BitStreamUtils.h" -#include "Loot.h" -#include "EntityInfo.h" -#include "LUTriggers.h" -#include "PhantomPhysicsComponent.h" -#include "ProximityMonitorComponent.h" -#include "dpShapeSphere.h" -#include "PossessableComponent.h" -#include "PossessorComponent.h" -#include "StringifiedEnum.h" -#include "HavokVehiclePhysicsComponent.h" -#include "BuffComponent.h" -#include "SkillComponent.h" -#include "VanityUtilities.h" -#include "ScriptedActivityComponent.h" -#include "LevelProgressionComponent.h" -#include "AssetManager.h" -#include "BinaryPathFinder.h" -#include "dConfig.h" -#include "eBubbleType.h" #include "Amf3.h" -#include "MovingPlatformComponent.h" -#include "eMissionState.h" -#include "TriggerComponent.h" -#include "eServerDisconnectIdentifiers.h" -#include "eObjectBits.h" -#include "eGameMasterLevel.h" -#include "eReplicaComponentType.h" -#include "RenderComponent.h" -#include "eControlScheme.h" -#include "eConnectionType.h" -#include "eChatInternalMessageType.h" -#include "eHelpType.h" -#include "eMasterMessageType.h" -#include "PlayerManager.h" +#include "Database.h" +#include "eChatMessageType.h" +#include "dServer.h" -#include "CDRewardCodesTable.h" -#include "CDObjectsTable.h" -#include "CDZoneTableTable.h" -#include "ePlayerFlag.h" -#include "dNavMesh.h" +namespace { + std::vector CommandInfos; + std::map RegisteredCommands; +} -void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr) { - auto commandCopy = command; - // Sanity check that a command was given - if (command.empty() || command.front() != u'/') return; - commandCopy.erase(commandCopy.begin()); - - // Split the command by spaces - std::string chatCommand; - std::vector args; - auto wideCommand = GeneralUtils::SplitString(commandCopy, u' '); - if (wideCommand.empty()) return; - - // Convert the command to lowercase - chatCommand = GeneralUtils::UTF16ToWTF8(wideCommand.front()); - std::transform(chatCommand.begin(), chatCommand.end(), chatCommand.begin(), ::tolower); - wideCommand.erase(wideCommand.begin()); - - // Convert the arguements to not u16strings - for (auto wideArg : wideCommand) args.push_back(GeneralUtils::UTF16ToWTF8(wideArg)); - - User* user = UserManager::Instance()->GetUser(sysAddr); - if ((chatCommand == "setgmlevel" || chatCommand == "makegm" || chatCommand == "gmlevel") && user->GetMaxGMLevel() > eGameMasterLevel::CIVILIAN) { - if (args.size() != 1) return; - - const auto level_intermed = GeneralUtils::TryParse(args[0]); - - if (!level_intermed) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid gm level."); - return; - } - eGameMasterLevel level = static_cast(level_intermed.value()); - -#ifndef DEVELOPER_SERVER - if (user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER) { - level = eGameMasterLevel::CIVILIAN; - } -#endif - - if (level > user->GetMaxGMLevel()) { - level = user->GetMaxGMLevel(); - } - - if (level == entity->GetGMLevel()) return; - bool success = user->GetMaxGMLevel() >= level; - - if (success) { - - if (entity->GetGMLevel() > eGameMasterLevel::CIVILIAN && level == eGameMasterLevel::CIVILIAN) { - GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); - } else if (entity->GetGMLevel() == eGameMasterLevel::CIVILIAN && level > eGameMasterLevel::CIVILIAN) { - GameMessages::SendToggleGMInvis(entity->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS); - } - - WorldPackets::SendGMLevelChange(sysAddr, success, user->GetMaxGMLevel(), entity->GetGMLevel(), level); - GameMessages::SendChatModeUpdate(entity->GetObjectID(), level); - entity->SetGMLevel(level); - LOG("User %s (%i) has changed their GM level to %i for charID %llu", user->GetUsername().c_str(), user->GetAccountID(), level, entity->GetObjectID()); - } - } - -#ifndef DEVELOPER_SERVER - if ((entity->GetGMLevel() > user->GetMaxGMLevel()) || (entity->GetGMLevel() > eGameMasterLevel::CIVILIAN && user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER)) { - WorldPackets::SendGMLevelChange(sysAddr, true, user->GetMaxGMLevel(), entity->GetGMLevel(), eGameMasterLevel::CIVILIAN); - GameMessages::SendChatModeUpdate(entity->GetObjectID(), eGameMasterLevel::CIVILIAN); - entity->SetGMLevel(eGameMasterLevel::CIVILIAN); - - GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); - - ChatPackets::SendSystemMessage(sysAddr, u"Your game master level has been changed, you may not be able to use all commands."); - } -#endif - - if (chatCommand == "togglenameplate" && (Game::config->GetValue("allow_nameplate_off") == "1" || entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER)) { - auto* character = entity->GetCharacter(); - - if (character && character->GetBillboardVisible()) { - character->SetBillboardVisible(false); - ChatPackets::SendSystemMessage(sysAddr, u"Your nameplate has been turned off and is not visible to players currently in this zone."); - } else { - character->SetBillboardVisible(true); - ChatPackets::SendSystemMessage(sysAddr, u"Your nameplate is now on and visible to all players."); - } +void SlashCommandHandler::RegisterCommand(Command command) { + if (command.aliases.empty()) { + LOG("Command %s has no aliases! Skipping!", command.help.c_str()); return; } - if (chatCommand == "toggleskipcinematics" && (Game::config->GetValue("allow_players_to_skip_cinematics") == "1" || entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER)) { - auto* character = entity->GetCharacter(); - if (!character) return; - bool current = character->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS); - character->SetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS, !current); - if (!current) { - ChatPackets::SendSystemMessage(sysAddr, u"You have elected to skip cinematics. Note that not all cinematics can be skipped, but most will be skipped now."); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Cinematics will no longer be skipped."); - } - - return; - } - - - //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - //HANDLE ALL NON GM SLASH COMMANDS RIGHT HERE! - //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - if (chatCommand == "pvp") { - auto* character = entity->GetComponent(); - - if (character == nullptr) { - LOG("Failed to find character component!"); - return; - } - - character->SetPvpEnabled(!character->GetPvpEnabled()); - Game::entityManager->SerializeEntity(entity); - - std::stringstream message; - message << character->GetName() << " changed their PVP flag to " << std::to_string(character->GetPvpEnabled()) << "!"; - - ChatPackets::SendSystemMessage(UNASSIGNED_SYSTEM_ADDRESS, GeneralUtils::UTF8ToUTF16(message.str()), true); - - return; - } - - if (chatCommand == "who") { - ChatPackets::SendSystemMessage( - sysAddr, - u"Players in this instance: (" + GeneralUtils::to_u16string(PlayerManager::GetAllPlayers().size()) + u")" - ); - - for (auto* player : PlayerManager::GetAllPlayers()) { - const auto& name = player->GetCharacter()->GetName(); - - ChatPackets::SendSystemMessage( - sysAddr, - GeneralUtils::UTF8ToUTF16(player == entity ? name + " (you)" : name) - ); + 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) { + LOG_DEBUG("Command alias %s is already registered! Skipping!", alias.c_str()); + continue; } } - if (chatCommand == "ping") { - if (!args.empty() && args[0] == "-l") { - std::stringstream message; - message << "Your latest ping: " << std::to_string(Game::server->GetLatestPing(sysAddr)) << "ms"; + CommandInfos.push_back(command); +}; - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(message.str())); - } else { - std::stringstream message; - message << "Your average ping: " << std::to_string(Game::server->GetPing(sysAddr)) << "ms"; +void SlashCommandHandler::HandleChatCommand(const std::u16string& chat, Entity* entity, const SystemAddress& sysAddr) { + auto input = GeneralUtils::UTF16ToWTF8(chat); + if (input.empty() || input.front() != '/') return; + const auto pos = input.find(' '); + std::string command = input.substr(1, pos - 1); - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(message.str())); + std::string args; + // make sure the space exists and isn't the last character + if (pos != std::string::npos && pos != input.size()) args = input.substr(input.find(' ') + 1); + LOG_DEBUG("Handling command \"%s\" with args \"%s\"", command.c_str(), args.c_str()); + + const auto commandItr = RegisteredCommands.find(command); + std::string error; + if (commandItr != RegisteredCommands.end()) { + auto& [alias, commandHandle] = *commandItr; + if (entity->GetGMLevel() >= commandHandle.requiredLevel) { + 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 + "\""; } - return; + } else if (entity->GetGMLevel() == eGameMasterLevel::CIVILIAN) { + // We don't need to tell civilians commands don't exist + error = "Command " + command + " does not exist!"; } - if (chatCommand == "fix-stats") { - // Reset skill component and buff component - auto* skillComponent = entity->GetComponent(); - auto* buffComponent = entity->GetComponent(); - auto* destroyableComponent = entity->GetComponent(); - - // If any of the components are nullptr, return - if (skillComponent == nullptr || buffComponent == nullptr || destroyableComponent == nullptr) { - return; - } - - // Reset skill component - skillComponent->Reset(); - - // Reset buff component - buffComponent->Reset(); - - // Fix the destroyable component - destroyableComponent->FixStats(); + if (!error.empty()) { + GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(error)); } +} - if (chatCommand == "credits" || chatCommand == "info") { - const auto& customText = chatCommand == "credits" ? VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/CREDITS.md").string()) : VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/INFO.md").string()); - - { - AMFArrayValue args; - - args.Insert("state", "Story"); - - GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "pushGameState", args); +// 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; } + } else { + bool foundCommand = false; + for (const auto& command : CommandInfos) { + if (std::ranges::find(command.aliases, args) == command.aliases.end()) continue; - entity->AddCallbackTimer(0.5f, [customText, entity]() { - AMFArrayValue args; + 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; - args.Insert("visible", true); - args.Insert("text", customText); - - LOG("Sending %s", customText.c_str()); - - GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "ToggleStoryBox", args); - }); - - return; - } - - if (chatCommand == "leave-zone" || chatCommand == "leavezone") { - const auto currentZone = Game::zoneManager->GetZone()->GetZoneID().GetMapID(); - LWOMAPID newZone = 0; - - if (currentZone == 1001 || currentZone % 100 == 0) { - ChatPackets::SendSystemMessage(sysAddr, u"You are not in an instanced zone."); - return; - } else { - newZone = (currentZone / 100) * 100; - } - // If new zone would be inaccessible, then default to Avant Gardens. - if (!Game::zoneManager->CheckIfAccessibleZone(newZone)) newZone = 1100; - - ChatPackets::SendSystemMessage(sysAddr, u"Leaving zone..."); - - const auto objid = entity->GetObjectID(); - - ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, newZone, 0, false, [objid](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { - auto* entity = Game::entityManager->GetEntity(objid); - - if (entity == nullptr) { - return; - } - - const auto sysAddr = entity->GetSystemAddress(); - - LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", entity->GetCharacter()->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); - - if (entity->GetCharacter()) { - entity->GetCharacter()->SetZoneID(zoneID); - entity->GetCharacter()->SetZoneInstance(zoneInstance); - entity->GetCharacter()->SetZoneClone(zoneClone); - } - - entity->GetCharacter()->SaveXMLToDatabase(); - - WorldPackets::SendTransferToWorld(sysAddr, serverIP, serverPort, mythranShift); - }); - } - - if (chatCommand == "join" && !args.empty()) { - ChatPackets::SendSystemMessage(sysAddr, u"Requesting private map..."); - const auto& password = args[0]; - - ZoneInstanceManager::Instance()->RequestPrivateZone(Game::server, false, password, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { - LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); - - if (entity->GetCharacter()) { - entity->GetCharacter()->SetZoneID(zoneID); - entity->GetCharacter()->SetZoneInstance(zoneInstance); - entity->GetCharacter()->SetZoneClone(zoneClone); - } - - entity->GetCharacter()->SaveXMLToDatabase(); - - WorldPackets::SendTransferToWorld(sysAddr, serverIP, serverPort, mythranShift); - }); - } - - if (user->GetMaxGMLevel() == eGameMasterLevel::CIVILIAN || entity->GetGMLevel() >= eGameMasterLevel::CIVILIAN) { - if (chatCommand == "die") { - entity->Smash(entity->GetObjectID()); - } - - if (chatCommand == "resurrect") { - ScriptedActivityComponent* scriptedActivityComponent = Game::zoneManager->GetZoneControlObject()->GetComponent(); - - if (scriptedActivityComponent) { // check if user is in activity world and if so, they can't resurrect - ChatPackets::SendSystemMessage(sysAddr, u"You cannot resurrect in an activity world."); - return; - } - - GameMessages::SendResurrect(entity); - } - - if (chatCommand == "requestmailcount") { - Mail::HandleNotificationRequest(entity->GetSystemAddress(), entity->GetObjectID()); - } - - if (chatCommand == "instanceinfo") { - const auto zoneId = Game::zoneManager->GetZone()->GetZoneID(); - - ChatPackets::SendSystemMessage(sysAddr, u"Map: " + (GeneralUtils::to_u16string(zoneId.GetMapID())) + u"\nClone: " + (GeneralUtils::to_u16string(zoneId.GetCloneID())) + u"\nInstance: " + (GeneralUtils::to_u16string(zoneId.GetInstanceID()))); - } - - if (entity->GetGMLevel() == eGameMasterLevel::CIVILIAN) return; - } - - if (chatCommand == "resetmission" && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto missionId = GeneralUtils::TryParse(args[0]); - if (!missionId) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission ID."); - return; - } - - auto* missionComponent = entity->GetComponent(); - if (!missionComponent) return; - missionComponent->ResetMission(missionId.value()); - } - - // Log command to database - Database::Get()->InsertSlashCommandUsage(entity->GetObjectID(), chatCommand); - - if (chatCommand == "setminifig" && args.size() == 2 && entity->GetGMLevel() >= eGameMasterLevel::FORUM_MODERATOR) { // could break characters so only allow if GM > 0 - const auto minifigItemIdExists = GeneralUtils::TryParse(args[1]); - if (!minifigItemIdExists) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid Minifig Item Id ID."); - return; - } - const int32_t minifigItemId = minifigItemIdExists.value(); - Game::entityManager->DestructEntity(entity, sysAddr); - auto* charComp = entity->GetComponent(); - std::string lowerName = args[0]; - if (lowerName.empty()) return; - std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); - if (lowerName == "eyebrows") { - charComp->m_Character->SetEyebrows(minifigItemId); - } else if (lowerName == "eyes") { - charComp->m_Character->SetEyes(minifigItemId); - } else if (lowerName == "haircolor") { - charComp->m_Character->SetHairColor(minifigItemId); - } else if (lowerName == "hairstyle") { - charComp->m_Character->SetHairStyle(minifigItemId); - } else if (lowerName == "pants") { - charComp->m_Character->SetPantsColor(minifigItemId); - } else if (lowerName == "lefthand") { - charComp->m_Character->SetLeftHand(minifigItemId); - } else if (lowerName == "mouth") { - charComp->m_Character->SetMouth(minifigItemId); - } else if (lowerName == "righthand") { - charComp->m_Character->SetRightHand(minifigItemId); - } else if (lowerName == "shirtcolor") { - charComp->m_Character->SetShirtColor(minifigItemId); - } else if (lowerName == "hands") { - charComp->m_Character->SetLeftHand(minifigItemId); - charComp->m_Character->SetRightHand(minifigItemId); - } else { - Game::entityManager->ConstructEntity(entity); - ChatPackets::SendSystemMessage(sysAddr, u"Invalid Minifig item to change, try one of the following: Eyebrows, Eyes, HairColor, HairStyle, Pants, LeftHand, Mouth, RightHand, Shirt, Hands"); - return; - } - - Game::entityManager->ConstructEntity(entity); - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(lowerName) + u" set to " + (GeneralUtils::to_u16string(minifigItemId))); - - GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); // need to retoggle because it gets reenabled on creation of new character - } - - if ((chatCommand == "playanimation" || chatCommand == "playanim") && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - std::u16string anim = GeneralUtils::ASCIIToUTF16(args[0], args[0].size()); - RenderComponent::PlayAnimation(entity, anim); - auto* possessorComponent = entity->GetComponent(); - if (possessorComponent) { - auto* possessedComponent = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); - if (possessedComponent) RenderComponent::PlayAnimation(possessedComponent, anim); - } - } - - if (chatCommand == "list-spawns" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - for (const auto& pair : Game::entityManager->GetSpawnPointEntities()) { - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(pair.first)); - } - - ChatPackets::SendSystemMessage(sysAddr, u"Current: " + GeneralUtils::ASCIIToUTF16(entity->GetCharacter()->GetTargetScene())); - - return; - } - - if (chatCommand == "unlock-emote" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto emoteID = GeneralUtils::TryParse(args.at(0)); - - if (!emoteID) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid emote ID."); - return; - } - - entity->GetCharacter()->UnlockEmote(emoteID.value()); - } - - if (chatCommand == "force-save" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - entity->GetCharacter()->SaveXMLToDatabase(); - } - - if (chatCommand == "kill" && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - ChatPackets::SendSystemMessage(sysAddr, u"Brutally murdering that player, if online on this server."); - - auto* player = PlayerManager::GetPlayer(args[0]); - if (player) { - player->Smash(entity->GetObjectID()); - ChatPackets::SendSystemMessage(sysAddr, u"It has been done, do you feel good about yourself now?"); - return; - } - - ChatPackets::SendSystemMessage(sysAddr, u"They were saved from your carnage."); - return; - } - - if (chatCommand == "speedboost" && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto boostOptional = GeneralUtils::TryParse(args[0]); - if (!boostOptional) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid boost."); - return; - } - const float boost = boostOptional.value(); - - auto* controllablePhysicsComponent = entity->GetComponent(); - - if (!controllablePhysicsComponent) return; - controllablePhysicsComponent->SetSpeedMultiplier(boost); - - // speedboost possessables - auto possessor = entity->GetComponent(); - if (possessor) { - auto possessedID = possessor->GetPossessable(); - if (possessedID != LWOOBJID_EMPTY) { - auto possessable = Game::entityManager->GetEntity(possessedID); - if (possessable) { - auto* possessControllablePhysicsComponent = possessable->GetComponent(); - if (possessControllablePhysicsComponent) { - possessControllablePhysicsComponent->SetSpeedMultiplier(boost); - } - } + feedback << "\nAliases: "; + for (size_t i = 0; i < command.aliases.size(); i++) { + if (i > 0) feedback << ", "; + feedback << command.aliases[i]; } } - Game::entityManager->SerializeEntity(entity); - } - - if (chatCommand == "freecam" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto state = !entity->GetVar(u"freecam"); - entity->SetVar(u"freecam", state); - - GameMessages::SendSetPlayerControlScheme(entity, static_cast(state ? 9 : 1)); - - ChatPackets::SendSystemMessage(sysAddr, u"Toggled freecam."); - return; - } - - if (chatCommand == "setcontrolscheme" && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto scheme = GeneralUtils::TryParse(args[0]); - - if (!scheme) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid control scheme."); - return; - } - - GameMessages::SendSetPlayerControlScheme(entity, static_cast(scheme.value())); - - ChatPackets::SendSystemMessage(sysAddr, u"Switched control scheme."); - return; - } - - if (chatCommand == "approveproperty" && entity->GetGMLevel() >= eGameMasterLevel::LEAD_MODERATOR) { - - if (PropertyManagementComponent::Instance() != nullptr) { - PropertyManagementComponent::Instance()->UpdateApprovedStatus(true); - } - - return; - } - - if (chatCommand == "setuistate" && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - AMFArrayValue uiState; - - uiState.Insert("state", args.at(0)); - - GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, "pushGameState", uiState); - - ChatPackets::SendSystemMessage(sysAddr, u"Switched UI state."); - - return; - } - - if (chatCommand == "toggle" && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - AMFArrayValue amfArgs; - - amfArgs.Insert("visible", true); - - GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, args[0], amfArgs); - - ChatPackets::SendSystemMessage(sysAddr, u"Toggled UI state."); - - return; - } - - if ((chatCommand == "setinventorysize" || chatCommand == "setinvsize") && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - const auto sizeOptional = GeneralUtils::TryParse(args[0]); - if (!sizeOptional) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid size."); - return; - } - const uint32_t size = sizeOptional.value(); - - eInventoryType selectedInventory = eInventoryType::ITEMS; - - // a possible inventory was provided if we got more than 1 argument - if (args.size() >= 2) { - selectedInventory = GeneralUtils::TryParse(args.at(1)).value_or(eInventoryType::INVALID); - if (selectedInventory == eInventoryType::INVALID) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid inventory."); - return; - } else { - // In this case, we treat the input as a string and try to find it in the reflection list - std::transform(args.at(1).begin(), args.at(1).end(), args.at(1).begin(), ::toupper); - for (uint32_t index = 0; index < NUMBER_OF_INVENTORIES; index++) { - if (std::string_view(args.at(1)) == std::string_view(InventoryType::InventoryTypeToString(static_cast(index)))) selectedInventory = static_cast(index); - } - } - - ChatPackets::SendSystemMessage(sysAddr, u"Setting inventory " + - GeneralUtils::ASCIIToUTF16(args.at(1)) + - u" to size " + - GeneralUtils::to_u16string(size)); - } else ChatPackets::SendSystemMessage(sysAddr, u"Setting inventory ITEMS to size " + GeneralUtils::to_u16string(size)); - - auto* inventoryComponent = entity->GetComponent(); - if (inventoryComponent) { - auto* inventory = inventoryComponent->GetInventory(selectedInventory); - - inventory->SetSize(size); - } - - return; - } - - if (chatCommand == "runmacro" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() != 1) return; - - // Only process if input does not contain separator charaters - if (args[0].find("/") != std::string::npos) return; - if (args[0].find("\\") != std::string::npos) return; - - auto infile = Game::assetManager->GetFile(("macros/" + args[0] + ".scm").c_str()); - - if (!infile) { - ChatPackets::SendSystemMessage(sysAddr, u"Unknown macro! Is the filename right?"); - return; - } - - if (infile.good()) { - std::string line; - while (std::getline(infile, line)) { - // Do this in two separate calls to catch both \n and \r\n - line.erase(std::remove(line.begin(), line.end(), '\n'), line.end()); - line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); - SlashCommandHandler::HandleChatCommand(GeneralUtils::ASCIIToUTF16(line), entity, sysAddr); - } - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Unknown macro! Is the filename right?"); - } - - return; - } - - if (chatCommand == "addmission" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() == 0) return; - - const auto missionID = GeneralUtils::TryParse(args.at(0)); - - if (!missionID) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission id."); - return; - } - - auto comp = static_cast(entity->GetComponent(eReplicaComponentType::MISSION)); - if (comp) comp->AcceptMission(missionID.value(), true); - return; - } - - if (chatCommand == "completemission" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() == 0) return; - - const auto missionID = GeneralUtils::TryParse(args.at(0)); - - if (!missionID) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission id."); - return; - } - - auto comp = static_cast(entity->GetComponent(eReplicaComponentType::MISSION)); - if (comp) comp->CompleteMission(missionID.value(), true); - return; - } - - // Send packet to display help message pop-up - if (chatCommand == "helpmsg") { - if (entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) { - const auto helpIdOpt = GeneralUtils::TryParse(args[0]); - if (!helpIdOpt) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid help message id."); - return; - } - - const eHelpType helpId = helpIdOpt.value(); - GameMessages::SendHelp(entity->GetObjectID(), helpId, sysAddr); - - // Convert and print enum string - const std::u16string msg = u"Sent help message '" - + GeneralUtils::ASCIIToUTF16(StringifiedEnum::ToString(helpId)) - + u"' (id: " - + GeneralUtils::to_u16string(GeneralUtils::ToUnderlying(helpId)) - + u')'; - - ChatPackets::SendSystemMessage(sysAddr, msg); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid command invocation."); - return; - } - } - - if (chatCommand == "setflag" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) { - const auto flagId = GeneralUtils::TryParse(args.at(0)); - - if (!flagId) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); - return; - } - - entity->GetCharacter()->SetPlayerFlag(flagId.value(), true); - } - - if (chatCommand == "setflag" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 2) { - const auto flagId = GeneralUtils::TryParse(args.at(1)); - std::string onOffFlag = args.at(0); - if (!flagId) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); - return; - } - if (onOffFlag != "off" && onOffFlag != "on") { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag type."); - return; - } - entity->GetCharacter()->SetPlayerFlag(flagId.value(), onOffFlag == "on"); - } - if (chatCommand == "clearflag" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) { - const auto flagId = GeneralUtils::TryParse(args.at(0)); - - if (!flagId) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); - return; - } - - entity->GetCharacter()->SetPlayerFlag(flagId.value(), false); - } - - // Pet status utility - if (chatCommand == "setpetflag" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() == 0) { - ChatPackets::SendSystemMessage(sysAddr, u"Too few arguments!"); - return; - } - - const auto petFlagInput = GeneralUtils::TryParse>(args[0]); - if (!petFlagInput) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid pet status!"); - return; - } - const PetFlag petFlag = static_cast(1 << petFlagInput.value()); - - // Determine if player has a pet summoned - auto* petComponent = PetComponent::GetActivePet(entity->GetObjectID()); - if (!petComponent) { - ChatPackets::SendSystemMessage(sysAddr, u"No active pet found!"); - return; - } - - petComponent->SetFlag(petFlag); - - std::u16string msg = u"Set pet flag to " + (GeneralUtils::to_u16string(GeneralUtils::ToUnderlying(petFlag))); - ChatPackets::SendSystemMessage(sysAddr, msg); - } - - //Pet command utility - if (chatCommand == "petcommand" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() < 5) { - ChatPackets::SendSystemMessage(sysAddr, u"Too few arguments!"); - return; - } - - const auto commandType = GeneralUtils::TryParse(args[0+3]); - if (!commandType) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid command type!"); - return; - } - - const auto typeId = GeneralUtils::TryParse(args[1+3]); - if (!typeId) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid command type id!"); - return; - } - - // Determine if player has a pet summoned - auto* petComponent = PetComponent::GetActivePet(entity->GetObjectID()); - if (!petComponent) { - ChatPackets::SendSystemMessage(sysAddr, u"No active pet found!"); - return; - } - - //Determine the positional coordinates - //NiPoint3 commandPos{}; - //float x, y, z; - const auto commandPos = GeneralUtils::TryParse(args[0], args[1], args[2]); - if (!commandPos) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid position."); - return; - } - - // Set command position - petComponent->Command(commandPos.value(), entity->GetObjectID(), commandType.value(), typeId.value(), true); - } - - // Pet speed utility - if (chatCommand == "setpetmaxspeed" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() != 1) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid number of arguments!"); - return; - } - - const auto petMaxSpeed = GeneralUtils::TryParse(args[0]); - if (!petMaxSpeed) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid speed!"); - return; - } - - // Determine if player has a pet summoned - auto* petComponent = PetComponent::GetActivePet(entity->GetObjectID()); - if (!petComponent) { - ChatPackets::SendSystemMessage(sysAddr, u"No active pet found!"); - return; - } - - auto* movementAIComponent = petComponent->GetParentEntity()->GetComponent(); - if (!movementAIComponent) return; - - movementAIComponent->SetMaxSpeed(petMaxSpeed.value()); - - std::u16string msg = u"Set pet max speed to " + (GeneralUtils::to_u16string(petMaxSpeed.value())); - ChatPackets::SendSystemMessage(sysAddr, msg); - } - - // Set pet acceleration - if (chatCommand == "setpetaccel" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() != 1) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid number of arguments!"); - return; - } - - const auto petAccel = GeneralUtils::TryParse(args[0]); - if (!petAccel) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid acceleration!"); - return; - } - - // Determine if player has a pet summoned - auto* const petComponent = PetComponent::GetActivePet(entity->GetObjectID()); - if (!petComponent) { - ChatPackets::SendSystemMessage(sysAddr, u"No active pet found!"); - return; - } - - auto* const movementAIComponent = petComponent->GetParentEntity()->GetComponent(); - if (!movementAIComponent) return; - - movementAIComponent->SetAcceleration(petAccel.value()); - - std::u16string msg = u"Set pet acceleration to " + GeneralUtils::to_u16string(petAccel.value()); - ChatPackets::SendSystemMessage(sysAddr, msg); - } - - // Set pet halt distance - if (chatCommand == "setpethalt" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() != 1) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid number of arguments!"); - return; - } - - const auto petHaltDistance = GeneralUtils::TryParse(args[0]); - if (!petHaltDistance) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid halt distance!"); - return; - } - - // Determine if player has a pet summoned - auto* petComponent = PetComponent::GetActivePet(entity->GetObjectID()); - if (!petComponent) { - ChatPackets::SendSystemMessage(sysAddr, u"No active pet found!"); - return; - } - - auto* movementAIComponent = petComponent->GetParentEntity()->GetComponent(); - if (!movementAIComponent) return; - - movementAIComponent->SetHaltDistance(petHaltDistance.value()); - - std::u16string msg = u"Set pet halt distance to " + GeneralUtils::to_u16string(petHaltDistance.value()); - ChatPackets::SendSystemMessage(sysAddr, msg); - } - - if (chatCommand == "playeffect" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 3) { - const auto effectID = GeneralUtils::TryParse(args.at(0)); - - if (!effectID) return; - - // FIXME: use fallible ASCIIToUTF16 conversion, because non-ascii isn't valid anyway - GameMessages::SendPlayFXEffect(entity->GetObjectID(), effectID.value(), GeneralUtils::ASCIIToUTF16(args.at(1)), args.at(2)); - } - - if (chatCommand == "stopeffect" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - GameMessages::SendStopFXEffect(entity, true, args[0]); - } - - if (chatCommand == "setanntitle" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() < 0) return; - - std::stringstream ss; - for (auto string : args) - ss << string << " "; - - entity->GetCharacter()->SetAnnouncementTitle(ss.str()); - return; - } - - if (chatCommand == "setannmsg" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() < 0) return; - - std::stringstream ss; - for (auto string : args) - ss << string << " "; - - entity->GetCharacter()->SetAnnouncementMessage(ss.str()); - return; - } - - if (chatCommand == "announce" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (entity->GetCharacter()->GetAnnouncementTitle().size() == 0 || entity->GetCharacter()->GetAnnouncementMessage().size() == 0) { - ChatPackets::SendSystemMessage(sysAddr, u"Use /setanntitle & /setannmsg <msg> first!"); - return; - } - - SendAnnouncement(entity->GetCharacter()->GetAnnouncementTitle(), entity->GetCharacter()->GetAnnouncementMessage()); - return; - } - - if (chatCommand == "shutdownuniverse" && entity->GetGMLevel() == eGameMasterLevel::OPERATOR) { - //Tell the master server that we're going to be shutting down whole "universe": - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::MASTER, eMasterMessageType::SHUTDOWN_UNIVERSE); - Game::server->SendToMaster(bitStream); - ChatPackets::SendSystemMessage(sysAddr, u"Sent universe shutdown notification to master."); - - //Tell chat to send an announcement to all servers - SendAnnouncement("Servers Closing Soon!", "DLU servers will close for maintenance in 10 minutes from now."); - return; - } - - if (chatCommand == "getnavmeshheight" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - auto control = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS)); - if (!control) return; - - float y = dpWorld::GetNavMesh()->GetHeightAtPoint(control->GetPosition()); - std::u16string msg = u"Navmesh height: " + (GeneralUtils::to_u16string(y)); - ChatPackets::SendSystemMessage(sysAddr, msg); - } - - if (chatCommand == "gmadditem" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() == 1) { - const auto itemLOT = GeneralUtils::TryParse<uint32_t>(args.at(0)); - - if (!itemLOT) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid item LOT."); - return; - } - - InventoryComponent* inventory = static_cast<InventoryComponent*>(entity->GetComponent(eReplicaComponentType::INVENTORY)); - - inventory->AddItem(itemLOT.value(), 1, eLootSourceType::MODERATION); - } else if (args.size() == 2) { - const auto itemLOT = GeneralUtils::TryParse<uint32_t>(args.at(0)); - if (!itemLOT) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid item LOT."); - return; - } - - const auto count = GeneralUtils::TryParse<uint32_t>(args.at(1)); - if (!count) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid item count."); - return; - } - - InventoryComponent* inventory = static_cast<InventoryComponent*>(entity->GetComponent(eReplicaComponentType::INVENTORY)); - - inventory->AddItem(itemLOT.value(), count.value(), eLootSourceType::MODERATION); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /gmadditem <lot>"); - } - } - - if (chatCommand == "mailitem" && entity->GetGMLevel() >= eGameMasterLevel::MODERATOR && args.size() >= 2) { - const auto& playerName = args[0]; - - auto playerInfo = Database::Get()->GetCharacterInfo(playerName); - - uint32_t receiverID = 0; - if (!playerInfo) { - ChatPackets::SendSystemMessage(sysAddr, u"Failed to find that player"); - - return; - } - - receiverID = playerInfo->id; - - const auto lot = GeneralUtils::TryParse<LOT>(args.at(1)); - - if (!lot) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid item lot."); - return; - } - - IMail::MailInfo mailInsert; - mailInsert.senderId = entity->GetObjectID(); - mailInsert.senderUsername = "Darkflame Universe"; - mailInsert.receiverId = receiverID; - mailInsert.recipient = playerName; - mailInsert.subject = "Lost item"; - mailInsert.body = "This is a replacement item for one you lost."; - mailInsert.itemID = LWOOBJID_EMPTY; - mailInsert.itemLOT = lot.value(); - mailInsert.itemSubkey = LWOOBJID_EMPTY; - mailInsert.itemCount = 1; - Database::Get()->InsertNewMail(mailInsert); - - ChatPackets::SendSystemMessage(sysAddr, u"Mail sent"); - - return; - } - - if (chatCommand == "setname" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - std::string name = ""; - - for (const auto& arg : args) { - name += arg + " "; - } - - GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS); - } - - if (chatCommand == "title" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - std::string name = entity->GetCharacter()->GetName() + " - "; - - for (const auto& arg : args) { - name += arg + " "; - } - - GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS); - } - - if ((chatCommand == "teleport" || chatCommand == "tele") && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_MODERATOR) { - NiPoint3 pos{}; - if (args.size() == 3) { - - const auto x = GeneralUtils::TryParse<float>(args.at(0)); - if (!x) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid x."); - return; - } - - const auto y = GeneralUtils::TryParse<float>(args.at(1)); - if (!y) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid y."); - return; - } - - const auto z = GeneralUtils::TryParse<float>(args.at(2)); - if (!z) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid z."); - return; - } - - pos.SetX(x.value()); - pos.SetY(y.value()); - pos.SetZ(z.value()); - - LOG("Teleporting objectID: %llu to %f, %f, %f", entity->GetObjectID(), pos.x, pos.y, pos.z); - GameMessages::SendTeleport(entity->GetObjectID(), pos, NiQuaternion(), sysAddr); - } else if (args.size() == 2) { - - const auto x = GeneralUtils::TryParse<float>(args.at(0)); - if (!x) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid x."); - return; - } - - const auto z = GeneralUtils::TryParse<float>(args.at(1)); - if (!z) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid z."); - return; - } - - pos.SetX(x.value()); - pos.SetY(0.0f); - pos.SetZ(z.value()); - - LOG("Teleporting objectID: %llu to X: %f, Z: %f", entity->GetObjectID(), pos.x, pos.z); - GameMessages::SendTeleport(entity->GetObjectID(), pos, NiQuaternion(), sysAddr); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /teleport <x> (<y>) <z> - if no Y given, will teleport to the height of the terrain (or any physics object)."); - } - - - auto* possessorComponent = entity->GetComponent<PossessorComponent>(); - if (possessorComponent) { - auto* possassableEntity = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); - - if (possassableEntity != nullptr) { - auto* havokVehiclePhysicsComponent = possassableEntity->GetComponent<HavokVehiclePhysicsComponent>(); - if (havokVehiclePhysicsComponent) { - havokVehiclePhysicsComponent->SetPosition(pos); - Game::entityManager->SerializeEntity(possassableEntity); - } else GameMessages::SendTeleport(possassableEntity->GetObjectID(), pos, NiQuaternion(), sysAddr); - } - } - } - - if (chatCommand == "tpall" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto pos = entity->GetPosition(); - - const auto characters = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::CHARACTER); - - for (auto* character : characters) { - GameMessages::SendTeleport(character->GetObjectID(), pos, NiQuaternion(), character->GetSystemAddress()); - } - - return; - } - - if (chatCommand == "dismount" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - auto* possessorComponent = entity->GetComponent<PossessorComponent>(); - if (possessorComponent) { - auto possessableId = possessorComponent->GetPossessable(); - if (possessableId != LWOOBJID_EMPTY) { - auto* possessableEntity = Game::entityManager->GetEntity(possessableId); - if (possessableEntity) possessorComponent->Dismount(possessableEntity, true); - } - } - } - - if (chatCommand == "fly" && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_DEVELOPER) { - auto* character = entity->GetCharacter(); - - if (character) { - bool isFlying = character->GetIsFlying(); - - if (isFlying) { - GameMessages::SendSetJetPackMode(entity, false); - - character->SetIsFlying(false); - } else { - float speedScale = 1.0f; - - if (args.size() >= 1) { - const auto tempScaleStore = GeneralUtils::TryParse<float>(args.at(0)); - - if (tempScaleStore) { - speedScale = tempScaleStore.value(); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Failed to parse speed scale argument."); - } - } - - float airSpeed = 20 * speedScale; - float maxAirSpeed = 30 * speedScale; - float verticalVelocity = 1.5 * speedScale; - - GameMessages::SendSetJetPackMode(entity, true, true, false, 167, airSpeed, maxAirSpeed, verticalVelocity); - - character->SetIsFlying(true); - } - } - } - - //------- GM COMMANDS TO ACTUALLY MODERATE -------- - - if (chatCommand == "mute" && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_DEVELOPER) { - if (args.size() >= 1) { - auto* player = PlayerManager::GetPlayer(args[0]); - - uint32_t accountId = 0; - LWOOBJID characterId = 0; - - if (player == nullptr) { - auto characterInfo = Database::Get()->GetCharacterInfo(args[0]); - - if (characterInfo) { - accountId = characterInfo->accountId; - characterId = characterInfo->id; - - GeneralUtils::SetBit(characterId, eObjectBits::CHARACTER); - GeneralUtils::SetBit(characterId, eObjectBits::PERSISTENT); - } - - if (accountId == 0) { - ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(args[0])); - - return; - } - } else { - auto* character = player->GetCharacter(); - auto* user = character != nullptr ? character->GetParentUser() : nullptr; - if (user) accountId = user->GetAccountID(); - characterId = player->GetObjectID(); - } - - time_t expire = 1; // Default to indefinate mute - - if (args.size() >= 2) { - const auto days = GeneralUtils::TryParse<uint32_t>(args[1]); - if (!days) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid days."); - - return; - } - - std::optional<uint32_t> hours; - if (args.size() >= 3) { - hours = GeneralUtils::TryParse<uint32_t>(args[2]); - if (!hours) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid hours."); - - return; - } - } - - expire = time(NULL); - expire += 24 * 60 * 60 * days.value(); - expire += 60 * 60 * hours.value_or(0); - } - - if (accountId != 0) Database::Get()->UpdateAccountUnmuteTime(accountId, expire); - - char buffer[32] = "brought up for review.\0"; - - if (expire != 1) { - std::tm* ptm = std::localtime(&expire); - // Format: Mo, 15.06.2009 20:20:00 - std::strftime(buffer, 32, "%a, %d.%m.%Y %H:%M:%S", ptm); - } - - const auto timeStr = GeneralUtils::ASCIIToUTF16(std::string(buffer)); - - ChatPackets::SendSystemMessage(sysAddr, u"Muted: " + GeneralUtils::UTF8ToUTF16(args[0]) + u" until " + timeStr); - - //Notify chat about it - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::MUTE_UPDATE); - - bitStream.Write(characterId); - bitStream.Write(expire); - - Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /mute <username> <days (optional)> <hours (optional)>"); - } - } - - if (chatCommand == "kick" && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_MODERATOR) { - if (args.size() == 1) { - auto* player = PlayerManager::GetPlayer(args[0]); - - std::u16string username = GeneralUtils::UTF8ToUTF16(args[0]); - if (player == nullptr) { - ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + username); - return; - } - - Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::KICK); - - ChatPackets::SendSystemMessage(sysAddr, u"Kicked: " + username); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /kick <username>"); - } - } - - if (chatCommand == "ban" && entity->GetGMLevel() >= eGameMasterLevel::SENIOR_MODERATOR) { - if (args.size() == 1) { - auto* player = PlayerManager::GetPlayer(args[0]); - - uint32_t accountId = 0; - - if (player == nullptr) { - auto characterInfo = Database::Get()->GetCharacterInfo(args[0]); - - if (characterInfo) { - accountId = characterInfo->accountId; - } - - if (accountId == 0) { - ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(args[0])); - - return; - } - } else { - auto* character = player->GetCharacter(); - auto* user = character != nullptr ? character->GetParentUser() : nullptr; - if (user) accountId = user->GetAccountID(); - } - - if (accountId != 0) Database::Get()->UpdateAccountBan(accountId, true); - - if (player != nullptr) { - Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::FREE_TRIAL_EXPIRED); - } - - ChatPackets::SendSystemMessage(sysAddr, u"Banned: " + GeneralUtils::ASCIIToUTF16(args[0])); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /ban <username>"); - } - } - - //------------------------------------------------- - - if (chatCommand == "buffme" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); - if (dest) { - dest->SetHealth(999); - dest->SetMaxHealth(999.0f); - dest->SetArmor(999); - dest->SetMaxArmor(999.0f); - dest->SetImagination(999); - dest->SetMaxImagination(999.0f); - } - Game::entityManager->SerializeEntity(entity); - } - - if (chatCommand == "startcelebration" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) { - const auto celebration = GeneralUtils::TryParse<int32_t>(args.at(0)); - - if (!celebration) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid celebration."); - return; - } - - GameMessages::SendStartCelebrationEffect(entity, entity->GetSystemAddress(), celebration.value()); - } - - if (chatCommand == "buffmed" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); - if (dest) { - dest->SetHealth(9); - dest->SetMaxHealth(9.0f); - dest->SetArmor(9); - dest->SetMaxArmor(9.0f); - dest->SetImagination(9); - dest->SetMaxImagination(9.0f); - } - Game::entityManager->SerializeEntity(entity); - } - - if (chatCommand == "refillstats" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - - auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); - if (dest) { - dest->SetHealth(static_cast<int32_t>(dest->GetMaxHealth())); - dest->SetArmor(static_cast<int32_t>(dest->GetMaxArmor())); - dest->SetImagination(static_cast<int32_t>(dest->GetMaxImagination())); - } - - Game::entityManager->SerializeEntity(entity); - } - - if (chatCommand == "lookup" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - auto query = CDClientDatabase::CreatePreppedStmt( - "SELECT `id`, `name` FROM `Objects` WHERE `displayName` LIKE ?1 OR `name` LIKE ?1 OR `description` LIKE ?1 LIMIT 50"); - // Concatenate all of the arguments into a single query so a multi word query can be used properly. - std::string conditional = args[0]; - args.erase(args.begin()); - for (auto& argument : args) { - conditional += ' ' + argument; - } - - const std::string query_text = "%" + conditional + "%"; - query.bind(1, query_text.c_str()); - - auto tables = query.execQuery(); - - while (!tables.eof()) { - std::string message = std::to_string(tables.getIntField(0)) + " - " + tables.getStringField(1); - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(message, message.size())); - tables.nextRow(); - } - } - - if (chatCommand == "spawn" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - ControllablePhysicsComponent* comp = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS)); - if (!comp) return; - - const auto lot = GeneralUtils::TryParse<uint32_t>(args[0]); - - if (!lot) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot."); - return; - } - - EntityInfo info; - info.lot = lot.value(); - info.pos = comp->GetPosition(); - info.rot = comp->GetRotation(); - info.spawner = nullptr; - info.spawnerID = entity->GetObjectID(); - info.spawnerNodeID = 0; - - Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr); - - if (newEntity == nullptr) { - ChatPackets::SendSystemMessage(sysAddr, u"Failed to spawn entity."); - return; - } - - Game::entityManager->ConstructEntity(newEntity); - } - - if (chatCommand == "spawngroup" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 3) { - auto controllablePhysicsComponent = entity->GetComponent<ControllablePhysicsComponent>(); - if (!controllablePhysicsComponent) return; - - const auto lot = GeneralUtils::TryParse<LOT>(args[0]); - if (!lot) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot."); - return; - } - - const auto numberToSpawnOptional = GeneralUtils::TryParse<uint32_t>(args[1]); - if (!numberToSpawnOptional && numberToSpawnOptional.value() > 0) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid number of enemies to spawn."); - return; - } - uint32_t numberToSpawn = numberToSpawnOptional.value(); - - // Must spawn within a radius of at least 0.0f - const auto radiusToSpawnWithinOptional = GeneralUtils::TryParse<float>(args[2]); - if (!radiusToSpawnWithinOptional && radiusToSpawnWithinOptional.value() < 0.0f) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid radius to spawn within."); - return; - } - const float radiusToSpawnWithin = radiusToSpawnWithinOptional.value(); - - EntityInfo info; - info.lot = lot.value(); - info.spawner = nullptr; - info.spawnerID = entity->GetObjectID(); - info.spawnerNodeID = 0; - - auto playerPosition = controllablePhysicsComponent->GetPosition(); - while (numberToSpawn > 0) { - auto randomAngle = GeneralUtils::GenerateRandomNumber<float>(0.0f, 2 * PI); - auto randomRadius = GeneralUtils::GenerateRandomNumber<float>(0.0f, radiusToSpawnWithin); - - // Set the position to the generated random position plus the player position. This will - // spawn the entity in a circle around the player. As you get further from the player, the angle chosen will get less accurate. - info.pos = playerPosition + NiPoint3(cos(randomAngle) * randomRadius, 0.0f, sin(randomAngle) * randomRadius); - info.rot = NiQuaternion(); - - auto newEntity = Game::entityManager->CreateEntity(info); - if (newEntity == nullptr) { - ChatPackets::SendSystemMessage(sysAddr, u"Failed to spawn entity."); - return; - } - - Game::entityManager->ConstructEntity(newEntity); - numberToSpawn--; - } - } - - if ((chatCommand == "giveuscore") && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto uscoreOptional = GeneralUtils::TryParse<int32_t>(args[0]); - if (!uscoreOptional) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid uscore."); - return; - } - const int32_t uscore = uscoreOptional.value(); - - CharacterComponent* character = entity->GetComponent<CharacterComponent>(); - if (character) character->SetUScore(character->GetUScore() + uscore); - // MODERATION should work but it doesn't. Relog to see uscore changes - - eLootSourceType lootType = eLootSourceType::MODERATION; - - if (args.size() >= 2) { - const auto type = GeneralUtils::TryParse<eLootSourceType>(args[1]); - lootType = type.value_or(lootType); - } - - GameMessages::SendModifyLEGOScore(entity, entity->GetSystemAddress(), uscore, lootType); - } - - if ((chatCommand == "setlevel") && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - // We may be trying to set a specific players level to a level. If so override the entity with the requested players. - std::string requestedPlayerToSetLevelOf = ""; - if (args.size() > 1) { - requestedPlayerToSetLevelOf = args[1]; - - auto requestedPlayer = PlayerManager::GetPlayer(requestedPlayerToSetLevelOf); - - if (!requestedPlayer) { - ChatPackets::SendSystemMessage(sysAddr, u"No player found with username: (" + GeneralUtils::UTF8ToUTF16(requestedPlayerToSetLevelOf) + u")."); - return; - } - - if (!requestedPlayer->GetOwner()) { - ChatPackets::SendSystemMessage(sysAddr, u"No entity found with username: (" + GeneralUtils::UTF8ToUTF16(requestedPlayerToSetLevelOf) + u")."); - return; - } - - entity = requestedPlayer->GetOwner(); - } - const auto requestedLevelOptional = GeneralUtils::TryParse<uint32_t>(args[0]); - uint32_t oldLevel; - - // first check the level is valid - if (!requestedLevelOptional) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid level."); - return; - } - uint32_t requestedLevel = requestedLevelOptional.value(); - // query to set our uscore to the correct value for this level - - auto characterComponent = entity->GetComponent<CharacterComponent>(); - if (!characterComponent) return; - auto levelComponent = entity->GetComponent<LevelProgressionComponent>(); - auto query = CDClientDatabase::CreatePreppedStmt("SELECT requiredUScore from LevelProgressionLookup WHERE id = ?;"); - query.bind(1, static_cast<int>(requestedLevel)); - auto result = query.execQuery(); - - if (result.eof()) return; - - // Set the UScore first - oldLevel = levelComponent->GetLevel(); - characterComponent->SetUScore(result.getIntField(0, characterComponent->GetUScore())); - - // handle level up for each level we have passed if we set our level to be higher than the current one. - if (oldLevel < requestedLevel) { - while (oldLevel < requestedLevel) { - oldLevel += 1; - levelComponent->SetLevel(oldLevel); - levelComponent->HandleLevelUp(); - } - } else { - levelComponent->SetLevel(requestedLevel); - } - - if (requestedPlayerToSetLevelOf != "") { - ChatPackets::SendSystemMessage( - sysAddr, u"Set " + GeneralUtils::UTF8ToUTF16(requestedPlayerToSetLevelOf) + u"'s level to " + GeneralUtils::to_u16string(requestedLevel) + - u" and UScore to " + GeneralUtils::to_u16string(characterComponent->GetUScore()) + - u". Relog to see changes."); - } else { - ChatPackets::SendSystemMessage( - sysAddr, u"Set your level to " + GeneralUtils::to_u16string(requestedLevel) + - u" and UScore to " + GeneralUtils::to_u16string(characterComponent->GetUScore()) + - u". Relog to see changes."); - } - return; - } - - if (chatCommand == "pos" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto position = entity->GetPosition(); - - ChatPackets::SendSystemMessage(sysAddr, u"<" + (GeneralUtils::to_u16string(position.x)) + u", " + (GeneralUtils::to_u16string(position.y)) + u", " + (GeneralUtils::to_u16string(position.z)) + u">"); - - LOG("Position: %f, %f, %f", position.x, position.y, position.z); - } - - if (chatCommand == "rot" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto rotation = entity->GetRotation(); - - ChatPackets::SendSystemMessage(sysAddr, u"<" + (GeneralUtils::to_u16string(rotation.w)) + u", " + (GeneralUtils::to_u16string(rotation.x)) + u", " + (GeneralUtils::to_u16string(rotation.y)) + u", " + (GeneralUtils::to_u16string(rotation.z)) + u">"); - - LOG("Rotation: %f, %f, %f, %f", rotation.w, rotation.x, rotation.y, rotation.z); - } - - if (chatCommand == "locrow" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto position = entity->GetPosition(); - const auto rotation = entity->GetRotation(); - - LOG("<location x=\"%f\" y=\"%f\" z=\"%f\" rw=\"%f\" rx=\"%f\" ry=\"%f\" rz=\"%f\" />", position.x, position.y, position.z, rotation.w, rotation.x, rotation.y, rotation.z); - } - - if (chatCommand == "playlvlfx" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - GameMessages::SendPlayFXEffect(entity, 7074, u"create", "7074", LWOOBJID_EMPTY, 1.0f, 1.0f, true); - } - - if (chatCommand == "playrebuildfx" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - GameMessages::SendPlayFXEffect(entity, 230, u"rebuild", "230", LWOOBJID_EMPTY, 1.0f, 1.0f, true); - } - - if ((chatCommand == "freemoney" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) && args.size() == 1) { - const auto money = GeneralUtils::TryParse<int64_t>(args[0]); - - if (!money) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid money."); - return; - } - - auto* ch = entity->GetCharacter(); - ch->SetCoins(ch->GetCoins() + money.value(), eLootSourceType::MODERATION); - } - - if ((chatCommand == "setcurrency") && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto money = GeneralUtils::TryParse<int64_t>(args[0]); - - if (!money) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid money."); - return; - } - - auto* ch = entity->GetCharacter(); - ch->SetCoins(money.value(), eLootSourceType::MODERATION); - } - - // Allow for this on even while not a GM, as it sometimes toggles incorrrectly. - if (chatCommand == "gminvis" && entity->GetCharacter()->GetParentUser()->GetMaxGMLevel() >= eGameMasterLevel::DEVELOPER) { - GameMessages::SendToggleGMInvis(entity->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS); - - return; - } - - if (chatCommand == "gmimmune" && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); - - const auto state = GeneralUtils::TryParse<int32_t>(args[0]); - - if (!state) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid state."); - return; - } - - if (destroyableComponent) destroyableComponent->SetIsGMImmune(state.value()); - return; - } - - //Testing basic attack immunity - if (chatCommand == "attackimmune" && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); - - const auto state = GeneralUtils::TryParse<int32_t>(args[0]); - - if (!state) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid state."); - return; - } - - if (destroyableComponent) destroyableComponent->SetIsImmune(state.value()); - return; - } - - if (chatCommand == "buff" && args.size() >= 2 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - auto* buffComponent = entity->GetComponent<BuffComponent>(); - - const auto id = GeneralUtils::TryParse<int32_t>(args[0]); - if (!id) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid buff id."); - return; - } - - const auto duration = GeneralUtils::TryParse<int32_t>(args[1]); - if (!duration) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid buff duration."); - return; - } - - if (buffComponent) buffComponent->ApplyBuff(id.value(), duration.value(), entity->GetObjectID()); - return; - } - - if ((chatCommand == "testmap" && args.size() >= 1) && entity->GetGMLevel() >= eGameMasterLevel::FORUM_MODERATOR) { - ChatPackets::SendSystemMessage(sysAddr, u"Requesting map change..."); - LWOCLONEID cloneId = 0; - bool force = false; - - const auto reqZoneOptional = GeneralUtils::TryParse<LWOMAPID>(args[0]); - if (!reqZoneOptional) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid zone."); - return; - } - const LWOMAPID reqZone = reqZoneOptional.value(); - - if (args.size() > 1) { - auto index = 1; - - if (args[index] == "force") { - index++; - - force = true; - } - - if (args.size() > index) { - const auto cloneIdOptional = GeneralUtils::TryParse<LWOCLONEID>(args[index]); - if (!cloneIdOptional) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone id."); - return; - } - cloneId = cloneIdOptional.value(); - } - } - - const auto objid = entity->GetObjectID(); - - if (force || Game::zoneManager->CheckIfAccessibleZone(reqZone)) { // to prevent tomfoolery - - ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, reqZone, cloneId, false, [objid](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { - - auto* entity = Game::entityManager->GetEntity(objid); - if (!entity) return; - - const auto sysAddr = entity->GetSystemAddress(); - - ChatPackets::SendSystemMessage(sysAddr, u"Transfering map..."); - - LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); - if (entity->GetCharacter()) { - entity->GetCharacter()->SetZoneID(zoneID); - entity->GetCharacter()->SetZoneInstance(zoneInstance); - entity->GetCharacter()->SetZoneClone(zoneClone); - entity->GetComponent<CharacterComponent>()->SetLastRocketConfig(u""); - } - - entity->GetCharacter()->SaveXMLToDatabase(); - - WorldPackets::SendTransferToWorld(sysAddr, serverIP, serverPort, mythranShift); - return; - }); - } else { - std::string msg = "ZoneID not found or allowed: "; - msg.append(args[0]); // FIXME: unnecessary utf16 re-encoding just for error - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(msg, msg.size())); - } - } - - if (chatCommand == "createprivate" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 3) { - const auto zone = GeneralUtils::TryParse<uint32_t>(args[0]); - if (!zone) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid zone."); - return; - } - - const auto clone = GeneralUtils::TryParse<uint32_t>(args[1]); - if (!clone) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone."); - return; - } - - const auto& password = args[2]; - - ZoneInstanceManager::Instance()->CreatePrivateZone(Game::server, zone.value(), clone.value(), password); - - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16("Sent request for private zone with password: " + password)); - - return; - } - - if ((chatCommand == "debugui") && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - ChatPackets::SendSystemMessage(sysAddr, u"Opening UIDebugger..."); - AMFArrayValue args; - GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, "ToggleUIDebugger;", args); - } - - if ((chatCommand == "boost") && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - auto* possessorComponent = entity->GetComponent<PossessorComponent>(); - - if (possessorComponent == nullptr) { - return; - } - - auto* vehicle = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); - - if (vehicle == nullptr) { - return; - } - - if (args.size() >= 1) { - const auto time = GeneralUtils::TryParse<float>(args[0]); - - if (!time) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid boost time."); - return; - } else { - GameMessages::SendVehicleAddPassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); - entity->AddCallbackTimer(time.value(), [vehicle]() { - if (!vehicle) return; - GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); - }); - } - } else { - GameMessages::SendVehicleAddPassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); - } - - } - - if ((chatCommand == "unboost") && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - auto* possessorComponent = entity->GetComponent<PossessorComponent>(); - - if (possessorComponent == nullptr) return; - auto* vehicle = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); - - if (vehicle == nullptr) return; - GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); - } - - if (chatCommand == "activatespawner" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - auto spawners = Game::zoneManager->GetSpawnersByName(args[0]); - - for (auto* spawner : spawners) { - spawner->Activate(); - } - - spawners = Game::zoneManager->GetSpawnersInGroup(args[0]); - - for (auto* spawner : spawners) { - spawner->Activate(); - } - } - - if (chatCommand == "spawnphysicsverts" && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_DEVELOPER) { - //Go tell physics to spawn all the vertices: - auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PHANTOM_PHYSICS); - for (auto en : entities) { - auto phys = static_cast<PhantomPhysicsComponent*>(en->GetComponent(eReplicaComponentType::PHANTOM_PHYSICS)); - if (phys) - phys->SpawnVertices(); - } - } - - if (chatCommand == "reportproxphys" && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_DEVELOPER) { - auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PROXIMITY_MONITOR); - for (auto en : entities) { - auto phys = static_cast<ProximityMonitorComponent*>(en->GetComponent(eReplicaComponentType::PROXIMITY_MONITOR)); - if (phys) { - for (auto prox : phys->GetProximitiesData()) { - if (!prox.second) continue; - - auto sphere = static_cast<dpShapeSphere*>(prox.second->GetShape()); - auto pos = prox.second->GetPosition(); - LOG("Proximity: %s, r: %f, pos: %f, %f, %f", prox.first.c_str(), sphere->GetRadius(), pos.x, pos.y, pos.z); - } - } - } - } - - if (chatCommand == "triggerspawner" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - auto spawners = Game::zoneManager->GetSpawnersByName(args[0]); - - for (auto* spawner : spawners) { - spawner->Spawn(); - } - - spawners = Game::zoneManager->GetSpawnersInGroup(args[0]); - - for (auto* spawner : spawners) { - spawner->Spawn(); - } - } - - if (chatCommand == "reforge" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 2) { - const auto baseItem = GeneralUtils::TryParse<LOT>(args[0]); - if (!baseItem) return; - - const auto reforgedItem = GeneralUtils::TryParse<LOT>(args[1]); - if (!reforgedItem) return; - - auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); - if (!inventoryComponent) return; - - std::vector<LDFBaseData*> data{}; - data.push_back(new LDFData<int32_t>(u"reforgedLOT", reforgedItem.value())); - - inventoryComponent->AddItem(baseItem.value(), 1, eLootSourceType::MODERATION, eInventoryType::INVALID, data); - } - - if (chatCommand == "crash" && entity->GetGMLevel() >= eGameMasterLevel::OPERATOR) { - ChatPackets::SendSystemMessage(sysAddr, u"Crashing..."); - - int* badPtr = nullptr; - *badPtr = 0; - - return; - } - - if (chatCommand == "metrics" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - for (const auto variable : Metrics::GetAllMetrics()) { - auto* metric = Metrics::GetMetric(variable); - - if (metric == nullptr) { - continue; - } - - ChatPackets::SendSystemMessage( - sysAddr, - GeneralUtils::ASCIIToUTF16(Metrics::MetricVariableToString(variable)) + - u": " + - GeneralUtils::to_u16string(Metrics::ToMiliseconds(metric->average)) + - u"ms" - ); - } - - ChatPackets::SendSystemMessage( - sysAddr, - u"Peak RSS: " + GeneralUtils::to_u16string(static_cast<float>(static_cast<double>(Metrics::GetPeakRSS()) / 1.024e6)) + - u"MB" - ); - - ChatPackets::SendSystemMessage( - sysAddr, - u"Current RSS: " + GeneralUtils::to_u16string(static_cast<float>(static_cast<double>(Metrics::GetCurrentRSS()) / 1.024e6)) + - u"MB" - ); - - ChatPackets::SendSystemMessage( - sysAddr, - u"Process ID: " + GeneralUtils::to_u16string(Metrics::GetProcessID()) - ); - - return; - } - - if (chatCommand == "reloadconfig" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - Game::config->ReloadConfig(); - VanityUtilities::SpawnVanity(); - dpWorld::Reload(); - auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPTED_ACTIVITY); - for (const auto* const entity : entities) { - auto* const scriptedActivityComponent = entity->GetComponent<ScriptedActivityComponent>(); - if (!scriptedActivityComponent) continue; - - scriptedActivityComponent->ReloadConfig(); - } - Game::server->UpdateMaximumMtuSize(); - Game::server->UpdateBandwidthLimit(); - ChatPackets::SendSystemMessage(sysAddr, u"Successfully reloaded config for world!"); - } - - if (chatCommand == "rollloot" && entity->GetGMLevel() >= eGameMasterLevel::OPERATOR && args.size() >= 3) { - const auto lootMatrixIndex = GeneralUtils::TryParse<uint32_t>(args[0]); - if (!lootMatrixIndex) return; - - const auto targetLot = GeneralUtils::TryParse<uint32_t>(args[1]); - if (!targetLot) return; - - const auto loops = GeneralUtils::TryParse<uint32_t>(args[2]); - if (!loops) return; - - uint64_t totalRuns = 0; - - for (uint32_t i = 0; i < loops; i++) { - while (true) { - auto lootRoll = Loot::RollLootMatrix(lootMatrixIndex.value()); - totalRuns += 1; - bool doBreak = false; - for (const auto& kv : lootRoll) { - if (static_cast<uint32_t>(kv.first) == targetLot) { - doBreak = true; - } - } - if (doBreak) break; - } - } - - std::u16string message = u"Ran loot drops looking for " - + GeneralUtils::to_u16string(targetLot.value()) - + u", " - + GeneralUtils::to_u16string(loops.value()) - + u" times. It ran " - + GeneralUtils::to_u16string(totalRuns) - + u" times. Averaging out at " - + GeneralUtils::to_u16string(static_cast<float>(totalRuns) / loops.value()); - - ChatPackets::SendSystemMessage(sysAddr, message); - } - - if (chatCommand == "deleteinven" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - eInventoryType inventoryType = eInventoryType::INVALID; - - const auto inventoryTypeOptional = GeneralUtils::TryParse<eInventoryType>(args[0]); - if (!inventoryTypeOptional) { - // In this case, we treat the input as a string and try to find it in the reflection list - std::transform(args[0].begin(), args[0].end(), args[0].begin(), ::toupper); - LOG("looking for inventory %s", args[0].c_str()); - for (uint32_t index = 0; index < NUMBER_OF_INVENTORIES; index++) { - if (std::string_view(args[0]) == std::string_view(InventoryType::InventoryTypeToString(static_cast<eInventoryType>(index)))) inventoryType = static_cast<eInventoryType>(index); - } - } else { - inventoryType = inventoryTypeOptional.value(); - } - - if (inventoryType == eInventoryType::INVALID || inventoryType >= NUMBER_OF_INVENTORIES) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid inventory provided."); - return; - } - - auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); - if (!inventoryComponent) return; - - auto* inventoryToDelete = inventoryComponent->GetInventory(inventoryType); - if (!inventoryToDelete) return; - - inventoryToDelete->DeleteAllItems(); - LOG("Deleted inventory %s for user %llu", args[0].c_str(), entity->GetObjectID()); - ChatPackets::SendSystemMessage(sysAddr, u"Deleted inventory " + GeneralUtils::UTF8ToUTF16(args[0])); - } - - if (chatCommand == "castskill" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - auto* skillComponent = entity->GetComponent<SkillComponent>(); - if (skillComponent) { - const auto skillId = GeneralUtils::TryParse<uint32_t>(args[0]); - - if (!skillId) { - ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill ID."); - return; - } else { - skillComponent->CastSkill(skillId.value(), entity->GetObjectID(), entity->GetObjectID()); - ChatPackets::SendSystemMessage(sysAddr, u"Cast skill"); - } - } - } - - if (chatCommand == "setskillslot" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 2) { - auto* const inventoryComponent = entity->GetComponent<InventoryComponent>(); - if (inventoryComponent) { - const auto slot = GeneralUtils::TryParse<BehaviorSlot>(args[0]); - if (!slot) { - ChatPackets::SendSystemMessage(sysAddr, u"Error getting slot."); - return; - } else { - const auto skillId = GeneralUtils::TryParse<uint32_t>(args[1]); - if (!skillId) { - ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill."); - return; - } else { - if (inventoryComponent->SetSkill(slot.value(), skillId.value())) ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot successfully"); - else ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot failed"); - } - } - } - } - - if (chatCommand == "setfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); - if (destroyableComponent) { - const auto faction = GeneralUtils::TryParse<int32_t>(args[0]); - - if (!faction) { - ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction."); - return; - } else { - destroyableComponent->SetFaction(faction.value()); - ChatPackets::SendSystemMessage(sysAddr, u"Set faction and updated enemies list"); - } - } - } - - if (chatCommand == "addfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); - if (destroyableComponent) { - const auto faction = GeneralUtils::TryParse<int32_t>(args[0]); - - if (!faction) { - ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction."); - return; - } else { - destroyableComponent->AddFaction(faction.value()); - ChatPackets::SendSystemMessage(sysAddr, u"Added faction and updated enemies list"); - } - } - } - - if (chatCommand == "getfactions" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); - if (destroyableComponent) { - ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:"); - for (const auto entry : destroyableComponent->GetFactionIDs()) { - ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); - } - - ChatPackets::SendSystemMessage(sysAddr, u"Enemy factions:"); - for (const auto entry : destroyableComponent->GetEnemyFactionsIDs()) { - ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); - } - } - } - - if (chatCommand == "setrewardcode" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) { - auto* cdrewardCodes = CDClientManager::GetTable<CDRewardCodesTable>(); - - auto id = cdrewardCodes->GetCodeID(args[0]); - if (id != -1) Database::Get()->InsertRewardCode(user->GetAccountID(), id); - } - - if (chatCommand == "inspect" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - Entity* closest = nullptr; - - std::u16string ldf; - - bool isLDF = false; - - auto component = GeneralUtils::TryParse<eReplicaComponentType>(args[0]); - if (!component) { - component = eReplicaComponentType::INVALID; - - ldf = GeneralUtils::UTF8ToUTF16(args[0]); - - isLDF = true; - } - - auto reference = entity->GetPosition(); - - auto closestDistance = 0.0f; - - const auto candidates = Game::entityManager->GetEntitiesByComponent(component.value()); - - for (auto* candidate : candidates) { - if (candidate->GetLOT() == 1 || candidate->GetLOT() == 8092) { - continue; - } - - if (isLDF && !candidate->HasVar(ldf)) { - continue; - } - - if (!closest) { - closest = candidate; - - closestDistance = NiPoint3::Distance(candidate->GetPosition(), reference); - - continue; - } - - const auto distance = NiPoint3::Distance(candidate->GetPosition(), reference); - - if (distance < closestDistance) { - closest = candidate; - - closestDistance = distance; - } - } - - if (!closest) return; - - Game::entityManager->SerializeEntity(closest); - - auto* table = CDClientManager::GetTable<CDObjectsTable>(); - - const auto& info = table->GetByID(closest->GetLOT()); - - std::stringstream header; - - header << info.name << " [" << std::to_string(info.id) << "]" << " " << std::to_string(closestDistance) << " " << std::to_string(closest->IsSleeping()); - - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(header.str())); - - for (const auto& pair : closest->GetComponents()) { - auto id = pair.first; - - std::stringstream stream; - - stream << "Component [" << std::to_string(static_cast<uint32_t>(id)) << "]"; - - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(stream.str())); - } - - if (args.size() >= 2) { - if (args[1] == "-m" && args.size() >= 3) { - auto* const movingPlatformComponent = closest->GetComponent<MovingPlatformComponent>(); - - const auto mValue = GeneralUtils::TryParse<int32_t>(args[2]); - - if (!movingPlatformComponent || !mValue) return; - - movingPlatformComponent->SetSerialized(true); - - if (mValue == -1) { - movingPlatformComponent->StopPathing(); - } else { - movingPlatformComponent->GotoWaypoint(mValue.value()); - } - - Game::entityManager->SerializeEntity(closest); - } else if (args[1] == "-a" && args.size() >= 3) { - RenderComponent::PlayAnimation(closest, args.at(2)); - } else if (args[1] == "-s") { - for (auto* entry : closest->GetSettings()) { - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(entry->GetString())); - } - - ChatPackets::SendSystemMessage(sysAddr, u"------"); - ChatPackets::SendSystemMessage(sysAddr, u"Spawner ID: " + GeneralUtils::to_u16string(closest->GetSpawnerID())); - } else if (args[1] == "-p") { - const auto postion = closest->GetPosition(); - - ChatPackets::SendSystemMessage( - sysAddr, - GeneralUtils::ASCIIToUTF16("< " + std::to_string(postion.x) + ", " + std::to_string(postion.y) + ", " + std::to_string(postion.z) + " >") - ); - } else if (args[1] == "-f") { - auto* destuctable = closest->GetComponent<DestroyableComponent>(); - - if (destuctable == nullptr) { - ChatPackets::SendSystemMessage(sysAddr, u"No destroyable component on this entity!"); - return; - } - - ChatPackets::SendSystemMessage(sysAddr, u"Smashable: " + (GeneralUtils::to_u16string(destuctable->GetIsSmashable()))); - - ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:"); - for (const auto entry : destuctable->GetFactionIDs()) { - ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); - } - - ChatPackets::SendSystemMessage(sysAddr, u"Enemy factions:"); - for (const auto entry : destuctable->GetEnemyFactionsIDs()) { - ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); - } - - if (args.size() >= 3) { - const auto faction = GeneralUtils::TryParse<int32_t>(args[2]); - if (!faction) return; - - destuctable->SetFaction(-1); - destuctable->AddFaction(faction.value(), true); - } - } else if (args[1] == "-cf") { - auto* destuctable = entity->GetComponent<DestroyableComponent>(); - if (!destuctable) { - ChatPackets::SendSystemMessage(sysAddr, u"No destroyable component on this entity!"); - return; - } - if (destuctable->IsEnemy(closest)) ChatPackets::SendSystemMessage(sysAddr, u"They are our enemy"); - else ChatPackets::SendSystemMessage(sysAddr, u"They are NOT our enemy"); - } else if (args[1] == "-t") { - auto* phantomPhysicsComponent = closest->GetComponent<PhantomPhysicsComponent>(); - - if (phantomPhysicsComponent != nullptr) { - ChatPackets::SendSystemMessage(sysAddr, u"Type: " + (GeneralUtils::to_u16string(static_cast<uint32_t>(phantomPhysicsComponent->GetEffectType())))); - const auto dir = phantomPhysicsComponent->GetDirection(); - ChatPackets::SendSystemMessage(sysAddr, u"Direction: <" + (GeneralUtils::to_u16string(dir.x)) + u", " + (GeneralUtils::to_u16string(dir.y)) + u", " + (GeneralUtils::to_u16string(dir.z)) + u">"); - ChatPackets::SendSystemMessage(sysAddr, u"Multiplier: " + (GeneralUtils::to_u16string(phantomPhysicsComponent->GetDirectionalMultiplier()))); - ChatPackets::SendSystemMessage(sysAddr, u"Active: " + (GeneralUtils::to_u16string(phantomPhysicsComponent->GetPhysicsEffectActive()))); - } - - auto* triggerComponent = closest->GetComponent<TriggerComponent>(); - if (triggerComponent) { - auto trigger = triggerComponent->GetTrigger(); - if (trigger) { - ChatPackets::SendSystemMessage(sysAddr, u"Trigger: " + (GeneralUtils::to_u16string(trigger->id))); - } - } - } - } + // Let GameMasters know if the command doesn't exist + if (!foundCommand && entity->GetGMLevel() > eGameMasterLevel::CIVILIAN) feedback << "Command " << std::quoted(args) << " does not exist!"; } + const auto feedbackStr = feedback.str(); + if (!feedbackStr.empty()) GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr)); } void SlashCommandHandler::SendAnnouncement(const std::string& title, const std::string& message) { @@ -2260,7 +121,7 @@ void SlashCommandHandler::SendAnnouncement(const std::string& title, const std:: //Notify chat about it CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ANNOUNCEMENT); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GM_ANNOUNCE); bitStream.Write<uint32_t>(title.size()); for (auto character : title) { @@ -2274,3 +135,884 @@ void SlashCommandHandler::SendAnnouncement(const std::string& title, const std:: Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); } + +void SlashCommandHandler::Startup() { + // Register Dev Commands + Command SetGMLevelCommand{ + .help = "Change the GM level of your character", + .info = "Within the authorized range of levels for the current account, changes the character's game master level to the specified value. This is required to use certain commands", + .aliases = { "setgmlevel", "makegm", "gmlevel" }, + .handle = DEVGMCommands::SetGMLevel, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(SetGMLevelCommand); + + Command ToggleNameplateCommand{ + .help = "Toggle the visibility of your nameplate. This must be enabled by a server admin to be used.", + .info = "Turns the nameplate above your head that is visible to other players off and on. This must be enabled by a server admin to be used.", + .aliases = { "togglenameplate", "tnp" }, + .handle = DEVGMCommands::ToggleNameplate, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(ToggleNameplateCommand); + + Command ToggleSkipCinematicsCommand{ + .help = "Toggle Skipping Cinematics", + .info = "Skips mission and world load related cinematics", + .aliases = { "toggleskipcinematics", "tsc" }, + .handle = DEVGMCommands::ToggleSkipCinematics, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(ToggleSkipCinematicsCommand); + + Command KillCommand{ + .help = "Smash a user", + .info = "Smashes the character whom the given user is playing", + .aliases = { "kill" }, + .handle = DEVGMCommands::Kill, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(KillCommand); + + Command MetricsCommand{ + .help = "Display server metrics", + .info = "Prints some information about the server's performance", + .aliases = { "metrics" }, + .handle = DEVGMCommands::Metrics, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(MetricsCommand); + + Command AnnounceCommand{ + .help = " Send and announcement", + .info = "Sends an announcement. `/setanntitle` and `/setannmsg` must be called first to configure the announcement.", + .aliases = { "announce" }, + .handle = DEVGMCommands::Announce, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(AnnounceCommand); + + Command SetAnnTitleCommand{ + .help = "Sets the title of an announcement", + .info = "Sets the title of an announcement. Use with `/setannmsg` and `/announce`", + .aliases = { "setanntitle" }, + .handle = DEVGMCommands::SetAnnTitle, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetAnnTitleCommand); + + Command SetAnnMsgCommand{ + .help = "Sets the message of an announcement", + .info = "Sets the message of an announcement. Use with `/setannmtitle` and `/announce`", + .aliases = { "setannmsg" }, + .handle = DEVGMCommands::SetAnnMsg, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetAnnMsgCommand); + + Command ShutdownUniverseCommand{ + .help = "Sends a shutdown message to the master server", + .info = "Sends a shutdown message to the master server. This will send an announcement to all players that the universe will shut down in 10 minutes.", + .aliases = { "shutdownuniverse" }, + .handle = DEVGMCommands::ShutdownUniverse + }; + RegisterCommand(ShutdownUniverseCommand); + + Command SetMinifigCommand{ + .help = "Alters your player's minifig", + .info = "Alters your player's minifig. Body part can be one of \"Eyebrows\", \"Eyes\", \"HairColor\", \"HairStyle\", \"Pants\", \"LeftHand\", \"Mouth\", \"RightHand\", \"Shirt\", or \"Hands\". Changing minifig parts could break the character so this command is limited to GMs.", + .aliases = { "setminifig" }, + .handle = DEVGMCommands::SetMinifig, + .requiredLevel = eGameMasterLevel::FORUM_MODERATOR + }; + RegisterCommand(SetMinifigCommand); + + Command TestMapCommand{ + .help = "Transfers you to the given zone", + .info = "Transfers you to the given zone by id and clone id. Add \"force\" to skip checking if the zone is accessible (this can softlock your character, though, if you e.g. try to teleport to Frostburgh).", + .aliases = { "testmap", "tm" }, + .handle = DEVGMCommands::TestMap, + .requiredLevel = eGameMasterLevel::FORUM_MODERATOR + }; + RegisterCommand(TestMapCommand); + + Command ReportProxPhysCommand{ + .help = "Display proximity sensor info", + .info = "Prints to console the position and radius of proximity sensors.", + .aliases = { "reportproxphys" }, + .handle = DEVGMCommands::ReportProxPhys, + .requiredLevel = eGameMasterLevel::OPERATOR + }; + RegisterCommand(ReportProxPhysCommand); + + Command SpawnPhysicsVertsCommand{ + .help = "Spawns a 1x1 brick at all vertices of phantom physics objects", + .info = "Spawns a 1x1 brick at all vertices of phantom physics objects", + .aliases = { "spawnphysicsverts" }, + .handle = DEVGMCommands::SpawnPhysicsVerts, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SpawnPhysicsVertsCommand); + + Command TeleportCommand{ + .help = "Teleports you", + .info = "Teleports you. If no Y is given, you are teleported to the height of the terrain or physics object at (x, z)", + .aliases = { "teleport", "tele", "tp" }, + .handle = DEVGMCommands::Teleport, + .requiredLevel = eGameMasterLevel::JUNIOR_DEVELOPER + }; + RegisterCommand(TeleportCommand); + + Command ActivateSpawnerCommand{ + .help = "Activates spawner by name", + .info = "Activates spawner by name", + .aliases = { "activatespawner" }, + .handle = DEVGMCommands::ActivateSpawner, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ActivateSpawnerCommand); + + Command AddMissionCommand{ + .help = "Accepts the mission, adding it to your journal.", + .info = "Accepts the mission, adding it to your journal.", + .aliases = { "addmission" }, + .handle = DEVGMCommands::AddMission, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(AddMissionCommand); + + Command BoostCommand{ + .help = "Adds boost to a vehicle", + .info = "Adds a passive boost action if you are in a vehicle. If time is given it will end after that amount of time", + .aliases = { "boost" }, + .handle = DEVGMCommands::Boost, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(BoostCommand); + + Command UnboostCommand{ + .help = "Removes a passive vehicle boost", + .info = "Removes a passive vehicle boost", + .aliases = { "unboost" }, + .handle = DEVGMCommands::Unboost, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(UnboostCommand); + + Command BuffCommand{ + .help = "Applies a buff", + .info = "Applies a buff with the given id for the given number of seconds", + .aliases = { "buff" }, + .handle = DEVGMCommands::Buff, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(BuffCommand); + + Command BuffMeCommand{ + .help = "Sets health, armor, and imagination to 999", + .info = "Sets health, armor, and imagination to 999", + .aliases = { "buffme" }, + .handle = DEVGMCommands::BuffMe, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(BuffMeCommand); + + Command BuffMedCommand{ + .help = "Sets health, armor, and imagination to 9", + .info = "Sets health, armor, and imagination to 9", + .aliases = { "buffmed" }, + .handle = DEVGMCommands::BuffMed, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(BuffMedCommand); + + Command ClearFlagCommand{ + .help = "Clear a player flag", + .info = "Removes the given health or inventory flag from your player. Equivalent of calling `/setflag off <flag id>`", + .aliases = { "clearflag" }, + .handle = DEVGMCommands::ClearFlag, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ClearFlagCommand); + + Command CompleteMissionCommand{ + .help = "Completes the mission", + .info = "Completes the mission, removing it from your journal", + .aliases = { "completemission" }, + .handle = DEVGMCommands::CompleteMission, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(CompleteMissionCommand); + + Command CreatePrivateCommand{ + .help = "Creates a private zone with password", + .info = "Creates a private zone with password", + .aliases = { "createprivate" }, + .handle = DEVGMCommands::CreatePrivate, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(CreatePrivateCommand); + + Command DebugUiCommand{ + .help = "Toggle Debug UI", + .info = "Toggle Debug UI", + .aliases = { "debugui" }, + .handle = DEVGMCommands::DebugUi, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(DebugUiCommand); + + Command DismountCommand{ + .help = "Dismounts you from the vehicle or mount", + .info = "Dismounts you from the vehicle or mount", + .aliases = { "dismount" }, + .handle = DEVGMCommands::Dismount, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(DismountCommand); + + Command ReloadConfigCommand{ + .help = "Reload Server configs", + .info = "Reloads the server with the new config values.", + .aliases = { "reloadconfig", "reload-config" }, + .handle = DEVGMCommands::ReloadConfig, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ReloadConfigCommand); + + Command ForceSaveCommand{ + .help = "Force save your player", + .info = "While saving to database usually happens on regular intervals and when you disconnect from the server, this command saves your player's data to the database", + .aliases = { "forcesave", "force-save" }, + .handle = DEVGMCommands::ForceSave, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ForceSaveCommand); + + Command FreecamCommand{ + .help = "Toggles freecam mode", + .info = "Toggles freecam mode", + .aliases = { "freecam" }, + .handle = DEVGMCommands::Freecam, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(FreecamCommand); + + Command FreeMoneyCommand{ + .help = "Give yourself coins", + .info = "Give yourself coins", + .aliases = { "freemoney", "givemoney", "money", "givecoins", "coins"}, + .handle = DEVGMCommands::FreeMoney, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(FreeMoneyCommand); + + Command GetNavmeshHeightCommand{ + .help = "Display the navmesh height", + .info = "Display the navmesh height at your current position", + .aliases = { "getnavmeshheight" }, + .handle = DEVGMCommands::GetNavmeshHeight, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GetNavmeshHeightCommand); + + Command GiveUScoreCommand{ + .help = "Gives uscore", + .info = "Gives uscore", + .aliases = { "giveuscore" }, + .handle = DEVGMCommands::GiveUScore, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GiveUScoreCommand); + + Command GmAddItemCommand{ + .help = "Give yourseld an item", + .info = "Adds the given item to your inventory by id", + .aliases = { "gmadditem", "give" }, + .handle = DEVGMCommands::GmAddItem, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GmAddItemCommand); + + Command InspectCommand{ + .help = "Inspect an object", + .info = "Finds the closest entity with the given component or LNV variable (ignoring players and racing cars), printing its ID, distance from the player, and whether it is sleeping, as well as the the IDs of all components the entity has. See detailed usage in the DLU docs", + .aliases = { "inspect" }, + .handle = DEVGMCommands::Inspect, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(InspectCommand); + + Command ListSpawnsCommand{ + .help = "List spawn points for players", + .info = "Lists all the character spawn points in the zone. Additionally, this command will display the current scene that plays when the character lands in the next zone, if there is one.", + .aliases = { "list-spawns", "listspawns" }, + .handle = DEVGMCommands::ListSpawns, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ListSpawnsCommand); + + Command LocRowCommand{ + .help = "Prints the your current position and rotation information to the console", + .info = "Prints the your current position and rotation information to the console", + .aliases = { "locrow" }, + .handle = DEVGMCommands::LocRow, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(LocRowCommand); + + Command LookupCommand{ + .help = "Lookup an object", + .info = "Searches through the Objects table in the client SQLite database for items whose display name, name, or description contains the query. Query can be multiple words delimited by spaces.", + .aliases = { "lookup" }, + .handle = DEVGMCommands::Lookup, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(LookupCommand); + + Command PlayAnimationCommand{ + .help = "Play an animation with given ID", + .info = "Play an animation with given ID", + .aliases = { "playanimation", "playanim" }, + .handle = DEVGMCommands::PlayAnimation, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(PlayAnimationCommand); + + Command PlayEffectCommand{ + .help = "Plays an effect", + .info = "Plays an effect", + .aliases = { "playeffect" }, + .handle = DEVGMCommands::PlayEffect, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(PlayEffectCommand); + + Command PlayLvlFxCommand{ + .help = "Plays the level up animation on your character", + .info = "Plays the level up animation on your character", + .aliases = { "playlvlfx" }, + .handle = DEVGMCommands::PlayLvlFx, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(PlayLvlFxCommand); + + Command PlayRebuildFxCommand{ + .help = "Plays the quickbuild animation on your character", + .info = "Plays the quickbuild animation on your character", + .aliases = { "playrebuildfx" }, + .handle = DEVGMCommands::PlayRebuildFx, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(PlayRebuildFxCommand); + + Command PosCommand{ + .help = "Displays your current position in chat and in the console", + .info = "Displays your current position in chat and in the console", + .aliases = { "pos" }, + .handle = DEVGMCommands::Pos, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(PosCommand); + + Command RefillStatsCommand{ + .help = "Refills health, armor, and imagination to their maximum level", + .info = "Refills health, armor, and imagination to their maximum level", + .aliases = { "refillstats" }, + .handle = DEVGMCommands::RefillStats, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(RefillStatsCommand); + + Command ReforgeCommand{ + .help = "Reforges an item", + .info = "Reforges an item", + .aliases = { "reforge" }, + .handle = DEVGMCommands::Reforge, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ReforgeCommand); + + Command ResetMissionCommand{ + .help = "Sets the state of the mission to accepted but not yet started", + .info = "Sets the state of the mission to accepted but not yet started", + .aliases = { "resetmission" }, + .handle = DEVGMCommands::ResetMission, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ResetMissionCommand); + + Command RotCommand{ + .help = "Displays your current rotation in chat and in the console", + .info = "Displays your current rotation in chat and in the console", + .aliases = { "rot" }, + .handle = DEVGMCommands::Rot, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(RotCommand); + + Command RunMacroCommand{ + .help = "Run a macro", + .info = "Runs any command macro found in `./res/macros/`", + .aliases = { "runmacro" }, + .handle = DEVGMCommands::RunMacro, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(RunMacroCommand); + + Command SetControlSchemeCommand{ + .help = "Sets the character control scheme to the specified number", + .info = "Sets the character control scheme to the specified number", + .aliases = { "setcontrolscheme" }, + .handle = DEVGMCommands::SetControlScheme, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetControlSchemeCommand); + + Command SetCurrencyCommand{ + .help = "Sets your coins", + .info = "Sets your coins", + .aliases = { "setcurrency", "setcoins" }, + .handle = DEVGMCommands::SetCurrency, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetCurrencyCommand); + + Command SetFlagCommand{ + .help = "Set a player flag", + .info = "Sets the given inventory or health flag to the given value, where value can be one of \"on\" or \"off\". If no value is given, by default this adds the flag to your character (equivalent of calling `/setflag on <flag id>`)", + .aliases = { "setflag" }, + .handle = DEVGMCommands::SetFlag, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetFlagCommand); + + Command SetInventorySizeCommand{ + .help = "Set your inventory size", + .info = "Sets your inventory size to the given size. If `inventory` is provided, the number or string will be used to set that inventory to the requested size", + .aliases = { "setinventorysize", "setinvsize", "setinvensize" }, + .handle = DEVGMCommands::SetInventorySize, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetInventorySizeCommand); + + Command SetUiStateCommand{ + .help = "Changes UI state", + .info = "Changes UI state", + .aliases = { "setuistate" }, + .handle = DEVGMCommands::SetUiState, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetUiStateCommand); + + Command SpawnCommand{ + .help = "Spawns an object at your location by id", + .info = "Spawns an object at your location by id", + .aliases = { "spawn" }, + .handle = DEVGMCommands::Spawn, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SpawnCommand); + + Command SpawnGroupCommand{ + .help = "", + .info = "", + .aliases = { "spawngroup" }, + .handle = DEVGMCommands::SpawnGroup, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SpawnGroupCommand); + + Command SpeedBoostCommand{ + .help = "Set the players speed multiplier", + .info = "Sets the speed multiplier to the given amount. `/speedboost 1.5` will set the speed multiplier to 1.5x the normal speed", + .aliases = { "speedboost" }, + .handle = DEVGMCommands::SpeedBoost, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SpeedBoostCommand); + + Command StartCelebrationCommand{ + .help = "Starts a celebration effect on your character", + .info = "Starts a celebration effect on your character", + .aliases = { "startcelebration" }, + .handle = DEVGMCommands::StartCelebration, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(StartCelebrationCommand); + + Command StopEffectCommand{ + .help = "Stops the given effect", + .info = "Stops the given effect", + .aliases = { "stopeffect" }, + .handle = DEVGMCommands::StopEffect, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(StopEffectCommand); + + Command ToggleCommand{ + .help = "Toggles UI state", + .info = "Toggles UI state", + .aliases = { "toggle" }, + .handle = DEVGMCommands::Toggle, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ToggleCommand); + + Command TpAllCommand{ + .help = "Teleports all characters to your current position", + .info = "Teleports all characters to your current position", + .aliases = { "tpall" }, + .handle = DEVGMCommands::TpAll, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(TpAllCommand); + + Command TriggerSpawnerCommand{ + .help = "Triggers spawner by name", + .info = "Triggers spawner by name", + .aliases = { "triggerspawner" }, + .handle = DEVGMCommands::TriggerSpawner, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(TriggerSpawnerCommand); + + Command UnlockEmoteCommand{ + .help = "Unlocks for your character the emote of the given id", + .info = "Unlocks for your character the emote of the given id", + .aliases = { "unlock-emote", "unlockemote" }, + .handle = DEVGMCommands::UnlockEmote, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(UnlockEmoteCommand); + + Command SetLevelCommand{ + .help = "Set player level", + .info = "Sets the using entities level to the requested level. Takes an optional parameter of an in-game players username to set the level of", + .aliases = { "setlevel" }, + .handle = DEVGMCommands::SetLevel, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetLevelCommand); + + Command SetSkillSlotCommand{ + .help = "Set an action slot to a specific skill", + .info = "Set an action slot to a specific skill", + .aliases = { "setskillslot" }, + .handle = DEVGMCommands::SetSkillSlot, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetSkillSlotCommand); + + Command SetFactionCommand{ + .help = "Set the players faction", + .info = "Clears the users current factions and sets it", + .aliases = { "setfaction" }, + .handle = DEVGMCommands::SetFaction, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetFactionCommand); + + Command AddFactionCommand{ + .help = "Add the faction to the users list of factions", + .info = "Add the faction to the users list of factions", + .aliases = { "addfaction" }, + .handle = DEVGMCommands::AddFaction, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(AddFactionCommand); + + Command GetFactionsCommand{ + .help = "Shows the player's factions", + .info = "Shows the player's factions", + .aliases = { "getfactions" }, + .handle = DEVGMCommands::GetFactions, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GetFactionsCommand); + + Command SetRewardCodeCommand{ + .help = "Set a reward code for your account", + .info = "Sets the rewardcode for the account you are logged into if it's a valid rewardcode, See cdclient table `RewardCodes`", + .aliases = { "setrewardcode" }, + .handle = DEVGMCommands::SetRewardCode, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetRewardCodeCommand); + + Command CrashCommand{ + .help = "Crash the server", + .info = "Crashes the server", + .aliases = { "crash", "pumpkin" }, + .handle = DEVGMCommands::Crash, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(CrashCommand); + + Command RollLootCommand{ + .help = "Simulate loot rolls", + .info = "Given a `loot matrix index`, look for `item id` in that matrix `amount` times and print to the chat box statistics of rolling that loot matrix.", + .aliases = { "rollloot", "roll-loot" }, + .handle = DEVGMCommands::RollLoot, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(RollLootCommand); + + Command CastSkillCommand{ + .help = "Casts the skill as the player", + .info = "Casts the skill as the player", + .aliases = { "castskill" }, + .handle = DEVGMCommands::CastSkill, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(CastSkillCommand); + + Command DeleteInvenCommand{ + .help = "Delete all items from a specified inventory", + .info = "Delete all items from a specified inventory", + .aliases = { "deleteinven" }, + .handle = DEVGMCommands::DeleteInven, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(DeleteInvenCommand); + + // Register Greater Than Zero Commands + + Command KickCommand{ + .help = "Kicks the player off the server", + .info = "Kicks the player off the server", + .aliases = { "kick" }, + .handle = GMGreaterThanZeroCommands::Kick, + .requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR + }; + RegisterCommand(KickCommand); + + Command MailItemCommand{ + .help = "Mails an item to the given player", + .info = "Mails an item to the given player. The mailed item has predetermined content. The sender name is set to \"Darkflame Universe\". The title of the message is \"Lost item\". The body of the message is \"This is a replacement item for one you lost\".", + .aliases = { "mailitem" }, + .handle = GMGreaterThanZeroCommands::MailItem, + .requiredLevel = eGameMasterLevel::MODERATOR + }; + RegisterCommand(MailItemCommand); + + Command BanCommand{ + .help = "Bans a user from the server", + .info = "Bans a user from the server", + .aliases = { "ban" }, + .handle = GMGreaterThanZeroCommands::Ban, + .requiredLevel = eGameMasterLevel::SENIOR_MODERATOR + }; + RegisterCommand(BanCommand); + + Command ApprovePropertyCommand{ + .help = "Approves a property", + .info = "Approves the property the player is currently visiting", + .aliases = { "approveproperty" }, + .handle = GMGreaterThanZeroCommands::ApproveProperty, + .requiredLevel = eGameMasterLevel::LEAD_MODERATOR + }; + RegisterCommand(ApprovePropertyCommand); + + Command MuteCommand{ + .help = "Mute a player", + .info = "Mute player for the given amount of time. If no time is given, the mute is indefinite.", + .aliases = { "mute" }, + .handle = GMGreaterThanZeroCommands::Mute, + .requiredLevel = eGameMasterLevel::JUNIOR_DEVELOPER + }; + RegisterCommand(MuteCommand); + + Command FlyCommand{ + .help = "Toggle flying", + .info = "Toggles your flying state with an optional parameter for the speed scale.", + .aliases = { "fly" }, + .handle = GMGreaterThanZeroCommands::Fly, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(FlyCommand); + + Command AttackImmuneCommand{ + .help = "Make yourself immune to attacks", + .info = "Sets the character's immunity to basic attacks state, where value can be one of \"1\", to make yourself immune to basic attack damage, or \"0\" to undo", + .aliases = { "attackimmune" }, + .handle = GMGreaterThanZeroCommands::AttackImmune, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(AttackImmuneCommand); + + Command GmImmuneCommand{ + .help = "Sets the character's GMImmune state", + .info = "Sets the character's GMImmune state, where value can be one of \"1\", to make yourself immune to damage, or \"0\" to undo", + .aliases = { "gmimmune" }, + .handle = GMGreaterThanZeroCommands::GmImmune, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GmImmuneCommand); + + Command GmInvisCommand{ + .help = "Toggles invisibility for the character", + .info = "Toggles invisibility for the character, though it's currently a bit buggy. Requires nonzero GM Level for the character, but the account must have a GM level of 8", + .aliases = { "gminvis" }, + .handle = GMGreaterThanZeroCommands::GmInvis, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GmInvisCommand); + + Command SetNameCommand{ + + .help = "Sets a temporary name for your player", + .info = "Sets a temporary name for your player. The name resets when you log out", + .aliases = { "setname" }, + .handle = GMGreaterThanZeroCommands::SetName, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetNameCommand); + + Command TitleCommand{ + .help = "Give your character a title", + .info = "Temporarily appends your player's name with \" - <title>\". This resets when you log out", + .aliases = { "title" }, + .handle = GMGreaterThanZeroCommands::Title, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + 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); + + // 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.", + .aliases = { "help", "h"}, + .handle = GMZeroCommands::Help, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(HelpCommand); + + Command CreditsCommand{ + .help = "Displays DLU Credits", + .info = "Displays the names of the people behind Darkflame Universe.", + .aliases = { "credits" }, + .handle = GMZeroCommands::Credits, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(CreditsCommand); + + Command InfoCommand{ + .help = "Displays server info", + .info = "Displays server info to the user, including where to find the server's source code", + .aliases = { "info" }, + .handle = GMZeroCommands::Info, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(InfoCommand); + + Command DieCommand{ + .help = "Smashes the player", + .info = "Smashes the player as if they were killed by something", + .aliases = { "die" }, + .handle = GMZeroCommands::Die, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(DieCommand); + + Command PingCommand{ + .help = "Displays your average ping.", + .info = "Displays your average ping. If the `-l` flag is used, the latest ping is displayed.", + .aliases = { "ping" }, + .handle = GMZeroCommands::Ping, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(PingCommand); + + Command PvpCommand{ + .help = "Toggle your PVP flag", + .info = "Toggle your PVP flag", + .aliases = { "pvp" }, + .handle = GMZeroCommands::Pvp, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(PvpCommand); + + Command RequestMailCountCommand{ + .help = "Gets the players mail count", + .info = "Sends notification with number of unread messages in the player's mailbox", + .aliases = { "requestmailcount" }, + .handle = GMZeroCommands::RequestMailCount, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(RequestMailCountCommand); + + Command WhoCommand{ + .help = "Displays all players on the instance", + .info = "Displays all players on the instance", + .aliases = { "who" }, + .handle = GMZeroCommands::Who, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(WhoCommand); + + Command FixStatsCommand{ + .help = "Resets skills, buffs, and destroyables", + .info = "Resets skills, buffs, and destroyables", + .aliases = { "fix-stats" }, + .handle = GMZeroCommands::FixStats, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(FixStatsCommand); + + Command JoinCommand{ + .help = "Join a private zone", + .info = "Join a private zone with given password", + .aliases = { "join" }, + .handle = GMZeroCommands::Join, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(JoinCommand); + + Command LeaveZoneCommand{ + .help = "Leave an instanced zone", + .info = "If you are in an instanced zone, transfers you to the closest main world. For example, if you are in an instance of Avant Gardens Survival or the Spider Queen Battle, you are sent to Avant Gardens. If you are in the Battle of Nimbus Station, you are sent to Nimbus Station.", + .aliases = { "leave-zone", "leavezone" }, + .handle = GMZeroCommands::LeaveZone, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(LeaveZoneCommand); + + Command ResurrectCommand{ + .help = "Resurrects the player", + .info = "Resurrects the player", + .aliases = { "resurrect" }, + .handle = GMZeroCommands::Resurrect, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(ResurrectCommand); + + Command InstanceInfoCommand{ + .help = "Display LWOZoneID info for the current zone", + .info = "Display LWOZoneID info for the current zone", + .aliases = { "instanceinfo" }, + .handle = GMZeroCommands::InstanceInfo, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(InstanceInfoCommand); + +} diff --git a/dGame/dUtilities/SlashCommandHandler.h b/dGame/dUtilities/SlashCommandHandler.h index 85b7c697..6a94dd3b 100644 --- a/dGame/dUtilities/SlashCommandHandler.h +++ b/dGame/dUtilities/SlashCommandHandler.h @@ -7,13 +7,28 @@ #define SLASHCOMMANDHANDLER_H #include "RakNetTypes.h" +#include "eGameMasterLevel.h" #include <string> class Entity; +struct Command { + std::string help; + std::string info; + std::vector<std::string> aliases; + std::function<void(Entity*, const SystemAddress&,const std::string)> handle; + eGameMasterLevel requiredLevel = eGameMasterLevel::OPERATOR; +}; + namespace SlashCommandHandler { void HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr); void SendAnnouncement(const std::string& title, const std::string& message); + void RegisterCommand(Command info); + void Startup(); }; +namespace GMZeroCommands { + void Help(Entity* entity, const SystemAddress& sysAddr, const std::string args); +} + #endif // SLASHCOMMANDHANDLER_H diff --git a/dGame/dUtilities/SlashCommands/CMakeLists.txt b/dGame/dUtilities/SlashCommands/CMakeLists.txt new file mode 100644 index 00000000..999cd0ec --- /dev/null +++ b/dGame/dUtilities/SlashCommands/CMakeLists.txt @@ -0,0 +1,6 @@ +set(DGAME_DUTILITIES_SLASHCOMMANDS + "DEVGMCommands.cpp" + "GMGreaterThanZeroCommands.cpp" + "GMZeroCommands.cpp" + PARENT_SCOPE +) diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp new file mode 100644 index 00000000..bc008ab2 --- /dev/null +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -0,0 +1,1592 @@ +#include "DEVGMCommands.h" + +// Classes +#include "AssetManager.h" +#include "Character.h" +#include "ChatPackets.h" +#include "dConfig.h" +#include "dNavMesh.h" +#include "dpWorld.h" +#include "dServer.h" +#include "dpShapeSphere.h" +#include "dZoneManager.h" +#include "EntityInfo.h" +#include "Metrics.hpp" +#include "PlayerManager.h" +#include "SlashCommandHandler.h" +#include "UserManager.h" +#include "User.h" +#include "VanityUtilities.h" +#include "WorldPackets.h" +#include "ZoneInstanceManager.h" + +// Database +#include "Database.h" +#include "CDObjectsTable.h" +#include "CDRewardCodesTable.h" + +// Components +#include "BuffComponent.h" +#include "CharacterComponent.h" +#include "ControllablePhysicsComponent.h" +#include "DestroyableComponent.h" +#include "HavokVehiclePhysicsComponent.h" +#include "InventoryComponent.h" +#include "LevelProgressionComponent.h" +#include "MissionComponent.h" +#include "MovingPlatformComponent.h" +#include "PossessorComponent.h" +#include "ProximityMonitorComponent.h" +#include "RenderComponent.h" +#include "ScriptedActivityComponent.h" +#include "SkillComponent.h" +#include "TriggerComponent.h" + +// Enums +#include "eGameMasterLevel.h" +#include "eMasterMessageType.h" +#include "eInventoryType.h" +#include "ePlayerFlag.h" + + +namespace DEVGMCommands { + void SetGMLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + User* user = UserManager::Instance()->GetUser(entity->GetSystemAddress()); + + const auto level_intermed = GeneralUtils::TryParse<uint32_t>(args); + if (!level_intermed) { + GameMessages::SendSlashCommandFeedbackText(entity, u"Invalid GM level."); + return; + } + eGameMasterLevel level = static_cast<eGameMasterLevel>(level_intermed.value()); + +#ifndef DEVELOPER_SERVER + if (user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER) { + level = eGameMasterLevel::CIVILIAN; + } +#endif + + if (level > user->GetMaxGMLevel()) level = user->GetMaxGMLevel(); + + if (level == entity->GetGMLevel()) return; + bool success = user->GetMaxGMLevel() >= level; + + if (success) { + WorldPackets::SendGMLevelChange(entity->GetSystemAddress(), success, user->GetMaxGMLevel(), entity->GetGMLevel(), level); + GameMessages::SendChatModeUpdate(entity->GetObjectID(), level); + entity->SetGMLevel(level); + LOG("User %s (%i) has changed their GM level to %i for charID %llu", user->GetUsername().c_str(), user->GetAccountID(), level, entity->GetObjectID()); + } + +#ifndef DEVELOPER_SERVER + if ((entity->GetGMLevel() > user->GetMaxGMLevel()) || (entity->GetGMLevel() > eGameMasterLevel::CIVILIAN && user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER)) { + WorldPackets::SendGMLevelChange(entity->GetSystemAddress(), true, user->GetMaxGMLevel(), entity->GetGMLevel(), eGameMasterLevel::CIVILIAN); + GameMessages::SendChatModeUpdate(entity->GetObjectID(), eGameMasterLevel::CIVILIAN); + entity->SetGMLevel(eGameMasterLevel::CIVILIAN); + + GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); + + GameMessages::SendSlashCommandFeedbackText(entity, u"Your game master level has been changed, you may not be able to use all commands."); + } +#endif + } + + + void ToggleNameplate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if ((Game::config->GetValue("allow_nameplate_off") != "1" && entity->GetGMLevel() < eGameMasterLevel::DEVELOPER)) return; + + auto* character = entity->GetCharacter(); + if (character && character->GetBillboardVisible()) { + character->SetBillboardVisible(false); + GameMessages::SendSlashCommandFeedbackText(entity, u"Your nameplate has been turned off and is not visible to players currently in this zone."); + } else { + character->SetBillboardVisible(true); + GameMessages::SendSlashCommandFeedbackText(entity, u"Your nameplate is now on and visible to all players."); + } + } + + void ToggleSkipCinematics(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1" && entity->GetGMLevel() < eGameMasterLevel::DEVELOPER) return; + auto* character = entity->GetCharacter(); + if (!character) return; + bool current = character->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS); + character->SetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS, !current); + if (!current) { + GameMessages::SendSlashCommandFeedbackText(entity, u"You have elected to skip cinematics. Note that not all cinematics can be skipped, but most will be skipped now."); + } else { + GameMessages::SendSlashCommandFeedbackText(entity, u"Cinematics will no longer be skipped."); + } + } + + void ResetMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto missionId = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + if (!missionId) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission ID."); + return; + } + + auto* missionComponent = entity->GetComponent<MissionComponent>(); + if (!missionComponent) return; + missionComponent->ResetMission(missionId.value()); + } + + void SetMinifig(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; + + const auto minifigItemIdExists = GeneralUtils::TryParse<int32_t>(splitArgs[1]); + if (!minifigItemIdExists) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid Minifig Item Id ID."); + return; + } + const int32_t minifigItemId = minifigItemIdExists.value(); + Game::entityManager->DestructEntity(entity, sysAddr); + auto* charComp = entity->GetComponent<CharacterComponent>(); + std::string lowerName = splitArgs[0]; + if (lowerName.empty()) return; + std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); + if (lowerName == "eyebrows") { + charComp->m_Character->SetEyebrows(minifigItemId); + } else if (lowerName == "eyes") { + charComp->m_Character->SetEyes(minifigItemId); + } else if (lowerName == "haircolor") { + charComp->m_Character->SetHairColor(minifigItemId); + } else if (lowerName == "hairstyle") { + charComp->m_Character->SetHairStyle(minifigItemId); + } else if (lowerName == "pants") { + charComp->m_Character->SetPantsColor(minifigItemId); + } else if (lowerName == "lefthand") { + charComp->m_Character->SetLeftHand(minifigItemId); + } else if (lowerName == "mouth") { + charComp->m_Character->SetMouth(minifigItemId); + } else if (lowerName == "righthand") { + charComp->m_Character->SetRightHand(minifigItemId); + } else if (lowerName == "shirtcolor") { + charComp->m_Character->SetShirtColor(minifigItemId); + } else if (lowerName == "hands") { + charComp->m_Character->SetLeftHand(minifigItemId); + charComp->m_Character->SetRightHand(minifigItemId); + } else { + Game::entityManager->ConstructEntity(entity); + ChatPackets::SendSystemMessage(sysAddr, u"Invalid Minifig item to change, try one of the following: Eyebrows, Eyes, HairColor, HairStyle, Pants, LeftHand, Mouth, RightHand, Shirt, Hands"); + return; + } + + Game::entityManager->ConstructEntity(entity); + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(lowerName) + u" set to " + (GeneralUtils::to_u16string(minifigItemId))); + + GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); // need to retoggle because it gets reenabled on creation of new character + } + + void PlayAnimation(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + std::u16string anim = GeneralUtils::ASCIIToUTF16(splitArgs[0], splitArgs[0].size()); + RenderComponent::PlayAnimation(entity, anim); + auto* possessorComponent = entity->GetComponent<PossessorComponent>(); + if (possessorComponent) { + auto* possessedComponent = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); + if (possessedComponent) RenderComponent::PlayAnimation(possessedComponent, anim); + } + } + + void ListSpawns(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + for (const auto& pair : Game::entityManager->GetSpawnPointEntities()) { + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(pair.first)); + } + + ChatPackets::SendSystemMessage(sysAddr, u"Current: " + GeneralUtils::ASCIIToUTF16(entity->GetCharacter()->GetTargetScene())); + } + + void UnlockEmote(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto emoteID = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + + if (!emoteID) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid emote ID."); + return; + } + + entity->GetCharacter()->UnlockEmote(emoteID.value()); + } + + void ForceSave(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + entity->GetCharacter()->SaveXMLToDatabase(); + } + + void Kill(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + ChatPackets::SendSystemMessage(sysAddr, u"Brutally murdering that player, if online on this server."); + + auto* player = PlayerManager::GetPlayer(splitArgs[0]); + if (player) { + player->Smash(entity->GetObjectID()); + ChatPackets::SendSystemMessage(sysAddr, u"It has been done, do you feel good about yourself now?"); + return; + } + + ChatPackets::SendSystemMessage(sysAddr, u"They were saved from your carnage."); + return; + } + + void SpeedBoost(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto boostOptional = GeneralUtils::TryParse<float>(splitArgs[0]); + if (!boostOptional) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid boost."); + return; + } + const float boost = boostOptional.value(); + + auto* controllablePhysicsComponent = entity->GetComponent<ControllablePhysicsComponent>(); + + if (!controllablePhysicsComponent) return; + controllablePhysicsComponent->SetSpeedMultiplier(boost); + + // speedboost possessables + auto possessor = entity->GetComponent<PossessorComponent>(); + if (possessor) { + auto possessedID = possessor->GetPossessable(); + if (possessedID != LWOOBJID_EMPTY) { + auto possessable = Game::entityManager->GetEntity(possessedID); + if (possessable) { + auto* possessControllablePhysicsComponent = possessable->GetComponent<ControllablePhysicsComponent>(); + if (possessControllablePhysicsComponent) { + possessControllablePhysicsComponent->SetSpeedMultiplier(boost); + } + } + } + } + + Game::entityManager->SerializeEntity(entity); + } + + void Freecam(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto state = !entity->GetVar<bool>(u"freecam"); + entity->SetVar<bool>(u"freecam", state); + + GameMessages::SendSetPlayerControlScheme(entity, static_cast<eControlScheme>(state ? 9 : 1)); + + ChatPackets::SendSystemMessage(sysAddr, u"Toggled freecam."); + } + + void SetControlScheme(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto scheme = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + + if (!scheme) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid control scheme."); + return; + } + + GameMessages::SendSetPlayerControlScheme(entity, static_cast<eControlScheme>(scheme.value())); + + ChatPackets::SendSystemMessage(sysAddr, u"Switched control scheme."); + } + + void SetUiState(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + AMFArrayValue uiState; + + uiState.Insert("state", splitArgs[0]); + + GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, "pushGameState", uiState); + + ChatPackets::SendSystemMessage(sysAddr, u"Switched UI state."); + } + + void Toggle(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + AMFArrayValue amfArgs; + + amfArgs.Insert("visible", true); + + GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, splitArgs[0], amfArgs); + + ChatPackets::SendSystemMessage(sysAddr, u"Toggled UI state."); + } + + void SetInventorySize(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto sizeOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + if (!sizeOptional) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid size."); + return; + } + const uint32_t size = sizeOptional.value(); + + eInventoryType selectedInventory = eInventoryType::ITEMS; + + // a possible inventory was provided if we got more than 1 argument + if (splitArgs.size() >= 2) { + selectedInventory = GeneralUtils::TryParse<eInventoryType>(splitArgs.at(1)).value_or(eInventoryType::INVALID); + if (selectedInventory == eInventoryType::INVALID) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid inventory."); + return; + } else { + // In this case, we treat the input as a string and try to find it in the reflection list + std::transform(splitArgs.at(1).begin(), splitArgs.at(1).end(), splitArgs.at(1).begin(), ::toupper); + for (uint32_t index = 0; index < NUMBER_OF_INVENTORIES; index++) { + if (std::string_view(splitArgs.at(1)) == std::string_view(InventoryType::InventoryTypeToString(static_cast<eInventoryType>(index)))) selectedInventory = static_cast<eInventoryType>(index); + } + } + + ChatPackets::SendSystemMessage(sysAddr, u"Setting inventory " + + GeneralUtils::ASCIIToUTF16(splitArgs.at(1)) + + u" to size " + + GeneralUtils::to_u16string(size)); + } else ChatPackets::SendSystemMessage(sysAddr, u"Setting inventory ITEMS to size " + GeneralUtils::to_u16string(size)); + + auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); + if (inventoryComponent) { + auto* inventory = inventoryComponent->GetInventory(selectedInventory); + + inventory->SetSize(size); + } + } + + void RunMacro(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + // Only process if input does not contain separator charaters + if (splitArgs[0].find("/") != std::string::npos) return; + if (splitArgs[0].find("\\") != std::string::npos) return; + + auto infile = Game::assetManager->GetFile(("macros/" + splitArgs[0] + ".scm").c_str()); + + if (!infile) { + ChatPackets::SendSystemMessage(sysAddr, u"Unknown macro! Is the filename right?"); + return; + } + + if (infile.good()) { + std::string line; + while (std::getline(infile, line)) { + // Do this in two separate calls to catch both \n and \r\n + line.erase(std::remove(line.begin(), line.end(), '\n'), line.end()); + line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); + SlashCommandHandler::HandleChatCommand(GeneralUtils::ASCIIToUTF16(line), entity, sysAddr); + } + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Unknown macro! Is the filename right?"); + } + } + + void AddMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto missionID = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0)); + + if (!missionID) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission id."); + return; + } + + auto comp = static_cast<MissionComponent*>(entity->GetComponent(eReplicaComponentType::MISSION)); + if (comp) comp->AcceptMission(missionID.value(), true); + } + + void CompleteMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto missionID = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0)); + + if (!missionID) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission id."); + return; + } + + auto comp = static_cast<MissionComponent*>(entity->GetComponent(eReplicaComponentType::MISSION)); + if (comp) comp->CompleteMission(missionID.value(), true); + } + + void SetFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() == 1) { + const auto flagId = GeneralUtils::TryParse<int32_t>(splitArgs.at(0)); + + if (!flagId) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); + return; + } + + entity->GetCharacter()->SetPlayerFlag(flagId.value(), true); + } else if (splitArgs.size() >= 2) { + const auto flagId = GeneralUtils::TryParse<int32_t>(splitArgs.at(1)); + std::string onOffFlag = splitArgs.at(0); + if (!flagId) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); + return; + } + + if (onOffFlag != "off" && onOffFlag != "on") { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag type."); + return; + } + + entity->GetCharacter()->SetPlayerFlag(flagId.value(), onOffFlag == "on"); + } + } + + void ClearFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto flagId = GeneralUtils::TryParse<int32_t>(splitArgs.at(0)); + + if (!flagId) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); + return; + } + + entity->GetCharacter()->SetPlayerFlag(flagId.value(), false); + } + + void PlayEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 3) return; + + const auto effectID = GeneralUtils::TryParse<int32_t>(splitArgs.at(0)); + + if (!effectID) return; + + // FIXME: use fallible ASCIIToUTF16 conversion, because non-ascii isn't valid anyway + GameMessages::SendPlayFXEffect(entity->GetObjectID(), effectID.value(), GeneralUtils::ASCIIToUTF16(splitArgs.at(1)), splitArgs.at(2)); + } + + void StopEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + GameMessages::SendStopFXEffect(entity, true, splitArgs[0]); + } + + void SetAnnTitle(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + entity->GetCharacter()->SetAnnouncementTitle(args); + } + + void SetAnnMsg(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + entity->GetCharacter()->SetAnnouncementMessage(args); + } + + void Announce(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (entity->GetCharacter()->GetAnnouncementTitle().empty() || entity->GetCharacter()->GetAnnouncementMessage().empty()) { + ChatPackets::SendSystemMessage(sysAddr, u"Use /setanntitle <title> & /setannmsg <msg> first!"); + return; + } + + SlashCommandHandler::SendAnnouncement(entity->GetCharacter()->GetAnnouncementTitle(), entity->GetCharacter()->GetAnnouncementMessage()); + } + + void ShutdownUniverse(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + //Tell the master server that we're going to be shutting down whole "universe": + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::MASTER, eMasterMessageType::SHUTDOWN_UNIVERSE); + Game::server->SendToMaster(bitStream); + ChatPackets::SendSystemMessage(sysAddr, u"Sent universe shutdown notification to master."); + + //Tell chat to send an announcement to all servers + SlashCommandHandler::SendAnnouncement("Servers Closing Soon!", "DLU servers will close for maintenance in 10 minutes from now."); + } + + void GetNavmeshHeight(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto control = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS)); + if (!control) return; + + float y = dpWorld::GetNavMesh()->GetHeightAtPoint(control->GetPosition()); + std::u16string msg = u"Navmesh height: " + (GeneralUtils::to_u16string(y)); + ChatPackets::SendSystemMessage(sysAddr, msg); + } + + void GmAddItem(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + if (splitArgs.size() == 1) { + const auto itemLOT = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0)); + + if (!itemLOT) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid item LOT."); + return; + } + + InventoryComponent* inventory = static_cast<InventoryComponent*>(entity->GetComponent(eReplicaComponentType::INVENTORY)); + + inventory->AddItem(itemLOT.value(), 1, eLootSourceType::MODERATION); + } else if (splitArgs.size() == 2) { + const auto itemLOT = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0)); + if (!itemLOT) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid item LOT."); + return; + } + + const auto count = GeneralUtils::TryParse<uint32_t>(splitArgs.at(1)); + if (!count) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid item count."); + return; + } + + InventoryComponent* inventory = static_cast<InventoryComponent*>(entity->GetComponent(eReplicaComponentType::INVENTORY)); + + inventory->AddItem(itemLOT.value(), count.value(), eLootSourceType::MODERATION); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /gmadditem <lot>"); + } + } + + void Teleport(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + NiPoint3 pos{}; + if (splitArgs.size() == 3) { + + const auto x = GeneralUtils::TryParse<float>(splitArgs.at(0)); + if (!x) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid x."); + return; + } + + const auto y = GeneralUtils::TryParse<float>(splitArgs.at(1)); + if (!y) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid y."); + return; + } + + const auto z = GeneralUtils::TryParse<float>(splitArgs.at(2)); + if (!z) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid z."); + return; + } + + pos.SetX(x.value()); + pos.SetY(y.value()); + pos.SetZ(z.value()); + + LOG("Teleporting objectID: %llu to %f, %f, %f", entity->GetObjectID(), pos.x, pos.y, pos.z); + GameMessages::SendTeleport(entity->GetObjectID(), pos, NiQuaternion(), sysAddr); + } else if (splitArgs.size() == 2) { + + const auto x = GeneralUtils::TryParse<float>(splitArgs.at(0)); + if (!x) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid x."); + return; + } + + const auto z = GeneralUtils::TryParse<float>(splitArgs.at(1)); + if (!z) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid z."); + return; + } + + pos.SetX(x.value()); + pos.SetY(0.0f); + pos.SetZ(z.value()); + + LOG("Teleporting objectID: %llu to X: %f, Z: %f", entity->GetObjectID(), pos.x, pos.z); + GameMessages::SendTeleport(entity->GetObjectID(), pos, NiQuaternion(), sysAddr); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /teleport <x> (<y>) <z> - if no Y given, will teleport to the height of the terrain (or any physics object)."); + } + + auto* possessorComponent = entity->GetComponent<PossessorComponent>(); + if (possessorComponent) { + auto* possassableEntity = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); + + if (possassableEntity != nullptr) { + auto* havokVehiclePhysicsComponent = possassableEntity->GetComponent<HavokVehiclePhysicsComponent>(); + if (havokVehiclePhysicsComponent) { + havokVehiclePhysicsComponent->SetPosition(pos); + Game::entityManager->SerializeEntity(possassableEntity); + } else GameMessages::SendTeleport(possassableEntity->GetObjectID(), pos, NiQuaternion(), sysAddr); + } + } + } + + void TpAll(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto pos = entity->GetPosition(); + + const auto characters = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::CHARACTER); + + for (auto* character : characters) { + GameMessages::SendTeleport(character->GetObjectID(), pos, NiQuaternion(), character->GetSystemAddress()); + } + } + + void Dismount(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto* possessorComponent = entity->GetComponent<PossessorComponent>(); + if (possessorComponent) { + auto possessableId = possessorComponent->GetPossessable(); + if (possessableId != LWOOBJID_EMPTY) { + auto* possessableEntity = Game::entityManager->GetEntity(possessableId); + if (possessableEntity) possessorComponent->Dismount(possessableEntity, true); + } + } + } + + void BuffMe(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); + if (dest) { + dest->SetHealth(999); + dest->SetMaxHealth(999.0f); + dest->SetArmor(999); + dest->SetMaxArmor(999.0f); + dest->SetImagination(999); + dest->SetMaxImagination(999.0f); + } + Game::entityManager->SerializeEntity(entity); + } + + void StartCelebration(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto celebration = GeneralUtils::TryParse<int32_t>(splitArgs.at(0)); + + if (!celebration) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid celebration."); + return; + } + + GameMessages::SendStartCelebrationEffect(entity, entity->GetSystemAddress(), celebration.value()); + } + + void BuffMed(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); + if (dest) { + dest->SetHealth(9); + dest->SetMaxHealth(9.0f); + dest->SetArmor(9); + dest->SetMaxArmor(9.0f); + dest->SetImagination(9); + dest->SetMaxImagination(9.0f); + } + Game::entityManager->SerializeEntity(entity); + } + + void RefillStats(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); + if (dest) { + dest->SetHealth(static_cast<int32_t>(dest->GetMaxHealth())); + dest->SetArmor(static_cast<int32_t>(dest->GetMaxArmor())); + dest->SetImagination(static_cast<int32_t>(dest->GetMaxImagination())); + } + + Game::entityManager->SerializeEntity(entity); + } + + void Lookup(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto query = CDClientDatabase::CreatePreppedStmt( + "SELECT `id`, `name` FROM `Objects` WHERE `displayName` LIKE ?1 OR `name` LIKE ?1 OR `description` LIKE ?1 LIMIT 50"); + + const std::string query_text = "%" + args + "%"; + query.bind(1, query_text.c_str()); + + auto tables = query.execQuery(); + + while (!tables.eof()) { + std::string message = std::to_string(tables.getIntField("id")) + " - " + tables.getStringField("name"); + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(message, message.size())); + tables.nextRow(); + } + } + + void Spawn(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + ControllablePhysicsComponent* comp = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS)); + if (!comp) return; + + const auto lot = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + + if (!lot) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot."); + return; + } + + EntityInfo info; + info.lot = lot.value(); + info.pos = comp->GetPosition(); + info.rot = comp->GetRotation(); + info.spawner = nullptr; + info.spawnerID = entity->GetObjectID(); + info.spawnerNodeID = 0; + + Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr); + + if (newEntity == nullptr) { + ChatPackets::SendSystemMessage(sysAddr, u"Failed to spawn entity."); + return; + } + + Game::entityManager->ConstructEntity(newEntity); + } + + void SpawnGroup(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 3) return; + + const auto lot = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + if (!lot) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot."); + return; + } + + const auto numberToSpawnOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); + if (!numberToSpawnOptional && numberToSpawnOptional.value() > 0) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid number of enemies to spawn."); + return; + } + uint32_t numberToSpawn = numberToSpawnOptional.value(); + + // Must spawn within a radius of at least 0.0f + const auto radiusToSpawnWithinOptional = GeneralUtils::TryParse<float>(splitArgs[2]); + if (!radiusToSpawnWithinOptional && radiusToSpawnWithinOptional.value() < 0.0f) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid radius to spawn within."); + return; + } + const float radiusToSpawnWithin = radiusToSpawnWithinOptional.value(); + + EntityInfo info; + info.lot = lot.value(); + info.spawner = nullptr; + info.spawnerID = entity->GetObjectID(); + info.spawnerNodeID = 0; + + auto playerPosition = entity->GetPosition(); + while (numberToSpawn > 0) { + auto randomAngle = GeneralUtils::GenerateRandomNumber<float>(0.0f, 2 * PI); + auto randomRadius = GeneralUtils::GenerateRandomNumber<float>(0.0f, radiusToSpawnWithin); + + // Set the position to the generated random position plus the player position. This will + // spawn the entity in a circle around the player. As you get further from the player, the angle chosen will get less accurate. + info.pos = playerPosition + NiPoint3(cos(randomAngle) * randomRadius, 0.0f, sin(randomAngle) * randomRadius); + info.rot = NiQuaternion(); + + auto newEntity = Game::entityManager->CreateEntity(info); + if (newEntity == nullptr) { + ChatPackets::SendSystemMessage(sysAddr, u"Failed to spawn entity."); + return; + } + + Game::entityManager->ConstructEntity(newEntity); + numberToSpawn--; + } + } + + void GiveUScore(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto uscoreOptional = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + if (!uscoreOptional) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid uscore."); + return; + } + const int32_t uscore = uscoreOptional.value(); + + CharacterComponent* character = entity->GetComponent<CharacterComponent>(); + if (character) character->SetUScore(character->GetUScore() + uscore); + // MODERATION should work but it doesn't. Relog to see uscore changes + + eLootSourceType lootType = eLootSourceType::MODERATION; + + if (splitArgs.size() >= 2) { + const auto type = GeneralUtils::TryParse<eLootSourceType>(splitArgs[1]); + lootType = type.value_or(lootType); + } + + GameMessages::SendModifyLEGOScore(entity, entity->GetSystemAddress(), uscore, lootType); + } + + void SetLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + // We may be trying to set a specific players level to a level. If so override the entity with the requested players. + std::string requestedPlayerToSetLevelOf = ""; + if (splitArgs.size() > 1) { + requestedPlayerToSetLevelOf = splitArgs[1]; + + auto requestedPlayer = PlayerManager::GetPlayer(requestedPlayerToSetLevelOf); + + if (!requestedPlayer) { + ChatPackets::SendSystemMessage(sysAddr, u"No player found with username: (" + GeneralUtils::UTF8ToUTF16(requestedPlayerToSetLevelOf) + u")."); + return; + } + + if (!requestedPlayer->GetOwner()) { + ChatPackets::SendSystemMessage(sysAddr, u"No entity found with username: (" + GeneralUtils::UTF8ToUTF16(requestedPlayerToSetLevelOf) + u")."); + return; + } + + entity = requestedPlayer->GetOwner(); + } + const auto requestedLevelOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + uint32_t oldLevel; + + // first check the level is valid + if (!requestedLevelOptional) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid level."); + return; + } + uint32_t requestedLevel = requestedLevelOptional.value(); + // query to set our uscore to the correct value for this level + + auto characterComponent = entity->GetComponent<CharacterComponent>(); + if (!characterComponent) return; + auto levelComponent = entity->GetComponent<LevelProgressionComponent>(); + auto query = CDClientDatabase::CreatePreppedStmt("SELECT requiredUScore from LevelProgressionLookup WHERE id = ?;"); + query.bind(1, static_cast<int>(requestedLevel)); + auto result = query.execQuery(); + + if (result.eof()) return; + + // Set the UScore first + oldLevel = levelComponent->GetLevel(); + characterComponent->SetUScore(result.getIntField(0, characterComponent->GetUScore())); + + // handle level up for each level we have passed if we set our level to be higher than the current one. + if (oldLevel < requestedLevel) { + while (oldLevel < requestedLevel) { + oldLevel += 1; + levelComponent->SetLevel(oldLevel); + levelComponent->HandleLevelUp(); + } + } else { + levelComponent->SetLevel(requestedLevel); + } + + if (requestedPlayerToSetLevelOf != "") { + ChatPackets::SendSystemMessage( + sysAddr, u"Set " + GeneralUtils::UTF8ToUTF16(requestedPlayerToSetLevelOf) + u"'s level to " + GeneralUtils::to_u16string(requestedLevel) + + u" and UScore to " + GeneralUtils::to_u16string(characterComponent->GetUScore()) + + u". Relog to see changes."); + } else { + ChatPackets::SendSystemMessage( + sysAddr, u"Set your level to " + GeneralUtils::to_u16string(requestedLevel) + + u" and UScore to " + GeneralUtils::to_u16string(characterComponent->GetUScore()) + + u". Relog to see changes."); + } + } + + void Pos(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto position = entity->GetPosition(); + + ChatPackets::SendSystemMessage(sysAddr, u"<" + (GeneralUtils::to_u16string(position.x)) + u", " + (GeneralUtils::to_u16string(position.y)) + u", " + (GeneralUtils::to_u16string(position.z)) + u">"); + + LOG("Position: %f, %f, %f", position.x, position.y, position.z); + } + + void Rot(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto rotation = entity->GetRotation(); + + ChatPackets::SendSystemMessage(sysAddr, u"<" + (GeneralUtils::to_u16string(rotation.w)) + u", " + (GeneralUtils::to_u16string(rotation.x)) + u", " + (GeneralUtils::to_u16string(rotation.y)) + u", " + (GeneralUtils::to_u16string(rotation.z)) + u">"); + + LOG("Rotation: %f, %f, %f, %f", rotation.w, rotation.x, rotation.y, rotation.z); + } + + void LocRow(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto position = entity->GetPosition(); + const auto rotation = entity->GetRotation(); + + LOG("<location x=\"%f\" y=\"%f\" z=\"%f\" rw=\"%f\" rx=\"%f\" ry=\"%f\" rz=\"%f\" />", position.x, position.y, position.z, rotation.w, rotation.x, rotation.y, rotation.z); + } + + void PlayLvlFx(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + GameMessages::SendPlayFXEffect(entity, 7074, u"create", "7074", LWOOBJID_EMPTY, 1.0f, 1.0f, true); + } + + void PlayRebuildFx(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + GameMessages::SendPlayFXEffect(entity, 230, u"rebuild", "230", LWOOBJID_EMPTY, 1.0f, 1.0f, true); + } + + void FreeMoney(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto money = GeneralUtils::TryParse<int64_t>(splitArgs[0]); + + if (!money) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid money."); + return; + } + + auto* ch = entity->GetCharacter(); + ch->SetCoins(ch->GetCoins() + money.value(), eLootSourceType::MODERATION); + } + + void SetCurrency(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto money = GeneralUtils::TryParse<int64_t>(splitArgs[0]); + + if (!money) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid money."); + return; + } + + auto* ch = entity->GetCharacter(); + ch->SetCoins(money.value(), eLootSourceType::MODERATION); + } + + void Buff(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; + + auto* buffComponent = entity->GetComponent<BuffComponent>(); + + const auto id = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + if (!id) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid buff id."); + return; + } + + const auto duration = GeneralUtils::TryParse<int32_t>(splitArgs[1]); + if (!duration) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid buff duration."); + return; + } + + if (buffComponent) buffComponent->ApplyBuff(id.value(), duration.value(), entity->GetObjectID()); + } + + void TestMap(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + ChatPackets::SendSystemMessage(sysAddr, u"Requesting map change..."); + LWOCLONEID cloneId = 0; + bool force = false; + + const auto reqZoneOptional = GeneralUtils::TryParse<LWOMAPID>(splitArgs[0]); + if (!reqZoneOptional) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid zone."); + return; + } + const LWOMAPID reqZone = reqZoneOptional.value(); + + if (splitArgs.size() > 1) { + auto index = 1; + + if (splitArgs[index] == "force") { + index++; + + force = true; + } + + if (splitArgs.size() > index) { + const auto cloneIdOptional = GeneralUtils::TryParse<LWOCLONEID>(splitArgs[index]); + if (!cloneIdOptional) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone id."); + return; + } + cloneId = cloneIdOptional.value(); + } + } + + const auto objid = entity->GetObjectID(); + + if (force || Game::zoneManager->CheckIfAccessibleZone(reqZone)) { // to prevent tomfoolery + + ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, reqZone, cloneId, false, [objid](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { + + auto* entity = Game::entityManager->GetEntity(objid); + if (!entity) return; + + const auto sysAddr = entity->GetSystemAddress(); + + ChatPackets::SendSystemMessage(sysAddr, u"Transfering map..."); + + LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); + if (entity->GetCharacter()) { + entity->GetCharacter()->SetZoneID(zoneID); + entity->GetCharacter()->SetZoneInstance(zoneInstance); + entity->GetCharacter()->SetZoneClone(zoneClone); + entity->GetComponent<CharacterComponent>()->SetLastRocketConfig(u""); + } + + entity->GetCharacter()->SaveXMLToDatabase(); + + WorldPackets::SendTransferToWorld(sysAddr, serverIP, serverPort, mythranShift); + return; + }); + } else { + std::string msg = "ZoneID not found or allowed: "; + msg.append(splitArgs[0]); // FIXME: unnecessary utf16 re-encoding just for error + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(msg, msg.size())); + } + } + + void CreatePrivate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 3) return; + + const auto zone = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + if (!zone) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid zone."); + return; + } + + const auto clone = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); + if (!clone) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone."); + return; + } + + const auto& password = splitArgs[2]; + + ZoneInstanceManager::Instance()->CreatePrivateZone(Game::server, zone.value(), clone.value(), password); + + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16("Sent request for private zone with password: " + password)); + } + + void DebugUi(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + ChatPackets::SendSystemMessage(sysAddr, u"Opening UIDebugger..."); + } + + void Boost(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + auto* possessorComponent = entity->GetComponent<PossessorComponent>(); + + if (possessorComponent == nullptr) { + return; + } + + auto* vehicle = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); + + if (vehicle == nullptr) { + return; + } + + if (splitArgs.size() >= 1) { + const auto time = GeneralUtils::TryParse<float>(splitArgs[0]); + + if (!time) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid boost time."); + return; + } else { + GameMessages::SendVehicleAddPassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + entity->AddCallbackTimer(time.value(), [vehicle]() { + if (!vehicle) return; + GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + }); + } + } else { + GameMessages::SendVehicleAddPassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + } + } + + void Unboost(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto* possessorComponent = entity->GetComponent<PossessorComponent>(); + + if (possessorComponent == nullptr) return; + auto* vehicle = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); + + if (vehicle == nullptr) return; + GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + } + + void ActivateSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto spawners = Game::zoneManager->GetSpawnersByName(splitArgs[0]); + + for (auto* spawner : spawners) { + spawner->Activate(); + } + + spawners = Game::zoneManager->GetSpawnersInGroup(splitArgs[0]); + + for (auto* spawner : spawners) { + spawner->Activate(); + } + } + + 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<PhantomPhysicsComponent*>(en->GetComponent(eReplicaComponentType::PHANTOM_PHYSICS)); + if (phys) + phys->SpawnVertices(); + } + } + + void ReportProxPhys(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PROXIMITY_MONITOR); + for (auto en : entities) { + auto phys = static_cast<ProximityMonitorComponent*>(en->GetComponent(eReplicaComponentType::PROXIMITY_MONITOR)); + if (phys) { + for (auto prox : phys->GetProximitiesData()) { + if (!prox.second) continue; + + auto sphere = static_cast<dpShapeSphere*>(prox.second->GetShape()); + auto pos = prox.second->GetPosition(); + LOG("Proximity: %s, r: %f, pos: %f, %f, %f", prox.first.c_str(), sphere->GetRadius(), pos.x, pos.y, pos.z); + } + } + } + } + + void TriggerSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto spawners = Game::zoneManager->GetSpawnersByName(splitArgs[0]); + + for (auto* spawner : spawners) { + spawner->Spawn(); + } + + spawners = Game::zoneManager->GetSpawnersInGroup(splitArgs[0]); + + for (auto* spawner : spawners) { + spawner->Spawn(); + } + } + + void Reforge(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; + + const auto baseItem = GeneralUtils::TryParse<LOT>(splitArgs[0]); + if (!baseItem) return; + + const auto reforgedItem = GeneralUtils::TryParse<LOT>(splitArgs[1]); + if (!reforgedItem) return; + + auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); + if (!inventoryComponent) return; + + std::vector<LDFBaseData*> data{}; + data.push_back(new LDFData<int32_t>(u"reforgedLOT", reforgedItem.value())); + + inventoryComponent->AddItem(baseItem.value(), 1, eLootSourceType::MODERATION, eInventoryType::INVALID, data); + } + + void Crash(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + ChatPackets::SendSystemMessage(sysAddr, u"Crashing..."); + + int* badPtr = nullptr; + *badPtr = 0; + } + + void Metrics(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + for (const auto variable : Metrics::GetAllMetrics()) { + auto* metric = Metrics::GetMetric(variable); + + if (metric == nullptr) { + continue; + } + + ChatPackets::SendSystemMessage( + sysAddr, + GeneralUtils::ASCIIToUTF16(Metrics::MetricVariableToString(variable)) + + u": " + + GeneralUtils::to_u16string(Metrics::ToMiliseconds(metric->average)) + + u"ms" + ); + } + + ChatPackets::SendSystemMessage( + sysAddr, + u"Peak RSS: " + GeneralUtils::to_u16string(static_cast<float>(static_cast<double>(Metrics::GetPeakRSS()) / 1.024e6)) + + u"MB" + ); + + ChatPackets::SendSystemMessage( + sysAddr, + u"Current RSS: " + GeneralUtils::to_u16string(static_cast<float>(static_cast<double>(Metrics::GetCurrentRSS()) / 1.024e6)) + + u"MB" + ); + + ChatPackets::SendSystemMessage( + sysAddr, + u"Process ID: " + GeneralUtils::to_u16string(Metrics::GetProcessID()) + ); + } + + void ReloadConfig(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + Game::config->ReloadConfig(); + VanityUtilities::SpawnVanity(); + dpWorld::Reload(); + auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPTED_ACTIVITY); + for (const auto* const entity : entities) { + auto* const scriptedActivityComponent = entity->GetComponent<ScriptedActivityComponent>(); + if (!scriptedActivityComponent) continue; + + scriptedActivityComponent->ReloadConfig(); + } + Game::server->UpdateMaximumMtuSize(); + Game::server->UpdateBandwidthLimit(); + ChatPackets::SendSystemMessage(sysAddr, u"Successfully reloaded config for world!"); + } + + void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 3) return; + + const auto lootMatrixIndex = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + if (!lootMatrixIndex) return; + + const auto targetLot = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); + if (!targetLot) return; + + const auto loops = GeneralUtils::TryParse<uint32_t>(splitArgs[2]); + if (!loops) return; + + uint64_t totalRuns = 0; + + for (uint32_t i = 0; i < loops; i++) { + while (true) { + auto lootRoll = Loot::RollLootMatrix(lootMatrixIndex.value()); + totalRuns += 1; + bool doBreak = false; + for (const auto& kv : lootRoll) { + if (static_cast<uint32_t>(kv.first) == targetLot) { + doBreak = true; + } + } + if (doBreak) break; + } + } + + std::u16string message = u"Ran loot drops looking for " + + GeneralUtils::to_u16string(targetLot.value()) + + u", " + + GeneralUtils::to_u16string(loops.value()) + + u" times. It ran " + + GeneralUtils::to_u16string(totalRuns) + + u" times. Averaging out at " + + GeneralUtils::to_u16string(static_cast<float>(totalRuns) / loops.value()); + + ChatPackets::SendSystemMessage(sysAddr, message); + } + + void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + eInventoryType inventoryType = eInventoryType::INVALID; + + const auto inventoryTypeOptional = GeneralUtils::TryParse<eInventoryType>(splitArgs[0]); + if (!inventoryTypeOptional) { + // In this case, we treat the input as a string and try to find it in the reflection list + std::transform(splitArgs[0].begin(), splitArgs[0].end(), splitArgs[0].begin(), ::toupper); + LOG("looking for inventory %s", splitArgs[0].c_str()); + for (uint32_t index = 0; index < NUMBER_OF_INVENTORIES; index++) { + if (std::string_view(splitArgs[0]) == std::string_view(InventoryType::InventoryTypeToString(static_cast<eInventoryType>(index)))) inventoryType = static_cast<eInventoryType>(index); + } + } else { + inventoryType = inventoryTypeOptional.value(); + } + + if (inventoryType == eInventoryType::INVALID || inventoryType >= NUMBER_OF_INVENTORIES) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid inventory provided."); + return; + } + + auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); + if (!inventoryComponent) return; + + auto* inventoryToDelete = inventoryComponent->GetInventory(inventoryType); + if (!inventoryToDelete) return; + + inventoryToDelete->DeleteAllItems(); + LOG("Deleted inventory %s for user %llu", splitArgs[0].c_str(), entity->GetObjectID()); + ChatPackets::SendSystemMessage(sysAddr, u"Deleted inventory " + GeneralUtils::UTF8ToUTF16(splitArgs[0])); + } + + void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto* skillComponent = entity->GetComponent<SkillComponent>(); + if (skillComponent) { + const auto skillId = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + + if (!skillId) { + ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill ID."); + return; + } else { + skillComponent->CastSkill(skillId.value(), entity->GetObjectID(), entity->GetObjectID()); + ChatPackets::SendSystemMessage(sysAddr, u"Cast skill"); + } + } + } + + void SetSkillSlot(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; + + auto* const inventoryComponent = entity->GetComponent<InventoryComponent>(); + if (inventoryComponent) { + const auto slot = GeneralUtils::TryParse<BehaviorSlot>(splitArgs[0]); + if (!slot) { + ChatPackets::SendSystemMessage(sysAddr, u"Error getting slot."); + return; + } else { + const auto skillId = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); + if (!skillId) { + ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill."); + return; + } else { + if (inventoryComponent->SetSkill(slot.value(), skillId.value())) ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot successfully"); + else ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot failed"); + } + } + } + } + + void SetFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); + if (destroyableComponent) { + const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + + if (!faction) { + ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction."); + return; + } else { + destroyableComponent->SetFaction(faction.value()); + ChatPackets::SendSystemMessage(sysAddr, u"Set faction and updated enemies list"); + } + } + } + + void AddFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); + if (destroyableComponent) { + const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + + if (!faction) { + ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction."); + return; + } else { + destroyableComponent->AddFaction(faction.value()); + ChatPackets::SendSystemMessage(sysAddr, u"Added faction and updated enemies list"); + } + } + } + + void GetFactions(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); + if (destroyableComponent) { + ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:"); + for (const auto entry : destroyableComponent->GetFactionIDs()) { + ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); + } + + ChatPackets::SendSystemMessage(sysAddr, u"Enemy factions:"); + for (const auto entry : destroyableComponent->GetEnemyFactionsIDs()) { + ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); + } + } + } + + void SetRewardCode(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto* character = entity->GetCharacter(); + if (!character) return; + auto* user = character->GetParentUser(); + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + auto* cdrewardCodes = CDClientManager::GetTable<CDRewardCodesTable>(); + + auto id = cdrewardCodes->GetCodeID(splitArgs[0]); + if (id != -1) Database::Get()->InsertRewardCode(user->GetAccountID(), id); + } + + void Inspect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + Entity* closest = nullptr; + + std::u16string ldf; + + bool isLDF = false; + + auto component = GeneralUtils::TryParse<eReplicaComponentType>(splitArgs[0]); + if (!component) { + component = eReplicaComponentType::INVALID; + + ldf = GeneralUtils::UTF8ToUTF16(splitArgs[0]); + + isLDF = true; + } + + auto reference = entity->GetPosition(); + + auto closestDistance = 0.0f; + + const auto candidates = Game::entityManager->GetEntitiesByComponent(component.value()); + + for (auto* candidate : candidates) { + if (candidate->GetLOT() == 1 || candidate->GetLOT() == 8092) { + continue; + } + + if (isLDF && !candidate->HasVar(ldf)) { + continue; + } + + if (!closest) { + closest = candidate; + + closestDistance = NiPoint3::Distance(candidate->GetPosition(), reference); + + continue; + } + + const auto distance = NiPoint3::Distance(candidate->GetPosition(), reference); + + if (distance < closestDistance) { + closest = candidate; + + closestDistance = distance; + } + } + + if (!closest) return; + + Game::entityManager->SerializeEntity(closest); + + auto* table = CDClientManager::GetTable<CDObjectsTable>(); + + const auto& info = table->GetByID(closest->GetLOT()); + + std::stringstream header; + + header << info.name << " [" << std::to_string(info.id) << "]" << " " << std::to_string(closestDistance) << " " << std::to_string(closest->IsSleeping()); + + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(header.str())); + + for (const auto& pair : closest->GetComponents()) { + auto id = pair.first; + + std::stringstream stream; + + stream << "Component [" << std::to_string(static_cast<uint32_t>(id)) << "]"; + + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(stream.str())); + } + + if (splitArgs.size() >= 2) { + if (splitArgs[1] == "-m" && splitArgs.size() >= 3) { + auto* const movingPlatformComponent = closest->GetComponent<MovingPlatformComponent>(); + + const auto mValue = GeneralUtils::TryParse<int32_t>(splitArgs[2]); + + if (!movingPlatformComponent || !mValue) return; + + movingPlatformComponent->SetSerialized(true); + + if (mValue == -1) { + movingPlatformComponent->StopPathing(); + } else { + movingPlatformComponent->GotoWaypoint(mValue.value()); + } + + Game::entityManager->SerializeEntity(closest); + } else if (splitArgs[1] == "-a" && splitArgs.size() >= 3) { + RenderComponent::PlayAnimation(closest, splitArgs.at(2)); + } else if (splitArgs[1] == "-s") { + for (auto* entry : closest->GetSettings()) { + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(entry->GetString())); + } + + ChatPackets::SendSystemMessage(sysAddr, u"------"); + ChatPackets::SendSystemMessage(sysAddr, u"Spawner ID: " + GeneralUtils::to_u16string(closest->GetSpawnerID())); + } else if (splitArgs[1] == "-p") { + const auto postion = closest->GetPosition(); + + ChatPackets::SendSystemMessage( + sysAddr, + GeneralUtils::ASCIIToUTF16("< " + std::to_string(postion.x) + ", " + std::to_string(postion.y) + ", " + std::to_string(postion.z) + " >") + ); + } else if (splitArgs[1] == "-f") { + auto* destuctable = closest->GetComponent<DestroyableComponent>(); + + if (destuctable == nullptr) { + ChatPackets::SendSystemMessage(sysAddr, u"No destroyable component on this entity!"); + return; + } + + ChatPackets::SendSystemMessage(sysAddr, u"Smashable: " + (GeneralUtils::to_u16string(destuctable->GetIsSmashable()))); + + ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:"); + for (const auto entry : destuctable->GetFactionIDs()) { + ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); + } + + ChatPackets::SendSystemMessage(sysAddr, u"Enemy factions:"); + for (const auto entry : destuctable->GetEnemyFactionsIDs()) { + ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); + } + + if (splitArgs.size() >= 3) { + const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[2]); + if (!faction) return; + + destuctable->SetFaction(-1); + destuctable->AddFaction(faction.value(), true); + } + } else if (splitArgs[1] == "-cf") { + auto* destuctable = entity->GetComponent<DestroyableComponent>(); + if (!destuctable) { + ChatPackets::SendSystemMessage(sysAddr, u"No destroyable component on this entity!"); + return; + } + if (destuctable->IsEnemy(closest)) ChatPackets::SendSystemMessage(sysAddr, u"They are our enemy"); + else ChatPackets::SendSystemMessage(sysAddr, u"They are NOT our enemy"); + } else if (splitArgs[1] == "-t") { + auto* phantomPhysicsComponent = closest->GetComponent<PhantomPhysicsComponent>(); + + if (phantomPhysicsComponent != nullptr) { + ChatPackets::SendSystemMessage(sysAddr, u"Type: " + (GeneralUtils::to_u16string(static_cast<uint32_t>(phantomPhysicsComponent->GetEffectType())))); + const auto dir = phantomPhysicsComponent->GetDirection(); + ChatPackets::SendSystemMessage(sysAddr, u"Direction: <" + (GeneralUtils::to_u16string(dir.x)) + u", " + (GeneralUtils::to_u16string(dir.y)) + u", " + (GeneralUtils::to_u16string(dir.z)) + u">"); + ChatPackets::SendSystemMessage(sysAddr, u"Multiplier: " + (GeneralUtils::to_u16string(phantomPhysicsComponent->GetDirectionalMultiplier()))); + ChatPackets::SendSystemMessage(sysAddr, u"Active: " + (GeneralUtils::to_u16string(phantomPhysicsComponent->GetPhysicsEffectActive()))); + } + + auto* triggerComponent = closest->GetComponent<TriggerComponent>(); + if (triggerComponent) { + auto trigger = triggerComponent->GetTrigger(); + if (trigger) { + ChatPackets::SendSystemMessage(sysAddr, u"Trigger: " + (GeneralUtils::to_u16string(trigger->id))); + } + } + } + } + } +}; diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.h b/dGame/dUtilities/SlashCommands/DEVGMCommands.h new file mode 100644 index 00000000..5e78ed80 --- /dev/null +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.h @@ -0,0 +1,73 @@ +namespace DEVGMCommands { + void SetGMLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ToggleNameplate(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ToggleSkipCinematics(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Kill(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Metrics(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Announce(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetAnnTitle(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetAnnMsg(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ShutdownUniverse(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetMinifig(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void TestMap(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ReportProxPhys(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SpawnPhysicsVerts(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Teleport(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ActivateSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void AddMission(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Boost(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Unboost(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Buff(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void BuffMe(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void BuffMed(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ClearFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void CompleteMission(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void CreatePrivate(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void DebugUi(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Dismount(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ReloadConfig(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ForceSave(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Freecam(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void FreeMoney(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GetNavmeshHeight(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GiveUScore(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GmAddItem(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Inspect(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ListSpawns(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void LocRow(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Lookup(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void PlayAnimation(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void PlayEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void PlayLvlFx(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void PlayRebuildFx(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Pos(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void RefillStats(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Reforge(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ResetMission(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Rot(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void RunMacro(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetControlScheme(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetCurrency(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetInventorySize(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetUiState(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Spawn(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SpawnGroup(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SpeedBoost(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void StartCelebration(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void StopEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Toggle(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void TpAll(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void TriggerSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void UnlockEmote(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetSkillSlot(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void AddFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GetFactions(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetRewardCode(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Crash(Entity* entity, const SystemAddress& sysAddr, const std::string args); + 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); +} diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp new file mode 100644 index 00000000..ea33aa03 --- /dev/null +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp @@ -0,0 +1,325 @@ +#include "GMGreaterThanZeroCommands.h" + +// Classes +#include "Character.h" +#include "ChatPackets.h" +#include "dServer.h" +#include "PlayerManager.h" +#include "User.h" + +// Database +#include "Database.h" + +// Components +#include "DestroyableComponent.h" +#include "PropertyManagementComponent.h" + +// Enums +#include "eChatMessageType.h" +#include "eServerDisconnectIdentifiers.h" +#include "eObjectBits.h" + +namespace GMGreaterThanZeroCommands { + + void Kick(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() == 1) { + auto* player = PlayerManager::GetPlayer(splitArgs[0]); + + std::u16string username = GeneralUtils::UTF8ToUTF16(splitArgs[0]); + if (player == nullptr) { + ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + username); + return; + } + + Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::KICK); + + ChatPackets::SendSystemMessage(sysAddr, u"Kicked: " + username); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /kick <username>"); + } + } + + void Ban(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + if (splitArgs.size() == 1) { + auto* player = PlayerManager::GetPlayer(splitArgs[0]); + + uint32_t accountId = 0; + + if (player == nullptr) { + auto characterInfo = Database::Get()->GetCharacterInfo(splitArgs[0]); + + if (characterInfo) { + accountId = characterInfo->accountId; + } + + if (accountId == 0) { + ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(splitArgs[0])); + + return; + } + } else { + auto* character = player->GetCharacter(); + auto* user = character != nullptr ? character->GetParentUser() : nullptr; + if (user) accountId = user->GetAccountID(); + } + + if (accountId != 0) Database::Get()->UpdateAccountBan(accountId, true); + + if (player != nullptr) { + Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::FREE_TRIAL_EXPIRED); + } + + ChatPackets::SendSystemMessage(sysAddr, u"Banned: " + GeneralUtils::ASCIIToUTF16(splitArgs[0])); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /ban <username>"); + } + } + + void MailItem(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; + + const auto& playerName = splitArgs[0]; + + auto playerInfo = Database::Get()->GetCharacterInfo(playerName); + + uint32_t receiverID = 0; + if (!playerInfo) { + ChatPackets::SendSystemMessage(sysAddr, u"Failed to find that player"); + + return; + } + + receiverID = playerInfo->id; + + const auto lot = GeneralUtils::TryParse<LOT>(splitArgs.at(1)); + + if (!lot) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid item lot."); + return; + } + + IMail::MailInfo mailInsert; + mailInsert.senderId = entity->GetObjectID(); + mailInsert.senderUsername = "Darkflame Universe"; + mailInsert.receiverId = receiverID; + mailInsert.recipient = playerName; + mailInsert.subject = "Lost item"; + mailInsert.body = "This is a replacement item for one you lost."; + mailInsert.itemID = LWOOBJID_EMPTY; + mailInsert.itemLOT = lot.value(); + mailInsert.itemSubkey = LWOOBJID_EMPTY; + mailInsert.itemCount = 1; + Database::Get()->InsertNewMail(mailInsert); + + ChatPackets::SendSystemMessage(sysAddr, u"Mail sent"); + } + + void ApproveProperty(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (PropertyManagementComponent::Instance() != nullptr) { + PropertyManagementComponent::Instance()->UpdateApprovedStatus(true); + } + } + + void Mute(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + if (splitArgs.size() >= 1) { + auto* player = PlayerManager::GetPlayer(splitArgs[0]); + + uint32_t accountId = 0; + LWOOBJID characterId = 0; + + if (player == nullptr) { + auto characterInfo = Database::Get()->GetCharacterInfo(splitArgs[0]); + + if (characterInfo) { + accountId = characterInfo->accountId; + characterId = characterInfo->id; + + GeneralUtils::SetBit(characterId, eObjectBits::CHARACTER); + GeneralUtils::SetBit(characterId, eObjectBits::PERSISTENT); + } + + if (accountId == 0) { + ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(splitArgs[0])); + + return; + } + } else { + auto* character = player->GetCharacter(); + auto* user = character != nullptr ? character->GetParentUser() : nullptr; + if (user) accountId = user->GetAccountID(); + characterId = player->GetObjectID(); + } + + time_t expire = 1; // Default to indefinate mute + + if (splitArgs.size() >= 2) { + const auto days = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); + if (!days) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid days."); + + return; + } + + std::optional<uint32_t> hours; + if (splitArgs.size() >= 3) { + hours = GeneralUtils::TryParse<uint32_t>(splitArgs[2]); + if (!hours) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid hours."); + + return; + } + } + + expire = time(NULL); + expire += 24 * 60 * 60 * days.value(); + expire += 60 * 60 * hours.value_or(0); + } + + if (accountId != 0) Database::Get()->UpdateAccountUnmuteTime(accountId, expire); + + char buffer[32] = "brought up for review.\0"; + + if (expire != 1) { + std::tm* ptm = std::localtime(&expire); + // Format: Mo, 15.06.2009 20:20:00 + std::strftime(buffer, 32, "%a, %d.%m.%Y %H:%M:%S", ptm); + } + + const auto timeStr = GeneralUtils::ASCIIToUTF16(std::string(buffer)); + + ChatPackets::SendSystemMessage(sysAddr, u"Muted: " + GeneralUtils::UTF8ToUTF16(splitArgs[0]) + u" until " + timeStr); + + //Notify chat about it + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GM_MUTE); + + bitStream.Write(characterId); + bitStream.Write(expire); + + Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /mute <username> <days (optional)> <hours (optional)>"); + } + } + + void Fly(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + auto* character = entity->GetCharacter(); + + if (character) { + bool isFlying = character->GetIsFlying(); + + if (isFlying) { + GameMessages::SendSetJetPackMode(entity, false); + + character->SetIsFlying(false); + } else { + float speedScale = 1.0f; + + if (splitArgs.size() >= 1) { + const auto tempScaleStore = GeneralUtils::TryParse<float>(splitArgs.at(0)); + + if (tempScaleStore) { + speedScale = tempScaleStore.value(); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Failed to parse speed scale argument."); + } + } + + float airSpeed = 20 * speedScale; + float maxAirSpeed = 30 * speedScale; + float verticalVelocity = 1.5 * speedScale; + + GameMessages::SendSetJetPackMode(entity, true, true, false, 167, airSpeed, maxAirSpeed, verticalVelocity); + + character->SetIsFlying(true); + } + } + } + + void AttackImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); + + const auto state = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + + if (!state) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid state."); + return; + } + + if (destroyableComponent) destroyableComponent->SetIsImmune(state.value()); + } + + void GmImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); + + const auto state = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + + if (!state) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid state."); + return; + } + + if (destroyableComponent) destroyableComponent->SetIsGMImmune(state.value()); + } + + void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + GameMessages::SendToggleGMInvis(entity->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS); + } + + void SetName(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(args), UNASSIGNED_SYSTEM_ADDRESS); + } + + void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + 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); + } +} diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h new file mode 100644 index 00000000..e8d60383 --- /dev/null +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h @@ -0,0 +1,15 @@ +namespace GMGreaterThanZeroCommands { + void Kick(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void MailItem(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Ban(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ApproveProperty(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Mute(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Fly(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void AttackImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GmImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args); + 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); + void ShowAll(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void FindPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args); +} diff --git a/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp new file mode 100644 index 00000000..f183d5ea --- /dev/null +++ b/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp @@ -0,0 +1,228 @@ +#include "GMZeroCommands.h" + +// Classes +#include "Amf3.h" +#include "BinaryPathFinder.h" +#include "ChatPackets.h" +#include "dServer.h" +#include "dZoneManager.h" +#include "Mail.h" +#include "PlayerManager.h" +#include "SlashCommandHandler.h" +#include "VanityUtilities.h" +#include "WorldPackets.h" +#include "ZoneInstanceManager.h" + +// Components +#include "BuffComponent.h" +#include "CharacterComponent.h" +#include "DestroyableComponent.h" +#include "ScriptedActivityComponent.h" +#include "SkillComponent.h" + +// Emuns +#include "eGameMasterLevel.h" + +namespace GMZeroCommands { + void Pvp(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto* character = entity->GetComponent<CharacterComponent>(); + + if (character == nullptr) { + LOG("Failed to find character component!"); + return; + } + + character->SetPvpEnabled(!character->GetPvpEnabled()); + Game::entityManager->SerializeEntity(entity); + + std::stringstream message; + message << character->GetName() << " changed their PVP flag to " << std::to_string(character->GetPvpEnabled()) << "!"; + + ChatPackets::SendSystemMessage(UNASSIGNED_SYSTEM_ADDRESS, GeneralUtils::UTF8ToUTF16(message.str()), true); + } + + void Who(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + ChatPackets::SendSystemMessage( + sysAddr, + u"Players in this instance: (" + GeneralUtils::to_u16string(PlayerManager::GetAllPlayers().size()) + u")" + ); + + for (auto* player : PlayerManager::GetAllPlayers()) { + const auto& name = player->GetCharacter()->GetName(); + + ChatPackets::SendSystemMessage( + sysAddr, + GeneralUtils::UTF8ToUTF16(player == entity ? name + " (you)" : name) + ); + } + } + + void Ping(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (!args.empty() && args.starts_with("-l")) { + std::stringstream message; + message << "Your latest ping: " << std::to_string(Game::server->GetLatestPing(sysAddr)) << "ms"; + + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(message.str())); + } else { + std::stringstream message; + message << "Your average ping: " << std::to_string(Game::server->GetPing(sysAddr)) << "ms"; + + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(message.str())); + } + } + + void FixStats(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + // Reset skill component and buff component + auto* skillComponent = entity->GetComponent<SkillComponent>(); + auto* buffComponent = entity->GetComponent<BuffComponent>(); + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); + + // If any of the components are nullptr, return + if (skillComponent == nullptr || buffComponent == nullptr || destroyableComponent == nullptr) { + return; + } + + // Reset skill component + skillComponent->Reset(); + + // Reset buff component + buffComponent->Reset(); + + // Fix the destroyable component + destroyableComponent->FixStats(); + } + + void Credits(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto& customText = VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/CREDITS.md").string()); + + { + AMFArrayValue args; + + args.Insert("state", "Story"); + + GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "pushGameState", args); + } + + entity->AddCallbackTimer(0.5f, [customText, entity]() { + AMFArrayValue args; + + args.Insert("visible", true); + args.Insert("text", customText); + + LOG("Sending %s", customText.c_str()); + + GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "ToggleStoryBox", args); + }); + } + + void Info(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto& customText = VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/INFO.md").string()); + + { + AMFArrayValue args; + + args.Insert("state", "Story"); + + GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "pushGameState", args); + } + + entity->AddCallbackTimer(0.5f, [customText, entity]() { + AMFArrayValue args; + + args.Insert("visible", true); + args.Insert("text", customText); + + LOG("Sending %s", customText.c_str()); + + GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "ToggleStoryBox", args); + }); + } + + void LeaveZone(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto currentZone = Game::zoneManager->GetZone()->GetZoneID().GetMapID(); + LWOMAPID newZone = 0; + + if (currentZone == 1001 || currentZone % 100 == 0) { + ChatPackets::SendSystemMessage(sysAddr, u"You are not in an instanced zone."); + return; + } else { + newZone = (currentZone / 100) * 100; + } + // If new zone would be inaccessible, then default to Avant Gardens. + if (!Game::zoneManager->CheckIfAccessibleZone(newZone)) newZone = 1100; + + ChatPackets::SendSystemMessage(sysAddr, u"Leaving zone..."); + + const auto objid = entity->GetObjectID(); + + ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, newZone, 0, false, [objid](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { + auto* entity = Game::entityManager->GetEntity(objid); + + if (entity == nullptr) { + return; + } + + const auto sysAddr = entity->GetSystemAddress(); + + LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", entity->GetCharacter()->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); + + if (entity->GetCharacter()) { + entity->GetCharacter()->SetZoneID(zoneID); + entity->GetCharacter()->SetZoneInstance(zoneInstance); + entity->GetCharacter()->SetZoneClone(zoneClone); + } + + entity->GetCharacter()->SaveXMLToDatabase(); + + WorldPackets::SendTransferToWorld(sysAddr, serverIP, serverPort, mythranShift); + }); + } + + void Join(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + ChatPackets::SendSystemMessage(sysAddr, u"Requesting private map..."); + const auto& password = splitArgs[0]; + + ZoneInstanceManager::Instance()->RequestPrivateZone(Game::server, false, password, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { + LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); + + if (entity->GetCharacter()) { + entity->GetCharacter()->SetZoneID(zoneID); + entity->GetCharacter()->SetZoneInstance(zoneInstance); + entity->GetCharacter()->SetZoneClone(zoneClone); + } + + entity->GetCharacter()->SaveXMLToDatabase(); + + WorldPackets::SendTransferToWorld(sysAddr, serverIP, serverPort, mythranShift); + }); + } + + void Die(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + entity->Smash(entity->GetObjectID()); + } + + void Resurrect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + ScriptedActivityComponent* scriptedActivityComponent = Game::zoneManager->GetZoneControlObject()->GetComponent<ScriptedActivityComponent>(); + + if (scriptedActivityComponent) { // check if user is in activity world and if so, they can't resurrect + ChatPackets::SendSystemMessage(sysAddr, u"You cannot resurrect in an activity world."); + return; + } + + GameMessages::SendResurrect(entity); + } + + void RequestMailCount(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + Mail::HandleNotificationRequest(entity->GetSystemAddress(), entity->GetObjectID()); + } + + void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto zoneId = Game::zoneManager->GetZone()->GetZoneID(); + + ChatPackets::SendSystemMessage(sysAddr, u"Map: " + (GeneralUtils::to_u16string(zoneId.GetMapID())) + u"\nClone: " + (GeneralUtils::to_u16string(zoneId.GetCloneID())) + u"\nInstance: " + (GeneralUtils::to_u16string(zoneId.GetInstanceID()))); + } +}; + diff --git a/dGame/dUtilities/SlashCommands/GMZeroCommands.h b/dGame/dUtilities/SlashCommands/GMZeroCommands.h new file mode 100644 index 00000000..29aa8f36 --- /dev/null +++ b/dGame/dUtilities/SlashCommands/GMZeroCommands.h @@ -0,0 +1,15 @@ +namespace GMZeroCommands { + void Help(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Credits(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Info(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Die(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Ping(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Pvp(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void RequestMailCount(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Who(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void FixStats(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Join(Entity* entity, const SystemAddress& sysAddr, const std::string args); + 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); +} 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 <string> #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 <iostream> +void HTTPMonitorInfo::Serialize(RakNet::BitStream &bitStream) const { + bitStream.Write(port); + bitStream.Write<uint8_t>(openWeb); + bitStream.Write<uint8_t>(supportsSum); + bitStream.Write<uint8_t>(supportsDetail); + bitStream.Write<uint8_t>(supportsWho); + bitStream.Write<uint8_t>(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<uint32_t>(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<std::pair<uint8_t, uint8_t>> 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/dPhysics/dpEntity.cpp b/dPhysics/dpEntity.cpp index cbe3f5e8..6fc40452 100644 --- a/dPhysics/dpEntity.cpp +++ b/dPhysics/dpEntity.cpp @@ -3,8 +3,6 @@ #include "dpShapeBox.h" #include "dpGrid.h" -#include <iostream> - dpEntity::dpEntity(const LWOOBJID& objectID, dpShapeType shapeType, bool isStatic) { m_ObjectID = objectID; m_IsStatic = isStatic; @@ -76,16 +74,17 @@ void dpEntity::CheckCollision(dpEntity* other) { return; } - bool wasFound = m_CurrentlyCollidingObjects.contains(other->GetObjectID()); - - bool isColliding = m_CollisionShape->IsColliding(other->GetShape()); + const auto objId = other->GetObjectID(); + const auto objItr = m_CurrentlyCollidingObjects.find(objId); + const bool wasFound = objItr != m_CurrentlyCollidingObjects.cend(); + const bool isColliding = m_CollisionShape->IsColliding(other->GetShape()); if (isColliding && !wasFound) { - m_CurrentlyCollidingObjects.emplace(other->GetObjectID(), other); - m_NewObjects.push_back(other); + m_CurrentlyCollidingObjects.emplace(objId); + m_NewObjects.push_back(objId); } else if (!isColliding && wasFound) { - m_CurrentlyCollidingObjects.erase(other->GetObjectID()); - m_RemovedObjects.push_back(other); + m_CurrentlyCollidingObjects.erase(objItr); + m_RemovedObjects.push_back(objId); } } diff --git a/dPhysics/dpEntity.h b/dPhysics/dpEntity.h index ea7a49b2..cc47d718 100644 --- a/dPhysics/dpEntity.h +++ b/dPhysics/dpEntity.h @@ -2,7 +2,8 @@ #include "NiPoint3.h" #include "NiQuaternion.h" #include <vector> -#include <map> +#include <unordered_set> +#include <span> #include "dCommonVars.h" #include "dpCommon.h" @@ -49,9 +50,9 @@ public: bool GetSleeping() const { return m_Sleeping; } void SetSleeping(bool value) { m_Sleeping = value; } - const std::vector<dpEntity*>& GetNewObjects() const { return m_NewObjects; } - const std::vector<dpEntity*>& GetRemovedObjects() const { return m_RemovedObjects; } - const std::map<LWOOBJID, dpEntity*>& GetCurrentlyCollidingObjects() const { return m_CurrentlyCollidingObjects; } + std::span<const LWOOBJID> GetNewObjects() const { return m_NewObjects; } + std::span<const LWOOBJID> GetRemovedObjects() const { return m_RemovedObjects; } + const std::unordered_set<LWOOBJID>& GetCurrentlyCollidingObjects() const { return m_CurrentlyCollidingObjects; } void PreUpdate() { m_NewObjects.clear(); m_RemovedObjects.clear(); } @@ -80,7 +81,7 @@ private: bool m_IsGargantuan = false; - std::vector<dpEntity*> m_NewObjects; - std::vector<dpEntity*> m_RemovedObjects; - std::map<LWOOBJID, dpEntity*> m_CurrentlyCollidingObjects; + std::vector<LWOOBJID> m_NewObjects; + std::vector<LWOOBJID> m_RemovedObjects; + std::unordered_set<LWOOBJID> m_CurrentlyCollidingObjects; }; diff --git a/dScripts/02_server/Map/AM/WanderingVendor.cpp b/dScripts/02_server/Map/AM/WanderingVendor.cpp index 742741d3..d6bb3247 100644 --- a/dScripts/02_server/Map/AM/WanderingVendor.cpp +++ b/dScripts/02_server/Map/AM/WanderingVendor.cpp @@ -21,7 +21,7 @@ void WanderingVendor::OnProximityUpdate(Entity* self, Entity* entering, std::str const auto proxObjs = proximityMonitorComponent->GetProximityObjects("playermonitor"); bool foundPlayer = false; - for (const auto id : proxObjs | std::views::keys) { + for (const auto id : proxObjs) { auto* entity = Game::entityManager->GetEntity(id); if (entity && entity->IsPlayer()) { foundPlayer = true; diff --git a/dScripts/02_server/Map/General/TokenConsoleServer.cpp b/dScripts/02_server/Map/General/TokenConsoleServer.cpp index e13011cb..0a1f679c 100644 --- a/dScripts/02_server/Map/General/TokenConsoleServer.cpp +++ b/dScripts/02_server/Map/General/TokenConsoleServer.cpp @@ -17,7 +17,9 @@ void TokenConsoleServer::OnUse(Entity* self, Entity* user) { inv->RemoveItem(6194, bricksToTake); //play sound - GameMessages::SendPlayNDAudioEmitter(self, user->GetSystemAddress(), "947d0d52-c7f8-4516-8dee-e1593a7fd1d1"); + if (self->HasVar(u"sound1")) { + GameMessages::SendPlayNDAudioEmitter(self, user->GetSystemAddress(), self->GetVarAsString(u"sound1")); + } //figure out which faction the player belongs to: auto character = user->GetCharacter(); diff --git a/dScripts/02_server/Map/NS/NsTokenConsoleServer.cpp b/dScripts/02_server/Map/NS/NsTokenConsoleServer.cpp index 74542f9f..7d825828 100644 --- a/dScripts/02_server/Map/NS/NsTokenConsoleServer.cpp +++ b/dScripts/02_server/Map/NS/NsTokenConsoleServer.cpp @@ -39,7 +39,7 @@ void NsTokenConsoleServer::OnUse(Entity* self, Entity* user) { const auto useSound = self->GetVar<std::string>(u"sound1"); if (!useSound.empty()) { - GameMessages::SendPlayNDAudioEmitter(self, UNASSIGNED_SYSTEM_ADDRESS, useSound); + GameMessages::SendPlayNDAudioEmitter(self, user->GetSystemAddress(), useSound); } // Player must be in faction to interact with this entity. diff --git a/dScripts/ai/AG/AgBusDoor.cpp b/dScripts/ai/AG/AgBusDoor.cpp index fd6c272e..a4106aaf 100644 --- a/dScripts/ai/AG/AgBusDoor.cpp +++ b/dScripts/ai/AG/AgBusDoor.cpp @@ -23,13 +23,13 @@ void AgBusDoor::OnProximityUpdate(Entity* self, Entity* entering, std::string na m_Counter = 0; m_OuterCounter = 0; - for (const auto& pair : proximityMonitorComponent->GetProximityObjects("busDoor")) { - auto* entity = Game::entityManager->GetEntity(pair.first); + for (const auto id : proximityMonitorComponent->GetProximityObjects("busDoor")) { + const auto* const entity = Game::entityManager->GetEntity(id); if (entity != nullptr && entity->IsPlayer()) m_Counter++; } - for (const auto& pair : proximityMonitorComponent->GetProximityObjects("busDoorOuter")) { - auto* entity = Game::entityManager->GetEntity(pair.first); + for (const auto id : proximityMonitorComponent->GetProximityObjects("busDoorOuter")) { + const auto* const entity = Game::entityManager->GetEntity(id); if (entity != nullptr && entity->IsPlayer()) m_OuterCounter++; } diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 5f7a8b3e..b6954ca2 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -66,7 +66,7 @@ #include "eObjectBits.h" #include "eConnectionType.h" #include "eServerMessageType.h" -#include "eChatInternalMessageType.h" +#include "eChatMessageType.h" #include "eWorldMessageType.h" #include "eMasterMessageType.h" #include "eGameMessageType.h" @@ -79,6 +79,7 @@ #include "PositionUpdate.h" #include "PlayerManager.h" #include "eLoginResponse.h" +#include "SlashCommandHandler.h" namespace Game { Logger* logger = nullptr; @@ -313,6 +314,9 @@ int main(int argc, char** argv) { uint32_t sqlPingTime = 10 * 60 * currentFramerate; // 10 minutes in frames uint32_t emptyShutdownTime = (cloneID == 0 ? 30 : 5) * 60 * currentFramerate; // 30 minutes for main worlds, 5 for all others. + // Register slash commands if not in zone 0 + if (zoneID != 0) SlashCommandHandler::Startup(); + Game::logger->Flush(); // once immediately before the main loop while (true) { Metrics::StartMeasurement(MetricVariable::Frame); @@ -541,118 +545,116 @@ void HandlePacketChat(Packet* packet) { } if (packet->data[0] == ID_USER_PACKET_ENUM) { - if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::CHAT_INTERNAL) { - switch (static_cast<eChatInternalMessageType>(packet->data[3])) { - case eChatInternalMessageType::ROUTE_TO_PLAYER: { - CINSTREAM_SKIP_HEADER; - LWOOBJID playerID; - inStream.Read(playerID); + if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::CHAT) { + switch (static_cast<eChatMessageType>(packet->data[3])) { + case eChatMessageType::WORLD_ROUTE_PACKET: { + CINSTREAM_SKIP_HEADER; + LWOOBJID playerID; + inStream.Read(playerID); - auto player = Game::entityManager->GetEntity(playerID); - if (!player) return; + auto player = Game::entityManager->GetEntity(playerID); + if (!player) return; - auto sysAddr = player->GetSystemAddress(); + auto sysAddr = player->GetSystemAddress(); - //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 + //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 + } + + SEND_PACKET; //send routed packet to player + break; } - SEND_PACKET; //send routed packet to player + case eChatMessageType::GM_ANNOUNCE: { + CINSTREAM_SKIP_HEADER; - break; - } + std::string title; + std::string msg; - case eChatInternalMessageType::ANNOUNCEMENT: { - CINSTREAM_SKIP_HEADER; + uint32_t len; + inStream.Read<uint32_t>(len); + for (uint32_t i = 0; len > i; i++) { + char character; + inStream.Read<char>(character); + title += character; + } - std::string title; - std::string msg; + len = 0; + inStream.Read<uint32_t>(len); + for (uint32_t i = 0; len > i; i++) { + char character; + inStream.Read<char>(character); + msg += character; + } - uint32_t len; - inStream.Read<uint32_t>(len); - for (uint32_t i = 0; len > i; i++) { - char character; - inStream.Read<char>(character); - title += character; - } + //Send to our clients: + AMFArrayValue args; - len = 0; - inStream.Read<uint32_t>(len); - for (uint32_t i = 0; len > i; i++) { - char character; - inStream.Read<char>(character); - msg += character; - } + args.Insert("title", title); + args.Insert("message", msg); - //Send to our clients: - AMFArrayValue args; - - args.Insert("title", title); - args.Insert("message", msg); - - GameMessages::SendUIMessageServerToAllClients("ToggleAnnounce", args); - - break; - } - - case eChatInternalMessageType::MUTE_UPDATE: { - CINSTREAM_SKIP_HEADER; - LWOOBJID playerId; - time_t expire = 0; - inStream.Read(playerId); - inStream.Read(expire); - - auto* entity = Game::entityManager->GetEntity(playerId); - auto* character = entity != nullptr ? entity->GetCharacter() : nullptr; - auto* user = character != nullptr ? character->GetParentUser() : nullptr; - if (user) { - user->SetMuteExpire(expire); - - entity->GetCharacter()->SendMuteNotice(); - } - - break; - } - - case eChatInternalMessageType::TEAM_UPDATE: { - CINSTREAM_SKIP_HEADER; - - LWOOBJID teamID = 0; - char lootOption = 0; - char memberCount = 0; - std::vector<LWOOBJID> members; - - inStream.Read(teamID); - bool deleteTeam = inStream.ReadBit(); - - if (deleteTeam) { - TeamManager::Instance()->DeleteTeam(teamID); - - LOG("Deleting team (%llu)", teamID); + GameMessages::SendUIMessageServerToAllClients("ToggleAnnounce", args); break; } - inStream.Read(lootOption); - inStream.Read(memberCount); - LOG("Updating team (%llu), (%i), (%i)", teamID, lootOption, memberCount); - for (char i = 0; i < memberCount; i++) { - LWOOBJID member = LWOOBJID_EMPTY; - inStream.Read(member); - members.push_back(member); + case eChatMessageType::GM_MUTE: { + CINSTREAM_SKIP_HEADER; + LWOOBJID playerId; + time_t expire = 0; + inStream.Read(playerId); + inStream.Read(expire); - LOG("Updating team member (%llu)", member); + auto* entity = Game::entityManager->GetEntity(playerId); + auto* character = entity != nullptr ? entity->GetCharacter() : nullptr; + auto* user = character != nullptr ? character->GetParentUser() : nullptr; + if (user) { + user->SetMuteExpire(expire); + + entity->GetCharacter()->SendMuteNotice(); + } + + break; } - TeamManager::Instance()->UpdateTeam(teamID, lootOption, members); + case eChatMessageType::TEAM_GET_STATUS: { + CINSTREAM_SKIP_HEADER; - break; - } + LWOOBJID teamID = 0; + char lootOption = 0; + char memberCount = 0; + std::vector<LWOOBJID> members; - default: - LOG("Received an unknown chat internal: %i", int(packet->data[3])); + inStream.Read(teamID); + bool deleteTeam = inStream.ReadBit(); + + if (deleteTeam) { + TeamManager::Instance()->DeleteTeam(teamID); + + LOG("Deleting team (%llu)", teamID); + + break; + } + + inStream.Read(lootOption); + inStream.Read(memberCount); + LOG("Updating team (%llu), (%i), (%i)", teamID, lootOption, memberCount); + for (char i = 0; i < memberCount; i++) { + LWOOBJID member = LWOOBJID_EMPTY; + inStream.Read(member); + members.push_back(member); + + LOG("Updating team member (%llu)", member); + } + + TeamManager::Instance()->UpdateTeam(teamID, lootOption, members); + + break; + } + default: + LOG("Received an unknown chat: %i", int(packet->data[3])); } } } @@ -817,7 +819,7 @@ void HandlePacket(Packet* packet) { { CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::PLAYER_REMOVED_NOTIFICATION); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::UNEXPECTED_DISCONNECT); bitStream.Write(user->GetLoggedInChar()); Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); } @@ -986,7 +988,7 @@ void HandlePacket(Packet* packet) { // This means we swapped characters and we need to remove the previous player from the container. if (static_cast<uint32_t>(lastCharacter) != playerID) { CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::PLAYER_REMOVED_NOTIFICATION); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::UNEXPECTED_DISCONNECT); bitStream.Write(lastCharacter); Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); } @@ -1132,7 +1134,7 @@ void HandlePacket(Packet* packet) { const auto& playerName = character->GetName(); CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::PLAYER_ADDED_NOTIFICATION); + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::LOGIN_SESSION_NOTIFY); bitStream.Write(player->GetObjectID()); bitStream.Write<uint32_t>(playerName.size()); for (size_t i = 0; i < playerName.size(); i++) { diff --git a/resources/blacklist.dcf b/resources/blocklist.dcf similarity index 100% rename from resources/blacklist.dcf rename to resources/blocklist.dcf 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<const char*>(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/tests/dCommonTests/dEnumsTests/MagicEnumTests.cpp b/tests/dCommonTests/dEnumsTests/MagicEnumTests.cpp index 0ca2e2ea..c47eb489 100644 --- a/tests/dCommonTests/dEnumsTests/MagicEnumTests.cpp +++ b/tests/dCommonTests/dEnumsTests/MagicEnumTests.cpp @@ -121,7 +121,7 @@ TEST(MagicEnumTest, eGameMessageTypeTest) { namespace { template <typename T> void AssertEnumArraySorted(const T& eArray) { - for (int i = 0; i < eArray->size(); ++i) { + for (int i = 0; i < eArray->size() - 1; ++i) { const auto entryCurr = eArray->at(i).first; LOG_EARRAY(eArray, i, entryCurr); const auto entryNext = eArray->at(++i).first; diff --git a/thirdparty/SQLite/CppSQLite3.h b/thirdparty/SQLite/CppSQLite3.h index 7ae8a8b7..70c4b8e8 100644 --- a/thirdparty/SQLite/CppSQLite3.h +++ b/thirdparty/SQLite/CppSQLite3.h @@ -36,10 +36,11 @@ #include "sqlite3.h" #include <cstdio> #include <cstring> +#include <exception> #define CPPSQLITE_ERROR 1000 -class CppSQLite3Exception +class CppSQLite3Exception : public std::exception { public: @@ -54,6 +55,8 @@ public: const int errorCode() { return mnErrCode; } const char* errorMessage() { return mpszErrMess; } + + const char* what() const noexcept override { return mpszErrMess; } static const char* errorCodeAsString(int nErrCode);