latest sources commit

This commit is contained in:
mikx
2023-11-07 05:04:30 -05:00
commit 749adf47ca
7570 changed files with 5705168 additions and 0 deletions

39
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,39 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Enforce compileparameters for corebuilds under GCC
# This to stop a few silly crashes that could have been avoided IF people
# weren't doing some -O3 psychooptimizations etc.
# Specified files for Windows
if (WIN32)
# Crash logs
set(winDebugging
${CMAKE_SOURCE_DIR}/src/common/Debugging/WheatyExceptionReport.cpp
${CMAKE_SOURCE_DIR}/src/common/Debugging/WheatyExceptionReport.h)
# Service
set(winService
${CMAKE_SOURCE_DIR}/src/common/Platform/ServiceWin32.cpp
${CMAKE_SOURCE_DIR}/src/common/Platform/ServiceWin32.h)
endif()
if(CMAKE_COMPILER_IS_GNUCXX AND NOT MINGW)
add_definitions(-fno-delete-null-pointer-checks)
endif()
add_subdirectory(genrev)
add_subdirectory(server)
if (TOOLS_BUILD AND NOT TOOLS_BUILD STREQUAL "none")
add_subdirectory(tools)
endif()

72
src/cmake/ac_macros.cmake Normal file
View File

@@ -0,0 +1,72 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
#
# AC_ADD_SCRIPT
#
MACRO(AC_ADD_SCRIPT path)
CU_ADD_GLOBAL("AC_SCRIPTS_SOURCES" "${path}")
ENDMACRO()
#
# AC_ADD_SCRIPTS
#
# This macro can be used to automatically load scripts for the ScriptMgr
# from a specified folder, instead of manually list them within the cmake
# NOTE: you must still manually specify the script loader header
#
MACRO(AC_ADD_SCRIPTS path)
CU_SUBDIRLIST(sub_DIRS ${path} TRUE TRUE)
FOREACH(subdir ${sub_DIRS})
file(GLOB sources "${subdir}/*.cpp" "${subdir}/*.h")
CU_LIST_ADD_CACHE(scripts_STAT_SRCS "${sources}")
ENDFOREACH()
ENDMACRO()
#
# AC_ADD_SCRIPT_LOADER
#
MACRO(AC_ADD_SCRIPT_LOADER script_dec include)
set (lower_prio_scripts ${ARGN})
list(LENGTH lower_prio_scripts num_lower_prio_scripts)
if (${num_lower_prio_scripts} GREATER 0)
CU_GET_GLOBAL("AC_ADD_SCRIPTS_LIST")
foreach(lower_prio_script ${lower_prio_scripts})
if ("${AC_ADD_SCRIPTS_LIST}" MATCHES "Add${lower_prio_script}Scripts()")
message("-- ${script_dec} demands lower priority: ${lower_prio_script} --")
list(REMOVE_ITEM AC_ADD_SCRIPTS_LIST "Add${lower_prio_script}Scripts()")
CU_SET_GLOBAL("AC_ADD_SCRIPTS_LIST" "${AC_ADD_SCRIPTS_LIST}")
list(APPEND removed_lower_prio_scripts ${lower_prio_script})
endif()
endforeach()
CU_ADD_GLOBAL("AC_ADD_SCRIPTS_LIST" "Add${script_dec}Scripts()\;")
foreach(lower_prio_script ${removed_lower_prio_scripts})
CU_ADD_GLOBAL("AC_ADD_SCRIPTS_LIST" "Add${lower_prio_script}Scripts()\;")
endforeach()
else()
CU_ADD_GLOBAL("AC_ADD_SCRIPTS_LIST" "Add${script_dec}Scripts()\;")
endif()
if (NOT ${include} STREQUAL "")
CU_GET_GLOBAL("AC_ADD_SCRIPTS_INCLUDE")
if (NOT ";${AC_ADD_SCRIPTS_INCLUDE};" MATCHES ";${include};")
CU_ADD_GLOBAL("AC_ADD_SCRIPTS_INCLUDE" "${include}\;")
endif()
endif()
ENDMACRO()
#
# AC_ADD_CONFIG_FILE
#
MACRO(AC_ADD_CONFIG_FILE configFilePath)
message("> Warning: module using deprecated add config file api")
ENDMACRO()

View File

@@ -0,0 +1,138 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
if ((USE_COREPCH OR USE_SCRIPTPCH) AND (CMAKE_C_COMPILER_LAUNCHER STREQUAL "ccache" OR CMAKE_CXX_COMPILER_LAUNCHER STREQUAL "ccache"))
message(STATUS "Clang: disable pch timestamp when ccache and pch enabled")
# TODO: for ccache https://github.com/ccache/ccache/issues/539
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -fno-pch-timestamp")
endif()
# Set build-directive (used in core to tell which buildtype we used)
target_compile_definitions(acore-compile-option-interface
INTERFACE
-D_BUILD_DIRECTIVE="${CMAKE_BUILD_TYPE}")
set(CLANG_EXPECTED_VERSION 10.0.0)
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS CLANG_EXPECTED_VERSION)
message(FATAL_ERROR "Clang: AzerothCore requires version ${CLANG_EXPECTED_VERSION} to build but found ${CMAKE_CXX_COMPILER_VERSION}")
else()
message(STATUS "Clang: Minimum version required is ${CLANG_EXPECTED_VERSION}, found ${CMAKE_CXX_COMPILER_VERSION} - ok!")
endif()
# This tests for a bug in clang-7 that causes linkage to fail for 64-bit from_chars (in some configurations)
# If the clang requirement is bumped to >= clang-8, you can remove this check, as well as
# the associated ifdef block in src/common/Utilities/StringConvert.h
include(CheckCXXSourceCompiles)
check_cxx_source_compiles("
#include <charconv>
#include <cstdint>
int main()
{
uint64_t n;
char const c[] = \"0\";
std::from_chars(c, c+1, n);
return static_cast<int>(n);
}
" CLANG_HAVE_PROPER_CHARCONV)
if(WITH_WARNINGS)
target_compile_options(acore-warning-interface
INTERFACE
-W
-Wall
-Wextra
-Winit-self
-Wfatal-errors
-Wno-mismatched-tags
-Woverloaded-virtual)
message(STATUS "Clang: All warnings enabled")
endif()
if(WITH_COREDEBUG)
target_compile_options(acore-compile-option-interface
INTERFACE
-g3)
message(STATUS "Clang: Debug-flags set (-g3)")
endif()
if(MSAN)
target_compile_options(acore-compile-option-interface
INTERFACE
-fno-omit-frame-pointer
-fsanitize=memory
-fsanitize-memory-track-origins
-mllvm
-msan-keep-going=1)
target_link_options(acore-compile-option-interface
INTERFACE
-fno-omit-frame-pointer
-fsanitize=memory
-fsanitize-memory-track-origins)
message(STATUS "Clang: Enabled Memory Sanitizer MSan")
endif()
if(UBSAN)
target_compile_options(acore-compile-option-interface
INTERFACE
-fno-omit-frame-pointer
-fsanitize=undefined)
target_link_options(acore-compile-option-interface
INTERFACE
-fno-omit-frame-pointer
-fsanitize=undefined)
message(STATUS "Clang: Enabled Undefined Behavior Sanitizer UBSan")
endif()
if(TSAN)
target_compile_options(acore-compile-option-interface
INTERFACE
-fno-omit-frame-pointer
-fsanitize=thread)
target_link_options(acore-compile-option-interface
INTERFACE
-fno-omit-frame-pointer
-fsanitize=thread)
message(STATUS "Clang: Enabled Thread Sanitizer TSan")
endif()
# -Wno-narrowing needed to suppress a warning in g3d
# -Wno-deprecated-register is needed to suppress gsoap warnings on Unix systems.
target_compile_options(acore-compile-option-interface
INTERFACE
-Wno-narrowing
-Wno-deprecated-register)
if(BUILD_SHARED_LIBS)
# -fPIC is needed to allow static linking in shared libs.
# -fvisibility=hidden sets the default visibility to hidden to prevent exporting of all symbols.
target_compile_options(acore-compile-option-interface
INTERFACE
-fPIC)
target_compile_options(acore-hidden-symbols-interface
INTERFACE
-fvisibility=hidden)
# --no-undefined to throw errors when there are undefined symbols
# (caused through missing ACORE_*_API macros).
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --no-undefined")
message(STATUS "Clang: Disallow undefined symbols")
endif()

View File

@@ -0,0 +1,76 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Set build-directive (used in core to tell which buildtype we used)
target_compile_definitions(acore-compile-option-interface
INTERFACE
-D_BUILD_DIRECTIVE="${CMAKE_BUILD_TYPE}")
set(GCC_EXPECTED_VERSION 8.0.0)
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS GCC_EXPECTED_VERSION)
message(FATAL_ERROR "GCC: This project requires version ${GCC_EXPECTED_VERSION} to build but found ${CMAKE_CXX_COMPILER_VERSION}")
else()
message(STATUS "GCC: Minimum version required is ${GCC_EXPECTED_VERSION}, found ${CMAKE_CXX_COMPILER_VERSION} - ok!")
endif()
if(PLATFORM EQUAL 32)
# Required on 32-bit systems to enable SSE2 (standard on x64)
target_compile_options(acore-compile-option-interface
INTERFACE
-msse2
-mfpmath=sse)
endif()
target_compile_definitions(acore-compile-option-interface
INTERFACE
-DHAVE_SSE2
-D__SSE2__)
message(STATUS "GCC: SFMT enabled, SSE2 flags forced")
if( WITH_WARNINGS )
target_compile_options(acore-warning-interface
INTERFACE
-W
-Wall
-Wextra
-Winit-self
-Winvalid-pch
-Wfatal-errors
-Woverloaded-virtual)
message(STATUS "GCC: All warnings enabled")
endif()
if( WITH_COREDEBUG )
target_compile_options(acore-compile-option-interface
INTERFACE
-g3)
message(STATUS "GCC: Debug-flags set (-g3)")
endif()
if(BUILD_SHARED_LIBS)
target_compile_options(acore-compile-option-interface
INTERFACE
-fPIC
-Wno-attributes)
target_compile_options(acore-hidden-symbols-interface
INTERFACE
-fvisibility=hidden)
# Should break the build when there are ACORE_*_API macros missing
# but it complains about missing references in precompiled headers.
# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wl,--no-undefined")
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-undefined")
message(STATUS "GCC: Enabled shared linking")
endif()

View File

@@ -0,0 +1,40 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Set build-directive (used in core to tell which buildtype we used)
target_compile_definitions(acore-compile-option-interface
INTERFACE
-D_BUILD_DIRECTIVE="${CMAKE_BUILD_TYPE}")
if(PLATFORM EQUAL 32)
target_compile_options(acore-compile-option-interface
INTERFACE
-axSSE2)
else()
target_compile_options(acore-compile-option-interface
INTERFACE
-xSSE2)
endif()
if(WITH_WARNINGS)
target_compile_options(acore-warning-interface
INTERFACE
-w1)
message(STATUS "ICC: All warnings enabled")
endif()
if(WITH_COREDEBUG)
target_compile_options(acore-compile-option-interface
INTERFACE
-g)
message(STATUS "ICC: Debug-flag set (-g)")
endif()

View File

@@ -0,0 +1,53 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# set up output paths for executable binaries (.exe-files, and .dll-files on DLL-capable platforms)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# Set build-directive (used in core to tell which buildtype we used)
target_compile_definitions(acore-compile-option-interface
INTERFACE
-D_BUILD_DIRECTIVE="${CMAKE_BUILD_TYPE}")
if(PLATFORM EQUAL 32)
# Required on 32-bit systems to enable SSE2 (standard on x64)
target_compile_options(acore-compile-option-interface
INTERFACE
-msse2
-mfpmath=sse)
endif()
target_compile_definitions(acore-compile-option-interface
INTERFACE
-DHAVE_SSE2
-D__SSE2__)
message(STATUS "GCC: SFMT enabled, SSE2 flags forced")
if(WITH_WARNINGS)
target_compile_options(acore-warning-interface
INTERFACE
-W
-Wall
-Wextra
-Winit-self
-Winvalid-pch
-Wfatal-errors
-Woverloaded-virtual)
message(STATUS "GCC: All warnings enabled")
endif()
if(WITH_COREDEBUG)
target_compile_options(acore-compile-option-interface
INTERFACE
-g3)
message(STATUS "GCC: Debug-flags set (-g3)")
endif()

View File

@@ -0,0 +1,162 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# set up output paths for executable binaries (.exe-files, and .dll-files on DLL-capable platforms)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(MSVC_EXPECTED_VERSION 19.24)
set(MSVC_EXPECTED_VERSION_STRING "Microsoft Visual Studio 2019 16.4")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS MSVC_EXPECTED_VERSION)
message(FATAL_ERROR "MSVC: AzerothCore requires version ${MSVC_EXPECTED_VERSION} (${MSVC_EXPECTED_VERSION_STRING}) to build but found ${CMAKE_CXX_COMPILER_VERSION}")
else()
message(STATUS "MSVC: Minimum version required is ${MSVC_EXPECTED_VERSION}, found ${CMAKE_CXX_COMPILER_VERSION} - ok!")
endif()
# CMake sets warning flags by default, however we manage it manually
# for different core and dependency targets
string(REGEX REPLACE "/W[0-4] " "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
# Search twice, once for space after /W argument,
# once for end of line as CMake regex has no \b
string(REGEX REPLACE "/W[0-4]$" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
string(REGEX REPLACE "/W[0-4] " "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
string(REGEX REPLACE "/W[0-4]$" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
# https://tinyurl.com/jxnc4s83
target_compile_options(acore-compile-option-interface
INTERFACE
/utf-8)
if(PLATFORM EQUAL 64)
# This definition is necessary to work around a bug with Intellisense described
# here: http://tinyurl.com/2cb428. Syntax highlighting is important for proper
# debugger functionality.
target_compile_definitions(acore-compile-option-interface
INTERFACE
-D_WIN64)
message(STATUS "MSVC: 64-bit platform, enforced -D_WIN64 parameter")
# Enable extended object support for debug compiles on X64 (not required on X86)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj")
message(STATUS "MSVC: Enabled extended object-support for debug-compiles")
else()
# mark 32 bit executables large address aware so they can use > 2GB address space
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /LARGEADDRESSAWARE")
message(STATUS "MSVC: Enabled large address awareness")
target_compile_options(acore-compile-option-interface
INTERFACE
/arch:SSE2)
message(STATUS "MSVC: Enabled SSE2 support")
endif()
# Set build-directive (used in core to tell which buildtype we used)
# msbuild/devenv don't set CMAKE_MAKE_PROGRAM, you can choose build type from a dropdown after generating projects
if("${CMAKE_MAKE_PROGRAM}" MATCHES "MSBuild")
target_compile_definitions(acore-compile-option-interface
INTERFACE
-D_BUILD_DIRECTIVE="$(ConfigurationName)")
else()
# while all make-like generators do (nmake, ninja)
target_compile_definitions(acore-compile-option-interface
INTERFACE
-D_BUILD_DIRECTIVE="${CMAKE_BUILD_TYPE}")
endif()
# multithreaded compiling on VS
target_compile_options(acore-compile-option-interface
INTERFACE
/MP)
# Define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES - eliminates the warning by changing the strcpy call to strcpy_s, which prevents buffer overruns
target_compile_definitions(acore-compile-option-interface
INTERFACE
-D_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES)
message(STATUS "MSVC: Overloaded standard names")
# Ignore warnings about older, less secure functions
target_compile_definitions(acore-compile-option-interface
INTERFACE
-D_CRT_SECURE_NO_WARNINGS)
message(STATUS "MSVC: Disabled NON-SECURE warnings")
# Ignore warnings about POSIX deprecation
target_compile_definitions(acore-compile-option-interface
INTERFACE
-D_CRT_NONSTDC_NO_WARNINGS)
message(STATUS "MSVC: Disabled POSIX warnings")
# Ignore warnings about INTMAX_MAX
target_compile_definitions(acore-compile-option-interface
INTERFACE
-D__STDC_LIMIT_MACROS)
message(STATUS "MSVC: Disabled INTMAX_MAX warnings")
# Ignore specific warnings
target_compile_options(acore-compile-option-interface
INTERFACE
/wd4351 # C4351: new behavior: elements of array 'x' will be default initialized
/wd4091) # C4091: 'typedef ': ignored on left of '' when no variable is declared
# Define NOMINMAX
target_compile_definitions(acore-compile-option-interface
INTERFACE
-DNOMINMAX)
message(STATUS "MSVC: Enable NOMINMAX")
if(NOT WITH_WARNINGS)
target_compile_options(acore-warning-interface
INTERFACE
/wd4996 # C4996 deprecation
/wd4985 # C4985 'symbol-name': attributes not present on previous declaration.
/wd4244 # C4244 'argument' : conversion from 'type1' to 'type2', possible loss of data
/wd4267 # C4267 'var' : conversion from 'size_t' to 'type', possible loss of data
/wd4619 # C4619 #pragma warning : there is no warning number 'number'
/wd4512) # C4512 'class' : assignment operator could not be generated
message(STATUS "MSVC: Disabled generic compiletime warnings")
endif()
# Move some warnings that are enabled for other compilers from level 4 to level 3
target_compile_options(acore-compile-option-interface
INTERFACE
/w34100 # C4100 'identifier' : unreferenced formal parameter
/w34101 # C4101: 'identifier' : unreferenced local variable
/w34189 # C4189: 'identifier' : local variable is initialized but not referenced
/w34389) # C4189: 'equality-operator' : signed/unsigned mismatch
if(BUILD_SHARED_LIBS)
target_compile_options(acore-compile-option-interface
INTERFACE
/wd4251 # C4251: needs to have dll-interface to be used by clients of class '...'
/wd4275) # C4275: non dll-interface class ...' used as base for dll-interface class '...'
message(STATUS "MSVC: Enabled shared linking")
endif()
# Enable and treat as errors the following warnings to easily detect virtual function signature failures:
target_compile_options(acore-warning-interface
INTERFACE
/we4263 # 'function' : member function does not override any base class virtual member function
/we4264) # 'virtual_function' : no override available for virtual member function from base 'class'; function is hidden
# Disable incremental linking in debug builds.
# To prevent linking getting stuck (which might be fixed in a later VS version).
macro(DisableIncrementalLinking variable)
string(REGEX REPLACE "/INCREMENTAL *" "" ${variable} "${${variable}}")
set(${variable} "${${variable}} /INCREMENTAL:NO")
endmacro()
DisableIncrementalLinking(CMAKE_EXE_LINKER_FLAGS_DEBUG)
DisableIncrementalLinking(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO)
DisableIncrementalLinking(CMAKE_SHARED_LINKER_FLAGS_DEBUG)
DisableIncrementalLinking(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO)

97
src/cmake/genrev.cmake Normal file
View File

@@ -0,0 +1,97 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# User has manually chosen to ignore the git-tests, so throw them a warning.
# This is done EACH compile so they can be alerted about the consequences.
if(NOT BUILDDIR)
# Workaround for funny MSVC behaviour - this segment is only used when using cmake gui
set(BUILDDIR ${CMAKE_BINARY_DIR})
endif()
if(WITHOUT_GIT)
set(rev_date "1970-01-01 00:00:00 +0000")
set(rev_hash "unknown")
set(rev_branch "Archived")
# No valid git commit date, use today
string(TIMESTAMP rev_date_fallback "%Y-%m-%d %H:%M:%S" UTC)
else()
# Workaround for not correctly detecting git
if (NOT GIT_EXECUTABLE)
set(GIT_EXECUTABLE "git")
endif()
if(GIT_EXECUTABLE)
# Create a revision-string that we can use
execute_process(
COMMAND "${GIT_EXECUTABLE}" describe --long --match 0.1 --dirty=+ --abbrev=12 --always
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE rev_info
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# And grab the commits timestamp
execute_process(
COMMAND "${GIT_EXECUTABLE}" show -s --format=%ci
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE rev_date
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
# Also retrieve branch name
execute_process(
COMMAND "${GIT_EXECUTABLE}" rev-parse --abbrev-ref HEAD
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE rev_branch
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
endif()
# Last minute check - ensure that we have a proper revision
# If everything above fails (means the user has erased the git revision control directory or removed the origin/HEAD tag)
if(NOT rev_info)
# No valid ways available to find/set the revision/hash, so let's force some defaults
message(STATUS "
Could not find a proper repository signature (hash) - you may need to pull tags with git fetch -t
Continuing anyway - note that the versionstring will be set to \"unknown 1970-01-01 00:00:00 (Archived)\"")
set(rev_date "1970-01-01 00:00:00 +0000")
set(rev_hash "unknown")
set(rev_branch "Archived")
# No valid git commit date, use today
string(TIMESTAMP rev_date_fallback "%Y-%m-%d %H:%M:%S" UTC)
else()
# We have valid date from git commit, use that
set(rev_date_fallback ${rev_date})
# Extract information required to build a proper versionstring
string(REGEX REPLACE 0.1-|[0-9]+-g "" rev_hash ${rev_info})
endif()
endif()
# For package/copyright information we always need a proper date - keep "Archived/1970" for displaying git info but a valid year elsewhere
string(REGEX MATCH "([0-9]+)-([0-9]+)-([0-9]+)" rev_date_fallback_match ${rev_date_fallback})
set(rev_year ${CMAKE_MATCH_1})
set(rev_month ${CMAKE_MATCH_2})
set(rev_day ${CMAKE_MATCH_3})
# Create the actual revision.h file from the above params
if(NOT "${rev_hash_cached}" STREQUAL "${rev_hash}" OR NOT "${rev_branch_cached}" STREQUAL "${rev_branch}" OR NOT EXISTS "${BUILDDIR}/revision.h")
configure_file(
"${CMAKE_SOURCE_DIR}/src/cmake/revision.h.in.cmake"
"${BUILDDIR}/revision.h"
@ONLY
)
set(rev_hash_cached "${rev_hash}" CACHE INTERNAL "Cached commit-hash")
set(rev_branch_cached "${rev_branch}" CACHE INTERNAL "Cached branch name")
endif()

View File

@@ -0,0 +1,32 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# code copied from https://crascit.com/2015/07/25/cmake-gtest/
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(googletest-download NONE)
include(ExternalProject)
ExternalProject_Add(
googletest
SOURCE_DIR "@GOOGLETEST_DOWNLOAD_ROOT@/googletest-src"
BINARY_DIR "@GOOGLETEST_DOWNLOAD_ROOT@/googletest-build"
GIT_REPOSITORY
https://github.com/google/googletest.git
GIT_TAG
release-1.12.1
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)

View File

@@ -0,0 +1,32 @@
# the following code to fetch googletest
# is inspired by and adapted after https://crascit.com/2015/07/25/cmake-gtest/
# download and unpack googletest at configure time
macro(fetch_googletest _download_module_path _download_root)
set(GOOGLETEST_DOWNLOAD_ROOT ${_download_root})
configure_file(
${_download_module_path}/googletest-download.cmake
${_download_root}/CMakeLists.txt
@ONLY
)
unset(GOOGLETEST_DOWNLOAD_ROOT)
execute_process(
COMMAND
"${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" .
WORKING_DIRECTORY
${_download_root}
)
execute_process(
COMMAND
"${CMAKE_COMMAND}" --build .
WORKING_DIRECTORY
${_download_root}
)
# adds the targers: gtest, gtest_main, gmock, gmock_main
add_subdirectory(
${_download_root}/googletest-src
${_download_root}/googletest-build
)
endmacro()

View File

@@ -0,0 +1,73 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Collects all source files into the given variable,
# which is useful to include all sources in subdirectories.
# Ignores full qualified directories listed in the variadic arguments.
#
# Use it like:
# CollectSourceFiles(
# ${CMAKE_CURRENT_SOURCE_DIR}
# COMMON_PRIVATE_SOURCES
# # Exclude
# ${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders
# ${CMAKE_CURRENT_SOURCE_DIR}/Platform)
#
function(CollectSourceFiles current_dir variable)
list(FIND ARGN "${current_dir}" IS_EXCLUDED)
if(IS_EXCLUDED EQUAL -1)
file(GLOB COLLECTED_SOURCES
${current_dir}/*.c
${current_dir}/*.cc
${current_dir}/*.cpp
${current_dir}/*.inl
${current_dir}/*.def
${current_dir}/*.h
${current_dir}/*.hh
${current_dir}/*.hpp)
list(APPEND ${variable} ${COLLECTED_SOURCES})
file(GLOB SUB_DIRECTORIES ${current_dir}/*)
foreach(SUB_DIRECTORY ${SUB_DIRECTORIES})
if (IS_DIRECTORY ${SUB_DIRECTORY})
CollectSourceFiles("${SUB_DIRECTORY}" "${variable}" "${ARGN}")
endif()
endforeach()
set(${variable} ${${variable}} PARENT_SCOPE)
endif()
endfunction()
# Collects all subdirectoroies into the given variable,
# which is useful to include all subdirectories.
# Ignores full qualified directories listed in the variadic arguments.
#
# Use it like:
# CollectIncludeDirectories(
# ${CMAKE_CURRENT_SOURCE_DIR}
# COMMON_PUBLIC_INCLUDES
# # Exclude
# ${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders
# ${CMAKE_CURRENT_SOURCE_DIR}/Platform)
#
function(CollectIncludeDirectories current_dir variable)
list(FIND ARGN "${current_dir}" IS_EXCLUDED)
if(IS_EXCLUDED EQUAL -1)
list(APPEND ${variable} ${current_dir})
file(GLOB SUB_DIRECTORIES ${current_dir}/*)
foreach(SUB_DIRECTORY ${SUB_DIRECTORIES})
if (IS_DIRECTORY ${SUB_DIRECTORY})
CollectIncludeDirectories("${SUB_DIRECTORY}" "${variable}" "${ARGN}")
endif()
endforeach()
set(${variable} ${${variable}} PARENT_SCOPE)
endif()
endfunction()

View File

@@ -0,0 +1,25 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
#
# Force out-of-source build
#
string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" BUILDING_IN_SOURCE)
if( BUILDING_IN_SOURCE )
message(FATAL_ERROR "
This project requires an out of source build. Remove the file 'CMakeCache.txt'
found in this directory before continuing, create a separate build directory
and run 'cmake path_to_project [options]' from there.
")
endif()

View File

@@ -0,0 +1,30 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# check what platform we're on (64-bit or 32-bit), and create a simpler test than CMAKE_SIZEOF_VOID_P
if(CMAKE_SIZEOF_VOID_P MATCHES 8)
set(PLATFORM 64)
MESSAGE(STATUS "Detected 64-bit platform")
else()
set(PLATFORM 32)
MESSAGE(STATUS "Detected 32-bit platform")
endif()
include("${CMAKE_SOURCE_DIR}/src/cmake/platform/settings.cmake")
if(WIN32)
include("${CMAKE_SOURCE_DIR}/src/cmake/platform/win/settings.cmake")
elseif(UNIX)
include("${CMAKE_SOURCE_DIR}/src/cmake/platform/unix/settings.cmake")
endif()
include("${CMAKE_SOURCE_DIR}/src/cmake/platform/after_platform.cmake")

View File

@@ -0,0 +1,106 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
#
# Use it like:
# CopyApplicationConfig(${APP_PROJECT_NAME} ${APPLICATION_NAME})
#
function(CopyApplicationConfig projectName appName)
GetPathToApplication(${appName} SOURCE_APP_PATH)
if(WIN32)
if("${CMAKE_MAKE_PROGRAM}" MATCHES "MSBuild")
add_custom_command(TARGET ${projectName}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bin/$(ConfigurationName)/configs")
add_custom_command(TARGET ${projectName}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "${SOURCE_APP_PATH}/${appName}.conf.dist" "${CMAKE_BINARY_DIR}/bin/$(ConfigurationName)/configs")
elseif(MINGW)
add_custom_command(TARGET ${servertype}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bin/configs")
add_custom_command(TARGET ${servertype}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "${SOURCE_APP_PATH}/${appName}.conf.dist ${CMAKE_BINARY_DIR}/bin/configs")
endif()
endif()
if(UNIX)
install(FILES "${SOURCE_APP_PATH}/${appName}.conf.dist" DESTINATION "${CONF_DIR}")
elseif(WIN32)
install(FILES "${SOURCE_APP_PATH}/${appName}.conf.dist" DESTINATION "${CMAKE_INSTALL_PREFIX}/configs")
endif()
endfunction()
function(CopyToolConfig projectName appName)
GetPathToTool(${appName} SOURCE_APP_PATH)
if(WIN32)
if("${CMAKE_MAKE_PROGRAM}" MATCHES "MSBuild")
add_custom_command(TARGET ${projectName}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bin/$(ConfigurationName)/configs")
add_custom_command(TARGET ${projectName}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "${SOURCE_APP_PATH}/${appName}.conf.dist" "${CMAKE_BINARY_DIR}/bin/$(ConfigurationName)/configs")
elseif(MINGW)
add_custom_command(TARGET ${servertype}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bin/configs")
add_custom_command(TARGET ${servertype}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "${SOURCE_APP_PATH}/${appName}.conf.dist ${CMAKE_BINARY_DIR}/bin/configs")
endif()
endif()
if(UNIX)
install(FILES "${SOURCE_APP_PATH}/${appName}.conf.dist" DESTINATION "${CONF_DIR}")
elseif(WIN32)
install(FILES "${SOURCE_APP_PATH}/${appName}.conf.dist" DESTINATION "${CMAKE_INSTALL_PREFIX}/configs")
endif()
endfunction()
#
# Use it like:
# CopyModuleConfig("acore.conf.dist")
#
function(CopyModuleConfig configDir)
set(postPath "configs/modules")
if(WIN32)
if("${CMAKE_MAKE_PROGRAM}" MATCHES "MSBuild")
add_custom_command(TARGET modules
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bin/$(ConfigurationName)/${postPath}")
add_custom_command(TARGET modules
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "${configDir}" "${CMAKE_BINARY_DIR}/bin/$(ConfigurationName)/${postPath}")
elseif(MINGW)
add_custom_command(TARGET modules
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bin/${postPath}")
add_custom_command(TARGET modules
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "${configDir} ${CMAKE_BINARY_DIR}/bin/${postPath}")
endif()
endif()
if(UNIX)
install(FILES "${configDir}" DESTINATION "${CONF_DIR}/modules")
elseif(WIN32)
install(FILES "${configDir}" DESTINATION "${CMAKE_INSTALL_PREFIX}/${postPath}")
endif()
unset(postPath)
endfunction()

View File

@@ -0,0 +1,108 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
set(BUILD_APPLICATION_AUTHSERVER 0)
set(BUILD_APPLICATION_WORLDSERVER 0)
# Returns the base path to the apps directory in the source directory
function(GetApplicationsBasePath variable)
set(${variable} "${CMAKE_SOURCE_DIR}/src/server/apps" PARENT_SCOPE)
endfunction()
# Stores the absolut path of the given app in the variable
function(GetPathToApplication app variable)
GetApplicationsBasePath(APPS_BASE_PATH)
set(${variable} "${APPS_BASE_PATH}/${app}" PARENT_SCOPE)
endfunction()
# Stores the project name of the given app in the variable
function(GetProjectNameOfApplicationName app variable)
string(TOLOWER "${app}" GENERATED_NAME)
set(${variable} "${GENERATED_NAME}" PARENT_SCOPE)
endfunction()
# Creates a list of all applications and stores it in the given variable.
function(GetApplicationsList variable)
GetApplicationsBasePath(BASE_PATH)
file(GLOB LOCALE_SOURCE_APP_LIST RELATIVE
${BASE_PATH}
${BASE_PATH}/*)
set(${variable})
foreach(SOURCE_APP ${LOCALE_SOURCE_APP_LIST})
GetPathToApplication(${SOURCE_APP} SOURCE_APP_PATH)
if(IS_DIRECTORY ${SOURCE_APP_PATH})
list(APPEND ${variable} ${SOURCE_APP})
endif()
endforeach()
set(${variable} ${${variable}} PARENT_SCOPE)
endfunction()
# Converts the given application name into it's
# variable name which holds the build type.
function(ApplicationNameToVariable application variable)
string(TOUPPER ${application} ${variable})
set(${variable} "APP_${${variable}}")
set(${variable} ${${variable}} PARENT_SCOPE)
endfunction()
function(CheckApplicationsBuildList)
GetApplicationsList(APPLICATIONS_BUILD_LIST)
if (APPS_BUILD STREQUAL "none")
set(APPS_DEFAULT_BUILD "disabled")
else()
set(APPS_DEFAULT_BUILD "enabled")
endif()
# Sets BUILD_APPS_USE_WHITELIST
# Sets BUILD_APPS_WHITELIST
if (APPS_BUILD MATCHES "-only")
set(BUILD_APPS_USE_WHITELIST ON)
if (APPS_BUILD STREQUAL "auth-only")
list(APPEND BUILD_APPS_WHITELIST authserver)
endif()
if (APPS_BUILD STREQUAL "world-only")
list(APPEND BUILD_APPS_WHITELIST worldserver)
endif()
endif()
foreach(APPLICATION_BUILD_NAME ${APPLICATIONS_BUILD_LIST})
ApplicationNameToVariable(${APPLICATION_BUILD_NAME} APPLICATION_BUILD_VARIABLE)
if(${APPLICATION_BUILD_VARIABLE} STREQUAL "default")
if(BUILD_APPS_USE_WHITELIST)
list(FIND BUILD_APPS_WHITELIST "${APPLICATION_BUILD_NAME}" INDEX)
if(${INDEX} GREATER -1)
set(${APPLICATION_BUILD_VARIABLE} ${APPS_DEFAULT_BUILD})
else()
set(${APPLICATION_BUILD_VARIABLE} "disabled")
endif()
else()
set(${APPLICATION_BUILD_VARIABLE} ${APPS_DEFAULT_BUILD})
endif()
endif()
# Build the Graph values
if(${APPLICATION_BUILD_VARIABLE} MATCHES "enabled")
if (${APPLICATION_BUILD_NAME} MATCHES "authserver")
set (BUILD_APPLICATION_AUTHSERVER 1 PARENT_SCOPE)
elseif(${APPLICATION_BUILD_NAME} MATCHES "worldserver")
set (BUILD_APPLICATION_WORLDSERVER 1 PARENT_SCOPE)
endif()
endif()
endforeach()
endfunction()

View File

@@ -0,0 +1,66 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# An interface library to make the target com available to other targets
add_library(acore-compile-option-interface INTERFACE)
# Use -std=c++11 instead of -std=gnu++11
set(CXX_EXTENSIONS OFF)
# Enable C++20 support
set(CMAKE_CXX_STANDARD 20)
message(STATUS "Enabled С++20 standard")
# An interface library to make the warnings level available to other targets
# This interface taget is set-up through the platform specific script
add_library(acore-warning-interface INTERFACE)
# An interface used for all other interfaces
add_library(acore-default-interface INTERFACE)
target_link_libraries(acore-default-interface
INTERFACE
acore-compile-option-interface)
# An interface used for silencing all warnings
add_library(acore-no-warning-interface INTERFACE)
if (MSVC)
target_compile_options(acore-no-warning-interface
INTERFACE
/W0)
else()
target_compile_options(acore-no-warning-interface
INTERFACE
-w)
endif()
# An interface library to change the default behaviour
# to hide symbols automatically.
add_library(acore-hidden-symbols-interface INTERFACE)
# An interface amalgamation which provides the flags and definitions
# used by the dependency targets.
add_library(acore-dependency-interface INTERFACE)
target_link_libraries(acore-dependency-interface
INTERFACE
acore-default-interface
acore-no-warning-interface
acore-hidden-symbols-interface)
# An interface amalgamation which provides the flags and definitions
# used by the core targets.
add_library(acore-core-interface INTERFACE)
target_link_libraries(acore-core-interface
INTERFACE
acore-default-interface
acore-warning-interface)

View File

@@ -0,0 +1,73 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Returns the base path to the script directory in the source directory
function(GetModulesBasePath variable)
set(${variable} "${CMAKE_SOURCE_DIR}/modules" PARENT_SCOPE)
endfunction()
# Stores the absolut path of the given module in the variable
function(GetPathToModuleSource module variable)
GetModulesBasePath(MODULE_BASE_PATH)
set(${variable} "${MODULE_BASE_PATH}/${module}/src" PARENT_SCOPE)
endfunction()
# Stores the project name of the given module in the variable
function(GetProjectNameOfModuleName module variable)
string(TOLOWER "mod_${SOURCE_MODULE}" GENERATED_NAME)
set(${variable} "${GENERATED_NAME}" PARENT_SCOPE)
endfunction()
# Creates a list of all script modules
# and stores it in the given variable.
function(GetModuleSourceList variable)
GetModulesBasePath(BASE_PATH)
file(GLOB LOCALE_MODULE_LIST RELATIVE
${BASE_PATH}
${BASE_PATH}/*)
set(${variable})
foreach(SOURCE_MODULE ${LOCALE_MODULE_LIST})
GetPathToModuleSource(${SOURCE_MODULE} MODULE_SOURCE_PATH)
if(IS_DIRECTORY ${MODULE_SOURCE_PATH})
list(APPEND ${variable} ${SOURCE_MODULE})
endif()
endforeach()
set(${variable} ${${variable}} PARENT_SCOPE)
endfunction()
# Converts the given script module name into it's
# variable name which holds the linkage type.
function(ModuleNameToVariable module variable)
string(TOUPPER ${module} ${variable})
set(${variable} "MODULE_${${variable}}")
set(${variable} ${${variable}} PARENT_SCOPE)
endfunction()
# Stores in the given variable whether dynamic linking is required
function(IsDynamicLinkingModulesRequired variable)
if(MODULES MATCHES "dynamic")
set(IS_DEFAULT_VALUE_DYNAMIC_MODULE ON)
endif()
GetModuleSourceList(MODULES_MODULE_LIST)
set(IS_REQUIRED OFF)
foreach(SOURCE_MODULE ${MODULES_MODULE_LIST})
ModuleNameToVariable(${SOURCE_MODULE} MODULE_MODULE_VARIABLE)
if((${MODULE_MODULE_VARIABLE} STREQUAL "dynamic") OR
(${MODULE_MODULE_VARIABLE} STREQUAL "default" AND IS_DEFAULT_VALUE_DYNAMIC_MODULE))
set(IS_REQUIRED ON)
break()
endif()
endforeach()
set(${variable} ${IS_REQUIRED} PARENT_SCOPE)
endfunction()

View File

@@ -0,0 +1,108 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Returns the base path to the script directory in the source directory
function(WarnAboutSpacesInBuildPath)
# Only check win32 since unix doesn't allow spaces in paths
if(WIN32)
string(FIND "${CMAKE_BINARY_DIR}" " " SPACE_INDEX_POS)
if(SPACE_INDEX_POS GREATER -1)
message("")
message(WARNING " *** WARNING!\n"
" *** Your selected build directory contains spaces!\n"
" *** Please note that this will cause issues!")
endif()
endif()
endfunction()
# Returns the base path to the script directory in the source directory
function(GetScriptsBasePath variable)
set(${variable} "${CMAKE_SOURCE_DIR}/src/server/scripts" PARENT_SCOPE)
endfunction()
# Stores the absolut path of the given module in the variable
function(GetPathToScriptModule module variable)
GetScriptsBasePath(SCRIPTS_BASE_PATH)
set(${variable} "${SCRIPTS_BASE_PATH}/${module}" PARENT_SCOPE)
endfunction()
# Stores the project name of the given module in the variable
function(GetProjectNameOfScriptModule module variable)
string(TOLOWER "scripts_${SCRIPT_MODULE}" GENERATED_NAME)
set(${variable} "${GENERATED_NAME}" PARENT_SCOPE)
endfunction()
# Creates a list of all script modules
# and stores it in the given variable.
function(GetScriptModuleList variable)
GetScriptsBasePath(BASE_PATH)
file(GLOB LOCALE_SCRIPT_MODULE_LIST RELATIVE
${BASE_PATH}
${BASE_PATH}/*)
set(${variable})
foreach(SCRIPT_MODULE ${LOCALE_SCRIPT_MODULE_LIST})
GetPathToScriptModule(${SCRIPT_MODULE} SCRIPT_MODULE_PATH)
if(IS_DIRECTORY ${SCRIPT_MODULE_PATH})
list(APPEND ${variable} ${SCRIPT_MODULE})
endif()
endforeach()
set(${variable} ${${variable}} PARENT_SCOPE)
endfunction()
# Converts the given script module name into it's
# variable name which holds the linkage type.
function(ScriptModuleNameToVariable module variable)
string(TOUPPER ${module} ${variable})
set(${variable} "SCRIPTS_${${variable}}")
set(${variable} ${${variable}} PARENT_SCOPE)
endfunction()
# Stores in the given variable whether dynamic linking is required
function(IsDynamicLinkingRequired variable)
if(SCRIPTS MATCHES "dynamic")
set(IS_DEFAULT_VALUE_DYNAMIC ON)
endif()
GetScriptModuleList(SCRIPT_MODULE_LIST)
set(IS_REQUIRED OFF)
foreach(SCRIPT_MODULE ${SCRIPT_MODULE_LIST})
ScriptModuleNameToVariable(${SCRIPT_MODULE} SCRIPT_MODULE_VARIABLE)
if((${SCRIPT_MODULE_VARIABLE} STREQUAL "dynamic") OR
(${SCRIPT_MODULE_VARIABLE} STREQUAL "default" AND IS_DEFAULT_VALUE_DYNAMIC))
set(IS_REQUIRED ON)
break()
endif()
endforeach()
set(${variable} ${IS_REQUIRED} PARENT_SCOPE)
endfunction()
# Stores the native variable name
function(GetNativeSharedLibraryName module variable)
if(WIN32)
set(${variable} "${module}.dll" PARENT_SCOPE)
elseif(APPLE)
set(${variable} "lib${module}.dylib" PARENT_SCOPE)
else()
set(${variable} "lib${module}.so" PARENT_SCOPE)
endif()
endfunction()
# Stores the native install path in the variable
function(GetInstallOffset variable)
if(WIN32)
set(${variable} "${CMAKE_INSTALL_PREFIX}/scripts" PARENT_SCOPE)
else()
set(${variable} "${CMAKE_INSTALL_PREFIX}/bin/scripts" PARENT_SCOPE)
endif()
endfunction()

View File

@@ -0,0 +1,110 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
set(BUILD_TOOLS_MAPS 0)
set(BUILD_TOOLS_DB_IMPORT 0)
# Returns the base path to the tools directory in the source directory
function(GetToolsBasePath variable)
set(${variable} "${CMAKE_SOURCE_DIR}/src/tools" PARENT_SCOPE)
endfunction()
# Stores the absolut path of the given tool in the variable
function(GetPathToTool tool variable)
GetToolsBasePath(TOOLS_BASE_PATH)
set(${variable} "${TOOLS_BASE_PATH}/${tool}" PARENT_SCOPE)
endfunction()
# Stores the project name of the given tool in the variable
function(GetProjectNameOfToolName tool variable)
string(TOLOWER "${tool}" GENERATED_NAME)
set(${variable} "${GENERATED_NAME}" PARENT_SCOPE)
endfunction()
# Creates a list of all applications and stores it in the given variable.
function(GetToolsList variable)
GetToolsBasePath(BASE_PATH)
file(GLOB LOCALE_SOURCE_TOOL_LIST RELATIVE
${BASE_PATH}
${BASE_PATH}/*)
set(${variable})
foreach(SOURCE_TOOL ${LOCALE_SOURCE_TOOL_LIST})
GetPathToTool(${SOURCE_TOOL} SOURCE_TOOL_PATH)
if(IS_DIRECTORY ${SOURCE_TOOL_PATH})
list(APPEND ${variable} ${SOURCE_TOOL})
endif()
endforeach()
set(${variable} ${${variable}} PARENT_SCOPE)
endfunction()
# Converts the given application name into it's
# variable name which holds the build type.
function(ToolNameToVariable application variable)
string(TOUPPER ${application} ${variable})
set(${variable} "TOOL_${${variable}}")
set(${variable} ${${variable}} PARENT_SCOPE)
endfunction()
function(CheckToolsBuildList)
GetToolsList(TOOLS_BUILD_LIST)
if (TOOLS_BUILD STREQUAL "none")
set(TOOLS_DEFAULT_BUILD "disabled")
else()
set(TOOLS_DEFAULT_BUILD "enabled")
endif()
# Sets BUILD_TOOLS_USE_WHITELIST
# Sets BUILD_TOOLS_WHITELIST
if (TOOLS_BUILD MATCHES "-only")
set(BUILD_TOOLS_USE_WHITELIST ON)
if (TOOLS_BUILD STREQUAL "maps-only")
list(APPEND BUILD_TOOLS_WHITELIST map_extractor mmaps_generator vmap4_assembler vmap4_extractor)
endif()
if (TOOLS_BUILD STREQUAL "db-only")
list(APPEND BUILD_TOOLS_WHITELIST dbimport)
endif()
endif()
# Set the TOOL_${TOOL_BUILD_NAME} variables from the
# variables set above
foreach(TOOL_BUILD_NAME ${TOOLS_BUILD_LIST})
ToolNameToVariable(${TOOL_BUILD_NAME} TOOL_BUILD_VARIABLE)
if (${TOOL_BUILD_VARIABLE} STREQUAL "default")
if (BUILD_TOOLS_USE_WHITELIST)
list(FIND BUILD_TOOLS_WHITELIST "${TOOL_BUILD_NAME}" INDEX)
if (${INDEX} GREATER -1)
set(${TOOL_BUILD_VARIABLE} ${TOOLS_DEFAULT_BUILD})
else()
set(${TOOL_BUILD_VARIABLE} "disabled")
endif()
else()
set(${TOOL_BUILD_VARIABLE} ${TOOLS_DEFAULT_BUILD})
endif()
endif()
# Build the Graph values
if (${TOOL_BUILD_VARIABLE} MATCHES "enabled")
if (${TOOL_BUILD_NAME} MATCHES "dbimport")
set(BUILD_TOOLS_DB_IMPORT 1 PARENT_SCOPE)
else()
set(BUILD_TOOLS_MAPS 1 PARENT_SCOPE)
endif()
endif()
endforeach()
endfunction()

View File

@@ -0,0 +1,114 @@
# This file defines the following macros for developers to use in ensuring
# that installed software is of the right version:
#
# ENSURE_VERSION - test that a version number is greater than
# or equal to some minimum
# ENSURE_VERSION_RANGE - test that a version number is greater than
# or equal to some minimum and less than some
# maximum
# ENSURE_VERSION2 - deprecated, do not use in new code
#
# ENSURE_VERSION
# This macro compares version numbers of the form "x.y.z" or "x.y"
# ENSURE_VERSION( FOO_MIN_VERSION FOO_VERSION_FOUND FOO_VERSION_OK)
# will set FOO_VERSION_OK to true if FOO_VERSION_FOUND >= FOO_MIN_VERSION
# Leading and trailing text is ok, e.g.
# ENSURE_VERSION( "2.5.31" "flex 2.5.4a" VERSION_OK)
# which means 2.5.31 is required and "flex 2.5.4a" is what was found on the system
# Copyright (c) 2006, David Faure, <faure@kde.org>
# Copyright (c) 2007, Will Stephenson <wstephenson@kde.org>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
# ENSURE_VERSION_RANGE
# This macro ensures that a version number of the form
# "x.y.z" or "x.y" falls within a range defined by
# min_version <= found_version < max_version.
# If this expression holds, FOO_VERSION_OK will be set TRUE
#
# Example: ENSURE_VERSION_RANGE3( "0.1.0" ${FOOCODE_VERSION} "0.7.0" FOO_VERSION_OK )
#
# This macro will break silently if any of x,y,z are greater than 100.
#
# Copyright (c) 2007, Will Stephenson <wstephenson@kde.org>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
# NORMALIZE_VERSION
# Helper macro to convert version numbers of the form "x.y.z"
# to an integer equal to 10^4 * x + 10^2 * y + z
#
# This macro will break silently if any of x,y,z are greater than 100.
#
# Copyright (c) 2006, David Faure, <faure@kde.org>
# Copyright (c) 2007, Will Stephenson <wstephenson@kde.org>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
# CHECK_RANGE_INCLUSIVE_LOWER
# Helper macro to check whether x <= y < z
#
# Copyright (c) 2007, Will Stephenson <wstephenson@kde.org>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
MACRO(NORMALIZE_VERSION _requested_version _normalized_version)
STRING(REGEX MATCH "[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+.*" _threePartMatch "${_requested_version}")
if (_threePartMatch)
# parse the parts of the version string
STRING(REGEX REPLACE "[^0-9]*([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" _major_vers "${_requested_version}")
STRING(REGEX REPLACE "[^0-9]*[0-9]+\\.([0-9]+)\\.[0-9]+.*" "\\1" _minor_vers "${_requested_version}")
STRING(REGEX REPLACE "[^0-9]*[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" _patch_vers "${_requested_version}")
else (_threePartMatch)
STRING(REGEX REPLACE "([0-9]+)\\.[0-9]+" "\\1" _major_vers "${_requested_version}")
STRING(REGEX REPLACE "[0-9]+\\.([0-9]+)" "\\1" _minor_vers "${_requested_version}")
set(_patch_vers "0")
endif (_threePartMatch)
# compute an overall version number which can be compared at once
MATH(EXPR ${_normalized_version} "${_major_vers}*10000 + ${_minor_vers}*100 + ${_patch_vers}")
ENDMACRO(NORMALIZE_VERSION)
MACRO(CHECK_RANGE_INCLUSIVE_LOWER _lower_limit _value _upper_limit _ok)
if (${_value} LESS ${_lower_limit})
set( ${_ok} FALSE )
elseif (${_value} EQUAL ${_lower_limit})
set( ${_ok} TRUE )
elseif (${_value} EQUAL ${_upper_limit})
set( ${_ok} FALSE )
elseif (${_value} GREATER ${_upper_limit})
set( ${_ok} FALSE )
else (${_value} LESS ${_lower_limit})
set( ${_ok} TRUE )
endif (${_value} LESS ${_lower_limit})
ENDMACRO(CHECK_RANGE_INCLUSIVE_LOWER)
MACRO(ENSURE_VERSION requested_version found_version var_too_old)
NORMALIZE_VERSION( ${requested_version} req_vers_num )
NORMALIZE_VERSION( ${found_version} found_vers_num )
if (found_vers_num LESS req_vers_num)
set( ${var_too_old} FALSE )
else (found_vers_num LESS req_vers_num)
set( ${var_too_old} TRUE )
endif (found_vers_num LESS req_vers_num)
ENDMACRO(ENSURE_VERSION)
MACRO(ENSURE_VERSION2 requested_version2 found_version2 var_too_old2)
ENSURE_VERSION( ${requested_version2} ${found_version2} ${var_too_old2})
ENDMACRO(ENSURE_VERSION2)
MACRO(ENSURE_VERSION_RANGE min_version found_version max_version var_ok)
NORMALIZE_VERSION( ${min_version} req_vers_num )
NORMALIZE_VERSION( ${found_version} found_vers_num )
NORMALIZE_VERSION( ${max_version} max_vers_num )
CHECK_RANGE_INCLUSIVE_LOWER( ${req_vers_num} ${found_vers_num} ${max_vers_num} ${var_ok})
ENDMACRO(ENSURE_VERSION_RANGE)

View File

@@ -0,0 +1,243 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#
# Copied from:
# https://github.com/lethal-guitar/RigelEngine/blob/master/cmake/Modules/FindFilesystem.cmake
#
# Which is copied from:
# https://github.com/vector-of-bool/CMakeCM/blob/master/modules/FindFilesystem.cmake
#[=======================================================================[.rst:
FindFilesystem
##############
This module supports the C++17 standard library's filesystem utilities. Use the
:imp-target:`std::filesystem` imported target to
Options
*******
The ``COMPONENTS`` argument to this module supports the following values:
.. find-component:: Experimental
:name: fs.Experimental
Allows the module to find the "experimental" Filesystem TS version of the
Filesystem library. This is the library that should be used with the
``std::experimental::filesystem`` namespace.
.. find-component:: Final
:name: fs.Final
Finds the final C++17 standard version of the filesystem library.
If no components are provided, behaves as if the
:find-component:`fs.Final` component was specified.
If both :find-component:`fs.Experimental` and :find-component:`fs.Final` are
provided, first looks for ``Final``, and falls back to ``Experimental`` in case
of failure. If ``Final`` is found, :imp-target:`std::filesystem` and all
:ref:`variables <fs.variables>` will refer to the ``Final`` version.
Imported Targets
****************
.. imp-target:: std::filesystem
The ``std::filesystem`` imported target is defined when any requested
version of the C++ filesystem library has been found, whether it is
*Experimental* or *Final*.
If no version of the filesystem library is available, this target will not
be defined.
.. note::
This target has ``cxx_std_17`` as an ``INTERFACE``
:ref:`compile language standard feature <req-lang-standards>`. Linking
to this target will automatically enable C++17 if no later standard
version is already required on the linking target.
.. _fs.variables:
Variables
*********
.. variable:: CXX_FILESYSTEM_IS_EXPERIMENTAL
Set to ``TRUE`` when the :find-component:`fs.Experimental` version of C++
filesystem library was found, otherwise ``FALSE``.
.. variable:: CXX_FILESYSTEM_HAVE_FS
Set to ``TRUE`` when a filesystem header was found.
.. variable:: CXX_FILESYSTEM_HEADER
Set to either ``filesystem`` or ``experimental/filesystem`` depending on
whether :find-component:`fs.Final` or :find-component:`fs.Experimental` was
found.
.. variable:: CXX_FILESYSTEM_NAMESPACE
Set to either ``std::filesystem`` or ``std::experimental::filesystem``
depending on whether :find-component:`fs.Final` or
:find-component:`fs.Experimental` was found.
Examples
********
Using `find_package(Filesystem)` with no component arguments:
.. code-block:: cmake
find_package(Filesystem REQUIRED)
add_executable(my-program main.cpp)
target_link_libraries(my-program PRIVATE std::filesystem)
#]=======================================================================]
if(TARGET std::filesystem)
# This module has already been processed. Don't do it again.
return()
endif()
cmake_policy(PUSH)
if(POLICY CMP0067)
# pass CMAKE_CXX_STANDARD to check_cxx_source_compiles()
# has to appear before including CheckCXXSourceCompiles module
cmake_policy(SET CMP0067 NEW)
endif()
include(CMakePushCheckState)
include(CheckIncludeFileCXX)
include(CheckCXXSourceCompiles)
cmake_push_check_state()
set(CMAKE_REQUIRED_QUIET ${Filesystem_FIND_QUIETLY})
# All of our tests required C++17 or later
# But AC already sets this in ConfigureBaseTargets.cmake
# set(CMAKE_CXX_STANDARD 17)
# Normalize and check the component list we were given
set(want_components ${Filesystem_FIND_COMPONENTS})
if(Filesystem_FIND_COMPONENTS STREQUAL "")
set(want_components Final)
endif()
# Warn on any unrecognized components
set(extra_components ${want_components})
list(REMOVE_ITEM extra_components Final Experimental)
foreach(component IN LISTS extra_components)
message(WARNING "Extraneous find_package component for Filesystem: ${component}")
endforeach()
# Detect which of Experimental and Final we should look for
set(find_experimental TRUE)
set(find_final TRUE)
if(NOT "Final" IN_LIST want_components)
set(find_final FALSE)
endif()
if(NOT "Experimental" IN_LIST want_components)
set(find_experimental FALSE)
endif()
if(find_final)
check_include_file_cxx("filesystem" _CXX_FILESYSTEM_HAVE_HEADER)
mark_as_advanced(_CXX_FILESYSTEM_HAVE_HEADER)
if(_CXX_FILESYSTEM_HAVE_HEADER)
# We found the non-experimental header. Don't bother looking for the
# experimental one.
set(find_experimental FALSE)
endif()
else()
set(_CXX_FILESYSTEM_HAVE_HEADER FALSE)
endif()
if(find_experimental)
check_include_file_cxx("experimental/filesystem" _CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER)
mark_as_advanced(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER)
else()
set(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER FALSE)
endif()
if(_CXX_FILESYSTEM_HAVE_HEADER)
set(_have_fs TRUE)
set(_fs_header filesystem)
set(_fs_namespace std::filesystem)
elseif(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER)
set(_have_fs TRUE)
set(_fs_header experimental/filesystem)
set(_fs_namespace std::experimental::filesystem)
else()
set(_have_fs FALSE)
endif()
set(CXX_FILESYSTEM_HAVE_FS ${_have_fs} CACHE BOOL "TRUE if we have the C++ filesystem headers")
set(CXX_FILESYSTEM_HEADER ${_fs_header} CACHE STRING "The header that should be included to obtain the filesystem APIs")
set(CXX_FILESYSTEM_NAMESPACE ${_fs_namespace} CACHE STRING "The C++ namespace that contains the filesystem APIs")
set(_found FALSE)
if(CXX_FILESYSTEM_HAVE_FS)
# We have some filesystem library available. Do link checks
string(CONFIGURE [[
#include <@CXX_FILESYSTEM_HEADER@>
int main() {
auto cwd = @CXX_FILESYSTEM_NAMESPACE@::current_path();
return static_cast<int>(cwd.string().size());
}
]] code @ONLY)
# Try to compile a simple filesystem program without any linker flags
check_cxx_source_compiles("${code}" CXX_FILESYSTEM_NO_LINK_NEEDED)
set(can_link ${CXX_FILESYSTEM_NO_LINK_NEEDED})
if(NOT CXX_FILESYSTEM_NO_LINK_NEEDED)
set(prev_libraries ${CMAKE_REQUIRED_LIBRARIES})
# Add the libstdc++ flag
set(CMAKE_REQUIRED_LIBRARIES ${prev_libraries} -lstdc++fs)
check_cxx_source_compiles("${code}" CXX_FILESYSTEM_STDCPPFS_NEEDED)
set(can_link ${CXX_FILESYSTEM_STDCPPFS_NEEDED})
if(NOT CXX_FILESYSTEM_STDCPPFS_NEEDED)
# Try the libc++ flag
set(CMAKE_REQUIRED_LIBRARIES ${prev_libraries} -lc++fs)
check_cxx_source_compiles("${code}" CXX_FILESYSTEM_CPPFS_NEEDED)
set(can_link ${CXX_FILESYSTEM_CPPFS_NEEDED})
endif()
endif()
if(can_link)
add_library(std::filesystem INTERFACE IMPORTED)
target_compile_features(std::filesystem INTERFACE cxx_std_17)
set(_found TRUE)
if(CXX_FILESYSTEM_NO_LINK_NEEDED)
# on certain linux distros we have a version of libstdc++ which has the final code for c++17 fs in the
# libstdc++.so.*. BUT when compiling with g++ < 9, we MUST still link with libstdc++fs.a
# libc++ should not suffer from this issue, so, in theory we should be fine with only checking for
# GCC's libstdc++
if((CMAKE_CXX_COMPILER_ID MATCHES "GNU") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.0.0"))
target_link_libraries(std::filesystem INTERFACE -lstdc++fs)
endif()
elseif(CXX_FILESYSTEM_STDCPPFS_NEEDED)
target_link_libraries(std::filesystem INTERFACE -lstdc++fs)
elseif(CXX_FILESYSTEM_CPPFS_NEEDED)
target_link_libraries(std::filesystem INTERFACE -lc++fs)
endif()
endif()
endif()
cmake_pop_check_state()
set(Filesystem_FOUND ${_found} CACHE BOOL "TRUE if we can compile and link a program using std::filesystem" FORCE)
if(Filesystem_FIND_REQUIRED AND NOT Filesystem_FOUND)
message(FATAL_ERROR "Cannot Compile simple program using std::filesystem")
endif()
cmake_policy(POP)

View File

@@ -0,0 +1,49 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
include(${CMAKE_SOURCE_DIR}/src/cmake/macros/EnsureVersion.cmake)
set(_REQUIRED_GIT_VERSION "1.7")
find_program(GIT_EXECUTABLE
NAMES
git git.cmd
HINTS
ENV PATH
DOC "Full path to git commandline client"
)
MARK_AS_ADVANCED(GIT_EXECUTABLE)
if(NOT GIT_EXECUTABLE)
message(FATAL_ERROR "
Git was NOT FOUND on your system - did you forget to install a recent version, or setting the path to it?
Observe that for revision hash/date to work you need at least version ${_REQUIRED_GIT_VERSION}")
else()
message(STATUS "Found git binary : ${GIT_EXECUTABLE}")
execute_process(
COMMAND "${GIT_EXECUTABLE}" --version
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE _GIT_VERSION
ERROR_QUIET
)
# make sure we're using minimum the required version of git, so the "dirty-testing" will work properly
ensure_version( "${_REQUIRED_GIT_VERSION}" "${_GIT_VERSION}" _GIT_VERSION_OK)
# throw an error if we don't have a recent enough version of git...
if(NOT _GIT_VERSION_OK)
message(STATUS "Git version too old : ${_GIT_VERSION}")
message(FATAL_ERROR "
Git was found but is OUTDATED - did you forget to install a recent version, or setting the path to it?
Observe that for revision hash/date to work you need at least version ${_REQUIRED_GIT_VERSION}")
endif()
endif()

View File

@@ -0,0 +1,51 @@
# Tries to find Gperftools.
#
# Usage of this module as follows:
#
# find_package(Gperftools)
#
# Variables used by this module, they can change the default behaviour and need
# to be set before calling find_package:
#
# Gperftools_ROOT_DIR Set this variable to the root installation of
# Gperftools if the module has problems finding
# the proper installation path.
#
# Variables defined by this module:
#
# GPERFTOOLS_FOUND System has Gperftools libs/headers
# GPERFTOOLS_LIBRARIES The Gperftools libraries (tcmalloc & profiler)
# GPERFTOOLS_INCLUDE_DIR The location of Gperftools headers
find_library(GPERFTOOLS_TCMALLOC
NAMES tcmalloc
HINTS ${Gperftools_ROOT_DIR}/lib)
find_library(GPERFTOOLS_PROFILER
NAMES profiler
HINTS ${Gperftools_ROOT_DIR}/lib)
find_library(GPERFTOOLS_TCMALLOC_AND_PROFILER
NAMES tcmalloc_and_profiler
HINTS ${Gperftools_ROOT_DIR}/lib)
find_path(GPERFTOOLS_INCLUDE_DIR
NAMES gperftools/heap-profiler.h
HINTS ${Gperftools_ROOT_DIR}/include)
set(GPERFTOOLS_LIBRARIES ${GPERFTOOLS_TCMALLOC_AND_PROFILER})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(
Gperftools
DEFAULT_MSG
GPERFTOOLS_LIBRARIES
GPERFTOOLS_INCLUDE_DIR)
mark_as_advanced(
Gperftools_ROOT_DIR
GPERFTOOLS_TCMALLOC
GPERFTOOLS_PROFILER
GPERFTOOLS_TCMALLOC_AND_PROFILER
GPERFTOOLS_LIBRARIES
GPERFTOOLS_INCLUDE_DIR)

View File

@@ -0,0 +1,273 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
#
# Find the MySQL client includes and library
#
# This module defines
# MYSQL_INCLUDE_DIR, where to find mysql.h
# MYSQL_LIBRARIES, the libraries to link against to connect to MySQL
# MYSQL_FOUND, if false, you cannot build anything that requires MySQL.
# also defined, but not for general use are
# MYSQL_LIBRARY, where to find the MySQL library.
set( MYSQL_FOUND 0 )
# Find MariaDB for Windows
if (WIN32)
# Set know versions MariaDB
set(_MARIADB_KNOWN_VERSIONS "MariaDB 10.9" "MariaDB 10.8" "MariaDB 10.7" "MariaDB 10.6" "MariaDB 10.5")
# Set default options
set(MARIADB_FOUND_LIB 0)
set(MARIADB_FOUND_INCLUDE 0)
set(MARIADB_FOUND_EXECUTABLE 0)
set(MARIADB_FOUND 0)
macro(FindLibMariaDB MariaDBVersion)
# Find include
find_path(MYSQL_INCLUDE_DIR
NAMES
mysql.h
PATHS
${MYSQL_ADD_INCLUDE_PATH}
"$ENV{ProgramW6432}/${MariaDBVersion}/include/mysql"
"$ENV{ProgramFiles}/${MariaDBVersion}/include/mysql"
"${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/include/mysql"
DOC
"Specify the directory containing mysql.h."
)
if(MYSQL_INCLUDE_DIR)
set(MARIADB_FOUND_INCLUDE 1)
endif()
find_library(MYSQL_LIBRARY
NAMES
libmariadb
PATHS
${MYSQL_ADD_LIBRARIES_PATH}
"$ENV{ProgramW6432}/${MariaDBVersion}/lib"
"$ENV{ProgramW6432}/${MariaDBVersion}/lib/opt"
"$ENV{ProgramFiles}/${MariaDBVersion}/lib"
"$ENV{ProgramFiles}/${MariaDBVersion}/lib/opt"
"$ENV{SystemDrive}/${MariaDBVersion}/lib/opt"
"${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/lib"
DOC
"Specify the location of the mysql library here."
)
if(MYSQL_LIBRARY)
set(MARIADB_FOUND_LIB 1)
endif()
find_program(MYSQL_EXECUTABLE mysql
PATHS
"$ENV{ProgramW6432}/${MariaDBVersion}/bin"
"$ENV{ProgramW6432}/${MariaDBVersion}/bin/opt"
"$ENV{ProgramFiles}/${MariaDBVersion}/bin"
"$ENV{ProgramFiles}/${MariaDBVersion}/bin/opt"
"$ENV{SystemDrive}/${MariaDBVersion}/bin/opt"
DOC
"path to your mysql binary.")
if (MYSQL_LIBRARY AND MYSQL_INCLUDE_DIR AND MYSQL_EXECUTABLE)
set(MARIADB_FOUND 1)
endif()
endmacro(FindLibMariaDB)
foreach(version ${_MARIADB_KNOWN_VERSIONS})
if (NOT MARIADB_FOUND)
FindLibMariaDB(${version})
endif()
endforeach()
endif()
if( UNIX )
set(MYSQL_CONFIG_PREFER_PATH "$ENV{MYSQL_HOME}/bin" CACHE FILEPATH
"preferred path to MySQL (mysql_config)"
)
find_program(MYSQL_CONFIG mysql_config
${MYSQL_CONFIG_PREFER_PATH}
/usr/local/mysql/bin/
/usr/local/bin/
/usr/bin/
)
if( MYSQL_CONFIG )
message(STATUS "Using mysql-config: ${MYSQL_CONFIG}")
# set INCLUDE_DIR
exec_program(${MYSQL_CONFIG}
ARGS --include
OUTPUT_VARIABLE MY_TMP
)
string(REGEX REPLACE "-I([^ ]*)( .*)?" "\\1" MY_TMP "${MY_TMP}")
set(MYSQL_ADD_INCLUDE_PATH ${MY_TMP} CACHE FILEPATH INTERNAL)
#message("[DEBUG] MYSQL ADD_INCLUDE_PATH : ${MYSQL_ADD_INCLUDE_PATH}")
# set LIBRARY_DIR
exec_program(${MYSQL_CONFIG}
ARGS --libs_r
OUTPUT_VARIABLE MY_TMP
)
set(MYSQL_ADD_LIBRARIES "")
string(REGEX MATCHALL "-l[^ ]*" MYSQL_LIB_LIST "${MY_TMP}")
foreach(LIB ${MYSQL_LIB_LIST})
string(REGEX REPLACE "[ ]*-l([^ ]*)" "\\1" LIB "${LIB}")
list(APPEND MYSQL_ADD_LIBRARIES "${LIB}")
#message("[DEBUG] MYSQL ADD_LIBRARIES : ${MYSQL_ADD_LIBRARIES}")
endforeach(LIB ${MYSQL_LIB_LIST})
set(MYSQL_ADD_LIBRARIES_PATH "")
string(REGEX MATCHALL "-L[^ ]*" MYSQL_LIBDIR_LIST "${MY_TMP}")
foreach(LIB ${MYSQL_LIBDIR_LIST})
string(REGEX REPLACE "[ ]*-L([^ ]*)" "\\1" LIB "${LIB}")
list(APPEND MYSQL_ADD_LIBRARIES_PATH "${LIB}")
#message("[DEBUG] MYSQL ADD_LIBRARIES_PATH : ${MYSQL_ADD_LIBRARIES_PATH}")
endforeach(LIB ${MYSQL_LIBS})
else( MYSQL_CONFIG )
set(MYSQL_ADD_LIBRARIES "")
list(APPEND MYSQL_ADD_LIBRARIES "mysqlclient_r")
endif( MYSQL_CONFIG )
endif( UNIX )
find_path(MYSQL_INCLUDE_DIR
NAMES
mysql.h
PATHS
${MYSQL_ADD_INCLUDE_PATH}
/usr/include
/usr/include/mysql
/usr/local/include
/usr/local/include/mysql
/usr/local/mysql/include
"C:/tools/mysql/current/include" # chocolatey package
"$ENV{ProgramW6432}/MySQL/MySQL Server 8.1/include"
"$ENV{ProgramW6432}/MySQL/MySQL Server 8.0/include"
"$ENV{ProgramW6432}/MySQL/MySQL Server 5.7/include"
"$ENV{ProgramFiles}/MySQL/MySQL Server 8.1/include"
"$ENV{ProgramFiles}/MySQL/MySQL Server 8.0/include"
"$ENV{ProgramFiles}/MySQL/MySQL Server 5.7/include"
"$ENV{SystemDrive}/MySQL/MySQL Server 8.1/include"
"$ENV{SystemDrive}/MySQL/MySQL Server 8.0/include"
"$ENV{SystemDrive}/MySQL/MySQL Server 5.7/include"
"$ENV{MYSQL_INCLUDE_DIR}"
"$ENV{MYSQL_DIR}/include"
DOC
"Specify the directory containing mysql.h."
)
if( UNIX )
foreach(LIB ${MYSQL_ADD_LIBRARIES})
find_library( MYSQL_LIBRARY
NAMES
mysql libmysql ${LIB}
PATHS
${MYSQL_ADD_LIBRARIES_PATH}
/usr/lib
/usr/lib/mysql
/usr/local/lib
/usr/local/lib/mysql
/usr/local/mysql/lib
DOC "Specify the location of the mysql library here."
)
endforeach(LIB ${MYSQL_ADD_LIBRARY})
endif( UNIX )
if( WIN32 )
find_library( MYSQL_LIBRARY
NAMES
libmysql
PATHS
${MYSQL_ADD_LIBRARIES_PATH}
"C:/tools/mysql/current/lib" # chocolatey package
"$ENV{ProgramW6432}/MySQL/MySQL Server 8.1/lib"
"$ENV{ProgramW6432}/MySQL/MySQL Server 8.0/lib"
"$ENV{ProgramW6432}/MySQL/MySQL Server 5.7/lib"
"$ENV{ProgramFiles}/MySQL/MySQL Server 8.1/lib"
"$ENV{ProgramFiles}/MySQL/MySQL Server 8.0/lib"
"$ENV{ProgramFiles}/MySQL/MySQL Server 5.7/lib"
"$ENV{SystemDrive}/MySQL/MySQL Server 8.1/lib"
"$ENV{SystemDrive}/MySQL/MySQL Server 8.0/lib"
"$ENV{SystemDrive}/MySQL/MySQL Server 5.7/lib"
"$ENV{MYSQL_LIBRARY}"
"$ENV{MYSQL_DIR}/lib"
DOC "Specify the location of the mysql library here."
)
endif( WIN32 )
# On Windows you typically don't need to include any extra libraries
# to build MYSQL stuff.
if( NOT WIN32 )
find_library( MYSQL_EXTRA_LIBRARIES
NAMES
z zlib
PATHS
/usr/lib
/usr/local/lib
DOC
"if more libraries are necessary to link in a MySQL client (typically zlib), specify them here."
)
else( NOT WIN32 )
set( MYSQL_EXTRA_LIBRARIES "" )
endif( NOT WIN32 )
if( UNIX )
find_program(MYSQL_EXECUTABLE mysql
PATHS
${MYSQL_CONFIG_PREFER_PATH}
/usr/local/mysql/bin/
/usr/local/bin/
/usr/bin/
DOC
"path to your mysql binary."
)
endif( UNIX )
if( WIN32 )
find_program(MYSQL_EXECUTABLE mysql
PATHS
"C:/tools/mysql/current/bin" # chocolatey package
"$ENV{ProgramW6432}/MySQL/MySQL Server 8.1/bin"
"$ENV{ProgramW6432}/MySQL/MySQL Server 8.0/bin"
"$ENV{ProgramW6432}/MySQL/MySQL Server 5.7/bin"
"$ENV{ProgramFiles}/MySQL/MySQL Server 8.1/bin"
"$ENV{ProgramFiles}/MySQL/MySQL Server 8.0/bin"
"$ENV{ProgramFiles}/MySQL/MySQL Server 5.7/bin"
"$ENV{SystemDrive}/MySQL/MySQL Server 8.1/bin"
"$ENV{SystemDrive}/MySQL/MySQL Server 8.0/bin"
"$ENV{SystemDrive}/MySQL/MySQL Server 5.7/bin"
"$ENV{MYSQL_ROOT}/bin"
DOC
"path to your mysql binary.")
endif( WIN32 )
if( MYSQL_LIBRARY )
if( MYSQL_INCLUDE_DIR )
set( MYSQL_FOUND 1 )
message(STATUS "Found MySQL library: ${MYSQL_LIBRARY}")
message(STATUS "Found MySQL headers: ${MYSQL_INCLUDE_DIR}")
else( MYSQL_INCLUDE_DIR )
message(FATAL_ERROR "Could not find MySQL headers! Please install the development libraries and headers")
endif( MYSQL_INCLUDE_DIR )
if( MYSQL_EXECUTABLE )
message(STATUS "Found MySQL executable: ${MYSQL_EXECUTABLE}")
endif( MYSQL_EXECUTABLE )
mark_as_advanced( MYSQL_FOUND MYSQL_LIBRARY MYSQL_EXTRA_LIBRARIES MYSQL_INCLUDE_DIR MYSQL_EXECUTABLE )
else( MYSQL_LIBRARY )
message(FATAL_ERROR "Could not find the MySQL libraries! Please install the development libraries and headers")
endif( MYSQL_LIBRARY )

View File

@@ -0,0 +1,671 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
FindOpenSSL
-----------
Find the OpenSSL encryption library.
Optional COMPONENTS
^^^^^^^^^^^^^^^^^^^
This module supports two optional COMPONENTS: ``Crypto`` and ``SSL``. Both
components have associated imported targets, as described below.
Imported Targets
^^^^^^^^^^^^^^^^
This module defines the following :prop_tgt:`IMPORTED` targets:
``OpenSSL::SSL``
The OpenSSL ``ssl`` library, if found.
``OpenSSL::Crypto``
The OpenSSL ``crypto`` library, if found.
``OpenSSL::applink``
The OpenSSL ``applink`` components that might be need to be compiled into
projects under MSVC. This target is available only if found OpenSSL version
is not less than 0.9.8. By linking this target the above OpenSSL targets can
be linked even if the project has different MSVC runtime configurations with
the above OpenSSL targets. This target has no effect on platforms other than
MSVC.
NOTE: Due to how ``INTERFACE_SOURCES`` are consumed by the consuming target,
unless you certainly know what you are doing, it is always preferred to link
``OpenSSL::applink`` target as ``PRIVATE`` and to make sure that this target is
linked at most once for the whole dependency graph of any library or
executable:
.. code-block:: cmake
target_link_libraries(myTarget PRIVATE OpenSSL::applink)
Otherwise you would probably encounter unexpected random problems when building
and linking, as both the ISO C and the ISO C++ standard claims almost nothing
about what a link process should be.
Result Variables
^^^^^^^^^^^^^^^^
This module will set the following variables in your project:
``OPENSSL_FOUND``
System has the OpenSSL library. If no components are requested it only
requires the crypto library.
``OPENSSL_INCLUDE_DIR``
The OpenSSL include directory.
``OPENSSL_CRYPTO_LIBRARY``
The OpenSSL crypto library.
``OPENSSL_CRYPTO_LIBRARIES``
The OpenSSL crypto library and its dependencies.
``OPENSSL_SSL_LIBRARY``
The OpenSSL SSL library.
``OPENSSL_SSL_LIBRARIES``
The OpenSSL SSL library and its dependencies.
``OPENSSL_LIBRARIES``
All OpenSSL libraries and their dependencies.
``OPENSSL_VERSION``
This is set to ``$major.$minor.$revision$patch`` (e.g. ``0.9.8s``).
``OPENSSL_APPLINK_SOURCE``
The sources in the target ``OpenSSL::applink`` that is mentioned above. This
variable shall always be undefined if found openssl version is less than
0.9.8 or if platform is not MSVC.
Hints
^^^^^
Set ``OPENSSL_ROOT_DIR`` to the root directory of an OpenSSL installation.
Set ``OPENSSL_USE_STATIC_LIBS`` to ``TRUE`` to look for static libraries.
Set ``OPENSSL_MSVC_STATIC_RT`` set ``TRUE`` to choose the MT version of the lib.
#]=======================================================================]
set(OPENSSL_EXPECTED_VERSION "1.0")
macro(_OpenSSL_test_and_find_dependencies ssl_library crypto_library)
if((CMAKE_SYSTEM_NAME STREQUAL "Linux") AND
(("${ssl_library}" MATCHES "\\${CMAKE_STATIC_LIBRARY_SUFFIX}$") OR
("${crypto_library}" MATCHES "\\${CMAKE_STATIC_LIBRARY_SUFFIX}$")))
set(_OpenSSL_has_dependencies TRUE)
find_package(Threads)
else()
set(_OpenSSL_has_dependencies FALSE)
endif()
endmacro()
function(_OpenSSL_add_dependencies libraries_var)
if(CMAKE_THREAD_LIBS_INIT)
list(APPEND ${libraries_var} ${CMAKE_THREAD_LIBS_INIT})
endif()
list(APPEND ${libraries_var} ${CMAKE_DL_LIBS})
set(${libraries_var} ${${libraries_var}} PARENT_SCOPE)
endfunction()
function(_OpenSSL_target_add_dependencies target)
if(_OpenSSL_has_dependencies)
set_property( TARGET ${target} APPEND PROPERTY INTERFACE_LINK_LIBRARIES Threads::Threads )
set_property( TARGET ${target} APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${CMAKE_DL_LIBS} )
endif()
endfunction()
if (UNIX)
find_package(PkgConfig QUIET)
pkg_check_modules(_OPENSSL QUIET openssl)
endif ()
# Support preference of static libs by adjusting CMAKE_FIND_LIBRARY_SUFFIXES
if(OPENSSL_USE_STATIC_LIBS)
set(_openssl_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
if(WIN32)
set(CMAKE_FIND_LIBRARY_SUFFIXES .lib .a ${CMAKE_FIND_LIBRARY_SUFFIXES})
else()
set(CMAKE_FIND_LIBRARY_SUFFIXES .a )
endif()
endif()
if (WIN32)
# http://www.slproweb.com/products/Win32OpenSSL.html
set(_OPENSSL_MSI_INSTALL_GUID "")
if(PLATFORM EQUAL 64)
set(_OPENSSL_MSI_INSTALL_GUID "117551DB-A110-4BBD-BB05-CFE0BCB3ED31")
set(_OPENSSL_ROOT_HINTS
"[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1;Inno Setup: App Path]"
${OPENSSL_ROOT_DIR}
ENV OPENSSL_ROOT_DIR
)
file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles)
set(_OPENSSL_ROOT_PATHS
"${_programfiles}/OpenSSL"
"${_programfiles}/OpenSSL-Win64"
"C:/OpenSSL/"
"C:/OpenSSL-Win64/"
)
else()
set(_OPENSSL_MSI_INSTALL_GUID "A1EEC576-43B9-4E75-9E02-03DA542D2A38")
set(_OPENSSL_ROOT_HINTS
"[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (32-bit)_is1;Inno Setup: App Path]"
${OPENSSL_ROOT_DIR}
ENV OPENSSL_ROOT_DIR
)
file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles)
set(_OPENSSL_ROOT_PATHS
"${_programfiles}/OpenSSL"
"${_programfiles}/OpenSSL-Win32"
"C:/OpenSSL/"
"C:/OpenSSL-Win32/"
)
endif()
unset(_programfiles)
# If OpenSSL was installed using .msi package instead of .exe, Inno Setup registry values are not written to Uninstall\OpenSSL
# but because it is only a shim around Inno Setup it does write the location of uninstaller which we can use to determine path
get_filename_component(_OPENSSL_MSI_INSTALL_PATH "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Inno Setup MSIs\\${_OPENSSL_MSI_INSTALL_GUID};]" DIRECTORY)
if(NOT _OPENSSL_MSI_INSTALL_PATH STREQUAL "/")
list(INSERT _OPENSSL_ROOT_HINTS 0 ${_OPENSSL_MSI_INSTALL_PATH})
endif()
else ()
set(_OPENSSL_ROOT_HINTS
${OPENSSL_ROOT_DIR}
ENV OPENSSL_ROOT_DIR
)
endif ()
set(_OPENSSL_ROOT_HINTS_AND_PATHS
HINTS ${_OPENSSL_ROOT_HINTS}
PATHS ${_OPENSSL_ROOT_PATHS}
)
find_path(OPENSSL_INCLUDE_DIR
NAMES
openssl/ssl.h
${_OPENSSL_ROOT_HINTS_AND_PATHS}
HINTS
${_OPENSSL_INCLUDEDIR}
${_OPENSSL_INCLUDE_DIRS}
PATH_SUFFIXES
include
)
if(WIN32 AND NOT CYGWIN)
if(MSVC)
# /MD and /MDd are the standard values - if someone wants to use
# others, the libnames have to change here too
# use also ssl and ssleay32 in debug as fallback for openssl < 0.9.8b
# enable OPENSSL_MSVC_STATIC_RT to get the libs build /MT (Multithreaded no-DLL)
# In Visual C++ naming convention each of these four kinds of Windows libraries has it's standard suffix:
# * MD for dynamic-release
# * MDd for dynamic-debug
# * MT for static-release
# * MTd for static-debug
# Implementation details:
# We are using the libraries located in the VC subdir instead of the parent directory even though :
# libeay32MD.lib is identical to ../libeay32.lib, and
# ssleay32MD.lib is identical to ../ssleay32.lib
# enable OPENSSL_USE_STATIC_LIBS to use the static libs located in lib/VC/static
if (OPENSSL_MSVC_STATIC_RT)
set(_OPENSSL_MSVC_RT_MODE "MT")
else ()
set(_OPENSSL_MSVC_RT_MODE "MD")
endif ()
# Since OpenSSL 1.1, lib names are like libcrypto32MTd.lib and libssl32MTd.lib
if( "${CMAKE_SIZEOF_VOID_P}" STREQUAL "8" )
set(_OPENSSL_MSVC_ARCH_SUFFIX "64")
else()
set(_OPENSSL_MSVC_ARCH_SUFFIX "32")
endif()
if(OPENSSL_USE_STATIC_LIBS)
set(_OPENSSL_STATIC_SUFFIX
"_static"
)
set(_OPENSSL_PATH_SUFFIXES
"lib/VC/static"
"VC/static"
"lib"
)
else()
set(_OPENSSL_STATIC_SUFFIX
""
)
set(_OPENSSL_PATH_SUFFIXES
"lib/VC"
"VC"
"lib"
)
endif ()
find_library(LIB_EAY_DEBUG
NAMES
# When OpenSSL is built with default options, the static library name is suffixed with "_static".
# Looking the "libcrypto_static.lib" with a higher priority than "libcrypto.lib" which is the
# import library of "libcrypto.dll".
libcrypto${_OPENSSL_STATIC_SUFFIX}${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE}d
libcrypto${_OPENSSL_STATIC_SUFFIX}${_OPENSSL_MSVC_RT_MODE}d
libcrypto${_OPENSSL_STATIC_SUFFIX}d
libeay32${_OPENSSL_STATIC_SUFFIX}${_OPENSSL_MSVC_RT_MODE}d
libeay32${_OPENSSL_STATIC_SUFFIX}d
crypto${_OPENSSL_STATIC_SUFFIX}d
# When OpenSSL is built with the "-static" option, only the static build is produced,
# and it is not suffixed with "_static".
libcrypto${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE}d
libcrypto${_OPENSSL_MSVC_RT_MODE}d
libcryptod
libeay32${_OPENSSL_MSVC_RT_MODE}d
libeay32d
cryptod
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
PATH_SUFFIXES
${_OPENSSL_PATH_SUFFIXES}
)
find_library(LIB_EAY_RELEASE
NAMES
# When OpenSSL is built with default options, the static library name is suffixed with "_static".
# Looking the "libcrypto_static.lib" with a higher priority than "libcrypto.lib" which is the
# import library of "libcrypto.dll".
libcrypto${_OPENSSL_STATIC_SUFFIX}${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE}
libcrypto${_OPENSSL_STATIC_SUFFIX}${_OPENSSL_MSVC_RT_MODE}
libcrypto${_OPENSSL_STATIC_SUFFIX}
libeay32${_OPENSSL_STATIC_SUFFIX}${_OPENSSL_MSVC_RT_MODE}
libeay32${_OPENSSL_STATIC_SUFFIX}
crypto${_OPENSSL_STATIC_SUFFIX}
# When OpenSSL is built with the "-static" option, only the static build is produced,
# and it is not suffixed with "_static".
libcrypto${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE}
libcrypto${_OPENSSL_MSVC_RT_MODE}
libcrypto
libeay32${_OPENSSL_MSVC_RT_MODE}
libeay32
crypto
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
PATH_SUFFIXES
${_OPENSSL_PATH_SUFFIXES}
)
find_library(SSL_EAY_DEBUG
NAMES
# When OpenSSL is built with default options, the static library name is suffixed with "_static".
# Looking the "libssl_static.lib" with a higher priority than "libssl.lib" which is the
# import library of "libssl.dll".
libssl${_OPENSSL_STATIC_SUFFIX}${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE}d
libssl${_OPENSSL_STATIC_SUFFIX}${_OPENSSL_MSVC_RT_MODE}d
libssl${_OPENSSL_STATIC_SUFFIX}d
ssleay32${_OPENSSL_STATIC_SUFFIX}${_OPENSSL_MSVC_RT_MODE}d
ssleay32${_OPENSSL_STATIC_SUFFIX}d
ssl${_OPENSSL_STATIC_SUFFIX}d
# When OpenSSL is built with the "-static" option, only the static build is produced,
# and it is not suffixed with "_static".
libssl${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE}d
libssl${_OPENSSL_MSVC_RT_MODE}d
libssld
ssleay32${_OPENSSL_MSVC_RT_MODE}d
ssleay32d
ssld
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
PATH_SUFFIXES
${_OPENSSL_PATH_SUFFIXES}
)
find_library(SSL_EAY_RELEASE
NAMES
# When OpenSSL is built with default options, the static library name is suffixed with "_static".
# Looking the "libssl_static.lib" with a higher priority than "libssl.lib" which is the
# import library of "libssl.dll".
libssl${_OPENSSL_STATIC_SUFFIX}${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE}
libssl${_OPENSSL_STATIC_SUFFIX}${_OPENSSL_MSVC_RT_MODE}
libssl${_OPENSSL_STATIC_SUFFIX}
ssleay32${_OPENSSL_STATIC_SUFFIX}${_OPENSSL_MSVC_RT_MODE}
ssleay32${_OPENSSL_STATIC_SUFFIX}
ssl${_OPENSSL_STATIC_SUFFIX}
# When OpenSSL is built with the "-static" option, only the static build is produced,
# and it is not suffixed with "_static".
libssl${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE}
libssl${_OPENSSL_MSVC_RT_MODE}
libssl
ssleay32${_OPENSSL_MSVC_RT_MODE}
ssleay32
ssl
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
PATH_SUFFIXES
${_OPENSSL_PATH_SUFFIXES}
)
set(LIB_EAY_LIBRARY_DEBUG "${LIB_EAY_DEBUG}")
set(LIB_EAY_LIBRARY_RELEASE "${LIB_EAY_RELEASE}")
set(SSL_EAY_LIBRARY_DEBUG "${SSL_EAY_DEBUG}")
set(SSL_EAY_LIBRARY_RELEASE "${SSL_EAY_RELEASE}")
include(SelectLibraryConfigurations)
select_library_configurations(LIB_EAY)
select_library_configurations(SSL_EAY)
mark_as_advanced(LIB_EAY_LIBRARY_DEBUG LIB_EAY_LIBRARY_RELEASE
SSL_EAY_LIBRARY_DEBUG SSL_EAY_LIBRARY_RELEASE)
set(OPENSSL_SSL_LIBRARY ${SSL_EAY_LIBRARY} )
set(OPENSSL_CRYPTO_LIBRARY ${LIB_EAY_LIBRARY} )
elseif(MINGW)
# same player, for MinGW
set(LIB_EAY_NAMES crypto libeay32)
set(SSL_EAY_NAMES ssl ssleay32)
find_library(LIB_EAY
NAMES
${LIB_EAY_NAMES}
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
PATH_SUFFIXES
"lib/MinGW"
"lib"
)
find_library(SSL_EAY
NAMES
${SSL_EAY_NAMES}
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
PATH_SUFFIXES
"lib/MinGW"
"lib"
)
mark_as_advanced(SSL_EAY LIB_EAY)
set(OPENSSL_SSL_LIBRARY ${SSL_EAY} )
set(OPENSSL_CRYPTO_LIBRARY ${LIB_EAY} )
unset(LIB_EAY_NAMES)
unset(SSL_EAY_NAMES)
else()
# Not sure what to pick for -say- intel, let's use the toplevel ones and hope someone report issues:
find_library(LIB_EAY
NAMES
libcrypto
libeay32
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
HINTS
${_OPENSSL_LIBDIR}
PATH_SUFFIXES
lib
)
find_library(SSL_EAY
NAMES
libssl
ssleay32
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
HINTS
${_OPENSSL_LIBDIR}
PATH_SUFFIXES
lib
)
mark_as_advanced(SSL_EAY LIB_EAY)
set(OPENSSL_SSL_LIBRARY ${SSL_EAY} )
set(OPENSSL_CRYPTO_LIBRARY ${LIB_EAY} )
endif()
else()
find_library(OPENSSL_SSL_LIBRARY
NAMES
ssl
ssleay32
ssleay32MD
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
HINTS
${_OPENSSL_LIBDIR}
${_OPENSSL_LIBRARY_DIRS}
PATH_SUFFIXES
lib
)
find_library(OPENSSL_CRYPTO_LIBRARY
NAMES
crypto
NAMES_PER_DIR
${_OPENSSL_ROOT_HINTS_AND_PATHS}
HINTS
${_OPENSSL_LIBDIR}
${_OPENSSL_LIBRARY_DIRS}
PATH_SUFFIXES
lib
)
mark_as_advanced(OPENSSL_CRYPTO_LIBRARY OPENSSL_SSL_LIBRARY)
endif()
set(OPENSSL_SSL_LIBRARIES ${OPENSSL_SSL_LIBRARY})
set(OPENSSL_CRYPTO_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY})
set(OPENSSL_LIBRARIES ${OPENSSL_SSL_LIBRARIES} ${OPENSSL_CRYPTO_LIBRARIES} )
_OpenSSL_test_and_find_dependencies("${OPENSSL_SSL_LIBRARY}" "${OPENSSL_CRYPTO_LIBRARY}")
if(_OpenSSL_has_dependencies)
_OpenSSL_add_dependencies( OPENSSL_SSL_LIBRARIES )
_OpenSSL_add_dependencies( OPENSSL_CRYPTO_LIBRARIES )
_OpenSSL_add_dependencies( OPENSSL_LIBRARIES )
endif()
function(from_hex HEX DEC)
string(TOUPPER "${HEX}" HEX)
set(_res 0)
string(LENGTH "${HEX}" _strlen)
while (_strlen GREATER 0)
math(EXPR _res "${_res} * 16")
string(SUBSTRING "${HEX}" 0 1 NIBBLE)
string(SUBSTRING "${HEX}" 1 -1 HEX)
if (NIBBLE STREQUAL "A")
math(EXPR _res "${_res} + 10")
elseif (NIBBLE STREQUAL "B")
math(EXPR _res "${_res} + 11")
elseif (NIBBLE STREQUAL "C")
math(EXPR _res "${_res} + 12")
elseif (NIBBLE STREQUAL "D")
math(EXPR _res "${_res} + 13")
elseif (NIBBLE STREQUAL "E")
math(EXPR _res "${_res} + 14")
elseif (NIBBLE STREQUAL "F")
math(EXPR _res "${_res} + 15")
else()
math(EXPR _res "${_res} + ${NIBBLE}")
endif()
string(LENGTH "${HEX}" _strlen)
endwhile()
set(${DEC} ${_res} PARENT_SCOPE)
endfunction()
if(OPENSSL_INCLUDE_DIR AND EXISTS "${OPENSSL_INCLUDE_DIR}/openssl/opensslv.h")
file(STRINGS "${OPENSSL_INCLUDE_DIR}/openssl/opensslv.h" openssl_version_str
REGEX "^#[\t ]*define[\t ]+OPENSSL_VERSION_NUMBER[\t ]+0x([0-9a-fA-F])+.*")
if(openssl_version_str)
# The version number is encoded as 0xMNNFFPPS: major minor fix patch status
# The status gives if this is a developer or prerelease and is ignored here.
# Major, minor, and fix directly translate into the version numbers shown in
# the string. The patch field translates to the single character suffix that
# indicates the bug fix state, which 00 -> nothing, 01 -> a, 02 -> b and so
# on.
string(REGEX REPLACE "^.*OPENSSL_VERSION_NUMBER[\t ]+0x([0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F]).*$"
"\\1;\\2;\\3;\\4;\\5" OPENSSL_VERSION_LIST "${openssl_version_str}")
list(GET OPENSSL_VERSION_LIST 0 OPENSSL_VERSION_MAJOR)
list(GET OPENSSL_VERSION_LIST 1 OPENSSL_VERSION_MINOR)
from_hex("${OPENSSL_VERSION_MINOR}" OPENSSL_VERSION_MINOR)
list(GET OPENSSL_VERSION_LIST 2 OPENSSL_VERSION_FIX)
from_hex("${OPENSSL_VERSION_FIX}" OPENSSL_VERSION_FIX)
list(GET OPENSSL_VERSION_LIST 3 OPENSSL_VERSION_PATCH)
if (NOT OPENSSL_VERSION_PATCH STREQUAL "00")
from_hex("${OPENSSL_VERSION_PATCH}" _tmp)
# 96 is the ASCII code of 'a' minus 1
math(EXPR OPENSSL_VERSION_PATCH_ASCII "${_tmp} + 96")
unset(_tmp)
# Once anyone knows how OpenSSL would call the patch versions beyond 'z'
# this should be updated to handle that, too. This has not happened yet
# so it is simply ignored here for now.
string(ASCII "${OPENSSL_VERSION_PATCH_ASCII}" OPENSSL_VERSION_PATCH_STRING)
endif ()
set(OPENSSL_VERSION "${OPENSSL_VERSION_MAJOR}.${OPENSSL_VERSION_MINOR}.${OPENSSL_VERSION_FIX}${OPENSSL_VERSION_PATCH_STRING}")
else ()
# Since OpenSSL 3.0.0, the new version format is MAJOR.MINOR.PATCH and
# a new OPENSSL_VERSION_STR macro contains exactly that
file(STRINGS "${OPENSSL_INCLUDE_DIR}/openssl/opensslv.h" OPENSSL_VERSION_STR
REGEX "^#[\t ]*define[\t ]+OPENSSL_VERSION_STR[\t ]+\"([0-9])+\\.([0-9])+\\.([0-9])+\".*")
string(REGEX REPLACE "^.*OPENSSL_VERSION_STR[\t ]+\"([0-9]+\\.[0-9]+\\.[0-9]+)\".*$"
"\\1" OPENSSL_VERSION_STR "${OPENSSL_VERSION_STR}")
set(OPENSSL_VERSION "${OPENSSL_VERSION_STR}")
unset(OPENSSL_VERSION_STR)
endif ()
endif ()
foreach(_comp IN LISTS OpenSSL_FIND_COMPONENTS)
if(_comp STREQUAL "Crypto")
if(EXISTS "${OPENSSL_INCLUDE_DIR}" AND
(EXISTS "${OPENSSL_CRYPTO_LIBRARY}" OR
EXISTS "${LIB_EAY_LIBRARY_DEBUG}" OR
EXISTS "${LIB_EAY_LIBRARY_RELEASE}")
)
set(OpenSSL_${_comp}_FOUND TRUE)
else()
set(OpenSSL_${_comp}_FOUND FALSE)
endif()
elseif(_comp STREQUAL "SSL")
if(EXISTS "${OPENSSL_INCLUDE_DIR}" AND
(EXISTS "${OPENSSL_SSL_LIBRARY}" OR
EXISTS "${SSL_EAY_LIBRARY_DEBUG}" OR
EXISTS "${SSL_EAY_LIBRARY_RELEASE}")
)
set(OpenSSL_${_comp}_FOUND TRUE)
else()
set(OpenSSL_${_comp}_FOUND FALSE)
endif()
else()
message(WARNING "${_comp} is not a valid OpenSSL component")
set(OpenSSL_${_comp}_FOUND FALSE)
endif()
endforeach()
unset(_comp)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(OpenSSL
REQUIRED_VARS
OPENSSL_CRYPTO_LIBRARY
OPENSSL_INCLUDE_DIR
VERSION_VAR
OPENSSL_VERSION
HANDLE_COMPONENTS
FAIL_MESSAGE
"Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the system variable OPENSSL_ROOT_DIR"
)
mark_as_advanced(OPENSSL_INCLUDE_DIR)
if(OPENSSL_FOUND)
message(STATUS "Found OpenSSL library: ${OPENSSL_LIBRARIES}")
message(STATUS "Found OpenSSL headers: ${OPENSSL_INCLUDE_DIR}")
include(EnsureVersion)
ENSURE_VERSION("${OPENSSL_EXPECTED_VERSION}" "${OPENSSL_VERSION}" OPENSSL_VERSION_OK)
if(NOT OPENSSL_VERSION_OK)
message(FATAL_ERROR "AzerothCore needs OpenSSL version ${OPENSSL_EXPECTED_VERSION} but found too new version ${OPENSSL_VERSION}. AzerothCore needs OpenSSL 1.0.x or 1.1.x to work properly. If you still have problems please install OpenSSL 1.0.x if you still have problems search on forum for TCE00022")
endif()
if(NOT TARGET OpenSSL::Crypto AND
(EXISTS "${OPENSSL_CRYPTO_LIBRARY}" OR
EXISTS "${LIB_EAY_LIBRARY_DEBUG}" OR
EXISTS "${LIB_EAY_LIBRARY_RELEASE}")
)
add_library(OpenSSL::Crypto UNKNOWN IMPORTED)
set_target_properties(OpenSSL::Crypto PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${OPENSSL_INCLUDE_DIR}")
if(EXISTS "${OPENSSL_CRYPTO_LIBRARY}")
set_target_properties(OpenSSL::Crypto PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION "${OPENSSL_CRYPTO_LIBRARY}")
endif()
if(EXISTS "${LIB_EAY_LIBRARY_RELEASE}")
set_property(TARGET OpenSSL::Crypto APPEND PROPERTY
IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(OpenSSL::Crypto PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C"
IMPORTED_LOCATION_RELEASE "${LIB_EAY_LIBRARY_RELEASE}")
endif()
if(EXISTS "${LIB_EAY_LIBRARY_DEBUG}")
set_property(TARGET OpenSSL::Crypto APPEND PROPERTY
IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(OpenSSL::Crypto PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C"
IMPORTED_LOCATION_DEBUG "${LIB_EAY_LIBRARY_DEBUG}")
endif()
_OpenSSL_target_add_dependencies(OpenSSL::Crypto)
endif()
if(NOT TARGET OpenSSL::SSL AND
(EXISTS "${OPENSSL_SSL_LIBRARY}" OR
EXISTS "${SSL_EAY_LIBRARY_DEBUG}" OR
EXISTS "${SSL_EAY_LIBRARY_RELEASE}")
)
add_library(OpenSSL::SSL UNKNOWN IMPORTED)
set_target_properties(OpenSSL::SSL PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${OPENSSL_INCLUDE_DIR}")
if(EXISTS "${OPENSSL_SSL_LIBRARY}")
set_target_properties(OpenSSL::SSL PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION "${OPENSSL_SSL_LIBRARY}")
endif()
if(EXISTS "${SSL_EAY_LIBRARY_RELEASE}")
set_property(TARGET OpenSSL::SSL APPEND PROPERTY
IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(OpenSSL::SSL PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C"
IMPORTED_LOCATION_RELEASE "${SSL_EAY_LIBRARY_RELEASE}")
endif()
if(EXISTS "${SSL_EAY_LIBRARY_DEBUG}")
set_property(TARGET OpenSSL::SSL APPEND PROPERTY
IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(OpenSSL::SSL PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C"
IMPORTED_LOCATION_DEBUG "${SSL_EAY_LIBRARY_DEBUG}")
endif()
if(TARGET OpenSSL::Crypto)
set_target_properties(OpenSSL::SSL PROPERTIES
INTERFACE_LINK_LIBRARIES OpenSSL::Crypto)
endif()
_OpenSSL_target_add_dependencies(OpenSSL::SSL)
endif()
if("${OPENSSL_VERSION_MAJOR}.${OPENSSL_VERSION_MAJOR}.${OPENSSL_VERSION_FIX}" VERSION_GREATER_EQUAL "0.9.8")
if(MSVC)
if(EXISTS "${OPENSSL_INCLUDE_DIR}")
set(_OPENSSL_applink_paths PATHS ${OPENSSL_INCLUDE_DIR})
endif()
find_file(OPENSSL_APPLINK_SOURCE
NAMES
openssl/applink.c
${_OPENSSL_applink_paths}
NO_DEFAULT_PATH)
if(OPENSSL_APPLINK_SOURCE)
set(_OPENSSL_applink_interface_srcs ${OPENSSL_APPLINK_SOURCE})
endif()
endif()
if(NOT TARGET OpenSSL::applink)
add_library(OpenSSL::applink INTERFACE IMPORTED)
set_property(TARGET OpenSSL::applink APPEND
PROPERTY INTERFACE_SOURCES
${_OPENSSL_applink_interface_srcs})
endif()
endif()
endif()
# Restore the original find library ordering
if(OPENSSL_USE_STATIC_LIBS)
set(CMAKE_FIND_LIBRARY_SUFFIXES ${_openssl_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES})
endif()

View File

@@ -0,0 +1,17 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
function(ADD_CXX_PCH TARGET_NAME_LIST PCH_HEADER)
foreach(TARGET_NAME ${TARGET_NAME_LIST})
target_precompile_headers(${TARGET_NAME} PRIVATE ${PCH_HEADER})
endforeach()
endfunction(ADD_CXX_PCH)

View File

@@ -0,0 +1,48 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
macro(GroupSources dir)
# Skip this if WITH_SOURCE_TREE is not set (empty string).
if (NOT ${WITH_SOURCE_TREE} STREQUAL "")
# Include all header and c files
file(GLOB_RECURSE elements RELATIVE ${dir} *.h *.hpp *.c *.cpp *.cc)
foreach(element ${elements})
# Extract filename and directory
get_filename_component(element_name ${element} NAME)
get_filename_component(element_dir ${element} DIRECTORY)
if (NOT ${element_dir} STREQUAL "")
# If the file is in a subdirectory use it as source group.
if (${WITH_SOURCE_TREE} STREQUAL "flat")
# Build flat structure by using only the first subdirectory.
string(FIND ${element_dir} "/" delemiter_pos)
if (NOT ${delemiter_pos} EQUAL -1)
string(SUBSTRING ${element_dir} 0 ${delemiter_pos} group_name)
source_group("${group_name}" FILES ${dir}/${element})
else()
# Build hierarchical structure.
# File is in root directory.
source_group("${element_dir}" FILES ${dir}/${element})
endif()
else()
# Use the full hierarchical structure to build source_groups.
string(REPLACE "/" "\\" group_name ${element_dir})
source_group("${group_name}" FILES ${dir}/${element})
endif()
else()
# If the file is in the root directory, place it in the root source_group.
source_group("\\" FILES ${dir}/${element})
endif()
endforeach()
endif()
endmacro()

View File

@@ -0,0 +1,16 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
if( NOT CONF_DIR )
set(CONF_DIR ${CMAKE_INSTALL_PREFIX})
message(STATUS "Using installation path for configuration files")
endif()

View File

@@ -0,0 +1,35 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# from cmake wiki
IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"")
ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files)
STRING(REGEX REPLACE "\n" ";" files "${files}")
FOREACH(file ${files})
MESSAGE(STATUS "Uninstalling \"${file}\"")
IF(EXISTS "${file}")
EXEC_PROGRAM(
"@CMAKE_COMMAND@" ARGS "-E remove \"${file}\""
OUTPUT_VARIABLE rm_out
RETURN_VALUE rm_retval
)
IF("${rm_retval}" STREQUAL 0)
ELSE("${rm_retval}" STREQUAL 0)
MESSAGE(FATAL_ERROR "Problem when removing \"${file}\"")
ENDIF("${rm_retval}" STREQUAL 0)
ELSE(EXISTS "${file}")
MESSAGE(STATUS "File \"${file}\" does not exist.")
ENDIF(EXISTS "${file}")
ENDFOREACH(file)

View File

@@ -0,0 +1,16 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# set installation prefix
if( PREFIX )
set(CMAKE_INSTALL_PREFIX "${PREFIX}")
endif()

View File

@@ -0,0 +1,58 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Package overloads - Linux
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
if (NOT NOJEM)
set(JEMALLOC_LIBRARY "jemalloc")
message(STATUS "UNIX: Using jemalloc")
endif()
endif()
# set default configuration directory
if( NOT CONF_DIR )
set(CONF_DIR ${CMAKE_INSTALL_PREFIX}/etc)
message(STATUS "UNIX: Using default configuration directory")
endif()
# set default library directory
if( NOT LIBSDIR )
set(LIBSDIR ${CMAKE_INSTALL_PREFIX}/lib)
message(STATUS "UNIX: Using default library directory")
endif()
# configure uninstaller
configure_file(
"${CMAKE_SOURCE_DIR}/src/cmake/platform/cmake_uninstall.in.cmake"
"${CMAKE_BINARY_DIR}/cmake_uninstall.cmake"
@ONLY
)
message(STATUS "UNIX: Configuring uninstall target")
# create uninstaller target (allows for using "make uninstall")
add_custom_target(uninstall
"${CMAKE_COMMAND}" -P "${CMAKE_BINARY_DIR}/cmake_uninstall.cmake"
)
message(STATUS "UNIX: Created uninstall target")
message(STATUS "UNIX: Detected compiler: ${CMAKE_C_COMPILER}")
if(CMAKE_C_COMPILER MATCHES "gcc" OR CMAKE_C_COMPILER_ID STREQUAL "GNU")
include(${CMAKE_SOURCE_DIR}/src/cmake/compiler/gcc/settings.cmake)
elseif(CMAKE_C_COMPILER MATCHES "icc")
include(${CMAKE_SOURCE_DIR}/src/cmake/compiler/icc/settings.cmake)
elseif(CMAKE_C_COMPILER MATCHES "clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang")
include(${CMAKE_SOURCE_DIR}/src/cmake/compiler/clang/settings.cmake)
else()
target_compile_definitions(acore-compile-option-interface
INTERFACE
-D_BUILD_DIRECTIVE="${CMAKE_BUILD_TYPE}")
endif()

View File

@@ -0,0 +1,38 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Platform-specfic options
option(USE_MYSQL_SOURCES "Use included MySQL-sources to build libraries" 0)
if( USE_MYSQL_SOURCES )
set(MYSQL_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/deps/mysqllite/include)
set(MYSQL_LIBRARY "libmysql")
set( MYSQL_FOUND 1 )
message(STATUS "Using supplied MySQL sources")
endif()
# check the CMake preload parameters (commented out by default)
# overload CMAKE_INSTALL_PREFIX if not being set properly
#if( WIN32 )
# if( NOT CYGWIN )
# if( NOT CMAKE_INSTALL_PREFIX )
# set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/bin")
# endif()
# endif()
#endif()
if ( MSVC )
include(${CMAKE_SOURCE_DIR}/src/cmake/compiler/msvc/settings.cmake)
elseif ( MINGW )
include(${CMAKE_SOURCE_DIR}/src/cmake/compiler/mingw/settings.cmake)
endif()

View File

@@ -0,0 +1,19 @@
#ifndef __REVISION_H__
#define __REVISION_H__
#define _REVISION "@rev_id_str@"
#define _HASH "@rev_hash@"
#define _DATE "@rev_date@"
#define _BRANCH "@rev_branch@"
#define _CMAKE_COMMAND R"(@CMAKE_COMMAND@)"
#define _CMAKE_VERSION R"(@CMAKE_VERSION@)"
#define _CMAKE_HOST_SYSTEM R"(@CMAKE_HOST_SYSTEM_NAME@ @CMAKE_HOST_SYSTEM_VERSION@)"
#define _SOURCE_DIRECTORY R"(@CMAKE_SOURCE_DIR@)"
#define _BUILD_DIRECTORY R"(@BUILDDIR@)"
#define _MYSQL_EXECUTABLE R"(@MYSQL_EXECUTABLE@)"
#define VER_COMPANYNAME_STR "AzerothCore"
#define VER_LEGALCOPYRIGHT_STR "(c)2016-@rev_year@ AzerothCore"
#define VER_FILEVERSION 0,0,0
#define VER_FILEVERSION_STR "@rev_hash@ @rev_date@ (@rev_branch@ branch)"
#define VER_PRODUCTVERSION VER_FILEVERSION
#define VER_PRODUCTVERSION_STR VER_FILEVERSION_STR
#endif // __REVISION_H__

215
src/cmake/showoptions.cmake Normal file
View File

@@ -0,0 +1,215 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# output generic information about the core and buildtype chosen
message("")
message("* AzerothCore revision : ${rev_hash} ${rev_date} (${rev_branch} branch)")
if( UNIX )
message("* AzerothCore buildtype : ${CMAKE_BUILD_TYPE}")
endif()
message("")
# output information about installation-directories and locations
message("* Install core to : ${CMAKE_INSTALL_PREFIX}")
if( UNIX )
message("* Install libraries to : ${LIBSDIR}")
endif()
message("* Install configs to : ${CONF_DIR}")
add_definitions(-D_CONF_DIR=$<1:"${CONF_DIR}">)
message("")
# Show infomation about the options selected during configuration
if (APPS_BUILD AND (NOT APPS_BUILD STREQUAL "none"))
message("* Build applications : Yes (${APPS_BUILD})")
else()
message("* Build applications : No")
endif()
if (TOOLS_BUILD AND (NOT TOOLS_BUILD STREQUAL "none"))
message("* Build tools : Yes (${TOOLS_BUILD})")
add_definitions(-DNO_CORE_FUNCS)
else()
message("* Build tools : No")
endif()
if (SCRIPTS AND (NOT SCRIPTS STREQUAL "none"))
message("* Build with scripts : Yes (${SCRIPTS})")
else()
message("* Build with scripts : No")
endif()
if (MODULES AND (NOT MODULES STREQUAL "none"))
message("* Build with modules : Yes (${MODULES})")
else()
message("* Build with modules : No")
endif()
if( BUILD_TESTING )
message("* Build unit tests : Yes")
else()
message("* Build unit tests : No (default)")
endif()
if( USE_COREPCH )
message("* Build core w/PCH : Yes (default)")
else()
message("* Build core w/PCH : No")
endif()
if( USE_SCRIPTPCH )
message("* Build scripts w/PCH : Yes (default)")
else()
message("* Build scripts w/PCH : No")
endif()
if( WITH_WARNINGS )
message("* Show all warnings : Yes")
else()
message("* Show compile-warnings : No (default)")
endif()
if( WITH_COREDEBUG )
message("* Use coreside debug : Yes")
add_definitions(-DACORE_DEBUG)
else()
message("* Use coreside debug : No (default)")
endif()
if ( UNIX )
if( WITH_PERFTOOLS )
message("* Use unix gperftools : Yes")
add_definitions(-DPERF_TOOLS)
else()
message("* Use unix gperftools : No (default)")
endif()
endif( UNIX )
if( WIN32 )
if( USE_MYSQL_SOURCES )
message("* Use MySQL sourcetree : Yes (default)")
else()
message("* Use MySQL sourcetree : No")
endif()
endif( WIN32 )
if ( WITHOUT_GIT )
message("* Use GIT revision hash : No")
message("")
message(" *** WITHOUT_GIT - WARNING!")
message(" *** By choosing the WITHOUT_GIT option you have waived all rights for support,")
message(" *** and accept that or all requests for support or assistance sent to the core")
message(" *** developers will be rejected. This due to that we will be unable to detect")
message(" *** what revision of the codebase you are using in a proper way.")
message(" *** We remind you that you need to use the repository codebase and a supported")
message(" *** version of git for the revision-hash to work, and be allowede to ask for")
message(" *** support if needed.")
else()
message("* Use GIT revision hash : Yes (default)")
endif()
if ( NOJEM )
message("")
message(" *** NOJEM - WARNING!")
message(" *** jemalloc linking has been disabled!")
message(" *** Please note that this is for DEBUGGING WITH VALGRIND only!")
message(" *** DO NOT DISABLE IT UNLESS YOU KNOW WHAT YOU'RE DOING!")
endif()
# Performance optimization options:
if( ENABLE_VMAP_CHECKS )
message("* Enable vmap DisableMgr checks : Yes (default)")
add_definitions(-DENABLE_VMAP_CHECKS)
else()
message("* Enable vmap DisableMgr checks : No")
endif()
if(WIN32)
if(NOT WITH_SOURCE_TREE STREQUAL "no")
message("* Show source tree : Yes - \"${WITH_SOURCE_TREE}\"")
else()
message("* Show source tree : No")
endif()
else()
message("* Show source tree : No (For UNIX default)")
endif()
if(WITH_STRICT_DATABASE_TYPE_CHECKS)
message("")
message(" *** WITH_STRICT_DATABASE_TYPE_CHECKS - WARNING!")
message(" *** Validates uses of database Get***() functions from Field class")
message(" *** invalid calls will result in returning value 0")
message(" *** NOT COMPATIBLE WITH MARIADB!")
add_definitions(-DACORE_STRICT_DATABASE_TYPE_CHECKS)
endif()
if(WITHOUT_METRICS)
message("")
message(" *** WITHOUT_METRICS - WARNING!")
message(" *** Please note that this will disable all metrics output (i.e. InfluxDB and Grafana)")
add_definitions(-DWITHOUT_METRICS)
elseif (WITH_DETAILED_METRICS)
message("")
message(" *** WITH_DETAILED_METRICS - WARNING!")
message(" *** Please note that this will enable detailed metrics output (i.e. time each session takes to update)")
add_definitions(-DWITH_DETAILED_METRICS)
endif()
if(MSAN)
message("")
message(" *** MSAN - WARNING!")
message(" *** Please note that this is for DEBUGGING WITH MEMORY SANITIZER only!")
add_definitions(-DMSAN)
endif()
if(UBSAN)
message("")
message(" *** UBSAN - WARNING!")
message(" *** Please note that this is for DEBUGGING WITH UNDEFINED BEHAVIOR SANITIZER only!")
add_definitions(-DUBSAN)
endif()
if(TSAN)
message("")
message(" *** TSAN - WARNING!")
message(" *** Please note that this is for DEBUGGING WITH THREAD SANITIZER only!")
add_definitions(-DTSAN -DNO_BUFFERPOOL)
endif()
if(BUILD_SHARED_LIBS)
message("")
message(" *** WITH_DYNAMIC_LINKING - INFO!")
message(" *** Will link against shared libraries!")
message(" *** Please note that this is an experimental feature!")
if(WITH_DYNAMIC_LINKING_FORCED)
message("")
message(" *** Dynamic linking was enforced through a dynamic script module!")
endif()
add_definitions(-DACORE_API_USE_DYNAMIC_LINKING)
WarnAboutSpacesInBuildPath()
endif()
if (CONFIG_ABORT_INCORRECT_OPTIONS)
message("")
message(" WARNING !")
message(" Enabled abort if core found incorrect option in config files")
add_definitions(-DCONFIG_ABORT_INCORRECT_OPTIONS)
endif()
message("")

View File

@@ -0,0 +1,56 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AsioHacksFwd_h__
#define AsioHacksFwd_h__
#include <boost/version.hpp>
/**
Collection of forward declarations to improve compile time
*/
namespace boost::posix_time
{
class ptime;
}
namespace boost::asio
{
template <typename Time>
struct time_traits;
}
namespace boost::asio::ip
{
class address;
class tcp;
template <typename InternetProtocol>
class basic_endpoint;
typedef basic_endpoint<tcp> tcp_endpoint;
}
namespace Acore::Asio
{
class DeadlineTimer;
class IoContext;
class Resolver;
class Strand;
}
#endif // AsioHacksFwd_h__

View File

@@ -0,0 +1,42 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef DeadlineTimer_h__
#define DeadlineTimer_h__
#include <boost/asio/deadline_timer.hpp>
#if BOOST_VERSION >= 107000
#define BasicDeadlineTimerThirdTemplateArg , boost::asio::io_context::executor_type
#elif BOOST_VERSION >= 106600
#define BasicDeadlineTimerThirdTemplateArg
#else
#define BasicDeadlineTimerThirdTemplateArg , boost::asio::deadline_timer_service<boost::posix_time::ptime, boost::asio::time_traits<boost::posix_time::ptime>>
#endif
#define DeadlineTimerBase boost::asio::basic_deadline_timer<boost::posix_time::ptime, boost::asio::time_traits<boost::posix_time::ptime> BasicDeadlineTimerThirdTemplateArg>
namespace Acore::Asio
{
class DeadlineTimer : public DeadlineTimerBase
{
public:
using DeadlineTimerBase::basic_deadline_timer;
};
}
#endif // DeadlineTimer_h__

View File

@@ -0,0 +1,77 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef IoContext_h__
#define IoContext_h__
#include <boost/version.hpp>
#if BOOST_VERSION >= 106600
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>
#define IoContextBaseNamespace boost::asio
#define IoContextBase io_context
#else
#include <boost/asio/io_service.hpp>
#define IoContextBaseNamespace boost::asio
#define IoContextBase io_service
#endif
namespace Acore::Asio
{
class IoContext
{
public:
IoContext() : _impl() { }
explicit IoContext(int concurrency_hint) : _impl(concurrency_hint) { }
operator IoContextBaseNamespace::IoContextBase&() { return _impl; }
operator IoContextBaseNamespace::IoContextBase const&() const { return _impl; }
std::size_t run() { return _impl.run(); }
void stop() { _impl.stop(); }
#if BOOST_VERSION >= 106600
boost::asio::io_context::executor_type get_executor() noexcept { return _impl.get_executor(); }
#endif
private:
IoContextBaseNamespace::IoContextBase _impl;
};
template<typename T>
inline decltype(auto) post(IoContextBaseNamespace::IoContextBase& ioContext, T&& t)
{
#if BOOST_VERSION >= 106600
return boost::asio::post(ioContext, std::forward<T>(t));
#else
return ioContext.post(std::forward<T>(t));
#endif
}
template<typename T>
inline decltype(auto) get_io_context(T&& ioObject)
{
#if BOOST_VERSION >= 106600
return ioObject.get_executor().context();
#else
return ioObject.get_io_service();
#endif
}
}
#endif // IoContext_h__

View File

@@ -0,0 +1,43 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef IpAddress_h__
#define IpAddress_h__
#include "Define.h"
#include <boost/asio/ip/address.hpp>
namespace Acore::Net
{
#if BOOST_VERSION >= 106600
using boost::asio::ip::make_address;
using boost::asio::ip::make_address_v4;
inline uint32 address_to_uint(boost::asio::ip::address_v4 const& address) { return address.to_uint(); }
#else
inline boost::asio::ip::address make_address(char const* str) { return boost::asio::ip::address::from_string(str); }
inline boost::asio::ip::address make_address(char const* str, boost::system::error_code& ec) { return boost::asio::ip::address::from_string(str, ec); }
inline boost::asio::ip::address make_address(std::string const& str) { return boost::asio::ip::address::from_string(str); }
inline boost::asio::ip::address make_address(std::string const& str, boost::system::error_code& ec) { return boost::asio::ip::address::from_string(str, ec); }
inline boost::asio::ip::address_v4 make_address_v4(char const* str) { return boost::asio::ip::address_v4::from_string(str); }
inline boost::asio::ip::address_v4 make_address_v4(char const* str, boost::system::error_code& ec) { return boost::asio::ip::address_v4::from_string(str, ec); }
inline boost::asio::ip::address_v4 make_address_v4(std::string const& str) { return boost::asio::ip::address_v4::from_string(str); }
inline boost::asio::ip::address_v4 make_address_v4(std::string const& str, boost::system::error_code& ec) { return boost::asio::ip::address_v4::from_string(str, ec); }
inline uint32 address_to_uint(boost::asio::ip::address_v4 const& address) { return address.to_ulong(); }
#endif
}
#endif // IpAddress_h__

View File

@@ -0,0 +1,75 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef IpNetwork_h__
#define IpNetwork_h__
#include "Define.h"
#include "IpAddress.h"
#include <boost/version.hpp>
#if BOOST_VERSION >= 106600
#include <boost/asio/ip/network_v4.hpp>
#include <boost/asio/ip/network_v6.hpp>
#endif
namespace Acore::Net
{
inline bool IsInNetwork(boost::asio::ip::address_v4 const& networkAddress, boost::asio::ip::address_v4 const& mask, boost::asio::ip::address_v4 const& clientAddress)
{
#if BOOST_VERSION >= 106600
boost::asio::ip::network_v4 network = boost::asio::ip::make_network_v4(networkAddress, mask);
boost::asio::ip::address_v4_range hosts = network.hosts();
return hosts.find(clientAddress) != hosts.end();
#else
return (clientAddress.to_ulong() & mask.to_ulong()) == (networkAddress.to_ulong() & mask.to_ulong());
#endif
}
inline boost::asio::ip::address_v4 GetDefaultNetmaskV4(boost::asio::ip::address_v4 const& networkAddress)
{
if ((address_to_uint(networkAddress) & 0x80000000) == 0)
{
return boost::asio::ip::address_v4(0xFF000000);
}
if ((address_to_uint(networkAddress) & 0xC0000000) == 0x80000000)
{
return boost::asio::ip::address_v4(0xFFFF0000);
}
if ((address_to_uint(networkAddress) & 0xE0000000) == 0xC0000000)
{
return boost::asio::ip::address_v4(0xFFFFFF00);
}
return boost::asio::ip::address_v4(0xFFFFFFFF);
}
inline bool IsInNetwork(boost::asio::ip::address_v6 const& networkAddress, uint16 prefixLength, boost::asio::ip::address_v6 const& clientAddress)
{
#if BOOST_VERSION >= 106600
boost::asio::ip::network_v6 network = boost::asio::ip::make_network_v6(networkAddress, prefixLength);
boost::asio::ip::address_v6_range hosts = network.hosts();
return hosts.find(clientAddress) != hosts.end();
#else
(void)networkAddress;
(void)prefixLength;
(void)clientAddress;
return false;
#endif
}
}
#endif // IpNetwork_h__

View File

@@ -0,0 +1,63 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef Resolver_h__
#define Resolver_h__
#include "IoContext.h"
#include "Optional.h"
#include <boost/asio/ip/tcp.hpp>
#include <string>
namespace Acore::Asio
{
/**
Hack to make it possible to forward declare resolver (one of its template arguments is a typedef to something super long and using nested classes)
*/
class Resolver
{
public:
explicit Resolver(IoContext& ioContext) : _impl(ioContext) { }
Optional<boost::asio::ip::tcp::endpoint> Resolve(boost::asio::ip::tcp const& protocol, std::string const& host, std::string const& service)
{
boost::system::error_code ec;
#if BOOST_VERSION >= 106600
boost::asio::ip::resolver_base::flags flagsResolver = boost::asio::ip::resolver_base::all_matching;
boost::asio::ip::tcp::resolver::results_type results = _impl.resolve(protocol, host, service, flagsResolver, ec);
if (results.begin() == results.end() || ec)
return {};
return results.begin()->endpoint();
#else
boost::asio::ip::resolver_query_base::flags flagsQuery = boost::asio::ip::tcp::resolver::query::all_matching;
boost::asio::ip::tcp::resolver::query query(std::move(protocol), std::move(host), std::move(service), flagsQuery);
boost::asio::ip::tcp::resolver::iterator itr = _impl.resolve(query, ec);
boost::asio::ip::tcp::resolver::iterator end;
if (itr == end || ec)
return {};
return itr->endpoint();
#endif
}
private:
boost::asio::ip::tcp::resolver _impl;
};
}
#endif // Resolver_h__

50
src/common/Asio/Strand.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef Strand_h__
#define Strand_h__
#include "IoContext.h"
#include <boost/asio/strand.hpp>
#if BOOST_VERSION >= 106600
#include <boost/asio/bind_executor.hpp>
#endif
namespace Acore::Asio
{
/**
Hack to make it possible to forward declare strand (which is a inner class)
*/
class Strand : public IoContextBaseNamespace::IoContextBase::strand
{
public:
Strand(IoContext& ioContext) : IoContextBaseNamespace::IoContextBase::strand(ioContext) { }
};
#if BOOST_VERSION >= 106600
using boost::asio::bind_executor;
#else
template<typename T>
inline decltype(auto) bind_executor(Strand& strand, T&& t)
{
return strand.wrap(std::forward<T>(t));
}
#endif
}
#endif // Strand_h__

46
src/common/Banner.cpp Normal file
View File

@@ -0,0 +1,46 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Banner.h"
#include "GitRevision.h"
#include "StringFormat.h"
void Acore::Banner::Show(std::string_view applicationName, void(*log)(std::string_view text), void(*logExtraInfo)())
{
log(Acore::StringFormatFmt("{} ({})", GitRevision::GetFullVersion(), applicationName));
log("<Ctrl-C> to stop.\n");
log(" ########################################");
log(" # # # # ##### ");
log(" ## ## # # # # # # ## ### #### ");
log(" # # # # # # # # # # # # # # ");
log(" # # # ## # # # # # # # ## ");
log(" # # ## # # # # # # # # ");
log(" # # # # # # # # ## # #### ");
log(" # # # # ## ## ##### ");
log(" ########################################");
log(" MxWCore 3.3.5 ");
log(" Dev.: mikx (core.mxwow.ovh) ");
log(" Based on AzerothCore (azerothcore.org) ");
log(" ########################################");
if (logExtraInfo)
{
logExtraInfo();
}
log(" ");
}

29
src/common/Banner.h Normal file
View File

@@ -0,0 +1,29 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AZEROTHCORE_BANNER_H
#define AZEROTHCORE_BANNER_H
#include "Define.h"
#include <string_view>
namespace Acore::Banner
{
AC_COMMON_API void Show(std::string_view applicationName, void(*log)(std::string_view text), void(*logExtraInfo)());
}
#endif // AZEROTHCORE_BANNER_H

94
src/common/CMakeLists.txt Normal file
View File

@@ -0,0 +1,94 @@
#
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
CollectSourceFiles(
${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE_SOURCES
# Exclude
${CMAKE_CURRENT_SOURCE_DIR}/Debugging
${CMAKE_CURRENT_SOURCE_DIR}/Platform
${CMAKE_CURRENT_SOURCE_DIR}/Collision
${CMAKE_CURRENT_SOURCE_DIR}/Navigation
${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders)
if (BUILD_APPLICATION_WORLDSERVER OR BUILD_TOOLS_MAPS)
unset(PRIVATE_SOURCES)
CollectSourceFiles(
${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE_SOURCES
# Exclude
${CMAKE_CURRENT_SOURCE_DIR}/Debugging
${CMAKE_CURRENT_SOURCE_DIR}/Platform
${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders)
endif()
# Manually set sources for Debugging directory as we don't want to include WheatyExceptionReport in common project
# It needs to be included both in authserver and worldserver for the static global variable to be properly initialized
# and to handle crash logs on windows
list(APPEND PRIVATE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/Debugging/Errors.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Debugging/Errors.h)
if (USE_COREPCH)
set(PRIVATE_PCH_HEADER PrecompiledHeaders/commonPCH.h)
endif()
# Group sources
GroupSources(${CMAKE_CURRENT_SOURCE_DIR})
add_library(common
${PRIVATE_SOURCES})
CollectIncludeDirectories(
${CMAKE_CURRENT_SOURCE_DIR}
PUBLIC_INCLUDES
# Exclude
${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders)
target_include_directories(common
PUBLIC
# Provide the binary dir for all child targets
${CMAKE_BINARY_DIR}
${PUBLIC_INCLUDES}
PRIVATE
${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(common
PRIVATE
acore-core-interface
PUBLIC
boost
argon2
sfmt
utf8cpp
openssl
threads
jemalloc
stdfs
fmt)
if (BUILD_APPLICATION_WORLDSERVER OR BUILD_TOOLS_MAPS)
target_link_libraries(common
PUBLIC
g3dlib
Detour)
endif()
set_target_properties(common
PROPERTIES
FOLDER
"server")
# Generate precompiled header
if (USE_COREPCH)
add_cxx_pch(common ${PRIVATE_PCH_HEADER})
endif ()

View File

@@ -0,0 +1,336 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "BoundingIntervalHierarchy.h"
#ifdef _MSC_VER
#define isnan _isnan
#else
#define isnan std::isnan
#endif
void BIH::buildHierarchy(std::vector<uint32>& tempTree, buildData& dat, BuildStats& stats)
{
// create space for the first node
// cppcheck-suppress integerOverflow
tempTree.push_back(uint32(3 << 30)); // dummy leaf
tempTree.insert(tempTree.end(), 2, 0);
//tempTree.add(0);
// seed bbox
AABound gridBox = { bounds.low(), bounds.high() };
AABound nodeBox = gridBox;
// seed subdivide function
subdivide(0, dat.numPrims - 1, tempTree, dat, gridBox, nodeBox, 0, 1, stats);
}
void BIH::subdivide(int left, int right, std::vector<uint32>& tempTree, buildData& dat, AABound& gridBox, AABound& nodeBox, int nodeIndex, int depth, BuildStats& stats)
{
if ((right - left + 1) <= dat.maxPrims || depth >= MAX_STACK_SIZE)
{
// write leaf node
stats.updateLeaf(depth, right - left + 1);
createNode(tempTree, nodeIndex, left, right);
return;
}
// calculate extents
int axis = -1, prevAxis, rightOrig;
float clipL = G3D::fnan(), clipR = G3D::fnan(), prevClip = G3D::fnan();
float split = G3D::fnan(), prevSplit;
bool wasLeft = true;
while (true)
{
prevAxis = axis;
prevSplit = split;
// perform quick consistency checks
G3D::Vector3 d( gridBox.hi - gridBox.lo );
if (d.x < 0 || d.y < 0 || d.z < 0)
{
throw std::logic_error("negative node extents");
}
for (int i = 0; i < 3; i++)
{
if (nodeBox.hi[i] < gridBox.lo[i] || nodeBox.lo[i] > gridBox.hi[i])
{
//UI.printError(Module.ACCEL, "Reached tree area in error - discarding node with: %d objects", right - left + 1);
throw std::logic_error("invalid node overlap");
}
}
// find longest axis
axis = d.primaryAxis();
split = 0.5f * (gridBox.lo[axis] + gridBox.hi[axis]);
// partition L/R subsets
clipL = -G3D::inf();
clipR = G3D::inf();
rightOrig = right; // save this for later
float nodeL = G3D::inf();
float nodeR = -G3D::inf();
for (int i = left; i <= right;)
{
int obj = dat.indices[i];
float minb = dat.primBound[obj].low()[axis];
float maxb = dat.primBound[obj].high()[axis];
float center = (minb + maxb) * 0.5f;
if (center <= split)
{
// stay left
i++;
if (clipL < maxb)
{
clipL = maxb;
}
}
else
{
// move to the right most
int t = dat.indices[i];
dat.indices[i] = dat.indices[right];
dat.indices[right] = t;
right--;
if (clipR > minb)
{
clipR = minb;
}
}
nodeL = std::min(nodeL, minb);
nodeR = std::max(nodeR, maxb);
}
// check for empty space
if (nodeL > nodeBox.lo[axis] && nodeR < nodeBox.hi[axis])
{
float nodeBoxW = nodeBox.hi[axis] - nodeBox.lo[axis];
float nodeNewW = nodeR - nodeL;
// node box is too big compare to space occupied by primitives?
if (1.3f * nodeNewW < nodeBoxW)
{
stats.updateBVH2();
int nextIndex = tempTree.size();
// allocate child
tempTree.push_back(0);
tempTree.push_back(0);
tempTree.push_back(0);
// write bvh2 clip node
stats.updateInner();
tempTree[nodeIndex + 0] = (axis << 30) | (1 << 29) | nextIndex;
tempTree[nodeIndex + 1] = floatToRawIntBits(nodeL);
tempTree[nodeIndex + 2] = floatToRawIntBits(nodeR);
// update nodebox and recurse
nodeBox.lo[axis] = nodeL;
nodeBox.hi[axis] = nodeR;
subdivide(left, rightOrig, tempTree, dat, gridBox, nodeBox, nextIndex, depth + 1, stats);
return;
}
}
// ensure we are making progress in the subdivision
if (right == rightOrig)
{
// all left
if (prevAxis == axis && G3D::fuzzyEq(prevSplit, split))
{
// we are stuck here - create a leaf
stats.updateLeaf(depth, right - left + 1);
createNode(tempTree, nodeIndex, left, right);
return;
}
if (clipL <= split)
{
// keep looping on left half
gridBox.hi[axis] = split;
prevClip = clipL;
wasLeft = true;
continue;
}
gridBox.hi[axis] = split;
prevClip = G3D::fnan();
}
else if (left > right)
{
// all right
right = rightOrig;
if (prevAxis == axis && G3D::fuzzyEq(prevSplit, split))
{
// we are stuck here - create a leaf
stats.updateLeaf(depth, right - left + 1);
createNode(tempTree, nodeIndex, left, right);
return;
}
if (clipR >= split)
{
// keep looping on right half
gridBox.lo[axis] = split;
prevClip = clipR;
wasLeft = false;
continue;
}
gridBox.lo[axis] = split;
prevClip = G3D::fnan();
}
else
{
// we are actually splitting stuff
if (prevAxis != -1 && !isnan(prevClip))
{
// second time through - lets create the previous split
// since it produced empty space
int nextIndex = tempTree.size();
// allocate child node
tempTree.push_back(0);
tempTree.push_back(0);
tempTree.push_back(0);
if (wasLeft)
{
// create a node with a left child
// write leaf node
stats.updateInner();
tempTree[nodeIndex + 0] = (prevAxis << 30) | nextIndex;
tempTree[nodeIndex + 1] = floatToRawIntBits(prevClip);
tempTree[nodeIndex + 2] = floatToRawIntBits(G3D::inf());
}
else
{
// create a node with a right child
// write leaf node
stats.updateInner();
tempTree[nodeIndex + 0] = (prevAxis << 30) | (nextIndex - 3);
tempTree[nodeIndex + 1] = floatToRawIntBits(-G3D::inf());
tempTree[nodeIndex + 2] = floatToRawIntBits(prevClip);
}
// count stats for the unused leaf
depth++;
stats.updateLeaf(depth, 0);
// now we keep going as we are, with a new nodeIndex:
nodeIndex = nextIndex;
}
break;
}
}
// compute index of child nodes
int nextIndex = tempTree.size();
// allocate left node
int nl = right - left + 1;
int nr = rightOrig - (right + 1) + 1;
if (nl > 0)
{
tempTree.push_back(0);
tempTree.push_back(0);
tempTree.push_back(0);
}
else
{
nextIndex -= 3;
}
// allocate right node
if (nr > 0)
{
tempTree.push_back(0);
tempTree.push_back(0);
tempTree.push_back(0);
}
// write leaf node
stats.updateInner();
tempTree[nodeIndex + 0] = (axis << 30) | nextIndex;
tempTree[nodeIndex + 1] = floatToRawIntBits(clipL);
tempTree[nodeIndex + 2] = floatToRawIntBits(clipR);
// prepare L/R child boxes
AABound gridBoxL(gridBox), gridBoxR(gridBox);
AABound nodeBoxL(nodeBox), nodeBoxR(nodeBox);
gridBoxL.hi[axis] = gridBoxR.lo[axis] = split;
nodeBoxL.hi[axis] = clipL;
nodeBoxR.lo[axis] = clipR;
// recurse
if (nl > 0)
{
subdivide(left, right, tempTree, dat, gridBoxL, nodeBoxL, nextIndex, depth + 1, stats);
}
else
{
stats.updateLeaf(depth + 1, 0);
}
if (nr > 0)
{
subdivide(right + 1, rightOrig, tempTree, dat, gridBoxR, nodeBoxR, nextIndex + 3, depth + 1, stats);
}
else
{
stats.updateLeaf(depth + 1, 0);
}
}
bool BIH::writeToFile(FILE* wf) const
{
uint32 treeSize = tree.size();
uint32 check = 0, count;
check += fwrite(&bounds.low(), sizeof(float), 3, wf);
check += fwrite(&bounds.high(), sizeof(float), 3, wf);
check += fwrite(&treeSize, sizeof(uint32), 1, wf);
check += fwrite(&tree[0], sizeof(uint32), treeSize, wf);
count = objects.size();
check += fwrite(&count, sizeof(uint32), 1, wf);
check += fwrite(&objects[0], sizeof(uint32), count, wf);
return check == (3 + 3 + 2 + treeSize + count);
}
bool BIH::readFromFile(FILE* rf)
{
uint32 treeSize;
G3D::Vector3 lo, hi;
uint32 check = 0, count = 0;
check += fread(&lo, sizeof(float), 3, rf);
check += fread(&hi, sizeof(float), 3, rf);
bounds = G3D::AABox(lo, hi);
check += fread(&treeSize, sizeof(uint32), 1, rf);
tree.resize(treeSize);
check += fread(&tree[0], sizeof(uint32), treeSize, rf);
check += fread(&count, sizeof(uint32), 1, rf);
objects.resize(count); // = new uint32[nObjects];
check += fread(&objects[0], sizeof(uint32), count, rf);
return uint64(check) == uint64(3 + 3 + 1 + 1 + uint64(treeSize) + uint64(count));
}
void BIH::BuildStats::updateLeaf(int depth, int n)
{
numLeaves++;
minDepth = std::min(depth, minDepth);
maxDepth = std::max(depth, maxDepth);
sumDepth += depth;
minObjects = std::min(n, minObjects);
maxObjects = std::max(n, maxObjects);
sumObjects += n;
int nl = std::min(n, 5);
++numLeavesN[nl];
}
void BIH::BuildStats::printStats()
{
printf("Tree stats:\n");
printf(" * Nodes: %d\n", numNodes);
printf(" * Leaves: %d\n", numLeaves);
printf(" * Objects: min %d\n", minObjects);
printf(" avg %.2f\n", (float) sumObjects / numLeaves);
printf(" avg(n>0) %.2f\n", (float) sumObjects / (numLeaves - numLeavesN[0]));
printf(" max %d\n", maxObjects);
printf(" * Depth: min %d\n", minDepth);
printf(" avg %.2f\n", (float) sumDepth / numLeaves);
printf(" max %d\n", maxDepth);
printf(" * Leaves w/: N=0 %3d%%\n", 100 * numLeavesN[0] / numLeaves);
printf(" N=1 %3d%%\n", 100 * numLeavesN[1] / numLeaves);
printf(" N=2 %3d%%\n", 100 * numLeavesN[2] / numLeaves);
printf(" N=3 %3d%%\n", 100 * numLeavesN[3] / numLeaves);
printf(" N=4 %3d%%\n", 100 * numLeavesN[4] / numLeaves);
printf(" N>4 %3d%%\n", 100 * numLeavesN[5] / numLeaves);
printf(" * BVH2 nodes: %d (%3d%%)\n", numBVH2, 100 * numBVH2 / (numNodes + numLeaves - 2 * numBVH2));
}

View File

@@ -0,0 +1,435 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _BIH_H
#define _BIH_H
#include "G3D/AABox.h"
#include "G3D/Ray.h"
#include "G3D/Vector3.h"
#include "Define.h"
#include <algorithm>
#include <cmath>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <vector>
#define MAX_STACK_SIZE 64
// https://stackoverflow.com/a/4328396
static inline uint32 floatToRawIntBits(float f)
{
static_assert(sizeof(float) == sizeof(uint32), "Size of uint32 and float must be equal for this to work");
uint32 ret;
memcpy(&ret, &f, sizeof(float));
return ret;
}
static inline float intBitsToFloat(uint32 i)
{
static_assert(sizeof(float) == sizeof(uint32), "Size of uint32 and float must be equal for this to work");
float ret;
memcpy(&ret, &i, sizeof(uint32));
return ret;
}
struct AABound
{
G3D::Vector3 lo, hi;
};
/** Bounding Interval Hierarchy Class.
Building and Ray-Intersection functions based on BIH from
Sunflow, a Java Raytracer, released under MIT/X11 License
http://sunflow.sourceforge.net/
Copyright (c) 2003-2007 Christopher Kulla
*/
class BIH
{
private:
void init_empty()
{
tree.clear();
objects.clear();
// create space for the first node
tree.push_back(3u << 30u); // dummy leaf
tree.insert(tree.end(), 2, 0);
}
public:
BIH() { init_empty(); }
template< class BoundsFunc, class PrimArray >
void build(const PrimArray& primitives, BoundsFunc& GetBounds, uint32 leafSize = 3, bool printStats = false)
{
if (primitives.size() == 0)
{
init_empty();
return;
}
buildData dat;
dat.maxPrims = leafSize;
dat.numPrims = primitives.size();
dat.indices = new uint32[dat.numPrims];
dat.primBound = new G3D::AABox[dat.numPrims];
GetBounds(primitives[0], bounds);
for (uint32 i = 0; i < dat.numPrims; ++i)
{
dat.indices[i] = i;
GetBounds(primitives[i], dat.primBound[i]);
bounds.merge(dat.primBound[i]);
}
std::vector<uint32> tempTree;
BuildStats stats;
buildHierarchy(tempTree, dat, stats);
if (printStats)
{
stats.printStats();
}
objects.resize(dat.numPrims);
for (uint32 i = 0; i < dat.numPrims; ++i)
{
objects[i] = dat.indices[i];
}
//nObjects = dat.numPrims;
tree = tempTree;
delete[] dat.primBound;
delete[] dat.indices;
}
[[nodiscard]] uint32 primCount() const { return objects.size(); }
template<typename RayCallback>
void intersectRay(const G3D::Ray& r, RayCallback& intersectCallback, float& maxDist, bool stopAtFirstHit) const
{
float intervalMin = -1.f;
float intervalMax = -1.f;
G3D::Vector3 org = r.origin();
G3D::Vector3 dir = r.direction();
G3D::Vector3 invDir;
for (int i = 0; i < 3; ++i)
{
invDir[i] = 1.f / dir[i];
if (G3D::fuzzyNe(dir[i], 0.0f))
{
float t1 = (bounds.low()[i] - org[i]) * invDir[i];
float t2 = (bounds.high()[i] - org[i]) * invDir[i];
if (t1 > t2)
{
std::swap(t1, t2);
}
if (t1 > intervalMin)
{
intervalMin = t1;
}
if (t2 < intervalMax || intervalMax < 0.f)
{
intervalMax = t2;
}
// intervalMax can only become smaller for other axis,
// and intervalMin only larger respectively, so stop early
if (intervalMax <= 0 || intervalMin >= maxDist)
{
return;
}
}
}
if (intervalMin > intervalMax)
{
return;
}
intervalMin = std::max(intervalMin, 0.f);
intervalMax = std::min(intervalMax, maxDist);
uint32 offsetFront[3];
uint32 offsetBack[3];
uint32 offsetFront3[3];
uint32 offsetBack3[3];
// compute custom offsets from direction sign bit
for (int i = 0; i < 3; ++i)
{
offsetFront[i] = floatToRawIntBits(dir[i]) >> 31;
offsetBack[i] = offsetFront[i] ^ 1;
offsetFront3[i] = offsetFront[i] * 3;
offsetBack3[i] = offsetBack[i] * 3;
// avoid always adding 1 during the inner loop
++offsetFront[i];
++offsetBack[i];
}
StackNode stack[MAX_STACK_SIZE];
int stackPos = 0;
int node = 0;
while (true)
{
while (true)
{
uint32 tn = tree[node];
uint32 axis = (tn & (3 << 30)) >> 30; // cppcheck-suppress integerOverflow
bool BVH2 = tn & (1 << 29); // cppcheck-suppress integerOverflow
int offset = tn & ~(7 << 29); // cppcheck-suppress integerOverflow
if (!BVH2)
{
if (axis < 3)
{
// "normal" interior node
float tf = (intBitsToFloat(tree[node + offsetFront[axis]]) - org[axis]) * invDir[axis];
float tb = (intBitsToFloat(tree[node + offsetBack[axis]]) - org[axis]) * invDir[axis];
// ray passes between clip zones
if (tf < intervalMin && tb > intervalMax)
{
break;
}
int back = offset + offsetBack3[axis];
node = back;
// ray passes through far node only
if (tf < intervalMin)
{
intervalMin = (tb >= intervalMin) ? tb : intervalMin;
continue;
}
node = offset + offsetFront3[axis]; // front
// ray passes through near node only
if (tb > intervalMax)
{
intervalMax = (tf <= intervalMax) ? tf : intervalMax;
continue;
}
// ray passes through both nodes
// push back node
stack[stackPos].node = back;
stack[stackPos].tnear = (tb >= intervalMin) ? tb : intervalMin;
stack[stackPos].tfar = intervalMax;
stackPos++;
// update ray interval for front node
intervalMax = (tf <= intervalMax) ? tf : intervalMax;
continue;
}
else
{
// leaf - test some objects
int n = tree[node + 1];
while (n > 0)
{
bool hit = intersectCallback(r, objects[offset], maxDist, stopAtFirstHit);
if (stopAtFirstHit && hit) { return; }
--n;
++offset;
}
break;
}
}
else
{
if (axis > 2)
{
return; // should not happen
}
float tf = (intBitsToFloat(tree[node + offsetFront[axis]]) - org[axis]) * invDir[axis];
float tb = (intBitsToFloat(tree[node + offsetBack[axis]]) - org[axis]) * invDir[axis];
node = offset;
intervalMin = (tf >= intervalMin) ? tf : intervalMin;
intervalMax = (tb <= intervalMax) ? tb : intervalMax;
if (intervalMin > intervalMax)
{
break;
}
continue;
}
} // traversal loop
do
{
// stack is empty?
if (stackPos == 0)
{
return;
}
// move back up the stack
stackPos--;
intervalMin = stack[stackPos].tnear;
if (maxDist < intervalMin)
{
continue;
}
node = stack[stackPos].node;
intervalMax = stack[stackPos].tfar;
break;
} while (true);
}
}
template<typename IsectCallback>
void intersectPoint(const G3D::Vector3& p, IsectCallback& intersectCallback) const
{
if (!bounds.contains(p))
{
return;
}
StackNode stack[MAX_STACK_SIZE];
int stackPos = 0;
int node = 0;
while (true)
{
while (true)
{
uint32 tn = tree[node];
uint32 axis = (tn & (3 << 30)) >> 30; // cppcheck-suppress integerOverflow
bool BVH2 = tn & (1 << 29); // cppcheck-suppress integerOverflow
int offset = tn & ~(7 << 29); // cppcheck-suppress integerOverflow
if (!BVH2)
{
if (axis < 3)
{
// "normal" interior node
float tl = intBitsToFloat(tree[node + 1]);
float tr = intBitsToFloat(tree[node + 2]);
// point is between clip zones
if (tl < p[axis] && tr > p[axis])
{
break;
}
int right = offset + 3;
node = right;
// point is in right node only
if (tl < p[axis])
{
continue;
}
node = offset; // left
// point is in left node only
if (tr > p[axis])
{
continue;
}
// point is in both nodes
// push back right node
stack[stackPos].node = right;
stackPos++;
continue;
}
else
{
// leaf - test some objects
int n = tree[node + 1];
while (n > 0)
{
intersectCallback(p, objects[offset]); // !!!
--n;
++offset;
}
break;
}
}
else // BVH2 node (empty space cut off left and right)
{
if (axis > 2)
{
return; // should not happen
}
float tl = intBitsToFloat(tree[node + 1]);
float tr = intBitsToFloat(tree[node + 2]);
node = offset;
if (tl > p[axis] || tr < p[axis])
{
break;
}
continue;
}
} // traversal loop
// stack is empty?
if (stackPos == 0)
{
return;
}
// move back up the stack
stackPos--;
node = stack[stackPos].node;
}
}
bool writeToFile(FILE* wf) const;
bool readFromFile(FILE* rf);
protected:
std::vector<uint32> tree;
std::vector<uint32> objects;
G3D::AABox bounds;
struct buildData
{
uint32* indices;
G3D::AABox* primBound;
uint32 numPrims;
int maxPrims;
};
struct StackNode
{
uint32 node;
float tnear;
float tfar;
};
class BuildStats
{
private:
int numNodes{0};
int numLeaves{0};
int sumObjects{0};
int minObjects{0x0FFFFFFF};
int maxObjects{-1}; // 0xFFFFFFFF
int sumDepth{0};
int minDepth{0x0FFFFFFF};
int maxDepth{-1}; // 0xFFFFFFFF
int numLeavesN[6];
int numBVH2{0};
public:
BuildStats()
{
for (int& i : numLeavesN) { i = 0; }
}
void updateInner() { numNodes++; }
void updateBVH2() { numBVH2++; }
void updateLeaf(int depth, int n);
void printStats();
};
void buildHierarchy(std::vector<uint32>& tempTree, buildData& dat, BuildStats& stats);
void createNode(std::vector<uint32>& tempTree, int nodeIndex, uint32 left, uint32 right) const
{
// write leaf node
tempTree[nodeIndex + 0] = (3 << 30) | left; // cppcheck-suppress integerOverflow
tempTree[nodeIndex + 1] = right - left + 1;
}
void subdivide(int left, int right, std::vector<uint32>& tempTree, buildData& dat, AABound& gridBox, AABound& nodeBox, int nodeIndex, int depth, BuildStats& stats);
};
#endif // _BIH_H

View File

@@ -0,0 +1,131 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _BIH_WRAP
#define _BIH_WRAP
#include "BoundingIntervalHierarchy.h"
#include "G3D/Array.h"
#include "G3D/Set.h"
#include "G3D/Table.h"
template<class T, class BoundsFunc = BoundsTrait<T>>
class BIHWrap
{
template<class RayCallback>
struct MDLCallback
{
const T* const* objects;
RayCallback& _callback;
uint32 objects_size;
MDLCallback(RayCallback& callback, const T* const* objects_array, uint32 objects_size ) : objects(objects_array), _callback(callback), objects_size(objects_size) { }
/// Intersect ray
bool operator() (const G3D::Ray& ray, uint32 idx, float& maxDist, bool stopAtFirstHit)
{
if (idx >= objects_size)
{
return false;
}
if (const T* obj = objects[idx])
{
return _callback(ray, *obj, maxDist, stopAtFirstHit);
}
return false;
}
/// Intersect point
void operator() (const G3D::Vector3& p, uint32 idx)
{
if (idx >= objects_size)
{
return;
}
if (const T* obj = objects[idx])
{
_callback(p, *obj);
}
}
};
typedef G3D::Array<const T*> ObjArray;
BIH m_tree;
ObjArray m_objects;
G3D::Table<const T*, uint32> m_obj2Idx;
G3D::Set<const T*> m_objects_to_push;
int unbalanced_times;
public:
BIHWrap() : unbalanced_times(0) { }
void insert(const T& obj)
{
++unbalanced_times;
m_objects_to_push.insert(&obj);
}
void remove(const T& obj)
{
++unbalanced_times;
uint32 Idx = 0;
const T* temp;
if (m_obj2Idx.getRemove(&obj, temp, Idx))
{
m_objects[Idx] = nullptr;
}
else
{
m_objects_to_push.remove(&obj);
}
}
void balance()
{
if (unbalanced_times == 0)
{
return;
}
unbalanced_times = 0;
m_objects.fastClear();
m_obj2Idx.getKeys(m_objects);
m_objects_to_push.getMembers(m_objects);
//assert that m_obj2Idx has all the keys
m_tree.build(m_objects, BoundsFunc::GetBounds2);
}
template<typename RayCallback>
void intersectRay(const G3D::Ray& ray, RayCallback& intersectCallback, float& maxDist, bool stopAtFirstHit)
{
balance();
MDLCallback<RayCallback> temp_cb(intersectCallback, m_objects.getCArray(), m_objects.size());
m_tree.intersectRay(ray, temp_cb, maxDist, stopAtFirstHit);
}
template<typename IsectCallback>
void intersectPoint(const G3D::Vector3& point, IsectCallback& intersectCallback)
{
balance();
MDLCallback<IsectCallback> callback(intersectCallback, m_objects.getCArray(), m_objects.size());
m_tree.intersectPoint(point, callback);
}
};
#endif // _BIH_WRAP

View File

@@ -0,0 +1,347 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "DynamicTree.h"
#include "BoundingIntervalHierarchyWrapper.h"
#include "GameObjectModel.h"
#include "MapTree.h"
#include "ModelIgnoreFlags.h"
#include "ModelInstance.h"
#include "RegularGrid.h"
#include "Timer.h"
#include "VMapFactory.h"
#include "VMapMgr2.h"
#include "WorldModel.h"
#include <G3D/AABox.h>
#include <G3D/Ray.h>
#include <G3D/Vector3.h>
using VMAP::ModelInstance;
namespace
{
int CHECK_TREE_PERIOD = 200;
}
template<> struct HashTrait< GameObjectModel>
{
static size_t hashCode(const GameObjectModel& g) { return (size_t)(void*)&g; }
};
template<> struct PositionTrait< GameObjectModel>
{
static void GetPosition(const GameObjectModel& g, G3D::Vector3& p) { p = g.GetPosition(); }
};
template<> struct BoundsTrait< GameObjectModel>
{
static void GetBounds(const GameObjectModel& g, G3D::AABox& out) { out = g.GetBounds();}
static void GetBounds2(const GameObjectModel* g, G3D::AABox& out) { out = g->GetBounds();}
};
typedef RegularGrid2D<GameObjectModel, BIHWrap<GameObjectModel>> ParentTree;
struct DynTreeImpl : public ParentTree
{
typedef GameObjectModel Model;
typedef ParentTree base;
DynTreeImpl() :
rebalance_timer(CHECK_TREE_PERIOD),
unbalanced_times(0)
{
}
void insert(const Model& mdl)
{
base::insert(mdl);
++unbalanced_times;
}
void remove(const Model& mdl)
{
base::remove(mdl);
++unbalanced_times;
}
void balance()
{
base::balance();
unbalanced_times = 0;
}
void update(uint32 difftime)
{
if (!size())
{
return;
}
rebalance_timer.Update(difftime);
if (rebalance_timer.Passed())
{
rebalance_timer.Reset(CHECK_TREE_PERIOD);
if (unbalanced_times > 0)
{
balance();
}
}
}
TimeTrackerSmall rebalance_timer;
int unbalanced_times;
};
DynamicMapTree::DynamicMapTree() : impl(new DynTreeImpl()) { }
DynamicMapTree::~DynamicMapTree()
{
delete impl;
}
void DynamicMapTree::insert(const GameObjectModel& mdl)
{
impl->insert(mdl);
}
void DynamicMapTree::remove(const GameObjectModel& mdl)
{
impl->remove(mdl);
}
bool DynamicMapTree::contains(const GameObjectModel& mdl) const
{
return impl->contains(mdl);
}
void DynamicMapTree::balance()
{
impl->balance();
}
int DynamicMapTree::size() const
{
return impl->size();
}
void DynamicMapTree::update(uint32 t_diff)
{
impl->update(t_diff);
}
struct DynamicTreeIntersectionCallback
{
DynamicTreeIntersectionCallback(uint32 phasemask, VMAP::ModelIgnoreFlags ignoreFlags) :
_didHit(false), _phaseMask(phasemask), _ignoreFlags(ignoreFlags) { }
bool operator()(const G3D::Ray& r, const GameObjectModel& obj, float& distance, bool stopAtFirstHit)
{
bool result = obj.intersectRay(r, distance, stopAtFirstHit, _phaseMask, _ignoreFlags);
if (result)
{
_didHit = result;
}
return result;
}
[[nodiscard]] bool didHit() const
{
return _didHit;
}
private:
bool _didHit;
uint32 _phaseMask;
VMAP::ModelIgnoreFlags _ignoreFlags;
};
struct DynamicTreeAreaInfoCallback
{
DynamicTreeAreaInfoCallback(uint32 phaseMask) : _phaseMask(phaseMask) { }
void operator()(G3D::Vector3 const& p, GameObjectModel const& obj)
{
obj.IntersectPoint(p, _areaInfo, _phaseMask);
}
VMAP::AreaInfo const& GetAreaInfo() const
{
return _areaInfo;
}
private:
uint32 _phaseMask;
VMAP::AreaInfo _areaInfo;
};
struct DynamicTreeLocationInfoCallback
{
DynamicTreeLocationInfoCallback(uint32 phaseMask)
: _phaseMask(phaseMask), _hitModel(nullptr) {}
void operator()(G3D::Vector3 const& p, GameObjectModel const& obj)
{
if (obj.GetLocationInfo(p, _locationInfo, _phaseMask))
_hitModel = &obj;
}
VMAP::LocationInfo& GetLocationInfo()
{
return _locationInfo;
}
GameObjectModel const* GetHitModel() const
{
return _hitModel;
}
private:
uint32 _phaseMask;
VMAP::LocationInfo _locationInfo;
GameObjectModel const* _hitModel;
};
bool DynamicMapTree::GetIntersectionTime(const uint32 phasemask, const G3D::Ray& ray, const G3D::Vector3& endPos, float& maxDist) const
{
float distance = maxDist;
DynamicTreeIntersectionCallback callback(phasemask, VMAP::ModelIgnoreFlags::Nothing);
impl->intersectRay(ray, callback, distance, endPos, false);
if (callback.didHit())
{
maxDist = distance;
}
return callback.didHit();
}
bool DynamicMapTree::GetObjectHitPos(const uint32 phasemask, const G3D::Vector3& startPos,
const G3D::Vector3& endPos, G3D::Vector3& resultHit,
float modifyDist) const
{
bool result = false;
float maxDist = (endPos - startPos).magnitude();
// valid map coords should *never ever* produce float overflow, but this would produce NaNs too
ASSERT(maxDist < std::numeric_limits<float>::max());
// prevent NaN values which can cause BIH intersection to enter infinite loop
if (maxDist < 1e-10f)
{
resultHit = endPos;
return false;
}
G3D::Vector3 dir = (endPos - startPos) / maxDist; // direction with length of 1
G3D::Ray ray(startPos, dir);
float dist = maxDist;
if (GetIntersectionTime(phasemask, ray, endPos, dist))
{
resultHit = startPos + dir * dist;
if (modifyDist < 0)
{
if ((resultHit - startPos).magnitude() > -modifyDist)
{
resultHit = resultHit + dir * modifyDist;
}
else
{
resultHit = startPos;
}
}
else
{
resultHit = resultHit + dir * modifyDist;
}
result = true;
}
else
{
resultHit = endPos;
result = false;
}
return result;
}
bool DynamicMapTree::isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, uint32 phasemask, VMAP::ModelIgnoreFlags ignoreFlags) const
{
G3D::Vector3 v1(x1, y1, z1), v2(x2, y2, z2);
float maxDist = (v2 - v1).magnitude();
if (!G3D::fuzzyGt(maxDist, 0) )
{
return true;
}
G3D::Ray r(v1, (v2 - v1) / maxDist);
DynamicTreeIntersectionCallback callback(phasemask, ignoreFlags);
impl->intersectRay(r, callback, maxDist, v2, true);
return !callback.didHit();
}
float DynamicMapTree::getHeight(float x, float y, float z, float maxSearchDist, uint32 phasemask) const
{
G3D::Vector3 v(x, y, z);
G3D::Ray r(v, G3D::Vector3(0, 0, -1));
DynamicTreeIntersectionCallback callback(phasemask, VMAP::ModelIgnoreFlags::Nothing);
impl->intersectZAllignedRay(r, callback, maxSearchDist);
if (callback.didHit())
{
return v.z - maxSearchDist;
}
else
{
return -G3D::finf();
}
}
bool DynamicMapTree::GetAreaInfo(float x, float y, float& z, uint32 phasemask, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const
{
G3D::Vector3 v(x, y, z + 0.5f);
DynamicTreeAreaInfoCallback intersectionCallBack(phasemask);
impl->intersectPoint(v, intersectionCallBack);
if (intersectionCallBack.GetAreaInfo().result)
{
flags = intersectionCallBack.GetAreaInfo().flags;
adtId = intersectionCallBack.GetAreaInfo().adtId;
rootId = intersectionCallBack.GetAreaInfo().rootId;
groupId = intersectionCallBack.GetAreaInfo().groupId;
z = intersectionCallBack.GetAreaInfo().ground_Z;
return true;
}
return false;
}
void DynamicMapTree::GetAreaAndLiquidData(float x, float y, float z, uint32 phasemask, uint8 reqLiquidType, VMAP::AreaAndLiquidData& data) const
{
G3D::Vector3 v(x, y, z + 0.5f);
DynamicTreeLocationInfoCallback intersectionCallBack(phasemask);
impl->intersectPoint(v, intersectionCallBack);
if (intersectionCallBack.GetLocationInfo().hitModel)
{
data.floorZ = intersectionCallBack.GetLocationInfo().ground_Z;
uint32 liquidType = intersectionCallBack.GetLocationInfo().hitModel->GetLiquidType();
float liquidLevel;
if (!reqLiquidType || (dynamic_cast<VMAP::VMapMgr2*>(VMAP::VMapFactory::createOrGetVMapMgr())->GetLiquidFlagsPtr(liquidType) & reqLiquidType))
if (intersectionCallBack.GetHitModel()->GetLiquidLevel(v, intersectionCallBack.GetLocationInfo(), liquidLevel))
data.liquidInfo.emplace(liquidType, liquidLevel);
data.areaInfo.emplace(0,
intersectionCallBack.GetLocationInfo().rootId,
intersectionCallBack.GetLocationInfo().hitModel->GetWmoID(),
intersectionCallBack.GetLocationInfo().hitModel->GetMogpFlags());
}
}

View File

@@ -0,0 +1,68 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _DYNTREE_H
#define _DYNTREE_H
#include "Define.h"
namespace G3D
{
class Ray;
class Vector3;
}
namespace VMAP
{
struct AreaAndLiquidData;
enum class ModelIgnoreFlags : uint32;
}
class GameObjectModel;
struct DynTreeImpl;
class DynamicMapTree
{
DynTreeImpl* impl;
public:
DynamicMapTree();
~DynamicMapTree();
[[nodiscard]] bool isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, uint32 phasemask, VMAP::ModelIgnoreFlags ignoreFlags) const;
bool GetIntersectionTime(uint32 phasemask, const G3D::Ray& ray, const G3D::Vector3& endPos, float& maxDist) const;
bool GetAreaInfo(float x, float y, float& z, uint32 phasemask, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const;
void GetAreaAndLiquidData(float x, float y, float z, uint32 phasemask, uint8 reqLiquidType, VMAP::AreaAndLiquidData& data) const;
bool GetObjectHitPos(uint32 phasemask, const G3D::Vector3& pPos1,
const G3D::Vector3& pPos2, G3D::Vector3& pResultHitPos,
float pModifyDist) const;
[[nodiscard]] float getHeight(float x, float y, float z, float maxSearchDist, uint32 phasemask) const;
void insert(const GameObjectModel&);
void remove(const GameObjectModel&);
[[nodiscard]] bool contains(const GameObjectModel&) const;
[[nodiscard]] int size() const;
void balance();
void update(uint32 diff);
};
#endif // _DYNTREE_H

View File

@@ -0,0 +1,49 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _IMMAPMANAGER_H
#define _IMMAPMANAGER_H
#include "Define.h"
#include <string>
// Interface for IMMapManger
namespace MMAP
{
enum MMAP_LOAD_RESULT
{
MMAP_LOAD_RESULT_ERROR,
MMAP_LOAD_RESULT_OK,
MMAP_LOAD_RESULT_IGNORED,
};
class IMMapMgr
{
private:
bool iEnablePathFinding;
public:
IMMapMgr() : iEnablePathFinding(true) {}
virtual ~IMMapMgr(void) {}
//Enabled/Disabled Pathfinding
void setEnablePathFinding(bool value) { iEnablePathFinding = value; }
bool isEnablePathFinding() const { return (iEnablePathFinding); }
};
}
#endif

View File

@@ -0,0 +1,134 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _IVMAPMANAGER_H
#define _IVMAPMANAGER_H
#include "Define.h"
#include "ModelIgnoreFlags.h"
#include "Optional.h"
#include <string>
//===========================================================
/**
This is the minimum interface to the VMapMamager.
*/
namespace VMAP
{
enum VMAP_LOAD_RESULT
{
VMAP_LOAD_RESULT_ERROR,
VMAP_LOAD_RESULT_OK,
VMAP_LOAD_RESULT_IGNORED
};
enum class LoadResult : uint8
{
Success,
FileNotFound,
VersionMismatch
};
#define VMAP_INVALID_HEIGHT -100000.0f // for check
#define VMAP_INVALID_HEIGHT_VALUE -200000.0f // real assigned value in unknown height case
struct AreaAndLiquidData
{
struct AreaInfo
{
AreaInfo(int32 _adtId, int32 _rootId, int32 _groupId, uint32 _flags)
: adtId(_adtId), rootId(_rootId), groupId(_groupId), mogpFlags(_flags) { }
int32 const adtId;
int32 const rootId;
int32 const groupId;
uint32 const mogpFlags;
};
struct LiquidInfo
{
LiquidInfo(uint32 _type, float _level)
: type(_type), level(_level) {}
uint32 const type;
float const level;
};
float floorZ = VMAP_INVALID_HEIGHT;
Optional<AreaInfo> areaInfo;
Optional<LiquidInfo> liquidInfo;
};
//===========================================================
class IVMapMgr
{
private:
bool iEnableLineOfSightCalc{true};
bool iEnableHeightCalc{true};
public:
IVMapMgr() { }
virtual ~IVMapMgr() = default;
virtual int loadMap(const char* pBasePath, unsigned int pMapId, int x, int y) = 0;
virtual LoadResult existsMap(const char* pBasePath, unsigned int pMapId, int x, int y) = 0;
virtual void unloadMap(unsigned int pMapId, int x, int y) = 0;
virtual void unloadMap(unsigned int pMapId) = 0;
virtual bool isInLineOfSight(unsigned int pMapId, float x1, float y1, float z1, float x2, float y2, float z2, ModelIgnoreFlags ignoreFlags) = 0;
virtual float getHeight(unsigned int pMapId, float x, float y, float z, float maxSearchDist) = 0;
/**
test if we hit an object. return true if we hit one. rx, ry, rz will hold the hit position or the dest position, if no intersection was found
return a position, that is pReduceDist closer to the origin
*/
virtual bool GetObjectHitPos(unsigned int pMapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float pModifyDist) = 0;
/**
send debug commands
*/
virtual bool processCommand(char* pCommand) = 0;
/**
Enable/disable LOS calculation
It is enabled by default. If it is enabled in mid game the maps have to loaded manualy
*/
void setEnableLineOfSightCalc(bool pVal) { iEnableLineOfSightCalc = pVal; }
/**
Enable/disable model height calculation
It is enabled by default. If it is enabled in mid game the maps have to loaded manualy
*/
void setEnableHeightCalc(bool pVal) { iEnableHeightCalc = pVal; }
[[nodiscard]] bool isLineOfSightCalcEnabled() const { return (iEnableLineOfSightCalc); }
[[nodiscard]] bool isHeightCalcEnabled() const { return (iEnableHeightCalc); }
[[nodiscard]] bool isMapLoadingEnabled() const { return (iEnableLineOfSightCalc || iEnableHeightCalc ); }
[[nodiscard]] virtual std::string getDirFileName(unsigned int pMapId, int x, int y) const = 0;
/**
Query world model area info.
\param z gets adjusted to the ground height for which this are info is valid
*/
virtual bool GetAreaInfo(uint32 pMapId, float x, float y, float& z, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const = 0;
virtual bool GetLiquidLevel(uint32 pMapId, float x, float y, float z, uint8 ReqLiquidType, float& level, float& floor, uint32& type, uint32& mogpFlags) const = 0;
// get both area + liquid data in a single vmap lookup
virtual void GetAreaAndLiquidData(uint32 mapId, float x, float y, float z, uint8 reqLiquidType, AreaAndLiquidData& data) const = 0;
};
}
#endif

View File

@@ -0,0 +1,58 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "MMapFactory.h"
#include <cstring>
#include <set>
namespace MMAP
{
// ######################## MMapFactory ########################
// our global singleton copy
MMapMgr* g_MMapMgr = nullptr;
bool MMapFactory::forbiddenMaps[1000] = {0};
MMapMgr* MMapFactory::createOrGetMMapMgr()
{
if (!g_MMapMgr)
{
g_MMapMgr = new MMapMgr();
}
return g_MMapMgr;
}
void MMapFactory::InitializeDisabledMaps()
{
memset(&forbiddenMaps, 0, sizeof(forbiddenMaps));
int32 f[] = {616 /*EoE*/, 649 /*ToC25*/, 650 /*ToC5*/, -1};
uint32 i = 0;
while (f[i] >= 0)
{
forbiddenMaps[f[i++]] = true;
}
}
void MMapFactory::clear()
{
if (g_MMapMgr)
{
delete g_MMapMgr;
g_MMapMgr = nullptr;
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _MMAP_FACTORY_H
#define _MMAP_FACTORY_H
#include "DetourAlloc.h"
#include "DetourExtended.h"
#include "DetourNavMesh.h"
#include "MMapMgr.h"
#include <unordered_map>
namespace MMAP
{
enum MMAP_LOAD_RESULT
{
MMAP_LOAD_RESULT_ERROR,
MMAP_LOAD_RESULT_OK,
MMAP_LOAD_RESULT_IGNORED,
};
// static class
// holds all mmap global data
// access point to MMapMgr singleton
class MMapFactory
{
public:
static MMapMgr* createOrGetMMapMgr();
static void clear();
static void InitializeDisabledMaps();
static bool forbiddenMaps[1000];
};
}
#endif

View File

@@ -0,0 +1,349 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "MMapMgr.h"
#include "Config.h"
#include "Errors.h"
#include "Log.h"
#include "MapDefines.h"
namespace MMAP
{
static char const* const MAP_FILE_NAME_FORMAT = "%s/mmaps/%03i.mmap";
static char const* const TILE_FILE_NAME_FORMAT = "%s/mmaps/%03i%02i%02i.mmtile";
// ######################## MMapMgr ########################
MMapMgr::~MMapMgr()
{
for (MMapDataSet::iterator i = loadedMMaps.begin(); i != loadedMMaps.end(); ++i)
{
delete i->second;
}
// by now we should not have maps loaded
// if we had, tiles in MMapData->mmapLoadedTiles, their actual data is lost!
}
void MMapMgr::InitializeThreadUnsafe(const std::vector<uint32>& mapIds)
{
// the caller must pass the list of all mapIds that will be used in the VMapMgr2 lifetime
for (const uint32& mapId : mapIds)
{
loadedMMaps.emplace(mapId, nullptr);
}
thread_safe_environment = false;
}
MMapDataSet::const_iterator MMapMgr::GetMMapData(uint32 mapId) const
{
// return the iterator if found or end() if not found/NULL
MMapDataSet::const_iterator itr = loadedMMaps.find(mapId);
if (itr != loadedMMaps.cend() && !itr->second)
{
itr = loadedMMaps.cend();
}
return itr;
}
bool MMapMgr::loadMapData(uint32 mapId)
{
// we already have this map loaded?
MMapDataSet::iterator itr = loadedMMaps.find(mapId);
if (itr != loadedMMaps.end())
{
if (itr->second)
{
return true;
}
}
else
{
if (thread_safe_environment)
{
itr = loadedMMaps.insert(MMapDataSet::value_type(mapId, nullptr)).first;
}
else
{
ABORT("Invalid mapId {} passed to MMapMgr after startup in thread unsafe environment", mapId);
}
}
// load and init dtNavMesh - read parameters from file
std::string fileName = Acore::StringFormat(MAP_FILE_NAME_FORMAT, sConfigMgr->GetOption<std::string>("DataDir", ".").c_str(), mapId);
FILE* file = fopen(fileName.c_str(), "rb");
if (!file)
{
LOG_DEBUG("maps", "MMAP:loadMapData: Error: Could not open mmap file '{}'", fileName);
return false;
}
dtNavMeshParams params;
uint32 count = uint32(fread(&params, sizeof(dtNavMeshParams), 1, file));
fclose(file);
if (count != 1)
{
LOG_DEBUG("maps", "MMAP:loadMapData: Error: Could not read params from file '{}'", fileName);
return false;
}
dtNavMesh* mesh = dtAllocNavMesh();
ASSERT(mesh);
if (DT_SUCCESS != mesh->init(&params))
{
dtFreeNavMesh(mesh);
LOG_ERROR("maps", "MMAP:loadMapData: Failed to initialize dtNavMesh for mmap {:03} from file {}", mapId, fileName);
return false;
}
LOG_DEBUG("maps", "MMAP:loadMapData: Loaded {:03}.mmap", mapId);
// store inside our map list
MMapData* mmap_data = new MMapData(mesh);
itr->second = mmap_data;
return true;
}
uint32 MMapMgr::packTileID(int32 x, int32 y)
{
return uint32(x << 16 | y);
}
bool MMapMgr::loadMap(uint32 mapId, int32 x, int32 y)
{
// make sure the mmap is loaded and ready to load tiles
if (!loadMapData(mapId))
{
return false;
}
// get this mmap data
MMapData* mmap = loadedMMaps[mapId];
ASSERT(mmap->navMesh);
// check if we already have this tile loaded
uint32 packedGridPos = packTileID(x, y);
if (mmap->loadedTileRefs.find(packedGridPos) != mmap->loadedTileRefs.end())
{
LOG_ERROR("maps", "MMAP:loadMap: Asked to load already loaded navmesh tile. {:03}{:02}{:02}.mmtile", mapId, x, y);
return false;
}
// load this tile :: mmaps/MMMXXYY.mmtile
std::string fileName = Acore::StringFormat(TILE_FILE_NAME_FORMAT, sConfigMgr->GetOption<std::string>("DataDir", ".").c_str(), mapId, x, y);
FILE* file = fopen(fileName.c_str(), "rb");
if (!file)
{
LOG_DEBUG("maps", "MMAP:loadMap: Could not open mmtile file '{}'", fileName);
return false;
}
// read header
MmapTileHeader fileHeader;
if (fread(&fileHeader, sizeof(MmapTileHeader), 1, file) != 1 || fileHeader.mmapMagic != MMAP_MAGIC)
{
LOG_ERROR("maps", "MMAP:loadMap: Bad header in mmap {:03}{:02}{:02}.mmtile", mapId, x, y);
fclose(file);
return false;
}
if (fileHeader.mmapVersion != MMAP_VERSION)
{
LOG_ERROR("maps", "MMAP:loadMap: {:03}{:02}{:02}.mmtile was built with generator v{}, expected v{}",
mapId, x, y, fileHeader.mmapVersion, MMAP_VERSION);
fclose(file);
return false;
}
unsigned char* data = (unsigned char*)dtAlloc(fileHeader.size, DT_ALLOC_PERM);
ASSERT(data);
size_t result = fread(data, fileHeader.size, 1, file);
if (!result)
{
LOG_ERROR("maps", "MMAP:loadMap: Bad header or data in mmap {:03}{:02}{:02}.mmtile", mapId, x, y);
fclose(file);
return false;
}
fclose(file);
dtTileRef tileRef = 0;
// memory allocated for data is now managed by detour, and will be deallocated when the tile is removed
if (dtStatusSucceed(mmap->navMesh->addTile(data, fileHeader.size, DT_TILE_FREE_DATA, 0, &tileRef)))
{
mmap->loadedTileRefs.insert(std::pair<uint32, dtTileRef>(packedGridPos, tileRef));
++loadedTiles;
dtMeshHeader* header = (dtMeshHeader*)data;
LOG_DEBUG("maps", "MMAP:loadMap: Loaded mmtile {:03}[{:02},{:02}] into {:03}[{:02},{:02}]", mapId, x, y, mapId, header->x, header->y);
return true;
}
LOG_ERROR("maps", "MMAP:loadMap: Could not load {:03}{:02}{:02}.mmtile into navmesh", mapId, x, y);
dtFree(data);
return false;
}
bool MMapMgr::unloadMap(uint32 mapId, int32 x, int32 y)
{
// check if we have this map loaded
MMapDataSet::const_iterator itr = GetMMapData(mapId);
if (itr == loadedMMaps.end())
{
// file may not exist, therefore not loaded
LOG_DEBUG("maps", "MMAP:unloadMap: Asked to unload not loaded navmesh map. {:03}{:02}{:02}.mmtile", mapId, x, y);
return false;
}
MMapData* mmap = itr->second;
// check if we have this tile loaded
uint32 packedGridPos = packTileID(x, y);
if (mmap->loadedTileRefs.find(packedGridPos) == mmap->loadedTileRefs.end())
{
// file may not exist, therefore not loaded
LOG_DEBUG("maps", "MMAP:unloadMap: Asked to unload not loaded navmesh tile. {:03}{:02}{:02}.mmtile", mapId, x, y);
return false;
}
dtTileRef tileRef = mmap->loadedTileRefs[packedGridPos];
// unload, and mark as non loaded
if (dtStatusFailed(mmap->navMesh->removeTile(tileRef, nullptr, nullptr)))
{
// this is technically a memory leak
// if the grid is later reloaded, dtNavMesh::addTile will return error but no extra memory is used
// we cannot recover from this error - assert out
LOG_ERROR("maps", "MMAP:unloadMap: Could not unload {:03}{:02}{:02}.mmtile from navmesh", mapId, x, y);
ABORT();
}
mmap->loadedTileRefs.erase(packedGridPos);
--loadedTiles;
LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded mmtile {:03}[{:02},{:02}] from {:03}", mapId, x, y, mapId);
return true;
}
bool MMapMgr::unloadMap(uint32 mapId)
{
MMapDataSet::iterator itr = loadedMMaps.find(mapId);
if (itr == loadedMMaps.end() || !itr->second)
{
// file may not exist, therefore not loaded
LOG_DEBUG("maps", "MMAP:unloadMap: Asked to unload not loaded navmesh map {:03}", mapId);
return false;
}
// unload all tiles from given map
MMapData* mmap = itr->second;
for (auto& i : mmap->loadedTileRefs)
{
uint32 x = (i.first >> 16);
uint32 y = (i.first & 0x0000FFFF);
if (dtStatusFailed(mmap->navMesh->removeTile(i.second, nullptr, nullptr)))
{
LOG_ERROR("maps", "MMAP:unloadMap: Could not unload {:03}{:02}{:02}.mmtile from navmesh", mapId, x, y);
}
else
{
--loadedTiles;
LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded mmtile {:03}[{:02},{:02}] from {:03}", mapId, x, y, mapId);
}
}
delete mmap;
itr->second = nullptr;
LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded {:03}.mmap", mapId);
return true;
}
bool MMapMgr::unloadMapInstance(uint32 mapId, uint32 instanceId)
{
// check if we have this map loaded
MMapDataSet::const_iterator itr = GetMMapData(mapId);
if (itr == loadedMMaps.end())
{
// file may not exist, therefore not loaded
LOG_DEBUG("maps", "MMAP:unloadMapInstance: Asked to unload not loaded navmesh map {:03}", mapId);
return false;
}
MMapData* mmap = itr->second;
if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end())
{
LOG_DEBUG("maps", "MMAP:unloadMapInstance: Asked to unload not loaded dtNavMeshQuery mapId {:03} instanceId {}", mapId, instanceId);
return false;
}
dtNavMeshQuery* query = mmap->navMeshQueries[instanceId];
dtFreeNavMeshQuery(query);
mmap->navMeshQueries.erase(instanceId);
LOG_DEBUG("maps", "MMAP:unloadMapInstance: Unloaded mapId {:03} instanceId {}", mapId, instanceId);
return true;
}
dtNavMesh const* MMapMgr::GetNavMesh(uint32 mapId)
{
MMapDataSet::const_iterator itr = GetMMapData(mapId);
if (itr == loadedMMaps.end())
{
return nullptr;
}
return itr->second->navMesh;
}
dtNavMeshQuery const* MMapMgr::GetNavMeshQuery(uint32 mapId, uint32 instanceId)
{
MMapDataSet::const_iterator itr = GetMMapData(mapId);
if (itr == loadedMMaps.end())
{
return nullptr;
}
MMapData* mmap = itr->second;
if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end())
{
// check again after acquiring mutex
if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end())
{
// allocate mesh query
dtNavMeshQuery* query = dtAllocNavMeshQuery();
ASSERT(query);
if (dtStatusFailed(query->init(mmap->navMesh, 1024)))
{
dtFreeNavMeshQuery(query);
LOG_ERROR("maps", "MMAP:GetNavMeshQuery: Failed to initialize dtNavMeshQuery for mapId {:03} instanceId {}", mapId, instanceId);
return nullptr;
}
LOG_DEBUG("maps", "MMAP:GetNavMeshQuery: created dtNavMeshQuery for mapId {:03} instanceId {}", mapId, instanceId);
mmap->navMeshQueries.insert(std::pair<uint32, dtNavMeshQuery*>(instanceId, query));
}
}
return mmap->navMeshQueries[instanceId];
}
}

View File

@@ -0,0 +1,104 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _MMAP_MANAGER_H
#define _MMAP_MANAGER_H
#include "Common.h"
#include "DetourAlloc.h"
#include "DetourExtended.h"
#include "DetourNavMesh.h"
#include <shared_mutex>
#include <unordered_map>
#include <vector>
// memory management
inline void* dtCustomAlloc(size_t size, dtAllocHint /*hint*/)
{
return (void*)new unsigned char[size];
}
inline void dtCustomFree(void* ptr)
{
delete [] (unsigned char*)ptr;
}
// move map related classes
namespace MMAP
{
typedef std::unordered_map<uint32, dtTileRef> MMapTileSet;
typedef std::unordered_map<uint32, dtNavMeshQuery*> NavMeshQuerySet;
// dummy struct to hold map's mmap data
struct MMapData
{
MMapData(dtNavMesh* mesh) : navMesh(mesh) { }
~MMapData()
{
for (auto& navMeshQuerie : navMeshQueries)
{
dtFreeNavMeshQuery(navMeshQuerie.second);
}
if (navMesh)
{
dtFreeNavMesh(navMesh);
}
}
// we have to use single dtNavMeshQuery for every instance, since those are not thread safe
NavMeshQuerySet navMeshQueries; // instanceId to query
dtNavMesh* navMesh;
MMapTileSet loadedTileRefs; // maps [map grid coords] to [dtTile]
};
typedef std::unordered_map<uint32, MMapData*> MMapDataSet;
// singleton class
// holds all all access to mmap loading unloading and meshes
class MMapMgr
{
public:
MMapMgr() = default;
~MMapMgr();
void InitializeThreadUnsafe(const std::vector<uint32>& mapIds);
bool loadMap(uint32 mapId, int32 x, int32 y);
bool unloadMap(uint32 mapId, int32 x, int32 y);
bool unloadMap(uint32 mapId);
bool unloadMapInstance(uint32 mapId, uint32 instanceId);
// the returned [dtNavMeshQuery const*] is NOT threadsafe
dtNavMeshQuery const* GetNavMeshQuery(uint32 mapId, uint32 instanceId);
dtNavMesh const* GetNavMesh(uint32 mapId);
[[nodiscard]] uint32 getLoadedTilesCount() const { return loadedTiles; }
[[nodiscard]] uint32 getLoadedMapsCount() const { return loadedMMaps.size(); }
private:
bool loadMapData(uint32 mapId);
uint32 packTileID(int32 x, int32 y);
[[nodiscard]] MMapDataSet::const_iterator GetMMapData(uint32 mapId) const;
MMapDataSet loadedMMaps;
uint32 loadedTiles{0};
bool thread_safe_environment{true};
};
}
#endif

View File

@@ -0,0 +1,44 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "VMapFactory.h"
#include "VMapMgr2.h"
namespace VMAP
{
VMapMgr2* gVMapMgr = nullptr;
//===============================================
// just return the instance
VMapMgr2* VMapFactory::createOrGetVMapMgr()
{
if (!gVMapMgr)
{
gVMapMgr = new VMapMgr2();
}
return gVMapMgr;
}
//===============================================
// delete all internal data structures
void VMapFactory::clear()
{
delete gVMapMgr;
gVMapMgr = nullptr;
}
}

View File

@@ -0,0 +1,35 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _VMAPFACTORY_H
#define _VMAPFACTORY_H
#include "IVMapMgr.h"
// This is the access point to the VMapMgr.
namespace VMAP
{
class VMapMgr2;
class VMapFactory
{
public:
static VMapMgr2* createOrGetVMapMgr();
static void clear();
};
}
#endif

View File

@@ -0,0 +1,395 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "VMapMgr2.h"
#include "Errors.h"
#include "Log.h"
#include "MapDefines.h"
#include "MapTree.h"
#include "ModelInstance.h"
#include "WorldModel.h"
#include <G3D/Vector3.h>
#include <iomanip>
#include <sstream>
#include <string>
using G3D::Vector3;
namespace VMAP
{
VMapMgr2::VMapMgr2()
{
GetLiquidFlagsPtr = &GetLiquidFlagsDummy;
IsVMAPDisabledForPtr = &IsVMAPDisabledForDummy;
thread_safe_environment = true;
}
VMapMgr2::~VMapMgr2()
{
for (InstanceTreeMap::iterator i = iInstanceMapTrees.begin(); i != iInstanceMapTrees.end(); ++i)
{
delete i->second;
}
for (ModelFileMap::iterator i = iLoadedModelFiles.begin(); i != iLoadedModelFiles.end(); ++i)
{
delete i->second.getModel();
}
}
void VMapMgr2::InitializeThreadUnsafe(const std::vector<uint32>& mapIds)
{
// the caller must pass the list of all mapIds that will be used in the VMapMgr2 lifetime
for (const uint32& mapId : mapIds)
{
iInstanceMapTrees.emplace(mapId, nullptr);
}
thread_safe_environment = false;
}
Vector3 VMapMgr2::convertPositionToInternalRep(float x, float y, float z) const
{
Vector3 pos;
const float mid = 0.5f * MAX_NUMBER_OF_GRIDS * SIZE_OF_GRIDS;
pos.x = mid - x;
pos.y = mid - y;
pos.z = z;
return pos;
}
InstanceTreeMap::const_iterator VMapMgr2::GetMapTree(uint32 mapId) const
{
// return the iterator if found or end() if not found/NULL
InstanceTreeMap::const_iterator itr = iInstanceMapTrees.find(mapId);
if (itr != iInstanceMapTrees.cend() && !itr->second)
{
itr = iInstanceMapTrees.cend();
}
return itr;
}
// move to MapTree too?
std::string VMapMgr2::getMapFileName(unsigned int mapId)
{
std::stringstream fname;
fname.width(3);
fname << std::setfill('0') << mapId << std::string(MAP_FILENAME_EXTENSION2);
return fname.str();
}
int VMapMgr2::loadMap(const char* basePath, unsigned int mapId, int x, int y)
{
int result = VMAP_LOAD_RESULT_IGNORED;
if (isMapLoadingEnabled())
{
if (_loadMap(mapId, basePath, x, y))
{
result = VMAP_LOAD_RESULT_OK;
}
else
{
result = VMAP_LOAD_RESULT_ERROR;
}
}
return result;
}
// load one tile (internal use only)
bool VMapMgr2::_loadMap(uint32 mapId, const std::string& basePath, uint32 tileX, uint32 tileY)
{
InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId);
if (instanceTree == iInstanceMapTrees.end())
{
if (thread_safe_environment)
{
instanceTree = iInstanceMapTrees.insert(InstanceTreeMap::value_type(mapId, nullptr)).first;
}
else
ABORT("Invalid mapId {} tile [{}, {}] passed to VMapMgr2 after startup in thread unsafe environment",
mapId, tileX, tileY);
}
if (!instanceTree->second)
{
std::string mapFileName = getMapFileName(mapId);
StaticMapTree* newTree = new StaticMapTree(mapId, basePath);
if (!newTree->InitMap(mapFileName, this))
{
delete newTree;
return false;
}
instanceTree->second = newTree;
}
return instanceTree->second->LoadMapTile(tileX, tileY, this);
}
void VMapMgr2::unloadMap(unsigned int mapId)
{
InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId);
if (instanceTree != iInstanceMapTrees.end() && instanceTree->second)
{
instanceTree->second->UnloadMap(this);
if (instanceTree->second->numLoadedTiles() == 0)
{
delete instanceTree->second;
instanceTree->second = nullptr;
}
}
}
void VMapMgr2::unloadMap(unsigned int mapId, int x, int y)
{
InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId);
if (instanceTree != iInstanceMapTrees.end() && instanceTree->second)
{
instanceTree->second->UnloadMapTile(x, y, this);
if (instanceTree->second->numLoadedTiles() == 0)
{
delete instanceTree->second;
instanceTree->second = nullptr;
}
}
}
bool VMapMgr2::isInLineOfSight(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, ModelIgnoreFlags ignoreFlags)
{
#if defined(ENABLE_VMAP_CHECKS)
if (!isLineOfSightCalcEnabled() || IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LOS))
{
return true;
}
#endif
InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
if (instanceTree != iInstanceMapTrees.end())
{
Vector3 pos1 = convertPositionToInternalRep(x1, y1, z1);
Vector3 pos2 = convertPositionToInternalRep(x2, y2, z2);
if (pos1 != pos2)
{
return instanceTree->second->isInLineOfSight(pos1, pos2, ignoreFlags);
}
}
return true;
}
/**
get the hit position and return true if we hit something
otherwise the result pos will be the dest pos
*/
bool VMapMgr2::GetObjectHitPos(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist)
{
#if defined(ENABLE_VMAP_CHECKS)
if (isLineOfSightCalcEnabled() && !IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LOS))
#endif
{
InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
if (instanceTree != iInstanceMapTrees.end())
{
Vector3 pos1 = convertPositionToInternalRep(x1, y1, z1);
Vector3 pos2 = convertPositionToInternalRep(x2, y2, z2);
Vector3 resultPos;
bool result = instanceTree->second->GetObjectHitPos(pos1, pos2, resultPos, modifyDist);
resultPos = convertPositionToInternalRep(resultPos.x, resultPos.y, resultPos.z);
rx = resultPos.x;
ry = resultPos.y;
rz = resultPos.z;
return result;
}
}
rx = x2;
ry = y2;
rz = z2;
return false;
}
/**
get height or INVALID_HEIGHT if no height available
*/
float VMapMgr2::getHeight(unsigned int mapId, float x, float y, float z, float maxSearchDist)
{
#if defined(ENABLE_VMAP_CHECKS)
if (isHeightCalcEnabled() && !IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_HEIGHT))
#endif
{
InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
if (instanceTree != iInstanceMapTrees.end())
{
Vector3 pos = convertPositionToInternalRep(x, y, z);
float height = instanceTree->second->getHeight(pos, maxSearchDist);
if (height >= G3D::finf())
{
return height = VMAP_INVALID_HEIGHT_VALUE; // No height
}
return height;
}
}
return VMAP_INVALID_HEIGHT_VALUE;
}
bool VMapMgr2::GetAreaInfo(uint32 mapId, float x, float y, float& z, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const
{
#if defined(ENABLE_VMAP_CHECKS)
if (!IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_AREAFLAG))
#endif
{
InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
if (instanceTree != iInstanceMapTrees.end())
{
Vector3 pos = convertPositionToInternalRep(x, y, z);
bool result = instanceTree->second->GetAreaInfo(pos, flags, adtId, rootId, groupId);
// z is not touched by convertPositionToInternalRep(), so just copy
z = pos.z;
return result;
}
}
return false;
}
bool VMapMgr2::GetLiquidLevel(uint32 mapId, float x, float y, float z, uint8 reqLiquidType, float& level, float& floor, uint32& type, uint32& mogpFlags) const
{
#if defined(ENABLE_VMAP_CHECKS)
if (!IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LIQUIDSTATUS))
#endif
{
InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
if (instanceTree != iInstanceMapTrees.end())
{
LocationInfo info;
Vector3 pos = convertPositionToInternalRep(x, y, z);
if (instanceTree->second->GetLocationInfo(pos, info))
{
floor = info.ground_Z;
ASSERT(floor < std::numeric_limits<float>::max());
type = info.hitModel->GetLiquidType(); // entry from LiquidType.dbc
mogpFlags = info.hitModel->GetMogpFlags();
if (reqLiquidType && !(GetLiquidFlagsPtr(type) & reqLiquidType))
{
return false;
}
if (info.hitInstance->GetLiquidLevel(pos, info, level))
{
return true;
}
}
}
}
return false;
}
void VMapMgr2::GetAreaAndLiquidData(uint32 mapId, float x, float y, float z, uint8 reqLiquidType, AreaAndLiquidData& data) const
{
if (IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LIQUIDSTATUS))
{
data.floorZ = z;
int32 adtId, rootId, groupId;
uint32 flags;
if (GetAreaInfo(mapId, x, y, data.floorZ, flags, adtId, rootId, groupId))
data.areaInfo.emplace(adtId, rootId, groupId, flags);
return;
}
InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
if (instanceTree != iInstanceMapTrees.end())
{
LocationInfo info;
Vector3 pos = convertPositionToInternalRep(x, y, z);
if (instanceTree->second->GetLocationInfo(pos, info))
{
data.floorZ = info.ground_Z;
uint32 liquidType = info.hitModel->GetLiquidType();
float liquidLevel;
if (!reqLiquidType || (GetLiquidFlagsPtr(liquidType) & reqLiquidType))
if (info.hitInstance->GetLiquidLevel(pos, info, liquidLevel))
data.liquidInfo.emplace(liquidType, liquidLevel);
if (!IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_AREAFLAG))
data.areaInfo.emplace(info.hitInstance->adtId, info.rootId, info.hitModel->GetWmoID(), info.hitModel->GetMogpFlags());
}
}
}
WorldModel* VMapMgr2::acquireModelInstance(const std::string& basepath, const std::string& filename, uint32 flags/* Only used when creating the model */)
{
//! Critical section, thread safe access to iLoadedModelFiles
std::lock_guard<std::mutex> lock(LoadedModelFilesLock);
ModelFileMap::iterator model = iLoadedModelFiles.find(filename);
if (model == iLoadedModelFiles.end())
{
WorldModel* worldmodel = new WorldModel();
if (!worldmodel->readFile(basepath + filename + ".vmo"))
{
LOG_ERROR("maps", "VMapMgr2: could not load '{}{}.vmo'", basepath, filename);
delete worldmodel;
return nullptr;
}
LOG_DEBUG("maps", "VMapMgr2: loading file '{}{}'", basepath, filename);
worldmodel->Flags = flags;
model = iLoadedModelFiles.insert(std::pair<std::string, ManagedModel>(filename, ManagedModel())).first;
model->second.setModel(worldmodel);
}
return model->second.getModel();
}
void VMapMgr2::releaseModelInstance(const std::string& filename)
{
//! Critical section, thread safe access to iLoadedModelFiles
std::lock_guard<std::mutex> lock(LoadedModelFilesLock);
ModelFileMap::iterator model = iLoadedModelFiles.find(filename);
if (model == iLoadedModelFiles.end())
{
LOG_ERROR("maps", "VMapMgr2: trying to unload non-loaded file '{}'", filename);
return;
}
if (model->second.decRefCount() == 0)
{
LOG_DEBUG("maps", "VMapMgr2: unloading file '{}'", filename);
delete model->second.getModel();
iLoadedModelFiles.erase(model);
}
}
LoadResult VMapMgr2::existsMap(const char* basePath, unsigned int mapId, int x, int y)
{
return StaticMapTree::CanLoadMap(std::string(basePath), mapId, x, y);
}
void VMapMgr2::GetInstanceMapTree(InstanceTreeMap& instanceMapTree)
{
instanceMapTree = iInstanceMapTrees;
}
} // namespace VMAP

View File

@@ -0,0 +1,142 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _VMAPMANAGER2_H
#define _VMAPMANAGER2_H
#include "Common.h"
#include "IVMapMgr.h"
#include <mutex>
#include <unordered_map>
#include <vector>
//===========================================================
#define MAP_FILENAME_EXTENSION2 ".vmtree"
#define FILENAMEBUFFER_SIZE 500
/**
This is the main Class to manage loading and unloading of maps, line of sight, height calculation and so on.
For each map or map tile to load it reads a directory file that contains the ModelContainer files used by this map or map tile.
Each global map or instance has its own dynamic BSP-Tree.
The loaded ModelContainers are included in one of these BSP-Trees.
Additionally a table to match map ids and map names is used.
*/
//===========================================================
namespace G3D
{
class Vector3;
}
namespace VMAP
{
class StaticMapTree;
class WorldModel;
class ManagedModel
{
public:
ManagedModel() { }
void setModel(WorldModel* model) { iModel = model; }
WorldModel* getModel() { return iModel; }
int decRefCount() { return --iRefCount; }
protected:
WorldModel* iModel{nullptr};
int iRefCount{0};
};
typedef std::unordered_map<uint32, StaticMapTree*> InstanceTreeMap;
typedef std::unordered_map<std::string, ManagedModel> ModelFileMap;
enum DisableTypes
{
VMAP_DISABLE_AREAFLAG = 0x1,
VMAP_DISABLE_HEIGHT = 0x2,
VMAP_DISABLE_LOS = 0x4,
VMAP_DISABLE_LIQUIDSTATUS = 0x8
};
class VMapMgr2 : public IVMapMgr
{
protected:
// Tree to check collision
ModelFileMap iLoadedModelFiles;
InstanceTreeMap iInstanceMapTrees;
bool thread_safe_environment;
// Mutex for iLoadedModelFiles
std::mutex LoadedModelFilesLock;
bool _loadMap(uint32 mapId, const std::string& basePath, uint32 tileX, uint32 tileY);
/* void _unloadMap(uint32 pMapId, uint32 x, uint32 y); */
static uint32 GetLiquidFlagsDummy(uint32) { return 0; }
static bool IsVMAPDisabledForDummy(uint32 /*entry*/, uint8 /*flags*/) { return false; }
InstanceTreeMap::const_iterator GetMapTree(uint32 mapId) const;
public:
// public for debug
[[nodiscard]] G3D::Vector3 convertPositionToInternalRep(float x, float y, float z) const;
static std::string getMapFileName(unsigned int mapId);
VMapMgr2();
~VMapMgr2() override;
void InitializeThreadUnsafe(const std::vector<uint32>& mapIds);
int loadMap(const char* pBasePath, unsigned int mapId, int x, int y) override;
void unloadMap(unsigned int mapId, int x, int y) override;
void unloadMap(unsigned int mapId) override;
bool isInLineOfSight(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, ModelIgnoreFlags ignoreFlags) override ;
/**
fill the hit pos and return true, if an object was hit
*/
bool GetObjectHitPos(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist) override;
float getHeight(unsigned int mapId, float x, float y, float z, float maxSearchDist) override;
bool processCommand(char* /*command*/) override { return false; } // for debug and extensions
bool GetAreaInfo(uint32 pMapId, float x, float y, float& z, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const override;
bool GetLiquidLevel(uint32 pMapId, float x, float y, float z, uint8 reqLiquidType, float& level, float& floor, uint32& type, uint32& mogpFlags) const override;
void GetAreaAndLiquidData(uint32 mapId, float x, float y, float z, uint8 reqLiquidType, AreaAndLiquidData& data) const override;
WorldModel* acquireModelInstance(const std::string& basepath, const std::string& filename, uint32 flags);
void releaseModelInstance(const std::string& filename);
// what's the use of this? o.O
[[nodiscard]] std::string getDirFileName(unsigned int mapId, int /*x*/, int /*y*/) const override
{
return getMapFileName(mapId);
}
LoadResult existsMap(const char* basePath, unsigned int mapId, int x, int y) override;
void GetInstanceMapTree(InstanceTreeMap& instanceMapTree);
typedef uint32(*GetLiquidFlagsFn)(uint32 liquidType);
GetLiquidFlagsFn GetLiquidFlagsPtr;
typedef bool(*IsVMAPDisabledForFn)(uint32 entry, uint8 flags);
IsVMAPDisabledForFn IsVMAPDisabledForPtr;
};
}
#endif

View File

@@ -0,0 +1,65 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _MAPDEFINES_H
#define _MAPDEFINES_H
#include "Define.h"
#include "DetourNavMesh.h"
#define MAX_NUMBER_OF_GRIDS 64
#define SIZE_OF_GRIDS 533.3333f
#define MMAP_MAGIC 0x4d4d4150 // 'MMAP'
#define MMAP_VERSION 16
struct MmapTileHeader
{
uint32 mmapMagic{MMAP_MAGIC};
uint32 dtVersion;
uint32 mmapVersion{MMAP_VERSION};
uint32 size{0};
char usesLiquids{true};
char padding[3] {};
MmapTileHeader() : dtVersion(DT_NAVMESH_VERSION) { }
};
// All padding fields must be handled and initialized to ensure mmaps_generator will produce binary-identical *.mmtile files
static_assert(sizeof(MmapTileHeader) == 20, "MmapTileHeader size is not correct, adjust the padding field size");
static_assert(sizeof(MmapTileHeader) == (sizeof(MmapTileHeader::mmapMagic) +
sizeof(MmapTileHeader::dtVersion) +
sizeof(MmapTileHeader::mmapVersion) +
sizeof(MmapTileHeader::size) +
sizeof(MmapTileHeader::usesLiquids) +
sizeof(MmapTileHeader::padding)), "MmapTileHeader has uninitialized padding fields");
enum NavTerrain
{
NAV_EMPTY = 0x00,
NAV_GROUND = 0x01,
NAV_MAGMA = 0x02,
NAV_SLIME = 0x04,
NAV_WATER = 0x08,
NAV_UNUSED1 = 0x10,
NAV_UNUSED2 = 0x20,
NAV_UNUSED3 = 0x40,
NAV_UNUSED4 = 0x80
// we only have 8 bits
};
#endif /* _MAPDEFINES_H */

View File

@@ -0,0 +1,533 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "MapTree.h"
#include "Errors.h"
#include "Log.h"
#include "Metric.h"
#include "ModelInstance.h"
#include "VMapDefinitions.h"
#include "VMapMgr2.h"
#include <iomanip>
#include <limits>
#include <sstream>
#include <string>
using G3D::Vector3;
namespace VMAP
{
class MapRayCallback
{
public:
MapRayCallback(ModelInstance* val, ModelIgnoreFlags ignoreFlags): prims(val), flags(ignoreFlags), hit(false) { }
bool operator()(const G3D::Ray& ray, uint32 entry, float& distance, bool StopAtFirstHit)
{
bool result = prims[entry].intersectRay(ray, distance, StopAtFirstHit, flags);
if (result)
{
hit = true;
}
return result;
}
bool didHit() { return hit; }
protected:
ModelInstance* prims;
ModelIgnoreFlags flags;
bool hit;
};
class AreaInfoCallback
{
public:
AreaInfoCallback(ModelInstance* val): prims(val) {}
void operator()(const Vector3& point, uint32 entry)
{
#if defined(VMAP_DEBUG)
LOG_DEBUG("maps", "AreaInfoCallback: trying to intersect '{}'", prims[entry].name);
#endif
prims[entry].intersectPoint(point, aInfo);
}
ModelInstance* prims;
AreaInfo aInfo;
};
class LocationInfoCallback
{
public:
LocationInfoCallback(ModelInstance* val, LocationInfo& info): prims(val), locInfo(info), result(false) {}
void operator()(const Vector3& point, uint32 entry)
{
#if defined(VMAP_DEBUG)
LOG_DEBUG("maps", "LocationInfoCallback: trying to intersect '{}'", prims[entry].name);
#endif
if (prims[entry].GetLocationInfo(point, locInfo))
{
result = true;
}
}
ModelInstance* prims;
LocationInfo& locInfo;
bool result;
};
//=========================================================
std::string StaticMapTree::getTileFileName(uint32 mapID, uint32 tileX, uint32 tileY)
{
std::stringstream tilefilename;
tilefilename.fill('0');
tilefilename << std::setw(3) << mapID << '_';
//tilefilename << std::setw(2) << tileX << '_' << std::setw(2) << tileY << ".vmtile";
tilefilename << std::setw(2) << tileY << '_' << std::setw(2) << tileX << ".vmtile";
return tilefilename.str();
}
bool StaticMapTree::GetAreaInfo(Vector3& pos, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const
{
AreaInfoCallback intersectionCallBack(iTreeValues);
iTree.intersectPoint(pos, intersectionCallBack);
if (intersectionCallBack.aInfo.result)
{
flags = intersectionCallBack.aInfo.flags;
adtId = intersectionCallBack.aInfo.adtId;
rootId = intersectionCallBack.aInfo.rootId;
groupId = intersectionCallBack.aInfo.groupId;
pos.z = intersectionCallBack.aInfo.ground_Z;
return true;
}
return false;
}
bool StaticMapTree::GetLocationInfo(const Vector3& pos, LocationInfo& info) const
{
LocationInfoCallback intersectionCallBack(iTreeValues, info);
iTree.intersectPoint(pos, intersectionCallBack);
return intersectionCallBack.result;
}
StaticMapTree::StaticMapTree(uint32 mapID, const std::string& basePath)
: iMapID(mapID), iIsTiled(false), iTreeValues(0), iBasePath(basePath)
{
if (iBasePath.length() > 0 && iBasePath[iBasePath.length() - 1] != '/' && iBasePath[iBasePath.length() - 1] != '\\')
{
iBasePath.push_back('/');
}
}
//=========================================================
//! Make sure to call unloadMap() to unregister acquired model references before destroying
StaticMapTree::~StaticMapTree()
{
delete[] iTreeValues;
}
//=========================================================
/**
If intersection is found within pMaxDist, sets pMaxDist to intersection distance and returns true.
Else, pMaxDist is not modified and returns false;
*/
bool StaticMapTree::GetIntersectionTime(const G3D::Ray& pRay, float& pMaxDist, bool StopAtFirstHit, ModelIgnoreFlags ignoreFlags) const
{
float distance = pMaxDist;
MapRayCallback intersectionCallBack(iTreeValues, ignoreFlags);
iTree.intersectRay(pRay, intersectionCallBack, distance, StopAtFirstHit);
if (intersectionCallBack.didHit())
{
pMaxDist = distance;
}
return intersectionCallBack.didHit();
}
//=========================================================
bool StaticMapTree::isInLineOfSight(const Vector3& pos1, const Vector3& pos2, ModelIgnoreFlags ignoreFlags) const
{
float maxDist = (pos2 - pos1).magnitude();
// return false if distance is over max float, in case of cheater teleporting to the end of the universe
if (maxDist == std::numeric_limits<float>::max() || !std::isfinite(maxDist))
{
return false;
}
// valid map coords should *never ever* produce float overflow, but this would produce NaNs too
ASSERT(maxDist < std::numeric_limits<float>::max());
// prevent NaN values which can cause BIH intersection to enter infinite loop
if (maxDist < 1e-10f)
{
return true;
}
// direction with length of 1
G3D::Ray ray = G3D::Ray::fromOriginAndDirection(pos1, (pos2 - pos1) / maxDist);
return !GetIntersectionTime(ray, maxDist, true, ignoreFlags);
}
//=========================================================
/**
When moving from pos1 to pos2 check if we hit an object. Return true and the position if we hit one
Return the hit pos or the original dest pos
*/
bool StaticMapTree::GetObjectHitPos(const Vector3& pPos1, const Vector3& pPos2, Vector3& pResultHitPos, float pModifyDist) const
{
bool result = false;
float maxDist = (pPos2 - pPos1).magnitude();
// valid map coords should *never ever* produce float overflow, but this would produce NaNs too
ASSERT(maxDist < std::numeric_limits<float>::max());
// prevent NaN values which can cause BIH intersection to enter infinite loop
if (maxDist < 1e-10f)
{
pResultHitPos = pPos2;
return false;
}
Vector3 dir = (pPos2 - pPos1) / maxDist; // direction with length of 1
G3D::Ray ray(pPos1, dir);
float dist = maxDist;
if (GetIntersectionTime(ray, dist, false, ModelIgnoreFlags::Nothing))
{
pResultHitPos = pPos1 + dir * dist;
if (pModifyDist < 0)
{
if ((pResultHitPos - pPos1).magnitude() > -pModifyDist)
{
pResultHitPos = pResultHitPos + dir * pModifyDist;
}
else
{
pResultHitPos = pPos1;
}
}
else
{
pResultHitPos = pResultHitPos + dir * pModifyDist;
}
result = true;
}
else
{
pResultHitPos = pPos2;
result = false;
}
return result;
}
//=========================================================
float StaticMapTree::getHeight(const Vector3& pPos, float maxSearchDist) const
{
float height = G3D::finf();
Vector3 dir = Vector3(0, 0, -1);
G3D::Ray ray(pPos, dir); // direction with length of 1
float maxDist = maxSearchDist;
if (GetIntersectionTime(ray, maxDist, false, ModelIgnoreFlags::Nothing))
{
height = pPos.z - maxDist;
}
return (height);
}
//=========================================================
LoadResult StaticMapTree::CanLoadMap(const std::string& vmapPath, uint32 mapID, uint32 tileX, uint32 tileY)
{
std::string basePath = vmapPath;
if (basePath.length() > 0 && basePath[basePath.length() - 1] != '/' && basePath[basePath.length() - 1] != '\\')
{
basePath.push_back('/');
}
std::string fullname = basePath + VMapMgr2::getMapFileName(mapID);
LoadResult result = LoadResult::Success;
FILE* rf = fopen(fullname.c_str(), "rb");
if (!rf)
{
return LoadResult::FileNotFound;
}
char tiled;
char chunk[8];
if (!readChunk(rf, chunk, VMAP_MAGIC, 8) || fread(&tiled, sizeof(char), 1, rf) != 1)
{
fclose(rf);
return LoadResult::VersionMismatch;
}
if (tiled)
{
std::string tilefile = basePath + getTileFileName(mapID, tileX, tileY);
FILE* tf = fopen(tilefile.c_str(), "rb");
if (!tf)
{
result = LoadResult::FileNotFound;
}
else
{
if (!readChunk(tf, chunk, VMAP_MAGIC, 8))
{
result = LoadResult::VersionMismatch;
}
fclose(tf);
}
}
fclose(rf);
return result;
}
//=========================================================
bool StaticMapTree::InitMap(const std::string& fname, VMapMgr2* vm)
{
//VMAP_DEBUG_LOG(LOG_FILTER_MAPS, "StaticMapTree::InitMap() : initializing StaticMapTree '{}'", fname);
bool success = false;
std::string fullname = iBasePath + fname;
FILE* rf = fopen(fullname.c_str(), "rb");
if (!rf)
{
return false;
}
char chunk[8];
char tiled = '\0';
if (readChunk(rf, chunk, VMAP_MAGIC, 8) && fread(&tiled, sizeof(char), 1, rf) == 1 &&
readChunk(rf, chunk, "NODE", 4) && iTree.readFromFile(rf))
{
iNTreeValues = iTree.primCount();
iTreeValues = new ModelInstance[iNTreeValues];
success = readChunk(rf, chunk, "GOBJ", 4);
}
iIsTiled = bool(tiled);
// global model spawns
// only non-tiled maps have them, and if so exactly one (so far at least...)
ModelSpawn spawn;
#ifdef VMAP_DEBUG
//LOG_DEBUG(LOG_FILTER_MAPS, "StaticMapTree::InitMap() : map isTiled: {}", static_cast<uint32>(iIsTiled));
#endif
if (!iIsTiled && ModelSpawn::readFromFile(rf, spawn))
{
WorldModel* model = vm->acquireModelInstance(iBasePath, spawn.name, spawn.flags);
//VMAP_DEBUG_LOG(LOG_FILTER_MAPS, "StaticMapTree::InitMap() : loading {}", spawn.name);
if (model)
{
// assume that global model always is the first and only tree value (could be improved...)
iTreeValues[0] = ModelInstance(spawn, model);
iLoadedSpawns[0] = 1;
}
else
{
success = false;
//VMAP_ERROR_LOG(LOG_FILTER_GENERAL, "StaticMapTree::InitMap() : could not acquire WorldModel pointer for '{}'", spawn.name);
}
}
fclose(rf);
return success;
}
//=========================================================
void StaticMapTree::UnloadMap(VMapMgr2* vm)
{
for (loadedSpawnMap::iterator i = iLoadedSpawns.begin(); i != iLoadedSpawns.end(); ++i)
{
iTreeValues[i->first].setUnloaded();
for (uint32 refCount = 0; refCount < i->second; ++refCount)
{
vm->releaseModelInstance(iTreeValues[i->first].name);
}
}
iLoadedSpawns.clear();
iLoadedTiles.clear();
}
//=========================================================
bool StaticMapTree::LoadMapTile(uint32 tileX, uint32 tileY, VMapMgr2* vm)
{
if (!iIsTiled)
{
// currently, core creates grids for all maps, whether it has terrain tiles or not
// so we need "fake" tile loads to know when we can unload map geometry
iLoadedTiles[packTileID(tileX, tileY)] = false;
return true;
}
if (!iTreeValues)
{
LOG_ERROR("maps", "StaticMapTree::LoadMapTile() : tree has not been initialized [{}, {}]", tileX, tileY);
return false;
}
bool result = true;
std::string tilefile = iBasePath + getTileFileName(iMapID, tileX, tileY);
FILE* tf = fopen(tilefile.c_str(), "rb");
if (tf)
{
char chunk[8];
if (!readChunk(tf, chunk, VMAP_MAGIC, 8))
{
result = false;
}
uint32 numSpawns = 0;
if (result && fread(&numSpawns, sizeof(uint32), 1, tf) != 1)
{
result = false;
}
for (uint32 i = 0; i < numSpawns && result; ++i)
{
// read model spawns
ModelSpawn spawn;
result = ModelSpawn::readFromFile(tf, spawn);
if (result)
{
// acquire model instance
WorldModel* model = vm->acquireModelInstance(iBasePath, spawn.name, spawn.flags);
if (!model)
{
LOG_ERROR("maps", "StaticMapTree::LoadMapTile() : could not acquire WorldModel pointer [{}, {}]", tileX, tileY);
}
// update tree
uint32 referencedVal;
if (fread(&referencedVal, sizeof(uint32), 1, tf) == 1)
{
if (!iLoadedSpawns.count(referencedVal))
{
#if defined(VMAP_DEBUG)
if (referencedVal > iNTreeValues)
{
LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : invalid tree element ({}/{})", referencedVal, iNTreeValues);
continue;
}
#endif
iTreeValues[referencedVal] = ModelInstance(spawn, model);
iLoadedSpawns[referencedVal] = 1;
}
else
{
++iLoadedSpawns[referencedVal];
#if defined(VMAP_DEBUG)
if (iTreeValues[referencedVal].ID != spawn.ID)
{
LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : trying to load wrong spawn in node");
}
else if (iTreeValues[referencedVal].name != spawn.name)
{
LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : name collision on GUID={}", spawn.ID);
}
#endif
}
}
else
{
result = false;
}
}
}
iLoadedTiles[packTileID(tileX, tileY)] = true;
fclose(tf);
}
else
{
iLoadedTiles[packTileID(tileX, tileY)] = false;
}
METRIC_EVENT("map_events", "LoadMapTile",
"Map: " + std::to_string(iMapID) + " TileX: " + std::to_string(tileX) + " TileY: " + std::to_string(tileY));
return result;
}
//=========================================================
void StaticMapTree::UnloadMapTile(uint32 tileX, uint32 tileY, VMapMgr2* vm)
{
uint32 tileID = packTileID(tileX, tileY);
loadedTileMap::iterator tile = iLoadedTiles.find(tileID);
if (tile == iLoadedTiles.end())
{
LOG_ERROR("maps", "StaticMapTree::UnloadMapTile() : trying to unload non-loaded tile - Map:{} X:{} Y:{}", iMapID, tileX, tileY);
return;
}
if (tile->second) // file associated with tile
{
std::string tilefile = iBasePath + getTileFileName(iMapID, tileX, tileY);
FILE* tf = fopen(tilefile.c_str(), "rb");
if (tf)
{
bool result = true;
char chunk[8];
if (!readChunk(tf, chunk, VMAP_MAGIC, 8))
{
result = false;
}
uint32 numSpawns;
if (fread(&numSpawns, sizeof(uint32), 1, tf) != 1)
{
result = false;
}
for (uint32 i = 0; i < numSpawns && result; ++i)
{
// read model spawns
ModelSpawn spawn;
result = ModelSpawn::readFromFile(tf, spawn);
if (result)
{
// release model instance
vm->releaseModelInstance(spawn.name);
// update tree
uint32 referencedNode;
if (fread(&referencedNode, sizeof(uint32), 1, tf) != 1)
{
result = false;
}
else
{
if (!iLoadedSpawns.count(referencedNode))
{
LOG_ERROR("maps", "StaticMapTree::UnloadMapTile() : trying to unload non-referenced model '{}' (ID:{})", spawn.name, spawn.ID);
}
else if (--iLoadedSpawns[referencedNode] == 0)
{
iTreeValues[referencedNode].setUnloaded();
iLoadedSpawns.erase(referencedNode);
}
}
}
}
fclose(tf);
}
}
iLoadedTiles.erase(tile);
METRIC_EVENT("map_events", "UnloadMapTile",
"Map: " + std::to_string(iMapID) + " TileX: " + std::to_string(tileX) + " TileY: " + std::to_string(tileY));
}
void StaticMapTree::GetModelInstances(ModelInstance*& models, uint32& count)
{
models = iTreeValues;
count = iNTreeValues;
}
}

View File

@@ -0,0 +1,100 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _MAPTREE_H
#define _MAPTREE_H
#include "BoundingIntervalHierarchy.h"
#include "Define.h"
#include <unordered_map>
namespace VMAP
{
class ModelInstance;
class GroupModel;
class VMapMgr2;
enum class ModelIgnoreFlags : uint32;
enum class LoadResult : uint8;
struct LocationInfo
{
LocationInfo(): ground_Z(-G3D::inf()) { }
const ModelInstance* hitInstance{nullptr};
const GroupModel* hitModel{nullptr};
float ground_Z;
int32 rootId = -1;
};
class StaticMapTree
{
typedef std::unordered_map<uint32, bool> loadedTileMap;
typedef std::unordered_map<uint32, uint32> loadedSpawnMap;
private:
uint32 iMapID;
bool iIsTiled;
BIH iTree;
ModelInstance* iTreeValues; // the tree entries
uint32 iNTreeValues;
// Store all the map tile idents that are loaded for that map
// some maps are not splitted into tiles and we have to make sure, not removing the map before all tiles are removed
// empty tiles have no tile file, hence map with bool instead of just a set (consistency check)
loadedTileMap iLoadedTiles;
// stores <tree_index, reference_count> to invalidate tree values, unload map, and to be able to report errors
loadedSpawnMap iLoadedSpawns;
std::string iBasePath;
private:
bool GetIntersectionTime(const G3D::Ray& pRay, float& pMaxDist, bool StopAtFirstHit, ModelIgnoreFlags ignoreFlags) const;
//bool containsLoadedMapTile(unsigned int pTileIdent) const { return(iLoadedMapTiles.containsKey(pTileIdent)); }
public:
static std::string getTileFileName(uint32 mapID, uint32 tileX, uint32 tileY);
static uint32 packTileID(uint32 tileX, uint32 tileY) { return tileX << 16 | tileY; }
static void unpackTileID(uint32 ID, uint32& tileX, uint32& tileY) { tileX = ID >> 16; tileY = ID & 0xFF; }
static LoadResult CanLoadMap(const std::string& basePath, uint32 mapID, uint32 tileX, uint32 tileY);
StaticMapTree(uint32 mapID, const std::string& basePath);
~StaticMapTree();
[[nodiscard]] bool isInLineOfSight(const G3D::Vector3& pos1, const G3D::Vector3& pos2, ModelIgnoreFlags ignoreFlags) const;
bool GetObjectHitPos(const G3D::Vector3& pos1, const G3D::Vector3& pos2, G3D::Vector3& pResultHitPos, float pModifyDist) const;
[[nodiscard]] float getHeight(const G3D::Vector3& pPos, float maxSearchDist) const;
bool GetAreaInfo(G3D::Vector3& pos, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const;
bool GetLocationInfo(const G3D::Vector3& pos, LocationInfo& info) const;
bool InitMap(const std::string& fname, VMapMgr2* vm);
void UnloadMap(VMapMgr2* vm);
bool LoadMapTile(uint32 tileX, uint32 tileY, VMapMgr2* vm);
void UnloadMapTile(uint32 tileX, uint32 tileY, VMapMgr2* vm);
[[nodiscard]] bool isTiled() const { return iIsTiled; }
[[nodiscard]] uint32 numLoadedTiles() const { return iLoadedTiles.size(); }
void GetModelInstances(ModelInstance*& models, uint32& count);
};
struct AreaInfo
{
AreaInfo(): ground_Z(-G3D::inf()) { }
bool result{false};
float ground_Z;
uint32 flags{0};
int32 adtId{0};
int32 rootId{0};
int32 groupId{0};
};
} // VMAP
#endif // _MAPTREE_H

View File

@@ -0,0 +1,610 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TileAssembler.h"
#include "BoundingIntervalHierarchy.h"
#include "MapDefines.h"
#include "MapTree.h"
#include "VMapDefinitions.h"
#include <iomanip>
#include <set>
#include <sstream>
#include <boost/filesystem.hpp>
using G3D::Vector3;
using G3D::AABox;
using G3D::inf;
using std::pair;
template<> struct BoundsTrait<VMAP::ModelSpawn*>
{
static void GetBounds(const VMAP::ModelSpawn* const& obj, G3D::AABox& out) { out = obj->GetBounds(); }
};
namespace VMAP
{
bool readChunk(FILE* rf, char* dest, const char* compare, uint32 len)
{
if (fread(dest, sizeof(char), len, rf) != len) { return false; }
return memcmp(dest, compare, len) == 0;
}
Vector3 ModelPosition::transform(const Vector3& pIn) const
{
Vector3 out = pIn * iScale;
out = iRotation * out;
return (out);
}
//=================================================================
TileAssembler::TileAssembler(const std::string& pSrcDirName, const std::string& pDestDirName)
: iDestDir(pDestDirName), iSrcDir(pSrcDirName)
{
boost::filesystem::create_directory(iDestDir);
//init();
}
TileAssembler::~TileAssembler()
{
//delete iCoordModelMapping;
}
bool TileAssembler::convertWorld2()
{
bool success = readMapSpawns();
if (!success)
{
return false;
}
// export Map data
for (MapData::iterator map_iter = mapData.begin(); map_iter != mapData.end() && success; ++map_iter)
{
// build global map tree
std::vector<ModelSpawn*> mapSpawns;
UniqueEntryMap::iterator entry;
printf("Calculating model bounds for map %u...\n", map_iter->first);
for (entry = map_iter->second->UniqueEntries.begin(); entry != map_iter->second->UniqueEntries.end(); ++entry)
{
// M2 models don't have a bound set in WDT/ADT placement data, i still think they're not used for LoS at all on retail
if (entry->second.flags & MOD_M2)
{
if (!calculateTransformedBound(entry->second))
{
break;
}
}
else if (entry->second.flags & MOD_WORLDSPAWN) // WMO maps and terrain maps use different origin, so we need to adapt :/
{
/// @todo remove extractor hack and uncomment below line:
//entry->second.iPos += Vector3(533.33333f*32, 533.33333f*32, 0.f);
entry->second.iBound = entry->second.iBound + Vector3(533.33333f * 32, 533.33333f * 32, 0.f);
}
mapSpawns.push_back(&(entry->second));
spawnedModelFiles.insert(entry->second.name);
}
printf("Creating map tree for map %u...\n", map_iter->first);
BIH pTree;
try
{
pTree.build(mapSpawns, BoundsTrait<ModelSpawn*>::GetBounds);
}
catch (std::exception& e)
{
printf("Exception ""%s"" when calling pTree.build", e.what());
return false;
}
// ===> possibly move this code to StaticMapTree class
std::map<uint32, uint32> modelNodeIdx;
for (uint32 i = 0; i < mapSpawns.size(); ++i)
{
modelNodeIdx.insert(pair<uint32, uint32>(mapSpawns[i]->ID, i));
}
// write map tree file
std::stringstream mapfilename;
mapfilename << iDestDir << '/' << std::setfill('0') << std::setw(3) << map_iter->first << ".vmtree";
FILE* mapfile = fopen(mapfilename.str().c_str(), "wb");
if (!mapfile)
{
success = false;
printf("Cannot open %s\n", mapfilename.str().c_str());
break;
}
//general info
if (success && fwrite(VMAP_MAGIC, 1, 8, mapfile) != 8) { success = false; }
uint32 globalTileID = StaticMapTree::packTileID(65, 65);
pair<TileMap::iterator, TileMap::iterator> globalRange = map_iter->second->TileEntries.equal_range(globalTileID);
char isTiled = globalRange.first == globalRange.second; // only maps without terrain (tiles) have global WMO
if (success && fwrite(&isTiled, sizeof(char), 1, mapfile) != 1) { success = false; }
// Nodes
if (success && fwrite("NODE", 4, 1, mapfile) != 1) { success = false; }
if (success) { success = pTree.writeToFile(mapfile); }
// global map spawns (WDT), if any (most instances)
if (success && fwrite("GOBJ", 4, 1, mapfile) != 1) { success = false; }
for (TileMap::iterator glob = globalRange.first; glob != globalRange.second && success; ++glob)
{
success = ModelSpawn::writeToFile(mapfile, map_iter->second->UniqueEntries[glob->second]);
}
fclose(mapfile);
// <====
// write map tile files, similar to ADT files, only with extra BSP tree node info
TileMap& tileEntries = map_iter->second->TileEntries;
TileMap::iterator tile;
for (tile = tileEntries.begin(); tile != tileEntries.end(); ++tile)
{
const ModelSpawn& spawn = map_iter->second->UniqueEntries[tile->second];
if (spawn.flags & MOD_WORLDSPAWN) // WDT spawn, saved as tile 65/65 currently...
{
continue;
}
uint32 nSpawns = tileEntries.count(tile->first);
std::stringstream tilefilename;
tilefilename.fill('0');
tilefilename << iDestDir << '/' << std::setw(3) << map_iter->first << '_';
uint32 x, y;
StaticMapTree::unpackTileID(tile->first, x, y);
tilefilename << std::setw(2) << x << '_' << std::setw(2) << y << ".vmtile";
if (FILE* tilefile = fopen(tilefilename.str().c_str(), "wb"))
{
// file header
if (success && fwrite(VMAP_MAGIC, 1, 8, tilefile) != 8) { success = false; }
// write number of tile spawns
if (success && fwrite(&nSpawns, sizeof(uint32), 1, tilefile) != 1) { success = false; }
// write tile spawns
for (uint32 s = 0; s < nSpawns; ++s)
{
if (s)
{
++tile;
}
const ModelSpawn& spawn2 = map_iter->second->UniqueEntries[tile->second];
success = success && ModelSpawn::writeToFile(tilefile, spawn2);
// MapTree nodes to update when loading tile:
std::map<uint32, uint32>::iterator nIdx = modelNodeIdx.find(spawn2.ID);
if (success && fwrite(&nIdx->second, sizeof(uint32), 1, tilefile) != 1) { success = false; }
}
fclose(tilefile);
}
}
// break; //test, extract only first map; TODO: remvoe this line
}
// add an object models, listed in temp_gameobject_models file
exportGameobjectModels();
// export objects
std::cout << "\nConverting Model Files" << std::endl;
for (std::set<std::string>::iterator mfile = spawnedModelFiles.begin(); mfile != spawnedModelFiles.end(); ++mfile)
{
std::cout << "Converting " << *mfile << std::endl;
if (!convertRawFile(*mfile))
{
std::cout << "error converting " << *mfile << std::endl;
success = false;
break;
}
}
//cleanup:
for (MapData::iterator map_iter = mapData.begin(); map_iter != mapData.end(); ++map_iter)
{
delete map_iter->second;
}
return success;
}
bool TileAssembler::readMapSpawns()
{
std::string fname = iSrcDir + "/dir_bin";
FILE* dirf = fopen(fname.c_str(), "rb");
if (!dirf)
{
printf("Could not read dir_bin file!\n");
return false;
}
printf("Read coordinate mapping...\n");
uint32 mapID, tileX, tileY, check = 0;
G3D::Vector3 v1, v2;
ModelSpawn spawn;
while (!feof(dirf))
{
// read mapID, tileX, tileY, Flags, NameSet, UniqueId, Pos, Rot, Scale, Bound_lo, Bound_hi, name
check = fread(&mapID, sizeof(uint32), 1, dirf);
if (check == 0) // EoF...
{
break;
}
check += fread(&tileX, sizeof(uint32), 1, dirf);
check += fread(&tileY, sizeof(uint32), 1, dirf);
if (!ModelSpawn::readFromFile(dirf, spawn))
{
break;
}
MapSpawns* current;
MapData::iterator map_iter = mapData.find(mapID);
if (map_iter == mapData.end())
{
printf("spawning Map %d\n", mapID);
mapData[mapID] = current = new MapSpawns();
}
else
{
current = map_iter->second;
}
current->UniqueEntries.emplace(spawn.ID, spawn);
current->TileEntries.insert(pair<uint32, uint32>(StaticMapTree::packTileID(tileX, tileY), spawn.ID));
}
bool success = (ferror(dirf) == 0);
fclose(dirf);
return success;
}
bool TileAssembler::calculateTransformedBound(ModelSpawn& spawn)
{
std::string modelFilename(iSrcDir);
modelFilename.push_back('/');
modelFilename.append(spawn.name);
ModelPosition modelPosition;
modelPosition.iDir = spawn.iRot;
modelPosition.iScale = spawn.iScale;
modelPosition.init();
WorldModel_Raw raw_model;
if (!raw_model.Read(modelFilename.c_str()))
{
return false;
}
uint32 groups = raw_model.groupsArray.size();
if (groups != 1)
{
printf("Warning: '%s' does not seem to be a M2 model!\n", modelFilename.c_str());
}
AABox modelBound;
bool boundEmpty = true;
for (uint32 g = 0; g < groups; ++g) // should be only one for M2 files...
{
std::vector<Vector3>& vertices = raw_model.groupsArray[g].vertexArray;
if (vertices.empty())
{
std::cout << "error: model '" << spawn.name << "' has no geometry!" << std::endl;
continue;
}
uint32 nvectors = vertices.size();
for (uint32 i = 0; i < nvectors; ++i)
{
Vector3 v = modelPosition.transform(vertices[i]);
if (boundEmpty)
{
modelBound = AABox(v, v), boundEmpty = false;
}
else
{
modelBound.merge(v);
}
}
}
spawn.iBound = modelBound + spawn.iPos;
spawn.flags |= MOD_HAS_BOUND;
return true;
}
#pragma pack(push, 1)
struct WMOLiquidHeader
{
int xverts, yverts, xtiles, ytiles;
float pos_x;
float pos_y;
float pos_z;
short material;
};
#pragma pack(pop)
//=================================================================
bool TileAssembler::convertRawFile(const std::string& pModelFilename)
{
bool success = true;
std::string filename = iSrcDir;
if (filename.length() > 0)
{
filename.push_back('/');
}
filename.append(pModelFilename);
WorldModel_Raw raw_model;
if (!raw_model.Read(filename.c_str()))
{
return false;
}
// write WorldModel
WorldModel model;
model.setRootWmoID(raw_model.RootWMOID);
if (!raw_model.groupsArray.empty())
{
std::vector<GroupModel> groupsArray;
uint32 groups = raw_model.groupsArray.size();
for (uint32 g = 0; g < groups; ++g)
{
GroupModel_Raw& raw_group = raw_model.groupsArray[g];
groupsArray.push_back(GroupModel(raw_group.mogpflags, raw_group.GroupWMOID, raw_group.bounds ));
groupsArray.back().setMeshData(raw_group.vertexArray, raw_group.triangles);
groupsArray.back().setLiquidData(raw_group.liquid);
}
model.setGroupModels(groupsArray);
}
success = model.writeFile(iDestDir + "/" + pModelFilename + ".vmo");
//std::cout << "readRawFile2: '" << pModelFilename << "' tris: " << nElements << " nodes: " << nNodes << std::endl;
return success;
}
void TileAssembler::exportGameobjectModels()
{
FILE* model_list = fopen((iSrcDir + "/" + "temp_gameobject_models").c_str(), "rb");
if (!model_list)
{
return;
}
char ident[8];
if (fread(ident, 1, 8, model_list) != 8 || memcmp(ident, VMAP::RAW_VMAP_MAGIC, 8) != 0)
{
fclose(model_list);
return;
}
FILE* model_list_copy = fopen((iDestDir + "/" + GAMEOBJECT_MODELS).c_str(), "wb");
if (!model_list_copy)
{
fclose(model_list);
return;
}
fwrite(VMAP::VMAP_MAGIC, 1, 8, model_list_copy);
uint32 name_length, displayId;
uint8 isWmo;
char buff[500];
while (!feof(model_list))
{
if (fread(&displayId, sizeof(uint32), 1, model_list) != 1)
if (feof(model_list)) // EOF flag is only set after failed reading attempt
{
break;
}
if (fread(&isWmo, sizeof(uint8), 1, model_list) != 1
|| fread(&name_length, sizeof(uint32), 1, model_list) != 1
|| name_length >= sizeof(buff)
|| fread(&buff, sizeof(char), name_length, model_list) != name_length)
{
std::cout << "\nFile 'temp_gameobject_models' seems to be corrupted" << std::endl;
break;
}
std::string model_name(buff, name_length);
WorldModel_Raw raw_model;
if ( !raw_model.Read((iSrcDir + "/" + model_name).c_str()) )
{
continue;
}
spawnedModelFiles.insert(model_name);
AABox bounds;
bool boundEmpty = true;
for (uint32 g = 0; g < raw_model.groupsArray.size(); ++g)
{
std::vector<Vector3>& vertices = raw_model.groupsArray[g].vertexArray;
uint32 nvectors = vertices.size();
for (uint32 i = 0; i < nvectors; ++i)
{
Vector3& v = vertices[i];
if (boundEmpty)
{
bounds = AABox(v, v), boundEmpty = false;
}
else
{
bounds.merge(v);
}
}
}
fwrite(&displayId, sizeof(uint32), 1, model_list_copy);
fwrite(&isWmo, sizeof(uint8), 1, model_list_copy);
fwrite(&name_length, sizeof(uint32), 1, model_list_copy);
fwrite(&buff, sizeof(char), name_length, model_list_copy);
fwrite(&bounds.low(), sizeof(Vector3), 1, model_list_copy);
fwrite(&bounds.high(), sizeof(Vector3), 1, model_list_copy);
}
fclose(model_list);
fclose(model_list_copy);
}
// temporary use defines to simplify read/check code (close file and return at fail)
#define READ_OR_RETURN(V, S) if (fread((V), (S), 1, rf) != 1) { \
fclose(rf); printf("readfail, op = %i\n", readOperation); return(false); }
#define READ_OR_RETURN_WITH_DELETE(V, S) if (fread((V), (S), 1, rf) != 1) { \
fclose(rf); printf("readfail, op = %i\n", readOperation); delete[] V; return(false); };
#define CMP_OR_RETURN(V, S) if (strcmp((V), (S)) != 0) { \
fclose(rf); printf("cmpfail, %s!=%s\n", V, S);return(false); }
bool GroupModel_Raw::Read(FILE* rf)
{
char blockId[5];
blockId[4] = 0;
int blocksize;
int readOperation = 0;
READ_OR_RETURN(&mogpflags, sizeof(uint32));
READ_OR_RETURN(&GroupWMOID, sizeof(uint32));
Vector3 vec1, vec2;
READ_OR_RETURN(&vec1, sizeof(Vector3));
READ_OR_RETURN(&vec2, sizeof(Vector3));
bounds.set(vec1, vec2);
READ_OR_RETURN(&liquidflags, sizeof(uint32));
// will this ever be used? what is it good for anyway??
uint32 branches;
READ_OR_RETURN(&blockId, 4);
CMP_OR_RETURN(blockId, "GRP ");
READ_OR_RETURN(&blocksize, sizeof(int));
READ_OR_RETURN(&branches, sizeof(uint32));
for (uint32 b = 0; b < branches; ++b)
{
uint32 indexes;
// indexes for each branch (not used jet)
READ_OR_RETURN(&indexes, sizeof(uint32));
}
// ---- indexes
READ_OR_RETURN(&blockId, 4);
CMP_OR_RETURN(blockId, "INDX");
READ_OR_RETURN(&blocksize, sizeof(int));
uint32 nindexes;
READ_OR_RETURN(&nindexes, sizeof(uint32));
if (nindexes > 0)
{
uint16* indexarray = new uint16[nindexes];
READ_OR_RETURN_WITH_DELETE(indexarray, nindexes * sizeof(uint16));
triangles.reserve(nindexes / 3);
for (uint32 i = 0; i < nindexes; i += 3)
{
triangles.push_back(MeshTriangle(indexarray[i], indexarray[i + 1], indexarray[i + 2]));
}
delete[] indexarray;
}
// ---- vectors
READ_OR_RETURN(&blockId, 4);
CMP_OR_RETURN(blockId, "VERT");
READ_OR_RETURN(&blocksize, sizeof(int));
uint32 nvectors;
READ_OR_RETURN(&nvectors, sizeof(uint32));
if (nvectors > 0)
{
float* vectorarray = new float[nvectors * 3];
READ_OR_RETURN_WITH_DELETE(vectorarray, nvectors * sizeof(float) * 3);
for (uint32 i = 0; i < nvectors; ++i)
{
vertexArray.push_back( Vector3(vectorarray + 3 * i) );
}
delete[] vectorarray;
}
// ----- liquid
liquid = nullptr;
if (liquidflags & 3)
{
READ_OR_RETURN(&blockId, 4);
CMP_OR_RETURN(blockId, "LIQU");
READ_OR_RETURN(&blocksize, sizeof(int));
uint32 liquidType;
READ_OR_RETURN(&liquidType, sizeof(uint32));
if (liquidflags & 1)
{
WMOLiquidHeader hlq;
READ_OR_RETURN(&hlq, sizeof(WMOLiquidHeader));
liquid = new WmoLiquid(hlq.xtiles, hlq.ytiles, Vector3(hlq.pos_x, hlq.pos_y, hlq.pos_z), liquidType);
uint32 size = hlq.xverts * hlq.yverts;
READ_OR_RETURN(liquid->GetHeightStorage(), size * sizeof(float));
size = hlq.xtiles * hlq.ytiles;
READ_OR_RETURN(liquid->GetFlagsStorage(), size);
}
else
{
liquid = new WmoLiquid(0, 0, Vector3::zero(), liquidType);
liquid->GetHeightStorage()[0] = bounds.high().z;
}
}
return true;
}
GroupModel_Raw::~GroupModel_Raw()
{
delete liquid;
}
bool WorldModel_Raw::Read(const char* path)
{
FILE* rf = fopen(path, "rb");
if (!rf)
{
printf("ERROR: Can't open raw model file: %s\n", path);
return false;
}
char ident[9];
ident[8] = '\0';
int readOperation = 0;
READ_OR_RETURN(&ident, 8);
CMP_OR_RETURN(ident, RAW_VMAP_MAGIC);
// we have to read one int. This is needed during the export and we have to skip it here
uint32 tempNVectors;
READ_OR_RETURN(&tempNVectors, sizeof(tempNVectors));
uint32 groups;
READ_OR_RETURN(&groups, sizeof(uint32));
READ_OR_RETURN(&RootWMOID, sizeof(uint32));
groupsArray.resize(groups);
bool succeed = true;
for (uint32 g = 0; g < groups && succeed; ++g)
{
succeed = groupsArray[g].Read(rf);
}
if (succeed) /// rf will be freed inside Read if the function had any errors.
{
fclose(rf);
}
return succeed;
}
// drop of temporary use defines
#undef READ_OR_RETURN
#undef CMP_OR_RETURN
}

View File

@@ -0,0 +1,114 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _TILEASSEMBLER_H_
#define _TILEASSEMBLER_H_
#include <G3D/Matrix3.h>
#include <G3D/Vector3.h>
#include <map>
#include <set>
#include "ModelInstance.h"
#include "WorldModel.h"
namespace VMAP
{
/**
This Class is used to convert raw vector data into balanced BSP-Trees.
To start the conversion call convertWorld().
*/
//===============================================
class ModelPosition
{
private:
G3D::Matrix3 iRotation;
public:
ModelPosition() { }
G3D::Vector3 iPos;
G3D::Vector3 iDir;
float iScale{0.0f};
void init()
{
iRotation = G3D::Matrix3::fromEulerAnglesZYX(G3D::pif() * iDir.y / 180.f, G3D::pif() * iDir.x / 180.f, G3D::pif() * iDir.z / 180.f);
}
[[nodiscard]] G3D::Vector3 transform(const G3D::Vector3& pIn) const;
void moveToBasePos(const G3D::Vector3& pBasePos) { iPos -= pBasePos; }
};
typedef std::map<uint32, ModelSpawn> UniqueEntryMap;
typedef std::multimap<uint32, uint32> TileMap;
struct MapSpawns
{
UniqueEntryMap UniqueEntries;
TileMap TileEntries;
};
typedef std::map<uint32, MapSpawns*> MapData;
//===============================================
struct GroupModel_Raw
{
uint32 mogpflags{0};
uint32 GroupWMOID{0};
G3D::AABox bounds;
uint32 liquidflags{0};
std::vector<MeshTriangle> triangles;
std::vector<G3D::Vector3> vertexArray;
class WmoLiquid* liquid;
GroupModel_Raw() : liquid(nullptr) { }
~GroupModel_Raw();
bool Read(FILE* f);
};
struct WorldModel_Raw
{
uint32 RootWMOID;
std::vector<GroupModel_Raw> groupsArray;
bool Read(const char* path);
};
class TileAssembler
{
private:
std::string iDestDir;
std::string iSrcDir;
G3D::Table<std::string, unsigned int > iUniqueNameIds;
MapData mapData;
std::set<std::string> spawnedModelFiles;
public:
TileAssembler(const std::string& pSrcDirName, const std::string& pDestDirName);
virtual ~TileAssembler();
bool convertWorld2();
bool readMapSpawns();
bool calculateTransformedBound(ModelSpawn& spawn);
void exportGameobjectModels();
bool convertRawFile(const std::string& pModelFilename);
};
} // VMAP
#endif /*_TILEASSEMBLER_H_*/

View File

@@ -0,0 +1,315 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "GameObjectModel.h"
#include "Log.h"
#include "MapTree.h"
#include "ModelInstance.h"
#include "Timer.h"
#include "VMapDefinitions.h"
#include "VMapFactory.h"
#include "VMapMgr2.h"
#include "WorldModel.h"
using G3D::Vector3;
using G3D::Ray;
using G3D::AABox;
struct GameobjectModelData
{
GameobjectModelData(char const* name_, uint32 nameLength, Vector3 const& lowBound, Vector3 const& highBound, bool isWmo_) :
bound(lowBound, highBound), name(name_, nameLength), isWmo(isWmo_) { }
AABox bound;
std::string name;
bool isWmo;
};
typedef std::unordered_map<uint32, GameobjectModelData> ModelList;
ModelList model_list;
void LoadGameObjectModelList(std::string const& dataPath)
{
uint32 oldMSTime = getMSTime();
FILE* model_list_file = fopen((dataPath + "vmaps/" + VMAP::GAMEOBJECT_MODELS).c_str(), "rb");
if (!model_list_file)
{
LOG_ERROR("maps", "Unable to open '{}' file.", VMAP::GAMEOBJECT_MODELS);
return;
}
char magic[8];
if (fread(magic, 1, 8, model_list_file) != 8 || memcmp(magic, VMAP::VMAP_MAGIC, 8) != 0)
{
LOG_ERROR("maps", "File '{}' has wrong header, expected {}.", VMAP::GAMEOBJECT_MODELS, VMAP::VMAP_MAGIC);
fclose(model_list_file);
return;
}
uint32 name_length, displayId;
uint8 isWmo;
char buff[500];
while (true)
{
Vector3 v1, v2;
if (fread(&displayId, sizeof(uint32), 1, model_list_file) != 1)
if (feof(model_list_file)) // EOF flag is only set after failed reading attempt
{
break;
}
if (fread(&isWmo, sizeof(uint8), 1, model_list_file) != 1
|| fread(&name_length, sizeof(uint32), 1, model_list_file) != 1
|| name_length >= sizeof(buff)
|| fread(&buff, sizeof(char), name_length, model_list_file) != name_length
|| fread(&v1, sizeof(Vector3), 1, model_list_file) != 1
|| fread(&v2, sizeof(Vector3), 1, model_list_file) != 1)
{
LOG_ERROR("maps", "File '{}' seems to be corrupted!", VMAP::GAMEOBJECT_MODELS);
fclose(model_list_file);
break;
}
if (v1.isNaN() || v2.isNaN())
{
LOG_ERROR("maps", "File '{}' Model '{}' has invalid v1{} v2{} values!",
VMAP::GAMEOBJECT_MODELS, std::string(buff, name_length), v1.toString(), v2.toString());
continue;
}
model_list.emplace(std::piecewise_construct, std::forward_as_tuple(displayId), std::forward_as_tuple(&buff[0], name_length, v1, v2, isWmo != 0));
}
fclose(model_list_file);
LOG_INFO("server.loading", ">> Loaded {} GameObject Models in {} ms", uint32(model_list.size()), GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
GameObjectModel::~GameObjectModel()
{
if (iModel)
{
VMAP::VMapFactory::createOrGetVMapMgr()->releaseModelInstance(name);
}
}
bool GameObjectModel::initialize(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath)
{
ModelList::const_iterator it = model_list.find(modelOwner->GetDisplayId());
if (it == model_list.end())
{
return false;
}
G3D::AABox mdl_box(it->second.bound);
// ignore models with no bounds
if (mdl_box == G3D::AABox::zero())
{
LOG_ERROR("maps", "GameObject model {} has zero bounds, loading skipped", it->second.name);
return false;
}
iModel = VMAP::VMapFactory::createOrGetVMapMgr()->acquireModelInstance(dataPath + "vmaps/", it->second.name,
it->second.isWmo ? VMAP::ModelFlags::MOD_WORLDSPAWN : VMAP::ModelFlags::MOD_M2);
if (!iModel)
{
return false;
}
name = it->second.name;
iPos = modelOwner->GetPosition();
phasemask = modelOwner->GetPhaseMask();
iScale = modelOwner->GetScale();
iInvScale = 1.f / iScale;
G3D::Matrix3 iRotation = G3D::Matrix3::fromEulerAnglesZYX(modelOwner->GetOrientation(), 0, 0);
iInvRot = iRotation.inverse();
// transform bounding box:
mdl_box = AABox(mdl_box.low() * iScale, mdl_box.high() * iScale);
AABox rotated_bounds;
for (int i = 0; i < 8; ++i)
{
rotated_bounds.merge(iRotation * mdl_box.corner(i));
}
iBound = rotated_bounds + iPos;
#ifdef SPAWN_CORNERS
// test:
for (int i = 0; i < 8; ++i)
{
Vector3 pos(iBound.corner(i));
modelOwner->DebugVisualizeCorner(pos);
}
#endif
owner = std::move(modelOwner);
isWmo = it->second.isWmo;
return true;
}
GameObjectModel* GameObjectModel::Create(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath)
{
GameObjectModel* mdl = new GameObjectModel();
if (!mdl->initialize(std::move(modelOwner), dataPath))
{
delete mdl;
return nullptr;
}
return mdl;
}
bool GameObjectModel::intersectRay(const G3D::Ray& ray, float& MaxDist, bool StopAtFirstHit, uint32 ph_mask, VMAP::ModelIgnoreFlags ignoreFlags) const
{
if (!(phasemask & ph_mask) || !owner->IsSpawned())
{
return false;
}
float time = ray.intersectionTime(iBound);
if (time == G3D::inf())
{
return false;
}
// child bounds are defined in object space:
Vector3 p = iInvRot * (ray.origin() - iPos) * iInvScale;
Ray modRay(p, iInvRot * ray.direction());
float distance = MaxDist * iInvScale;
bool hit = iModel->IntersectRay(modRay, distance, StopAtFirstHit, ignoreFlags);
if (hit)
{
distance *= iScale;
MaxDist = distance;
}
return hit;
}
void GameObjectModel::IntersectPoint(G3D::Vector3 const& point, VMAP::AreaInfo& info, uint32 ph_mask) const
{
if (!(phasemask & ph_mask) || !owner->IsSpawned() || !IsMapObject())
return;
if (!iBound.contains(point))
return;
// child bounds are defined in object space:
Vector3 pModel = iInvRot * (point - iPos) * iInvScale;
Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f);
float zDist;
if (iModel->IntersectPoint(pModel, zDirModel, zDist, info))
{
Vector3 modelGround = pModel + zDist * zDirModel;
float world_Z = ((modelGround * iInvRot) * iScale + iPos).z;
if (info.ground_Z < world_Z)
info.ground_Z = world_Z;
}
}
bool GameObjectModel::GetLocationInfo(G3D::Vector3 const& point, VMAP::LocationInfo& info, uint32 ph_mask) const
{
if (!(phasemask & ph_mask) || !owner->IsSpawned() || !IsMapObject())
return false;
if (!iBound.contains(point))
return false;
// child bounds are defined in object space:
Vector3 pModel = iInvRot * (point - iPos) * iInvScale;
Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f);
float zDist;
if (iModel->GetLocationInfo(pModel, zDirModel, zDist, info))
{
Vector3 modelGround = pModel + zDist * zDirModel;
float world_Z = ((modelGround * iInvRot) * iScale + iPos).z;
if (info.ground_Z < world_Z)
{
info.ground_Z = world_Z;
return true;
}
}
return false;
}
bool GameObjectModel::GetLiquidLevel(G3D::Vector3 const& point, VMAP::LocationInfo& info, float& liqHeight) const
{
// child bounds are defined in object space:
Vector3 pModel = iInvRot * (point - iPos) * iInvScale;
//Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f);
float zDist;
if (info.hitModel->GetLiquidLevel(pModel, zDist))
{
// calculate world height (zDist in model coords):
// assume WMO not tilted (wouldn't make much sense anyway)
liqHeight = zDist * iScale + iPos.z;
return true;
}
return false;
}
bool GameObjectModel::UpdatePosition()
{
if (!iModel)
{
return false;
}
ModelList::const_iterator it = model_list.find(owner->GetDisplayId());
if (it == model_list.end())
{
return false;
}
G3D::AABox mdl_box(it->second.bound);
// ignore models with no bounds
if (mdl_box == G3D::AABox::zero())
{
//VMAP_ERROR_LOG("misc", "GameObject model %s has zero bounds, loading skipped", it->second.name.c_str());
return false;
}
iPos = owner->GetPosition();
G3D::Matrix3 iRotation = G3D::Matrix3::fromEulerAnglesZYX(owner->GetOrientation(), 0, 0);
iInvRot = iRotation.inverse();
// transform bounding box:
mdl_box = AABox(mdl_box.low() * iScale, mdl_box.high() * iScale);
AABox rotated_bounds;
for (int i = 0; i < 8; ++i)
{
rotated_bounds.merge(iRotation * mdl_box.corner(i));
}
iBound = rotated_bounds + iPos;
#ifdef SPAWN_CORNERS
// test:
for (int i = 0; i < 8; ++i)
{
Vector3 pos(iBound.corner(i));
owner->DebugVisualizeCorner(pos);
}
#endif
return true;
}

View File

@@ -0,0 +1,97 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _GAMEOBJECT_MODEL_H
#define _GAMEOBJECT_MODEL_H
#include "Define.h"
#include <G3D/AABox.h>
#include <G3D/Matrix3.h>
#include <G3D/Ray.h>
#include <G3D/Vector3.h>
namespace VMAP
{
class WorldModel;
struct AreaInfo;
struct LocationInfo;
enum class ModelIgnoreFlags : uint32;
}
class GameObject;
struct GameObjectDisplayInfoEntry;
class GameObjectModelOwnerBase
{
public:
virtual ~GameObjectModelOwnerBase() = default;
[[nodiscard]] virtual bool IsSpawned() const = 0;
[[nodiscard]] virtual uint32 GetDisplayId() const = 0;
[[nodiscard]] virtual uint32 GetPhaseMask() const = 0;
[[nodiscard]] virtual G3D::Vector3 GetPosition() const = 0;
[[nodiscard]] virtual float GetOrientation() const = 0;
[[nodiscard]] virtual float GetScale() const = 0;
virtual void DebugVisualizeCorner(G3D::Vector3 const& /*corner*/) const = 0;
};
class GameObjectModel
{
GameObjectModel() = default;
public:
std::string name;
[[nodiscard]] const G3D::AABox& GetBounds() const { return iBound; }
~GameObjectModel();
[[nodiscard]] const G3D::Vector3& GetPosition() const { return iPos; }
/** Enables\disables collision. */
void disable() { phasemask = 0; }
void enable(uint32 ph_mask) { phasemask = ph_mask; }
[[nodiscard]] bool isEnabled() const { return phasemask != 0; }
[[nodiscard]] bool IsMapObject() const { return isWmo; }
bool intersectRay(const G3D::Ray& Ray, float& MaxDist, bool StopAtFirstHit, uint32 ph_mask, VMAP::ModelIgnoreFlags ignoreFlags) const;
void IntersectPoint(G3D::Vector3 const& point, VMAP::AreaInfo& info, uint32 ph_mask) const;
bool GetLocationInfo(G3D::Vector3 const& point, VMAP::LocationInfo& info, uint32 ph_mask) const;
bool GetLiquidLevel(G3D::Vector3 const& point, VMAP::LocationInfo& info, float& liqHeight) const;
static GameObjectModel* Create(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath);
bool UpdatePosition();
private:
bool initialize(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath);
uint32 phasemask{0};
G3D::AABox iBound;
G3D::Matrix3 iInvRot;
G3D::Vector3 iPos;
float iInvScale{0};
float iScale{0};
VMAP::WorldModel* iModel{nullptr};
std::unique_ptr<GameObjectModelOwnerBase> owner;
bool isWmo{false};
};
void LoadGameObjectModelList(std::string const& dataPath);
#endif // _GAMEOBJECT_MODEL_H

View File

@@ -0,0 +1,37 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ModelIgnoreFlags_h__
#define ModelIgnoreFlags_h__
#include "Define.h"
namespace VMAP
{
enum class ModelIgnoreFlags : uint32
{
Nothing = 0x00,
M2 = 0x01
};
inline ModelIgnoreFlags operator&(ModelIgnoreFlags left, ModelIgnoreFlags right)
{
return ModelIgnoreFlags(uint32(left) & uint32(right));
}
}
#endif // ModelIgnoreFlags_h__

View File

@@ -0,0 +1,231 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ModelInstance.h"
#include "MapTree.h"
#include "WorldModel.h"
using G3D::Vector3;
using G3D::Ray;
namespace VMAP
{
ModelInstance::ModelInstance(const ModelSpawn& spawn, WorldModel* model): ModelSpawn(spawn), iModel(model)
{
iInvRot = G3D::Matrix3::fromEulerAnglesZYX(G3D::pi() * iRot.y / 180.f, G3D::pi() * iRot.x / 180.f, G3D::pi() * iRot.z / 180.f).inverse();
iInvScale = 1.f / iScale;
}
bool ModelInstance::intersectRay(const G3D::Ray& pRay, float& pMaxDist, bool StopAtFirstHit, ModelIgnoreFlags ignoreFlags) const
{
if (!iModel)
{
//std::cout << "<object not loaded>\n";
return false;
}
float time = pRay.intersectionTime(iBound);
if (time == G3D::inf())
{
// std::cout << "Ray does not hit '" << name << "'\n";
return false;
}
// std::cout << "Ray crosses bound of '" << name << "'\n";
/* std::cout << "ray from:" << pRay.origin().x << ", " << pRay.origin().y << ", " << pRay.origin().z
<< " dir:" << pRay.direction().x << ", " << pRay.direction().y << ", " << pRay.direction().z
<< " t/tmax:" << time << '/' << pMaxDist;
std::cout << "\nBound lo:" << iBound.low().x << ", " << iBound.low().y << ", " << iBound.low().z << " hi: "
<< iBound.high().x << ", " << iBound.high().y << ", " << iBound.high().z << std::endl; */
// child bounds are defined in object space:
Vector3 p = iInvRot * (pRay.origin() - iPos) * iInvScale;
Ray modRay(p, iInvRot * pRay.direction());
float distance = pMaxDist * iInvScale;
bool hit = iModel->IntersectRay(modRay, distance, StopAtFirstHit, ignoreFlags);
if (hit)
{
distance *= iScale;
pMaxDist = distance;
}
return hit;
}
void ModelInstance::intersectPoint(const G3D::Vector3& p, AreaInfo& info) const
{
if (!iModel)
{
#ifdef VMAP_DEBUG
std::cout << "<object not loaded>\n";
#endif
return;
}
// M2 files don't contain area info, only WMO files
if (flags & MOD_M2)
{
return;
}
if (!iBound.contains(p))
{
return;
}
// child bounds are defined in object space:
Vector3 pModel = iInvRot * (p - iPos) * iInvScale;
Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f);
float zDist;
if (iModel->IntersectPoint(pModel, zDirModel, zDist, info))
{
Vector3 modelGround = pModel + zDist * zDirModel;
// Transform back to world space. Note that:
// Mat * vec == vec * Mat.transpose()
// and for rotation matrices: Mat.inverse() == Mat.transpose()
float world_Z = ((modelGround * iInvRot) * iScale + iPos).z;
if (info.ground_Z < world_Z)
{
info.ground_Z = world_Z;
info.adtId = adtId;
}
}
}
bool ModelInstance::GetLocationInfo(const G3D::Vector3& p, LocationInfo& info) const
{
if (!iModel)
{
#ifdef VMAP_DEBUG
std::cout << "<object not loaded>\n";
#endif
return false;
}
// M2 files don't contain area info, only WMO files
if (flags & MOD_M2)
{
return false;
}
if (!iBound.contains(p))
{
return false;
}
// child bounds are defined in object space:
Vector3 pModel = iInvRot * (p - iPos) * iInvScale;
Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f);
float zDist;
if (iModel->GetLocationInfo(pModel, zDirModel, zDist, info))
{
Vector3 modelGround = pModel + zDist * zDirModel;
// Transform back to world space. Note that:
// Mat * vec == vec * Mat.transpose()
// and for rotation matrices: Mat.inverse() == Mat.transpose()
float world_Z = ((modelGround * iInvRot) * iScale + iPos).z;
if (info.ground_Z < world_Z) // hm...could it be handled automatically with zDist at intersection?
{
info.ground_Z = world_Z;
info.hitInstance = this;
return true;
}
}
return false;
}
bool ModelInstance::GetLiquidLevel(const G3D::Vector3& p, LocationInfo& info, float& liqHeight) const
{
// child bounds are defined in object space:
Vector3 pModel = iInvRot * (p - iPos) * iInvScale;
//Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f);
float zDist;
if (info.hitModel->GetLiquidLevel(pModel, zDist))
{
// calculate world height (zDist in model coords):
// assume WMO not tilted (wouldn't make much sense anyway)
liqHeight = zDist * iScale + iPos.z;
return true;
}
return false;
}
bool ModelSpawn::readFromFile(FILE* rf, ModelSpawn& spawn)
{
uint32 check = 0, nameLen;
check += fread(&spawn.flags, sizeof(uint32), 1, rf);
// EoF?
if (!check)
{
if (ferror(rf))
{
std::cout << "Error reading ModelSpawn!\n";
}
return false;
}
check += fread(&spawn.adtId, sizeof(uint16), 1, rf);
check += fread(&spawn.ID, sizeof(uint32), 1, rf);
check += fread(&spawn.iPos, sizeof(float), 3, rf);
check += fread(&spawn.iRot, sizeof(float), 3, rf);
check += fread(&spawn.iScale, sizeof(float), 1, rf);
bool has_bound = (spawn.flags & MOD_HAS_BOUND);
if (has_bound) // only WMOs have bound in MPQ, only available after computation
{
Vector3 bLow, bHigh;
check += fread(&bLow, sizeof(float), 3, rf);
check += fread(&bHigh, sizeof(float), 3, rf);
spawn.iBound = G3D::AABox(bLow, bHigh);
}
check += fread(&nameLen, sizeof(uint32), 1, rf);
if (check != uint32(has_bound ? 17 : 11))
{
std::cout << "Error reading ModelSpawn!\n";
return false;
}
char nameBuff[500];
if (nameLen > 500) // file names should never be that long, must be file error
{
std::cout << "Error reading ModelSpawn, file name too long!\n";
return false;
}
check = fread(nameBuff, sizeof(char), nameLen, rf);
if (check != nameLen)
{
std::cout << "Error reading ModelSpawn!\n";
return false;
}
spawn.name = std::string(nameBuff, nameLen);
return true;
}
bool ModelSpawn::writeToFile(FILE* wf, const ModelSpawn& spawn)
{
uint32 check = 0;
check += fwrite(&spawn.flags, sizeof(uint32), 1, wf);
check += fwrite(&spawn.adtId, sizeof(uint16), 1, wf);
check += fwrite(&spawn.ID, sizeof(uint32), 1, wf);
check += fwrite(&spawn.iPos, sizeof(float), 3, wf);
check += fwrite(&spawn.iRot, sizeof(float), 3, wf);
check += fwrite(&spawn.iScale, sizeof(float), 1, wf);
bool has_bound = (spawn.flags & MOD_HAS_BOUND);
if (has_bound) // only WMOs have bound in MPQ, only available after computation
{
check += fwrite(&spawn.iBound.low(), sizeof(float), 3, wf);
check += fwrite(&spawn.iBound.high(), sizeof(float), 3, wf);
}
uint32 nameLen = spawn.name.length();
check += fwrite(&nameLen, sizeof(uint32), 1, wf);
if (check != uint32(has_bound ? 17 : 11)) { return false; }
check = fwrite(spawn.name.c_str(), sizeof(char), nameLen, wf);
if (check != nameLen) { return false; }
return true;
}
}

View File

@@ -0,0 +1,80 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _MODELINSTANCE_H_
#define _MODELINSTANCE_H_
#include "Define.h"
#include <G3D/AABox.h>
#include <G3D/Matrix3.h>
#include <G3D/Ray.h>
#include <G3D/Vector3.h>
namespace VMAP
{
class WorldModel;
struct AreaInfo;
struct LocationInfo;
enum class ModelIgnoreFlags : uint32;
enum ModelFlags
{
MOD_M2 = 1,
MOD_WORLDSPAWN = 1 << 1,
MOD_HAS_BOUND = 1 << 2
};
class ModelSpawn
{
public:
//mapID, tileX, tileY, Flags, ID, Pos, Rot, Scale, Bound_lo, Bound_hi, name
uint32 flags;
uint16 adtId;
uint32 ID;
G3D::Vector3 iPos;
G3D::Vector3 iRot;
float iScale;
G3D::AABox iBound;
std::string name;
bool operator==(const ModelSpawn& other) const { return ID == other.ID; }
//uint32 hashCode() const { return ID; }
// temp?
[[nodiscard]] const G3D::AABox& GetBounds() const { return iBound; }
static bool readFromFile(FILE* rf, ModelSpawn& spawn);
static bool writeToFile(FILE* rw, const ModelSpawn& spawn);
};
class ModelInstance: public ModelSpawn
{
public:
ModelInstance() { }
ModelInstance(const ModelSpawn& spawn, WorldModel* model);
void setUnloaded() { iModel = nullptr; }
bool intersectRay(const G3D::Ray& pRay, float& pMaxDist, bool StopAtFirstHit, ModelIgnoreFlags ignoreFlags) const;
void intersectPoint(const G3D::Vector3& p, AreaInfo& info) const;
bool GetLocationInfo(const G3D::Vector3& p, LocationInfo& info) const;
bool GetLiquidLevel(const G3D::Vector3& p, LocationInfo& info, float& liqHeight) const;
WorldModel* getWorldModel() { return iModel; }
protected:
G3D::Matrix3 iInvRot;
float iInvScale{0.0f};
WorldModel* iModel{nullptr};
};
} // namespace VMAP
#endif // _MODELINSTANCE_H_

View File

@@ -0,0 +1,700 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "WorldModel.h"
#include "MapTree.h"
#include "ModelIgnoreFlags.h"
#include "ModelInstance.h"
#include "VMapDefinitions.h"
using G3D::Vector3;
using G3D::Ray;
template<> struct BoundsTrait<VMAP::GroupModel>
{
static void GetBounds(const VMAP::GroupModel& obj, G3D::AABox& out) { out = obj.GetBound(); }
};
namespace VMAP
{
bool IntersectTriangle(const MeshTriangle& tri, std::vector<Vector3>::const_iterator points, const G3D::Ray& ray, float& distance)
{
static const float EPS = 1e-5f;
// See RTR2 ch. 13.7 for the algorithm.
const Vector3 e1 = points[tri.idx1] - points[tri.idx0];
const Vector3 e2 = points[tri.idx2] - points[tri.idx0];
const Vector3 p(ray.direction().cross(e2));
const float a = e1.dot(p);
if (std::fabs(a) < EPS)
{
// Determinant is ill-conditioned; abort early
return false;
}
const float f = 1.0f / a;
const Vector3 s(ray.origin() - points[tri.idx0]);
const float u = f * s.dot(p);
if ((u < 0.0f) || (u > 1.0f))
{
// We hit the plane of the m_geometry, but outside the m_geometry
return false;
}
const Vector3 q(s.cross(e1));
const float v = f * ray.direction().dot(q);
if ((v < 0.0f) || ((u + v) > 1.0f))
{
// We hit the plane of the triangle, but outside the triangle
return false;
}
const float t = f * e2.dot(q);
if ((t > 0.0f) && (t < distance))
{
// This is a new hit, closer than the previous one
distance = t;
/* baryCoord[0] = 1.0 - u - v;
baryCoord[1] = u;
baryCoord[2] = v; */
return true;
}
// This hit is after the previous hit, so ignore it
return false;
}
class TriBoundFunc
{
public:
TriBoundFunc(std::vector<Vector3>& vert): vertices(vert.begin()) { }
void operator()(const MeshTriangle& tri, G3D::AABox& out) const
{
G3D::Vector3 lo = vertices[tri.idx0];
G3D::Vector3 hi = lo;
lo = (lo.min(vertices[tri.idx1])).min(vertices[tri.idx2]);
hi = (hi.max(vertices[tri.idx1])).max(vertices[tri.idx2]);
out = G3D::AABox(lo, hi);
}
protected:
const std::vector<Vector3>::const_iterator vertices;
};
// ===================== WmoLiquid ==================================
WmoLiquid::WmoLiquid(uint32 width, uint32 height, const Vector3& corner, uint32 type):
iTilesX(width), iTilesY(height), iCorner(corner), iType(type)
{
if (width && height)
{
iHeight = new float[(width + 1) * (height + 1)];
iFlags = new uint8[width * height];
}
else
{
iHeight = new float[1];
iFlags = nullptr;
}
}
WmoLiquid::WmoLiquid(const WmoLiquid& other): iHeight(0), iFlags(0)
{
*this = other; // use assignment operator...
}
WmoLiquid::~WmoLiquid()
{
delete[] iHeight;
delete[] iFlags;
}
WmoLiquid& WmoLiquid::operator=(const WmoLiquid& other)
{
if (this == &other)
{
return *this;
}
iTilesX = other.iTilesX;
iTilesY = other.iTilesY;
iCorner = other.iCorner;
iType = other.iType;
delete[] iHeight;
delete[] iFlags;
if (other.iHeight)
{
iHeight = new float[(iTilesX + 1) * (iTilesY + 1)];
memcpy(iHeight, other.iHeight, (iTilesX + 1) * (iTilesY + 1)*sizeof(float));
}
else
{
iHeight = 0;
}
if (other.iFlags)
{
iFlags = new uint8[iTilesX * iTilesY];
memcpy(iFlags, other.iFlags, iTilesX * iTilesY);
}
else
{
iFlags = 0;
}
return *this;
}
bool WmoLiquid::GetLiquidHeight(const Vector3& pos, float& liqHeight) const
{
// simple case
if (!iFlags)
{
liqHeight = iHeight[0];
return true;
}
float tx_f = (pos.x - iCorner.x) / LIQUID_TILE_SIZE;
uint32 tx = uint32(tx_f);
if (tx_f < 0.0f || tx >= iTilesX)
{
return false;
}
float ty_f = (pos.y - iCorner.y) / LIQUID_TILE_SIZE;
uint32 ty = uint32(ty_f);
if (ty_f < 0.0f || ty >= iTilesY)
{
return false;
}
// check if tile shall be used for liquid level
// checking for 0x08 *might* be enough, but disabled tiles always are 0x?F:
if (iFlags && (iFlags[tx + ty * iTilesX] & 0x0F) == 0x0F)
{
return false;
}
// (dx, dy) coordinates inside tile, in [0, 1]^2
float dx = tx_f - (float)tx;
float dy = ty_f - (float)ty;
/* Tesselate tile to two triangles (not sure if client does it exactly like this)
^ dy
|
1 x---------x (1, 1)
| (b) / |
| / |
| / |
| / (a) |
x---------x---> dx
0 1
*/
if (!iHeight)
{
return false;
}
const uint32 rowOffset = iTilesX + 1;
if (dx > dy) // case (a)
{
float sx = iHeight[tx + 1 + ty * rowOffset] - iHeight[tx + ty * rowOffset];
float sy = iHeight[tx + 1 + (ty + 1) * rowOffset] - iHeight[tx + 1 + ty * rowOffset];
liqHeight = iHeight[tx + ty * rowOffset] + dx * sx + dy * sy;
}
else // case (b)
{
float sx = iHeight[tx + 1 + (ty + 1) * rowOffset] - iHeight[tx + (ty + 1) * rowOffset];
float sy = iHeight[tx + (ty + 1) * rowOffset] - iHeight[tx + ty * rowOffset];
liqHeight = iHeight[tx + ty * rowOffset] + dx * sx + dy * sy;
}
return true;
}
uint32 WmoLiquid::GetFileSize()
{
return 2 * sizeof(uint32) +
sizeof(Vector3) +
sizeof(uint32) +
(iFlags ? ((iTilesX + 1) * (iTilesY + 1) * sizeof(float) + iTilesX * iTilesY) : sizeof(float));
}
bool WmoLiquid::writeToFile(FILE* wf)
{
bool result = false;
if (fwrite(&iTilesX, sizeof(uint32), 1, wf) == 1 &&
fwrite(&iTilesY, sizeof(uint32), 1, wf) == 1 &&
fwrite(&iCorner, sizeof(Vector3), 1, wf) == 1 &&
fwrite(&iType, sizeof(uint32), 1, wf) == 1)
{
if (iTilesX && iTilesY)
{
uint32 size = (iTilesX + 1) * (iTilesY + 1);
if (fwrite(iHeight, sizeof(float), size, wf) == size)
{
size = iTilesX * iTilesY;
result = fwrite(iFlags, sizeof(uint8), size, wf) == size;
}
}
else
result = fwrite(iHeight, sizeof(float), 1, wf) == 1;
}
return result;
}
bool WmoLiquid::readFromFile(FILE* rf, WmoLiquid*& out)
{
bool result = false;
WmoLiquid* liquid = new WmoLiquid();
if (fread(&liquid->iTilesX, sizeof(uint32), 1, rf) == 1 &&
fread(&liquid->iTilesY, sizeof(uint32), 1, rf) == 1 &&
fread(&liquid->iCorner, sizeof(Vector3), 1, rf) == 1 &&
fread(&liquid->iType, sizeof(uint32), 1, rf) == 1)
{
if (liquid->iTilesX && liquid->iTilesY)
{
uint32 size = (liquid->iTilesX + 1) * (liquid->iTilesY + 1);
liquid->iHeight = new float[size];
if (fread(liquid->iHeight, sizeof(float), size, rf) == size)
{
size = liquid->iTilesX * liquid->iTilesY;
liquid->iFlags = new uint8[size];
result = fread(liquid->iFlags, sizeof(uint8), size, rf) == size;
}
}
else
{
liquid->iHeight = new float[1];
result = fread(liquid->iHeight, sizeof(float), 1, rf) == 1;
}
}
if (!result)
{
delete liquid;
}
else
{
out = liquid;
}
return result;
}
void WmoLiquid::GetPosInfo(uint32& tilesX, uint32& tilesY, G3D::Vector3& corner) const
{
tilesX = iTilesX;
tilesY = iTilesY;
corner = iCorner;
}
// ===================== GroupModel ==================================
GroupModel::GroupModel(const GroupModel& other):
iBound(other.iBound), iMogpFlags(other.iMogpFlags), iGroupWMOID(other.iGroupWMOID),
vertices(other.vertices), triangles(other.triangles), meshTree(other.meshTree), iLiquid(0)
{
if (other.iLiquid)
{
iLiquid = new WmoLiquid(*other.iLiquid);
}
}
void GroupModel::setMeshData(std::vector<Vector3>& vert, std::vector<MeshTriangle>& tri)
{
vertices.swap(vert);
triangles.swap(tri);
TriBoundFunc bFunc(vertices);
meshTree.build(triangles, bFunc);
}
bool GroupModel::writeToFile(FILE* wf)
{
bool result = true;
uint32 chunkSize, count;
if (fwrite(&iBound, sizeof(G3D::AABox), 1, wf) != 1) { result = false; }
if (result && fwrite(&iMogpFlags, sizeof(uint32), 1, wf) != 1) { result = false; }
if (result && fwrite(&iGroupWMOID, sizeof(uint32), 1, wf) != 1) { result = false; }
// write vertices
if (result && fwrite("VERT", 1, 4, wf) != 4) { result = false; }
count = vertices.size();
chunkSize = sizeof(uint32) + sizeof(Vector3) * count;
if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) { result = false; }
if (result && fwrite(&count, sizeof(uint32), 1, wf) != 1) { result = false; }
if (!count) // models without (collision) geometry end here, unsure if they are useful
{
return result;
}
if (result && fwrite(&vertices[0], sizeof(Vector3), count, wf) != count) { result = false; }
// write triangle mesh
if (result && fwrite("TRIM", 1, 4, wf) != 4) { result = false; }
count = triangles.size();
chunkSize = sizeof(uint32) + sizeof(MeshTriangle) * count;
if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) { result = false; }
if (result && fwrite(&count, sizeof(uint32), 1, wf) != 1) { result = false; }
if (result && fwrite(&triangles[0], sizeof(MeshTriangle), count, wf) != count) { result = false; }
// write mesh BIH
if (result && fwrite("MBIH", 1, 4, wf) != 4) { result = false; }
if (result) { result = meshTree.writeToFile(wf); }
// write liquid data
if (result && fwrite("LIQU", 1, 4, wf) != 4) { result = false; }
if (!iLiquid)
{
chunkSize = 0;
if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) { result = false; }
return result;
}
chunkSize = iLiquid->GetFileSize();
if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) { result = false; }
if (result) { result = iLiquid->writeToFile(wf); }
return result;
}
bool GroupModel::readFromFile(FILE* rf)
{
char chunk[8];
bool result = true;
uint32 chunkSize = 0;
uint32 count = 0;
triangles.clear();
vertices.clear();
delete iLiquid;
iLiquid = nullptr;
if (fread(&iBound, sizeof(G3D::AABox), 1, rf) != 1) { result = false; }
if (result && fread(&iMogpFlags, sizeof(uint32), 1, rf) != 1) { result = false; }
if (result && fread(&iGroupWMOID, sizeof(uint32), 1, rf) != 1) { result = false; }
// read vertices
if (result && !readChunk(rf, chunk, "VERT", 4)) { result = false; }
if (result && fread(&chunkSize, sizeof(uint32), 1, rf) != 1) { result = false; }
if (result && fread(&count, sizeof(uint32), 1, rf) != 1) { result = false; }
if (!count) // models without (collision) geometry end here, unsure if they are useful
{
return result;
}
if (result) { vertices.resize(count); }
if (result && fread(&vertices[0], sizeof(Vector3), count, rf) != count) { result = false; }
// read triangle mesh
if (result && !readChunk(rf, chunk, "TRIM", 4)) { result = false; }
if (result && fread(&chunkSize, sizeof(uint32), 1, rf) != 1) { result = false; }
if (result && fread(&count, sizeof(uint32), 1, rf) != 1) { result = false; }
if (result) { triangles.resize(count); }
if (result && fread(&triangles[0], sizeof(MeshTriangle), count, rf) != count) { result = false; }
// read mesh BIH
if (result && !readChunk(rf, chunk, "MBIH", 4)) { result = false; }
if (result) { result = meshTree.readFromFile(rf); }
// write liquid data
if (result && !readChunk(rf, chunk, "LIQU", 4)) { result = false; }
if (result && fread(&chunkSize, sizeof(uint32), 1, rf) != 1) { result = false; }
if (result && chunkSize > 0)
{
result = WmoLiquid::readFromFile(rf, iLiquid);
}
return result;
}
struct GModelRayCallback
{
GModelRayCallback(const std::vector<MeshTriangle>& tris, const std::vector<Vector3>& vert):
vertices(vert.begin()), triangles(tris.begin()), hit(false) { }
bool operator()(const G3D::Ray& ray, uint32 entry, float& distance, bool /*StopAtFirstHit*/)
{
bool result = IntersectTriangle(triangles[entry], vertices, ray, distance);
if (result) { hit = true; }
return hit;
}
std::vector<Vector3>::const_iterator vertices;
std::vector<MeshTriangle>::const_iterator triangles;
bool hit;
};
bool GroupModel::IntersectRay(const G3D::Ray& ray, float& distance, bool stopAtFirstHit) const
{
if (triangles.empty())
{
return false;
}
GModelRayCallback callback(triangles, vertices);
meshTree.intersectRay(ray, callback, distance, stopAtFirstHit);
return callback.hit;
}
bool GroupModel::IsInsideObject(const Vector3& pos, const Vector3& down, float& z_dist) const
{
if (triangles.empty() || !iBound.contains(pos))
{
return false;
}
Vector3 rPos = pos - 0.1f * down;
float dist = G3D::inf();
G3D::Ray ray(rPos, down);
bool hit = IntersectRay(ray, dist, false);
if (hit)
{
z_dist = dist - 0.1f;
}
return hit;
}
bool GroupModel::GetLiquidLevel(const Vector3& pos, float& liqHeight) const
{
if (iLiquid)
{
return iLiquid->GetLiquidHeight(pos, liqHeight);
}
return false;
}
uint32 GroupModel::GetLiquidType() const
{
if (iLiquid)
{
return iLiquid->GetType();
}
return 0;
}
void GroupModel::GetMeshData(std::vector<G3D::Vector3>& outVertices, std::vector<MeshTriangle>& outTriangles, WmoLiquid*& liquid)
{
outVertices = vertices;
outTriangles = triangles;
liquid = iLiquid;
}
// ===================== WorldModel ==================================
void WorldModel::setGroupModels(std::vector<GroupModel>& models)
{
groupModels.swap(models);
groupTree.build(groupModels, BoundsTrait<GroupModel>::GetBounds, 1);
}
struct WModelRayCallBack
{
WModelRayCallBack(const std::vector<GroupModel>& mod): models(mod.begin()), hit(false) { }
bool operator()(const G3D::Ray& ray, uint32 entry, float& distance, bool StopAtFirstHit)
{
bool result = models[entry].IntersectRay(ray, distance, StopAtFirstHit);
if (result) { hit = true; }
return hit;
}
std::vector<GroupModel>::const_iterator models;
bool hit;
};
bool WorldModel::IntersectRay(const G3D::Ray& ray, float& distance, bool stopAtFirstHit, ModelIgnoreFlags ignoreFlags) const
{
// If the caller asked us to ignore certain objects we should check flags
if ((ignoreFlags & ModelIgnoreFlags::M2) != ModelIgnoreFlags::Nothing)
{
// M2 models are not taken into account for LoS calculation if caller requested their ignoring.
if (Flags & MOD_M2)
{
return false;
}
}
// small M2 workaround, maybe better make separate class with virtual intersection funcs
// in any case, there's no need to use a bound tree if we only have one submodel
if (groupModels.size() == 1)
{
return groupModels[0].IntersectRay(ray, distance, stopAtFirstHit);
}
WModelRayCallBack isc(groupModels);
groupTree.intersectRay(ray, isc, distance, stopAtFirstHit);
return isc.hit;
}
class WModelAreaCallback
{
public:
WModelAreaCallback(const std::vector<GroupModel>& vals, const Vector3& down):
prims(vals.begin()), hit(vals.end()), minVol(G3D::inf()), zDist(G3D::inf()), zVec(down) { }
std::vector<GroupModel>::const_iterator prims;
std::vector<GroupModel>::const_iterator hit;
float minVol;
float zDist;
Vector3 zVec;
void operator()(const Vector3& point, uint32 entry)
{
float group_Z;
//float pVol = prims[entry].GetBound().volume();
//if (pVol < minVol)
//{
/* if (prims[entry].iBound.contains(point)) */
if (prims[entry].IsInsideObject(point, zVec, group_Z))
{
//minVol = pVol;
//hit = prims + entry;
if (group_Z < zDist)
{
zDist = group_Z;
hit = prims + entry;
}
#ifdef VMAP_DEBUG
const GroupModel& gm = prims[entry];
printf("%10u %8X %7.3f, %7.3f, %7.3f | %7.3f, %7.3f, %7.3f | z=%f, p_z=%f\n", gm.GetWmoID(), gm.GetMogpFlags(),
gm.GetBound().low().x, gm.GetBound().low().y, gm.GetBound().low().z,
gm.GetBound().high().x, gm.GetBound().high().y, gm.GetBound().high().z, group_Z, point.z);
#endif
}
//}
//std::cout << "trying to intersect '" << prims[entry].name << "'\n";
}
};
bool WorldModel::IntersectPoint(const G3D::Vector3& p, const G3D::Vector3& down, float& dist, AreaInfo& info) const
{
if (groupModels.empty())
{
return false;
}
WModelAreaCallback callback(groupModels, down);
groupTree.intersectPoint(p, callback);
if (callback.hit != groupModels.end())
{
info.rootId = RootWMOID;
info.groupId = callback.hit->GetWmoID();
info.flags = callback.hit->GetMogpFlags();
info.result = true;
dist = callback.zDist;
return true;
}
return false;
}
bool WorldModel::GetLocationInfo(const G3D::Vector3& p, const G3D::Vector3& down, float& dist, LocationInfo& info) const
{
if (groupModels.empty())
{
return false;
}
WModelAreaCallback callback(groupModels, down);
groupTree.intersectPoint(p, callback);
if (callback.hit != groupModels.end())
{
info.rootId = RootWMOID;
info.hitModel = &(*callback.hit);
dist = callback.zDist;
return true;
}
return false;
}
bool WorldModel::writeFile(const std::string& filename)
{
FILE* wf = fopen(filename.c_str(), "wb");
if (!wf)
{
return false;
}
uint32 chunkSize, count;
bool result = fwrite(VMAP_MAGIC, 1, 8, wf) == 8;
if (result && fwrite("WMOD", 1, 4, wf) != 4) { result = false; }
chunkSize = sizeof(uint32) + sizeof(uint32);
if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) { result = false; }
if (result && fwrite(&RootWMOID, sizeof(uint32), 1, wf) != 1) { result = false; }
// write group models
count = groupModels.size();
if (count)
{
if (result && fwrite("GMOD", 1, 4, wf) != 4) { result = false; }
//chunkSize = sizeof(uint32)+ sizeof(GroupModel)*count;
//if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false;
if (result && fwrite(&count, sizeof(uint32), 1, wf) != 1) { result = false; }
for (uint32 i = 0; i < groupModels.size() && result; ++i)
{
result = groupModels[i].writeToFile(wf);
}
// write group BIH
if (result && fwrite("GBIH", 1, 4, wf) != 4) { result = false; }
if (result) { result = groupTree.writeToFile(wf); }
}
fclose(wf);
return result;
}
bool WorldModel::readFile(const std::string& filename)
{
FILE* rf = fopen(filename.c_str(), "rb");
if (!rf)
{
return false;
}
bool result = true;
uint32 chunkSize = 0;
uint32 count = 0;
char chunk[8]; // Ignore the added magic header
if (!readChunk(rf, chunk, VMAP_MAGIC, 8)) { result = false; }
if (result && !readChunk(rf, chunk, "WMOD", 4)) { result = false; }
if (result && fread(&chunkSize, sizeof(uint32), 1, rf) != 1) { result = false; }
if (result && fread(&RootWMOID, sizeof(uint32), 1, rf) != 1) { result = false; }
// read group models
if (result && readChunk(rf, chunk, "GMOD", 4))
{
//if (fread(&chunkSize, sizeof(uint32), 1, rf) != 1) result = false;
if (fread(&count, sizeof(uint32), 1, rf) != 1) { result = false; }
if (result) { groupModels.resize(count); }
//if (result && fread(&groupModels[0], sizeof(GroupModel), count, rf) != count) result = false;
for (uint32 i = 0; i < count && result; ++i)
{
result = groupModels[i].readFromFile(rf);
}
// read group BIH
if (result && !readChunk(rf, chunk, "GBIH", 4)) { result = false; }
if (result) { result = groupTree.readFromFile(rf); }
}
fclose(rf);
return result;
}
void WorldModel::GetGroupModels(std::vector<GroupModel>& outGroupModels)
{
outGroupModels = groupModels;
}
}

View File

@@ -0,0 +1,126 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _WORLDMODEL_H
#define _WORLDMODEL_H
#include "BoundingIntervalHierarchy.h"
#include "Define.h"
#include <G3D/AABox.h>
#include <G3D/HashTrait.h>
#include <G3D/Ray.h>
#include <G3D/Vector3.h>
namespace VMAP
{
class TreeNode;
struct AreaInfo;
struct LocationInfo;
enum class ModelIgnoreFlags : uint32;
class MeshTriangle
{
public:
MeshTriangle() { }
MeshTriangle(uint32 na, uint32 nb, uint32 nc): idx0(na), idx1(nb), idx2(nc) { }
uint32 idx0{0};
uint32 idx1{0};
uint32 idx2{0};
};
class WmoLiquid
{
public:
WmoLiquid(uint32 width, uint32 height, const G3D::Vector3& corner, uint32 type);
WmoLiquid(const WmoLiquid& other);
~WmoLiquid();
WmoLiquid& operator=(const WmoLiquid& other);
bool GetLiquidHeight(const G3D::Vector3& pos, float& liqHeight) const;
[[nodiscard]] uint32 GetType() const { return iType; }
float* GetHeightStorage() { return iHeight; }
uint8* GetFlagsStorage() { return iFlags; }
uint32 GetFileSize();
bool writeToFile(FILE* wf);
static bool readFromFile(FILE* rf, WmoLiquid*& liquid);
void GetPosInfo(uint32& tilesX, uint32& tilesY, G3D::Vector3& corner) const;
private:
WmoLiquid() { }
uint32 iTilesX{0}; //!< number of tiles in x direction, each
uint32 iTilesY{0};
G3D::Vector3 iCorner; //!< the lower corner
uint32 iType{0}; //!< liquid type
float* iHeight{nullptr}; //!< (tilesX + 1)*(tilesY + 1) height values
uint8* iFlags{nullptr}; //!< info if liquid tile is used
};
/*! holding additional info for WMO group files */
class GroupModel
{
public:
GroupModel() { }
GroupModel(const GroupModel& other);
GroupModel(uint32 mogpFlags, uint32 groupWMOID, const G3D::AABox& bound):
iBound(bound), iMogpFlags(mogpFlags), iGroupWMOID(groupWMOID), iLiquid(nullptr) { }
~GroupModel() { delete iLiquid; }
//! pass mesh data to object and create BIH. Passed vectors get get swapped with old geometry!
void setMeshData(std::vector<G3D::Vector3>& vert, std::vector<MeshTriangle>& tri);
void setLiquidData(WmoLiquid*& liquid) { iLiquid = liquid; liquid = nullptr; }
bool IntersectRay(const G3D::Ray& ray, float& distance, bool stopAtFirstHit) const;
bool IsInsideObject(const G3D::Vector3& pos, const G3D::Vector3& down, float& z_dist) const;
bool GetLiquidLevel(const G3D::Vector3& pos, float& liqHeight) const;
[[nodiscard]] uint32 GetLiquidType() const;
bool writeToFile(FILE* wf);
bool readFromFile(FILE* rf);
[[nodiscard]] const G3D::AABox& GetBound() const { return iBound; }
[[nodiscard]] uint32 GetMogpFlags() const { return iMogpFlags; }
[[nodiscard]] uint32 GetWmoID() const { return iGroupWMOID; }
void GetMeshData(std::vector<G3D::Vector3>& outVertices, std::vector<MeshTriangle>& outTriangles, WmoLiquid*& liquid);
protected:
G3D::AABox iBound;
uint32 iMogpFlags{0};// 0x8 outdor; 0x2000 indoor
uint32 iGroupWMOID{0};
std::vector<G3D::Vector3> vertices;
std::vector<MeshTriangle> triangles;
BIH meshTree;
WmoLiquid* iLiquid{nullptr};
};
/*! Holds a model (converted M2 or WMO) in its original coordinate space */
class WorldModel
{
public:
WorldModel() { }
//! pass group models to WorldModel and create BIH. Passed vector is swapped with old geometry!
void setGroupModels(std::vector<GroupModel>& models);
void setRootWmoID(uint32 id) { RootWMOID = id; }
bool IntersectRay(const G3D::Ray& ray, float& distance, bool stopAtFirstHit, ModelIgnoreFlags ignoreFlags) const;
bool IntersectPoint(const G3D::Vector3& p, const G3D::Vector3& down, float& dist, AreaInfo& info) const;
bool GetLocationInfo(const G3D::Vector3& p, const G3D::Vector3& down, float& dist, LocationInfo& info) const;
bool writeFile(const std::string& filename);
bool readFile(const std::string& filename);
void GetGroupModels(std::vector<GroupModel>& outGroupModels);
uint32 Flags;
protected:
uint32 RootWMOID{0};
std::vector<GroupModel> groupModels;
BIH groupTree;
};
} // namespace VMAP
#endif // _WORLDMODEL_H

View File

@@ -0,0 +1,297 @@
#ifndef _REGULAR_GRID_H
#define _REGULAR_GRID_H
#include <G3D/BoundsTrait.h>
#include <G3D/PositionTrait.h>
#include <G3D/Ray.h>
#include <G3D/Table.h>
#include "Errors.h"
template <class Node>
class NodeArray
{
public:
explicit NodeArray() { memset(&_nodes, 0, sizeof(_nodes)); }
void AddNode(Node* n)
{
for (uint8 i = 0; i < 9; ++i)
if (_nodes[i] == 0)
{
_nodes[i] = n;
return;
}
else if (_nodes[i] == n)
{
return;
}
}
Node* _nodes[9];
};
template<class Node>
struct NodeCreator
{
static Node* makeNode(int /*x*/, int /*y*/) { return new Node();}
};
template<class T,
class Node,
class NodeCreatorFunc = NodeCreator<Node>,
/*class BoundsFunc = BoundsTrait<T>,*/
class PositionFunc = PositionTrait<T>
>
class RegularGrid2D
{
public:
enum
{
CELL_NUMBER = 64,
};
#define HGRID_MAP_SIZE (533.33333f * 64.f) // shouldn't be changed
#define CELL_SIZE float(HGRID_MAP_SIZE/(float)CELL_NUMBER)
typedef G3D::Table<const T*, NodeArray<Node>> MemberTable;
MemberTable memberTable;
Node* nodes[CELL_NUMBER][CELL_NUMBER];
RegularGrid2D()
{
memset(nodes, 0, sizeof(nodes));
}
~RegularGrid2D()
{
for (int x = 0; x < CELL_NUMBER; ++x)
for (int y = 0; y < CELL_NUMBER; ++y)
{
delete nodes[x][y];
}
}
void insert(const T& value)
{
G3D::Vector3 pos[9];
pos[0] = value.GetBounds().corner(0);
pos[1] = value.GetBounds().corner(1);
pos[2] = value.GetBounds().corner(2);
pos[3] = value.GetBounds().corner(3);
pos[4] = (pos[0] + pos[1]) / 2.0f;
pos[5] = (pos[1] + pos[2]) / 2.0f;
pos[6] = (pos[2] + pos[3]) / 2.0f;
pos[7] = (pos[3] + pos[0]) / 2.0f;
pos[8] = (pos[0] + pos[2]) / 2.0f;
NodeArray<Node> na;
for (uint8 i = 0; i < 9; ++i)
{
Cell c = Cell::ComputeCell(pos[i].x, pos[i].y);
if (!c.isValid())
{
continue;
}
Node& node = getGridFor(pos[i].x, pos[i].y);
na.AddNode(&node);
}
for (uint8 i = 0; i < 9; ++i)
{
if (na._nodes[i])
{
na._nodes[i]->insert(value);
}
else
{
break;
}
}
memberTable.set(&value, na);
}
void remove(const T& value)
{
NodeArray<Node>& na = memberTable[&value];
for (uint8 i = 0; i < 9; ++i)
{
if (na._nodes[i])
{
na._nodes[i]->remove(value);
}
else
{
break;
}
}
// Remove the member
memberTable.remove(&value);
}
void balance()
{
for (int x = 0; x < CELL_NUMBER; ++x)
for (int y = 0; y < CELL_NUMBER; ++y)
if (Node* n = nodes[x][y])
{
n->balance();
}
}
bool contains(const T& value) const { return memberTable.containsKey(&value); }
int size() const { return memberTable.size(); }
struct Cell
{
int x, y;
bool operator == (const Cell& c2) const { return x == c2.x && y == c2.y;}
static Cell ComputeCell(float fx, float fy)
{
Cell c = { int(fx * (1.f / CELL_SIZE) + (CELL_NUMBER / 2)), int(fy * (1.f / CELL_SIZE) + (CELL_NUMBER / 2)) };
return c;
}
bool isValid() const { return x >= 0 && x < CELL_NUMBER && y >= 0 && y < CELL_NUMBER;}
};
Node& getGridFor(float fx, float fy)
{
Cell c = Cell::ComputeCell(fx, fy);
return getGrid(c.x, c.y);
}
Node& getGrid(int x, int y)
{
ASSERT(x < CELL_NUMBER && y < CELL_NUMBER);
if (!nodes[x][y])
{
nodes[x][y] = NodeCreatorFunc::makeNode(x, y);
}
return *nodes[x][y];
}
template<typename RayCallback>
void intersectRay(const G3D::Ray& ray, RayCallback& intersectCallback, float max_dist, bool stopAtFirstHit)
{
intersectRay(ray, intersectCallback, max_dist, ray.origin() + ray.direction() * max_dist, stopAtFirstHit);
}
template<typename RayCallback>
void intersectRay(const G3D::Ray& ray, RayCallback& intersectCallback, float& max_dist, const G3D::Vector3& end, bool stopAtFirstHit)
{
Cell cell = Cell::ComputeCell(ray.origin().x, ray.origin().y);
if (!cell.isValid())
{
return;
}
Cell last_cell = Cell::ComputeCell(end.x, end.y);
if (cell == last_cell)
{
if (Node* node = nodes[cell.x][cell.y])
{
node->intersectRay(ray, intersectCallback, max_dist, stopAtFirstHit);
}
return;
}
float voxel = (float)CELL_SIZE;
float kx_inv = ray.invDirection().x, bx = ray.origin().x;
float ky_inv = ray.invDirection().y, by = ray.origin().y;
int stepX, stepY;
float tMaxX, tMaxY;
if (kx_inv >= 0)
{
stepX = 1;
float x_border = (cell.x + 1) * voxel;
tMaxX = (x_border - bx) * kx_inv;
}
else
{
stepX = -1;
float x_border = (cell.x - 1) * voxel;
tMaxX = (x_border - bx) * kx_inv;
}
if (ky_inv >= 0)
{
stepY = 1;
float y_border = (cell.y + 1) * voxel;
tMaxY = (y_border - by) * ky_inv;
}
else
{
stepY = -1;
float y_border = (cell.y - 1) * voxel;
tMaxY = (y_border - by) * ky_inv;
}
//int Cycles = std::max((int)ceilf(max_dist/tMaxX),(int)ceilf(max_dist/tMaxY));
//int i = 0;
float tDeltaX = voxel * std::fabs(kx_inv);
float tDeltaY = voxel * std::fabs(ky_inv);
do
{
if (Node* node = nodes[cell.x][cell.y])
{
//float enterdist = max_dist;
node->intersectRay(ray, intersectCallback, max_dist, stopAtFirstHit);
}
if (cell == last_cell)
{
break;
}
if (tMaxX < tMaxY)
{
tMaxX += tDeltaX;
cell.x += stepX;
}
else
{
tMaxY += tDeltaY;
cell.y += stepY;
}
//++i;
} while (cell.isValid());
}
template<typename IsectCallback>
void intersectPoint(const G3D::Vector3& point, IsectCallback& intersectCallback)
{
Cell cell = Cell::ComputeCell(point.x, point.y);
if (!cell.isValid())
{
return;
}
if (Node* node = nodes[cell.x][cell.y])
{
node->intersectPoint(point, intersectCallback);
}
}
// Optimized verson of intersectRay function for rays with vertical directions
template<typename RayCallback>
void intersectZAllignedRay(const G3D::Ray& ray, RayCallback& intersectCallback, float& max_dist)
{
Cell cell = Cell::ComputeCell(ray.origin().x, ray.origin().y);
if (!cell.isValid())
{
return;
}
if (Node* node = nodes[cell.x][cell.y])
{
node->intersectRay(ray, intersectCallback, max_dist, false);
}
}
};
#undef CELL_SIZE
#undef HGRID_MAP_SIZE
#endif

View File

@@ -0,0 +1,33 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _VMAPDEFINITIONS_H
#define _VMAPDEFINITIONS_H
#include <cstring>
#define LIQUID_TILE_SIZE (533.333f / 128.f)
namespace VMAP
{
const char VMAP_MAGIC[] = "VMAP_4.7";
const char RAW_VMAP_MAGIC[] = "VMAP047"; // used in extracted vmap files with raw data
const char GAMEOBJECT_MODELS[] = "GameObjectModels.dtree";
// defined in TileAssembler.cpp currently...
bool readChunk(FILE* rf, char* dest, const char* compare, uint32 len);
}
#endif

View File

@@ -0,0 +1,147 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _VMAPTOOLS_H
#define _VMAPTOOLS_H
#include "Define.h"
#include <G3D/AABox.h>
#include <G3D/CollisionDetection.h>
/**
The Class is mainly taken from G3D/AABSPTree.h but modified to be able to use our internal data structure.
This is an iterator that helps us analysing the BSP-Trees.
The collision detection is modified to return true, if we are inside an object.
*/
namespace VMAP
{
template<class TValue>
class IntersectionCallBack
{
public:
TValue* closestEntity;
G3D::Vector3 hitLocation;
G3D::Vector3 hitNormal;
void operator()(const G3D::Ray& ray, const TValue* entity, bool StopAtFirstHit, float& distance)
{
entity->intersect(ray, distance, StopAtFirstHit, hitLocation, hitNormal);
}
};
//==============================================================
//==============================================================
//==============================================================
class MyCollisionDetection
{
public:
static bool collisionLocationForMovingPointFixedAABox(
const G3D::Vector3& origin,
const G3D::Vector3& dir,
const G3D::AABox& box,
G3D::Vector3& location,
bool& Inside)
{
// Integer representation of a floating-point value.
#define IR(x) (reinterpret_cast<G3D::uint32 const&>(x))
Inside = true;
const G3D::Vector3& MinB = box.low();
const G3D::Vector3& MaxB = box.high();
G3D::Vector3 MaxT(-1.0f, -1.0f, -1.0f);
// Find candidate planes.
for (int i = 0; i < 3; ++i)
{
if (origin[i] < MinB[i])
{
location[i] = MinB[i];
Inside = false;
// Calculate T distances to candidate planes
if (IR(dir[i]))
{
MaxT[i] = (MinB[i] - origin[i]) / dir[i];
}
}
else if (origin[i] > MaxB[i])
{
location[i] = MaxB[i];
Inside = false;
// Calculate T distances to candidate planes
if (IR(dir[i]))
{
MaxT[i] = (MaxB[i] - origin[i]) / dir[i];
}
}
}
if (Inside)
{
// definite hit
location = origin;
return true;
}
// Get largest of the maxT's for final choice of intersection
int WhichPlane = 0;
if (MaxT[1] > MaxT[WhichPlane])
{
WhichPlane = 1;
}
if (MaxT[2] > MaxT[WhichPlane])
{
WhichPlane = 2;
}
// Check final candidate actually inside box
if (IR(MaxT[WhichPlane]) & 0x80000000)
{
// Miss the box
return false;
}
for (int i = 0; i < 3; ++i)
{
if (i != WhichPlane)
{
location[i] = origin[i] + MaxT[WhichPlane] * dir[i];
if ((location[i] < MinB[i]) ||
(location[i] > MaxB[i]))
{
// On this plane we're outside the box extents, so
// we miss the box
return false;
}
}
}
/*
// Choose the normal to be the plane normal facing into the ray
normal = G3D::Vector3::zero();
normal[WhichPlane] = (dir[WhichPlane] > 0) ? -1.0 : 1.0;
*/
return true;
#undef IR
}
};
}
#endif

50
src/common/Common.cpp Normal file
View File

@@ -0,0 +1,50 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Common.h"
char const* localeNames[TOTAL_LOCALES] =
{
"enUS",
"koKR",
"frFR",
"deDE",
"zhCN",
"zhTW",
"esES",
"esMX",
"ruRU"
};
LocaleConstant GetLocaleByName(const std::string& name)
{
for (uint32 i = 0; i < TOTAL_LOCALES; ++i)
if (name == localeNames[i])
{
return LocaleConstant(i);
}
return LOCALE_enUS; // including enGB case
}
void CleanStringForMysqlQuery(std::string& str)
{
std::string::size_type n = 0;
while ((n = str.find('\\')) != str.npos) { str.erase(n, 1); }
while ((n = str.find('"')) != str.npos) { str.erase(n, 1); }
while ((n = str.find('\'')) != str.npos) { str.erase(n, 1); }
}

103
src/common/Common.h Normal file
View File

@@ -0,0 +1,103 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AZEROTHCORE_COMMON_H
#define AZEROTHCORE_COMMON_H
#include "Define.h"
#include <array>
#include <memory>
#include <string>
#include <utility>
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
#include <ws2tcpip.h>
#if AC_COMPILER == AC_COMPILER_INTEL
# if !defined(BOOST_ASIO_HAS_MOVE)
# define BOOST_ASIO_HAS_MOVE
# endif // !defined(BOOST_ASIO_HAS_MOVE)
# endif // if AC_COMPILER == AC_COMPILER_INTEL
#else
#include <cstdlib>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#define STRINGIZE(a) #a
#define MAX_NETCLIENT_PACKET_SIZE (32767 - 1) // Client hardcap: int16 with trailing zero space otherwise crash on memory free
// TimeConstants
constexpr auto MINUTE = 60;
constexpr auto HOUR = MINUTE * 60;
constexpr auto DAY = HOUR * 24;
constexpr auto WEEK = DAY * 7;
constexpr auto MONTH = DAY * 30;
constexpr auto YEAR = MONTH * 12;
constexpr auto IN_MILLISECONDS = 1000;
enum AccountTypes
{
SEC_PLAYER = 0,
SEC_MODERATOR = 1,
SEC_GAMEMASTER = 2,
SEC_ADMINISTRATOR = 3,
SEC_CONSOLE = 4 // must be always last in list, accounts must have less security level always also
};
enum LocaleConstant
{
LOCALE_enUS = 0,
LOCALE_koKR = 1,
LOCALE_frFR = 2,
LOCALE_deDE = 3,
LOCALE_zhCN = 4,
LOCALE_zhTW = 5,
LOCALE_esES = 6,
LOCALE_esMX = 7,
LOCALE_ruRU = 8,
TOTAL_LOCALES
};
#define DEFAULT_LOCALE LOCALE_enUS
#define MAX_LOCALES 8
#define MAX_ACCOUNT_TUTORIAL_VALUES 8
AC_COMMON_API extern char const* localeNames[TOTAL_LOCALES];
AC_COMMON_API LocaleConstant GetLocaleByName(const std::string& name);
AC_COMMON_API void CleanStringForMysqlQuery(std::string& str);
#define MAX_QUERY_LEN 32*1024
namespace Acore
{
template<class ArgumentType, class ResultType>
struct unary_function
{
typedef ArgumentType argument_type;
typedef ResultType result_type;
};
}
#endif

57
src/common/CompilerDefs.h Normal file
View File

@@ -0,0 +1,57 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ACORE_COMPILERDEFS_H
#define ACORE_COMPILERDEFS_H
#define AC_PLATFORM_WINDOWS 0
#define AC_PLATFORM_UNIX 1
#define AC_PLATFORM_APPLE 2
#define AC_PLATFORM_INTEL 3
// must be first (win 64 also define _WIN32)
#if defined( _WIN64 )
#define AC_PLATFORM AC_PLATFORM_WINDOWS
#elif defined( __WIN32__ ) || defined( WIN32 ) || defined( _WIN32 )
#define AC_PLATFORM AC_PLATFORM_WINDOWS
#elif defined( __APPLE_CC__ )
#define AC_PLATFORM AC_PLATFORM_APPLE
#elif defined( __INTEL_COMPILER )
#define AC_PLATFORM AC_PLATFORM_INTEL
#else
#define AC_PLATFORM AC_PLATFORM_UNIX
#endif
#define AC_COMPILER_MICROSOFT 0
#define AC_COMPILER_GNU 1
#define AC_COMPILER_BORLAND 2
#define AC_COMPILER_INTEL 3
#ifdef _MSC_VER
#define AC_COMPILER AC_COMPILER_MICROSOFT
#elif defined( __BORLANDC__ )
#define AC_COMPILER AC_COMPILER_BORLAND
#elif defined( __INTEL_COMPILER )
#define AC_COMPILER AC_COMPILER_INTEL
#elif defined( __GNUC__ )
#define AC_COMPILER AC_COMPILER_GNU
#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
#else
# error "FATAL ERROR: Unknown compiler."
#endif
#endif

View File

@@ -0,0 +1,52 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "BuiltInConfig.h"
#include "Config.h"
#include "GitRevision.h"
template<typename Fn>
static std::string GetStringWithDefaultValueFromFunction(
std::string const& key, Fn getter)
{
std::string const value = sConfigMgr->GetOption<std::string>(key, "");
return value.empty() ? getter() : value;
}
std::string BuiltInConfig::GetCMakeCommand()
{
return GetStringWithDefaultValueFromFunction(
"CMakeCommand", GitRevision::GetCMakeCommand);
}
std::string BuiltInConfig::GetBuildDirectory()
{
return GetStringWithDefaultValueFromFunction(
"BuildDirectory", GitRevision::GetBuildDirectory);
}
std::string BuiltInConfig::GetSourceDirectory()
{
return GetStringWithDefaultValueFromFunction(
"SourceDirectory", GitRevision::GetSourceDirectory);
}
std::string BuiltInConfig::GetMySQLExecutable()
{
return GetStringWithDefaultValueFromFunction(
"MySQLExecutable", GitRevision::GetMySQLExecutable);
}

View File

@@ -0,0 +1,46 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef BUILT_IN_CONFIG_H
#define BUILT_IN_CONFIG_H
#include "Define.h"
#include <string>
/// Provides helper functions to access built-in values
/// which can be overwritten in config
namespace BuiltInConfig
{
/// Returns the CMake command when any is specified in the config,
/// returns the built-in path otherwise
AC_COMMON_API std::string GetCMakeCommand();
/// Returns the build directory path when any is specified in the config,
/// returns the built-in one otherwise
AC_COMMON_API std::string GetBuildDirectory();
/// Returns the source directory path when any is specified in the config,
/// returns the built-in one otherwise
AC_COMMON_API std::string GetSourceDirectory();
/// Returns the path to the mysql executable (`mysql`) when any is specified
/// in the config, returns the built-in one otherwise
AC_COMMON_API std::string GetMySQLExecutable();
} // namespace BuiltInConfig
#endif // BUILT_IN_CONFIG_H

View File

@@ -0,0 +1,672 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Config.h"
#include "Log.h"
#include "StringConvert.h"
#include "StringFormat.h"
#include "Tokenize.h"
#include "Util.h"
#include <cstdlib>
#include <fstream>
#include <mutex>
#include <unordered_map>
namespace
{
std::string _filename;
std::vector<std::string> _additonalFiles;
std::vector<std::string> _args;
std::unordered_map<std::string /*name*/, std::string /*value*/> _configOptions;
std::unordered_map<std::string /*name*/, std::string /*value*/> _envVarCache;
std::mutex _configLock;
// Check system configs like *server.conf*
bool IsAppConfig(std::string_view fileName)
{
size_t foundAuth = fileName.find("authserver.conf");
size_t foundWorld = fileName.find("worldserver.conf");
size_t foundImport = fileName.find("dbimport.conf");
return foundAuth != std::string_view::npos || foundWorld != std::string_view::npos || foundImport != std::string_view::npos;
}
// Check logging system configs like Appender.* and Logger.*
bool IsLoggingSystemOptions(std::string_view optionName)
{
size_t foundAppender = optionName.find("Appender.");
size_t foundLogger = optionName.find("Logger.");
return foundAppender != std::string_view::npos || foundLogger != std::string_view::npos;
}
template<typename Format, typename... Args>
inline void PrintError(std::string_view filename, Format&& fmt, Args&& ... args)
{
std::string message = Acore::StringFormatFmt(std::forward<Format>(fmt), std::forward<Args>(args)...);
if (IsAppConfig(filename))
{
fmt::print("{}\n", message);
}
else
{
LOG_ERROR("server.loading", message);
}
}
void AddKey(std::string const& optionName, std::string const& optionKey, std::string_view fileName, bool isOptional, [[maybe_unused]] bool isReload)
{
auto const& itr = _configOptions.find(optionName);
// Check old option
if (isOptional && itr == _configOptions.end())
{
if (!IsLoggingSystemOptions(optionName) && !isReload)
{
PrintError(fileName, "> Config::LoadFile: Found incorrect option '{}' in config file '{}'. Skip", optionName, fileName);
#ifdef CONFIG_ABORT_INCORRECT_OPTIONS
ABORT("> Core can't start if found incorrect options");
#endif
return;
}
}
// Check exit option
if (itr != _configOptions.end())
{
_configOptions.erase(optionName);
}
_configOptions.emplace(optionName, optionKey);
}
bool ParseFile(std::string const& file, bool isOptional, bool isReload)
{
std::ifstream in(file);
if (in.fail())
{
if (isOptional)
{
// No display erorr if file optional
return false;
}
throw ConfigException(Acore::StringFormatFmt("Config::LoadFile: Failed open {}file '{}'", isOptional ? "optional " : "", file));
}
uint32 count = 0;
uint32 lineNumber = 0;
std::unordered_map<std::string /*name*/, std::string /*value*/> fileConfigs;
auto IsDuplicateOption = [&](std::string const& confOption)
{
auto const& itr = fileConfigs.find(confOption);
if (itr != fileConfigs.end())
{
PrintError(file, "> Config::LoadFile: Duplicate key name '{}' in config file '{}'", confOption, file);
return true;
}
return false;
};
while (in.good())
{
lineNumber++;
std::string line;
std::getline(in, line);
// read line error
if (!in.good() && !in.eof())
{
throw ConfigException(Acore::StringFormatFmt("> Config::LoadFile: Failure to read line number {} in file '{}'", lineNumber, file));
}
// remove whitespace in line
line = Acore::String::Trim(line, in.getloc());
if (line.empty())
{
continue;
}
// comments
if (line[0] == '#' || line[0] == '[')
{
continue;
}
size_t found = line.find_first_of('#');
if (found != std::string::npos)
{
line = line.substr(0, found);
}
auto const equal_pos = line.find('=');
if (equal_pos == std::string::npos || equal_pos == line.length())
{
PrintError(file, "> Config::LoadFile: Failure to read line number {} in file '{}'. Skip this line", lineNumber, file);
continue;
}
auto entry = Acore::String::Trim(line.substr(0, equal_pos), in.getloc());
auto value = Acore::String::Trim(line.substr(equal_pos + 1, std::string::npos), in.getloc());
value.erase(std::remove(value.begin(), value.end(), '"'), value.end());
// Skip if 2+ same options in one config file
if (IsDuplicateOption(entry))
{
continue;
}
// Add to temp container
fileConfigs.emplace(entry, value);
count++;
}
// No lines read
if (!count)
{
if (isOptional)
{
// No display erorr if file optional
return false;
}
throw ConfigException(Acore::StringFormatFmt("Config::LoadFile: Empty file '{}'", file));
}
// Add correct keys if file load without errors
for (auto const& [entry, key] : fileConfigs)
{
AddKey(entry, key, file, isOptional, isReload);
}
return true;
}
bool LoadFile(std::string const& file, bool isOptional, bool isReload)
{
try
{
return ParseFile(file, isOptional, isReload);
}
catch (const std::exception& e)
{
PrintError(file, "> {}", e.what());
}
return false;
}
// Converts ini keys to the environment variable key (upper snake case).
// Example of conversions:
// SomeConfig => SOME_CONFIG
// myNestedConfig.opt1 => MY_NESTED_CONFIG_OPT_1
// LogDB.Opt.ClearTime => LOG_DB_OPT_CLEAR_TIME
std::string IniKeyToEnvVarKey(std::string const& key)
{
std::string result;
const char* str = key.c_str();
size_t n = key.length();
char curr;
bool isEnd;
bool nextIsUpper;
bool currIsNumeric;
bool nextIsNumeric;
for (size_t i = 0; i < n; ++i)
{
curr = str[i];
if (curr == ' ' || curr == '.' || curr == '-')
{
result += '_';
continue;
}
isEnd = i == n - 1;
if (!isEnd)
{
nextIsUpper = isupper(str[i + 1]);
// handle "aB" to "A_B"
if (!isupper(curr) && nextIsUpper)
{
result += static_cast<char>(std::toupper(curr));
result += '_';
continue;
}
currIsNumeric = isNumeric(curr);
nextIsNumeric = isNumeric(str[i + 1]);
// handle "a1" to "a_1"
if (!currIsNumeric && nextIsNumeric)
{
result += static_cast<char>(std::toupper(curr));
result += '_';
continue;
}
// handle "1a" to "1_a"
if (currIsNumeric && !nextIsNumeric)
{
result += static_cast<char>(std::toupper(curr));
result += '_';
continue;
}
}
result += static_cast<char>(std::toupper(curr));
}
return result;
}
std::string GetEnvVarName(std::string const& configName)
{
return "AC_" + IniKeyToEnvVarKey(configName);
}
Optional<std::string> EnvVarForIniKey(std::string const& key)
{
std::string envKey = GetEnvVarName(key);
char* val = std::getenv(envKey.c_str());
if (!val)
return std::nullopt;
return std::string(val);
}
}
bool ConfigMgr::LoadInitial(std::string const& file, bool isReload /*= false*/)
{
std::lock_guard<std::mutex> lock(_configLock);
_configOptions.clear();
return LoadFile(file, false, isReload);
}
bool ConfigMgr::LoadAdditionalFile(std::string file, bool isOptional /*= false*/, bool isReload /*= false*/)
{
std::lock_guard<std::mutex> lock(_configLock);
return LoadFile(file, isOptional, isReload);
}
ConfigMgr* ConfigMgr::instance()
{
static ConfigMgr instance;
return &instance;
}
bool ConfigMgr::Reload()
{
if (!LoadAppConfigs(true))
{
return false;
}
if (!LoadModulesConfigs(true, false))
{
return false;
}
OverrideWithEnvVariablesIfAny();
return true;
}
// Check the _envVarCache if the env var is there
// if not, check the env for the value
Optional<std::string> GetEnvFromCache(std::string const& configName, std::string const& envVarName)
{
auto foundInCache = _envVarCache.find(envVarName);
Optional<std::string> foundInEnv;
// If it's not in the cache
if (foundInCache == _envVarCache.end())
{
// Check the env itself
foundInEnv = EnvVarForIniKey(configName);
if (foundInEnv)
{
// If it's found in the env, put it in the cache
_envVarCache.emplace(envVarName, *foundInEnv);
}
// Return the result of checking env
return foundInEnv;
}
return foundInCache->second;
}
std::vector<std::string> ConfigMgr::OverrideWithEnvVariablesIfAny()
{
std::lock_guard<std::mutex> lock(_configLock);
std::vector<std::string> overriddenKeys;
for (auto& itr : _configOptions)
{
if (itr.first.empty())
continue;
Optional<std::string> envVar = EnvVarForIniKey(itr.first);
if (!envVar)
continue;
itr.second = *envVar;
overriddenKeys.push_back(itr.first);
}
return overriddenKeys;
}
template<class T>
T ConfigMgr::GetValueDefault(std::string const& name, T const& def, bool showLogs /*= true*/) const
{
std::string strValue;
auto const& itr = _configOptions.find(name);
bool notFound = itr == _configOptions.end();
auto envVarName = GetEnvVarName(name);
Optional<std::string> envVar = GetEnvFromCache(name, envVarName);
if (envVar)
{
// If showLogs and this key/value pair wasn't found in the currently saved config
if (showLogs && (notFound || itr->second != envVar->c_str()))
{
LOG_INFO("server.loading", "> Config: Found config value '{}' from environment variable '{}'.", name, envVarName );
AddKey(name, envVar->c_str(), "ENVIRONMENT", false, false);
}
strValue = *envVar;
}
else if (notFound)
{
if (showLogs)
{
LOG_ERROR("server.loading", "> Config: Missing property {} in config file {}, add \"{} = {}\" to this file or define '{}' as an environment variable.",
name, _filename, name, Acore::ToString(def), envVarName);
}
return def;
}
else
{
strValue = itr->second;
}
auto value = Acore::StringTo<T>(strValue);
if (!value)
{
if (showLogs)
{
LOG_ERROR("server.loading", "> Config: Bad value defined for name '{}', going to use '{}' instead",
name, Acore::ToString(def));
}
return def;
}
return *value;
}
template<>
std::string ConfigMgr::GetValueDefault<std::string>(std::string const& name, std::string const& def, bool showLogs /*= true*/) const
{
auto const& itr = _configOptions.find(name);
bool notFound = itr == _configOptions.end();
auto envVarName = GetEnvVarName(name);
Optional<std::string> envVar = GetEnvFromCache(name, envVarName);
if (envVar)
{
// If showLogs and this key/value pair wasn't found in the currently saved config
if (showLogs && (notFound || itr->second != envVar->c_str()))
{
LOG_INFO("server.loading", "> Config: Found config value '{}' from environment variable '{}'.", name, envVarName);
AddKey(name, *envVar, "ENVIRONMENT", false, false);
}
return *envVar;
}
else if (notFound)
{
if (showLogs)
{
LOG_ERROR("server.loading", "> Config: Missing property {} in config file {}, add \"{} = {}\" to this file or define '{}' as an environment variable.",
name, _filename, name, def, envVarName);
}
return def;
}
return itr->second;
}
template<class T>
T ConfigMgr::GetOption(std::string const& name, T const& def, bool showLogs /*= true*/) const
{
return GetValueDefault<T>(name, def, showLogs);
}
template<>
bool ConfigMgr::GetOption<bool>(std::string const& name, bool const& def, bool showLogs /*= true*/) const
{
std::string val = GetValueDefault(name, std::string(def ? "1" : "0"), showLogs);
auto boolVal = Acore::StringTo<bool>(val);
if (!boolVal)
{
if (showLogs)
{
LOG_ERROR("server.loading", "> Config: Bad value defined for name '{}', going to use '{}' instead",
name, def ? "true" : "false");
}
return def;
}
return *boolVal;
}
std::vector<std::string> ConfigMgr::GetKeysByString(std::string const& name)
{
std::lock_guard<std::mutex> lock(_configLock);
std::vector<std::string> keys;
for (auto const& [optionName, key] : _configOptions)
{
if (!optionName.compare(0, name.length(), name))
{
keys.emplace_back(optionName);
}
}
return keys;
}
std::string const ConfigMgr::GetFilename()
{
std::lock_guard<std::mutex> lock(_configLock);
return _filename;
}
std::vector<std::string> const& ConfigMgr::GetArguments() const
{
return _args;
}
std::string const ConfigMgr::GetConfigPath()
{
std::lock_guard<std::mutex> lock(_configLock);
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
return "configs/";
#else
return std::string(_CONF_DIR) + "/";
#endif
}
void ConfigMgr::Configure(std::string const& initFileName, std::vector<std::string> args, std::string_view modulesConfigList /*= {}*/)
{
_filename = initFileName;
_args = std::move(args);
// Add modules config if exist
if (!modulesConfigList.empty())
{
for (auto const& itr : Acore::Tokenize(modulesConfigList, ',', false))
{
_additonalFiles.emplace_back(itr);
}
}
}
bool ConfigMgr::LoadAppConfigs(bool isReload /*= false*/)
{
// #1 - Load init config file .conf
if (!LoadInitial(_filename, isReload))
{
return false;
}
return true;
}
bool ConfigMgr::LoadModulesConfigs(bool isReload /*= false*/, bool isNeedPrintInfo /*= true*/)
{
if (_additonalFiles.empty())
{
// Send successful load if no found files
return true;
}
if (isNeedPrintInfo)
{
LOG_INFO("server.loading", " ");
LOG_INFO("server.loading", "Loading Modules Configuration...");
}
// Start loading module configs
std::string const& moduleConfigPath = GetConfigPath() + "modules/";
bool isExistDefaultConfig = true;
bool isExistDistConfig = true;
for (auto const& distFileName : _additonalFiles)
{
std::string defaultFileName = distFileName;
if (!defaultFileName.empty())
{
defaultFileName.erase(defaultFileName.end() - 5, defaultFileName.end());
}
// Load .conf.dist config
isExistDistConfig = LoadAdditionalFile(moduleConfigPath + distFileName, false, isReload);
if (!isReload && !isExistDistConfig)
{
LOG_FATAL("server.loading", "> ConfigMgr::LoadModulesConfigs: Not found original config '{}'. Stop loading", distFileName);
ABORT();
}
// Load .conf config
isExistDefaultConfig = LoadAdditionalFile(moduleConfigPath + defaultFileName, true, isReload);
if (isExistDefaultConfig && isExistDistConfig)
{
_moduleConfigFiles.emplace_back(defaultFileName);
}
else if (!isExistDefaultConfig && isExistDistConfig)
{
_moduleConfigFiles.emplace_back(distFileName);
}
}
if (isNeedPrintInfo)
{
if (!_moduleConfigFiles.empty())
{
// Print modules configurations
LOG_INFO("server.loading", " ");
LOG_INFO("server.loading", "Using modules configuration:");
for (auto const& itr : _moduleConfigFiles)
{
LOG_INFO("server.loading", "> {}", itr);
}
}
else
{
LOG_INFO("server.loading", "> Not found modules config files");
}
}
if (isNeedPrintInfo)
{
LOG_INFO("server.loading", " ");
}
return true;
}
/// @deprecated DO NOT USE - use GetOption<std::string> instead.
std::string ConfigMgr::GetStringDefault(std::string const& name, const std::string& def, bool showLogs /*= true*/)
{
return GetOption<std::string>(name, def, showLogs);
}
/// @deprecated DO NOT USE - use GetOption<bool> instead.
bool ConfigMgr::GetBoolDefault(std::string const& name, bool def, bool showLogs /*= true*/)
{
return GetOption<bool>(name, def, showLogs);
}
/// @deprecated DO NOT USE - use GetOption<int32> instead.
int ConfigMgr::GetIntDefault(std::string const& name, int def, bool showLogs /*= true*/)
{
return GetOption<int32>(name, def, showLogs);
}
/// @deprecated DO NOT USE - use GetOption<float> instead.
float ConfigMgr::GetFloatDefault(std::string const& name, float def, bool showLogs /*= true*/)
{
return GetOption<float>(name, def, showLogs);
}
#define TEMPLATE_CONFIG_OPTION(__typename) \
template __typename ConfigMgr::GetOption<__typename>(std::string const& name, __typename const& def, bool showLogs /*= true*/) const;
TEMPLATE_CONFIG_OPTION(std::string)
TEMPLATE_CONFIG_OPTION(uint8)
TEMPLATE_CONFIG_OPTION(int8)
TEMPLATE_CONFIG_OPTION(uint16)
TEMPLATE_CONFIG_OPTION(int16)
TEMPLATE_CONFIG_OPTION(uint32)
TEMPLATE_CONFIG_OPTION(int32)
TEMPLATE_CONFIG_OPTION(uint64)
TEMPLATE_CONFIG_OPTION(int64)
TEMPLATE_CONFIG_OPTION(float)
#undef TEMPLATE_CONFIG_OPTION

View File

@@ -0,0 +1,97 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CONFIG_H
#define CONFIG_H
#include "Define.h"
#include <stdexcept>
#include <string_view>
#include <vector>
class ConfigMgr
{
ConfigMgr() = default;
ConfigMgr(ConfigMgr const&) = delete;
ConfigMgr& operator=(ConfigMgr const&) = delete;
~ConfigMgr() = default;
public:
bool LoadAppConfigs(bool isReload = false);
bool LoadModulesConfigs(bool isReload = false, bool isNeedPrintInfo = true);
void Configure(std::string const& initFileName, std::vector<std::string> args, std::string_view modulesConfigList = {});
static ConfigMgr* instance();
bool Reload();
/// Overrides configuration with environment variables and returns overridden keys
std::vector<std::string> OverrideWithEnvVariablesIfAny();
std::string const GetFilename();
std::string const GetConfigPath();
[[nodiscard]] std::vector<std::string> const& GetArguments() const;
std::vector<std::string> GetKeysByString(std::string const& name);
template<class T>
T GetOption(std::string const& name, T const& def, bool showLogs = true) const;
/*
* Deprecated geters. This geters will be deleted
*/
[[deprecated("Use GetOption<std::string> instead")]]
std::string GetStringDefault(std::string const& name, const std::string& def, bool showLogs = true);
[[deprecated("Use GetOption<bool> instead")]]
bool GetBoolDefault(std::string const& name, bool def, bool showLogs = true);
[[deprecated("Use GetOption<int32> instead")]]
int GetIntDefault(std::string const& name, int def, bool showLogs = true);
[[deprecated("Use GetOption<float> instead")]]
float GetFloatDefault(std::string const& name, float def, bool showLogs = true);
/*
* End deprecated geters
*/
bool isDryRun() { return dryRun; }
void setDryRun(bool mode) { dryRun = mode; }
private:
/// Method used only for loading main configuration files (authserver.conf and worldserver.conf)
bool LoadInitial(std::string const& file, bool isReload = false);
bool LoadAdditionalFile(std::string file, bool isOptional = false, bool isReload = false);
template<class T>
T GetValueDefault(std::string const& name, T const& def, bool showLogs = true) const;
bool dryRun = false;
std::vector<std::string /*config variant*/> _moduleConfigFiles;
};
class ConfigException : public std::length_error
{
public:
explicit ConfigException(std::string const& message) : std::length_error(message) { }
};
#define sConfigMgr ConfigMgr::instance()
#endif

View File

@@ -0,0 +1,65 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AES.h"
#include "Errors.h"
#include <limits>
Acore::Crypto::AES::AES(bool encrypting) : _ctx(EVP_CIPHER_CTX_new()), _encrypting(encrypting)
{
EVP_CIPHER_CTX_init(_ctx);
int status = EVP_CipherInit_ex(_ctx, EVP_aes_128_gcm(), nullptr, nullptr, nullptr, _encrypting ? 1 : 0);
ASSERT(status);
}
Acore::Crypto::AES::~AES()
{
EVP_CIPHER_CTX_free(_ctx);
}
void Acore::Crypto::AES::Init(Key const& key)
{
int status = EVP_CipherInit_ex(_ctx, nullptr, nullptr, key.data(), nullptr, -1);
ASSERT(status);
}
bool Acore::Crypto::AES::Process(IV const& iv, uint8* data, size_t length, Tag& tag)
{
ASSERT(length <= static_cast<size_t>(std::numeric_limits<int>::max()));
int len = static_cast<int>(length);
if (!EVP_CipherInit_ex(_ctx, nullptr, nullptr, nullptr, iv.data(), -1))
return false;
int outLen;
if (!EVP_CipherUpdate(_ctx, data, &outLen, data, len))
return false;
len -= outLen;
if (!_encrypting && !EVP_CIPHER_CTX_ctrl(_ctx, EVP_CTRL_GCM_SET_TAG, sizeof(tag), tag))
return false;
if (!EVP_CipherFinal_ex(_ctx, data + outLen, &outLen))
return false;
ASSERT(len == outLen);
if (_encrypting && !EVP_CIPHER_CTX_ctrl(_ctx, EVP_CTRL_GCM_GET_TAG, sizeof(tag), tag))
return false;
return true;
}

View File

@@ -0,0 +1,51 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef Azeroth_AES_h__
#define Azeroth_AES_h__
#include "Define.h"
#include <array>
#include <openssl/evp.h>
namespace Acore::Crypto
{
class AC_COMMON_API AES
{
public:
static constexpr size_t IV_SIZE_BYTES = 12;
static constexpr size_t KEY_SIZE_BYTES = 16;
static constexpr size_t TAG_SIZE_BYTES = 12;
using IV = std::array<uint8, IV_SIZE_BYTES>;
using Key = std::array<uint8, KEY_SIZE_BYTES>;
using Tag = uint8[TAG_SIZE_BYTES];
explicit AES(bool encrypting);
~AES();
void Init(Key const& key);
bool Process(IV const& iv, uint8* data, size_t length, Tag& tag);
private:
EVP_CIPHER_CTX* _ctx;
bool _encrypting;
};
}
#endif // Azeroth_AES_h__

View File

@@ -0,0 +1,58 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ARC4.h"
#include "Errors.h"
Acore::Crypto::ARC4::ARC4() : _ctx(EVP_CIPHER_CTX_new())
{
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
_cipher = EVP_CIPHER_fetch(nullptr, "RC4", nullptr);
#else
EVP_CIPHER const* _cipher = EVP_rc4();
#endif
EVP_CIPHER_CTX_init(_ctx);
int result = EVP_EncryptInit_ex(_ctx, _cipher, nullptr, nullptr, nullptr);
ASSERT(result == 1);
}
Acore::Crypto::ARC4::~ARC4()
{
EVP_CIPHER_CTX_free(_ctx);
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
EVP_CIPHER_free(_cipher);
#endif
}
void Acore::Crypto::ARC4::Init(uint8 const* seed, size_t len)
{
int result1 = EVP_CIPHER_CTX_set_key_length(_ctx, len);
ASSERT(result1 == 1);
int result2 = EVP_EncryptInit_ex(_ctx, nullptr, nullptr, seed, nullptr);
ASSERT(result2 == 1);
}
void Acore::Crypto::ARC4::UpdateData(uint8* data, size_t len)
{
int outlen = 0;
int result1 = EVP_EncryptUpdate(_ctx, data, &outlen, data, len);
ASSERT(result1 == 1);
int result2 = EVP_EncryptFinal_ex(_ctx, data, &outlen);
ASSERT(result2 == 1);
}

View File

@@ -0,0 +1,50 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _AUTH_SARC4_H
#define _AUTH_SARC4_H
#include "Define.h"
#include <array>
#include <openssl/evp.h>
namespace Acore::Crypto
{
class AC_COMMON_API ARC4
{
public:
ARC4();
~ARC4();
void Init(uint8 const* seed, size_t len);
template <typename Container>
void Init(Container const& c) { Init(std::data(c), std::size(c)); }
void UpdateData(uint8* data, size_t len);
template <typename Container>
void UpdateData(Container& c) { UpdateData(std::data(c), std::size(c)); }
private:
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
EVP_CIPHER* _cipher;
#endif
EVP_CIPHER_CTX* _ctx;
};
}
#endif

View File

@@ -0,0 +1,44 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Argon2.h"
#include <argon2/argon2.h>
/*static*/ Optional<std::string> Acore::Crypto::Argon2::Hash(std::string const& password, BigNumber const& salt, uint32 nIterations, uint32 kibMemoryCost)
{
char buf[ENCODED_HASH_LEN];
std::vector<uint8> saltBytes = salt.ToByteVector();
int status = argon2id_hash_encoded(
nIterations,
kibMemoryCost,
PARALLELISM,
password.c_str(), password.length(),
saltBytes.data(), saltBytes.size(),
HASH_LEN, buf, ENCODED_HASH_LEN
);
if (status == ARGON2_OK)
return std::string(buf);
return {};
}
/*static*/ bool Acore::Crypto::Argon2::Verify(std::string const& password, std::string const& hash)
{
int status = argon2id_verify(hash.c_str(), password.c_str(), password.length());
return (status == ARGON2_OK);
}

View File

@@ -0,0 +1,39 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AC_ARGON2_H
#define AC_ARGON2_H
#include "BigNumber.h"
#include "Optional.h"
namespace Acore::Crypto
{
struct AC_COMMON_API Argon2
{
static constexpr uint32 HASH_LEN = 16; // 128 bits, in bytes
static constexpr uint32 ENCODED_HASH_LEN = 100; // in chars
static constexpr uint32 DEFAULT_ITERATIONS = 10; // determined by dice roll, guaranteed to be secure (not really)
static constexpr uint32 DEFAULT_MEMORY_COST = (1u << 17); // 2^17 kibibytes is 2^7 mebibytes is ~100MB
static constexpr uint32 PARALLELISM = 1; // we don't support threaded hashing
static Optional<std::string> Hash(std::string const& password, BigNumber const& salt, uint32 nIterations = DEFAULT_ITERATIONS, uint32 kibMemoryCost = DEFAULT_MEMORY_COST);
static bool Verify(std::string const& password, std::string const& hash);
};
}
#endif

View File

@@ -0,0 +1,48 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AuthCrypt.h"
#include "Errors.h"
#include "HMAC.h"
void AuthCrypt::Init(SessionKey const& K)
{
uint8 ServerEncryptionKey[] = { 0xCC, 0x98, 0xAE, 0x04, 0xE8, 0x97, 0xEA, 0xCA, 0x12, 0xDD, 0xC0, 0x93, 0x42, 0x91, 0x53, 0x57 };
_serverEncrypt.Init(Acore::Crypto::HMAC_SHA1::GetDigestOf(ServerEncryptionKey, K));
uint8 ServerDecryptionKey[] = { 0xC2, 0xB3, 0x72, 0x3C, 0xC6, 0xAE, 0xD9, 0xB5, 0x34, 0x3C, 0x53, 0xEE, 0x2F, 0x43, 0x67, 0xCE };
_clientDecrypt.Init(Acore::Crypto::HMAC_SHA1::GetDigestOf(ServerDecryptionKey, K));
// Drop first 1024 bytes, as WoW uses ARC4-drop1024.
std::array<uint8, 1024> syncBuf{};
_serverEncrypt.UpdateData(syncBuf);
_clientDecrypt.UpdateData(syncBuf);
_initialized = true;
}
void AuthCrypt::DecryptRecv(uint8* data, size_t len)
{
ASSERT(_initialized);
_clientDecrypt.UpdateData(data, len);
}
void AuthCrypt::EncryptSend(uint8* data, size_t len)
{
ASSERT(_initialized);
_serverEncrypt.UpdateData(data, len);
}

View File

@@ -0,0 +1,40 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _AUTHCRYPT_H
#define _AUTHCRYPT_H
#include "ARC4.h"
#include "AuthDefines.h"
class AC_COMMON_API AuthCrypt
{
public:
AuthCrypt() = default;
void Init(SessionKey const& K);
void DecryptRecv(uint8* data, size_t len);
void EncryptSend(uint8* data, size_t len);
bool IsInitialized() const { return _initialized; }
private:
Acore::Crypto::ARC4 _clientDecrypt;
Acore::Crypto::ARC4 _serverEncrypt;
bool _initialized{ false };
};
#endif

View File

@@ -0,0 +1,27 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AZEROTHCORE_AUTHDEFINES_H
#define AZEROTHCORE_AUTHDEFINES_H
#include "Define.h"
#include <array>
constexpr size_t SESSION_KEY_LENGTH = 40;
using SessionKey = std::array<uint8, SESSION_KEY_LENGTH>;
#endif

View File

@@ -0,0 +1,112 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SRP6.h"
#include "CryptoRandom.h"
#include "Util.h"
#include <functional>
using SHA1 = Acore::Crypto::SHA1;
using SRP6 = Acore::Crypto::SRP6;
/*static*/ std::array<uint8, 1> const SRP6::g = { 7 };
/*static*/ std::array<uint8, 32> const SRP6::N = HexStrToByteArray<32>("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", true);
/*static*/ BigNumber const SRP6::_g(SRP6::g);
/*static*/ BigNumber const SRP6::_N(N);
/*static*/ std::pair<SRP6::Salt, SRP6::Verifier> SRP6::MakeRegistrationData(std::string const& username, std::string const& password)
{
std::pair<SRP6::Salt, SRP6::Verifier> res;
Crypto::GetRandomBytes(res.first); // random salt
res.second = CalculateVerifier(username, password, res.first);
return res;
}
/*static*/ SRP6::Verifier SRP6::CalculateVerifier(std::string const& username, std::string const& password, SRP6::Salt const& salt)
{
// v = g ^ H(s || H(u || ':' || p)) mod N
return _g.ModExp(
SHA1::GetDigestOf(
salt,
SHA1::GetDigestOf(username, ":", password)
)
,_N).ToByteArray<32>();
}
/*static*/ SessionKey SRP6::SHA1Interleave(SRP6::EphemeralKey const& S)
{
// split S into two buffers
std::array<uint8, EPHEMERAL_KEY_LENGTH / 2> buf0{}, buf1{};
for (size_t i = 0; i < EPHEMERAL_KEY_LENGTH / 2; ++i)
{
buf0[i] = S[2 * i + 0];
buf1[i] = S[2 * i + 1];
}
// find position of first nonzero byte
size_t p = 0;
while (p < EPHEMERAL_KEY_LENGTH && !S[p])
++p;
if (p & 1)
++p; // skip one extra byte if p is odd
p /= 2; // offset into buffers
// hash each of the halves, starting at the first nonzero byte
SHA1::Digest const hash0 = SHA1::GetDigestOf(buf0.data() + p, EPHEMERAL_KEY_LENGTH / 2 - p);
SHA1::Digest const hash1 = SHA1::GetDigestOf(buf1.data() + p, EPHEMERAL_KEY_LENGTH / 2 - p);
// stick the two hashes back together
SessionKey K;
for (size_t i = 0; i < SHA1::DIGEST_LENGTH; ++i)
{
K[2 * i + 0] = hash0[i];
K[2 * i + 1] = hash1[i];
}
return K;
}
SRP6::SRP6(std::string const& username, Salt const& salt, Verifier const& verifier)
: _I(SHA1::GetDigestOf(username)), _b(Crypto::GetRandomBytes<32>()), _v(verifier), s(salt), B(_B(_b, _v)) {}
std::optional<SessionKey> SRP6::VerifyChallengeResponse(EphemeralKey const& A, SHA1::Digest const& clientM)
{
ASSERT(!_used, "A single SRP6 object must only ever be used to verify ONCE!");
_used = true;
BigNumber const _A(A);
if ((_A % _N).IsZero())
return std::nullopt;
BigNumber const u(SHA1::GetDigestOf(A, B));
EphemeralKey const S = (_A * (_v.ModExp(u, _N))).ModExp(_b, N).ToByteArray<32>();
SessionKey K = SHA1Interleave(S);
// NgHash = H(N) xor H(g)
SHA1::Digest const NHash = SHA1::GetDigestOf(N);
SHA1::Digest const gHash = SHA1::GetDigestOf(g);
SHA1::Digest NgHash;
std::transform(NHash.begin(), NHash.end(), gHash.begin(), NgHash.begin(), std::bit_xor<>());
SHA1::Digest const ourM = SHA1::GetDigestOf(NgHash, _I, s, A, B, K);
if (ourM == clientM)
return K;
return std::nullopt;
}

View File

@@ -0,0 +1,83 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AZEROTHCORE_SRP6_H
#define AZEROTHCORE_SRP6_H
#include "AuthDefines.h"
#include "BigNumber.h"
#include "CryptoHash.h"
#include <optional>
namespace Acore::Crypto
{
class AC_COMMON_API SRP6
{
public:
static constexpr size_t SALT_LENGTH = 32;
using Salt = std::array<uint8, SALT_LENGTH>;
static constexpr size_t VERIFIER_LENGTH = 32;
using Verifier = std::array<uint8, VERIFIER_LENGTH>;
static constexpr size_t EPHEMERAL_KEY_LENGTH = 32;
using EphemeralKey = std::array<uint8, EPHEMERAL_KEY_LENGTH>;
static std::array<uint8, 1> const g;
static std::array<uint8, 32> const N;
// username + password must be passed through Utf8ToUpperOnlyLatin FIRST!
static std::pair<Salt, Verifier> MakeRegistrationData(std::string const& username, std::string const& password);
// username + password must be passed through Utf8ToUpperOnlyLatin FIRST!
static bool CheckLogin(std::string const& username, std::string const& password, Salt const& salt, Verifier const& verifier)
{
return (verifier == CalculateVerifier(username, password, salt));
}
static SHA1::Digest GetSessionVerifier(EphemeralKey const& A, SHA1::Digest const& clientM, SessionKey const& K)
{
return SHA1::GetDigestOf(A, clientM, K);
}
SRP6(std::string const& username, Salt const& salt, Verifier const& verifier);
std::optional<SessionKey> VerifyChallengeResponse(EphemeralKey const& A, SHA1::Digest const& clientM);
private:
bool _used = false; // a single instance can only be used to verify once
static Verifier CalculateVerifier(std::string const& username, std::string const& password, Salt const& salt);
static SessionKey SHA1Interleave(EphemeralKey const& S);
/* global algorithm parameters */
static BigNumber const _g; // a [g]enerator for the ring of integers mod N, algorithm parameter
static BigNumber const _N; // the modulus, an algorithm parameter; all operations are mod this
static EphemeralKey _B(BigNumber const& b, BigNumber const& v) { return ((_g.ModExp(b, _N) + (v * 3)) % N).ToByteArray<EPHEMERAL_KEY_LENGTH>(); }
/* per-instantiation parameters, set on construction */
SHA1::Digest const _I; // H(I) - the username, all uppercase
BigNumber const _b; // b - randomly chosen by the server, 19 bytes, never given out
BigNumber const _v; // v - the user's password verifier, derived from s + H(USERNAME || ":" || PASSWORD)
public:
Salt const s; // s - the user's password salt, random, used to calculate v on registration
EphemeralKey const B; // B = 3v + g^b
};
}
#endif

View File

@@ -0,0 +1,246 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "BigNumber.h"
#include "Errors.h"
#include <algorithm>
#include <cstring>
#include <memory>
#include <openssl/bn.h>
BigNumber::BigNumber()
: _bn(BN_new())
{ }
BigNumber::BigNumber(BigNumber const& bn)
: _bn(BN_dup(bn.BN()))
{ }
BigNumber::~BigNumber()
{
BN_free(_bn);
}
void BigNumber::SetDword(int32 val)
{
SetDword(uint32(std::abs(val)));
if (val < 0)
BN_set_negative(_bn, 1);
}
void BigNumber::SetDword(uint32 val)
{
BN_set_word(_bn, val);
}
void BigNumber::SetQword(uint64 val)
{
BN_set_word(_bn, (uint32)(val >> 32));
BN_lshift(_bn, _bn, 32);
BN_add_word(_bn, (uint32)(val & 0xFFFFFFFF));
}
void BigNumber::SetBinary(uint8 const* bytes, int32 len, bool littleEndian)
{
if (littleEndian)
{
#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L
uint8* array = new uint8[len];
for (int i = 0; i < len; i++)
array[i] = bytes[len - 1 - i];
BN_bin2bn(array, len, _bn);
delete[] array;
#else
BN_lebin2bn(bytes, len, _bn);
#endif
}
else
BN_bin2bn(bytes, len, _bn);
}
bool BigNumber::SetHexStr(char const* str)
{
int n = BN_hex2bn(&_bn, str);
return (n > 0);
}
void BigNumber::SetRand(int32 numbits)
{
BN_rand(_bn, numbits, 0, 1);
}
BigNumber& BigNumber::operator=(BigNumber const& bn)
{
if (this == &bn)
return *this;
BN_copy(_bn, bn._bn);
return *this;
}
BigNumber& BigNumber::operator+=(BigNumber const& bn)
{
BN_add(_bn, _bn, bn._bn);
return *this;
}
BigNumber& BigNumber::operator-=(BigNumber const& bn)
{
BN_sub(_bn, _bn, bn._bn);
return *this;
}
BigNumber& BigNumber::operator*=(BigNumber const& bn)
{
BN_CTX *bnctx;
bnctx = BN_CTX_new();
BN_mul(_bn, _bn, bn._bn, bnctx);
BN_CTX_free(bnctx);
return *this;
}
BigNumber& BigNumber::operator/=(BigNumber const& bn)
{
BN_CTX *bnctx;
bnctx = BN_CTX_new();
BN_div(_bn, nullptr, _bn, bn._bn, bnctx);
BN_CTX_free(bnctx);
return *this;
}
BigNumber& BigNumber::operator%=(BigNumber const& bn)
{
BN_CTX *bnctx;
bnctx = BN_CTX_new();
BN_mod(_bn, _bn, bn._bn, bnctx);
BN_CTX_free(bnctx);
return *this;
}
BigNumber& BigNumber::operator<<=(int n)
{
BN_lshift(_bn, _bn, n);
return *this;
}
int BigNumber::CompareTo(BigNumber const& bn) const
{
return BN_cmp(_bn, bn._bn);
}
BigNumber BigNumber::Exp(BigNumber const& bn) const
{
BigNumber ret;
BN_CTX *bnctx;
bnctx = BN_CTX_new();
BN_exp(ret._bn, _bn, bn._bn, bnctx);
BN_CTX_free(bnctx);
return ret;
}
BigNumber BigNumber::ModExp(BigNumber const& bn1, BigNumber const& bn2) const
{
BigNumber ret;
BN_CTX *bnctx;
bnctx = BN_CTX_new();
BN_mod_exp(ret._bn, _bn, bn1._bn, bn2._bn, bnctx);
BN_CTX_free(bnctx);
return ret;
}
int32 BigNumber::GetNumBytes() const
{
return BN_num_bytes(_bn);
}
uint32 BigNumber::AsDword() const
{
return (uint32)BN_get_word(_bn);
}
bool BigNumber::IsZero() const
{
return BN_is_zero(_bn);
}
bool BigNumber::IsNegative() const
{
return BN_is_negative(_bn);
}
void BigNumber::GetBytes(uint8* buf, size_t bufsize, bool littleEndian) const
{
#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L
int nBytes = GetNumBytes();
ASSERT(nBytes >= 0, "Bignum has negative number of bytes ({}).", nBytes);
std::size_t numBytes = static_cast<std::size_t>(nBytes);
// too large to store
ASSERT(numBytes <= bufsize, "Buffer of size {} is too small to hold bignum with {} bytes.\n", bufsize, numBytes);
// If we need more bytes than length of BigNumber set the rest to 0
if (numBytes < bufsize)
memset((void*)buf, 0, bufsize);
BN_bn2bin(_bn, buf + (bufsize - numBytes));
// openssl's BN stores data internally in big endian format, reverse if little endian desired
if (littleEndian)
std::reverse(buf, buf + bufsize);
#else
int res = littleEndian ? BN_bn2lebinpad(_bn, buf, bufsize) : BN_bn2binpad(_bn, buf, bufsize);
ASSERT(res > 0, "Buffer of size {} is too small to hold bignum with {} bytes.\n", bufsize, BN_num_bytes(_bn));
#endif
}
std::vector<uint8> BigNumber::ToByteVector(int32 minSize, bool littleEndian) const
{
std::size_t length = std::max(GetNumBytes(), minSize);
std::vector<uint8> v;
v.resize(length);
GetBytes(v.data(), length, littleEndian);
return v;
}
std::string BigNumber::AsHexStr() const
{
char* ch = BN_bn2hex(_bn);
std::string ret = ch;
OPENSSL_free(ch);
return ret;
}
std::string BigNumber::AsDecStr() const
{
char* ch = BN_bn2dec(_bn);
std::string ret = ch;
OPENSSL_free(ch);
return ret;
}

View File

@@ -0,0 +1,137 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _AUTH_BIGNUMBER_H
#define _AUTH_BIGNUMBER_H
#include "Define.h"
#include <array>
#include <string>
#include <vector>
struct bignum_st;
class AC_COMMON_API BigNumber
{
public:
BigNumber();
BigNumber(BigNumber const& bn);
BigNumber(uint32 v) : BigNumber() { SetDword(v); }
BigNumber(int32 v) : BigNumber() { SetDword(v); }
BigNumber(std::string const& v) : BigNumber() { SetHexStr(v); }
template <size_t Size>
BigNumber(std::array<uint8, Size> const& v, bool littleEndian = true) : BigNumber() { SetBinary(v.data(), Size, littleEndian); }
~BigNumber();
void SetDword(int32);
void SetDword(uint32);
void SetQword(uint64);
void SetBinary(uint8 const* bytes, int32 len, bool littleEndian = true);
template <typename Container>
auto SetBinary(Container const& c, bool littleEndian = true) -> std::enable_if_t<!std::is_pointer_v<std::decay_t<Container>>> { SetBinary(std::data(c), std::size(c), littleEndian); }
bool SetHexStr(char const* str);
bool SetHexStr(std::string const& str) { return SetHexStr(str.c_str()); }
void SetRand(int32 numbits);
BigNumber& operator=(BigNumber const& bn);
BigNumber& operator+=(BigNumber const& bn);
BigNumber operator+(BigNumber const& bn) const
{
BigNumber t(*this);
return t += bn;
}
BigNumber& operator-=(BigNumber const& bn);
BigNumber operator-(BigNumber const& bn) const
{
BigNumber t(*this);
return t -= bn;
}
BigNumber& operator*=(BigNumber const& bn);
BigNumber operator*(BigNumber const& bn) const
{
BigNumber t(*this);
return t *= bn;
}
BigNumber& operator/=(BigNumber const& bn);
BigNumber operator/(BigNumber const& bn) const
{
BigNumber t(*this);
return t /= bn;
}
BigNumber& operator%=(BigNumber const& bn);
BigNumber operator%(BigNumber const& bn) const
{
BigNumber t(*this);
return t %= bn;
}
BigNumber& operator<<=(int n);
BigNumber operator<<(int n) const
{
BigNumber t(*this);
return t <<= n;
}
[[nodiscard]] int CompareTo(BigNumber const& bn) const;
bool operator<=(BigNumber const& bn) const { return (CompareTo(bn) <= 0); }
bool operator==(BigNumber const& bn) const { return (CompareTo(bn) == 0); }
bool operator>=(BigNumber const& bn) const { return (CompareTo(bn) >= 0); }
bool operator<(BigNumber const& bn) const { return (CompareTo(bn) < 0); }
bool operator>(BigNumber const& bn) const { return (CompareTo(bn) > 0); }
[[nodiscard]] bool IsZero() const;
[[nodiscard]] bool IsNegative() const;
[[nodiscard]] BigNumber ModExp(BigNumber const& bn1, BigNumber const& bn2) const;
[[nodiscard]] BigNumber Exp(BigNumber const&) const;
[[nodiscard]] int32 GetNumBytes() const;
struct bignum_st* BN() { return _bn; }
[[nodiscard]] struct bignum_st const* BN() const { return _bn; }
[[nodiscard]] uint32 AsDword() const;
void GetBytes(uint8* buf, size_t bufsize, bool littleEndian = true) const;
[[nodiscard]] std::vector<uint8> ToByteVector(int32 minSize = 0, bool littleEndian = true) const;
template <std::size_t Size>
std::array<uint8, Size> ToByteArray(bool littleEndian = true) const
{
std::array<uint8, Size> buf;
GetBytes(buf.data(), Size, littleEndian);
return buf;
}
[[nodiscard]] std::string AsHexStr() const;
[[nodiscard]] std::string AsDecStr() const;
private:
struct bignum_st* _bn;
};
#endif

View File

@@ -0,0 +1,33 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AZEROTHCORE_CRYPTO_CONSTANTS_H
#define AZEROTHCORE_CRYPTO_CONSTANTS_H
#include "Define.h"
namespace Acore::Crypto
{
struct Constants
{
static constexpr size_t MD5_DIGEST_LENGTH_BYTES = 16;
static constexpr size_t SHA1_DIGEST_LENGTH_BYTES = 20;
static constexpr size_t SHA256_DIGEST_LENGTH_BYTES = 32;
};
}
#endif

View File

@@ -0,0 +1,112 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AZEROTHCORE_CRYPTO_GENERICS_HPP
#define AZEROTHCORE_CRYPTO_GENERICS_HPP
#include "BigNumber.h"
#include "CryptoRandom.h"
#include "Define.h"
#include "Errors.h"
#include <iterator>
#include <vector>
namespace Acore::Impl
{
struct CryptoGenericsImpl
{
template <typename Cipher>
static typename Cipher::IV GenerateRandomIV()
{
typename Cipher::IV iv;
Acore::Crypto::GetRandomBytes(iv);
return iv;
}
template <typename Container>
static void AppendToBack(std::vector<uint8>& data, Container const& tail)
{
data.insert(data.end(), std::begin(tail), std::end(tail));
}
template <typename Container>
static void SplitFromBack(std::vector<uint8>& data, Container& tail)
{
ASSERT(data.size() >= std::size(tail));
for (size_t i = 1, N = std::size(tail); i <= N; ++i)
{
tail[N - i] = data.back();
data.pop_back();
}
}
};
}
namespace Acore::Crypto
{
template <typename Cipher>
void AEEncryptWithRandomIV(std::vector<uint8>& data, typename Cipher::Key const& key)
{
using IV = typename Cipher::IV;
using Tag = typename Cipher::Tag;
// select random IV
IV iv = Acore::Impl::CryptoGenericsImpl::GenerateRandomIV<Cipher>();
Tag tag;
// encrypt data
Cipher cipher(true);
cipher.Init(key);
bool success = cipher.Process(iv, data.data(), data.size(), tag);
ASSERT(success);
// append trailing IV and tag
Acore::Impl::CryptoGenericsImpl::AppendToBack(data, iv);
Acore::Impl::CryptoGenericsImpl::AppendToBack(data, tag);
}
template <typename Cipher>
void AEEncryptWithRandomIV(std::vector<uint8>& data, BigNumber const& key)
{
AEEncryptWithRandomIV<Cipher>(data, key.ToByteArray<Cipher::KEY_SIZE_BYTES>());
}
template <typename Cipher>
bool AEDecrypt(std::vector<uint8>& data, typename Cipher::Key const& key)
{
using IV = typename Cipher::IV;
using Tag = typename Cipher::Tag;
// extract trailing IV and tag
IV iv;
Tag tag;
Acore::Impl::CryptoGenericsImpl::SplitFromBack(data, tag);
Acore::Impl::CryptoGenericsImpl::SplitFromBack(data, iv);
// decrypt data
Cipher cipher(false);
cipher.Init(key);
return cipher.Process(iv, data.data(), data.size(), tag);
}
template <typename Cipher>
bool AEDecrypt(std::vector<uint8>& data, BigNumber const& key)
{
return AEDecrypt<Cipher>(data, key.ToByteArray<Cipher::KEY_SIZE_BYTES>());
}
}
#endif

View File

@@ -0,0 +1,151 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AZEROTHCORE_CRYPTOHASH_H
#define AZEROTHCORE_CRYPTOHASH_H
#include "CryptoConstants.h"
#include "Errors.h"
#include <array>
#include <openssl/evp.h>
#include <string>
#include <string_view>
#include <utility>
class BigNumber;
namespace Acore::Impl
{
struct GenericHashImpl
{
typedef EVP_MD const* (*HashCreator)();
#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L
static EVP_MD_CTX* MakeCTX() noexcept { return EVP_MD_CTX_create(); }
static void DestroyCTX(EVP_MD_CTX* ctx) { EVP_MD_CTX_destroy(ctx); }
#else
static EVP_MD_CTX* MakeCTX() noexcept { return EVP_MD_CTX_new(); }
static void DestroyCTX(EVP_MD_CTX* ctx) { EVP_MD_CTX_free(ctx); }
#endif
};
template <GenericHashImpl::HashCreator HashCreator, size_t DigestLength>
class GenericHash
{
public:
static constexpr size_t DIGEST_LENGTH = DigestLength;
using Digest = std::array<uint8, DIGEST_LENGTH>;
static Digest GetDigestOf(uint8 const* data, size_t len)
{
GenericHash hash;
hash.UpdateData(data, len);
hash.Finalize();
return hash.GetDigest();
}
template <typename... Ts>
static auto GetDigestOf(Ts&&... pack) -> std::enable_if_t<!(std::is_integral_v<std::decay_t<Ts>> || ...), Digest>
{
GenericHash hash;
(hash.UpdateData(std::forward<Ts>(pack)), ...);
hash.Finalize();
return hash.GetDigest();
}
GenericHash() : _ctx(GenericHashImpl::MakeCTX())
{
int result = EVP_DigestInit_ex(_ctx, HashCreator(), nullptr);
ASSERT(result == 1);
}
GenericHash(GenericHash const& right) : _ctx(GenericHashImpl::MakeCTX())
{
*this = right;
}
GenericHash(GenericHash&& right) noexcept
{
*this = std::move(right);
}
~GenericHash()
{
if (!_ctx)
return;
GenericHashImpl::DestroyCTX(_ctx);
_ctx = nullptr;
}
GenericHash& operator=(GenericHash const& right)
{
if (this == &right)
return *this;
int result = EVP_MD_CTX_copy_ex(_ctx, right._ctx);
ASSERT(result == 1);
_digest = right._digest;
return *this;
}
GenericHash& operator=(GenericHash&& right) noexcept
{
if (this == &right)
return *this;
_ctx = std::exchange(right._ctx, GenericHashImpl::MakeCTX());
_digest = std::exchange(right._digest, Digest{});
return *this;
}
void UpdateData(uint8 const* data, size_t len)
{
int result = EVP_DigestUpdate(_ctx, data, len);
ASSERT(result == 1);
}
void UpdateData(std::string_view str) { UpdateData(reinterpret_cast<uint8 const*>(str.data()), str.size()); }
void UpdateData(std::string const& str) { UpdateData(std::string_view(str)); } /* explicit overload to avoid using the container template */
void UpdateData(char const* str) { UpdateData(std::string_view(str)); } /* explicit overload to avoid using the container template */
template <typename Container>
void UpdateData(Container const& c) { UpdateData(std::data(c), std::size(c)); }
void Finalize()
{
uint32 length;
int result = EVP_DigestFinal_ex(_ctx, _digest.data(), &length);
ASSERT(result == 1);
ASSERT(length == DIGEST_LENGTH);
}
Digest const& GetDigest() const { return _digest; }
private:
EVP_MD_CTX* _ctx{};
Digest _digest{};
};
}
namespace Acore::Crypto
{
using MD5 = Acore::Impl::GenericHash<EVP_md5, Constants::MD5_DIGEST_LENGTH_BYTES>;
using SHA1 = Acore::Impl::GenericHash<EVP_sha1, Constants::SHA1_DIGEST_LENGTH_BYTES>;
using SHA256 = Acore::Impl::GenericHash<EVP_sha256, Constants::SHA256_DIGEST_LENGTH_BYTES>;
}
#endif

View File

@@ -0,0 +1,26 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "CryptoRandom.h"
#include "Errors.h"
#include <openssl/rand.h>
void Acore::Crypto::GetRandomBytes(uint8* buf, size_t len)
{
int result = RAND_bytes(buf, len);
ASSERT(result == 1, "Not enough randomness in OpenSSL's entropy pool. What in the world are you running on?");
}

View File

@@ -0,0 +1,43 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AZEROTHCORE_CRYPTORANDOM_H
#define AZEROTHCORE_CRYPTORANDOM_H
#include "Define.h"
#include <array>
namespace Acore::Crypto
{
AC_COMMON_API void GetRandomBytes(uint8* buf, size_t len);
template <typename Container>
void GetRandomBytes(Container& c)
{
GetRandomBytes(std::data(c), std::size(c));
}
template <size_t S>
std::array<uint8, S> GetRandomBytes()
{
std::array<uint8, S> arr;
GetRandomBytes(arr);
return arr;
}
}
#endif

View File

@@ -0,0 +1,143 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AZEROTHCORE_HMAC_H
#define AZEROTHCORE_HMAC_H
#include "CryptoConstants.h"
#include "CryptoHash.h"
#include "Errors.h"
#include <array>
#include <string>
#include <string_view>
class BigNumber;
namespace Acore::Impl
{
template <GenericHashImpl::HashCreator HashCreator, size_t DigestLength>
class GenericHMAC
{
public:
static constexpr size_t DIGEST_LENGTH = DigestLength;
using Digest = std::array<uint8, DIGEST_LENGTH>;
template <typename Container>
static Digest GetDigestOf(Container const& seed, uint8 const* data, size_t len)
{
GenericHMAC hash(seed);
hash.UpdateData(data, len);
hash.Finalize();
return hash.GetDigest();
}
template <typename Container, typename... Ts>
static auto GetDigestOf(Container const& seed, Ts&&... pack) -> std::enable_if_t<!(std::is_integral_v<std::decay_t<Ts>> || ...), Digest>
{
GenericHMAC hash(seed);
(hash.UpdateData(std::forward<Ts>(pack)), ...);
hash.Finalize();
return hash.GetDigest();
}
GenericHMAC(uint8 const* seed, size_t len) : _ctx(GenericHashImpl::MakeCTX()), _key(EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, nullptr, seed, len))
{
int result = EVP_DigestSignInit(_ctx, nullptr, HashCreator(), nullptr, _key);
ASSERT(result == 1);
}
template <typename Container>
GenericHMAC(Container const& container) : GenericHMAC(std::data(container), std::size(container)) {}
GenericHMAC(GenericHMAC const& right) : _ctx(GenericHashImpl::MakeCTX())
{
*this = right;
}
GenericHMAC(GenericHMAC&& right) noexcept
{
*this = std::move(right);
}
~GenericHMAC()
{
GenericHashImpl::DestroyCTX(_ctx);
_ctx = nullptr;
EVP_PKEY_free(_key);
_key = nullptr;
}
GenericHMAC& operator=(GenericHMAC const& right)
{
if (this == &right)
return *this;
int result = EVP_MD_CTX_copy_ex(_ctx, right._ctx);
ASSERT(result == 1);
_key = right._key; // EVP_PKEY uses reference counting internally, just copy the pointer
EVP_PKEY_up_ref(_key); // Bump reference count for PKEY, as every instance of this class holds two references to PKEY and destructor decrements it twice
_digest = right._digest;
return *this;
}
GenericHMAC& operator=(GenericHMAC&& right) noexcept
{
if (this == &right)
return *this;
_ctx = std::exchange(right._ctx, GenericHashImpl::MakeCTX());
_key = std::exchange(right._key, EVP_PKEY_new());
_digest = std::exchange(right._digest, Digest{});
return *this;
}
void UpdateData(uint8 const* data, size_t len)
{
int result = EVP_DigestSignUpdate(_ctx, data, len);
ASSERT(result == 1);
}
void UpdateData(std::string_view str) { UpdateData(reinterpret_cast<uint8 const*>(str.data()), str.size()); }
void UpdateData(std::string const& str) { UpdateData(std::string_view(str)); } /* explicit overload to avoid using the container template */
void UpdateData(char const* str) { UpdateData(std::string_view(str)); } /* explicit overload to avoid using the container template */
template <typename Container>
void UpdateData(Container const& c) { UpdateData(std::data(c), std::size(c)); }
void Finalize()
{
size_t length = DIGEST_LENGTH;
int result = EVP_DigestSignFinal(_ctx, _digest.data(), &length);
ASSERT(result == 1);
ASSERT(length == DIGEST_LENGTH);
}
Digest const& GetDigest() const { return _digest; }
private:
EVP_MD_CTX* _ctx{};
EVP_PKEY* _key{};
Digest _digest{};
};
}
namespace Acore::Crypto
{
using HMAC_SHA1 = Acore::Impl::GenericHMAC<EVP_sha1, Constants::SHA1_DIGEST_LENGTH_BYTES>;
using HMAC_SHA256 = Acore::Impl::GenericHMAC<EVP_sha256, Constants::SHA256_DIGEST_LENGTH_BYTES>;
}
#endif

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