Merge remote-tracking branch 'upstream/master' into test-staging

# Conflicts:
#	.github/workflows/windows_build.yml
#	src/server/game/Handlers/CharacterHandler.cpp
This commit is contained in:
Keleborn
2026-07-17 15:15:37 -07:00
156 changed files with 2834 additions and 948 deletions
+17
View File
@@ -0,0 +1,17 @@
# .agents
Source of truth for this repo's agent skills and rules:
- `skills/<name>/SKILL.md`
- `rules/<name>.md`
Agent-specific directories expose each via a relative symlink — for Claude Code:
.claude/skills/<name> -> ../../.agents/skills/<name>
.claude/rules/<name>.md -> ../../.agents/rules/<name>.md
To add a skill or rule: create it here, then symlink it.
The skills in this repository often build on those from
[agent-toolkit](https://github.com/eai-org/agent-toolkit/), invoking them when available.
Installing the toolkit at user level is therefore recommended.
@@ -0,0 +1,69 @@
---
name: generate-pr-description
description: Generate the PR title and description from the current branch changes, following the repo's PR template.
metadata:
version: "1.0"
---
# Generate PR description
Produce a copy-pasteable PR title and body for the current changes, based on the repo's
`pull_request_template.md`. Output text only — never create the PR, push, or run any
state-changing git command.
If a `use-conversational-language` skill is available, invoke it and follow it when writing the
prose. If not available, fetch it from its [remote source](https://github.com/eai-org/agent-toolkit/blob/main/skills/use-conversational-language/SKILL.md).
## Gather the changes
"The changes" is the union of:
- committed branch work: `git diff <master-ref>...HEAD`, where `<master-ref>` is `origin/master` —
or `upstream/master` when the checkout is a fork with an `upstream` remote;
- staged and unstaged changes;
- the branch's commit messages (context only).
If all three are empty, say so and stop.
## Facts, never assumptions
Only two sources may fill the description: the diff and facts stated in the conversation. Never
present anything else as fact — no assumed testing, sources, issue links, or TODOs. A section
without backing facts stays empty/unchecked, silently: an empty section is correct, a guessed one
is wrong.
Ask the user only when unsure about substance — what the change does, which scope the title should
carry, contradictory information. Missing fill-in facts are never a reason to ask.
## Title
`type(Scope/Subscope): short description` — imperative, max 50 chars. Types: `feat`, `fix`,
`refactor`, `style`, `docs`, `test`, `chore`; scopes: `Core`, `Scripts`, `DB`. Copy subscope naming
from recent commits touching the same areas (`git log --oneline -- <paths>`).
## Body
Fill in `pull_request_template.md` (repo root):
- Strip the instructional `<!-- -->` comments; keep the "How to Test AzerothCore PRs" footer
intact.
- Describe what the changes do and why.
- "Changes Proposed" checkboxes, from diff paths: `src/server/scripts/` → Scripts; `data/sql/` →
Database; other `src/` → Core.
- AI disclosure checkbox: always tick it; name the agent/model running this skill and state
whether AI produced the changes themselves or only this description, per the conversation.
- Issues Addressed, SOURCE, Tests Performed, How to Test, Known Issues/TODO: stated facts only
(see above).
## Output
Print exactly:
~~~
TITLE: <title>
PR DESCRIPTION:
```markdown
<body>
```
~~~
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/generate-pr-description
+3 -3
View File
@@ -1,5 +1,5 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
# CodeRabbit review config. Mirrors the hard rules in CLAUDE.md so the AI
# CodeRabbit review config. Mirrors the hard rules in AGENTS.md so the AI
# reviewer enforces project conventions on every PR.
language: en-US
@@ -74,10 +74,10 @@ reviews:
knowledge_base:
code_guidelines:
enabled: true
# CLAUDE.md is in CodeRabbit's defaults, but list it explicitly so the project's
# AGENTS.md is in CodeRabbit's defaults, but list it explicitly so the project's
# full guideline doc is always pulled into review context.
filePatterns:
- "CLAUDE.md"
- "AGENTS.md"
chat:
auto_reply: true
+4 -4
View File
@@ -23,10 +23,10 @@ We support the following versions of dependencies.
### Supported Operating Systems
| Linux (Ubuntu) | Status | Recommended |
| :------------- | :----------------: | :------------------: |
| 24.04 | :white_check_mark: | :large_blue_diamond: |
| 22.04 | :white_check_mark: | |
| 20.04 ≤ | :red_circle: | |
|:---------------| :----------------: | :------------------: |
| 26.04 | :white_check_mark: | :large_blue_diamond: |
| 24.04 | :white_check_mark: | |
| 22.04 ≤ | :red_circle: | |
| macOS | Status | Recommended |
| :---- | :----------------: | :------------------: |
+7 -16
View File
@@ -64,23 +64,14 @@ runs:
run: |
sudo apt update
sudo apt remove needrestart #refer: https://github.com/actions/runner-images/issues/9937
sudo apt-get -y install ccache clang cmake curl google-perftools \
libmysqlclient-dev make unzip build-essential cmake-data \
libboost-all-dev libbz2-dev libncurses5-dev libmysql++-dev \
sudo apt-get -y install ccache clang cmake curl \
make unzip build-essential cmake-data \
libboost-all-dev libbz2-dev libmysql++-dev \
libreadline6-dev libssl-dev libtool openssl zlib1g-dev
# Account for https://github.com/actions/runner-images/issues/8659
# based off of https://github.com/actions/runner-images/issues/8659#issuecomment-1852353116
UBUNTU_VERSION="$(grep VERSION_ID /etc/os-release | cut -f2 -d\")"
source /etc/os-release
if [[ "$VERSION_CODENAME" == "jammy" ]]; then
if [[ "${{ inputs.CC }}" =~ "clang-" ]]; then
CLANG_VERSION="$(echo '${{ inputs.CC }}' | cut -f2 -d\-)"
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo add-apt-repository "deb http://apt.llvm.org/$VERSION_CODENAME/ llvm-toolchain-$VERSION_CODENAME-$CLANG_VERSION main"
sudo apt-get -qq update
sudo apt-get -qq install '${{ inputs.CC }}'
fi
if [[ "$(lsb_release -sr)" == "26.04" ]]; then
sudo apt-get -y install libgoogle-perftools-dev default-libmysqlclient-dev
else
sudo apt-get -y install google-perftools libmysqlclient-dev libncurses5-dev
fi
- name: setup ccache
+5 -5
View File
@@ -19,14 +19,14 @@ The PR Reviewer agent is configured to review pull requests with deep understand
- Testing and validation requirements
- Security and quality standards
**Key Feature**: This agent always references `/CLAUDE.md`, the C++ Code Standards wiki, and the SQL Standards wiki before reviewing any PR.
**Key Feature**: This agent always references `/AGENTS.md`, the C++ Code Standards wiki, and the SQL Standards wiki before reviewing any PR.
## How It Works
When an AI tool reviews a PR in this repository:
1. The tool reads the appropriate agent configuration from this directory
2. The agent configuration instructs it to read `/CLAUDE.md` for full project context
2. The agent configuration instructs it to read `/AGENTS.md` for full project context
3. For C++ changes, the agent also references the [C++ Code Standards wiki](https://github.com/azerothcore/wiki/blob/master/docs/cpp-code-standards.md)
4. For SQL changes, the agent also references the [SQL Standards wiki](https://github.com/azerothcore/wiki/blob/master/docs/sql-standards.md)
5. The agent applies project-specific rules during review
@@ -36,7 +36,7 @@ When an AI tool reviews a PR in this repository:
If you're using AI tools to assist with contributions:
1. Familiarize yourself with `/CLAUDE.md` - it contains essential project knowledge
1. Familiarize yourself with `/AGENTS.md` - it contains essential project knowledge
2. Review the [C++ Code Standards](https://github.com/azerothcore/wiki/blob/master/docs/cpp-code-standards.md) for C++ contributions
3. Review the [SQL Standards](https://github.com/azerothcore/wiki/blob/master/docs/sql-standards.md) for database contributions
4. The AI agents will help ensure your changes follow project standards
@@ -47,13 +47,13 @@ If you're using AI tools to assist with contributions:
To update agent configurations:
1. Edit the relevant `.md` file in this directory
2. Ensure any changes align with documentation in `/CLAUDE.md` and the wiki standards
2. Ensure any changes align with documentation in `/AGENTS.md` and the wiki standards
3. Test the updated configuration with a sample PR review
4. Commit changes following the project's commit message format
## References
- Project guidelines: `/CLAUDE.md`
- Project guidelines: `/AGENTS.md`
- C++ standards: [C++ Code Standards](https://github.com/azerothcore/wiki/blob/master/docs/cpp-code-standards.md)
- SQL standards: [SQL Standards](https://github.com/azerothcore/wiki/blob/master/docs/sql-standards.md)
- PR template: `/pull_request_template.md`
+4 -4
View File
@@ -5,13 +5,13 @@ You are an expert code reviewer for the AzerothCore project. When reviewing pull
## Required Reading
Before reviewing any PR, you MUST read and follow the instructions in:
- `/CLAUDE.md` - Contains comprehensive project architecture, coding standards, build instructions, and PR requirements
- `/AGENTS.md` - Contains comprehensive project architecture, coding standards, build instructions, and PR requirements
- [C++ Code Standards](https://github.com/azerothcore/wiki/blob/master/docs/cpp-code-standards.md) - Detailed C++ coding conventions and style guide
- [SQL Standards](https://github.com/azerothcore/wiki/blob/master/docs/sql-standards.md) - SQL query formatting and database standards
## Key Review Focus Areas
Based on CLAUDE.md, always verify:
Based on AGENTS.md, always verify:
### 1. Commit Message Format
- Uses Conventional Commits: `Type(Scope/Subscope): Short description`
@@ -95,7 +95,7 @@ Based on CLAUDE.md, always verify:
## Review Process
1. Read CLAUDE.md to understand project context
1. Read AGENTS.md to understand project context
2. Review C++ Code Standards wiki for C++ changes
3. Review SQL Standards wiki for database changes
4. Check commit message format
@@ -108,7 +108,7 @@ Based on CLAUDE.md, always verify:
## Feedback Style
- Be constructive and educational
- Reference specific sections of CLAUDE.md, C++ Code Standards, or SQL Standards when applicable
- Reference specific sections of AGENTS.md, C++ Code Standards, or SQL Standards when applicable
- Suggest specific fixes with code examples if necessary
- Highlight both issues and good practices
- For C++ issues, cite specific rule from C++ Code Standards wiki
+4 -4
View File
@@ -25,10 +25,6 @@ jobs:
fail-fast: true
matrix:
include:
- os: ubuntu-22.04
compiler:
CC: clang-15
CXX: clang++-15
- os: ubuntu-24.04
compiler:
CC: clang-18
@@ -37,6 +33,10 @@ jobs:
compiler:
CC: gcc-14
CXX: g++-14
- os: ubuntu-26.04
compiler:
CC: clang-20
CXX: clang++-20
runs-on: ${{ matrix.os }}
name: ${{ matrix.os }}-${{ matrix.compiler.CC }}-nopch
if: github.repository == 'mod-playerbots/azerothcore-wotlk' && !github.event.pull_request.draft
+4 -4
View File
@@ -25,14 +25,14 @@ jobs:
fail-fast: true
matrix:
include:
- os: ubuntu-22.04
compiler:
CC: clang-15
CXX: clang++-15
- os: ubuntu-24.04
compiler:
CC: clang-18
CXX: clang++-18
- os: ubuntu-26.04
compiler:
CC: clang-20
CXX: clang++-20
runs-on: ${{ matrix.os }}
name: ${{ matrix.os }}-${{ matrix.compiler.CC }}-pch
if: github.repository == 'mod-playerbots/azerothcore-wotlk' && !github.event.pull_request.draft
+2 -2
View File
@@ -42,8 +42,8 @@ jobs:
fail-fast: true
matrix:
include:
- os: ubuntu-22.04
- os: ubuntu-24.04
- os: ubuntu-26.04
runs-on: ${{ matrix.os }}
if: github.repository == 'azerothcore/azerothcore-wotlk' && !github.event.pull_request.draft
steps:
@@ -74,8 +74,8 @@ jobs:
fail-fast: true
matrix:
include:
- os: ubuntu-22.04
- os: ubuntu-24.04
- os: ubuntu-26.04
runs-on: ${{ matrix.os }}
if: github.repository == 'azerothcore/azerothcore-wotlk' && !github.event.pull_request.draft
steps:
+64 -6
View File
@@ -39,15 +39,73 @@ jobs:
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.13
# Cache the fetched Boost + MySQL client so we don't re-download ~350 MB
# and re-run the Boost installer on every run. The Test-Path guards in
# "Configure OS" short-circuit the install work when these are restored.
- name: Cache Boost & MySQL
uses: actions/cache@v4
with:
path: |
C:\local\boost_1_87_0
C:\tools\mysql\current
key: winbuild-deps:boost-1.87.0:mysql-8.4.9
- name: Configure OS
shell: bash
env:
CONTINUOUS_INTEGRATION: true
shell: pwsh
run: |
cd C:/ac
./acore.sh install-deps
$ErrorActionPreference = 'Stop'
# The windows-latest runner image already ships CMake, Visual Studio
# (with the native desktop workload + MSVC) and OpenSSL, so we only need to
# provide Boost and the MySQL client libraries. We fetch these directly
# instead of via chocolatey, whose pinned packages keep breaking on CI
# (VS detection failures and slproweb removing old OpenSSL installers).
# --- Boost 1.87.0 (msvc-14.3, 64-bit) prebuilt binaries -> C:\local\boost_1_87_0 (BOOST_ROOT) ---
# Use the direct-download host; the plain /download URL serves an HTML interstitial.
if (-not (Test-Path 'C:\local\boost_1_87_0\boost'))
{
$boostExe = Join-Path $env:RUNNER_TEMP 'boost.exe'
Invoke-WebRequest -Uri 'https://master.dl.sourceforge.net/project/boost/boost-binaries/1.87.0/boost_1_87_0-msvc-14.3-64.exe?viasf=1' -OutFile $boostExe
$boostMB = (Get-Item $boostExe).Length / 1MB
Write-Host ("Downloaded Boost installer: {0:N1} MB" -f $boostMB)
if ($boostMB -lt 50) { throw "Boost installer download looks wrong ($([int]$boostMB) MB) - likely an HTML page." }
$boostProc = Start-Process -FilePath $boostExe -ArgumentList '/VERYSILENT','/SUPPRESSMSGBOXES','/NORESTART','/DIR=C:\local\boost_1_87_0' -Wait -PassThru
if ($boostProc.ExitCode -ne 0) { throw "Boost installer failed with exit code $($boostProc.ExitCode)." }
# Fail fast if the installer "succeeded" but didn't lay down the expected tree,
# instead of surfacing a cryptic error later during CMake configuration.
if (-not (Test-Path 'C:\local\boost_1_87_0\boost\version.hpp')) { throw 'Boost install missing headers at C:\local\boost_1_87_0\boost.' }
if (-not (Test-Path 'C:\local\boost_1_87_0\lib64-msvc-14.3')) { throw 'Boost install missing prebuilt libs at C:\local\boost_1_87_0\lib64-msvc-14.3.' }
}
# --- MySQL 8.4 client headers + libs -> C:\tools\mysql\current (path the build expects) ---
if (-not (Test-Path 'C:\tools\mysql\current\lib\mysqlclient.lib'))
{
# Use the archives CDN directly (keeps every version, unlike dev.mysql.com/get which only
# serves the current GA). A browser user-agent is required or Oracle returns an HTML error page.
$mysqlZip = Join-Path $env:RUNNER_TEMP 'mysql.zip'
Invoke-WebRequest -Uri 'https://cdn.mysql.com/archives/mysql-8.4/mysql-8.4.9-winx64.zip' -OutFile $mysqlZip -UserAgent 'Mozilla/5.0'
$mysqlMB = (Get-Item $mysqlZip).Length / 1MB
Write-Host ("Downloaded MySQL zip: {0:N1} MB" -f $mysqlMB)
if ($mysqlMB -lt 50) { throw "MySQL zip download looks wrong ($([int]$mysqlMB) MB)." }
New-Item -ItemType Directory -Force -Path 'C:\tools\mysql' | Out-Null
Expand-Archive -Path $mysqlZip -DestinationPath 'C:\tools\mysql' -Force
if (Test-Path 'C:\tools\mysql\current') { Remove-Item -Recurse -Force 'C:\tools\mysql\current' }
Rename-Item 'C:\tools\mysql\mysql-8.4.9-winx64' 'C:\tools\mysql\current'
# Fail fast if the archive layout changed and the client import library isn't
# where the build expects it, rather than failing later during CMake/link.
if (-not (Test-Path 'C:\tools\mysql\current\lib\mysqlclient.lib')) { throw 'MySQL client import library missing at C:\tools\mysql\current\lib\mysqlclient.lib.' }
}
# --- OpenSSL: use the copy already installed on the runner image ---
$opensslRoot = @($env:OPENSSL_ROOT_DIR, 'C:\Program Files\OpenSSL', 'C:\Program Files\OpenSSL-Win64') |
Where-Object { $_ -and (Test-Path (Join-Path $_ 'include\openssl\opensslv.h')) } |
Select-Object -First 1
if (-not $opensslRoot)
{
throw 'No OpenSSL development install found on the runner image.'
}
"OPENSSL_ROOT_DIR=$opensslRoot" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
Write-Host "Using OpenSSL at $opensslRoot"
- name: Build
shell: bash
run: |
+100
View File
@@ -0,0 +1,100 @@
# AGENTS.md
AzerothCore is a C++ MMORPG server emulator for World of Warcraft 3.3.5a (WotLK), built with CMake, backed by MySQL.
## Agent rules
- **Do not configure or build unless explicitly asked.** Builds are slow and rarely needed for code changes.
- **Never edit SQL files outside `data/sql/updates/pending_db_*/` unless explicitly requested. ** `data/sql/base/`, `data/sql/archive/`, and `data/sql/updates/db_*/` are immutable.
## Build
Out-of-source build is required (in-source is blocked).
```bash
mkdir -p build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/azeroth-server -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DSCRIPTS=static -DMODULES=static
make -j$(nproc) && make install
```
C++20 required (`CMAKE_CXX_STANDARD 20`). Useful flags: `BUILD_TESTING=ON` (Google Test), `NOPCH=1` (disable precompiled headers). Full set in `conf/dist/config.cmake`. `compile_commands.json` is exported automatically.
Tests (Google Test, in `src/test/`): configure `-DBUILD_TESTING=ON`, then `ctest` or `./src/test/unit_tests` from the build dir.
## Repository layout
- `src/common/` — networking (Asio), crypto, config, logging, shared utilities.
- `src/server/game/` — core gameplay; compiled into worldserver.
- `src/server/scripts/` — content scripts grouped by region (`EasternKingdoms/`, `Northrend/`, …), class (`Spells/spell_mage.cpp`, …), and domain (`Commands/`, `Pet/`, `OutdoorPvP/`, `World/`).
- `src/server/database/` — DB abstraction and schema updater.
- `src/server/shared/` — code shared by auth and world servers.
- `src/server/apps/{authserver,worldserver}/` — entry points (ports 3724 and 8085).
- `src/test/` — Google Test unit tests + mocks.
- `data/sql/` — `base/` (historical schema), `updates/db_*/` (merged), `updates/pending_db_*/` (in-flight, **edit here**), `custom/` (gitignored).
- `modules/` — external modules (each a subdir with its own `CMakeLists.txt`). Disable with `-DDISABLED_AC_MODULES="mod1;mod2"`. See `modules/how_to_make_a_module.md`.
- `apps/` — helper scripts; `apps/codestyle/` holds the lint scripts.
- `conf/dist/` — distributed config templates; `conf/*.conf` is gitignored.
- `deps/` — vendored third-party dependencies.
## Adding SQL updates
1. `cd data/sql/updates/pending_db_world/` (or `pending_db_auth` / `pending_db_characters`).
2. `./create_sql.sh` generates an empty `rev_<timestamp>.sql` to write into.
3. Conventions enforced by `apps/codestyle/codestyle-sql.py`: every `INSERT` preceded by a matching `DELETE` (idempotency); no double semicolons; no multiple blank lines; InnoDB engine.
The three databases:
- `acore_auth` — accounts, realm list, IP/account bans, session keys. Shared across all realms.
- `acore_characters` — per-character state: characters, inventory, in-progress quests, mail, guilds, arena teams, achievements. One per realm.
- `acore_world` — static game content: creature/gameobject/item/quest templates, spawn lists, loot tables, SmartAI scripts, gossip, conditions. Read-mostly; rebuilt from SQL.
## Code style
Formatting (charset, indent width, line length, final newline, trailing whitespace) follows `.editorconfig`.
Run the linters before claiming a change is done:
```bash
python apps/codestyle/codestyle-cpp.py # C++
python apps/codestyle/codestyle-sql.py # SQL (compares to origin/master)
```
Hard rules (also enforced by CI with `-Werror`, plus `cppcheck`):
- 4-space indent for C++ (tabs forbidden); 2-space for JSON/YAML/sh/ts/js. UTF-8, LF, max 120 cols, trailing newline.
- Allman braces. No braces around single-line statements. `if (x)` — never `if(x)` or `if ( x )`.
- `auto const&` (not `const auto&`); `Type const*` (not `const Type*`).
- Use `{}` format specifiers (`fmt`-style), not `%u`/`%s`.
- Use the typed helpers, not raw flag access:
- `IsPlayer()`, `IsCreature()`, `IsItem()`, … instead of `GetTypeId() == TYPEID_*`.
- `GetNpcFlags()`, `HasNpcFlag()`, `SetNpcFlag()`, `RemoveNpcFlag()`, `ReplaceAllNpcFlags()` instead of `*Flag(UNIT_NPC_FLAGS, …)`.
- `IsRefundable()`, `IsBOPTradable()`, `IsWrapped()` instead of `HasFlag(ITEM_FIELD_FLAGS, …)`.
- `HasFlag(ItemFlag)` / `HasFlag2(ItemFlag2)` / `HasFlagCu(ItemFlagsCustom)` instead of bitwise `Flags & ITEM_FLAG…`.
- `ObjectGuid::ToString().c_str()` instead of `ObjectGuid::GetCounter()`.
## Project conventions
- **Logging**: `LOG_INFO("category.sub", "msg with {}", arg)` (also `LOG_WARN`/`ERROR`/`DEBUG`/`TRACE`). Categories are hierarchical, dot-separated (`server.loading`, `entities.player`, `sql.dev`). No `printf`-style, no `sLog->`, no `TC_LOG_*`. Macro in `src/common/Logging/Log.h`.
- **Random**: use helpers in `src/common/Utilities/Random.h` — `urand`, `irand`, `frand`, `rand32`, `rand_chance`, `roll_chance_f`, `roll_chance_i`. Not `std::rand` or `<random>`.
- **Strings**: `Acore::StringFormat(fmt, args...)` (`{}` placeholders) — `src/common/Utilities/StringFormat.h`.
- **Config**: `sConfigMgr->GetOption<T>("Name", default)`.
- **Namespace**: project-wide `Acore::` (no `Trinity::` remnants — rename when porting from upstream forks).
- **Long-lived references**: don't store a raw `Player*` / `Creature*` / `Unit*` past the current call/tick — the object can be removed (logout, despawn, instance unload) and the pointer dangles. Store the `ObjectGuid` and resolve at use time via `ObjectAccessor::FindPlayer(guid)`, `Map::GetCreature(guid)`, etc.
- **DB queries**: use `PreparedStatement` (via `WorldDatabase` / `CharacterDatabase` / `LoginDatabase` and the prepared-statement enums), not raw query strings. Non-blocking reads go async: `_queryProcessor.AddCallback(db.AsyncQuery(stmt).WithPreparedCallback(...))` (or `WithCallback`). Multi-statement writes wrap in `SQLTransaction` + `Execute` / `AppendPreparedStatement`.
- **Timed actions in AI**: use `EventMap` (event id → delay; simple) or `TaskScheduler` (lambdas, repeats, cancellation), both members of `CreatureAI` — don't roll your own tick counters. See any boss script under `src/server/scripts/`.
## Scripting registration
Scripts inherit from a `ScriptObject` subclass (`SpellScript`, `AuraScript`, `CreatureScript`, `InstanceMapScript`, `GameObjectScript`, `CommandScript`, …). Two registration styles coexist:
- **Spell / aura scripts**: `RegisterSpellScript(ClassName)` (or `RegisterSpellAndAuraScriptPair(...)`) inside `AddSC_<name>()`.
- **Creature scripts**: prefer `RegisterCreatureAI(ClassName)` for new code; legacy zones still use `new ClassName();`. Match the surrounding pattern.
Then declare and call `AddSC_<name>()` from the regional loader (`Spells/spells_script_loader.cpp`, `EasternKingdoms/eastern_kingdoms_script_loader.cpp`, …).
**SmartAI** (data-driven creature behaviour) lives in the world DB's `smart_scripts` table, not C++ (engine: `src/server/game/AI/SmartScripts/`). For new creature behaviour prefer SmartAI (via the SQL update workflow); reach for `CreatureScript` only when SmartAI's event/action vocabulary isn't enough.
**Module hooks** (e.g. `OnPlayerLogin`, `OnWorldUpdate`, `OnSpellCast`) are declared in `src/server/game/Scripting/ScriptDefines/*.h`. Implement by inheriting the matching base (`PlayerScript`, `WorldScript`, …) and registering with `new MyClass();` (or its `RegisterXxxScript` macro) inside `AddSC_<name>()`. Full list: https://www.azerothcore.org/wiki/hooks-script.
Custom (non-upstream) scripts go in `src/server/scripts/Custom/` (gitignored).
+1 -104
View File
@@ -1,104 +1 @@
# CLAUDE.md
AzerothCore is a C++ MMORPG server emulator for World of Warcraft 3.3.5a (WotLK), built with CMake, backed by MySQL.
## Agent rules
- **Do not configure or build unless explicitly asked.** Builds are slow (CMake + compile of a large C++ codebase) and rarely needed to make code changes.
- **Never edit SQL files outside `data/sql/updates/pending_db_*/`.** `data/sql/base/`, `data/sql/archive/`, and `data/sql/updates/db_*/` are immutable (do not modify).
- **Do not run git commands that modify repo state** (commit, branch, merge, rebase, reset, push, …) unless explicitly requested, and do not include them in plans. Read-only git (status, diff, log) is fine.
## Build
Out-of-source build is required (in-source is blocked by CMake).
```bash
mkdir -p build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/azeroth-server -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DSCRIPTS=static -DMODULES=static
make -j$(nproc) && make install
```
Compiler: **C++20** required (`CMAKE_CXX_STANDARD 20`). Useful CMake flags: `BUILD_TESTING=ON` (Google Test), `NOPCH=1` (disable precompiled headers). Full flag set in `conf/dist/config.cmake`. `compile_commands.json` is exported automatically.
Tests (Google Test, in `src/test/`): configure with `-DBUILD_TESTING=ON`, then `ctest` or `./src/test/unit_tests` from the build dir.
## Repository layout
- `src/common/` — networking (Asio), crypto, config, logging, shared utilities.
- `src/server/game/` — core gameplay; compiled into worldserver.
- `src/server/scripts/` — content scripts grouped by region (`EasternKingdoms/`, `Northrend/`, …), class (`Spells/spell_mage.cpp`, …), and domain (`Commands/`, `Pet/`, `OutdoorPvP/`, `World/`).
- `src/server/database/` — DB abstraction and schema updater.
- `src/server/shared/` — code shared by auth and world servers.
- `src/server/apps/{authserver,worldserver}/` — entry points (ports 3724 and 8085).
- `src/test/` — Google Test unit tests + mocks.
- `data/sql/` — `base/` (historical schema), `updates/db_*/` (merged), `updates/pending_db_*/` (in-flight, **edit here**), `custom/` (gitignored).
- `modules/` — external modules (each a subdir with its own `CMakeLists.txt`). Disable with `-DDISABLED_AC_MODULES="mod1;mod2"`. See `modules/how_to_make_a_module.md`.
- `apps/` — helper scripts; `apps/codestyle/` holds the lint scripts (see below).
- `conf/dist/` — distributed config templates; `conf/*.conf` is gitignored.
- `deps/` — vendored third-party dependencies.
## Adding SQL updates
1. `cd data/sql/updates/pending_db_world/` (or `pending_db_auth` / `pending_db_characters`).
2. `./create_sql.sh` generates an empty `rev_<timestamp>.sql` you write into.
3. Required SQL conventions (enforced by `apps/codestyle/codestyle-sql.py`):
- Every `INSERT` must be preceded by a matching `DELETE` (idempotency).
- 4-space indent (no tabs), trailing newline, no double semicolons, no multiple blank lines.
- Tables must use the InnoDB engine.
The three databases:
- `acore_auth` — accounts, realm list, IP/account bans, session keys. Shared across all realms.
- `acore_characters` — per-character state: characters, inventory, in-progress quests, mail, guilds, arena teams, achievements. One per realm.
- `acore_world` — static game content: creature/gameobject/item/quest templates, spawn lists, loot tables, SmartAI scripts, gossip, conditions. Read-mostly; rebuilt from SQL.
## Code style
Run the linters before claiming a change is done:
```bash
python apps/codestyle/codestyle-cpp.py # C++
python apps/codestyle/codestyle-sql.py # SQL (compares to origin/master)
```
Hard rules (also enforced by CI with `-Werror`):
- 4-space indent for C++ (tabs forbidden); 2-space for JSON/YAML/sh/ts/js. UTF-8, LF, max 120 cols, trailing newline.
- Allman braces. No braces around single-line statements. `if (x)` — never `if(x)` or `if ( x )`.
- `auto const&` (not `const auto&`); `Type const*` (not `const Type*`).
- Use `{}` format specifiers (`fmt`-style), not `%u`/`%s`.
- Use the typed helpers, not raw flag access:
- `IsPlayer()`, `IsCreature()`, `IsItem()`, … instead of `GetTypeId() == TYPEID_*`.
- `GetNpcFlags()`, `HasNpcFlag()`, `SetNpcFlag()`, `RemoveNpcFlag()`, `ReplaceAllNpcFlags()` instead of `*Flag(UNIT_NPC_FLAGS, …)`.
- `IsRefundable()`, `IsBOPTradable()`, `IsWrapped()` instead of `HasFlag(ITEM_FIELD_FLAGS, …)`.
- `HasFlag(ItemFlag)` / `HasFlag2(ItemFlag2)` / `HasFlagCu(ItemFlagsCustom)` instead of bitwise `Flags & ITEM_FLAG…`.
- `ObjectGuid::ToString().c_str()` instead of `ObjectGuid::GetCounter()`.
CI also runs `cppcheck`.
## Project conventions
- **Logging**: `LOG_INFO("category.sub", "msg with {}", arg)` (also `LOG_WARN`, `LOG_ERROR`, `LOG_DEBUG`, `LOG_TRACE`). Categories are hierarchical, dot-separated (e.g. `server.loading`, `entities.player`, `sql.dev`). No `printf`-style; no `sLog->`; no `TC_LOG_*`. Macro in `src/common/Logging/Log.h`.
- **Random**: use project helpers from `src/common/Utilities/Random.h` — `urand`, `irand`, `frand`, `rand32`, `rand_chance`, `roll_chance_f`, `roll_chance_i`. Do not use `std::rand` or `<random>` directly.
- **Strings**: `Acore::StringFormat(fmt, args...)` (wraps `fmt::format`, `{}` placeholders) — `src/common/Utilities/StringFormat.h`.
- **Config**: read options with `sConfigMgr->GetOption<T>("Name", default)`.
- **Namespace**: project-wide is `Acore::` (no `Trinity::` remnants — agents porting from upstream forks must rename).
- **Long-lived references**: do not store a raw `Player*` / `Creature*` / `Unit*` past the current call/tick — the object can be removed (logout, despawn, instance unload) and the pointer dangles. Store the `ObjectGuid` and resolve at use time via `ObjectAccessor::FindPlayer(guid)`, `ObjectAccessor::GetCreature(*from, guid)`, `Map::GetCreature(guid)`, etc.
- **DB queries**: use `PreparedStatement` (via `WorldDatabase` / `CharacterDatabase` / `LoginDatabase` and the prepared-statement enums) rather than raw query strings. Reads that don't need to block the world tick go through the async path: `_queryProcessor.AddCallback(db.AsyncQuery(stmt).WithPreparedCallback(...))` (or `WithCallback` for non-prepared). Multi-statement writes wrap in `SQLTransaction` + `Execute` / `AppendPreparedStatement`.
- **Timed actions in AI**: use `EventMap` (event id → delay; simple) or `TaskScheduler` (lambdas, repeats, cancellation). Both are members of `CreatureAI`; see any boss script under `src/server/scripts/` for examples — don't roll your own tick counters.
## Scripting registration
Scripts inherit from a `ScriptObject` subclass (`SpellScript`, `AuraScript`, `CreatureScript`, `InstanceMapScript`, `GameObjectScript`, `CommandScript`, …). Two registration styles coexist:
- **Spell / aura scripts**: use the `RegisterSpellScript(ClassName)` (or `RegisterSpellAndAuraScriptPair(...)`) macro inside `AddSC_<name>()`.
- **Creature scripts**: prefer `RegisterCreatureAI(ClassName)` for new code; legacy zones still use `new ClassName();`. Match the surrounding pattern.
Then declare and call `AddSC_<name>()` from the regional loader: `Spells/spells_script_loader.cpp`, `EasternKingdoms/eastern_kingdoms_script_loader.cpp`, etc.
**SmartAI** (data-driven creature behaviour) lives in the world DB's `smart_scripts` table — not in C++. Engine: `src/server/game/AI/SmartScripts/`. For new creature behaviour prefer SmartAI (added via the SQL update workflow); reach for `CreatureScript` only when SmartAI's event/action vocabulary isn't enough.
**Module hooks** (e.g. `OnPlayerLogin`, `OnWorldUpdate`, `OnSpellCast`) are declared in `src/server/game/Scripting/ScriptDefines/*.h`. Implement by inheriting the matching base (`PlayerScript`, `WorldScript`, …) and registering with `new MyClass();` (or its `RegisterXxxScript` macro where one exists) inside `AddSC_<name>()`. Full hook list: https://www.azerothcore.org/wiki/hooks-script.
Custom (non-upstream) scripts go in `src/server/scripts/Custom/` (gitignored).
@AGENTS.md
+13 -1
View File
@@ -225,9 +225,21 @@ LABEL description="AzerothCore client-data"
ENV DATAPATH=/azerothcore/env/dist/data
RUN apt-get update && \
apt-get install -y curl unzip && \
apt-get install -y curl unzip adduser && \
rm -rf /var/lib/apt/lists/*
ARG USER_ID=1000
ARG GROUP_ID=1000
ARG DOCKER_USER=acore
# To use GID/UID 1000 in ubuntu > 23.04 the existing user must be deleted
# See https://bugs.launchpad.net/cloud-images/+bug/2005129
RUN userdel --remove ubuntu \
&& addgroup --gid "$GROUP_ID" "$DOCKER_USER" \
&& adduser --disabled-password --gecos '' --uid "$USER_ID" --gid "$GROUP_ID" "$DOCKER_USER" \
&& passwd -d "$DOCKER_USER" \
&& chown -R "$DOCKER_USER:$DOCKER_USER" /azerothcore
COPY --chown=$DOCKER_USER:$DOCKER_USER apps apps
VOLUME /azerothcore/env/dist/data
+22 -11
View File
@@ -12,15 +12,15 @@ fi
UBUNTU_VERSION=$(lsb_release -sr);
case $UBUNTU_VERSION in
"22.04")
;;
"24.04")
;;
"26.04")
;;
*)
echo "########## ########## ##########"
echo ""
echo " using unsupported Ubuntu version " $UBUNTU_VERSION
echo " please update to Ubuntu 22.04 or later"
echo " please update to Ubuntu 24.04 or later"
echo ""
echo "########## ########## ##########"
;;
@@ -30,21 +30,32 @@ $SUDO apt update
# shared deps
DEBIAN_FRONTEND="noninteractive" $SUDO \
apt-get -y install ccache clang cmake curl google-perftools libmysqlclient-dev make unzip jq screen tmux \
libreadline-dev libncurses5-dev libncursesw5-dev libbz2-dev git gcc g++ libssl-dev \
apt-get -y install ccache clang cmake curl make unzip jq screen tmux \
libreadline-dev libbz2-dev git gcc g++ libssl-dev \
libncurses-dev libboost-all-dev gdb gdbserver expect
# version-specific deps
if [[ "$UBUNTU_VERSION" == "26.04" ]]; then
DEBIAN_FRONTEND="noninteractive" $SUDO \
apt-get -y install libgoogle-perftools-dev default-libmysqlclient-dev
else
DEBIAN_FRONTEND="noninteractive" $SUDO \
apt-get -y install google-perftools libmysqlclient-dev libncurses5-dev libncursesw5-dev
fi
VAR_PATH="$CURRENT_PATH/../../../../var"
# Do not install MySQL if we are in docker (It will be used a docker container instead) or we are explicitly skipping it.
if [[ $DOCKER != 1 && $SKIP_MYSQL_INSTALL != 1 ]]; then
# run noninteractive install for MYSQL 8.4 LTS
wget https://dev.mysql.com/get/mysql-apt-config_0.8.35-1_all.deb -P "$VAR_PATH"
# resolve expired key issue
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys A8D3785C
DEBIAN_FRONTEND="noninteractive" $SUDO dpkg -i "$VAR_PATH/mysql-apt-config_0.8.35-1_all.deb"
$SUDO apt-get update
if [[ "$UBUNTU_VERSION" == "24.04" ]]; then
# run noninteractive install for MYSQL 8.4 LTS
wget https://dev.mysql.com/get/mysql-apt-config_0.8.35-1_all.deb -P "$VAR_PATH"
# resolve expired key issue
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys A8D3785C
DEBIAN_FRONTEND="noninteractive" $SUDO dpkg -i "$VAR_PATH/mysql-apt-config_0.8.35-1_all.deb"
$SUDO apt-get update
fi
DEBIAN_FRONTEND="noninteractive" $SUDO apt-get install -y mysql-server
fi
+170
View File
@@ -45,6 +45,174 @@ if [ ! -f "$BINARY" ]; then
exit 1
fi
function config_value() {
local key="$1"
if [ -z "${CONFIG_ABS:-}" ] || [ ! -f "$CONFIG_ABS" ]; then
return 1
fi
awk -F= -v key="$key" '
{
configKey = $1
gsub(/^[[:space:]]+|[[:space:]]+$/, "", configKey)
}
configKey == key {
value = $0
sub(/^[^=]*=/, "", value)
sub(/[[:space:]]*[#;].*$/, "", value)
gsub(/"/, "", value)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
print value
exit
}
' "$CONFIG_ABS"
}
function command_contains_config() {
local command_line="$1"
if [ -z "${CONFIG_ABS:-}" ]; then
return 0
fi
case "$command_line" in
*" -c $CONFIG_ABS"*|*" -c \"$CONFIG_ABS\""*)
return 0
;;
esac
return 1
}
function find_existing_instances() {
local line pid command_line
while IFS= read -r line; do
pid="${line%% *}"
command_line="${line#* }"
[ -z "$pid" ] && continue
[ "$pid" = "$$" ] && continue
[ "$pid" = "$PPID" ] && continue
case "$command_line" in
*"/starter "*|*"pgrep "*)
continue
;;
esac
if command_contains_config "$command_line"; then
printf '%s\n' "$line"
fi
done < <(pgrep -fa "$EXECPATH" 2>/dev/null || true)
}
function listening_on_port() {
local port="$1"
command -v ss >/dev/null 2>&1 || return 1
ss -H -ltn "sport = :$port" 2>/dev/null | grep -q .
}
function guarded_ports() {
local port soap_enabled soap_port
if [ "$BINFILE" != "worldserver" ]; then
return 0
fi
port="$(config_value "WorldServerPort" || true)"
if [ -n "$port" ]; then
printf '%s\n' "$port"
fi
soap_enabled="$(config_value "SOAP.Enabled" || true)"
soap_port="$(config_value "SOAP.Port" || true)"
if [ "$soap_enabled" = "1" ] && [ -n "$soap_port" ]; then
printf '%s\n' "$soap_port"
fi
}
function find_listening_guard_ports() {
local port
while IFS= read -r port; do
[ -z "$port" ] && continue
if listening_on_port "$port"; then
printf '%s\n' "$port"
fi
done < <(guarded_ports)
}
function wait_for_startup_guard() {
local timeout="${AC_STARTUP_GUARD_TIMEOUT:-90}"
local interval="${AC_STARTUP_GUARD_INTERVAL:-2}"
local elapsed=0
local instances ports
local sleep_for
local warned=0
if [ "${AC_STARTUP_SKIP_GUARD:-0}" = "1" ]; then
return 0
fi
case "$timeout" in
''|*[!0-9]*)
echo "Startup guard: invalid AC_STARTUP_GUARD_TIMEOUT '$timeout', using 90."
timeout=90
;;
esac
case "$interval" in
''|*[!0-9]*)
echo "Startup guard: invalid AC_STARTUP_GUARD_INTERVAL '$interval', using 2."
interval=2
;;
esac
if [ "$interval" -lt 1 ]; then
echo "Startup guard: AC_STARTUP_GUARD_INTERVAL must be at least 1, using 1."
interval=1
fi
while true; do
instances="$(find_existing_instances)"
ports="$(find_listening_guard_ports)"
if [ -z "$instances" ] && [ -z "$ports" ]; then
return 0
fi
if [ "$elapsed" -ge "$timeout" ]; then
echo "Startup guard: refusing to start duplicate $BINFILE after waiting ${timeout}s."
if [ -n "$instances" ]; then
echo "Startup guard: existing matching process(es):"
echo "$instances"
fi
if [ -n "$ports" ]; then
echo "Startup guard: configured port(s) still listening: $(echo "$ports" | paste -sd ',' -)"
fi
exit 0
fi
if [ "$warned" -eq 0 ]; then
echo "Startup guard: waiting for existing $BINFILE instance or configured port(s) to clear..."
warned=1
fi
sleep_for="$interval"
if [ $((elapsed + sleep_for)) -gt "$timeout" ]; then
sleep_for=$((timeout - elapsed))
fi
[ "$sleep_for" -le 0 ] && continue
sleep "$sleep_for"
elapsed=$((elapsed + sleep_for))
done
}
# Create crashes directory if it doesn't exist
mkdir -p "$CRASHES_PATH"
@@ -55,6 +223,8 @@ cd "$BINPATH" || {
EXECPATH=$(realpath "$BINFILE")
wait_for_startup_guard
if [ "$GDB_ENABLED" -eq 1 ]; then
echo "Starting $EXECPATH with GDB enabled"
@@ -50,6 +50,86 @@ teardown() {
[[ "$output" =~ "Test server starting" ]]
}
@test "starter: should refuse duplicate binary/config start" {
cat > "$TEST_DIR/bin/duplicate-server" << 'EOF'
#!/usr/bin/env bash
sleep 30
EOF
chmod +x "$TEST_DIR/bin/duplicate-server"
"$TEST_DIR/bin/duplicate-server" -c "$TEST_DIR/test-server.conf" &
local duplicate_pid=$!
AC_STARTUP_GUARD_TIMEOUT=1 AC_STARTUP_GUARD_INTERVAL=1 run timeout 5s "$SCRIPT_DIR/starter" "$TEST_DIR/bin" "duplicate-server" "" "$TEST_DIR/test-server.conf" "" "" 0
kill "$duplicate_pid" 2>/dev/null || true
wait "$duplicate_pid" 2>/dev/null || true
debug_on_failure
[ "$status" -eq 0 ]
[[ "$output" =~ "Startup guard: refusing to start duplicate duplicate-server" ]]
[[ "$output" =~ "existing matching process" ]]
}
@test "starter: should refuse occupied configured worldserver ports" {
if ! command -v nc >/dev/null 2>&1; then
skip "nc is required for this test"
fi
cat > "$TEST_DIR/bin/worldserver" << 'EOF'
#!/usr/bin/env bash
echo "worldserver should not start"
EOF
chmod +x "$TEST_DIR/bin/worldserver"
local listen_port=40123
cat > "$TEST_DIR/worldserver.conf" << EOF
WorldServerPort = $listen_port
SOAP.Enabled = 0
EOF
nc -l 127.0.0.1 "$listen_port" >/dev/null &
local listener_pid=$!
AC_STARTUP_GUARD_TIMEOUT=1 AC_STARTUP_GUARD_INTERVAL=1 run timeout 5s "$SCRIPT_DIR/starter" "$TEST_DIR/bin" "worldserver" "" "$TEST_DIR/worldserver.conf" "" "" 0
kill "$listener_pid" 2>/dev/null || true
wait "$listener_pid" 2>/dev/null || true
debug_on_failure
[ "$status" -eq 0 ]
[[ "$output" =~ "Startup guard: refusing to start duplicate worldserver" ]]
[[ "$output" =~ "configured port(s) still listening: $listen_port" ]]
}
@test "starter: should parse config keys literally for startup guard" {
if ! command -v nc >/dev/null 2>&1; then
skip "nc is required for this test"
fi
cat > "$TEST_DIR/bin/worldserver" << 'EOF'
#!/usr/bin/env bash
echo "worldserver started"
EOF
chmod +x "$TEST_DIR/bin/worldserver"
local listen_port=40124
cat > "$TEST_DIR/worldserver.conf" << EOF
WorldServerPort = 40125
SOAPXEnabled = 1
SOAPXPort = $listen_port
SOAP.Enabled = 0
EOF
nc -l 127.0.0.1 "$listen_port" >/dev/null &
local listener_pid=$!
AC_STARTUP_GUARD_TIMEOUT=1 AC_STARTUP_GUARD_INTERVAL=1 run timeout 5s "$SCRIPT_DIR/starter" "$TEST_DIR/bin" "worldserver" "" "$TEST_DIR/worldserver.conf" "" "" 0
kill "$listener_pid" 2>/dev/null || true
wait "$listener_pid" 2>/dev/null || true
debug_on_failure
[ "$status" -eq 0 ]
[[ "$output" =~ "worldserver started" ]]
[[ ! "$output" =~ "Startup guard: refusing" ]]
}
# ===== SIMPLE RESTARTER TESTS =====
@test "simple-restarter: should fail with missing parameters" {
@@ -0,0 +1,34 @@
-- DB update 2026_06_01_00 -> 2026_07_15_00
-- RBAC permissions for core commands migrated off deprecated GM security levels.
DELETE FROM `rbac_permissions` WHERE `id` BETWEEN 926 AND 938;
INSERT INTO `rbac_permissions` (`id`, `name`) VALUES
(926, 'Command: autobroadcast list'),
(927, 'Command: autobroadcast add'),
(928, 'Command: autobroadcast locale'),
(929, 'Command: autobroadcast remove'),
(930, 'Command: mail list'),
(931, 'Command: mail return'),
(932, 'Command: npc load'),
(933, 'Command: pool info'),
(934, 'Command: pool lookup'),
(935, 'Command: spellinfo attributes'),
(936, 'Command: spellinfo effects'),
(937, 'Command: spellinfo targets'),
(938, 'Command: spellinfo all');
-- Link each command permission to its role: 196 = Administrator role, 197 = Gamemaster role.
DELETE FROM `rbac_linked_permissions` WHERE `linkedId` BETWEEN 926 AND 938;
INSERT INTO `rbac_linked_permissions` (`id`, `linkedId`) VALUES
(197, 926),
(196, 927),
(196, 928),
(196, 929),
(197, 930),
(197, 931),
(196, 932),
(197, 933),
(197, 934),
(197, 935),
(197, 936),
(197, 937),
(197, 938);
@@ -0,0 +1,4 @@
-- DB update 2026_07_11_00 -> 2026_07_12_00
DELETE FROM `gameobject_template` WHERE `entry` = 301337;
DELETE FROM `gameobject_template_addon` WHERE `entry` = 301337;
DELETE FROM `gameobject_template_locale` WHERE `entry` = 301337;
@@ -0,0 +1,14 @@
-- DB update 2026_07_12_00 -> 2026_07_12_01
DELETE FROM `areatrigger_teleport` WHERE `ID` = 5206;
INSERT INTO `areatrigger_teleport` (`ID`, `Name`, `target_map`, `target_position_x`, `target_position_y`, `target_position_z`, `target_orientation`) VALUES
(5206, 'Gundrak (North Entrance)', 604, 1882.32, 631.027, 176.696, 3.1415927);
-- For below, Wrath Classic (51943) has the player arrive at 1880.74, 853.76, 176.696, 3.14159.
-- However, retail Wrath seems to put the player on the opposing side.
-- The above areatrigger_teleport position was also sniffed from Wrath Classic, so that is being used for the LFG teleport,
-- as it is on the correct side of the instance.
DELETE FROM `lfg_dungeon_template` WHERE `dungeonId` IN (216, 217);
INSERT INTO `lfg_dungeon_template` (`dungeonId`, `name`, `position_x`, `position_y`, `position_z`, `orientation`, `VerifiedBuild`) VALUES
(216, 'Gundrak', 1882.32, 631.027, 176.696, 3.1415927, 0), -- Normal
(217, 'Gundrak', 1882.32, 631.027, 176.696, 3.1415927, 0); -- Heroic
@@ -0,0 +1,4 @@
-- DB update 2026_07_12_01 -> 2026_07_12_02
DELETE FROM `spell_proc` WHERE `SpellId` = -13960;
INSERT INTO `spell_proc` (`SpellId`, `SchoolMask`, `SpellFamilyName`, `SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`, `ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`, `AttributesMask`, `DisableEffectsMask`, `ProcsPerMinute`, `Chance`, `Cooldown`, `Charges`) VALUES
(-13960, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0);
@@ -0,0 +1,5 @@
-- DB update 2026_07_12_02 -> 2026_07_13_00
-- Hodir helpers (#26330): swap PACIFIED (131072) for IMMUNE_TO_NPC (512) per sniffs, so Freya's
-- instance-wide Ground Tremor can't tag them and block her reset. SWIMMING kept where set;
-- immunity is cleared on Hodir engage (boss_hodir.cpp).
UPDATE `creature_template` SET `unit_flags` = (`unit_flags` & ~131072) | 512 WHERE `entry` IN (32893, 32897, 32900, 32901, 32941, 32946, 32948, 32950, 33325, 33326, 33327, 33328, 33330, 33331, 33332, 33333);
+247
View File
@@ -0,0 +1,247 @@
-- DB update 2026_07_13_00 -> 2026_07_14_00
-- -------------------------------------------
-- Acherus: The Ebon Hold (Eastern Plaguelands, map 0)
-- Ebon Hold Re-haul Part 1: Training Room (Upper Floor)
-- NPC Updates, waypoints, etc
-- -------------------------------------------
-- Corpulous (Entry 29205, GUID 125759)
-- Spawn, home anchor, waypoints
UPDATE `creature` SET `position_x`= 2438.5864, `position_y`= -5554.151, `position_z`= 420.79996, `orientation`= 3.9704, `wander_distance`= 0, `MovementType`= 2 WHERE `guid`= 125759;
DELETE FROM `creature_addon` WHERE `guid`= 125759;
INSERT INTO `creature_addon` (`guid`, `path_id`, `mount`, `bytes1`, `bytes2`, `emote`, `visibilityDistanceType`, `auras`) VALUES
(125759, 1257590, 0, 0, 0, 0, 0, NULL);
DELETE FROM `waypoint_data` WHERE `id`= 1257590;
INSERT INTO `waypoint_data` (`id`, `point`, `position_x`, `position_y`, `position_z`, `orientation`, `velocity`, `delay`, `smoothTransition`, `move_type`, `action`, `action_chance`, `wpguid`) VALUES
(1257590, 1, 2438.5864, -5554.151, 420.79996, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257590, 2, 2417.4072, -5577.2603, 420.76572, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257590, 3, 2418.5613, -5602.91, 420.8, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257590, 4, 2430.862, -5626.988, 420.79996, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257590, 5, 2462.9768, -5636.701, 420.6738, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257590, 6, 2484.44, -5626.886, 420.42795, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257590, 7, 2500.5364, -5595.214, 420.70547, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257590, 8, 2492.1143, -5571.9556, 420.5543, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257590, 9, 2468.1182, -5553.14, 420.66876, NULL, 0, 0, 0, 0, 0, 100, 0);
-- Knight of the Ebon Blade (Entry 29202, GUIDs 125750-751)
-- Patrol pair
UPDATE `creature` SET `wander_distance`= 0, `MovementType`= 2 WHERE `guid`= 125750;
DELETE FROM `creature_addon` WHERE `guid`= 125750;
INSERT INTO `creature_addon` (`guid`, `path_id`, `mount`, `bytes1`, `bytes2`, `emote`, `visibilityDistanceType`, `auras`) VALUES
(125750, 1257500, 25280, 0, 1, 0, 0, NULL);
DELETE FROM `waypoint_data` WHERE `id`= 1257500;
INSERT INTO `waypoint_data` (`id`, `point`, `position_x`, `position_y`, `position_z`, `orientation`, `velocity`, `delay`, `smoothTransition`, `move_type`, `action`, `action_chance`, `wpguid`) VALUES
(1257500, 1, 2433.57, -5553.8066, 420.79996, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257500, 2, 2420.6594, -5566.9326, 420.63525, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257500, 3, 2413.44, -5584.155, 420.79105, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257500, 4, 2413.6936, -5602.8584, 420.8, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257500, 5, 2420.8923, -5620.4087, 420.9104, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257500, 6, 2433.7537, -5633.345, 420.73993, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257500, 7, 2450.98, -5640.4243, 420.79996, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257500, 8, 2469.9941, -5640.2007, 420.79996, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257500, 9, 2487.1394, -5633.4414, 420.70834, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257500, 10, 2500.1155, -5620.1206, 420.79996, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257500, 11, 2507.2742, -5602.695, 420.84003, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257500, 12, 2507.081, -5584.0166, 420.79993, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257500, 13, 2500.0461, -5566.8726, 420.79993, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257500, 14, 2486.89, -5553.5205, 420.79996, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257500, 15, 2469.6587, -5546.532, 420.6999, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257500, 16, 2450.987, -5546.5557, 420.70724, NULL, 0, 0, 0, 0, 0, 100, 0);
-- Follower 125751 (formation: follows 125750)
DELETE FROM `creature_formations` WHERE `leaderGUID`= 125750 OR `memberGUID` IN (125750, 125751);
INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `dist`, `angle`, `groupAI`, `point_1`, `point_2`) VALUES
(125750, 125750, 0, 0, 7, 0, 0),
(125750, 125751, 3, 90, 519, 0, 0);
-- Knight of the Ebon Blade (Entry 29202, GUID 125754)
-- Idle, no wander
UPDATE `creature` SET `wander_distance`= 0, `MovementType`= 0 WHERE `guid`= 125754;
-- Risen Drudge (Entry 29212, dead decorative corpses)
-- Make the dead-posed drudges (StandState 7) untargettable: NOT_SELECTABLE (0x2000000) | template flags (32784) = 33587216
UPDATE `creature` SET `unit_flags`= 33587216 WHERE `guid` IN (125766, 125768, 125770, 125771, 125773, 125774, 125775, 125776, 125779, 125780, 125781, 125784, 125786, 125788, 125789, 125790, 125792, 125793, 125794, 125795, 125796, 125797, 125799, 125800, 125801, 125803, 125804, 125805, 125807, 125808, 125809, 125810);
-- Risen Drudge (Entry 29212) - remove 2 AC-only spawns absent from the retail sniff
DELETE FROM `creature_addon` WHERE `guid` IN (125767, 125806);
DELETE FROM `creature` WHERE `guid` IN (125767, 125806);
-- Knight of the Ebon Blade (Entry 29202)
-- Sheath weapons
DELETE FROM `creature_addon` WHERE `guid` IN (125651, 125718, 125737, 125742, 125744, 125745, 125748, 125752, 125754, 125755);
INSERT INTO `creature_addon` (`guid`, `path_id`, `mount`, `bytes1`, `bytes2`, `emote`, `visibilityDistanceType`, `auras`) VALUES
(125651, 0, 0, 0, 0, 0, 0, NULL),
(125718, 0, 0, 0, 0, 0, 0, NULL),
(125737, 0, 0, 0, 0, 0, 0, NULL),
(125742, 0, 0, 0, 0, 0, 0, NULL),
(125744, 0, 0, 0, 0, 0, 0, NULL),
(125745, 0, 0, 0, 0, 0, 0, NULL),
(125748, 0, 0, 0, 0, 0, 0, NULL),
(125752, 0, 0, 0, 0, 0, 0, NULL),
(125754, 0, 0, 0, 0, 0, 0, NULL),
(125755, 0, 0, 0, 0, 0, 0, NULL);
-- Knight of the Ebon Blade (Entry 29202) - hidden in the rafters (Z~444)
DELETE FROM `creature_addon` WHERE `guid` IN (125714, 125716, 125721, 125732, 125734, 125735, 125739, 125740);
INSERT INTO `creature_addon` (`guid`, `path_id`, `mount`, `bytes1`, `bytes2`, `emote`, `visibilityDistanceType`, `auras`) VALUES
(125714, 0, 0, 0, 0, 0, 0, NULL),
(125716, 0, 0, 0, 0, 0, 0, NULL),
(125721, 0, 0, 0, 0, 0, 0, NULL),
(125732, 0, 0, 0, 0, 0, 0, NULL),
(125734, 0, 0, 0, 0, 0, 0, NULL),
(125735, 0, 0, 0, 0, 0, 0, NULL),
(125739, 0, 0, 0, 0, 0, 0, NULL),
(125740, 0, 0, 0, 0, 0, 0, NULL);
-- Vigilant Gargoyle (Entry 29239) - all 8 (Training Room airspace)
UPDATE `creature_template` SET `AIName`= 'SmartAI' WHERE `entry`= 29239;
UPDATE `creature` SET `position_x`= 2434.2053, `position_y`= -5662.7285, `position_z`= 445.16962, `orientation`= 1.17073, `MovementType`= 2, `wander_distance`= 0 WHERE `guid`= 125826;
UPDATE `creature` SET `position_x`= 2386.5322, `position_y`= -5591.9404, `position_z`= 445.1513, `orientation`= 6.21624, `MovementType`= 2, `wander_distance`= 0 WHERE `guid`= 125827;
UPDATE `creature` SET `position_x`= 2391.4082, `position_y`= -5567.293, `position_z`= 444.41574, `orientation`= 5.88485, `MovementType`= 2, `wander_distance`= 0 WHERE `guid`= 125828;
UPDATE `creature` SET `position_x`= 2483.3416, `position_y`= -5565.2993, `position_z`= 465.3641, `orientation`= 5.16390, `MovementType`= 2, `wander_distance`= 0 WHERE `guid`= 125829;
UPDATE `creature` SET `position_x`= 2451.695, `position_y`= -5646.18, `position_z`= 434.86523, `orientation`= 2.34530, `MovementType`= 2, `wander_distance`= 0 WHERE `guid`= 125830;
UPDATE `creature` SET `position_x`= 2430.0688, `position_y`= -5548.5366, `position_z`= 444.9371, `orientation`= 5.23599, `MovementType`= 2, `wander_distance`= 0 WHERE `guid`= 125831;
UPDATE `creature` SET `position_x`= 2458.673, `position_y`= -5667.2334, `position_z`= 445.40192, `orientation`= 1.52346, `MovementType`= 2, `wander_distance`= 0 WHERE `guid`= 125832;
UPDATE `creature` SET `position_x`= 2488.3079, `position_y`= -5584.386, `position_z`= 481.03842, `orientation`= 2.61030, `MovementType`= 2, `wander_distance`= 0 WHERE `guid`= 125833;
DELETE FROM `creature_addon` WHERE `guid` IN (125826, 125827, 125828, 125829, 125830, 125831, 125832, 125833);
INSERT INTO `creature_addon` (`guid`, `path_id`, `mount`, `bytes1`, `bytes2`, `emote`, `visibilityDistanceType`, `auras`) VALUES
(125826, 1258260, 0, 9, 1, 444, 0, NULL),
(125827, 1258270, 0, 9, 1, 444, 0, NULL),
(125828, 1258280, 0, 9, 1, 444, 0, NULL),
(125829, 1258290, 0, 0, 1, 444, 0, NULL),
(125830, 1258300, 0, 0, 1, 444, 0, NULL),
(125831, 1258310, 0, 9, 1, 444, 0, NULL),
(125832, 1258320, 0, 9, 1, 444, 0, NULL),
(125833, 1258330, 0, 0, 1, 444, 0, NULL);
-- Flight paths: patrollers
DELETE FROM `waypoint_data` WHERE `id` IN (1258260, 1258270, 1258280, 1258290, 1258300, 1258310, 1258320, 1258330);
INSERT INTO `waypoint_data` (`id`, `point`, `position_x`, `position_y`, `position_z`, `orientation`, `velocity`, `delay`, `smoothTransition`, `move_type`, `action`, `action_chance`, `wpguid`) VALUES
(1258260, 1, 2434.21, -5662.73, 445.17, 1.17073, 5, 65000, 0, 2, 0, 100, 0),
(1258260, 2, 2437.7, -5653.26, 447.963, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258260, 3, 2449.12, -5599.83, 455.546, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258260, 4, 2463.19, -5545.83, 455.546, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258260, 5, 2462.16, -5519.81, 445.019, 4.71029, 5, 90000, 0, 2, 0, 100, 0),
(1258260, 6, 2461.56, -5528.22, 448.031, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258260, 7, 2456.53, -5572.52, 454.475, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258260, 8, 2441, -5638.43, 454.475, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258270, 1, 2386.53, -5591.94, 445.151, 6.21624, 5, 100000, 0, 2, 0, 100, 0),
(1258270, 2, 2396.07, -5594.14, 447.163, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258270, 3, 2436.67, -5610.5, 451.023, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258270, 4, 2473.23, -5633.96, 451.023, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258270, 5, 2490.76, -5638.42, 445.051, 2.17773, 5, 70000, 0, 2, 0, 100, 0),
(1258270, 6, 2482.04, -5634.05, 447.43, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258270, 7, 2437.04, -5610.4, 455.235, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258270, 8, 2406.09, -5597.51, 451.596, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258280, 1, 2391.41, -5567.29, 444.416, 5.88485, 5, 82000, 0, 2, 0, 100, 0),
(1258280, 2, 2398.94, -5570.2, 446.666, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258280, 3, 2440.29, -5586.84, 449.832, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258280, 4, 2505.82, -5612.69, 449.832, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258280, 5, 2529.11, -5619.67, 445.193, 2.81575, 5, 108000, 0, 2, 0, 100, 0),
(1258280, 6, 2523.4, -5615.98, 445.721, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258280, 7, 2457.17, -5592.02, 452.471, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258280, 8, 2417.3, -5576.37, 452.471, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258290, 1, 2483.34, -5565.3, 465.364, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258290, 2, 2496.26, -5592, 465.364, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258290, 3, 2487.79, -5622.76, 465.364, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258290, 4, 2454.34, -5633.35, 465.364, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258290, 5, 2425.15, -5610.25, 465.364, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258290, 6, 2428.72, -5575.9, 465.364, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258290, 7, 2455.77, -5556.59, 465.364, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258300, 1, 2451.7, -5646.18, 434.865, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258300, 2, 2439.38, -5633.58, 434.865, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258300, 3, 2435.8, -5612.65, 434.865, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258300, 4, 2415.5, -5592.08, 434.865, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258300, 5, 2420.02, -5564.28, 434.865, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258300, 6, 2442.14, -5561.77, 434.865, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258300, 7, 2455.83, -5546.95, 434.865, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258300, 8, 2473.27, -5540.89, 434.865, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258300, 9, 2485.81, -5558.36, 434.865, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258300, 10, 2486.56, -5585.89, 434.865, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258300, 11, 2512.01, -5598.65, 434.865, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258300, 12, 2498.43, -5621.79, 434.865, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258300, 13, 2473.17, -5621.09, 434.865, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258300, 14, 2464.31, -5642.48, 434.865, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258310, 1, 2430.07, -5548.54, 444.937, 5.23599, 5, 95000, 0, 3, 0, 100, 0),
(1258310, 2, 2440.2, -5553.77, 447.152, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258310, 3, 2479.28, -5579.33, 458.901, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258310, 4, 2508.33, -5596.56, 458.901, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258310, 5, 2534.25, -5595.29, 445.207, 3.49066, 5, 60000, 0, 3, 0, 100, 0),
(1258310, 6, 2531.36, -5596.21, 447.298, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258310, 7, 2486.9, -5597.17, 448.131, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258310, 8, 2441.01, -5584.44, 448.52, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258320, 1, 2458.67, -5667.23, 445.402, 1.52346, 5, 75000, 0, 2, 0, 100, 0),
(1258320, 2, 2461.16, -5656.92, 448.499, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258320, 3, 2478.7, -5619.14, 450.054, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258320, 4, 2495.67, -5585.25, 450.054, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258320, 5, 2504.88, -5563.37, 444.887, 3.80665, 5, 105000, 0, 2, 0, 100, 0),
(1258320, 6, 2499.79, -5572.24, 447.68, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258320, 7, 2471.41, -5619.1, 454.346, NULL, 5, 0, 0, 3, 0, 100, 0),
(1258330, 1, 2488.31, -5584.39, 481.038, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258330, 2, 2454.49, -5564.5, 481.038, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258330, 3, 2435.28, -5578, 481.038, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258330, 4, 2431.28, -5602.2, 481.038, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258330, 5, 2446.33, -5619.11, 481.038, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258330, 6, 2468.88, -5620.94, 481.038, NULL, 5, 0, 1, 0, 0, 100, 0),
(1258330, 7, 2486.97, -5604.07, 481.038, NULL, 5, 0, 1, 0, 0, 100, 0);
-- SmartAI (5 patrollers)
DELETE FROM `smart_scripts` WHERE `entryorguid` IN (-125826, -125827, -125828, -125831, -125832) AND `source_type`= 0;
INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES
(-125826, 0, 0, 0, 108, 0, 100, 0, 1, 0, 0, 0, 0, 0, 90, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - perch A: SUBMERGED (idle)'),
(-125826, 0, 1, 0, 108, 0, 100, 0, 2, 0, 0, 0, 0, 0, 91, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - takeoff A: STAND (flap)'),
(-125826, 0, 2, 0, 108, 0, 100, 0, 5, 0, 0, 0, 0, 0, 90, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - perch B: SUBMERGED (idle)'),
(-125826, 0, 3, 0, 108, 0, 100, 0, 6, 0, 0, 0, 0, 0, 91, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - takeoff B: STAND (flap)'),
(-125827, 0, 0, 0, 108, 0, 100, 0, 1, 0, 0, 0, 0, 0, 90, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - perch A: SUBMERGED (idle)'),
(-125827, 0, 1, 0, 108, 0, 100, 0, 2, 0, 0, 0, 0, 0, 91, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - takeoff A: STAND (flap)'),
(-125827, 0, 2, 0, 108, 0, 100, 0, 5, 0, 0, 0, 0, 0, 90, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - perch B: SUBMERGED (idle)'),
(-125827, 0, 3, 0, 108, 0, 100, 0, 6, 0, 0, 0, 0, 0, 91, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - takeoff B: STAND (flap)'),
(-125828, 0, 0, 0, 108, 0, 100, 0, 1, 0, 0, 0, 0, 0, 90, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - perch A: SUBMERGED (idle)'),
(-125828, 0, 1, 0, 108, 0, 100, 0, 2, 0, 0, 0, 0, 0, 91, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - takeoff A: STAND (flap)'),
(-125828, 0, 2, 0, 108, 0, 100, 0, 5, 0, 0, 0, 0, 0, 90, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - perch B: SUBMERGED (idle)'),
(-125828, 0, 3, 0, 108, 0, 100, 0, 6, 0, 0, 0, 0, 0, 91, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - takeoff B: STAND (flap)'),
(-125831, 0, 0, 0, 108, 0, 100, 0, 1, 0, 0, 0, 0, 0, 90, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - perch A: SUBMERGED (idle)'),
(-125831, 0, 1, 0, 108, 0, 100, 0, 2, 0, 0, 0, 0, 0, 91, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - takeoff A: STAND (flap)'),
(-125831, 0, 2, 0, 108, 0, 100, 0, 5, 0, 0, 0, 0, 0, 90, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - perch B: SUBMERGED (idle)'),
(-125831, 0, 3, 0, 108, 0, 100, 0, 6, 0, 0, 0, 0, 0, 91, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - takeoff B: STAND (flap)'),
(-125832, 0, 0, 0, 108, 0, 100, 0, 1, 0, 0, 0, 0, 0, 90, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - perch A: SUBMERGED (idle)'),
(-125832, 0, 1, 0, 108, 0, 100, 0, 2, 0, 0, 0, 0, 0, 91, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - takeoff A: STAND (flap)'),
(-125832, 0, 2, 0, 108, 0, 100, 0, 5, 0, 0, 0, 0, 0, 90, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - perch B: SUBMERGED (idle)'),
(-125832, 0, 3, 0, 108, 0, 100, 0, 6, 0, 0, 0, 0, 0, 91, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Gargoyle - takeoff B: STAND (flap)');
-- Faction correction for vendors
UPDATE `creature_template` SET `faction`= 2050 WHERE `entry` IN (28500, 29203, 29205, 29207, 29208);
-- Master Siegesmith Corvus (Entry 28500, GUID 125869)
UPDATE `creature_template` SET `AIName`= 'SmartAI' WHERE `entry`= 28500;
UPDATE `creature` SET `MovementType`= 2, `wander_distance`= 0 WHERE `guid`= 125869;
DELETE FROM `creature_addon` WHERE `guid`= 125869;
INSERT INTO `creature_addon` (`guid`, `path_id`, `mount`, `bytes1`, `bytes2`, `emote`, `visibilityDistanceType`, `auras`) VALUES
(125869, 1258690, 0, 0, 1, 233, 0, NULL);
DELETE FROM `waypoint_data` WHERE `id`= 1258690;
INSERT INTO `waypoint_data` (`id`, `point`, `position_x`, `position_y`, `position_z`, `orientation`, `velocity`, `delay`, `smoothTransition`, `move_type`, `action`, `action_chance`, `wpguid`) VALUES
-- 16-point weave loop (close-range sniff); ~22-32s craft pause at the 4 anvils (pt 5,7,10,16)
(1258690, 1, 2438.5435, -5634.903, 420.79996, NULL, 0, 0, 0, 0, 0, 100, 0),
(1258690, 2, 2449.8833, -5641.199, 420.79996, NULL, 0, 0, 0, 0, 0, 100, 0),
(1258690, 3, 2460.378, -5650.1074, 420.8016, NULL, 0, 0, 0, 0, 0, 100, 0),
(1258690, 4, 2467.7244, -5654.838, 420.79996, NULL, 0, 0, 0, 0, 0, 100, 0),
(1258690, 5, 2466.9749, -5656.5464, 420.79996, NULL, 0, 22000, 0, 0, 0, 100, 0),
(1258690, 6, 2440.863, -5647.832, 420.79996, NULL, 0, 0, 0, 0, 0, 100, 0),
(1258690, 7, 2433.588, -5649.348, 420.79996, NULL, 0, 32000, 0, 0, 0, 100, 0),
(1258690, 8, 2449.2322, -5647.929, 420.79996, NULL, 0, 0, 0, 0, 0, 100, 0),
(1258690, 9, 2456.4922, -5647.385, 420.77432, NULL, 0, 0, 0, 0, 0, 100, 0),
(1258690, 10, 2466.7793, -5648.771, 420.79996, NULL, 0, 28000, 0, 0, 0, 100, 0),
(1258690, 11, 2455.2024, -5647.0728, 420.78763, NULL, 0, 0, 0, 0, 0, 100, 0),
(1258690, 12, 2447.792, -5646.166, 420.79996, NULL, 0, 0, 0, 0, 0, 100, 0),
(1258690, 13, 2439.7908, -5641.177, 420.79996, NULL, 0, 0, 0, 0, 0, 100, 0),
(1258690, 14, 2435.9255, -5634.6216, 420.72903, NULL, 0, 0, 0, 0, 0, 100, 0),
(1258690, 15, 2432.0063, -5633.902, 420.7254, NULL, 0, 0, 0, 0, 0, 100, 0),
(1258690, 16, 2431.6282, -5635.6772, 420.65244, NULL, 0, 25000, 0, 0, 0, 100, 0);
DELETE FROM `creature_text` WHERE `CreatureID`= 28500;
INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES
(28500, 0, 0, 'First, he gives away all of my precious Saronite, then he demands I begin crafting Runeblades of even greater power! Pah!', 12, 0, 100, 11, 0, 0, 0, 0, 'Master Siegesmith Corvus'),
(28500, 0, 1, 'Fix my armor, Corvus! My blade is rusty, Corvus! ... when will these damned mortals ever learn to care for themselves?', 12, 0, 100, 11, 0, 0, 0, 0, 'Master Siegesmith Corvus'),
(28500, 0, 2, 'I may not have any mindless servants, but at least I can see a quality blade made from time to time.', 12, 0, 100, 11, 0, 0, 0, 0, 'Master Siegesmith Corvus'),
(28500, 0, 3, 'There is work to be done here, but do you see anyone jumping to help me? No! Wretches, all of them!', 12, 0, 100, 11, 0, 0, 0, 0, 'Master Siegesmith Corvus'),
(28500, 0, 4, 'These Death Knights truly know no honor. First, they take my greatest arms and armor, crafted with care, forged with the Lich King''s blessing. Then they wander off to who knows where and come back dressed like Goblins, mumbling something about pawning off their armor for a few coppers! Pathetic!', 12, 0, 100, 11, 0, 0, 0, 0, 'Master Siegesmith Corvus'),
(28500, 0, 5, 'What is this nonsense about the Light? I have weapons and armor to make!', 12, 0, 100, 11, 0, 0, 0, 0, 'Master Siegesmith Corvus');
DELETE FROM `smart_scripts` WHERE `entryorguid`= 28500 AND `source_type`= 0;
INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES
(28500, 0, 0, 0, 1, 0, 100, 0, 2100000, 2700000, 2100000, 2700000, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Corvus - ambient line at random ~40min');
@@ -0,0 +1,95 @@
-- DB update 2026_07_14_00 -> 2026_07_14_01
-- -------------------------------------------
-- Acherus: The Ebon Hold (Eastern Plaguelands, map 0)
-- Ebon Hold Re-haul Part 2: Command Center (Bottom Floor)
-- NPC Updates, waypoints, etc
-- -------------------------------------------
-- Faction corrections
-- Siouxsie the Banshee (Entry 27928)
UPDATE `creature_template` SET `faction`= 2050 WHERE `entry`= 27928;
-- Squire Edwards (Entry 28486)
UPDATE `creature_template` SET `faction`= 2050 WHERE `entry`= 28486;
-- Coldwraith (Entry 28488)
UPDATE `creature_template` SET `faction`= 2050 WHERE `entry`= 28488;
-- Disciple of Frost (Entry 28490)
UPDATE `creature_template` SET `faction`= 2082 WHERE `entry`= 28490;
-- Lord Thorval (Entry 29196)
UPDATE `creature_template` SET `faction`= 2050 WHERE `entry`= 29196;
-- Knight of the Ebon Blade (Entry 31094) (part of the DK questline, is phased, but here nonetheless)
UPDATE `creature_template` SET `faction`= 2050 WHERE `entry`= 31094;
-- Knight of the Ebon Blade (Entry 29202) - Command Center floor - weapon sheathing
DELETE FROM `creature_addon` WHERE `guid` IN (125719, 125727, 125730, 125733, 125738, 125743, 125749, 125753);
INSERT INTO `creature_addon` (`guid`, `path_id`, `mount`, `bytes1`, `bytes2`, `emote`, `visibilityDistanceType`, `auras`) VALUES
(125719, 0, 0, 0, 0, 0, 0, NULL),
(125727, 0, 0, 0, 0, 0, 0, NULL),
(125730, 0, 0, 0, 0, 0, 0, NULL),
(125733, 0, 0, 0, 0, 0, 0, NULL),
(125738, 0, 0, 0, 0, 0, 0, NULL),
(125743, 0, 0, 0, 0, 0, 0, NULL),
(125749, 0, 0, 0, 0, 0, 0, NULL),
(125753, 0, 0, 0, 0, 0, 0, NULL);
-- Knight of the Ebon Blade (Entry 29202, GUIDs 125728-729) - Command Center OUTER ring patrol
UPDATE `creature` SET `wander_distance`= 0, `MovementType`= 2 WHERE `guid`= 125728;
UPDATE `creature` SET `wander_distance`= 0, `MovementType`= 0 WHERE `guid`= 125729;
DELETE FROM `creature_addon` WHERE `guid`= 125728;
INSERT INTO `creature_addon` (`guid`, `path_id`, `mount`, `bytes1`, `bytes2`, `emote`, `visibilityDistanceType`, `auras`) VALUES
(125728, 1257280, 25280, 0, 1, 0, 0, NULL);
DELETE FROM `waypoint_data` WHERE `id`= 1257280;
INSERT INTO `waypoint_data` (`id`, `point`, `position_x`, `position_y`, `position_z`, `orientation`, `velocity`, `delay`, `smoothTransition`, `move_type`, `action`, `action_chance`, `wpguid`) VALUES
(1257280, 1, 2453.019, -5659.009, 376.963, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 2, 2436.503, -5654.292, 377.010, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 3, 2421.870, -5646.072, 377.085, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 4, 2409.462, -5633.826, 377.061, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 5, 2401.033, -5619.471, 377.059, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 6, 2396.028, -5602.503, 377.042, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 7, 2395.134, -5584.744, 377.032, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 8, 2400.252, -5568.880, 377.175, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 9, 2408.467, -5554.637, 377.087, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 10, 2422.047, -5542.582, 377.077, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 11, 2436.357, -5533.727, 377.048, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 12, 2453.023, -5528.724, 377.021, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 13, 2470.619, -5528.724, 376.969, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 14, 2486.896, -5534.060, 377.074, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 15, 2500.929, -5542.755, 377.070, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 16, 2511.579, -5556.004, 377.029, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 17, 2520.007, -5569.621, 377.088, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 18, 2525.179, -5586.072, 377.027, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 19, 2525.346, -5603.292, 376.955, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 20, 2521.025, -5618.274, 377.021, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 21, 2512.638, -5633.113, 377.022, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 22, 2500.469, -5644.090, 377.130, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 23, 2485.997, -5652.299, 377.080, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257280, 24, 2470.307, -5658.005, 377.048, NULL, 0, 0, 0, 0, 0, 100, 0);
-- Follower 125729
DELETE FROM `creature_formations` WHERE `leaderGUID`= 125728 OR `memberGUID` IN (125728, 125729);
INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `dist`, `angle`, `groupAI`, `point_1`, `point_2`) VALUES
(125728, 125728, 0, 0, 7, 0, 0),
(125728, 125729, 3, 270, 519, 0, 0);
-- Knight of the Ebon Blade (Entry 29202, GUID 125746) - Command Center INNER ring patrol
UPDATE `creature` SET `wander_distance`= 0, `MovementType`= 2 WHERE `guid`= 125746;
DELETE FROM `creature_addon` WHERE `guid`= 125746;
INSERT INTO `creature_addon` (`guid`, `path_id`, `mount`, `bytes1`, `bytes2`, `emote`, `visibilityDistanceType`, `auras`) VALUES
(125746, 1257460, 25280, 0, 1, 0, 0, NULL);
DELETE FROM `waypoint_data` WHERE `id`= 1257460;
INSERT INTO `waypoint_data` (`id`, `point`, `position_x`, `position_y`, `position_z`, `orientation`, `velocity`, `delay`, `smoothTransition`, `move_type`, `action`, `action_chance`, `wpguid`) VALUES
(1257460, 1, 2476.547, -5569.105, 366.987, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257460, 2, 2465.888, -5564.635, 366.987, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257460, 3, 2454.430, -5564.898, 366.996, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257460, 4, 2439.936, -5573.309, 366.987, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257460, 5, 2431.440, -5587.982, 366.987, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257460, 6, 2431.727, -5599.489, 367.319, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257460, 7, 2436.017, -5610.170, 367.033, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257460, 8, 2442.707, -5616.534, 367.034, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257460, 9, 2446.977, -5619.203, 366.987, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257460, 10, 2456.497, -5622.578, 366.988, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257460, 11, 2465.452, -5622.340, 367.009, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257460, 12, 2468.838, -5621.344, 367.016, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257460, 13, 2476.673, -5618.066, 366.987, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257460, 14, 2482.256, -5612.358, 366.987, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257460, 15, 2489.440, -5599.080, 367.042, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257460, 16, 2488.702, -5586.393, 367.230, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257460, 17, 2485.971, -5579.744, 367.045, NULL, 0, 0, 0, 0, 0, 100, 0);
@@ -0,0 +1,49 @@
-- DB update 2026_07_14_01 -> 2026_07_14_02
-- -------------------------------------------
-- Acherus: The Ebon Hold (Eastern Plaguelands, map 0)
-- Ebon Hold Re-haul Part 3: Lord Thorval (Walking Lecture)
-- NPC Updates, waypoints, etc
-- -------------------------------------------
-- Lord Thorval (Entry 29196, GUID 125712)
UPDATE `creature` SET `position_x`= 2527.7344, `position_y`= -5549.2075, `position_z`= 377.18475, `orientation`= 3.735, `wander_distance`= 0, `MovementType`= 2 WHERE `guid`= 125712;
DELETE FROM `creature_addon` WHERE `guid`= 125712;
INSERT INTO `creature_addon` (`guid`, `path_id`, `mount`, `bytes1`, `bytes2`, `emote`, `visibilityDistanceType`, `auras`) VALUES
(125712, 1257120, 0, 0, 1, 0, 0, NULL);
-- Waypoints (22 min between lectures/walking cycle)
DELETE FROM `waypoint_data` WHERE `id`= 1257120;
INSERT INTO `waypoint_data` (`id`, `point`, `position_x`, `position_y`, `position_z`, `orientation`, `velocity`, `delay`, `smoothTransition`, `move_type`, `action`, `action_chance`, `wpguid`) VALUES
(1257120, 1, 2527.7344, -5549.2075, 377.18475, 3.735, 0, 1320000, 0, 0, 0, 100, 0),
(1257120, 2, 2523.0664, -5544.4194, 377.22162, 4.433136, 0, 8000, 0, 0, 0, 100, 0),
(1257120, 3, 2530.72, -5555.91, 377.13217, 3.263766, 0, 6500, 0, 0, 0, 100, 0),
(1257120, 4, 2530.4983, -5555.5771, 377.135, 3.263766, 0, 8500, 0, 0, 0, 100, 0),
(1257120, 5, 2523.0664, -5544.4194, 377.22162, 4.433136, 0, 6400, 0, 0, 0, 100, 0),
(1257120, 6, 2523.2881, -5544.7523, 377.219, 4.433136, 0, 8500, 0, 0, 0, 100, 0),
(1257120, 7, 2530.72, -5555.91, 377.13217, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257120, 8, 2523.0664, -5544.4194, 377.22162, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257120, 9, 2530.72, -5555.91, 377.13217, NULL, 0, 0, 0, 0, 0, 100, 0),
(1257120, 10, 2523.0664, -5544.4194, 377.22162, 4.433136, 0, 10000, 0, 0, 0, 100, 0);
-- Speech
DELETE FROM `creature_text` WHERE (`CreatureID` = 29196) AND (`GroupID` IN (0, 1, 2, 3, 4, 5, 6, 7, 8));
INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES
(29196, 0, 0, 'As disciples of blood, you strive to master the very lifeforce of your enemies.', 12, 0, 100, 1, 0, 0, 29867, 0, 'Lord Thorval Lecture 1'),
(29196, 1, 0, 'Be it by blade or incantation, blood feeds our attacks and weakens our foes.', 12, 0, 100, 1, 0, 0, 29868, 0, 'Lord Thorval Lecture 2'),
(29196, 2, 0, 'True masters learn to make blood serve more than just their strength in battle.', 12, 0, 100, 1, 0, 0, 29869, 0, 'Lord Thorval Lecture 3'),
(29196, 3, 0, 'Stripping energy from our foes, both fighting and fallen, allows us to persevere where lesser beings fall exhausted.', 12, 0, 100, 1, 0, 0, 29870, 0, 'Lord Thorval Lecture 4'),
(29196, 4, 0, 'And every foe that falls, energy sapped and stolen, only further fuels our assault.', 12, 0, 100, 1, 0, 0, 29871, 0, 'Lord Thorval Lecture 5'),
(29196, 5, 0, 'As masters of blood, we know battle without end...', 12, 0, 100, 1, 0, 0, 29872, 0, 'Lord Thorval Lecture 6'),
(29196, 6, 0, 'We know hunger never to be quenched...', 12, 0, 100, 1, 0, 0, 29873, 0, 'Lord Thorval Lecture 7'),
(29196, 7, 0, 'We know power never to be overcome...', 12, 0, 100, 1, 0, 0, 29874, 0, 'Lord Thorval Lecture 8'),
(29196, 8, 0, 'As masters of blood, we are masters of life and death itself. Against us, even hope falls drained and lifeless.', 12, 0, 100, 1, 0, 0, 29875, 0, 'Lord Thorval Lecture 9');
-- SAI to talk on waypoint reached
DELETE FROM `smart_scripts` WHERE (`entryorguid`= 29196 AND `source_type`= 0) OR (`entryorguid`= 2919600 AND `source_type`= 9);
INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES
(29196, 0, 0, 0, 108, 0, 100, 0, 2, 1257120, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Lord Thorval - On Waypoint 2 Reached - Say Line 0'),
(29196, 0, 1, 0, 108, 0, 100, 0, 3, 1257120, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Lord Thorval - On Waypoint 3 Reached - Say Line 1'),
(29196, 0, 2, 0, 108, 0, 100, 0, 4, 1257120, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Lord Thorval - On Waypoint 4 Reached - Say Line 2'),
(29196, 0, 3, 0, 108, 0, 100, 0, 5, 1257120, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Lord Thorval - On Waypoint 5 Reached - Say Line 3'),
(29196, 0, 4, 0, 108, 0, 100, 0, 6, 1257120, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Lord Thorval - On Waypoint 6 Reached - Say Line 4'),
(29196, 0, 5, 0, 108, 0, 100, 0, 7, 1257120, 0, 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Lord Thorval - On Waypoint 7 Reached - Say Line 5'),
(29196, 0, 6, 0, 108, 0, 100, 0, 8, 1257120, 0, 0, 0, 0, 1, 6, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Lord Thorval - On Waypoint 8 Reached - Say Line 6'),
(29196, 0, 7, 0, 108, 0, 100, 0, 9, 1257120, 0, 0, 0, 0, 1, 7, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Lord Thorval - On Waypoint 9 Reached - Say Line 7'),
(29196, 0, 8, 0, 108, 0, 100, 0, 10, 1257120, 0, 0, 0, 0, 1, 8, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Lord Thorval - On Waypoint 10 Reached - Say Line 8');
+115
View File
@@ -0,0 +1,115 @@
-- DB update 2026_07_14_02 -> 2026_07_14_03
-- Reusable option sets for creature text groups
CREATE TABLE IF NOT EXISTS `creature_text_option_sets` (
`SetID` TINYINT UNSIGNED NOT NULL,
`Cooldown` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Group cooldown in ms before it can fire again',
`TriggerChance` TINYINT UNSIGNED NOT NULL DEFAULT 100 COMMENT '0-100 pct chance to fire at all',
`PlayerOnly` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Only fire if target is a player',
`comment` VARCHAR(255) DEFAULT '',
PRIMARY KEY (`SetID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DELETE FROM `creature_text_option_sets`;
INSERT INTO `creature_text_option_sets` (`SetID`, `Cooldown`, `TriggerChance`, `PlayerOnly`, `comment`) VALUES
(1, 6000, 100, 0, 'Standard kill yell: 6s cooldown'),
(2, 6000, 100, 1, 'Player-only kill yell: 6s cooldown'),
(3, 5000, 100, 1, 'Short cooldown player-only kill yell: 5s cooldown'),
(4, 0, 30, 0, 'Chance-based: 30% trigger, no cooldown'),
(5, 5000, 100, 0, 'Standard kill yell: 5s cooldown'),
(6, 0, 25, 1, 'Chance-based: 25% trigger, player-only'),
(7, 0, 50, 1, 'Chance-based: 50% trigger, player-only');
-- Assign option sets to creature text groups
CREATE TABLE IF NOT EXISTS `creature_text_options` (
`CreatureID` INT UNSIGNED NOT NULL,
`GroupID` TINYINT UNSIGNED NOT NULL,
`OptionSetID` TINYINT UNSIGNED NOT NULL,
PRIMARY KEY (`CreatureID`, `GroupID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DELETE FROM `creature_text_options`;
INSERT INTO `creature_text_options` (`CreatureID`, `GroupID`, `OptionSetID`) VALUES
-- Black Temple
(22887, 2, 3), -- High Warlord Najentus SAY_SLAY
(22871, 2, 2), -- Teron Gorefiend SAY_SLAY
(22917, 1, 1), -- Illidan Stormrage SAY_ILLIDAN_KILL
(22947, 3, 1), -- Mother Shahraz SAY_SLAY
(22949, 3, 1), -- Gathios the Shatterer SAY_COUNCIL_SLAY
(22950, 3, 1), -- High Nethermancer Zerevor SAY_COUNCIL_SLAY
(22951, 3, 1), -- Lady Malande SAY_COUNCIL_SLAY
(22952, 3, 1), -- Veras Darkshadow SAY_COUNCIL_SLAY
-- Azjol-Nerub
(28684, 1, 1), -- Krik'thir the Gatewatcher SAY_SLAY
(29120, 1, 1), -- Anub'arak SAY_SLAY
-- Drak'Tharon Keep
(26631, 1, 1), -- Novos the Summoner SAY_KILL
(26632, 1, 1), -- The Prophet Tharon'ja SAY_KILL
(26630, 1, 1), -- Trollgore SAY_KILL
-- Gundrak
(29306, 1, 1), -- Gal'darah SAY_SLAY
(29305, 1, 1), -- Moorabi SAY_SLAY
(29304, 1, 1), -- Slad'ran SAY_SLAY
-- The Nexus
(26723, 1, 1), -- Keristrasza SAY_SLAY
(26731, 1, 1), -- Grand Magus Telestra SAY_KILL
(26794, 5, 1), -- Ormorok the Tree-Shaper SAY_KILL
-- Ruby Sanctum
(39863, 6, 2), -- Halion SAY_KILL
(39751, 2, 1), -- Baltharus the Warborn SAY_KILL
(39746, 1, 1), -- General Zarithrian SAY_KILL
(39747, 3, 1), -- Saviana Ragefire SAY_KILL
-- Icecrown Citadel
(36597, 10, 3), -- The Lich King SAY_LK_KILL
-- Naxxramas
(16061, 1, 4), -- Instructor Razuvious SAY_SLAY
-- Karazhan
(15687, 2, 3), -- Moroes SAY_KILL
-- Battle for Mount Hyjal
(17888, 0, 2), -- Kazrogal SAY_ONSLAY
(17842, 1, 2), -- Azgalor SAY_ONSLAY
(17767, 1, 2), -- Rage Winterchill SAY_ONSLAY
(17808, 1, 2), -- Anetheron SAY_ONSLAY
-- Serpentshrine Cavern
(21212, 6, 1), -- Lady Vashj SAY_SLAY
(21216, 2, 1), -- Hydross the Unstable SAY_CLEAN_SLAY
(21216, 5, 1), -- Hydross the Unstable SAY_CORRUPT_SLAY
(21214, 5, 1), -- Fathom-Lord Karathress SAY_SLAY
(21213, 3, 1), -- Morogrim Tidewalker SAY_SLAY
-- Gruul's Lair
(19044, 3, 5), -- Gruul the Dragonkiller SAY_SLAY
(18831, 3, 5), -- High King Maulgar SAY_SLAY
-- Magtheridon's Lair
(17257, 2, 5), -- Magtheridon SAY_SLAY
-- Tempest Keep: The Eye
(19516, 1, 5), -- Void Reaver SAY_SLAY
-- Black Temple (additional)
(22948, 1, 1), -- Gurtogg Bloodboil SAY_SLAY
(23418, 2, 1), -- Essence of Suffering SUFF_SAY_SLAY
(23419, 1, 1), -- Essence of Desire DESI_SAY_SLAY
(23420, 1, 1), -- Essence of Anger ANGER_SAY_SLAY
-- Molten Core
(12018, 2, 6), -- Majordomo Executus SAY_SLAY
(11502, 9, 6), -- Ragnaros SAY_KILL
-- Sunwell Plateau
(24882, 6, 7), -- Brutallus YELL_KILL
(25038, 1, 7), -- Felmyst YELL_KILL
(24850, 1, 7), -- Kalecgos SAY_EVIL_SLAY
-- Tempest Keep: The Eye (additional)
(18805, 2, 7); -- High Astromancer Solarian SAY_KILL
@@ -0,0 +1,4 @@
-- DB update 2026_07_14_03 -> 2026_07_15_00
-- Missile Barrage: consume on FINISH instead of CAST; its mods (DURATION,
-- ACTIVATION_TIME, COST) are applied inside handle_immediate, after CAST fires.
UPDATE `spell_proc` SET `SpellPhaseMask` = 4 WHERE `SpellId` = 44401;
@@ -0,0 +1,7 @@
-- DB update 2026_07_15_00 -> 2026_07_15_01
-- Watcher Gashra's Enrage (52470) is a self-buff, but its -50% physical damage
-- done effect (EFFECT_2) makes the core classify the whole aura as negative,
-- so it shows as a debuff on the target frame.
-- Mark all three effects positive (SPELL_ATTR0_CU_POSITIVE_EFF0 | EFF1 | EFF2).
DELETE FROM `spell_custom_attr` WHERE `spell_id` = 52470;
INSERT INTO `spell_custom_attr` (`spell_id`, `attributes`) VALUES (52470, 0x0E000000);
@@ -0,0 +1,5 @@
-- DB update 2026_07_15_01 -> 2026_07_15_02
--
DELETE FROM `gossip_menu_option` WHERE `MenuID` = 6626 AND `OptionID` = 0;
INSERT INTO `gossip_menu_option` (`MenuID`, `OptionID`, `OptionIcon`, `OptionText`, `OptionBroadcastTextID`, `OptionType`, `OptionNpcFlag`, `ActionMenuID`, `ActionPoiID`, `BoxCoded`, `BoxMoney`, `BoxText`, `BoxBroadcastTextID`) VALUES
(6626, 0, 0, 'Please tell me the Phrase..', 0, 0, 0, 0, 0, 0, 0, '', 0);
@@ -0,0 +1,6 @@
-- DB update 2026_07_15_02 -> 2026_07_15_03
-- Yogg Portal 'Descend Into Madness'
-- Movement flags: DisableGravity + Root
DELETE FROM `creature_template_movement` WHERE (`CreatureId` = 34072);
INSERT INTO `creature_template_movement` (`CreatureId`, `Ground`, `Swim`, `Flight`, `Rooted`, `Chase`, `Random`, `InteractionPauseTimer`) VALUES
(34072, 0, 0, 1, 1, 0, 0, 0);
@@ -0,0 +1,8 @@
-- DB update 2026_07_15_03 -> 2026_07_15_04
-- Sara should start friendly, becoming hostile when Yogg-Saron reveals himself (phase 2)
UPDATE `creature_template` SET `faction` = 35 WHERE `entry` IN (33134, 34332);
-- Sara (1)
DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 13) AND (`SourceGroup` = 1) AND (`SourceEntry` = 65719) AND (`SourceId` = 0) AND (`ElseGroup` = 1) AND (`ConditionTypeOrReference` = 31) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 3) AND (`ConditionValue2` = 34332) AND (`ConditionValue3` = 0);
INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES
(13, 1, 65719, 0, 1, 31, 0, 3, 34332, 0, 0, 0, 0, '', 'Shadow Nova');
@@ -0,0 +1,2 @@
-- DB update 2026_07_15_04 -> 2026_07_16_00
UPDATE `quest_offer_reward` SET `RewardText` = 'You''ve been a tremendous help here today; we now have a solid chance at unlocking the mystery of why these creatures choose to beach themselves on the coast of Darkshore. The idea that they might be fleeing the area around Teldrassil is disturbing. Should you come across others in your travels, please let me know. Our research here is but in its infancy.$B$BOn behalf of the Temple of the Moon, please accept this as thanks for the effort you have given. Thank you, $N!' WHERE `ID` = 4681;
@@ -0,0 +1,8 @@
-- DB update 2026_07_16_00 -> 2026_07_16_01
-- Aerial Command Unit: hover height and flags to match TDB (hover-based chase)
-- 256 = UNIT_FLAG_IMMUNE_TO_PC (cleared by script at phase start), 512 = CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE
UPDATE `creature_template` SET `HoverHeight` = 15, `unit_flags` = `unit_flags`|256, `flags_extra` = `flags_extra`|512 WHERE `entry` IN (33670, 34109);
-- Values ported from TrinityCore (TrinityCore/TrinityCore@7c13b383); zeroed combat reach
-- made the hovering ACU unreachable for Magnetic Core's 15y check
UPDATE `creature_model_info` SET `BoundingRadius` = 0.31, `CombatReach` = 5 WHERE `DisplayID` = 28979;
+1 -1
View File
@@ -360,7 +360,7 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw) {\
// Bring in shared_ptr and weak_ptr
#if (defined(__GNUC__) && defined(__APPLE__)) || defined(__linux__)
#include <ciso646> // Defines _LIBCC_VERSION if linking against libc++ or does nothing
#include <version> // Defines _LIBCC_VERSION if linking against libc++ or does nothing
#endif
#if (!defined(_LIBCPP_VERSION) && defined(__APPLE__)) || (!defined(_LIBCPP_VERSION) && defined(__linux__))
# include <tr1/memory>
+5 -3
View File
@@ -13,11 +13,13 @@ This PR proposes changes to:
### AI-assisted Pull Requests
> [!IMPORTANT]
> While the use of AI tools when preparing pull requests is not prohibited, contributors must clearly disclose when such tools have been used and specify the model involved.
> Using AI tools to prepare pull requests is allowed, but it must be disclosed and it must follow our AC guidelines for AI Agentic Engineering (link below).
>
> Contributors are also expected to fully understand the changes they are submitting and must be able to explain and justify those changes when requested by maintainers.
> You are expected to fully understand the changes you submit and to be able to explain and justify them when maintainers ask.
- [ ] AI tools (e.g. ChatGPT, Claude, or similar) were used entirely or partially in preparing this pull request. Please specify which tools were used, if any.
- [ ] AI tools (e.g. Claude, ChatGPT, or similar) were used entirely or partially to prepare this pull request. If checked, specify which tools and models below.
- Tools/models used:
- [ ] I have read and understood the [AC guidelines for AI Agentic Engineering](https://www.azerothcore.org/wiki/agentic-engineering)
## Issues Addressed:
<!-- If your fix has a relating issue, link it below -->
+1 -1
View File
@@ -93,7 +93,7 @@ enum AccountFlag
ACCOUNT_FLAG_EXPANSION2_COLLECTOR = 0x4000000, // NYI WotLK Collector's Edition
ACCOUNT_FLAG_OVERMIND_LINKED = 0x8000000, // NYI Linked with Battle.net account
ACCOUNT_FLAG_DEMOS = 0x10000000, // NYI UNK
ACCOUNT_FLAG_DEATH_KNIGHT_OK = 0x20000000, // NYI Has level 55 on account?
ACCOUNT_FLAG_DEATH_KNIGHT_OK = 0x20000000, // Allowed to create Death Knight (account met heroic level requirement on WotLK+)
// Below might be StarCraft II related
ACCOUNT_FLAG_S2_REQUIRE_IGR = 0x40000000, // NYI UNK
ACCOUNT_FLAG_S2_TRIAL = 0x80000000, // NYI UNK
+4
View File
@@ -129,6 +129,10 @@ int main(int argc, char** argv)
std::shared_ptr<void> dbHandle(nullptr, [](void*) { StopDB(); });
// Mark every realm offline on startup; each worldserver clears this flag for its own realm once it is ready.
// This prevents realms from appearing online in the realm list when no worldserver is actually running.
LoginDatabase.DirectExecute("UPDATE realmlist SET flag = flag | {}", REALM_FLAG_OFFLINE);
std::shared_ptr<Acore::Asio::IoContext> ioContext = std::make_shared<Acore::Asio::IoContext>();
// Get the list of realms for the server
@@ -3978,10 +3978,35 @@ Battleground.SpeedBuffRespawn = 150
#
# Battleground.Override.LowLevels.MinPlayers
# Description: Overrides the minimum number of required players per team for all levels < MaxPlayerLevel
# Used as fallback for any battleground without a per-battleground override (see below)
# Default: 0 (Disabled)
Battleground.Override.LowLevels.MinPlayers = 0
#
# Battleground.Override.LowLevels.MinPlayers.AV
# Battleground.Override.LowLevels.MinPlayers.WS
# Battleground.Override.LowLevels.MinPlayers.AB
# Battleground.Override.LowLevels.MinPlayers.EY
# Battleground.Override.LowLevels.MinPlayers.SA
# Battleground.Override.LowLevels.MinPlayers.IC
# Description: Per-battleground overrides of the minimum number of required players per team
# for all levels < MaxPlayerLevel.
# AV = Alterac Valley, WS = Warsong Gulch, AB = Arathi Basin,
# EY = Eye of the Storm, SA = Strand of the Ancients, IC = Isle of Conquest
# Resolution order: per-battleground key, then Battleground.Override.LowLevels.MinPlayers,
# then the battleground_template DB value.
# Matches entered through the Random Battleground queue always use the global
# Battleground.Override.LowLevels.MinPlayers, never the per-battleground keys.
# Default: 0 (Disabled, use Battleground.Override.LowLevels.MinPlayers)
Battleground.Override.LowLevels.MinPlayers.AV = 0
Battleground.Override.LowLevels.MinPlayers.WS = 0
Battleground.Override.LowLevels.MinPlayers.AB = 0
Battleground.Override.LowLevels.MinPlayers.EY = 0
Battleground.Override.LowLevels.MinPlayers.SA = 0
Battleground.Override.LowLevels.MinPlayers.IC = 0
#
# Battleground.Warsong.Flags
# Description: Set the number of flags required for a team to win in Warsong battleground
@@ -92,6 +92,7 @@ void LoginDatabaseConnection::DoPrepareStatements()
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 = 1 WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_UPTIME_PLAYERS, "UPDATE uptime SET uptime = ?, maxplayers = ? WHERE realmid = ? AND starttime = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_REALM_ONLINE, "UPDATE realmlist SET flag = flag & ~? WHERE id = ?", 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);
PrepareStatement(LOGIN_DEL_ACCOUNT_ACCESS_BY_REALM, "DELETE FROM account_access WHERE id = ? AND (RealmID = ? OR RealmID = -1)", CONNECTION_ASYNC);
@@ -76,6 +76,7 @@ enum LoginDatabaseStatements : uint32
LOGIN_UPD_LAST_ATTEMPT_IP,
LOGIN_UPD_ACCOUNT_ONLINE,
LOGIN_UPD_UPTIME_PLAYERS,
LOGIN_UPD_REALM_ONLINE,
LOGIN_DEL_OLD_LOGS,
LOGIN_DEL_ACCOUNT_ACCESS,
LOGIN_DEL_ACCOUNT_ACCESS_BY_REALM,
@@ -27,6 +27,7 @@ void WorldDatabaseConnection::DoPrepareStatements()
PrepareStatement(WORLD_DEL_CRELINKED_RESPAWN, "DELETE FROM linked_respawn WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_REP_CREATURE_LINKED_RESPAWN, "REPLACE INTO linked_respawn (guid, linkedGuid) VALUES (?, ?)", CONNECTION_ASYNC);
PrepareStatement(WORLD_SEL_CREATURE_TEXT, "SELECT CreatureID, GroupID, ID, Text, Type, Language, Probability, Emote, Duration, Sound, BroadcastTextId, TextRange FROM creature_text", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_CREATURE_TEXT_OPTIONS, "SELECT o.CreatureID, o.GroupID, s.Cooldown, s.TriggerChance, s.PlayerOnly FROM creature_text_options o JOIN creature_text_option_sets s ON o.OptionSetID = s.SetID", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_SMART_SCRIPTS, "SELECT entryorguid, source_type, id, link, event_type, event_phase_mask, event_chance, event_flags, event_param1, event_param2, event_param3, event_param4, event_param5, event_param6, action_type, action_param1, action_param2, action_param3, action_param4, action_param5, action_param6, target_type, target_param1, target_param2, target_param3, target_param4, target_x, target_y, target_z, target_o FROM smart_scripts ORDER BY entryorguid, source_type, id, link", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_SMARTAI_WP, "SELECT entry, pointid, position_x, position_y, position_z, orientation, delay FROM waypoints ORDER BY entry, pointid", CONNECTION_SYNCH);
PrepareStatement(WORLD_DEL_GAMEOBJECT, "DELETE FROM gameobject WHERE guid = ?", CONNECTION_ASYNC);
@@ -32,6 +32,7 @@ enum WorldDatabaseStatements : uint32
WORLD_DEL_CRELINKED_RESPAWN,
WORLD_REP_CREATURE_LINKED_RESPAWN,
WORLD_SEL_CREATURE_TEXT,
WORLD_SEL_CREATURE_TEXT_OPTIONS,
WORLD_SEL_SMART_SCRIPTS,
WORLD_SEL_SMARTAI_WP,
WORLD_DEL_GAMEOBJECT,
+13
View File
@@ -687,6 +687,19 @@ enum RBACPermissions
RBAC_PERM_COMMAND_CHATFILTER_LIST = 923,
RBAC_PERM_COMMAND_CHATFILTER_ADD = 924,
RBAC_PERM_COMMAND_CHATFILTER_REMOVE = 925,
RBAC_PERM_COMMAND_AUTOBROADCAST_LIST = 926,
RBAC_PERM_COMMAND_AUTOBROADCAST_ADD = 927,
RBAC_PERM_COMMAND_AUTOBROADCAST_LOCALE = 928,
RBAC_PERM_COMMAND_AUTOBROADCAST_REMOVE = 929,
RBAC_PERM_COMMAND_MAIL_LIST = 930,
RBAC_PERM_COMMAND_MAIL_RETURN = 931,
RBAC_PERM_COMMAND_NPC_LOAD = 932,
RBAC_PERM_COMMAND_POOL_INFO = 933,
RBAC_PERM_COMMAND_POOL_LOOKUP = 934,
RBAC_PERM_COMMAND_SPELLINFO_ATTRIBUTES = 935,
RBAC_PERM_COMMAND_SPELLINFO_EFFECTS = 936,
RBAC_PERM_COMMAND_SPELLINFO_TARGETS = 937,
RBAC_PERM_COMMAND_SPELLINFO_ALL = 938,
// custom permissions 1000+
RBAC_PERM_MAX
};
+9 -48
View File
@@ -18,12 +18,8 @@
#include "Battleground.h"
#include "ArenaSpectator.h"
#include "ArenaTeam.h"
#include "BattlegroundBE.h"
#include "BattlegroundDS.h"
#include "BattlegroundMgr.h"
#include "BattlegroundNA.h"
#include "BattlegroundRL.h"
#include "BattlegroundRV.h"
#include "BattlegroundUtils.h"
#include "Chat.h"
#include "ChatTextBuilder.h"
#include "Creature.h"
@@ -247,6 +243,12 @@ Battleground::~Battleground()
delete itr.second;
}
uint32 Battleground::GetMinPlayersPerTeam() const
{
uint32 lowLevelsOverride = GetLowLevelsMinPlayersOverride(GetBgTypeID());
return (lowLevelsOverride && !isTemplate() && !isMaxLevel() && !isArena()) ? lowLevelsOverride : m_MinPlayersPerTeam;
}
void Battleground::Update(uint32 diff)
{
// pussywizard:
@@ -533,6 +535,8 @@ inline void Battleground::_ProcessJoin(uint32 diff)
// Mark setup as completed
m_SetupCompleted = true;
sScriptMgr->OnBattlegroundSetup(this);
}
// First announcement at 120s or 60s (Depending on BG or Arena and configured time)
@@ -558,33 +562,6 @@ inline void Battleground::_ProcessJoin(uint32 diff)
if (StartMessageIds[BG_STARTING_EVENT_THIRD])
SendBroadcastText(StartMessageIds[BG_STARTING_EVENT_THIRD], CHAT_MSG_BG_SYSTEM_NEUTRAL);
if (isArena())
switch (GetBgTypeID())
{
case BATTLEGROUND_NA:
DelObject(BG_NA_OBJECT_READY_MARKER_1);
DelObject(BG_NA_OBJECT_READY_MARKER_2);
break;
case BATTLEGROUND_BE:
DelObject(BG_BE_OBJECT_READY_MARKER_1);
DelObject(BG_BE_OBJECT_READY_MARKER_2);
break;
case BATTLEGROUND_RL:
DelObject(BG_RL_OBJECT_READY_MARKER_1);
DelObject(BG_RL_OBJECT_READY_MARKER_2);
break;
case BATTLEGROUND_DS:
DelObject(BG_DS_OBJECT_READY_MARKER_1);
DelObject(BG_DS_OBJECT_READY_MARKER_2);
break;
case BATTLEGROUND_RV:
DelObject(BG_RV_OBJECT_READY_MARKER_1);
DelObject(BG_RV_OBJECT_READY_MARKER_2);
break;
default:
break;
}
}
// Delay expired (after configured prep time)
else if (GetStartDelayTime() <= 0 && !(m_Events & BG_STARTING_EVENT_4))
@@ -1380,22 +1357,6 @@ void Battleground::SpectatorsSendPacket(WorldPacket& data)
(*itr)->SendDirectMessage(&data);
}
void Battleground::ReadyMarkerClicked(Player* p)
{
if (!isArena() || GetStatus() >= STATUS_IN_PROGRESS || GetStartDelayTime() <= BG_START_DELAY_15S || (m_Events & BG_STARTING_EVENT_3) || p->IsSpectator())
return;
readyMarkerClickedSet.insert(p->GetGUID());
uint32 count = readyMarkerClickedSet.size();
uint32 req = ArenaTeam::GetReqPlayersForType(GetArenaType());
ChatHandler(p->GetSession()).SendNotification("You are marked as ready {}/{}", count, req);
if (count == req)
{
m_Events |= BG_STARTING_EVENT_2;
m_StartTime += GetStartDelayTime() - BG_START_DELAY_15S;
SetStartDelayTime(BG_START_DELAY_15S);
}
}
void Battleground::BuildPvPLogDataPacket(WorldPacket& data)
{
uint8 type = (isArena() ? 1 : 0);
+4 -9
View File
@@ -284,7 +284,6 @@ enum BGHonorMode
};
#define ARENA_TIMELIMIT_POINTS_LOSS -16
#define ARENA_READY_MARKER_ENTRY 301337
/*
This class is used to:
@@ -349,11 +348,7 @@ public:
}
[[nodiscard]] uint32 GetMaxPlayersPerTeam() const { return m_MaxPlayersPerTeam; }
[[nodiscard]] uint32 GetMinPlayersPerTeam() const
{
auto lowLevelsOverride = sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS);
return (lowLevelsOverride && !isTemplate() && !isMaxLevel() && !isArena()) ? lowLevelsOverride : m_MinPlayersPerTeam;
}
[[nodiscard]] uint32 GetMinPlayersPerTeam() const;
[[nodiscard]] int32 GetStartDelayTime() const { return m_StartDelayTime; }
[[nodiscard]] uint8 GetArenaType() const { return m_ArenaType; }
@@ -388,6 +383,9 @@ public:
void ModifyStartDelayTime(int32 diff) { m_StartDelayTime -= diff; }
void SetStartDelayTime(int32 Time) { m_StartDelayTime = Time; }
[[nodiscard]] uint8 GetStartingEventFlags() const { return m_Events; }
void AddStartingEventFlag(uint8 flag) { m_Events |= flag; }
void SetMaxPlayersPerTeam(uint32 MaxPlayers) { m_MaxPlayersPerTeam = MaxPlayers; }
void SetMinPlayersPerTeam(uint32 MinPlayers) { m_MinPlayersPerTeam = MinPlayers; }
@@ -420,9 +418,6 @@ public:
[[nodiscard]] BattlegroundPlayerMap const& GetPlayers() const { return m_Players; }
[[nodiscard]] uint32 GetPlayersSize() const { return m_Players.size(); }
void ReadyMarkerClicked(Player* p); // pussywizard
GuidSet readyMarkerClickedSet; // pussywizard
typedef std::unordered_map<ObjectGuid::LowType, BattlegroundScore*> BattlegroundScoreMap;
typedef std::unordered_map<ObjectGuid, ArenaLogEntryData> ArenaLogEntryDataMap; // pussywizard
ArenaLogEntryDataMap ArenaLogEntries; // pussywizard
@@ -948,7 +948,7 @@ BGFreeSlotQueueContainer& BattlegroundMgr::GetBGFreeSlotQueueStore(BattlegroundT
void BattlegroundMgr::AddToBGFreeSlotQueue(BattlegroundTypeId bgTypeId, Battleground* bg)
{
bgDataStore[bgTypeId].BGFreeSlotQueue.push_front(bg);
bgDataStore[bgTypeId].BGFreeSlotQueue.push_back(bg);
}
void BattlegroundMgr::RemoveFromBGFreeSlotQueue(BattlegroundTypeId bgTypeId, uint32 instanceId)
@@ -805,8 +805,7 @@ void BattlegroundQueue::BattlegroundQueueUpdate(uint32 diff, BattlegroundTypeId
}
};
// battleground with free slot for player should be always in the beggining of the queue
// maybe it would be better to create bgfreeslotqueue for each bracket_id
// fill existing joinable battlegrounds, oldest first, before a new instance may form below
BGFreeSlotQueueContainer& bgQueues = sBattlegroundMgr->GetBGFreeSlotQueueStore(bgTypeId);
for (BGFreeSlotQueueContainer::iterator itr = bgQueues.begin(); itr != bgQueues.end();)
{
@@ -14,7 +14,23 @@ uint32 GetMinPlayersPerTeam(Battleground* bg, PvPDifficultyEntry const* bracketE
auto maxPlayerLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
auto isMaxLevel = bracketEntry->minLevel <= maxPlayerLevel && maxPlayerLevel <= bracketEntry->maxLevel;
auto lowLevelsOverride = sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS);
auto lowLevelsOverride = GetLowLevelsMinPlayersOverride(bg->GetBgTypeID());
return (lowLevelsOverride && !isMaxLevel) ? lowLevelsOverride : bg->GetMinPlayersPerTeam();
}
uint32 GetLowLevelsMinPlayersOverride(BattlegroundTypeId bgTypeId)
{
uint32 perBg = 0;
switch (bgTypeId)
{
case BATTLEGROUND_AV: perBg = sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_AV); break;
case BATTLEGROUND_WS: perBg = sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_WS); break;
case BATTLEGROUND_AB: perBg = sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_AB); break;
case BATTLEGROUND_EY: perBg = sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_EY); break;
case BATTLEGROUND_SA: perBg = sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_SA); break;
case BATTLEGROUND_IC: perBg = sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_IC); break;
default: break;
}
return perBg ? perBg : sWorld->getIntConfig(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS);
}
@@ -5,4 +5,10 @@
uint32 GetMinPlayersPerTeam(Battleground* bg, PvPDifficultyEntry const* bracketEntry);
// Effective low-levels MinPlayersPerTeam override for a BG type:
// the per-BG config if set, else the global one. 0 = no override.
// BGs entered through the Random BG queue have BATTLEGROUND_RB as
// their type, so they always resolve through the global key.
uint32 GetLowLevelsMinPlayersOverride(BattlegroundTypeId bgTypeId);
#endif // BATTLEGROUNDUTILS_H
@@ -104,10 +104,7 @@ bool BattlegroundBE::SetupBattleground()
|| !AddObject(BG_BE_OBJECT_DOOR_4, BG_BE_OBJECT_TYPE_DOOR_4, 6177.708f, 227.3481f, 3.604374f, -2.260201f, 0, 0, 0.9044551f, -0.4265689f, RESPAWN_IMMEDIATELY)
// buffs
|| !AddObject(BG_BE_OBJECT_BUFF_1, BG_BE_OBJECT_TYPE_BUFF_1, 6249.042f, 275.3239f, 11.22033f, -1.448624f, 0, 0, 0.6626201f, -0.7489557f, 120)
|| !AddObject(BG_BE_OBJECT_BUFF_2, BG_BE_OBJECT_TYPE_BUFF_2, 6228.26f, 249.566f, 11.21812f, -0.06981307f, 0, 0, 0.03489945f, -0.9993908f, 120)
// Arena Ready Marker
|| !AddObject(BG_BE_OBJECT_READY_MARKER_1, ARENA_READY_MARKER_ENTRY, 6189.47f, 235.54f, 5.52f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 300)
|| !AddObject(BG_BE_OBJECT_READY_MARKER_2, ARENA_READY_MARKER_ENTRY, 6287.19f, 288.25f, 5.33f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 300))
|| !AddObject(BG_BE_OBJECT_BUFF_2, BG_BE_OBJECT_TYPE_BUFF_2, 6228.26f, 249.566f, 11.21812f, -0.06981307f, 0, 0, 0.03489945f, -0.9993908f, 120))
{
LOG_ERROR("sql.sql", "BatteGroundBE: Failed to spawn some object!");
return false;
@@ -28,9 +28,7 @@ enum BattlegroundBEObjectTypes
BG_BE_OBJECT_DOOR_4 = 3,
BG_BE_OBJECT_BUFF_1 = 4,
BG_BE_OBJECT_BUFF_2 = 5,
BG_BE_OBJECT_READY_MARKER_1 = 6,
BG_BE_OBJECT_READY_MARKER_2 = 7,
BG_BE_OBJECT_MAX = 8
BG_BE_OBJECT_MAX = 6
};
enum BattlegroundBEGameObjects
@@ -195,10 +195,7 @@ bool BattlegroundDS::SetupBattleground()
// knockback creatures
|| !AddCreature(BG_DS_NPC_TYPE_WATER_SPOUT, BG_DS_NPC_WATERFALL_KNOCKBACK, 1291.76f, 791.02f, 7.115f, 3.054326f, RESPAWN_IMMEDIATELY)
|| !AddCreature(BG_DS_NPC_TYPE_WATER_SPOUT, BG_DS_NPC_PIPE_KNOCKBACK_1, 1369.977f, 817.2882f, 16.08718f, 3.106686f, RESPAWN_IMMEDIATELY)
|| !AddCreature(BG_DS_NPC_TYPE_WATER_SPOUT, BG_DS_NPC_PIPE_KNOCKBACK_2, 1212.833f, 765.3871f, 16.09484f, 0.0f, RESPAWN_IMMEDIATELY)
// Arena Ready Marker
|| !AddObject(BG_DS_OBJECT_READY_MARKER_1, ARENA_READY_MARKER_ENTRY, 1229.44f, 759.35f, 17.89f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 300)
|| !AddObject(BG_DS_OBJECT_READY_MARKER_2, ARENA_READY_MARKER_ENTRY, 1352.90f, 822.77f, 17.96f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 300))
|| !AddCreature(BG_DS_NPC_TYPE_WATER_SPOUT, BG_DS_NPC_PIPE_KNOCKBACK_2, 1212.833f, 765.3871f, 16.09484f, 0.0f, RESPAWN_IMMEDIATELY))
{
LOG_ERROR("sql.sql", "BatteGroundDS: Failed to spawn some object!");
return false;
@@ -30,9 +30,7 @@ enum BattlegroundDSObjectTypes
BG_DS_OBJECT_WATER_2 = 3,
BG_DS_OBJECT_BUFF_1 = 4,
BG_DS_OBJECT_BUFF_2 = 5,
BG_DS_OBJECT_READY_MARKER_1 = 6,
BG_DS_OBJECT_READY_MARKER_2 = 7,
BG_DS_OBJECT_MAX = 8
BG_DS_OBJECT_MAX = 6
};
enum BattlegroundDSGameObjects
@@ -80,10 +80,7 @@ bool BattlegroundNA::SetupBattleground()
|| !AddObject(BG_NA_OBJECT_DOOR_4, BG_NA_OBJECT_TYPE_DOOR_4, 4090.064f, 2858.438f, 10.23631f, 0.4928045f, 0, 0, 0.2439165f, 0.9697962f, RESPAWN_IMMEDIATELY)
// buffs
|| !AddObject(BG_NA_OBJECT_BUFF_1, BG_NA_OBJECT_TYPE_BUFF_1, 4009.189941f, 2895.250000f, 13.052700f, -1.448624f, 0, 0, 0.6626201f, -0.7489557f, 120)
|| !AddObject(BG_NA_OBJECT_BUFF_2, BG_NA_OBJECT_TYPE_BUFF_2, 4103.330078f, 2946.350098f, 13.051300f, -0.06981307f, 0, 0, 0.03489945f, -0.9993908f, 120)
// Arena Ready Marker
|| !AddObject(BG_NA_OBJECT_READY_MARKER_1, ARENA_READY_MARKER_ENTRY, 4090.46f, 2875.43f, 12.16f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 300)
|| !AddObject(BG_NA_OBJECT_READY_MARKER_2, ARENA_READY_MARKER_ENTRY, 4022.82f, 2966.61f, 12.17f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 300))
|| !AddObject(BG_NA_OBJECT_BUFF_2, BG_NA_OBJECT_TYPE_BUFF_2, 4103.330078f, 2946.350098f, 13.051300f, -0.06981307f, 0, 0, 0.03489945f, -0.9993908f, 120))
{
LOG_ERROR("sql.sql", "BatteGroundNA: Failed to spawn some object!");
return false;
@@ -28,9 +28,7 @@ enum BattlegroundNAObjectTypes
BG_NA_OBJECT_DOOR_4 = 3,
BG_NA_OBJECT_BUFF_1 = 4,
BG_NA_OBJECT_BUFF_2 = 5,
BG_NA_OBJECT_READY_MARKER_1 = 6,
BG_NA_OBJECT_READY_MARKER_2 = 7,
BG_NA_OBJECT_MAX = 8
BG_NA_OBJECT_MAX = 6
};
enum BattlegroundNAGameObjects
@@ -105,10 +105,7 @@ bool BattlegroundRL::SetupBattleground()
|| !AddObject(BG_RL_OBJECT_DOOR_2, BG_RL_OBJECT_TYPE_DOOR_2, 1278.648f, 1730.557f, 31.60557f, 1.684245f, 0, 0, 0.7460582f, 0.6658807f, RESPAWN_IMMEDIATELY)
// buffs
|| !AddObject(BG_RL_OBJECT_BUFF_1, BG_RL_OBJECT_TYPE_BUFF_1, 1328.719971f, 1632.719971f, 36.730400f, -1.448624f, 0, 0, 0.6626201f, -0.7489557f, 120)
|| !AddObject(BG_RL_OBJECT_BUFF_2, BG_RL_OBJECT_TYPE_BUFF_2, 1243.300049f, 1699.170044f, 34.872601f, -0.06981307f, 0, 0, 0.03489945f, -0.9993908f, 120)
// Arena Ready Marker
|| !AddObject(BG_RL_OBJECT_READY_MARKER_1, ARENA_READY_MARKER_ENTRY, 1298.61f, 1598.59f, 31.62f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 300)
|| !AddObject(BG_RL_OBJECT_READY_MARKER_2, ARENA_READY_MARKER_ENTRY, 1273.71f, 1734.05f, 31.61f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 300))
|| !AddObject(BG_RL_OBJECT_BUFF_2, BG_RL_OBJECT_TYPE_BUFF_2, 1243.300049f, 1699.170044f, 34.872601f, -0.06981307f, 0, 0, 0.03489945f, -0.9993908f, 120))
{
LOG_ERROR("sql.sql", "BatteGroundRL: Failed to spawn some object!");
return false;
@@ -26,9 +26,7 @@ enum BattlegroundRLObjectTypes
BG_RL_OBJECT_DOOR_2 = 1,
BG_RL_OBJECT_BUFF_1 = 2,
BG_RL_OBJECT_BUFF_2 = 3,
BG_RL_OBJECT_READY_MARKER_1 = 4,
BG_RL_OBJECT_READY_MARKER_2 = 5,
BG_RL_OBJECT_MAX = 6
BG_RL_OBJECT_MAX = 4
};
enum BattlegroundRLObjects
@@ -240,12 +240,7 @@ bool BattlegroundRV::SetupBattleground()
|| !AddObject(BG_RV_OBJECT_PILAR_1, BG_RV_OBJECT_TYPE_PILAR_1, 763.632385f, -306.162384f, 25.909504f, 3.141593f, 0, 0, 0, RESPAWN_IMMEDIATELY)
|| !AddObject(BG_RV_OBJECT_PILAR_2, BG_RV_OBJECT_TYPE_PILAR_2, 723.644287f, -284.493256f, 24.648525f, 3.141593f, 0, 0, 0, RESPAWN_IMMEDIATELY)
|| !AddObject(BG_RV_OBJECT_PILAR_3, BG_RV_OBJECT_TYPE_PILAR_3, 763.611145f, -261.856750f, 25.909504f, 0.000000f, 0, 0, 0, RESPAWN_IMMEDIATELY)
|| !AddObject(BG_RV_OBJECT_PILAR_4, BG_RV_OBJECT_TYPE_PILAR_4, 802.211609f, -284.493256f, 24.648525f, 0.000000f, 0, 0, 0, RESPAWN_IMMEDIATELY)
// Arena Ready Marker
|| !AddObject(BG_RV_OBJECT_READY_MARKER_1, ARENA_READY_MARKER_ENTRY, 769.93f, -301.04f, 2.80f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 300)
|| !AddObject(BG_RV_OBJECT_READY_MARKER_2, ARENA_READY_MARKER_ENTRY, 757.02f, -267.30f, 2.80f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 300)
)
|| !AddObject(BG_RV_OBJECT_PILAR_4, BG_RV_OBJECT_TYPE_PILAR_4, 802.211609f, -284.493256f, 24.648525f, 0.000000f, 0, 0, 0, RESPAWN_IMMEDIATELY))
{
LOG_ERROR("sql.sql", "BatteGroundRV: Failed to spawn some object!");
return false;
@@ -22,53 +22,50 @@
enum BattlegroundRVObjectTypes
{
BG_RV_OBJECT_BUFF_1,
BG_RV_OBJECT_BUFF_2,
BG_RV_OBJECT_FIRE_1,
BG_RV_OBJECT_FIRE_2,
BG_RV_OBJECT_FIREDOOR_1,
BG_RV_OBJECT_FIREDOOR_2,
BG_RV_OBJECT_BUFF_1 = 0,
BG_RV_OBJECT_BUFF_2 = 1,
BG_RV_OBJECT_FIRE_1 = 2,
BG_RV_OBJECT_FIRE_2 = 3,
BG_RV_OBJECT_FIREDOOR_1 = 4,
BG_RV_OBJECT_FIREDOOR_2 = 5,
BG_RV_OBJECT_PILAR_1,
BG_RV_OBJECT_PILAR_3,
BG_RV_OBJECT_GEAR_1,
BG_RV_OBJECT_GEAR_2,
BG_RV_OBJECT_PILAR_1 = 6,
BG_RV_OBJECT_PILAR_3 = 7,
BG_RV_OBJECT_GEAR_1 = 8,
BG_RV_OBJECT_GEAR_2 = 9,
BG_RV_OBJECT_PILAR_2,
BG_RV_OBJECT_PILAR_4,
BG_RV_OBJECT_PULLEY_1,
BG_RV_OBJECT_PULLEY_2,
BG_RV_OBJECT_PILAR_2 = 10,
BG_RV_OBJECT_PILAR_4 = 11,
BG_RV_OBJECT_PULLEY_1 = 12,
BG_RV_OBJECT_PULLEY_2 = 13,
BG_RV_OBJECT_ELEVATOR_1,
BG_RV_OBJECT_ELEVATOR_2,
BG_RV_OBJECT_ELEVATOR_1 = 14,
BG_RV_OBJECT_ELEVATOR_2 = 15,
BG_RV_OBJECT_READY_MARKER_1,
BG_RV_OBJECT_READY_MARKER_2,
BG_RV_OBJECT_MAX,
BG_RV_OBJECT_MAX = 16
};
enum BattlegroundRVGameObjects
{
BG_RV_OBJECT_TYPE_BUFF_1 = 184663,
BG_RV_OBJECT_TYPE_BUFF_2 = 184664,
BG_RV_OBJECT_TYPE_FIRE_1 = 192704,
BG_RV_OBJECT_TYPE_FIRE_2 = 192705,
BG_RV_OBJECT_TYPE_BUFF_1 = 184663,
BG_RV_OBJECT_TYPE_BUFF_2 = 184664,
BG_RV_OBJECT_TYPE_FIRE_1 = 192704,
BG_RV_OBJECT_TYPE_FIRE_2 = 192705,
BG_RV_OBJECT_TYPE_FIREDOOR_2 = 192387,
BG_RV_OBJECT_TYPE_FIREDOOR_1 = 192388,
BG_RV_OBJECT_TYPE_PULLEY_1 = 192389,
BG_RV_OBJECT_TYPE_PULLEY_2 = 192390,
BG_RV_OBJECT_TYPE_GEAR_1 = 192393,
BG_RV_OBJECT_TYPE_GEAR_2 = 192394,
BG_RV_OBJECT_TYPE_FIREDOOR_2 = 192387,
BG_RV_OBJECT_TYPE_FIREDOOR_1 = 192388,
BG_RV_OBJECT_TYPE_PULLEY_1 = 192389,
BG_RV_OBJECT_TYPE_PULLEY_2 = 192390,
BG_RV_OBJECT_TYPE_GEAR_1 = 192393,
BG_RV_OBJECT_TYPE_GEAR_2 = 192394,
BG_RV_OBJECT_TYPE_ELEVATOR_1 = 194582,
BG_RV_OBJECT_TYPE_ELEVATOR_2 = 194586,
BG_RV_OBJECT_TYPE_ELEVATOR_1 = 194582,
BG_RV_OBJECT_TYPE_ELEVATOR_2 = 194586,
BG_RV_OBJECT_TYPE_PILAR_1 = 194583, // axe
BG_RV_OBJECT_TYPE_PILAR_2 = 194584, // arena
BG_RV_OBJECT_TYPE_PILAR_3 = 194585, // lightning
BG_RV_OBJECT_TYPE_PILAR_4 = 194587, // ivory
BG_RV_OBJECT_TYPE_PILAR_1 = 194583, // axe
BG_RV_OBJECT_TYPE_PILAR_2 = 194584, // arena
BG_RV_OBJECT_TYPE_PILAR_3 = 194585, // lightning
BG_RV_OBJECT_TYPE_PILAR_4 = 194587, // ivory
};
enum BattlegroundRVData
+1 -1
View File
@@ -483,7 +483,7 @@ uint32 CalendarMgr::GetPlayerNumPending(ObjectGuid guid)
std::string CalendarEvent::BuildCalendarMailSubject(ObjectGuid remover) const
{
std::ostringstream strm;
strm << remover.ToString() << ':' << _title;
strm << remover.GetRawValue() << ':' << _title;
return strm.str();
}
@@ -3817,6 +3817,21 @@ void Creature::ClearTextRepeatGroup(uint8 textGroup)
groupItr->second.clear();
}
bool Creature::IsTextOnCooldown(uint8 textGroup) const
{
auto itr = m_textCooldowns.find(textGroup);
if (itr == m_textCooldowns.end())
return false;
return GameTime::GetGameTime().count() < itr->second;
}
void Creature::SetTextCooldown(uint8 textGroup, uint32 cooldownMs)
{
m_textCooldowns[textGroup] =
GameTime::GetGameTime().count() + ((cooldownMs + 999) / 1000);
}
void Creature::SetRespawnTime(uint32 respawn)
{
m_respawnTime = respawn ? GameTime::GetGameTime().count() + respawn : 0;
@@ -411,6 +411,9 @@ public:
void SetTextRepeatId(uint8 textGroup, uint8 id);
void ClearTextRepeatGroup(uint8 textGroup);
bool IsTextOnCooldown(uint8 textGroup) const;
void SetTextCooldown(uint8 textGroup, uint32 cooldownMs);
bool IsFreeToMove();
static constexpr uint32 MOVE_CIRCLE_CHECK_INTERVAL = 3000;
static constexpr uint32 MOVE_BACKWARDS_CHECK_INTERVAL = 2000;
@@ -548,6 +551,7 @@ private:
ObjectGuid _spellFocusTarget; ///> Saved target during spell focus for restoration
CreatureTextRepeatGroup m_textRepeat;
std::unordered_map<uint8, uint32> m_textCooldowns; // groupID -> expiry time (s)
bool _isMissingSwimmingFlagOutOfCombat;
@@ -1846,14 +1846,19 @@ void GameObject::Use(Unit* user)
if (GetUniqueUseCount() == info->summoningRitual.reqParticipants)
return;
SpellCastResult castResult;
if (info->summoningRitual.animSpell)
player->CastSpell(player, info->summoningRitual.animSpell, true);
castResult = player->CastSpell(player, info->summoningRitual.animSpell, true);
else
player->CastSpell(player, GetSpellId(),
castResult = player->CastSpell(player, GetSpellId(),
TriggerCastFlags(TRIGGERED_IGNORE_EFFECTS
| TRIGGERED_IGNORE_SPELL_AND_CATEGORY_CD
| TRIGGERED_IGNORE_POWER_AND_REAGENT_COST
| TRIGGERED_CAST_DIRECTLY));
if (castResult != SPELL_CAST_OK)
return;
AddUniqueUse(player);
// full amount unique participants including original summoner
@@ -11682,6 +11682,12 @@ void Player::SendInitialPacketsAfterAddToMap()
auraList.front()->HandleEffect(this, AURA_EFFECT_HANDLE_SEND_FOR_CLIENT, true);
}
// Explicitly synchronize CAN_FLY state with client on login to prevent
// players from retaining flight ability after disconnecting during a
// teleport from a flyable to non-flyable zone (e.g. entering an instance).
if (!HasIncreaseMountedFlightSpeedAura() && !HasFlyAura())
SetCanFly(false);
// Fix mount, update block gets messed somewhere
{
if (!isBeingLoaded() && GetMountBlockId() && !HasMountedAura())
@@ -1888,6 +1888,10 @@ InventoryResult Player::CanEquipNewItem(uint8 slot, uint16& dest, uint32 item, b
if (pItem)
{
InventoryResult result = CanEquipItem(slot, dest, pItem, swap);
// Random-property items queue themselves on creation (SetItemRandomProperties
// -> SetState(ITEM_CHANGED, owner)); the probe must leave the update queue
// before deletion or the queue keeps a pointer to freed memory.
pItem->RemoveFromUpdateQueueOf(const_cast<Player*>(this));
delete pItem;
return result;
}
+33 -27
View File
@@ -384,8 +384,6 @@ Unit::Unit() : WorldObject(),
for (uint8 i = 0; i < MAX_GAMEOBJECT_SLOT; ++i)
m_ObjectSlot[i].Clear();
m_auraUpdateIterator = m_ownedAuras.end();
m_interruptMask = 0;
m_transform = 0;
m_canModifyStats = false;
@@ -4034,13 +4032,16 @@ void Unit::_UpdateSpells(uint32 time)
}
}
// m_auraUpdateIterator can be updated in indirect called code at aura remove to skip next planned to update but removed auras
for (m_auraUpdateIterator = m_ownedAuras.begin(); m_auraUpdateIterator != m_ownedAuras.end();)
{
Aura* i_aura = m_auraUpdateIterator->second;
++m_auraUpdateIterator; // need shift to next for allow update if need into aura update
i_aura->UpdateOwner(time, this);
}
// snapshot - UpdateOwner can mutate the map; pointers stay valid (removed auras
// are deleted later in _DeleteRemovedAuras)
m_auraUpdateSnapshot.clear();
m_auraUpdateSnapshot.reserve(m_ownedAuras.size());
for (auto& [id, aura] : m_ownedAuras)
m_auraUpdateSnapshot.push_back(aura);
for (Aura* aura : m_auraUpdateSnapshot)
if (!aura->IsRemoved())
aura->UpdateOwner(time, this);
// remove expired auras - do that after updates(used in scripts?)
for (AuraMap::iterator i = m_ownedAuras.begin(); i != m_ownedAuras.end();)
@@ -4986,10 +4987,6 @@ void Unit::RemoveOwnedAura(AuraMap::iterator& i, AuraRemoveMode removeMode)
Aura* aura = i->second;
ASSERT(!aura->IsRemoved());
// if unit currently update aura list then make safe update iterator shift to next
if (m_auraUpdateIterator == i && m_auraUpdateIterator != m_ownedAuras.end())
++m_auraUpdateIterator;
m_ownedAuras.erase(i);
m_removedAuras.push_back(aura);
@@ -5063,6 +5060,9 @@ void Unit::RemoveAura(AuraApplicationMap::iterator& i, AuraRemoveMode mode)
// Remove aura - for Area and Target auras
if (aura->GetOwner() == this)
aura->Remove(mode);
// Aura::Remove can cascade into m_appliedAuras mutations - reset again for the caller
i = m_appliedAuras.begin();
}
void Unit::RemoveAura(uint32 spellId, ObjectGuid caster, uint8 reqEffMask, AuraRemoveMode removeMode)
@@ -5150,7 +5150,8 @@ void Unit::RemoveAppliedAuras(std::function<bool(AuraApplication const*)> const&
{
for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();)
{
if (check(iter->second))
// RemoveAura no-ops on applications already mid-removal
if (!iter->second->GetRemoveMode() && check(iter->second))
{
RemoveAura(iter);
continue;
@@ -5176,9 +5177,11 @@ void Unit::RemoveAppliedAuras(uint32 spellId, std::function<bool(AuraApplication
{
for (AuraApplicationMap::iterator iter = m_appliedAuras.lower_bound(spellId); iter != m_appliedAuras.upper_bound(spellId);)
{
if (check(iter->second))
// RemoveAura no-ops on applications already mid-removal
if (!iter->second->GetRemoveMode() && check(iter->second))
{
RemoveAura(iter);
iter = m_appliedAuras.lower_bound(spellId);
continue;
}
++iter;
@@ -7428,24 +7431,27 @@ bool Unit::Attack(Unit* victim, bool meleeAttack)
else if (victim->IsCreature())
victim->ToCreature()->UpdateLeashExtensionTime();
// set position before any AI calls/assistance
//if (IsCreature())
// ToCreature()->SetCombatStartPosition(GetPositionX(), GetPositionY(), GetPositionZ());
// player-controlled creatures (pets, charms) enter combat on contact instead
// (melee swing execution or spell launch/hit, see Unit::AtTargetAttacked)
if (creature && !IsControlledByPlayer())
// Player-controlled creatures (pets, charms) enter combat on contact instead
// (melee swing execution or spell launch/hit, see Unit::AtTargetAttacked).
// Non-controllable guardians (e.g. Shaman Elementals, Infernal) have no attack
// command and must engage immediately so their AI can chase and attack.
if (creature && (!IsControlledByPlayer() || (IsGuardian() && !IsControllableGuardian())))
{
EngageWithTarget(victim);
creature->SendAIReaction(AI_REACTION_HOSTILE);
if (!IsControlledByPlayer())
{
creature->SendAIReaction(AI_REACTION_HOSTILE);
/// @todo: Implement aggro range, detection range and assistance range templates
if (!(creature->HasFlagsExtra(CREATURE_FLAG_EXTRA_DONT_CALL_ASSISTANCE)))
creature->CallAssistance();
/// @todo: Implement aggro range, detection range and assistance range templates
if (!(creature->HasFlagsExtra(CREATURE_FLAG_EXTRA_DONT_CALL_ASSISTANCE)))
creature->CallAssistance();
creature->SetAssistanceTimer(sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_PERIOD));
creature->SetAssistanceTimer(sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_PERIOD));
SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE);
SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE);
}
}
// delay offhand weapon attack by 50% of the base attack time
+5 -4
View File
@@ -31,6 +31,7 @@
#include "ThreatManager.h"
#include "UnitDefines.h"
#include "UnitUtils.h"
#include <boost/container/flat_map.hpp>
#include <functional>
#include <utility>
@@ -666,15 +667,15 @@ public:
typedef std::unordered_set<Unit*> AttackerSet;
typedef std::set<Unit*> ControlSet;
typedef std::multimap<uint32, Aura*> AuraMap;
typedef boost::container::flat_multimap<uint32, Aura*> AuraMap;
typedef std::pair<AuraMap::const_iterator, AuraMap::const_iterator> AuraMapBounds;
typedef std::pair<AuraMap::iterator, AuraMap::iterator> AuraMapBoundsNonConst;
typedef std::multimap<uint32, AuraApplication*> AuraApplicationMap;
typedef boost::container::flat_multimap<uint32, AuraApplication*> AuraApplicationMap;
typedef std::pair<AuraApplicationMap::const_iterator, AuraApplicationMap::const_iterator> AuraApplicationMapBounds;
typedef std::pair<AuraApplicationMap::iterator, AuraApplicationMap::iterator> AuraApplicationMapBoundsNonConst;
typedef std::multimap<AuraStateType, AuraApplication*> AuraStateAurasMap;
typedef boost::container::flat_multimap<AuraStateType, AuraApplication*> AuraStateAurasMap;
typedef std::pair<AuraStateAurasMap::const_iterator, AuraStateAurasMap::const_iterator> AuraStateAurasMapBounds;
typedef std::vector<AuraEffect*> AuraEffectList;
@@ -2166,7 +2167,7 @@ protected:
AuraMap m_ownedAuras;
AuraApplicationMap m_appliedAuras;
AuraList m_removedAuras;
AuraMap::iterator m_auraUpdateIterator;
std::vector<Aura*> m_auraUpdateSnapshot; // _UpdateSpells scratch buffer
uint32 m_removedAurasCount;
AuraEffectList m_modAuras[TOTAL_AURAS];
+11
View File
@@ -1960,6 +1960,17 @@ void GameEventMgr::SetHolidayEventTime(GameEventData& event)
time_t curTime = GameTime::GetGameTime().count();
if (holiday->Looping)
{
// Looping events (Battleground Call to Arms) carry a single past anchor in the DBC and
// recur via event.Occurence. Anchor to that DBC date so the window keeps the correct phase
// instead of inheriting the wrong time of day from a stale game_event.start_time.
if (time_t start = HolidayDateCalculator::FindLoopingStartTime(holiday->Date[0], stageOffset, event.Occurence, curTime))
event.Start = start;
return;
}
if (!singleDate)
{
time_t start = HolidayDateCalculator::FindStartTimeForStage(
@@ -671,3 +671,28 @@ time_t HolidayDateCalculator::FindStartTimeForStage(const uint32_t* packedDates,
return 0;
}
time_t HolidayDateCalculator::FindLoopingStartTime(uint32_t packedAnchor, time_t stageOffset,
uint32_t occurenceMinutes, time_t curTime)
{
if (!packedAnchor)
return 0;
std::tm timeInfo = {};
timeInfo.tm_year = static_cast<int>((packedAnchor >> 24) & 0x1F) + 100; // years since 1900 (2000-2031)
timeInfo.tm_mon = static_cast<int>((packedAnchor >> 20) & 0xF);
timeInfo.tm_mday = static_cast<int>((packedAnchor >> 14) & 0x3F) + 1;
timeInfo.tm_hour = static_cast<int>((packedAnchor >> 6) & 0x1F);
timeInfo.tm_min = static_cast<int>(packedAnchor & 0x3F);
timeInfo.tm_sec = 0;
timeInfo.tm_isdst = -1;
time_t anchor = mktime(&timeInfo) + stageOffset;
// Roll forward by whole periods to the most recent occurrence, preserving phase.
time_t const period = static_cast<time_t>(occurenceMinutes) * 60;
if (period > 0 && anchor < curTime)
anchor += ((curTime - anchor) / period) * period;
return anchor;
}
@@ -107,6 +107,11 @@ public:
static time_t FindStartTimeForStage(const uint32_t* packedDates, uint8_t numDates,
time_t stageOffset, uint32_t stageLengthMinutes, time_t curTime);
// Start time for a looping holiday event (Battleground Call to Arms): rolls the packed anchor
// forward by whole occurenceMinutes periods to the most recent occurrence. Returns 0 if anchor is 0.
static time_t FindLoopingStartTime(uint32_t packedAnchor, time_t stageOffset,
uint32_t occurenceMinutes, time_t curTime);
private:
// Julian Date conversions for lunar calculations
static double DateToJulianDay(int year, int month, double day);
@@ -554,6 +554,8 @@ void WorldSession::HandleBattleFieldPortOpcode(WorldPacket& recvData)
// leaks forever, permanently skewing team selection and blocking the
// empty instance's cleanup.
bg->DecreaseInvitedCount(teamId);
if (bg->HasFreeSlots())
bg->AddToBGFreeSlotQueue();
_player->RemoveBattlegroundQueueId(bgQueueTypeId);
_player->SetBattlegroundId(0, BATTLEGROUND_TYPE_NONE, PLAYER_MAX_BATTLEGROUND_QUEUES, false, false, TEAM_NEUTRAL);
+13 -5
View File
@@ -368,7 +368,8 @@ void WorldSession::HandleCharCreateOpcode(WorldPacket& recvData)
// speedup check for heroic class disabled case
uint32 req_level_for_heroic = sWorld->getIntConfig(CONFIG_CHARACTER_CREATING_MIN_LEVEL_FOR_HEROIC_CHARACTER);
if (!HasPermission(rbac::RBAC_PERM_SKIP_CHECK_CHARACTER_CREATION_DEATH_KNIGHT) && createInfo->Class == CLASS_DEATH_KNIGHT && req_level_for_heroic > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
if (!HasPermission(rbac::RBAC_PERM_SKIP_CHECK_CHARACTER_CREATION_DEATH_KNIGHT) && createInfo->Class == CLASS_DEATH_KNIGHT
&& req_level_for_heroic > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) && !HasAccountFlag(ACCOUNT_FLAG_DEATH_KNIGHT_OK))
{
SendCharCreate(CHAR_CREATE_LEVEL_REQUIREMENT);
return;
@@ -435,7 +436,8 @@ void WorldSession::HandleCharCreateOpcode(WorldPacket& recvData)
}
bool haveSameRace = false;
uint32 heroicReqLevel = sWorld->getIntConfig(CONFIG_CHARACTER_CREATING_MIN_LEVEL_FOR_HEROIC_CHARACTER);
bool hasHeroicReqLevel = (heroicReqLevel == 0);
bool const deathKnightFlagAlreadySet = HasAccountFlag(ACCOUNT_FLAG_DEATH_KNIGHT_OK); // Account flag is superior to level requirement.
bool hasHeroicReqLevel = (heroicReqLevel == 0) || deathKnightFlagAlreadySet;
bool allowTwoSideAccounts = !sWorld->IsPvPRealm() || sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_ACCOUNTS) || HasPermission(rbac::RBAC_PERM_TWO_SIDE_CHARACTER_CREATION);
uint32 skipCinematics = sWorld->getIntConfig(CONFIG_SKIP_CINEMATICS);
bool checkDeathKnightReqs = !HasPermission(rbac::RBAC_PERM_SKIP_CHECK_CHARACTER_CREATION_DEATH_KNIGHT) && createInfo->Class == CLASS_DEATH_KNIGHT;
@@ -524,10 +526,16 @@ void WorldSession::HandleCharCreateOpcode(WorldPacket& recvData)
}
}
if (checkDeathKnightReqs && !hasHeroicReqLevel)
if (checkDeathKnightReqs)
{
SendCharCreate(CHAR_CREATE_LEVEL_REQUIREMENT);
return;
if (!deathKnightFlagAlreadySet && hasHeroicReqLevel)
UpdateAccountFlag(ACCOUNT_FLAG_DEATH_KNIGHT_OK);
if (!hasHeroicReqLevel)
{
SendCharCreate(CHAR_CREATE_LEVEL_REQUIREMENT);
return;
}
}
// Check name uniqueness in the same step as saving to database
-1
View File
@@ -21,7 +21,6 @@
#include "Creature.h"
#include "DatabaseEnv.h"
#include "GameGraveyard.h"
#include "Language.h"
#include "NPCPackets.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
+7 -4
View File
@@ -398,6 +398,8 @@ bool InstanceScript::SetBossState(uint32 id, EncounterState state)
if (id < bosses.size())
{
BossInfo* bossInfo = &bosses[id];
MinionSet minions = bossInfo->minion;
sScriptMgr->OnBeforeSetBossState(id, state, bossInfo->state, instance);
if (bossInfo->state == TO_BE_DECIDED) // loading
{
@@ -410,8 +412,8 @@ bool InstanceScript::SetBossState(uint32 id, EncounterState state)
return false;
if (state == DONE)
for (MinionSet::iterator i = bossInfo->minion.begin(); i != bossInfo->minion.end(); ++i)
if ((*i)->isWorldBoss() && (*i)->IsAlive())
for (Creature* minion : minions)
if (minion && minion->isWorldBoss() && minion->IsAlive())
return false;
bossInfo->state = state;
@@ -422,8 +424,9 @@ bool InstanceScript::SetBossState(uint32 id, EncounterState state)
for (DoorSet::iterator i = bossInfo->door[type].begin(); i != bossInfo->door[type].end(); ++i)
UpdateDoorState(*i);
for (MinionSet::iterator i = bossInfo->minion.begin(); i != bossInfo->minion.end(); ++i)
UpdateMinionState(*i, state);
for (Creature* minion : minions)
if (minion)
UpdateMinionState(minion, state);
return true;
}
@@ -104,6 +104,11 @@ void ScriptMgr::OnBattlegroundCreate(Battleground* bg)
CALL_ENABLED_HOOKS(AllBattlegroundScript, ALLBATTLEGROUNDHOOK_ON_BATTLEGROUND_CREATE, script->OnBattlegroundCreate(bg));
}
void ScriptMgr::OnBattlegroundSetup(Battleground* bg)
{
CALL_ENABLED_HOOKS(AllBattlegroundScript, ALLBATTLEGROUNDHOOK_ON_BATTLEGROUND_SETUP, script->OnBattlegroundSetup(bg));
}
bool ScriptMgr::CanAddGroupToMatchingPool(BattlegroundQueue* queue, GroupQueueInfo* group, uint32 poolPlayerCount, Battleground* bg, BattlegroundBracketId bracketId)
{
CALL_ENABLED_BOOLEAN_HOOKS(AllBattlegroundScript, ALLBATTLEGROUNDHOOK_CAN_ADD_GROUP_TO_MATCHING_POOL, !script->CanAddGroupToMatchingPool(queue, group, poolPlayerCount, bg, bracketId));
@@ -41,6 +41,7 @@ enum AllBattlegroundHook
ALLBATTLEGROUNDHOOK_ON_BATTLEGROUND_END,
ALLBATTLEGROUNDHOOK_ON_BATTLEGROUND_DESTROY,
ALLBATTLEGROUNDHOOK_ON_BATTLEGROUND_CREATE,
ALLBATTLEGROUNDHOOK_ON_BATTLEGROUND_SETUP,
ALLBATTLEGROUNDHOOK_CAN_ADD_GROUP_TO_MATCHING_POOL,
ALLBATTLEGROUNDHOOK_GET_PLAYER_MATCHMAKING_RATING,
ALLBATTLEGROUNDHOOK_END
@@ -139,6 +140,13 @@ public:
*/
virtual void OnBattlegroundCreate(Battleground* /*bg*/) { }
/**
* @brief This hook runs after the Battleground setup is completed (map is created and initial objects are spawned)
*
* @param bg Contains information about the Battleground
*/
virtual void OnBattlegroundSetup(Battleground* /*bg*/) { }
/**
* @brief This hook runs before adding a group to the battleground matching pool
*
+1
View File
@@ -634,6 +634,7 @@ public: /* BGScript */
void OnBattlegroundEnd(Battleground* bg, TeamId winnerTeamId);
void OnBattlegroundDestroy(Battleground* bg);
void OnBattlegroundCreate(Battleground* bg);
void OnBattlegroundSetup(Battleground* bg);
bool CanAddGroupToMatchingPool(BattlegroundQueue* queue, GroupQueueInfo* group, uint32 poolPlayerCount, Battleground* bg, BattlegroundBracketId bracketId);
bool GetPlayerMatchmakingRating(ObjectGuid playerGuid, BattlegroundTypeId bgTypeId, float& outRating);
@@ -2989,7 +2989,7 @@ void AuraEffect::HandleFeignDeath(AuraApplication const* aurApp, uint8 mode, boo
for (auto& pair : target->GetThreatMgr().GetThreatenedByMeList())
pair.second->ScaleThreat(0.0f);
if (target->GetMap()->IsDungeon()) // feign death does not remove combat in dungeons
if (target->GetInstanceScript() && target->GetInstanceScript()->IsEncounterInProgress())
{
target->AttackStop();
if (Player* targetPlayer = target->ToPlayer())
@@ -201,6 +201,11 @@ void AuraApplication::BuildUpdatePacket(ByteBuffer& data, bool remove) const
uint32 flags = _flags;
if (aura->GetMaxDuration() > 0 && !aura->GetSpellInfo()->HasAttribute(SPELL_ATTR5_DO_NOT_DISPLAY_DURATION))
flags |= AFLAG_DURATION;
// paladin auras are positive for self-cast only
if (!IsSelfcasted() && aura->GetSpellInfo()->GetSpellSpecific() == SPELL_SPECIFIC_AURA)
flags &= ~AFLAG_POSITIVE;
data << uint8(flags);
data << uint8(aura->GetCasterLevel());
// send stack amount for aura which could be stacked (never 0 - causes incorrect display) or charges
+38 -40
View File
@@ -3951,6 +3951,44 @@ void Spell::_cast(bool skipCheck)
}
}
// CAST -> HIT -> FINISH ordering: fire CAST before handle_immediate so an aura
// applied during HIT (e.g. Arcane Potency from Clearcasting) isn't consumed by
// the same cast. Triggered spells skip this so periodic ticks (Blizzard etc.)
// don't burn cast-charge buffs.
if (m_originalCaster && !IsTriggered())
{
uint32 procAttacker = m_procAttacker;
if (!procAttacker)
{
bool IsPositive = m_spellInfo->IsPositive();
if (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MAGIC)
{
procAttacker = IsPositive ? PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS : PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG;
}
else
{
procAttacker = IsPositive ? PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS : PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG;
}
}
uint32 hitMask = PROC_HIT_NORMAL;
for (std::list<TargetInfo>::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit)
{
if (ihit->missCondition != SPELL_MISS_NONE)
continue;
if (!ihit->crit)
continue;
hitMask |= PROC_HIT_CRITICAL;
break;
}
Unit::ProcSkillsAndAuras(m_originalCaster, nullptr, procAttacker, PROC_FLAG_NONE, hitMask, 1, BASE_ATTACK, m_spellInfo, m_triggeredByAuraSpell.spellInfo,
m_triggeredByAuraSpell.effectIndex, this, nullptr, nullptr, PROC_SPELL_PHASE_CAST);
}
// Okay, everything is prepared. Now we need to distinguish between immediate and evented delayed spells
if ((m_spellInfo->Speed > 0.0f && !m_spellInfo->IsChanneled())/* xinef: we dont need this || m_spellInfo->Id == 14157*/)
{
@@ -4008,46 +4046,6 @@ void Spell::_cast(bool skipCheck)
if (modOwner)
modOwner->SetSpellModTakingSpell(this, false);
// Handle procs on cast - only for non-triggered spells
// Triggered spells (from auras, items, etc.) should not fire CAST phase procs
// as they are not player-initiated casts. This prevents issues like Arcane Potency
// charges being consumed by periodic damage effects (e.g., Blizzard ticks).
// Must be called AFTER handle_immediate() so spell mods (like Missile Barrage's
// duration reduction) are applied before the aura is consumed by the proc.
if (m_originalCaster && !IsTriggered())
{
uint32 procAttacker = m_procAttacker;
if (!procAttacker)
{
bool IsPositive = m_spellInfo->IsPositive();
if (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MAGIC)
{
procAttacker = IsPositive ? PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS : PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG;
}
else
{
procAttacker = IsPositive ? PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS : PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG;
}
}
uint32 hitMask = PROC_HIT_NORMAL;
for (std::list<TargetInfo>::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit)
{
if (ihit->missCondition != SPELL_MISS_NONE)
continue;
if (!ihit->crit)
continue;
hitMask |= PROC_HIT_CRITICAL;
break;
}
Unit::ProcSkillsAndAuras(m_originalCaster, nullptr, procAttacker, PROC_FLAG_NONE, hitMask, 1, BASE_ATTACK, m_spellInfo, m_triggeredByAuraSpell.spellInfo,
m_triggeredByAuraSpell.effectIndex, this, nullptr, nullptr, PROC_SPELL_PHASE_CAST);
}
if (std::vector<int32> const* spell_triggered = sSpellMgr->GetSpellLinked(m_spellInfo->Id))
{
for (int32 id : *spell_triggered)
@@ -2057,12 +2057,6 @@ void SpellMgr::LoadSpellInfoCorrections()
spellInfo->AttributesEx6 |= SPELL_ATTR6_IGNORE_PHASE_SHIFT;
});
// Shadow Nova
ApplySpellFix({ 62714, 65209 }, [](SpellInfo* spellInfo)
{
spellInfo->AttributesEx3 |= SPELL_ATTR3_ALWAYS_HIT;
});
// Cosmic Smash (Algalon the Observer)
ApplySpellFix({ 62293 }, [](SpellInfo* spellInfo)
{
@@ -5345,6 +5339,13 @@ void SpellMgr::LoadSpellInfoCorrections()
vse = const_cast<VehicleSeatEntry*>(sVehicleSeatStore.LookupEntry(1520)); // Wyrmrest Vanquisher
vse->m_flags |= VEHICLE_SEAT_FLAG_PASSENGER_NOT_SELECTABLE;
// Salvaged Siege Engine (Ulduar) extra passenger seats: the only salvaged vehicle seats
// without this flag, leaving those passengers targetable by Flame Leviathan's AoE
vse = const_cast<VehicleSeatEntry*>(sVehicleSeatStore.LookupEntry(4026));
vse->m_flags |= VEHICLE_SEAT_FLAG_PASSENGER_NOT_SELECTABLE;
vse = const_cast<VehicleSeatEntry*>(sVehicleSeatStore.LookupEntry(4027));
vse->m_flags |= VEHICLE_SEAT_FLAG_PASSENGER_NOT_SELECTABLE;
// pussywizard: fix z offset for some vehicles:
vse = const_cast<VehicleSeatEntry*>(sVehicleSeatStore.LookupEntry(6206)); // Marrowgar - Bone Spike
vse->m_attachmentOffsetZ = 4.0f;
+2
View File
@@ -823,12 +823,14 @@ public:
#define AuraEffectSplitFn(F, I) EffectSplitFunction(&F, I)
// executed when aura checks if it can proc
// do not use this hook for triggering spellcasts/removing auras etc - runs mid proc-iteration and may be unsafe
// example: DoCheckProc += AuraCheckProcFn(class::function);
// where function is: bool function (ProcEventInfo& eventInfo);
HookList<CheckProcHandler> DoCheckProc;
#define AuraCheckProcFn(F) CheckProcHandlerFunction(&F)
// executed when aura effect checks if it can proc the aura
// do not use this hook for triggering spellcasts/removing auras etc - runs mid proc-iteration and may be unsafe
// example: DoCheckEffectProc += AuraCheckEffectProcFn(class::function, EffectIndexSpecifier, EffectAuraNameSpecifier);
// where function is: bool function (AuraEffect const* aurEff, ProcEventInfo& eventInfo);
HookList<CheckEffectProcHandler> DoCheckEffectProc;
+78
View File
@@ -21,9 +21,11 @@
#include "Chat.h"
#include "Common.h"
#include "DatabaseEnv.h"
#include "GameTime.h"
#include "GridNotifiers.h"
#include "MiscPackets.h"
#include "ObjectMgr.h"
#include "Random.h"
class CreatureTextBuilder
{
@@ -196,6 +198,62 @@ void CreatureTextMgr::LoadCreatureTextLocales()
LOG_INFO("server.loading", " ");
}
void CreatureTextMgr::LoadCreatureTextOptions()
{
uint32 oldMSTime = getMSTime();
mTextOptionsMap.clear();
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_CREATURE_TEXT_OPTIONS);
PreparedQueryResult result = WorldDatabase.Query(stmt);
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 creature text options. DB table `creature_text_options` is empty.");
LOG_INFO("server.loading", " ");
return;
}
uint32 count = 0;
do
{
Field* fields = result->Fetch();
uint32 creatureId = fields[0].Get<uint32>();
uint8 groupId = fields[1].Get<uint8>();
CreatureTextOptions options;
options.cooldown = fields[2].Get<uint32>();
options.triggerChance = fields[3].Get<uint8>();
options.playerOnly = fields[4].Get<bool>();
if (options.triggerChance > 100)
{
LOG_ERROR("sql.sql", "CreatureTextMgr: Entry {}, Group {} in table `creature_text_options` has TriggerChance {} > 100. Setting to 100.", creatureId, groupId, options.triggerChance);
options.triggerChance = 100;
}
mTextOptionsMap[creatureId][groupId] = options;
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} Creature Text Options in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
CreatureTextOptions const* CreatureTextMgr::GetTextOptions(uint32 entry, uint8 group) const
{
auto entryItr = mTextOptionsMap.find(entry);
if (entryItr == mTextOptionsMap.end())
return nullptr;
auto groupItr = entryItr->second.find(group);
if (groupItr == entryItr->second.end())
return nullptr;
return &groupItr->second;
}
uint32 CreatureTextMgr::SendChat(Creature* source, uint8 textGroup, WorldObject const* target /*= nullptr*/, ChatMsg msgType /*= CHAT_MSG_ADDON*/, Language language /*= LANG_ADDON*/, CreatureTextRange range /*= TEXT_RANGE_NORMAL*/, uint32 sound /*= 0*/, TeamId teamId /*= TEAM_NEUTRAL*/, bool gmOnly /*= false*/, Player* srcPlr /*= nullptr*/)
{
if (!source)
@@ -218,6 +276,21 @@ uint32 CreatureTextMgr::SendChat(Creature* source, uint8 textGroup, WorldObject
}
CreatureTextGroup const& textGroupContainer = itr->second; //has all texts in the group
// Check creature_text_options for this group
CreatureTextOptions const* options = GetTextOptions(source->GetEntry(), textGroup);
if (options)
{
if (options->playerOnly && (!target || !target->IsPlayer()))
return 0;
if (options->triggerChance < 100 && !roll_chance_i(options->triggerChance))
return 0;
if (options->cooldown > 0 && source->IsTextOnCooldown(textGroup))
return 0;
}
CreatureTextRepeatIds repeatGroup = source->GetTextRepeatGroup(textGroup);//has all textIDs from the group that were already said
CreatureTextGroup tempGroup;//will use this to talk after sorting repeatGroup
@@ -265,6 +338,11 @@ uint32 CreatureTextMgr::SendChat(Creature* source, uint8 textGroup, WorldObject
}
source->SetTextRepeatId(textGroup, iter->id);
// Set cooldown if creature_text_options specifies one
if (options && options->cooldown > 0)
source->SetTextCooldown(textGroup, options->cooldown);
return iter->duration;
}
+13
View File
@@ -71,10 +71,20 @@ struct CreatureTextId
uint32 textId;
};
struct CreatureTextOptions
{
uint32 cooldown; // ms before this group can fire again
uint8 triggerChance; // 0-100 chance to fire at all
bool playerOnly; // only fire if target is a player
};
typedef std::vector<CreatureTextEntry> CreatureTextGroup; // texts in a group
typedef std::unordered_map<uint8, CreatureTextGroup> CreatureTextHolder; // groups for a creature by groupid
typedef std::unordered_map<uint32, CreatureTextHolder> CreatureTextMap; // all creatures by entry
typedef std::unordered_map<uint8, CreatureTextOptions> CreatureTextOptionsMap; // options per group
typedef std::unordered_map<uint32, CreatureTextOptionsMap> CreatureTextOptionsContainer; // per creature entry
typedef std::map<CreatureTextId, CreatureTextLocale> LocaleCreatureTextMap;
class CreatureTextMgr
@@ -87,7 +97,9 @@ public:
~CreatureTextMgr() { }
void LoadCreatureTexts();
void LoadCreatureTextLocales();
void LoadCreatureTextOptions();
CreatureTextMap const& GetTextMap() const { return mTextMap; }
CreatureTextOptions const* GetTextOptions(uint32 entry, uint8 group) const;
void SendSound(Creature* source, uint32 sound, ChatMsg msgType, WorldObject const* target, CreatureTextRange range, TeamId teamId, bool gmOnly);
void SendEmote(Unit* source, uint32 emote);
@@ -105,6 +117,7 @@ private:
CreatureTextMap mTextMap;
LocaleCreatureTextMap mLocaleTextMap;
CreatureTextOptionsContainer mTextOptionsMap;
};
#define sCreatureTextMgr CreatureTextMgr::instance()
+9
View File
@@ -870,6 +870,9 @@ void World::SetInitialWorldSettings()
LOG_INFO("server.loading", "Loading Creature Texts...");
sCreatureTextMgr->LoadCreatureTexts();
LOG_INFO("server.loading", "Loading Creature Text Options...");
sCreatureTextMgr->LoadCreatureTextOptions();
LOG_INFO("server.loading", "Loading Creature Text Locales...");
sCreatureTextMgr->LoadCreatureTextLocales();
@@ -1286,6 +1289,12 @@ void World::Update(uint32 diff)
stmt->SetData(2, realm.Id.Realm);
stmt->SetData(3, uint32(GameTime::GetStartTime().count()));
LoginDatabase.Execute(stmt);
// Re-assert this realm as online in case the offline flag was set externally (e.g. an authserver restart).
LoginDatabasePreparedStatement* onlineStmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_REALM_ONLINE);
onlineStmt->SetData(0, uint8(REALM_FLAG_OFFLINE));
onlineStmt->SetData(1, realm.Id.Realm);
LoginDatabase.Execute(onlineStmt);
}
///- Process Game events when necessary
+6
View File
@@ -426,6 +426,12 @@ void WorldConfig::BuildConfigCache()
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_PREP_TIME, "Battleground.PrepTime", 120);
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS, "Battleground.Override.LowLevels.MinPlayers", 0);
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_AV, "Battleground.Override.LowLevels.MinPlayers.AV", 0);
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_WS, "Battleground.Override.LowLevels.MinPlayers.WS", 0);
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_AB, "Battleground.Override.LowLevels.MinPlayers.AB", 0);
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_EY, "Battleground.Override.LowLevels.MinPlayers.EY", 0);
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_SA, "Battleground.Override.LowLevels.MinPlayers.SA", 0);
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_IC, "Battleground.Override.LowLevels.MinPlayers.IC", 0);
SetConfigValue<bool>(CONFIG_BATTLEGROUND_DISABLE_QUEST_SHARE_IN_BG, "Battleground.DisableQuestShareInBG", false);
SetConfigValue<bool>(CONFIG_BATTLEGROUND_DISABLE_READY_CHECK_IN_BG, "Battleground.DisableReadyCheckInBG", false);
SetConfigValue<bool>(CONFIG_BATTLEGROUND_CAST_DESERTER, "Battleground.CastDeserter", true);
+6
View File
@@ -264,6 +264,12 @@ enum ServerConfigs
CONFIG_DISABLE_BREATHING,
CONFIG_BATTLEGROUND_PREP_TIME,
CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS,
CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_AV,
CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_WS,
CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_AB,
CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_EY,
CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_SA,
CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS_IC,
CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_SPAM_DELAY,
CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_TIMER,
CONFIG_BATTLEGROUND_PREMATURE_FINISH_TIMER,
@@ -20,6 +20,7 @@
#include "CommandScript.h"
#include "Config.h"
#include "Language.h"
#include "RBAC.h"
using namespace Acore::ChatCommands;
@@ -32,10 +33,10 @@ public:
{
static ChatCommandTable autobroadcastCommandTable =
{
{ "list", HandleAutobroadcastListCommand, SEC_GAMEMASTER, Console::Yes },
{ "add", HandleAutobroadcastAddCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "locale", HandleAutobroadcastLocaleCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "remove", HandleAutobroadcastRemoveCommand, SEC_ADMINISTRATOR, Console::Yes }
{ "list", HandleAutobroadcastListCommand, rbac::RBAC_PERM_COMMAND_AUTOBROADCAST_LIST, Console::Yes },
{ "add", HandleAutobroadcastAddCommand, rbac::RBAC_PERM_COMMAND_AUTOBROADCAST_ADD, Console::Yes },
{ "locale", HandleAutobroadcastLocaleCommand, rbac::RBAC_PERM_COMMAND_AUTOBROADCAST_LOCALE, Console::Yes },
{ "remove", HandleAutobroadcastRemoveCommand, rbac::RBAC_PERM_COMMAND_AUTOBROADCAST_REMOVE, Console::Yes }
};
static ChatCommandTable commandTable =
+1 -1
View File
@@ -223,7 +223,7 @@ public:
std::string str = name.data(); // Making subtractions to the last character does not with in string_view
WorldDatabase.EscapeString(str);
QueryResult result = WorldDatabase.Query("SELECT entry FROM creature_template WHERE name = \"{}\" LIMIT 1", str);
QueryResult result = WorldDatabase.Query("SELECT entry FROM creature_template WHERE name = '{}' LIMIT 1", str);
if (!result)
{
handler->SendErrorMessage(LANG_COMMAND_GOCREATNOTFOUND);
+3 -2
View File
@@ -26,6 +26,7 @@
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "RBAC.h"
#include "ScriptMgr.h"
#include "Timer.h"
@@ -40,8 +41,8 @@ public:
{
static ChatCommandTable mailCommandTable =
{
{ "list", HandleMailListCommand, SEC_GAMEMASTER, Console::Yes },
{ "return", HandleMailReturnCommand, SEC_GAMEMASTER, Console::Yes }
{ "list", HandleMailListCommand, rbac::RBAC_PERM_COMMAND_MAIL_LIST, Console::Yes },
{ "return", HandleMailReturnCommand, rbac::RBAC_PERM_COMMAND_MAIL_RETURN, Console::Yes }
};
static ChatCommandTable commandTable =
+4 -4
View File
@@ -200,11 +200,11 @@ public:
{ "add", npcAddCommandTable },
{ "delete", npcDeleteCommandTable },
{ "follow", npcFollowCommandTable },
{ "load", HandleNpcLoadCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "load", HandleNpcLoadCommand, rbac::RBAC_PERM_COMMAND_NPC_LOAD, Console::Yes },
{ "set", npcSetCommandTable },
{ "showloot", HandleNpcShowLootCommand, rbac::RBAC_PERM_COMMAND_NPC_SHOWLOOT, Console::No },
{ "spawngroup", HandleNpcSpawnGroupCommand, SEC_ADMINISTRATOR, Console::No },
{ "despawngroup", HandleNpcDespawnGroupCommand, SEC_ADMINISTRATOR, Console::No }
{ "showloot", HandleNpcShowLootCommand, rbac::RBAC_PERM_COMMAND_NPC_SHOWLOOT, Console::No },
{ "spawngroup", HandleNpcSpawnGroupCommand, rbac::RBAC_PERM_COMMAND_NPC_SPAWNGROUP, Console::No },
{ "despawngroup", HandleNpcDespawnGroupCommand, rbac::RBAC_PERM_COMMAND_NPC_DESPAWNGROUP, Console::No }
};
static ChatCommandTable commandTable =
{
+3 -2
View File
@@ -25,6 +25,7 @@
#include "ObjectMgr.h"
#include "Player.h"
#include "PoolMgr.h"
#include "RBAC.h"
using namespace Acore::ChatCommands;
@@ -37,8 +38,8 @@ public:
{
static ChatCommandTable poolCommandTable =
{
{ "info", HandlePoolInfoCommand, SEC_GAMEMASTER, Console::Yes },
{ "lookup", HandlePoolLookupCommand, SEC_GAMEMASTER, Console::No },
{ "info", HandlePoolInfoCommand, rbac::RBAC_PERM_COMMAND_POOL_INFO, Console::Yes },
{ "lookup", HandlePoolLookupCommand, rbac::RBAC_PERM_COMMAND_POOL_LOOKUP, Console::No },
};
static ChatCommandTable commandTable =
{
+5 -4
View File
@@ -19,6 +19,7 @@
#include "CommandScript.h"
#include "DBCStructure.h"
#include "Language.h"
#include "RBAC.h"
#include "SmartEnum.h"
#include "SpellAuraDefines.h"
#include "SpellInfo.h"
@@ -35,10 +36,10 @@ public:
{
static ChatCommandTable spellinfoCommandTable =
{
{ "attributes", HandleSpellInfoAttributesCommand, SEC_GAMEMASTER, Console::Yes },
{ "effects", HandleSpellInfoEffectsCommand, SEC_GAMEMASTER, Console::Yes },
{ "targets", HandleSpellInfoTargetsCommand, SEC_GAMEMASTER, Console::Yes },
{ "all", HandleSpellInfoAllCommand, SEC_GAMEMASTER, Console::Yes }
{ "attributes", HandleSpellInfoAttributesCommand, rbac::RBAC_PERM_COMMAND_SPELLINFO_ATTRIBUTES, Console::Yes },
{ "effects", HandleSpellInfoEffectsCommand, rbac::RBAC_PERM_COMMAND_SPELLINFO_EFFECTS, Console::Yes },
{ "targets", HandleSpellInfoTargetsCommand, rbac::RBAC_PERM_COMMAND_SPELLINFO_TARGETS, Console::Yes },
{ "all", HandleSpellInfoAllCommand, rbac::RBAC_PERM_COMMAND_SPELLINFO_ALL, Console::Yes }
};
static ChatCommandTable commandTable =
@@ -219,8 +219,7 @@ struct boss_majordomo : public BossAI
void KilledUnit(Unit* victim) override
{
if (roll_chance_i(25) && victim->IsPlayer())
Talk(SAY_SLAY);
Talk(SAY_SLAY, victim);
}
void JustEngagedWith(Unit* /*attacker*/) override
@@ -209,8 +209,7 @@ struct boss_ragnaros : public BossAI
void KilledUnit(Unit* victim) override
{
if (roll_chance_i(25) && victim->IsPlayer())
Talk(SAY_KILL);
Talk(SAY_KILL, victim);
}
void AttackStart(Unit* target) override
@@ -117,7 +117,6 @@ struct boss_moroes : public BossAI
{
BossAI::Reset();
DoCastSelf(SPELL_DUAL_WIELD, true);
_recentlySpoken = false;
_vanished = false;
me->SetImmuneToAll(false);
me->SetReactState(REACT_AGGRESSIVE);
@@ -170,15 +169,7 @@ struct boss_moroes : public BossAI
void KilledUnit(Unit* victim) override
{
if (!_recentlySpoken && victim->IsPlayer())
{
Talk(SAY_KILL);
_recentlySpoken = true;
scheduler.Schedule(5s, [this](TaskContext)
{
_recentlySpoken = false;
});
}
Talk(SAY_KILL, victim);
}
void JustDied(Unit* killer) override
@@ -243,7 +234,6 @@ struct boss_moroes : public BossAI
private:
EventMap _events2;
uint8 _activeGuests;
bool _recentlySpoken;
bool _vanished;
};
@@ -104,8 +104,7 @@ struct boss_brutallus : public BossAI
void KilledUnit(Unit* victim) override
{
if (victim->IsPlayer() && roll_chance_i(50))
Talk(YELL_KILL);
Talk(YELL_KILL, victim);
}
void JustDied(Unit* killer) override
@@ -186,8 +186,7 @@ struct boss_felmyst : public BossAI
void KilledUnit(Unit* victim) override
{
if (victim->IsPlayer() && roll_chance_i(50))
Talk(YELL_KILL);
Talk(YELL_KILL, victim);
}
void SpellHitTarget(Unit* target, const SpellInfo* spell) override
@@ -260,8 +260,7 @@ struct boss_kalecgos : public BossAI
void KilledUnit(Unit* victim) override
{
if (victim->IsPlayer() && roll_chance_i(50))
Talk(SAY_EVIL_SLAY);
Talk(SAY_EVIL_SLAY, victim);
}
private:

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