It makes it easier for everyone to maintain and read the written code as well as it gives us more control over it.
It can also act as a safeguard to prevent errors in the code.
We only accept code that is written to the standards, this means that a PR you want to contribute with can be merged faster if you follow the standards from the beginning.
We never use tabs, instead, we use spaces.
One tab is equal to 4 spaces and that is what should be used throughout the whole project.
Visual Studio:
Tools -> Options -> Text Editor -> C/C++ -> Tabs -> Smart, 4, 4, Insert spaces.
Notepad++:
Settings -> Preferences -> Language -> Tab size: 4, Replace by space: checked
Always comment on code where it is not typical repeated code and where the code is not self-explanatory.
Avoid including hyperlinks in code comments, as they may become outdated or broken over time. If a link is relevant, include it in the pull request description instead鈥攊t can be referenced later via Git history.
Comments should either be placed directly above the code, or directly beside it.
// A Comment
if (a == b)
if (a == b)
{
a = b; // A Comment
Trailing whitespace is not allowed.
You should also not have unneeded spaces within a bracket.
Wrong:
if( var )
if ( var )
Correct:
if (var)
Brackets should be refrained from using for if statements that are followed by one line.
if (var)
{
me->DoA();
me->DoB();
}
else
me->DoC();
for (uint32 i = 0; i < loopEnd; ++i)
{
DoSomething();
DoSomethingElse();
}
uint32 i = 0;
while (i < 10)
{
DoSomething();
DoSomethingElse();
++i;
}
do
{
DoSomething();
DoSomethingElse();
++i;
} while (i > 0);
Please note that brackets should always start on a new line, as displayed in the example above.
Constants make the code easier to read and understand, they also provide a safeguard and prevent numbers from being hard-coded.
Wrong:
if (player->GetQuestStatus(10090) == 1)
me->RemoveFlag(58, 2);
Correct:
if (player->GetQuestStatus(QUEST_BEAT_UP) == QUEST_STATUS_INCOMPLETE)
me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE);
Constants are set with #defines, constexpr, or enum/enum class. If it does not exist - create one.
A default action should always be present in a switch statement, even if it is just a break.
switch (spells)
{
case SPELL_1:
case SPELL_2:
{
if (moreThanOneLine)
UseBrackets();
break;
}
case SPELL_3:
DoSomethinCool();
[[fallthrough]]
default:
break;
}
It is strongly advised to avoid using #define for constants. use either a const variable or an enum if multiple variables can be grouped together.
Enums must have a name. Separate constant on different enums depending on their type.
enum Spells
{
SPELL_1 = 1111,
SPELL_2 = 2222,
SPELL_3 = 3333
};
constexpr uint32 SPELL_4 = 4444;
Enum classes are preferred to be used as they can cause fewer surprises that could lead to bugs as the enum will not implicitly convert to other types like integers or other enums.
enum class Spell : uint32
{
One = 1111,
Two = 2222,
Three = 3333
}
All constants that we store have a standardized prefix.
| PREFIX | Comment |
|---|---|
| SPELL_ | Spell ID |
| NPC_ | creature_template.entry |
| ITEM_ | item_template.entry |
| GO_ | gameobject_template.entry |
| QUEST_ | quest_template.id |
| SAY_ | creature_text.GroupID |
| EMOTE_ | creature_text.GroupID Different prefix from SAY_ to show that this is an emote. |
| MODEL_ | Creature model, DisplayID |
| XX_G | Heroic mode prefix (goes after the other prefix) XX is the max man amount from mode. (OBSOLETE AS OF PATCH 3.2 WITH SpellDifficulty.dbc) |
| RAID_XX | Raid mode prefix (goes before the other prefix) XX is the max man amount from mode. (OBSOLETE AS OF PATCH 3.2 WITH SpellDifficulty.dbc) |
| EVENT_ | Event/Encounter identifier for instances |
| DATA_ | Identifiers in instance used for GUIDs/data not being event/encounter |
| ACHIEV_ | Achievement ID |
Correct:
SPELL_ENRAGE
SPELL_ENRAGE_H
EVENT_ILLIDAN
DATA_ILLIDAN
ACHIEVE_MAKE_IT_COUNT
Never use HUNGARIAN NOTATION in variable names!
for public/protected members or global variables:
uint64 SomeGuid;
uint32 ShadowBoltTimer;
uint8 ShadowBoltCount;
bool IsEnraged;
float HeightData;
for private members:
uint64 _someGuid;
uint32 _mapEntry;
uint8 _count;
bool _isDead;
float _heightData;
Methods are always UpperCamelCase and their parameters in lowerCamelCase
void DoSomething(uint32 someNumber)
{
uint32 someOtherNumber = 5;
}
Always use 'f' after float values when declaring them to avoid compile warnings.
float posX = 234.3456f;
Position const PosMobs[5] =
{
{-724.12f, -176.64f, 430.03f, 2.543f},
{-766.70f, -225.03f, 430.50f, 1.710f},
{-729.54f, -186.26f, 430.12f, 1.902f},
{-756.01f, -219.23f, 430.50f, 2.369f},
{-798.01f, -227.24f, 429.84f ,1.446f},
};
We define WorldObjects in this way:
GameObject* go;
Creature* creature;
Item* item;
Player* player;
Unit* unit;
We never use multiple declarations with pointers
Something* obj1, *obj2;
The proper way to do this is
Something* obj1;
Something* obj2;
References are defined in a similar way (& must be stuck to the type)
Creature& creature;
Never define "me" in a creature or object script!
'me' is the pointer to the scripted creature or object.
const keyword should always go after the type name
Player const* player; // player object is constant
Unit* const unit; // pointer to the unit is constant
SpellEntry const* const spell; // both spell and pointer to spell are constant
static keyword always should be put as first
static uint32 someVar = 5;
static float const otherVar = 1.0f;
All Header files should contain header guards
#ifndef MY_HEADER_H
#define MY_HEADER_H
// Header content here
#endif // MY_HEADER_H
Every header must be self-contained: it must compile on its own, without depending on other headers being included before it. This prevents hidden fragile dependencies. If A needs B and C, it should include both B and C. It shouldn't only include B just because B happens to include C right now. A should also not include anything it doesn't directly use.
Includes are written as a single block with no blank lines inside it, ordered as follows:
ItemEnchantmentMgr.cpp example:
#include "ItemEnchantmentMgr.h" // The file's own header, first
#include "DBCStores.h" // then project headers, alphabetically
#include "DatabaseEnv.h"
#include "Log.h"
#include "ObjectMgr.h"
#include "QueryResult.h"
#include "Timer.h"
#include "Util.h"
#include <cmath> // then library headers, alphabetically
#include <functional>
#include <vector>
Alphabetical ordering is case-sensitive (ASCII order): uppercase letters sort before lowercase. Above, DBCStores.h precedes DatabaseEnv.h because the uppercase B sorts before the lowercase a.
Project headers use quotes ("..."); library headers (C++ standard library, boost, etc.) use angle brackets (<...>). A third-party library bundled into the codebase, such as G3D, is the exception and uses quotes, but it still sorts with the library headers rather than with the project headers.
WaypointDefines.h example:
#include "Define.h" // project headers, alphabetically
#include "G3D/Vector3.h" // then library headers, alphabetically
#include <optional>
#include <vector>
Conditionally compiled includes are the exception to the single-block rule. Keep every unconditional include together in one sorted block, then place #if / #ifdef guarded includes after it, separated by a blank line. Do not break up the sorted block to keep a guarded include next to a related one.
Errors.cpp example (shortened):
#include "Errors.h"
#include "Duration.h"
#include <cstdio>
#include <cstdlib>
#include <thread>
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
#include <Windows.h>
#endif
Only guard an include when the header itself is platform or configuration specific. Never wrap an ordinary include in a guard to work around a build error elsewhere, and never leave out an include because some other header happens to pull it in under the current configuration, that is exactly the hidden dependency the self-contained rule prevents.
All C++ script/system/command text output must use acore_string whenever possible (e.g., ChatHandler messages, script system messages).
NPC dialog, emotes, and other in-game text that is managed via database text systems (such as creature_text, broadcast_text, etc.) should continue to use those systems instead of acore_string.
When adding new strings, localization must be provided for all available languages.
Tip: You may use AI to assist with translating localization strings.