Compare commits

..

14 Commits

Author SHA1 Message Date
wincent
328743f1e1 Local work 2024-09-08 17:40:32 +02:00
wincent
3178a702a7 Updated how skills and upgrades work, more gui stuff 2024-07-17 20:59:11 +02:00
wincent
5e3312850c Refactor damage calculations and add additional modifiers 2024-07-06 00:02:30 +02:00
wincent
756dc4e44f Updated how upgrade adds skills 2024-06-08 17:31:22 +02:00
wincent
87613f287f Fix g++ 14 2024-06-06 09:37:49 +02:00
wincent
ac1b00fdaa Updates to upgrades 2024-06-06 09:37:24 +02:00
wincent
364bcf822a Added upgrades 2024-06-02 15:43:35 +02:00
wincent
d5b2278dc5 Refactor and combat changes 2024-06-02 09:53:56 +02:00
wincent
0bf5ee02e4 Merge remote-tracking branch 'origin/main' into nejlika 2024-05-30 10:56:58 +02:00
wincent
c2ad76ee66 Moved to a dedicated nejlika fle 2024-05-30 10:54:38 +02:00
wincent
c6528d444a Merge branch 'observable' into nejlika 2024-05-25 19:26:49 +02:00
wincent
78425aacb1 Proposal for observers and deferred implementations 2024-05-25 19:13:22 +02:00
wincent
b8d610987f Correct try-parse 2024-05-24 15:10:38 +02:00
wincent
7845131649 Test for reactive item descriptions 2024-05-24 15:06:40 +02:00
231 changed files with 33978 additions and 5797 deletions

View File

@@ -73,4 +73,4 @@ cpp_space_around_assignment_operator=insert
cpp_space_pointer_reference_alignment=left
cpp_space_around_ternary_operator=insert
cpp_wrap_preserve_blocks=one_liners
cpp_indent_comment=false
cpp_indent_comment=fasle

View File

@@ -16,12 +16,12 @@ jobs:
os: [ windows-2022, ubuntu-22.04, macos-13 ]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
submodules: true
- name: Add msbuild to PATH (Windows only)
if: ${{ matrix.os == 'windows-2022' }}
uses: microsoft/setup-msbuild@v2
uses: microsoft/setup-msbuild@v1.1
with:
vs-version: '[17,18)'
msbuild-architecture: x64
@@ -33,19 +33,21 @@ jobs:
- name: cmake
uses: lukka/run-cmake@v10
with:
workflowPreset: "ci-${{matrix.os}}"
configurePreset: "ci-${{matrix.os}}"
buildPreset: "ci-${{matrix.os}}"
testPreset: "ci-${{matrix.os}}"
- name: artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: build-${{matrix.os}}
path: |
build/*/*Server*
build/*/*.ini
build/*/*.so
build/*/*.dll
build/*/vanity/
build/*/navmeshes/
build/*/migrations/
build/*/*.dcf
!build/*/*.pdb
!build/*/d*/
build/*Server*
build/*.ini
build/*.so
build/*.dll
build/vanity/
build/navmeshes/
build/migrations/
build/*.dcf
!build/*.pdb
!build/d*/

3
.gitignore vendored
View File

@@ -122,7 +122,4 @@ docker/__pycache__
docker-compose.override.yml
!*Test.bin
# CMake scripts
!cmake/*
!cmake/toolchains/*

View File

@@ -1,8 +1,5 @@
cmake_minimum_required(VERSION 3.25)
project(Darkflame
HOMEPAGE_URL "https://github.com/DarkflameUniverse/DarkflameServer"
LANGUAGES C CXX
)
project(Darkflame)
# check if the path to the source directory contains a space
if("${CMAKE_SOURCE_DIR}" MATCHES " ")
@@ -11,10 +8,8 @@ endif()
include(CTest)
set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Export the compile commands for debugging
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW) # Set CMAKE visibility policy to NEW on project and subprojects
set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) # Set C and C++ symbol visibility to hide inlined functions
@@ -66,36 +61,35 @@ set(RECASTNAVIGATION_EXAMPLES OFF CACHE BOOL "" FORCE)
# Disabled no-register
# Disabled unknown pragmas because Linux doesn't understand Windows pragmas.
if(UNIX)
add_compile_options("-fPIC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -Wuninitialized -fPIC")
add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0 _GLIBCXX_USE_CXX17_ABI=0)
# For all except Clang and Apple Clang
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options("-static-libgcc" "-lstdc++fs")
if(NOT APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -lstdc++fs")
endif()
if(${DYNAMIC} AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
add_link_options("-export-dynamic")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -rdynamic")
endif()
if(${GGDB})
add_compile_options("-ggdb")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb")
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -O2 -fPIC")
elseif(MSVC)
# Skip warning for invalid conversion from size_t to uint32_t for all targets below for now
# Also disable non-portable MSVC volatile behavior
add_compile_options("/wd4267" "/utf-8" "/volatile:iso" "/Zc:inline")
add_compile_options("/wd4267" "/utf-8" "/volatile:iso")
elseif(WIN32)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
endif()
# Our output dir
set(CMAKE_BINARY_DIR ${PROJECT_BINARY_DIR})
#set(CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON) # unfortunately, forces all libraries to be built in series, which will slow down the build process
# TODO make this not have to override the build type directories
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR})
@@ -115,39 +109,31 @@ make_directory(${CMAKE_BINARY_DIR}/resServer)
# Create a /logs directory
make_directory(${CMAKE_BINARY_DIR}/logs)
# Get DLU config directory
if(DEFINED ENV{DLU_CONFIG_DIR})
set(DLU_CONFIG_DIR $ENV{DLU_CONFIG_DIR})
else()
set(DLU_CONFIG_DIR ${PROJECT_BINARY_DIR})
endif()
message(STATUS "Variable: DLU_CONFIG_DIR = ${DLU_CONFIG_DIR}")
# Copy resource files on first build
set(RESOURCE_FILES "sharedconfig.ini" "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini" "blocklist.dcf")
message(STATUS "Checking resource file integrity")
include(Utils)
UpdateConfigOption(${DLU_CONFIG_DIR}/authconfig.ini "port" "auth_server_port")
UpdateConfigOption(${DLU_CONFIG_DIR}/chatconfig.ini "port" "chat_server_port")
UpdateConfigOption(${DLU_CONFIG_DIR}/masterconfig.ini "port" "master_server_port")
UpdateConfigOption(${PROJECT_BINARY_DIR}/authconfig.ini "port" "auth_server_port")
UpdateConfigOption(${PROJECT_BINARY_DIR}/chatconfig.ini "port" "chat_server_port")
UpdateConfigOption(${PROJECT_BINARY_DIR}/masterconfig.ini "port" "master_server_port")
foreach(resource_file ${RESOURCE_FILES})
set(file_size 0)
if(EXISTS ${DLU_CONFIG_DIR}/${resource_file})
file(SIZE ${DLU_CONFIG_DIR}/${resource_file} file_size)
if(EXISTS ${PROJECT_BINARY_DIR}/${resource_file})
file(SIZE ${PROJECT_BINARY_DIR}/${resource_file} file_size)
endif()
if(${file_size} EQUAL 0)
configure_file(
${CMAKE_SOURCE_DIR}/resources/${resource_file} ${DLU_CONFIG_DIR}/${resource_file}
${CMAKE_SOURCE_DIR}/resources/${resource_file} ${PROJECT_BINARY_DIR}/${resource_file}
COPYONLY
)
message(STATUS "Moved " ${resource_file} " to DLU config directory")
message(STATUS "Moved " ${resource_file} " to project binary directory")
elseif(resource_file MATCHES ".ini")
message(STATUS "Checking " ${resource_file} " for missing config options")
file(READ ${DLU_CONFIG_DIR}/${resource_file} current_file_contents)
file(READ ${PROJECT_BINARY_DIR}/${resource_file} current_file_contents)
string(REPLACE "\\\n" "" current_file_contents ${current_file_contents})
string(REPLACE "\n" ";" current_file_contents ${current_file_contents})
set(parsed_current_file_contents "")
@@ -178,10 +164,10 @@ foreach(resource_file ${RESOURCE_FILES})
set(line_to_add ${line_to_add} ${line})
foreach(line_to_append ${line_to_add})
file(APPEND ${DLU_CONFIG_DIR}/${resource_file} "\n" ${line_to_append})
file(APPEND ${PROJECT_BINARY_DIR}/${resource_file} "\n" ${line_to_append})
endforeach()
file(APPEND ${DLU_CONFIG_DIR}/${resource_file} "\n")
file(APPEND ${PROJECT_BINARY_DIR}/${resource_file} "\n")
endif()
set(line_to_add "")
@@ -250,15 +236,14 @@ include_directories(
"tests/dGameTests"
"tests/dGameTests/dComponentsTests"
SYSTEM
"thirdparty/magic_enum/include/magic_enum"
"thirdparty/raknet/Source"
"thirdparty/tinyxml2"
"thirdparty/recastnavigation"
"thirdparty/SQLite"
"thirdparty/cpplinq"
"thirdparty/cpp-httplib"
"thirdparty/MD5"
SYSTEM "thirdparty/magic_enum/include/magic_enum"
SYSTEM "thirdparty/raknet/Source"
SYSTEM "thirdparty/tinyxml2"
SYSTEM "thirdparty/recastnavigation"
SYSTEM "thirdparty/SQLite"
SYSTEM "thirdparty/cpplinq"
SYSTEM "thirdparty/cpp-httplib"
SYSTEM "thirdparty/MD5"
)
# Add system specfic includes for Apple, Windows and Other Unix OS' (including Linux)
@@ -267,17 +252,10 @@ if(APPLE)
include_directories("/usr/local/include/")
endif()
# Set warning flags
if(MSVC)
# add_compile_options("/W4")
# Want to enable warnings eventually, but WAY too much noise right now
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
add_compile_options("-Wuninitialized" "-Wold-style-cast")
else()
message(WARNING "Unknown compiler: '${CMAKE_CXX_COMPILER_ID}' - No warning flags enabled.")
endif()
# Add linking directories:
if (UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wold-style-cast -Werror") # Warning flags
endif()
file(
GLOB HEADERS_DZONEMANAGER
LIST_DIRECTORIES false

View File

@@ -1,641 +1,128 @@
{
"version": 6,
"cmakeMinimumRequired": {
"major": 3,
"minor": 25,
"patch": 0
},
"configurePresets": [
{
"name": "default",
"displayName": "Default configure step",
"description": "Use 'build' dir and Unix makefiles",
"binaryDir": "${sourceDir}/build",
"environment": {
"DLU_CONFIG_DIR": "${sourceDir}/build"
},
"generator": "Unix Makefiles"
},
{
"name": "debug-config",
"hidden": true,
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "relwithdebinfo-config",
"hidden": true,
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
}
},
{
"name": "release-config",
"hidden": true,
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "clang-config",
"hidden": true,
"toolchainFile": "${sourceDir}/cmake/toolchains/linux-clang.cmake"
},
{
"name": "gnu-config",
"hidden": true,
"toolchainFile": "${sourceDir}/cmake/toolchains/linux-gnu.cmake"
},
{
"name": "windows-msvc",
"inherits": "default",
"displayName": "[Multi] Windows (MSVC)",
"description": "Set architecture to 64-bit (b/c RakNet)",
"generator": "Visual Studio 17 2022",
"binaryDir": "${sourceDir}/build/msvc",
"architecture": {
"value": "x64"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
}
},
{
"name": "windows-default",
"inherits": "windows-msvc",
"displayName": "Windows only Configure Settings",
"description": "Sets build and install directories",
"generator": "Ninja",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
},
"architecture": {
"value": "x64"
}
},
{
"name": "linux-config",
"inherits": "default",
"hidden": true,
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
}
},
{
"name": "linux-clang-debug",
"inherits": [
"linux-config",
"clang-config",
"debug-config"
],
"displayName": "EXPERIMENTAL - [Debug] Linux (Clang)",
"description": "Create a debug build using the Clang toolchain for Linux",
"binaryDir": "${sourceDir}/build/clang-debug"
},
{
"name": "linux-clang-relwithdebinfo",
"inherits": [
"linux-config",
"clang-config",
"relwithdebinfo-config"
],
"displayName": "EXPERIMENTAL - [RelWithDebInfo] Linux (Clang)",
"description": "Create a release build with debug info using the Clang toolchain for Linux",
"binaryDir": "${sourceDir}/build/clang-relwithdebinfo"
},
{
"name": "linux-clang-release",
"inherits": [
"linux-config",
"clang-config",
"release-config"
],
"displayName": "EXPERIMENTAL - [Release] Linux (Clang)",
"description": "Create a release build using the Clang toolchain for Linux",
"binaryDir": "${sourceDir}/build/clang-release"
},
{
"name": "linux-gnu-debug",
"inherits": [
"linux-config",
"gnu-config",
"debug-config"
],
"displayName": "[Debug] Linux (GNU)",
"description": "Create a debug build using the GNU toolchain for Linux",
"binaryDir": "${sourceDir}/build/gnu-debug"
},
{
"name": "linux-gnu-relwithdebinfo",
"inherits": [
"linux-config",
"gnu-config",
"relwithdebinfo-config"
],
"displayName": "[RelWithDebInfo] Linux (GNU)",
"description": "Create a release build with debug info using the GNU toolchain for Linux",
"binaryDir": "${sourceDir}/build/gnu-relwithdebinfo"
},
{
"name": "linux-gnu-release",
"inherits": [
"linux-config",
"gnu-config",
"release-config"
],
"displayName": "[Release] Linux (GNU)",
"description": "Create a release build using the GNU toolchain for Linux",
"binaryDir": "${sourceDir}/build/gnu-release"
},
{
"name": "macos",
"inherits": "default",
"displayName": "[Multi] MacOS",
"description": "Create a build for MacOS",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
},
"binaryDir": "${sourceDir}/build/macos"
}
],
"buildPresets": [
{
"name": "default",
"configurePreset": "default",
"displayName": "Default Build",
"description": "Default Build",
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 14,
"patch": 0
},
"configurePresets": [
{
"name": "default",
"displayName": "Default configure step",
"description": "Use 'build' dir and Unix makefiles",
"binaryDir": "${sourceDir}/build",
"generator": "Unix Makefiles"
},
{
"name": "ci-ubuntu-22.04",
"displayName": "CI configure step for Ubuntu",
"description": "Same as default, Used in GitHub actions workflow",
"inherits": "default"
},
{
"name": "ci-macos-13",
"displayName": "CI configure step for MacOS",
"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",
"generator": "Ninja",
"architecture": {
"value": "x64",
"strategy": "external"
}
}
],
"buildPresets": [
{
"name": "default",
"configurePreset": "default",
"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-22.04",
"configurePreset": "ci-ubuntu-22.04",
"displayName": "Linux CI Build",
"description": "This preset is used by the CI build on linux",
"jobs": 2
},
{
"name": "ci-macos-13",
"configurePreset": "ci-macos-13",
"displayName": "MacOS CI Build",
"description": "This preset is used by the CI build on MacOS",
"jobs": 2
}
],
"testPresets": [
{
"name": "ci-ubuntu-22.04",
"configurePreset": "ci-ubuntu-22.04",
"displayName": "CI Tests on Linux",
"description": "Runs all tests on a linux configuration",
"execution": {
"jobs": 2
},
{
"name": "windows-msvc-debug",
"inherits": "default",
"configurePreset": "windows-msvc",
"displayName": "[Debug] Windows (MSVC)",
"description": "This preset is used to build in debug mode using the MSVC toolchain on Windows",
"configuration": "Debug"
"output": {
"outputOnFailure": true
}
},
{
"name": "ci-macos-13",
"configurePreset": "ci-macos-13",
"displayName": "CI Tests on MacOS",
"description": "Runs all tests on a Mac configuration",
"execution": {
"jobs": 2
},
{
"name": "windows-msvc-relwithdebinfo",
"inherits": "default",
"configurePreset": "windows-msvc",
"displayName": "[RelWithDebInfo] Windows (MSVC)",
"description": "This preset is used to build in debug mode using the MSVC toolchain on Windows",
"configuration": "RelWithDebInfo"
"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
},
{
"name": "windows-msvc-release",
"inherits": "default",
"configurePreset": "windows-msvc",
"displayName": "[Release] Windows (MSVC)",
"description": "This preset is used to build in release mode using the MSVC toolchain on Windows",
"configuration": "Release"
},
{
"name": "linux-clang-debug",
"inherits": "default",
"configurePreset": "linux-clang-debug",
"displayName": "EXPERIMENTAL - [Debug] Linux (Clang)",
"description": "This preset is used to build in debug mode using the Clang toolchain on Linux",
"configuration": "Debug"
},
{
"name": "linux-clang-relwithdebinfo",
"inherits": "default",
"configurePreset": "linux-clang-relwithdebinfo",
"displayName": "EXPERIMENTAL - [RelWithDebInfo] Linux (Clang)",
"description": "This preset is used to build in release mode with debug info using the Clang toolchain on Linux",
"configuration": "RelWithDebInfo"
},
{
"name": "linux-clang-release",
"inherits": "default",
"configurePreset": "linux-clang-release",
"displayName": "EXPERIMENTAL - [Release] Linux (Clang)",
"description": "This preset is used to build in release mode using the Clang toolchain on Linux",
"configuration": "Release"
},
{
"name": "linux-gnu-debug",
"inherits": "default",
"configurePreset": "linux-gnu-debug",
"displayName": "[Debug] Linux (GNU)",
"description": "This preset is used to build in debug mode using the GNU toolchain on Linux",
"configuration": "Debug"
},
{
"name": "linux-gnu-relwithdebinfo",
"inherits": "default",
"configurePreset": "linux-gnu-relwithdebinfo",
"displayName": "[RelWithDebInfo] Linux (GNU)",
"description": "This preset is used to build in release mode with debug info using the GNU toolchain on Linux",
"configuration": "RelWithDebInfo"
},
{
"name": "linux-gnu-release",
"inherits": "default",
"configurePreset": "linux-gnu-release",
"displayName": "[Release] Linux (GNU)",
"description": "This preset is used to build in release mode using the GNU toolchain on Linux",
"configuration": "Release"
},
{
"name": "macos-debug",
"inherits": "default",
"configurePreset": "macos",
"displayName": "[Debug] MacOS",
"description": "This preset is used to build in debug mode on MacOS",
"configuration": "Debug"
},
{
"name": "macos-relwithdebinfo",
"inherits": "default",
"configurePreset": "macos",
"displayName": "[RelWithDebInfo] MacOS",
"description": "This preset is used to build in release mode with debug info on MacOS",
"configuration": "RelWithDebInfo"
},
{
"name": "macos-release",
"inherits": "default",
"configurePreset": "macos",
"displayName": "[Release] MacOS",
"description": "This preset is used to build in release mode on MacOS",
"configuration": "Release"
}
],
"testPresets": [
{
"name": "default",
"configurePreset": "default",
"execution": {
"jobs": 2
},
"output": {
"outputOnFailure": true
"output": {
"outputOnFailure": true
},
"filter": {
"exclude": {
"name": "((example)|(minigzip))+"
}
},
{
"name": "windows-msvc-test",
"inherits": "default",
"configurePreset": "windows-msvc",
"hidden": true,
"filter": {
"exclude": {
"name": "((example)|(minigzip))+"
}
}
},
{
"name": "windows-msvc-debug",
"inherits": "windows-msvc-test",
"configurePreset": "windows-msvc",
"displayName": "[Debug] Windows (MSVC)",
"description": "Runs all tests on a Windows configuration",
"configuration": "Debug"
},
{
"name": "windows-msvc-relwithdebinfo",
"inherits": "windows-msvc-test",
"configurePreset": "windows-msvc",
"displayName": "[RelWithDebInfo] Windows (MSVC)",
"description": "Runs all tests on a Windows configuration",
"configuration": "RelWithDebInfo"
},
{
"name": "windows-msvc-release",
"inherits": "windows-msvc-test",
"configurePreset": "windows-msvc",
"displayName": "[Release] Windows (MSVC)",
"description": "Runs all tests on a Windows configuration",
"configuration": "Release"
},
{
"name": "linux-clang-debug",
"inherits": "default",
"configurePreset": "linux-clang-debug",
"displayName": "EXPERIMENTAL - [Debug] Linux (Clang)",
"description": "Runs all tests on a Linux Clang configuration",
"configuration": "Release"
},
{
"name": "linux-clang-relwithdebinfo",
"inherits": "default",
"configurePreset": "linux-clang-relwithdebinfo",
"displayName": "EXPERIMENTAL - [RelWithDebInfo] Linux (Clang)",
"description": "Runs all tests on a Linux Clang configuration",
"configuration": "RelWithDebInfo"
},
{
"name": "linux-clang-release",
"inherits": "default",
"configurePreset": "linux-clang-release",
"displayName": "EXPERIMENTAL - [Release] Linux (Clang)",
"description": "Runs all tests on a Linux Clang configuration",
"configuration": "Release"
},
{
"name": "linux-gnu-debug",
"inherits": "default",
"configurePreset": "linux-gnu-debug",
"displayName": "[Debug] Linux (GNU)",
"description": "Runs all tests on a Linux GNU configuration",
"configuration": "Release"
},
{
"name": "linux-gnu-relwithdebinfo",
"inherits": "default",
"configurePreset": "linux-gnu-relwithdebinfo",
"displayName": "[RelWithDebInfo] Linux (GNU)",
"description": "Runs all tests on a Linux GNU configuration",
"configuration": "RelWithDebInfo"
},
{
"name": "linux-gnu-release",
"inherits": "default",
"configurePreset": "linux-gnu-release",
"displayName": "[Release] Linux (GNU)",
"description": "Runs all tests on a Linux GNU configuration",
"configuration": "Release"
},
{
"name": "macos-debug",
"inherits": "default",
"configurePreset": "macos",
"displayName": "[Debug] MacOS",
"description": "Runs all tests on a MacOS configuration",
"configuration": "Debug"
},
{
"name": "macos-relwithdebinfo",
"inherits": "default",
"configurePreset": "macos",
"displayName": "[RelWithDebInfo] MacOS",
"description": "Runs all tests on a MacOS configuration",
"configuration": "RelWithDebInfo"
},
{
"name": "macos-release",
"inherits": "default",
"configurePreset": "macos",
"displayName": "[Release] MacOS",
"description": "Runs all tests on a MacOS configuration",
"configuration": "Release"
}
],
"workflowPresets": [
{
"name": "default",
"steps": [
{
"type": "configure",
"name": "default"
},
{
"type": "build",
"name": "default"
},
{
"type": "test",
"name": "default"
}
]
},
{
"name": "windows-msvc-debug",
"displayName": "[Debug] Windows (MSVC)",
"description": "MSVC debug workflow preset for Windows",
"steps": [
{
"type": "configure",
"name": "windows-msvc"
},
{
"type": "build",
"name": "windows-msvc-debug"
},
{
"type": "test",
"name": "windows-msvc-debug"
}
]
},
{
"name": "windows-msvc-relwithdebinfo",
"displayName": "[RelWithDebInfo] Windows (MSVC)",
"description": "MSVC release with debug info workflow preset for Windows",
"steps": [
{
"type": "configure",
"name": "windows-msvc"
},
{
"type": "build",
"name": "windows-msvc-relwithdebinfo"
},
{
"type": "test",
"name": "windows-msvc-relwithdebinfo"
}
]
},
{
"name": "ci-windows-2022",
"displayName": "[Release] Windows (MSVC)",
"description": "CI workflow preset for Windows",
"steps": [
{
"type": "configure",
"name": "windows-msvc"
},
{
"type": "build",
"name": "windows-msvc-release"
},
{
"type": "test",
"name": "windows-msvc-release"
}
]
},
{
"name": "linux-gnu-debug",
"displayName": "[Debug] Linux (GNU)",
"description": "GNU debug workflow preset for Linux",
"steps": [
{
"type": "configure",
"name": "linux-gnu-debug"
},
{
"type": "build",
"name": "linux-gnu-debug"
},
{
"type": "test",
"name": "linux-gnu-debug"
}
]
},
{
"name": "linux-gnu-relwithdebinfo",
"displayName": "[RelWithDebInfo] Linux (GNU)",
"description": "GNU release with debug info workflow preset for Linux",
"steps": [
{
"type": "configure",
"name": "linux-gnu-relwithdebinfo"
},
{
"type": "build",
"name": "linux-gnu-relwithdebinfo"
},
{
"type": "test",
"name": "linux-gnu-relwithdebinfo"
}
]
},
{
"name": "ci-ubuntu-22.04",
"displayName": "[Release] Linux (GNU)",
"description": "CI workflow preset for Ubuntu",
"steps": [
{
"type": "configure",
"name": "linux-gnu-release"
},
{
"type": "build",
"name": "linux-gnu-release"
},
{
"type": "test",
"name": "linux-gnu-release"
}
]
},
{
"name": "linux-clang-debug",
"displayName": "EXPERIMENTAL - [Debug] Linux (Clang)",
"description": "Clang debug workflow preset for Linux",
"steps": [
{
"type": "configure",
"name": "linux-clang-debug"
},
{
"type": "build",
"name": "linux-clang-debug"
},
{
"type": "test",
"name": "linux-clang-debug"
}
]
},
{
"name": "linux-clang-relwithdebinfo",
"displayName": "EXPERIMENTAL - [RelWithDebInfo] Linux (Clang)",
"description": "Clang release with debug info workflow preset for Linux",
"steps": [
{
"type": "configure",
"name": "linux-clang-relwithdebinfo"
},
{
"type": "build",
"name": "linux-clang-relwithdebinfo"
},
{
"type": "test",
"name": "linux-clang-relwithdebinfo"
}
]
},
{
"name": "linux-clang-release",
"displayName": "EXPERIMENTAL - [Release] Linux (Clang)",
"description": "Clang release workflow preset for Linux",
"steps": [
{
"type": "configure",
"name": "linux-clang-release"
},
{
"type": "build",
"name": "linux-clang-release"
},
{
"type": "test",
"name": "linux-clang-release"
}
]
},
{
"name": "macos-debug",
"displayName": "[Debug] MacOS",
"description": "Release workflow preset for MacOS",
"steps": [
{
"type": "configure",
"name": "macos"
},
{
"type": "build",
"name": "macos-debug"
},
{
"type": "test",
"name": "macos-debug"
}
]
},
{
"name": "macos-relwithdebinfo",
"displayName": "[RelWithDebInfo] MacOS",
"description": "Release with debug info workflow preset for MacOS",
"steps": [
{
"type": "configure",
"name": "macos"
},
{
"type": "build",
"name": "macos-relwithdebinfo"
},
{
"type": "test",
"name": "macos-relwithdebinfo"
}
]
},
{
"name": "ci-macos-13",
"displayName": "[Release] MacOS",
"description": "CI workflow preset for MacOS",
"steps": [
{
"type": "configure",
"name": "macos"
},
{
"type": "build",
"name": "macos-release"
},
{
"type": "test",
"name": "macos-release"
}
]
}
]
}
}
]
}

View File

@@ -6,7 +6,8 @@ mkdir -p build
cd build
# Run cmake to generate make files
cmake -DCMAKE_BUILD_TYPE="Release" ..
cmake ..
# To build utilizing multiple cores, append `-j` and the amount of cores to utilize, for example `cmake --build . --config Release -j8'
cmake --build . --config Release $1

View File

@@ -1,14 +0,0 @@
# Try and find a clang-16 install, falling back to a generic clang install otherwise
find_program(CLANG_C_COMPILER clang-16 | clang REQUIRED)
find_program(CLANG_CXX_COMPILER clang++-16 | clang++ REQUIRED)
# Debug messages
message(DEBUG "CLANG_C_COMPILER = ${CLANG_C_COMPILER}")
message(DEBUG "CLANG_CXX_COMPILER = ${CLANG_CXX_COMPILER}")
# Set compilers to clang (need to cache for VSCode tools to work correctly)
set(CMAKE_C_COMPILER ${CLANG_C_COMPILER} CACHE STRING "Set C compiler")
set(CMAKE_CXX_COMPILER ${CLANG_CXX_COMPILER} CACHE STRING "Set C++ compiler")
# Set linker to lld
add_link_options("-fuse-ld=lld")

View File

@@ -1,11 +0,0 @@
# Try and find a gcc/g++ install
find_program(GNU_C_COMPILER cc | gcc REQUIRED)
find_program(GNU_CXX_COMPILER c++ | g++ REQUIRED)
# Debug messages
message(DEBUG "GNU_C_COMPILER = ${GNU_C_COMPILER}")
message(DEBUG "GNU_CXX_COMPILER = ${GNU_CXX_COMPILER}")
# Set compilers to GNU (need to cache for VSCode tools to work correctly)
set(CMAKE_C_COMPILER ${GNU_C_COMPILER} CACHE STRING "Set C compiler")
set(CMAKE_CXX_COMPILER ${GNU_CXX_COMPILER} CACHE STRING "Set C++ compiler")

View File

@@ -21,8 +21,8 @@
//Auth includes:
#include "AuthPackets.h"
#include "eConnectionType.h"
#include "MessageType/Server.h"
#include "MessageType/Auth.h"
#include "eServerMessageType.h"
#include "eAuthMessageType.h"
#include "Game.h"
#include "Server.h"
@@ -102,7 +102,7 @@ int main(int argc, char** argv) {
uint32_t framesSinceLastSQLPing = 0;
AuthPackets::LoadClaimCodes();
Game::logger->Flush(); // once immediately before main loop
while (!Game::ShouldShutdown()) {
//Check if we're still connected to master:
@@ -166,11 +166,11 @@ void HandlePacket(Packet* packet) {
if (packet->data[0] == ID_USER_PACKET_ENUM) {
if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::SERVER) {
if (static_cast<MessageType::Server>(packet->data[3]) == MessageType::Server::VERSION_CONFIRM) {
if (static_cast<eServerMessageType>(packet->data[3]) == eServerMessageType::VERSION_CONFIRM) {
AuthPackets::HandleHandshake(Game::server, packet);
}
} else if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::AUTH) {
if (static_cast<MessageType::Auth>(packet->data[3]) == MessageType::Auth::LOGIN_REQUEST) {
if (static_cast<eAuthMessageType>(packet->data[3]) == eAuthMessageType::LOGIN_REQUEST) {
AuthPackets::HandleLoginRequest(Game::server, packet);
}
}

View File

@@ -1,6 +1,6 @@
#include "ChatIgnoreList.h"
#include "PlayerContainer.h"
#include "MessageType/Chat.h"
#include "eChatMessageType.h"
#include "BitStreamUtils.h"
#include "Game.h"
#include "Logger.h"
@@ -13,7 +13,7 @@
// The only thing not auto-handled is instance activities force joining the team on the server.
void WriteOutgoingReplyHeader(RakNet::BitStream& bitStream, const LWOOBJID& receivingPlayer, const ChatIgnoreList::Response type) {
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receivingPlayer);
//portion that will get routed:

View File

@@ -13,9 +13,9 @@
#include "dConfig.h"
#include "eObjectBits.h"
#include "eConnectionType.h"
#include "MessageType/Chat.h"
#include "MessageType/Client.h"
#include "MessageType/Game.h"
#include "eChatMessageType.h"
#include "eClientMessageType.h"
#include "eGameMessageType.h"
#include "StringifiedEnum.h"
#include "eGameMasterLevel.h"
#include "ChatPackets.h"
@@ -60,11 +60,11 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
//Now, we need to send the friendlist to the server they came from:
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::GET_FRIENDS_LIST_RESPONSE);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::GET_FRIENDS_LIST_RESPONSE);
bitStream.Write<uint8_t>(0);
bitStream.Write<uint16_t>(1); //Length of packet -- just writing one as it doesn't matter, client skips it.
bitStream.Write<uint16_t>(player.friends.size());
@@ -368,10 +368,10 @@ void ChatPacketHandler::HandleWho(Packet* packet) {
bool online = player;
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(request.requestor);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::WHO_RESPONSE);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::WHO_RESPONSE);
bitStream.Write<uint8_t>(online);
bitStream.Write(player.zoneID.GetMapID());
bitStream.Write(player.zoneID.GetInstanceID());
@@ -391,10 +391,10 @@ void ChatPacketHandler::HandleShowAll(Packet* packet) {
if (!sender) return;
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(request.requestor);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::SHOW_ALL_RESPONSE);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::SHOW_ALL_RESPONSE);
bitStream.Write<uint8_t>(!request.displayZoneData && !request.displayIndividualPlayers);
bitStream.Write(Game::playerContainer.GetPlayerCount());
bitStream.Write(Game::playerContainer.GetSimCount());
@@ -416,7 +416,7 @@ void ChatPacketHandler::HandleShowAll(Packet* packet) {
SEND_PACKET;
}
// the structure the client uses to send this packet is shared in many chat messages
// the structure the client uses to send this packet is shared in many chat messages
// that are sent to the server. Because of this, there are large gaps of unused data in chat messages
void ChatPacketHandler::HandleChatMessage(Packet* packet) {
CINSTREAM_SKIP_HEADER;
@@ -428,7 +428,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
eChatChannel channel;
uint32_t size;
inStream.IgnoreBytes(4);
inStream.Read(channel);
inStream.Read(size);
@@ -436,7 +436,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
LUWString message(size);
inStream.Read(message);
LOG("Got a message from (%s) via [%s]: %s", sender.playerName.c_str(), StringifiedEnum::ToString(channel).data(), message.GetAsString().c_str());
switch (channel) {
@@ -457,7 +457,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
}
}
// the structure the client uses to send this packet is shared in many chat messages
// the structure the client uses to send this packet is shared in many chat messages
// that are sent to the server. Because of this, there are large gaps of unused data in chat messages
void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
CINSTREAM_SKIP_HEADER;
@@ -484,7 +484,7 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
LUWString message(size);
inStream.Read(message);
LOG("Got a message from (%s) via [%s]: %s to %s", sender.playerName.c_str(), StringifiedEnum::ToString(channel).data(), message.GetAsString().c_str(), receiverName.c_str());
const auto& receiver = Game::playerContainer.GetPlayerData(receiverName);
@@ -515,10 +515,10 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(routeTo.playerID);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::PRIVATE_CHAT_MESSAGE);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::PRIVATE_CHAT_MESSAGE);
bitStream.Write(sender.playerID);
bitStream.Write(channel);
bitStream.Write<uint32_t>(0); // not used
@@ -757,11 +757,11 @@ void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) {
void ChatPacketHandler::SendTeamInvite(const PlayerData& receiver, const PlayerData& sender) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::TEAM_INVITE);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::TEAM_INVITE);
bitStream.Write(LUWString(sender.playerName.c_str()));
bitStream.Write(sender.playerID);
@@ -772,14 +772,14 @@ void ChatPacketHandler::SendTeamInvite(const PlayerData& receiver, const PlayerD
void ChatPacketHandler::SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_INVITE_CONFIRM);
bitStream.Write(eGameMessageType::TEAM_INVITE_CONFIRM);
bitStream.Write(bLeaderIsFreeTrial);
bitStream.Write(i64LeaderID);
@@ -799,14 +799,14 @@ void ChatPacketHandler::SendTeamInviteConfirm(const PlayerData& receiver, bool b
void ChatPacketHandler::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_GET_STATUS_RESPONSE);
bitStream.Write(eGameMessageType::TEAM_GET_STATUS_RESPONSE);
bitStream.Write(i64LeaderID);
bitStream.Write(i64LeaderZoneID);
@@ -824,14 +824,14 @@ void ChatPacketHandler::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64L
void ChatPacketHandler::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_SET_LEADER);
bitStream.Write(eGameMessageType::TEAM_SET_LEADER);
bitStream.Write(i64PlayerID);
@@ -841,14 +841,14 @@ void ChatPacketHandler::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i
void ChatPacketHandler::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_ADD_PLAYER);
bitStream.Write(eGameMessageType::TEAM_ADD_PLAYER);
bitStream.Write(bIsFreeTrial);
bitStream.Write(bLocal);
@@ -870,14 +870,14 @@ void ChatPacketHandler::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFr
void ChatPacketHandler::SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_REMOVE_PLAYER);
bitStream.Write(eGameMessageType::TEAM_REMOVE_PLAYER);
bitStream.Write(bDisband);
bitStream.Write(bIsKicked);
@@ -896,14 +896,14 @@ void ChatPacketHandler::SendTeamRemovePlayer(const PlayerData& receiver, bool bD
void ChatPacketHandler::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_SET_OFF_WORLD_FLAG);
bitStream.Write(eGameMessageType::TEAM_SET_OFF_WORLD_FLAG);
bitStream.Write(i64PlayerID);
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
@@ -930,11 +930,11 @@ void ChatPacketHandler::SendFriendUpdate(const PlayerData& friendData, const Pla
[bool] - is FTP*/
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(friendData.playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::UPDATE_FRIEND_NOTIFY);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::UPDATE_FRIEND_NOTIFY);
bitStream.Write<uint8_t>(notifyType);
std::string playerName = playerData.playerName.c_str();
@@ -967,11 +967,11 @@ void ChatPacketHandler::SendFriendRequest(const PlayerData& receiver, const Play
}
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::ADD_FRIEND_REQUEST);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::ADD_FRIEND_REQUEST);
bitStream.Write(LUWString(sender.playerName));
bitStream.Write<uint8_t>(0); // This is a BFF flag however this is unused in live and does not have an implementation client side.
@@ -981,11 +981,11 @@ void ChatPacketHandler::SendFriendRequest(const PlayerData& receiver, const Play
void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const PlayerData& sender, eAddFriendResponseType responseCode, uint8_t isBestFriendsAlready, uint8_t isBestFriendRequest) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
// Portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::ADD_FRIEND_RESPONSE);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::ADD_FRIEND_RESPONSE);
bitStream.Write(responseCode);
// For all requests besides accepted, write a flag that says whether or not we are already best friends with the receiver.
bitStream.Write<uint8_t>(responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender.sysAddr != UNASSIGNED_SYSTEM_ADDRESS);
@@ -1004,11 +1004,11 @@ void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const Pla
void ChatPacketHandler::SendRemoveFriend(const PlayerData& receiver, std::string& personToRemove, bool isSuccessful) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::REMOVE_FRIEND_RESPONSE);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::REMOVE_FRIEND_RESPONSE);
bitStream.Write<uint8_t>(isSuccessful); //isOnline
bitStream.Write(LUWString(personToRemove));

