Scripting: Conventions – Arma Reforger

From Bohemia Interactive Community
Revision as of 21:01, 17 May 2022 by Lou Montana (talk | contribs) (1 revision imported)
Jump to navigation Jump to search
Format reminder:
  • camelCase is writing by gluing words together and making their first letter capital, but for the first one, e.g namingLikeThis.
  • PascalCase is writing in camelCase but with the first letter set uppercase, e.g NamingLikeThis.
  • snake casing (naming_like_this) is not a convention used in Enfusion.
    Only const values should be NAMED_LIKE_THIS.


Tag

Bohemia Interactive scripts are prefixed with SCR_ as internal convention - it is recommended that each developer entity has its own prefix. See the Tag page for more information.


File/Class

  • File should be called TAG_MyObject.c
    • A component must end with "component": TAG_ExampleComponent.c
    • An entity must end with "entity': TAG_ExampleEntity.c
  • Class should be called the same (without file extension):
    • class TAG_MyObject
    • class TAG_ExampleComponent
    • class TAG_ExampleEntity
  • Enum:
    • name must be prefixed with a capital E, e.g EMyEnum
    • values use all capital letters with words separated by underscores, e.g EMyEnum.VALUE_1
  • The file should be located in the scripts\Game directory
  • The use of plural is prohibited at the end of combined keywords: e.g SCR_NotificationsComponentSCR_NotificationComponent


Method

  • Functions/Methods naming uses PascalCase, e.g:
    int ReturnNumber()
    
  • Parameters are named with camelCase, e.g:
    int ReturnNumber(bool canBeNegative)
    


Variable

{{Feature|informative|See ]] for more information.}

  • Member variables are prefixed with m_, eventually one-letter type prefix, and use PascalCase, e.g:
    Entity m_Entity;
    int m_iHealth;
    bool m_bIsEnemy;
    
  • Global variables are prefixed with g_.
    Global variables are bad practice and must not be used beside the bare minimum!
  • Static variables are prefixed with s_ (constants are not), eventually a one-letter type prefix, and use PascalCase, e.g
    protected static int s_iUnitsCount;
    
  • Local variables and arguments (method parameters) use camelCase, e.g:
    void MethodA()
    {
    	int value = 42;
    	string name = "John";
    
    	float result = MethodB(value, name);
    };
    
  • Constant values use all capital letters with words separated by underscores (uppercase snake casing), e.g:
    const int MAX_VALUE = 9999;		// no 'i' type prefix
    static const int TOTAL = 10;	// a constant does not need
    


Script

General

  • Curly braces must always be on a new line - Enforce Script uses Allman style
  • Variables and functions should be protected whenever possible (respecting OOP black box principle) unless they are intended to be exposed
  • Getters/Setters: variables should be made protected and accessed through getters and setters (entry methods getting/setting the value)
class SCR_HumanComponent : ScriptComponent
{
	private int m_iAge;

	void SetAge(int age)
	{
		m_iAge = age;
		PrintFormat("Age of instance %1 is now %2", this, m_iAge);
	}

	int GetAge()
	{
		return m_iAge;
	}
}

Spacing

  • Tabs are used for indentation - they are set to a size of 4 spaces in Script Editor
  • A space is used before and after:
    • a binary operator
    • a foreach colon
    • a class' inheritance colon
  • A space is used after:
    • if, for, foreach, switch, while keywords
    • a for semicolon
  • Spaces are used inside parentheses but not around their content
class SCR_HumanComponent : ScriptComponent
{
	if (true)
	{
	}

	for (int i = 0; i < 10; i++)
	{
	}

	foreach (string item : stringArray)
	{
	}

	switch (value)
	{
		case 42:
			break;
	}

	while (true)
	{
	}
}

Method

  • All methods must be separated using this sequence of characters: two dashes followed by 96 dashes (see Example)
    //------------------------------------------------------------------------------------------------
    
  • Documentation must be done with Doxygen support in mind, using the //! syntax (see Example)
  • Methods should be sorted in the following order (top to bottom):
    • General methods
    • EOnFrame
    • EOnInit
    • Constructor
    • Destructor
 //! A scripted entity
class SCR_ScriptedEntity : GenericEntity
{
	//------------------------------------------------------------------------------------------------
	//! Get the normalized direction vector at position A pointing to B
	//! \param vectorA First position, direction origin
	//! \param vectorB Second position, direction goal
	//! \return The direction from A to B as a normalized vector
	private vector GetNormalizedDirection(vector vectorA, vector vectorB)
	{
		vector dir = vectorB - vectorA;
		return dir.Normalized();
	}

	//------------------------------------------------------------------------------------------------
	//! Frame
	override void EOnFrame(IEntity owner, float timeSlice)
	{
		vector direction = GetNormalizedDirection(owner.GetOrigin(), vector.Zero);
		Print("OnFrame was called! Direction: " + direction);
	}

	//------------------------------------------------------------------------------------------------
	//! Init
	override void EOnInit(IEntity owner)
	{
		Print("Init was called!");
	}

