Skip to content

Commit d6e8f74

Browse files
Merge AzerothCore 3.3.5 to ElunaAzerothcore [skip ci]
2 parents 8966669 + af2c766 commit d6e8f74

5 files changed

Lines changed: 116 additions & 96 deletions

File tree

CLAUDE.md

Lines changed: 69 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,104 @@
11
# CLAUDE.md
22

3-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
3+
AzerothCore is a C++ MMORPG server emulator for World of Warcraft 3.3.5a (WotLK), built with CMake, backed by MySQL.
44

5-
## Project Overview
5+
## Agent rules
66

7-
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.
7+
- **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.
8+
- **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).
9+
- **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.
810

9-
## Build Commands
11+
## Build
1012

11-
### Configure and build (out-of-source build required)
12-
13-
- Skip building unless explicitly requested.
13+
Out-of-source build is required (in-source is blocked by CMake).
1414

1515
```bash
16-
# Create build directory and configure
1716
mkdir -p build && cd build
1817
cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/azeroth-server -DCMAKE_BUILD_TYPE=RelWithDebInfo \
1918
-DSCRIPTS=static -DMODULES=static
20-
21-
# Build (use appropriate core count)
22-
make -j$(nproc)
23-
make install
19+
make -j$(nproc) && make install
2420
```
2521

26-
### Key CMake options
27-
28-
- `SCRIPTS`: none, static, dynamic, minimal-static, minimal-dynamic (default: static)
29-
- `MODULES`: none, static, dynamic (default: static)
30-
- `APPS_BUILD`: none, all, auth-only, world-only (default: all)
31-
- `TOOLS_BUILD`: none, all, db-only, maps-only (default: none)
32-
- `BUILD_TESTING`: Enable unit tests (default: OFF)
33-
- `USE_COREPCH` / `USE_SCRIPTPCH`: Precompiled headers (default: ON)
22+
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.
3423

35-
### Unit tests
36-
37-
```bash
38-
# Configure with testing enabled
39-
cmake .. -DBUILD_TESTING=ON
40-
make -j$(nproc)
41-
42-
# Run tests
43-
./src/test/unit_tests
44-
# or
45-
ctest
46-
```
24+
Tests (Google Test, in `src/test/`): configure with `-DBUILD_TESTING=ON`, then `ctest` or `./src/test/unit_tests` from the build dir.
4725

48-
Tests use Google Test and live in `src/test/`. The test binary links against the `game` library.
26+
## Repository layout
4927

50-
## Architecture
28+
- `src/common/` — networking (Asio), crypto, config, logging, shared utilities.
29+
- `src/server/game/` — core gameplay; compiled into worldserver.
30+
- `src/server/scripts/` — content scripts grouped by region (`EasternKingdoms/`, `Northrend/`, …), class (`Spells/spell_mage.cpp`, …), and domain (`Commands/`, `Pet/`, `OutdoorPvP/`, `World/`).
31+
- `src/server/database/` — DB abstraction and schema updater.
32+
- `src/server/shared/` — code shared by auth and world servers.
33+
- `src/server/apps/{authserver,worldserver}/` — entry points (ports 3724 and 8085).
34+
- `src/test/` — Google Test unit tests + mocks.
35+
- `data/sql/``base/` (historical schema), `updates/db_*/` (merged), `updates/pending_db_*/` (in-flight, **edit here**), `custom/` (gitignored).
36+
- `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`.
37+
- `apps/` — helper scripts; `apps/codestyle/` holds the lint scripts (see below).
38+
- `conf/dist/` — distributed config templates; `conf/*.conf` is gitignored.
39+
- `deps/` — vendored third-party dependencies.
5140

52-
### Two server executables
53-
- **authserver** (`src/server/apps/authserver/`): Handles authentication and realm selection (port 3724)
54-
- **worldserver** (`src/server/apps/worldserver/`): Main game server handling all gameplay (port 8085)
41+
## Adding SQL updates
5542

56-
### Source layout (`src/`)
43+
1. `cd data/sql/updates/pending_db_world/` (or `pending_db_auth` / `pending_db_characters`).
44+
2. `./create_sql.sh` generates an empty `rev_<timestamp>.sql` you write into.
45+
3. Required SQL conventions (enforced by `apps/codestyle/codestyle-sql.py`):
46+
- Every `INSERT` must be preceded by a matching `DELETE` (idempotency).
47+
- 4-space indent (no tabs), trailing newline, no double semicolons, no multiple blank lines.
48+
- Tables must use the InnoDB engine.
5749

