Merge DarkflameServer

This commit is contained in:
TheMatt2 2022-01-30 13:40:08 -05:00
commit 59afc63a2f
77 changed files with 1019 additions and 331 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,27 +8,45 @@ 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
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)
@ -371,6 +371,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)
# 3rdparty static libraries:
#add_library(zlib ${SOURCES_ZLIB})
add_library(raknet ${SOURCES_RAKNET})
@ -391,6 +396,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})
@ -421,9 +430,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:
@ -436,9 +442,6 @@ if(UNIX)
target_link_libraries(AuthServer pthread)
target_link_libraries(AuthServer dl)
endif(UNIX)
if(WIN32)
target_link_libraries(AuthServer ws2_32)
endif(WIN32)
# Target libraries for Master:
target_link_libraries(MasterServer dCommon)
@ -450,9 +453,6 @@ if(UNIX)
target_link_libraries(MasterServer pthread)
target_link_libraries(MasterServer dl)
endif(UNIX)
if(WIN32)
target_link_libraries(MasterServer ws2_32)
endif(WIN32)
# Target libraries for Chat:
target_link_libraries(ChatServer dCommon)
@ -465,9 +465,6 @@ if(UNIX)
target_link_libraries(ChatServer pthread)
target_link_libraries(ChatServer dl)
endif(UNIX)
if(WIN32)
target_link_libraries(ChatServer ws2_32)
endif(WIN32)
# 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

