mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-12-21 11:59:37 -06:00
Compare commits
81 Commits
friction-v
...
EmosewaMC-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f114b22aef | ||
| 631365b7f7 | |||
| 32f8bda538 | |||
|
|
cd78a3dec7 | ||
|
|
a2ca273370 | ||
|
|
1ed3af63b9 | ||
|
|
213c3c37b0 | ||
|
|
b7341c8106 | ||
|
|
5292f36417 | ||
|
|
da910309a0 | ||
|
|
430c6da4a7 | ||
|
|
d5613b8034 | ||
| 8886bf6547 | |||
|
|
fde62a4777 | ||
|
|
18a0ae599b | ||
|
|
0a616f891f | ||
|
|
2ba3103a0c | ||
|
|
e8ba3357e8 | ||
|
|
de3e53de6c | ||
|
|
ab5adea24c | ||
|
|
e1af528d9b | ||
| d8945e9067 | |||
| 2b9c014b86 | |||
|
|
63460ea00d | ||
|
|
09dfb6df3a | ||
|
|
56da3f8543 | ||
|
|
3222e78815 | ||
|
|
3939f19b08 | ||
|
|
1556f580d6 | ||
|
|
d382eb3bc2 | ||
|
|
f8f5b731f1 | ||
|
|
e40a597f18 | ||
|
|
af28d170fb | ||
|
|
e2616c5f11 | ||
| 36eecd693d | |||
|
|
4569f62100 | ||
|
|
afb97a81b5 | ||
|
|
b17ba56af1 | ||
|
|
9d62a8cd0b | ||
|
|
416021c208 | ||
| 3fa6ea4cea | |||
| 37524af549 | |||
|
|
53b559bef3 | ||
| 848c066924 | |||
| 7429902a64 | |||
| 22e5d02400 | |||
| cf7fa8e52d | |||
| 1eff3ae454 | |||
|
|
7c2437173b | ||
|
|
2570c74b71 | ||
|
|
2f48981801 | ||
|
|
1464762bcd | ||
| 9c58ea5c41 | |||
|
|
1d5c71eb9b | ||
| 162f84e285 | |||
|
|
8880486c8b | ||
|
|
8d37d9b681 | ||
|
|
b974eed8f5 | ||
|
|
8edade5f98 | ||
| 353c328485 | |||
|
|
4a6f3e44ee | ||
|
|
971e0fb3b6 | ||
|
|
62213cd701 | ||
|
|
d4af7d76a2 | ||
|
|
89fb66c4a9 | ||
|
|
906887bda9 | ||
|
|
a745cdb727 | ||
|
|
d8e73def9d | ||
|
|
2bdbf129cf | ||
|
|
c13937bd1f | ||
| 8d44f2f5da | |||
| 366c3db7fe | |||
|
|
f02e9c0f6a | ||
| 409d682c9d | |||
|
|
63af2c8da7 | ||
|
|
ce2e6f595b | ||
| c552f46780 | |||
| 14d4bf3cc5 | |||
| 26f2eb409f | |||
| 1af3e59348 | |||
|
|
e3077aa410 |
5
.github/workflows/build-and-test.yml
vendored
5
.github/workflows/build-and-test.yml
vendored
@@ -10,12 +10,13 @@ jobs:
|
||||
build-and-test:
|
||||
name: Build & Test (${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ windows-2022, ubuntu-20.04, macos-11 ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- name: Add msbuild to PATH (Windows only)
|
||||
@@ -34,7 +35,7 @@ jobs:
|
||||
buildPreset: "ci-${{matrix.os}}"
|
||||
testPreset: "ci-${{matrix.os}}"
|
||||
- name: artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ github.ref == 'ref/head/main' }}
|
||||
with:
|
||||
name: build-${{matrix.os}}
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -121,4 +121,4 @@ docker/__pycache__
|
||||
|
||||
docker-compose.override.yml
|
||||
|
||||
!/tests/TestBitStreams/*.bin
|
||||
!*Test.bin
|
||||
|
||||
130
CMakeLists.txt
130
CMakeLists.txt
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
project(Darkflame)
|
||||
include(CTest)
|
||||
|
||||
@@ -58,7 +58,7 @@ if(UNIX)
|
||||
if(APPLE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17 -O2 -Wuninitialized -D_GLIBCXX_USE_CXX11_ABI=0 -D_GLIBCXX_USE_CXX17_ABI=0 -fPIC")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17 -O2 -Wuninitialized -D_GLIBCXX_USE_CXX11_ABI=0 -D_GLIBCXX_USE_CXX17_ABI=0 -static-libgcc -fPIC")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17 -O2 -Wuninitialized -D_GLIBCXX_USE_CXX11_ABI=0 -D_GLIBCXX_USE_CXX17_ABI=0 -static-libgcc -fPIC -lstdc++fs")
|
||||
endif()
|
||||
if (__dynamic AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -rdynamic")
|
||||
@@ -76,6 +76,15 @@ endif()
|
||||
|
||||
# Our output dir
|
||||
set(CMAKE_BINARY_DIR ${PROJECT_BINARY_DIR})
|
||||
|
||||
# TODO make this not have to override the build type directories
|
||||
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})
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR})
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR})
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR})
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
@@ -83,14 +92,11 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
# Create a /res directory
|
||||
make_directory(${CMAKE_BINARY_DIR}/res)
|
||||
|
||||
# Create a /locale directory
|
||||
make_directory(${CMAKE_BINARY_DIR}/locale)
|
||||
|
||||
# Create a /logs directory
|
||||
make_directory(${CMAKE_BINARY_DIR}/logs)
|
||||
|
||||
# Copy resource files on first build
|
||||
set(RESOURCE_FILES "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini" "blacklist.dcf")
|
||||
set(RESOURCE_FILES "sharedconfig.ini" "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini" "blacklist.dcf")
|
||||
foreach(resource_file ${RESOURCE_FILES})
|
||||
if (NOT EXISTS ${PROJECT_BINARY_DIR}/${resource_file})
|
||||
configure_file(
|
||||
@@ -101,6 +107,17 @@ foreach(resource_file ${RESOURCE_FILES})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Copy navmesh data on first build and extract it
|
||||
if (NOT EXISTS ${PROJECT_BINARY_DIR}/navmeshes/)
|
||||
configure_file(
|
||||
${CMAKE_SOURCE_DIR}/resources/navmeshes.zip ${PROJECT_BINARY_DIR}/navmeshes.zip
|
||||
COPYONLY
|
||||
)
|
||||
|
||||
file(ARCHIVE_EXTRACT INPUT ${PROJECT_BINARY_DIR}/navmeshes.zip)
|
||||
file(REMOVE ${PROJECT_BINARY_DIR}/navmeshes.zip)
|
||||
endif()
|
||||
|
||||
# Copy vanity files on first build
|
||||
set(VANITY_FILES "CREDITS.md" "INFO.md" "TESTAMENT.md" "NPC.xml")
|
||||
foreach(file ${VANITY_FILES})
|
||||
@@ -108,13 +125,25 @@ foreach(file ${VANITY_FILES})
|
||||
endforeach()
|
||||
|
||||
# Move our migrations for MasterServer to run
|
||||
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/migrations/)
|
||||
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/migrations/dlu/)
|
||||
file(GLOB SQL_FILES ${CMAKE_SOURCE_DIR}/migrations/dlu/*.sql)
|
||||
foreach(file ${SQL_FILES})
|
||||
get_filename_component(file ${file} NAME)
|
||||
if (NOT EXISTS ${PROJECT_BINARY_DIR}/migrations/${file})
|
||||
if (NOT EXISTS ${PROJECT_BINARY_DIR}/migrations/dlu/${file})
|
||||
configure_file(
|
||||
${CMAKE_SOURCE_DIR}/migrations/dlu/${file} ${PROJECT_BINARY_DIR}/migrations/${file}
|
||||
${CMAKE_SOURCE_DIR}/migrations/dlu/${file} ${PROJECT_BINARY_DIR}/migrations/dlu/${file}
|
||||
COPYONLY
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/migrations/cdserver/)
|
||||
file(GLOB SQL_FILES ${CMAKE_SOURCE_DIR}/migrations/cdserver/*.sql)
|
||||
foreach(file ${SQL_FILES})
|
||||
get_filename_component(file ${file} NAME)
|
||||
if (NOT EXISTS ${PROJECT_BINARY_DIR}/migrations/cdserver/${file})
|
||||
configure_file(
|
||||
${CMAKE_SOURCE_DIR}/migrations/cdserver/${file} ${PROJECT_BINARY_DIR}/migrations/cdserver/${file}
|
||||
COPYONLY
|
||||
)
|
||||
endif()
|
||||
@@ -123,6 +152,8 @@ endforeach()
|
||||
# Create our list of include directories
|
||||
set(INCLUDED_DIRECTORIES
|
||||
"dCommon"
|
||||
"dCommon/dClient"
|
||||
"dCommon/dEnums"
|
||||
"dChatFilter"
|
||||
"dGame"
|
||||
"dGame/dBehaviors"
|
||||
@@ -131,6 +162,7 @@ set(INCLUDED_DIRECTORIES
|
||||
"dGame/dInventory"
|
||||
"dGame/dMission"
|
||||
"dGame/dEntity"
|
||||
"dGame/dPropertyBehaviors"
|
||||
"dGame/dUtilities"
|
||||
"dPhysics"
|
||||
"dNavigation"
|
||||
@@ -140,12 +172,85 @@ set(INCLUDED_DIRECTORIES
|
||||
"dDatabase/Tables"
|
||||
"dNet"
|
||||
"dScripts"
|
||||
"dScripts/02_server"
|
||||
"dScripts/ai"
|
||||
"dScripts/client"
|
||||
"dScripts/EquipmentScripts"
|
||||
"dScripts/zone"
|
||||
"dScripts/02_server/DLU"
|
||||
"dScripts/02_server/Enemy"
|
||||
"dScripts/02_server/Equipment"
|
||||
"dScripts/02_server/Map"
|
||||
"dScripts/02_server/Minigame"
|
||||
"dScripts/02_server/Objects"
|
||||
"dScripts/02_server/Pets"
|
||||
"dScripts/02_server/Enemy/AG"
|
||||
"dScripts/02_server/Enemy/AM"
|
||||
"dScripts/02_server/Enemy/FV"
|
||||
"dScripts/02_server/Enemy/General"
|
||||
"dScripts/02_server/Enemy/Survival"
|
||||
"dScripts/02_server/Enemy/VE"
|
||||
"dScripts/02_server/Enemy/Waves"
|
||||
"dScripts/02_server/Map/AG"
|
||||
"dScripts/02_server/Map/AG_Spider_Queen"
|
||||
"dScripts/02_server/Map/AM"
|
||||
"dScripts/02_server/Map/FV"
|
||||
"dScripts/02_server/Map/General"
|
||||
"dScripts/02_server/Map/GF"
|
||||
"dScripts/02_server/Map/njhub"
|
||||
"dScripts/02_server/Map/NS"
|
||||
"dScripts/02_server/Map/NT"
|
||||
"dScripts/02_server/Map/PR"
|
||||
"dScripts/02_server/Map/Property"
|
||||
"dScripts/02_server/Map/SS"
|
||||
"dScripts/02_server/Map/VE"
|
||||
"dScripts/02_server/Map/FV/Racing"
|
||||
"dScripts/02_server/Map/General/Ninjago"
|
||||
"dScripts/02_server/Map/njhub/boss_instance"
|
||||
"dScripts/02_server/Map/NS/Waves"
|
||||
"dScripts/02_server/Map/Property/AG_Med"
|
||||
"dScripts/02_server/Map/Property/AG_Small"
|
||||
"dScripts/02_server/Map/Property/NS_Med"
|
||||
"dScripts/02_server/Minigame/General"
|
||||
"dScripts/ai/ACT"
|
||||
"dScripts/ai/AG"
|
||||
"dScripts/ai/FV"
|
||||
"dScripts/ai/GENERAL"
|
||||
"dScripts/ai/GF"
|
||||
"dScripts/ai/MINIGAME"
|
||||
"dScripts/ai/NP"
|
||||
"dScripts/ai/NS"
|
||||
"dScripts/ai/PETS"
|
||||
"dScripts/ai/PROPERTY"
|
||||
"dScripts/ai/RACING"
|
||||
"dScripts/ai/SPEC"
|
||||
"dScripts/ai/WILD"
|
||||
"dScripts/ai/ACT/FootRace"
|
||||
"dScripts/ai/MINIGAME/SG_GF"
|
||||
"dScripts/ai/MINIGAME/SG_GF/SERVER"
|
||||
"dScripts/ai/NS/NS_PP_01"
|
||||
"dScripts/ai/NS/WH"
|
||||
"dScripts/ai/PROPERTY/AG"
|
||||
"dScripts/ai/RACING/OBJECTS"
|
||||
"dScripts/client/ai"
|
||||
"dScripts/client/ai/PR"
|
||||
"dScripts/zone/AG"
|
||||
"dScripts/zone/LUPs"
|
||||
"dScripts/zone/PROPERTY"
|
||||
"dScripts/zone/PROPERTY/FV"
|
||||
"dScripts/zone/PROPERTY/GF"
|
||||
"dScripts/zone/PROPERTY/NS"
|
||||
|
||||
"thirdparty/raknet/Source"
|
||||
"thirdparty/tinyxml2"
|
||||
"thirdparty/recastnavigation"
|
||||
"thirdparty/SQLite"
|
||||
"thirdparty/cpplinq"
|
||||
|
||||
"tests"
|
||||
"tests/dCommonTests"
|
||||
"tests/dGameTests"
|
||||
"tests/dGameTests/dComponentsTests"
|
||||
)
|
||||
|
||||
# Add system specfic includes for Apple, Windows and Other Unix OS' (including Linux)
|
||||
@@ -160,7 +265,6 @@ elseif (UNIX)
|
||||
set(INCLUDED_DIRECTORIES ${INCLUDED_DIRECTORIES} "thirdparty/libbcrypt/include/bcrypt")
|
||||
endif()
|
||||
|
||||
include_directories(${ZLIB_INCLUDE_DIRS})
|
||||
# Add binary directory as an include directory
|
||||
include_directories(${PROJECT_BINARY_DIR})
|
||||
|
||||
@@ -228,8 +332,6 @@ if (UNIX)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(tests)
|
||||
|
||||
# Include all of our binary directories
|
||||
add_subdirectory(dWorldServer)
|
||||
add_subdirectory(dAuthServer)
|
||||
@@ -262,3 +364,7 @@ target_precompile_headers(
|
||||
tinyxml2 PRIVATE
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${PROJECT_SOURCE_DIR}/thirdparty/tinyxml2/tinyxml2.h>"
|
||||
)
|
||||
|
||||
if (${__enable_testing__} MATCHES "1")
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
@@ -1,126 +1,131 @@
|
||||
{
|
||||
"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"
|
||||
"version": 3,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 14,
|
||||
"patch": 0
|
||||
},
|
||||
{
|
||||
"name": "ci-ubuntu-20.04",
|
||||
"displayName": "CI configure step for Ubuntu",
|
||||
"description": "Same as default, Used in GitHub actions workflow",
|
||||
"inherits": "default"
|
||||
},
|
||||
{
|
||||
"name": "ci-macos-11",
|
||||
"displayName": "CI configure step for MacOS",
|
||||
"description": "Same as default, Used in GitHub actions workflow",
|
||||
"inherits": "default",
|
||||
"cacheVariables": {
|
||||
"OPENSSL_ROOT_DIR": "/usr/local/Cellar/openssl@3/3.0.5/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "default",
|
||||
"displayName": "Default configure step",
|
||||
"description": "Use 'build' dir and Unix makefiles",
|
||||
"binaryDir": "${sourceDir}/build",
|
||||
"generator": "Unix Makefiles"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
|
||||
{
|
||||
"name": "ci-ubuntu-20.04",
|
||||
"displayName": "CI configure step for Ubuntu",
|
||||
"description": "Same as default, Used in GitHub actions workflow",
|
||||
"inherits": "default"
|
||||
},
|
||||
{
|
||||
"name": "ci-macos-11",
|
||||
"displayName": "CI configure step for MacOS",
|
||||
"description": "Same as default, Used in GitHub actions workflow",
|
||||
"inherits": "default",
|
||||
"cacheVariables": {
|
||||
"OPENSSL_ROOT_DIR": "/usr/local/opt/openssl@3/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"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-20.04",
|
||||
"configurePreset": "ci-ubuntu-20.04",
|
||||
"displayName": "Linux CI Build",
|
||||
"description": "This preset is used by the CI build on linux",
|
||||
"jobs": 2
|
||||
},
|
||||
{
|
||||
"name": "ci-macos-11",
|
||||
"configurePreset": "ci-macos-11",
|
||||
"displayName": "MacOS CI Build",
|
||||
"description": "This preset is used by the CI build on MacOS",
|
||||
"jobs": 2
|
||||
}
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "ci-ubuntu-20.04",
|
||||
"configurePreset": "ci-ubuntu-20.04",
|
||||
"displayName": "CI Tests on Linux",
|
||||
"description": "Runs all tests on a linux configuration",
|
||||
"execution": {
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "default",
|
||||
"configurePreset": "default",
|
||||
"displayName": "Default Build",
|
||||
"description": "Default Build",
|
||||
"jobs": 2
|
||||
},
|
||||
"output": {
|
||||
"outputOnFailure": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ci-macos-11",
|
||||
"configurePreset": "ci-macos-11",
|
||||
"displayName": "CI Tests on MacOS",
|
||||
"description": "Runs all tests on a Mac configuration",
|
||||
"execution": {
|
||||
{
|
||||
"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
|
||||
},
|
||||
"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": {
|
||||
{
|
||||
"name": "ci-ubuntu-20.04",
|
||||
"configurePreset": "ci-ubuntu-20.04",
|
||||
"displayName": "Linux CI Build",
|
||||
"description": "This preset is used by the CI build on linux",
|
||||
"jobs": 2
|
||||
},
|
||||
"output": {
|
||||
"outputOnFailure": true
|
||||
{
|
||||
"name": "ci-macos-11",
|
||||
"configurePreset": "ci-macos-11",
|
||||
"displayName": "MacOS CI Build",
|
||||
"description": "This preset is used by the CI build on MacOS",
|
||||
"jobs": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "ci-ubuntu-20.04",
|
||||
"configurePreset": "ci-ubuntu-20.04",
|
||||
"displayName": "CI Tests on Linux",
|
||||
"description": "Runs all tests on a linux configuration",
|
||||
"execution": {
|
||||
"jobs": 2
|
||||
},
|
||||
"output": {
|
||||
"outputOnFailure": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ci-macos-11",
|
||||
"configurePreset": "ci-macos-11",
|
||||
"displayName": "CI Tests on MacOS",
|
||||
"description": "Runs all tests on a Mac configuration",
|
||||
"execution": {
|
||||
"jobs": 2
|
||||
},
|
||||
"output": {
|
||||
"outputOnFailure": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ci-windows-2022",
|
||||
"configurePreset": "ci-windows-2022",
|
||||
"displayName": "CI Tests on windows",
|
||||
"description": "Runs all tests on a windows configuration",
|
||||
"configuration": "RelWithDebInfo",
|
||||
"execution": {
|
||||
"jobs": 2
|
||||
},
|
||||
"output": {
|
||||
"outputOnFailure": true
|
||||
},
|
||||
"filter": {
|
||||
"exclude": {
|
||||
"name": "((example)|(minigzip))+"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ __dynamic=1
|
||||
# __include_backtrace__=1
|
||||
# Set __include_backtrace__ to 1 to includes the backtrace library for better crashlogs.
|
||||
# __compile_backtrace__=1
|
||||
# Set __compile_backtrace__ to 1 to compile the backtrace library instead of using system libraries.
|
||||
# Set __compile_backtrace__ to 1 to compile the backtrace library instead of using system libraries.
|
||||
__maria_db_connector_compile_jobs__=1
|
||||
# Set to the number of jobs (make -j equivalent) to compile the mariadbconn files with.
|
||||
__enable_testing__=1
|
||||
# When set to 1 and uncommented, compiling and linking testing folders and libraries will be done.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
- [Docker](https://docs.docker.com/get-docker/) (Docker Desktop or on Linux normal Docker)
|
||||
- [Docker Compose](https://docs.docker.com/compose/install/) (Included in Docker Desktop)
|
||||
- LEGO® Universe packed Client. Check the main [README](./README.md) for details on this.
|
||||
- LEGO® Universe Client. Check the main [README](./README.md) for details on this.
|
||||
|
||||
## Run server inside Docker
|
||||
|
||||
|
||||
38
README.md
38
README.md
@@ -38,10 +38,14 @@ Some tools utilized to streamline the setup process require Python 3, make sure
|
||||
|
||||
### Choosing the right version for your client
|
||||
DLU clients identify themselves using a higher version number than the regular live clients out there.
|
||||
This was done make sure that older and incomplete clients wouldn't produce false positive bug reports for us, and because we made bug fixes and new content for the client.
|
||||
This was done make sure that older and incomplete clients wouldn't produce false positive bug reports for us, and because we made bug fixes and new content for the client.
|
||||
|
||||
If you're using a DLU client you'll have to go into the "CMakeVariables.txt" file and change the NET_VERSION variable to 171023 to match the modified client's version number.
|
||||
|
||||
### Enabling testing
|
||||
While it is highly recommended to enable testing, if you would like to save compilation time, you'll want to comment out the enable_testing variable in CMakeVariables.txt.
|
||||
It is recommended that after building and if testing is enabled, to run `ctest` and make sure all the tests pass.
|
||||
|
||||
### Using Docker
|
||||
Refer to [Docker.md](/Docker.md).
|
||||
|
||||
@@ -169,7 +173,7 @@ Known good SHA256 checksums of the client:
|
||||
- `0d862f71eedcadc4494c4358261669721b40b2131101cbd6ef476c5a6ec6775b` (unpacked client, includes extra locales, rar compressed)
|
||||
|
||||
Known good *SHA1* checksum of the DLU client:
|
||||
- `91498e09b83ce69f46baf9e521d48f23fe502985` (packed client, zip compressed)
|
||||
- `91498e09b83ce69f46baf9e521d48f23fe502985` (packed client, zip compressed)
|
||||
|
||||
How to generate a SHA256 checksum:
|
||||
```bash
|
||||
@@ -196,23 +200,25 @@ certutil -hashfile <file> SHA256
|
||||
* Copy over or create symlinks from `locale.xml` in your client `locale` directory to the `build/locale` directory
|
||||
|
||||
#### Client database
|
||||
* Use `fdb_to_sqlite.py` in lcdr's utilities on `res/cdclient.fdb` in the unpacked client to convert the client database to `cdclient.sqlite`
|
||||
* Move and rename `cdclient.sqlite` into `build/res/CDServer.sqlite`
|
||||
* Run each SQL file in the order at which they appear [here](migrations/cdserver/) on the SQLite database
|
||||
* Move the file `res/cdclient.fdb` from the unpacked client to the `build/res` folder on the server.
|
||||
* The server will automatically copy and convert the file from fdb to sqlite should `CDServer.sqlite` not already exist.
|
||||
* You can also convert the database manually using `fdb_to_sqlite.py` using lcdr's utilities. Just make sure to rename the file to `CDServer.sqlite` instead of `cdclient.sqlite`.
|
||||
* Migrations to the database are automatically run on server start. When migrations are needed to be ran, the server may take a bit longer to start.
|
||||
|
||||
### Database
|
||||
Darkflame Universe utilizes a MySQL/MariaDB database for account and character information.
|
||||
|
||||
Initial setup can vary drastically based on which operating system or distribution you are running; there are instructions out there for most setups, follow those and come back here when you have a database up and running.
|
||||
* Create a database for Darkflame Universe to use
|
||||
|
||||
* All that you need to do is create a database to connect to. As long as the server can connect to the database, the schema will always be kept up to date when you start the server.
|
||||
|
||||
#### Configuration
|
||||
|
||||
After the server has been built there should be four `ini` files in the build director: `authconfig.ini`, `chatconfig.ini`, `masterconfig.ini`, and `worldconfig.ini`. Go through them and fill in the database credentials and configure other settings if necessary.
|
||||
After the server has been built there should be four `ini` files in the build director: `sharedconfig.ini`, `authconfig.ini`, `chatconfig.ini`, `masterconfig.ini`, and `worldconfig.ini`. Go through them and fill in the database credentials and configure other settings if necessary.
|
||||
|
||||
#### Setup and Migrations
|
||||
#### Migrations
|
||||
|
||||
Use the command `./MasterServer -m` to setup the tables in the database. The first time this command is run on a database, the tables will be up to date with the most recent version. To update your database tables, run this command again. Multiple invocations will not affect any functionality.
|
||||
The database is automatically setup and migrated to what it should look like for the latest commit whenever you start the server.
|
||||
|
||||
#### Verify
|
||||
|
||||
@@ -228,7 +234,7 @@ Your build directory should now look like this:
|
||||
* **locale/**
|
||||
* locale.xml
|
||||
* **res/**
|
||||
* CDServer.sqlite
|
||||
* cdclient.fdb
|
||||
* chatplus_en_us.txt
|
||||
* **macros/**
|
||||
* ...
|
||||
@@ -267,16 +273,6 @@ To connect to a server follow these steps:
|
||||
* Launch `legouniverse.exe`, through `wine` if on a Unix-like operating system
|
||||
* Note that if you are on WSL2, you will need to configure the public IP in the server and client to be the IP of the WSL2 instance and not localhost, which can be found by running `ifconfig` in the terminal. Windows defaults to WSL1, so this will not apply to most users.
|
||||
|
||||
### Survival
|
||||
|
||||
The client script for the survival minigame has a bug in it which can cause the minigame to not load. To fix this, follow these instructions:
|
||||
* Open `res/scripts/ai/minigame/survival/l_zone_survival_client.lua`
|
||||
* Navigate to line `617`
|
||||
* Change `PlayerReady(self)` to `onPlayerReady(self)`
|
||||
* Save the file, overriding readonly mode if required
|
||||
|
||||
If you still experience the bug, try deleting/renaming `res/pack/scripts.pk`.
|
||||
|
||||
### Brick-By-Brick building
|
||||
|
||||
Brick-By-Brick building requires `PATCHSERVERIP=0:` in the `boot.cfg` to point to a HTTP server which always returns `HTTP 404 - Not Found` for all requests. This can be achieved by pointing it to `localhost` while having `sudo python -m http.server 80` running in the background.
|
||||
@@ -334,7 +330,7 @@ Here is a summary of the commands available in-game. All commands are prefixed b
|
||||
/instanceinfo
|
||||
</td>
|
||||
<td>
|
||||
Displays in the chat the current zone, clone, and instance id.
|
||||
Displays in the chat the current zone, clone, and instance id.
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
|
||||
5
build.sh
5
build.sh
@@ -1,3 +1,6 @@
|
||||
# Error if any command fails
|
||||
set -e
|
||||
|
||||
# Create the build directory, preserving it if it already exists
|
||||
mkdir -p build
|
||||
cd build
|
||||
@@ -8,5 +11,3 @@ 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
|
||||
|
||||
# Run migrations
|
||||
./MasterServer -m
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "Database.h"
|
||||
#include "dConfig.h"
|
||||
#include "Diagnostics.h"
|
||||
#include "BinaryPathFinder.h"
|
||||
|
||||
//RakNet includes:
|
||||
#include "RakNetDefines.h"
|
||||
@@ -21,9 +22,9 @@
|
||||
|
||||
#include "Game.h"
|
||||
namespace Game {
|
||||
dLogger* logger;
|
||||
dServer* server;
|
||||
dConfig* config;
|
||||
dLogger* logger = nullptr;
|
||||
dServer* server = nullptr;
|
||||
dConfig* config = nullptr;
|
||||
}
|
||||
|
||||
dLogger* SetupLogger();
|
||||
@@ -36,22 +37,22 @@ int main(int argc, char** argv) {
|
||||
|
||||
//Create all the objects we need to run our service:
|
||||
Game::logger = SetupLogger();
|
||||
if (!Game::logger) return 0;
|
||||
if (!Game::logger) return EXIT_FAILURE;
|
||||
|
||||
//Read our config:
|
||||
Game::config = new dConfig((BinaryPathFinder::GetBinaryDir() / "authconfig.ini").string());
|
||||
Game::logger->SetLogToConsole(Game::config->GetValue("log_to_console") != "0");
|
||||
Game::logger->SetLogDebugStatements(Game::config->GetValue("log_debug_statements") == "1");
|
||||
|
||||
Game::logger->Log("AuthServer", "Starting Auth server...");
|
||||
Game::logger->Log("AuthServer", "Version: %i.%i", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR);
|
||||
Game::logger->Log("AuthServer", "Compiled on: %s", __TIMESTAMP__);
|
||||
|
||||
//Read our config:
|
||||
dConfig config("authconfig.ini");
|
||||
Game::config = &config;
|
||||
Game::logger->SetLogToConsole(bool(std::stoi(config.GetValue("log_to_console"))));
|
||||
Game::logger->SetLogDebugStatements(config.GetValue("log_debug_statements") == "1");
|
||||
|
||||
//Connect to the MySQL Database
|
||||
std::string mysql_host = config.GetValue("mysql_host");
|
||||
std::string mysql_database = config.GetValue("mysql_database");
|
||||
std::string mysql_username = config.GetValue("mysql_username");
|
||||
std::string mysql_password = config.GetValue("mysql_password");
|
||||
std::string mysql_host = Game::config->GetValue("mysql_host");
|
||||
std::string mysql_database = Game::config->GetValue("mysql_database");
|
||||
std::string mysql_username = Game::config->GetValue("mysql_username");
|
||||
std::string mysql_password = Game::config->GetValue("mysql_password");
|
||||
|
||||
try {
|
||||
Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password);
|
||||
@@ -60,7 +61,7 @@ int main(int argc, char** argv) {
|
||||
Database::Destroy("AuthServer");
|
||||
delete Game::server;
|
||||
delete Game::logger;
|
||||
return 0;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
//Find out the master's IP:
|
||||
@@ -79,10 +80,10 @@ int main(int argc, char** argv) {
|
||||
//It's safe to pass 'localhost' here, as the IP is only used as the external IP.
|
||||
int maxClients = 50;
|
||||
int ourPort = 1001; //LU client is hardcoded to use this for auth port, so I'm making it the default.
|
||||
if (config.GetValue("max_clients") != "") maxClients = std::stoi(config.GetValue("max_clients"));
|
||||
if (config.GetValue("port") != "") ourPort = std::atoi(config.GetValue("port").c_str());
|
||||
if (Game::config->GetValue("max_clients") != "") maxClients = std::stoi(Game::config->GetValue("max_clients"));
|
||||
if (Game::config->GetValue("port") != "") ourPort = std::atoi(Game::config->GetValue("port").c_str());
|
||||
|
||||
Game::server = new dServer(config.GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth);
|
||||
Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth, Game::config);
|
||||
|
||||
//Run it until server gets a kill message from Master:
|
||||
auto t = std::chrono::high_resolution_clock::now();
|
||||
@@ -144,13 +145,13 @@ int main(int argc, char** argv) {
|
||||
Database::Destroy("AuthServer");
|
||||
delete Game::server;
|
||||
delete Game::logger;
|
||||
delete Game::config;
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
dLogger* SetupLogger() {
|
||||
std::string logPath = "./logs/AuthServer_" + std::to_string(time(nullptr)) + ".log";
|
||||
std::string logPath = (BinaryPathFinder::GetBinaryDir() / ("logs/AuthServer_" + std::to_string(time(nullptr)) + ".log")).string();
|
||||
bool logToConsole = false;
|
||||
bool logDebugStatements = false;
|
||||
#ifdef _DEBUG
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
set(DAUTHSERVER_SOURCES "AuthServer.cpp")
|
||||
|
||||
add_executable(AuthServer ${DAUTHSERVER_SOURCES})
|
||||
add_executable(AuthServer "AuthServer.cpp")
|
||||
target_link_libraries(AuthServer ${COMMON_LIBRARIES})
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
set(DCHATSERVER_SOURCES "ChatPacketHandler.cpp"
|
||||
"ChatServer.cpp"
|
||||
"PlayerContainer.cpp")
|
||||
|
||||
add_executable(ChatServer ${DCHATSERVER_SOURCES})
|
||||
target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter)
|
||||
set(DCHATSERVER_SOURCES
|
||||
"ChatPacketHandler.cpp"
|
||||
"PlayerContainer.cpp"
|
||||
)
|
||||
|
||||
add_executable(ChatServer "ChatServer.cpp")
|
||||
add_library(dChatServer ${DCHATSERVER_SOURCES})
|
||||
|
||||
target_link_libraries(dChatServer ${COMMON_LIBRARIES} dChatFilter)
|
||||
target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer)
|
||||
|
||||
@@ -33,9 +33,10 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
|
||||
"WHEN friend_id = ? THEN player_id "
|
||||
"END AS requested_player, best_friend FROM friends) AS fr "
|
||||
"JOIN charinfo AS ci ON ci.id = fr.requested_player "
|
||||
"WHERE fr.requested_player IS NOT NULL;"));
|
||||
"WHERE fr.requested_player IS NOT NULL AND fr.requested_player != ?;"));
|
||||
stmt->setUInt(1, static_cast<uint32_t>(playerID));
|
||||
stmt->setUInt(2, static_cast<uint32_t>(playerID));
|
||||
stmt->setUInt(3, static_cast<uint32_t>(playerID));
|
||||
|
||||
std::vector<FriendData> friends;
|
||||
|
||||
@@ -113,6 +114,10 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
|
||||
inStream.Read(isBestFriendRequest);
|
||||
|
||||
auto requestor = playerContainer.GetPlayerData(requestorPlayerID);
|
||||
if (requestor->playerName == playerName) {
|
||||
SendFriendResponse(requestor, requestor, AddFriendResponseType::MYTHRAN);
|
||||
return;
|
||||
};
|
||||
std::unique_ptr<PlayerData> requestee(playerContainer.GetPlayerData(playerName));
|
||||
|
||||
// Check if player is online first
|
||||
@@ -389,7 +394,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
|
||||
uint8_t channel = 0;
|
||||
inStream.Read(channel);
|
||||
|
||||
std::string message = PacketUtils::ReadString(0x66, packet, true);
|
||||
std::string message = PacketUtils::ReadString(0x66, packet, true, 512);
|
||||
|
||||
Game::logger->Log("ChatPacketHandler", "Got a message from (%s) [%d]: %s", senderName.c_str(), channel, message.c_str());
|
||||
|
||||
@@ -431,7 +436,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
|
||||
void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
|
||||
LWOOBJID senderID = PacketUtils::ReadPacketS64(0x08, packet);
|
||||
std::string receiverName = PacketUtils::ReadString(0x66, packet, true);
|
||||
std::string message = PacketUtils::ReadString(0xAA, packet, true);
|
||||
std::string message = PacketUtils::ReadString(0xAA, packet, true, 512);
|
||||
|
||||
//Get the bois:
|
||||
auto goonA = playerContainer.GetPlayerData(senderID);
|
||||
|
||||
@@ -12,16 +12,19 @@
|
||||
#include "dMessageIdentifiers.h"
|
||||
#include "dChatFilter.h"
|
||||
#include "Diagnostics.h"
|
||||
#include "AssetManager.h"
|
||||
#include "BinaryPathFinder.h"
|
||||
|
||||
#include "PlayerContainer.h"
|
||||
#include "ChatPacketHandler.h"
|
||||
|
||||
#include "Game.h"
|
||||
namespace Game {
|
||||
dLogger* logger;
|
||||
dServer* server;
|
||||
dConfig* config;
|
||||
dChatFilter* chatFilter;
|
||||
dLogger* logger = nullptr;
|
||||
dServer* server = nullptr;
|
||||
dConfig* config = nullptr;
|
||||
dChatFilter* chatFilter = nullptr;
|
||||
AssetManager* assetManager = nullptr;
|
||||
}
|
||||
|
||||
//RakNet includes:
|
||||
@@ -39,22 +42,37 @@ int main(int argc, char** argv) {
|
||||
|
||||
//Create all the objects we need to run our service:
|
||||
Game::logger = SetupLogger();
|
||||
if (!Game::logger) return 0;
|
||||
if (!Game::logger) return EXIT_FAILURE;
|
||||
|
||||
//Read our config:
|
||||
Game::config = new dConfig((BinaryPathFinder::GetBinaryDir() / "chatconfig.ini").string());
|
||||
Game::logger->SetLogToConsole(Game::config->GetValue("log_to_console") != "0");
|
||||
Game::logger->SetLogDebugStatements(Game::config->GetValue("log_debug_statements") == "1");
|
||||
|
||||
Game::logger->Log("ChatServer", "Starting Chat server...");
|
||||
Game::logger->Log("ChatServer", "Version: %i.%i", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR);
|
||||
Game::logger->Log("ChatServer", "Compiled on: %s", __TIMESTAMP__);
|
||||
|
||||
//Read our config:
|
||||
dConfig config("chatconfig.ini");
|
||||
Game::config = &config;
|
||||
Game::logger->SetLogToConsole(bool(std::stoi(config.GetValue("log_to_console"))));
|
||||
Game::logger->SetLogDebugStatements(config.GetValue("log_debug_statements") == "1");
|
||||
try {
|
||||
std::string clientPathStr = Game::config->GetValue("client_location");
|
||||
if (clientPathStr.empty()) clientPathStr = "./res";
|
||||
std::filesystem::path clientPath = std::filesystem::path(clientPathStr);
|
||||
if (clientPath.is_relative()) {
|
||||
clientPath = BinaryPathFinder::GetBinaryDir() / clientPath;
|
||||
}
|
||||
|
||||
Game::assetManager = new AssetManager(clientPath);
|
||||
} catch (std::runtime_error& ex) {
|
||||
Game::logger->Log("ChatServer", "Got an error while setting up assets: %s", ex.what());
|
||||
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
//Connect to the MySQL Database
|
||||
std::string mysql_host = config.GetValue("mysql_host");
|
||||
std::string mysql_database = config.GetValue("mysql_database");
|
||||
std::string mysql_username = config.GetValue("mysql_username");
|
||||
std::string mysql_password = config.GetValue("mysql_password");
|
||||
std::string mysql_host = Game::config->GetValue("mysql_host");
|
||||
std::string mysql_database = Game::config->GetValue("mysql_database");
|
||||
std::string mysql_username = Game::config->GetValue("mysql_username");
|
||||
std::string mysql_password = Game::config->GetValue("mysql_password");
|
||||
|
||||
try {
|
||||
Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password);
|
||||
@@ -63,7 +81,7 @@ int main(int argc, char** argv) {
|
||||
Database::Destroy("ChatServer");
|
||||
delete Game::server;
|
||||
delete Game::logger;
|
||||
return 0;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
//Find out the master's IP:
|
||||
@@ -82,12 +100,12 @@ int main(int argc, char** argv) {
|
||||
//It's safe to pass 'localhost' here, as the IP is only used as the external IP.
|
||||
int maxClients = 50;
|
||||
int ourPort = 1501;
|
||||
if (config.GetValue("max_clients") != "") maxClients = std::stoi(config.GetValue("max_clients"));
|
||||
if (config.GetValue("port") != "") ourPort = std::atoi(config.GetValue("port").c_str());
|
||||
if (Game::config->GetValue("max_clients") != "") maxClients = std::stoi(Game::config->GetValue("max_clients"));
|
||||
if (Game::config->GetValue("port") != "") ourPort = std::atoi(Game::config->GetValue("port").c_str());
|
||||
|
||||
Game::server = new dServer(config.GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat);
|
||||
Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config);
|
||||
|
||||
Game::chatFilter = new dChatFilter("./res/chatplus_en_us", bool(std::stoi(config.GetValue("dont_generate_dcf"))));
|
||||
Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", bool(std::stoi(Game::config->GetValue("dont_generate_dcf"))));
|
||||
|
||||
//Run it until server gets a kill message from Master:
|
||||
auto t = std::chrono::high_resolution_clock::now();
|
||||
@@ -149,13 +167,13 @@ int main(int argc, char** argv) {
|
||||
Database::Destroy("ChatServer");
|
||||
delete Game::server;
|
||||
delete Game::logger;
|
||||
delete Game::config;
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
dLogger* SetupLogger() {
|
||||
std::string logPath = "./logs/ChatServer_" + std::to_string(time(nullptr)) + ".log";
|
||||
std::string logPath = (BinaryPathFinder::GetBinaryDir() / ("logs/ChatServer_" + std::to_string(time(nullptr)) + ".log")).string();
|
||||
bool logToConsole = false;
|
||||
bool logDebugStatements = false;
|
||||
#ifdef _DEBUG
|
||||
|
||||
@@ -58,16 +58,6 @@ void AMFArrayValue::RemoveValue(const std::string& key) {
|
||||
}
|
||||
}
|
||||
|
||||
// AMFArray Find Value
|
||||
AMFValue* AMFArrayValue::FindValue(const std::string& key) {
|
||||
_AMFArrayMap_::iterator it = this->associative.find(key);
|
||||
if (it != this->associative.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// AMFArray Get Associative Iterator Begin
|
||||
_AMFArrayMap_::iterator AMFArrayValue::GetAssociativeIteratorValueBegin() {
|
||||
return this->associative.begin();
|
||||
@@ -93,11 +83,6 @@ uint32_t AMFArrayValue::GetDenseValueSize() {
|
||||
return (uint32_t)this->dense.size();
|
||||
}
|
||||
|
||||
// AMFArray Get value at index in Dense List
|
||||
AMFValue* AMFArrayValue::GetValueAt(uint32_t index) {
|
||||
return this->dense.at(index);
|
||||
}
|
||||
|
||||
// AMFArray Get Dense Iterator Begin
|
||||
_AMFArrayList_::iterator AMFArrayValue::GetDenseIteratorBegin() {
|
||||
return this->dense.begin();
|
||||
|
||||
@@ -75,7 +75,9 @@ private:
|
||||
/*!
|
||||
\return The AMF value type
|
||||
*/
|
||||
AMFValueType GetValueType() { return AMFUndefined; }
|
||||
AMFValueType GetValueType() { return ValueType; }
|
||||
public:
|
||||
static const AMFValueType ValueType = AMFUndefined;
|
||||
};
|
||||
|
||||
//! The null value AMF type
|
||||
@@ -85,7 +87,9 @@ private:
|
||||
/*!
|
||||
\return The AMF value type
|
||||
*/
|
||||
AMFValueType GetValueType() { return AMFNull; }
|
||||
AMFValueType GetValueType() { return ValueType; }
|
||||
public:
|
||||
static const AMFValueType ValueType = AMFNull;
|
||||
};
|
||||
|
||||
//! The false value AMF type
|
||||
@@ -95,7 +99,9 @@ private:
|
||||
/*!
|
||||
\return The AMF value type
|
||||
*/
|
||||
AMFValueType GetValueType() { return AMFFalse; }
|
||||
AMFValueType GetValueType() { return ValueType; }
|
||||
public:
|
||||
static const AMFValueType ValueType = AMFFalse;
|
||||
};
|
||||
|
||||
//! The true value AMF type
|
||||
@@ -105,7 +111,9 @@ private:
|
||||
/*!
|
||||
\return The AMF value type
|
||||
*/
|
||||
AMFValueType GetValueType() { return AMFTrue; }
|
||||
AMFValueType GetValueType() { return ValueType; }
|
||||
public:
|
||||
static const AMFValueType ValueType = AMFTrue;
|
||||
};
|
||||
|
||||
//! The integer value AMF type
|
||||
@@ -117,9 +125,10 @@ private:
|
||||
/*!
|
||||
\return The AMF value type
|
||||
*/
|
||||
AMFValueType GetValueType() { return AMFInteger; }
|
||||
AMFValueType GetValueType() { return ValueType; }
|
||||
|
||||
public:
|
||||
static const AMFValueType ValueType = AMFInteger;
|
||||
//! Sets the integer value
|
||||
/*!
|
||||
\param value The value to set
|
||||
@@ -142,9 +151,10 @@ private:
|
||||
/*!
|
||||
\return The AMF value type
|
||||
*/
|
||||
AMFValueType GetValueType() { return AMFDouble; }
|
||||
AMFValueType GetValueType() { return ValueType; }
|
||||
|
||||
public:
|
||||
static const AMFValueType ValueType = AMFDouble;
|
||||
//! Sets the double value
|
||||
/*!
|
||||
\param value The value to set to
|
||||
@@ -167,9 +177,10 @@ private:
|
||||
/*!
|
||||
\return The AMF value type
|
||||
*/
|
||||
AMFValueType GetValueType() { return AMFString; }
|
||||
AMFValueType GetValueType() { return ValueType; }
|
||||
|
||||
public:
|
||||
static const AMFValueType ValueType = AMFString;
|
||||
//! Sets the string value
|
||||
/*!
|
||||
\param value The string value to set to
|
||||
@@ -192,9 +203,10 @@ private:
|
||||
/*!
|
||||
\return The AMF value type
|
||||
*/
|
||||
AMFValueType GetValueType() { return AMFXMLDoc; }
|
||||
AMFValueType GetValueType() { return ValueType; }
|
||||
|
||||
public:
|
||||
static const AMFValueType ValueType = AMFXMLDoc;
|
||||
//! Sets the XML Doc value
|
||||
/*!
|
||||
\param value The value to set to
|
||||
@@ -217,9 +229,10 @@ private:
|
||||
/*!
|
||||
\return The AMF value type
|
||||
*/
|
||||
AMFValueType GetValueType() { return AMFDate; }
|
||||
AMFValueType GetValueType() { return ValueType; }
|
||||
|
||||
public:
|
||||
static const AMFValueType ValueType = AMFDate;
|
||||
//! Sets the date time
|
||||
/*!
|
||||
\param value The value to set to
|
||||
@@ -244,9 +257,11 @@ private:
|
||||
/*!
|
||||
\return The AMF value type
|
||||
*/
|
||||
AMFValueType GetValueType() { return AMFArray; }
|
||||
AMFValueType GetValueType() { return ValueType; }
|
||||
|
||||
public:
|
||||
static const AMFValueType ValueType = AMFArray;
|
||||
|
||||
~AMFArrayValue() override;
|
||||
//! Inserts an item into the array map for a specific key
|
||||
/*!
|
||||
@@ -265,7 +280,15 @@ public:
|
||||
/*!
|
||||
\return The AMF value if found, nullptr otherwise
|
||||
*/
|
||||
AMFValue* FindValue(const std::string& key);
|
||||
template <typename T>
|
||||
T* FindValue(const std::string& key) const {
|
||||
_AMFArrayMap_::const_iterator it = this->associative.find(key);
|
||||
if (it != this->associative.end() && T::ValueType == it->second->GetValueType()) {
|
||||
return dynamic_cast<T*>(it->second);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
//! Returns where the associative iterator begins
|
||||
/*!
|
||||
@@ -298,7 +321,12 @@ public:
|
||||
/*!
|
||||
\param index The index to get
|
||||
*/
|
||||
AMFValue* GetValueAt(uint32_t index);
|
||||
template <typename T>
|
||||
T* GetValueAt(uint32_t index) {
|
||||
if (index >= this->dense.size()) return nullptr;
|
||||
AMFValue* foundValue = this->dense.at(index);
|
||||
return T::ValueType == foundValue->GetValueType() ? dynamic_cast<T*>(foundValue) : nullptr;
|
||||
};
|
||||
|
||||
//! Returns where the dense iterator begins
|
||||
/*!
|
||||
@@ -334,10 +362,11 @@ private:
|
||||
/*!
|
||||
\return The AMF value type
|
||||
*/
|
||||
AMFValueType GetValueType() { return AMFObject; }
|
||||
AMFValueType GetValueType() { return ValueType; }
|
||||
~AMFObjectValue() override;
|
||||
|
||||
public:
|
||||
static const AMFValueType ValueType = AMFObject;
|
||||
//! Constructor
|
||||
/*!
|
||||
\param traits The traits to set
|
||||
|
||||
@@ -10,7 +10,7 @@ void BinaryIO::WriteString(const std::string& stringToWrite, std::ofstream& outs
|
||||
}
|
||||
|
||||
//For reading null-terminated strings
|
||||
std::string BinaryIO::ReadString(std::ifstream& instream) {
|
||||
std::string BinaryIO::ReadString(std::istream& instream) {
|
||||
std::string toReturn;
|
||||
char buffer;
|
||||
|
||||
@@ -25,7 +25,7 @@ std::string BinaryIO::ReadString(std::ifstream& instream) {
|
||||
}
|
||||
|
||||
//For reading strings of a specific size
|
||||
std::string BinaryIO::ReadString(std::ifstream& instream, size_t size) {
|
||||
std::string BinaryIO::ReadString(std::istream& instream, size_t size) {
|
||||
std::string toReturn;
|
||||
char buffer;
|
||||
|
||||
@@ -37,7 +37,7 @@ std::string BinaryIO::ReadString(std::ifstream& instream, size_t size) {
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
std::string BinaryIO::ReadWString(std::ifstream& instream) {
|
||||
std::string BinaryIO::ReadWString(std::istream& instream) {
|
||||
size_t size;
|
||||
BinaryRead(instream, size);
|
||||
//toReturn.resize(size);
|
||||
|
||||
@@ -10,16 +10,15 @@ namespace BinaryIO {
|
||||
|
||||
template<typename T>
|
||||
std::istream& BinaryRead(std::istream& stream, T& value) {
|
||||
if (!stream.good())
|
||||
printf("bla");
|
||||
if (!stream.good()) throw std::runtime_error("Failed to read from istream.");
|
||||
|
||||
return stream.read(reinterpret_cast<char*>(&value), sizeof(T));
|
||||
}
|
||||
|
||||
void WriteString(const std::string& stringToWrite, std::ofstream& outstream);
|
||||
std::string ReadString(std::ifstream& instream);
|
||||
std::string ReadString(std::ifstream& instream, size_t size);
|
||||
std::string ReadWString(std::ifstream& instream);
|
||||
std::string ReadString(std::istream& instream);
|
||||
std::string ReadString(std::istream& instream, size_t size);
|
||||
std::string ReadWString(std::istream& instream);
|
||||
|
||||
inline bool DoesFileExist(const std::string& name) {
|
||||
std::ifstream f(name.c_str());
|
||||
|
||||
71
dCommon/BinaryPathFinder.cpp
Normal file
71
dCommon/BinaryPathFinder.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include "BinaryPathFinder.h"
|
||||
#include "dPlatforms.h"
|
||||
|
||||
#if defined(DARKFLAME_PLATFORM_WIN32)
|
||||
#include <Windows.h>
|
||||
#elif defined(DARKFLAME_PLATFORM_MACOS) || defined(DARKFLAME_PLATFORM_IOS)
|
||||
#include <mach-o/dyld.h>
|
||||
#elif defined(DARKFLAME_PLATFORM_FREEBSD)
|
||||
#include <sys/types.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
std::filesystem::path BinaryPathFinder::binaryDir;
|
||||
|
||||
std::filesystem::path BinaryPathFinder::GetBinaryDir() {
|
||||
if (!binaryDir.empty()) {
|
||||
return binaryDir;
|
||||
}
|
||||
|
||||
std::string pathStr;
|
||||
|
||||
// Derived from boost::dll::program_location, licensed under the Boost Software License: http://www.boost.org/LICENSE_1_0.txt
|
||||
#if defined(DARKFLAME_PLATFORM_WIN32)
|
||||
char path[MAX_PATH];
|
||||
GetModuleFileName(NULL, path, MAX_PATH);
|
||||
pathStr = std::string(path);
|
||||
#elif defined(DARKFLAME_PLATFORM_MACOS) || defined(DARKFLAME_PLATFORM_IOS)
|
||||
char path[1024];
|
||||
uint32_t size = sizeof(path);
|
||||
if (_NSGetExecutablePath(path, &size) == 0) {
|
||||
pathStr = std::string(path);
|
||||
} else {
|
||||
// The filepath size is greater than our initial buffer size, so try again with the size
|
||||
// that _NSGetExecutablePath told us it actually is
|
||||
char *p = new char[size];
|
||||
if (_NSGetExecutablePath(p, &size) != 0) {
|
||||
throw std::runtime_error("Failed to get binary path from _NSGetExecutablePath");
|
||||
}
|
||||
|
||||
pathStr = std::string(p);
|
||||
delete[] p;
|
||||
}
|
||||
#elif defined(DARKFLAME_PLATFORM_FREEBSD)
|
||||
int mib[4];
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[2] = KERN_PROC_PATHNAME;
|
||||
mib[3] = -1;
|
||||
char buf[10240];
|
||||
size_t cb = sizeof(buf);
|
||||
sysctl(mib, 4, buf, &cb, NULL, 0);
|
||||
pathStr = std::string(buf);
|
||||
#else // DARKFLAME_PLATFORM_LINUX || DARKFLAME_PLATFORM_UNIX || DARKFLAME_PLATFORM_ANDROID
|
||||
pathStr = std::filesystem::read_symlink("/proc/self/exe");
|
||||
#endif
|
||||
|
||||
// Some methods like _NSGetExecutablePath could return a symlink
|
||||
// Either way, we need to get the parent path because we want the directory, not the binary itself
|
||||
// We also ensure that it is an absolute path so that it is valid if we need to construct a path
|
||||
// to exucute on unix systems (eg sudo BinaryPathFinder::GetBinaryDir() / WorldServer)
|
||||
if (std::filesystem::is_symlink(pathStr)) {
|
||||
binaryDir = std::filesystem::absolute(std::filesystem::read_symlink(pathStr).parent_path());
|
||||
} else {
|
||||
binaryDir = std::filesystem::absolute(std::filesystem::path(pathStr).parent_path());
|
||||
}
|
||||
|
||||
return binaryDir;
|
||||
}
|
||||
15
dCommon/BinaryPathFinder.h
Normal file
15
dCommon/BinaryPathFinder.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __BINARYPATHFINDER__H__
|
||||
#define __BINARYPATHFINDER__H__
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
class BinaryPathFinder {
|
||||
private:
|
||||
static std::filesystem::path binaryDir;
|
||||
public:
|
||||
static std::filesystem::path GetBinaryDir();
|
||||
};
|
||||
|
||||
#endif //!__BINARYPATHFINDER__H__
|
||||
180
dCommon/BrickByBrickFix.cpp
Normal file
180
dCommon/BrickByBrickFix.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
#include "BrickByBrickFix.h"
|
||||
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include "Database.h"
|
||||
#include "Game.h"
|
||||
#include "ZCompression.h"
|
||||
#include "dLogger.h"
|
||||
|
||||
//! Forward declarations
|
||||
|
||||
std::unique_ptr<sql::ResultSet> GetModelsFromDatabase();
|
||||
void WriteSd0Magic(char* input, uint32_t chunkSize);
|
||||
bool CheckSd0Magic(sql::Blob* streamToCheck);
|
||||
|
||||
/**
|
||||
* @brief Truncates all models with broken data from the database.
|
||||
*
|
||||
* @return The number of models deleted
|
||||
*/
|
||||
uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
|
||||
uint32_t modelsTruncated{};
|
||||
auto modelsToTruncate = GetModelsFromDatabase();
|
||||
bool previousCommitValue = Database::GetAutoCommit();
|
||||
Database::SetAutoCommit(false);
|
||||
while (modelsToTruncate->next()) {
|
||||
std::unique_ptr<sql::PreparedStatement> ugcModelToDelete(Database::CreatePreppedStmt("DELETE FROM ugc WHERE ugc.id = ?;"));
|
||||
std::unique_ptr<sql::PreparedStatement> pcModelToDelete(Database::CreatePreppedStmt("DELETE FROM properties_contents WHERE ugc_id = ?;"));
|
||||
std::string completeUncompressedModel{};
|
||||
uint32_t chunkCount{};
|
||||
uint64_t modelId = modelsToTruncate->getInt(1);
|
||||
std::unique_ptr<sql::Blob> modelAsSd0(modelsToTruncate->getBlob(2));
|
||||
// Check that header is sd0 by checking for the sd0 magic.
|
||||
if (CheckSd0Magic(modelAsSd0.get())) {
|
||||
while (true) {
|
||||
uint32_t chunkSize{};
|
||||
modelAsSd0->read(reinterpret_cast<char*>(&chunkSize), sizeof(uint32_t)); // Extract chunk size from istream
|
||||
|
||||
// Check if good here since if at the end of an sd0 file, this will have eof flagged.
|
||||
if (!modelAsSd0->good()) break;
|
||||
|
||||
std::unique_ptr<uint8_t[]> compressedChunk(new uint8_t[chunkSize]);
|
||||
for (uint32_t i = 0; i < chunkSize; i++) {
|
||||
compressedChunk[i] = modelAsSd0->get();
|
||||
}
|
||||
|
||||
// Ignore the valgrind warning about uninitialized values. These are discarded later when we know the actual uncompressed size.
|
||||
std::unique_ptr<uint8_t[]> uncompressedChunk(new uint8_t[ZCompression::MAX_SD0_CHUNK_SIZE]);
|
||||
int32_t err{};
|
||||
int32_t actualUncompressedSize = ZCompression::Decompress(
|
||||
compressedChunk.get(), chunkSize, uncompressedChunk.get(), ZCompression::MAX_SD0_CHUNK_SIZE, err);
|
||||
|
||||
if (actualUncompressedSize != -1) {
|
||||
uint32_t previousSize = completeUncompressedModel.size();
|
||||
completeUncompressedModel.append((char*)uncompressedChunk.get());
|
||||
completeUncompressedModel.resize(previousSize + actualUncompressedSize);
|
||||
} else {
|
||||
Game::logger->Log("BrickByBrickFix", "Failed to inflate chunk %i for model %llu. Error: %i", chunkCount, modelId, err);
|
||||
break;
|
||||
}
|
||||
chunkCount++;
|
||||
}
|
||||
std::unique_ptr<tinyxml2::XMLDocument> document = std::make_unique<tinyxml2::XMLDocument>();
|
||||
if (!document) {
|
||||
Game::logger->Log("BrickByBrickFix", "Failed to initialize tinyxml document. Aborting.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!(document->Parse(completeUncompressedModel.c_str(), completeUncompressedModel.size()) == tinyxml2::XML_SUCCESS)) {
|
||||
if (completeUncompressedModel.find(
|
||||
"</LXFML>",
|
||||
completeUncompressedModel.length() >= 15 ? completeUncompressedModel.length() - 15 : 0) == std::string::npos
|
||||
) {
|
||||
Game::logger->Log("BrickByBrickFix",
|
||||
"Brick-by-brick model %llu will be deleted!", modelId);
|
||||
ugcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1));
|
||||
pcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1));
|
||||
ugcModelToDelete->execute();
|
||||
pcModelToDelete->execute();
|
||||
modelsTruncated++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Game::logger->Log("BrickByBrickFix",
|
||||
"Brick-by-brick model %llu will be deleted!", modelId);
|
||||
ugcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1));
|
||||
pcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1));
|
||||
ugcModelToDelete->execute();
|
||||
pcModelToDelete->execute();
|
||||
modelsTruncated++;
|
||||
}
|
||||
}
|
||||
|
||||
Database::Commit();
|
||||
Database::SetAutoCommit(previousCommitValue);
|
||||
return modelsTruncated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates all current models in the database to have the Segmented Data 0 (SD0) format.
|
||||
* Any models that do not start with zlib and best compression magic will not be updated.
|
||||
*
|
||||
* @return The number of models updated to SD0
|
||||
*/
|
||||
uint32_t BrickByBrickFix::UpdateBrickByBrickModelsToSd0() {
|
||||
uint32_t updatedModels = 0;
|
||||
auto modelsToUpdate = GetModelsFromDatabase();
|
||||
auto previousAutoCommitState = Database::GetAutoCommit();
|
||||
Database::SetAutoCommit(false);
|
||||
std::unique_ptr<sql::PreparedStatement> insertionStatement(Database::CreatePreppedStmt("UPDATE ugc SET lxfml = ? WHERE id = ?;"));
|
||||
while (modelsToUpdate->next()) {
|
||||
int64_t modelId = modelsToUpdate->getInt64(1);
|
||||
std::unique_ptr<sql::Blob> oldLxfml(modelsToUpdate->getBlob(2));
|
||||
// Check if the stored blob starts with zlib magic (0x78 0xDA - best compression of zlib)
|
||||
// If it does, convert it to sd0.
|
||||
if (oldLxfml->get() == 0x78 && oldLxfml->get() == 0xDA) {
|
||||
|
||||
// Get and save size of zlib compressed chunk.
|
||||
oldLxfml->seekg(0, std::ios::end);
|
||||
uint32_t oldLxfmlSize = static_cast<uint32_t>(oldLxfml->tellg());
|
||||
oldLxfml->seekg(0);
|
||||
|
||||
// Allocate 9 extra bytes. 5 for sd0 magic, 4 for the only zlib compressed size.
|
||||
uint32_t oldLxfmlSizeWithHeader = oldLxfmlSize + 9;
|
||||
std::unique_ptr<char[]> sd0ConvertedModel(new char[oldLxfmlSizeWithHeader]);
|
||||
|
||||
WriteSd0Magic(sd0ConvertedModel.get(), oldLxfmlSize);
|
||||
for (uint32_t i = 9; i < oldLxfmlSizeWithHeader; i++) {
|
||||
sd0ConvertedModel.get()[i] = oldLxfml->get();
|
||||
}
|
||||
|
||||
std::string outputString(sd0ConvertedModel.get(), oldLxfmlSizeWithHeader);
|
||||
std::istringstream outputStringStream(outputString);
|
||||
|
||||
insertionStatement->setBlob(1, static_cast<std::istream*>(&outputStringStream));
|
||||
insertionStatement->setInt64(2, modelId);
|
||||
try {
|
||||
insertionStatement->executeUpdate();
|
||||
Game::logger->Log("BrickByBrickFix", "Updated model %i to sd0", modelId);
|
||||
updatedModels++;
|
||||
} catch (sql::SQLException exception) {
|
||||
Game::logger->Log(
|
||||
"BrickByBrickFix",
|
||||
"Failed to update model %i. This model should be inspected manually to see why."
|
||||
"The database error is %s", modelId, exception.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
Database::Commit();
|
||||
Database::SetAutoCommit(previousAutoCommitState);
|
||||
return updatedModels;
|
||||
}
|
||||
|
||||
std::unique_ptr<sql::ResultSet> GetModelsFromDatabase() {
|
||||
std::unique_ptr<sql::PreparedStatement> modelsRawDataQuery(Database::CreatePreppedStmt("SELECT id, lxfml FROM ugc;"));
|
||||
return std::unique_ptr<sql::ResultSet>(modelsRawDataQuery->executeQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes sd0 magic at the front of a char*
|
||||
*
|
||||
* @param input the char* to write at the front of
|
||||
* @param chunkSize The size of the first chunk to write the size of
|
||||
*/
|
||||
void WriteSd0Magic(char* input, uint32_t chunkSize) {
|
||||
input[0] = 's';
|
||||
input[1] = 'd';
|
||||
input[2] = '0';
|
||||
input[3] = 0x01;
|
||||
input[4] = 0xFF;
|
||||
*reinterpret_cast<uint32_t*>(input + 5) = chunkSize; // Write the integer to the character array
|
||||
}
|
||||
|
||||
bool CheckSd0Magic(sql::Blob* streamToCheck) {
|
||||
return streamToCheck->get() == 's' && streamToCheck->get() == 'd' && streamToCheck->get() == '0' && streamToCheck->get() == 0x01 && streamToCheck->get() == 0xFF;
|
||||
}
|
||||
20
dCommon/BrickByBrickFix.h
Normal file
20
dCommon/BrickByBrickFix.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace BrickByBrickFix {
|
||||
/**
|
||||
* @brief Deletes all broken BrickByBrick models that have invalid XML
|
||||
*
|
||||
* @return The number of BrickByBrick models that were truncated
|
||||
*/
|
||||
uint32_t TruncateBrokenBrickByBrickXml();
|
||||
|
||||
/**
|
||||
* @brief Updates all BrickByBrick models in the database to be
|
||||
* in the sd0 format as opposed to a zlib compressed format.
|
||||
*
|
||||
* @return The number of BrickByBrick models that were updated
|
||||
*/
|
||||
uint32_t UpdateBrickByBrickModelsToSd0();
|
||||
};
|
||||
@@ -13,15 +13,51 @@ set(DCOMMON_SOURCES "AMFFormat.cpp"
|
||||
"NiQuaternion.cpp"
|
||||
"SHA512.cpp"
|
||||
"Type.cpp"
|
||||
"ZCompression.cpp")
|
||||
"ZCompression.cpp"
|
||||
"BrickByBrickFix.cpp"
|
||||
"BinaryPathFinder.cpp"
|
||||
"FdbToSqlite.cpp"
|
||||
)
|
||||
|
||||
add_subdirectory(dClient)
|
||||
|
||||
foreach(file ${DCOMMON_DCLIENT_SOURCES})
|
||||
set(DCOMMON_SOURCES ${DCOMMON_SOURCES} "dClient/${file}")
|
||||
endforeach()
|
||||
|
||||
include_directories(${PROJECT_SOURCE_DIR}/dCommon/)
|
||||
|
||||
add_library(dCommon STATIC ${DCOMMON_SOURCES})
|
||||
|
||||
target_link_libraries(dCommon bcrypt)
|
||||
target_link_libraries(dCommon bcrypt dDatabase tinyxml2)
|
||||
|
||||
if (UNIX)
|
||||
find_package(ZLIB REQUIRED)
|
||||
target_link_libraries(dCommon ZLIB::ZLIB)
|
||||
endif()
|
||||
elseif (WIN32)
|
||||
include(FetchContent)
|
||||
|
||||
# TODO Keep an eye on the zlib repository for an update to disable testing. Don't forget to update CMakePresets
|
||||
FetchContent_Declare(
|
||||
zlib
|
||||
URL https://github.com/madler/zlib/archive/refs/tags/v1.2.11.zip
|
||||
URL_HASH MD5=9d6a627693163bbbf3f26403a3a0b0b1
|
||||
)
|
||||
|
||||
# Disable warning about no project version.
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0048 NEW)
|
||||
# Disable warning about the minimum version of cmake used for bcrypt being deprecated in the future
|
||||
set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "" FORCE)
|
||||
|
||||
FetchContent_MakeAvailable(zlib)
|
||||
|
||||
set(ZLIB_INCLUDE_DIRS ${zlib_SOURCE_DIR} ${zlib_BINARY_DIR})
|
||||
set_target_properties(zlib PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIRS}")
|
||||
add_library(ZLIB::ZLIB ALIAS zlib)
|
||||
else ()
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"This platform does not have a way to use zlib.\nCreate an issue on GitHub with your build system so it can be configured."
|
||||
)
|
||||
endif ()
|
||||
|
||||
target_link_libraries(dCommon ZLIB::ZLIB)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "Diagnostics.h"
|
||||
#include "Game.h"
|
||||
#include "dLogger.h"
|
||||
|
||||
// If we're on Win32, we'll include our minidump writer
|
||||
#ifdef _WIN32
|
||||
@@ -26,7 +28,7 @@ void make_minidump(EXCEPTION_POINTERS* e) {
|
||||
"_%4d%02d%02d_%02d%02d%02d.dmp",
|
||||
t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond);
|
||||
}
|
||||
|
||||
Game::logger->Log("Diagnostics", "Creating crash dump %s", name);
|
||||
auto hFile = CreateFileA(name, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
|
||||
if (hFile == INVALID_HANDLE_VALUE)
|
||||
return;
|
||||
@@ -81,6 +83,7 @@ struct bt_ctx {
|
||||
|
||||
static inline void Bt(struct backtrace_state* state) {
|
||||
std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log";
|
||||
Game::logger->Log("Diagnostics", "backtrace is enabled, crash dump located at %s", fileName.c_str());
|
||||
FILE* file = fopen(fileName.c_str(), "w+");
|
||||
if (file != nullptr) {
|
||||
backtrace_print(state, 2, file);
|
||||
@@ -114,6 +117,8 @@ void GenerateDump() {
|
||||
void CatchUnhandled(int sig) {
|
||||
#ifndef __include_backtrace__
|
||||
|
||||
std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log";
|
||||
Game::logger->Log("Diagnostics", "Encountered signal %i, creating crash dump %s", sig, fileName.c_str());
|
||||
if (Diagnostics::GetProduceMemoryDump()) {
|
||||
GenerateDump();
|
||||
}
|
||||
@@ -124,7 +129,6 @@ void CatchUnhandled(int sig) {
|
||||
// get void*'s for all entries on the stack
|
||||
size = backtrace(array, 10);
|
||||
|
||||
printf("Fatal error %i\nStacktrace:\n", sig);
|
||||
#if defined(__GNUG__) and defined(__dynamic)
|
||||
|
||||
// Loop through the returned addresses, and get the symbols to be demangled
|
||||
@@ -142,19 +146,18 @@ void CatchUnhandled(int sig) {
|
||||
demangled = demangle(functionName.c_str());
|
||||
|
||||
if (demangled.empty()) {
|
||||
printf("[%02zu] %s\n", i, demangled.c_str());
|
||||
Game::logger->Log("Diagnostics", "[%02zu] %s", i, demangled.c_str());
|
||||
} else {
|
||||
printf("[%02zu] %s\n", i, functionName.c_str());
|
||||
Game::logger->Log("Diagnostics", "[%02zu] %s", i, functionName.c_str());
|
||||
}
|
||||
} else {
|
||||
printf("[%02zu] %s\n", i, functionName.c_str());
|
||||
Game::logger->Log("Diagnostics", "[%02zu] %s", i, functionName.c_str());
|
||||
}
|
||||
}
|
||||
#else
|
||||
backtrace_symbols_fd(array, size, STDOUT_FILENO);
|
||||
#endif
|
||||
|
||||
std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log";
|
||||
FILE* file = fopen(fileName.c_str(), "w+");
|
||||
if (file != NULL) {
|
||||
// print out all the frames to stderr
|
||||
|
||||
248
dCommon/FdbToSqlite.cpp
Normal file
248
dCommon/FdbToSqlite.cpp
Normal file
@@ -0,0 +1,248 @@
|
||||
#include "FdbToSqlite.h"
|
||||
|
||||
#include <map>
|
||||
#include <fstream>
|
||||
#include <cassert>
|
||||
#include <iomanip>
|
||||
|
||||
#include "BinaryIO.h"
|
||||
#include "CDClientDatabase.h"
|
||||
#include "GeneralUtils.h"
|
||||
#include "Game.h"
|
||||
#include "dLogger.h"
|
||||
|
||||
#include "eSqliteDataType.h"
|
||||
|
||||
std::map<eSqliteDataType, std::string> FdbToSqlite::Convert::sqliteType = {
|
||||
{ eSqliteDataType::NONE, "none"},
|
||||
{ eSqliteDataType::INT32, "int32"},
|
||||
{ eSqliteDataType::REAL, "real"},
|
||||
{ eSqliteDataType::TEXT_4, "text_4"},
|
||||
{ eSqliteDataType::INT_BOOL, "int_bool"},
|
||||
{ eSqliteDataType::INT64, "int64"},
|
||||
{ eSqliteDataType::TEXT_8, "text_8"}
|
||||
};
|
||||
|
||||
FdbToSqlite::Convert::Convert(std::string basePath) {
|
||||
this->basePath = basePath;
|
||||
}
|
||||
|
||||
bool FdbToSqlite::Convert::ConvertDatabase() {
|
||||
fdb.open(basePath + "/cdclient.fdb", std::ios::binary);
|
||||
|
||||
try {
|
||||
CDClientDatabase::Connect(basePath + "/CDServer.sqlite");
|
||||
|
||||
CDClientDatabase::ExecuteQuery("BEGIN TRANSACTION;");
|
||||
|
||||
int32_t numberOfTables = ReadInt32();
|
||||
ReadTables(numberOfTables);
|
||||
|
||||
CDClientDatabase::ExecuteQuery("COMMIT;");
|
||||
} catch (CppSQLite3Exception& e) {
|
||||
Game::logger->Log("FdbToSqlite", "Encountered error %s converting FDB to SQLite", e.errorMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
fdb.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t FdbToSqlite::Convert::ReadInt32() {
|
||||
int32_t nextInt{};
|
||||
BinaryIO::BinaryRead(fdb, nextInt);
|
||||
return nextInt;
|
||||
}
|
||||
|
||||
int64_t FdbToSqlite::Convert::ReadInt64() {
|
||||
int32_t prevPosition = SeekPointer();
|
||||
|
||||
int64_t value{};
|
||||
BinaryIO::BinaryRead(fdb, value);
|
||||
|
||||
fdb.seekg(prevPosition);
|
||||
return value;
|
||||
}
|
||||
|
||||
std::string FdbToSqlite::Convert::ReadString() {
|
||||
int32_t prevPosition = SeekPointer();
|
||||
|
||||
auto readString = BinaryIO::ReadString(fdb);
|
||||
|
||||
fdb.seekg(prevPosition);
|
||||
return readString;
|
||||
}
|
||||
|
||||
int32_t FdbToSqlite::Convert::SeekPointer() {
|
||||
int32_t position{};
|
||||
BinaryIO::BinaryRead(fdb, position);
|
||||
int32_t prevPosition = fdb.tellg();
|
||||
fdb.seekg(position);
|
||||
return prevPosition;
|
||||
}
|
||||
|
||||
std::string FdbToSqlite::Convert::ReadColumnHeader() {
|
||||
int32_t prevPosition = SeekPointer();
|
||||
|
||||
int32_t numberOfColumns = ReadInt32();
|
||||
std::string tableName = ReadString();
|
||||
|
||||
auto columns = ReadColumns(numberOfColumns);
|
||||
std::string newTable = "CREATE TABLE IF NOT EXISTS '" + tableName + "' (" + columns + ");";
|
||||
CDClientDatabase::ExecuteDML(newTable);
|
||||
|
||||
fdb.seekg(prevPosition);
|
||||
|
||||
return tableName;
|
||||
}
|
||||
|
||||
void FdbToSqlite::Convert::ReadTables(int32_t& numberOfTables) {
|
||||
int32_t prevPosition = SeekPointer();
|
||||
|
||||
for (int32_t i = 0; i < numberOfTables; i++) {
|
||||
auto columnHeader = ReadColumnHeader();
|
||||
ReadRowHeader(columnHeader);
|
||||
}
|
||||
|
||||
fdb.seekg(prevPosition);
|
||||
}
|
||||
|
||||
std::string FdbToSqlite::Convert::ReadColumns(int32_t& numberOfColumns) {
|
||||
std::stringstream columnsToCreate;
|
||||
int32_t prevPosition = SeekPointer();
|
||||
|
||||
std::string name{};
|
||||
eSqliteDataType dataType{};
|
||||
for (int32_t i = 0; i < numberOfColumns; i++) {
|
||||
if (i != 0) columnsToCreate << ", ";
|
||||
dataType = static_cast<eSqliteDataType>(ReadInt32());
|
||||
name = ReadString();
|
||||
columnsToCreate << "'" << name << "' " << FdbToSqlite::Convert::sqliteType[dataType];
|
||||
}
|
||||
|
||||
fdb.seekg(prevPosition);
|
||||
return columnsToCreate.str();
|
||||
}
|
||||
|
||||
void FdbToSqlite::Convert::ReadRowHeader(std::string& tableName) {
|
||||
int32_t prevPosition = SeekPointer();
|
||||
|
||||
int32_t numberOfAllocatedRows = ReadInt32();
|
||||
if (numberOfAllocatedRows != 0) assert((numberOfAllocatedRows & (numberOfAllocatedRows - 1)) == 0); // assert power of 2 allocation size
|
||||
ReadRows(numberOfAllocatedRows, tableName);
|
||||
|
||||
fdb.seekg(prevPosition);
|
||||
}
|
||||
|
||||
void FdbToSqlite::Convert::ReadRows(int32_t& numberOfAllocatedRows, std::string& tableName) {
|
||||
int32_t prevPosition = SeekPointer();
|
||||
|
||||
int32_t rowid = 0;
|
||||
for (int32_t row = 0; row < numberOfAllocatedRows; row++) {
|
||||
int32_t rowPointer = ReadInt32();
|
||||
if (rowPointer == -1) rowid++;
|
||||
else ReadRow(rowid, rowPointer, tableName);
|
||||
}
|
||||
|
||||
fdb.seekg(prevPosition);
|
||||
}
|
||||
|
||||
void FdbToSqlite::Convert::ReadRow(int32_t& rowid, int32_t& position, std::string& tableName) {
|
||||
int32_t prevPosition = fdb.tellg();
|
||||
fdb.seekg(position);
|
||||
|
||||
while (true) {
|
||||
ReadRowInfo(tableName);
|
||||
int32_t linked = ReadInt32();
|
||||
|
||||
rowid += 1;
|
||||
|
||||
if (linked == -1) break;
|
||||
|
||||
fdb.seekg(linked);
|
||||
}
|
||||
|
||||
fdb.seekg(prevPosition);
|
||||
}
|
||||
|
||||
void FdbToSqlite::Convert::ReadRowInfo(std::string& tableName) {
|
||||
int32_t prevPosition = SeekPointer();
|
||||
|
||||
int32_t numberOfColumns = ReadInt32();
|
||||
ReadRowValues(numberOfColumns, tableName);
|
||||
|
||||
fdb.seekg(prevPosition);
|
||||
}
|
||||
|
||||
void FdbToSqlite::Convert::ReadRowValues(int32_t& numberOfColumns, std::string& tableName) {
|
||||
int32_t prevPosition = SeekPointer();
|
||||
|
||||
int32_t emptyValue{};
|
||||
int32_t intValue{};
|
||||
float_t floatValue{};
|
||||
std::string stringValue{};
|
||||
int32_t boolValue{};
|
||||
int64_t int64Value{};
|
||||
bool insertedFirstEntry = false;
|
||||
std::stringstream insertedRow;
|
||||
insertedRow << "INSERT INTO " << tableName << " values (";
|
||||
|
||||
for (int32_t i = 0; i < numberOfColumns; i++) {
|
||||
if (i != 0) insertedRow << ", "; // Only append comma and space after first entry in row.
|
||||
switch (static_cast<eSqliteDataType>(ReadInt32())) {
|
||||
case eSqliteDataType::NONE:
|
||||
BinaryIO::BinaryRead(fdb, emptyValue);
|
||||
assert(emptyValue == 0);
|
||||
insertedRow << "NULL";
|
||||
break;
|
||||
|
||||
case eSqliteDataType::INT32:
|
||||
intValue = ReadInt32();
|
||||
insertedRow << intValue;
|
||||
break;
|
||||
|
||||
case eSqliteDataType::REAL:
|
||||
BinaryIO::BinaryRead(fdb, floatValue);
|
||||
insertedRow << std::fixed << std::setprecision(34) << floatValue; // maximum precision of floating point number
|
||||
break;
|
||||
|
||||
case eSqliteDataType::TEXT_4:
|
||||
case eSqliteDataType::TEXT_8: {
|
||||
stringValue = ReadString();
|
||||
size_t position = 0;
|
||||
|
||||
// Need to escape quote with a double of ".
|
||||
while (position < stringValue.size()) {
|
||||
if (stringValue.at(position) == '\"') {
|
||||
stringValue.insert(position, "\"");
|
||||
position++;
|
||||
}
|
||||
position++;
|
||||
}
|
||||
insertedRow << "\"" << stringValue << "\"";
|
||||
break;
|
||||
}
|
||||
|
||||
case eSqliteDataType::INT_BOOL:
|
||||
BinaryIO::BinaryRead(fdb, boolValue);
|
||||
insertedRow << static_cast<bool>(boolValue);
|
||||
break;
|
||||
|
||||
case eSqliteDataType::INT64:
|
||||
int64Value = ReadInt64();
|
||||
insertedRow << std::to_string(int64Value);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw std::invalid_argument("Unsupported SQLite type encountered.");
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
insertedRow << ");";
|
||||
|
||||
auto copiedString = insertedRow.str();
|
||||
CDClientDatabase::ExecuteDML(copiedString);
|
||||
fdb.seekg(prevPosition);
|
||||
}
|
||||
49
dCommon/FdbToSqlite.h
Normal file
49
dCommon/FdbToSqlite.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef __FDBTOSQLITE__H__
|
||||
#define __FDBTOSQLITE__H__
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <iosfwd>
|
||||
#include <map>
|
||||
|
||||
enum class eSqliteDataType : int32_t;
|
||||
|
||||
namespace FdbToSqlite {
|
||||
class Convert {
|
||||
public:
|
||||
Convert(std::string inputFile);
|
||||
|
||||
bool ConvertDatabase();
|
||||
|
||||
int32_t ReadInt32();
|
||||
|
||||
int64_t ReadInt64();
|
||||
|
||||
std::string ReadString();
|
||||
|
||||
int32_t SeekPointer();
|
||||
|
||||
std::string ReadColumnHeader();
|
||||
|
||||
void ReadTables(int32_t& numberOfTables);
|
||||
|
||||
std::string ReadColumns(int32_t& numberOfColumns);
|
||||
|
||||
void ReadRowHeader(std::string& tableName);
|
||||
|
||||
void ReadRows(int32_t& numberOfAllocatedRows, std::string& tableName);
|
||||
|
||||
void ReadRow(int32_t& rowid, int32_t& position, std::string& tableName);
|
||||
|
||||
void ReadRowInfo(std::string& tableName);
|
||||
|
||||
void ReadRowValues(int32_t& numberOfColumns, std::string& tableName);
|
||||
private:
|
||||
static std::map<eSqliteDataType, std::string> sqliteType;
|
||||
std::string basePath{};
|
||||
std::ifstream fdb{};
|
||||
}; // class FdbToSqlite
|
||||
}; //! namespace FdbToSqlite
|
||||
|
||||
#endif //!__FDBTOSQLITE__H__
|
||||
@@ -5,22 +5,20 @@
|
||||
class dServer;
|
||||
class dLogger;
|
||||
class InstanceManager;
|
||||
class dpWorld;
|
||||
class dChatFilter;
|
||||
class dConfig;
|
||||
class dLocale;
|
||||
class RakPeerInterface;
|
||||
class AssetManager;
|
||||
struct SystemAddress;
|
||||
|
||||
namespace Game {
|
||||
extern dLogger* logger;
|
||||
extern dServer* server;
|
||||
extern InstanceManager* im;
|
||||
extern dpWorld* physicsWorld;
|
||||
extern dChatFilter* chatFilter;
|
||||
extern dConfig* config;
|
||||
extern dLocale* locale;
|
||||
extern std::mt19937 randomEngine;
|
||||
extern RakPeerInterface* chatServer;
|
||||
extern AssetManager* assetManager;
|
||||
extern SystemAddress chatSysAddr;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
|
||||
template <typename T>
|
||||
inline size_t MinSize(size_t size, const std::basic_string_view<T>& string) {
|
||||
@@ -50,6 +52,7 @@ bool _IsSuffixChar(uint8_t c) {
|
||||
|
||||
bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) {
|
||||
size_t rem = slice.length();
|
||||
if (slice.empty()) return false;
|
||||
const uint8_t* bytes = (const uint8_t*)&slice.front();
|
||||
if (rem > 0) {
|
||||
uint8_t first = bytes[0];
|
||||
@@ -289,51 +292,30 @@ std::u16string GeneralUtils::ReadWString(RakNet::BitStream* inStream) {
|
||||
return string;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
std::vector<std::string> GeneralUtils::GetFileNamesFromFolder(const std::string& folder) {
|
||||
std::vector<std::string> names;
|
||||
std::string search_path = folder + "/*.*";
|
||||
WIN32_FIND_DATA fd;
|
||||
HANDLE hFind = ::FindFirstFile(search_path.c_str(), &fd);
|
||||
if (hFind != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
names.push_back(fd.cFileName);
|
||||
}
|
||||
} while (::FindNextFile(hFind, &fd));
|
||||
::FindClose(hFind);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
#else
|
||||
#include <stdio.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/types.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
|
||||
std::vector<std::string> GeneralUtils::GetFileNamesFromFolder(const std::string& folder) {
|
||||
std::vector<std::string> names;
|
||||
struct dirent* entry;
|
||||
DIR* dir = opendir(folder.c_str());
|
||||
if (dir == NULL) {
|
||||
return names;
|
||||
std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::string& folder) {
|
||||
// Because we dont know how large the initial number before the first _ is we need to make it a map like so.
|
||||
std::map<uint32_t, std::string> filenames{};
|
||||
for (auto& t : std::filesystem::directory_iterator(folder)) {
|
||||
auto filename = t.path().filename().string();
|
||||
auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0));
|
||||
filenames.insert(std::make_pair(index, filename));
|
||||
}
|
||||
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
std::string value(entry->d_name, strlen(entry->d_name));
|
||||
if (value == "." || value == "..") {
|
||||
continue;
|
||||
// Now sort the map by the oldest migration.
|
||||
std::vector<std::string> sortedFiles{};
|
||||
auto fileIterator = filenames.begin();
|
||||
std::map<uint32_t, std::string>::iterator oldest = filenames.begin();
|
||||
while (!filenames.empty()) {
|
||||
if (fileIterator == filenames.end()) {
|
||||
sortedFiles.push_back(oldest->second);
|
||||
filenames.erase(oldest);
|
||||
fileIterator = filenames.begin();
|
||||
oldest = filenames.begin();
|
||||
continue;
|
||||
}
|
||||
names.push_back(value);
|
||||
if (oldest->first > fileIterator->first) oldest = fileIterator;
|
||||
fileIterator++;
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return names;
|
||||
return sortedFiles;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <BitStream.h>
|
||||
|
||||
#include "Game.h"
|
||||
#include "dLogger.h"
|
||||
|
||||
/*!
|
||||
\file GeneralUtils.hpp
|
||||
@@ -138,7 +139,7 @@ namespace GeneralUtils {
|
||||
|
||||
std::vector<std::string> SplitString(const std::string& str, char delimiter);
|
||||
|
||||
std::vector<std::string> GetFileNamesFromFolder(const std::string& folder);
|
||||
std::vector<std::string> GetSqlFileNamesFromFolder(const std::string& folder);
|
||||
|
||||
template <typename T>
|
||||
T Parse(const char* value);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "ZCompression.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
namespace ZCompression {
|
||||
@@ -27,7 +25,6 @@ namespace ZCompression {
|
||||
}
|
||||
deflateEnd(&zInfo); // zlib function
|
||||
return(nRet);
|
||||
|
||||
}
|
||||
|
||||
int32_t Decompress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst, int32_t& nErr) {
|
||||
@@ -48,26 +45,6 @@ namespace ZCompression {
|
||||
}
|
||||
inflateEnd(&zInfo); // zlib function
|
||||
return(nRet);
|
||||
|
||||
/*
|
||||
z_stream zInfo = { 0 };
|
||||
zInfo.total_in = zInfo.avail_in = nLenSrc;
|
||||
zInfo.total_out = zInfo.avail_out = nLenDst;
|
||||
zInfo.next_in = const_cast<Bytef*>(abSrc);
|
||||
zInfo.next_out = const_cast<Bytef*>(abDst);
|
||||
|
||||
int nRet = -1;
|
||||
nErr = inflateInit(&zInfo); // zlib function
|
||||
if (nErr == Z_OK) {
|
||||
nErr = inflate(&zInfo, Z_FINISH); // zlib function
|
||||
if (nErr == Z_STREAM_END) {
|
||||
nRet = zInfo.total_out;
|
||||
}
|
||||
}
|
||||
inflateEnd(&zInfo); // zlib function
|
||||
return(nRet); // -1 or len of output
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -2,16 +2,17 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "dPlatforms.h"
|
||||
|
||||
#ifndef DARKFLAME_PLATFORM_WIN32
|
||||
|
||||
namespace ZCompression {
|
||||
int32_t GetMaxCompressedLength(int32_t nLenSrc);
|
||||
|
||||
int32_t Compress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst);
|
||||
|
||||
int32_t Decompress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst, int32_t& nErr);
|
||||
|
||||
/**
|
||||
* @brief Max size of an inflated sd0 zlib chunk
|
||||
*
|
||||
*/
|
||||
constexpr uint32_t MAX_SD0_CHUNK_SIZE = 1024 * 256;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
207
dCommon/dClient/AssetManager.cpp
Normal file
207
dCommon/dClient/AssetManager.cpp
Normal file
@@ -0,0 +1,207 @@
|
||||
#include <filesystem>
|
||||
|
||||
#include "AssetManager.h"
|
||||
#include "Game.h"
|
||||
#include "dLogger.h"
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
AssetManager::AssetManager(const std::filesystem::path& path) {
|
||||
if (!std::filesystem::is_directory(path)) {
|
||||
throw std::runtime_error("Attempted to load asset bundle (" + path.string() + ") however it is not a valid directory.");
|
||||
}
|
||||
|
||||
m_Path = path;
|
||||
|
||||
if (std::filesystem::exists(m_Path / "client") && std::filesystem::exists(m_Path / "versions")) {
|
||||
m_AssetBundleType = eAssetBundleType::Packed;
|
||||
|
||||
m_RootPath = m_Path;
|
||||
m_ResPath = (m_Path / "client" / "res");
|
||||
} else if (std::filesystem::exists(m_Path / ".." / "versions") && std::filesystem::exists(m_Path / "res")) {
|
||||
m_AssetBundleType = eAssetBundleType::Packed;
|
||||
|
||||
m_RootPath = (m_Path / "..");
|
||||
m_ResPath = (m_Path / "res");
|
||||
} else if (std::filesystem::exists(m_Path / "pack") && std::filesystem::exists(m_Path / ".." / ".." / "versions")) {
|
||||
m_AssetBundleType = eAssetBundleType::Packed;
|
||||
|
||||
m_RootPath = (m_Path / ".." / "..");
|
||||
m_ResPath = m_Path;
|
||||
} else if ((std::filesystem::exists(m_Path / "res" / "cdclient.fdb") || std::filesystem::exists(m_Path / "res" / "CDServer.sqlite")) && !std::filesystem::exists(m_Path / "res" / "pack")) {
|
||||
m_AssetBundleType = eAssetBundleType::Unpacked;
|
||||
|
||||
m_ResPath = (m_Path / "res");
|
||||
} else if ((std::filesystem::exists(m_Path / "cdclient.fdb") || std::filesystem::exists(m_Path / "CDServer.sqlite")) && !std::filesystem::exists(m_Path / "pack")) {
|
||||
m_AssetBundleType = eAssetBundleType::Unpacked;
|
||||
|
||||
m_ResPath = m_Path;
|
||||
}
|
||||
|
||||
if (m_AssetBundleType == eAssetBundleType::None) {
|
||||
throw std::runtime_error("Failed to identify client type, cannot read client data.");
|
||||
}
|
||||
|
||||
switch (m_AssetBundleType) {
|
||||
case eAssetBundleType::Packed: {
|
||||
this->LoadPackIndex();
|
||||
|
||||
this->UnpackRequiredAssets();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AssetManager::LoadPackIndex() {
|
||||
m_PackIndex = new PackIndex(m_RootPath);
|
||||
}
|
||||
|
||||
std::filesystem::path AssetManager::GetResPath() {
|
||||
return m_ResPath;
|
||||
}
|
||||
|
||||
eAssetBundleType AssetManager::GetAssetBundleType() {
|
||||
return m_AssetBundleType;
|
||||
}
|
||||
|
||||
bool AssetManager::HasFile(const char* name) {
|
||||
auto fixedName = std::string(name);
|
||||
std::transform(fixedName.begin(), fixedName.end(), fixedName.begin(), [](uint8_t c) { return std::tolower(c); });
|
||||
|
||||
// Special case for unpacked client have BrickModels in upper case
|
||||
if (this->m_AssetBundleType == eAssetBundleType::Unpacked) GeneralUtils::ReplaceInString(fixedName, "brickmodels", "BrickModels");
|
||||
|
||||
std::replace(fixedName.begin(), fixedName.end(), '\\', '/');
|
||||
if (std::filesystem::exists(m_ResPath / fixedName)) return true;
|
||||
|
||||
if (this->m_AssetBundleType == eAssetBundleType::Unpacked) return false;
|
||||
|
||||
std::replace(fixedName.begin(), fixedName.end(), '/', '\\');
|
||||
if (fixedName.rfind("client\\res\\", 0) != 0) fixedName = "client\\res\\" + fixedName;
|
||||
|
||||
uint32_t crc = crc32b(0xFFFFFFFF, (uint8_t*)fixedName.c_str(), fixedName.size());
|
||||
crc = crc32b(crc, (Bytef*)"\0\0\0\0", 4);
|
||||
|
||||
for (const auto& item : this->m_PackIndex->GetPackFileIndices()) {
|
||||
if (item.m_Crc == crc) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AssetManager::GetFile(const char* name, char** data, uint32_t* len) {
|
||||
auto fixedName = std::string(name);
|
||||
std::transform(fixedName.begin(), fixedName.end(), fixedName.begin(), [](uint8_t c) { return std::tolower(c); });
|
||||
std::replace(fixedName.begin(), fixedName.end(), '\\', '/'); // On the off chance someone has the wrong slashes, force forward slashes
|
||||
|
||||
// Special case for unpacked client have BrickModels in upper case
|
||||
if (this->m_AssetBundleType == eAssetBundleType::Unpacked) GeneralUtils::ReplaceInString(fixedName, "brickmodels", "BrickModels");
|
||||
|
||||
if (std::filesystem::exists(m_ResPath / fixedName)) {
|
||||
FILE* file;
|
||||
#ifdef _WIN32
|
||||
fopen_s(&file, (m_ResPath / fixedName).string().c_str(), "rb");
|
||||
#elif __APPLE__
|
||||
// macOS has 64bit file IO by default
|
||||
file = fopen((m_ResPath / fixedName).string().c_str(), "rb");
|
||||
#else
|
||||
file = fopen64((m_ResPath / fixedName).string().c_str(), "rb");
|
||||
#endif
|
||||
fseek(file, 0, SEEK_END);
|
||||
*len = ftell(file);
|
||||
*data = (char*)malloc(*len);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
fread(*data, sizeof(uint8_t), *len, file);
|
||||
fclose(file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this->m_AssetBundleType == eAssetBundleType::Unpacked) return false;
|
||||
|
||||
// The crc in side of the pack always uses backslashes, so we need to convert them again...
|
||||
std::replace(fixedName.begin(), fixedName.end(), '/', '\\');
|
||||
if (fixedName.rfind("client\\res\\", 0) != 0) {
|
||||
fixedName = "client\\res\\" + fixedName;
|
||||
}
|
||||
int32_t packIndex = -1;
|
||||
uint32_t crc = crc32b(0xFFFFFFFF, (uint8_t*)fixedName.c_str(), fixedName.size());
|
||||
crc = crc32b(crc, (Bytef*)"\0\0\0\0", 4);
|
||||
|
||||
for (const auto& item : this->m_PackIndex->GetPackFileIndices()) {
|
||||
if (item.m_Crc == crc) {
|
||||
packIndex = item.m_PackFileIndex;
|
||||
crc = item.m_Crc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (packIndex == -1 || !crc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto packs = this->m_PackIndex->GetPacks();
|
||||
auto* pack = packs.at(packIndex);
|
||||
|
||||
bool success = pack->ReadFileFromPack(crc, data, len);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
AssetMemoryBuffer AssetManager::GetFileAsBuffer(const char* name) {
|
||||
char* buf;
|
||||
uint32_t len;
|
||||
|
||||
bool success = this->GetFile(name, &buf, &len);
|
||||
|
||||
return AssetMemoryBuffer(buf, len, success);
|
||||
}
|
||||
|
||||
void AssetManager::UnpackRequiredAssets() {
|
||||
if (std::filesystem::exists(m_ResPath / "cdclient.fdb")) return;
|
||||
|
||||
char* data;
|
||||
uint32_t size;
|
||||
|
||||
bool success = this->GetFile("cdclient.fdb", &data, &size);
|
||||
|
||||
if (!success) {
|
||||
Game::logger->Log("AssetManager", "Failed to extract required files from the packs.");
|
||||
|
||||
delete data;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
std::ofstream cdclientOutput(m_ResPath / "cdclient.fdb", std::ios::out | std::ios::binary);
|
||||
cdclientOutput.write(data, size);
|
||||
cdclientOutput.close();
|
||||
|
||||
delete data;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t AssetManager::crc32b(uint32_t base, uint8_t* message, size_t l) {
|
||||
size_t i, j;
|
||||
uint32_t crc, msb;
|
||||
|
||||
crc = base;
|
||||
for (i = 0; i < l; i++) {
|
||||
// xor next byte to upper bits of crc
|
||||
crc ^= (((unsigned int)message[i]) << 24);
|
||||
for (j = 0; j < 8; j++) { // Do eight times.
|
||||
msb = crc >> 31;
|
||||
crc <<= 1;
|
||||
crc ^= (0 - msb) & 0x04C11DB7;
|
||||
}
|
||||
}
|
||||
return crc; // don't complement crc on output
|
||||
}
|
||||
|
||||
AssetManager::~AssetManager() {
|
||||
delete m_PackIndex;
|
||||
}
|
||||
78
dCommon/dClient/AssetManager.h
Normal file
78
dCommon/dClient/AssetManager.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <filesystem>
|
||||
|
||||
#include "Pack.h"
|
||||
#include "PackIndex.h"
|
||||
|
||||
enum class eAssetBundleType {
|
||||
None,
|
||||
Unpacked,
|
||||
Packed
|
||||
};
|
||||
|
||||
struct AssetMemoryBuffer : std::streambuf {
|
||||
char* m_Base;
|
||||
bool m_Success;
|
||||
|
||||
AssetMemoryBuffer(char* base, std::ptrdiff_t n, bool success) {
|
||||
m_Base = base;
|
||||
m_Success = success;
|
||||
if (!m_Success) return;
|
||||
this->setg(base, base, base + n);
|
||||
}
|
||||
|
||||
pos_type seekpos(pos_type sp, std::ios_base::openmode which) override {
|
||||
return seekoff(sp - pos_type(off_type(0)), std::ios_base::beg, which);
|
||||
}
|
||||
|
||||
pos_type seekoff(off_type off,
|
||||
std::ios_base::seekdir dir,
|
||||
std::ios_base::openmode which = std::ios_base::in) override {
|
||||
if (dir == std::ios_base::cur)
|
||||
gbump(off);
|
||||
else if (dir == std::ios_base::end)
|
||||
setg(eback(), egptr() + off, egptr());
|
||||
else if (dir == std::ios_base::beg)
|
||||
setg(eback(), eback() + off, egptr());
|
||||
return gptr() - eback();
|
||||
}
|
||||
|
||||
void close() {
|
||||
delete m_Base;
|
||||
}
|
||||
};
|
||||
|
||||
class AssetManager {
|
||||
public:
|
||||
AssetManager(const std::filesystem::path& path);
|
||||
~AssetManager();
|
||||
|
||||
std::filesystem::path GetResPath();
|
||||
eAssetBundleType GetAssetBundleType();
|
||||
|
||||
bool HasFile(const char* name);
|
||||
bool GetFile(const char* name, char** data, uint32_t* len);
|
||||
AssetMemoryBuffer GetFileAsBuffer(const char* name);
|
||||
|
||||
private:
|
||||
void LoadPackIndex();
|
||||
void UnpackRequiredAssets();
|
||||
|
||||
// Modified crc algorithm (mpeg2)
|
||||
// Reference: https://stackoverflow.com/questions/54339800/how-to-modify-crc-32-to-crc-32-mpeg-2
|
||||
inline uint32_t crc32b(uint32_t base, uint8_t* message, size_t l);
|
||||
|
||||
bool m_SuccessfullyLoaded;
|
||||
|
||||
std::filesystem::path m_Path;
|
||||
std::filesystem::path m_RootPath;
|
||||
std::filesystem::path m_ResPath;
|
||||
|
||||
eAssetBundleType m_AssetBundleType = eAssetBundleType::None;
|
||||
|
||||
PackIndex* m_PackIndex;
|
||||
};
|
||||
6
dCommon/dClient/CMakeLists.txt
Normal file
6
dCommon/dClient/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
set(DCOMMON_DCLIENT_SOURCES
|
||||
"PackIndex.cpp"
|
||||
"Pack.cpp"
|
||||
"AssetManager.cpp"
|
||||
PARENT_SCOPE
|
||||
)
|
||||
119
dCommon/dClient/Pack.cpp
Normal file
119
dCommon/dClient/Pack.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
#include "Pack.h"
|
||||
|
||||
#include "BinaryIO.h"
|
||||
#include "ZCompression.h"
|
||||
|
||||
Pack::Pack(const std::filesystem::path& filePath) {
|
||||
m_FilePath = filePath;
|
||||
|
||||
if (!std::filesystem::exists(filePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_FileStream = std::ifstream(filePath, std::ios::in | std::ios::binary);
|
||||
|
||||
m_FileStream.read(m_Version, 7);
|
||||
|
||||
m_FileStream.seekg(-8, std::ios::end); // move file pointer to 8 bytes before the end (location of the address of the record count)
|
||||
|
||||
uint32_t recordCountPos = 0;
|
||||
BinaryIO::BinaryRead<uint32_t>(m_FileStream, recordCountPos);
|
||||
|
||||
m_FileStream.seekg(recordCountPos, std::ios::beg);
|
||||
|
||||
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_RecordCount);
|
||||
|
||||
for (int i = 0; i < m_RecordCount; i++) {
|
||||
PackRecord record;
|
||||
BinaryIO::BinaryRead<PackRecord>(m_FileStream, record);
|
||||
|
||||
m_Records.push_back(record);
|
||||
}
|
||||
|
||||
m_FileStream.close();
|
||||
}
|
||||
|
||||
bool Pack::HasFile(uint32_t crc) {
|
||||
for (const auto& record : m_Records) {
|
||||
if (record.m_Crc == crc) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Pack::ReadFileFromPack(uint32_t crc, char** data, uint32_t* len) {
|
||||
// Time for some wacky C file reading for speed reasons
|
||||
|
||||
PackRecord pkRecord{};
|
||||
|
||||
for (const auto& record : m_Records) {
|
||||
if (record.m_Crc == crc) {
|
||||
pkRecord = record;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pkRecord.m_Crc == 0) return false;
|
||||
|
||||
size_t pos = 0;
|
||||
pos += pkRecord.m_FilePointer;
|
||||
|
||||
bool isCompressed = (pkRecord.m_IsCompressed & 0xff) > 0;
|
||||
auto inPackSize = isCompressed ? pkRecord.m_CompressedSize : pkRecord.m_UncompressedSize;
|
||||
|
||||
FILE* file;
|
||||
#ifdef _WIN32
|
||||
fopen_s(&file, m_FilePath.string().c_str(), "rb");
|
||||
#elif __APPLE__
|
||||
// macOS has 64bit file IO by default
|
||||
file = fopen(m_FilePath.string().c_str(), "rb");
|
||||
#else
|
||||
file = fopen64(m_FilePath.string().c_str(), "rb");
|
||||
#endif
|
||||
|
||||
fseek(file, pos, SEEK_SET);
|
||||
|
||||
if (!isCompressed) {
|
||||
char* tempData = (char*)malloc(pkRecord.m_UncompressedSize);
|
||||
fread(tempData, sizeof(uint8_t), pkRecord.m_UncompressedSize, file);
|
||||
|
||||
*data = tempData;
|
||||
*len = pkRecord.m_UncompressedSize;
|
||||
fclose(file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pos += 5; // skip header
|
||||
|
||||
fseek(file, pos, SEEK_SET);
|
||||
|
||||
char* decompressedData = (char*)malloc(pkRecord.m_UncompressedSize);
|
||||
uint32_t currentReadPos = 0;
|
||||
|
||||
while (true) {
|
||||
if (currentReadPos >= pkRecord.m_UncompressedSize) break;
|
||||
|
||||
uint32_t size;
|
||||
fread(&size, sizeof(uint32_t), 1, file);
|
||||
pos += 4; // Move pointer position 4 to the right
|
||||
|
||||
char* chunk = (char*)malloc(size);
|
||||
fread(chunk, sizeof(int8_t), size, file);
|
||||
pos += size; // Move pointer position the amount of bytes read to the right
|
||||
|
||||
int32_t err;
|
||||
currentReadPos += ZCompression::Decompress((uint8_t*)chunk, size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), ZCompression::MAX_SD0_CHUNK_SIZE, err);
|
||||
|
||||
free(chunk);
|
||||
}
|
||||
|
||||
*data = decompressedData;
|
||||
*len = pkRecord.m_UncompressedSize;
|
||||
|
||||
fclose(file);
|
||||
|
||||
return true;
|
||||
}
|
||||
38
dCommon/dClient/Pack.h
Normal file
38
dCommon/dClient/Pack.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct PackRecord {
|
||||
uint32_t m_Crc;
|
||||
int32_t m_LowerCrc;
|
||||
int32_t m_UpperCrc;
|
||||
uint32_t m_UncompressedSize;
|
||||
char m_UncompressedHash[32];
|
||||
uint32_t m_Padding1;
|
||||
uint32_t m_CompressedSize;
|
||||
char m_CompressedHash[32];
|
||||
uint32_t m_Padding2;
|
||||
uint32_t m_FilePointer;
|
||||
uint32_t m_IsCompressed; // u32 bool
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
class Pack {
|
||||
public:
|
||||
Pack(const std::filesystem::path& filePath);
|
||||
~Pack() = default;
|
||||
|
||||
bool HasFile(uint32_t crc);
|
||||
bool ReadFileFromPack(uint32_t crc, char** data, uint32_t* len);
|
||||
private:
|
||||
std::ifstream m_FileStream;
|
||||
std::filesystem::path m_FilePath;
|
||||
|
||||
char m_Version[7];
|
||||
|
||||
uint32_t m_RecordCount;
|
||||
std::vector<PackRecord> m_Records;
|
||||
};
|
||||
54
dCommon/dClient/PackIndex.cpp
Normal file
54
dCommon/dClient/PackIndex.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "PackIndex.h"
|
||||
#include "BinaryIO.h"
|
||||
#include "Game.h"
|
||||
#include "dLogger.h"
|
||||
|
||||
PackIndex::PackIndex(const std::filesystem::path& filePath) {
|
||||
m_FileStream = std::ifstream(filePath / "versions" / "primary.pki", std::ios::in | std::ios::binary);
|
||||
|
||||
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_Version);
|
||||
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_PackPathCount);
|
||||
|
||||
for (int i = 0; i < m_PackPathCount; i++) {
|
||||
uint32_t stringLen = 0;
|
||||
BinaryIO::BinaryRead<uint32_t>(m_FileStream, stringLen);
|
||||
|
||||
std::string path;
|
||||
|
||||
for (int j = 0; j < stringLen; j++) {
|
||||
char inChar;
|
||||
BinaryIO::BinaryRead<char>(m_FileStream, inChar);
|
||||
|
||||
path += inChar;
|
||||
}
|
||||
|
||||
m_PackPaths.push_back(path);
|
||||
}
|
||||
|
||||
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_PackFileIndexCount);
|
||||
|
||||
for (int i = 0; i < m_PackFileIndexCount; i++) {
|
||||
PackFileIndex packFileIndex;
|
||||
BinaryIO::BinaryRead<PackFileIndex>(m_FileStream, packFileIndex);
|
||||
|
||||
m_PackFileIndices.push_back(packFileIndex);
|
||||
}
|
||||
|
||||
Game::logger->Log("PackIndex", "Loaded pack catalog with %i pack files and %i files", m_PackPaths.size(), m_PackFileIndices.size());
|
||||
|
||||
for (auto& item : m_PackPaths) {
|
||||
std::replace(item.begin(), item.end(), '\\', '/');
|
||||
|
||||
auto* pack = new Pack(filePath / item);
|
||||
|
||||
m_Packs.push_back(pack);
|
||||
}
|
||||
|
||||
m_FileStream.close();
|
||||
}
|
||||
|
||||
PackIndex::~PackIndex() {
|
||||
for (const auto* item : m_Packs) {
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
40
dCommon/dClient/PackIndex.h
Normal file
40
dCommon/dClient/PackIndex.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
#include "Pack.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct PackFileIndex {
|
||||
uint32_t m_Crc;
|
||||
int32_t m_LowerCrc;
|
||||
int32_t m_UpperCrc;
|
||||
uint32_t m_PackFileIndex;
|
||||
uint32_t m_IsCompressed; // u32 bool?
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
class PackIndex {
|
||||
public:
|
||||
PackIndex(const std::filesystem::path& filePath);
|
||||
~PackIndex();
|
||||
|
||||
const std::vector<std::string>& GetPackPaths() { return m_PackPaths; }
|
||||
const std::vector<PackFileIndex>& GetPackFileIndices() { return m_PackFileIndices; }
|
||||
const std::vector<Pack*>& GetPacks() { return m_Packs; }
|
||||
private:
|
||||
std::ifstream m_FileStream;
|
||||
|
||||
uint32_t m_Version;
|
||||
|
||||
uint32_t m_PackPathCount;
|
||||
std::vector<std::string> m_PackPaths;
|
||||
uint32_t m_PackFileIndexCount;
|
||||
std::vector<PackFileIndex> m_PackFileIndices;
|
||||
|
||||
std::vector<Pack*> m_Packs;
|
||||
};
|
||||
@@ -1,45 +1,53 @@
|
||||
#include "dConfig.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "BinaryPathFinder.h"
|
||||
#include "GeneralUtils.h"
|
||||
|
||||
dConfig::dConfig(const std::string& filepath) {
|
||||
m_EmptyString = "";
|
||||
std::ifstream in(filepath);
|
||||
m_ConfigFilePath = filepath;
|
||||
LoadConfig();
|
||||
}
|
||||
|
||||
void dConfig::LoadConfig() {
|
||||
std::ifstream in(BinaryPathFinder::GetBinaryDir() / m_ConfigFilePath);
|
||||
if (!in.good()) return;
|
||||
|
||||
std::string line;
|
||||
std::string line{};
|
||||
while (std::getline(in, line)) {
|
||||
if (line.length() > 0) {
|
||||
if (line[0] != '#') ProcessLine(line);
|
||||
}
|
||||
if (!line.empty() && line.front() != '#') ProcessLine(line);
|
||||
}
|
||||
|
||||
std::ifstream sharedConfig(BinaryPathFinder::GetBinaryDir() / "sharedconfig.ini", std::ios::in);
|
||||
if (!sharedConfig.good()) return;
|
||||
|
||||
line.clear();
|
||||
while (std::getline(sharedConfig, line)) {
|
||||
if (!line.empty() && line.front() != '#') ProcessLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
dConfig::~dConfig(void) {
|
||||
void dConfig::ReloadConfig() {
|
||||
this->m_ConfigValues.clear();
|
||||
LoadConfig();
|
||||
}
|
||||
|
||||
const std::string& dConfig::GetValue(std::string key) {
|
||||
for (size_t i = 0; i < m_Keys.size(); ++i) {
|
||||
if (m_Keys[i] == key) return m_Values[i];
|
||||
}
|
||||
|
||||
return m_EmptyString;
|
||||
return this->m_ConfigValues[key];
|
||||
}
|
||||
|
||||
void dConfig::ProcessLine(const std::string& line) {
|
||||
std::stringstream ss(line);
|
||||
std::string segment;
|
||||
std::vector<std::string> seglist;
|
||||
auto splitLine = GeneralUtils::SplitString(line, '=');
|
||||
|
||||
while (std::getline(ss, segment, '=')) {
|
||||
seglist.push_back(segment);
|
||||
}
|
||||
|
||||
if (seglist.size() != 2) return;
|
||||
if (splitLine.size() != 2) return;
|
||||
|
||||
//Make sure that on Linux, we remove special characters:
|
||||
if (!seglist[1].empty() && seglist[1][seglist[1].size() - 1] == '\r')
|
||||
seglist[1].erase(seglist[1].size() - 1);
|
||||
auto& key = splitLine.at(0);
|
||||
auto& value = splitLine.at(1);
|
||||
if (!value.empty() && value.at(value.size() - 1) == '\r') value.erase(value.size() - 1);
|
||||
|
||||
m_Keys.push_back(seglist[0]);
|
||||
m_Values.push_back(seglist[1]);
|
||||
if (this->m_ConfigValues.find(key) != this->m_ConfigValues.end()) return;
|
||||
|
||||
this->m_ConfigValues.insert(std::make_pair(key, value));
|
||||
}
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
#pragma once
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class dConfig {
|
||||
public:
|
||||
dConfig(const std::string& filepath);
|
||||
~dConfig(void);
|
||||
|
||||
/**
|
||||
* Gets the specified key from the config. Returns an empty string if the value is not found.
|
||||
*
|
||||
* @param key Key to find
|
||||
* @return The keys value in the config
|
||||
*/
|
||||
const std::string& GetValue(std::string key);
|
||||
|
||||
/**
|
||||
* Loads the config from a file
|
||||
*/
|
||||
void LoadConfig();
|
||||
|
||||
/**
|
||||
* Reloads the config file to reset values
|
||||
*/
|
||||
void ReloadConfig();
|
||||
|
||||
private:
|
||||
void ProcessLine(const std::string& line);
|
||||
|
||||
private:
|
||||
std::vector<std::string> m_Keys;
|
||||
std::vector<std::string> m_Values;
|
||||
std::string m_EmptyString;
|
||||
std::map<std::string, std::string> m_ConfigValues;
|
||||
std::string m_ConfigFilePath;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __DCOMMONVARS__H__
|
||||
#define __DCOMMONVARS__H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <set>
|
||||
@@ -30,6 +33,8 @@ typedef uint32_t LWOCLONEID; //!< Used for Clone IDs
|
||||
typedef uint16_t LWOMAPID; //!< Used for Map IDs
|
||||
typedef uint16_t LWOINSTANCEID; //!< Used for Instance IDs
|
||||
typedef uint32_t PROPERTYCLONELIST; //!< Used for Property Clone IDs
|
||||
typedef uint32_t STRIPID;
|
||||
typedef uint32_t BEHAVIORSTATE;
|
||||
|
||||
typedef int32_t PetTamingPiece; //!< Pet Taming Pieces
|
||||
|
||||
@@ -429,6 +434,7 @@ enum eInventoryType : uint32_t {
|
||||
ITEMS = 0,
|
||||
VAULT_ITEMS,
|
||||
BRICKS,
|
||||
MODELS_IN_BBB,
|
||||
TEMP_ITEMS = 4,
|
||||
MODELS,
|
||||
TEMP_MODELS,
|
||||
@@ -554,6 +560,7 @@ enum ePlayerFlags {
|
||||
ENTER_BBB_FROM_PROPERTY_EDIT_CONFIRMATION_DIALOG = 64,
|
||||
AG_FIRST_COMBAT_COMPLETE = 65,
|
||||
AG_COMPLETE_BOB_MISSION = 66,
|
||||
IS_NEWS_SCREEN_VISIBLE = 114,
|
||||
NJ_GARMADON_CINEMATIC_SEEN = 125,
|
||||
ELEPHANT_PET_3050 = 801,
|
||||
CAT_PET_3054 = 802,
|
||||
@@ -648,3 +655,5 @@ inline T const& clamp(const T& val, const T& low, const T& high) {
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
#endif //!__DCOMMONVARS__H__
|
||||
@@ -433,6 +433,7 @@ enum GAME_MSG : unsigned short {
|
||||
GAME_MSG_ORIENT_TO_POSITION = 906,
|
||||
GAME_MSG_ORIENT_TO_ANGLE = 907,
|
||||
GAME_MSG_BOUNCER_ACTIVE_STATUS = 942,
|
||||
GAME_MSG_UN_USE_BBB_MODEL = 999,
|
||||
GAME_MSG_BBB_LOAD_ITEM_REQUEST = 1000,
|
||||
GAME_MSG_BBB_SAVE_REQUEST = 1001,
|
||||
GAME_MSG_BBB_SAVE_RESPONSE = 1006,
|
||||
@@ -487,6 +488,7 @@ enum GAME_MSG : unsigned short {
|
||||
GAME_MSG_MATCH_UPDATE = 1310,
|
||||
GAME_MSG_MODULE_ASSEMBLY_DB_DATA_FOR_CLIENT = 1131,
|
||||
GAME_MSG_MODULE_ASSEMBLY_QUERY_DATA = 1132,
|
||||
GAME_MSG_CHANGE_IDLE_FLAGS = 1338,
|
||||
GAME_MSG_VEHICLE_ADD_PASSIVE_BOOST_ACTION = 1340,
|
||||
GAME_MSG_VEHICLE_REMOVE_PASSIVE_BOOST_ACTION = 1341,
|
||||
GAME_MSG_VEHICLE_NOTIFY_SERVER_ADD_PASSIVE_BOOST_ACTION = 1342,
|
||||
@@ -537,6 +539,7 @@ enum GAME_MSG : unsigned short {
|
||||
GAME_MSG_REMOVE_RUN_SPEED_MODIFIER = 1506,
|
||||
GAME_MSG_UPDATE_PROPERTY_PERFORMANCE_COST = 1547,
|
||||
GAME_MSG_PROPERTY_ENTRANCE_BEGIN = 1553,
|
||||
GAME_MSG_REMOVE_BUFF = 1648,
|
||||
GAME_MSG_REQUEST_MOVE_ITEM_BETWEEN_INVENTORY_TYPES = 1666,
|
||||
GAME_MSG_RESPONSE_MOVE_ITEM_BETWEEN_INVENTORY_TYPES = 1667,
|
||||
GAME_MSG_PLAYER_SET_CAMERA_CYCLING_MODE = 1676,
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <cstdint>
|
||||
|
||||
enum class eAnimationFlags : uint32_t {
|
||||
IDLE_INVALID = 0, // made up, for internal use!!!
|
||||
IDLE_NONE = 0,
|
||||
IDLE_BASIC,
|
||||
IDLE_SWIM,
|
||||
IDLE_CARRY,
|
||||
16
dCommon/dEnums/eSqliteDataType.h
Normal file
16
dCommon/dEnums/eSqliteDataType.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef __ESQLITEDATATYPE__H__
|
||||
#define __ESQLITEDATATYPE__H__
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class eSqliteDataType : int32_t {
|
||||
NONE = 0,
|
||||
INT32,
|
||||
REAL = 3,
|
||||
TEXT_4,
|
||||
INT_BOOL,
|
||||
INT64,
|
||||
TEXT_8 = 8
|
||||
};
|
||||
|
||||
#endif //!__ESQLITEDATATYPE__H__
|
||||
14
dCommon/dEnums/eUnequippableActiveType.h
Normal file
14
dCommon/dEnums/eUnequippableActiveType.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __EUNEQUIPPABLEACTIVETYPE__H__
|
||||
#define __EUNEQUIPPABLEACTIVETYPE__H__
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class eUnequippableActiveType : int32_t {
|
||||
INVALID = -1,
|
||||
PET = 0,
|
||||
MOUNT
|
||||
};
|
||||
|
||||
#endif //!__EUNEQUIPPABLEACTIVETYPE__H__
|
||||
26
dCommon/eBlueprintSaveResponseType.h
Normal file
26
dCommon/eBlueprintSaveResponseType.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __EBLUEPRINTSAVERESPONSETYPE__H__
|
||||
#define __EBLUEPRINTSAVERESPONSETYPE__H__
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class eBlueprintSaveResponseType : uint32_t {
|
||||
EverythingWorked = 0,
|
||||
SaveCancelled,
|
||||
CantBeginTransaction,
|
||||
SaveBlueprintFailed,
|
||||
SaveUgobjectFailed,
|
||||
CantEndTransaction,
|
||||
SaveFilesFailed,
|
||||
BadInput,
|
||||
NotEnoughBricks,
|
||||
InventoryFull,
|
||||
ModelGenerationFailed,
|
||||
PlacementFailed,
|
||||
GmLevelInsufficient,
|
||||
WaitForPreviousSave,
|
||||
FindMatchesFailed
|
||||
};
|
||||
|
||||
#endif //!__EBLUEPRINTSAVERESPONSETYPE__H__
|
||||
@@ -14,6 +14,11 @@ CppSQLite3Query CDClientDatabase::ExecuteQuery(const std::string& query) {
|
||||
return conn->execQuery(query.c_str());
|
||||
}
|
||||
|
||||
//! Updates the CDClient file with Data Manipulation Language (DML) commands.
|
||||
int CDClientDatabase::ExecuteDML(const std::string& query) {
|
||||
return conn->execDML(query.c_str());
|
||||
}
|
||||
|
||||
//! Makes prepared statements
|
||||
CppSQLite3Statement CDClientDatabase::CreatePreppedStmt(const std::string& query) {
|
||||
return conn->compileStatement(query.c_str());
|
||||
|
||||
@@ -40,6 +40,14 @@ namespace CDClientDatabase {
|
||||
*/
|
||||
CppSQLite3Query ExecuteQuery(const std::string& query);
|
||||
|
||||
//! Updates the CDClient file with Data Manipulation Language (DML) commands.
|
||||
/*!
|
||||
\param query The DML command to run. DML command can be multiple queries in one string but only
|
||||
the last one will return its number of updated rows.
|
||||
\return The number of updated rows.
|
||||
*/
|
||||
int ExecuteDML(const std::string& query);
|
||||
|
||||
//! Queries the CDClient and parses arguments
|
||||
/*!
|
||||
\param query The query with formatted arguments
|
||||
|
||||
@@ -14,8 +14,6 @@ std::string Database::database;
|
||||
void Database::Connect(const string& host, const string& database, const string& username, const string& password) {
|
||||
|
||||
//To bypass debug issues:
|
||||
std::string newHost = "tcp://" + host;
|
||||
const char* szHost = newHost.c_str();
|
||||
const char* szDatabase = database.c_str();
|
||||
const char* szUsername = username.c_str();
|
||||
const char* szPassword = password.c_str();
|
||||
@@ -23,7 +21,24 @@ void Database::Connect(const string& host, const string& database, const string&
|
||||
driver = sql::mariadb::get_driver_instance();
|
||||
|
||||
sql::Properties properties;
|
||||
properties["hostName"] = szHost;
|
||||
// The mariadb connector is *supposed* to handle unix:// and pipe:// prefixes to hostName, but there are bugs where
|
||||
// 1) it tries to parse a database from the connection string (like in tcp://localhost:3001/darkflame) based on the
|
||||
// presence of a /
|
||||
// 2) even avoiding that, the connector still assumes you're connecting with a tcp socket
|
||||
// So, what we do in the presence of a unix socket or pipe is to set the hostname to the protocol and localhost,
|
||||
// which avoids parsing errors while still ensuring the correct connection type is used, and then setting the appropriate
|
||||
// property manually (which the URL parsing fails to do)
|
||||
const std::string UNIX_PROTO = "unix://";
|
||||
const std::string PIPE_PROTO = "pipe://";
|
||||
if (host.find(UNIX_PROTO) == 0) {
|
||||
properties["hostName"] = "unix://localhost";
|
||||
properties["localSocket"] = host.substr(UNIX_PROTO.length()).c_str();
|
||||
} else if (host.find(PIPE_PROTO) == 0) {
|
||||
properties["hostName"] = "pipe://localhost";
|
||||
properties["pipe"] = host.substr(PIPE_PROTO.length()).c_str();
|
||||
} else {
|
||||
properties["hostName"] = host.c_str();
|
||||
}
|
||||
properties["user"] = szUsername;
|
||||
properties["password"] = szPassword;
|
||||
properties["autoReconnect"] = "true";
|
||||
@@ -35,8 +50,14 @@ void Database::Connect(const string& host, const string& database, const string&
|
||||
}
|
||||
|
||||
void Database::Connect() {
|
||||
con = driver->connect(Database::props);
|
||||
con->setSchema(Database::database);
|
||||
// `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 (Database::props.find("localSocket") != Database::props.end() || Database::props.find("pipe") != Database::props.end()) {
|
||||
con = driver->connect(Database::props);
|
||||
} else {
|
||||
con = driver->connect(Database::props["hostName"].c_str(), Database::props["user"].c_str(), Database::props["password"].c_str());
|
||||
}
|
||||
con->setSchema(Database::database.c_str());
|
||||
}
|
||||
|
||||
void Database::Destroy(std::string source, bool log) {
|
||||
@@ -83,3 +104,15 @@ sql::PreparedStatement* Database::CreatePreppedStmt(const std::string& query) {
|
||||
void Database::Commit() {
|
||||
Database::con->commit();
|
||||
}
|
||||
|
||||
bool Database::GetAutoCommit() {
|
||||
// TODO This should not just access a pointer. A future PR should update this
|
||||
// to check for null and throw an error if the connection is not valid.
|
||||
return con->getAutoCommit();
|
||||
}
|
||||
|
||||
void Database::SetAutoCommit(bool value) {
|
||||
// TODO This should not just access a pointer. A future PR should update this
|
||||
// to check for null and throw an error if the connection is not valid.
|
||||
Database::con->setAutoCommit(value);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ public:
|
||||
static sql::Statement* CreateStmt();
|
||||
static sql::PreparedStatement* CreatePreppedStmt(const std::string& query);
|
||||
static void Commit();
|
||||
static bool GetAutoCommit();
|
||||
static void SetAutoCommit(bool value);
|
||||
|
||||
static std::string GetDatabase() { return database; }
|
||||
static sql::Properties GetProperties() { return props; }
|
||||
|
||||
@@ -1,65 +1,20 @@
|
||||
#include "MigrationRunner.h"
|
||||
|
||||
#include "BrickByBrickFix.h"
|
||||
#include "CDClientDatabase.h"
|
||||
#include "Database.h"
|
||||
#include "Game.h"
|
||||
#include "GeneralUtils.h"
|
||||
#include "dLogger.h"
|
||||
#include "BinaryPathFinder.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
#include <istream>
|
||||
|
||||
void MigrationRunner::RunMigrations() {
|
||||
auto stmt = Database::CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP());");
|
||||
stmt->executeQuery();
|
||||
delete stmt;
|
||||
|
||||
sql::SQLString finalSQL = "";
|
||||
Migration checkMigration{};
|
||||
|
||||
for (const auto& entry : GeneralUtils::GetFileNamesFromFolder("./migrations/")) {
|
||||
auto migration = LoadMigration(entry);
|
||||
|
||||
if (migration.data.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
checkMigration = migration;
|
||||
|
||||
stmt = Database::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;");
|
||||
stmt->setString(1, migration.name);
|
||||
auto res = stmt->executeQuery();
|
||||
bool doExit = res->next();
|
||||
delete res;
|
||||
delete stmt;
|
||||
if (doExit) continue;
|
||||
|
||||
Game::logger->Log("MigrationRunner", "Running migration: %s", migration.name.c_str());
|
||||
|
||||
finalSQL.append(migration.data);
|
||||
finalSQL.append('\n');
|
||||
|
||||
stmt = Database::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
|
||||
stmt->setString(1, entry);
|
||||
stmt->execute();
|
||||
delete stmt;
|
||||
}
|
||||
|
||||
if (!finalSQL.empty()) {
|
||||
try {
|
||||
auto simpleStatement = Database::CreateStmt();
|
||||
simpleStatement->execute(finalSQL);
|
||||
delete simpleStatement;
|
||||
} catch (sql::SQLException e) {
|
||||
Game::logger->Log("MigrationRunner", "Encountered error running migration: %s", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Migration MigrationRunner::LoadMigration(std::string path) {
|
||||
Migration LoadMigration(std::string path) {
|
||||
Migration migration{};
|
||||
std::ifstream file("./migrations/" + path);
|
||||
std::ifstream file(BinaryPathFinder::GetBinaryDir() / "migrations/" / path);
|
||||
|
||||
if (file.is_open()) {
|
||||
std::hash<std::string> hash;
|
||||
|
||||
std::string line;
|
||||
std::string total = "";
|
||||
|
||||
@@ -75,3 +30,129 @@ Migration MigrationRunner::LoadMigration(std::string path) {
|
||||
|
||||
return migration;
|
||||
}
|
||||
|
||||
void MigrationRunner::RunMigrations() {
|
||||
auto* stmt = Database::CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP());");
|
||||
stmt->execute();
|
||||
delete stmt;
|
||||
|
||||
sql::SQLString finalSQL = "";
|
||||
bool runSd0Migrations = false;
|
||||
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/").string())) {
|
||||
auto migration = LoadMigration("dlu/" + entry);
|
||||
|
||||
if (migration.data.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
stmt = Database::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;");
|
||||
stmt->setString(1, migration.name.c_str());
|
||||
auto* res = stmt->executeQuery();
|
||||
bool doExit = res->next();
|
||||
delete res;
|
||||
delete stmt;
|
||||
if (doExit) continue;
|
||||
|
||||
Game::logger->Log("MigrationRunner", "Running migration: %s", migration.name.c_str());
|
||||
if (migration.name == "5_brick_model_sd0.sql") {
|
||||
runSd0Migrations = true;
|
||||
} else {
|
||||
finalSQL.append(migration.data.c_str());
|
||||
}
|
||||
|
||||
stmt = Database::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
|
||||
stmt->setString(1, migration.name.c_str());
|
||||
stmt->execute();
|
||||
delete stmt;
|
||||
}
|
||||
|
||||
if (finalSQL.empty() && !runSd0Migrations) {
|
||||
Game::logger->Log("MigrationRunner", "Server database is up to date.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!finalSQL.empty()) {
|
||||
auto migration = GeneralUtils::SplitString(static_cast<std::string>(finalSQL), ';');
|
||||
std::unique_ptr<sql::Statement> simpleStatement(Database::CreateStmt());
|
||||
for (auto& query : migration) {
|
||||
try {
|
||||
if (query.empty()) continue;
|
||||
simpleStatement->execute(query.c_str());
|
||||
} catch (sql::SQLException& e) {
|
||||
Game::logger->Log("MigrationRunner", "Encountered error running migration: %s", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do this last on the off chance none of the other migrations have been run yet.
|
||||
if (runSd0Migrations) {
|
||||
uint32_t numberOfUpdatedModels = BrickByBrickFix::UpdateBrickByBrickModelsToSd0();
|
||||
Game::logger->Log("MasterServer", "%i models were updated from zlib to sd0.", numberOfUpdatedModels);
|
||||
uint32_t numberOfTruncatedModels = BrickByBrickFix::TruncateBrokenBrickByBrickXml();
|
||||
Game::logger->Log("MasterServer", "%i models were truncated from the database.", numberOfTruncatedModels);
|
||||
}
|
||||
}
|
||||
|
||||
void MigrationRunner::RunSQLiteMigrations() {
|
||||
auto cdstmt = CDClientDatabase::CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP);");
|
||||
cdstmt.execQuery().finalize();
|
||||
cdstmt.finalize();
|
||||
|
||||
auto* stmt = Database::CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP());");
|
||||
stmt->execute();
|
||||
delete stmt;
|
||||
|
||||
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "migrations/cdserver/").string())) {
|
||||
auto migration = LoadMigration("cdserver/" + entry);
|
||||
|
||||
if (migration.data.empty()) continue;
|
||||
|
||||
// Check if there is an entry in the migration history table on the cdclient database.
|
||||
cdstmt = CDClientDatabase::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;");
|
||||
cdstmt.bind((int32_t) 1, migration.name.c_str());
|
||||
auto cdres = cdstmt.execQuery();
|
||||
bool doExit = !cdres.eof();
|
||||
cdres.finalize();
|
||||
cdstmt.finalize();
|
||||
|
||||
if (doExit) continue;
|
||||
|
||||
// Check first if there is entry in the migration history table on the main database.
|
||||
stmt = Database::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;");
|
||||
stmt->setString(1, migration.name.c_str());
|
||||
auto* res = stmt->executeQuery();
|
||||
doExit = res->next();
|
||||
delete res;
|
||||
delete stmt;
|
||||
if (doExit) {
|
||||
// Insert into cdclient database if there is an entry in the main database but not the cdclient database.
|
||||
cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
|
||||
cdstmt.bind((int32_t) 1, migration.name.c_str());
|
||||
cdstmt.execQuery().finalize();
|
||||
cdstmt.finalize();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Doing these 1 migration at a time since one takes a long time and some may think it is crashing.
|
||||
// This will at the least guarentee that the full migration needs to be run in order to be counted as "migrated".
|
||||
Game::logger->Log("MigrationRunner", "Executing migration: %s. This may take a while. Do not shut down server.", migration.name.c_str());
|
||||
CDClientDatabase::ExecuteQuery("BEGIN TRANSACTION;");
|
||||
for (const auto& dml : GeneralUtils::SplitString(migration.data, ';')) {
|
||||
if (dml.empty()) continue;
|
||||
try {
|
||||
CDClientDatabase::ExecuteDML(dml.c_str());
|
||||
} catch (CppSQLite3Exception& e) {
|
||||
Game::logger->Log("MigrationRunner", "Encountered error running DML command: (%i) : %s", e.errorCode(), e.errorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Insert into cdclient database.
|
||||
cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
|
||||
cdstmt.bind((int32_t) 1, migration.name.c_str());
|
||||
cdstmt.execQuery().finalize();
|
||||
cdstmt.finalize();
|
||||
CDClientDatabase::ExecuteQuery("COMMIT;");
|
||||
}
|
||||
|
||||
Game::logger->Log("MigrationRunner", "CDServer database is up to date.");
|
||||
}
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "Database.h"
|
||||
|
||||
#include "dCommonVars.h"
|
||||
#include "Game.h"
|
||||
#include "dCommonVars.h"
|
||||
#include "dLogger.h"
|
||||
#include <string>
|
||||
|
||||
struct Migration {
|
||||
std::string data;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
class MigrationRunner {
|
||||
public:
|
||||
static void RunMigrations();
|
||||
static Migration LoadMigration(std::string path);
|
||||
namespace MigrationRunner {
|
||||
void RunMigrations();
|
||||
void RunSQLiteMigrations();
|
||||
};
|
||||
|
||||
@@ -45,7 +45,7 @@ CDItemComponentTable::CDItemComponentTable(void) {
|
||||
entry.offsetGroupID = tableData.getIntField(19, -1);
|
||||
entry.buildTypes = tableData.getIntField(20, -1);
|
||||
entry.reqPrecondition = tableData.getStringField(21, "");
|
||||
entry.animationFlag = tableData.getIntField(22, -1);
|
||||
entry.animationFlag = tableData.getIntField(22, 0);
|
||||
entry.equipEffects = tableData.getIntField(23, -1);
|
||||
entry.readyForQA = tableData.getIntField(24, -1) == 1 ? true : false;
|
||||
entry.itemRating = tableData.getIntField(25, -1);
|
||||
@@ -123,7 +123,7 @@ const CDItemComponent& CDItemComponentTable::GetItemComponentByID(unsigned int s
|
||||
entry.offsetGroupID = tableData.getIntField(19, -1);
|
||||
entry.buildTypes = tableData.getIntField(20, -1);
|
||||
entry.reqPrecondition = tableData.getStringField(21, "");
|
||||
entry.animationFlag = tableData.getIntField(22, -1);
|
||||
entry.animationFlag = tableData.getIntField(22, 0);
|
||||
entry.equipEffects = tableData.getIntField(23, -1);
|
||||
entry.readyForQA = tableData.getIntField(24, -1) == 1 ? true : false;
|
||||
entry.itemRating = tableData.getIntField(25, -1);
|
||||
|
||||
@@ -44,13 +44,19 @@ foreach(file ${DGAME_DMISSION_SOURCES})
|
||||
set(DGAME_SOURCES ${DGAME_SOURCES} "dMission/${file}")
|
||||
endforeach()
|
||||
|
||||
add_subdirectory(dPropertyBehaviors)
|
||||
|
||||
foreach(file ${DGAME_DPROPERTYBEHAVIORS_SOURCES})
|
||||
set(DGAME_SOURCES ${DGAME_SOURCES} "dPropertyBehaviors/${file}")
|
||||
endforeach()
|
||||
|
||||
add_subdirectory(dUtilities)
|
||||
|
||||
foreach(file ${DGAME_DUTILITIES_SOURCES})
|
||||
set(DGAME_SOURCES ${DGAME_SOURCES} "dUtilities/${file}")
|
||||
endforeach()
|
||||
|
||||
foreach(file ${DSCRIPT_SOURCES})
|
||||
foreach(file ${DSCRIPTS_SOURCES})
|
||||
set(DGAME_SOURCES ${DGAME_SOURCES} "${PROJECT_SOURCE_DIR}/dScripts/${file}")
|
||||
endforeach()
|
||||
|
||||
|
||||
@@ -264,14 +264,17 @@ void Character::DoQuickXMLDataParse() {
|
||||
if (flags) {
|
||||
auto* currentChild = flags->FirstChildElement();
|
||||
while (currentChild) {
|
||||
uint32_t index = 0;
|
||||
uint64_t value = 0;
|
||||
const auto* temp = currentChild->Attribute("v");
|
||||
const auto* id = currentChild->Attribute("id");
|
||||
if (temp && id) {
|
||||
uint32_t index = 0;
|
||||
uint64_t value = 0;
|
||||
|
||||
index = std::stoul(currentChild->Attribute("id"));
|
||||
value = std::stoull(temp);
|
||||
index = std::stoul(id);
|
||||
value = std::stoull(temp);
|
||||
|
||||
m_PlayerFlags.insert(std::make_pair(index, value));
|
||||
m_PlayerFlags.insert(std::make_pair(index, value));
|
||||
}
|
||||
currentChild = currentChild->NextSiblingElement();
|
||||
}
|
||||
}
|
||||
@@ -351,6 +354,13 @@ void Character::SaveXMLToDatabase() {
|
||||
flags->LinkEndChild(f);
|
||||
}
|
||||
|
||||
// Prevents the news feed from showing up on world transfers
|
||||
if (GetPlayerFlag(ePlayerFlags::IS_NEWS_SCREEN_VISIBLE)) {
|
||||
auto* s = m_Doc->NewElement("s");
|
||||
s->SetAttribute("si", ePlayerFlags::IS_NEWS_SCREEN_VISIBLE);
|
||||
flags->LinkEndChild(s);
|
||||
}
|
||||
|
||||
SaveXmlRespawnCheckpoints();
|
||||
|
||||
//Call upon the entity to update our xmlDoc:
|
||||
@@ -361,6 +371,31 @@ void Character::SaveXMLToDatabase() {
|
||||
|
||||
m_OurEntity->UpdateXMLDoc(m_Doc);
|
||||
|
||||
WriteToDatabase();
|
||||
|
||||
//For metrics, log the time it took to save:
|
||||
auto end = std::chrono::system_clock::now();
|
||||
std::chrono::duration<double> elapsed = end - start;
|
||||
Game::logger->Log("Character", "Saved character to Database in: %fs", elapsed.count());
|
||||
}
|
||||
|
||||
void Character::SetIsNewLogin() {
|
||||
// If we dont have a flag element, then we cannot have a s element as a child of flag.
|
||||
auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag");
|
||||
if (!flags) return;
|
||||
|
||||
auto* currentChild = flags->FirstChildElement();
|
||||
while (currentChild) {
|
||||
if (currentChild->Attribute("si")) {
|
||||
flags->DeleteChild(currentChild);
|
||||
Game::logger->Log("Character", "Removed isLoggedIn flag from character %i, saving character to database", GetID());
|
||||
WriteToDatabase();
|
||||
}
|
||||
currentChild = currentChild->NextSiblingElement();
|
||||
}
|
||||
}
|
||||
|
||||
void Character::WriteToDatabase() {
|
||||
//Dump our xml into m_XMLData:
|
||||
auto* printer = new tinyxml2::XMLPrinter(0, true, 0);
|
||||
m_Doc->Print(printer);
|
||||
@@ -372,12 +407,6 @@ void Character::SaveXMLToDatabase() {
|
||||
stmt->setUInt(2, m_ID);
|
||||
stmt->execute();
|
||||
delete stmt;
|
||||
|
||||
//For metrics, log the time it took to save:
|
||||
auto end = std::chrono::system_clock::now();
|
||||
std::chrono::duration<double> elapsed = end - start;
|
||||
Game::logger->Log("Character", "Saved character to Database in: %fs", elapsed.count());
|
||||
|
||||
delete printer;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ public:
|
||||
Character(uint32_t id, User* parentUser);
|
||||
~Character();
|
||||
|
||||
/**
|
||||
* Write the current m_Doc to the database for saving.
|
||||
*/
|
||||
void WriteToDatabase();
|
||||
void SaveXMLToDatabase();
|
||||
void UpdateFromDatabase();
|
||||
|
||||
@@ -32,6 +36,15 @@ public:
|
||||
const std::string& GetXMLData() const { return m_XMLData; }
|
||||
tinyxml2::XMLDocument* GetXMLDoc() const { return m_Doc; }
|
||||
|
||||
/**
|
||||
* Out of abundance of safety and clarity of what this saves, this is its own function.
|
||||
*
|
||||
* Clears the s element from the flag element and saves the xml to the database. Used to prevent the news
|
||||
* feed from showing up on world transfers.
|
||||
*
|
||||
*/
|
||||
void SetIsNewLogin();
|
||||
|
||||
/**
|
||||
* Gets the database ID of the character
|
||||
* @return the database ID of the character
|
||||
@@ -427,7 +440,7 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Get the flying state
|
||||
* @return value of the flying state
|
||||
* @return value of the flying state
|
||||
*/
|
||||
bool GetIsFlying() { return m_IsFlying; }
|
||||
|
||||
|
||||
@@ -317,15 +317,6 @@ void Entity::Initialize() {
|
||||
m_Components.insert(std::make_pair(COMPONENT_TYPE_SOUND_TRIGGER, comp));
|
||||
}
|
||||
|
||||
//Check to see if we have a moving platform component:
|
||||
//Which, for some reason didn't get added to the ComponentsRegistry so we have to check for a path manually here.
|
||||
std::string attachedPath = GetVarAsString(u"attached_path");
|
||||
|
||||
if (!attachedPath.empty() || compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_MOVING_PLATFORM, -1) != -1) {
|
||||
MovingPlatformComponent* plat = new MovingPlatformComponent(this, attachedPath);
|
||||
m_Components.insert(std::make_pair(COMPONENT_TYPE_MOVING_PLATFORM, plat));
|
||||
}
|
||||
|
||||
//Also check for the collectible id:
|
||||
m_CollectibleID = GetVarAs<int32_t>(u"collectible_id");
|
||||
|
||||
@@ -463,7 +454,7 @@ void Entity::Initialize() {
|
||||
*/
|
||||
|
||||
CDScriptComponentTable* scriptCompTable = CDClientManager::Instance()->GetTable<CDScriptComponentTable>("ScriptComponent");
|
||||
int scriptComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_SCRIPT);
|
||||
int32_t scriptComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_SCRIPT, -1);
|
||||
|
||||
std::string scriptName = "";
|
||||
bool client = false;
|
||||
@@ -505,7 +496,7 @@ void Entity::Initialize() {
|
||||
scriptName = customScriptServer;
|
||||
}
|
||||
|
||||
if (!scriptName.empty() || client || m_Character) {
|
||||
if (!scriptName.empty() || client || m_Character || scriptComponentID >= 0) {
|
||||
m_Components.insert(std::make_pair(COMPONENT_TYPE_SCRIPT, new ScriptComponent(this, scriptName, true, client && scriptName.empty())));
|
||||
}
|
||||
|
||||
@@ -696,6 +687,26 @@ void Entity::Initialize() {
|
||||
m_Components.insert(std::make_pair(COMPONENT_TYPE_MOVEMENT_AI, new MovementAIComponent(this, moveInfo)));
|
||||
}
|
||||
|
||||
std::string pathName = GetVarAsString(u"attached_path");
|
||||
const Path* path = dZoneManager::Instance()->GetZone()->GetPath(pathName);
|
||||
|
||||
//Check to see if we have an attached path and add the appropiate component to handle it:
|
||||
if (path){
|
||||
// if we have a moving platform path, then we need a moving platform component
|
||||
if (path->pathType == PathType::MovingPlatform) {
|
||||
MovingPlatformComponent* plat = new MovingPlatformComponent(this, pathName);
|
||||
m_Components.insert(std::make_pair(COMPONENT_TYPE_MOVING_PLATFORM, plat));
|
||||
// else if we are a movement path
|
||||
} /*else if (path->pathType == PathType::Movement) {
|
||||
auto movementAIcomp = GetComponent<MovementAIComponent>();
|
||||
if (movementAIcomp){
|
||||
// TODO: set path in existing movementAIComp
|
||||
} else {
|
||||
// TODO: create movementAIcomp and set path
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
int proximityMonitorID = compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_PROXIMITY_MONITOR);
|
||||
if (proximityMonitorID > 0) {
|
||||
CDProximityMonitorComponentTable* proxCompTable = CDClientManager::Instance()->GetTable<CDProximityMonitorComponentTable>("ProximityMonitorComponent");
|
||||
@@ -1454,6 +1465,13 @@ void Entity::Smash(const LWOOBJID source, const eKillType killType, const std::u
|
||||
Kill(EntityManager::Instance()->GetEntity(source));
|
||||
return;
|
||||
}
|
||||
auto* possessorComponent = GetComponent<PossessorComponent>();
|
||||
if (possessorComponent) {
|
||||
if (possessorComponent->GetPossessable() != LWOOBJID_EMPTY) {
|
||||
auto* mount = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable());
|
||||
if (mount) possessorComponent->Dismount(mount, true);
|
||||
}
|
||||
}
|
||||
|
||||
destroyableComponent->Smash(source, killType, deathType);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "Entity.h"
|
||||
#include "EntityManager.h"
|
||||
#include "SkillComponent.h"
|
||||
#include "AssetManager.h"
|
||||
|
||||
UserManager* UserManager::m_Address = nullptr;
|
||||
|
||||
@@ -32,43 +33,59 @@ inline void StripCR(std::string& str) {
|
||||
}
|
||||
|
||||
void UserManager::Initialize() {
|
||||
std::string firstNamePath = "./res/names/minifigname_first.txt";
|
||||
std::string middleNamePath = "./res/names/minifigname_middle.txt";
|
||||
std::string lastNamePath = "./res/names/minifigname_last.txt";
|
||||
std::string line;
|
||||
|
||||
std::fstream fnFile(firstNamePath, std::ios::in);
|
||||
std::fstream mnFile(middleNamePath, std::ios::in);
|
||||
std::fstream lnFile(lastNamePath, std::ios::in);
|
||||
|
||||
while (std::getline(fnFile, line, '\n')) {
|
||||
AssetMemoryBuffer fnBuff = Game::assetManager->GetFileAsBuffer("names/minifigname_first.txt");
|
||||
if (!fnBuff.m_Success) {
|
||||
Game::logger->Log("UserManager", "Failed to load %s", (Game::assetManager->GetResPath() / "names/minifigname_first.txt").string().c_str());
|
||||
throw std::runtime_error("Aborting initialization due to missing minifigure name file.");
|
||||
}
|
||||
std::istream fnStream = std::istream(&fnBuff);
|
||||
while (std::getline(fnStream, line, '\n')) {
|
||||
std::string name = line;
|
||||
StripCR(name);
|
||||
m_FirstNames.push_back(name);
|
||||
}
|
||||
fnBuff.close();
|
||||
|
||||
while (std::getline(mnFile, line, '\n')) {
|
||||
AssetMemoryBuffer mnBuff = Game::assetManager->GetFileAsBuffer("names/minifigname_middle.txt");
|
||||
if (!mnBuff.m_Success) {
|
||||
Game::logger->Log("UserManager", "Failed to load %s", (Game::assetManager->GetResPath() / "names/minifigname_middle.txt").string().c_str());
|
||||
throw std::runtime_error("Aborting initialization due to missing minifigure name file.");
|
||||
}
|
||||
std::istream mnStream = std::istream(&mnBuff);
|
||||
while (std::getline(mnStream, line, '\n')) {
|
||||
std::string name = line;
|
||||
StripCR(name);
|
||||
m_MiddleNames.push_back(name);
|
||||
}
|
||||
mnBuff.close();
|
||||
|
||||
while (std::getline(lnFile, line, '\n')) {
|
||||
AssetMemoryBuffer lnBuff = Game::assetManager->GetFileAsBuffer("names/minifigname_last.txt");
|
||||
if (!lnBuff.m_Success) {
|
||||
Game::logger->Log("UserManager", "Failed to load %s", (Game::assetManager->GetResPath() / "names/minifigname_last.txt").string().c_str());
|
||||
throw std::runtime_error("Aborting initialization due to missing minifigure name file.");
|
||||
}
|
||||
std::istream lnStream = std::istream(&lnBuff);
|
||||
while (std::getline(lnStream, line, '\n')) {
|
||||
std::string name = line;
|
||||
StripCR(name);
|
||||
m_LastNames.push_back(name);
|
||||
}
|
||||
|
||||
fnFile.close();
|
||||
mnFile.close();
|
||||
lnFile.close();
|
||||
lnBuff.close();
|
||||
|
||||
//Load our pre-approved names:
|
||||
std::fstream chatList("./res/chatplus_en_us.txt", std::ios::in);
|
||||
while (std::getline(chatList, line, '\n')) {
|
||||
AssetMemoryBuffer chatListBuff = Game::assetManager->GetFileAsBuffer("chatplus_en_us.txt");
|
||||
if (!chatListBuff.m_Success) {
|
||||
Game::logger->Log("UserManager", "Failed to load %s", (Game::assetManager->GetResPath() / "chatplus_en_us.txt").string().c_str());
|
||||
throw std::runtime_error("Aborting initialization due to missing chat whitelist file.");
|
||||
}
|
||||
std::istream chatListStream = std::istream(&chatListBuff);
|
||||
while (std::getline(chatListStream, line, '\n')) {
|
||||
StripCR(line);
|
||||
m_PreapprovedNames.push_back(line);
|
||||
}
|
||||
chatListBuff.close();
|
||||
}
|
||||
|
||||
UserManager::~UserManager() {
|
||||
@@ -210,6 +227,7 @@ void UserManager::RequestCharacterList(const SystemAddress& sysAddr) {
|
||||
while (res->next()) {
|
||||
LWOOBJID objID = res->getUInt64(1);
|
||||
Character* character = new Character(uint32_t(objID), u);
|
||||
character->SetIsNewLogin();
|
||||
chars.push_back(character);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
#include "BehaviorBranchContext.h"
|
||||
#include "BehaviorContext.h"
|
||||
#include "EntityManager.h"
|
||||
#include "Game.h"
|
||||
#include "dLogger.h"
|
||||
|
||||
void AirMovementBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
|
||||
uint32_t handle;
|
||||
uint32_t handle{};
|
||||
|
||||
bitStream->Read(handle);
|
||||
if (!bitStream->Read(handle)) {
|
||||
Game::logger->Log("AirMovementBehavior", "Unable to read handle from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
}
|
||||
|
||||
context->RegisterSyncBehavior(handle, this, branch);
|
||||
}
|
||||
@@ -17,14 +22,20 @@ void AirMovementBehavior::Calculate(BehaviorContext* context, RakNet::BitStream*
|
||||
bitStream->Write(handle);
|
||||
}
|
||||
|
||||
void AirMovementBehavior::Sync(BehaviorContext* context, RakNet::BitStream* bit_stream, BehaviorBranchContext branch) {
|
||||
uint32_t behaviorId;
|
||||
void AirMovementBehavior::Sync(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
|
||||
uint32_t behaviorId{};
|
||||
|
||||
bit_stream->Read(behaviorId);
|
||||
if (!bitStream->Read(behaviorId)) {
|
||||
Game::logger->Log("AirMovementBehavior", "Unable to read behaviorId from bitStream, aborting Sync! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
}
|
||||
|
||||
LWOOBJID target;
|
||||
LWOOBJID target{};
|
||||
|
||||
bit_stream->Read(target);
|
||||
if (!bitStream->Read(target)) {
|
||||
Game::logger->Log("AirMovementBehavior", "Unable to read target from bitStream, aborting Sync! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
}
|
||||
|
||||
auto* behavior = CreateBehavior(behaviorId);
|
||||
|
||||
@@ -32,7 +43,7 @@ void AirMovementBehavior::Sync(BehaviorContext* context, RakNet::BitStream* bit_
|
||||
branch.target = target;
|
||||
}
|
||||
|
||||
behavior->Handle(context, bit_stream, branch);
|
||||
behavior->Handle(context, bitStream, branch);
|
||||
}
|
||||
|
||||
void AirMovementBehavior::Load() {
|
||||
|
||||
@@ -16,7 +16,7 @@ public:
|
||||
|
||||
void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override;
|
||||
|
||||
void Sync(BehaviorContext* context, RakNet::BitStream* bit_stream, BehaviorBranchContext branch) override;
|
||||
void Sync(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override;
|
||||
|
||||
void Load() override;
|
||||
};
|
||||
|
||||
@@ -9,11 +9,16 @@
|
||||
#include "BehaviorContext.h"
|
||||
#include "RebuildComponent.h"
|
||||
#include "DestroyableComponent.h"
|
||||
#include "Game.h"
|
||||
#include "dLogger.h"
|
||||
|
||||
void AreaOfEffectBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
|
||||
uint32_t targetCount;
|
||||
uint32_t targetCount{};
|
||||
|
||||
bitStream->Read(targetCount);
|
||||
if (!bitStream->Read(targetCount)) {
|
||||
Game::logger->Log("AreaOfEffectBehavior", "Unable to read targetCount from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetCount > this->m_maxTargets) {
|
||||
return;
|
||||
@@ -24,9 +29,12 @@ void AreaOfEffectBehavior::Handle(BehaviorContext* context, RakNet::BitStream* b
|
||||
targets.reserve(targetCount);
|
||||
|
||||
for (auto i = 0u; i < targetCount; ++i) {
|
||||
LWOOBJID target;
|
||||
LWOOBJID target{};
|
||||
|
||||
bitStream->Read(target);
|
||||
if (!bitStream->Read(target)) {
|
||||
Game::logger->Log("AreaOfEffectBehavior", "failed to read in target %i from bitStream, aborting target Handle!", i);
|
||||
return;
|
||||
};
|
||||
|
||||
targets.push_back(target);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
#include "dLogger.h"
|
||||
|
||||
void AttackDelayBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, const BehaviorBranchContext branch) {
|
||||
uint32_t handle;
|
||||
uint32_t handle{};
|
||||
|
||||
bitStream->Read(handle);
|
||||
if (!bitStream->Read(handle)) {
|
||||
Game::logger->Log("AttackDelayBehavior", "Unable to read handle from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
};
|
||||
|
||||
for (auto i = 0u; i < this->m_numIntervals; ++i) {
|
||||
context->RegisterSyncBehavior(handle, this, branch);
|
||||
|
||||
@@ -14,43 +14,65 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bi
|
||||
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
|
||||
if (destroyableComponent != nullptr) {
|
||||
PlayFx(u"onhit", entity->GetObjectID());
|
||||
destroyableComponent->Damage(this->m_maxDamage, context->originator, context->skillID);
|
||||
destroyableComponent->Damage(this->m_MaxDamage, context->originator, context->skillID);
|
||||
}
|
||||
|
||||
this->m_onSuccess->Handle(context, bitStream, branch);
|
||||
this->m_OnSuccess->Handle(context, bitStream, branch);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bitStream->AlignReadToByteBoundary();
|
||||
|
||||
uint16_t allocatedBits;
|
||||
bitStream->Read(allocatedBits);
|
||||
|
||||
uint16_t allocatedBits{};
|
||||
if (!bitStream->Read(allocatedBits) || allocatedBits == 0) {
|
||||
Game::logger->LogDebug("BasicAttackBehavior", "No allocated bits");
|
||||
return;
|
||||
}
|
||||
Game::logger->LogDebug("BasicAttackBehavior", "Number of allocated bits %i", allocatedBits);
|
||||
const auto baseAddress = bitStream->GetReadOffset();
|
||||
if (bitStream->ReadBit()) { // Blocked
|
||||
bool isBlocked{};
|
||||
bool isImmune{};
|
||||
bool isSuccess{};
|
||||
|
||||
if (!bitStream->Read(isBlocked)) {
|
||||
Game::logger->LogDebug("BasicAttackBehavior", "Unable to read isBlocked");
|
||||
return;
|
||||
}
|
||||
|
||||
if (bitStream->ReadBit()) { // Immune
|
||||
if (isBlocked) return;
|
||||
|
||||
if (!bitStream->Read(isImmune)) {
|
||||
Game::logger->LogDebug("BasicAttackBehavior", "Unable to read isImmune");
|
||||
return;
|
||||
}
|
||||
|
||||
if (bitStream->ReadBit()) { // Success
|
||||
uint32_t unknown;
|
||||
bitStream->Read(unknown);
|
||||
if (isImmune) return;
|
||||
|
||||
uint32_t damageDealt;
|
||||
bitStream->Read(damageDealt);
|
||||
if (bitStream->Read(isSuccess) && isSuccess) { // Success
|
||||
uint32_t unknown{};
|
||||
if (!bitStream->Read(unknown)) {
|
||||
Game::logger->LogDebug("BasicAttackBehavior", "Unable to read unknown");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t damageDealt{};
|
||||
if (!bitStream->Read(damageDealt)) {
|
||||
Game::logger->LogDebug("BasicAttackBehavior", "Unable to read damageDealt");
|
||||
return;
|
||||
}
|
||||
|
||||
// A value that's too large may be a cheating attempt, so we set it to MIN too
|
||||
if (damageDealt > this->m_maxDamage || damageDealt < this->m_minDamage) {
|
||||
damageDealt = this->m_minDamage;
|
||||
if (damageDealt > this->m_MaxDamage || damageDealt < this->m_MinDamage) {
|
||||
damageDealt = this->m_MinDamage;
|
||||
}
|
||||
|
||||
auto* entity = EntityManager::Instance()->GetEntity(branch.target);
|
||||
bool died;
|
||||
bitStream->Read(died);
|
||||
bool died{};
|
||||
if (!bitStream->Read(died)) {
|
||||
Game::logger->LogDebug("BasicAttackBehavior", "Unable to read died");
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity != nullptr) {
|
||||
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
|
||||
@@ -61,15 +83,18 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bi
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t successState;
|
||||
bitStream->Read(successState);
|
||||
uint8_t successState{};
|
||||
if (!bitStream->Read(successState)) {
|
||||
Game::logger->LogDebug("BasicAttackBehavior", "Unable to read success state");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (successState) {
|
||||
case 1:
|
||||
this->m_onSuccess->Handle(context, bitStream, branch);
|
||||
this->m_OnSuccess->Handle(context, bitStream, branch);
|
||||
break;
|
||||
default:
|
||||
Game::logger->Log("BasicAttackBehavior", "Unknown success state (%i)!", successState);
|
||||
Game::logger->LogDebug("BasicAttackBehavior", "Unknown success state (%i)!", successState);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -79,7 +104,7 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bi
|
||||
void BasicAttackBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
|
||||
auto* self = EntityManager::Instance()->GetEntity(context->originator);
|
||||
if (self == nullptr) {
|
||||
Game::logger->Log("BasicAttackBehavior", "Invalid self entity (%llu)!", context->originator);
|
||||
Game::logger->LogDebug("BasicAttackBehavior", "Invalid self entity (%llu)!", context->originator);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -99,7 +124,7 @@ void BasicAttackBehavior::Calculate(BehaviorContext* context, RakNet::BitStream*
|
||||
uint32_t unknown3 = 0;
|
||||
bitStream->Write(unknown3);
|
||||
|
||||
auto damage = this->m_minDamage;
|
||||
auto damage = this->m_MinDamage;
|
||||
auto* entity = EntityManager::Instance()->GetEntity(branch.target);
|
||||
|
||||
if (entity == nullptr) {
|
||||
@@ -124,10 +149,10 @@ void BasicAttackBehavior::Calculate(BehaviorContext* context, RakNet::BitStream*
|
||||
|
||||
switch (successState) {
|
||||
case 1:
|
||||
this->m_onSuccess->Calculate(context, bitStream, branch);
|
||||
this->m_OnSuccess->Calculate(context, bitStream, branch);
|
||||
break;
|
||||
default:
|
||||
Game::logger->Log("BasicAttackBehavior", "Unknown success state (%i)!", successState);
|
||||
Game::logger->LogDebug("BasicAttackBehavior", "Unknown success state (%i)!", successState);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -140,11 +165,13 @@ void BasicAttackBehavior::Calculate(BehaviorContext* context, RakNet::BitStream*
|
||||
}
|
||||
|
||||
void BasicAttackBehavior::Load() {
|
||||
this->m_minDamage = GetInt("min damage");
|
||||
if (this->m_minDamage == 0) this->m_minDamage = 1;
|
||||
this->m_MinDamage = GetInt("min damage");
|
||||
if (this->m_MinDamage == 0) this->m_MinDamage = 1;
|
||||
|
||||
this->m_maxDamage = GetInt("max damage");
|
||||
if (this->m_maxDamage == 0) this->m_maxDamage = 1;
|
||||
this->m_MaxDamage = GetInt("max damage");
|
||||
if (this->m_MaxDamage == 0) this->m_MaxDamage = 1;
|
||||
|
||||
this->m_onSuccess = GetAction("on_success");
|
||||
this->m_OnSuccess = GetAction("on_success");
|
||||
|
||||
this->m_OnFailArmor = GetAction("on_fail_armor");
|
||||
}
|
||||
|
||||
@@ -4,12 +4,6 @@
|
||||
class BasicAttackBehavior final : public Behavior
|
||||
{
|
||||
public:
|
||||
uint32_t m_minDamage;
|
||||
|
||||
uint32_t m_maxDamage;
|
||||
|
||||
Behavior* m_onSuccess;
|
||||
|
||||
explicit BasicAttackBehavior(const uint32_t behaviorId) : Behavior(behaviorId) {
|
||||
}
|
||||
|
||||
@@ -18,4 +12,12 @@ public:
|
||||
void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override;
|
||||
|
||||
void Load() override;
|
||||
private:
|
||||
uint32_t m_MinDamage;
|
||||
|
||||
uint32_t m_MaxDamage;
|
||||
|
||||
Behavior* m_OnSuccess;
|
||||
|
||||
Behavior* m_OnFailArmor;
|
||||
};
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "SkillCastFailedBehavior.h"
|
||||
#include "SpawnBehavior.h"
|
||||
#include "ForceMovementBehavior.h"
|
||||
#include "RemoveBuffBehavior.h"
|
||||
#include "ImmunityBehavior.h"
|
||||
#include "InterruptBehavior.h"
|
||||
#include "PlayEffectBehavior.h"
|
||||
@@ -59,6 +60,7 @@
|
||||
#include "SpeedBehavior.h"
|
||||
#include "DamageReductionBehavior.h"
|
||||
#include "JetPackBehavior.h"
|
||||
#include "ChangeIdleFlagsBehavior.h"
|
||||
|
||||
//CDClient includes
|
||||
#include "CDBehaviorParameterTable.h"
|
||||
@@ -195,7 +197,9 @@ Behavior* Behavior::CreateBehavior(const uint32_t behaviorId) {
|
||||
behavior = new SkillCastFailedBehavior(behaviorId);
|
||||
break;
|
||||
case BehaviorTemplates::BEHAVIOR_IMITATION_SKUNK_STINK: break;
|
||||
case BehaviorTemplates::BEHAVIOR_CHANGE_IDLE_FLAGS: break;
|
||||
case BehaviorTemplates::BEHAVIOR_CHANGE_IDLE_FLAGS:
|
||||
behavior = new ChangeIdleFlagsBehavior(behaviorId);
|
||||
break;
|
||||
case BehaviorTemplates::BEHAVIOR_APPLY_BUFF:
|
||||
behavior = new ApplyBuffBehavior(behaviorId);
|
||||
break;
|
||||
@@ -226,7 +230,9 @@ Behavior* Behavior::CreateBehavior(const uint32_t behaviorId) {
|
||||
break;
|
||||
case BehaviorTemplates::BEHAVIOR_ALTER_CHAIN_DELAY: break;
|
||||
case BehaviorTemplates::BEHAVIOR_CAMERA: break;
|
||||
case BehaviorTemplates::BEHAVIOR_REMOVE_BUFF: break;
|
||||
case BehaviorTemplates::BEHAVIOR_REMOVE_BUFF:
|
||||
behavior = new RemoveBuffBehavior(behaviorId);
|
||||
break;
|
||||
case BehaviorTemplates::BEHAVIOR_GRAB: break;
|
||||
case BehaviorTemplates::BEHAVIOR_MODULAR_BUILD: break;
|
||||
case BehaviorTemplates::BEHAVIOR_NPC_COMBAT_SKILL:
|
||||
|
||||
@@ -12,6 +12,7 @@ set(DGAME_DBEHAVIORS_SOURCES "AirMovementBehavior.cpp"
|
||||
"BuffBehavior.cpp"
|
||||
"CarBoostBehavior.cpp"
|
||||
"ChainBehavior.cpp"
|
||||
"ChangeIdleFlagsBehavior.cpp"
|
||||
"ChangeOrientationBehavior.cpp"
|
||||
"ChargeUpBehavior.cpp"
|
||||
"ClearTargetBehavior.cpp"
|
||||
@@ -34,6 +35,7 @@ set(DGAME_DBEHAVIORS_SOURCES "AirMovementBehavior.cpp"
|
||||
"PlayEffectBehavior.cpp"
|
||||
"ProjectileAttackBehavior.cpp"
|
||||
"PullToPointBehavior.cpp"
|
||||
"RemoveBuffBehavior.cpp"
|
||||
"RepairBehavior.cpp"
|
||||
"SkillCastFailedBehavior.cpp"
|
||||
"SkillEventBehavior.cpp"
|
||||
|
||||
@@ -4,14 +4,19 @@
|
||||
#include "dLogger.h"
|
||||
|
||||
void ChainBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, const BehaviorBranchContext branch) {
|
||||
uint32_t chain_index;
|
||||
uint32_t chainIndex{};
|
||||
|
||||
bitStream->Read(chain_index);
|
||||
if (!bitStream->Read(chainIndex)) {
|
||||
Game::logger->Log("ChainBehavior", "Unable to read chainIndex from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
}
|
||||
|
||||
chain_index--;
|
||||
chainIndex--;
|
||||
|
||||
if (chain_index < this->m_behaviors.size()) {
|
||||
this->m_behaviors.at(chain_index)->Handle(context, bitStream, branch);
|
||||
if (chainIndex < this->m_behaviors.size()) {
|
||||
this->m_behaviors.at(chainIndex)->Handle(context, bitStream, branch);
|
||||
} else {
|
||||
Game::logger->Log("ChainBehavior", "chainIndex out of bounds, aborting handle of chain %i bits unread %i", chainIndex, bitStream->GetNumberOfUnreadBits());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
37
dGame/dBehaviors/ChangeIdleFlagsBehavior.cpp
Normal file
37
dGame/dBehaviors/ChangeIdleFlagsBehavior.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
#include "ChangeIdleFlagsBehavior.h"
|
||||
#include "BehaviorContext.h"
|
||||
#include "BehaviorBranchContext.h"
|
||||
|
||||
void ChangeIdleFlagsBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
|
||||
const auto target = branch.target != LWOOBJID_EMPTY ? branch.target : context->originator;
|
||||
if (!target) return;
|
||||
|
||||
GameMessages::SendChangeIdleFlags(target, m_FlagsOn, m_FlagsOff, UNASSIGNED_SYSTEM_ADDRESS);
|
||||
|
||||
if (branch.duration > 0.0f) {
|
||||
context->RegisterTimerBehavior(this, branch);
|
||||
} else if (branch.start > 0) {
|
||||
context->RegisterEndBehavior(this, branch);
|
||||
}
|
||||
}
|
||||
|
||||
void ChangeIdleFlagsBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
|
||||
Handle(context, bitStream, branch);
|
||||
}
|
||||
|
||||
void ChangeIdleFlagsBehavior::End(BehaviorContext* context, BehaviorBranchContext branch, LWOOBJID second) {
|
||||
const auto target = branch.target != LWOOBJID_EMPTY ? branch.target : context->originator;
|
||||
if (!target) return;
|
||||
// flip on and off to end behavior
|
||||
GameMessages::SendChangeIdleFlags(target, m_FlagsOff, m_FlagsOn, UNASSIGNED_SYSTEM_ADDRESS);
|
||||
}
|
||||
|
||||
void ChangeIdleFlagsBehavior::Timer(BehaviorContext* context, BehaviorBranchContext branch, LWOOBJID second) {
|
||||
End(context, branch, second);
|
||||
}
|
||||
|
||||
void ChangeIdleFlagsBehavior::Load() {
|
||||
m_FlagsOff = static_cast<eAnimationFlags>(GetInt("flags_off", 0));
|
||||
m_FlagsOn = static_cast<eAnimationFlags>(GetInt("flags_on", 0));
|
||||
}
|
||||
23
dGame/dBehaviors/ChangeIdleFlagsBehavior.h
Normal file
23
dGame/dBehaviors/ChangeIdleFlagsBehavior.h
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
#pragma once
|
||||
#include "Behavior.h"
|
||||
#include "eAninmationFlags.h"
|
||||
|
||||
class ChangeIdleFlagsBehavior final : public Behavior {
|
||||
public:
|
||||
|
||||
/*
|
||||
* Inherited
|
||||
*/
|
||||
explicit ChangeIdleFlagsBehavior(const uint32_t behaviorId) : Behavior(behaviorId) {}
|
||||
|
||||
void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override;
|
||||
void Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override;
|
||||
void Timer(BehaviorContext* context, BehaviorBranchContext branch, LWOOBJID second) override;
|
||||
void End(BehaviorContext* context, BehaviorBranchContext branch, LWOOBJID second) override;
|
||||
void Load() override;
|
||||
|
||||
private:
|
||||
eAnimationFlags m_FlagsOff;
|
||||
eAnimationFlags m_FlagsOn;
|
||||
};
|
||||
@@ -1,12 +1,16 @@
|
||||
#include "ChargeUpBehavior.h"
|
||||
#include "BehaviorBranchContext.h"
|
||||
#include "BehaviorContext.h"
|
||||
#include "Game.h"
|
||||
#include "dLogger.h"
|
||||
|
||||
void ChargeUpBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, const BehaviorBranchContext branch) {
|
||||
uint32_t handle;
|
||||
uint32_t handle{};
|
||||
|
||||
bitStream->Read(handle);
|
||||
if (!bitStream->Read(handle)) {
|
||||
Game::logger->Log("ChargeUpBehavior", "Unable to read handle from bitStream, aborting Handle! variable_type");
|
||||
return;
|
||||
};
|
||||
|
||||
context->RegisterSyncBehavior(handle, this, branch);
|
||||
}
|
||||
|
||||
@@ -3,23 +3,34 @@
|
||||
#include "BehaviorContext.h"
|
||||
#include "ControllablePhysicsComponent.h"
|
||||
#include "EntityManager.h"
|
||||
#include "Game.h"
|
||||
#include "dLogger.h"
|
||||
|
||||
void ForceMovementBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, const BehaviorBranchContext branch) {
|
||||
if (this->m_hitAction->m_templateId == BehaviorTemplates::BEHAVIOR_EMPTY && this->m_hitEnemyAction->m_templateId == BehaviorTemplates::BEHAVIOR_EMPTY && this->m_hitFactionAction->m_templateId == BehaviorTemplates::BEHAVIOR_EMPTY) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t handle;
|
||||
bitStream->Read(handle);
|
||||
uint32_t handle{};
|
||||
if (!bitStream->Read(handle)) {
|
||||
Game::logger->Log("ForceMovementBehavior", "Unable to read handle from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
}
|
||||
context->RegisterSyncBehavior(handle, this, branch);
|
||||
}
|
||||
|
||||
void ForceMovementBehavior::Sync(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
|
||||
uint32_t next;
|
||||
bitStream->Read(next);
|
||||
uint32_t next{};
|
||||
if (!bitStream->Read(next)) {
|
||||
Game::logger->Log("ForceMovementBehavior", "Unable to read target from bitStream, aborting Sync! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
}
|
||||
|
||||
LWOOBJID target;
|
||||
bitStream->Read(target);
|
||||
LWOOBJID target{};
|
||||
if (!bitStream->Read(target)) {
|
||||
Game::logger->Log("ForceMovementBehavior", "Unable to read target from bitStream, aborting Sync! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
}
|
||||
|
||||
branch.target = target;
|
||||
auto* behavior = CreateBehavior(next);
|
||||
@@ -75,5 +86,5 @@ void ForceMovementBehavior::SyncCalculation(BehaviorContext* context, RakNet::Bi
|
||||
|
||||
this->m_hitAction->Calculate(context, bitStream, branch);
|
||||
this->m_hitEnemyAction->Calculate(context, bitStream, branch);
|
||||
this->m_hitEnemyAction->Calculate(context, bitStream, branch);
|
||||
this->m_hitFactionAction->Calculate(context, bitStream, branch);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ void InterruptBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitS
|
||||
if (branch.target != context->originator) {
|
||||
bool unknown = false;
|
||||
|
||||
bitStream->Read(unknown);
|
||||
if (!bitStream->Read(unknown)) {
|
||||
Game::logger->Log("InterruptBehavior", "Unable to read unknown1 from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
};
|
||||
|
||||
if (unknown) return;
|
||||
}
|
||||
@@ -19,7 +22,10 @@ void InterruptBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitS
|
||||
if (!this->m_interruptBlock) {
|
||||
bool unknown = false;
|
||||
|
||||
bitStream->Read(unknown);
|
||||
if (!bitStream->Read(unknown)) {
|
||||
Game::logger->Log("InterruptBehavior", "Unable to read unknown2 from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
};
|
||||
|
||||
if (unknown) return;
|
||||
}
|
||||
@@ -28,7 +34,10 @@ void InterruptBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitS
|
||||
{
|
||||
bool unknown = false;
|
||||
|
||||
bitStream->Read(unknown);
|
||||
if (!bitStream->Read(unknown)) {
|
||||
Game::logger->Log("InterruptBehavior", "Unable to read unknown3 from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
if (branch.target == context->originator) return;
|
||||
|
||||
@@ -6,11 +6,16 @@
|
||||
#include "EntityManager.h"
|
||||
#include "GameMessages.h"
|
||||
#include "DestroyableComponent.h"
|
||||
#include "Game.h"
|
||||
#include "dLogger.h"
|
||||
|
||||
void KnockbackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
|
||||
bool unknown;
|
||||
bool unknown{};
|
||||
|
||||
bitStream->Read(unknown);
|
||||
if (!bitStream->Read(unknown)) {
|
||||
Game::logger->Log("KnockbackBehavior", "Unable to read unknown from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
void KnockbackBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
|
||||
|
||||
@@ -13,9 +13,11 @@ void MovementSwitchBehavior::Handle(BehaviorContext* context, RakNet::BitStream*
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t movementType;
|
||||
|
||||
bitStream->Read(movementType);
|
||||
uint32_t movementType{};
|
||||
if (!bitStream->Read(movementType)) {
|
||||
Game::logger->Log("MovementSwitchBehavior", "Unable to read movementType from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
};
|
||||
|
||||
switch (movementType) {
|
||||
case 1:
|
||||
@@ -55,4 +57,3 @@ void MovementSwitchBehavior::Load() {
|
||||
|
||||
this->m_jumpAction = GetAction("jump_action");
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,12 @@
|
||||
#include "../dWorldServer/ObjectIDManager.h"
|
||||
|
||||
void ProjectileAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
|
||||
LWOOBJID target;
|
||||
LWOOBJID target{};
|
||||
|
||||
bitStream->Read(target);
|
||||
if (!bitStream->Read(target)) {
|
||||
Game::logger->Log("ProjectileAttackBehavior", "Unable to read target from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
};
|
||||
|
||||
auto* entity = EntityManager::Instance()->GetEntity(context->originator);
|
||||
|
||||
@@ -30,15 +33,21 @@ void ProjectileAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStrea
|
||||
|
||||
if (m_useMouseposit) {
|
||||
NiPoint3 targetPosition = NiPoint3::ZERO;
|
||||
bitStream->Read(targetPosition);
|
||||
if (!bitStream->Read(targetPosition)) {
|
||||
Game::logger->Log("ProjectileAttackBehavior", "Unable to read targetPosition from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
auto* targetEntity = EntityManager::Instance()->GetEntity(target);
|
||||
|
||||
for (auto i = 0u; i < this->m_projectileCount; ++i) {
|
||||
LWOOBJID projectileId;
|
||||
LWOOBJID projectileId{};
|
||||
|
||||
bitStream->Read(projectileId);
|
||||
if (!bitStream->Read(projectileId)) {
|
||||
Game::logger->Log("ProjectileAttackBehavior", "Unable to read projectileId from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
};
|
||||
|
||||
branch.target = target;
|
||||
branch.isProjectile = true;
|
||||
@@ -98,7 +107,7 @@ void ProjectileAttackBehavior::Calculate(BehaviorContext* context, RakNet::BitSt
|
||||
for (auto i = 0u; i < this->m_projectileCount; ++i) {
|
||||
auto id = static_cast<LWOOBJID>(ObjectIDManager::Instance()->GenerateObjectID());
|
||||
|
||||
id = GeneralUtils::SetBit(id, OBJECT_BIT_CLIENT);
|
||||
id = GeneralUtils::SetBit(id, OBJECT_BIT_SPAWNED);
|
||||
|
||||
bitStream->Write(id);
|
||||
|
||||
|
||||
21
dGame/dBehaviors/RemoveBuffBehavior.cpp
Normal file
21
dGame/dBehaviors/RemoveBuffBehavior.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "RemoveBuffBehavior.h"
|
||||
|
||||
#include "BehaviorBranchContext.h"
|
||||
#include "BehaviorContext.h"
|
||||
#include "EntityManager.h"
|
||||
#include "BuffComponent.h"
|
||||
|
||||
void RemoveBuffBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
|
||||
auto* entity = EntityManager::Instance()->GetEntity(context->caster);
|
||||
if (!entity) return;
|
||||
|
||||
auto* buffComponent = entity->GetComponent<BuffComponent>();
|
||||
if (!buffComponent) return;
|
||||
|
||||
buffComponent->RemoveBuff(m_BuffId, false, m_RemoveImmunity);
|
||||
}
|
||||
|
||||
void RemoveBuffBehavior::Load() {
|
||||
this->m_RemoveImmunity = GetBoolean("remove_immunity");
|
||||
this->m_BuffId = GetInt("buff_id");
|
||||
}
|
||||
22
dGame/dBehaviors/RemoveBuffBehavior.h
Normal file
22
dGame/dBehaviors/RemoveBuffBehavior.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "Behavior.h"
|
||||
|
||||
class RemoveBuffBehavior final : public Behavior
|
||||
{
|
||||
public:
|
||||
|
||||
/*
|
||||
* Inherited
|
||||
*/
|
||||
|
||||
explicit RemoveBuffBehavior(const uint32_t behaviorId) : Behavior(behaviorId) {
|
||||
}
|
||||
|
||||
void Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override;
|
||||
|
||||
void Load() override;
|
||||
|
||||
private:
|
||||
bool m_RemoveImmunity;
|
||||
uint32_t m_BuffId;
|
||||
};
|
||||
@@ -14,8 +14,11 @@ void StunBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream
|
||||
return;
|
||||
}
|
||||
|
||||
bool blocked;
|
||||
bitStream->Read(blocked);
|
||||
bool blocked{};
|
||||
if (!bitStream->Read(blocked)) {
|
||||
Game::logger->Log("StunBehavior", "Unable to read blocked from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
};
|
||||
|
||||
auto* target = EntityManager::Instance()->GetEntity(branch.target);
|
||||
|
||||
|
||||
@@ -10,7 +10,10 @@ void SwitchBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStre
|
||||
auto state = true;
|
||||
|
||||
if (this->m_imagination > 0 || !this->m_isEnemyFaction) {
|
||||
bitStream->Read(state);
|
||||
if (!bitStream->Read(state)) {
|
||||
Game::logger->Log("SwitchBehavior", "Unable to read state from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
auto* entity = EntityManager::Instance()->GetEntity(context->originator);
|
||||
@@ -25,7 +28,7 @@ void SwitchBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStre
|
||||
return;
|
||||
}
|
||||
|
||||
Game::logger->Log("SwitchBehavior", "[%i] State: (%d), imagination: (%i) / (%f)", entity->GetLOT(), state, destroyableComponent->GetImagination(), destroyableComponent->GetMaxImagination());
|
||||
Game::logger->LogDebug("SwitchBehavior", "[%i] State: (%d), imagination: (%i) / (%f)", entity->GetLOT(), state, destroyableComponent->GetImagination(), destroyableComponent->GetMaxImagination());
|
||||
|
||||
if (state || (entity->GetLOT() == 8092 && destroyableComponent->GetImagination() >= m_imagination)) {
|
||||
this->m_actionTrue->Handle(context, bitStream, branch);
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
#include "EntityManager.h"
|
||||
|
||||
|
||||
void SwitchMultipleBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bit_stream, BehaviorBranchContext branch) {
|
||||
float value;
|
||||
void SwitchMultipleBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
|
||||
float value{};
|
||||
|
||||
bit_stream->Read(value);
|
||||
if (!bitStream->Read(value)) {
|
||||
Game::logger->Log("SwitchMultipleBehavior", "Unable to read value from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
};
|
||||
|
||||
uint32_t trigger = 0;
|
||||
|
||||
@@ -30,10 +32,10 @@ void SwitchMultipleBehavior::Handle(BehaviorContext* context, RakNet::BitStream*
|
||||
|
||||
auto* behavior = this->m_behaviors.at(trigger).second;
|
||||
|
||||
behavior->Handle(context, bit_stream, branch);
|
||||
behavior->Handle(context, bitStream, branch);
|
||||
}
|
||||
|
||||
void SwitchMultipleBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bit_stream, BehaviorBranchContext branch) {
|
||||
void SwitchMultipleBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
@@ -20,12 +20,16 @@ void TacArcBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStre
|
||||
|
||||
bool hit = false;
|
||||
|
||||
bitStream->Read(hit);
|
||||
if (!bitStream->Read(hit)) {
|
||||
Game::logger->Log("TacArcBehavior", "Unable to read hit from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
};
|
||||
|
||||
if (this->m_checkEnv) {
|
||||
bool blocked = false;
|
||||
|
||||
bitStream->Read(blocked);
|
||||
if (!bitStream->Read(blocked)) {
|
||||
Game::logger->Log("TacArcBehavior", "Unable to read blocked from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
};
|
||||
|
||||
if (blocked) {
|
||||
this->m_blockedAction->Handle(context, bitStream, branch);
|
||||
@@ -37,7 +41,9 @@ void TacArcBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStre
|
||||
if (hit) {
|
||||
uint32_t count = 0;
|
||||
|
||||
bitStream->Read(count);
|
||||
if (!bitStream->Read(count)) {
|
||||
Game::logger->Log("TacArcBehavior", "Unable to read count from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
};
|
||||
|
||||
if (count > m_maxTargets && m_maxTargets > 0) {
|
||||
count = m_maxTargets;
|
||||
@@ -46,9 +52,11 @@ void TacArcBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStre
|
||||
std::vector<LWOOBJID> targets;
|
||||
|
||||
for (auto i = 0u; i < count; ++i) {
|
||||
LWOOBJID id;
|
||||
LWOOBJID id{};
|
||||
|
||||
bitStream->Read(id);
|
||||
if (!bitStream->Read(id)) {
|
||||
Game::logger->Log("TacArcBehavior", "Unable to read id from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
|
||||
};
|
||||
|
||||
targets.push_back(id);
|
||||
}
|
||||
|
||||
@@ -123,13 +123,15 @@ void BuffComponent::ApplyBuff(const int32_t id, const float duration, const LWOO
|
||||
m_Buffs.emplace(id, buff);
|
||||
}
|
||||
|
||||
void BuffComponent::RemoveBuff(int32_t id) {
|
||||
void BuffComponent::RemoveBuff(int32_t id, bool fromUnEquip, bool removeImmunity) {
|
||||
const auto& iter = m_Buffs.find(id);
|
||||
|
||||
if (iter == m_Buffs.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GameMessages::SendRemoveBuff(m_Parent, fromUnEquip, removeImmunity, id);
|
||||
|
||||
m_Buffs.erase(iter);
|
||||
|
||||
RemoveBuffEffect(id);
|
||||
|
||||
@@ -78,8 +78,9 @@ public:
|
||||
/**
|
||||
* Removes a buff from the parent entity, reversing its effects
|
||||
* @param id the id of the buff to remove
|
||||
* @param removeImmunity whether or not to remove immunity on removing the buff
|
||||
*/
|
||||
void RemoveBuff(int32_t id);
|
||||
void RemoveBuff(int32_t id, bool fromUnEquip = false, bool removeImmunity = false);
|
||||
|
||||
/**
|
||||
* Returns whether or not the entity has a buff identified by `id`
|
||||
|
||||
@@ -47,6 +47,7 @@ bool CharacterComponent::LandingAnimDisabled(int zoneID) {
|
||||
case 1202:
|
||||
case 1203:
|
||||
case 1204:
|
||||
case 1261:
|
||||
case 1301:
|
||||
case 1302:
|
||||
case 1303:
|
||||
|
||||
@@ -167,18 +167,6 @@ public:
|
||||
*/
|
||||
void SetLastRocketItemID(LWOOBJID lastRocketItemID) { m_LastRocketItemID = lastRocketItemID; }
|
||||
|
||||
/**
|
||||
* Gets the object ID of the mount item that is being used
|
||||
* @return the object ID of the mount item that is being used
|
||||
*/
|
||||
LWOOBJID GetMountItemID() const { return m_MountItemID; }
|
||||
|
||||
/**
|
||||
* Sets the object ID of the mount item that is being used
|
||||
* @param m_MountItemID the object ID of the mount item that is being used
|
||||
*/
|
||||
void SetMountItemID(LWOOBJID mountItemID) { m_MountItemID = mountItemID; }
|
||||
|
||||
/**
|
||||
* Gets the name of this character
|
||||
* @return the name of this character
|
||||
@@ -569,11 +557,6 @@ private:
|
||||
* ID of the last rocket used
|
||||
*/
|
||||
LWOOBJID m_LastRocketItemID = LWOOBJID_EMPTY;
|
||||
|
||||
/**
|
||||
* Mount Item ID
|
||||
*/
|
||||
LWOOBJID m_MountItemID = LWOOBJID_EMPTY;
|
||||
};
|
||||
|
||||
#endif // CHARACTERCOMPONENT_H
|
||||
|
||||
@@ -32,6 +32,7 @@ ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity) : Com
|
||||
m_IgnoreMultipliers = false;
|
||||
m_PickupRadius = 0.0f;
|
||||
m_DirtyPickupRadiusScale = true;
|
||||
m_IsTeleporting = false;
|
||||
|
||||
if (entity->GetLOT() != 1) // Other physics entities we care about will be added by BaseCombatAI
|
||||
return;
|
||||
@@ -128,7 +129,10 @@ void ControllablePhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bo
|
||||
outBitStream->Write0();
|
||||
}
|
||||
|
||||
if (!bIsInitialUpdate) outBitStream->Write0();
|
||||
if (!bIsInitialUpdate) {
|
||||
outBitStream->Write(m_IsTeleporting);
|
||||
m_IsTeleporting = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ControllablePhysicsComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
|
||||
|
||||
@@ -220,6 +220,18 @@ public:
|
||||
*/
|
||||
bool GetStatic() const { return m_Static; }
|
||||
|
||||
/**
|
||||
* Sets if the entity is Teleporting,
|
||||
* @param value whether or not the entity is Is Teleporting
|
||||
*/
|
||||
void SetIsTeleporting(const bool value) { m_IsTeleporting = value; }
|
||||
|
||||
/**
|
||||
* Returns whether or not this entity is currently is teleporting
|
||||
* @return whether or not this entity is currently is teleporting
|
||||
*/
|
||||
bool GetIsTeleporting() const { return m_IsTeleporting; }
|
||||
|
||||
/**
|
||||
* Returns the Physics entity for the component
|
||||
* @return Physics entity for the component
|
||||
@@ -355,6 +367,11 @@ private:
|
||||
* The active pickup radius for this entity
|
||||
*/
|
||||
float m_PickupRadius;
|
||||
|
||||
/**
|
||||
* If the entity is teleporting
|
||||
*/
|
||||
bool m_IsTeleporting;
|
||||
};
|
||||
|
||||
#endif // CONTROLLABLEPHYSICSCOMPONENT_H
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
#include "MissionComponent.h"
|
||||
#include "CharacterComponent.h"
|
||||
#include "PossessableComponent.h"
|
||||
#include "PossessorComponent.h"
|
||||
#include "dZoneManager.h"
|
||||
|
||||
DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
|
||||
@@ -136,25 +138,17 @@ void DestroyableComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsIn
|
||||
|
||||
if (m_IsSmashable) {
|
||||
outBitStream->Write(m_HasBricks);
|
||||
|
||||
if (m_ExplodeFactor != 1.0f) {
|
||||
outBitStream->Write1();
|
||||
outBitStream->Write(m_ExplodeFactor);
|
||||
} else {
|
||||
outBitStream->Write0();
|
||||
}
|
||||
outBitStream->Write(m_ExplodeFactor != 1.0f);
|
||||
if (m_ExplodeFactor != 1.0f) outBitStream->Write(m_ExplodeFactor);
|
||||
}
|
||||
}
|
||||
|
||||
m_DirtyHealth = false;
|
||||
}
|
||||
|
||||
outBitStream->Write(m_DirtyThreatList || bIsInitialUpdate);
|
||||
if (m_DirtyThreatList || bIsInitialUpdate) {
|
||||
outBitStream->Write1();
|
||||
outBitStream->Write(m_HasThreats);
|
||||
m_DirtyThreatList = false;
|
||||
} else {
|
||||
outBitStream->Write0();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,7 +430,6 @@ void DestroyableComponent::AddEnemyFaction(int32_t factionID) {
|
||||
void DestroyableComponent::SetIsSmashable(bool value) {
|
||||
m_DirtyHealth = true;
|
||||
m_IsSmashable = value;
|
||||
//m_HasBricks = value;
|
||||
}
|
||||
|
||||
void DestroyableComponent::SetAttacksToBlock(const uint32_t value) {
|
||||
@@ -608,6 +601,24 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
|
||||
SetHealth(health);
|
||||
SetIsShielded(absorb > 0);
|
||||
|
||||
// Dismount on the possessable hit
|
||||
auto possessable = m_Parent->GetComponent<PossessableComponent>();
|
||||
if (possessable && possessable->GetDepossessOnHit()) {
|
||||
possessable->Dismount();
|
||||
}
|
||||
|
||||
// Dismount on the possessor hit
|
||||
auto possessor = m_Parent->GetComponent<PossessorComponent>();
|
||||
if (possessor) {
|
||||
auto possessableId = possessor->GetPossessable();
|
||||
if (possessableId != LWOOBJID_EMPTY) {
|
||||
auto possessable = EntityManager::Instance()->GetEntity(possessableId);
|
||||
if (possessable) {
|
||||
possessor->Dismount(possessable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Parent->GetLOT() != 1) {
|
||||
echo = true;
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ public:
|
||||
* Sets the multiplier for the explosion that's visible when the bricks fly out when this entity is smashed
|
||||
* @param value the multiplier for the explosion that's visible when the bricks fly out when this entity is smashed
|
||||
*/
|
||||
void SetExplodeFactor(float value);
|
||||
void SetExplodeFactor(float value) { m_ExplodeFactor = value; };
|
||||
|
||||
/**
|
||||
* Returns the current multiplier for explosions
|
||||
@@ -414,6 +414,14 @@ public:
|
||||
*/
|
||||
void AddOnHitCallback(const std::function<void(Entity*)>& callback);
|
||||
|
||||
/**
|
||||
* Pushes a faction back to the list of factions.
|
||||
* @param value Faction to add to list.
|
||||
*
|
||||
* This method should only be used for testing. Use AddFaction(int32_t, bool) for adding a faction properly.
|
||||
*/
|
||||
void AddFactionNoLookup(int32_t faction) { m_FactionIDs.push_back(faction); };
|
||||
|
||||
private:
|
||||
/**
|
||||
* Whether or not the health should be serialized
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "DestroyableComponent.h"
|
||||
#include "dConfig.h"
|
||||
#include "eItemType.h"
|
||||
#include "eUnequippableActiveType.h"
|
||||
|
||||
InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* document) : Component(parent) {
|
||||
this->m_Dirty = true;
|
||||
@@ -62,23 +63,23 @@ InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* do
|
||||
const auto& info = Inventory::FindItemComponent(item.itemid);
|
||||
|
||||
UpdateSlot(info.equipLocation, { id, static_cast<LOT>(item.itemid), item.count, slot++ });
|
||||
|
||||
|
||||
// Equip this items proxies.
|
||||
auto subItems = info.subItems;
|
||||
|
||||
|
||||
subItems.erase(std::remove_if(subItems.begin(), subItems.end(), ::isspace), subItems.end());
|
||||
|
||||
|
||||
if (!subItems.empty()) {
|
||||
const auto subItemsSplit = GeneralUtils::SplitString(subItems, ',');
|
||||
|
||||
|
||||
for (auto proxyLotAsString : subItemsSplit) {
|
||||
const auto proxyLOT = static_cast<LOT>(std::stoi(proxyLotAsString));
|
||||
|
||||
|
||||
const auto& proxyInfo = Inventory::FindItemComponent(proxyLOT);
|
||||
const LWOOBJID proxyId = ObjectIDManager::Instance()->GenerateObjectID();
|
||||
|
||||
|
||||
// Use item.count since we equip item.count number of the item this is a requested proxy of
|
||||
UpdateSlot(proxyInfo.equipLocation, { proxyId, proxyLOT, item.count, slot++ } );
|
||||
UpdateSlot(proxyInfo.equipLocation, { proxyId, proxyLOT, item.count, slot++ });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,9 +209,11 @@ void InventoryComponent::AddItem(
|
||||
|
||||
auto stack = static_cast<uint32_t>(info.stackSize);
|
||||
|
||||
bool isBrick = inventoryType == eInventoryType::BRICKS || (stack == 0 && info.itemType == 1);
|
||||
|
||||
// info.itemType of 1 is item type brick
|
||||
if (inventoryType == eInventoryType::BRICKS || (stack == 0 && info.itemType == 1)) {
|
||||
stack = 999;
|
||||
if (isBrick) {
|
||||
stack = UINT32_MAX;
|
||||
} else if (stack == 0) {
|
||||
stack = 1;
|
||||
}
|
||||
@@ -231,7 +234,8 @@ void InventoryComponent::AddItem(
|
||||
}
|
||||
}
|
||||
|
||||
while (left > 0) {
|
||||
// If we have some leftover and we aren't bricks, make a new stack
|
||||
while (left > 0 && (!isBrick || (isBrick && !existing))) {
|
||||
const auto size = std::min(left, stack);
|
||||
|
||||
left -= size;
|
||||
@@ -326,7 +330,9 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in
|
||||
|
||||
const auto lot = item->GetLot();
|
||||
|
||||
if (item->GetConfig().empty() && !item->GetBound() || (item->GetBound() && item->GetInfo().isBOP)) {
|
||||
const auto subkey = item->GetSubKey();
|
||||
|
||||
if (subkey == LWOOBJID_EMPTY && item->GetConfig().empty() && (!item->GetBound() || (item->GetBound() && item->GetInfo().isBOP))) {
|
||||
auto left = std::min<uint32_t>(count, origin->GetLotCount(lot));
|
||||
|
||||
while (left > 0) {
|
||||
@@ -357,7 +363,7 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in
|
||||
|
||||
const auto delta = std::min<uint32_t>(item->GetCount(), count);
|
||||
|
||||
AddItem(lot, delta, eLootSourceType::LOOT_SOURCE_NONE, inventory, config, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, LWOOBJID_EMPTY, origin->GetType(), 0, item->GetBound(), preferredSlot);
|
||||
AddItem(lot, delta, eLootSourceType::LOOT_SOURCE_NONE, inventory, config, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, subkey, origin->GetType(), 0, item->GetBound(), preferredSlot);
|
||||
|
||||
item->SetCount(item->GetCount() - delta, false, false);
|
||||
}
|
||||
@@ -604,16 +610,17 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Inventory*> inventories;
|
||||
std::vector<Inventory*> inventoriesToSave;
|
||||
|
||||
// Need to prevent some transfer inventories from being saved
|
||||
for (const auto& pair : this->m_Inventories) {
|
||||
auto* inventory = pair.second;
|
||||
|
||||
if (inventory->GetType() == VENDOR_BUYBACK) {
|
||||
if (inventory->GetType() == VENDOR_BUYBACK || inventory->GetType() == eInventoryType::MODELS_IN_BBB) {
|
||||
continue;
|
||||
}
|
||||
|
||||
inventories.push_back(inventory);
|
||||
inventoriesToSave.push_back(inventory);
|
||||
}
|
||||
|
||||
inventoryElement->SetAttribute("csl", m_Consumable);
|
||||
@@ -628,7 +635,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) {
|
||||
|
||||
bags->DeleteChildren();
|
||||
|
||||
for (const auto* inventory : inventories) {
|
||||
for (const auto* inventory : inventoriesToSave) {
|
||||
auto* bag = document->NewElement("b");
|
||||
|
||||
bag->SetAttribute("t", inventory->GetType());
|
||||
@@ -647,7 +654,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) {
|
||||
|
||||
items->DeleteChildren();
|
||||
|
||||
for (auto* inventory : inventories) {
|
||||
for (auto* inventory : inventoriesToSave) {
|
||||
if (inventory->GetSize() == 0) {
|
||||
continue;
|
||||
}
|
||||
@@ -795,9 +802,7 @@ void InventoryComponent::RemoveSlot(const std::string& location) {
|
||||
}
|
||||
|
||||
void InventoryComponent::EquipItem(Item* item, const bool skipChecks) {
|
||||
if (!Inventory::IsValidItem(item->GetLot())) {
|
||||
return;
|
||||
}
|
||||
if (!Inventory::IsValidItem(item->GetLot())) return;
|
||||
|
||||
// Temp items should be equippable but other transfer items shouldn't be (for example the instruments in RB)
|
||||
if (item->IsEquipped()
|
||||
@@ -820,9 +825,7 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks) {
|
||||
|
||||
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
|
||||
|
||||
if (characterComponent != nullptr) {
|
||||
characterComponent->SetLastRocketItemID(item->GetId());
|
||||
}
|
||||
if (characterComponent != nullptr) characterComponent->SetLastRocketItemID(item->GetId());
|
||||
|
||||
lauchPad->OnUse(m_Parent);
|
||||
|
||||
@@ -836,94 +839,8 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks) {
|
||||
|
||||
const auto type = static_cast<eItemType>(item->GetInfo().itemType);
|
||||
|
||||
if (item->GetLot() == 8092 && m_Parent->GetGMLevel() >= GAME_MASTER_LEVEL_OPERATOR && hasCarEquipped == false) {
|
||||
auto startPosition = m_Parent->GetPosition();
|
||||
|
||||
auto startRotation = NiQuaternion::LookAt(startPosition, startPosition + NiPoint3::UNIT_X);
|
||||
auto angles = startRotation.GetEulerAngles();
|
||||
angles.y -= PI;
|
||||
startRotation = NiQuaternion::FromEulerAngles(angles);
|
||||
|
||||
GameMessages::SendTeleport(m_Parent->GetObjectID(), startPosition, startRotation, m_Parent->GetSystemAddress(), true, true);
|
||||
|
||||
EntityInfo info{};
|
||||
info.lot = 8092;
|
||||
info.pos = startPosition;
|
||||
info.rot = startRotation;
|
||||
info.spawnerID = m_Parent->GetObjectID();
|
||||
|
||||
auto* carEntity = EntityManager::Instance()->CreateEntity(info, nullptr, m_Parent);
|
||||
m_Parent->AddChild(carEntity);
|
||||
|
||||
auto* destroyableComponent = carEntity->GetComponent<DestroyableComponent>();
|
||||
|
||||
// Setup the vehicle stats.
|
||||
if (destroyableComponent != nullptr) {
|
||||
destroyableComponent->SetIsSmashable(false);
|
||||
destroyableComponent->SetIsImmune(true);
|
||||
}
|
||||
// #108
|
||||
auto* possessableComponent = carEntity->GetComponent<PossessableComponent>();
|
||||
|
||||
if (possessableComponent != nullptr) {
|
||||
previousPossessableID = possessableComponent->GetPossessor();
|
||||
possessableComponent->SetPossessor(m_Parent->GetObjectID());
|
||||
}
|
||||
|
||||
auto* moduleAssemblyComponent = carEntity->GetComponent<ModuleAssemblyComponent>();
|
||||
|
||||
if (moduleAssemblyComponent != nullptr) {
|
||||
moduleAssemblyComponent->SetSubKey(item->GetSubKey());
|
||||
moduleAssemblyComponent->SetUseOptionalParts(false);
|
||||
|
||||
for (auto* config : item->GetConfig()) {
|
||||
if (config->GetKey() == u"assemblyPartLOTs") {
|
||||
moduleAssemblyComponent->SetAssemblyPartsLOTs(GeneralUtils::ASCIIToUTF16(config->GetValueAsString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
// #107
|
||||
auto* possessorComponent = m_Parent->GetComponent<PossessorComponent>();
|
||||
|
||||
if (possessorComponent) possessorComponent->SetPossessable(carEntity->GetObjectID());
|
||||
|
||||
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
|
||||
|
||||
if (characterComponent) characterComponent->SetIsRacing(true);
|
||||
|
||||
EntityManager::Instance()->ConstructEntity(carEntity);
|
||||
EntityManager::Instance()->SerializeEntity(m_Parent);
|
||||
GameMessages::SendSetJetPackMode(m_Parent, false);
|
||||
|
||||
GameMessages::SendNotifyVehicleOfRacingObject(carEntity->GetObjectID(), m_Parent->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
|
||||
GameMessages::SendRacingPlayerLoaded(LWOOBJID_EMPTY, m_Parent->GetObjectID(), carEntity->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
|
||||
GameMessages::SendVehicleUnlockInput(carEntity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS);
|
||||
GameMessages::SendTeleport(m_Parent->GetObjectID(), startPosition, startRotation, m_Parent->GetSystemAddress(), true, true);
|
||||
GameMessages::SendTeleport(carEntity->GetObjectID(), startPosition, startRotation, m_Parent->GetSystemAddress(), true, true);
|
||||
EntityManager::Instance()->SerializeEntity(m_Parent);
|
||||
|
||||
hasCarEquipped = true;
|
||||
equippedCarEntity = carEntity;
|
||||
return;
|
||||
} else if (item->GetLot() == 8092 && m_Parent->GetGMLevel() >= GAME_MASTER_LEVEL_OPERATOR && hasCarEquipped == true) {
|
||||
GameMessages::SendNotifyRacingClient(LWOOBJID_EMPTY, 3, 0, LWOOBJID_EMPTY, u"", m_Parent->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
|
||||
auto player = dynamic_cast<Player*>(m_Parent);
|
||||
player->SendToZone(player->GetCharacter()->GetZoneID());
|
||||
equippedCarEntity->Kill();
|
||||
hasCarEquipped = false;
|
||||
equippedCarEntity = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!building) {
|
||||
if (item->GetLot() == 6086) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == eItemType::ITEM_TYPE_LOOT_MODEL || type == eItemType::ITEM_TYPE_VEHICLE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!building && (item->GetLot() == 6086 || type == eItemType::ITEM_TYPE_LOOT_MODEL || type == eItemType::ITEM_TYPE_VEHICLE)) return;
|
||||
|
||||
if (type != eItemType::ITEM_TYPE_LOOT_MODEL && type != eItemType::ITEM_TYPE_MODEL) {
|
||||
if (!item->GetBound() && !item->GetPreconditionExpression()->Check(m_Parent)) {
|
||||
@@ -940,9 +857,7 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks) {
|
||||
set->OnEquip(lot);
|
||||
}
|
||||
|
||||
if (item->GetInfo().isBOE) {
|
||||
item->SetBound(true);
|
||||
}
|
||||
if (item->GetInfo().isBOE) item->SetBound(true);
|
||||
|
||||
GenerateProxies(item);
|
||||
|
||||
@@ -989,6 +904,85 @@ void InventoryComponent::UnEquipItem(Item* item) {
|
||||
}
|
||||
}
|
||||
|
||||
void InventoryComponent::HandlePossession(Item* item) {
|
||||
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
|
||||
if (!characterComponent) return;
|
||||
|
||||
auto* possessorComponent = m_Parent->GetComponent<PossessorComponent>();
|
||||
if (!possessorComponent) return;
|
||||
|
||||
// Don't do anything if we are busy dismounting
|
||||
if (possessorComponent->GetIsDismounting()) return;
|
||||
|
||||
// Check to see if we are already mounting something
|
||||
auto* currentlyPossessedEntity = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable());
|
||||
auto currentlyPossessedItem = possessorComponent->GetMountItemID();
|
||||
|
||||
if (currentlyPossessedItem) {
|
||||
if (currentlyPossessedEntity) possessorComponent->Dismount(currentlyPossessedEntity);
|
||||
return;
|
||||
}
|
||||
|
||||
GameMessages::SendSetStunned(m_Parent->GetObjectID(), eStunState::PUSH, m_Parent->GetSystemAddress(), LWOOBJID_EMPTY, true, false, true, false, false, false, false, true, true, true, true, true, true, true, true, true);
|
||||
|
||||
// Set the mount Item ID so that we know what were handling
|
||||
possessorComponent->SetMountItemID(item->GetId());
|
||||
GameMessages::SendSetMountInventoryID(m_Parent, item->GetId(), UNASSIGNED_SYSTEM_ADDRESS);
|
||||
|
||||
// Create entity to mount
|
||||
auto startRotation = m_Parent->GetRotation();
|
||||
|
||||
EntityInfo info{};
|
||||
info.lot = item->GetLot();
|
||||
info.pos = m_Parent->GetPosition();
|
||||
info.rot = startRotation;
|
||||
info.spawnerID = m_Parent->GetObjectID();
|
||||
|
||||
auto* mount = EntityManager::Instance()->CreateEntity(info, nullptr, m_Parent);
|
||||
|
||||
// Check to see if the mount is a vehicle, if so, flip it
|
||||
auto* vehicleComponent = mount->GetComponent<VehiclePhysicsComponent>();
|
||||
if (vehicleComponent) {
|
||||
auto angles = startRotation.GetEulerAngles();
|
||||
// Make it right side up
|
||||
angles.x -= PI;
|
||||
// Make it going in the direction of the player
|
||||
angles.y -= PI;
|
||||
startRotation = NiQuaternion::FromEulerAngles(angles);
|
||||
mount->SetRotation(startRotation);
|
||||
// We're pod racing now
|
||||
characterComponent->SetIsRacing(true);
|
||||
}
|
||||
|
||||
// Setup the destroyable stats
|
||||
auto* destroyableComponent = mount->GetComponent<DestroyableComponent>();
|
||||
if (destroyableComponent) {
|
||||
destroyableComponent->SetIsSmashable(false);
|
||||
destroyableComponent->SetIsImmune(true);
|
||||
}
|
||||
|
||||
// Mount it
|
||||
auto* possessableComponent = mount->GetComponent<PossessableComponent>();
|
||||
if (possessableComponent) {
|
||||
possessableComponent->SetIsItemSpawned(true);
|
||||
possessableComponent->SetPossessor(m_Parent->GetObjectID());
|
||||
// Possess it
|
||||
possessorComponent->SetPossessable(mount->GetObjectID());
|
||||
possessorComponent->SetPossessableType(possessableComponent->GetPossessionType());
|
||||
}
|
||||
|
||||
GameMessages::SendSetJetPackMode(m_Parent, false);
|
||||
|
||||
// Make it go to the client
|
||||
EntityManager::Instance()->ConstructEntity(mount);
|
||||
// Update the possessor
|
||||
EntityManager::Instance()->SerializeEntity(m_Parent);
|
||||
|
||||
// have to unlock the input so it vehicle can be driven
|
||||
if (vehicleComponent) GameMessages::SendVehicleUnlockInput(mount->GetObjectID(), false, m_Parent->GetSystemAddress());
|
||||
GameMessages::SendMarkInventoryItemAsActive(m_Parent->GetObjectID(), true, eUnequippableActiveType::MOUNT, item->GetId(), m_Parent->GetSystemAddress());
|
||||
}
|
||||
|
||||
void InventoryComponent::ApplyBuff(Item* item) const {
|
||||
const auto buffs = FindBuffs(item, true);
|
||||
|
||||
@@ -997,6 +991,7 @@ void InventoryComponent::ApplyBuff(Item* item) const {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Something needs to send the remove buff GameMessage as well when it is unequipping items that would remove buffs.
|
||||
void InventoryComponent::RemoveBuff(Item* item) const {
|
||||
const auto buffs = FindBuffs(item, false);
|
||||
|
||||
@@ -1270,7 +1265,7 @@ BehaviorSlot InventoryComponent::FindBehaviorSlot(const eItemType type) {
|
||||
}
|
||||
|
||||
bool InventoryComponent::IsTransferInventory(eInventoryType type) {
|
||||
return type == VENDOR_BUYBACK || type == VAULT_ITEMS || type == VAULT_MODELS || type == TEMP_ITEMS || type == TEMP_MODELS;
|
||||
return type == VENDOR_BUYBACK || type == VAULT_ITEMS || type == VAULT_MODELS || type == TEMP_ITEMS || type == TEMP_MODELS || type == MODELS_IN_BBB;
|
||||
}
|
||||
|
||||
uint32_t InventoryComponent::FindSkill(const LOT lot) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user