Go back to CONTRIBUTING
This guide is for any project developed by us. It is a result of trial and error, combined with ideas borrowed from some other languages. You might find a lot of code in our repos that do not follow this style, this is because this document was formulated after a lot of projects were finished and its specification differs greatly from them. As of 26.02.2023 none of the projects follow these guidelines.
However all new code from now on should conform to them. Old code, ideally, should also be updated to use them, but that might take awhile due to the lack of the specialized tooling for that.
- Names of classes, methods, enumerations, public fields:
PascalCase; - Constants' names:
UPPER_SNAKE_CASE; - Filenames and directory names are
PascalCase, e.g.MyFile.uc; - Names of local variables, parameters:
camelCase; - Names of private, protected, internal and protected internal fields and properties:
_camelCase; - Naming convention is unaffected by variable/return type or modifiers such as
const,static,out, etc; none,default,super,false/trueand all other keywords should be in lower case;- For casing, a “word” is anything written without internal spaces, including acronyms.
For example,
MyJsoninstead of;MyJSON - Except for cycle counters such as
i,jandk, always use meaningful variable names - there is no need to be stingy with characters. But do try not to make names longer than 20 characters (soft limit, can be broken if necessary).
Modifiers (for a single function/variable) occur in the following order:
public, protected, private, final, static, localized, simulated, exec, function;
Class member ordering:
- Group class members in the following order:
structs,enums anddelegates;staticandconstfields;- Variable declarations;
- Replication block;
- If applicable, methods that can at least in some sense be thought of as constructors and finalizers for the instances of the class.
- Methods.
- Within each group, elements should be in the following order:
- Public.
- Protected.
- Private.
Developed from Google Java style.
- No trailing whitespaces are allowed;
- A maximum of one statement per line;
- Indentation of 4 spaces, no tabs;
- Column limit: 100;
- One empty line between functions;
- No line break before opening brace, has to have a break after (even for empty blocks);
- No line break between closing brace and
else; - Braces used even when optional;
defaultproperiesshould be formatted like all other blocks and appear in every file, even if it is empty;- In the definitions of
defaultpropertiesblock=sign in variable assignment should be surrounded by white spaces. Several whitespaces to the left of=are allowed to group several assignments at the same ident level; Note that when assigning a default value of a struct, there's no spaces allowed inside value's definition:def = (a="Hello, world!",b=false); - In
replicationblock condition should be indented by 4 spaces, then all the replicated variables/functions should be indented by 4 more spaces. In case of having to specify them on several lines, no additional indentation is necessary. Functions and variables should be grouped separately. - In
switchblockscase <label>:must be indented by 4 whitespaces all following instructions by 4 more whitespaces (to 8 total, relative to theswitchdefinition); - Space after
if/for/whileetc., and after commas; - Inside
forcycle's declaration put space after each semicolon; - No space after an opening parenthesis or before a closing parenthesis;
- No space between a unary operator and its operand. One space between the operator and each operand of all other operators;
- In case class has any modifiers (
abstract,config, etc.), each modifier has to be specified on its own line in the following order (with specified casing):native,abstract,within,transient,config,perObjectConfig,dependsOn. Semicolon must appear after the last modifier without any space or line break; - Names of enumeration variants should start with common sequence of upper-case characters
(if possible, abbreviation of the enumeration's name, e.g. for
JustCoolEnumit should beJCE), then underscore, thenCamelCasename (e.g.JCE_SubType1,JCE_SubType2); - Each enumeration variant should appear on its own line;
- Line wrapping rules:
- In general, line continuations are indented 4 additional spaces;
- For function definitions and calls, if the arguments do not all fit on one line they should be broken up onto multiple lines, with each argument on its own line, indented by 4 spaces, closing brace should not be indented; In that case, there should be line breaks after opening parenthesis and before closing one. The code example below illustrates this.
- Any function's body can consist of three different parts that have to be separated by line breaks:
localvariable definitions;- Set of guard checks -
ifblock with a singlereturninstruction. These guard checks have special syntax -returninstruction shouldn't be surrounded by braces, but all of thereturninstructions should be aligned on the same vertical level (equal to the minimal possible multiple of 4). They can also be interspersed with assignment instruction to variables, as long as resulting values of said variables are immediately tested in the nextifcheck; - The body of function with the main logic.
class MyCoolClass
abstract
config(MyCoolConfig);
struct MyStruct {
var public int openField;
var private string closedField;
};
enum JustCoolEnum {
JCE_SubType1,
JCE_SubType2,
JCE_WhateverThisIs
};
var protected int globalValue;
var protected localized string message;
replication {
reliable if (role < ROLE_Authority)
globalValue;
// Functions are grouped separately
reliable if (role < ROLE_Authority)
LongEnoughFunctionNameSoThatArgumentsDoNotFitIntoTheLine;
}
public function SimpleFunction(string arg1, Object arg2) {
return;
}
protected final simulated function bool LongEnoughFunctionNameSoThatArgumentsDoNotFitIntoTheLine(
int argument1,
int argument2,
int argument3
) {
local bool booleanVariable;
local int anotherVariable;
if (argument1 < 0) return false;
booleanVariable = (argument2 > 13);
if (booleanVariable) return true;
if (argument3 == 7) return true;
booleanVariable = (argument2 > 13);
switch (argument2) {
case 5:
argument2 += 7;
break;
case 9:
argument3 = 0;
break;
default:
argument2 -= 3;
}
if (argument1 > 10) {
return (argument1 + argument2 * argument3) > 12;
} else {
anotherVariable = argument1 * argument2 - argument3;
if (booleanVariable) {
return anotherVariable;
} else {
return anotherVariable * 10;
}
}
}
defaultproperties {
globalValue = 29
message = "Just a message"
}- Variables and fields that can be made
constshould always be madeconst; - Prefer named constants to magic numbers.
- Try not to make functions with more than 30 lines in them
(only lines with actual instructions and
localvariable declarations count; not empty lines, comments or lines with single parenthesis/braces); - Prefer nesting code blocks at most 2 times, avoid nesting more than 3 blocks.
function MyFunc() {
local int i;
if (/*...*/) {
for (i = 0; i < 100; i += 1) {
// Avoid nesting anything else inside this `if` block
if (i == 5) {
Log("`i` is equal to 5! Huzzah!");
} else {
Log("What a sad world we live in... without fives at all...");
}
}
} else {
/*...*/
}
}If your function is complex enough that these conditions cannot be satisfied - consider moving some of the logic into auxiliary functions.
Always check Object parameters for being none.
This check can only be dropped in internal methods that will only be called by other methods
within the same class and can guarantee that passed argument isn't none
(for example if they've already performed the check).
Note Execution of any code that doesn't come from your own code package might destroy
Actorobjects, so checks on them might need to be redone. If you have a reason to believe they won't - add a comment with an explanation why.
-
If function returns a value - it should be assigned to a variable, especially if that value is an
Objectorstruct. Not following this rule might cause rare, but hard to debug bugs; -
If you call a function with an
outparameter - explicitly mark such parameter with commentedout:function MyFunction(out int arg) { /*...*/ } MyFunction(/*out*/ value);
Avoid -- or ++ operators and use += and -= instead.
- Content of the files should be encoded in any one-byte encoding compatible with ASCII,
while only using ASCII characters.
In rare circumstances (when one needs to define
stringwith non-ASCII characters) one can use UTF-16 BE; - Top level structure of the project should consist of:
configsdirectory with default configuration files for your project;classesorsources(but not both) for directory with source code;docsfor any documentation you've decided to package along with your project. Their names should be in lower case.
- Large enough projects should further group their code files into sub-directories, which is possible when using our tool for compilation.
All following instructions are not simple preference, but vital requirements to avoid bugs or flaws with UnrealScript implementation:
- Never store
Actorinstances in non-Actorobjects; - Make sure to clean up any
Objectreferences fromdefaultvalues before level change; - Do not change dynamic array's
lengthwith--,++,+=or-=and use explicit assignment:array.length = array.length + 1; - Do not use
%- it works onfloatargument, making it ill-suitable for integer arithmetic; - Do not use recursion.
Three slashes /// denote documentation comment - a comment that can potentially be used to
generate external reference documentation.
It should come as a sequence of ///-prefixed comments right before field/struct/enum/function's
declaration without any empty lines in between.
Class comments are special and should come right after its declaration in the sequence of
line comments that start with //!.
Somewhat unrelated, but copyright and license info should be specified in the
/** */ block comment, where each line starts with *.
Each line in that block should start with declaring what is being specified, followed by the colon
with a whitespace and then corresponding value:
/**
* Author: some guy
* Home repo: https://go.fuck.yourself/
* License: GPL
* Copyright 2022-2023 Some guy
*------------------------------------------------------------------------------
* Optional license info: GPL, MIT, whatever.
*/
class MyClass;
//! Documenting class: first explanatory sentence.
//!
//! This is and extensive class doc!
/// This is a struct: first explanatory sentence.
///
/// Here is what it does!
struct MyStruct {
public int field;
};
/// Also documentation for the function: first explanatory sentence.
///
/// It should describe what it does and what is the meaning of its arguments and return value.
public function float MyFunc(int arg1, bool arg2) {
/*...*/
}In API documentation, the first line should be a single-line short sentence providing a summary of the code and should be kept short.
The summary line should be written in third person singular present indicative form. Basically, this means write ‘Returns’ instead of ‘Return’.
It should be separated from the rest of the documentation by an empty comment line:
simply /// or //!.
Rest of documentation should describe the method as a whole, along with the meaning of
its parameters and return value.
This is in contrast to javadoc, where they have had their own descriptions specified via
@param or @return, which in a lot of cases had to simply duplicate the rest of the description.
Within documentation comments, use Markdown to format your documentation.
Use top level headings (#) to indicate sections within your comment.
Common headings:
Examples- for examples to illustrate usage. They should not attempt to explain basic concepts (or how to achieve common tasks), but simply how to use the method.Errors- for describing in which cases function can log an error.
Even if you only include one example, use the plural form: ‘Examples’ rather than ‘Example’.
Use backticks (`) to denote a code fragment within a sentence. Use triple backticks (```) to write longer examples. You don't need to specify UnrealScript as a language for triple backticks.
To refer to a type, field or enum variant use combination of square brackets and backticks:
/// The [`String`] passed in lorum ipsum...
/// To refer for a function use [`MyClass::MyFunction`].