diff --git a/.github/README.md b/.github/README.md index 3a9e654d6..995688155 100644 --- a/.github/README.md +++ b/.github/README.md @@ -82,7 +82,8 @@ You can check the [authors](https://github.com/azerothcore/azerothcore-wotlk/blo ## License -- The AzerothCore source code is released under the [GNU GPL v2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +- The new AzerothCore source components are released under the [GNU AGPL v3](https://www.gnu.org/licenses/agpl-3.0.en.html) +- The old sources based on MaNGOS/TrinityCore are released under the [GNU GPL v2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) It's important to note that AzerothCore is not an official Blizzard Entertainment product, and it is not affiliated with or endorsed by World of Warcraft or Blizzard Entertainment. AzerothCore does not in any case sponsor nor support illegal public servers. If you use this project to run an illegal public server and not for testing and learning it is your own personal choice. diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml index 643593a0c..4fc3f97ef 100644 --- a/.github/workflows/codestyle.yml +++ b/.github/workflows/codestyle.yml @@ -14,7 +14,7 @@ jobs: triage: runs-on: ubuntu-latest name: C++ - if: github.repository == 'azerothcore/azerothcore-wotlk' && !github.event.pull_request.draft + if: github.repository == 'mod-playerbots/azerothcore-wotlk' && !github.event.pull_request.draft steps: - uses: actions/checkout@v4 - name: Setup python diff --git a/.github/workflows/core-build-nopch.yml b/.github/workflows/core-build-nopch.yml index 56f0a6a2d..98eb1f207 100644 --- a/.github/workflows/core-build-nopch.yml +++ b/.github/workflows/core-build-nopch.yml @@ -44,7 +44,7 @@ jobs: CXX: g++-14 runs-on: ${{ matrix.os }} name: ${{ matrix.os }}-${{ matrix.compiler.CC }}-nopch - if: github.repository == 'azerothcore/azerothcore-wotlk' && !github.event.pull_request.draft + if: github.repository == 'mod-playerbots/azerothcore-wotlk' && !github.event.pull_request.draft steps: - uses: actions/checkout@v4 - uses: ./.github/actions/linux-build diff --git a/.github/workflows/core-build-pch.yml b/.github/workflows/core-build-pch.yml index 3ac8cc6fd..ad07eaf28 100644 --- a/.github/workflows/core-build-pch.yml +++ b/.github/workflows/core-build-pch.yml @@ -39,8 +39,10 @@ jobs: CC: clang-18 CXX: clang++-18 runs-on: ${{ matrix.os }} - name: ${{ matrix.os }}-${{ matrix.compiler.CC }}-pch - if: github.repository == 'azerothcore/azerothcore-wotlk' && !github.event.pull_request.draft + name: ${{ matrix.os }}-${{ matrix.compiler }}-pch + env: + COMPILER: ${{ matrix.compiler }} + if: github.repository == 'mod-playerbots/azerothcore-wotlk' && !github.event.pull_request.draft steps: - uses: actions/checkout@v4 - uses: ./.github/actions/linux-build diff --git a/.github/workflows/core-build-playerbots.yml b/.github/workflows/core-build-playerbots.yml new file mode 100644 index 000000000..0d7bf98f7 --- /dev/null +++ b/.github/workflows/core-build-playerbots.yml @@ -0,0 +1,100 @@ +# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml +name: ubuntu-build + +on: + push: + branches: [ "Playerbot" ] + pull_request: + branches: [ "Playerbot" ] + +jobs: + build: + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + matrix: + # the result of the matrix will be the combination of all attributes, so we get os*compiler builds + include: + - os: ubuntu-22.04 + c_compiler: clang + cpp_compiler: clang++ + build_type: Release + - os: ubuntu-22.04 + c_compiler: gcc + cpp_compiler: g++ + build_type: Release + - os: ubuntu-24.04 + c_compiler: gcc + cpp_compiler: g++ + build_type: Release + + runs-on: ${{ matrix.os }} + name: ${{ matrix.os }}-${{ matrix.cpp_compiler }} + + steps: + - name: Checkout AzerothCore + uses: actions/checkout@v3 + + - name: Set reusable strings + # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + # - name: Clone Playerbot Module + # run: git clone --depth=1 --branch=master https://github.com/mod-playerbots/mod-playerbots.git modules/mod-playerbots + + - name: Checkout Playerbot Module + uses: actions/checkout@v3 + with: + repository: 'mod-playerbots/mod-playerbots' + #ref: 'feature/core_update_10_2025' #used on core merge conflicts builds + path: 'modules/mod-playerbots' + + - name: Install Requirements + run: sudo apt-get update && sudo apt-get install git cmake make gcc g++ clang libmysqlclient-dev libssl-dev libbz2-dev libreadline-dev libncurses-dev mysql-server libboost-all-dev + + # - name: Cache + # uses: actions/cache@v3 + # with: + # path: var/ccache + # key: ccache:${{ matrix.os }}:${{ matrix.compiler }}:${{ matrix.modules }}-modules:${{ github.ref }}:${{ github.sha }} + # restore-keys: | + # ccache:${{ matrix.os }}:${{ matrix.compiler }}:${{ matrix.modules }}-modules:${{ github.ref }} + # ccache:${{ matrix.os }}:${{ matrix.compiler }}:${{ matrix.modules }}-modules + + # - name: Configure OS + # run: source ./acore.sh install-deps + # env: + # CONTINUOUS_INTEGRATION: true + + # - name: Create conf/config.sh + # run: source ./apps/ci/ci-conf-core.sh + + # - name: Process pending sql + # run: bash bin/acore-db-pendings + + # - name: Build + # run: source ./apps/ci/ci-compile.sh + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -S ${{ github.workspace }} + + - name: Build + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} + + # - name: Test + # working-directory: ${{ steps.strings.outputs.build-output-dir }} + # # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + # run: ctest --build-config ${{ matrix.build_type }} diff --git a/.github/workflows/core-build.yml b/.github/workflows/core-build.yml new file mode 100644 index 000000000..7f1c06572 --- /dev/null +++ b/.github/workflows/core-build.yml @@ -0,0 +1,99 @@ +# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml +name: ubuntu-build + +on: + push: + branches: [ "Playerbot" ] + pull_request: + branches: [ "Playerbot" ] + +jobs: + build: + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + matrix: + # the result of the matrix will be the combination of all attributes, so we get os*compiler builds + include: + - os: ubuntu-22.04 + c_compiler: clang + cpp_compiler: clang++ + build_type: Release + - os: ubuntu-22.04 + c_compiler: gcc + cpp_compiler: g++ + build_type: Release + - os: ubuntu-24.04 + c_compiler: gcc + cpp_compiler: g++ + build_type: Release + + runs-on: ${{ matrix.os }} + name: ${{ matrix.os }}-${{ matrix.cpp_compiler }} + + steps: + - name: Checkout AzerothCore + uses: actions/checkout@v3 + + - name: Set reusable strings + # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + # - name: Clone Playerbot Module + # run: git clone --depth=1 --branch=master https://github.com/mod-playerbots/mod-playerbots.git modules/mod-playerbots + + # - name: Checkout Playerbot Module + # uses: actions/checkout@v3 + # with: + # repository: 'mod-playerbots/mod-playerbots' + # path: 'modules/mod-playerbots' + + - name: Install Requirements + run: sudo apt-get update && sudo apt-get install git cmake make gcc g++ clang libmysqlclient-dev libssl-dev libbz2-dev libreadline-dev libncurses-dev mysql-server libboost-all-dev + + # - name: Cache + # uses: actions/cache@v3 + # with: + # path: var/ccache + # key: ccache:${{ matrix.os }}:${{ matrix.compiler }}:${{ matrix.modules }}-modules:${{ github.ref }}:${{ github.sha }} + # restore-keys: | + # ccache:${{ matrix.os }}:${{ matrix.compiler }}:${{ matrix.modules }}-modules:${{ github.ref }} + # ccache:${{ matrix.os }}:${{ matrix.compiler }}:${{ matrix.modules }}-modules + + # - name: Configure OS + # run: source ./acore.sh install-deps + # env: + # CONTINUOUS_INTEGRATION: true + + # - name: Create conf/config.sh + # run: source ./apps/ci/ci-conf-core.sh + + # - name: Process pending sql + # run: bash bin/acore-db-pendings + + # - name: Build + # run: source ./apps/ci/ci-compile.sh + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -S ${{ github.workspace }} + + - name: Build + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} + + # - name: Test + # working-directory: ${{ steps.strings.outputs.build-output-dir }} + # # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + # run: ctest --build-config ${{ matrix.build_type }} diff --git a/.github/workflows/core_modules_build.yml b/.github/workflows/core_modules_build.yml index 322c06385..3d1943344 100644 --- a/.github/workflows/core_modules_build.yml +++ b/.github/workflows/core_modules_build.yml @@ -46,7 +46,7 @@ jobs: CXX: clang++-18 runs-on: ${{ matrix.os }} name: ${{ matrix.os }}-${{ matrix.compiler.CC }}-nopch-modules - if: github.repository == 'azerothcore/azerothcore-wotlk' && !github.event.pull_request.draft + if: github.repository == 'mod-playerbots/azerothcore-wotlk' && !github.event.pull_request.draft steps: - uses: actions/checkout@v4 # This script installs a general list of modules to compile with diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml index e770e0eae..d6d2f2905 100644 --- a/.github/workflows/docker_build.yml +++ b/.github/workflows/docker_build.yml @@ -23,13 +23,13 @@ env: COMPOSE_DOCKER_CLI_BUILD: 1 DOCKER_BUILDKIT: 1 RUNNING_ON_PRIMARY_BRANCH: | - ${{ (github.repository == 'azerothcore/azerothcore-wotlk' && github.ref_name == 'master') && 'true' || 'false' }} + ${{ (github.repository == 'mod-playerbots/azerothcore-wotlk' && github.ref_name == 'master') && 'true' || 'false' }} jobs: build-containers: runs-on: "ubuntu-latest" if: | - github.repository == 'azerothcore/azerothcore-wotlk' + github.repository == 'mod-playerbots/azerothcore-wotlk' && !github.event.pull_request.draft && (github.ref_name == 'master' || contains(github.event.pull_request.labels.*.name, 'run-build') || github.event.label.name == 'run-build') steps: diff --git a/.github/workflows/macos_build.yml b/.github/workflows/macos_build.yml index 09ae976a2..03974bca6 100644 --- a/.github/workflows/macos_build.yml +++ b/.github/workflows/macos_build.yml @@ -1,12 +1,9 @@ name: macos-build on: push: - branches: - - 'master' + branches: [ "Playerbot" ] pull_request: - types: - - labeled - - synchronize + branches: [ "Playerbot" ] concurrency: group: ${{ github.head_ref }} || concat(${{ github.ref_name }}, ${{ github.workflow }}) @@ -25,10 +22,6 @@ jobs: - macos-14 runs-on: ${{ matrix.os }} name: ${{ matrix.os }} - if: | - github.repository == 'azerothcore/azerothcore-wotlk' - && !github.event.pull_request.draft - && (github.ref == 'refs/heads/master' || contains(github.event.pull_request.labels.*.name, 'run-build') || github.event.label.name == 'run-build') steps: - uses: actions/checkout@v4 - name: Cache diff --git a/.github/workflows/tools_build.yml b/.github/workflows/tools_build.yml index dbd8eba50..d414f233d 100644 --- a/.github/workflows/tools_build.yml +++ b/.github/workflows/tools_build.yml @@ -32,9 +32,11 @@ jobs: runs-on: ${{ matrix.os }} name: ${{ matrix.os }}-${{ matrix.compiler.CC }} if: | - github.repository == 'azerothcore/azerothcore-wotlk' - && !github.event.pull_request.draft - && (github.ref == 'refs/heads/master' || contains(github.event.pull_request.labels.*.name, 'run-build') || github.event.label.name == 'run-build') + github.repository == 'mod-playerbots/azerothcore-wotlk' && !github.event.pull_request.draft + && ( + contains(github.event.pull_request.labels.*.name, 'run-build') + || github.event.label.name == 'run-build' + ) steps: - uses: actions/checkout@v4 - uses: ./.github/actions/linux-build diff --git a/.github/workflows/windows_build.yml b/.github/workflows/windows_build.yml index c23c87be1..d5d095511 100644 --- a/.github/workflows/windows_build.yml +++ b/.github/workflows/windows_build.yml @@ -1,12 +1,9 @@ name: windows-build on: push: - branches: - - 'master' + branches: [ "Playerbot" ] pull_request: - types: - - labeled - - synchronize + branches: [ "Playerbot" ] concurrency: # One concurrency group per workflow + ref. @@ -29,10 +26,6 @@ jobs: name: ${{ matrix.os }} env: BOOST_ROOT: C:\local\boost_1_82_0 - if: | - github.repository == 'azerothcore/azerothcore-wotlk' - && !github.event.pull_request.draft - && (github.ref == 'refs/heads/master' || contains(github.event.pull_request.labels.*.name, 'run-build') || github.event.label.name == 'run-build') steps: - uses: actions/checkout@v4 - name: ccache diff --git a/.gitignore b/.gitignore index 548213a63..a25db2997 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,8 @@ CMakeLists.txt.user # /.settings/ /.externalToolBuilders/* +/.vs +/out # exclude in all levels nbproject/ .sync.ffs_db @@ -102,3 +104,5 @@ local.properties # !modules/yourmodule # # ================== +.cache +compile_commands.json \ No newline at end of file diff --git a/apps/ci/mac/ci-compile.sh b/apps/ci/mac/ci-compile.sh index 79507bc9f..10dfbc4b6 100755 --- a/apps/ci/mac/ci-compile.sh +++ b/apps/ci/mac/ci-compile.sh @@ -22,8 +22,7 @@ if [ ! -d "$mysql_include_path" ]; then fi time cmake ../../../ \ --DTOOLS=1 \ --DBUILD_TESTING=1 \ +-DTOOLS_BUILD=all \ -DSCRIPTS=static \ -DCMAKE_BUILD_TYPE=Release \ -DMYSQL_ADD_INCLUDE_PATH=$mysql_include_path \ @@ -33,9 +32,6 @@ time cmake ../../../ \ -DOPENSSL_INCLUDE_DIR="$OPENSSL_ROOT_DIR/include" \ -DOPENSSL_SSL_LIBRARIES="$OPENSSL_ROOT_DIR/lib/libssl.dylib" \ -DOPENSSL_CRYPTO_LIBRARIES="$OPENSSL_ROOT_DIR/lib/libcrypto.dylib" \ --DWITH_WARNINGS=1 \ --DCMAKE_C_FLAGS="-Werror" \ --DCMAKE_CXX_FLAGS="-Werror" \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DUSE_SCRIPTPCH=0 \ diff --git a/deps/boost/CMakeLists.txt b/deps/boost/CMakeLists.txt index c41ce041a..783356283 100644 --- a/deps/boost/CMakeLists.txt +++ b/deps/boost/CMakeLists.txt @@ -32,7 +32,7 @@ else() endif() # Boost.System is header-only since 1.69; do not require it explicitly. -find_package(Boost ${BOOST_REQUIRED_VERSION} REQUIRED COMPONENTS filesystem program_options iostreams regex) +find_package(Boost ${BOOST_REQUIRED_VERSION} REQUIRED COMPONENTS filesystem program_options iostreams regex thread) if(NOT Boost_FOUND) if(NOT DEFINED ENV{Boost_ROOT} AND NOT DEFINED Boost_DIR AND NOT DEFINED BOOST_ROOT AND NOT DEFINED BOOSTROOT) diff --git a/doc/changelog/master.md b/doc/changelog/master.md index 59b9f7bb9..3cd001b8d 100644 --- a/doc/changelog/master.md +++ b/doc/changelog/master.md @@ -414,7 +414,7 @@ minimal-dynamic - builds commands and spells dynamically. Now don't support - Example loader script for modules: ```cpp /* - * Copyright (C) 2016+ AzerothCore + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE */ // From SC diff --git a/docker-compose.yml b/docker-compose.yml index 4c6525df0..f14f6dac8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -75,6 +75,7 @@ services: AC_LOGIN_DATABASE_INFO: "ac-database;3306;root;${DOCKER_DB_ROOT_PASSWORD:-password};acore_auth" AC_WORLD_DATABASE_INFO: "ac-database;3306;root;${DOCKER_DB_ROOT_PASSWORD:-password};acore_world" AC_CHARACTER_DATABASE_INFO: "ac-database;3306;root;${DOCKER_DB_ROOT_PASSWORD:-password};acore_characters" + AC_PLAYERBOTS_DATABASE_INFO: "ac-database;3306;root;${DOCKER_DB_ROOT_PASSWORD:-password};acore_playerbots" ports: - ${DOCKER_WORLD_EXTERNAL_PORT:-8085}:8085 - ${DOCKER_SOAP_EXTERNAL_PORT:-7878}:7878 diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index fd36c5068..a4b8c14c6 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -37,6 +37,7 @@ set("AC_MODULE_LIST" "") set("AC_SCRIPTS_LIST" "") set(MOD_ALE_FOUND 0) set(MOD_ALE_PATH "") +set(MOD_PLAYERBOTS_FOUND 0) foreach(include ${AC_ADD_SCRIPTS_INCLUDE}) set("AC_SCRIPTS_INCLUDES" "#include \"${include}\"\n${AC_SCRIPTS_INCLUDES}") @@ -81,6 +82,16 @@ foreach(SOURCE_MODULE ${MODULES_MODULE_LIST}) ConfigureALEModule(${SOURCE_MODULE}) endif() + if (SOURCE_MODULE MATCHES "mod-playerbots") + set(MOD_PLAYERBOTS_FOUND 1) + target_compile_options(database + PRIVATE + -DMOD_PLAYERBOTS) + target_compile_options(game-interface + INTERFACE + -DMOD_PLAYERBOTS) + endif() + # Build the Graph values if(${MODULE_MODULE_VARIABLE} MATCHES "dynamic") GetProjectNameOfModuleName(${SOURCE_MODULE} MODULE_SOURCE_PROJECT_NAME) @@ -289,6 +300,7 @@ endif() target_link_libraries(modules PRIVATE acore-core-interface + mysql PUBLIC game-interface) @@ -363,6 +375,12 @@ target_compile_options(modules INTERFACE -DCONFIG_FILE_LIST=$<1:"${CONFIG_LIST}">) +if (MOD_PLAYERBOTS_FOUND) + target_compile_options(modules + PRIVATE + -DMOD_PLAYERBOTS) +endif() + if (MOD_ALE_FOUND) if (APPLE) target_compile_definitions(modules diff --git a/src/server/apps/worldserver/Main.cpp b/src/server/apps/worldserver/Main.cpp index 511c17e36..6a08e9689 100644 --- a/src/server/apps/worldserver/Main.cpp +++ b/src/server/apps/worldserver/Main.cpp @@ -434,6 +434,11 @@ bool StartDB() if (!loader.Load()) return false; + if (!sScriptMgr->OnDatabasesLoading()) + { + return false; + } + ///- Get the realm Id from the configuration file realm.Id.Realm = sConfigMgr->GetOption("RealmID", 1); if (!realm.Id.Realm) @@ -479,6 +484,8 @@ void StopDB() WorldDatabase.Close(); LoginDatabase.Close(); + sScriptMgr->OnDatabasesClosing(); + MySQL::Library_End(); } @@ -569,6 +576,8 @@ void WorldUpdateLoop() CharacterDatabase.WarnAboutSyncQueries(true); WorldDatabase.WarnAboutSyncQueries(true); + sScriptMgr->OnDatabaseWarnAboutSyncQueries(true); + ///- While we have not World::m_stopEvent, update the world while (!World::IsStopped()) { @@ -598,6 +607,8 @@ void WorldUpdateLoop() #endif } + sScriptMgr->OnDatabaseWarnAboutSyncQueries(false); + LoginDatabase.WarnAboutSyncQueries(false); CharacterDatabase.WarnAboutSyncQueries(false); WorldDatabase.WarnAboutSyncQueries(false); diff --git a/src/server/apps/worldserver/worldserver.conf.dist b/src/server/apps/worldserver/worldserver.conf.dist index c6305069d..59bf10b06 100644 --- a/src/server/apps/worldserver/worldserver.conf.dist +++ b/src/server/apps/worldserver/worldserver.conf.dist @@ -678,6 +678,7 @@ Allow.IP.Based.Action.Logging = 0 Appender.Console=1,4,0,"1 9 3 6 5 8" Appender.Server=2,5,0,Server.log,w +Appender.Playerbots=2,5,0,Playerbots.log,w # Appender.GM=2,5,15,gm_%s.log Appender.Errors=2,2,0,Errors.log,w # Appender.DB=3,5,0 @@ -714,6 +715,7 @@ Logger.sql=4,Console Server Logger.time.update=4,Console Server Logger.module=4,Console Server Logger.spells.scripts=2,Console Errors +Logger.playerbots=5,Console Playerbots #Logger.achievement=4,Console Server #Logger.addon=4,Console Server #Logger.ahbot=4,Console Server diff --git a/src/server/database/Database/DatabaseEnv.cpp b/src/server/database/Database/DatabaseEnv.cpp index 561e80f4b..56f904d2e 100644 --- a/src/server/database/Database/DatabaseEnv.cpp +++ b/src/server/database/Database/DatabaseEnv.cpp @@ -20,3 +20,7 @@ DatabaseWorkerPool WorldDatabase; DatabaseWorkerPool CharacterDatabase; DatabaseWorkerPool LoginDatabase; + +#ifdef MOD_PLAYERBOTS +DatabaseWorkerPool PlayerbotsDatabase; +#endif diff --git a/src/server/database/Database/DatabaseEnv.h b/src/server/database/Database/DatabaseEnv.h index 3e0cd2e86..7d2b5da77 100644 --- a/src/server/database/Database/DatabaseEnv.h +++ b/src/server/database/Database/DatabaseEnv.h @@ -25,6 +25,10 @@ #include "Implementation/LoginDatabase.h" #include "Implementation/WorldDatabase.h" +#ifdef MOD_PLAYERBOTS +#include "Implementation/PlayerbotsDatabase.h" +#endif + #include "PreparedStatement.h" #include "QueryCallback.h" #include "Transaction.h" @@ -36,4 +40,9 @@ AC_DATABASE_API extern DatabaseWorkerPool Character /// Accessor to the realm/login database AC_DATABASE_API extern DatabaseWorkerPool LoginDatabase; +#ifdef MOD_PLAYERBOTS +/// Accessor to the playerbots database +AC_DATABASE_API extern DatabaseWorkerPool PlayerbotsDatabase; +#endif + #endif diff --git a/src/server/database/Database/DatabaseEnvFwd.h b/src/server/database/Database/DatabaseEnvFwd.h index b8e5a8882..d6bbaa65e 100644 --- a/src/server/database/Database/DatabaseEnvFwd.h +++ b/src/server/database/Database/DatabaseEnvFwd.h @@ -32,6 +32,10 @@ class CharacterDatabaseConnection; class LoginDatabaseConnection; class WorldDatabaseConnection; +#ifdef MOD_PLAYERBOTS +class PlayerbotsDatabaseConnection; +#endif + class PreparedStatementBase; template @@ -41,6 +45,10 @@ using CharacterDatabasePreparedStatement = PreparedStatement; using WorldDatabasePreparedStatement = PreparedStatement; +#ifdef MOD_PLAYERBOTS +using PlayerbotsDatabasePreparedStatement = PreparedStatement; +#endif + class PreparedResultSet; using PreparedQueryResult = std::shared_ptr; using PreparedQueryResultFuture = std::future; @@ -70,6 +78,10 @@ using CharacterDatabaseTransaction = SQLTransaction using LoginDatabaseTransaction = SQLTransaction; using WorldDatabaseTransaction = SQLTransaction; +#ifdef MOD_PLAYERBOTS +using PlayerbotsDatabaseTransaction = SQLTransaction; +#endif + class SQLQueryHolderBase; using QueryResultHolderFuture = std::future; using QueryResultHolderPromise = std::promise; @@ -81,6 +93,10 @@ using CharacterDatabaseQueryHolder = SQLQueryHolder using LoginDatabaseQueryHolder = SQLQueryHolder; using WorldDatabaseQueryHolder = SQLQueryHolder; +#ifdef MOD_PLAYERBOTS +using PlayerbotsDatabaseQueryHolder = SQLQueryHolder; +#endif + class SQLQueryHolderCallback; // mysql diff --git a/src/server/database/Database/DatabaseLoader.cpp b/src/server/database/Database/DatabaseLoader.cpp index c39aa41b9..0328dd49d 100644 --- a/src/server/database/Database/DatabaseLoader.cpp +++ b/src/server/database/Database/DatabaseLoader.cpp @@ -238,3 +238,8 @@ template AC_DATABASE_API DatabaseLoader& DatabaseLoader::AddDatabase(DatabaseWorkerPool&, std::string const&); template AC_DATABASE_API DatabaseLoader& DatabaseLoader::AddDatabase(DatabaseWorkerPool&, std::string const&); + +#ifdef MOD_PLAYERBOTS +template AC_DATABASE_API +DatabaseLoader& DatabaseLoader::AddDatabase(DatabaseWorkerPool&, std::string const&); +#endif diff --git a/src/server/database/Database/DatabaseLoader.h b/src/server/database/Database/DatabaseLoader.h index 0f18315e6..0ef3e82cb 100644 --- a/src/server/database/Database/DatabaseLoader.h +++ b/src/server/database/Database/DatabaseLoader.h @@ -48,8 +48,12 @@ public: DATABASE_LOGIN = 1, DATABASE_CHARACTER = 2, DATABASE_WORLD = 4, - +#ifdef MOD_PLAYERBOTS + DATABASE_PLAYERBOTS = 8, + DATABASE_MASK_ALL = DATABASE_LOGIN | DATABASE_CHARACTER | DATABASE_WORLD | DATABASE_PLAYERBOTS +#else DATABASE_MASK_ALL = DATABASE_LOGIN | DATABASE_CHARACTER | DATABASE_WORLD +#endif }; [[nodiscard]] uint32 GetUpdateFlags() const @@ -57,6 +61,11 @@ public: return _updateFlags; } + void SetUpdateFlags(uint32 newUpdateFlags) + { + _updateFlags |= newUpdateFlags; + } + private: bool OpenDatabases(); bool PopulateDatabases(); @@ -73,7 +82,7 @@ private: std::string const _logger; std::string_view _modulesList; bool const _autoSetup; - uint32 const _updateFlags; + uint32 _updateFlags; std::queue _open, _populate, _update, _prepare; std::stack _close; diff --git a/src/server/database/Database/DatabaseWorkerPool.cpp b/src/server/database/Database/DatabaseWorkerPool.cpp index ee19a5a48..f539e2a6a 100644 --- a/src/server/database/Database/DatabaseWorkerPool.cpp +++ b/src/server/database/Database/DatabaseWorkerPool.cpp @@ -41,6 +41,10 @@ #include #endif +#ifdef MOD_PLAYERBOTS +#include "Implementation/PlayerbotsDatabase.h" +#endif + class PingOperation : public SQLOperation { //! Operation for idle delaythreads @@ -571,3 +575,7 @@ void DatabaseWorkerPool::ExecuteOrAppend(SQLTransaction& trans, PreparedSt template class AC_DATABASE_API DatabaseWorkerPool; template class AC_DATABASE_API DatabaseWorkerPool; template class AC_DATABASE_API DatabaseWorkerPool; + +#ifdef MOD_PLAYERBOTS +template class AC_DATABASE_API DatabaseWorkerPool; +#endif diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 1b8650b49..25e9b635a 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -356,6 +356,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_UPD_PETITION_NAME, "UPDATE petition SET name = ? WHERE petition_id = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_PETITION_SIGNATURE, "INSERT INTO petition_sign (ownerguid, petition_id, playerguid, player_account) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_ACCOUNT_ONLINE, "UPDATE characters SET online = 0 WHERE account = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_CHAR_OFFLINE, "UPDATE characters SET online = 0 WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_GROUP, "INSERT INTO `groups` (guid, leaderGuid, lootMethod, looterGuid, lootThreshold, icon1, icon2, icon3, icon4, icon5, icon6, icon7, icon8, groupType, difficulty, raidDifficulty, masterLooterGuid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_REP_GROUP_MEMBER, "REPLACE INTO group_member (guid, memberGuid, memberFlags, subgroup, roles) VALUES(?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_GROUP_MEMBER, "DELETE FROM group_member WHERE memberGuid = ? AND guid = ?", CONNECTION_ASYNC); @@ -404,7 +405,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_SEL_CHAR_DEL_INFO_BY_GUID, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL AND guid = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_DEL_INFO_BY_NAME, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL AND deleteInfos_Name LIKE CONCAT('%%', ?, '%%')", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_DEL_INFO, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_CHARS_BY_ACCOUNT_ID, "SELECT guid FROM characters WHERE account = ?", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_CHARS_BY_ACCOUNT_ID, "SELECT guid, class, race FROM characters WHERE account = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_PINFO, "SELECT totaltime, level, money, account, race, class, map, zone, gender, health, playerFlags FROM characters WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_PINFO_BANS, "SELECT unbandate, bandate = unbandate, bannedby, banreason FROM character_banned WHERE guid = ? AND active ORDER BY bandate ASC LIMIT 1", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_PINFO_MAILS, "SELECT SUM(CASE WHEN (checked & 1) THEN 1 ELSE 0 END) AS 'readmail', COUNT(*) AS 'totalmail' FROM mail WHERE `receiver` = ?", CONNECTION_SYNCH); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index 3ead3cf40..0329464f6 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -280,6 +280,7 @@ enum CharacterDatabaseStatements : uint32 CHAR_UPD_PETITION_NAME, CHAR_INS_PETITION_SIGNATURE, CHAR_UPD_ACCOUNT_ONLINE, + CHAR_UPD_CHAR_OFFLINE, CHAR_INS_GROUP, CHAR_REP_GROUP_MEMBER, CHAR_DEL_GROUP_MEMBER, diff --git a/src/server/database/Database/Implementation/LoginDatabase.cpp b/src/server/database/Database/Implementation/LoginDatabase.cpp index 1e091b56e..069abd665 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.cpp +++ b/src/server/database/Database/Implementation/LoginDatabase.cpp @@ -75,6 +75,7 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_DEL_IP_NOT_BANNED, "DELETE FROM ip_banned WHERE ip = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_INS_ACCOUNT_BANNED, "INSERT INTO account_banned VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, ?, ?, 1)", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_ACCOUNT_NOT_BANNED, "UPDATE account_banned SET active = 0 WHERE id = ? AND active != 0", CONNECTION_ASYNC); + PrepareStatement(LOGIN_DEL_REALM_CHARACTERS_BY_REALM, "DELETE FROM realmcharacters WHERE acctid = ? AND realmid = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_DEL_REALM_CHARACTERS, "DELETE FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_REP_REALM_CHARACTERS, "REPLACE INTO realmcharacters (numchars, acctid, realmid) VALUES (?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_SUM_REALM_CHARACTERS, "SELECT SUM(numchars) FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC); @@ -89,7 +90,7 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_UPD_MUTE_TIME_LOGIN, "UPDATE account SET mutetime = ? WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_LAST_IP, "UPDATE account SET last_ip = ? WHERE username = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_LAST_ATTEMPT_IP, "UPDATE account SET last_attempt_ip = ? WHERE username = ?", CONNECTION_ASYNC); - PrepareStatement(LOGIN_UPD_ACCOUNT_ONLINE, "UPDATE account SET online = ? WHERE id = ?", CONNECTION_ASYNC); + PrepareStatement(LOGIN_UPD_ACCOUNT_ONLINE, "UPDATE account SET online = 1 WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_UPTIME_PLAYERS, "UPDATE uptime SET uptime = ?, maxplayers = ? WHERE realmid = ? AND starttime = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_DEL_OLD_LOGS, "DELETE FROM logs WHERE (time + ?) < ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_DEL_ACCOUNT_ACCESS, "DELETE FROM account_access WHERE id = ?", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/LoginDatabase.h b/src/server/database/Database/Implementation/LoginDatabase.h index 563cdf3eb..4f0129782 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.h +++ b/src/server/database/Database/Implementation/LoginDatabase.h @@ -59,6 +59,7 @@ enum LoginDatabaseStatements : uint32 LOGIN_SEL_ACCOUNT_BY_ID, LOGIN_INS_ACCOUNT_BANNED, LOGIN_UPD_ACCOUNT_NOT_BANNED, + LOGIN_DEL_REALM_CHARACTERS_BY_REALM, LOGIN_DEL_REALM_CHARACTERS, LOGIN_REP_REALM_CHARACTERS, LOGIN_SEL_SUM_REALM_CHARACTERS, diff --git a/src/server/database/Database/Implementation/PlayerbotsDatabase.cpp b/src/server/database/Database/Implementation/PlayerbotsDatabase.cpp new file mode 100644 index 000000000..930862e04 --- /dev/null +++ b/src/server/database/Database/Implementation/PlayerbotsDatabase.cpp @@ -0,0 +1,129 @@ +/* + * 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 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 . + */ + +#ifdef MOD_PLAYERBOTS + +#include "PlayerbotsDatabase.h" +#include "MySQLPreparedStatement.h" + +void PlayerbotsDatabaseConnection::DoPrepareStatements() +{ + if (!m_reconnecting) + m_stmts.resize(MAX_PLAYERBOTS_STATEMENTS); + + PrepareStatement(PLAYERBOTS_SEL_CUSTOM_STRATEGY_BY_OWNER, "SELECT DISTINCT name FROM playerbots_custom_strategy WHERE owner = ?", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_SEL_CUSTOM_STRATEGY_BY_OWNER_AND_NAME, "SELECT idx, action_line FROM playerbots_custom_strategy WHERE owner = ? AND name = ? ORDER BY idx", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_SEL_CUSTOM_STRATEGY_BY_OWNER_AND_NAME_AND_IDX, "SELECT action_line FROM playerbots_custom_strategy WHERE owner = ? AND name = ? AND idx = ?", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_DEL_CUSTOM_STRATEGY, "DELETE FROM playerbots_custom_strategy WHERE name = ? AND owner = ? AND idx = ?", CONNECTION_ASYNC); + PrepareStatement(PLAYERBOTS_UPD_CUSTOM_STRATEGY, "UPDATE playerbots_custom_strategy SET action_line = ? WHERE name = ? AND owner = ? AND idx = ?", CONNECTION_ASYNC); + PrepareStatement(PLAYERBOTS_INS_CUSTOM_STRATEGY, "INSERT INTO playerbots_custom_strategy (name, owner, idx, action_line) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); + + PrepareStatement(PLAYERBOTS_SEL_DB_STORE, "SELECT `key`,`value` FROM `playerbots_db_store` WHERE `guid` = ?", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_DEL_DB_STORE, "DELETE FROM `playerbots_db_store` WHERE `guid` = ?", CONNECTION_ASYNC); + PrepareStatement(PLAYERBOTS_INS_DB_STORE, "INSERT INTO `playerbots_db_store` (`guid`, `key`, `value`) VALUES (?, ?, ?)", CONNECTION_ASYNC); + + PrepareStatement(PLAYERBOTS_SEL_ENCHANTS, "SELECT class, spec, spellid, slotid FROM playerbots_enchants", CONNECTION_SYNCH); + + PrepareStatement(PLAYERBOTS_SEL_EQUIP_CACHE, "SELECT clazz, lvl, slot, quality, item FROM playerbots_equip_cache", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_INS_EQUIP_CACHE, "INSERT INTO playerbots_equip_cache (clazz, lvl, slot, quality, item) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC); + + PrepareStatement(PLAYERBOTS_SEL_GUILD_TASKS_BY_VALUE, "SELECT `value`, `time`, validIn FROM playerbots_guild_tasks WHERE `value` = ? AND guildid = ? AND `type` = ?", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_SEL_GUILD_TASKS_BY_OWNER, "SELECT `value`, `time`, validIn, guildid FROM playerbots_guild_tasks WHERE owner = ? AND `type` = ?", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_SEL_GUILD_TASKS_BY_OWNER_AND_TYPE, "SELECT `value`, `time`, validIn FROM playerbots_guild_tasks WHERE owner = ? AND guildid = ? AND `type` = ?", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_SEL_GUILD_TASKS_BY_OWNER_DISTINCT, "SELECT DISTINCT guildid FROM playerbots_guild_tasks WHERE owner = ?", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_SEL_GUILD_TASKS_BY_OWNER_ORDERED, "SELECT `value`, `time`, validIn, guildid FROM playerbots_guild_tasks WHERE owner = ? AND type = ? ORDER BY guildid", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_DEL_GUILD_TASKS, "DELETE FROM playerbots_guild_tasks WHERE owner = ? AND guildid = ? AND `type` = ?", CONNECTION_ASYNC); + PrepareStatement(PLAYERBOTS_INS_GUILD_TASKS, "INSERT INTO playerbots_guild_tasks (owner, guildid, `time`, validIn, `type`, `value`) VALUES (?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + + PrepareStatement(PLAYERBOTS_SEL_RANDOM_BOTS_VALUE, "SELECT value FROM playerbots_random_bots WHERE event = ?", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BOT, "SELECT `bot` FROM playerbots_random_bots WHERE event = ?", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BY_OWNER_AND_EVENT, "SELECT bot FROM playerbots_random_bots WHERE owner = ? AND event = ?", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BY_OWNER_AND_BOT, "SELECT `event`, `value`, `time`, validIn, `data` FROM playerbots_random_bots WHERE owner = ? AND bot = ?", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BY_EVENT_AND_VALUE, "SELECT bot FROM playerbots_random_bots WHERE event = ? AND value = ?", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_INS_RANDOM_BOTS, "INSERT INTO playerbots_random_bots (owner, bot, `time`, validIn, event, `value`, `data`) VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(PLAYERBOTS_DEL_RANDOM_BOTS, "DELETE FROM playerbots_random_bots", CONNECTION_ASYNC); + PrepareStatement(PLAYERBOTS_DEL_RANDOM_BOTS_BY_OWNER, "DELETE FROM playerbots_random_bots WHERE owner = ? AND bot = ?", CONNECTION_ASYNC); + PrepareStatement(PLAYERBOTS_DEL_RANDOM_BOTS_BY_OWNER_AND_EVENT, "DELETE FROM playerbots_random_bots WHERE owner = ? AND bot = ? AND event = ?", CONNECTION_ASYNC); + PrepareStatement(PLAYERBOTS_UPD_RANDOM_BOTS, "UPDATE playerbots_random_bots SET validIn = ? WHERE event = ? AND bot = ?", CONNECTION_ASYNC); + + PrepareStatement(PLAYERBOTS_SEL_RARITY_CACHE, "SELECT item, rarity FROM playerbots_rarity_cache", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_INS_RARITY_CACHE, "INSERT INTO playerbots_rarity_cache (item, rarity) VALUES (?, ?)", CONNECTION_ASYNC); + + PrepareStatement(PLAYERBOTS_SEL_RNDITEM_CACHE, "SELECT lvl, type, item FROM playerbots_rnditem_cache", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_INS_RNDITEM_CACHE, "INSERT INTO playerbots_rnditem_cache (lvl, type, item) VALUES (?, ?, ?)", CONNECTION_ASYNC); + + PrepareStatement(PLAYERBOTS_SEL_SPEECH, "SELECT name, text, type FROM playerbots_speech", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_SEL_SPEECH_PROBABILITY, "SELECT name, probability FROM playerbots_speech_probability", CONNECTION_SYNCH); + + PrepareStatement(PLAYERBOTS_SEL_TELE_CACHE, "SELECT map_id, x, y, z, level FROM playerbots_tele_cache", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_INS_TELE_CACHE, "INSERT INTO playerbots_tele_cache (level, map_id, x, y, z) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC); + + PrepareStatement(PLAYERBOTS_SEL_TRAVELNODE, "SELECT id, name, map_id, x, y, z, linked FROM playerbots_travelnode", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_INS_TRAVELNODE, "INSERT INTO `playerbots_travelnode` (`id`, `name`, `map_id`, `x`, `y`, `z`, `linked`) VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(PLAYERBOTS_DEL_TRAVELNODE, "DELETE FROM playerbots_travelnode", CONNECTION_ASYNC); + + PrepareStatement(PLAYERBOTS_SEL_TRAVELNODE_LINK, "SELECT node_id, to_node_id,type,object,distance,swim_distance, extra_cost,calculated, max_creature_0,max_creature_1,max_creature_2 FROM playerbots_travelnode_link", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_INS_TRAVELNODE_LINK, "INSERT INTO `playerbots_travelnode_link` (`node_id`, `to_node_id`,`type`,`object`,`distance`,`swim_distance`, `extra_cost`,`calculated`, `max_creature_0`,`max_creature_1`,`max_creature_2`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(PLAYERBOTS_DEL_TRAVELNODE_LINK, "DELETE FROM playerbots_travelnode_link", CONNECTION_ASYNC); + + PrepareStatement(PLAYERBOTS_SEL_TRAVELNODE_PATH, "SELECT node_id, to_node_id, nr, map_id, x, y, z FROM playerbots_travelnode_path order by node_id, to_node_id, nr", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_INS_TRAVELNODE_PATH, "INSERT INTO `playerbots_travelnode_path` (`node_id`, `to_node_id`, `nr`, `map_id`, `x`, `y`, `z`) VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(PLAYERBOTS_DEL_TRAVELNODE_PATH, "DELETE FROM playerbots_travelnode_path", CONNECTION_ASYNC); + + PrepareStatement(PLAYERBOTS_SEL_TEXT, "SELECT `name`, `text`, `say_type`, `reply_type`, `text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`, `text_loc5`, `text_loc6`, `text_loc7`, `text_loc8` FROM `ai_playerbot_texts`", CONNECTION_SYNCH); + PrepareStatement( + PLAYERBOTS_SEL_DUNGEON_SUGGESTION, + "SELECT" + " d.`name`, " + " d.`difficulty`, " + " d.`min_level`, " + " d.`max_level`, " + " a.`abbrevation`, " + " s.`strategy` " + "FROM playerbots_dungeon_suggestion_definition d " + "LEFT OUTER JOIN playerbots_dungeon_suggestion_abbrevation a " + " ON d.slug = a.definition_slug " + "LEFT OUTER JOIN playerbots_dungeon_suggestion_strategy s " + " ON d.slug = s.definition_slug " + " AND d.difficulty = s.difficulty " + "WHERE d.expansion <= ?;", + CONNECTION_SYNCH + ); + + PrepareStatement(PLAYERBOTS_SEL_WEIGHTSCALES, "SELECT id, name, class FROM playerbots_weightscales", CONNECTION_SYNCH); + PrepareStatement(PLAYERBOTS_SEL_WEIGHTSCALE_DATA, "SELECT id, field, val FROM playerbots_weightscale_data", CONNECTION_SYNCH); + + PrepareStatement(PLAYERBOTS_INS_EQUIP_CACHE_NEW, "INSERT INTO playerbots_item_info_cache (id, quality, slot, source, sourceId, team, faction, factionRepRank, minLevel, " + "scale_1, scale_2, scale_3, scale_4, scale_5, scale_6, scale_7, scale_8, scale_9, scale_10, scale_11, scale_12, scale_13, scale_14, scale_15, " + "scale_16, scale_17, scale_18, scale_19, scale_20, scale_21, scale_22, scale_23, scale_24, scale_25, scale_26, scale_27, scale_28, scale_29, scale_30, scale_31, scale_32) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(PLAYERBOTS_DEL_EQUIP_CACHE_NEW, "DELETE FROM playerbots_item_info_cache WHERE id = ?", CONNECTION_ASYNC); +} + +PlayerbotsDatabaseConnection::PlayerbotsDatabaseConnection(MySQLConnectionInfo& connInfo) : MySQLConnection(connInfo) +{ +} + +PlayerbotsDatabaseConnection::PlayerbotsDatabaseConnection(ProducerConsumerQueue* q, MySQLConnectionInfo& connInfo) : MySQLConnection(q, connInfo) +{ +} + +PlayerbotsDatabaseConnection::~PlayerbotsDatabaseConnection() +{ +} + +#endif diff --git a/src/server/database/Database/Implementation/PlayerbotsDatabase.h b/src/server/database/Database/Implementation/PlayerbotsDatabase.h new file mode 100644 index 000000000..4b826f237 --- /dev/null +++ b/src/server/database/Database/Implementation/PlayerbotsDatabase.h @@ -0,0 +1,120 @@ +/* + * 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 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 . + */ + +#ifdef MOD_PLAYERBOTS + +#ifndef _PlayerbotsDatabase_H +#define _PlayerbotsDatabase_H + +#include "MySQLConnection.h" + +enum PlayerbotsDatabaseStatements : uint32 +{ + /* Naming standard for defines: + {DB}_{SEL/INS/UPD/DEL/REP}_{Summary of data changed} + When updating more than one field, consider looking at the calling function + name for a suiting suffix. + */ + + PLAYERBOTS_SEL_CUSTOM_STRATEGY_BY_OWNER, + PLAYERBOTS_SEL_CUSTOM_STRATEGY_BY_OWNER_AND_NAME, + PLAYERBOTS_SEL_CUSTOM_STRATEGY_BY_OWNER_AND_NAME_AND_IDX, + PLAYERBOTS_DEL_CUSTOM_STRATEGY, + PLAYERBOTS_UPD_CUSTOM_STRATEGY, + PLAYERBOTS_INS_CUSTOM_STRATEGY, + + PLAYERBOTS_SEL_DB_STORE, + PLAYERBOTS_DEL_DB_STORE, + PLAYERBOTS_INS_DB_STORE, + + PLAYERBOTS_SEL_ENCHANTS, + + PLAYERBOTS_SEL_EQUIP_CACHE, + PLAYERBOTS_INS_EQUIP_CACHE, + + PLAYERBOTS_SEL_GUILD_TASKS_BY_VALUE, + PLAYERBOTS_SEL_GUILD_TASKS_BY_OWNER, + PLAYERBOTS_SEL_GUILD_TASKS_BY_OWNER_AND_TYPE, + PLAYERBOTS_SEL_GUILD_TASKS_BY_OWNER_DISTINCT, + PLAYERBOTS_SEL_GUILD_TASKS_BY_OWNER_ORDERED, + PLAYERBOTS_DEL_GUILD_TASKS, + PLAYERBOTS_INS_GUILD_TASKS, + + PLAYERBOTS_SEL_RANDOM_BOTS_VALUE, + PLAYERBOTS_SEL_RANDOM_BOTS_BOT, + PLAYERBOTS_SEL_RANDOM_BOTS_BY_OWNER_AND_EVENT, + PLAYERBOTS_SEL_RANDOM_BOTS_BY_OWNER_AND_BOT, + PLAYERBOTS_SEL_RANDOM_BOTS_BY_EVENT_AND_VALUE, + PLAYERBOTS_INS_RANDOM_BOTS, + PLAYERBOTS_DEL_RANDOM_BOTS, + PLAYERBOTS_DEL_RANDOM_BOTS_BY_OWNER, + PLAYERBOTS_DEL_RANDOM_BOTS_BY_OWNER_AND_EVENT, + PLAYERBOTS_UPD_RANDOM_BOTS, + + PLAYERBOTS_SEL_RARITY_CACHE, + PLAYERBOTS_INS_RARITY_CACHE, + + PLAYERBOTS_SEL_RNDITEM_CACHE, + PLAYERBOTS_INS_RNDITEM_CACHE, + + PLAYERBOTS_SEL_SPEECH, + PLAYERBOTS_SEL_SPEECH_PROBABILITY, + + PLAYERBOTS_SEL_TELE_CACHE, + PLAYERBOTS_INS_TELE_CACHE, + + PLAYERBOTS_SEL_TEXT, + PLAYERBOTS_SEL_DUNGEON_SUGGESTION, + + PLAYERBOTS_SEL_TRAVELNODE, + PLAYERBOTS_INS_TRAVELNODE, + PLAYERBOTS_DEL_TRAVELNODE, + + PLAYERBOTS_SEL_TRAVELNODE_LINK, + PLAYERBOTS_INS_TRAVELNODE_LINK, + PLAYERBOTS_DEL_TRAVELNODE_LINK, + + PLAYERBOTS_SEL_TRAVELNODE_PATH, + PLAYERBOTS_INS_TRAVELNODE_PATH, + PLAYERBOTS_DEL_TRAVELNODE_PATH, + + PLAYERBOTS_SEL_WEIGHTSCALES, + PLAYERBOTS_SEL_WEIGHTSCALE_DATA, + + PLAYERBOTS_INS_EQUIP_CACHE_NEW, + PLAYERBOTS_DEL_EQUIP_CACHE_NEW, + + MAX_PLAYERBOTS_STATEMENTS +}; + +class AC_DATABASE_API PlayerbotsDatabaseConnection : public MySQLConnection +{ +public: + typedef PlayerbotsDatabaseStatements Statements; + + //- Constructors for sync and async connections + PlayerbotsDatabaseConnection(MySQLConnectionInfo& connInfo); + PlayerbotsDatabaseConnection(ProducerConsumerQueue* q, MySQLConnectionInfo& connInfo); + ~PlayerbotsDatabaseConnection(); + + //- Loads database type specific prepared statements + void DoPrepareStatements() override; +}; + +#endif + +#endif diff --git a/src/server/database/Updater/DBUpdater.cpp b/src/server/database/Updater/DBUpdater.cpp index 4e06beb48..a583a33a3 100644 --- a/src/server/database/Updater/DBUpdater.cpp +++ b/src/server/database/Updater/DBUpdater.cpp @@ -77,10 +77,16 @@ std::string DBUpdater::GetTableName() return "Auth"; } +template<> +std::string DBUpdater::GetSourceDirectory() +{ + return BuiltInConfig::GetSourceDirectory(); +} + template<> std::string DBUpdater::GetBaseFilesDirectory() { - return BuiltInConfig::GetSourceDirectory() + "/data/sql/base/db_auth/"; + return DBUpdater::GetSourceDirectory() + "/data/sql/base/db_auth/"; } template<> @@ -93,7 +99,7 @@ bool DBUpdater::IsEnabled(uint32 const updateMask) template<> std::string DBUpdater::GetDBModuleName() { - return "db-auth"; + return "auth"; } // World Database @@ -109,10 +115,16 @@ std::string DBUpdater::GetTableName() return "World"; } +template<> +std::string DBUpdater::GetSourceDirectory() +{ + return BuiltInConfig::GetSourceDirectory(); +} + template<> std::string DBUpdater::GetBaseFilesDirectory() { - return BuiltInConfig::GetSourceDirectory() + "/data/sql/base/db_world/"; + return DBUpdater::GetSourceDirectory() + "/data/sql/base/db_world/"; } template<> @@ -125,7 +137,7 @@ bool DBUpdater::IsEnabled(uint32 const updateMask) template<> std::string DBUpdater::GetDBModuleName() { - return "db-world"; + return "world"; } // Character Database @@ -141,10 +153,16 @@ std::string DBUpdater::GetTableName() return "Character"; } +template<> +std::string DBUpdater::GetSourceDirectory() +{ + return BuiltInConfig::GetSourceDirectory(); +} + template<> std::string DBUpdater::GetBaseFilesDirectory() { - return BuiltInConfig::GetSourceDirectory() + "/data/sql/base/db_characters/"; + return DBUpdater::GetSourceDirectory() + "/data/sql/base/db_characters/"; } template<> @@ -157,9 +175,49 @@ bool DBUpdater::IsEnabled(uint32 const updateMask) template<> std::string DBUpdater::GetDBModuleName() { - return "db-characters"; + return "characters"; } +#ifdef MOD_PLAYERBOTS +// Playerbots Database +template<> +std::string DBUpdater::GetConfigEntry() +{ + return "Updates.Playerbots"; +} + +template<> +std::string DBUpdater::GetTableName() +{ + return "Playerbots"; +} + +template<> +std::string DBUpdater::GetSourceDirectory() +{ + return BuiltInConfig::GetSourceDirectory() + "/modules/mod-playerbots"; +} + +template<> +std::string DBUpdater::GetBaseFilesDirectory() +{ + return DBUpdater::GetSourceDirectory() + "/data/sql/playerbots/base/"; +} + +template<> +bool DBUpdater::IsEnabled(uint32 const updateMask) +{ + // This way silences warnings under msvc + return (updateMask & DatabaseLoader::DATABASE_PLAYERBOTS) ? true : false; +} + +template<> +std::string DBUpdater::GetDBModuleName() +{ + return "db_playerbot"; +} +#endif + // All template BaseLocation DBUpdater::GetBaseLocationType() @@ -225,7 +283,7 @@ bool DBUpdater::Update(DatabaseWorkerPool& pool, std::string_view modulesL LOG_INFO("sql.updates", "Updating {} database...", DBUpdater::GetTableName()); - Path const sourceDirectory(BuiltInConfig::GetSourceDirectory()); + Path const sourceDirectory(DBUpdater::GetSourceDirectory()); if (!is_directory(sourceDirectory)) { @@ -300,7 +358,7 @@ bool DBUpdater::Update(DatabaseWorkerPool& pool, std::vector return false; } - Path const sourceDirectory(BuiltInConfig::GetSourceDirectory()); + Path const sourceDirectory(DBUpdater::GetSourceDirectory()); if (!is_directory(sourceDirectory)) { return false; @@ -534,3 +592,7 @@ void DBUpdater::ApplyFile(DatabaseWorkerPool& pool, std::string const& hos template class AC_DATABASE_API DBUpdater; template class AC_DATABASE_API DBUpdater; template class AC_DATABASE_API DBUpdater; + +#ifdef MOD_PLAYERBOTS +template class AC_DATABASE_API DBUpdater; +#endif diff --git a/src/server/database/Updater/DBUpdater.h b/src/server/database/Updater/DBUpdater.h index 58ebf3320..3a0e58709 100644 --- a/src/server/database/Updater/DBUpdater.h +++ b/src/server/database/Updater/DBUpdater.h @@ -72,6 +72,7 @@ public: static inline std::string GetConfigEntry(); static inline std::string GetTableName(); + static std::string GetSourceDirectory(); static std::string GetBaseFilesDirectory(); static bool IsEnabled(uint32 const updateMask); static BaseLocation GetBaseLocationType(); diff --git a/src/server/game/Battlegrounds/ArenaTeam.cpp b/src/server/game/Battlegrounds/ArenaTeam.cpp index 985193f9d..0e4dd3fde 100644 --- a/src/server/game/Battlegrounds/ArenaTeam.cpp +++ b/src/server/game/Battlegrounds/ArenaTeam.cpp @@ -1103,3 +1103,22 @@ std::unordered_map ArenaTeam::ArenaReqPlayersForType = { ARENA_TYPE_3v3, 6}, { ARENA_TYPE_5v5, 10} }; + +void ArenaTeam::SetEmblem(uint32 backgroundColor, uint8 emblemStyle, uint32 emblemColor, uint8 borderStyle, uint32 borderColor) +{ + BackgroundColor = backgroundColor; + EmblemStyle = emblemStyle; + EmblemColor = emblemColor; + BorderStyle = borderStyle; + BorderColor = borderColor; +} + +void ArenaTeam::SetRatingForAll(uint32 rating) +{ + Stats.Rating = rating; + + for (MemberList::iterator itr = Members.begin(); itr != Members.end(); ++itr) + { + itr->PersonalRating = rating; + } +} diff --git a/src/server/game/Battlegrounds/ArenaTeam.h b/src/server/game/Battlegrounds/ArenaTeam.h index 33c47920e..a0ec07397 100644 --- a/src/server/game/Battlegrounds/ArenaTeam.h +++ b/src/server/game/Battlegrounds/ArenaTeam.h @@ -217,6 +217,10 @@ public: static std::unordered_map ArenaSlotByType; // Slot -> Type static std::unordered_map ArenaReqPlayersForType; // Type -> Players count + void SetEmblem(uint32 backgroundColor, uint8 emblemStyle, uint32 emblemColor, uint8 borderStyle, uint32 borderColor); + void SetRatingForAll(uint32 rating); + + protected: uint32 TeamId; uint8 Type; diff --git a/src/server/game/Battlegrounds/Battleground.h b/src/server/game/Battlegrounds/Battleground.h index b22cdc143..6178869f5 100644 --- a/src/server/game/Battlegrounds/Battleground.h +++ b/src/server/game/Battlegrounds/Battleground.h @@ -205,6 +205,7 @@ struct BattlegroundObjectInfo enum ArenaType : uint8 { + ARENA_TYPE_NONE = 0, ARENA_TYPE_2v2 = 2, ARENA_TYPE_3v3 = 3, ARENA_TYPE_5v5 = 5 diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundAB.h b/src/server/game/Battlegrounds/Zones/BattlegroundAB.h index 72932386a..92e58975e 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundAB.h +++ b/src/server/game/Battlegrounds/Zones/BattlegroundAB.h @@ -246,6 +246,18 @@ protected: uint32 BasesDefended = 0; }; +struct CaptureABPointInfo +{ + CaptureABPointInfo() : _ownerTeamId(TEAM_NEUTRAL), _iconNone(0), _iconCapture(0), _state(BG_AB_NODE_STATE_NEUTRAL), _captured(false) {} + + TeamId _ownerTeamId; + uint32 _iconNone; + uint32 _iconCapture; + uint8 _state; + + bool _captured; +}; + class AC_GAME_API BattlegroundAB : public Battleground { public: @@ -270,6 +282,9 @@ public: bool IsTeamScores500Disadvantage(TeamId teamId) const { return _teamScores500Disadvantage[teamId]; } TeamId GetPrematureWinner() override; + + [[nodiscard]] CaptureABPointInfo const& GetCapturePointInfo(uint32 node) const { return _capturePointInfo[node]; } + private: void PostUpdateImpl(uint32 diff) override; @@ -280,21 +295,7 @@ private: void NodeDeoccupied(uint8 node); void ApplyPhaseMask(); - struct CapturePointInfo - { - CapturePointInfo() : _ownerTeamId(TEAM_NEUTRAL), _iconNone(0), _iconCapture(0), _state(BG_AB_NODE_STATE_NEUTRAL), _captured(false) - { - } - - TeamId _ownerTeamId; - uint32 _iconNone; - uint32 _iconCapture; - uint8 _state; - - bool _captured; - }; - - CapturePointInfo _capturePointInfo[BG_AB_DYNAMIC_NODES_COUNT]; + CaptureABPointInfo _capturePointInfo[BG_AB_DYNAMIC_NODES_COUNT]; EventMap _bgEvents; uint32 _honorTics; uint32 _reputationTics; diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundAV.h b/src/server/game/Battlegrounds/Zones/BattlegroundAV.h index 57417dd1d..eec6ff058 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundAV.h +++ b/src/server/game/Battlegrounds/Zones/BattlegroundAV.h @@ -1791,6 +1791,10 @@ public: TeamId GetPrematureWinner() override; + [[nodiscard]] BG_AV_NodeInfo const& GetAVNodeInfo(uint32 node) const { return m_Nodes[node]; } + [[nodiscard]] bool IsCaptainAlive(uint8 index) const { return m_CaptainAlive[index]; } + [[nodiscard]] TeamId GetMineOwner(uint8 index) const { return m_Mine_Owner[index]; } + private: void PostUpdateImpl(uint32 diff) override; diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundEY.h b/src/server/game/Battlegrounds/Zones/BattlegroundEY.h index 2730a0604..b4da9d7f5 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundEY.h +++ b/src/server/game/Battlegrounds/Zones/BattlegroundEY.h @@ -346,6 +346,25 @@ protected: uint32 FlagCaptures = 0; }; +struct CaptureEYPointInfo +{ + CaptureEYPointInfo() : _ownerTeamId(TEAM_NEUTRAL), _barStatus(BG_EY_PROGRESS_BAR_STATE_MIDDLE), _areaTrigger(0) + { + _playersCount[TEAM_ALLIANCE] = 0; + _playersCount[TEAM_HORDE] = 0; + } + + Player* player = nullptr; + TeamId _ownerTeamId; + int8 _barStatus; + uint32 _areaTrigger; + int8 _playersCount[PVP_TEAMS_COUNT]; + + bool IsUnderControl(TeamId teamId) const { return _ownerTeamId == teamId; } + bool IsUnderControl() const { return _ownerTeamId != TEAM_NEUTRAL; } + bool IsUncontrolled() const { return _ownerTeamId == TEAM_NEUTRAL; } +}; + class AC_GAME_API BattlegroundEY : public Battleground { public: @@ -385,6 +404,8 @@ public: bool AllNodesConrolledByTeam(TeamId teamId) const override; TeamId GetPrematureWinner() override; + [[nodiscard]] CaptureEYPointInfo const& GetCapturePointInfo(uint32 node) const { return _capturePointInfo[node]; } + private: void PostUpdateImpl(uint32 diff) override; @@ -400,26 +421,7 @@ private: /* Scorekeeping */ void AddPoints(TeamId teamId, uint32 points); - struct CapturePointInfo - { - CapturePointInfo() : _ownerTeamId(TEAM_NEUTRAL), _barStatus(BG_EY_PROGRESS_BAR_STATE_MIDDLE), _areaTrigger(0) - { - _playersCount[TEAM_ALLIANCE] = 0; - _playersCount[TEAM_HORDE] = 0; - } - - TeamId _ownerTeamId; - int8 _barStatus; - uint32 _areaTrigger; - int8 _playersCount[PVP_TEAMS_COUNT]; - Player* player = nullptr; - - bool IsUnderControl(TeamId teamId) const { return _ownerTeamId == teamId; } - bool IsUnderControl() const { return _ownerTeamId != TEAM_NEUTRAL; } - bool IsUncontrolled() const { return _ownerTeamId == TEAM_NEUTRAL; } - }; - - CapturePointInfo _capturePointInfo[EY_POINTS_MAX]; + CaptureEYPointInfo _capturePointInfo[EY_POINTS_MAX]; EventMap _bgEvents; uint32 _honorTics; uint8 _ownedPointsCount[PVP_TEAMS_COUNT]; diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundIC.h b/src/server/game/Battlegrounds/Zones/BattlegroundIC.h index 9f859ddfe..bc5daf8fc 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundIC.h +++ b/src/server/game/Battlegrounds/Zones/BattlegroundIC.h @@ -922,6 +922,9 @@ public: bool AllNodesConrolledByTeam(TeamId teamId) const override; // overwrited bool IsResourceGlutAllowed(TeamId teamId) const; void DoAction(uint32 action, ObjectGuid guid) override; + + [[nodiscard]] ICNodePoint const& GetICNodePoint(uint8 index) { return nodePoint[index]; } + private: uint32 closeFortressDoorsTimer; bool doorsClosed; diff --git a/src/server/game/Chat/Channels/ChannelMgr.h b/src/server/game/Chat/Channels/ChannelMgr.h index daf595d2f..868f2a171 100644 --- a/src/server/game/Chat/Channels/ChannelMgr.h +++ b/src/server/game/Chat/Channels/ChannelMgr.h @@ -40,6 +40,7 @@ public: Channel* GetJoinChannel(std::string const& name, uint32 channel_id); Channel* GetChannel(std::string const& name, Player* p, bool pkt = true); + const ChannelMap& GetChannels() const { return channels; } static void LoadChannels(); static void LoadChannelRights(); diff --git a/src/server/game/Chat/Chat.cpp b/src/server/game/Chat/Chat.cpp index a0dc7f090..33892e072 100644 --- a/src/server/game/Chat/Chat.cpp +++ b/src/server/game/Chat/Chat.cpp @@ -371,6 +371,78 @@ std::size_t ChatHandler::BuildChatPacket(WorldPacket& data, ChatMsg chatType, La return BuildChatPacket(data, chatType, language, senderGUID, receiverGUID, message, chatTag, senderName, receiverName, achievementId, gmMessage, channelName); } +void ChatHandler::BuildChatPacket(WorldPacket& data, ChatMsg msgtype, std::string_view message, Language language /*= LANG_UNIVERSAL*/, PlayerChatTag chatTag /*= CHAT_TAG_NONE*/, + ObjectGuid const& senderGuid /*= ObjectGuid()*/, std::string_view senderName /*= nullptr*/, + ObjectGuid const& targetGuid /*= ObjectGuid()*/, std::string_view targetName /*= nullptr*/, + std::string_view channelName /*= nullptr*/, uint32 achievementId /*= 0*/) +{ + const bool isGM = (chatTag & CHAT_TAG_GM) != 0; + bool isAchievement = false; + + data.Initialize((isGM && language != LANG_ADDON) ? SMSG_GM_MESSAGECHAT : SMSG_MESSAGECHAT); + data << uint8(msgtype); + data << uint32(language); + data << ObjectGuid(senderGuid); + data << uint32(0); // 2.1.0 + + switch (msgtype) + { + case CHAT_MSG_MONSTER_SAY: + case CHAT_MSG_MONSTER_PARTY: + case CHAT_MSG_MONSTER_YELL: + case CHAT_MSG_MONSTER_WHISPER: + case CHAT_MSG_MONSTER_EMOTE: + case CHAT_MSG_RAID_BOSS_WHISPER: + case CHAT_MSG_RAID_BOSS_EMOTE: + case CHAT_MSG_BATTLENET: + case CHAT_MSG_WHISPER_FOREIGN: + data << uint32(senderName.size() + 1); + data << senderName; + data << ObjectGuid(targetGuid); // Unit Target + if (targetGuid && !targetGuid.IsPlayer() && !targetGuid.IsPet() && (msgtype != CHAT_MSG_WHISPER_FOREIGN)) + { + data << uint32(targetName.size() + 1); // target name length + data << targetName; // target name + } + break; + case CHAT_MSG_BG_SYSTEM_NEUTRAL: + case CHAT_MSG_BG_SYSTEM_ALLIANCE: + case CHAT_MSG_BG_SYSTEM_HORDE: + data << ObjectGuid(targetGuid); // Unit Target + if (targetGuid && !targetGuid.IsPlayer()) + { + data << uint32(targetName.size() + 1); // target name length + data << targetName; // target name + } + break; + case CHAT_MSG_ACHIEVEMENT: + case CHAT_MSG_GUILD_ACHIEVEMENT: + data << ObjectGuid(targetGuid); // Unit Target + isAchievement = true; + break; + default: + if (isGM) + { + data << uint32(senderName.size() + 1); + data << senderName; + } + + if (msgtype == CHAT_MSG_CHANNEL) + { + data << channelName; + } + data << ObjectGuid(targetGuid); + break; + } + data << uint32(message.size() + 1); + data << message; + data << uint8(chatTag); + + if (isAchievement) + data << uint32(achievementId); +} + + Player* ChatHandler::getSelectedPlayer() const { if (!m_session) diff --git a/src/server/game/Chat/Chat.h b/src/server/game/Chat/Chat.h index e2891a3b8..ce733b3a5 100644 --- a/src/server/game/Chat/Chat.h +++ b/src/server/game/Chat/Chat.h @@ -47,6 +47,13 @@ public: // Builds chat packet and returns receiver guid position in the packet to substitute in whisper builders static std::size_t BuildChatPacket(WorldPacket& data, ChatMsg chatType, Language language, WorldObject const* sender, WorldObject const* receiver, std::string_view message, uint32 achievementId = 0, std::string const& channelName = "", LocaleConstant locale = DEFAULT_LOCALE); + // All in one chat message builder + static void BuildChatPacket( + WorldPacket& data, ChatMsg msgtype, std::string_view message, Language language = LANG_UNIVERSAL, PlayerChatTag chatTag = CHAT_TAG_NONE, + ObjectGuid const& senderGuid = ObjectGuid(), std::string_view senderName = {}, + ObjectGuid const& targetGuid = ObjectGuid(), std::string_view targetName = {}, + std::string_view channelName = {}, uint32 achievementId = 0); + static char* LineFromMessage(char*& pos) { char* start = strtok(pos, "\n"); pos = nullptr; return start; } void SendNotification(std::string_view str); diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp index 7aa741332..7cb68e1e4 100644 --- a/src/server/game/DataStores/DBCStores.cpp +++ b/src/server/game/DataStores/DBCStores.cpp @@ -34,11 +34,16 @@ typedef std::map AreaFlagByMapID; typedef std::tuple WMOAreaTableKey; typedef std::map WMOAreaInfoByTripple; +typedef std::multimap CharSectionsMap; + DBCStorage sAreaTableStore(AreaTableEntryfmt); DBCStorage sAreaGroupStore(AreaGroupEntryfmt); DBCStorage sAreaPOIStore(AreaPOIEntryfmt); static WMOAreaInfoByTripple sWMOAreaInfoByTripple; +static AreaFlagByAreaID sAreaFlagByAreaID; +// for instances without generated *.map files +static AreaFlagByMapID sAreaFlagByMapID; DBCStorage sAchievementStore(Achievementfmt); DBCStorage sAchievementCategoryStore(AchievementCategoryfmt); @@ -49,6 +54,10 @@ DBCStorage sBattlemasterListStore(BattlemasterListEntryf DBCStorage sBarberShopStyleStore(BarberShopStyleEntryfmt); DBCStorage sCharStartOutfitStore(CharStartOutfitEntryfmt); std::map sCharStartOutfitMap; + +DBCStorage sCharSectionsStore(CharSectionsEntryfmt); +CharSectionsMap sCharSectionMap; + DBCStorage sCharTitlesStore(CharTitlesEntryfmt); DBCStorage sChatChannelsStore(ChatChannelsEntryfmt); DBCStorage sChrClassesStore(ChrClassesEntryfmt); @@ -71,6 +80,10 @@ DBCStorage sDurabilityCostsStore(DurabilityCostsfmt); DBCStorage sEmotesStore(EmotesEntryfmt); DBCStorage sEmotesTextStore(EmotesTextEntryfmt); +typedef std::tuple EmotesTextSoundKey; +static std::map sEmotesTextSoundMap; +DBCStorage sEmotesTextSoundStore(EmotesTextSoundEntryfmt); + typedef std::map FactionTeamMap; static FactionTeamMap sFactionTeamMap; DBCStorage sFactionStore(FactionEntryfmt); @@ -280,6 +293,7 @@ void LoadDBCStores(const std::string& dataPath) LOAD_DBC(sBattlemasterListStore, "BattlemasterList.dbc", "battlemasterlist_dbc"); LOAD_DBC(sBarberShopStyleStore, "BarberShopStyle.dbc", "barbershopstyle_dbc"); LOAD_DBC(sCharStartOutfitStore, "CharStartOutfit.dbc", "charstartoutfit_dbc"); + LOAD_DBC(sCharSectionsStore, "CharSections.dbc", "charsections_dbc"); LOAD_DBC(sCharTitlesStore, "CharTitles.dbc", "chartitles_dbc"); LOAD_DBC(sChatChannelsStore, "ChatChannels.dbc", "chatchannels_dbc"); LOAD_DBC(sChrClassesStore, "ChrClasses.dbc", "chrclasses_dbc"); @@ -299,6 +313,7 @@ void LoadDBCStores(const std::string& dataPath) LOAD_DBC(sDurabilityQualityStore, "DurabilityQuality.dbc", "durabilityquality_dbc"); LOAD_DBC(sEmotesStore, "Emotes.dbc", "emotes_dbc"); LOAD_DBC(sEmotesTextStore, "EmotesText.dbc", "emotestext_dbc"); + LOAD_DBC(sEmotesTextSoundStore, "EmotesTextSound.dbc", "emotetextsound_dbc"); LOAD_DBC(sFactionStore, "Faction.dbc", "faction_dbc"); LOAD_DBC(sFactionTemplateStore, "FactionTemplate.dbc", "factiontemplate_dbc"); LOAD_DBC(sGameObjectArtKitStore, "GameObjectArtKit.dbc", "gameobjectartkit_dbc"); @@ -384,9 +399,26 @@ void LoadDBCStores(const std::string& dataPath) #undef LOAD_DBC + for (uint32 i = 0; i < sAreaTableStore.GetNumRows(); ++i) // areaflag numbered from 0 + { + if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(i)) + { + // fill AreaId->DBC records + sAreaFlagByAreaID.insert(AreaFlagByAreaID::value_type(uint16(area->ID), area->exploreFlag)); + + // fill MapId->DBC records ( skip sub zones and continents ) + if (area->zone == 0 && area->mapid != 0 && area->mapid != 1 && area->mapid != 530) + sAreaFlagByMapID.insert(AreaFlagByMapID::value_type(area->mapid, area->exploreFlag)); + } + } + for (CharStartOutfitEntry const* outfit : sCharStartOutfitStore) sCharStartOutfitMap[outfit->Race | (outfit->Class << 8) | (outfit->Gender << 16)] = outfit; + for (CharSectionsEntry const* charSection : sCharSectionsStore) + if (charSection->Race && ((1 << (charSection->Race - 1)) & RACEMASK_ALL_PLAYABLE) != 0) //ignore Nonplayable races + sCharSectionMap.insert({ charSection->GenType | (charSection->Gender << 8) | (charSection->Race << 16), charSection }); + for (FactionEntry const* faction : sFactionStore) { if (faction->team) @@ -408,6 +440,9 @@ void LoadDBCStores(const std::string& dataPath) std::swap(*(float*)(&info->maxZ), *(float*)(&info->minZ)); } + for (EmotesTextSoundEntry const* emoteTextSound : sEmotesTextSoundStore) + sEmotesTextSoundMap[EmotesTextSoundKey(emoteTextSound->EmotesTextId, emoteTextSound->RaceId, emoteTextSound->SexId)] = emoteTextSound; + // fill data for (MapDifficultyEntry const* entry : sMapDifficultyStore) sMapDifficultyMap[MAKE_PAIR32(entry->MapId, entry->Difficulty)] = MapDifficulty(entry->resetTime, entry->maxPlayers, entry->areaTriggerText[0] != '\0'); @@ -848,6 +883,18 @@ CharStartOutfitEntry const* GetCharStartOutfitEntry(uint8 race, uint8 class_, ui return itr->second; } +CharSectionsEntry const* GetCharSectionEntry(uint8 race, CharSectionType genType, uint8 gender, uint8 type, uint8 color) +{ + std::pair eqr = sCharSectionMap.equal_range(uint32(genType) | uint32(gender << 8) | uint32(race << 16)); + for (CharSectionsMap::const_iterator itr = eqr.first; itr != eqr.second; ++itr) + { + if (itr->second->Type == type && itr->second->Color == color) + return itr->second; + } + + return nullptr; +} + /// Returns LFGDungeonEntry for a specific map and difficulty. Will return first found entry if multiple dungeons use the same map (such as Scarlet Monastery) LFGDungeonEntry const* GetLFGDungeon(uint32 mapId, Difficulty difficulty) { @@ -913,6 +960,12 @@ SkillRaceClassInfoEntry const* GetSkillRaceClassInfo(uint32 skill, uint8 race, u return nullptr; } +EmotesTextSoundEntry const* FindTextSoundEmoteFor(uint32 emote, uint32 race, uint32 gender) +{ + auto itr = sEmotesTextSoundMap.find(EmotesTextSoundKey(emote, race, gender)); + return itr != sEmotesTextSoundMap.end() ? itr->second : nullptr; +} + const std::vector& GetSkillLineAbilitiesBySkillLine(uint32 skillLine) { auto it = sSkillLineAbilityIndexBySkillLine.find(skillLine); @@ -923,3 +976,40 @@ const std::vector& GetSkillLineAbilitiesBySkillLin } return it->second; } + +uint32 GetAreaFlagByMapId(uint32 mapid) +{ + AreaFlagByMapID::iterator i = sAreaFlagByMapID.find(mapid); + if (i == sAreaFlagByMapID.end()) + return 0; + return i->second; +} + +int32 GetAreaFlagByAreaID(uint32 area_id) +{ + AreaFlagByAreaID::iterator i = sAreaFlagByAreaID.find(area_id); + if (i == sAreaFlagByAreaID.end()) + return -1; + + return i->second; +} + +AreaTableEntry const* GetAreaEntryByAreaID(uint32 area_id) +{ + int32 areaflag = GetAreaFlagByAreaID(area_id); + if (areaflag < 0) + return nullptr; + + return sAreaTableStore.LookupEntry(areaflag); +} + +AreaTableEntry const* GetAreaEntryByAreaFlagAndMap(uint32 area_flag, uint32 map_id) +{ + if (area_flag) + return sAreaTableStore.LookupEntry(area_flag); + + if (MapEntry const* mapEntry = sMapStore.LookupEntry(map_id)) + return GetAreaEntryByAreaID(mapEntry->linked_zone); + + return nullptr; +} \ No newline at end of file diff --git a/src/server/game/DataStores/DBCStores.h b/src/server/game/DataStores/DBCStores.h index ec8cb68bb..011a9cbab 100644 --- a/src/server/game/DataStores/DBCStores.h +++ b/src/server/game/DataStores/DBCStores.h @@ -35,6 +35,13 @@ TalentSpellPos const* GetTalentSpellPos(uint32 spellId); WMOAreaTableEntry const* GetWMOAreaTableEntryByTripple(int32 rootid, int32 adtid, int32 groupid); + +// -1 if not found +int32 GetAreaFlagByAreaID(uint32 area_id); +uint32 GetAreaFlagByMapId(uint32 mapid); +AreaTableEntry const* GetAreaEntryByAreaID(uint32 area_id); +AreaTableEntry const* GetAreaEntryByAreaFlagAndMap(uint32 area_flag, uint32 map_id); + uint32 GetVirtualMapForMapAndZone(uint32 mapid, uint32 zoneId); enum ContentLevels : uint8 @@ -63,6 +70,8 @@ PvPDifficultyEntry const* GetBattlegroundBracketById(uint32 mapid, BattlegroundB CharStartOutfitEntry const* GetCharStartOutfitEntry(uint8 race, uint8 class_, uint8 gender); +CharSectionsEntry const* GetCharSectionEntry(uint8 race, CharSectionType genType, uint8 gender, uint8 type, uint8 color); + LFGDungeonEntry const* GetLFGDungeon(uint32 mapId, Difficulty difficulty); LFGDungeonEntry const* GetZoneLFGDungeonEntry(std::string const& zoneName, LocaleConstant locale); @@ -72,6 +81,7 @@ typedef std::unordered_multimap SkillRac typedef std::pair SkillRaceClassInfoBounds; SkillRaceClassInfoEntry const* GetSkillRaceClassInfo(uint32 skill, uint8 race, uint8 class_); +EmotesTextSoundEntry const* FindTextSoundEmoteFor(uint32 emote, uint32 race, uint32 gender); typedef std::unordered_map > SkillLineAbilityIndexBySkillLine; const std::vector& GetSkillLineAbilitiesBySkillLine(uint32 skillLine); @@ -87,6 +97,7 @@ extern DBCStorage sBarberShopStyleStore; extern DBCStorage sBattlemasterListStore; extern DBCStorage sChatChannelsStore; extern DBCStorage sCharStartOutfitStore; +extern DBCStorage sCharSectionsStore; extern DBCStorage sCharTitlesStore; extern DBCStorage sChrClassesStore; extern DBCStorage sChrRacesStore; @@ -105,6 +116,7 @@ extern DBCStorage sDurabilityCostsStore; extern DBCStorage sDurabilityQualityStore; extern DBCStorage sEmotesStore; extern DBCStorage sEmotesTextStore; +extern DBCStorage sEmotesTextSoundStore; extern DBCStorage sFactionStore; extern DBCStorage sFactionTemplateStore; extern DBCStorage sGameObjectArtKitStore; diff --git a/src/server/game/DungeonFinding/LFGMgr.h b/src/server/game/DungeonFinding/LFGMgr.h index c96ae4f2a..fd4bd6ce9 100644 --- a/src/server/game/DungeonFinding/LFGMgr.h +++ b/src/server/game/DungeonFinding/LFGMgr.h @@ -581,6 +581,7 @@ namespace lfg [[nodiscard]] bool IsTesting() const { return m_Testing; } void SetDungeon(ObjectGuid guid, uint32 dungeon); + LFGDungeonData const* GetLFGDungeon(uint32 id); private: TeamId GetTeam(ObjectGuid guid); @@ -593,7 +594,6 @@ namespace lfg void SetCanOverrideRBState(ObjectGuid guid, bool val); void GetCompatibleDungeons(LfgDungeonSet& dungeons, LfgGuidSet const& players, LfgLockPartyMap& lockMap, uint32 randomDungeonId = 0); void _SaveToDB(ObjectGuid guid); - LFGDungeonData const* GetLFGDungeon(uint32 id); // Proposals void RemoveProposal(LfgProposalContainer::iterator itProposal, LfgUpdateType type); diff --git a/src/server/game/DungeonFinding/LFGQueue.cpp b/src/server/game/DungeonFinding/LFGQueue.cpp index aae5df67a..6e7f99a32 100644 --- a/src/server/game/DungeonFinding/LFGQueue.cpp +++ b/src/server/game/DungeonFinding/LFGQueue.cpp @@ -25,6 +25,7 @@ #include "Log.h" #include "ObjectMgr.h" #include "Player.h" +#include "ScriptMgr.h" #include "World.h" namespace lfg @@ -411,6 +412,11 @@ namespace lfg if (!sLFGMgr->AllQueued(check)) // can't create proposal return LFG_COMPATIBILITY_PENDING; + if (!sScriptMgr->OnPlayerbotCheckLFGQueue(proposal.queues)) + { + return LFG_INCOMPATIBLES_HAS_IGNORES; + } + // Create a new proposal proposal.cancelTime = GameTime::GetGameTime().count() + LFG_TIME_PROPOSAL; proposal.state = LFG_PROPOSAL_INITIATING; diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 4a8a98354..23077fe9b 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -1373,7 +1373,7 @@ void Creature::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask) m_spawnId = sObjectMgr->GenerateCreatureSpawnId(); CreatureData& data = sObjectMgr->NewOrExistCreatureData(m_spawnId); - + data.spawnId = m_spawnId; // mod_playerbots uint32 displayId = GetNativeDisplayId(); uint32 npcflag = GetNpcFlags(); uint32 unit_flags = GetUnitFlags(); diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index f1f3d6f02..e1022f122 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -365,6 +365,7 @@ typedef std::unordered_map EquipmentInfo struct CreatureData { CreatureData() = default; + ObjectGuid::LowType spawnId{ 0 }; // mod_playerbots uint32 id1{0}; // entry in creature_template uint32 id2{0}; // entry in creature_template uint32 id3{0}; // entry in creature_template diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp index 9aea0ea74..9c0c27fb7 100644 --- a/src/server/game/Entities/Item/Item.cpp +++ b/src/server/game/Entities/Item/Item.cpp @@ -1084,7 +1084,7 @@ void Item::SendTimeUpdate(Player* owner) owner->SendDirectMessage(&data); } -Item* Item::CreateItem(uint32 item, uint32 count, Player const* player, bool clone, uint32 randomPropertyId) +Item* Item::CreateItem(uint32 item, uint32 count, Player const* player, bool clone, uint32 randomPropertyId, bool temp) { if (count < 1) return nullptr; //don't create item at zero count @@ -1098,7 +1098,8 @@ Item* Item::CreateItem(uint32 item, uint32 count, Player const* player, bool clo ASSERT_NODEBUGINFO(count != 0 && "pProto->Stackable == 0 but checked at loading already"); Item* pItem = NewItemOrBag(pProto); - if (pItem->Create(sObjectMgr->GetGenerator().Generate(), item, player)) + uint32 guid = temp ? 0xFFFFFFFF : sObjectMgr->GetGenerator().Generate(); + if (pItem->Create(guid, item, player)) { pItem->SetCount(count); if (!clone) @@ -1276,10 +1277,15 @@ void Item::ClearSoulboundTradeable(Player* currentOwner) bool Item::CheckSoulboundTradeExpire() { - // called from owner's update - GetOwner() MUST be valid - if (GetUInt32Value(ITEM_FIELD_CREATE_PLAYED_TIME) + 2 * HOUR < GetOwner()->GetTotalPlayedTime()) + // we have to check the owner for mod_playerbots since bots programically call methods like DestroyItem, + // MoveItemToMail, DestroyItemCount which do not handle soulboundTradeable clearing. + Player* owner = GetOwner(); + if (!owner) + return true; // remove from tradeable list + + if (GetUInt32Value(ITEM_FIELD_CREATE_PLAYED_TIME) + 2 * HOUR < owner->GetTotalPlayedTime()) { - ClearSoulboundTradeable(GetOwner()); + ClearSoulboundTradeable(owner); return true; // remove from tradeable list } diff --git a/src/server/game/Entities/Item/Item.h b/src/server/game/Entities/Item/Item.h index 51289594b..e333a4ba7 100644 --- a/src/server/game/Entities/Item/Item.h +++ b/src/server/game/Entities/Item/Item.h @@ -219,7 +219,7 @@ bool ItemCanGoIntoBag(ItemTemplate const* proto, ItemTemplate const* pBagProto); class Item : public Object { public: - static Item* CreateItem(uint32 item, uint32 count, Player const* player = nullptr, bool clone = false, uint32 randomPropertyId = 0); + static Item* CreateItem(uint32 item, uint32 count, Player const* player = nullptr, bool clone = false, uint32 randomPropertyId = 0, bool temp = false); Item* CloneItem(uint32 count, Player const* player = nullptr) const; Item(); diff --git a/src/server/game/Entities/Item/ItemTemplate.h b/src/server/game/Entities/Item/ItemTemplate.h index b41616c11..dca51af79 100644 --- a/src/server/game/Entities/Item/ItemTemplate.h +++ b/src/server/game/Entities/Item/ItemTemplate.h @@ -251,7 +251,7 @@ enum SocketColor #define SOCKET_COLOR_ALL (SOCKET_COLOR_META | SOCKET_COLOR_RED | SOCKET_COLOR_YELLOW | SOCKET_COLOR_BLUE) -enum InventoryType +enum InventoryType : uint32 { INVTYPE_NON_EQUIP = 0, INVTYPE_HEAD = 1, @@ -818,6 +818,8 @@ struct ItemTemplate [[nodiscard]] bool IsWeaponVellum() const { return Class == ITEM_CLASS_TRADE_GOODS && SubClass == ITEM_SUBCLASS_WEAPON_ENCHANTMENT; } [[nodiscard]] bool IsArmorVellum() const { return Class == ITEM_CLASS_TRADE_GOODS && SubClass == ITEM_SUBCLASS_ARMOR_ENCHANTMENT; } [[nodiscard]] bool IsConjuredConsumable() const { return Class == ITEM_CLASS_CONSUMABLE && HasFlag(ITEM_FLAG_CONJURED); } + [[nodiscard]] bool IsWeapon() const { return Class == ITEM_CLASS_WEAPON; } + [[nodiscard]] bool IsRangedWeapon() const { return IsWeapon() && (InventoryType == INVTYPE_RANGED || InventoryType == INVTYPE_THROWN || InventoryType == INVTYPE_RANGEDRIGHT); } [[nodiscard]] bool HasStat(ItemModType stat) const; [[nodiscard]] bool HasSpellPowerStat() const; diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index d0eaceae0..096fbde04 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -4925,6 +4925,15 @@ void Player::CleanupChannels() } } +// Playerbot helper if bot talks in a different locale +bool Player::IsInChannel(const Channel* c) +{ + return std::any_of(m_channels.begin(), m_channels.end(), [c](const Channel* chan) + { + return c->GetChannelId() == chan->GetChannelId(); + }); +} + void Player::ClearChannelWatch() { for (JoinedChannelsList::iterator itr = m_channels.begin(); itr != m_channels.end(); ++itr) @@ -12337,6 +12346,10 @@ bool Player::IsSpellFitByClassAndRace(uint32 spell_id) const if (_spell_idx->second->ClassMask && (_spell_idx->second->ClassMask & classmask) == 0) continue; + // skip wrong class and race skill saved in SkillRaceClassInfo.dbc + if (!GetSkillRaceClassInfo(_spell_idx->second->SkillLine, getRace(), getClass())) + continue; + return true; } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 9496eb950..3befdbc0c 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -656,7 +656,7 @@ enum PlayerSlots #define INVENTORY_SLOT_BAG_0 255 -enum EquipmentSlots // 19 slots +enum EquipmentSlots : uint32 // 19 slots { EQUIPMENT_SLOT_START = 0, EQUIPMENT_SLOT_HEAD = 0, @@ -1292,6 +1292,7 @@ public: InventoryResult CanUseItem(Item* pItem, bool not_loading = true) const; [[nodiscard]] bool HasItemTotemCategory(uint32 TotemCategory) const; bool IsTotemCategoryCompatiableWith(ItemTemplate const* pProto, uint32 requiredTotemCategoryId) const; + InventoryResult BotCanUseItem(ItemTemplate const* pItem) const; InventoryResult CanUseItem(ItemTemplate const* pItem) const; [[nodiscard]] InventoryResult CanUseAmmo(uint32 item) const; InventoryResult CanRollForItemInLFG(ItemTemplate const* item, WorldObject const* lootedObject) const; @@ -2050,10 +2051,13 @@ public: } bool IsMirrorTimerActive(MirrorTimerType type) { return m_MirrorTimer[type] == getMaxTimer(type); } + void SetMovement(PlayerMovementType pType); + bool CanJoinConstantChannelInZone(ChatChannelsEntry const* channel, AreaTableEntry const* zone); void JoinedChannel(Channel* c); void LeftChannel(Channel* c); + bool IsInChannel(const Channel* c); void CleanupChannels(); void ClearChannelWatch(); void UpdateLFGChannel(); @@ -2634,6 +2638,8 @@ public: void SendSystemMessage(std::string_view msg, bool escapeCharacters = false); + void ResetSpeakTimers(); + std::string GetDebugInfo() const override; bool IsExpectingChangeTransport() const { return _expectingChangeTransport; } diff --git a/src/server/game/Entities/Player/PlayerStorage.cpp b/src/server/game/Entities/Player/PlayerStorage.cpp index e8117aa58..b7932bbc3 100644 --- a/src/server/game/Entities/Player/PlayerStorage.cpp +++ b/src/server/game/Entities/Player/PlayerStorage.cpp @@ -908,6 +908,31 @@ bool Player::IsTotemCategoryCompatiableWith(ItemTemplate const* pProto, uint32 r return true; } +InventoryResult Player::BotCanUseItem(ItemTemplate const* proto) const +{ + if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass == ITEM_SUBCLASS_ARMOR_IDOL && !IsClass(CLASS_DRUID, CLASS_CONTEXT_EQUIP_RELIC)) + { + return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM; + } + + if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass == ITEM_SUBCLASS_ARMOR_TOTEM && !IsClass(CLASS_SHAMAN, CLASS_CONTEXT_EQUIP_RELIC)) + { + return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM; + } + + if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass == ITEM_SUBCLASS_ARMOR_LIBRAM && !IsClass(CLASS_PALADIN, CLASS_CONTEXT_EQUIP_RELIC)) + { + return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM; + } + + if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass == ITEM_SUBCLASS_ARMOR_SIGIL && !IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_EQUIP_RELIC)) + { + return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM; + } + + return CanUseItem(proto); +} + InventoryResult Player::CanStoreItem_InSpecificSlot(uint8 bag, uint8 slot, ItemPosCountVec& dest, ItemTemplate const* pProto, uint32& count, bool swap, Item* pSrcItem) const { Item* pItem2 = GetItemByPos(bag, slot); diff --git a/src/server/game/Entities/Player/PlayerUpdates.cpp b/src/server/game/Entities/Player/PlayerUpdates.cpp index b0c7a8521..ba8c92576 100644 --- a/src/server/game/Entities/Player/PlayerUpdates.cpp +++ b/src/server/game/Entities/Player/PlayerUpdates.cpp @@ -430,6 +430,7 @@ void Player::Update(uint32 p_time) m_delayed_unit_relocation_timer = 0; RemoveFromNotify(NOTIFY_VISIBILITY_CHANGED); } + sScriptMgr->OnPlayerAfterUpdate(this, p_time); } void Player::UpdateMirrorTimers() diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 5fb2c5925..c1249c925 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -10403,6 +10403,11 @@ ReputationRank Unit::GetFactionReactionTo(FactionTemplateEntry const* factionTem } } + return GetFactionReactionTo(factionTemplateEntry, targetFactionTemplateEntry); +} + +ReputationRank Unit::GetFactionReactionTo(FactionTemplateEntry const* factionTemplateEntry, FactionTemplateEntry const* targetFactionTemplateEntry) +{ // common faction based check if (factionTemplateEntry->IsHostileTo(*targetFactionTemplateEntry)) return REP_HOSTILE; @@ -10412,6 +10417,7 @@ ReputationRank Unit::GetFactionReactionTo(FactionTemplateEntry const* factionTem return REP_FRIENDLY; if (factionTemplateEntry->factionFlags & FACTION_TEMPLATE_FLAG_HATES_ALL_EXCEPT_FRIENDS) return REP_HOSTILE; + // neutral by default return REP_NEUTRAL; } @@ -15975,6 +15981,11 @@ void Unit::CleanupBeforeRemoveFromMap(bool finalCleanup) if (IsInWorld()) // not in world and not being removed atm RemoveFromWorld(); + // Added for mod_playerbots crash fixes; cancel and remove pending events before aura/spellmod cleanup. + // Without this SpellEvent may be cancelled later during EventProcessor destruction after auras/spellmods + // are already removed and leading to invalid access in Player::RestoreSpellMods on logout. + m_Events.KillAllEvents(false); + ASSERT(GetGUID()); // A unit may be in removelist and not in world, but it is still in grid @@ -18225,6 +18236,8 @@ void Unit::Kill(Unit* killer, Unit* victim, bool durabilityLoss, WeaponAttackTyp } } + sScriptMgr->OnPlayerbotCheckKillTask(player, victim); + // Dungeon specific stuff, only applies to players killing creatures if (creature->GetInstanceId()) { @@ -19167,11 +19180,23 @@ void Unit::SendPlaySpellVisual(uint32 id) SendMessageToSet(&data, true); } +void Unit::SendPlaySpellVisual(ObjectGuid guid, uint32 id) +{ + WorldPacket data(SMSG_PLAY_SPELL_VISUAL, 8 + 4); + data << guid; + data << uint32(id); // SpellVisualKit.dbc index + SendMessageToSet(&data, true); +} + void Unit::SendPlaySpellImpact(ObjectGuid guid, uint32 id) { WorldPacket data(SMSG_PLAY_SPELL_IMPACT, 8 + 4); data << guid; // target data << uint32(id); // SpellVisualKit.dbc index + + if (IsPlayer()) + ToPlayer()->SendDirectMessage(&data); + else SendMessageToSet(&data, true); } diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 1590c2721..7313a7562 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1851,6 +1851,7 @@ public: // Reputations system ReputationRank GetReactionTo(Unit const* target, bool checkOriginalFaction = false) const; ReputationRank GetFactionReactionTo(FactionTemplateEntry const* factionTemplateEntry, Unit const* target) const; + static ReputationRank GetFactionReactionTo(FactionTemplateEntry const* factionTemplateEntry, FactionTemplateEntry const* targetFactionTemplateEntry); // Shared vision SharedVisionList const& GetSharedVisionList() { return m_sharedVision; } @@ -2008,6 +2009,7 @@ public: void SendComboPoints(); void SendPlaySpellVisual(uint32 id); + void SendPlaySpellVisual(ObjectGuid guid, uint32 id); void SendPlaySpellImpact(ObjectGuid guid, uint32 id); void SendPetActionFeedback(uint8 msg) const; @@ -2050,6 +2052,8 @@ public: void OutDebugInfo() const; std::string GetDebugInfo() const override; + bool m_cannotReachTarget; + //----------- Public variables ----------// uint32 m_extraAttacks; DualWieldMode _dualWieldMode; diff --git a/src/server/game/Entities/Vehicle/VehicleDefines.h b/src/server/game/Entities/Vehicle/VehicleDefines.h index 1aa254581..86822d05e 100644 --- a/src/server/game/Entities/Vehicle/VehicleDefines.h +++ b/src/server/game/Entities/Vehicle/VehicleDefines.h @@ -45,6 +45,7 @@ enum VehicleFlags VEHICLE_FLAG_CUSTOM_PITCH = 0x00000040, // If set use pitchMin and pitchMax from DBC, otherwise pitchMin = -pi/2, pitchMax = pi/2 VEHICLE_FLAG_ADJUST_AIM_ANGLE = 0x00000400, // Lua_IsVehicleAimAngleAdjustable VEHICLE_FLAG_ADJUST_AIM_POWER = 0x00000800, // Lua_IsVehicleAimPowerAdjustable + VEHICLE_FLAG_FIXED_POSITION = 0x00200000 // Used for cannons, when they should be rooted (mod_playerbots, not sure this is still valid since its only used my playerbot) }; enum VehicleSpells diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index 36028c0e3..0acf11080 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -867,6 +867,7 @@ public: return nullptr; } + [[nodiscard]] AreaTriggerTeleportContainer const& GetAllAreaTriggerTeleports() const { return _areaTriggerTeleportStore; } [[nodiscard]] AreaTriggerTeleport const* GetAreaTriggerTeleport(uint32 trigger) const { AreaTriggerTeleportContainer::const_iterator itr = _areaTriggerTeleportStore.find(trigger); diff --git a/src/server/game/Groups/Group.h b/src/server/game/Groups/Group.h index c4a9b9262..b9cdb1e35 100644 --- a/src/server/game/Groups/Group.h +++ b/src/server/game/Groups/Group.h @@ -262,6 +262,9 @@ public: void SetGroupMemberFlag(ObjectGuid guid, bool apply, GroupMemberFlags flag); void RemoveUniqueGroupMemberFlag(GroupMemberFlags flag); + //mod_playerbots + ObjectGuid const GetTargetIcon(uint8 id) const { return m_targetIcons[id]; } + Difficulty GetDifficulty(bool isRaid) const; Difficulty GetDungeonDifficulty() const; Difficulty GetRaidDifficulty() const; @@ -300,6 +303,8 @@ public: bool CountRollVote(ObjectGuid playerGUID, ObjectGuid Guid, uint8 Choise); void EndRoll(Loot* loot, Map* allowedMap); + Rolls GetRolls() const { return RollId; } + // related to disenchant rolls void ResetMaxEnchantingLevel(); diff --git a/src/server/game/Guilds/Guild.cpp b/src/server/game/Guilds/Guild.cpp index d38838121..86b3cc832 100644 --- a/src/server/game/Guilds/Guild.cpp +++ b/src/server/game/Guilds/Guild.cpp @@ -44,48 +44,48 @@ std::string _GetGuildEventString(GuildEvents event) { switch (event) { - case GE_PROMOTION: - return "Member promotion"; - case GE_DEMOTION: - return "Member demotion"; - case GE_MOTD: - return "Guild MOTD"; - case GE_JOINED: - return "Member joined"; - case GE_LEFT: - return "Member left"; - case GE_REMOVED: - return "Member removed"; - case GE_LEADER_IS: - return "Leader is"; - case GE_LEADER_CHANGED: - return "Leader changed"; - case GE_DISBANDED: - return "Guild disbanded"; - case GE_TABARDCHANGE: - return "Tabard change"; - case GE_RANK_UPDATED: - return "Rank updated"; - case GE_RANK_DELETED: - return "Rank deleted"; - case GE_SIGNED_ON: - return "Member signed on"; - case GE_SIGNED_OFF: - return "Member signed off"; - case GE_GUILDBANKBAGSLOTS_CHANGED: - return "Bank bag slots changed"; - case GE_BANK_TAB_PURCHASED: - return "Bank tab purchased"; - case GE_BANK_TAB_UPDATED: - return "Bank tab updated"; - case GE_BANK_MONEY_SET: - return "Bank money set"; - case GE_BANK_TAB_AND_MONEY_UPDATED: - return "Bank and money updated"; - case GE_BANK_TEXT_CHANGED: - return "Bank tab text changed"; - default: - break; + case GE_PROMOTION: + return "Member promotion"; + case GE_DEMOTION: + return "Member demotion"; + case GE_MOTD: + return "Guild MOTD"; + case GE_JOINED: + return "Member joined"; + case GE_LEFT: + return "Member left"; + case GE_REMOVED: + return "Member removed"; + case GE_LEADER_IS: + return "Leader is"; + case GE_LEADER_CHANGED: + return "Leader changed"; + case GE_DISBANDED: + return "Guild disbanded"; + case GE_TABARDCHANGE: + return "Tabard change"; + case GE_RANK_UPDATED: + return "Rank updated"; + case GE_RANK_DELETED: + return "Rank deleted"; + case GE_SIGNED_ON: + return "Member signed on"; + case GE_SIGNED_OFF: + return "Member signed off"; + case GE_GUILDBANKBAGSLOTS_CHANGED: + return "Bank bag slots changed"; + case GE_BANK_TAB_PURCHASED: + return "Bank tab purchased"; + case GE_BANK_TAB_UPDATED: + return "Bank tab updated"; + case GE_BANK_MONEY_SET: + return "Bank money set"; + case GE_BANK_TAB_AND_MONEY_UPDATED: + return "Bank and money updated"; + case GE_BANK_TEXT_CHANGED: + return "Bank tab text changed"; + default: + break; } return ""; } @@ -94,20 +94,20 @@ inline uint32 _GetGuildBankTabPrice(uint8 tabId) { switch (tabId) { - case 0: - return sWorld->getIntConfig(CONFIG_GUILD_BANK_TAB_COST_0); - case 1: - return sWorld->getIntConfig(CONFIG_GUILD_BANK_TAB_COST_1); - case 2: - return sWorld->getIntConfig(CONFIG_GUILD_BANK_TAB_COST_2); - case 3: - return sWorld->getIntConfig(CONFIG_GUILD_BANK_TAB_COST_3); - case 4: - return sWorld->getIntConfig(CONFIG_GUILD_BANK_TAB_COST_4); - case 5: - return sWorld->getIntConfig(CONFIG_GUILD_BANK_TAB_COST_5); - default: - return 0; + case 0: + return sWorld->getIntConfig(CONFIG_GUILD_BANK_TAB_COST_0); + case 1: + return sWorld->getIntConfig(CONFIG_GUILD_BANK_TAB_COST_1); + case 2: + return sWorld->getIntConfig(CONFIG_GUILD_BANK_TAB_COST_2); + case 3: + return sWorld->getIntConfig(CONFIG_GUILD_BANK_TAB_COST_3); + case 4: + return sWorld->getIntConfig(CONFIG_GUILD_BANK_TAB_COST_4); + case 5: + return sWorld->getIntConfig(CONFIG_GUILD_BANK_TAB_COST_5); + default: + return 0; } } @@ -134,8 +134,9 @@ void Guild::SendSaveEmblemResult(WorldSession* session, GuildEmblemError errCode // LogHolder template Guild::LogHolder::LogHolder() - : m_maxRecords(sWorld->getIntConfig(std::is_same_v ? CONFIG_GUILD_BANK_EVENT_LOG_COUNT : CONFIG_GUILD_EVENT_LOG_COUNT)), m_nextGUID(uint32(GUILD_EVENT_LOG_GUID_UNDEFINED)) -{ } + : m_maxRecords(sWorld->getIntConfig(std::is_same_v ? CONFIG_GUILD_BANK_EVENT_LOG_COUNT : CONFIG_GUILD_EVENT_LOG_COUNT)), m_nextGUID(uint32(GUILD_EVENT_LOG_GUID_UNDEFINED)) +{ +} template template void Guild::LogHolder::LoadEvent(Ts&&... args) @@ -173,7 +174,8 @@ inline uint32 Guild::LogHolder::GetNextGUID() } Guild::LogEntry::LogEntry(uint32 guildId, ObjectGuid::LowType guid) : - m_guildId(guildId), m_guid(guid), m_timestamp(GameTime::GetGameTime().count()) { } + m_guildId(guildId), m_guid(guid), m_timestamp(GameTime::GetGameTime().count()) { +} // EventLogEntry void Guild::EventLogEntry::SaveToDB(CharacterDatabaseTransaction trans) const @@ -185,12 +187,12 @@ void Guild::EventLogEntry::SaveToDB(CharacterDatabaseTransaction trans) const uint8 index = 0; stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GUILD_EVENTLOG); - stmt->SetData( index, m_guildId); + stmt->SetData(index, m_guildId); stmt->SetData(++index, m_guid); - stmt->SetData (++index, uint8(m_eventType)); + stmt->SetData(++index, uint8(m_eventType)); stmt->SetData(++index, m_playerGuid1.GetCounter()); stmt->SetData(++index, m_playerGuid2.GetCounter()); - stmt->SetData (++index, m_newRank); + stmt->SetData(++index, m_newRank); stmt->SetData(++index, m_timestamp); CharacterDatabase.ExecuteOrAppend(trans, stmt); } @@ -215,21 +217,21 @@ void Guild::BankEventLogEntry::SaveToDB(CharacterDatabaseTransaction trans) cons uint8 index = 0; CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_BANK_EVENTLOG); - stmt->SetData( index, m_guildId); + stmt->SetData(index, m_guildId); stmt->SetData(++index, m_guid); - stmt->SetData (++index, m_bankTabId); + stmt->SetData(++index, m_bankTabId); CharacterDatabase.ExecuteOrAppend(trans, stmt); index = 0; stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GUILD_BANK_EVENTLOG); - stmt->SetData( index, m_guildId); + stmt->SetData(index, m_guildId); stmt->SetData(++index, m_guid); - stmt->SetData (++index, m_bankTabId); - stmt->SetData (++index, uint8(m_eventType)); + stmt->SetData(++index, m_bankTabId); + stmt->SetData(++index, uint8(m_eventType)); stmt->SetData(++index, m_playerGuid.GetCounter()); stmt->SetData(++index, m_itemOrMoney); stmt->SetData(++index, m_itemStackCount); - stmt->SetData (++index, m_destTabId); + stmt->SetData(++index, m_destTabId); stmt->SetData(++index, m_timestamp); CharacterDatabase.ExecuteOrAppend(trans, stmt); } @@ -243,19 +245,19 @@ void Guild::BankEventLogEntry::WritePacket(WorldPackets::Guild::GuildBankLogQuer switch (m_eventType) { - case GUILD_BANK_LOG_DEPOSIT_ITEM: - case GUILD_BANK_LOG_WITHDRAW_ITEM: - bankLogEntry.ItemID = int32(m_itemOrMoney); - bankLogEntry.Count = int32(m_itemStackCount); - break; - case GUILD_BANK_LOG_MOVE_ITEM: - case GUILD_BANK_LOG_MOVE_ITEM2: - bankLogEntry.ItemID = int32(m_itemOrMoney); - bankLogEntry.Count = int32(m_itemStackCount); - bankLogEntry.OtherTab = int8(m_destTabId); - break; - default: - bankLogEntry.Money = uint32(m_itemOrMoney); + case GUILD_BANK_LOG_DEPOSIT_ITEM: + case GUILD_BANK_LOG_WITHDRAW_ITEM: + bankLogEntry.ItemID = int32(m_itemOrMoney); + bankLogEntry.Count = int32(m_itemStackCount); + break; + case GUILD_BANK_LOG_MOVE_ITEM: + case GUILD_BANK_LOG_MOVE_ITEM2: + bankLogEntry.ItemID = int32(m_itemOrMoney); + bankLogEntry.Count = int32(m_itemStackCount); + bankLogEntry.OtherTab = int8(m_destTabId); + break; + default: + bankLogEntry.Money = uint32(m_itemOrMoney); } packet.Entry.push_back(bankLogEntry); @@ -264,10 +266,10 @@ void Guild::BankEventLogEntry::WritePacket(WorldPackets::Guild::GuildBankLogQuer // RankInfo void Guild::RankInfo::LoadFromDB(Field* fields) { - m_rankId = fields[1].Get(); - m_name = fields[2].Get(); - m_rights = fields[3].Get(); - m_bankMoneyPerDay = fields[4].Get(); + m_rankId = fields[1].Get(); + m_name = fields[2].Get(); + m_rights = fields[3].Get(); + m_bankMoneyPerDay = fields[4].Get(); if (m_rankId == GR_GUILDMASTER) // Prevent loss of leader rights m_rights |= GR_RIGHT_ALL; } @@ -276,7 +278,7 @@ void Guild::RankInfo::SaveToDB(CharacterDatabaseTransaction trans) const { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GUILD_RANK); stmt->SetData(0, m_guildId); - stmt->SetData (1, m_rankId); + stmt->SetData(1, m_rankId); stmt->SetData(2, m_name); stmt->SetData(3, m_rights); stmt->SetData(4, m_bankMoneyPerDay); @@ -317,7 +319,7 @@ void Guild::RankInfo::SetName(std::string_view name) CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GUILD_RANK_NAME); stmt->SetData(0, m_name); - stmt->SetData (1, m_rankId); + stmt->SetData(1, m_rankId); stmt->SetData(2, m_guildId); CharacterDatabase.Execute(stmt); } @@ -334,7 +336,7 @@ void Guild::RankInfo::SetRights(uint32 rights) CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GUILD_RANK_RIGHTS); stmt->SetData(0, m_rights); - stmt->SetData (1, m_rankId); + stmt->SetData(1, m_rankId); stmt->SetData(2, m_guildId); CharacterDatabase.Execute(stmt); } @@ -351,7 +353,7 @@ void Guild::RankInfo::SetBankMoneyPerDay(uint32 money) CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GUILD_RANK_BANK_MONEY); stmt->SetData(0, money); - stmt->SetData (1, m_rankId); + stmt->SetData(1, m_rankId); stmt->SetData(2, m_guildId); CharacterDatabase.Execute(stmt); } @@ -368,9 +370,9 @@ void Guild::RankInfo::SetBankTabSlotsAndRights(GuildBankRightsAndSlots rightsAnd { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GUILD_BANK_RIGHT); stmt->SetData(0, m_guildId); - stmt->SetData (1, guildBR.GetTabId()); - stmt->SetData (2, m_rankId); - stmt->SetData (3, guildBR.GetRights()); + stmt->SetData(1, guildBR.GetTabId()); + stmt->SetData(2, m_rankId); + stmt->SetData(3, guildBR.GetRights()); stmt->SetData(4, guildBR.GetSlots()); CharacterDatabase.Execute(stmt); } @@ -409,8 +411,8 @@ bool Guild::BankTab::LoadItemFromDB(Field* fields) CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_NONEXISTENT_GUILD_BANK_ITEM); stmt->SetData(0, m_guildId); - stmt->SetData (1, m_tabId); - stmt->SetData (2, slotId); + stmt->SetData(1, m_tabId); + stmt->SetData(2, slotId); CharacterDatabase.Execute(stmt); delete pItem; @@ -448,7 +450,7 @@ void Guild::BankTab::SetInfo(std::string_view name, std::string_view icon) stmt->SetData(0, m_name); stmt->SetData(1, m_icon); stmt->SetData(2, m_guildId); - stmt->SetData (3, m_tabId); + stmt->SetData(3, m_tabId); CharacterDatabase.Execute(stmt); } @@ -463,7 +465,7 @@ void Guild::BankTab::SetText(std::string_view text) CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GUILD_BANK_TAB_TEXT); stmt->SetData(0, m_text); stmt->SetData(1, m_guildId); - stmt->SetData (2, m_tabId); + stmt->SetData(2, m_tabId); CharacterDatabase.Execute(stmt); } @@ -478,16 +480,16 @@ bool Guild::BankTab::SetItem(CharacterDatabaseTransaction trans, uint8 slotId, I CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_BANK_ITEM); stmt->SetData(0, m_guildId); - stmt->SetData (1, m_tabId); - stmt->SetData (2, slotId); + stmt->SetData(1, m_tabId); + stmt->SetData(2, slotId); CharacterDatabase.ExecuteOrAppend(trans, stmt); if (item) { stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GUILD_BANK_ITEM); stmt->SetData(0, m_guildId); - stmt->SetData (1, m_tabId); - stmt->SetData (2, slotId); + stmt->SetData(1, m_tabId); + stmt->SetData(2, slotId); stmt->SetData(3, item->GetGUID().GetCounter()); CharacterDatabase.ExecuteOrAppend(trans, stmt); @@ -508,7 +510,7 @@ void Guild::BankTab::SendText(Guild const* guild, WorldSession* session) const if (session) { LOG_DEBUG("guild", "MSG_QUERY_GUILD_BANK_TEXT [{}]: Tabid: {}, Text: {}" - , session->GetPlayerInfo(), m_tabId, m_text); + , session->GetPlayerInfo(), m_tabId, m_text); session->SendPacket(textQuery.Write()); } else @@ -521,21 +523,21 @@ void Guild::BankTab::SendText(Guild const* guild, WorldSession* session) const // Member void Guild::Member::SetStats(Player* player) { - m_name = player->GetName(); - m_level = player->GetLevel(); - m_class = player->getClass(); - m_gender = player->getGender(); - m_zoneId = player->GetZoneId(); + m_name = player->GetName(); + m_level = player->GetLevel(); + m_class = player->getClass(); + m_gender = player->getGender(); + m_zoneId = player->GetZoneId(); m_accountId = player->GetSession()->GetAccountId(); } void Guild::Member::SetStats(std::string_view name, uint8 level, uint8 _class, uint8 gender, uint32 zoneId, uint32 accountId) { - m_name = name; - m_level = level; - m_class = _class; - m_gender = gender; - m_zoneId = zoneId; + m_name = name; + m_level = level; + m_class = _class; + m_gender = gender; + m_zoneId = zoneId; m_accountId = accountId; } @@ -574,7 +576,7 @@ void Guild::Member::ChangeRank(uint8 newRank) player->SetRank(newRank); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GUILD_MEMBER_RANK); - stmt->SetData (0, newRank); + stmt->SetData(0, newRank); stmt->SetData(1, m_guid.GetCounter()); CharacterDatabase.Execute(stmt); } @@ -589,7 +591,7 @@ void Guild::Member::SaveToDB(CharacterDatabaseTransaction trans) const CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GUILD_MEMBER); stmt->SetData(0, m_guildId); stmt->SetData(1, m_guid.GetCounter()); - stmt->SetData (2, m_rankId); + stmt->SetData(2, m_rankId); stmt->SetData(3, m_publicNote); stmt->SetData(4, m_officerNote); CharacterDatabase.ExecuteOrAppend(trans, stmt); @@ -600,18 +602,18 @@ void Guild::Member::SaveToDB(CharacterDatabaseTransaction trans) const // In this case member has to be removed from guild. bool Guild::Member::LoadFromDB(Field* fields) { - m_publicNote = fields[3].Get(); + m_publicNote = fields[3].Get(); m_officerNote = fields[4].Get(); for (uint8 i = 0; i <= GUILD_BANK_MAX_TABS; ++i) m_bankWithdraw[i] = fields[5 + i].Get(); SetStats(fields[12].Get(), - fields[13].Get(), // characters.level - fields[14].Get(), // characters.class - fields[15].Get(), // characters.gender - fields[16].Get(), // characters.zone - fields[17].Get()); // characters.account + fields[13].Get(), // characters.level + fields[14].Get(), // characters.class + fields[15].Get(), // characters.gender + fields[16].Get(), // characters.zone + fields[17].Get()); // characters.account m_logoutTime = fields[18].Get(); // characters.logout_time if (!CheckStats()) @@ -691,11 +693,11 @@ void EmblemInfo::ReadPacket(WorldPackets::Guild::SaveGuildEmblem& packet) void EmblemInfo::LoadFromDB(Field* fields) { - m_style = fields[3].Get(); - m_color = fields[4].Get(); - m_borderStyle = fields[5].Get(); - m_borderColor = fields[6].Get(); - m_backgroundColor = fields[7].Get(); + m_style = fields[3].Get(); + m_color = fields[4].Get(); + m_borderStyle = fields[5].Get(); + m_borderColor = fields[6].Get(); + m_backgroundColor = fields[7].Get(); } void EmblemInfo::SaveToDB(uint32 guildId) const @@ -747,8 +749,8 @@ void Guild::MoveItemData::LogAction(MoveItemData* pFrom) const ASSERT(pFrom->GetItem()); sScriptMgr->OnGuildItemMove(m_pGuild, m_pPlayer, pFrom->GetItem(), - pFrom->IsBank(), pFrom->GetContainer(), pFrom->GetSlotId(), - IsBank(), GetContainer(), GetSlotId()); + pFrom->IsBank(), pFrom->GetContainer(), pFrom->GetSlotId(), + IsBank(), GetContainer(), GetSlotId()); } inline void Guild::MoveItemData::CopySlots(SlotIds& ids) const @@ -808,7 +810,7 @@ void Guild::PlayerMoveItemData::LogBankEvent(CharacterDatabaseTransaction trans, ASSERT(pFrom); // Bank -> Char m_pGuild->_LogBankEvent(trans, GUILD_BANK_LOG_WITHDRAW_ITEM, pFrom->GetContainer(), m_pPlayer->GetGUID(), - pFrom->GetItem()->GetEntry(), count); + pFrom->GetItem()->GetEntry(), count); } inline InventoryResult Guild::PlayerMoveItemData::CanStore(Item* pItem, bool swap) @@ -829,7 +831,7 @@ bool Guild::BankMoveItemData::HasStoreRights(MoveItemData* pOther) const // Do not check rights if item is being swapped within the same bank tab if (pOther->IsBank() && pOther->GetContainer() == m_container) return true; - return m_pGuild->_MemberHasTabRights(m_pPlayer->GetGUID(), m_container, GUILD_BANK_RIGHT_DEPOSIT_ITEM); + return m_pGuild->MemberHasTabRights(m_pPlayer->GetGUID(), m_container, GUILD_BANK_RIGHT_DEPOSIT_ITEM); } bool Guild::BankMoveItemData::HasWithdrawRights(MoveItemData* pOther) const @@ -881,7 +883,7 @@ Item* Guild::BankMoveItemData::StoreItem(CharacterDatabaseTransaction trans, Ite ++itr; LOG_DEBUG("guild", "GUILD STORAGE: StoreItem tab = {}, slot = {}, item = {}, count = {}", - m_container, m_slotId, pItem->GetEntry(), pItem->GetCount()); + m_container, m_slotId, pItem->GetEntry(), pItem->GetCount()); pLastItem = _StoreItem(trans, pTab, pItem, pos, itr != m_vec.end()); } return pLastItem; @@ -893,11 +895,11 @@ void Guild::BankMoveItemData::LogBankEvent(CharacterDatabaseTransaction trans, M if (pFrom->IsBank()) // Bank -> Bank m_pGuild->_LogBankEvent(trans, GUILD_BANK_LOG_MOVE_ITEM, pFrom->GetContainer(), m_pPlayer->GetGUID(), - pFrom->GetItem()->GetEntry(), count, m_container); + pFrom->GetItem()->GetEntry(), count, m_container); else // Char -> Bank m_pGuild->_LogBankEvent(trans, GUILD_BANK_LOG_DEPOSIT_ITEM, m_container, m_pPlayer->GetGUID(), - pFrom->GetItem()->GetEntry(), count); + pFrom->GetItem()->GetEntry(), count); } void Guild::BankMoveItemData::LogAction(MoveItemData* pFrom) const @@ -984,7 +986,7 @@ void Guild::BankMoveItemData::CanStoreItemInTab(Item* pItem, uint8 skipSlotId, b InventoryResult Guild::BankMoveItemData::CanStore(Item* pItem, bool swap) { LOG_DEBUG("guild", "GUILD STORAGE: CanStore() tab = {}, slot = {}, item = {}, count = {}", - m_container, m_slotId, pItem->GetEntry(), pItem->GetCount()); + m_container, m_slotId, pItem->GetEntry(), pItem->GetCount()); uint32 count = pItem->GetCount(); // Soulbound items cannot be moved if (pItem->IsSoulBound()) @@ -1031,7 +1033,7 @@ InventoryResult Guild::BankMoveItemData::CanStore(Item* pItem, bool swap) } // Guild -Guild::Guild(): +Guild::Guild() : m_id(0), m_createdDate(0), m_accountsNumber(0), @@ -1065,7 +1067,7 @@ bool Guild::Create(Player* pLeader, std::string_view name) m_createdDate = GameTime::GetGameTime().count(); LOG_DEBUG("guild", "GUILD: creating guild [{}] for leader {} ({})", - m_name, pLeader->GetName(), m_leaderGuid.ToString()); + m_name, pLeader->GetName(), m_leaderGuid.ToString()); CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); @@ -1075,7 +1077,7 @@ bool Guild::Create(Player* pLeader, std::string_view name) uint8 index = 0; stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GUILD); - stmt->SetData( index, m_id); + stmt->SetData(index, m_id); stmt->SetData(++index, m_name); stmt->SetData(++index, m_leaderGuid.GetCounter()); stmt->SetData(++index, m_info); @@ -1161,15 +1163,15 @@ void Guild::UpdateMemberData(Player* player, uint8 dataid, uint32 value) { switch (dataid) { - case GUILD_MEMBER_DATA_ZONEID: - member->SetZoneID(value); - break; - case GUILD_MEMBER_DATA_LEVEL: - member->SetLevel(value); - break; - default: - LOG_ERROR("guild", "Guild::UpdateMemberData: Called with incorrect DATAID {} (value {})", dataid, value); - return; + case GUILD_MEMBER_DATA_ZONEID: + member->SetZoneID(value); + break; + case GUILD_MEMBER_DATA_LEVEL: + member->SetLevel(value); + break; + default: + LOG_ERROR("guild", "Guild::UpdateMemberData: Called with incorrect DATAID {} (value {})", dataid, value); + return; } //HandleRoster(); } @@ -1207,7 +1209,7 @@ void Guild::HandleRoster(WorldSession* session) roster.RankData.reserve(m_ranks.size()); for (RankInfo const& rank : m_ranks) { - WorldPackets::Guild::GuildRankData& rankData = roster.RankData.emplace_back(); + WorldPackets::Guild::GuildRankData& rankData = roster.RankData.emplace_back(); rankData.Flags = rank.GetRights(); rankData.WithdrawGoldLimit = rank.GetBankMoneyPerDay(); @@ -1218,7 +1220,7 @@ void Guild::HandleRoster(WorldSession* session) } } - bool sendOfficerNote = _HasRankRight(session->GetPlayer(), GR_RIGHT_VIEWOFFNOTE); + bool sendOfficerNote = HasRankRight(session->GetPlayer(), GR_RIGHT_VIEWOFFNOTE); roster.MemberData.reserve(m_members.size()); for (auto const& [guid, member] : m_members) { @@ -1275,7 +1277,7 @@ void Guild::HandleSetMOTD(WorldSession* session, std::string_view motd) return; // Player must have rights to set MOTD - if (!_HasRankRight(session->GetPlayer(), GR_RIGHT_SETMOTD)) + if (!HasRankRight(session->GetPlayer(), GR_RIGHT_SETMOTD)) SendCommandResult(session, GUILD_COMMAND_EDIT_MOTD, ERR_GUILD_PERMISSIONS); else { @@ -1298,7 +1300,7 @@ void Guild::HandleSetInfo(WorldSession* session, std::string_view info) return; // Player must have rights to set guild's info - if (_HasRankRight(session->GetPlayer(), GR_RIGHT_MODIFY_GUILD_INFO)) + if (HasRankRight(session->GetPlayer(), GR_RIGHT_MODIFY_GUILD_INFO)) { m_info = info; @@ -1331,6 +1333,12 @@ void Guild::HandleSetEmblem(WorldSession* session, const EmblemInfo& emblemInfo) } } +void Guild::HandleSetEmblem(EmblemInfo const& emblemInfo) +{ + m_emblemInfo = emblemInfo; + m_emblemInfo.SaveToDB(m_id); +} + void Guild::HandleSetLeader(WorldSession* session, std::string_view name) { Player* player = session->GetPlayer(); @@ -1356,7 +1364,7 @@ void Guild::HandleSetBankTabInfo(WorldSession* session, uint8 tabId, std::string if (!tab) { LOG_ERROR("guild", "Guild::HandleSetBankTabInfo: Player {} trying to change bank tab info from unexisting tab {}.", - session->GetPlayerInfo(), tabId); + session->GetPlayerInfo(), tabId); return; } @@ -1367,7 +1375,7 @@ void Guild::HandleSetBankTabInfo(WorldSession* session, uint8 tabId, std::string void Guild::HandleSetMemberNote(WorldSession* session, std::string_view name, std::string_view note, bool isPublic) { // Player must have rights to set public/officer note - if (!_HasRankRight(session->GetPlayer(), isPublic ? GR_RIGHT_EPNOTE : GR_RIGHT_EOFFNOTE)) + if (!HasRankRight(session->GetPlayer(), isPublic ? GR_RIGHT_EPNOTE : GR_RIGHT_EOFFNOTE)) SendCommandResult(session, GUILD_COMMAND_PUBLIC_NOTE, ERR_GUILD_PERMISSIONS); else if (Member* member = GetMember(name)) { @@ -1400,6 +1408,29 @@ void Guild::HandleSetRankInfo(WorldSession* session, uint8 rankId, std::string_v } } +void Guild::HandleSetRankInfo(uint8 rankId, uint32 rights, std::string_view name, uint32 moneyPerDay) +{ + if (RankInfo* rankInfo = GetRankInfo(rankId)) + { + if (!name.empty()) + { + rankInfo->SetName(name); + } + + if (rights > 0) + { + rankInfo->SetRights(rights); + } + + if (moneyPerDay > 0) + { + _SetRankBankMoneyPerDay(rankId, moneyPerDay); + } + + _BroadcastEvent(GE_RANK_UPDATED, ObjectGuid::Empty, std::to_string(rankId), rankInfo->GetName(), std::to_string(m_ranks.size())); + } +} + void Guild::HandleBuyBankTab(WorldSession* session, uint8 tabId) { Player* player = session->GetPlayer(); @@ -1470,7 +1501,7 @@ void Guild::HandleInviteMember(WorldSession* session, std::string const& name) return; } // Inviting player must have rights to invite - if (!_HasRankRight(player, GR_RIGHT_INVITE)) + if (!HasRankRight(player, GR_RIGHT_INVITE)) { SendCommandResult(session, GUILD_COMMAND_INVITE, ERR_GUILD_PERMISSIONS); return; @@ -1541,7 +1572,7 @@ void Guild::HandleRemoveMember(WorldSession* session, std::string_view name) { Player* player = session->GetPlayer(); // Player must have rights to remove members - if (!_HasRankRight(player, GR_RIGHT_REMOVE)) + if (!HasRankRight(player, GR_RIGHT_REMOVE)) SendCommandResult(session, GUILD_COMMAND_REMOVE, ERR_GUILD_PERMISSIONS); else if (Member* member = GetMember(name)) { @@ -1574,7 +1605,7 @@ void Guild::HandleUpdateMemberRank(WorldSession* session, std::string_view name, Player* player = session->GetPlayer(); GuildCommandType type = demote ? GUILD_COMMAND_DEMOTE : GUILD_COMMAND_PROMOTE; // Player must have rights to promote - if (!_HasRankRight(player, demote ? GR_RIGHT_DEMOTE : GR_RIGHT_PROMOTE)) + if (!HasRankRight(player, demote ? GR_RIGHT_DEMOTE : GR_RIGHT_PROMOTE)) SendCommandResult(session, type, ERR_GUILD_PERMISSIONS); // Promoted player must be a member of guild else if (Member* member = GetMember(name)) @@ -1708,7 +1739,7 @@ bool Guild::HandleMemberWithdrawMoney(WorldSession* session, uint32 amount, bool if (uint32(_GetMemberRemainingMoney(*member)) < amount) // Check if we have enough slot/money today return false; - if (!(_GetRankRights(member->GetRankId()) & GR_RIGHT_WITHDRAW_REPAIR) && repair) + if (!(GetRankRights(member->GetRankId()) & GR_RIGHT_WITHDRAW_REPAIR) && repair) return false; // Call script after validation and before money transfer. @@ -1850,7 +1881,7 @@ void Guild::SendPermissions(WorldSession* session) WorldPackets::Guild::GuildPermissionsQueryResults queryResult; queryResult.RankID = rankId; queryResult.WithdrawGoldLimit = _GetRankBankMoneyPerDay(rankId); - queryResult.Flags = _GetRankRights(rankId); + queryResult.Flags = GetRankRights(rankId); queryResult.NumTabs = _GetPurchasedTabsSize(); for (uint8 tabId = 0; tabId < GUILD_BANK_MAX_TABS; ++tabId) @@ -1902,14 +1933,14 @@ void Guild::SendLoginInfo(WorldSession* session) // Loading methods bool Guild::LoadFromDB(Field* fields) { - m_id = fields[0].Get(); - m_name = fields[1].Get(); - m_leaderGuid = ObjectGuid::Create(fields[2].Get()); + m_id = fields[0].Get(); + m_name = fields[1].Get(); + m_leaderGuid = ObjectGuid::Create(fields[2].Get()); m_emblemInfo.LoadFromDB(fields); - m_info = fields[8].Get(); - m_motd = fields[9].Get(); - m_createdDate = time_t(fields[10].Get()); - m_bankMoney = fields[11].Get(); + m_info = fields[8].Get(); + m_motd = fields[9].Get(); + m_createdDate = time_t(fields[10].Get()); + m_bankMoney = fields[11].Get(); uint8 purchasedTabs = uint8(fields[12].Get()); if (purchasedTabs > GUILD_BANK_MAX_TABS) @@ -1968,13 +1999,13 @@ bool Guild::LoadEventLogFromDB(Field* fields) if (m_eventLog.CanInsert()) { m_eventLog.LoadEvent( - m_id, // guild id - fields[1].Get(), // guid - time_t(fields[6].Get()), // timestamp - GuildEventLogTypes(fields[2].Get()), // event type - ObjectGuid::Create(fields[3].Get()), // player guid 1 - ObjectGuid::Create(fields[4].Get()), // player guid 2 - fields[5].Get()); // rank + m_id, // guild id + fields[1].Get(), // guid + time_t(fields[6].Get()), // timestamp + GuildEventLogTypes(fields[2].Get()), // event type + ObjectGuid::Create(fields[3].Get()), // player guid 1 + ObjectGuid::Create(fields[4].Get()), // player guid 2 + fields[5].Get()); // rank return true; } return false; @@ -2006,15 +2037,15 @@ bool Guild::LoadBankEventLogFromDB(Field* fields) return false; } bankLog.LoadEvent( - m_id, // guild id - guid, // guid - time_t(fields[8].Get()), // timestamp - dbTabId, // tab id - eventType, // event type - ObjectGuid::Create(fields[4].Get()), // player guid - fields[5].Get(), // item or money - fields[6].Get(), // itam stack count - fields[7].Get()); // dest tab id + m_id, // guild id + guid, // guid + time_t(fields[8].Get()), // timestamp + dbTabId, // tab id + eventType, // event type + ObjectGuid::Create(fields[4].Get()), // player guid + fields[5].Get(), // item or money + fields[6].Get(), // itam stack count + fields[7].Get()); // dest tab id } } return true; @@ -2035,7 +2066,7 @@ bool Guild::LoadBankItemFromDB(Field* fields) if (tabId >= _GetPurchasedTabsSize()) { LOG_ERROR("guild", "Invalid tab for item (GUID: {}, id: #{}) in guild bank, skipped.", - fields[14].Get(), fields[15].Get()); + fields[14].Get(), fields[15].Get()); return false; } return m_bankTabs[tabId].LoadItemFromDB(fields); @@ -2116,13 +2147,13 @@ bool Guild::Validate() // Broadcasts void Guild::BroadcastToGuild(WorldSession* session, bool officerOnly, std::string_view msg, uint32 language) const { - if (session && session->GetPlayer() && _HasRankRight(session->GetPlayer(), officerOnly ? GR_RIGHT_OFFCHATSPEAK : GR_RIGHT_GCHATSPEAK)) + if (session && session->GetPlayer() && HasRankRight(session->GetPlayer(), officerOnly ? GR_RIGHT_OFFCHATSPEAK : GR_RIGHT_GCHATSPEAK)) { WorldPacket data; ChatHandler::BuildChatPacket(data, officerOnly ? CHAT_MSG_OFFICER : CHAT_MSG_GUILD, Language(language), session->GetPlayer(), nullptr, msg); for (auto const& [guid, member] : m_members) if (Player* player = member.FindPlayer()) - if (_HasRankRight(player, officerOnly ? GR_RIGHT_OFFCHATLISTEN : GR_RIGHT_GCHATLISTEN) && !player->GetSocial()->HasIgnore(session->GetPlayer()->GetGUID())) + if (HasRankRight(player, officerOnly ? GR_RIGHT_OFFCHATLISTEN : GR_RIGHT_GCHATLISTEN) && !player->GetSocial()->HasIgnore(session->GetPlayer()->GetGUID())) player->SendDirectMessage(&data); } } @@ -2177,11 +2208,19 @@ void Guild::MassInviteToEvent(WorldSession* session, uint32 minLevel, uint32 max // Members handling bool Guild::AddMember(ObjectGuid guid, uint8 rankId) { + Player* leader = nullptr; + if (this->GetLeaderGUID()) + { + leader = ObjectAccessor::FindConnectedPlayer(this->GetLeaderGUID()); + } + Player* player = ObjectAccessor::FindConnectedPlayer(guid); + // Player cannot be in guild if (player) { - if (player->GetGuildId() != 0) + if (player->GetGuildId() != 0 || + (!sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD) && (leader && leader->GetTeamId() != player->GetTeamId()))) return false; } else if (sCharacterCache->GetCharacterGuildIdByGuid(guid) != 0) @@ -2346,7 +2385,7 @@ bool Guild::ChangeMemberRank(ObjectGuid guid, uint8 newRank) void Guild::SwapItems(Player* player, uint8 tabId, uint8 slotId, uint8 destTabId, uint8 destSlotId, uint32 splitedAmount) { if (tabId >= _GetPurchasedTabsSize() || slotId >= GUILD_BANK_MAX_SLOTS || - destTabId >= _GetPurchasedTabsSize() || destSlotId >= GUILD_BANK_MAX_SLOTS) + destTabId >= _GetPurchasedTabsSize() || destSlotId >= GUILD_BANK_MAX_SLOTS) return; if (tabId == destTabId && slotId == destSlotId) @@ -2390,12 +2429,12 @@ void Guild::_CreateNewBankTab() CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_BANK_TAB); stmt->SetData(0, m_id); - stmt->SetData (1, tabId); + stmt->SetData(1, tabId); trans->Append(stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GUILD_BANK_TAB); stmt->SetData(0, m_id); - stmt->SetData (1, tabId); + stmt->SetData(1, tabId); trans->Append(stmt); ++tabId; @@ -2415,10 +2454,10 @@ void Guild::_CreateDefaultGuildRanks(LocaleConstant loc) stmt->SetData(0, m_id); CharacterDatabase.Execute(stmt); - _CreateRank(sObjectMgr->GetAcoreString(LANG_GUILD_MASTER, loc), GR_RIGHT_ALL); - _CreateRank(sObjectMgr->GetAcoreString(LANG_GUILD_OFFICER, loc), GR_RIGHT_ALL); - _CreateRank(sObjectMgr->GetAcoreString(LANG_GUILD_VETERAN, loc), GR_RIGHT_GCHATLISTEN | GR_RIGHT_GCHATSPEAK); - _CreateRank(sObjectMgr->GetAcoreString(LANG_GUILD_MEMBER, loc), GR_RIGHT_GCHATLISTEN | GR_RIGHT_GCHATSPEAK); + _CreateRank(sObjectMgr->GetAcoreString(LANG_GUILD_MASTER, loc), GR_RIGHT_ALL); + _CreateRank(sObjectMgr->GetAcoreString(LANG_GUILD_OFFICER, loc), GR_RIGHT_ALL); + _CreateRank(sObjectMgr->GetAcoreString(LANG_GUILD_VETERAN, loc), GR_RIGHT_GCHATLISTEN | GR_RIGHT_GCHATSPEAK); + _CreateRank(sObjectMgr->GetAcoreString(LANG_GUILD_MEMBER, loc), GR_RIGHT_GCHATLISTEN | GR_RIGHT_GCHATSPEAK); _CreateRank(sObjectMgr->GetAcoreString(LANG_GUILD_INITIATE, loc), GR_RIGHT_GCHATLISTEN | GR_RIGHT_GCHATSPEAK); } @@ -2525,7 +2564,7 @@ inline std::string Guild::_GetRankName(uint8 rankId) const return ""; } -inline uint32 Guild::_GetRankRights(uint8 rankId) const +uint32 Guild::GetRankRights(uint8 rankId) const { if (const RankInfo* rankInfo = GetRankInfo(rankId)) return rankInfo->GetRights(); @@ -2574,7 +2613,7 @@ inline int32 Guild::_GetMemberRemainingMoney(Member const& member) const if (rankId == GR_GUILDMASTER) return static_cast(GUILD_WITHDRAW_MONEY_UNLIMITED); - if ((_GetRankRights(rankId) & (GR_RIGHT_WITHDRAW_REPAIR | GR_RIGHT_WITHDRAW_GOLD)) != 0) + if ((GetRankRights(rankId) & (GR_RIGHT_WITHDRAW_REPAIR | GR_RIGHT_WITHDRAW_GOLD)) != 0) { int32 remaining = _GetRankBankMoneyPerDay(rankId) - member.GetBankWithdrawValue(GUILD_BANK_MAX_TABS); if (remaining > 0) @@ -2589,12 +2628,12 @@ inline void Guild::_UpdateMemberWithdrawSlots(CharacterDatabaseTransaction trans { uint8 rankId = member->GetRankId(); if (rankId != GR_GUILDMASTER - && member->GetBankWithdrawValue(tabId) < _GetRankBankTabSlotsPerDay(rankId, tabId)) + && member->GetBankWithdrawValue(tabId) < _GetRankBankTabSlotsPerDay(rankId, tabId)) member->UpdateBankWithdrawValue(trans, tabId, 1); } } -inline bool Guild::_MemberHasTabRights(ObjectGuid guid, uint8 tabId, uint32 rights) const +bool Guild::MemberHasTabRights(ObjectGuid guid, uint8 tabId, uint32 rights) const { if (const Member* member = GetMember(guid)) { @@ -2606,6 +2645,19 @@ inline bool Guild::_MemberHasTabRights(ObjectGuid guid, uint8 tabId, uint32 righ return false; } +bool Guild::HasRankRight(Player* player, uint32 right) const +{ + if (player) + { + if (Member const* member = GetMember(player->GetGUID())) + { + return (GetRankRights(member->GetRankId()) & right) != GR_RIGHT_EMPTY; + } + } + + return false; +} + // Add new event log record inline void Guild::_LogEvent(GuildEventLogTypes eventType, ObjectGuid playerGuid1, ObjectGuid playerGuid2, uint8 newRank) { @@ -2751,7 +2803,7 @@ bool Guild::_DoItemsMove(MoveItemData* pSrc, MoveItemData* pDest, bool sendError void Guild::_SendBankContent(WorldSession* session, uint8 tabId, bool sendAllSlots) const { ObjectGuid guid = session->GetPlayer()->GetGUID(); - if (!_MemberHasTabRights(guid, tabId, GUILD_BANK_RIGHT_VIEW_TAB)) + if (!MemberHasTabRights(guid, tabId, GUILD_BANK_RIGHT_VIEW_TAB)) return; _SendBankList(session, tabId, sendAllSlots); @@ -2800,7 +2852,7 @@ void Guild::_SendBankContentUpdate(uint8 tabId, SlotIds slots) const } void Guild::_BroadcastEvent(GuildEvents guildEvent, ObjectGuid guid, - Optional param1 /*= {}*/, Optional param2 /*= {}*/, Optional param3 /*= {}*/) const + Optional param1 /*= {}*/, Optional param2 /*= {}*/, Optional param3 /*= {}*/) const { WorldPackets::Guild::GuildEvent event; event.Type = guildEvent; @@ -2823,7 +2875,7 @@ void Guild::_BroadcastEvent(GuildEvents guildEvent, ObjectGuid guid, LOG_DEBUG("guild", "SMSG_GUILD_EVENT [Broadcast] Event: {}", guildEvent); } -void Guild::_SendBankList(WorldSession* session /* = nullptr*/, uint8 tabId /*= 0*/, bool sendAllSlots /*= false*/, SlotIds *slots /*= nullptr*/) const +void Guild::_SendBankList(WorldSession* session /* = nullptr*/, uint8 tabId /*= 0*/, bool sendAllSlots /*= false*/, SlotIds* slots /*= nullptr*/) const { if (!sScriptMgr->CanGuildSendBankList(this, session, tabId, sendAllSlots)) return; @@ -2849,47 +2901,47 @@ void Guild::_SendBankList(WorldSession* session /* = nullptr*/, uint8 tabId /*= if (BankTab const* tab = GetBankTab(tabId)) { auto fillItems = [&](auto begin, auto end, bool skipEmpty) - { - for (auto itr = begin; itr != end; ++itr) { - if (Item* tabItem = tab->GetItem(*itr)) + for (auto itr = begin; itr != end; ++itr) { - WorldPackets::Guild::GuildBankItemInfo itemInfo; - - itemInfo.Slot = *itr; - itemInfo.ItemID = tabItem->GetEntry(); - itemInfo.Count = int32(tabItem->GetCount()); - itemInfo.Charges = int32(std::abs(tabItem->GetSpellCharges())); - itemInfo.EnchantmentID = int32(tabItem->GetEnchantmentId(PERM_ENCHANTMENT_SLOT)); - itemInfo.Flags = tabItem->GetInt32Value(ITEM_FIELD_FLAGS); - itemInfo.RandomPropertiesID = tabItem->GetItemRandomPropertyId(); - itemInfo.RandomPropertiesSeed = int32(tabItem->GetItemSuffixFactor()); - - for (uint32 socketSlot = 0; socketSlot < MAX_GEM_SOCKETS; ++socketSlot) + if (Item* tabItem = tab->GetItem(*itr)) { - if (uint32 enchId = tabItem->GetEnchantmentId(EnchantmentSlot(SOCK_ENCHANTMENT_SLOT + socketSlot))) + WorldPackets::Guild::GuildBankItemInfo itemInfo; + + itemInfo.Slot = *itr; + itemInfo.ItemID = tabItem->GetEntry(); + itemInfo.Count = int32(tabItem->GetCount()); + itemInfo.Charges = int32(std::abs(tabItem->GetSpellCharges())); + itemInfo.EnchantmentID = int32(tabItem->GetEnchantmentId(PERM_ENCHANTMENT_SLOT)); + itemInfo.Flags = tabItem->GetInt32Value(ITEM_FIELD_FLAGS); + itemInfo.RandomPropertiesID = tabItem->GetItemRandomPropertyId(); + itemInfo.RandomPropertiesSeed = int32(tabItem->GetItemSuffixFactor()); + + for (uint32 socketSlot = 0; socketSlot < MAX_GEM_SOCKETS; ++socketSlot) { - WorldPackets::Guild::GuildBankSocketEnchant gem; - gem.SocketIndex = socketSlot; - gem.SocketEnchantID = int32(enchId); - itemInfo.SocketEnchant.push_back(gem); + if (uint32 enchId = tabItem->GetEnchantmentId(EnchantmentSlot(SOCK_ENCHANTMENT_SLOT + socketSlot))) + { + WorldPackets::Guild::GuildBankSocketEnchant gem; + gem.SocketIndex = socketSlot; + gem.SocketEnchantID = int32(enchId); + itemInfo.SocketEnchant.push_back(gem); + } } + + packet.ItemInfo.push_back(itemInfo); } + else if (!skipEmpty) + { + WorldPackets::Guild::GuildBankItemInfo itemInfo; - packet.ItemInfo.push_back(itemInfo); + itemInfo.Slot = *itr; + itemInfo.ItemID = 0; + + packet.ItemInfo.push_back(itemInfo); + } } - else if (!skipEmpty) - { - WorldPackets::Guild::GuildBankItemInfo itemInfo; - itemInfo.Slot = *itr; - itemInfo.ItemID = 0; - - packet.ItemInfo.push_back(itemInfo); - } - } - - }; + }; if (sendAllSlots) fillItems(boost::make_counting_iterator(uint8(0)), boost::make_counting_iterator(uint8(GUILD_BANK_MAX_SLOTS)), true); @@ -2904,7 +2956,7 @@ void Guild::_SendBankList(WorldSession* session /* = nullptr*/, uint8 tabId /*= session->SendPacket(packet.Write()); LOG_DEBUG("guild", "SMSG_GUILD_BANK_LIST [{}]: TabId: {}, FullSlots: {}, slots: {}", - session->GetPlayerInfo(), tabId, sendAllSlots, packet.WithdrawalsRemaining); + session->GetPlayerInfo(), tabId, sendAllSlots, packet.WithdrawalsRemaining); } else { @@ -2914,7 +2966,7 @@ void Guild::_SendBankList(WorldSession* session /* = nullptr*/, uint8 tabId /*= if (!member.ShouldReceiveBankPartialUpdatePackets()) continue; - if (!_MemberHasTabRights(member.GetGUID(), tabId, GUILD_BANK_RIGHT_VIEW_TAB)) + if (!MemberHasTabRights(member.GetGUID(), tabId, GUILD_BANK_RIGHT_VIEW_TAB)) continue; Player* player = member.FindPlayer(); @@ -2924,7 +2976,7 @@ void Guild::_SendBankList(WorldSession* session /* = nullptr*/, uint8 tabId /*= packet.SetWithdrawalsRemaining(_GetMemberRemainingSlots(member, tabId)); player->SendDirectMessage(packet.GetRawPacket()); LOG_DEBUG("guild", "SMSG_GUILD_BANK_LIST [{}]: TabId: {}, FullSlots: {}, slots: {}" - , player->GetName(), tabId, sendAllSlots, packet.WithdrawalsRemaining); + , player->GetName(), tabId, sendAllSlots, packet.WithdrawalsRemaining); } } } diff --git a/src/server/game/Guilds/Guild.h b/src/server/game/Guilds/Guild.h index ab5d47295..ceb935e86 100644 --- a/src/server/game/Guilds/Guild.h +++ b/src/server/game/Guilds/Guild.h @@ -39,16 +39,16 @@ namespace WorldPackets enum GuildMisc { - GUILD_BANK_MAX_TABS = 6, // send by client for money log also - GUILD_BANK_MAX_SLOTS = 98, - GUILD_BANK_MONEY_LOGS_TAB = 100, // used for money log in DB - GUILD_RANKS_MIN_COUNT = 5, - GUILD_RANKS_MAX_COUNT = 10, - GUILD_RANK_NONE = 0xFF, - GUILD_WITHDRAW_MONEY_UNLIMITED = 0xFFFFFFFF, - GUILD_WITHDRAW_SLOT_UNLIMITED = 0xFFFFFFFF, - GUILD_EVENT_LOG_GUID_UNDEFINED = 0xFFFFFFFF, - TAB_UNDEFINED = 0xFF, + GUILD_BANK_MAX_TABS = 6, // send by client for money log also + GUILD_BANK_MAX_SLOTS = 98, + GUILD_BANK_MONEY_LOGS_TAB = 100, // used for money log in DB + GUILD_RANKS_MIN_COUNT = 5, + GUILD_RANKS_MAX_COUNT = 10, + GUILD_RANK_NONE = 0xFF, + GUILD_WITHDRAW_MONEY_UNLIMITED = 0xFFFFFFFF, + GUILD_WITHDRAW_SLOT_UNLIMITED = 0xFFFFFFFF, + GUILD_EVENT_LOG_GUID_UNDEFINED = 0xFFFFFFFF, + TAB_UNDEFINED = 0xFF, }; constexpr uint64 GUILD_BANK_MONEY_LIMIT = UI64LIT(0x7FFFFFFFFFFFF); @@ -62,183 +62,185 @@ enum GuildMemberData enum GuildDefaultRanks { // These ranks can be modified, but they cannot be deleted - GR_GUILDMASTER = 0, - GR_OFFICER = 1, - GR_VETERAN = 2, - GR_MEMBER = 3, - GR_INITIATE = 4 - // When promoting member server does: rank-- - // When demoting member server does: rank++ + GR_GUILDMASTER = 0, + GR_OFFICER = 1, + GR_VETERAN = 2, + GR_MEMBER = 3, + GR_INITIATE = 4 + // When promoting member server does: rank-- + // When demoting member server does: rank++ }; enum GuildRankRights { - GR_RIGHT_EMPTY = 0x00000040, - GR_RIGHT_GCHATLISTEN = GR_RIGHT_EMPTY | 0x00000001, - GR_RIGHT_GCHATSPEAK = GR_RIGHT_EMPTY | 0x00000002, - GR_RIGHT_OFFCHATLISTEN = GR_RIGHT_EMPTY | 0x00000004, - GR_RIGHT_OFFCHATSPEAK = GR_RIGHT_EMPTY | 0x00000008, - GR_RIGHT_INVITE = GR_RIGHT_EMPTY | 0x00000010, - GR_RIGHT_REMOVE = GR_RIGHT_EMPTY | 0x00000020, - GR_RIGHT_PROMOTE = GR_RIGHT_EMPTY | 0x00000080, - GR_RIGHT_DEMOTE = GR_RIGHT_EMPTY | 0x00000100, - GR_RIGHT_SETMOTD = GR_RIGHT_EMPTY | 0x00001000, - GR_RIGHT_EPNOTE = GR_RIGHT_EMPTY | 0x00002000, - GR_RIGHT_VIEWOFFNOTE = GR_RIGHT_EMPTY | 0x00004000, - GR_RIGHT_EOFFNOTE = GR_RIGHT_EMPTY | 0x00008000, - GR_RIGHT_MODIFY_GUILD_INFO = GR_RIGHT_EMPTY | 0x00010000, - GR_RIGHT_WITHDRAW_GOLD_LOCK = 0x00020000, // remove money withdraw capacity - GR_RIGHT_WITHDRAW_REPAIR = 0x00040000, // withdraw for repair - GR_RIGHT_WITHDRAW_GOLD = 0x00080000, // withdraw gold - GR_RIGHT_CREATE_GUILD_EVENT = 0x00100000, // wotlk - GR_RIGHT_ALL = 0x001DF1FF + GR_RIGHT_EMPTY = 0x00000040, + GR_RIGHT_GCHATLISTEN = GR_RIGHT_EMPTY | 0x00000001, + GR_RIGHT_GCHATSPEAK = GR_RIGHT_EMPTY | 0x00000002, + GR_RIGHT_OFFCHATLISTEN = GR_RIGHT_EMPTY | 0x00000004, + GR_RIGHT_OFFCHATSPEAK = GR_RIGHT_EMPTY | 0x00000008, + GR_RIGHT_INVITE = GR_RIGHT_EMPTY | 0x00000010, + GR_RIGHT_REMOVE = GR_RIGHT_EMPTY | 0x00000020, + GR_RIGHT_PROMOTE = GR_RIGHT_EMPTY | 0x00000080, + GR_RIGHT_DEMOTE = GR_RIGHT_EMPTY | 0x00000100, + GR_RIGHT_SETMOTD = GR_RIGHT_EMPTY | 0x00001000, + GR_RIGHT_EPNOTE = GR_RIGHT_EMPTY | 0x00002000, + GR_RIGHT_VIEWOFFNOTE = GR_RIGHT_EMPTY | 0x00004000, + GR_RIGHT_EOFFNOTE = GR_RIGHT_EMPTY | 0x00008000, + GR_RIGHT_MODIFY_GUILD_INFO = GR_RIGHT_EMPTY | 0x00010000, + GR_RIGHT_WITHDRAW_GOLD_LOCK = 0x00020000, // remove money withdraw capacity + GR_RIGHT_WITHDRAW_REPAIR = 0x00040000, // withdraw for repair + GR_RIGHT_WITHDRAW_GOLD = 0x00080000, // withdraw gold + GR_RIGHT_CREATE_GUILD_EVENT = 0x00100000, // wotlk + GR_RIGHT_ALL = 0x001DF1FF }; enum GuildCommandType { - GUILD_COMMAND_CREATE = 0, - GUILD_COMMAND_INVITE = 1, - GUILD_COMMAND_QUIT = 3, - GUILD_COMMAND_ROSTER = 5, - GUILD_COMMAND_PROMOTE = 6, - GUILD_COMMAND_DEMOTE = 7, - GUILD_COMMAND_REMOVE = 8, - GUILD_COMMAND_CHANGE_LEADER = 10, - GUILD_COMMAND_EDIT_MOTD = 11, - GUILD_COMMAND_GUILD_CHAT = 13, - GUILD_COMMAND_FOUNDER = 14, - GUILD_COMMAND_CHANGE_RANK = 16, - GUILD_COMMAND_PUBLIC_NOTE = 19, - GUILD_COMMAND_VIEW_TAB = 21, - GUILD_COMMAND_MOVE_ITEM = 22, - GUILD_COMMAND_REPAIR = 25, + GUILD_COMMAND_CREATE = 0, + GUILD_COMMAND_INVITE = 1, + GUILD_COMMAND_QUIT = 3, + GUILD_COMMAND_ROSTER = 5, + GUILD_COMMAND_PROMOTE = 6, + GUILD_COMMAND_DEMOTE = 7, + GUILD_COMMAND_REMOVE = 8, + GUILD_COMMAND_CHANGE_LEADER = 10, + GUILD_COMMAND_EDIT_MOTD = 11, + GUILD_COMMAND_GUILD_CHAT = 13, + GUILD_COMMAND_FOUNDER = 14, + GUILD_COMMAND_CHANGE_RANK = 16, + GUILD_COMMAND_PUBLIC_NOTE = 19, + GUILD_COMMAND_VIEW_TAB = 21, + GUILD_COMMAND_MOVE_ITEM = 22, + GUILD_COMMAND_REPAIR = 25, }; enum GuildCommandError { - ERR_GUILD_COMMAND_SUCCESS = 0, - ERR_GUILD_INTERNAL = 1, - ERR_ALREADY_IN_GUILD = 2, - ERR_ALREADY_IN_GUILD_S = 3, - ERR_INVITED_TO_GUILD = 4, - ERR_ALREADY_INVITED_TO_GUILD_S = 5, - ERR_GUILD_NAME_INVALID = 6, - ERR_GUILD_NAME_EXISTS_S = 7, - ERR_GUILD_LEADER_LEAVE = 8, - ERR_GUILD_PERMISSIONS = 8, - ERR_GUILD_PLAYER_NOT_IN_GUILD = 9, - ERR_GUILD_PLAYER_NOT_IN_GUILD_S = 10, - ERR_GUILD_PLAYER_NOT_FOUND_S = 11, - ERR_GUILD_NOT_ALLIED = 12, - ERR_GUILD_RANK_TOO_HIGH_S = 13, - ERR_GUILD_RANK_TOO_LOW_S = 14, - ERR_GUILD_RANKS_LOCKED = 17, - ERR_GUILD_RANK_IN_USE = 18, - ERR_GUILD_IGNORING_YOU_S = 19, - ERR_GUILD_UNK1 = 20, // Forces roster update - ERR_GUILD_WITHDRAW_LIMIT = 25, - ERR_GUILD_NOT_ENOUGH_MONEY = 26, - ERR_GUILD_BANK_FULL = 28, - ERR_GUILD_ITEM_NOT_FOUND = 29, + ERR_GUILD_COMMAND_SUCCESS = 0, + ERR_GUILD_INTERNAL = 1, + ERR_ALREADY_IN_GUILD = 2, + ERR_ALREADY_IN_GUILD_S = 3, + ERR_INVITED_TO_GUILD = 4, + ERR_ALREADY_INVITED_TO_GUILD_S = 5, + ERR_GUILD_NAME_INVALID = 6, + ERR_GUILD_NAME_EXISTS_S = 7, + ERR_GUILD_LEADER_LEAVE = 8, + ERR_GUILD_PERMISSIONS = 8, + ERR_GUILD_PLAYER_NOT_IN_GUILD = 9, + ERR_GUILD_PLAYER_NOT_IN_GUILD_S = 10, + ERR_GUILD_PLAYER_NOT_FOUND_S = 11, + ERR_GUILD_NOT_ALLIED = 12, + ERR_GUILD_RANK_TOO_HIGH_S = 13, + ERR_GUILD_RANK_TOO_LOW_S = 14, + ERR_GUILD_RANKS_LOCKED = 17, + ERR_GUILD_RANK_IN_USE = 18, + ERR_GUILD_IGNORING_YOU_S = 19, + ERR_GUILD_UNK1 = 20, // Forces roster update + ERR_GUILD_WITHDRAW_LIMIT = 25, + ERR_GUILD_NOT_ENOUGH_MONEY = 26, + ERR_GUILD_BANK_FULL = 28, + ERR_GUILD_ITEM_NOT_FOUND = 29, }; enum GuildEvents { - GE_PROMOTION = 0, - GE_DEMOTION = 1, - GE_MOTD = 2, - GE_JOINED = 3, - GE_LEFT = 4, - GE_REMOVED = 5, - GE_LEADER_IS = 6, - GE_LEADER_CHANGED = 7, - GE_DISBANDED = 8, - GE_TABARDCHANGE = 9, - GE_RANK_UPDATED = 10, - GE_RANK_DELETED = 11, - GE_SIGNED_ON = 12, - GE_SIGNED_OFF = 13, - GE_GUILDBANKBAGSLOTS_CHANGED = 14, /// @todo: Sent when items are moved in gbank - all players with bank open will send tab query - GE_BANK_TAB_PURCHASED = 15, - GE_BANK_TAB_UPDATED = 16, - GE_BANK_MONEY_SET = 17, - GE_BANK_TAB_AND_MONEY_UPDATED = 18, - GE_BANK_TEXT_CHANGED = 19, + GE_PROMOTION = 0, + GE_DEMOTION = 1, + GE_MOTD = 2, + GE_JOINED = 3, + GE_LEFT = 4, + GE_REMOVED = 5, + GE_LEADER_IS = 6, + GE_LEADER_CHANGED = 7, + GE_DISBANDED = 8, + GE_TABARDCHANGE = 9, + GE_RANK_UPDATED = 10, + GE_RANK_DELETED = 11, + GE_SIGNED_ON = 12, + GE_SIGNED_OFF = 13, + GE_GUILDBANKBAGSLOTS_CHANGED = 14, /// @todo: Sent when items are moved in gbank - all players with bank open will send tab query + GE_BANK_TAB_PURCHASED = 15, + GE_BANK_TAB_UPDATED = 16, + GE_BANK_MONEY_SET = 17, + GE_BANK_TAB_AND_MONEY_UPDATED = 18, + GE_BANK_TEXT_CHANGED = 19, }; enum PetitionTurns { - PETITION_TURN_OK = 0, - PETITION_TURN_ALREADY_IN_GUILD = 2, - PETITION_TURN_NEED_MORE_SIGNATURES = 4 + PETITION_TURN_OK = 0, + PETITION_TURN_ALREADY_IN_GUILD = 2, + PETITION_TURN_NEED_MORE_SIGNATURES = 4 }; enum PetitionSigns { - PETITION_SIGN_OK = 0, - PETITION_SIGN_ALREADY_SIGNED = 1, - PETITION_SIGN_ALREADY_IN_GUILD = 2, - PETITION_SIGN_CANT_SIGN_OWN = 3, - PETITION_SIGN_NOT_SERVER = 4 + PETITION_SIGN_OK = 0, + PETITION_SIGN_ALREADY_SIGNED = 1, + PETITION_SIGN_ALREADY_IN_GUILD = 2, + PETITION_SIGN_CANT_SIGN_OWN = 3, + PETITION_SIGN_NOT_SERVER = 4 }; enum GuildBankRights { - GUILD_BANK_RIGHT_VIEW_TAB = 0x01, - GUILD_BANK_RIGHT_PUT_ITEM = 0x02, - GUILD_BANK_RIGHT_UPDATE_TEXT = 0x04, + GUILD_BANK_RIGHT_VIEW_TAB = 0x01, + GUILD_BANK_RIGHT_PUT_ITEM = 0x02, + GUILD_BANK_RIGHT_UPDATE_TEXT = 0x04, - GUILD_BANK_RIGHT_DEPOSIT_ITEM = GUILD_BANK_RIGHT_VIEW_TAB | GUILD_BANK_RIGHT_PUT_ITEM, - GUILD_BANK_RIGHT_FULL = 0xFF + GUILD_BANK_RIGHT_DEPOSIT_ITEM = GUILD_BANK_RIGHT_VIEW_TAB | GUILD_BANK_RIGHT_PUT_ITEM, + GUILD_BANK_RIGHT_FULL = 0xFF }; enum GuildBankEventLogTypes { - GUILD_BANK_LOG_DEPOSIT_ITEM = 1, - GUILD_BANK_LOG_WITHDRAW_ITEM = 2, - GUILD_BANK_LOG_MOVE_ITEM = 3, - GUILD_BANK_LOG_DEPOSIT_MONEY = 4, - GUILD_BANK_LOG_WITHDRAW_MONEY = 5, - GUILD_BANK_LOG_REPAIR_MONEY = 6, - GUILD_BANK_LOG_MOVE_ITEM2 = 7, - GUILD_BANK_LOG_UNK1 = 8, - GUILD_BANK_LOG_BUY_SLOT = 9 + GUILD_BANK_LOG_DEPOSIT_ITEM = 1, + GUILD_BANK_LOG_WITHDRAW_ITEM = 2, + GUILD_BANK_LOG_MOVE_ITEM = 3, + GUILD_BANK_LOG_DEPOSIT_MONEY = 4, + GUILD_BANK_LOG_WITHDRAW_MONEY = 5, + GUILD_BANK_LOG_REPAIR_MONEY = 6, + GUILD_BANK_LOG_MOVE_ITEM2 = 7, + GUILD_BANK_LOG_UNK1 = 8, + GUILD_BANK_LOG_BUY_SLOT = 9 }; enum GuildEventLogTypes { - GUILD_EVENT_LOG_INVITE_PLAYER = 1, - GUILD_EVENT_LOG_JOIN_GUILD = 2, - GUILD_EVENT_LOG_PROMOTE_PLAYER = 3, - GUILD_EVENT_LOG_DEMOTE_PLAYER = 4, - GUILD_EVENT_LOG_UNINVITE_PLAYER = 5, - GUILD_EVENT_LOG_LEAVE_GUILD = 6 + GUILD_EVENT_LOG_INVITE_PLAYER = 1, + GUILD_EVENT_LOG_JOIN_GUILD = 2, + GUILD_EVENT_LOG_PROMOTE_PLAYER = 3, + GUILD_EVENT_LOG_DEMOTE_PLAYER = 4, + GUILD_EVENT_LOG_UNINVITE_PLAYER = 5, + GUILD_EVENT_LOG_LEAVE_GUILD = 6 }; enum GuildEmblemError { - ERR_GUILDEMBLEM_SUCCESS = 0, + ERR_GUILDEMBLEM_SUCCESS = 0, ERR_GUILDEMBLEM_INVALID_TABARD_COLORS = 1, - ERR_GUILDEMBLEM_NOGUILD = 2, - ERR_GUILDEMBLEM_NOTGUILDMASTER = 3, - ERR_GUILDEMBLEM_NOTENOUGHMONEY = 4, - ERR_GUILDEMBLEM_INVALIDVENDOR = 5 + ERR_GUILDEMBLEM_NOGUILD = 2, + ERR_GUILDEMBLEM_NOTGUILDMASTER = 3, + ERR_GUILDEMBLEM_NOTENOUGHMONEY = 4, + ERR_GUILDEMBLEM_INVALIDVENDOR = 5 }; enum GuildMemberFlags { - GUILDMEMBER_STATUS_NONE = 0x0000, - GUILDMEMBER_STATUS_ONLINE = 0x0001, - GUILDMEMBER_STATUS_AFK = 0x0002, - GUILDMEMBER_STATUS_DND = 0x0004, - GUILDMEMBER_STATUS_MOBILE = 0x0008, // remote chat from mobile app + GUILDMEMBER_STATUS_NONE = 0x0000, + GUILDMEMBER_STATUS_ONLINE = 0x0001, + GUILDMEMBER_STATUS_AFK = 0x0002, + GUILDMEMBER_STATUS_DND = 0x0004, + GUILDMEMBER_STATUS_MOBILE = 0x0008, // remote chat from mobile app }; // Emblem info class EmblemInfo { public: - EmblemInfo() : m_style(0), m_color(0), m_borderStyle(0), m_borderColor(0), m_backgroundColor(0) { } + EmblemInfo(uint32 /*style*/ = 0, uint32 /*color*/ = 0, uint32 /*borderStyle*/ = 0, uint32 /*borderColor*/ = 0, uint32 /*backgroundColor*/ = 0) : + m_style(0), m_color(0), m_borderStyle(0), m_borderColor(0), m_backgroundColor(0) { + } void LoadFromDB(Field* fields); void SaveToDB(uint32 guildId) const; @@ -262,9 +264,9 @@ private: class GuildBankRightsAndSlots { public: - GuildBankRightsAndSlots() : tabId(TAB_UNDEFINED), rights(0), slots(0) { } - GuildBankRightsAndSlots(uint8 _tabId) : tabId(_tabId), rights(0), slots(0) { } - GuildBankRightsAndSlots(uint8 _tabId, uint8 _rights, uint32 _slots) : tabId(_tabId), rights(_rights), slots(_slots) { } + GuildBankRightsAndSlots() : tabId(TAB_UNDEFINED), rights(0), slots(0) {} + GuildBankRightsAndSlots(uint8 _tabId) : tabId(_tabId), rights(0), slots(0) {} + GuildBankRightsAndSlots(uint8 _tabId, uint8 _rights, uint32 _slots) : tabId(_tabId), rights(_rights), slots(_slots) {} void SetGuildMasterValues() { @@ -295,7 +297,7 @@ public: // pussywizard: public class Member class Member { public: - Member(uint32 guildId, ObjectGuid guid, uint8 rankId): + Member(uint32 guildId, ObjectGuid guid, uint8 rankId) : m_guildId(guildId), m_guid(guid), m_zoneId(0), @@ -403,8 +405,8 @@ private: { public: LogEntry(uint32 guildId, ObjectGuid::LowType guid); - LogEntry(uint32 guildId, ObjectGuid::LowType guid, time_t timestamp) : m_guildId(guildId), m_guid(guid), m_timestamp(timestamp) { } - virtual ~LogEntry() { } + LogEntry(uint32 guildId, ObjectGuid::LowType guid, time_t timestamp) : m_guildId(guildId), m_guid(guid), m_timestamp(timestamp) {} + virtual ~LogEntry() {} ObjectGuid::LowType GetGUID() const { return m_guid; } uint64 GetTimestamp() const { return m_timestamp; } @@ -422,12 +424,14 @@ private: { public: EventLogEntry(uint32 guildId, ObjectGuid::LowType guid, GuildEventLogTypes eventType, ObjectGuid playerGuid1, ObjectGuid playerGuid2, uint8 newRank) : - LogEntry(guildId, guid), m_eventType(eventType), m_playerGuid1(playerGuid1), m_playerGuid2(playerGuid2), m_newRank(newRank) { } + LogEntry(guildId, guid), m_eventType(eventType), m_playerGuid1(playerGuid1), m_playerGuid2(playerGuid2), m_newRank(newRank) { + } EventLogEntry(uint32 guildId, ObjectGuid::LowType guid, time_t timestamp, GuildEventLogTypes eventType, ObjectGuid playerGuid1, ObjectGuid playerGuid2, uint8 newRank) : - LogEntry(guildId, guid, timestamp), m_eventType(eventType), m_playerGuid1(playerGuid1), m_playerGuid2(playerGuid2), m_newRank(newRank) { } + LogEntry(guildId, guid, timestamp), m_eventType(eventType), m_playerGuid1(playerGuid1), m_playerGuid2(playerGuid2), m_newRank(newRank) { + } - ~EventLogEntry() override { } + ~EventLogEntry() override {} void SaveToDB(CharacterDatabaseTransaction trans) const override; void WritePacket(WorldPackets::Guild::GuildEventLogQueryResults& packet) const; @@ -458,13 +462,15 @@ private: BankEventLogEntry(uint32 guildId, ObjectGuid::LowType guid, GuildBankEventLogTypes eventType, uint8 tabId, ObjectGuid playerGuid, uint32 itemOrMoney, uint16 itemStackCount, uint8 destTabId) : LogEntry(guildId, guid), m_eventType(eventType), m_bankTabId(tabId), m_playerGuid(playerGuid), - m_itemOrMoney(itemOrMoney), m_itemStackCount(itemStackCount), m_destTabId(destTabId) { } + m_itemOrMoney(itemOrMoney), m_itemStackCount(itemStackCount), m_destTabId(destTabId) { + } BankEventLogEntry(uint32 guildId, ObjectGuid::LowType guid, time_t timestamp, uint8 tabId, GuildBankEventLogTypes eventType, ObjectGuid playerGuid, uint32 itemOrMoney, uint16 itemStackCount, uint8 destTabId) : LogEntry(guildId, guid, timestamp), m_eventType(eventType), m_bankTabId(tabId), m_playerGuid(playerGuid), - m_itemOrMoney(itemOrMoney), m_itemStackCount(itemStackCount), m_destTabId(destTabId) { } + m_itemOrMoney(itemOrMoney), m_itemStackCount(itemStackCount), m_destTabId(destTabId) { + } - ~BankEventLogEntry() override { } + ~BankEventLogEntry() override {} void SaveToDB(CharacterDatabaseTransaction trans) const override; void WritePacket(WorldPackets::Guild::GuildBankLogQueryResults& packet) const; @@ -509,11 +515,12 @@ private: class RankInfo { public: - RankInfo(): m_guildId(0), m_rankId(GUILD_RANK_NONE), m_rights(GR_RIGHT_EMPTY), m_bankMoneyPerDay(0) { } - RankInfo(uint32 guildId) : m_guildId(guildId), m_rankId(GUILD_RANK_NONE), m_rights(GR_RIGHT_EMPTY), m_bankMoneyPerDay(0) { } + RankInfo() : m_guildId(0), m_rankId(GUILD_RANK_NONE), m_rights(GR_RIGHT_EMPTY), m_bankMoneyPerDay(0) {} + RankInfo(uint32 guildId) : m_guildId(guildId), m_rankId(GUILD_RANK_NONE), m_rights(GR_RIGHT_EMPTY), m_bankMoneyPerDay(0) {} RankInfo(uint32 guildId, uint8 rankId, std::string_view name, uint32 rights, uint32 money) : m_guildId(guildId), m_rankId(rankId), m_name(name), m_rights(rights), - m_bankMoneyPerDay(rankId != GR_GUILDMASTER ? money : GUILD_WITHDRAW_MONEY_UNLIMITED) { } + m_bankMoneyPerDay(rankId != GR_GUILDMASTER ? money : GUILD_WITHDRAW_MONEY_UNLIMITED) { + } void LoadFromDB(Field* fields); void SaveToDB(CharacterDatabaseTransaction trans) const; @@ -572,7 +579,7 @@ private: std::string const& GetIcon() const { return m_icon; } std::string const& GetText() const { return m_text; } - inline Item* GetItem(uint8 slotId) const { return slotId < GUILD_BANK_MAX_SLOTS ? m_items[slotId] : nullptr; } + inline Item* GetItem(uint8 slotId) const { return slotId < GUILD_BANK_MAX_SLOTS ? m_items[slotId] : nullptr; } bool SetItem(CharacterDatabaseTransaction trans, uint8 slotId, Item* pItem); private: @@ -590,8 +597,9 @@ private: { public: MoveItemData(Guild* guild, Player* player, uint8 container, uint8 slotId) : m_pGuild(guild), m_pPlayer(player), - m_container(container), m_slotId(slotId), m_pItem(nullptr), m_pClonedItem(nullptr) { } - virtual ~MoveItemData() { } + m_container(container), m_slotId(slotId), m_pItem(nullptr), m_pClonedItem(nullptr) { + } + virtual ~MoveItemData() {} virtual bool IsBank() const = 0; // Initializes item pointer. Returns true, if item exists, false otherwise. @@ -637,7 +645,8 @@ private: { public: PlayerMoveItemData(Guild* guild, Player* player, uint8 container, uint8 slotId) : - MoveItemData(guild, player, container, slotId) { } + MoveItemData(guild, player, container, slotId) { + } bool IsBank() const override { return false; } bool InitItem() override; @@ -652,7 +661,8 @@ private: { public: BankMoveItemData(Guild* guild, Player* player, uint8 container, uint8 slotId) : - MoveItemData(guild, player, container, slotId) { } + MoveItemData(guild, player, container, slotId) { + } bool IsBank() const override { return true; } bool InitItem() override; @@ -696,11 +706,13 @@ public: void HandleQuery(WorldSession* session); void HandleSetMOTD(WorldSession* session, std::string_view motd); void HandleSetInfo(WorldSession* session, std::string_view info); - void HandleSetEmblem(WorldSession* session, const EmblemInfo& emblemInfo); + void HandleSetEmblem(WorldSession* session, EmblemInfo const& emblemInfo); + void HandleSetEmblem(EmblemInfo const& emblemInfo); void HandleSetLeader(WorldSession* session, std::string_view name); void HandleSetBankTabInfo(WorldSession* session, uint8 tabId, std::string_view name, std::string_view icon); void HandleSetMemberNote(WorldSession* session, std::string_view name, std::string_view note, bool isPublic); void HandleSetRankInfo(WorldSession* session, uint8 rankId, std::string_view name, uint32 rights, uint32 moneyPerDay, std::array const& rightsAndSlots); + void HandleSetRankInfo(uint8 rankId, uint32 rights = 0, std::string_view name = "", uint32 moneyPerDay = 0); void HandleBuyBankTab(WorldSession* session, uint8 tabId); void HandleInviteMember(WorldSession* session, std::string const& name); void HandleAcceptMember(WorldSession* session); @@ -779,6 +791,10 @@ public: [[nodiscard]] bool ModifyBankMoney(CharacterDatabaseTransaction trans, const uint64& amount, bool add) { return _ModifyBankMoney(trans, amount, add); } [[nodiscard]] uint32 GetMemberSize() const { return m_members.size(); } + bool MemberHasTabRights(ObjectGuid guid, uint8 tabId, uint32 rights) const; + bool HasRankRight(Player* player, uint32 right) const; + uint32 GetRankRights(uint8 rankId) const; + protected: uint32 m_id; std::string m_name; @@ -803,13 +819,6 @@ private: inline uint8 _GetRanksSize() const { return uint8(m_ranks.size()); } inline const RankInfo* GetRankInfo(uint8 rankId) const { return rankId < _GetRanksSize() ? &m_ranks[rankId] : nullptr; } inline RankInfo* GetRankInfo(uint8 rankId) { return rankId < _GetRanksSize() ? &m_ranks[rankId] : nullptr; } - inline bool _HasRankRight(Player* player, uint32 right) const - { - if (player) - if (Member const* member = GetMember(player->GetGUID())) - return (_GetRankRights(member->GetRankId()) & right) != GR_RIGHT_EMPTY; - return false; - } inline uint8 _GetLowestRankId() const { return uint8(m_ranks.size() - 1); } @@ -840,7 +849,6 @@ private: void _SetRankBankMoneyPerDay(uint8 rankId, uint32 moneyPerDay); void _SetRankBankTabRightsAndSlots(uint8 rankId, GuildBankRightsAndSlots rightsAndSlots, bool saveToDB = true); int8 _GetRankBankTabRights(uint8 rankId, uint8 tabId) const; - uint32 _GetRankRights(uint8 rankId) const; int32 _GetRankBankMoneyPerDay(uint8 rankId) const; int32 _GetRankBankTabSlotsPerDay(uint8 rankId, uint8 tabId) const; std::string _GetRankName(uint8 rankId) const; @@ -848,7 +856,6 @@ private: int32 _GetMemberRemainingSlots(Member const& member, uint8 tabId) const; int32 _GetMemberRemainingMoney(Member const& member) const; void _UpdateMemberWithdrawSlots(CharacterDatabaseTransaction trans, ObjectGuid guid, uint8 tabId); - bool _MemberHasTabRights(ObjectGuid guid, uint8 tabId, uint32 rights) const; void _LogEvent(GuildEventLogTypes eventType, ObjectGuid playerGuid1, ObjectGuid playerGuid2 = ObjectGuid::Empty, uint8 newRank = 0); void _LogBankEvent(CharacterDatabaseTransaction trans, GuildBankEventLogTypes eventType, uint8 tabId, ObjectGuid playerGuid, uint32 itemOrMoney, uint16 itemStackCount = 0, uint8 destTabId = 0); diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index eeff0d3d6..97c8815d9 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -60,19 +60,9 @@ #include "WorldSession.h" #include "WorldSessionMgr.h" -class LoginQueryHolder : public CharacterDatabaseQueryHolder +LoginQueryHolder::LoginQueryHolder(uint32 accountId, ObjectGuid guid) : m_accountId(accountId), m_guid(guid) { -private: - uint32 m_accountId; - ObjectGuid m_guid; -public: - LoginQueryHolder(uint32 accountId, ObjectGuid guid) - : m_accountId(accountId), m_guid(guid) { } - - ObjectGuid GetGuid() const { return m_guid; } - uint32 GetAccountId() const { return m_accountId; } - bool Initialize(); -}; +} bool LoginQueryHolder::Initialize() { @@ -274,15 +264,15 @@ void WorldSession::HandleCharCreateOpcode(WorldPacket& recvData) std::shared_ptr createInfo = std::make_shared(); recvData >> createInfo->Name - >> createInfo->Race - >> createInfo->Class - >> createInfo->Gender - >> createInfo->Skin - >> createInfo->Face - >> createInfo->HairStyle - >> createInfo->HairColor - >> createInfo->FacialHair - >> createInfo->OutfitId; + >> createInfo->Race + >> createInfo->Class + >> createInfo->Gender + >> createInfo->Skin + >> createInfo->Face + >> createInfo->HairStyle + >> createInfo->HairColor + >> createInfo->FacialHair + >> createInfo->OutfitId; if (AccountMgr::IsPlayerAccount(GetSecurity())) { @@ -381,228 +371,233 @@ void WorldSession::HandleCharCreateOpcode(WorldPacket& recvData) stmt->SetData(0, createInfo->Name); _queryProcessor.AddCallback(CharacterDatabase.AsyncQuery(stmt) - .WithChainingPreparedCallback([this](QueryCallback& queryCallback, PreparedQueryResult result) - { - if (result) - { - SendCharCreate(CHAR_CREATE_NAME_IN_USE); - return; - } - - LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_SUM_REALM_CHARACTERS); - stmt->SetData(0, GetAccountId()); - queryCallback.SetNextQuery(LoginDatabase.AsyncQuery(stmt)); - }) - .WithChainingPreparedCallback([this](QueryCallback& queryCallback, PreparedQueryResult result) - { - uint64 acctCharCount = 0; - if (result) - { - Field* fields = result->Fetch(); - acctCharCount = uint64(fields[0].Get()); - } - - if (acctCharCount >= static_cast(sWorld->getIntConfig(CONFIG_CHARACTERS_PER_ACCOUNT))) - { - SendCharCreate(CHAR_CREATE_ACCOUNT_LIMIT); - return; - } - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_SUM_CHARS); - stmt->SetData(0, GetAccountId()); - queryCallback.SetNextQuery(CharacterDatabase.AsyncQuery(stmt)); - }) - .WithChainingPreparedCallback([this, createInfo](QueryCallback& queryCallback, PreparedQueryResult result) - { - if (result) - { - Field* fields = result->Fetch(); - createInfo->CharCount = uint8(fields[0].Get()); // SQL's COUNT() returns uint64 but it will always be less than uint8.Max - - if (createInfo->CharCount >= sWorld->getIntConfig(CONFIG_CHARACTERS_PER_REALM)) + .WithChainingPreparedCallback([this](QueryCallback& queryCallback, PreparedQueryResult result) { - SendCharCreate(CHAR_CREATE_SERVER_LIMIT); - return; - } - } - - bool allowTwoSideAccounts = !sWorld->IsPvPRealm() || sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_ACCOUNTS) || !AccountMgr::IsPlayerAccount(GetSecurity()); - uint32 skipCinematics = sWorld->getIntConfig(CONFIG_SKIP_CINEMATICS); - - std::function finalizeCharacterCreation = [this, createInfo](PreparedQueryResult result) - { - if (!sScriptMgr->CanAccountCreateCharacter(GetAccountId(), createInfo->Race, createInfo->Class)) - { - SendCharCreate(CHAR_CREATE_DISABLED); - return; - } - bool haveSameRace = false; - uint32 heroicReqLevel = sWorld->getIntConfig(CONFIG_CHARACTER_CREATING_MIN_LEVEL_FOR_HEROIC_CHARACTER); - bool hasHeroicReqLevel = (heroicReqLevel == 0); - bool allowTwoSideAccounts = !sWorld->IsPvPRealm() || sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_ACCOUNTS) || !AccountMgr::IsPlayerAccount(GetSecurity()); - uint32 skipCinematics = sWorld->getIntConfig(CONFIG_SKIP_CINEMATICS); - bool checkDeathKnightReqs = AccountMgr::IsPlayerAccount(GetSecurity()) && createInfo->Class == CLASS_DEATH_KNIGHT; - - if (result) - { - TeamId teamId = Player::TeamIdForRace(createInfo->Race); - uint32 freeDeathKnightSlots = sWorld->getIntConfig(CONFIG_HEROIC_CHARACTERS_PER_REALM); - - Field* field = result->Fetch(); - uint8 accRace = field[1].Get(); - - if (checkDeathKnightReqs) + if (result) { - uint8 accClass = field[2].Get(); - if (accClass == CLASS_DEATH_KNIGHT) - { - if (freeDeathKnightSlots > 0) - --freeDeathKnightSlots; - - if (freeDeathKnightSlots == 0) - { - SendCharCreate(CHAR_CREATE_UNIQUE_CLASS_LIMIT); - return; - } - } - - if (!hasHeroicReqLevel) - { - uint8 accLevel = field[0].Get(); - if (accLevel >= heroicReqLevel) - hasHeroicReqLevel = true; - } + SendCharCreate(CHAR_CREATE_NAME_IN_USE); + return; } - // need to check team only for first character - /// @todo what to if account already has characters of both races? - if (!allowTwoSideAccounts) + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_SUM_REALM_CHARACTERS); + stmt->SetData(0, GetAccountId()); + queryCallback.SetNextQuery(LoginDatabase.AsyncQuery(stmt)); + }) + .WithChainingPreparedCallback([this](QueryCallback& queryCallback, PreparedQueryResult result) + { + uint64 acctCharCount = 0; + if (result) { - uint32 accTeam = 0; - if (accRace > 0) - accTeam = Player::TeamIdForRace(accRace); + Field* fields = result->Fetch(); + acctCharCount = uint64(fields[0].Get()); + } - if (accTeam != teamId) + if (acctCharCount >= static_cast(sWorld->getIntConfig(CONFIG_CHARACTERS_PER_ACCOUNT))) + { + SendCharCreate(CHAR_CREATE_ACCOUNT_LIMIT); + return; + } + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_SUM_CHARS); + stmt->SetData(0, GetAccountId()); + queryCallback.SetNextQuery(CharacterDatabase.AsyncQuery(stmt)); + }) + .WithChainingPreparedCallback([this, createInfo](QueryCallback& queryCallback, PreparedQueryResult result) + { + if (result) + { + Field* fields = result->Fetch(); + createInfo->CharCount = uint8(fields[0].Get()); // SQL's COUNT() returns uint64 but it will always be less than uint8.Max + + if (createInfo->CharCount >= sWorld->getIntConfig(CONFIG_CHARACTERS_PER_REALM)) { - SendCharCreate(CHAR_CREATE_PVP_TEAMS_VIOLATION); + SendCharCreate(CHAR_CREATE_SERVER_LIMIT); return; } } - // search same race for cinematic or same class if need - /// @todo check if cinematic already shown? (already logged in?; cinematic field) - while ((skipCinematics == 1 && !haveSameRace) || createInfo->Class == CLASS_DEATH_KNIGHT) - { - if (!result->NextRow()) - break; + bool allowTwoSideAccounts = !sWorld->IsPvPRealm() || sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_ACCOUNTS) || !AccountMgr::IsPlayerAccount(GetSecurity()); + uint32 skipCinematics = sWorld->getIntConfig(CONFIG_SKIP_CINEMATICS); - field = result->Fetch(); - accRace = field[1].Get(); - - if (!haveSameRace) - haveSameRace = createInfo->Race == accRace; - - if (checkDeathKnightReqs) + std::function finalizeCharacterCreation = [this, createInfo](PreparedQueryResult result) { - uint8 acc_class = field[2].Get(); - if (acc_class == CLASS_DEATH_KNIGHT) + if (!sScriptMgr->CanAccountCreateCharacter(GetAccountId(), createInfo->Race, createInfo->Class)) { - if (freeDeathKnightSlots > 0) - --freeDeathKnightSlots; + SendCharCreate(CHAR_CREATE_DISABLED); + return; + } + bool haveSameRace = false; + uint32 heroicReqLevel = sWorld->getIntConfig(CONFIG_CHARACTER_CREATING_MIN_LEVEL_FOR_HEROIC_CHARACTER); + bool hasHeroicReqLevel = (heroicReqLevel == 0); + bool allowTwoSideAccounts = !sWorld->IsPvPRealm() || sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_ACCOUNTS) || !AccountMgr::IsPlayerAccount(GetSecurity()); + uint32 skipCinematics = sWorld->getIntConfig(CONFIG_SKIP_CINEMATICS); + bool checkDeathKnightReqs = AccountMgr::IsPlayerAccount(GetSecurity()) && createInfo->Class == CLASS_DEATH_KNIGHT; - if (freeDeathKnightSlots == 0) + if (result) + { + TeamId teamId = Player::TeamIdForRace(createInfo->Race); + uint32 freeDeathKnightSlots = sWorld->getIntConfig(CONFIG_HEROIC_CHARACTERS_PER_REALM); + + Field* field = result->Fetch(); + uint8 accRace = field[1].Get(); + + if (checkDeathKnightReqs) { - SendCharCreate(CHAR_CREATE_UNIQUE_CLASS_LIMIT); - return; + uint8 accClass = field[2].Get(); + if (accClass == CLASS_DEATH_KNIGHT) + { + if (freeDeathKnightSlots > 0) + --freeDeathKnightSlots; + + if (freeDeathKnightSlots == 0) + { + SendCharCreate(CHAR_CREATE_UNIQUE_CLASS_LIMIT); + return; + } + } + + if (!hasHeroicReqLevel) + { + uint8 accLevel = field[0].Get(); + if (accLevel >= heroicReqLevel) + hasHeroicReqLevel = true; + } + } + + // need to check team only for first character + /// @todo what to if account already has characters of both races? + if (!allowTwoSideAccounts) + { + uint32 accTeam = 0; + if (accRace > 0) + accTeam = Player::TeamIdForRace(accRace); + + if (accTeam != teamId) + { + SendCharCreate(CHAR_CREATE_PVP_TEAMS_VIOLATION); + return; + } + } + + // search same race for cinematic or same class if need + /// @todo check if cinematic already shown? (already logged in?; cinematic field) + while ((skipCinematics == 1 && !haveSameRace) || createInfo->Class == CLASS_DEATH_KNIGHT) + { + if (!result->NextRow()) + break; + + field = result->Fetch(); + accRace = field[1].Get(); + + if (!haveSameRace) + haveSameRace = createInfo->Race == accRace; + + if (checkDeathKnightReqs) + { + uint8 acc_class = field[2].Get(); + if (acc_class == CLASS_DEATH_KNIGHT) + { + if (freeDeathKnightSlots > 0) + --freeDeathKnightSlots; + + if (freeDeathKnightSlots == 0) + { + SendCharCreate(CHAR_CREATE_UNIQUE_CLASS_LIMIT); + return; + } + } + + if (!hasHeroicReqLevel) + { + uint8 acc_level = field[0].Get(); + if (acc_level >= heroicReqLevel) + hasHeroicReqLevel = true; + } + } } } - if (!hasHeroicReqLevel) + if (checkDeathKnightReqs && !hasHeroicReqLevel) { - uint8 acc_level = field[0].Get(); - if (acc_level >= heroicReqLevel) - hasHeroicReqLevel = true; + SendCharCreate(CHAR_CREATE_LEVEL_REQUIREMENT); + return; } - } - } - } - if (checkDeathKnightReqs && !hasHeroicReqLevel) - { - SendCharCreate(CHAR_CREATE_LEVEL_REQUIREMENT); - return; - } + // Check name uniqueness in the same step as saving to database + if (sCharacterCache->GetCharacterGuidByName(createInfo->Name)) + { + SendCharCreate(CHAR_CREATE_NAME_IN_USE); + return; + } - // Check name uniqueness in the same step as saving to database - if (sCharacterCache->GetCharacterGuidByName(createInfo->Name)) - { - SendCharCreate(CHAR_CREATE_NAME_IN_USE); - return; - } + std::shared_ptr newChar(new Player(this), [](Player* ptr) + { + // Only when player is created correctly do clean + if (ptr->HasAtLoginFlag(AT_LOGIN_FIRST)) + { + ptr->CleanupsBeforeDelete(); + } + delete ptr; + }); - std::shared_ptr newChar(new Player(this), [](Player* ptr) - { - // Only when player is created correctly do clean - if (ptr->HasAtLoginFlag(AT_LOGIN_FIRST)) + newChar->GetMotionMaster()->Initialize(); + if (!newChar->Create(sObjectMgr->GetGenerator().Generate(), createInfo.get())) + { + // Player not create (race/class/etc problem?) + SendCharCreate(CHAR_CREATE_ERROR); + return; + } + + if ((haveSameRace && skipCinematics == 1) || skipCinematics == 2) + newChar->setCinematic(1); // not show intro + + newChar->SetAtLoginFlag(AT_LOGIN_FIRST); // First login + + CharacterDatabaseTransaction characterTransaction = CharacterDatabase.BeginTransaction(); + LoginDatabaseTransaction trans = LoginDatabase.BeginTransaction(); + + // Player created, save it now + newChar->SaveToDB(characterTransaction, true, false); + createInfo->CharCount++; + + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_DEL_REALM_CHARACTERS_BY_REALM); + stmt->SetData(0, GetAccountId()); + stmt->SetData(1, realm.Id.Realm); + trans->Append(stmt); + + stmt = LoginDatabase.GetPreparedStatement(LOGIN_REP_REALM_CHARACTERS); + stmt->SetData(0, createInfo->CharCount); + stmt->SetData(1, GetAccountId()); + stmt->SetData(2, realm.Id.Realm); + trans->Append(stmt); + + LoginDatabase.CommitTransaction(trans); + + AddTransactionCallback(CharacterDatabase.AsyncCommitTransaction(characterTransaction)).AfterComplete([this, newChar = std::move(newChar)](bool success) + { + if (success) + { + LOG_INFO("entities.player.character", "Account: {} (IP: {}) Create Character: {} {}", GetAccountId(), GetRemoteAddress(), newChar->GetName(), newChar->GetGUID().ToString()); + sScriptMgr->OnPlayerCreate(newChar.get()); + sCharacterCache->AddCharacterCacheEntry(newChar->GetGUID(), GetAccountId(), newChar->GetName(), newChar->getGender(), newChar->getRace(), newChar->getClass(), newChar->GetLevel()); + SendCharCreate(CHAR_CREATE_SUCCESS); + } + else + SendCharCreate(CHAR_CREATE_ERROR); + }); + }; + + if (allowTwoSideAccounts && !skipCinematics && createInfo->Class != CLASS_DEATH_KNIGHT) { - ptr->CleanupsBeforeDelete(); + finalizeCharacterCreation(PreparedQueryResult(nullptr)); + return; } - delete ptr; - }); - newChar->GetMotionMaster()->Initialize(); - if (!newChar->Create(sObjectMgr->GetGenerator().Generate(), createInfo.get())) - { - // Player not create (race/class/etc problem?) - SendCharCreate(CHAR_CREATE_ERROR); - return; - } - - if ((haveSameRace && skipCinematics == 1) || skipCinematics == 2) - newChar->setCinematic(1); // not show intro - - newChar->SetAtLoginFlag(AT_LOGIN_FIRST); // First login - - CharacterDatabaseTransaction characterTransaction = CharacterDatabase.BeginTransaction(); - LoginDatabaseTransaction trans = LoginDatabase.BeginTransaction(); - - // Player created, save it now - newChar->SaveToDB(characterTransaction, true, false); - createInfo->CharCount++; - - LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_REP_REALM_CHARACTERS); - stmt->SetData(0, createInfo->CharCount); - stmt->SetData(1, GetAccountId()); - stmt->SetData(2, realm.Id.Realm); - trans->Append(stmt); - - LoginDatabase.CommitTransaction(trans); - - AddTransactionCallback(CharacterDatabase.AsyncCommitTransaction(characterTransaction)).AfterComplete([this, newChar = std::move(newChar)](bool success) - { - if (success) - { - LOG_INFO("entities.player.character", "Account: {} (IP: {}) Create Character: {} {}", GetAccountId(), GetRemoteAddress(), newChar->GetName(), newChar->GetGUID().ToString()); - sScriptMgr->OnPlayerCreate(newChar.get()); - sCharacterCache->AddCharacterCacheEntry(newChar->GetGUID(), GetAccountId(), newChar->GetName(), newChar->getGender(), newChar->getRace(), newChar->getClass(), newChar->GetLevel()); - SendCharCreate(CHAR_CREATE_SUCCESS); - } - else - SendCharCreate(CHAR_CREATE_ERROR); - }); - }; - - if (allowTwoSideAccounts && !skipCinematics && createInfo->Class != CLASS_DEATH_KNIGHT) - { - finalizeCharacterCreation(PreparedQueryResult(nullptr)); - return; - } - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_CREATE_INFO); - stmt->SetData(0, GetAccountId()); - stmt->SetData(1, (skipCinematics == 1 || createInfo->Class == CLASS_DEATH_KNIGHT) ? 10 : 1); - queryCallback.WithPreparedCallback(std::move(finalizeCharacterCreation)).SetNextQuery(CharacterDatabase.AsyncQuery(stmt)); - })); + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_CREATE_INFO); + stmt->SetData(0, GetAccountId()); + stmt->SetData(1, (skipCinematics == 1 || createInfo->Class == CLASS_DEATH_KNIGHT) ? 10 : 1); + queryCallback.WithPreparedCallback(std::move(finalizeCharacterCreation)).SetNextQuery(CharacterDatabase.AsyncQuery(stmt)); + })); } void WorldSession::HandleCharDeleteOpcode(WorldPacket& recvData) @@ -780,13 +775,14 @@ void WorldSession::HandlePlayerLoginOpcode(WorldPacket& recvData) m_playerLoading = true; AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder)).AfterComplete([this](SQLQueryHolderBase const& holder) - { - HandlePlayerLoginFromDB(static_cast(holder)); - }); + { + HandlePlayerLoginFromDB(static_cast(holder)); + }); } void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder) { + m_playerLoading = true; ObjectGuid playerGuid = holder.GetGuid(); Player* pCurrChar = new Player(this); @@ -904,8 +900,7 @@ void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder) CharacterDatabase.Execute(stmt); LoginDatabasePreparedStatement* loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_ONLINE); - loginStmt->SetData(0, realm.Id.Realm); - loginStmt->SetData(1, GetAccountId()); + loginStmt->SetData(0, GetAccountId()); LoginDatabase.Execute(loginStmt); pCurrChar->SetInGameTime(GameTime::GetGameTimeMS().count()); @@ -956,7 +951,7 @@ void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder) if (sWorld->IsFFAPvPRealm() && !pCurrChar->IsGameMaster() && !pCurrChar->HasPlayerFlag(PLAYER_FLAGS_RESTING)) if (!pCurrChar->HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP)) { - sScriptMgr->OnPlayerFfaPvpStateUpdate(pCurrChar,true); + sScriptMgr->OnPlayerFfaPvpStateUpdate(pCurrChar, true); pCurrChar->SetByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP); } @@ -983,10 +978,10 @@ void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder) { // If we process the check while players are loading they won't be notified of the changes. pCurrChar->m_Events.AddEventAtOffset([pCurrChar] - { - pCurrChar->RemoveAtLoginFlag(AT_LOGIN_CHECK_ACHIEVS, true); - pCurrChar->CheckAllAchievementCriteria(); - }, 1s); + { + pCurrChar->RemoveAtLoginFlag(AT_LOGIN_CHECK_ACHIEVS, true); + pCurrChar->CheckAllAchievementCriteria(); + }, 1s); } bool firstLogin = pCurrChar->HasAtLoginFlag(AT_LOGIN_FIRST); @@ -1013,25 +1008,25 @@ void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder) ReputationMgr& repMgr = pCurrChar->GetReputationMgr(); auto SendFullReputation = [&repMgr](std::initializer_list factionsList) - { - for (auto const& itr : factionsList) { - repMgr.SetOneFactionReputation(sFactionStore.LookupEntry(itr), 42999.f, false); - } - }; + for (auto const& itr : factionsList) + { + repMgr.SetOneFactionReputation(sFactionStore.LookupEntry(itr), 42999.f, false); + } + }; SendFullReputation({ 942, 935, 936, 1011, 970, 967, 989, 932, 934, 1038, 1077, 1106, 1104, 1090, 1098, 1156, 1073, 1105, 1119, 1091 }); switch (pCurrChar->GetFaction()) { - case ALLIANCE: - SendFullReputation({ 72, 47, 69, 930, 730, 978, 54, 946, 1037, 1068, 1126, 1094, 1050 }); - break; - case HORDE: - SendFullReputation({ 76, 68, 81, 911, 729, 941, 530, 947, 1052, 1067, 1124, 1064, 1085 }); - break; - default: - break; + case ALLIANCE: + SendFullReputation({ 72, 47, 69, 930, 730, 978, 54, 946, 1037, 1068, 1126, 1094, 1050 }); + break; + case HORDE: + SendFullReputation({ 76, 68, 81, 911, 729, 941, 530, 947, 1052, 1067, 1124, 1064, 1085 }); + break; + default: + break; } repMgr.SendStates(); @@ -1050,7 +1045,7 @@ void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder) std::string IP_str = GetRemoteAddress(); LOG_INFO("entities.player", "Account: {} (IP: {}) Login Character:[{}] ({}) Level: {}", - GetAccountId(), IP_str, pCurrChar->GetName(), pCurrChar->GetGUID().ToString(), pCurrChar->GetLevel()); + GetAccountId(), IP_str, pCurrChar->GetName(), pCurrChar->GetGUID().ToString(), pCurrChar->GetLevel()); if (!pCurrChar->IsStandState() && !pCurrChar->HasUnitState(UNIT_STATE_STUNNED)) pCurrChar->SetStandState(UNIT_STAND_STATE_STAND); @@ -1345,7 +1340,7 @@ void WorldSession::HandleCharRenameOpcode(WorldPacket& recvData) std::shared_ptr renameInfo = std::make_shared(); recvData >> renameInfo->Guid - >> renameInfo->Name; + >> renameInfo->Name; // prevent character rename to invalid name if (!normalizePlayerName(renameInfo->Name)) @@ -1642,12 +1637,12 @@ void WorldSession::HandleCharCustomize(WorldPacket& recvData) } recvData >> customizeInfo->Name - >> customizeInfo->Gender - >> customizeInfo->Skin - >> customizeInfo->HairColor - >> customizeInfo->HairStyle - >> customizeInfo->FacialHair - >> customizeInfo->Face; + >> customizeInfo->Gender + >> customizeInfo->Skin + >> customizeInfo->HairColor + >> customizeInfo->HairStyle + >> customizeInfo->FacialHair + >> customizeInfo->Face; CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_CUSTOMIZE_INFO); stmt->SetData(0, customizeInfo->Guid.GetCounter()); @@ -1779,10 +1774,10 @@ void WorldSession::HandleEquipmentSetSave(WorldPacket& recvData) EquipmentSet eqSet; - eqSet.Guid = setGuid; - eqSet.Name = name; - eqSet.IconName = iconName; - eqSet.state = EQUIPMENT_SET_NEW; + eqSet.Guid = setGuid; + eqSet.Name = name; + eqSet.IconName = iconName; + eqSet.state = EQUIPMENT_SET_NEW; for (uint32 i = 0; i < EQUIPMENT_SLOT_END; ++i) { @@ -1937,13 +1932,13 @@ void WorldSession::HandleCharFactionOrRaceChange(WorldPacket& recvData) } recvData >> factionChangeInfo->Name - >> factionChangeInfo->Gender - >> factionChangeInfo->Skin - >> factionChangeInfo->HairColor - >> factionChangeInfo->HairStyle - >> factionChangeInfo->FacialHair - >> factionChangeInfo->Face - >> factionChangeInfo->Race; + >> factionChangeInfo->Gender + >> factionChangeInfo->Skin + >> factionChangeInfo->HairColor + >> factionChangeInfo->HairStyle + >> factionChangeInfo->FacialHair + >> factionChangeInfo->Face + >> factionChangeInfo->Race; // pussywizard: if (ObjectAccessor::FindConnectedPlayer(factionChangeInfo->Guid) || sWorldSessionMgr->FindOfflineSessionForCharacterGUID(factionChangeInfo->Guid.GetCounter())) @@ -2221,16 +2216,16 @@ void WorldSession::HandleCharFactionOrRaceChangeCallback(std::shared_ptrGetZoneAndAreaId(zoneId, areaId); + return GetClosestGraveyard(mapId, x, y, z, teamId, areaId, zoneId, player->getClass() == CLASS_DEATH_KNIGHT); +} + +GraveyardStruct const* Graveyard::GetClosestGraveyard(uint32 mapId, float x, float y, float z, TeamId teamId, uint32 areaId, uint32 zoneId, bool isDeathKnight) +{ if (!zoneId && !areaId) { if (z > -500) @@ -202,7 +207,7 @@ GraveyardStruct const* Graveyard::GetClosestGraveyard(Player* player, TeamId tea GRAVEYARD_ARCHERUS = 1405 }; - if (!player->IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_GRAVEYARD) && (graveyardLink.safeLocId == GRAVEYARD_EBON_HOLD || graveyardLink.safeLocId == GRAVEYARD_ARCHERUS)) + if (!isDeathKnight && (graveyardLink.safeLocId == GRAVEYARD_EBON_HOLD || graveyardLink.safeLocId == GRAVEYARD_ARCHERUS)) { continue; } diff --git a/src/server/game/Misc/GameGraveyard.h b/src/server/game/Misc/GameGraveyard.h index 1ba22d1f7..741dd0ea8 100644 --- a/src/server/game/Misc/GameGraveyard.h +++ b/src/server/game/Misc/GameGraveyard.h @@ -57,6 +57,7 @@ public: GraveyardStruct const* GetGraveyard(const std::string& name) const; GraveyardStruct const* GetDefaultGraveyard(TeamId teamId); GraveyardStruct const* GetClosestGraveyard(Player* player, TeamId teamId, bool nearCorpse = false); + GraveyardStruct const* GetClosestGraveyard(uint32 mapId, float x, float y, float z, TeamId teamId, uint32 areaId, uint32 zoneId, bool isDeathKnight); GraveyardData const* FindGraveyardData(uint32 id, uint32 zone); GraveyardContainer const& GetGraveyardData() const { return _graveyardStore; } bool AddGraveyardLink(uint32 id, uint32 zoneId, TeamId teamId, bool persist = true); diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp index 8e4f91c16..71d21d48a 100644 --- a/src/server/game/Movement/MotionMaster.cpp +++ b/src/server/game/Movement/MotionMaster.cpp @@ -923,6 +923,48 @@ void MotionMaster::MoveRotate(uint32 time, RotateDirection direction) Mutate(new RotateMovementGenerator(time, direction), MOTION_SLOT_ACTIVE); } +#ifdef MOD_PLAYERBOTS +void MotionMaster::MoveKnockbackFromForPlayer(float srcX, float srcY, float speedXY, float speedZ) +{ + if (speedXY <= 0.1f) + return; + + Position dest = _owner->GetPosition(); + float moveTimeHalf = speedZ / Movement::gravity; + float dist = 2 * moveTimeHalf * speedXY; + float max_height = -Movement::computeFallElevation(moveTimeHalf, false, -speedZ); + + // Use a mmap raycast to get a valid destination. + _owner->MovePositionToFirstCollision(dest, dist, _owner->GetRelativeAngle(srcX, srcY) + float(M_PI)); + + Movement::MoveSplineInit init(_owner); + init.MoveTo(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ()); + init.SetParabolic(max_height, 0); + init.SetOrientationFixed(true); + init.SetVelocity(speedXY); + Mutate(new EffectMovementGenerator(init, 0), MOTION_SLOT_CONTROLLED); +} + +// Similar to MovePoint except setting orientationInversed +void MotionMaster::MovePointBackwards(uint32 id, float x, float y, float z, bool generatePath, bool forceDestination, MovementSlot slot, float orientation /* = 0.0f*/) +{ + if (_owner->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE)) + return; + + if (_owner->IsPlayer()) + { + LOG_DEBUG("movement.motionmaster", "Player ({}) targeted point (Id: {} X: {} Y: {} Z: {})", _owner->GetGUID().ToString(), id, x, y, z); + Mutate(new PointMovementGenerator(id, x, y, z, FORCED_MOVEMENT_NONE, 0.0f, orientation, nullptr, generatePath, forceDestination, std::nullopt, ObjectGuid::Empty, true), slot); + } + else + { + LOG_DEBUG("movement.motionmaster", "Creature ({}) targeted point (ID: {} X: {} Y: {} Z: {})", _owner->GetGUID().ToString(), id, x, y, z); + Mutate(new PointMovementGenerator(id, x, y, z, FORCED_MOVEMENT_NONE, 0.0f, orientation, nullptr, generatePath, forceDestination, std::nullopt, ObjectGuid::Empty, true), slot); + } +} + +#endif + void MotionMaster::propagateSpeedChange() { /*Impl::container_type::iterator it = Impl::c.begin(); diff --git a/src/server/game/Movement/MotionMaster.h b/src/server/game/Movement/MotionMaster.h index 4ac140260..e4b2d2c20 100644 --- a/src/server/game/Movement/MotionMaster.h +++ b/src/server/game/Movement/MotionMaster.h @@ -262,7 +262,10 @@ public: void MoveDistract(uint32 time); void MoveWaypoint(uint32 path_id, bool repeatable, PathSource pathSource = PathSource::WAYPOINT_MGR); void MoveRotate(uint32 time, RotateDirection direction); - +#ifdef MOD_PLAYERBOTS + void MoveKnockbackFromForPlayer(float srcX, float srcY, float speedXY, float speedZ); + void MovePointBackwards(uint32 id, float x, float y, float z, bool generatePath = true, bool forceDestination = true, MovementSlot slot = MOTION_SLOT_ACTIVE, float orientation = 0.0f); +#endif [[nodiscard]] MovementGeneratorType GetCurrentMovementGeneratorType() const; [[nodiscard]] MovementGeneratorType GetMotionSlotType(int slot) const; bool HasMovementGeneratorType(MovementGeneratorType type) const; diff --git a/src/server/game/Scripting/ScriptDefines/DatabaseScript.cpp b/src/server/game/Scripting/ScriptDefines/DatabaseScript.cpp index df84d2156..e2831a9d1 100644 --- a/src/server/game/Scripting/ScriptDefines/DatabaseScript.cpp +++ b/src/server/game/Scripting/ScriptDefines/DatabaseScript.cpp @@ -19,6 +19,21 @@ #include "ScriptMgr.h" #include "ScriptMgrMacros.h" +bool ScriptMgr::OnDatabasesLoading() +{ + auto ret = IsValidBoolScript([&](DatabaseScript* script) + { + return !script->OnDatabasesLoading(); + }); + + if (ret && *ret) + { + return false; + } + + return true; +} + void ScriptMgr::OnAfterDatabasesLoaded(uint32 updateFlags) { CALL_ENABLED_HOOKS(DatabaseScript, DATABASEHOOK_ON_AFTER_DATABASES_LOADED, script->OnAfterDatabasesLoaded(updateFlags)); @@ -29,6 +44,46 @@ void ScriptMgr::OnAfterDatabaseLoadCreatureTemplates(std::vectorOnAfterDatabaseLoadCreatureTemplates(creatureTemplates)); } +void ScriptMgr::OnDatabasesKeepAlive() +{ + ExecuteScript([&](DatabaseScript* script) + { + script->OnDatabasesKeepAlive(); + }); +} + +void ScriptMgr::OnDatabasesClosing() +{ + ExecuteScript([&](DatabaseScript* script) + { + script->OnDatabasesClosing(); + }); +} + +void ScriptMgr::OnDatabaseWarnAboutSyncQueries(bool apply) +{ + ExecuteScript([&](DatabaseScript* script) + { + script->OnDatabaseWarnAboutSyncQueries(apply); + }); +} + +void ScriptMgr::OnDatabaseSelectIndexLogout(Player* player, uint32& statementIndex, uint32& statementParam) +{ + ExecuteScript([&](DatabaseScript* script) + { + script->OnDatabaseSelectIndexLogout(player, statementIndex, statementParam); + }); +} + +void ScriptMgr::OnDatabaseGetDBRevision(std::string& revision) +{ + ExecuteScript([&](DatabaseScript* script) + { + script->OnDatabaseGetDBRevision(revision); + }); +} + DatabaseScript::DatabaseScript(const char* name, std::vector enabledHooks) : ScriptObject(name, DATABASEHOOK_END) { diff --git a/src/server/game/Scripting/ScriptDefines/DatabaseScript.h b/src/server/game/Scripting/ScriptDefines/DatabaseScript.h index 708f0a986..21b55ee8b 100644 --- a/src/server/game/Scripting/ScriptDefines/DatabaseScript.h +++ b/src/server/game/Scripting/ScriptDefines/DatabaseScript.h @@ -52,6 +52,13 @@ public: */ virtual void OnAfterDatabaseLoadCreatureTemplates(std::vector /*creatureTemplates*/) { } + [[nodiscard]] virtual bool OnDatabasesLoading() { return true; } + virtual void OnDatabasesKeepAlive() { } + virtual void OnDatabasesClosing() { } + virtual void OnDatabaseWarnAboutSyncQueries(bool /*apply*/) { } + virtual void OnDatabaseSelectIndexLogout(Player* /*player*/, uint32& /*statementIndex*/, uint32& /*statementParam*/) { } + virtual void OnDatabaseGetDBRevision(std::string& /*revision*/) { } + }; #endif diff --git a/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp b/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp index 62439d284..c2afb5804 100644 --- a/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp +++ b/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp @@ -194,11 +194,17 @@ void ScriptMgr::OnPlayerBeforeUpdate(Player* player, uint32 p_time) CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_BEFORE_UPDATE, script->OnPlayerBeforeUpdate(player, p_time)); } +void ScriptMgr::OnPlayerAfterUpdate(Player* player, uint32 p_time) +{ + CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_AFTER_UPDATE, script->OnPlayerAfterUpdate(player, p_time)); +} + void ScriptMgr::OnPlayerUpdate(Player* player, uint32 p_time) { CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_UPDATE, script->OnPlayerUpdate(player, p_time)); } + void ScriptMgr::OnPlayerLogin(Player* player) { CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_LOGIN, script->OnPlayerLogin(player)); diff --git a/src/server/game/Scripting/ScriptDefines/PlayerScript.h b/src/server/game/Scripting/ScriptDefines/PlayerScript.h index 5ee56e7ac..8177dea2c 100644 --- a/src/server/game/Scripting/ScriptDefines/PlayerScript.h +++ b/src/server/game/Scripting/ScriptDefines/PlayerScript.h @@ -45,6 +45,7 @@ enum PlayerHook PLAYERHOOK_ON_AFTER_SPEC_SLOT_CHANGED, PLAYERHOOK_ON_BEFORE_UPDATE, PLAYERHOOK_ON_UPDATE, + PLAYERHOOK_ON_AFTER_UPDATE, PLAYERHOOK_ON_MONEY_CHANGED, PLAYERHOOK_ON_BEFORE_LOOT_MONEY, PLAYERHOOK_ON_GIVE_EXP, @@ -55,7 +56,12 @@ enum PlayerHook PLAYERHOOK_ON_DUEL_REQUEST, PLAYERHOOK_ON_DUEL_START, PLAYERHOOK_ON_DUEL_END, + PLAYERHOOK_ON_CHAT, PLAYERHOOK_ON_BEFORE_SEND_CHAT_MESSAGE, + PLAYERHOOK_ON_CHAT_WITH_RECEIVER, + PLAYERHOOK_ON_CHAT_WITH_GROUP, + PLAYERHOOK_ON_CHAT_WITH_GUILD, + PLAYERHOOK_ON_CHAT_WITH_CHANNEL, PLAYERHOOK_ON_EMOTE, PLAYERHOOK_ON_TEXT_EMOTE, PLAYERHOOK_ON_SPELL_CAST, @@ -263,6 +269,7 @@ public: // Called for player::update virtual void OnPlayerBeforeUpdate(Player* /*player*/, uint32 /*p_time*/) { } + virtual void OnPlayerAfterUpdate(Player* /*player*/, uint32 /*p_time*/) { } virtual void OnPlayerUpdate(Player* /*player*/, uint32 /*p_time*/) { } // Called when a player's money is modified (before the modification is done) diff --git a/src/server/game/Scripting/ScriptDefines/PlayerbotsScript.cpp b/src/server/game/Scripting/ScriptDefines/PlayerbotsScript.cpp new file mode 100644 index 000000000..22379a1fb --- /dev/null +++ b/src/server/game/Scripting/ScriptDefines/PlayerbotsScript.cpp @@ -0,0 +1,105 @@ +/* + * 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 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 . + */ + +#include "ScriptMgr.h" +#include "ScriptMgrMacros.h" + +bool ScriptMgr::OnPlayerbotCheckLFGQueue(lfg::Lfg5Guids const& guidsList) +{ + auto ret = IsValidBoolScript([&](PlayerbotScript* script) + { + return !script->OnPlayerbotCheckLFGQueue(guidsList); + }); + + if (ret && *ret) + { + return false; + } + + return true; +} + +void ScriptMgr::OnPlayerbotCheckKillTask(Player* player, Unit* victim) +{ + ExecuteScript([&](PlayerbotScript* script) + { + script->OnPlayerbotCheckKillTask(player, victim); + }); +} + +void ScriptMgr::OnPlayerbotCheckPetitionAccount(Player* player, bool& found) +{ + ExecuteScript([&](PlayerbotScript* script) + { + script->OnPlayerbotCheckPetitionAccount(player, found); + }); +} + +bool ScriptMgr::OnPlayerbotCheckUpdatesToSend(Player* player) +{ + auto ret = IsValidBoolScript([&](PlayerbotScript* script) + { + return !script->OnPlayerbotCheckUpdatesToSend(player); + }); + + if (ret && *ret) + { + return false; + } + + return true; +} + +void ScriptMgr::OnPlayerbotPacketSent(Player* player, WorldPacket const* packet) +{ + ExecuteScript([&](PlayerbotScript* script) + { + script->OnPlayerbotPacketSent(player, packet); + }); +} + +void ScriptMgr::OnPlayerbotUpdate(uint32 diff) +{ + ExecuteScript([&](PlayerbotScript* script) + { + script->OnPlayerbotUpdate(diff); + }); +} + +void ScriptMgr::OnPlayerbotUpdateSessions(Player* player) +{ + ExecuteScript([&](PlayerbotScript* script) + { + script->OnPlayerbotUpdateSessions(player); + }); +} + +void ScriptMgr::OnPlayerbotLogout(Player* player) +{ + ExecuteScript([&](PlayerbotScript* script) + { + script->OnPlayerbotLogout(player); + }); +} + +void ScriptMgr::OnPlayerbotLogoutBots() +{ + ExecuteScript([&](PlayerbotScript* script) + { + script->OnPlayerbotLogoutBots(); + }); +} diff --git a/src/server/game/Scripting/ScriptDefines/ServerScript.cpp b/src/server/game/Scripting/ScriptDefines/ServerScript.cpp index 9794127f0..2860141a8 100644 --- a/src/server/game/Scripting/ScriptDefines/ServerScript.cpp +++ b/src/server/game/Scripting/ScriptDefines/ServerScript.cpp @@ -43,6 +43,15 @@ void ScriptMgr::OnSocketClose(std::shared_ptr socket) CALL_ENABLED_HOOKS(ServerScript, SERVERHOOK_ON_SOCKET_CLOSE, script->OnSocketClose(socket)); } +void ScriptMgr::OnPacketReceived(WorldSession* session, WorldPacket const& packet) +{ + WorldPacket copy(packet); + ExecuteScript([&](ServerScript* script) + { + script->OnPacketReceived(session, copy); + }); +} + bool ScriptMgr::CanPacketSend(WorldSession* session, WorldPacket const& packet) { ASSERT(session); diff --git a/src/server/game/Scripting/ScriptDefines/ServerScript.h b/src/server/game/Scripting/ScriptDefines/ServerScript.h index 5f3a7787b..64b6762d2 100644 --- a/src/server/game/Scripting/ScriptDefines/ServerScript.h +++ b/src/server/game/Scripting/ScriptDefines/ServerScript.h @@ -70,6 +70,8 @@ public: * @return True if you want to continue receive the packet, false if you want to disallow receive the packet */ [[nodiscard]] virtual bool CanPacketReceive(WorldSession* /*session*/, WorldPacket& /*packet*/) { return true; } + + virtual void OnPacketReceived(WorldSession* /*session*/, WorldPacket const& /*packet*/) { } }; #endif diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 34ad17546..0d99f7fd0 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -58,6 +58,11 @@ ScriptMgr* ScriptMgr::instance() return &instance; } +PlayerbotScript::PlayerbotScript(const char* name) : ScriptObject(name) +{ + ScriptRegistry::AddScript(this); +} + void ScriptMgr::Initialize() { LOG_INFO("server.loading", "> Loading C++ scripts"); @@ -143,6 +148,7 @@ void ScriptMgr::Unload() SCR_CLEAR(); SCR_CLEAR(); SCR_CLEAR(); + SCR_CLEAR(); SCR_CLEAR(); SCR_CLEAR(); SCR_CLEAR(); @@ -227,6 +233,7 @@ void ScriptMgr::CheckIfScriptsInDatabaseExist() !ScriptRegistry::GetScriptById(sid) && !ScriptRegistry::GetScriptById(sid) && !ScriptRegistry::GetScriptById(sid) && + !ScriptRegistry::GetScriptById(sid) && !ScriptRegistry::GetScriptById(sid)) { LOG_ERROR("sql.sql", "Script named '{}' is assigned in the database, but has no code!", scriptName); diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 0dadd18f6..e1d535007 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -105,7 +105,26 @@ namespace Acore::ChatCommands */ -// Manages registration, loading, and execution of scripts. +class PlayerbotScript : public ScriptObject +{ +protected: + + PlayerbotScript(const char* name); + +public: + bool IsDatabaseBound() const { return false; } + + [[nodiscard]] virtual bool OnPlayerbotCheckLFGQueue(lfg::Lfg5Guids const& /*guidsList*/) { return true; } + virtual void OnPlayerbotCheckKillTask(Player* /*player*/, Unit* /*victim*/) { } + virtual void OnPlayerbotCheckPetitionAccount(Player* /*player*/, bool& /*found*/) { } + [[nodiscard]] virtual bool OnPlayerbotCheckUpdatesToSend(Player* /*player*/) { return true; } + virtual void OnPlayerbotPacketSent(Player* /*player*/, WorldPacket const* /*packet*/) { } + virtual void OnPlayerbotUpdate(uint32 /*diff*/) { } + virtual void OnPlayerbotUpdateSessions(Player* /*player*/) { } + virtual void OnPlayerbotLogout(Player* /*player*/) { } + virtual void OnPlayerbotLogoutBots() { } +}; + class ScriptMgr { friend class ScriptObject; @@ -158,6 +177,7 @@ public: /* ServerScript */ void OnSocketOpen(std::shared_ptr socket); void OnSocketClose(std::shared_ptr socket); bool CanPacketReceive(WorldSession* session, WorldPacket const& packet); + void OnPacketReceived(WorldSession* session, WorldPacket const& packet); bool CanPacketSend(WorldSession* session, WorldPacket const& packet); public: /* WorldScript */ @@ -298,6 +318,7 @@ public: /* PlayerScript */ void OnPlayerReleasedGhost(Player* player); void OnPlayerSendInitialPacketsBeforeAddToMap(Player* player, WorldPacket& data); void OnPlayerBeforeUpdate(Player* player, uint32 p_time); + void OnPlayerAfterUpdate(Player* player, uint32 diff); void OnPlayerUpdate(Player* player, uint32 p_time); void OnPlayerPVPKill(Player* killer, Player* killed); void OnPlayerPVPFlagChange(Player* player, bool state); @@ -679,8 +700,14 @@ public: /* CommandSC */ public: /* DatabaseScript */ + bool OnDatabasesLoading(); void OnAfterDatabasesLoaded(uint32 updateFlags); void OnAfterDatabaseLoadCreatureTemplates(std::vector creatureTemplateStore); + void OnDatabasesKeepAlive(); + void OnDatabasesClosing(); + void OnDatabaseWarnAboutSyncQueries(bool apply); + void OnDatabaseSelectIndexLogout(Player* player, uint32& statementIndex, uint32& statementParam); + void OnDatabaseGetDBRevision(std::string& revision); public: /* WorldObjectScript */ @@ -698,6 +725,18 @@ public: /* LootScript */ void OnLootMoney(Player* player, uint32 gold); +public: /* PlayerbotScript */ + + bool OnPlayerbotCheckLFGQueue(lfg::Lfg5Guids const& guidsList); + void OnPlayerbotCheckKillTask(Player* player, Unit* victim); + void OnPlayerbotCheckPetitionAccount(Player* player, bool& found); + bool OnPlayerbotCheckUpdatesToSend(Player* player); + void OnPlayerbotPacketSent(Player* player, WorldPacket const* packet); + void OnPlayerbotUpdate(uint32 diff); + void OnPlayerbotUpdateSessions(Player* player); + void OnPlayerbotLogout(Player* player); + void OnPlayerbotLogoutBots(); + public: /* TicketScript */ void OnTicketCreate(GmTicket* ticket); diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 254328b80..8a212f546 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -105,7 +105,7 @@ bool WorldSessionFilter::Process(WorldPacket* packet) /// WorldSession constructor WorldSession::WorldSession(uint32 id, std::string&& name, uint32 accountFlags, std::shared_ptr sock, AccountTypes sec, uint8 expansion, - time_t mute_time, LocaleConstant locale, uint32 recruiter, bool isARecruiter, bool skipQueue, uint32 TotalTime) : + time_t mute_time, LocaleConstant locale, uint32 recruiter, bool isARecruiter, bool skipQueue, uint32 TotalTime, bool isBot) : m_muteTime(mute_time), m_timeOutTime(0), AntiDOS(this), @@ -137,7 +137,8 @@ WorldSession::WorldSession(uint32 id, std::string&& name, uint32 accountFlags, s _timeSyncClockDeltaQueue(6), _timeSyncClockDelta(0), _pendingTimeSyncRequests(), - _orderCounter(0) + _orderCounter(0), + _isBot(isBot) { memset(m_Tutorials, 0, sizeof(m_Tutorials)); @@ -153,6 +154,10 @@ WorldSession::WorldSession(uint32 id, std::string&& name, uint32 accountFlags, s ResetTimeOutTime(false); LoginDatabase.Execute("UPDATE account SET online = 1 WHERE id = {};", GetAccountId()); // One-time query } + else if (isBot) + { + m_Address = "bot"; + } } /// WorldSession destructor @@ -251,6 +256,14 @@ ObjectGuid::LowType WorldSession::GetGuidLow() const /// Send a packet to the client void WorldSession::SendPacket(WorldPacket const* packet) { + if (packet->GetOpcode() == NULL_OPCODE) + { + LOG_ERROR("network.opcode", "{} send NULL_OPCODE", GetPlayerInfo()); + return; + } + + sScriptMgr->OnPlayerbotPacketSent(GetPlayer(), packet); + if (!m_Socket) return; @@ -401,6 +414,9 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) opHandle->Call(this, *packet); LogUnprocessedTail(packet); +#ifdef MOD_PLAYERBOTS + sScriptMgr->OnPacketReceived(this, *packet); +#endif } // lag can cause STATUS_LOGGEDIN opcodes to arrive after the player started a transfer @@ -419,6 +435,9 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) opHandle->Call(this, *packet); LogUnprocessedTail(packet); +#ifdef MOD_PLAYERBOTS + sScriptMgr->OnPacketReceived(this, *packet); +#endif } break; case STATUS_TRANSFER: @@ -429,6 +448,9 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) opHandle->Call(this, *packet); LogUnprocessedTail(packet); +#ifdef MOD_PLAYERBOTS + sScriptMgr->OnPacketReceived(this, *packet); +#endif } break; case STATUS_AUTHED: @@ -445,6 +467,9 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) opHandle->Call(this, *packet); LogUnprocessedTail(packet); +#ifdef MOD_PLAYERBOTS + sScriptMgr->OnPacketReceived(this, *packet); +#endif break; case STATUS_NEVER: LOG_ERROR("network.opcode", "Received not allowed opcode {} from {}", @@ -533,6 +558,8 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) //logout procedure should happen only in World::UpdateSessions() method!!! if (updater.ProcessUnsafe()) { + sScriptMgr->OnPlayerbotUpdateSessions(GetPlayer()); + if (m_Socket && m_Socket->IsOpen() && _warden) { _warden->Update(diff); @@ -625,6 +652,8 @@ void WorldSession::LogoutPlayer(bool save) if (ObjectGuid lguid = _player->GetLootGUID()) DoLootRelease(lguid); + sScriptMgr->OnPlayerbotLogout(_player); + ///- If the player just died before logging out, make him appear as a ghost //FIXME: logout must be delayed in case lost connection with client in time of combat if (_player->GetDeathTimer()) @@ -757,6 +786,10 @@ void WorldSession::LogoutPlayer(bool save) LOG_INFO("entities.player", "Account: {} (IP: {}) Logout Character:[{}] ({}) Level: {}", GetAccountId(), GetRemoteAddress(), _player->GetName(), _player->GetGUID().ToString(), _player->GetLevel()); + uint32 statementIndex = CHAR_UPD_ACCOUNT_ONLINE; + uint32 statementParam = GetAccountId(); + sScriptMgr->OnDatabaseSelectIndexLogout(_player, statementIndex, statementParam); + //! Remove the player from the world // the player may not be in the world when logging out // e.g if he got disconnected during a transfer to another map @@ -776,8 +809,8 @@ void WorldSession::LogoutPlayer(bool save) LOG_DEBUG("network", "SESSION: Sent SMSG_LOGOUT_COMPLETE Message"); //! Since each account can only have one online character at any given time, ensure all characters for active account are marked as offline - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ACCOUNT_ONLINE); - stmt->SetData(0, GetAccountId()); + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CharacterDatabaseStatements(statementIndex)); + stmt->SetData(0, statementParam); CharacterDatabase.Execute(stmt); } @@ -1516,3 +1549,8 @@ void WorldSession::SetPacketLogging(bool state) if (m_Socket) m_Socket->SetPacketLogging(state); } + +LockedQueue& WorldSession::GetPacketQueue() +{ + return _recvQueue; +} diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 6a1c554ca..283a301b2 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -29,6 +29,7 @@ #include "Common.h" #include "DatabaseEnv.h" #include "GossipDef.h" +#include "QueryHolder.h" #include "Packet.h" #include "SharedDefines.h" #include "World.h" @@ -40,7 +41,6 @@ class Creature; class GameObject; class InstanceSave; class Item; -class LoginQueryHolder; class LoadPetFromDBQueryHolder; class Object; class Pet; @@ -270,6 +270,20 @@ enum CharterTypes ARENA_TEAM_CHARTER_5v5_TYPE = 5 }; +class LoginQueryHolder : public CharacterDatabaseQueryHolder +{ + private: + uint32 m_accountId; + ObjectGuid m_guid; + + public: + LoginQueryHolder(uint32 accountId, ObjectGuid guid); + + ObjectGuid GetGuid() const { return m_guid; } + uint32 GetAccountId() const { return m_accountId; } + bool Initialize(); +}; + //class to deal with packet processing //allows to determine if next packet is safe to be processed class PacketFilter @@ -314,6 +328,11 @@ class CharacterCreateInfo friend class WorldSession; friend class Player; +public: + CharacterCreateInfo(std::string const name = "", uint8 _race = 0, uint8 _class = 0, uint8 gender = 0, uint8 skin = 0, uint8 face = 0, + uint8 hairStyle = 0, uint8 hairColor = 0, uint8 facialHair = 0) + : Name(name), Race(_race), Class(_class), Gender(gender), Skin(skin), Face(face), HairStyle(hairStyle), HairColor(hairColor), FacialHair(facialHair) { } + protected: /// User specified variables std::string Name; @@ -374,7 +393,7 @@ struct PacketCounter class WorldSession { public: - WorldSession(uint32 id, std::string&& name, uint32 accountFlags, std::shared_ptr sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale, uint32 recruiter, bool isARecruiter, bool skipQueue, uint32 TotalTime); + WorldSession(uint32 id, std::string&& name, uint32 accountFlags, std::shared_ptr sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale, uint32 recruiter, bool isARecruiter, bool skipQueue, uint32 TotalTime, bool is_bot = false); ~WorldSession(); uint32 GetAccountFlags() const { return _accountFlags; } @@ -1132,6 +1151,8 @@ public: // opcodes handlers void SetKicked(bool val) { _kicked = val; } bool IsSocketClosed() const; + void SetAddress(std::string const& address) { m_Address = address; } + /* * CALLBACKS */ @@ -1145,6 +1166,13 @@ public: // opcodes handlers void SetPacketLogging(bool state); + LockedQueue& GetPacketQueue(); + + [[nodiscard]] bool IsBot() const + { + return _isBot; + } + private: void ProcessQueryCallbacks(); @@ -1257,6 +1285,8 @@ private: uint32 _orderCounter; + bool _isBot; + WorldSession(WorldSession const& right) = delete; WorldSession& operator=(WorldSession const& right) = delete; }; diff --git a/src/server/game/Server/WorldSessionMgr.cpp b/src/server/game/Server/WorldSessionMgr.cpp index b43099669..91256db9b 100644 --- a/src/server/game/Server/WorldSessionMgr.cpp +++ b/src/server/game/Server/WorldSessionMgr.cpp @@ -20,6 +20,7 @@ #include "GameTime.h" #include "Metric.h" #include "Player.h" +#include "ScriptMgr.h" #include "World.h" #include "WorldSession.h" #include "WorldSessionMgr.h" @@ -190,6 +191,10 @@ void WorldSessionMgr::KickAll() // pussywizard: kick offline sessions for (SessionMap::const_iterator itr = _offlineSessions.begin(); itr != _offlineSessions.end(); ++itr) itr->second->KickPlayer("KickAll offline sessions"); + +#ifdef MOD_PLAYERBOTS + sScriptMgr->OnPlayerbotLogoutBots(); +#endif } /// Kick (and save) all players with security level less `sec` diff --git a/src/server/game/World/IWorld.h b/src/server/game/World/IWorld.h index 03e70e55c..dc6c60681 100644 --- a/src/server/game/World/IWorld.h +++ b/src/server/game/World/IWorld.h @@ -22,6 +22,7 @@ #include "Common.h" #include "Duration.h" #include "ObjectGuid.h" +#include "QueryHolder.h" #include "SharedDefines.h" #include "WorldConfig.h" #include @@ -104,12 +105,16 @@ public: [[nodiscard]] virtual LocaleConstant GetAvailableDbcLocale(LocaleConstant locale) const = 0; virtual void LoadDBVersion() = 0; [[nodiscard]] virtual char const* GetDBVersion() const = 0; +#ifdef MOD_PLAYERBOTS + [[nodiscard]] virtual char const* GetPlayerbotsDBRevision() const = 0; +#endif virtual void UpdateAreaDependentAuras() = 0; [[nodiscard]] virtual uint32 GetCleaningFlags() const = 0; virtual void SetCleaningFlags(uint32 flags) = 0; virtual void ResetEventSeasonalQuests(uint16 event_id) = 0; [[nodiscard]] virtual std::string const& GetRealmName() const = 0; virtual void SetRealmName(std::string name) = 0; + virtual SQLQueryHolderCallback& AddQueryHolderCallback(SQLQueryHolderCallback&& callback) = 0; }; #endif //AZEROTHCORE_IWORLD_H diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index e1de134a2..d072bd56f 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -67,6 +67,7 @@ #include "ObjectMgr.h" #include "Opcodes.h" #include "OutdoorPvPMgr.h" +#include "QueryHolder.h" #include "PetitionMgr.h" #include "Player.h" #include "PlayerDump.h" @@ -1171,6 +1172,8 @@ void World::Update(uint32 diff) ResetGuildCap(); } + sScriptMgr->OnPlayerbotUpdate(diff); + { // pussywizard: handle expired auctions, auctions expired when realm was offline are also handled here (not during loading when many required things aren't loaded yet) METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update expired auctions")); @@ -1290,6 +1293,7 @@ void World::Update(uint32 diff) CharacterDatabase.KeepAlive(); LoginDatabase.KeepAlive(); WorldDatabase.KeepAlive(); + sScriptMgr->OnDatabasesKeepAlive(); } { @@ -1456,6 +1460,7 @@ void World::_UpdateGameTime() void World::ShutdownServ(uint32 time, uint32 options, uint8 exitcode, std::string const& reason) { // ignore if server shutdown at next tick + if (IsStopped()) return; @@ -1803,6 +1808,12 @@ void World::UpdateAreaDependentAuras() void World::ProcessQueryCallbacks() { _queryProcessor.ProcessReadyCallbacks(); + _queryHolderProcessor.ProcessReadyCallbacks(); +} + +SQLQueryHolderCallback& World::AddQueryHolderCallback(SQLQueryHolderCallback&& callback) +{ + return _queryHolderProcessor.AddCallback(std::move(callback)); } bool World::IsPvPRealm() const diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index 942665b4d..f42004e30 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -229,6 +229,9 @@ public: // used World DB version void LoadDBVersion() override; [[nodiscard]] char const* GetDBVersion() const override { return _dbVersion.c_str(); } +#ifdef MOD_PLAYERBOTS + [[nodiscard]] char const* GetPlayerbotsDBRevision() const override { return m_PlayerbotsDBRevision.c_str(); } +#endif void UpdateAreaDependentAuras() override; @@ -256,6 +259,9 @@ protected: void ResetRandomBG(); void CalendarDeleteOldEvents(); void ResetGuildCap(); + + SQLQueryHolderCallback& AddQueryHolderCallback(SQLQueryHolderCallback&& callback) override; + private: WorldConfig _worldConfig; @@ -300,9 +306,13 @@ private: // used versions std::string _dbVersion; uint32 _dbClientCacheVersion; +#ifdef MOD_PLAYERBOTS + std::string m_PlayerbotsDBRevision; +#endif void ProcessQueryCallbacks(); QueryCallbackProcessor _queryProcessor; + AsyncCallbackProcessor _queryHolderProcessor; /** * @brief Executed when a World Session is being finalized. Be it from a normal login or via queue popping. diff --git a/src/server/scripts/Commands/cs_server.cpp b/src/server/scripts/Commands/cs_server.cpp index 98bfbbac3..63815aeec 100644 --- a/src/server/scripts/Commands/cs_server.cpp +++ b/src/server/scripts/Commands/cs_server.cpp @@ -213,6 +213,10 @@ public: handler->PSendSysMessage("Default DBC locale: {}.\nAll available DBC locales: {}", localeNames[defaultLocale], availableLocales); handler->PSendSysMessage("Using World DB: {}", sWorld->GetDBVersion()); +#ifdef MOD_PLAYERBOTS + handler->PSendSysMessage("Using Playerbots DB Revision: {}", sWorld->GetPlayerbotsDBRevision()); +#endif + std::string lldb = "No updates found!"; if (QueryResult resL = LoginDatabase.Query("SELECT name FROM updates ORDER BY name DESC LIMIT 1")) @@ -240,6 +244,10 @@ public: handler->PSendSysMessage("LoginDatabase queue size: {}", LoginDatabase.QueueSize()); handler->PSendSysMessage("CharacterDatabase queue size: {}", CharacterDatabase.QueueSize()); handler->PSendSysMessage("WorldDatabase queue size: {}", WorldDatabase.QueueSize()); +#ifdef MOD_PLAYERBOTS + handler->PSendSysMessage("PlayerbotsDatabase queue size: {}", PlayerbotsDatabase.QueueSize()); +#endif + if (Acore::Module::GetEnableModulesList().empty()) handler->PSendSysMessage("No modules are enabled"); diff --git a/src/server/shared/DataStores/DBCDatabaseLoader.cpp b/src/server/shared/DataStores/DBCDatabaseLoader.cpp index 44e65653d..883fe527f 100644 --- a/src/server/shared/DataStores/DBCDatabaseLoader.cpp +++ b/src/server/shared/DataStores/DBCDatabaseLoader.cpp @@ -29,8 +29,7 @@ DBCDatabaseLoader::DBCDatabaseLoader(char const* tableName, char const* dbcForma _stringPool(stringPool) { // Get sql index position - int32 indexPos = -1; - _recordSize = DBCFileLoader::GetFormatRecordSize(_dbcFormat, &indexPos); + _recordSize = DBCFileLoader::GetFormatRecordSize(_dbcFormat, &_sqlIndexPos); ASSERT(_recordSize); } @@ -72,11 +71,11 @@ char* DBCDatabaseLoader::Load(uint32& records, char**& indexTable) { Field* fields = result->Fetch(); uint32 indexValue = fields[_sqlIndexPos].Get(); - char* dataValue = indexTable[indexValue]; + char* oldDataValue = indexTable[indexValue]; // If exist in DBC file override from DB newIndexes[newRecords] = indexValue; - dataValue = &dataTable[newRecords++ * _recordSize]; + char* dataValue = &dataTable[newRecords++ * _recordSize]; uint32 dataOffset = 0; uint32 sqlColumnNumber = 0; @@ -100,7 +99,15 @@ char* DBCDatabaseLoader::Load(uint32& records, char**& indexTable) dataOffset += sizeof(uint8); break; case FT_STRING: - *reinterpret_cast(&dataValue[dataOffset]) = CloneStringToPool(fields[sqlColumnNumber].Get()); + // not override string if new string is empty + if (fields[sqlColumnNumber].Get().empty() && oldDataValue) + { + *reinterpret_cast(&dataValue[dataOffset]) = *reinterpret_cast(&oldDataValue[dataOffset]); + } + else + { + *reinterpret_cast(&dataValue[dataOffset]) = CloneStringToPool(fields[sqlColumnNumber].Get()); + } dataOffset += sizeof(char*); break; case FT_SORT: diff --git a/src/server/shared/DataStores/DBCStructure.h b/src/server/shared/DataStores/DBCStructure.h index 4e7e8ca2c..98ddad98d 100644 --- a/src/server/shared/DataStores/DBCStructure.h +++ b/src/server/shared/DataStores/DBCStructure.h @@ -628,6 +628,33 @@ struct CharStartOutfitEntry //int32 ItemInventorySlot[MAX_OUTFIT_ITEMS]; // 53-76 not required at server side }; +enum CharSectionFlags +{ + SECTION_FLAG_PLAYER = 0x01, + SECTION_FLAG_DEATH_KNIGHT = 0x04 +}; + +enum CharSectionType +{ + SECTION_TYPE_SKIN = 0, + SECTION_TYPE_FACE = 1, + SECTION_TYPE_FACIAL_HAIR = 2, + SECTION_TYPE_HAIR = 3, + SECTION_TYPE_UNDERWEAR = 4 +}; + +struct CharSectionsEntry +{ + //uint32 Id; + uint32 Race; + uint32 Gender; + uint32 GenType; + //char* TexturePath[3]; + uint32 Flags; + uint32 Type; + uint32 Color; +}; + struct CharTitlesEntry { uint32 ID; // 0, title ids, for example in Quest::GetCharTitleId() @@ -903,6 +930,15 @@ struct EmotesTextEntry uint32 textid; }; +struct EmotesTextSoundEntry +{ + uint32 Id; // 0 + uint32 EmotesTextId; // 1 + uint32 RaceId; // 2 + uint32 SexId; // 3, 0 male / 1 female + uint32 SoundId; // 4 +}; + struct FactionEntry { uint32 ID; // 0 m_ID diff --git a/src/server/shared/DataStores/DBCfmt.h b/src/server/shared/DataStores/DBCfmt.h index 9a568d5bb..b87dddb52 100644 --- a/src/server/shared/DataStores/DBCfmt.h +++ b/src/server/shared/DataStores/DBCfmt.h @@ -29,6 +29,7 @@ char constexpr BankBagSlotPricesEntryfmt[] = "ni"; char constexpr BarberShopStyleEntryfmt[] = "nixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxiii"; char constexpr BattlemasterListEntryfmt[] = "niiiiiiiiixssssssssssssssssxiixx"; char constexpr CharStartOutfitEntryfmt[] = "dbbbXiiiiiiiiiiiiiiiiiiiiiiiixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; +char constexpr CharSectionsEntryfmt[] = "diiixxxiii"; char constexpr CharTitlesEntryfmt[] = "nxssssssssssssssssxssssssssssssssssxi"; char constexpr ChatChannelsEntryfmt[] = "nixssssssssssssssssxxxxxxxxxxxxxxxxxx"; // ChatChannelsEntryfmt, index not used (more compact store) char constexpr ChrClassesEntryfmt[] = "nxixssssssssssssssssxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxixii"; @@ -48,6 +49,7 @@ char constexpr DurabilityCostsfmt[] = "niiiiiiiiiiiiiiiiiiiiiiiiiiiii"; char constexpr DurabilityQualityfmt[] = "nf"; char constexpr EmotesEntryfmt[] = "nxxiiix"; char constexpr EmotesTextEntryfmt[] = "nxixxxxxxxxxxxxxxxx"; +char constexpr EmotesTextSoundEntryfmt[] = "niiii"; char constexpr FactionEntryfmt[] = "niiiiiiiiiiiiiiiiiiffixssssssssssssssssxxxxxxxxxxxxxxxxxx"; char constexpr FactionTemplateEntryfmt[] = "niiiiiiiiiiiii"; char constexpr GameObjectArtKitfmt[] = "nxxxxxxx"; diff --git a/src/server/shared/SharedDefines.h b/src/server/shared/SharedDefines.h index b5a979dea..527d5ae7b 100644 --- a/src/server/shared/SharedDefines.h +++ b/src/server/shared/SharedDefines.h @@ -1378,7 +1378,7 @@ enum Mechanics : uint32 (1<