Merge branch 'main' into scripting-lua

This commit is contained in:
Jett 2022-07-17 16:15:17 +01:00 committed by GitHub
commit e37f1bcee3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
328 changed files with 4228 additions and 2250 deletions

View File

@ -12,18 +12,21 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ windows-2022, ubuntu-20.04 ]
os: [ windows-2022, ubuntu-20.04, macos-11 ]
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Add msbuild to PATH (windows only)
- name: Add msbuild to PATH (Windows only)
if: ${{ matrix.os == 'windows-2022' }}
uses: microsoft/setup-msbuild@v1.1
with:
vs-version: '[17,18)'
msbuild-architecture: x64
- name: Install libssl (Mac Only)
if: ${{ matrix.os == 'macos-11' }}
run: brew install openssl@3
- name: cmake
uses: lukka/run-cmake@v10
with:
@ -32,6 +35,7 @@ jobs:
testPreset: "ci-${{matrix.os}}"
- name: artifacts
uses: actions/upload-artifact@v2
if: ${{ github.ref == 'ref/head/main' }}
with:
name: build-${{matrix.os}}
path: |

4
.gitmodules vendored
View File

@ -10,6 +10,10 @@
[submodule "thirdparty/libbcrypt"]
path = thirdparty/libbcrypt
url = https://github.com/trusch/libbcrypt.git
[submodule "thirdparty/mariadb-connector-cpp"]
path = thirdparty/mariadb-connector-cpp
url = https://github.com/mariadb-corporation/mariadb-connector-cpp.git
ignore = dirty
[submodule "thirdparty/docker-utils"]
path = thirdparty/docker-utils
url = https://github.com/lcdr/utils.git

View File

