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

4
.gitmodules vendored
View File

@ -10,6 +10,10 @@
[submodule "thirdparty/libbcrypt"] [submodule "thirdparty/libbcrypt"]
path = thirdparty/libbcrypt path = thirdparty/libbcrypt
url = https://github.com/trusch/libbcrypt.git 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"] [submodule "thirdparty/docker-utils"]
path = thirdparty/docker-utils path = thirdparty/docker-utils
url = https://github.com/lcdr/utils.git url = https://github.com/lcdr/utils.git

View File

@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.14)
project(Darkflame) project(Darkflame)
include(CTest) include(CTest)
set (CMAKE_CXX_STANDARD 17)
# Read variables from file # Read variables from file
FILE(READ "${CMAKE_SOURCE_DIR}/CMakeVariables.txt" variables) FILE(READ "${CMAKE_SOURCE_DIR}/CMakeVariables.txt" variables)
@ -10,113 +12,67 @@ string(REPLACE "\n" ";" variables ${variables})
# Set the cmake variables, formatted as "VARIABLE #" in variables # Set the cmake variables, formatted as "VARIABLE #" in variables
foreach(variable ${variables}) foreach(variable ${variables})
# If the string contains a #, skip it # If the string contains a #, skip it
if("${variable}" MATCHES "#") if(NOT "${variable}" MATCHES "#")
continue()
endif() # Split the variable into name and value
string(REPLACE "=" ";" variable ${variable})
# Split the variable into name and value # Check that the length of the variable is 2 (name and value)
string(REPLACE "=" ";" variable ${variable}) list(LENGTH variable length)
if(${length} EQUAL 2)
# Check that the length of the variable is 2 (name and value) list(GET variable 0 variable_name)
list(LENGTH variable length) list(GET variable 1 variable_value)
if(NOT ${length} EQUAL 2)
continue()
endif()
list(GET variable 0 variable_name) # Set the variable
list(GET variable 1 variable_value) set(${variable_name} ${variable_value})
# Set the variable # Add compiler definition
set(${variable_name} ${variable_value}) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D${variable_name}=${variable_value}")
# Add compiler definition message(STATUS "Variable: ${variable_name} = ${variable_value}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D${variable_name}=${variable_value}") endif()
endif()
message(STATUS "Variable: ${variable_name} = ${variable_value}")
endforeach() 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)
# Set the version # Set the version
set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") 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 # Echo the version
message(STATUS "Version: ${PROJECT_VERSION}") message(STATUS "Version: ${PROJECT_VERSION}")
set(CMAKE_CXX_STANDARD 17) # Compiler flags:
# Disabled deprecated warnings as the MySQL includes have deprecated code in them.
if(WIN32) # Disabled misleading indentation as DL_LinkedList from RakNet has a weird indent.
add_compile_definitions(_CRT_SECURE_NO_WARNINGS) # Disabled no-register
endif(WIN32) # 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()
# Our output dir # Our output dir
set(CMAKE_BINARY_DIR ${PROJECT_BINARY_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 # Create a /res directory
make_directory(${CMAKE_BINARY_DIR}/res) make_directory(${CMAKE_BINARY_DIR}/res)
@ -128,86 +84,82 @@ make_directory(${CMAKE_BINARY_DIR}/locale)
make_directory(${CMAKE_BINARY_DIR}/logs) make_directory(${CMAKE_BINARY_DIR}/logs)
# Copy ini files on first build # 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")
configure_file( foreach(ini ${INI_FILES})
${CMAKE_SOURCE_DIR}/resources/authconfig.ini ${PROJECT_BINARY_DIR}/authconfig.ini if (NOT EXISTS ${PROJECT_BINARY_DIR}/${ini})
COPYONLY configure_file(
) ${CMAKE_SOURCE_DIR}/resources/${ini} ${PROJECT_BINARY_DIR}/${ini}
endif() COPYONLY
if (NOT EXISTS ${PROJECT_BINARY_DIR}/chatconfig.ini) )
configure_file( endif()
${CMAKE_SOURCE_DIR}/resources/chatconfig.ini ${PROJECT_BINARY_DIR}/chatconfig.ini endforeach()
COPYONLY
) # Copy vanity files on first build
endif() set(VANITY_FILES "CREDITS.md" "INFO.md" "TESTAMENT.md" "NPC.xml")
if (NOT EXISTS ${PROJECT_BINARY_DIR}/worldconfig.ini) foreach(file ${VANITY_FILES})
configure_file( configure_file("${CMAKE_SOURCE_DIR}/vanity/${file}" "${CMAKE_BINARY_DIR}/vanity/${file}" COPYONLY)
${CMAKE_SOURCE_DIR}/resources/worldconfig.ini ${PROJECT_BINARY_DIR}/worldconfig.ini endforeach()
COPYONLY
) # Move our migrations for MasterServer to run
endif() file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/migrations/)
if (NOT EXISTS ${PROJECT_BINARY_DIR}/masterconfig.ini) file(GLOB SQL_FILES ${CMAKE_SOURCE_DIR}/migrations/dlu/*.sql)
configure_file( foreach(file ${SQL_FILES})
${CMAKE_SOURCE_DIR}/resources/masterconfig.ini ${PROJECT_BINARY_DIR}/masterconfig.ini get_filename_component(file ${file} NAME)
COPYONLY if (NOT EXISTS ${PROJECT_BINARY_DIR}/migrations/${file})
) configure_file(
${CMAKE_SOURCE_DIR}/migrations/dlu/${file} ${PROJECT_BINARY_DIR}/migrations/${file}
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() endif()
# Copy files to output if (WIN32)
configure_file("${CMAKE_SOURCE_DIR}/vanity/CREDITS.md" "${CMAKE_BINARY_DIR}/vanity/CREDITS.md" COPYONLY) set(INCLUDED_DIRECTORIES ${INCLUDED_DIRECTORIES} "thirdparty/libbcrypt/include")
configure_file("${CMAKE_SOURCE_DIR}/vanity/INFO.md" "${CMAKE_BINARY_DIR}/vanity/INFO.md" COPYONLY) elseif (UNIX)
configure_file("${CMAKE_SOURCE_DIR}/vanity/TESTAMENT.md" "${CMAKE_BINARY_DIR}/vanity/TESTAMENT.md" COPYONLY) set(INCLUDED_DIRECTORIES ${INCLUDED_DIRECTORIES} "thirdparty/libbcrypt")
configure_file("${CMAKE_SOURCE_DIR}/vanity/NPC.xml" "${CMAKE_BINARY_DIR}/vanity/NPC.xml" COPYONLY) 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}) include_directories(${ZLIB_INCLUDE_DIRS})
# Add binary directory as an include directory
# 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
include_directories(${PROJECT_BINARY_DIR}) include_directories(${PROJECT_BINARY_DIR})
include_directories(${PROJECT_SOURCE_DIR}/dChatFilter/)
include_directories(${PROJECT_SOURCE_DIR}/dCommon/) # Actually include the directories from our list
include_directories(${PROJECT_SOURCE_DIR}/dGame/) foreach (dir ${INCLUDED_DIRECTORIES})
include_directories(${PROJECT_SOURCE_DIR}/dGame/dBehaviors) include_directories(${PROJECT_SOURCE_DIR}/${dir})
include_directories(${PROJECT_SOURCE_DIR}/dGame/dComponents) endforeach()
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/)
# Link dGame to LUA # Link dGame to LUA
if(__include_lua__) if(__include_lua__)
@ -217,311 +169,116 @@ if(__include_lua__)
include_directories(${PROJECT_SOURCE_DIR}/dLua/) include_directories(${PROJECT_SOURCE_DIR}/dLua/)
endif(UNIX) endif(UNIX)
# Default to linking to libmysql # Add linking directories:
set(MYSQL_LIB mysql)
if(WIN32)
set(MYSQL_LIB mysqlcppconn)
endif(WIN32)
# Lib folders:
link_directories(${PROJECT_BINARY_DIR}) 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 # Load all of our third party directories
set(MYSQL_LIB mysqlcppconn) add_subdirectory(thirdparty)
endif(APPLE)
endif(UNIX)
if(WIN32)
link_directories(${mysql_SOURCE_DIR}/lib64/vs14)
endif(WIN32)
# Source Code # Glob together all headers that need to be precompiled
file( file(
GLOB SOURCES GLOB HEADERS_DDATABASE
LIST_DIRECTORIES false LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" ${PROJECT_SOURCE_DIR}/dDatabase/*.h
${PROJECT_SOURCE_DIR}/dWorldServer/*.cpp ${PROJECT_SOURCE_DIR}/dDatabase/Tables/*.h
${PROJECT_SOURCE_DIR}/thirdparty/SQLite/*.h
) )
# Source Code for AuthServer
file( file(
GLOB SOURCES_AUTH GLOB HEADERS_DZONEMANAGER
LIST_DIRECTORIES false LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" ${PROJECT_SOURCE_DIR}/dZoneManager/*.h
${PROJECT_SOURCE_DIR}/dAuthServer/*.cpp
) )
# Source Code for MasterServer
file( file(
GLOB SOURCES_MASTER GLOB HEADERS_DCOMMON
LIST_DIRECTORIES false LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" ${PROJECT_SOURCE_DIR}/dCommon/*.h
${PROJECT_SOURCE_DIR}/dMasterServer/*.cpp
) )
# Source Code for ChatServer
file( file(
GLOB SOURCES_CHAT GLOB HEADERS_DGAME
LIST_DIRECTORIES false LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" ${PROJECT_SOURCE_DIR}/dGame/Entity.h
${PROJECT_SOURCE_DIR}/dChatServer/*.cpp ${PROJECT_SOURCE_DIR}/dGame/dGameMessages/GameMessages.h
${PROJECT_SOURCE_DIR}/dGame/EntityManager.h
${PROJECT_SOURCE_DIR}/dScripts/CppScripts.h
) )
# Source Code for raknet # Add our library subdirectories for creation of the library object
file( add_subdirectory(dCommon)
GLOB SOURCES_RAKNET add_subdirectory(dDatabase)
LIST_DIRECTORIES false add_subdirectory(dChatFilter)
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" add_subdirectory(dNet)
${PROJECT_SOURCE_DIR}/thirdparty/raknet/Source/*.cpp 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 target_precompile_headers(
file( dZoneManager PRIVATE
GLOB SOURCES_RECAST ${HEADERS_DZONEMANAGER}
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
) )
# If we are including LUA, include the dLua files in dGame # If we are including LUA, include the dLua files in dGame
if(__include_lua__) #if(__include_lua__)
file( # file(
GLOB SOURCES_DLUA # GLOB SOURCES_DLUA
LIST_DIRECTORIES false # LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" # RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/dLua/*.cpp # ${PROJECT_SOURCE_DIR}/dLua/*.cpp
) # )
# Append the dLua files to the dGame files # Append the dLua files to the dGame files
set(SOURCES_DGAME ${SOURCES_DGAME} ${SOURCES_DLUA}) #set(SOURCES_DGAME ${SOURCES_DGAME} ${SOURCES_DLUA})
endif(__include_lua__) #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 target_precompile_headers(
file( dCommon PRIVATE
GLOB SOURCES_DZM ${HEADERS_DCOMMON}
LIST_DIRECTORIES false
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
${PROJECT_SOURCE_DIR}/dZoneManager/*.cpp
) )
# 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 # Link dGame to LUA
if(__include_lua__) #if(__include_lua__)
find_package(Lua REQUIRED) # find_package(Lua REQUIRED)
target_link_libraries(dGame ${LUA_LIBRARIES}) # target_link_libraries(dGame ${LUA_LIBRARIES})
message(STATUS "Linking dGame to LUA " ${LUA_LIBRARIES}) # message(STATUS "Linking dGame to LUA " ${LUA_LIBRARIES})
endif(UNIX) #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)
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", "description": "Same as default, Used in GitHub actions workflow",
"inherits": "default" "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", "name": "ci-windows-2022",
"displayName": "CI configure step for Windows", "displayName": "CI configure step for Windows",
@ -66,6 +75,13 @@
"displayName": "Linux CI Build", "displayName": "Linux CI Build",
"description": "This preset is used by the CI build on linux", "description": "This preset is used by the CI build on linux",
"jobs": 2 "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": [ "testPresets": [
@ -81,6 +97,18 @@
"outputOnFailure": true "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", "name": "ci-windows-2022",
"configurePreset": "ci-windows-2022", "configurePreset": "ci-windows-2022",

View File

@ -1,6 +1,6 @@
PROJECT_VERSION_MAJOR=1 PROJECT_VERSION_MAJOR=1
PROJECT_VERSION_MINOR=0 PROJECT_VERSION_MINOR=0
PROJECT_VERSION_PATCH=2 PROJECT_VERSION_PATCH=3
# LICENSE # LICENSE
LICENSE=AGPL-3.0 LICENSE=AGPL-3.0
# The network version. # The network version.
@ -16,3 +16,5 @@ NET_VERSION=171022
# Set __include_backtrace__ to 1 to includes the backtrace library for better crashlogs. # Set __include_backtrace__ to 1 to includes the backtrace library for better crashlogs.
# __compile_backtrace__=1 # __compile_backtrace__=1
# Set __compile_backtrace__ to 1 to compile the backtrace library instead of using system libraries. # Set __compile_backtrace__ to 1 to compile the backtrace library instead of using system libraries.
__maria_db_connector_compile_jobs__=1
# Set to the number of jobs (make -j equivalent) to compile the mariadbconn files with.

View File

@ -4,7 +4,7 @@
- [Docker](https://docs.docker.com/get-docker/) (Docker Desktop or on Linux normal Docker) - [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) - [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 ## 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 #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 ## Disable brickbuildfix
If you don't need the http server running on port 80 do this: 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 ```bash
git clone --recursive https://github.com/DarkflameUniverse/DarkflameServer git clone --recursive https://github.com/DarkflameUniverse/DarkflameServer
``` ```
**Python** **Python**
Some tools utilized to streamline the setup process require Python 3, make sure you have it installed. 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. 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 ### 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! CMake must be version 3.14 or higher!
@ -71,22 +70,19 @@ make
``` ```
### MacOS builds ### 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 ```bash
# Install required tools # Create the build directory, preserving it if it already exists
brew install boost mysql-connector-c++ mkdir -p build
cd build
# Symlinks for finding the required modules # Run CMake to generate build files
sudo ln -s /usr/local/mysql-connector-c++/lib64/libmysqlcppconn.dylib /usr/local/mysql-connector-c++/lib64/libmysql.dylib cmake .. -DOPENSSL_ROOT_DIR=/path/to/openssl
sudo ln -s /usr/local/mysql-connector-c++/lib64/libcrypto.1.1.dylib /usr/local/mysql/lib/libcrypto.1.1.dylib
```
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: # 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
```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
``` ```
### Windows builds (native) ### Windows builds (native)
@ -102,9 +98,20 @@ cd build
cmake .. cmake ..
:: Run CMake with build flag to build :: 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) ### 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. 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) [**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 ## 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 ### Resources
**LEGO® Universe 1.10.64** **LEGO® Universe 1.10.64**
@ -180,6 +195,13 @@ certutil -hashfile <file> SHA256
* Move and rename `cdclient.sqlite` into `build/res/CDServer.sqlite` * Move and rename `cdclient.sqlite` into `build/res/CDServer.sqlite`
* Run each SQL file in the order at which they appear [here](migrations/cdserver/) on the SQLite database * 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** **Configuration**
After the server has been built there should be four `ini` files in the build director: `authconfig.ini`, `chatconfig.ini`, `masterconfig.ini`, and `worldconfig.ini`. Go through them and fill in the database credentials and configure other settings if necessary. After the server has been built there should be four `ini` files in the build director: `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> </table>
## Credits ## Credits
### Contributors to DLUv3 ## Active Contributors
* DarwinAnim8or * [EmosewaMC](https://github.com/EmosewaMC)
* Wincent01 * [Jettford](https://github.com/Jettford)
* Mick
* averysumner ## DLU Team
* Jon002 * [DarwinAnim8or](https://github.com/DarwinAnim8or)
* Jonny * [Wincent01](https://github.com/Wincent01)
* Xiphoseer * [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 ### Research and tools
* lcdr * [lcdr](https://github.com/lcdr)
* [Xiphoseer](https://github.com/Xiphoseer)
### Community management ### Community management
* Neal * [Neal](https://github.com/NealSpellman)
### Former contributors ### Former contributors
* TheMachine * 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 "dServer.h"
#include "GeneralUtils.h" #include "GeneralUtils.h"
#include "dLogger.h" #include "dLogger.h"
#include "AddFriendResponseCode.h"
#include "AddFriendResponseType.h"
#include "RakString.h"
#include "dConfig.h"
extern PlayerContainer playerContainer; extern PlayerContainer playerContainer;
@ -21,44 +25,41 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
auto player = playerContainer.GetPlayerData(playerID); auto player = playerContainer.GetPlayerData(playerID);
if (!player) return; if (!player) return;
//Get our friends list from the Db: //Get our friends list from the Db. Using a derived table since the friend of a player can be in either column.
auto stmt = Database::CreatePreppedStmt("SELECT * FROM friends WHERE player_id = ? OR friend_id = ?"); std::unique_ptr<sql::PreparedStatement> stmt(Database::CreatePreppedStmt(
stmt->setUInt64(1, playerID); "SELECT fr.requested_player, best_friend, ci.name FROM "
stmt->setUInt64(2, playerID); "(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; std::vector<FriendData> friends;
auto res = stmt->executeQuery(); std::unique_ptr<sql::ResultSet> res(stmt->executeQuery());
while (res->next()) { while (res->next()) {
FriendData fd; FriendData fd;
fd.isFTP = false; // not a thing in DLU fd.isFTP = false; // not a thing in DLU
fd.friendID = res->getInt64(1); fd.friendID = res->getUInt(1);
if (fd.friendID == playerID) fd.friendID = res->getUInt64(2); 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 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;
//We need to find their name as well: fd.friendName = res->getString(3);
{
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;
}
//Now check if they're online: //Now check if they're online:
auto fr = playerContainer.GetPlayerData(fd.friendID); auto fr = playerContainer.GetPlayerData(fd.friendID);
if (fr) { if (fr) {
fd.isOnline = true; fd.isOnline = true;
fd.zoneID = fr->zoneID; fd.zoneID = fr->zoneID;
//Since this friend is online, we need to update them on the fact that we've just logged in: //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 { else {
fd.isOnline = false; fd.isOnline = false;
@ -68,9 +69,6 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
friends.push_back(fd); friends.push_back(fd);
} }
delete res;
delete stmt;
//Now, we need to send the friendlist to the server they came from: //Now, we need to send the friendlist to the server they came from:
CBITSTREAM; CBITSTREAM;
PacketUtils::WriteHeader(bitStream, CHAT_INTERNAL, MSG_CHAT_INTERNAL_ROUTE_TO_PLAYER); 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) { 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; CINSTREAM;
LWOOBJID playerID; LWOOBJID requestorPlayerID;
inStream.Read(playerID); inStream.Read(requestorPlayerID);
inStream.Read(playerID); inStream.Read(requestorPlayerID);
std::string playerName = PacketUtils::ReadString(0x14, packet, true); uint32_t spacing{};
//There's another bool here to determine if it's a best friend request, but we're not handling it right now. inStream.Read(spacing);
std::string playerName = "";
uint16_t character;
bool noMoreLettersInName = false;
//PacketUtils::SavePacket("FriendRequest.bin", (char*)inStream.GetData(), inStream.GetNumberOfBytesUsed()); for (uint32_t j = 0; j < 33; j++) {
inStream.Read(character);
//We need to check to see if the player is actually online or not: if (character == '\0') noMoreLettersInName = true;
auto targetData = playerContainer.GetPlayerData(playerName); if (!noMoreLettersInName) playerName.push_back(static_cast<char>(character));
if (targetData) {
SendFriendRequest(targetData, playerContainer.GetPlayerData(playerID));
} }
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) { void ChatPacketHandler::HandleFriendResponse(Packet* packet) {
@ -114,30 +242,75 @@ void ChatPacketHandler::HandleFriendResponse(Packet* packet) {
LWOOBJID playerID; LWOOBJID playerID;
inStream.Read(playerID); inStream.Read(playerID);
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); 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: //Now to try and find both of these:
auto goonA = playerContainer.GetPlayerData(playerID); auto requestor = playerContainer.GetPlayerData(playerID);
auto goonB = playerContainer.GetPlayerData(friendName); auto requestee = playerContainer.GetPlayerData(friendName);
if (!goonA || !goonB) return; if (!requestor || !requestee) return;
SendFriendResponse(goonB, goonA, responseCode); AddFriendResponseType serverResponseCode{};
SendFriendResponse(goonA, goonB, responseCode); //Do we need to send it to both? I think so so both get the updated friendlist but... idk. 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 (?,?,?)"); // Now that we have handled the base cases, we need to check the other cases.
stmt->setUInt64(1, goonA->playerID); if (serverResponseCode == AddFriendResponseType::ACCEPTED) {
stmt->setUInt64(2, goonB->playerID); for (auto friendData : requestor->friends) {
stmt->setInt(3, 0); if (friendData.friendID == requestee->playerID) {
stmt->execute(); serverResponseCode = AddFriendResponseType::ALREADYFRIEND;
delete stmt; 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) { void ChatPacketHandler::HandleRemoveFriend(Packet* packet) {
@ -145,50 +318,55 @@ void ChatPacketHandler::HandleRemoveFriend(Packet* packet) {
LWOOBJID playerID; LWOOBJID playerID;
inStream.Read(playerID); inStream.Read(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. //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: //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()); stmt->setString(1, friendName.c_str());
LWOOBJID friendID = 0; LWOOBJID friendID = 0;
auto res = stmt->executeQuery(); std::unique_ptr<sql::ResultSet> res(stmt->executeQuery());
while (res->next()) { while (res->next()) {
friendID = res->getUInt64(1); friendID = res->getUInt(1);
} }
delete res; // Convert friendID to LWOOBJID
delete stmt; 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. 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;"));
friendID = GeneralUtils::ClearBit(friendID, OBJECT_BIT_CHARACTER); deletestmt->setUInt(1, static_cast<uint32_t>(playerID));
friendID = GeneralUtils::ClearBit(friendID, OBJECT_BIT_PERSISTENT); deletestmt->setUInt(2, static_cast<uint32_t>(friendID));
deletestmt->setUInt(3, static_cast<uint32_t>(friendID));
//YEET: deletestmt->setUInt(4, static_cast<uint32_t>(playerID));
auto deletestmt = Database::CreatePreppedStmt("DELETE FROM `friends` WHERE player_id=? AND friend_id=? LIMIT 1");
deletestmt->setUInt64(1, playerID);
deletestmt->setUInt64(2, friendID);
deletestmt->execute(); 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: //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); auto goonA = playerContainer.GetPlayerData(playerID);
if (goonA) { 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); SendRemoveFriend(goonA, friendName, true);
} }
auto goonB = playerContainer.GetPlayerData(friendID); auto goonB = playerContainer.GetPlayerData(friendID);
if (!goonB) return; 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)); std::string goonAName = GeneralUtils::UTF16ToWTF8(playerContainer.GetName(playerID));
SendRemoveFriend(goonB, goonAName, true); SendRemoveFriend(goonB, goonAName, true);
} }
@ -206,7 +384,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet)
if (playerContainer.GetIsMuted(sender)) return; 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); 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()); 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; if (channel != 8) return;
auto* team = playerContainer.GetTeam(playerID); auto* team = playerContainer.GetTeam(playerID);
@ -231,7 +407,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet)
if (otherMember == nullptr) return; if (otherMember == nullptr) return;
const auto otherName = std::string(otherMember->playerName.C_String()); const auto otherName = std::string(otherMember->playerName.c_str());
CBITSTREAM; CBITSTREAM;
PacketUtils::WriteHeader(bitStream, CHAT_INTERNAL, MSG_CHAT_INTERNAL_ROUTE_TO_PLAYER); 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; if (playerContainer.GetIsMuted(goonA)) return;
std::string goonAName = goonA->playerName.C_String(); std::string goonAName = goonA->playerName.c_str();
std::string goonBName = goonB->playerName.C_String(); std::string goonBName = goonB->playerName.c_str();
//To the sender: //To the sender:
{ {
@ -454,8 +630,6 @@ void ChatPacketHandler::HandleTeamKick(Packet* packet)
playerContainer.RemoveMember(team, kickedId, false, true, false); playerContainer.RemoveMember(team, kickedId, false, true, false);
} }
//PacketUtils::SavePacket("kick.bin", reinterpret_cast<char*>(packet->data), packet->length);
} }
void ChatPacketHandler::HandleTeamPromote(Packet* packet) void ChatPacketHandler::HandleTeamPromote(Packet* packet)
@ -481,8 +655,6 @@ void ChatPacketHandler::HandleTeamPromote(Packet* packet)
playerContainer.PromoteMember(team, promoted->playerID); playerContainer.PromoteMember(team, promoted->playerID);
} }
//PacketUtils::SavePacket("promote.bin", reinterpret_cast<char*>(packet->data), packet->length);
} }
void ChatPacketHandler::HandleTeamLootOption(Packet* packet) void ChatPacketHandler::HandleTeamLootOption(Packet* packet)
@ -509,8 +681,6 @@ void ChatPacketHandler::HandleTeamLootOption(Packet* packet)
playerContainer.UpdateTeamsOnWorld(team, false); playerContainer.UpdateTeamsOnWorld(team, false);
} }
//PacketUtils::SavePacket("option.bin", reinterpret_cast<char*>(packet->data), packet->length);
} }
void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet)
@ -550,7 +720,7 @@ void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet)
playerContainer.TeamStatusUpdate(team); 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) for (const auto memberId : team->memberIDs)
{ {
@ -560,7 +730,6 @@ void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet)
const auto memberName = playerContainer.GetName(memberId); const auto memberName = playerContainer.GetName(memberId);
//ChatPacketHandler::SendTeamAddPlayer(otherMember, false, false, false, data->playerID, leaderName, data->zoneID);
if (otherMember != nullptr) if (otherMember != nullptr)
{ {
ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, data->playerID, data->zoneID); ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, data->playerID, data->zoneID);
@ -581,7 +750,7 @@ void ChatPacketHandler::SendTeamInvite(PlayerData* receiver, PlayerData* sender)
//portion that will get routed: //portion that will get routed:
PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_TEAM_INVITE); 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); bitStream.Write(sender->playerID);
SystemAddress sysAddr = receiver->sysAddr; SystemAddress sysAddr = receiver->sysAddr;
@ -745,7 +914,7 @@ void ChatPacketHandler::SendTeamSetOffWorldFlag(PlayerData* receiver, LWOOBJID i
SEND_PACKET; 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 /*chat notification is displayed if log in / out and friend is updated in friends list
[u8] - update type [u8] - update type
Update types Update types
@ -767,7 +936,7 @@ void ChatPacketHandler::SendFriendUpdate(PlayerData* friendData, PlayerData* pla
PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_UPDATE_FRIEND_NOTIFY); PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_UPDATE_FRIEND_NOTIFY);
bitStream.Write<uint8_t>(notifyType); bitStream.Write<uint8_t>(notifyType);
std::string playerName = playerData->playerName.C_String(); std::string playerName = playerData->playerName.c_str();
PacketUtils::WritePacketWString(playerName, 33, &bitStream); PacketUtils::WritePacketWString(playerName, 33, &bitStream);
@ -783,19 +952,20 @@ void ChatPacketHandler::SendFriendUpdate(PlayerData* friendData, PlayerData* pla
bitStream.Write(playerData->zoneID.GetCloneID()); bitStream.Write(playerData->zoneID.GetCloneID());
} }
bitStream.Write<uint8_t>(0); //isBFF bitStream.Write<uint8_t>(isBestFriend); //isBFF
bitStream.Write<uint8_t>(0); //isFTP bitStream.Write<uint8_t>(0); //isFTP
SystemAddress sysAddr = friendData->sysAddr; SystemAddress sysAddr = friendData->sysAddr;
SEND_PACKET; SEND_PACKET;
} }
void ChatPacketHandler::SendFriendRequest(PlayerData* receiver, PlayerData* sender, bool isBFFReq) { void ChatPacketHandler::SendFriendRequest(PlayerData* receiver, PlayerData* sender) {
if (!receiver || !sender) return; if (!receiver || !sender) return;
//Make sure people aren't requesting people that they're already friends with: //Make sure people aren't requesting people that they're already friends with:
for (auto fr : receiver->friends) { for (auto fr : receiver->friends) {
if (fr.friendID == sender->playerID) { 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. 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: //portion that will get routed:
PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_ADD_FRIEND_REQUEST); PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_ADD_FRIEND_REQUEST);
PacketUtils::WritePacketWString(sender->playerName.C_String(), 33, &bitStream); PacketUtils::WritePacketWString(sender->playerName.c_str(), 33, &bitStream);
bitStream.Write<uint8_t>(0); 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; SystemAddress sysAddr = receiver->sysAddr;
SEND_PACKET; 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; if (!receiver || !sender) return;
CBITSTREAM; CBITSTREAM;
PacketUtils::WriteHeader(bitStream, CHAT_INTERNAL, MSG_CHAT_INTERNAL_ROUTE_TO_PLAYER); PacketUtils::WriteHeader(bitStream, CHAT_INTERNAL, MSG_CHAT_INTERNAL_ROUTE_TO_PLAYER);
bitStream.Write(receiver->playerID); bitStream.Write(receiver->playerID);
//portion that will get routed: // Portion that will get routed:
PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_ADD_FRIEND_RESPONSE); PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_ADD_FRIEND_RESPONSE);
bitStream.Write<uint8_t>(responseCode); bitStream.Write(responseCode);
bitStream.Write<uint8_t>(1); //isOnline // For all requests besides accepted, write a flag that says whether or not we are already best friends with the receiver.
PacketUtils::WritePacketWString(sender->playerName.C_String(), 33, &bitStream); bitStream.Write<uint8_t>(responseCode != AddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender->sysAddr != UNASSIGNED_SYSTEM_ADDRESS);
bitStream.Write(sender->playerID); // Then write the player name
bitStream.Write(sender->zoneID); PacketUtils::WritePacketWString(sender->playerName.c_str(), 33, &bitStream);
bitStream.Write<uint8_t>(0); //isBFF // Then if this is an acceptance code, write the following extra info.
bitStream.Write<uint8_t>(0); //isFTP if (responseCode == AddFriendResponseType::ACCEPTED) {
bitStream.Write(sender->playerID);
bitStream.Write(sender->zoneID);
bitStream.Write(isBestFriendRequest); //isBFF
bitStream.Write<uint8_t>(0); //isFTP
}
SystemAddress sysAddr = receiver->sysAddr; SystemAddress sysAddr = receiver->sysAddr;
SEND_PACKET; SEND_PACKET;
} }

View File

@ -4,6 +4,7 @@
#include "BitStream.h" #include "BitStream.h"
struct PlayerData; struct PlayerData;
enum class AddFriendResponseType : uint8_t;
namespace ChatPacketHandler { namespace ChatPacketHandler {
void HandleFriendlistRequest(Packet* packet); void HandleFriendlistRequest(Packet* packet);
@ -31,10 +32,9 @@ namespace ChatPacketHandler {
void SendTeamSetOffWorldFlag(PlayerData* receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID); 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. //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 SendFriendRequest(PlayerData* receiver, PlayerData* sender);
void SendFriendResponse(PlayerData* receiver, PlayerData* sender, uint8_t responseCode = 3); 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); void SendRemoveFriend(PlayerData* receiver, std::string& personToRemove, bool isSuccessful);
}; };

View File

@ -20,17 +20,25 @@ PlayerContainer::~PlayerContainer() {
void PlayerContainer::InsertPlayer(Packet* packet) { void PlayerContainer::InsertPlayer(Packet* packet) {
CINSTREAM; CINSTREAM;
PlayerData* data = new PlayerData(); PlayerData* data = new PlayerData();
inStream.SetReadOffset(inStream.GetReadOffset() + 64);
inStream.Read(data->playerID); 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->zoneID);
inStream.Read(data->muteExpire); inStream.Read(data->muteExpire);
data->sysAddr = packet->systemAddress; 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)); 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 (?, ?, ?, ?);"); 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); inStream.Read(playerID);
//Before they get kicked, we need to also send a message to their friends saying that they disconnected. //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) { if (player == nullptr) {
return; return;
} }
for (auto& fr : player->friends) { for (auto& fr : player->friends) {
//if (!fr.isOnline) continue;
auto fd = this->GetPlayerData(fr.friendID); 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); auto* team = GetTeam(playerID);
if (team != nullptr) if (team != nullptr)
{ {
//TeamStatusUpdate(team); const auto memberName = GeneralUtils::ASCIIToUTF16(std::string(player->playerName.c_str()));
const auto memberName = GeneralUtils::ASCIIToUTF16(std::string(player->playerName.C_String()));
for (const auto memberId : team->memberIDs) for (const auto memberId : team->memberIDs)
{ {
@ -77,7 +81,6 @@ void PlayerContainer::RemovePlayer(Packet* packet) {
if (otherMember == nullptr) continue; if (otherMember == nullptr) continue;
ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, playerID, {0, 0, 0}); ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, playerID, {0, 0, 0});
//ChatPacketHandler::SendTeamRemovePlayer(otherMember, false, false, true, false, team->leaderID, player->playerID, memberName);
} }
} }
@ -237,16 +240,10 @@ void PlayerContainer::AddMember(TeamData* team, LWOOBJID playerID)
if (leader == nullptr || member == nullptr) return; if (leader == nullptr || member == nullptr) return;
const auto leaderName = GeneralUtils::ASCIIToUTF16(std::string(leader->playerName.C_String())); const auto leaderName = GeneralUtils::ASCIIToUTF16(std::string(leader->playerName.c_str()));
const auto memberName = GeneralUtils::ASCIIToUTF16(std::string(member->playerName.C_String())); 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::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) if (!team->local)
{ {
@ -348,7 +345,7 @@ void PlayerContainer::DisbandTeam(TeamData* team)
if (otherMember == nullptr) continue; 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::SendTeamSetLeader(otherMember, LWOOBJID_EMPTY);
ChatPacketHandler::SendTeamRemovePlayer(otherMember, true, false, false, team->local, team->leaderID, otherMember->playerID, memberName); 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; 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) 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); 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); UpdateTeamsOnWorld(team, false);

View File

@ -9,11 +9,12 @@
struct PlayerData { struct PlayerData {
LWOOBJID playerID; LWOOBJID playerID;
RakNet::RakString playerName; std::string playerName;
SystemAddress sysAddr; SystemAddress sysAddr;
LWOZONEID zoneID; LWOZONEID zoneID;
std::vector<FriendData> friends; std::vector<FriendData> friends;
time_t muteExpire; time_t muteExpire;
uint8_t countOfBestFriends = 0;
}; };
struct TeamData { struct TeamData {
@ -45,7 +46,7 @@ public:
PlayerData* GetPlayerData(const std::string& playerName) { PlayerData* GetPlayerData(const std::string& playerName) {
for (auto player : mPlayers) { for (auto player : mPlayers) {
if (player.second) { 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; 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; 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> SplitString(const std::string& str, char delimiter);
std::vector<std::string> GetFileNamesFromFolder(const std::string& folder);
template <typename T> template <typename T>
T Parse(const char* value); T Parse(const char* value);

View File

@ -54,7 +54,7 @@ LDFBaseData * LDFBaseData::DataFromString(const std::string& format) {
} }
case LDF_TYPE_S32: { 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); return new LDFData<int32_t>(key, data);
} }

View File

@ -1,5 +1,7 @@
#include "ZCompression.h" #include "ZCompression.h"
#ifndef _WIN32
#include <zlib.h> #include <zlib.h>
namespace ZCompression namespace ZCompression
@ -70,4 +72,6 @@ namespace ZCompression
return(nRet); // -1 or len of output return(nRet); // -1 or len of output
*/ */
} }
} }
#endif

