Contributing code style - rebaserHEAD/NexusForever GitHub Wiki

reference, committed verified 2026-10-14 game_rework f9bb26ef

How upstream's C# looks, from the file down to the line. Each rule names its source: written on upstream's code style page, editor from Source/NexusForever.sln.DotSettings, review when reviewers ask for it, or habit when nearly all of the code does it without anything saying so. Which source wins when they disagree is on Contributing.

Files

The file itself

Rule Source Note
CRLF line endings written .gitattributes enforces it for *.cs
A newline at the end written, editor
British English in identifiers, comments and commit messages written, review Initialise, Customisation, Neighbour. Names taken from the client's tables or from .NET keep their own spelling: DyeColorRamp, Serialize
UTF-8, usually with a byte-order mark habit
Four spaces, no tabs habit
One top-level type per file, named after it habit Nested types are the exception, below
About 120 characters a line habit A soft limit; nothing enforces it

Namespaces and usings

Rule Source Note
Block-scoped namespaces habit A handful of files in the newer services are file-scoped
The namespace is the project plus the folder path habit, review Folder names are singular
using directives outside the namespace, in one block with no blank lines habit Then one blank line, then namespace
System first, then alphabetical habit Aliases (using X = ...;) go last. No global using, no using static
Implicit usings aren't repeated habit Most projects switch ImplicitUsings on and leave out using System; and friends. The projects that don't write them out
No unused usings review

Types and members

Declarations

Rule Source Note
Explicit access modifiers, private included habit
sealed only on Singleton<T> managers habit Other classes are left open
Member order: constants and static fields, instance fields (readonly first), properties, constructors, interface members, other members, nested types editor The usual drift is a backing field placed straight after its property
No this. qualifier editor Except where a parameter shares the field's name, as in a constructor: this.log = log;
A constructor initializer on its own line editor : base(movementManager) under the parameter list
Attributes on their own line, on parameters too in practice written, editor The written rule allows a parameter attribute inline, but every [Parameter] on a command sits on its own line

Nested types

Three kinds of type are nested, and little else:

Nested type Where it goes
The save mask An enum at the top of the class whose changes it tracks
Message sub-structures Inside the message model that carries them
Command subcategories Inside the command category

The dependency injection region

A class with injected services wraps its fields and constructor in a region. The parameters go one per line, even when there's only one, and the assignments are aligned. From ClientChatHandler:

#region Dependency Injection

private readonly ILogger<ClientChatHandler> log;
private readonly IGlobalChatManager globalChatManager;
private readonly ICommandManager commandManager;

public ClientChatHandler(
    ILogger<ClientChatHandler> log,
    IGlobalChatManager globalChatManager,
    ICommandManager commandManager)
{
    this.log               = log;
    this.globalChatManager = globalChatManager;
    this.commandManager    = commandManager;
}

#endregion

A constructor that takes an owner object instead of services, such as a manager built with (IPlayer owner, CharacterModel model), has no region. Regions aren't used for anything else.

Locals and expressions

Rule Source Note
Explicit types, with var only for new habit The editor setting asks for explicit types outside built-ins. The code goes slightly further: var x = new Thing() is common, and everything else is typed, foreach and out included
Block-bodied methods, one-liners included habit Expression bodies (=>) are for properties and accessors
== null and != null habit is null is rare upstream
$"..." interpolation habit Never string.Format
Collection expressions for new collections habit, review List<uint> Ids { get; } = [];
switch statements more than switch expressions habit
LINQ at load time, loops on the tick habit Index building at start-up uses LINQ; Update bodies almost never do
The Async suffix on new asynchronous methods habit

Formatting

Braces

Rule Source
Allman braces written
No braces around a single-line body of if, else, for or foreach written, editor
In an if/else, only a branch spanning several lines takes braces written
An empty method keeps its braces, on separate lines written
An auto-property keeps { get; set; } on one line; an accessor with a body puts its braces on separate lines written

Spacing and alignment

