Merge branch 'main' into feature/improve-ci

This commit is contained in:
Jett 2022-01-22 09:22:26 +00:00 committed by GitHub
commit c25a8004be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 898 additions and 311 deletions

10
.dockerignore Normal file
View File

@ -0,0 +1,10 @@
.git
Dockerfile
*.md
logo.png
versions.txt
build.sh
docker-compose.yml
.env
docker/__pycache__
.env.example

17
.env.example Normal file
View File

@ -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

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.sh eol=lf

View File

@ -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

6
.gitignore vendored
View File

@ -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

9
.gitmodules vendored
View File

@ -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

View File

@ -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.

View File

@ -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
},

38
Docker.md Normal file
View File

@ -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://<EXTERNAL_IP>: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`

58
Docker_Windows.md Normal file
View File

@ -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$\<your linux OS>\...` 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)

View File

@ -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**

View File

@ -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;
}

View File

@ -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,

View File

@ -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

View File

@ -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);

View File

@ -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,7 +574,7 @@ 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;
@ -575,10 +582,7 @@ void Character::SetCoins(int64_t newCoins, const bool message) {
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

View File

@ -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

View File

@ -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)
{

View File

@ -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;
}
shirtStyle--; // to start at 0 instead of 1
uint32_t stylesCount = 34;
uint32_t colorId = GetShirtColorId(shirtColor);
case 1: {
shirtID = shirtStyle >= 35 ? 5736 : SHIRT_BRIGHT_BLUE;
break;
}
uint32_t startID = 4049; // item ID of the shirt with color 0 (red) and style 0 (plain)
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,
// 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;
}
//cout << "Shirt ID is: " << shirtIDFinal << endl;
// Get the final ID of the shirt
uint32_t shirtID = startID + (colorId * stylesCount) + shirtStyle;
return shirtIDFinal;
return shirtID;
}
uint32_t FindCharPantsID(uint32_t pantsColor) {

View File

@ -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<uint32_t> 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

View File

@ -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;
}

View File

@ -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)) {

View File

@ -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);

View File

@ -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,

View File

@ -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);

View File

@ -185,6 +185,7 @@ std::vector<ItemSetPassiveAbility> 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;

View File

@ -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) {

View File

@ -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) {

View File

@ -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);

View File

@ -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.
@ -1961,6 +1961,7 @@ bool SlashCommandHandler::CheckIfAccessibleZone(const unsigned int zoneID) {
case 1603:
case 1604:
case 1700:
case 1800:
case 1900:
case 2000:

View File

@ -5,6 +5,7 @@
#include <map>
#include <string>
#include <thread>
#include <fstream>
#ifdef _WIN32
#include <bcrypt/BCrypt.hpp>
@ -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;
}
//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;

View File

@ -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;
}
}

View File

@ -14,7 +14,7 @@ void ActSharkPlayerDeathTrigger::OnFireEventServerSide(Entity *self, Entity *sen
MissionComponent* mis = static_cast<MissionComponent*>(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

View File

@ -368,16 +368,7 @@ void BaseWavesServer::GameOver(Entity *self, bool won) {
// Update all mission progression
auto* missionComponent = player->GetComponent<MissionComponent>();
if (missionComponent != nullptr) {
missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_MINIGAME, time, self->GetObjectID(),
self->GetVar<std::string>(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<std::string>(MissionTypeVariable));
}
StopActivity(self, playerID, wave, time, score);
@ -511,19 +502,34 @@ bool BaseWavesServer::UpdateSpawnedEnemies(Entity* self, LWOOBJID enemyID, uint3
auto* missionComponent = player->GetComponent<MissionComponent>();
if (missionComponent != nullptr) {
for (const auto& missionID : waveMission) {
// 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);
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());
}
@ -532,6 +538,7 @@ bool BaseWavesServer::UpdateSpawnedEnemies(Entity* self, LWOOBJID enemyID, uint3
}
}
}
}
// Might seem odd to send the next wave but the client isn't 0-indexed so it thinks it completed the correct wave
self->SetNetworkVar<uint32_t>(WaveCompleteVariable, { state.waveNumber, (uint32_t) currentTime });
@ -548,15 +555,23 @@ void BaseWavesServer::UpdateMissionForAllPlayers(Entity* self, uint32_t missionI
auto* player = EntityManager::Instance()->GetEntity(playerID);
if (player != nullptr) {
auto* missionComponent = player->GetComponent<MissionComponent>();
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);
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());
}
}
}
}
}
void BaseWavesServer::ClearSpawners() {
for (const auto& spawnerName : spawners) {

View File

@ -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();

View File

@ -65,7 +65,7 @@ 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<DestroyableComponent>();

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}

View File

@ -42,14 +42,14 @@ std::vector<std::string> ZoneNsWaves::GetSpawnerNames() {
std::vector<WaveMission> 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<Wave> ZoneNsWaves::GetWaves() {
5.0f,
(uint32_t) -1,
true,
5,
30,
},
};
}

View File

@ -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<CharacterComponent>()->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)
{

103
docker-compose.yml Normal file
View File

@ -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:

View File

@ -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

59
docker/Dockerfile Normal file
View File

@ -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" ]

View File

@ -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

View File

@ -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"]

View File

@ -0,0 +1,3 @@
FROM mariadb:10.6
COPY ./migrations/dlu /docker-entrypoint-initdb.d

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

BIN
docker/images/Open_Powershell.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docker/images/WSL_2_download.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docker/images/setup_finished.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -0,0 +1,3 @@
LOGO = 'logo/logo.png'
PRIVACY_POLICY = 'policy/Privacy Policy.pdf'
TERMS_OF_USE = 'policy/Terms of Use.pdf'

23
docker/setup.Dockerfile Normal file
View File

@ -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" ]

95
docker/setup.sh Executable file
View File

@ -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

49
docker/start_server.sh Executable file
View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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

1
thirdparty/AccountManager vendored Submodule

@ -0,0 +1 @@
Subproject commit 19e2a4fe9faba8ec2e40162e0019acd711967744

1
thirdparty/LUnpack vendored Submodule

@ -0,0 +1 @@
Subproject commit f8d7e442a78910b298fe1cd5780f07c9c9285b8c

1
thirdparty/docker-utils vendored Submodule

@ -0,0 +1 @@
Subproject commit 3f0129e0939ce5ccf41f0808dcbbe71a6243e37f