View File

@ -2,6 +2,10 @@
#include <cstdint> #include <cstdint>
#include "dPlatforms.h"
#ifndef DARKFLAME_PLATFORM_WIN32
namespace ZCompression namespace ZCompression
{ {
int32_t GetMaxCompressedLength(int32_t nLenSrc); 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); 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_MISSION = 84, //!< The Mission Component
COMPONENT_TYPE_ROCKET_LAUNCH_LUP = 97, //!< The LUP Launchpad Componen COMPONENT_TYPE_ROCKET_LAUNCH_LUP = 97, //!< The LUP Launchpad Componen
COMPONENT_TYPE_RAIL_ACTIVATOR = 104, COMPONENT_TYPE_RAIL_ACTIVATOR = 104,
COMPONENT_TYPE_POSSESSOR = 107, //!< The Component 107 COMPONENT_TYPE_POSSESSABLE = 108, //!< The Possessable Component
COMPONENT_TYPE_POSSESSABLE = 108, //!< The Component 108 COMPONENT_TYPE_POSSESSOR = 110, //!< The Possessor Component
COMPONENT_TYPE_BUILD_BORDER = 114, //!< The Build Border Component COMPONENT_TYPE_BUILD_BORDER = 114, //!< The Build Border Component
COMPONENT_TYPE_DESTROYABLE = 1000, //!< The Destroyable Component COMPONENT_TYPE_DESTROYABLE = 1000, //!< The Destroyable Component
COMPONENT_TYPE_MODEL = 5398484 //look man idk 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 * Represents the different types of inventories an entity may have
*/ */
@ -434,33 +440,6 @@ enum eInventoryType : uint32_t {
INVALID // made up, for internal use!!! 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 { enum eRebuildState : uint32_t {
REBUILD_OPEN, REBUILD_OPEN,
REBUILD_COMPLETED = 2, REBUILD_COMPLETED = 2,
@ -658,7 +637,6 @@ enum ePlayerFlags {
NJ_WU_SHOW_DAILY_CHEST = 2099 NJ_WU_SHOW_DAILY_CHEST = 2099
}; };
//======== FUNC =========== //======== FUNC ===========
template<typename T> 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) { CppSQLite3Query CDClientDatabase::ExecuteQuery(const std::string& query) {
return conn->execQuery(query.c_str()); 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); 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())); tables.insert(std::make_pair("ActivityRewards", new CDActivityRewardsTable()));
UNUSED(tables.insert(std::make_pair("Animations", new CDAnimationsTable()))); UNUSED(tables.insert(std::make_pair("Animations", new CDAnimationsTable())));
tables.insert(std::make_pair("BehaviorParameter", new CDBehaviorParameterTable())); 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("ComponentsRegistry", new CDComponentsRegistryTable()));
tables.insert(std::make_pair("CurrencyTable", new CDCurrencyTableTable())); tables.insert(std::make_pair("CurrencyTable", new CDCurrencyTableTable()));
tables.insert(std::make_pair("DestructibleComponent", new CDDestructibleComponentTable())); 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::Driver * Database::driver;
sql::Connection * Database::con; 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) { void Database::Connect(const string& host, const string& database, const string& username, const string& password) {
driver = get_driver_instance();
//To bypass debug issues: //To bypass debug issues:
std::string newHost = "tcp://" + host; 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* szUsername = username.c_str();
const char* szPassword = password.c_str(); const char* szPassword = password.c_str();
con = driver->connect(szHost, szUsername, szPassword); driver = sql::mariadb::get_driver_instance();
con->setSchema(szDatabase);
bool myTrue = true; sql::Properties properties;
con->setClientOption("MYSQL_OPT_RECONNECT", &myTrue); properties["hostName"] = szHost;
} //Connect 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 (!con) return;
if (source != "") Game::logger->Log("Database", "Destroying MySQL connection from %s!\n", source.c_str());
else Game::logger->Log("Database", "Destroying MySQL connection!\n"); 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(); con->close();
delete con; delete con;
} //Destroy } //Destroy
@ -45,13 +62,7 @@ sql::PreparedStatement* Database::CreatePreppedStmt(const std::string& query) {
sql::SQLString str(test, size); sql::SQLString str(test, size);
if (!con) { if (!con) {
//Connect to the MySQL Database Connect();
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);
Game::logger->Log("Database", "Trying to reconnect to MySQL\n"); Game::logger->Log("Database", "Trying to reconnect to MySQL\n");
} }
@ -61,17 +72,15 @@ sql::PreparedStatement* Database::CreatePreppedStmt(const std::string& query) {
con = nullptr; con = nullptr;
//Connect to the MySQL Database Connect();
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);
Game::logger->Log("Database", "Trying to reconnect to MySQL from invalid or closed connection\n"); Game::logger->Log("Database", "Trying to reconnect to MySQL from invalid or closed connection\n");
} }
auto* stmt = con->prepareStatement(str); auto* stmt = con->prepareStatement(str);
return stmt; return stmt;
} //CreatePreppedStmt } //CreatePreppedStmt
void Database::Commit() {
Database::con->commit();
}

View File

