diff --git a/README.md b/README.md index fc076dff..48fb3259 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ Darkflame Universe is a server emulator and does not distribute any LEGO® Unive * To connect to the server, either delete the file `boot.cfg` which is found in your LEGO Universe client, rename the file `boot.cfg` to something else or follow the steps [here](#allowing-a-user-to-connect-to-your-server) if you wish to keep the file. * When shutting down the server, it is highly recommended to click the `MasterServer.exe` window and hold `ctrl` while pressing `c` to stop the server. * We are working on a way to make it so when you close the game, the server stops automatically alongside when you open the game, the server starts automatically. +* If you are not setting a server up on mac, you can ignore this note +* Note: you'll need to allow through System Preferences `AuthServer`, `ChatServer`, `MasterServer`, `WorldServer` and `libmariadbcpp.dylib` to run. The initial pop-up will block it due to the binaries being unsigned, after allowing them to run the servers will run as normal. **If you are not planning on hosting a server for others, working in the codebase or wanting to use MariaDB for a database, you can stop reading here.** @@ -68,6 +70,12 @@ git clone --recursive https://github.com/DarkflameUniverse/DarkflameServer ## Install dependencies +### Required compiler versions +- g++11 or greater +- MSVC unchecked +- clang unchecked +- appleclang unchecked + ### Windows packages Ensure that you have either the [MSVC C++ compiler](https://visualstudio.microsoft.com/vs/features/cplusplus/) (recommended) or the [Clang compiler](https://github.com/llvm/llvm-project/releases/) installed. You'll also need to download and install [CMake](https://cmake.org/download/) (version **CMake version 3.25** or later!). @@ -202,6 +210,7 @@ If you would like to build the server faster, append `-j` where number i ### Notes Depending on your operating system, you may need to adjust some pre-processor defines in [CMakeVariables.txt](./CMakeVariables.txt) before building: * If you are on MacOS, ensure OPENSSL_ROOT_DIR is pointing to the openssl root directory. +* By default it should be set to the correct directory. * If you are using a Darkflame Universe client, ensure `client_net_version` in `build/sharedconfig.ini` is changed to 171023. ## Configuring your server @@ -224,28 +233,41 @@ Navigate to `build/sharedconfig.ini` and fill in the following fields: * `chatconfig.ini` contains a port option. * `masterconfig.ini` contains options related to permissions you want to run your servers with. * `sharedconfig.ini` contains several options that are shared across all servers -* `worldconfig.ini` contains several options to turn on QOL improvements should you want them. If you would like the most vanilla experience possible, you will need to turn some of these settings off. +* `worldconfig.ini` contains several options to turn on Quality of Life improvements should you want them. If you would like the most vanilla experience possible, you will need to turn some of these settings off. ## Verify your setup -Your build directory should now look like this: -* AuthServer -* ChatServer -* MasterServer -* WorldServer -* authconfig.ini -* chatconfig.ini -* masterconfig.ini +Your build directory should contain at a minimum all of the following files. +All listed files are required for a server to start. +`ini` files can be located at the environment variable `DLU_CONFIG_DIR` and do not need to be located in this directory. +(windows will have .exe at the end of the executables): * sharedconfig.ini +* AuthServer(.exe) +* authconfig.ini +* ChatServer(.exe) +* chatconfig.ini +* MasterServer(.exe) +* masterconfig.ini +* WorldServer(.exe) * worldconfig.ini -* ... +* blocklist.dcf +* migrations +* vanity +* navmeshes +* 1 of the following lists based on platform + * windows + * libmariadb.dll + * mariadbcpp.dll + * zlib.dll + * MacOS + * libmariadbcpp.dylib + * *nix + * libmariadbcpp.so ## Running the server -If everything has been configured correctly you should now be able to run the `MasterServer` binary which is located in the `build` directory. Darkflame Universe utilizes port numbers under 1024, so under Linux you either have to give the `AuthServer` binary network permissions or run it under sudo. -To give `AuthServer` network permissions and not require sudo, run the following command +If everything has been configured correctly you should now be able to run the `MasterServer` binary which is located in the `build` directory. Darkflame Universe utilizes port numbers under 1024, so under Linux you have to give the `AuthServer` binary network permissions by running the following command: ```bash sudo setcap 'cap_net_bind_service=+ep' AuthServer ``` -and then go to `build/masterconfig.ini` and change `use_sudo_auth` to 0. ### Linux Service If you are running this on a linux based system, it will use your terminal to run the program interactively, preventing you using it for other tasks and requiring it to be open to run the server. @@ -308,8 +330,14 @@ To connect to a server follow these steps: * Replace the contents after to `:` and the following `,` with what you configured as the server's public facing IP. For example `AUTHSERVERIP=0:localhost` for locally hosted servers * Next locate the line `UGCUSE3DSERVICES=7:` * Ensure the number after the 7 is a `0` +* Alternatively, remove the line with `UGCUSE3DSERVICES` altogether * Launch `legouniverse.exe`, through `wine` if on a Unix-like operating system * Note that if you are on WSL2, you will need to configure the public IP in the server and client to be the IP of the WSL2 instance and not localhost, which can be found by running `ifconfig` in the terminal. Windows defaults to WSL1, so this will not apply to most users. +As an example, here is what the boot.cfg is required to contain for a server with the ip 12.34.56.78 +```cfg +AUTHSERVERIP=0:12.34.56.78, +UGCUSE3DSERVICES=7:0 +``` ## Updating your server To update your server to the latest version navigate to your cloned directory diff --git a/dAuthServer/AuthServer.cpp b/dAuthServer/AuthServer.cpp index 741a6e59..362dd431 100644 --- a/dAuthServer/AuthServer.cpp +++ b/dAuthServer/AuthServer.cpp @@ -27,7 +27,6 @@ #include "Game.h" #include "Server.h" - namespace Game { Logger* logger = nullptr; dServer* server = nullptr; diff --git a/dAuthServer/CMakeLists.txt b/dAuthServer/CMakeLists.txt index 7dcbf041..37799b8e 100644 --- a/dAuthServer/CMakeLists.txt +++ b/dAuthServer/CMakeLists.txt @@ -1,7 +1,4 @@ add_executable(AuthServer "AuthServer.cpp") - target_link_libraries(AuthServer ${COMMON_LIBRARIES} dServer) - target_include_directories(AuthServer PRIVATE ${PROJECT_SOURCE_DIR}/dServer) - add_compile_definitions(AuthServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}\"") diff --git a/dChatServer/ChatPacketHandler.cpp b/dChatServer/ChatPacketHandler.cpp index 891119dc..40901440 100644 --- a/dChatServer/ChatPacketHandler.cpp +++ b/dChatServer/ChatPacketHandler.cpp @@ -29,33 +29,35 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) { auto& player = Game::playerContainer.GetPlayerDataMutable(playerID); if (!player) return; - auto friendsList = Database::Get()->GetFriendsList(playerID); - for (const auto& friendData : friendsList) { - FriendData fd; - fd.isFTP = false; // not a thing in DLU - fd.friendID = friendData.friendID; - GeneralUtils::SetBit(fd.friendID, eObjectBits::PERSISTENT); - GeneralUtils::SetBit(fd.friendID, eObjectBits::CHARACTER); + if (player.friends.empty()) { + auto friendsList = Database::Get()->GetFriendsList(playerID); + for (const auto& friendData : friendsList) { + FriendData fd; + fd.isFTP = false; // not a thing in DLU + fd.friendID = friendData.friendID; + GeneralUtils::SetBit(fd.friendID, eObjectBits::PERSISTENT); + GeneralUtils::SetBit(fd.friendID, eObjectBits::CHARACTER); - fd.isBestFriend = friendData.isBestFriend; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs - if (fd.isBestFriend) player.countOfBestFriends += 1; - fd.friendName = friendData.friendName; + fd.isBestFriend = friendData.isBestFriend; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs + if (fd.isBestFriend) player.countOfBestFriends += 1; + fd.friendName = friendData.friendName; - //Now check if they're online: - const auto& fr = Game::playerContainer.GetPlayerData(fd.friendID); + //Now check if they're online: + const auto& fr = Game::playerContainer.GetPlayerData(fd.friendID); - if (fr) { - fd.isOnline = true; - fd.zoneID = fr.zoneID; + if (fr) { + fd.isOnline = true; + fd.zoneID = fr.zoneID; - //Since this friend is online, we need to update them on the fact that we've just logged in: - SendFriendUpdate(fr, player, 1, fd.isBestFriend); - } else { - fd.isOnline = false; - fd.zoneID = LWOZONEID(); + //Since this friend is online, we need to update them on the fact that we've just logged in: + SendFriendUpdate(fr, player, 1, fd.isBestFriend); + } else { + fd.isOnline = false; + fd.zoneID = LWOZONEID(); + } + + player.friends.push_back(fd); } - - player.friends.push_back(fd); } //Now, we need to send the friendlist to the server they came from: @@ -140,7 +142,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) { // Prevent GM friend spam // If the player we are trying to be friends with is not a civilian and we are a civilian, abort the process - if (requestee.gmLevel > eGameMasterLevel::CIVILIAN && requestor.gmLevel == eGameMasterLevel::CIVILIAN ) { + if (requestee.gmLevel > eGameMasterLevel::CIVILIAN && requestor.gmLevel == eGameMasterLevel::CIVILIAN) { SendFriendResponse(requestor, requestee, eAddFriendResponseType::MYTHRAN); return; } @@ -400,8 +402,8 @@ void ChatPacketHandler::HandleShowAll(Packet* packet) { 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 (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)); diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index a420173c..7eef11d5 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -131,6 +131,8 @@ int main(int argc, char** argv) { if (web_server_enabled) { LOG("Web server enabled, will process http requests."); } + + auto lastTime = std::chrono::high_resolution_clock::now(); Game::logger->Flush(); // once immediately before main loop while (!Game::ShouldShutdown()) { @@ -142,7 +144,11 @@ int main(int argc, char** argv) { break; //Exit our loop, shut down. } else framesSinceMasterDisconnect = 0; - //In world we'd update our other systems here. + const auto currentTime = std::chrono::high_resolution_clock::now(); + const float deltaTime = std::chrono::duration(currentTime - lastTime).count(); + lastTime = currentTime; + + Game::playerContainer.Update(deltaTime); //Check for packets here: Game::server->ReceiveFromMaster(); //ReceiveFromMaster also handles the master packets if needed. @@ -182,7 +188,7 @@ int main(int argc, char** argv) { t += std::chrono::milliseconds(chatFrameDelta); //Chat can run at a lower "fps" std::this_thread::sleep_until(t); } - + Game::playerContainer.Shutdown(); //Delete our objects here: Database::Destroy("ChatServer"); delete Game::server; @@ -211,150 +217,150 @@ void HandlePacket(Packet* packet) { inStream.Read(chatMessageID); switch (chatMessageID) { - case MessageType::Chat::GM_MUTE: - Game::playerContainer.MuteUpdate(packet); - break; + case MessageType::Chat::GM_MUTE: + Game::playerContainer.MuteUpdate(packet); + break; - case MessageType::Chat::CREATE_TEAM: - Game::playerContainer.CreateTeamServer(packet); - break; + case MessageType::Chat::CREATE_TEAM: + Game::playerContainer.CreateTeamServer(packet); + break; - case MessageType::Chat::GET_FRIENDS_LIST: - ChatPacketHandler::HandleFriendlistRequest(packet); - break; + case MessageType::Chat::GET_FRIENDS_LIST: + ChatPacketHandler::HandleFriendlistRequest(packet); + break; - case MessageType::Chat::GET_IGNORE_LIST: - ChatIgnoreList::GetIgnoreList(packet); - break; + case MessageType::Chat::GET_IGNORE_LIST: + ChatIgnoreList::GetIgnoreList(packet); + break; - case MessageType::Chat::ADD_IGNORE: - ChatIgnoreList::AddIgnore(packet); - break; + case MessageType::Chat::ADD_IGNORE: + ChatIgnoreList::AddIgnore(packet); + break; - case MessageType::Chat::REMOVE_IGNORE: - ChatIgnoreList::RemoveIgnore(packet); - break; + case MessageType::Chat::REMOVE_IGNORE: + ChatIgnoreList::RemoveIgnore(packet); + break; - case MessageType::Chat::TEAM_GET_STATUS: - ChatPacketHandler::HandleTeamStatusRequest(packet); - break; + case MessageType::Chat::TEAM_GET_STATUS: + ChatPacketHandler::HandleTeamStatusRequest(packet); + break; - case MessageType::Chat::ADD_FRIEND_REQUEST: - //this involves someone sending the initial request, the response is below, response as in from the other player. - //We basically just check to see if this player is online or not and route the packet. - ChatPacketHandler::HandleFriendRequest(packet); - break; + case MessageType::Chat::ADD_FRIEND_REQUEST: + //this involves someone sending the initial request, the response is below, response as in from the other player. + //We basically just check to see if this player is online or not and route the packet. + ChatPacketHandler::HandleFriendRequest(packet); + break; - case MessageType::Chat::ADD_FRIEND_RESPONSE: - //This isn't the response a server sent, rather it is a player's response to a received request. - //Here, we'll actually have to add them to eachother's friend lists depending on the response code. - ChatPacketHandler::HandleFriendResponse(packet); - break; + case MessageType::Chat::ADD_FRIEND_RESPONSE: + //This isn't the response a server sent, rather it is a player's response to a received request. + //Here, we'll actually have to add them to eachother's friend lists depending on the response code. + ChatPacketHandler::HandleFriendResponse(packet); + break; - case MessageType::Chat::REMOVE_FRIEND: - ChatPacketHandler::HandleRemoveFriend(packet); - break; + case MessageType::Chat::REMOVE_FRIEND: + ChatPacketHandler::HandleRemoveFriend(packet); + break; - case MessageType::Chat::GENERAL_CHAT_MESSAGE: - ChatPacketHandler::HandleChatMessage(packet); - break; + case MessageType::Chat::GENERAL_CHAT_MESSAGE: + ChatPacketHandler::HandleChatMessage(packet); + break; - case MessageType::Chat::PRIVATE_CHAT_MESSAGE: - //This message is supposed to be echo'd to both the sender and the receiver - //BUT: they have to have different responseCodes, so we'll do some of the ol hacky wacky to fix that right up. - ChatPacketHandler::HandlePrivateChatMessage(packet); - break; + case MessageType::Chat::PRIVATE_CHAT_MESSAGE: + //This message is supposed to be echo'd to both the sender and the receiver + //BUT: they have to have different responseCodes, so we'll do some of the ol hacky wacky to fix that right up. + ChatPacketHandler::HandlePrivateChatMessage(packet); + break; - case MessageType::Chat::TEAM_INVITE: - ChatPacketHandler::HandleTeamInvite(packet); - break; + case MessageType::Chat::TEAM_INVITE: + ChatPacketHandler::HandleTeamInvite(packet); + break; - case MessageType::Chat::TEAM_INVITE_RESPONSE: - ChatPacketHandler::HandleTeamInviteResponse(packet); - break; + case MessageType::Chat::TEAM_INVITE_RESPONSE: + ChatPacketHandler::HandleTeamInviteResponse(packet); + break; - case MessageType::Chat::TEAM_LEAVE: - ChatPacketHandler::HandleTeamLeave(packet); - break; + case MessageType::Chat::TEAM_LEAVE: + ChatPacketHandler::HandleTeamLeave(packet); + break; - case MessageType::Chat::TEAM_SET_LEADER: - ChatPacketHandler::HandleTeamPromote(packet); - break; + case MessageType::Chat::TEAM_SET_LEADER: + ChatPacketHandler::HandleTeamPromote(packet); + break; - case MessageType::Chat::TEAM_KICK: - ChatPacketHandler::HandleTeamKick(packet); - break; + case MessageType::Chat::TEAM_KICK: + ChatPacketHandler::HandleTeamKick(packet); + break; - case MessageType::Chat::TEAM_SET_LOOT: - ChatPacketHandler::HandleTeamLootOption(packet); - break; - case MessageType::Chat::GMLEVEL_UPDATE: - ChatPacketHandler::HandleGMLevelUpdate(packet); - break; - case MessageType::Chat::LOGIN_SESSION_NOTIFY: - Game::playerContainer.InsertPlayer(packet); - break; - case MessageType::Chat::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 MessageType::Chat::UNEXPECTED_DISCONNECT: - Game::playerContainer.RemovePlayer(packet); - break; - case MessageType::Chat::WHO: - ChatPacketHandler::HandleWho(packet); - break; - case MessageType::Chat::SHOW_ALL: - ChatPacketHandler::HandleShowAll(packet); - break; - case MessageType::Chat::USER_CHANNEL_CHAT_MESSAGE: - case MessageType::Chat::WORLD_DISCONNECT_REQUEST: - case MessageType::Chat::WORLD_PROXIMITY_RESPONSE: - case MessageType::Chat::WORLD_PARCEL_RESPONSE: - case MessageType::Chat::TEAM_MISSED_INVITE_CHECK: - case MessageType::Chat::GUILD_CREATE: - case MessageType::Chat::GUILD_INVITE: - case MessageType::Chat::GUILD_INVITE_RESPONSE: - case MessageType::Chat::GUILD_LEAVE: - case MessageType::Chat::GUILD_KICK: - case MessageType::Chat::GUILD_GET_STATUS: - case MessageType::Chat::GUILD_GET_ALL: - case MessageType::Chat::BLUEPRINT_MODERATED: - case MessageType::Chat::BLUEPRINT_MODEL_READY: - case MessageType::Chat::PROPERTY_READY_FOR_APPROVAL: - case MessageType::Chat::PROPERTY_MODERATION_CHANGED: - case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED: - case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED_REPORT: - case MessageType::Chat::MAIL: - case MessageType::Chat::WORLD_INSTANCE_LOCATION_REQUEST: - case MessageType::Chat::REPUTATION_UPDATE: - case MessageType::Chat::SEND_CANNED_TEXT: - case MessageType::Chat::CHARACTER_NAME_CHANGE_REQUEST: - case MessageType::Chat::CSR_REQUEST: - case MessageType::Chat::CSR_REPLY: - case MessageType::Chat::GM_KICK: - case MessageType::Chat::WORLD_ROUTE_PACKET: - case MessageType::Chat::GET_ZONE_POPULATIONS: - case MessageType::Chat::REQUEST_MINIMUM_CHAT_MODE: - case MessageType::Chat::MATCH_REQUEST: - case MessageType::Chat::UGCMANIFEST_REPORT_MISSING_FILE: - case MessageType::Chat::UGCMANIFEST_REPORT_DONE_FILE: - case MessageType::Chat::UGCMANIFEST_REPORT_DONE_BLUEPRINT: - case MessageType::Chat::UGCC_REQUEST: - case MessageType::Chat::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE: - case MessageType::Chat::ACHIEVEMENT_NOTIFY: - case MessageType::Chat::GM_CLOSE_PRIVATE_CHAT_WINDOW: - case MessageType::Chat::PLAYER_READY: - case MessageType::Chat::GET_DONATION_TOTAL: - case MessageType::Chat::UPDATE_DONATION: - case MessageType::Chat::PRG_CSR_COMMAND: - case MessageType::Chat::HEARTBEAT_REQUEST_FROM_WORLD: - case MessageType::Chat::UPDATE_FREE_TRIAL_STATUS: - LOG("Unhandled CHAT Message id: %s (%i)", StringifiedEnum::ToString(chatMessageID).data(), chatMessageID); - break; - default: - LOG("Unknown CHAT Message id: %i", chatMessageID); + case MessageType::Chat::TEAM_SET_LOOT: + ChatPacketHandler::HandleTeamLootOption(packet); + break; + case MessageType::Chat::GMLEVEL_UPDATE: + ChatPacketHandler::HandleGMLevelUpdate(packet); + break; + case MessageType::Chat::LOGIN_SESSION_NOTIFY: + Game::playerContainer.InsertPlayer(packet); + break; + case MessageType::Chat::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 MessageType::Chat::UNEXPECTED_DISCONNECT: + Game::playerContainer.ScheduleRemovePlayer(packet); + break; + case MessageType::Chat::WHO: + ChatPacketHandler::HandleWho(packet); + break; + case MessageType::Chat::SHOW_ALL: + ChatPacketHandler::HandleShowAll(packet); + break; + case MessageType::Chat::USER_CHANNEL_CHAT_MESSAGE: + case MessageType::Chat::WORLD_DISCONNECT_REQUEST: + case MessageType::Chat::WORLD_PROXIMITY_RESPONSE: + case MessageType::Chat::WORLD_PARCEL_RESPONSE: + case MessageType::Chat::TEAM_MISSED_INVITE_CHECK: + case MessageType::Chat::GUILD_CREATE: + case MessageType::Chat::GUILD_INVITE: + case MessageType::Chat::GUILD_INVITE_RESPONSE: + case MessageType::Chat::GUILD_LEAVE: + case MessageType::Chat::GUILD_KICK: + case MessageType::Chat::GUILD_GET_STATUS: + case MessageType::Chat::GUILD_GET_ALL: + case MessageType::Chat::BLUEPRINT_MODERATED: + case MessageType::Chat::BLUEPRINT_MODEL_READY: + case MessageType::Chat::PROPERTY_READY_FOR_APPROVAL: + case MessageType::Chat::PROPERTY_MODERATION_CHANGED: + case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED: + case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED_REPORT: + case MessageType::Chat::MAIL: + case MessageType::Chat::WORLD_INSTANCE_LOCATION_REQUEST: + case MessageType::Chat::REPUTATION_UPDATE: + case MessageType::Chat::SEND_CANNED_TEXT: + case MessageType::Chat::CHARACTER_NAME_CHANGE_REQUEST: + case MessageType::Chat::CSR_REQUEST: + case MessageType::Chat::CSR_REPLY: + case MessageType::Chat::GM_KICK: + case MessageType::Chat::WORLD_ROUTE_PACKET: + case MessageType::Chat::GET_ZONE_POPULATIONS: + case MessageType::Chat::REQUEST_MINIMUM_CHAT_MODE: + case MessageType::Chat::MATCH_REQUEST: + case MessageType::Chat::UGCMANIFEST_REPORT_MISSING_FILE: + case MessageType::Chat::UGCMANIFEST_REPORT_DONE_FILE: + case MessageType::Chat::UGCMANIFEST_REPORT_DONE_BLUEPRINT: + case MessageType::Chat::UGCC_REQUEST: + case MessageType::Chat::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE: + case MessageType::Chat::ACHIEVEMENT_NOTIFY: + case MessageType::Chat::GM_CLOSE_PRIVATE_CHAT_WINDOW: + case MessageType::Chat::PLAYER_READY: + case MessageType::Chat::GET_DONATION_TOTAL: + case MessageType::Chat::UPDATE_DONATION: + case MessageType::Chat::PRG_CSR_COMMAND: + case MessageType::Chat::HEARTBEAT_REQUEST_FROM_WORLD: + case MessageType::Chat::UPDATE_FREE_TRIAL_STATUS: + LOG("Unhandled CHAT Message id: %s (%i)", StringifiedEnum::ToString(chatMessageID).data(), chatMessageID); + break; + default: + LOG("Unknown CHAT Message id: %i", chatMessageID); } } diff --git a/dChatServer/PlayerContainer.cpp b/dChatServer/PlayerContainer.cpp index c34e867a..257bd40d 100644 --- a/dChatServer/PlayerContainer.cpp +++ b/dChatServer/PlayerContainer.cpp @@ -74,13 +74,32 @@ void PlayerContainer::InsertPlayer(Packet* packet) { LOG("Added user: %s (%llu), zone: %i", data.playerName.c_str(), data.playerID, data.zoneID.GetMapID()); Database::Get()->UpdateActivityLog(data.playerID, eActivityType::PlayerLoggedIn, data.zoneID.GetMapID()); + m_PlayersToRemove.erase(playerId); } -void PlayerContainer::RemovePlayer(Packet* packet) { +void PlayerContainer::ScheduleRemovePlayer(Packet* packet) { CINSTREAM_SKIP_HEADER; - LWOOBJID playerID; + LWOOBJID playerID{ LWOOBJID_EMPTY }; inStream.Read(playerID); + constexpr float updatePlayerOnLogoutTime = 20.0f; + if (playerID != LWOOBJID_EMPTY) m_PlayersToRemove.insert_or_assign(playerID, updatePlayerOnLogoutTime); +} +void PlayerContainer::Update(const float deltaTime) { + for (auto it = m_PlayersToRemove.begin(); it != m_PlayersToRemove.end();) { + auto& [id, time] = *it; + time -= deltaTime; + + if (time <= 0.0f) { + RemovePlayer(id); + it = m_PlayersToRemove.erase(it); + } else { + ++it; + } + } +} + +void PlayerContainer::RemovePlayer(const LWOOBJID playerID) { //Before they get kicked, we need to also send a message to their friends saying that they disconnected. const auto& player = GetPlayerData(playerID); @@ -434,3 +453,13 @@ const PlayerData& PlayerContainer::GetPlayerData(const LWOOBJID& playerID) { const PlayerData& PlayerContainer::GetPlayerData(const std::string& playerName) { return GetPlayerDataMutable(playerName); } + +void PlayerContainer::Shutdown() { + m_Players.erase(LWOOBJID_EMPTY); + while (!m_Players.empty()) { + const auto& [id, playerData] = *m_Players.begin(); + Database::Get()->UpdateActivityLog(id, eActivityType::PlayerLoggedOut, playerData.zoneID.GetMapID()); + m_Players.erase(m_Players.begin()); + } + for (auto* team : mTeams) if (team) delete team; +} diff --git a/dChatServer/PlayerContainer.h b/dChatServer/PlayerContainer.h index 2bd675a2..aa749e7e 100644 --- a/dChatServer/PlayerContainer.h +++ b/dChatServer/PlayerContainer.h @@ -65,10 +65,12 @@ class PlayerContainer { public: void Initialize(); void InsertPlayer(Packet* packet); - void RemovePlayer(Packet* packet); + void ScheduleRemovePlayer(Packet* packet); + void RemovePlayer(const LWOOBJID playerID); void MuteUpdate(Packet* packet); void CreateTeamServer(Packet* packet); void BroadcastMuteUpdate(LWOOBJID player, time_t time); + void Shutdown(); const PlayerData& GetPlayerData(const LWOOBJID& playerID); const PlayerData& GetPlayerData(const std::string& playerName); @@ -93,11 +95,15 @@ public: uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; } const std::vector& GetAllTeams() { return mTeams;}; + void Update(const float deltaTime); + bool PlayerBeingRemoved(const LWOOBJID playerID) { return m_PlayersToRemove.contains(playerID); } + private: LWOOBJID m_TeamIDCounter = 0; std::map m_Players; std::vector mTeams; std::unordered_map m_Names; + std::map m_PlayersToRemove; uint32_t m_MaxNumberOfBestFriends = 5; uint32_t m_MaxNumberOfFriends = 50; uint32_t m_PlayerCount = 0; diff --git a/dDatabase/GameDatabase/MySQL/Tables/Property.cpp b/dDatabase/GameDatabase/MySQL/Tables/Property.cpp index 8aaf93c4..bf372874 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Property.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Property.cpp @@ -56,6 +56,7 @@ std::optional MySQLDatabase::GetProperties(co params.playerId ); if (count->next()) { + if (!result) result = IProperty::PropertyEntranceResult(); result->totalEntriesMatchingQuery = count->getUInt("count"); } } else { @@ -109,11 +110,13 @@ std::optional MySQLDatabase::GetProperties(co params.playerSort ); if (count->next()) { + if (!result) result = IProperty::PropertyEntranceResult(); result->totalEntriesMatchingQuery = count->getUInt("count"); } } while (properties->next()) { + if (!result) result = IProperty::PropertyEntranceResult(); auto& entry = result->entries.emplace_back(); entry.id = properties->getUInt64("id"); entry.ownerId = properties->getUInt64("owner_id"); diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index bd1a2793..5a0dab77 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -775,6 +775,12 @@ void Entity::Initialize() { // Hacky way to trigger these when the object has had a chance to get constructed AddCallbackTimer(0, [this]() { this->GetScript()->OnStartup(this); + if (this->m_ParentEntity) { + GameMessages::ChildLoaded childLoaded; + childLoaded.childID = this->m_ObjectID; + childLoaded.templateID = this->GetLOT(); + this->m_ParentEntity->OnChildLoaded(childLoaded); + } }); if (!m_Character && Game::entityManager->GetGhostingEnabled()) { @@ -1501,6 +1507,10 @@ void Entity::OnShootingGalleryFire(GameMessages::ShootingGalleryFire& fire) { GetScript()->OnShootingGalleryFire(*this, fire); } +void Entity::OnChildLoaded(GameMessages::ChildLoaded& childLoaded) { + GetScript()->OnChildLoaded(*this, childLoaded); +} + void Entity::RequestActivityExit(Entity* sender, LWOOBJID player, bool canceled) { GetScript()->OnRequestActivityExit(sender, player, canceled); } diff --git a/dGame/Entity.h b/dGame/Entity.h index 1e07aff0..7bb53c2d 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -16,6 +16,7 @@ namespace GameMessages { struct ActivityNotify; struct ShootingGalleryFire; + struct ChildLoaded; }; namespace Loot { @@ -217,6 +218,7 @@ public: void OnZonePropertyModelRotated(Entity* player); void OnActivityNotify(GameMessages::ActivityNotify& notify); void OnShootingGalleryFire(GameMessages::ShootingGalleryFire& notify); + void OnChildLoaded(GameMessages::ChildLoaded& childLoaded); void OnMessageBoxResponse(Entity* sender, int32_t button, const std::u16string& identifier, const std::u16string& userData); void OnChoiceBoxResponse(Entity* sender, int32_t button, const std::u16string& buttonIdentifier, const std::u16string& identifier); diff --git a/dGame/dComponents/PhysicsComponent.cpp b/dGame/dComponents/PhysicsComponent.cpp index 67fca8d9..15a6d61f 100644 --- a/dGame/dComponents/PhysicsComponent.cpp +++ b/dGame/dComponents/PhysicsComponent.cpp @@ -84,6 +84,10 @@ dpEntity* PhysicsComponent::CreatePhysicsEntity(eReplicaComponentType type) { } else if (info->physicsAsset == "env\\env_won_fv_gas-blocking-volume.hkx") { toReturn = new dpEntity(m_Parent->GetObjectID(), 390.496826f, 111.467964f, 600.821534f, true); m_Position.y -= (111.467964f * m_Parent->GetDefaultScale()) / 2; + } else if (info->physicsAsset == "env\\GFTrack_DeathVolume1_CaveExit.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 112.416870f, 50.363434f, 87.679268f); + } else if (info->physicsAsset == "env\\GFTrack_DeathVolume2_RoadGaps.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 48.386536f, 50.363434f, 259.361755f); } else { // LOG_DEBUG("This one is supposed to have %s", info->physicsAsset.c_str()); diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index f03fb9b4..02a7ca0e 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -285,7 +285,7 @@ void RacingControlComponent::OnRacingClientReady(Entity* player) { Game::entityManager->SerializeEntity(m_Parent); } -void RacingControlComponent::OnRequestDie(Entity* player) { +void RacingControlComponent::OnRequestDie(Entity* player, const std::u16string& deathType) { // Sent by the client when they collide with something which should smash // them. @@ -301,8 +301,9 @@ void RacingControlComponent::OnRequestDie(Entity* player) { if (!racingPlayer.noSmashOnReload) { racingPlayer.smashedTimes++; + LOG("Death type %s", GeneralUtils::UTF16ToWTF8(deathType).c_str()); GameMessages::SendDie(vehicle, vehicle->GetObjectID(), LWOOBJID_EMPTY, true, - eKillType::VIOLENT, u"", 0, 0, 90.0f, false, true, 0); + eKillType::VIOLENT, deathType, 0, 0, 90.0f, false, true, 0); auto* destroyableComponent = vehicle->GetComponent(); uint32_t respawnImagination = 0; diff --git a/dGame/dComponents/RacingControlComponent.h b/dGame/dComponents/RacingControlComponent.h index 4a661ca7..67884d0d 100644 --- a/dGame/dComponents/RacingControlComponent.h +++ b/dGame/dComponents/RacingControlComponent.h @@ -135,7 +135,7 @@ public: /** * Invoked when the client says it should be smashed. */ - void OnRequestDie(Entity* player); + void OnRequestDie(Entity* player, const std::u16string& deathType = u""); /** * Invoked when the player has finished respawning. diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index 5ecc5e68..2e00ffab 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -117,7 +117,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System } case MessageType::Game::PLAYER_LOADED: { - GameMessages::SendRestoreToPostLoadStats(entity, sysAddr); entity->SetPlayerReadyForUpdates(); auto* ghostComponent = entity->GetComponent(); @@ -135,6 +134,8 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System } } + GameMessages::SendRestoreToPostLoadStats(entity, sysAddr); + auto* destroyable = entity->GetComponent(); destroyable->SetImagination(destroyable->GetImagination()); Game::entityManager->SerializeEntity(entity); diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 0b1bcf52..b5ef62d9 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -758,6 +758,13 @@ namespace GameMessages { NiPoint3 target{}; NiQuaternion rotation{}; }; + + struct ChildLoaded : public GameMsg { + ChildLoaded() : GameMsg(MessageType::Game::CHILD_LOADED) {} + + LOT templateID{}; + LWOOBJID childID{}; + }; }; #endif // GAMEMESSAGES_H diff --git a/dMasterServer/Start.cpp b/dMasterServer/Start.cpp index d35392f1..119092a4 100644 --- a/dMasterServer/Start.cpp +++ b/dMasterServer/Start.cpp @@ -4,68 +4,146 @@ #include "Game.h" #include "BinaryPathFinder.h" -void StartChatServer() { +#ifdef _WIN32 +#include +#include +#include + +namespace { + const auto startup = STARTUPINFOW{ + .cb = sizeof(STARTUPINFOW), + .lpReserved = nullptr, + .lpDesktop = nullptr, + .lpTitle = nullptr, + .dwX = 0, + .dwY = 0, + .dwXSize = 0, + .dwYSize = 0, + .dwXCountChars = 0, + .dwYCountChars = 0, + .dwFillAttribute = 0, + .dwFlags = 0, + .wShowWindow = 0, + .cbReserved2 = 0, + .lpReserved2 = nullptr, + .hStdInput = INVALID_HANDLE_VALUE, + .hStdOutput = INVALID_HANDLE_VALUE, + .hStdError = INVALID_HANDLE_VALUE, + }; +} +#else +#include +#endif + +uint32_t StartChatServer() { if (Game::ShouldShutdown()) { LOG("Currently shutting down. Chat will not be restarted."); - return; + return 0; + } + auto chat_path = BinaryPathFinder::GetBinaryDir() / "ChatServer"; +#ifdef _WIN32 + chat_path.replace_extension(".exe"); + auto chat_startup = startup; + auto chat_info = PROCESS_INFORMATION{}; + if (!CreateProcessW(chat_path.wstring().data(), chat_path.wstring().data(), + nullptr, nullptr, false, 0, nullptr, nullptr, + &chat_startup, &chat_info)) + { + LOG("Failed to launch ChatServer"); + return 0; + } + + // get pid and close unused handles + auto chat_pid = chat_info.dwProcessId; + CloseHandle(chat_info.hProcess); + CloseHandle(chat_info.hThread); +#else // *nix systems + const auto chat_pid = fork(); + if (chat_pid < 0) { + LOG("Failed to launch ChatServer"); + return 0; + } else if (chat_pid == 0) { + // We are the child process + execl(chat_path.string().c_str(), chat_path.string().c_str(), nullptr); } -#ifdef __APPLE__ - //macOS doesn't need sudo to run on ports < 1024 - auto result = system(((BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str()); -#elif _WIN32 - auto result = system(("start /B " + (BinaryPathFinder::GetBinaryDir() / "ChatServer.exe").string()).c_str()); -#else - if (std::atoi(Game::config->GetValue("use_sudo_chat").c_str())) { - auto result = system(("sudo " + (BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str()); - } else { - auto result = system(((BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str()); -} #endif + LOG("ChatServer PID is %d", chat_pid); + return chat_pid; } -void StartAuthServer() { +uint32_t StartAuthServer() { if (Game::ShouldShutdown()) { LOG("Currently shutting down. Auth will not be restarted."); - return; + return 0; } -#ifdef __APPLE__ - auto result = system(((BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str()); -#elif _WIN32 - auto result = system(("start /B " + (BinaryPathFinder::GetBinaryDir() / "AuthServer.exe").string()).c_str()); -#else - if (std::atoi(Game::config->GetValue("use_sudo_auth").c_str())) { - auto result = system(("sudo " + (BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str()); - } else { - auto result = system(((BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str()); -} -#endif -} - -void StartWorldServer(LWOMAPID mapID, uint16_t port, LWOINSTANCEID lastInstanceID, int maxPlayers, LWOCLONEID cloneID) { + auto auth_path = BinaryPathFinder::GetBinaryDir() / "AuthServer"; #ifdef _WIN32 - std::string cmd = "start /B " + (BinaryPathFinder::GetBinaryDir() / "WorldServer.exe").string() + " -zone "; -#else - std::string cmd; - if (std::atoi(Game::config->GetValue("use_sudo_world").c_str())) { - cmd = "sudo " + (BinaryPathFinder::GetBinaryDir() / "WorldServer").string() + " -zone "; - } else { - cmd = (BinaryPathFinder::GetBinaryDir() / "WorldServer").string() + " -zone "; + auth_path.replace_extension(".exe"); + auto auth_startup = startup; + auto auth_info = PROCESS_INFORMATION{}; + if (!CreateProcessW(auth_path.wstring().data(), auth_path.wstring().data(), + nullptr, nullptr, false, 0, nullptr, nullptr, + &auth_startup, &auth_info)) + { + LOG("Failed to launch AuthServer"); + return 0; + } + + // get pid and close unused handles + auto auth_pid = auth_info.dwProcessId; + CloseHandle(auth_info.hProcess); + CloseHandle(auth_info.hThread); +#else // *nix systems + const auto auth_pid = fork(); + if (auth_pid < 0) { + LOG("Failed to launch AuthServer"); + return 0; + } else if (auth_pid == 0) { + // We are the child process + execl(auth_path.string().c_str(), auth_path.string().c_str(), nullptr); } #endif - - cmd.append(std::to_string(mapID)); - cmd.append(" -port "); - cmd.append(std::to_string(port)); - cmd.append(" -instance "); - cmd.append(std::to_string(lastInstanceID)); - cmd.append(" -maxclients "); - cmd.append(std::to_string(maxPlayers)); - cmd.append(" -clone "); - cmd.append(std::to_string(cloneID)); - -#ifndef _WIN32 - cmd.append("&"); //Sends our next process to the background on Linux -#endif - - auto ret = system(cmd.c_str()); + LOG("AuthServer PID is %d", auth_pid); + return auth_pid; +} + +uint32_t StartWorldServer(LWOMAPID mapID, uint16_t port, LWOINSTANCEID lastInstanceID, int maxPlayers, LWOCLONEID cloneID) { + auto world_path = BinaryPathFinder::GetBinaryDir() / "WorldServer"; +#ifdef _WIN32 + world_path.replace_extension(".exe"); + auto cmd = world_path.wstring() + L" -zone " + std::to_wstring(mapID) + L" -port " + std::to_wstring(port) + + L" -instance " + std::to_wstring(lastInstanceID) + L" -maxclients " + std::to_wstring(maxPlayers) + + L" -clone " + std::to_wstring(cloneID); + + auto world_startup = startup; + auto world_info = PROCESS_INFORMATION{}; + if (!CreateProcessW(world_path.wstring().data(), cmd.data(), + nullptr, nullptr, false, 0, nullptr, nullptr, + &world_startup, &world_info)) + { + LOG("Failed to launch WorldServer"); + return 0; + } + + // get pid and close unused handles + auto world_pid = world_info.dwProcessId; + CloseHandle(world_info.hProcess); + CloseHandle(world_info.hThread); +#else + const auto world_pid = fork(); + if (world_pid < 0) { + LOG("Failed to launch WorldServer"); + return 0; + } else if (world_pid == 0) { + // We are the child process + execl(world_path.string().c_str(), world_path.string().c_str(), + "-zone", std::to_string(mapID).c_str(), + "-port", std::to_string(port).c_str(), + "-instance", std::to_string(lastInstanceID).c_str(), + "-maxclients", std::to_string(maxPlayers).c_str(), + "-clone", std::to_string(cloneID).c_str(), nullptr); + } +#endif + LOG("WorldServer PID is %d", world_pid); + return world_pid; } diff --git a/dMasterServer/Start.h b/dMasterServer/Start.h index 98c83233..85041f6e 100644 --- a/dMasterServer/Start.h +++ b/dMasterServer/Start.h @@ -1,6 +1,6 @@ #pragma once #include "dCommonVars.h" -void StartAuthServer(); -void StartChatServer(); -void StartWorldServer(LWOMAPID mapID, uint16_t port, LWOINSTANCEID lastInstanceID, int maxPlayers, LWOCLONEID cloneID); +uint32_t StartAuthServer(); +uint32_t StartChatServer(); +uint32_t StartWorldServer(LWOMAPID mapID, uint16_t port, LWOINSTANCEID lastInstanceID, int maxPlayers, LWOCLONEID cloneID); diff --git a/dNet/dServer.cpp b/dNet/dServer.cpp index 20a21d49..c7e4b226 100644 --- a/dNet/dServer.cpp +++ b/dNet/dServer.cpp @@ -10,6 +10,7 @@ #include "MessageType/Server.h" #include "MessageType/Master.h" +#include "BinaryPathFinder.h" #include "BitStreamUtils.h" #include "MasterPackets.h" #include "ZoneInstanceManager.h" @@ -68,7 +69,16 @@ dServer::dServer(const std::string& ip, int port, int instanceID, int maxConnect LOG("%s Server is listening on %s:%i with encryption: %i", StringifiedEnum::ToString(serverType).data(), ip.c_str(), port, int(useEncryption)); else LOG("%s Server is listening on %s:%i with encryption: %i, running zone %i / %i", StringifiedEnum::ToString(serverType).data(), ip.c_str(), port, int(useEncryption), zoneID, instanceID); - } else { LOG("FAILED TO START SERVER ON IP/PORT: %s:%i", ip.c_str(), port); return; } + } else { + LOG("FAILED TO START SERVER ON IP/PORT: %s:%i", ip.c_str(), port); +#ifdef DARKFLAME_PLATFORM_LINUX + if (mServerType == ServerType::Auth) { + const auto cwd = BinaryPathFinder::GetBinaryDir(); + LOG("Try running the following command before launching again:\n sudo setcap 'cap_net_bind_service=+ep' \"%s/AuthServer\"", cwd.string().c_str()); + } +#endif + return; + } mLogger->SetLogToConsole(prevLogSetting); @@ -109,20 +119,23 @@ Packet* dServer::ReceiveFromMaster() { if (packet) { if (packet->length < 1) { mMasterPeer->DeallocatePacket(packet); return nullptr; } - if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { + switch (packet->data[0]) { + case ID_DISCONNECTION_NOTIFICATION: + [[fallthrough]]; + case ID_CONNECTION_LOST: { LOG("Lost our connection to master, shutting DOWN!"); mMasterConnectionActive = false; - //ConnectToMaster(); //We'll just shut down now + // ConnectToMaster(); // We'll just shut down now + break; } - - if (packet->data[0] == ID_CONNECTION_REQUEST_ACCEPTED) { + case ID_CONNECTION_REQUEST_ACCEPTED: { LOG("Established connection to master, zone (%i), instance (%i)", this->GetZoneID(), this->GetInstanceID()); mMasterConnectionActive = true; mMasterSystemAddress = packet->systemAddress; MasterPackets::SendServerInfo(this, packet); + break; } - - if (packet->data[0] == ID_USER_PACKET_ENUM) { + case ID_USER_PACKET_ENUM: { if (static_cast(packet->data[1]) == eConnectionType::MASTER) { switch (static_cast(packet->data[3])) { case MessageType::Master::REQUEST_ZONE_TRANSFER_RESPONSE: { @@ -133,12 +146,13 @@ Packet* dServer::ReceiveFromMaster() { *mShouldShutdown = -2; break; - //When we handle these packets in World instead dServer, we just return the packet's pointer. + // When we handle these packets in World instead dServer, we just return the packet's pointer. default: - return packet; } } + break; + } } mMasterPeer->DeallocatePacket(packet); diff --git a/dScripts/02_server/Map/AM/AmSkullkinTower.cpp b/dScripts/02_server/Map/AM/AmSkullkinTower.cpp index 1eaad3c9..a6a4b8c8 100644 --- a/dScripts/02_server/Map/AM/AmSkullkinTower.cpp +++ b/dScripts/02_server/Map/AM/AmSkullkinTower.cpp @@ -64,21 +64,22 @@ void AmSkullkinTower::SpawnLegs(Entity* self, const std::string& loc) { info.rot = NiQuaternion::LookAt(info.pos, self->GetPosition()); - auto* entity = Game::entityManager->CreateEntity(info); + auto* entity = Game::entityManager->CreateEntity(info, nullptr, self); Game::entityManager->ConstructEntity(entity); - - OnChildLoaded(self, entity); } -void AmSkullkinTower::OnChildLoaded(Entity* self, Entity* child) { - auto legTable = self->GetVar>(u"legTable"); +void AmSkullkinTower::OnChildLoaded(Entity& self, GameMessages::ChildLoaded& childLoaded) { + auto legTable = self.GetVar>(u"legTable"); - legTable.push_back(child->GetObjectID()); + legTable.push_back(childLoaded.childID); - self->SetVar(u"legTable", legTable); + self.SetVar(u"legTable", legTable); - const auto selfID = self->GetObjectID(); + const auto selfID = self.GetObjectID(); + auto* const child = Game::entityManager->GetEntity(childLoaded.childID); + + if (!child) return; child->AddDieCallback([this, selfID, child]() { auto* self = Game::entityManager->GetEntity(selfID); diff --git a/dScripts/02_server/Map/AM/AmSkullkinTower.h b/dScripts/02_server/Map/AM/AmSkullkinTower.h index 495641de..53638f32 100644 --- a/dScripts/02_server/Map/AM/AmSkullkinTower.h +++ b/dScripts/02_server/Map/AM/AmSkullkinTower.h @@ -8,7 +8,7 @@ public: void SpawnLegs(Entity* self, const std::string& loc); - void OnChildLoaded(Entity* self, Entity* child); + void OnChildLoaded(Entity& self, GameMessages::ChildLoaded& childLoaded) override; void NotifyDie(Entity* self, Entity* other, Entity* killer); diff --git a/dScripts/02_server/Map/General/QbSpawner.cpp b/dScripts/02_server/Map/General/QbSpawner.cpp index 6776c8ea..6fc93d05 100644 --- a/dScripts/02_server/Map/General/QbSpawner.cpp +++ b/dScripts/02_server/Map/General/QbSpawner.cpp @@ -77,8 +77,6 @@ void QbSpawner::OnTimerDone(Entity* self, std::string timerName) { auto* child = Game::entityManager->CreateEntity(info, nullptr, self); Game::entityManager->ConstructEntity(child); - - OnChildLoaded(self, child); } else { auto* mob = Game::entityManager->GetEntity(mobTable[i]); AggroTargetObject(self, mob); @@ -88,16 +86,19 @@ void QbSpawner::OnTimerDone(Entity* self, std::string timerName) { } } -void QbSpawner::OnChildLoaded(Entity* self, Entity* child) { - auto mobTable = self->GetVar>(u"mobTable"); +void QbSpawner::OnChildLoaded(Entity& self, GameMessages::ChildLoaded& childLoaded) { + auto* const child = Game::entityManager->GetEntity(childLoaded.childID); + if (!child) return; + + auto mobTable = self.GetVar>(u"mobTable"); auto tableLoc = child->GetVar(u"mobTableLoc"); mobTable[tableLoc] = child->GetObjectID(); - self->SetVar>(u"mobTable", mobTable); + self.SetVar>(u"mobTable", mobTable); - AggroTargetObject(self, child); + AggroTargetObject(&self, child); - const auto selfID = self->GetObjectID(); + const auto selfID = self.GetObjectID(); child->AddDieCallback([this, selfID, child]() { auto* self = Game::entityManager->GetEntity(selfID); diff --git a/dScripts/02_server/Map/General/QbSpawner.h b/dScripts/02_server/Map/General/QbSpawner.h index 105d0835..14990122 100644 --- a/dScripts/02_server/Map/General/QbSpawner.h +++ b/dScripts/02_server/Map/General/QbSpawner.h @@ -6,7 +6,7 @@ public: void OnStartup(Entity* self) override; void OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) override; void OnTimerDone(Entity* self, std::string timerName) override; - void OnChildLoaded(Entity* self, Entity* child); + void OnChildLoaded(Entity& self, GameMessages::ChildLoaded& childLoaded) override; void OnChildRemoved(Entity* self, Entity* child); void AggroTargetObject(Entity* self, Entity* enemy); private: diff --git a/dScripts/02_server/Map/NT/NtCombatChallengeServer.cpp b/dScripts/02_server/Map/NT/NtCombatChallengeServer.cpp index 011a67ea..94ad718f 100644 --- a/dScripts/02_server/Map/NT/NtCombatChallengeServer.cpp +++ b/dScripts/02_server/Map/NT/NtCombatChallengeServer.cpp @@ -91,7 +91,7 @@ void NtCombatChallengeServer::SpawnTargetDummy(Entity* self) { info.rot = self->GetRotation(); info.settings = { new LDFData(u"custom_script_server", "scripts\\02_server\\Map\\NT\\L_NT_COMBAT_CHALLENGE_DUMMY.lua") }; - auto* dummy = Game::entityManager->CreateEntity(info); + auto* dummy = Game::entityManager->CreateEntity(info, nullptr, self); dummy->SetVar(u"challengeObjectID", self->GetObjectID()); @@ -104,26 +104,18 @@ void NtCombatChallengeServer::SetAttackImmunity(LWOOBJID objID, bool bTurnOn) { } -void NtCombatChallengeServer::OnChildLoaded(Entity* self, Entity* child) { - auto targetNumber = self->GetVar(u"TargetNumber"); - if (targetNumber == 0) targetNumber = 1; - self->SetVar(u"TargetNumber", targetNumber + 1); +void NtCombatChallengeServer::OnChildLoaded(Entity& self, GameMessages::ChildLoaded& childLoaded) { + auto* const child = Game::entityManager->GetEntity(childLoaded.childID); - const auto playerID = self->GetVar(u"playerID"); - - auto* player = Game::entityManager->GetEntity(playerID); - - if (player == nullptr) { - return; + if (child) { + child->SetRotation(NiQuaternion::FromEulerAngles(child->GetRotation().GetEulerAngles() += NiPoint3(0, PI, 0))); // rotate 180 degrees } - child->SetRotation(NiQuaternion::LookAt(child->GetPosition(), player->GetPosition())); - - self->SetVar(u"currentTargetID", child->GetObjectID()); + self.SetVar(u"currentTargetID", child->GetObjectID()); Game::entityManager->SerializeEntity(child); - child->GetGroups().push_back("targets_" + std::to_string(self->GetObjectID())); + child->GetGroups().push_back("targets_" + std::to_string(self.GetObjectID())); } void NtCombatChallengeServer::ResetGame(Entity* self) { diff --git a/dScripts/02_server/Map/NT/NtCombatChallengeServer.h b/dScripts/02_server/Map/NT/NtCombatChallengeServer.h index 72bb981f..3b59b937 100644 --- a/dScripts/02_server/Map/NT/NtCombatChallengeServer.h +++ b/dScripts/02_server/Map/NT/NtCombatChallengeServer.h @@ -12,7 +12,7 @@ public: void OnMessageBoxResponse(Entity* self, Entity* sender, int32_t button, const std::u16string& identifier, const std::u16string& userData) override; void SpawnTargetDummy(Entity* self); void SetAttackImmunity(LWOOBJID objID, bool bTurnOn); - void OnChildLoaded(Entity* self, Entity* child); + void OnChildLoaded(Entity& self, GameMessages::ChildLoaded& childLoaded) override; void ResetGame(Entity* self); void OnActivityTimerUpdate(Entity* self, float timeRemaining); void OnTimerDone(Entity* self, std::string timerName) override; diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 696fa590..c8087c94 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -294,6 +294,9 @@ #include "ShardArmor.h" #include "TeslaPack.h" #include "StunImmunity.h" +#include "GfRaceServer.h" +#include "FvRaceServer.h" +#include "VehicleDeathTriggerWaterServer.h" // Survival scripts #include "AgSurvivalStromling.h" @@ -694,6 +697,9 @@ namespace { {"scripts\\ai\\AG\\L_AG_SPIDER_BOSS_MESSAGE.lua", []() {return new AgSpiderBossMessage();}}, {"scripts\\ai\\GF\\L_GF_RACE_INSTANCER.lua", []() {return new GfRaceInstancer();}}, {"scripts\\ai\\RACING\\TRACK_NS\\NS_RACE_SERVER.lua", []() {return new NsRaceServer();}}, + {"scripts\\ai\\RACING\\TRACK_GF\\GF_RACE_SERVER.lua", []() {return new GfRaceServer();}}, + {"scripts\\ai\\RACING\\TRACK_FV\\FV_RACE_SERVER.lua", []() {return new FvRaceServer();}}, + {"scripts\\ai\\RACING\\OBJECTS\\VEHICLE_DEATH_TRIGGER_WATER_SERVER.lua", []() {return new VehicleDeathTriggerWaterServer();}}, }; @@ -712,6 +718,12 @@ namespace { "scripts\\ai\\PETS\\PET_BLOCKER.lua", "scripts\\ai\\PETS\\PET_FLEA_MISSION.lua", "scripts\\ai\\ACT\\L_ACT_PET_INSTANCE_EXIT.lua", + "scripts\\ai\\WILD\\L_WILD_GF_FROG.lua", + "scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_Robotanist.lua", + "scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_Seperator.lua", + "scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_InfectedCitizen.lua", + "scripts\\ai\\MINIGAME\\SIEGE\\OBJECTS\\ATTACKER_BOUNCER_SERVER.lua", + "scripts\\ai\\AG\\L_AG_ZONE_PLAYER.lua", }; }; diff --git a/dScripts/CppScripts.h b/dScripts/CppScripts.h index 573dab89..18e0ec10 100644 --- a/dScripts/CppScripts.h +++ b/dScripts/CppScripts.h @@ -373,6 +373,14 @@ namespace CppScripts { * @param fire The firing data */ virtual void OnShootingGalleryFire(Entity& self, GameMessages::ShootingGalleryFire& fire) {}; + + /** + * @brief Handles when a child is loaded + * + * @param self + * @param fire The child info + */ + virtual void OnChildLoaded(Entity& self, GameMessages::ChildLoaded& childLoaded) {}; }; Script* const GetScript(Entity* parent, const std::string& scriptName); diff --git a/dScripts/ai/RACING/CMakeLists.txt b/dScripts/ai/RACING/CMakeLists.txt index 1fef44d7..a51273e6 100644 --- a/dScripts/ai/RACING/CMakeLists.txt +++ b/dScripts/ai/RACING/CMakeLists.txt @@ -13,6 +13,18 @@ foreach(file ${DSCRIPTS_SOURCES_AI_RACING_TRACK_NS}) set(DSCRIPTS_SOURCES_AI_RACING ${DSCRIPTS_SOURCES_AI_RACING} "TRACK_NS/${file}") endforeach() +add_subdirectory(TRACK_GF) + +foreach(file ${DSCRIPTS_SOURCES_AI_RACING_TRACK_GF}) + set(DSCRIPTS_SOURCES_AI_RACING ${DSCRIPTS_SOURCES_AI_RACING} "TRACK_GF/${file}") +endforeach() + +add_subdirectory(TRACK_FV) + +foreach(file ${DSCRIPTS_SOURCES_AI_RACING_TRACK_FV}) + set(DSCRIPTS_SOURCES_AI_RACING ${DSCRIPTS_SOURCES_AI_RACING} "TRACK_FV/${file}") +endforeach() + add_library(dScriptsAiRacing OBJECT ${DSCRIPTS_SOURCES_AI_RACING}) -target_include_directories(dScriptsAiRacing PUBLIC "." "OBJECTS" "TRACK_NS") +target_include_directories(dScriptsAiRacing PUBLIC "." "OBJECTS" "TRACK_NS" "TRACK_GF" "TRACK_FV") target_precompile_headers(dScriptsAiRacing REUSE_FROM dScriptsBase) diff --git a/dScripts/ai/RACING/OBJECTS/CMakeLists.txt b/dScripts/ai/RACING/OBJECTS/CMakeLists.txt index 83f4b8b3..a1bd7362 100644 --- a/dScripts/ai/RACING/OBJECTS/CMakeLists.txt +++ b/dScripts/ai/RACING/OBJECTS/CMakeLists.txt @@ -7,4 +7,5 @@ set(DSCRIPTS_SOURCES_AI_RACING_OBJECTS "FvRacePillarDServer.cpp" "FvRaceSmashEggImagineServer.cpp" "RaceSmashServer.cpp" + "VehicleDeathTriggerWaterServer.cpp" PARENT_SCOPE) diff --git a/dScripts/ai/RACING/OBJECTS/VehicleDeathTriggerWaterServer.cpp b/dScripts/ai/RACING/OBJECTS/VehicleDeathTriggerWaterServer.cpp new file mode 100644 index 00000000..f04c74ed --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/VehicleDeathTriggerWaterServer.cpp @@ -0,0 +1,16 @@ +#include "VehicleDeathTriggerWaterServer.h" + +#include "PossessorComponent.h" +#include "RacingControlComponent.h" + +void VehicleDeathTriggerWaterServer::OnCollisionPhantom(Entity* self, Entity* target) { + if (target->IsPlayer() && !target->GetIsDead()) { + const std::vector racingControllers = Game::entityManager->GetEntitiesByComponent(RacingControlComponent::ComponentType); + for (auto* const racingController : racingControllers) { + auto* racingControlComponent = racingController->GetComponent(); + if (racingControlComponent) { + racingControlComponent->OnRequestDie(target, u"death_water"); + } + } + } +} diff --git a/dScripts/ai/RACING/OBJECTS/VehicleDeathTriggerWaterServer.h b/dScripts/ai/RACING/OBJECTS/VehicleDeathTriggerWaterServer.h new file mode 100644 index 00000000..95d878b9 --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/VehicleDeathTriggerWaterServer.h @@ -0,0 +1,11 @@ +#ifndef VEHICLEDEATHTRIGGERWATERSERVER_H +#define VEHICLEDEATHTRIGGERWATERSERVER_H + +#include "CppScripts.h" + +class VehicleDeathTriggerWaterServer : public CppScripts::Script { +public: + void OnCollisionPhantom(Entity* self, Entity* target) override; +}; + +#endif //!VEHICLEDEATHTRIGGERWATERSERVER_H diff --git a/dScripts/ai/RACING/TRACK_FV/CMakeLists.txt b/dScripts/ai/RACING/TRACK_FV/CMakeLists.txt new file mode 100644 index 00000000..f8a0867b --- /dev/null +++ b/dScripts/ai/RACING/TRACK_FV/CMakeLists.txt @@ -0,0 +1,3 @@ +set(DSCRIPTS_SOURCES_AI_RACING_TRACK_FV + "FvRaceServer.cpp" + PARENT_SCOPE) diff --git a/dScripts/ai/RACING/TRACK_FV/FvRaceServer.cpp b/dScripts/ai/RACING/TRACK_FV/FvRaceServer.cpp new file mode 100644 index 00000000..5dd43f48 --- /dev/null +++ b/dScripts/ai/RACING/TRACK_FV/FvRaceServer.cpp @@ -0,0 +1,55 @@ +#include "FvRaceServer.h" + +#include "RacingControlComponent.h" +#include "Entity.h" + +using std::unique_ptr; +using std::make_unique; + +void FvRaceServer::OnStartup(Entity* self) { + GameMessages::ConfigureRacingControl config; + auto& raceSet = config.racingSettings; + + raceSet.push_back(make_unique>(u"GameType", u"Racing")); + raceSet.push_back(make_unique>(u"GameState", u"Starting")); + raceSet.push_back(make_unique>(u"Number_Of_PlayersPerTeam", 6)); + raceSet.push_back(make_unique>(u"Minimum_Players_to_Start", 2)); + raceSet.push_back(make_unique>(u"Minimum_Players_for_Group_Achievements", 2)); + + raceSet.push_back(make_unique>(u"Car_Object", 7703)); + raceSet.push_back(make_unique>(u"Race_PathName", u"MainPath")); + raceSet.push_back(make_unique>(u"Current_Lap", 1)); + raceSet.push_back(make_unique>(u"Number_of_Laps", 3)); + raceSet.push_back(make_unique>(u"activityID", 54)); + + raceSet.push_back(make_unique>(u"Place_1", 100)); + raceSet.push_back(make_unique>(u"Place_2", 90)); + raceSet.push_back(make_unique>(u"Place_3", 80)); + raceSet.push_back(make_unique>(u"Place_4", 70)); + raceSet.push_back(make_unique>(u"Place_5", 60)); + raceSet.push_back(make_unique>(u"Place_6", 50)); + + raceSet.push_back(make_unique>(u"Num_of_Players_1", 15)); + raceSet.push_back(make_unique>(u"Num_of_Players_2", 25)); + raceSet.push_back(make_unique>(u"Num_of_Players_3", 50)); + raceSet.push_back(make_unique>(u"Num_of_Players_4", 85)); + raceSet.push_back(make_unique>(u"Num_of_Players_5", 90)); + raceSet.push_back(make_unique>(u"Num_of_Players_6", 100)); + + raceSet.push_back(make_unique>(u"Number_of_Spawn_Groups", 1)); + raceSet.push_back(make_unique>(u"Red_Spawners", 4847)); + raceSet.push_back(make_unique>(u"Blue_Spawners", 4848)); + raceSet.push_back(make_unique>(u"Blue_Flag", 4850)); + raceSet.push_back(make_unique>(u"Red_Flag", 4851)); + raceSet.push_back(make_unique>(u"Red_Point", 4846)); + raceSet.push_back(make_unique>(u"Blue_Point", 4845)); + raceSet.push_back(make_unique>(u"Red_Mark", 4844)); + raceSet.push_back(make_unique>(u"Blue_Mark", 4843)); + + const std::vector racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); + for (auto* const racingController : racingControllers) { + auto* racingComponent = racingController->GetComponent(); + if (racingComponent) racingComponent->MsgConfigureRacingControl(config); + } +} + diff --git a/dScripts/ai/RACING/TRACK_FV/FvRaceServer.h b/dScripts/ai/RACING/TRACK_FV/FvRaceServer.h new file mode 100644 index 00000000..b807c870 --- /dev/null +++ b/dScripts/ai/RACING/TRACK_FV/FvRaceServer.h @@ -0,0 +1,11 @@ +#ifndef FVRACESERVER_H +#define FVRACESERVER_H + +#include "RaceImaginationServer.h" + +class FvRaceServer : public RaceImaginationServer { +public: + void OnStartup(Entity* self) override; +}; + +#endif //!FVRACESERVER_H diff --git a/dScripts/ai/RACING/TRACK_GF/CMakeLists.txt b/dScripts/ai/RACING/TRACK_GF/CMakeLists.txt new file mode 100644 index 00000000..3d5501b1 --- /dev/null +++ b/dScripts/ai/RACING/TRACK_GF/CMakeLists.txt @@ -0,0 +1,3 @@ +set(DSCRIPTS_SOURCES_AI_RACING_TRACK_GF + "GfRaceServer.cpp" + PARENT_SCOPE) diff --git a/dScripts/ai/RACING/TRACK_GF/GfRaceServer.cpp b/dScripts/ai/RACING/TRACK_GF/GfRaceServer.cpp new file mode 100644 index 00000000..d465d57f --- /dev/null +++ b/dScripts/ai/RACING/TRACK_GF/GfRaceServer.cpp @@ -0,0 +1,55 @@ +#include "GfRaceServer.h" + +#include "RacingControlComponent.h" +#include "Entity.h" + +using std::unique_ptr; +using std::make_unique; + +void GfRaceServer::OnStartup(Entity* self) { + GameMessages::ConfigureRacingControl config; + auto& raceSet = config.racingSettings; + + raceSet.push_back(make_unique>(u"GameType", u"Racing")); + raceSet.push_back(make_unique>(u"GameState", u"Starting")); + raceSet.push_back(make_unique>(u"Number_Of_PlayersPerTeam", 6)); + raceSet.push_back(make_unique>(u"Minimum_Players_to_Start", 2)); + raceSet.push_back(make_unique>(u"Minimum_Players_for_Group_Achievements", 2)); + + raceSet.push_back(make_unique>(u"Car_Object", 7703)); + raceSet.push_back(make_unique>(u"Race_PathName", u"MainPath")); + raceSet.push_back(make_unique>(u"Current_Lap", 1)); + raceSet.push_back(make_unique>(u"Number_of_Laps", 3)); + raceSet.push_back(make_unique>(u"activityID", 39)); + + raceSet.push_back(make_unique>(u"Place_1", 100)); + raceSet.push_back(make_unique>(u"Place_2", 90)); + raceSet.push_back(make_unique>(u"Place_3", 80)); + raceSet.push_back(make_unique>(u"Place_4", 70)); + raceSet.push_back(make_unique>(u"Place_5", 60)); + raceSet.push_back(make_unique>(u"Place_6", 50)); + + raceSet.push_back(make_unique>(u"Num_of_Players_1", 15)); + raceSet.push_back(make_unique>(u"Num_of_Players_2", 25)); + raceSet.push_back(make_unique>(u"Num_of_Players_3", 50)); + raceSet.push_back(make_unique>(u"Num_of_Players_4", 85)); + raceSet.push_back(make_unique>(u"Num_of_Players_5", 90)); + raceSet.push_back(make_unique>(u"Num_of_Players_6", 100)); + + raceSet.push_back(make_unique>(u"Number_of_Spawn_Groups", 1)); + raceSet.push_back(make_unique>(u"Red_Spawners", 4847)); + raceSet.push_back(make_unique>(u"Blue_Spawners", 4848)); + raceSet.push_back(make_unique>(u"Blue_Flag", 4850)); + raceSet.push_back(make_unique>(u"Red_Flag", 4851)); + raceSet.push_back(make_unique>(u"Red_Point", 4846)); + raceSet.push_back(make_unique>(u"Blue_Point", 4845)); + raceSet.push_back(make_unique>(u"Red_Mark", 4844)); + raceSet.push_back(make_unique>(u"Blue_Mark", 4843)); + + const std::vector racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); + for (auto* const racingController : racingControllers) { + auto* racingComponent = racingController->GetComponent(); + if (racingComponent) racingComponent->MsgConfigureRacingControl(config); + } +} + diff --git a/dScripts/ai/RACING/TRACK_GF/GfRaceServer.h b/dScripts/ai/RACING/TRACK_GF/GfRaceServer.h new file mode 100644 index 00000000..c17b907b --- /dev/null +++ b/dScripts/ai/RACING/TRACK_GF/GfRaceServer.h @@ -0,0 +1,11 @@ +#ifndef GFRACESERVER_H +#define GFRACESERVER_H + +#include "RaceImaginationServer.h" + +class GfRaceServer : public RaceImaginationServer { +public: + void OnStartup(Entity* self) override; +}; + +#endif //!GFRACESERVER_H diff --git a/resources/masterconfig.ini b/resources/masterconfig.ini index ab04af99..1f14cf8d 100644 --- a/resources/masterconfig.ini +++ b/resources/masterconfig.ini @@ -7,15 +7,5 @@ master_server_port=2000 # The port number to start world servers on. Will be incremented for each world world_port_start=3000 -# Use sudo when launching the auth server. -# Required by default if on Linux as auth runs on port 1001 -use_sudo_auth=1 - -# Use sudo when launching the chat server -use_sudo_chat=0 - -# Use sudo when launching world servers -use_sudo_world=0 - # 0 or 1, should autostart auth, chat, and char servers prestart_servers=1