Merge remote-tracking branch 'master' into test-staging
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
### TITLE
|
### TITLE
|
||||||
## Type(Scope/Subscope): Commit ultra short explanation
|
## Type(Scope/Subscope): Commit ultra short explanation
|
||||||
## |---- Write below the examples with a maximum of 50 characters ----|
|
## |---- Write below the examples with a maximum of 72 characters ----|
|
||||||
## Example 1: fix(DB/SAI): Missing spell to NPC Hogger
|
## Example 1: fix(DB/SAI): Missing spell to NPC Hogger
|
||||||
## Example 2: fix(CORE/Raid): Phase 2 of Ragnaros
|
## Example 2: feat(Scripts/Commands): Add .mail list and .mail return commands
|
||||||
## Example 3: feat(CORE/Commands): New GM command to do something
|
## Example 3: refactor(Scripts/Ulduar): Leverage DoorData and persistent data
|
||||||
|
## Example 4: fix(Scripts/SlavePens): Despawn Frozen Core after Ahune emerges
|
||||||
|
## Example 5: fix(Scripts/VioletHold): Prevent Sinclari from despawning
|
||||||
|
|
||||||
|
|
||||||
### DESCRIPTION
|
### DESCRIPTION
|
||||||
@@ -46,7 +48,7 @@
|
|||||||
## More info here https://www.conventionalcommits.org/en/v1.0.0-beta.2/
|
## More info here https://www.conventionalcommits.org/en/v1.0.0-beta.2/
|
||||||
## =======================================================
|
## =======================================================
|
||||||
## "Scope" can be:
|
## "Scope" can be:
|
||||||
## CORE (core related, c++)
|
## Core (core related, c++)
|
||||||
## DB (database related, sql)
|
## DB (database related, sql)
|
||||||
## =======================================================
|
## =======================================================
|
||||||
## "Subscope" is optional and depends on the nature of the commit.
|
## "Subscope" is optional and depends on the nature of the commit.
|
||||||
|
|||||||
Vendored
+5
@@ -1,4 +1,9 @@
|
|||||||
{
|
{
|
||||||
|
"github.copilot.chat.commitMessageGeneration.instructions": [
|
||||||
|
{
|
||||||
|
"file": ".git_commit_template.txt"
|
||||||
|
},
|
||||||
|
],
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.dist": "properties",
|
"*.dist": "properties",
|
||||||
"*.crash": "properties",
|
"*.crash": "properties",
|
||||||
|
|||||||
@@ -1,126 +1,104 @@
|
|||||||
# CLAUDE.md
|
# CLAUDE.md
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
AzerothCore is a C++ MMORPG server emulator for World of Warcraft 3.3.5a (WotLK), built with CMake, backed by MySQL.
|
||||||
|
|
||||||
## Project Overview
|
## Agent rules
|
||||||
|
|
||||||
AzerothCore is an open-source MMORPG server emulator for World of Warcraft patch 3.3.5a (Wrath of the Lich King). It's a C++ project built with CMake, using MySQL for data storage. Licensed under GNU GPL v2.
|
- **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 Commands
|
## Build
|
||||||
|
|
||||||
### Configure and build (out-of-source build required)
|
Out-of-source build is required (in-source is blocked by CMake).
|
||||||
|
|
||||||
- Skip building unless explicitly requested.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create build directory and configure
|
|
||||||
mkdir -p build && cd build
|
mkdir -p build && cd build
|
||||||
cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/azeroth-server -DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/azeroth-server -DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||||
-DSCRIPTS=static -DMODULES=static
|
-DSCRIPTS=static -DMODULES=static
|
||||||
|
make -j$(nproc) && make install
|
||||||
# Build (use appropriate core count)
|
|
||||||
make -j$(nproc)
|
|
||||||
make install
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Key CMake options
|
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.
|
||||||
|
|
||||||
- `SCRIPTS`: none, static, dynamic, minimal-static, minimal-dynamic (default: static)
|
Tests (Google Test, in `src/test/`): configure with `-DBUILD_TESTING=ON`, then `ctest` or `./src/test/unit_tests` from the build dir.
|
||||||
- `MODULES`: none, static, dynamic (default: static)
|
|
||||||
- `APPS_BUILD`: none, all, auth-only, world-only (default: all)
|
|
||||||
- `TOOLS_BUILD`: none, all, db-only, maps-only (default: none)
|
|
||||||
- `BUILD_TESTING`: Enable unit tests (default: OFF)
|
|
||||||
- `USE_COREPCH` / `USE_SCRIPTPCH`: Precompiled headers (default: ON)
|
|
||||||
|
|
||||||
### Unit tests
|
## 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
|
```bash
|
||||||
# Configure with testing enabled
|
python apps/codestyle/codestyle-cpp.py # C++
|
||||||
cmake .. -DBUILD_TESTING=ON
|
python apps/codestyle/codestyle-sql.py # SQL (compares to origin/master)
|
||||||
make -j$(nproc)
|
|
||||||
|
|
||||||
# Run tests
|
|
||||||
./src/test/unit_tests
|
|
||||||
# or
|
|
||||||
ctest
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Tests use Google Test and live in `src/test/`. The test binary links against the `game` library.
|
Hard rules (also enforced by CI with `-Werror`):
|
||||||
|
|
||||||
## Architecture
|
- 4-space indent for C++ (tabs forbidden); 2-space for JSON/YAML/sh/ts/js. UTF-8, LF, max 80 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()`.
|
||||||
|
|
||||||
### Two server executables
|
CI also runs `cppcheck`.
|
||||||
- **authserver** (`src/server/apps/authserver/`): Handles authentication and realm selection (port 3724)
|
|
||||||
- **worldserver** (`src/server/apps/worldserver/`): Main game server handling all gameplay (port 8085)
|
|
||||||
|
|
||||||
### Source layout (`src/`)
|
## Project conventions
|
||||||
|
|
||||||
- **`src/common/`** - Shared libraries: networking (Asio), cryptography, configuration, logging, threading, collision detection, utilities
|
- **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`.
|
||||||
- **`src/server/game/`** - Core game logic (~52 subsystems), the heart of the worldserver
|
- **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.
|
||||||
- **`src/server/scripts/`** - Content scripts (bosses, spells, commands, instances)
|
- **Strings**: `Acore::StringFormat(fmt, args...)` (wraps `fmt::format`, `{}` placeholders) — `src/common/Utilities/StringFormat.h`.
|
||||||
- **`src/server/database/`** - Database abstraction layer and schema updater
|
- **Config**: read options with `sConfigMgr->GetOption<T>("Name", default)`.
|
||||||
- **`src/server/shared/`** - Code shared between auth and world servers (packets, network, realm definitions)
|
- **Namespace**: project-wide is `Acore::` (no `Trinity::` remnants — agents porting from upstream forks must rename).
|
||||||
- **`src/test/`** - Unit tests (Google Test)
|
- **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.
|
||||||
|
|
||||||
### Key game subsystems (`src/server/game/`)
|
## Scripting registration
|
||||||
|
|
||||||
- **Entities/** - Core game objects: `Player`, `Creature`, `Unit`, `Item`, `GameObject`
|
Scripts inherit from a `ScriptObject` subclass (`SpellScript`, `AuraScript`, `CreatureScript`, `InstanceMapScript`, `GameObjectScript`, `CommandScript`, …). Two registration styles coexist:
|
||||||
- **Spells/** - Spell mechanics, aura system, spell effects
|
|
||||||
- **Maps/** - Map management, grid system, instancing
|
|
||||||
- **Handlers/** - Client packet handlers (one file per system: `MovementHandler.cpp`, `SpellHandler.cpp`, etc.). These are methods on `WorldSession`
|
|
||||||
- **AI/** - Creature AI framework
|
|
||||||
- **Scripting/** - Script system with typed base classes (`ScriptObject` subclasses: `CreatureScript`, `SpellScript`, `InstanceMapScript`, `GameObjectScript`, `CommandScript`, etc.)
|
|
||||||
- **Server/** - `WorldSession` (per-player connection), `World` (global state), opcode definitions
|
|
||||||
|
|
||||||
### Scripting system
|
- **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.
|
||||||
|
|
||||||
Scripts follow a registration pattern:
|
Then declare and call `AddSC_<name>()` from the regional loader: `Spells/spells_script_loader.cpp`, `EasternKingdoms/eastern_kingdoms_script_loader.cpp`, etc.
|
||||||
1. Define a class inheriting from `SpellScript`, `CreatureScript`, etc.
|
|
||||||
2. Implement an `AddSC_*()` function that calls `RegisterSpellScript(ClassName)` (or similar)
|
|
||||||
3. The `AddSC_*()` is declared and called from the regional `*_script_loader.cpp`
|
|
||||||
4. Script loaders per region: `spells_script_loader.cpp`, `eastern_kingdoms_script_loader.cpp`, `northrend_script_loader.cpp`, etc.
|
|
||||||
5. Spell script files are organized by class: `spell_dk.cpp`, `spell_mage.cpp`, `spell_generic.cpp`, etc.
|
|
||||||
|
|
||||||
### Three databases
|
**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.
|
||||||
- **acore_auth** - Accounts, realm list, bans (`data/sql/base/db_auth/`)
|
|
||||||
- **acore_characters** - Character data, inventories, progress (`data/sql/base/db_characters/`)
|
|
||||||
- **acore_world** - Game content: creatures, items, quests, spells, loot (`data/sql/base/db_world/`)
|
|
||||||
|
|
||||||
- SQL updates go in `data/sql/updates/pending_*` with separate subdirectories per database until pull request is merged. Pending SQL files are assigned random names.
|
**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.
|
||||||
- SQL updates go in `data/sql/updates/` with separate subdirectories per database after their pull request is merged.
|
|
||||||
- SQL files outside the `data/sql/updates/pending_*` folders should never be updated.
|
|
||||||
|
|
||||||
### Module system
|
Custom (non-upstream) scripts go in `src/server/scripts/Custom/` (gitignored).
|
||||||
|
|
||||||
External modules are loaded from the `modules/` directory. Each module is a subdirectory with its own `CMakeLists.txt`. Disable specific modules with `-DDISABLED_AC_MODULES="mod1;mod2"`. Module skeleton: https://github.com/azerothcore/skeleton-module/
|
|
||||||
|
|
||||||
### Dependencies
|
|
||||||
|
|
||||||
Bundled in `deps/`: boost, MySQL client, OpenSSL, zlib, recastnavigation (pathfinding), g3dlite (geometry), fmt, argon2, jemalloc, and others.
|
|
||||||
|
|
||||||
## Commit Message Format
|
|
||||||
|
|
||||||
Uses Conventional Commits:
|
|
||||||
```
|
|
||||||
Type(Scope/Subscope): Short description (max 50 chars)
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Types**: feat, fix, refactor, style, docs, test, chore
|
|
||||||
- **Scopes**: Core (C++ changes), DB (SQL changes)
|
|
||||||
- **Examples**: `fix(Core/Spells): Fix damage calculation for Fireball`, `fix(DB/SAI): Missing spell to NPC Hogger`
|
|
||||||
|
|
||||||
## Code Style
|
|
||||||
|
|
||||||
- 4-space indentation for C++ (no tabs)
|
|
||||||
- 2-space indentation for JSON, YAML, shell scripts
|
|
||||||
- UTF-8 encoding, LF line endings
|
|
||||||
- Max 80 character line length
|
|
||||||
- No braces around single-line statements
|
|
||||||
- Use {} to parse variables into output instead of %u etc.
|
|
||||||
- CI enforces code style checks and compiles with `-Werror`
|
|
||||||
|
|
||||||
## PR Requirements
|
|
||||||
|
|
||||||
- AI tool usage must be disclosed in PRs
|
|
||||||
- In-game testing expected
|
|
||||||
- Changes to generic code require regression testing of related systems
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- DB update 2026_05_17_00 -> 2026_05_24_00
|
||||||
|
-- RBAC permission for .pet rename (sec 3+, Admin role 196).
|
||||||
|
DELETE FROM `rbac_permissions` WHERE `id` = 922;
|
||||||
|
INSERT INTO `rbac_permissions` (`id`, `name`) VALUES
|
||||||
|
(922, 'Command: pet rename');
|
||||||
|
|
||||||
|
DELETE FROM `rbac_linked_permissions` WHERE `linkedId` = 922;
|
||||||
|
INSERT INTO `rbac_linked_permissions` (`id`, `linkedId`) VALUES
|
||||||
|
(196, 922);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- DB update 2026_05_22_00 -> 2026_05_23_00
|
||||||
|
--
|
||||||
|
UPDATE `creature_template_addon` SET `auras` = '' WHERE (`entry` IN (31841, 31842));
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
-- DB update 2026_05_23_00 -> 2026_05_24_00
|
||||||
|
--
|
||||||
|
DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 22) AND (`SourceGroup` = 4) AND (`SourceEntry` = 30886);
|
||||||
|
INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES
|
||||||
|
(22, 4, 30886, 0, 0, 23, 1, 4526, 0, 0, 1, 0, 0, '', 'Despawn Subjugated Iskalder when outside quest areas'),
|
||||||
|
(22, 4, 30886, 0, 0, 23, 1, 4498, 0, 0, 1, 0, 0, '', 'Despawn Subjugated Iskalder when outside quest areas'),
|
||||||
|
(22, 4, 30886, 0, 0, 23, 1, 4496, 0, 0, 1, 0, 0, '', 'Despawn Subjugated Iskalder when outside quest areas');
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- DB update 2026_05_24_00 -> 2026_05_24_01
|
||||||
|
--
|
||||||
|
UPDATE `creature_template` SET `CreatureImmunitiesId` = -375 WHERE `entry` IN (30183, 16486);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
-- DB update 2026_05_24_01 -> 2026_05_24_02
|
||||||
|
-- acore_string entries for .pet rename command.
|
||||||
|
DELETE FROM `acore_string` WHERE `entry` IN (35453, 35454);
|
||||||
|
INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, `locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`) VALUES
|
||||||
|
(35453, 'Invalid pet name: {}.', '잘못된 펫 이름입니다: {}.', 'Nom de familier invalide : {}.', 'Ungültiger Begleitername: {}.', '无效的宠物名称: {}。', '無效的寵物名稱:{}。', 'Nombre de mascota no válido: {}.', 'Nombre de mascota no válido: {}.', 'Недопустимое имя питомца: {}.'),
|
||||||
|
(35454, 'Renamed pet {0} ({1} -> {2}) for player {3} ({4}).', '플레이어 {3} ({4})의 펫 {0}의 이름을 {1}에서 {2}(으)로 변경했습니다.', 'Familier {0} renommé ({1} -> {2}) pour le joueur {3} ({4}).', 'Begleiter {0} umbenannt ({1} -> {2}) für Spieler {3} ({4}).', '已将玩家 {3} ({4}) 的宠物 {0} 重命名 ({1} -> {2})。', '已將玩家 {3} ({4}) 的寵物 {0} 重新命名({1} -> {2})。', 'Mascota {0} renombrada ({1} -> {2}) para el jugador {3} ({4}).', 'Mascota {0} renombrada ({1} -> {2}) para el jugador {3} ({4}).', 'Питомец {0} переименован ({1} -> {2}) у игрока {3} ({4}).');
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
-- DB update 2026_05_24_02 -> 2026_05_25_00
|
||||||
|
-- Hide Whispering Wind (30848) during Wintergrasp battle.
|
||||||
|
-- Aura 52107 phases the creature out while war is active; the other five
|
||||||
|
-- small WG elementals (30842, 30845, 30846, 30847, 30849) already have it
|
||||||
|
-- in creature_template_addon — 30848 was missed.
|
||||||
|
DELETE FROM `creature_template_addon` WHERE `entry` = 30848;
|
||||||
|
INSERT INTO `creature_template_addon` (`entry`, `path_id`, `mount`, `bytes1`, `bytes2`, `emote`, `visibilityDistanceType`, `auras`) VALUES
|
||||||
|
(30848, 0, 0, 0, 1, 0, 0, '52107');
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
-- DB update 2026_05_25_00 -> 2026_05_26_00
|
||||||
|
--
|
||||||
|
-- Change Target CREATURE_DISTANCE to CLOSEST_FRIENDLY
|
||||||
|
DELETE FROM `smart_scripts` WHERE (`entryorguid` = 23960) AND (`source_type` = 0) AND (`id` IN (1, 2));
|
||||||
|
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
|
||||||
|
(23960, 0, 1, 0, 0, 0, 100, 2, 5000, 7000, 14000, 17000, 0, 0, 11, 42740, 0, 0, 0, 0, 0, 26, 30, 0, 0, 0, 0, 0, 0, 0, 'Dragonflayer Runecaster - In Combat - Cast \'Njord`s Rune of Protection\' (Normal Dungeon)'),
|
||||||
|
(23960, 0, 2, 0, 0, 0, 100, 4, 5000, 7000, 14000, 17000, 0, 0, 11, 59616, 0, 0, 0, 0, 0, 26, 30, 0, 0, 0, 0, 0, 0, 0, 'Dragonflayer Runecaster - In Combat - Cast \'Njord`s Rune of Protection\' (Heroic Dungeon)');
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
-- DB update 2026_05_26_00 -> 2026_05_28_00
|
||||||
|
--
|
||||||
|
DELETE FROM `spawn_group` WHERE `spawnId` IN (153154, 153155, 153156, 153157);
|
||||||
|
INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`) VALUES
|
||||||
|
(1, 0, 153154),
|
||||||
|
(1, 0, 153155),
|
||||||
|
(1, 0, 153156),
|
||||||
|
(1, 0, 153157);
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
-- DB update 2026_05_28_00 -> 2026_05_30_00
|
||||||
|
-- Hellfire Channelers formation: groupAI = 3 (MEMBER_ASSIST_LEADER | LEADER_ASSIST_MEMBER), pack-aggro only. Wipe handling lives in the instance script's hard reset.
|
||||||
|
DELETE FROM `creature_formations` WHERE `leaderGUID`=90978;
|
||||||
|
INSERT INTO `creature_formations`
|
||||||
|
(`leaderGUID`,`memberGUID`,`dist`,`angle`,`groupAI`,`point_1`,`point_2`)
|
||||||
|
VALUES
|
||||||
|
(90978, 90978, 0, 0, 3, 0, 0),
|
||||||
|
(90978, 90979, 0, 0, 3, 0, 0),
|
||||||
|
(90978, 90980, 0, 0, 3, 0, 0),
|
||||||
|
(90978, 90981, 0, 0, 3, 0, 0),
|
||||||
|
(90978, 90982, 0, 0, 3, 0, 0);
|
||||||
|
|
||||||
|
-- Hellfire Channeler SAI: drop id=6. Death tracking moved to the instance script (OnUnitDeath).
|
||||||
|
DELETE FROM `smart_scripts` WHERE `entryorguid`=17256 AND `source_type`=0 AND `id`=6;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
-- DB update 2026_05_30_00 -> 2026_05_30_01
|
||||||
|
--
|
||||||
|
DROP TABLE IF EXISTS `spell_cone`;
|
||||||
|
CREATE TABLE `spell_cone` (
|
||||||
|
`ID` INT UNSIGNED NOT NULL COMMENT 'Spell ID',
|
||||||
|
`ConeDegrees` SMALLINT NOT NULL DEFAULT '60' COMMENT 'Cone angle in degrees',
|
||||||
|
PRIMARY KEY (`ID`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
-- DB update 2026_05_30_01 -> 2026_05_30_02
|
||||||
|
--
|
||||||
|
DELETE FROM `spell_cone` WHERE `ID` > 0;
|
||||||
|
INSERT INTO `spell_cone` VALUES
|
||||||
|
(5708, 90),
|
||||||
|
(8374, 90),
|
||||||
|
-- (9457, 90), -- Tharnariun's Heal
|
||||||
|
(11019, 90),
|
||||||
|
(12882, 90),
|
||||||
|
(12887, 90),
|
||||||
|
-- (15529, 359), -- Gout of Flame
|
||||||
|
(15847, -120),
|
||||||
|
(16094, 90),
|
||||||
|
(16099, 90),
|
||||||
|
(16169, 90),
|
||||||
|
(16340, 90),
|
||||||
|
(16350, 90),
|
||||||
|
(16359, 90),
|
||||||
|
(16390, 90),
|
||||||
|
(16396, 120),
|
||||||
|
(18144, 90),
|
||||||
|
(18435, 120),
|
||||||
|
(18500, 120),
|
||||||
|
(19630, 120),
|
||||||
|
(19641, 120),
|
||||||
|
(19813, 180),
|
||||||
|
(20667, 90),
|
||||||
|
(20712, 120),
|
||||||
|
(20716, 90),
|
||||||
|
(20717, 90),
|
||||||
|
(21071, 120),
|
||||||
|
(21099, 120),
|
||||||
|
(21189, 90),
|
||||||
|
(21333, 180),
|
||||||
|
(22479, 90),
|
||||||
|
(22539, 120),
|
||||||
|
(22558, 90),
|
||||||
|
(22559, 90),
|
||||||
|
(22560, 90),
|
||||||
|
(22561, 90),
|
||||||
|
-- (22600, 120), -- Force Reactive Disk
|
||||||
|
(22642, 90),
|
||||||
|
-- (22682, 120), -- Shadow Flame
|
||||||
|
(22833, 120),
|
||||||
|
(22945, 120),
|
||||||
|
-- (22993, 120), -- Shadow Flame
|
||||||
|
-- (23187, 120), -- Frost Burn
|
||||||
|
-- (23189, 120), -- Frost Burn
|
||||||
|
-- (23308, 120), -- Incinerate
|
||||||
|
-- (23309, 120), -- Incinerate
|
||||||
|
-- (23310, 120), -- Time Lapse
|
||||||
|
-- (23311, 120), -- Time Lapse
|
||||||
|
-- (23312, 120), -- Time Lapse
|
||||||
|
-- (23313, 120), -- Corrosive Acid
|
||||||
|
-- (23314, 120), -- Corrosive Acid
|
||||||
|
-- (23315, 120), -- Ignite Flesh
|
||||||
|
-- (23316, 120), -- Ignite Flesh
|
||||||
|
(23339, 120),
|
||||||
|
(23364, -120),
|
||||||
|
(23461, 120),
|
||||||
|
(23618, 90),
|
||||||
|
(23919, 120),
|
||||||
|
(24192, 120),
|
||||||
|
(24682, 120),
|
||||||
|
(24818, 120),
|
||||||
|
(24821, 45),
|
||||||
|
(24822, 135),
|
||||||
|
(24823, 90),
|
||||||
|
(24835, 180),
|
||||||
|
(24836, 225),
|
||||||
|
(24837, 270),
|
||||||
|
(24838, 315),
|
||||||
|
(24839, 120),
|
||||||
|
(24933, 7),
|
||||||
|
-- (25024, 45), -- Drop Mine
|
||||||
|
-- (25026, 45), Activate MG Turret
|
||||||
|
-- (25027, 30), -- Flamethrower
|
||||||
|
(25029, 30), -- Flamethrower
|
||||||
|
(25030, 90), -- Shoot Rocket
|
||||||
|
-- (25031, 45), -- Shoot Missile
|
||||||
|
-- (25032, 10), -- Machine Gun
|
||||||
|
-- (25060, 90), -- Updraft
|
||||||
|
(25145, 90),
|
||||||
|
(25149, 90),
|
||||||
|
(25150, 90),
|
||||||
|
(25322, 90),
|
||||||
|
(25653, -120),
|
||||||
|
(25993, 120),
|
||||||
|
(26025, 180), -- Impale used by 15233 Vekniss Guardian; Knockback AoE, Bleed Cone
|
||||||
|
-- (26027, 180), -- Knockback
|
||||||
|
(26029, 5), -- Dark Glare
|
||||||
|
-- (27766, 90), -- Cannon
|
||||||
|
(27807, 90),
|
||||||
|
(28127, 90),
|
||||||
|
(28157, 45),
|
||||||
|
(28168, 120),
|
||||||
|
(28405, 180),
|
||||||
|
(28438, 180),
|
||||||
|
(28459, 90),
|
||||||
|
-- (28460, 90), -- Wail of Souls
|
||||||
|
-- (28469, 360), -- Ghost Vision
|
||||||
|
(28741, 120),
|
||||||
|
(28783, 30),
|
||||||
|
-- (29998, 180), -- Decrepit Fever
|
||||||
|
(30043, 90),
|
||||||
|
(30056, 180),
|
||||||
|
(30732, 180);
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
-- DB update 2026_05_30_02 -> 2026_05_30_03
|
||||||
|
--
|
||||||
|
-- 25029 Flamethrower TARGET_UNIT_CONE_ENTRY
|
||||||
|
-- 25030 Shoot Rocket TARGET_UNIT_CONE_ENTRY
|
||||||
|
DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 13) AND (`SourceEntry` IN (25029, 25030)) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 31) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 3) AND (`ConditionValue2` = 15328) AND (`ConditionValue3` = 0);
|
||||||
|
INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES
|
||||||
|
(13, 5, 25029, 0, 0, 31, 0, 3, 15328, 0, 0, 0, 0, '', 'target must be \'Steam Stonk\''),
|
||||||
|
(13, 1, 25030, 0, 0, 31, 0, 3, 15328, 0, 0, 0, 0, '', 'target must be \'Steam Stonk\'');
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Vendored
+4
@@ -17,6 +17,10 @@ if( UNIX )
|
|||||||
# READLINE_LIBRARY - full path to the READLINE library
|
# READLINE_LIBRARY - full path to the READLINE library
|
||||||
find_path(READLINE_INCLUDE_DIR readline/readline.h)
|
find_path(READLINE_INCLUDE_DIR readline/readline.h)
|
||||||
find_library(READLINE_LIBRARY NAMES readline)
|
find_library(READLINE_LIBRARY NAMES readline)
|
||||||
|
# macOS: Homebrew readline is keg-only, so this finds the SDK's libedit stub
|
||||||
|
# and worldserver fails to link. After `brew install readline`, configure with
|
||||||
|
# -DREADLINE_LIBRARY=/opt/homebrew/opt/readline/lib/libreadline.dylib and
|
||||||
|
# -DREADLINE_INCLUDE_DIR=/opt/homebrew/opt/readline/include
|
||||||
|
|
||||||
message(STATUS "Found Readline library: ${READLINE_LIBRARY}")
|
message(STATUS "Found Readline library: ${READLINE_LIBRARY}")
|
||||||
message(STATUS "Include dir is: ${READLINE_INCLUDE_DIR}")
|
message(STATUS "Include dir is: ${READLINE_INCLUDE_DIR}")
|
||||||
|
|||||||
@@ -3677,6 +3677,31 @@ Wintergrasp.CrashRestartTimer = 10
|
|||||||
|
|
||||||
Wintergrasp.SkipBattleSessionCount = 3500
|
Wintergrasp.SkipBattleSessionCount = 3500
|
||||||
|
|
||||||
|
#
|
||||||
|
# Wintergrasp.KickVoAPlayers
|
||||||
|
# Description: Kick players from Vault of Archavon and freeze its bosses
|
||||||
|
# in the minutes leading up to a Wintergrasp battle, and
|
||||||
|
# block new entries during wartime / the 10-minute warmup.
|
||||||
|
# Requires a server restart to take effect (not reloadable).
|
||||||
|
# Default: 1 - (Enabled, players are warned then teleported to their
|
||||||
|
# homebind before the battle starts)
|
||||||
|
# 0 - (Disabled, players stay inside and bosses behave
|
||||||
|
# normally during Wintergrasp)
|
||||||
|
|
||||||
|
Wintergrasp.KickVoAPlayers = 1
|
||||||
|
|
||||||
|
#
|
||||||
|
# Wintergrasp.EssenceBothFactions
|
||||||
|
# Description: Grant the "Essence of Wintergrasp" buff to both factions
|
||||||
|
# during peacetime, regardless of who controls the keep.
|
||||||
|
# The wartime suppression still applies; this only affects
|
||||||
|
# who receives the buff once the battle is over.
|
||||||
|
# Default: 0 - (Disabled, only the defending faction gets the buff)
|
||||||
|
# 1 - (Enabled, attackers and defenders both get the buff,
|
||||||
|
# so both factions can access Vault of Archavon)
|
||||||
|
|
||||||
|
Wintergrasp.EssenceBothFactions = 0
|
||||||
|
|
||||||
#
|
#
|
||||||
###################################################################################################
|
###################################################################################################
|
||||||
|
|
||||||
|
|||||||
@@ -683,6 +683,7 @@ enum RBACPermissions
|
|||||||
RBAC_PERM_COMMAND_RESPAWN_GAMEOBJECT_ENTRY = 919,
|
RBAC_PERM_COMMAND_RESPAWN_GAMEOBJECT_ENTRY = 919,
|
||||||
RBAC_PERM_COMMAND_DEBUG_INFO = 920,
|
RBAC_PERM_COMMAND_DEBUG_INFO = 920,
|
||||||
RBAC_PERM_COMMAND_DEBUG_COSMETIC = 921,
|
RBAC_PERM_COMMAND_DEBUG_COSMETIC = 921,
|
||||||
|
RBAC_PERM_COMMAND_PET_RENAME = 922,
|
||||||
// custom permissions 1000+
|
// custom permissions 1000+
|
||||||
RBAC_PERM_MAX
|
RBAC_PERM_MAX
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -74,12 +74,27 @@ Battlefield::~Battlefield()
|
|||||||
CapturePoints.clear();
|
CapturePoints.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Battlefield::RemovePlayerFromTracking(ObjectGuid playerGuid)
|
||||||
|
{
|
||||||
|
for (uint8 i = 0; i < PVP_TEAMS_COUNT; ++i)
|
||||||
|
{
|
||||||
|
InvitedPlayers[i].erase(playerGuid);
|
||||||
|
PlayersInQueue[i].erase(playerGuid);
|
||||||
|
PlayersWillBeKick[i].erase(playerGuid);
|
||||||
|
Players[i].erase(playerGuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Battlefield::HandlePlayerEnterZone(Player* player, uint32 /*zone*/)
|
void Battlefield::HandlePlayerEnterZone(Player* player, uint32 /*zone*/)
|
||||||
{
|
{
|
||||||
|
RemovePlayerFromTracking(player->GetGUID());
|
||||||
|
|
||||||
// Allow scripts to adjust the player's effective team or appearance before
|
// Allow scripts to adjust the player's effective team or appearance before
|
||||||
// any team-based battlefield containers (such as player lists or queues) are updated.
|
// any team-based battlefield containers (such as player lists or queues) are updated.
|
||||||
sScriptMgr->OnBattlefieldPlayerEnterZone(this, player);
|
sScriptMgr->OnBattlefieldPlayerEnterZone(this, player);
|
||||||
|
|
||||||
|
TryRejoinAfterLogout(player); // relog: auto-rejoin, skip invite below
|
||||||
|
|
||||||
// Xinef: do not invite players on taxi
|
// Xinef: do not invite players on taxi
|
||||||
if (!player->IsInFlight())
|
if (!player->IsInFlight())
|
||||||
{
|
{
|
||||||
@@ -111,12 +126,20 @@ void Battlefield::HandlePlayerEnterZone(Player* player, uint32 /*zone*/)
|
|||||||
|
|
||||||
void Battlefield::HandlePlayerLeaveZone(Player* player, uint32 /*zone*/)
|
void Battlefield::HandlePlayerLeaveZone(Player* player, uint32 /*zone*/)
|
||||||
{
|
{
|
||||||
|
// Logout still runs full leave-war cleanup, but marks the player for grace-window auto-rejoin.
|
||||||
|
bool const isLogout = player->GetSession() && player->GetSession()->PlayerLogout();
|
||||||
|
|
||||||
if (IsWarTime())
|
if (IsWarTime())
|
||||||
{
|
{
|
||||||
// If the player is participating to the battle
|
// If the player is participating to the battle
|
||||||
if (PlayersInWar[player->GetTeamId()].erase(player->GetGUID()))
|
if (PlayersInWar[player->GetTeamId()].erase(player->GetGUID()))
|
||||||
{
|
{
|
||||||
player->GetSession()->SendBfLeaveMessage(BattleId);
|
if (isLogout)
|
||||||
|
LogoutGracePlayers[player->GetTeamId()][player->GetGUID()] =
|
||||||
|
GameTime::GetGameTime().count() + LOGOUT_GRACE_SECONDS;
|
||||||
|
else
|
||||||
|
player->GetSession()->SendBfLeaveMessage(BattleId);
|
||||||
|
|
||||||
if (Group* group = player->GetGroup()) // Remove the player from the raid group
|
if (Group* group = player->GetGroup()) // Remove the player from the raid group
|
||||||
if (group->isBFGroup())
|
if (group->isBFGroup())
|
||||||
group->RemoveMember(player->GetGUID());
|
group->RemoveMember(player->GetGUID());
|
||||||
@@ -129,13 +152,7 @@ void Battlefield::HandlePlayerLeaveZone(Player* player, uint32 /*zone*/)
|
|||||||
for (BfCapturePoint* cp : CapturePoints)
|
for (BfCapturePoint* cp : CapturePoints)
|
||||||
cp->HandlePlayerLeave(player);
|
cp->HandlePlayerLeave(player);
|
||||||
|
|
||||||
for (uint8 i = 0; i < PVP_TEAMS_COUNT; ++i)
|
RemovePlayerFromTracking(player->GetGUID());
|
||||||
{
|
|
||||||
InvitedPlayers[i].erase(player->GetGUID());
|
|
||||||
PlayersInQueue[i].erase(player->GetGUID());
|
|
||||||
PlayersWillBeKick[i].erase(player->GetGUID());
|
|
||||||
Players[i].erase(player->GetGUID());
|
|
||||||
}
|
|
||||||
SendRemoveWorldStates(player);
|
SendRemoveWorldStates(player);
|
||||||
RemovePlayerFromResurrectQueue(player->GetGUID());
|
RemovePlayerFromResurrectQueue(player->GetGUID());
|
||||||
OnPlayerLeaveZone(player);
|
OnPlayerLeaveZone(player);
|
||||||
@@ -235,6 +252,9 @@ void Battlefield::InvitePlayerToWar(Player* player)
|
|||||||
if (!player)
|
if (!player)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (player->IsGameMaster())
|
||||||
|
return;
|
||||||
|
|
||||||
/// @todo : needed ?
|
/// @todo : needed ?
|
||||||
if (player->IsInFlight())
|
if (player->IsInFlight())
|
||||||
return;
|
return;
|
||||||
@@ -292,7 +312,14 @@ void Battlefield::KickPlayerFromBattlefield(ObjectGuid guid)
|
|||||||
if (Player* player = ObjectAccessor::FindPlayer(guid))
|
if (Player* player = ObjectAccessor::FindPlayer(guid))
|
||||||
if (player->GetZoneId() == GetZoneId() && !player->IsGameMaster()
|
if (player->GetZoneId() == GetZoneId() && !player->IsGameMaster()
|
||||||
&& !PlayersInWar[player->GetTeamId()].count(guid))
|
&& !PlayersInWar[player->GetTeamId()].count(guid))
|
||||||
|
{
|
||||||
player->TeleportTo(KickPosition);
|
player->TeleportTo(KickPosition);
|
||||||
|
// Eagerly drop zone tracking: the teleport's zone change does not
|
||||||
|
// propagate until the next Player::Update, so callers iterating
|
||||||
|
// Players[team] in the same tick would otherwise still see them.
|
||||||
|
for (uint8 i = 0; i < PVP_TEAMS_COUNT; ++i)
|
||||||
|
Players[i].erase(guid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Battlefield::StartBattle()
|
void Battlefield::StartBattle()
|
||||||
@@ -304,6 +331,7 @@ void Battlefield::StartBattle()
|
|||||||
{
|
{
|
||||||
PlayersInWar[team].clear();
|
PlayersInWar[team].clear();
|
||||||
Groups[team].clear();
|
Groups[team].clear();
|
||||||
|
LogoutGracePlayers[team].clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer = BattleTime;
|
Timer = BattleTime;
|
||||||
@@ -318,11 +346,25 @@ void Battlefield::StartBattle()
|
|||||||
|
|
||||||
_scheduler.Schedule(1s, BATTLEFIELD_TIMER_GROUP_WAR, [this](TaskContext context)
|
_scheduler.Schedule(1s, BATTLEFIELD_TIMER_GROUP_WAR, [this](TaskContext context)
|
||||||
{
|
{
|
||||||
time_t now = GameTime::GetGameTime().count();
|
time_t const now = GameTime::GetGameTime().count();
|
||||||
|
|
||||||
|
// Send eject so the 3.3.5 client closes its popup (it does not on its
|
||||||
|
// own when the timer hits zero), then drop the entry and teleport.
|
||||||
for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team)
|
for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team)
|
||||||
for (PlayerTimerMap::value_type const& pair : InvitedPlayers[team])
|
{
|
||||||
if (pair.second <= now)
|
std::vector<ObjectGuid> expired;
|
||||||
KickPlayerFromBattlefield(pair.first);
|
for (auto const& [guid, expireAt] : InvitedPlayers[team])
|
||||||
|
if (expireAt <= now)
|
||||||
|
expired.push_back(guid);
|
||||||
|
|
||||||
|
for (ObjectGuid const& guid : expired)
|
||||||
|
{
|
||||||
|
if (Player* player = ObjectAccessor::FindPlayer(guid))
|
||||||
|
player->GetSession()->SendBfLeaveMessage(BattleId, BF_LEAVE_REASON_EXITED);
|
||||||
|
InvitedPlayers[team].erase(guid);
|
||||||
|
KickPlayerFromBattlefield(guid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
InvitePlayersInZoneToWar();
|
InvitePlayersInZoneToWar();
|
||||||
for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team)
|
for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team)
|
||||||
@@ -365,6 +407,14 @@ void Battlefield::EndBattle(bool endByTimer)
|
|||||||
OnBattleEnd(endByTimer);
|
OnBattleEnd(endByTimer);
|
||||||
sScriptMgr->OnBattlefieldWarEnd(this, endByTimer);
|
sScriptMgr->OnBattlefieldWarEnd(this, endByTimer);
|
||||||
|
|
||||||
|
for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team)
|
||||||
|
{
|
||||||
|
for (ObjectGuid const& guid : Groups[team])
|
||||||
|
if (Group* group = sGroupMgr->GetGroupByGUID(guid.GetCounter()))
|
||||||
|
group->Disband();
|
||||||
|
Groups[team].clear();
|
||||||
|
}
|
||||||
|
|
||||||
// Reset battlefield timer
|
// Reset battlefield timer
|
||||||
Timer = NoWarBattleTime;
|
Timer = NoWarBattleTime;
|
||||||
SendInitWorldStatesToAll();
|
SendInitWorldStatesToAll();
|
||||||
@@ -415,13 +465,21 @@ void Battlefield::PlayerAcceptInviteToWar(Player* player)
|
|||||||
if (!IsWarTime())
|
if (!IsWarTime())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Reject unknown / expired invites; the kick task only sweeps every 5s.
|
||||||
|
TeamId const invitedTeam = player->GetTeamId();
|
||||||
|
auto itr = InvitedPlayers[invitedTeam].find(player->GetGUID());
|
||||||
|
if (itr == InvitedPlayers[invitedTeam].end()
|
||||||
|
|| itr->second <= GameTime::GetGameTime().count())
|
||||||
|
return;
|
||||||
|
|
||||||
sScriptMgr->OnBattlefieldPlayerJoinWar(this, player);
|
sScriptMgr->OnBattlefieldPlayerJoinWar(this, player);
|
||||||
|
|
||||||
if (AddOrSetPlayerToCorrectBfGroup(player))
|
if (AddOrSetPlayerToCorrectBfGroup(player))
|
||||||
{
|
{
|
||||||
player->GetSession()->SendBfEntered(BattleId);
|
player->GetSession()->SendBfEntered(BattleId);
|
||||||
PlayersInWar[player->GetTeamId()].insert(player->GetGUID());
|
PlayersInWar[player->GetTeamId()].insert(player->GetGUID());
|
||||||
InvitedPlayers[player->GetTeamId()].erase(player->GetGUID());
|
// Use pre-hook team: JoinWar may have just reassigned GetTeamId().
|
||||||
|
InvitedPlayers[invitedTeam].erase(player->GetGUID());
|
||||||
|
|
||||||
if (player->isAFK())
|
if (player->isAFK())
|
||||||
player->ToggleAFK();
|
player->ToggleAFK();
|
||||||
@@ -549,6 +607,41 @@ bool Battlefield::AddOrSetPlayerToCorrectBfGroup(Player* player)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Battlefield::TryRejoinAfterLogout(Player* player)
|
||||||
|
{
|
||||||
|
ObjectGuid const guid = player->GetGUID();
|
||||||
|
time_t const now = GameTime::GetGameTime().count();
|
||||||
|
|
||||||
|
// Consume the marker and honor its grace window (check both teams; team may have changed).
|
||||||
|
bool pending = false;
|
||||||
|
for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team)
|
||||||
|
if (auto itr = LogoutGracePlayers[team].find(guid); itr != LogoutGracePlayers[team].end())
|
||||||
|
{
|
||||||
|
pending = itr->second > now;
|
||||||
|
LogoutGracePlayers[team].erase(itr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vacancy gate mirrors HandlePlayerEnterZone (full team -> queue path). Pre-hook:
|
||||||
|
// we can't abort after JoinWar, which may already have mutated module state.
|
||||||
|
if (!pending || !IsWarTime() || !HasWarVacancy(player->GetTeamId()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Group* current = player->GetGroup())
|
||||||
|
if (current->isBGGroup() || current->isBFGroup())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Rejoin via the normal join path: firing JoinWar lets modules rebuild
|
||||||
|
// per-session (Player*-keyed) state and pick the team before the raid bind.
|
||||||
|
sScriptMgr->OnBattlefieldPlayerJoinWar(this, player);
|
||||||
|
|
||||||
|
if (AddOrSetPlayerToCorrectBfGroup(player))
|
||||||
|
{
|
||||||
|
player->GetSession()->SendBfEntered(BattleId);
|
||||||
|
PlayersInWar[player->GetTeamId()].insert(guid);
|
||||||
|
OnPlayerJoinWar(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BfGraveyard* Battlefield::GetGraveyardById(uint32 id) const
|
BfGraveyard* Battlefield::GetGraveyardById(uint32 id) const
|
||||||
{
|
{
|
||||||
if (id < GraveyardList.size())
|
if (id < GraveyardList.size())
|
||||||
@@ -590,34 +683,22 @@ GraveyardStruct const* Battlefield::GetClosestGraveyard(Player* player)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Battlefield::AddPlayerToResurrectQueue(ObjectGuid npcGuid, ObjectGuid playerGuid)
|
void Battlefield::AddPlayerToResurrectQueue(ObjectGuid /*npcGuid*/, ObjectGuid playerGuid)
|
||||||
{
|
{
|
||||||
for (BfGraveyard* gy : GraveyardList)
|
Player* player = ObjectAccessor::FindPlayer(playerGuid);
|
||||||
{
|
if (!player)
|
||||||
if (!gy)
|
return;
|
||||||
continue;
|
|
||||||
|
|
||||||
if (gy->HasNpc(npcGuid))
|
player->CastSpell(player, SPELL_WAITING_FOR_RESURRECT, true);
|
||||||
{
|
|
||||||
gy->AddPlayer(playerGuid);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Battlefield::RemovePlayerFromResurrectQueue(ObjectGuid playerGuid)
|
void Battlefield::RemovePlayerFromResurrectQueue(ObjectGuid playerGuid)
|
||||||
{
|
{
|
||||||
for (BfGraveyard* gy : GraveyardList)
|
Player* player = ObjectAccessor::FindPlayer(playerGuid);
|
||||||
{
|
if (!player)
|
||||||
if (!gy)
|
return;
|
||||||
continue;
|
|
||||||
|
|
||||||
if (gy->HasPlayer(playerGuid))
|
player->RemoveAurasDueToSpell(SPELL_WAITING_FOR_RESURRECT);
|
||||||
{
|
|
||||||
gy->RemovePlayer(playerGuid);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Battlefield::SendAreaSpiritHealerQueryOpcode(Player* player, ObjectGuid const& guid)
|
void Battlefield::SendAreaSpiritHealerQueryOpcode(Player* player, ObjectGuid const& guid)
|
||||||
@@ -663,80 +744,9 @@ float BfGraveyard::GetDistance(Player* player)
|
|||||||
return player->GetDistance2d(safeLoc->x, safeLoc->y);
|
return player->GetDistance2d(safeLoc->x, safeLoc->y);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BfGraveyard::AddPlayer(ObjectGuid playerGuid)
|
|
||||||
{
|
|
||||||
if (!ResurrectQueue.count(playerGuid))
|
|
||||||
{
|
|
||||||
ResurrectQueue.insert(playerGuid);
|
|
||||||
|
|
||||||
if (Player* player = ObjectAccessor::FindPlayer(playerGuid))
|
|
||||||
player->CastSpell(player, SPELL_WAITING_FOR_RESURRECT, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BfGraveyard::RemovePlayer(ObjectGuid playerGuid)
|
|
||||||
{
|
|
||||||
ResurrectQueue.erase(ResurrectQueue.find(playerGuid));
|
|
||||||
|
|
||||||
if (Player* player = ObjectAccessor::FindPlayer(playerGuid))
|
|
||||||
player->RemoveAurasDueToSpell(SPELL_WAITING_FOR_RESURRECT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BfGraveyard::Resurrect()
|
|
||||||
{
|
|
||||||
if (ResurrectQueue.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (ObjectGuid const& guid : ResurrectQueue)
|
|
||||||
{
|
|
||||||
// Get player object from his guid
|
|
||||||
Player* player = ObjectAccessor::FindPlayer(guid);
|
|
||||||
if (!player)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Check if the player is in world and on the good graveyard
|
|
||||||
if (player->IsInWorld())
|
|
||||||
if (Unit* spirit = ObjectAccessor::GetCreature(*player, SpiritGuide[ControlTeam]))
|
|
||||||
spirit->CastSpell(spirit, SPELL_SPIRIT_HEAL, true);
|
|
||||||
|
|
||||||
// Resurrect player
|
|
||||||
player->CastSpell(player, SPELL_RESURRECTION_VISUAL, true);
|
|
||||||
player->ResurrectPlayer(1.0f);
|
|
||||||
player->CastSpell(player, 6962, true);
|
|
||||||
player->CastSpell(player, SPELL_SPIRIT_HEAL_MANA, true);
|
|
||||||
|
|
||||||
player->SpawnCorpseBones(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
ResurrectQueue.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// For changing graveyard control
|
|
||||||
void BfGraveyard::GiveControlTo(TeamId team)
|
void BfGraveyard::GiveControlTo(TeamId team)
|
||||||
{
|
{
|
||||||
ControlTeam = team;
|
ControlTeam = team;
|
||||||
// Teleport to other graveyard, players which were on this graveyard
|
|
||||||
RelocateDeadPlayers();
|
|
||||||
}
|
|
||||||
|
|
||||||
void BfGraveyard::RelocateDeadPlayers()
|
|
||||||
{
|
|
||||||
GraveyardStruct const* closestGrave = nullptr;
|
|
||||||
for (ObjectGuid const& guid : ResurrectQueue)
|
|
||||||
{
|
|
||||||
Player* player = ObjectAccessor::FindPlayer(guid);
|
|
||||||
if (!player)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (closestGrave)
|
|
||||||
player->TeleportTo(player->GetMapId(), closestGrave->x, closestGrave->y, closestGrave->z, player->GetOrientation());
|
|
||||||
else
|
|
||||||
{
|
|
||||||
closestGrave = Bf->GetClosestGraveyard(player);
|
|
||||||
if (closestGrave)
|
|
||||||
player->TeleportTo(player->GetMapId(), closestGrave->x, closestGrave->y, closestGrave->z, player->GetOrientation());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Creature* Battlefield::SpawnCreature(uint32 entry, Position pos, TeamId teamId)
|
Creature* Battlefield::SpawnCreature(uint32 entry, Position pos, TeamId teamId)
|
||||||
|
|||||||
@@ -169,18 +169,6 @@ public:
|
|||||||
// Set spirit service for the graveyard
|
// Set spirit service for the graveyard
|
||||||
void SetSpirit(Creature* spirit, TeamId team);
|
void SetSpirit(Creature* spirit, TeamId team);
|
||||||
|
|
||||||
// Add a player to the graveyard
|
|
||||||
void AddPlayer(ObjectGuid playerGuid);
|
|
||||||
|
|
||||||
// Remove a player from the graveyard
|
|
||||||
void RemovePlayer(ObjectGuid playerGuid);
|
|
||||||
|
|
||||||
// Resurrect players
|
|
||||||
void Resurrect();
|
|
||||||
|
|
||||||
// Move players waiting to that graveyard on the nearest one
|
|
||||||
void RelocateDeadPlayers();
|
|
||||||
|
|
||||||
// Check if this graveyard has a spirit guide
|
// Check if this graveyard has a spirit guide
|
||||||
bool HasNpc(ObjectGuid guid)
|
bool HasNpc(ObjectGuid guid)
|
||||||
{
|
{
|
||||||
@@ -190,8 +178,7 @@ public:
|
|||||||
return (SpiritGuide[0] == guid || SpiritGuide[1] == guid);
|
return (SpiritGuide[0] == guid || SpiritGuide[1] == guid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a player is in this graveyard's resurrect queue
|
ObjectGuid GetSpiritGuide(TeamId team) const { return SpiritGuide[team]; }
|
||||||
bool HasPlayer(ObjectGuid guid) const { return ResurrectQueue.find(guid) != ResurrectQueue.end(); }
|
|
||||||
|
|
||||||
// Get the graveyard's ID.
|
// Get the graveyard's ID.
|
||||||
uint32 GetGraveyardId() const { return GraveyardId; }
|
uint32 GetGraveyardId() const { return GraveyardId; }
|
||||||
@@ -200,7 +187,6 @@ protected:
|
|||||||
TeamId ControlTeam;
|
TeamId ControlTeam;
|
||||||
uint32 GraveyardId;
|
uint32 GraveyardId;
|
||||||
ObjectGuid SpiritGuide[2];
|
ObjectGuid SpiritGuide[2];
|
||||||
GuidUnorderedSet ResurrectQueue;
|
|
||||||
Battlefield* Bf;
|
Battlefield* Bf;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -292,6 +278,11 @@ public:
|
|||||||
/// Force player to join a battlefield group
|
/// Force player to join a battlefield group
|
||||||
bool AddOrSetPlayerToCorrectBfGroup(Player* player);
|
bool AddOrSetPlayerToCorrectBfGroup(Player* player);
|
||||||
|
|
||||||
|
/// Auto-rejoin a player who relogged within the grace window after a mid-war
|
||||||
|
/// logout, via the normal join hooks. No-op without a pending logout marker.
|
||||||
|
/// Called from HandlePlayerEnterZone (which fires on the post-login zone set).
|
||||||
|
void TryRejoinAfterLogout(Player* player);
|
||||||
|
|
||||||
// Graveyard methods
|
// Graveyard methods
|
||||||
// Find which graveyard the player must be teleported to to be resurrected by spiritguide
|
// Find which graveyard the player must be teleported to to be resurrected by spiritguide
|
||||||
GraveyardStruct const* GetClosestGraveyard(Player* player);
|
GraveyardStruct const* GetClosestGraveyard(Player* player);
|
||||||
@@ -395,6 +386,10 @@ protected:
|
|||||||
GuidUnorderedSet PlayersInWar[PVP_TEAMS_COUNT]; // Players in WG combat
|
GuidUnorderedSet PlayersInWar[PVP_TEAMS_COUNT]; // Players in WG combat
|
||||||
PlayerTimerMap InvitedPlayers[PVP_TEAMS_COUNT];
|
PlayerTimerMap InvitedPlayers[PVP_TEAMS_COUNT];
|
||||||
PlayerTimerMap PlayersWillBeKick[PVP_TEAMS_COUNT];
|
PlayerTimerMap PlayersWillBeKick[PVP_TEAMS_COUNT];
|
||||||
|
// Mid-war logouts: GUID -> timestamp until which a relog auto-rejoins the war.
|
||||||
|
PlayerTimerMap LogoutGracePlayers[PVP_TEAMS_COUNT];
|
||||||
|
|
||||||
|
static constexpr uint32 LOGOUT_GRACE_SECONDS = 120; // relog auto-rejoin window
|
||||||
|
|
||||||
// Variables that must exist for each battlefield
|
// Variables that must exist for each battlefield
|
||||||
uint32 TypeId; // See enum BattlefieldTypes
|
uint32 TypeId; // See enum BattlefieldTypes
|
||||||
@@ -444,6 +439,8 @@ protected:
|
|||||||
/// Returns true if the player is already tracked as actively in the war or invited to join it.
|
/// Returns true if the player is already tracked as actively in the war or invited to join it.
|
||||||
bool IsPlayerInWarOrInvited(Player* player) const;
|
bool IsPlayerInWarOrInvited(Player* player) const;
|
||||||
|
|
||||||
|
void RemovePlayerFromTracking(ObjectGuid playerGuid);
|
||||||
|
|
||||||
// Player-iteration helpers: resolve each GUID to a live Player* and call fn(player).
|
// Player-iteration helpers: resolve each GUID to a live Player* and call fn(player).
|
||||||
// Using templates avoids std::function overhead and works naturally with lambdas.
|
// Using templates avoids std::function overhead and works naturally with lambdas.
|
||||||
template<typename Func>
|
template<typename Func>
|
||||||
|
|||||||
@@ -112,6 +112,13 @@ Battlefield* BattlefieldMgr::GetBattlefieldByBattleId(uint32 battleId)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BattlefieldMgr::IsWintergraspAttackerVictory()
|
||||||
|
{
|
||||||
|
if (Battlefield* bf = GetBattlefieldByBattleId(BATTLEFIELD_BATTLEID_WG))
|
||||||
|
return static_cast<BattlefieldWG*>(bf)->IsLastBattleAttackerVictory();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void BattlefieldMgr::Update(uint32 diff)
|
void BattlefieldMgr::Update(uint32 diff)
|
||||||
{
|
{
|
||||||
_updateTimer += diff;
|
_updateTimer += diff;
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ public:
|
|||||||
Battlefield* GetBattlefieldToZoneId(uint32 zoneId);
|
Battlefield* GetBattlefieldToZoneId(uint32 zoneId);
|
||||||
Battlefield* GetBattlefieldByBattleId(uint32 battleId);
|
Battlefield* GetBattlefieldByBattleId(uint32 battleId);
|
||||||
|
|
||||||
|
// True iff the most recent Wintergrasp battle ended with the keep captured.
|
||||||
|
[[nodiscard]] bool IsWintergraspAttackerVictory();
|
||||||
|
|
||||||
ZoneScript* GetZoneScript(uint32 zoneId);
|
ZoneScript* GetZoneScript(uint32 zoneId);
|
||||||
|
|
||||||
void AddZone(uint32 zoneId, Battlefield* handle);
|
void AddZone(uint32 zoneId, Battlefield* handle);
|
||||||
|
|||||||
@@ -193,9 +193,39 @@ bool BattlefieldWG::SetupBattlefield()
|
|||||||
_scheduler.Schedule(Milliseconds(RESURRECTION_INTERVAL),
|
_scheduler.Schedule(Milliseconds(RESURRECTION_INTERVAL),
|
||||||
BATTLEFIELD_TIMER_GROUP_RESURRECT, [this](TaskContext context)
|
BATTLEFIELD_TIMER_GROUP_RESURRECT, [this](TaskContext context)
|
||||||
{
|
{
|
||||||
for (BfGraveyard* gy : GraveyardList)
|
ForEachPlayerInZone([this](Player* player)
|
||||||
if (gy)
|
{
|
||||||
gy->Resurrect();
|
if (!player->HasAura(SPELL_WAITING_FOR_RESURRECT))
|
||||||
|
return;
|
||||||
|
|
||||||
|
TeamId team = player->GetTeamId();
|
||||||
|
Unit* closestSpirit = nullptr;
|
||||||
|
float closestDist = -1.0f;
|
||||||
|
for (BfGraveyard* gy : GraveyardList)
|
||||||
|
{
|
||||||
|
if (!gy)
|
||||||
|
continue;
|
||||||
|
Unit* spirit = ObjectAccessor::GetCreature(*player, gy->GetSpiritGuide(team));
|
||||||
|
if (!spirit)
|
||||||
|
continue;
|
||||||
|
float dist = player->GetDistance(spirit);
|
||||||
|
if (closestDist < 0.0f || dist < closestDist)
|
||||||
|
{
|
||||||
|
closestDist = dist;
|
||||||
|
closestSpirit = spirit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closestSpirit)
|
||||||
|
closestSpirit->CastSpell(closestSpirit, SPELL_SPIRIT_HEAL, true);
|
||||||
|
|
||||||
|
player->CastSpell(player, SPELL_RESURRECTION_VISUAL, true);
|
||||||
|
player->ResurrectPlayer(1.0f);
|
||||||
|
player->CastSpell(player, 6962, true);
|
||||||
|
player->CastSpell(player, SPELL_SPIRIT_HEAL_MANA, true);
|
||||||
|
player->SpawnCorpseBones(false);
|
||||||
|
player->RemoveAurasDueToSpell(SPELL_WAITING_FOR_RESURRECT);
|
||||||
|
});
|
||||||
context.Repeat();
|
context.Repeat();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -266,19 +296,7 @@ void BattlefieldWG::OnBattleStart()
|
|||||||
capturePoint->SetCapturePointData(capturePoint->GetCapturePointGo(),
|
capturePoint->SetCapturePointData(capturePoint->GetCapturePointGo(),
|
||||||
capturePoint->GetCapturePointGo()->GetEntry() == GO_WINTERGRASP_FACTORY_BANNER_SE || capturePoint->GetCapturePointGo()->GetEntry() == GO_WINTERGRASP_FACTORY_BANNER_SW ? GetAttackerTeam() : GetDefenderTeam());
|
capturePoint->GetCapturePointGo()->GetEntry() == GO_WINTERGRASP_FACTORY_BANNER_SE || capturePoint->GetCapturePointGo()->GetEntry() == GO_WINTERGRASP_FACTORY_BANNER_SW ? GetAttackerTeam() : GetDefenderTeam());
|
||||||
|
|
||||||
for (uint8 team = 0; team < 2; ++team)
|
SendInitWorldStatesToAll();
|
||||||
for (ObjectGuid const& guid : Players[team])
|
|
||||||
{
|
|
||||||
// Kick player in orb room, TODO: offline player ?
|
|
||||||
if (Player* player = ObjectAccessor::FindPlayer(guid))
|
|
||||||
{
|
|
||||||
float x, y, z;
|
|
||||||
player->GetPosition(x, y, z);
|
|
||||||
if (5500 > x && x > 5392 && y < 2880 && y > 2800 && z < 480)
|
|
||||||
player->TeleportTo(MAP_NORTHREND, 5349.8686f, 2838.481f, 409.240f, 0.046328f);
|
|
||||||
SendInitWorldStatesTo(player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Initialize vehicle counter
|
// Initialize vehicle counter
|
||||||
UpdateCounterVehicle(true);
|
UpdateCounterVehicle(true);
|
||||||
// Send start warning to all players
|
// Send start warning to all players
|
||||||
@@ -348,6 +366,9 @@ void BattlefieldWG::CapturePointTaken(uint32 areaId)
|
|||||||
|
|
||||||
void BattlefieldWG::OnBattleEnd(bool endByTimer)
|
void BattlefieldWG::OnBattleEnd(bool endByTimer)
|
||||||
{
|
{
|
||||||
|
// Must be set before SPELL_VICTORY_REWARD so the 1755 criterion can gate on it.
|
||||||
|
LastBattleAttackerVictory = !endByTimer;
|
||||||
|
|
||||||
// Remove relic
|
// Remove relic
|
||||||
if (GameObject* go = GetRelic())
|
if (GameObject* go = GetRelic())
|
||||||
go->RemoveFromWorld();
|
go->RemoveFromWorld();
|
||||||
@@ -458,12 +479,17 @@ void BattlefieldWG::OnBattleEnd(bool endByTimer)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool const grantEssenceToAttackers = sWorld->getBoolConfig(CONFIG_WINTERGRASP_ESSENCE_BOTH_FACTIONS);
|
||||||
|
|
||||||
for (ObjectGuid const& guid : PlayersInWar[GetAttackerTeam()])
|
for (ObjectGuid const& guid : PlayersInWar[GetAttackerTeam()])
|
||||||
if (Player* player = ObjectAccessor::FindPlayer(guid))
|
if (Player* player = ObjectAccessor::FindPlayer(guid))
|
||||||
{
|
{
|
||||||
player->CastSpell(player, SPELL_DEFEAT_REWARD, true);
|
player->CastSpell(player, SPELL_DEFEAT_REWARD, true);
|
||||||
RemoveAurasFromPlayer(player);
|
RemoveAurasFromPlayer(player);
|
||||||
|
|
||||||
|
if (grantEssenceToAttackers)
|
||||||
|
player->CastSpell(player, SPELL_ESSENCE_OF_WINTERGRASP, true);
|
||||||
|
|
||||||
for (uint8 i = 0; i < damagedTowersAtt; ++i)
|
for (uint8 i = 0; i < damagedTowersAtt; ++i)
|
||||||
player->CastSpell(player, spellDamagedAtt, true);
|
player->CastSpell(player, spellDamagedAtt, true);
|
||||||
for (uint8 i = 0; i < brokenTowersAtt; ++i)
|
for (uint8 i = 0; i < brokenTowersAtt; ++i)
|
||||||
@@ -908,8 +934,8 @@ void BattlefieldWG::OnPlayerEnterZone(Player* player)
|
|||||||
// Send worldstate to player
|
// Send worldstate to player
|
||||||
SendInitWorldStatesTo(player);
|
SendInitWorldStatesTo(player);
|
||||||
|
|
||||||
// xinef: Attacker, if hidden in relic room kick him out
|
// xinef: Attacker, if hidden in relic room kick him out (only during wartime)
|
||||||
if (player->GetTeamId() == GetAttackerTeam())
|
if (IsWarTime() && player->GetTeamId() == GetAttackerTeam())
|
||||||
if (player->GetPositionX() > 5400.0f && player->GetPositionX() < 5490.0f && player->GetPositionY() > 2803.0f && player->GetPositionY() < 2878.0f)
|
if (player->GetPositionX() > 5400.0f && player->GetPositionX() < 5490.0f && player->GetPositionY() > 2803.0f && player->GetPositionY() < 2878.0f)
|
||||||
KickPlayerFromBattlefield(player->GetGUID());
|
KickPlayerFromBattlefield(player->GetGUID());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -403,6 +403,9 @@ public:
|
|||||||
|
|
||||||
uint32 GetData(uint32 data) const override;
|
uint32 GetData(uint32 data) const override;
|
||||||
|
|
||||||
|
// True iff the most recent battle ended with the keep captured (attacker win).
|
||||||
|
[[nodiscard]] bool IsLastBattleAttackerVictory() const { return LastBattleAttackerVictory; }
|
||||||
|
|
||||||
bool IsKeepNpc(uint32 entry)
|
bool IsKeepNpc(uint32 entry)
|
||||||
{
|
{
|
||||||
switch (entry)
|
switch (entry)
|
||||||
@@ -451,6 +454,8 @@ protected:
|
|||||||
int32 TenacityStack;
|
int32 TenacityStack;
|
||||||
|
|
||||||
ObjectGuid TitansRelic;
|
ObjectGuid TitansRelic;
|
||||||
|
|
||||||
|
bool LastBattleAttackerVictory{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8 const WG_MAX_OBJ = 32;
|
uint8 const WG_MAX_OBJ = 32;
|
||||||
|
|||||||
@@ -675,7 +675,7 @@ inline void Battleground::_ProcessJoin(uint32 diff)
|
|||||||
|
|
||||||
// Announce BG starting
|
// Announce BG starting
|
||||||
if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_ENABLE))
|
if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_ENABLE))
|
||||||
ChatHandler(nullptr).SendWorldText(LANG_BG_STARTED_ANNOUNCE_WORLD, GetName(), std::min(GetMinLevel(), (uint32)80), std::min(GetMaxLevel(), (uint32)80));
|
ChatHandler(nullptr).SendWorldTextOptional(LANG_BG_STARTED_ANNOUNCE_WORLD, ANNOUNCER_FLAG_DISABLE_PVP_START, GetName(), std::min(GetMinLevel(), (uint32)80), std::min(GetMaxLevel(), (uint32)80));
|
||||||
|
|
||||||
sScriptMgr->OnBattlegroundStart(this);
|
sScriptMgr->OnBattlegroundStart(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1583,7 +1583,10 @@ uint8 BattlegroundAV::GetAttackString(BG_AV_Nodes node, TeamId teamId)
|
|||||||
strId = AV_TEXT_H_HERALD_SNOWFALL_GRAVEYARD_ATTACK;
|
strId = AV_TEXT_H_HERALD_SNOWFALL_GRAVEYARD_ATTACK;
|
||||||
break;
|
break;
|
||||||
case BG_AV_NODES_ICEBLOOD_TOWER:
|
case BG_AV_NODES_ICEBLOOD_TOWER:
|
||||||
strId = AV_TEXT_A_HERALD_ICEBLOOD_TOWER_ATTACK;
|
if (teamId == TEAM_ALLIANCE)
|
||||||
|
strId = AV_TEXT_A_HERALD_ICEBLOOD_TOWER_ATTACK;
|
||||||
|
else
|
||||||
|
strId = AV_TEXT_H_HERALD_ICEBLOOD_TOWER_ATTACK;
|
||||||
break;
|
break;
|
||||||
case BG_AV_NODES_ICEBLOOD_GRAVE:
|
case BG_AV_NODES_ICEBLOOD_GRAVE:
|
||||||
if (teamId == TEAM_ALLIANCE)
|
if (teamId == TEAM_ALLIANCE)
|
||||||
@@ -1682,7 +1685,10 @@ uint8 BattlegroundAV::GetDefendString(BG_AV_Nodes node, TeamId teamId)
|
|||||||
strId = AV_TEXT_H_HERALD_SNOWFALL_GRAVEYARD_TAKEN;
|
strId = AV_TEXT_H_HERALD_SNOWFALL_GRAVEYARD_TAKEN;
|
||||||
break;
|
break;
|
||||||
case BG_AV_NODES_ICEBLOOD_TOWER:
|
case BG_AV_NODES_ICEBLOOD_TOWER:
|
||||||
strId = AV_TEXT_A_HERALD_ICEBLOOD_TOWER_TAKEN;
|
if (teamId == TEAM_ALLIANCE)
|
||||||
|
strId = AV_TEXT_A_HERALD_ICEBLOOD_TOWER_TAKEN;
|
||||||
|
else
|
||||||
|
strId = AV_TEXT_H_HERALD_ICEBLOOD_TOWER_TAKEN;
|
||||||
break;
|
break;
|
||||||
case BG_AV_NODES_ICEBLOOD_GRAVE:
|
case BG_AV_NODES_ICEBLOOD_GRAVE:
|
||||||
if (teamId == TEAM_ALLIANCE)
|
if (teamId == TEAM_ALLIANCE)
|
||||||
|
|||||||
@@ -1622,50 +1622,50 @@ enum BG_AV_Objectives
|
|||||||
|
|
||||||
enum BG_AV_HERALD
|
enum BG_AV_HERALD
|
||||||
{
|
{
|
||||||
AV_TEXT_A_HERALD_STORMPIKE_GRAVEYARD_TAKEN = 1,
|
AV_TEXT_A_HERALD_STORMPIKE_GRAVEYARD_TAKEN = 1,
|
||||||
AV_TEXT_A_HERALD_STORMPIKE_GRAVEYARD_ATTACK = 2,
|
AV_TEXT_A_HERALD_STORMPIKE_GRAVEYARD_ATTACK = 2,
|
||||||
AV_TEXT_H_HERALD_STORMPIKE_GRAVEYARD_TAKEN = 3,
|
AV_TEXT_H_HERALD_STORMPIKE_GRAVEYARD_TAKEN = 3,
|
||||||
AV_TEXT_H_HERALD_STORMPIKE_GRAVEYARD_ATTACK = 4,
|
AV_TEXT_H_HERALD_STORMPIKE_GRAVEYARD_ATTACK = 4,
|
||||||
AV_TEXT_A_HERALD_SNOWFALL_GRAVEYARD_TAKEN = 5,
|
AV_TEXT_A_HERALD_SNOWFALL_GRAVEYARD_TAKEN = 5,
|
||||||
AV_TEXT_A_HERALD_SNOWFALL_GRAVEYARD_ATTACK = 6,
|
AV_TEXT_A_HERALD_SNOWFALL_GRAVEYARD_ATTACK = 6,
|
||||||
AV_TEXT_H_HERALD_SNOWFALL_GRAVEYARD_TAKEN = 7,
|
AV_TEXT_H_HERALD_SNOWFALL_GRAVEYARD_TAKEN = 7,
|
||||||
AV_TEXT_H_HERALD_SNOWFALL_GRAVEYARD_ATTACK = 8,
|
AV_TEXT_H_HERALD_SNOWFALL_GRAVEYARD_ATTACK = 8,
|
||||||
AV_TEXT_A_HERALD_FROSTWOLF_GRAVEYARD_TAKEN = 9,
|
AV_TEXT_A_HERALD_FROSTWOLF_GRAVEYARD_TAKEN = 9,
|
||||||
AV_TEXT_A_HERALD_FROSTWOLF_GRAVEYARD_ATTACK = 10,
|
AV_TEXT_A_HERALD_FROSTWOLF_GRAVEYARD_ATTACK = 10,
|
||||||
AV_TEXT_H_HERALD_FROSTWOLF_GRAVEYARD_TAKEN = 11,
|
AV_TEXT_H_HERALD_FROSTWOLF_GRAVEYARD_TAKEN = 11,
|
||||||
AV_TEXT_H_HERALD_FROSTWOLF_GRAVEYARD_ATTACK = 12,
|
AV_TEXT_H_HERALD_FROSTWOLF_GRAVEYARD_ATTACK = 12,
|
||||||
AV_TEXT_H_HERALD_DUN_BALDAR_SOUTH_ATTACK = 13,
|
AV_TEXT_H_HERALD_DUN_BALDAR_SOUTH_ATTACK = 13,
|
||||||
AV_TEXT_H_HERALD_DUN_BALDAR_SOUTH_TAKEN = 14,
|
AV_TEXT_H_HERALD_DUN_BALDAR_SOUTH_TAKEN = 14,
|
||||||
AV_TEXT_A_HERALD_DUN_BALDAR_SOUTH_ATTACK = 15,
|
AV_TEXT_A_HERALD_DUN_BALDAR_SOUTH_ATTACK = 15,
|
||||||
AV_TEXT_A_HERALD_DUN_BALDAR_SOUTH_TAKEN = 16,
|
AV_TEXT_A_HERALD_DUN_BALDAR_SOUTH_TAKEN = 16,
|
||||||
AV_TEXT_H_HERALD_DUN_BALDAR_NORTH_ATTACK = 17,
|
AV_TEXT_H_HERALD_DUN_BALDAR_NORTH_ATTACK = 17,
|
||||||
AV_TEXT_H_HERALD_DUN_BALDAR_NORTH_TAKEN = 18,
|
AV_TEXT_H_HERALD_DUN_BALDAR_NORTH_TAKEN = 18,
|
||||||
AV_TEXT_A_HERALD_DUN_BALDAR_NORTH_ATTACK = 19,
|
AV_TEXT_A_HERALD_DUN_BALDAR_NORTH_ATTACK = 19,
|
||||||
AV_TEXT_A_HERALD_DUN_BALDAR_NORTH_TAKEN = 20,
|
AV_TEXT_A_HERALD_DUN_BALDAR_NORTH_TAKEN = 20,
|
||||||
AV_TEXT_H_HERALD_ICEWING_BUNKER_ATTACK = 21,
|
AV_TEXT_H_HERALD_ICEWING_BUNKER_ATTACK = 21,
|
||||||
AV_TEXT_H_HERALD_ICEWING_BUNKER_TAKEN = 22,
|
AV_TEXT_H_HERALD_ICEWING_BUNKER_TAKEN = 22,
|
||||||
AV_TEXT_A_HERALD_ICEWING_BUNKER_ATTACK = 23,
|
AV_TEXT_A_HERALD_ICEWING_BUNKER_ATTACK = 23,
|
||||||
AV_TEXT_A_HERALD_ICEWING_BUNKER_TAKEN = 24,
|
AV_TEXT_A_HERALD_ICEWING_BUNKER_TAKEN = 24,
|
||||||
AV_TEXT_H_HERALD_STONEHEARTH_BUNKER_ATTACK = 25,
|
AV_TEXT_H_HERALD_STONEHEARTH_BUNKER_ATTACK = 25,
|
||||||
AV_TEXT_H_HERALD_STONEHEARTH_BUNKER_TAKEN = 26,
|
AV_TEXT_H_HERALD_STONEHEARTH_BUNKER_TAKEN = 26,
|
||||||
AV_TEXT_A_HERALD_STONEHEARTH_BUNKER_ATTACK = 27,
|
AV_TEXT_A_HERALD_STONEHEARTH_BUNKER_ATTACK = 27,
|
||||||
AV_TEXT_A_HERALD_STONEHEARTH_BUNKER_TAKEN = 28,
|
AV_TEXT_A_HERALD_STONEHEARTH_BUNKER_TAKEN = 28,
|
||||||
AV_TEXT_H_HERALD_WEST_FROSTWOLF_TOWER_ATTACK = 29,
|
AV_TEXT_H_HERALD_WEST_FROSTWOLF_TOWER_ATTACK = 29,
|
||||||
AV_TEXT_H_HERALD_WEST_FROSTWOLF_TOWER_TAKEN = 30,
|
AV_TEXT_H_HERALD_WEST_FROSTWOLF_TOWER_TAKEN = 30,
|
||||||
AV_TEXT_A_HERALD_WEST_FROSTWOLF_TOWER_ATTACK = 31,
|
AV_TEXT_A_HERALD_WEST_FROSTWOLF_TOWER_ATTACK = 31,
|
||||||
AV_TEXT_A_HERALD_WEST_FROSTWOLF_TOWER_TAKEN = 32,
|
AV_TEXT_A_HERALD_WEST_FROSTWOLF_TOWER_TAKEN = 32,
|
||||||
AV_TEXT_H_HERALD_EAST_FROSTWOLF_TOWER_ATTACK = 33,
|
AV_TEXT_H_HERALD_EAST_FROSTWOLF_TOWER_ATTACK = 33,
|
||||||
AV_TEXT_H_HERALD_EAST_FROSTWOLF_TOWER_TAKEN = 34,
|
AV_TEXT_H_HERALD_EAST_FROSTWOLF_TOWER_TAKEN = 34,
|
||||||
AV_TEXT_A_HERALD_EAST_FROSTWOLF_TOWER_ATTACK = 35,
|
AV_TEXT_A_HERALD_EAST_FROSTWOLF_TOWER_ATTACK = 35,
|
||||||
AV_TEXT_A_HERALD_EAST_FROSTWOLF_TOWER_TAKEN = 36,
|
AV_TEXT_A_HERALD_EAST_FROSTWOLF_TOWER_TAKEN = 36,
|
||||||
AV_TEXT_H_HERALD_TOWER_POINT_ATTACK = 37,
|
AV_TEXT_H_HERALD_TOWER_POINT_ATTACK = 37,
|
||||||
AV_TEXT_H_HERALD_TOWER_POINT_TAKEN = 38,
|
AV_TEXT_H_HERALD_TOWER_POINT_TAKEN = 38,
|
||||||
AV_TEXT_A_HERALD_TOWER_POINT_ATTACK = 39,
|
AV_TEXT_A_HERALD_TOWER_POINT_ATTACK = 39,
|
||||||
AV_TEXT_A_HERALD_TOWER_POINT_TAKEN = 40,
|
AV_TEXT_A_HERALD_TOWER_POINT_TAKEN = 40,
|
||||||
AV_TEXT_H_HERALD_ICEBLOOD_BUNKER_ATTACK = 41,
|
AV_TEXT_H_HERALD_ICEBLOOD_TOWER_ATTACK = 41,
|
||||||
AV_TEXT_H_HERALD_ICEBLOOD_BUNKER_TAKEN = 42,
|
AV_TEXT_H_HERALD_ICEBLOOD_TOWER_TAKEN = 42,
|
||||||
AV_TEXT_A_HERALD_ICEBLOOD_TOWER_ATTACK = 43,
|
AV_TEXT_A_HERALD_ICEBLOOD_TOWER_ATTACK = 43,
|
||||||
AV_TEXT_A_HERALD_ICEBLOOD_TOWER_TAKEN = 44,
|
AV_TEXT_A_HERALD_ICEBLOOD_TOWER_TAKEN = 44,
|
||||||
AV_TEXT_H_HERALD_STORMPIKE_AID_STATION_TAKEN = 45,
|
AV_TEXT_H_HERALD_STORMPIKE_AID_STATION_TAKEN = 45,
|
||||||
AV_TEXT_H_HERALD_STORMPIKE_AID_STATION_ATTACK = 46,
|
AV_TEXT_H_HERALD_STORMPIKE_AID_STATION_ATTACK = 46,
|
||||||
AV_TEXT_A_HERALD_STORMPIKE_AID_STATION_TAKEN = 47,
|
AV_TEXT_A_HERALD_STORMPIKE_AID_STATION_TAKEN = 47,
|
||||||
|
|||||||
@@ -1692,18 +1692,32 @@ namespace lfg
|
|||||||
LfgGuidList players;
|
LfgGuidList players;
|
||||||
GuidUnorderedSet playersToTeleport;
|
GuidUnorderedSet playersToTeleport;
|
||||||
|
|
||||||
for (LfgProposalPlayerContainer::const_iterator it = proposal.players.begin(); it != proposal.players.end(); ++it)
|
// Sort players by role, leader first, then tank, healer and dps
|
||||||
{
|
std::vector<ObjectGuid> tanks, healers, dps;
|
||||||
ObjectGuid guid = it->first;
|
|
||||||
if (guid == proposal.leader)
|
|
||||||
players.push_front(guid);
|
|
||||||
else
|
|
||||||
players.push_back(guid);
|
|
||||||
|
|
||||||
|
if (proposal.leader && proposal.players.contains(proposal.leader))
|
||||||
|
players.push_back(proposal.leader);
|
||||||
|
|
||||||
|
for (auto const& [guid, player] : proposal.players)
|
||||||
|
{
|
||||||
if (proposal.isNew || GetGroup(guid) != proposal.group)
|
if (proposal.isNew || GetGroup(guid) != proposal.group)
|
||||||
playersToTeleport.insert(guid);
|
playersToTeleport.insert(guid);
|
||||||
|
|
||||||
|
if (guid == proposal.leader)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (player.role & lfg::PLAYER_ROLE_TANK)
|
||||||
|
tanks.push_back(guid);
|
||||||
|
else if (player.role & lfg::PLAYER_ROLE_HEALER)
|
||||||
|
healers.push_back(guid);
|
||||||
|
else
|
||||||
|
dps.push_back(guid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
players.insert(players.end(), tanks.begin(), tanks.end());
|
||||||
|
players.insert(players.end(), healers.begin(), healers.end());
|
||||||
|
players.insert(players.end(), dps.begin(), dps.end());
|
||||||
|
|
||||||
// Set the dungeon difficulty
|
// Set the dungeon difficulty
|
||||||
LFGDungeonData const* dungeon = GetLFGDungeon(proposal.dungeonId);
|
LFGDungeonData const* dungeon = GetLFGDungeon(proposal.dungeonId);
|
||||||
ASSERT(dungeon);
|
ASSERT(dungeon);
|
||||||
|
|||||||
@@ -5384,6 +5384,7 @@ void Player::SetSkill(uint16 id, uint16 step, uint16 newVal, uint16 maxVal)
|
|||||||
RemoveAurasDueToSpell(pAbility->Spell);
|
RemoveAurasDueToSpell(pAbility->Spell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sScriptMgr->OnPlayerSetSkill(this, id, currVal, maxVal, step, newVal);
|
||||||
}
|
}
|
||||||
else if (newVal) //add
|
else if (newVal) //add
|
||||||
{
|
{
|
||||||
@@ -5430,6 +5431,7 @@ void Player::SetSkill(uint16 id, uint16 step, uint16 newVal, uint16 maxVal)
|
|||||||
learnSkillRewardedSpells(id, newVal);
|
learnSkillRewardedSpells(id, newVal);
|
||||||
UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, id);
|
UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_REACH_SKILL_LEVEL, id);
|
||||||
UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LEVEL, id);
|
UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LEVEL, id);
|
||||||
|
sScriptMgr->OnPlayerSetSkill(this, id, currVal, maxVal, step, newVal);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13115,6 +13117,11 @@ PartyResult Player::CanUninviteFromGroup(ObjectGuid targetPlayerGUID) const
|
|||||||
|
|
||||||
if (InBattleground())
|
if (InBattleground())
|
||||||
return ERR_INVITE_RESTRICTED;
|
return ERR_INVITE_RESTRICTED;
|
||||||
|
|
||||||
|
// BF raids are owned by the Battlefield system; leaders/assistants must not
|
||||||
|
// be able to kick members (would drop their team assignment mid-battle).
|
||||||
|
if (grp->isBFGroup())
|
||||||
|
return ERR_NOT_LEADER;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ERR_PARTY_RESULT_OK;
|
return ERR_PARTY_RESULT_OK;
|
||||||
|
|||||||
@@ -33,7 +33,11 @@ enum AnnouncerFlags : uint8
|
|||||||
{
|
{
|
||||||
ANNOUNCER_FLAG_DISABLE_BG_QUEUE = 1,
|
ANNOUNCER_FLAG_DISABLE_BG_QUEUE = 1,
|
||||||
ANNOUNCER_FLAG_DISABLE_ARENA_QUEUE = 2,
|
ANNOUNCER_FLAG_DISABLE_ARENA_QUEUE = 2,
|
||||||
ANNOUNCER_FLAG_DISABLE_AUTOBROADCAST = 4
|
ANNOUNCER_FLAG_DISABLE_AUTOBROADCAST = 4,
|
||||||
|
ANNOUNCER_FLAG_DISABLE_PVP_START = 8,
|
||||||
|
ANNOUNCER_FLAG_DISABLE_PVP_ALL = ANNOUNCER_FLAG_DISABLE_BG_QUEUE
|
||||||
|
| ANNOUNCER_FLAG_DISABLE_ARENA_QUEUE
|
||||||
|
| ANNOUNCER_FLAG_DISABLE_PVP_START
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PlayerSetting
|
struct PlayerSetting
|
||||||
|
|||||||
@@ -6234,6 +6234,32 @@ Item* Player::_LoadMailedItem(ObjectGuid const& playerGuid, Player* player, uint
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rehydrate looters for BoP-tradeable mail items; only the LFG mail path writes this flag, gated on the same config.
|
||||||
|
if (item->IsBOPTradable() && sWorld->getBoolConfig(CONFIG_SET_BOP_ITEM_TRADEABLE))
|
||||||
|
{
|
||||||
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEM_BOP_TRADE);
|
||||||
|
stmt->SetData(0, item->GetGUID().GetCounter());
|
||||||
|
|
||||||
|
if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
|
||||||
|
{
|
||||||
|
AllowedLooterSet looters;
|
||||||
|
for (std::string_view guidStr : Acore::Tokenize((*result)[0].Get<std::string_view>(), ' ', false))
|
||||||
|
{
|
||||||
|
if (Optional<ObjectGuid::LowType> guid = Acore::StringTo<ObjectGuid::LowType>(guidStr))
|
||||||
|
looters.insert(ObjectGuid::Create<HighGuid::Player>(*guid));
|
||||||
|
else
|
||||||
|
LOG_WARN("entities.player.loading", "Player::_LoadMailedItem: invalid item_soulbound_trade_data GUID '{}' for item {}. Skipped.", guidStr, item->GetGUID().ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (looters.size() > 1 && proto->GetMaxStackSize() == 1 && item->IsSoulBound())
|
||||||
|
item->SetSoulboundTradeable(looters);
|
||||||
|
else
|
||||||
|
item->RemoveFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_BOP_TRADEABLE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
item->RemoveFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_BOP_TRADEABLE);
|
||||||
|
}
|
||||||
|
|
||||||
if (mail)
|
if (mail)
|
||||||
{
|
{
|
||||||
mail->AddItem(itemGuid, itemEntry);
|
mail->AddItem(itemGuid, itemEntry);
|
||||||
|
|||||||
@@ -954,6 +954,9 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, Unit
|
|||||||
for (auto const& [immunitySchoolMask, immunityAuraId] : schoolList)
|
for (auto const& [immunitySchoolMask, immunityAuraId] : schoolList)
|
||||||
{
|
{
|
||||||
SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(immunityAuraId);
|
SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(immunityAuraId);
|
||||||
|
if (immunityAuraId == spellInfo->Id)
|
||||||
|
continue;
|
||||||
|
|
||||||
if ((immunitySchoolMask & schoolMask) != schoolMask)
|
if ((immunitySchoolMask & schoolMask) != schoolMask)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -10093,6 +10096,9 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell)
|
|||||||
SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL];
|
SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL];
|
||||||
for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr)
|
for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr)
|
||||||
{
|
{
|
||||||
|
if (itr->second == spellInfo->Id)
|
||||||
|
continue;
|
||||||
|
|
||||||
SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(itr->second);
|
SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(itr->second);
|
||||||
if (!(itr->first & spellSchoolMask))
|
if (!(itr->first & spellSchoolMask))
|
||||||
continue;
|
continue;
|
||||||
@@ -10624,6 +10630,8 @@ void Unit::Dismount()
|
|||||||
if (charm->IsCreature() && !charm->HasUnitState(UNIT_STATE_STUNNED))
|
if (charm->IsCreature() && !charm->HasUnitState(UNIT_STATE_STUNNED))
|
||||||
charm->RemoveUnitFlag(UNIT_FLAG_STUNNED);
|
charm->RemoveUnitFlag(UNIT_FLAG_STUNNED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateSpeed(MOVE_RUN, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::SetImmuneToPC(bool apply, bool keepCombat)
|
void Unit::SetImmuneToPC(bool apply, bool keepCombat)
|
||||||
@@ -15338,30 +15346,10 @@ void Unit::SetPhaseMask(uint32 newPhaseMask, bool update)
|
|||||||
|
|
||||||
if (!sScriptMgr->CanSetPhaseMask(this, newPhaseMask, update))
|
if (!sScriptMgr->CanSetPhaseMask(this, newPhaseMask, update))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Phase-related threat updates are done AFTER the phase change below
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WorldObject::SetPhaseMask(newPhaseMask, false);
|
WorldObject::SetPhaseMask(newPhaseMask, false);
|
||||||
|
|
||||||
// Now update threat online states with the new phase mask applied
|
|
||||||
if (IsCreature() || (IsPlayer() && !ToPlayer()->IsGameMaster() && !ToPlayer()->GetSession()->PlayerLogout()))
|
|
||||||
{
|
|
||||||
// Update online state for units that have me on their threat list
|
|
||||||
for (auto const& pair : GetThreatMgr().GetThreatenedByMeList())
|
|
||||||
{
|
|
||||||
if (ThreatReference* ref = pair.second)
|
|
||||||
ref->UpdateOffline();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update online state for units on my threat list
|
|
||||||
if (!IsPlayer())
|
|
||||||
{
|
|
||||||
for (ThreatReference* ref : GetThreatMgr().GetModifiableThreatList())
|
|
||||||
ref->UpdateOffline();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsInWorld())
|
if (!IsInWorld())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -63,6 +63,40 @@ Loot* Roll::getLoot()
|
|||||||
return getTarget();
|
return getTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void SendRollWonItemViaMail(Player* player, LootItem const* lootItem, uint32 itemId)
|
||||||
|
{
|
||||||
|
Item* mailItem = Item::CreateItem(itemId, lootItem->count, player, false, lootItem->randomPropertyId);
|
||||||
|
if (!mailItem)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AllowedLooterSet looters = lootItem->GetAllowedLooters();
|
||||||
|
ItemTemplate const* proto = mailItem->GetTemplate();
|
||||||
|
// Preserve the 2-hour group trade window the item would have had if stored directly.
|
||||||
|
if (looters.size() > 1 && proto->GetMaxStackSize() == 1 &&
|
||||||
|
(proto->Bonding == BIND_WHEN_PICKED_UP || proto->Bonding == BIND_QUEST_ITEM) &&
|
||||||
|
sWorld->getBoolConfig(CONFIG_SET_BOP_ITEM_TRADEABLE))
|
||||||
|
{
|
||||||
|
mailItem->SetBinding(true);
|
||||||
|
mailItem->SetSoulboundTradeable(looters);
|
||||||
|
mailItem->SetUInt32Value(ITEM_FIELD_CREATE_PLAYED_TIME, player->GetTotalPlayedTime());
|
||||||
|
|
||||||
|
std::string lootersStr;
|
||||||
|
for (ObjectGuid const& guid : looters)
|
||||||
|
{
|
||||||
|
if (!lootersStr.empty())
|
||||||
|
lootersStr += ' ';
|
||||||
|
lootersStr += std::to_string(guid.GetCounter());
|
||||||
|
}
|
||||||
|
|
||||||
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEM_BOP_TRADE);
|
||||||
|
stmt->SetData(0, mailItem->GetGUID().GetCounter());
|
||||||
|
stmt->SetData(1, lootersStr);
|
||||||
|
CharacterDatabase.Execute(stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
player->SendItemRetrievalMail(mailItem);
|
||||||
|
}
|
||||||
|
|
||||||
Group::Group() : m_leaderName(""), m_groupType(GROUPTYPE_NORMAL),
|
Group::Group() : m_leaderName(""), m_groupType(GROUPTYPE_NORMAL),
|
||||||
m_dungeonDifficulty(DUNGEON_DIFFICULTY_NORMAL), m_raidDifficulty(RAID_DIFFICULTY_10MAN_NORMAL),
|
m_dungeonDifficulty(DUNGEON_DIFFICULTY_NORMAL), m_raidDifficulty(RAID_DIFFICULTY_10MAN_NORMAL),
|
||||||
m_bfGroup(nullptr), m_bgGroup(nullptr), m_lootMethod(FREE_FOR_ALL), m_lootThreshold(ITEM_QUALITY_UNCOMMON),
|
m_bfGroup(nullptr), m_bgGroup(nullptr), m_lootMethod(FREE_FOR_ALL), m_lootThreshold(ITEM_QUALITY_UNCOMMON),
|
||||||
@@ -1504,8 +1538,7 @@ void Group::CountTheRoll(Rolls::iterator rollI, Map* allowedMap)
|
|||||||
roll->getLoot()->NotifyItemRemoved(roll->itemSlot);
|
roll->getLoot()->NotifyItemRemoved(roll->itemSlot);
|
||||||
roll->getLoot()->unlootedCount--;
|
roll->getLoot()->unlootedCount--;
|
||||||
player->SendEquipError(msg, nullptr, nullptr, roll->itemid);
|
player->SendEquipError(msg, nullptr, nullptr, roll->itemid);
|
||||||
if (Item* mailItem = Item::CreateItem(roll->itemid, item->count, player, false, item->randomPropertyId))
|
SendRollWonItemViaMail(player, item, roll->itemid);
|
||||||
player->SendItemRetrievalMail(mailItem);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1587,8 +1620,7 @@ void Group::CountTheRoll(Rolls::iterator rollI, Map* allowedMap)
|
|||||||
roll->getLoot()->NotifyItemRemoved(roll->itemSlot);
|
roll->getLoot()->NotifyItemRemoved(roll->itemSlot);
|
||||||
roll->getLoot()->unlootedCount--;
|
roll->getLoot()->unlootedCount--;
|
||||||
player->SendEquipError(msg, nullptr, nullptr, roll->itemid);
|
player->SendEquipError(msg, nullptr, nullptr, roll->itemid);
|
||||||
if (Item* mailItem = Item::CreateItem(roll->itemid, item->count, player, false, item->randomPropertyId))
|
SendRollWonItemViaMail(player, item, roll->itemid);
|
||||||
player->SendItemRetrievalMail(mailItem);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -126,6 +126,16 @@ void WorldSession::HandleGroupInviteOpcode(WorldPacket& recvData)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Battlefield raids (e.g. Wintergrasp) have their composition managed by the BF
|
||||||
|
// system based on queue and team balance. Letting raid members recruit outsiders
|
||||||
|
// bypasses Battlefield::AddOrSetPlayerToCorrectBfGroup, which on WG entry then
|
||||||
|
// refuses to add the invitee because they are already in a BF group.
|
||||||
|
if (Group* invitingGroup = invitingPlayer->GetGroup(); invitingGroup && invitingGroup->isBFGroup())
|
||||||
|
{
|
||||||
|
SendPartyResult(PARTY_OP_INVITE, membername, ERR_NOT_LEADER);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Group* group = invitingPlayer->GetGroup();
|
Group* group = invitingPlayer->GetGroup();
|
||||||
if (group && group->isBGGroup())
|
if (group && group->isBGGroup())
|
||||||
group = invitingPlayer->GetOriginalGroup();
|
group = invitingPlayer->GetOriginalGroup();
|
||||||
|
|||||||
@@ -569,12 +569,32 @@ void WorldSession::SendLfgUpdateProposal(lfg::LfgProposal const& proposal)
|
|||||||
data << uint8(silent); // Show proposal window
|
data << uint8(silent); // Show proposal window
|
||||||
data << uint8(proposal.players.size()); // Group size
|
data << uint8(proposal.players.size()); // Group size
|
||||||
|
|
||||||
for (lfg::LfgProposalPlayerContainer::const_iterator it = proposal.players.begin(); it != proposal.players.end(); ++it)
|
// Sort by roles: tank, healer, dps
|
||||||
|
std::vector<ObjectGuid> ordered;
|
||||||
|
ordered.reserve(proposal.players.size());
|
||||||
|
|
||||||
|
std::vector<ObjectGuid> tanks, healers, dps;
|
||||||
|
|
||||||
|
for (auto const& [pguid, player] : proposal.players)
|
||||||
{
|
{
|
||||||
lfg::LfgProposalPlayer const& player = it->second;
|
if (player.role & lfg::PLAYER_ROLE_TANK)
|
||||||
|
tanks.push_back(pguid);
|
||||||
|
else if (player.role & lfg::PLAYER_ROLE_HEALER)
|
||||||
|
healers.push_back(pguid);
|
||||||
|
else
|
||||||
|
dps.push_back(pguid);
|
||||||
|
}
|
||||||
|
|
||||||
|
ordered.insert(ordered.end(), tanks.begin(), tanks.end());
|
||||||
|
ordered.insert(ordered.end(), healers.begin(), healers.end());
|
||||||
|
ordered.insert(ordered.end(), dps.begin(), dps.end());
|
||||||
|
|
||||||
|
for (auto const& pguid : ordered)
|
||||||
|
{
|
||||||
|
lfg::LfgProposalPlayer const& player = proposal.players.find(pguid)->second;
|
||||||
data << uint32(player.role); // Role
|
data << uint32(player.role); // Role
|
||||||
data << uint8(it->first == guid); // Self player
|
data << uint8(pguid == guid); // Self player
|
||||||
if (!player.group) // Player not it a group
|
if (!player.group) // Player not in a group
|
||||||
{
|
{
|
||||||
data << uint8(0); // Not in dungeon
|
data << uint8(0); // Not in dungeon
|
||||||
data << uint8(0); // Not same group
|
data << uint8(0); // Not same group
|
||||||
|
|||||||
@@ -1508,6 +1508,10 @@ enum AcoreStrings
|
|||||||
LANG_RESPAWN_ENTRY_GAMEOBJECT_QUEUED = 35452,
|
LANG_RESPAWN_ENTRY_GAMEOBJECT_QUEUED = 35452,
|
||||||
|
|
||||||
// List respawns console support
|
// List respawns console support
|
||||||
LANG_LIST_RESPAWNS_NO_MAP = 35447
|
LANG_LIST_RESPAWNS_NO_MAP = 35447,
|
||||||
|
|
||||||
|
// Pet rename command
|
||||||
|
LANG_PET_RENAME_INVALID = 35453,
|
||||||
|
LANG_PET_RENAME_SUCCESS = 35454
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -914,6 +914,11 @@ void ScriptMgr::OnPlayerUpdateSkill(Player* player, uint32 skillId, uint32 value
|
|||||||
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_UPDATE_SKILL, script->OnPlayerUpdateSkill(player, skillId, value, max, step, newValue));
|
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_UPDATE_SKILL, script->OnPlayerUpdateSkill(player, skillId, value, max, step, newValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScriptMgr::OnPlayerSetSkill(Player* player, uint32 skillId, uint32 value, uint32 max, uint32 step, uint32 newValue)
|
||||||
|
{
|
||||||
|
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_SET_SKILL, script->OnPlayerSetSkill(player, skillId, value, max, step, newValue));
|
||||||
|
}
|
||||||
|
|
||||||
bool ScriptMgr::OnPlayerCanResurrect(Player* player)
|
bool ScriptMgr::OnPlayerCanResurrect(Player* player)
|
||||||
{
|
{
|
||||||
CALL_ENABLED_BOOLEAN_HOOKS(PlayerScript, PLAYERHOOK_CAN_RESURRECT, !script->OnPlayerCanResurrect(player));
|
CALL_ENABLED_BOOLEAN_HOOKS(PlayerScript, PLAYERHOOK_CAN_RESURRECT, !script->OnPlayerCanResurrect(player));
|
||||||
|
|||||||
@@ -212,6 +212,7 @@ enum PlayerHook
|
|||||||
PLAYERHOOK_ON_CAN_UPDATE_SKILL,
|
PLAYERHOOK_ON_CAN_UPDATE_SKILL,
|
||||||
PLAYERHOOK_ON_BEFORE_UPDATE_SKILL,
|
PLAYERHOOK_ON_BEFORE_UPDATE_SKILL,
|
||||||
PLAYERHOOK_ON_UPDATE_SKILL,
|
PLAYERHOOK_ON_UPDATE_SKILL,
|
||||||
|
PLAYERHOOK_ON_SET_SKILL,
|
||||||
PLAYERHOOK_CAN_RESURRECT,
|
PLAYERHOOK_CAN_RESURRECT,
|
||||||
PLAYERHOOK_ON_CAN_GIVE_LEVEL,
|
PLAYERHOOK_ON_CAN_GIVE_LEVEL,
|
||||||
PLAYERHOOK_ON_SEND_LIST_INVENTORY,
|
PLAYERHOOK_ON_SEND_LIST_INVENTORY,
|
||||||
@@ -792,6 +793,7 @@ public:
|
|||||||
virtual bool OnPlayerCanUpdateSkill(Player* /*player*/, uint32 /*skillId*/) { return true; }
|
virtual bool OnPlayerCanUpdateSkill(Player* /*player*/, uint32 /*skillId*/) { return true; }
|
||||||
virtual void OnPlayerBeforeUpdateSkill(Player* /*player*/, uint32 /*skillId*/, uint32& /*value*/, uint32 /*max*/, uint32 /*step*/) { }
|
virtual void OnPlayerBeforeUpdateSkill(Player* /*player*/, uint32 /*skillId*/, uint32& /*value*/, uint32 /*max*/, uint32 /*step*/) { }
|
||||||
virtual void OnPlayerUpdateSkill(Player* /*player*/, uint32 /*skillId*/, uint32 /*value*/, uint32 /*max*/, uint32 /*step*/, uint32 /*newValue*/) { }
|
virtual void OnPlayerUpdateSkill(Player* /*player*/, uint32 /*skillId*/, uint32 /*value*/, uint32 /*max*/, uint32 /*step*/, uint32 /*newValue*/) { }
|
||||||
|
virtual void OnPlayerSetSkill(Player* /*player*/, uint32 /*skillId*/, uint32 /*value*/, uint32 /*max*/, uint32 /*step*/, uint32 /*newValue*/) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief This hook is called, to avoid player resurrect
|
* @brief This hook is called, to avoid player resurrect
|
||||||
|
|||||||
@@ -483,6 +483,7 @@ public: /* PlayerScript */
|
|||||||
bool OnPlayerCanUpdateSkill(Player* player, uint32 skillId);
|
bool OnPlayerCanUpdateSkill(Player* player, uint32 skillId);
|
||||||
void OnPlayerBeforeUpdateSkill(Player* player, uint32 skill_id, uint32& value, uint32 max, uint32 step);
|
void OnPlayerBeforeUpdateSkill(Player* player, uint32 skill_id, uint32& value, uint32 max, uint32 step);
|
||||||
void OnPlayerUpdateSkill(Player* player, uint32 skillId, uint32 value, uint32 max, uint32 step, uint32 newValue);
|
void OnPlayerUpdateSkill(Player* player, uint32 skillId, uint32 value, uint32 max, uint32 step, uint32 newValue);
|
||||||
|
void OnPlayerSetSkill(Player* player, uint32 skillId, uint32 value, uint32 max, uint32 step, uint32 newValue);
|
||||||
bool OnPlayerCanResurrect(Player* player);
|
bool OnPlayerCanResurrect(Player* player);
|
||||||
bool OnPlayerCanGiveLevel(Player* player, uint8 newLevel);
|
bool OnPlayerCanGiveLevel(Player* player, uint8 newLevel);
|
||||||
void OnPlayerSendListInventory(Player* player, ObjectGuid vendorGuid, uint32& vendorEntry);
|
void OnPlayerSendListInventory(Player* player, ObjectGuid vendorGuid, uint32& vendorEntry);
|
||||||
|
|||||||
@@ -5344,13 +5344,10 @@ void AuraEffect::HandleAuraDummy(AuraApplication const* aurApp, uint8 mode, bool
|
|||||||
{
|
{
|
||||||
case 2584: // Waiting to Resurrect
|
case 2584: // Waiting to Resurrect
|
||||||
// Waiting to resurrect spell cancel, we must remove player from resurrect queue
|
// Waiting to resurrect spell cancel, we must remove player from resurrect queue
|
||||||
|
// bf branch omitted: it would cascade back into this handler.
|
||||||
if (target->IsPlayer())
|
if (target->IsPlayer())
|
||||||
{
|
|
||||||
if (Battleground* bg = target->ToPlayer()->GetBattleground())
|
if (Battleground* bg = target->ToPlayer()->GetBattleground())
|
||||||
bg->RemovePlayerFromResurrectQueue(target->ToPlayer());
|
bg->RemovePlayerFromResurrectQueue(target->ToPlayer());
|
||||||
if (Battlefield* bf = sBattlefieldMgr->GetBattlefieldToZoneId(target->GetZoneId()))
|
|
||||||
bf->RemovePlayerFromResurrectQueue(target->GetGUID());
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 43681: // Inactive
|
case 43681: // Inactive
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
#include "World.h"
|
#include "World.h"
|
||||||
#include "WorldPacket.h"
|
#include "WorldPacket.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <G3D/g3dmath.h>
|
||||||
|
|
||||||
/// @todo: this import is not necessary for compilation and marked as unused by the IDE
|
/// @todo: this import is not necessary for compilation and marked as unused by the IDE
|
||||||
// however, for some reasons removing it would cause a damn linking issue
|
// however, for some reasons removing it would cause a damn linking issue
|
||||||
@@ -1225,9 +1226,28 @@ void Spell::SelectImplicitConeTargets(SpellEffIndex effIndex, SpellImplicitTarge
|
|||||||
SpellTargetObjectTypes objectType = targetType.GetObjectType();
|
SpellTargetObjectTypes objectType = targetType.GetObjectType();
|
||||||
SpellTargetCheckTypes selectionType = targetType.GetCheckType();
|
SpellTargetCheckTypes selectionType = targetType.GetCheckType();
|
||||||
ConditionList* condList = m_spellInfo->Effects[effIndex].ImplicitTargetConditions;
|
ConditionList* condList = m_spellInfo->Effects[effIndex].ImplicitTargetConditions;
|
||||||
float coneAngle = M_PI / 2;
|
float coneAngle = G3D::toRadians(60.0f);
|
||||||
float radius = m_spellInfo->Effects[effIndex].CalcRadius(m_caster) * m_spellValue->RadiusMod;
|
if (SpellCone const* sc = sSpellMgr->GetSpellCone(m_spellInfo->Id))
|
||||||
|
coneAngle = G3D::toRadians(static_cast<float>(sc->cone_degrees));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (targetType.GetTarget())
|
||||||
|
{
|
||||||
|
case TARGET_UNIT_CONE_ENEMY_24:
|
||||||
|
coneAngle = G3D::toRadians(24.0f);
|
||||||
|
break;
|
||||||
|
case TARGET_UNIT_CONE_ENEMY_54:
|
||||||
|
coneAngle = G3D::toRadians(54.0f);
|
||||||
|
break;
|
||||||
|
case TARGET_UNIT_CONE_ENEMY_104:
|
||||||
|
coneAngle = G3D::toRadians(104.0f);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float radius = m_spellInfo->Effects[effIndex].CalcRadius(m_caster) * m_spellValue->RadiusMod;
|
||||||
if (uint32 containerTypeMask = GetSearcherTypeMask(objectType, condList))
|
if (uint32 containerTypeMask = GetSearcherTypeMask(objectType, condList))
|
||||||
{
|
{
|
||||||
Acore::WorldObjectSpellConeTargetCheck check(coneAngle, radius, m_caster, m_spellInfo, selectionType, condList);
|
Acore::WorldObjectSpellConeTargetCheck check(coneAngle, radius, m_caster, m_spellInfo, selectionType, condList);
|
||||||
|
|||||||
@@ -1527,7 +1527,7 @@ void SpellMgr::LoadSpellInfoCorrections()
|
|||||||
spellInfo->Speed = 8.0f;
|
spellInfo->Speed = 8.0f;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Spell Absorption
|
// Shadowmoon Reaver - Spell Absorption
|
||||||
ApplySpellFix({ 41034 }, [](SpellInfo* spellInfo)
|
ApplySpellFix({ 41034 }, [](SpellInfo* spellInfo)
|
||||||
{
|
{
|
||||||
spellInfo->Effects[EFFECT_2].Effect = SPELL_EFFECT_APPLY_AURA;
|
spellInfo->Effects[EFFECT_2].Effect = SPELL_EFFECT_APPLY_AURA;
|
||||||
@@ -1536,12 +1536,13 @@ void SpellMgr::LoadSpellInfoCorrections()
|
|||||||
spellInfo->Effects[EFFECT_2].MiscValue = SPELL_SCHOOL_MASK_MAGIC;
|
spellInfo->Effects[EFFECT_2].MiscValue = SPELL_SCHOOL_MASK_MAGIC;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Shared Bonds
|
// Priestess of Delight and Priestess of Torment - Shared Bonds
|
||||||
ApplySpellFix({ 41363 }, [](SpellInfo* spellInfo)
|
ApplySpellFix({ 41363 }, [](SpellInfo* spellInfo)
|
||||||
{
|
{
|
||||||
spellInfo->AttributesEx &= ~SPELL_ATTR1_IS_CHANNELED;
|
spellInfo->AttributesEx &= ~SPELL_ATTR1_IS_CHANNELED;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Illidari Council - Veras Darkshadow
|
||||||
ApplySpellFix({
|
ApplySpellFix({
|
||||||
41485, // Deadly Poison
|
41485, // Deadly Poison
|
||||||
41487 // Envenom
|
41487 // Envenom
|
||||||
@@ -1570,13 +1571,13 @@ void SpellMgr::LoadSpellInfoCorrections()
|
|||||||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(13); // 0-50000yd
|
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(13); // 0-50000yd
|
||||||
});
|
});
|
||||||
|
|
||||||
// Watery Grave Explosion
|
// Morogrim Tidewalker - Watery Grave Explosion
|
||||||
ApplySpellFix({ 37852 }, [](SpellInfo* spellInfo)
|
ApplySpellFix({ 37852 }, [](SpellInfo* spellInfo)
|
||||||
{
|
{
|
||||||
spellInfo->AttributesEx5 |= SPELL_ATTR5_ALLOW_WHILE_STUNNED;
|
spellInfo->AttributesEx5 |= SPELL_ATTR5_ALLOW_WHILE_STUNNED;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Amplify Damage
|
// Prince Malchezaar - Amplify Damage
|
||||||
ApplySpellFix({ 39095 }, [](SpellInfo* spellInfo)
|
ApplySpellFix({ 39095 }, [](SpellInfo* spellInfo)
|
||||||
{
|
{
|
||||||
spellInfo->MaxAffectedTargets = 1;
|
spellInfo->MaxAffectedTargets = 1;
|
||||||
@@ -1589,6 +1590,7 @@ void SpellMgr::LoadSpellInfoCorrections()
|
|||||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_SINGLE_AURA_STACK;
|
spellInfo->AttributesCu |= SPELL_ATTR0_CU_SINGLE_AURA_STACK;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Archimonde
|
||||||
ApplySpellFix({
|
ApplySpellFix({
|
||||||
31984, // Finger of Death
|
31984, // Finger of Death
|
||||||
35354 // Hand of Death
|
35354 // Hand of Death
|
||||||
@@ -1598,12 +1600,18 @@ void SpellMgr::LoadSpellInfoCorrections()
|
|||||||
spellInfo->Attributes = SPELL_ATTR0_IS_ABILITY;
|
spellInfo->Attributes = SPELL_ATTR0_IS_ABILITY;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Finger of Death
|
// Archimonde - Finger of Death
|
||||||
ApplySpellFix({ 32111 }, [](SpellInfo* spellInfo)
|
ApplySpellFix({ 32111 }, [](SpellInfo* spellInfo)
|
||||||
{
|
{
|
||||||
spellInfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(0); // We only need the animation, no damage
|
spellInfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(0); // We only need the animation, no damage
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Archimonde - Doomfire
|
||||||
|
ApplySpellFix({ 31944, 31969 }, [](SpellInfo* spellInfo)
|
||||||
|
{
|
||||||
|
spellInfo->AttributesEx4 |= SPELL_ATTR4_NO_CAST_LOG;
|
||||||
|
});
|
||||||
|
|
||||||
// Flame Breath, catapult spell
|
// Flame Breath, catapult spell
|
||||||
ApplySpellFix({ 50989 }, [](SpellInfo* spellInfo)
|
ApplySpellFix({ 50989 }, [](SpellInfo* spellInfo)
|
||||||
{
|
{
|
||||||
@@ -5199,6 +5207,13 @@ void SpellMgr::LoadSpellInfoCorrections()
|
|||||||
spellInfo->Effects[EFFECT_0].BasePoints = 1;
|
spellInfo->Effects[EFFECT_0].BasePoints = 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 26025 Impale
|
||||||
|
ApplySpellFix({ 26025 }, [](SpellInfo* spellInfo)
|
||||||
|
{
|
||||||
|
spellInfo->Effects[EFFECT_1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CONE_ENEMY_54);
|
||||||
|
spellInfo->Effects[EFFECT_1].TargetB = SpellImplicitTargetInfo(0);
|
||||||
|
});
|
||||||
|
|
||||||
for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i)
|
for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i)
|
||||||
{
|
{
|
||||||
SpellInfo* spellInfo = mSpellInfoMap[i];
|
SpellInfo* spellInfo = mSpellInfoMap[i];
|
||||||
|
|||||||
@@ -695,6 +695,16 @@ SpellTargetPosition const* SpellMgr::GetSpellTargetPosition(uint32 spell_id, Spe
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SpellCone const* SpellMgr::GetSpellCone(uint32 spell_id) const
|
||||||
|
{
|
||||||
|
spell_id = GetFirstSpellInChain(spell_id);
|
||||||
|
auto itr = mSpellCones.find(spell_id);
|
||||||
|
if (itr != mSpellCones.end())
|
||||||
|
return &itr->second;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
SpellSpellGroupMapBounds SpellMgr::GetSpellSpellGroupMapBounds(uint32 spell_id) const
|
SpellSpellGroupMapBounds SpellMgr::GetSpellSpellGroupMapBounds(uint32 spell_id) const
|
||||||
{
|
{
|
||||||
spell_id = GetFirstSpellInChain(spell_id);
|
spell_id = GetFirstSpellInChain(spell_id);
|
||||||
@@ -1101,7 +1111,9 @@ bool SpellArea::IsFitToRequirements(Player const* player, uint32 newZone, uint32
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
Battlefield* Bf = sBattlefieldMgr->GetBattlefieldByBattleId(BATTLEFIELD_BATTLEID_WG);
|
Battlefield* Bf = sBattlefieldMgr->GetBattlefieldByBattleId(BATTLEFIELD_BATTLEID_WG);
|
||||||
if (!Bf || player->GetTeamId() != Bf->GetDefenderTeam() || Bf->IsWarTime())
|
if (!Bf || Bf->IsWarTime())
|
||||||
|
return false;
|
||||||
|
if (!sWorld->getBoolConfig(CONFIG_WINTERGRASP_ESSENCE_BOTH_FACTIONS) && player->GetTeamId() != Bf->GetDefenderTeam())
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1578,6 +1590,93 @@ void SpellMgr::LoadSpellTargetPositions()
|
|||||||
LOG_INFO("server.loading", " ");
|
LOG_INFO("server.loading", " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SpellMgr::LoadSpellCones()
|
||||||
|
{
|
||||||
|
uint32 oldMSTime = getMSTime();
|
||||||
|
|
||||||
|
mSpellCones.clear(); // need for reload case
|
||||||
|
|
||||||
|
// 0 1
|
||||||
|
QueryResult result = WorldDatabase.Query("SELECT ID, ConeDegrees FROM spell_cone");
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
LOG_WARN("server.loading", ">> Loaded 0 spell cone definitions. DB table `spell_cone` is empty.");
|
||||||
|
LOG_INFO("server.loading", " ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 count = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
Field* fields = result->Fetch();
|
||||||
|
|
||||||
|
uint32 spellId = fields[0].Get<uint32>();
|
||||||
|
int16 coneDeg = fields[1].Get<int16>();
|
||||||
|
|
||||||
|
SpellInfo const* spellInfo = GetSpellInfo(spellId);
|
||||||
|
if (!spellInfo)
|
||||||
|
{
|
||||||
|
LOG_ERROR("sql.sql", "Spell (ID:{}) listed in `spell_cone` does not exist.", spellId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coneDeg < -360 || coneDeg > 360)
|
||||||
|
{
|
||||||
|
LOG_ERROR("sql.sql", "Spell (Id: {}) cone degrees {} out of range (-360..360).", spellId, coneDeg);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 firstRankId = GetFirstSpellInChain(spellId);
|
||||||
|
SpellInfo const* firstSpellInfo = GetSpellInfo(firstRankId);
|
||||||
|
|
||||||
|
SpellInfo const* checkInfo = firstSpellInfo ? firstSpellInfo : spellInfo;
|
||||||
|
|
||||||
|
bool hasCone = false;
|
||||||
|
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||||||
|
{
|
||||||
|
if (checkInfo->Effects[i].TargetA.GetSelectionCategory() == TARGET_SELECT_CATEGORY_CONE ||
|
||||||
|
checkInfo->Effects[i].TargetB.GetSelectionCategory() == TARGET_SELECT_CATEGORY_CONE)
|
||||||
|
{
|
||||||
|
hasCone = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasCone)
|
||||||
|
{
|
||||||
|
LOG_ERROR("sql.sql", "Spell (ID:{}) listed in `spell_cone` does not have a cone implicit target.", spellId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstRankId != spellId)
|
||||||
|
LOG_INFO("server.loading", "Spell (ID:{}) listed in `spell_cone` is not first rank; mapping to first rank {}.", spellId, firstRankId);
|
||||||
|
|
||||||
|
SpellCone sc{};
|
||||||
|
sc.cone_degrees = coneDeg;
|
||||||
|
|
||||||
|
// Avoid overwriting an existing first-rank entry with conflicting values
|
||||||
|
auto itr = mSpellCones.find(firstRankId);
|
||||||
|
if (itr != mSpellCones.end())
|
||||||
|
{
|
||||||
|
if (itr->second.cone_degrees != sc.cone_degrees)
|
||||||
|
LOG_ERROR("sql.sql",
|
||||||
|
"Conflicting `spell_cone` entries for first-rank spell ID {}: {} vs {}. Keeping first value.",
|
||||||
|
firstRankId,
|
||||||
|
itr->second.cone_degrees,
|
||||||
|
sc.cone_degrees);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mSpellCones[firstRankId] = sc;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
} while (result->NextRow());
|
||||||
|
|
||||||
|
LOG_INFO("server.loading", ">> Loaded {} Spell Cone definitions in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||||||
|
LOG_INFO("server.loading", " ");
|
||||||
|
}
|
||||||
|
|
||||||
void SpellMgr::LoadSpellGroups()
|
void SpellMgr::LoadSpellGroups()
|
||||||
{
|
{
|
||||||
uint32 oldMSTime = getMSTime();
|
uint32 oldMSTime = getMSTime();
|
||||||
|
|||||||
@@ -393,6 +393,13 @@ struct SpellTargetPosition
|
|||||||
|
|
||||||
typedef std::map<std::pair<uint32 /*spell_id*/, SpellEffIndex /*effIndex*/>, SpellTargetPosition> SpellTargetPositionMap;
|
typedef std::map<std::pair<uint32 /*spell_id*/, SpellEffIndex /*effIndex*/>, SpellTargetPosition> SpellTargetPositionMap;
|
||||||
|
|
||||||
|
struct SpellCone
|
||||||
|
{
|
||||||
|
int16_t cone_degrees;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::map<uint32 /*spell_id*/, SpellCone> SpellConeMap;
|
||||||
|
|
||||||
// Enum with EffectRadiusIndex and their actual radius
|
// Enum with EffectRadiusIndex and their actual radius
|
||||||
enum EffectRadiusIndex
|
enum EffectRadiusIndex
|
||||||
{
|
{
|
||||||
@@ -683,6 +690,7 @@ public:
|
|||||||
|
|
||||||
// Spell target coordinates
|
// Spell target coordinates
|
||||||
[[nodiscard]] SpellTargetPosition const* GetSpellTargetPosition(uint32 spell_id, SpellEffIndex effIndex) const;
|
[[nodiscard]] SpellTargetPosition const* GetSpellTargetPosition(uint32 spell_id, SpellEffIndex effIndex) const;
|
||||||
|
[[nodiscard]] SpellCone const* GetSpellCone(uint32 spell_id) const;
|
||||||
|
|
||||||
// Spell Groups table
|
// Spell Groups table
|
||||||
SpellSpellGroupMapBounds GetSpellSpellGroupMapBounds(uint32 spell_id) const;
|
SpellSpellGroupMapBounds GetSpellSpellGroupMapBounds(uint32 spell_id) const;
|
||||||
@@ -775,6 +783,7 @@ public:
|
|||||||
void LoadSpellRequired();
|
void LoadSpellRequired();
|
||||||
void LoadSpellLearnSkills();
|
void LoadSpellLearnSkills();
|
||||||
void LoadSpellTargetPositions();
|
void LoadSpellTargetPositions();
|
||||||
|
void LoadSpellCones();
|
||||||
void LoadSpellGroups();
|
void LoadSpellGroups();
|
||||||
void LoadSpellGroupStackRules();
|
void LoadSpellGroupStackRules();
|
||||||
void LoadSpellProcs();
|
void LoadSpellProcs();
|
||||||
@@ -807,6 +816,7 @@ private:
|
|||||||
SpellRequiredMap mSpellReq;
|
SpellRequiredMap mSpellReq;
|
||||||
SpellLearnSkillMap mSpellLearnSkills;
|
SpellLearnSkillMap mSpellLearnSkills;
|
||||||
SpellTargetPositionMap mSpellTargetPositions;
|
SpellTargetPositionMap mSpellTargetPositions;
|
||||||
|
SpellConeMap mSpellCones;
|
||||||
SpellSpellGroupMap mSpellSpellGroup;
|
SpellSpellGroupMap mSpellSpellGroup;
|
||||||
SpellGroupSpellMap mSpellGroupSpell;
|
SpellGroupSpellMap mSpellGroupSpell;
|
||||||
SpellGroupStackMap mSpellGroupStack;
|
SpellGroupStackMap mSpellGroupStack;
|
||||||
|
|||||||
@@ -683,6 +683,9 @@ void World::SetInitialWorldSettings()
|
|||||||
LOG_INFO("server.loading", "Loading Spell Target Coordinates...");
|
LOG_INFO("server.loading", "Loading Spell Target Coordinates...");
|
||||||
sSpellMgr->LoadSpellTargetPositions();
|
sSpellMgr->LoadSpellTargetPositions();
|
||||||
|
|
||||||
|
LOG_INFO("server.loading", "Loading Spell Cone definitions...");
|
||||||
|
sSpellMgr->LoadSpellCones();
|
||||||
|
|
||||||
LOG_INFO("server.loading", "Loading Enchant Custom Attributes...");
|
LOG_INFO("server.loading", "Loading Enchant Custom Attributes...");
|
||||||
sSpellMgr->LoadEnchantCustomAttr();
|
sSpellMgr->LoadEnchantCustomAttr();
|
||||||
|
|
||||||
|
|||||||
@@ -595,6 +595,8 @@ void WorldConfig::BuildConfigCache()
|
|||||||
SetConfigValue<uint32>(CONFIG_WINTERGRASP_RESTART_AFTER_CRASH, "Wintergrasp.CrashRestartTimer", 10);
|
SetConfigValue<uint32>(CONFIG_WINTERGRASP_RESTART_AFTER_CRASH, "Wintergrasp.CrashRestartTimer", 10);
|
||||||
|
|
||||||
SetConfigValue<uint32>(CONFIG_WINTERGRASP_SKIP_BATTLE_SESSION_COUNT, "Wintergrasp.SkipBattleSessionCount", 3500);
|
SetConfigValue<uint32>(CONFIG_WINTERGRASP_SKIP_BATTLE_SESSION_COUNT, "Wintergrasp.SkipBattleSessionCount", 3500);
|
||||||
|
SetConfigValue<bool>(CONFIG_WINTERGRASP_KICK_VOA_PLAYERS, "Wintergrasp.KickVoAPlayers", true, ConfigValueCache::Reloadable::No);
|
||||||
|
SetConfigValue<bool>(CONFIG_WINTERGRASP_ESSENCE_BOTH_FACTIONS, "Wintergrasp.EssenceBothFactions", false);
|
||||||
|
|
||||||
SetConfigValue<uint32>(CONFIG_BIRTHDAY_TIME, "BirthdayTime", 1222964635);
|
SetConfigValue<uint32>(CONFIG_BIRTHDAY_TIME, "BirthdayTime", 1222964635);
|
||||||
SetConfigValue<bool>(CONFIG_MINIGOB_MANABONK, "Minigob.Manabonk.Enable", true);
|
SetConfigValue<bool>(CONFIG_MINIGOB_MANABONK, "Minigob.Manabonk.Enable", true);
|
||||||
|
|||||||
@@ -326,6 +326,8 @@ enum ServerConfigs
|
|||||||
CONFIG_WINTERGRASP_NOBATTLETIME,
|
CONFIG_WINTERGRASP_NOBATTLETIME,
|
||||||
CONFIG_WINTERGRASP_RESTART_AFTER_CRASH,
|
CONFIG_WINTERGRASP_RESTART_AFTER_CRASH,
|
||||||
CONFIG_WINTERGRASP_SKIP_BATTLE_SESSION_COUNT,
|
CONFIG_WINTERGRASP_SKIP_BATTLE_SESSION_COUNT,
|
||||||
|
CONFIG_WINTERGRASP_KICK_VOA_PLAYERS,
|
||||||
|
CONFIG_WINTERGRASP_ESSENCE_BOTH_FACTIONS,
|
||||||
CONFIG_PACKET_SPOOF_BANMODE,
|
CONFIG_PACKET_SPOOF_BANMODE,
|
||||||
CONFIG_PACKET_SPOOF_BANDURATION,
|
CONFIG_PACKET_SPOOF_BANDURATION,
|
||||||
CONFIG_WARDEN_CLIENT_RESPONSE_DELAY,
|
CONFIG_WARDEN_CLIENT_RESPONSE_DELAY,
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
#include "CommandScript.h"
|
#include "CommandScript.h"
|
||||||
#include "DatabaseEnv.h"
|
#include "DatabaseEnv.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include "GameTime.h"
|
||||||
|
#include "Group.h"
|
||||||
#include "Language.h"
|
#include "Language.h"
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include "ObjectMgr.h"
|
#include "ObjectMgr.h"
|
||||||
@@ -43,6 +45,7 @@ public:
|
|||||||
{ "delete", HandlePetDeleteCommand, rbac::RBAC_PERM_COMMAND_PET_DELETE, Console::Yes },
|
{ "delete", HandlePetDeleteCommand, rbac::RBAC_PERM_COMMAND_PET_DELETE, Console::Yes },
|
||||||
{ "learn", HandlePetLearnCommand, rbac::RBAC_PERM_COMMAND_PET_LEARN, Console::No },
|
{ "learn", HandlePetLearnCommand, rbac::RBAC_PERM_COMMAND_PET_LEARN, Console::No },
|
||||||
{ "list", HandlePetListCommand, rbac::RBAC_PERM_COMMAND_PET_LIST, Console::Yes },
|
{ "list", HandlePetListCommand, rbac::RBAC_PERM_COMMAND_PET_LIST, Console::Yes },
|
||||||
|
{ "rename", HandlePetRenameCommand, rbac::RBAC_PERM_COMMAND_PET_RENAME, Console::Yes },
|
||||||
{ "unlearn", HandlePetUnlearnCommand, rbac::RBAC_PERM_COMMAND_PET_UNLEARN, Console::No }
|
{ "unlearn", HandlePetUnlearnCommand, rbac::RBAC_PERM_COMMAND_PET_UNLEARN, Console::No }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -227,6 +230,79 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool HandlePetRenameCommand(ChatHandler* handler, PlayerIdentifier owner, uint32 petNumber, std::string newName)
|
||||||
|
{
|
||||||
|
if (ObjectMgr::CheckPetName(newName) != PET_NAME_SUCCESS)
|
||||||
|
{
|
||||||
|
handler->SendErrorMessage(LANG_PET_RENAME_INVALID, newName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectGuid ownerGuid = owner.GetGUID();
|
||||||
|
|
||||||
|
QueryResult result = CharacterDatabase.Query("SELECT name FROM character_pet WHERE id = {} AND owner = {}", petNumber, ownerGuid.GetCounter());
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
handler->SendErrorMessage(LANG_PET_DELETE_NOT_FOUND, petNumber, owner.GetName(), ownerGuid.GetCounter());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string oldName = result->Fetch()[0].Get<std::string>();
|
||||||
|
|
||||||
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_NAME);
|
||||||
|
stmt->SetData(0, newName);
|
||||||
|
stmt->SetData(1, ownerGuid.GetCounter());
|
||||||
|
stmt->SetData(2, petNumber);
|
||||||
|
CharacterDatabase.Execute(stmt);
|
||||||
|
|
||||||
|
if (Player* online = owner.GetConnectedPlayer())
|
||||||
|
{
|
||||||
|
if (Pet* activePet = online->GetPet())
|
||||||
|
{
|
||||||
|
if (activePet->GetCharmInfo() && activePet->GetCharmInfo()->GetPetNumber() == petNumber)
|
||||||
|
{
|
||||||
|
activePet->SetName(newName);
|
||||||
|
// Bump the name timestamp so nearby clients re-query and drop their cached pet name.
|
||||||
|
activePet->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, uint32(GameTime::GetGameTime().count()));
|
||||||
|
activePet->RemoveByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_RENAMED);
|
||||||
|
if (online->GetGroup())
|
||||||
|
online->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PetStable* stable = online->GetPetStable())
|
||||||
|
{
|
||||||
|
if (stable->CurrentPet && stable->CurrentPet->PetNumber == petNumber)
|
||||||
|
{
|
||||||
|
stable->CurrentPet->Name = newName;
|
||||||
|
stable->CurrentPet->WasRenamed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Optional<PetStable::PetInfo>& stabled : stable->StabledPets)
|
||||||
|
{
|
||||||
|
if (stabled && stabled->PetNumber == petNumber)
|
||||||
|
{
|
||||||
|
stabled->Name = newName;
|
||||||
|
stabled->WasRenamed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PetStable::PetInfo& unslotted : stable->UnslottedPets)
|
||||||
|
{
|
||||||
|
if (unslotted.PetNumber == petNumber)
|
||||||
|
{
|
||||||
|
unslotted.Name = newName;
|
||||||
|
unslotted.WasRenamed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler->PSendSysMessage(LANG_PET_RENAME_SUCCESS, petNumber, oldName, newName, owner.GetName(), ownerGuid.GetCounter());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool HandlePetUnlearnCommand(ChatHandler* handler, SpellInfo const* spell)
|
static bool HandlePetUnlearnCommand(ChatHandler* handler, SpellInfo const* spell)
|
||||||
{
|
{
|
||||||
if (!spell)
|
if (!spell)
|
||||||
|
|||||||
@@ -48,15 +48,31 @@ public:
|
|||||||
PlayerSetting setting;
|
PlayerSetting setting;
|
||||||
setting = player->GetPlayerSetting(AzerothcorePSSource, SETTING_ANNOUNCER_FLAGS);
|
setting = player->GetPlayerSetting(AzerothcorePSSource, SETTING_ANNOUNCER_FLAGS);
|
||||||
|
|
||||||
|
char const* label = nullptr;
|
||||||
|
|
||||||
if (type == "bg")
|
if (type == "bg")
|
||||||
{
|
{
|
||||||
on ? setting.RemoveFlag(ANNOUNCER_FLAG_DISABLE_BG_QUEUE) : setting.AddFlag(ANNOUNCER_FLAG_DISABLE_BG_QUEUE);
|
on ? setting.RemoveFlag(ANNOUNCER_FLAG_DISABLE_BG_QUEUE) : setting.AddFlag(ANNOUNCER_FLAG_DISABLE_BG_QUEUE);
|
||||||
player->UpdatePlayerSetting(AzerothcorePSSource, SETTING_ANNOUNCER_FLAGS, setting.value);
|
player->UpdatePlayerSetting(AzerothcorePSSource, SETTING_ANNOUNCER_FLAGS, setting.value);
|
||||||
|
label = "battleground queue";
|
||||||
}
|
}
|
||||||
else if (type == "arena")
|
else if (type == "arena")
|
||||||
{
|
{
|
||||||
on ? setting.RemoveFlag(ANNOUNCER_FLAG_DISABLE_ARENA_QUEUE) : setting.AddFlag(ANNOUNCER_FLAG_DISABLE_ARENA_QUEUE);
|
on ? setting.RemoveFlag(ANNOUNCER_FLAG_DISABLE_ARENA_QUEUE) : setting.AddFlag(ANNOUNCER_FLAG_DISABLE_ARENA_QUEUE);
|
||||||
player->UpdatePlayerSetting(AzerothcorePSSource, SETTING_ANNOUNCER_FLAGS, setting.value);
|
player->UpdatePlayerSetting(AzerothcorePSSource, SETTING_ANNOUNCER_FLAGS, setting.value);
|
||||||
|
label = "arena queue";
|
||||||
|
}
|
||||||
|
else if (type == "pvpstart")
|
||||||
|
{
|
||||||
|
on ? setting.RemoveFlag(ANNOUNCER_FLAG_DISABLE_PVP_START) : setting.AddFlag(ANNOUNCER_FLAG_DISABLE_PVP_START);
|
||||||
|
player->UpdatePlayerSetting(AzerothcorePSSource, SETTING_ANNOUNCER_FLAGS, setting.value);
|
||||||
|
label = "PvP start";
|
||||||
|
}
|
||||||
|
else if (type == "pvpall")
|
||||||
|
{
|
||||||
|
on ? setting.RemoveFlag(ANNOUNCER_FLAG_DISABLE_PVP_ALL) : setting.AddFlag(ANNOUNCER_FLAG_DISABLE_PVP_ALL);
|
||||||
|
player->UpdatePlayerSetting(AzerothcorePSSource, SETTING_ANNOUNCER_FLAGS, setting.value);
|
||||||
|
label = "PvP";
|
||||||
}
|
}
|
||||||
else if (type == "autobroadcast")
|
else if (type == "autobroadcast")
|
||||||
{
|
{
|
||||||
@@ -68,10 +84,11 @@ public:
|
|||||||
|
|
||||||
on ? setting.RemoveFlag(ANNOUNCER_FLAG_DISABLE_AUTOBROADCAST) : setting.AddFlag(ANNOUNCER_FLAG_DISABLE_AUTOBROADCAST);
|
on ? setting.RemoveFlag(ANNOUNCER_FLAG_DISABLE_AUTOBROADCAST) : setting.AddFlag(ANNOUNCER_FLAG_DISABLE_AUTOBROADCAST);
|
||||||
player->UpdatePlayerSetting(AzerothcorePSSource, SETTING_ANNOUNCER_FLAGS, setting.value);
|
player->UpdatePlayerSetting(AzerothcorePSSource, SETTING_ANNOUNCER_FLAGS, setting.value);
|
||||||
|
label = "autobroadcast";
|
||||||
}
|
}
|
||||||
|
|
||||||
handler->SetSentErrorMessage(false);
|
handler->SetSentErrorMessage(false);
|
||||||
handler->PSendSysMessage(on ? LANG_CMD_SETTINGS_ANNOUNCER_ON : LANG_CMD_SETTINGS_ANNOUNCER_OFF, type);
|
handler->PSendSysMessage(on ? LANG_CMD_SETTINGS_ANNOUNCER_ON : LANG_CMD_SETTINGS_ANNOUNCER_OFF, label ? label : type.c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ public:
|
|||||||
{ "spell_proc", HandleReloadSpellProcsCommand, rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_PROC, Console::Yes },
|
{ "spell_proc", HandleReloadSpellProcsCommand, rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_PROC, Console::Yes },
|
||||||
{ "spell_scripts", HandleReloadSpellScriptsCommand, rbac::RBAC_PERM_COMMAND_RELOAD, Console::Yes },
|
{ "spell_scripts", HandleReloadSpellScriptsCommand, rbac::RBAC_PERM_COMMAND_RELOAD, Console::Yes },
|
||||||
{ "spell_target_position", HandleReloadSpellTargetPositionCommand, rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_TARGET_POSITION, Console::Yes },
|
{ "spell_target_position", HandleReloadSpellTargetPositionCommand, rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_TARGET_POSITION, Console::Yes },
|
||||||
|
{ "spell_cone", HandleReloadSpellConeCommand, rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_TARGET_POSITION, Console::Yes },
|
||||||
{ "spell_threats", HandleReloadSpellThreatsCommand, rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_THREATS, Console::Yes },
|
{ "spell_threats", HandleReloadSpellThreatsCommand, rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_THREATS, Console::Yes },
|
||||||
{ "spell_group_stack_rules", HandleReloadSpellGroupStackRulesCommand, rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_GROUP_STACK_RULES, Console::Yes },
|
{ "spell_group_stack_rules", HandleReloadSpellGroupStackRulesCommand, rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_GROUP_STACK_RULES, Console::Yes },
|
||||||
{ "player_loot_template", HandleReloadLootTemplatesPlayerCommand, rbac::RBAC_PERM_COMMAND_RELOAD, Console::Yes },
|
{ "player_loot_template", HandleReloadLootTemplatesPlayerCommand, rbac::RBAC_PERM_COMMAND_RELOAD, Console::Yes },
|
||||||
@@ -936,6 +937,14 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool HandleReloadSpellConeCommand(ChatHandler* handler)
|
||||||
|
{
|
||||||
|
LOG_INFO("server.loading", "Reloading Spell cone definitions...");
|
||||||
|
sSpellMgr->LoadSpellCones();
|
||||||
|
handler->SendGlobalGMSysMessage("DB table `spell_cone` reloaded.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool HandleReloadSpellThreatsCommand(ChatHandler* handler)
|
static bool HandleReloadSpellThreatsCommand(ChatHandler* handler)
|
||||||
{
|
{
|
||||||
LOG_INFO("server.loading", "Reloading Aggro Spells Definitions...");
|
LOG_INFO("server.loading", "Reloading Aggro Spells Definitions...");
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ public:
|
|||||||
|
|
||||||
uint32 a2, a3, a4, a5, a6;
|
uint32 a2, a3, a4, a5, a6;
|
||||||
float a8, a9, a10, a11;
|
float a8, a9, a10, a11;
|
||||||
char const* a7;
|
std::string a7;
|
||||||
|
|
||||||
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_WAYPOINT_SCRIPT_BY_ID);
|
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_WAYPOINT_SCRIPT_BY_ID);
|
||||||
stmt->SetData(0, id);
|
stmt->SetData(0, id);
|
||||||
@@ -362,7 +362,7 @@ public:
|
|||||||
a4 = fields[2].Get<uint32>();
|
a4 = fields[2].Get<uint32>();
|
||||||
a5 = fields[3].Get<uint32>();
|
a5 = fields[3].Get<uint32>();
|
||||||
a6 = fields[4].Get<uint32>();
|
a6 = fields[4].Get<uint32>();
|
||||||
a7 = fields[5].Get<std::string>().c_str();
|
a7 = fields[5].Get<std::string>();
|
||||||
a8 = fields[6].Get<float>();
|
a8 = fields[6].Get<float>();
|
||||||
a9 = fields[7].Get<float>();
|
a9 = fields[7].Get<float>();
|
||||||
a10 = fields[8].Get<float>();
|
a10 = fields[8].Get<float>();
|
||||||
|
|||||||
@@ -119,6 +119,8 @@ struct boss_moroes : public BossAI
|
|||||||
DoCastSelf(SPELL_DUAL_WIELD, true);
|
DoCastSelf(SPELL_DUAL_WIELD, true);
|
||||||
_recentlySpoken = false;
|
_recentlySpoken = false;
|
||||||
_vanished = false;
|
_vanished = false;
|
||||||
|
me->SetImmuneToAll(false);
|
||||||
|
me->SetReactState(REACT_AGGRESSIVE);
|
||||||
|
|
||||||
InitializeGuests();
|
InitializeGuests();
|
||||||
|
|
||||||
@@ -142,9 +144,11 @@ struct boss_moroes : public BossAI
|
|||||||
Talk(SAY_SPECIAL);
|
Talk(SAY_SPECIAL);
|
||||||
DoCastSelf(SPELL_VANISH);
|
DoCastSelf(SPELL_VANISH);
|
||||||
me->SetImmuneToAll(true, true);
|
me->SetImmuneToAll(true, true);
|
||||||
|
me->SetReactState(REACT_PASSIVE);
|
||||||
scheduler.Schedule(5s, 7s, [this](TaskContext)
|
scheduler.Schedule(5s, 7s, [this](TaskContext)
|
||||||
{
|
{
|
||||||
me->SetImmuneToAll(false);
|
me->SetImmuneToAll(false);
|
||||||
|
me->SetReactState(REACT_AGGRESSIVE);
|
||||||
DoCastSelf(SPELL_VANISH_TELEPORT);
|
DoCastSelf(SPELL_VANISH_TELEPORT);
|
||||||
_vanished = false;
|
_vanished = false;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -395,9 +395,12 @@ public:
|
|||||||
active->RemoveNpcFlag(UNIT_NPC_FLAG_QUESTGIVER);
|
active->RemoveNpcFlag(UNIT_NPC_FLAG_QUESTGIVER);
|
||||||
break;
|
break;
|
||||||
case EVENT_RESTART_ESCORT:
|
case EVENT_RESTART_ESCORT:
|
||||||
CheckCaravan();
|
if (!IsEscorted())
|
||||||
SetDespawnAtEnd(false);
|
{
|
||||||
Start(true, ObjectGuid::Empty, 0, false, false, true);
|
CheckCaravan();
|
||||||
|
SetDespawnAtEnd(false);
|
||||||
|
Start(true, ObjectGuid::Empty, 0, false, false, true);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -217,16 +217,21 @@ public:
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
events.Repeat(5s);
|
events.Repeat(5s);
|
||||||
Unit* unit = SelectTarget(SelectTargetMethod::Random, 0, BoneStormMoveTargetSelector(me));
|
std::list<Unit*> targets;
|
||||||
if (!unit)
|
SelectTargetList(targets, Is25ManRaid() ? 2 : 1, SelectTargetMethod::MaxDistance, 0, BoneStormMoveTargetSelector(me));
|
||||||
|
|
||||||
|
Unit* unit = nullptr;
|
||||||
|
if (!targets.empty())
|
||||||
|
unit = targets.size() == 1 ? targets.front() : Acore::Containers::SelectRandomContainerElement(targets);
|
||||||
|
else if ((unit = SelectTarget(SelectTargetMethod::MaxThreat, 0, 175.0f, true)))
|
||||||
{
|
{
|
||||||
if ((unit = SelectTarget(SelectTargetMethod::MaxThreat, 0, 175.0f, true)))
|
if (unit->GetPositionX() > -337.0f)
|
||||||
if (unit->GetPositionX() > -337.0f)
|
{
|
||||||
{
|
EnterEvadeMode();
|
||||||
EnterEvadeMode();
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unit)
|
if (unit)
|
||||||
me->GetMotionMaster()->MoveCharge(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ(), 25.0f, 1337);
|
me->GetMotionMaster()->MoveCharge(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ(), 25.0f, 1337);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
#include "ScriptedCreature.h"
|
#include "ScriptedCreature.h"
|
||||||
#include "SpellAuras.h"
|
#include "SpellAuras.h"
|
||||||
|
#include "World.h"
|
||||||
#include "vault_of_archavon.h"
|
#include "vault_of_archavon.h"
|
||||||
|
|
||||||
/* Vault of Archavon encounters:
|
/* Vault of Archavon encounters:
|
||||||
@@ -73,6 +74,8 @@ public:
|
|||||||
if (checkTimer >= 60000)
|
if (checkTimer >= 60000)
|
||||||
{
|
{
|
||||||
checkTimer -= 60000; // one minute
|
checkTimer -= 60000; // one minute
|
||||||
|
if (!sWorld->getBoolConfig(CONFIG_WINTERGRASP_KICK_VOA_PLAYERS))
|
||||||
|
return;
|
||||||
if (Battlefield* bf = sBattlefieldMgr->GetBattlefieldByBattleId(BATTLEFIELD_BATTLEID_WG))
|
if (Battlefield* bf = sBattlefieldMgr->GetBattlefieldByBattleId(BATTLEFIELD_BATTLEID_WG))
|
||||||
{
|
{
|
||||||
if (!bf->IsWarTime())
|
if (!bf->IsWarTime())
|
||||||
@@ -133,6 +136,9 @@ public:
|
|||||||
if (m_auiEncounter[i] == IN_PROGRESS)
|
if (m_auiEncounter[i] == IN_PROGRESS)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
if (!sWorld->getBoolConfig(CONFIG_WINTERGRASP_KICK_VOA_PLAYERS))
|
||||||
|
return false;
|
||||||
|
|
||||||
Battlefield* bf = sBattlefieldMgr->GetBattlefieldByBattleId(BATTLEFIELD_BATTLEID_WG);
|
Battlefield* bf = sBattlefieldMgr->GetBattlefieldByBattleId(BATTLEFIELD_BATTLEID_WG);
|
||||||
if (!bf || bf->IsWarTime() || bf->GetTimer() <= 10 * MINUTE * IN_MILLISECONDS)
|
if (!bf || bf->IsWarTime() || bf->GetTimer() <= 10 * MINUTE * IN_MILLISECONDS)
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
#include "ObjectMgr.h"
|
#include "ObjectMgr.h"
|
||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
#include "PoolMgr.h"
|
#include "PoolMgr.h"
|
||||||
#include "RaceMgr.h"
|
|
||||||
#include "ScriptedCreature.h"
|
#include "ScriptedCreature.h"
|
||||||
#include "ScriptedGossip.h"
|
#include "ScriptedGossip.h"
|
||||||
#include "SpellScript.h"
|
#include "SpellScript.h"
|
||||||
@@ -216,13 +215,19 @@ public:
|
|||||||
|
|
||||||
bool OnGossipHello(Player* player, Creature* creature) override
|
bool OnGossipHello(Player* player, Creature* creature) override
|
||||||
{
|
{
|
||||||
if (creature->IsQuestGiver())
|
|
||||||
player->PrepareQuestMenu(creature->GetGUID());
|
|
||||||
|
|
||||||
Battlefield* wintergrasp = sBattlefieldMgr->GetBattlefieldByBattleId(BATTLEFIELD_BATTLEID_WG);
|
Battlefield* wintergrasp = sBattlefieldMgr->GetBattlefieldByBattleId(BATTLEFIELD_BATTLEID_WG);
|
||||||
if (!wintergrasp)
|
if (!wintergrasp)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
if (!player->IsAlive())
|
||||||
|
{
|
||||||
|
wintergrasp->SendAreaSpiritHealerQueryOpcode(player, creature->GetGUID());
|
||||||
|
player->CastSpell(player, SPELL_WAITING_FOR_RESURRECT, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (creature->IsQuestGiver())
|
||||||
|
player->PrepareQuestMenu(creature->GetGUID());
|
||||||
|
|
||||||
GraveyardVect graveyard = wintergrasp->GetGraveyardVector();
|
GraveyardVect graveyard = wintergrasp->GetGraveyardVector();
|
||||||
for (uint8 i = 0; i < graveyard.size(); i++)
|
for (uint8 i = 0; i < graveyard.size(); i++)
|
||||||
if (graveyard[i]->GetControlTeamId() == player->GetTeamId())
|
if (graveyard[i]->GetControlTeamId() == player->GetTeamId())
|
||||||
@@ -860,8 +865,12 @@ public:
|
|||||||
|
|
||||||
bool IsFriendly(Unit* passenger)
|
bool IsFriendly(Unit* passenger)
|
||||||
{
|
{
|
||||||
return ((me->GetUInt32Value(GAMEOBJECT_FACTION) == WintergraspFaction[TEAM_HORDE] && passenger->getRaceMask() & sRaceMgr->GetHordeRaceMask()) ||
|
Player* player = passenger->ToPlayer();
|
||||||
(me->GetUInt32Value(GAMEOBJECT_FACTION) == WintergraspFaction[TEAM_ALLIANCE] && passenger->getRaceMask() & sRaceMgr->GetAllianceRaceMask()));
|
if (!player)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
TeamId goTeam = me->GetUInt32Value(GAMEOBJECT_FACTION) == WintergraspFaction[TEAM_HORDE] ? TEAM_HORDE : TEAM_ALLIANCE;
|
||||||
|
return player->GetTeamId() == goTeam;
|
||||||
}
|
}
|
||||||
|
|
||||||
Creature* IsValidVehicle(Creature* cVeh)
|
Creature* IsValidVehicle(Creature* cVeh)
|
||||||
@@ -1160,6 +1169,10 @@ public:
|
|||||||
if (!wintergrasp)
|
if (!wintergrasp)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Attacker-only achievement -- defenders winning fast must not qualify.
|
||||||
|
if (!sBattlefieldMgr->IsWintergraspAttackerVictory())
|
||||||
|
return false;
|
||||||
|
|
||||||
return wintergrasp->GetTimer() >= (20 * MINUTE * IN_MILLISECONDS);
|
return wintergrasp->GetTimer() >= (20 * MINUTE * IN_MILLISECONDS);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -70,12 +70,6 @@ enum Groups
|
|||||||
GROUP_EARLY_RELEASE_CHECK = 0
|
GROUP_EARLY_RELEASE_CHECK = 0
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Actions
|
|
||||||
{
|
|
||||||
ACTION_INCREASE_HELLFIRE_CHANNELER_DEATH_COUNT = 1,
|
|
||||||
ACTION_BANISH_SELF = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
struct boss_magtheridon : public BossAI
|
struct boss_magtheridon : public BossAI
|
||||||
{
|
{
|
||||||
boss_magtheridon(Creature* creature) : BossAI(creature, DATA_MAGTHERIDON)
|
boss_magtheridon(Creature* creature) : BossAI(creature, DATA_MAGTHERIDON)
|
||||||
@@ -84,7 +78,6 @@ struct boss_magtheridon : public BossAI
|
|||||||
void Reset() override
|
void Reset() override
|
||||||
{
|
{
|
||||||
BossAI::Reset();
|
BossAI::Reset();
|
||||||
_channelersKilled = 0;
|
|
||||||
_currentPhase = 0;
|
_currentPhase = 0;
|
||||||
_castingQuake = false;
|
_castingQuake = false;
|
||||||
_recentlySpoken = false;
|
_recentlySpoken = false;
|
||||||
@@ -194,21 +187,19 @@ struct boss_magtheridon : public BossAI
|
|||||||
|
|
||||||
void DoAction(int32 action) override
|
void DoAction(int32 action) override
|
||||||
{
|
{
|
||||||
if (action == ACTION_INCREASE_HELLFIRE_CHANNELER_DEATH_COUNT)
|
if (action == ACTION_RELEASE_MAGTHERIDON)
|
||||||
{
|
{
|
||||||
_channelersKilled++;
|
if (_magReleased)
|
||||||
|
return;
|
||||||
|
|
||||||
if (_channelersKilled >= 5 && !_magReleased)
|
Talk(SAY_EMOTE_FREE);
|
||||||
|
Talk(SAY_FREE);
|
||||||
|
scheduler.CancelGroup(GROUP_EARLY_RELEASE_CHECK); //cancel regular countdown
|
||||||
|
_magReleased = true;
|
||||||
|
scheduler.Schedule(3s, [this](TaskContext)
|
||||||
{
|
{
|
||||||
Talk(SAY_EMOTE_FREE);
|
ScheduleCombatEvents();
|
||||||
Talk(SAY_FREE);
|
});
|
||||||
scheduler.CancelGroup(GROUP_EARLY_RELEASE_CHECK); //cancel regular countdown
|
|
||||||
_magReleased = true;
|
|
||||||
scheduler.Schedule(3s, [this](TaskContext)
|
|
||||||
{
|
|
||||||
ScheduleCombatEvents();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (action == ACTION_BANISH_SELF)
|
else if (action == ACTION_BANISH_SELF)
|
||||||
{
|
{
|
||||||
@@ -222,10 +213,6 @@ struct boss_magtheridon : public BossAI
|
|||||||
BossAI::JustEngagedWith(who);
|
BossAI::JustEngagedWith(who);
|
||||||
Talk(SAY_EMOTE_BEGIN);
|
Talk(SAY_EMOTE_BEGIN);
|
||||||
|
|
||||||
instance->DoForAllMinions(DATA_MAGTHERIDON, [&](Creature* creature) {
|
|
||||||
creature->SetInCombatWithZone();
|
|
||||||
});
|
|
||||||
|
|
||||||
scheduler.Schedule(60s, GROUP_EARLY_RELEASE_CHECK, [this](TaskContext /*context*/)
|
scheduler.Schedule(60s, GROUP_EARLY_RELEASE_CHECK, [this](TaskContext /*context*/)
|
||||||
{
|
{
|
||||||
Talk(SAY_EMOTE_NEARLY);
|
Talk(SAY_EMOTE_NEARLY);
|
||||||
@@ -260,7 +247,6 @@ private:
|
|||||||
bool _recentlySpoken;
|
bool _recentlySpoken;
|
||||||
bool _magReleased;
|
bool _magReleased;
|
||||||
uint8 _currentPhase;
|
uint8 _currentPhase;
|
||||||
uint8 _channelersKilled;
|
|
||||||
TaskScheduler _interruptScheduler;
|
TaskScheduler _interruptScheduler;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+60
-14
@@ -15,6 +15,7 @@
|
|||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "CreatureAI.h"
|
||||||
#include "InstanceMapScript.h"
|
#include "InstanceMapScript.h"
|
||||||
#include "InstanceScript.h"
|
#include "InstanceScript.h"
|
||||||
#include "magtheridons_lair.h"
|
#include "magtheridons_lair.h"
|
||||||
@@ -30,12 +31,6 @@ DoorData const doorData[] =
|
|||||||
{ 0, 0, DOOR_TYPE_ROOM } // END
|
{ 0, 0, DOOR_TYPE_ROOM } // END
|
||||||
};
|
};
|
||||||
|
|
||||||
MinionData const minionData[] =
|
|
||||||
{
|
|
||||||
{ NPC_HELLFIRE_CHANNELER, DATA_MAGTHERIDON },
|
|
||||||
{ 0, 0 } // END
|
|
||||||
};
|
|
||||||
|
|
||||||
class instance_magtheridons_lair : public InstanceMapScript
|
class instance_magtheridons_lair : public InstanceMapScript
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -48,17 +43,28 @@ public:
|
|||||||
SetHeaders(DataHeader);
|
SetHeaders(DataHeader);
|
||||||
SetBossNumber(MAX_ENCOUNTER);
|
SetBossNumber(MAX_ENCOUNTER);
|
||||||
LoadDoorData(doorData);
|
LoadDoorData(doorData);
|
||||||
LoadMinionData(minionData);
|
|
||||||
LoadBossBoundaries(boundaries);
|
LoadBossBoundaries(boundaries);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Initialize() override
|
void Initialize() override
|
||||||
{
|
{
|
||||||
|
_channelersSet.clear();
|
||||||
_wardersSet.clear();
|
_wardersSet.clear();
|
||||||
|
_burningAbyssalsSet.clear();
|
||||||
_cubesSet.clear();
|
_cubesSet.clear();
|
||||||
_columnSet.clear();
|
_columnSet.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsAnyChannelerAlive()
|
||||||
|
{
|
||||||
|
return std::ranges::any_of(_channelersSet, [&](ObjectGuid const& guid)
|
||||||
|
{
|
||||||
|
if (Creature* channeler = instance->GetCreature(guid))
|
||||||
|
return channeler->IsAlive();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void OnCreatureCreate(Creature* creature) override
|
void OnCreatureCreate(Creature* creature) override
|
||||||
{
|
{
|
||||||
switch (creature->GetEntry())
|
switch (creature->GetEntry())
|
||||||
@@ -67,22 +73,42 @@ public:
|
|||||||
_magtheridonGUID = creature->GetGUID();
|
_magtheridonGUID = creature->GetGUID();
|
||||||
break;
|
break;
|
||||||
case NPC_HELLFIRE_CHANNELER:
|
case NPC_HELLFIRE_CHANNELER:
|
||||||
AddMinion(creature);
|
_channelersSet.insert(creature->GetGUID());
|
||||||
break;
|
break;
|
||||||
case NPC_HELLFIRE_WARDER:
|
case NPC_HELLFIRE_WARDER:
|
||||||
_wardersSet.insert(creature->GetGUID());
|
_wardersSet.insert(creature->GetGUID());
|
||||||
break;
|
break;
|
||||||
|
case NPC_BURNING_ABYSSAL:
|
||||||
|
_burningAbyssalsSet.insert(creature->GetGUID());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnCreatureRemove(Creature* creature) override
|
void OnCreatureRemove(Creature* creature) override
|
||||||
{
|
{
|
||||||
switch (creature->GetEntry())
|
if (creature->GetEntry() == NPC_BURNING_ABYSSAL)
|
||||||
{
|
_burningAbyssalsSet.erase(creature->GetGUID());
|
||||||
case NPC_HELLFIRE_CHANNELER:
|
}
|
||||||
RemoveMinion(creature);
|
|
||||||
break;
|
void OnUnitDeath(Unit* unit) override
|
||||||
}
|
{
|
||||||
|
Creature* creature = unit ? unit->ToCreature() : nullptr;
|
||||||
|
if (!creature || creature->GetEntry() != NPC_HELLFIRE_CHANNELER)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// IN_PROGRESS guard: stays inert during the hard-reset Respawn(true) cycle.
|
||||||
|
if (GetBossState(DATA_MAGTHERIDON) != IN_PROGRESS || IsAnyChannelerAlive())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Creature* magtheridon = instance->GetCreature(_magtheridonGUID))
|
||||||
|
magtheridon->AI()->DoAction(ACTION_RELEASE_MAGTHERIDON);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnCreatureEvade(Creature* creature) override
|
||||||
|
{
|
||||||
|
// Phase-1 wipe signal: Mag is ImmuneToPC so BossAI evade does not fire; a Channeler evade is the trigger.
|
||||||
|
if (creature->GetEntry() == NPC_HELLFIRE_CHANNELER && GetBossState(DATA_MAGTHERIDON) == IN_PROGRESS)
|
||||||
|
SetBossState(DATA_MAGTHERIDON, NOT_STARTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnGameObjectCreate(GameObject* go) override
|
void OnGameObjectCreate(GameObject* go) override
|
||||||
@@ -154,6 +180,18 @@ public:
|
|||||||
|
|
||||||
if (state == NOT_STARTED)
|
if (state == NOT_STARTED)
|
||||||
SetData(DATA_COLLAPSE, GO_READY);
|
SetData(DATA_COLLAPSE, GO_READY);
|
||||||
|
|
||||||
|
// Hard reset: vanish Channelers and their lingering Burning Abyssal summons.
|
||||||
|
if (state == NOT_STARTED || state == FAIL)
|
||||||
|
{
|
||||||
|
for (ObjectGuid const& guid : _channelersSet)
|
||||||
|
if (Creature* channeler = instance->GetCreature(guid))
|
||||||
|
channeler->Respawn(true);
|
||||||
|
|
||||||
|
for (ObjectGuid const& guid : _burningAbyssalsSet)
|
||||||
|
if (Creature* abyssal = instance->GetCreature(guid))
|
||||||
|
abyssal->DespawnOrUnsummon();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -164,9 +202,13 @@ public:
|
|||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case DATA_CHANNELER_COMBAT:
|
case DATA_CHANNELER_COMBAT:
|
||||||
|
// Force the encounter start: Mag is ImmuneToPC so SetInCombatWithZone alone may miss JustEngagedWith.
|
||||||
if (GetBossState(DATA_MAGTHERIDON) != IN_PROGRESS)
|
if (GetBossState(DATA_MAGTHERIDON) != IN_PROGRESS)
|
||||||
|
{
|
||||||
|
SetBossState(DATA_MAGTHERIDON, IN_PROGRESS);
|
||||||
if (Creature* magtheridon = instance->GetCreature(_magtheridonGUID))
|
if (Creature* magtheridon = instance->GetCreature(_magtheridonGUID))
|
||||||
magtheridon->SetInCombatWithZone();
|
magtheridon->SetInCombatWithZone();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case DATA_ACTIVATE_CUBES:
|
case DATA_ACTIVATE_CUBES:
|
||||||
for (ObjectGuid const& guid : _cubesSet)
|
for (ObjectGuid const& guid : _cubesSet)
|
||||||
@@ -178,12 +220,16 @@ public:
|
|||||||
if (GameObject* column = instance->GetGameObject(guid))
|
if (GameObject* column = instance->GetGameObject(guid))
|
||||||
column->SetGoState(GOState(data));
|
column->SetGoState(GOState(data));
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ObjectGuid _magtheridonGUID;
|
ObjectGuid _magtheridonGUID;
|
||||||
|
GuidSet _channelersSet;
|
||||||
GuidSet _wardersSet;
|
GuidSet _wardersSet;
|
||||||
|
GuidSet _burningAbyssalsSet;
|
||||||
GuidSet _cubesSet;
|
GuidSet _cubesSet;
|
||||||
GuidSet _columnSet;
|
GuidSet _columnSet;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,7 +39,14 @@ enum NpcIds
|
|||||||
NPC_HELLFIRE_CHANNELER = 17256,
|
NPC_HELLFIRE_CHANNELER = 17256,
|
||||||
NPC_HELLFIRE_WARDER = 18829,
|
NPC_HELLFIRE_WARDER = 18829,
|
||||||
NPC_HELLFIRE_RAID_TRIGGER = 17376,
|
NPC_HELLFIRE_RAID_TRIGGER = 17376,
|
||||||
NPC_TARGET_TRIGGER = 17474
|
NPC_TARGET_TRIGGER = 17474,
|
||||||
|
NPC_BURNING_ABYSSAL = 17454
|
||||||
|
};
|
||||||
|
|
||||||
|
enum MagtheridonActions
|
||||||
|
{
|
||||||
|
ACTION_RELEASE_MAGTHERIDON = 1,
|
||||||
|
ACTION_BANISH_SELF = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
enum GoIds
|
enum GoIds
|
||||||
|
|||||||
@@ -787,27 +787,25 @@ class spell_item_feast : public SpellScript
|
|||||||
Unit* caster = GetCaster();
|
Unit* caster = GetCaster();
|
||||||
if (Player* player = caster->ToPlayer())
|
if (Player* player = caster->ToPlayer())
|
||||||
{
|
{
|
||||||
LocaleConstant loc_idx = player->GetSession()->GetSessionDbLocaleIndex();
|
uint32 textId = 0;
|
||||||
|
|
||||||
switch (GetSpellInfo()->Id)
|
switch (GetSpellInfo()->Id)
|
||||||
{
|
{
|
||||||
case SPELL_GREAT_FEAST:
|
case SPELL_GREAT_FEAST:
|
||||||
if (BroadcastText const* bct = sObjectMgr->GetBroadcastText(GREAT_FEAST_BROADCAST_TEXT_ID_PREPARE))
|
textId = GREAT_FEAST_BROADCAST_TEXT_ID_PREPARE;
|
||||||
player->TextEmote(bct->GetText(loc_idx, player->getGender()), player);
|
|
||||||
break;
|
break;
|
||||||
case SPELL_FISH_FEAST:
|
case SPELL_FISH_FEAST:
|
||||||
if (BroadcastText const* bct = sObjectMgr->GetBroadcastText(FISH_FEAST_BROADCAST_TEXT_ID_PREPARE))
|
textId = FISH_FEAST_BROADCAST_TEXT_ID_PREPARE;
|
||||||
player->TextEmote(bct->GetText(loc_idx, player->getGender()), player);
|
|
||||||
break;
|
break;
|
||||||
case SPELL_SMALL_FEAST:
|
case SPELL_SMALL_FEAST:
|
||||||
if (BroadcastText const* bct = sObjectMgr->GetBroadcastText(SMALL_FEAST_BROADCAST_TEXT_ID_PREPARE))
|
textId = SMALL_FEAST_BROADCAST_TEXT_ID_PREPARE;
|
||||||
player->TextEmote(bct->GetText(loc_idx, player->getGender()), player);
|
|
||||||
break;
|
break;
|
||||||
case SPELL_GIGANTIC_FEAST:
|
case SPELL_GIGANTIC_FEAST:
|
||||||
if (BroadcastText const* bct = sObjectMgr->GetBroadcastText(GIGANTIC_FEAST_BROADCAST_TEXT_ID_PREPARE))
|
textId = GIGANTIC_FEAST_BROADCAST_TEXT_ID_PREPARE;
|
||||||
player->TextEmote(bct->GetText(loc_idx, player->getGender()), player);
|
break;
|
||||||
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
player->Unit::TextEmote(textId, player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,18 +52,25 @@ public:
|
|||||||
if (player->GetZoneId() != AREA_ISLE_OF_QUEL_DANAS)
|
if (player->GetZoneId() != AREA_ISLE_OF_QUEL_DANAS)
|
||||||
disabled = true;
|
disabled = true;
|
||||||
break;
|
break;
|
||||||
case 34475:
|
|
||||||
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(SPELL_ARCANE_CHARGES))
|
|
||||||
Spell::SendCastResult(player, spellInfo, 1, SPELL_FAILED_NOT_ON_GROUND);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// allow use in flight only
|
// allow use in flight only
|
||||||
if (player->IsInFlight() && !disabled)
|
if (player->IsInFlight())
|
||||||
return false;
|
{
|
||||||
|
if (!disabled)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else // error
|
||||||
|
{
|
||||||
|
if (itemId == 34475)
|
||||||
|
{
|
||||||
|
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(SPELL_ARCANE_CHARGES))
|
||||||
|
Spell::SendCastResult(player, spellInfo, 1, SPELL_FAILED_NOT_ON_GROUND);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
player->SendEquipError(EQUIP_ERR_CANT_DO_RIGHT_NOW, item, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
// error
|
|
||||||
player->SendEquipError(EQUIP_ERR_CANT_DO_RIGHT_NOW, item, nullptr);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -734,7 +734,7 @@ TEST_F(ThreatManagerIntegrationTest,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Phase Change + Threat Offline State Tests (SetPhaseMask order fix)
|
// Phase Change + Threat Offline State Tests
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
TEST_F(ThreatManagerIntegrationTest,
|
TEST_F(ThreatManagerIntegrationTest,
|
||||||
@@ -748,6 +748,10 @@ TEST_F(ThreatManagerIntegrationTest,
|
|||||||
// Move B to a different phase
|
// Move B to a different phase
|
||||||
_creatureB->SetPhase(2);
|
_creatureB->SetPhase(2);
|
||||||
|
|
||||||
|
// Online state is refreshed lazily on the next threat update tick (matches
|
||||||
|
// TrinityCore - SetPhaseMask itself does not touch threat)
|
||||||
|
_creatureA->TestGetThreatMgr().Update(ThreatManager::THREAT_UPDATE_INTERVAL);
|
||||||
|
|
||||||
// B should now be offline on A's threat list (different phases)
|
// B should now be offline on A's threat list (different phases)
|
||||||
// The list is not empty if we include offline, but empty if we don't
|
// The list is not empty if we include offline, but empty if we don't
|
||||||
EXPECT_TRUE(_creatureA->TestGetThreatMgr().IsThreatenedBy(_creatureB, true)); // includeOffline
|
EXPECT_TRUE(_creatureA->TestGetThreatMgr().IsThreatenedBy(_creatureB, true)); // includeOffline
|
||||||
|
|||||||
Reference in New Issue
Block a user