@ -1,13 +1,7 @@
#pragma once #pragma once
#include <string> #include <string>
#include <mysql_connection.h> #include <conncpp.hpp>
#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>
class MySqlException : public std::runtime_error { class MySqlException : public std::runtime_error {
public: public:
@ -19,10 +13,17 @@ class Database {
private: private:
static sql::Driver *driver; static sql::Driver *driver;
static sql::Connection *con; static sql::Connection *con;
static sql::Properties props;
static std::string database;
public: public:
static void Connect(const std::string& host, const std::string& database, const std::string& username, const std::string& password); 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::Statement* CreateStmt();
static sql::PreparedStatement* CreatePreppedStmt(const std::string& query); 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 //! Constructor
CDBehaviorParameterTable::CDBehaviorParameterTable(void) { CDBehaviorParameterTable::CDBehaviorParameterTable(void) {
#ifdef CDCLIENT_CACHE_ALL auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM BehaviorParameter");
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM BehaviorParameter"); size_t hash = 0;
while (!tableData.eof()) { while (!tableData.eof()) {
CDBehaviorParameter entry; hash = 0;
entry.behaviorID = tableData.getIntField(0, -1); CDBehaviorParameter entry;
entry.parameterID = tableData.getStringField(1, ""); entry.behaviorID = tableData.getIntField(0, -1);
entry.value = tableData.getFloatField(2, -1.0f); auto candidateStringToAdd = std::string(tableData.getStringField(1, ""));
auto parameter = m_ParametersList.find(candidateStringToAdd);
//Check if we have an entry with this ID: if (parameter != m_ParametersList.end()) {
auto it = m_entries.find(entry.behaviorID); entry.parameterID = parameter;
if (it != m_entries.end()) { } else {
it->second.insert(std::make_pair(entry.parameterID, entry.value)); entry.parameterID = m_ParametersList.insert(candidateStringToAdd).first;
} }
else { entry.value = tableData.getFloatField(2, -1.0f);
//Otherwise, insert it:
m_entries.insert(std::make_pair(entry.behaviorID, std::map<std::string, float>()));
auto jit = m_entries.find(entry.behaviorID);
//Add our value as well: GeneralUtils::hash_combine(hash, entry.behaviorID);
jit->second.insert(std::make_pair(entry.parameterID, entry.value)); GeneralUtils::hash_combine(hash, *entry.parameterID);
}
tableData.nextRow(); auto it = m_Entries.find(entry.behaviorID);
} m_ParametersList.insert(*entry.parameterID);
m_Entries.insert(std::make_pair(hash, entry));
tableData.nextRow();
}
tableData.finalize(); tableData.finalize();
#endif
} }
//! Destructor //! Destructor
@ -37,54 +35,36 @@ CDBehaviorParameterTable::~CDBehaviorParameterTable(void) { }
//! Returns the table's name //! Returns the table's name
std::string CDBehaviorParameterTable::GetName(void) const { std::string CDBehaviorParameterTable::GetName(void) const {
return "BehaviorParameter"; 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; size_t hash = 0;
GeneralUtils::hash_combine(hash, behaviorID); GeneralUtils::hash_combine(hash, behaviorID);
GeneralUtils::hash_combine(hash, name); GeneralUtils::hash_combine(hash, name);
// Search for specific parameter // Search for specific parameter
const auto& it = m_Entries.find(hash); const auto& it = m_Entries.find(hash);
if (it != m_Entries.end()) { return it != m_Entries.end() ? it->second : returnValue;
return it->second; }
}
std::map<std::string, float> CDBehaviorParameterTable::GetParametersByBehaviorID(uint32_t behaviorID) {
// Check if this behavior has already been checked size_t hash;
const auto& itChecked = m_Entries.find(behaviorID); std::map<std::string, float> returnInfo;
if (itChecked != m_Entries.end()) { for (auto parameterCandidate : m_ParametersList) {
return defaultValue; hash = 0;
} GeneralUtils::hash_combine(hash, behaviorID);
GeneralUtils::hash_combine(hash, parameterCandidate);
#ifndef CDCLIENT_CACHE_ALL auto infoCandidate = m_Entries.find(hash);
std::stringstream query; if (infoCandidate != m_Entries.end()) {
returnInfo.insert(std::make_pair(*(infoCandidate->second.parameterID), infoCandidate->second.value));
query << "SELECT parameterID, value FROM BehaviorParameter WHERE behaviorID = " << std::to_string(behaviorID); }
}
auto tableData = CDClientDatabase::ExecuteQuery(query.str()); return returnInfo;
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;
} }

View File

@ -2,7 +2,8 @@
// Custom Classes // Custom Classes
#include "CDTable.h" #include "CDTable.h"
#include <map> #include <unordered_map>
#include <unordered_set>
/*! /*!
\file CDBehaviorParameterTable.hpp \file CDBehaviorParameterTable.hpp
@ -11,16 +12,16 @@
//! BehaviorParameter Entry Struct //! BehaviorParameter Entry Struct
struct CDBehaviorParameter { struct CDBehaviorParameter {
unsigned int behaviorID; //!< The Behavior ID 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 float value; //!< The value of the behavior template
}; };
//! BehaviorParameter table //! BehaviorParameter table
class CDBehaviorParameterTable : public CDTable { class CDBehaviorParameterTable : public CDTable {
private: private:
std::map<size_t, float> m_Entries; std::unordered_map<size_t, CDBehaviorParameter> m_Entries;
std::unordered_set<std::string> m_ParametersList;
public: public:
//! Constructor //! Constructor
@ -35,5 +36,7 @@ public:
*/ */
std::string GetName(void) const override; 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

@ -16,7 +16,7 @@ CDBehaviorTemplateTable::CDBehaviorTemplateTable(void) {
// Reserve the size // Reserve the size
this->entries.reserve(size); this->entries.reserve(size);
// Now get the data // Now get the data
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM BehaviorTemplate"); auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM BehaviorTemplate");
while (!tableData.eof()) { while (!tableData.eof()) {
@ -24,9 +24,16 @@ CDBehaviorTemplateTable::CDBehaviorTemplateTable(void) {
entry.behaviorID = tableData.getIntField(0, -1); entry.behaviorID = tableData.getIntField(0, -1);
entry.templateID = tableData.getIntField(1, -1); entry.templateID = tableData.getIntField(1, -1);
entry.effectID = tableData.getIntField(2, -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->entries.push_back(entry);
this->entriesMappedByBehaviorID.insert(std::make_pair(entry.behaviorID, entry));
tableData.nextRow(); tableData.nextRow();
} }
@ -55,3 +62,16 @@ std::vector<CDBehaviorTemplate> CDBehaviorTemplateTable::Query(std::function<boo
std::vector<CDBehaviorTemplate> CDBehaviorTemplateTable::GetEntries(void) const { std::vector<CDBehaviorTemplate> CDBehaviorTemplateTable::GetEntries(void) const {
return this->entries; 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 // Custom Classes
#include "CDTable.h" #include "CDTable.h"
#include <unordered_map>
#include <unordered_set>
/*! /*!
\file CDBehaviorTemplateTable.hpp \file CDBehaviorTemplateTable.hpp
@ -10,10 +12,10 @@
//! BehaviorTemplate Entry Struct //! BehaviorTemplate Entry Struct
struct CDBehaviorTemplate { struct CDBehaviorTemplate {
unsigned int behaviorID; //!< The Behavior ID unsigned int behaviorID; //!< The Behavior ID
unsigned int templateID; //!< The Template ID (LOT) unsigned int templateID; //!< The Template ID (LOT)
unsigned int effectID; //!< The Effect ID attached 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 { class CDBehaviorTemplateTable : public CDTable {
private: private:
std::vector<CDBehaviorTemplate> entries; std::vector<CDBehaviorTemplate> entries;
std::unordered_map<uint32_t, CDBehaviorTemplate> entriesMappedByBehaviorID;
std::unordered_set<std::string> m_EffectHandles;
public: public:
//! Constructor //! Constructor
@ -47,5 +50,6 @@ public:
\return The entries \return The entries
*/ */
std::vector<CDBehaviorTemplate> GetEntries(void) const; 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 // Checking for 2 here, not sure what to do when there's more stuff than expected
if (amountSplit.size() == 2) { if (amountSplit.size() == 2) {
currencies.insert({ currencies.insert({
std::stol(amountSplit[0]), std::stoull(amountSplit[0]),
std::stoi(amountSplit[1]) 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_Components = {};
m_DieCallbacks = {}; m_DieCallbacks = {};
m_PhantomCollisionCallbacks = {}; m_PhantomCollisionCallbacks = {};
m_IsParentChildDirty = true;
m_Settings = info.settings; m_Settings = info.settings;
m_NetworkSettings = info.networkSettings; m_NetworkSettings = info.networkSettings;
@ -98,22 +99,6 @@ Entity::~Entity() {
m_Character->SaveXMLToDatabase(); 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(); CancelAllTimers();
CancelCallbackTimers(); CancelCallbackTimers();
@ -124,6 +109,14 @@ Entity::~Entity() {
m_Components.erase(pair.first); m_Components.erase(pair.first);
} }
for (auto child : m_ChildEntities) {
if (child) child->RemoveParent();
}
if (m_ParentEntity) {
m_ParentEntity->RemoveChild(this);
}
} }
void Entity::Initialize() void Entity::Initialize()
@ -220,8 +213,9 @@ void Entity::Initialize()
m_Components.insert(std::make_pair(COMPONENT_TYPE_ZONE_CONTROL, nullptr)); m_Components.insert(std::make_pair(COMPONENT_TYPE_ZONE_CONTROL, nullptr));
} }
if (compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_POSSESSABLE) > 0) { uint32_t possessableComponentId = compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_POSSESSABLE);
m_Components.insert(std::make_pair(COMPONENT_TYPE_POSSESSABLE, new PossessableComponent(this))); 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) { 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) { 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); CharacterComponent* comp = new CharacterComponent(this, m_Character);
m_Components.insert(std::make_pair(COMPONENT_TYPE_CHARACTER, comp)); 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->SetPostImaginationCost(rebCompData[0].post_imagination_cost);
comp->SetTimeBeforeSmash(rebCompData[0].time_before_smash); 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"); const auto rebuildResetTime = GetVar<float>(u"rebuild_reset_time");
if (rebuildResetTime != 0.0f) { if (rebuildResetTime != 0.0f) {
@ -631,10 +614,6 @@ void Entity::Initialize()
m_Components.insert(std::make_pair(COMPONENT_TYPE_RENDER, render)); 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) { 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))); 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 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); outBitStream->Write(m_ParentEntity != nullptr);
if (m_ParentEntity) { if (m_ParentEntity) {
outBitStream->Write(m_ParentEntity->GetObjectID()); outBitStream->Write(m_ParentEntity->GetObjectID());
@ -1079,8 +1061,15 @@ void Entity::WriteComponents(RakNet::BitStream* outBitStream, eReplicaPacketType
} }
CharacterComponent* characterComponent; 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); characterComponent->Serialize(outBitStream, bIsInitialUpdate, flags);
} }
@ -1186,11 +1175,10 @@ void Entity::WriteComponents(RakNet::BitStream* outBitStream, eReplicaPacketType
outBitStream->Write<uint32_t>(0x40000000); outBitStream->Write<uint32_t>(0x40000000);
} }
PossessorComponent* possessorComponent; // BBB Component, unused currently
if (TryGetComponent(COMPONENT_TYPE_POSSESSOR, possessorComponent)) // Need to to write0 so that is serlaizese correctly
{ // TODO: Implement BBB Component
possessorComponent->Serialize(outBitStream, bIsInitialUpdate, flags); outBitStream->Write0();
}
/* /*
if (m_Trigger != nullptr) if (m_Trigger != nullptr)
@ -1218,17 +1206,21 @@ void Entity::UpdateXMLDoc(tinyxml2::XMLDocument* doc) {
} }
void Entity::Update(const float deltaTime) { void Entity::Update(const float deltaTime) {
for (int i = 0; i < m_Timers.size(); i++) { uint32_t timerPosition;
m_Timers[i]->Update(deltaTime); timerPosition = 0;
if (m_Timers[i]->GetTime() <= 0) { while (timerPosition < m_Timers.size()) {
const auto timerName = m_Timers[i]->GetName(); m_Timers[timerPosition]->Update(deltaTime);
if (m_Timers[timerPosition]->GetTime() <= 0) {
const auto timerName = m_Timers[timerPosition]->GetName();
delete m_Timers[i]; delete m_Timers[timerPosition];
m_Timers.erase(m_Timers.begin() + i); m_Timers.erase(m_Timers.begin() + timerPosition);
for (CppScripts::Script* script : CppScripts::GetEntityScripts(this)) { for (CppScripts::Script* script : CppScripts::GetEntityScripts(this)) {
script->OnTimerDone(this, timerName); script->OnTimerDone(this, timerName);
} }
} else {
timerPosition++;
} }
} }
@ -1240,6 +1232,14 @@ void Entity::Update(const float deltaTime) {
m_CallbackTimers.erase(m_CallbackTimers.begin() + i); m_CallbackTimers.erase(m_CallbackTimers.begin() + i);
} }
} }
// 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()) if (IsSleeping())
{ {
@ -1661,12 +1661,30 @@ void Entity::RegisterCoinDrop(uint64_t count) {
} }
void Entity::AddChild(Entity* child) { void Entity::AddChild(Entity* child) {
m_IsParentChildDirty = true;
m_ChildEntities.push_back(child); 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) { void Entity::AddTimer(std::string name, float time) {
EntityTimer* timer = new EntityTimer(name, 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) { void Entity::AddCallbackTimer(float time, std::function<void()> callback) {

View File

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

View File

@ -217,37 +217,42 @@ void EntityManager::UpdateEntities(const float deltaTime) {
m_EntitiesToKill.clear(); 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 we are a player run through the player destructor.
} if (entityToDelete->IsPlayer())
if (entity != nullptr)
{
if (entity->GetNetworkId() != 0)
{ {
m_LostNetworkIds.push(entity->GetNetworkId()); delete dynamic_cast<Player*>(entityToDelete);
}
if (entity->IsPlayer())
{
delete dynamic_cast<Player*>(entity);
} }
else 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(); m_EntitiesToDelete.clear();
@ -396,7 +401,7 @@ void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr
Game::server->Send(&stream, sysAddr, false); 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()) if (entity->IsPlayer())
{ {

View File

@ -3,8 +3,10 @@
#include "Database.h" #include "Database.h"
#include "EntityManager.h" #include "EntityManager.h"
#include "Character.h" #include "Character.h"
#include "Game.h"
#include "GameMessages.h" #include "GameMessages.h"
#include "dLogger.h" #include "dLogger.h"
#include "dConfig.h"
Leaderboard::Leaderboard(uint32_t gameID, uint32_t infoType, bool weekly, std::vector<LeaderboardEntry> entries, Leaderboard::Leaderboard(uint32_t gameID, uint32_t infoType, bool weekly, std::vector<LeaderboardEntry> entries,
LWOOBJID relatedPlayer, LeaderboardType leaderboardType) { 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 storedTime = result->getInt(1);
const auto storedScore = result->getInt(2); const auto storedScore = result->getInt(2);
auto highscore = true; auto highscore = true;
bool classicSurvivalScoring = Game::config->GetValue("classic_survival_scoring") == "1";
switch (leaderboardType) { switch (leaderboardType) {
case ShootingGallery: case ShootingGallery:
@ -97,8 +100,11 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t
highscore = false; highscore = false;
break; break;
case Racing: case Racing:
if (time >= storedTime)
highscore = false;
break;
case MonumentRace: case MonumentRace:
if (time > storedTime) if (time >= storedTime)
highscore = false; highscore = false;
break; break;
case FootRace: case FootRace:
@ -106,8 +112,18 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t
highscore = false; highscore = false;
break; break;
case Survival: 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: case SurvivalNS:
if (score < storedScore || time >= storedTime) if (!(score > storedScore || (time < storedTime && score >= storedScore)))
highscore = false; highscore = false;
break; break;
default: default:
@ -125,7 +141,7 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t
delete result; delete result;
if (any) { 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(1, time);
statement->setInt(2, score); statement->setInt(2, score);
statement->setUInt64(3, character->GetID()); statement->setUInt64(3, character->GetID());
@ -134,6 +150,7 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t
delete statement; delete statement;
} else { } 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 (?, ?, ?, ?);"); auto* statement = Database::CreatePreppedStmt("INSERT INTO leaderboard (character_id, game_id, time, score) VALUES (?, ?, ?, ?);");
statement->setUInt64(1, character->GetID()); statement->setUInt64(1, character->GetID());
statement->setInt(2, gameID); statement->setInt(2, gameID);
@ -149,15 +166,62 @@ Leaderboard *LeaderboardManager::GetLeaderboard(uint32_t gameID, InfoType infoTy
auto leaderboardType = GetLeaderboardType(gameID); auto leaderboardType = GetLeaderboardType(gameID);
std::string query; std::string query;
bool classicSurvivalScoring = Game::config->GetValue("classic_survival_scoring") == "1";
switch (infoType) { switch (infoType) {
case InfoType::Standings: case InfoType::Standings:
query = leaderboardType == MonumentRace ? standingsQueryAsc : standingsQuery; switch (leaderboardType) {
case ShootingGallery:
query = standingsScoreQuery; // Shooting gallery is based on the highest score.
break;
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 = standingsTimeQueryAsc; // MonumentRace and Racing are based on the shortest time.
}
break; break;
case InfoType::Friends: case InfoType::Friends:
query = leaderboardType == MonumentRace ? friendsQueryAsc : friendsQuery; 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; break;
default: default:
query = leaderboardType == MonumentRace ? topPlayersQueryAsc : topPlayersQuery; 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); auto* statement = Database::CreatePreppedStmt(query);
@ -183,14 +247,15 @@ Leaderboard *LeaderboardManager::GetLeaderboard(uint32_t gameID, InfoType infoTy
uint32_t index = 0; uint32_t index = 0;
while (res->next()) { while (res->next()) {
entries.push_back({ LeaderboardEntry entry;
res->getUInt64(4), entry.playerID = res->getUInt64(4);
res->getString(5), entry.playerName = res->getString(5);
res->getUInt(1), entry.time = res->getUInt(1);
res->getUInt(2), entry.score = res->getUInt(2);
res->getUInt(3), entry.placement = res->getUInt(3);
res->getUInt(6) entry.lastPlayed = res->getUInt(6);
});
entries.push_back(entry);
index++; index++;
} }
@ -220,7 +285,7 @@ LeaderboardType LeaderboardManager::GetLeaderboardType(uint32_t gameID) {
return LeaderboardType::None; return LeaderboardType::None;
} }
const std::string LeaderboardManager::topPlayersQuery = const std::string LeaderboardManager::topPlayersScoreQuery =
"WITH leaderboard_vales AS ( " "WITH leaderboard_vales AS ( "
" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, " " 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 " "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 " "SELECT time, score, leaderboard_rank, id, name, last_played "
"FROM leaderboard_vales LIMIT 11;"; "FROM leaderboard_vales LIMIT 11;";
const std::string LeaderboardManager::friendsQuery = const std::string LeaderboardManager::friendsScoreQuery =
"WITH leaderboard_vales AS ( " "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, " " 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 " " 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 " "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);"; "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 ( " "WITH leaderboard_vales AS ( "
" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, " " 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 " " 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 " "FROM leaderboard_vales, personal_values "
"WHERE leaderboard_rank BETWEEN min_rank AND max_rank;"; "WHERE leaderboard_rank BETWEEN min_rank AND max_rank;";
const std::string LeaderboardManager::topPlayersQueryAsc = const std::string LeaderboardManager::topPlayersScoreQueryAsc =
"WITH leaderboard_vales AS ( " "WITH leaderboard_vales AS ( "
" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, " " 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 " "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 " "SELECT time, score, leaderboard_rank, id, name, last_played "
"FROM leaderboard_vales LIMIT 11;"; "FROM leaderboard_vales LIMIT 11;";
const std::string LeaderboardManager::friendsQueryAsc = const std::string LeaderboardManager::friendsScoreQueryAsc =
"WITH leaderboard_vales AS ( " "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, " " 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 " " 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 " "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);"; "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 ( " "WITH leaderboard_vales AS ( "
" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, " " 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 " " 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 " "SELECT time, score, leaderboard_rank, id, name, last_played "
"FROM leaderboard_vales, personal_values " "FROM leaderboard_vales, personal_values "
"WHERE leaderboard_rank BETWEEN min_rank AND max_rank;"; "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); static LeaderboardType GetLeaderboardType(uint32_t gameID);
private: private:
static LeaderboardManager* address; static LeaderboardManager* address;
static const std::string topPlayersQuery;
static const std::string friendsQuery; // Modified 12/12/2021: Existing queries were renamed to be more descriptive.
static const std::string standingsQuery; static const std::string topPlayersScoreQuery;
static const std::string topPlayersQueryAsc; static const std::string friendsScoreQuery;
static const std::string friendsQueryAsc; static const std::string standingsScoreQuery;
static const std::string standingsQueryAsc; 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 "dZoneManager.h"
#include "CharacterComponent.h" #include "CharacterComponent.h"
#include "Mail.h" #include "Mail.h"
#include "CppScripts.h"
std::vector<Player*> Player::m_Players = {}; std::vector<Player*> Player::m_Players = {};
@ -275,5 +276,21 @@ Player::~Player()
return; 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); m_Players.erase(iter);
} }

View File

@ -369,10 +369,8 @@ void UserManager::DeleteCharacter(const SystemAddress& sysAddr, Packet* packet)
} }
LWOOBJID objectID = PacketUtils::ReadPacketS64(8, packet); LWOOBJID objectID = PacketUtils::ReadPacketS64(8, packet);
objectID = GeneralUtils::ClearBit(objectID, OBJECT_BIT_CHARACTER); uint32_t charID = static_cast<uint32_t>(objectID);
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); Game::logger->Log("UserManager", "Received char delete req for ID: %llu (%u)\n", objectID, charID);
//Check if this user has this character: //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=?;"); sql::PreparedStatement* stmt = Database::CreatePreppedStmt("DELETE FROM friends WHERE player_id=? OR friend_id=?;");
stmt->setUInt64(1, charID); stmt->setUInt(1, charID);
stmt->setUInt64(2, charID); stmt->setUInt(2, charID);
stmt->execute(); stmt->execute();
delete stmt; 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=?;"); sql::PreparedStatement* stmt = Database::CreatePreppedStmt("DELETE FROM leaderboard WHERE character_id=?;");

View File

@ -7,6 +7,7 @@
#include "dLogger.h" #include "dLogger.h"
#include "BehaviorTemplates.h" #include "BehaviorTemplates.h"
#include "BehaviorBranchContext.h" #include "BehaviorBranchContext.h"
#include <unordered_map>
/* /*
* Behavior includes * Behavior includes
@ -45,6 +46,7 @@
#include "InterruptBehavior.h" #include "InterruptBehavior.h"
#include "PlayEffectBehavior.h" #include "PlayEffectBehavior.h"
#include "DamageAbsorptionBehavior.h" #include "DamageAbsorptionBehavior.h"
#include "VentureVisionBehavior.h"
#include "BlockBehavior.h" #include "BlockBehavior.h"
#include "ClearTargetBehavior.h" #include "ClearTargetBehavior.h"
#include "PullToPointBehavior.h" #include "PullToPointBehavior.h"
@ -68,7 +70,7 @@
#include "RenderComponent.h" #include "RenderComponent.h"
#include "DestroyableComponent.h" #include "DestroyableComponent.h"
std::map<uint32_t, Behavior*> Behavior::Cache = {}; std::unordered_map<uint32_t, Behavior*> Behavior::Cache = {};
CDBehaviorParameterTable* Behavior::BehaviorParameterTable = nullptr; CDBehaviorParameterTable* Behavior::BehaviorParameterTable = nullptr;
Behavior* Behavior::GetBehavior(const uint32_t behaviorId) Behavior* Behavior::GetBehavior(const uint32_t behaviorId)
@ -91,21 +93,21 @@ Behavior* Behavior::GetBehavior(const uint32_t behaviorId)
Behavior* Behavior::CreateBehavior(const uint32_t behaviorId) Behavior* Behavior::CreateBehavior(const uint32_t behaviorId)
{ {
auto* cached = GetBehavior(behaviorId); auto* cached = GetBehavior(behaviorId);
if (cached != nullptr) if (cached != nullptr)
{ {
return cached; return cached;
} }
if (behaviorId == 0) if (behaviorId == 0)
{ {
return new EmptyBehavior(0); return new EmptyBehavior(0);
} }
const auto templateId = GetBehaviorTemplate(behaviorId); const auto templateId = GetBehaviorTemplate(behaviorId);
Behavior* behavior = nullptr; Behavior* behavior = nullptr;
switch (templateId) switch (templateId)
{ {
case BehaviorTemplates::BEHAVIOR_EMPTY: break; case BehaviorTemplates::BEHAVIOR_EMPTY: break;
@ -176,7 +178,9 @@ Behavior* Behavior::CreateBehavior(const uint32_t behaviorId)
case BehaviorTemplates::BEHAVIOR_LOOT_BUFF: case BehaviorTemplates::BEHAVIOR_LOOT_BUFF:
behavior = new LootBuffBehavior(behaviorId); behavior = new LootBuffBehavior(behaviorId);
break; break;
case BehaviorTemplates::BEHAVIOR_VENTURE_VISION: break; case BehaviorTemplates::BEHAVIOR_VENTURE_VISION:
behavior = new VentureVisionBehavior(behaviorId);
break;
case BehaviorTemplates::BEHAVIOR_SPAWN_OBJECT: case BehaviorTemplates::BEHAVIOR_SPAWN_OBJECT:
behavior = new SpawnBehavior(behaviorId); behavior = new SpawnBehavior(behaviorId);
break; break;
@ -191,8 +195,8 @@ Behavior* Behavior::CreateBehavior(const uint32_t behaviorId)
behavior = new JetPackBehavior(behaviorId); behavior = new JetPackBehavior(behaviorId);
break; break;
case BehaviorTemplates::BEHAVIOR_SKILL_EVENT: case BehaviorTemplates::BEHAVIOR_SKILL_EVENT:
behavior = new SkillEventBehavior(behaviorId); behavior = new SkillEventBehavior(behaviorId);
break; break;
case BehaviorTemplates::BEHAVIOR_CONSUME_ITEM: break; case BehaviorTemplates::BEHAVIOR_CONSUME_ITEM: break;
case BehaviorTemplates::BEHAVIOR_SKILL_CAST_FAILED: case BehaviorTemplates::BEHAVIOR_SKILL_CAST_FAILED:
behavior = new SkillCastFailedBehavior(behaviorId); behavior = new SkillCastFailedBehavior(behaviorId);
@ -272,7 +276,7 @@ Behavior* Behavior::CreateBehavior(const uint32_t behaviorId)
if (behavior == nullptr) if (behavior == nullptr)
{ {
//Game::logger->Log("Behavior", "Failed to load unimplemented template id (%i)!\n", templateId); //Game::logger->Log("Behavior", "Failed to load unimplemented template id (%i)!\n", templateId);
behavior = new EmptyBehavior(behaviorId); behavior = new EmptyBehavior(behaviorId);
} }
@ -281,30 +285,23 @@ Behavior* Behavior::CreateBehavior(const uint32_t behaviorId)
return behavior; return behavior;
} }
BehaviorTemplates Behavior::GetBehaviorTemplate(const uint32_t behaviorId) BehaviorTemplates Behavior::GetBehaviorTemplate(const uint32_t behaviorId) {
{ auto behaviorTemplateTable = CDClientManager::Instance()->GetTable<CDBehaviorTemplateTable>("BehaviorTemplate");
std::stringstream query;
query << "SELECT templateID FROM BehaviorTemplate WHERE behaviorID = " << std::to_string(behaviorId); BehaviorTemplates templateID = BehaviorTemplates::BEHAVIOR_EMPTY;
// Find behavior template by its behavior id. Default to 0.
auto result = CDClientDatabase::ExecuteQuery(query.str()); if (behaviorTemplateTable) {
auto templateEntry = behaviorTemplateTable->GetByBehaviorID(behaviorId);
// Make sure we do not proceed if we are trying to load an invalid behavior if (templateEntry.behaviorID == behaviorId) {
if (result.eof()) templateID = static_cast<BehaviorTemplates>(templateEntry.templateID);
{
if (behaviorId != 0)
{
Game::logger->Log("Behavior::GetBehaviorTemplate", "Failed to load behavior template with id (%i)!\n", behaviorId);
} }
return BehaviorTemplates::BEHAVIOR_EMPTY;
} }
const auto id = static_cast<BehaviorTemplates>(result.getIntField(0));
result.finalize(); if (templateID == BehaviorTemplates::BEHAVIOR_EMPTY && behaviorId != 0) {
Game::logger->Log("Behavior", "Failed to load behavior template with id (%i)!\n", behaviorId);
}
return id; return templateID;
} }
// For use with enemies, to display the correct damage animations on the players // For use with enemies, to display the correct damage animations on the players
@ -325,7 +322,7 @@ void Behavior::PlayFx(std::u16string type, const LWOOBJID target, const LWOOBJID
return; return;
} }
auto* renderComponent = targetEntity->GetComponent<RenderComponent>(); auto* renderComponent = targetEntity->GetComponent<RenderComponent>();
const auto typeString = GeneralUtils::UTF16ToWTF8(type); const auto typeString = GeneralUtils::UTF16ToWTF8(type);
@ -348,28 +345,35 @@ void Behavior::PlayFx(std::u16string type, const LWOOBJID target, const LWOOBJID
if (renderComponent == nullptr) if (renderComponent == nullptr)
{ {
GameMessages::SendPlayFXEffect(targetEntity, effectId, type, pair->second, secondary, 1, 1, true); GameMessages::SendPlayFXEffect(targetEntity, effectId, type, pair->second, secondary, 1, 1, true);
return; return;
} }
renderComponent->PlayEffect(effectId, type, pair->second, secondary); renderComponent->PlayEffect(effectId, type, pair->second, secondary);
return; return;
} }
} }
std::stringstream query;
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 result = CDClientDatabase::ExecuteQuery(query.str()); // 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 = ?;");
auto idQuery = CDClientDatabase::CreatePreppedStmt(
"SELECT effectName, effectType FROM BehaviorEffect WHERE effectID = ?;");
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)) if (result.eof() || result.fieldIsNull(0))
{ {
@ -381,7 +385,7 @@ void Behavior::PlayFx(std::u16string type, const LWOOBJID target, const LWOOBJID
if (type.empty()) if (type.empty())
{ {
const auto typeResult = result.getStringField(1); const auto typeResult = result.getStringField(1);
type = GeneralUtils::ASCIIToUTF16(typeResult); type = GeneralUtils::ASCIIToUTF16(typeResult);
m_effectType = new std::string(typeResult); m_effectType = new std::string(typeResult);
@ -394,7 +398,7 @@ void Behavior::PlayFx(std::u16string type, const LWOOBJID target, const LWOOBJID
if (renderComponent == nullptr) if (renderComponent == nullptr)
{ {
GameMessages::SendPlayFXEffect(targetEntity, effectId, type, name, secondary, 1, 1, true); GameMessages::SendPlayFXEffect(targetEntity, effectId, type, name, secondary, 1, 1, true);
return; return;
} }
@ -403,6 +407,17 @@ void Behavior::PlayFx(std::u16string type, const LWOOBJID target, const LWOOBJID
Behavior::Behavior(const uint32_t behaviorId) 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; this->m_behaviorId = behaviorId;
// Add to cache // Add to cache
@ -414,18 +429,8 @@ Behavior::Behavior(const uint32_t behaviorId)
this->m_templateId = BehaviorTemplates::BEHAVIOR_EMPTY; 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 // 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); Game::logger->Log("Behavior", "Failed to load behavior with id (%i)!\n", behaviorId);
@ -436,34 +441,19 @@ Behavior::Behavior(const uint32_t behaviorId)
return; return;
} }
this->m_templateId = static_cast<BehaviorTemplates>(result.getIntField(0)); this->m_templateId = static_cast<BehaviorTemplates>(templateInDatabase.templateID);
this->m_effectId = result.getIntField(1);
if (!result.fieldIsNull(2)) this->m_effectId = templateInDatabase.effectID;
{
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 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> Behavior::GetParameterNames() const
{ {
std::map<std::string, float> parameters; std::map<std::string, float> templatesInDatabase;
// Find behavior template by its behavior id.
std::stringstream query; if (!BehaviorParameterTable) BehaviorParameterTable = CDClientManager::Instance()->GetTable<CDBehaviorParameterTable>("BehaviorParameter");
if (BehaviorParameterTable) {
query << "SELECT parameterID, value FROM BehaviorParameter WHERE behaviorID = " << std::to_string(this->m_behaviorId); templatesInDatabase = BehaviorParameterTable->GetParametersByBehaviorID(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();
} }
tableData.finalize(); return templatesInDatabase;
return parameters;
} }
void Behavior::Load() void Behavior::Load()

View File

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

View File

@ -1,5 +1,8 @@
#pragma once #pragma once
#ifndef BEHAVIORSLOT_H
#define BEHAVIORSLOT_H
enum class BehaviorSlot enum class BehaviorSlot
{ {
Invalid = -1, Invalid = -1,
@ -9,3 +12,5 @@ enum class BehaviorSlot
Head, Head,
Consumable 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 // TODO
} }
void SwitchMultipleBehavior::Load() void SwitchMultipleBehavior::Load() {
{ auto query = CDClientDatabase::CreatePreppedStmt(
const auto b = std::to_string(this->m_behaviorId); "SELECT replace(bP1.parameterID, 'behavior ', '') as key, bP1.value as behavior, "
std::stringstream query; "(select bP2.value FROM BehaviorParameter bP2 WHERE bP2.behaviorID = ?1 AND bP2.parameterID LIKE 'value %' "
query << "SELECT replace(bP1.parameterID, 'behavior ', '') as key, bP1.value as behavior, " "AND replace(bP1.parameterID, 'behavior ', '') = replace(bP2.parameterID, 'value ', '')) as value "
<< "(select bP2.value FROM BehaviorParameter bP2 WHERE bP2.behaviorID = " << b << " AND bP2.parameterID LIKE 'value %' " "FROM BehaviorParameter bP1 WHERE bP1.behaviorID = ?1 AND bP1.parameterID LIKE 'behavior %';");
<< "AND replace(bP1.parameterID, 'behavior ', '') = replace(bP2.parameterID, 'value ', '')) as value " query.bind(1, (int) this->m_behaviorId);
<< "FROM BehaviorParameter bP1 WHERE bP1.behaviorID = " << b << " AND bP1.parameterID LIKE 'behavior %'";
auto result = CDClientDatabase::ExecuteQuery(query.str()); auto result = query.execQuery();
while (!result.eof()) { while (!result.eof()) {
const auto behavior_id = static_cast<uint32_t>(result.getFloatField(1)); 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; m_SoftTimer = 5.0f;
//Grab the aggro information from BaseCombatAI: //Grab the aggro information from BaseCombatAI:
std::stringstream componentQuery; auto componentQuery = CDClientDatabase::CreatePreppedStmt(
"SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius FROM BaseCombatAIComponent WHERE id = ?;");
componentQuery << "SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius FROM BaseCombatAIComponent WHERE id = " << std::to_string(id); componentQuery.bind(1, (int) id);
auto componentResult = CDClientDatabase::ExecuteQuery(componentQuery.str()); auto componentResult = componentQuery.execQuery();
if (!componentResult.eof()) if (!componentResult.eof())
{ {
@ -64,12 +64,11 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id)
/* /*
* Find skills * Find skills
*/ */
auto skillQuery = CDClientDatabase::CreatePreppedStmt(
std::stringstream query; "SELECT skillID, cooldown, behaviorID FROM SkillBehavior WHERE skillID IN (SELECT skillID FROM ObjectSkills WHERE objectTemplate = ?);");
skillQuery.bind(1, (int) parent->GetLOT());
query << "SELECT skillID, cooldown, behaviorID FROM SkillBehavior WHERE skillID IN (SELECT skillID FROM ObjectSkills WHERE objectTemplate = " << std::to_string(parent->GetLOT()) << " )";
auto result = skillQuery.execQuery();
auto result = CDClientDatabase::ExecuteQuery(query.str());
while (!result.eof()) { while (!result.eof()) {
const auto skillId = static_cast<uint32_t>(result.getIntField(0)); const auto skillId = static_cast<uint32_t>(result.getIntField(0));

View File

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

View File

@ -42,6 +42,8 @@ void BuildBorderComponent::OnUse(Entity* originator) {
return; return;
} }
inventoryComponent->PushEquippedItems();
Game::logger->Log("BuildBorderComponent", "Starting with %llu\n", buildArea); Game::logger->Log("BuildBorderComponent", "Starting with %llu\n", buildArea);
if (PropertyManagementComponent::Instance() != nullptr) { 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 "InventoryComponent.h"
#include "ControllablePhysicsComponent.h" #include "ControllablePhysicsComponent.h"
#include "EntityManager.h" #include "EntityManager.h"
#include "PossessorComponent.h"
#include "VehiclePhysicsComponent.h" #include "VehiclePhysicsComponent.h"
#include "GameMessages.h" #include "GameMessages.h"
#include "Item.h" #include "Item.h"
@ -81,13 +80,6 @@ CharacterComponent::~CharacterComponent() {
} }
void CharacterComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { 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->Write1();
outBitStream->Write(m_Level); outBitStream->Write(m_Level);
outBitStream->Write0(); outBitStream->Write0();
@ -793,3 +785,32 @@ ZoneStatistics& CharacterComponent::GetZoneStatisticsForMap(LWOMAPID mapID) {
m_ZoneStatistics.insert({ mapID, {0, 0, 0, 0, 0 } }); m_ZoneStatistics.insert({ mapID, {0, 0, 0, 0, 0 } });
return m_ZoneStatistics.at(mapID); 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; } 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 * Gets whether this character has PvP enabled, allowing combat between players
* @return * @return
@ -197,6 +179,18 @@ public:
*/ */
void SetLastRocketItemID(LWOOBJID lastRocketItemID) { m_LastRocketItemID = lastRocketItemID; } 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 * 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); 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 info regarding this character, including clothing styles, etc.
*/ */
Character* m_Character; Character* m_Character;
private: private:
/**
* The map of active venture vision effects
*/
std::map<std::string, uint32_t> m_ActiveVentureVisionEffects;
/** /**
* Whether this character is racing * Whether this character is racing
*/ */
bool m_IsRacing; bool m_IsRacing;
/**
* The object ID of the vehicle the character is currently in
*/
LWOOBJID m_VehicleObjectID;
/** /**
* Possessible type, used by the shooting gallery * Possessible type, used by the shooting gallery
*/ */
@ -582,6 +591,11 @@ private:
* ID of the last rocket used * ID of the last rocket used
*/ */
LWOOBJID m_LastRocketItemID = LWOOBJID_EMPTY; LWOOBJID m_LastRocketItemID = LWOOBJID_EMPTY;
/**
* Mount Item ID
*/
LWOOBJID m_MountItemID = LWOOBJID_EMPTY;
}; };
#endif // CHARACTERCOMPONENT_H #endif // CHARACTERCOMPONENT_H

View File

@ -106,7 +106,7 @@ void DestroyableComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsIn
outBitStream->Write0(); //Contains info about immunities this object has, but it's left out for now. outBitStream->Write0(); //Contains info about immunities this object has, but it's left out for now.
} }
outBitStream->Write(m_DirtyHealth || bIsInitialUpdate); outBitStream->Write(m_DirtyHealth || bIsInitialUpdate);
if (m_DirtyHealth || bIsInitialUpdate) { if (m_DirtyHealth || bIsInitialUpdate) {
outBitStream->Write(m_iHealth); outBitStream->Write(m_iHealth);
outBitStream->Write(m_fMaxHealth); outBitStream->Write(m_fMaxHealth);
@ -114,12 +114,12 @@ void DestroyableComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsIn
outBitStream->Write(m_fMaxArmor); outBitStream->Write(m_fMaxArmor);
outBitStream->Write(m_iImagination); outBitStream->Write(m_iImagination);
outBitStream->Write(m_fMaxImagination); outBitStream->Write(m_fMaxImagination);
outBitStream->Write(m_DamageToAbsorb); outBitStream->Write(m_DamageToAbsorb);
outBitStream->Write(IsImmune()); outBitStream->Write(IsImmune());
outBitStream->Write(m_IsGMImmune); outBitStream->Write(m_IsGMImmune);
outBitStream->Write(m_IsShielded); outBitStream->Write(m_IsShielded);
outBitStream->Write(m_fMaxHealth); outBitStream->Write(m_fMaxHealth);
outBitStream->Write(m_fMaxArmor); outBitStream->Write(m_fMaxArmor);
outBitStream->Write(m_fMaxImagination); outBitStream->Write(m_fMaxImagination);
@ -130,14 +130,14 @@ void DestroyableComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsIn
} }
outBitStream->Write(m_IsSmashable); outBitStream->Write(m_IsSmashable);
if (bIsInitialUpdate) { if (bIsInitialUpdate) {
outBitStream->Write(m_IsDead); outBitStream->Write(m_IsDead);
outBitStream->Write(m_IsSmashed); outBitStream->Write(m_IsSmashed);
if (m_IsSmashable) { if (m_IsSmashable) {
outBitStream->Write(m_HasBricks); outBitStream->Write(m_HasBricks);
if (m_ExplodeFactor != 1.0f) { if (m_ExplodeFactor != 1.0f) {
outBitStream->Write1(); outBitStream->Write1();
outBitStream->Write(m_ExplodeFactor); outBitStream->Write(m_ExplodeFactor);
@ -146,10 +146,10 @@ void DestroyableComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsIn
} }
} }
} }
m_DirtyHealth = false; m_DirtyHealth = false;
} }
if (m_DirtyThreatList || bIsInitialUpdate) { if (m_DirtyThreatList || bIsInitialUpdate) {
outBitStream->Write1(); outBitStream->Write1();
outBitStream->Write(m_HasThreats); outBitStream->Write(m_HasThreats);
@ -167,7 +167,7 @@ void DestroyableComponent::LoadFromXML(tinyxml2::XMLDocument* doc) {
} }
auto* buffComponent = m_Parent->GetComponent<BuffComponent>(); auto* buffComponent = m_Parent->GetComponent<BuffComponent>();
if (buffComponent != nullptr) { if (buffComponent != nullptr) {
buffComponent->LoadFromXML(doc); buffComponent->LoadFromXML(doc);
} }
@ -187,9 +187,9 @@ void DestroyableComponent::UpdateXml(tinyxml2::XMLDocument* doc) {
Game::logger->Log("DestroyableComponent", "Failed to find dest tag!\n"); Game::logger->Log("DestroyableComponent", "Failed to find dest tag!\n");
return; return;
} }
auto* buffComponent = m_Parent->GetComponent<BuffComponent>(); auto* buffComponent = m_Parent->GetComponent<BuffComponent>();
if (buffComponent != nullptr) { if (buffComponent != nullptr) {
buffComponent->UpdateXml(doc); buffComponent->UpdateXml(doc);
} }
@ -235,7 +235,7 @@ void DestroyableComponent::SetMaxHealth(float value, bool playAnim) {
args.InsertValue("amount", amount); args.InsertValue("amount", amount);
args.InsertValue("type", type); args.InsertValue("type", type);
GameMessages::SendUIMessageServerToSingleClient(m_Parent, m_Parent->GetParentUser()->GetSystemAddress(), "MaxPlayerBarUpdate", &args); GameMessages::SendUIMessageServerToSingleClient(m_Parent, m_Parent->GetParentUser()->GetSystemAddress(), "MaxPlayerBarUpdate", &args);
delete amount; delete amount;
delete type; delete type;
} }
@ -283,7 +283,7 @@ void DestroyableComponent::SetMaxArmor(float value, bool playAnim) {
args.InsertValue("type", type); args.InsertValue("type", type);
GameMessages::SendUIMessageServerToSingleClient(m_Parent, m_Parent->GetParentUser()->GetSystemAddress(), "MaxPlayerBarUpdate", &args); GameMessages::SendUIMessageServerToSingleClient(m_Parent, m_Parent->GetParentUser()->GetSystemAddress(), "MaxPlayerBarUpdate", &args);
delete amount; delete amount;
delete type; delete type;
} }
@ -329,7 +329,7 @@ void DestroyableComponent::SetMaxImagination(float value, bool playAnim) {
args.InsertValue("amount", amount); args.InsertValue("amount", amount);
args.InsertValue("type", type); args.InsertValue("type", type);
GameMessages::SendUIMessageServerToSingleClient(m_Parent, m_Parent->GetParentUser()->GetSystemAddress(), "MaxPlayerBarUpdate", &args); GameMessages::SendUIMessageServerToSingleClient(m_Parent, m_Parent->GetParentUser()->GetSystemAddress(), "MaxPlayerBarUpdate", &args);
delete amount; delete amount;
delete type; delete type;
} }
@ -342,7 +342,7 @@ void DestroyableComponent::SetDamageToAbsorb(int32_t value)
m_DamageToAbsorb = value; m_DamageToAbsorb = value;
} }
void DestroyableComponent::SetDamageReduction(int32_t value) void DestroyableComponent::SetDamageReduction(int32_t value)
{ {
m_DirtyHealth = true; m_DirtyHealth = true;
m_DamageReduction = value; m_DamageReduction = value;
@ -354,7 +354,7 @@ void DestroyableComponent::SetIsImmune(bool value)
m_ImmuneStacks = value ? 1 : 0; m_ImmuneStacks = value ? 1 : 0;
} }
void DestroyableComponent::SetIsGMImmune(bool value) void DestroyableComponent::SetIsGMImmune(bool value)
{ {
m_DirtyHealth = true; m_DirtyHealth = true;
m_IsGMImmune = value; m_IsGMImmune = value;
@ -375,11 +375,11 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore
m_FactionIDs.push_back(factionID); m_FactionIDs.push_back(factionID);
m_DirtyHealth = true; 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 = query.execQuery();
auto result = CDClientDatabase::ExecuteQuery(query.str());
if (result.eof()) return; if (result.eof()) return;
@ -389,10 +389,10 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore
std::stringstream ss(list_string); std::stringstream ss(list_string);
std::string token; std::string token;
while (std::getline(ss, token, ',')) { while (std::getline(ss, token, ',')) {
if (token.empty()) continue; if (token.empty()) continue;
auto id = std::stoi(token); auto id = std::stoi(token);
auto exclude = std::find(m_FactionIDs.begin(), m_FactionIDs.end(), id) != m_FactionIDs.end(); auto exclude = std::find(m_FactionIDs.begin(), m_FactionIDs.end(), id) != m_FactionIDs.end();
@ -406,7 +406,7 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore
{ {
continue; continue;
} }
AddEnemyFaction(id); AddEnemyFaction(id);
} }
@ -522,7 +522,7 @@ bool DestroyableComponent::CheckValidity(const LWOOBJID target, const bool ignor
if (targetQuickbuild != nullptr) if (targetQuickbuild != nullptr)
{ {
const auto state = targetQuickbuild->GetState(); const auto state = targetQuickbuild->GetState();
if (state != REBUILD_COMPLETED) if (state != REBUILD_COMPLETED)
{ {
return false; return false;
@ -562,7 +562,7 @@ void DestroyableComponent::Imagine(const int32_t deltaImagination)
{ {
auto current = static_cast<int32_t>(GetImagination()); auto current = static_cast<int32_t>(GetImagination());
const auto max = static_cast<int32_t>(GetMaxImagination()); const auto max = static_cast<int32_t>(GetMaxImagination());
current += deltaImagination; current += deltaImagination;
current = std::min(current, max); current = std::min(current, max);
@ -635,7 +635,7 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
damage -= absorbDamage; damage -= absorbDamage;
absorb -= absorbDamage; absorb -= absorbDamage;
const auto armorDamage = std::min(damage, armor); const auto armorDamage = std::min(damage, armor);
damage -= armorDamage; damage -= armorDamage;
@ -657,7 +657,7 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
{ {
EntityManager::Instance()->SerializeEntity(m_Parent); EntityManager::Instance()->SerializeEntity(m_Parent);
} }
auto* attacker = EntityManager::Instance()->GetEntity(source); auto* attacker = EntityManager::Instance()->GetEntity(source);
m_Parent->OnHit(attacker); m_Parent->OnHit(attacker);
m_Parent->OnHitOrHealResult(attacker, sourceDamage); m_Parent->OnHitOrHealResult(attacker, sourceDamage);
@ -696,10 +696,10 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
if (owner != nullptr) if (owner != nullptr)
{ {
auto* team = TeamManager::Instance()->GetTeam(owner->GetObjectID());
owner = owner->GetOwner(); // If the owner is overwritten, we collect that here 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; const auto isEnemy = m_Parent->GetComponent<BaseCombatAIComponent>() != nullptr;
auto* inventoryComponent = owner->GetComponent<InventoryComponent>(); auto* inventoryComponent = owner->GetComponent<InventoryComponent>();
@ -736,7 +736,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
} }
} }
} }
const auto isPlayer = m_Parent->IsPlayer(); const auto isPlayer = m_Parent->IsPlayer();
GameMessages::SendDie(m_Parent, source, source, true, killType, deathType, 0, 0, 0, isPlayer, false, 1); GameMessages::SendDie(m_Parent, source, source, true, killType, deathType, 0, 0, 0, isPlayer, false, 1);
@ -765,14 +765,14 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
auto* member = EntityManager::Instance()->GetEntity(specificOwner); auto* member = EntityManager::Instance()->GetEntity(specificOwner);
if (member) LootGenerator::Instance().DropLoot(member, m_Parent, lootMatrixId, GetMinCoins(), GetMaxCoins()); if (member) LootGenerator::Instance().DropLoot(member, m_Parent, lootMatrixId, GetMinCoins(), GetMaxCoins());
} }
else { else {
for (const auto memberId : team->members) { // Free for all for (const auto memberId : team->members) { // Free for all
auto* member = EntityManager::Instance()->GetEntity(memberId); auto* member = EntityManager::Instance()->GetEntity(memberId);
if (member == nullptr) continue; if (member == nullptr) continue;
LootGenerator::Instance().DropLoot(member, m_Parent, lootMatrixId, GetMinCoins(), GetMaxCoins()); LootGenerator::Instance().DropLoot(member, m_Parent, lootMatrixId, GetMinCoins(), GetMaxCoins());
} }
} }
} }
@ -846,7 +846,7 @@ void DestroyableComponent::PopImmunity(int32_t stacks)
m_ImmuneStacks -= stacks; m_ImmuneStacks -= stacks;
} }
void DestroyableComponent::FixStats() void DestroyableComponent::FixStats()
{ {
auto* entity = GetParent(); auto* entity = GetParent();
@ -904,7 +904,7 @@ void DestroyableComponent::FixStats()
// Add the stats // Add the stats
const auto& info = mission->GetClientInfo(); const auto& info = mission->GetClientInfo();
maxHealth += info.reward_maxhealth; maxHealth += info.reward_maxhealth;
maxImagination += info.reward_maximagination; maxImagination += info.reward_maximagination;
} }

