AzerothCore is a C++ MMORPG server emulator for World of Warcraft 3.3.5a (WotLK), built with CMake, backed by MySQL.
- 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/, anddata/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.
Out-of-source build is required (in-source is blocked by CMake).
mkdir -p build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/azeroth-server -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DSCRIPTS=static -DMODULES=static
make -j$(nproc) && make installCompiler: C++20 required (CMAKE_CXX_STANDARD 20). Useful CMake flags: BUILD_TESTING=ON (Google Test), NOPCH=1 (disable precompiled headers). Full flag set in conf/dist/config.cmake. compile_commands.json is exported automatically.
Tests (Google Test, in src/test/): configure with -DBUILD_TESTING=ON, then ctest or ./src/test/unit_tests from the build dir.
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 ownCMakeLists.txt). Disable with-DDISABLED_AC_MODULES="mod1;mod2". Seemodules/how_to_make_a_module.md.apps/— helper scripts;apps/codestyle/holds the lint scripts (see below).conf/dist/— distributed config templates;conf/*.confis gitignored.deps/— vendored third-party dependencies.
cd data/sql/updates/pending_db_world/(orpending_db_auth/pending_db_characters)../create_sql.shgenerates an emptyrev_<timestamp>.sqlyou write into.- Required SQL conventions (enforced by
apps/codestyle/codestyle-sql.py):- Every
INSERTmust be preceded by a matchingDELETE(idempotency). - 4-space indent (no tabs), trailing newline, no double semicolons, no multiple blank lines.
- Tables must use the InnoDB engine.
- Every
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.
Run the linters before claiming a change is done:
python apps/codestyle/codestyle-cpp.py # C++
python apps/codestyle/codestyle-sql.py # SQL (compares to origin/master)Hard rules (also enforced by CI with -Werror):
- 4-space indent for C++ (tabs forbidden); 2-space for JSON/YAML/sh/ts/js. UTF-8, LF, max 120 cols, trailing newline.
- Allman braces. No braces around single-line statements.
if (x)— neverif(x)orif ( x ). auto const&(notconst auto&);Type const*(notconst Type*).- Use
{}format specifiers (fmt-style), not%u/%s. - Use the typed helpers, not raw flag access:
IsPlayer(),IsCreature(),IsItem(), … instead ofGetTypeId() == TYPEID_*.GetNpcFlags(),HasNpcFlag(),SetNpcFlag(),RemoveNpcFlag(),ReplaceAllNpcFlags()instead of*Flag(UNIT_NPC_FLAGS, …).IsRefundable(),IsBOPTradable(),IsWrapped()instead ofHasFlag(ITEM_FIELD_FLAGS, …).HasFlag(ItemFlag)/HasFlag2(ItemFlag2)/HasFlagCu(ItemFlagsCustom)instead of bitwiseFlags & ITEM_FLAG….ObjectGuid::ToString().c_str()instead ofObjectGuid::GetCounter().
CI also runs cppcheck.
- Logging:
LOG_INFO("category.sub", "msg with {}", arg)(alsoLOG_WARN,LOG_ERROR,LOG_DEBUG,LOG_TRACE). Categories are hierarchical, dot-separated (e.g.server.loading,entities.player,sql.dev). Noprintf-style; nosLog->; noTC_LOG_*. Macro insrc/common/Logging/Log.h. - Random: use project helpers from
src/common/Utilities/Random.h—urand,irand,frand,rand32,rand_chance,roll_chance_f,roll_chance_i. Do not usestd::randor<random>directly. - Strings:
Acore::StringFormat(fmt, args...)(wrapsfmt::format,{}placeholders) —src/common/Utilities/StringFormat.h. - Config: read options with
sConfigMgr->GetOption<T>("Name", default). - Namespace: project-wide is
Acore::(noTrinity::remnants — agents porting from upstream forks must rename). - Long-lived references: do not store a raw
Player*/Creature*/Unit*past the current call/tick — the object can be removed (logout, despawn, instance unload) and the pointer dangles. Store theObjectGuidand resolve at use time viaObjectAccessor::FindPlayer(guid),ObjectAccessor::GetCreature(*from, guid),Map::GetCreature(guid), etc. - DB queries: use
PreparedStatement(viaWorldDatabase/CharacterDatabase/LoginDatabaseand 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(...))(orWithCallbackfor non-prepared). Multi-statement writes wrap inSQLTransaction+Execute/AppendPreparedStatement. - Timed actions in AI: use
EventMap(event id → delay; simple) orTaskScheduler(lambdas, repeats, cancellation). Both are members ofCreatureAI; see any boss script undersrc/server/scripts/for examples — don't roll your own tick counters.
Scripts inherit from a ScriptObject subclass (SpellScript, AuraScript, CreatureScript, InstanceMapScript, GameObjectScript, CommandScript, …). Two registration styles coexist:
- Spell / aura scripts: use the
RegisterSpellScript(ClassName)(orRegisterSpellAndAuraScriptPair(...)) macro insideAddSC_<name>(). - Creature scripts: prefer
RegisterCreatureAI(ClassName)for new code; legacy zones still usenew ClassName();. Match the surrounding pattern.
Then declare and call AddSC_<name>() from the regional loader: Spells/spells_script_loader.cpp, EasternKingdoms/eastern_kingdoms_script_loader.cpp, etc.
SmartAI (data-driven creature behaviour) lives in the world DB's smart_scripts table — not in C++. Engine: src/server/game/AI/SmartScripts/. For new creature behaviour prefer SmartAI (added via the SQL update workflow); reach for CreatureScript only when SmartAI's event/action vocabulary isn't enough.
Module hooks (e.g. OnPlayerLogin, OnWorldUpdate, OnSpellCast) are declared in src/server/game/Scripting/ScriptDefines/*.h. Implement by inheriting the matching base (PlayerScript, WorldScript, …) and registering with new MyClass(); (or its RegisterXxxScript macro where one exists) inside AddSC_<name>(). Full hook list: https://www.azerothcore.org/wiki/hooks-script.
Custom (non-upstream) scripts go in src/server/scripts/Custom/ (gitignored).