58-
- **`src/common/`** - Shared libraries: networking (Asio), cryptography, configuration, logging, threading, collision detection, utilities
59-
- **`src/server/game/`** - Core game logic (~52 subsystems), the heart of the worldserver
60-
- **`src/server/scripts/`** - Content scripts (bosses, spells, commands, instances)
61-
- **`src/server/database/`** - Database abstraction layer and schema updater
62-
- **`src/server/shared/`** - Code shared between auth and world servers (packets, network, realm definitions)
63-
- **`src/test/`** - Unit tests (Google Test)
50+
The three databases:
6451

65-
### Key game subsystems (`src/server/game/`)
52+
- `acore_auth` — accounts, realm list, IP/account bans, session keys. Shared across all realms.
53+
- `acore_characters` — per-character state: characters, inventory, in-progress quests, mail, guilds, arena teams, achievements. One per realm.
54+
- `acore_world` — static game content: creature/gameobject/item/quest templates, spawn lists, loot tables, SmartAI scripts, gossip, conditions. Read-mostly; rebuilt from SQL.
6655

67-
- **Entities/** - Core game objects: `Player`, `Creature`, `Unit`, `Item`, `GameObject`
68-
- **Spells/** - Spell mechanics, aura system, spell effects
69-
- **Maps/** - Map management, grid system, instancing
70-
- **Handlers/** - Client packet handlers (one file per system: `MovementHandler.cpp`, `SpellHandler.cpp`, etc.). These are methods on `WorldSession`
71-
- **AI/** - Creature AI framework
72-
- **Scripting/** - Script system with typed base classes (`ScriptObject` subclasses: `CreatureScript`, `SpellScript`, `InstanceMapScript`, `GameObjectScript`, `CommandScript`, etc.)
73-
- **Server/** - `WorldSession` (per-player connection), `World` (global state), opcode definitions
56+
## Code style
7457

75-
### Scripting system
58+
Run the linters before claiming a change is done:
7659

77-
Scripts follow a registration pattern:
78-
1. Define a class inheriting from `SpellScript`, `CreatureScript`, etc.
79-
2. Implement an `AddSC_*()` function that calls `RegisterSpellScript(ClassName)` (or similar)
80-
3. The `AddSC_*()` is declared and called from the regional `*_script_loader.cpp`
81-
4. Script loaders per region: `spells_script_loader.cpp`, `eastern_kingdoms_script_loader.cpp`, `northrend_script_loader.cpp`, etc.
82-
5. Spell script files are organized by class: `spell_dk.cpp`, `spell_mage.cpp`, `spell_generic.cpp`, etc.
83-
84-
### Three databases
85-
- **acore_auth** - Accounts, realm list, bans (`data/sql/base/db_auth/`)
86-
- **acore_characters** - Character data, inventories, progress (`data/sql/base/db_characters/`)
87-
- **acore_world** - Game content: creatures, items, quests, spells, loot (`data/sql/base/db_world/`)
60+
```bash
61+
python apps/codestyle/codestyle-cpp.py # C++
62+
python apps/codestyle/codestyle-sql.py # SQL (compares to origin/master)
63+
```
8864

89-
- 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.
90-
- SQL updates go in `data/sql/updates/` with separate subdirectories per database after their pull request is merged.
91-
- SQL files outside the `data/sql/updates/pending_*` folders should never be updated.
65+
Hard rules (also enforced by CI with `-Werror`):
9266

93-
### Module system
67+
- 4-space indent for C++ (tabs forbidden); 2-space for JSON/YAML/sh/ts/js. UTF-8, LF, max 80 cols, trailing newline.
68+
- Allman braces. No braces around single-line statements. `if (x)` — never `if(x)` or `if ( x )`.
69+
- `auto const&` (not `const auto&`); `Type const*` (not `const Type*`).
70+
- Use `{}` format specifiers (`fmt`-style), not `%u`/`%s`.
71+
- Use the typed helpers, not raw flag access:
72+
- `IsPlayer()`, `IsCreature()`, `IsItem()`, … instead of `GetTypeId() == TYPEID_*`.
73+
- `GetNpcFlags()`, `HasNpcFlag()`, `SetNpcFlag()`, `RemoveNpcFlag()`, `ReplaceAllNpcFlags()` instead of `*Flag(UNIT_NPC_FLAGS, …)`.
74+
- `IsRefundable()`, `IsBOPTradable()`, `IsWrapped()` instead of `HasFlag(ITEM_FIELD_FLAGS, …)`.
75+
- `HasFlag(ItemFlag)` / `HasFlag2(ItemFlag2)` / `HasFlagCu(ItemFlagsCustom)` instead of bitwise `Flags & ITEM_FLAG…`.
76+
- `ObjectGuid::ToString().c_str()` instead of `ObjectGuid::GetCounter()`.
9477

95-
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/
78+
CI also runs `cppcheck`.
9679

97-
### Dependencies
80+
## Project conventions
9881

99-
Bundled in `deps/`: boost, MySQL client, OpenSSL, zlib, recastnavigation (pathfinding), g3dlite (geometry), fmt, argon2, jemalloc, and others.
82+
- **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`.
83+
- **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.
84+
- **Strings**: `Acore::StringFormat(fmt, args...)` (wraps `fmt::format`, `{}` placeholders) — `src/common/Utilities/StringFormat.h`.
85+
- **Config**: read options with `sConfigMgr->GetOption<T>("Name", default)`.
86+
- **Namespace**: project-wide is `Acore::` (no `Trinity::` remnants — agents porting from upstream forks must rename).
87+
- **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.
88+
- **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`.
89+
- **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.
10090

101-
## Commit Message Format
91+
## Scripting registration
10292

103-
Uses Conventional Commits:
104-
```
105-
Type(Scope/Subscope): Short description (max 50 chars)
106-
```
93+
Scripts inherit from a `ScriptObject` subclass (`SpellScript`, `AuraScript`, `CreatureScript`, `InstanceMapScript`, `GameObjectScript`, `CommandScript`, …). Two registration styles coexist:
10794

108-
- **Types**: feat, fix, refactor, style, docs, test, chore
109-
- **Scopes**: Core (C++ changes), DB (SQL changes)
110-
- **Examples**: `fix(Core/Spells): Fix damage calculation for Fireball`, `fix(DB/SAI): Missing spell to NPC Hogger`
95+
- **Spell / aura scripts**: use the `RegisterSpellScript(ClassName)` (or `RegisterSpellAndAuraScriptPair(...)`) macro inside `AddSC_<name>()`.
96+
- **Creature scripts**: prefer `RegisterCreatureAI(ClassName)` for new code; legacy zones still use `new ClassName();`. Match the surrounding pattern.
11197

112-
## Code Style
98+
Then declare and call `AddSC_<name>()` from the regional loader: `Spells/spells_script_loader.cpp`, `EasternKingdoms/eastern_kingdoms_script_loader.cpp`, etc.
11399

114-
- 4-space indentation for C++ (no tabs)
115-
- 2-space indentation for JSON, YAML, shell scripts
116-
- UTF-8 encoding, LF line endings
117-
- Max 80 character line length
118-
- No braces around single-line statements
119-
- Use {} to parse variables into output instead of %u etc.
120-
- CI enforces code style checks and compiles with `-Werror`
100+
**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.
121101

122-
## PR Requirements
102+
**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.
123103

124-
- AI tool usage must be disclosed in PRs
125-
- In-game testing expected
126-
- Changes to generic code require regression testing of related systems
104+
Custom (non-upstream) scripts go in `src/server/scripts/Custom/` (gitignored).
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- DB update 2026_05_24_02 -> 2026_05_25_00
2+
-- Hide Whispering Wind (30848) during Wintergrasp battle.
3+
-- Aura 52107 phases the creature out while war is active; the other five
4+
-- small WG elementals (30842, 30845, 30846, 30847, 30849) already have it
5+
-- in creature_template_addon — 30848 was missed.
6+
DELETE FROM `creature_template_addon` WHERE `entry` = 30848;
7+
INSERT INTO `creature_template_addon` (`entry`, `path_id`, `mount`, `bytes1`, `bytes2`, `emote`, `visibilityDistanceType`, `auras`) VALUES
8+
(30848, 0, 0, 0, 1, 0, 0, '52107');

deps/readline/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ if( UNIX )
1717
# READLINE_LIBRARY - full path to the READLINE library
1818
find_path(READLINE_INCLUDE_DIR readline/readline.h)
1919
find_library(READLINE_LIBRARY NAMES readline)
20+
# macOS: Homebrew readline is keg-only, so this finds the SDK's libedit stub
21+
# and worldserver fails to link. After `brew install readline`, configure with
22+
# -DREADLINE_LIBRARY=/opt/homebrew/opt/readline/lib/libreadline.dylib and
23+
# -DREADLINE_INCLUDE_DIR=/opt/homebrew/opt/readline/include
2024

2125
message(STATUS "Found Readline library: ${READLINE_LIBRARY}")
2226
message(STATUS "Include dir is: ${READLINE_INCLUDE_DIR}")

src/server/game/Battlefield/Battlefield.cpp

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,14 @@ void Battlefield::KickPlayerFromBattlefield(ObjectGuid guid)
292292
if (Player* player = ObjectAccessor::FindPlayer(guid))
293293
if (player->GetZoneId() == GetZoneId() && !player->IsGameMaster()
294294
&& !PlayersInWar[player->GetTeamId()].count(guid))
295+
{
295296
player->TeleportTo(KickPosition);
297+
// Eagerly drop zone tracking: the teleport's zone change does not
298+
// propagate until the next Player::Update, so callers iterating
299+
// Players[team] in the same tick would otherwise still see them.
300+
for (uint8 i = 0; i < PVP_TEAMS_COUNT; ++i)
301+
Players[i].erase(guid);
302+
}
296303
}
297304

298305
void Battlefield::StartBattle()
@@ -318,11 +325,25 @@ void Battlefield::StartBattle()
318325

319326
_scheduler.Schedule(1s, BATTLEFIELD_TIMER_GROUP_WAR, [this](TaskContext context)
320327
{
321-
time_t now = GameTime::GetGameTime().count();
328+
time_t const now = GameTime::GetGameTime().count();
329+
330+
// Send eject so the 3.3.5 client closes its popup (it does not on its
331+
// own when the timer hits zero), then drop the entry and teleport.
322332
for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team)
323-
for (PlayerTimerMap::value_type const& pair : InvitedPlayers[team])
324-
if (pair.second <= now)
325-
KickPlayerFromBattlefield(pair.first);
333+
{
334+
std::vector<ObjectGuid> expired;
335+
for (auto const& [guid, expireAt] : InvitedPlayers[team])
336+
if (expireAt <= now)
337+
expired.push_back(guid);
338+
339+
for (ObjectGuid const& guid : expired)
340+
{
341+
if (Player* player = ObjectAccessor::FindPlayer(guid))
342+
player->GetSession()->SendBfLeaveMessage(BattleId, BF_LEAVE_REASON_EXITED);
343+
InvitedPlayers[team].erase(guid);
344+
KickPlayerFromBattlefield(guid);
345+
}
346+
}
326347

327348
InvitePlayersInZoneToWar();
328349
for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team)
@@ -415,13 +436,21 @@ void Battlefield::PlayerAcceptInviteToWar(Player* player)
415436
if (!IsWarTime())
416437
return;
417438

439+
// Reject unknown / expired invites; the kick task only sweeps every 5s.
440+
TeamId const invitedTeam = player->GetTeamId();
441+
auto itr = InvitedPlayers[invitedTeam].find(player->GetGUID());
442+
if (itr == InvitedPlayers[invitedTeam].end()
443+
|| itr->second <= GameTime::GetGameTime().count())
444+
return;
445+
418446
sScriptMgr->OnBattlefieldPlayerJoinWar(this, player);
419447

420448
if (AddOrSetPlayerToCorrectBfGroup(player))
421449
{
422450
player->GetSession()->SendBfEntered(BattleId);
423451
PlayersInWar[player->GetTeamId()].insert(player->GetGUID());
424-
InvitedPlayers[player->GetTeamId()].erase(player->GetGUID());
452+
// Use pre-hook team: JoinWar may have just reassigned GetTeamId().
453+
InvitedPlayers[invitedTeam].erase(player->GetGUID());
425454

426455
if (player->isAFK())
427456
player->ToggleAFK();

src/server/scripts/EasternKingdoms/Karazhan/boss_moroes.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ struct boss_moroes : public BossAI
119119
DoCastSelf(SPELL_DUAL_WIELD, true);
120120
_recentlySpoken = false;
121121
_vanished = false;
122+
me->SetImmuneToAll(false);
122123

123124
InitializeGuests();
124125

0 commit comments

Comments
 (0)