View File

@ -24,6 +24,8 @@
#include "dZoneManager.h" #include "dZoneManager.h"
#include "PropertyManagementComponent.h" #include "PropertyManagementComponent.h"
#include "DestroyableComponent.h" #include "DestroyableComponent.h"
#include "dConfig.h"
#include "eItemType.h"
InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* document) : Component(parent) InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* document) : Component(parent)
{ {
@ -51,18 +53,18 @@ InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* do
auto items = inventoryComponentTable->Query([=](const CDInventoryComponent entry) { return entry.id == componentId; }); auto items = inventoryComponentTable->Query([=](const CDInventoryComponent entry) { return entry.id == componentId; });
auto slot = 0u; auto slot = 0u;
for (const auto& item : items) for (const auto& item : items)
{ {
if (!item.equip || !Inventory::IsValidItem(item.itemid)) if (!item.equip || !Inventory::IsValidItem(item.itemid))
{ {
continue; continue;
} }
const LWOOBJID id = ObjectIDManager::Instance()->GenerateObjectID(); const LWOOBJID id = ObjectIDManager::Instance()->GenerateObjectID();
const auto& info = Inventory::FindItemComponent(item.itemid); const auto& info = Inventory::FindItemComponent(item.itemid);
UpdateSlot(info.equipLocation, { id, static_cast<LOT>(item.itemid), item.count, slot++ }); UpdateSlot(info.equipLocation, { id, static_cast<LOT>(item.itemid), item.count, slot++ });
} }
} }
@ -178,7 +180,7 @@ void InventoryComponent::AddItem(
auto* missions = static_cast<MissionComponent*>(this->m_Parent->GetComponent(COMPONENT_TYPE_MISSION)); auto* missions = static_cast<MissionComponent*>(this->m_Parent->GetComponent(COMPONENT_TYPE_MISSION));
auto* inventory = GetInventory(inventoryType); auto* inventory = GetInventory(inventoryType);
if (!config.empty() || bound) if (!config.empty() || bound)
{ {
const auto slot = preferredSlot != -1 && inventory->IsSlotEmpty(preferredSlot) ? preferredSlot : inventory->FindEmptySlot(); const auto slot = preferredSlot != -1 && inventory->IsSlotEmpty(preferredSlot) ? preferredSlot : inventory->FindEmptySlot();
@ -189,6 +191,7 @@ void InventoryComponent::AddItem(
return; return;
} }
auto* item = new Item(lot, inventory, slot, count, config, parent, showFlyingLoot, isModMoveAndEquip, subKey, bound, lootSourceType); auto* item = new Item(lot, inventory, slot, count, config, parent, showFlyingLoot, isModMoveAndEquip, subKey, bound, lootSourceType);
if (missions != nullptr && !IsTransferInventory(inventoryType)) if (missions != nullptr && !IsTransferInventory(inventoryType))
@ -200,14 +203,15 @@ void InventoryComponent::AddItem(
} }
const auto info = Inventory::FindItemComponent(lot); const auto info = Inventory::FindItemComponent(lot);
auto left = count; auto left = count;
int32_t outOfSpace = 0; int32_t outOfSpace = 0;
auto stack = static_cast<uint32_t>(info.stackSize); 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; stack = 999;
} }
@ -215,7 +219,7 @@ void InventoryComponent::AddItem(
{ {
stack = 1; stack = 1;
} }
auto* existing = FindItemByLot(lot, inventoryType); auto* existing = FindItemByLot(lot, inventoryType);
if (existing != nullptr) if (existing != nullptr)
@ -239,7 +243,7 @@ void InventoryComponent::AddItem(
const auto size = std::min(left, stack); const auto size = std::min(left, stack);
left -= size; left -= size;
int32_t slot; int32_t slot;
if (preferredSlot != -1 && inventory->IsSlotEmpty(preferredSlot)) if (preferredSlot != -1 && inventory->IsSlotEmpty(preferredSlot))
@ -252,7 +256,7 @@ void InventoryComponent::AddItem(
{ {
slot = inventory->FindEmptySlot(); slot = inventory->FindEmptySlot();
} }
if (slot == -1) if (slot == -1)
{ {
auto* player = dynamic_cast<Player*>(GetParent()); auto* player = dynamic_cast<Player*>(GetParent());
@ -275,9 +279,9 @@ void InventoryComponent::AddItem(
{ {
GameMessages::SendDropClientLoot(this->m_Parent, this->m_Parent->GetObjectID(), lot, 0, this->m_Parent->GetPosition(), 1); GameMessages::SendDropClientLoot(this->m_Parent, this->m_Parent->GetObjectID(), lot, 0, this->m_Parent->GetPosition(), 1);
} }
break; break;
default: default:
break; break;
} }
@ -326,7 +330,7 @@ void InventoryComponent::RemoveItem(const LOT lot, const uint32_t count, eInvent
{ {
break; break;
} }
const auto delta = std::min<uint32_t>(left, item->GetCount()); const auto delta = std::min<uint32_t>(left, item->GetCount());
item->SetCount(item->GetCount() - delta); item->SetCount(item->GetCount() - delta);
@ -341,19 +345,17 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in
{ {
return; return;
} }
auto* origin = item->GetInventory(); auto* origin = item->GetInventory();
const auto lot = item->GetLot(); const auto lot = item->GetLot();
if (item->GetConfig().empty() && !item->GetBound() || (item->GetBound() && item->GetInfo().isBOP)) if (item->GetConfig().empty() && !item->GetBound() || (item->GetBound() && item->GetInfo().isBOP))
{ {
auto left = std::min<uint32_t>(count, origin->GetLotCount(lot)); auto left = std::min<uint32_t>(count, origin->GetLotCount(lot));
while (left > 0) while (left > 0)
{ {
item = origin->FindItemByLot(lot, ignoreEquipped);
if (item == nullptr) if (item == nullptr)
{ {
item = origin->FindItemByLot(lot, false); item = origin->FindItemByLot(lot, false);
@ -383,7 +385,7 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in
{ {
config.push_back(data->Copy()); config.push_back(data->Copy());
} }
const auto delta = std::min<uint32_t>(item->GetCount(), count); const auto delta = std::min<uint32_t>(item->GetCount(), count);
AddItem(lot, delta, eLootSourceType::LOOT_SOURCE_NONE, inventory, config, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, LWOOBJID_EMPTY, origin->GetType(), 0, item->GetBound(), preferredSlot); AddItem(lot, delta, eLootSourceType::LOOT_SOURCE_NONE, inventory, config, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, LWOOBJID_EMPTY, origin->GetType(), 0, item->GetBound(), preferredSlot);
@ -441,7 +443,7 @@ Item* InventoryComponent::FindItemByLot(const LOT lot, eInventoryType inventoryT
return inventory->FindItemByLot(lot, ignoreEquipped, ignoreBound); return inventory->FindItemByLot(lot, ignoreEquipped, ignoreBound);
} }
Item* InventoryComponent::FindItemBySubKey(LWOOBJID id, eInventoryType inventoryType) Item* InventoryComponent::FindItemBySubKey(LWOOBJID id, eInventoryType inventoryType)
{ {
if (inventoryType == INVALID) if (inventoryType == INVALID)
{ {
@ -600,7 +602,7 @@ void InventoryComponent::LoadXml(tinyxml2::XMLDocument* document)
unsigned int count; unsigned int count;
bool bound; bool bound;
LWOOBJID subKey = LWOOBJID_EMPTY; LWOOBJID subKey = LWOOBJID_EMPTY;
itemElement->QueryAttribute("id", &id); itemElement->QueryAttribute("id", &id);
itemElement->QueryAttribute("l", &lot); itemElement->QueryAttribute("l", &lot);
itemElement->QueryAttribute("eq", &equipped); itemElement->QueryAttribute("eq", &equipped);
@ -611,23 +613,23 @@ void InventoryComponent::LoadXml(tinyxml2::XMLDocument* document)
// Begin custom xml // Begin custom xml
auto parent = LWOOBJID_EMPTY; auto parent = LWOOBJID_EMPTY;
itemElement->QueryAttribute("parent", &parent); itemElement->QueryAttribute("parent", &parent);
// End custom xml // End custom xml
std::vector<LDFBaseData*> config; std::vector<LDFBaseData*> config;
auto* extraInfo = itemElement->FirstChildElement("x"); auto* extraInfo = itemElement->FirstChildElement("x");
if (extraInfo) if (extraInfo)
{ {
std::string modInfo = extraInfo->Attribute("ma"); std::string modInfo = extraInfo->Attribute("ma");
LDFBaseData* moduleAssembly = new LDFData<std::u16string>(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(modInfo.substr(2, modInfo.size() - 1))); LDFBaseData* moduleAssembly = new LDFData<std::u16string>(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(modInfo.substr(2, modInfo.size() - 1)));
config.push_back(moduleAssembly); config.push_back(moduleAssembly);
} }
const auto* item = new Item(id, lot, inventory, slot, count, bound, config, parent, subKey); const auto* item = new Item(id, lot, inventory, slot, count, bound, config, parent, subKey);
if (equipped) if (equipped)
@ -705,7 +707,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document)
bags->LinkEndChild(bag); bags->LinkEndChild(bag);
} }
auto* items = inventoryElement->FirstChildElement("items"); auto* items = inventoryElement->FirstChildElement("items");
if (items == nullptr) if (items == nullptr)
@ -759,10 +761,10 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document)
itemElement->LinkEndChild(extraInfo); itemElement->LinkEndChild(extraInfo);
} }
bagElement->LinkEndChild(itemElement); bagElement->LinkEndChild(itemElement);
} }
items->LinkEndChild(bagElement); items->LinkEndChild(bagElement);
} }
} }
@ -772,13 +774,13 @@ void InventoryComponent::Serialize(RakNet::BitStream* outBitStream, const bool b
if (bIsInitialUpdate || m_Dirty) if (bIsInitialUpdate || m_Dirty)
{ {
outBitStream->Write(true); outBitStream->Write(true);
outBitStream->Write<uint32_t>(m_Equipped.size()); outBitStream->Write<uint32_t>(m_Equipped.size());
for (const auto& pair : m_Equipped) for (const auto& pair : m_Equipped)
{ {
const auto item = pair.second; const auto item = pair.second;
if (bIsInitialUpdate) if (bIsInitialUpdate)
{ {
AddItemSkills(item.lot); AddItemSkills(item.lot);
@ -786,12 +788,12 @@ void InventoryComponent::Serialize(RakNet::BitStream* outBitStream, const bool b
outBitStream->Write(item.id); outBitStream->Write(item.id);
outBitStream->Write(item.lot); outBitStream->Write(item.lot);
outBitStream->Write0(); outBitStream->Write0();
outBitStream->Write(item.count > 0); outBitStream->Write(item.count > 0);
if (item.count > 0) outBitStream->Write(item.count); if (item.count > 0) outBitStream->Write(item.count);
outBitStream->Write(item.slot != 0); outBitStream->Write(item.slot != 0);
if (item.slot != 0) outBitStream->Write<uint16_t>(item.slot); if (item.slot != 0) outBitStream->Write<uint16_t>(item.slot);
@ -818,7 +820,7 @@ void InventoryComponent::Serialize(RakNet::BitStream* outBitStream, const bool b
outBitStream->Write(ldfStream); outBitStream->Write(ldfStream);
} }
outBitStream->Write1(); outBitStream->Write1();
} }
m_Dirty = false; m_Dirty = false;
@ -827,7 +829,7 @@ void InventoryComponent::Serialize(RakNet::BitStream* outBitStream, const bool b
{ {
outBitStream->Write(false); outBitStream->Write(false);
} }
outBitStream->Write(false); outBitStream->Write(false);
} }
@ -836,7 +838,7 @@ void InventoryComponent::ResetFlags()
m_Dirty = false; m_Dirty = false;
} }
void InventoryComponent::Update(float deltaTime) void InventoryComponent::Update(float deltaTime)
{ {
for (auto* set : m_Itemsets) for (auto* set : m_Itemsets)
{ {
@ -865,7 +867,7 @@ void InventoryComponent::UpdateSlot(const std::string& location, EquippedItem it
UnEquipItem(old); UnEquipItem(old);
} }
} }
m_Equipped.insert_or_assign(location, item); m_Equipped.insert_or_assign(location, item);
m_Dirty = true; m_Dirty = true;
@ -877,14 +879,14 @@ void InventoryComponent::RemoveSlot(const std::string& location)
{ {
return; return;
} }
m_Equipped.erase(location); m_Equipped.erase(location);
m_Dirty = true; m_Dirty = true;
} }
void InventoryComponent::EquipItem(Item* item, const bool skipChecks) void InventoryComponent::EquipItem(Item* item, const bool skipChecks)
{ {
if (!Inventory::IsValidItem(item->GetLot())) if (!Inventory::IsValidItem(item->GetLot()))
{ {
return; return;
@ -985,19 +987,11 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks)
// #107 // #107
auto* possessorComponent = m_Parent->GetComponent<PossessorComponent>(); auto* possessorComponent = m_Parent->GetComponent<PossessorComponent>();
if (possessorComponent != nullptr) if (possessorComponent) possessorComponent->SetPossessable(carEntity->GetObjectID());
{
previousPossessorID = possessorComponent->GetPossessable();
possessorComponent->SetPossessable(carEntity->GetObjectID());
}
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>(); auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
if (characterComponent != nullptr) if (characterComponent) characterComponent->SetIsRacing(true);
{
characterComponent->SetIsRacing(true);
characterComponent->SetVehicleObjectID(carEntity->GetObjectID());
}
EntityManager::Instance()->ConstructEntity(carEntity); EntityManager::Instance()->ConstructEntity(carEntity);
EntityManager::Instance()->SerializeEntity(m_Parent); EntityManager::Instance()->SerializeEntity(m_Parent);
@ -1030,14 +1024,14 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks)
{ {
return; return;
} }
if (type == ITEM_TYPE_LOOT_MODEL || type == ITEM_TYPE_VEHICLE) if (type == eItemType::ITEM_TYPE_LOOT_MODEL || type == eItemType::ITEM_TYPE_VEHICLE)
{ {
return; 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)) if (!item->GetBound() && !item->GetPreconditionExpression()->Check(m_Parent))
{ {
@ -1049,7 +1043,7 @@ void InventoryComponent::EquipItem(Item* item, const bool skipChecks)
const auto lot = item->GetLot(); const auto lot = item->GetLot();
CheckItemSet(lot); CheckItemSet(lot);
for (auto* set : m_Itemsets) for (auto* set : m_Itemsets)
{ {
set->OnEquip(lot); set->OnEquip(lot);
@ -1079,7 +1073,7 @@ void InventoryComponent::UnEquipItem(Item* item)
} }
const auto lot = item->GetLot(); const auto lot = item->GetLot();
if (!Inventory::IsValidItem(lot)) if (!Inventory::IsValidItem(lot))
{ {
return; return;
@ -1097,7 +1091,7 @@ void InventoryComponent::UnEquipItem(Item* item)
RemoveItemSkills(item->GetLot()); RemoveItemSkills(item->GetLot());
RemoveSlot(item->GetInfo().equipLocation); RemoveSlot(item->GetInfo().equipLocation);
PurgeProxies(item); PurgeProxies(item);
EntityManager::Instance()->SerializeEntity(m_Parent); EntityManager::Instance()->SerializeEntity(m_Parent);
@ -1115,7 +1109,7 @@ void InventoryComponent::ApplyBuff(Item* item) const
const auto buffs = FindBuffs(item, true); const auto buffs = FindBuffs(item, true);
for (const auto buff : buffs) for (const auto buff : buffs)
{ {
SkillComponent::HandleUnmanaged(buff, m_Parent->GetObjectID()); SkillComponent::HandleUnmanaged(buff, m_Parent->GetObjectID());
} }
} }
@ -1165,6 +1159,18 @@ void InventoryComponent::PopEquippedItems()
item->Equip(); 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; m_Dirty = true;
} }
@ -1182,22 +1188,21 @@ bool InventoryComponent::IsEquipped(const LOT lot) const
return false; return false;
} }
void InventoryComponent::CheckItemSet(const LOT lot) void InventoryComponent::CheckItemSet(const LOT lot) {
{
// Check if the lot is in the item set cache // 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; 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); const auto id = result.getIntField(0);
bool found = false; bool found = false;
@ -1223,11 +1228,11 @@ void InventoryComponent::CheckItemSet(const LOT lot)
} }
m_ItemSetsChecked.push_back(lot); m_ItemSetsChecked.push_back(lot);
result.finalize(); result.finalize();
} }
void InventoryComponent::SetConsumable(LOT lot) void InventoryComponent::SetConsumable(LOT lot)
{ {
m_Consumable = lot; m_Consumable = lot;
} }
@ -1247,7 +1252,7 @@ void InventoryComponent::AddItemSkills(const LOT lot)
{ {
return; return;
} }
const auto index = m_Skills.find(slot); const auto index = m_Skills.find(slot);
const auto skill = FindSkill(lot); const auto skill = FindSkill(lot);
@ -1272,14 +1277,14 @@ void InventoryComponent::AddItemSkills(const LOT lot)
void InventoryComponent::RemoveItemSkills(const LOT lot) void InventoryComponent::RemoveItemSkills(const LOT lot)
{ {
const auto info = Inventory::FindItemComponent(lot); const auto info = Inventory::FindItemComponent(lot);
const auto slot = FindBehaviorSlot(static_cast<eItemType>(info.itemType)); const auto slot = FindBehaviorSlot(static_cast<eItemType>(info.itemType));
if (slot == BehaviorSlot::Invalid) if (slot == BehaviorSlot::Invalid)
{ {
return; return;
} }
const auto index = m_Skills.find(slot); const auto index = m_Skills.find(slot);
if (index == m_Skills.end()) if (index == m_Skills.end())
@ -1292,7 +1297,7 @@ void InventoryComponent::RemoveItemSkills(const LOT lot)
GameMessages::SendRemoveSkill(m_Parent, old); GameMessages::SendRemoveSkill(m_Parent, old);
m_Skills.erase(slot); m_Skills.erase(slot);
if (slot == BehaviorSlot::Primary) if (slot == BehaviorSlot::Primary)
{ {
m_Skills.insert_or_assign(BehaviorSlot::Primary, 1); m_Skills.insert_or_assign(BehaviorSlot::Primary, 1);
@ -1301,7 +1306,7 @@ void InventoryComponent::RemoveItemSkills(const LOT lot)
} }
} }
void InventoryComponent::TriggerPassiveAbility(PassiveAbilityTrigger trigger) void InventoryComponent::TriggerPassiveAbility(PassiveAbilityTrigger trigger)
{ {
for (auto* set : m_Itemsets) for (auto* set : m_Itemsets)
{ {
@ -1328,7 +1333,7 @@ bool InventoryComponent::HasAnyPassive(const std::vector<ItemSetPassiveAbilityID
return false; return false;
} }
void InventoryComponent::DespawnPet() void InventoryComponent::DespawnPet()
{ {
auto* current = PetComponent::GetActivePet(m_Parent->GetObjectID()); auto* current = PetComponent::GetActivePet(m_Parent->GetObjectID());
@ -1338,7 +1343,7 @@ void InventoryComponent::DespawnPet()
} }
} }
void InventoryComponent::SpawnPet(Item* item) void InventoryComponent::SpawnPet(Item* item)
{ {
auto* current = PetComponent::GetActivePet(m_Parent->GetObjectID()); auto* current = PetComponent::GetActivePet(m_Parent->GetObjectID());
@ -1352,16 +1357,24 @@ 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 {}; EntityInfo info {};
info.lot = item->GetLot(); info.lot = item->GetLot();
info.pos = m_Parent->GetPosition(); info.pos = m_Parent->GetPosition();
info.rot = NiQuaternion::IDENTITY; info.rot = NiQuaternion::IDENTITY;
info.spawnerID = m_Parent->GetObjectID(); info.spawnerID = m_Parent->GetObjectID();
auto* pet = EntityManager::Instance()->CreateEntity(info); auto* pet = EntityManager::Instance()->CreateEntity(info);
auto* petComponent = pet->GetComponent<PetComponent>(); auto* petComponent = pet->GetComponent<PetComponent>();
if (petComponent != nullptr) if (petComponent != nullptr)
{ {
petComponent->Activate(item); petComponent->Activate(item);
@ -1370,7 +1383,7 @@ void InventoryComponent::SpawnPet(Item* item)
EntityManager::Instance()->ConstructEntity(pet); EntityManager::Instance()->ConstructEntity(pet);
} }
void InventoryComponent::SetDatabasePet(LWOOBJID id, const DatabasePet& data) void InventoryComponent::SetDatabasePet(LWOOBJID id, const DatabasePet& data)
{ {
m_Pets.insert_or_assign(id, data); m_Pets.insert_or_assign(id, data);
} }
@ -1391,7 +1404,7 @@ bool InventoryComponent::IsPet(LWOOBJID id) const
return pair != m_Pets.end(); return pair != m_Pets.end();
} }
void InventoryComponent::RemoveDatabasePet(LWOOBJID id) void InventoryComponent::RemoveDatabasePet(LWOOBJID id)
{ {
m_Pets.erase(id); m_Pets.erase(id);
} }
@ -1399,22 +1412,22 @@ void InventoryComponent::RemoveDatabasePet(LWOOBJID id)
BehaviorSlot InventoryComponent::FindBehaviorSlot(const eItemType type) BehaviorSlot InventoryComponent::FindBehaviorSlot(const eItemType type)
{ {
switch (type) { switch (type) {
case ITEM_TYPE_HAT: case eItemType::ITEM_TYPE_HAT:
return BehaviorSlot::Head; return BehaviorSlot::Head;
case ITEM_TYPE_NECK: case eItemType::ITEM_TYPE_NECK:
return BehaviorSlot::Neck; return BehaviorSlot::Neck;
case ITEM_TYPE_LEFT_HAND: case eItemType::ITEM_TYPE_LEFT_HAND:
return BehaviorSlot::Offhand; return BehaviorSlot::Offhand;
case ITEM_TYPE_RIGHT_HAND: case eItemType::ITEM_TYPE_RIGHT_HAND:
return BehaviorSlot::Primary; return BehaviorSlot::Primary;
case ITEM_TYPE_CONSUMABLE: case eItemType::ITEM_TYPE_CONSUMABLE:
return BehaviorSlot::Consumable; return BehaviorSlot::Consumable;
default: default:
return BehaviorSlot::Invalid; return BehaviorSlot::Invalid;
} }
} }
bool InventoryComponent::IsTransferInventory(eInventoryType type) bool InventoryComponent::IsTransferInventory(eInventoryType type)
{ {
return type == VENDOR_BUYBACK || type == VAULT_ITEMS || type == VAULT_MODELS || type == TEMP_ITEMS || type == TEMP_MODELS; return type == VENDOR_BUYBACK || type == VAULT_ITEMS || type == VAULT_MODELS || type == TEMP_ITEMS || type == TEMP_MODELS;
} }
@ -1465,11 +1478,12 @@ std::vector<uint32_t> InventoryComponent::FindBuffs(Item* item, bool castOnEquip
continue; continue;
} }
if (missions != nullptr && castOnEquip) if (missions != nullptr && castOnEquip)
{ {
missions->Progress(MissionTaskType::MISSION_TASK_TYPE_SKILL, result.skillID); missions->Progress(MissionTaskType::MISSION_TASK_TYPE_SKILL, result.skillID);
} }
// If item is not a proxy, add its buff to the added buffs. // 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)); if (item->GetParent() == LWOOBJID_EMPTY) buffs.push_back(static_cast<uint32_t>(entry.behaviorID));
} }
@ -1478,18 +1492,18 @@ std::vector<uint32_t> InventoryComponent::FindBuffs(Item* item, bool castOnEquip
return buffs; return buffs;
} }
void InventoryComponent::SetNPCItems(const std::vector<LOT>& items) void InventoryComponent::SetNPCItems(const std::vector<LOT>& items)
{ {
m_Equipped.clear(); m_Equipped.clear();
auto slot = 0u; auto slot = 0u;
for (const auto& item : items) for (const auto& item : items)
{ {
const LWOOBJID id = ObjectIDManager::Instance()->GenerateObjectID(); const LWOOBJID id = ObjectIDManager::Instance()->GenerateObjectID();
const auto& info = Inventory::FindItemComponent(item); const auto& info = Inventory::FindItemComponent(item);
UpdateSlot(info.equipLocation, { id, static_cast<LOT>(item), 1, slot++ }, true); UpdateSlot(info.equipLocation, { id, static_cast<LOT>(item), 1, slot++ }, true);
} }
@ -1524,9 +1538,9 @@ std::vector<Item*> InventoryComponent::GenerateProxies(Item* parent)
{ {
return proxies; return proxies;
} }
subItems.erase(std::remove_if(subItems.begin(), subItems.end(), ::isspace), subItems.end()); subItems.erase(std::remove_if(subItems.begin(), subItems.end(), ::isspace), subItems.end());
std::stringstream stream(subItems); std::stringstream stream(subItems);
std::string segment; std::string segment;
std::vector<uint32_t> lots; std::vector<uint32_t> lots;
@ -1541,7 +1555,7 @@ std::vector<Item*> InventoryComponent::GenerateProxies(Item* parent)
{ {
Game::logger->Log("InventoryComponent", "Failed to parse proxy (%s): (%s)!\n", segment.c_str(), exception.what()); Game::logger->Log("InventoryComponent", "Failed to parse proxy (%s): (%s)!\n", segment.c_str(), exception.what());
} }
} }
for (const auto lot : lots) for (const auto lot : lots)
{ {
@ -1558,7 +1572,7 @@ std::vector<Item*> InventoryComponent::GenerateProxies(Item* parent)
proxies.push_back(proxy); proxies.push_back(proxy);
} }
return proxies; return proxies;
} }
@ -1567,7 +1581,7 @@ std::vector<Item*> InventoryComponent::FindProxies(const LWOOBJID parent)
auto* inventory = GetInventory(ITEM_SETS); auto* inventory = GetInventory(ITEM_SETS);
std::vector<Item*> proxies; std::vector<Item*> proxies;
for (const auto& pair : inventory->GetItems()) for (const auto& pair : inventory->GetItems())
{ {
auto* item = pair.second; auto* item = pair.second;
@ -1609,7 +1623,7 @@ bool InventoryComponent::IsParentValid(Item* root)
} }
const auto id = root->GetId(); const auto id = root->GetId();
for (const auto& pair : m_Inventories) for (const auto& pair : m_Inventories)
{ {
const auto items = pair.second->GetItems(); const auto items = pair.second->GetItems();
@ -1631,7 +1645,7 @@ bool InventoryComponent::IsParentValid(Item* root)
void InventoryComponent::CheckProxyIntegrity() void InventoryComponent::CheckProxyIntegrity()
{ {
std::vector<Item*> dead; std::vector<Item*> dead;
for (const auto& pair : m_Inventories) for (const auto& pair : m_Inventories)
{ {
const auto& items = pair.second->GetItems(); const auto& items = pair.second->GetItems();
@ -1646,7 +1660,7 @@ void InventoryComponent::CheckProxyIntegrity()
{ {
continue; continue;
} }
if (IsValidProxy(parent)) if (IsValidProxy(parent))
{ {
continue; continue;
@ -1671,7 +1685,7 @@ void InventoryComponent::CheckProxyIntegrity()
for (const auto& candidate : items) for (const auto& candidate : items)
{ {
auto* item = candidate.second; auto* item = candidate.second;
const auto parent = item->GetParent(); const auto parent = item->GetParent();
if (parent != LWOOBJID_EMPTY) if (parent != LWOOBJID_EMPTY)
@ -1703,7 +1717,7 @@ void InventoryComponent::CheckProxyIntegrity()
void InventoryComponent::PurgeProxies(Item* item) void InventoryComponent::PurgeProxies(Item* item)
{ {
const auto root = item->GetParent(); const auto root = item->GetParent();
if (root != LWOOBJID_EMPTY) if (root != LWOOBJID_EMPTY)
{ {
item = FindItemById(root); item = FindItemById(root);
@ -1712,7 +1726,7 @@ void InventoryComponent::PurgeProxies(Item* item)
{ {
UnEquipItem(item); UnEquipItem(item);
} }
return; return;
} }
@ -1726,7 +1740,7 @@ void InventoryComponent::PurgeProxies(Item* item)
} }
} }
void InventoryComponent::LoadPetXml(tinyxml2::XMLDocument* document) void InventoryComponent::LoadPetXml(tinyxml2::XMLDocument* document)
{ {
auto* petInventoryElement = document->FirstChildElement("obj")->FirstChildElement("pet"); auto* petInventoryElement = document->FirstChildElement("obj")->FirstChildElement("pet");
@ -1744,7 +1758,7 @@ void InventoryComponent::LoadPetXml(tinyxml2::XMLDocument* document)
LWOOBJID id; LWOOBJID id;
LOT lot; LOT lot;
int32_t moderationStatus; int32_t moderationStatus;
petElement->QueryAttribute("id", &id); petElement->QueryAttribute("id", &id);
petElement->QueryAttribute("l", &lot); petElement->QueryAttribute("l", &lot);
petElement->QueryAttribute("m", &moderationStatus); petElement->QueryAttribute("m", &moderationStatus);
@ -1761,7 +1775,7 @@ void InventoryComponent::LoadPetXml(tinyxml2::XMLDocument* document)
} }
} }
void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument* document) void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument* document)
{ {
auto* petInventoryElement = document->FirstChildElement("obj")->FirstChildElement("pet"); auto* petInventoryElement = document->FirstChildElement("obj")->FirstChildElement("pet");
@ -1783,8 +1797,7 @@ void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument* document)
petElement->SetAttribute("m", pet.second.moderationState); petElement->SetAttribute("m", pet.second.moderationState);
petElement->SetAttribute("n", pet.second.name.c_str()); petElement->SetAttribute("n", pet.second.name.c_str());
petElement->SetAttribute("t", 0); petElement->SetAttribute("t", 0);
petInventoryElement->LinkEndChild(petElement); petInventoryElement->LinkEndChild(petElement);
} }
} }

View File

@ -1,4 +1,7 @@
#pragma once #pragma once
#ifndef INVENTORYCOMPONENT_H
#define INVENTORYCOMPONENT_H
#include <map> #include <map>
#include <stack> #include <stack>
@ -22,6 +25,8 @@ class ItemSet;
typedef std::map<std::string, EquippedItem> EquipmentMap; 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 * 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 * 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); 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) { 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 = query.execQuery();
auto result = CDClientDatabase::ExecuteQuery(query.str());
if (result.eof()) { if (result.eof()) {
return false; return false;

View File

@ -16,6 +16,7 @@
#include "../dWorldServer/ObjectIDManager.h" #include "../dWorldServer/ObjectIDManager.h"
#include "Game.h" #include "Game.h"
#include "dConfig.h"
#include "dChatFilter.h" #include "dChatFilter.h"
#include "Database.h" #include "Database.h"
@ -81,6 +82,20 @@ PetComponent::PetComponent(Entity* parent, uint32_t componentId) : Component(par
if (!checkPreconditions.empty()) { if (!checkPreconditions.empty()) {
SetPreconditions(checkPreconditions); 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) void PetComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags)
@ -172,13 +187,12 @@ void PetComponent::OnUse(Entity* originator)
std::string buildFile; std::string buildFile;
if (cached == buildCache.end()) if (cached == buildCache.end()) {
{ auto query = CDClientDatabase::CreatePreppedStmt(
std::stringstream query; "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 = query.execQuery();
auto result = CDClientDatabase::ExecuteQuery(query.str());
if (result.eof()) if (result.eof())
{ {
@ -637,7 +651,7 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position)
inventoryComponent->SetDatabasePet(petSubKey, databasePet); inventoryComponent->SetDatabasePet(petSubKey, databasePet);
Activate(item, false); Activate(item, false, true);
m_Timer = 0; m_Timer = 0;
@ -898,8 +912,10 @@ void PetComponent::Wander()
m_Timer += (m_MovementAI->GetCurrentPosition().x - destination.x) / info.wanderSpeed; 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_ItemId = item->GetId();
m_DatabaseId = item->GetSubKey(); m_DatabaseId = item->GetSubKey();
@ -969,6 +985,44 @@ void PetComponent::Activate(Item* item, bool registerPet)
GameMessages::SendShowPetActionButton(m_Owner, 3, true, owner->GetSystemAddress()); 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() void PetComponent::Deactivate()
{ {
GameMessages::SendPlayFXEffect(m_Parent->GetObjectID(), -1, u"despawn", "", LWOOBJID_EMPTY, 1, 1, true); 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 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 * @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 * Despawns the pet
@ -203,6 +203,14 @@ public:
*/ */
static PetComponent* GetActivePet(LWOOBJID owner); 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: private:
/** /**
@ -346,4 +354,9 @@ private:
* Preconditions that need to be met before an entity can tame this pet * Preconditions that need to be met before an entity can tame this pet
*/ */
PreconditionExpression* m_Preconditions; 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 "PossessableComponent.h"
#include "PossessorComponent.h" #include "PossessorComponent.h"
#include "EntityManager.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;
m_Possessor = LWOOBJID_EMPTY; CDItemComponent item = Inventory::FindItemComponent(m_Parent->GetLOT());
m_AnimationFlag = static_cast<eAnimationFlags>(item.animationFlag);
// 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));
auto result = query.execQuery();
// 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();
} }
PossessableComponent::~PossessableComponent() 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);
void PossessableComponent::SetPossessor(LWOOBJID value) outBitStream->Write(m_AnimationFlag != eAnimationFlags::IDLE_INVALID);
{ if(m_AnimationFlag != eAnimationFlags::IDLE_INVALID) outBitStream->Write(m_AnimationFlag);
m_Possessor = value;
}
LWOOBJID PossessableComponent::GetPossessor() const outBitStream->Write(m_ImmediatelyDepossess);
{ }
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();
}
}
void PossessableComponent::Update(float deltaTime)
{
} }
void PossessableComponent::OnUse(Entity* originator) { void PossessableComponent::OnUse(Entity* originator) {
PossessorComponent* possessorComponent; // TODO: Implement this
if (originator->TryGetComponent(COMPONENT_TYPE_POSSESSOR, possessorComponent)) { }
SetPossessor(originator->GetObjectID());
possessorComponent->SetPossessable(m_Parent->GetObjectID());
EntityManager::Instance()->SerializeEntity(m_Parent);
EntityManager::Instance()->SerializeEntity(originator);
}
}

View File

@ -3,44 +3,113 @@
#include "BitStream.h" #include "BitStream.h"
#include "Entity.h" #include "Entity.h"
#include "Component.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 * Represents an entity that can be controlled by some other entity, generally used by cars to indicate that some
* player is controlling it. * player is controlling it.
*/ */
class PossessableComponent : public Component { class PossessableComponent : public Component {
public: public:
static const uint32_t ComponentType = COMPONENT_TYPE_POSSESSABLE; static const uint32_t ComponentType = COMPONENT_TYPE_POSSESSABLE;
PossessableComponent(Entity* parentEntity);
~PossessableComponent() override;
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); PossessableComponent(Entity* parentEntity, uint32_t componentId);
void Update(float deltaTime) override;
/** void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags);
* Sets the possessor of this entity
* @param value the ID of the possessor to set
*/
void SetPossessor(LWOOBJID value);
/** /**
* Returns the possessor of this entity * Sets the possessor of this entity
* @return the possessor of this entitythe * @param value the ID of the possessor to set
*/ */
LWOOBJID GetPossessor() const; void SetPossessor(LWOOBJID value) { m_Possessor = value; m_DirtyPossessable = true;};
/** /**
* Handles an OnUsed event by some other entity, if said entity has a PossessorComponent it becomes the possessor * Returns the possessor of this entity
* of this entity * @return the possessor of this entity
* @param originator the entity that caused the event to trigger */
*/ LWOOBJID GetPossessor() const { return m_Possessor; };
void OnUse(Entity* originator) override;
private: /**
* 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;};
/** /**
* The possessor of this entity, e.g. the entity that controls this entity * Returns the possession type of this entity
*/ * @return the possession type of this entity
LWOOBJID m_Possessor; */
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:
/**
* @brief Whether the possessor is dirty
*/
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" #include "PossessorComponent.h"
PossessorComponent::PossessorComponent(Entity* parent) : Component(parent) PossessorComponent::PossessorComponent(Entity* parent) : Component(parent) {
{ m_Possessable = LWOOBJID_EMPTY;
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);
outBitStream->Write(m_Possessable != LWOOBJID_EMPTY); if (m_DirtyPossesor || bIsInitialUpdate) {
if (m_Possessable != LWOOBJID_EMPTY) m_DirtyPossesor = false;
{ outBitStream->Write(m_Possessable != LWOOBJID_EMPTY);
outBitStream->Write(m_Possessable); if (m_Possessable != LWOOBJID_EMPTY) {
} outBitStream->Write(m_Possessable);
} }
outBitStream->Write(m_PossessableType);
void PossessorComponent::Update(float deltaTime) }
{
} }

View File

@ -4,35 +4,78 @@
#include "Entity.h" #include "Entity.h"
#include "Component.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. * Represents an entity that can posess other entities. Generally used by players to drive a car.
*/ */
class PossessorComponent : public Component { class PossessorComponent : public Component {
public: public:
static const uint32_t ComponentType = COMPONENT_TYPE_POSSESSOR; static const uint32_t ComponentType = COMPONENT_TYPE_POSSESSOR;
PossessorComponent(Entity* parent);
~PossessorComponent() override;
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); PossessorComponent(Entity* parent);
void Update(float deltaTime) override; ~PossessorComponent() override;
/** void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags);
* Sets the entity that this entity is possessing
* @param value the ID of the entity this ID should posess
*/
void SetPossessable(LWOOBJID value);
/** /**
* Returns the entity that this entity is currently posessing * Sets the entity that this entity is possessing
* @return the entity that this entity is currently posessing * @param value the ID of the entity this ID should posess
*/ */
LWOOBJID GetPossessable() const; void SetPossessable(LWOOBJID value) { m_Possessable = value; m_DirtyPossesor = true; }
private: /**
* Returns the entity that this entity is currently posessing
* @return the entity that this entity is currently posessing
*/
LWOOBJID GetPossessable() const { return m_Possessable; }
/** /**
* The ID of the entity this entity is possessing (e.g. the ID of a car) * Sets if we are busy mounting or dismounting
*/ * @param value if we are busy mounting or dismounting
LWOOBJID m_Possessable; */
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_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; 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; std::string base;
if (customQuery == "") { if (customQuery == "") {
base = baseQueryForProperties; 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;"); 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->setUInt(1, character->GetID());
friendsListQuery->setInt64(2, entity->GetObjectID()); friendsListQuery->setUInt(2, character->GetID());
auto friendsListQueryResult = friendsListQuery->executeQuery(); auto friendsListQueryResult = friendsListQuery->executeQuery();
while (friendsListQueryResult->next()) { while (friendsListQueryResult->next()) {
auto playerIDToConvert = friendsListQueryResult->getInt64(1); auto playerIDToConvert = friendsListQueryResult->getInt(1);
playerIDToConvert = GeneralUtils::ClearBit(playerIDToConvert, OBJECT_BIT_CHARACTER);
playerIDToConvert = GeneralUtils::ClearBit(playerIDToConvert, OBJECT_BIT_PERSISTENT);
friendsList = friendsList + std::to_string(playerIDToConvert) + ","; friendsList = friendsList + std::to_string(playerIDToConvert) + ",";
} }
// Replace trailing comma with the closing parenthesis. // 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 the player has a property this query will have a single result.
if (playerPropertyLookupResults->next()) { if (playerPropertyLookupResults->next()) {
const auto cloneId = playerPropertyLookupResults->getUInt64(4); const auto cloneId = playerPropertyLookupResults->getUInt64(4);
const auto propertyName = playerPropertyLookupResults->getString(5).asStdString(); const auto propertyName = std::string(playerPropertyLookupResults->getString(5).c_str());
const auto propertyDescription = playerPropertyLookupResults->getString(6).asStdString(); const auto propertyDescription = std::string(playerPropertyLookupResults->getString(6).c_str());
const auto privacyOption = playerPropertyLookupResults->getInt(9); const auto privacyOption = playerPropertyLookupResults->getInt(9);
const auto modApproved = playerPropertyLookupResults->getBoolean(10); const auto modApproved = playerPropertyLookupResults->getBoolean(10);
const auto dateLastUpdated = playerPropertyLookupResults->getInt64(11); const auto dateLastUpdated = playerPropertyLookupResults->getInt64(11);
@ -193,7 +191,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
entries.push_back(playerEntry); entries.push_back(playerEntry);
const auto query = BuildQuery(entity, sortMethod); const auto query = BuildQuery(entity, sortMethod, character);
auto propertyLookup = Database::CreatePreppedStmt(query); auto propertyLookup = Database::CreatePreppedStmt(query);
@ -212,8 +210,8 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
const auto propertyId = propertyEntry->getUInt64(1); const auto propertyId = propertyEntry->getUInt64(1);
const auto owner = propertyEntry->getInt(2); const auto owner = propertyEntry->getInt(2);
const auto cloneId = propertyEntry->getUInt64(4); const auto cloneId = propertyEntry->getUInt64(4);
const auto propertyNameFromDb = propertyEntry->getString(5).asStdString(); const auto propertyNameFromDb = std::string(propertyEntry->getString(5).c_str());
const auto propertyDescriptionFromDb = propertyEntry->getString(6).asStdString(); const auto propertyDescriptionFromDb = std::string(propertyEntry->getString(6).c_str());
const auto privacyOption = propertyEntry->getInt(9); const auto privacyOption = propertyEntry->getInt(9);
const auto modApproved = propertyEntry->getBoolean(10); const auto modApproved = propertyEntry->getBoolean(10);
const auto dateLastUpdated = propertyEntry->getInt(11); const auto dateLastUpdated = propertyEntry->getInt(11);
@ -239,7 +237,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
continue; continue;
} else { } else {
isOwned = cloneId == character->GetPropertyCloneID(); isOwned = cloneId == character->GetPropertyCloneID();
ownerName = nameResult->getString(1).asStdString(); ownerName = std::string(nameResult->getString(1).c_str());
} }
delete nameResult; delete nameResult;
@ -262,17 +260,17 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
// Query to get friend and best friend fields // 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 = ?)"); 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->setUInt(1, character->GetID());
friendCheck->setInt64(2, ownerObjId); friendCheck->setUInt(2, ownerObjId);
friendCheck->setInt64(3, ownerObjId); friendCheck->setUInt(3, ownerObjId);
friendCheck->setInt64(4, entity->GetObjectID()); friendCheck->setUInt(4, character->GetID());
auto friendResult = friendCheck->executeQuery(); auto friendResult = friendCheck->executeQuery();
// If we got a result than the two players are friends. // If we got a result than the two players are friends.
if (friendResult->next()) { if (friendResult->next()) {
isFriend = true; isFriend = true;
if (friendResult->getInt(1) == 2) { if (friendResult->getInt(1) == 3) {
isBestFriend = true; 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. // 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; 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); auto propertiesLeft = Database::CreatePreppedStmt(buttonQuery);
propertiesLeft->setUInt(1, this->m_MapID); propertiesLeft->setUInt(1, this->m_MapID);
@ -346,4 +344,4 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
propertiesLeft = nullptr; propertiesLeft = nullptr;
GameMessages::SendPropertySelectQuery(m_Parent->GetObjectID(), startIndex, numberOfProperties - (startIndex + numResults) > 0, character->GetPropertyCloneID(), false, true, entries, sysAddr); GameMessages::SendPropertySelectQuery(m_Parent->GetObjectID(), startIndex, numberOfProperties - (startIndex + numResults) > 0, character->GetPropertyCloneID(), false, true, entries, sysAddr);
} }

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); 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: private:
/** /**

View File

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

View File

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

View File

@ -6,6 +6,8 @@
#include "Game.h" #include "Game.h"
#include "dLogger.h" #include "dLogger.h"
#include "CharacterComponent.h" #include "CharacterComponent.h"
#include "MissionComponent.h"
#include "MissionTaskType.h"
#include "dServer.h" #include "dServer.h"
#include "PacketUtils.h" #include "PacketUtils.h"
@ -22,6 +24,20 @@ RebuildComponent::RebuildComponent(Entity* entity) : Component(entity) {
{ {
m_Precondition = new PreconditionExpression(GeneralUtils::UTF16ToWTF8(checkPreconditions)); 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() { RebuildComponent::~RebuildComponent() {

View File

@ -198,14 +198,16 @@ void RenderComponent::PlayEffect(const int32_t effectId, const std::u16string& e
return; 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(); result.finalize();
m_DurationCache[effectId] = 0; m_DurationCache[effectId] = 0;
@ -214,7 +216,7 @@ void RenderComponent::PlayEffect(const int32_t effectId, const std::u16string& e
return; return;
} }
effect->time = static_cast<float>(result.getFloatField(0)); effect->time = static_cast<float>(result.getFloatField(0));
result.finalize(); result.finalize();

View File

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

View File

@ -19,11 +19,11 @@
#include "PacketUtils.h" #include "PacketUtils.h"
RocketLaunchpadControlComponent::RocketLaunchpadControlComponent(Entity* parent, int rocketId) : Component(parent) { 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 = query.execQuery();
auto result = CDClientDatabase::ExecuteQuery(query.str());
if (!result.eof() && !result.fieldIsNull(0)) if (!result.eof() && !result.fieldIsNull(0))
{ {
@ -48,7 +48,7 @@ void RocketLaunchpadControlComponent::Launch(Entity* originator, LWOMAPID mapId,
{ {
return; return;
} }
// This also gets triggered by a proximity monitor + item equip, I will set that up when havok is ready // This also gets triggered by a proximity monitor + item equip, I will set that up when havok is ready
auto* characterComponent = originator->GetComponent<CharacterComponent>(); auto* characterComponent = originator->GetComponent<CharacterComponent>();
auto* character = originator->GetCharacter(); auto* character = originator->GetCharacter();
@ -77,11 +77,11 @@ void RocketLaunchpadControlComponent::Launch(Entity* originator, LWOMAPID mapId,
character->SaveXMLToDatabase(); character->SaveXMLToDatabase();
SetSelectedMapId(originator->GetObjectID(), zone); SetSelectedMapId(originator->GetObjectID(), zone);
GameMessages::SendFireEventClientSide(m_Parent->GetObjectID(), originator->GetSystemAddress(), u"RocketEquipped", rocket->GetId(), cloneId, -1, originator->GetObjectID());
GameMessages::SendChangeObjectWorldState(rocket->GetId(), WORLDSTATE_ATTACHED, UNASSIGNED_SYSTEM_ADDRESS); GameMessages::SendFireEventClientSide(m_Parent->GetObjectID(), originator->GetSystemAddress(), u"RocketEquipped", rocket->GetId(), cloneId, -1, originator->GetObjectID());
GameMessages::SendChangeObjectWorldState(rocket->GetId(), WORLDSTATE_ATTACHED, UNASSIGNED_SYSTEM_ADDRESS);
EntityManager::Instance()->SerializeEntity(originator); EntityManager::Instance()->SerializeEntity(originator);
} }
@ -112,7 +112,7 @@ void RocketLaunchpadControlComponent::OnProximityUpdate(Entity* entering, std::s
// Proximity rockets are handled by item equipment // Proximity rockets are handled by item equipment
} }
void RocketLaunchpadControlComponent::SetSelectedMapId(LWOOBJID player, LWOMAPID mapID) void RocketLaunchpadControlComponent::SetSelectedMapId(LWOOBJID player, LWOMAPID mapID)
{ {
m_SelectedMapIds[player] = mapID; m_SelectedMapIds[player] = mapID;
} }
@ -126,7 +126,7 @@ LWOMAPID RocketLaunchpadControlComponent::GetSelectedMapId(LWOOBJID player) cons
return index->second; return index->second;
} }
void RocketLaunchpadControlComponent::SetSelectedCloneId(LWOOBJID player, LWOCLONEID cloneId) void RocketLaunchpadControlComponent::SetSelectedCloneId(LWOOBJID player, LWOCLONEID cloneId)
{ {
m_SelectedCloneIds[player] = cloneId; m_SelectedCloneIds[player] = cloneId;
} }

View File

@ -487,22 +487,24 @@ void ActivityInstance::StartZone() {
return; return;
auto* leader = participants[0]; auto* leader = participants[0];
CBITSTREAM;
PacketUtils::WriteHeader(bitStream, CHAT_INTERNAL, MSG_CHAT_INTERNAL_CREATE_TEAM);
bitStream.Write(leader->GetObjectID());
bitStream.Write(m_Participants.size());
for (const auto& participant : m_Participants) {
bitStream.Write(participant);
}
LWOZONEID zoneId = LWOZONEID(m_ActivityInfo.instanceMapID, 0, leader->GetCharacter()->GetPropertyCloneID()); LWOZONEID zoneId = LWOZONEID(m_ActivityInfo.instanceMapID, 0, leader->GetCharacter()->GetPropertyCloneID());
bitStream.Write(zoneId); // 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);
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); bitStream.Write(leader->GetObjectID());
bitStream.Write(m_Participants.size());
for (const auto& participant : m_Participants) {
bitStream.Write(participant);
}
bitStream.Write(zoneId);
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
}
const auto cloneId = GeneralUtils::GenerateRandomNumber<uint32_t>(1, UINT32_MAX); const auto cloneId = GeneralUtils::GenerateRandomNumber<uint32_t>(1, UINT32_MAX);
for (Entity* player : participants) { for (Entity* player : participants) {

View File

@ -17,6 +17,17 @@ SimplePhysicsComponent::SimplePhysicsComponent(uint32_t componentID, Entity* par
m_Position = m_Parent->GetDefaultPosition(); m_Position = m_Parent->GetDefaultPosition();
m_Rotation = m_Parent->GetDefaultRotation(); m_Rotation = m_Parent->GetDefaultRotation();
m_IsDirty = true; 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() { SimplePhysicsComponent::~SimplePhysicsComponent() {
@ -24,10 +35,10 @@ SimplePhysicsComponent::~SimplePhysicsComponent() {
void SimplePhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { void SimplePhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
if (bIsInitialUpdate) { if (bIsInitialUpdate) {
outBitStream->Write0(); // climbable outBitStream->Write(m_ClimbableType != eClimbableType::CLIMBABLE_TYPE_NOT);
outBitStream->Write<int32_t>(0); // climbableType outBitStream->Write(m_ClimbableType);
} }
outBitStream->Write(m_DirtyVelocity || bIsInitialUpdate); outBitStream->Write(m_DirtyVelocity || bIsInitialUpdate);
if (m_DirtyVelocity || bIsInitialUpdate) { if (m_DirtyVelocity || bIsInitialUpdate) {
outBitStream->Write(m_Velocity); outBitStream->Write(m_Velocity);
@ -46,7 +57,7 @@ void SimplePhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bool bIs
{ {
outBitStream->Write0(); outBitStream->Write0();
} }
outBitStream->Write(m_IsDirty || bIsInitialUpdate); outBitStream->Write(m_IsDirty || bIsInitialUpdate);
if (m_IsDirty || bIsInitialUpdate) { if (m_IsDirty || bIsInitialUpdate) {
outBitStream->Write(m_Position.x); outBitStream->Write(m_Position.x);
@ -66,7 +77,7 @@ uint32_t SimplePhysicsComponent::GetPhysicsMotionState() const
return m_PhysicsMotionState; return m_PhysicsMotionState;
} }
void SimplePhysicsComponent::SetPhysicsMotionState(uint32_t value) void SimplePhysicsComponent::SetPhysicsMotionState(uint32_t value)
{ {
m_PhysicsMotionState = value; m_PhysicsMotionState = value;
} }

View File

@ -14,16 +14,24 @@
class Entity; 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 * Component that serializes locations of entities to the client
*/ */
class SimplePhysicsComponent : public Component { class SimplePhysicsComponent : public Component {
public: public:
static const uint32_t ComponentType = COMPONENT_TYPE_SIMPLE_PHYSICS; static const uint32_t ComponentType = COMPONENT_TYPE_SIMPLE_PHYSICS;
SimplePhysicsComponent(uint32_t componentID, Entity* parent); SimplePhysicsComponent(uint32_t componentID, Entity* parent);
~SimplePhysicsComponent() override; ~SimplePhysicsComponent() override;
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags);
/** /**
@ -86,6 +94,18 @@ public:
*/ */
void SetPhysicsMotionState(uint32_t value); 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: private:
/** /**
@ -122,6 +142,11 @@ private:
* The current physics motion state * The current physics motion state
*/ */
uint32_t m_PhysicsMotionState = 0; uint32_t m_PhysicsMotionState = 0;
/**
* Whether or not the entity is climbable
*/
eClimbableType m_ClimbableType = eClimbableType::CLIMBABLE_TYPE_NOT;
}; };
#endif // SIMPLEPHYSICSCOMPONENT_H #endif // SIMPLEPHYSICSCOMPONENT_H

View File

@ -38,11 +38,11 @@ bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t s
auto* behavior = Behavior::CreateBehavior(behaviorId); auto* behavior = Behavior::CreateBehavior(behaviorId);
const auto branch = BehaviorBranchContext(target, 0); const auto branch = BehaviorBranchContext(target, 0);
behavior->Handle(context, bitStream, branch); behavior->Handle(context, bitStream, branch);
context->ExecuteUpdates(); context->ExecuteUpdates();
return !context->failed; return !context->failed;
} }
@ -78,24 +78,23 @@ void SkillComponent::SyncPlayerProjectile(const LWOOBJID projectileId, RakNet::B
break; break;
} }
} }
if (index == -1) if (index == -1)
{ {
Game::logger->Log("SkillComponent", "Failed to find projectile id (%llu)!\n", projectileId); Game::logger->Log("SkillComponent", "Failed to find projectile id (%llu)!\n", projectileId);
return; return;
} }
const auto sync_entry = this->m_managedProjectiles.at(index); 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); Game::logger->Log("SkillComponent", "Failed to find skill id for (%i)!\n", sync_entry.lot);
return; return;
@ -115,7 +114,7 @@ void SkillComponent::SyncPlayerProjectile(const LWOOBJID projectileId, RakNet::B
{ {
branch.target = target; branch.target = target;
} }
behavior->Handle(sync_entry.context, bitStream, branch); behavior->Handle(sync_entry.context, bitStream, branch);
this->m_managedProjectiles.erase(this->m_managedProjectiles.begin() + index); this->m_managedProjectiles.erase(this->m_managedProjectiles.begin() + index);
@ -129,7 +128,7 @@ void SkillComponent::RegisterPlayerProjectile(const LWOOBJID projectileId, Behav
entry.branchContext = branch; entry.branchContext = branch;
entry.lot = lot; entry.lot = lot;
entry.id = projectileId; entry.id = projectileId;
this->m_managedProjectiles.push_back(entry); this->m_managedProjectiles.push_back(entry);
} }
@ -141,7 +140,7 @@ void SkillComponent::Update(const float deltaTime)
} }
std::map<uint32_t, BehaviorContext*> keep {}; std::map<uint32_t, BehaviorContext*> keep {};
for (const auto& pair : this->m_managedBehaviors) for (const auto& pair : this->m_managedBehaviors)
{ {
auto* context = pair.second; auto* context = pair.second;
@ -150,7 +149,7 @@ void SkillComponent::Update(const float deltaTime)
{ {
continue; continue;
} }
if (context->clientInitalized) if (context->clientInitalized)
{ {
context->CalculateUpdate(deltaTime); context->CalculateUpdate(deltaTime);
@ -164,7 +163,7 @@ void SkillComponent::Update(const float deltaTime)
if (context->syncEntries.empty() && context->timerEntries.empty()) if (context->syncEntries.empty() && context->timerEntries.empty())
{ {
auto any = false; auto any = false;
for (const auto& projectile : this->m_managedProjectiles) for (const auto& projectile : this->m_managedProjectiles)
{ {
if (projectile.context == context) if (projectile.context == context)
@ -180,13 +179,13 @@ void SkillComponent::Update(const float deltaTime)
context->Reset(); context->Reset();
delete context; delete context;
context = nullptr; context = nullptr;
continue; continue;
} }
} }
keep.insert_or_assign(pair.first, context); keep.insert_or_assign(pair.first, context);
} }
@ -254,9 +253,9 @@ SkillExecutionResult SkillComponent::CalculateBehavior(const uint32_t skillId, c
context->caster = m_Parent->GetObjectID(); context->caster = m_Parent->GetObjectID();
context->clientInitalized = clientInitalized; context->clientInitalized = clientInitalized;
context->foundTarget = target != LWOOBJID_EMPTY || ignoreTarget || clientInitalized; context->foundTarget = target != LWOOBJID_EMPTY || ignoreTarget || clientInitalized;
behavior->Calculate(context, bitStream, { target, 0}); behavior->Calculate(context, bitStream, { target, 0});
for (auto* script : CppScripts::GetEntityScripts(m_Parent)) { for (auto* script : CppScripts::GetEntityScripts(m_Parent)) {
@ -278,7 +277,7 @@ SkillExecutionResult SkillComponent::CalculateBehavior(const uint32_t skillId, c
{ {
// Echo start skill // Echo start skill
GameMessages::EchoStartSkill start; GameMessages::EchoStartSkill start;
start.iCastType = 0; start.iCastType = 0;
start.skillID = skillId; start.skillID = skillId;
start.uiSkillHandle = context->skillUId; start.uiSkillHandle = context->skillUId;
@ -353,7 +352,7 @@ void SkillComponent::CalculateUpdate(const float deltaTime)
const auto targetPosition = target->GetPosition(); const auto targetPosition = target->GetPosition();
const auto closestPoint = Vector3::ClosestPointOnLine(entry.lastPosition, position, targetPosition); const auto closestPoint = Vector3::ClosestPointOnLine(entry.lastPosition, position, targetPosition);
const auto distance = Vector3::DistanceSquared(targetPosition, closestPoint); const auto distance = Vector3::DistanceSquared(targetPosition, closestPoint);
if (distance > 3 * 3) if (distance > 3 * 3)
@ -399,12 +398,12 @@ void SkillComponent::CalculateUpdate(const float deltaTime)
} }
entry.lastPosition = position; entry.lastPosition = position;
managedProjectile = entry; managedProjectile = entry;
} }
std::vector<ProjectileSyncEntry> valid; std::vector<ProjectileSyncEntry> valid;
for (auto& entry : this->m_managedProjectiles) for (auto& entry : this->m_managedProjectiles)
{ {
if (entry.calculation) if (entry.calculation)
@ -412,7 +411,7 @@ void SkillComponent::CalculateUpdate(const float deltaTime)
if (entry.time >= entry.maxTime) if (entry.time >= entry.maxTime)
{ {
entry.branchContext.target = LWOOBJID_EMPTY; entry.branchContext.target = LWOOBJID_EMPTY;
SyncProjectileCalculation(entry); SyncProjectileCalculation(entry);
continue; continue;
@ -430,8 +429,7 @@ void SkillComponent::SyncProjectileCalculation(const ProjectileSyncEntry& entry)
{ {
auto* other = EntityManager::Instance()->GetEntity(entry.branchContext.target); auto* other = EntityManager::Instance()->GetEntity(entry.branchContext.target);
if (other == nullptr) if (other == nullptr) {
{
if (entry.branchContext.target != LWOOBJID_EMPTY) if (entry.branchContext.target != LWOOBJID_EMPTY)
{ {
Game::logger->Log("SkillComponent", "Invalid projectile target (%llu)!\n", entry.branchContext.target); Game::logger->Log("SkillComponent", "Invalid projectile target (%llu)!\n", entry.branchContext.target);
@ -440,14 +438,12 @@ void SkillComponent::SyncProjectileCalculation(const ProjectileSyncEntry& entry)
return; 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) << ")"; if (result.eof()) {
auto result = CDClientDatabase::ExecuteQuery(query.str());
if (result.eof())
{
Game::logger->Log("SkillComponent", "Failed to find skill id for (%i)!\n", entry.lot); Game::logger->Log("SkillComponent", "Failed to find skill id for (%i)!\n", entry.lot);
return; return;
@ -456,13 +452,13 @@ void SkillComponent::SyncProjectileCalculation(const ProjectileSyncEntry& entry)
const auto behaviorId = static_cast<uint32_t>(result.getIntField(0)); const auto behaviorId = static_cast<uint32_t>(result.getIntField(0));
result.finalize(); result.finalize();
auto* behavior = Behavior::CreateBehavior(behaviorId); auto* behavior = Behavior::CreateBehavior(behaviorId);
auto* bitStream = new RakNet::BitStream(); auto* bitStream = new RakNet::BitStream();
behavior->Calculate(entry.context, bitStream, entry.branchContext); behavior->Calculate(entry.context, bitStream, entry.branchContext);
GameMessages::DoClientProjectileImpact projectileImpact; GameMessages::DoClientProjectileImpact projectileImpact;
projectileImpact.sBitStream.assign((char*) bitStream->GetData(), bitStream->GetNumberOfBytesUsed()); projectileImpact.sBitStream.assign((char*) bitStream->GetData(), bitStream->GetNumberOfBytesUsed());
@ -498,19 +494,19 @@ void SkillComponent::HandleUnmanaged(const uint32_t behaviorId, const LWOOBJID t
delete bitStream; delete bitStream;
delete context; delete context;
} }
void SkillComponent::HandleUnCast(const uint32_t behaviorId, const LWOOBJID target) void SkillComponent::HandleUnCast(const uint32_t behaviorId, const LWOOBJID target)
{ {
auto* context = new BehaviorContext(target); auto* context = new BehaviorContext(target);
context->caster = target; context->caster = target;
auto* behavior = Behavior::CreateBehavior(behaviorId); auto* behavior = Behavior::CreateBehavior(behaviorId);
behavior->UnCast(context, { target }); behavior->UnCast(context, { target });
delete context; delete context;
} }

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)

File diff suppressed because it is too large Load Diff

View File

@ -328,6 +328,34 @@ namespace GameMessages {
void SendDisplayChatBubble(LWOOBJID objectId, const std::u16string& text, const SystemAddress& sysAddr); 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: //Racing:
void HandleModuleAssemblyQueryData(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr); 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 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 HandleRequestDie(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr);
void HandleVehicleNotifyServerAddPassiveBoostAction(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 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 SendStartActivityTime(LWOOBJID objectId, float_t startTime, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS);
void SendRequestActivityEnter(LWOOBJID objectId, const SystemAddress& sysAddr, bool bStart, LWOOBJID userID); void SendRequestActivityEnter(LWOOBJID objectId, const SystemAddress& sysAddr, bool bStart, LWOOBJID userID);
void SendUseItemRequirementsResponse(LWOOBJID objectID, const SystemAddress& sysAddr, UseItemResponse itemResponse);
// SG: // SG:

View File

@ -1,5 +1,8 @@
#pragma once #pragma once
#ifndef PROPERTYSELECTQUERY_H
#define PROPERTYSELECTQUERY_H
#include "Entity.h" #include "Entity.h"
class PropertySelectQueryProperty final class PropertySelectQueryProperty final
@ -24,3 +27,5 @@ public:
float PerformanceCost = 0; // The performance cost of the property float PerformanceCost = 0; // The performance cost of the property
uint32_t PerformanceIndex = 0; // The performance index of the property? Always 0? 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 "GameMessages.h"
#include "Game.h" #include "Game.h"
#include "Item.h" #include "Item.h"
#include "eItemType.h"
std::vector<LOT> Inventory::m_GameMasterRestrictedItems = { std::vector<LOT> Inventory::m_GameMasterRestrictedItems = {
1727, // GM Only - JetPack 1727, // GM Only - JetPack
@ -274,40 +275,41 @@ eInventoryType Inventory::FindInventoryTypeForLot(const LOT lot)
const auto itemType = static_cast<eItemType>(itemComponent.itemType); const auto itemType = static_cast<eItemType>(itemComponent.itemType);
switch (itemType) { switch (itemType) {
case ITEM_TYPE_BRICK: case eItemType::ITEM_TYPE_BRICK:
return BRICKS; return BRICKS;
case ITEM_TYPE_BEHAVIOR: case eItemType::ITEM_TYPE_BEHAVIOR:
return BEHAVIORS; return BEHAVIORS;
case ITEM_TYPE_PROPERTY: case eItemType::ITEM_TYPE_PROPERTY:
return PROPERTY_DEEDS; return PROPERTY_DEEDS;
case ITEM_TYPE_MODEL: case eItemType::ITEM_TYPE_MODEL:
case ITEM_TYPE_VEHICLE: case eItemType::ITEM_TYPE_VEHICLE:
case ITEM_TYPE_LOOT_MODEL: case eItemType::ITEM_TYPE_LOOT_MODEL:
case eItemType::ITEM_TYPE_MOUNT:
return MODELS; return MODELS;
case ITEM_TYPE_HAT: case eItemType::ITEM_TYPE_HAT:
case ITEM_TYPE_HAIR: case eItemType::ITEM_TYPE_HAIR:
case ITEM_TYPE_NECK: case eItemType::ITEM_TYPE_NECK:
case ITEM_TYPE_LEFT_HAND: case eItemType::ITEM_TYPE_LEFT_HAND:
case ITEM_TYPE_RIGHT_HAND: case eItemType::ITEM_TYPE_RIGHT_HAND:
case ITEM_TYPE_LEGS: case eItemType::ITEM_TYPE_LEGS:
case ITEM_TYPE_LEFT_TRINKET: case eItemType::ITEM_TYPE_LEFT_TRINKET:
case ITEM_TYPE_RIGHT_TRINKET: case eItemType::ITEM_TYPE_RIGHT_TRINKET:
case ITEM_TYPE_COLLECTIBLE: case eItemType::ITEM_TYPE_COLLECTIBLE:
case ITEM_TYPE_CONSUMABLE: case eItemType::ITEM_TYPE_CONSUMABLE:
case ITEM_TYPE_CHEST: case eItemType::ITEM_TYPE_CHEST:
case ITEM_TYPE_EGG: case eItemType::ITEM_TYPE_EGG:
case ITEM_TYPE_PET_FOOD: case eItemType::ITEM_TYPE_PET_FOOD:
case ITEM_TYPE_PET_INVENTORY_ITEM: case eItemType::ITEM_TYPE_PET_INVENTORY_ITEM:
case ITEM_TYPE_PACKAGE: case eItemType::ITEM_TYPE_PACKAGE:
case ITEM_TYPE_CURRENCY: case eItemType::ITEM_TYPE_CURRENCY:
return ITEMS; return ITEMS;
case ITEM_TYPE_QUEST_OBJECT: case eItemType::ITEM_TYPE_QUEST_OBJECT:
case ITEM_TYPE_UNKNOWN: case eItemType::ITEM_TYPE_UNKNOWN:
default: default:
return HIDDEN; return HIDDEN;
} }

View File

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

View File

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

View File

@ -15,17 +15,17 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent)
this->m_PassiveAbilities = ItemSetPassiveAbility::FindAbilities(id, m_InventoryComponent->GetParent(), this); 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 = query.execQuery();
auto result = CDClientDatabase::ExecuteQuery(query.str());
if (result.eof()) if (result.eof())
{ {
return; return;
} }
for (auto i = 0; i < 5; ++i) for (auto i = 0; i < 5; ++i)
{ {
if (result.fieldIsNull(i)) if (result.fieldIsNull(i))
@ -33,11 +33,11 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent)
continue; 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 = skillQuery.execQuery();
auto skillResult = CDClientDatabase::ExecuteQuery(skillQuery.str());
if (skillResult.eof()) if (skillResult.eof())
{ {
@ -49,10 +49,10 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent)
if (skillResult.fieldIsNull(0)) if (skillResult.fieldIsNull(0))
{ {
skillResult.nextRow(); skillResult.nextRow();
continue; continue;
} }
const auto skillId = skillResult.getIntField(0); const auto skillId = skillResult.getIntField(0);
switch (i) switch (i)
@ -75,7 +75,7 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent)
default: default:
break; break;
} }
skillResult.nextRow(); skillResult.nextRow();
} }
} }
@ -83,7 +83,7 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent)
std::string ids = result.getStringField(5); std::string ids = result.getStringField(5);
ids.erase(std::remove_if(ids.begin(), ids.end(), ::isspace), ids.end()); ids.erase(std::remove_if(ids.begin(), ids.end(), ::isspace), ids.end());
std::istringstream stream(ids); std::istringstream stream(ids);
std::string token; std::string token;
@ -99,9 +99,9 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent)
m_Items.push_back(value); m_Items.push_back(value);
} }
} }
m_Equipped = {}; m_Equipped = {};
for (const auto item : m_Items) for (const auto item : m_Items)
{ {
if (inventoryComponent->IsEquipped(item)) if (inventoryComponent->IsEquipped(item))
@ -141,11 +141,11 @@ void ItemSet::OnEquip(const LOT lot)
auto* skillComponent = m_InventoryComponent->GetParent()->GetComponent<SkillComponent>(); auto* skillComponent = m_InventoryComponent->GetParent()->GetComponent<SkillComponent>();
auto* missionComponent = m_InventoryComponent->GetParent()->GetComponent<MissionComponent>(); auto* missionComponent = m_InventoryComponent->GetParent()->GetComponent<MissionComponent>();
for (const auto skill : skillSet) for (const auto skill : skillSet)
{ {
auto* skillTable = CDClientManager::Instance()->GetTable<CDSkillBehaviorTable>("SkillBehavior"); auto* skillTable = CDClientManager::Instance()->GetTable<CDSkillBehaviorTable>("SkillBehavior");
const auto behaviorId = skillTable->GetSkillByID(skill).behaviorID; const auto behaviorId = skillTable->GetSkillByID(skill).behaviorID;
missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_SKILL, skill); missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_SKILL, skill);
@ -167,11 +167,11 @@ void ItemSet::OnUnEquip(const LOT lot)
{ {
return; return;
} }
const auto& skillSet = GetSkillSet(m_Equipped.size()); const auto& skillSet = GetSkillSet(m_Equipped.size());
m_Equipped.erase(index); m_Equipped.erase(index);
if (skillSet.empty()) if (skillSet.empty())
{ {
return; return;
@ -199,7 +199,7 @@ uint32_t ItemSet::GetID() const
return m_ID; return m_ID;
} }
void ItemSet::Update(float deltaTime) void ItemSet::Update(float deltaTime)
{ {
for (auto& passiveAbility : m_PassiveAbilities) for (auto& passiveAbility : m_PassiveAbilities)
{ {
@ -207,7 +207,7 @@ void ItemSet::Update(float deltaTime)
} }
} }
void ItemSet::TriggerPassiveAbility(PassiveAbilityTrigger trigger) void ItemSet::TriggerPassiveAbility(PassiveAbilityTrigger trigger)
{ {
for (auto& passiveAbility : m_PassiveAbilities) for (auto& passiveAbility : m_PassiveAbilities)
{ {

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? if (param.empty() || (param[0] & 1) == 0) // Should items be removed?
{ {
for (const auto target : task->GetAllTargets()) { 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); missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_ITEM_COLLECTION, target, LWOOBJID_EMPTY, "", -task->GetClientInfo().targetValue);
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,8 @@
#pragma once #pragma once
#ifndef MISSIONTASKTYPE_H
#define MISSIONTASKTYPE_H
//! An enum for mission task types //! An enum for mission task types
enum class MissionTaskType : int { enum class MissionTaskType : int {
MISSION_TASK_TYPE_UNKNOWN = -1, //!< The task type is unknown 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_PLACE_MODEL = 25, //!< A task for picking up a model
MISSION_TASK_TYPE_VISIT_PROPERTY = 30 //!< A task for visiting a property 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