Make sure that your editor is correctly reading the project's .editorconfig file at the root of the project.
This will automatically format and give warnings for code that needs fixing.
- Variables should be implicitly typed with
varwhere possible.
-
Components should be defined in Content.Shared as much as possible, with
RegisterComponent,NetworkedComponent,AutoGenerateComponentState, andAccessattributes.- Typing
nscompabove the component's class definition automatically fills these in for you.
- Typing
-
Fields on the component should have the
DataFieldandAutoNetworkedFieldattributes where relevant.DataFieldmakes the data be read from and written to YAML, andAutoNetworkedFieldsynchronizes changes over the network to the client.- Typing
nfieldabove a field automatically fills these in for you.
- Typing
-
DataFieldattributes should not include a custom field name, it will automatically use the field name's camel-cased. If the names don't match, they should be made to match instead of specifying the data filed name explicitly. For example:[DataField] public string Field;
Instead of:
[DataField("field")] public string Field;
Both work the same way, but the first is preferred in all cases.
- Systems should be defined in
Content.Sharedas much as possible.- A system should only be split up into client and server components if there are methods that otherwise can't be moved to shared, such as database methods.
- If you just need a system to handle UI code, create a new one in
Content.Client, but leave the one inContent.Sharedas sealed, this avoids having an empty system inContent.Server. For example, ThingSystem in Shared, and ThingUISystem in Client.
- Event subscriptions in
Initializeshould use the overload that uses anEntity<T>argument, not either of the two that useEntityUid. - Event subscriptions should use method names that start with "On".
- Large amounts of entities should not be queried every tick in an
Updatemethod. Event subscriptions and components that mark entities to be processed next update should be used instead. - Values used in system code should be able to be defined through a component's YAML, or at least a CVar defined in RMCCVars.
- Prototype IDs, if not defined in a component, should be stored in a static readonly
ProtoId<T>field, so that it can be validated by the YAML linter.
- Prototype IDs, if not defined in a component, should be stored in a static readonly
- Dependencies to other systems and managers should be listed at the top of the file, together and in alphabetical order. The field name of system dependencies should not include the word "System" in it.
- For example:
_damageableinstead of_damageableSystem.
- For example:
- Do not make a dependency for
IEntityManagerin systems, it already has one with the field nameEntMan. - Proxy methods should be used over
IEntityManagermethods, for example TryComp overEntMan.TryGetComponent. - Calls to
EntityLookupSystemmethods should use the overloads that allow you to pass in aHashSet, instead of creating and returning a new one. ThisHashSetshould be stored in aprivate readonlyfield on the system that calls the method.
- Events should be record structs where possible, with a
ByRefEventattribute added above the struct event definition. - Any events that need to inherit from other types need to be classes instead, such as
EntityEventArgssent over the network. HandledEntityEventArgsdoes not need to be inherited, a boolean fieldHandledshould be added to the record struct event instead.- This also applies to
CancellableEventArgs, adding a boolean fieldCancelledinstead.
- This also applies to
- Events should be readonly when no data on them is supposed to be changed by event handlers.
-
New prototype types (kinds) should not be created. Entity prototypes with components should be used instead. This automatically enables localization of names and descriptions, for example.
-
The prototype can be resolved by ID through
IPrototypeManager, and the component on the prototype obtained throughTryCompon theEntityPrototype, passing in an instance ofIComponentFactory.- The data on component instances tied to an
EntityPrototypeshould never be changed in code.
- The data on component instances tied to an
-
Singleton entities can also be created for these entities, and events can be raised on them (see surgery code for an example).
-
Prototype data should never be changed in code, as those changes are not synced between client and server, and will persist between round restarts.
- The exception to this is prototype hot reloading, which updates prototypes when the
loadprototypeadmin command is used, or when a YAML file is changed in local development.
- The exception to this is prototype hot reloading, which updates prototypes when the
-
New fields in upstream prototypes should be added in a new partial class defined under an _RMC14 folder. To do this, change the namespace of the file to be equal to that of the upstream file, and suppress the warning that the namespace is incorrect. Example below:
// ReSharper disable CheckNamespace namespace Content.Shared.Humanoid.Markings; public sealed partial class MarkingPrototype { [DataField] public readonly Vector2 Offset; }
- Upstream tables shouldn't be modified, new tables should be created instead, with a foreign key to the upstream table that it associates with. For example, to add relational data to the Player table:
[Table("rmc_player_data")]
[PrimaryKey(nameof(PlayerId), nameof(SomeId))]
public sealed class RMCPlayerData
{
[Key]
[ForeignKey("Player")]
public Guid PlayerId { get; set; }
public Player Player { get; set; } = null!;
[Key]
public int SomeId { get; set; } = null!;
}- Primary keys should be defined in the order in which they will be queried. For example, in the code above, lookups for
PlayerIdorPlayerId + SomeIdwill be fast, but justSomeIdwill be slow.- If you need to do lookups in a different order, an index for them should be added separately.
- UI elements should be defined and created through
.xamlfiles as much as possible, not in C# code. - Bound user interface states should not be used, add the data to components instead. BUI code can access any component on the Owner entity (the one that the UI was opened on) directly.
- If you need to refresh the UI when the component's data is changed, handle the
AfterAutoHandleStateEventthat is raised directed at that component.- To do this, make sure that the
AutoGenerateComponentStatehasraiseAfterAutoHandleStateset to true (AutoGenerateComponentState(true)). - Ensure that you wrap the contents of the method handling the event in a try-catch block that logs the error, as any errors thrown within it will cause the client's screen to flash and refresh repeatedly otherwise. Errors in UI code are common.
- To do this, make sure that the
- If you need to refresh the UI when the component's data is changed, handle the
- Most business code should be on the BUI, while UI elements (
.xaml.csfiles) should only contain methods that help with modifying the appearance of the element.
- New localization IDs should be defined under the _RMC14 directory, not in existing upstream
.ftlfiles. - You are not required to localize your code for RMC14, but you may if you want to.
- You are free to make a PR localizing existing RMC14 code, however be aware that it may not be reviewed over other pending PRs until time allows, specially if the PR is very big, as the game is still in active development. Make sure to test that your changes work properly in-game.
-
New code and yml should be put in files inside
_RMC14folders as much as possible, using events and making new ones where needed to make this possible.- For example, if you wanted to change how zombies work, you should make an
RMCZombieSystemthat handles theEntityZombifiedEventevent, running your code there, instead of modifying the upstreamZombifyEntitymethod.
- For example, if you wanted to change how zombies work, you should make an
-
Where this is not possible, the code should have
// RMC14markers above and below every block of code that was changed. For example:// RMC14 your new code here new code line 2 new line 3 // RMC14
-
Changes to the upstream code should call RMC-specific methods wherever possible to minimize the number of lines that need to be changed. This makes merge conflicts easier to process in the future.
- TODOs should be marked with
// TODO RMC14, not just// TODO, which allows us to later see TODOs that are specific to RMC14.
- Upstream files and file contents should not be removed unless completely necessary.
- For example, if we don't want access to a specific weapon, that weapon should be commented out from any sources that give you access to it, instead of deleting the weapon's YAML or the file that it is in.