@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.14)
project(Darkflame)
include(CTest)
set (CMAKE_CXX_STANDARD 17)
# Read variables from file
FILE(READ "${CMAKE_SOURCE_DIR}/CMakeVariables.txt" variables)
@ -11,18 +13,14 @@ string(REPLACE "\n" ";" variables ${variables})
# Set the cmake variables, formatted as "VARIABLE #" in variables
foreach(variable ${variables})
# If the string contains a #, skip it
if("${variable}" MATCHES "#")
continue()
endif()
if(NOT "${variable}" MATCHES "#")
# Split the variable into name and value
string(REPLACE "=" ";" variable ${variable})
# Check that the length of the variable is 2 (name and value)
list(LENGTH variable length)
if(NOT ${length} EQUAL 2)
continue()
endif()
if(${length} EQUAL 2)
list(GET variable 0 variable_name)
list(GET variable 1 variable_value)
@ -34,89 +32,47 @@ foreach(variable ${variables})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D${variable_name}=${variable_value}")
message(STATUS "Variable: ${variable_name} = ${variable_value}")
endforeach()
# On windows it's better to build this from source, as there's no way FindZLIB is gonna find it
if(NOT WIN32)
find_package(ZLIB REQUIRED)
endif()
# Fetch External (Non-Submodule) Libraries
if(WIN32)
include(FetchContent)
FetchContent_Declare(
mysql
URL https://dev.mysql.com/get/Downloads/Connector-C++/mysql-connector-c++-8.0.27-winx64.zip
URL_HASH MD5=e3c53f6e4d0a72fde2713f7597bf9468
)
FetchContent_Declare(
zlib
URL https://github.com/madler/zlib/archive/refs/tags/v1.2.11.zip
URL_HASH MD5=9d6a627693163bbbf3f26403a3a0b0b1
)
FetchContent_MakeAvailable(zlib)
FetchContent_MakeAvailable(mysql)
set(ZLIB_INCLUDE_DIRS ${zlib_SOURCE_DIR} ${zlib_BINARY_DIR})
set_target_properties(zlib PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIRS}") # Why?
add_library(ZLIB::ZLIB ALIAS zlib) # You're welcome
endif(WIN32)
if(UNIX)
if(APPLE)
else()
include(FetchContent)
FetchContent_Declare(
mysql
URL https://dev.mysql.com/get/Downloads/Connector-C++/mysql-connector-c++-8.0.27-linux-glibc2.12-x86-64bit.tar.gz
URL_HASH MD5=12f086b76c11022cc7139b41a36cdf9e
)
FetchContent_MakeAvailable(mysql)
if (__include_backtrace__ AND __compile_backtrace__)
FetchContent_Declare(
backtrace
GIT_REPOSITORY https://github.com/ianlancetaylor/libbacktrace.git
)
FetchContent_MakeAvailable(backtrace)
if (NOT EXISTS ${backtrace_SOURCE_DIR}/.libs)
set(backtrace_make_cmd "${backtrace_SOURCE_DIR}/configure --prefix=\"/usr\" --enable-shared --with-system-libunwind")
execute_process(
COMMAND bash -c "cd ${backtrace_SOURCE_DIR} && ${backtrace_make_cmd} && make && cd ${CMAKE_SOURCE_DIR}"
)
endif()
link_directories(${backtrace_SOURCE_DIR}/.libs/)
include_directories(${backtrace_SOURCE_DIR})
endif(__include_backtrace__)
endif()
endif(UNIX)
endif()
endforeach()
# Set the version
set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DPROJECT_VERSION=${PROJECT_VERSION}")
# Echo the version
message(STATUS "Version: ${PROJECT_VERSION}")
set(CMAKE_CXX_STANDARD 17)
if(WIN32)
# Compiler flags:
# Disabled deprecated warnings as the MySQL includes have deprecated code in them.
# Disabled misleading indentation as DL_LinkedList from RakNet has a weird indent.
# Disabled no-register
# Disabled unknown pragmas because Linux doesn't understand Windows pragmas.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DPROJECT_VERSION=${PROJECT_VERSION}")
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")
endif()
if (__dynamic)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -rdynamic")
endif()
if (__ggdb)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb")
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -O2 -fPIC")
elseif(MSVC)
# Skip warning for invalid conversion from size_t to uint32_t for all targets below for now
add_compile_options("/wd4267")
elseif(WIN32)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
endif(WIN32)
endif()
# Our output dir
set(CMAKE_BINARY_DIR ${PROJECT_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})
# Create a /res directory
make_directory(${CMAKE_BINARY_DIR}/res)
@ -128,86 +84,82 @@ make_directory(${CMAKE_BINARY_DIR}/locale)
make_directory(${CMAKE_BINARY_DIR}/logs)
# Copy ini files on first build
if (NOT EXISTS ${PROJECT_BINARY_DIR}/authconfig.ini)
set(INI_FILES "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini")
foreach(ini ${INI_FILES})
if (NOT EXISTS ${PROJECT_BINARY_DIR}/${ini})
configure_file(
${CMAKE_SOURCE_DIR}/resources/authconfig.ini ${PROJECT_BINARY_DIR}/authconfig.ini
${CMAKE_SOURCE_DIR}/resources/${ini} ${PROJECT_BINARY_DIR}/${ini}
COPYONLY
)
endif()
if (NOT EXISTS ${PROJECT_BINARY_DIR}/chatconfig.ini)
endif()
endforeach()
# Copy vanity files on first build
set(VANITY_FILES "CREDITS.md" "INFO.md" "TESTAMENT.md" "NPC.xml")
foreach(file ${VANITY_FILES})
configure_file("${CMAKE_SOURCE_DIR}/vanity/${file}" "${CMAKE_BINARY_DIR}/vanity/${file}" COPYONLY)
endforeach()
# Move our migrations for MasterServer to run
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/migrations/)
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})
configure_file(
${CMAKE_SOURCE_DIR}/resources/chatconfig.ini ${PROJECT_BINARY_DIR}/chatconfig.ini
${CMAKE_SOURCE_DIR}/migrations/dlu/${file} ${PROJECT_BINARY_DIR}/migrations/${file}
COPYONLY
)
endif()
if (NOT EXISTS ${PROJECT_BINARY_DIR}/worldconfig.ini)
configure_file(
${CMAKE_SOURCE_DIR}/resources/worldconfig.ini ${PROJECT_BINARY_DIR}/worldconfig.ini
COPYONLY
)
endif()
if (NOT EXISTS ${PROJECT_BINARY_DIR}/masterconfig.ini)
configure_file(
${CMAKE_SOURCE_DIR}/resources/masterconfig.ini ${PROJECT_BINARY_DIR}/masterconfig.ini
COPYONLY
endif()
endforeach()
# Create our list of include directories
set(INCLUDED_DIRECTORIES
"dCommon"
"dChatFilter"
"dGame"
"dGame/dBehaviors"
"dGame/dComponents"
"dGame/dGameMessages"
"dGame/dInventory"
"dGame/dMission"
"dGame/dEntity"
"dGame/dUtilities"
"dPhysics"
"dZoneManager"
"dDatabase"
"dDatabase/Tables"
"dNet"
"dScripts"
"thirdparty/raknet/Source"
"thirdparty/tinyxml2"
"thirdparty/recastnavigation/Recast/Include"
"thirdparty/recastnavigation/Detour/Include"
"thirdparty/SQLite"
"thirdparty/cpplinq"
)
# Add system specfic includes for Apple, Windows and Other Unix OS' (including Linux)
if (APPLE)
include_directories("/usr/local/include/")
endif()
# Copy files to output
configure_file("${CMAKE_SOURCE_DIR}/vanity/CREDITS.md" "${CMAKE_BINARY_DIR}/vanity/CREDITS.md" COPYONLY)
configure_file("${CMAKE_SOURCE_DIR}/vanity/INFO.md" "${CMAKE_BINARY_DIR}/vanity/INFO.md" COPYONLY)
configure_file("${CMAKE_SOURCE_DIR}/vanity/TESTAMENT.md" "${CMAKE_BINARY_DIR}/vanity/TESTAMENT.md" COPYONLY)
configure_file("${CMAKE_SOURCE_DIR}/vanity/NPC.xml" "${CMAKE_BINARY_DIR}/vanity/NPC.xml" COPYONLY)
if (WIN32)
set(INCLUDED_DIRECTORIES ${INCLUDED_DIRECTORIES} "thirdparty/libbcrypt/include")
elseif (UNIX)
set(INCLUDED_DIRECTORIES ${INCLUDED_DIRECTORIES} "thirdparty/libbcrypt")
set(INCLUDED_DIRECTORIES ${INCLUDED_DIRECTORIES} "thirdparty/libbcrypt/include/bcrypt")
endif()
# 3rdparty includes
include_directories(${PROJECT_SOURCE_DIR}/thirdparty/raknet/Source/)
if(UNIX)
if(APPLE)
include_directories(/usr/local/include/)
include_directories(/usr/local/mysql-connector-c++/include/jdbc/)
include_directories(/usr/local/mysql-connector-c++/include/jdbc/cppconn/)
else()
include_directories(${mysql_SOURCE_DIR}/include/jdbc/)
include_directories(${mysql_SOURCE_DIR}/include/jdbc/cppconn/)
endif(APPLE)
endif(UNIX)
if(WIN32)
include_directories(${mysql_SOURCE_DIR}/include/jdbc)
include_directories(${mysql_SOURCE_DIR}/include/jdbc/cppconn)
endif(WIN32)
include_directories(${PROJECT_SOURCE_DIR}/thirdparty/tinyxml2/)
include_directories(${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Recast/Include)
include_directories(${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Detour/Include)
include_directories(${ZLIB_INCLUDE_DIRS})
# Bcrypt
if (NOT WIN32)
include_directories(${PROJECT_SOURCE_DIR}/thirdparty/libbcrypt)
include_directories(${PROJECT_SOURCE_DIR}/thirdparty/libbcrypt/include/bcrypt)
else ()
include_directories(${PROJECT_SOURCE_DIR}/thirdparty/libbcrypt/include)
endif ()
# Our includes
# Add binary directory as an include directory
include_directories(${PROJECT_BINARY_DIR})
include_directories(${PROJECT_SOURCE_DIR}/dChatFilter/)
include_directories(${PROJECT_SOURCE_DIR}/dCommon/)
include_directories(${PROJECT_SOURCE_DIR}/dGame/)
include_directories(${PROJECT_SOURCE_DIR}/dGame/dBehaviors)
include_directories(${PROJECT_SOURCE_DIR}/dGame/dComponents)
include_directories(${PROJECT_SOURCE_DIR}/dGame/dGameMessages)
include_directories(${PROJECT_SOURCE_DIR}/dGame/dInventory)
include_directories(${PROJECT_SOURCE_DIR}/dGame/dMission)
include_directories(${PROJECT_SOURCE_DIR}/dGame/dEntity)
include_directories(${PROJECT_SOURCE_DIR}/dGame/dUtilities)
include_directories(${PROJECT_SOURCE_DIR}/dPhysics/)
include_directories(${PROJECT_SOURCE_DIR}/dZoneManager/)
include_directories(${PROJECT_SOURCE_DIR}/dDatabase/)
include_directories(${PROJECT_SOURCE_DIR}/dDatabase/Tables/)
include_directories(${PROJECT_SOURCE_DIR}/thirdparty/SQLite/)
include_directories(${PROJECT_SOURCE_DIR}/thirdparty/cpplinq/)
include_directories(${PROJECT_SOURCE_DIR}/dNet/)
include_directories(${PROJECT_SOURCE_DIR}/dScripts/)
# Actually include the directories from our list
foreach (dir ${INCLUDED_DIRECTORIES})
include_directories(${PROJECT_SOURCE_DIR}/${dir})
endforeach()
# Link dGame to LUA
if(__include_lua__)
@ -217,311 +169,116 @@ if(__include_lua__)
include_directories(${PROJECT_SOURCE_DIR}/dLua/)
endif(UNIX)
# Default to linking to libmysql
set(MYSQL_LIB mysql)
if(WIN32)
set(MYSQL_LIB mysqlcppconn)
endif(WIN32)
# Lib folders:
# Add linking directories:
link_directories(${PROJECT_BINARY_DIR})
if(UNIX)
if(APPLE)
link_directories(/usr/local/mysql-connector-c++/lib64/)
else()
link_directories(${mysql_SOURCE_DIR}/lib64/)
# Link to libmysqlcppconn on Linux
set(MYSQL_LIB mysqlcppconn)
endif(APPLE)
endif(UNIX)
if(WIN32)
link_directories(${mysql_SOURCE_DIR}/lib64/vs14)
endif(WIN32)
# Load all of our third party directories
add_subdirectory(thirdparty)
# Source Code
# Glob together all headers that need to be precompiled
file(
GLOB SOURCES
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/dWorldServer/*.cpp
GLOB HEADERS_DDATABASE
LIST_DIRECTORIES false
${PROJECT_SOURCE_DIR}/dDatabase/*.h
${PROJECT_SOURCE_DIR}/dDatabase/Tables/*.h
${PROJECT_SOURCE_DIR}/thirdparty/SQLite/*.h
)
# Source Code for AuthServer
file(
GLOB SOURCES_AUTH
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/dAuthServer/*.cpp
GLOB HEADERS_DZONEMANAGER
LIST_DIRECTORIES false
${PROJECT_SOURCE_DIR}/dZoneManager/*.h
)
# Source Code for MasterServer
file(
GLOB SOURCES_MASTER
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/dMasterServer/*.cpp
GLOB HEADERS_DCOMMON
LIST_DIRECTORIES false
${PROJECT_SOURCE_DIR}/dCommon/*.h
)
# Source Code for ChatServer
file(
GLOB SOURCES_CHAT
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/dChatServer/*.cpp
GLOB HEADERS_DGAME
LIST_DIRECTORIES false
${PROJECT_SOURCE_DIR}/dGame/Entity.h
${PROJECT_SOURCE_DIR}/dGame/dGameMessages/GameMessages.h
${PROJECT_SOURCE_DIR}/dGame/EntityManager.h
${PROJECT_SOURCE_DIR}/dScripts/CppScripts.h
)
# Source Code for raknet
file(
GLOB SOURCES_RAKNET
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/thirdparty/raknet/Source/*.cpp
# Add our library subdirectories for creation of the library object
add_subdirectory(dCommon)
add_subdirectory(dDatabase)
add_subdirectory(dChatFilter)
add_subdirectory(dNet)
add_subdirectory(dScripts) # Add for dGame to use
add_subdirectory(dGame)
add_subdirectory(dZoneManager)
add_subdirectory(dPhysics)
# Create a list of common libraries shared between all binaries
set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "mariadbConnCpp")
# Add platform specific common libraries
if (UNIX)
set(COMMON_LIBRARIES ${COMMON_LIBRARIES} "dl" "pthread")
if (NOT APPLE AND __include_backtrace__)
set(COMMON_LIBRARIES ${COMMON_LIBRARIES} "backtrace")
endif()
endif()
add_subdirectory(tests)
# Include all of our binary directories
add_subdirectory(dWorldServer)
add_subdirectory(dAuthServer)
add_subdirectory(dChatServer)
add_subdirectory(dMasterServer) # Add MasterServer last so it can rely on the other binaries
# Add our precompiled headers
target_precompile_headers(
dGame PRIVATE
${HEADERS_DGAME}
)
# Source Code for recast
file(
GLOB SOURCES_RECAST
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Recast/Source/*.cpp
)
# Source Code for detour
file(
GLOB SOURCES_DETOUR
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/thirdparty/recastnavigation/Detour/Source/*.cpp
)
# Source Code for tinyxml2
file(
GLOB SOURCES_TINYXML2
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/thirdparty/tinyxml2/tinyxml2.cpp
)
# Source Code for libbcrypt
file(
GLOB SOURCES_LIBBCRYPT
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/thirdparty/libbcrypt/*.c
${PROJECT_SOURCE_DIR}/thirdparty/libbcrypt/src/*.c
)
# Source Code for dCommon
file(
GLOB SOURCES_DCOMMON
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/dCommon/*.cpp
)
# Source Code for dChatFilter
file(
GLOB SOURCES_DCHATFILTER
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/dChatFilter/*.cpp
)
# Source Code for dDatabase
file(
GLOB SOURCES_DDATABASE
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/dDatabase/*.cpp
${PROJECT_SOURCE_DIR}/dDatabase/Tables/*.cpp
${PROJECT_SOURCE_DIR}/thirdparty/SQLite/*.cpp
${PROJECT_SOURCE_DIR}/thirdparty/SQLite/*.c
)
# Source Code for dNet
file(
GLOB SOURCES_DNET
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/dNet/*.cpp
)
# Source Code for dGame
file(
GLOB SOURCES_DGAME
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/dGame/*.cpp
${PROJECT_SOURCE_DIR}/dGame/dBehaviors/*.cpp
${PROJECT_SOURCE_DIR}/dGame/dComponents/*.cpp
${PROJECT_SOURCE_DIR}/dGame/dGameMessages/*.cpp
${PROJECT_SOURCE_DIR}/dGame/dInventory/*.cpp
${PROJECT_SOURCE_DIR}/dGame/dMission/*.cpp
${PROJECT_SOURCE_DIR}/dGame/dEntity/*.cpp
${PROJECT_SOURCE_DIR}/dGame/dUtilities/*.cpp
${PROJECT_SOURCE_DIR}/dScripts/*.cpp
target_precompile_headers(
dZoneManager PRIVATE
${HEADERS_DZONEMANAGER}
)
# If we are including LUA, include the dLua files in dGame
if(__include_lua__)
file(
GLOB SOURCES_DLUA
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/dLua/*.cpp
)
#if(__include_lua__)
# file(
# GLOB SOURCES_DLUA
# LIST_DIRECTORIES false
# RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
# ${PROJECT_SOURCE_DIR}/dLua/*.cpp
# )
# Append the dLua files to the dGame files
set(SOURCES_DGAME ${SOURCES_DGAME} ${SOURCES_DLUA})
endif(__include_lua__)
# Append the dLua files to the dGame files
#set(SOURCES_DGAME ${SOURCES_DGAME} ${SOURCES_DLUA})
#endif(__include_lua__)
## Need to specify to use the CXX compiler language here or else we get errors including <string>.
#target_precompile_headers(
# dDatabase PRIVATE
# "$<$<COMPILE_LANGUAGE:CXX>:${HEADERS_DDATABASE}>"
#)
# Source Code for dZoneManager
file(
GLOB SOURCES_DZM
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/dZoneManager/*.cpp
target_precompile_headers(
dCommon PRIVATE
${HEADERS_DCOMMON}
)
# Source Code for dPhysics
file(
GLOB SOURCES_DPHYSICS
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/dPhysics/*.cpp
)
if(MSVC)
# Skip warning for invalid conversion from size_t to uint32_t for all targets below for now
add_compile_options("/wd4267")
endif(MSVC)
# 3rdparty static libraries:
#add_library(zlib ${SOURCES_ZLIB})
add_library(raknet ${SOURCES_RAKNET})
add_library(tinyxml2 ${SOURCES_TINYXML2})
add_library(detour ${SOURCES_DETOUR})
add_library(recast ${SOURCES_RECAST})
add_library(libbcrypt ${SOURCES_LIBBCRYPT})
# Our static libraries:
add_library(dCommon ${SOURCES_DCOMMON})
add_library(dChatFilter ${SOURCES_DCHATFILTER})
add_library(dDatabase ${SOURCES_DDATABASE})
add_library(dNet ${SOURCES_DNET})
add_library(dGame ${SOURCES_DGAME})
add_library(dZoneManager ${SOURCES_DZM})
add_library(dPhysics ${SOURCES_DPHYSICS})
target_link_libraries(dNet dCommon) #Needed because otherwise linker errors occur.
target_link_libraries(dCommon ZLIB::ZLIB)
target_link_libraries(dCommon libbcrypt)
if(WIN32)
target_link_libraries(raknet ws2_32)
endif(WIN32)
# Our executables:
add_executable(WorldServer ${SOURCES})
add_executable(AuthServer ${SOURCES_AUTH})
add_executable(MasterServer ${SOURCES_MASTER})
add_executable(ChatServer ${SOURCES_CHAT})
# Target libraries to link to:
target_link_libraries(WorldServer dCommon)
target_link_libraries(WorldServer dChatFilter)
target_link_libraries(WorldServer dDatabase)
target_link_libraries(WorldServer dNet)
target_link_libraries(WorldServer dGame)
target_link_libraries(WorldServer dZoneManager)
target_link_libraries(WorldServer dPhysics)
target_link_libraries(WorldServer detour)
target_link_libraries(WorldServer recast)
target_link_libraries(WorldServer raknet)
target_link_libraries(WorldServer ${MYSQL_LIB})
if(UNIX)
target_link_libraries(WorldServer pthread)
target_link_libraries(WorldServer dl)
if(NOT APPLE AND __include_backtrace__)
target_link_libraries(WorldServer backtrace)
target_link_libraries(MasterServer backtrace)
target_link_libraries(AuthServer backtrace)
target_link_libraries(ChatServer backtrace)
endif()
endif(UNIX)
target_link_libraries(WorldServer tinyxml2)
# Target libraries for Auth:
target_link_libraries(AuthServer dCommon)
target_link_libraries(AuthServer dDatabase)
target_link_libraries(AuthServer dNet)
target_link_libraries(AuthServer raknet)
target_link_libraries(AuthServer ${MYSQL_LIB})
if(UNIX)
target_link_libraries(AuthServer pthread)
target_link_libraries(AuthServer dl)
endif(UNIX)
# Target libraries for Master:
target_link_libraries(MasterServer dCommon)
target_link_libraries(MasterServer dDatabase)
target_link_libraries(MasterServer dNet)
target_link_libraries(MasterServer raknet)
target_link_libraries(MasterServer ${MYSQL_LIB})
if(UNIX)
target_link_libraries(MasterServer pthread)
target_link_libraries(MasterServer dl)
endif(UNIX)
# Target libraries for Chat:
target_link_libraries(ChatServer dCommon)
target_link_libraries(ChatServer dChatFilter)
target_link_libraries(ChatServer dDatabase)
target_link_libraries(ChatServer dNet)
target_link_libraries(ChatServer raknet)
target_link_libraries(ChatServer ${MYSQL_LIB})
if(UNIX)
target_link_libraries(ChatServer pthread)
target_link_libraries(ChatServer dl)
endif(UNIX)
# Link dGame to LUA
if(__include_lua__)
find_package(Lua REQUIRED)
#if(__include_lua__)
# find_package(Lua REQUIRED)
target_link_libraries(dGame ${LUA_LIBRARIES})
# target_link_libraries(dGame ${LUA_LIBRARIES})
message(STATUS "Linking dGame to LUA " ${LUA_LIBRARIES})
endif(UNIX)
# Compiler flags:
# Disabled deprecated warnings as the MySQL includes have deprecated code in them.
# Disabled misleading indentation as DL_LinkedList from RakNet has a weird indent.
# Disabled no-register
# Disabled unknown pragmas because Linux doesn't understand Windows pragmas.
if(UNIX)
if(APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17 -O2 -Wuninitialized -Wno-unused-result -Wno-unknown-pragmas -fpermissive -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 -Wno-unused-result -Wno-unknown-pragmas -fpermissive -D_GLIBCXX_USE_CXX11_ABI=0 -D_GLIBCXX_USE_CXX17_ABI=0 -static-libgcc -fPIC")
endif()
if (__dynamic)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -rdynamic")
endif()
if (__ggdb)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb")
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -O2 -fPIC")
endif(UNIX)
if(WIN32)
add_dependencies(MasterServer WorldServer)
add_dependencies(MasterServer AuthServer)
add_dependencies(MasterServer ChatServer)
endif()
# Finally, add the tests
add_subdirectory(tests)
# message(STATUS "Linking dGame to LUA " ${LUA_LIBRARIES})
#endif(UNIX)
target_precompile_headers(
tinyxml2 PRIVATE
"$<$<COMPILE_LANGUAGE:CXX>:${PROJECT_SOURCE_DIR}/thirdparty/tinyxml2/tinyxml2.h>"
)

View File

@ -19,6 +19,15 @@
"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",
@ -66,6 +75,13 @@
"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": [
@ -81,6 +97,18 @@
"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",

View File

@ -1,6 +1,6 @@
PROJECT_VERSION_MAJOR=1
PROJECT_VERSION_MINOR=0
PROJECT_VERSION_PATCH=2
PROJECT_VERSION_PATCH=3
# LICENSE
LICENSE=AGPL-3.0
# The network version.
@ -16,3 +16,5 @@ NET_VERSION=171022
# 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.
__maria_db_connector_compile_jobs__=1
# Set to the number of jobs (make -j equivalent) to compile the mariadbconn files with.

View File

@ -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 Client (packed or unpacked). Check the main [README](./README.md) for details on this.
- LEGO® Universe packed Client. Check the main [README](./README.md) for details on this.
## Run server inside Docker
@ -21,6 +21,15 @@
**NOTE #2**: To stop the server simply run `docker compose down` and to restart it just run `docker compose up -d` again. No need to run all the steps above every time.
**NOTE #3**: Docker buildkit needs to be enabled. https://docs.docker.com/develop/develop-images/build_enhancements/#to-enable-buildkit-builds
**NOTE #4**: Make sure to run the following in the repo root directory after cloning so submodules are also downloaded.
```
git submodule init
git submodule update
```
**NOTE #5**: If DarkflameSetup fails due to not having cdclient.fdb, rename CDClient.fdb (in the same folder) to cdclient.fdb
## Disable brickbuildfix
If you don't need the http server running on port 80 do this:

View File

@ -31,7 +31,6 @@ Development of the latest iteration of Darkflame Universe has been done primaril
```bash
git clone --recursive https://github.com/DarkflameUniverse/DarkflameServer
```
**Python**
Some tools utilized to streamline the setup process require Python 3, make sure you have it installed.
@ -44,7 +43,7 @@ This was done make sure that older and incomplete clients wouldn't produce false
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.
### Linux builds
Make sure packages like `gcc`, `cmake`, and `zlib` are installed. Depending on the distribution, these packages might already be installed. Note that on systems like Ubuntu, you will need the `zlib1g-dev` package so that the header files are available.
Make sure packages like `gcc`, `cmake`, and `zlib` are installed. Depending on the distribution, these packages might already be installed. Note that on systems like Ubuntu, you will need the `zlib1g-dev` package so that the header files are available. `libssl-dev` will also be required as well as `openssl`.
CMake must be version 3.14 or higher!
@ -71,22 +70,19 @@ make
```
### MacOS builds
Ensure `cmake`, `zlib` and `open ssl` are installed as well as a compiler (e.g `clang` or `gcc`).
**Download precompiled MySQL connector**
In the repository root folder run the following. Ensure -DOPENSSL_ROOT_DIR=/path/to/openssl points to your openssl install location
```bash
# Install required tools
brew install boost mysql-connector-c++
# Create the build directory, preserving it if it already exists
mkdir -p build
cd build
# Symlinks for finding the required modules
sudo ln -s /usr/local/mysql-connector-c++/lib64/libmysqlcppconn.dylib /usr/local/mysql-connector-c++/lib64/libmysql.dylib
sudo ln -s /usr/local/mysql-connector-c++/lib64/libcrypto.1.1.dylib /usr/local/mysql/lib/libcrypto.1.1.dylib
```
# Run CMake to generate build files
cmake .. -DOPENSSL_ROOT_DIR=/path/to/openssl
Then follow the Linux build steps (gcc is not required), but before running `make`, run the following to make sure all the libs are available in the build folder:
```bash
sudo ln -s /usr/local/mysql-connector-c++/lib64/libssl.1.1.dylib /path/to/build/folder/libssl.1.1.dylib
sudo ln -s /usr/local/mysql-connector-c++/lib64/libcrypto.1.1.dylib /path/to/build/folder/libcrypto.1.1.dylib
# Get cmake to build the project. If make files are being used then using make and appending `-j` and the amount of cores to utilize may be preferable, for example `make -j8`
cmake --build . --config Release
```
### Windows builds (native)
@ -102,9 +98,20 @@ cd build
cmake ..
:: Run CMake with build flag to build
cmake --build .
cmake --build . --config Release
```
**Windows for ARM** has not been tested but should build by doing the following
```batch
:: Create the build directory
mkdir build
cd build
:: Run CMake to generate make files
cmake .. -DMARIADB_BUILD_SOURCE=ON
:: Run CMake with build flag to build
cmake --build . --config Release
```
### Windows builds (WSL)
This section will go through how to install [WSL](https://docs.microsoft.com/en-us/windows/wsl/install) and building in a Linux environment under Windows. WSL requires Windows 10 version 2004 and higher (Build 19041 and higher) or Windows 11.
@ -128,15 +135,23 @@ sudo apt install build-essential
[**Follow the Linux instructions**](#linux-builds)
### ARM builds
AArch64 builds should work on linux and MacOS using their respective build steps. Windows ARM should build but it has not been tested
### Updating your build
To update your server to the latest version navigate to your cloned directory
```bash
cd /path/to/DarkflameServer
```
run the following commands to update to the latest changes
```bash
git pull
git submodule update --init --recursive
```
now follow the build section for your system
## Setting up the environment
### 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
* Run each SQL file in the order at which they appear [here](migrations/dlu/) on the database
### Resources
**LEGO® Universe 1.10.64**
@ -180,6 +195,13 @@ certutil -hashfile <file> SHA256
* 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
### 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
* Use the command `./MasterServer -m` to automatically run them.
**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.
@ -396,20 +418,28 @@ Here is a summary of the commands available in-game. All commands are prefixed b
</table>
## Credits
### Contributors to DLUv3
* DarwinAnim8or
* Wincent01
* Mick
* averysumner
* Jon002
* Jonny
* Xiphoseer
## Active Contributors
* [EmosewaMC](https://github.com/EmosewaMC)
* [Jettford](https://github.com/Jettford)
## DLU Team
* [DarwinAnim8or](https://github.com/DarwinAnim8or)
* [Wincent01](https://github.com/Wincent01)
* [Mick](https://github.com/MickVermeulen)
* [averysumner](https://github.com/codeshaunted)
* [Jon002](https://github.com/jaller200)
* [Jonny](https://github.com/cuzitsjonny)
* TheMachine
* Matthew
* [Raine](https://github.com/Rainebannister)
* Bricknave
### Research and tools
* lcdr
* [lcdr](https://github.com/lcdr)
* [Xiphoseer](https://github.com/Xiphoseer)
### Community management
* Neal
* [Neal](https://github.com/NealSpellman)
### Former contributors
* TheMachine

View File

@ -0,0 +1,4 @@
set(DAUTHSERVER_SOURCES "AuthServer.cpp")
add_executable(AuthServer ${DAUTHSERVER_SOURCES})
target_link_libraries(AuthServer ${COMMON_LIBRARIES})

View File

@ -0,0 +1,4 @@
set(DCHATFILTER_SOURCES "dChatFilter.cpp")
add_library(dChatFilter STATIC ${DCHATFILTER_SOURCES})
target_link_libraries(dChatFilter dDatabase)

View File

@ -0,0 +1,6 @@
set(DCHATSERVER_SOURCES "ChatPacketHandler.cpp"
"ChatServer.cpp"
"PlayerContainer.cpp")
add_executable(ChatServer ${DCHATSERVER_SOURCES})
target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter)

View File

@ -8,6 +8,10 @@
#include "dServer.h"
#include "GeneralUtils.h"
#include "dLogger.h"
#include "AddFriendResponseCode.h"
#include "AddFriendResponseType.h"
#include "RakString.h"
#include "dConfig.h"
extern PlayerContainer playerContainer;
@ -21,44 +25,41 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
auto player = playerContainer.GetPlayerData(playerID);
if (!player) return;
//Get our friends list from the Db:
auto stmt = Database::CreatePreppedStmt("SELECT * FROM friends WHERE player_id = ? OR friend_id = ?");
stmt->setUInt64(1, playerID);
stmt->setUInt64(2, playerID);
//Get our friends list from the Db. Using a derived table since the friend of a player can be in either column.
std::unique_ptr<sql::PreparedStatement> stmt(Database::CreatePreppedStmt(
"SELECT fr.requested_player, best_friend, ci.name FROM "
"(SELECT CASE "
"WHEN player_id = ? THEN friend_id "
"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;"));
stmt->setUInt(1, static_cast<uint32_t>(playerID));
stmt->setUInt(2, static_cast<uint32_t>(playerID));
std::vector<FriendData> friends;
auto res = stmt->executeQuery();
std::unique_ptr<sql::ResultSet> res(stmt->executeQuery());
while (res->next()) {
FriendData fd;
fd.isFTP = false; // not a thing in DLU
fd.friendID = res->getInt64(1);
if (fd.friendID == playerID) fd.friendID = res->getUInt64(2);
fd.friendID = res->getUInt(1);
GeneralUtils::SetBit(fd.friendID, static_cast<size_t>(eObjectBits::OBJECT_BIT_PERSISTENT));
GeneralUtils::SetBit(fd.friendID, static_cast<size_t>(eObjectBits::OBJECT_BIT_CHARACTER));
fd.isBestFriend = res->getInt(3) == 2; //0 = friends, 1 = requested, 2 = bffs
//We need to find their name as well:
{
auto stmt = Database::CreatePreppedStmt("SELECT name FROM charinfo WHERE id=? limit 1");
stmt->setInt(1, fd.friendID);
auto res = stmt->executeQuery();
while (res->next()) {
fd.friendName = res->getString(1);
}
delete res;
delete stmt;
}
fd.isBestFriend = res->getInt(2) == 3; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs
if (fd.isBestFriend) player->countOfBestFriends+=1;
fd.friendName = res->getString(3);
//Now check if they're online:
auto fr = playerContainer.GetPlayerData(fd.friendID);
if (fr) {
fd.isOnline = true;
fd.zoneID = fr->zoneID;
//Since this friend is online, we need to update them on the fact that we've just logged in:
SendFriendUpdate(fr, player, 1);
SendFriendUpdate(fr, player, 1, fd.isBestFriend);
}
else {
fd.isOnline = false;
@ -68,9 +69,6 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
friends.push_back(fd);
}
delete res;
delete stmt;
//Now, we need to send the friendlist to the server they came from:
CBITSTREAM;
PacketUtils::WriteHeader(bitStream, CHAT_INTERNAL, MSG_CHAT_INTERNAL_ROUTE_TO_PLAYER);
@ -93,20 +91,150 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
}
void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
auto maxNumberOfBestFriendsAsString = Game::config->GetValue("max_number_of_best_friends");
// If this config option doesn't exist, default to 5 which is what live used.
auto maxNumberOfBestFriends = maxNumberOfBestFriendsAsString != "" ? std::stoi(maxNumberOfBestFriendsAsString) : 5U;
CINSTREAM;
LWOOBJID playerID;
inStream.Read(playerID);
inStream.Read(playerID);
std::string playerName = PacketUtils::ReadString(0x14, packet, true);
//There's another bool here to determine if it's a best friend request, but we're not handling it right now.
LWOOBJID requestorPlayerID;
inStream.Read(requestorPlayerID);
inStream.Read(requestorPlayerID);
uint32_t spacing{};
inStream.Read(spacing);
std::string playerName = "";
uint16_t character;
bool noMoreLettersInName = false;
//PacketUtils::SavePacket("FriendRequest.bin", (char*)inStream.GetData(), inStream.GetNumberOfBytesUsed());
//We need to check to see if the player is actually online or not:
auto targetData = playerContainer.GetPlayerData(playerName);
if (targetData) {
SendFriendRequest(targetData, playerContainer.GetPlayerData(playerID));
for (uint32_t j = 0; j < 33; j++) {
inStream.Read(character);
if (character == '\0') noMoreLettersInName = true;
if (!noMoreLettersInName) playerName.push_back(static_cast<char>(character));
}
char isBestFriendRequest{};
inStream.Read(isBestFriendRequest);
auto requestor = playerContainer.GetPlayerData(requestorPlayerID);
std::unique_ptr<PlayerData> requestee(playerContainer.GetPlayerData(playerName));
// Check if player is online first
if (isBestFriendRequest && !requestee) {
for (auto friendDataCandidate : requestor->friends) {
if (friendDataCandidate.friendName == playerName) {
requestee.reset(new PlayerData());
// Setup the needed info since you can add a best friend offline.
requestee->playerID = friendDataCandidate.friendID;
requestee->playerName = friendDataCandidate.friendName;
requestee->zoneID = LWOZONEID();
FriendData requesteeFriendData{};
requesteeFriendData.friendID = requestor->playerID;
requesteeFriendData.friendName = requestor->playerName;
requesteeFriendData.isFTP = false;
requesteeFriendData.isOnline = false;
requesteeFriendData.zoneID = requestor->zoneID;
requestee->friends.push_back(requesteeFriendData);
requestee->sysAddr = UNASSIGNED_SYSTEM_ADDRESS;
break;
}
}
}
// If at this point we dont have a target, then they arent online and we cant send the request.
// Send the response code that corresponds to what the error is.
if (!requestee) {
std::unique_ptr<sql::PreparedStatement> nameQuery(Database::CreatePreppedStmt("SELECT name from charinfo where name = ?;"));
nameQuery->setString(1, playerName);
std::unique_ptr<sql::ResultSet> result(nameQuery->executeQuery());
requestee.reset(new PlayerData());
requestee->playerName = playerName;
SendFriendResponse(requestor, requestee.get(), result->next() ? AddFriendResponseType::NOTONLINE : AddFriendResponseType::INVALIDCHARACTER);
return;
}
if (isBestFriendRequest) {
std::unique_ptr<sql::PreparedStatement> friendUpdate(Database::CreatePreppedStmt("SELECT * FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;"));
friendUpdate->setUInt(1, static_cast<uint32_t>(requestorPlayerID));
friendUpdate->setUInt(2, static_cast<uint32_t>(requestee->playerID));
friendUpdate->setUInt(3, static_cast<uint32_t>(requestee->playerID));
friendUpdate->setUInt(4, static_cast<uint32_t>(requestorPlayerID));
std::unique_ptr<sql::ResultSet> result(friendUpdate->executeQuery());
LWOOBJID queryPlayerID = LWOOBJID_EMPTY;
LWOOBJID queryFriendID = LWOOBJID_EMPTY;
uint8_t oldBestFriendStatus{};
uint8_t bestFriendStatus{};
if (result->next()) {
// Get the IDs
queryPlayerID = result->getInt(1);
queryFriendID = result->getInt(2);
oldBestFriendStatus = result->getInt(3);
bestFriendStatus = oldBestFriendStatus;
// Set the bits
GeneralUtils::SetBit(queryPlayerID, static_cast<size_t>(eObjectBits::OBJECT_BIT_CHARACTER));
GeneralUtils::SetBit(queryPlayerID, static_cast<size_t>(eObjectBits::OBJECT_BIT_PERSISTENT));
GeneralUtils::SetBit(queryFriendID, static_cast<size_t>(eObjectBits::OBJECT_BIT_CHARACTER));
GeneralUtils::SetBit(queryFriendID, static_cast<size_t>(eObjectBits::OBJECT_BIT_PERSISTENT));
// Since this player can either be the friend of someone else or be friends with someone else
// their column in the database determines what bit gets set. When the value hits 3, they
// are now best friends with the other player.
if (queryPlayerID == requestorPlayerID) {
bestFriendStatus |= 1ULL << 0;
} else {
bestFriendStatus |= 1ULL << 1;
}
}
// Only do updates if there was a change in the bff status.
if (oldBestFriendStatus != bestFriendStatus) {
if (requestee->countOfBestFriends >= maxNumberOfBestFriends || requestor->countOfBestFriends >= maxNumberOfBestFriends) {
if (requestee->countOfBestFriends >= maxNumberOfBestFriends) {
SendFriendResponse(requestor, requestee.get(), AddFriendResponseType::THEIRFRIENDLISTFULL, false);
}
if (requestor->countOfBestFriends >= maxNumberOfBestFriends) {
SendFriendResponse(requestor, requestee.get(), AddFriendResponseType::YOURFRIENDSLISTFULL, false);
}
} else {
// Then update the database with this new info.
std::unique_ptr<sql::PreparedStatement> updateQuery(Database::CreatePreppedStmt("UPDATE friends SET best_friend = ? WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;"));
updateQuery->setUInt(1, bestFriendStatus);
updateQuery->setUInt(2, static_cast<uint32_t>(requestorPlayerID));
updateQuery->setUInt(3, static_cast<uint32_t>(requestee->playerID));
updateQuery->setUInt(4, static_cast<uint32_t>(requestee->playerID));
updateQuery->setUInt(5, static_cast<uint32_t>(requestorPlayerID));
updateQuery->executeUpdate();
// Sent the best friend update here if the value is 3
if (bestFriendStatus == 3U) {
requestee->countOfBestFriends+=1;
requestor->countOfBestFriends+=1;
if (requestee->sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee.get(), requestor, AddFriendResponseType::ACCEPTED, false, true);
if (requestor->sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee.get(), AddFriendResponseType::ACCEPTED, false, true);
for (auto& friendData : requestor->friends) {
if (friendData.friendID == requestee->playerID) {
friendData.isBestFriend = true;
}
}
for (auto& friendData : requestee->friends) {
if (friendData.friendID == requestor->playerID) {
friendData.isBestFriend = true;
}
}
}
}
} else {
if (requestor->sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee.get(), AddFriendResponseType::WAITINGAPPROVAL, true, true);
}
} else {
// Do not send this if we are requesting to be a best friend.
SendFriendRequest(requestee.get(), requestor);
}
// If the player is actually a player and not a ghost one defined above, release it from being deleted.
if (requestee->sysAddr != UNASSIGNED_SYSTEM_ADDRESS) requestee.release();
}
void ChatPacketHandler::HandleFriendResponse(Packet* packet) {
@ -115,29 +243,74 @@ void ChatPacketHandler::HandleFriendResponse(Packet* packet) {
inStream.Read(playerID);
inStream.Read(playerID);
uint8_t responseCode = packet->data[0x14];
AddFriendResponseCode clientResponseCode = static_cast<AddFriendResponseCode>(packet->data[0x14]);
std::string friendName = PacketUtils::ReadString(0x15, packet, true);
Game::logger->Log("ChatPacketHandler", "Friend response code: %i\n", responseCode);
if (responseCode != 0) return; //If we're not accepting the request, end here, do not insert to friends table.
PacketUtils::SavePacket("HandleFriendResponse.bin", (char*)inStream.GetData(), inStream.GetNumberOfBytesUsed());
//Now to try and find both of these:
auto goonA = playerContainer.GetPlayerData(playerID);
auto goonB = playerContainer.GetPlayerData(friendName);
if (!goonA || !goonB) return;
auto requestor = playerContainer.GetPlayerData(playerID);
auto requestee = playerContainer.GetPlayerData(friendName);
if (!requestor || !requestee) return;
SendFriendResponse(goonB, goonA, responseCode);
SendFriendResponse(goonA, goonB, responseCode); //Do we need to send it to both? I think so so both get the updated friendlist but... idk.
AddFriendResponseType serverResponseCode{};
uint8_t isAlreadyBestFriends = 0U;
// We need to convert this response code to one we can actually send back to the client.
switch (clientResponseCode) {
case AddFriendResponseCode::ACCEPTED:
serverResponseCode = AddFriendResponseType::ACCEPTED;
break;
case AddFriendResponseCode::BUSY:
serverResponseCode = AddFriendResponseType::BUSY;
break;
case AddFriendResponseCode::CANCELLED:
serverResponseCode = AddFriendResponseType::CANCELLED;
break;
case AddFriendResponseCode::REJECTED:
serverResponseCode = AddFriendResponseType::DECLINED;
break;
}
auto stmt = Database::CreatePreppedStmt("INSERT INTO `friends`(`player_id`, `friend_id`, `best_friend`) VALUES (?,?,?)");
stmt->setUInt64(1, goonA->playerID);
stmt->setUInt64(2, goonB->playerID);
stmt->setInt(3, 0);
stmt->execute();
delete stmt;
// Now that we have handled the base cases, we need to check the other cases.
if (serverResponseCode == AddFriendResponseType::ACCEPTED) {
for (auto friendData : requestor->friends) {
if (friendData.friendID == requestee->playerID) {
serverResponseCode = AddFriendResponseType::ALREADYFRIEND;
if (friendData.isBestFriend) {
isAlreadyBestFriends = 1U;
}
}
}
}
// This message is NOT sent for best friends and is handled differently for those requests.
if (serverResponseCode == AddFriendResponseType::ACCEPTED) {
// Add the each player to the others friend list.
FriendData requestorData;
requestorData.zoneID = requestor->zoneID;
requestorData.friendID = requestor->playerID;
requestorData.friendName = requestor->playerName;
requestorData.isBestFriend = false;
requestorData.isFTP = false;
requestorData.isOnline = true;
requestee->friends.push_back(requestorData);
FriendData requesteeData;
requesteeData.zoneID = requestee->zoneID;
requesteeData.friendID = requestee->playerID;
requesteeData.friendName = requestee->playerName;
requesteeData.isBestFriend = false;
requesteeData.isFTP = false;
requesteeData.isOnline = true;
requestor->friends.push_back(requesteeData);
std::unique_ptr<sql::PreparedStatement> statement(Database::CreatePreppedStmt("INSERT IGNORE INTO `friends` (`player_id`, `friend_id`, `best_friend`) VALUES (?,?,?);"));
statement->setUInt(1, static_cast<uint32_t>(requestor->playerID));
statement->setUInt(2, static_cast<uint32_t>(requestee->playerID));
statement->setInt(3, 0);
statement->execute();
}
if (serverResponseCode != AddFriendResponseType::DECLINED) SendFriendResponse(requestor, requestee, serverResponseCode, isAlreadyBestFriends);
if (serverResponseCode != AddFriendResponseType::ALREADYFRIEND) SendFriendResponse(requestee, requestor, serverResponseCode, isAlreadyBestFriends);
}
void ChatPacketHandler::HandleRemoveFriend(Packet* packet) {
@ -145,50 +318,55 @@ void ChatPacketHandler::HandleRemoveFriend(Packet* packet) {
LWOOBJID playerID;
inStream.Read(playerID);
inStream.Read(playerID);
std::string friendName = PacketUtils::ReadString(16, packet, true);
std::string friendName = PacketUtils::ReadString(0x14, packet, true);
//we'll have to query the db here to find the user, since you can delete them while they're offline.
//First, we need to find their ID:
auto stmt = Database::CreatePreppedStmt("select id from charinfo where name=? limit 1;");
std::unique_ptr<sql::PreparedStatement> stmt(Database::CreatePreppedStmt("SELECT id FROM charinfo WHERE name=? LIMIT 1;"));
stmt->setString(1, friendName.c_str());
LWOOBJID friendID = 0;
auto res = stmt->executeQuery();
std::unique_ptr<sql::ResultSet> res(stmt->executeQuery());
while (res->next()) {
friendID = res->getUInt64(1);
friendID = res->getUInt(1);
}
delete res;
delete stmt;
// Convert friendID to LWOOBJID
GeneralUtils::SetBit(friendID, static_cast<size_t>(eObjectBits::OBJECT_BIT_PERSISTENT));
GeneralUtils::SetBit(friendID, static_cast<size_t>(eObjectBits::OBJECT_BIT_CHARACTER));
//Set our bits to convert to the BIG BOY objectID.
friendID = GeneralUtils::ClearBit(friendID, OBJECT_BIT_CHARACTER);
friendID = GeneralUtils::ClearBit(friendID, OBJECT_BIT_PERSISTENT);
//YEET:
auto deletestmt = Database::CreatePreppedStmt("DELETE FROM `friends` WHERE player_id=? AND friend_id=? LIMIT 1");
deletestmt->setUInt64(1, playerID);
deletestmt->setUInt64(2, friendID);
std::unique_ptr<sql::PreparedStatement> deletestmt(Database::CreatePreppedStmt("DELETE FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;"));
deletestmt->setUInt(1, static_cast<uint32_t>(playerID));
deletestmt->setUInt(2, static_cast<uint32_t>(friendID));
deletestmt->setUInt(3, static_cast<uint32_t>(friendID));
deletestmt->setUInt(4, static_cast<uint32_t>(playerID));
deletestmt->execute();
delete deletestmt;
//because I'm lazy and they can be reversed:
{
auto deletestmt = Database::CreatePreppedStmt("DELETE FROM `friends` WHERE player_id=? AND friend_id=? LIMIT 1");
deletestmt->setUInt64(1, friendID);
deletestmt->setUInt64(2, playerID);
deletestmt->execute();
delete deletestmt;
}
//Now, we need to send an update to notify the sender (and possibly, receiver) that their friendship has been ended:
auto goonA = playerContainer.GetPlayerData(playerID);
if (goonA) {
// Remove the friend from our list of friends
for (auto friendData = goonA->friends.begin(); friendData != goonA->friends.end(); friendData++) {
if ((*friendData).friendID == friendID) {
if ((*friendData).isBestFriend) --goonA->countOfBestFriends;
goonA->friends.erase(friendData);
break;
}
}
SendRemoveFriend(goonA, friendName, true);
}
auto goonB = playerContainer.GetPlayerData(friendID);
if (!goonB) return;
// Do it again for other person
for (auto friendData = goonB->friends.begin(); friendData != goonB->friends.end(); friendData++) {
if ((*friendData).friendID == playerID) {
if ((*friendData).isBestFriend) --goonB->countOfBestFriends;
goonB->friends.erase(friendData);
break;
}
}
std::string goonAName = GeneralUtils::UTF16ToWTF8(playerContainer.GetName(playerID));
SendRemoveFriend(goonB, goonAName, true);
}
@ -206,7 +384,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet)
if (playerContainer.GetIsMuted(sender)) return;
const auto senderName = std::string(sender->playerName.C_String());
const auto senderName = std::string(sender->playerName.c_str());
inStream.SetReadOffset(0x14 * 8);
@ -217,8 +395,6 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet)
Game::logger->Log("ChatPacketHandler", "Got a message from (%s) [%d]: %s\n", senderName.c_str(), channel, message.c_str());
//PacketUtils::SavePacket("chat.bin", reinterpret_cast<char*>(packet->data), packet->length);
if (channel != 8) return;
auto* team = playerContainer.GetTeam(playerID);
@ -231,7 +407,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet)
if (otherMember == nullptr) return;
const auto otherName = std::string(otherMember->playerName.C_String());
const auto otherName = std::string(otherMember->playerName.c_str());
CBITSTREAM;
PacketUtils::WriteHeader(bitStream, CHAT_INTERNAL, MSG_CHAT_INTERNAL_ROUTE_TO_PLAYER);
@ -267,8 +443,8 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
if (playerContainer.GetIsMuted(goonA)) return;
std::string goonAName = goonA->playerName.C_String();
std::string goonBName = goonB->playerName.C_String();
std::string goonAName = goonA->playerName.c_str();
std::string goonBName = goonB->playerName.c_str();
//To the sender:
{
@ -454,8 +630,6 @@ void ChatPacketHandler::HandleTeamKick(Packet* packet)
playerContainer.RemoveMember(team, kickedId, false, true, false);
}
//PacketUtils::SavePacket("kick.bin", reinterpret_cast<char*>(packet->data), packet->length);
}
void ChatPacketHandler::HandleTeamPromote(Packet* packet)
@ -481,8 +655,6 @@ void ChatPacketHandler::HandleTeamPromote(Packet* packet)
playerContainer.PromoteMember(team, promoted->playerID);
}
//PacketUtils::SavePacket("promote.bin", reinterpret_cast<char*>(packet->data), packet->length);
}
void ChatPacketHandler::HandleTeamLootOption(Packet* packet)
@ -509,8 +681,6 @@ void ChatPacketHandler::HandleTeamLootOption(Packet* packet)
playerContainer.UpdateTeamsOnWorld(team, false);
}
//PacketUtils::SavePacket("option.bin", reinterpret_cast<char*>(packet->data), packet->length);
}
void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet)
@ -550,7 +720,7 @@ void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet)
playerContainer.TeamStatusUpdate(team);
const auto leaderName = GeneralUtils::ASCIIToUTF16(std::string(data->playerName.C_String()));
const auto leaderName = GeneralUtils::ASCIIToUTF16(std::string(data->playerName.c_str()));
for (const auto memberId : team->memberIDs)
{
@ -560,7 +730,6 @@ void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet)
const auto memberName = playerContainer.GetName(memberId);
//ChatPacketHandler::SendTeamAddPlayer(otherMember, false, false, false, data->playerID, leaderName, data->zoneID);
if (otherMember != nullptr)
{
ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, data->playerID, data->zoneID);
@ -581,7 +750,7 @@ void ChatPacketHandler::SendTeamInvite(PlayerData* receiver, PlayerData* sender)
//portion that will get routed:
PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_TEAM_INVITE);
PacketUtils::WritePacketWString(sender->playerName.C_String(), 33, &bitStream);
PacketUtils::WritePacketWString(sender->playerName.c_str(), 33, &bitStream);
bitStream.Write(sender->playerID);
SystemAddress sysAddr = receiver->sysAddr;
@ -745,7 +914,7 @@ void ChatPacketHandler::SendTeamSetOffWorldFlag(PlayerData* receiver, LWOOBJID i
SEND_PACKET;
}
void ChatPacketHandler::SendFriendUpdate(PlayerData* friendData, PlayerData* playerData, uint8_t notifyType) {
void ChatPacketHandler::SendFriendUpdate(PlayerData* friendData, PlayerData* playerData, uint8_t notifyType, uint8_t isBestFriend) {
/*chat notification is displayed if log in / out and friend is updated in friends list
[u8] - update type
Update types
@ -767,7 +936,7 @@ void ChatPacketHandler::SendFriendUpdate(PlayerData* friendData, PlayerData* pla
PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_UPDATE_FRIEND_NOTIFY);
bitStream.Write<uint8_t>(notifyType);
std::string playerName = playerData->playerName.C_String();
std::string playerName = playerData->playerName.c_str();
PacketUtils::WritePacketWString(playerName, 33, &bitStream);
@ -783,19 +952,20 @@ void ChatPacketHandler::SendFriendUpdate(PlayerData* friendData, PlayerData* pla
bitStream.Write(playerData->zoneID.GetCloneID());
}
bitStream.Write<uint8_t>(0); //isBFF
bitStream.Write<uint8_t>(isBestFriend); //isBFF
bitStream.Write<uint8_t>(0); //isFTP
SystemAddress sysAddr = friendData->sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::SendFriendRequest(PlayerData* receiver, PlayerData* sender, bool isBFFReq) {
void ChatPacketHandler::SendFriendRequest(PlayerData* receiver, PlayerData* sender) {
if (!receiver || !sender) return;
//Make sure people aren't requesting people that they're already friends with:
for (auto fr : receiver->friends) {
if (fr.friendID == sender->playerID) {
SendFriendResponse(sender, receiver, AddFriendResponseType::ALREADYFRIEND, fr.isBestFriend);
return; //we have this player as a friend, yeet this function so it doesn't send another request.
}
}
@ -806,30 +976,34 @@ void ChatPacketHandler::SendFriendRequest(PlayerData* receiver, PlayerData* send
//portion that will get routed:
PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_ADD_FRIEND_REQUEST);
PacketUtils::WritePacketWString(sender->playerName.C_String(), 33, &bitStream);
bitStream.Write<uint8_t>(0);
PacketUtils::WritePacketWString(sender->playerName.c_str(), 33, &bitStream);
bitStream.Write<uint8_t>(0); // This is a BFF flag however this is unused in live and does not have an implementation client side.
SystemAddress sysAddr = receiver->sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::SendFriendResponse(PlayerData* receiver, PlayerData* sender, uint8_t responseCode) {
void ChatPacketHandler::SendFriendResponse(PlayerData* receiver, PlayerData* sender, AddFriendResponseType responseCode, uint8_t isBestFriendsAlready, uint8_t isBestFriendRequest) {
if (!receiver || !sender) return;
CBITSTREAM;
PacketUtils::WriteHeader(bitStream, CHAT_INTERNAL, MSG_CHAT_INTERNAL_ROUTE_TO_PLAYER);
bitStream.Write(receiver->playerID);
//portion that will get routed:
// Portion that will get routed:
PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_ADD_FRIEND_RESPONSE);
bitStream.Write<uint8_t>(responseCode);
bitStream.Write<uint8_t>(1); //isOnline
PacketUtils::WritePacketWString(sender->playerName.C_String(), 33, &bitStream);
bitStream.Write(responseCode);
// For all requests besides accepted, write a flag that says whether or not we are already best friends with the receiver.
bitStream.Write<uint8_t>(responseCode != AddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender->sysAddr != UNASSIGNED_SYSTEM_ADDRESS);
// Then write the player name
PacketUtils::WritePacketWString(sender->playerName.c_str(), 33, &bitStream);
// Then if this is an acceptance code, write the following extra info.
if (responseCode == AddFriendResponseType::ACCEPTED) {
bitStream.Write(sender->playerID);
bitStream.Write(sender->zoneID);
bitStream.Write<uint8_t>(0); //isBFF
bitStream.Write(isBestFriendRequest); //isBFF
bitStream.Write<uint8_t>(0); //isFTP
}
SystemAddress sysAddr = receiver->sysAddr;
SEND_PACKET;
}

View File

@ -4,6 +4,7 @@
#include "BitStream.h"
struct PlayerData;
enum class AddFriendResponseType : uint8_t;
namespace ChatPacketHandler {
void HandleFriendlistRequest(Packet* packet);
@ -31,10 +32,9 @@ namespace ChatPacketHandler {
void SendTeamSetOffWorldFlag(PlayerData* receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID);
//FriendData is the player we're SENDING this stuff to. Player is the friend that changed state.
void SendFriendUpdate(PlayerData* friendData, PlayerData* playerData, uint8_t notifyType);
void SendFriendUpdate(PlayerData* friendData, PlayerData* playerData, uint8_t notifyType, uint8_t isBestFriend);
void SendFriendRequest(PlayerData* receiver, PlayerData* sender, bool isBFFReq = false);
void SendFriendResponse(PlayerData* receiver, PlayerData* sender, uint8_t responseCode = 3);
void SendFriendRequest(PlayerData* receiver, PlayerData* sender);
void SendFriendResponse(PlayerData* receiver, PlayerData* sender, AddFriendResponseType responseCode, uint8_t isBestFriendsAlready = 0U, uint8_t isBestFriendRequest = 0U);
void SendRemoveFriend(PlayerData* receiver, std::string& personToRemove, bool isSuccessful);
};

View File

@ -20,17 +20,25 @@ PlayerContainer::~PlayerContainer() {
void PlayerContainer::InsertPlayer(Packet* packet) {
CINSTREAM;
PlayerData* data = new PlayerData();
inStream.SetReadOffset(inStream.GetReadOffset() + 64);
inStream.Read(data->playerID);
inStream.Read(data->playerID);
inStream.Read(data->playerName);
uint32_t len;
inStream.Read<uint32_t>(len);
for (int i = 0; i < len; i++) {
char character; inStream.Read<char>(character);
data->playerName += character;
}
inStream.Read(data->zoneID);
inStream.Read(data->muteExpire);
data->sysAddr = packet->systemAddress;
mNames[data->playerID] = GeneralUtils::ASCIIToUTF16(std::string(data->playerName.C_String()));
mNames[data->playerID] = GeneralUtils::ASCIIToUTF16(std::string(data->playerName.c_str()));
mPlayers.insert(std::make_pair(data->playerID, data));
Game::logger->Log("PlayerContainer", "Added user: %s (%llu), zone: %i\n", data->playerName.C_String(), data->playerID, data->zoneID.GetMapID());
Game::logger->Log("PlayerContainer", "Added user: %s (%llu), zone: %i\n", data->playerName.c_str(), data->playerID, data->zoneID.GetMapID());
auto* insertLog = Database::CreatePreppedStmt("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);");
@ -49,26 +57,22 @@ void PlayerContainer::RemovePlayer(Packet* packet) {
inStream.Read(playerID);
//Before they get kicked, we need to also send a message to their friends saying that they disconnected.
auto player = this->GetPlayerData(playerID);
std::unique_ptr<PlayerData> player(this->GetPlayerData(playerID));
if (player == nullptr) {
return;
}
for (auto& fr : player->friends) {
//if (!fr.isOnline) continue;
auto fd = this->GetPlayerData(fr.friendID);
if (fd) ChatPacketHandler::SendFriendUpdate(fd, player, 0);
if (fd) ChatPacketHandler::SendFriendUpdate(fd, player.get(), 0, fr.isBestFriend);
}
auto* team = GetTeam(playerID);
if (team != nullptr)
{
//TeamStatusUpdate(team);
const auto memberName = GeneralUtils::ASCIIToUTF16(std::string(player->playerName.C_String()));
const auto memberName = GeneralUtils::ASCIIToUTF16(std::string(player->playerName.c_str()));
for (const auto memberId : team->memberIDs)
{
@ -77,7 +81,6 @@ void PlayerContainer::RemovePlayer(Packet* packet) {
if (otherMember == nullptr) continue;
ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, playerID, {0, 0, 0});
//ChatPacketHandler::SendTeamRemovePlayer(otherMember, false, false, true, false, team->leaderID, player->playerID, memberName);
}
}
@ -237,17 +240,11 @@ void PlayerContainer::AddMember(TeamData* team, LWOOBJID playerID)
if (leader == nullptr || member == nullptr) return;
const auto leaderName = GeneralUtils::ASCIIToUTF16(std::string(leader->playerName.C_String()));
const auto memberName = GeneralUtils::ASCIIToUTF16(std::string(member->playerName.C_String()));
const auto leaderName = GeneralUtils::ASCIIToUTF16(std::string(leader->playerName.c_str()));
const auto memberName = GeneralUtils::ASCIIToUTF16(std::string(member->playerName.c_str()));
ChatPacketHandler::SendTeamInviteConfirm(member, false, leader->playerID, leader->zoneID, team->lootFlag, 0, 0, leaderName);
/*
ChatPacketHandler::SendTeamAddPlayer(member, false, false, false, leader->playerID, leaderName, leader->zoneID);
Game::logger->Log("PlayerContainer", "Team invite successfully accepted, leader: %s, member: %s\n", leader->playerName.C_String(), member->playerName.C_String());
*/
if (!team->local)
{
ChatPacketHandler::SendTeamSetLeader(member, leader->playerID);
@ -348,7 +345,7 @@ void PlayerContainer::DisbandTeam(TeamData* team)
if (otherMember == nullptr) continue;
const auto memberName = GeneralUtils::ASCIIToUTF16(std::string(otherMember->playerName.C_String()));
const auto memberName = GeneralUtils::ASCIIToUTF16(std::string(otherMember->playerName.c_str()));
ChatPacketHandler::SendTeamSetLeader(otherMember, LWOOBJID_EMPTY);
ChatPacketHandler::SendTeamRemovePlayer(otherMember, true, false, false, team->local, team->leaderID, otherMember->playerID, memberName);
@ -371,7 +368,7 @@ void PlayerContainer::TeamStatusUpdate(TeamData* team)
if (leader == nullptr) return;
const auto leaderName = GeneralUtils::ASCIIToUTF16(std::string(leader->playerName.C_String()));
const auto leaderName = GeneralUtils::ASCIIToUTF16(std::string(leader->playerName.c_str()));
for (const auto memberId : team->memberIDs)
{
@ -383,10 +380,6 @@ void PlayerContainer::TeamStatusUpdate(TeamData* team)
{
ChatPacketHandler::SendTeamStatus(otherMember, team->leaderID, leader->zoneID, team->lootFlag, 0, leaderName);
}
else
{
//ChatPacketHandler::SendTeamStatus(otherMember, LWOOBJID_EMPTY, LWOZONEID(0, 0, 0), 1, 0, u"");
}
}
UpdateTeamsOnWorld(team, false);

View File

@ -9,11 +9,12 @@
struct PlayerData {
LWOOBJID playerID;
RakNet::RakString playerName;
std::string playerName;
SystemAddress sysAddr;
LWOZONEID zoneID;
std::vector<FriendData> friends;
time_t muteExpire;
uint8_t countOfBestFriends = 0;
};
struct TeamData {
@ -45,7 +46,7 @@ public:
PlayerData* GetPlayerData(const std::string& playerName) {
for (auto player : mPlayers) {
if (player.second) {
std::string pn = player.second->playerName.C_String();
std::string pn = player.second->playerName.c_str();
if (pn == playerName) return player.second;
}
}

View File

@ -0,0 +1,15 @@
#pragma once
#ifndef __ADDFRIENDRESPONSECODE__H__
#define __ADDFRIENDRESPONSECODE__H__
#include <cstdint>
enum class AddFriendResponseCode : uint8_t {
ACCEPTED = 0,
REJECTED,
BUSY,
CANCELLED
};
#endif //!__ADDFRIENDRESPONSECODE__H__

View File

@ -0,0 +1,24 @@
#pragma once
#ifndef __ADDFRIENDRESPONSETYPE__H__
#define __ADDFRIENDRESPONSETYPE__H__
#include <cstdint>
enum class AddFriendResponseType : uint8_t {
ACCEPTED = 0,
ALREADYFRIEND,
INVALIDCHARACTER,
GENERALERROR,
YOURFRIENDSLISTFULL,
THEIRFRIENDLISTFULL,
DECLINED,
BUSY,
NOTONLINE,
WAITINGAPPROVAL,
MYTHRAN,
CANCELLED,
FRIENDISFREETRIAL
};
#endif //!__ADDFRIENDRESPONSETYPE__H__

26
dCommon/CMakeLists.txt Normal file
View File

@ -0,0 +1,26 @@
set(DCOMMON_SOURCES "AMFFormat.cpp"
"AMFFormat_BitStream.cpp"
"BinaryIO.cpp"
"dConfig.cpp"
"Diagnostics.cpp"
"dLogger.cpp"
"GeneralUtils.cpp"
"LDFFormat.cpp"
"MD5.cpp"
"Metrics.cpp"
"NiPoint3.cpp"
"NiQuaternion.cpp"
"SHA512.cpp"
"Type.cpp"
"ZCompression.cpp")
include_directories(${PROJECT_SOURCE_DIR}/dCommon/)
add_library(dCommon STATIC ${DCOMMON_SOURCES})
target_link_libraries(dCommon libbcrypt)
if (UNIX)
find_package(ZLIB REQUIRED)
target_link_libraries(dCommon ZLIB::ZLIB)
endif()

View File

@ -188,3 +188,53 @@ 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;
}
while ((entry = readdir(dir)) != NULL) {
std::string value(entry->d_name, strlen(entry->d_name));
if (value == "." || value == "..") {
continue;
}
names.push_back(value);
}
closedir(dir);
return names;
}
#endif

View File

@ -128,6 +128,8 @@ namespace GeneralUtils {
std::vector<std::string> SplitString(const std::string& str, char delimiter);
std::vector<std::string> GetFileNamesFromFolder(const std::string& folder);
template <typename T>
T Parse(const char* value);

View File

@ -54,7 +54,7 @@ LDFBaseData * LDFBaseData::DataFromString(const std::string& format) {
}
case LDF_TYPE_S32: {
int32_t data = static_cast<int32_t>(stol(dataArray[1]));
int32_t data = static_cast<int32_t>(stoull(dataArray[1]));
return new LDFData<int32_t>(key, data);
}

View File

@ -1,5 +1,7 @@
#include "ZCompression.h"
#ifndef _WIN32
#include <zlib.h>
namespace ZCompression
@ -71,3 +73,5 @@ namespace ZCompression
*/
}
}
#endif

View File

@ -2,6 +2,10 @@
#include <cstdint>
#include "dPlatforms.h"
#ifndef DARKFLAME_PLATFORM_WIN32
namespace ZCompression
{
int32_t GetMaxCompressedLength(int32_t nLenSrc);
@ -10,3 +14,5 @@ namespace ZCompression
int32_t Decompress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst, int32_t& nErr);
}
#endif

View File

@ -407,14 +407,20 @@ enum eReplicaComponentType : int32_t {
COMPONENT_TYPE_MISSION = 84, //!< The Mission Component
COMPONENT_TYPE_ROCKET_LAUNCH_LUP = 97, //!< The LUP Launchpad Componen
COMPONENT_TYPE_RAIL_ACTIVATOR = 104,
COMPONENT_TYPE_POSSESSOR = 107, //!< The Component 107
COMPONENT_TYPE_POSSESSABLE = 108, //!< The Component 108
COMPONENT_TYPE_POSSESSABLE = 108, //!< The Possessable Component
COMPONENT_TYPE_POSSESSOR = 110, //!< The Possessor Component
COMPONENT_TYPE_BUILD_BORDER = 114, //!< The Build Border Component
COMPONENT_TYPE_DESTROYABLE = 1000, //!< The Destroyable Component
COMPONENT_TYPE_MODEL = 5398484 //look man idk
};
enum class UseItemResponse : uint32_t {
NoImaginationForPet = 1,
FailedPrecondition,
MountsNotAllowed
};
/**
* Represents the different types of inventories an entity may have
*/
@ -434,33 +440,6 @@ enum eInventoryType : uint32_t {
INVALID // made up, for internal use!!!
};
enum eItemType : int32_t {
ITEM_TYPE_UNKNOWN = -1, //!< An unknown item type
ITEM_TYPE_BRICK = 1, //!< A brick
ITEM_TYPE_HAT = 2, //!< A hat / head item
ITEM_TYPE_HAIR = 3, //!< A hair item
ITEM_TYPE_NECK = 4, //!< A neck item
ITEM_TYPE_LEFT_HAND = 5, //!< A left handed item
ITEM_TYPE_RIGHT_HAND = 6, //!< A right handed item
ITEM_TYPE_LEGS = 7, //!< A pants item
ITEM_TYPE_LEFT_TRINKET = 8, //!< A left handled trinket item
ITEM_TYPE_RIGHT_TRINKET = 9, //!< A right handed trinket item
ITEM_TYPE_BEHAVIOR = 10, //!< A behavior
ITEM_TYPE_PROPERTY = 11, //!< A property
ITEM_TYPE_MODEL = 12, //!< A model
ITEM_TYPE_COLLECTIBLE = 13, //!< A collectible item
ITEM_TYPE_CONSUMABLE = 14, //!< A consumable item
ITEM_TYPE_CHEST = 15, //!< A chest item
ITEM_TYPE_EGG = 16, //!< An egg
ITEM_TYPE_PET_FOOD = 17, //!< A pet food item
ITEM_TYPE_QUEST_OBJECT = 18, //!< A quest item
ITEM_TYPE_PET_INVENTORY_ITEM = 19, //!< A pet inventory item
ITEM_TYPE_PACKAGE = 20, //!< A package
ITEM_TYPE_LOOT_MODEL = 21, //!< A loot model
ITEM_TYPE_VEHICLE = 22, //!< A vehicle
ITEM_TYPE_CURRENCY = 23 //!< Currency
};
enum eRebuildState : uint32_t {
REBUILD_OPEN,
REBUILD_COMPLETED = 2,
@ -658,7 +637,6 @@ enum ePlayerFlags {
NJ_WU_SHOW_DAILY_CHEST = 2099
};
//======== FUNC ===========
template<typename T>

29
dCommon/dPlatforms.h Normal file
View File

@ -0,0 +1,29 @@
#pragma once
#if defined(_WIN32)
#define DARKFLAME_PLATFORM_WIN32
#elif defined(__APPLE__) && defined(__MACH__)
#include <TargetConditionals.h>
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
#define DARKFLAME_PLATFORM_IOS
#elif TARGET_OS_MAC
#define DARKFLAME_PLATFORM_MACOS
#else
#error unknown Apple operating system
#endif
#elif defined(__unix__)
#define DARKFLAME_PLATFORM_UNIX
#if defined(__ANDROID__)
#define DARKFLAME_PLATFORM_ANDROID
#elif defined(__linux__)
#define DARKFLAME_PLATFORM_LINUX
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
#define DARKFLAME_PLATFORM_FREEBSD
#elif defined(__CYGWIN__)
#define DARKFLAME_PLATFORM_CYGWIN
#else
#error unknown unix operating system
#endif
#else
#error unknown operating system
#endif

View File

@ -0,0 +1,44 @@
#pragma once
#ifndef __EANINMATIONFLAGS__H__
#define __EANINMATIONFLAGS__H__
#include <cstdint>
enum class eAnimationFlags : uint32_t {
IDLE_INVALID = 0, // made up, for internal use!!!
IDLE_BASIC,
IDLE_SWIM,
IDLE_CARRY,
IDLE_SWORD,
IDLE_HAMMER,
IDLE_SPEAR,
IDLE_PISTOL,
IDLE_BOW,
IDLE_COMBAT,
IDLE_JETPACK,
IDLE_HORSE,
IDLE_SG,
IDLE_ORGAN,
IDLE_SKATEBOARD,
IDLE_DAREDEVIL,
IDLE_SAMURAI,
IDLE_SUMMONER,
IDLE_BUCCANEER,
IDLE_MISC,
IDLE_NINJA,
IDLE_MISC1,
IDLE_MISC2,
IDLE_MISC3,
IDLE_MISC4,
IDLE_MISC5,
IDLE_MISC6,
IDLE_MISC7,
IDLE_MISC8,
IDLE_MISC9,
IDLE_MISC10,
IDLE_MISC11,
IDLE_MISC12
};
#endif //!__EANINMATIONFLAGS__H__

36
dCommon/eItemType.h Normal file
View File

@ -0,0 +1,36 @@
#pragma once
#ifndef __EITEMTYPE__H__
#define __EITEMTYPE__H__
#include <cstdint>
enum class eItemType : int32_t {
ITEM_TYPE_UNKNOWN = -1, //!< An unknown item type
ITEM_TYPE_BRICK = 1, //!< A brick
ITEM_TYPE_HAT = 2, //!< A hat / head item
ITEM_TYPE_HAIR = 3, //!< A hair item
ITEM_TYPE_NECK = 4, //!< A neck item
ITEM_TYPE_LEFT_HAND = 5, //!< A left handed item
ITEM_TYPE_RIGHT_HAND = 6, //!< A right handed item
ITEM_TYPE_LEGS = 7, //!< A pants item
ITEM_TYPE_LEFT_TRINKET = 8, //!< A left handled trinket item
ITEM_TYPE_RIGHT_TRINKET = 9, //!< A right handed trinket item
ITEM_TYPE_BEHAVIOR = 10, //!< A behavior
ITEM_TYPE_PROPERTY = 11, //!< A property
ITEM_TYPE_MODEL = 12, //!< A model
ITEM_TYPE_COLLECTIBLE = 13, //!< A collectible item
ITEM_TYPE_CONSUMABLE = 14, //!< A consumable item
ITEM_TYPE_CHEST = 15, //!< A chest item
ITEM_TYPE_EGG = 16, //!< An egg
ITEM_TYPE_PET_FOOD = 17, //!< A pet food item
ITEM_TYPE_QUEST_OBJECT = 18, //!< A quest item
ITEM_TYPE_PET_INVENTORY_ITEM = 19, //!< A pet inventory item
ITEM_TYPE_PACKAGE = 20, //!< A package
ITEM_TYPE_LOOT_MODEL = 21, //!< A loot model
ITEM_TYPE_VEHICLE = 22, //!< A vehicle
ITEM_TYPE_CURRENCY = 23, //!< Currency
ITEM_TYPE_MOUNT = 24 //!< A Mount
};
#endif //!__EITEMTYPE__H__

View File

@ -13,3 +13,8 @@ void CDClientDatabase::Connect(const std::string& filename) {
CppSQLite3Query CDClientDatabase::ExecuteQuery(const std::string& query) {
return conn->execQuery(query.c_str());
}
//! Makes prepared statements
CppSQLite3Statement CDClientDatabase::CreatePreppedStmt(const std::string& query) {
return conn->compileStatement(query.c_str());
}

View File

@ -40,4 +40,10 @@ namespace CDClientDatabase {
*/
CppSQLite3Query ExecuteQuery(const std::string& query);
//! Queries the CDClient and parses arguments
/*!
\param query The query with formatted arguments
\return prepared SQLite Statement
*/
CppSQLite3Statement CreatePreppedStmt(const std::string& query);
};

View File

@ -8,7 +8,7 @@ void CDClientManager::Initialize(void) {
tables.insert(std::make_pair("ActivityRewards", new CDActivityRewardsTable()));
UNUSED(tables.insert(std::make_pair("Animations", new CDAnimationsTable())));
tables.insert(std::make_pair("BehaviorParameter", new CDBehaviorParameterTable()));
UNUSED(tables.insert(std::make_pair("BehaviorTemplate", new CDBehaviorTemplateTable())));
tables.insert(std::make_pair("BehaviorTemplate", new CDBehaviorTemplateTable()));
tables.insert(std::make_pair("ComponentsRegistry", new CDComponentsRegistryTable()));
tables.insert(std::make_pair("CurrencyTable", new CDCurrencyTableTable()));
tables.insert(std::make_pair("DestructibleComponent", new CDDestructibleComponentTable()));

13
dDatabase/CMakeLists.txt Normal file
View File

@ -0,0 +1,13 @@
set(DDATABASE_SOURCES "CDClientDatabase.cpp"
"CDClientManager.cpp"
"Database.cpp"
"MigrationRunner.cpp")
add_subdirectory(Tables)
foreach(file ${DDATABASE_TABLES_SOURCES})
set(DDATABASE_SOURCES ${DDATABASE_SOURCES} "Tables/${file}")
endforeach()
add_library(dDatabase STATIC ${DDATABASE_SOURCES})
target_link_libraries(dDatabase sqlite3 mariadbConnCpp)

View File

@ -8,9 +8,10 @@ using namespace std;
sql::Driver * Database::driver;
sql::Connection * Database::con;
sql::Properties Database::props;
std::string Database::database;
void Database::Connect(const string& host, const string& database, const string& username, const string& password) {
driver = get_driver_instance();
//To bypass debug issues:
std::string newHost = "tcp://" + host;
@ -19,17 +20,33 @@ void Database::Connect(const string& host, const string& database, const string&
const char* szUsername = username.c_str();
const char* szPassword = password.c_str();
con = driver->connect(szHost, szUsername, szPassword);
con->setSchema(szDatabase);
driver = sql::mariadb::get_driver_instance();
bool myTrue = true;
con->setClientOption("MYSQL_OPT_RECONNECT", &myTrue);
} //Connect
sql::Properties properties;
properties["hostName"] = szHost;
properties["user"] = szUsername;
properties["password"] = szPassword;
properties["autoReconnect"] = "true";
void Database::Destroy(std::string source) {
Database::props = properties;
Database::database = database;
Database::Connect();
}
void Database::Connect() {
con = driver->connect(Database::props);
con->setSchema(Database::database);
}
void Database::Destroy(std::string source, bool log) {
if (!con) return;
if (log) {
if (source != "") Game::logger->Log("Database", "Destroying MySQL connection from %s!\n", source.c_str());
else Game::logger->Log("Database", "Destroying MySQL connection!\n");
}
con->close();
delete con;
} //Destroy
@ -45,13 +62,7 @@ sql::PreparedStatement* Database::CreatePreppedStmt(const std::string& query) {
sql::SQLString str(test, size);
if (!con) {
//Connect to the MySQL Database
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");
Connect(mysql_host, mysql_database, mysql_username, mysql_password);
Connect();
Game::logger->Log("Database", "Trying to reconnect to MySQL\n");
}
@ -61,13 +72,7 @@ sql::PreparedStatement* Database::CreatePreppedStmt(const std::string& query) {
con = nullptr;
//Connect to the MySQL Database
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");
Connect(mysql_host, mysql_database, mysql_username, mysql_password);
Connect();
Game::logger->Log("Database", "Trying to reconnect to MySQL from invalid or closed connection\n");
}
@ -75,3 +80,7 @@ sql::PreparedStatement* Database::CreatePreppedStmt(const std::string& query) {
return stmt;
} //CreatePreppedStmt
void Database::Commit() {
Database::con->commit();
}

View File

@ -1,13 +1,7 @@
#pragma once
#include <string>
#include <mysql_connection.h>
#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/resultset.h>
#include <cppconn/statement.h>
#include <cppconn/prepared_statement.h>
#include <cppconn/sqlstring.h>
#include <conncpp.hpp>
class MySqlException : public std::runtime_error {
public:
@ -19,10 +13,17 @@ class Database {
private:
static sql::Driver *driver;
static sql::Connection *con;
static sql::Properties props;
static std::string database;
public:
static void Connect(const std::string& host, const std::string& database, const std::string& username, const std::string& password);
static void Destroy(std::string source="");
static void Connect();
static void Destroy(std::string source = "", bool log = true);
static sql::Statement* CreateStmt();
static sql::PreparedStatement* CreatePreppedStmt(const std::string& query);
static void Commit();
static std::string GetDatabase() { return database; }
static sql::Properties GetProperties() { return props; }
};

View File

@ -0,0 +1,78 @@
#include "MigrationRunner.h"
#include "GeneralUtils.h"
#include <fstream>
#include <algorithm>
#include <thread>
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: " + migration.name + "\n");
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", std::string("Encountered error running migration: ") + e.what() + "\n");
}
}
}
Migration MigrationRunner::LoadMigration(std::string path) {
Migration migration{};
std::ifstream file("./migrations/" + path);
if (file.is_open()) {
std::hash<std::string> hash;
std::string line;
std::string total = "";
while (std::getline(file, line)) {
total += line;
}
file.close();
migration.name = path;
migration.data = total;
}
return migration;
}

View File

@ -0,0 +1,19 @@
#pragma once
#include "Database.h"
#include "dCommonVars.h"
#include "Game.h"
#include "dCommonVars.h"
#include "dLogger.h"
struct Migration {
std::string data;
std::string name;
};
class MigrationRunner {
public:
static void RunMigrations();
static Migration LoadMigration(std::string path);
};

View File

@ -3,33 +3,31 @@
//! Constructor
CDBehaviorParameterTable::CDBehaviorParameterTable(void) {
#ifdef CDCLIENT_CACHE_ALL
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM BehaviorParameter");
size_t hash = 0;
while (!tableData.eof()) {
hash = 0;
CDBehaviorParameter entry;
entry.behaviorID = tableData.getIntField(0, -1);
entry.parameterID = tableData.getStringField(1, "");
auto candidateStringToAdd = std::string(tableData.getStringField(1, ""));
auto parameter = m_ParametersList.find(candidateStringToAdd);
if (parameter != m_ParametersList.end()) {
entry.parameterID = parameter;
} else {
entry.parameterID = m_ParametersList.insert(candidateStringToAdd).first;
}
entry.value = tableData.getFloatField(2, -1.0f);
//Check if we have an entry with this ID:
auto it = m_entries.find(entry.behaviorID);
if (it != m_entries.end()) {
it->second.insert(std::make_pair(entry.parameterID, entry.value));
}
else {
//Otherwise, insert it:
m_entries.insert(std::make_pair(entry.behaviorID, std::map<std::string, float>()));
auto jit = m_entries.find(entry.behaviorID);
GeneralUtils::hash_combine(hash, entry.behaviorID);
GeneralUtils::hash_combine(hash, *entry.parameterID);
//Add our value as well:
jit->second.insert(std::make_pair(entry.parameterID, entry.value));
}
auto it = m_Entries.find(entry.behaviorID);
m_ParametersList.insert(*entry.parameterID);
m_Entries.insert(std::make_pair(hash, entry));
tableData.nextRow();
}
tableData.finalize();
#endif
}
//! Destructor
@ -40,51 +38,33 @@ std::string CDBehaviorParameterTable::GetName(void) const {
return "BehaviorParameter";
}
float CDBehaviorParameterTable::GetEntry(const uint32_t behaviorID, const std::string& name, const float defaultValue)
CDBehaviorParameter CDBehaviorParameterTable::GetEntry(const uint32_t behaviorID, const std::string& name, const float defaultValue)
{
CDBehaviorParameter returnValue;
returnValue.behaviorID = 0;
returnValue.parameterID = m_ParametersList.end();
returnValue.value = defaultValue;
size_t hash = 0;
GeneralUtils::hash_combine(hash, behaviorID);
GeneralUtils::hash_combine(hash, name);
// Search for specific parameter
const auto& it = m_Entries.find(hash);
if (it != m_Entries.end()) {
return it->second;
}
// Check if this behavior has already been checked
const auto& itChecked = m_Entries.find(behaviorID);
if (itChecked != m_Entries.end()) {
return defaultValue;
}
#ifndef CDCLIENT_CACHE_ALL
std::stringstream query;
query << "SELECT parameterID, value FROM BehaviorParameter WHERE behaviorID = " << std::to_string(behaviorID);
auto tableData = CDClientDatabase::ExecuteQuery(query.str());
m_Entries.insert_or_assign(behaviorID, 0);
while (!tableData.eof()) {
const std::string parameterID = tableData.getStringField(0, "");
const float value = tableData.getFloatField(1, 0);
size_t parameterHash = 0;
GeneralUtils::hash_combine(parameterHash, behaviorID);
GeneralUtils::hash_combine(parameterHash, parameterID);
m_Entries.insert_or_assign(parameterHash, value);
tableData.nextRow();
}
const auto& it2 = m_Entries.find(hash);
if (it2 != m_Entries.end()) {
return it2->second;
}
#endif
return defaultValue;
return it != m_Entries.end() ? it->second : returnValue;
}
std::map<std::string, float> CDBehaviorParameterTable::GetParametersByBehaviorID(uint32_t behaviorID) {
size_t hash;
std::map<std::string, float> returnInfo;
for (auto parameterCandidate : m_ParametersList) {
hash = 0;
GeneralUtils::hash_combine(hash, behaviorID);
GeneralUtils::hash_combine(hash, parameterCandidate);
auto infoCandidate = m_Entries.find(hash);
if (infoCandidate != m_Entries.end()) {
returnInfo.insert(std::make_pair(*(infoCandidate->second.parameterID), infoCandidate->second.value));
}
}
return returnInfo;
}

View File

@ -2,7 +2,8 @@
// Custom Classes
#include "CDTable.h"
#include <map>
#include <unordered_map>
#include <unordered_set>
/*!
\file CDBehaviorParameterTable.hpp
@ -12,15 +13,15 @@
//! BehaviorParameter Entry Struct
struct CDBehaviorParameter {
unsigned int behaviorID; //!< The Behavior ID
std::string parameterID; //!< The Parameter ID
std::unordered_set<std::string>::iterator parameterID; //!< The Parameter ID
float value; //!< The value of the behavior template
};
//! BehaviorParameter table
class CDBehaviorParameterTable : public CDTable {
private:
std::map<size_t, float> m_Entries;
std::unordered_map<size_t, CDBehaviorParameter> m_Entries;
std::unordered_set<std::string> m_ParametersList;
public:
//! Constructor
@ -35,5 +36,7 @@ public:
*/
std::string GetName(void) const override;
float GetEntry(const uint32_t behaviorID, const std::string& name, const float defaultValue = 0);
CDBehaviorParameter GetEntry(const uint32_t behaviorID, const std::string& name, const float defaultValue = 0);
std::map<std::string, float> GetParametersByBehaviorID(uint32_t behaviorID);
};

View File

@ -24,9 +24,16 @@ CDBehaviorTemplateTable::CDBehaviorTemplateTable(void) {
entry.behaviorID = tableData.getIntField(0, -1);
entry.templateID = tableData.getIntField(1, -1);
entry.effectID = tableData.getIntField(2, -1);
entry.effectHandle = tableData.getStringField(3, "");
auto candidateToAdd = tableData.getStringField(3, "");
auto parameter = m_EffectHandles.find(candidateToAdd);
if (parameter != m_EffectHandles.end()) {
entry.effectHandle = parameter;
} else {
entry.effectHandle = m_EffectHandles.insert(candidateToAdd).first;
}
this->entries.push_back(entry);
this->entriesMappedByBehaviorID.insert(std::make_pair(entry.behaviorID, entry));
tableData.nextRow();
}
@ -55,3 +62,16 @@ std::vector<CDBehaviorTemplate> CDBehaviorTemplateTable::Query(std::function<boo
std::vector<CDBehaviorTemplate> CDBehaviorTemplateTable::GetEntries(void) const {
return this->entries;
}
const CDBehaviorTemplate CDBehaviorTemplateTable::GetByBehaviorID(uint32_t behaviorID) {
auto entry = this->entriesMappedByBehaviorID.find(behaviorID);
if (entry == this->entriesMappedByBehaviorID.end()) {
CDBehaviorTemplate entryToReturn;
entryToReturn.behaviorID = 0;
entryToReturn.effectHandle = m_EffectHandles.end();
entryToReturn.effectID = 0;
return entryToReturn;
} else {
return entry->second;
}
}

View File

@ -2,6 +2,8 @@
// Custom Classes
#include "CDTable.h"
#include <unordered_map>
#include <unordered_set>
/*!
\file CDBehaviorTemplateTable.hpp
@ -13,7 +15,7 @@ struct CDBehaviorTemplate {
unsigned int behaviorID; //!< The Behavior ID
unsigned int templateID; //!< The Template ID (LOT)
unsigned int effectID; //!< The Effect ID attached
std::string effectHandle; //!< The effect handle
std::unordered_set<std::string>::iterator effectHandle; //!< The effect handle
};
@ -21,7 +23,8 @@ struct CDBehaviorTemplate {
class CDBehaviorTemplateTable : public CDTable {
private:
std::vector<CDBehaviorTemplate> entries;
std::unordered_map<uint32_t, CDBehaviorTemplate> entriesMappedByBehaviorID;
std::unordered_set<std::string> m_EffectHandles;
public:
//! Constructor
@ -48,4 +51,5 @@ public:
*/
std::vector<CDBehaviorTemplate> GetEntries(void) const;
const CDBehaviorTemplate GetByBehaviorID(uint32_t behaviorID);
};

View File

@ -168,7 +168,7 @@ std::map<LOT, uint32_t> CDItemComponentTable::ParseCraftingCurrencies(const CDIt
// Checking for 2 here, not sure what to do when there's more stuff than expected
if (amountSplit.size() == 2) {
currencies.insert({
std::stol(amountSplit[0]),
std::stoull(amountSplit[0]),
std::stoi(amountSplit[1])
});
}

View File

@ -0,0 +1,38 @@
set(DDATABASE_TABLES_SOURCES "CDActivitiesTable.cpp"
"CDActivityRewardsTable.cpp"
"CDAnimationsTable.cpp"
"CDBehaviorParameterTable.cpp"
"CDBehaviorTemplateTable.cpp"
"CDBrickIDTableTable.cpp"
"CDComponentsRegistryTable.cpp"
"CDCurrencyTableTable.cpp"
"CDDestructibleComponentTable.cpp"
"CDEmoteTable.cpp"
"CDFeatureGatingTable.cpp"
"CDInventoryComponentTable.cpp"
"CDItemComponentTable.cpp"
"CDItemSetSkillsTable.cpp"
"CDItemSetsTable.cpp"
"CDLevelProgressionLookupTable.cpp"
"CDLootMatrixTable.cpp"
"CDLootTableTable.cpp"
"CDMissionEmailTable.cpp"
"CDMissionNPCComponentTable.cpp"
"CDMissionsTable.cpp"
"CDMissionTasksTable.cpp"
"CDMovementAIComponentTable.cpp"
"CDObjectSkillsTable.cpp"
"CDObjectsTable.cpp"
"CDPackageComponentTable.cpp"
"CDPhysicsComponentTable.cpp"
"CDPropertyEntranceComponentTable.cpp"
"CDPropertyTemplateTable.cpp"
"CDProximityMonitorComponentTable.cpp"
"CDRailActivatorComponent.cpp"
"CDRarityTableTable.cpp"
"CDRebuildComponentTable.cpp"
"CDRewardsTable.cpp"
"CDScriptComponentTable.cpp"
"CDSkillBehaviorTable.cpp"
"CDVendorComponentTable.cpp"
"CDZoneTableTable.cpp" PARENT_SCOPE)

59
dGame/CMakeLists.txt Normal file
View File

@ -0,0 +1,59 @@
set(DGAME_SOURCES "Character.cpp"
"Entity.cpp"
"EntityManager.cpp"
"LeaderboardManager.cpp"
"Player.cpp"
"TeamManager.cpp"
"TradingManager.cpp"
"User.cpp"
"UserManager.cpp")
add_subdirectory(dBehaviors)
foreach(file ${DGAME_DBEHAVIORS_SOURCES})
set(DGAME_SOURCES ${DGAME_SOURCES} "dBehaviors/${file}")
endforeach()
add_subdirectory(dComponents)
foreach(file ${DGAME_DCOMPONENTS_SOURCES})
set(DGAME_SOURCES ${DGAME_SOURCES} "dComponents/${file}")
endforeach()
add_subdirectory(dEntity)
foreach(file ${DGAME_DENTITY_SOURCES})
set(DGAME_SOURCES ${DGAME_SOURCES} "dEntity/${file}")
endforeach()
add_subdirectory(dGameMessages)
foreach(file ${DGAME_DGAMEMESSAGES_SOURCES})
set(DGAME_SOURCES ${DGAME_SOURCES} "dGameMessages/${file}")
endforeach()
add_subdirectory(dInventory)
foreach(file ${DGAME_DINVENTORY_SOURCES})
set(DGAME_SOURCES ${DGAME_SOURCES} "dInventory/${file}")
endforeach()
add_subdirectory(dMission)
foreach(file ${DGAME_DMISSION_SOURCES})
set(DGAME_SOURCES ${DGAME_SOURCES} "dMission/${file}")
endforeach()
add_subdirectory(dUtilities)
foreach(file ${DGAME_DUTILITIES_SOURCES})
set(DGAME_SOURCES ${DGAME_SOURCES} "dUtilities/${file}")
endforeach()
foreach(file ${DSCRIPT_SOURCES})
set(DGAME_SOURCES ${DGAME_SOURCES} "${PROJECT_SOURCE_DIR}/dScripts/${file}")
endforeach()
add_library(dGame STATIC ${DGAME_SOURCES})
target_link_libraries(dGame dDatabase)

View File

@ -79,6 +79,7 @@ Entity::Entity(const LWOOBJID& objectID, EntityInfo info, Entity* parentEntity)
m_Components = {};
m_DieCallbacks = {};
m_PhantomCollisionCallbacks = {};
m_IsParentChildDirty = true;
m_Settings = info.settings;
m_NetworkSettings = info.networkSettings;
@ -98,22 +99,6 @@ Entity::~Entity() {
m_Character->SaveXMLToDatabase();
}
if (IsPlayer()) {
Entity* zoneControl = EntityManager::Instance()->GetZoneControlEntity();
for (CppScripts::Script* script : CppScripts::GetEntityScripts(zoneControl)) {
script->OnPlayerExit(zoneControl, this);
}
std::vector<Entity*> scriptedActs = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_SCRIPTED_ACTIVITY);
for (Entity* scriptEntity : scriptedActs) {
if (scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds
for (CppScripts::Script* script : CppScripts::GetEntityScripts(scriptEntity)) {
script->OnPlayerExit(scriptEntity, this);
}
}
}
}
CancelAllTimers();
CancelCallbackTimers();
@ -124,6 +109,14 @@ Entity::~Entity() {
m_Components.erase(pair.first);
}
for (auto child : m_ChildEntities) {
if (child) child->RemoveParent();
}
if (m_ParentEntity) {
m_ParentEntity->RemoveChild(this);
}
}
void Entity::Initialize()
@ -220,8 +213,9 @@ void Entity::Initialize()
m_Components.insert(std::make_pair(COMPONENT_TYPE_ZONE_CONTROL, nullptr));
}
if (compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_POSSESSABLE) > 0) {
m_Components.insert(std::make_pair(COMPONENT_TYPE_POSSESSABLE, new PossessableComponent(this)));
uint32_t possessableComponentId = compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_POSSESSABLE);
if (possessableComponentId > 0) {
m_Components.insert(std::make_pair(COMPONENT_TYPE_POSSESSABLE, new PossessableComponent(this, possessableComponentId)));
}
if (compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_MODULE_ASSEMBLY) > 0) {
@ -447,6 +441,8 @@ void Entity::Initialize()
}*/
if (compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_CHARACTER) > 0 || m_Character) {
// Character Component always has a possessor component
m_Components.insert(std::make_pair(COMPONENT_TYPE_POSSESSOR, new PossessorComponent(this)));
CharacterComponent* comp = new CharacterComponent(this, m_Character);
m_Components.insert(std::make_pair(COMPONENT_TYPE_CHARACTER, comp));
}
@ -561,19 +557,6 @@ void Entity::Initialize()
comp->SetPostImaginationCost(rebCompData[0].post_imagination_cost);
comp->SetTimeBeforeSmash(rebCompData[0].time_before_smash);
const auto rebuildActivatorValue = GetVarAsString(u"rebuild_activators");
if (!rebuildActivatorValue.empty()) {
std::vector<std::string> split = GeneralUtils::SplitString(rebuildActivatorValue, 0x1f);
NiPoint3 pos;
pos.x = std::stof(split[0]);
pos.y = std::stof(split[1]);
pos.z = std::stof(split[2]);
comp->SetActivatorPosition(pos);
}
const auto rebuildResetTime = GetVar<float>(u"rebuild_reset_time");
if (rebuildResetTime != 0.0f) {
@ -631,10 +614,6 @@ void Entity::Initialize()
m_Components.insert(std::make_pair(COMPONENT_TYPE_RENDER, render));
}
if ((compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_POSSESSOR) > 0) || m_Character) {
m_Components.insert(std::make_pair(COMPONENT_TYPE_POSSESSOR, new PossessorComponent(this)));
}
if ((compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_MISSION_OFFER) > 0) || m_Character) {
m_Components.insert(std::make_pair(COMPONENT_TYPE_MISSION_OFFER, new MissionOfferComponent(this, m_TemplateID)));
}
@ -974,8 +953,11 @@ void Entity::WriteBaseReplicaData(RakNet::BitStream* outBitStream, eReplicaPacke
}
else outBitStream->Write0(); //No GM Level
}
outBitStream->Write((m_ParentEntity != nullptr || m_ChildEntities.size() > 0));
if (m_ParentEntity || m_ChildEntities.size() > 0) {
// Only serialize parent / child info should the info be dirty (changed) or if this is the construction of the entity.
outBitStream->Write(m_IsParentChildDirty || packetType == PACKET_TYPE_CONSTRUCTION);
if (m_IsParentChildDirty || packetType == PACKET_TYPE_CONSTRUCTION) {
m_IsParentChildDirty = false;
outBitStream->Write(m_ParentEntity != nullptr);
if (m_ParentEntity) {
outBitStream->Write(m_ParentEntity->GetObjectID());
@ -1079,8 +1061,15 @@ void Entity::WriteComponents(RakNet::BitStream* outBitStream, eReplicaPacketType
}
CharacterComponent* characterComponent;
if (TryGetComponent(COMPONENT_TYPE_CHARACTER, characterComponent))
{
if (TryGetComponent(COMPONENT_TYPE_CHARACTER, characterComponent)) {
PossessorComponent* possessorComponent;
if (TryGetComponent(COMPONENT_TYPE_POSSESSOR, possessorComponent)) {
possessorComponent->Serialize(outBitStream, bIsInitialUpdate, flags);
} else {
// Should never happen, but just to be safe
outBitStream->Write0();
}
characterComponent->Serialize(outBitStream, bIsInitialUpdate, flags);
}
@ -1186,11 +1175,10 @@ void Entity::WriteComponents(RakNet::BitStream* outBitStream, eReplicaPacketType
outBitStream->Write<uint32_t>(0x40000000);
}
PossessorComponent* possessorComponent;
if (TryGetComponent(COMPONENT_TYPE_POSSESSOR, possessorComponent))
{
possessorComponent->Serialize(outBitStream, bIsInitialUpdate, flags);
}
// BBB Component, unused currently
// Need to to write0 so that is serlaizese correctly
// TODO: Implement BBB Component
outBitStream->Write0();
/*
if (m_Trigger != nullptr)
@ -1218,17 +1206,21 @@ void Entity::UpdateXMLDoc(tinyxml2::XMLDocument* doc) {
}
void Entity::Update(const float deltaTime) {
for (int i = 0; i < m_Timers.size(); i++) {
m_Timers[i]->Update(deltaTime);
if (m_Timers[i]->GetTime() <= 0) {
const auto timerName = m_Timers[i]->GetName();
uint32_t timerPosition;
timerPosition = 0;
while (timerPosition < m_Timers.size()) {
m_Timers[timerPosition]->Update(deltaTime);
if (m_Timers[timerPosition]->GetTime() <= 0) {
const auto timerName = m_Timers[timerPosition]->GetName();
delete m_Timers[i];
m_Timers.erase(m_Timers.begin() + i);
delete m_Timers[timerPosition];
m_Timers.erase(m_Timers.begin() + timerPosition);
for (CppScripts::Script* script : CppScripts::GetEntityScripts(this)) {
script->OnTimerDone(this, timerName);
}
} else {
timerPosition++;
}
}
@ -1241,6 +1233,14 @@ void Entity::Update(const float deltaTime) {
}
}
// Add pending timers to the list of timers so they start next tick.
if (m_PendingTimers.size() > 0) {
for (auto namedTimer : m_PendingTimers) {
m_Timers.push_back(namedTimer);
}
m_PendingTimers.clear();
}
if (IsSleeping())
{
Sleep();
@ -1661,12 +1661,30 @@ void Entity::RegisterCoinDrop(uint64_t count) {
}
void Entity::AddChild(Entity* child) {
m_IsParentChildDirty = true;
m_ChildEntities.push_back(child);
}
void Entity::RemoveChild(Entity* child) {
if (!child) return;
uint32_t entityPosition = 0;
while (entityPosition < m_ChildEntities.size()) {
if (!m_ChildEntities[entityPosition] || (m_ChildEntities[entityPosition])->GetObjectID() == child->GetObjectID()) {
m_IsParentChildDirty = true;
m_ChildEntities.erase(m_ChildEntities.begin() + entityPosition);
} else {
entityPosition++;
}
}
}
void Entity::RemoveParent() {
this->m_ParentEntity = nullptr;
}
void Entity::AddTimer(std::string name, float time) {
EntityTimer* timer = new EntityTimer(name, time);
m_Timers.push_back(timer);
m_PendingTimers.push_back(timer);
}
void Entity::AddCallbackTimer(float time, std::function<void()> callback) {

View File

@ -143,6 +143,8 @@ public:
void SetProximityRadius(dpEntity* entity, std::string name);
void AddChild(Entity* child);
void RemoveChild(Entity* child);
void RemoveParent();
void AddTimer(std::string name, float time);
void AddCallbackTimer(float time, std::function<void()> callback);
bool HasTimer(const std::string& name);
@ -308,6 +310,7 @@ protected:
std::unordered_map<int32_t, Component*> m_Components; //The int is the ID of the component
std::vector<EntityTimer*> m_Timers;
std::vector<EntityTimer*> m_PendingTimers;
std::vector<EntityCallbackTimer*> m_CallbackTimers;
bool m_ShouldDestroyAfterUpdate = false;
@ -322,6 +325,8 @@ protected:
int8_t m_Observers = 0;
bool m_IsParentChildDirty = true;
/*
* Collision
*/

View File

@ -217,39 +217,44 @@ void EntityManager::UpdateEntities(const float deltaTime) {
m_EntitiesToKill.clear();
for (const auto& entry : m_EntitiesToDelete)
for (const auto entry : m_EntitiesToDelete)
{
auto* entity = GetEntity(entry);
// Get all this info first before we delete the player.
auto entityToDelete = GetEntity(entry);
m_Entities.erase(entry);
auto networkIdToErase = entityToDelete->GetNetworkId();
const auto& iter = std::find(m_EntitiesToGhost.begin(), m_EntitiesToGhost.end(), entity);
const auto& ghostingToDelete = std::find(m_EntitiesToGhost.begin(), m_EntitiesToGhost.end(), entityToDelete);
if (iter != m_EntitiesToGhost.end())
if (entityToDelete)
{
m_EntitiesToGhost.erase(iter);
}
if (entity != nullptr)
// If we are a player run through the player destructor.
if (entityToDelete->IsPlayer())
{
if (entity->GetNetworkId() != 0)
{
m_LostNetworkIds.push(entity->GetNetworkId());
}
if (entity->IsPlayer())
{
delete dynamic_cast<Player*>(entity);
delete dynamic_cast<Player*>(entityToDelete);
}
else
{
delete entity;
delete entityToDelete;
}
entity = nullptr;
entityToDelete = nullptr;
if (networkIdToErase != 0)
{
m_LostNetworkIds.push(networkIdToErase);
}
}
if (ghostingToDelete != m_EntitiesToGhost.end())
{
m_EntitiesToGhost.erase(ghostingToDelete);
}
m_Entities.erase(entry);
}
m_EntitiesToDelete.clear();
}
@ -396,7 +401,7 @@ void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr
Game::server->Send(&stream, sysAddr, false);
}
PacketUtils::SavePacket("[24]_"+std::to_string(entity->GetObjectID()) + "_" + std::to_string(m_SerializationCounter) + ".bin", (char*)stream.GetData(), stream.GetNumberOfBytesUsed());
// PacketUtils::SavePacket("[24]_"+std::to_string(entity->GetObjectID()) + "_" + std::to_string(m_SerializationCounter) + ".bin", (char*)stream.GetData(), stream.GetNumberOfBytesUsed());
if (entity->IsPlayer())
{

View File

@ -3,8 +3,10 @@
#include "Database.h"
#include "EntityManager.h"
#include "Character.h"
#include "Game.h"
#include "GameMessages.h"
#include "dLogger.h"
#include "dConfig.h"
Leaderboard::Leaderboard(uint32_t gameID, uint32_t infoType, bool weekly, std::vector<LeaderboardEntry> entries,
LWOOBJID relatedPlayer, LeaderboardType leaderboardType) {
@ -90,6 +92,7 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t
const auto storedTime = result->getInt(1);
const auto storedScore = result->getInt(2);
auto highscore = true;
bool classicSurvivalScoring = Game::config->GetValue("classic_survival_scoring") == "1";
switch (leaderboardType) {
case ShootingGallery:
@ -97,8 +100,11 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t
highscore = false;
break;
case Racing:
if (time >= storedTime)
highscore = false;
break;
case MonumentRace:
if (time > storedTime)
if (time >= storedTime)
highscore = false;
break;
case FootRace:
@ -106,8 +112,18 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t
highscore = false;
break;
case Survival:
if (classicSurvivalScoring) {
if (time <= storedTime) { // Based on time (LU live)
highscore = false;
}
}
else {
if (score <= storedScore) // Based on score (DLU)
highscore = false;
}
break;
case SurvivalNS:
if (score < storedScore || time >= storedTime)
if (!(score > storedScore || (time < storedTime && score >= storedScore)))
highscore = false;
break;
default:
@ -125,7 +141,7 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t
delete result;
if (any) {
auto* statement = Database::CreatePreppedStmt("UPDATE leaderboard SET time = ?, score = ? WHERE character_id = ? AND game_id = ?");
auto* statement = Database::CreatePreppedStmt("UPDATE leaderboard SET time = ?, score = ?, last_played=SYSDATE() WHERE character_id = ? AND game_id = ?;");
statement->setInt(1, time);
statement->setInt(2, score);
statement->setUInt64(3, character->GetID());
@ -134,6 +150,7 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t
delete statement;
} else {
// Note: last_played will be set to SYSDATE() by default when inserting into leaderboard
auto* statement = Database::CreatePreppedStmt("INSERT INTO leaderboard (character_id, game_id, time, score) VALUES (?, ?, ?, ?);");
statement->setUInt64(1, character->GetID());
statement->setInt(2, gameID);
@ -149,15 +166,62 @@ Leaderboard *LeaderboardManager::GetLeaderboard(uint32_t gameID, InfoType infoTy
auto leaderboardType = GetLeaderboardType(gameID);
std::string query;
bool classicSurvivalScoring = Game::config->GetValue("classic_survival_scoring") == "1";
switch (infoType) {
case InfoType::Standings:
query = leaderboardType == MonumentRace ? standingsQueryAsc : standingsQuery;
switch (leaderboardType) {
case ShootingGallery:
query = standingsScoreQuery; // Shooting gallery is based on the highest score.
break;
case InfoType::Friends:
query = leaderboardType == MonumentRace ? friendsQueryAsc : friendsQuery;
case FootRace:
query = standingsTimeQuery; // The higher your time, the better for FootRace.
break;
case Survival:
query = classicSurvivalScoring ? standingsTimeQuery : standingsScoreQuery;
break;
case SurvivalNS:
query = standingsScoreQueryAsc; // BoNS is scored by highest wave (score) first, then time.
break;
default:
query = leaderboardType == MonumentRace ? topPlayersQueryAsc : topPlayersQuery;
query = standingsTimeQueryAsc; // MonumentRace and Racing are based on the shortest time.
}
break;
case InfoType::Friends:
switch (leaderboardType) {
case ShootingGallery:
query = friendsScoreQuery; // Shooting gallery is based on the highest score.
break;
case FootRace:
query = friendsTimeQuery; // The higher your time, the better for FootRace.
break;
case Survival:
query = classicSurvivalScoring ? friendsTimeQuery : friendsScoreQuery;
break;
case SurvivalNS:
query = friendsScoreQueryAsc; // BoNS is scored by highest wave (score) first, then time.
break;
default:
query = friendsTimeQueryAsc; // MonumentRace and Racing are based on the shortest time.
}
break;
default:
switch (leaderboardType) {
case ShootingGallery:
query = topPlayersScoreQuery; // Shooting gallery is based on the highest score.
break;
case FootRace:
query = topPlayersTimeQuery; // The higher your time, the better for FootRace.
break;
case Survival:
query = classicSurvivalScoring ? topPlayersTimeQuery : topPlayersScoreQuery;
break;
case SurvivalNS:
query = topPlayersScoreQueryAsc; // BoNS is scored by highest wave (score) first, then time.
break;
default:
query = topPlayersTimeQueryAsc; // MonumentRace and Racing are based on the shortest time.
}
}
auto* statement = Database::CreatePreppedStmt(query);
@ -183,14 +247,15 @@ Leaderboard *LeaderboardManager::GetLeaderboard(uint32_t gameID, InfoType infoTy
uint32_t index = 0;
while (res->next()) {
entries.push_back({
res->getUInt64(4),
res->getString(5),
res->getUInt(1),
res->getUInt(2),
res->getUInt(3),
res->getUInt(6)
});
LeaderboardEntry entry;
entry.playerID = res->getUInt64(4);
entry.playerName = res->getString(5);
entry.time = res->getUInt(1);
entry.score = res->getUInt(2);
entry.placement = res->getUInt(3);
entry.lastPlayed = res->getUInt(6);
entries.push_back(entry);
index++;
}
@ -220,7 +285,7 @@ LeaderboardType LeaderboardManager::GetLeaderboardType(uint32_t gameID) {
return LeaderboardType::None;
}
const std::string LeaderboardManager::topPlayersQuery =
const std::string LeaderboardManager::topPlayersScoreQuery =
"WITH leaderboard_vales AS ( "
" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, "
"RANK() OVER ( ORDER BY l.score DESC, l.time DESC, last_played ) leaderboard_rank "
@ -231,7 +296,7 @@ const std::string LeaderboardManager::topPlayersQuery =
"SELECT time, score, leaderboard_rank, id, name, last_played "
"FROM leaderboard_vales LIMIT 11;";
const std::string LeaderboardManager::friendsQuery =
const std::string LeaderboardManager::friendsScoreQuery =
"WITH leaderboard_vales AS ( "
" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, f.friend_id, f.player_id, "
" RANK() OVER ( ORDER BY l.score DESC, l.time DESC, last_played ) leaderboard_rank "
@ -249,7 +314,7 @@ const std::string LeaderboardManager::friendsQuery =
"FROM leaderboard_vales, personal_values "
"WHERE leaderboard_rank BETWEEN min_rank AND max_rank AND (player_id = related_player_id OR friend_id = related_player_id);";
const std::string LeaderboardManager::standingsQuery =
const std::string LeaderboardManager::standingsScoreQuery =
"WITH leaderboard_vales AS ( "
" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, "
" RANK() OVER ( ORDER BY l.score DESC, l.time DESC, last_played ) leaderboard_rank "
@ -265,7 +330,7 @@ const std::string LeaderboardManager::standingsQuery =
"FROM leaderboard_vales, personal_values "
"WHERE leaderboard_rank BETWEEN min_rank AND max_rank;";
const std::string LeaderboardManager::topPlayersQueryAsc =
const std::string LeaderboardManager::topPlayersScoreQueryAsc =
"WITH leaderboard_vales AS ( "
" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, "
"RANK() OVER ( ORDER BY l.score DESC, l.time ASC, last_played ) leaderboard_rank "
@ -276,7 +341,7 @@ const std::string LeaderboardManager::topPlayersQueryAsc =
"SELECT time, score, leaderboard_rank, id, name, last_played "
"FROM leaderboard_vales LIMIT 11;";
const std::string LeaderboardManager::friendsQueryAsc =
const std::string LeaderboardManager::friendsScoreQueryAsc =
"WITH leaderboard_vales AS ( "
" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, f.friend_id, f.player_id, "
" RANK() OVER ( ORDER BY l.score DESC, l.time ASC, last_played ) leaderboard_rank "
@ -294,7 +359,7 @@ const std::string LeaderboardManager::friendsQueryAsc =
"FROM leaderboard_vales, personal_values "
"WHERE leaderboard_rank BETWEEN min_rank AND max_rank AND (player_id = related_player_id OR friend_id = related_player_id);";
const std::string LeaderboardManager::standingsQueryAsc =
const std::string LeaderboardManager::standingsScoreQueryAsc =
"WITH leaderboard_vales AS ( "
" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, "
" RANK() OVER ( ORDER BY l.score DESC, l.time ASC, last_played ) leaderboard_rank "
@ -309,3 +374,93 @@ const std::string LeaderboardManager::standingsQueryAsc =
"SELECT time, score, leaderboard_rank, id, name, last_played "
"FROM leaderboard_vales, personal_values "
"WHERE leaderboard_rank BETWEEN min_rank AND max_rank;";
const std::string LeaderboardManager::topPlayersTimeQuery =
"WITH leaderboard_vales AS ( "
" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, "
"RANK() OVER ( ORDER BY l.time DESC, l.score DESC, last_played ) leaderboard_rank "
" FROM leaderboard l "
"INNER JOIN charinfo c ON l.character_id = c.id "
"WHERE l.game_id = ? "
"ORDER BY leaderboard_rank) "
"SELECT time, score, leaderboard_rank, id, name, last_played "
"FROM leaderboard_vales LIMIT 11;";
const std::string LeaderboardManager::friendsTimeQuery =
"WITH leaderboard_vales AS ( "
" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, f.friend_id, f.player_id, "
" RANK() OVER ( ORDER BY l.time DESC, l.score DESC, last_played ) leaderboard_rank "
" FROM leaderboard l "
" INNER JOIN charinfo c ON l.character_id = c.id "
" INNER JOIN friends f ON f.player_id = c.id "
" WHERE l.game_id = ? "
" ORDER BY leaderboard_rank), "
" personal_values AS ( "
" SELECT id as related_player_id, "
" GREATEST(CAST(leaderboard_rank AS SIGNED) - 5, 1) AS min_rank, "
" GREATEST(leaderboard_rank + 5, 11) AS max_rank "
" FROM leaderboard_vales WHERE leaderboard_vales.id = ? LIMIT 1) "
"SELECT time, score, leaderboard_rank, id, name, last_played "
"FROM leaderboard_vales, personal_values "
"WHERE leaderboard_rank BETWEEN min_rank AND max_rank AND (player_id = related_player_id OR friend_id = related_player_id);";
const std::string LeaderboardManager::standingsTimeQuery =
"WITH leaderboard_vales AS ( "
" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, "
" RANK() OVER ( ORDER BY l.time DESC, l.score DESC, last_played ) leaderboard_rank "
" FROM leaderboard l "
" INNER JOIN charinfo c ON l.character_id = c.id "
" WHERE l.game_id = ? "
" ORDER BY leaderboard_rank), "
"personal_values AS ( "
" SELECT GREATEST(CAST(leaderboard_rank AS SIGNED) - 5, 1) AS min_rank, "
" GREATEST(leaderboard_rank + 5, 11) AS max_rank "
" FROM leaderboard_vales WHERE id = ? LIMIT 1) "
"SELECT time, score, leaderboard_rank, id, name, last_played "
"FROM leaderboard_vales, personal_values "
"WHERE leaderboard_rank BETWEEN min_rank AND max_rank;";
const std::string LeaderboardManager::topPlayersTimeQueryAsc =
"WITH leaderboard_vales AS ( "
" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, "
"RANK() OVER ( ORDER BY l.time ASC, l.score DESC, last_played ) leaderboard_rank "
" FROM leaderboard l "
"INNER JOIN charinfo c ON l.character_id = c.id "
"WHERE l.game_id = ? "
"ORDER BY leaderboard_rank) "
"SELECT time, score, leaderboard_rank, id, name, last_played "
"FROM leaderboard_vales LIMIT 11;";
const std::string LeaderboardManager::friendsTimeQueryAsc =
"WITH leaderboard_vales AS ( "
" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, f.friend_id, f.player_id, "
" RANK() OVER ( ORDER BY l.time ASC, l.score DESC, last_played ) leaderboard_rank "
" FROM leaderboard l "
" INNER JOIN charinfo c ON l.character_id = c.id "
" INNER JOIN friends f ON f.player_id = c.id "
" WHERE l.game_id = ? "
" ORDER BY leaderboard_rank), "
" personal_values AS ( "
" SELECT id as related_player_id, "
" GREATEST(CAST(leaderboard_rank AS SIGNED) - 5, 1) AS min_rank, "
" GREATEST(leaderboard_rank + 5, 11) AS max_rank "
" FROM leaderboard_vales WHERE leaderboard_vales.id = ? LIMIT 1) "
"SELECT time, score, leaderboard_rank, id, name, last_played "
"FROM leaderboard_vales, personal_values "
"WHERE leaderboard_rank BETWEEN min_rank AND max_rank AND (player_id = related_player_id OR friend_id = related_player_id);";
const std::string LeaderboardManager::standingsTimeQueryAsc =
"WITH leaderboard_vales AS ( "
" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, "
" RANK() OVER ( ORDER BY l.time ASC, l.score DESC, last_played ) leaderboard_rank "
" FROM leaderboard l "
" INNER JOIN charinfo c ON l.character_id = c.id "
" WHERE l.game_id = ? "
" ORDER BY leaderboard_rank), "
"personal_values AS ( "
" SELECT GREATEST(CAST(leaderboard_rank AS SIGNED) - 5, 1) AS min_rank, "
" GREATEST(leaderboard_rank + 5, 11) AS max_rank "
" FROM leaderboard_vales WHERE id = ? LIMIT 1) "
"SELECT time, score, leaderboard_rank, id, name, last_played "
"FROM leaderboard_vales, personal_values "
"WHERE leaderboard_rank BETWEEN min_rank AND max_rank;";

View File

@ -60,11 +60,21 @@ public:
static LeaderboardType GetLeaderboardType(uint32_t gameID);
private:
static LeaderboardManager* address;
static const std::string topPlayersQuery;
static const std::string friendsQuery;
static const std::string standingsQuery;
static const std::string topPlayersQueryAsc;
static const std::string friendsQueryAsc;
static const std::string standingsQueryAsc;
// Modified 12/12/2021: Existing queries were renamed to be more descriptive.
static const std::string topPlayersScoreQuery;
static const std::string friendsScoreQuery;
static const std::string standingsScoreQuery;
static const std::string topPlayersScoreQueryAsc;
static const std::string friendsScoreQueryAsc;
static const std::string standingsScoreQueryAsc;
// Added 12/12/2021: Queries dictated by time are needed for certain minigames.
static const std::string topPlayersTimeQuery;
static const std::string friendsTimeQuery;
static const std::string standingsTimeQuery;
static const std::string topPlayersTimeQueryAsc;
static const std::string friendsTimeQueryAsc;
static const std::string standingsTimeQueryAsc;
};

View File

@ -13,6 +13,7 @@
#include "dZoneManager.h"
#include "CharacterComponent.h"
#include "Mail.h"
#include "CppScripts.h"
std::vector<Player*> Player::m_Players = {};
@ -275,5 +276,21 @@ Player::~Player()
return;
}
if (IsPlayer()) {
Entity* zoneControl = EntityManager::Instance()->GetZoneControlEntity();
for (CppScripts::Script* script : CppScripts::GetEntityScripts(zoneControl)) {
script->OnPlayerExit(zoneControl, this);
}
std::vector<Entity*> scriptedActs = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_SCRIPTED_ACTIVITY);
for (Entity* scriptEntity : scriptedActs) {
if (scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds
for (CppScripts::Script* script : CppScripts::GetEntityScripts(scriptEntity)) {
script->OnPlayerExit(scriptEntity, this);
}
}
}
}
m_Players.erase(iter);
}

View File

@ -369,10 +369,8 @@ void UserManager::DeleteCharacter(const SystemAddress& sysAddr, Packet* packet)
}
LWOOBJID objectID = PacketUtils::ReadPacketS64(8, packet);
objectID = GeneralUtils::ClearBit(objectID, OBJECT_BIT_CHARACTER);
objectID = GeneralUtils::ClearBit(objectID, OBJECT_BIT_PERSISTENT);
uint32_t charID = static_cast<uint32_t>(objectID);
Game::logger->Log("UserManager", "Received char delete req for ID: %llu (%u)\n", objectID, charID);
//Check if this user has this character:
@ -402,10 +400,14 @@ void UserManager::DeleteCharacter(const SystemAddress& sysAddr, Packet* packet)
}
{
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("DELETE FROM friends WHERE player_id=? OR friend_id=?;");
stmt->setUInt64(1, charID);
stmt->setUInt64(2, charID);
stmt->setUInt(1, charID);
stmt->setUInt(2, charID);
stmt->execute();
delete stmt;
CBITSTREAM;
PacketUtils::WriteHeader(bitStream, CHAT_INTERNAL, MSG_CHAT_INTERNAL_PLAYER_REMOVED_NOTIFICATION);
bitStream.Write(objectID);
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
}
{
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("DELETE FROM leaderboard WHERE character_id=?;");

View File

@ -7,6 +7,7 @@
#include "dLogger.h"
#include "BehaviorTemplates.h"
#include "BehaviorBranchContext.h"
#include <unordered_map>
/*
* Behavior includes
@ -45,6 +46,7 @@
#include "InterruptBehavior.h"
#include "PlayEffectBehavior.h"
#include "DamageAbsorptionBehavior.h"
#include "VentureVisionBehavior.h"
#include "BlockBehavior.h"
#include "ClearTargetBehavior.h"
#include "PullToPointBehavior.h"
@ -68,7 +70,7 @@
#include "RenderComponent.h"
#include "DestroyableComponent.h"
std::map<uint32_t, Behavior*> Behavior::Cache = {};
std::unordered_map<uint32_t, Behavior*> Behavior::Cache = {};
CDBehaviorParameterTable* Behavior::BehaviorParameterTable = nullptr;
Behavior* Behavior::GetBehavior(const uint32_t behaviorId)
@ -176,7 +178,9 @@ Behavior* Behavior::CreateBehavior(const uint32_t behaviorId)
case BehaviorTemplates::BEHAVIOR_LOOT_BUFF:
behavior = new LootBuffBehavior(behaviorId);
break;
case BehaviorTemplates::BEHAVIOR_VENTURE_VISION: break;
case BehaviorTemplates::BEHAVIOR_VENTURE_VISION:
behavior = new VentureVisionBehavior(behaviorId);
break;
case BehaviorTemplates::BEHAVIOR_SPAWN_OBJECT:
behavior = new SpawnBehavior(behaviorId);
break;
@ -281,30 +285,23 @@ Behavior* Behavior::CreateBehavior(const uint32_t behaviorId)
return behavior;
}
BehaviorTemplates Behavior::GetBehaviorTemplate(const uint32_t behaviorId)
{
std::stringstream query;
BehaviorTemplates Behavior::GetBehaviorTemplate(const uint32_t behaviorId) {
auto behaviorTemplateTable = CDClientManager::Instance()->GetTable<CDBehaviorTemplateTable>("BehaviorTemplate");
query << "SELECT templateID FROM BehaviorTemplate WHERE behaviorID = " << std::to_string(behaviorId);
auto result = CDClientDatabase::ExecuteQuery(query.str());
// Make sure we do not proceed if we are trying to load an invalid behavior
if (result.eof())
{
if (behaviorId != 0)
{
Game::logger->Log("Behavior::GetBehaviorTemplate", "Failed to load behavior template with id (%i)!\n", behaviorId);
BehaviorTemplates templateID = BehaviorTemplates::BEHAVIOR_EMPTY;
// Find behavior template by its behavior id. Default to 0.
if (behaviorTemplateTable) {
auto templateEntry = behaviorTemplateTable->GetByBehaviorID(behaviorId);
if (templateEntry.behaviorID == behaviorId) {
templateID = static_cast<BehaviorTemplates>(templateEntry.templateID);
}
}
return BehaviorTemplates::BEHAVIOR_EMPTY;
if (templateID == BehaviorTemplates::BEHAVIOR_EMPTY && behaviorId != 0) {
Game::logger->Log("Behavior", "Failed to load behavior template with id (%i)!\n", behaviorId);
}
const auto id = static_cast<BehaviorTemplates>(result.getIntField(0));
result.finalize();
return id;
return templateID;
}
// For use with enemies, to display the correct damage animations on the players
@ -358,18 +355,25 @@ void Behavior::PlayFx(std::u16string type, const LWOOBJID target, const LWOOBJID
}
}
std::stringstream query;
// The SQlite result object becomes invalid if the query object leaves scope.
// So both queries are defined before the if statement
CppSQLite3Query result;
auto typeQuery = CDClientDatabase::CreatePreppedStmt(
"SELECT effectName FROM BehaviorEffect WHERE effectType = ? AND effectID = ?;");
if (!type.empty())
{
query << "SELECT effectName FROM BehaviorEffect WHERE effectType = '" << typeString << "' AND effectID = " << std::to_string(effectId) << ";";
}
else
{
query << "SELECT effectName, effectType FROM BehaviorEffect WHERE effectID = " << std::to_string(effectId) << ";";
}
auto idQuery = CDClientDatabase::CreatePreppedStmt(
"SELECT effectName, effectType FROM BehaviorEffect WHERE effectID = ?;");
auto result = CDClientDatabase::ExecuteQuery(query.str());
if (!type.empty()) {
typeQuery.bind(1, typeString.c_str());
typeQuery.bind(2, (int) effectId);
result = typeQuery.execQuery();
} else {
idQuery.bind(1, (int) effectId);
result = idQuery.execQuery();
}
if (result.eof() || result.fieldIsNull(0))
{
@ -403,6 +407,17 @@ void Behavior::PlayFx(std::u16string type, const LWOOBJID target, const LWOOBJID
Behavior::Behavior(const uint32_t behaviorId)
{
auto behaviorTemplateTable = CDClientManager::Instance()->GetTable<CDBehaviorTemplateTable>("BehaviorTemplate");
CDBehaviorTemplate templateInDatabase{};
if (behaviorTemplateTable) {
auto templateEntry = behaviorTemplateTable->GetByBehaviorID(behaviorId);
if (templateEntry.behaviorID == behaviorId) {
templateInDatabase = templateEntry;
}
}
this->m_behaviorId = behaviorId;
// Add to cache
@ -414,18 +429,8 @@ Behavior::Behavior(const uint32_t behaviorId)
this->m_templateId = BehaviorTemplates::BEHAVIOR_EMPTY;
}
/*
* Get standard info
*/
std::stringstream query;
query << "SELECT templateID, effectID, effectHandle FROM BehaviorTemplate WHERE behaviorID = " << std::to_string(behaviorId);
auto result = CDClientDatabase::ExecuteQuery(query.str());
// Make sure we do not proceed if we are trying to load an invalid behavior
if (result.eof())
if (templateInDatabase.behaviorID == 0)
{
Game::logger->Log("Behavior", "Failed to load behavior with id (%i)!\n", behaviorId);
@ -436,34 +441,19 @@ Behavior::Behavior(const uint32_t behaviorId)
return;
}
this->m_templateId = static_cast<BehaviorTemplates>(result.getIntField(0));
this->m_templateId = static_cast<BehaviorTemplates>(templateInDatabase.templateID);
this->m_effectId = result.getIntField(1);
this->m_effectId = templateInDatabase.effectID;
if (!result.fieldIsNull(2))
{
const std::string effectHandle = result.getStringField(2);
if (effectHandle == "")
{
this->m_effectHandle = nullptr;
}
else
{
this->m_effectHandle = new std::string(effectHandle);
}
}
else
{
this->m_effectHandle = nullptr;
}
result.finalize();
this->m_effectHandle = *templateInDatabase.effectHandle != "" ? new std::string(*templateInDatabase.effectHandle) : nullptr;
}
float Behavior::GetFloat(const std::string& name, const float defaultValue) const
{
return BehaviorParameterTable->GetEntry(this->m_behaviorId, name, defaultValue);
// Get the behavior parameter entry and return its value.
if (!BehaviorParameterTable) BehaviorParameterTable = CDClientManager::Instance()->GetTable<CDBehaviorParameterTable>("BehaviorParameter");
return BehaviorParameterTable->GetEntry(this->m_behaviorId, name, defaultValue).value;
}
@ -493,24 +483,14 @@ Behavior* Behavior::GetAction(float value) const
std::map<std::string, float> Behavior::GetParameterNames() const
{
std::map<std::string, float> parameters;
std::stringstream query;
query << "SELECT parameterID, value FROM BehaviorParameter WHERE behaviorID = " << std::to_string(this->m_behaviorId);
auto tableData = CDClientDatabase::ExecuteQuery(query.str());
while (!tableData.eof())
{
parameters.insert_or_assign(tableData.getStringField(0, ""), tableData.getFloatField(1, 0));
tableData.nextRow();
std::map<std::string, float> templatesInDatabase;
// Find behavior template by its behavior id.
if (!BehaviorParameterTable) BehaviorParameterTable = CDClientManager::Instance()->GetTable<CDBehaviorParameterTable>("BehaviorParameter");
if (BehaviorParameterTable) {
templatesInDatabase = BehaviorParameterTable->GetParametersByBehaviorID(this->m_behaviorId);
}
tableData.finalize();
return parameters;
return templatesInDatabase;
}
void Behavior::Load()

View File

@ -19,7 +19,7 @@ public:
/*
* Static
*/
static std::map<uint32_t, Behavior*> Cache;
static std::unordered_map<uint32_t, Behavior*> Cache;
static CDBehaviorParameterTable* BehaviorParameterTable;
static Behavior* GetBehavior(uint32_t behaviorId);

View File

@ -1,5 +1,8 @@
#pragma once
#ifndef BEHAVIORSLOT_H
#define BEHAVIORSLOT_H
enum class BehaviorSlot
{
Invalid = -1,
@ -9,3 +12,5 @@ enum class BehaviorSlot
Head,
Consumable
};
#endif

View File

@ -0,0 +1,51 @@
set(DGAME_DBEHAVIORS_SOURCES "AirMovementBehavior.cpp"
"AndBehavior.cpp"
"ApplyBuffBehavior.cpp"
"AreaOfEffectBehavior.cpp"
"AttackDelayBehavior.cpp"
"BasicAttackBehavior.cpp"
"Behavior.cpp"
"BehaviorBranchContext.cpp"
"BehaviorContext.cpp"
"BehaviorTemplates.cpp"
"BlockBehavior.cpp"
"BuffBehavior.cpp"
"CarBoostBehavior.cpp"
"ChainBehavior.cpp"
"ChangeOrientationBehavior.cpp"
"ChargeUpBehavior.cpp"
"ClearTargetBehavior.cpp"
"DamageAbsorptionBehavior.cpp"
"DamageReductionBehavior.cpp"
"DurationBehavior.cpp"
"EmptyBehavior.cpp"
"EndBehavior.cpp"
"ForceMovementBehavior.cpp"
"HealBehavior.cpp"
"ImaginationBehavior.cpp"
"ImmunityBehavior.cpp"
"InterruptBehavior.cpp"
"JetPackBehavior.cpp"
"KnockbackBehavior.cpp"
"LootBuffBehavior.cpp"
"MovementSwitchBehavior.cpp"
"NpcCombatSkillBehavior.cpp"
"OverTimeBehavior.cpp"
"PlayEffectBehavior.cpp"
"ProjectileAttackBehavior.cpp"
"PullToPointBehavior.cpp"
"RepairBehavior.cpp"
"SkillCastFailedBehavior.cpp"
"SkillEventBehavior.cpp"
"SpawnBehavior.cpp"
"SpawnQuickbuildBehavior.cpp"
"SpeedBehavior.cpp"
"StartBehavior.cpp"
"StunBehavior.cpp"
"SwitchBehavior.cpp"
"SwitchMultipleBehavior.cpp"
"TacArcBehavior.cpp"
"TargetCasterBehavior.cpp"
"TauntBehavior.cpp"
"VentureVisionBehavior.cpp"
"VerifyBehavior.cpp" PARENT_SCOPE)

View File

@ -39,15 +39,15 @@ void SwitchMultipleBehavior::Calculate(BehaviorContext* context, RakNet::BitStre
// TODO
}
void SwitchMultipleBehavior::Load()
{
const auto b = std::to_string(this->m_behaviorId);
std::stringstream query;
query << "SELECT replace(bP1.parameterID, 'behavior ', '') as key, bP1.value as behavior, "
<< "(select bP2.value FROM BehaviorParameter bP2 WHERE bP2.behaviorID = " << b << " AND bP2.parameterID LIKE 'value %' "
<< "AND replace(bP1.parameterID, 'behavior ', '') = replace(bP2.parameterID, 'value ', '')) as value "
<< "FROM BehaviorParameter bP1 WHERE bP1.behaviorID = " << b << " AND bP1.parameterID LIKE 'behavior %'";
auto result = CDClientDatabase::ExecuteQuery(query.str());
void SwitchMultipleBehavior::Load() {
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT replace(bP1.parameterID, 'behavior ', '') as key, bP1.value as behavior, "
"(select bP2.value FROM BehaviorParameter bP2 WHERE bP2.behaviorID = ?1 AND bP2.parameterID LIKE 'value %' "
"AND replace(bP1.parameterID, 'behavior ', '') = replace(bP2.parameterID, 'value ', '')) as value "
"FROM BehaviorParameter bP1 WHERE bP1.behaviorID = ?1 AND bP1.parameterID LIKE 'behavior %';");
query.bind(1, (int) this->m_behaviorId);
auto result = query.execQuery();
while (!result.eof()) {
const auto behavior_id = static_cast<uint32_t>(result.getFloatField(1));

View File

@ -0,0 +1,47 @@
#include "VentureVisionBehavior.h"
#include "BehaviorBranchContext.h"
#include "CharacterComponent.h"
#include "BehaviorContext.h"
void VentureVisionBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch){
const auto targetEntity = EntityManager::Instance()->GetEntity(branch.target);
if (targetEntity) {
auto characterComponent = targetEntity->GetComponent<CharacterComponent>();
if (characterComponent) {
if (m_show_collectibles) characterComponent->AddVentureVisionEffect(m_ShowCollectibles);
if (m_show_minibosses) characterComponent->AddVentureVisionEffect(m_ShowMiniBosses);
if (m_show_pet_digs) characterComponent->AddVentureVisionEffect(m_ShowPetDigs);
}
if (branch.duration > 0) context->RegisterTimerBehavior(this, branch);
}
}
void VentureVisionBehavior::UnCast(BehaviorContext* context, BehaviorBranchContext branch) {
const auto targetEntity = EntityManager::Instance()->GetEntity(branch.target);
if (targetEntity) {
auto characterComponent = targetEntity->GetComponent<CharacterComponent>();
if (characterComponent) {
if (m_show_collectibles) characterComponent->RemoveVentureVisionEffect(m_ShowCollectibles);
if (m_show_minibosses) characterComponent->RemoveVentureVisionEffect(m_ShowMiniBosses);
if (m_show_pet_digs) characterComponent->RemoveVentureVisionEffect(m_ShowPetDigs);
}
}
}
void VentureVisionBehavior::Timer(BehaviorContext* context, BehaviorBranchContext branch, LWOOBJID second) {
UnCast(context, branch);
}
void VentureVisionBehavior::Load(){
this->m_show_pet_digs = GetBoolean("show_pet_digs");
this->m_show_minibosses = GetBoolean("show_minibosses");
this->m_show_collectibles = GetBoolean("show_collectibles");
}

View File

@ -0,0 +1,41 @@
#pragma once
#ifndef __VENTUREVISIONBEHAVIOR__H__
#define __VENTUREVISIONBEHAVIOR__H__
#include "Behavior.h"
class VentureVisionBehavior final : public Behavior
{
public:
bool m_show_pet_digs;
bool m_show_minibosses;
bool m_show_collectibles;
const std::string m_ShowCollectibles = "bShowCollectibles";
const std::string m_ShowMiniBosses = "bShowMiniBosses";
const std::string m_ShowPetDigs = "bShowPetDigs";
/*
* Inherited
*/
explicit VentureVisionBehavior(const uint32_t behaviorId) : Behavior(behaviorId)
{
}
void Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override;
void UnCast(BehaviorContext* context, BehaviorBranchContext branch) override;
void Timer(BehaviorContext* context, BehaviorBranchContext branch, LWOOBJID second) override;
void Load() override;
};
#endif //!__VENTUREVISIONBEHAVIOR__H__

View File

@ -35,11 +35,11 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id)
m_SoftTimer = 5.0f;
//Grab the aggro information from BaseCombatAI:
std::stringstream componentQuery;
auto componentQuery = CDClientDatabase::CreatePreppedStmt(
"SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius FROM BaseCombatAIComponent WHERE id = ?;");
componentQuery.bind(1, (int) id);
componentQuery << "SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius FROM BaseCombatAIComponent WHERE id = " << std::to_string(id);
auto componentResult = CDClientDatabase::ExecuteQuery(componentQuery.str());
auto componentResult = componentQuery.execQuery();
if (!componentResult.eof())
{
@ -64,12 +64,11 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id)
/*
* Find skills
*/
auto skillQuery = CDClientDatabase::CreatePreppedStmt(
"SELECT skillID, cooldown, behaviorID FROM SkillBehavior WHERE skillID IN (SELECT skillID FROM ObjectSkills WHERE objectTemplate = ?);");
skillQuery.bind(1, (int) parent->GetLOT());
std::stringstream query;
query << "SELECT skillID, cooldown, behaviorID FROM SkillBehavior WHERE skillID IN (SELECT skillID FROM ObjectSkills WHERE objectTemplate = " << std::to_string(parent->GetLOT()) << " )";
auto result = CDClientDatabase::ExecuteQuery(query.str());
auto result = skillQuery.execQuery();
while (!result.eof()) {
const auto skillId = static_cast<uint32_t>(result.getIntField(0));

View File

@ -371,11 +371,11 @@ const std::vector<BuffParameter>& BuffComponent::GetBuffParameters(int32_t buffI
return pair->second;
}
std::stringstream query;
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT * FROM BuffParameters WHERE BuffID = ?;");
query.bind(1, (int) buffId);
query << "SELECT * FROM BuffParameters WHERE BuffID = " << std::to_string(buffId) << ";";
auto result = CDClientDatabase::ExecuteQuery(query.str());
auto result = query.execQuery();
std::vector<BuffParameter> parameters {};

View File

@ -42,6 +42,8 @@ void BuildBorderComponent::OnUse(Entity* originator) {
return;
}
inventoryComponent->PushEquippedItems();
Game::logger->Log("BuildBorderComponent", "Starting with %llu\n", buildArea);
if (PropertyManagementComponent::Instance() != nullptr) {

View File

@ -0,0 +1,40 @@
set(DGAME_DCOMPONENTS_SOURCES "BaseCombatAIComponent.cpp"
"BouncerComponent.cpp"
"BuffComponent.cpp"
"BuildBorderComponent.cpp"
"CharacterComponent.cpp"
"Component.cpp"
"ControllablePhysicsComponent.cpp"
"DestroyableComponent.cpp"
"InventoryComponent.cpp"
"LUPExhibitComponent.cpp"
"MissionComponent.cpp"
"MissionOfferComponent.cpp"
"ModelComponent.cpp"
"ModuleAssemblyComponent.cpp"
"MovementAIComponent.cpp"
"MovingPlatformComponent.cpp"
"PetComponent.cpp"
"PhantomPhysicsComponent.cpp"
"PossessableComponent.cpp"
"PossessorComponent.cpp"
"PropertyComponent.cpp"
"PropertyEntranceComponent.cpp"
"PropertyManagementComponent.cpp"
"PropertyVendorComponent.cpp"
"ProximityMonitorComponent.cpp"
"RacingControlComponent.cpp"
"RailActivatorComponent.cpp"
"RebuildComponent.cpp"
"RenderComponent.cpp"
"RigidbodyPhantomPhysicsComponent.cpp"
"RocketLaunchLupComponent.cpp"
"RocketLaunchpadControlComponent.cpp"
"ScriptedActivityComponent.cpp"
"ShootingGalleryComponent.cpp"
"SimplePhysicsComponent.cpp"
"SkillComponent.cpp"
"SoundTriggerComponent.cpp"
"SwitchComponent.cpp"
"VehiclePhysicsComponent.cpp"
"VendorComponent.cpp" PARENT_SCOPE)

View File

@ -10,7 +10,6 @@
#include "InventoryComponent.h"
#include "ControllablePhysicsComponent.h"
#include "EntityManager.h"
#include "PossessorComponent.h"
#include "VehiclePhysicsComponent.h"
#include "GameMessages.h"
#include "Item.h"
@ -81,13 +80,6 @@ CharacterComponent::~CharacterComponent() {
}
void CharacterComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
outBitStream->Write(m_IsRacing);
if (m_IsRacing) {
outBitStream->Write1();
outBitStream->Write(m_VehicleObjectID);
outBitStream->Write<uint8_t>(0);
}
outBitStream->Write1();
outBitStream->Write(m_Level);
outBitStream->Write0();
@ -793,3 +785,32 @@ ZoneStatistics& CharacterComponent::GetZoneStatisticsForMap(LWOMAPID mapID) {
m_ZoneStatistics.insert({ mapID, {0, 0, 0, 0, 0 } });
return m_ZoneStatistics.at(mapID);
}
void CharacterComponent::AddVentureVisionEffect(std::string ventureVisionType) {
const auto ventureVisionTypeIterator = m_ActiveVentureVisionEffects.find(ventureVisionType);
if (ventureVisionTypeIterator != m_ActiveVentureVisionEffects.end()) {
ventureVisionTypeIterator->second = ++ventureVisionTypeIterator->second;
} else {
// If the effect it not found, insert it into the active effects.
m_ActiveVentureVisionEffects.insert(std::make_pair(ventureVisionType, 1U));
}
UpdateClientMinimap(true, ventureVisionType);
}
void CharacterComponent::RemoveVentureVisionEffect(std::string ventureVisionType) {
const auto ventureVisionTypeIterator = m_ActiveVentureVisionEffects.find(ventureVisionType);
if (ventureVisionTypeIterator != m_ActiveVentureVisionEffects.end()) {
ventureVisionTypeIterator->second = --ventureVisionTypeIterator->second;
UpdateClientMinimap(ventureVisionTypeIterator->second != 0U, ventureVisionType);
}
}
void CharacterComponent::UpdateClientMinimap(bool showFaction, std::string ventureVisionType) const {
if (!m_Parent) return;
AMFArrayValue arrayToSend;
arrayToSend.InsertValue(ventureVisionType, showFaction ? static_cast<AMFValue*>(new AMFTrueValue()) : static_cast<AMFValue*>(new AMFFalseValue()));
GameMessages::SendUIMessageServerToSingleClient(m_Parent, m_Parent ? m_Parent->GetSystemAddress() : UNASSIGNED_SYSTEM_ADDRESS, "SetFactionVisibility", &arrayToSend);
}

View File

@ -143,24 +143,6 @@ public:
*/
void SetIsRacing(bool isRacing) { m_IsRacing = isRacing; }
/**
* Gets the (optional) object ID of the vehicle the character is currently in
* @return the object ID of the vehilce the character is in
*/
const LWOOBJID GetVehicleObjectID() const { return m_VehicleObjectID; }
/**
* Sets the (optional) object ID of the vehicle the character is currently in
* @param vehicleObjectID the ID of the vehicle the character is in
*/
void SetVehicleObjectID(LWOOBJID vehicleObjectID) { m_VehicleObjectID = vehicleObjectID; }
/**
* Sets the possesible type that's currently used, merely used by the shooting gallery if it's 0
* @param value the possesible type to set
*/
void SetPossessableType(uint8_t value) { m_PossessableType = value; }
/**
* Gets whether this character has PvP enabled, allowing combat between players
* @return
@ -197,6 +179,18 @@ 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; }
/**
* Gives the player rewards for the last level that they leveled up from
*/
@ -292,23 +286,38 @@ public:
*/
void UpdatePlayerStatistic(StatisticID updateID, uint64_t updateValue = 1);
/**
* Add a venture vision effect to the player minimap.
*/
void AddVentureVisionEffect(std::string ventureVisionType);
/**
* Remove a venture vision effect from the player minimap.
* When an effect hits 0 active effects, it is deactivated.
*/
void RemoveVentureVisionEffect(std::string ventureVisionType);
/**
* Update the client minimap to reveal the specified factions
*/
void UpdateClientMinimap(bool showFaction, std::string ventureVisionType) const;
/**
* Character info regarding this character, including clothing styles, etc.
*/
Character* m_Character;
private:
/**
* The map of active venture vision effects
*/
std::map<std::string, uint32_t> m_ActiveVentureVisionEffects;
/**
* Whether this character is racing
*/
bool m_IsRacing;
/**
* The object ID of the vehicle the character is currently in
*/
LWOOBJID m_VehicleObjectID;
/**
* Possessible type, used by the shooting gallery
*/
@ -582,6 +591,11 @@ private:
* ID of the last rocket used
*/
LWOOBJID m_LastRocketItemID = LWOOBJID_EMPTY;
/**
* Mount Item ID
*/
LWOOBJID m_MountItemID = LWOOBJID_EMPTY;
};
#endif // CHARACTERCOMPONENT_H

View File

@ -375,11 +375,11 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore
m_FactionIDs.push_back(factionID);
m_DirtyHealth = true;
std::stringstream query;
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT enemyList FROM Factions WHERE faction = ?;");
query.bind(1, (int) factionID);
query << "SELECT enemyList FROM Factions WHERE faction = " << std::to_string(factionID);
auto result = CDClientDatabase::ExecuteQuery(query.str());
auto result = query.execQuery();
if (result.eof()) return;
@ -696,10 +696,10 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
if (owner != nullptr)
{
auto* team = TeamManager::Instance()->GetTeam(owner->GetObjectID());
owner = owner->GetOwner(); // If the owner is overwritten, we collect that here
auto* team = TeamManager::Instance()->GetTeam(owner->GetObjectID());
const auto isEnemy = m_Parent->GetComponent<BaseCombatAIComponent>() != nullptr;
auto* inventoryComponent = owner->GetComponent<InventoryComponent>();

View File

@ -24,6 +24,8 @@
#include "dZoneManager.h"
#include "PropertyManagementComponent.h"
#include "DestroyableComponent.h"
#include "dConfig.h"
#include "eItemType.h"
InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* document) : Component(parent)
{
@ -189,6 +191,7 @@ void InventoryComponent::AddItem(
return;
}
auto* item = new Item(lot, inventory, slot, count, config, parent, showFlyingLoot, isModMoveAndEquip, subKey, bound, lootSourceType);
if (missions != nullptr && !IsTransferInventory(inventoryType))
@ -207,7 +210,8 @@ void InventoryComponent::AddItem(
auto stack = static_cast<uint32_t>(info.stackSize);
if (inventoryType == eInventoryType::BRICKS)
// info.itemType of 1 is item type brick
if (inventoryType == eInventoryType::BRICKS || (stack == 0 && info.itemType == 1))
{
stack = 999;
}
@ -352,8 +356,6 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in
while (left > 0)
{
item = origin->FindItemByLot(lot, ignoreEquipped);
if (item == nullptr)
{
item = origin->FindItemByLot(lot, false);
@ -985,19 +987,11 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks)
// #107
auto* possessorComponent = m_Parent->GetComponent<PossessorComponent>();
if (possessorComponent != nullptr)
{
previousPossessorID = possessorComponent->GetPossessable();
possessorComponent->SetPossessable(carEntity->GetObjectID());
}
if (possessorComponent) possessorComponent->SetPossessable(carEntity->GetObjectID());
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
if (characterComponent != nullptr)
{
characterComponent->SetIsRacing(true);
characterComponent->SetVehicleObjectID(carEntity->GetObjectID());
}
if (characterComponent) characterComponent->SetIsRacing(true);
EntityManager::Instance()->ConstructEntity(carEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
@ -1031,13 +1025,13 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks)
return;
}
if (type == ITEM_TYPE_LOOT_MODEL || type == ITEM_TYPE_VEHICLE)
if (type == eItemType::ITEM_TYPE_LOOT_MODEL || type == eItemType::ITEM_TYPE_VEHICLE)
{
return;
}
}
if (type != ITEM_TYPE_LOOT_MODEL && type != ITEM_TYPE_MODEL)
if (type != eItemType::ITEM_TYPE_LOOT_MODEL && type != eItemType::ITEM_TYPE_MODEL)
{
if (!item->GetBound() && !item->GetPreconditionExpression()->Check(m_Parent))
{
@ -1165,6 +1159,18 @@ void InventoryComponent::PopEquippedItems()
item->Equip();
}
m_Pushed.clear();
auto destroyableComponent = m_Parent->GetComponent<DestroyableComponent>();
// Reset stats to full
if (destroyableComponent) {
destroyableComponent->SetHealth(static_cast<int32_t>(destroyableComponent->GetMaxHealth()));
destroyableComponent->SetArmor(static_cast<int32_t>(destroyableComponent->GetMaxArmor()));
destroyableComponent->SetImagination(static_cast<int32_t>(destroyableComponent->GetMaxImagination()));
EntityManager::Instance()->SerializeEntity(m_Parent);
}
m_Dirty = true;
}
@ -1182,22 +1188,21 @@ bool InventoryComponent::IsEquipped(const LOT lot) const
return false;
}
void InventoryComponent::CheckItemSet(const LOT lot)
{
void InventoryComponent::CheckItemSet(const LOT lot) {
// Check if the lot is in the item set cache
if (std::find(m_ItemSetsChecked.begin(), m_ItemSetsChecked.end(), lot) != m_ItemSetsChecked.end())
{
if (std::find(m_ItemSetsChecked.begin(), m_ItemSetsChecked.end(), lot) != m_ItemSetsChecked.end()) {
return;
}
std::stringstream query;
const std::string lot_query = "%" + std::to_string(lot) + "%";
query << "SELECT setID FROM ItemSets WHERE itemIDs LIKE '%" << std::to_string(lot) << "%'";
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT setID FROM ItemSets WHERE itemIDs LIKE ?;");
query.bind(1, lot_query.c_str());
auto result = CDClientDatabase::ExecuteQuery(query.str());
auto result = query.execQuery();
while (!result.eof())
{
while (!result.eof()) {
const auto id = result.getIntField(0);
bool found = false;
@ -1352,6 +1357,14 @@ void InventoryComponent::SpawnPet(Item* item)
}
}
// First check if we can summon the pet. You need 1 imagination to do so.
auto destroyableComponent = m_Parent->GetComponent<DestroyableComponent>();
if (Game::config->GetValue("pets_take_imagination") == "1" && destroyableComponent && destroyableComponent->GetImagination() <= 0) {
GameMessages::SendUseItemRequirementsResponse(m_Parent->GetObjectID(), m_Parent->GetSystemAddress(), UseItemResponse::NoImaginationForPet);
return;
}
EntityInfo info {};
info.lot = item->GetLot();
info.pos = m_Parent->GetPosition();
@ -1399,15 +1412,15 @@ void InventoryComponent::RemoveDatabasePet(LWOOBJID id)
BehaviorSlot InventoryComponent::FindBehaviorSlot(const eItemType type)
{
switch (type) {
case ITEM_TYPE_HAT:
case eItemType::ITEM_TYPE_HAT:
return BehaviorSlot::Head;
case ITEM_TYPE_NECK:
case eItemType::ITEM_TYPE_NECK:
return BehaviorSlot::Neck;
case ITEM_TYPE_LEFT_HAND:
case eItemType::ITEM_TYPE_LEFT_HAND:
return BehaviorSlot::Offhand;
case ITEM_TYPE_RIGHT_HAND:
case eItemType::ITEM_TYPE_RIGHT_HAND:
return BehaviorSlot::Primary;
case ITEM_TYPE_CONSUMABLE:
case eItemType::ITEM_TYPE_CONSUMABLE:
return BehaviorSlot::Consumable;
default:
return BehaviorSlot::Invalid;
@ -1470,6 +1483,7 @@ std::vector<uint32_t> InventoryComponent::FindBuffs(Item* item, bool castOnEquip
{
missions->Progress(MissionTaskType::MISSION_TASK_TYPE_SKILL, result.skillID);
}
// If item is not a proxy, add its buff to the added buffs.
if (item->GetParent() == LWOOBJID_EMPTY) buffs.push_back(static_cast<uint32_t>(entry.behaviorID));
}
@ -1787,4 +1801,3 @@ void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument* document)
petInventoryElement->LinkEndChild(petElement);
}
}

View File

@ -1,4 +1,7 @@
#pragma once
#pragma once
#ifndef INVENTORYCOMPONENT_H
#define INVENTORYCOMPONENT_H
#include <map>
#include <stack>
@ -22,6 +25,8 @@ class ItemSet;
typedef std::map<std::string, EquippedItem> EquipmentMap;
enum class eItemType : int32_t;
/**
* Handles the inventory of entity, including the items they possess and have equipped. An entity can have inventories
* of different types, each type representing a different group of items, see `eInventoryType` for a list of
@ -445,3 +450,5 @@ private:
*/
void UpdatePetXml(tinyxml2::XMLDocument* document);
};
#endif

View File

@ -450,11 +450,11 @@ const std::vector<uint32_t>& MissionComponent::QueryAchievements(MissionTaskType
}
bool MissionComponent::RequiresItem(const LOT lot) {
std::stringstream query;
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT type FROM Objects WHERE id = ?;");
query.bind(1, (int) lot);
query << "SELECT type FROM Objects WHERE id = " << std::to_string(lot);
auto result = CDClientDatabase::ExecuteQuery(query.str());
auto result = query.execQuery();
if (result.eof()) {
return false;

View File

@ -16,6 +16,7 @@
#include "../dWorldServer/ObjectIDManager.h"
#include "Game.h"
#include "dConfig.h"
#include "dChatFilter.h"
#include "Database.h"
@ -81,6 +82,20 @@ PetComponent::PetComponent(Entity* parent, uint32_t componentId) : Component(par
if (!checkPreconditions.empty()) {
SetPreconditions(checkPreconditions);
}
// Get the imagination drain rate from the CDClient
auto query = CDClientDatabase::CreatePreppedStmt("SELECT imaginationDrainRate FROM PetComponent WHERE id = ?;");
query.bind(1, static_cast<int>(componentId));
auto result = query.execQuery();
// Should a result not exist for this pet default to 60 seconds.
if (!result.eof() && !result.fieldIsNull(0)) {
imaginationDrainRate = result.getFloatField(0, 60.0f);
} else {
imaginationDrainRate = 60.0f;
}
result.finalize();
}
void PetComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags)
@ -172,13 +187,12 @@ void PetComponent::OnUse(Entity* originator)
std::string buildFile;
if (cached == buildCache.end())
{
std::stringstream query;
if (cached == buildCache.end()) {
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT ValidPiecesLXF, PuzzleModelLot, Timelimit, NumValidPieces, imagCostPerBuild FROM TamingBuildPuzzles WHERE NPCLot = ?;");
query.bind(1, (int) m_Parent->GetLOT());
query << "SELECT ValidPiecesLXF, PuzzleModelLot, Timelimit, NumValidPieces, imagCostPerBuild FROM TamingBuildPuzzles WHERE NPCLot = " << std::to_string(m_Parent->GetLOT()) << ";";
auto result = CDClientDatabase::ExecuteQuery(query.str());
auto result = query.execQuery();
if (result.eof())
{
@ -637,7 +651,7 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position)
inventoryComponent->SetDatabasePet(petSubKey, databasePet);
Activate(item, false);
Activate(item, false, true);
m_Timer = 0;
@ -898,8 +912,10 @@ void PetComponent::Wander()
m_Timer += (m_MovementAI->GetCurrentPosition().x - destination.x) / info.wanderSpeed;
}
void PetComponent::Activate(Item* item, bool registerPet)
void PetComponent::Activate(Item* item, bool registerPet, bool fromTaming)
{
AddDrainImaginationTimer(item, fromTaming);
m_ItemId = item->GetId();
m_DatabaseId = item->GetSubKey();
@ -969,6 +985,44 @@ void PetComponent::Activate(Item* item, bool registerPet)
GameMessages::SendShowPetActionButton(m_Owner, 3, true, owner->GetSystemAddress());
}
void PetComponent::AddDrainImaginationTimer(Item* item, bool fromTaming) {
if (Game::config->GetValue("pets_take_imagination") != "1") return;
auto playerInventory = item->GetInventory();
if (!playerInventory) return;
auto playerInventoryComponent = playerInventory->GetComponent();
if (!playerInventoryComponent) return;
auto playerEntity = playerInventoryComponent->GetParent();
if (!playerEntity) return;
auto playerDestroyableComponent = playerEntity->GetComponent<DestroyableComponent>();
if (!playerDestroyableComponent) return;
// Drain by 1 when you summon pet or when this method is called, but not when we have just tamed this pet.
if (!fromTaming) playerDestroyableComponent->Imagine(-1);
// Set this to a variable so when this is called back from the player the timer doesn't fire off.
m_Parent->AddCallbackTimer(imaginationDrainRate, [playerDestroyableComponent, this, item](){
if (!playerDestroyableComponent) {
Game::logger->Log("PetComponent", "No petComponent and/or no playerDestroyableComponent\n");
return;
}
// If we are out of imagination despawn the pet.
if (playerDestroyableComponent->GetImagination() == 0) {
this->Deactivate();
auto playerEntity = playerDestroyableComponent->GetParent();
if (!playerEntity) return;
GameMessages::SendUseItemRequirementsResponse(playerEntity->GetObjectID(), playerEntity->GetSystemAddress(), UseItemResponse::NoImaginationForPet);
}
this->AddDrainImaginationTimer(item);
});
}
void PetComponent::Deactivate()
{
GameMessages::SendPlayFXEffect(m_Parent->GetObjectID(), -1, u"despawn", "", LWOOBJID_EMPTY, 1, 1, true);

View File

@ -82,7 +82,7 @@ public:
* @param item the item to create the pet from
* @param registerPet notifies the client that the pet was spawned, not necessary if this pet is being tamed
*/
void Activate(Item* item, bool registerPet = true);
void Activate(Item* item, bool registerPet = true, bool fromTaming = false);
/**
* Despawns the pet
@ -203,6 +203,14 @@ public:
*/
static PetComponent* GetActivePet(LWOOBJID owner);
/**
* Adds the timer to the owner of this pet to drain imagination at the rate
* specified by the parameter imaginationDrainRate
*
* @param item The item that represents this pet in the inventory.
*/
void AddDrainImaginationTimer(Item* item, bool fromTaming = false);
private:
/**
@ -346,4 +354,9 @@ private:
* Preconditions that need to be met before an entity can tame this pet
*/
PreconditionExpression* m_Preconditions;
/**
* The rate at which imagination is drained from the user for having the pet out.
*/
float imaginationDrainRate;
};

View File

@ -1,51 +1,46 @@
#include "PossessableComponent.h"
#include "PossessorComponent.h"
#include "EntityManager.h"
#include "Inventory.h"
#include "Item.h"
PossessableComponent::PossessableComponent(Entity* parent) : Component(parent)
{
PossessableComponent::PossessableComponent(Entity* parent, uint32_t componentId) : Component(parent){
m_Possessor = LWOOBJID_EMPTY;
}
CDItemComponent item = Inventory::FindItemComponent(m_Parent->GetLOT());
m_AnimationFlag = static_cast<eAnimationFlags>(item.animationFlag);
PossessableComponent::~PossessableComponent()
{
// Get the possession Type from the CDClient
auto query = CDClientDatabase::CreatePreppedStmt("SELECT possessionType, depossessOnHit FROM PossessableComponent WHERE id = ?;");
}
query.bind(1, static_cast<int>(componentId));
void PossessableComponent::SetPossessor(LWOOBJID value)
{
m_Possessor = value;
}
auto result = query.execQuery();
LWOOBJID PossessableComponent::GetPossessor() const
{
return m_Possessor;
}
void PossessableComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags)
{
outBitStream->Write(m_Possessor != LWOOBJID_EMPTY);
if (m_Possessor != LWOOBJID_EMPTY)
{
outBitStream->Write1();
outBitStream->Write(m_Possessor);
outBitStream->Write0();
outBitStream->Write0();
// Should a result not exist for this default to attached visible
if (!result.eof()) {
m_PossessionType = static_cast<ePossessionType>(result.getIntField(0, 0));
m_DepossessOnHit = static_cast<bool>(result.getIntField(1, 0));
} else {
m_PossessionType = ePossessionType::ATTACHED_VISIBLE;
m_DepossessOnHit = false;
}
result.finalize();
}
void PossessableComponent::Update(float deltaTime)
{
void PossessableComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
outBitStream->Write(m_DirtyPossessable || bIsInitialUpdate);
if (m_DirtyPossessable || bIsInitialUpdate) {
m_DirtyPossessable = false;
outBitStream->Write(m_Possessor != LWOOBJID_EMPTY);
if (m_Possessor != LWOOBJID_EMPTY) outBitStream->Write(m_Possessor);
outBitStream->Write(m_AnimationFlag != eAnimationFlags::IDLE_INVALID);
if(m_AnimationFlag != eAnimationFlags::IDLE_INVALID) outBitStream->Write(m_AnimationFlag);
outBitStream->Write(m_ImmediatelyDepossess);
}
}
void PossessableComponent::OnUse(Entity* originator) {
PossessorComponent* possessorComponent;
if (originator->TryGetComponent(COMPONENT_TYPE_POSSESSOR, possessorComponent)) {
SetPossessor(originator->GetObjectID());
possessorComponent->SetPossessable(m_Parent->GetObjectID());
EntityManager::Instance()->SerializeEntity(m_Parent);
EntityManager::Instance()->SerializeEntity(originator);
}
// TODO: Implement this
}

View File

@ -3,44 +3,113 @@
#include "BitStream.h"
#include "Entity.h"
#include "Component.h"
#include "Item.h"
#include "PossessorComponent.h"
#include "eAninmationFlags.h"
/**
* Represents an entity that can be controlled by some other entity, generally used by cars to indicate that some
* player is controlling it.
*/
class PossessableComponent : public Component {
public:
public:
static const uint32_t ComponentType = COMPONENT_TYPE_POSSESSABLE;
PossessableComponent(Entity* parentEntity);
~PossessableComponent() override;
PossessableComponent(Entity* parentEntity, uint32_t componentId);
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags);
void Update(float deltaTime) override;
/**
* Sets the possessor of this entity
* @param value the ID of the possessor to set
*/
void SetPossessor(LWOOBJID value);
void SetPossessor(LWOOBJID value) { m_Possessor = value; m_DirtyPossessable = true;};
/**
* Returns the possessor of this entity
* @return the possessor of this entitythe
* @return the possessor of this entity
*/
LWOOBJID GetPossessor() const;
LWOOBJID GetPossessor() const { return m_Possessor; };
/**
* Handles an OnUsed event by some other entity, if said entity has a PossessorComponent it becomes the possessor
* Sets the animation Flag of the possessable
* @param value the animation flag to set to
*/
void SetAnimationFlag(eAnimationFlags value) { m_AnimationFlag = value; m_DirtyPossessable = true;};
/**
* Returns the possession type of this entity
* @return the possession type of this entity
*/
ePossessionType GetPossessionType() const { return m_PossessionType; };
/**
* Returns if the entity should deposses on hit
* @return if the entity should deposses on hit
*/
bool GetDepossessOnHit() const { return m_DepossessOnHit; };
/**
* Forcibly depossess the entity
*/
void ForceDepossess() { m_ImmediatelyDepossess = true; m_DirtyPossessable = true;};
/**
* Set if the parent entity was spawned from an item
* @param value if the parent entity was spawned from an item
*/
void SetItemSpawned(bool value) { m_ItemSpawned = value;};
/**
* Returns if the parent entity was spawned from an item
* @return if the parent entity was spawned from an item
*/
LWOOBJID GetItemSpawned() const { return m_ItemSpawned; };
/**
* Handles an OnUsed event by some other entity, if said entity has a Possessor it becomes the possessor
* of this entity
* @param originator the entity that caused the event to trigger
*/
void OnUse(Entity* originator) override;
private:
private:
/**
* The possessor of this entity, e.g. the entity that controls this entity
* @brief Whether the possessor is dirty
*/
LWOOBJID m_Possessor;
bool m_DirtyPossessable = true;
/**
* @brief The possessor of this entity, e.g. the entity that controls this entity
*/
LWOOBJID m_Possessor = LWOOBJID_EMPTY;
/**
* @brief The type of possesstion to use on this entity
*/
ePossessionType m_PossessionType = ePossessionType::NO_POSSESSION;
/**
* @brief Should the possessable be dismount on hit
*/
bool m_DepossessOnHit = false;
/**
* @brief What animaiton flag to use
*
*/
eAnimationFlags m_AnimationFlag = eAnimationFlags::IDLE_INVALID;
/**
* @brief Should this be immediately depossessed
*
*/
bool m_ImmediatelyDepossess = false;
/**
* @brief Whether the parent entity was spawned from an item
*
*/
bool m_ItemSpawned = false;
};

View File

@ -1,35 +1,21 @@
#include "PossessorComponent.h"
PossessorComponent::PossessorComponent(Entity* parent) : Component(parent)
{
PossessorComponent::PossessorComponent(Entity* parent) : Component(parent) {
m_Possessable = LWOOBJID_EMPTY;
}
PossessorComponent::~PossessorComponent()
{
PossessorComponent::~PossessorComponent() {}
}
void PossessorComponent::SetPossessable(LWOOBJID value)
{
m_Possessable = value;
}
LWOOBJID PossessorComponent::GetPossessable() const
{
return m_Possessable;
}
void PossessorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags)
{
void PossessorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
outBitStream->Write(m_DirtyPossesor || bIsInitialUpdate);
if (m_DirtyPossesor || bIsInitialUpdate) {
m_DirtyPossesor = false;
outBitStream->Write(m_Possessable != LWOOBJID_EMPTY);
if (m_Possessable != LWOOBJID_EMPTY)
{
if (m_Possessable != LWOOBJID_EMPTY) {
outBitStream->Write(m_Possessable);
}
}
void PossessorComponent::Update(float deltaTime)
{
outBitStream->Write(m_PossessableType);
}
}

View File

@ -4,35 +4,78 @@
#include "Entity.h"
#include "Component.h"
// possession types
enum class ePossessionType : uint8_t {
NO_POSSESSION = 0,
ATTACHED_VISIBLE,
NOT_ATTACHED_VISIBLE,
NOT_ATTACHED_NOT_VISIBLE,
};
/**
* Represents an entity that can posess other entities. Generally used by players to drive a car.
*/
class PossessorComponent : public Component {
public:
public:
static const uint32_t ComponentType = COMPONENT_TYPE_POSSESSOR;
PossessorComponent(Entity* parent);
~PossessorComponent() override;
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags);
void Update(float deltaTime) override;
/**
* Sets the entity that this entity is possessing
* @param value the ID of the entity this ID should posess
*/
void SetPossessable(LWOOBJID value);
void SetPossessable(LWOOBJID value) { m_Possessable = value; m_DirtyPossesor = true; }
/**
* Returns the entity that this entity is currently posessing
* @return the entity that this entity is currently posessing
*/
LWOOBJID GetPossessable() const;
LWOOBJID GetPossessable() const { return m_Possessable; }
private:
/**
* Sets if we are busy mounting or dismounting
* @param value if we are busy mounting or dismounting
*/
void SetIsBusy(bool value) { m_IsBusy = value; }
/**
* Returns if we are busy mounting or dismounting
* @return if we are busy mounting or dismounting
*/
bool GetIsBusy() const { return m_IsBusy; }
/**
* Sets the possesible type that's currently used, merely used by the shooting gallery if it's 0
* @param value the possesible type to set
*/
void SetPossessableType(ePossessionType value) { m_PossessableType = value; m_DirtyPossesor = true; }
private:
/**
* The ID of the entity this entity is possessing (e.g. the ID of a car)
*/
LWOOBJID m_Possessable;
LWOOBJID m_Possessable = LWOOBJID_EMPTY;
/**
* @brief possessable type
*
*/
ePossessionType m_PossessableType = ePossessionType::NO_POSSESSION;
/**
* @brief if the possessor is dirty
*
*/
bool m_DirtyPossesor = false;
/**
* @brief if the possessor is busy mounting or dismounting
*
*/
bool m_IsBusy = false;
};

View File

@ -102,7 +102,7 @@ PropertySelectQueryProperty PropertyEntranceComponent::SetPropertyValues(Propert
return property;
}
std::string PropertyEntranceComponent::BuildQuery(Entity* entity, int32_t sortMethod, std::string customQuery, bool wantLimits) {
std::string PropertyEntranceComponent::BuildQuery(Entity* entity, int32_t sortMethod, Character* character, std::string customQuery, bool wantLimits) {
std::string base;
if (customQuery == "") {
base = baseQueryForProperties;
@ -115,15 +115,13 @@ std::string PropertyEntranceComponent::BuildQuery(Entity* entity, int32_t sortMe
auto friendsListQuery = Database::CreatePreppedStmt("SELECT * FROM (SELECT CASE WHEN player_id = ? THEN friend_id WHEN friend_id = ? THEN player_id END AS requested_player FROM friends ) AS fr WHERE requested_player IS NOT NULL ORDER BY requested_player DESC;");
friendsListQuery->setInt64(1, entity->GetObjectID());
friendsListQuery->setInt64(2, entity->GetObjectID());
friendsListQuery->setUInt(1, character->GetID());
friendsListQuery->setUInt(2, character->GetID());
auto friendsListQueryResult = friendsListQuery->executeQuery();
while (friendsListQueryResult->next()) {
auto playerIDToConvert = friendsListQueryResult->getInt64(1);
playerIDToConvert = GeneralUtils::ClearBit(playerIDToConvert, OBJECT_BIT_CHARACTER);
playerIDToConvert = GeneralUtils::ClearBit(playerIDToConvert, OBJECT_BIT_PERSISTENT);
auto playerIDToConvert = friendsListQueryResult->getInt(1);
friendsList = friendsList + std::to_string(playerIDToConvert) + ",";
}
// Replace trailing comma with the closing parenthesis.
@ -172,8 +170,8 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
// If the player has a property this query will have a single result.
if (playerPropertyLookupResults->next()) {
const auto cloneId = playerPropertyLookupResults->getUInt64(4);
const auto propertyName = playerPropertyLookupResults->getString(5).asStdString();
const auto propertyDescription = playerPropertyLookupResults->getString(6).asStdString();
const auto propertyName = std::string(playerPropertyLookupResults->getString(5).c_str());
const auto propertyDescription = std::string(playerPropertyLookupResults->getString(6).c_str());
const auto privacyOption = playerPropertyLookupResults->getInt(9);
const auto modApproved = playerPropertyLookupResults->getBoolean(10);
const auto dateLastUpdated = playerPropertyLookupResults->getInt64(11);
@ -193,7 +191,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
entries.push_back(playerEntry);
const auto query = BuildQuery(entity, sortMethod);
const auto query = BuildQuery(entity, sortMethod, character);
auto propertyLookup = Database::CreatePreppedStmt(query);
@ -212,8 +210,8 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
const auto propertyId = propertyEntry->getUInt64(1);
const auto owner = propertyEntry->getInt(2);
const auto cloneId = propertyEntry->getUInt64(4);
const auto propertyNameFromDb = propertyEntry->getString(5).asStdString();
const auto propertyDescriptionFromDb = propertyEntry->getString(6).asStdString();
const auto propertyNameFromDb = std::string(propertyEntry->getString(5).c_str());
const auto propertyDescriptionFromDb = std::string(propertyEntry->getString(6).c_str());
const auto privacyOption = propertyEntry->getInt(9);
const auto modApproved = propertyEntry->getBoolean(10);
const auto dateLastUpdated = propertyEntry->getInt(11);
@ -239,7 +237,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
continue;
} else {
isOwned = cloneId == character->GetPropertyCloneID();
ownerName = nameResult->getString(1).asStdString();
ownerName = std::string(nameResult->getString(1).c_str());
}
delete nameResult;
@ -262,17 +260,17 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
// Query to get friend and best friend fields
auto friendCheck = Database::CreatePreppedStmt("SELECT best_friend FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?)");
friendCheck->setInt64(1, entity->GetObjectID());
friendCheck->setInt64(2, ownerObjId);
friendCheck->setInt64(3, ownerObjId);
friendCheck->setInt64(4, entity->GetObjectID());
friendCheck->setUInt(1, character->GetID());
friendCheck->setUInt(2, ownerObjId);
friendCheck->setUInt(3, ownerObjId);
friendCheck->setUInt(4, character->GetID());
auto friendResult = friendCheck->executeQuery();
// If we got a result than the two players are friends.
if (friendResult->next()) {
isFriend = true;
if (friendResult->getInt(1) == 2) {
if (friendResult->getInt(1) == 3) {
isBestFriend = true;
}
}
@ -326,7 +324,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
// Query here is to figure out whether or not to display the button to go to the next page or not.
int32_t numberOfProperties = 0;
auto buttonQuery = BuildQuery(entity, sortMethod, "SELECT COUNT(*) FROM properties as p JOIN charinfo as ci ON ci.prop_clone_id = p.clone_id where p.zone_id = ? AND (p.description LIKE ? OR p.name LIKE ? OR ci.name LIKE ?) AND p.privacy_option >= ? ", false);
auto buttonQuery = BuildQuery(entity, sortMethod, character, "SELECT COUNT(*) FROM properties as p JOIN charinfo as ci ON ci.prop_clone_id = p.clone_id where p.zone_id = ? AND (p.description LIKE ? OR p.name LIKE ? OR ci.name LIKE ?) AND p.privacy_option >= ? ", false);
auto propertiesLeft = Database::CreatePreppedStmt(buttonQuery);
propertiesLeft->setUInt(1, this->m_MapID);

View File

@ -60,7 +60,7 @@ class PropertyEntranceComponent : public Component {
PropertySelectQueryProperty SetPropertyValues(PropertySelectQueryProperty property, LWOCLONEID cloneId = LWOCLONEID_INVALID, std::string ownerName = "", std::string propertyName = "", std::string propertyDescription = "", float reputation = 0, bool isBFF = false, bool isFriend = false, bool isModeratorApproved = false, bool isAlt = false, bool isOwned = false, uint32_t privacyOption = 0, uint32_t timeLastUpdated = 0, float performanceCost = 0.0f);
std::string BuildQuery(Entity* entity, int32_t sortMethod, std::string customQuery = "", bool wantLimits = true);
std::string BuildQuery(Entity* entity, int32_t sortMethod, Character* character, std::string customQuery = "", bool wantLimits = true);
private:
/**

View File

@ -2,6 +2,7 @@
#include <sstream>
#include "MissionComponent.h"
#include "EntityManager.h"
#include "PropertyDataMessage.h"
#include "UserManager.h"
@ -40,11 +41,11 @@ PropertyManagementComponent::PropertyManagementComponent(Entity* parent) : Compo
const auto zoneId = worldId.GetMapID();
const auto cloneId = worldId.GetCloneID();
std::stringstream query;
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT id FROM PropertyTemplate WHERE mapID = ?;");
query.bind(1, (int) zoneId);
query << "SELECT id FROM PropertyTemplate WHERE mapID = " << std::to_string(zoneId) << ";";
auto result = CDClientDatabase::ExecuteQuery(query.str());
auto result = query.execQuery();
if (result.eof() || result.fieldIsNull(0))
{
@ -75,7 +76,7 @@ PropertyManagementComponent::PropertyManagementComponent(Entity* parent) : Compo
this->moderatorRequested = propertyEntry->getInt(10) == 0 && rejectionReason == "" && privacyOption == PropertyPrivacyOption::Public;
this->LastUpdatedTime = propertyEntry->getUInt64(11);
this->claimedTime = propertyEntry->getUInt64(12);
this->rejectionReason = propertyEntry->getString(13).asStdString();
this->rejectionReason = std::string(propertyEntry->getString(13).c_str());
this->reputation = propertyEntry->getUInt(14);
Load();
@ -103,11 +104,11 @@ std::vector<NiPoint3> PropertyManagementComponent::GetPaths() const
{
const auto zoneId = dZoneManager::Instance()->GetZone()->GetWorldID();
std::stringstream query {};
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT path FROM PropertyTemplate WHERE mapID = ?;");
query.bind(1, (int) zoneId);
query << "SELECT path FROM PropertyTemplate WHERE mapID = " << std::to_string(zoneId) << ";";
auto result = CDClientDatabase::ExecuteQuery(query.str());
auto result = query.execQuery();
std::vector<NiPoint3> paths {};
@ -285,6 +286,10 @@ void PropertyManagementComponent::OnStartBuilding()
player->SendToZone(zoneId);
}
auto inventoryComponent = ownerEntity->GetComponent<InventoryComponent>();
// Push equipped items
if (inventoryComponent) inventoryComponent->PushEquippedItems();
}
void PropertyManagementComponent::OnFinishBuilding()
@ -865,7 +870,7 @@ void PropertyManagementComponent::OnQueryPropertyData(Entity* originator, const
result->next();
const auto reason = result->getString(1).asStdString();;
const auto reason = std::string(result->getString(1).c_str());
const auto modApproved = result->getInt(2);
if (reason != "") {
moderatorRequested = false;

View File

@ -212,6 +212,7 @@ void RacingControlComponent::LoadPlayerVehicle(Entity *player,
if (possessorComponent != nullptr) {
possessorComponent->SetPossessable(carEntity->GetObjectID());
possessorComponent->SetPossessableType(ePossessionType::ATTACHED_VISIBLE); // for racing it's always Attached_Visible
}
// Set the player's current activity as racing.
@ -219,7 +220,6 @@ void RacingControlComponent::LoadPlayerVehicle(Entity *player,
if (characterComponent != nullptr) {
characterComponent->SetIsRacing(true);
characterComponent->SetVehicleObjectID(carEntity->GetObjectID());
}
// Init the player's racing entry.

View File

@ -6,6 +6,8 @@
#include "Game.h"
#include "dLogger.h"
#include "CharacterComponent.h"
#include "MissionComponent.h"
#include "MissionTaskType.h"
#include "dServer.h"
#include "PacketUtils.h"
@ -22,6 +24,20 @@ RebuildComponent::RebuildComponent(Entity* entity) : Component(entity) {
{
m_Precondition = new PreconditionExpression(GeneralUtils::UTF16ToWTF8(checkPreconditions));
}
// Should a setting that has the build activator position exist, fetch that setting here and parse it for position.
// It is assumed that the user who sets this setting uses the correct character delimiter (character 31 or in hex 0x1F)
auto positionAsVector = GeneralUtils::SplitString(m_Parent->GetVarAsString(u"rebuild_activators"), 0x1F);
if (positionAsVector.size() == 3 &&
GeneralUtils::TryParse(positionAsVector[0], m_ActivatorPosition.x) &&
GeneralUtils::TryParse(positionAsVector[1], m_ActivatorPosition.y) &&
GeneralUtils::TryParse(positionAsVector[2], m_ActivatorPosition.z)) {
} else {
Game::logger->Log("RebuildComponent", "Failed to find activator position for lot %i. Defaulting to parents position.\n", m_Parent->GetLOT());
m_ActivatorPosition = m_Parent->GetPosition();
}
SpawnActivator();
}
RebuildComponent::~RebuildComponent() {

View File

@ -198,14 +198,16 @@ void RenderComponent::PlayEffect(const int32_t effectId, const std::u16string& e
return;
}
std::stringstream query;
const std::string effectType_str = GeneralUtils::UTF16ToWTF8(effectType);
query << "SELECT animation_length FROM Animations WHERE animation_type IN (SELECT animationName FROM BehaviorEffect WHERE effectID = " << std::to_string(effectId) << " AND effectType = '" << GeneralUtils::UTF16ToWTF8(effectType) << "');";
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT animation_length FROM Animations WHERE animation_type IN (SELECT animationName FROM BehaviorEffect WHERE effectID = ? AND effectType = ?);");
query.bind(1, effectId);
query.bind(2, effectType_str.c_str());
auto result = CDClientDatabase::ExecuteQuery(query.str());
auto result = query.execQuery();
if (result.eof() || result.fieldIsNull(0))
{
if (result.eof() || result.fieldIsNull(0)) {
result.finalize();
m_DurationCache[effectId] = 0;

View File

@ -1,21 +1,17 @@
#include "RocketLaunchLupComponent.h"
#include "CDClientDatabase.h"
#include "RocketLaunchpadControlComponent.h"
#include "InventoryComponent.h"
#include "CharacterComponent.h"
RocketLaunchLupComponent::RocketLaunchLupComponent(Entity* parent) : Component(parent) {
m_Parent = parent;
// get the lup worlds from the cdclient
std::string query = "SELECT * FROM LUPZoneIDs;";
auto results = CDClientDatabase::ExecuteQuery(query);
while (!results.eof()) {
// fallback to 1600 incase there is an issue
m_LUPWorlds.push_back(results.getIntField(0, 1600));
results.nextRow();
std::string zoneString = GeneralUtils::UTF16ToWTF8(m_Parent->GetVar<std::u16string>(u"MultiZoneIDs"));
std::stringstream ss(zoneString);
for (int i; ss >> i;) {
m_LUPWorlds.push_back(i);
if (ss.peek() == ';')
ss.ignore();
}
results.finalize();
}
RocketLaunchLupComponent::~RocketLaunchLupComponent() {}
@ -29,11 +25,7 @@ void RocketLaunchLupComponent::OnUse(Entity* originator) {
}
void RocketLaunchLupComponent::OnSelectWorld(Entity* originator, uint32_t index) {
// Add one to index because the actual LUP worlds start at index 1.
index++;
auto* rocketLaunchpadControlComponent = m_Parent->GetComponent<RocketLaunchpadControlComponent>();
if (!rocketLaunchpadControlComponent) return;
rocketLaunchpadControlComponent->Launch(originator, m_LUPWorlds[index], 0);

View File

@ -19,11 +19,11 @@
#include "PacketUtils.h"
RocketLaunchpadControlComponent::RocketLaunchpadControlComponent(Entity* parent, int rocketId) : Component(parent) {
std::stringstream query;
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT targetZone, defaultZoneID, targetScene, altLandingPrecondition, altLandingSpawnPointName FROM RocketLaunchpadControlComponent WHERE id = ?;");
query.bind(1, rocketId);
query << "SELECT targetZone, defaultZoneID, targetScene, altLandingPrecondition, altLandingSpawnPointName FROM RocketLaunchpadControlComponent WHERE id = " << std::to_string(rocketId);
auto result = CDClientDatabase::ExecuteQuery(query.str());
auto result = query.execQuery();
if (!result.eof() && !result.fieldIsNull(0))
{

View File

@ -487,7 +487,10 @@ void ActivityInstance::StartZone() {
return;
auto* leader = participants[0];
LWOZONEID zoneId = LWOZONEID(m_ActivityInfo.instanceMapID, 0, leader->GetCharacter()->GetPropertyCloneID());
// only make a team if we have more than one participant
if (participants.size() > 1){
CBITSTREAM;
PacketUtils::WriteHeader(bitStream, CHAT_INTERNAL, MSG_CHAT_INTERNAL_CREATE_TEAM);
@ -498,11 +501,10 @@ void ActivityInstance::StartZone() {
bitStream.Write(participant);
}
LWOZONEID zoneId = LWOZONEID(m_ActivityInfo.instanceMapID, 0, leader->GetCharacter()->GetPropertyCloneID());
bitStream.Write(zoneId);
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
}
const auto cloneId = GeneralUtils::GenerateRandomNumber<uint32_t>(1, UINT32_MAX);
for (Entity* player : participants) {

View File

@ -17,6 +17,17 @@ SimplePhysicsComponent::SimplePhysicsComponent(uint32_t componentID, Entity* par
m_Position = m_Parent->GetDefaultPosition();
m_Rotation = m_Parent->GetDefaultRotation();
m_IsDirty = true;
const auto& climbable_type = m_Parent->GetVar<std::u16string>(u"climbable");
if (climbable_type == u"wall") {
SetClimbableType(eClimbableType::CLIMBABLE_TYPE_WALL);
} else if (climbable_type == u"ladder") {
SetClimbableType(eClimbableType::CLIMBABLE_TYPE_LADDER);
} else if (climbable_type == u"wallstick") {
SetClimbableType(eClimbableType::CLIMBABLE_TYPE_WALL_STICK);
} else {
SetClimbableType(eClimbableType::CLIMBABLE_TYPE_NOT);
}
}
SimplePhysicsComponent::~SimplePhysicsComponent() {
@ -24,8 +35,8 @@ SimplePhysicsComponent::~SimplePhysicsComponent() {
void SimplePhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
if (bIsInitialUpdate) {
outBitStream->Write0(); // climbable
outBitStream->Write<int32_t>(0); // climbableType
outBitStream->Write(m_ClimbableType != eClimbableType::CLIMBABLE_TYPE_NOT);
outBitStream->Write(m_ClimbableType);
}
outBitStream->Write(m_DirtyVelocity || bIsInitialUpdate);

View File

@ -14,6 +14,14 @@
class Entity;
enum class eClimbableType : int32_t {
CLIMBABLE_TYPE_NOT = 0,
CLIMBABLE_TYPE_LADDER,
CLIMBABLE_TYPE_WALL,
CLIMBABLE_TYPE_WALL_STICK
};
/**
* Component that serializes locations of entities to the client
*/
@ -86,6 +94,18 @@ public:
*/
void SetPhysicsMotionState(uint32_t value);
/**
* Returns the ClimbableType of this entity
* @return the ClimbableType of this entity
*/
const eClimbableType& GetClimabbleType() { return m_ClimbableType; }
/**
* Sets the ClimbableType of this entity
* @param value the ClimbableType to set
*/
void SetClimbableType(const eClimbableType& value) { m_ClimbableType = value; }
private:
/**
@ -122,6 +142,11 @@ private:
* The current physics motion state
*/
uint32_t m_PhysicsMotionState = 0;
/**
* Whether or not the entity is climbable
*/
eClimbableType m_ClimbableType = eClimbableType::CLIMBABLE_TYPE_NOT;
};
#endif // SIMPLEPHYSICSCOMPONENT_H

View File

@ -88,14 +88,13 @@ void SkillComponent::SyncPlayerProjectile(const LWOOBJID projectileId, RakNet::B
const auto sync_entry = this->m_managedProjectiles.at(index);
std::stringstream query;
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT behaviorID FROM SkillBehavior WHERE skillID = (SELECT skillID FROM ObjectSkills WHERE objectTemplate = ?);");
query.bind(1, (int) sync_entry.lot);
query << "SELECT behaviorID FROM SkillBehavior WHERE skillID = (SELECT skillID FROM ObjectSkills WHERE objectTemplate = " << std::to_string(sync_entry.lot) << ")";
auto result = query.execQuery();
auto result = CDClientDatabase::ExecuteQuery(query.str());
if (result.eof())
{
if (result.eof()) {
Game::logger->Log("SkillComponent", "Failed to find skill id for (%i)!\n", sync_entry.lot);
return;
@ -430,8 +429,7 @@ void SkillComponent::SyncProjectileCalculation(const ProjectileSyncEntry& entry)
{
auto* other = EntityManager::Instance()->GetEntity(entry.branchContext.target);
if (other == nullptr)
{
if (other == nullptr) {
if (entry.branchContext.target != LWOOBJID_EMPTY)
{
Game::logger->Log("SkillComponent", "Invalid projectile target (%llu)!\n", entry.branchContext.target);
@ -440,14 +438,12 @@ void SkillComponent::SyncProjectileCalculation(const ProjectileSyncEntry& entry)
return;
}
std::stringstream query;
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT behaviorID FROM SkillBehavior WHERE skillID = (SELECT skillID FROM ObjectSkills WHERE objectTemplate = ?);");
query.bind(1, (int) entry.lot);
auto result = query.execQuery();
query << "SELECT behaviorID FROM SkillBehavior WHERE skillID = (SELECT skillID FROM ObjectSkills WHERE objectTemplate = " << std::to_string(entry.lot) << ")";
auto result = CDClientDatabase::ExecuteQuery(query.str());
if (result.eof())
{
if (result.eof()) {
Game::logger->Log("SkillComponent", "Failed to find skill id for (%i)!\n", entry.lot);
return;

View File

@ -0,0 +1,2 @@
set(DGAME_DENTITY_SOURCES "EntityCallbackTimer.cpp"
"EntityTimer.cpp" PARENT_SCOPE)

View File

@ -0,0 +1,4 @@
set(DGAME_DGAMEMESSAGES_SOURCES "GameMessageHandler.cpp"
"GameMessages.cpp"
"PropertyDataMessage.cpp"
"PropertySelectQueryProperty.cpp" PARENT_SCOPE)

View File

@ -1363,6 +1363,18 @@ void GameMessages::SendUseItemResult(Entity* entity, LOT templateID, bool useIte
SEND_PACKET
}
void GameMessages::SendUseItemRequirementsResponse(LWOOBJID objectID, const SystemAddress& sysAddr, UseItemResponse itemResponse) {
CBITSTREAM
CMSGHEADER
bitStream.Write(objectID);
bitStream.Write(GAME_MSG::GAME_MSG_USE_ITEM_REQUIREMENTS_RESPONSE);
bitStream.Write(itemResponse);
SEND_PACKET
}
void GameMessages::SendMoveInventoryBatch(Entity* entity, uint32_t stackCount, int srcInv, int dstInv, const LWOOBJID& iObjID) {
CBITSTREAM
CMSGHEADER
@ -2515,11 +2527,11 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent
const auto zoneId = worldId.GetMapID();
const auto cloneId = worldId.GetCloneID();
std::stringstream query;
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT id FROM PropertyTemplate WHERE mapID = ?;");
query.bind(1, (int) zoneId);
query << "SELECT id FROM PropertyTemplate WHERE mapID = " << std::to_string(zoneId) << ";";
auto result = CDClientDatabase::ExecuteQuery(query.str());
auto result = query.execQuery();
if (result.eof() || result.fieldIsNull(0)) {
return;
@ -3974,6 +3986,47 @@ void GameMessages::SendDisplayChatBubble(LWOOBJID objectId, const std::u16string
SEND_PACKET;
}
// Mounts
void GameMessages::SendSetMountInventoryID(Entity* entity, const LWOOBJID& objectID, const SystemAddress& sysAddr){
CBITSTREAM;
CMSGHEADER;
bitStream.Write(entity->GetObjectID());
bitStream.Write(GAME_MSG::GAME_MSG_SET_MOUNT_INVENTORY_ID);
bitStream.Write(objectID);
SEND_PACKET_BROADCAST;
}
void GameMessages::HandleDismountComplete(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr){
LWOOBJID objectId{};
inStream->Read(objectId);
auto* mount = EntityManager::Instance()->GetEntity(objectId);
if (objectId != LWOOBJID_EMPTY) {
PossessorComponent* possessor;
if (entity->TryGetComponent(COMPONENT_TYPE_POSSESSOR, possessor)) {
if (mount) {
possessor->SetIsBusy(false);
possessor->SetPossessable(LWOOBJID_EMPTY);
possessor->SetPossessableType(ePossessionType::NO_POSSESSION);
GameMessages::SendSetStunned(entity->GetObjectID(), eStunState::POP, UNASSIGNED_SYSTEM_ADDRESS, LWOOBJID_EMPTY, true, false, true, false, false, false, false, true, true, true, true, true, true, true, true, true);
EntityManager::Instance()->SerializeEntity(entity);
}
}
}
}
void GameMessages::HandleAcknowledgePossession(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
Game::logger->Log("HandleAcknowledgePossession", "Got AcknowledgePossession from %i\n", entity->GetLOT());
EntityManager::Instance()->SerializeEntity(entity);
}
//Racing
void GameMessages::HandleModuleAssemblyQueryData(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr)
@ -4030,14 +4083,6 @@ void GameMessages::HandleRacingClientReady(RakNet::BitStream* inStream, Entity*
}
void GameMessages::HandleAcknowledgePossession(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr)
{
Game::logger->Log("HandleAcknowledgePossession", "Got AcknowledgePossession from %i\n", entity->GetLOT());
EntityManager::Instance()->SerializeEntity(entity);
}
void GameMessages::HandleRequestDie(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr)
{
bool bClientDeath;
@ -5502,15 +5547,17 @@ void GameMessages::HandleMoveItemBetweenInventoryTypes(RakNet::BitStream* inStre
auto* item = inv->FindItemById(objectID);
if (item == nullptr)
{
if (!item) {
// Attempt to find the item by lot in inventory A since A is the source inventory.
item = inv->FindItemByLot(templateID, static_cast<eInventoryType>(inventoryTypeA));
if (!item) {
// As a final resort, try to find the item in its default inventory based on type.
item = inv->FindItemByLot(templateID);
if (item == nullptr)
{
if (!item) {
return;
}
}
}
if (entity->GetCharacter()) {
if (entity->GetCharacter()->GetBuildMode()) {

View File

@ -328,6 +328,34 @@ namespace GameMessages {
void SendDisplayChatBubble(LWOOBJID objectId, const std::u16string& text, const SystemAddress& sysAddr);
// Mounts
/**
* @brief Set the Inventory LWOOBJID of the mount
*
* @param entity The entity that is mounting
* @param sysAddr the system address to send game message responses to
* @param objectID LWOOBJID of the item in inventory that is being used
*/
void SendSetMountInventoryID(Entity* entity, const LWOOBJID& objectID, const SystemAddress& sysAddr);
/**
* @brief Handle client dismounting mount
*
* @param inStream Raknet BitStream of incoming data
* @param entity The Entity that is dismounting
* @param sysAddr the system address to send game message responses to
*/
void HandleDismountComplete(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr);
/**
* @brief Handle acknowledging that the client possessed something
*
* @param inStream Raknet BitStream of incoming data
* @param entity The Entity that is possessing
* @param sysAddr the system address to send game message responses to
*/
void HandleAcknowledgePossession(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr);
//Racing:
void HandleModuleAssemblyQueryData(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr);
@ -337,8 +365,6 @@ namespace GameMessages {
void HandleRacingClientReady(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr);
void HandleAcknowledgePossession(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr);
void HandleRequestDie(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr);
void HandleVehicleNotifyServerAddPassiveBoostAction(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr);
@ -372,6 +398,7 @@ namespace GameMessages {
void SendActivityPause(LWOOBJID objectId, bool pause = false, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS);
void SendStartActivityTime(LWOOBJID objectId, float_t startTime, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS);
void SendRequestActivityEnter(LWOOBJID objectId, const SystemAddress& sysAddr, bool bStart, LWOOBJID userID);
void SendUseItemRequirementsResponse(LWOOBJID objectID, const SystemAddress& sysAddr, UseItemResponse itemResponse);
// SG:

View File

@ -1,5 +1,8 @@
#pragma once
#ifndef PROPERTYSELECTQUERY_H
#define PROPERTYSELECTQUERY_H
#include "Entity.h"
class PropertySelectQueryProperty final
@ -24,3 +27,5 @@ public:
float PerformanceCost = 0; // The performance cost of the property
uint32_t PerformanceIndex = 0; // The performance index of the property? Always 0?
};
#endif

View File

@ -0,0 +1,5 @@
set(DGAME_DINVENTORY_SOURCES "EquippedItem.cpp"
"Inventory.cpp"
"Item.cpp"
"ItemSet.cpp"
"ItemSetPassiveAbility.cpp" PARENT_SCOPE)

View File

@ -1,7 +1,8 @@
#include "Inventory.h"
#include "Inventory.h"
#include "GameMessages.h"
#include "Game.h"
#include "Item.h"
#include "eItemType.h"
std::vector<LOT> Inventory::m_GameMasterRestrictedItems = {
1727, // GM Only - JetPack
@ -274,40 +275,41 @@ eInventoryType Inventory::FindInventoryTypeForLot(const LOT lot)
const auto itemType = static_cast<eItemType>(itemComponent.itemType);
switch (itemType) {
case ITEM_TYPE_BRICK:
case eItemType::ITEM_TYPE_BRICK:
return BRICKS;
case ITEM_TYPE_BEHAVIOR:
case eItemType::ITEM_TYPE_BEHAVIOR:
return BEHAVIORS;
case ITEM_TYPE_PROPERTY:
case eItemType::ITEM_TYPE_PROPERTY:
return PROPERTY_DEEDS;
case ITEM_TYPE_MODEL:
case ITEM_TYPE_VEHICLE:
case ITEM_TYPE_LOOT_MODEL:
case eItemType::ITEM_TYPE_MODEL:
case eItemType::ITEM_TYPE_VEHICLE:
case eItemType::ITEM_TYPE_LOOT_MODEL:
case eItemType::ITEM_TYPE_MOUNT:
return MODELS;
case ITEM_TYPE_HAT:
case ITEM_TYPE_HAIR:
case ITEM_TYPE_NECK:
case ITEM_TYPE_LEFT_HAND:
case ITEM_TYPE_RIGHT_HAND:
case ITEM_TYPE_LEGS:
case ITEM_TYPE_LEFT_TRINKET:
case ITEM_TYPE_RIGHT_TRINKET:
case ITEM_TYPE_COLLECTIBLE:
case ITEM_TYPE_CONSUMABLE:
case ITEM_TYPE_CHEST:
case ITEM_TYPE_EGG:
case ITEM_TYPE_PET_FOOD:
case ITEM_TYPE_PET_INVENTORY_ITEM:
case ITEM_TYPE_PACKAGE:
case ITEM_TYPE_CURRENCY:
case eItemType::ITEM_TYPE_HAT:
case eItemType::ITEM_TYPE_HAIR:
case eItemType::ITEM_TYPE_NECK:
case eItemType::ITEM_TYPE_LEFT_HAND:
case eItemType::ITEM_TYPE_RIGHT_HAND:
case eItemType::ITEM_TYPE_LEGS:
case eItemType::ITEM_TYPE_LEFT_TRINKET:
case eItemType::ITEM_TYPE_RIGHT_TRINKET:
case eItemType::ITEM_TYPE_COLLECTIBLE:
case eItemType::ITEM_TYPE_CONSUMABLE:
case eItemType::ITEM_TYPE_CHEST:
case eItemType::ITEM_TYPE_EGG:
case eItemType::ITEM_TYPE_PET_FOOD:
case eItemType::ITEM_TYPE_PET_INVENTORY_ITEM:
case eItemType::ITEM_TYPE_PACKAGE:
case eItemType::ITEM_TYPE_CURRENCY:
return ITEMS;
case ITEM_TYPE_QUEST_OBJECT:
case ITEM_TYPE_UNKNOWN:
case eItemType::ITEM_TYPE_QUEST_OBJECT:
case eItemType::ITEM_TYPE_UNKNOWN:
default:
return HIDDEN;
}

View File

@ -1,5 +1,8 @@
#pragma once
#ifndef INVENTORY_H
#define INVENTORY_H
#include <map>
#include <vector>
@ -182,3 +185,5 @@ private:
*/
static std::vector<LOT> m_GameMasterRestrictedItems;
};
#endif

View File

@ -311,7 +311,9 @@ bool Item::UseNonEquip()
const auto success = !packages.empty();
Game::logger->Log("Item", "Used (%i) with (%d)\n", lot, success);
auto inventoryComponent = inventory->GetComponent();
auto playerEntity = inventoryComponent->GetParent();
if (subKey != LWOOBJID_EMPTY)
{
@ -324,8 +326,7 @@ bool Item::UseNonEquip()
return true;
}
}
if (success)
if (success && (playerEntity->GetGMLevel() >= eGameMasterLevel::GAME_MASTER_LEVEL_JUNIOR_DEVELOPER || this->GetPreconditionExpression()->Check(playerEntity)))
{
auto* entityParent = inventory->GetComponent()->GetParent();
@ -342,7 +343,7 @@ bool Item::UseNonEquip()
LootGenerator::Instance().GiveLoot(inventory->GetComponent()->GetParent(), result, eLootSourceType::LOOT_SOURCE_CONSUMPTION);
}
Game::logger->Log("Item", "Used (%i)\n", lot);
inventory->GetComponent()->RemoveItem(lot, 1);
}
@ -386,11 +387,11 @@ void Item::DisassembleModel()
const auto componentId = table->GetByIDAndType(GetLot(), COMPONENT_TYPE_RENDER);
std::stringstream query;
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT render_asset FROM RenderComponent WHERE id = ?;");
query.bind(1, (int) componentId);
query << "SELECT render_asset FROM RenderComponent WHERE id = " << std::to_string(componentId) << ";";
auto result = CDClientDatabase::ExecuteQuery(query.str());
auto result = query.execQuery();
if (result.eof())
{

View File

@ -15,11 +15,11 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent)
this->m_PassiveAbilities = ItemSetPassiveAbility::FindAbilities(id, m_InventoryComponent->GetParent(), this);
std::stringstream query;
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT skillSetWith2, skillSetWith3, skillSetWith4, skillSetWith5, skillSetWith6, itemIDs FROM ItemSets WHERE setID = ?;");
query.bind(1, (int) id);
query << "SELECT skillSetWith2, skillSetWith3, skillSetWith4, skillSetWith5, skillSetWith6, itemIDs FROM ItemSets WHERE setID = " << std::to_string(id);
auto result = CDClientDatabase::ExecuteQuery(query.str());
auto result = query.execQuery();
if (result.eof())
{
@ -33,11 +33,11 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent)
continue;
}
std::stringstream skillQuery;
auto skillQuery = CDClientDatabase::CreatePreppedStmt(
"SELECT SkillID FROM ItemSetSkills WHERE SkillSetID = ?;");
skillQuery.bind(1, result.getIntField(i));
skillQuery << "SELECT SkillID FROM ItemSetSkills WHERE SkillSetID = " << std::to_string(result.getIntField(i));
auto skillResult = CDClientDatabase::ExecuteQuery(skillQuery.str());
auto skillResult = skillQuery.execQuery();
if (skillResult.eof())
{

View File

@ -0,0 +1,3 @@
set(DGAME_DMISSION_SOURCES "Mission.cpp"
"MissionPrerequisites.cpp"
"MissionTask.cpp" PARENT_SCOPE)

View File

@ -421,7 +421,9 @@ void Mission::YieldRewards() {
if (param.empty() || (param[0] & 1) == 0) // Should items be removed?
{
for (const auto target : task->GetAllTargets()) {
inventoryComponent->RemoveItem(target, task->GetClientInfo().targetValue);
// This is how live did it. ONLY remove item collection items from the items and hidden inventories and none of the others.
inventoryComponent->RemoveItem(target, task->GetClientInfo().targetValue, eInventoryType::ITEMS);
inventoryComponent->RemoveItem(target, task->GetClientInfo().targetValue, eInventoryType::HIDDEN);
missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_ITEM_COLLECTION, target, LWOOBJID_EMPTY, "", -task->GetClientInfo().targetValue);
}

View File

@ -1,5 +1,8 @@
#pragma once
#ifndef MISSION_H
#define MISSION_H
#include <vector>
#include <string>
@ -259,3 +262,5 @@ private:
*/
std::vector<MissionTask*> m_Tasks;
};
#endif

View File

@ -1,8 +1,13 @@
#pragma once
#ifndef MISSIONLOCKSTATE_H
#define MISSIONLOCKSTATE_H
enum class MissionLockState : int
{
MISSION_LOCK_LOCKED,
MISSION_LOCK_NEW,
MISSION_LOCK_UNLOCKED,
};
#endif

View File

@ -1,5 +1,8 @@
#pragma once
#ifndef __MISSIONSTATE__H__
#define __MISSIONSTATE__H__
/**
* Represents the possible states a mission can be in
*/
@ -49,3 +52,5 @@ enum class MissionState : int {
*/
MISSION_STATE_COMPLETE_READY_TO_COMPLETE = 12
};
#endif //!__MISSIONSTATE__H__

View File

@ -1,5 +1,8 @@
#pragma once
#ifndef MISSIONTASK_H
#define MISSIONTASK_H
#include "CDMissionTasksTable.h"
#include "MissionTaskType.h"
#include "dCommonVars.h"
@ -180,3 +183,5 @@ private:
*/
void CheckCompletion() const;
};
#endif

View File

@ -1,5 +1,8 @@
#pragma once
#ifndef MISSIONTASKTYPE_H
#define MISSIONTASKTYPE_H
//! An enum for mission task types
enum class MissionTaskType : int {
MISSION_TASK_TYPE_UNKNOWN = -1, //!< The task type is unknown
@ -24,3 +27,5 @@ enum class MissionTaskType : int {
MISSION_TASK_TYPE_PLACE_MODEL = 25, //!< A task for picking up a model
MISSION_TASK_TYPE_VISIT_PROPERTY = 30 //!< A task for visiting a property
};
#endif

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