diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..7da6456c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.git +Dockerfile +*.md +logo.png +versions.txt +build.sh +docker-compose.yml +.env +docker/__pycache__ +.env.example \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..15385906 --- /dev/null +++ b/.env.example @@ -0,0 +1,17 @@ +# Full path to the LEGO Universe client +CLIENT_PATH=/Users/someuser/LEGO Universe +# Can improve build time +BUILD_THREADS=1 +# Updates NET_VERSION in CMakeVariables.txt +BUILD_VERSION=171022 +# make sure this is a long random string +# grab a "SHA 256-bit Key" from here: https://keygen.io/ +ACCOUNT_MANAGER_SECRET= +# Should be the externally facing IP of your server host +EXTERNAL_IP=localhost +# Database values +# Be careful with special characters here. It is more safe to use normal characters and/or numbers. +MARIADB_USER=darkflame +MARIADB_PASSWORD=SECRET_VALUE_CHANGE_ME +MARIADB_ROOT_PASSWORD=SECRET_VALUE_CHANGE_ME +MARIADB_DATABASE=darkflame \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..8218efdb --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh eol=lf \ No newline at end of file diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 5cd60785..0e7785d9 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -1,4 +1,4 @@ -name: Cmake CI +name: CI on: push: @@ -8,28 +8,46 @@ on: jobs: build-and-test: - - runs-on: ubuntu-latest + name: Build & Test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ windows-2022, ubuntu-20.04 ] steps: - uses: actions/checkout@v2 with: submodules: true + - name: Add msbuild to PATH (windows only) + if: ${{ matrix.os == 'windows-2022' }} + uses: microsoft/setup-msbuild@v1.1 + with: + vs-version: '[17,18)' + msbuild-architecture: x64 - name: cmake uses: lukka/run-cmake@v10 with: - configurePreset: 'default' - buildPreset: 'default' - testPreset: 'default' + configurePreset: "ci-${{matrix.os}}" + buildPreset: "ci-${{matrix.os}}" + testPreset: "ci-${{matrix.os}}" - name: artifacts uses: actions/upload-artifact@v2 if: ${{ github.ref == 'ref/head/main' }} with: - name: linux-build + name: build-${{matrix.os}} path: | build + !build/tests + !build/Testing + !build/CMakeFiles + !build/DartConfiguration.tcl + !build/CTestTestfile.cmake !build/CMakeCache.txt !build/build.ninja !build/_deps !build/cmake_install.cmake !build/*.a + !build/*.lib + !build/*.dir + !build/*.vcxproj + !build/*.vcxproj.filters diff --git a/.gitignore b/.gitignore index 3e0873e5..28f8f297 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ temp/ cmake-build-debug/ RelWithDebInfo/ +docker/configs # Third party libraries thirdparty/mysql/ @@ -114,3 +115,8 @@ CMakeFiles/TargetDirectories.txt # clangd .cache thirdparty/zlib-1.2.11/ + +.env +docker/__pycache__ + +docker-compose.override.yml \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 64976fd4..086ee95c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,12 @@ [submodule "thirdparty/libbcrypt"] path = thirdparty/libbcrypt url = https://github.com/trusch/libbcrypt.git +[submodule "thirdparty/docker-utils"] + path = thirdparty/docker-utils + url = https://github.com/lcdr/utils.git +[submodule "thirdparty/LUnpack"] + path = thirdparty/LUnpack + url = https://github.com/Xiphoseer/LUnpack.git +[submodule "thirdparty/AccountManager"] + path = thirdparty/AccountManager + url = https://github.com/DarkflameUniverse/AccountManager diff --git a/CMakeLists.txt b/CMakeLists.txt index 43b2afb2..bd5753dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.14) project(Darkflame) include(CTest) @@ -331,6 +331,11 @@ RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" ${PROJECT_SOURCE_DIR}/dPhysics/*.cpp ) +if(MSVC) + # Skip warning for invalid conversion from size_t to uint32_t for all targets below for now + add_compile_options("/wd4267") +endif(MSVC) + # Our static libraries: add_library(dCommon ${SOURCES_DCOMMON}) add_library(dChatFilter ${SOURCES_DCHATFILTER}) @@ -344,6 +349,10 @@ target_link_libraries(dNet dCommon) #Needed because otherwise linker errors occu target_link_libraries(dCommon ZLIB::ZLIB) target_link_libraries(dCommon libbcrypt) +if(WIN32) +target_link_libraries(raknet ws2_32) +endif(WIN32) + # Our executables: add_executable(WorldServer ${SOURCES}) add_executable(AuthServer ${SOURCES_AUTH}) @@ -371,9 +380,6 @@ target_link_libraries(ChatServer backtrace) endif() endif(UNIX) -if(WIN32) -target_link_libraries(WorldServer ws2_32) -endif(WIN32) target_link_libraries(WorldServer tinyxml2) # Target libraries for Auth: @@ -382,9 +388,10 @@ target_link_libraries(AuthServer dDatabase) target_link_libraries(AuthServer dNet) target_link_libraries(AuthServer raknet) target_link_libraries(AuthServer ${MYSQL_LIB}) -if(WIN32) -target_link_libraries(AuthServer ws2_32) -endif(WIN32) +if(UNIX) +target_link_libraries(AuthServer pthread) +target_link_libraries(AuthServer dl) +endif(UNIX) # Target libraries for Master: target_link_libraries(MasterServer dCommon) @@ -392,9 +399,10 @@ target_link_libraries(MasterServer dDatabase) target_link_libraries(MasterServer dNet) target_link_libraries(MasterServer raknet) target_link_libraries(MasterServer ${MYSQL_LIB}) -if(WIN32) -target_link_libraries(MasterServer ws2_32) -endif(WIN32) +if(UNIX) +target_link_libraries(MasterServer pthread) +target_link_libraries(MasterServer dl) +endif(UNIX) # Target libraries for Chat: target_link_libraries(ChatServer dCommon) @@ -403,9 +411,10 @@ target_link_libraries(ChatServer dDatabase) target_link_libraries(ChatServer dNet) target_link_libraries(ChatServer raknet) target_link_libraries(ChatServer ${MYSQL_LIB}) -if(WIN32) -target_link_libraries(ChatServer ws2_32) -endif(WIN32) +if(UNIX) +target_link_libraries(ChatServer pthread) +target_link_libraries(ChatServer dl) +endif(UNIX) # Compiler flags: # Disabled deprecated warnings as the MySQL includes have deprecated code in them. diff --git a/CMakePresets.json b/CMakePresets.json index 77d41205..241220e0 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -2,29 +2,45 @@ "version": 3, "cmakeMinimumRequired": { "major": 3, - "minor": 12, + "minor": 14, "patch": 0 }, "configurePresets": [ { "name": "default", - "displayName": "Default Configure Settings", - "description": "Sets build and install directories", + "displayName": "Default configure step", + "description": "Use 'build' dir and Unix makefiles", "binaryDir": "${sourceDir}/build", "generator": "Unix Makefiles" }, + { + "name": "ci-ubuntu-20.04", + "displayName": "CI configure step for Ubuntu", + "description": "Same as default, Used in GitHub actions workflow", + "inherits": "default" + }, + { + "name": "ci-windows-2022", + "displayName": "CI configure step for Windows", + "description": "Set architecture to 64-bit (b/c RakNet)", + "inherits": "default", + "generator": "Visual Studio 17 2022", + "architecture": { + "value": "x64" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + }, { "name": "windows-default", + "inherits": "ci-windows-2022", "displayName": "Windows only Configure Settings", "description": "Sets build and install directories", - "binaryDir": "${sourceDir}/build", "generator": "Ninja", "architecture": { "value": "x64", "strategy": "external" - }, - "cacheVariables": { - "CMAKE_BUILD_TYPE": "RelWithDebInfo" } } ], @@ -35,14 +51,42 @@ "displayName": "Default Build", "description": "Default Build", "jobs": 2 + }, + { + "name": "ci-windows-2022", + "configurePreset": "ci-windows-2022", + "displayName": "Windows CI Build", + "description": "This preset is used by the CI build on windows", + "configuration": "RelWithDebInfo", + "jobs": 2 + }, + { + "name": "ci-ubuntu-20.04", + "configurePreset": "ci-ubuntu-20.04", + "displayName": "Linux CI Build", + "description": "This preset is used by the CI build on linux", + "jobs": 2 } ], "testPresets": [ { - "name": "default", - "configurePreset": "default", - "displayName": "Default Tests", - "description": "Runs all tests", + "name": "ci-ubuntu-20.04", + "configurePreset": "ci-ubuntu-20.04", + "displayName": "CI Tests on Linux", + "description": "Runs all tests on a linux configuration", + "execution": { + "jobs": 2 + }, + "output": { + "outputOnFailure": true + } + }, + { + "name": "ci-windows-2022", + "configurePreset": "ci-windows-2022", + "displayName": "CI Tests on windows", + "description": "Runs all tests on a windows configuration", + "configuration": "RelWithDebInfo", "execution": { "jobs": 2 }, diff --git a/Docker.md b/Docker.md new file mode 100644 index 00000000..ba52c39c --- /dev/null +++ b/Docker.md @@ -0,0 +1,38 @@ +# Run the Darkflame Server inside Docker + +## What you need + +- [Docker](https://docs.docker.com/get-docker/) (Docker Desktop or on Linux normal Docker) +- [Docker Compose](https://docs.docker.com/compose/install/) (Included in Docker Desktop) +- LEGO® Universe Client (packed or unpacked). Check the main [README](./README.md) for details on this. + +## Run server inside Docker + +1. Copy `.env.example` and save it as `.env` inside the root directory of this repository +2. Edit the `.env` file and add your path to the root directory of your LEGO® Universe Client after `CLIENT_PATH=` +3. Update other values in the `.env` file as needed (be sure to update passwords!) +4. Run `docker compose up -d --build` +5. Run `docker compose exec darkflame /app/MasterServer -a` and setup your admin account +6. Follow the directions [here](https://github.com/DarkflameUniverse/AccountManager) to setup regular user accounts. The server will be accessible at: `http://:5000` +7. Now you can see the output of the server with `docker compose logs -f --tail 100` or `docker compose logs -f --tail 100`. This can help you understand issues and there you can also see when the server finishes it's startup. +8. You're ready to connect your client! + +**NOTE #1**: If you're running an older version of Docker, you may need to use the command `docker-compose` instead of `docker compose`. + +**NOTE #2**: To stop the server simply run `docker compose down` and to restart it just run `docker compose up -d` again. No need to run all the steps above every time. + +## Disable brickbuildfix + +If you don't need the http server running on port 80 do this: + +1. Create a file with the name `docker-compose.override.yml` in the root of the repository +2. Paste this content: + +```yml +services: + brickbuildfix: + profiles: + - donotstart +``` + +3. Now run `docker compose up -d` diff --git a/Docker_Windows.md b/Docker_Windows.md new file mode 100644 index 00000000..1cc633cc --- /dev/null +++ b/Docker_Windows.md @@ -0,0 +1,58 @@ +# Installation under Windows +## First Run +1. Navigate to the [Docker download page](https://www.docker.com/products/docker-desktop) and download docker. + + ![Docker Download Page](docker/images/Docker_Download_Page.png) + +2. Once the file has finished downloading, run it and proceed through the installation. Make sure, "Install required Windows components for WSL 2" is checked. + + ![Docker Desktop Installer Configuration](docker/images/Docker_Desktop_Installer_Configuration.png) + +3. If necessary, restart your computer. +4. After the restart, Docker Desktop will automatically open. If it does not, simply start it like any other program. +5. If a window "WSL 2 Installation is incomplete." pops up, follow the link and click "WSL2 Linux kernel update package for x64 machines". Run the downloaded file and once that finishes, click "Restart" in the Docker Desktop window. + + ![WSL 2 download](docker/images/WSL_2_download.png) + +6. Wait until Docker Desktop has started. You may skip the tutorial. +7. You may want to disable "Open Docker Dashboard at startup" in _Settings_ -> _General_ + + ![Disable Dashboard Autostart](docker/images/DD_General_Settings.png) + +8. Install [Git for Windows](https://git-scm.com/download/win). During the installation, simply confirming the defaults is sufficient. +9. In the folder you wish to save the Server, right click and select "Git Bash Here". +10. Type `git clone --recursive https://github.com/DarkflameUniverse/DarkflameServer` +11. Once the command has completed (you can see you path again and can enter commands), close the window. +12. Inside the downloaded folder, copy `.env.example` and name the copy `.env` +13. Open `.env` with Notepad by right-clicking it and selecting _Open With_ -> _More apps_ -> _Notepad_. +14. Change the text after `CLIENT_PATH=` to the location of your client. The folder you are pointing to must contain a folder called `client` which should contain the client files. + > If you need the extra performance, place the client files in `\\wsl$\\...` to avoid working across file systems, see [Docker Best Practices](https://docs.docker.com/desktop/windows/wsl/#best-practices) and [WSL documentation](https://docs.microsoft.com/en-us/windows/wsl/filesystems#file-storage-and-performance-across-file-systems). + +15. Optionally, you can change the number after `BUILD_THREADS=` to the number of cores / threads your processor has. If your computer crashes while building, you can try to reduce this value. +16. After `ACCOUNT_MANAGER_SECRET=` paste a "SHA 256-bit Key" from https://keygen.io/ +17. If you are not only hosting a local server, change the value after `EXTERNAL_IP=` to the external IP address of your computer. +18. Change the two values `SECRET_VALUE_CHANGE_ME` to passwords only you know. Save and close the file. +19. In the extracted folder hit Shift+Right Click and select "Open PowerShell window here". + + ![Open PowerShell](docker/images/Open_Powershell.png) + +17. In the new window, paste (with right click) or type `docker compose up -d --build` and confirm with enter. +18. Once you see the blinking cursor and the path again, setup has finished and the server is already running. + + ![setup done](docker/images/setup_finished.png) + +19. Create an admin account by pasting `docker compose exec darkflame /app/MasterServer -a` and following the prompts. + + ![admin account creation](docker/images/Account_Creation.png) + +20. You can now login with these credentials at `http://your_ip:5000` (replace your_ip with your external IP). There you can create your account for playing as well as generate keys for other people to join; use these at `http://your_ip:5000/activate` + +## Normal Use +1. In Docker Desktop you should now see an entry `darkflameserver-main` and when you click on it all containers but `DarkflameSetup` should eventually be green. That means the server is running. + + ![server running](docker/images/Docker_Compose_Finished.png) + +2. For troubleshooting, you can check the logs of the various parts by clicking their entry. +3. You can start and stop the server with the corresponding buttons. Once all containers are grey, the server has shut down, and when all containers but `DarkflameSetup` are green, the server is running. Note that starting and stopping takes some time, please be patient. + + ![start stop buttons](docker/images/DD_Server_Startstop.png) diff --git a/README.md b/README.md index aee2ae84..47f116db 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ If you're using a DLU client you'll have to go into the "CMakeVariables.txt" fil ### Linux builds Make sure packages like `gcc`, `cmake`, and `zlib` are installed. Depending on the distribution, these packages might already be installed. Note that on systems like Ubuntu, you will need the `zlib1g-dev` package so that the header files are available. -cmake must be version 3.12 or higher! +CMake must be version 3.14 or higher! **Build the repository** diff --git a/dChatServer/ChatPacketHandler.cpp b/dChatServer/ChatPacketHandler.cpp index 737bbba4..899fd355 100644 --- a/dChatServer/ChatPacketHandler.cpp +++ b/dChatServer/ChatPacketHandler.cpp @@ -354,7 +354,7 @@ void ChatPacketHandler::HandleTeamInvite(Packet* packet) if (team->memberIDs.size() > 3) { // no more teams greater than 4 - Game::logger->Log("ChatPacketHandler", "Someone tried to invite a 5th player to a team"); + Game::logger->Log("ChatPacketHandler", "Someone tried to invite a 5th player to a team\n"); return; } diff --git a/dCommon/dCommonVars.h b/dCommon/dCommonVars.h index 77570282..4920249d 100644 --- a/dCommon/dCommonVars.h +++ b/dCommon/dCommonVars.h @@ -469,6 +469,31 @@ enum eRebuildState : uint32_t { REBUILD_INCOMPLETE }; +enum eLootSourceType : int32_t { + LOOT_SOURCE_NONE = 0, + LOOT_SOURCE_CHEST, + LOOT_SOURCE_MISSION, + LOOT_SOURCE_MAIL, + LOOT_SOURCE_CURRENCY, + LOOT_SOURCE_ACHIEVEMENT, + LOOT_SOURCE_TRADE, + LOOT_SOURCE_QUICKBUILD, + LOOT_SOURCE_DELETION, + LOOT_SOURCE_VENDOR, + LOOT_SOURCE_ACTIVITY, + LOOT_SOURCE_PICKUP, + LOOT_SOURCE_BRICK, + LOOT_SOURCE_PROPERTY, + LOOT_SOURCE_MODERATION, + LOOT_SOURCE_EXHIBIT, + LOOT_SOURCE_INVENTORY, + LOOT_SOURCE_CLAIMCODE, + LOOT_SOURCE_CONSUMPTION, + LOOT_SOURCE_CRAFTING, + LOOT_SOURCE_LEVELREWARD, + LOOT_SOURCE_RELOCATE +}; + enum eGameActivities : uint32_t { ACTIVITY_NONE, ACTIVITY_QUICKBUILDING, diff --git a/dDatabase/Database.cpp b/dDatabase/Database.cpp index 23fed182..cdadbbaa 100644 --- a/dDatabase/Database.cpp +++ b/dDatabase/Database.cpp @@ -28,7 +28,7 @@ void Database::Connect(const string& host, const string& database, const string& void Database::Destroy() { if (!con) return; - cout << "Destroying MySQL connection!" << endl; + Game::logger->Log("Database", "Destroying MySQL connection!\n"); con->close(); delete con; } //Destroy diff --git a/dDatabase/Tables/CDMissionsTable.cpp b/dDatabase/Tables/CDMissionsTable.cpp index 9e351094..58243bd8 100644 --- a/dDatabase/Tables/CDMissionsTable.cpp +++ b/dDatabase/Tables/CDMissionsTable.cpp @@ -58,7 +58,7 @@ CDMissionsTable::CDMissionsTable(void) { entry.reward_item2_repeatable = tableData.getIntField(32, -1); entry.reward_item2_repeat_count = tableData.getIntField(33, -1); entry.reward_item3_repeatable = tableData.getIntField(34, -1); - entry.reward_item3_repeat_count = tableData.getIntField(34, -1); + entry.reward_item3_repeat_count = tableData.getIntField(35, -1); entry.reward_item4_repeatable = tableData.getIntField(36, -1); entry.reward_item4_repeat_count = tableData.getIntField(37, -1); entry.time_limit = tableData.getIntField(38, -1); diff --git a/dGame/Character.cpp b/dGame/Character.cpp index 267e4577..d396382c 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -451,6 +451,13 @@ bool Character::GetPlayerFlag(const uint32_t flagId) const { return false; //by def, return false. } +void Character::SetRetroactiveFlags() { + // Retroactive check for if player has joined a faction to set their 'joined a faction' flag to true. + if (GetPlayerFlag(ePlayerFlags::VENTURE_FACTION) || GetPlayerFlag(ePlayerFlags::ASSEMBLY_FACTION) || GetPlayerFlag(ePlayerFlags::PARADOX_FACTION) || GetPlayerFlag(ePlayerFlags::SENTINEL_FACTION)) { + SetPlayerFlag(ePlayerFlags::JOINED_A_FACTION, true); + } +} + void Character::SaveXmlRespawnCheckpoints() { //Export our respawn points: @@ -527,7 +534,7 @@ void Character::OnZoneLoad() */ if (HasPermission(PermissionMap::Old)) { if (GetCoins() > 1000000) { - SetCoins(1000000); + SetCoins(1000000, LOOT_SOURCE_NONE); } } @@ -567,18 +574,15 @@ const NiPoint3& Character::GetRespawnPoint(LWOMAPID map) const return pair->second; } -void Character::SetCoins(int64_t newCoins, const bool message) { +void Character::SetCoins(int64_t newCoins, eLootSourceType lootSource) { if (newCoins < 0) { newCoins = 0; } m_Coins = newCoins; - - if (message) - { - GameMessages::SendSetCurrency(EntityManager::Instance()->GetEntity(m_ObjectID), m_Coins, 0, 0, 0, 0, true); - } + + GameMessages::SendSetCurrency(EntityManager::Instance()->GetEntity(m_ObjectID), m_Coins, 0, 0, 0, 0, true, lootSource); } bool Character::HasBeenToWorld(LWOMAPID mapID) const diff --git a/dGame/Character.h b/dGame/Character.h index 57da8640..9678eb3d 100644 --- a/dGame/Character.h +++ b/dGame/Character.h @@ -309,12 +309,11 @@ public: const int64_t GetCoins() const { return m_Coins; } /** - * Updates the current amount of coins of the character by a specified amount, for achievements this is not sent - * as it's tracked by the client + * Updates the current amount of coins of the character by a specified amount * @param newCoins the amount of coins to update by - * @param message whether to notify the client of the change + * @param coinSource The source of the loot */ - void SetCoins(int64_t newCoins, bool message = true); + void SetCoins(int64_t newCoins, eLootSourceType coinSource); /** * Get the entity this character belongs to @@ -414,6 +413,12 @@ public: */ void SendMuteNotice() const; + /** + * Sets any flags that are meant to have been set that may not have been set due to them being + * missing in a previous patch. + */ + void SetRetroactiveFlags(); + /** * Get the equipped items for this character, only used for character creation * @return the equipped items for this character on world load diff --git a/dGame/TradingManager.cpp b/dGame/TradingManager.cpp index 20af95e0..d0ec08d8 100644 --- a/dGame/TradingManager.cpp +++ b/dGame/TradingManager.cpp @@ -151,8 +151,8 @@ void Trade::Complete() if (inventoryA == nullptr || inventoryB == nullptr || characterA == nullptr || characterB == nullptr || missionsA == nullptr || missionsB == nullptr) return; - characterA->SetCoins(characterA->GetCoins() - m_CoinsA + m_CoinsB); - characterB->SetCoins(characterB->GetCoins() - m_CoinsB + m_CoinsA); + characterA->SetCoins(characterA->GetCoins() - m_CoinsA + m_CoinsB, LOOT_SOURCE_TRADE); + characterB->SetCoins(characterB->GetCoins() - m_CoinsB + m_CoinsA, LOOT_SOURCE_TRADE); for (const auto& tradeItem : m_ItemsA) { diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index c01c5687..1535364c 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -552,7 +552,7 @@ void UserManager::LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID if (zoneID == LWOZONEID_INVALID) zoneID = 1000; //Send char to VE ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, zoneID, character->GetZoneClone(), false, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { - Game::logger->Log("UserManager", "Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i\n", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); + Game::logger->Log("UserManager", "Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i\n", character->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); if (character) { character->SetZoneID(zoneID); character->SetZoneInstance(zoneInstance); @@ -566,114 +566,34 @@ void UserManager::LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID } } +uint32_t GetShirtColorId(uint32_t color) { + + // get the index of the color in shirtColorVector + auto colorId = std::find(shirtColorVector.begin(), shirtColorVector.end(), color); + return color = std::distance(shirtColorVector.begin(), colorId); +} + uint32_t FindCharShirtID(uint32_t shirtColor, uint32_t shirtStyle) { - uint32_t shirtID = 0; - - // s p e c i a l code follows - switch (shirtColor) { - case 0: { - shirtID = shirtStyle >= 35 ? 5730 : SHIRT_BRIGHT_RED; - break; - } - - case 1: { - shirtID = shirtStyle >= 35 ? 5736 : SHIRT_BRIGHT_BLUE; - break; - } - - case 3: { - shirtID = shirtStyle >= 35 ? 5808 : SHIRT_DARK_GREEN; - break; - } - - case 5: { - shirtID = shirtStyle >= 35 ? 5754 : SHIRT_BRIGHT_ORANGE; - break; - } - - case 6: { - shirtID = shirtStyle >= 35 ? 5760 : SHIRT_BLACK; - break; - } - - case 7: { - shirtID = shirtStyle >= 35 ? 5766 : SHIRT_DARK_STONE_GRAY; - break; - } - - case 8: { - shirtID = shirtStyle >= 35 ? 5772 : SHIRT_MEDIUM_STONE_GRAY; - break; - } - - case 9: { - shirtID = shirtStyle >= 35 ? 5778 : SHIRT_REDDISH_BROWN; - break; - } - - case 10: { - shirtID = shirtStyle >= 35 ? 5784 : SHIRT_WHITE; - break; - } - - case 11: { - shirtID = shirtStyle >= 35 ? 5802 : SHIRT_MEDIUM_BLUE; - break; - } - - case 13: { - shirtID = shirtStyle >= 35 ? 5796 : SHIRT_DARK_RED; - break; - } - - case 14: { - shirtID = shirtStyle >= 35 ? 5802 : SHIRT_EARTH_BLUE; - break; - } - - case 15: { - shirtID = shirtStyle >= 35 ? 5808 : SHIRT_EARTH_GREEN; - break; - } - - case 16: { - shirtID = shirtStyle >= 35 ? 5814 : SHIRT_BRICK_YELLOW; - break; - } - - case 84: { - shirtID = shirtStyle >= 35 ? 5820 : SHIRT_SAND_BLUE; - break; - } - - case 96: { - shirtID = shirtStyle >= 35 ? 5826 : SHIRT_SAND_GREEN; - shirtColor = 16; - break; - } - } - - // Initialize another variable for the shirt color - uint32_t editedShirtColor = shirtID; - - // This will be the final shirt ID - uint32_t shirtIDFinal; - - // For some reason, if the shirt color is 35 - 40, + + shirtStyle--; // to start at 0 instead of 1 + uint32_t stylesCount = 34; + uint32_t colorId = GetShirtColorId(shirtColor); + + uint32_t startID = 4049; // item ID of the shirt with color 0 (red) and style 0 (plain) + + // For some reason, if the shirt style is 34 - 39, // The ID is different than the original... Was this because // these shirts were added later? - if (shirtStyle >= 35) { - shirtIDFinal = editedShirtColor += (shirtStyle - 35); - } - else { - // Get the final ID of the shirt by adding the shirt - // style to the editedShirtColor - shirtIDFinal = editedShirtColor += (shirtStyle - 1); + if (shirtStyle >= 34) { + startID = 5730; // item ID of the shirt with color 0 (red) and style 34 (butterflies) + shirtStyle -= stylesCount; //change style from range 35-40 to range 0-5 + stylesCount = 6; } + + // Get the final ID of the shirt + uint32_t shirtID = startID + (colorId * stylesCount) + shirtStyle; - //cout << "Shirt ID is: " << shirtIDFinal << endl; - - return shirtIDFinal; + return shirtID; } uint32_t FindCharPantsID(uint32_t pantsColor) { diff --git a/dGame/UserManager.h b/dGame/UserManager.h index f014b183..b29cf501 100644 --- a/dGame/UserManager.h +++ b/dGame/UserManager.h @@ -33,10 +33,10 @@ public: bool IsNamePreapproved(const std::string& requestedName); void RequestCharacterList(const SystemAddress& sysAddr); - void CreateCharacter(const SystemAddress& sysAddr, Packet* packet); - void DeleteCharacter(const SystemAddress& sysAddr, Packet* packet); - void RenameCharacter(const SystemAddress& sysAddr, Packet* packet); - void LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID); + void CreateCharacter(const SystemAddress& sysAddr, Packet* packet); + void DeleteCharacter(const SystemAddress& sysAddr, Packet* packet); + void RenameCharacter(const SystemAddress& sysAddr, Packet* packet); + void LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID); void SaveAllActiveCharacters(); @@ -48,9 +48,9 @@ private: std::map m_Users; std::vector m_UsersToDelete; - std::vector m_FirstNames; - std::vector m_MiddleNames; - std::vector m_LastNames; + std::vector m_FirstNames; + std::vector m_MiddleNames; + std::vector m_LastNames; std::vector m_PreapprovedNames; }; @@ -73,24 +73,24 @@ enum CharCreatePantsColor : uint32_t { PANTS_DARK_RED = 2527 }; -enum CharCreateShirtColor : uint32_t { - SHIRT_BRIGHT_RED = 4049, - SHIRT_BRIGHT_BLUE = 4083, - SHIRT_BRIGHT_YELLOW = 4117, - SHIRT_DARK_GREEN = 4151, - SHIRT_BRIGHT_ORANGE = 4185, - SHIRT_BLACK = 4219, - SHIRT_DARK_STONE_GRAY = 4253, - SHIRT_MEDIUM_STONE_GRAY = 4287, - SHIRT_REDDISH_BROWN = 4321, - SHIRT_WHITE = 4355, - SHIRT_MEDIUM_BLUE = 4389, - SHIRT_DARK_RED = 4423, - SHIRT_EARTH_BLUE = 4457, - SHIRT_EARTH_GREEN = 4491, - SHIRT_BRICK_YELLOW = 4525, - SHIRT_SAND_BLUE = 4559, - SHIRT_SAND_GREEN = 4593 +const std::vector shirtColorVector { + 0, // BRIGHT_RED + 1, // BRIGHT_BLUE + 2, // BRIGHT_YELLOW + 3, // DARK_GREEN + 5, // BRIGHT_ORANGE + 6, // BLACK + 7, // DARK_STONE_GRAY + 8, // MEDIUM_STONE_GRAY + 9, // REDDISH_BROWN + 10, // WHITE + 11, // MEDIUM_BLUE + 13, // DARK_RED + 14, // EARTH_BLUE + 15, // EARTH_GREEN + 16, // BRICK_YELLOW + 84, // SAND_BLUE + 96 // SAND_GREEN }; #endif // USERMANAGER_H diff --git a/dGame/dBehaviors/VerifyBehavior.cpp b/dGame/dBehaviors/VerifyBehavior.cpp index 71f75a18..f4edfece 100644 --- a/dGame/dBehaviors/VerifyBehavior.cpp +++ b/dGame/dBehaviors/VerifyBehavior.cpp @@ -23,7 +23,7 @@ void VerifyBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitS if (self == nullptr) { - Game::logger->Log("VerifyBehavior", "Invalid self for (%llu)", context->originator); + Game::logger->Log("VerifyBehavior", "Invalid self for (%llu)\n", context->originator); return; } diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 01beaa5f..fb4dfe61 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -825,7 +825,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType LootGenerator::Instance().DropLoot(m_Parent, m_Parent, -1, coinsToLoose, coinsToLoose); } - character->SetCoins(coinsTotal); + character->SetCoins(coinsTotal, LOOT_SOURCE_PICKUP); Entity* zoneControl = EntityManager::Instance()->GetZoneControlEntity(); for (CppScripts::Script* script : CppScripts::GetEntityScripts(zoneControl)) { diff --git a/dGame/dComponents/ScriptedActivityComponent.cpp b/dGame/dComponents/ScriptedActivityComponent.cpp index dba5714d..55add0b6 100644 --- a/dGame/dComponents/ScriptedActivityComponent.cpp +++ b/dGame/dComponents/ScriptedActivityComponent.cpp @@ -513,7 +513,7 @@ void ActivityInstance::StartZone() { if (player == nullptr) return; - Game::logger->Log("UserManager", "Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i\n", player->GetSystemAddress().ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); + Game::logger->Log("UserManager", "Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i\n", player->GetCharacter()->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); if (player->GetCharacter()) { player->GetCharacter()->SetZoneID(zoneID); player->GetCharacter()->SetZoneInstance(zoneInstance); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index fad5d7de..0ade588e 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -706,7 +706,7 @@ void GameMessages::SendBroadcastTextToChatbox(Entity* entity, const SystemAddres SEND_PACKET_BROADCAST } -void GameMessages::SendSetCurrency(Entity* entity, int64_t currency, int lootType, const LWOOBJID& sourceID, const LOT& sourceLOT, int sourceTradeID, bool overrideCurrent) { +void GameMessages::SendSetCurrency(Entity* entity, int64_t currency, int lootType, const LWOOBJID& sourceID, const LOT& sourceLOT, int sourceTradeID, bool overrideCurrent, eLootSourceType sourceType) { CBITSTREAM CMSGHEADER @@ -729,7 +729,6 @@ void GameMessages::SendSetCurrency(Entity* entity, int64_t currency, int lootTyp bitStream.Write(sourceTradeID != LWOOBJID_EMPTY); if (sourceTradeID != LWOOBJID_EMPTY) bitStream.Write(sourceTradeID); - int sourceType = 0; //For now. bitStream.Write(sourceType != LOOTTYPE_NONE); if (sourceType != LOOTTYPE_NONE) bitStream.Write(sourceType); @@ -4685,7 +4684,7 @@ void GameMessages::HandleBuyFromVendor(RakNet::BitStream* inStream, Entity* enti inv->RemoveItem(itemComp.currencyLOT, altCurrencyCost); } - character->SetCoins(character->GetCoins() - (coinCost)); + character->SetCoins(character->GetCoins() - (coinCost), LOOT_SOURCE_VENDOR); inv->AddItem(item, count); } @@ -4734,7 +4733,7 @@ void GameMessages::HandleSellToVendor(RakNet::BitStream* inStream, Entity* entit //inv->RemoveItem(count, -1, iObjID); inv->MoveItemToInventory(item, VENDOR_BUYBACK, count, true, false, true); - character->SetCoins(std::floor(character->GetCoins() + ((itemComp.baseValue * sellScalar)*count))); + character->SetCoins(std::floor(character->GetCoins() + ((itemComp.baseValue * sellScalar)*count)), LOOT_SOURCE_VENDOR); //EntityManager::Instance()->SerializeEntity(player); // so inventory updates GameMessages::SendVendorTransactionResult(entity, sysAddr); } @@ -4796,7 +4795,7 @@ void GameMessages::HandleBuybackFromVendor(RakNet::BitStream* inStream, Entity* //inv->RemoveItem(count, -1, iObjID); inv->MoveItemToInventory(item, Inventory::FindInventoryTypeForLot(item->GetLot()), count, true, false); - character->SetCoins(character->GetCoins() - cost); + character->SetCoins(character->GetCoins() - cost, LOOT_SOURCE_VENDOR); //EntityManager::Instance()->SerializeEntity(player); // so inventory updates GameMessages::SendVendorTransactionResult(entity, sysAddr); } @@ -4899,7 +4898,7 @@ void GameMessages::HandleFireEventServerSide(RakNet::BitStream* inStream, Entity } ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, mapId, cloneId, false, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { - Game::logger->Log("UserManager", "Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i\n", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); + Game::logger->Log("UserManager", "Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i\n", character->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); if (character) { character->SetZoneID(zoneID); @@ -5240,7 +5239,7 @@ void GameMessages::HandlePickupCurrency(RakNet::BitStream* inStream, Entity* ent auto* ch = entity->GetCharacter(); if (entity->CanPickupCoins(currency)) { - ch->SetCoins(ch->GetCoins() + currency); + ch->SetCoins(ch->GetCoins() + currency, LOOT_SOURCE_PICKUP); } } @@ -5673,7 +5672,7 @@ void GameMessages::HandleDoneArrangingWithItem(RakNet::BitStream* inStream, Enti return; } - Game::logger->Log("GameMessages", "Build area found: %llu", buildArea->GetObjectID()); + Game::logger->Log("GameMessages", "Build area found: %llu\n", buildArea->GetObjectID()); GameMessages::SendStartArrangingWithItem( character, diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index e11f502a..0bc51c01 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -78,7 +78,7 @@ namespace GameMessages { void SendPlayFXEffect(const LWOOBJID& entity, int32_t effectID, const std::u16string& effectType, const std::string& name, LWOOBJID secondary = LWOOBJID_EMPTY, float priority = 1, float scale = 1, bool serialize = true); void SendStopFXEffect(Entity* entity, bool killImmediate, std::string name); void SendBroadcastTextToChatbox(Entity* entity, const SystemAddress& sysAddr, const std::u16string& attrs, const std::u16string& wsText); - void SendSetCurrency(Entity* entity, int64_t currency, int lootType, const LWOOBJID& sourceID, const LOT& sourceLOT, int sourceTradeID, bool overrideCurrent); + void SendSetCurrency(Entity* entity, int64_t currency, int lootType, const LWOOBJID& sourceID, const LOT& sourceLOT, int sourceTradeID, bool overrideCurrent, eLootSourceType sourceType); void SendRebuildNotifyState(Entity* entity, int prevState, int state, const LWOOBJID& playerID); void SendEnableRebuild(Entity* entity, bool enable, bool fail, bool success, int failReason, float duration, const LWOOBJID& playerID); diff --git a/dGame/dInventory/ItemSetPassiveAbility.cpp b/dGame/dInventory/ItemSetPassiveAbility.cpp index 11f142c6..9edb671b 100644 --- a/dGame/dInventory/ItemSetPassiveAbility.cpp +++ b/dGame/dInventory/ItemSetPassiveAbility.cpp @@ -185,6 +185,7 @@ std::vector ItemSetPassiveAbility::FindAbilities(uint32_t break; } // Paradox + case ItemSetPassiveAbilityID::BatLord: case ItemSetPassiveAbilityID::SpaceMarauderRank1: case ItemSetPassiveAbilityID::SpaceMarauderRank2: case ItemSetPassiveAbilityID::SpaceMarauderRank3: @@ -223,6 +224,12 @@ void ItemSetPassiveAbility::OnEnemySmshed() switch (id) { + // Bat Lord + case ItemSetPassiveAbilityID::BatLord: { + if(equippedCount < 5) return; + destroyableComponent->Heal(3); + break; + } // Sentinel case ItemSetPassiveAbilityID::KnightRank1: { if (equippedCount < 5) return; diff --git a/dGame/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index 7f5104e6..f2f06e58 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -438,13 +438,16 @@ void Mission::YieldRewards() { items.emplace_back(info->reward_item4_repeatable, info->reward_item4_repeat_count); for (const auto& pair : items) { - if (pair.second <= 0 || (m_Reward > 0 && pair.first != m_Reward)) { + // Some missions reward zero of an item and so they must be allowed through this clause, + // hence pair.second < 0 instead of pair.second <= 0. + if (pair.second < 0 || (m_Reward > 0 && pair.first != m_Reward)) { continue; } + // If a mission rewards zero of an item, make it reward 1. auto count = pair.second > 0 ? pair.second : 1; - // Sanitfy check, 6 is the max any mission yields + // Sanity check, 6 is the max any mission yields if (count > 6) { count = 0; } @@ -453,7 +456,7 @@ void Mission::YieldRewards() { } if (info->reward_currency_repeatable > 0) { - character->SetCoins(character->GetCoins() + info->reward_currency_repeatable); + character->SetCoins(character->GetCoins() + info->reward_currency_repeatable, LOOT_SOURCE_MISSION); } return; @@ -467,13 +470,16 @@ void Mission::YieldRewards() { items.emplace_back(info->reward_item4, info->reward_item4_count); for (const auto& pair : items) { + // Some missions reward zero of an item and so they must be allowed through this clause, + // hence pair.second < 0 instead of pair.second <= 0. if (pair.second < 0 || (m_Reward > 0 && pair.first != m_Reward)) { continue; } - + + // If a mission rewards zero of an item, make it reward 1. auto count = pair.second > 0 ? pair.second : 1; - // Sanitfy check, 6 is the max any mission yields + // Sanity check, 6 is the max any mission yields if (count > 6) { count = 0; } @@ -482,7 +488,8 @@ void Mission::YieldRewards() { } if (info->reward_currency > 0) { - character->SetCoins(character->GetCoins() + info->reward_currency, info->isMission); + eLootSourceType lootSource = info->isMission ? LOOT_SOURCE_MISSION : LOOT_SOURCE_ACHIEVEMENT; + character->SetCoins(character->GetCoins() + info->reward_currency, lootSource); } if (info->reward_maxinventory > 0) { diff --git a/dGame/dUtilities/Loot.cpp b/dGame/dUtilities/Loot.cpp index 4ec056a5..635b2620 100644 --- a/dGame/dUtilities/Loot.cpp +++ b/dGame/dUtilities/Loot.cpp @@ -320,7 +320,7 @@ void LootGenerator::GiveActivityLoot(Entity* player, Entity* source, uint32_t ac auto* character = player->GetCharacter(); - character->SetCoins(character->GetCoins() + coins); + character->SetCoins(character->GetCoins() + coins, LOOT_SOURCE_ACTIVITY); } void LootGenerator::DropLoot(Entity* player, Entity* killedObject, uint32_t matrixIndex, uint32_t minCoins, uint32_t maxCoins) { diff --git a/dGame/dUtilities/Mail.cpp b/dGame/dUtilities/Mail.cpp index 2c3baf8c..302ba1cd 100644 --- a/dGame/dUtilities/Mail.cpp +++ b/dGame/dUtilities/Mail.cpp @@ -262,7 +262,7 @@ void Mail::HandleSendMail(RakNet::BitStream* packet, const SystemAddress& sysAdd } Mail::SendSendResponse(sysAddr, Mail::MailSendResponse::Success); - entity->GetCharacter()->SetCoins(entity->GetCharacter()->GetCoins() - mailCost); + entity->GetCharacter()->SetCoins(entity->GetCharacter()->GetCoins() - mailCost, LOOT_SOURCE_MAIL); Game::logger->Log("Mail", "Seeing if we need to remove item with ID/count/LOT: %i %i %i\n", itemID, attachmentCount, itemLOT); diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 47704d2b..0217554e 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -340,7 +340,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit const auto sysAddr = entity->GetSystemAddress(); - Game::logger->Log("UserManager", "Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i\n", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); + Game::logger->Log("UserManager", "Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i\n", entity->GetCharacter()->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); if (entity->GetCharacter()) { entity->GetCharacter()->SetZoneID(zoneID); @@ -1334,7 +1334,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } auto* ch = entity->GetCharacter(); - ch->SetCoins(ch->GetCoins() + money); + ch->SetCoins(ch->GetCoins() + money, LOOT_SOURCE_MODERATION); } if ((chatCommand == "setcurrency") && args.size() == 1 && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { @@ -1347,7 +1347,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } auto* ch = entity->GetCharacter(); - ch->SetCoins(money); + ch->SetCoins(money, LOOT_SOURCE_MODERATION); } // Allow for this on even while not a GM, as it sometimes toggles incorrrectly. @@ -1928,53 +1928,54 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } bool SlashCommandHandler::CheckIfAccessibleZone(const unsigned int zoneID) { - switch (zoneID) { - case 98: - case 1000: - case 1001: - - case 1100: - case 1101: - case 1150: - case 1151: - case 1152: - - case 1200: - case 1201: + switch (zoneID) { + case 98: + case 1000: + case 1001: - case 1250: - case 1251: - case 1260: - - case 1300: - case 1350: - case 1351: - - case 1400: - case 1401: - case 1450: - case 1451: - - case 1600: - case 1601: - case 1602: - case 1603: - case 1604: - - case 1800: - case 1900: - case 2000: + case 1100: + case 1101: + case 1150: + case 1151: + case 1152: - case 58004: - case 58005: - case 58006: - return true; - - default: - return false; - } - - return false; + case 1200: + case 1201: + + case 1250: + case 1251: + case 1260: + + case 1300: + case 1350: + case 1351: + + case 1400: + case 1401: + case 1450: + case 1451: + + case 1600: + case 1601: + case 1602: + case 1603: + case 1604: + + case 1700: + case 1800: + case 1900: + case 2000: + + case 58004: + case 58005: + case 58006: + return true; + + default: + return false; + } + + return false; } void SlashCommandHandler::SendAnnouncement(const std::string& title, const std::string& message) { diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index f16e791d..b6ccac63 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -77,9 +78,18 @@ int main(int argc, char** argv) { Game::logger->SetLogToConsole(bool(std::stoi(config.GetValue("log_to_console")))); Game::logger->SetLogDebugStatements(config.GetValue("log_debug_statements") == "1"); + //Check CDClient exists + const std::string cdclient_path = "./res/CDServer.sqlite"; + std::ifstream cdclient_fd(cdclient_path); + if (!cdclient_fd.good()) { + Game::logger->Log("WorldServer", "%s could not be opened\n", cdclient_path.c_str()); + return -1; + } + cdclient_fd.close(); + //Connect to CDClient try { - CDClientDatabase::Connect("./res/CDServer.sqlite"); + CDClientDatabase::Connect(cdclient_path); } catch (CppSQLite3Exception& e) { Game::logger->Log("WorldServer", "Unable to connect to CDServer SQLite Database\n"); Game::logger->Log("WorldServer", "Error: %s\n", e.errorMessage()); @@ -87,7 +97,16 @@ int main(int argc, char** argv) { return -1; } - CDClientManager::Instance()->Initialize(); + //Get CDClient initial information + try { + CDClientManager::Instance()->Initialize(); + } catch (CppSQLite3Exception& e) { + Game::logger->Log("WorldServer", "Failed to initialize CDServer SQLite Database\n"); + Game::logger->Log("WorldServer", "May be caused by corrupted file: %s\n", cdclient_path.c_str()); + Game::logger->Log("WorldServer", "Error: %s\n", e.errorMessage()); + Game::logger->Log("WorldServer", "Error Code: %i\n", e.errorCode()); + return -1; + } //Connect to the MySQL Database std::string mysql_host = config.GetValue("mysql_host"); @@ -161,10 +180,16 @@ int main(int argc, char** argv) { auto* masterLookupStatement = Database::CreatePreppedStmt("SELECT id FROM `servers` WHERE `name` = 'master'"); auto* result = masterLookupStatement->executeQuery(); + auto master_server_ip = config.GetValue("master_ip"); + + if (master_server_ip == "") { + master_server_ip = Game::server->GetIP(); + } + //If we found a server, update it's IP and port to the current one. if (result->next()) { auto* updateStatement = Database::CreatePreppedStmt("UPDATE `servers` SET `ip` = ?, `port` = ? WHERE `id` = ?"); - updateStatement->setString(1, Game::server->GetIP()); + updateStatement->setString(1, master_server_ip); updateStatement->setInt(2, Game::server->GetPort()); updateStatement->setInt(3, result->getInt("id")); updateStatement->execute(); @@ -173,7 +198,7 @@ int main(int argc, char** argv) { else { //If we didn't find a server, create one. auto* insertStatement = Database::CreatePreppedStmt("INSERT INTO `servers` (`name`, `ip`, `port`, `state`, `version`) VALUES ('master', ?, ?, 0, 171023)"); - insertStatement->setString(1, Game::server->GetIP()); + insertStatement->setString(1, master_server_ip); insertStatement->setInt(2, Game::server->GetPort()); insertStatement->execute(); delete insertStatement; @@ -645,7 +670,7 @@ void HandlePacket(Packet* packet) { } case MSG_MASTER_SHUTDOWN_UNIVERSE: { - Game::logger->Log("MasterServer","Received shutdown universe command, ""shutting down in 10 minutes.\n"); + Game::logger->Log("MasterServer","Received shutdown universe command, shutting down in 10 minutes.\n"); shouldShutdown = true; break; } @@ -707,7 +732,7 @@ void ShutdownSequence() { auto* objIdManager = ObjectIDManager::TryInstance(); if (objIdManager != nullptr) { objIdManager->SaveToDatabase(); - printf("Saved objidtracker...\n"); + Game::logger->Log("MasterServer", "Saved ObjectIDTracker to DB\n"); } auto t = std::chrono::high_resolution_clock::now(); @@ -717,7 +742,8 @@ void ShutdownSequence() { exit(0); } - printf("Attempting to shutdown instances, max 60 seconds...\n"); + Game::logger->Log("MasterServer", "Attempting to shutdown instances, max 60 seconds...\n"); + while (true) { auto done = true; diff --git a/dNet/dServer.cpp b/dNet/dServer.cpp index df366955..27b6df7c 100644 --- a/dNet/dServer.cpp +++ b/dNet/dServer.cpp @@ -104,13 +104,13 @@ Packet* dServer::ReceiveFromMaster() { if (packet->length < 1) { mMasterPeer->DeallocatePacket(packet); return nullptr; } if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { - mLogger->Log("Server", "Lost our connection to master, shutting DOWN!\n"); + mLogger->Log("dServer", "Lost our connection to master, shutting DOWN!\n"); mMasterConnectionActive = false; //ConnectToMaster(); //We'll just shut down now } if (packet->data[0] == ID_CONNECTION_REQUEST_ACCEPTED) { - mLogger->Log("Server", "Established connection to master\n"); + mLogger->Log("dServer", "Established connection to master, zone (%i), instance (%i)\n",this->GetZoneID(), this->GetInstanceID()); mMasterConnectionActive = true; mMasterSystemAddress = packet->systemAddress; MasterPackets::SendServerInfo(this, packet); @@ -127,6 +127,7 @@ Packet* dServer::ReceiveFromMaster() { //When we handle these packets in World instead dServer, we just return the packet's pointer. default: + return packet; } } diff --git a/dScripts/ActSharkPlayerDeathTrigger.cpp b/dScripts/ActSharkPlayerDeathTrigger.cpp index 9df01d2d..4185496c 100644 --- a/dScripts/ActSharkPlayerDeathTrigger.cpp +++ b/dScripts/ActSharkPlayerDeathTrigger.cpp @@ -14,7 +14,7 @@ void ActSharkPlayerDeathTrigger::OnFireEventServerSide(Entity *self, Entity *sen MissionComponent* mis = static_cast(sender->GetComponent(COMPONENT_TYPE_MISSION)); if (!mis) return; - mis->Progress(MissionTaskType::MISSION_TASK_TYPE_SCRIPT, 4734); + mis->Progress(MissionTaskType::MISSION_TASK_TYPE_SCRIPT, 8419); if (sender->GetIsDead() || !sender->GetPlayerReadyForUpdates()) return; //Don't kill already dead players or players not ready diff --git a/dScripts/BaseWavesServer.cpp b/dScripts/BaseWavesServer.cpp index 3762aafc..ac8da8a7 100644 --- a/dScripts/BaseWavesServer.cpp +++ b/dScripts/BaseWavesServer.cpp @@ -368,16 +368,7 @@ void BaseWavesServer::GameOver(Entity *self, bool won) { // Update all mission progression auto* missionComponent = player->GetComponent(); if (missionComponent != nullptr) { - missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_MINIGAME, time, self->GetObjectID(), - self->GetVar(MissionTypeVariable)); - - auto* mission = missionComponent->GetMission(479); - if (mission->GetMissionState() > MissionState::MISSION_STATE_AVAILABLE - && mission->GetMissionState() < MissionState::MISSION_STATE_READY_TO_COMPLETE - && time >= 60) { - - mission->Progress(MissionTaskType::MISSION_TASK_TYPE_SCRIPT, self->GetObjectID()); - } + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_MINIGAME, time, self->GetObjectID(), self->GetVar(MissionTypeVariable)); } StopActivity(self, playerID, wave, time, score); @@ -511,24 +502,40 @@ bool BaseWavesServer::UpdateSpawnedEnemies(Entity* self, LWOOBJID enemyID, uint3 auto* missionComponent = player->GetComponent(); if (missionComponent != nullptr) { for (const auto& missionID : waveMission) { - missionComponent->AcceptMission(missionID); - auto* mission = missionComponent->GetMission(missionID); - - if (mission != nullptr) { - mission->Progress(MissionTaskType::MISSION_TASK_TYPE_SCRIPT, self->GetLOT()); - } - } - - if (state.players.size() == 1) { - for (const auto& missionID : soloWaveMissions) { + // Get the mission state + auto missionState = missionComponent->GetMissionState(missionID); + // For some reason these achievements are not accepted by default, so we accept them here if they arent already. + if (missionState != MissionState::MISSION_STATE_COMPLETE && missionState != MissionState::MISSION_STATE_UNKNOWN) { missionComponent->AcceptMission(missionID); - auto* mission = missionComponent->GetMission(missionID); + missionState = missionComponent->GetMissionState(missionID); + } + if (missionState != MissionState::MISSION_STATE_COMPLETE) { + auto mission = missionComponent->GetMission(missionID); if (mission != nullptr) { mission->Progress(MissionTaskType::MISSION_TASK_TYPE_SCRIPT, self->GetLOT()); } } } + // Progress solo missions + if (state.players.size() == 1) { + for (const auto& missionID : soloWaveMissions) { + // Get the mission state + auto missionState = missionComponent->GetMissionState(missionID); + // For some reason these achievements are not accepted by default, so we accept them here if they arent already. + if (missionState != MissionState::MISSION_STATE_COMPLETE && missionState != MissionState::MISSION_STATE_UNKNOWN) { + missionComponent->AcceptMission(missionID); + missionState = missionComponent->GetMissionState(missionID); + } + + if (missionState != MissionState::MISSION_STATE_COMPLETE) { + auto mission = missionComponent->GetMission(missionID); + if (mission != nullptr) { + mission->Progress(MissionTaskType::MISSION_TASK_TYPE_SCRIPT, self->GetLOT()); + } + } + } + } } } } @@ -548,11 +555,19 @@ void BaseWavesServer::UpdateMissionForAllPlayers(Entity* self, uint32_t missionI auto* player = EntityManager::Instance()->GetEntity(playerID); if (player != nullptr) { auto* missionComponent = player->GetComponent(); - missionComponent->AcceptMission(missionID); - - auto* mission = missionComponent->GetMission(missionID); - if (mission != nullptr) { - mission->Progress(MissionTaskType::MISSION_TASK_TYPE_SCRIPT, self->GetLOT()); + if (missionComponent == nullptr) return; + // Get the mission state + auto missionState = missionComponent->GetMissionState(missionID); + // For some reason these achievements are not accepted by default, so we accept them here if they arent already. + if (missionState != MissionState::MISSION_STATE_COMPLETE && missionState != MissionState::MISSION_STATE_UNKNOWN) { + missionComponent->AcceptMission(missionID); + missionState = missionComponent->GetMissionState(missionID); + } + if (missionState != MissionState::MISSION_STATE_COMPLETE) { + auto mission = missionComponent->GetMission(missionID); + if (mission != nullptr) { + mission->Progress(MissionTaskType::MISSION_TASK_TYPE_SCRIPT, self->GetLOT()); + } } } } diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 53cbf425..f6029076 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -681,7 +681,7 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr else if (scriptName == "scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_DRAGON.lua") script = new AmNamedDarklingDragon(); else if (scriptName == "scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_APE.lua") - script = new AmNamedDarklingDragon(); + script = new BaseEnemyApe(); else if (scriptName == "scripts\\02_server\\Map\\AM\\L_BLUE_X.lua") script = new AmBlueX(); diff --git a/dScripts/GfBanana.cpp b/dScripts/GfBanana.cpp index 919e54b4..65c434ba 100644 --- a/dScripts/GfBanana.cpp +++ b/dScripts/GfBanana.cpp @@ -64,8 +64,8 @@ void GfBanana::OnHit(Entity* self, Entity* attacker) return; } - - bananaEntity->SetPosition(bananaEntity->GetPosition() - NiPoint3::UNIT_Y * 5); + + bananaEntity->SetPosition(bananaEntity->GetPosition() - NiPoint3::UNIT_Y * 8); auto* bananaDestroyable = bananaEntity->GetComponent(); diff --git a/dScripts/ImgBrickConsoleQB.cpp b/dScripts/ImgBrickConsoleQB.cpp index b24cb9bf..4d2d1e96 100644 --- a/dScripts/ImgBrickConsoleQB.cpp +++ b/dScripts/ImgBrickConsoleQB.cpp @@ -96,7 +96,6 @@ void ImgBrickConsoleQB::OnUse(Entity* self, Entity* user) if (missionComponent->GetMissionState(1926) == MissionState::MISSION_STATE_ACTIVE) { inventoryComponent->RemoveItem(14472, 1); - inventoryComponent->AddItem(14472, 1); missionComponent->ForceProgressTaskType(1926, 1, 1); } diff --git a/dScripts/NpcWispServer.cpp b/dScripts/NpcWispServer.cpp index 8156bbaa..2f94236d 100644 --- a/dScripts/NpcWispServer.cpp +++ b/dScripts/NpcWispServer.cpp @@ -14,15 +14,15 @@ void NpcWispServer::OnMissionDialogueOK(Entity* self, Entity* target, int missio if (inventory == nullptr) return; - LOT maelstromCubeLot = 14592; - auto* maelstromCube = inventory->FindItemByLot(maelstromCubeLot); + LOT maelstromVacuumLot = 14592; + auto* maelstromVacuum = inventory->FindItemByLot(maelstromVacuumLot); - // For the daily we add the maelstrom cube if the player doesn't have it yet + // For the daily we add the maelstrom vacuum if the player doesn't have it yet if (missionID == 1883 && (missionState == MissionState::MISSION_STATE_AVAILABLE || missionState == MissionState::MISSION_STATE_COMPLETE_AVAILABLE) - && maelstromCube == nullptr) { - inventory->AddItem(maelstromCubeLot, 1); + && maelstromVacuum == nullptr) { + inventory->AddItem(maelstromVacuumLot, 1); } else if (missionState == MissionState::MISSION_STATE_READY_TO_COMPLETE || missionState == MissionState::MISSION_STATE_COMPLETE_READY_TO_COMPLETE) { - inventory->RemoveItem(maelstromCubeLot, 1); + inventory->RemoveItem(maelstromVacuumLot, 1); } // Next up hide or show the samples based on the mission state diff --git a/dScripts/NsGetFactionMissionServer.cpp b/dScripts/NsGetFactionMissionServer.cpp index 99f2f838..d4d03243 100644 --- a/dScripts/NsGetFactionMissionServer.cpp +++ b/dScripts/NsGetFactionMissionServer.cpp @@ -44,6 +44,7 @@ void NsGetFactionMissionServer::OnRespondToMission(Entity* self, int missionID, } if (flagID != -1) { + player->GetCharacter()->SetPlayerFlag(ePlayerFlags::JOINED_A_FACTION, true); player->GetCharacter()->SetPlayerFlag(flagID, true); } diff --git a/dScripts/ZoneNsWaves.cpp b/dScripts/ZoneNsWaves.cpp index 23158d1e..9f54ae49 100644 --- a/dScripts/ZoneNsWaves.cpp +++ b/dScripts/ZoneNsWaves.cpp @@ -42,14 +42,14 @@ std::vector ZoneNsWaves::GetSpawnerNames() { std::vector ZoneNsWaves::GetWaveMissions() { return { - {190, 6, 1242}, - {240, 6, 1226}, - {450, 14, 1243}, - {600, 14, 1227}, - {720, 21, 1244}, - {840, 21, 1228}, - {1080, 28, 1245}, - {1200, 28, 1229}, + {190, 7, 1242}, + {240, 7, 1226}, + {450, 15, 1243}, + {600, 15, 1227}, + {720, 22, 1244}, + {840, 22, 1228}, + {1080, 29, 1245}, + {1200, 29, 1229}, }; } @@ -439,7 +439,7 @@ std::vector ZoneNsWaves::GetWaves() { 5.0f, (uint32_t) -1, true, - 5, + 30, }, }; } diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 520907ff..53abfd7a 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -475,7 +475,7 @@ int main(int argc, char** argv) { if (framesSinceMasterStatus >= 200) { - Game::logger->Log("WorldServer", "Finished loading world, ready up!\n"); + Game::logger->Log("WorldServer", "Finished loading world with zone (%i), ready up!\n", Game::server->GetZoneID()); MasterPackets::SendWorldReady(Game::server, Game::server->GetZoneID(), Game::server->GetInstanceID()); @@ -546,7 +546,7 @@ int main(int argc, char** argv) { if (Game::physicsWorld) Game::physicsWorld = nullptr; if (Game::zoneManager) delete Game::zoneManager; - Game::logger->Log("Test", "Quitting\n"); + Game::logger->Log("WorldServer", "Shutdown complete, zone (%i), instance (%i)\n", Game::server->GetZoneID(), instanceID); Metrics::Clear(); Database::Destroy(); @@ -573,13 +573,13 @@ dLogger * SetupLogger(int zoneID, int instanceID) { void HandlePacketChat(Packet* packet) { if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { - Game::logger->Log("WorldServer", "Lost our connection to chat.\n"); - + Game::logger->Log("WorldServer", "Lost our connection to chat, zone(%i), instance(%i)\n", Game::server->GetZoneID(), Game::server->GetInstanceID()); + chatConnected = false; } if (packet->data[0] == ID_CONNECTION_REQUEST_ACCEPTED) { - Game::logger->Log("WorldServer", "Established connection to chat\n"); + Game::logger->Log("WorldServer", "Established connection to chat, zone(%i), instance (%i)\n",Game::server -> GetZoneID(), Game::server -> GetInstanceID()); Game::chatSysAddr = packet->systemAddress; chatConnected = true; @@ -863,7 +863,7 @@ void HandlePacket(Packet* packet) { case MSG_MASTER_SHUTDOWN: { worldShutdownSequenceStarted = true; - Game::logger->Log("WorldServer", "Got shutdown request\n"); + Game::logger->Log("WorldServer", "Got shutdown request from master, zone (%i), instance (%i)\n", Game::server->GetZoneID(), Game::server->GetInstanceID()); break; } @@ -1042,6 +1042,9 @@ void HandlePacket(Packet* packet) { EntityManager::Instance()->ConstructAllEntities(packet->systemAddress); player->GetComponent()->SetLastRocketConfig(u""); + + c->SetRetroactiveFlags(); + player->GetCharacter()->SetTargetScene(""); // Fix the destroyable component @@ -1186,11 +1189,11 @@ void HandlePacket(Packet* packet) { } } else { - Game::logger->Log("WorldMain", "Couldn't find character to log in with for user %s (%i)!\n", user->GetUsername().c_str(), user->GetAccountID()); + Game::logger->Log("WorldServer", "Couldn't find character to log in with for user %s (%i)!\n", user->GetUsername().c_str(), user->GetAccountID()); Game::server->Disconnect(packet->systemAddress, SERVER_DISCON_CHARACTER_NOT_FOUND); } } else { - Game::logger->Log("WorldMain", "Couldn't get user for level load complete!\n"); + Game::logger->Log("WorldServer", "Couldn't get user for level load complete!\n"); } break; } @@ -1296,7 +1299,7 @@ void WorldShutdownSequence() auto t = std::chrono::high_resolution_clock::now(); auto ticks = 0; - printf("Attempting to shutdown world, max 10 seconds...");; + Game::logger->Log("WorldServer", "Attempting to shutdown world, zone (%i), instance (%i), max 10 seconds...\n", Game::server->GetZoneID(), instanceID); while (true) { diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..db90bd21 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,103 @@ +version: "3" + +services: + setup: + container_name: DarkflameSetup + build: + context: . + dockerfile: ./docker/setup.Dockerfile + environment: + - DATABASE=${MARIADB_DATABASE:-darkflame} + - DATABASE_HOST=database + - DATABASE_USER=${MARIADB_USER:-darkflame} + - DATABASE_PASSWORD=${MARIADB_PASSWORD:-darkflame} + - EXTERNAL_IP=${EXTERNAL_IP:-darkflame} + volumes: + - ${CLIENT_PATH:?missing_client_path}:/client + - shared_configs:/docker/ + + database: + container_name: DarkflameDatabase + build: + context: . + dockerfile: ./docker/database.Dockerfile + environment: + - MARIADB_USER=${MARIADB_USER:-darkflame} + - MARIADB_PASSWORD=${MARIADB_PASSWORD:-darkflame} + - MARIADB_ROOT_PASSWORD=${MARIADB_ROOT_PASSWORD:-darkflame} + - MARIADB_DATABASE=${MARIADB_DATABASE:-darkflame} + volumes: + - database:/var/lib/mysql + networks: + - darkflame + # You can expose these so that DB management tools can connect (WARNING: INSECURE) + # ports: + # - 3306:3306 + + darkflame: + container_name: DarkflameServer + networks: + - darkflame + build: + context: . + dockerfile: ./docker/Dockerfile + args: + - BUILD_THREADS=${BUILD_THREADS:-1} + - BUILD_VERSION=${BUILD_VERSION:-171022} + volumes: + - ${CLIENT_PATH:?missing_client_path}:/client + - shared_configs:/shared_configs + depends_on: + - database + ports: + - "1001:1001/udp" + - "2000:2000/udp" + - "2005:2005/udp" + - "3000-3300:3000-3300/udp" + + brickbuildfix: + container_name: DarkflameBrickBuildFix + networks: + - darkflame + build: + context: . + dockerfile: ./docker/brickfix.Dockerfile + ports: + - 80:80 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:80"] + interval: 2m + timeout: 3s + retries: 3 + start_period: 40s + + account-manager: + container_name: DarkflameAccountManager + networks: + - darkflame + build: + context: . + dockerfile: ./docker/AccountManager.Dockerfile + environment: + - DATABASE=${MARIADB_DATABASE:-darkflame} + - DATABASE_HOST=database + - DATABASE_USER=${MARIADB_USER:-darkflame} + - DATABASE_PASSWORD=${MARIADB_PASSWORD:-darkflame} + - ACCOUNT_SECRET=${ACCOUNT_MANAGER_SECRET:?missing_account_secret} + ports: + - 5000:5000 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5000"] + interval: 2m + timeout: 3s + retries: 3 + start_period: 40s + depends_on: + - database + +networks: + darkflame: + +volumes: + database: + shared_configs: diff --git a/docker/AccountManager.Dockerfile b/docker/AccountManager.Dockerfile new file mode 100644 index 00000000..dd56e3b9 --- /dev/null +++ b/docker/AccountManager.Dockerfile @@ -0,0 +1,14 @@ +# syntax=docker/dockerfile:1 +FROM python:3.10-alpine3.14 + +WORKDIR /usr/local/share/AccountManager + +COPY ./thirdparty/AccountManager . + +ADD docker/credentials_example.py credentials.py +ADD docker/resources_example.py resources.py + +RUN apk add libffi-dev build-base --no-cache && pip3 install -r requirements.txt + +EXPOSE 5000 +CMD python3 app.py diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..98a9b93d --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,59 @@ +FROM gcc:11 as build + +WORKDIR /build + +RUN --mount=type=cache,id=build-apt-cache,target=/var/cache/apt \ + echo "Install build dependencies" && \ + apt update && \ + apt remove -y libmysqlcppconn7v5 libmysqlcppconn-dev && \ + apt install cmake zlib1g zlib1g-dev unzip -yqq --no-install-recommends && \ + rm -rf /var/lib/apt/lists/* + +COPY dAuthServer/ /build/dAuthServer +COPY dChatServer/ /build/dChatServer +COPY dCommon/ /build/dCommon +COPY dChatFilter/ /build/dChatFilter +COPY dDatabase/ /build/dDatabase +COPY dGame/ /build/dGame +COPY dMasterServer/ /build/dMasterServer +COPY dNet/ /build/dNet +COPY dPhysics/ /build/dPhysics +COPY dScripts/ /build/dScripts +COPY dWorldServer/ /build/dWorldServer +COPY dZoneManager/ /build/dZoneManager +COPY migrations/ /build/migrations +COPY resources/ /build/resources +COPY thirdparty/ /build/thirdparty +COPY vanity /build/vanity +COPY tests/ /build/tests +COPY .clang-* CMake* LICENSE /build/ + +ARG BUILD_THREADS=1 +ARG BUILD_VERSION=171022 + +RUN echo "Build server" && \ + mkdir -p cmake_build && \ + cd cmake_build && \ + sed -i -e "s/171022/${BUILD_VERSION}/g" ../CMakeVariables.txt && \ + cmake .. && \ + make -j $BUILD_THREADS + +RUN unzip /build/resources/navmeshes.zip -d /build/cmake_build/res/maps + +FROM gcc:11 as runtime + +RUN --mount=type=cache,id=runtime-apt-cache,target=/var/cache/apt \ + apt update && \ + apt install sudo -yqq --no-install-recommends && \ + apt remove -y libmysqlcppconn7v5 libmysqlcppconn-dev && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY --from=build /build/cmake_build /app + +RUN mkdir -p /build/cmake_build && ln -s /app/_deps /build/cmake_build/_deps + +COPY docker/start_server.sh /start_server.sh + +CMD [ "/start_server.sh" ] \ No newline at end of file diff --git a/docker/brickfix.Dockerfile b/docker/brickfix.Dockerfile new file mode 100644 index 00000000..20a9444a --- /dev/null +++ b/docker/brickfix.Dockerfile @@ -0,0 +1,5 @@ +# syntax=docker/dockerfile:1 +FROM python:3.9.9-slim +WORKDIR /empty_dir +EXPOSE 80 +CMD python -m http.server 80 diff --git a/docker/credentials_example.py b/docker/credentials_example.py new file mode 100644 index 00000000..2112fbed --- /dev/null +++ b/docker/credentials_example.py @@ -0,0 +1,4 @@ +import os + +DB_URL = f'mysql+pymysql://{os.environ["DATABASE_USER"]}:{os.environ["DATABASE_PASSWORD"]}@{os.environ["DATABASE_HOST"]}/{os.environ["DATABASE"]}' +SECRET_KEY = os.environ["ACCOUNT_SECRET"] diff --git a/docker/database.Dockerfile b/docker/database.Dockerfile new file mode 100644 index 00000000..68261e44 --- /dev/null +++ b/docker/database.Dockerfile @@ -0,0 +1,3 @@ +FROM mariadb:10.6 + +COPY ./migrations/dlu /docker-entrypoint-initdb.d \ No newline at end of file diff --git a/docker/images/Account_Creation.png b/docker/images/Account_Creation.png new file mode 100755 index 00000000..1b8135dd Binary files /dev/null and b/docker/images/Account_Creation.png differ diff --git a/docker/images/DD_General_Settings.png b/docker/images/DD_General_Settings.png new file mode 100755 index 00000000..d0e694aa Binary files /dev/null and b/docker/images/DD_General_Settings.png differ diff --git a/docker/images/DD_Server_Startstop.png b/docker/images/DD_Server_Startstop.png new file mode 100755 index 00000000..8f482bb7 Binary files /dev/null and b/docker/images/DD_Server_Startstop.png differ diff --git a/docker/images/Docker_Compose_Finished.png b/docker/images/Docker_Compose_Finished.png new file mode 100755 index 00000000..549915ee Binary files /dev/null and b/docker/images/Docker_Compose_Finished.png differ diff --git a/docker/images/Docker_Desktop_Installer_Configuration.png b/docker/images/Docker_Desktop_Installer_Configuration.png new file mode 100755 index 00000000..d8c07eaa Binary files /dev/null and b/docker/images/Docker_Desktop_Installer_Configuration.png differ diff --git a/docker/images/Docker_Download_Page.png b/docker/images/Docker_Download_Page.png new file mode 100755 index 00000000..4c478a66 Binary files /dev/null and b/docker/images/Docker_Download_Page.png differ diff --git a/docker/images/Open_Powershell.png b/docker/images/Open_Powershell.png new file mode 100755 index 00000000..861e609d Binary files /dev/null and b/docker/images/Open_Powershell.png differ diff --git a/docker/images/WSL_2_download.png b/docker/images/WSL_2_download.png new file mode 100755 index 00000000..e9be9a31 Binary files /dev/null and b/docker/images/WSL_2_download.png differ diff --git a/docker/images/setup_finished.png b/docker/images/setup_finished.png new file mode 100755 index 00000000..6278ac6b Binary files /dev/null and b/docker/images/setup_finished.png differ diff --git a/docker/resources_example.py b/docker/resources_example.py new file mode 100644 index 00000000..652281f5 --- /dev/null +++ b/docker/resources_example.py @@ -0,0 +1,3 @@ +LOGO = 'logo/logo.png' +PRIVACY_POLICY = 'policy/Privacy Policy.pdf' +TERMS_OF_USE = 'policy/Terms of Use.pdf' diff --git a/docker/setup.Dockerfile b/docker/setup.Dockerfile new file mode 100644 index 00000000..2664e2fa --- /dev/null +++ b/docker/setup.Dockerfile @@ -0,0 +1,23 @@ +FROM rust:alpine3.14 as LUnpack + +WORKDIR /build_LUnpack + +COPY ./thirdparty/LUnpack . + +RUN apk add musl-dev --no-cache && cargo build --release + +FROM python:3.10-alpine3.14 as prep + +RUN apk add sqlite bash --no-cache + +WORKDIR /setup + +# copy needed files from repo +COPY resources/ resources/ +COPY migrations/cdserver/ migrations/cdserver +COPY --from=LUnpack /build_LUnpack/target/release/lunpack /usr/local/bin/lunpack +ADD thirdparty/docker-utils/utils/*.py utils/ + +COPY docker/setup.sh /setup.sh + +CMD [ "/setup.sh" ] \ No newline at end of file diff --git a/docker/setup.sh b/docker/setup.sh new file mode 100755 index 00000000..1a95f4de --- /dev/null +++ b/docker/setup.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +# fail on first error +set -e + +function update_ini() { + FILE="/docker/configs/$1" + KEY=$2 + NEW_VALUE=$3 + sed -i "/^$KEY=/s/=.*/=$NEW_VALUE/" $FILE +} + +function update_database_ini_values_for() { + INI_FILE=$1 + + update_ini $INI_FILE mysql_host $DATABASE_HOST + update_ini $INI_FILE mysql_database $DATABASE + update_ini $INI_FILE mysql_username $DATABASE_USER + update_ini $INI_FILE mysql_password $DATABASE_PASSWORD + if [[ "$INI_FILE" != "worldconfig.ini" ]]; then + update_ini $INI_FILE external_ip $EXTERNAL_IP + fi +} + +function update_ini_values() { + echo "Copying and updating config files" + + mkdir -p /docker/configs + cp resources/masterconfig.ini /docker/configs/ + cp resources/authconfig.ini /docker/configs/ + cp resources/chatconfig.ini /docker/configs/ + cp resources/worldconfig.ini /docker/configs/ + + update_ini worldconfig.ini chat_server_port $CHAT_SERVER_PORT + update_ini worldconfig.ini max_clients $MAX_CLIENTS + + # always use the internal docker hostname + update_ini masterconfig.ini master_ip "darkflame" + + update_database_ini_values_for masterconfig.ini + update_database_ini_values_for authconfig.ini + update_database_ini_values_for chatconfig.ini + update_database_ini_values_for worldconfig.ini +} + +function fdb_to_sqlite() { + echo "Run fdb_to_sqlite" + python3 utils/fdb_to_sqlite.py /client/client/res/cdclient.fdb --sqlite_path /client/client/res/CDServer.sqlite + + ( + cd migrations/cdserver + readarray -d '' entries < <(printf '%s\0' *.sql | sort -zV) + for entry in "${entries[@]}"; do + echo "Execute $entry" + sqlite3 /client/client/res/CDServer.sqlite < $entry + done + ) +} + +update_ini_values + +if [[ ! -d "/client" ]]; then + echo "Client not found." + echo "Did you forget to mount the client into the \"/client\" directory?" + exit 1 +fi + +if [[ ! -f "/client/extracted" ]]; then + echo "Start client resource extraction" + + touch globs.txt + + echo "client/res/macros/**" >> globs.txt + echo "client/res/BrickModels/**" >> globs.txt + echo "client/res/maps/**" >> globs.txt + echo "*.fdb" >> globs.txt + + lunpack -g ./globs.txt /client/ + + touch /client/extracted +else + echo "Client already extracted. Skip this step..." + echo "If you want to force a re-extract, just delete the file called \"extracted\" in the client directory" +fi + +if [[ ! -f "/client/migrated" ]]; then + echo "Start client db migration" + + fdb_to_sqlite + + touch /client/migrated +else + echo "Client db already migrated. Skip this step..." + echo "If you want to force a re-migrate, just delete the file called \"migrated\" in the client directory" +fi diff --git a/docker/start_server.sh b/docker/start_server.sh new file mode 100755 index 00000000..feb61361 --- /dev/null +++ b/docker/start_server.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +function symlink_client_files() { + echo "Creating symlinks for client files" + ln -s /client/client/res/macros/ /app/res/macros + ln -s /client/client/res/BrickModels/ /app/res/BrickModels + ln -s /client/client/res/chatplus_en_us.txt /app/res/chatplus_en_us.txt + ln -s /client/client/res/names/ /app/res/names + ln -s /client/client/res/CDServer.sqlite /app/res/CDServer.sqlite + ln -s /client/client/locale/locale.xml /app/locale/locale.xml + # need to iterate over entries in maps due to maps already being a directory with navmeshes/ in it + ( + cd /client/client/res/maps + readarray -d '' entries < <(printf '%s\0' * | sort -zV) + for entry in "${entries[@]}"; do + ln -s /client/client/res/maps/$entry /app/res/maps/ + done + ) +} + +function symlink_config_files() { + echo "Creating symlinks for config files" + rm /app/*.ini + ln -s /shared_configs/configs/authconfig.ini /app/authconfig.ini + ln -s /shared_configs/configs/chatconfig.ini /app/chatconfig.ini + ln -s /shared_configs/configs/masterconfig.ini /app/masterconfig.ini + ln -s /shared_configs/configs/worldconfig.ini /app/worldconfig.ini +} + +# check to make sure the setup has completed +while [ ! -f "/client/extracted" ] || [ ! -f "/client/migrated" ]; do + echo "Client setup not finished. Waiting for setup container to complete..." + sleep 5 +done + +if [[ ! -f "/app/initialized" ]]; then + # setup symlinks for volume files + symlink_client_files + symlink_config_files + # do not run symlinks more than once + touch /app/initialized +else + echo "Server already initialized" +fi + +# start the server +echo "Starting MasterServer" +./MasterServer +tail -f /dev/null \ No newline at end of file diff --git a/resources/masterconfig.ini b/resources/masterconfig.ini index cedb7c3a..c2d884a5 100644 --- a/resources/masterconfig.ini +++ b/resources/masterconfig.ini @@ -7,6 +7,9 @@ mysql_password= # The public facing IP address. Can be 'localhost' for locally hosted servers external_ip=localhost +# The internal ip of the master server +master_ip=localhost + # Port number port=2000 diff --git a/tests/TestLDFFormat.cpp b/tests/TestLDFFormat.cpp index 276a8175..7ba31420 100644 --- a/tests/TestLDFFormat.cpp +++ b/tests/TestLDFFormat.cpp @@ -8,7 +8,7 @@ * @param argv Command line arguments * @return 0 on success, non-zero on failure */ -int TestLDFFormat(int argc, char** argv) { +int TestLDFFormat(int argc, char* *const argv) { // Create auto* data = LDFBaseData::DataFromString("KEY=0:VALUE"); diff --git a/tests/TestNiPoint3.cpp b/tests/TestNiPoint3.cpp index 68da4571..076b186d 100644 --- a/tests/TestNiPoint3.cpp +++ b/tests/TestNiPoint3.cpp @@ -3,7 +3,7 @@ #include "NiPoint3.h" #include "CommonCxxTests.h" -int TestNiPoint3(int argc, char** argv) { +int TestNiPoint3(int argc, char* *const argv) { // Check that Unitize works ASSERT_EQ(NiPoint3(3,0,0).Unitize(), NiPoint3::UNIT_X); // Check what unitize does to a vector of length 0 diff --git a/thirdparty/AccountManager b/thirdparty/AccountManager new file mode 160000 index 00000000..19e2a4fe --- /dev/null +++ b/thirdparty/AccountManager @@ -0,0 +1 @@ +Subproject commit 19e2a4fe9faba8ec2e40162e0019acd711967744 diff --git a/thirdparty/LUnpack b/thirdparty/LUnpack new file mode 160000 index 00000000..f8d7e442 --- /dev/null +++ b/thirdparty/LUnpack @@ -0,0 +1 @@ +Subproject commit f8d7e442a78910b298fe1cd5780f07c9c9285b8c diff --git a/thirdparty/docker-utils b/thirdparty/docker-utils new file mode 160000 index 00000000..3f0129e0 --- /dev/null +++ b/thirdparty/docker-utils @@ -0,0 +1 @@ +Subproject commit 3f0129e0939ce5ccf41f0808dcbbe71a6243e37f