diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml index 74f93205041482..486b3a9524ec5d 100644 --- a/.github/workflows/codestyle.yml +++ b/.github/workflows/codestyle.yml @@ -10,7 +10,7 @@ jobs: triage: runs-on: ubuntu-latest name: C++ - if: github.repository == 'azerothcore/azerothcore-wotlk' + if: github.repository == 'liyunfan1223/azerothcore-wotlk' 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 a72f9c6cc374ce..23611a122c1e9d 100644 --- a/.github/workflows/core-build-nopch.yml +++ b/.github/workflows/core-build-nopch.yml @@ -32,8 +32,10 @@ jobs: CC: gcc-14 CXX: g++-14 runs-on: ${{ matrix.os }} - name: ${{ matrix.os }}-${{ matrix.compiler.CC }}-nopch - if: github.repository == 'azerothcore/azerothcore-wotlk' + name: ${{ matrix.os }}-${{ matrix.compiler }}-nopch + env: + COMPILER: ${{ matrix.compiler }} + if: github.repository == 'liyunfan1223/azerothcore-wotlk' 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 4da23908a153e7..2f71753ce85d6c 100644 --- a/.github/workflows/core-build-pch.yml +++ b/.github/workflows/core-build-pch.yml @@ -25,8 +25,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 == 'liyunfan1223/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 00000000000000..7b7bf67bb148ef --- /dev/null +++ b/.github/workflows/core-build-playerbots.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/liyunfan1223/mod-playerbots.git modules/mod-playerbots + + - name: Checkout Playerbot Module + uses: actions/checkout@v3 + with: + repository: 'liyunfan1223/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-build.yml b/.github/workflows/core-build.yml new file mode 100644 index 00000000000000..5ead9c7da98900 --- /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/liyunfan1223/mod-playerbots.git modules/mod-playerbots + + # - name: Checkout Playerbot Module + # uses: actions/checkout@v3 + # with: + # repository: 'liyunfan1223/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 090ceae92092f7..d46e1f418b84b4 100644 --- a/.github/workflows/core_modules_build.yml +++ b/.github/workflows/core_modules_build.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest name: modules build on latest ubuntu if: | - github.repository == 'azerothcore/azerothcore-wotlk' + github.repository == 'liyunfan1223/azerothcore-wotlk' && !github.event.pull_request.draft && ( github.ref_name == 'master' diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml index 141d3403652016..9d2d0192e51687 100644 --- a/.github/workflows/docker_build.yml +++ b/.github/workflows/docker_build.yml @@ -18,13 +18,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 == 'liyunfan1223/azerothcore-wotlk' && github.ref_name == 'master') && 'true' || 'false' }} jobs: build-containers: runs-on: "ubuntu-latest" if: | - github.repository == 'azerothcore/azerothcore-wotlk' + github.repository == 'liyunfan1223/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 2fb927004ef78d..a33ba9e8bbc366 100644 --- a/.github/workflows/macos_build.yml +++ b/.github/workflows/macos_build.yml @@ -1,10 +1,9 @@ name: macos-build on: push: - branches: - - 'master' + branches: [ "Playerbot" ] pull_request: - types: ['labeled', 'opened', 'synchronize', 'reopened'] + branches: [ "Playerbot" ] concurrency: group: ${{ github.head_ref }} || concat(${{ github.ref }}, ${{ github.workflow }}) @@ -19,10 +18,6 @@ jobs: - macos-12 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 91dd7c4dccdd3b..347269335af399 100644 --- a/.github/workflows/tools_build.yml +++ b/.github/workflows/tools_build.yml @@ -22,9 +22,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 == 'liyunfan1223/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 ac492a77af3fb9..84e06908267cd2 100644 --- a/.github/workflows/windows_build.yml +++ b/.github/workflows/windows_build.yml @@ -1,10 +1,9 @@ name: windows-build on: push: - branches: - - 'master' + branches: [ "Playerbot" ] pull_request: - types: ['labeled', 'opened', 'synchronize', 'reopened'] + branches: [ "Playerbot" ] concurrency: group: ${{ github.head_ref }} || concat(${{ github.ref }}, ${{ github.workflow }}) @@ -20,10 +19,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 92e08b0b14ad4e..afe157983f0700 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,8 @@ CMakeLists.txt.user # /.settings/ /.externalToolBuilders/* +/.vs +/out # exclude in all levels nbproject/ .sync.ffs_db @@ -92,3 +94,5 @@ local.properties # !modules/yourmodule # # ================== +.cache +compile_commands.json \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index d3250c271ddda5..421f888d128bee 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -118,5 +118,7 @@ }, "deno.enable": true, "deno.path": "deps/deno/bin/deno", - "deno.lint": true + "deno.lint": true, + "search.useIgnoreFiles": false, + "clangd.onConfigChanged": "restart" } diff --git a/apps/ci/mac/ci-compile.sh b/apps/ci/mac/ci-compile.sh index aebca818f026b2..38e7db56ae76cf 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/apps/docker/Dockerfile b/apps/docker/Dockerfile index e003ae875de46a..c77a849a6a1489 100644 --- a/apps/docker/Dockerfile +++ b/apps/docker/Dockerfile @@ -256,4 +256,4 @@ COPY --chown=$DOCKER_USER:$DOCKER_USER --from=build \ /azerothcore/env/dist/bin/vmap4_assembler /azerothcore/env/dist/bin/vmap4_assembler COPY --chown=$DOCKER_USER:$DOCKER_USER --from=build \ - /azerothcore/env/dist/bin/vmap4_extractor /azerothcore/env/dist/bin/vmap4_extractor + /azerothcore/env/dist/bin/vmap4_extractor /azerothcore/env/dist/bin/vmap4_extractor \ No newline at end of file diff --git a/data/sql/updates/db_world/2024_09_09_00.sql b/data/sql/updates/db_world/2024_09_09_00.sql deleted file mode 100644 index e27b3f59ec4791..00000000000000 --- a/data/sql/updates/db_world/2024_09_09_00.sql +++ /dev/null @@ -1,3 +0,0 @@ --- DB update 2024_09_05_00 -> 2024_09_09_00 --- -UPDATE `creature_template` SET `flags_extra` = `flags_extra` |2147483648 WHERE `entry` = 22841; diff --git a/data/sql/updates/db_world/2024_09_11_00.sql b/data/sql/updates/db_world/2024_09_11_00.sql deleted file mode 100644 index f5e7b3a93bb435..00000000000000 --- a/data/sql/updates/db_world/2024_09_11_00.sql +++ /dev/null @@ -1,6 +0,0 @@ --- DB update 2024_09_09_00 -> 2024_09_11_00 -UPDATE `smart_scripts` -SET `event_param4` = 6000 -WHERE `entryorguid` = 22945 - AND `source_type` = 0 - AND `id` = 0; diff --git a/data/sql/updates/db_world/2024_09_11_01.sql b/data/sql/updates/db_world/2024_09_11_01.sql deleted file mode 100644 index e579b766523090..00000000000000 --- a/data/sql/updates/db_world/2024_09_11_01.sql +++ /dev/null @@ -1,15 +0,0 @@ --- DB update 2024_09_11_00 -> 2024_09_11_01 --- Missing spell (Cleave) for Bladespire Brute -DELETE FROM `smart_scripts` WHERE (`entryorguid` = 19995) AND (`source_type` = 0) AND (`id` IN (0)); -INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES -(19995, 0, 0, 0, 0, 0, 100, 0, 7000, 12000, 8000, 30000, 0, 0, 11, 15496, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Bladespire Brute - In Combat - Cast Cleave'); - --- Missing spell (Knockdown) for Bloodmaul Mauler -DELETE FROM `smart_scripts` WHERE (`entryorguid` = 19993) AND (`source_type` = 0) AND (`id` IN (4)); -INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES -(19993, 0, 4, 0, 0, 0, 100, 0, 15000, 30000, 30000, 60000, 0, 0, 11, 37592, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Bloodmaul Mauler - In Combat - Cast Knockdown'); - --- Missing spell (Knockdown) for Bloodmaul Taskmaster -DELETE FROM `smart_scripts` WHERE (`entryorguid` = 22160) AND (`source_type` = 0) AND (`id` IN (5)); -INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES -(22160, 0, 5, 0, 0, 0, 100, 0, 20000, 50000, 60000, 90000, 0, 0, 11, 37592, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Bloodmaul Taskmaster - In Combat - Cast Knockdown'); diff --git a/deps/boost/CMakeLists.txt b/deps/boost/CMakeLists.txt index 61334ee2ef07f3..1899fdf6d73a52 100644 --- a/deps/boost/CMakeLists.txt +++ b/deps/boost/CMakeLists.txt @@ -31,7 +31,7 @@ else() set(BOOST_REQUIRED_VERSION 1.67) endif() -find_package(Boost ${BOOST_REQUIRED_VERSION} REQUIRED system filesystem program_options iostreams regex) +find_package(Boost ${BOOST_REQUIRED_VERSION} REQUIRED system 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/pendings/changes_1647137971165231200.md b/doc/changelog/pendings/changes_1647137971165231200.md new file mode 100644 index 00000000000000..86f51b9afccebd --- /dev/null +++ b/doc/changelog/pendings/changes_1647137971165231200.md @@ -0,0 +1,7 @@ +### Added + +- New hook for OnQuestComputeXP(). The intended use is to change the XP values for certain quests programmatically. The hook is triggered after XP calculation and before rewarding XP or gold to the player. + +### How to upgrade + +- No special changes needed. The new hook is available for use and should not interfere with any existing hooks or logic. diff --git a/docker-compose.yml b/docker-compose.yml index 8af635419cf0b7..254766ea306cf7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -77,6 +77,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 afdfe2c5a5c0de..23a154bee05fb2 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -37,6 +37,7 @@ set("AC_MODULE_LIST" "") set("AC_SCRIPTS_LIST" "") set(MOD_ELUNA_FOUND 0) set(MOD_ELUNA_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}) ConfigureElunaModule(${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) @@ -280,6 +291,7 @@ add_library(modules STATIC target_link_libraries(modules PRIVATE acore-core-interface + mysql PUBLIC game-interface) @@ -353,6 +365,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_ELUNA_FOUND) if (APPLE) target_compile_definitions(modules diff --git a/src/common/Collision/Management/MMapMgr.cpp b/src/common/Collision/Management/MMapMgr.cpp index e08ecba7905afe..56057ca92b9cd0 100644 --- a/src/common/Collision/Management/MMapMgr.cpp +++ b/src/common/Collision/Management/MMapMgr.cpp @@ -141,7 +141,8 @@ namespace MMAP uint32 packedGridPos = packTileID(x, y); if (mmap->loadedTileRefs.find(packedGridPos) != mmap->loadedTileRefs.end()) { - LOG_ERROR("maps", "MMAP:loadMap: Asked to load already loaded navmesh tile. {:03}{:02}{:02}.mmtile", mapId, x, y); + // Peiru: Commented out for now because Playerbots system uses this method to load or check loaded maps and will spam logs +// LOG_ERROR("maps", "MMAP:loadMap: Asked to load already loaded navmesh tile. {:03}{:02}{:02}.mmtile", mapId, x, y); return false; } diff --git a/src/common/Utilities/Util.h b/src/common/Utilities/Util.h index e5ced069918468..10fcfec56cb1a4 100644 --- a/src/common/Utilities/Util.h +++ b/src/common/Utilities/Util.h @@ -356,6 +356,7 @@ inline wchar_t wcharToLower(wchar_t wchar) void wstrToUpper(std::wstring& str); void wstrToLower(std::wstring& str); +void strToLower(std::string& str); std::wstring GetMainPartOfName(std::wstring const& wname, uint32 declension); diff --git a/src/server/apps/worldserver/Main.cpp b/src/server/apps/worldserver/Main.cpp index 0b485bffadf4cb..ad8bf3a221a0c4 100644 --- a/src/server/apps/worldserver/Main.cpp +++ b/src/server/apps/worldserver/Main.cpp @@ -291,6 +291,7 @@ int main(int argc, char** argv) METRIC_VALUE("db_queue_login", uint64(LoginDatabase.QueueSize())); METRIC_VALUE("db_queue_character", uint64(CharacterDatabase.QueueSize())); METRIC_VALUE("db_queue_world", uint64(WorldDatabase.QueueSize())); + sScriptMgr->OnMetricLogging(); }); METRIC_EVENT("events", "Worldserver started", ""); @@ -448,6 +449,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", 0); if (!realm.Id.Realm) @@ -490,6 +496,8 @@ void StopDB() WorldDatabase.Close(); LoginDatabase.Close(); + sScriptMgr->OnDatabasesClosing(); + MySQL::Library_End(); } @@ -580,6 +588,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()) { @@ -609,6 +619,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 2fa6d467ee607a..c2c35da7eb3a5c 100644 --- a/src/server/apps/worldserver/worldserver.conf.dist +++ b/src/server/apps/worldserver/worldserver.conf.dist @@ -1277,17 +1277,17 @@ Visibility.GroupMode = 1 # Visibility.Distance.Instances # Visibility.Distance.BGArenas # Description: Visibility distance to see other players or gameobjects. -# Visibility on continents on retail ~100 yards. In BG/Arenas ~250. +# Visibility on continents on retail ~100 yards. In BG/Arenas ~533. # For instances default ~170. # Max limited by active player zone: ~ 333 # Min limit is max aggro radius (45) * Rate.Creature.Aggro # Default: 100 - (Visibility.Distance.Continents) # 170 - (Visibility.Distance.Instances) -# 250 - (Visibility.Distance.BGArenas) +# 533 - (Visibility.Distance.BGArenas) Visibility.Distance.Continents = 100 Visibility.Distance.Instances = 170 -Visibility.Distance.BGArenas = 250 +Visibility.Distance.BGArenas = 533 # # Visibility.ObjectSparkles @@ -4433,3 +4433,12 @@ Debug.Arena = 0 # GAME SETTINGS END # # # ################################################################################################### + +################################################################################## +# # +# Logging Stuff # +# # +################################################################################## + +Appender.Playerbots=2,5,0,Playerbots.log,w +Logger.playerbots=5,Console Playerbots \ No newline at end of file diff --git a/src/server/database/Database/DatabaseEnv.cpp b/src/server/database/Database/DatabaseEnv.cpp index e6bf6b082457e8..303a053296e7b3 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 eec79f00494ef6..a7bc68ee33b748 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 "Field.h" #include "PreparedStatement.h" #include "QueryCallback.h" @@ -38,4 +42,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 34a590c4562e6a..94fe1440545798 100644 --- a/src/server/database/Database/DatabaseEnvFwd.h +++ b/src/server/database/Database/DatabaseEnvFwd.h @@ -33,6 +33,10 @@ class CharacterDatabaseConnection; class LoginDatabaseConnection; class WorldDatabaseConnection; +#ifdef MOD_PLAYERBOTS +class PlayerbotsDatabaseConnection; +#endif + class PreparedStatementBase; template @@ -42,6 +46,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; @@ -71,6 +79,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; @@ -82,6 +94,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 5ad77d35d6caa4..2bee9d6838214c 100644 --- a/src/server/database/Database/DatabaseLoader.cpp +++ b/src/server/database/Database/DatabaseLoader.cpp @@ -219,3 +219,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 233b0a5be9f04b..08e66633f44791 100644 --- a/src/server/database/Database/DatabaseLoader.h +++ b/src/server/database/Database/DatabaseLoader.h @@ -48,8 +48,12 @@ class AC_DATABASE_API DatabaseLoader 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 @@ class AC_DATABASE_API DatabaseLoader return _updateFlags; } + void SetUpdateFlags(uint32 newUpdateFlags) + { + _updateFlags |= newUpdateFlags; + } + private: bool OpenDatabases(); bool PopulateDatabases(); @@ -73,7 +82,7 @@ class AC_DATABASE_API DatabaseLoader 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 3be45ed401d222..a362a698e91bc9 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 @@ -584,3 +588,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/LoginDatabase.cpp b/src/server/database/Database/Implementation/LoginDatabase.cpp index 5b20d86930c5d6..43729bebbc22f3 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 36509991352eef..1555210ffb9e15 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 00000000000000..ddbd39f0a9fb1b --- /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 Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#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 00000000000000..2f33f8f9070ce3 --- /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 Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#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 7b3ead0669275e..287368b2be3423 100644 --- a/src/server/database/Updater/DBUpdater.cpp +++ b/src/server/database/Updater/DBUpdater.cpp @@ -76,10 +76,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<> @@ -92,7 +98,7 @@ bool DBUpdater::IsEnabled(uint32 const updateMask) template<> std::string DBUpdater::GetDBModuleName() { - return "db-auth"; + return "auth"; } // World Database @@ -108,10 +114,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<> @@ -124,7 +136,7 @@ bool DBUpdater::IsEnabled(uint32 const updateMask) template<> std::string DBUpdater::GetDBModuleName() { - return "db-world"; + return "world"; } // Character Database @@ -140,10 +152,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<> @@ -156,9 +174,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() + "/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() @@ -224,7 +282,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)) { @@ -299,7 +357,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; @@ -533,3 +591,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 62af83df1ead66..49ef448ef52e23 100644 --- a/src/server/database/Updater/DBUpdater.h +++ b/src/server/database/Updater/DBUpdater.h @@ -71,6 +71,7 @@ class AC_DATABASE_API DBUpdater 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/database/Updater/UpdateFetcher.cpp b/src/server/database/Updater/UpdateFetcher.cpp index 720662ff6d6c85..d0ade98809e773 100644 --- a/src/server/database/Updater/UpdateFetcher.cpp +++ b/src/server/database/Updater/UpdateFetcher.cpp @@ -162,7 +162,7 @@ UpdateFetcher::DirectoryStorage UpdateFetcher::ReceiveIncludedDirectories() cons // data/sql for (auto const& itr : moduleList) { - std::string path = _sourceDirectory->generic_string() + "/modules/" + itr + "/data/sql/" + _dbModuleName; // modules/mod-name/data/sql/db-world + std::string path = _sourceDirectory->generic_string() + "/modules/" + itr + "/sql/" + _dbModuleName; // modules/mod-name/sql/world Path const p(path); if (!is_directory(p)) diff --git a/src/server/game/Battlegrounds/ArenaTeam.cpp b/src/server/game/Battlegrounds/ArenaTeam.cpp index 552b229adb4b01..9f5ca8fa59fe8f 100644 --- a/src/server/game/Battlegrounds/ArenaTeam.cpp +++ b/src/server/game/Battlegrounds/ArenaTeam.cpp @@ -1034,10 +1034,7 @@ void ArenaTeam::CreateTempArenaTeam(std::vector playerList, uint8 type, { auto playerCountInTeam = static_cast(playerList.size()); - const auto standardArenaType = { ARENA_TYPE_2v2, ARENA_TYPE_3v3, ARENA_TYPE_5v5 }; - bool isStandardArenaType = std::find(std::begin(standardArenaType), std::end(standardArenaType), type) != std::end(standardArenaType); - if (isStandardArenaType) - ASSERT(playerCountInTeam == GetReqPlayersForType(type)); + ASSERT(playerCountInTeam == GetReqPlayersForType(type)); // Generate new arena team id TeamId = sArenaTeamMgr->GenerateTempArenaTeamId(); @@ -1100,3 +1097,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 2bac7b5716b190..5e7e95089153b7 100644 --- a/src/server/game/Battlegrounds/ArenaTeam.h +++ b/src/server/game/Battlegrounds/ArenaTeam.h @@ -218,6 +218,10 @@ class ArenaTeam 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.cpp b/src/server/game/Battlegrounds/Battleground.cpp index b9858b3506106f..a68c8c643509c8 100644 --- a/src/server/game/Battlegrounds/Battleground.cpp +++ b/src/server/game/Battlegrounds/Battleground.cpp @@ -611,8 +611,6 @@ inline void Battleground::_ProcessJoin(uint32 diff) } m_ToBeTeleported.clear(); } - - sScriptMgr->OnArenaStart(this); } else { diff --git a/src/server/game/Battlegrounds/Battleground.h b/src/server/game/Battlegrounds/Battleground.h index 18922c029f0d08..709a047cc75786 100644 --- a/src/server/game/Battlegrounds/Battleground.h +++ b/src/server/game/Battlegrounds/Battleground.h @@ -209,6 +209,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/BattlegroundQueue.cpp b/src/server/game/Battlegrounds/BattlegroundQueue.cpp index 395a62a16d2e94..1d602967d8bbb7 100644 --- a/src/server/game/Battlegrounds/BattlegroundQueue.cpp +++ b/src/server/game/Battlegrounds/BattlegroundQueue.cpp @@ -773,10 +773,6 @@ void BattlegroundQueue::BattlegroundQueueUpdate(uint32 diff, BattlegroundTypeId sScriptMgr->OnQueueUpdate(this, diff, bgTypeId, bracket_id, arenaType, isRated, arenaRating); - if (!sScriptMgr->OnQueueUpdateValidity(this, diff, bgTypeId, bracket_id, arenaType, isRated, arenaRating)) { - return; - } - m_SelectionPools[TEAM_ALLIANCE].Init(); m_SelectionPools[TEAM_HORDE].Init(); diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundAB.h b/src/server/game/Battlegrounds/Zones/BattlegroundAB.h index 403664fafed556..6933f094493cff 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundAB.h +++ b/src/server/game/Battlegrounds/Zones/BattlegroundAB.h @@ -287,6 +287,18 @@ struct BattlegroundABScore final : public BattlegroundScore 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: @@ -311,6 +323,9 @@ class AC_GAME_API BattlegroundAB : public Battleground 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; @@ -321,21 +336,7 @@ class AC_GAME_API BattlegroundAB : public Battleground 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 1d62a96fd0307a..c6eaf917183748 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundAV.h +++ b/src/server/game/Battlegrounds/Zones/BattlegroundAV.h @@ -1816,6 +1816,10 @@ class AC_GAME_API BattlegroundAV : public Battleground 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 d0662cc9c7fb36..2a1a4c0fe31d47 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundEY.h +++ b/src/server/game/Battlegrounds/Zones/BattlegroundEY.h @@ -376,6 +376,25 @@ struct BattlegroundEYScore final : public BattlegroundScore 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: @@ -415,6 +434,8 @@ class AC_GAME_API BattlegroundEY : public Battleground 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; @@ -430,26 +451,7 @@ class AC_GAME_API BattlegroundEY : public Battleground /* 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 6a6b40188a1a26..e8ec81bf0be31b 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundIC.h +++ b/src/server/game/Battlegrounds/Zones/BattlegroundIC.h @@ -983,6 +983,9 @@ class AC_GAME_API BattlegroundIC : public Battleground 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 397b13a6198240..c4cc5a36ec1e21 100644 --- a/src/server/game/Chat/Channels/ChannelMgr.h +++ b/src/server/game/Chat/Channels/ChannelMgr.h @@ -41,6 +41,7 @@ class ChannelMgr 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 7ffce0fc25998f..947a9293e3dc24 100644 --- a/src/server/game/Chat/Chat.cpp +++ b/src/server/game/Chat/Chat.cpp @@ -373,6 +373,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 8a31c3c76e9e03..42b0142e8c9d63 100644 --- a/src/server/game/Chat/Chat.h +++ b/src/server/game/Chat/Chat.h @@ -35,6 +35,17 @@ class WorldObject; struct GameTele; +enum PlayerChatTag +{ + CHAT_TAG_NONE = 0x00, + CHAT_TAG_AFK = 0x01, + CHAT_TAG_DND = 0x02, + CHAT_TAG_GM = 0x04, + CHAT_TAG_COM = 0x08, // Commentator + CHAT_TAG_DEV = 0x10, // Developer +}; +typedef uint32 ChatTagFlags; + class AC_GAME_API ChatHandler { public: @@ -49,6 +60,13 @@ class AC_GAME_API ChatHandler // 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/Combat/UnitEvents.h b/src/server/game/Combat/UnitEvents.h index 1f106898fb5dcf..7f5958aea66607 100644 --- a/src/server/game/Combat/UnitEvents.h +++ b/src/server/game/Combat/UnitEvents.h @@ -92,13 +92,27 @@ class ThreatRefStatusChangeEvent : public UnitBaseEvent }; ThreatMgr* iThreatMgr; public: - ThreatRefStatusChangeEvent(uint32 pType) : UnitBaseEvent(pType), iThreatMgr(nullptr) { iHostileReference = nullptr; } + ThreatRefStatusChangeEvent(uint32 pType) : UnitBaseEvent(pType), iFValue(0.f), iThreatMgr(nullptr) + { + iHostileReference = nullptr; + } - ThreatRefStatusChangeEvent(uint32 pType, HostileReference* pHostileReference) : UnitBaseEvent(pType), iThreatMgr(nullptr) { iHostileReference = pHostileReference; } + ThreatRefStatusChangeEvent(uint32 pType, HostileReference* pHostileReference) : UnitBaseEvent(pType), iFValue(0.f), iThreatMgr(nullptr) + { + iHostileReference = pHostileReference; + } - ThreatRefStatusChangeEvent(uint32 pType, HostileReference* pHostileReference, float pValue) : UnitBaseEvent(pType), iThreatMgr(nullptr) { iHostileReference = pHostileReference; iFValue = pValue; } + ThreatRefStatusChangeEvent(uint32 pType, HostileReference* pHostileReference, float pValue) : UnitBaseEvent(pType), iThreatMgr(nullptr) + { + iHostileReference = pHostileReference; + iFValue = pValue; + } - ThreatRefStatusChangeEvent(uint32 pType, HostileReference* pHostileReference, bool pValue) : UnitBaseEvent(pType), iThreatMgr(nullptr) { iHostileReference = pHostileReference; iBValue = pValue; } + ThreatRefStatusChangeEvent(uint32 pType, HostileReference* pHostileReference, bool pValue) : UnitBaseEvent(pType), iThreatMgr(nullptr) + { + iHostileReference = pHostileReference; + iBValue = pValue; + } [[nodiscard]] int32 getIValue() const { return iIValue; } diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp index 72a852244efd1a..e84c70fba9a759 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 0f71e25fdd26e4..bf44025d86a71e 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 0eb27e71c03a08..162a424807460e 100644 --- a/src/server/game/DungeonFinding/LFGMgr.h +++ b/src/server/game/DungeonFinding/LFGMgr.h @@ -579,6 +579,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); @@ -591,7 +592,6 @@ namespace lfg void SetCanOverrideRBState(ObjectGuid guid, bool val); void GetCompatibleDungeons(LfgDungeonSet& dungeons, LfgGuidSet const& players, LfgLockPartyMap& lockMap); 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 59508328c3a862..593b94beb541b3 100644 --- a/src/server/game/DungeonFinding/LFGQueue.cpp +++ b/src/server/game/DungeonFinding/LFGQueue.cpp @@ -26,6 +26,7 @@ #include "ObjectDefines.h" #include "ObjectMgr.h" #include "Player.h" +#include "ScriptMgr.h" #include "World.h" namespace lfg @@ -412,6 +413,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 d52ea041178213..961d55c1975da0 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -1407,6 +1407,7 @@ void Creature::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask) m_spawnId = sObjectMgr->GenerateCreatureSpawnId(); CreatureData& data = sObjectMgr->NewOrExistCreatureData(m_spawnId); + data.spawnId = m_spawnId; uint32 displayId = GetNativeDisplayId(); uint32 npcflag = GetNpcFlags(); @@ -3647,7 +3648,7 @@ bool Creature::IsMovementPreventedByCasting() const void Creature::SetCannotReachTarget(ObjectGuid const& cannotReach) { if (cannotReach == m_cannotReachTarget) - { +{ return; } @@ -3678,7 +3679,7 @@ bool Creature::IsNotReachableAndNeedRegen() const std::shared_ptr const& Creature::GetLastLeashExtensionTimePtr() const { if (m_lastLeashExtensionTime == nullptr) - m_lastLeashExtensionTime = std::make_shared(GameTime::GetGameTime().count()); + m_lastLeashExtensionTime = std::make_shared(time(nullptr)); return m_lastLeashExtensionTime; } @@ -3699,7 +3700,7 @@ time_t Creature::GetLastLeashExtensionTime() const void Creature::UpdateLeashExtensionTime() { - (*GetLastLeashExtensionTimePtr()) = GameTime::GetGameTime().count(); + (*GetLastLeashExtensionTimePtr()) = time(nullptr); } bool Creature::CanPeriodicallyCallForAssistance() const diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index b14cb85ea7dd81..1f688348e85941 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -372,6 +372,7 @@ typedef std::unordered_map EquipmentInfo struct CreatureData { CreatureData() = default; + ObjectGuid::LowType spawnId{0}; 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/Creature/TemporarySummon.cpp b/src/server/game/Entities/Creature/TemporarySummon.cpp index 9513d1fc9877c7..86ea77673995d6 100644 --- a/src/server/game/Entities/Creature/TemporarySummon.cpp +++ b/src/server/game/Entities/Creature/TemporarySummon.cpp @@ -419,7 +419,7 @@ std::string Minion::GetDebugInfo() const Guardian::Guardian(SummonPropertiesEntry const* properties, ObjectGuid owner, bool isWorldObject) : Minion(properties, owner, isWorldObject) { m_unitTypeMask |= UNIT_MASK_GUARDIAN; - if (properties && (properties->Type == SUMMON_TYPE_PET || properties->Category == SUMMON_CATEGORY_PET)) + if (properties && properties->Type == SUMMON_TYPE_PET) { m_unitTypeMask |= UNIT_MASK_CONTROLABLE_GUARDIAN; InitCharmInfo(); diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp index c75e0e32c2304c..9c3d51dcf4d96b 100644 --- a/src/server/game/Entities/Item/Item.cpp +++ b/src/server/game/Entities/Item/Item.cpp @@ -1085,7 +1085,7 @@ void Item::SendTimeUpdate(Player* owner) owner->GetSession()->SendPacket(&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 @@ -1099,7 +1099,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) diff --git a/src/server/game/Entities/Item/Item.h b/src/server/game/Entities/Item/Item.h index 6fdc2f509df992..fa0bb2ce1df847 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 1550b3883bd81a..76a8ba4ef6905d 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, @@ -820,6 +820,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/Object/ObjectDefines.h b/src/server/game/Entities/Object/ObjectDefines.h index 2913e587b6d002..55c4826339ab93 100644 --- a/src/server/game/Entities/Object/ObjectDefines.h +++ b/src/server/game/Entities/Object/ObjectDefines.h @@ -40,7 +40,7 @@ #define DEFAULT_VISIBILITY_DISTANCE 100.0f // default visible distance, 100 yards on continents #define DEFAULT_VISIBILITY_INSTANCE 170.0f // default visible distance in instances, 170 yards #define VISIBILITY_DIST_WINTERGRASP 175.0f -#define DEFAULT_VISIBILITY_BGARENAS 250.0f // default visible distance in BG/Arenas, roughly 250 yards +#define DEFAULT_VISIBILITY_BGARENAS 533.0f // default visible distance in BG/Arenas, roughly 533 yards #define DEFAULT_WORLD_OBJECT_SIZE 0.388999998569489f // player size, also currently used (correctly?) for any non Unit world objects #define DEFAULT_COMBAT_REACH 1.5f diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 068eaa8d26ddd9..676d53663da685 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -3273,7 +3273,9 @@ void Player::learnSpell(uint32 spellId, bool temporary /*= false*/, bool learnFr // Xinef: don't allow to learn active spell once more if (HasActiveSpell(spellId)) { +#ifndef MOD_PLAYERBOTS LOG_DEBUG("entities.player", "Player ({}) tries to learn already active spell: {}", GetGUID().ToString(), spellId); +#endif return; } @@ -4953,6 +4955,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) diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 2ebf6c8dbcb4d2..70de7e631b47df 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -669,7 +669,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, @@ -840,16 +840,6 @@ enum EnviromentalDamage DAMAGE_FALL_TO_VOID = 6 // custom case for fall without durability loss }; -enum PlayerChatTag -{ - CHAT_TAG_NONE = 0x00, - CHAT_TAG_AFK = 0x01, - CHAT_TAG_DND = 0x02, - CHAT_TAG_GM = 0x04, - CHAT_TAG_COM = 0x08, // Commentator tag. Do not exist in clean client - CHAT_TAG_DEV = 0x10, -}; - enum PlayedTimeIndex { PLAYED_TIME_TOTAL = 0, @@ -2043,6 +2033,7 @@ class Player : public Unit, public GridObject void JoinedChannel(Channel* c); void LeftChannel(Channel* c); + bool IsInChannel(const Channel* c); void CleanupChannels(); void ClearChannelWatch(); void UpdateLocalChannels(uint32 newZone); @@ -2607,6 +2598,8 @@ class Player : public Unit, public GridObject void SendSystemMessage(std::string_view msg, bool escapeCharacters = false); + void ResetSpeakTimers(); + std::string GetDebugInfo() const override; protected: diff --git a/src/server/game/Entities/Player/PlayerQuest.cpp b/src/server/game/Entities/Player/PlayerQuest.cpp index 2a95b3e2777ec9..833fd1bd2079a9 100644 --- a/src/server/game/Entities/Player/PlayerQuest.cpp +++ b/src/server/game/Entities/Player/PlayerQuest.cpp @@ -828,25 +828,31 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, // cast spells after mark quest complete (some spells have quest completed state requirements in spell_area data) if (quest->GetRewSpellCast() > 0) { - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(quest->GetRewSpellCast()); - if (questGiver->IsUnit() && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL) && !spellInfo->HasEffect(SPELL_EFFECT_CREATE_ITEM) && !spellInfo->IsSelfCast()) - { - if (Creature* creature = GetMap()->GetCreature(questGiver->GetGUID())) - creature->CastSpell(this, quest->GetRewSpellCast(), true); - } - else - CastSpell(this, quest->GetRewSpellCast(), true); + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(quest->GetRewSpellCast())) + if (questGiver->IsUnit() && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL) && !spellInfo->HasEffect(SPELL_EFFECT_CREATE_ITEM) && !spellInfo->IsSelfCast()) + { + if (questGiver->isType(TYPEMASK_UNIT) && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL) && !spellInfo->HasEffect(SPELL_EFFECT_CREATE_ITEM) && !spellInfo->IsSelfCast()) + { + if (Creature* creature = GetMap()->GetCreature(questGiver->GetGUID())) + creature->CastSpell(this, quest->GetRewSpellCast(), true); + } + else + CastSpell(this, quest->GetRewSpellCast(), true); + } } else if (quest->GetRewSpell() > 0) { - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(quest->GetRewSpell()); - if (questGiver->IsUnit() && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL) && !spellInfo->HasEffect(SPELL_EFFECT_CREATE_ITEM) && !spellInfo->IsSelfCast()) - { - if (Creature* creature = GetMap()->GetCreature(questGiver->GetGUID())) - creature->CastSpell(this, quest->GetRewSpell(), true); - } - else - CastSpell(this, quest->GetRewSpell(), true); + if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(quest->GetRewSpell())) + if (questGiver->IsUnit() && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL) && !spellInfo->HasEffect(SPELL_EFFECT_CREATE_ITEM) && !spellInfo->IsSelfCast()) + { + if (questGiver->isType(TYPEMASK_UNIT) && !spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL) && !spellInfo->HasEffect(SPELL_EFFECT_CREATE_ITEM) && !spellInfo->IsSelfCast()) + { + if (Creature* creature = GetMap()->GetCreature(questGiver->GetGUID())) + creature->CastSpell(this, quest->GetRewSpell(), true); + } + else + CastSpell(this, quest->GetRewSpell(), true); + } } if (quest->GetZoneOrSort() > 0) diff --git a/src/server/game/Entities/Player/PlayerUpdates.cpp b/src/server/game/Entities/Player/PlayerUpdates.cpp index 594100616deada..43f34e22935506 100644 --- a/src/server/game/Entities/Player/PlayerUpdates.cpp +++ b/src/server/game/Entities/Player/PlayerUpdates.cpp @@ -422,6 +422,7 @@ void Player::Update(uint32 p_time) m_delayed_unit_relocation_timer = 0; RemoveFromNotify(NOTIFY_VISIBILITY_CHANGED); } + sScriptMgr->OnAfterPlayerUpdate(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 705c36e2380ac6..525ef48c0a1f6f 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -738,20 +738,6 @@ bool Unit::GetRandomContactPoint(Unit const* obj, float& x, float& y, float& z, return true; } -Unit* Unit::getAttackerForHelper() const -{ - if (GetVictim() != nullptr) - return GetVictim(); - - if (!IsEngaged()) - return nullptr; - - if (!m_attackers.empty()) - return *(m_attackers.begin()); - - return nullptr; -} - void Unit::UpdateInterruptMask() { m_interruptMask = 0; @@ -10179,15 +10165,32 @@ 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; + } + if (factionTemplateEntry->IsFriendlyTo(*targetFactionTemplateEntry)) + { return REP_FRIENDLY; + } + if (targetFactionTemplateEntry->IsFriendlyTo(*factionTemplateEntry)) + { return REP_FRIENDLY; + } + if (factionTemplateEntry->factionFlags & FACTION_TEMPLATE_FLAG_HATES_ALL_EXCEPT_FRIENDS) + { return REP_HOSTILE; + } + // neutral by default return REP_NEUTRAL; } @@ -13556,9 +13559,6 @@ void Unit::SetInCombatWith(Unit* enemy, uint32 duration) } } - if (Creature* pCreature = ToCreature()) - pCreature->UpdateLeashExtensionTime(); - SetInCombatState(false, enemy, duration); } @@ -17236,17 +17236,6 @@ void Unit::SetContestedPvP(Player* attackedPlayer, bool lookForNearContestedGuar } } -void Unit::SetCantProc(bool apply) -{ - if (apply) - ++m_procDeep; - else - { - ASSERT(m_procDeep); - --m_procDeep; - } -} - void Unit::AddPetAura(PetAura const* petSpell) { if (!IsPlayer()) @@ -17916,6 +17905,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()) { @@ -18869,11 +18860,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); } @@ -20225,13 +20228,13 @@ void Unit::OutDebugInfo() const class AuraMunchingQueue : public BasicEvent { public: - AuraMunchingQueue(Unit& owner, ObjectGuid targetGUID, int32 basePoints, uint32 spellId, AuraEffect* aurEff) : _owner(owner), _targetGUID(targetGUID), _basePoints(basePoints), _spellId(spellId), _aurEff(aurEff) { } + AuraMunchingQueue(Unit& owner, ObjectGuid targetGUID, int32 basePoints, uint32 spellId) : _owner(owner), _targetGUID(targetGUID), _basePoints(basePoints), _spellId(spellId) { } bool Execute(uint64 /*eventTime*/, uint32 /*updateTime*/) override { if (_owner.IsInWorld() && _owner.FindMap()) if (Unit* target = ObjectAccessor::GetUnit(_owner, _targetGUID)) - _owner.CastCustomSpell(_spellId, SPELLVALUE_BASE_POINT0, _basePoints, target, TriggerCastFlags(TRIGGERED_FULL_MASK & ~TRIGGERED_NO_PERIODIC_RESET), nullptr, _aurEff, _owner.GetGUID()); + _owner.CastCustomSpell(_spellId, SPELLVALUE_BASE_POINT0, _basePoints, target, TriggerCastFlags(TRIGGERED_FULL_MASK & ~TRIGGERED_NO_PERIODIC_RESET), nullptr, nullptr, _owner.GetGUID()); return true; } @@ -20241,15 +20244,13 @@ class AuraMunchingQueue : public BasicEvent ObjectGuid _targetGUID; int32 _basePoints; uint32 _spellId; - AuraEffect* _aurEff; }; void Unit::CastDelayedSpellWithPeriodicAmount(Unit* caster, uint32 spellId, AuraType auraType, int32 addAmount, uint8 effectIndex) { - AuraEffect* aurEff = nullptr; for (AuraEffectList::iterator i = m_modAuras[auraType].begin(); i != m_modAuras[auraType].end(); ++i) { - aurEff = *i; + AuraEffect* aurEff = *i; if (aurEff->GetCasterGUID() != caster->GetGUID() || aurEff->GetId() != spellId || aurEff->GetEffIndex() != effectIndex || !aurEff->GetTotalTicks()) continue; @@ -20259,9 +20260,9 @@ void Unit::CastDelayedSpellWithPeriodicAmount(Unit* caster, uint32 spellId, Aura // xinef: delay only for casting on different unit if (this == caster || !sWorld->getBoolConfig(CONFIG_MUNCHING_BLIZZLIKE)) - caster->CastCustomSpell(spellId, SPELLVALUE_BASE_POINT0, addAmount, this, TriggerCastFlags(TRIGGERED_FULL_MASK & ~TRIGGERED_NO_PERIODIC_RESET), nullptr, aurEff, caster->GetGUID()); + caster->CastCustomSpell(spellId, SPELLVALUE_BASE_POINT0, addAmount, this, TriggerCastFlags(TRIGGERED_FULL_MASK & ~TRIGGERED_NO_PERIODIC_RESET), nullptr, nullptr, caster->GetGUID()); else - caster->m_Events.AddEvent(new AuraMunchingQueue(*caster, GetGUID(), addAmount, spellId, aurEff), caster->m_Events.CalculateQueueTime(400)); + caster->m_Events.AddEvent(new AuraMunchingQueue(*caster, GetGUID(), addAmount, spellId), caster->m_Events.CalculateQueueTime(400)); } void Unit::SendClearTarget() @@ -21262,3 +21263,18 @@ std::string Unit::GetDebugInfo() const << " Class: " << std::to_string(getClass()); return sstr.str(); } + +void Unit::SetCannotReachTargetUnit(bool cannotReach, bool isChase) +{ + if (cannotReach == m_cannotReachTarget) + { + return; + } + + m_cannotReachTarget = cannotReach; +} + +bool Unit::CanNotReachTarget() const +{ + return m_cannotReachTarget; +} diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 74dae46679677a..634a84226fcab3 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -653,8 +653,6 @@ class Unit : public WorldObject ~Unit() override; - void Update(uint32 time) override; - UnitAI* GetAI() { return i_AI; } void SetAI(UnitAI* newAI) { i_AI = newAI; } @@ -664,101 +662,20 @@ class Unit : public WorldObject void CleanupBeforeRemoveFromMap(bool finalCleanup); void CleanupsBeforeDelete(bool finalCleanup = true) override; // used in ~Creature/~Player (or before mass creature delete to remove cross-references to already deleted units) - /*********************************************************/ - /*** UNIT HELPERS ***/ - /*********************************************************/ - void SetUInt32Value(uint16 index, uint32 value); /// @todo: move this in Object class or move GetUInt32value here but keep consistency - - void AddUnitState(uint32 f) { m_state |= f; } - [[nodiscard]] bool HasUnitState(const uint32 f) const { return (m_state & f); } - void ClearUnitState(uint32 f) { m_state &= ~f; } - [[nodiscard]] uint32 GetUnitState() const { return m_state; } - - [[nodiscard]] uint32 HasUnitTypeMask(uint32 mask) const { return mask & m_unitTypeMask; } - void AddUnitTypeMask(uint32 mask) { m_unitTypeMask |= mask; } - [[nodiscard]] uint32 GetUnitTypeMask() const { return m_unitTypeMask; } - - UnitFlags GetUnitFlags() const { return UnitFlags(GetUInt32Value(UNIT_FIELD_FLAGS)); } - bool HasUnitFlag(UnitFlags flags) const { return HasFlag(UNIT_FIELD_FLAGS, flags); } /// @brief UnitFlags available in UnitDefines.h - void SetUnitFlag(UnitFlags flags) { SetFlag(UNIT_FIELD_FLAGS, flags); } /// @brief UnitFlags available in UnitDefines.h - void RemoveUnitFlag(UnitFlags flags) { RemoveFlag(UNIT_FIELD_FLAGS, flags); } /// @brief Remove the Unit flag specify only - void ReplaceAllUnitFlags(UnitFlags flags) { SetUInt32Value(UNIT_FIELD_FLAGS, flags); } /// @brief Remove all UnitFlags and set new ones. UnitFlags available in UnitDefines.h - - UnitFlags2 GetUnitFlags2() const { return UnitFlags2(GetUInt32Value(UNIT_FIELD_FLAGS_2)); } - bool HasUnitFlag2(UnitFlags2 flags) const { return HasFlag(UNIT_FIELD_FLAGS_2, flags); } - void SetUnitFlag2(UnitFlags2 flags) { SetFlag(UNIT_FIELD_FLAGS_2, flags); } - void RemoveUnitFlag2(UnitFlags2 flags) { RemoveFlag(UNIT_FIELD_FLAGS_2, flags); } - void ReplaceAllUnitFlags2(UnitFlags2 flags) { SetUInt32Value(UNIT_FIELD_FLAGS_2, flags); } - - NPCFlags GetNpcFlags() const { return NPCFlags(GetUInt32Value(UNIT_NPC_FLAGS)); } - bool HasNpcFlag(NPCFlags flags) const { return HasFlag(UNIT_NPC_FLAGS, flags) != 0; } - void SetNpcFlag(NPCFlags flags) { SetFlag(UNIT_NPC_FLAGS, flags); } - void RemoveNpcFlag(NPCFlags flags) { RemoveFlag(UNIT_NPC_FLAGS, flags); } - void ReplaceAllNpcFlags(NPCFlags flags) { SetUInt32Value(UNIT_NPC_FLAGS, flags); } - uint32 GetDynamicFlags() const override { return GetUInt32Value(UNIT_DYNAMIC_FLAGS); } void ReplaceAllDynamicFlags(uint32 flag) override { SetUInt32Value(UNIT_DYNAMIC_FLAGS, flag); } - /*********************************************************/ - /*** UNIT TYPES, CLASSES, RACES... ***/ - /*********************************************************/ - - // Unit type methods - [[nodiscard]] bool IsSummon() const { return m_unitTypeMask & UNIT_MASK_SUMMON; } - [[nodiscard]] bool IsGuardian() const { return m_unitTypeMask & UNIT_MASK_GUARDIAN; } - [[nodiscard]] bool IsControllableGuardian() const { return m_unitTypeMask & UNIT_MASK_CONTROLABLE_GUARDIAN; } - [[nodiscard]] bool IsPet() const { return m_unitTypeMask & UNIT_MASK_PET; } - [[nodiscard]] bool IsHunterPet() const { return m_unitTypeMask & UNIT_MASK_HUNTER_PET; } - [[nodiscard]] bool IsTotem() const { return m_unitTypeMask & UNIT_MASK_TOTEM; } - [[nodiscard]] bool IsVehicle() const { return m_unitTypeMask & UNIT_MASK_VEHICLE; } - - // NPC type methods - [[nodiscard]] bool IsVendor() const { return HasNpcFlag(UNIT_NPC_FLAG_VENDOR); } - [[nodiscard]] bool IsTrainer() const { return HasNpcFlag(UNIT_NPC_FLAG_TRAINER); } - [[nodiscard]] bool IsQuestGiver() const { return HasNpcFlag(UNIT_NPC_FLAG_QUESTGIVER); } - [[nodiscard]] bool IsGossip() const { return HasNpcFlag(UNIT_NPC_FLAG_GOSSIP); } - [[nodiscard]] bool IsTaxi() const { return HasNpcFlag(UNIT_NPC_FLAG_FLIGHTMASTER); } - [[nodiscard]] bool IsGuildMaster() const { return HasNpcFlag(UNIT_NPC_FLAG_PETITIONER); } - [[nodiscard]] bool IsBattleMaster() const { return HasNpcFlag(UNIT_NPC_FLAG_BATTLEMASTER); } - [[nodiscard]] bool IsBanker() const { return HasNpcFlag(UNIT_NPC_FLAG_BANKER); } - [[nodiscard]] bool IsInnkeeper() const { return HasNpcFlag(UNIT_NPC_FLAG_INNKEEPER); } - [[nodiscard]] bool IsSpiritHealer() const { return HasNpcFlag(UNIT_NPC_FLAG_SPIRITHEALER); } - [[nodiscard]] bool IsSpiritGuide() const { return HasNpcFlag(UNIT_NPC_FLAG_SPIRITGUIDE); } - [[nodiscard]] bool IsTabardDesigner() const { return HasNpcFlag(UNIT_NPC_FLAG_TABARDDESIGNER); } - [[nodiscard]] bool IsAuctioner() const { return HasNpcFlag(UNIT_NPC_FLAG_AUCTIONEER); } - [[nodiscard]] bool IsArmorer() const { return HasNpcFlag(UNIT_NPC_FLAG_REPAIR); } - [[nodiscard]] bool IsServiceProvider() const - { - return HasNpcFlag(UNIT_NPC_FLAG_VENDOR | UNIT_NPC_FLAG_TRAINER | UNIT_NPC_FLAG_FLIGHTMASTER | - UNIT_NPC_FLAG_PETITIONER | UNIT_NPC_FLAG_BATTLEMASTER | UNIT_NPC_FLAG_BANKER | - UNIT_NPC_FLAG_INNKEEPER | UNIT_NPC_FLAG_SPIRITHEALER | - UNIT_NPC_FLAG_SPIRITGUIDE | UNIT_NPC_FLAG_TABARDDESIGNER | UNIT_NPC_FLAG_AUCTIONEER); - } - [[nodiscard]] bool IsSpiritService() const { return HasNpcFlag(UNIT_NPC_FLAG_SPIRITHEALER | UNIT_NPC_FLAG_SPIRITGUIDE); } - - // Race methods - [[nodiscard]] uint8 getRace(bool original = false) const; - void setRace(uint8 race); - [[nodiscard]] uint32 getRaceMask() const { return 1 << (getRace(true) - 1); } - [[nodiscard]] DisplayRace GetDisplayRaceFromModelId(uint32 modelId) const; - [[nodiscard]] DisplayRace GetDisplayRace() const { return GetDisplayRaceFromModelId(GetDisplayId()); }; - - // Class methods - [[nodiscard]] uint8 getClass() const { return GetByteValue(UNIT_FIELD_BYTES_0, 1); } - [[nodiscard]] virtual bool IsClass(Classes unitClass, [[maybe_unused]] ClassContext context = CLASS_CONTEXT_NONE) const { return (getClass() == unitClass); } - [[nodiscard]] uint32 getClassMask() const { return 1 << (getClass() - 1); } - - // Gender methods - [[nodiscard]] uint8 getGender() const { return GetByteValue(UNIT_FIELD_BYTES_0, 2); } + DiminishingLevels GetDiminishing(DiminishingGroup group); + void IncrDiminishing(DiminishingGroup group); + float ApplyDiminishingToDuration(DiminishingGroup group, int32& duration, Unit* caster, DiminishingLevels Level, int32 limitduration); + void ApplyDiminishingAura(DiminishingGroup group, bool apply); + void ClearDiminishings() { m_Diminishing.clear(); } - // Factions methods - [[nodiscard]] uint32 GetFaction() const { return GetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE); } - void SetFaction(uint32 faction); - [[nodiscard]] FactionTemplateEntry const* GetFactionTemplateEntry() const; + // target dependent range checks + float GetSpellMaxRangeForTarget(Unit const* target, SpellInfo const* spellInfo) const; + float GetSpellMinRangeForTarget(Unit const* target, SpellInfo const* spellInfo) const; - /*********************************************************/ - /*** METHODS RELATED TO COMBATS ***/ - /*********************************************************/ + void Update(uint32 time) override; void setAttackTimer(WeaponAttackType type, int32 time) { m_attackTimer[type] = time; } /// @todo - Look to convert to std::chrono void resetAttackTimer(WeaponAttackType type = BASE_ATTACK); @@ -775,9 +692,30 @@ class Unit : public WorldObject float GetMeleeRange(Unit const* target) const; virtual SpellSchoolMask GetMeleeDamageSchoolMask(WeaponAttackType attackType = BASE_ATTACK, uint8 damageIndex = 0) const = 0; bool GetRandomContactPoint(Unit const* target, float& x, float& y, float& z, bool force = false) const; + uint32 m_extraAttacks; + bool m_canDualWield; + + void _addAttacker(Unit* pAttacker) // must be called only from Unit::Attack(Unit*) + { + m_attackers.insert(pAttacker); + } + void _removeAttacker(Unit* pAttacker) // must be called only from Unit::AttackStop() + { + m_attackers.erase(pAttacker); + } + [[nodiscard]] Unit* getAttackerForHelper() const // If someone wants to help, who to give them + { + if (GetVictim() != nullptr) + return GetVictim(); - [[nodiscard]] Unit* getAttackerForHelper() const; // If someone wants to help, who to give them + if (!IsEngaged()) + return nullptr; + if (!m_attackers.empty()) + return *(m_attackers.begin()); + + return nullptr; + } bool Attack(Unit* victim, bool meleeAttack); void CastStop(uint32 except_spellid = 0, bool withInstant = true); @@ -797,64 +735,39 @@ class Unit : public WorldObject void SendMeleeAttackStop(Unit* victim = nullptr); void SendMeleeAttackStart(Unit* victim, Player* sendTo = nullptr); - [[nodiscard]] uint32 GetAttackTime(WeaponAttackType att) const + void AddUnitState(uint32 f) { m_state |= f; } + [[nodiscard]] bool HasUnitState(const uint32 f) const { return (m_state & f); } + void ClearUnitState(uint32 f) { m_state &= ~f; } + [[nodiscard]] uint32 GetUnitState() const { return m_state; } + [[nodiscard]] bool CanFreeMove() const { - float f_BaseAttackTime = GetFloatValue(static_cast(UNIT_FIELD_BASEATTACKTIME) + att) / m_modAttackSpeedPct[att]; - return (uint32)f_BaseAttackTime; + return !HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_FLEEING | UNIT_STATE_IN_FLIGHT | + UNIT_STATE_ROOT | UNIT_STATE_STUNNED | UNIT_STATE_DISTRACTED) && !GetOwnerGUID(); } - void SetAttackTime(WeaponAttackType att, uint32 val) { SetFloatValue(static_cast(UNIT_FIELD_BASEATTACKTIME) + att, val * m_modAttackSpeedPct[att]); } - void ApplyAttackTimePercentMod(WeaponAttackType att, float val, bool apply); - void ApplyCastTimePercentMod(float val, bool apply); - - void SetImmuneToAll(bool apply, bool keepCombat = false) { SetImmuneToPC(apply, keepCombat); SetImmuneToNPC(apply, keepCombat); } - bool IsImmuneToAll() const { return IsImmuneToPC() && IsImmuneToNPC(); } - void SetImmuneToPC(bool apply, bool keepCombat = false); - bool IsImmuneToPC() const { return HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC); } - void SetImmuneToNPC(bool apply, bool keepCombat = false); - bool IsImmuneToNPC() const { return HasUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC); } - - bool IsEngaged() const { return IsInCombat(); } - bool IsEngagedBy(Unit const* who) const { return IsInCombatWith(who); } - - [[nodiscard]] bool IsInCombat() const { return HasUnitFlag(UNIT_FLAG_IN_COMBAT); } - bool IsInCombatWith(Unit const* who) const; - - [[nodiscard]] bool IsPetInCombat() const { return HasUnitFlag(UNIT_FLAG_PET_IN_COMBAT); } - void CombatStart(Unit* target, bool initialAggro = true); - void CombatStartOnCast(Unit* target, bool initialAggro = true, uint32 duration = 0); - void SetInCombatState(bool PvP, Unit* enemy = nullptr, uint32 duration = 0); - void SetInCombatWith(Unit* enemy, uint32 duration = 0); - void ClearInCombat(); - void ClearInPetCombat(); - [[nodiscard]] uint32 GetCombatTimer() const { return m_CombatTimer; } - void SetCombatTimer(uint32 timer) { m_CombatTimer = timer; } - - // Threat related methods - [[nodiscard]] bool CanHaveThreatList() const; - void AddThreat(Unit* victim, float fThreat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL, SpellInfo const* threatSpell = nullptr); - float ApplyTotalThreatModifier(float fThreat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL); - void TauntApply(Unit* victim); - void TauntFadeOut(Unit* taunter); - ThreatMgr& GetThreatMgr() { return m_ThreatMgr; } - ThreatMgr const& GetThreatMgr() const { return m_ThreatMgr; } - void addHatedBy(HostileReference* pHostileReference) { m_HostileRefMgr.insertFirst(pHostileReference); }; - void removeHatedBy(HostileReference* /*pHostileReference*/) { /* nothing to do yet */ } - HostileRefMgr& getHostileRefMgr() { return m_HostileRefMgr; } - - // Redirect Threat - void SetRedirectThreat(ObjectGuid guid, uint32 pct) { _redirectThreatInfo.Set(guid, pct); } - void ResetRedirectThreat() { SetRedirectThreat(ObjectGuid::Empty, 0); } - void ModifyRedirectThreat(int32 amount) { _redirectThreatInfo.ModifyThreatPct(amount); } - uint32 GetRedirectThreatPercent() { return _redirectThreatInfo.GetThreatPct(); } - [[nodiscard]] Unit* GetRedirectThreatTarget() const; + [[nodiscard]] uint32 HasUnitTypeMask(uint32 mask) const { return mask & m_unitTypeMask; } + void AddUnitTypeMask(uint32 mask) { m_unitTypeMask |= mask; } + [[nodiscard]] uint32 GetUnitTypeMask() const { return m_unitTypeMask; } + [[nodiscard]] bool IsSummon() const { return m_unitTypeMask & UNIT_MASK_SUMMON; } + [[nodiscard]] bool IsGuardian() const { return m_unitTypeMask & UNIT_MASK_GUARDIAN; } + [[nodiscard]] bool IsControllableGuardian() const { return m_unitTypeMask & UNIT_MASK_CONTROLABLE_GUARDIAN; } + [[nodiscard]] bool IsPet() const { return m_unitTypeMask & UNIT_MASK_PET; } + [[nodiscard]] bool IsHunterPet() const { return m_unitTypeMask & UNIT_MASK_HUNTER_PET; } + [[nodiscard]] bool IsTotem() const { return m_unitTypeMask & UNIT_MASK_TOTEM; } + [[nodiscard]] bool IsVehicle() const { return m_unitTypeMask & UNIT_MASK_VEHICLE; } - /*********************************************************/ - /*** METHODS RELATED TO STATS ***/ - /*********************************************************/ [[nodiscard]] uint8 GetLevel() const { return uint8(GetUInt32Value(UNIT_FIELD_LEVEL)); } uint8 getLevelForTarget(WorldObject const* /*target*/) const override { return GetLevel(); } void SetLevel(uint8 lvl, bool showLevelChange = true); + [[nodiscard]] uint8 getRace(bool original = false) const; + void setRace(uint8 race); + [[nodiscard]] uint32 getRaceMask() const { return 1 << (getRace(true) - 1); } + [[nodiscard]] uint8 getClass() const { return GetByteValue(UNIT_FIELD_BYTES_0, 1); } + [[nodiscard]] virtual bool IsClass(Classes unitClass, [[maybe_unused]] ClassContext context = CLASS_CONTEXT_NONE) const { return (getClass() == unitClass); } + [[nodiscard]] uint32 getClassMask() const { return 1 << (getClass() - 1); } + [[nodiscard]] uint8 getGender() const { return GetByteValue(UNIT_FIELD_BYTES_0, 2); } + [[nodiscard]] DisplayRace GetDisplayRaceFromModelId(uint32 modelId) const; + [[nodiscard]] DisplayRace GetDisplayRace() const { return GetDisplayRaceFromModelId(GetDisplayId()); }; [[nodiscard]] float GetStat(Stats stat) const { return float(GetUInt32Value(static_cast(UNIT_FIELD_STAT0) + stat)); } void SetStat(Stats stat, int32 val) { SetStatInt32Value(static_cast(UNIT_FIELD_STAT0) + stat, val); } @@ -892,60 +805,45 @@ class Unit : public WorldObject [[nodiscard]] uint32 GetMaxPower(Powers power) const { return GetUInt32Value(static_cast(UNIT_FIELD_MAXPOWER1) + power); } void SetPower(Powers power, uint32 val, bool withPowerUpdate = true, bool fromRegenerate = false); void SetMaxPower(Powers power, uint32 val); - // returns the change in power int32 ModifyPower(Powers power, int32 val, bool withPowerUpdate = true); int32 ModifyPowerPct(Powers power, float pct, bool apply = true); - // stat system - bool HandleStatModifier(UnitMods unitMod, UnitModifierType modifierType, float amount, bool apply); - void SetModifierValue(UnitMods unitMod, UnitModifierType modifierType, float value) { m_auraModifiersGroup[unitMod][modifierType] = value; } - [[nodiscard]] float GetModifierValue(UnitMods unitMod, UnitModifierType modifierType) const; - [[nodiscard]] float GetTotalStatValue(Stats stat, float additionalValue = 0.0f) const; - [[nodiscard]] float GetTotalAuraModValue(UnitMods unitMod) const; - [[nodiscard]] SpellSchools GetSpellSchoolByAuraGroup(UnitMods unitMod) const; - [[nodiscard]] Stats GetStatByAuraGroup(UnitMods unitMod) const; - [[nodiscard]] Powers GetPowerTypeByAuraGroup(UnitMods unitMod) const; - [[nodiscard]] bool CanModifyStats() const { return m_canModifyStats; } - void SetCanModifyStats(bool modifyStats) { m_canModifyStats = modifyStats; } - virtual bool UpdateStats(Stats stat) = 0; - virtual bool UpdateAllStats() = 0; - virtual void UpdateResistances(uint32 school) = 0; - virtual void UpdateAllResistances(); - virtual void UpdateArmor() = 0; - virtual void UpdateMaxHealth() = 0; - virtual void UpdateMaxPower(Powers power) = 0; - virtual void UpdateAttackPowerAndDamage(bool ranged = false) = 0; - virtual void UpdateDamagePhysical(WeaponAttackType attType); - float GetTotalAttackPowerValue(WeaponAttackType attType, Unit* pVictim = nullptr) const; - [[nodiscard]] float GetWeaponDamageRange(WeaponAttackType attType, WeaponDamageRange type, uint8 damageIndex = 0) const; - void SetBaseWeaponDamage(WeaponAttackType attType, WeaponDamageRange damageRange, float value, uint8 damageIndex = 0) { m_weaponDamage[attType][damageRange][damageIndex] = value; } - virtual void CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, float& minDamage, float& maxDamage, uint8 damageIndex = 0) = 0; - uint32 CalculateDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, uint8 itemDamagesMask = 0); - float GetAPMultiplier(WeaponAttackType attType, bool normalized); - //------------------------------------------------------// + [[nodiscard]] uint32 GetAttackTime(WeaponAttackType att) const + { + float f_BaseAttackTime = GetFloatValue(static_cast(UNIT_FIELD_BASEATTACKTIME) + att) / m_modAttackSpeedPct[att]; + return (uint32)f_BaseAttackTime; + } - DiminishingLevels GetDiminishing(DiminishingGroup group); - void IncrDiminishing(DiminishingGroup group); - float ApplyDiminishingToDuration(DiminishingGroup group, int32& duration, Unit* caster, DiminishingLevels Level, int32 limitduration); - void ApplyDiminishingAura(DiminishingGroup group, bool apply); - void ClearDiminishings() { m_Diminishing.clear(); } + void SetAttackTime(WeaponAttackType att, uint32 val) { SetFloatValue(static_cast(UNIT_FIELD_BASEATTACKTIME) + att, val * m_modAttackSpeedPct[att]); } + void ApplyAttackTimePercentMod(WeaponAttackType att, float val, bool apply); + void ApplyCastTimePercentMod(float val, bool apply); - // target dependent range checks - float GetSpellMaxRangeForTarget(Unit const* target, SpellInfo const* spellInfo) const; - float GetSpellMinRangeForTarget(Unit const* target, SpellInfo const* spellInfo) const; + void SetUInt32Value(uint16 index, uint32 value); - [[nodiscard]] bool CanFreeMove() const - { - return !HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_FLEEING | UNIT_STATE_IN_FLIGHT | - UNIT_STATE_ROOT | UNIT_STATE_STUNNED | UNIT_STATE_DISTRACTED) && !GetOwnerGUID(); - } + UnitFlags GetUnitFlags() const { return UnitFlags(GetUInt32Value(UNIT_FIELD_FLAGS)); } + bool HasUnitFlag(UnitFlags flags) const { return HasFlag(UNIT_FIELD_FLAGS, flags); } /// @brief UnitFlags available in UnitDefines.h + void SetUnitFlag(UnitFlags flags) { SetFlag(UNIT_FIELD_FLAGS, flags); } /// @brief UnitFlags available in UnitDefines.h + void RemoveUnitFlag(UnitFlags flags) { RemoveFlag(UNIT_FIELD_FLAGS, flags); } /// @brief Remove the Unit flag specify only + void ReplaceAllUnitFlags(UnitFlags flags) { SetUInt32Value(UNIT_FIELD_FLAGS, flags); } /// @brief Remove all UnitFlags and set new ones. UnitFlags available in UnitDefines.h + + UnitFlags2 GetUnitFlags2() const { return UnitFlags2(GetUInt32Value(UNIT_FIELD_FLAGS_2)); } + bool HasUnitFlag2(UnitFlags2 flags) const { return HasFlag(UNIT_FIELD_FLAGS_2, flags); } + void SetUnitFlag2(UnitFlags2 flags) { SetFlag(UNIT_FIELD_FLAGS_2, flags); } + void RemoveUnitFlag2(UnitFlags2 flags) { RemoveFlag(UNIT_FIELD_FLAGS_2, flags); } + void ReplaceAllUnitFlags2(UnitFlags2 flags) { SetUInt32Value(UNIT_FIELD_FLAGS_2, flags); } [[nodiscard]] SheathState GetSheath() const { return SheathState(GetByteValue(UNIT_FIELD_BYTES_2, 0)); } virtual void SetSheath(SheathState sheathed) { SetByteValue(UNIT_FIELD_BYTES_2, 0, sheathed); } + // faction template id + [[nodiscard]] uint32 GetFaction() const { return GetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE); } + void SetFaction(uint32 faction); + [[nodiscard]] FactionTemplateEntry const* GetFactionTemplateEntry() const; + 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); bool IsHostileTo(Unit const* unit) const; [[nodiscard]] bool IsHostileToPlayers() const; @@ -1081,7 +979,6 @@ class Unit : public WorldObject return value; } - uint32 GetUnitMeleeSkill(Unit const* target = nullptr) const { return (target ? getLevelForTarget(target) : GetLevel()) * 5; } uint32 GetDefenseSkillValue(Unit const* target = nullptr) const; uint32 GetWeaponSkillValue(WeaponAttackType attType, Unit const* target = nullptr) const; @@ -1091,10 +988,61 @@ class Unit : public WorldObject MeleeHitOutcome RollMeleeOutcomeAgainst (Unit const* victim, WeaponAttackType attType) const; MeleeHitOutcome RollMeleeOutcomeAgainst (Unit const* victim, WeaponAttackType attType, int32 crit_chance, int32 miss_chance, int32 dodge_chance, int32 parry_chance, int32 block_chance) const; + NPCFlags GetNpcFlags() const { return NPCFlags(GetUInt32Value(UNIT_NPC_FLAGS)); } + bool HasNpcFlag(NPCFlags flags) const { return HasFlag(UNIT_NPC_FLAGS, flags) != 0; } + void SetNpcFlag(NPCFlags flags) { SetFlag(UNIT_NPC_FLAGS, flags); } + void RemoveNpcFlag(NPCFlags flags) { RemoveFlag(UNIT_NPC_FLAGS, flags); } + void ReplaceAllNpcFlags(NPCFlags flags) { SetUInt32Value(UNIT_NPC_FLAGS, flags); } + + [[nodiscard]] bool IsVendor() const { return HasNpcFlag(UNIT_NPC_FLAG_VENDOR); } + [[nodiscard]] bool IsTrainer() const { return HasNpcFlag(UNIT_NPC_FLAG_TRAINER); } + [[nodiscard]] bool IsQuestGiver() const { return HasNpcFlag(UNIT_NPC_FLAG_QUESTGIVER); } + [[nodiscard]] bool IsGossip() const { return HasNpcFlag(UNIT_NPC_FLAG_GOSSIP); } + [[nodiscard]] bool IsTaxi() const { return HasNpcFlag(UNIT_NPC_FLAG_FLIGHTMASTER); } + [[nodiscard]] bool IsGuildMaster() const { return HasNpcFlag(UNIT_NPC_FLAG_PETITIONER); } + [[nodiscard]] bool IsBattleMaster() const { return HasNpcFlag(UNIT_NPC_FLAG_BATTLEMASTER); } + [[nodiscard]] bool IsBanker() const { return HasNpcFlag(UNIT_NPC_FLAG_BANKER); } + [[nodiscard]] bool IsInnkeeper() const { return HasNpcFlag(UNIT_NPC_FLAG_INNKEEPER); } + [[nodiscard]] bool IsSpiritHealer() const { return HasNpcFlag(UNIT_NPC_FLAG_SPIRITHEALER); } + [[nodiscard]] bool IsSpiritGuide() const { return HasNpcFlag(UNIT_NPC_FLAG_SPIRITGUIDE); } + [[nodiscard]] bool IsTabardDesigner() const { return HasNpcFlag(UNIT_NPC_FLAG_TABARDDESIGNER); } + [[nodiscard]] bool IsAuctioner() const { return HasNpcFlag(UNIT_NPC_FLAG_AUCTIONEER); } + [[nodiscard]] bool IsArmorer() const { return HasNpcFlag(UNIT_NPC_FLAG_REPAIR); } + [[nodiscard]] bool IsServiceProvider() const + { + return HasNpcFlag(UNIT_NPC_FLAG_VENDOR | UNIT_NPC_FLAG_TRAINER | UNIT_NPC_FLAG_FLIGHTMASTER | + UNIT_NPC_FLAG_PETITIONER | UNIT_NPC_FLAG_BATTLEMASTER | UNIT_NPC_FLAG_BANKER | + UNIT_NPC_FLAG_INNKEEPER | UNIT_NPC_FLAG_SPIRITHEALER | + UNIT_NPC_FLAG_SPIRITGUIDE | UNIT_NPC_FLAG_TABARDDESIGNER | UNIT_NPC_FLAG_AUCTIONEER); + } + [[nodiscard]] bool IsSpiritService() const { return HasNpcFlag(UNIT_NPC_FLAG_SPIRITHEALER | UNIT_NPC_FLAG_SPIRITGUIDE); } [[nodiscard]] bool IsCritter() const { return GetCreatureType() == CREATURE_TYPE_CRITTER; } [[nodiscard]] bool IsInFlight() const { return HasUnitState(UNIT_STATE_IN_FLIGHT); } + void SetImmuneToAll(bool apply, bool keepCombat = false) { SetImmuneToPC(apply, keepCombat); SetImmuneToNPC(apply, keepCombat); } + bool IsImmuneToAll() const { return IsImmuneToPC() && IsImmuneToNPC(); } + void SetImmuneToPC(bool apply, bool keepCombat = false); + bool IsImmuneToPC() const { return HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC); } + void SetImmuneToNPC(bool apply, bool keepCombat = false); + bool IsImmuneToNPC() const { return HasUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC); } + + bool IsEngaged() const { return IsInCombat(); } + bool IsEngagedBy(Unit const* who) const { return IsInCombatWith(who); } + + [[nodiscard]] bool IsInCombat() const { return HasUnitFlag(UNIT_FLAG_IN_COMBAT); } + bool IsInCombatWith(Unit const* who) const; + + [[nodiscard]] bool IsPetInCombat() const { return HasUnitFlag(UNIT_FLAG_PET_IN_COMBAT); } + void CombatStart(Unit* target, bool initialAggro = true); + void CombatStartOnCast(Unit* target, bool initialAggro = true, uint32 duration = 0); + void SetInCombatState(bool PvP, Unit* enemy = nullptr, uint32 duration = 0); + void SetInCombatWith(Unit* enemy, uint32 duration = 0); + void ClearInCombat(); + void ClearInPetCombat(); + [[nodiscard]] uint32 GetCombatTimer() const { return m_CombatTimer; } + void SetCombatTimer(uint32 timer) { m_CombatTimer = timer; } + [[nodiscard]] bool HasAuraTypeWithFamilyFlags(AuraType auraType, uint32 familyName, uint32 familyFlags) const; [[nodiscard]] bool virtual HasSpell(uint32 /*spellID*/) const { return false; } [[nodiscard]] bool HasBreakableByDamageAuraType(AuraType type, uint32 excludeAura = 0) const; @@ -1140,6 +1088,7 @@ class Unit : public WorldObject Aura* AddAura(SpellInfo const* spellInfo, uint8 effMask, Unit* target); void SetAuraStack(uint32 spellId, Unit* target, uint32 stack); void SendPlaySpellVisual(uint32 id); + void SendPlaySpellVisual(ObjectGuid guid, uint32 id); void SendPlaySpellImpact(ObjectGuid guid, uint32 id); void BuildCooldownPacket(WorldPacket& data, uint8 flags, uint32 spellId, uint32 cooldown); void BuildCooldownPacket(WorldPacket& data, uint8 flags, PacketCooldowns const& cooldowns); @@ -1260,6 +1209,7 @@ class Unit : public WorldObject void RemoveCharmedBy(Unit* charmer); void RestoreFaction(); + ControlSet m_Controlled; [[nodiscard]] Unit* GetFirstControlled() const; void RemoveAllControlled(bool onDeath = false); @@ -1280,7 +1230,7 @@ class Unit : public WorldObject void DeleteCharmInfo(); void UpdateCharmAI(); //Player* GetMoverSource() const; - + SafeUnitPointer m_movedByPlayer; SharedVisionList const& GetSharedVisionList() { return m_sharedVision; } void AddPlayerToVision(Player* player); void RemovePlayerFromVision(Player* player); @@ -1460,6 +1410,9 @@ class Unit : public WorldObject [[nodiscard]] virtual bool IsMovementPreventedByCasting() const; + ObjectGuid m_SummonSlot[MAX_SUMMON_SLOT]; + ObjectGuid m_ObjectSlot[MAX_GAMEOBJECT_SLOT]; + [[nodiscard]] ShapeshiftForm GetShapeshiftForm() const { return ShapeshiftForm(GetByteValue(UNIT_FIELD_BYTES_2, 3)); } void SetShapeshiftForm(ShapeshiftForm form) { @@ -1474,6 +1427,44 @@ class Unit : public WorldObject [[nodiscard]] bool IsInDisallowedMountForm() const; + float m_modMeleeHitChance; + float m_modRangedHitChance; + float m_modSpellHitChance; + int32 m_baseSpellCritChance; + + float m_threatModifier[MAX_SPELL_SCHOOL]; + float m_modAttackSpeedPct[3]; + + // Event handler + EventProcessor m_Events; + + // stat system + bool HandleStatModifier(UnitMods unitMod, UnitModifierType modifierType, float amount, bool apply); + void SetModifierValue(UnitMods unitMod, UnitModifierType modifierType, float value) { m_auraModifiersGroup[unitMod][modifierType] = value; } + [[nodiscard]] float GetModifierValue(UnitMods unitMod, UnitModifierType modifierType) const; + [[nodiscard]] float GetTotalStatValue(Stats stat, float additionalValue = 0.0f) const; + [[nodiscard]] float GetTotalAuraModValue(UnitMods unitMod) const; + [[nodiscard]] SpellSchools GetSpellSchoolByAuraGroup(UnitMods unitMod) const; + [[nodiscard]] Stats GetStatByAuraGroup(UnitMods unitMod) const; + [[nodiscard]] Powers GetPowerTypeByAuraGroup(UnitMods unitMod) const; + [[nodiscard]] bool CanModifyStats() const { return m_canModifyStats; } + void SetCanModifyStats(bool modifyStats) { m_canModifyStats = modifyStats; } + virtual bool UpdateStats(Stats stat) = 0; + virtual bool UpdateAllStats() = 0; + virtual void UpdateResistances(uint32 school) = 0; + virtual void UpdateAllResistances(); + virtual void UpdateArmor() = 0; + virtual void UpdateMaxHealth() = 0; + virtual void UpdateMaxPower(Powers power) = 0; + virtual void UpdateAttackPowerAndDamage(bool ranged = false) = 0; + virtual void UpdateDamagePhysical(WeaponAttackType attType); + float GetTotalAttackPowerValue(WeaponAttackType attType, Unit* pVictim = nullptr) const; + [[nodiscard]] float GetWeaponDamageRange(WeaponAttackType attType, WeaponDamageRange type, uint8 damageIndex = 0) const; + void SetBaseWeaponDamage(WeaponAttackType attType, WeaponDamageRange damageRange, float value, uint8 damageIndex = 0) { m_weaponDamage[attType][damageRange][damageIndex] = value; } + virtual void CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, float& minDamage, float& maxDamage, uint8 damageIndex = 0) = 0; + uint32 CalculateDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, uint8 itemDamagesMask = 0); + float GetAPMultiplier(WeaponAttackType attType, bool normalized); + bool isInFrontInMap(Unit const* target, float distance, float arc = M_PI) const; bool isInBackInMap(Unit const* target, float distance, float arc = M_PI) const; @@ -1487,6 +1478,21 @@ class Unit : public WorldObject void SetPhaseMask(uint32 newPhaseMask, bool update) override;// overwrite WorldObject::SetPhaseMask void UpdateObjectVisibility(bool forced = true, bool fromUpdate = false) override; + SpellImmuneList m_spellImmune[MAX_SPELL_IMMUNITY]; + uint32 m_lastSanctuaryTime; + + // Threat related methods + [[nodiscard]] bool CanHaveThreatList() const; + void AddThreat(Unit* victim, float fThreat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL, SpellInfo const* threatSpell = nullptr); + float ApplyTotalThreatModifier(float fThreat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL); + void TauntApply(Unit* victim); + void TauntFadeOut(Unit* taunter); + ThreatMgr& GetThreatMgr() { return m_ThreatMgr; } + ThreatMgr const& GetThreatMgr() const { return m_ThreatMgr; } + void addHatedBy(HostileReference* pHostileReference) { m_HostileRefMgr.insertFirst(pHostileReference); }; + void removeHatedBy(HostileReference* /*pHostileReference*/) { /* nothing to do yet */ } + HostileRefMgr& getHostileRefMgr() { return m_HostileRefMgr; } + VisibleAuraMap const* GetVisibleAuras() { return &m_visibleAuras; } AuraApplication* GetVisibleAura(uint8 slot) { @@ -1625,7 +1631,7 @@ class Unit : public WorldObject void DisableSpline(); ///-----------Combo point system------------------- - // This unit having CP on other units + // This unit having CP on other units [[nodiscard]] uint8 GetComboPoints(Unit const* who = nullptr) const { return (who && m_comboTarget != who) ? 0 : m_comboPoints; } [[nodiscard]] uint8 GetComboPoints(ObjectGuid const& guid) const { return (m_comboTarget && m_comboTarget->GetGUID() == guid) ? m_comboPoints : 0; } [[nodiscard]] Unit* GetComboTarget() const { return m_comboTarget; } @@ -1656,9 +1662,21 @@ class Unit : public WorldObject void UpdateAuraForGroup(uint8 slot); // proc trigger system - bool CanProc() { return !m_procDeep; } - void SetCantProc(bool apply); + bool CanProc() {return !m_procDeep;} + void SetCantProc(bool apply) + { + if (apply) + ++m_procDeep; + else + { + ASSERT(m_procDeep); + --m_procDeep; + } + } + // pet auras + typedef std::set PetAuraSet; + PetAuraSet m_petAuras; void AddPetAura(PetAura const* petSpell); void RemovePetAura(PetAura const* petSpell); void CastPetAura(PetAura const* aura); @@ -1667,6 +1685,14 @@ class Unit : public WorldObject [[nodiscard]] uint32 GetModelForForm(ShapeshiftForm form, uint32 spellId) const; uint32 GetModelForTotem(PlayerTotemType totemType); + // Redirect Threat + void SetRedirectThreat(ObjectGuid guid, uint32 pct) { _redirectThreatInfo.Set(guid, pct); } + void ResetRedirectThreat() { SetRedirectThreat(ObjectGuid::Empty, 0); } + void ModifyRedirectThreat(int32 amount) { _redirectThreatInfo.ModifyThreatPct(amount); } + uint32 GetRedirectThreatPercent() { return _redirectThreatInfo.GetThreatPct(); } + [[nodiscard]] Unit* GetRedirectThreatTarget() const; + + bool IsAIEnabled, NeedChangeAI; bool CreateVehicleKit(uint32 id, uint32 creatureEntry); void RemoveVehicleKit(); [[nodiscard]] Vehicle* GetVehicleKit()const { return m_vehicleKit; } @@ -1678,6 +1704,9 @@ class Unit : public WorldObject /// Returns the transport this unit is on directly (if on vehicle and transport, return vehicle) [[nodiscard]] TransportBase* GetDirectTransport() const; + bool m_ControlledByPlayer; + bool m_CreatedByPlayer; + bool HandleSpellClick(Unit* clicker, int8 seatId = -1); void EnterVehicle(Unit* base, int8 seatId = -1); void EnterVehicleUnattackable(Unit* base, int8 seatId = -1); @@ -1716,10 +1745,17 @@ class Unit : public WorldObject TempSummon* ToTempSummon() { if (IsSummon()) return reinterpret_cast(this); else return nullptr; } [[nodiscard]] const TempSummon* ToTempSummon() const { if (IsSummon()) return reinterpret_cast(this); else return nullptr; } + // Safe mover + std::set SafeUnitPointerSet; void AddPointedBy(SafeUnitPointer* sup) { SafeUnitPointerSet.insert(sup); } void RemovePointedBy(SafeUnitPointer* sup) { SafeUnitPointerSet.erase(sup); } static void HandleSafeUnitPointersOnDelete(Unit* thisUnit); - + // Relocation Nofier optimization + Position m_last_notify_position; + uint32 m_last_notify_mstime; + uint16 m_delayed_unit_relocation_timer; + uint16 m_delayed_unit_ai_notify_timer; + bool bRequestForcedVisibilityUpdate; void ExecuteDelayedUnitRelocationEvent(); void ExecuteDelayedUnitAINotifyEvent(); @@ -1745,6 +1781,9 @@ class Unit : public WorldObject void SetInstantCast(bool set) { _instantCast = set; } [[nodiscard]] bool CanInstantCast() const { return _instantCast; } + // Movement info + Movement::MoveSpline* movespline; + virtual void Talk(std::string_view text, ChatMsg msgType, Language language, float textRange, WorldObject const* target); virtual void Say(std::string_view text, Language language, WorldObject const* target = nullptr); virtual void Yell(std::string_view text, Language language, WorldObject const* target = nullptr); @@ -1766,79 +1805,25 @@ class Unit : public WorldObject [[nodiscard]] bool CanRestoreMana(SpellInfo const* spellInfo) const; std::string GetDebugInfo() const override; + void SetCannotReachTargetUnit(bool target, bool isChase); + [[nodiscard]] bool CanNotReachTarget() const; - [[nodiscard]] uint32 GetOldFactionId() const { return _oldFactionId; } - - //----------- Public variables ----------// - uint32 m_extraAttacks; - bool m_canDualWield; - - ControlSet m_Controlled; - - SafeUnitPointer m_movedByPlayer; - - ObjectGuid m_SummonSlot[MAX_SUMMON_SLOT]; - ObjectGuid m_ObjectSlot[MAX_GAMEOBJECT_SLOT]; + bool m_cannotReachTarget; - float m_modMeleeHitChance; - float m_modRangedHitChance; - float m_modSpellHitChance; - int32 m_baseSpellCritChance; - - float m_threatModifier[MAX_SPELL_SCHOOL]; - float m_modAttackSpeedPct[3]; - - // Event handler - EventProcessor m_Events; - - SpellImmuneList m_spellImmune[MAX_SPELL_IMMUNITY]; - uint32 m_lastSanctuaryTime; - - // pet auras - typedef std::set PetAuraSet; - PetAuraSet m_petAuras; - - bool IsAIEnabled; - bool NeedChangeAI; - - bool m_ControlledByPlayer; - bool m_CreatedByPlayer; - - // Safe mover - std::set SafeUnitPointerSet; - - // Relocation Nofier optimization - Position m_last_notify_position; - uint32 m_last_notify_mstime; - uint16 m_delayed_unit_relocation_timer; - uint16 m_delayed_unit_ai_notify_timer; - bool bRequestForcedVisibilityUpdate; - - // Movement info - Movement::MoveSpline* movespline; + [[nodiscard]] uint32 GetOldFactionId() const { return _oldFactionId; } protected: explicit Unit (bool isWorldObject); void BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target) override; + UnitAI* i_AI, *i_disabledAI; + void _UpdateSpells(uint32 time); void _DeleteRemovedAuras(); void _UpdateAutoRepeatSpell(); - bool IsAlwaysVisibleFor(WorldObject const* seer) const override; - bool IsAlwaysDetectableFor(WorldObject const* seer) const override; - - void SetFeared(bool apply, Unit* fearedBy = nullptr, bool isFear = false); - void SetConfused(bool apply); - void SetStunned(bool apply); - void SetRooted(bool apply, bool isStun = false); - - //----------- Protected variables ----------// - UnitAI* i_AI; - UnitAI* i_disabledAI; - uint8 m_realRace; uint8 m_race; @@ -1872,7 +1857,7 @@ class Unit : public WorldObject AuraEffectList m_modAuras[TOTAL_AURAS]; AuraList m_scAuras; // casted singlecast auras - AuraApplicationList m_interruptableAuras; // auras which have interrupt mask applied on unit + AuraApplicationList m_interruptableAuras; // auras which have interrupt mask applied on unit AuraStateAurasMap m_auraStateAuras; // Used for improve performance of aura state checks on aura apply/remove uint32 m_interruptMask; @@ -1903,10 +1888,10 @@ class Unit : public WorldObject // xinef: apply resilience bool m_applyResilience; + bool IsAlwaysVisibleFor(WorldObject const* seer) const override; + bool IsAlwaysDetectableFor(WorldObject const* seer) const override; bool _instantCast; - uint32 m_rootTimes; - private: bool IsTriggeredAtSpellProcEvent(Unit* victim, Aura* aura, WeaponAttackType attType, bool isVictim, bool active, SpellProcEventEntry const*& spellProcEvent, ProcEventInfo const& eventInfo); bool HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggeredByAura, SpellInfo const* procSpell, uint32 procFlag, uint32 procEx, uint32 cooldown, ProcEventInfo const& eventInfo); @@ -1926,12 +1911,15 @@ class Unit : public WorldObject void PatchValuesUpdate(ByteBuffer& valuesUpdateBuf, BuildValuesCachePosPointers& posPointers, Player* target); void InvalidateValuesUpdateCache() { _valuesUpdateCache.clear(); } - [[nodiscard]] float processDummyAuras(float TakenTotalMod) const; +protected: + void SetFeared(bool apply, Unit* fearedBy = nullptr, bool isFear = false); + void SetConfused(bool apply); + void SetStunned(bool apply); + void SetRooted(bool apply, bool isStun = false); - void _addAttacker(Unit* pAttacker) { m_attackers.insert(pAttacker); } ///@note: Call only in Unit::Attack() - void _removeAttacker(Unit* pAttacker) { m_attackers.erase(pAttacker); } ///@note: Call only in Unit::AttackStop() + uint32 m_rootTimes; - //----------- Private variables ----------// +private: uint32 m_state; // Even derived shouldn't modify uint32 m_CombatTimer; uint32 m_lastManaUse; // msecs @@ -1955,6 +1943,8 @@ class Unit : public WorldObject uint32 _oldFactionId; ///< faction before charm bool _isWalkingBeforeCharm; ///< Are we walking before we were charmed? + [[nodiscard]] float processDummyAuras(float TakenTotalMod) const; + uint32 _lastExtraAttackSpell; std::unordered_map extraAttacksTargets; ObjectGuid _lastDamagedTargetGuid; diff --git a/src/server/game/Entities/Vehicle/VehicleDefines.h b/src/server/game/Entities/Vehicle/VehicleDefines.h index 227bce7a8ddd56..8facc8aee34c86 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 }; enum VehicleSpells diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index e09935f351178a..836f93f5511934 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -858,6 +858,7 @@ class ObjectMgr 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 f4afabf6f79d00..e07360342b5fe0 100644 --- a/src/server/game/Groups/Group.h +++ b/src/server/game/Groups/Group.h @@ -260,6 +260,8 @@ class Group void SetGroupMemberFlag(ObjectGuid guid, bool apply, GroupMemberFlags flag); void RemoveUniqueGroupMemberFlag(GroupMemberFlags flag); + ObjectGuid const GetTargetIcon(uint8 id) const { return m_targetIcons[id]; } + Difficulty GetDifficulty(bool isRaid) const; Difficulty GetDungeonDifficulty() const; Difficulty GetRaidDifficulty() const; @@ -298,6 +300,8 @@ class Group 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 648131080b3d06..06326e9fe5983e 100644 --- a/src/server/game/Guilds/Guild.cpp +++ b/src/server/game/Guilds/Guild.cpp @@ -829,7 +829,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 @@ -1218,7 +1218,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 +1275,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 +1298,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 +1331,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(); @@ -1367,7 +1373,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 +1406,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 +1499,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 +1570,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)) { @@ -1571,7 +1600,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)) @@ -1705,7 +1734,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. @@ -1847,7 +1876,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) @@ -2113,13 +2142,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->GetSession()->SendPacket(&data); } } @@ -2522,7 +2551,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(); @@ -2571,7 +2600,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) @@ -2591,7 +2620,7 @@ inline void Guild::_UpdateMemberWithdrawSlots(CharacterDatabaseTransaction trans } } -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)) { @@ -2603,6 +2632,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) { @@ -2748,7 +2790,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); @@ -2911,7 +2953,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(); diff --git a/src/server/game/Guilds/Guild.h b/src/server/game/Guilds/Guild.h index 3ed7e10ccbef56..303a035726958a 100644 --- a/src/server/game/Guilds/Guild.h +++ b/src/server/game/Guilds/Guild.h @@ -241,7 +241,8 @@ enum GuildMemberFlags 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; @@ -699,11 +700,13 @@ class Guild 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 officer); 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); @@ -782,6 +785,10 @@ class Guild [[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; @@ -806,13 +813,6 @@ class Guild 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); } @@ -843,7 +843,6 @@ class Guild 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; @@ -851,7 +850,6 @@ class Guild 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 4021d541f702b3..2e637662d25001 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -59,19 +59,9 @@ #include "WorldPacket.h" #include "WorldSession.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() { @@ -569,7 +559,12 @@ void WorldSession::HandleCharCreateOpcode(WorldPacket& recvData) newChar->SaveToDB(characterTransaction, true, false); createInfo->CharCount++; - LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_REP_REALM_CHARACTERS); + 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); @@ -793,6 +788,7 @@ void WorldSession::HandlePlayerLoginOpcode(WorldPacket& recvData) void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder) { + m_playerLoading = true; ObjectGuid playerGuid = holder.GetGuid(); Player* pCurrChar = new Player(this); @@ -909,8 +905,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()); diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp index f5c3df9b85d858..a5b5fcc90a0b5c 100644 --- a/src/server/game/Handlers/ChatHandler.cpp +++ b/src/server/game/Handlers/ChatHandler.cpp @@ -415,6 +415,13 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) if (!senderIsPlayer && !sender->isAcceptWhispers() && !sender->IsInWhisperWhiteList(receiver->GetGUID())) sender->AddWhisperWhiteList(receiver->GetGUID()); + if (!sScriptMgr->CanPlayerUseChat(GetPlayer(), type, lang, msg, receiver)) + { + return; + } + + sScriptMgr->OnPlayerChat(GetPlayer(), type, lang, msg, receiver); + GetPlayer()->Whisper(msg, Language(lang), receiver); } break; @@ -460,6 +467,10 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) guild->BroadcastToGuild(this, false, msg, lang == LANG_ADDON ? LANG_ADDON : LANG_UNIVERSAL); } + else + { + sScriptMgr->OnPlayerChat(GetPlayer(), type, lang, msg); + } } } break; diff --git a/src/server/game/Handlers/PetitionsHandler.cpp b/src/server/game/Handlers/PetitionsHandler.cpp index 3023822345f550..41d553ed0d0843 100644 --- a/src/server/game/Handlers/PetitionsHandler.cpp +++ b/src/server/game/Handlers/PetitionsHandler.cpp @@ -478,6 +478,8 @@ void WorldSession::HandlePetitionSignOpcode(WorldPacket& recvData) break; } + sScriptMgr->OnPlayerbotCheckPetitionAccount(_player, found); + if (found) { WorldPacket data(SMSG_PETITION_SIGN_RESULTS, (8 + 8 + 4)); diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index ec3dffb8fa605f..21fa4d3aeb4b53 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -2626,6 +2626,12 @@ void Map::SendObjectUpdates() WorldPacket packet; // here we allocate a std::vector with a size of 0x10000 for (UpdateDataMapType::iterator iter = update_players.begin(); iter != update_players.end(); ++iter) { + if (!sScriptMgr->OnPlayerbotCheckUpdatesToSend(iter->first)) + { + iter->second.Clear(); + continue; + } + iter->second.BuildPacket(packet); iter->first->GetSession()->SendPacket(&packet); packet.clear(); // clean the string diff --git a/src/server/game/Misc/GameGraveyard.cpp b/src/server/game/Misc/GameGraveyard.cpp index 08d9232bc88072..37a20bc7e3eb7e 100644 --- a/src/server/game/Misc/GameGraveyard.cpp +++ b/src/server/game/Misc/GameGraveyard.cpp @@ -119,6 +119,11 @@ GraveyardStruct const* Graveyard::GetClosestGraveyard(Player* player, TeamId tea uint32 areaId = 0; player->GetZoneAndAreaId(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 35092818bea294..0349f558bf900a 100644 --- a/src/server/game/Misc/GameGraveyard.h +++ b/src/server/game/Misc/GameGraveyard.h @@ -58,6 +58,7 @@ class Graveyard 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 39b8aa7173dfef..7fdf96c7b68162 100644 --- a/src/server/game/Movement/MotionMaster.cpp +++ b/src/server/game/Movement/MotionMaster.cpp @@ -884,6 +884,30 @@ 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); + init.Launch(); + Mutate(new EffectMovementGenerator(0), MOTION_SLOT_CONTROLLED); +} +#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 1c3d16c393d01d..39769e1aa1ac12 100644 --- a/src/server/game/Movement/MotionMaster.h +++ b/src/server/game/Movement/MotionMaster.h @@ -235,7 +235,9 @@ class MotionMaster //: private std::stack void MoveDistract(uint32 time); void MovePath(uint32 path_id, bool repeatable); void MoveRotate(uint32 time, RotateDirection direction); - +#ifdef MOD_PLAYERBOTS + void MoveKnockbackFromForPlayer(float srcX, float srcY, float speedXY, float speedZ); +#endif [[nodiscard]] MovementGeneratorType GetCurrentMovementGeneratorType() const; [[nodiscard]] MovementGeneratorType GetMotionSlotType(int slot) const; [[nodiscard]] uint32 GetCurrentSplineId() const; // Xinef: Escort system diff --git a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp index 0a58d145088cd3..4dc2f55bd28243 100644 --- a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp @@ -78,11 +78,8 @@ bool ChaseMovementGenerator::DoUpdate(T* owner, uint32 time_diff) { owner->StopMoving(); _lastTargetPosition.reset(); - if (cOwner) - { - cOwner->UpdateLeashExtensionTime(); - cOwner->SetCannotReachTarget(); - } + if (Creature* cOwner2 = owner->ToCreature()) + cOwner2->SetCannotReachTarget(); return true; } @@ -131,8 +128,8 @@ bool ChaseMovementGenerator::DoUpdate(T* owner, uint32 time_diff) { i_recalculateTravel = false; i_path = nullptr; - if (cOwner) - cOwner->SetCannotReachTarget(); + if (Creature* cOwner2 = owner->ToCreature()) + cOwner2->SetCannotReachTarget(); owner->StopMoving(); owner->SetInFront(target); MovementInform(owner); @@ -146,25 +143,21 @@ bool ChaseMovementGenerator::DoUpdate(T* owner, uint32 time_diff) { i_recalculateTravel = false; i_path = nullptr; - if (cOwner) - cOwner->SetCannotReachTarget(); + if (Creature* cOwner2 = owner->ToCreature()) + cOwner2->SetCannotReachTarget(); owner->ClearUnitState(UNIT_STATE_CHASE_MOVE); owner->SetInFront(target); MovementInform(owner); - } - if (owner->movespline->Finalized()) - { // Mobs should chase you infinitely if you stop and wait every few seconds. + // Mobs should chase you infinitely if you stop and wait every few seconds. i_leashExtensionTimer.Update(time_diff); if (i_leashExtensionTimer.Passed()) { - i_leashExtensionTimer.Reset(1500); - if (cOwner) - cOwner->UpdateLeashExtensionTime(); + i_leashExtensionTimer.Reset(5000); + if (Creature* creature = owner->ToCreature()) + creature->UpdateLeashExtensionTime(); } } - else if (i_recalculateTravel) - i_leashExtensionTimer.Reset(1500); // if the target moved, we have to consider whether to adjust if (!_lastTargetPosition || target->GetPosition() != _lastTargetPosition.value() || mutualChase != _mutualChase || !owner->IsWithinLOSInMap(target)) diff --git a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h index fd3064b5138ce4..4e7df89db811e1 100644 --- a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h +++ b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h @@ -39,7 +39,7 @@ class ChaseMovementGenerator : public MovementGeneratorMedium range = {}, Optional angle = {}) - : TargetedMovementGeneratorBase(target), i_leashExtensionTimer(1500), i_path(nullptr), i_recheckDistance(0), i_recalculateTravel(true), _range(range), _angle(angle) {} + : TargetedMovementGeneratorBase(target), i_leashExtensionTimer(0), i_path(nullptr), i_recheckDistance(0), i_recalculateTravel(true), _range(range), _angle(angle) {} ~ChaseMovementGenerator() { } MovementGeneratorType GetMovementGeneratorType() { return CHASE_MOTION_TYPE; } diff --git a/src/server/game/Scripting/ScriptDefines/AllBattlegroundScript.cpp b/src/server/game/Scripting/ScriptDefines/AllBattlegroundScript.cpp index af617ddba39076..9367a748b5df7e 100644 --- a/src/server/game/Scripting/ScriptDefines/AllBattlegroundScript.cpp +++ b/src/server/game/Scripting/ScriptDefines/AllBattlegroundScript.cpp @@ -54,11 +54,6 @@ void ScriptMgr::OnQueueUpdate(BattlegroundQueue* queue, uint32 diff, Battlegroun CALL_ENABLED_HOOKS(AllBattlegroundScript, ALLBATTLEGROUNDHOOK_ON_QUEUE_UPDATE, script->OnQueueUpdate(queue, diff, bgTypeId, bracket_id, arenaType, isRated, arenaRating)); } -bool ScriptMgr::OnQueueUpdateValidity(BattlegroundQueue* queue, uint32 diff, BattlegroundTypeId bgTypeId, BattlegroundBracketId bracket_id, uint8 arenaType, bool isRated, uint32 arenaRating) -{ - CALL_ENABLED_BOOLEAN_HOOKS(AllBattlegroundScript, ALLBATTLEGROUNDHOOK_ON_QUEUE_UPDATE_VALIDITY, !script->OnQueueUpdateValidity(queue, diff, bgTypeId, bracket_id, arenaType, isRated, arenaRating)); -} - void ScriptMgr::OnAddGroup(BattlegroundQueue* queue, GroupQueueInfo* ginfo, uint32& index, Player* leader, Group* group, BattlegroundTypeId bgTypeId, PvPDifficultyEntry const* bracketEntry, uint8 arenaType, bool isRated, bool isPremade, uint32 arenaRating, uint32 matchmakerRating, uint32 arenaTeamId, uint32 opponentsArenaTeamId) { CALL_ENABLED_HOOKS(AllBattlegroundScript, ALLBATTLEGROUNDHOOK_ON_ADD_GROUP, script->OnAddGroup(queue, ginfo, index, leader, group, bgTypeId, bracketEntry, arenaType, isRated, isPremade, arenaRating, matchmakerRating, arenaTeamId, opponentsArenaTeamId)); diff --git a/src/server/game/Scripting/ScriptDefines/AllBattlegroundScript.h b/src/server/game/Scripting/ScriptDefines/AllBattlegroundScript.h index 9e8baba15103fe..880518b5dc548e 100644 --- a/src/server/game/Scripting/ScriptDefines/AllBattlegroundScript.h +++ b/src/server/game/Scripting/ScriptDefines/AllBattlegroundScript.h @@ -30,7 +30,6 @@ enum AllBattlegroundHook ALLBATTLEGROUNDHOOK_ON_BATTLEGROUND_BEFORE_ADD_PLAYER, ALLBATTLEGROUNDHOOK_ON_BATTLEGROUND_REMOVE_PLAYER_AT_LEAVE, ALLBATTLEGROUNDHOOK_ON_QUEUE_UPDATE, - ALLBATTLEGROUNDHOOK_ON_QUEUE_UPDATE_VALIDITY, ALLBATTLEGROUNDHOOK_ON_ADD_GROUP, ALLBATTLEGROUNDHOOK_CAN_FILL_PLAYERS_TO_BG, ALLBATTLEGROUNDHOOK_IS_CHECK_NORMAL_MATCH, @@ -79,8 +78,6 @@ class AllBattlegroundScript : public ScriptObject virtual void OnQueueUpdate(BattlegroundQueue* /*queue*/, uint32 /* diff */, BattlegroundTypeId /* bgTypeId */, BattlegroundBracketId /* bracket_id */, uint8 /* arenaType */, bool /* isRated */, uint32 /* arenaRating */) { } - [[nodiscard]] virtual bool OnQueueUpdateValidity(BattlegroundQueue* /*queue*/, uint32 /* diff */, BattlegroundTypeId /* bgTypeId */, BattlegroundBracketId /* bracket_id */, uint8 /* arenaType */, bool /* isRated */, uint32 /* arenaRating */) { return true; } - virtual void OnAddGroup(BattlegroundQueue* /*queue*/, GroupQueueInfo* /*ginfo*/, uint32& /*index*/, Player* /*leader*/, Group* /*group*/, BattlegroundTypeId /* bgTypeId */, PvPDifficultyEntry const* /* bracketEntry */, uint8 /* arenaType */, bool /* isRated */, bool /* isPremade */, uint32 /* arenaRating */, uint32 /* matchmakerRating */, uint32 /* arenaTeamId */, uint32 /* opponentsArenaTeamId */) { } diff --git a/src/server/game/Scripting/ScriptDefines/ArenaScript.cpp b/src/server/game/Scripting/ScriptDefines/ArenaScript.cpp index b0869c79963afe..35baeb271a44cc 100644 --- a/src/server/game/Scripting/ScriptDefines/ArenaScript.cpp +++ b/src/server/game/Scripting/ScriptDefines/ArenaScript.cpp @@ -39,11 +39,6 @@ bool ScriptMgr::OnBeforeArenaCheckWinConditions(Battleground* const bg) CALL_ENABLED_BOOLEAN_HOOKS(ArenaScript, ARENAHOOK_ON_BEFORE_CHECK_WIN_CONDITION, !script->OnBeforeArenaCheckWinConditions(bg)); } -void ScriptMgr::OnArenaStart(Battleground* bg) -{ - CALL_ENABLED_HOOKS(ArenaScript, ARENAHOOK_ON_ARENA_START, script->OnArenaStart(bg)); -} - ArenaScript::ArenaScript(const char* name, std::vector enabledHooks) : ScriptObject(name, ARENAHOOK_END) { diff --git a/src/server/game/Scripting/ScriptDefines/ArenaScript.h b/src/server/game/Scripting/ScriptDefines/ArenaScript.h index e9a3f1c800bc75..c50a964498a812 100644 --- a/src/server/game/Scripting/ScriptDefines/ArenaScript.h +++ b/src/server/game/Scripting/ScriptDefines/ArenaScript.h @@ -28,7 +28,6 @@ enum ArenaHook ARENAHOOK_ON_GET_POINTS, ARENAHOOK_CAN_SAVE_TO_DB, ARENAHOOK_ON_BEFORE_CHECK_WIN_CONDITION, - ARENAHOOK_ON_ARENA_START, ARENAHOOK_END }; @@ -49,8 +48,6 @@ class ArenaScript : public ScriptObject [[nodiscard]] virtual bool OnBeforeArenaCheckWinConditions(Battleground* const /* bg */) { return true; } [[nodiscard]] virtual bool CanSaveToDB(ArenaTeam* /*team*/) { return true; } - - virtual void OnArenaStart(Battleground* /* bg */) { }; }; #endif diff --git a/src/server/game/Scripting/ScriptDefines/DatabaseScript.cpp b/src/server/game/Scripting/ScriptDefines/DatabaseScript.cpp index 550564373e610f..f5e1f353a8a240 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 3340676fa860d5..f935b783add178 100644 --- a/src/server/game/Scripting/ScriptDefines/DatabaseScript.h +++ b/src/server/game/Scripting/ScriptDefines/DatabaseScript.h @@ -52,6 +52,13 @@ class DatabaseScript : public ScriptObject */ 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/MetricScript.cpp b/src/server/game/Scripting/ScriptDefines/MetricScript.cpp new file mode 100644 index 00000000000000..a9de340a05eed9 --- /dev/null +++ b/src/server/game/Scripting/ScriptDefines/MetricScript.cpp @@ -0,0 +1,27 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "ScriptMgr.h" +#include "ScriptMgrMacros.h" + +void ScriptMgr::OnMetricLogging() +{ + ExecuteScript([&](MetricScript* script) + { + script->OnMetricLogging(); + }); +} diff --git a/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp b/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp index 9c6769f4587601..3e4713adbd1c38 100644 --- a/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp +++ b/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp @@ -219,6 +219,14 @@ void ScriptMgr::OnPlayerUpdate(Player* player, uint32 p_time) CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_UPDATE, script->OnUpdate(player, p_time)); } +void ScriptMgr::OnAfterPlayerUpdate(Player* player, uint32 diff) +{ + ExecuteScript([&](PlayerScript* script) + { + script->OnAfterUpdate(player, diff); + }); +} + void ScriptMgr::OnPlayerLogin(Player* player) { CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_LOGIN, script->OnLogin(player)); @@ -359,6 +367,16 @@ void ScriptMgr::OnPlayerJoinArena(Player* player) CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_PLAYER_JOIN_ARENA, script->OnPlayerJoinArena(player)); } +void ScriptMgr::GetCustomGetArenaTeamId(Player const* player, uint8 slot, uint32& teamID) const +{ + CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_GET_CUSTOM_GET_ARENA_TEAM_ID, script->GetCustomGetArenaTeamId(player, slot, teamID)); +} + +void ScriptMgr::GetCustomArenaPersonalRating(Player const* player, uint8 slot, uint32& rating) const +{ + CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_GET_CUSTOM_ARENA_PERSONAL_RATING, script->GetCustomArenaPersonalRating(player, slot, rating)); +} + void ScriptMgr::OnGetMaxPersonalArenaRatingRequirement(Player const* player, uint32 minSlot, uint32& maxArenaRating) const { CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_GET_MAX_PERSONAL_ARENA_RATING_REQUIREMENT, script->OnGetMaxPersonalArenaRatingRequirement(player, minSlot, maxArenaRating)); diff --git a/src/server/game/Scripting/ScriptDefines/PlayerScript.h b/src/server/game/Scripting/ScriptDefines/PlayerScript.h index 4b13b96e47ae77..0afa8081dd178b 100644 --- a/src/server/game/Scripting/ScriptDefines/PlayerScript.h +++ b/src/server/game/Scripting/ScriptDefines/PlayerScript.h @@ -258,7 +258,8 @@ class PlayerScript : public ScriptObject // Called for player::update virtual void OnBeforeUpdate(Player* /*player*/, uint32 /*p_time*/) { } - virtual void OnUpdate(Player* /*player*/, uint32 /*p_time*/) { } + virtual void OnUpdate(Player* /*player*/, uint32 /*p_time*/) {} + virtual void OnAfterUpdate(Player* /*player*/, uint32 /*diff*/) {} // Called when a player's money is modified (before the modification is done) virtual void OnMoneyChanged(Player* /*player*/, int32& /*amount*/) { } @@ -404,6 +405,12 @@ class PlayerScript : public ScriptObject // After player enters queue for Arena virtual void OnPlayerJoinArena(Player* /*player*/) { } + //Called when trying to get a team ID of a slot > 2 (This is for custom teams created by modules) + virtual void GetCustomGetArenaTeamId(Player const* /*player*/, uint8 /*slot*/, uint32& /*teamID*/) const { } + + //Called when trying to get players personal rating of an arena slot > 2 (This is for custom teams created by modules) + virtual void GetCustomArenaPersonalRating(Player const* /*player*/, uint8 /*slot*/, uint32& /*rating*/) const { } + //Called after the normal slots (0..2) for arena have been evaluated so that custom arena teams could modify it if nececasry virtual void OnGetMaxPersonalArenaRatingRequirement(Player const* /*player*/, uint32 /*minSlot*/, uint32& /*maxArenaRating*/) const {} diff --git a/src/server/game/Scripting/ScriptDefines/PlayerbotsScript.cpp b/src/server/game/Scripting/ScriptDefines/PlayerbotsScript.cpp new file mode 100644 index 00000000000000..c180888bdf2cb8 --- /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 Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#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 1384d680be1dd4..0ee0399974075e 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 03c974194cf5d0..546d9152b89c5e 100644 --- a/src/server/game/Scripting/ScriptDefines/ServerScript.h +++ b/src/server/game/Scripting/ScriptDefines/ServerScript.h @@ -69,6 +69,8 @@ class ServerScript : public ScriptObject * @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 9ec3ffeea13936..265a39c799edae 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -30,9 +30,24 @@ namespace template inline void SCR_CLEAR() { - for (auto const& [scriptID, script] : ScriptRegistry::ScriptPointerList) + for (auto& [scriptID, script] : ScriptRegistry::ScriptPointerList) { - delete script; + try + { + if(script) + { + delete script; + script = nullptr; + } + } + catch (const std::exception& e) + { + LOG_ERROR("scripts.unloading", "Failed to unload script {} with ID: {}. Error: {}", script->GetName(), scriptID, e.what()); + } + catch (...) + { + LOG_ERROR("scripts.unloading", "Failed to unload script {} with ID: {}. Unknown error occurred.", script->GetName(), scriptID); + } } ScriptRegistry::ScriptPointerList.clear(); @@ -59,6 +74,17 @@ ScriptMgr* ScriptMgr::instance() return &instance; } +// Yunfan: refactor +MetricScript::MetricScript(const char* name) : ScriptObject(name) +{ + ScriptRegistry::AddScript(this); +} + +PlayerbotScript::PlayerbotScript(const char* name) : ScriptObject(name) +{ + ScriptRegistry::AddScript(this); +} + void ScriptMgr::Initialize() { LOG_INFO("server.loading", "> Loading C++ scripts"); @@ -137,11 +163,13 @@ void ScriptMgr::Unload() SCR_CLEAR(); SCR_CLEAR(); SCR_CLEAR(); + SCR_CLEAR(); SCR_CLEAR(); SCR_CLEAR(); SCR_CLEAR(); SCR_CLEAR(); SCR_CLEAR(); + SCR_CLEAR(); SCR_CLEAR(); SCR_CLEAR(); SCR_CLEAR(); @@ -224,7 +252,9 @@ void ScriptMgr::CheckIfScriptsInDatabaseExist() !ScriptRegistry::GetScriptById(sid) && !ScriptRegistry::GetScriptById(sid) && !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 4dcb3bdfbea1e8..47a6c8669528d8 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -106,7 +106,38 @@ namespace Acore::ChatCommands */ -// Manages registration, loading, and execution of scripts. +// Yunfan: refactor +class MetricScript : public ScriptObject +{ +protected: + MetricScript(const char* name); + +public: + bool IsDatabaseBound() const { return false; } + + virtual void OnMetricLogging() { } +}; + +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; @@ -159,6 +190,7 @@ class ScriptMgr 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 */ @@ -296,6 +328,7 @@ class ScriptMgr public: /* PlayerScript */ void OnBeforePlayerUpdate(Player* player, uint32 p_time); void OnPlayerUpdate(Player* player, uint32 p_time); + void OnAfterPlayerUpdate(Player* player, uint32 diff); void OnSendInitialPacketsBeforeAddToMap(Player* player, WorldPacket& data); void OnPlayerJustDied(Player* player); void OnCalculateTalentsPoints(Player const* player, uint32& talentPointsForLevel); @@ -358,6 +391,8 @@ class ScriptMgr void OnEquip(Player* player, Item* it, uint8 bag, uint8 slot, bool update); void OnPlayerJoinBG(Player* player); void OnPlayerJoinArena(Player* player); + void GetCustomGetArenaTeamId(Player const* player, uint8 slot, uint32& teamID) const; + void GetCustomArenaPersonalRating(Player const* player, uint8 slot, uint32& rating) const; void OnGetMaxPersonalArenaRatingRequirement(Player const* player, uint32 minSlot, uint32& maxArenaRating) const; void OnLootItem(Player* player, Item* item, uint32 count, ObjectGuid lootguid); void OnBeforeFillQuestLootItem(Player* player, LootItem& item); @@ -582,7 +617,6 @@ class ScriptMgr void OnBattlegroundBeforeAddPlayer(Battleground* bg, Player* player); void OnBattlegroundRemovePlayerAtLeave(Battleground* bg, Player* player); void OnQueueUpdate(BattlegroundQueue* queue, uint32 diff, BattlegroundTypeId bgTypeId, BattlegroundBracketId bracket_id, uint8 arenaType, bool isRated, uint32 arenaRating); - bool OnQueueUpdateValidity(BattlegroundQueue* queue, uint32 diff, BattlegroundTypeId bgTypeId, BattlegroundBracketId bracket_id, uint8 arenaType, bool isRated, uint32 arenaRating); void OnAddGroup(BattlegroundQueue* queue, GroupQueueInfo* ginfo, uint32& index, Player* leader, Group* group, BattlegroundTypeId bgTypeId, PvPDifficultyEntry const* bracketEntry, uint8 arenaType, bool isRated, bool isPremade, uint32 arenaRating, uint32 matchmakerRating, uint32 arenaTeamId, uint32 opponentsArenaTeamId); bool CanFillPlayersToBG(BattlegroundQueue* queue, Battleground* bg, BattlegroundBracketId bracket_id); @@ -646,7 +680,6 @@ class ScriptMgr void OnGetPoints(ArenaTeam* team, uint32 memberRating, float& points); bool CanSaveToDB(ArenaTeam* team); bool OnBeforeArenaCheckWinConditions(Battleground* const bg); - void OnArenaStart(Battleground* const bg); public: /* MiscScript */ @@ -676,8 +709,14 @@ class ScriptMgr 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 */ @@ -695,6 +734,21 @@ class ScriptMgr void OnLootMoney(Player* player, uint32 gold); +public: /* MetricScript */ + + void OnMetricLogging(); + +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(); + private: uint32 _scriptCount; diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 55ab51c007a66b..371c6ad45847f6 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -104,7 +104,7 @@ bool WorldSessionFilter::Process(WorldPacket* packet) /// WorldSession constructor WorldSession::WorldSession(uint32 id, std::string&& name, 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), _lastAuctionListItemsMSTime(0), @@ -136,7 +136,8 @@ WorldSession::WorldSession(uint32 id, std::string&& name, std::shared_ptrGetOpcode() == NULL_OPCODE) + { + LOG_ERROR("network.opcode", "{} send NULL_OPCODE", GetPlayerInfo()); + return; + } + + sScriptMgr->OnPlayerbotPacketSent(GetPlayer(), packet); + if (!m_Socket) return; @@ -348,6 +361,7 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) opHandle->Call(this, *packet); LogUnprocessedTail(packet); + sScriptMgr->OnPacketReceived(this, *packet); } else processedPackets = MAX_PROCESSED_PACKETS_IN_SAME_WORLDSESSION_UPDATE; // break out of packet processing loop @@ -369,6 +383,8 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) opHandle->Call(this, *packet); LogUnprocessedTail(packet); + + sScriptMgr->OnPacketReceived(this, *packet); } else processedPackets = MAX_PROCESSED_PACKETS_IN_SAME_WORLDSESSION_UPDATE; // break out of packet processing loop @@ -383,6 +399,8 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) opHandle->Call(this, *packet); LogUnprocessedTail(packet); + + sScriptMgr->OnPacketReceived(this, *packet); } else processedPackets = MAX_PROCESSED_PACKETS_IN_SAME_WORLDSESSION_UPDATE; // break out of packet processing loop @@ -405,6 +423,8 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) opHandle->Call(this, *packet); LogUnprocessedTail(packet); + + sScriptMgr->OnPacketReceived(this, *packet); } else processedPackets = MAX_PROCESSED_PACKETS_IN_SAME_WORLDSESSION_UPDATE; // break out of packet processing loop @@ -495,6 +515,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); @@ -587,6 +609,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()) @@ -714,6 +738,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 @@ -733,8 +761,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); } @@ -1695,3 +1723,8 @@ void WorldSession::InitializeSessionCallback(CharacterDatabaseQueryHolder const& SendClientCacheVersion(clientCacheVersion); SendTutorialsData(); } + +LockedQueue& WorldSession::GetPacketQueue() +{ + return _recvQueue; +} diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 31e9e566ddfed1..aacc59b6b4c2c8 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; @@ -225,6 +225,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 @@ -269,6 +283,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; @@ -329,7 +348,8 @@ struct PacketCounter class WorldSession { public: - WorldSession(uint32 id, std::string&& name, 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, std::shared_ptr sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale, + uint32 recruiter, bool isARecruiter, bool skipQueue, uint32 TotalTime, bool isBot = false); ~WorldSession(); bool IsGMAccount() const; @@ -494,8 +514,8 @@ class WorldSession time_t m_muteTime; // Locales - LocaleConstant GetSessionDbcLocale() const { return m_sessionDbcLocale; } - LocaleConstant GetSessionDbLocaleIndex() const { return m_sessionDbLocaleIndex; } + LocaleConstant GetSessionDbcLocale() const { return /*_isBot? LOCALE_enUS : */m_sessionDbcLocale; } + LocaleConstant GetSessionDbLocaleIndex() const { return /*_isBot? LOCALE_enUS : */m_sessionDbLocaleIndex; } char const* GetAcoreString(uint32 entry) const; std::string const* GetModuleString(std::string module, uint32 id) const; @@ -533,6 +553,7 @@ class WorldSession // Time Synchronisation void ResetTimeSync(); void SendTimeSync(); + public: // opcodes handlers void Handle_NULL(WorldPacket& null); // not used void Handle_EarlyProccess(WorldPacket& recvPacket); // just mark packets processed in WorldSocket::OnRead @@ -1070,6 +1091,8 @@ class WorldSession void SetKicked(bool val) { _kicked = val; } bool IsSocketClosed() const; + void SetAddress(std::string const& address) { m_Address = address; } + /* * CALLBACKS */ @@ -1081,6 +1104,13 @@ class WorldSession void InitializeSession(); void InitializeSessionCallback(CharacterDatabaseQueryHolder const& realmHolder, uint32 clientCacheVersion); + LockedQueue& GetPacketQueue(); + + [[nodiscard]] bool IsBot() const + { + return _isBot; + } + private: void ProcessQueryCallbacks(); @@ -1191,6 +1221,8 @@ class WorldSession uint32 _timeSyncNextCounter; uint32 _timeSyncTimer; + bool _isBot; + WorldSession(WorldSession const& right) = delete; WorldSession& operator=(WorldSession const& right) = delete; }; diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index e26a2a087ab4e2..56b58548749f7a 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -2931,8 +2931,7 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) { if (missInfo != SPELL_MISS_EVADE && !m_caster->IsFriendlyTo(effectUnit) && (!m_spellInfo->IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL))) { - if (!m_triggeredByAuraSpell.spellInfo || (!(m_triggeredByAuraSpell.spellInfo->Effects[m_triggeredByAuraSpell.effectIndex].TriggerSpell == m_spellInfo->Id) && !(m_triggeredByAuraSpell.spellInfo->IsAuraEffectEqual(m_spellInfo)))) - m_caster->CombatStart(effectUnit, !(m_spellInfo->AttributesEx3 & SPELL_ATTR3_SUPRESS_TARGET_PROCS)); + m_caster->CombatStart(effectUnit, !(m_spellInfo->AttributesEx3 & SPELL_ATTR3_SUPRESS_TARGET_PROCS)); // Patch 3.0.8: All player spells which cause a creature to become aggressive to you will now also immediately cause the creature to be tapped. if (effectUnit->IsInCombatWith(m_caster)) @@ -7519,9 +7518,9 @@ SpellCastResult Spell::CheckItems() // Xinef: Apply item level restriction if the enchanting spell has max level restrition set if (m_CastItem && m_spellInfo->MaxLevel > 0) { - if (item->GetTemplate()->ItemLevel < m_CastItem->GetTemplate()->RequiredLevel) + if (item->GetTemplate()->RequiredLevel < m_CastItem->GetTemplate()->RequiredLevel) return SPELL_FAILED_LOWLEVEL; - if (item->GetTemplate()->ItemLevel > m_spellInfo->MaxLevel) + if (item->GetTemplate()->RequiredLevel > m_spellInfo->MaxLevel) return SPELL_FAILED_HIGHLEVEL; } diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 5c16b3090d1e3b..aab419f79df4f9 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -6064,6 +6064,23 @@ void Spell::SummonGuardian(uint32 i, uint32 entry, SummonPropertiesEntry const* ExecuteLogEffectSummonObject(i, summon); } + + // Summon infernal, cast enslave demon + // xinef: have to do it here because in Pet init stats infernal is not in world, imo this should be changed... + if (summon) + { + switch (m_spellInfo->Id) + { + // Inferno, Warlock summon spell + case 1122: + caster->AddAura(61191, summon); + break; + // Ritual of Doom, Warlock summon spell + case 60478: + caster->AddAura(SPELL_RITUAL_ENSLAVEMENT, summon); + break; + } + } } void Spell::EffectRenamePet(SpellEffIndex /*effIndex*/) diff --git a/src/server/game/Spells/SpellInfoCorrections.cpp b/src/server/game/Spells/SpellInfoCorrections.cpp index f88dfbdc93c989..0e3c9833a7e18a 100644 --- a/src/server/game/Spells/SpellInfoCorrections.cpp +++ b/src/server/game/Spells/SpellInfoCorrections.cpp @@ -3954,6 +3954,12 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->AttributesEx7 |= SPELL_ATTR7_CAN_CAUSE_INTERRUPT; }); + // Ritual of Summoning + ApplySpellFix({ 61994 }, [](SpellInfo* spellInfo) + { + spellInfo->ManaCostPercentage = 0; // Clicking on Warlock Summoning portal should not require mana + }); + // Shadowmeld ApplySpellFix({ 58984 }, [](SpellInfo* spellInfo) { @@ -4816,21 +4822,6 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->AttributesEx3 |= SPELL_ATTR3_SUPRESS_CASTER_PROCS; }); - // Conjure Refreshment Table (Rank 1, Rank 2) - ApplySpellFix({ 43985, 58661 }, [](SpellInfo* spellInfo) - { - spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_CASTER_FRONT); - spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_5_YARDS); - }); - - ApplySpellFix({ - 698, // Ritual of Summoning (portal for clicking) - 61993 // Ritual of Summoning (summons the closet) - }, [](SpellInfo* spellInfo) - { - spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_3_YARDS); - }); - for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i) { SpellInfo* spellInfo = mSpellInfoMap[i]; diff --git a/src/server/game/World/IWorld.h b/src/server/game/World/IWorld.h index 78eab7d7bef475..cc2f04c5a93cd6 100644 --- a/src/server/game/World/IWorld.h +++ b/src/server/game/World/IWorld.h @@ -602,6 +602,9 @@ class IWorld [[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; @@ -609,6 +612,7 @@ class IWorld [[nodiscard]] virtual std::string const& GetRealmName() const = 0; virtual void SetRealmName(std::string name) = 0; virtual void RemoveOldCorpses() = 0; + virtual SQLQueryHolderCallback& AddQueryHolderCallback(SQLQueryHolderCallback&& callback) = 0; virtual void DoForAllOnlinePlayers(std::function exec) = 0; }; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 8350e4e726c593..82614f911154b0 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" @@ -2328,6 +2329,8 @@ void World::Update(uint32 diff) ResetGuildCap(); } + sScriptMgr->OnPlayerbotUpdate(diff); + // pussywizard: handle auctions when the timer has passed if (_timers[WUPDATE_AUCTIONS].Passed()) { @@ -2466,6 +2469,7 @@ void World::Update(uint32 diff) CharacterDatabase.KeepAlive(); LoginDatabase.KeepAlive(); WorldDatabase.KeepAlive(); + sScriptMgr->OnDatabasesKeepAlive(); } { @@ -2628,6 +2632,9 @@ void World::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` @@ -2673,6 +2680,7 @@ void World::_UpdateGameTime() void World::ShutdownServ(uint32 time, uint32 options, uint8 exitcode, const std::string& reason) { // ignore if server shutdown at next tick + if (IsStopped()) return; @@ -2693,6 +2701,9 @@ void World::ShutdownServ(uint32 time, uint32 options, uint8 exitcode, const std: { playersSaveScheduler.Schedule(Seconds(time - 5), [this](TaskContext /*context*/) { +#ifdef MOD_PLAYERBOTS + sScriptMgr->OnPlayerbotLogoutBots(); +#endif if (!GetActiveSessionCount()) { LOG_INFO("server", "> No players online. Skip save before shutdown"); @@ -3170,6 +3181,12 @@ uint64 World::getWorldState(uint32 index) const void World::ProcessQueryCallbacks() { _queryProcessor.ProcessReadyCallbacks(); + _queryHolderProcessor.ProcessReadyCallbacks(); +} + +SQLQueryHolderCallback& World::AddQueryHolderCallback(SQLQueryHolderCallback&& callback) +{ + return _queryHolderProcessor.AddCallback(std::move(callback)); } void World::RemoveOldCorpses() diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index daaa53e15942f0..70b1db4f8b2778 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -332,6 +332,9 @@ class World: public IWorld // 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; @@ -363,6 +366,9 @@ class World: public IWorld void ResetRandomBG(); void CalendarDeleteOldEvents(); void ResetGuildCap(); + + SQLQueryHolderCallback& AddQueryHolderCallback(SQLQueryHolderCallback&& callback) override; + private: static std::atomic_long _stopEvent; static uint8 _exitCode; @@ -428,9 +434,13 @@ class World: public IWorld // used versions std::string _dbVersion; +#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_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp index db29dcb36eeddf..e9f71773159977 100644 --- a/src/server/scripts/Commands/cs_misc.cpp +++ b/src/server/scripts/Commands/cs_misc.cpp @@ -1925,7 +1925,7 @@ class misc_commandscript : public CommandScript // the max level of the new profession. uint16 max = maxPureSkill ? *maxPureSkill : targetHasSkill ? target->GetPureMaxSkillValue(skillID) : uint16(level); - if (level <= 0 || level > max || max <= 0) + if (level < 0 || level > max || max < 0) { return false; } diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index a7daa30246214d..445281218733d2 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -218,6 +218,7 @@ class npc_commandscript : public CommandScript { ObjectGuid::LowType guid = sObjectMgr->GenerateCreatureSpawnId(); CreatureData& data = sObjectMgr->NewOrExistCreatureData(guid); + data.spawnId = guid; data.id1 = id; data.phaseMask = chr->GetPhaseMaskForSpawn(); data.posX = chr->GetTransOffsetX(); diff --git a/src/server/scripts/Commands/cs_server.cpp b/src/server/scripts/Commands/cs_server.cpp index 39d2fd8a90b119..da1daf0d2f1f8c 100644 --- a/src/server/scripts/Commands/cs_server.cpp +++ b/src/server/scripts/Commands/cs_server.cpp @@ -214,6 +214,10 @@ class server_commandscript : public CommandScript 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")) @@ -241,6 +245,10 @@ class server_commandscript : public CommandScript 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/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_drakkisath.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_drakkisath.cpp index 3935c78a129b0a..02e6196276aae8 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_drakkisath.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockSpire/boss_drakkisath.cpp @@ -94,7 +94,7 @@ class boss_drakkisath : public CreatureScript _conflagrateThreat = me->GetThreatMgr().GetThreat(me->GetVictim()); me->GetThreatMgr().ModifyThreatByPercent(target, -100); } - events.ScheduleEvent(EVENT_CONFLAGRATION, 18s, 25s); + events.ScheduleEvent(EVENT_CONFLAGRATION, 10s, 13s); events.ScheduleEvent(EVENT_CHECK_CONFLAGRATION_TARGET, 10s); break; case EVENT_THUNDERCLAP: diff --git a/src/server/scripts/EasternKingdoms/ZulAman/boss_nalorakk.cpp b/src/server/scripts/EasternKingdoms/ZulAman/boss_nalorakk.cpp index 4a290c25311446..af0e1e19d30fe3 100644 --- a/src/server/scripts/EasternKingdoms/ZulAman/boss_nalorakk.cpp +++ b/src/server/scripts/EasternKingdoms/ZulAman/boss_nalorakk.cpp @@ -89,7 +89,6 @@ struct boss_nalorakk : public BossAI { boss_nalorakk(Creature* creature) : BossAI(creature, DATA_NALORAKKEVENT) { - _phase = PHASE_SEND_GUARDS_1; _ranIntro = false; _active = true; creature->SetReactState(REACT_PASSIVE); diff --git a/src/server/scripts/Northrend/Naxxramas/boss_anubrekhan.cpp b/src/server/scripts/Northrend/Naxxramas/boss_anubrekhan.cpp index 57edeb32ff9f78..d02902ae85c673 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_anubrekhan.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_anubrekhan.cpp @@ -15,233 +15,12 @@ * with this program. If not, see . */ +#include "boss_anubrekhan.h" #include "CreatureScript.h" #include "ScriptedCreature.h" #include "naxxramas.h" -enum Says -{ - SAY_AGGRO = 0, - SAY_GREET = 1, - SAY_SLAY = 2, - EMOTE_LOCUST = 3 -}; - -enum GuardSays -{ - EMOTE_SPAWN = 1, - EMOTE_SCARAB = 2 -}; - -enum Spells -{ - SPELL_IMPALE_10 = 28783, - SPELL_IMPALE_25 = 56090, - SPELL_LOCUST_SWARM_10 = 28785, - SPELL_LOCUST_SWARM_25 = 54021, - SPELL_SUMMON_CORPSE_SCRABS_5 = 29105, - SPELL_SUMMON_CORPSE_SCRABS_10 = 28864, - SPELL_BERSERK = 26662 -}; - -enum Events -{ - EVENT_IMPALE = 1, - EVENT_LOCUST_SWARM = 2, - EVENT_BERSERK = 3, - EVENT_SPAWN_GUARD = 4 -}; - -enum Misc -{ - NPC_CORPSE_SCARAB = 16698, - NPC_CRYPT_GUARD = 16573, - - ACHIEV_TIMED_START_EVENT = 9891 -}; - -class boss_anubrekhan : public CreatureScript -{ -public: - boss_anubrekhan() : CreatureScript("boss_anubrekhan") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_anubrekhanAI : public BossAI - { - explicit boss_anubrekhanAI(Creature* c) : BossAI(c, BOSS_ANUB), summons(me) - { - pInstance = c->GetInstanceScript(); - sayGreet = false; - } - - InstanceScript* pInstance; - EventMap events; - SummonList summons; - bool sayGreet; - - void SummonCryptGuards() - { - if (Is25ManRaid()) - { - me->SummonCreature(NPC_CRYPT_GUARD, 3299.732f, -3502.489f, 287.077f, 2.378f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 60000); - me->SummonCreature(NPC_CRYPT_GUARD, 3299.086f, -3450.929f, 287.077f, 3.999f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 60000); - } - } - - void Reset() override - { - BossAI::Reset(); - events.Reset(); - summons.DespawnAll(); - SummonCryptGuards(); - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_ANUB_GATE))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - } - } - - void JustSummoned(Creature* cr) override - { - if (me->IsInCombat()) - { - cr->SetInCombatWithZone(); - if (cr->GetEntry() == NPC_CRYPT_GUARD) - { - cr->AI()->Talk(EMOTE_SPAWN, me); - } - } - summons.Summon(cr); - } - - void SummonedCreatureDies(Creature* cr, Unit*) override - { - if (cr->GetEntry() == NPC_CRYPT_GUARD) - { - cr->CastSpell(cr, SPELL_SUMMON_CORPSE_SCRABS_10, true, nullptr, nullptr, me->GetGUID()); - cr->AI()->Talk(EMOTE_SCARAB); - } - } - - void SummonedCreatureDespawn(Creature* cr) override - { - summons.Despawn(cr); - } - - void JustDied(Unit* killer) override - { - BossAI::JustDied(killer); - summons.DespawnAll(); - if (pInstance) - { - pInstance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_TIMED_START_EVENT); - } - } - - void KilledUnit(Unit* victim) override - { - if (!victim->IsPlayer()) - return; - - Talk(SAY_SLAY); - victim->CastSpell(victim, SPELL_SUMMON_CORPSE_SCRABS_5, true, nullptr, nullptr, me->GetGUID()); - if (pInstance) - { - pInstance->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - me->CallForHelp(30.0f); - Talk(SAY_AGGRO); - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_ANUB_GATE))) - { - go->SetGoState(GO_STATE_READY); - } - } - events.ScheduleEvent(EVENT_IMPALE, 15s); - events.ScheduleEvent(EVENT_LOCUST_SWARM, 70s, 120s); - events.ScheduleEvent(EVENT_BERSERK, 10min); - if (!summons.HasEntry(NPC_CRYPT_GUARD)) - { - SummonCryptGuards(); - } - if (!Is25ManRaid()) - { - events.ScheduleEvent(EVENT_SPAWN_GUARD, 15s, 20s); - } - } - - void MoveInLineOfSight(Unit* who) override - { - if (!sayGreet && who->IsPlayer()) - { - Talk(SAY_GREET); - sayGreet = true; - } - ScriptedAI::MoveInLineOfSight(who); - } - - void UpdateAI(uint32 diff) override - { - if (!me->IsInCombat() && sayGreet) - { - for (SummonList::iterator itr = summons.begin(); itr != summons.end(); ++itr) - { - if (pInstance) - { - if (Creature* cr = pInstance->instance->GetCreature(*itr)) - { - if (cr->IsInCombat()) - DoZoneInCombat(); - } - } - } - } - - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_IMPALE: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0)) - { - me->CastSpell(target, RAID_MODE(SPELL_IMPALE_10, SPELL_IMPALE_25), false); - } - events.Repeat(20s); - break; - case EVENT_LOCUST_SWARM: - Talk(EMOTE_LOCUST); - me->CastSpell(me, RAID_MODE(SPELL_LOCUST_SWARM_10, SPELL_LOCUST_SWARM_25), false); - events.ScheduleEvent(EVENT_SPAWN_GUARD, 3s); - events.Repeat(90s); - break; - case EVENT_SPAWN_GUARD: - me->SummonCreature(NPC_CRYPT_GUARD, 3331.217f, -3476.607f, 287.074f, 3.269f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 60000); - break; - case EVENT_BERSERK: - me->CastSpell(me, SPELL_BERSERK, true); - break; - } - DoMeleeAttackIfReady(); - } - }; -}; +using namespace Anubrekhan; void AddSC_boss_anubrekhan() { diff --git a/src/server/scripts/Northrend/Naxxramas/boss_anubrekhan.h b/src/server/scripts/Northrend/Naxxramas/boss_anubrekhan.h new file mode 100644 index 00000000000000..d5d62ab7df22fe --- /dev/null +++ b/src/server/scripts/Northrend/Naxxramas/boss_anubrekhan.h @@ -0,0 +1,237 @@ +#ifndef BOSS_ANUBREKHAN_H_ +#define BOSS_ANUBREKHAN_H_ + +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "SpellInfo.h" +#include "naxxramas.h" + +namespace Anubrekhan { + +enum Says +{ + SAY_AGGRO = 0, + SAY_GREET = 1, + SAY_SLAY = 2, + EMOTE_LOCUST = 3 +}; + +enum GuardSays +{ + EMOTE_SPAWN = 1, + EMOTE_SCARAB = 2 +}; + +enum Spells +{ + SPELL_IMPALE_10 = 28783, + SPELL_IMPALE_25 = 56090, + SPELL_LOCUST_SWARM_10 = 28785, + SPELL_LOCUST_SWARM_25 = 54021, + SPELL_SUMMON_CORPSE_SCRABS_5 = 29105, + SPELL_SUMMON_CORPSE_SCRABS_10 = 28864, + SPELL_BERSERK = 26662 +}; + +enum Events +{ + EVENT_IMPALE = 1, + EVENT_LOCUST_SWARM = 2, + EVENT_BERSERK = 3, + EVENT_SPAWN_GUARD = 4 +}; + +enum Misc +{ + NPC_CORPSE_SCARAB = 16698, + NPC_CRYPT_GUARD = 16573, + + ACHIEV_TIMED_START_EVENT = 9891 +}; + +class boss_anubrekhan : public CreatureScript +{ +public: + boss_anubrekhan() : CreatureScript("boss_anubrekhan") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_anubrekhanAI : public BossAI + { + explicit boss_anubrekhanAI(Creature* c) : BossAI(c, BOSS_ANUB), summons(me) + { + pInstance = c->GetInstanceScript(); + sayGreet = false; + } + + InstanceScript* pInstance; + EventMap events; + SummonList summons; + bool sayGreet; + + void SummonCryptGuards() + { + if (Is25ManRaid()) + { + me->SummonCreature(NPC_CRYPT_GUARD, 3299.732f, -3502.489f, 287.077f, 2.378f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 60000); + me->SummonCreature(NPC_CRYPT_GUARD, 3299.086f, -3450.929f, 287.077f, 3.999f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 60000); + } + } + + void Reset() override + { + BossAI::Reset(); + events.Reset(); + summons.DespawnAll(); + SummonCryptGuards(); + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_ANUB_GATE))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + } + } + + void JustSummoned(Creature* cr) override + { + if (me->IsInCombat()) + { + cr->SetInCombatWithZone(); + if (cr->GetEntry() == NPC_CRYPT_GUARD) + { + cr->AI()->Talk(EMOTE_SPAWN, me); + } + } + summons.Summon(cr); + } + + void SummonedCreatureDies(Creature* cr, Unit*) override + { + if (cr->GetEntry() == NPC_CRYPT_GUARD) + { + cr->CastSpell(cr, SPELL_SUMMON_CORPSE_SCRABS_10, true, nullptr, nullptr, me->GetGUID()); + cr->AI()->Talk(EMOTE_SCARAB); + } + } + + void SummonedCreatureDespawn(Creature* cr) override + { + summons.Despawn(cr); + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + summons.DespawnAll(); + if (pInstance) + { + pInstance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_TIMED_START_EVENT); + } + } + + void KilledUnit(Unit* victim) override + { + if (!victim->IsPlayer()) + return; + + Talk(SAY_SLAY); + victim->CastSpell(victim, SPELL_SUMMON_CORPSE_SCRABS_5, true, nullptr, nullptr, me->GetGUID()); + if (pInstance) + { + pInstance->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + me->CallForHelp(30.0f); + Talk(SAY_AGGRO); + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_ANUB_GATE))) + { + go->SetGoState(GO_STATE_READY); + } + } + events.ScheduleEvent(EVENT_IMPALE, 15s); + events.ScheduleEvent(EVENT_LOCUST_SWARM, 70s, 120s); + events.ScheduleEvent(EVENT_BERSERK, 10min); + if (!summons.HasEntry(NPC_CRYPT_GUARD)) + { + SummonCryptGuards(); + } + if (!Is25ManRaid()) + { + events.ScheduleEvent(EVENT_SPAWN_GUARD, 15s, 20s); + } + } + + void MoveInLineOfSight(Unit* who) override + { + if (!sayGreet && who->IsPlayer()) + { + Talk(SAY_GREET); + sayGreet = true; + } + ScriptedAI::MoveInLineOfSight(who); + } + + void UpdateAI(uint32 diff) override + { + if (!me->IsInCombat() && sayGreet) + { + for (SummonList::iterator itr = summons.begin(); itr != summons.end(); ++itr) + { + if (pInstance) + { + if (Creature* cr = pInstance->instance->GetCreature(*itr)) + { + if (cr->IsInCombat()) + DoZoneInCombat(); + } + } + } + } + + if (!UpdateVictim()) + return; + + events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_IMPALE: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0)) + { + me->CastSpell(target, RAID_MODE(SPELL_IMPALE_10, SPELL_IMPALE_25), false); + } + events.Repeat(20s); + break; + case EVENT_LOCUST_SWARM: + Talk(EMOTE_LOCUST); + me->CastSpell(me, RAID_MODE(SPELL_LOCUST_SWARM_10, SPELL_LOCUST_SWARM_25), false); + events.ScheduleEvent(EVENT_SPAWN_GUARD, 3s); + events.Repeat(90s); + break; + case EVENT_SPAWN_GUARD: + me->SummonCreature(NPC_CRYPT_GUARD, 3331.217f, -3476.607f, 287.074f, 3.269f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 60000); + break; + case EVENT_BERSERK: + me->CastSpell(me, SPELL_BERSERK, true); + break; + } + DoMeleeAttackIfReady(); + } + }; +}; + +} + +#endif \ No newline at end of file diff --git a/src/server/scripts/Northrend/Naxxramas/boss_faerlina.cpp b/src/server/scripts/Northrend/Naxxramas/boss_faerlina.cpp index ad9c534a6a3b7f..0b35acde0ed7ad 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_faerlina.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_faerlina.cpp @@ -15,236 +15,14 @@ * with this program. If not, see . */ +#include "boss_faerlina.h" #include "CreatureScript.h" #include "ScriptedCreature.h" #include "SpellInfo.h" #include "naxxramas.h" -enum Yells -{ - SAY_GREET = 0, - SAY_AGGRO = 1, - SAY_SLAY = 2, - SAY_DEATH = 3, - EMOTE_WIDOWS_EMBRACE = 4, - EMOTE_FRENZY = 5, - SAY_FRENZY = 6 -}; - -enum Spells -{ - SPELL_POISON_BOLT_VOLLEY_10 = 28796, - SPELL_POISON_BOLT_VOLLEY_25 = 54098, - SPELL_RAIN_OF_FIRE_10 = 28794, - SPELL_RAIN_OF_FIRE_25 = 54099, - SPELL_FRENZY_10 = 28798, - SPELL_FRENZY_25 = 54100, - SPELL_WIDOWS_EMBRACE = 28732, - SPELL_MINION_WIDOWS_EMBRACE = 54097 -}; - -enum Events -{ - EVENT_POISON_BOLT = 1, - EVENT_RAIN_OF_FIRE = 2, - EVENT_FRENZY = 3 -}; - -enum Misc -{ - NPC_NAXXRAMAS_WORSHIPPER = 16506, - NPC_NAXXRAMAS_FOLLOWER = 16505 -}; - -class boss_faerlina : public CreatureScript -{ -public: - boss_faerlina() : CreatureScript("boss_faerlina") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_faerlinaAI : public BossAI - { - boss_faerlinaAI(Creature* c) : BossAI(c, BOSS_FAERLINA), summons(me) - { - pInstance = me->GetInstanceScript(); - sayGreet = false; - } - - InstanceScript* pInstance; - EventMap events; - SummonList summons; - bool sayGreet; - - void SummonHelpers() - { - me->SummonCreature(NPC_NAXXRAMAS_WORSHIPPER, 3362.66f, -3620.97f, 261.08f, 4.57276f); - me->SummonCreature(NPC_NAXXRAMAS_WORSHIPPER, 3344.3f, -3618.31f, 261.08f, 4.69494f); - me->SummonCreature(NPC_NAXXRAMAS_WORSHIPPER, 3356.71f, -3620.05f, 261.08f, 4.57276f); - me->SummonCreature(NPC_NAXXRAMAS_WORSHIPPER, 3350.26f, -3619.11f, 261.08f, 4.67748f); - if (Is25ManRaid()) - { - me->SummonCreature(NPC_NAXXRAMAS_FOLLOWER, 3347.49f, -3617.59f, 261.0f, 4.49f); - me->SummonCreature(NPC_NAXXRAMAS_FOLLOWER, 3359.64f, -3619.16f, 261.0f, 4.56f); - } - } - - void JustSummoned(Creature* cr) override - { - summons.Summon(cr); - } - - void Reset() override - { - BossAI::Reset(); - events.Reset(); - summons.DespawnAll(); - SummonHelpers(); - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_FAERLINA_WEB))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - } - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - me->CallForHelp(VISIBLE_RANGE); - summons.DoZoneInCombat(); - Talk(SAY_AGGRO); - events.ScheduleEvent(EVENT_POISON_BOLT, 7s, 15s); - events.ScheduleEvent(EVENT_RAIN_OF_FIRE, 8s, 18s); - events.ScheduleEvent(EVENT_FRENZY, 60s, 80s, 1); - events.SetPhase(1); - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_FAERLINA_WEB))) - { - go->SetGoState(GO_STATE_READY); - } - } - } - - void MoveInLineOfSight(Unit* who) override - { - if (!sayGreet && who->IsPlayer()) - { - Talk(SAY_GREET); - sayGreet = true; - } - ScriptedAI::MoveInLineOfSight(who); - } - - void KilledUnit(Unit* who) override - { - if (!who->IsPlayer()) - return; - - if (!urand(0, 3)) - { - Talk(SAY_SLAY); - } - if (pInstance) - { - pInstance->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void JustDied(Unit* killer) override - { - BossAI::JustDied(killer); - Talk(SAY_DEATH); - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_FAERLINA_WEB))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - } - } - - void UpdateAI(uint32 diff) override - { - if (!me->IsInCombat() && sayGreet) - { - for (SummonList::iterator itr = summons.begin(); itr != summons.end(); ++itr) - { - if (pInstance) - { - if (Creature* cr = pInstance->instance->GetCreature(*itr)) - { - if (cr->IsInCombat()) - DoZoneInCombat(); - } - } - } - } - - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_POISON_BOLT: - if (!me->HasAura(RAID_MODE(SPELL_WIDOWS_EMBRACE, SPELL_MINION_WIDOWS_EMBRACE))) - { - me->CastCustomSpell(RAID_MODE(SPELL_POISON_BOLT_VOLLEY_10, SPELL_POISON_BOLT_VOLLEY_25), SPELLVALUE_MAX_TARGETS, RAID_MODE(3, 10), me, false); - } - events.Repeat(7s, 15s); - break; - case EVENT_RAIN_OF_FIRE: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0)) - { - me->CastSpell(target, RAID_MODE(SPELL_RAIN_OF_FIRE_10, SPELL_RAIN_OF_FIRE_25), false); - } - events.Repeat(8s, 18s); - break; - case EVENT_FRENZY: - if (!me->HasAura(RAID_MODE(SPELL_FRENZY_10, SPELL_FRENZY_25))) - { - Talk(SAY_FRENZY); - Talk(EMOTE_FRENZY); - me->CastSpell(me, RAID_MODE(SPELL_FRENZY_10, SPELL_FRENZY_25), true); - events.Repeat(1min); - } - else - { - events.Repeat(30s); - } - break; - } - DoMeleeAttackIfReady(); - } - void SpellHit(Unit* caster, SpellInfo const* spell) override - { - if (spell->Id == RAID_MODE(SPELL_WIDOWS_EMBRACE, SPELL_MINION_WIDOWS_EMBRACE)) - { - Talk(EMOTE_WIDOWS_EMBRACE); - if (me->HasAura(RAID_MODE(SPELL_FRENZY_10, SPELL_FRENZY_25))) - { - me->RemoveAurasDueToSpell(RAID_MODE(SPELL_FRENZY_10, SPELL_FRENZY_25)); - events.RescheduleEvent(EVENT_FRENZY, 1min); - } - pInstance->SetData(DATA_FRENZY_REMOVED, 0); - if (Is25ManRaid()) - { - Unit::Kill(caster, caster); - } - } - } - }; -}; +using namespace Faerlina; void AddSC_boss_faerlina() { diff --git a/src/server/scripts/Northrend/Naxxramas/boss_faerlina.h b/src/server/scripts/Northrend/Naxxramas/boss_faerlina.h new file mode 100644 index 00000000000000..bcd9e2b365726b --- /dev/null +++ b/src/server/scripts/Northrend/Naxxramas/boss_faerlina.h @@ -0,0 +1,239 @@ +#ifndef BOSS_FAERLINA_H_ +#define BOSS_FAERLINA_H_ + +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "SpellInfo.h" +#include "naxxramas.h" + +namespace Faerlina { + +enum Yells +{ + SAY_GREET = 0, + SAY_AGGRO = 1, + SAY_SLAY = 2, + SAY_DEATH = 3, + EMOTE_WIDOWS_EMBRACE = 4, + EMOTE_FRENZY = 5, + SAY_FRENZY = 6 +}; + +enum Spells +{ + SPELL_POISON_BOLT_VOLLEY_10 = 28796, + SPELL_POISON_BOLT_VOLLEY_25 = 54098, + SPELL_RAIN_OF_FIRE_10 = 28794, + SPELL_RAIN_OF_FIRE_25 = 54099, + SPELL_FRENZY_10 = 28798, + SPELL_FRENZY_25 = 54100, + SPELL_WIDOWS_EMBRACE = 28732, + SPELL_MINION_WIDOWS_EMBRACE = 54097 +}; + +enum Events +{ + EVENT_POISON_BOLT = 1, + EVENT_RAIN_OF_FIRE = 2, + EVENT_FRENZY = 3 +}; + +enum Misc +{ + NPC_NAXXRAMAS_WORSHIPPER = 16506, + NPC_NAXXRAMAS_FOLLOWER = 16505 +}; + +class boss_faerlina : public CreatureScript +{ +public: + boss_faerlina() : CreatureScript("boss_faerlina") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_faerlinaAI : public BossAI + { + boss_faerlinaAI(Creature* c) : BossAI(c, BOSS_FAERLINA), summons(me) + { + pInstance = me->GetInstanceScript(); + sayGreet = false; + } + + InstanceScript* pInstance; + EventMap events; + SummonList summons; + bool sayGreet; + + void SummonHelpers() + { + me->SummonCreature(NPC_NAXXRAMAS_WORSHIPPER, 3362.66f, -3620.97f, 261.08f, 4.57276f); + me->SummonCreature(NPC_NAXXRAMAS_WORSHIPPER, 3344.3f, -3618.31f, 261.08f, 4.69494f); + me->SummonCreature(NPC_NAXXRAMAS_WORSHIPPER, 3356.71f, -3620.05f, 261.08f, 4.57276f); + me->SummonCreature(NPC_NAXXRAMAS_WORSHIPPER, 3350.26f, -3619.11f, 261.08f, 4.67748f); + if (Is25ManRaid()) + { + me->SummonCreature(NPC_NAXXRAMAS_FOLLOWER, 3347.49f, -3617.59f, 261.0f, 4.49f); + me->SummonCreature(NPC_NAXXRAMAS_FOLLOWER, 3359.64f, -3619.16f, 261.0f, 4.56f); + } + } + + void JustSummoned(Creature* cr) override + { + summons.Summon(cr); + } + + void Reset() override + { + BossAI::Reset(); + events.Reset(); + summons.DespawnAll(); + SummonHelpers(); + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_FAERLINA_WEB))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + } + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + me->CallForHelp(VISIBLE_RANGE); + summons.DoZoneInCombat(); + Talk(SAY_AGGRO); + events.ScheduleEvent(EVENT_POISON_BOLT, 7s, 15s); + events.ScheduleEvent(EVENT_RAIN_OF_FIRE, 8s, 18s); + events.ScheduleEvent(EVENT_FRENZY, 60s, 80s, 1); + events.SetPhase(1); + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_FAERLINA_WEB))) + { + go->SetGoState(GO_STATE_READY); + } + } + } + + void MoveInLineOfSight(Unit* who) override + { + if (!sayGreet && who->IsPlayer()) + { + Talk(SAY_GREET); + sayGreet = true; + } + ScriptedAI::MoveInLineOfSight(who); + } + + void KilledUnit(Unit* who) override + { + if (!who->IsPlayer()) + return; + + if (!urand(0, 3)) + { + Talk(SAY_SLAY); + } + if (pInstance) + { + pInstance->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + Talk(SAY_DEATH); + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_FAERLINA_WEB))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + } + } + + void UpdateAI(uint32 diff) override + { + if (!me->IsInCombat() && sayGreet) + { + for (SummonList::iterator itr = summons.begin(); itr != summons.end(); ++itr) + { + if (pInstance) + { + if (Creature* cr = pInstance->instance->GetCreature(*itr)) + { + if (cr->IsInCombat()) + DoZoneInCombat(); + } + } + } + } + + if (!UpdateVictim()) + return; + + events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_POISON_BOLT: + if (!me->HasAura(RAID_MODE(SPELL_WIDOWS_EMBRACE, SPELL_MINION_WIDOWS_EMBRACE))) + { + me->CastCustomSpell(RAID_MODE(SPELL_POISON_BOLT_VOLLEY_10, SPELL_POISON_BOLT_VOLLEY_25), SPELLVALUE_MAX_TARGETS, RAID_MODE(3, 10), me, false); + } + events.Repeat(7s, 15s); + break; + case EVENT_RAIN_OF_FIRE: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0)) + { + me->CastSpell(target, RAID_MODE(SPELL_RAIN_OF_FIRE_10, SPELL_RAIN_OF_FIRE_25), false); + } + events.Repeat(8s, 18s); + break; + case EVENT_FRENZY: + if (!me->HasAura(RAID_MODE(SPELL_FRENZY_10, SPELL_FRENZY_25))) + { + Talk(SAY_FRENZY); + Talk(EMOTE_FRENZY); + me->CastSpell(me, RAID_MODE(SPELL_FRENZY_10, SPELL_FRENZY_25), true); + events.Repeat(1min); + } + else + { + events.Repeat(30s); + } + break; + } + DoMeleeAttackIfReady(); + } + + void SpellHit(Unit* caster, SpellInfo const* spell) override + { + if (spell->Id == RAID_MODE(SPELL_WIDOWS_EMBRACE, SPELL_MINION_WIDOWS_EMBRACE)) + { + Talk(EMOTE_WIDOWS_EMBRACE); + if (me->HasAura(RAID_MODE(SPELL_FRENZY_10, SPELL_FRENZY_25))) + { + me->RemoveAurasDueToSpell(RAID_MODE(SPELL_FRENZY_10, SPELL_FRENZY_25)); + events.RescheduleEvent(EVENT_FRENZY, 1min); + } + pInstance->SetData(DATA_FRENZY_REMOVED, 0); + if (Is25ManRaid()) + { + Unit::Kill(caster, caster); + } + } + } + }; +}; + +} + +#endif \ No newline at end of file diff --git a/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp b/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp index 556371bb2db9f1..dcb1caf4084250 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp @@ -15,6 +15,7 @@ * with this program. If not, see . */ +#include "boss_four_horsemen.h" #include "CreatureScript.h" #include "Player.h" #include "ScriptedCreature.h" @@ -23,413 +24,8 @@ #include "SpellScriptLoader.h" #include "naxxramas.h" -enum Spells -{ - SPELL_BERSERK = 26662, - // Marks - SPELL_MARK_OF_KORTHAZZ = 28832, - SPELL_MARK_OF_BLAUMEUX = 28833, - SPELL_MARK_OF_RIVENDARE = 28834, - SPELL_MARK_OF_ZELIEK = 28835, - SPELL_MARK_DAMAGE = 28836, - // Korth'azz - SPELL_KORTHAZZ_METEOR_10 = 28884, - SPELL_KORTHAZZ_METEOR_25 = 57467, - // Blaumeux - SPELL_BLAUMEUX_SHADOW_BOLT_10 = 57374, - SPELL_BLAUMEUX_SHADOW_BOLT_25 = 57464, - SPELL_BLAUMEUX_VOID_ZONE_10 = 28863, - SPELL_BLAUMEUX_VOID_ZONE_25 = 57463, - SPELL_BLAUMEUX_UNYIELDING_PAIN = 57381, - // Zeliek - SPELL_ZELIEK_HOLY_WRATH_10 = 28883, - SPELL_ZELIEK_HOLY_WRATH_25 = 57466, - SPELL_ZELIEK_HOLY_BOLT_10 = 57376, - SPELL_ZELIEK_HOLY_BOLT_25 = 57465, - SPELL_ZELIEK_CONDEMNATION = 57377, - // Rivendare - SPELL_RIVENDARE_UNHOLY_SHADOW_10 = 28882, - SPELL_RIVENDARE_UNHOLY_SHADOW_25 = 57369 -}; - -enum Events -{ - EVENT_MARK_CAST = 1, - EVENT_PRIMARY_SPELL = 2, - EVENT_SECONDARY_SPELL = 3, - EVENT_BERSERK = 4 -}; - -enum Misc -{ - // Movement - MOVE_PHASE_NONE = 0, - MOVE_PHASE_STARTED = 1, - MOVE_PHASE_FINISHED = 2, - // Horseman - HORSEMAN_ZELIEK = 0, - HORSEMAN_BLAUMEUX = 1, - HORSEMAN_RIVENDARE = 2, - HORSEMAN_KORTHAZZ = 3 -}; - -enum FourHorsemen -{ - SAY_AGGRO = 0, - SAY_TAUNT = 1, - SAY_SPECIAL = 2, - SAY_SLAY = 3, - SAY_DEATH = 4, - EMOTE_RAGECAST = 7 -}; - -// MARKS -const uint32 TABLE_SPELL_MARK[4] = {SPELL_MARK_OF_ZELIEK, SPELL_MARK_OF_BLAUMEUX, SPELL_MARK_OF_RIVENDARE, SPELL_MARK_OF_KORTHAZZ}; - -// PRIMARY SPELL -const uint32 TABLE_SPELL_PRIMARY_10[4] = {SPELL_ZELIEK_HOLY_BOLT_10, SPELL_BLAUMEUX_SHADOW_BOLT_10, SPELL_RIVENDARE_UNHOLY_SHADOW_10, SPELL_KORTHAZZ_METEOR_10}; -const uint32 TABLE_SPELL_PRIMARY_25[4] = {SPELL_ZELIEK_HOLY_BOLT_25, SPELL_BLAUMEUX_SHADOW_BOLT_25, SPELL_RIVENDARE_UNHOLY_SHADOW_25, SPELL_KORTHAZZ_METEOR_25}; - -// PUNISH -const uint32 TABLE_SPELL_PUNISH[4] = {SPELL_ZELIEK_CONDEMNATION, SPELL_BLAUMEUX_UNYIELDING_PAIN, 0, 0}; - -// SECONDARY SPELL -const uint32 TABLE_SPELL_SECONDARY_10[4] = {SPELL_ZELIEK_HOLY_WRATH_10, SPELL_BLAUMEUX_VOID_ZONE_10, 0, 0}; -const uint32 TABLE_SPELL_SECONDARY_25[4] = {SPELL_ZELIEK_HOLY_WRATH_25, SPELL_BLAUMEUX_VOID_ZONE_25, 0, 0}; - -const Position WaypointPositions[12] = -{ - // Thane waypoints - {2542.3f, -2984.1f, 241.49f, 5.362f}, - {2547.6f, -2999.4f, 241.34f, 5.049f}, - {2542.9f, -3015.0f, 241.35f, 4.654f}, - // Lady waypoints - {2498.3f, -2961.8f, 241.28f, 3.267f}, - {2487.7f, -2959.2f, 241.28f, 2.890f}, - {2469.4f, -2947.6f, 241.28f, 2.576f}, - // Baron waypoints - {2553.8f, -2968.4f, 241.33f, 5.757f}, - {2564.3f, -2972.5f, 241.33f, 5.890f}, - {2583.9f, -2971.6f, 241.35f, 0.008f}, - // Sir waypoints - {2534.5f, -2921.7f, 241.53f, 1.363f}, - {2523.5f, -2902.8f, 241.28f, 2.095f}, - {2517.8f, -2896.6f, 241.28f, 2.315f} -}; - -class boss_four_horsemen : public CreatureScript -{ -public: - boss_four_horsemen() : CreatureScript("boss_four_horsemen") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_four_horsemenAI : public BossAI - { - explicit boss_four_horsemenAI(Creature* c) : BossAI(c, BOSS_HORSEMAN) - { - pInstance = me->GetInstanceScript(); - switch (me->GetEntry()) - { - case NPC_SIR_ZELIEK: - horsemanId = HORSEMAN_ZELIEK; - break; - case NPC_LADY_BLAUMEUX: - horsemanId = HORSEMAN_BLAUMEUX; - break; - case NPC_BARON_RIVENDARE: - horsemanId = HORSEMAN_RIVENDARE; - break; - case NPC_THANE_KORTHAZZ: - horsemanId = HORSEMAN_KORTHAZZ; - break; - } - } - - EventMap events; - InstanceScript* pInstance; - uint8 currentWaypoint{}; - uint8 movementPhase{}; - uint8 horsemanId; - - void MoveToCorner() - { - switch(me->GetEntry()) - { - case NPC_THANE_KORTHAZZ: - currentWaypoint = 0; - break; - case NPC_LADY_BLAUMEUX: - currentWaypoint = 3; - break; - case NPC_BARON_RIVENDARE: - currentWaypoint = 6; - break; - case NPC_SIR_ZELIEK: - currentWaypoint = 9; - break; - } - me->GetMotionMaster()->MovePoint(currentWaypoint, WaypointPositions[currentWaypoint]); - } - - bool IsInRoom() - { - if (me->GetExactDist(2535.1f, -2968.7f, 241.3f) > 100.0f) - { - EnterEvadeMode(); - return false; - } - return true; - } - - void Reset() override - { - BossAI::Reset(); - me->SetPosition(me->GetHomePosition()); - movementPhase = MOVE_PHASE_NONE; - currentWaypoint = 0; - me->SetReactState(REACT_AGGRESSIVE); - events.Reset(); - events.RescheduleEvent(EVENT_MARK_CAST, 24s); - events.RescheduleEvent(EVENT_BERSERK, 10min); - if ((me->GetEntry() != NPC_LADY_BLAUMEUX && me->GetEntry() != NPC_SIR_ZELIEK)) - { - events.RescheduleEvent(EVENT_PRIMARY_SPELL, 10s, 15s); - } - else - { - events.RescheduleEvent(EVENT_SECONDARY_SPELL, 15s); - } - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_HORSEMEN_GATE))) - { - if (pInstance->GetBossState(BOSS_GOTHIK) == DONE) - { - go->SetGoState(GO_STATE_ACTIVE); - } - } - } - } - - void MovementInform(uint32 type, uint32 id) override - { - if (type != POINT_MOTION_TYPE) - return; - - // final waypoint - if (id % 3 == 2) - { - movementPhase = MOVE_PHASE_FINISHED; - me->SetReactState(REACT_AGGRESSIVE); - me->SetInCombatWithZone(); - if (!UpdateVictim()) - { - EnterEvadeMode(); - return; - } - if (me->GetEntry() == NPC_LADY_BLAUMEUX || me->GetEntry() == NPC_SIR_ZELIEK) - { - me->GetMotionMaster()->Clear(false); - me->GetMotionMaster()->MoveIdle(); - } - return; - } - currentWaypoint = id + 1; - } - - void AttackStart(Unit* who) override - { - if (movementPhase == MOVE_PHASE_FINISHED) - { - if (me->GetEntry() == NPC_LADY_BLAUMEUX || me->GetEntry() == NPC_SIR_ZELIEK) - { - me->Attack(who, false); - } - else - { - ScriptedAI::AttackStart(who); - } - } - } - - void KilledUnit(Unit* who) override - { - if (!who->IsPlayer()) - return; - - Talk(SAY_SLAY); - if (pInstance) - { - pInstance->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void JustDied(Unit* killer) override - { - BossAI::JustDied(killer); - if (pInstance) - { - if (pInstance->GetBossState(BOSS_HORSEMAN) == DONE) - { - if (!me->GetMap()->GetPlayers().IsEmpty()) - { - if (Player* player = me->GetMap()->GetPlayers().getFirst()->GetSource()) - { - if (GameObject* chest = player->SummonGameObject(RAID_MODE(GO_HORSEMEN_CHEST_10, GO_HORSEMEN_CHEST_25), 2514.8f, -2944.9f, 245.55f, 5.51f, 0, 0, 0, 0, 0)) - { - chest->SetLootRecipient(me); - } - } - } - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_HORSEMEN_GATE))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - } - } - Talk(SAY_DEATH); - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - if (movementPhase == MOVE_PHASE_NONE) - { - Talk(SAY_AGGRO); - me->SetReactState(REACT_PASSIVE); - movementPhase = MOVE_PHASE_STARTED; - me->SetSpeed(MOVE_RUN, me->GetSpeedRate(MOVE_RUN), true); - MoveToCorner(); - } - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_HORSEMEN_GATE))) - { - go->SetGoState(GO_STATE_READY); - } - } - } - - void UpdateAI(uint32 diff) override - { - if (movementPhase == MOVE_PHASE_STARTED && currentWaypoint) - { - me->GetMotionMaster()->MovePoint(currentWaypoint, WaypointPositions[currentWaypoint]); - currentWaypoint = 0; - } - - if (!IsInRoom()) - return; - - if (movementPhase < MOVE_PHASE_FINISHED || !UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_MARK_CAST: - me->CastSpell(me, TABLE_SPELL_MARK[horsemanId], false); - events.Repeat((me->GetEntry() == NPC_LADY_BLAUMEUX || me->GetEntry() == NPC_SIR_ZELIEK) ? 15s : 12s); - return; - case EVENT_BERSERK: - Talk(SAY_SPECIAL); - me->CastSpell(me, SPELL_BERSERK, true); - return; - case EVENT_PRIMARY_SPELL: - Talk(SAY_TAUNT); - me->CastSpell(me->GetVictim(), RAID_MODE(TABLE_SPELL_PRIMARY_10[horsemanId], TABLE_SPELL_PRIMARY_25[horsemanId]), false); - events.Repeat(15s); - return; - case EVENT_SECONDARY_SPELL: - me->CastSpell(me->GetVictim(), RAID_MODE(TABLE_SPELL_SECONDARY_10[horsemanId], TABLE_SPELL_SECONDARY_25[horsemanId]), false); - events.Repeat(15s); - return; - } - - if (me->GetEntry() == NPC_LADY_BLAUMEUX || me->GetEntry() == NPC_SIR_ZELIEK) - { - if (Unit* pTarget = me->SelectNearestPlayer(300.0f)) - { - if (pTarget && me->IsValidAttackTarget(pTarget)) - { - AttackStart(pTarget); - } - } - if (me->IsWithinDistInMap(me->GetVictim(), 45.0f) && me->IsValidAttackTarget(me->GetVictim())) - { - DoCastVictim(RAID_MODE(TABLE_SPELL_PRIMARY_10[horsemanId], TABLE_SPELL_PRIMARY_25[horsemanId])); - } - else if (!me->IsWithinDistInMap(me->GetVictim(), 45.0f) || !me->IsValidAttackTarget(me->GetVictim())) - { - DoCastAOE(TABLE_SPELL_PUNISH[horsemanId]); - Talk(EMOTE_RAGECAST); - } - } - else - { - DoMeleeAttackIfReady(); - } - } - }; -}; - -class spell_four_horsemen_mark_aura : public AuraScript -{ - PrepareAuraScript(spell_four_horsemen_mark_aura); - - bool Validate(SpellInfo const* /*spellInfo*/) override - { - return ValidateSpellInfo({ SPELL_MARK_DAMAGE }); - } - - void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) - { - if (Unit* caster = GetCaster()) - { - int32 damage; - switch (GetStackAmount()) - { - case 1: - damage = 0; - break; - case 2: - damage = 500; - break; - case 3: - damage = 1500; - break; - case 4: - damage = 4000; - break; - case 5: - damage = 12500; - break; - case 6: - damage = 20000; - break; - default: - damage = 20000 + 1000 * (GetStackAmount() - 7); - break; - } - if (damage) - { - caster->CastCustomSpell(SPELL_MARK_DAMAGE, SPELLVALUE_BASE_POINT0, damage, GetTarget()); - } - } - } - void Register() override - { - AfterEffectApply += AuraEffectApplyFn(spell_four_horsemen_mark_aura::OnApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK); - } -}; +using namespace FourHorsemen; void AddSC_boss_four_horsemen() { diff --git a/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.h b/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.h new file mode 100644 index 00000000000000..6508552bd77084 --- /dev/null +++ b/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.h @@ -0,0 +1,425 @@ +#ifndef BOSS_FOURHORSEMEN_H_ +#define BOSS_FOURHORSEMEN_H_ + +#include "CreatureScript.h" +#include "Player.h" +#include "ScriptedCreature.h" +#include "SpellAuraEffects.h" +#include "SpellScript.h" +#include "SpellScriptLoader.h" +#include "SpellInfo.h" +#include "naxxramas.h" + +namespace FourHorsemen { + +enum Spells +{ + SPELL_BERSERK = 26662, + // Marks + SPELL_MARK_OF_KORTHAZZ = 28832, + SPELL_MARK_OF_BLAUMEUX = 28833, + SPELL_MARK_OF_RIVENDARE = 28834, + SPELL_MARK_OF_ZELIEK = 28835, + SPELL_MARK_DAMAGE = 28836, + // Korth'azz + SPELL_KORTHAZZ_METEOR_10 = 28884, + SPELL_KORTHAZZ_METEOR_25 = 57467, + // Blaumeux + SPELL_BLAUMEUX_SHADOW_BOLT_10 = 57374, + SPELL_BLAUMEUX_SHADOW_BOLT_25 = 57464, + SPELL_BLAUMEUX_VOID_ZONE_10 = 28863, + SPELL_BLAUMEUX_VOID_ZONE_25 = 57463, + SPELL_BLAUMEUX_UNYIELDING_PAIN = 57381, + // Zeliek + SPELL_ZELIEK_HOLY_WRATH_10 = 28883, + SPELL_ZELIEK_HOLY_WRATH_25 = 57466, + SPELL_ZELIEK_HOLY_BOLT_10 = 57376, + SPELL_ZELIEK_HOLY_BOLT_25 = 57465, + SPELL_ZELIEK_CONDEMNATION = 57377, + // Rivendare + SPELL_RIVENDARE_UNHOLY_SHADOW_10 = 28882, + SPELL_RIVENDARE_UNHOLY_SHADOW_25 = 57369 +}; + +enum Events +{ + EVENT_MARK_CAST = 1, + EVENT_PRIMARY_SPELL = 2, + EVENT_SECONDARY_SPELL = 3, + EVENT_BERSERK = 4 +}; + +enum Misc +{ + // Movement + MOVE_PHASE_NONE = 0, + MOVE_PHASE_STARTED = 1, + MOVE_PHASE_FINISHED = 2, + // Horseman + HORSEMAN_ZELIEK = 0, + HORSEMAN_BLAUMEUX = 1, + HORSEMAN_RIVENDARE = 2, + HORSEMAN_KORTHAZZ = 3 +}; + +enum FourHorsemen +{ + SAY_AGGRO = 0, + SAY_TAUNT = 1, + SAY_SPECIAL = 2, + SAY_SLAY = 3, + SAY_DEATH = 4, + EMOTE_RAGECAST = 7 +}; + +// MARKS +const uint32 TABLE_SPELL_MARK[4] = {SPELL_MARK_OF_ZELIEK, SPELL_MARK_OF_BLAUMEUX, SPELL_MARK_OF_RIVENDARE, SPELL_MARK_OF_KORTHAZZ}; + +// PRIMARY SPELL +const uint32 TABLE_SPELL_PRIMARY_10[4] = {SPELL_ZELIEK_HOLY_BOLT_10, SPELL_BLAUMEUX_SHADOW_BOLT_10, SPELL_RIVENDARE_UNHOLY_SHADOW_10, SPELL_KORTHAZZ_METEOR_10}; +const uint32 TABLE_SPELL_PRIMARY_25[4] = {SPELL_ZELIEK_HOLY_BOLT_25, SPELL_BLAUMEUX_SHADOW_BOLT_25, SPELL_RIVENDARE_UNHOLY_SHADOW_25, SPELL_KORTHAZZ_METEOR_25}; + +// PUNISH +const uint32 TABLE_SPELL_PUNISH[4] = {SPELL_ZELIEK_CONDEMNATION, SPELL_BLAUMEUX_UNYIELDING_PAIN, 0, 0}; + +// SECONDARY SPELL +const uint32 TABLE_SPELL_SECONDARY_10[4] = {SPELL_ZELIEK_HOLY_WRATH_10, SPELL_BLAUMEUX_VOID_ZONE_10, 0, 0}; +const uint32 TABLE_SPELL_SECONDARY_25[4] = {SPELL_ZELIEK_HOLY_WRATH_25, SPELL_BLAUMEUX_VOID_ZONE_25, 0, 0}; + +const Position WaypointPositions[12] = +{ + // Thane waypoints + {2542.3f, -2984.1f, 241.49f, 5.362f}, + {2547.6f, -2999.4f, 241.34f, 5.049f}, + {2542.9f, -3015.0f, 241.35f, 4.654f}, + // Lady waypoints + {2498.3f, -2961.8f, 241.28f, 3.267f}, + {2487.7f, -2959.2f, 241.28f, 2.890f}, + {2469.4f, -2947.6f, 241.28f, 2.576f}, + // Baron waypoints + {2553.8f, -2968.4f, 241.33f, 5.757f}, + {2564.3f, -2972.5f, 241.33f, 5.890f}, + {2583.9f, -2971.6f, 241.35f, 0.008f}, + // Sir waypoints + {2534.5f, -2921.7f, 241.53f, 1.363f}, + {2523.5f, -2902.8f, 241.28f, 2.095f}, + {2517.8f, -2896.6f, 241.28f, 2.315f} +}; + +class boss_four_horsemen : public CreatureScript +{ +public: + boss_four_horsemen() : CreatureScript("boss_four_horsemen") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_four_horsemenAI : public BossAI + { + explicit boss_four_horsemenAI(Creature* c) : BossAI(c, BOSS_HORSEMAN) + { + pInstance = me->GetInstanceScript(); + switch (me->GetEntry()) + { + case NPC_SIR_ZELIEK: + horsemanId = HORSEMAN_ZELIEK; + break; + case NPC_LADY_BLAUMEUX: + horsemanId = HORSEMAN_BLAUMEUX; + break; + case NPC_BARON_RIVENDARE: + horsemanId = HORSEMAN_RIVENDARE; + break; + case NPC_THANE_KORTHAZZ: + horsemanId = HORSEMAN_KORTHAZZ; + break; + } + } + + EventMap events; + InstanceScript* pInstance; + uint8 currentWaypoint{}; + uint8 movementPhase{}; + uint8 horsemanId; + + void MoveToCorner() + { + switch(me->GetEntry()) + { + case NPC_THANE_KORTHAZZ: + currentWaypoint = 0; + break; + case NPC_LADY_BLAUMEUX: + currentWaypoint = 3; + break; + case NPC_BARON_RIVENDARE: + currentWaypoint = 6; + break; + case NPC_SIR_ZELIEK: + currentWaypoint = 9; + break; + } + me->GetMotionMaster()->MovePoint(currentWaypoint, WaypointPositions[currentWaypoint]); + } + + bool IsInRoom() + { + if (me->GetExactDist(2535.1f, -2968.7f, 241.3f) > 100.0f) + { + EnterEvadeMode(); + return false; + } + return true; + } + + void Reset() override + { + BossAI::Reset(); + me->SetPosition(me->GetHomePosition()); + movementPhase = MOVE_PHASE_NONE; + currentWaypoint = 0; + me->SetReactState(REACT_AGGRESSIVE); + events.Reset(); + events.RescheduleEvent(EVENT_MARK_CAST, 24s); + events.RescheduleEvent(EVENT_BERSERK, 10min); + if ((me->GetEntry() != NPC_LADY_BLAUMEUX && me->GetEntry() != NPC_SIR_ZELIEK)) + { + events.RescheduleEvent(EVENT_PRIMARY_SPELL, 10s, 15s); + } + else + { + events.RescheduleEvent(EVENT_SECONDARY_SPELL, 15s); + } + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_HORSEMEN_GATE))) + { + if (pInstance->GetBossState(BOSS_GOTHIK) == DONE) + { + go->SetGoState(GO_STATE_ACTIVE); + } + } + } + } + + void MovementInform(uint32 type, uint32 id) override + { + if (type != POINT_MOTION_TYPE) + return; + + // final waypoint + if (id % 3 == 2) + { + movementPhase = MOVE_PHASE_FINISHED; + me->SetReactState(REACT_AGGRESSIVE); + me->SetInCombatWithZone(); + if (!UpdateVictim()) + { + EnterEvadeMode(); + return; + } + if (me->GetEntry() == NPC_LADY_BLAUMEUX || me->GetEntry() == NPC_SIR_ZELIEK) + { + me->GetMotionMaster()->Clear(false); + me->GetMotionMaster()->MoveIdle(); + } + return; + } + currentWaypoint = id + 1; + } + + void AttackStart(Unit* who) override + { + if (movementPhase == MOVE_PHASE_FINISHED) + { + if (me->GetEntry() == NPC_LADY_BLAUMEUX || me->GetEntry() == NPC_SIR_ZELIEK) + { + me->Attack(who, false); + } + else + { + ScriptedAI::AttackStart(who); + } + } + } + + void KilledUnit(Unit* who) override + { + if (!who->IsPlayer()) + return; + + Talk(SAY_SLAY); + if (pInstance) + { + pInstance->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + if (pInstance) + { + if (pInstance->GetBossState(BOSS_HORSEMAN) == DONE) + { + if (!me->GetMap()->GetPlayers().IsEmpty()) + { + if (Player* player = me->GetMap()->GetPlayers().getFirst()->GetSource()) + { + if (GameObject* chest = player->SummonGameObject(RAID_MODE(GO_HORSEMEN_CHEST_10, GO_HORSEMEN_CHEST_25), 2514.8f, -2944.9f, 245.55f, 5.51f, 0, 0, 0, 0, 0)) + { + chest->SetLootRecipient(me); + } + } + } + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_HORSEMEN_GATE))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + } + } + Talk(SAY_DEATH); + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + if (movementPhase == MOVE_PHASE_NONE) + { + Talk(SAY_AGGRO); + me->SetReactState(REACT_PASSIVE); + movementPhase = MOVE_PHASE_STARTED; + me->SetSpeed(MOVE_RUN, me->GetSpeedRate(MOVE_RUN), true); + MoveToCorner(); + } + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_HORSEMEN_GATE))) + { + go->SetGoState(GO_STATE_READY); + } + } + } + + void UpdateAI(uint32 diff) override + { + if (movementPhase == MOVE_PHASE_STARTED && currentWaypoint) + { + me->GetMotionMaster()->MovePoint(currentWaypoint, WaypointPositions[currentWaypoint]); + currentWaypoint = 0; + } + + if (!IsInRoom()) + return; + + if (movementPhase < MOVE_PHASE_FINISHED || !UpdateVictim()) + return; + + events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_MARK_CAST: + me->CastSpell(me, TABLE_SPELL_MARK[horsemanId], false); + events.Repeat((me->GetEntry() == NPC_LADY_BLAUMEUX || me->GetEntry() == NPC_SIR_ZELIEK) ? 15s : 12s); + return; + case EVENT_BERSERK: + Talk(SAY_SPECIAL); + me->CastSpell(me, SPELL_BERSERK, true); + return; + case EVENT_PRIMARY_SPELL: + Talk(SAY_TAUNT); + me->CastSpell(me->GetVictim(), RAID_MODE(TABLE_SPELL_PRIMARY_10[horsemanId], TABLE_SPELL_PRIMARY_25[horsemanId]), false); + events.Repeat(15s); + return; + case EVENT_SECONDARY_SPELL: + me->CastSpell(me->GetVictim(), RAID_MODE(TABLE_SPELL_SECONDARY_10[horsemanId], TABLE_SPELL_SECONDARY_25[horsemanId]), false); + events.Repeat(15s); + return; + } + + if (me->GetEntry() == NPC_LADY_BLAUMEUX || me->GetEntry() == NPC_SIR_ZELIEK) + { + if (Unit* pTarget = me->SelectNearestPlayer(300.0f)) + { + if (pTarget && me->IsValidAttackTarget(pTarget)) + { + AttackStart(pTarget); + } + } + if (me->IsWithinDistInMap(me->GetVictim(), 45.0f) && me->IsValidAttackTarget(me->GetVictim())) + { + DoCastVictim(RAID_MODE(TABLE_SPELL_PRIMARY_10[horsemanId], TABLE_SPELL_PRIMARY_25[horsemanId])); + } + else if (!me->IsWithinDistInMap(me->GetVictim(), 45.0f) || !me->IsValidAttackTarget(me->GetVictim())) + { + DoCastAOE(TABLE_SPELL_PUNISH[horsemanId]); + Talk(EMOTE_RAGECAST); + } + } + else + { + DoMeleeAttackIfReady(); + } + } + }; +}; + +class spell_four_horsemen_mark_aura : public AuraScript +{ + PrepareAuraScript(spell_four_horsemen_mark_aura); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MARK_DAMAGE }); + } + + void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + if (Unit* caster = GetCaster()) + { + int32 damage; + switch (GetStackAmount()) + { + case 1: + damage = 0; + break; + case 2: + damage = 500; + break; + case 3: + damage = 1500; + break; + case 4: + damage = 4000; + break; + case 5: + damage = 12500; + break; + case 6: + damage = 20000; + break; + default: + damage = 20000 + 1000 * (GetStackAmount() - 7); + break; + } + if (damage) + { + caster->CastCustomSpell(SPELL_MARK_DAMAGE, SPELLVALUE_BASE_POINT0, damage, GetTarget()); + } + } + } + + void Register() override + { + AfterEffectApply += AuraEffectApplyFn(spell_four_horsemen_mark_aura::OnApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK); + } +}; + +} // namespace FourHorsemen + +#endif \ No newline at end of file diff --git a/src/server/scripts/Northrend/Naxxramas/boss_gluth.cpp b/src/server/scripts/Northrend/Naxxramas/boss_gluth.cpp index cea3baccf2b108..f7e536ff77b90a 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_gluth.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_gluth.cpp @@ -15,6 +15,7 @@ * with this program. If not, see . */ +#include "boss_gluth.h" #include "CreatureScript.h" #include "Player.h" #include "ScriptedCreature.h" @@ -22,256 +23,8 @@ #include "SpellScriptLoader.h" #include "naxxramas.h" -enum Spells -{ - SPELL_MORTAL_WOUND = 25646, - SPELL_ENRAGE_10 = 28371, - SPELL_ENRAGE_25 = 54427, - SPELL_DECIMATE_10 = 28374, - SPELL_DECIMATE_25 = 54426, - SPELL_DECIMATE_DAMAGE = 28375, - SPELL_BERSERK = 26662, - SPELL_INFECTED_WOUND = 29306, - SPELL_CHOW_SEARCHER = 28404 -}; - -enum Events -{ - EVENT_MORTAL_WOUND = 1, - EVENT_ENRAGE = 2, - EVENT_DECIMATE = 3, - EVENT_BERSERK = 4, - EVENT_SUMMON_ZOMBIE = 5, - EVENT_CAN_EAT_ZOMBIE = 6 -}; - -enum Misc -{ - NPC_ZOMBIE_CHOW = 16360 -}; - -enum Emotes -{ - EMOTE_SPOTS_ONE = 0, - EMOTE_DECIMATE = 1, - EMOTE_ENRAGE = 2, - EMOTE_DEVOURS_ALL = 3, - EMOTE_BERSERK = 4 -}; - -const Position zombiePos[3] = -{ - {3267.9f, -3172.1f, 297.42f, 0.94f}, - {3253.2f, -3132.3f, 297.42f, 0}, - {3308.3f, -3185.8f, 297.42f, 1.58f} -}; - -class boss_gluth : public CreatureScript -{ -public: - boss_gluth() : CreatureScript("boss_gluth") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_gluthAI : public BossAI - { - explicit boss_gluthAI(Creature* c) : BossAI(c, BOSS_GLUTH), summons(me) - { - pInstance = me->GetInstanceScript(); - } - - EventMap events; - SummonList summons; - InstanceScript* pInstance; - - void Reset() override - { - BossAI::Reset(); - me->ApplySpellImmune(SPELL_INFECTED_WOUND, IMMUNITY_ID, SPELL_INFECTED_WOUND, true); - events.Reset(); - summons.DespawnAll(); - me->SetReactState(REACT_AGGRESSIVE); - } - - void MoveInLineOfSight(Unit* who) override - { - if (!me->GetVictim() || me->GetVictim()->GetEntry() != NPC_ZOMBIE_CHOW) - { - if (who->GetEntry() == NPC_ZOMBIE_CHOW && me->IsWithinDistInMap(who, 6.5f)) - { - SetGazeOn(who); - Talk(EMOTE_SPOTS_ONE); - } - else - { - ScriptedAI::MoveInLineOfSight(who); - } - } - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - me->SetInCombatWithZone(); - events.ScheduleEvent(EVENT_MORTAL_WOUND, 10s); - events.ScheduleEvent(EVENT_ENRAGE, 22s); - events.ScheduleEvent(EVENT_DECIMATE, RAID_MODE(110000, 90000)); - events.ScheduleEvent(EVENT_BERSERK, 6min); - events.ScheduleEvent(EVENT_SUMMON_ZOMBIE, 10s); - events.ScheduleEvent(EVENT_CAN_EAT_ZOMBIE, 1s); - } - - void JustSummoned(Creature* summon) override - { - if (summon->GetEntry() == NPC_ZOMBIE_CHOW) - { - summon->AI()->AttackStart(me); - } - summons.Summon(summon); - } - - void SummonedCreatureDies(Creature* cr, Unit*) override { summons.Despawn(cr); } - - void KilledUnit(Unit* who) override - { - if (me->IsAlive() && who->GetEntry() == NPC_ZOMBIE_CHOW) - { - me->ModifyHealth(int32(me->GetMaxHealth() * 0.05f)); - } - if (who->IsPlayer() && pInstance) - { - pInstance->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void JustDied(Unit* killer) override - { - BossAI::JustDied(killer); - summons.DespawnAll(); - } - - bool SelectPlayerInRoom() - { - if (me->IsInCombat()) - return false; - - Map::PlayerList const& pList = me->GetMap()->GetPlayers(); - for (auto const& itr : pList) - { - Player* player = itr.GetSource(); - if (!player || !player->IsAlive()) - continue; - - if (player->GetPositionZ() > 300.0f || me->GetExactDist(player) > 50.0f) - continue; - - AttackStart(player); - return true; - } - return false; - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictimWithGaze() && !SelectPlayerInRoom()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_BERSERK: - me->CastSpell(me, SPELL_BERSERK, true); - break; - case EVENT_ENRAGE: - Talk(EMOTE_ENRAGE); - me->CastSpell(me, RAID_MODE(SPELL_ENRAGE_10, SPELL_ENRAGE_25), true); - events.Repeat(22s); - break; - case EVENT_MORTAL_WOUND: - me->CastSpell(me->GetVictim(), SPELL_MORTAL_WOUND, false); - events.Repeat(10s); - break; - case EVENT_DECIMATE: - Talk(EMOTE_DECIMATE); - me->CastSpell(me, RAID_MODE(SPELL_DECIMATE_10, SPELL_DECIMATE_25), false); - events.RepeatEvent(RAID_MODE(110000, 90000)); - break; - case EVENT_SUMMON_ZOMBIE: - { - uint8 rand = urand(0, 2); - for (int32 i = 0; i < RAID_MODE(1, 2); ++i) - { - // In 10 man raid, normal mode - should spawn only from mid gate - // \1 |0 /2 pos - // In 25 man raid - should spawn from all 3 gates - if (me->GetMap()->GetDifficulty() == RAID_DIFFICULTY_10MAN_NORMAL) - { - me->SummonCreature(NPC_ZOMBIE_CHOW, zombiePos[0]); - } - else - { - me->SummonCreature(NPC_ZOMBIE_CHOW, zombiePos[urand(0, 2)]); - } - (rand == 2 ? rand = 0 : rand++); - } - events.Repeat(10s); - break; - } - case EVENT_CAN_EAT_ZOMBIE: - events.RepeatEvent(1000); - if (me->GetVictim()->GetEntry() == NPC_ZOMBIE_CHOW && me->IsWithinMeleeRange(me->GetVictim())) - { - me->CastCustomSpell(SPELL_CHOW_SEARCHER, SPELLVALUE_RADIUS_MOD, 20000, me, true); - Talk(EMOTE_DEVOURS_ALL); - return; // leave it to skip DoMeleeAttackIfReady - } - break; - } - DoMeleeAttackIfReady(); - } - }; -}; - -class spell_gluth_decimate : public SpellScript -{ - PrepareSpellScript(spell_gluth_decimate); - - bool Validate(SpellInfo const* /*spellInfo*/) override - { - return ValidateSpellInfo({ SPELL_DECIMATE_DAMAGE }); - } - - void HandleScriptEffect(SpellEffIndex /*effIndex*/) - { - if (Unit* unitTarget = GetHitUnit()) - { - int32 damage = int32(unitTarget->GetHealth()) - int32(unitTarget->CountPctFromMaxHealth(5)); - if (damage <= 0) - return; - - if (Creature* cTarget = unitTarget->ToCreature()) - { - cTarget->SetWalk(true); - cTarget->GetMotionMaster()->MoveFollow(GetCaster(), 0.0f, 0.0f, MOTION_SLOT_CONTROLLED); - cTarget->SetReactState(REACT_PASSIVE); - Unit::DealDamage(GetCaster(), cTarget, damage); - return; - } - GetCaster()->CastCustomSpell(SPELL_DECIMATE_DAMAGE, SPELLVALUE_BASE_POINT0, damage, unitTarget); - } - } - void Register() override - { - OnEffectHitTarget += SpellEffectFn(spell_gluth_decimate::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); - } -}; +using namespace Gluth; void AddSC_boss_gluth() { diff --git a/src/server/scripts/Northrend/Naxxramas/boss_gluth.h b/src/server/scripts/Northrend/Naxxramas/boss_gluth.h new file mode 100644 index 00000000000000..5c4f4514377a51 --- /dev/null +++ b/src/server/scripts/Northrend/Naxxramas/boss_gluth.h @@ -0,0 +1,266 @@ +#ifndef BOSSGLUTH_H_ +#define BOSSGLUTH_H_ + +#include "Player.h" +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "SpellScript.h" +#include "SpellInfo.h" +#include "naxxramas.h" + +namespace Gluth { + +enum Spells +{ + SPELL_MORTAL_WOUND = 25646, + SPELL_ENRAGE_10 = 28371, + SPELL_ENRAGE_25 = 54427, + SPELL_DECIMATE_10 = 28374, + SPELL_DECIMATE_25 = 54426, + SPELL_DECIMATE_DAMAGE = 28375, + SPELL_BERSERK = 26662, + SPELL_INFECTED_WOUND = 29306, + SPELL_CHOW_SEARCHER = 28404 +}; + +enum Events +{ + EVENT_MORTAL_WOUND = 1, + EVENT_ENRAGE = 2, + EVENT_DECIMATE = 3, + EVENT_BERSERK = 4, + EVENT_SUMMON_ZOMBIE = 5, + EVENT_CAN_EAT_ZOMBIE = 6 +}; + +enum Misc +{ + NPC_ZOMBIE_CHOW = 16360 +}; + +enum Emotes +{ + EMOTE_SPOTS_ONE = 0, + EMOTE_DECIMATE = 1, + EMOTE_ENRAGE = 2, + EMOTE_DEVOURS_ALL = 3, + EMOTE_BERSERK = 4 +}; + +const Position zombiePos[3] = +{ + {3267.9f, -3172.1f, 297.42f, 0.94f}, + {3253.2f, -3132.3f, 297.42f, 0}, + {3308.3f, -3185.8f, 297.42f, 1.58f} +}; + +class boss_gluth : public CreatureScript +{ +public: + boss_gluth() : CreatureScript("boss_gluth") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_gluthAI : public BossAI + { + explicit boss_gluthAI(Creature* c) : BossAI(c, BOSS_GLUTH), summons(me) + { + pInstance = me->GetInstanceScript(); + } + + EventMap events; + SummonList summons; + InstanceScript* pInstance; + + void Reset() override + { + BossAI::Reset(); + me->ApplySpellImmune(SPELL_INFECTED_WOUND, IMMUNITY_ID, SPELL_INFECTED_WOUND, true); + events.Reset(); + summons.DespawnAll(); + me->SetReactState(REACT_AGGRESSIVE); + } + + void MoveInLineOfSight(Unit* who) override + { + if (!me->GetVictim() || me->GetVictim()->GetEntry() != NPC_ZOMBIE_CHOW) + { + if (who->GetEntry() == NPC_ZOMBIE_CHOW && me->IsWithinDistInMap(who, 6.5f)) + { + SetGazeOn(who); + Talk(EMOTE_SPOTS_ONE); + } + else + { + ScriptedAI::MoveInLineOfSight(who); + } + } + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + me->SetInCombatWithZone(); + events.ScheduleEvent(EVENT_MORTAL_WOUND, 10s); + events.ScheduleEvent(EVENT_ENRAGE, 22s); + events.ScheduleEvent(EVENT_DECIMATE, RAID_MODE(110000, 90000)); + events.ScheduleEvent(EVENT_BERSERK, 6min); + events.ScheduleEvent(EVENT_SUMMON_ZOMBIE, 10s); + events.ScheduleEvent(EVENT_CAN_EAT_ZOMBIE, 1s); + } + + void JustSummoned(Creature* summon) override + { + if (summon->GetEntry() == NPC_ZOMBIE_CHOW) + { + summon->AI()->AttackStart(me); + } + summons.Summon(summon); + } + + void SummonedCreatureDies(Creature* cr, Unit*) override { summons.Despawn(cr); } + + void KilledUnit(Unit* who) override + { + if (me->IsAlive() && who->GetEntry() == NPC_ZOMBIE_CHOW) + { + me->ModifyHealth(int32(me->GetMaxHealth() * 0.05f)); + } + if (who->IsPlayer() && pInstance) + { + pInstance->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + summons.DespawnAll(); + } + + bool SelectPlayerInRoom() + { + if (me->IsInCombat()) + return false; + + Map::PlayerList const& pList = me->GetMap()->GetPlayers(); + for (auto const& itr : pList) + { + Player* player = itr.GetSource(); + if (!player || !player->IsAlive()) + continue; + + if (player->GetPositionZ() > 300.0f || me->GetExactDist(player) > 50.0f) + continue; + + AttackStart(player); + return true; + } + return false; + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictimWithGaze() && !SelectPlayerInRoom()) + return; + + events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_BERSERK: + me->CastSpell(me, SPELL_BERSERK, true); + break; + case EVENT_ENRAGE: + Talk(EMOTE_ENRAGE); + me->CastSpell(me, RAID_MODE(SPELL_ENRAGE_10, SPELL_ENRAGE_25), true); + events.Repeat(22s); + break; + case EVENT_MORTAL_WOUND: + me->CastSpell(me->GetVictim(), SPELL_MORTAL_WOUND, false); + events.Repeat(10s); + break; + case EVENT_DECIMATE: + Talk(EMOTE_DECIMATE); + me->CastSpell(me, RAID_MODE(SPELL_DECIMATE_10, SPELL_DECIMATE_25), false); + events.RepeatEvent(RAID_MODE(110000, 90000)); + break; + case EVENT_SUMMON_ZOMBIE: + { + uint8 rand = urand(0, 2); + for (int32 i = 0; i < RAID_MODE(1, 2); ++i) + { + // In 10 man raid, normal mode - should spawn only from mid gate + // \1 |0 /2 pos + // In 25 man raid - should spawn from all 3 gates + if (me->GetMap()->GetDifficulty() == RAID_DIFFICULTY_10MAN_NORMAL) + { + me->SummonCreature(NPC_ZOMBIE_CHOW, zombiePos[0]); + } + else + { + me->SummonCreature(NPC_ZOMBIE_CHOW, zombiePos[urand(0, 2)]); + } + (rand == 2 ? rand = 0 : rand++); + } + events.Repeat(10s); + break; + } + case EVENT_CAN_EAT_ZOMBIE: + events.RepeatEvent(1000); + if (me->GetVictim()->GetEntry() == NPC_ZOMBIE_CHOW && me->IsWithinMeleeRange(me->GetVictim())) + { + me->CastCustomSpell(SPELL_CHOW_SEARCHER, SPELLVALUE_RADIUS_MOD, 20000, me, true); + Talk(EMOTE_DEVOURS_ALL); + return; // leave it to skip DoMeleeAttackIfReady + } + break; + } + DoMeleeAttackIfReady(); + } + }; +}; + +class spell_gluth_decimate : public SpellScript +{ + PrepareSpellScript(spell_gluth_decimate); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_DECIMATE_DAMAGE }); + } + + void HandleScriptEffect(SpellEffIndex /*effIndex*/) + { + if (Unit* unitTarget = GetHitUnit()) + { + int32 damage = int32(unitTarget->GetHealth()) - int32(unitTarget->CountPctFromMaxHealth(5)); + if (damage <= 0) + return; + + if (Creature* cTarget = unitTarget->ToCreature()) + { + cTarget->SetWalk(true); + cTarget->GetMotionMaster()->MoveFollow(GetCaster(), 0.0f, 0.0f, MOTION_SLOT_CONTROLLED); + cTarget->SetReactState(REACT_PASSIVE); + Unit::DealDamage(GetCaster(), cTarget, damage); + return; + } + GetCaster()->CastCustomSpell(SPELL_DECIMATE_DAMAGE, SPELLVALUE_BASE_POINT0, damage, unitTarget); + } + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_gluth_decimate::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } +}; + +} + +#endif \ No newline at end of file diff --git a/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp b/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp index ba1e94e1a3f866..507bd07feb6e80 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp @@ -15,6 +15,7 @@ * with this program. If not, see . */ +#include "boss_gothik.h" #include "CombatAI.h" #include "CreatureScript.h" #include "GridNotifiers.h" @@ -23,784 +24,8 @@ #include "SpellScriptLoader.h" #include "naxxramas.h" -enum Yells -{ - SAY_INTRO_1 = 0, - SAY_INTRO_2 = 1, - SAY_INTRO_3 = 2, - SAY_INTRO_4 = 3, - SAY_PHASE_TWO = 4, - SAY_DEATH = 5, - SAY_KILL = 6, - - EMOTE_PHASE_TWO = 7, - EMOTE_GATE_OPENED = 8 -}; - -enum Spells -{ - // Gothik - SPELL_HARVEST_SOUL = 28679, - SPELL_SHADOW_BOLT_10 = 29317, - SPELL_SHADOW_BOLT_25 = 56405, - // Teleport spells - SPELL_TELEPORT_DEAD = 28025, - SPELL_TELEPORT_LIVE = 28026, - // Visual spells - SPELL_ANCHOR_1_TRAINEE = 27892, - SPELL_ANCHOR_1_DK = 27928, - SPELL_ANCHOR_1_RIDER = 27935, - SPELL_ANCHOR_2_TRAINEE = 27893, - SPELL_ANCHOR_2_DK = 27929, - SPELL_ANCHOR_2_RIDER = 27936, - SPELL_SKULLS_TRAINEE = 27915, - SPELL_SKULLS_DK = 27931, - SPELL_SKULLS_RIDER = 27937, - // Living trainee - SPELL_DEATH_PLAGUE = 55604, - // Dead trainee - SPELL_ARCANE_EXPLOSION = 27989, - // Living knight - SPELL_SHADOW_MARK = 27825, - // Dead knight - SPELL_WHIRLWIND = 56408, - // Living rider - SPELL_SHADOW_BOLT_VOLLEY = 27831, - // Dead rider - SPELL_DRAIN_LIFE = 27994, - SPELL_UNHOLY_FRENZY = 55648, - // Horse - SPELL_STOMP = 27993 -}; - -enum Misc -{ - NPC_LIVING_TRAINEE = 16124, - NPC_LIVING_KNIGHT = 16125, - NPC_LIVING_RIDER = 16126, - NPC_DEAD_TRAINEE = 16127, - NPC_DEAD_KNIGHT = 16148, - NPC_DEAD_HORSE = 16149, - NPC_DEAD_RIDER = 16150, - NPC_TRIGGER = 16137 -}; - -enum Events -{ - // Gothik - EVENT_SUMMON_ADDS = 1, - EVENT_HARVEST_SOUL = 2, - EVENT_SHADOW_BOLT = 3, - EVENT_TELEPORT = 4, - EVENT_CHECK_HEALTH = 5, - EVENT_CHECK_PLAYERS = 6, - // Living trainee - EVENT_DEATH_PLAGUE = 7, - // Dead trainee - EVENT_ARCANE_EXPLOSION = 8, - // Living knight - EVENT_SHADOW_MARK = 9, - // Dead knight - EVENT_WHIRLWIND = 10, - // Living rider - EVENT_SHADOW_BOLT_VOLLEY = 11, - // Dead rider - EVENT_DRAIN_LIFE = 12, - EVENT_UNHOLY_FRENZY = 13, - // HORSE - EVENT_STOMP = 14, - // Intro - EVENT_INTRO_2 = 15, - EVENT_INTRO_3 = 16, - EVENT_INTRO_4 = 17 -}; - -const uint32 gothikWaves[24][2] = -{ - {NPC_LIVING_TRAINEE, 20000}, - {NPC_LIVING_TRAINEE, 20000}, - {NPC_LIVING_TRAINEE, 10000}, - {NPC_LIVING_KNIGHT, 10000}, - {NPC_LIVING_TRAINEE, 15000}, - {NPC_LIVING_KNIGHT, 10000}, - {NPC_LIVING_TRAINEE, 15000}, - {NPC_LIVING_TRAINEE, 0}, - {NPC_LIVING_KNIGHT, 10000}, - {NPC_LIVING_RIDER, 10000}, - {NPC_LIVING_TRAINEE, 5000}, - {NPC_LIVING_KNIGHT, 15000}, - {NPC_LIVING_RIDER, 0}, - {NPC_LIVING_TRAINEE, 10000}, - {NPC_LIVING_KNIGHT, 10000}, - {NPC_LIVING_TRAINEE, 10000}, - {NPC_LIVING_RIDER, 5000}, - {NPC_LIVING_KNIGHT, 5000}, - {NPC_LIVING_TRAINEE, 20000}, - {NPC_LIVING_RIDER, 0}, - {NPC_LIVING_KNIGHT, 0}, - {NPC_LIVING_TRAINEE, 15000}, - {NPC_LIVING_TRAINEE, 29000}, - {0, 0} -}; - -const Position PosSummonLiving[6] = -{ - {2669.7f, -3428.76f, 268.56f, 1.6f}, - {2692.1f, -3428.76f, 268.56f, 1.6f}, - {2714.4f, -3428.76f, 268.56f, 1.6f}, - {2669.7f, -3431.67f, 268.56f, 1.6f}, - {2692.1f, -3431.67f, 268.56f, 1.6f}, - {2714.4f, -3431.67f, 268.56f, 1.6f} -}; - -const Position PosSummonDead[5] = -{ - {2725.1f, -3310.0f, 268.85f, 3.4f}, - {2699.3f, -3322.8f, 268.60f, 3.3f}, - {2733.1f, -3348.5f, 268.84f, 3.1f}, - {2682.8f, -3304.2f, 268.85f, 3.9f}, - {2664.8f, -3340.7f, 268.23f, 3.7f} -}; - -//const Position PosGroundLivingSide = {2691.2f, -3387.0f, 267.68f, 1.52f}; -//const Position PosGroundDeadSide = {2693.5f, -3334.6f, 267.68f, 4.67f}; -//const Position PosPlatform = {2640.5f, -3360.6f, 285.26f, 0.0f}; - -#define POS_Y_GATE -3360.78f -#define POS_Y_WEST -3285.0f -#define POS_Y_EAST -3434.0f -#define POS_X_NORTH 2750.49f -#define POS_X_SOUTH 2633.84f -#define IN_LIVE_SIDE(who) (who->GetPositionY() < POS_Y_GATE) - -// Predicate function to check that the r efzr unit is NOT on the same side as the source. -struct NotOnSameSide -{ -public: - explicit NotOnSameSide(Unit* pSource) : m_inLiveSide(IN_LIVE_SIDE(pSource)) { } - - bool operator() (Unit const* pTarget) - { - return (m_inLiveSide != IN_LIVE_SIDE(pTarget)); - } - -private: - bool m_inLiveSide; -}; - -class boss_gothik : public CreatureScript -{ -public: - boss_gothik() : CreatureScript("boss_gothik") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_gothikAI : public BossAI - { - explicit boss_gothikAI(Creature* c) : BossAI(c, BOSS_GOTHIK), summons(me) - { - pInstance = me->GetInstanceScript(); - } - EventMap events; - SummonList summons; - InstanceScript* pInstance; - bool secondPhase{}; - bool gateOpened{}; - uint8 waveCount{}; - - bool IsInRoom() - { - if (me->GetPositionX() > 2767 || me->GetPositionX() < 2618 || me->GetPositionY() > -3285 || me->GetPositionY() < -3435) - { - EnterEvadeMode(); - return false; - } - return true; - } - - void Reset() override - { - BossAI::Reset(); - events.Reset(); - summons.DespawnAll(); - me->RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE); - me->SetImmuneToPC(false); - me->SetReactState(REACT_PASSIVE); - secondPhase = false; - gateOpened = false; - waveCount = 0; - me->NearTeleportTo(2642.139f, -3386.959f, 285.492f, 6.265f); - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_ENTER_GATE))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_INNER_GATE))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_EXIT_GATE))) - { - go->SetGoState(GO_STATE_READY); - } - } - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - me->SetInCombatWithZone(); - Talk(SAY_INTRO_1); - events.ScheduleEvent(EVENT_INTRO_2, 4s); - events.ScheduleEvent(EVENT_INTRO_3, 9s); - events.ScheduleEvent(EVENT_INTRO_4, 14s); - me->SetUnitFlag(UNIT_FLAG_DISABLE_MOVE); - events.ScheduleEvent(EVENT_SUMMON_ADDS, 30s); - events.ScheduleEvent(EVENT_CHECK_PLAYERS, 2min); - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_ENTER_GATE))) - { - go->SetGoState(GO_STATE_READY); - } - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_INNER_GATE))) - { - go->SetGoState(GO_STATE_READY); - } - } - } - - void JustSummoned(Creature* summon) override - { - summons.Summon(summon); - // If central gate is open, attack any one - if (gateOpened) - { - if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, 200.0f)) - { - summon->AI()->AttackStart(target); - summon->SetInCombatWithZone(); - summon->SetReactState(REACT_AGGRESSIVE); - summon->CallForHelp(150.0f); - } - } - // Else look for a random target on the side the summoned NPC is - else - { - Map::PlayerList const& pList = me->GetMap()->GetPlayers(); - std::vector tList; - for(Map::PlayerList::const_iterator itr = pList.begin(); itr != pList.end(); ++itr) - { - if (!me->IsWithinDistInMap(itr->GetSource(), 200.0f, true, false) || !itr->GetSource()->IsAlive() || itr->GetSource()->IsGameMaster()) - { - continue; - } - if (IN_LIVE_SIDE(itr->GetSource()) != IN_LIVE_SIDE(summon)) - { - continue; - } - tList.push_back(itr->GetSource()); - } - if (!tList.empty()) - { - Player* target = tList[urand(0, tList.size() - 1)]; - summon->AI()->AttackStart(target); - summon->SetInCombatWithZone(); - summon->SetReactState(REACT_AGGRESSIVE); - summon->CallForHelp(150.0f); - } - } - } - - void SummonedCreatureDespawn(Creature* cr) override - { - summons.Despawn(cr); - } - - void KilledUnit(Unit* who) override - { - if (!who->IsPlayer()) - return; - - Talk(SAY_KILL); - if (pInstance) - { - pInstance->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void JustDied(Unit* killer) override - { - BossAI::JustDied(killer); - Talk(SAY_DEATH); - summons.DespawnAll(); - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_ENTER_GATE))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_INNER_GATE))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_EXIT_GATE))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - } - } - - void SummonHelpers(uint32 entry) - { - switch(entry) - { - case NPC_LIVING_TRAINEE: - me->SummonCreature(NPC_LIVING_TRAINEE, PosSummonLiving[0].GetPositionX(), PosSummonLiving[0].GetPositionY(), PosSummonLiving[0].GetPositionZ(), PosSummonLiving[0].GetOrientation()); - me->SummonCreature(NPC_LIVING_TRAINEE, PosSummonLiving[1].GetPositionX(), PosSummonLiving[1].GetPositionY(), PosSummonLiving[1].GetPositionZ(), PosSummonLiving[1].GetOrientation()); - if (Is25ManRaid()) - { - me->SummonCreature(NPC_LIVING_TRAINEE, PosSummonLiving[2].GetPositionX(), PosSummonLiving[2].GetPositionY(), PosSummonLiving[2].GetPositionZ(), PosSummonLiving[2].GetOrientation()); - } - break; - case NPC_LIVING_KNIGHT: - me->SummonCreature(NPC_LIVING_KNIGHT, PosSummonLiving[3].GetPositionX(), PosSummonLiving[3].GetPositionY(), PosSummonLiving[3].GetPositionZ(), PosSummonLiving[3].GetOrientation()); - if (Is25ManRaid()) - { - me->SummonCreature(NPC_LIVING_KNIGHT, PosSummonLiving[5].GetPositionX(), PosSummonLiving[5].GetPositionY(), PosSummonLiving[5].GetPositionZ(), PosSummonLiving[5].GetOrientation()); - } - break; - case NPC_LIVING_RIDER: - me->SummonCreature(NPC_LIVING_RIDER, PosSummonLiving[4].GetPositionX(), PosSummonLiving[4].GetPositionY(), PosSummonLiving[4].GetPositionZ(), PosSummonLiving[4].GetOrientation()); - break; - } - } - - bool CheckGroupSplitted() - { - Map::PlayerList const& PlayerList = me->GetMap()->GetPlayers(); - if (!PlayerList.IsEmpty()) - { - bool checklife = false; - bool checkdead = false; - for (auto const& i : PlayerList) - { - Player* player = i.GetSource(); - if (player->IsAlive() && - player->GetPositionX() <= POS_X_NORTH && - player->GetPositionX() >= POS_X_SOUTH && - player->GetPositionY() <= POS_Y_GATE && - player->GetPositionY() >= POS_Y_EAST) - { - checklife = true; - } - else if (player->IsAlive() && - player->GetPositionX() <= POS_X_NORTH && - player->GetPositionX() >= POS_X_SOUTH && - player->GetPositionY() >= POS_Y_GATE && - player->GetPositionY() <= POS_Y_WEST) - { - checkdead = true; - } - - if (checklife && checkdead) - return true; - } - } - return false; - } - - void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override - { - if (!secondPhase) - { - damage = 0; - } - } - - void UpdateAI(uint32 diff) override - { - if (!IsInRoom()) - return; - - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_INTRO_2: - Talk(SAY_INTRO_2); - break; - case EVENT_INTRO_3: - Talk(SAY_INTRO_3); - break; - case EVENT_INTRO_4: - Talk(SAY_INTRO_4); - break; - case EVENT_SHADOW_BOLT: - me->CastSpell(me->GetVictim(), RAID_MODE(SPELL_SHADOW_BOLT_10, SPELL_SHADOW_BOLT_25), false); - events.Repeat(1s); - break; - case EVENT_HARVEST_SOUL: - me->CastSpell(me, SPELL_HARVEST_SOUL, false); - events.Repeat(15s); - break; - case EVENT_TELEPORT: - me->AttackStop(); - if (IN_LIVE_SIDE(me)) - { - me->CastSpell(me, SPELL_TELEPORT_DEAD, false); - } - else - { - me->CastSpell(me, SPELL_TELEPORT_LIVE, false); - } - me->GetThreatMgr().resetAggro(NotOnSameSide(me)); - if (Unit* pTarget = SelectTarget(SelectTargetMethod::MaxDistance, 0)) - { - me->GetThreatMgr().AddThreat(pTarget, 100.0f); - AttackStart(pTarget); - } - events.Repeat(20s); - break; - case EVENT_CHECK_HEALTH: - if (me->HealthBelowPct(30) && pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_INNER_GATE))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - events.CancelEvent(EVENT_TELEPORT); - break; - } - events.Repeat(1s); - break; - case EVENT_SUMMON_ADDS: - if (gothikWaves[waveCount][0]) - { - SummonHelpers(gothikWaves[waveCount][0]); - events.RepeatEvent(gothikWaves[waveCount][1]); - } - else - { - secondPhase = true; - Talk(SAY_PHASE_TWO); - Talk(EMOTE_PHASE_TWO); - me->CastSpell(me, SPELL_TELEPORT_LIVE, false); - me->SetReactState(REACT_AGGRESSIVE); - me->RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE); - me->SetImmuneToPC(false); - me->RemoveAllAuras(); - events.ScheduleEvent(EVENT_SHADOW_BOLT, 1s); - events.ScheduleEvent(EVENT_HARVEST_SOUL, 5s, 15s); - events.ScheduleEvent(EVENT_TELEPORT, 20s); - events.ScheduleEvent(EVENT_CHECK_HEALTH, 1s); - } - waveCount++; - break; - case EVENT_CHECK_PLAYERS: - if (!CheckGroupSplitted()) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_INNER_GATE))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - gateOpened = true; - Talk(EMOTE_GATE_OPENED); - } - break; - } - DoMeleeAttackIfReady(); - } - }; -}; - -class npc_boss_gothik_minion : public CreatureScript -{ -public: - npc_boss_gothik_minion() : CreatureScript("npc_boss_gothik_minion") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct npc_boss_gothik_minionAI : public CombatAI - { - explicit npc_boss_gothik_minionAI(Creature* c) : CombatAI(c) - { - livingSide = IN_LIVE_SIDE(me); - } - EventMap events; - bool livingSide; - bool IsOnSameSide(Unit const* who) const { return livingSide == IN_LIVE_SIDE(who); } - - void Reset() override - { - me->SetReactState(REACT_AGGRESSIVE); - me->SetNoCallAssistance(false); - events.Reset(); - } - - void JustEngagedWith(Unit* /*who*/) override - { - switch (me->GetEntry()) - { - case NPC_LIVING_TRAINEE: - events.ScheduleEvent(EVENT_DEATH_PLAGUE, 3s); - break; - case NPC_DEAD_TRAINEE: - events.ScheduleEvent(EVENT_ARCANE_EXPLOSION, 2500ms); - break; - case NPC_LIVING_KNIGHT: - events.ScheduleEvent(EVENT_SHADOW_MARK, 3s); - break; - case NPC_DEAD_KNIGHT: - events.ScheduleEvent(EVENT_WHIRLWIND, 2s); - break; - case NPC_LIVING_RIDER: - events.ScheduleEvent(EVENT_SHADOW_BOLT_VOLLEY, 3s); - break; - case NPC_DEAD_RIDER: - events.ScheduleEvent(EVENT_DRAIN_LIFE, 2000ms, 3500ms); - events.ScheduleEvent(EVENT_UNHOLY_FRENZY, 5s, 9s); - break; - case NPC_DEAD_HORSE: - events.ScheduleEvent(EVENT_STOMP, 2s, 5s); - break; - } - } - - void JustDied(Unit*) override - { - switch (me->GetEntry()) - { - case NPC_LIVING_TRAINEE: - DoCastAOE(SPELL_ANCHOR_1_TRAINEE, true); - break; - case NPC_LIVING_KNIGHT: - DoCastAOE(SPELL_ANCHOR_1_DK, true); - break; - case NPC_LIVING_RIDER: - DoCastAOE(SPELL_ANCHOR_1_RIDER, true); - break; - } - } - - void KilledUnit(Unit* who) override - { - if (who->IsPlayer() && me->GetInstanceScript()) - { - me->GetInstanceScript()->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void UpdateAI(uint32 diff) override - { - events.Update(diff); - if (me->GetUnitState() == UNIT_STATE_CASTING) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_DEATH_PLAGUE: - me->CastSpell(me->GetVictim(), SPELL_DEATH_PLAGUE, false); - events.Repeat(4s, 7s); - break; - case EVENT_ARCANE_EXPLOSION: - if (Unit* victim = me->GetVictim()) - { - if (victim->IsWithinDist(me, 20)) - { - me->CastSpell(victim, SPELL_ARCANE_EXPLOSION, false); - } - } - events.Repeat(2500ms); - break; - case EVENT_SHADOW_MARK: - if (Unit* victim = me->GetVictim()) - { - if (victim->IsWithinDist(me, 10)) - { - me->CastSpell(victim, SPELL_SHADOW_MARK, false); - } - } - events.Repeat(5s, 7s); - break; - case EVENT_WHIRLWIND: - if (Unit* victim = me->GetVictim()) - { - if (victim->IsWithinDist(me, 10)) - { - me->CastSpell(victim, SPELL_WHIRLWIND, false); - } - } - events.Repeat(4s, 6s); - break; - case EVENT_SHADOW_BOLT_VOLLEY: - me->CastSpell(me->GetVictim(), SPELL_SHADOW_BOLT_VOLLEY, false); - events.Repeat(5s); - break; - case EVENT_DRAIN_LIFE: - if (Unit* victim = me->GetVictim()) - { - if (victim->IsWithinDist(me, 20)) - { - me->CastSpell(victim, SPELL_DRAIN_LIFE, false); - } - } - events.Repeat(8s, 12s); - break; - case EVENT_UNHOLY_FRENZY: - me->AddAura(SPELL_UNHOLY_FRENZY, me); - events.Repeat(15s, 17s); - break; - case EVENT_STOMP: - if (Unit* victim = me->GetVictim()) - { - if (victim->IsWithinDist(me, 10)) - { - me->CastSpell(victim, SPELL_STOMP, false); - } - } - events.Repeat(4s, 9s); - break; - } - DoMeleeAttackIfReady(); - } - }; -}; - -class npc_gothik_trigger : public CreatureScript -{ -public: - npc_gothik_trigger() : CreatureScript("npc_gothik_trigger") { } - - CreatureAI* GetAI(Creature* creature) const override - { - return new npc_gothik_triggerAI(creature); - } - - struct npc_gothik_triggerAI : public ScriptedAI - { - npc_gothik_triggerAI(Creature* creature) : ScriptedAI(creature) { creature->SetDisableGravity(true); } - - void EnterEvadeMode(EvadeReason /*why*/) override {} - void UpdateAI(uint32 /*diff*/) override {} - void JustEngagedWith(Unit* /*who*/) override {} - void DamageTaken(Unit* /*who*/, uint32& damage, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/) override { damage = 0; } - - Creature* SelectRandomSkullPile() - { - std::list triggers; - me->GetCreatureListWithEntryInGrid(triggers, NPC_TRIGGER, 150.0f); - // Remove triggers that are on live side or soul triggers on the platform - triggers.remove_if([](Creature* trigger){ - return ((trigger->GetPositionY() < POS_Y_GATE) || (trigger->GetPositionZ() > 280.0f)); - }); - if (!triggers.empty()) - { - std::list::iterator itr = triggers.begin(); - std::advance(itr, urand(0, triggers.size() - 1)); - return *itr; - } - return nullptr; - } - - void SpellHit(Unit* /*caster*/, SpellInfo const* spell) override - { - if (!spell) - { - return; - } - - switch (spell->Id) - { - case SPELL_ANCHOR_1_TRAINEE: - DoCastAOE(SPELL_ANCHOR_2_TRAINEE, true); - break; - case SPELL_ANCHOR_1_DK: - DoCastAOE(SPELL_ANCHOR_2_DK, true); - break; - case SPELL_ANCHOR_1_RIDER: - DoCastAOE(SPELL_ANCHOR_2_RIDER, true); - break; - case SPELL_ANCHOR_2_TRAINEE: - if (Creature* target = SelectRandomSkullPile()) - { - DoCast(target, SPELL_SKULLS_TRAINEE, true); - } - break; - case SPELL_ANCHOR_2_DK: - if (Creature* target = SelectRandomSkullPile()) - { - DoCast(target, SPELL_SKULLS_DK, true); - } - break; - case SPELL_ANCHOR_2_RIDER: - if (Creature* target = SelectRandomSkullPile()) - { - DoCast(target, SPELL_SKULLS_RIDER, true); - } - break; - case SPELL_SKULLS_TRAINEE: - DoSummon(NPC_DEAD_TRAINEE, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); - break; - case SPELL_SKULLS_DK: - DoSummon(NPC_DEAD_KNIGHT, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); - break; - case SPELL_SKULLS_RIDER: - DoSummon(NPC_DEAD_RIDER, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); - DoSummon(NPC_DEAD_HORSE, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); - break; - } - } - - // dead side summons are "owned" by gothik - void JustSummoned(Creature* summon) override - { - if (Creature* gothik = ObjectAccessor::GetCreature(*me, me->GetInstanceScript()->GetGuidData(DATA_GOTHIK_BOSS))) - { - gothik->AI()->JustSummoned(summon); - } - } - void SummonedCreatureDespawn(Creature* summon) override - { - if (Creature* gothik = ObjectAccessor::GetCreature(*me, me->GetInstanceScript()->GetGuidData(DATA_GOTHIK_BOSS))) - { - gothik->AI()->SummonedCreatureDespawn(summon); - } - } - }; -}; - -class spell_gothik_shadow_bolt_volley : public SpellScript -{ - PrepareSpellScript(spell_gothik_shadow_bolt_volley); - - bool Validate(SpellInfo const* /*spellInfo*/) override - { - return ValidateSpellInfo({ SPELL_SHADOW_MARK }); - } - - void FilterTargets(std::list& targets) - { - targets.remove_if(Acore::UnitAuraCheck(false, SPELL_SHADOW_MARK)); - } - void Register() override - { - OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_gothik_shadow_bolt_volley::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY); - } -}; +using namespace Gothik; void AddSC_boss_gothik() { diff --git a/src/server/scripts/Northrend/Naxxramas/boss_gothik.h b/src/server/scripts/Northrend/Naxxramas/boss_gothik.h new file mode 100644 index 00000000000000..e8d343296139d1 --- /dev/null +++ b/src/server/scripts/Northrend/Naxxramas/boss_gothik.h @@ -0,0 +1,795 @@ +#ifndef BOSSGOTHIK_H_ +#define BOSSGOTHIK_H_ + +#include "CombatAI.h" +#include "GridNotifiers.h" +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "SpellScript.h" +#include "SpellInfo.h" +#include "naxxramas.h" + +namespace Gothik { + +enum Yells +{ + SAY_INTRO_1 = 0, + SAY_INTRO_2 = 1, + SAY_INTRO_3 = 2, + SAY_INTRO_4 = 3, + SAY_PHASE_TWO = 4, + SAY_DEATH = 5, + SAY_KILL = 6, + + EMOTE_PHASE_TWO = 7, + EMOTE_GATE_OPENED = 8 +}; + +enum Spells +{ + // Gothik + SPELL_HARVEST_SOUL = 28679, + SPELL_SHADOW_BOLT_10 = 29317, + SPELL_SHADOW_BOLT_25 = 56405, + // Teleport spells + SPELL_TELEPORT_DEAD = 28025, + SPELL_TELEPORT_LIVE = 28026, + // Visual spells + SPELL_ANCHOR_1_TRAINEE = 27892, + SPELL_ANCHOR_1_DK = 27928, + SPELL_ANCHOR_1_RIDER = 27935, + SPELL_ANCHOR_2_TRAINEE = 27893, + SPELL_ANCHOR_2_DK = 27929, + SPELL_ANCHOR_2_RIDER = 27936, + SPELL_SKULLS_TRAINEE = 27915, + SPELL_SKULLS_DK = 27931, + SPELL_SKULLS_RIDER = 27937, + // Living trainee + SPELL_DEATH_PLAGUE = 55604, + // Dead trainee + SPELL_ARCANE_EXPLOSION = 27989, + // Living knight + SPELL_SHADOW_MARK = 27825, + // Dead knight + SPELL_WHIRLWIND = 56408, + // Living rider + SPELL_SHADOW_BOLT_VOLLEY = 27831, + // Dead rider + SPELL_DRAIN_LIFE = 27994, + SPELL_UNHOLY_FRENZY = 55648, + // Horse + SPELL_STOMP = 27993 +}; + +enum Misc +{ + NPC_LIVING_TRAINEE = 16124, + NPC_LIVING_KNIGHT = 16125, + NPC_LIVING_RIDER = 16126, + NPC_DEAD_TRAINEE = 16127, + NPC_DEAD_KNIGHT = 16148, + NPC_DEAD_HORSE = 16149, + NPC_DEAD_RIDER = 16150, + NPC_TRIGGER = 16137 +}; + +enum Events +{ + // Gothik + EVENT_SUMMON_ADDS = 1, + EVENT_HARVEST_SOUL = 2, + EVENT_SHADOW_BOLT = 3, + EVENT_TELEPORT = 4, + EVENT_CHECK_HEALTH = 5, + EVENT_CHECK_PLAYERS = 6, + // Living trainee + EVENT_DEATH_PLAGUE = 7, + // Dead trainee + EVENT_ARCANE_EXPLOSION = 8, + // Living knight + EVENT_SHADOW_MARK = 9, + // Dead knight + EVENT_WHIRLWIND = 10, + // Living rider + EVENT_SHADOW_BOLT_VOLLEY = 11, + // Dead rider + EVENT_DRAIN_LIFE = 12, + EVENT_UNHOLY_FRENZY = 13, + // HORSE + EVENT_STOMP = 14, + // Intro + EVENT_INTRO_2 = 15, + EVENT_INTRO_3 = 16, + EVENT_INTRO_4 = 17 +}; + +const uint32 gothikWaves[24][2] = +{ + {NPC_LIVING_TRAINEE, 20000}, + {NPC_LIVING_TRAINEE, 20000}, + {NPC_LIVING_TRAINEE, 10000}, + {NPC_LIVING_KNIGHT, 10000}, + {NPC_LIVING_TRAINEE, 15000}, + {NPC_LIVING_KNIGHT, 10000}, + {NPC_LIVING_TRAINEE, 15000}, + {NPC_LIVING_TRAINEE, 0}, + {NPC_LIVING_KNIGHT, 10000}, + {NPC_LIVING_RIDER, 10000}, + {NPC_LIVING_TRAINEE, 5000}, + {NPC_LIVING_KNIGHT, 15000}, + {NPC_LIVING_RIDER, 0}, + {NPC_LIVING_TRAINEE, 10000}, + {NPC_LIVING_KNIGHT, 10000}, + {NPC_LIVING_TRAINEE, 10000}, + {NPC_LIVING_RIDER, 5000}, + {NPC_LIVING_KNIGHT, 5000}, + {NPC_LIVING_TRAINEE, 20000}, + {NPC_LIVING_RIDER, 0}, + {NPC_LIVING_KNIGHT, 0}, + {NPC_LIVING_TRAINEE, 15000}, + {NPC_LIVING_TRAINEE, 29000}, + {0, 0} +}; + +const Position PosSummonLiving[6] = +{ + {2669.7f, -3428.76f, 268.56f, 1.6f}, + {2692.1f, -3428.76f, 268.56f, 1.6f}, + {2714.4f, -3428.76f, 268.56f, 1.6f}, + {2669.7f, -3431.67f, 268.56f, 1.6f}, + {2692.1f, -3431.67f, 268.56f, 1.6f}, + {2714.4f, -3431.67f, 268.56f, 1.6f} +}; + +const Position PosSummonDead[5] = +{ + {2725.1f, -3310.0f, 268.85f, 3.4f}, + {2699.3f, -3322.8f, 268.60f, 3.3f}, + {2733.1f, -3348.5f, 268.84f, 3.1f}, + {2682.8f, -3304.2f, 268.85f, 3.9f}, + {2664.8f, -3340.7f, 268.23f, 3.7f} +}; + +//const Position PosGroundLivingSide = {2691.2f, -3387.0f, 267.68f, 1.52f}; +//const Position PosGroundDeadSide = {2693.5f, -3334.6f, 267.68f, 4.67f}; +//const Position PosPlatform = {2640.5f, -3360.6f, 285.26f, 0.0f}; + +#define POS_Y_GATE -3360.78f +#define POS_Y_WEST -3285.0f +#define POS_Y_EAST -3434.0f +#define POS_X_NORTH 2750.49f +#define POS_X_SOUTH 2633.84f +#define IN_LIVE_SIDE(who) (who->GetPositionY() < POS_Y_GATE) + +// Predicate function to check that the r efzr unit is NOT on the same side as the source. +struct NotOnSameSide +{ +public: + explicit NotOnSameSide(Unit* pSource) : m_inLiveSide(IN_LIVE_SIDE(pSource)) { } + + bool operator() (Unit const* pTarget) + { + return (m_inLiveSide != IN_LIVE_SIDE(pTarget)); + } + +private: + bool m_inLiveSide; +}; + +class boss_gothik : public CreatureScript +{ +public: + boss_gothik() : CreatureScript("boss_gothik") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_gothikAI : public BossAI + { + explicit boss_gothikAI(Creature* c) : BossAI(c, BOSS_GOTHIK), summons(me) + { + pInstance = me->GetInstanceScript(); + } + EventMap events; + SummonList summons; + InstanceScript* pInstance; + bool secondPhase{}; + bool gateOpened{}; + uint8 waveCount{}; + + bool IsInRoom() + { + if (me->GetPositionX() > 2767 || me->GetPositionX() < 2618 || me->GetPositionY() > -3285 || me->GetPositionY() < -3435) + { + EnterEvadeMode(); + return false; + } + return true; + } + + void Reset() override + { + BossAI::Reset(); + events.Reset(); + summons.DespawnAll(); + me->RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE); + me->SetImmuneToPC(false); + me->SetReactState(REACT_PASSIVE); + secondPhase = false; + gateOpened = false; + waveCount = 0; + me->NearTeleportTo(2642.139f, -3386.959f, 285.492f, 6.265f); + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_ENTER_GATE))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_INNER_GATE))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_EXIT_GATE))) + { + go->SetGoState(GO_STATE_READY); + } + } + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + me->SetInCombatWithZone(); + Talk(SAY_INTRO_1); + events.ScheduleEvent(EVENT_INTRO_2, 4s); + events.ScheduleEvent(EVENT_INTRO_3, 9s); + events.ScheduleEvent(EVENT_INTRO_4, 14s); + me->SetUnitFlag(UNIT_FLAG_DISABLE_MOVE); + events.ScheduleEvent(EVENT_SUMMON_ADDS, 30s); + events.ScheduleEvent(EVENT_CHECK_PLAYERS, 2min); + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_ENTER_GATE))) + { + go->SetGoState(GO_STATE_READY); + } + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_INNER_GATE))) + { + go->SetGoState(GO_STATE_READY); + } + } + } + + void JustSummoned(Creature* summon) override + { + summons.Summon(summon); + // If central gate is open, attack any one + if (gateOpened) + { + if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, 200.0f)) + { + summon->AI()->AttackStart(target); + summon->SetInCombatWithZone(); + summon->SetReactState(REACT_AGGRESSIVE); + summon->CallForHelp(150.0f); + } + } + // Else look for a random target on the side the summoned NPC is + else + { + Map::PlayerList const& pList = me->GetMap()->GetPlayers(); + std::vector tList; + for(Map::PlayerList::const_iterator itr = pList.begin(); itr != pList.end(); ++itr) + { + if (!me->IsWithinDistInMap(itr->GetSource(), 200.0f, true, false) || !itr->GetSource()->IsAlive() || itr->GetSource()->IsGameMaster()) + { + continue; + } + if (IN_LIVE_SIDE(itr->GetSource()) != IN_LIVE_SIDE(summon)) + { + continue; + } + tList.push_back(itr->GetSource()); + } + if (!tList.empty()) + { + Player* target = tList[urand(0, tList.size() - 1)]; + summon->AI()->AttackStart(target); + summon->SetInCombatWithZone(); + summon->SetReactState(REACT_AGGRESSIVE); + summon->CallForHelp(150.0f); + } + } + } + + void SummonedCreatureDespawn(Creature* cr) override + { + summons.Despawn(cr); + } + + void KilledUnit(Unit* who) override + { + if (!who->IsPlayer()) + return; + + Talk(SAY_KILL); + if (pInstance) + { + pInstance->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + Talk(SAY_DEATH); + summons.DespawnAll(); + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_ENTER_GATE))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_INNER_GATE))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_EXIT_GATE))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + } + } + + void SummonHelpers(uint32 entry) + { + switch(entry) + { + case NPC_LIVING_TRAINEE: + me->SummonCreature(NPC_LIVING_TRAINEE, PosSummonLiving[0].GetPositionX(), PosSummonLiving[0].GetPositionY(), PosSummonLiving[0].GetPositionZ(), PosSummonLiving[0].GetOrientation()); + me->SummonCreature(NPC_LIVING_TRAINEE, PosSummonLiving[1].GetPositionX(), PosSummonLiving[1].GetPositionY(), PosSummonLiving[1].GetPositionZ(), PosSummonLiving[1].GetOrientation()); + if (Is25ManRaid()) + { + me->SummonCreature(NPC_LIVING_TRAINEE, PosSummonLiving[2].GetPositionX(), PosSummonLiving[2].GetPositionY(), PosSummonLiving[2].GetPositionZ(), PosSummonLiving[2].GetOrientation()); + } + break; + case NPC_LIVING_KNIGHT: + me->SummonCreature(NPC_LIVING_KNIGHT, PosSummonLiving[3].GetPositionX(), PosSummonLiving[3].GetPositionY(), PosSummonLiving[3].GetPositionZ(), PosSummonLiving[3].GetOrientation()); + if (Is25ManRaid()) + { + me->SummonCreature(NPC_LIVING_KNIGHT, PosSummonLiving[5].GetPositionX(), PosSummonLiving[5].GetPositionY(), PosSummonLiving[5].GetPositionZ(), PosSummonLiving[5].GetOrientation()); + } + break; + case NPC_LIVING_RIDER: + me->SummonCreature(NPC_LIVING_RIDER, PosSummonLiving[4].GetPositionX(), PosSummonLiving[4].GetPositionY(), PosSummonLiving[4].GetPositionZ(), PosSummonLiving[4].GetOrientation()); + break; + } + } + + bool CheckGroupSplitted() + { + Map::PlayerList const& PlayerList = me->GetMap()->GetPlayers(); + if (!PlayerList.IsEmpty()) + { + bool checklife = false; + bool checkdead = false; + for (auto const& i : PlayerList) + { + Player* player = i.GetSource(); + if (player->IsAlive() && + player->GetPositionX() <= POS_X_NORTH && + player->GetPositionX() >= POS_X_SOUTH && + player->GetPositionY() <= POS_Y_GATE && + player->GetPositionY() >= POS_Y_EAST) + { + checklife = true; + } + else if (player->IsAlive() && + player->GetPositionX() <= POS_X_NORTH && + player->GetPositionX() >= POS_X_SOUTH && + player->GetPositionY() >= POS_Y_GATE && + player->GetPositionY() <= POS_Y_WEST) + { + checkdead = true; + } + + if (checklife && checkdead) + return true; + } + } + return false; + } + + void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override + { + if (!secondPhase) + { + damage = 0; + } + } + + void UpdateAI(uint32 diff) override + { + if (!IsInRoom()) + return; + + if (!UpdateVictim()) + return; + + events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_INTRO_2: + Talk(SAY_INTRO_2); + break; + case EVENT_INTRO_3: + Talk(SAY_INTRO_3); + break; + case EVENT_INTRO_4: + Talk(SAY_INTRO_4); + break; + case EVENT_SHADOW_BOLT: + me->CastSpell(me->GetVictim(), RAID_MODE(SPELL_SHADOW_BOLT_10, SPELL_SHADOW_BOLT_25), false); + events.Repeat(1s); + break; + case EVENT_HARVEST_SOUL: + me->CastSpell(me, SPELL_HARVEST_SOUL, false); + events.Repeat(15s); + break; + case EVENT_TELEPORT: + me->AttackStop(); + if (IN_LIVE_SIDE(me)) + { + me->CastSpell(me, SPELL_TELEPORT_DEAD, false); + } + else + { + me->CastSpell(me, SPELL_TELEPORT_LIVE, false); + } + me->GetThreatMgr().resetAggro(NotOnSameSide(me)); + if (Unit* pTarget = SelectTarget(SelectTargetMethod::MaxDistance, 0)) + { + me->GetThreatMgr().AddThreat(pTarget, 100.0f); + AttackStart(pTarget); + } + events.Repeat(20s); + break; + case EVENT_CHECK_HEALTH: + if (me->HealthBelowPct(30) && pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_INNER_GATE))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + events.CancelEvent(EVENT_TELEPORT); + break; + } + events.Repeat(1s); + break; + case EVENT_SUMMON_ADDS: + if (gothikWaves[waveCount][0]) + { + SummonHelpers(gothikWaves[waveCount][0]); + events.RepeatEvent(gothikWaves[waveCount][1]); + } + else + { + secondPhase = true; + Talk(SAY_PHASE_TWO); + Talk(EMOTE_PHASE_TWO); + me->CastSpell(me, SPELL_TELEPORT_LIVE, false); + me->SetReactState(REACT_AGGRESSIVE); + me->RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE); + me->SetImmuneToPC(false); + me->RemoveAllAuras(); + events.ScheduleEvent(EVENT_SHADOW_BOLT, 1s); + events.ScheduleEvent(EVENT_HARVEST_SOUL, 5s, 15s); + events.ScheduleEvent(EVENT_TELEPORT, 20s); + events.ScheduleEvent(EVENT_CHECK_HEALTH, 1s); + } + waveCount++; + break; + case EVENT_CHECK_PLAYERS: + if (!CheckGroupSplitted()) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_GOTHIK_INNER_GATE))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + gateOpened = true; + Talk(EMOTE_GATE_OPENED); + } + break; + } + DoMeleeAttackIfReady(); + } + }; +}; + +class npc_boss_gothik_minion : public CreatureScript +{ +public: + npc_boss_gothik_minion() : CreatureScript("npc_boss_gothik_minion") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct npc_boss_gothik_minionAI : public CombatAI + { + explicit npc_boss_gothik_minionAI(Creature* c) : CombatAI(c) + { + livingSide = IN_LIVE_SIDE(me); + } + EventMap events; + bool livingSide; + bool IsOnSameSide(Unit const* who) const { return livingSide == IN_LIVE_SIDE(who); } + + void Reset() override + { + me->SetReactState(REACT_AGGRESSIVE); + me->SetNoCallAssistance(false); + events.Reset(); + } + + void JustEngagedWith(Unit* /*who*/) override + { + switch (me->GetEntry()) + { + case NPC_LIVING_TRAINEE: + events.ScheduleEvent(EVENT_DEATH_PLAGUE, 3s); + break; + case NPC_DEAD_TRAINEE: + events.ScheduleEvent(EVENT_ARCANE_EXPLOSION, 2500ms); + break; + case NPC_LIVING_KNIGHT: + events.ScheduleEvent(EVENT_SHADOW_MARK, 3s); + break; + case NPC_DEAD_KNIGHT: + events.ScheduleEvent(EVENT_WHIRLWIND, 2s); + break; + case NPC_LIVING_RIDER: + events.ScheduleEvent(EVENT_SHADOW_BOLT_VOLLEY, 3s); + break; + case NPC_DEAD_RIDER: + events.ScheduleEvent(EVENT_DRAIN_LIFE, 2000ms, 3500ms); + events.ScheduleEvent(EVENT_UNHOLY_FRENZY, 5s, 9s); + break; + case NPC_DEAD_HORSE: + events.ScheduleEvent(EVENT_STOMP, 2s, 5s); + break; + } + } + + void JustDied(Unit*) override + { + switch (me->GetEntry()) + { + case NPC_LIVING_TRAINEE: + DoCastAOE(SPELL_ANCHOR_1_TRAINEE, true); + break; + case NPC_LIVING_KNIGHT: + DoCastAOE(SPELL_ANCHOR_1_DK, true); + break; + case NPC_LIVING_RIDER: + DoCastAOE(SPELL_ANCHOR_1_RIDER, true); + break; + } + } + + void KilledUnit(Unit* who) override + { + if (who->IsPlayer() && me->GetInstanceScript()) + { + me->GetInstanceScript()->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void UpdateAI(uint32 diff) override + { + events.Update(diff); + if (me->GetUnitState() == UNIT_STATE_CASTING) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_DEATH_PLAGUE: + me->CastSpell(me->GetVictim(), SPELL_DEATH_PLAGUE, false); + events.Repeat(4s, 7s); + break; + case EVENT_ARCANE_EXPLOSION: + if (Unit* victim = me->GetVictim()) + { + if (victim->IsWithinDist(me, 20)) + { + me->CastSpell(victim, SPELL_ARCANE_EXPLOSION, false); + } + } + events.Repeat(2500ms); + break; + case EVENT_SHADOW_MARK: + if (Unit* victim = me->GetVictim()) + { + if (victim->IsWithinDist(me, 10)) + { + me->CastSpell(victim, SPELL_SHADOW_MARK, false); + } + } + events.Repeat(5s, 7s); + break; + case EVENT_WHIRLWIND: + if (Unit* victim = me->GetVictim()) + { + if (victim->IsWithinDist(me, 10)) + { + me->CastSpell(victim, SPELL_WHIRLWIND, false); + } + } + events.Repeat(4s, 6s); + break; + case EVENT_SHADOW_BOLT_VOLLEY: + me->CastSpell(me->GetVictim(), SPELL_SHADOW_BOLT_VOLLEY, false); + events.Repeat(5s); + break; + case EVENT_DRAIN_LIFE: + if (Unit* victim = me->GetVictim()) + { + if (victim->IsWithinDist(me, 20)) + { + me->CastSpell(victim, SPELL_DRAIN_LIFE, false); + } + } + events.Repeat(8s, 12s); + break; + case EVENT_UNHOLY_FRENZY: + me->AddAura(SPELL_UNHOLY_FRENZY, me); + events.Repeat(15s, 17s); + break; + case EVENT_STOMP: + if (Unit* victim = me->GetVictim()) + { + if (victim->IsWithinDist(me, 10)) + { + me->CastSpell(victim, SPELL_STOMP, false); + } + } + events.Repeat(4s, 9s); + break; + } + DoMeleeAttackIfReady(); + } + }; +}; + +class npc_gothik_trigger : public CreatureScript +{ +public: + npc_gothik_trigger() : CreatureScript("npc_gothik_trigger") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_gothik_triggerAI(creature); + } + + struct npc_gothik_triggerAI : public ScriptedAI + { + npc_gothik_triggerAI(Creature* creature) : ScriptedAI(creature) { creature->SetDisableGravity(true); } + + void EnterEvadeMode(EvadeReason /*why*/) override {} + void UpdateAI(uint32 /*diff*/) override {} + void JustEngagedWith(Unit* /*who*/) override {} + void DamageTaken(Unit* /*who*/, uint32& damage, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/) override { damage = 0; } + + Creature* SelectRandomSkullPile() + { + std::list triggers; + me->GetCreatureListWithEntryInGrid(triggers, NPC_TRIGGER, 150.0f); + // Remove triggers that are on live side or soul triggers on the platform + triggers.remove_if([](Creature* trigger){ + return ((trigger->GetPositionY() < POS_Y_GATE) || (trigger->GetPositionZ() > 280.0f)); + }); + if (!triggers.empty()) + { + std::list::iterator itr = triggers.begin(); + std::advance(itr, urand(0, triggers.size() - 1)); + return *itr; + } + return nullptr; + } + + void SpellHit(Unit* /*caster*/, SpellInfo const* spell) override + { + if (!spell) + { + return; + } + + switch (spell->Id) + { + case SPELL_ANCHOR_1_TRAINEE: + DoCastAOE(SPELL_ANCHOR_2_TRAINEE, true); + break; + case SPELL_ANCHOR_1_DK: + DoCastAOE(SPELL_ANCHOR_2_DK, true); + break; + case SPELL_ANCHOR_1_RIDER: + DoCastAOE(SPELL_ANCHOR_2_RIDER, true); + break; + case SPELL_ANCHOR_2_TRAINEE: + if (Creature* target = SelectRandomSkullPile()) + { + DoCast(target, SPELL_SKULLS_TRAINEE, true); + } + break; + case SPELL_ANCHOR_2_DK: + if (Creature* target = SelectRandomSkullPile()) + { + DoCast(target, SPELL_SKULLS_DK, true); + } + break; + case SPELL_ANCHOR_2_RIDER: + if (Creature* target = SelectRandomSkullPile()) + { + DoCast(target, SPELL_SKULLS_RIDER, true); + } + break; + case SPELL_SKULLS_TRAINEE: + DoSummon(NPC_DEAD_TRAINEE, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); + break; + case SPELL_SKULLS_DK: + DoSummon(NPC_DEAD_KNIGHT, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); + break; + case SPELL_SKULLS_RIDER: + DoSummon(NPC_DEAD_RIDER, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); + DoSummon(NPC_DEAD_HORSE, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN); + break; + } + } + + // dead side summons are "owned" by gothik + void JustSummoned(Creature* summon) override + { + if (Creature* gothik = ObjectAccessor::GetCreature(*me, me->GetInstanceScript()->GetGuidData(DATA_GOTHIK_BOSS))) + { + gothik->AI()->JustSummoned(summon); + } + } + void SummonedCreatureDespawn(Creature* summon) override + { + if (Creature* gothik = ObjectAccessor::GetCreature(*me, me->GetInstanceScript()->GetGuidData(DATA_GOTHIK_BOSS))) + { + gothik->AI()->SummonedCreatureDespawn(summon); + } + } + }; +}; + +class spell_gothik_shadow_bolt_volley : public SpellScript +{ + PrepareSpellScript(spell_gothik_shadow_bolt_volley); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHADOW_MARK }); + } + + void FilterTargets(std::list& targets) + { + targets.remove_if(Acore::UnitAuraCheck(false, SPELL_SHADOW_MARK)); + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_gothik_shadow_bolt_volley::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY); + } +}; + +} + +#endif \ No newline at end of file diff --git a/src/server/scripts/Northrend/Naxxramas/boss_grobbulus.cpp b/src/server/scripts/Northrend/Naxxramas/boss_grobbulus.cpp index 39a84201ea5629..6e667d96adffc8 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_grobbulus.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_grobbulus.cpp @@ -15,6 +15,7 @@ * with this program. If not, see . */ +#include "boss_grobbulus.h" #include "CreatureScript.h" #include "PassiveAI.h" #include "ScriptedCreature.h" @@ -24,279 +25,7 @@ #include "SpellScriptLoader.h" #include "naxxramas.h" -enum Spells -{ - SPELL_POISON_CLOUD = 28240, - SPELL_MUTATING_INJECTION = 28169, - SPELL_MUTATING_EXPLOSION = 28206, - SPELL_SLIME_SPRAY_10 = 28157, - SPELL_SLIME_SPRAY_25 = 54364, - SPELL_POISON_CLOUD_DAMAGE_AURA_10 = 28158, - SPELL_POISON_CLOUD_DAMAGE_AURA_25 = 54362, - SPELL_BERSERK = 26662, - SPELL_BOMBARD_SLIME = 28280 -}; - -enum Emotes -{ - EMOTE_SLIME = 0 -}; - -enum Events -{ - EVENT_BERSERK = 1, - EVENT_POISON_CLOUD = 2, - EVENT_SLIME_SPRAY = 3, - EVENT_MUTATING_INJECTION = 4 -}; - -enum Misc -{ - NPC_FALLOUT_SLIME = 16290, - NPC_SEWAGE_SLIME = 16375, - NPC_STICHED_GIANT = 16025 -}; - -class boss_grobbulus : public CreatureScript -{ -public: - boss_grobbulus() : CreatureScript("boss_grobbulus") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_grobbulusAI : public BossAI - { - explicit boss_grobbulusAI(Creature* c) : BossAI(c, BOSS_GROBBULUS), summons(me) - { - pInstance = me->GetInstanceScript(); - } - - EventMap events; - SummonList summons; - InstanceScript* pInstance; - uint32 dropSludgeTimer{}; - - void Reset() override - { - BossAI::Reset(); - events.Reset(); - summons.DespawnAll(); - dropSludgeTimer = 0; - } - - void PullChamberAdds() - { - std::list StichedGiants; - me->GetCreaturesWithEntryInRange(StichedGiants, 300.0f, NPC_STICHED_GIANT); - for (std::list::const_iterator itr = StichedGiants.begin(); itr != StichedGiants.end(); ++itr) - { - (*itr)->ToCreature()->AI()->AttackStart(me->GetVictim()); - } - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - PullChamberAdds(); - me->SetInCombatWithZone(); - events.ScheduleEvent(EVENT_POISON_CLOUD, 15s); - events.ScheduleEvent(EVENT_MUTATING_INJECTION, 20s); - events.ScheduleEvent(EVENT_SLIME_SPRAY, 10s); - events.ScheduleEvent(EVENT_BERSERK, RAID_MODE(720000, 540000)); - } - - void SpellHitTarget(Unit* target, SpellInfo const* spellInfo) override - { - if (spellInfo->Id == RAID_MODE(SPELL_SLIME_SPRAY_10, SPELL_SLIME_SPRAY_25) && target->IsPlayer()) - { - me->SummonCreature(NPC_FALLOUT_SLIME, target->GetPositionX(), target->GetPositionY(), target->GetPositionZ()); - } - } - - void JustSummoned(Creature* cr) override - { - if (cr->GetEntry() == NPC_FALLOUT_SLIME) - { - cr->SetInCombatWithZone(); - } - summons.Summon(cr); - } - - void SummonedCreatureDespawn(Creature* summon) override - { - summons.Despawn(summon); - } - - void JustDied(Unit* killer) override - { - BossAI::JustDied(killer); - summons.DespawnAll(); - } - - void KilledUnit(Unit* who) override - { - if (who->IsPlayer() && pInstance) - { - pInstance->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void UpdateAI(uint32 diff) override - { - dropSludgeTimer += diff; - if (!me->IsInCombat() && dropSludgeTimer >= 5000) - { - if (me->IsWithinDist3d(3178, -3305, 319, 5.0f) && !summons.HasEntry(NPC_SEWAGE_SLIME)) - { - me->CastSpell(3128.96f + irand(-20, 20), -3312.96f + irand(-20, 20), 293.25f, SPELL_BOMBARD_SLIME, false); - } - dropSludgeTimer = 0; - } - - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_POISON_CLOUD: - me->CastSpell(me, SPELL_POISON_CLOUD, true); - events.Repeat(15s); - break; - case EVENT_BERSERK: - me->CastSpell(me, SPELL_BERSERK, true); - break; - case EVENT_SLIME_SPRAY: - Talk(EMOTE_SLIME); - me->CastSpell(me->GetVictim(), RAID_MODE(SPELL_SLIME_SPRAY_10, SPELL_SLIME_SPRAY_25), false); - events.Repeat(20s); - break; - case EVENT_MUTATING_INJECTION: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 100.0f, true, true, -SPELL_MUTATING_INJECTION)) - { - me->CastSpell(target, SPELL_MUTATING_INJECTION, false); - } - events.RepeatEvent(6000 + uint32(120 * me->GetHealthPct())); - break; - } - DoMeleeAttackIfReady(); - } - }; -}; - -class boss_grobbulus_poison_cloud : public CreatureScript -{ -public: - boss_grobbulus_poison_cloud() : CreatureScript("boss_grobbulus_poison_cloud") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_grobbulus_poison_cloudAI : public NullCreatureAI - { - explicit boss_grobbulus_poison_cloudAI(Creature* pCreature) : NullCreatureAI(pCreature) { } - - uint32 sizeTimer{}; - uint32 auraVisualTimer{}; - - void Reset() override - { - sizeTimer = 0; - auraVisualTimer = 1; - me->SetFloatValue(UNIT_FIELD_COMBATREACH, 2.0f); - me->SetFaction(FACTION_BOOTY_BAY); - } - - void KilledUnit(Unit* who) override - { - if (who->IsPlayer() && me->GetInstanceScript()) - { - me->GetInstanceScript()->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void UpdateAI(uint32 diff) override - { - if (auraVisualTimer) // this has to be delayed to be visible - { - auraVisualTimer += diff; - if (auraVisualTimer >= 1000) - { - me->CastSpell(me, (me->GetMap()->Is25ManRaid() ? SPELL_POISON_CLOUD_DAMAGE_AURA_25 : SPELL_POISON_CLOUD_DAMAGE_AURA_10), true); - auraVisualTimer = 0; - } - } - sizeTimer += diff; // increase size to 15yd in 60 seconds, 0.00025 is the growth of size in 1ms - me->SetFloatValue(UNIT_FIELD_COMBATREACH, 2.0f + (0.00025f * sizeTimer)); - } - }; -}; - -class spell_grobbulus_poison : public SpellScript -{ - PrepareSpellScript(spell_grobbulus_poison); - - void FilterTargets(std::list& targets) - { - std::list tmplist; - for (auto& target : targets) - { - if (GetCaster()->IsWithinDist3d(target, 0.0f)) - { - tmplist.push_back(target); - } - } - targets.clear(); - for (auto& itr : tmplist) - { - targets.push_back(itr); - } - } - - void Register() override - { - OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_grobbulus_poison::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY); - } -}; - -class spell_grobbulus_mutating_injection_aura : public AuraScript -{ - PrepareAuraScript(spell_grobbulus_mutating_injection_aura); - - bool Validate(SpellInfo const* /*spellInfo*/) override - { - return ValidateSpellInfo({ SPELL_MUTATING_EXPLOSION }); - } - - void HandleRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) - { - switch (GetTargetApplication()->GetRemoveMode()) - { - case AURA_REMOVE_BY_ENEMY_SPELL: - case AURA_REMOVE_BY_EXPIRE: - if (auto caster = GetCaster()) - { - caster->CastSpell(GetTarget(), SPELL_MUTATING_EXPLOSION, true); - } - break; - default: - return; - } - } - - void Register() override - { - AfterEffectRemove += AuraEffectRemoveFn(spell_grobbulus_mutating_injection_aura::HandleRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL); - } -}; +using namespace Grobbulus; void AddSC_boss_grobbulus() { diff --git a/src/server/scripts/Northrend/Naxxramas/boss_grobbulus.h b/src/server/scripts/Northrend/Naxxramas/boss_grobbulus.h new file mode 100644 index 00000000000000..0a31df559fdab2 --- /dev/null +++ b/src/server/scripts/Northrend/Naxxramas/boss_grobbulus.h @@ -0,0 +1,291 @@ +#ifndef BOSSGROBBULUS_H_ +#define BOSSGROBBULUS_H_ + +#include "PassiveAI.h" +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "SpellAuraEffects.h" +#include "SpellAuras.h" +#include "SpellScript.h" +#include "SpellInfo.h" +#include "naxxramas.h" + +namespace Grobbulus { + +enum Spells +{ + SPELL_POISON_CLOUD = 28240, + SPELL_MUTATING_INJECTION = 28169, + SPELL_MUTATING_EXPLOSION = 28206, + SPELL_SLIME_SPRAY_10 = 28157, + SPELL_SLIME_SPRAY_25 = 54364, + SPELL_POISON_CLOUD_DAMAGE_AURA_10 = 28158, + SPELL_POISON_CLOUD_DAMAGE_AURA_25 = 54362, + SPELL_BERSERK = 26662, + SPELL_BOMBARD_SLIME = 28280 +}; + +enum Emotes +{ + EMOTE_SLIME = 0 +}; + +enum Events +{ + EVENT_BERSERK = 1, + EVENT_POISON_CLOUD = 2, + EVENT_SLIME_SPRAY = 3, + EVENT_MUTATING_INJECTION = 4 +}; + +enum Misc +{ + NPC_FALLOUT_SLIME = 16290, + NPC_SEWAGE_SLIME = 16375, + NPC_STICHED_GIANT = 16025 +}; + +class boss_grobbulus : public CreatureScript +{ +public: + boss_grobbulus() : CreatureScript("boss_grobbulus") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_grobbulusAI : public BossAI + { + explicit boss_grobbulusAI(Creature* c) : BossAI(c, BOSS_GROBBULUS), summons(me) + { + pInstance = me->GetInstanceScript(); + } + + EventMap events; + SummonList summons; + InstanceScript* pInstance; + uint32 dropSludgeTimer{}; + + void Reset() override + { + BossAI::Reset(); + events.Reset(); + summons.DespawnAll(); + dropSludgeTimer = 0; + } + + void PullChamberAdds() + { + std::list StichedGiants; + me->GetCreaturesWithEntryInRange(StichedGiants, 300.0f, NPC_STICHED_GIANT); + for (std::list::const_iterator itr = StichedGiants.begin(); itr != StichedGiants.end(); ++itr) + { + (*itr)->ToCreature()->AI()->AttackStart(me->GetVictim()); + } + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + PullChamberAdds(); + me->SetInCombatWithZone(); + events.ScheduleEvent(EVENT_POISON_CLOUD, 15s); + events.ScheduleEvent(EVENT_MUTATING_INJECTION, 20s); + events.ScheduleEvent(EVENT_SLIME_SPRAY, 10s); + events.ScheduleEvent(EVENT_BERSERK, RAID_MODE(720000, 540000)); + } + + void SpellHitTarget(Unit* target, SpellInfo const* spellInfo) override + { + if (spellInfo->Id == RAID_MODE(SPELL_SLIME_SPRAY_10, SPELL_SLIME_SPRAY_25) && target->IsPlayer()) + { + me->SummonCreature(NPC_FALLOUT_SLIME, target->GetPositionX(), target->GetPositionY(), target->GetPositionZ()); + } + } + + void JustSummoned(Creature* cr) override + { + if (cr->GetEntry() == NPC_FALLOUT_SLIME) + { + cr->SetInCombatWithZone(); + } + summons.Summon(cr); + } + + void SummonedCreatureDespawn(Creature* summon) override + { + summons.Despawn(summon); + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + summons.DespawnAll(); + } + + void KilledUnit(Unit* who) override + { + if (who->IsPlayer() && pInstance) + { + pInstance->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void UpdateAI(uint32 diff) override + { + dropSludgeTimer += diff; + if (!me->IsInCombat() && dropSludgeTimer >= 5000) + { + if (me->IsWithinDist3d(3178, -3305, 319, 5.0f) && !summons.HasEntry(NPC_SEWAGE_SLIME)) + { + me->CastSpell(3128.96f + irand(-20, 20), -3312.96f + irand(-20, 20), 293.25f, SPELL_BOMBARD_SLIME, false); + } + dropSludgeTimer = 0; + } + + if (!UpdateVictim()) + return; + + events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_POISON_CLOUD: + me->CastSpell(me, SPELL_POISON_CLOUD, true); + events.Repeat(15s); + break; + case EVENT_BERSERK: + me->CastSpell(me, SPELL_BERSERK, true); + break; + case EVENT_SLIME_SPRAY: + Talk(EMOTE_SLIME); + me->CastSpell(me->GetVictim(), RAID_MODE(SPELL_SLIME_SPRAY_10, SPELL_SLIME_SPRAY_25), false); + events.Repeat(20s); + break; + case EVENT_MUTATING_INJECTION: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 100.0f, true, true, -SPELL_MUTATING_INJECTION)) + { + me->CastSpell(target, SPELL_MUTATING_INJECTION, false); + } + events.RepeatEvent(6000 + uint32(120 * me->GetHealthPct())); + break; + } + DoMeleeAttackIfReady(); + } + }; +}; + +class boss_grobbulus_poison_cloud : public CreatureScript +{ +public: + boss_grobbulus_poison_cloud() : CreatureScript("boss_grobbulus_poison_cloud") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_grobbulus_poison_cloudAI : public NullCreatureAI + { + explicit boss_grobbulus_poison_cloudAI(Creature* pCreature) : NullCreatureAI(pCreature) { } + + uint32 sizeTimer{}; + uint32 auraVisualTimer{}; + + void Reset() override + { + sizeTimer = 0; + auraVisualTimer = 1; + me->SetFloatValue(UNIT_FIELD_COMBATREACH, 2.0f); + me->SetFaction(FACTION_BOOTY_BAY); + } + + void KilledUnit(Unit* who) override + { + if (who->IsPlayer() && me->GetInstanceScript()) + { + me->GetInstanceScript()->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void UpdateAI(uint32 diff) override + { + if (auraVisualTimer) // this has to be delayed to be visible + { + auraVisualTimer += diff; + if (auraVisualTimer >= 1000) + { + me->CastSpell(me, (me->GetMap()->Is25ManRaid() ? SPELL_POISON_CLOUD_DAMAGE_AURA_25 : SPELL_POISON_CLOUD_DAMAGE_AURA_10), true); + auraVisualTimer = 0; + } + } + sizeTimer += diff; // increase size to 15yd in 60 seconds, 0.00025 is the growth of size in 1ms + me->SetFloatValue(UNIT_FIELD_COMBATREACH, 2.0f + (0.00025f * sizeTimer)); + } + }; +}; + +class spell_grobbulus_poison : public SpellScript +{ + PrepareSpellScript(spell_grobbulus_poison); + + void FilterTargets(std::list& targets) + { + std::list tmplist; + for (auto& target : targets) + { + if (GetCaster()->IsWithinDist3d(target, 0.0f)) + { + tmplist.push_back(target); + } + } + targets.clear(); + for (auto& itr : tmplist) + { + targets.push_back(itr); + } + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_grobbulus_poison::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY); + } +}; + +class spell_grobbulus_mutating_injection_aura : public AuraScript +{ + PrepareAuraScript(spell_grobbulus_mutating_injection_aura); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_MUTATING_EXPLOSION }); + } + + void HandleRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + switch (GetTargetApplication()->GetRemoveMode()) + { + case AURA_REMOVE_BY_ENEMY_SPELL: + case AURA_REMOVE_BY_EXPIRE: + if (auto caster = GetCaster()) + { + caster->CastSpell(GetTarget(), SPELL_MUTATING_EXPLOSION, true); + } + break; + default: + return; + } + } + + void Register() override + { + AfterEffectRemove += AuraEffectRemoveFn(spell_grobbulus_mutating_injection_aura::HandleRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL); + } +}; + +} + +#endif \ No newline at end of file diff --git a/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp b/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp index c91f67ba73022e..9b9891c7fe6416 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp @@ -20,233 +20,12 @@ #include "ScriptedCreature.h" #include "naxxramas.h" -enum Says -{ - SAY_AGGRO = 0, - SAY_SLAY = 1, - SAY_TAUNT = 2, - EMOTE_DEATH = 3, - EMOTE_DANCE = 4, - EMOTE_DANCE_END = 5, - SAY_DANCE = 6 -}; - -enum Spells -{ - SPELL_SPELL_DISRUPTION = 29310, - SPELL_DECREPIT_FEVER_10 = 29998, - SPELL_DECREPIT_FEVER_25 = 55011, - SPELL_PLAGUE_CLOUD = 29350, - SPELL_TELEPORT_SELF = 30211 -}; - -enum Events -{ - EVENT_DISRUPTION = 1, - EVENT_DECEPIT_FEVER = 2, - EVENT_ERUPT_SECTION = 3, - EVENT_SWITCH_PHASE = 4, - EVENT_SAFETY_DANCE = 5, - EVENT_PLAGUE_CLOUD = 6 -}; - -enum Misc -{ - PHASE_SLOW_DANCE = 0, - PHASE_FAST_DANCE = 1 -}; - -class boss_heigan : public CreatureScript -{ -public: - boss_heigan() : CreatureScript("boss_heigan") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_heiganAI : public BossAI - { - explicit boss_heiganAI(Creature* c) : BossAI(c, BOSS_HEIGAN) - { - pInstance = me->GetInstanceScript(); - } - - InstanceScript* pInstance; - EventMap events; - uint8 currentPhase{}; - uint8 currentSection{}; - bool moveRight{}; - - void Reset() override - { - BossAI::Reset(); - events.Reset(); - currentPhase = 0; - currentSection = 3; - moveRight = true; - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_HEIGAN_ENTER_GATE))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - } - } - - void KilledUnit(Unit* who) override - { - if (!who->IsPlayer()) - return; - - Talk(SAY_SLAY); - if (pInstance) - { - pInstance->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void JustDied(Unit* killer) override - { - BossAI::JustDied(killer); - Talk(EMOTE_DEATH); - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - me->SetInCombatWithZone(); - Talk(SAY_AGGRO); - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_HEIGAN_ENTER_GATE))) - { - go->SetGoState(GO_STATE_READY); - } - } - StartFightPhase(PHASE_SLOW_DANCE); - } - - void StartFightPhase(uint8 phase) - { - currentSection = 3; - currentPhase = phase; - events.Reset(); - if (phase == PHASE_SLOW_DANCE) - { - me->CastStop(); - me->SetReactState(REACT_AGGRESSIVE); - DoZoneInCombat(); - events.ScheduleEvent(EVENT_DISRUPTION, 12s, 15s); - events.ScheduleEvent(EVENT_DECEPIT_FEVER, 17s); - events.ScheduleEvent(EVENT_ERUPT_SECTION, 15s); - events.ScheduleEvent(EVENT_SWITCH_PHASE, 90s); - } - else // if (phase == PHASE_FAST_DANCE) - { - Talk(EMOTE_DANCE); - Talk(SAY_DANCE); - me->AttackStop(); - me->StopMoving(); - me->SetReactState(REACT_PASSIVE); - me->CastSpell(me, SPELL_TELEPORT_SELF, false); - me->SetFacingTo(2.40f); - events.ScheduleEvent(EVENT_PLAGUE_CLOUD, 1s); - events.ScheduleEvent(EVENT_ERUPT_SECTION, 7s); - events.ScheduleEvent(EVENT_SWITCH_PHASE, 45s); - } - events.ScheduleEvent(EVENT_SAFETY_DANCE, 5s); - } - - bool IsInRoom(Unit* who) - { - if (who->GetPositionX() > 2826 || who->GetPositionX() < 2723 || who->GetPositionY() > -3641 || who->GetPositionY() < -3736) - { - if (who->GetGUID() == me->GetGUID()) - EnterEvadeMode(); - - return false; - } - return true; - } - - void UpdateAI(uint32 diff) override - { - if (!IsInRoom(me)) - return; - - if (!UpdateVictim()) - return; +#include "boss_heigan.h" - events.Update(diff); - switch (events.ExecuteEvent()) - { - case EVENT_DISRUPTION: - me->CastSpell(me, SPELL_SPELL_DISRUPTION, false); - events.Repeat(10s); - break; - case EVENT_DECEPIT_FEVER: - me->CastSpell(me, RAID_MODE(SPELL_DECREPIT_FEVER_10, SPELL_DECREPIT_FEVER_25), false); - events.Repeat(22s, 25s); - break; - case EVENT_PLAGUE_CLOUD: - me->CastSpell(me, SPELL_PLAGUE_CLOUD, false); - break; - case EVENT_SWITCH_PHASE: - if (currentPhase == PHASE_SLOW_DANCE) - { - StartFightPhase(PHASE_FAST_DANCE); - } - else - { - StartFightPhase(PHASE_SLOW_DANCE); - Talk(EMOTE_DANCE_END); // avoid play the emote on aggro - } - break; - case EVENT_ERUPT_SECTION: - if (pInstance) - { - pInstance->SetData(DATA_HEIGAN_ERUPTION, currentSection); - if (currentSection == 3) - { - moveRight = false; - } - else if (currentSection == 0) - { - moveRight = true; - } - moveRight ? currentSection++ : currentSection--; - } - if (currentPhase == PHASE_SLOW_DANCE) - { - Talk(SAY_TAUNT); - } - events.Repeat(currentPhase == PHASE_SLOW_DANCE ? 10s : 4s); - break; - case EVENT_SAFETY_DANCE: - { - Map::PlayerList const& pList = me->GetMap()->GetPlayers(); - for (auto const& itr : pList) - { - if (IsInRoom(itr.GetSource()) && !itr.GetSource()->IsAlive()) - { - pInstance->SetData(DATA_DANCE_FAIL, 0); - pInstance->SetData(DATA_IMMORTAL_FAIL, 0); - return; - } - } - events.Repeat(5s); - return; - } - } - DoMeleeAttackIfReady(); - } - }; -}; +using namespace Heigan; void AddSC_boss_heigan() { new boss_heigan(); -} +} \ No newline at end of file diff --git a/src/server/scripts/Northrend/Naxxramas/boss_heigan.h b/src/server/scripts/Northrend/Naxxramas/boss_heigan.h new file mode 100644 index 00000000000000..2850a10f53edac --- /dev/null +++ b/src/server/scripts/Northrend/Naxxramas/boss_heigan.h @@ -0,0 +1,239 @@ +#ifndef BOSS_HEIGAN_H_ +#define BOSS_HEIGAN_H_ + +#include "Player.h" +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "SpellInfo.h" +#include "naxxramas.h" + +namespace Heigan { + +enum Says +{ + SAY_AGGRO = 0, + SAY_SLAY = 1, + SAY_TAUNT = 2, + EMOTE_DEATH = 3, + EMOTE_DANCE = 4, + EMOTE_DANCE_END = 5, + SAY_DANCE = 6 +}; + +enum Spells +{ + SPELL_SPELL_DISRUPTION = 29310, + SPELL_DECREPIT_FEVER_10 = 29998, + SPELL_DECREPIT_FEVER_25 = 55011, + SPELL_PLAGUE_CLOUD = 29350, + SPELL_TELEPORT_SELF = 30211 +}; + +enum Events +{ + EVENT_DISRUPTION = 1, + EVENT_DECEPIT_FEVER = 2, + EVENT_ERUPT_SECTION = 3, + EVENT_SWITCH_PHASE = 4, + EVENT_SAFETY_DANCE = 5, + EVENT_PLAGUE_CLOUD = 6 +}; + +enum Misc +{ + PHASE_SLOW_DANCE = 0, + PHASE_FAST_DANCE = 1 +}; + +class boss_heigan : public CreatureScript +{ +public: + boss_heigan() : CreatureScript("boss_heigan") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_heiganAI : public BossAI + { + explicit boss_heiganAI(Creature* c) : BossAI(c, BOSS_HEIGAN) + { + pInstance = me->GetInstanceScript(); + } + + InstanceScript* pInstance; + EventMap events; + uint8 currentPhase{}; + uint8 currentSection{}; + bool moveRight{}; + + void Reset() override + { + BossAI::Reset(); + events.Reset(); + currentPhase = 0; + currentSection = 3; + moveRight = true; + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_HEIGAN_ENTER_GATE))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + } + } + + void KilledUnit(Unit* who) override + { + if (!who->IsPlayer()) + return; + + Talk(SAY_SLAY); + if (pInstance) + { + pInstance->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + Talk(EMOTE_DEATH); + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + me->SetInCombatWithZone(); + Talk(SAY_AGGRO); + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_HEIGAN_ENTER_GATE))) + { + go->SetGoState(GO_STATE_READY); + } + } + StartFightPhase(PHASE_SLOW_DANCE); + } + + void StartFightPhase(uint8 phase) + { + currentSection = 3; + currentPhase = phase; + events.Reset(); + if (phase == PHASE_SLOW_DANCE) + { + me->CastStop(); + me->SetReactState(REACT_AGGRESSIVE); + DoZoneInCombat(); + events.ScheduleEvent(EVENT_DISRUPTION, 12s, 15s); + events.ScheduleEvent(EVENT_DECEPIT_FEVER, 17s); + events.ScheduleEvent(EVENT_ERUPT_SECTION, 15s); + events.ScheduleEvent(EVENT_SWITCH_PHASE, 90s); + } + else // if (phase == PHASE_FAST_DANCE) + { + Talk(EMOTE_DANCE); + Talk(SAY_DANCE); + me->AttackStop(); + me->StopMoving(); + me->SetReactState(REACT_PASSIVE); + me->CastSpell(me, SPELL_TELEPORT_SELF, false); + me->SetFacingTo(2.40f); + events.ScheduleEvent(EVENT_PLAGUE_CLOUD, 1s); + events.ScheduleEvent(EVENT_ERUPT_SECTION, 7s); + events.ScheduleEvent(EVENT_SWITCH_PHASE, 45s); + } + events.ScheduleEvent(EVENT_SAFETY_DANCE, 5s); + } + + bool IsInRoom(Unit* who) + { + if (who->GetPositionX() > 2826 || who->GetPositionX() < 2723 || who->GetPositionY() > -3641 || who->GetPositionY() < -3736) + { + if (who->GetGUID() == me->GetGUID()) + EnterEvadeMode(); + + return false; + } + return true; + } + + void UpdateAI(uint32 diff) override + { + if (!IsInRoom(me)) + return; + + if (!UpdateVictim()) + return; + + events.Update(diff); + + switch (events.ExecuteEvent()) + { + case EVENT_DISRUPTION: + me->CastSpell(me, SPELL_SPELL_DISRUPTION, false); + events.Repeat(10s); + break; + case EVENT_DECEPIT_FEVER: + me->CastSpell(me, RAID_MODE(SPELL_DECREPIT_FEVER_10, SPELL_DECREPIT_FEVER_25), false); + events.Repeat(22s, 25s); + break; + case EVENT_PLAGUE_CLOUD: + me->CastSpell(me, SPELL_PLAGUE_CLOUD, false); + break; + case EVENT_SWITCH_PHASE: + if (currentPhase == PHASE_SLOW_DANCE) + { + StartFightPhase(PHASE_FAST_DANCE); + } + else + { + StartFightPhase(PHASE_SLOW_DANCE); + Talk(EMOTE_DANCE_END); // avoid play the emote on aggro + } + break; + case EVENT_ERUPT_SECTION: + if (pInstance) + { + pInstance->SetData(DATA_HEIGAN_ERUPTION, currentSection); + if (currentSection == 3) + { + moveRight = false; + } + else if (currentSection == 0) + { + moveRight = true; + } + moveRight ? currentSection++ : currentSection--; + } + if (currentPhase == PHASE_SLOW_DANCE) + { + Talk(SAY_TAUNT); + } + events.Repeat(currentPhase == PHASE_SLOW_DANCE ? 10s : 4s); + break; + case EVENT_SAFETY_DANCE: + { + Map::PlayerList const& pList = me->GetMap()->GetPlayers(); + for (auto const& itr : pList) + { + if (IsInRoom(itr.GetSource()) && !itr.GetSource()->IsAlive()) + { + pInstance->SetData(DATA_DANCE_FAIL, 0); + pInstance->SetData(DATA_IMMORTAL_FAIL, 0); + return; + } + } + events.Repeat(5s); + return; + } + } + DoMeleeAttackIfReady(); + } + }; +}; + +} +#endif \ No newline at end of file diff --git a/src/server/scripts/Northrend/Naxxramas/boss_kelthuzad.cpp b/src/server/scripts/Northrend/Naxxramas/boss_kelthuzad.cpp index 72588d466ffa1d..c5e5d13f84343c 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_kelthuzad.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_kelthuzad.cpp @@ -15,6 +15,7 @@ * with this program. If not, see . */ +#include "boss_kelthuzad.h" #include "CreatureScript.h" #include "Player.h" #include "ScriptedCreature.h" @@ -22,710 +23,8 @@ #include "SpellScriptLoader.h" #include "naxxramas.h" -enum Yells -{ - SAY_ANSWER_REQUEST = 3, - SAY_TAUNT = 6, - SAY_AGGRO = 7, - SAY_SLAY = 8, - SAY_DEATH = 9, - SAY_CHAIN = 10, - SAY_FROST_BLAST = 11, - SAY_REQUEST_AID = 12, - EMOTE_PHASE_TWO = 13, - SAY_SUMMON_MINIONS = 14, - SAY_SPECIAL = 15, - - EMOTE_GUARDIAN_FLEE = 0, - EMOTE_GUARDIAN_APPEAR = 1 -}; - -enum Spells -{ - // Kel'Thzuad - SPELL_FROST_BOLT_SINGLE_10 = 28478, - SPELL_FROST_BOLT_SINGLE_25 = 55802, - SPELL_FROST_BOLT_MULTI_10 = 28479, - SPELL_FROST_BOLT_MULTI_25 = 55807, - SPELL_SHADOW_FISURE = 27810, - SPELL_VOID_BLAST = 27812, - SPELL_DETONATE_MANA = 27819, - SPELL_MANA_DETONATION_DAMAGE = 27820, - SPELL_FROST_BLAST = 27808, - SPELL_CHAINS_OF_KELTHUZAD = 28410, // 28408 script effect - SPELL_BERSERK = 28498, - SPELL_KELTHUZAD_CHANNEL = 29423, - - // Minions - SPELL_FRENZY = 28468, - SPELL_MORTAL_WOUND = 28467, - SPELL_BLOOD_TAP = 28470 -}; - -enum Misc -{ - NPC_SOLDIER_OF_THE_FROZEN_WASTES = 16427, - NPC_UNSTOPPABLE_ABOMINATION = 16428, - NPC_SOUL_WEAVER = 16429, - NPC_GUARDIAN_OF_ICECROWN = 16441, - - ACTION_CALL_HELP_ON = 1, - ACTION_CALL_HELP_OFF = 2, - ACTION_SECOND_PHASE = 3, - ACTION_GUARDIANS_OFF = 4 -}; - -enum Event -{ - // Kel'Thuzad - EVENT_SUMMON_SOLDIER = 1, - EVENT_SUMMON_UNSTOPPABLE_ABOMINATION = 2, - EVENT_SUMMON_SOUL_WEAVER = 3, - EVENT_PHASE_2 = 4, - EVENT_FROST_BOLT_SINGLE = 5, - EVENT_FROST_BOLT_MULTI = 6, - EVENT_DETONATE_MANA = 7, - EVENT_PHASE_3 = 8, - EVENT_P3_LICH_KING_SAY = 9, - EVENT_SHADOW_FISSURE = 10, - EVENT_FROST_BLAST = 11, - EVENT_CHAINS = 12, - EVENT_SUMMON_GUARDIAN_OF_ICECROWN = 13, - EVENT_FLOOR_CHANGE = 14, - EVENT_ENRAGE = 15, - EVENT_SPAWN_POOL = 16, - - // Minions - EVENT_MINION_FRENZY = 17, - EVENT_MINION_MORTAL_WOUND = 18, - EVENT_MINION_BLOOD_TAP = 19 -}; - -const Position SummonGroups[12] = -{ - // Portals - {3783.272705f, -5062.697266f, 143.711203f, 3.617599f}, // LEFT_FAR - {3730.291260f, -5027.239258f, 143.956909f, 4.461900f}, // LEFT_MIDDLE - {3683.868652f, -5057.281250f, 143.183884f, 5.237086f}, // LEFT_NEAR - {3759.355225f, -5174.128418f, 143.802383f, 2.170104f}, // RIGHT_FAR - {3700.724365f, -5185.123047f, 143.928024f, 1.309310f}, // RIGHT_MIDDLE - {3665.121094f, -5138.679199f, 143.183212f, 0.604023f}, // RIGHT_NEAR - - // Middle - {3769.34f, -5071.80f, 143.2082f, 3.658f}, - {3729.78f, -5043.56f, 143.3867f, 4.475f}, - {3682.75f, -5055.26f, 143.1848f, 5.295f}, - {3752.58f, -5161.82f, 143.2944f, 2.126f}, - {3702.83f, -5171.70f, 143.4356f, 1.305f}, - {3665.30f, -5141.55f, 143.1846f, 0.566f} -}; - -const Position SpawnPool[7] = -{ - // Portals - {3783.272705f, -5062.697266f, 143.711203f, 3.617599f}, // LEFT_FAR - {3730.291260f, -5027.239258f, 143.956909f, 4.461900f}, // LEFT_MIDDLE - {3683.868652f, -5057.281250f, 143.183884f, 5.237086f}, // LEFT_NEAR - {3759.355225f, -5174.128418f, 143.802383f, 2.170104f}, // RIGHT_FAR - {3700.724365f, -5185.123047f, 143.928024f, 1.309310f}, // RIGHT_MIDDLE - {3665.121094f, -5138.679199f, 143.183212f, 0.604023f}, // RIGHT_NEAR - {3651.729980f, -5092.620117f, 143.380005f, 6.050000f} // GATE -}; - -class boss_kelthuzad : public CreatureScript -{ -public: - boss_kelthuzad() : CreatureScript("boss_kelthuzad") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_kelthuzadAI : public BossAI - { - explicit boss_kelthuzadAI(Creature* c) : BossAI(c, BOSS_KELTHUZAD), summons(me) - { - pInstance = me->GetInstanceScript(); - _justSpawned = true; - } - - EventMap events; - SummonList summons; - InstanceScript* pInstance; - bool _justSpawned; - - float NormalizeOrientation(float o) - { - return std::fmod(o, 2.0f * static_cast(M_PI)); // Only positive values will be passed - } - - void SpawnHelpers() - { - // spawn at gate - me->SummonCreature(NPC_UNSTOPPABLE_ABOMINATION, 3656.19f, -5093.78f, 143.33f, 6.08, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000);// abo center - me->SummonCreature(NPC_UNSTOPPABLE_ABOMINATION, 3657.94f, -5087.68f, 143.60f, 6.08, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000);// abo left - me->SummonCreature(NPC_UNSTOPPABLE_ABOMINATION, 3655.48f, -5100.05f, 143.53f, 6.08, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000);// abo right - me->SummonCreature(NPC_SOUL_WEAVER, 3651.73f, -5092.62f, 143.38f, 6.05, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000); // soul behind - me->SummonCreature(NPC_SOLDIER_OF_THE_FROZEN_WASTES, 3660.17f, -5092.45f, 143.37f, 6.07, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000); // ske front left - me->SummonCreature(NPC_SOLDIER_OF_THE_FROZEN_WASTES, 3659.39f, -5096.21f, 143.29f, 6.07, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000); // ske front right - me->SummonCreature(NPC_SOLDIER_OF_THE_FROZEN_WASTES, 3659.29f, -5090.19f, 143.48f, 6.07, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000); // ske left left - me->SummonCreature(NPC_SOLDIER_OF_THE_FROZEN_WASTES, 3657.43f, -5098.03f, 143.41f, 6.07, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000); // ske right right - me->SummonCreature(NPC_SOLDIER_OF_THE_FROZEN_WASTES, 3654.36f, -5090.51f, 143.48f, 6.09, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000); // ske behind left - me->SummonCreature(NPC_SOLDIER_OF_THE_FROZEN_WASTES, 3653.35f, -5095.91f, 143.41f, 6.09, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000); // ske right right - - // 6 rooms, 8 soldiers, 3 abominations and 1 weaver in each room | middle positions in table starts from 6 - for (uint8 i = 6; i < 12; ++i) - { - for (uint8 j = 0; j < 8; ++j) - { - float angle = M_PI * 2 / 8 * j; - me->SummonCreature(NPC_SOLDIER_OF_THE_FROZEN_WASTES, SummonGroups[i].GetPositionX() + 6 * cos(angle), SummonGroups[i].GetPositionY() + 6 * std::sin(angle), SummonGroups[i].GetPositionZ(), SummonGroups[i].GetOrientation(), TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000); - } - } - for (uint8 i = 6; i < 12; ++i) - { - for (uint8 j = 1; j < 4; ++j) - { - float dist = j == 2 ? 0.0f : 8.0f; // second in middle - float angle = SummonGroups[i].GetOrientation() + M_PI * 2 / 4 * j; - me->SummonCreature(NPC_UNSTOPPABLE_ABOMINATION, SummonGroups[i].GetPositionX() + dist * cos(angle), SummonGroups[i].GetPositionY() + dist * std::sin(angle), SummonGroups[i].GetPositionZ(), SummonGroups[i].GetOrientation(), TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000); - } - } - for (uint8 i = 6; i < 12; ++i) - { - for (uint8 j = 0; j < 1; ++j) - { - float angle = SummonGroups[i].GetOrientation() + M_PI; - me->SummonCreature(NPC_SOUL_WEAVER, SummonGroups[i].GetPositionX() + 6 * cos(angle), SummonGroups[i].GetPositionY() + 6 * std::sin(angle), SummonGroups[i].GetPositionZ() + 0.5f, SummonGroups[i].GetOrientation(), TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000); - } - } - } - - void SummonHelper(uint32 entry, uint32 count) - { - for (uint8 i = 0; i < count; ++i) - { - if (Creature* cr = me->SummonCreature(entry, SpawnPool[urand(0, 6)], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000)) - { - if (Unit* target = SelectTargetFromPlayerList(100.0f)) - { - cr->AI()->DoAction(ACTION_CALL_HELP_OFF); - cr->AI()->AttackStart(target); - } - } - } - } - - void Reset() override - { - BossAI::Reset(); - events.Reset(); - summons.DespawnAll(); - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_DISABLE_MOVE); - me->SetReactState(REACT_AGGRESSIVE); - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_FLOOR))) - { - go->SetPhaseMask(1, true); - go->SetGoState(GO_STATE_READY); - } - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_GATE))) - { - if(!_justSpawned) // Don't open the door if we just spawned and are still doing the conversation - { - go->SetGoState(GO_STATE_ACTIVE); - } - } - _justSpawned = false; - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_PORTAL_1))) - { - go->SetGoState(GO_STATE_READY); - } - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_PORTAL_2))) - { - go->SetGoState(GO_STATE_READY); - } - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_PORTAL_3))) - { - go->SetGoState(GO_STATE_READY); - } - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_PORTAL_4))) - { - go->SetGoState(GO_STATE_READY); - } - } - - void EnterEvadeMode(EvadeReason why) override - { - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_DISABLE_MOVE); - ScriptedAI::EnterEvadeMode(why); - } - - void KilledUnit(Unit* who) override - { - if (!who->IsPlayer()) - return; - - Talk(SAY_SLAY); - if (pInstance) - { - pInstance->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void JustDied(Unit* killer) override - { - BossAI::JustDied(killer); - summons.DoAction(ACTION_GUARDIANS_OFF); - if (Creature* guardian = summons.GetCreatureWithEntry(NPC_GUARDIAN_OF_ICECROWN)) - { - guardian->AI()->Talk(EMOTE_GUARDIAN_FLEE); - } - Talk(SAY_DEATH); - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_GATE))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - } - } - - void MoveInLineOfSight(Unit* who) override - { - if (!me->IsInCombat() && who->IsPlayer() && who->IsAlive() && me->GetDistance(who) <= 50.0f) - AttackStart(who); - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - Talk(SAY_SUMMON_MINIONS); - me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_DISABLE_MOVE); - me->RemoveAllAttackers(); - me->SetTarget(); - me->SetReactState(REACT_PASSIVE); - me->CastSpell(me, SPELL_KELTHUZAD_CHANNEL, false); - events.ScheduleEvent(EVENT_SPAWN_POOL, 5s); - events.ScheduleEvent(EVENT_SUMMON_SOLDIER, 6400ms); - events.ScheduleEvent(EVENT_SUMMON_UNSTOPPABLE_ABOMINATION, 10s); - events.ScheduleEvent(EVENT_SUMMON_SOUL_WEAVER, 12s); - events.ScheduleEvent(EVENT_PHASE_2, 228s); - events.ScheduleEvent(EVENT_ENRAGE, 15min); - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_FLOOR))) - { - events.ScheduleEvent(EVENT_FLOOR_CHANGE, 15s); - go->SetGoState(GO_STATE_ACTIVE); - } - } - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_GATE))) - { - go->SetGoState(GO_STATE_READY); - } - } - - void JustSummoned(Creature* cr) override - { - summons.Summon(cr); - if (!cr->IsInCombat()) - { - cr->GetMotionMaster()->MoveRandom(5); - } - if (cr->GetEntry() == NPC_GUARDIAN_OF_ICECROWN) - { - cr->SetHomePosition(cr->GetPositionX(), cr->GetPositionY(), cr->GetPositionZ(), cr->GetOrientation()); - } - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - if (!me->HasAura(SPELL_KELTHUZAD_CHANNEL)) - { - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - } - - switch (events.ExecuteEvent()) - { - case EVENT_FLOOR_CHANGE: - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_FLOOR))) - { - events.ScheduleEvent(EVENT_FLOOR_CHANGE, 15s); - go->SetGoState(GO_STATE_READY); - go->SetPhaseMask(2, true); - } - } - break; - case EVENT_SPAWN_POOL: - SpawnHelpers(); - break; - case EVENT_SUMMON_SOLDIER: - SummonHelper(NPC_SOLDIER_OF_THE_FROZEN_WASTES, 1); - events.Repeat(3100ms); - break; - case EVENT_SUMMON_UNSTOPPABLE_ABOMINATION: - SummonHelper(NPC_UNSTOPPABLE_ABOMINATION, 1); - events.Repeat(18s + 500ms); - break; - case EVENT_SUMMON_SOUL_WEAVER: - SummonHelper(NPC_SOUL_WEAVER, 1); - events.Repeat(30s); - break; - case EVENT_PHASE_2: - Talk(EMOTE_PHASE_TWO); - Talk(SAY_AGGRO); - events.Reset(); - summons.DoAction(ACTION_SECOND_PHASE); - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_DISABLE_MOVE); - me->GetMotionMaster()->MoveChase(me->GetVictim()); - me->RemoveAura(SPELL_KELTHUZAD_CHANNEL); - me->SetReactState(REACT_AGGRESSIVE); - events.ScheduleEvent(EVENT_FROST_BOLT_SINGLE, 2s, 10s); - events.ScheduleEvent(EVENT_FROST_BOLT_MULTI, 15s, 30s); - events.ScheduleEvent(EVENT_DETONATE_MANA, 30s); - events.ScheduleEvent(EVENT_PHASE_3, 1s); - events.ScheduleEvent(EVENT_SHADOW_FISSURE, 25s); - events.ScheduleEvent(EVENT_FROST_BLAST, 45s); - if (Is25ManRaid()) - { - events.ScheduleEvent(EVENT_CHAINS, 90s); - } - break; - case EVENT_ENRAGE: - me->CastSpell(me, SPELL_BERSERK, true); - break; - case EVENT_FROST_BOLT_SINGLE: - me->CastSpell(me->GetVictim(), RAID_MODE(SPELL_FROST_BOLT_SINGLE_10, SPELL_FROST_BOLT_SINGLE_25), false); - events.Repeat(2s, 10s); - break; - case EVENT_FROST_BOLT_MULTI: - me->CastSpell(me, RAID_MODE(SPELL_FROST_BOLT_MULTI_10, SPELL_FROST_BOLT_MULTI_25), false); - events.Repeat(15s, 30s); - break; - case EVENT_SHADOW_FISSURE: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true)) - { - me->CastSpell(target, SPELL_SHADOW_FISURE, false); - } - events.Repeat(25s); - break; - case EVENT_FROST_BLAST: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, RAID_MODE(1, 0), 0, true)) - { - me->CastSpell(target, SPELL_FROST_BLAST, false); - } - Talk(SAY_FROST_BLAST); - events.Repeat(45s); - break; - case EVENT_CHAINS: - for (uint8 i = 0; i < 3; ++i) - { - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 200, true, true, -SPELL_CHAINS_OF_KELTHUZAD)) - { - me->CastSpell(target, SPELL_CHAINS_OF_KELTHUZAD, true); - } - } - Talk(SAY_CHAIN); - events.Repeat(90s); - break; - case EVENT_DETONATE_MANA: - { - std::vector unitList; - ThreatContainer::StorageType const& threatList = me->GetThreatMgr().GetThreatList(); - for (auto itr : threatList) - { - if (itr->getTarget()->IsPlayer() - && itr->getTarget()->getPowerType() == POWER_MANA - && itr->getTarget()->GetPower(POWER_MANA)) - { - unitList.push_back(itr->getTarget()); - } - } - if (!unitList.empty()) - { - auto itr = unitList.begin(); - advance(itr, urand(0, unitList.size() - 1)); - me->CastSpell(*itr, SPELL_DETONATE_MANA, false); - Talk(SAY_SPECIAL); - } - events.Repeat(30s); - break; - } - case EVENT_PHASE_3: - if (me->HealthBelowPct(45)) - { - Talk(SAY_REQUEST_AID); - events.DelayEvents(5500ms); - events.ScheduleEvent(EVENT_P3_LICH_KING_SAY, 5s); - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_PORTAL_1))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_PORTAL_2))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_PORTAL_3))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_PORTAL_4))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - break; - } - events.Repeat(1s); - break; - case EVENT_P3_LICH_KING_SAY: - if (pInstance) - { - if (Creature* cr = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(DATA_LICH_KING_BOSS))) - { - cr->AI()->Talk(SAY_ANSWER_REQUEST); - } - } - for (uint8 i = 0 ; i < RAID_MODE(2, 4); ++i) - { - events.ScheduleEvent(EVENT_SUMMON_GUARDIAN_OF_ICECROWN, 10000 + (i * 5000)); - } - break; - case EVENT_SUMMON_GUARDIAN_OF_ICECROWN: - if (Creature* cr = me->SummonCreature(NPC_GUARDIAN_OF_ICECROWN, SpawnPool[RAND(0, 1, 3, 4)])) - { - cr->AI()->Talk(EMOTE_GUARDIAN_APPEAR); - cr->AI()->AttackStart(me->GetVictim()); - } - break; - } - if (!me->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE)) - DoMeleeAttackIfReady(); - } - }; -}; - -class boss_kelthuzad_minion : public CreatureScript -{ -public: - boss_kelthuzad_minion() : CreatureScript("boss_kelthuzad_minion") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_kelthuzad_minionAI : public ScriptedAI - { - explicit boss_kelthuzad_minionAI(Creature* c) : ScriptedAI(c) { } - - EventMap events; - bool callHelp{}; - - void Reset() override - { - me->SetNoCallAssistance(true); - callHelp = true; - events.Reset(); - } - - void DoAction(int32 param) override - { - if (param == ACTION_CALL_HELP_ON) - { - callHelp = true; - } - else if (param == ACTION_CALL_HELP_OFF) - { - callHelp = false; - } - else if (param == ACTION_SECOND_PHASE) - { - if (!me->IsInCombat()) - { - me->DespawnOrUnsummon(500); - } - } - if (param == ACTION_GUARDIANS_OFF) - { - me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); - me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - me->RemoveAllAuras(); - EnterEvadeMode(); - me->SetPosition(me->GetHomePosition()); - } - } - - void MoveInLineOfSight(Unit* who) override - { - if (!who->IsPlayer() && !who->IsPet()) - return; - - ScriptedAI::MoveInLineOfSight(who); - } - - void JustDied(Unit* /*killer*/) override - { - if (me->GetEntry() == NPC_UNSTOPPABLE_ABOMINATION && me->GetInstanceScript()) - { - me->GetInstanceScript()->SetData(DATA_ABOMINATION_KILLED, 0); - } - } - - void AttackStart(Unit* who) override - { - ScriptedAI::AttackStart(who); - if (callHelp) - { - std::list targets; - me->GetCreaturesWithEntryInRange(targets, 15.0f, me->GetEntry()); - for (std::list::const_iterator itr = targets.begin(); itr != targets.end(); ++itr) - { - if ((*itr)->GetGUID() != me->GetGUID()) - { - (*itr)->ToCreature()->AI()->DoAction(ACTION_CALL_HELP_OFF); - (*itr)->ToCreature()->AI()->AttackStart(who); - } - } - } - - if (me->GetEntry() != NPC_UNSTOPPABLE_ABOMINATION && me->GetEntry() != NPC_GUARDIAN_OF_ICECROWN) - { - me->AddThreat(who, 1000000.0f); - } - } - - void JustEngagedWith(Unit* /*who*/) override - { - me->SetInCombatWithZone(); - if (me->GetEntry() == NPC_UNSTOPPABLE_ABOMINATION) - { - events.ScheduleEvent(EVENT_MINION_FRENZY, 1s); - events.ScheduleEvent(EVENT_MINION_MORTAL_WOUND, 5s); - } - else if (me->GetEntry() == NPC_GUARDIAN_OF_ICECROWN) - { - events.ScheduleEvent(EVENT_MINION_BLOOD_TAP, 15s); - } - } - - void KilledUnit(Unit* who) override - { - if (who->IsPlayer() && me->GetInstanceScript()) - { - me->GetInstanceScript()->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void JustReachedHome() override - { - if (me->GetEntry() == NPC_GUARDIAN_OF_ICECROWN) - { - me->DespawnOrUnsummon(); - } - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_MINION_MORTAL_WOUND: - me->CastSpell(me->GetVictim(), SPELL_MORTAL_WOUND, false); - events.Repeat(15s); - break; - case EVENT_MINION_FRENZY: - if (me->HealthBelowPct(35)) - { - me->CastSpell(me, SPELL_FRENZY, true); - break; - } - events.Repeat(1s); - break; - case EVENT_MINION_BLOOD_TAP: - me->CastSpell(me->GetVictim(), SPELL_BLOOD_TAP, false); - events.Repeat(15s); - break; - } - DoMeleeAttackIfReady(); - } - }; -}; - -class spell_kelthuzad_frost_blast : public SpellScript -{ - PrepareSpellScript(spell_kelthuzad_frost_blast); - - bool Validate(SpellInfo const* /*spell*/) override - { - return ValidateSpellInfo({ SPELL_FROST_BLAST }); - } - - void FilterTargets(std::list& targets) - { - Unit* caster = GetCaster(); - if (!caster || !caster->ToCreature()) - return; - - std::list tmplist; - for (auto& target : targets) - { - if (!target->ToUnit()->HasAura(SPELL_FROST_BLAST)) - { - tmplist.push_back(target); - } - } - targets.clear(); - for (auto& itr : tmplist) - { - targets.push_back(itr); - } - } - - void Register() override - { - OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_kelthuzad_frost_blast::FilterTargets, EFFECT_ALL, TARGET_UNIT_DEST_AREA_ENEMY); - } -}; - -class spell_kelthuzad_detonate_mana_aura : public AuraScript -{ - PrepareAuraScript(spell_kelthuzad_detonate_mana_aura); - - bool Validate(SpellInfo const* /*spell*/) override - { - return ValidateSpellInfo({ SPELL_MANA_DETONATION_DAMAGE }); - } - - void HandleScript(AuraEffect const* aurEff) - { - PreventDefaultAction(); - Unit* target = GetTarget(); - if (auto mana = int32(target->GetMaxPower(POWER_MANA) / 10)) - { - mana = target->ModifyPower(POWER_MANA, -mana); - target->CastCustomSpell(SPELL_MANA_DETONATION_DAMAGE, SPELLVALUE_BASE_POINT0, -mana * 10, target, true, nullptr, aurEff); - } - } - void Register() override - { - OnEffectPeriodic += AuraEffectPeriodicFn(spell_kelthuzad_detonate_mana_aura::HandleScript, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL); - } -}; +using namespace Kelthuzad; void AddSC_boss_kelthuzad() { diff --git a/src/server/scripts/Northrend/Naxxramas/boss_kelthuzad.h b/src/server/scripts/Northrend/Naxxramas/boss_kelthuzad.h new file mode 100644 index 00000000000000..a80aee93136139 --- /dev/null +++ b/src/server/scripts/Northrend/Naxxramas/boss_kelthuzad.h @@ -0,0 +1,719 @@ +#ifndef BOSS_KELTHUZAD_H_ +#define BOSS_KELTHUZAD_H_ + +#include "Player.h" +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "SpellScript.h" +#include "SpellInfo.h" +#include "naxxramas.h" + +namespace Kelthuzad { + +enum Yells +{ + SAY_ANSWER_REQUEST = 3, + SAY_TAUNT = 6, + SAY_AGGRO = 7, + SAY_SLAY = 8, + SAY_DEATH = 9, + SAY_CHAIN = 10, + SAY_FROST_BLAST = 11, + SAY_REQUEST_AID = 12, + EMOTE_PHASE_TWO = 13, + SAY_SUMMON_MINIONS = 14, + SAY_SPECIAL = 15, + + EMOTE_GUARDIAN_FLEE = 0, + EMOTE_GUARDIAN_APPEAR = 1 +}; + +enum Spells +{ + // Kel'Thzuad + SPELL_FROST_BOLT_SINGLE_10 = 28478, + SPELL_FROST_BOLT_SINGLE_25 = 55802, + SPELL_FROST_BOLT_MULTI_10 = 28479, + SPELL_FROST_BOLT_MULTI_25 = 55807, + SPELL_SHADOW_FISURE = 27810, + SPELL_VOID_BLAST = 27812, + SPELL_DETONATE_MANA = 27819, + SPELL_MANA_DETONATION_DAMAGE = 27820, + SPELL_FROST_BLAST = 27808, + SPELL_CHAINS_OF_KELTHUZAD = 28410, // 28408 script effect + SPELL_BERSERK = 28498, + SPELL_KELTHUZAD_CHANNEL = 29423, + + // Minions + SPELL_FRENZY = 28468, + SPELL_MORTAL_WOUND = 28467, + SPELL_BLOOD_TAP = 28470 +}; + +enum Misc +{ + NPC_SOLDIER_OF_THE_FROZEN_WASTES = 16427, + NPC_UNSTOPPABLE_ABOMINATION = 16428, + NPC_SOUL_WEAVER = 16429, + NPC_GUARDIAN_OF_ICECROWN = 16441, + + ACTION_CALL_HELP_ON = 1, + ACTION_CALL_HELP_OFF = 2, + ACTION_SECOND_PHASE = 3, + ACTION_GUARDIANS_OFF = 4 +}; + +enum Event +{ + // Kel'Thuzad + EVENT_SUMMON_SOLDIER = 1, + EVENT_SUMMON_UNSTOPPABLE_ABOMINATION = 2, + EVENT_SUMMON_SOUL_WEAVER = 3, + EVENT_PHASE_2 = 4, + EVENT_FROST_BOLT_SINGLE = 5, + EVENT_FROST_BOLT_MULTI = 6, + EVENT_DETONATE_MANA = 7, + EVENT_PHASE_3 = 8, + EVENT_P3_LICH_KING_SAY = 9, + EVENT_SHADOW_FISSURE = 10, + EVENT_FROST_BLAST = 11, + EVENT_CHAINS = 12, + EVENT_SUMMON_GUARDIAN_OF_ICECROWN = 13, + EVENT_FLOOR_CHANGE = 14, + EVENT_ENRAGE = 15, + EVENT_SPAWN_POOL = 16, + + // Minions + EVENT_MINION_FRENZY = 17, + EVENT_MINION_MORTAL_WOUND = 18, + EVENT_MINION_BLOOD_TAP = 19 +}; + +const Position SummonGroups[12] = +{ + // Portals + {3783.272705f, -5062.697266f, 143.711203f, 3.617599f}, // LEFT_FAR + {3730.291260f, -5027.239258f, 143.956909f, 4.461900f}, // LEFT_MIDDLE + {3683.868652f, -5057.281250f, 143.183884f, 5.237086f}, // LEFT_NEAR + {3759.355225f, -5174.128418f, 143.802383f, 2.170104f}, // RIGHT_FAR + {3700.724365f, -5185.123047f, 143.928024f, 1.309310f}, // RIGHT_MIDDLE + {3665.121094f, -5138.679199f, 143.183212f, 0.604023f}, // RIGHT_NEAR + + // Middle + {3769.34f, -5071.80f, 143.2082f, 3.658f}, + {3729.78f, -5043.56f, 143.3867f, 4.475f}, + {3682.75f, -5055.26f, 143.1848f, 5.295f}, + {3752.58f, -5161.82f, 143.2944f, 2.126f}, + {3702.83f, -5171.70f, 143.4356f, 1.305f}, + {3665.30f, -5141.55f, 143.1846f, 0.566f} +}; + +const Position SpawnPool[7] = +{ + // Portals + {3783.272705f, -5062.697266f, 143.711203f, 3.617599f}, // LEFT_FAR + {3730.291260f, -5027.239258f, 143.956909f, 4.461900f}, // LEFT_MIDDLE + {3683.868652f, -5057.281250f, 143.183884f, 5.237086f}, // LEFT_NEAR + {3759.355225f, -5174.128418f, 143.802383f, 2.170104f}, // RIGHT_FAR + {3700.724365f, -5185.123047f, 143.928024f, 1.309310f}, // RIGHT_MIDDLE + {3665.121094f, -5138.679199f, 143.183212f, 0.604023f}, // RIGHT_NEAR + {3651.729980f, -5092.620117f, 143.380005f, 6.050000f} // GATE +}; + +class boss_kelthuzad : public CreatureScript +{ +public: + boss_kelthuzad() : CreatureScript("boss_kelthuzad") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_kelthuzadAI : public BossAI + { + explicit boss_kelthuzadAI(Creature* c) : BossAI(c, BOSS_KELTHUZAD), summons(me) + { + pInstance = me->GetInstanceScript(); + _justSpawned = true; + } + + EventMap events; + SummonList summons; + InstanceScript* pInstance; + bool _justSpawned; + + float NormalizeOrientation(float o) + { + return std::fmod(o, 2.0f * static_cast(M_PI)); // Only positive values will be passed + } + + void SpawnHelpers() + { + // spawn at gate + me->SummonCreature(NPC_UNSTOPPABLE_ABOMINATION, 3656.19f, -5093.78f, 143.33f, 6.08, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000);// abo center + me->SummonCreature(NPC_UNSTOPPABLE_ABOMINATION, 3657.94f, -5087.68f, 143.60f, 6.08, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000);// abo left + me->SummonCreature(NPC_UNSTOPPABLE_ABOMINATION, 3655.48f, -5100.05f, 143.53f, 6.08, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000);// abo right + me->SummonCreature(NPC_SOUL_WEAVER, 3651.73f, -5092.62f, 143.38f, 6.05, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000); // soul behind + me->SummonCreature(NPC_SOLDIER_OF_THE_FROZEN_WASTES, 3660.17f, -5092.45f, 143.37f, 6.07, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000); // ske front left + me->SummonCreature(NPC_SOLDIER_OF_THE_FROZEN_WASTES, 3659.39f, -5096.21f, 143.29f, 6.07, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000); // ske front right + me->SummonCreature(NPC_SOLDIER_OF_THE_FROZEN_WASTES, 3659.29f, -5090.19f, 143.48f, 6.07, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000); // ske left left + me->SummonCreature(NPC_SOLDIER_OF_THE_FROZEN_WASTES, 3657.43f, -5098.03f, 143.41f, 6.07, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000); // ske right right + me->SummonCreature(NPC_SOLDIER_OF_THE_FROZEN_WASTES, 3654.36f, -5090.51f, 143.48f, 6.09, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000); // ske behind left + me->SummonCreature(NPC_SOLDIER_OF_THE_FROZEN_WASTES, 3653.35f, -5095.91f, 143.41f, 6.09, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2000); // ske right right + + // 6 rooms, 8 soldiers, 3 abominations and 1 weaver in each room | middle positions in table starts from 6 + for (uint8 i = 6; i < 12; ++i) + { + for (uint8 j = 0; j < 8; ++j) + { + float angle = M_PI * 2 / 8 * j; + me->SummonCreature(NPC_SOLDIER_OF_THE_FROZEN_WASTES, SummonGroups[i].GetPositionX() + 6 * cos(angle), SummonGroups[i].GetPositionY() + 6 * std::sin(angle), SummonGroups[i].GetPositionZ(), SummonGroups[i].GetOrientation(), TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000); + } + } + for (uint8 i = 6; i < 12; ++i) + { + for (uint8 j = 1; j < 4; ++j) + { + float dist = j == 2 ? 0.0f : 8.0f; // second in middle + float angle = SummonGroups[i].GetOrientation() + M_PI * 2 / 4 * j; + me->SummonCreature(NPC_UNSTOPPABLE_ABOMINATION, SummonGroups[i].GetPositionX() + dist * cos(angle), SummonGroups[i].GetPositionY() + dist * std::sin(angle), SummonGroups[i].GetPositionZ(), SummonGroups[i].GetOrientation(), TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000); + } + } + for (uint8 i = 6; i < 12; ++i) + { + for (uint8 j = 0; j < 1; ++j) + { + float angle = SummonGroups[i].GetOrientation() + M_PI; + me->SummonCreature(NPC_SOUL_WEAVER, SummonGroups[i].GetPositionX() + 6 * cos(angle), SummonGroups[i].GetPositionY() + 6 * std::sin(angle), SummonGroups[i].GetPositionZ() + 0.5f, SummonGroups[i].GetOrientation(), TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000); + } + } + } + + void SummonHelper(uint32 entry, uint32 count) + { + for (uint8 i = 0; i < count; ++i) + { + if (Creature* cr = me->SummonCreature(entry, SpawnPool[urand(0, 6)], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000)) + { + if (Unit* target = SelectTargetFromPlayerList(100.0f)) + { + cr->AI()->DoAction(ACTION_CALL_HELP_OFF); + cr->AI()->AttackStart(target); + } + } + } + } + + void Reset() override + { + BossAI::Reset(); + events.Reset(); + summons.DespawnAll(); + me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_DISABLE_MOVE); + me->SetReactState(REACT_AGGRESSIVE); + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_FLOOR))) + { + go->SetPhaseMask(1, true); + go->SetGoState(GO_STATE_READY); + } + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_GATE))) + { + if(!_justSpawned) // Don't open the door if we just spawned and are still doing the conversation + { + go->SetGoState(GO_STATE_ACTIVE); + } + } + _justSpawned = false; + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_PORTAL_1))) + { + go->SetGoState(GO_STATE_READY); + } + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_PORTAL_2))) + { + go->SetGoState(GO_STATE_READY); + } + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_PORTAL_3))) + { + go->SetGoState(GO_STATE_READY); + } + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_PORTAL_4))) + { + go->SetGoState(GO_STATE_READY); + } + } + + void EnterEvadeMode(EvadeReason why) override + { + me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_DISABLE_MOVE); + ScriptedAI::EnterEvadeMode(why); + } + + void KilledUnit(Unit* who) override + { + if (!who->IsPlayer()) + return; + + Talk(SAY_SLAY); + if (pInstance) + { + pInstance->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + summons.DoAction(ACTION_GUARDIANS_OFF); + if (Creature* guardian = summons.GetCreatureWithEntry(NPC_GUARDIAN_OF_ICECROWN)) + { + guardian->AI()->Talk(EMOTE_GUARDIAN_FLEE); + } + Talk(SAY_DEATH); + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_GATE))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + } + } + + void MoveInLineOfSight(Unit* who) override + { + if (!me->IsInCombat() && who->IsPlayer() && who->IsAlive() && me->GetDistance(who) <= 50.0f) + AttackStart(who); + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + Talk(SAY_SUMMON_MINIONS); + me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_DISABLE_MOVE); + me->RemoveAllAttackers(); + me->SetTarget(); + me->SetReactState(REACT_PASSIVE); + me->CastSpell(me, SPELL_KELTHUZAD_CHANNEL, false); + events.ScheduleEvent(EVENT_SPAWN_POOL, 5s); + events.ScheduleEvent(EVENT_SUMMON_SOLDIER, 6400ms); + events.ScheduleEvent(EVENT_SUMMON_UNSTOPPABLE_ABOMINATION, 10s); + events.ScheduleEvent(EVENT_SUMMON_SOUL_WEAVER, 12s); + events.ScheduleEvent(EVENT_PHASE_2, 228s); + events.ScheduleEvent(EVENT_ENRAGE, 15min); + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_FLOOR))) + { + events.ScheduleEvent(EVENT_FLOOR_CHANGE, 15s); + go->SetGoState(GO_STATE_ACTIVE); + } + } + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_GATE))) + { + go->SetGoState(GO_STATE_READY); + } + } + + void JustSummoned(Creature* cr) override + { + summons.Summon(cr); + if (!cr->IsInCombat()) + { + cr->GetMotionMaster()->MoveRandom(5); + } + if (cr->GetEntry() == NPC_GUARDIAN_OF_ICECROWN) + { + cr->SetHomePosition(cr->GetPositionX(), cr->GetPositionY(), cr->GetPositionZ(), cr->GetOrientation()); + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + if (!me->HasAura(SPELL_KELTHUZAD_CHANNEL)) + { + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + } + + switch (events.ExecuteEvent()) + { + case EVENT_FLOOR_CHANGE: + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_FLOOR))) + { + events.ScheduleEvent(EVENT_FLOOR_CHANGE, 15s); + go->SetGoState(GO_STATE_READY); + go->SetPhaseMask(2, true); + } + } + break; + case EVENT_SPAWN_POOL: + SpawnHelpers(); + break; + case EVENT_SUMMON_SOLDIER: + SummonHelper(NPC_SOLDIER_OF_THE_FROZEN_WASTES, 1); + events.Repeat(3100ms); + break; + case EVENT_SUMMON_UNSTOPPABLE_ABOMINATION: + SummonHelper(NPC_UNSTOPPABLE_ABOMINATION, 1); + events.Repeat(18s + 500ms); + break; + case EVENT_SUMMON_SOUL_WEAVER: + SummonHelper(NPC_SOUL_WEAVER, 1); + events.Repeat(30s); + break; + case EVENT_PHASE_2: + Talk(EMOTE_PHASE_TWO); + Talk(SAY_AGGRO); + events.Reset(); + summons.DoAction(ACTION_SECOND_PHASE); + me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_DISABLE_MOVE); + me->GetMotionMaster()->MoveChase(me->GetVictim()); + me->RemoveAura(SPELL_KELTHUZAD_CHANNEL); + me->SetReactState(REACT_AGGRESSIVE); + events.ScheduleEvent(EVENT_FROST_BOLT_SINGLE, 2s, 10s); + events.ScheduleEvent(EVENT_FROST_BOLT_MULTI, 15s, 30s); + events.ScheduleEvent(EVENT_DETONATE_MANA, 30s); + events.ScheduleEvent(EVENT_PHASE_3, 1s); + events.ScheduleEvent(EVENT_SHADOW_FISSURE, 25s); + events.ScheduleEvent(EVENT_FROST_BLAST, 45s); + if (Is25ManRaid()) + { + events.ScheduleEvent(EVENT_CHAINS, 90s); + } + break; + case EVENT_ENRAGE: + me->CastSpell(me, SPELL_BERSERK, true); + break; + case EVENT_FROST_BOLT_SINGLE: + me->CastSpell(me->GetVictim(), RAID_MODE(SPELL_FROST_BOLT_SINGLE_10, SPELL_FROST_BOLT_SINGLE_25), false); + events.Repeat(2s, 10s); + break; + case EVENT_FROST_BOLT_MULTI: + me->CastSpell(me, RAID_MODE(SPELL_FROST_BOLT_MULTI_10, SPELL_FROST_BOLT_MULTI_25), false); + events.Repeat(15s, 30s); + break; + case EVENT_SHADOW_FISSURE: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true)) + { + me->CastSpell(target, SPELL_SHADOW_FISURE, false); + } + events.Repeat(25s); + break; + case EVENT_FROST_BLAST: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, RAID_MODE(1, 0), 0, true)) + { + me->CastSpell(target, SPELL_FROST_BLAST, false); + } + Talk(SAY_FROST_BLAST); + events.Repeat(45s); + break; + case EVENT_CHAINS: + for (uint8 i = 0; i < 3; ++i) + { + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 200, true, true, -SPELL_CHAINS_OF_KELTHUZAD)) + { + me->CastSpell(target, SPELL_CHAINS_OF_KELTHUZAD, true); + } + } + Talk(SAY_CHAIN); + events.Repeat(90s); + break; + case EVENT_DETONATE_MANA: + { + std::vector unitList; + ThreatContainer::StorageType const& threatList = me->GetThreatMgr().GetThreatList(); + for (auto itr : threatList) + { + if (itr->getTarget()->IsPlayer() + && itr->getTarget()->getPowerType() == POWER_MANA + && itr->getTarget()->GetPower(POWER_MANA)) + { + unitList.push_back(itr->getTarget()); + } + } + if (!unitList.empty()) + { + auto itr = unitList.begin(); + advance(itr, urand(0, unitList.size() - 1)); + me->CastSpell(*itr, SPELL_DETONATE_MANA, false); + Talk(SAY_SPECIAL); + } + events.Repeat(30s); + break; + } + case EVENT_PHASE_3: + if (me->HealthBelowPct(45)) + { + Talk(SAY_REQUEST_AID); + events.DelayEvents(5500ms); + events.ScheduleEvent(EVENT_P3_LICH_KING_SAY, 5s); + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_PORTAL_1))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_PORTAL_2))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_PORTAL_3))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_KELTHUZAD_PORTAL_4))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + break; + } + events.Repeat(1s); + break; + case EVENT_P3_LICH_KING_SAY: + if (pInstance) + { + if (Creature* cr = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(DATA_LICH_KING_BOSS))) + { + cr->AI()->Talk(SAY_ANSWER_REQUEST); + } + } + for (uint8 i = 0 ; i < RAID_MODE(2, 4); ++i) + { + events.ScheduleEvent(EVENT_SUMMON_GUARDIAN_OF_ICECROWN, 10000 + (i * 5000)); + } + break; + case EVENT_SUMMON_GUARDIAN_OF_ICECROWN: + if (Creature* cr = me->SummonCreature(NPC_GUARDIAN_OF_ICECROWN, SpawnPool[RAND(0, 1, 3, 4)])) + { + cr->AI()->Talk(EMOTE_GUARDIAN_APPEAR); + cr->AI()->AttackStart(me->GetVictim()); + } + break; + } + if (!me->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE)) + DoMeleeAttackIfReady(); + } + }; +}; + +class boss_kelthuzad_minion : public CreatureScript +{ +public: + boss_kelthuzad_minion() : CreatureScript("boss_kelthuzad_minion") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_kelthuzad_minionAI : public ScriptedAI + { + explicit boss_kelthuzad_minionAI(Creature* c) : ScriptedAI(c) { } + + EventMap events; + bool callHelp{}; + + void Reset() override + { + me->SetNoCallAssistance(true); + callHelp = true; + events.Reset(); + } + + void DoAction(int32 param) override + { + if (param == ACTION_CALL_HELP_ON) + { + callHelp = true; + } + else if (param == ACTION_CALL_HELP_OFF) + { + callHelp = false; + } + else if (param == ACTION_SECOND_PHASE) + { + if (!me->IsInCombat()) + { + me->DespawnOrUnsummon(500); + } + } + if (param == ACTION_GUARDIANS_OFF) + { + me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); + me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->RemoveAllAuras(); + EnterEvadeMode(); + me->SetPosition(me->GetHomePosition()); + } + } + + void MoveInLineOfSight(Unit* who) override + { + if (!who->IsPlayer() && !who->IsPet()) + return; + + ScriptedAI::MoveInLineOfSight(who); + } + + void JustDied(Unit* /*killer*/) override + { + if (me->GetEntry() == NPC_UNSTOPPABLE_ABOMINATION && me->GetInstanceScript()) + { + me->GetInstanceScript()->SetData(DATA_ABOMINATION_KILLED, 0); + } + } + + void AttackStart(Unit* who) override + { + ScriptedAI::AttackStart(who); + if (callHelp) + { + std::list targets; + me->GetCreaturesWithEntryInRange(targets, 15.0f, me->GetEntry()); + for (std::list::const_iterator itr = targets.begin(); itr != targets.end(); ++itr) + { + if ((*itr)->GetGUID() != me->GetGUID()) + { + (*itr)->ToCreature()->AI()->DoAction(ACTION_CALL_HELP_OFF); + (*itr)->ToCreature()->AI()->AttackStart(who); + } + } + } + + if (me->GetEntry() != NPC_UNSTOPPABLE_ABOMINATION && me->GetEntry() != NPC_GUARDIAN_OF_ICECROWN) + { + me->AddThreat(who, 1000000.0f); + } + } + + void JustEngagedWith(Unit* /*who*/) override + { + me->SetInCombatWithZone(); + if (me->GetEntry() == NPC_UNSTOPPABLE_ABOMINATION) + { + events.ScheduleEvent(EVENT_MINION_FRENZY, 1s); + events.ScheduleEvent(EVENT_MINION_MORTAL_WOUND, 5s); + } + else if (me->GetEntry() == NPC_GUARDIAN_OF_ICECROWN) + { + events.ScheduleEvent(EVENT_MINION_BLOOD_TAP, 15s); + } + } + + void KilledUnit(Unit* who) override + { + if (who->IsPlayer() && me->GetInstanceScript()) + { + me->GetInstanceScript()->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void JustReachedHome() override + { + if (me->GetEntry() == NPC_GUARDIAN_OF_ICECROWN) + { + me->DespawnOrUnsummon(); + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_MINION_MORTAL_WOUND: + me->CastSpell(me->GetVictim(), SPELL_MORTAL_WOUND, false); + events.Repeat(15s); + break; + case EVENT_MINION_FRENZY: + if (me->HealthBelowPct(35)) + { + me->CastSpell(me, SPELL_FRENZY, true); + break; + } + events.Repeat(1s); + break; + case EVENT_MINION_BLOOD_TAP: + me->CastSpell(me->GetVictim(), SPELL_BLOOD_TAP, false); + events.Repeat(15s); + break; + } + DoMeleeAttackIfReady(); + } + }; +}; + +class spell_kelthuzad_frost_blast : public SpellScript +{ + PrepareSpellScript(spell_kelthuzad_frost_blast); + + bool Validate(SpellInfo const* /*spell*/) override + { + return ValidateSpellInfo({ SPELL_FROST_BLAST }); + } + + void FilterTargets(std::list& targets) + { + Unit* caster = GetCaster(); + if (!caster || !caster->ToCreature()) + return; + + std::list tmplist; + for (auto& target : targets) + { + if (!target->ToUnit()->HasAura(SPELL_FROST_BLAST)) + { + tmplist.push_back(target); + } + } + targets.clear(); + for (auto& itr : tmplist) + { + targets.push_back(itr); + } + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_kelthuzad_frost_blast::FilterTargets, EFFECT_ALL, TARGET_UNIT_DEST_AREA_ENEMY); + } +}; + +class spell_kelthuzad_detonate_mana_aura : public AuraScript +{ + PrepareAuraScript(spell_kelthuzad_detonate_mana_aura); + + bool Validate(SpellInfo const* /*spell*/) override + { + return ValidateSpellInfo({ SPELL_MANA_DETONATION_DAMAGE }); + } + + void HandleScript(AuraEffect const* aurEff) + { + PreventDefaultAction(); + Unit* target = GetTarget(); + if (auto mana = int32(target->GetMaxPower(POWER_MANA) / 10)) + { + mana = target->ModifyPower(POWER_MANA, -mana); + target->CastCustomSpell(SPELL_MANA_DETONATION_DAMAGE, SPELLVALUE_BASE_POINT0, -mana * 10, target, true, nullptr, aurEff); + } + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_kelthuzad_detonate_mana_aura::HandleScript, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL); + } +}; + +} +#endif \ No newline at end of file diff --git a/src/server/scripts/Northrend/Naxxramas/boss_loatheb.cpp b/src/server/scripts/Northrend/Naxxramas/boss_loatheb.cpp index 52a0b4fe53b147..3f15517692b455 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_loatheb.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_loatheb.cpp @@ -15,187 +15,12 @@ * with this program. If not, see . */ +#include "boss_loatheb.h" #include "CreatureScript.h" #include "ScriptedCreature.h" #include "naxxramas.h" -enum Spells -{ - SPELL_NECROTIC_AURA = 55593, - SPELL_SUMMON_SPORE = 29234, - SPELL_DEATHBLOOM_10 = 29865, - SPELL_DEATHBLOOM_25 = 55053, - SPELL_INEVITABLE_DOOM_10 = 29204, - SPELL_INEVITABLE_DOOM_25 = 55052, - SPELL_BERSERK = 26662 -}; - -enum Events -{ - EVENT_NECROTIC_AURA = 1, - EVENT_DEATHBLOOM = 2, - EVENT_INEVITABLE_DOOM = 3, - EVENT_BERSERK = 4, - EVENT_SUMMON_SPORE = 5, - EVENT_NECROTIC_AURA_FADING = 6, - EVENT_NECROTIC_AURA_REMOVED = 7 -}; - -enum Texts -{ - SAY_NECROTIC_AURA_APPLIED = 0, - SAY_NECROTIC_AURA_REMOVED = 1, - SAY_NECROTIC_AURA_FADING = 2 -}; - -class boss_loatheb : public CreatureScript -{ -public: - boss_loatheb() : CreatureScript("boss_loatheb") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_loathebAI : public BossAI - { - explicit boss_loathebAI(Creature* c) : BossAI(c, BOSS_LOATHEB), summons(me) - { - pInstance = me->GetInstanceScript(); - me->SetHomePosition(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), me->GetOrientation()); - } - - InstanceScript* pInstance; - uint8 doomCounter; - EventMap events; - SummonList summons; - - void Reset() override - { - BossAI::Reset(); - events.Reset(); - summons.DespawnAll(); - doomCounter = 0; - if (pInstance) - { - pInstance->SetData(BOSS_LOATHEB, NOT_STARTED); - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_LOATHEB_GATE))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - } - } - - void JustSummoned(Creature* cr) override - { - cr->SetInCombatWithZone(); - summons.Summon(cr); - } - - void SummonedCreatureDies(Creature* /*cr*/, Unit*) override - { - if (pInstance) - { - pInstance->SetData(DATA_SPORE_KILLED, 0); - } - } - - void KilledUnit(Unit* who) override - { - if (who->IsPlayer() && pInstance) - { - pInstance->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - me->SetInCombatWithZone(); - events.ScheduleEvent(EVENT_NECROTIC_AURA, 10s); - events.ScheduleEvent(EVENT_DEATHBLOOM, 5s); - events.ScheduleEvent(EVENT_INEVITABLE_DOOM, 2min); - events.ScheduleEvent(EVENT_SUMMON_SPORE, 15s); - events.ScheduleEvent(EVENT_BERSERK, 12min); - if (pInstance) - { - pInstance->SetData(BOSS_LOATHEB, IN_PROGRESS); - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_LOATHEB_GATE))) - { - go->SetGoState(GO_STATE_READY); - } - } - } - - void JustDied(Unit* killer) override - { - BossAI::JustDied(killer); - summons.DespawnAll(); - if (pInstance) - { - pInstance->SetData(BOSS_LOATHEB, DONE); - } - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim() || !IsInRoom()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_SUMMON_SPORE: - me->CastSpell(me, SPELL_SUMMON_SPORE, true); - events.Repeat(35s); - break; - case EVENT_NECROTIC_AURA: - me->CastSpell(me, SPELL_NECROTIC_AURA, true); - Talk(SAY_NECROTIC_AURA_APPLIED); - events.ScheduleEvent(EVENT_NECROTIC_AURA_FADING, 14s); - events.ScheduleEvent(EVENT_NECROTIC_AURA_REMOVED, 17s); - events.Repeat(20s); - break; - case EVENT_DEATHBLOOM: - me->CastSpell(me, RAID_MODE(SPELL_DEATHBLOOM_10, SPELL_DEATHBLOOM_25), false); - events.Repeat(30s); - break; - case EVENT_INEVITABLE_DOOM: - me->CastSpell(me, RAID_MODE(SPELL_INEVITABLE_DOOM_10, SPELL_INEVITABLE_DOOM_25), false); - doomCounter++; - events.Repeat(doomCounter < 6 ? 30s : 15s); - break; - case EVENT_BERSERK: - me->CastSpell(me, SPELL_BERSERK, true); - break; - case EVENT_NECROTIC_AURA_FADING: - Talk(SAY_NECROTIC_AURA_FADING); - break; - case EVENT_NECROTIC_AURA_REMOVED: - Talk(SAY_NECROTIC_AURA_REMOVED); - break; - } - DoMeleeAttackIfReady(); - } - - bool IsInRoom() - { - // Calculate the distance between his home position to the gate - if (me->GetExactDist(me->GetHomePosition().GetPositionX(), - me->GetHomePosition().GetPositionY(), - me->GetHomePosition().GetPositionZ()) > 50.0f) - { - EnterEvadeMode(); - return false; - } - return true; - } - }; -}; +using namespace Loatheb; void AddSC_boss_loatheb() { diff --git a/src/server/scripts/Northrend/Naxxramas/boss_loatheb.h b/src/server/scripts/Northrend/Naxxramas/boss_loatheb.h new file mode 100644 index 00000000000000..798042a530273e --- /dev/null +++ b/src/server/scripts/Northrend/Naxxramas/boss_loatheb.h @@ -0,0 +1,191 @@ +#ifndef BOSS_LOATHEB_H_ +#define BOSS_LOATHEB_H_ + +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "SpellInfo.h" +#include "naxxramas.h" + +namespace Loatheb { + +enum Spells +{ + SPELL_NECROTIC_AURA = 55593, + SPELL_SUMMON_SPORE = 29234, + SPELL_DEATHBLOOM_10 = 29865, + SPELL_DEATHBLOOM_25 = 55053, + SPELL_INEVITABLE_DOOM_10 = 29204, + SPELL_INEVITABLE_DOOM_25 = 55052, + SPELL_BERSERK = 26662 +}; + +enum Events +{ + EVENT_NECROTIC_AURA = 1, + EVENT_DEATHBLOOM = 2, + EVENT_INEVITABLE_DOOM = 3, + EVENT_BERSERK = 4, + EVENT_SUMMON_SPORE = 5, + EVENT_NECROTIC_AURA_FADING = 6, + EVENT_NECROTIC_AURA_REMOVED = 7 +}; + +enum Texts +{ + SAY_NECROTIC_AURA_APPLIED = 0, + SAY_NECROTIC_AURA_REMOVED = 1, + SAY_NECROTIC_AURA_FADING = 2 +}; + +class boss_loatheb : public CreatureScript +{ +public: + boss_loatheb() : CreatureScript("boss_loatheb") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_loathebAI : public BossAI + { + explicit boss_loathebAI(Creature* c) : BossAI(c, BOSS_LOATHEB), summons(me) + { + pInstance = me->GetInstanceScript(); + me->SetHomePosition(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), me->GetOrientation()); + } + + InstanceScript* pInstance; + uint8 doomCounter; + EventMap events; + SummonList summons; + + void Reset() override + { + BossAI::Reset(); + events.Reset(); + summons.DespawnAll(); + doomCounter = 0; + if (pInstance) + { + pInstance->SetData(BOSS_LOATHEB, NOT_STARTED); + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_LOATHEB_GATE))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + } + } + + void JustSummoned(Creature* cr) override + { + cr->SetInCombatWithZone(); + summons.Summon(cr); + } + + void SummonedCreatureDies(Creature* /*cr*/, Unit*) override + { + if (pInstance) + { + pInstance->SetData(DATA_SPORE_KILLED, 0); + } + } + + void KilledUnit(Unit* who) override + { + if (who->IsPlayer() && pInstance) + { + pInstance->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + me->SetInCombatWithZone(); + events.ScheduleEvent(EVENT_NECROTIC_AURA, 10s); + events.ScheduleEvent(EVENT_DEATHBLOOM, 5s); + events.ScheduleEvent(EVENT_INEVITABLE_DOOM, 2min); + events.ScheduleEvent(EVENT_SUMMON_SPORE, 15s); + events.ScheduleEvent(EVENT_BERSERK, 12min); + if (pInstance) + { + pInstance->SetData(BOSS_LOATHEB, IN_PROGRESS); + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_LOATHEB_GATE))) + { + go->SetGoState(GO_STATE_READY); + } + } + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + summons.DespawnAll(); + if (pInstance) + { + pInstance->SetData(BOSS_LOATHEB, DONE); + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim() || !IsInRoom()) + return; + + events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_SUMMON_SPORE: + me->CastSpell(me, SPELL_SUMMON_SPORE, true); + events.Repeat(35s); + break; + case EVENT_NECROTIC_AURA: + me->CastSpell(me, SPELL_NECROTIC_AURA, true); + Talk(SAY_NECROTIC_AURA_APPLIED); + events.ScheduleEvent(EVENT_NECROTIC_AURA_FADING, 14s); + events.ScheduleEvent(EVENT_NECROTIC_AURA_REMOVED, 17s); + events.Repeat(20s); + break; + case EVENT_DEATHBLOOM: + me->CastSpell(me, RAID_MODE(SPELL_DEATHBLOOM_10, SPELL_DEATHBLOOM_25), false); + events.Repeat(30s); + break; + case EVENT_INEVITABLE_DOOM: + me->CastSpell(me, RAID_MODE(SPELL_INEVITABLE_DOOM_10, SPELL_INEVITABLE_DOOM_25), false); + doomCounter++; + events.Repeat(doomCounter < 6 ? 30s : 15s); + break; + case EVENT_BERSERK: + me->CastSpell(me, SPELL_BERSERK, true); + break; + case EVENT_NECROTIC_AURA_FADING: + Talk(SAY_NECROTIC_AURA_FADING); + break; + case EVENT_NECROTIC_AURA_REMOVED: + Talk(SAY_NECROTIC_AURA_REMOVED); + break; + } + DoMeleeAttackIfReady(); + } + + bool IsInRoom() + { + // Calculate the distance between his home position to the gate + if (me->GetExactDist(me->GetHomePosition().GetPositionX(), + me->GetHomePosition().GetPositionY(), + me->GetHomePosition().GetPositionZ()) > 50.0f) + { + EnterEvadeMode(); + return false; + } + return true; + } + }; +}; + +} + +#endif \ No newline at end of file diff --git a/src/server/scripts/Northrend/Naxxramas/boss_maexxna.cpp b/src/server/scripts/Northrend/Naxxramas/boss_maexxna.cpp index e2f22a6153e998..73444bdc004b36 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_maexxna.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_maexxna.cpp @@ -15,6 +15,7 @@ * with this program. If not, see . */ +#include "boss_maexxna.h" #include "CreatureScript.h" #include "PassiveAI.h" #include "Player.h" @@ -24,359 +25,7 @@ #include "SpellScriptLoader.h" #include "naxxramas.h" -enum Spells -{ - SPELL_WEB_SPRAY_10 = 29484, - SPELL_WEB_SPRAY_25 = 54125, - SPELL_POISON_SHOCK_10 = 28741, - SPELL_POISON_SHOCK_25 = 54122, - SPELL_NECROTIC_POISON_10 = 54121, - SPELL_NECROTIC_POISON_25 = 28776, - SPELL_FRENZY_10 = 54123, - SPELL_FRENZY_25 = 54124, - SPELL_WEB_WRAP_STUN = 28622, - SPELL_WEB_WRAP_SUMMON = 28627, - SPELL_WEB_WRAP_KILL_WEBS = 52512, - SPELL_WEB_WRAP_PACIFY_5 = 28618 // 5 seconds pacify silence -}; - -enum Events -{ - EVENT_WEB_SPRAY = 1, - EVENT_POISON_SHOCK = 2, - EVENT_NECROTIC_POISON = 3, - EVENT_WEB_WRAP = 4, - EVENT_HEALTH_CHECK = 5, - EVENT_SUMMON_SPIDERLINGS = 6, - EVENT_WEB_WRAP_APPLY_STUN = 7 -}; - -enum Emotes -{ - EMOTE_SPIDERS = 0, - EMOTE_WEB_WRAP = 1, - EMOTE_WEB_SPRAY = 2 -}; - -enum Misc -{ - NPC_WEB_WRAP = 16486, - NPC_MAEXXNA_SPIDERLING = 17055 -}; - -const Position PosWrap[7] = -{ - {3496.615f, -3834.182f, 320.7863f}, - {3509.108f, -3833.922f, 320.4750f}, - {3523.644f, -3838.309f, 320.5775f}, - {3538.152f, -3846.353f, 320.5188f}, - {3546.219f, -3856.167f, 320.9324f}, - {3555.135f, -3869.507f, 320.8307f}, - {3560.282f, -3886.143f, 321.2827f} -}; - -struct WebTargetSelector -{ - WebTargetSelector(Unit* maexxna) : _maexxna(maexxna) {} - bool operator()(Unit const* target) const - { - if (!target->IsPlayer()) // never web nonplayers (pets, guardians, etc.) - return false; - if (_maexxna->GetVictim() == target) // never target tank - return false; - if (target->HasAura(SPELL_WEB_WRAP_STUN)) // never target targets that are already webbed - return false; - return true; - } - - private: - Unit const* _maexxna; -}; - -class boss_maexxna : public CreatureScript -{ -public: - boss_maexxna() : CreatureScript("boss_maexxna") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_maexxnaAI : public BossAI - { - explicit boss_maexxnaAI(Creature* c) : BossAI(c, BOSS_MAEXXNA), summons(me) - { - pInstance = me->GetInstanceScript(); - } - - InstanceScript* pInstance; - EventMap events; - SummonList summons; - - GuidList wraps; - - bool IsInRoom() - { - if (me->GetExactDist(3486.6f, -3890.6f, 291.8f) > 100.0f) - { - EnterEvadeMode(); - return false; - } - return true; - } - - void Reset() override - { - BossAI::Reset(); - events.Reset(); - summons.DespawnAll(); - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_MAEXXNA_GATE))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - } - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - me->SetInCombatWithZone(); - events.ScheduleEvent(EVENT_WEB_WRAP, 20s); - events.ScheduleEvent(EVENT_WEB_SPRAY, 40s); - events.ScheduleEvent(EVENT_POISON_SHOCK, 10s); - events.ScheduleEvent(EVENT_NECROTIC_POISON, 5s); - events.ScheduleEvent(EVENT_HEALTH_CHECK, 1s); - events.ScheduleEvent(EVENT_SUMMON_SPIDERLINGS, 30s); - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_MAEXXNA_GATE))) - { - go->SetGoState(GO_STATE_READY); - } - } - } - - void JustSummoned(Creature* cr) override - { - if (cr->GetEntry() == NPC_MAEXXNA_SPIDERLING) - { - cr->SetInCombatWithZone(); - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0)) - { - cr->AI()->AttackStart(target); - } - } - summons.Summon(cr); - } - - void KilledUnit(Unit* who) override - { - if (who->IsPlayer() && pInstance) - { - pInstance->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void JustDied(Unit* killer) override - { - BossAI::JustDied(killer); - } - - void DoCastWebWrap() - { - std::list candidates; - SelectTargetList(candidates, RAID_MODE(1, 2), SelectTargetMethod::Random, 0, WebTargetSelector(me)); - - std::vector positions {0, 1, 2, 3, 4, 5, 6}; - Acore::Containers::RandomShuffle(positions); - - if (candidates.empty()) - return; - - for (int i = 0; i < RAID_MODE(1, 2) ; i++) - { - if (candidates.empty()) - break; - const Position &randomPos = PosWrap[positions[i]]; - - auto itr = candidates.begin(); - - if (candidates.size() > 1) - std::advance(itr, urand(0, candidates.size() - 1)); - - Unit *target = *itr; - candidates.erase(itr); - - float dx = randomPos.GetPositionX() - target->GetPositionX(); - float dy = randomPos.GetPositionY() - target->GetPositionY(); - float distXY = std::hypotf(dx, dy); - - // smooth knockback arc that avoids the ceiling - float horizontalSpeed = distXY / 1.5f; - float verticalSpeed = 28.0f; - if (distXY <= 10.0f) - verticalSpeed = 12.0f; - else if (distXY <= 20.0f) - verticalSpeed = 16.0f; - else if (distXY <= 30.0f) - verticalSpeed = 20.0f; - else if (distXY <= 40.0f) - verticalSpeed = 24.0f; - - target->KnockbackFrom(randomPos.GetPositionX(), randomPos.GetPositionY(), -horizontalSpeed, verticalSpeed); - me->CastSpell(target, SPELL_WEB_WRAP_PACIFY_5, true); // pacify silence for 5 seconds - - wraps.push_back(target->GetGUID()); - } - events.ScheduleEvent(EVENT_WEB_WRAP_APPLY_STUN, 2s); - } - - void UpdateAI(uint32 diff) override - { - if (!IsInRoom()) - return; - - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_WEB_SPRAY: - Talk(EMOTE_WEB_SPRAY); - me->CastSpell(me, RAID_MODE(SPELL_WEB_SPRAY_10, SPELL_WEB_SPRAY_25), true); - events.Repeat(40s); - break; - case EVENT_POISON_SHOCK: - me->CastSpell(me->GetVictim(), RAID_MODE(SPELL_POISON_SHOCK_10, SPELL_POISON_SHOCK_25), false); - events.Repeat(10s); - break; - case EVENT_NECROTIC_POISON: - me->CastSpell(me->GetVictim(), RAID_MODE(SPELL_NECROTIC_POISON_10, SPELL_NECROTIC_POISON_25), false); - events.Repeat(30s); - break; - case EVENT_SUMMON_SPIDERLINGS: - Talk(EMOTE_SPIDERS); - for (uint8 i = 0; i < 8; ++i) - { - me->SummonCreature(NPC_MAEXXNA_SPIDERLING, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), me->GetOrientation()); - } - events.Repeat(40s); - break; - case EVENT_HEALTH_CHECK: - if (me->GetHealthPct() < 30) - { - me->CastSpell(me, RAID_MODE(SPELL_FRENZY_10, SPELL_FRENZY_25), true); - break; - } - events.Repeat(1s); - break; - case EVENT_WEB_WRAP: - Talk(EMOTE_WEB_WRAP); - DoCastWebWrap(); - events.Repeat(40s); - break; - case EVENT_WEB_WRAP_APPLY_STUN: - { - for (auto& p : wraps) - { - if (Player* player = ObjectAccessor::GetPlayer(*me, p)) - { - player->CastSpell(player, SPELL_WEB_WRAP_STUN, true); - } - } - wraps.clear(); - break; - } - } - DoMeleeAttackIfReady(); - } - }; -}; - -class boss_maexxna_webwrap : public CreatureScript -{ -public: - boss_maexxna_webwrap() : CreatureScript("boss_maexxna_webwrap") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_maexxna_webwrapAI : public NullCreatureAI - { - explicit boss_maexxna_webwrapAI(Creature* c) : NullCreatureAI(c) { } - - ObjectGuid victimGUID; - - void IsSummonedBy(WorldObject* summoner) override - { - if (!summoner) - return; - victimGUID = summoner->GetGUID(); - } - - void JustDied(Unit* /*killer*/) override - { - if (victimGUID) - { - if (Unit* victim = ObjectAccessor::GetUnit(*me, victimGUID)) - { - if (victim->IsAlive()) - { - victim->RemoveAurasDueToSpell(SPELL_WEB_WRAP_STUN); - victim->RemoveAurasDueToSpell(SPELL_WEB_WRAP_SUMMON); - } - } - } - } - - void UpdateAI(uint32 /*diff*/) override - { - if (victimGUID) - { - if (Unit* victim = ObjectAccessor::GetUnit(*me, victimGUID)) - { - if (!victim->IsAlive()) - { - me->CastSpell(me, SPELL_WEB_WRAP_KILL_WEBS, true); - } - } - } - } - }; -}; - -class spell_web_wrap_damage : public AuraScript -{ -public: - PrepareAuraScript(spell_web_wrap_damage); - - bool Validate(SpellInfo const* /*spellInfo*/) override - { - return ValidateSpellInfo({ SPELL_WEB_WRAP_SUMMON }); - } - - void OnPeriodic(AuraEffect const* aurEff) - { - if (aurEff->GetTickNumber() == 2) - { - GetTarget()->CastSpell(GetTarget(), SPELL_WEB_WRAP_SUMMON, true); - } - } - - void Register() override - { - OnEffectPeriodic += AuraEffectPeriodicFn(spell_web_wrap_damage::OnPeriodic, EFFECT_1, SPELL_AURA_PERIODIC_DAMAGE); - } -}; +using namespace Maexxna; void AddSC_boss_maexxna() { diff --git a/src/server/scripts/Northrend/Naxxramas/boss_maexxna.h b/src/server/scripts/Northrend/Naxxramas/boss_maexxna.h new file mode 100644 index 00000000000000..a275eedbde3068 --- /dev/null +++ b/src/server/scripts/Northrend/Naxxramas/boss_maexxna.h @@ -0,0 +1,371 @@ +#ifndef BOSS_MAEXXNA_H_ +#define BOSS_MAEXXNA_H_ + +#include "CreatureScript.h" +#include "Player.h" +#include "PassiveAI.h" +#include "ScriptedCreature.h" +#include "SpellAuraEffects.h" +#include "SpellScript.h" +#include "SpellScriptLoader.h" +#include "SpellInfo.h" +#include "naxxramas.h" + +namespace Maexxna { + +enum Spells +{ + SPELL_WEB_SPRAY_10 = 29484, + SPELL_WEB_SPRAY_25 = 54125, + SPELL_POISON_SHOCK_10 = 28741, + SPELL_POISON_SHOCK_25 = 54122, + SPELL_NECROTIC_POISON_10 = 54121, + SPELL_NECROTIC_POISON_25 = 28776, + SPELL_FRENZY_10 = 54123, + SPELL_FRENZY_25 = 54124, + SPELL_WEB_WRAP_STUN = 28622, + SPELL_WEB_WRAP_SUMMON = 28627, + SPELL_WEB_WRAP_KILL_WEBS = 52512, + SPELL_WEB_WRAP_PACIFY_5 = 28618 // 5 seconds pacify silence +}; + +enum Events +{ + EVENT_WEB_SPRAY = 1, + EVENT_POISON_SHOCK = 2, + EVENT_NECROTIC_POISON = 3, + EVENT_WEB_WRAP = 4, + EVENT_HEALTH_CHECK = 5, + EVENT_SUMMON_SPIDERLINGS = 6, + EVENT_WEB_WRAP_APPLY_STUN = 7 +}; + +enum Emotes +{ + EMOTE_SPIDERS = 0, + EMOTE_WEB_WRAP = 1, + EMOTE_WEB_SPRAY = 2 +}; + +enum Misc +{ + NPC_WEB_WRAP = 16486, + NPC_MAEXXNA_SPIDERLING = 17055 +}; + +const Position PosWrap[7] = +{ + {3496.615f, -3834.182f, 320.7863f}, + {3509.108f, -3833.922f, 320.4750f}, + {3523.644f, -3838.309f, 320.5775f}, + {3538.152f, -3846.353f, 320.5188f}, + {3546.219f, -3856.167f, 320.9324f}, + {3555.135f, -3869.507f, 320.8307f}, + {3560.282f, -3886.143f, 321.2827f} +}; + +struct WebTargetSelector +{ + WebTargetSelector(Unit* maexxna) : _maexxna(maexxna) {} + bool operator()(Unit const* target) const + { + if (!target->IsPlayer()) // never web nonplayers (pets, guardians, etc.) + return false; + if (_maexxna->GetVictim() == target) // never target tank + return false; + if (target->HasAura(SPELL_WEB_WRAP_STUN)) // never target targets that are already webbed + return false; + return true; + } + + private: + Unit const* _maexxna; +}; + +class boss_maexxna : public CreatureScript +{ +public: + boss_maexxna() : CreatureScript("boss_maexxna") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_maexxnaAI : public BossAI + { + explicit boss_maexxnaAI(Creature* c) : BossAI(c, BOSS_MAEXXNA), summons(me) + { + pInstance = me->GetInstanceScript(); + } + + InstanceScript* pInstance; + EventMap events; + SummonList summons; + + GuidList wraps; + + bool IsInRoom() + { + if (me->GetExactDist(3486.6f, -3890.6f, 291.8f) > 100.0f) + { + EnterEvadeMode(); + return false; + } + return true; + } + + void Reset() override + { + BossAI::Reset(); + events.Reset(); + summons.DespawnAll(); + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_MAEXXNA_GATE))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + } + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + me->SetInCombatWithZone(); + events.ScheduleEvent(EVENT_WEB_WRAP, 20s); + events.ScheduleEvent(EVENT_WEB_SPRAY, 40s); + events.ScheduleEvent(EVENT_POISON_SHOCK, 10s); + events.ScheduleEvent(EVENT_NECROTIC_POISON, 5s); + events.ScheduleEvent(EVENT_HEALTH_CHECK, 1s); + events.ScheduleEvent(EVENT_SUMMON_SPIDERLINGS, 30s); + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_MAEXXNA_GATE))) + { + go->SetGoState(GO_STATE_READY); + } + } + } + + void JustSummoned(Creature* cr) override + { + if (cr->GetEntry() == NPC_MAEXXNA_SPIDERLING) + { + cr->SetInCombatWithZone(); + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0)) + { + cr->AI()->AttackStart(target); + } + } + summons.Summon(cr); + } + + void KilledUnit(Unit* who) override + { + if (who->IsPlayer() && pInstance) + { + pInstance->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + } + + void DoCastWebWrap() + { + std::list candidates; + SelectTargetList(candidates, RAID_MODE(1, 2), SelectTargetMethod::Random, 0, WebTargetSelector(me)); + + std::vector positions {0, 1, 2, 3, 4, 5, 6}; + Acore::Containers::RandomShuffle(positions); + + if (candidates.empty()) + return; + + for (int i = 0; i < RAID_MODE(1, 2) ; i++) + { + if (candidates.empty()) + break; + const Position &randomPos = PosWrap[positions[i]]; + + auto itr = candidates.begin(); + + if (candidates.size() > 1) + std::advance(itr, urand(0, candidates.size() - 1)); + + Unit *target = *itr; + candidates.erase(itr); + + float dx = randomPos.GetPositionX() - target->GetPositionX(); + float dy = randomPos.GetPositionY() - target->GetPositionY(); + float distXY = std::hypotf(dx, dy); + + // smooth knockback arc that avoids the ceiling + float horizontalSpeed = distXY / 1.5f; + float verticalSpeed = 28.0f; + if (distXY <= 10.0f) + verticalSpeed = 12.0f; + else if (distXY <= 20.0f) + verticalSpeed = 16.0f; + else if (distXY <= 30.0f) + verticalSpeed = 20.0f; + else if (distXY <= 40.0f) + verticalSpeed = 24.0f; + + target->KnockbackFrom(randomPos.GetPositionX(), randomPos.GetPositionY(), -horizontalSpeed, verticalSpeed); + me->CastSpell(target, SPELL_WEB_WRAP_PACIFY_5, true); // pacify silence for 5 seconds + + wraps.push_back(target->GetGUID()); + } + events.ScheduleEvent(EVENT_WEB_WRAP_APPLY_STUN, 2s); + } + + void UpdateAI(uint32 diff) override + { + if (!IsInRoom()) + return; + + if (!UpdateVictim()) + return; + + events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_WEB_SPRAY: + Talk(EMOTE_WEB_SPRAY); + me->CastSpell(me, RAID_MODE(SPELL_WEB_SPRAY_10, SPELL_WEB_SPRAY_25), true); + events.Repeat(40s); + break; + case EVENT_POISON_SHOCK: + me->CastSpell(me->GetVictim(), RAID_MODE(SPELL_POISON_SHOCK_10, SPELL_POISON_SHOCK_25), false); + events.Repeat(10s); + break; + case EVENT_NECROTIC_POISON: + me->CastSpell(me->GetVictim(), RAID_MODE(SPELL_NECROTIC_POISON_10, SPELL_NECROTIC_POISON_25), false); + events.Repeat(30s); + break; + case EVENT_SUMMON_SPIDERLINGS: + Talk(EMOTE_SPIDERS); + for (uint8 i = 0; i < 8; ++i) + { + me->SummonCreature(NPC_MAEXXNA_SPIDERLING, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), me->GetOrientation()); + } + events.Repeat(40s); + break; + case EVENT_HEALTH_CHECK: + if (me->GetHealthPct() < 30) + { + me->CastSpell(me, RAID_MODE(SPELL_FRENZY_10, SPELL_FRENZY_25), true); + break; + } + events.Repeat(1s); + break; + case EVENT_WEB_WRAP: + Talk(EMOTE_WEB_WRAP); + DoCastWebWrap(); + events.Repeat(40s); + break; + case EVENT_WEB_WRAP_APPLY_STUN: + { + for (auto& p : wraps) + { + if (Player* player = ObjectAccessor::GetPlayer(*me, p)) + { + player->CastSpell(player, SPELL_WEB_WRAP_STUN, true); + } + } + wraps.clear(); + break; + } + } + DoMeleeAttackIfReady(); + } + }; +}; + +class boss_maexxna_webwrap : public CreatureScript +{ +public: + boss_maexxna_webwrap() : CreatureScript("boss_maexxna_webwrap") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_maexxna_webwrapAI : public NullCreatureAI + { + explicit boss_maexxna_webwrapAI(Creature* c) : NullCreatureAI(c) { } + + ObjectGuid victimGUID; + + void IsSummonedBy(WorldObject* summoner) override + { + if (!summoner) + return; + victimGUID = summoner->GetGUID(); + } + + void JustDied(Unit* /*killer*/) override + { + if (victimGUID) + { + if (Unit* victim = ObjectAccessor::GetUnit(*me, victimGUID)) + { + if (victim->IsAlive()) + { + victim->RemoveAurasDueToSpell(SPELL_WEB_WRAP_STUN); + victim->RemoveAurasDueToSpell(SPELL_WEB_WRAP_SUMMON); + } + } + } + } + + void UpdateAI(uint32 /*diff*/) override + { + if (victimGUID) + { + if (Unit* victim = ObjectAccessor::GetUnit(*me, victimGUID)) + { + if (!victim->IsAlive()) + { + me->CastSpell(me, SPELL_WEB_WRAP_KILL_WEBS, true); + } + } + } + } + }; +}; + +class spell_web_wrap_damage : public AuraScript +{ +public: + PrepareAuraScript(spell_web_wrap_damage); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_WEB_WRAP_SUMMON }); + } + + void OnPeriodic(AuraEffect const* aurEff) + { + if (aurEff->GetTickNumber() == 2) + { + GetTarget()->CastSpell(GetTarget(), SPELL_WEB_WRAP_SUMMON, true); + } + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_web_wrap_damage::OnPeriodic, EFFECT_1, SPELL_AURA_PERIODIC_DAMAGE); + } +}; + +} // namespace Maexxna +#endif \ No newline at end of file diff --git a/src/server/scripts/Northrend/Naxxramas/boss_noth.cpp b/src/server/scripts/Northrend/Naxxramas/boss_noth.cpp index c461384fd88238..4cbc7a79a0cf11 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_noth.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_noth.cpp @@ -15,289 +15,13 @@ * with this program. If not, see . */ +#include "boss_noth.h" #include "CreatureScript.h" #include "ScriptedCreature.h" #include "naxxramas.h" -enum Says -{ - SAY_AGGRO = 0, - SAY_SUMMON = 1, - SAY_SLAY = 2, - SAY_DEATH = 3, - EMOTE_SUMMON = 4, - EMOTE_SUMMON_WAVE = 5, - EMOTE_TELEPORT_BALCONY = 6, - EMOTE_TELEPORT_BACK = 7, - EMOTE_BLINK = 8 -}; - -enum Spells -{ - SPELL_CURSE_OF_THE_PLAGUEBRINGER_10 = 29213, - SPELL_CURSE_OF_THE_PLAGUEBRINGER_25 = 54835, - SPELL_CRIPPLE_10 = 29212, - SPELL_CRIPPLE_25 = 54814, - SPELL_SUMMON_PLAGUED_WARRIORS = 29237, - SPELL_TELEPORT = 29216, - SPELL_TELEPORT_BACK = 29231, - SPELL_BERSERK = 68378, - SPELL_BLINK = 29208 -}; - -enum Events -{ - EVENT_CURSE = 1, - EVENT_CRIPPLE = 2, - EVENT_SUMMON_PLAGUED_WARRIOR_ANNOUNCE = 3, - EVENT_MOVE_TO_BALCONY = 4, - EVENT_BLINK = 5, - EVENT_MOVE_TO_GROUND = 6, - EVENT_SUMMON_PLAGUED_WARRIOR_REAL = 7, - EVENT_BALCONY_SUMMON_ANNOUNCE = 8, - EVENT_BALCONY_SUMMON_REAL = 9 -}; - -enum Misc -{ - NPC_PLAGUED_WARRIOR = 16984, - NPC_PLAGUED_CHAMPION = 16983, - NPC_PLAGUED_GUARDIAN = 16981 -}; - -const Position summoningPosition[5] = -{ - {2728.06f, -3535.38f, 263.21f, 2.75f}, - {2725.71f, -3514.80f, 263.23f, 2.86f}, - {2728.24f, -3465.08f, 264.20f, 3.56f}, - {2704.79f, -3459.17f, 263.74f, 4.25f}, - {2652.02f, -3459.13f, 262.50f, 5.39f} -}; - -const Position nothPosition = {2684.94f, -3502.53f, 261.31f, 4.7f}; - -class boss_noth : public CreatureScript -{ -public: - boss_noth() : CreatureScript("boss_noth") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_nothAI : public BossAI - { - explicit boss_nothAI(Creature* c) : BossAI(c, BOSS_NOTH), summons(me) - { - pInstance = me->GetInstanceScript(); - } - - InstanceScript* pInstance; - uint8 timesInBalcony; - EventMap events; - SummonList summons; - - void StartGroundPhase() - { - me->SetReactState(REACT_AGGRESSIVE); - me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_DISABLE_MOVE); - me->SetControlled(false, UNIT_STATE_ROOT); - events.Reset(); - events.ScheduleEvent(EVENT_MOVE_TO_BALCONY, 110s); - events.ScheduleEvent(EVENT_CURSE, 15s); - events.ScheduleEvent(EVENT_SUMMON_PLAGUED_WARRIOR_ANNOUNCE, 10s); - if (Is25ManRaid()) - { - events.ScheduleEvent(EVENT_BLINK, 26s); - } - } - - void StartBalconyPhase() - { - me->SetReactState(REACT_PASSIVE); - me->AttackStop(); - me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_DISABLE_MOVE); - me->SetControlled(true, UNIT_STATE_ROOT); - events.Reset(); - events.ScheduleEvent(EVENT_BALCONY_SUMMON_ANNOUNCE, 4s); - events.ScheduleEvent(EVENT_MOVE_TO_GROUND, 70s); - } - - void SummonHelper(uint32 entry, uint32 count) - { - for (uint8 i = 0; i < count; ++i) - { - me->SummonCreature(entry, summoningPosition[urand(0, 4)]); - } - } - - bool IsInRoom() - { - if (me->GetExactDist(2684.8f, -3502.5f, 261.3f) > 80.0f) - { - EnterEvadeMode(EVADE_REASON_OTHER); - return false; - } - return true; - } - - void Reset() override - { - BossAI::Reset(); - events.Reset(); - summons.DespawnAll(); - me->CastSpell(me, SPELL_TELEPORT_BACK, true); - me->SetControlled(false, UNIT_STATE_ROOT); - me->SetReactState(REACT_AGGRESSIVE); - timesInBalcony = 0; - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_NOTH_ENTRY_GATE))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - } - } - - void EnterEvadeMode(EvadeReason why) override - { - me->SetControlled(false, UNIT_STATE_ROOT); - ScriptedAI::EnterEvadeMode(why); - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - Talk(SAY_AGGRO); - StartGroundPhase(); - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_NOTH_ENTRY_GATE))) - { - go->SetGoState(GO_STATE_READY); - } - } - } - - void JustSummoned(Creature* summon) override - { - summons.Summon(summon); - summon->SetInCombatWithZone(); - } - - void JustDied(Unit* killer) override - { - if (me->GetPositionZ() > 270.27f) - { - me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_DISABLE_MOVE); - me->NearTeleportTo(nothPosition.GetPositionX(), nothPosition.GetPositionY(), nothPosition.GetPositionZ(), nothPosition.GetOrientation(), true); - } - BossAI::JustDied(killer); - Talk(SAY_DEATH); - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_NOTH_ENTRY_GATE))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - } - } - - void KilledUnit(Unit* who) override - { - if (!who->IsPlayer()) - return; - - Talk(SAY_SLAY); - if (pInstance) - { - pInstance->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void UpdateAI(uint32 diff) override - { - if (!IsInRoom()) - return; - - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - switch (events.ExecuteEvent()) - { - // GROUND - case EVENT_CURSE: - if (events.GetPhaseMask() == 0) - { - me->CastCustomSpell(RAID_MODE(SPELL_CURSE_OF_THE_PLAGUEBRINGER_10, SPELL_CURSE_OF_THE_PLAGUEBRINGER_25), SPELLVALUE_MAX_TARGETS, RAID_MODE(3, 10), me, false); - } - events.Repeat(25s); - break; - case EVENT_SUMMON_PLAGUED_WARRIOR_ANNOUNCE: - Talk(SAY_SUMMON); - Talk(EMOTE_SUMMON); - events.Repeat(30s); - events.ScheduleEvent(EVENT_SUMMON_PLAGUED_WARRIOR_REAL, 4s); - break; - case EVENT_SUMMON_PLAGUED_WARRIOR_REAL: - me->CastSpell(me, SPELL_SUMMON_PLAGUED_WARRIORS, true); - SummonHelper(NPC_PLAGUED_WARRIOR, RAID_MODE(2, 3)); - break; - case EVENT_MOVE_TO_BALCONY: - Talk(EMOTE_TELEPORT_BALCONY); - me->CastSpell(me, SPELL_TELEPORT, true); - StartBalconyPhase(); - break; - case EVENT_BLINK: - DoResetThreatList(); - me->CastSpell(me, RAID_MODE(SPELL_CRIPPLE_10, SPELL_CRIPPLE_25), false); - me->CastSpell(me, SPELL_BLINK, true); - Talk(EMOTE_BLINK); - events.Repeat(30s); - break; - // BALCONY - case EVENT_BALCONY_SUMMON_ANNOUNCE: - Talk(EMOTE_SUMMON_WAVE); - events.Repeat(30s); - events.ScheduleEvent(EVENT_BALCONY_SUMMON_REAL, 4s); - break; - case EVENT_BALCONY_SUMMON_REAL: - me->CastSpell(me, SPELL_SUMMON_PLAGUED_WARRIORS, true); // visual - switch (timesInBalcony) - { - case 0: - SummonHelper(NPC_PLAGUED_CHAMPION, RAID_MODE(2, 4)); - break; - case 1: - SummonHelper(NPC_PLAGUED_CHAMPION, RAID_MODE(1, 2)); - SummonHelper(NPC_PLAGUED_GUARDIAN, RAID_MODE(1, 2)); - break; - default: - SummonHelper(NPC_PLAGUED_GUARDIAN, RAID_MODE(2, 4)); - break; - } - break; - case EVENT_MOVE_TO_GROUND: - Talk(EMOTE_TELEPORT_BACK); - me->CastSpell(me, SPELL_TELEPORT_BACK, true); - timesInBalcony++; - if (timesInBalcony == 3) - { - DoCastSelf(SPELL_BERSERK); - } - StartGroundPhase(); - break; - } - if (me->HasReactState(REACT_AGGRESSIVE)) - DoMeleeAttackIfReady(); - } - }; -}; +using namespace Noth; void AddSC_boss_noth() { diff --git a/src/server/scripts/Northrend/Naxxramas/boss_noth.h b/src/server/scripts/Northrend/Naxxramas/boss_noth.h new file mode 100644 index 00000000000000..05225e19ec4cfd --- /dev/null +++ b/src/server/scripts/Northrend/Naxxramas/boss_noth.h @@ -0,0 +1,293 @@ +#ifndef BOSS_NOTH_H_ +#define BOSS_NOTH_H_ + +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "SpellInfo.h" +#include "naxxramas.h" + +namespace Noth { + +enum Says +{ + SAY_AGGRO = 0, + SAY_SUMMON = 1, + SAY_SLAY = 2, + SAY_DEATH = 3, + EMOTE_SUMMON = 4, + EMOTE_SUMMON_WAVE = 5, + EMOTE_TELEPORT_BALCONY = 6, + EMOTE_TELEPORT_BACK = 7, + EMOTE_BLINK = 8 +}; + +enum Spells +{ + SPELL_CURSE_OF_THE_PLAGUEBRINGER_10 = 29213, + SPELL_CURSE_OF_THE_PLAGUEBRINGER_25 = 54835, + SPELL_CRIPPLE_10 = 29212, + SPELL_CRIPPLE_25 = 54814, + SPELL_SUMMON_PLAGUED_WARRIORS = 29237, + SPELL_TELEPORT = 29216, + SPELL_TELEPORT_BACK = 29231, + SPELL_BERSERK = 68378, + SPELL_BLINK = 29208 +}; + +enum Events +{ + EVENT_CURSE = 1, + EVENT_CRIPPLE = 2, + EVENT_SUMMON_PLAGUED_WARRIOR_ANNOUNCE = 3, + EVENT_MOVE_TO_BALCONY = 4, + EVENT_BLINK = 5, + EVENT_MOVE_TO_GROUND = 6, + EVENT_SUMMON_PLAGUED_WARRIOR_REAL = 7, + EVENT_BALCONY_SUMMON_ANNOUNCE = 8, + EVENT_BALCONY_SUMMON_REAL = 9 +}; + +enum Misc +{ + NPC_PLAGUED_WARRIOR = 16984, + NPC_PLAGUED_CHAMPION = 16983, + NPC_PLAGUED_GUARDIAN = 16981 +}; + +const Position summoningPosition[5] = +{ + {2728.06f, -3535.38f, 263.21f, 2.75f}, + {2725.71f, -3514.80f, 263.23f, 2.86f}, + {2728.24f, -3465.08f, 264.20f, 3.56f}, + {2704.79f, -3459.17f, 263.74f, 4.25f}, + {2652.02f, -3459.13f, 262.50f, 5.39f} +}; + +const Position nothPosition = {2684.94f, -3502.53f, 261.31f, 4.7f}; + +class boss_noth : public CreatureScript +{ +public: + boss_noth() : CreatureScript("boss_noth") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_nothAI : public BossAI + { + explicit boss_nothAI(Creature* c) : BossAI(c, BOSS_NOTH), summons(me) + { + pInstance = me->GetInstanceScript(); + } + + InstanceScript* pInstance; + uint8 timesInBalcony; + EventMap events; + SummonList summons; + + void StartGroundPhase() + { + me->SetReactState(REACT_AGGRESSIVE); + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_DISABLE_MOVE); + me->SetControlled(false, UNIT_STATE_ROOT); + events.Reset(); + events.ScheduleEvent(EVENT_MOVE_TO_BALCONY, 110s); + events.ScheduleEvent(EVENT_CURSE, 15s); + events.ScheduleEvent(EVENT_SUMMON_PLAGUED_WARRIOR_ANNOUNCE, 10s); + if (Is25ManRaid()) + { + events.ScheduleEvent(EVENT_BLINK, 26s); + } + } + + void StartBalconyPhase() + { + me->SetReactState(REACT_PASSIVE); + me->AttackStop(); + me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_DISABLE_MOVE); + me->SetControlled(true, UNIT_STATE_ROOT); + events.Reset(); + events.ScheduleEvent(EVENT_BALCONY_SUMMON_ANNOUNCE, 4s); + events.ScheduleEvent(EVENT_MOVE_TO_GROUND, 70s); + } + + void SummonHelper(uint32 entry, uint32 count) + { + for (uint8 i = 0; i < count; ++i) + { + me->SummonCreature(entry, summoningPosition[urand(0, 4)]); + } + } + + bool IsInRoom() + { + if (me->GetExactDist(2684.8f, -3502.5f, 261.3f) > 80.0f) + { + EnterEvadeMode(EVADE_REASON_OTHER); + return false; + } + return true; + } + + void Reset() override + { + BossAI::Reset(); + events.Reset(); + summons.DespawnAll(); + me->CastSpell(me, SPELL_TELEPORT_BACK, true); + me->SetControlled(false, UNIT_STATE_ROOT); + me->SetReactState(REACT_AGGRESSIVE); + timesInBalcony = 0; + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_NOTH_ENTRY_GATE))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + } + } + + void EnterEvadeMode(EvadeReason why) override + { + me->SetControlled(false, UNIT_STATE_ROOT); + ScriptedAI::EnterEvadeMode(why); + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + Talk(SAY_AGGRO); + StartGroundPhase(); + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_NOTH_ENTRY_GATE))) + { + go->SetGoState(GO_STATE_READY); + } + } + } + + void JustSummoned(Creature* summon) override + { + summons.Summon(summon); + summon->SetInCombatWithZone(); + } + + void JustDied(Unit* killer) override + { + if (me->GetPositionZ() > 270.27f) + { + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_DISABLE_MOVE); + me->NearTeleportTo(nothPosition.GetPositionX(), nothPosition.GetPositionY(), nothPosition.GetPositionZ(), nothPosition.GetOrientation(), true); + } + BossAI::JustDied(killer); + Talk(SAY_DEATH); + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_NOTH_ENTRY_GATE))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + } + } + + void KilledUnit(Unit* who) override + { + if (!who->IsPlayer()) + return; + + Talk(SAY_SLAY); + if (pInstance) + { + pInstance->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void UpdateAI(uint32 diff) override + { + if (!IsInRoom()) + return; + + if (!UpdateVictim()) + return; + + events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + // GROUND + case EVENT_CURSE: + if (events.GetPhaseMask() == 0) + { + me->CastCustomSpell(RAID_MODE(SPELL_CURSE_OF_THE_PLAGUEBRINGER_10, SPELL_CURSE_OF_THE_PLAGUEBRINGER_25), SPELLVALUE_MAX_TARGETS, RAID_MODE(3, 10), me, false); + } + events.Repeat(25s); + break; + case EVENT_SUMMON_PLAGUED_WARRIOR_ANNOUNCE: + Talk(SAY_SUMMON); + Talk(EMOTE_SUMMON); + events.Repeat(30s); + events.ScheduleEvent(EVENT_SUMMON_PLAGUED_WARRIOR_REAL, 4s); + break; + case EVENT_SUMMON_PLAGUED_WARRIOR_REAL: + me->CastSpell(me, SPELL_SUMMON_PLAGUED_WARRIORS, true); + SummonHelper(NPC_PLAGUED_WARRIOR, RAID_MODE(2, 3)); + break; + case EVENT_MOVE_TO_BALCONY: + Talk(EMOTE_TELEPORT_BALCONY); + me->CastSpell(me, SPELL_TELEPORT, true); + StartBalconyPhase(); + break; + case EVENT_BLINK: + DoResetThreatList(); + me->CastSpell(me, RAID_MODE(SPELL_CRIPPLE_10, SPELL_CRIPPLE_25), false); + me->CastSpell(me, SPELL_BLINK, true); + Talk(EMOTE_BLINK); + events.Repeat(30s); + break; + // BALCONY + case EVENT_BALCONY_SUMMON_ANNOUNCE: + Talk(EMOTE_SUMMON_WAVE); + events.Repeat(30s); + events.ScheduleEvent(EVENT_BALCONY_SUMMON_REAL, 4s); + break; + case EVENT_BALCONY_SUMMON_REAL: + me->CastSpell(me, SPELL_SUMMON_PLAGUED_WARRIORS, true); // visual + switch (timesInBalcony) + { + case 0: + SummonHelper(NPC_PLAGUED_CHAMPION, RAID_MODE(2, 4)); + break; + case 1: + SummonHelper(NPC_PLAGUED_CHAMPION, RAID_MODE(1, 2)); + SummonHelper(NPC_PLAGUED_GUARDIAN, RAID_MODE(1, 2)); + break; + default: + SummonHelper(NPC_PLAGUED_GUARDIAN, RAID_MODE(2, 4)); + break; + } + break; + case EVENT_MOVE_TO_GROUND: + Talk(EMOTE_TELEPORT_BACK); + me->CastSpell(me, SPELL_TELEPORT_BACK, true); + timesInBalcony++; + if (timesInBalcony == 3) + { + DoCastSelf(SPELL_BERSERK); + } + StartGroundPhase(); + break; + } + if (me->HasReactState(REACT_AGGRESSIVE)) + DoMeleeAttackIfReady(); + } + }; +}; + +} + +#endif \ No newline at end of file diff --git a/src/server/scripts/Northrend/Naxxramas/boss_patchwerk.cpp b/src/server/scripts/Northrend/Naxxramas/boss_patchwerk.cpp index aa61c5fb1211d1..aa8783aa54e80d 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_patchwerk.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_patchwerk.cpp @@ -15,184 +15,13 @@ * with this program. If not, see . */ +#include "boss_patchwerk.h" #include "CreatureScript.h" #include "ScriptedCreature.h" #include "naxxramas.h" -enum Yells -{ - SAY_AGGRO = 0, - SAY_SLAY = 1, - SAY_DEATH = 2, - EMOTE_BERSERK = 3, - EMOTE_ENRAGE = 4 -}; - -enum Spells -{ - SPELL_HATEFUL_STRIKE_10 = 41926, - SPELL_HATEFUL_STRIKE_25 = 59192, - SPELL_FRENZY = 28131, - SPELL_BERSERK = 26662, - SPELL_SLIME_BOLT = 32309 -}; - -enum Events -{ - EVENT_HEALTH_CHECK = 1, - EVENT_HATEFUL_STRIKE = 2, - EVENT_SLIME_BOLT = 3, - EVENT_BERSERK = 4 -}; - -enum Misc -{ - ACHIEV_TIMED_START_EVENT = 10286 -}; - -class boss_patchwerk : public CreatureScript -{ -public: - boss_patchwerk() : CreatureScript("boss_patchwerk") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_patchwerkAI : public BossAI - { - explicit boss_patchwerkAI(Creature* c) : BossAI(c, BOSS_PATCHWERK) - { - pInstance = me->GetInstanceScript(); - } - - EventMap events; - InstanceScript* pInstance; - - void Reset() override - { - BossAI::Reset(); - events.Reset(); - } - - void KilledUnit(Unit* who) override - { - if (!who->IsPlayer()) - return; - - if (!urand(0, 3)) - { - Talk(SAY_SLAY); - } - if (pInstance) - { - pInstance->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void JustDied(Unit* killer) override - { - BossAI::JustDied(killer); - Talk(SAY_DEATH); - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - Talk(SAY_AGGRO); - me->SetInCombatWithZone(); - events.ScheduleEvent(EVENT_HATEFUL_STRIKE, 1500ms); - events.ScheduleEvent(EVENT_BERSERK, 6min); - events.ScheduleEvent(EVENT_HEALTH_CHECK, 1s); - if (pInstance) - { - pInstance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_TIMED_START_EVENT); - } - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - switch (events.ExecuteEvent()) - { - case EVENT_HATEFUL_STRIKE: - { - // Cast Hateful strike on the player with the highest amount of HP within melee distance, and second threat amount - std::list meleeRangeTargets; - Unit* finalTarget = nullptr; - uint8 counter = 0; - auto i = me->GetThreatMgr().GetThreatList().begin(); - for (; i != me->GetThreatMgr().GetThreatList().end(); ++i, ++counter) - { - // Gather all units with melee range - Unit* target = (*i)->getTarget(); - if (me->IsWithinMeleeRange(target)) - { - meleeRangeTargets.push_back(target); - } - // and add threat to most hated - if (counter < RAID_MODE(2, 3)) - { - me->AddThreat(target, 500.0f); - } - } - counter = 0; - std::list>::iterator itr; - for (itr = meleeRangeTargets.begin(); itr != meleeRangeTargets.end(); ++itr, ++counter) - { - // if there is only one target available - if (meleeRangeTargets.size() == 1) - { - finalTarget = (*itr); - } - else if (counter > 0) // skip first target - { - if (!finalTarget || (*itr)->GetHealth() > finalTarget->GetHealth()) - { - finalTarget = (*itr); - } - // third loop - if (counter >= 2) - break; - } - } - if (finalTarget) - { - me->CastSpell(finalTarget, RAID_MODE(SPELL_HATEFUL_STRIKE_10, SPELL_HATEFUL_STRIKE_25), false); - } - events.Repeat(1s); - break; - } - case EVENT_BERSERK: - Talk(EMOTE_BERSERK); - me->CastSpell(me, SPELL_BERSERK, true); - events.ScheduleEvent(EVENT_SLIME_BOLT, 3s); - break; - case EVENT_SLIME_BOLT: - me->CastSpell(me, SPELL_SLIME_BOLT, false); - events.Repeat(3s); - break; - case EVENT_HEALTH_CHECK: - if (me->GetHealthPct() <= 5) - { - Talk(EMOTE_ENRAGE); - me->CastSpell(me, SPELL_FRENZY, true); - break; - } - events.Repeat(1s); - break; - } - DoMeleeAttackIfReady(); - } - }; -}; +using namespace PatchWerk; void AddSC_boss_patchwerk() { diff --git a/src/server/scripts/Northrend/Naxxramas/boss_patchwerk.h b/src/server/scripts/Northrend/Naxxramas/boss_patchwerk.h new file mode 100644 index 00000000000000..2cc7600a0d0785 --- /dev/null +++ b/src/server/scripts/Northrend/Naxxramas/boss_patchwerk.h @@ -0,0 +1,187 @@ +#ifndef BOSS_PATCHWERK_H_ +#define BOSS_PATCHWERK_H_ + +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "SpellInfo.h" +#include "naxxramas.h" + +namespace PatchWerk { + +enum Yells +{ + SAY_AGGRO = 0, + SAY_SLAY = 1, + SAY_DEATH = 2, + EMOTE_BERSERK = 3, + EMOTE_ENRAGE = 4 +}; + +enum Spells +{ + SPELL_HATEFUL_STRIKE_10 = 41926, + SPELL_HATEFUL_STRIKE_25 = 59192, + SPELL_FRENZY = 28131, + SPELL_BERSERK = 26662, + SPELL_SLIME_BOLT = 32309 +}; + +enum Events +{ + EVENT_HEALTH_CHECK = 1, + EVENT_HATEFUL_STRIKE = 2, + EVENT_SLIME_BOLT = 3, + EVENT_BERSERK = 4 +}; + +enum Misc +{ + ACHIEV_TIMED_START_EVENT = 10286 +}; + +class boss_patchwerk : public CreatureScript +{ +public: + boss_patchwerk() : CreatureScript("boss_patchwerk") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_patchwerkAI : public BossAI + { + explicit boss_patchwerkAI(Creature* c) : BossAI(c, BOSS_PATCHWERK) + { + pInstance = me->GetInstanceScript(); + } + + EventMap events; + InstanceScript* pInstance; + + void Reset() override + { + BossAI::Reset(); + events.Reset(); + } + + void KilledUnit(Unit* who) override + { + if (!who->IsPlayer()) + return; + + if (!urand(0, 3)) + { + Talk(SAY_SLAY); + } + if (pInstance) + { + pInstance->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + Talk(SAY_DEATH); + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + Talk(SAY_AGGRO); + me->SetInCombatWithZone(); + events.ScheduleEvent(EVENT_HATEFUL_STRIKE, 1500ms); + events.ScheduleEvent(EVENT_BERSERK, 6min); + events.ScheduleEvent(EVENT_HEALTH_CHECK, 1s); + if (pInstance) + { + pInstance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_TIMED_START_EVENT); + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_HATEFUL_STRIKE: + { + // Cast Hateful strike on the player with the highest amount of HP within melee distance, and second threat amount + std::list meleeRangeTargets; + Unit* finalTarget = nullptr; + uint8 counter = 0; + auto i = me->GetThreatMgr().GetThreatList().begin(); + for (; i != me->GetThreatMgr().GetThreatList().end(); ++i, ++counter) + { + // Gather all units with melee range + Unit* target = (*i)->getTarget(); + if (me->IsWithinMeleeRange(target)) + { + meleeRangeTargets.push_back(target); + } + // and add threat to most hated + if (counter < RAID_MODE(2, 3)) + { + me->AddThreat(target, 500.0f); + } + } + counter = 0; + std::list>::iterator itr; + for (itr = meleeRangeTargets.begin(); itr != meleeRangeTargets.end(); ++itr, ++counter) + { + // if there is only one target available + if (meleeRangeTargets.size() == 1) + { + finalTarget = (*itr); + } + else if (counter > 0) // skip first target + { + if (!finalTarget || (*itr)->GetHealth() > finalTarget->GetHealth()) + { + finalTarget = (*itr); + } + // third loop + if (counter >= 2) + break; + } + } + if (finalTarget) + { + me->CastSpell(finalTarget, RAID_MODE(SPELL_HATEFUL_STRIKE_10, SPELL_HATEFUL_STRIKE_25), false); + } + events.Repeat(1s); + break; + } + case EVENT_BERSERK: + Talk(EMOTE_BERSERK); + me->CastSpell(me, SPELL_BERSERK, true); + events.ScheduleEvent(EVENT_SLIME_BOLT, 3s); + break; + case EVENT_SLIME_BOLT: + me->CastSpell(me, SPELL_SLIME_BOLT, false); + events.Repeat(3s); + break; + case EVENT_HEALTH_CHECK: + if (me->GetHealthPct() <= 5) + { + Talk(EMOTE_ENRAGE); + me->CastSpell(me, SPELL_FRENZY, true); + break; + } + events.Repeat(1s); + break; + } + DoMeleeAttackIfReady(); + } + }; +}; + +} +#endif \ No newline at end of file diff --git a/src/server/scripts/Northrend/Naxxramas/boss_razuvious.cpp b/src/server/scripts/Northrend/Naxxramas/boss_razuvious.cpp index 77a687572803ca..106f70e540cab4 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_razuvious.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_razuvious.cpp @@ -15,384 +15,14 @@ * with this program. If not, see . */ +#include "boss_razuvious.h" #include "CreatureScript.h" #include "ScriptedCreature.h" #include "naxxramas.h" #include "SpellInfo.h" -enum Says -{ - SAY_AGGRO = 0, - SAY_SLAY = 1, - SAY_TAUNTED = 2, - SAY_DEATH = 3, - SAY_PATHETIC = 4, - SAY_TARGET_DUMMY = 5, - SAY_DEATH_KNIGHT_UNDERSTUDY = 0, -}; - -enum Spells -{ - SPELL_UNBALANCING_STRIKE = 26613, - SPELL_DISRUPTING_SHOUT_10 = 55543, - SPELL_DISRUPTING_SHOUT_25 = 29107, - SPELL_JAGGED_KNIFE = 55550, - SPELL_HOPELESS = 29125, - SPELL_TAUNT = 29060 -}; - -enum Events -{ - EVENT_UNBALANCING_STRIKE = 1, - EVENT_DISRUPTING_SHOUT = 2, - EVENT_JAGGED_KNIFE = 3 -}; - -enum NPCs -{ - NPC_DEATH_KNIGHT_UNDERSTUDY = 16803, - NPC_TARGET_DUMMY = 16211, -}; - -enum Actions -{ - ACTION_FACE_ME = 0, - ACTION_TALK = 1, - ACTION_EMOTE = 2, - ACTION_SALUTE = 3, - ACTION_BACK_TO_TRAINING = 4, -}; - -enum Misc -{ - GROUP_OOC_RP = 0, - POINT_DEATH_KNIGHT = 0, -}; - -class boss_razuvious : public CreatureScript -{ -public: - boss_razuvious() : CreatureScript("boss_razuvious") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_razuviousAI : public BossAI - { - explicit boss_razuviousAI(Creature* c) : BossAI(c, BOSS_RAZUVIOUS), summons(me) - { - pInstance = me->GetInstanceScript(); - } - - EventMap events; - SummonList summons; - InstanceScript* pInstance; - - void SpawnHelpers() - { - me->SummonCreature(NPC_DEATH_KNIGHT_UNDERSTUDY, 2762.23f, -3085.07f, 267.685f, 1.95f); - me->SummonCreature(NPC_DEATH_KNIGHT_UNDERSTUDY, 2758.24f, -3110.97f, 267.685f, 3.94f); - if (Is25ManRaid()) - { - me->SummonCreature(NPC_DEATH_KNIGHT_UNDERSTUDY, 2782.45f, -3088.03f, 267.685f, 0.75f); - me->SummonCreature(NPC_DEATH_KNIGHT_UNDERSTUDY, 2778.56f, -3113.74f, 267.685f, 5.28f); - } - } - - void JustSummoned(Creature* cr) override - { - summons.Summon(cr); - } - - void Reset() override - { - BossAI::Reset(); - summons.DespawnAll(); - events.Reset(); - SpawnHelpers(); - ScheduleRP(); - } - - void ScheduleInteractWithDeathKnight() - { - if (_rpBuddyGUID) - if (Creature* understudy = ObjectAccessor::GetCreature(*me, _rpBuddyGUID)) - me->SetFacingToObject(understudy); - - scheduler.Schedule(2s, GROUP_OOC_RP, [this](TaskContext /*context*/) - { - if (roll_chance_i(75)) - { - bool longText = roll_chance_i(50); - Talk(longText ? SAY_TARGET_DUMMY : SAY_PATHETIC); - scheduler.Schedule(4s, GROUP_OOC_RP, [this](TaskContext /*context*/) - { - if (_rpBuddyGUID) - if (Creature* understudy = ObjectAccessor::GetCreature(*me, _rpBuddyGUID)) - understudy->AI()->DoAction(ACTION_TALK); - }); - if (longText) - scheduler.DelayGroup(GROUP_OOC_RP, 5s); - } - else - { - me->HandleEmoteCommand(EMOTE_ONESHOT_EXCLAMATION); - scheduler.Schedule(4s, GROUP_OOC_RP, [this](TaskContext /*context*/) - { - if (_rpBuddyGUID) - if (Creature* understudy = ObjectAccessor::GetCreature(*me, _rpBuddyGUID)) - { - if (roll_chance_i(25)) - understudy->AI()->DoAction(ACTION_EMOTE); - else - understudy->AI()->DoAction(ACTION_TALK); - } - }); - } - }).Schedule(4s, GROUP_OOC_RP, [this](TaskContext /*context*/) - { - if (_rpBuddyGUID) - if (Creature* understudy = ObjectAccessor::GetCreature(*me, _rpBuddyGUID)) - understudy->AI()->DoAction(ACTION_FACE_ME); - }).Schedule(10s, GROUP_OOC_RP, [this](TaskContext /*context*/) - { - if (_rpBuddyGUID) - if (Creature* understudy = ObjectAccessor::GetCreature(*me, _rpBuddyGUID)) - understudy->AI()->DoAction(ACTION_SALUTE); - }).Schedule(13s, GROUP_OOC_RP, [this](TaskContext /*context*/) - { - me->ResumeMovement(); - }).Schedule(16s, GROUP_OOC_RP, [this](TaskContext /*context*/) - { - if (_rpBuddyGUID) - if (Creature* understudy = ObjectAccessor::GetCreature(*me, _rpBuddyGUID)) - understudy->AI()->DoAction(ACTION_BACK_TO_TRAINING); - ScheduleRP(); - }); - } - - void MovementInform(uint32 type, uint32 id) override - { - if (type == POINT_MOTION_TYPE && id == POINT_DEATH_KNIGHT) - { - ScheduleInteractWithDeathKnight(); - } - } - - void ScheduleRP() - { - _rpBuddyGUID = Acore::Containers::SelectRandomContainerElement(summons); - scheduler.Schedule(60s, 80s, GROUP_OOC_RP, [this](TaskContext context) - { - if (_rpBuddyGUID) - { - if (Creature* understudy = ObjectAccessor::GetCreature(*me, _rpBuddyGUID)) - { - if (me->GetDistance2d(understudy) <= 6.0f) - { - me->PauseMovement(); - scheduler.Schedule(500ms, GROUP_OOC_RP, [this](TaskContext /*context*/) - { - if (_rpBuddyGUID) - if (Creature* understudy = ObjectAccessor::GetCreature(*me, _rpBuddyGUID)) - me->GetMotionMaster()->MovePoint(POINT_DEATH_KNIGHT, understudy->GetNearPosition(3.2f, understudy->GetRelativeAngle(me))); - }); - return; - } - } - } - context.Repeat(2s); - }); - } - - void KilledUnit(Unit* who) override - { - if (roll_chance_i(30)) - { - Talk(SAY_SLAY); - } - if (who->IsPlayer() && pInstance) - { - pInstance->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void DamageTaken(Unit* who, uint32& damage, DamageEffectType, SpellSchoolMask) override - { - // Damage done by the controlled Death Knight understudies should also count toward damage done by players - if(who && who->IsCreature() && who->GetEntry() == NPC_DEATH_KNIGHT_UNDERSTUDY) - { - me->LowerPlayerDamageReq(damage); - } - } - - void JustDied(Unit* killer) override - { - BossAI::JustDied(killer); - Talk(SAY_DEATH); - me->CastSpell(me, SPELL_HOPELESS, true); - } - - void SpellHit(Unit* caster, SpellInfo const* spell) override - { - if (spell->Id == SPELL_TAUNT) - { - Talk(SAY_TAUNTED, caster); - } - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - scheduler.CancelGroup(GROUP_OOC_RP); - Talk(SAY_AGGRO); - events.ScheduleEvent(EVENT_UNBALANCING_STRIKE, 20s); - events.ScheduleEvent(EVENT_DISRUPTING_SHOUT, 15s); - events.ScheduleEvent(EVENT_JAGGED_KNIFE, 10s); - summons.DoZoneInCombat(); - } - - void UpdateAI(uint32 diff) override - { - if (!me->IsInCombat()) - scheduler.Update(diff); - - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_UNBALANCING_STRIKE: - me->CastSpell(me->GetVictim(), SPELL_UNBALANCING_STRIKE, false); - events.Repeat(20s); - break; - case EVENT_DISRUPTING_SHOUT: - me->CastSpell(me, RAID_MODE(SPELL_DISRUPTING_SHOUT_10, SPELL_DISRUPTING_SHOUT_25), false); - events.Repeat(15s); - break; - case EVENT_JAGGED_KNIFE: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 45.0f)) - { - me->CastSpell(target, SPELL_JAGGED_KNIFE, false); - } - events.Repeat(10s); - break; - } - DoMeleeAttackIfReady(); - } - - private: - ObjectGuid _rpBuddyGUID; - }; -}; - -class boss_razuvious_minion : public CreatureScript -{ -public: - boss_razuvious_minion() : CreatureScript("boss_razuvious_minion") { } - - CreatureAI* GetAI(Creature* creature) const override - { - return GetNaxxramasAI(creature); - } - - struct boss_razuvious_minionAI : public ScriptedAI - { - explicit boss_razuvious_minionAI(Creature* creature) : ScriptedAI(creature) { } - - void Reset() override - { - scheduler.CancelAll(); - ScheduleAttackDummy(); - } - - void ScheduleAttackDummy() - { - me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_READY1H); - if (Creature* targetDummy = me->FindNearestCreature(NPC_TARGET_DUMMY, 10.0f)) - { - me->SetFacingToObject(targetDummy); - } - scheduler.Schedule(6s, 9s, GROUP_OOC_RP, [this](TaskContext context) - { - me->HandleEmoteCommand(EMOTE_ONESHOT_ATTACK1H); - context.Repeat(6s, 9s); - }); - } - - void DoAction(int32 action) override - { - switch (action) - { - case ACTION_FACE_ME: - scheduler.CancelGroup(GROUP_OOC_RP); - me->SetSheath(SHEATH_STATE_UNARMED); - me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_NONE); - if (InstanceScript* instance = me->GetInstanceScript()) - { - if (Creature* creature = instance->GetCreature(DATA_RAZUVIOUS)) - { - me->SetFacingToObject(creature); - } - } - break; - case ACTION_TALK: - Talk(SAY_DEATH_KNIGHT_UNDERSTUDY); - break; - case ACTION_EMOTE: - me->HandleEmoteCommand(EMOTE_ONESHOT_TALK); - break; - case ACTION_SALUTE: - me->HandleEmoteCommand(EMOTE_ONESHOT_SALUTE); - break; - case ACTION_BACK_TO_TRAINING: - me->SetSheath(SHEATH_STATE_MELEE); - ScheduleAttackDummy(); - break; - } - } - - void KilledUnit(Unit* who) override - { - if (who->IsPlayer() && me->GetInstanceScript()) - { - me->GetInstanceScript()->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void JustEngagedWith(Unit* who) override - { - scheduler.CancelGroup(GROUP_OOC_RP); - if (InstanceScript* instance = me->GetInstanceScript()) - { - if (Creature* creature = instance->GetCreature(DATA_RAZUVIOUS)) - { - creature->SetInCombatWithZone(); - creature->AI()->AttackStart(who); - } - } - } - - void UpdateAI(uint32 diff) override - { - scheduler.Update(diff); - if (UpdateVictim()) - { - if (!me->HasUnitState(UNIT_STATE_CASTING) || !me->IsCharmed()) - { - DoMeleeAttackIfReady(); - } - } - } - }; -}; +using namespace Razuvious; void AddSC_boss_razuvious() { diff --git a/src/server/scripts/Northrend/Naxxramas/boss_razuvious.h b/src/server/scripts/Northrend/Naxxramas/boss_razuvious.h new file mode 100644 index 00000000000000..66f43e7258efd3 --- /dev/null +++ b/src/server/scripts/Northrend/Naxxramas/boss_razuvious.h @@ -0,0 +1,386 @@ +#ifndef BOSS_RAZUVIOUS_H_ +#define BOSS_RAZUVIOUS_H_ + +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "SpellInfo.h" +#include "naxxramas.h" + +namespace Razuvious { + +enum Says +{ + SAY_AGGRO = 0, + SAY_SLAY = 1, + SAY_TAUNTED = 2, + SAY_DEATH = 3, + SAY_PATHETIC = 4, + SAY_TARGET_DUMMY = 5, + SAY_DEATH_KNIGHT_UNDERSTUDY = 0, +}; + +enum Spells +{ + SPELL_UNBALANCING_STRIKE = 26613, + SPELL_DISRUPTING_SHOUT_10 = 55543, + SPELL_DISRUPTING_SHOUT_25 = 29107, + SPELL_JAGGED_KNIFE = 55550, + SPELL_HOPELESS = 29125, + SPELL_TAUNT = 29060 +}; + +enum Events +{ + EVENT_UNBALANCING_STRIKE = 1, + EVENT_DISRUPTING_SHOUT = 2, + EVENT_JAGGED_KNIFE = 3 +}; + +enum NPCs +{ + NPC_DEATH_KNIGHT_UNDERSTUDY = 16803, + NPC_TARGET_DUMMY = 16211, +}; + +enum Actions +{ + ACTION_FACE_ME = 0, + ACTION_TALK = 1, + ACTION_EMOTE = 2, + ACTION_SALUTE = 3, + ACTION_BACK_TO_TRAINING = 4, +}; + +enum Misc +{ + GROUP_OOC_RP = 0, + POINT_DEATH_KNIGHT = 0, +}; + +class boss_razuvious : public CreatureScript +{ +public: + boss_razuvious() : CreatureScript("boss_razuvious") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_razuviousAI : public BossAI + { + explicit boss_razuviousAI(Creature* c) : BossAI(c, BOSS_RAZUVIOUS), summons(me) + { + pInstance = me->GetInstanceScript(); + } + + EventMap events; + SummonList summons; + InstanceScript* pInstance; + + void SpawnHelpers() + { + me->SummonCreature(NPC_DEATH_KNIGHT_UNDERSTUDY, 2762.23f, -3085.07f, 267.685f, 1.95f); + me->SummonCreature(NPC_DEATH_KNIGHT_UNDERSTUDY, 2758.24f, -3110.97f, 267.685f, 3.94f); + if (Is25ManRaid()) + { + me->SummonCreature(NPC_DEATH_KNIGHT_UNDERSTUDY, 2782.45f, -3088.03f, 267.685f, 0.75f); + me->SummonCreature(NPC_DEATH_KNIGHT_UNDERSTUDY, 2778.56f, -3113.74f, 267.685f, 5.28f); + } + } + + void JustSummoned(Creature* cr) override + { + summons.Summon(cr); + } + + void Reset() override + { + BossAI::Reset(); + summons.DespawnAll(); + events.Reset(); + SpawnHelpers(); + ScheduleRP(); + } + + void ScheduleInteractWithDeathKnight() + { + if (_rpBuddyGUID) + if (Creature* understudy = ObjectAccessor::GetCreature(*me, _rpBuddyGUID)) + me->SetFacingToObject(understudy); + + scheduler.Schedule(2s, GROUP_OOC_RP, [this](TaskContext /*context*/) + { + if (roll_chance_i(75)) + { + bool longText = roll_chance_i(50); + Talk(longText ? SAY_TARGET_DUMMY : SAY_PATHETIC); + scheduler.Schedule(4s, GROUP_OOC_RP, [this](TaskContext /*context*/) + { + if (_rpBuddyGUID) + if (Creature* understudy = ObjectAccessor::GetCreature(*me, _rpBuddyGUID)) + understudy->AI()->DoAction(ACTION_TALK); + }); + if (longText) + scheduler.DelayGroup(GROUP_OOC_RP, 5s); + } + else + { + me->HandleEmoteCommand(EMOTE_ONESHOT_EXCLAMATION); + scheduler.Schedule(4s, GROUP_OOC_RP, [this](TaskContext /*context*/) + { + if (_rpBuddyGUID) + if (Creature* understudy = ObjectAccessor::GetCreature(*me, _rpBuddyGUID)) + { + if (roll_chance_i(25)) + understudy->AI()->DoAction(ACTION_EMOTE); + else + understudy->AI()->DoAction(ACTION_TALK); + } + }); + } + }).Schedule(4s, GROUP_OOC_RP, [this](TaskContext /*context*/) + { + if (_rpBuddyGUID) + if (Creature* understudy = ObjectAccessor::GetCreature(*me, _rpBuddyGUID)) + understudy->AI()->DoAction(ACTION_FACE_ME); + }).Schedule(10s, GROUP_OOC_RP, [this](TaskContext /*context*/) + { + if (_rpBuddyGUID) + if (Creature* understudy = ObjectAccessor::GetCreature(*me, _rpBuddyGUID)) + understudy->AI()->DoAction(ACTION_SALUTE); + }).Schedule(13s, GROUP_OOC_RP, [this](TaskContext /*context*/) + { + me->ResumeMovement(); + }).Schedule(16s, GROUP_OOC_RP, [this](TaskContext /*context*/) + { + if (_rpBuddyGUID) + if (Creature* understudy = ObjectAccessor::GetCreature(*me, _rpBuddyGUID)) + understudy->AI()->DoAction(ACTION_BACK_TO_TRAINING); + ScheduleRP(); + }); + } + + void MovementInform(uint32 type, uint32 id) override + { + if (type == POINT_MOTION_TYPE && id == POINT_DEATH_KNIGHT) + { + ScheduleInteractWithDeathKnight(); + } + } + + void ScheduleRP() + { + _rpBuddyGUID = Acore::Containers::SelectRandomContainerElement(summons); + scheduler.Schedule(60s, 80s, GROUP_OOC_RP, [this](TaskContext context) + { + if (_rpBuddyGUID) + { + if (Creature* understudy = ObjectAccessor::GetCreature(*me, _rpBuddyGUID)) + { + if (me->GetDistance2d(understudy) <= 6.0f) + { + me->PauseMovement(); + scheduler.Schedule(500ms, GROUP_OOC_RP, [this](TaskContext /*context*/) + { + if (_rpBuddyGUID) + if (Creature* understudy = ObjectAccessor::GetCreature(*me, _rpBuddyGUID)) + me->GetMotionMaster()->MovePoint(POINT_DEATH_KNIGHT, understudy->GetNearPosition(3.2f, understudy->GetRelativeAngle(me))); + }); + return; + } + } + } + context.Repeat(2s); + }); + } + + void KilledUnit(Unit* who) override + { + if (roll_chance_i(30)) + { + Talk(SAY_SLAY); + } + if (who->IsPlayer() && pInstance) + { + pInstance->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void DamageTaken(Unit* who, uint32& damage, DamageEffectType, SpellSchoolMask) override + { + // Damage done by the controlled Death Knight understudies should also count toward damage done by players + if(who && who->IsCreature() && who->GetEntry() == NPC_DEATH_KNIGHT_UNDERSTUDY) + { + me->LowerPlayerDamageReq(damage); + } + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + Talk(SAY_DEATH); + me->CastSpell(me, SPELL_HOPELESS, true); + } + + void SpellHit(Unit* caster, SpellInfo const* spell) override + { + if (spell->Id == SPELL_TAUNT) + { + Talk(SAY_TAUNTED, caster); + } + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + scheduler.CancelGroup(GROUP_OOC_RP); + Talk(SAY_AGGRO); + events.ScheduleEvent(EVENT_UNBALANCING_STRIKE, 20s); + events.ScheduleEvent(EVENT_DISRUPTING_SHOUT, 15s); + events.ScheduleEvent(EVENT_JAGGED_KNIFE, 10s); + summons.DoZoneInCombat(); + } + + void UpdateAI(uint32 diff) override + { + if (!me->IsInCombat()) + scheduler.Update(diff); + + if (!UpdateVictim()) + return; + + events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_UNBALANCING_STRIKE: + me->CastSpell(me->GetVictim(), SPELL_UNBALANCING_STRIKE, false); + events.Repeat(20s); + break; + case EVENT_DISRUPTING_SHOUT: + me->CastSpell(me, RAID_MODE(SPELL_DISRUPTING_SHOUT_10, SPELL_DISRUPTING_SHOUT_25), false); + events.Repeat(15s); + break; + case EVENT_JAGGED_KNIFE: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 45.0f)) + { + me->CastSpell(target, SPELL_JAGGED_KNIFE, false); + } + events.Repeat(10s); + break; + } + DoMeleeAttackIfReady(); + } + + private: + ObjectGuid _rpBuddyGUID; + }; +}; + +class boss_razuvious_minion : public CreatureScript +{ +public: + boss_razuvious_minion() : CreatureScript("boss_razuvious_minion") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return GetNaxxramasAI(creature); + } + + struct boss_razuvious_minionAI : public ScriptedAI + { + explicit boss_razuvious_minionAI(Creature* creature) : ScriptedAI(creature) { } + + void Reset() override + { + scheduler.CancelAll(); + ScheduleAttackDummy(); + } + + void ScheduleAttackDummy() + { + me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_READY1H); + if (Creature* targetDummy = me->FindNearestCreature(NPC_TARGET_DUMMY, 10.0f)) + { + me->SetFacingToObject(targetDummy); + } + scheduler.Schedule(6s, 9s, GROUP_OOC_RP, [this](TaskContext context) + { + me->HandleEmoteCommand(EMOTE_ONESHOT_ATTACK1H); + context.Repeat(6s, 9s); + }); + } + + void DoAction(int32 action) override + { + switch (action) + { + case ACTION_FACE_ME: + scheduler.CancelGroup(GROUP_OOC_RP); + me->SetSheath(SHEATH_STATE_UNARMED); + me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_NONE); + if (InstanceScript* instance = me->GetInstanceScript()) + { + if (Creature* creature = instance->GetCreature(DATA_RAZUVIOUS)) + { + me->SetFacingToObject(creature); + } + } + break; + case ACTION_TALK: + Talk(SAY_DEATH_KNIGHT_UNDERSTUDY); + break; + case ACTION_EMOTE: + me->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + break; + case ACTION_SALUTE: + me->HandleEmoteCommand(EMOTE_ONESHOT_SALUTE); + break; + case ACTION_BACK_TO_TRAINING: + me->SetSheath(SHEATH_STATE_MELEE); + ScheduleAttackDummy(); + break; + } + } + + void KilledUnit(Unit* who) override + { + if (who->IsPlayer() && me->GetInstanceScript()) + { + me->GetInstanceScript()->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void JustEngagedWith(Unit* who) override + { + scheduler.CancelGroup(GROUP_OOC_RP); + if (InstanceScript* instance = me->GetInstanceScript()) + { + if (Creature* creature = instance->GetCreature(DATA_RAZUVIOUS)) + { + creature->SetInCombatWithZone(); + creature->AI()->AttackStart(who); + } + } + } + + void UpdateAI(uint32 diff) override + { + scheduler.Update(diff); + + if (UpdateVictim()) + { + if (!me->HasUnitState(UNIT_STATE_CASTING) || !me->IsCharmed()) + { + DoMeleeAttackIfReady(); + } + } + } + }; +}; + +} +#endif \ No newline at end of file diff --git a/src/server/scripts/Northrend/Naxxramas/boss_sapphiron.cpp b/src/server/scripts/Northrend/Naxxramas/boss_sapphiron.cpp index 8ecc62f568cc5d..7d7fee5b7e8107 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_sapphiron.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_sapphiron.cpp @@ -15,6 +15,7 @@ * with this program. If not, see . */ +#include "boss_sapphiron.h" #include "CreatureScript.h" #include "Player.h" #include "ScriptedCreature.h" @@ -22,431 +23,8 @@ #include "SpellScriptLoader.h" #include "naxxramas.h" -enum Yells -{ - EMOTE_AIR_PHASE = 0, - EMOTE_GROUND_PHASE = 1, - EMOTE_BREATH = 2, - EMOTE_ENRAGE = 3 -}; - -enum Spells -{ - // Fight - SPELL_FROST_AURA_10 = 28531, - SPELL_FROST_AURA_25 = 55799, - SPELL_CLEAVE = 19983, - SPELL_TAIL_SWEEP_10 = 55697, - SPELL_TAIL_SWEEP_25 = 55696, - SPELL_SUMMON_BLIZZARD = 28560, - SPELL_LIFE_DRAIN_10 = 28542, - SPELL_LIFE_DRAIN_25 = 55665, - SPELL_BERSERK = 26662, - - // Ice block - SPELL_ICEBOLT_CAST = 28526, - SPELL_ICEBOLT_TRIGGER = 28522, - SPELL_FROST_MISSILE = 30101, - SPELL_FROST_EXPLOSION = 28524, - - // Visuals - SPELL_SAPPHIRON_DIES = 29357 -}; - -enum Misc -{ - GO_ICE_BLOCK = 181247, - NPC_BLIZZARD = 16474, - - POINT_CENTER = 1 -}; - -enum Events -{ - EVENT_BERSERK = 1, - EVENT_CLEAVE = 2, - EVENT_TAIL_SWEEP = 3, - EVENT_LIFE_DRAIN = 4, - EVENT_BLIZZARD = 5, - EVENT_FLIGHT_START = 6, - EVENT_FLIGHT_LIFTOFF = 7, - EVENT_FLIGHT_ICEBOLT = 8, - EVENT_FLIGHT_BREATH = 9, - EVENT_FLIGHT_SPELL_EXPLOSION = 10, - EVENT_FLIGHT_START_LAND = 11, - EVENT_LAND = 12, - EVENT_GROUND = 13, - EVENT_HUNDRED_CLUB = 14 -}; - -class boss_sapphiron : public CreatureScript -{ -public: - boss_sapphiron() : CreatureScript("boss_sapphiron") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_sapphironAI : public BossAI - { - explicit boss_sapphironAI(Creature* c) : BossAI(c, BOSS_SAPPHIRON) - { - pInstance = me->GetInstanceScript(); - } - - EventMap events; - InstanceScript* pInstance; - uint8 iceboltCount{}; - uint32 spawnTimer{}; - GuidList blockList; - ObjectGuid currentTarget; - - void InitializeAI() override - { - me->SummonGameObject(GO_SAPPHIRON_BIRTH, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), 0, 0, 0, 0, 0, 0); - me->SetVisible(false); - me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); - me->SetReactState(REACT_PASSIVE); - ScriptedAI::InitializeAI(); - } - - bool IsInRoom() - { - if (me->GetExactDist(3523.5f, -5235.3f, 137.6f) > 100.0f) - { - EnterEvadeMode(); - return false; - } - return true; - } - - void Reset() override - { - BossAI::Reset(); - if (me->IsVisible()) - { - me->SetReactState(REACT_AGGRESSIVE); - } - events.Reset(); - iceboltCount = 0; - spawnTimer = 0; - currentTarget.Clear(); - blockList.clear(); - } - - void EnterCombatSelfFunction() - { - Map::PlayerList const& PlList = me->GetMap()->GetPlayers(); - if (PlList.IsEmpty()) - return; - - for (auto const& i : PlList) - { - if (Player* player = i.GetSource()) - { - if (player->IsGameMaster()) - continue; - - if (player->IsAlive() && me->GetDistance(player) < 80.0f) - { - me->SetInCombatWith(player); - player->SetInCombatWith(me); - me->AddThreat(player, 0.0f); - } - } - } - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - EnterCombatSelfFunction(); - me->CastSpell(me, RAID_MODE(SPELL_FROST_AURA_10, SPELL_FROST_AURA_25), true); - events.ScheduleEvent(EVENT_BERSERK, 15min); - events.ScheduleEvent(EVENT_CLEAVE, 5s); - events.ScheduleEvent(EVENT_TAIL_SWEEP, 10s); - events.ScheduleEvent(EVENT_LIFE_DRAIN, 17s); - events.ScheduleEvent(EVENT_BLIZZARD, 17s); - events.ScheduleEvent(EVENT_FLIGHT_START, 45s); - events.ScheduleEvent(EVENT_HUNDRED_CLUB, 5s); - } - - void JustDied(Unit* killer) override - { - BossAI::JustDied(killer); - me->CastSpell(me, SPELL_SAPPHIRON_DIES, true); - } - - void DoAction(int32 param) override - { - if (param == ACTION_SAPPHIRON_BIRTH) - { - spawnTimer = 1; - } - } - - void MovementInform(uint32 type, uint32 id) override - { - if (type == POINT_MOTION_TYPE && id == POINT_CENTER) - { - events.ScheduleEvent(EVENT_FLIGHT_LIFTOFF, 500ms); - } - } - - void SpellHitTarget(Unit* target, SpellInfo const* spellInfo) override - { - if (spellInfo->Id == SPELL_ICEBOLT_CAST) - { - me->CastSpell(target, SPELL_ICEBOLT_TRIGGER, true); - } - } - - bool IsValidExplosionTarget(WorldObject* target) - { - for (ObjectGuid const& guid : blockList) - { - if (target->GetGUID() == guid) - return false; - - if (Unit* block = ObjectAccessor::GetUnit(*me, guid)) - { - if (block->IsInBetween(me, target, 2.0f) && block->IsWithinDist(target, 10.0f)) - return false; - } - } - return true; - } - - void KilledUnit(Unit* who) override - { - if (who->IsPlayer() && pInstance) - { - pInstance->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void UpdateAI(uint32 diff) override - { - if (spawnTimer) - { - spawnTimer += diff; - if (spawnTimer >= 21500) - { - me->SetVisible(true); - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE); - me->SetReactState(REACT_AGGRESSIVE); - spawnTimer = 0; - } - return; - } - - if (!IsInRoom()) - return; - - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_BERSERK: - Talk(EMOTE_ENRAGE); - me->CastSpell(me, SPELL_BERSERK, true); - return; - case EVENT_CLEAVE: - me->CastSpell(me->GetVictim(), SPELL_CLEAVE, false); - events.Repeat(10s); - return; - case EVENT_TAIL_SWEEP: - me->CastSpell(me, RAID_MODE(SPELL_TAIL_SWEEP_10, SPELL_TAIL_SWEEP_25), false); - events.Repeat(10s); - return; - case EVENT_LIFE_DRAIN: - me->CastCustomSpell(RAID_MODE(SPELL_LIFE_DRAIN_10, SPELL_LIFE_DRAIN_25), SPELLVALUE_MAX_TARGETS, RAID_MODE(2, 5), me, false); - events.Repeat(24s); - return; - case EVENT_BLIZZARD: - { - Creature* cr; - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 40.0f, true)) - { - cr = me->SummonCreature(NPC_BLIZZARD, *target, TEMPSUMMON_TIMED_DESPAWN, 16000); - } - else - { - cr = me->SummonCreature(NPC_BLIZZARD, *me, TEMPSUMMON_TIMED_DESPAWN, 16000); - } - if (cr) - { - cr->GetMotionMaster()->MoveRandom(40); - } - events.RepeatEvent(RAID_MODE(8000, 6500)); - return; - } - case EVENT_FLIGHT_START: - if (me->HealthBelowPct(11)) - { - return; - } - events.Repeat(45s); - events.DelayEvents(35s); - me->SetReactState(REACT_PASSIVE); - me->AttackStop(); - float x, y, z, o; - me->GetHomePosition(x, y, z, o); - me->GetMotionMaster()->MovePoint(POINT_CENTER, x, y, z); - return; - case EVENT_FLIGHT_LIFTOFF: - Talk(EMOTE_AIR_PHASE); - me->GetMotionMaster()->MoveIdle(); - me->SendMeleeAttackStop(me->GetVictim()); - me->HandleEmoteCommand(EMOTE_ONESHOT_LIFTOFF); - me->SetDisableGravity(true); - currentTarget.Clear(); - events.ScheduleEvent(EVENT_FLIGHT_ICEBOLT, 3s); - iceboltCount = RAID_MODE(2, 3); - return; - case EVENT_FLIGHT_ICEBOLT: - { - if (currentTarget) - { - if (Unit* target = ObjectAccessor::GetUnit(*me, currentTarget)) - { - me->SummonGameObject(GO_ICE_BLOCK, target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), target->GetOrientation(), 0.0f, 0.0f, 0.0f, 0.0f, 0); - } - } - - std::vector targets; - auto i = me->GetThreatMgr().GetThreatList().begin(); - for (; i != me->GetThreatMgr().GetThreatList().end(); ++i) - { - if ((*i)->getTarget()->IsPlayer()) - { - bool inList = false; - if (!blockList.empty()) - { - for (GuidList::const_iterator itr = blockList.begin(); itr != blockList.end(); ++itr) - { - if ((*i)->getTarget()->GetGUID() == *itr) - { - inList = true; - break; - } - } - } - if (!inList) - { - targets.push_back((*i)->getTarget()); - } - } - } - - if (!targets.empty() && iceboltCount) - { - auto itr = targets.begin(); - advance(itr, urand(0, targets.size() - 1)); - me->CastSpell(*itr, SPELL_ICEBOLT_CAST, false); - blockList.push_back((*itr)->GetGUID()); - currentTarget = (*itr)->GetGUID(); - --iceboltCount; - events.ScheduleEvent(EVENT_FLIGHT_ICEBOLT, (me->GetExactDist(*itr) / 13.0f)*IN_MILLISECONDS); - } - else - { - events.ScheduleEvent(EVENT_FLIGHT_BREATH, 1s); - } - return; - } - case EVENT_FLIGHT_BREATH: - currentTarget.Clear(); - Talk(EMOTE_BREATH); - me->CastSpell(me, SPELL_FROST_MISSILE, false); - events.ScheduleEvent(EVENT_FLIGHT_SPELL_EXPLOSION, 8500ms); - return; - case EVENT_FLIGHT_SPELL_EXPLOSION: - me->CastSpell(me, SPELL_FROST_EXPLOSION, true); - events.ScheduleEvent(EVENT_FLIGHT_START_LAND, 3s); - return; - case EVENT_FLIGHT_START_LAND: - if (!blockList.empty()) - { - for (GuidList::const_iterator itr = blockList.begin(); itr != blockList.end(); ++itr) - { - if (Unit* block = ObjectAccessor::GetUnit(*me, *itr)) - { - block->RemoveAurasDueToSpell(SPELL_ICEBOLT_TRIGGER); - } - } - } - blockList.clear(); - me->RemoveAllGameObjects(); - events.ScheduleEvent(EVENT_LAND, 1s); - return; - case EVENT_LAND: - me->HandleEmoteCommand(EMOTE_ONESHOT_LAND); - me->SetDisableGravity(false); - events.ScheduleEvent(EVENT_GROUND, 1500ms); - return; - case EVENT_GROUND: - Talk(EMOTE_GROUND_PHASE); - me->SetReactState(REACT_AGGRESSIVE); - me->SetInCombatWithZone(); - return; - case EVENT_HUNDRED_CLUB: - { - Map::PlayerList const& pList = me->GetMap()->GetPlayers(); - for (auto const& itr : pList) - { - if (itr.GetSource()->GetResistance(SPELL_SCHOOL_FROST) > 100 && pInstance) - { - pInstance->SetData(DATA_HUNDRED_CLUB, 0); - return; - } - } - events.Repeat(5s); - return; - } - } - DoMeleeAttackIfReady(); - } - }; -}; - -class spell_sapphiron_frost_explosion : public SpellScript -{ - PrepareSpellScript(spell_sapphiron_frost_explosion); - - void FilterTargets(std::list& targets) - { - Unit* caster = GetCaster(); - if (!caster || !caster->ToCreature()) - return; - - std::list tmplist; - for (auto& target : targets) - { - if (CAST_AI(boss_sapphiron::boss_sapphironAI, caster->ToCreature()->AI())->IsValidExplosionTarget(target)) - { - tmplist.push_back(target); - } - } - targets.clear(); - for (auto& itr : tmplist) - { - targets.push_back(itr); - } - } - void Register() override - { - OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_sapphiron_frost_explosion::FilterTargets, EFFECT_0, TARGET_UNIT_DEST_AREA_ENEMY); - } -}; +using namespace Sapphiron; void AddSC_boss_sapphiron() { diff --git a/src/server/scripts/Northrend/Naxxramas/boss_sapphiron.h b/src/server/scripts/Northrend/Naxxramas/boss_sapphiron.h new file mode 100644 index 00000000000000..29c05bde2d667e --- /dev/null +++ b/src/server/scripts/Northrend/Naxxramas/boss_sapphiron.h @@ -0,0 +1,441 @@ +#ifndef BOSS_SAPPHIRON_H_ +#define BOSS_SAPPHIRON_H_ + +#include "Player.h" +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "SpellScript.h" +#include "SpellInfo.h" +#include "naxxramas.h" + +namespace Sapphiron { + +enum Yells +{ + EMOTE_AIR_PHASE = 0, + EMOTE_GROUND_PHASE = 1, + EMOTE_BREATH = 2, + EMOTE_ENRAGE = 3 +}; + +enum Spells +{ + // Fight + SPELL_FROST_AURA_10 = 28531, + SPELL_FROST_AURA_25 = 55799, + SPELL_CLEAVE = 19983, + SPELL_TAIL_SWEEP_10 = 55697, + SPELL_TAIL_SWEEP_25 = 55696, + SPELL_SUMMON_BLIZZARD = 28560, + SPELL_LIFE_DRAIN_10 = 28542, + SPELL_LIFE_DRAIN_25 = 55665, + SPELL_BERSERK = 26662, + + // Ice block + SPELL_ICEBOLT_CAST = 28526, + SPELL_ICEBOLT_TRIGGER = 28522, + SPELL_FROST_MISSILE = 30101, + SPELL_FROST_EXPLOSION = 28524, + + // Visuals + SPELL_SAPPHIRON_DIES = 29357 +}; + +enum Misc +{ + GO_ICE_BLOCK = 181247, + NPC_BLIZZARD = 16474, + + POINT_CENTER = 1 +}; + +enum Events +{ + EVENT_BERSERK = 1, + EVENT_CLEAVE = 2, + EVENT_TAIL_SWEEP = 3, + EVENT_LIFE_DRAIN = 4, + EVENT_BLIZZARD = 5, + EVENT_FLIGHT_START = 6, + EVENT_FLIGHT_LIFTOFF = 7, + EVENT_FLIGHT_ICEBOLT = 8, + EVENT_FLIGHT_BREATH = 9, + EVENT_FLIGHT_SPELL_EXPLOSION = 10, + EVENT_FLIGHT_START_LAND = 11, + EVENT_LAND = 12, + EVENT_GROUND = 13, + EVENT_HUNDRED_CLUB = 14 +}; + +class boss_sapphiron : public CreatureScript +{ +public: + boss_sapphiron() : CreatureScript("boss_sapphiron") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_sapphironAI : public BossAI + { + explicit boss_sapphironAI(Creature* c) : BossAI(c, BOSS_SAPPHIRON) + { + pInstance = me->GetInstanceScript(); + } + + EventMap events; + InstanceScript* pInstance; + uint8 iceboltCount{}; + uint32 spawnTimer{}; + GuidList blockList; + ObjectGuid currentTarget; + + void InitializeAI() override + { + me->SummonGameObject(GO_SAPPHIRON_BIRTH, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), 0, 0, 0, 0, 0, 0); + me->SetVisible(false); + me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); + me->SetReactState(REACT_PASSIVE); + ScriptedAI::InitializeAI(); + } + + bool IsInRoom() + { + if (me->GetExactDist(3523.5f, -5235.3f, 137.6f) > 100.0f) + { + EnterEvadeMode(); + return false; + } + return true; + } + + void Reset() override + { + BossAI::Reset(); + if (me->IsVisible()) + { + me->SetReactState(REACT_AGGRESSIVE); + } + events.Reset(); + iceboltCount = 0; + spawnTimer = 0; + currentTarget.Clear(); + blockList.clear(); + } + + void EnterCombatSelfFunction() + { + Map::PlayerList const& PlList = me->GetMap()->GetPlayers(); + if (PlList.IsEmpty()) + return; + + for (auto const& i : PlList) + { + if (Player* player = i.GetSource()) + { + if (player->IsGameMaster()) + continue; + + if (player->IsAlive() && me->GetDistance(player) < 80.0f) + { + me->SetInCombatWith(player); + player->SetInCombatWith(me); + me->AddThreat(player, 0.0f); + } + } + } + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + EnterCombatSelfFunction(); + me->CastSpell(me, RAID_MODE(SPELL_FROST_AURA_10, SPELL_FROST_AURA_25), true); + events.ScheduleEvent(EVENT_BERSERK, 15min); + events.ScheduleEvent(EVENT_CLEAVE, 5s); + events.ScheduleEvent(EVENT_TAIL_SWEEP, 10s); + events.ScheduleEvent(EVENT_LIFE_DRAIN, 17s); + events.ScheduleEvent(EVENT_BLIZZARD, 17s); + events.ScheduleEvent(EVENT_FLIGHT_START, 45s); + events.ScheduleEvent(EVENT_HUNDRED_CLUB, 5s); + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + me->CastSpell(me, SPELL_SAPPHIRON_DIES, true); + } + + void DoAction(int32 param) override + { + if (param == ACTION_SAPPHIRON_BIRTH) + { + spawnTimer = 1; + } + } + + void MovementInform(uint32 type, uint32 id) override + { + if (type == POINT_MOTION_TYPE && id == POINT_CENTER) + { + events.ScheduleEvent(EVENT_FLIGHT_LIFTOFF, 500ms); + } + } + + void SpellHitTarget(Unit* target, SpellInfo const* spellInfo) override + { + if (spellInfo->Id == SPELL_ICEBOLT_CAST) + { + me->CastSpell(target, SPELL_ICEBOLT_TRIGGER, true); + } + } + + bool IsValidExplosionTarget(WorldObject* target) + { + for (ObjectGuid const& guid : blockList) + { + if (target->GetGUID() == guid) + return false; + + if (Unit* block = ObjectAccessor::GetUnit(*me, guid)) + { + if (block->IsInBetween(me, target, 2.0f) && block->IsWithinDist(target, 10.0f)) + return false; + } + } + return true; + } + + void KilledUnit(Unit* who) override + { + if (who->IsPlayer() && pInstance) + { + pInstance->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void UpdateAI(uint32 diff) override + { + if (spawnTimer) + { + spawnTimer += diff; + if (spawnTimer >= 21500) + { + me->SetVisible(true); + me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE); + me->SetReactState(REACT_AGGRESSIVE); + spawnTimer = 0; + } + return; + } + + if (!IsInRoom()) + return; + + if (!UpdateVictim()) + return; + + events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_BERSERK: + Talk(EMOTE_ENRAGE); + me->CastSpell(me, SPELL_BERSERK, true); + return; + case EVENT_CLEAVE: + me->CastSpell(me->GetVictim(), SPELL_CLEAVE, false); + events.Repeat(10s); + return; + case EVENT_TAIL_SWEEP: + me->CastSpell(me, RAID_MODE(SPELL_TAIL_SWEEP_10, SPELL_TAIL_SWEEP_25), false); + events.Repeat(10s); + return; + case EVENT_LIFE_DRAIN: + me->CastCustomSpell(RAID_MODE(SPELL_LIFE_DRAIN_10, SPELL_LIFE_DRAIN_25), SPELLVALUE_MAX_TARGETS, RAID_MODE(2, 5), me, false); + events.Repeat(24s); + return; + case EVENT_BLIZZARD: + { + Creature* cr; + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 40.0f, true)) + { + cr = me->SummonCreature(NPC_BLIZZARD, *target, TEMPSUMMON_TIMED_DESPAWN, 16000); + } + else + { + cr = me->SummonCreature(NPC_BLIZZARD, *me, TEMPSUMMON_TIMED_DESPAWN, 16000); + } + if (cr) + { + cr->GetMotionMaster()->MoveRandom(40); + } + events.RepeatEvent(RAID_MODE(8000, 6500)); + return; + } + case EVENT_FLIGHT_START: + if (me->HealthBelowPct(11)) + { + return; + } + events.Repeat(45s); + events.DelayEvents(35s); + me->SetReactState(REACT_PASSIVE); + me->AttackStop(); + float x, y, z, o; + me->GetHomePosition(x, y, z, o); + me->GetMotionMaster()->MovePoint(POINT_CENTER, x, y, z); + return; + case EVENT_FLIGHT_LIFTOFF: + Talk(EMOTE_AIR_PHASE); + me->GetMotionMaster()->MoveIdle(); + me->SendMeleeAttackStop(me->GetVictim()); + me->HandleEmoteCommand(EMOTE_ONESHOT_LIFTOFF); + me->SetDisableGravity(true); + currentTarget.Clear(); + events.ScheduleEvent(EVENT_FLIGHT_ICEBOLT, 3s); + iceboltCount = RAID_MODE(2, 3); + return; + case EVENT_FLIGHT_ICEBOLT: + { + if (currentTarget) + { + if (Unit* target = ObjectAccessor::GetUnit(*me, currentTarget)) + { + me->SummonGameObject(GO_ICE_BLOCK, target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), target->GetOrientation(), 0.0f, 0.0f, 0.0f, 0.0f, 0); + } + } + + std::vector targets; + auto i = me->GetThreatMgr().GetThreatList().begin(); + for (; i != me->GetThreatMgr().GetThreatList().end(); ++i) + { + if ((*i)->getTarget()->IsPlayer()) + { + bool inList = false; + if (!blockList.empty()) + { + for (GuidList::const_iterator itr = blockList.begin(); itr != blockList.end(); ++itr) + { + if ((*i)->getTarget()->GetGUID() == *itr) + { + inList = true; + break; + } + } + } + if (!inList) + { + targets.push_back((*i)->getTarget()); + } + } + } + + if (!targets.empty() && iceboltCount) + { + auto itr = targets.begin(); + advance(itr, urand(0, targets.size() - 1)); + me->CastSpell(*itr, SPELL_ICEBOLT_CAST, false); + blockList.push_back((*itr)->GetGUID()); + currentTarget = (*itr)->GetGUID(); + --iceboltCount; + events.ScheduleEvent(EVENT_FLIGHT_ICEBOLT, (me->GetExactDist(*itr) / 13.0f)*IN_MILLISECONDS); + } + else + { + events.ScheduleEvent(EVENT_FLIGHT_BREATH, 1s); + } + return; + } + case EVENT_FLIGHT_BREATH: + currentTarget.Clear(); + Talk(EMOTE_BREATH); + me->CastSpell(me, SPELL_FROST_MISSILE, false); + events.ScheduleEvent(EVENT_FLIGHT_SPELL_EXPLOSION, 8500ms); + return; + case EVENT_FLIGHT_SPELL_EXPLOSION: + me->CastSpell(me, SPELL_FROST_EXPLOSION, true); + events.ScheduleEvent(EVENT_FLIGHT_START_LAND, 3s); + return; + case EVENT_FLIGHT_START_LAND: + if (!blockList.empty()) + { + for (GuidList::const_iterator itr = blockList.begin(); itr != blockList.end(); ++itr) + { + if (Unit* block = ObjectAccessor::GetUnit(*me, *itr)) + { + block->RemoveAurasDueToSpell(SPELL_ICEBOLT_TRIGGER); + } + } + } + blockList.clear(); + me->RemoveAllGameObjects(); + events.ScheduleEvent(EVENT_LAND, 1s); + return; + case EVENT_LAND: + me->HandleEmoteCommand(EMOTE_ONESHOT_LAND); + me->SetDisableGravity(false); + events.ScheduleEvent(EVENT_GROUND, 1500ms); + return; + case EVENT_GROUND: + Talk(EMOTE_GROUND_PHASE); + me->SetReactState(REACT_AGGRESSIVE); + me->SetInCombatWithZone(); + return; + case EVENT_HUNDRED_CLUB: + { + Map::PlayerList const& pList = me->GetMap()->GetPlayers(); + for (auto const& itr : pList) + { + if (itr.GetSource()->GetResistance(SPELL_SCHOOL_FROST) > 100 && pInstance) + { + pInstance->SetData(DATA_HUNDRED_CLUB, 0); + return; + } + } + events.Repeat(5s); + return; + } + } + DoMeleeAttackIfReady(); + } + }; +}; + +class spell_sapphiron_frost_explosion : public SpellScript +{ + PrepareSpellScript(spell_sapphiron_frost_explosion); + + void FilterTargets(std::list& targets) + { + Unit* caster = GetCaster(); + if (!caster || !caster->ToCreature()) + return; + + std::list tmplist; + for (auto& target : targets) + { + if (CAST_AI(boss_sapphiron::boss_sapphironAI, caster->ToCreature()->AI())->IsValidExplosionTarget(target)) + { + tmplist.push_back(target); + } + } + targets.clear(); + for (auto& itr : tmplist) + { + targets.push_back(itr); + } + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_sapphiron_frost_explosion::FilterTargets, EFFECT_0, TARGET_UNIT_DEST_AREA_ENEMY); + } +}; + +} + +#endif \ No newline at end of file diff --git a/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp b/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp index dd25921e90a222..3855db22ae1da1 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp @@ -15,6 +15,7 @@ * with this program. If not, see . */ +#include "boss_thaddius.h" #include "AreaTriggerScript.h" #include "CreatureScript.h" #include "Player.h" @@ -23,731 +24,8 @@ #include "SpellScriptLoader.h" #include "naxxramas.h" -enum Says -{ - // Stalagg - SAY_STAL_AGGRO = 0, - SAY_STAL_SLAY = 1, - SAY_STAL_DEATH = 2, - EMOTE_STAL_DEATH = 3, - EMOTE_STAL_REVIVE = 4, - - // Feugen - SAY_FEUG_AGGRO = 0, - SAY_FEUG_SLAY = 1, - SAY_FEUG_DEATH = 2, - EMOTE_FEUG_DEATH = 3, - EMOTE_FEUG_REVIVE = 4, - - // Thaddius - SAY_GREET = 0, - SAY_AGGRO = 1, - SAY_SLAY = 2, - SAY_ELECT = 3, - SAY_DEATH = 4, - EMOTE_POLARITY_SHIFTED = 6, - - // Tesla Coil - EMOTE_TESLA_LINK_BREAKS = 0, - EMOTE_TESLA_OVERLOAD = 1 -}; - -enum Spells -{ - SPELL_MAGNETIC_PULL = 28337, - SPELL_TESLA_SHOCK = 28099, - SPELL_SHOCK_VISUAL = 28159, - - // Stalagg - SPELL_POWER_SURGE_10 = 54529, - SPELL_POWER_SURGE_25 = 28134, - SPELL_STALAGG_CHAIN = 28096, - - // Feugen - SPELL_STATIC_FIELD_10 = 28135, - SPELL_STATIC_FIELD_25 = 54528, - SPELL_FEUGEN_CHAIN = 28111, - - // Thaddius - SPELL_POLARITY_SHIFT = 28089, - SPELL_BALL_LIGHTNING = 28299, - SPELL_CHAIN_LIGHTNING_10 = 28167, - SPELL_CHAIN_LIGHTNING_25 = 54531, - SPELL_BERSERK = 27680, - SPELL_THADDIUS_VISUAL_LIGHTNING = 28136, - SPELL_THADDIUS_SPAWN_STUN = 28160, - - SPELL_POSITIVE_CHARGE = 28062, - SPELL_POSITIVE_CHARGE_STACK = 29659, - SPELL_NEGATIVE_CHARGE = 28085, - SPELL_NEGATIVE_CHARGE_STACK = 29660, - SPELL_POSITIVE_POLARITY = 28059, - SPELL_NEGATIVE_POLARITY = 28084 -}; - -enum Events -{ - EVENT_MINION_POWER_SURGE = 1, - EVENT_MINION_MAGNETIC_PULL = 2, - EVENT_MINION_CHECK_DISTANCE = 3, - EVENT_MINION_STATIC_FIELD = 4, - - EVENT_THADDIUS_INIT = 5, - EVENT_THADDIUS_ENTER_COMBAT = 6, - EVENT_THADDIUS_CHAIN_LIGHTNING = 7, - EVENT_THADDIUS_BERSERK = 8, - EVENT_THADDIUS_POLARITY_SHIFT = 9, - EVENT_ALLOW_BALL_LIGHTNING = 10 -}; - -enum Misc -{ - ACTION_MAGNETIC_PULL = 1, - ACTION_SUMMON_DIED = 2, - ACTION_RESTORE = 3, - GO_TESLA_COIL_LEFT = 181478, - GO_TESLA_COIL_RIGHT = 181477, - NPC_TESLA_COIL = 16218 -}; - -class boss_thaddius : public CreatureScript -{ -public: - boss_thaddius() : CreatureScript("boss_thaddius") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_thaddiusAI : public BossAI - { - explicit boss_thaddiusAI(Creature* c) : BossAI(c, BOSS_THADDIUS), summons(me), ballLightningEnabled(false) - { - pInstance = me->GetInstanceScript(); - } - - InstanceScript* pInstance; - EventMap events; - SummonList summons; - uint32 summonTimer{}; - uint32 reviveTimer{}; - uint32 resetTimer{}; - bool ballLightningEnabled; - - void DoAction(int32 param) override - { - if (param == ACTION_SUMMON_DIED) - { - if (summonTimer) - { - summonTimer = 0; - reviveTimer = 1; - return; - } - summonTimer = 1; - } - } - - void Reset() override - { - BossAI::Reset(); - events.Reset(); - summons.DespawnAll(); - me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); - me->SetControlled(true, UNIT_STATE_ROOT); - summonTimer = 0; - reviveTimer = 0; - resetTimer = 1; - me->SetPosition(me->GetHomePosition()); - ballLightningEnabled = false; - - me->SummonCreature(NPC_STALAGG, 3450.45f, -2931.42f, 312.091f, 5.49779f); - me->SummonCreature(NPC_FEUGEN, 3508.14f, -2988.65f, 312.092f, 2.37365f); - if (Creature* cr = me->SummonCreature(NPC_TESLA_COIL, 3527.34f, -2951.56f, 318.75f, 0.0f)) - { - cr->RemoveAllAuras(); - cr->InterruptNonMeleeSpells(true); - cr->CastSpell(cr, SPELL_FEUGEN_CHAIN, false); - cr->SetDisableGravity(true); - cr->SetImmuneToPC(false); - cr->SetControlled(true, UNIT_STATE_ROOT); - } - if (Creature* cr = me->SummonCreature(NPC_TESLA_COIL, 3487.04f, -2911.68f, 318.75f, 0.0f)) - { - cr->RemoveAllAuras(); - cr->InterruptNonMeleeSpells(true); - cr->CastSpell(cr, SPELL_STALAGG_CHAIN, false); - cr->SetDisableGravity(true); - cr->SetImmuneToPC(false); - cr->SetControlled(true, UNIT_STATE_ROOT); - } - - if (GameObject* go = me->FindNearestGameObject(GO_TESLA_COIL_LEFT, 100.0f)) - { - go->SetGoState(GO_STATE_ACTIVE); - } - if (GameObject* go = me->FindNearestGameObject(GO_TESLA_COIL_RIGHT, 100.0f)) - { - go->SetGoState(GO_STATE_ACTIVE); - } - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_THADDIUS_GATE))) - { - if (pInstance->GetBossState(BOSS_GLUTH) == DONE) - { - go->SetGoState(GO_STATE_ACTIVE); - } - } - } - - pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_POSITIVE_POLARITY); - pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_POSITIVE_CHARGE_STACK); - pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_NEGATIVE_POLARITY); - pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_NEGATIVE_CHARGE_STACK); - } - - void KilledUnit(Unit* who) override - { - if (!who->IsPlayer()) - return; - - Talk(SAY_SLAY); - if (pInstance) - { - pInstance->SetData(DATA_IMMORTAL_FAIL, 0); - } - } - - void JustDied(Unit* killer) override - { - BossAI::JustDied(killer); - Talk(SAY_DEATH); - if (pInstance) - { - pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_POSITIVE_POLARITY); - pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_POSITIVE_CHARGE_STACK); - pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_NEGATIVE_POLARITY); - pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_NEGATIVE_CHARGE_STACK); - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_THADDIUS_GATE))) - { - go->SetGoState(GO_STATE_ACTIVE); - } - } - } - - void JustSummoned(Creature* cr) override - { - summons.Summon(cr); - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - me->SetInCombatWithZone(); - summons.DoZoneInCombat(NPC_FEUGEN); - summons.DoZoneInCombat(NPC_STALAGG); - } - - void UpdateAI(uint32 diff) override - { - if (resetTimer) - { - resetTimer += diff; - if (resetTimer > 1000) - { - resetTimer = 0; - me->CastSpell(me, SPELL_THADDIUS_SPAWN_STUN, true); - } - return; - } - if (reviveTimer) - { - reviveTimer += diff; - if (reviveTimer >= 12000) - { - for (SummonList::const_iterator itr = summons.begin(); itr != summons.end(); ++itr) - { - if (Creature* cr = ObjectAccessor::GetCreature(*me, (*itr))) - { - if (cr->GetEntry() == NPC_TESLA_COIL) - { - cr->AI()->Talk(EMOTE_TESLA_OVERLOAD); - cr->CastSpell(me, SPELL_SHOCK_VISUAL, true); - } - } - } - reviveTimer = 0; - events.ScheduleEvent(EVENT_THADDIUS_INIT, 750ms); - } - return; - } - - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - if (summonTimer) // Revive - { - summonTimer += diff; - if (summonTimer >= 5000) - { - summons.DoAction(ACTION_RESTORE); - summonTimer = 0; - } - } - - switch (events.ExecuteEvent()) - { - case EVENT_THADDIUS_INIT: - { - me->RemoveAllAuras(); - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE); - for (SummonList::const_iterator itr = summons.begin(); itr != summons.end(); ++itr) - { - if (Creature* cr = ObjectAccessor::GetCreature(*me, (*itr))) - { - if (cr->GetEntry() == NPC_TESLA_COIL) - { - Unit::Kill(cr, cr); - } - } - } - if (GameObject* go = me->FindNearestGameObject(GO_TESLA_COIL_LEFT, 100.0f)) - { - go->SetGoState(GO_STATE_READY); - } - if (GameObject* go = me->FindNearestGameObject(GO_TESLA_COIL_RIGHT, 100.0f)) - { - go->SetGoState(GO_STATE_READY); - } - me->CastSpell(me, SPELL_THADDIUS_VISUAL_LIGHTNING, true); - events.ScheduleEvent(EVENT_THADDIUS_ENTER_COMBAT, 1s); - break; - } - case EVENT_THADDIUS_ENTER_COMBAT: - Talk(SAY_AGGRO); - me->SetReactState(REACT_AGGRESSIVE); - me->SetControlled(false, UNIT_STATE_STUNNED); - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE); - events.ScheduleEvent(EVENT_THADDIUS_CHAIN_LIGHTNING, 14s); - events.ScheduleEvent(EVENT_THADDIUS_BERSERK, 6min); - events.ScheduleEvent(EVENT_THADDIUS_POLARITY_SHIFT, 30s); - events.ScheduleEvent(EVENT_ALLOW_BALL_LIGHTNING, 5s); - return; - case EVENT_THADDIUS_BERSERK: - me->CastSpell(me, SPELL_BERSERK, true); - break; - case EVENT_THADDIUS_CHAIN_LIGHTNING: - me->CastSpell(me->GetVictim(), RAID_MODE(SPELL_CHAIN_LIGHTNING_10, SPELL_CHAIN_LIGHTNING_25), false); - events.Repeat(15s); - break; - case EVENT_THADDIUS_POLARITY_SHIFT: - me->CastSpell(me, SPELL_POLARITY_SHIFT, false); - events.Repeat(30s); - break; - case EVENT_ALLOW_BALL_LIGHTNING: - ballLightningEnabled = true; - break; - } - - if (me->IsWithinMeleeRange(me->GetVictim())) - { - DoMeleeAttackIfReady(); - } - else if (ballLightningEnabled) - { - if (Unit* target = SelectTarget(SelectTargetMethod::MaxThreat)) - { - me->CastSpell(target, SPELL_BALL_LIGHTNING, false); - } - } - } - }; -}; - -class boss_thaddius_summon : public CreatureScript -{ -public: - boss_thaddius_summon() : CreatureScript("boss_thaddius_summon") { } - - CreatureAI* GetAI(Creature* pCreature) const override - { - return GetNaxxramasAI(pCreature); - } - - struct boss_thaddius_summonAI : public ScriptedAI - { - explicit boss_thaddius_summonAI(Creature* c) : ScriptedAI(c) - { - pInstance = me->GetInstanceScript(); - overload = false; - } - - InstanceScript* pInstance; - EventMap events; - uint32 pullTimer{}; - uint32 visualTimer{}; - bool overload; - ObjectGuid myCoil; - - void Reset() override - { - pullTimer = 0; - visualTimer = 1; - overload = false; - events.Reset(); - me->SetControlled(false, UNIT_STATE_STUNNED); - if (Creature* cr = me->FindNearestCreature(NPC_TESLA_COIL, 150.0f)) - { - cr->CastSpell(cr, me->GetEntry() == NPC_STALAGG ? SPELL_STALAGG_CHAIN : SPELL_FEUGEN_CHAIN, false); - cr->SetImmuneToPC(false); - myCoil = cr->GetGUID(); - } - } - - void EnterEvadeMode(EvadeReason why) override - { - me->SetControlled(false, UNIT_STATE_STUNNED); - ScriptedAI::EnterEvadeMode(why); - } - - void JustEngagedWith(Unit* pWho) override - { - me->SetInCombatWithZone(); - if (Creature* cr = me->FindNearestCreature(NPC_TESLA_COIL, 150.f, true)) - { - myCoil = cr->GetGUID(); - } - if (me->GetEntry() == NPC_STALAGG) - { - events.ScheduleEvent(EVENT_MINION_POWER_SURGE, 10s); - Talk(SAY_STAL_AGGRO); - } - else - { - events.ScheduleEvent(EVENT_MINION_STATIC_FIELD, 5s); - Talk(SAY_FEUG_AGGRO); - } - events.ScheduleEvent(EVENT_MINION_CHECK_DISTANCE, 5s); - - if (me->GetEntry() == NPC_STALAGG) // This event needs synchronisation, called for stalagg only - { - events.ScheduleEvent(EVENT_MINION_MAGNETIC_PULL, 20s); - } - if (pInstance) - { - if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_THADDIUS_GATE))) - { - go->SetGoState(GO_STATE_READY); - } - if (Creature* cr = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(DATA_THADDIUS_BOSS))) - { - cr->AI()->AttackStart(pWho); - cr->AddThreat(pWho, 10.0f); - } - } - } - - void DoAction(int32 param) override - { - if (param == ACTION_MAGNETIC_PULL) - { - pullTimer = 1; - me->SetControlled(true, UNIT_STATE_STUNNED); - } - else if (param == ACTION_RESTORE) - { - if (!me->IsAlive()) - { - me->Respawn(); - me->SetInCombatWithZone(); - Talk(me->GetEntry() == NPC_STALAGG ? EMOTE_STAL_REVIVE : EMOTE_FEUG_REVIVE); - } - else - { - me->SetHealth(me->GetMaxHealth()); - } - } - } - - void JustDied(Unit* /*killer*/) override - { - Talk(me->GetEntry() == NPC_STALAGG ? SAY_STAL_DEATH : SAY_FEUG_DEATH); - Talk(me->GetEntry() == NPC_STALAGG ? EMOTE_STAL_DEATH : EMOTE_FEUG_DEATH); - if (pInstance) - { - if (Creature* cr = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(DATA_THADDIUS_BOSS))) - { - cr->AI()->DoAction(ACTION_SUMMON_DIED); - } - } - } - - void KilledUnit(Unit* who) override - { - if (!who->IsPlayer()) - return; - - if (pInstance) - { - pInstance->SetData(DATA_IMMORTAL_FAIL, 0); - } - if (!urand(0, 2)) - { - Talk(me->GetEntry() == NPC_STALAGG ? SAY_STAL_SLAY : SAY_FEUG_SLAY); - } - } - - void UpdateAI(uint32 diff) override - { - if (visualTimer) - { - visualTimer += diff; - if (visualTimer >= 3000) - { - visualTimer = 0; - if (Creature* cr = me->FindNearestCreature(NPC_TESLA_COIL, 150.0f)) - { - cr->CastSpell(cr, me->GetEntry() == NPC_STALAGG ? SPELL_STALAGG_CHAIN : SPELL_FEUGEN_CHAIN, false); - } - } - } - - if (!UpdateVictim()) - return; - - if (pullTimer) // Disable AI during pull - { - pullTimer += diff; - if (pullTimer >= 3000) - { - me->SetControlled(false, UNIT_STATE_STUNNED); - pullTimer = 0; - } - return; - } - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_MINION_POWER_SURGE: - me->CastSpell(me, RAID_MODE(SPELL_POWER_SURGE_10, SPELL_POWER_SURGE_25), false); - events.Repeat(19s); - break; - case EVENT_MINION_STATIC_FIELD: - me->CastSpell(me, RAID_MODE(SPELL_STATIC_FIELD_10, SPELL_STATIC_FIELD_25), false); - events.Repeat(3s); - break; - case EVENT_MINION_MAGNETIC_PULL: - events.Repeat(20s); - if (pInstance) - { - if (Creature* feugen = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(DATA_FEUGEN_BOSS))) - { - if (!feugen->IsAlive() || !feugen->GetVictim() || !me->GetVictim()) - return; - - float threatFeugen = feugen->GetThreatMgr().GetThreat(feugen->GetVictim()); - float threatStalagg = me->GetThreatMgr().GetThreat(me->GetVictim()); - Unit* tankFeugen = feugen->GetVictim(); - Unit* tankStalagg = me->GetVictim(); - - feugen->GetThreatMgr().ModifyThreatByPercent(tankFeugen, -100); - feugen->AddThreat(tankStalagg, threatFeugen); - feugen->CastSpell(tankStalagg, SPELL_MAGNETIC_PULL, true); - feugen->AI()->DoAction(ACTION_MAGNETIC_PULL); - - me->GetThreatMgr().ModifyThreatByPercent(tankStalagg, -100); - me->AddThreat(tankFeugen, threatStalagg); - me->CastSpell(tankFeugen, SPELL_MAGNETIC_PULL, true); - DoAction(ACTION_MAGNETIC_PULL); - } - } - break; - case EVENT_MINION_CHECK_DISTANCE: - if (Creature* cr = ObjectAccessor::GetCreature(*me, myCoil)) - { - if (!me->GetHomePosition().IsInDist(me, 28) && me->IsInCombat()) - { - if (!overload) - { - overload = true; - cr->AI()->Talk(EMOTE_TESLA_LINK_BREAKS); - me->RemoveAurasDueToSpell(me->GetEntry() == NPC_STALAGG ? SPELL_STALAGG_CHAIN : SPELL_FEUGEN_CHAIN); - cr->InterruptNonMeleeSpells(true); - } - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 1000.f, true)) - { - cr->CastStop(SPELL_TESLA_SHOCK); - cr->CastSpell(target, SPELL_TESLA_SHOCK, true); - } - events.Repeat(1500ms); - break; - } - else - { - overload = false; - cr->CastSpell(cr, me->GetEntry() == NPC_STALAGG ? SPELL_STALAGG_CHAIN : SPELL_FEUGEN_CHAIN, false); - } - } - events.Repeat(5s); - break; - } - DoMeleeAttackIfReady(); - } - }; -}; - -class spell_thaddius_pos_neg_charge : public SpellScript -{ - PrepareSpellScript(spell_thaddius_pos_neg_charge); - - bool Validate(SpellInfo const* /*spellInfo*/) override - { - return ValidateSpellInfo({ SPELL_POSITIVE_CHARGE, SPELL_POSITIVE_CHARGE_STACK }); - } - - void HandleTargets(std::list& targets) - { - uint8 count = 0; - for (auto& ihit : targets) - { - if (ihit->GetGUID() != GetCaster()->GetGUID()) - { - if (Player* target = ihit->ToPlayer()) - { - if (target->HasAura(GetTriggeringSpell()->Id)) - { - ++count; - } - } - } - } - - if (count) - { - uint32 spellId = GetSpellInfo()->Id == SPELL_POSITIVE_CHARGE ? SPELL_POSITIVE_CHARGE_STACK : SPELL_NEGATIVE_CHARGE_STACK; - GetCaster()->SetAuraStack(spellId, GetCaster(), count); - } - } - - void HandleDamage(SpellEffIndex /*effIndex*/) - { - if (!GetTriggeringSpell()) - return; - - Unit* target = GetHitUnit(); - if (!target) - return; - - if (target->HasAura(GetTriggeringSpell()->Id) || !target->IsPlayer()) - { - SetHitDamage(0); - } - else if (target->GetInstanceScript()) - { - target->GetInstanceScript()->SetData(DATA_CHARGES_CROSSED, 0); - } - } - - void Register() override - { - OnEffectHitTarget += SpellEffectFn(spell_thaddius_pos_neg_charge::HandleDamage, EFFECT_0, SPELL_EFFECT_SCHOOL_DAMAGE); - OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_thaddius_pos_neg_charge::HandleTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ALLY); - } -}; - -class spell_thaddius_polarity_shift : public SpellScript -{ - PrepareSpellScript(spell_thaddius_polarity_shift); - - bool Validate(SpellInfo const* /*spell*/) override - { - return ValidateSpellInfo({ SPELL_POSITIVE_POLARITY, SPELL_NEGATIVE_POLARITY }); - } - - void HandleDummy(SpellEffIndex /* effIndex */) - { - Unit* caster = GetCaster(); - if (Unit* target = GetHitUnit()) - { - target->RemoveAurasDueToSpell(SPELL_POSITIVE_CHARGE_STACK); - target->RemoveAurasDueToSpell(SPELL_NEGATIVE_CHARGE_STACK); - target->CastSpell(target, roll_chance_i(50) ? SPELL_POSITIVE_POLARITY : SPELL_NEGATIVE_POLARITY, true, nullptr, nullptr, caster->GetGUID()); - } - } - - void HandleAfterCast() - { - if (GetCaster()) - { - if (Creature* caster = GetCaster()->ToCreature()) - { - if (caster->GetEntry() == NPC_THADDIUS) - { - caster->AI()->Talk(SAY_ELECT); - caster->AI()->Talk(EMOTE_POLARITY_SHIFTED); - } - } - } - } - - void Register() override - { - OnEffectHitTarget += SpellEffectFn(spell_thaddius_polarity_shift::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY); - AfterCast += SpellCastFn(spell_thaddius_polarity_shift::HandleAfterCast); - } -}; - -class npc_tesla : public CreatureScript -{ -public: - npc_tesla() : CreatureScript("npc_tesla") { } - - CreatureAI* GetAI(Creature* creature) const override - { - return GetNaxxramasAI(creature); - } - - struct npc_teslaAI : public ScriptedAI - { - public: - npc_teslaAI(Creature* creature) : ScriptedAI(creature) { } - void EnterEvadeMode(EvadeReason /*why*/) override { } // never stop casting due to evade - void UpdateAI(uint32 /*diff*/) override { } // never do anything unless told - void JustEngagedWith(Unit* /*who*/) override { } - void DamageTaken(Unit* /*who*/, uint32& damage, DamageEffectType, SpellSchoolMask) override { damage = 0; } // no, you can't kill it - }; -}; - -class at_thaddius_entrance : public AreaTriggerScript -{ -public: - at_thaddius_entrance() : AreaTriggerScript("at_thaddius_entrance") { } - - bool OnTrigger(Player* player, AreaTrigger const* /*areaTrigger*/) override - { - InstanceScript* instance = player->GetInstanceScript(); - if (!instance || instance->GetData(DATA_HAD_THADDIUS_GREET) || instance->GetBossState(BOSS_THADDIUS) == DONE) - return true; - - if (Creature* thaddius = ObjectAccessor::GetCreature(*player, instance->GetGuidData(DATA_THADDIUS_BOSS))) - { - thaddius->AI()->Talk(SAY_GREET); - } - instance->SetData(DATA_HAD_THADDIUS_GREET, 1); - return true; - } -}; +using namespace Thaddius; void AddSC_boss_thaddius() { diff --git a/src/server/scripts/Northrend/Naxxramas/boss_thaddius.h b/src/server/scripts/Northrend/Naxxramas/boss_thaddius.h new file mode 100644 index 00000000000000..e7962364aac11d --- /dev/null +++ b/src/server/scripts/Northrend/Naxxramas/boss_thaddius.h @@ -0,0 +1,741 @@ +#ifndef BOSS_THADDIUS_H_ +#define BOSS_THADDIUS_H_ + +#include "Player.h" +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "SpellScript.h" +#include "SpellInfo.h" +#include "naxxramas.h" + +namespace Thaddius { + +enum Says +{ + // Stalagg + SAY_STAL_AGGRO = 0, + SAY_STAL_SLAY = 1, + SAY_STAL_DEATH = 2, + EMOTE_STAL_DEATH = 3, + EMOTE_STAL_REVIVE = 4, + + // Feugen + SAY_FEUG_AGGRO = 0, + SAY_FEUG_SLAY = 1, + SAY_FEUG_DEATH = 2, + EMOTE_FEUG_DEATH = 3, + EMOTE_FEUG_REVIVE = 4, + + // Thaddius + SAY_GREET = 0, + SAY_AGGRO = 1, + SAY_SLAY = 2, + SAY_ELECT = 3, + SAY_DEATH = 4, + EMOTE_POLARITY_SHIFTED = 6, + + // Tesla Coil + EMOTE_TESLA_LINK_BREAKS = 0, + EMOTE_TESLA_OVERLOAD = 1 +}; + +enum Spells +{ + SPELL_MAGNETIC_PULL = 28337, + SPELL_TESLA_SHOCK = 28099, + SPELL_SHOCK_VISUAL = 28159, + + // Stalagg + SPELL_POWER_SURGE_10 = 54529, + SPELL_POWER_SURGE_25 = 28134, + SPELL_STALAGG_CHAIN = 28096, + + // Feugen + SPELL_STATIC_FIELD_10 = 28135, + SPELL_STATIC_FIELD_25 = 54528, + SPELL_FEUGEN_CHAIN = 28111, + + // Thaddius + SPELL_POLARITY_SHIFT = 28089, + SPELL_BALL_LIGHTNING = 28299, + SPELL_CHAIN_LIGHTNING_10 = 28167, + SPELL_CHAIN_LIGHTNING_25 = 54531, + SPELL_BERSERK = 27680, + SPELL_THADDIUS_VISUAL_LIGHTNING = 28136, + SPELL_THADDIUS_SPAWN_STUN = 28160, + + SPELL_POSITIVE_CHARGE = 28062, + SPELL_POSITIVE_CHARGE_STACK = 29659, + SPELL_NEGATIVE_CHARGE = 28085, + SPELL_NEGATIVE_CHARGE_STACK = 29660, + SPELL_POSITIVE_POLARITY = 28059, + SPELL_NEGATIVE_POLARITY = 28084 +}; + +enum Events +{ + EVENT_MINION_POWER_SURGE = 1, + EVENT_MINION_MAGNETIC_PULL = 2, + EVENT_MINION_CHECK_DISTANCE = 3, + EVENT_MINION_STATIC_FIELD = 4, + + EVENT_THADDIUS_INIT = 5, + EVENT_THADDIUS_ENTER_COMBAT = 6, + EVENT_THADDIUS_CHAIN_LIGHTNING = 7, + EVENT_THADDIUS_BERSERK = 8, + EVENT_THADDIUS_POLARITY_SHIFT = 9, + EVENT_ALLOW_BALL_LIGHTNING = 10 +}; + +enum Misc +{ + ACTION_MAGNETIC_PULL = 1, + ACTION_SUMMON_DIED = 2, + ACTION_RESTORE = 3, + GO_TESLA_COIL_LEFT = 181478, + GO_TESLA_COIL_RIGHT = 181477, + NPC_TESLA_COIL = 16218 +}; + +class boss_thaddius : public CreatureScript +{ +public: + boss_thaddius() : CreatureScript("boss_thaddius") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_thaddiusAI : public BossAI + { + explicit boss_thaddiusAI(Creature* c) : BossAI(c, BOSS_THADDIUS), summons(me), ballLightningEnabled(false) + { + pInstance = me->GetInstanceScript(); + } + + InstanceScript* pInstance; + EventMap events; + SummonList summons; + uint32 summonTimer{}; + uint32 reviveTimer{}; + uint32 resetTimer{}; + bool ballLightningEnabled; + + void DoAction(int32 param) override + { + if (param == ACTION_SUMMON_DIED) + { + if (summonTimer) + { + summonTimer = 0; + reviveTimer = 1; + return; + } + summonTimer = 1; + } + } + + void Reset() override + { + BossAI::Reset(); + events.Reset(); + summons.DespawnAll(); + me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); + me->SetControlled(true, UNIT_STATE_ROOT); + summonTimer = 0; + reviveTimer = 0; + resetTimer = 1; + me->SetPosition(me->GetHomePosition()); + ballLightningEnabled = false; + + me->SummonCreature(NPC_STALAGG, 3450.45f, -2931.42f, 312.091f, 5.49779f); + me->SummonCreature(NPC_FEUGEN, 3508.14f, -2988.65f, 312.092f, 2.37365f); + if (Creature* cr = me->SummonCreature(NPC_TESLA_COIL, 3527.34f, -2951.56f, 318.75f, 0.0f)) + { + cr->RemoveAllAuras(); + cr->InterruptNonMeleeSpells(true); + cr->CastSpell(cr, SPELL_FEUGEN_CHAIN, false); + cr->SetDisableGravity(true); + cr->SetImmuneToPC(false); + cr->SetControlled(true, UNIT_STATE_ROOT); + } + if (Creature* cr = me->SummonCreature(NPC_TESLA_COIL, 3487.04f, -2911.68f, 318.75f, 0.0f)) + { + cr->RemoveAllAuras(); + cr->InterruptNonMeleeSpells(true); + cr->CastSpell(cr, SPELL_STALAGG_CHAIN, false); + cr->SetDisableGravity(true); + cr->SetImmuneToPC(false); + cr->SetControlled(true, UNIT_STATE_ROOT); + } + + if (GameObject* go = me->FindNearestGameObject(GO_TESLA_COIL_LEFT, 100.0f)) + { + go->SetGoState(GO_STATE_ACTIVE); + } + if (GameObject* go = me->FindNearestGameObject(GO_TESLA_COIL_RIGHT, 100.0f)) + { + go->SetGoState(GO_STATE_ACTIVE); + } + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_THADDIUS_GATE))) + { + if (pInstance->GetBossState(BOSS_GLUTH) == DONE) + { + go->SetGoState(GO_STATE_ACTIVE); + } + } + } + + pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_POSITIVE_POLARITY); + pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_POSITIVE_CHARGE_STACK); + pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_NEGATIVE_POLARITY); + pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_NEGATIVE_CHARGE_STACK); + } + + void KilledUnit(Unit* who) override + { + if (!who->IsPlayer()) + return; + + Talk(SAY_SLAY); + if (pInstance) + { + pInstance->SetData(DATA_IMMORTAL_FAIL, 0); + } + } + + void JustDied(Unit* killer) override + { + BossAI::JustDied(killer); + Talk(SAY_DEATH); + if (pInstance) + { + pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_POSITIVE_POLARITY); + pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_POSITIVE_CHARGE_STACK); + pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_NEGATIVE_POLARITY); + pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_NEGATIVE_CHARGE_STACK); + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_THADDIUS_GATE))) + { + go->SetGoState(GO_STATE_ACTIVE); + } + } + } + + void JustSummoned(Creature* cr) override + { + summons.Summon(cr); + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + me->SetInCombatWithZone(); + summons.DoZoneInCombat(NPC_FEUGEN); + summons.DoZoneInCombat(NPC_STALAGG); + } + + void UpdateAI(uint32 diff) override + { + if (resetTimer) + { + resetTimer += diff; + if (resetTimer > 1000) + { + resetTimer = 0; + me->CastSpell(me, SPELL_THADDIUS_SPAWN_STUN, true); + } + return; + } + if (reviveTimer) + { + reviveTimer += diff; + if (reviveTimer >= 12000) + { + for (SummonList::const_iterator itr = summons.begin(); itr != summons.end(); ++itr) + { + if (Creature* cr = ObjectAccessor::GetCreature(*me, (*itr))) + { + if (cr->GetEntry() == NPC_TESLA_COIL) + { + cr->AI()->Talk(EMOTE_TESLA_OVERLOAD); + cr->CastSpell(me, SPELL_SHOCK_VISUAL, true); + } + } + } + reviveTimer = 0; + events.ScheduleEvent(EVENT_THADDIUS_INIT, 750ms); + } + return; + } + + if (!UpdateVictim()) + return; + + events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + if (summonTimer) // Revive + { + summonTimer += diff; + if (summonTimer >= 5000) + { + summons.DoAction(ACTION_RESTORE); + summonTimer = 0; + } + } + + switch (events.ExecuteEvent()) + { + case EVENT_THADDIUS_INIT: + { + me->RemoveAllAuras(); + me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE); + for (SummonList::const_iterator itr = summons.begin(); itr != summons.end(); ++itr) + { + if (Creature* cr = ObjectAccessor::GetCreature(*me, (*itr))) + { + if (cr->GetEntry() == NPC_TESLA_COIL) + { + Unit::Kill(cr, cr); + } + } + } + if (GameObject* go = me->FindNearestGameObject(GO_TESLA_COIL_LEFT, 100.0f)) + { + go->SetGoState(GO_STATE_READY); + } + if (GameObject* go = me->FindNearestGameObject(GO_TESLA_COIL_RIGHT, 100.0f)) + { + go->SetGoState(GO_STATE_READY); + } + me->CastSpell(me, SPELL_THADDIUS_VISUAL_LIGHTNING, true); + events.ScheduleEvent(EVENT_THADDIUS_ENTER_COMBAT, 1s); + break; + } + case EVENT_THADDIUS_ENTER_COMBAT: + Talk(SAY_AGGRO); + me->SetReactState(REACT_AGGRESSIVE); + me->SetControlled(false, UNIT_STATE_STUNNED); + me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE); + events.ScheduleEvent(EVENT_THADDIUS_CHAIN_LIGHTNING, 14s); + events.ScheduleEvent(EVENT_THADDIUS_BERSERK, 6min); + events.ScheduleEvent(EVENT_THADDIUS_POLARITY_SHIFT, 30s); + events.ScheduleEvent(EVENT_ALLOW_BALL_LIGHTNING, 5s); + return; + case EVENT_THADDIUS_BERSERK: + me->CastSpell(me, SPELL_BERSERK, true); + break; + case EVENT_THADDIUS_CHAIN_LIGHTNING: + me->CastSpell(me->GetVictim(), RAID_MODE(SPELL_CHAIN_LIGHTNING_10, SPELL_CHAIN_LIGHTNING_25), false); + events.Repeat(15s); + break; + case EVENT_THADDIUS_POLARITY_SHIFT: + me->CastSpell(me, SPELL_POLARITY_SHIFT, false); + events.Repeat(30s); + break; + case EVENT_ALLOW_BALL_LIGHTNING: + ballLightningEnabled = true; + break; + } + + if (me->IsWithinMeleeRange(me->GetVictim())) + { + DoMeleeAttackIfReady(); + } + else if (ballLightningEnabled) + { + if (Unit* target = SelectTarget(SelectTargetMethod::MaxThreat)) + { + me->CastSpell(target, SPELL_BALL_LIGHTNING, false); + } + } + } + }; +}; + +class boss_thaddius_summon : public CreatureScript +{ +public: + boss_thaddius_summon() : CreatureScript("boss_thaddius_summon") { } + + CreatureAI* GetAI(Creature* pCreature) const override + { + return GetNaxxramasAI(pCreature); + } + + struct boss_thaddius_summonAI : public ScriptedAI + { + explicit boss_thaddius_summonAI(Creature* c) : ScriptedAI(c) + { + pInstance = me->GetInstanceScript(); + overload = false; + } + + InstanceScript* pInstance; + EventMap events; + uint32 pullTimer{}; + uint32 visualTimer{}; + bool overload; + ObjectGuid myCoil; + + void Reset() override + { + pullTimer = 0; + visualTimer = 1; + overload = false; + events.Reset(); + me->SetControlled(false, UNIT_STATE_STUNNED); + if (Creature* cr = me->FindNearestCreature(NPC_TESLA_COIL, 150.0f)) + { + cr->CastSpell(cr, me->GetEntry() == NPC_STALAGG ? SPELL_STALAGG_CHAIN : SPELL_FEUGEN_CHAIN, false); + cr->SetImmuneToPC(false); + myCoil = cr->GetGUID(); + } + } + + void EnterEvadeMode(EvadeReason why) override + { + me->SetControlled(false, UNIT_STATE_STUNNED); + ScriptedAI::EnterEvadeMode(why); + } + + void JustEngagedWith(Unit* pWho) override + { + me->SetInCombatWithZone(); + if (Creature* cr = me->FindNearestCreature(NPC_TESLA_COIL, 150.f, true)) + { + myCoil = cr->GetGUID(); + } + if (me->GetEntry() == NPC_STALAGG) + { + events.ScheduleEvent(EVENT_MINION_POWER_SURGE, 10s); + Talk(SAY_STAL_AGGRO); + } + else + { + events.ScheduleEvent(EVENT_MINION_STATIC_FIELD, 5s); + Talk(SAY_FEUG_AGGRO); + } + events.ScheduleEvent(EVENT_MINION_CHECK_DISTANCE, 5s); + + if (me->GetEntry() == NPC_STALAGG) // This event needs synchronisation, called for stalagg only + { + events.ScheduleEvent(EVENT_MINION_MAGNETIC_PULL, 20s); + } + if (pInstance) + { + if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_THADDIUS_GATE))) + { + go->SetGoState(GO_STATE_READY); + } + if (Creature* cr = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(DATA_THADDIUS_BOSS))) + { + cr->AI()->AttackStart(pWho); + cr->AddThreat(pWho, 10.0f); + } + } + } + + void DoAction(int32 param) override + { + if (param == ACTION_MAGNETIC_PULL) + { + pullTimer = 1; + me->SetControlled(true, UNIT_STATE_STUNNED); + } + else if (param == ACTION_RESTORE) + { + if (!me->IsAlive()) + { + me->Respawn(); + me->SetInCombatWithZone(); + Talk(me->GetEntry() == NPC_STALAGG ? EMOTE_STAL_REVIVE : EMOTE_FEUG_REVIVE); + } + else + { + me->SetHealth(me->GetMaxHealth()); + } + } + } + + void JustDied(Unit* /*killer*/) override + { + Talk(me->GetEntry() == NPC_STALAGG ? SAY_STAL_DEATH : SAY_FEUG_DEATH); + Talk(me->GetEntry() == NPC_STALAGG ? EMOTE_STAL_DEATH : EMOTE_FEUG_DEATH); + if (pInstance) + { + if (Creature* cr = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(DATA_THADDIUS_BOSS))) + { + cr->AI()->DoAction(ACTION_SUMMON_DIED); + } + } + } + + void KilledUnit(Unit* who) override + { + if (!who->IsPlayer()) + return; + + if (pInstance) + { + pInstance->SetData(DATA_IMMORTAL_FAIL, 0); + } + if (!urand(0, 2)) + { + Talk(me->GetEntry() == NPC_STALAGG ? SAY_STAL_SLAY : SAY_FEUG_SLAY); + } + } + + void UpdateAI(uint32 diff) override + { + if (visualTimer) + { + visualTimer += diff; + if (visualTimer >= 3000) + { + visualTimer = 0; + if (Creature* cr = me->FindNearestCreature(NPC_TESLA_COIL, 150.0f)) + { + cr->CastSpell(cr, me->GetEntry() == NPC_STALAGG ? SPELL_STALAGG_CHAIN : SPELL_FEUGEN_CHAIN, false); + } + } + } + + if (!UpdateVictim()) + return; + + if (pullTimer) // Disable AI during pull + { + pullTimer += diff; + if (pullTimer >= 3000) + { + me->SetControlled(false, UNIT_STATE_STUNNED); + pullTimer = 0; + } + return; + } + + events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (events.ExecuteEvent()) + { + case EVENT_MINION_POWER_SURGE: + me->CastSpell(me, RAID_MODE(SPELL_POWER_SURGE_10, SPELL_POWER_SURGE_25), false); + events.Repeat(19s); + break; + case EVENT_MINION_STATIC_FIELD: + me->CastSpell(me, RAID_MODE(SPELL_STATIC_FIELD_10, SPELL_STATIC_FIELD_25), false); + events.Repeat(3s); + break; + case EVENT_MINION_MAGNETIC_PULL: + events.Repeat(20s); + if (pInstance) + { + if (Creature* feugen = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(DATA_FEUGEN_BOSS))) + { + if (!feugen->IsAlive() || !feugen->GetVictim() || !me->GetVictim()) + return; + + float threatFeugen = feugen->GetThreatMgr().GetThreat(feugen->GetVictim()); + float threatStalagg = me->GetThreatMgr().GetThreat(me->GetVictim()); + Unit* tankFeugen = feugen->GetVictim(); + Unit* tankStalagg = me->GetVictim(); + + feugen->GetThreatMgr().ModifyThreatByPercent(tankFeugen, -100); + feugen->AddThreat(tankStalagg, threatFeugen); + feugen->CastSpell(tankStalagg, SPELL_MAGNETIC_PULL, true); + feugen->AI()->DoAction(ACTION_MAGNETIC_PULL); + + me->GetThreatMgr().ModifyThreatByPercent(tankStalagg, -100); + me->AddThreat(tankFeugen, threatStalagg); + me->CastSpell(tankFeugen, SPELL_MAGNETIC_PULL, true); + DoAction(ACTION_MAGNETIC_PULL); + } + } + break; + case EVENT_MINION_CHECK_DISTANCE: + if (Creature* cr = ObjectAccessor::GetCreature(*me, myCoil)) + { + if (!me->GetHomePosition().IsInDist(me, 28) && me->IsInCombat()) + { + if (!overload) + { + overload = true; + cr->AI()->Talk(EMOTE_TESLA_LINK_BREAKS); + me->RemoveAurasDueToSpell(me->GetEntry() == NPC_STALAGG ? SPELL_STALAGG_CHAIN : SPELL_FEUGEN_CHAIN); + cr->InterruptNonMeleeSpells(true); + } + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 1000.f, true)) + { + cr->CastStop(SPELL_TESLA_SHOCK); + cr->CastSpell(target, SPELL_TESLA_SHOCK, true); + } + events.Repeat(1500ms); + break; + } + else + { + overload = false; + cr->CastSpell(cr, me->GetEntry() == NPC_STALAGG ? SPELL_STALAGG_CHAIN : SPELL_FEUGEN_CHAIN, false); + } + } + events.Repeat(5s); + break; + } + DoMeleeAttackIfReady(); + } + }; +}; + +class spell_thaddius_pos_neg_charge : public SpellScript +{ + PrepareSpellScript(spell_thaddius_pos_neg_charge); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_POSITIVE_CHARGE, SPELL_POSITIVE_CHARGE_STACK }); + } + + void HandleTargets(std::list& targets) + { + uint8 count = 0; + for (auto& ihit : targets) + { + if (ihit->GetGUID() != GetCaster()->GetGUID()) + { + if (Player* target = ihit->ToPlayer()) + { + if (target->HasAura(GetTriggeringSpell()->Id)) + { + ++count; + } + } + } + } + + if (count) + { + uint32 spellId = GetSpellInfo()->Id == SPELL_POSITIVE_CHARGE ? SPELL_POSITIVE_CHARGE_STACK : SPELL_NEGATIVE_CHARGE_STACK; + GetCaster()->SetAuraStack(spellId, GetCaster(), count); + } + } + + void HandleDamage(SpellEffIndex /*effIndex*/) + { + if (!GetTriggeringSpell()) + return; + + Unit* target = GetHitUnit(); + if (!target) + return; + + if (target->HasAura(GetTriggeringSpell()->Id) || !target->IsPlayer()) + { + SetHitDamage(0); + } + else if (target->GetInstanceScript()) + { + target->GetInstanceScript()->SetData(DATA_CHARGES_CROSSED, 0); + } + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_thaddius_pos_neg_charge::HandleDamage, EFFECT_0, SPELL_EFFECT_SCHOOL_DAMAGE); + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_thaddius_pos_neg_charge::HandleTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ALLY); + } +}; + +class spell_thaddius_polarity_shift : public SpellScript +{ + PrepareSpellScript(spell_thaddius_polarity_shift); + + bool Validate(SpellInfo const* /*spell*/) override + { + return ValidateSpellInfo({ SPELL_POSITIVE_POLARITY, SPELL_NEGATIVE_POLARITY }); + } + + void HandleDummy(SpellEffIndex /* effIndex */) + { + Unit* caster = GetCaster(); + if (Unit* target = GetHitUnit()) + { + target->RemoveAurasDueToSpell(SPELL_POSITIVE_CHARGE_STACK); + target->RemoveAurasDueToSpell(SPELL_NEGATIVE_CHARGE_STACK); + target->CastSpell(target, roll_chance_i(50) ? SPELL_POSITIVE_POLARITY : SPELL_NEGATIVE_POLARITY, true, nullptr, nullptr, caster->GetGUID()); + } + } + + void HandleAfterCast() + { + if (GetCaster()) + { + if (Creature* caster = GetCaster()->ToCreature()) + { + if (caster->GetEntry() == NPC_THADDIUS) + { + caster->AI()->Talk(SAY_ELECT); + caster->AI()->Talk(EMOTE_POLARITY_SHIFTED); + } + } + } + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_thaddius_polarity_shift::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY); + AfterCast += SpellCastFn(spell_thaddius_polarity_shift::HandleAfterCast); + } +}; + +class npc_tesla : public CreatureScript +{ +public: + npc_tesla() : CreatureScript("npc_tesla") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return GetNaxxramasAI(creature); + } + + struct npc_teslaAI : public ScriptedAI + { + public: + npc_teslaAI(Creature* creature) : ScriptedAI(creature) { } + void EnterEvadeMode(EvadeReason /*why*/) override { } // never stop casting due to evade + void UpdateAI(uint32 /*diff*/) override { } // never do anything unless told + void JustEngagedWith(Unit* /*who*/) override { } + void DamageTaken(Unit* /*who*/, uint32& damage, DamageEffectType, SpellSchoolMask) override { damage = 0; } // no, you can't kill it + }; +}; + +class at_thaddius_entrance : public AreaTriggerScript +{ +public: + at_thaddius_entrance() : AreaTriggerScript("at_thaddius_entrance") { } + + bool OnTrigger(Player* player, AreaTrigger const* /*areaTrigger*/) override + { + InstanceScript* instance = player->GetInstanceScript(); + if (!instance || instance->GetData(DATA_HAD_THADDIUS_GREET) || instance->GetBossState(BOSS_THADDIUS) == DONE) + return true; + + if (Creature* thaddius = ObjectAccessor::GetCreature(*player, instance->GetGuidData(DATA_THADDIUS_BOSS))) + { + thaddius->AI()->Talk(SAY_GREET); + } + instance->SetData(DATA_HAD_THADDIUS_GREET, 1); + + return true; + } +}; + +} + +#endif \ No newline at end of file diff --git a/src/server/scripts/Outland/BlackTemple/boss_bloodboil.cpp b/src/server/scripts/Outland/BlackTemple/boss_bloodboil.cpp index af5189dad91f57..97c2a096d4df13 100644 --- a/src/server/scripts/Outland/BlackTemple/boss_bloodboil.cpp +++ b/src/server/scripts/Outland/BlackTemple/boss_bloodboil.cpp @@ -65,110 +65,127 @@ enum Misc GROUP_DELAY = 1 }; -struct boss_gurtogg_bloodboil : public BossAI +class boss_gurtogg_bloodboil : public CreatureScript { - boss_gurtogg_bloodboil(Creature* creature) : BossAI(creature, DATA_GURTOGG_BLOODBOIL), _recentlySpoken(false) { } +public: + boss_gurtogg_bloodboil() : CreatureScript("boss_gurtogg_bloodboil") { } - void Reset() override + CreatureAI* GetAI(Creature* creature) const override { - BossAI::Reset(); - _recentlySpoken = false; + return GetBlackTempleAI(creature); } - void JustEngagedWith(Unit* who) override + struct boss_gurtogg_bloodboilAI : public BossAI { - BossAI::JustEngagedWith(who); - Talk(SAY_AGGRO); + boss_gurtogg_bloodboilAI(Creature* creature) : BossAI(creature, DATA_GURTOGG_BLOODBOIL), _recentlySpoken(false) { } - DoCastSelf(SPELL_ACIDIC_WOUND, true); - - ScheduleTimedEvent(10s, [&] { - if (!me->HasAura(SPELL_FEL_RAGE_SELF)) - me->CastCustomSpell(SPELL_BLOODBOIL, SPELLVALUE_MAX_TARGETS, 5, me, false); - }, 10s); + void Reset() override + { + BossAI::Reset(); + _recentlySpoken = false; + } - ScheduleTimedEvent(38s, [&] { - DoCastVictim(me->HasAura(SPELL_FEL_RAGE_SELF) ? SPELL_FEL_ACID_BREATH2 : SPELL_FEL_ACID_BREATH1); - }, 30s); + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + Talk(SAY_AGGRO); - ScheduleTimedEvent(14s, [&] { - DoCastVictim(me->HasAura(SPELL_FEL_RAGE_SELF) ? SPELL_EJECT2 : SPELL_EJECT1); - }, 20s); + DoCastSelf(SPELL_ACIDIC_WOUND, true); - ScheduleTimedEvent(5s, [&] { - DoCastVictim(me->HasAura(SPELL_FEL_RAGE_SELF) ? SPELL_ARCING_SMASH2 : SPELL_ARCING_SMASH1); - }, 15s); + ScheduleTimedEvent(10s, [&] { + me->CastCustomSpell(SPELL_BLOODBOIL, SPELLVALUE_MAX_TARGETS, 5, me, false); + }, 10s); + + ScheduleTimedEvent(38s, [&] { + DoCastVictim(me->HasAura(SPELL_FEL_RAGE_SELF) ? SPELL_FEL_ACID_BREATH2 : SPELL_FEL_ACID_BREATH1); + }, 30s); + + ScheduleTimedEvent(14s, [&] { + DoCastVictim(me->HasAura(SPELL_FEL_RAGE_SELF) ? SPELL_EJECT2 : SPELL_EJECT1); + }, 20s); + + ScheduleTimedEvent(5s, [&] { + DoCastVictim(me->HasAura(SPELL_FEL_RAGE_SELF) ? SPELL_ARCING_SMASH2 : SPELL_ARCING_SMASH1); + }, 15s); + + ScheduleTimedEvent(1min, [&] { + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 40.0f, true)) + { + me->RemoveAurasByType(SPELL_AURA_MOD_TAUNT); + DoCastSelf(SPELL_FEL_RAGE_SELF, true); + DoCast(target, SPELL_FEL_RAGE_TARGET, true); + DoCast(target, SPELL_FEL_RAGE_2, true); + DoCast(target, SPELL_FEL_RAGE_3, true); + DoCast(target, SPELL_FEL_RAGE_SIZE, true); + target->CastSpell(me, SPELL_TAUNT_GURTOGG, true); + + DoCast(target, SPELL_FEL_GEYSER_SUMMON, true); + DoCastSelf(SPELL_FEL_GEYSER_STUN, true); + DoCastSelf(SPELL_INSIGNIFICANCE, true); + + me->m_Events.AddEventAtOffset([&] { + DoCastVictim(SPELL_CHARGE); + }, 2s); + + scheduler.DelayGroup(GROUP_DELAY, 30s); + } + }, 90s); + + ScheduleUniqueTimedEvent(10min, [&] { + Talk(SAY_ENRAGE); + DoCastSelf(SPELL_BERSERK, true); + }, EVENT_SPELL_BERSERK); + + scheduler.Schedule(28s, [this](TaskContext context) { + context.SetGroup(GROUP_DELAY); + DoCastVictim(SPELL_BEWILDERING_STRIKE); + context.Repeat(30s); + }); + } - ScheduleTimedEvent(1min, [&] { - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 40.0f, true)) + void KilledUnit(Unit* /*victim*/) override + { + if (!_recentlySpoken) { - me->RemoveAurasByType(SPELL_AURA_MOD_TAUNT); - DoCastSelf(SPELL_FEL_RAGE_SELF, true); - DoCast(target, SPELL_FEL_RAGE_TARGET, true); - DoCast(target, SPELL_FEL_RAGE_2, true); - DoCast(target, SPELL_FEL_RAGE_3, true); - DoCast(target, SPELL_FEL_RAGE_SIZE, true); - target->CastSpell(me, SPELL_TAUNT_GURTOGG, true); - - DoCast(target, SPELL_FEL_GEYSER_SUMMON, true); - DoCastSelf(SPELL_FEL_GEYSER_STUN, true); - DoCastSelf(SPELL_INSIGNIFICANCE, true); - + Talk(SAY_SLAY); me->m_Events.AddEventAtOffset([&] { - DoCastVictim(SPELL_CHARGE); - }, 2s); - - scheduler.DelayGroup(GROUP_DELAY, 30s); + _recentlySpoken = false; + }, 6s); } - }, 90s); - - ScheduleUniqueTimedEvent(10min, [&] { - Talk(SAY_ENRAGE); - DoCastSelf(SPELL_BERSERK, true); - }, EVENT_SPELL_BERSERK); - - scheduler.Schedule(28s, [this](TaskContext context) { - context.SetGroup(GROUP_DELAY); - DoCastVictim(SPELL_BEWILDERING_STRIKE); - context.Repeat(30s); - }); - } + } - bool CanAIAttack(Unit const* who) const override - { - return !who->IsImmunedToDamage(SPELL_SCHOOL_MASK_ALL); - } + void JustSummoned(Creature* summon) override + { + summons.Summon(summon); + summon->CastSpell(summon, SPELL_FEL_GEYSER_DAMAGE, false); + } - void KilledUnit(Unit* /*victim*/) override - { - if (!_recentlySpoken) + void JustDied(Unit* killer) override { - Talk(SAY_SLAY); - me->m_Events.AddEventAtOffset([&] { - _recentlySpoken = false; - }, 6s); + BossAI::JustDied(killer); + Talk(SAY_DEATH); } - } - void JustSummoned(Creature* summon) override - { - summons.Summon(summon); - summon->CastSpell(summon, SPELL_FEL_GEYSER_DAMAGE, false); - } + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; - void JustDied(Unit* killer) override - { - BossAI::JustDied(killer); - Talk(SAY_DEATH); - } + scheduler.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; - bool CheckEvadeIfOutOfCombatArea() const override - { - return me->GetHomePosition().GetExactDist2d(me) > 105.0f; - } + DoMeleeAttackIfReady(); + } + + bool CheckEvadeIfOutOfCombatArea() const override + { + return me->GetHomePosition().GetExactDist2d(me) > 105.0f; + } -private: - bool _recentlySpoken; + private: + bool _recentlySpoken; + }; }; class spell_gurtogg_bloodboil : public SpellScript @@ -214,7 +231,7 @@ class spell_gurtogg_eject : public SpellScript void AddSC_boss_gurtogg_bloodboil() { - RegisterBlackTempleCreatureAI(boss_gurtogg_bloodboil); + new boss_gurtogg_bloodboil(); RegisterSpellScript(spell_gurtogg_bloodboil); RegisterSpellScript(spell_gurtogg_eject); } diff --git a/src/server/scripts/Outland/BlackTemple/boss_illidan.cpp b/src/server/scripts/Outland/BlackTemple/boss_illidan.cpp index e4de895c3511dd..a07fc810d31c6b 100644 --- a/src/server/scripts/Outland/BlackTemple/boss_illidan.cpp +++ b/src/server/scripts/Outland/BlackTemple/boss_illidan.cpp @@ -1209,10 +1209,7 @@ enum WarbladeTear struct npc_blade_of_azzinoth : public ScriptedAI { - npc_blade_of_azzinoth(Creature* creature) : ScriptedAI(creature) - { - me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); - } + npc_blade_of_azzinoth(Creature* creature) : ScriptedAI(creature) { } void IsSummonedBy(WorldObject* /*summoner*/) override { @@ -1260,13 +1257,12 @@ enum FlameAzzinoth struct npc_flame_of_azzinoth : public ScriptedAI { - npc_flame_of_azzinoth(Creature* creature) : ScriptedAI(creature) { } + npc_flame_of_azzinoth(Creature* creature) : ScriptedAI(creature), _bladeSummoner(nullptr) { } void IsSummonedBy(WorldObject* /*summoner*/) override { // Flame is set to be Illidan's summon, so we check for nearest blade - if (Creature* _blade = me->FindNearestCreature(NPC_BLADE_OF_AZZINOTH, 15.0f)) - _bladeGUID = _blade->GetGUID(); + _bladeSummoner = me->FindNearestCreature(NPC_BLADE_OF_AZZINOTH, 15.0f); me->SetCorpseDelay(2); me->SetReactState(REACT_DEFENSIVE); @@ -1289,8 +1285,8 @@ struct npc_flame_of_azzinoth : public ScriptedAI void JustEngagedWith(Unit* /*who*/) override { ScheduleTimedEvent(10s, [&] { - if (Creature* _blade = ObjectAccessor::GetCreature(*me, _bladeGUID)) - if (Unit* target = _blade->AI()->SelectTarget(SelectTargetMethod::Random, 0, 30.0f, true)) + if (_bladeSummoner) + if (Unit* target = _bladeSummoner->AI()->SelectTarget(SelectTargetMethod::Random, 0, 30.f, true)) DoCast(target, SPELL_CHARGE); }, 5s, 20s); @@ -1305,7 +1301,7 @@ struct npc_flame_of_azzinoth : public ScriptedAI } private: - ObjectGuid _bladeGUID; + Creature* _bladeSummoner; }; class spell_illidan_draw_soul : public SpellScript diff --git a/src/server/scripts/Outland/BlackTemple/boss_illidari_council.cpp b/src/server/scripts/Outland/BlackTemple/boss_illidari_council.cpp index 016cbdba89d090..407506228f2747 100644 --- a/src/server/scripts/Outland/BlackTemple/boss_illidari_council.cpp +++ b/src/server/scripts/Outland/BlackTemple/boss_illidari_council.cpp @@ -262,7 +262,7 @@ struct boss_illidari_council_memberAI : public ScriptedAI if (events.GetNextEventTime(EVENT_KILL_TALK) == 0) { Talk(SAY_COUNCIL_SLAY); - events.ScheduleEvent(EVENT_KILL_TALK, 6s); + events.ScheduleEvent(EVENT_KILL_TALK, 6000); } } @@ -303,12 +303,12 @@ struct boss_gathios_the_shatterer : public boss_illidari_council_memberAI void JustEngagedWith(Unit* who) override { boss_illidari_council_memberAI::JustEngagedWith(who); - events.ScheduleEvent(EVENT_SPELL_BLESSING, 10s); - events.ScheduleEvent(EVENT_SPELL_AURA, 0s); - events.ScheduleEvent(EVENT_SPELL_SEAL, 2s); - events.ScheduleEvent(EVENT_SPELL_HAMMER_OF_JUSTICE, 6s); - events.ScheduleEvent(EVENT_SPELL_JUDGEMENT, 15s); - events.ScheduleEvent(EVENT_SPELL_CONSECRATION, 4s); + events.ScheduleEvent(EVENT_SPELL_BLESSING, 10000); + events.ScheduleEvent(EVENT_SPELL_AURA, 0); + events.ScheduleEvent(EVENT_SPELL_SEAL, 2000); + events.ScheduleEvent(EVENT_SPELL_HAMMER_OF_JUSTICE, 6000); + events.ScheduleEvent(EVENT_SPELL_JUDGEMENT, 8000); + events.ScheduleEvent(EVENT_SPELL_CONSECRATION, 4000); } void UpdateAI(uint32 diff) override @@ -328,18 +328,18 @@ struct boss_gathios_the_shatterer : public boss_illidari_council_memberAI me->CastSpell(member, _toggleBlessing ? SPELL_BLESSING_OF_PROTECTION : SPELL_BLESSING_OF_SPELL_WARDING); _toggleBlessing = !_toggleBlessing; } - events.ScheduleEvent(EVENT_SPELL_BLESSING, 15s); + events.ScheduleEvent(EVENT_SPELL_BLESSING, 15000); break; case EVENT_SPELL_AURA: me->CastSpell(me, _toggleAura ? SPELL_DEVOTION_AURA : SPELL_CHROMATIC_RESISTANCE_AURA); _toggleAura = !_toggleAura; - events.ScheduleEvent(EVENT_SPELL_AURA, 60s); + events.ScheduleEvent(EVENT_SPELL_AURA, 60000); break; case EVENT_SPELL_CONSECRATION: if (roll_chance_i(50)) Talk(SAY_COUNCIL_SPECIAL); me->CastSpell(me, SPELL_CONSECRATION, false); - events.ScheduleEvent(EVENT_SPELL_AURA, 30s); + events.ScheduleEvent(EVENT_SPELL_AURA, 30000); break; case EVENT_SPELL_HAMMER_OF_JUSTICE: if (Unit* target = me->GetVictim()) @@ -349,16 +349,16 @@ struct boss_gathios_the_shatterer : public boss_illidari_council_memberAI events.ScheduleEvent(EVENT_SPELL_HAMMER_OF_JUSTICE, 20s); break; } - events.ScheduleEvent(EVENT_SPELL_HAMMER_OF_JUSTICE, 0s); + events.ScheduleEvent(EVENT_SPELL_HAMMER_OF_JUSTICE, 0); break; case EVENT_SPELL_SEAL: me->CastSpell(me, _toggleSeal ? SPELL_SEAL_OF_COMMAND : SPELL_SEAL_OF_BLOOD); _toggleSeal = !_toggleSeal; - events.ScheduleEvent(EVENT_SPELL_SEAL, 20s); + events.ScheduleEvent(EVENT_SPELL_SEAL, 20000); break; case EVENT_SPELL_JUDGEMENT: me->CastSpell(me->GetVictim(), SPELL_JUDGEMENT, false); - events.ScheduleEvent(EVENT_SPELL_JUDGEMENT, 16s, 20s); + events.ScheduleEvent(EVENT_SPELL_JUDGEMENT, 20000); break; } @@ -493,10 +493,10 @@ struct boss_lady_malande : public boss_illidari_council_memberAI void JustEngagedWith(Unit* who) override { boss_illidari_council_memberAI::JustEngagedWith(who); - events.ScheduleEvent(EVENT_SPELL_REFLECTIVE_SHIELD, 10s); - events.ScheduleEvent(EVENT_SPELL_CIRCLE_OF_HEALING, 20s); - events.ScheduleEvent(EVENT_SPELL_DIVINE_WRATH, 5s); - events.ScheduleEvent(EVENT_SPELL_EMPOWERED_SMITE, 15s); + events.ScheduleEvent(EVENT_SPELL_REFLECTIVE_SHIELD, 10000); + events.ScheduleEvent(EVENT_SPELL_CIRCLE_OF_HEALING, 20000); + events.ScheduleEvent(EVENT_SPELL_DIVINE_WRATH, 5000); + events.ScheduleEvent(EVENT_SPELL_EMPOWERED_SMITE, 15000); } void UpdateAI(uint32 diff) override @@ -512,22 +512,22 @@ struct boss_lady_malande : public boss_illidari_council_memberAI { case EVENT_SPELL_CIRCLE_OF_HEALING: me->CastSpell(me, SPELL_CIRCLE_OF_HEALING, false); - events.ScheduleEvent(EVENT_SPELL_CIRCLE_OF_HEALING, 20s); + events.ScheduleEvent(EVENT_SPELL_CIRCLE_OF_HEALING, 20000); break; case EVENT_SPELL_REFLECTIVE_SHIELD: if (roll_chance_i(50)) Talk(SAY_COUNCIL_SPECIAL); me->CastSpell(me, SPELL_REFLECTIVE_SHIELD, false); - events.ScheduleEvent(EVENT_SPELL_REFLECTIVE_SHIELD, 40s); + events.ScheduleEvent(EVENT_SPELL_REFLECTIVE_SHIELD, 40000); break; case EVENT_SPELL_DIVINE_WRATH: if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f)) me->CastSpell(target, SPELL_DIVINE_WRATH, false); - events.ScheduleEvent(EVENT_SPELL_DIVINE_WRATH, 20s); + events.ScheduleEvent(EVENT_SPELL_DIVINE_WRATH, 20000); break; case EVENT_SPELL_EMPOWERED_SMITE: me->CastSpell(me->GetVictim(), SPELL_EMPOWERED_SMITE, false); - events.ScheduleEvent(EVENT_SPELL_EMPOWERED_SMITE, 3s); + events.ScheduleEvent(EVENT_SPELL_EMPOWERED_SMITE, 3000); break; } } @@ -541,8 +541,8 @@ struct boss_veras_darkshadow : public boss_illidari_council_memberAI { me->SetCanDualWield(true); boss_illidari_council_memberAI::JustEngagedWith(who); - events.ScheduleEvent(EVENT_SPELL_VANISH, 10s); - events.ScheduleEvent(EVENT_SPELL_ENRAGE, 90s); + events.ScheduleEvent(EVENT_SPELL_VANISH, 10000); + events.ScheduleEvent(EVENT_SPELL_ENRAGE, 900000); } void JustSummoned(Creature* summon) override @@ -566,8 +566,8 @@ struct boss_veras_darkshadow : public boss_illidari_council_memberAI Talk(SAY_COUNCIL_SPECIAL); me->CastSpell(me, SPELL_DEADLY_STRIKE, false); me->CastSpell(me, SPELL_VANISH, false); - events.ScheduleEvent(EVENT_SPELL_VANISH, 60s); - events.ScheduleEvent(EVENT_SPELL_VANISH_OUT, 29s); + events.ScheduleEvent(EVENT_SPELL_VANISH, 60000); + events.ScheduleEvent(EVENT_SPELL_VANISH_OUT, 29000); break; case EVENT_SPELL_VANISH_OUT: me->CastSpell(me, SPELL_VANISH_OUT, false); diff --git a/src/server/scripts/Outland/BlackTemple/boss_shade_of_akama.cpp b/src/server/scripts/Outland/BlackTemple/boss_shade_of_akama.cpp index 1f59b410e08492..9852bc345ea1ac 100644 --- a/src/server/scripts/Outland/BlackTemple/boss_shade_of_akama.cpp +++ b/src/server/scripts/Outland/BlackTemple/boss_shade_of_akama.cpp @@ -220,7 +220,6 @@ struct npc_akama_shade : public ScriptedAI DoCastSelf(SPELL_STEALTH, true); me->SetWalk(true); _sayLowHealth = false; - _died = false; scheduler.CancelAll(); } @@ -233,14 +232,14 @@ struct npc_akama_shade : public ScriptedAI case POINT_ENGAGE: me->SetHomePosition(me->GetPosition()); me->SetFaction(FACTION_ENGAGE); - DoCastSelf(SPELL_AKAMA_SOUL_CHANNEL, true); + DoCast(me, SPELL_AKAMA_SOUL_CHANNEL, true); break; case POINT_OUTRO: DoCastSelf(SPELL_AKAMA_SOUL_RETRIEVE, true); ScheduleUniqueTimedEvent(15600ms, [&] { - Talk(SAY_BROKEN_FREE_0); - me->SummonCreatureGroup(SUMMON_GROUP_BROKENS); + Talk(SAY_BROKEN_FREE_0); + me->SummonCreatureGroup(SUMMON_GROUP_BROKENS); }, 1); ScheduleUniqueTimedEvent(26550ms, [&] { @@ -276,19 +275,6 @@ struct npc_akama_shade : public ScriptedAI _sayLowHealth = true; Talk(SAY_LOW_HEALTH); } - else if (damage >= me->GetHealth() && !_died) - { - damage = me->GetHealth() - 1; - Talk(SAY_DEATH); - if (Creature* shade = instance->GetCreature(DATA_SHADE_OF_AKAMA)) - { - shade->SetHomePosition(shade->GetHomePosition()); - shade->AI()->EnterEvadeMode(); - } - - me->DespawnOrUnsummon(); - ScriptedAI::EnterEvadeMode(EvadeReason::EVADE_REASON_OTHER); - } } void DoAction(int32 param) override @@ -312,6 +298,18 @@ struct npc_akama_shade : public ScriptedAI void EnterEvadeMode(EvadeReason /*why*/) override { } + void JustDied(Unit* /*killer*/) override + { + Talk(SAY_DEATH); + if (Creature* shade = instance->GetCreature(DATA_SHADE_OF_AKAMA)) + { + shade->SetHomePosition(shade->GetHomePosition()); + shade->AI()->EnterEvadeMode(); + } + + me->DespawnOrUnsummon(); + } + void JustEngagedWith(Unit* /*who*/) override { ScheduleTimedEvent(2s, [&] @@ -340,7 +338,6 @@ struct npc_akama_shade : public ScriptedAI private: bool _sayLowHealth; - bool _died; }; struct npc_creature_generator_akama : public ScriptedAI @@ -448,18 +445,27 @@ struct npc_creature_generator_akama : public ScriptedAI struct npc_ashtongue_sorcerer : public NullCreatureAI { - npc_ashtongue_sorcerer(Creature* creature) : NullCreatureAI(creature) { } + npc_ashtongue_sorcerer(Creature* creature) : NullCreatureAI(creature) + { + instance = creature->GetInstanceScript(); + } void MovementInform(uint32 type, uint32 point) override { if (type == POINT_MOTION_TYPE && point == POINT_ENGAGE) me->CastSpell(me, SPELL_SHADE_SOUL_CHANNEL, true); } + +private: + InstanceScript* instance; }; struct npc_ashtongue_channeler : public NullCreatureAI { - npc_ashtongue_channeler(Creature* creature) : NullCreatureAI(creature) { } + npc_ashtongue_channeler(Creature* creature) : NullCreatureAI(creature) + { + instance = creature->GetInstanceScript(); + } void Reset() override { @@ -478,6 +484,7 @@ struct npc_ashtongue_channeler : public NullCreatureAI } private: + InstanceScript* instance; TaskScheduler scheduler; }; diff --git a/src/server/scripts/Outland/TempestKeep/Eye/boss_kaelthas.cpp b/src/server/scripts/Outland/TempestKeep/Eye/boss_kaelthas.cpp index 4f881ff03bbbee..23226e22555a15 100644 --- a/src/server/scripts/Outland/TempestKeep/Eye/boss_kaelthas.cpp +++ b/src/server/scripts/Outland/TempestKeep/Eye/boss_kaelthas.cpp @@ -294,6 +294,11 @@ struct boss_kaelthas : public BossAI BossAI::AttackStart(who); } + void JustReachedHome() override + { + Reset(); + } + void MoveInLineOfSight(Unit* who) override { if (_phase == PHASE_NONE && who->IsPlayer() && me->IsValidAttackTarget(who)) diff --git a/src/server/shared/DataStores/DBCDatabaseLoader.cpp b/src/server/shared/DataStores/DBCDatabaseLoader.cpp index 1985b01bb5f566..cd139243e1cb87 100644 --- a/src/server/shared/DataStores/DBCDatabaseLoader.cpp +++ b/src/server/shared/DataStores/DBCDatabaseLoader.cpp @@ -28,8 +28,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); } @@ -71,11 +70,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; @@ -99,7 +98,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 6dfcadb48656ef..cc3f71edff997a 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 d17da2dd991a18..26ecc9f030ef07 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 42c81bccfa39b9..1a11f2904a6033 100644 --- a/src/server/shared/SharedDefines.h +++ b/src/server/shared/SharedDefines.h @@ -1367,7 +1367,7 @@ enum Mechanics : uint32 (1<