View File

@@ -16,8 +16,8 @@
#include "eConnectionType.h"
#include "PlayerContainer.h"
#include "ChatPacketHandler.h"
#include "MessageType/Chat.h"
#include "MessageType/World.h"
#include "eChatMessageType.h"
#include "eWorldMessageType.h"
#include "ChatIgnoreList.h"
#include "StringifiedEnum.h"
@@ -108,7 +108,7 @@ int main(int argc, char** argv) {
const bool dontGenerateDCF = GeneralUtils::TryParse<bool>(Game::config->GetValue("dont_generate_dcf")).value_or(false);
Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", dontGenerateDCF);
Game::randomEngine = std::mt19937(time(0));
Game::playerContainer.Initialize();
@@ -179,7 +179,6 @@ int main(int argc, char** argv) {
}
void HandlePacket(Packet* packet) {
if (packet->length < 1) return;
if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) {
LOG("A server has disconnected, erasing their connected players from the list.");
} else if (packet->data[0] == ID_NEW_INCOMING_CONNECTION) {
@@ -190,154 +189,154 @@ void HandlePacket(Packet* packet) {
inStream.SetReadOffset(BYTES_TO_BITS(1));
eConnectionType connection;
MessageType::Chat chatMessageID;
eChatMessageType chatMessageID;
inStream.Read(connection);
if (connection != eConnectionType::CHAT) return;
inStream.Read(chatMessageID);
switch (chatMessageID) {
case MessageType::Chat::GM_MUTE:
case eChatMessageType::GM_MUTE:
Game::playerContainer.MuteUpdate(packet);
break;
case MessageType::Chat::CREATE_TEAM:
case eChatMessageType::CREATE_TEAM:
Game::playerContainer.CreateTeamServer(packet);
break;
case MessageType::Chat::GET_FRIENDS_LIST:
case eChatMessageType::GET_FRIENDS_LIST:
ChatPacketHandler::HandleFriendlistRequest(packet);
break;
case MessageType::Chat::GET_IGNORE_LIST:
case eChatMessageType::GET_IGNORE_LIST:
ChatIgnoreList::GetIgnoreList(packet);
break;
case MessageType::Chat::ADD_IGNORE:
case eChatMessageType::ADD_IGNORE:
ChatIgnoreList::AddIgnore(packet);
break;
case MessageType::Chat::REMOVE_IGNORE:
case eChatMessageType::REMOVE_IGNORE:
ChatIgnoreList::RemoveIgnore(packet);
break;
case MessageType::Chat::TEAM_GET_STATUS:
case eChatMessageType::TEAM_GET_STATUS:
ChatPacketHandler::HandleTeamStatusRequest(packet);
break;
case MessageType::Chat::ADD_FRIEND_REQUEST:
case eChatMessageType::ADD_FRIEND_REQUEST:
//this involves someone sending the initial request, the response is below, response as in from the other player.
//We basically just check to see if this player is online or not and route the packet.
ChatPacketHandler::HandleFriendRequest(packet);
break;
case MessageType::Chat::ADD_FRIEND_RESPONSE:
case eChatMessageType::ADD_FRIEND_RESPONSE:
//This isn't the response a server sent, rather it is a player's response to a received request.
//Here, we'll actually have to add them to eachother's friend lists depending on the response code.
ChatPacketHandler::HandleFriendResponse(packet);
break;
case MessageType::Chat::REMOVE_FRIEND:
case eChatMessageType::REMOVE_FRIEND:
ChatPacketHandler::HandleRemoveFriend(packet);
break;
case MessageType::Chat::GENERAL_CHAT_MESSAGE:
case eChatMessageType::GENERAL_CHAT_MESSAGE:
ChatPacketHandler::HandleChatMessage(packet);
break;
case MessageType::Chat::PRIVATE_CHAT_MESSAGE:
case eChatMessageType::PRIVATE_CHAT_MESSAGE:
//This message is supposed to be echo'd to both the sender and the receiver
//BUT: they have to have different responseCodes, so we'll do some of the ol hacky wacky to fix that right up.
ChatPacketHandler::HandlePrivateChatMessage(packet);
break;
case MessageType::Chat::TEAM_INVITE:
case eChatMessageType::TEAM_INVITE:
ChatPacketHandler::HandleTeamInvite(packet);
break;
case MessageType::Chat::TEAM_INVITE_RESPONSE:
case eChatMessageType::TEAM_INVITE_RESPONSE:
ChatPacketHandler::HandleTeamInviteResponse(packet);
break;
case MessageType::Chat::TEAM_LEAVE:
case eChatMessageType::TEAM_LEAVE:
ChatPacketHandler::HandleTeamLeave(packet);
break;
case MessageType::Chat::TEAM_SET_LEADER:
case eChatMessageType::TEAM_SET_LEADER:
ChatPacketHandler::HandleTeamPromote(packet);
break;
case MessageType::Chat::TEAM_KICK:
case eChatMessageType::TEAM_KICK:
ChatPacketHandler::HandleTeamKick(packet);
break;
case MessageType::Chat::TEAM_SET_LOOT:
case eChatMessageType::TEAM_SET_LOOT:
ChatPacketHandler::HandleTeamLootOption(packet);
break;
case MessageType::Chat::GMLEVEL_UPDATE:
case eChatMessageType::GMLEVEL_UPDATE:
ChatPacketHandler::HandleGMLevelUpdate(packet);
break;
case MessageType::Chat::LOGIN_SESSION_NOTIFY:
case eChatMessageType::LOGIN_SESSION_NOTIFY:
Game::playerContainer.InsertPlayer(packet);
break;
case MessageType::Chat::GM_ANNOUNCE:{
case eChatMessageType::GM_ANNOUNCE:{
// we just forward this packet to every connected server
inStream.ResetReadPointer();
Game::server->Send(inStream, packet->systemAddress, true); // send to everyone except origin
}
break;
case MessageType::Chat::UNEXPECTED_DISCONNECT:
case eChatMessageType::UNEXPECTED_DISCONNECT:
Game::playerContainer.RemovePlayer(packet);
break;
case MessageType::Chat::WHO:
case eChatMessageType::WHO:
ChatPacketHandler::HandleWho(packet);
break;
case MessageType::Chat::SHOW_ALL:
case eChatMessageType::SHOW_ALL:
ChatPacketHandler::HandleShowAll(packet);
break;
case MessageType::Chat::USER_CHANNEL_CHAT_MESSAGE:
case MessageType::Chat::WORLD_DISCONNECT_REQUEST:
case MessageType::Chat::WORLD_PROXIMITY_RESPONSE:
case MessageType::Chat::WORLD_PARCEL_RESPONSE:
case MessageType::Chat::TEAM_MISSED_INVITE_CHECK:
case MessageType::Chat::GUILD_CREATE:
case MessageType::Chat::GUILD_INVITE:
case MessageType::Chat::GUILD_INVITE_RESPONSE:
case MessageType::Chat::GUILD_LEAVE:
case MessageType::Chat::GUILD_KICK:
case MessageType::Chat::GUILD_GET_STATUS:
case MessageType::Chat::GUILD_GET_ALL:
case MessageType::Chat::BLUEPRINT_MODERATED:
case MessageType::Chat::BLUEPRINT_MODEL_READY:
case MessageType::Chat::PROPERTY_READY_FOR_APPROVAL:
case MessageType::Chat::PROPERTY_MODERATION_CHANGED:
case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED:
case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED_REPORT:
case MessageType::Chat::MAIL:
case MessageType::Chat::WORLD_INSTANCE_LOCATION_REQUEST:
case MessageType::Chat::REPUTATION_UPDATE:
case MessageType::Chat::SEND_CANNED_TEXT:
case MessageType::Chat::CHARACTER_NAME_CHANGE_REQUEST:
case MessageType::Chat::CSR_REQUEST:
case MessageType::Chat::CSR_REPLY:
case MessageType::Chat::GM_KICK:
case MessageType::Chat::WORLD_ROUTE_PACKET:
case MessageType::Chat::GET_ZONE_POPULATIONS:
case MessageType::Chat::REQUEST_MINIMUM_CHAT_MODE:
case MessageType::Chat::MATCH_REQUEST:
case MessageType::Chat::UGCMANIFEST_REPORT_MISSING_FILE:
case MessageType::Chat::UGCMANIFEST_REPORT_DONE_FILE:
case MessageType::Chat::UGCMANIFEST_REPORT_DONE_BLUEPRINT:
case MessageType::Chat::UGCC_REQUEST:
case MessageType::Chat::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE:
case MessageType::Chat::ACHIEVEMENT_NOTIFY:
case MessageType::Chat::GM_CLOSE_PRIVATE_CHAT_WINDOW:
case MessageType::Chat::PLAYER_READY:
case MessageType::Chat::GET_DONATION_TOTAL:
case MessageType::Chat::UPDATE_DONATION:
case MessageType::Chat::PRG_CSR_COMMAND:
case MessageType::Chat::HEARTBEAT_REQUEST_FROM_WORLD:
case MessageType::Chat::UPDATE_FREE_TRIAL_STATUS:
case eChatMessageType::USER_CHANNEL_CHAT_MESSAGE:
case eChatMessageType::WORLD_DISCONNECT_REQUEST:
case eChatMessageType::WORLD_PROXIMITY_RESPONSE:
case eChatMessageType::WORLD_PARCEL_RESPONSE:
case eChatMessageType::TEAM_MISSED_INVITE_CHECK:
case eChatMessageType::GUILD_CREATE:
case eChatMessageType::GUILD_INVITE:
case eChatMessageType::GUILD_INVITE_RESPONSE:
case eChatMessageType::GUILD_LEAVE:
case eChatMessageType::GUILD_KICK:
case eChatMessageType::GUILD_GET_STATUS:
case eChatMessageType::GUILD_GET_ALL:
case eChatMessageType::BLUEPRINT_MODERATED:
case eChatMessageType::BLUEPRINT_MODEL_READY:
case eChatMessageType::PROPERTY_READY_FOR_APPROVAL:
case eChatMessageType::PROPERTY_MODERATION_CHANGED:
case eChatMessageType::PROPERTY_BUILDMODE_CHANGED:
case eChatMessageType::PROPERTY_BUILDMODE_CHANGED_REPORT:
case eChatMessageType::MAIL:
case eChatMessageType::WORLD_INSTANCE_LOCATION_REQUEST:
case eChatMessageType::REPUTATION_UPDATE:
case eChatMessageType::SEND_CANNED_TEXT:
case eChatMessageType::CHARACTER_NAME_CHANGE_REQUEST:
case eChatMessageType::CSR_REQUEST:
case eChatMessageType::CSR_REPLY:
case eChatMessageType::GM_KICK:
case eChatMessageType::WORLD_ROUTE_PACKET:
case eChatMessageType::GET_ZONE_POPULATIONS:
case eChatMessageType::REQUEST_MINIMUM_CHAT_MODE:
case eChatMessageType::MATCH_REQUEST:
case eChatMessageType::UGCMANIFEST_REPORT_MISSING_FILE:
case eChatMessageType::UGCMANIFEST_REPORT_DONE_FILE:
case eChatMessageType::UGCMANIFEST_REPORT_DONE_BLUEPRINT:
case eChatMessageType::UGCC_REQUEST:
case eChatMessageType::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE:
case eChatMessageType::ACHIEVEMENT_NOTIFY:
case eChatMessageType::GM_CLOSE_PRIVATE_CHAT_WINDOW:
case eChatMessageType::PLAYER_READY:
case eChatMessageType::GET_DONATION_TOTAL:
case eChatMessageType::UPDATE_DONATION:
case eChatMessageType::PRG_CSR_COMMAND:
case eChatMessageType::HEARTBEAT_REQUEST_FROM_WORLD:
case eChatMessageType::UPDATE_FREE_TRIAL_STATUS:
LOG("Unhandled CHAT Message id: %s (%i)", StringifiedEnum::ToString(chatMessageID).data(), chatMessageID);
break;
default:

View File

@@ -11,7 +11,7 @@
#include "eConnectionType.h"
#include "ChatPackets.h"
#include "dConfig.h"
#include "MessageType/Chat.h"
#include "eChatMessageType.h"
void PlayerContainer::Initialize() {
m_MaxNumberOfBestFriends =
@@ -36,19 +36,16 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
data.playerID = playerId;
uint32_t len;
if (!inStream.Read<uint32_t>(len)) return;
inStream.Read<uint32_t>(len);
if (len > 33) {
LOG("Received a really long player name, probably a fake packet %i.", len);
return;
for (int i = 0; i < len; i++) {
char character; inStream.Read<char>(character);
data.playerName += character;
}
data.playerName.resize(len);
inStream.ReadAlignedBytes(reinterpret_cast<unsigned char*>(data.playerName.data()), len);
if (!inStream.Read(data.zoneID)) return;
if (!inStream.Read(data.muteExpire)) return;
if (!inStream.Read(data.gmLevel)) return;
inStream.Read(data.zoneID);
inStream.Read(data.muteExpire);
inStream.Read(data.gmLevel);
data.sysAddr = packet->systemAddress;
m_Names[data.playerID] = GeneralUtils::UTF8ToUTF16(data.playerName);
@@ -125,11 +122,6 @@ void PlayerContainer::CreateTeamServer(Packet* packet) {
size_t membersSize = 0;
inStream.Read(membersSize);
if (membersSize >= 4) {
LOG("Tried to create a team with more than 4 players");
return;
}
std::vector<LWOOBJID> members;
members.reserve(membersSize);
@@ -148,13 +140,14 @@ void PlayerContainer::CreateTeamServer(Packet* packet) {
if (team != nullptr) {
team->zoneId = zoneId;
UpdateTeamsOnWorld(team, false);
}
UpdateTeamsOnWorld(team, false);
}
void PlayerContainer::BroadcastMuteUpdate(LWOOBJID player, time_t time) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::GM_MUTE);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GM_MUTE);
bitStream.Write(player);
bitStream.Write(time);
@@ -361,7 +354,7 @@ void PlayerContainer::TeamStatusUpdate(TeamData* team) {
void PlayerContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::TEAM_GET_STATUS);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::TEAM_GET_STATUS);
bitStream.Write(team->teamID);
bitStream.Write(deleteTeam);

View File

@@ -9,54 +9,73 @@
* AMF3 Deserializer written by EmosewaMC
*/
std::unique_ptr<AMFBaseValue> AMFDeserialize::Read(RakNet::BitStream& inStream) {
AMFBaseValue* AMFDeserialize::Read(RakNet::BitStream& inStream) {
AMFBaseValue* returnValue = nullptr;
// Read in the value type from the bitStream
eAmf marker;
inStream.Read(marker);
// Based on the typing, create the value associated with that and return the base value class
switch (marker) {
case eAmf::Undefined:
return std::make_unique<AMFBaseValue>();
case eAmf::Null:
return std::make_unique<AMFNullValue>();
case eAmf::False:
return std::make_unique<AMFBoolValue>(false);
case eAmf::True:
return std::make_unique<AMFBoolValue>(true);
case eAmf::Integer:
return ReadAmfInteger(inStream);
case eAmf::Double:
return ReadAmfDouble(inStream);
case eAmf::String:
return ReadAmfString(inStream);
case eAmf::Array:
return ReadAmfArray(inStream);
case eAmf::Undefined: {
returnValue = new AMFBaseValue();
break;
}
case eAmf::Null: {
returnValue = new AMFNullValue();
break;
}
case eAmf::False: {
returnValue = new AMFBoolValue(false);
break;
}
case eAmf::True: {
returnValue = new AMFBoolValue(true);
break;
}
case eAmf::Integer: {
returnValue = ReadAmfInteger(inStream);
break;
}
case eAmf::Double: {
returnValue = ReadAmfDouble(inStream);
break;
}
case eAmf::String: {
returnValue = ReadAmfString(inStream);
break;
}
case eAmf::Array: {
returnValue = ReadAmfArray(inStream);
break;
}
// These values are unimplemented in the live client and will remain unimplemented
// unless someone modifies the client to allow serializing of these values.
case eAmf::XMLDoc:
[[fallthrough]];
case eAmf::Date:
[[fallthrough]];
case eAmf::Object:
[[fallthrough]];
case eAmf::XML:
[[fallthrough]];
case eAmf::ByteArray:
[[fallthrough]];
case eAmf::VectorInt:
[[fallthrough]];
case eAmf::VectorUInt:
[[fallthrough]];
case eAmf::VectorDouble:
[[fallthrough]];
case eAmf::VectorObject:
[[fallthrough]];
case eAmf::Dictionary:
case eAmf::Dictionary: {
throw marker;
break;
}
default:
throw std::invalid_argument("Invalid AMF3 marker" + std::to_string(static_cast<int32_t>(marker)));
break;
}
return returnValue;
}
uint32_t AMFDeserialize::ReadU29(RakNet::BitStream& inStream) {
@@ -99,14 +118,14 @@ const std::string AMFDeserialize::ReadString(RakNet::BitStream& inStream) {
}
}
std::unique_ptr<AMFDoubleValue> AMFDeserialize::ReadAmfDouble(RakNet::BitStream& inStream) {
AMFBaseValue* AMFDeserialize::ReadAmfDouble(RakNet::BitStream& inStream) {
double value;
inStream.Read<double>(value);
return std::make_unique<AMFDoubleValue>(value);
return new AMFDoubleValue(value);
}
std::unique_ptr<AMFArrayValue> AMFDeserialize::ReadAmfArray(RakNet::BitStream& inStream) {
auto arrayValue = std::make_unique<AMFArrayValue>();
AMFBaseValue* AMFDeserialize::ReadAmfArray(RakNet::BitStream& inStream) {
auto arrayValue = new AMFArrayValue();
// Read size of dense array
const auto sizeOfDenseArray = (ReadU29(inStream) >> 1);
@@ -124,10 +143,10 @@ std::unique_ptr<AMFArrayValue> AMFDeserialize::ReadAmfArray(RakNet::BitStream& i
return arrayValue;
}
std::unique_ptr<AMFStringValue> AMFDeserialize::ReadAmfString(RakNet::BitStream& inStream) {
return std::make_unique<AMFStringValue>(ReadString(inStream));
AMFBaseValue* AMFDeserialize::ReadAmfString(RakNet::BitStream& inStream) {
return new AMFStringValue(ReadString(inStream));
}
std::unique_ptr<AMFIntValue> AMFDeserialize::ReadAmfInteger(RakNet::BitStream& inStream) {
return std::make_unique<AMFIntValue>(ReadU29(inStream)); // NOTE: NARROWING CONVERSION FROM UINT TO INT. IS THIS INTENDED?
AMFBaseValue* AMFDeserialize::ReadAmfInteger(RakNet::BitStream& inStream) {
return new AMFIntValue(ReadU29(inStream));
}

View File

@@ -1,12 +1,12 @@
#pragma once
#include "Amf3.h"
#include "BitStream.h"
#include <memory>
#include <vector>
#include <string>
class AMFBaseValue;
class AMFDeserialize {
public:
/**
@@ -15,7 +15,7 @@ public:
* @param inStream inStream to read value from.
* @return Returns an AMFValue with all the information from the bitStream in it.
*/
std::unique_ptr<AMFBaseValue> Read(RakNet::BitStream& inStream);
AMFBaseValue* Read(RakNet::BitStream& inStream);
private:
/**
* @brief Private method to read a U29 integer from a bitstream
@@ -39,7 +39,7 @@ private:
* @param inStream bitStream to read data from
* @return Double value represented as an AMFValue
*/
static std::unique_ptr<AMFDoubleValue> ReadAmfDouble(RakNet::BitStream& inStream);
AMFBaseValue* ReadAmfDouble(RakNet::BitStream& inStream);
/**
* @brief Read an AMFArray from a bitStream
@@ -47,7 +47,7 @@ private:
* @param inStream bitStream to read data from
* @return Array value represented as an AMFValue
*/
std::unique_ptr<AMFArrayValue> ReadAmfArray(RakNet::BitStream& inStream);
AMFBaseValue* ReadAmfArray(RakNet::BitStream& inStream);
/**
* @brief Read an AMFString from a bitStream
@@ -55,7 +55,7 @@ private:
* @param inStream bitStream to read data from
* @return String value represented as an AMFValue
*/
std::unique_ptr<AMFStringValue> ReadAmfString(RakNet::BitStream& inStream);
AMFBaseValue* ReadAmfString(RakNet::BitStream& inStream);
/**
* @brief Read an AMFInteger from a bitStream
@@ -63,7 +63,7 @@ private:
* @param inStream bitStream to read data from
* @return Integer value represented as an AMFValue
*/
static std::unique_ptr<AMFIntValue> ReadAmfInteger(RakNet::BitStream& inStream);
AMFBaseValue* ReadAmfInteger(RakNet::BitStream& inStream);
/**
* List of strings read so far saved to be read by reference.

View File

@@ -105,14 +105,27 @@ using AMFDoubleValue = AMFValue<double>;
* and are not to be deleted by a caller.
*/
class AMFArrayValue : public AMFBaseValue {
using AMFAssociative =
std::unordered_map<std::string, std::unique_ptr<AMFBaseValue>, GeneralUtils::transparent_string_hash, std::equal_to<>>;
using AMFDense = std::vector<std::unique_ptr<AMFBaseValue>>;
using AMFAssociative = std::unordered_map<std::string, AMFBaseValue*>;
using AMFDense = std::vector<AMFBaseValue*>;
public:
[[nodiscard]] constexpr eAmf GetValueType() const noexcept override { return eAmf::Array; }
~AMFArrayValue() override {
for (const auto* valueToDelete : GetDense()) {
if (valueToDelete) {
delete valueToDelete;
valueToDelete = nullptr;
}
}
for (auto valueToDelete : GetAssociative()) {
if (valueToDelete.second) {
delete valueToDelete.second;
valueToDelete.second = nullptr;
}
}
}
/**
* Returns the Associative portion of the object
*/
@@ -138,32 +151,30 @@ public:
* or nullptr if a key existed and was not the same type
*/
template <typename ValueType>
[[maybe_unused]] std::pair<AMFValue<ValueType>*, bool> Insert(const std::string_view key, const ValueType value) {
[[maybe_unused]] std::pair<AMFValue<ValueType>*, bool> Insert(const std::string& key, const ValueType value) {
const auto element = m_Associative.find(key);
AMFValue<ValueType>* val = nullptr;
bool found = true;
if (element == m_Associative.cend()) {
auto newVal = std::make_unique<AMFValue<ValueType>>(value);
val = newVal.get();
m_Associative.emplace(key, std::move(newVal));
val = new AMFValue<ValueType>(value);
m_Associative.emplace(key, val);
} else {
val = dynamic_cast<AMFValue<ValueType>*>(element->second.get());
val = dynamic_cast<AMFValue<ValueType>*>(element->second);
found = false;
}
return std::make_pair(val, found);
}
// Associates an array with a string key
[[maybe_unused]] std::pair<AMFBaseValue*, bool> Insert(const std::string_view key) {
[[maybe_unused]] std::pair<AMFBaseValue*, bool> Insert(const std::string& key) {
const auto element = m_Associative.find(key);
AMFArrayValue* val = nullptr;
bool found = true;
if (element == m_Associative.cend()) {
auto newVal = std::make_unique<AMFArrayValue>();
val = newVal.get();
m_Associative.emplace(key, std::move(newVal));
val = new AMFArrayValue();
m_Associative.emplace(key, val);
} else {
val = dynamic_cast<AMFArrayValue*>(element->second.get());
val = dynamic_cast<AMFArrayValue*>(element->second);
found = false;
}
return std::make_pair(val, found);
@@ -171,13 +182,15 @@ public:
// Associates an array with an integer key
[[maybe_unused]] std::pair<AMFBaseValue*, bool> Insert(const size_t index) {
AMFArrayValue* val = nullptr;
bool inserted = false;
if (index >= m_Dense.size()) {
m_Dense.resize(index + 1);
m_Dense.at(index) = std::make_unique<AMFArrayValue>();
val = new AMFArrayValue();
m_Dense.at(index) = val;
inserted = true;
}
return std::make_pair(dynamic_cast<AMFArrayValue*>(m_Dense.at(index).get()), inserted);
return std::make_pair(dynamic_cast<AMFArrayValue*>(m_Dense.at(index)), inserted);
}
/**
@@ -192,13 +205,15 @@ public:
*/
template <typename ValueType>
[[maybe_unused]] std::pair<AMFValue<ValueType>*, bool> Insert(const size_t index, const ValueType value) {
AMFValue<ValueType>* val = nullptr;
bool inserted = false;
if (index >= m_Dense.size()) {
m_Dense.resize(index + 1);
m_Dense.at(index) = std::make_unique<AMFValue<ValueType>>(value);
val = new AMFValue<ValueType>(value);
m_Dense.at(index) = val;
inserted = true;
}
return std::make_pair(dynamic_cast<AMFValue<ValueType>*>(m_Dense.at(index).get()), inserted);
return std::make_pair(dynamic_cast<AMFValue<ValueType>*>(m_Dense.at(index)), inserted);
}
/**
@@ -210,12 +225,13 @@ public:
* @param key The key to associate with the value
* @param value The value to insert
*/
void Insert(const std::string_view key, std::unique_ptr<AMFBaseValue> value) {
void Insert(const std::string& key, AMFBaseValue* const value) {
const auto element = m_Associative.find(key);
if (element != m_Associative.cend() && element->second) {
element->second = std::move(value);
delete element->second;
element->second = value;
} else {
m_Associative.emplace(key, std::move(value));
m_Associative.emplace(key, value);
}
}
@@ -228,11 +244,14 @@ public:
* @param key The key to associate with the value
* @param value The value to insert
*/
void Insert(const size_t index, std::unique_ptr<AMFBaseValue> value) {
if (index >= m_Dense.size()) {
void Insert(const size_t index, AMFBaseValue* const value) {
if (index < m_Dense.size()) {
const AMFDense::const_iterator itr = m_Dense.cbegin() + index;
if (*itr) delete m_Dense.at(index);
} else {
m_Dense.resize(index + 1);
}
m_Dense.at(index) = std::move(value);
m_Dense.at(index) = value;
}
/**
@@ -260,7 +279,8 @@ public:
void Remove(const std::string& key, const bool deleteValue = true) {
const AMFAssociative::const_iterator it = m_Associative.find(key);
if (it != m_Associative.cend()) {
if (deleteValue) m_Associative.erase(it);
if (deleteValue) delete it->second;
m_Associative.erase(it);
}
}
@@ -270,6 +290,7 @@ public:
void Remove(const size_t index) {
if (!m_Dense.empty() && index < m_Dense.size()) {
const auto itr = m_Dense.cbegin() + index;
if (*itr) delete (*itr);
m_Dense.erase(itr);
}
}
@@ -278,16 +299,16 @@ public:
if (!m_Dense.empty()) Remove(m_Dense.size() - 1);
}
[[nodiscard]] AMFArrayValue* GetArray(const std::string_view key) const {
[[nodiscard]] AMFArrayValue* GetArray(const std::string& key) const {
const AMFAssociative::const_iterator it = m_Associative.find(key);
return it != m_Associative.cend() ? dynamic_cast<AMFArrayValue*>(it->second.get()) : nullptr;
return it != m_Associative.cend() ? dynamic_cast<AMFArrayValue*>(it->second) : nullptr;
}
[[nodiscard]] AMFArrayValue* GetArray(const size_t index) const {
return index < m_Dense.size() ? dynamic_cast<AMFArrayValue*>(m_Dense.at(index).get()) : nullptr;
return index < m_Dense.size() ? dynamic_cast<AMFArrayValue*>(m_Dense.at(index)) : nullptr;
}
[[maybe_unused]] inline AMFArrayValue* InsertArray(const std::string_view key) {
[[maybe_unused]] inline AMFArrayValue* InsertArray(const std::string& key) {
return static_cast<AMFArrayValue*>(Insert(key).first);
}
@@ -309,17 +330,17 @@ public:
* @return The AMFValue
*/
template <typename AmfType>
[[nodiscard]] AMFValue<AmfType>* Get(const std::string_view key) const {
[[nodiscard]] AMFValue<AmfType>* Get(const std::string& key) const {
const AMFAssociative::const_iterator it = m_Associative.find(key);
return it != m_Associative.cend() ?
dynamic_cast<AMFValue<AmfType>*>(it->second.get()) :
dynamic_cast<AMFValue<AmfType>*>(it->second) :
nullptr;
}
// Get from the array but dont cast it
[[nodiscard]] AMFBaseValue* Get(const std::string_view key) const {
[[nodiscard]] AMFBaseValue* Get(const std::string& key) const {
const AMFAssociative::const_iterator it = m_Associative.find(key);
return it != m_Associative.cend() ? it->second.get() : nullptr;
return it != m_Associative.cend() ? it->second : nullptr;
}
/**
@@ -334,13 +355,13 @@ public:
template <typename AmfType>
[[nodiscard]] AMFValue<AmfType>* Get(const size_t index) const {
return index < m_Dense.size() ?
dynamic_cast<AMFValue<AmfType>*>(m_Dense.at(index).get()) :
dynamic_cast<AMFValue<AmfType>*>(m_Dense.at(index)) :
nullptr;
}
// Get from the dense but dont cast it
[[nodiscard]] AMFBaseValue* Get(const size_t index) const {
return index < m_Dense.size() ? m_Dense.at(index).get() : nullptr;
return index < m_Dense.size() ? m_Dense.at(index) : nullptr;
}
private:

View File

@@ -201,7 +201,7 @@ void OnTerminate() {
}
void MakeBacktrace() {
struct sigaction sigact{};
struct sigaction sigact;
sigact.sa_sigaction = CritErrHdlr;
sigact.sa_flags = SA_RESTART | SA_SIGINFO;

View File

@@ -129,29 +129,6 @@ namespace GeneralUtils {
std::vector<std::string> GetSqlFileNamesFromFolder(const std::string_view folder);
/**
* Transparent string hasher - used to allow string_view key lookups for maps storing std::string keys
* https://www.reddit.com/r/cpp_questions/comments/12xw3sn/find_stdstring_view_in_unordered_map_with/jhki225/
* https://godbolt.org/z/789xv8Eeq
*/
template <typename... Bases>
struct overload : Bases... {
using is_transparent = void;
using Bases::operator() ... ;
};
struct char_pointer_hash {
auto operator()(const char* const ptr) const noexcept {
return std::hash<std::string_view>{}(ptr);
}
};
using transparent_string_hash = overload<
std::hash<std::string>,
std::hash<std::string_view>,
char_pointer_hash
>;
// Concept constraining to enum types
template <typename T>
concept Enum = std::is_enum_v<T>;

View File

@@ -89,4 +89,4 @@ private:
};
#endif //!__IMPLEMENTATION_H__
#endif //!__IMPLEMENTATION_H__

View File

@@ -1,13 +0,0 @@
#pragma once
#include <cstdint>
namespace MessageType {
enum class Auth : uint32_t {
LOGIN_REQUEST = 0,
LOGOUT_REQUEST,
CREATE_NEW_ACCOUNT_REQUEST,
LEGOINTERFACE_AUTH_RESPONSE,
SESSIONKEY_RECEIVED_CONFIRM,
RUNTIME_CONFIG
};
}

View File

@@ -1,78 +0,0 @@
#pragma once
#include <cstdint>
namespace MessageType {
//! The Internal Chat Packet Identifiers
enum class Chat : uint32_t {
LOGIN_SESSION_NOTIFY = 0,
GENERAL_CHAT_MESSAGE,
PRIVATE_CHAT_MESSAGE,
USER_CHANNEL_CHAT_MESSAGE,
WORLD_DISCONNECT_REQUEST,
WORLD_PROXIMITY_RESPONSE,
WORLD_PARCEL_RESPONSE,
ADD_FRIEND_REQUEST,
ADD_FRIEND_RESPONSE,
REMOVE_FRIEND,
GET_FRIENDS_LIST,
ADD_IGNORE,
REMOVE_IGNORE,
GET_IGNORE_LIST,
TEAM_MISSED_INVITE_CHECK,
TEAM_INVITE,
TEAM_INVITE_RESPONSE,
TEAM_KICK,
TEAM_LEAVE,
TEAM_SET_LOOT,
TEAM_SET_LEADER,
TEAM_GET_STATUS,
GUILD_CREATE,
GUILD_INVITE,
GUILD_INVITE_RESPONSE,
GUILD_LEAVE,
GUILD_KICK,
GUILD_GET_STATUS,
GUILD_GET_ALL,
SHOW_ALL,
BLUEPRINT_MODERATED,
BLUEPRINT_MODEL_READY,
PROPERTY_READY_FOR_APPROVAL,
PROPERTY_MODERATION_CHANGED,
PROPERTY_BUILDMODE_CHANGED,
PROPERTY_BUILDMODE_CHANGED_REPORT,
MAIL,
WORLD_INSTANCE_LOCATION_REQUEST,
REPUTATION_UPDATE,
SEND_CANNED_TEXT,
GMLEVEL_UPDATE,
CHARACTER_NAME_CHANGE_REQUEST,
CSR_REQUEST,
CSR_REPLY,
GM_KICK,
GM_ANNOUNCE,
GM_MUTE,
ACTIVITY_UPDATE,
WORLD_ROUTE_PACKET,
GET_ZONE_POPULATIONS,
REQUEST_MINIMUM_CHAT_MODE,
REQUEST_MINIMUM_CHAT_MODE_PRIVATE,
MATCH_REQUEST,
UGCMANIFEST_REPORT_MISSING_FILE,
UGCMANIFEST_REPORT_DONE_FILE,
UGCMANIFEST_REPORT_DONE_BLUEPRINT,
UGCC_REQUEST,
WHO,
WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE,
ACHIEVEMENT_NOTIFY,
GM_CLOSE_PRIVATE_CHAT_WINDOW,
UNEXPECTED_DISCONNECT,
PLAYER_READY,
GET_DONATION_TOTAL,
UPDATE_DONATION,
PRG_CSR_COMMAND,
HEARTBEAT_REQUEST_FROM_WORLD,
UPDATE_FREE_TRIAL_STATUS,
// CUSTOM DLU MESSAGE ID FOR INTERNAL USE
CREATE_TEAM,
};
}

View File

@@ -1,74 +0,0 @@
#pragma once
#include <cstdint>
namespace MessageType {
enum class Client : uint32_t {
LOGIN_RESPONSE = 0,
LOGOUT_RESPONSE,
LOAD_STATIC_ZONE,
CREATE_OBJECT,
CREATE_CHARACTER,
CREATE_CHARACTER_EXTENDED,
CHARACTER_LIST_RESPONSE,
CHARACTER_CREATE_RESPONSE,
CHARACTER_RENAME_RESPONSE,
CHAT_CONNECT_RESPONSE,
AUTH_ACCOUNT_CREATE_RESPONSE,
DELETE_CHARACTER_RESPONSE,
GAME_MSG,
CONNECT_CHAT,
TRANSFER_TO_WORLD,
IMPENDING_RELOAD_NOTIFY,
MAKE_GM_RESPONSE,
HTTP_MONITOR_INFO_RESPONSE,
SLASH_PUSH_MAP_RESPONSE,
SLASH_PULL_MAP_RESPONSE,
SLASH_LOCK_MAP_RESPONSE,
BLUEPRINT_SAVE_RESPONSE,
BLUEPRINT_LUP_SAVE_RESPONSE,
BLUEPRINT_LOAD_RESPONSE_ITEMID,
BLUEPRINT_GET_ALL_DATA_RESPONSE,
MODEL_INSTANTIATE_RESPONSE,
DEBUG_OUTPUT,
ADD_FRIEND_REQUEST,
ADD_FRIEND_RESPONSE,
REMOVE_FRIEND_RESPONSE,
GET_FRIENDS_LIST_RESPONSE,
UPDATE_FRIEND_NOTIFY,
ADD_IGNORE_RESPONSE,
REMOVE_IGNORE_RESPONSE,
GET_IGNORE_LIST_RESPONSE,
TEAM_INVITE,
TEAM_INVITE_INITIAL_RESPONSE,
GUILD_CREATE_RESPONSE,
GUILD_GET_STATUS_RESPONSE,
GUILD_INVITE,
GUILD_INVITE_INITIAL_RESPONSE,
GUILD_INVITE_FINAL_RESPONSE,
GUILD_INVITE_CONFIRM,
GUILD_ADD_PLAYER,
GUILD_REMOVE_PLAYER,
GUILD_LOGIN_LOGOUT,
GUILD_RANK_CHANGE,
GUILD_DATA,
GUILD_STATUS,
MAIL,
DB_PROXY_RESULT,
SHOW_ALL_RESPONSE,
WHO_RESPONSE,
SEND_CANNED_TEXT,
UPDATE_CHARACTER_NAME,
SET_NETWORK_SIMULATOR,
INVALID_CHAT_MESSAGE,
MINIMUM_CHAT_MODE_RESPONSE,
MINIMUM_CHAT_MODE_RESPONSE_PRIVATE,
CHAT_MODERATION_STRING,
UGC_MANIFEST_RESPONSE,
IN_LOGIN_QUEUE,
SERVER_STATES,
GM_CLOSE_TARGET_CHAT_WINDOW,
GENERAL_TEXT_FOR_LOCALIZATION,
UPDATE_FREE_TRIAL_STATUS,
UGC_DOWNLOAD_FAILED = 120
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,34 +0,0 @@
#pragma once
#include <cstdint>
namespace MessageType {
enum class Master : uint32_t {
REQUEST_PERSISTENT_ID = 1,
REQUEST_PERSISTENT_ID_RESPONSE,
REQUEST_ZONE_TRANSFER,
REQUEST_ZONE_TRANSFER_RESPONSE,
SERVER_INFO,
REQUEST_SESSION_KEY,
SET_SESSION_KEY,
SESSION_KEY_RESPONSE,
PLAYER_ADDED,
PLAYER_REMOVED,
CREATE_PRIVATE_ZONE,
REQUEST_PRIVATE_ZONE,
WORLD_READY,
PREP_ZONE,
SHUTDOWN,
SHUTDOWN_RESPONSE,
SHUTDOWN_IMMEDIATE,
SHUTDOWN_UNIVERSE,
AFFIRM_TRANSFER_REQUEST,
AFFIRM_TRANSFER_RESPONSE,
NEW_SESSION_ALERT
};
}

View File

@@ -1,11 +0,0 @@
#pragma once
#include <cstdint>
namespace MessageType {
//! The Internal Server Packet Identifiers
enum class Server : uint32_t {
VERSION_CONFIRM = 0,
DISCONNECT_NOTIFY,
GENERAL_NOTIFY
};
}

View File

@@ -1,49 +0,0 @@
#pragma once
#include <cstdint>
#include "magic_enum.hpp"
namespace MessageType {
enum class World : uint32_t {
VALIDATION = 1, // Session info
CHARACTER_LIST_REQUEST,
CHARACTER_CREATE_REQUEST,
LOGIN_REQUEST, // Character selected
GAME_MSG,
CHARACTER_DELETE_REQUEST,
CHARACTER_RENAME_REQUEST,
HAPPY_FLOWER_MODE_NOTIFY,
SLASH_RELOAD_MAP, // Reload map cmp
SLASH_PUSH_MAP_REQUEST, // Push map req cmd
SLASH_PUSH_MAP, // Push map cmd
SLASH_PULL_MAP, // Pull map cmd
LOCK_MAP_REQUEST,
GENERAL_CHAT_MESSAGE, // General chat message
HTTP_MONITOR_INFO_REQUEST,
SLASH_DEBUG_SCRIPTS, // Debug scripts cmd
MODELS_CLEAR,
EXHIBIT_INSERT_MODEL,
LEVEL_LOAD_COMPLETE, // Character data request
TMP_GUILD_CREATE,
ROUTE_PACKET, // Social?
POSITION_UPDATE,
MAIL,
WORD_CHECK, // AllowList word check
STRING_CHECK, // AllowList string check
GET_PLAYERS_IN_ZONE,
REQUEST_UGC_MANIFEST_INFO,
BLUEPRINT_GET_ALL_DATA_REQUEST,
CANCEL_MAP_QUEUE,
HANDLE_FUNNESS,
FAKE_PRG_CSR_MESSAGE,
REQUEST_FREE_TRIAL_REFRESH,
GM_SET_FREE_TRIAL_STATUS,
UI_HELP_TOP_5 = 91
};
}
template <>
struct magic_enum::customize::enum_range<MessageType::World> {
static constexpr int min = 0;
static constexpr int max = 91;
};

View File

@@ -8,7 +8,7 @@
#include <set>
#include "BitStream.h"
#include "eConnectionType.h"
#include "MessageType/Client.h"
#include "eClientMessageType.h"
#include "BitStreamUtils.h"
#pragma warning (disable:4251) //Disables SQL warnings
@@ -33,7 +33,7 @@ constexpr uint32_t lowFrameDelta = FRAMES_TO_MS(lowFramerate);
#define CBITSTREAM RakNet::BitStream bitStream;
#define CINSTREAM RakNet::BitStream inStream(packet->data, packet->length, false);
#define CINSTREAM_SKIP_HEADER CINSTREAM if (inStream.GetNumberOfUnreadBits() >= BYTES_TO_BITS(HEADER_SIZE)) inStream.IgnoreBytes(HEADER_SIZE); else inStream.IgnoreBits(inStream.GetNumberOfUnreadBits());
#define CMSGHEADER BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::GAME_MSG);
#define CMSGHEADER BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::GAME_MSG);
#define SEND_PACKET Game::server->Send(bitStream, sysAddr, false);
#define SEND_PACKET_BROADCAST Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);

View File

@@ -0,0 +1,15 @@
#ifndef __EAUTHMESSAGETYPE__H__
#define __EAUTHMESSAGETYPE__H__
#include <cstdint>
enum class eAuthMessageType : uint32_t {
LOGIN_REQUEST = 0,
LOGOUT_REQUEST,
CREATE_NEW_ACCOUNT_REQUEST,
LEGOINTERFACE_AUTH_RESPONSE,
SESSIONKEY_RECEIVED_CONFIRM,
RUNTIME_CONFIG
};
#endif //!__EAUTHMESSAGETYPE__H__

View File

@@ -15,8 +15,7 @@ enum class eCharacterVersion : uint32_t {
// Fixes vault size value
VAULT_SIZE,
// Fixes speed base value in level component
SPEED_BASE,
UP_TO_DATE, // will become NJ_JAYMISSIONS
UP_TO_DATE, // will become SPEED_BASE
};
#endif //!__ECHARACTERVERSION__H__

View File

@@ -0,0 +1,80 @@
#ifndef __ECHATMESSAGETYPE__H__
#define __ECHATMESSAGETYPE__H__
#include <cstdint>
//! The Internal Chat Packet Identifiers
enum class eChatMessageType :uint32_t {
LOGIN_SESSION_NOTIFY = 0,
GENERAL_CHAT_MESSAGE,
PRIVATE_CHAT_MESSAGE,
USER_CHANNEL_CHAT_MESSAGE,
WORLD_DISCONNECT_REQUEST,
WORLD_PROXIMITY_RESPONSE,
WORLD_PARCEL_RESPONSE,
ADD_FRIEND_REQUEST,
ADD_FRIEND_RESPONSE,
REMOVE_FRIEND,
GET_FRIENDS_LIST,
ADD_IGNORE,
REMOVE_IGNORE,
GET_IGNORE_LIST,
TEAM_MISSED_INVITE_CHECK,
TEAM_INVITE,
TEAM_INVITE_RESPONSE,
TEAM_KICK,
TEAM_LEAVE,
TEAM_SET_LOOT,
TEAM_SET_LEADER,
TEAM_GET_STATUS,
GUILD_CREATE,
GUILD_INVITE,
GUILD_INVITE_RESPONSE,
GUILD_LEAVE,
GUILD_KICK,
GUILD_GET_STATUS,
GUILD_GET_ALL,
SHOW_ALL,
BLUEPRINT_MODERATED,
BLUEPRINT_MODEL_READY,
PROPERTY_READY_FOR_APPROVAL,
PROPERTY_MODERATION_CHANGED,
PROPERTY_BUILDMODE_CHANGED,
PROPERTY_BUILDMODE_CHANGED_REPORT,
MAIL,
WORLD_INSTANCE_LOCATION_REQUEST,
REPUTATION_UPDATE,
SEND_CANNED_TEXT,
GMLEVEL_UPDATE,
CHARACTER_NAME_CHANGE_REQUEST,
CSR_REQUEST,
CSR_REPLY,
GM_KICK,
GM_ANNOUNCE,
GM_MUTE,
ACTIVITY_UPDATE,
WORLD_ROUTE_PACKET,
GET_ZONE_POPULATIONS,
REQUEST_MINIMUM_CHAT_MODE,
REQUEST_MINIMUM_CHAT_MODE_PRIVATE,
MATCH_REQUEST,
UGCMANIFEST_REPORT_MISSING_FILE,
UGCMANIFEST_REPORT_DONE_FILE,
UGCMANIFEST_REPORT_DONE_BLUEPRINT,
UGCC_REQUEST,
WHO,
WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE,
ACHIEVEMENT_NOTIFY,
GM_CLOSE_PRIVATE_CHAT_WINDOW,
UNEXPECTED_DISCONNECT,
PLAYER_READY,
GET_DONATION_TOTAL,
UPDATE_DONATION,
PRG_CSR_COMMAND,
HEARTBEAT_REQUEST_FROM_WORLD,
UPDATE_FREE_TRIAL_STATUS,
// CUSTOM DLU MESSAGE ID FOR INTERNAL USE
CREATE_TEAM,
};
#endif //!__ECHATMESSAGETYPE__H__

View File

@@ -0,0 +1,76 @@
#ifndef __ECLIENTMESSAGETYPE__H__
#define __ECLIENTMESSAGETYPE__H__
#include <cstdint>
enum class eClientMessageType : uint32_t {
LOGIN_RESPONSE = 0,
LOGOUT_RESPONSE,
LOAD_STATIC_ZONE,
CREATE_OBJECT,
CREATE_CHARACTER,
CREATE_CHARACTER_EXTENDED,
CHARACTER_LIST_RESPONSE,
CHARACTER_CREATE_RESPONSE,
CHARACTER_RENAME_RESPONSE,
CHAT_CONNECT_RESPONSE,
AUTH_ACCOUNT_CREATE_RESPONSE,
DELETE_CHARACTER_RESPONSE,
GAME_MSG,
CONNECT_CHAT,
TRANSFER_TO_WORLD,
IMPENDING_RELOAD_NOTIFY,
MAKE_GM_RESPONSE,
HTTP_MONITOR_INFO_RESPONSE,
SLASH_PUSH_MAP_RESPONSE,
SLASH_PULL_MAP_RESPONSE,
SLASH_LOCK_MAP_RESPONSE,
BLUEPRINT_SAVE_RESPONSE,
BLUEPRINT_LUP_SAVE_RESPONSE,
BLUEPRINT_LOAD_RESPONSE_ITEMID,
BLUEPRINT_GET_ALL_DATA_RESPONSE,
MODEL_INSTANTIATE_RESPONSE,
DEBUG_OUTPUT,
ADD_FRIEND_REQUEST,
ADD_FRIEND_RESPONSE,
REMOVE_FRIEND_RESPONSE,
GET_FRIENDS_LIST_RESPONSE,
UPDATE_FRIEND_NOTIFY,
ADD_IGNORE_RESPONSE,
REMOVE_IGNORE_RESPONSE,
GET_IGNORE_LIST_RESPONSE,
TEAM_INVITE,
TEAM_INVITE_INITIAL_RESPONSE,
GUILD_CREATE_RESPONSE,
GUILD_GET_STATUS_RESPONSE,
GUILD_INVITE,
GUILD_INVITE_INITIAL_RESPONSE,
GUILD_INVITE_FINAL_RESPONSE,
GUILD_INVITE_CONFIRM,
GUILD_ADD_PLAYER,
GUILD_REMOVE_PLAYER,
GUILD_LOGIN_LOGOUT,
GUILD_RANK_CHANGE,
GUILD_DATA,
GUILD_STATUS,
MAIL,
DB_PROXY_RESULT,
SHOW_ALL_RESPONSE,
WHO_RESPONSE,
SEND_CANNED_TEXT,
UPDATE_CHARACTER_NAME,
SET_NETWORK_SIMULATOR,
INVALID_CHAT_MESSAGE,
MINIMUM_CHAT_MODE_RESPONSE,
MINIMUM_CHAT_MODE_RESPONSE_PRIVATE,
CHAT_MODERATION_STRING,
UGC_MANIFEST_RESPONSE,
IN_LOGIN_QUEUE,
SERVER_STATES,
GM_CLOSE_TARGET_CHAT_WINDOW,
GENERAL_TEXT_FOR_LOCALIZATION,
UPDATE_FREE_TRIAL_STATUS,
UGC_DOWNLOAD_FAILED = 120
};
#endif //!__ECLIENTMESSAGETYPE__H__

File diff suppressed because it is too large Load Diff

View File

@@ -4,9 +4,6 @@
#define __EINVENTORYTYPE__H__
#include <cstdint>
#include "magic_enum.hpp"
static const uint8_t NUMBER_OF_INVENTORIES = 17;
/**
* Represents the different types of inventories an entity may have
@@ -59,10 +56,4 @@ public:
};
};
template <>
struct magic_enum::customize::enum_range<eInventoryType> {
static constexpr int min = 0;
static constexpr int max = 16;
};
#endif //!__EINVENTORYTYPE__H__

View File

@@ -0,0 +1,36 @@
#ifndef __EMASTERMESSAGETYPE__H__
#define __EMASTERMESSAGETYPE__H__
#include <cstdint>
enum class eMasterMessageType : uint32_t {
REQUEST_PERSISTENT_ID = 1,
REQUEST_PERSISTENT_ID_RESPONSE,
REQUEST_ZONE_TRANSFER,
REQUEST_ZONE_TRANSFER_RESPONSE,
SERVER_INFO,
REQUEST_SESSION_KEY,
SET_SESSION_KEY,
SESSION_KEY_RESPONSE,
PLAYER_ADDED,
PLAYER_REMOVED,
CREATE_PRIVATE_ZONE,
REQUEST_PRIVATE_ZONE,
WORLD_READY,
PREP_ZONE,
SHUTDOWN,
SHUTDOWN_RESPONSE,
SHUTDOWN_IMMEDIATE,
SHUTDOWN_UNIVERSE,
AFFIRM_TRANSFER_REQUEST,
AFFIRM_TRANSFER_RESPONSE,
NEW_SESSION_ALERT
};
#endif //!__EMASTERMESSAGETYPE__H__

View File

@@ -1,11 +0,0 @@
#ifndef EPROPERTYSORTTYPE_H
#define EPROPERTYSORTTYPE_H
enum ePropertySortType : int32_t {
SORT_TYPE_FRIENDS = 0,
SORT_TYPE_REPUTATION = 1,
SORT_TYPE_RECENT = 3,
SORT_TYPE_FEATURED = 5
};
#endif //!EPROPERTYSORTTYPE_H

View File

@@ -1,21 +0,0 @@
#ifndef __EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__
#define __EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__
#include <cstdint>
enum class eReponseMoveItemBetweenInventoryTypeCode : int32_t {
SUCCESS,
FAIL_GENERIC,
FAIL_INV_FULL,
FAIL_ITEM_NOT_FOUND,
FAIL_CANT_MOVE_TO_THAT_INV_TYPE,
FAIL_NOT_NEAR_BANK,
FAIL_CANT_SWAP_ITEMS,
FAIL_SOURCE_TYPE,
FAIL_WRONG_DEST_TYPE,
FAIL_SWAP_DEST_TYPE,
FAIL_CANT_MOVE_THINKING_HAT,
FAIL_DISMOUNT_BEFORE_MOVING
};
#endif //!__EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__

View File

@@ -0,0 +1,12 @@
#ifndef __ESERVERMESSAGETYPE__H__
#define __ESERVERMESSAGETYPE__H__
#include <cstdint>
//! The Internal Server Packet Identifiers
enum class eServerMessageType : uint32_t {
VERSION_CONFIRM = 0,
DISCONNECT_NOTIFY,
GENERAL_NOTIFY
};
#endif //!__ESERVERMESSAGETYPE__H__

View File

@@ -0,0 +1,51 @@
#ifndef __EWORLDMESSAGETYPE__H__
#define __EWORLDMESSAGETYPE__H__
#include <cstdint>
#include "magic_enum.hpp"
enum class eWorldMessageType : uint32_t {
VALIDATION = 1, // Session info
CHARACTER_LIST_REQUEST,
CHARACTER_CREATE_REQUEST,
LOGIN_REQUEST, // Character selected
GAME_MSG,
CHARACTER_DELETE_REQUEST,
CHARACTER_RENAME_REQUEST,
HAPPY_FLOWER_MODE_NOTIFY,
SLASH_RELOAD_MAP, // Reload map cmp
SLASH_PUSH_MAP_REQUEST, // Push map req cmd
SLASH_PUSH_MAP, // Push map cmd
SLASH_PULL_MAP, // Pull map cmd
LOCK_MAP_REQUEST,
GENERAL_CHAT_MESSAGE, // General chat message
HTTP_MONITOR_INFO_REQUEST,
SLASH_DEBUG_SCRIPTS, // Debug scripts cmd
MODELS_CLEAR,
EXHIBIT_INSERT_MODEL,
LEVEL_LOAD_COMPLETE, // Character data request
TMP_GUILD_CREATE,
ROUTE_PACKET, // Social?
POSITION_UPDATE,
MAIL,
WORD_CHECK, // AllowList word check
STRING_CHECK, // AllowList string check
GET_PLAYERS_IN_ZONE,
REQUEST_UGC_MANIFEST_INFO,
BLUEPRINT_GET_ALL_DATA_REQUEST,
CANCEL_MAP_QUEUE,
HANDLE_FUNNESS,
FAKE_PRG_CSR_MESSAGE,
REQUEST_FREE_TRIAL_REFRESH,
GM_SET_FREE_TRIAL_STATUS,
UI_HELP_TOP_5 = 91
};
template <>
struct magic_enum::customize::enum_range<eWorldMessageType> {
static constexpr int min = 0;
static constexpr int max = 91;
};
#endif //!__EWORLDMESSAGETYPE__H__

View File

@@ -8,15 +8,9 @@ foreach(file ${DDATABSE_DATABSES_MYSQL_SOURCES})
set(DDATABASE_GAMEDATABASE_SOURCES ${DDATABASE_GAMEDATABASE_SOURCES} "MySQL/${file}")
endforeach()
add_subdirectory(TestSQL)
foreach(file ${DDATABSE_DATABSES_TEST_SQL_SOURCES})
set(DDATABASE_GAMEDATABASE_SOURCES ${DDATABASE_GAMEDATABASE_SOURCES} "TestSQL/${file}")
endforeach()
add_library(dDatabaseGame STATIC ${DDATABASE_GAMEDATABASE_SOURCES})
target_include_directories(dDatabaseGame PUBLIC "."
"ITables" PRIVATE "MySQL" "TestSQL"
"ITables" PRIVATE "MySQL"
"${PROJECT_SOURCE_DIR}/dCommon"
"${PROJECT_SOURCE_DIR}/dCommon/dEnums"
)

View File

@@ -38,8 +38,3 @@ void Database::Destroy(std::string source) {
LOG("Trying to destroy database when it's not connected!");
}
}
void Database::_setDatabase(GameDatabase* const db) {
if (database) delete database;
database = db;
}

View File

@@ -9,8 +9,4 @@ namespace Database {
void Connect();
GameDatabase* Get();
void Destroy(std::string source = "");
// Used for assigning a test database as the handler for database logic.
// Do not use in production code.
void _setDatabase(GameDatabase* const db);
};

View File

@@ -23,7 +23,6 @@
#include "IActivityLog.h"
#include "IIgnoreList.h"
#include "IAccountsRewardCodes.h"
#include "IBehaviors.h"
namespace sql {
class Statement;
@@ -41,8 +40,7 @@ class GameDatabase :
public IMail, public ICommandLog, public IPlayerCheatDetections, public IBugReports,
public IPropertyContents, public IProperty, public IPetNames, public ICharXml,
public IMigrationHistory, public IUgc, public IFriends, public ICharInfo,
public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList,
public IBehaviors {
public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList {
public:
virtual ~GameDatabase() = default;
// TODO: These should be made private.

View File

@@ -33,9 +33,6 @@ public:
// Add a new account to the database.
virtual void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) = 0;
// Update the GameMaster level of an account.
virtual void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) = 0;
};
#endif //!__IACCOUNTS__H__

View File

@@ -1,22 +0,0 @@
#ifndef IBEHAVIORS_H
#define IBEHAVIORS_H
#include <cstdint>
#include "dCommonVars.h"
class IBehaviors {
public:
struct Info {
int32_t behaviorId{};
uint32_t characterId{};
std::string behaviorInfo;
};
// This Add also takes care of updating if it exists.
virtual void AddBehavior(const Info& info) = 0;
virtual std::string GetBehavior(const int32_t behaviorId) = 0;
virtual void RemoveBehavior(const int32_t behaviorId) = 0;
};
#endif //!IBEHAVIORS_H

View File

@@ -4,8 +4,6 @@
#include <cstdint>
#include <optional>
enum ePropertySortType : int32_t;
class IProperty {
public:
struct Info {
@@ -20,33 +18,11 @@ public:
uint32_t lastUpdatedTime{};
uint32_t claimedTime{};
uint32_t reputation{};
float performanceCost{};
};
struct PropertyLookup {
uint32_t mapId{};
std::string searchString;
ePropertySortType sortChoice{};
uint32_t playerId{};
uint32_t numResults{};
uint32_t startIndex{};
uint32_t playerSort{};
};
struct PropertyEntranceResult {
int32_t totalEntriesMatchingQuery{};
// The entries that match the query. This should only contain up to 12 entries.
std::vector<IProperty::Info> entries;
};
// Get the property info for the given property id.
virtual std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) = 0;
// Get the properties for the given property lookup params.
// This is expected to return a result set of up to 12 properties
// so as not to transfer too much data at once.
virtual std::optional<IProperty::PropertyEntranceResult> GetProperties(const PropertyLookup& params) = 0;
// Update the property moderation info for the given property id.
virtual void UpdatePropertyModerationInfo(const IProperty::Info& info) = 0;

View File

@@ -1,7 +1,6 @@
#ifndef __IPROPERTIESCONTENTS__H__
#define __IPROPERTIESCONTENTS__H__
#include <array>
#include <cstdint>
#include <string_view>
@@ -17,7 +16,6 @@ public:
LWOOBJID id{};
LOT lot{};
uint32_t ugcId{};
std::array<int32_t, 5> behaviors{};
};
// Inserts a new UGC model into the database.
@@ -34,7 +32,7 @@ public:
virtual void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) = 0;
// Update the model position and rotation for the given property id.
virtual void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) = 0;
virtual void UpdateModelPositionRotation(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation) = 0;
// Remove the model for the given property id.
virtual void RemoveModel(const LWOOBJID& modelId) = 0;

View File

@@ -4,7 +4,6 @@
#include "Game.h"
#include "dConfig.h"
#include "Logger.h"
#include "dPlatforms.h"
namespace {
std::string databaseName;
@@ -40,13 +39,14 @@ void MySQLDatabase::Connect() {
properties["autoReconnect"] = "true";
databaseName = Game::config->GetValue("mysql_database").c_str();
// `connect(const Properties& props)` segfaults in windows debug, but
// `connect(const SQLString& host, const SQLString& user, const SQLString& pwd)` doesn't handle pipes/unix sockets correctly
#if defined(DARKFLAME_PLATFORM_WIN32) && defined(_DEBUG)
con = driver->connect(properties["hostName"].c_str(), properties["user"].c_str(), properties["password"].c_str());
#else
if (properties.find("localSocket") != properties.end() || properties.find("pipe") != properties.end()) {
con = driver->connect(properties);
#endif
} else {
con = driver->connect(properties["hostName"].c_str(), properties["user"].c_str(), properties["password"].c_str());
}
con->setSchema(databaseName.c_str());
}

View File

@@ -74,7 +74,7 @@ public:
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override;
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override;
void UpdateModelPositionRotation(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation) override;
void RemoveModel(const LWOOBJID& modelId) override;
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override;
@@ -108,11 +108,6 @@ public:
std::vector<IIgnoreList::Info> GetIgnoreList(const uint32_t playerId) override;
void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override;
std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override;
void AddBehavior(const IBehaviors::Info& info) override;
std::string GetBehavior(const int32_t behaviorId) override;
void RemoveBehavior(const int32_t characterId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override;
private:
// Generic query functions that can be used for any query.

View File

@@ -35,7 +35,3 @@ void MySQLDatabase::UpdateAccountPassword(const uint32_t accountId, const std::s
void MySQLDatabase::InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) {
ExecuteInsert("INSERT INTO accounts (name, password, gm_level) VALUES (?, ?, ?);", username, bcryptpassword, static_cast<int32_t>(eGameMasterLevel::OPERATOR));
}
void MySQLDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) {
ExecuteUpdate("UPDATE accounts SET gm_level = ? WHERE id = ?;", static_cast<int32_t>(gmLevel), accountId);
}

View File

@@ -1,19 +0,0 @@
#include "IBehaviors.h"
#include "MySQLDatabase.h"
void MySQLDatabase::AddBehavior(const IBehaviors::Info& info) {
ExecuteInsert(
"INSERT INTO behaviors (behavior_info, character_id, behavior_id) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE behavior_info = ?",
info.behaviorInfo, info.characterId, info.behaviorId, info.behaviorInfo
);
}
void MySQLDatabase::RemoveBehavior(const int32_t behaviorId) {
ExecuteDelete("DELETE FROM behaviors WHERE behavior_id = ?", behaviorId);
}
std::string MySQLDatabase::GetBehavior(const int32_t behaviorId) {
auto result = ExecuteSelect("SELECT behavior_info FROM behaviors WHERE behavior_id = ?", behaviorId);
return result->next() ? result->getString("behavior_info").c_str() : "";
}

View File

@@ -2,7 +2,6 @@ set(DDATABASES_DATABASES_MYSQL_TABLES_SOURCES
"Accounts.cpp"
"AccountsRewardCodes.cpp"
"ActivityLog.cpp"
"Behaviors.cpp"
"BugReports.cpp"
"CharInfo.cpp"
"CharXml.cpp"

View File

@@ -1,140 +1,8 @@
#include "MySQLDatabase.h"
#include "ePropertySortType.h"
std::optional<IProperty::PropertyEntranceResult> MySQLDatabase::GetProperties(const IProperty::PropertyLookup& params) {
std::optional<IProperty::PropertyEntranceResult> result;
std::string query;
std::unique_ptr<sql::ResultSet> properties;
if (params.sortChoice == SORT_TYPE_FEATURED || params.sortChoice == SORT_TYPE_FRIENDS) {
query = R"QUERY(
FROM properties as p
JOIN charinfo as ci
ON ci.prop_clone_id = p.clone_id
where p.zone_id = ?
AND (
p.description LIKE ?
OR p.name LIKE ?
OR ci.name LIKE ?
)
AND p.privacy_option >= ?
AND p.owner_id IN (
SELECT fr.requested_player AS player FROM (
SELECT CASE
WHEN player_id = ? THEN friend_id
WHEN friend_id = ? THEN player_id
END AS requested_player FROM friends
) AS fr
JOIN charinfo AS ci ON ci.id = fr.requested_player
WHERE fr.requested_player IS NOT NULL AND fr.requested_player != ?
) ORDER BY ci.name ASC
)QUERY";
const auto completeQuery = "SELECT p.* " + query + " LIMIT ? OFFSET ?;";
properties = ExecuteSelect(
completeQuery,
params.mapId,
"%" + params.searchString + "%",
"%" + params.searchString + "%",
"%" + params.searchString + "%",
params.playerSort,
params.playerId,
params.playerId,
params.playerId,
params.numResults,
params.startIndex
);
const auto countQuery = "SELECT COUNT(*) as count" + query + ";";
auto count = ExecuteSelect(
countQuery,
params.mapId,
"%" + params.searchString + "%",
"%" + params.searchString + "%",
"%" + params.searchString + "%",
params.playerSort,
params.playerId,
params.playerId,
params.playerId
);
if (count->next()) {
result->totalEntriesMatchingQuery = count->getUInt("count");
}
} else {
if (params.sortChoice == SORT_TYPE_REPUTATION) {
query = R"QUERY(
FROM properties as p
JOIN charinfo as ci
ON ci.prop_clone_id = p.clone_id
where p.zone_id = ?
AND (
p.description LIKE ?
OR p.name LIKE ?
OR ci.name LIKE ?
)
AND p.privacy_option >= ?
ORDER BY p.reputation DESC, p.last_updated DESC
)QUERY";
} else {
query = R"QUERY(
FROM properties as p
JOIN charinfo as ci
ON ci.prop_clone_id = p.clone_id
where p.zone_id = ?
AND (
p.description LIKE ?
OR p.name LIKE ?
OR ci.name LIKE ?
)
AND p.privacy_option >= ?
ORDER BY p.last_updated DESC
)QUERY";
}
const auto completeQuery = "SELECT p.* " + query + " LIMIT ? OFFSET ?;";
properties = ExecuteSelect(
completeQuery,
params.mapId,
"%" + params.searchString + "%",
"%" + params.searchString + "%",
"%" + params.searchString + "%",
params.playerSort,
params.numResults,
params.startIndex
);
const auto countQuery = "SELECT COUNT(*) as count" + query + ";";
auto count = ExecuteSelect(
countQuery,
params.mapId,
"%" + params.searchString + "%",
"%" + params.searchString + "%",
"%" + params.searchString + "%",
params.playerSort
);
if (count->next()) {
result->totalEntriesMatchingQuery = count->getUInt("count");
}
}
while (properties->next()) {
auto& entry = result->entries.emplace_back();
entry.id = properties->getUInt64("id");
entry.ownerId = properties->getUInt64("owner_id");
entry.cloneId = properties->getUInt64("clone_id");
entry.name = properties->getString("name").c_str();
entry.description = properties->getString("description").c_str();
entry.privacyOption = properties->getInt("privacy_option");
entry.rejectionReason = properties->getString("rejection_reason").c_str();
entry.lastUpdatedTime = properties->getUInt("last_updated");
entry.claimedTime = properties->getUInt("time_claimed");
entry.reputation = properties->getUInt("reputation");
entry.modApproved = properties->getUInt("mod_approved");
entry.performanceCost = properties->getFloat("performance_cost");
}
return result;
}
std::optional<IProperty::Info> MySQLDatabase::GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) {
auto propertyEntry = ExecuteSelect(
"SELECT id, owner_id, clone_id, name, description, privacy_option, rejection_reason, last_updated, time_claimed, reputation, mod_approved, performance_cost "
"SELECT id, owner_id, clone_id, name, description, privacy_option, rejection_reason, last_updated, time_claimed, reputation, mod_approved "
"FROM properties WHERE zone_id = ? AND clone_id = ?;", mapId, cloneId);
if (!propertyEntry->next()) {
@@ -153,7 +21,6 @@ std::optional<IProperty::Info> MySQLDatabase::GetPropertyInfo(const LWOMAPID map
toReturn.claimedTime = propertyEntry->getUInt("time_claimed");
toReturn.reputation = propertyEntry->getUInt("reputation");
toReturn.modApproved = propertyEntry->getUInt("mod_approved");
toReturn.performanceCost = propertyEntry->getFloat("performance_cost");
return toReturn;
}

View File

@@ -1,10 +1,7 @@
#include "MySQLDatabase.h"
std::vector<IPropertyContents::Model> MySQLDatabase::GetPropertyModels(const LWOOBJID& propertyId) {
auto result = ExecuteSelect(
"SELECT id, lot, x, y, z, rx, ry, rz, rw, ugc_id, "
"behavior_1, behavior_2, behavior_3, behavior_4, behavior_5 "
"FROM properties_contents WHERE property_id = ?;", propertyId);
auto result = ExecuteSelect("SELECT id, lot, x, y, z, rx, ry, rz, rw, ugc_id FROM properties_contents WHERE property_id = ?;", propertyId);
std::vector<IPropertyContents::Model> toReturn;
toReturn.reserve(result->rowsCount());
@@ -20,12 +17,6 @@ std::vector<IPropertyContents::Model> MySQLDatabase::GetPropertyModels(const LWO
model.rotation.y = result->getFloat("ry");
model.rotation.z = result->getFloat("rz");
model.ugcId = result->getUInt64("ugc_id");
model.behaviors[0] = result->getInt("behavior_1");
model.behaviors[1] = result->getInt("behavior_2");
model.behaviors[2] = result->getInt("behavior_3");
model.behaviors[3] = result->getInt("behavior_4");
model.behaviors[4] = result->getInt("behavior_5");
toReturn.push_back(std::move(model));
}
return toReturn;
@@ -41,23 +32,21 @@ void MySQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IPr
model.id, propertyId, model.ugcId == 0 ? std::nullopt : std::optional(model.ugcId), static_cast<uint32_t>(model.lot),
model.position.x, model.position.y, model.position.z, model.rotation.x, model.rotation.y, model.rotation.z, model.rotation.w,
name, "", // Model description. TODO implement this.
model.behaviors[0], // behavior 1
model.behaviors[1], // behavior 2
model.behaviors[2], // behavior 3
model.behaviors[3], // behavior 4
model.behaviors[4] // behavior 5
0, // behavior 1. TODO implement this.
0, // behavior 2. TODO implement this.
0, // behavior 3. TODO implement this.
0, // behavior 4. TODO implement this.
0 // behavior 5. TODO implement this.
);
} catch (sql::SQLException& e) {
LOG("Error inserting new property model: %s", e.what());
}
}
void MySQLDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
void MySQLDatabase::UpdateModelPositionRotation(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation) {
ExecuteUpdate(
"UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ?, "
"behavior_1 = ?, behavior_2 = ?, behavior_3 = ?, behavior_4 = ?, behavior_5 = ? WHERE id = ?;",
position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w,
behaviors[0].first, behaviors[1].first, behaviors[2].first, behaviors[3].first, behaviors[4].first, propertyId);
"UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ? WHERE id = ?;",
position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, propertyId);
}
void MySQLDatabase::RemoveModel(const LWOOBJID& modelId) {

View File

@@ -1,4 +0,0 @@
SET(DDATABSE_DATABSES_TEST_SQL_SOURCES
"TestSQLDatabase.cpp"
PARENT_SCOPE
)

View File

@@ -1,306 +0,0 @@
#include "TestSQLDatabase.h"
void TestSQLDatabase::Connect() {
}
void TestSQLDatabase::Destroy(std::string source) {
}
sql::PreparedStatement* TestSQLDatabase::CreatePreppedStmt(const std::string& query) {
return nullptr;
}
void TestSQLDatabase::Commit() {
}
bool TestSQLDatabase::GetAutoCommit() {
return {};
}
void TestSQLDatabase::SetAutoCommit(bool value) {
}
void TestSQLDatabase::ExecuteCustomQuery(const std::string_view query) {
}
std::optional<IServers::MasterInfo> TestSQLDatabase::GetMasterInfo() {
return {};
}
std::vector<std::string> TestSQLDatabase::GetApprovedCharacterNames() {
return {};
}
std::vector<FriendData> TestSQLDatabase::GetFriendsList(uint32_t charID) {
return {};
}
std::optional<IFriends::BestFriendStatus> TestSQLDatabase::GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) {
return {};
}
void TestSQLDatabase::SetBestFriendStatus(const uint32_t playerAccountId, const uint32_t friendAccountId, const uint32_t bestFriendStatus) {
}
void TestSQLDatabase::AddFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) {
}
void TestSQLDatabase::RemoveFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) {
}
void TestSQLDatabase::UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) {
}
void TestSQLDatabase::DeleteUgcModelData(const LWOOBJID& modelId) {
}
void TestSQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) {
}
std::vector<IUgc::Model> TestSQLDatabase::GetAllUgcModels() {
return {};
}
void TestSQLDatabase::CreateMigrationHistoryTable() {
}
bool TestSQLDatabase::IsMigrationRun(const std::string_view str) {
return {};
}
void TestSQLDatabase::InsertMigration(const std::string_view str) {
}
std::optional<ICharInfo::Info> TestSQLDatabase::GetCharacterInfo(const uint32_t charId) {
return {};
}
std::optional<ICharInfo::Info> TestSQLDatabase::GetCharacterInfo(const std::string_view charId) {
return {};
}
std::string TestSQLDatabase::GetCharacterXml(const uint32_t accountId) {
return {};
}
void TestSQLDatabase::UpdateCharacterXml(const uint32_t characterId, const std::string_view lxfml) {
}
std::optional<IAccounts::Info> TestSQLDatabase::GetAccountInfo(const std::string_view username) {
return {};
}
void TestSQLDatabase::InsertNewCharacter(const ICharInfo::Info info) {
}
void TestSQLDatabase::InsertCharacterXml(const uint32_t accountId, const std::string_view lxfml) {
}
std::vector<uint32_t> TestSQLDatabase::GetAccountCharacterIds(uint32_t accountId) {
return {};
}
void TestSQLDatabase::DeleteCharacter(const uint32_t characterId) {
}
void TestSQLDatabase::SetCharacterName(const uint32_t characterId, const std::string_view name) {
}
void TestSQLDatabase::SetPendingCharacterName(const uint32_t characterId, const std::string_view name) {
}
void TestSQLDatabase::UpdateLastLoggedInCharacter(const uint32_t characterId) {
}
void TestSQLDatabase::SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) {
}
std::optional<IPetNames::Info> TestSQLDatabase::GetPetNameInfo(const LWOOBJID& petId) {
return {};
}
std::optional<IProperty::Info> TestSQLDatabase::GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) {
return {};
}
void TestSQLDatabase::UpdatePropertyModerationInfo(const IProperty::Info& info) {
}
void TestSQLDatabase::UpdatePropertyDetails(const IProperty::Info& info) {
}
void TestSQLDatabase::InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) {
}
std::vector<IPropertyContents::Model> TestSQLDatabase::GetPropertyModels(const LWOOBJID& propertyId) {
return {};
}
void TestSQLDatabase::RemoveUnreferencedUgcModels() {
}
void TestSQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) {
}
void TestSQLDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
}
void TestSQLDatabase::RemoveModel(const LWOOBJID& modelId) {
}
void TestSQLDatabase::UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) {
}
void TestSQLDatabase::InsertNewBugReport(const IBugReports::Info& info) {
}
void TestSQLDatabase::InsertCheatDetection(const IPlayerCheatDetections::Info& info) {
}
void TestSQLDatabase::InsertNewMail(const IMail::MailInfo& mail) {
}
void TestSQLDatabase::InsertNewUgcModel(std::istringstream& sd0Data, const uint32_t blueprintId, const uint32_t accountId, const uint32_t characterId) {
}
std::vector<IMail::MailInfo> TestSQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
return {};
}
std::optional<IMail::MailInfo> TestSQLDatabase::GetMail(const uint64_t mailId) {
return {};
}
uint32_t TestSQLDatabase::GetUnreadMailCount(const uint32_t characterId) {
return {};
}
void TestSQLDatabase::MarkMailRead(const uint64_t mailId) {
}
void TestSQLDatabase::DeleteMail(const uint64_t mailId) {
}
void TestSQLDatabase::ClaimMailItem(const uint64_t mailId) {
}
void TestSQLDatabase::InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) {
}
void TestSQLDatabase::UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) {
}
void TestSQLDatabase::UpdateAccountBan(const uint32_t accountId, const bool banned) {
}
void TestSQLDatabase::UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) {
}
void TestSQLDatabase::InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) {
}
void TestSQLDatabase::SetMasterIp(const std::string_view ip, const uint32_t port) {
}
std::optional<uint32_t> TestSQLDatabase::GetCurrentPersistentId() {
return {};
}
void TestSQLDatabase::InsertDefaultPersistentId() {
}
void TestSQLDatabase::UpdatePersistentId(const uint32_t id) {
}
std::optional<uint32_t> TestSQLDatabase::GetDonationTotal(const uint32_t activityId) {
return {};
}
std::optional<bool> TestSQLDatabase::IsPlaykeyActive(const int32_t playkeyId) {
return {};
}
std::vector<IUgc::Model> TestSQLDatabase::GetUgcModels(const LWOOBJID& propertyId) {
return {};
}
void TestSQLDatabase::AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) {
}
void TestSQLDatabase::RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) {
}
std::vector<IIgnoreList::Info> TestSQLDatabase::GetIgnoreList(const uint32_t playerId) {
return {};
}
void TestSQLDatabase::InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) {
}
std::vector<uint32_t> TestSQLDatabase::GetRewardCodesByAccountID(const uint32_t account_id) {
return {};
}
void TestSQLDatabase::AddBehavior(const IBehaviors::Info& info) {
}
std::string TestSQLDatabase::GetBehavior(const int32_t behaviorId) {
return {};
}
void TestSQLDatabase::RemoveBehavior(const int32_t behaviorId) {
}
void TestSQLDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) {
}

View File

@@ -1,96 +0,0 @@
#ifndef TESTSQLDATABASE_H
#define TESTSQLDATABASE_H
#include "GameDatabase.h"
class TestSQLDatabase : public GameDatabase {
void Connect() override;
void Destroy(std::string source = "") override;
sql::PreparedStatement* CreatePreppedStmt(const std::string& query) override;
void Commit() override;
bool GetAutoCommit() override;
void SetAutoCommit(bool value) override;
void ExecuteCustomQuery(const std::string_view query) override;
// Overloaded queries
std::optional<IServers::MasterInfo> GetMasterInfo() override;
std::vector<std::string> GetApprovedCharacterNames() override;
std::vector<FriendData> GetFriendsList(uint32_t charID) override;
std::optional<IFriends::BestFriendStatus> GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) override;
void SetBestFriendStatus(const uint32_t playerAccountId, const uint32_t friendAccountId, const uint32_t bestFriendStatus) override;
void AddFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override;
void RemoveFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override;
void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) override;
void DeleteUgcModelData(const LWOOBJID& modelId) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) override;
std::vector<IUgc::Model> GetAllUgcModels() override;
void CreateMigrationHistoryTable() override;
bool IsMigrationRun(const std::string_view str) override;
void InsertMigration(const std::string_view str) override;
std::optional<ICharInfo::Info> GetCharacterInfo(const uint32_t charId) override;
std::optional<ICharInfo::Info> GetCharacterInfo(const std::string_view charId) override;
std::string GetCharacterXml(const uint32_t accountId) override;
void UpdateCharacterXml(const uint32_t characterId, const std::string_view lxfml) override;
std::optional<IAccounts::Info> GetAccountInfo(const std::string_view username) override;
void InsertNewCharacter(const ICharInfo::Info info) override;
void InsertCharacterXml(const uint32_t accountId, const std::string_view lxfml) override;
std::vector<uint32_t> GetAccountCharacterIds(uint32_t accountId) override;
void DeleteCharacter(const uint32_t characterId) override;
void SetCharacterName(const uint32_t characterId, const std::string_view name) override;
void SetPendingCharacterName(const uint32_t characterId, const std::string_view name) override;
void UpdateLastLoggedInCharacter(const uint32_t characterId) override;
void SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) override;
std::optional<IPetNames::Info> GetPetNameInfo(const LWOOBJID& petId) override;
std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override;
void UpdatePropertyModerationInfo(const IProperty::Info& info) override;
void UpdatePropertyDetails(const IProperty::Info& info) override;
void InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) override;
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override;
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override;
void RemoveModel(const LWOOBJID& modelId) override;
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override;
void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override;
void InsertNewMail(const IMail::MailInfo& mail) override;
void InsertNewUgcModel(
std::istringstream& sd0Data,
const uint32_t blueprintId,
const uint32_t accountId,
const uint32_t characterId) override;
std::vector<IMail::MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<IMail::MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override;
void ClaimMailItem(const uint64_t mailId) override;
void InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) override;
void UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) override;
void UpdateAccountBan(const uint32_t accountId, const bool banned) override;
void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override;
void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override;
void SetMasterIp(const std::string_view ip, const uint32_t port) override;
std::optional<uint32_t> GetCurrentPersistentId() override;
void InsertDefaultPersistentId() override;
void UpdatePersistentId(const uint32_t id) override;
std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) override;
std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) override;
std::vector<IUgc::Model> GetUgcModels(const LWOOBJID& propertyId) override;
void AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) override;
void RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) override;
std::vector<IIgnoreList::Info> GetIgnoreList(const uint32_t playerId) override;
void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override;
std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override;
void AddBehavior(const IBehaviors::Info& info) override;
std::string GetBehavior(const int32_t behaviorId) override;
void RemoveBehavior(const int32_t behaviorId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override { return {}; };
};
#endif //!TESTSQLDATABASE_H

View File

@@ -0,0 +1,677 @@
#include "AdditionalEntityData.h"
#include "NejlikaData.h"
#include <DestroyableComponent.h>
#include <LevelProgressionComponent.h>
#include <InventoryComponent.h>
#include <BaseCombatAIComponent.h>
#include <TeamManager.h>
#include <ControllablePhysicsComponent.h>
#include <Item.h>
#include <queue>
using namespace nejlika;
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, ModifierOperator op, bool resistance) const
{
float total = 0;
for (const auto& modifier : activeModifiers) {
if (modifier.GetConvertTo() != ModifierType::Invalid) {
continue;
}
if (modifier.GetType() != type || modifier.GetOperator() != op || modifier.IsResistance() != resistance) {
continue;
}
total += modifier.GetValue();
}
return total;
}
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers, ModifierOperator op, bool resistance) const {
float total = 0;
for (const auto& modifier : additionalModifiers) {
if (modifier.GetConvertTo() != ModifierType::Invalid) {
continue;
}
if (modifier.GetType() != type || modifier.GetOperator() != op || modifier.IsResistance() != resistance) {
continue;
}
total += modifier.GetValue();
}
return total + CalculateModifier(type, op, resistance);
}
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type, int32_t level) const
{
const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot);
if (!templateDataOpt.has_value()) {
return 0;
}
const auto& templateData = *templateDataOpt.value();
const auto scaler = templateData.GetScaler(type, false, level);
float additive = CalculateModifier(type, ModifierOperator::Additive, false);
if (scaler != 0 && additive >= scaler) {
additive -= scaler;
}
float multiplicative = CalculateModifier(type, ModifierOperator::Multiplicative, false);
return (scaler + additive) * (1 + multiplicative / 100);
}
float nejlika::AdditionalEntityData::CalculateModifier(ModifierType type) const {
return CalculateModifier(type, level);
}
float nejlika::AdditionalEntityData::CalculateFinalModifier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers, int32_t level) const
{
const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot);
if (!templateDataOpt.has_value()) {
return 0;
}
const auto& templateData = *templateDataOpt.value();
const auto scaler = templateData.GetScaler(type, false, level);
float additive = CalculateModifier(type, additionalModifiers, ModifierOperator::Additive, false);
if (scaler != 0 && additive >= scaler) {
additive -= scaler;
}
float multiplicative = CalculateModifier(type, additionalModifiers, ModifierOperator::Multiplicative, false);
static const std::unordered_set<ModifierType> elementalDamage = {
ModifierType::Fire,
ModifierType::Cold,
ModifierType::Lightning
};
if (type == ModifierType::Health) {
if (lot == 1) additive += 25;
additive += CalculateModifier(ModifierType::Physique, additionalModifiers, ModifierOperator::Additive, false) * 2.5f;
additive += CalculateModifier(ModifierType::Cunning, additionalModifiers, ModifierOperator::Additive, false) * 1.0f;
additive += CalculateModifier(ModifierType::Spirit, additionalModifiers, ModifierOperator::Additive, false) * 1.0f;
}
else if (type == ModifierType::Imagination) {
additive += CalculateModifier(ModifierType::Spirit, additionalModifiers, ModifierOperator::Additive, false) * 2.0f;
}
else if (type == ModifierType::Seperation || type == ModifierType::InternalDisassembly || type == ModifierType::Physical) {
multiplicative += CalculateModifier(ModifierType::Cunning, additionalModifiers, ModifierOperator::Additive, false) * 0.33f;
}
else if (type == ModifierType::Pierce) {
multiplicative += CalculateModifier(ModifierType::Cunning, additionalModifiers, ModifierOperator::Additive, false) * 0.285f;
}
else if (nejlika::IsOverTimeType(type) || nejlika::IsNormalDamageType(type)) {
multiplicative += CalculateModifier(ModifierType::Spirit, additionalModifiers, ModifierOperator::Additive, false) * 0.33f;
}
else if (type == ModifierType::ImaginationRegen) {
const auto spirit = CalculateModifier(ModifierType::Spirit, additionalModifiers, ModifierOperator::Additive, false);
additive += spirit * 0.01f + 1;
multiplicative += spirit * 0.25f;
}
else if (type == ModifierType::HealthRegen) {
const auto physique = CalculateModifier(ModifierType::Physique, additionalModifiers, ModifierOperator::Additive, false);
additive += physique * 0.04f;
}
else if (type == ModifierType::Defensive) {
additive += CalculateModifier(ModifierType::Physique, additionalModifiers, ModifierOperator::Additive, false) * 0.5f;
if (lot == 1) additive += level * 10;
}
else if (type == ModifierType::Offensive) {
additive += CalculateModifier(ModifierType::Cunning, additionalModifiers, ModifierOperator::Additive, false) * 0.5f;
if (lot == 1) additive += level * 10;
}
if (elementalDamage.contains(type)) {
additive += CalculateModifier(ModifierType::Elemental, additionalModifiers, ModifierOperator::Additive, false) / elementalDamage.size();
multiplicative += CalculateModifier(ModifierType::Elemental, additionalModifiers, ModifierOperator::Multiplicative, false) / elementalDamage.size();
}
if (nejlika::IsNormalDamageType(type) || nejlika::IsOverTimeType(type)) {
additive += CalculateModifier(ModifierType::Damage, additionalModifiers, ModifierOperator::Additive, false);
multiplicative += CalculateModifier(ModifierType::Damage, additionalModifiers, ModifierOperator::Multiplicative, false);
}
float total = (scaler + additive) * (1 + multiplicative / 100);
return total;
}
float nejlika::AdditionalEntityData::CalculateResistance(ModifierType type) const
{
type = nejlika::GetResistanceType(type);
return CalculateModifier(type, ModifierOperator::Multiplicative, true);
}
float nejlika::AdditionalEntityData::CalculateMultiplier(ModifierType type) const
{
return 100 + CalculateModifier(type, ModifierOperator::Multiplicative, false);
}
std::vector<ModifierInstance> nejlika::AdditionalEntityData::TriggerUpgradeItems(UpgradeTriggerType triggerType, const TriggerParameters& params) {
auto* entity = Game::entityManager->GetEntity(id);
if (entity == nullptr) {
return {};
}
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) {
return {};
}
std::vector<ModifierInstance> result;
for (const auto& itemID : upgradeItems) {
auto* item = inventoryComponent->FindItemById(itemID);
if (item == nullptr) {
continue;
}
const auto upgradeDataOpt = NejlikaData::GetUpgradeTemplate(item->GetLot());
if (!upgradeDataOpt.has_value()) {
continue;
}
const auto& upgradeData = *upgradeDataOpt.value();
const auto modifiers = upgradeData.Trigger(item->GetCount(), triggerType, id, params);
result.insert(result.end(), modifiers.begin(), modifiers.end());
}
return result;
}
std::vector<ModifierInstance> nejlika::AdditionalEntityData::TriggerUpgradeItems(UpgradeTriggerType triggerType) {
return TriggerUpgradeItems(triggerType, {});
}
void nejlika::AdditionalEntityData::InitializeSkills() {
auto* entity = Game::entityManager->GetEntity(id);
if (entity == nullptr) {
return;
}
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) {
return;
}
struct entry {
LWOOBJID id;
int32_t priority;
entry(LWOOBJID id, int32_t priority) : id(id), priority(priority) {}
};
std::vector<entry> items;
for (const auto& itemID : upgradeItems) {
auto* item = inventoryComponent->FindItemById(itemID);
if (item == nullptr) {
continue;
}
const auto priority = item->GetSlot();
items.push_back(entry(itemID, priority));
}
std::sort(items.begin(), items.end(), [](const entry& a, const entry& b) {
return a.priority < b.priority;
});
for (const auto& item : items) {
AddSkills(item.id);
}
}
void nejlika::AdditionalEntityData::AddSkills(LWOOBJID item) {
if (!upgradeItems.contains(item)) {
return;
}
auto* entity = Game::entityManager->GetEntity(id);
if (entity == nullptr) {
return;
}
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) {
return;
}
auto* itemData = inventoryComponent->FindItemById(item);
if (itemData == nullptr) {
return;
}
const auto upgradeDataOpt = NejlikaData::GetUpgradeTemplate(itemData->GetLot());
if (!upgradeDataOpt.has_value()) {
return;
}
const auto& upgradeData = *upgradeDataOpt.value();
LOG("Adding skills for item %i", id);
upgradeData.AddSkills(id);
}
void nejlika::AdditionalEntityData::RemoveSkills(LOT lot) {
const auto upgradeDataOpt = NejlikaData::GetUpgradeTemplate(lot);
if (!upgradeDataOpt.has_value()) {
return;
}
const auto& upgradeData = *upgradeDataOpt.value();
LOG("Removing skills for item %i", id);
upgradeData.RemoveSkills(id);
}
std::vector<ModifierInstance> nejlika::AdditionalEntityData::CalculateMainWeaponDamage() {
auto* entity = Game::entityManager->GetEntity(id);
if (entity == nullptr) {
return {};
}
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) {
return {};
}
Item* mainWeapon = nullptr;
for (const auto& [location, item] : inventoryComponent->GetEquippedItems()) {
if (location == "special_r") {
mainWeapon = inventoryComponent->FindItemById(item.id);
break;
}
}
if (mainWeapon == nullptr) {
return {};
}
const auto additionalItemDataOpt = NejlikaData::GetAdditionalItemData(mainWeapon->GetId());
if (!additionalItemDataOpt.has_value()) {
return {};
}
const auto& additionalItemData = *additionalItemDataOpt.value();
std::vector<ModifierInstance> result;
for (const auto& modifier : additionalItemData.GetModifierInstances()) {
if (nejlika::IsNormalDamageType(modifier.GetType()) || nejlika::IsOverTimeType(modifier.GetType()) || nejlika::IsDurationType(modifier.GetType())) {
if (modifier.GetOperator() == ModifierOperator::Additive && modifier.GetUpgradeName().empty()) {
result.push_back(modifier);
}
}
}
return result;
}
void nejlika::AdditionalEntityData::RollStandardModifiers(int32_t level) {
standardModifiers.clear();
const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot);
if (templateDataOpt.has_value()) {
const auto& templateData = *templateDataOpt.value();
const auto modifiers = templateData.GenerateModifiers(level);
standardModifiers.insert(standardModifiers.end(), modifiers.begin(), modifiers.end());
}
const auto objectDataVec = NejlikaData::GetModifierNameTemplates(ModifierNameType::Object);
for (const auto& objectData : objectDataVec) {
if (objectData.GetLOT() != lot) {
continue;
}
const auto modifiers = objectData.GenerateModifiers(level);
standardModifiers.insert(standardModifiers.end(), modifiers.begin(), modifiers.end());
}
}
void nejlika::AdditionalEntityData::TriggerPassiveRegeneration() {
auto* entity = Game::entityManager->GetEntity(id);
if (entity == nullptr) {
return;
}
auto* destroyable = entity->GetComponent<DestroyableComponent>();
if (destroyable == nullptr) {
return;
}
const auto healthRegen = CalculateFinalModifier(ModifierType::HealthRegen, {}, level);
const auto imaginationRegen = CalculateFinalModifier(ModifierType::ImaginationRegen, {}, level);
if (healthRegen > 0) {
destroyable->SetHealth(std::min(destroyable->GetHealth() + static_cast<int32_t>(healthRegen), static_cast<int32_t>(destroyable->GetMaxHealth())));
}
if (imaginationRegen > 0) {
destroyable->SetImagination(std::min(destroyable->GetImagination() + static_cast<int32_t>(imaginationRegen), static_cast<int32_t>(destroyable->GetMaxImagination())));
}
Game::entityManager->SerializeEntity(entity);
// Trigger it again in 1 second
entity->AddCallbackTimer(1.0f, [this]() {
TriggerPassiveRegeneration();
});
}
float nejlika::AdditionalEntityData::CalculateMultiplier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers) const {
return 100 + CalculateModifier(type, additionalModifiers, ModifierOperator::Multiplicative, false);
}
std::unordered_map<ModifierType, std::unordered_map<ModifierType, float>> nejlika::AdditionalEntityData::CalculateDamageConversion(std::vector<ModifierInstance>& additionalModifiers) const {
std::unordered_map<ModifierType, std::unordered_map<ModifierType, float>> conversion;
for (const auto& modifier : activeModifiers) {
if (modifier.GetConvertTo() == ModifierType::Invalid) {
continue;
}
conversion[modifier.GetType()][modifier.GetConvertTo()] += modifier.GetValue();
}
for (const auto& modifier : additionalModifiers) {
if (modifier.GetConvertTo() == ModifierType::Invalid) {
continue;
}
conversion[modifier.GetType()][modifier.GetConvertTo()] += modifier.GetValue();
}
// Third pass: adjust bidirectional conversions
auto copy = conversion; // Create a copy to iterate over
for (const auto& [type, convertMap] : copy) {
for (const auto& [convertTo, value] : convertMap) {
if (conversion[convertTo][type] > 0) {
if (value > conversion[convertTo][type]) {
conversion[type][convertTo] -= conversion[convertTo][type];
conversion[convertTo][type] = 0; // Ensure no negative values
} else {
conversion[convertTo][type] -= value;
conversion[type][convertTo] = 0; // Ensure no negative values
}
}
}
}
// Fourth pass: if a type converts to multiple types, and the sum of the conversion values is greater than 100, normalize the values
for (const auto& [type, convertMap] : conversion) {
float sum = 0;
for (const auto& [convertTo, value] : convertMap) {
sum += value;
}
if (sum > 100) {
for (const auto& [convertTo, value] : convertMap) {
conversion[type][convertTo] = value / sum * 100;
}
}
}
return conversion;
}
void nejlika::AdditionalEntityData::ApplyToEntity() {
const auto templateDataOpt = NejlikaData::GetEntityTemplate(lot);
if (!templateDataOpt.has_value()) {
return;
}
const auto& templateData = *templateDataOpt.value();
auto* entity = Game::entityManager->GetEntity(id);
if (entity == nullptr) {
return;
}
auto* destroyable = entity->GetComponent<DestroyableComponent>();
if (destroyable == nullptr) {
return;
}
auto* levelProgression = entity->GetComponent<LevelProgressionComponent>();
if (levelProgression != nullptr) {
this->level = levelProgression->GetLevel();
}
else {
this->level = templateData.GetMinLevel();
}
if (!initialized) {
RollStandardModifiers(level);
}
activeModifiers = standardModifiers;
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent) {
for (const auto& [location, item] : inventoryComponent->GetEquippedItems()) {
const auto itemDataOpt = NejlikaData::GetAdditionalItemData(item.id);
if (!itemDataOpt.has_value()) {
continue;
}
const auto& itemData = *itemDataOpt.value();
const auto& itemModifiers = itemData.GetModifierInstances();
for (const auto& modifier : itemModifiers) {
if (nejlika::IsNormalDamageType(modifier.GetType()) || nejlika::IsOverTimeType(modifier.GetType()) || nejlika::IsDurationType(modifier.GetType())) {
if (modifier.GetOperator() == ModifierOperator::Additive && modifier.GetUpgradeName().empty()) {
continue;
}
}
activeModifiers.push_back(modifier);
}
}
for (const auto& upgradeItem : upgradeItems) {
auto* item = inventoryComponent->FindItemById(upgradeItem);
if (item == nullptr) {
continue;
}
LOG("Applying upgrade item %i", item->GetLot());
const auto itemDataOpt = NejlikaData::GetUpgradeTemplate(item->GetLot());
if (!itemDataOpt.has_value()) {
LOG("Upgrade item %i has no data", item->GetLot());
continue;
}
const auto& itemData = *itemDataOpt.value();
const auto& itemModifiers = itemData.GenerateModifiers(item->GetCount());
LOG("Upgrade item %i has %i modifiers with level %i", item->GetLot(), itemModifiers.size(), item->GetCount());
activeModifiers.insert(activeModifiers.end(), itemModifiers.begin(), itemModifiers.end());
}
}
destroyable->SetMaxHealth(static_cast<int32_t>(CalculateFinalModifier(ModifierType::Health, {}, level)));
destroyable->SetMaxArmor(static_cast<int32_t>(CalculateFinalModifier(ModifierType::Armor, {}, level)));
//if (!entity->IsPlayer()) {
destroyable->SetMaxImagination(static_cast<int32_t>(CalculateFinalModifier(ModifierType::Imagination, {}, level)));
//}
if (initialized) {
return;
}
destroyable->SetHealth(destroyable->GetMaxHealth());
destroyable->SetArmor(destroyable->GetMaxArmor());
//if (!entity->IsPlayer()) {
destroyable->SetImagination(destroyable->GetMaxImagination());
//}
if (entity->IsPlayer()) {
auto* controllablePhysicsComponent = entity->GetComponent<ControllablePhysicsComponent>();
if (controllablePhysicsComponent) controllablePhysicsComponent->SetSpeedMultiplier(CalculateMultiplier(ModifierType::Speed) / 100.0f);
}
TriggerPassiveRegeneration();
initialized = true;
}
void nejlika::AdditionalEntityData::CheckForRescale(AdditionalEntityData* other) {
auto* entity = Game::entityManager->GetEntity(id);
if (entity == nullptr) {
return;
}
if (entity->IsPlayer()) {
return;
}
auto* baseCombat = entity->GetComponent<BaseCombatAIComponent>();
if (baseCombat == nullptr) {
return;
}
const auto& threats = baseCombat->GetThreats();
int32_t totalThreats = 0;
int32_t totalLevel = 0;
for (const auto& [threat, _] : threats) {
const auto threatEntityOpt = NejlikaData::GetAdditionalEntityData(threat);
if (!threatEntityOpt.has_value()) {
continue;
}
const auto& threatEntity = *threatEntityOpt.value();
if (other->id == threatEntity.id) {
continue;
}
totalLevel += threatEntity.level;
totalThreats++;
}
if (other != nullptr) {
totalLevel += other->level;
totalThreats++;
auto* team = TeamManager::Instance()->GetTeam(other->id);
if (team != nullptr) {
for (const auto& member : team->members) {
const auto memberEntityOpt = NejlikaData::GetAdditionalEntityData(member);
if (!memberEntityOpt.has_value()) {
continue;
}
const auto& memberEntity = *memberEntityOpt.value();
if (other->id == memberEntity.id) {
continue;
}
totalLevel += memberEntity.level;
totalThreats++;
}
}
}
if (totalThreats == 0) {
return;
}
const auto averageLevel = totalLevel / totalThreats;
// Can't rescale to a lower level
if (averageLevel <= level) {
return;
}
level = averageLevel;
auto* destroyable = entity->GetComponent<DestroyableComponent>();
if (destroyable == nullptr) {
return;
}
float healthPercentage = destroyable->GetMaxHealth() == 0 ? 1 : static_cast<float>(destroyable->GetHealth()) / destroyable->GetMaxHealth();
float armorPercentage = destroyable->GetMaxArmor() == 0 ? 1 : static_cast<float>(destroyable->GetArmor()) / destroyable->GetMaxArmor();
float imaginationPercentage = destroyable->GetMaxImagination() == 0 ? 1 : static_cast<float>(destroyable->GetImagination()) / destroyable->GetMaxImagination();
RollStandardModifiers(level);
ApplyToEntity();
destroyable->SetHealth(static_cast<int32_t>(destroyable->GetMaxHealth() * healthPercentage));
destroyable->SetArmor(static_cast<int32_t>(destroyable->GetMaxArmor() * armorPercentage));
destroyable->SetImagination(static_cast<int32_t>(destroyable->GetMaxImagination() * imaginationPercentage));
LOG("Rescaled entity %i to level %d", entity->GetLOT(), level);
}

View File

@@ -0,0 +1,109 @@
#pragma once
#include <cstdint>
#include <vector>
#include "Entity.h"
#include "ModifierInstance.h"
#include "EntityTemplate.h"
#include "UpgradeTriggerType.h"
#include "TriggerParameters.h"
#include <unordered_set>
namespace nejlika
{
class AdditionalEntityData
{
public:
AdditionalEntityData() = default;
AdditionalEntityData(LWOOBJID id, LOT lot) : id(id), lot(lot) {}
float CalculateModifier(ModifierType type, ModifierOperator op, bool resistance) const;
float CalculateModifier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers, ModifierOperator op, bool resistance) const;
float CalculateModifier(ModifierType type, int32_t level) const;
float CalculateModifier(ModifierType type) const;
float CalculateFinalModifier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers, int32_t level) const;
float CalculateResistance(ModifierType type) const;
/**
* @brief Calculate the multiplier for a given modifier type. With a base value of 100 (%).
*
* @param type The modifier type.
* @return The multiplier.
*/
float CalculateMultiplier(ModifierType type) const;
/**
* @brief Calculate the multiplier for a given modifier type. With a base value of 100 (%).
*
* @param type The modifier type.
* @param additionalModifiers Additional modifiers to apply.
* @return The multiplier.
*/
float CalculateMultiplier(ModifierType type, const std::vector<ModifierInstance>& additionalModifiers) const;
/**
* @brief Calculate damage conversation mapping.
*
* @param additionalModifiers Additional modifiers to apply.
*
* @return The damage conversion mapping.
*/
std::unordered_map<ModifierType, std::unordered_map<ModifierType, float>> CalculateDamageConversion(std::vector<ModifierInstance>& additionalModifiers) const;
void ApplyToEntity();
void CheckForRescale(AdditionalEntityData* other);
int32_t GetLevel() const { return level; }
LWOOBJID GetID() const { return id; }
LOT GetLOT() const { return lot; }
const std::unordered_set<LWOOBJID>& GetUpgradeItems() const { return upgradeItems; }
void AddUpgradeItem(LWOOBJID id) { upgradeItems.insert(id); }
void RemoveUpgradeItem(LWOOBJID id) { upgradeItems.erase(id); }
std::vector<ModifierInstance> TriggerUpgradeItems(UpgradeTriggerType triggerType, const TriggerParameters& params);
std::vector<ModifierInstance> TriggerUpgradeItems(UpgradeTriggerType triggerType);
void InitializeSkills();
void AddSkills(LWOOBJID item);
void RemoveSkills(LOT lot);
const std::vector<ModifierInstance>& GetActiveModifiers() const { return activeModifiers; }
std::vector<ModifierInstance> CalculateMainWeaponDamage();
private:
void RollStandardModifiers(int32_t level);
void TriggerPassiveRegeneration();
bool initialized = false;
std::vector<ModifierInstance> standardModifiers;
std::vector<ModifierInstance> activeModifiers;
std::unordered_set<LWOOBJID> upgradeItems;
LWOOBJID id;
LOT lot;
int32_t level = 1;
};
}

View File

@@ -0,0 +1,240 @@
#include "AdditionalItemData.h"
#include "Item.h"
#include "eItemType.h"
#include "NejlikaData.h"
using namespace nejlika;
nejlika::AdditionalItemData::AdditionalItemData(Item* item)
{
const auto& config = item->GetConfig();
for (const auto& entry : config)
{
if (entry->GetKey() != u"modifiers")
{
continue;
}
const auto str = entry->GetValueAsString();
if (str.empty())
{
continue;
}
try
{
const auto json = nlohmann::json::parse(str);
Load(json);
}
catch (const nlohmann::json::exception& e)
{
std::cout << "Failed to parse additional item data: " << e.what() << std::endl;
}
}
}
nejlika::AdditionalItemData::AdditionalItemData(const nlohmann::json& json) {
Load(json);
}
void nejlika::AdditionalItemData::Load(const nlohmann::json& json) {
if (json.contains("names"))
{
for (const auto& name : json["names"])
{
modifierNames.emplace_back(name);
}
}
if (json.contains("instances"))
{
for (const auto& instance : json["instances"])
{
modifierInstances.emplace_back(instance);
}
}
}
nlohmann::json nejlika::AdditionalItemData::ToJson() const {
nlohmann::json json;
json["names"] = nlohmann::json::array();
for (const auto& name : modifierNames)
{
json["names"].push_back(name.ToJson());
}
json["instances"] = nlohmann::json::array();
for (const auto& instance : modifierInstances)
{
json["instances"].push_back(instance.ToJson());
}
return json;
}
void nejlika::AdditionalItemData::Save(Item* item) {
auto& config = item->GetConfig();
// Remove the old data
for (size_t i = 0; i < config.size(); i++)
{
if (config[i]->GetKey() == u"modifiers")
{
config.erase(config.begin() + i);
break;
}
}
std::stringstream ss;
ss << ToJson().dump();
std::cout << ss.str() << std::endl;
config.push_back(new LDFData<std::string>(u"modifiers", ToJson().dump()));
}
void nejlika::AdditionalItemData::RollModifiers(Item* item, int32_t level) {
modifierNames.clear();
modifierInstances.clear();
const auto& info = item->GetInfo();
const auto itemType = static_cast<eItemType>(info.itemType);
const auto itemRarity = info.rarity == 0 ? 1 : info.rarity;
uint32_t rarityRollPrefix = 0;
uint32_t rarityRollSuffix = 0;
// Generate (itemRarity) amout of names and modifiers rolls, take the highest rarity. 0-1000
for (int i = 0; i < itemRarity; i++) {
auto roll = GeneralUtils::GenerateRandomNumber<uint32_t>() % 1000;
if (roll > rarityRollPrefix) {
rarityRollPrefix = roll;
}
roll = GeneralUtils::GenerateRandomNumber<uint32_t>() % 1000;
if (roll > rarityRollSuffix) {
rarityRollSuffix = roll;
}
}
const auto& templates = NejlikaData::GetModifierNameTemplates();
std::vector<ModifierNameTemplate> availablePrefixes;
std::vector<ModifierNameTemplate> availableSuffixes;
for (const auto& [type, nameTemplates] : templates) {
for (const auto& nameTemplate : nameTemplates) {
if (type != ModifierNameType::Prefix && type != ModifierNameType::Suffix) {
continue;
}
if (nameTemplate.GetMinLevel() > level || nameTemplate.GetMaxLevel() < level) {
continue;
}
const auto rarity = nameTemplate.GetRarity();
if (rarity == ModifierRarity::Common) {
continue;
}
const auto& itemTypes = nameTemplate.GetItemTypes();
if (std::find(itemTypes.begin(), itemTypes.end(), itemType) == itemTypes.end()) {
continue;
}
/*
Uncommon: rarityRoll > 500,
Rare: rarityRoll > 900,
Epic: rarityRoll > 990,
Legendary: rarityRoll = 999
*/
const auto roll = type == ModifierNameType::Prefix ? rarityRollPrefix : rarityRollSuffix;
if (rarity == ModifierRarity::Uncommon && roll > 900) {
continue;
}
if (rarity == ModifierRarity::Rare && (roll <= 900 || roll > 990)) {
continue;
}
if (rarity == ModifierRarity::Epic && (roll <= 990 || roll > 998)) {
continue;
}
if (rarity == ModifierRarity::Legendary && roll != 999) {
continue;
}
if (type == ModifierNameType::Prefix) {
availablePrefixes.push_back(nameTemplate);
}
else {
availableSuffixes.push_back(nameTemplate);
}
}
}
if (!availablePrefixes.empty()) {
const auto& prefix = availablePrefixes[GeneralUtils::GenerateRandomNumber<uint32_t>() % availablePrefixes.size()];
modifierNames.push_back(ModifierName(prefix));
const auto modifiers = prefix.GenerateModifiers(level);
modifierInstances.insert(modifierInstances.end(), modifiers.begin(), modifiers.end());
}
if (!availableSuffixes.empty()) {
const auto& suffix = availableSuffixes[GeneralUtils::GenerateRandomNumber<uint32_t>() % availableSuffixes.size()];
modifierNames.push_back(ModifierName(suffix));
const auto modifiers = suffix.GenerateModifiers(level);
modifierInstances.insert(modifierInstances.end(), modifiers.begin(), modifiers.end());
}
const auto& itemTemplateVec = NejlikaData::GetModifierNameTemplates(ModifierNameType::Object);
std::vector<const ModifierNameTemplate*> availableObjects;
for (const auto& itemTemplate : itemTemplateVec) {
if (itemTemplate.GetMinLevel() > level || itemTemplate.GetMaxLevel() < level) {
continue;
}
if (itemTemplate.GetLOT() != static_cast<int32_t>(item->GetLot())) {
continue;
}
availableObjects.push_back(&itemTemplate);
}
if (availableObjects.empty()) {
Save(item);
return;
}
const auto& itemTemplate = *availableObjects[GeneralUtils::GenerateRandomNumber<uint32_t>() % availableObjects.size()];
const auto itemModifiers = itemTemplate.GenerateModifiers(level);
modifierInstances.insert(modifierInstances.end(), itemModifiers.begin(), itemModifiers.end());
Save(item);
}

View File

@@ -0,0 +1,41 @@
#pragma once
#include <cstdint>
#include "ModifierName.h"
#include "ModifierInstance.h"
#include "json.hpp"
class Item;
namespace nejlika
{
class AdditionalItemData
{
public:
AdditionalItemData() = default;
AdditionalItemData(Item* item);
AdditionalItemData(const nlohmann::json& json);
nlohmann::json ToJson() const;
void Load(const nlohmann::json& json);
void Save(Item* item);
void RollModifiers(Item* item, int32_t level);
const std::vector<ModifierName>& GetModifierNames() const { return modifierNames; }
const std::vector<ModifierInstance>& GetModifierInstances() const { return modifierInstances; }
private:
std::vector<ModifierName> modifierNames;
std::vector<ModifierInstance> modifierInstances;
};
}

View File

@@ -6,7 +6,24 @@ set(DGAME_SOURCES "Character.cpp"
"TeamManager.cpp"
"TradingManager.cpp"
"User.cpp"
"UserManager.cpp")
"UserManager.cpp"
"ModifierTemplate.cpp"
"ModifierInstance.cpp"
"ModifierRarity.cpp"
"ModifierType.cpp"
"ModifierScale.cpp"
"ModifierName.cpp"
"ModifierNameTemplate.cpp"
"NejlikaData.cpp"
"AdditionalItemData.cpp"
"EntityTemplate.cpp"
"AdditionalEntityData.cpp"
"NejlikaHooks.cpp"
"UpgradeTemplate.cpp"
"UpgradeEffect.cpp"
"Lookup.cpp"
"NejlikaHelpers.cpp"
)
include_directories(
${PROJECT_SOURCE_DIR}/dScripts

View File

@@ -27,8 +27,6 @@ Character::Character(uint32_t id, User* parentUser) {
m_ID = id;
m_ParentUser = parentUser;
m_OurEntity = nullptr;
m_GMLevel = eGameMasterLevel::CIVILIAN;
m_PermissionMap = static_cast<ePermissionMap>(0);
}
Character::~Character() {
@@ -40,8 +38,8 @@ void Character::UpdateInfoFromDatabase() {
auto charInfo = Database::Get()->GetCharacterInfo(m_ID);
if (charInfo) {
m_Name = charInfo->name;
m_UnapprovedName = charInfo->pendingName;
m_Name = charInfo->name;
m_UnapprovedName = charInfo->pendingName;
m_NameRejected = charInfo->needsRename;
m_PropertyCloneID = charInfo->cloneId;
m_PermissionMap = charInfo->permissionMap;
@@ -76,7 +74,7 @@ void Character::DoQuickXMLDataParse() {
if (m_Doc.Parse(m_XMLData.c_str(), m_XMLData.size()) == 0) {
LOG("Loaded xmlData for character %s (%i)!", m_Name.c_str(), m_ID);
} else {
LOG("Failed to load xmlData (%i) (%s) (%s)!", m_Doc.ErrorID(), m_Doc.ErrorIDToName(m_Doc.ErrorID()), m_Doc.ErrorStr());
LOG("Failed to load xmlData!");
//Server::rakServer->CloseConnection(m_ParentUser->GetSystemAddress(), true);
return;
}

View File

@@ -38,7 +38,6 @@ public:
const std::string& GetXMLData() const { return m_XMLData; }
const tinyxml2::XMLDocument& GetXMLDoc() const { return m_Doc; }
void _setXmlDoc(tinyxml2::XMLDocument& doc) { doc.DeepCopy(&m_Doc); }
/**
* Out of abundance of safety and clarity of what this saves, this is its own function.
@@ -460,31 +459,27 @@ public:
User* GetParentUser() const { return m_ParentUser; }
void _doQuickXMLDataParse() { DoQuickXMLDataParse(); }
void _setXmlData(const std::string& xmlData) { m_XMLData = xmlData; }
private:
void UpdateInfoFromDatabase();
/**
* The ID of this character. First 32 bits of the ObjectID.
*/
uint32_t m_ID{};
uint32_t m_ID;
/**
* The 64-bit unique ID used in the game.
*/
LWOOBJID m_ObjectID{ LWOOBJID_EMPTY };
LWOOBJID m_ObjectID;
/**
* The user that owns this character.
*/
User* m_ParentUser{};
User* m_ParentUser;
/**
* If the character is in game, this is the entity that it represents, else nullptr.
*/
Entity* m_OurEntity{};
Entity* m_OurEntity;
/**
* 0-9, the Game Master level of this character.
@@ -511,17 +506,17 @@ private:
/**
* Whether the custom name of this character is rejected
*/
bool m_NameRejected{};
bool m_NameRejected;
/**
* The current amount of coins of this character
*/
int64_t m_Coins{};
int64_t m_Coins;
/**
* Whether the character is building
*/
bool m_BuildMode{};
bool m_BuildMode;
/**
* The items equipped by the character on world load
@@ -588,7 +583,7 @@ private:
/**
* The ID of the properties of this character
*/
uint32_t m_PropertyCloneID{};
uint32_t m_PropertyCloneID;
/**
* The XML data for this character, stored as string
@@ -618,7 +613,7 @@ private:
/**
* The last time this character logged in
*/
uint64_t m_LastLogin{};
uint64_t m_LastLogin;
/**
* The gameplay flags this character has (not just true values)

View File

@@ -24,7 +24,7 @@
#include "eTriggerEventType.h"
#include "eObjectBits.h"
#include "PositionUpdate.h"
#include "MessageType/Chat.h"
#include "eChatMessageType.h"
#include "PlayerManager.h"
//Component includes:
@@ -97,6 +97,7 @@
#include "CDZoneTableTable.h"
Observable<Entity*, const PositionUpdate&> Entity::OnPlayerPositionUpdate;
Observable<Entity*> Entity::OnReadyForUpdates;
Entity::Entity(const LWOOBJID& objectID, EntityInfo info, User* parentUser, Entity* parentEntity) {
m_ObjectID = objectID;
@@ -227,7 +228,7 @@ void Entity::Initialize() {
AddComponent<SimplePhysicsComponent>(simplePhysicsComponentID);
AddComponent<ModelComponent>()->LoadBehaviors();
AddComponent<ModelComponent>();
AddComponent<RenderComponent>();
@@ -651,7 +652,7 @@ void Entity::Initialize() {
}
if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MODEL, -1) != -1 && !GetComponent<PetComponent>()) {
AddComponent<ModelComponent>()->LoadBehaviors();
AddComponent<ModelComponent>();
if (!HasComponent(eReplicaComponentType::DESTROYABLE)) {
auto* destroyableComponent = AddComponent<DestroyableComponent>();
destroyableComponent->SetHealth(1);
@@ -883,7 +884,7 @@ void Entity::SetGMLevel(eGameMasterLevel value) {
// Update the chat server of our GM Level
{
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::GMLEVEL_UPDATE);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GMLEVEL_UPDATE);
bitStream.Write(m_ObjectID);
bitStream.Write(m_GMLevel);
@@ -1351,6 +1352,11 @@ void Entity::OnCollisionPhantom(const LWOOBJID otherEntity) {
callback(other);
}
SwitchComponent* switchComp = GetComponent<SwitchComponent>();
if (switchComp) {
switchComp->EntityEnter(other);
}
TriggerEvent(eTriggerEventType::ENTER, other);
// POI system
@@ -1775,6 +1781,12 @@ void Entity::SetOwnerOverride(const LWOOBJID value) {
m_OwnerOverride = value;
}
void Entity::SetPlayerReadyForUpdates() {
m_PlayerIsReadyForUpdates = true;
OnReadyForUpdates(this);
}
bool Entity::GetIsGhostingCandidate() const {
return m_IsGhostingCandidate;
}

View File

@@ -117,7 +117,7 @@ public:
void SetOwnerOverride(LWOOBJID value);
void SetPlayerReadyForUpdates() { m_PlayerIsReadyForUpdates = true; }
void SetPlayerReadyForUpdates();
void SetObservers(int8_t value);
@@ -304,6 +304,8 @@ public:
* @brief The observable for player entity position updates.
*/
static Observable<Entity*, const PositionUpdate&> OnPlayerPositionUpdate;
static Observable<Entity*> OnReadyForUpdates;
protected:
LWOOBJID m_ObjectID;

View File

@@ -26,6 +26,9 @@
#include "GhostComponent.h"
#include <ranges>
Observable<Entity*> EntityManager::OnEntityCreated;
Observable<Entity*> EntityManager::OnEntityDestroyed;
// Configure which zones have ghosting disabled, mostly small worlds.
std::vector<LWOMAPID> EntityManager::m_GhostingExcludedZones = {
// Small zones
@@ -138,6 +141,9 @@ Entity* EntityManager::CreateEntity(EntityInfo info, User* user, Entity* parentE
m_SpawnPoints.insert_or_assign(GeneralUtils::UTF16ToWTF8(spawnName), entity->GetObjectID());
}
// Notify observers that a new entity has been created
OnEntityCreated(entity);
return entity;
}
@@ -161,6 +167,9 @@ void EntityManager::DestroyEntity(Entity* entity) {
DestructEntity(entity);
}
// Notify observers that an entity is about to be destroyed
OnEntityDestroyed(entity);
// Delete this entity at the end of the frame
ScheduleForDeletion(id);
}
@@ -505,6 +514,7 @@ void EntityManager::UpdateGhosting(Entity* player) {
if (collectionId != 0) {
collectionId = static_cast<uint32_t>(collectionId) + static_cast<uint32_t>(Game::server->GetZoneID() << 8);
if (missionComponent->HasCollectible(collectionId)) {
continue;
}

View File

@@ -7,6 +7,7 @@
#include <unordered_map>
#include "dCommonVars.h"
#include "Observable.h"
class Entity;
class EntityInfo;
@@ -72,6 +73,9 @@ public:
const bool GetHardcoreDropinventoryOnDeath() { return m_HardcoreDropinventoryOnDeath; };
const uint32_t GetHardcoreUscoreEnemiesMultiplier() { return m_HardcoreUscoreEnemiesMultiplier; };
static Observable<Entity*> OnEntityCreated;
static Observable<Entity*> OnEntityDestroyed;
private:
void SerializeEntities();
void KillEntities();

90
dGame/EntityTemplate.cpp Normal file
View File

@@ -0,0 +1,90 @@
#include "EntityTemplate.h"
#include <magic_enum.hpp>
nejlika::EntityTemplate::EntityTemplate(const nlohmann::json& json) {
lot = json["lot"].get<LOT>();
minLevel = json.contains("min-level") ? json["min-level"].get<int32_t>() : 1;
for (const auto& scaler : json["scaling"])
{
EntityTemplateScaler s;
s.type = magic_enum::enum_cast<ModifierType>(scaler["type"].get<std::string>()).value();
s.isResistance = scaler.contains("resistance") && scaler["resistance"].get<bool>();
s.polynomial = scaler["polynomial"].get<std::vector<float>>();
scalers.push_back(s);
}
}
nlohmann::json nejlika::EntityTemplate::ToJson() const {
nlohmann::json json;
json["lot"] = lot;
json["min-level"] = minLevel;
nlohmann::json scalersJson;
for (const auto& scaler : scalers)
{
nlohmann::json s;
s["type"] = magic_enum::enum_name(scaler.type);
s["resistance"] = scaler.isResistance;
s["polynomial"] = scaler.polynomial;
scalersJson.push_back(s);
}
json["scaling"] = scalersJson;
return json;
}
float nejlika::EntityTemplate::GetScaler(ModifierType type, bool isResistance, int32_t level) const {
for (const auto& scaler : scalers)
{
if (scaler.type == type && scaler.isResistance == isResistance)
{
return CalculateScaler(scaler, level);
}
}
return 0.0f;
}
std::vector<nejlika::ModifierInstance> nejlika::EntityTemplate::GenerateModifiers(int32_t level) const {
std::vector<ModifierInstance> modifiers;
for (const auto& scaler : scalers)
{
ModifierInstance modifier(
scaler.type,
CalculateScaler(scaler, level),
ModifierOperator::Additive,
scaler.isResistance,
ModifierCategory::Player,
0,
"",
ModifierType::Invalid,
""
);
modifiers.push_back(modifier);
}
return modifiers;
}
float nejlika::EntityTemplate::CalculateScaler(const EntityTemplateScaler& scaler, int32_t level) const {
float result = 0.0f;
for (size_t i = 0; i < scaler.polynomial.size(); ++i)
{
result += scaler.polynomial[i] * std::pow(level, i);
}
return result;
}

47
dGame/EntityTemplate.h Normal file
View File

@@ -0,0 +1,47 @@
#pragma once
#include <cstdint>
#include <vector>
#include "Entity.h"
#include "ModifierType.h"
#include "ModifierInstance.h"
#include "json.hpp"
namespace nejlika
{
class EntityTemplate
{
public:
EntityTemplate() = default;
EntityTemplate(const nlohmann::json& json);
nlohmann::json ToJson() const;
LOT GetLOT() const { return lot; }
int32_t GetMinLevel() const { return minLevel; }
float GetScaler(ModifierType type, bool isResistance, int32_t level) const;
std::vector<ModifierInstance> GenerateModifiers(int32_t level) const;
private:
struct EntityTemplateScaler
{
ModifierType type;
bool isResistance;
std::vector<float> polynomial;
};
float CalculateScaler(const EntityTemplateScaler& scaler, int32_t level) const;
LOT lot;
std::vector<EntityTemplateScaler> scalers;
int32_t minLevel;
};
}

181
dGame/Lookup.cpp Normal file
View File

@@ -0,0 +1,181 @@
#include "Lookup.h"
#include <fstream>
#include "json.hpp"
#include <iostream>
using namespace nejlika;
Lookup::Lookup(const std::filesystem::path& lookup)
{
m_Lookup = lookup;
// Check if the file exists.
if (!std::filesystem::exists(lookup))
{
// Empty lookup
return;
}
// Read the json file
std::ifstream file(lookup);
std::string json((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
// Parse the json
auto doc = nlohmann::json::parse(json);
// The document is a map, so we can iterate over it.
for (const auto& [key, v] : doc.items()) {
if (v.is_number_integer())
{
m_LookupMap[key] = v.get<int64_t>();
continue;
}
if (!v.is_object()) {
std::stringstream ss;
ss << "Invalid value for key \"" << key << "\" in lookup.";
throw std::runtime_error(ss.str());
}
// Get the data
auto data = v.get<nlohmann::json>();
// Get the id
auto id = data["id"].get<int64_t>();
m_LookupMap[key] = id;
// Get the metadata
auto metadata = data["metadata"].get<std::string>();
m_Metadata[key] = metadata;
}
}
nejlika::Lookup::Lookup(const Lookup &other)
{
m_Lookup = other.m_Lookup;
m_LookupMap = other.m_LookupMap;
m_Metadata = other.m_Metadata;
m_CoreSymbols = other.m_CoreSymbols;
}
id Lookup::GetValue(const name& symbol) const
{
id value;
if (IsCoreSymbol(symbol, value)) {
return value;
}
const auto& it = m_LookupMap.find(symbol);
if (it == m_LookupMap.end())
{
std::stringstream ss;
ss << "Symbol \"" << symbol << "\" does not exist in the lookup.";
throw std::runtime_error(ss.str());
}
return it->second;
}
bool nejlika::Lookup::Exists(const name &symbol) const
{
return IsCoreSymbol(symbol) || (m_CoreSymbols.find(symbol) != m_CoreSymbols.end()) || (m_LookupMap.find(symbol) != m_LookupMap.end());
}
bool nejlika::Lookup::Exists(id value) const
{
for (const auto& [k, v] : m_LookupMap)
{
if (v == value)
{
return true;
}
}
return false;
}
const std::string& Lookup::GetMetadata(const name& symbol) const
{
const auto& it = m_Metadata.find(symbol);
if (it == m_Metadata.end())
{
static std::string empty;
return empty;
}
return it->second;
}
const std::filesystem::path& Lookup::GetLookup() const
{
return m_Lookup;
}
const std::unordered_map<name, id> &nejlika::Lookup::GetMap() const
{
return m_LookupMap;
}
bool nejlika::Lookup::IsCoreSymbol(const name &symbol, id &value) const
{
try {
value = std::stoi(symbol);
return true;
} catch (...) {
// cont...
}
if (!symbol.starts_with(core_prefix)) {
return false;
}
// Check in the core symbols
const auto& it = m_CoreSymbols.find(symbol);
if (it != m_CoreSymbols.end())
{
value = it->second;
return true;
}
// In the format "core:<value>"
try {
value = std::stoi(symbol.substr(core_prefix.size() + 1));
} catch (...) {
return false;
}
return true;
}
bool nejlika::Lookup::IsCoreSymbol(const name &symbol)
{
// Check if it can be converted to an integer
try {
[[maybe_unused]] auto value = std::stoi(symbol);
return true;
} catch (...) {
// cont...
}
return symbol.starts_with(core_prefix);
}

117
dGame/Lookup.h Normal file
View File

@@ -0,0 +1,117 @@
#pragma once
#include <filesystem>
#include <functional>
#include <cstdint>
#include <unordered_map>
#include <unordered_set>
namespace nejlika {
typedef std::string name;
typedef int id;
/**
* @brief A one-way mapping between symbols (names) and their corresponding numerical values.
*
* This is an active lookup, meaning that it is possible to add new symbols and their values to the lookup.
*
* A method is provided to wait for a symbol to be added to the lookup.
*/
class Lookup
{
public:
inline static const name core_prefix = "lego-universe";
/**
* @brief Constructs a Lookup object with the specified lookup file path.
*
* @param lookup The path to the lookup file.
* @throw If the lookup file could not be parsed.
*/
Lookup(const std::filesystem::path& lookup);
Lookup(const Lookup& other);
Lookup() = default;
/**
* @brief Gets the value of the specified symbol.
*
* @param symbol The symbol to get the value of.
* @return The value of the specified symbol.
* @throw If the specified symbol does not exist.
*/
id GetValue(const name& symbol) const;
/**
* @brief Checks whether the specified symbol exists.
*
* @param symbol The symbol to check.
* @return Whether the specified symbol exists.
*/
bool Exists(const name& symbol) const;
/**
* @brief Checks whether any symbol has the specified value.
*
* @param value The value to check.
* @return Whether any symbol has the specified value.
*/
bool Exists(id value) const;
/**
* @brief Gets the metadata of a specified symbol.
*
* @param symbol The symbol to get metadata of.
* @return The metadata of the specified symbol or an empty string if the symbol does not exist.
*/
const std::string& GetMetadata(const name& symbol) const;
/**
* @brief Gets the path to the lookup file.
*
* @return The path to the lookup file.
*/
const std::filesystem::path& GetLookup() const;
/**
* @brief Gets the map of all symbols and their values.
*
* @return The map of all symbols and their values.
*/
const std::unordered_map<name, id>& GetMap() const;
/**
* @brief Checks whether the specified symbol is a core symbol.
*
* @param symbol The symbol to check.
* @param value The value of the core symbol.
* @return Whether the specified symbol is a core symbol.
*/
bool IsCoreSymbol(const name& symbol, id& value) const;
/**
* @brief Checks whether the specified symbol is a core symbol.
*
* A symbol is considered a core symbol if it is either:
* a number;
* or a string starting with the core_prefix.
*
* @param symbol The symbol to check.
* @return Whether the specified symbol is a core symbol.
*/
static bool IsCoreSymbol(const name& symbol);
private:
std::filesystem::path m_Lookup;
std::unordered_map<name, id> m_LookupMap;
std::unordered_map<name, std::string> m_Metadata;
std::unordered_map<name, id> m_CoreSymbols;
};
} // namespace nejlika

14
dGame/ModifierCategory.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <cstdint>
namespace nejlika
{
enum class ModifierCategory : uint8_t
{
Player = 0 << 0,
Pet = 1 << 0
};
}

170
dGame/ModifierInstance.cpp Normal file
View File

@@ -0,0 +1,170 @@
#include "ModifierInstance.h"
#include <sstream>
#include <magic_enum.hpp>
nejlika::ModifierInstance::ModifierInstance(const nlohmann::json& config) {
type = magic_enum::enum_cast<ModifierType>(config["type"].get<std::string>()).value_or(ModifierType::Invalid);
convertTo = magic_enum::enum_cast<ModifierType>(config["convert-to"].get<std::string>()).value_or(ModifierType::Invalid);
value = config["value"].get<float>();
if (config.contains("op")) {
op = magic_enum::enum_cast<ModifierOperator>(config["op"].get<std::string>()).value_or(ModifierOperator::Additive);
}
else {
op = ModifierOperator::Additive;
}
isResistance = config.contains("resistance") ? config["resistance"].get<bool>() : false;
if (config.contains("category")) {
category = magic_enum::enum_cast<ModifierCategory>(config["category"].get<std::string>()).value_or(ModifierCategory::Player);
}
else {
category = ModifierCategory::Player;
}
effectID = config.contains("effect-id") ? config["effect-id"].get<uint32_t>() : 0;
effectType = config.contains("effect-type") ? config["effect-type"].get<std::string>() : "";
}
nlohmann::json nejlika::ModifierInstance::ToJson() const
{
nlohmann::json config;
config["type"] = magic_enum::enum_name(type);
config["convert-to"] = magic_enum::enum_name(convertTo);
config["value"] = value;
config["op"] = magic_enum::enum_name(op);
config["resistance"] = isResistance;
config["category"] = magic_enum::enum_name(category);
config["effect-id"] = effectID;
config["effect-type"] = effectType;
return config;
}
std::string nejlika::ModifierInstance::GenerateHtmlString(const std::vector<ModifierInstance>& modifiers)
{
std::stringstream ss;
// target -> resistance -> op -> type -> value
std::unordered_map<ModifierCategory, std::unordered_map<bool, std::unordered_map<ModifierOperator, std::unordered_map<ModifierType, float>>>> modifierMap;
bool hasConvertTo = false;
bool hasSkillModifier = false;
for (const auto& modifier : modifiers) {
if (modifier.type == ModifierType::Invalid) {
continue;
}
if (modifier.GetConvertTo() != ModifierType::Invalid)
{
hasConvertTo = true;
continue;
}
if (!modifier.GetUpgradeName().empty())
{
hasSkillModifier = true;
continue;
}
modifierMap[modifier.category][modifier.isResistance][modifier.op][modifier.type] = modifier.value;
}
// Resistances and addatives are not separated, pet and player are
// Summarize the resistances and addatives
for (const auto& target : modifierMap) {
if (target.first == ModifierCategory::Pet) {
ss << "\n<font color=\"#D0AB62\">Pets:</font>\n";
}
for (const auto& resistance : target.second) {
for (const auto& math : resistance.second) {
for (const auto& modifier : math.second) {
ss << "<font color=\"#FFFFFF\">";
ss << ((modifier.second > 0) ? (math.first == ModifierOperator::Multiplicative ? "+" : "") : "-");
ss << std::fixed << std::setprecision(1) << std::abs(modifier.second);
if (math.first == ModifierOperator::Multiplicative) {
ss << "%";
}
ss << "</font> <font color=\"#D0AB62\">";
ss << " " << nejlika::GetModifierTypeName(modifier.first);
if (resistance.first) {
// If the ss now ends with 'Damage' remove it
if (ss.str().substr(ss.str().size() - 7) == " Damage") {
ss.seekp(-7, std::ios_base::end);
}
ss << " " << "Resistance";
}
ss << "</font>\n";
}
}
}
}
if (hasSkillModifier)
{
for (const auto& modifier : modifiers) {
if (modifier.type != ModifierType::SkillModifier) {
continue;
}
ss << "<font color=\"" << GetModifierTypeColor(modifier.type) << "\">";
ss << ((modifier.value > 0) ? "+" : "-");
ss << std::fixed << std::setprecision(0) << std::abs(modifier.value);
ss << " to ";
ss << modifier.GetUpgradeName();
ss << "</font>\n";
}
}
if (hasConvertTo)
{
for (const auto& modifier : modifiers) {
if (modifier.GetConvertTo() == ModifierType::Invalid)
{
continue;
}
if (modifier.type == ModifierType::Invalid) {
continue;
}
ss << "<font color=\"#FFFFFF\">";
// +xx/yy% of T1 converted to T2
ss << ((modifier.value > 0) ? "" : "-");
ss << std::fixed << std::setprecision(0) << std::abs(modifier.value);
ss << "%</font> <font color=\"#D0AB62\">";
ss << " of ";
ss << nejlika::GetModifierTypeName(modifier.type);
ss << " converted to ";
ss << nejlika::GetModifierTypeName(modifier.GetConvertTo());
ss << "</font>\n";
}
}
return ss.str();
}

97
dGame/ModifierInstance.h Normal file
View File

@@ -0,0 +1,97 @@
#pragma once
#include "ModifierType.h"
#include "ModifierCategory.h"
#include "ModifierOperator.h"
#include <cstdint>
#include <string>
#include "json.hpp"
namespace nejlika
{
class ModifierInstance
{
public:
ModifierInstance(
ModifierType type, float value, ModifierOperator op, bool isResistance, ModifierCategory category, uint32_t effectID, const std::string& effectType, ModifierType convertTo,
const std::string& upgradeName
) : type(type), value(value), op(op), isResistance(isResistance), category(category), effectID(effectID), effectType(effectType), convertTo(convertTo),
upgradeName(upgradeName)
{}
/**
* @brief Construct a new Modifier Instance object from a json configuration.
*
* @param config The json configuration.
*/
ModifierInstance(const nlohmann::json& config);
/**
* @brief Convert the modifier instance to a json representation.
*
* @return The json representation.
*/
nlohmann::json ToJson() const;
/**
* @brief Generate a HTML string representation of a set of modifiers.
*
* @param modifiers The modifiers to generate the HTML string for.
* @return The HTML string.
*/
static std::string GenerateHtmlString(const std::vector<ModifierInstance>& modifiers);
// Getters and setters
ModifierType GetType() const { return type; }
ModifierType GetConvertTo() const { return convertTo; }
float GetValue() const { return value; }
ModifierOperator GetOperator() const { return op; }
bool IsResistance() const { return isResistance; }
ModifierCategory GetCategory() const { return category; }
uint32_t GetEffectID() const { return effectID; }
std::string GetEffectType() const { return effectType; }
std::string GetUpgradeName() const { return upgradeName; }
void SetType(ModifierType type) { this->type = type; }
void SetConvertTo(ModifierType convertTo) { this->convertTo = convertTo; }
void SetValue(float value) { this->value = value; }
void SetOperator(ModifierOperator op) { this->op = op; }
void SetIsResistance(bool isResistance) { this->isResistance = isResistance; }
void SetCategory(ModifierCategory category) { this->category = category; }
void SetEffectID(uint32_t effectID) { this->effectID = effectID; }
void SetEffectType(const std::string& effectType) { this->effectType = effectType; }
void SetUpgradeName(const std::string& upgradeName) { this->upgradeName = upgradeName; }
private:
ModifierType type;
ModifierType convertTo;
float value;
ModifierOperator op;
bool isResistance;
ModifierCategory category;
uint32_t effectID;
std::string effectType;
std::string upgradeName;
};
}

83
dGame/ModifierName.cpp Normal file
View File

@@ -0,0 +1,83 @@
#include "ModifierName.h"
#include <magic_enum.hpp>
using namespace nejlika;
nejlika::ModifierName::ModifierName(const nlohmann::json & json)
{
name = json["name"].get<std::string>();
if (json.contains("type"))
{
type = magic_enum::enum_cast<ModifierNameType>(json["type"].get<std::string>()).value_or(ModifierNameType::Prefix);
}
else
{
type = ModifierNameType::Prefix;
}
if (json.contains("rarity"))
{
rarity = magic_enum::enum_cast<ModifierRarity>(json["rarity"].get<std::string>()).value_or(ModifierRarity::Common);
}
else
{
rarity = ModifierRarity::Common;
}
}
nejlika::ModifierName::ModifierName(const ModifierNameTemplate& templateData) {
name = templateData.GetName();
type = templateData.GetType();
rarity = templateData.GetRarity();
}
nlohmann::json nejlika::ModifierName::ToJson() const
{
nlohmann::json json;
json["name"] = name;
json["type"] = magic_enum::enum_name(type);
json["rarity"] = magic_enum::enum_name(rarity);
return json;
}
std::string nejlika::ModifierName::GenerateHtmlString() const {
const auto& rarityColor = ModifierRarityHelper::GetModifierRarityColor(rarity);
std::stringstream ss;
ss << "<font color=\"" << rarityColor << "\">" << name << "</font>";
return ss.str();
}
std::string nejlika::ModifierName::GenerateHtmlString(const std::vector<ModifierName>& names)
{
std::stringstream ss;
for (const auto& name : names) {
if (name.GetType() == ModifierNameType::Prefix && !name.name.empty()) {
ss << name.GenerateHtmlString() << "\n";
}
}
ss << "<font color=\"#D0AB62\">NAME</font>";
for (const auto& name : names) {
if (name.GetType() == ModifierNameType::Suffix && !name.name.empty()) {
ss << "\n" << name.GenerateHtmlString();
}
}
// Remove the last newline
auto str = ss.str();
if (!str.empty() && str.back() == '\n') {
str.pop_back();
}
return str;
}

57
dGame/ModifierName.h Normal file
View File

@@ -0,0 +1,57 @@
#pragma once
#include <cstdint>
#include <string>
#include "ModifierNameType.h"
#include "ModifierRarity.h"
#include "ModifierNameTemplate.h"
#include "json.hpp"
namespace nejlika
{
class ModifierName
{
public:
ModifierName(const std::string& name, ModifierNameType type, ModifierRarity rarity) :
name(name), type(type), rarity(rarity) {}
ModifierName(const nlohmann::json& json);
ModifierName(const ModifierNameTemplate& templateData);
nlohmann::json ToJson() const;
std::string GenerateHtmlString() const;
/**
* @brief Generate a HTML string representation of a set of names.
*
* @param modifiers The names to generate the HTML string for.
* @return The HTML string.
*/
static std::string GenerateHtmlString(const std::vector<ModifierName>& names);
// Getters and setters
const std::string& GetName() const { return name; }
ModifierNameType GetType() const { return type; }
ModifierRarity GetRarity() const { return rarity; }
void SetName(const std::string& name) { this->name = name; }
void SetType(ModifierNameType type) { this->type = type; }
void SetRarity(ModifierRarity rarity) { this->rarity = rarity; }
private:
std::string name;
ModifierNameType type;
ModifierRarity rarity;
};
}

View File

@@ -0,0 +1,166 @@
#include "ModifierNameTemplate.h"
#include <iostream>
#include "magic_enum.hpp"
using namespace nejlika;
nejlika::ModifierNameTemplate::ModifierNameTemplate(const nlohmann::json & json)
{
name = json["name"].get<std::string>();
if (json.contains("lot"))
{
lot = json["lot"].get<int32_t>();
}
else
{
lot = 0;
}
if (json.contains("type"))
{
type = magic_enum::enum_cast<ModifierNameType>(json["type"].get<std::string>()).value_or(ModifierNameType::Prefix);
}
else
{
type = ModifierNameType::Prefix;
}
if (json.contains("items"))
{
for (const auto& itemType : json["items"])
{
std::string type = itemType.get<std::string>();
// Make uppercase
std::transform(type.begin(), type.end(), type.begin(), ::toupper);
// Replace spaces with underscores
std::replace(type.begin(), type.end(), ' ', '_');
const auto itemTypeEnum = magic_enum::enum_cast<eItemType>(type);
if (itemTypeEnum.has_value())
{
itemTypes.push_back(itemTypeEnum.value());
}
else
{
std::cout << "Invalid item type: " << type << std::endl;
}
}
}
if (json.contains("modifiers"))
{
for (const auto& modifier : json["modifiers"])
{
const auto modifierTemplate = ModifierTemplate(modifier);
if (modifierTemplate.GetTypes().empty() || modifierTemplate.GetTypes().at(0) == ModifierType::Invalid)
{
continue;
}
modifiers.push_back(modifierTemplate);
}
}
if (json.contains("levels"))
{
auto levels = json["levels"];
if (levels.contains("min"))
{
minLevel = levels["min"].get<int32_t>();
const auto rasio = 1; //45.0f / 85.0f;
minLevel = std::max(1, static_cast<int32_t>(minLevel * rasio));
}
else
{
minLevel = 1;
}
if (levels.contains("max"))
{
maxLevel = levels["max"].get<int32_t>();
}
else
{
maxLevel = 45;
}
}
if (json.contains("rarity"))
{
rarity = magic_enum::enum_cast<ModifierRarity>(json["rarity"].get<std::string>()).value_or(ModifierRarity::Common);
}
else
{
rarity = ModifierRarity::Common;
}
}
nlohmann::json nejlika::ModifierNameTemplate::ToJson() const {
nlohmann::json json;
json["name"] = name;
json["type"] = magic_enum::enum_name(type);
if (lot != 0)
{
json["lot"] = lot;
}
if (!itemTypes.empty())
{
nlohmann::json items;
for (const auto& itemType : itemTypes)
{
items.push_back(magic_enum::enum_name(itemType));
}
json["items"] = items;
}
if (!modifiers.empty())
{
nlohmann::json modifierTemplates;
for (const auto& modifier : modifiers)
{
modifierTemplates.push_back(modifier.ToJson());
}
json["modifiers"] = modifierTemplates;
}
nlohmann::json levels;
levels["min"] = minLevel;
levels["max"] = maxLevel;
json["levels"] = levels;
json["rarity"] = magic_enum::enum_name(rarity);
return json;
}
std::vector<ModifierInstance> nejlika::ModifierNameTemplate::GenerateModifiers(int32_t level) const
{
std::vector<ModifierInstance> result;
for (const auto& modifierTemplate : modifiers)
{
auto modifiers = modifierTemplate.GenerateModifiers(level);
result.insert(result.end(), modifiers.begin(), modifiers.end());
}
return result;
}

View File

@@ -0,0 +1,55 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include "eItemType.h"
#include "ModifierNameType.h"
#include "ModifierRarity.h"
#include "ModifierTemplate.h"
#include "json.hpp"
namespace nejlika
{
class ModifierNameTemplate
{
public:
ModifierNameTemplate(const nlohmann::json& json);
nlohmann::json ToJson() const;
std::vector<ModifierInstance> GenerateModifiers(int32_t level) const;
// Getters
const std::string& GetName() const { return name; }
ModifierNameType GetType() const { return type; }
const std::vector<ModifierTemplate>& GetModifiers() const { return modifiers; }
const std::vector<eItemType>& GetItemTypes() const { return itemTypes; }
int32_t GetMinLevel() const { return minLevel; }
int32_t GetMaxLevel() const { return maxLevel; }
ModifierRarity GetRarity() const { return rarity; }
int32_t GetLOT() const { return lot; }
private:
std::string name;
int32_t lot;
ModifierNameType type;
std::vector<ModifierTemplate> modifiers;
std::vector<eItemType> itemTypes;
int32_t minLevel;
int32_t maxLevel;
ModifierRarity rarity;
};
}

16
dGame/ModifierNameType.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include <cstdint>
namespace nejlika
{
enum class ModifierNameType : uint8_t
{
Prefix,
Suffix,
Object,
Skill
};
}

14
dGame/ModifierOperator.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <cstdint>
namespace nejlika
{
enum class ModifierOperator : uint8_t
{
Additive,
Multiplicative
};
}

31
dGame/ModifierRarity.cpp Normal file
View File

@@ -0,0 +1,31 @@
#include "ModifierRarity.h"
#include <unordered_map>
using namespace nejlika;
namespace
{
const std::unordered_map<ModifierRarity, std::string> colorMap = {
{ModifierRarity::Common, "#FFFFFF"},
{ModifierRarity::Uncommon, "#B5AC15"},
{ModifierRarity::Rare, "#3EEA4A"},
{ModifierRarity::Epic, "#2F83C1"},
{ModifierRarity::Legendary, "#852DCA"},
{ModifierRarity::Relic, "#00FFFF"}
};
}
const std::string& nejlika::ModifierRarityHelper::GetModifierRarityColor(ModifierRarity rarity)
{
const auto color = colorMap.find(rarity);
if (color != colorMap.end()) {
return color->second;
}
static const std::string white = "#FFFFFF";
return white;
}

24
dGame/ModifierRarity.h Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include <cstdint>
#include <string>
namespace nejlika
{
enum class ModifierRarity : uint8_t
{
Common,
Uncommon,
Rare,
Epic,
Legendary,
Relic
};
namespace ModifierRarityHelper
{
const std::string& GetModifierRarityColor(ModifierRarity rarity);
}
}

40
dGame/ModifierScale.cpp Normal file
View File

@@ -0,0 +1,40 @@
#include "ModifierScale.h"
nejlika::ModifierScale::ModifierScale(const nlohmann::json & json)
{
level = json["level"].get<int32_t>();
const auto rasio = 1; //45.0f / 85.0f;
level = std::max(1, static_cast<int32_t>(level * rasio));
if (json.contains("min")) {
min = json["min"].get<float>();
}
else {
min = 0.0f;
}
if (json.contains("max")) {
max = json["max"].get<float>();
}
else {
max = 0.0f;
}
if (json.contains("value")) {
min = json["value"].get<float>();
max = json["value"].get<float>();
}
}
nlohmann::json nejlika::ModifierScale::ToJson() const
{
nlohmann::json json;
json["level"] = level;
json["min"] = min;
json["max"] = max;
return json;
}

33
dGame/ModifierScale.h Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
#include <cstdint>
#include "json.hpp"
namespace nejlika
{
class ModifierScale
{
public:
ModifierScale() = default;
ModifierScale(int32_t level, float min, float max) : level(level), min(min), max(max) {}
ModifierScale(const nlohmann::json& json);
nlohmann::json ToJson() const;
int32_t GetLevel() const { return level; }
float GetMin() const { return min; }
float GetMax() const { return max; }
private:
int32_t level = 0;
float min = 0.0f;
float max = 0.0f;
};
}

501
dGame/ModifierTemplate.cpp Normal file
View File

@@ -0,0 +1,501 @@
#include "ModifierTemplate.h"
#include <magic_enum.hpp>
#include <random>
#include <algorithm>
#include <sstream>
#include <iostream>
using namespace nejlika;
nejlika::ModifierTemplate::ModifierTemplate(const nlohmann::json& config) {
if (config.contains("type"))
{
selector = ModifierTemplateSelector::One;
const auto type = magic_enum::enum_cast<ModifierType>(config["type"].get<std::string>());
if (type.has_value())
{
types = {type.value()};
}
else
{
types = {};
}
}
else if (config.contains("all"))
{
selector = ModifierTemplateSelector::All;
types = {};
for (const auto& type : config["all"])
{
const auto t = magic_enum::enum_cast<ModifierType>(type.get<std::string>());
if (t.has_value())
{
types.push_back(t.value());
}
}
}
else if (config.contains("two-of"))
{
selector = ModifierTemplateSelector::Two;
types = {};
for (const auto& type : config["two-of"])
{
const auto t = magic_enum::enum_cast<ModifierType>(type.get<std::string>());
if (t.has_value())
{
types.push_back(t.value());
}
}
}
else
{
types = {};
}
if (config.contains("convert-to"))
{
convertTo = magic_enum::enum_cast<ModifierType>(config["convert-to"].get<std::string>()).value_or(ModifierType::Invalid);
}
else
{
convertTo = ModifierType::Invalid;
}
if (config.contains("scaling"))
{
const auto scaling = config["scaling"];
for (const auto& scaler : scaling)
{
scales.push_back(ModifierScale(scaler));
}
}
if (config.contains("polynomial"))
{
const auto polynomialConfig = config["polynomial"];
for (const auto& term : polynomialConfig)
{
polynomial.push_back(term.get<float>());
}
}
if (config.contains("category"))
{
category = magic_enum::enum_cast<ModifierCategory>(config["category"].get<std::string>()).value_or(ModifierCategory::Player);
}
else
{
category = ModifierCategory::Player;
}
isResistance = config.contains("resistance") ? config["resistance"].get<bool>() : false;
effectID = config.contains("effect-id") ? config["effect-id"].get<uint32_t>() : 0;
effectType = config.contains("effect-type") ? config["effect-type"].get<std::string>() : "";
if (config.contains("operator"))
{
operatorType = magic_enum::enum_cast<ModifierOperator>(config["operator"].get<std::string>()).value_or(ModifierOperator::Additive);
}
else
{
operatorType = ModifierOperator::Additive;
}
// Old format
if (config.contains("percentage"))
{
if (config["percentage"].get<bool>()) {
operatorType = ModifierOperator::Multiplicative;
}
}
if (config.contains("pet"))
{
if (config["pet"].get<bool>()) {
category = ModifierCategory::Pet;
}
}
if (config.contains("owner"))
{
if (config["owner"].get<bool>()) {
category = ModifierCategory::Player;
}
}
if (config.contains("skill"))
{
upgradeName = config["skill"].get<std::string>();
}
}
nlohmann::json nejlika::ModifierTemplate::ToJson() const {
nlohmann::json config;
if (selector == ModifierTemplateSelector::One)
{
config["type"] = magic_enum::enum_name(types[0]);
}
else if (selector == ModifierTemplateSelector::All)
{
config["all"] = true;
for (const auto& type : types)
{
config["types"].push_back(magic_enum::enum_name(type));
}
}
else if (selector == ModifierTemplateSelector::Two)
{
config["two-of"] = true;
for (const auto& type : types)
{
config["two-of"].push_back(magic_enum::enum_name(type));
}
}
if (!scales.empty())
{
nlohmann::json scaling;
for (const auto& scale : scales)
{
scaling.push_back(scale.ToJson());
}
config["scaling"] = scaling;
}
if (!polynomial.empty())
{
nlohmann::json polynomialConfig;
for (const auto& term : polynomial)
{
polynomialConfig.push_back(term);
}
config["polynomial"] = polynomialConfig;
}
config["convert-to"] = magic_enum::enum_name(convertTo);
config["category"] = magic_enum::enum_name(category);
config["resistance"] = isResistance;
config["effect-id"] = effectID;
config["effect-type"] = effectType;
config["operator"] = magic_enum::enum_name(operatorType);
if (!upgradeName.empty())
{
config["skill"] = upgradeName;
}
return config;
}
std::vector<ModifierInstance> nejlika::ModifierTemplate::GenerateModifiers(int32_t level) const {
std::vector<ModifierInstance> modifiers;
std::vector<ModifierType> selectedTypes;
if (types.empty())
{
return modifiers;
}
if (selector == ModifierTemplateSelector::One)
{
selectedTypes = {types[0]};
}
else if (selector == ModifierTemplateSelector::All)
{
selectedTypes = types;
}
else if (selector == ModifierTemplateSelector::Two)
{
if (types.size() < 2)
{
selectedTypes = types;
}
else
{
// Randomly select two types
selectedTypes = types;
std::shuffle(selectedTypes.begin(), selectedTypes.end(), std::mt19937(std::random_device()()));
selectedTypes.resize(2);
}
}
for (const auto& selectedType : selectedTypes)
{
auto modifierOpt = GenerateModifier(selectedType, level);
if (modifierOpt.has_value())
{
modifiers.push_back(modifierOpt.value());
}
}
return modifiers;
}
std::string nejlika::ModifierTemplate::GenerateHtmlString(const std::vector<ModifierTemplate>& modifiers, int32_t level) {
std::stringstream ss;
// target -> resistance -> op -> type -> (min, max)
std::unordered_map<ModifierCategory, std::unordered_map<bool, std::unordered_map<ModifierOperator, std::unordered_map<ModifierType, std::pair<float, float>>>>> modifierMap;
bool hasConvertTo = false;
bool hasSkillModifier = false;
for (const auto& modifier : modifiers) {
if (modifier.GetConvertTo() != ModifierType::Invalid)
{
hasConvertTo = true;
continue;
}
if (!modifier.GetUpgradeName().empty())
{
hasSkillModifier = true;
continue;
}
for (const auto& type : modifier.types) {
if (type == ModifierType::Invalid || type == ModifierType::SkillModifier) {
continue;
}
if (!modifier.polynomial.empty())
{
float value = 0.0f;
int32_t power = 0;
for (const auto& term : modifier.polynomial)
{
value += term * std::pow(level, power);
power++;
}
modifierMap[modifier.category][modifier.isResistance][modifier.operatorType][type] = {value, value};
continue;
}
ModifierScale scale;
bool found = false;
// Select the scale with the highest level that is less than or equal to the current level
for (const auto& s : modifier.scales) {
if (s.GetLevel() <= level && s.GetLevel() > scale.GetLevel()) {
scale = s;
found = true;
}
}
if (!found) {
continue;
}
modifierMap[modifier.category][modifier.isResistance][modifier.operatorType][type] = {scale.GetMin(), scale.GetMax()};
}
}
// Resistances and addatives are not separated, pet and player are
// Summarize the resistances and addatives
for (const auto& target : modifierMap) {
if (target.first == ModifierCategory::Pet) {
ss << "\n<font color=\"#D0AB62\">Pets:</font>\n";
}
for (const auto& resistance : target.second) {
for (const auto& math : resistance.second) {
for (const auto& modifier : math.second) {
ss << "<font color=\"#FFFFFF\">";
ss << ((modifier.second.first > 0) ? (math.first == ModifierOperator::Multiplicative ? "+" : "") : "-");
ss << std::fixed << std::setprecision(1) << std::abs(modifier.second.first);
if (modifier.second.first != modifier.second.second)
{
ss << "/";
ss << std::fixed << std::setprecision(1) << std::abs(modifier.second.second);
}
if (math.first == ModifierOperator::Multiplicative) {
ss << "%";
}
ss << "</font> <font color=\"#D0AB62\">";
ss << " " << nejlika::GetModifierTypeName(modifier.first);
if (resistance.first) {
// If the ss now ends with 'Damage' remove it
if (ss.str().substr(ss.str().size() - 6) == "Damage") {
ss.seekp(-6, std::ios_base::end);
}
ss << " " << "Resistance";
}
ss << "</font>\n";
}
}
}
}
if (hasSkillModifier)
{
for (const auto& modifier : modifiers) {
for (const auto& type : modifier.types) {
if (type != ModifierType::SkillModifier) {
continue;
}
const auto& scalors = modifier.GetScales();
if (scalors.empty())
{
continue;
}
const auto& m = scalors[0];
ss << "<font color=\"" << GetModifierTypeColor(type) << "\">";
ss << ((m.GetMin() > 0) ? "+" : "-");
ss << std::fixed << std::setprecision(0) << std::abs(m.GetMin());
ss << " to ";
ss << modifier.GetUpgradeName();
ss << "</font>\n";
}
}
}
if (hasConvertTo)
{
for (const auto& modifier : modifiers) {
if (modifier.GetConvertTo() == ModifierType::Invalid)
{
continue;
}
for (const auto& type : modifier.types) {
if (type == ModifierType::Invalid) {
continue;
}
const auto& scalors = modifier.GetScales();
auto m = scalors[0];
for (const auto& s : scalors) {
if (s.GetLevel() <= level && s.GetLevel() > m.GetLevel()) {
m = s;
}
}
ss << "<font color=\"#FFFFFF\">";
// +xx/yy% of T1 converted to T2
ss << ((m.GetMin() > 0) ? "" : "-");
ss << std::fixed << std::setprecision(0) << std::abs(m.GetMin());
if (m.GetMin() != m.GetMax())
{
ss << "/";
ss << std::fixed << std::setprecision(0) << std::abs(m.GetMax());
}
ss << "%</font> <font color=\"#D0AB62\">";
ss << " of ";
ss << nejlika::GetModifierTypeName(type);
ss << " converted to ";
ss << nejlika::GetModifierTypeName(modifier.GetConvertTo());
ss << "</font>\n";
}
}
}
return ss.str();
}
std::optional<ModifierInstance> nejlika::ModifierTemplate::GenerateModifier(ModifierType type, int32_t level) const {
ModifierScale scale;
bool found = false;
if (!polynomial.empty())
{
float value = 0.0f;
int32_t power = 0;
for (const auto& term : polynomial)
{
value += term * std::pow(level, power);
power++;
}
return ModifierInstance(type, value, operatorType, isResistance, category, effectID, effectType, convertTo, upgradeName);
}
// Select the scale with the highest level that is less than or equal to the current level
for (const auto& s : scales) {
if ((s.GetLevel() <= level) && (s.GetLevel() > scale.GetLevel())) {
std::cout << "Found scale: " << s.GetMin() << " - " << s.GetMax() << " for level " << s.GetLevel() << std::endl;
scale = s;
found = true;
}
}
if (!found) {
return std::nullopt;
}
float value = 0;
if (scale.GetMax() == scale.GetMin())
{
value = scale.GetMin();
}
else
{
value = (GeneralUtils::GenerateRandomNumber<uint32_t>(0, 100) / 100.0f) * (scale.GetMax() - scale.GetMin()) + scale.GetMin();
}
std::cout << "Generated modifier: " << value << " with level " << level << " for type: " << magic_enum::enum_name(type) << std::endl;
return ModifierInstance(type, value, operatorType, isResistance, category, effectID, effectType, convertTo, upgradeName);
}

109
dGame/ModifierTemplate.h Normal file
View File

@@ -0,0 +1,109 @@
#pragma once
#include <cstdint>
#include <vector>
#include <string>
#include <optional>
#include "ModifierInstance.h"
#include "ModifierScale.h"
namespace nejlika
{
enum ModifierTemplateSelector : uint8_t
{
One,
All,
Two
};
class ModifierTemplate
{
public:
ModifierTemplate(
const std::vector<ModifierType>& types, ModifierTemplateSelector selector, ModifierCategory category, bool isResistance, uint32_t effectID, const std::string& effectType
) : types(types), selector(selector), category(category), isResistance(isResistance), effectID(effectID), effectType(effectType) {}
/**
* @brief Construct a new Modifier Template object from a json configuration.
*
* @param config The json configuration.
*/
ModifierTemplate(const nlohmann::json& config);
/**
* @brief Convert the modifier template to a json representation.
*
* @return The json representation.
*/
nlohmann::json ToJson() const;
std::vector<ModifierInstance> GenerateModifiers(int32_t level) const;
// Getters and setters
const std::vector<ModifierType>& GetTypes() const { return types; }
ModifierType GetConvertTo() const { return convertTo; }
ModifierTemplateSelector GetSelector() const { return selector; }
const std::vector<ModifierScale>& GetScales() const { return scales; }
ModifierCategory GetCategory() const { return category; }
bool IsResistance() const { return isResistance; }
uint32_t GetEffectID() const { return effectID; }
std::string GetEffectType() const { return effectType; }
const std::string& GetUpgradeName() const { return upgradeName; }
void SetTypes(const std::vector<ModifierType>& types) { this->types = types; }
void SetConvertTo(ModifierType convertTo) { this->convertTo = convertTo; }
void SetSelector(ModifierTemplateSelector selector) { this->selector = selector; }
void SetScales(const std::vector<ModifierScale>& scales) { this->scales = scales; }
void SetCategory(ModifierCategory category) { this->category = category; }
void SetIsResistance(bool isResistance) { this->isResistance = isResistance; }
void SetEffectID(uint32_t effectID) { this->effectID = effectID; }
void SetEffectType(const std::string& effectType) { this->effectType = effectType; }
void SetUpgradeName(const std::string& upgradeName) { this->upgradeName = upgradeName; }
/**
* @brief Generate a HTML string representation of a set of modifier templates.
*
* @param modifiers The modifier templates to generate the HTML string for.
* @param level The level of the modifier templates.
* @return The HTML string.
*/
static std::string GenerateHtmlString(const std::vector<ModifierTemplate>& modifiers, int32_t level);
private:
std::optional<ModifierInstance> GenerateModifier(ModifierType type, int32_t level) const;
std::vector<ModifierType> types;
ModifierType convertTo;
ModifierTemplateSelector selector;
std::vector<ModifierScale> scales;
std::vector<float> polynomial;
ModifierCategory category;
ModifierOperator operatorType;
bool isResistance;
uint32_t effectID;
std::string effectType;
std::string upgradeName;
};
}

216
dGame/ModifierType.cpp Normal file
View File

@@ -0,0 +1,216 @@
#include "ModifierType.h"
#include <unordered_map>
#include <unordered_set>
using namespace nejlika;
namespace
{
const std::unordered_map<ModifierType, std::string> colorMap = {
{ModifierType::Health, "#750000"},
{ModifierType::Armor, "#525252"},
{ModifierType::Imagination, "#0077FF"},
{ModifierType::Offensive, "#71583B"},
{ModifierType::Defensive, "#71583B"},
{ModifierType::Physical, "#666666"},
{ModifierType::Pierce, "#4f4f4f"},
{ModifierType::Vitality, "#e84646"},
{ModifierType::Fire, "#ff0000"},
{ModifierType::Cold, "#94d0f2"},
{ModifierType::Lightning, "#00a2ff"},
{ModifierType::Corruption, "#3d00ad"},
{ModifierType::Psychic, "#4b0161"},
{ModifierType::Acid, "#00ff00"},
};
const std::unordered_map<ModifierType, std::string> nameMap = {
{ModifierType::Health, "Health"},
{ModifierType::Armor, "Armor"},
{ModifierType::Imagination, "Imagination"},
{ModifierType::Offensive, "Offensive Ability"},
{ModifierType::Defensive, "Defensive Ability"},
{ModifierType::Physical, "Physical Damage"},
{ModifierType::Pierce, "Pierce Damage"},
{ModifierType::Vitality, "Vitality Damage"},
{ModifierType::Fire, "Fire Damage"},
{ModifierType::Cold, "Cold Damage"},
{ModifierType::Lightning, "Lightning Damage"},
{ModifierType::Corruption, "Corruption Damage"},
{ModifierType::Psychic, "Psychic Damage"},
{ModifierType::Acid, "Acid Damage"},
{ModifierType::InternalDisassembly, "Internal Disassembly"},
{ModifierType::InternalDisassemblyDuration, "Internal Disassembly Duration"},
{ModifierType::Burn, "Burn"},
{ModifierType::BurnDuration, "Burn Duration"},
{ModifierType::Frostburn, "Frostburn"},
{ModifierType::FrostburnDuration, "Frostburn Duration"},
{ModifierType::Poison, "Poison"},
{ModifierType::PoisonDuration, "Poison Duration"},
{ModifierType::Electrocute, "Electrocute"},
{ModifierType::ElectrocuteDuration, "Electrocute Duration"},
{ModifierType::VitalityDecay, "Vitality Decay"},
{ModifierType::VitalityDecayDuration, "Vitality Decay Duration"},
{ModifierType::Seperation, "Seperation"},
{ModifierType::SeperationDuration, "Seperation Duration"},
{ModifierType::Elemental, "Elemental Damage"},
{ModifierType::Damage, "Damage"},
{ModifierType::Speed, "Speed"},
{ModifierType::AttackSpeed, "Attack Speed"},
{ModifierType::BlockRecovery, "Block Recovery Speed"},
{ModifierType::BlockChance, "Chance to Block"},
{ModifierType::Block, "Damage to Block"},
{ModifierType::CriticalDamage, "Critical-hit Damage"},
{ModifierType::HealthDrain, "Damage converted to Health"},
{ModifierType::ArmorPiercing, "Armor Piercing"},
{ModifierType::ReducedStunDuration, "Reduced Stun Duration"},
{ModifierType::SkillCooldownReduction, "Skill Cooldown Reduction"},
{ModifierType::SkillRecharge, "Skill Recharge Time"},
{ModifierType::Slow, "Slow"},
{ModifierType::Physique, "Physique"},
{ModifierType::Cunning, "Cunning"},
{ModifierType::Spirit, "Imagination"},
{ModifierType::AttacksPerSecond, "Attacks per Second"},
{ModifierType::ImaginationCost, "Imagination Cost"},
{ModifierType::MainWeaponDamage, "Main Weapon Damage"},
{ModifierType::Stun, "Target Stun Duration"}
};
const std::unordered_map<ModifierType, ModifierType> resistanceMap = {
{ModifierType::Physical, ModifierType::Physical},
{ModifierType::Pierce, ModifierType::Pierce},
{ModifierType::Vitality, ModifierType::Vitality},
{ModifierType::Fire, ModifierType::Fire},
{ModifierType::Cold, ModifierType::Cold},
{ModifierType::Lightning, ModifierType::Lightning},
{ModifierType::Corruption, ModifierType::Corruption},
{ModifierType::Psychic, ModifierType::Psychic},
{ModifierType::Acid, ModifierType::Acid},
{ModifierType::InternalDisassembly, ModifierType::Physical},
{ModifierType::Burn, ModifierType::Fire},
{ModifierType::Frostburn, ModifierType::Cold},
{ModifierType::Poison, ModifierType::Acid},
{ModifierType::VitalityDecay, ModifierType::Vitality},
{ModifierType::Electrocute, ModifierType::Lightning},
{ModifierType::Seperation, ModifierType::Seperation}
};
const std::unordered_set<ModifierType> normalDamageTypes = {
ModifierType::Physical,
ModifierType::Pierce,
ModifierType::Vitality,
ModifierType::Fire,
ModifierType::Cold,
ModifierType::Lightning,
ModifierType::Corruption,
ModifierType::Psychic,
ModifierType::Acid
};
const std::unordered_map<ModifierType, ModifierType> durationMap = {
{ModifierType::InternalDisassembly, ModifierType::InternalDisassemblyDuration},
{ModifierType::Burn, ModifierType::BurnDuration},
{ModifierType::Frostburn, ModifierType::FrostburnDuration},
{ModifierType::Poison, ModifierType::PoisonDuration},
{ModifierType::VitalityDecay, ModifierType::VitalityDecayDuration},
{ModifierType::Electrocute, ModifierType::ElectrocuteDuration},
{ModifierType::Seperation, ModifierType::SeperationDuration}
};
const std::unordered_map<ModifierType, ModifierType> overTimeMap = {
{ModifierType::Physical, ModifierType::InternalDisassembly},
{ModifierType::Fire, ModifierType::Burn},
{ModifierType::Cold, ModifierType::Frostburn},
{ModifierType::Poison, ModifierType::Poison},
{ModifierType::Vitality, ModifierType::VitalityDecay},
{ModifierType::Lightning, ModifierType::Electrocute}
};
const std::unordered_set<ModifierType> isOverTimeMap = {
ModifierType::InternalDisassembly,
ModifierType::Burn,
ModifierType::Frostburn,
ModifierType::Poison,
ModifierType::VitalityDecay,
ModifierType::Electrocute,
ModifierType::Seperation
};
const std::unordered_set<ModifierType> isDurationType = {
ModifierType::InternalDisassemblyDuration,
ModifierType::BurnDuration,
ModifierType::FrostburnDuration,
ModifierType::PoisonDuration,
ModifierType::VitalityDecayDuration,
ModifierType::ElectrocuteDuration,
ModifierType::SeperationDuration
};
}
const std::string& nejlika::GetModifierTypeColor(ModifierType type)
{
const auto color = colorMap.find(type);
if (color != colorMap.end()) {
return color->second;
}
static const std::string white = "#FFFFFF";
return white;
}
const std::string& nejlika::GetModifierTypeName(ModifierType type) {
const auto name = nameMap.find(type);
if (name != nameMap.end()) {
return name->second;
}
static const std::string invalid = "Invalid";
return invalid;
}
const ModifierType nejlika::GetResistanceType(ModifierType type) {
const auto resistance = resistanceMap.find(type);
if (resistance != resistanceMap.end()) {
return resistance->second;
}
return ModifierType::Invalid;
}
const bool nejlika::IsNormalDamageType(ModifierType type) {
return normalDamageTypes.find(type) != normalDamageTypes.end();
}
const ModifierType nejlika::GetOverTimeType(ModifierType type) {
const auto overTime = overTimeMap.find(type);
if (overTime != overTimeMap.end()) {
return overTime->second;
}
return ModifierType::Invalid;
}
const ModifierType nejlika::GetDurationType(ModifierType type) {
const auto duration = durationMap.find(type);
if (duration != durationMap.end()) {
return duration->second;
}
return ModifierType::Invalid;
}
const bool nejlika::IsOverTimeType(ModifierType type) {
return isOverTimeMap.find(type) != isOverTimeMap.end();
}
const bool nejlika::IsDurationType(ModifierType type) {
return isDurationType.find(type) != isDurationType.end();
}

96
dGame/ModifierType.h Normal file
View File

@@ -0,0 +1,96 @@
#pragma once
#include <cstdint>
#include <string>
namespace nejlika
{
enum class ModifierType : uint8_t
{
Health,
Armor,
Imagination,
Physique,
Cunning,
Spirit,
Offensive,
Defensive,
// Normal Types
Physical,
Fire,
Cold,
Lightning,
Acid,
Vitality,
Pierce,
Corruption, // Aether
Psychic, // Chaos
// Duration Types
InternalDisassembly, // Internal Trauma
InternalDisassemblyDuration,
Burn,
BurnDuration,
Frostburn,
FrostburnDuration,
Poison,
PoisonDuration,
VitalityDecay,
VitalityDecayDuration,
Electrocute,
ElectrocuteDuration,
Seperation, // Bleeding
SeperationDuration,
// Special
Elemental, // Even split between Fire, Cold, Lightning
Damage,
Speed,
AttackSpeed,
SkillModifier,
Slow,
ArmorPiercing,
ReducedStunDuration,
SkillCooldownReduction,
SkillRecharge,
BlockRecovery,
BlockChance,
Block,
HealthRegen,
ImaginationRegen,
AttacksPerSecond,
ImaginationCost,
MainWeaponDamage,
Stun,
CriticalDamage,
HealthDrain,
Invalid
};
const std::string& GetModifierTypeColor(ModifierType type);
const std::string& GetModifierTypeName(ModifierType type);
const ModifierType GetResistanceType(ModifierType type);
const bool IsNormalDamageType(ModifierType type);
const bool IsOverTimeType(ModifierType type);
const bool IsDurationType(ModifierType type);
const ModifierType GetOverTimeType(ModifierType type);
const ModifierType GetDurationType(ModifierType type);
}

215
dGame/NejlikaData.cpp Normal file
View File

@@ -0,0 +1,215 @@
#include "NejlikaData.h"
#include "Game.h"
#include "dConfig.h"
#include "json.hpp"
#include "Logger.h"
#include "Lookup.h"
#include <fstream>
using namespace nejlika;
namespace
{
std::unordered_map<nejlika::ModifierNameType, std::vector<nejlika::ModifierNameTemplate>> modifierNameTemplates;
std::unordered_map<LWOOBJID, nejlika::AdditionalItemData> additionalItemData;
std::unordered_map<LWOOBJID, nejlika::AdditionalEntityData> additionalEntityData;
std::unordered_map<LOT, nejlika::EntityTemplate> entityTemplates;
std::unordered_map<LOT, nejlika::UpgradeTemplate> upgradeTemplates;
nejlika::Lookup lookup;
}
const nejlika::Lookup& nejlika::NejlikaData::GetLookup()
{
return lookup;
}
const std::unordered_map<ModifierNameType,std::vector<ModifierNameTemplate>>& nejlika::NejlikaData::GetModifierNameTemplates()
{
return modifierNameTemplates;
}
const std::vector<nejlika::ModifierNameTemplate>& nejlika::NejlikaData::GetModifierNameTemplates(ModifierNameType type)
{
const auto it = modifierNameTemplates.find(type);
if (it != modifierNameTemplates.end()) {
return it->second;
}
static const std::vector<nejlika::ModifierNameTemplate> empty;
return empty;
}
const std::optional<AdditionalItemData*> nejlika::NejlikaData::GetAdditionalItemData(LWOOBJID id) {
const auto& it = additionalItemData.find(id);
if (it != additionalItemData.end()) {
return std::optional<AdditionalItemData*>(&it->second);
}
return std::nullopt;
}
const std::optional<AdditionalEntityData*> nejlika::NejlikaData::GetAdditionalEntityData(LWOOBJID id) {
const auto& it = additionalEntityData.find(id);
if (it != additionalEntityData.end()) {
return std::optional<AdditionalEntityData*>(&it->second);
}
return std::nullopt;
}
const std::optional<EntityTemplate*> nejlika::NejlikaData::GetEntityTemplate(LOT lot) {
const auto& it = entityTemplates.find(lot);
if (it != entityTemplates.end()) {
return std::optional<EntityTemplate*>(&it->second);
}
return std::nullopt;
}
const std::optional<UpgradeTemplate*> nejlika::NejlikaData::GetUpgradeTemplate(LOT lot) {
const auto& it = upgradeTemplates.find(lot);
if (it != upgradeTemplates.end()) {
return std::optional<UpgradeTemplate*>(&it->second);
}
return std::nullopt;
}
void nejlika::NejlikaData::SetAdditionalItemData(LWOOBJID id, AdditionalItemData data) {
additionalItemData[id] = data;
}
void nejlika::NejlikaData::SetAdditionalEntityData(LWOOBJID id, AdditionalEntityData data) {
additionalEntityData[id] = data;
}
void nejlika::NejlikaData::UnsetAdditionalItemData(LWOOBJID id) {
additionalItemData.erase(id);
}
void nejlika::NejlikaData::UnsetAdditionalEntityData(LWOOBJID id) {
additionalEntityData.erase(id);
}
void nejlika::NejlikaData::LoadNejlikaData()
{
const auto& lookupFile = Game::config->GetValue("lookup");
if (!lookupFile.empty())
{
lookup = Lookup(lookupFile);
}
modifierNameTemplates.clear();
// Load data from json file
const auto& directory_name = Game::config->GetValue("nejlika");
if (directory_name.empty())
{
return;
}
// Loop through all files in the directory
for (const auto& entry : std::filesystem::directory_iterator(directory_name))
{
if (!entry.is_regular_file())
{
continue;
}
// It has to end on .mod.json
const auto& path = entry.path().string();
if (path.size() < 9 || path.substr(path.size() - 9) != ".mod.json")
{
continue;
}
LoadNejlikaDataFile(entry.path().string());
}
}
void nejlika::NejlikaData::LoadNejlikaDataFile(const std::string& path) {
std::ifstream file(path);
if (!file.is_open())
{
LOG("Failed to open nejlika data file: %s", path.c_str());
return;
}
nlohmann::json json;
try
{
json = nlohmann::json::parse(file);
}
catch (const nlohmann::json::exception& e)
{
LOG("Failed to parse nejlika data file: %s", e.what());
return;
}
if (json.contains("modifier-templates"))
{
const auto& modifierTemplates = json["modifier-templates"];
for (const auto& value : modifierTemplates)
{
auto modifierTemplate = ModifierNameTemplate(value);
if (modifierTemplate.GetModifiers().empty())
{
continue;
}
modifierNameTemplates[modifierTemplate.GetType()].push_back(modifierTemplate);
}
}
LOG("Loaded %d modifier templates", modifierNameTemplates.size());
if (json.contains("entity-templates"))
{
const auto& entityTemplatesArray = json["entity-templates"];
for (const auto& value : entityTemplatesArray)
{
auto entityTemplate = EntityTemplate(value);
entityTemplates[entityTemplate.GetLOT()] = entityTemplate;
}
}
LOG("Loaded %d entity templates", entityTemplates.size());
if (json.contains("upgrade-templates"))
{
const auto& upgradeTemplatesArray = json["upgrade-templates"];
for (const auto& value : upgradeTemplatesArray)
{
auto upgradeTemplate = UpgradeTemplate(value);
upgradeTemplates[upgradeTemplate.GetLot()] = upgradeTemplate;
}
}
LOG("Loaded %d upgrade templates", upgradeTemplates.size());
}

42
dGame/NejlikaData.h Normal file
View File

@@ -0,0 +1,42 @@
#pragma once
#include <cstdint>
#include <vector>
#include "ModifierNameTemplate.h"
#include "EntityTemplate.h"
#include "AdditionalItemData.h"
#include "AdditionalEntityData.h"
#include "UpgradeTemplate.h"
#include "Lookup.h"
namespace nejlika::NejlikaData
{
const std::unordered_map<ModifierNameType, std::vector<ModifierNameTemplate>>& GetModifierNameTemplates();
const std::vector<ModifierNameTemplate>& GetModifierNameTemplates(ModifierNameType type);
const std::optional<AdditionalItemData*> GetAdditionalItemData(LWOOBJID id);
const std::optional<AdditionalEntityData*> GetAdditionalEntityData(LWOOBJID id);
const std::optional<EntityTemplate*> GetEntityTemplate(LOT lot);
const std::optional<UpgradeTemplate*> GetUpgradeTemplate(LOT lot);
void SetAdditionalItemData(LWOOBJID id, AdditionalItemData data);
void SetAdditionalEntityData(LWOOBJID id, AdditionalEntityData data);
void UnsetAdditionalItemData(LWOOBJID id);
void UnsetAdditionalEntityData(LWOOBJID id);
void LoadNejlikaData();
void LoadNejlikaDataFile(const std::string& path);
const Lookup& GetLookup();
}

33
dGame/NejlikaHelpers.cpp Normal file
View File

@@ -0,0 +1,33 @@
#include "NejlikaHelpers.h"
void nejlika::NejlikaHelpers::RenderDamageText(const std::string & text, Entity * attacker, Entity * damaged,
float scale, int32_t fontSize, int32_t colorR, int32_t colorG, int32_t colorB, int32_t colorA)
{
if (damaged == nullptr) {
return;
}
auto damagedPosition = damaged->GetPosition();
// Add a slight random offset to the damage position
damagedPosition.x += (rand() % 10 - 5) / 5.0f;
damagedPosition.y += (rand() % 10 - 5) / 5.0f;
damagedPosition.z += (rand() % 10 - 5) / 5.0f;
const auto& damageText = text;
std::stringstream damageUIMessage;
damageUIMessage << 0.0825 << ";" << 0.12 << ";" << damagedPosition.x << ";" << damagedPosition.y + 4.5f << ";" << damagedPosition.z << ";" << 0.1 << ";";
damageUIMessage << 200 * scale << ";" << 200 * scale << ";" << 0.5 << ";" << 1.0 << ";" << damageText << ";" << 4 << ";" << fontSize << ";" << colorR << ";" << colorG << ";" << colorB << ";";
damageUIMessage << colorA;
const auto damageUIStr = damageUIMessage.str();
if (damaged->IsPlayer()) {
damaged->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
} else if (attacker != nullptr && attacker->IsPlayer()) {
attacker->SetNetworkVar<std::string>(u"renderText", damageUIStr, UNASSIGNED_SYSTEM_ADDRESS);
}
}

9
dGame/NejlikaHelpers.h Normal file
View File

@@ -0,0 +1,9 @@
#include <Entity.h>
namespace nejlika::NejlikaHelpers
{
void RenderDamageText(const std::string& text, Entity* attacker, Entity* damaged,
float scale = 1, int32_t fontSize = 4, int32_t colorR = 255, int32_t colorG = 255, int32_t colorB = 255, int32_t colorA = 0);
}

1048
dGame/NejlikaHooks.cpp Normal file

File diff suppressed because it is too large Load Diff

10
dGame/NejlikaHooks.h Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
namespace nejlika::NejlikaHooks
{
void InstallHooks();
void ItemDescription(Entity* entity, const SystemAddress& sysAddr, const std::string args);
}

View File

@@ -18,6 +18,8 @@ void PlayerManager::AddPlayer(Entity* player) {
if (iter == m_Players.end()) {
m_Players.push_back(player);
OnPlayerAdded(player);
}
}
@@ -27,6 +29,8 @@ bool PlayerManager::RemovePlayer(Entity* player) {
const bool toReturn = iter != m_Players.end();
if (toReturn) {
m_Players.erase(iter);
OnPlayerRemoved(player);
}
return toReturn;

View File

@@ -2,6 +2,7 @@
#define __PLAYERMANAGER__H__
#include "dCommonVars.h"
#include "Observable.h"
#include <string>
@@ -20,6 +21,9 @@ namespace PlayerManager {
Entity* GetPlayer(LWOOBJID playerID);
const std::vector<Entity*>& GetAllPlayers();
static Observable<Entity*> OnPlayerAdded;
static Observable<Entity*> OnPlayerRemoved;
};
#endif //!__PLAYERMANAGER__H__

16
dGame/TriggerParameters.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include <cstdint>
#include "BehaviorSlot.h"
namespace nejlika
{
class TriggerParameters
{
public:
int32_t SkillID = 0;
BehaviorSlot SelectedBehaviorSlot = BehaviorSlot::Invalid;
};
}

355
dGame/UpgradeEffect.cpp Normal file
View File

@@ -0,0 +1,355 @@
#include "UpgradeEffect.h"
#include "GeneralUtils.h"
#include "GameMessages.h"
#include "InventoryComponent.h"
#include <magic_enum.hpp>
#include <iostream>
using namespace nejlika;
nejlika::UpgradeEffect::UpgradeEffect(const nlohmann::json& json)
{
Load(json);
}
nlohmann::json nejlika::UpgradeEffect::ToJson() const
{
nlohmann::json json;
json["trigger-type"] = static_cast<int32_t>(triggerType);
nlohmann::json modifiersJson = nlohmann::json::array();
for (const auto& modifier : modifiers) {
modifiersJson.push_back(modifier.ToJson());
}
json["modifiers"] = modifiersJson;
if (!chance.empty()) {
nlohmann::json chanceJson = nlohmann::json::array();
for (const auto& scale : chance) {
chanceJson.push_back({
{"level", scale.level},
{"value", scale.value}
});
}
json["chance"] = chanceJson;
}
if (effectID != 0) {
json["effect-id"] = effectID;
}
if (!effectType.empty()) {
json["effect-type"] = effectType;
}
if (!conditions.empty()) {
nlohmann::json conditionsJson = nlohmann::json::array();
for (const auto& condition : conditions) {
conditionsJson.push_back(magic_enum::enum_name(condition));
}
json["conditions"] = conditionsJson;
}
if (equipSkillID != 0) {
json["grant-skill-id"] = equipSkillID;
}
return json;
}
std::vector<ModifierInstance> nejlika::UpgradeEffect::GenerateModifiers(int32_t level) const
{
std::vector<ModifierInstance> result;
for (const auto& modifier : modifiers) {
auto instances = modifier.GenerateModifiers(level);
result.insert(result.end(), instances.begin(), instances.end());
}
return result;
}
void nejlika::UpgradeEffect::Load(const nlohmann::json& json)
{
triggerType = magic_enum::enum_cast<UpgradeTriggerType>(json["trigger-type"].get<std::string>()).value_or(UpgradeTriggerType::OnHit);
modifiers.clear();
if (json.contains("modifiers")){
for (const auto& modifier : json["modifiers"]) {
ModifierTemplate effect(modifier);
modifiers.push_back(effect);
}
}
if (json.contains("chance")) {
chance.clear();
for (const auto& scale : json["chance"]) {
chance.push_back({
scale["level"].get<int32_t>(),
scale["value"].get<float>()
});
}
}
if (json.contains("effect-id")) {
effectID = json["effect-id"].get<int32_t>();
}
if (json.contains("effect-type")) {
effectType = json["effect-type"].get<std::string>();
}
if (json.contains("conditions")) {
conditions.clear();
for (const auto& condition : json["conditions"]) {
conditions.push_back(magic_enum::enum_cast<UpgradeTriggerCondition>(condition.get<std::string>()).value_or(UpgradeTriggerCondition::None));
}
}
if (json.contains("grant-skill-id")) {
equipSkillID = json["grant-skill-id"].get<int32_t>();
}
}
float nejlika::UpgradeEffect::CalculateChance(int32_t level) const {
if (chance.empty()) {
return 1;
}
// Find the highest level that is less than or equal to the given level
float value = 0;
for (const auto& scale : chance) {
if (scale.level <= level) {
value = scale.value;
}
}
return value;
}
bool nejlika::UpgradeEffect::CheckConditions(LWOOBJID origin, const TriggerParameters& params) const {
auto* entity = Game::entityManager->GetEntity(origin);
if (!entity) {
return false;
}
auto* inventory = entity->GetComponent<InventoryComponent>();
if (!inventory) {
return false;
}
const auto& skills = inventory->GetSkills();
const auto& equipped = inventory->GetEquippedItems();
for (const auto& condition : conditions) {
switch (condition) {
case UpgradeTriggerCondition::PrimaryAbility:
if (params.SelectedBehaviorSlot != BehaviorSlot::Primary) {
return false;
}
break;
case UpgradeTriggerCondition::UseSkill:
if (params.SkillID != equipSkillID) {
return false;
}
case UpgradeTriggerCondition::None:
break;
case UpgradeTriggerCondition::Unarmed:
if (equipped.contains("special_r")) {
return false;
}
break;
case UpgradeTriggerCondition::Melee:
if (!equipped.contains("special_r")) {
return false;
}
break;
case UpgradeTriggerCondition::TwoHanded:
{
if (!equipped.contains("special_r")) {
return false;
}
const auto& weaponLot = equipped.at("special_r").lot;
const auto& info = Inventory::FindItemComponent(weaponLot);
if (!info.isTwoHanded) {
return false;
}
break;
}
case UpgradeTriggerCondition::Shield:
if (!equipped.contains("special_l")) {
return false;
}
break;
default:
break;
}
}
return true;
}
void nejlika::UpgradeEffect::OnTrigger(LWOOBJID origin) const {
auto* entity = Game::entityManager->GetEntity(origin);
if (!entity) {
return;
}
auto* inventory = entity->GetComponent<InventoryComponent>();
if (!inventory) {
return;
}
}
std::vector<ModifierInstance> nejlika::UpgradeEffect::Trigger(const std::vector<UpgradeEffect>& modifiers, int32_t level, UpgradeTriggerType triggerType, LWOOBJID origin, const TriggerParameters& params) {
std::vector<ModifierInstance> result;
for (const auto& modifier : modifiers) {
if (modifier.GetTriggerType() != triggerType) {
continue;
}
if (!modifier.CheckConditions(origin, params)) {
continue;
}
float chanceRoll = GeneralUtils::GenerateRandomNumber<float>(0, 1);
if (chanceRoll > modifier.CalculateChance(level)) {
continue;
}
std::cout << "Triggering effect trigger type: " << magic_enum::enum_name(triggerType) << std::endl;
modifier.OnTrigger(origin);
auto instances = modifier.GenerateModifiers(level);
result.insert(result.end(), instances.begin(), instances.end());
if (modifier.effectID == 0) {
continue;
}
GameMessages::SendPlayFXEffect(
origin,
modifier.effectID,
GeneralUtils::UTF8ToUTF16(modifier.effectType),
std::to_string(GeneralUtils::GenerateRandomNumber<size_t>())
);
}
return result;
}
void nejlika::UpgradeEffect::AddSkill(LWOOBJID origin) const {
auto* entity = Game::entityManager->GetEntity(origin);
if (!entity) {
return;
}
auto* inventory = entity->GetComponent<InventoryComponent>();
if (!inventory) {
return;
}
if (triggerType != UpgradeTriggerType::Active) {
return;
}
if (equipSkillID != 0) {
inventory->AddSkill(equipSkillID);
}
}
void nejlika::UpgradeEffect::RemoveSkill(LWOOBJID origin) const {
auto* entity = Game::entityManager->GetEntity(origin);
if (!entity) {
return;
}
auto* inventory = entity->GetComponent<InventoryComponent>();
if (!inventory) {
return;
}
if (triggerType != UpgradeTriggerType::Active) {
return;
}
if (equipSkillID != 0) {
inventory->RemoveSkill(equipSkillID);
}
}
std::string nejlika::UpgradeEffect::GenerateHtmlString(const std::vector<UpgradeEffect>& effects, int32_t level) {
std::stringstream ss;
for (const auto& effect : effects) {
const auto chance = effect.CalculateChance(level);
for (const auto& condition : effect.conditions) {
if (condition == UpgradeTriggerCondition::None || condition == UpgradeTriggerCondition::UseSkill) {
continue;
}
ss << "<font color=\"#D0AB62\">";
switch (condition) {
case UpgradeTriggerCondition::PrimaryAbility:
ss << "On main-hand attack";
break;
case UpgradeTriggerCondition::Melee:
ss << "Requires melee weapon";
break;
case UpgradeTriggerCondition::TwoHanded:
ss << "Requires two-handed weapon";
break;
case UpgradeTriggerCondition::Shield:
ss << "Requires a shield";
break;
case UpgradeTriggerCondition::Unarmed:
ss << "Requires unarmed attack";
break;
}
ss << "</font>\n\n";
}
if (chance < 1.0f) {
ss << "<font color=\"#FFFFFF\">" << chance * 100 << "%</font><font color=\"#D0AB62\"> chance to trigger</font>\n";
}
std::cout << "Level " << level << " chance: " << chance << std::endl;
ss << ModifierInstance::GenerateHtmlString(effect.GenerateModifiers(level));
}
return ss.str();
}

69
dGame/UpgradeEffect.h Normal file
View File

@@ -0,0 +1,69 @@
#pragma once
#include "ModifierTemplate.h"
#include "UpgradeTriggerType.h"
#include "UpgradeTriggerCondition.h"
#include <InventoryComponent.h>
#include "TriggerParameters.h"
#include <dCommonVars.h>
namespace nejlika
{
class UpgradeEffect
{
public:
UpgradeEffect(const nlohmann::json& json);
nlohmann::json ToJson() const;
std::vector<ModifierInstance> GenerateModifiers(int32_t level) const;
void Load(const nlohmann::json& json);
float CalculateChance(int32_t level) const;
bool CheckConditions(LWOOBJID origin, const TriggerParameters& params) const;
void OnTrigger(LWOOBJID origin) const;
static std::vector<ModifierInstance> Trigger(const std::vector<UpgradeEffect>& modifiers, int32_t level, UpgradeTriggerType triggerType, LWOOBJID origin, const TriggerParameters& params);
// Getters
const std::vector<ModifierTemplate>& GetModifiers() const { return modifiers; }
UpgradeTriggerType GetTriggerType() const { return triggerType; }
void AddSkill(LWOOBJID origin) const;
void RemoveSkill(LWOOBJID origin) const;
/**
* @brief Generate a HTML string representation of a set of upgrade effects.
*
* @param modifiers The upgrade effects to generate the HTML string for.
* @param level The level of the upgrade effects.
* @return The HTML string.
*/
static std::string GenerateHtmlString(const std::vector<UpgradeEffect>& effects, int32_t level);
private:
struct UpgradeScale
{
int32_t level;
float value;
};
std::vector<UpgradeScale> chance;
std::vector<UpgradeTriggerCondition> conditions;
UpgradeTriggerType triggerType;
int32_t equipSkillID = 0;
std::vector<ModifierTemplate> modifiers;
int32_t effectID = 0;
std::string effectType = "";
std::vector<ModifierTemplate> debuffs;
std::vector<UpgradeScale> debuffDuration;
};
}

Some files were not shown because too many files have changed in this diff Show More