	//------------------------------------------------------------------------------------------------
	//! Constructor
	void SCR_ScriptedEntity(IEntitySource src, IEntity parent)
	{
		SetEventMask(EntityEvent.INIT | EntityEvent.FRAME | EntityEvent.CONTACT);
		SetFlags(EntityFlags.ACTIVE, true);
	}

	//------------------------------------------------------------------------------------------------
	//! Destructor
	void ~SCR_ScriptedEntity()
	{
		Print("Destructing SCR_ScriptedEntity");
	}
}

Miscellaneous

  • class instanciation with the new keyword must use parentheses:
    SCR_Class myClass = new SCR_Class();			// correct
    SCR_Class myClass = new SCR_Class;				// wrong
    
  • same with arrays:
    array<string> myArray = new array<string>();	// correct
    array<string> myArray = new array<string>;
    


Modability

Pay special attention here if the script you are writing should be friendly to modding.

Modded classes work very similar to inherited ones, and so the following things are not moddable:

  • Constructor/Destructor: Modded class has its own const/destructor and cant modify parent one → you can have the constructor call another method like InitClass()
  • Private variables & methods: Modded class like inherited one cannot override private members of the parent → use protected keyword instead of private
  • Static variables & methods: Same as above → do not use unless absolutely necessary
  • Global methods: No class to mod → do not use unless absolutely necessary.


Example

[EditorAttribute("box", "GameScripted/SomeFolder", "Description of this component", "-0.25 -0.25 -0.25", "0.25 0.25 0.25", "255 0 0 255", "0 0 0 0", true, true, true)]
class SCR_SomeComponentClass
{
}

SCR_SomeComponentClass SCR_SomeComponentSource;

//! Flags used for an entity to define its currently active components.
enum SomeFlags
{
	MESH = 1,
	BODY = 2,
	HIERARCHY = 4
	NET = 8
}

//! A brief explanation of what this component does.
//! The explanation can be spread across multiple lines.
//! This should help with quickly understanding the script's purpose.
class SCR_SomeComponent : ScriptComponent
{
	//! Defines the minimum distance (in metres) for this object to render. If below this value, object will be culled.
	const float RENDER_DISTANCE_MINIMUM = 10;

	//! Defines the maximum distance (in metres) for this object to render. If above this value, object will be culled.
	const float RENDER_DISTANCE_MAXIMUM = 100;

	//! Defines the maximum distance at which this object will be rendered in metres.
	[Attribute("30.0", UIWidgets.Slider, "The maximum distance at which this object will be rendered in metres.", "0 120 0.1")]
	private float m_fRenderDistance;
	//! Maximum count of children that can be spawned at any time. If the limit is exceeded no more children are spawned.
	[Attribute("100.0", UIWidgets.EditBox, "Maximum count of children that can be spawned at any time. If the limit is exceeded no more children are spawned.", "0 500 1")]
	private int m_iMaximumChildCount;
	//! The offset of this object in metres.
	private vector m_vPositionOffset = "0 0 0";

	//! A public variable
	float m_fSomethingPublic = 3.2;

	//! A public vector
	vector m_vOtherPublic = "1 2 3";

	//------------------------------------------------------------------------------------------------
	//! Returns the render distance of this object (metres).
	float GetRenderDistance()
	{
		return m_fRenderDistance;
	}

	//------------------------------------------------------------------------------------------------
	//! Set the render distance of this object.
	//! \param renderDistance Distance in metres. Is clamped between RENDER_DISTANCE_MINIMUM and RENDER_DISTANCE_MAXIMUM.
	void SetRenderDistance(float renderDistance)
	{
		m_fRenderDistance = Math.Clamp(renderDistance, RENDER_DISTANCE_MINIMUM, RENDER_DISTANCE_MAXIMUM);
	}

	//------------------------------------------------------------------------------------------------
	//! Prints hello to the debug console. Private function, not exposed to outside classes.
	private void SayHello()
	{
		string localString = "Hello!";
		Print(localString);
	}

	//------------------------------------------------------------------------------------------------
	//! Compare two integers, return the larger one. (Don't mind the silly documentation, mind the syntax)
	//! \param a First parameter to be compared with the second one
	//! \param b Second parameter to be compared with the first one
	//! \return Returns true if a is equal to b, false otherwise.
	bool IsEqual(int a, int b)
	{
		// can be simplified to "return a == b;"
		if (a == b)
			return true;
		else
			return false;
	}

	//------------------------------------------------------------------------------------------------
	override void EOnInit(IEntity owner)
	{
		Print("Initialized some component!");
	}

	//------------------------------------------------------------------------------------------------
	void SCR_SomeComponent(IEntityComponentSource src, IEntity ent, IEntity parent)
	{
		ent.SetEventMask(EntityEvent.INIT);

		// If offset is 0, no need to update
		if (m_vPositionOffset != "0 0 0")
		{
			// Get current transformation matrix, add the position offset and update transformation.
			vector mat[4];
			ent.GetTransform(mat);
			mat[3] = mat[3] + m_vPositionOffset;
			ent.SetTransform(mat);
		}
	}

	//------------------------------------------------------------------------------------------------
	void ~SCR_SomeComponent()
	{
	}
};