Rule Source Note
A blank line between members habit Consecutive auto-properties sit together with none
A blank line after a one-line if (...) return; habit
= aligned in a run of assignments habit Object initializers, constructor assignments, enum members. Message Read bodies are the exception
A wrapped call breaks after (, one argument per line habit
A fluent chain breaks before the . habit

Naming

Identifiers

Thing Style Source Example
Types, namespaces, methods, properties, public fields PascalCase written
Interfaces I and PascalCase written IGuildManager
Locals and parameters lowerCamel written
Private and protected fields, world side lowerCamel, no prefix written, editor log, entityFactory
Private and protected fields, broker services _ and lowerCamel habit The Server.* and API.* projects, and the chat, group, friendship and query database projects
The logger field log habit _log in the projects above
The IServiceCollection parameter sc habit
Ids unsigned habit uint for most, ulong for character ids and guids, ushort for realm ids

Names from the client

Where the client has a name for something, in its tables or its UI scripts, reviewers ask for that name over an invented one. Message and handler names put the area first and stay consistent across a family: ServerPetClearFlair, not ServerClearPetFlair. Names for the rest of the server's moving parts are on Contributing code patterns.

Comments

Documentation comments

Rule Source Note
A /// <summary> on every public method written The code drifts most on message Read and Write, handlers' HandleMessage and constructors. Write one anyway
The three-line form, a sentence with a full stop habit
A verb first habit "Returns ...", "Initialise ...", and "Invoked when ..." for On hooks
<see cref="..."/> for the types a summary names habit
The same summary on the interface and the class habit The text is copied; <inheritdoc/> isn't used
<param>, <returns>, <exception> written Optional

Update methods carry a stock summary, copied from IUpdate: "Invoked each world tick with the delta since the previous tick occurred."

Line comments

Rule Source Note
// with a space, no closing full stop habit
// TODO: for a known gap habit FIXME and HACK are almost never used
Evidence goes beside what it explains habit In message models: what a capture showed, or which table a value comes from

Literals, enums and logging

Literals

Rule Source Example
Lower-case suffixes habit 0u, 1ul, 0.5f
Upper-case hex digits habit 0x1A, never 0x1a
Bit widths as unsigned literals habit writer.Write(Result, 8u)

Enums

Rule Source Note
PascalCase names and values written
Values ordered, = aligned written A list kept in another order says so in a comment
No comma after the last value written
Explicit values, in hex for flags habit Flags are usually padded: 0x0001
The default int base habit : uint only when a value needs bit 31
In Game.Static, one per file habit Under a folder for its area

Logging

Rule Source Note
ILogger<T> injected in new code habit Older classes hold NLog's LogManager.GetCurrentClassLogger() in a static field
Interpolated messages on the world side habit Structured templates ({Name}) in the broker services and scripts
Trace for per-entity events, Info for start-up and load counts, Warn for unexpected data, Error for exceptions habit Debug is barely used

Exceptions

Situation What's thrown
A client sent a value it couldn't have InvalidPacketValueException, with no message. The session logs it and disconnects the client
A saved row doesn't match the game tables DatabaseDataException
A caller broke a contract InvalidOperationException, ArgumentException, ArgumentOutOfRangeException

A message, when there is one, is a sentence with a full stop. catch is rare, and catches Exception by name.

Known edges

Places where the code doesn't follow its own written rules. Don't copy them:

  • Many files in Game.Abstract have no newline at the end.
  • Some enums still carry a comma after the last value.
  • About a third of the files under Network.World/Message/Model/AccountInventory leave .Model out of their namespace.
  • A few files use tabs.

Where the code lives

Piece Path
Editor settings Source/NexusForever.sln.DotSettings
Line endings .gitattributes
The dependency injection region, by example Source/NexusForever.WorldServer/Network/Message/Handler/Chat/ClientChatHandler.cs
The stock Update summary Source/NexusForever.Shared/IUpdate.cs
Upstream's written rules code style
⚠️ **GitHub.com Fallback** ⚠️