@ -40,13 +40,13 @@ std::string CDBehaviorParameterTable::GetName(void) const {
return "BehaviorParameter";
}
float CDBehaviorParameterTable::GetEntry(const uint32_t behaviorID, const std::string& name)
float CDBehaviorParameterTable::GetEntry(const uint32_t behaviorID, const std::string& name, const float defaultValue)
{
size_t hash = 0;
GeneralUtils::hash_combine(hash, behaviorID);
GeneralUtils::hash_combine(hash, name);
// Search for specific perameter
// Search for specific parameter
const auto& it = m_Entries.find(hash);
if (it != m_Entries.end()) {
return it->second;
@ -55,7 +55,7 @@ float CDBehaviorParameterTable::GetEntry(const uint32_t behaviorID, const std::s
// Check if this behavior has already been checked
const auto& itChecked = m_Entries.find(behaviorID);
if (itChecked != m_Entries.end()) {
return itChecked->second;
return defaultValue;
}
#ifndef CDCLIENT_CACHE_ALL
@ -86,5 +86,5 @@ float CDBehaviorParameterTable::GetEntry(const uint32_t behaviorID, const std::s
}
#endif
return 0;
return defaultValue;
}

View File

@ -35,5 +35,5 @@ public:
*/
std::string GetName(void) const override;
float GetEntry(const uint32_t behaviorID, const std::string& name);
float GetEntry(const uint32_t behaviorID, const std::string& name, const float defaultValue = 0);
};

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:

View File

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

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

View File

@ -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<SystemAddress, User*> m_Users;
std::vector<User*> m_UsersToDelete;
std::vector<std::string> m_FirstNames;
std::vector<std::string> m_MiddleNames;
std::vector<std::string> m_LastNames;
std::vector<std::string> m_FirstNames;
std::vector<std::string> m_MiddleNames;
std::vector<std::string> m_LastNames;
std::vector<std::string> 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<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

@ -74,7 +74,8 @@ void AreaOfEffectBehavior::Calculate(BehaviorContext* context, RakNet::BitStream
includeFaction = 1;
}
for (auto validTarget : context->GetValidTargets(m_ignoreFaction , includeFaction, m_TargetSelf == 1))
// Gets all of the valid targets, passing in if should target enemies and friends
for (auto validTarget : context->GetValidTargets(m_ignoreFaction , includeFaction, m_TargetSelf == 1, m_targetEnemy == 1, m_targetFriend == 1))
{
auto* entity = EntityManager::Instance()->GetEntity(validTarget);
@ -156,5 +157,9 @@ void AreaOfEffectBehavior::Load()
this->m_includeFaction = GetInt("include_faction");
this->m_TargetSelf = GetInt("target_self");
this->m_TargetSelf = GetInt("target_self");
this->m_targetEnemy = GetInt("target_enemy");
this->m_targetFriend = GetInt("target_friend");
}

View File

@ -14,7 +14,11 @@ public:
int32_t m_includeFaction;
int32_t m_TargetSelf;
int32_t m_TargetSelf;
int32_t m_targetEnemy;
int32_t m_targetFriend;
/*
* Inherited

View File

@ -55,6 +55,7 @@
#include "SkillEventBehavior.h"
#include "SpeedBehavior.h"
#include "DamageReductionBehavior.h"
#include "JetPackBehavior.h"
//CDClient includes
#include "CDBehaviorParameterTable.h"
@ -183,7 +184,9 @@ Behavior* Behavior::CreateBehavior(const uint32_t behaviorId)
case BehaviorTemplates::BEHAVIOR_BUFF:
behavior = new BuffBehavior(behaviorId);
break;
case BehaviorTemplates::BEHAVIOR_JETPACK: break;
case BehaviorTemplates::BEHAVIOR_JETPACK:
behavior = new JetPackBehavior(behaviorId);
break;
case BehaviorTemplates::BEHAVIOR_SKILL_EVENT:
behavior = new SkillEventBehavior(behaviorId);
break;
@ -457,21 +460,21 @@ Behavior::Behavior(const uint32_t behaviorId)
}
float Behavior::GetFloat(const std::string& name) const
float Behavior::GetFloat(const std::string& name, const float defaultValue) const
{
return BehaviorParameterTable->GetEntry(this->m_behaviorId, name);
return BehaviorParameterTable->GetEntry(this->m_behaviorId, name, defaultValue);
}
bool Behavior::GetBoolean(const std::string& name) const
bool Behavior::GetBoolean(const std::string& name, const bool defaultValue) const
{
return GetFloat(name) > 0;
return GetFloat(name, defaultValue) > 0;
}
int32_t Behavior::GetInt(const std::string& name) const
int32_t Behavior::GetInt(const std::string& name, const int defaultValue) const
{
return static_cast<int32_t>(GetFloat(name));
return static_cast<int32_t>(GetFloat(name, defaultValue));
}

View File

@ -49,11 +49,11 @@ public:
* Behavior parameters
*/
float GetFloat(const std::string& name) const;
float GetFloat(const std::string& name, const float defaultValue = 0) const;
bool GetBoolean(const std::string& name) const;
bool GetBoolean(const std::string& name, const bool defaultValue = false) const;
int32_t GetInt(const std::string& name) const;
int32_t GetInt(const std::string& name, const int32_t defaultValue = 0) const;
Behavior* GetAction(const std::string& name) const;

View File

@ -325,7 +325,7 @@ void BehaviorContext::Reset()
this->scheduledUpdates.clear();
}
std::vector<LWOOBJID> BehaviorContext::GetValidTargets(int32_t ignoreFaction, int32_t includeFaction, bool targetSelf) const
std::vector<LWOOBJID> BehaviorContext::GetValidTargets(int32_t ignoreFaction, int32_t includeFaction, bool targetSelf, bool targetEnemy, bool targetFriend) const
{
auto* entity = EntityManager::Instance()->GetEntity(this->caster);
@ -366,7 +366,7 @@ std::vector<LWOOBJID> BehaviorContext::GetValidTargets(int32_t ignoreFaction, in
{
const auto id = candidate->GetObjectID();
if ((id != entity->GetObjectID() || targetSelf) && destroyableComponent->CheckValidity(id, ignoreFaction || includeFaction))
if ((id != entity->GetObjectID() || targetSelf) && destroyableComponent->CheckValidity(id, ignoreFaction || includeFaction, targetEnemy, targetFriend))
{
targets.push_back(id);
}

View File

@ -102,7 +102,7 @@ struct BehaviorContext
void Reset();
std::vector<LWOOBJID> GetValidTargets(int32_t ignoreFaction = 0, int32_t includeFaction = 0, const bool targetSelf = false) const;
std::vector<LWOOBJID> GetValidTargets(int32_t ignoreFaction = 0, int32_t includeFaction = 0, const bool targetSelf = false, const bool targetEnemy = true, const bool targetFriend = false) const;
explicit BehaviorContext(LWOOBJID originator, bool calculation = false);

View File

@ -0,0 +1,29 @@
#include "JetPackBehavior.h"
#include "BehaviorBranchContext.h"
#include "GameMessages.h"
void JetPackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bit_stream, const BehaviorBranchContext branch) {
auto* entity = EntityManager::Instance()->GetEntity(branch.target);
GameMessages::SendSetJetPackMode(entity, true, this->m_BypassChecks, this->m_EnableHover, this->m_effectId, this->m_Airspeed, this->m_MaxAirspeed, this->m_VerticalVelocity, this->m_WarningEffectID);
}
void JetPackBehavior::UnCast(BehaviorContext* context, BehaviorBranchContext branch) {
auto* entity = EntityManager::Instance()->GetEntity(branch.target);
GameMessages::SendSetJetPackMode(entity, false);
}
void JetPackBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bit_stream, const BehaviorBranchContext branch) {
Handle(context, bit_stream, branch);
}
void JetPackBehavior::Load() {
this->m_WarningEffectID = GetInt("warning_effect_id");
this->m_Airspeed = GetFloat("airspeed");
this->m_MaxAirspeed = GetFloat("max_airspeed");
this->m_VerticalVelocity = GetFloat("vertical_velocity");
this->m_EnableHover = GetBoolean("enable_hover");
this->m_BypassChecks = GetBoolean("bypass_checks", true);
}

View File

@ -0,0 +1,28 @@
#pragma once
#include "Behavior.h"
class JetPackBehavior final : public Behavior
{
public:
int32_t m_WarningEffectID;
float m_Airspeed;
float m_MaxAirspeed;
float m_VerticalVelocity;
bool m_EnableHover;
bool m_BypassChecks = true; // from what I can tell this defaulted true in live
/*
* Inherited
*/
explicit JetPackBehavior(const uint32_t behavior_id) : Behavior(behavior_id) {
}
void Handle(BehaviorContext* context, RakNet::BitStream* bit_stream, BehaviorBranchContext branch) override;
void UnCast(BehaviorContext* context, BehaviorBranchContext branch) override;
void Calculate(BehaviorContext* context, RakNet::BitStream* bit_stream, BehaviorBranchContext branch) override;
void Load() override;
};

View File

@ -498,29 +498,28 @@ Entity* DestroyableComponent::GetKiller() const
return EntityManager::Instance()->GetEntity(m_KillerID);
}
bool DestroyableComponent::CheckValidity(const LWOOBJID target, const bool ignoreFactions) const
bool DestroyableComponent::CheckValidity(const LWOOBJID target, const bool ignoreFactions, const bool targetEnemy, const bool targetFriend) const
{
auto* entity = EntityManager::Instance()->GetEntity(target);
auto* targetEntity = EntityManager::Instance()->GetEntity(target);
if (entity == nullptr)
if (targetEntity == nullptr)
{
Game::logger->Log("DestroyableComponent", "Invalid entity for checking validity (%llu)!\n", target);
return false;
}
auto* destroyable = entity->GetComponent<DestroyableComponent>();
auto* targetDestroyable = targetEntity->GetComponent<DestroyableComponent>();
if (destroyable == nullptr)
if (targetDestroyable == nullptr)
{
return false;
}
auto* quickbuild = entity->GetComponent<RebuildComponent>();
auto* targetQuickbuild = targetEntity->GetComponent<RebuildComponent>();
if (quickbuild != nullptr)
if (targetQuickbuild != nullptr)
{
const auto state = quickbuild->GetState();
const auto state = targetQuickbuild->GetState();
if (state != REBUILD_COMPLETED)
{
@ -533,19 +532,12 @@ bool DestroyableComponent::CheckValidity(const LWOOBJID target, const bool ignor
return true;
}
auto enemyList = GetEnemyFactionsIDs();
// Get if the target entity is an enemy and friend
bool isEnemy = IsEnemy(targetEntity);
bool isFriend = IsFriend(targetEntity);
auto candidateList = destroyable->GetFactionIDs();
for (auto value : candidateList)
{
if (std::find(enemyList.begin(), enemyList.end(), value) != enemyList.end())
{
return true;
}
}
return false;
// Return true if the target type matches what we are targeting
return (isEnemy && targetEnemy) || (isFriend && targetFriend);
}

View File

@ -371,7 +371,7 @@ public:
* @param ignoreFactions whether or not check for the factions, e.g. just return true if the entity cannot be smashed
* @return if the target ID is a valid enemy
*/
bool CheckValidity(LWOOBJID target, bool ignoreFactions = false) const;
bool CheckValidity(LWOOBJID target, bool ignoreFactions = false, bool targetEnemy = true, bool targetFriend = false) const;
/**
* Attempt to damage this entity, handles everything from health and armor to absorption, immunity and callbacks.

View File

@ -1001,10 +1001,6 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks)
set->OnEquip(lot);
}
if (lot == 1727) GameMessages::SendSetJetpackMode(m_Parent, false, true, false);
if (lot == 7292) GameMessages::SendSetJetpackMode(m_Parent, true, true, false);
if (lot == 14442) GameMessages::SendSetJetpackMode(m_Parent, false, true, true);
if (item->GetInfo().isBOE)
{
item->SetBound(true);
@ -1042,10 +1038,6 @@ void InventoryComponent::UnEquipItem(Item* item)
set->OnUnEquip(lot);
}
if (lot == 1727) GameMessages::SendSetJetpackMode(m_Parent, false, false, false);
if (lot == 7292) GameMessages::SendSetJetpackMode(m_Parent, true, false, false);
if (lot == 14442) GameMessages::SendSetJetpackMode(m_Parent, false, false, true);
RemoveBuff(item->GetLot());
RemoveItemSkills(item->GetLot());

View File

@ -257,7 +257,7 @@ void RacingControlComponent::LoadPlayerVehicle(Entity *player,
m_Parent->GetObjectID(), playerID, UNASSIGNED_SYSTEM_ADDRESS);
});
GameMessages::SendSetJetpackMode(player, false, false, false);
GameMessages::SendSetJetPackMode(player, false);
// Set the vehicle's state.
GameMessages::SendNotifyVehicleOfRacingObject(carEntity->GetObjectID(),

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

@ -105,9 +105,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System
auto items = inv->GetEquippedItems();
for (auto pair : items) {
const auto item = pair.second;
if (item.lot == 1727) GameMessages::SendSetJetpackMode(entity, false, true, false);
if (item.lot == 7292) GameMessages::SendSetJetpackMode(entity, true, true, false);
if (item.lot == 14442) GameMessages::SendSetJetpackMode(entity, false, true, true);
inv->AddItemSkills(item.lot);
}

View File

@ -872,19 +872,14 @@ void GameMessages::SendSetEmoteLockState(Entity* entity, bool bLock, int emoteID
SEND_PACKET
}
void GameMessages::SendSetJetpackMode(Entity* entity, bool bDoHover, bool bUse, bool bIsJamessterPhysics) {
int effectID = 167;
int iWarningEffectID = -1;
float fAirspeed = 25;
float fMaxAirspeed = 25;
float fVertVel = 2;
bool bBypassChecks = true;
void GameMessages::SendSetJetPackMode(Entity* entity, bool use, bool bypassChecks, bool doHover, int effectID, float airspeed, float maxAirspeed, float verticalVelocity, int warningEffectID) {
/* historical jamesster jetpack values
if (bIsJamessterPhysics) {
fAirspeed = 75;
fMaxAirspeed = 75;
fVertVel = 15;
}
*/
CBITSTREAM
CMSGHEADER
@ -892,24 +887,24 @@ void GameMessages::SendSetJetpackMode(Entity* entity, bool bDoHover, bool bUse,
bitStream.Write(entity->GetObjectID());
bitStream.Write(uint16_t(GAME_MSG_SET_JET_PACK_MODE));
bitStream.Write(bBypassChecks);
bitStream.Write(bDoHover);
bitStream.Write(bUse);
bitStream.Write(bypassChecks);
bitStream.Write(doHover);
bitStream.Write(use);
bitStream.Write(effectID != -1);
if (effectID != -1) bitStream.Write(effectID);
bitStream.Write(fAirspeed != 10);
if (fAirspeed != 10) bitStream.Write(fAirspeed);
bitStream.Write(airspeed != 10);
if (airspeed != 10) bitStream.Write(airspeed);
bitStream.Write(fMaxAirspeed != 15);
if (fMaxAirspeed != 15) bitStream.Write(fMaxAirspeed);
bitStream.Write(maxAirspeed != 15);
if (maxAirspeed != 15) bitStream.Write(maxAirspeed);
bitStream.Write(fVertVel != 1);
if (fVertVel != 1) bitStream.Write(fVertVel);
bitStream.Write(verticalVelocity != 1);
if (verticalVelocity != 1) bitStream.Write(verticalVelocity);
bitStream.Write(iWarningEffectID != -1);
if (iWarningEffectID != -1) bitStream.Write(iWarningEffectID);
bitStream.Write(warningEffectID != -1);
if (warningEffectID != -1) bitStream.Write(warningEffectID);
SEND_PACKET_BROADCAST
}
@ -4898,7 +4893,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);

View File

@ -91,7 +91,7 @@ namespace GameMessages {
void SendSetInventorySize(Entity* entity, int invType, int size);
void SendSetEmoteLockState(Entity* entity, bool bLock, int emoteID);
void SendSetJetpackMode(Entity* entity, bool bDoHover, bool bUse, bool bIsJamessterPhysics);
void SendSetJetPackMode(Entity* entity, bool use, bool bypassChecks = false, bool doHover = false, int effectID = -1, float airspeed = 10, float maxAirspeed = 15, float verticalVelocity = 1, int warningEffectID = -1);
void SendResurrect(Entity* entity);
void SendStop2DAmbientSound(Entity* entity, bool force, std::string audioGUID, bool result = false);
void SendPlay2DAmbientSound(Entity* entity, std::string audioGUID, bool result = false);

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,10 +438,13 @@ 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;
// Sanity check, 6 is the max any mission yields
@ -467,10 +470,13 @@ 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;
// Sanity check, 6 is the max any mission yields

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);
@ -675,6 +675,21 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
entity->GetCharacter()->SetPlayerFlag(flagId, true);
}
if (chatCommand == "setflag" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER && args.size() == 2)
{
uint32_t flagId;
std::string onOffFlag = args[0];
if (!GeneralUtils::TryParse(args[1], flagId))
{
ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id.");
return;
}
if (onOffFlag != "off" && onOffFlag != "on") {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag type.");
return;
}
entity->GetCharacter()->SetPlayerFlag(flagId, onOffFlag == "on");
}
if (chatCommand == "clearflag" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER && args.size() == 1)
{
uint32_t flagId;
@ -1938,53 +1953,54 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
}
bool SlashCommandHandler::CheckIfAccessibleZone(const unsigned int zoneID) {
switch (zoneID) {
case 98:
case 1000:
case 1001:
switch (zoneID) {
case 98:
case 1000:
case 1001:
case 1100:
case 1101:
case 1150:
case 1151:
case 1152:
case 1100:
case 1101:
case 1150:
case 1151:
case 1152:
case 1200:
case 1201:
case 1200:
case 1201:
case 1250:
case 1251:
case 1260:
case 1250:
case 1251:
case 1260:
case 1300:
case 1350:
case 1351:
case 1300:
case 1350:
case 1351:
case 1400:
case 1401:
case 1450:
case 1451:
case 1400:
case 1401:
case 1450:
case 1451:
case 1600:
case 1601:
case 1602:
case 1603:
case 1604:
case 1600:
case 1601:
case 1602:
case 1603:
case 1604:
case 1800:
case 1900:
case 2000:
case 1700:
case 1800:
case 1900:
case 2000:
case 58004:
case 58005:
case 58006:
return true;
case 58004:
case 58005:
case 58006:
return true;
default:
return false;
}
default:
return false;
}
return false;
return false;
}
void SlashCommandHandler::SendAnnouncement(const std::string& title, const std::string& message) {

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

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

@ -0,0 +1,15 @@
#include "AgSurvivalBuffStation.h"
#include "SkillComponent.h"
#include "dLogger.h"
void AgSurvivalBuffStation::OnRebuildComplete(Entity* self, Entity* target) {
auto skillComponent = self->GetComponent<SkillComponent>();
if (skillComponent == nullptr) return;
skillComponent->CalculateBehavior(201, 1784, self->GetObjectID());
self->AddCallbackTimer(10.0f, [self]() {
self->Smash();
});
}

View File

@ -0,0 +1,23 @@
#pragma once
#include "CppScripts.h"
class AgSurvivalBuffStation : public CppScripts::Script
{
public:
/**
* @brief When the rebuild of self is complete, we calculate the behavior that is assigned to self in the database.
*
* @param self The Entity that called this script.
* @param target The target of the self that called this script.
*/
void OnRebuildComplete(Entity* self, Entity* target) override;
private:
/**
* Skill ID for the buff station.
*/
uint32_t skillIdForBuffStation = 201;
/**
* Behavior ID for the buff station.
*/
uint32_t behaviorIdForBuffStation = 1784;
};

View File

@ -302,6 +302,7 @@ void BaseSurvivalServer::StartWaves(Entity *self) {
self->SetVar<bool>(FirstTimeDoneVariable, true);
self->SetVar<std::string>(MissionTypeVariable, state.players.size() == 1 ? "survival_time_solo" : "survival_time_team");
ActivateSpawnerNetwork(spawnerNetworks.rewardNetworks);
ActivateSpawnerNetwork(spawnerNetworks.smashNetworks);
self->SetNetworkVar<bool>(WavesStartedVariable, true);
self->SetNetworkVar<std::string>(StartWaveMessageVariable, "Start!");

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,24 +502,40 @@ bool BaseWavesServer::UpdateSpawnedEnemies(Entity* self, LWOOBJID enemyID, uint3
auto* missionComponent = player->GetComponent<MissionComponent>();
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>();
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());
}
}
}
}

