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 9e0d1090a6b463..88f9e64e137170 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-14 runs-on: ${{ matrix.os }} name: ${{ matrix.os }} - if: | - github.repository == 'azerothcore/azerothcore-wotlk' - && !github.event.pull_request.draft - && (github.ref == 'refs/heads/master' || contains(github.event.pull_request.labels.*.name, 'run-build') || github.event.label.name == 'run-build') steps: - uses: actions/checkout@v4 - name: Cache diff --git a/.github/workflows/tools_build.yml b/.github/workflows/tools_build.yml index 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 79507bc9f3c975..10dfbc4b64e14f 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/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 68fb037e839ca5..ddca1a45ff69a3 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", ""); @@ -442,6 +443,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) @@ -484,6 +490,8 @@ void StopDB() WorldDatabase.Close(); LoginDatabase.Close(); + sScriptMgr->OnDatabasesClosing(); + MySQL::Library_End(); } @@ -574,6 +582,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()) { @@ -603,6 +613,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 ca79c6b08b7569..d0736d66149912 100644 --- a/src/server/apps/worldserver/worldserver.conf.dist +++ b/src/server/apps/worldserver/worldserver.conf.dist @@ -4448,3 +4448,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 7bf4a32077a412..31d2dabe3e9fce 100644 --- a/src/server/database/Database/DatabaseEnv.h +++ b/src/server/database/Database/DatabaseEnv.h @@ -25,6 +25,10 @@ #include "Implementation/LoginDatabase.h" #include "Implementation/WorldDatabase.h" +#ifdef MOD_PLAYERBOTS +#include "Implementation/PlayerbotsDatabase.h" +#endif + #include "PreparedStatement.h" #include "QueryCallback.h" #include "QueryResult.h" @@ -37,4 +41,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 eb3276d16418b0..357a0a9da42499 100644 --- a/src/server/database/Database/DatabaseEnvFwd.h +++ b/src/server/database/Database/DatabaseEnvFwd.h @@ -32,6 +32,10 @@ class CharacterDatabaseConnection; class LoginDatabaseConnection; class WorldDatabaseConnection; +#ifdef MOD_PLAYERBOTS +class PlayerbotsDatabaseConnection; +#endif + class PreparedStatementBase; template @@ -41,6 +45,10 @@ using CharacterDatabasePreparedStatement = PreparedStatement; using WorldDatabasePreparedStatement = PreparedStatement; +#ifdef MOD_PLAYERBOTS +using PlayerbotsDatabasePreparedStatement = PreparedStatement; +#endif + class PreparedResultSet; using PreparedQueryResult = std::shared_ptr; using PreparedQueryResultFuture = std::future; @@ -70,6 +78,10 @@ using CharacterDatabaseTransaction = SQLTransaction using LoginDatabaseTransaction = SQLTransaction; using WorldDatabaseTransaction = SQLTransaction; +#ifdef MOD_PLAYERBOTS +using PlayerbotsDatabaseTransaction = SQLTransaction; +#endif + class SQLQueryHolderBase; using QueryResultHolderFuture = std::future; using QueryResultHolderPromise = std::promise; @@ -81,6 +93,10 @@ using CharacterDatabaseQueryHolder = SQLQueryHolder using LoginDatabaseQueryHolder = SQLQueryHolder; using WorldDatabaseQueryHolder = SQLQueryHolder; +#ifdef MOD_PLAYERBOTS +using PlayerbotsDatabaseQueryHolder = SQLQueryHolder; +#endif + class SQLQueryHolderCallback; // mysql diff --git a/src/server/database/Database/DatabaseLoader.cpp b/src/server/database/Database/DatabaseLoader.cpp index 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 0fd20d4b479529..0b206885b3ab3a 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 @@ -566,3 +570,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 2f91224cefe3f4..70f8c681cb9268 100644 --- a/src/server/database/Updater/DBUpdater.cpp +++ b/src/server/database/Updater/DBUpdater.cpp @@ -75,10 +75,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<> @@ -91,7 +97,7 @@ bool DBUpdater::IsEnabled(uint32 const updateMask) template<> std::string DBUpdater::GetDBModuleName() { - return "db-auth"; + return "auth"; } // World Database @@ -107,10 +113,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<> @@ -123,7 +135,7 @@ bool DBUpdater::IsEnabled(uint32 const updateMask) template<> std::string DBUpdater::GetDBModuleName() { - return "db-world"; + return "world"; } // Character Database @@ -139,10 +151,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<> @@ -155,9 +173,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() @@ -223,7 +281,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)) { @@ -298,7 +356,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; @@ -523,3 +581,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 79e288d1e25b8b..d1008ba5d73206 100644 --- a/src/server/database/Updater/UpdateFetcher.cpp +++ b/src/server/database/Updater/UpdateFetcher.cpp @@ -164,7 +164,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..f0532ee5b445e5 100644 --- a/src/server/game/Battlegrounds/ArenaTeam.cpp +++ b/src/server/game/Battlegrounds/ArenaTeam.cpp @@ -1100,3 +1100,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 3aa09b3e5cb7dd..a7862d101e29f3 100644 --- a/src/server/game/Battlegrounds/ArenaTeam.h +++ b/src/server/game/Battlegrounds/ArenaTeam.h @@ -217,6 +217,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.h b/src/server/game/Battlegrounds/Battleground.h index 4ab13e9d86e347..1460514d9eeee4 100644 --- a/src/server/game/Battlegrounds/Battleground.h +++ b/src/server/game/Battlegrounds/Battleground.h @@ -214,6 +214,7 @@ struct BattlegroundObjectInfo enum ArenaType : uint8 { + ARENA_TYPE_NONE = 0, ARENA_TYPE_2v2 = 2, ARENA_TYPE_3v3 = 3, ARENA_TYPE_5v5 = 5 diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundAB.h b/src/server/game/Battlegrounds/Zones/BattlegroundAB.h index 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 0383af41ba517e..83f5ed4fe98a1c 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 936e9e9b5e75d5..41ac26f1ccaf48 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundEY.h +++ b/src/server/game/Battlegrounds/Zones/BattlegroundEY.h @@ -375,6 +375,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: @@ -414,6 +433,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; @@ -429,26 +450,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 b9e958677f65da..a0fa83d96bcd5a 100644 --- a/src/server/game/Chat/Chat.cpp +++ b/src/server/game/Chat/Chat.cpp @@ -370,6 +370,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 d843f35400d5be..f2ea70c8985984 100644 --- a/src/server/game/Chat/Chat.h +++ b/src/server/game/Chat/Chat.h @@ -34,6 +34,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: @@ -48,6 +59,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 e662419acc1428..6655b0d4c4c3d7 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 83373b6816b350..51bd3bef858c64 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -1411,6 +1411,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(); @@ -3659,7 +3660,7 @@ bool Creature::IsMovementPreventedByCasting() const void Creature::SetCannotReachTarget(ObjectGuid const& cannotReach) { if (cannotReach == m_cannotReachTarget) - { +{ return; } diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index 60f4c6a7ff485e..2f878a4c48fa26 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -370,6 +370,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/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/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 07e6043cd34632..ede6668a1ad6b2 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; } @@ -4996,6 +4998,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 b00468be5bd220..97696d22caa38f 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, @@ -2048,6 +2038,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); @@ -2612,6 +2603,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/PlayerUpdates.cpp b/src/server/game/Entities/Player/PlayerUpdates.cpp index 5dcf409faf3af3..9733bf4cc6f2d1 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 08a11773f231c6..1854dec2480822 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -5075,6 +5075,14 @@ void Unit::RemoveAurasDueToItemSpell(uint32 spellId, ObjectGuid castItemGuid) else ++iter; } + + for (AuraMap::iterator iter = m_ownedAuras.begin(); iter != m_ownedAuras.end();) + { + if (iter->second->GetCastItemGUID() == castItemGuid) + RemoveOwnedAura(iter, AURA_REMOVE_BY_DEFAULT); + else + ++iter; + } } void Unit::RemoveAurasByType(AuraType auraType, ObjectGuid casterGUID, Aura* except, bool negative, bool positive) @@ -10181,15 +10189,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; } @@ -17921,6 +17946,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()) { @@ -18874,11 +18901,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); } @@ -21305,3 +21344,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 165e8917efcc8d..78b406e0f5e027 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -946,6 +946,7 @@ class Unit : public WorldObject 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; @@ -1152,6 +1153,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); @@ -1783,6 +1785,10 @@ 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; + + bool m_cannotReachTarget; [[nodiscard]] uint32 GetOldFactionId() const { return _oldFactionId; } 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 3c46a356416a2b..2efeecbb4050e2 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -856,6 +856,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 16473a2ac81f9e..d9f0cd96827702 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 ba650b87103cac..0fa470eb3fbf8f 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -58,19 +58,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() { @@ -568,7 +558,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); @@ -792,6 +787,7 @@ void WorldSession::HandlePlayerLoginOpcode(WorldPacket& recvData) void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder) { + m_playerLoading = true; ObjectGuid playerGuid = holder.GetGuid(); Player* pCurrChar = new Player(this); @@ -908,8 +904,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 a8bde64e8d3287..f214201f218b7c 100644 --- a/src/server/game/Movement/MotionMaster.cpp +++ b/src/server/game/Movement/MotionMaster.cpp @@ -887,6 +887,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 7eda4b22ff73cc..2c99d21ddfe40a 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/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..883b9eaf9b2891 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)); diff --git a/src/server/game/Scripting/ScriptDefines/PlayerScript.h b/src/server/game/Scripting/ScriptDefines/PlayerScript.h index 21df41a2d053a2..166f6b94a6d5db 100644 --- a/src/server/game/Scripting/ScriptDefines/PlayerScript.h +++ b/src/server/game/Scripting/ScriptDefines/PlayerScript.h @@ -259,7 +259,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*/) { } 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 3ff8933e346490..0862826328df33 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -59,6 +59,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"); @@ -138,11 +149,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(); @@ -225,7 +238,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..888262995b7672 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); @@ -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 5e0c2667fb61a5..d2195f738c39e0 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()) @@ -717,6 +741,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 @@ -736,8 +764,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); } @@ -1698,3 +1726,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 e28ee5626e10dd..d6b6cb5110ff65 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -7525,9 +7525,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/World/IWorld.h b/src/server/game/World/IWorld.h index 1263de1a5fa0f8..fd5a3252c763f9 100644 --- a/src/server/game/World/IWorld.h +++ b/src/server/game/World/IWorld.h @@ -22,6 +22,7 @@ #include "Common.h" #include "Duration.h" #include "ObjectGuid.h" +#include "QueryHolder.h" #include "SharedDefines.h" #include @@ -599,6 +600,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; @@ -606,6 +610,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 dd71be5283bf08..fcebe617ac9623 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" @@ -2332,6 +2333,8 @@ void World::Update(uint32 diff) ResetGuildCap(); } + sScriptMgr->OnPlayerbotUpdate(diff); + // pussywizard: handle auctions when the timer has passed if (_timers[WUPDATE_AUCTIONS].Passed()) { @@ -2470,6 +2473,7 @@ void World::Update(uint32 diff) CharacterDatabase.KeepAlive(); LoginDatabase.KeepAlive(); WorldDatabase.KeepAlive(); + sScriptMgr->OnDatabasesKeepAlive(); } { @@ -2632,6 +2636,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` @@ -2677,6 +2684,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; @@ -2697,6 +2705,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"); @@ -3174,6 +3185,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 2eb435f5256a6c..ff33de5a471a76 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -331,6 +331,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; @@ -362,6 +365,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; @@ -427,9 +433,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 94ccfb6eb5e625..759ab2bbc00c8f 100644 --- a/src/server/scripts/Commands/cs_misc.cpp +++ b/src/server/scripts/Commands/cs_misc.cpp @@ -1922,7 +1922,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/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 0308ab316883c6..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..787c979091c6ad --- /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 fd4bb92e72eaa5..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..abbb888f4ed296 --- /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 2fab24707e86fc..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..511cd0aa52b674 --- /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 b3cac8840a8a76..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..4c934b87f03d23 --- /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/shared/DataStores/DBCDatabaseLoader.cpp b/src/server/shared/DataStores/DBCDatabaseLoader.cpp index 4208b3c40bb694..f7c43baec66f17 100644 --- a/src/server/shared/DataStores/DBCDatabaseLoader.cpp +++ b/src/server/shared/DataStores/DBCDatabaseLoader.cpp @@ -29,8 +29,7 @@ DBCDatabaseLoader::DBCDatabaseLoader(char const* tableName, char const* dbcForma _stringPool(stringPool) { // Get sql index position - int32 indexPos = -1; - _recordSize = DBCFileLoader::GetFormatRecordSize(_dbcFormat, &indexPos); + _recordSize = DBCFileLoader::GetFormatRecordSize(_dbcFormat, &_sqlIndexPos); ASSERT(_recordSize); } @@ -72,11 +71,11 @@ char* DBCDatabaseLoader::Load(uint32& records, char**& indexTable) { Field* fields = result->Fetch(); uint32 indexValue = fields[_sqlIndexPos].Get(); - char* dataValue = indexTable[indexValue]; + char* oldDataValue = indexTable[indexValue]; // If exist in DBC file override from DB newIndexes[newRecords] = indexValue; - dataValue = &dataTable[newRecords++ * _recordSize]; + char* dataValue = &dataTable[newRecords++ * _recordSize]; uint32 dataOffset = 0; uint32 sqlColumnNumber = 0; @@ -100,7 +99,15 @@ char* DBCDatabaseLoader::Load(uint32& records, char**& indexTable) dataOffset += sizeof(uint8); break; case FT_STRING: - *reinterpret_cast(&dataValue[dataOffset]) = CloneStringToPool(fields[sqlColumnNumber].Get()); + // not override string if new string is empty + if (fields[sqlColumnNumber].Get().empty() && oldDataValue) + { + *reinterpret_cast(&dataValue[dataOffset]) = *reinterpret_cast(&oldDataValue[dataOffset]); + } + else + { + *reinterpret_cast(&dataValue[dataOffset]) = CloneStringToPool(fields[sqlColumnNumber].Get()); + } dataOffset += sizeof(char*); break; case FT_SORT: diff --git a/src/server/shared/DataStores/DBCStructure.h b/src/server/shared/DataStores/DBCStructure.h index 0271352767719e..44b22be40c9cb6 100644 --- a/src/server/shared/DataStores/DBCStructure.h +++ b/src/server/shared/DataStores/DBCStructure.h @@ -627,6 +627,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() @@ -902,6 +929,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<