View File

@ -62,6 +62,7 @@
#include "VeMech.h"
#include "VeMissionConsole.h"
#include "VeEpsilonServer.h"
#include "AgSurvivalBuffStation.h"
// NS Scripts
#include "NsModularBuild.h"
@ -144,6 +145,7 @@
#include "ImgBrickConsoleQB.h"
#include "ActParadoxPipeFix.h"
#include "FvNinjaGuard.h"
#include "FvBounceOverWall.h"
// FB Scripts
#include "AgJetEffectServer.h"
@ -319,6 +321,8 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr
script = new BaseEnemyMech();
else if (scriptName == "scripts\\zone\\AG\\L_ZONE_AG_SURVIVAL.lua")
script = new ZoneAgSurvival();
else if (scriptName == "scripts\\02_server\\Objects\\L_BUFF_STATION_SERVER.lua")
script = new AgSurvivalBuffStation();
else if (scriptName == "scripts\\ai\\AG\\L_AG_BUS_DOOR.lua")
script = new AgBusDoor();
else if (scriptName == "scripts\\02_server\\Equipment\\L_MAESTROM_EXTRACTICATOR_SERVER.lua")
@ -553,6 +557,8 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr
script = new ActParadoxPipeFix();
else if (scriptName == "scripts\\ai\\FV\\L_FV_NINJA_GUARDS.lua")
script = new FvNinjaGuard();
else if (scriptName == "scripts\\ai\\FV\\L_ACT_BOUNCE_OVER_WALL.lua")
script = new FvBounceOverWall();
//Misc:
if (scriptName == "scripts\\02_server\\Map\\General\\L_EXPLODING_ASSET.lua")
@ -681,7 +687,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

@ -0,0 +1,9 @@
#include "FvBounceOverWall.h"
void FvBounceOverWall::OnCollisionPhantom(Entity* self, Entity* target) {
auto missionComponent = target->GetComponent<MissionComponent>();
if (missionComponent == nullptr) return;
// We force progress here to the Gate Crasher mission due to an overlap in LOTs with the 'Shark Bite' missions.
missionComponent->ForceProgress(GateCrasherMissionId, GateCrasherMissionUid, 1);
}

View File

@ -0,0 +1,22 @@
#pragma once
#include "CppScripts.h"
class FvBounceOverWall : public CppScripts::Script
{
/**
* @brief When a collision has been made with self this method is called.
*
* @param self The Entity that called this function.
* @param target The target Entity of self.
*/
void OnCollisionPhantom(Entity* self, Entity* target) override;
private:
/**
* MissionId for the Gate Crasher mission.
*/
int32_t GateCrasherMissionId = 849;
/**
* MissionUid for the Gate Crasher mission.
*/
int32_t GateCrasherMissionUid = 1241;
};

View File

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

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

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