# The Slarti guide (WIP)
## Pairing uids and Components
- `EntityUid`
Each entity spawned in the game has its own unique identifier, or uid for short. This is basically just an integer. Unique means that if an entity gets deleted you can be sure no other new entity will get the same uid (unless you restart the server).
- Components
The building block used for composing entities. One entity can have many different components, but only one of each type. Components have datafields that store information about the entity.
See our [ECS guide](https://docs.spacestation14.com/en/robust-toolbox/ecs.html) for more details.
- `Entity<SomeComponent>`
This is simply a pair of an `EntityUid` and a component of type `SomeComponent` that belongs to that entity. We prefer to pair them up like this for better readability.
To create one, you can either use the constructor
`var ent = new Entity<ClumsyComponent>(uid, clumsyComp);`
or simply pass them as a tuple into a function, where it will automatically be cast into an `Entity<SomeComponent>`:
```
public void SomeFunction(Entity<ClumsyComponent> ent)
{
// do stuff
}
SomeFunction((uid, clumsyComp));
```
- If you want to go the other direction and want to get the uid and component from your `Entity<ClumsyComponent> ent` then you can use
```
var uid = ent.Owner;
var comp = ent.Comp;
```
or
`var (uid, comp) = ent;` to deconstruct it.
:::danger
:warning: Common Mistake:
What you will see in a lot of old files is passing both the uid and component as separate parameters into functions.
```
public void MyFunction(EntityUid uid, ClumsyComponent comp)
{
// do stuff
}
```
Our code convention for that has changed over time and if you write anything new, always pair them up if they belong together:
```
public void MyFunction(Entity<ClumsyComponent> ent)
{
// do stuff
}
```
Functionally these two are identical.
:::
:::danger
:warning: Common Mistake:
Don't confuse `ent.Owner` with `comp.Owner`! The first one gets the `EntityUid` from an `Entity<SomeComponent>`, whereas the latter gets the `EntityUid` from a component.
`comp.Owner` is deprectated and should not be used under any circumstances!
Always pair up the uid along with the component instead.
:::
- You can combine up to four components with an uid to get an
`Entity<Component1, Component2, Component3, Component4>`.
- The component can also be nullable and you get a `Entity<SomeComponent?>`, which is not to be confused with a `Entity<SomeComponent>?` (here the whole object is nullable, rather than only the component).
- If you ever need to convert an `Entity<SomeComponent>` into an `Entity<SomeComponent?>` you can use the `ent.AsNullable()` method. This might be needed to pass it into some API functions as the correct datatype.
## Shorthands and other important functions
These are some commonly used functions available in all EntitySystems without needing an extra namespace import or dependency. Always use the shorthand when possible for better readability. If you are not inside an EntitySystem you will need to use the full function name. This list only contains the most common ones. You can look at the rest in [EntitySystem.Proxy.cs](https://github.com/space-wizards/RobustToolbox/blob/master/Robust.Shared/GameObjects/EntitySystem.Proxy.cs).
- `HasComp<SomeComponent>(uid)`
The same as `EntityManager.HasComponent<SomeComponent>(uid)`.
Used to check if a given entity uid has a certain component. Returns `true` or `false`.
- `Comp<SomeComponent>(uid)`
The same as `EntityManager.GetComponent<SomeComponent>(uid)`.
Gets a certain component from a given entity uid. Will throw an error if it does not exist.
Example:
`var clumsyComp = EntityManager.GetComponent<ClumsyComponent>(uid)`
The variable `clumsyComp` is of type `ClumsyComponent`.
- `CompOrNull<SomeComponent>(uid)`
The same as `EntityManager.GetComponent<SomeComponent>(uid)`.
Gets a certain component from a given entity uid. Will return `null` if it does not exist.
Example:
`var clumsyComp = EntityManager.GetComponentOrNull<ClumsyComponent>(uid)`
The variable clumsyComp is of type `ClumsyComponent?` (a nullable).
Since `Trycomp` checks if the component exists and returns that at the same time, you see that used more often, for example for guard statements.
- `TryComp<SomeComponent>(uid, out var comp)`
The same as `EntityManager.TryGetComponent<SomeComponent>(uid)`.
Used to get a component of a certain type for a given entity uid and assign it to a new variable `comp`. `comp` is of type `SomeComponent?`, a so-called nullable which can either have a value, or be `null`. To get a non-nullable variable of type `SomeComponent` from it you can use `comp.Value`, but make sure `comp` is not null before or you will get an error.
If you don't need the `comp` variable, then use `HasComp` instead.
A common code pattern you see is using `TryComp` as a guard statement.
Example:
```
public void MyFunction(EntityUid uid)
{
if (!TryComp<ClumsyComponent>(uid, out var clumsyComp))
return; // The given uid does not have that component, so we cancel the function.
// Here we can do stuff with the component, for example change one of its datafields
clumsyComp.Gun.ShootFailStunTime = TimeSpan.FromSeconds(5);
// Note that in this code block clumsyComp is never null, otherwise we would have left the function already, so we don't need another null check to work with it.
}
```
:::danger
Common Mistake:
If you have to do a lot of HasComps, TryComps or similar in a loop, then use a query instead! This is much better for performance. See below for an example.
:::
- `Resolve(uid, ref someComp)`
`TryComp`'s cooler cousin. Works the almost the same, but you put in an existing variable for a component. What component you look for is decided by the type of `someComp`. If `someComp` is `null`, then it will get the component from the entity, assign it to `someComp` and return `true`. If the entity does not have that component, then the `Resolve` will return `false` and `someComp` stays `null`.
If `someComp` already has a value when passed into `Resolve` then it only checks if the component belongs to the given uid. A common code pattern you see is this:
```
public void MyFunction(Entity<ClumsyComponent?> ent)
{
if (!Resolve(ent.Owner, ref ent.Comp))
return; // The given uid does not have a ClumsyComponent, so we cancel the function.
// here we can do stuff with the component, for example change one of its datafields
optionalComponent.GunShootFailStunTime = TimeSpan.FromSeconds(5);
// Note that in this code block ClumsyComponent is never null, otherwise we would have left the function already, so we don't need another null check to work with it.
}
```
With this we can call both
`MyFunction(uid)` where `uid` is of type `EntityUid` or
`MyFunction(ent)` where `ent` is of type `Entity<ClumsyComponent>`.
In the first case the type is automatically cast to the required one.
Passing the component as an optional parameter means you won't have to do a `TryComp` to get the component inside the function a second time in case you already have it available as a variable, which is better for performance!
You can also resolve up to four components at the same time
`Resolve(uid, ref comp1, ref comp2, ref comp3, ref comp4)`.
:::danger
:warning: Common Mistake:
If the `Resolve` fails to get the component, then it will throw a warning, which is a common cause for integration test fails. In cases where you expect the entity sometimes not to have the component, you have to disable that warning by using
`Resolve(uid, ref someComp, false)`.
:::
- `AddComp<SomeComponent>(uid)`
The same as `EntityManager.AddComponent<SomeComponent>(uid)`.
Adds a component to a given entity and returns it. Will throw an error if the entity already has that component.
Example:
```
// make an entity clumsy and set a datafield in the component
var clumsyComp = AddComp<ClumsyComponent>(uid);
clumsyComp.GunShootFailStunTime = TimeSpan.FromSeconds(5);
```
- `EnsureComp<SomeComponent>(uid)`
The same as `EntityManager.EnsureComponent<SomeComponent>(uid)`.
Similar to AddComp, but if the component already exists it will return the existing component instead of throwing an error.
- `RemComp<SomeComponent>(uid)`
Removes the specified component from an entity immediately. Returns `true` if the entity did have that component, `false` if it didn't.
- `RemCompDeferred<SomeComponent>(uid)`
Removes the specified component from an entity at the end of a tick. Returns `true` if the entity did have that component, `false` if it didn't. Use this function when actively enumerating that component (for example in update loops) or in event listeners. This makes sure the component is not deleted while it might still be used somewhere.
- `Del(uid)`
The same as `EntityManager.DeleteEntity(uid)`
Deletes the entity with the given uid immediately.
Don't use this inside an event subscription, as other subscriptions to the same event may be processed afterwards, causing errors if the entity no longer exists. In that casue use `QueueDel` instead.
If your code is in `Content.Shared` and predicted, then you will instead have to use `PredictedDel` to avoid errors.
- `QueueDel(uid)`
The same as `EntityManager.QueueDeleteEntity(uid)`.
This marks an entity to be deleted at the end of the current game tick.
Useful inside event subscriptions to make sure the deletion causes no errors.
If your code is in `Content.Shared` and predicted, then you will instead have to use `PredictedQueueDeleteEntity` to avoid errors.
- `TryQueueDel(uid)`
The same as `QueueDel` but returns a bool to tell you if the entity was successfully deleted. Returns `false` if it was already scheduled for deletion before.
- Spawn
- SpawnAttachedTo
- SpawnAtPosition
- SpawnNextToOrDrop
- `Transform(uid)`
The same as `GetComp<TransformComponent>(uid)`.
Every entity always has the `TransformComponent`, so this never fails (unless the entity does not exist) and does not need a `TryComp`. The `TransformComponent` contains information about an entity's position, rotation, the map or grid it is on, if it is anchored etc.
- `MetaData(uid)`
The same as `GetComp<MetaDataComponent>(uid)`.
Every entity always has the `MetaDataComponent`, so this never fails (unless the entity does not exist) and does not need a `TryComp`. The `MetaDataComponent` contains information about an entity's name, description, the prototype it is spawned from etc.
- `Name(uid)`
Shorter version of `MetaData(uid).Name`.
Returns the name of the entity, for example "Urist McHands".
:::danger
:warning: Common Mistake:
Don't use the name of a person for popups or other information received from vision! If you hide your face and have no ID card on you, your identity becomes hidden in the game, but the entity's metatdata name stays the same. Use `Identity.Name(uid)` instead so it properly shows up as "young man" or similar.
:::
- `Prototype(uid)`
A shorthand that gets the `EntityPrototype` an entity was spawned from, if any. You very likely should not be using this.
:::danger
:warning: Common Mistake:
You should in general never use the `MetaDataComponent`'s `PrototypeId` or `EntityPrototype` datafields. Not all entities are spawned from prototypes and many entities are modified after spawning, so this can break unexpectedly. If you want to check if your uid is a certain type of entity, then check for the components it has and the datafields inside. If needed, add a tag or empty marker component to it, so that it can be identified.
:::
- `_prototypeManager.Index(protoId)`
Gets a prototype defined in the .yml files from its ID. The ID of a prototype is just a name, the prototype itself contains all the information defined in yaml. The variable `protoId` has the type `ProtoId<SomePrototype>` which will decide what type of prototype will be looked for. `Index` will throw an error if no valid prototype with this ID and of this type exists.
Example:
```
# in yaml
- type: microwaveMealRecipe # defines a MicrowaveMealRecipePrototype. Note how the "Prototype" part of the name is omitted.
id: RecipeXenoburger
name: xenoburger recipe
result: FoodBurgerXeno
time: 10
solids:
FoodBreadBun: 1
FoodMeatXeno: 1
```
```
// in C#:
ProtoId<MicrowaveMealRecipePrototype> recipeId = "RecipeXenoburger";
MicrowaveMealRecipePrototype recipe = _prototypeManager.Index(recipeId);
// You can now have a look at the prototype's datafields:
var name = recipe.Name; // "xenoburger recipe"
var time = recipe.Time; // 10
```
:::danger
:warning: Common Mistake:
`_prototypeManager.Index<MicrowaveMealRecipePrototype>("RecipeXenoburger");`
This will work identically, but is less clear. A `ProtoId<T>` is basically just a string, but it has the additional information of what type of prototype you are using, so it should always be preferred over using a plain string for variables containing IDs you want to index. This has the additional advantage that the yaml linter will check any `ProtoId<T>` assigned to a datafield for existence.
:::
:::danger
:warning: Common Mistake:
If you have an ID for an `EntityPrototype` then always use the shorthand `EntProtoId` instead of`ProtoId<EntityPrototype>`. While this is functionally identical `ProtoId<EntityPrototype>` will throw a debug assert when used.
:::
- `_prototypeManager.TryIndex(protoId, out var prototype)`
Similar to index, but won't throw an error if the prototype could not be indexed and will return `true` or `false` instead.
- `_prototypeManager.HasIndex(protoId)`
Returns `true` if the specified prototype exists, `false` if not.
- `_proto.EnumeratePrototypes<SomePrototype>()`
Gives you an enumarator for all protoypes of type `SomePrototype` so you can use it in a `foreach` loop.
- `EntityQuery`
Returns an `IEnumerable` of all currently existing components of a certain type. For example:
```
foreach (component in EntityQuery<SomeComponent>())
{
// do stuff
}
```
This does not include the uid of the corresponding entity. If you need that, use `EntityQueryEnumerator` instead.
By default the query will exclude paused entities. If you use `EntityQuery<SomeComponent>(true)` they will be included.
- `EntityQueryEnumerator`
Gives you an enumerator for all unpaused entities with one or more specific components. For example:
```
var query = EntityQueryEnumerator<SomeComponent>(); // create a query
while (query.MoveNext(out var uid, out var someComponent)) // iterate over all entities with that component and assign them to variables
{
// do stuff
}
```
These are often used in update loops. This means paused entities are not updated. (TODO: section about paused entities/maps).
You can search for entities that have multiple components as well:
```
var query = EntityQueryEnumerator<FirstComponent, SecondComponent, ThirdComponent>();
while (query.MoveNext(out var uid, out var firstComponent, out var secondComponent, out var thirdComponent))
{
// do stuff
}
```
For performance reasons these should be ordered such that the rarest component is listed first - this way the query will have to check for the second and third component less often.
- `AllEntityQuery`
This is a shorthand for `AllEntityQueryEnumerator` and works the same as `EntityQueryEnumerator`, but also gives you paused entities. The name is a little confusing as it seems to be related to `EntityQuery`, but that one does not give you the uid of the found entities.
- `GetEntityQuery`
This one is completely different from the above, despite the similar name. It is used to create a cached lookup for `HasComp/TryComp`, making them faster. You should use this when you got a lot of them in a loop. For example:
```
public sealed class SomeSystem : EntitySystem
{
// ...
// on the top of the class below the dependencies:
private EntityQuery<PhysicsComponent> _physicsQuery; // declare the variable for the query
private HashSet<EntityUid> _entSet = new();
public override void Initialize()
{
base.Initialize();
_physicsQuery = GetEntityQuery<PhysicsComponent>(); // get the query itself
}
// our example: a function where we do something with a lot of entities and need the physics component for each one
public void MyFunction(EntityCoordinates coordinates, float distance)
{
// we look up all entities in range of a certain position that have the SomeComponent
var foundEntities = new HashSet<Entity<SomeComponent>>();
_entityLookup.GetEntitiesInRange(coordinates, distance, foundEntities);
// loop over all found entities
foreach (var ent in foundEntities)
{
// we want the physics component for each one, so we use TryComp with the query
_physicsQuery.TryComp(ent, out var physicsComp)
// this is equivalent to doing
// TryComp<PhysicsComponent>(ent, out var physicsComp)
// but much faster!
// do stuff
}
}
}
```
## Attributes
- `[RegisterComponent]`
Turns a class into a component that can be added to a prototype using yaml.
Example:
```
[RegisterComponent]
public sealed partial class MyNewComponent : Component { }
```
and in a .yml file:
```
- type: entity
id: TestEntity
name: test
description: Just a test!
components:
- type: Sprite
sprite: Objects/Fun/toys.rsi
state: plushie_lizard
- MyNew
```
This entity could now be spawned in-game using the spawn menu (press F5) in debug mode and it will have the `MyNewComponent` and a lizard sprite.
Note how the component name got automatically converted to lose the "Component" part, which works the same for (almost) all registered components. This way you have to type less!
- `[DataField]`
The `DataField` attribute allows a variable in a component to be serialized, meaning it can be loaded from or saved into a yaml file. This is used in two ways, the first one is that it allows setting the the value of the datafield in yaml for a specific prototype:
Example:
```
[RegisterComponent]
public sealed partial class MyNewComponent : Component
{
[DataField]
public int SomeNumber = 123;
}
```
and in a .yml file
```
- type: entity
id: TestEntity
name: test
description: Just a test!
components:
- type: Sprite
sprite: Objects/Fun/toys.rsi
state: plushie_lizard
- type: MyNew
someNumber: 42
```
When spawned the entity will now have the `SomeNumber` datafield set to `42` in its `MyNewComponent`.
If you omit the last line, the entity will have the default value of `123` instead.
Note how the datafield name is in PascalCase in the C# file, but got automatically converted into camelCase in yaml. And the `Component` part of the component's name is automatically omitted. Always follow these naming conventions!
:::danger
:warning: Common Mistake:
Old files still assign the datafield name manually like this:
```
[DataField("someNumber")]
public int SomeNumber = 123;
```
This is now obsolete and should not be done anymore!
:::
The second effect of the `DataField` attribute is that the variable will be serialized into a yaml file if the current game state is saved. While this is less relevant for SS14 as the rounds are continuous and rogue-like, this is important for mapping. As a rule of thumb you almost always want your variable to be a `DataField`, unless it is only used for caching. If it is not a `Datafield` then the information will be lost when saving the game, which breaks persistance. See this [issue](https://github.com/space-wizards/space-station-14/issues/34084) for more information.
:::danger
:warning: Common Mistake:
A variable of type `Entity<SomeComponent>` cannot be serialized at the moment, so you should never use these as a `DataField`. Instead store only the `EntityUid` and get the component using `TryComp` where you need it.
See this [issue](https://github.com/space-wizards/RobustToolbox/issues/5580) for more information.
:::
- `[ViewVariables]`
The ViewVariables attribute allows a variable inside a component to be inspected in-game using the ViewVariables window if you are an admin.
`[ViewVariables(VVAccess.ReadWrite)]` gives read and write access.
`[ViewVariables(VVAccess.ReadOnly)]` or `[ViewVariables]` give readonly access.
Use these to make debugging easier or allow admins to do some shenanigans by modifying these fields in-game. In general you can always allow these to be edited, unless doing so manually would horribly break stuff.
:::danger
:warning: Common Mistake:
`[DataField]` includes `[ViewVariables(VVAccess.ReadWrite)]` nowadays! So instead of `[DataField, ViewVariables(VVAccess.ReadWrite)]`, just write `[DataField]`, which is equivalent.
Old files still do this manually, but it should be omitted unless you want to set it to read only access.
:::
- `[NetworkedComponent]`
Used for easy networking!
If your component is defined in `Content.Shared` and has this attribute, adding or removing this component to/from an entity will be automatically synced between server and client.
Note that this does not sync the datafields in the component!
- `[AutoGenerateComponentState]`
Used for easy networking!
If your component is defined in `Content.Shared` and has this attribute, then you will be able to easily sync the component's datafields between server and client.
For this you have to add `[AutoNetworkedField]` to the datafields you want to network and call `Dirty(uid, comp)` on the server to update the component for the given entity on the client.
TODO: `AutoGenerateComponentState(true)`
A more in-detail guide for networking can be found [here](https://docs.spacestation14.com/en/ss14-by-example/basic-networking-and-you.html).
- `[AutoGenerateComponentPause]`, `[AutoPausedField]`
Used for `Timespan` datafields serving as a timestamp in update loops to make them automatically get corrected when the game is paused and unpaused. See the update loop example below for more details.
- `[Serializable]`
Makes it possible for a class, struct or enum to serialized in yaml. Use this if you define you own datatype to be used as a `DataField`.
- `[NetSerialzable]`
Makes it possible for a class, struct or enum to be networked, for example when used in a `DataField` in combination with `AutoNetworkedField`. Also requires the `[Serializable]` attribute.
## Subscriptions
- Raising events:
Events are how different systems communicate with each other. They allow us to keep our code modular and easily extendable. To send out or "raise" an event you simply do this:
```
// This is a heavily simplified and cleaned up version of the one in SlipperySystem.
// Someone walked over soap and we try to knock them down.
public bool TrySlip(EntityUid player, EntityUid soap)
{
// create an event instance
var attemptEv = new SlipAttemptEvent();
// we have to ask other systems if this mob can currently be slipped
RaiseLocalEvent(player, ref attemptEv);
if (attemptEv.Cancelled)
return false; // if the event was cancelled we stop and don't slip the target
Slip(player) // whatever code that actually does the stun, sound effect, animation etc
// raise another event, this time on the soap entity to inform it that someone slipped on it
var ev = new SlipEvent(player);
RaiseLocalEvent(soap, ref ev);
return true; // success!
}
```
```
// the event is usually defined somewhere in else, often in its own file in Content.Shared
[ByRefEvent]
public record struct SlipAttemptEvent(bool Cancelled=false);
[ByRefEvent]
public record struct SlipEvent(EntityUid Slipped);
// here we used the primary constructor feature of C#, which is sharter than, but equivalent to
public record struct SlipEvent
{
public EntityUid Slipped;
// constructor
public SlipEvent(EntityUid slipped)
{
Slipped = slipped;
}
}
```
A few notes things to note:
Structs are [value types](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-types). This means whenever we pass them somewhere else a copy is created, unless we specificly use the `ref` keyword. Some events are classes insteads of structs, which means they are reference types and are by default passed by reference. So in those cases we only need `RaiseLocalEvent(player, event)` without the `ref` keyword. The `[ByRefEvent]` attribute will ensure that we always pass this event by reference and will cause an error in case we forget.
A "local" event means if you raise it in Content.Server, then only the server will know about it. If you raise it in Content.Client, then only the client will know about it. If you raise it in Content.Shared then both the server and the client will know about it.
:::danger
:warning: Common mistake:
Events should always be record structs if possible. They are better for performance than classes.
A lot of old code defines events as classes inheriting from other abstract classes like for example `EntityEventArgs` or `CancellableEntityEventArgs`. Don't do this, we can simply use a `bool` for the `Cancelled` field and directly set it instead of using the `Cancelled()` function this class has.
:::
- Subscribing to events:
By itself an event does nothing, we need to subscribe to it in another system. The counterpart we use in combination with `RaiseLocalEvent` is `SubscribeLocalEvent`.
```
// simplified example from SleepingSystem
public sealed partial class SleepingSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
// We subscribe to the SipAttemptEvent with the SleepingComponent.
// This means if the event is raised on an entity with the SleepingComponent,
// then the OnSlipAttempt function is called.
SubscribeLocalEvent<SleepingComponent, SlipAttemptEvent>(OnSlipAttempt);
}
// Always follow the XYZEvent -> OnXYZ naming pattern for these functions
private void OnSlipAttempt(Entity<SleepingComponent> ent, ref SlipAttemptEvent args)
{
// Someone who is sleeping is already lying down, so they should not slip!
// We cancel the event to stop that from happening.
args.Cancelled = true;
}
}
```
Multiple components can subscribe to the same event! So you will find a similar subscription in `GodModeSystem` that cancels the event the same way for anyone with the `GodModeComponent`.
:::danger
:warning: Common Mistake:
A lot of old code uses the
`private void OnSlipAttempt(EntityUid uid, SleepingComponent comp, SlipAttemptEvent args)`
subscription style, which is equivalent, but out of date. Nowadays we always prefer the `Entity<SomeComponent>` pattern.
:::
Here is another example for what is done with the `SlipEvent`:
```
// simplified example for the subcription to SlipEvent, taken from TriggerSystem
public sealed partial class TriggerSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
// if the entity the player slipped on (soap, a banana peel etc) has the TriggerOnSlipComponent, then call the OnSlip function
SubscribeLocalEvent<TriggerOnSlipComponent, SlipEvent>(OnSlip);
}
private void OnSlipTriggered(Entity<TriggerOnSlipComponent> ent, ref SlipEvent args)
{
// do something with the entity that was slipped on.
Trigger(ent.Owner, args.Slipped);
}
}
```
This is used in case of the explosive banana peel, wich explodes when a player slips on it. The `Trigger` function is what tells the `ExplodeOnTriggerComponent` that it should explode now.
- Handled and cancelable events:
In the above example you got to know cancelable events, which are a common pattern. We ask other systems if we are allowed to do something at this moment for a certain entity. If one of the subscribers cancels the event, then we do nothing.
A similar pattern is are handled events.
TODO: example
Other common usecases for events can be found in our [ECS guide](https://docs.spacestation14.com/en/robust-toolbox/ecs.html).
- Subscription order:
The order in which multiple subscriptions with different components to the same event are done is basically random and can differ if someone else compiles the code. In most cases this does not matter though, for example a cancelable event is cancelled as long as at least one subscription sets the bool to false, regardless of the order. For handled events this can become a problem though, since some subscriptions are checking if the event has already been handled by another system before.
To enforce the order in which the events are raised in different systems, you can use
`SubscribeLocalEvent<SomeComponent, SlipComponent>(OnSlip, before: new[] { typeof(SomeOtherSystem) });`
or
`SubscribeLocalEvent<SomeComponent, SlipComponent>(OnSlip, after: new[] { typeof(SomeOtherSystem) });`
This should be used sparingly as it can quickly become very messy with multiple systems that have to be ordered somehow. Sorted events are slower performance-wise as well. So instead it might be better to split the event up into several stages that are raised one after the other. A common naming pattern for these are:
`AttemptSlipEvent`
`BeforeSlipEvent`
`SlipEvent`
`AfterSlipEvent`
- relay events
- ComponentInit, ComponentStartup, MapInit (Lifecycles)
- ComponentShutdown, ComponentRemove
- Some other common events you should know:
- `UseInHandEvent`: A player used an item by holding it and pressing the Z key
- `ActivateInWorldEvent`: Same as the above, but also works when pressing E while having the mouse cursor over the entity.
- `ExaminedEvent`: An entity is being examined using Shift + left click. In the subscription you can add text that will then be shown in the info window.
- `EntInsertedIntoContainerMessage` and `EntRemovedFromContainerMessage`: An entity got inster into or removed from a container. This could be a backpack (gridstorage), an item slot (for example the ID card in your PDA), a crate you are closing while items are on top of it, and so on.
- `GotEquippedEvent` and `GotUnequippedEvent`: A clothing item got equipped or unequipped by a player from their inventory slot.
- Broadcast events:
Events are normally *directed* towards one specific entity. However, they can also be broadcast so that all systems can listen to it:
```
ev = new SomeEvent();
// bradcast the event, not directed towards a specific entity
RaiseLocalEvent(ref ev)
// alternatively we can set the broadcast parameter to true to raise it both directed and as a broadcast at the same time
RaiseLocalEvent(uid, ref ev, true);
```
This will allow a subscription like this
```
SubscribeLocalEvent<SomeEvent>(SomeFunction);
// ...
private void SomeFunction(SomeEvent args)
{
//do stuff
}
```
This function will be called when the event is raised on *any* entity and does not have a requirement for a specific component. Since the subscription function does not know which entity the event was raised on (different from directed events) you should add the entity's `EntitiyUid` as a field to the event struct so that the subscription can use it.
- networked events
## Networking
- For a component to be networked, it needs to be in `Content.Shared` and it needs the `[NetworkedComponent]` and `[AutoGenerateComponentState]` attributes. The datafields that should be networked need the `[AutoNetworkedField]` attribute.
- Networking a component allows the corresponding system to be made *predicted*, which means the client can respond to a player input without having to wait for the server to acknowledge it, reducing input lag and greatly improving how responsive the game feels.
- The other advantage is that the datafields will available on the client, which might be needed for things like UI, sprite changes, or visual effects, which are applied client side.
- For a system to be predicted it needs to be in `Content.Shared`, the corresponding components need to be networked and you need to dirty them whenever the server changes any datafields.
- `Dirty(uid, component)` or `Dirty(ent)` where `ent` is of type `Entity<SomeComponent>` marks a component as dirty for networking. This will make the server send the current value of all datafields with the `[AutoNetworkedField]` attribute to the client. Datafields without `[AutoNetworkedField]` will not be updated on the client, which is fine if they never change or only the server needs them.
Here a small code example:
```
// We restrict who can modify datafields with the access attribute.
// This is useful to restrict other coders to set datafields correctly using a public API.
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(NetworkTestSystem))]
public sealed partial class NetworkTestComponent : Component
{
[DataField, AutoNetworkedField]
public bool TestField = false;
}
public sealed partial class NetworkTestSystem : EntitySystem
{
/// <summary>
/// Always add documentation to public API functions!
/// </summary>
/// <param name="newValue">The new value to set the datafield to.</param>
public void SetTestField(Entity<NetworkTestComponent> ent, bool newValue)
{
ent.Comp.TestField = newValue;
// Tell the client that the value has changed.
Dirty(ent);
}
}
```
On the client `Dirty` does nothing, as the server decides in the end what the correct value is. Otherwise cheat clients could modify any component data. If you want to send information from the client to the server you will have to use a message instead, for example a `BoundUserInterfaceMessage` in the case of an UI.
You can check if something is networked correctly in-game using the `ViewVariables` window, which can show components and their datafields for both the server and the client. Directly modifying a datafield in there for the server-side component will also dirty it.
- `DirtyField`
Dirtying a component always sends *all* networked datafields inside to the client, even if they haven't changed. This can become quite network expensive if the component has a lot of datafields. `DirtyField(ent, nameof(NetworkTestComponent.TestField))` allows you to only dirty a specific datafield instead. You need to set `AutoGenerateComponentState(fieldDeltas: true)` for this to work. Doing this has a small overhead, so it is mostly worth it for larger components, or those that are updated a lot.
- Manual networking: In 99% of cases you want to use autonetworking as that is simpler and easier to read. But some old code still does networking manually using a `ComponentGetState` and `ComponentHandleState` subscription. The former will pack the datafields into a `ComponentState` and send it to the client when dirtied. The latter will apply that state to the client side component, adjusting the datafields accordingly.
- If you ever want to do something on the client after a new component state is received (for example update an UI to show the new values in the component) you can subscribe `AfterAutoHandleStateEvent` if you are using autonetworking.
- Since code in `Content.Shared` can only use other code in `Content.Shared` (but not code in `Content.Client` or `Content.Server`) a system can only be made predicted if all of its dependencies and the other systems that are raising events towards it are predicted as well. This results in a situation where non-predicted code is kinda infectious, and making a system predicted often requires a lot of other code to be made predicted as well first. Currently a lot of old code on upstream is not predicted and cleaning that up is an ongoing effort. In general all new code has to be predicted where ever possible, so keep that in mind when implementing a new feature as it will be a requirement for your PR to pass review. But of course we don't expect you to make things like chat or power code predicted first if your feature uses them.
- `NetEntity`:
Entities don't always exist on both the server and the client. The client can spawn entities as well, for example for visual effects or preview icons in UIs, and these will not exist on other player's clients. In the same way not all entities that a server spawns will be networked to every client. As a result servers and clients may have a different number of entities that exist, and the EntityUid of entities will differ between them for the same entity (for example on the server an entity could have the uid 4321 but on the client it will be 625). To to be able to network entity uids between the server and client without having mixups we have `NetEntity`, which can be converted to and from an `EntityUid` by using `GetNetEntity(uid)`, `TryGetNetEntity(uid, out var netId)` and `TryGetEntity(netId)`. You will only need this when doing manual networking. When doing autonetworking then datafields with the `EntityUid` type will be automatically converted to their corresponding counterpart on the client. So in most cases you will never need NetEntities. Also note that a `NetEntity` cannot be serialized as a datafield, always use `EntityUid` inside components!
## Other common mistakes
- Documentation:
Don't forget to document your components and datafields. Even if you only add one new datafield to an existing component that is not documented yet, that doesn't mean you can't do better!
Example:
```
/// <summary>
/// Describe here what the component is doing when added to an entity.
/// </summary>
[RegisterComponent]
public sealed partial class MyNewComponent : Component
{
/// <summary>
/// Describe here what this datafield is for.
/// </summary>
[DataField]
public int SomeNumber = 123;
}
```
- Abstract prototypes:
If you got a base prototype that should not a fully functioning prototype on its own, but used for inheritance, then make sure to mark it as `abstract`. This will make sure it cannot be spawned by any means and it will be hidden from the F5 spawn menu.
Example:
```
# A simplified plushie that inherits from BaseItem and has a sprite,
# but no other special components. Abstract!
- type: entity
parent: BaseItem
id: BasePlushie
abstract: true # should not be spawned since this prototype is incomplete
components:
- type: Sprite
sprite: Objects/Fun/toys.rsi # we have a sprite, but not the state
# A plushie that can be spawned in the game.
- type: entity
parent: BasePlushie
id: PlushieBee
name: bee plushie
components:
- type: Sprite
state: plushie_h # add a state (the sprite path is inherited from BasePlushie)
# And another one.
- type: entity
parent: BasePlushie
id: PlushieLizard # Weh!
name: lizard plushie
components:
- type: Sprite
state: plushie_lizard
```
- HideSpawnMenu:
Some prototypes are not meant to be spawned on their own, for example mind entities or some visual effects like this one which is spawned when a flashbang activates:
```
- type: entity
id: GrenadeFlashEffect
categories: [ HideSpawnMenu ]
components:
- type: PointLight
enabled: true
radius: 5
energy: 8
netsync: false
- type: LightFade
duration: 0.5
- type: TimedDespawn
lifetime: 0.5
```
This is a simple pointlight that quickly disappears. We use `categories: [HideSpawnMenu]` to make sure it does not show up in the F5 spawn menu, but it can still be spawned using the `Spawn(protoId, coords)` function. This will also exclude it from several integration tests, which may otherwise fail for such an entity.
- Yaml inheritance:
There are different ways datafields get merged or overwritten when inheriting from another yaml prototype and they can be a little confusing.
Let's look at a few examples:
```
# Define a few tags for testing purposes.
# Normally these should be in tags.yml.
- type: Tag
id: TagA1
- type: Tag
id: TagA2
- type: Tag
id: TagB
- type: entity
id: ParentA
abstract: true
components:
- type: Tag
tags:
- TagA1
- TagA2
- type: MeleeWeapon # turn the entity into a weapon
damage:
types:
Heat: 10
- type: entity
id: ParentB
abstract: true
components:
- type: Tag
tags:
- TagB
- type: PointLight # make it glow
color: green
- type: entity
id: ParentC
abstract: true
components:
- type: Sprite
state: plushie_hampter # change the sprite state only, but not the rsi
# A simple item with a lizard sprite.
# A sprite needs both the 'sprite' datafield, which contains the path to the rsi folder the image file is in.
# And the 'state' datafield which is the name of the .png file itself.
- type: entity
parent: BaseItem
id: TestItem1
name: test lizard 1
components:
- type: Sprite
sprite: Objects/Fun/toys.rsi
state: plushie_lizard
# The lizard sprite state is inherited first.
# The state is not overwritten by the hampter because it already exists.
- type: entity
parent: [ TestItem1, ParentC ]
id: TestItem2
name: test lizard 2
# If we inherit in this order the state will be taken from ParentC.
# TestItem1 will then add the rsi path, but not overwrite the state.
- type: entity
parent: [ ParentC, TestItem1]
id: TestItem3
name: test hampter 3
# This time we manually overwrite the inherited state.
# The rsi remains unchanged.
# The result will be another hampter.
- type: entity
parent: TestItem1
id: TestItem4
name: test hampter 4
components:
- type: Sprite
state: plushie_hampter
# This item will inherit the tags from ParentA, but not from ParentB.
# To fix this we have to redefine the list manually and include all three tags.
# The item will have both the PointLightComponent and the MeleeWeaponComponent and the corresponding datafields set in the parents.
- type: entity
parent: [ TestItem1, ParentA, ParentB ]
id: TestItem5
name: test lizard 5
components:
- type: Tag
tags:
- TagA1
- TagA2
- TagB
```
To summarize the inheritance rules:
1. A datafield that is not set in yaml gets its default value from its C# definition. In C# all variables have a default value if not specified otherwise, for example `public bool SomeVariable;` will always be `false` (in other programming languages you may get random bits).
2. If you inherit from multiple parents then components and datafields are merged, but not overwritten. The order of inheritance matters.
3. You can overwrite datafields by reassigning a new value in the child.
4. If a datafield is overwritten, then the whole instance of the variable is reassigned. This means datatypes like lists (for example for tags) won't get merged, but replaced.
5. You cannot remove components that are inherited from a parent. You will have to make another abstract parent instead to avoid copy pasting everything.
- Update loops:
A lot of systems have an update function, which is often used to regularly do stuff for entities with a certain component.
`public override void Update(float frameTime)`
This function overrides the base `Update` function in the `EntitySystem` class. All `Update` functions in every system will be called every game tick, 30 times per second by default. Since there are a lot of systems doing this you can imagine this can become quite costly performance-wise. That is why we usually restrict the update rate to something lower like once every few seconds. Here is an example for how to properly implement an update loop:
In the component:
```
/// <summary>
/// A simple component that plays a sound every few seconds at a constant rate.
/// This is of course only an example and you can do anything in the update loop,
/// but the idea of how to restrict the number of updates stay the same.
/// </summary>
[RegisterComponent, NetworkedComponent]
[AutoGenerateComponentState, AutoGenerateComponentPause]
public sealed partial class AnnoyingSoundComponent
{
/// <summary>
/// The server time at which the next sound will play.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoNetworkedField, AutoPausedField]
public TimeSpan NextSound = TimeSpan.Zero;
/// <summary>
/// How often the sound is played.
/// In this case every two seconds.
/// </summary>
[DataField]
[AutoNetworkedField]
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(2);
/// <summary>
/// The sound that is played. Honk!
/// </summary>
[DataField]
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Items/bikehorn.ogg");
}
```
`[AutoGenerateComponentPause]` and `[AutoPausedField]` automatically adjust the next update time if the game has been paused and then unpaused. Without this the timer would continue running, causing all components to update simultaneously the moment the game is unpaused.
`customTypeSerializer: typeof(TimeOffsetSerializer)` ensures that the current game time is added to the initialization value. In this case the first update would trigger the moment the component is initialized. This is important for mapping, as the server time continues to run while the map is being worked on.
The other attributes are for enabling networking (assuming the component and system are in Content.Shared).
In the corresponding entity system:
```
public override void Update(float frameTime)
{
base.Update(frameTime);
// Get the current server time.
var curTime = _timing.CurTime;
// Find all entities with the AnnoyingSoundComponent
// and turn them into an enumerator so we can loop over them.
// Note that EntityQueryEnumerator ignores paused entities,
// for example those that are currently located in nullspace.
// This means paused entities don't get updated.
var query = EntityQueryEnumerator<AnnoyingSoundComponent>();
// Loop over all entities.
while (query.MoveNext(out var uid, out var comp))
{
// Skip this entity if it should not be updated yet.
if (comp.NextSound > curTime)
continue;
// Play the sound at the entity location.
// We set the user to null.
_audio.PlayPredicted(component.Sound, uid, null);
// Now we update set the next update time.
comp.NextSound += comp.UpdateInterval;
// Be careful if you are using randomized update intervals,
// as non-determinism may cause the server and client to desync!
}
}
// subscription to MapInitEvent
private void OnMapInit(Entity<ThrusterComponent> ent, ref MapInitEvent args)
{
// We have to set the first update time after the entity is spawned.
// Without this it will start at time 0 and update every single frame until it catches up to the server time.
// It is important that this is done in MapInitEvent, not ComponentInit.
ent.Comp.NextSound = _timing.CurTime + ent.Comp.UpdateCooldown;
}
```
:::danger
:warning: Common Mistake:
Do not update the `NextSound` datafield this way:
`comp.NextSound = curTime + comp.UpdateInterval;`
This causes it to eat up the remainder every update, making the update interval imprecise.
A small example:
`curTime` is 5.02 seconds, `NextSound` 5 seconds, triggering an update.
`comp.NextSound = curTime + comp.UpdateInterval; // 5.02+1=6.02 seconds`
`comp.NextSound += comp.UpdateInterval; // 5+1=6 seconds`
While this level of precision is not needed for a simple sound emitting component, it is important for other systems like power or emitters and it caused a singuloose in the past due to the accumulating error over time. It is good practice to do the update loop the precise way anyways since contributors tend to copy paste this code from whereever they find it.
:::
:::danger
:warning: Common Mistake:
Do not accumulate `frametime`:
```
while (query.MoveNext(out var uid, out var comp))
{
comp.TimeRemaining -= frameTime;
if (comp.TimeRemaining > 0)
continue;
// do stuff
comp.TimeRemaining = comp.UpdateInterval; // UpdateInterval is a float in this case
}
```
You often see this in older files, but again this becomes imprecise over time and causes problems with datafield serialization.
Always use a `TimeSpan` as a timestamp instead of accumulating time as a `float`.
:::
- Localization:
When making a PR, make sure that all player facing strings are localized by turning them into a localization string inside a `.ftl` file! See our [localization guide](https://docs.spacestation14.com/en/ss14-by-example/fluent-and-localization.html) for how to do that. The only exceptions are the names and descriptions of entity prototypes, which can be directly written inside the yaml file as these can be overwritten by forks in other languages by using automatically generated localization strings.
When turning a `LocId` into a localized `string` you can pass in parameters.
Here an example with or without parameters passed into the loc string:
```C#
// in C#
var message0 = Loc.GetString("test-message-0")
var message1 = Loc.GetString("test-message-0", ("user", userVariable))
var message2 = Loc.GetString("test-message-0", ("someName", foo), ("someOtherName"), bar)
// You can have an arbitrary number of parameters by adding them to the GetString method with this formatting
// For anything that uses a player characters name, make sure to use their identity instead, so that it does not get revealed if they wear a mask and no ID!
var message4 = Loc.GetString("petting-success-generic", ("target", Identity.Entity(playerUid, EntityManager)));
```
```
# in a fluent file
test-message-0 = This loc string has no parameters.
test-message-1 = This loc string has one parameter: { $user }.
test-message-2 = This loc string has two parameters: { $someName } and { $someOtherName }.
petting-success-generic = You pet {THE($target)} on {POSS-ADJ($target)} head.
```
The pre-defined functions like `THE(...)` available in RT can be found in [this file](https://github.com/space-wizards/RobustToolbox/blob/master/Robust.Shared/Localization/LocalizationManager.Functions.cs).
You can find more information about fluent in their official [documentation](https://projectfluent.org/fluent/guide/index.html).
- events vs hardcoding components into systems with TryComp (example: Clumsy refactor)
- Renaming protype IDs:
WizDen is the upstream repository for many forks and when we introduce breaking changes they will have to adjust their own custom code accordingly. One example of that is renaming prototype IDs, which should be avoided since they are not player facing anyways - only translation strings and the prototype name and description are! The prototype ID could be used by a fork in a spawn table, loadout, map etc. and they will have to fix every single one of these.
For maps this can be automated by adding the prototype name to [migration.yml](https://github.com/space-wizards/space-station-14/blob/master/Resources/migration.yml). Howevery this only works for *entity* prototypes. When the map is loaded the migrated ID is used instead, preventing any errors. If you completely remove an entity prototype you can migrate it to `null` which causes the old entity to be deleted. Once a mapper saves a new version of the map, the new ID will be used in the map's .yml file.
A similar migration file exists for floor tiles, see [tile_migrations.yml](https://github.com/space-wizards/space-station-14/blob/master/Resources/Prototypes/Tiles/tile_migrations.yml).
- Validating prototype IDs:
You might see this being used in older code:
`customTypeSerializer:typeof(PrototypeIdSerializer<SomePrototype>))`
This was used to check if a prototype ID string exists when setting it in a datafield. The yaml linter will throw an error if it doesn't. Similarly `[ValidatePrototypeId<SomePrototype>]` can be used for constant or static prototype strings in systems. However, both of these are obolete now and you should simply use `ProtoId<SomePrototype>` or `EntProtoId` instead. These will automatically do the same. See above for examples of how to index or spawn prototypes.
- Sandbox Violations:
To prevent servers from sending malicious code to players that are connecting to it, the game has a [whitelist](https://github.com/space-wizards/RobustToolbox/blob/master/Robust.Shared/ContentPack/Sandbox.yml) of libraries, functions and datatypes that are safe to use (look [here](https://docs.spacestation14.com/en/robust-toolbox/sandboxing.html?highlight=sandbox#sandboxing) for more details on sandboxing). If you try to run anything not in this list on the client or in shared (since shared code is run both on the client and server), you will get a sandbox violation causing the game to freeze:
`[ERRO] res.typecheck: Sandbox violation: Access to type not allowed:`
or
`[ERRO] res.typecheck: Sandbox violation: Access to method not allowed:`
This error is a little hard to find sometimes because it happens when starting the game, not when compiling and it is hidden in a bunch of other messages. Note that server-side code is not restricted by sandboxing, so you might run into sandbox violations if you try to move existing code from Content.Server to Content.Shared to make it predicted.
- Use mono audio for positional audio sources, it will break otherwise with no warning or test fail and play globally for every player. Lobby or ambient music is fine in stereo.
## other useful stuff:
- Dependencies
- Verbs
- Examine text
- PVS
- Admin Logging
- NullSpace
- Example for defining your own type of prototype
- Popups (PopupPredicted vs PopupClient vs PopupEntity)
- (predicted vs client vs server)
- file structure for shared components/sytems
- EntityTables
- when to predict stuff? Always!
- how to predict stuff? Move everything to shared, networks stuff, use predicted functions.
- CVar, GetCVar or Subs.CVar, Server/Client/Replicated
- crafting recipes and construction graphs
- relay events
- IDE errors in .xaml.cs files (they don't know the variables defined in xaml until you compile once, after that they disappear)
- Entity coordinates, world coordinates (with diagrams!)
- add this explanation from discord: a prototype is like a blueprint, which you define in yaml
entities are objects you spawn in the game, including items, map, grids, players, audio sources, basically everything
an entity prototype is a blueprint for an entity with a list of the components it gains upon spawning
there are other (non-entity) prototypes as well, for example microwave recipes, reagents, atmos gases, contraband levels, events and so on
- EntProtoId<SomeComponent>
- fixtures, Layers vs Masks
## Tips for debugging:
#### Running the yaml linter
The yaml linter will check all `.yml` files and tell you if your prototypes are valid or contain errors. It will automatically run as part of the integration tests when making a PR, but it can also be run locally and is a very helpful tool to find yaml errors (however, it will not check for correct indentation). If your entity prototype is not showing up in the spawn menu, running the linter may help you to find the error.
To run it in VSCodium, select it in the dropdown menu here and press the green button.

In Rider it looks like this:

If everything is fine you will get this message after a while:
`No errors found in 140862 ms.`
Examples of errors it can catch are wrong sprites
```
- type: Sprite
sprite: Objects/Fun/toys.rsi
state: plushie_lizard_typo # should be plushie_lizard
```
which gives you this error (a little hard to see inside the whole stack trace)
`State 'plushie_lizard_typo' not found in RSI: '/Textures/Objects/Fun/toys.rsi'` or missing prototypes
```
- type: entity
parent: PlushieLizardTypo # should be PlushieLizard
id: PlushieRainbowLizard
```
which gives you `The given key 'PlushieLizard' was not present in the dictionary`.
#### setting breakpoints
TODO
#### using Log.Debug / sawmill
Sometimes if bugs are hard to track using breakpoints or you want to observe a variable over time you can simply print it to the console using something like
`Log.Debug($"the current value of my variable is {variable}")`
The dollar sign formats the string by inserting the variable inside the curly braces as text (see the [dotnet documentation](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated)). This will print the string to the console in your IDE when this line of code is reached. The server and client have a separate log, so this is especially useful to track down prediction issues by comparing the two.
`Log.Debug` is only available inside entity systems. If you are not inside one, you have to use
`var sawmill = _logManager.GetSawmill("someName");`
and
`sawmill.Debug($"the current value of my variable is {variable}")` instead.
You will need the log manager as a dependency:
`[Dependency] private readonly ILogManager _logManager = default!;`
Make sure to remove the logging again before you make your PR!
#### common console commands
- `golobby`
Usually the the game will launch directly into the dev map in debug mode. You can still access the lobby and character editor with this command. It will end the current round. If you want to select another job than captain you will have to select a different map, because the dev map only has slots for the captain job.
- `endround`
Ends the current round and shows the round end screen with info about antags and players, but will not start the countdown for the new round.
- `restartround`
Ends the round and starts a countdown for the lobby, like when the evac shuttle arrives.
- `restartroundnow`
Immediately loads the game into the next round on the selected map.
- `forcemap <mapname>`
Future rounds will use the specified map. By default this is set to the `Dev` map in development mode. Use `forcemap ""` to clear the selection.
- `setgamepreset <presetname>`
The next round will start with the selected game mode. These are found in `game_presets.yml`. For example `Greenshift` will start a round without any antagonists or random events. If you want to test a gamemode like `Nukeops` you will ahve to edit the corresponding gamerule in `roundstart.yml` and reduce its minimum player count to 1 so it does not fail to start.
- `addgamerule <gamerulename>`
Used to create midround events.
For example `addgamerule NinjaSpawn` creates a ninja ghostrole.
- `shapes physics`
Makes fixtures (hitboxes) of all entities visible.
- `showpos`, `showrot`, `showvel`, `showangvel`
Activates a debug overlay showing entity positions, rotations, velocities, or angular velocities respectively.
- `sudo cvar shuttle.arrivals true`, `sudo cvar shuttle.emergency true`
Activates the arrivals or evac shuttle, which are disabled in dev mode by default
- `sudo cvar shuttle.grid_fill true`
Spawns other grids like the ATS, salvage relicts or the nukie shuttle. This is disabled by default in dev mode.
- `ghost`
Removes you from your body and turns you into an observer ghost. Can also be used via chat by typing `/ghost`
- `aghost`
If you have admin permissions this turns you into an admin observer, which is a ghost that has hands, inventory and a few other abilities for easy debugging interactions. Can also be used via chat by typing `/aghost`
- `vv <entityUid>`
Opens the ViewVariables window for the entity with the given uid, which allows you to see or edit all of its components. You can also use the `View Variables` verb when right clicking on it, or press Alt+V when your mouse is hovering above.
- `entities with <componentname> visualize`
Gives you a list of all entities with the selected component and allows you to teleport to them or open the VV window for them. The component name does not include the "Component" part.
- `entities with <componentname> delete`
Deletes all entities with that component. Useful if you want to breakpoint something and other entities than the one you want to check get in the way.
- `loadgrid`
Used to spawn shuttles or other grids like salvage ruins.
`loadgrid [mapid] [filepath] [x] [y]` will spawn it at the given coodinates. For example `/Maps/Shuttles/mining.yml ` is the salvage reclaimer. The map Id is usually 1 for the map the station is on.
- `tp [x] [y]`
Teleports you to the given coordinates.
- `tpto <name>`
Teleports you to the specified player.
- `devwindow`
Opens a new window for inspecing UI elements.
#### other useful debugging tools
- Spawn Menu
Press F5 to open the spawn menu. You can spawn any EntityPrototype from there by searching either via its name or Id. The game supports yaml hotreloading, which means if you edit a prototype in a yaml file and spawn a new entity from it, then the new entity will have the changed components or datafields. For any C# changes you will have to recompile the game though. If your prototype does not show up in the spawn menu that means it is invalid and contains an error. F6 opens a similar menu for floor tiles.
- Copying entities
Hover over an entity and press `P` to quickly spawn a copy of it. This creates a new entity from its original prototype, so any changes after it was spawned are not copied over.
- Solution Editor
Use the `Debug / Edit Solutions verb` on an entity to open the solution editor. With this you can modify or observe reagents inside a food item, a mob's stomach or bloodstream, the contents of a container and so on.
- Powering the station
If you want to test on a map other then the dev map you can use the `Tricks / Infinite Battery` verb to make a SMES, substation, or APC have infinite power so you don't have to set up a power source.
- Antag control
You can use the `Antag ctrl` verbs to make a mob that is currently controlled by a client into an antagonist of your choice.
- Testing area
Use the `Tricks / Send to test arena` verb to send yourself or someone else to the admin testing area. This is a small, separate map and useful if you want to test something on a life server without disrupting the current round. Each admin gets their own area.
- Rejuvenate
Use the `Debug / Rejuvenate` verb to quickly heal someone from all damage and status effects.
- Godmode
Use the `Tricks / Make Indestructible` verb to make someone immune to all damage.
## GitHub tips:
- Pulling a PR:
If you want to test or review someone else's PR this will be useful:
```
[alias]
pr = "!f() { git fetch -fu ${2:-upstream} refs/pull/$1/head:pr/$1 && git checkout pr/$1; }; f"
```
Put this somewhere into your /.git/config file.
Then use the console command `git pr 12345` to automatically pull that PR into a new branch.
Or use the github CLI, but you have to install that one first.
- Heisentests:
Some integration tests are failing randomly sometimes, for reasons unrelated to your PR. Common ones are `TryStopNukeOpsFromConstantlyFailing` and `SpawnAndDeleteAllEntitiesInTheSameSpot`. If you think an integration test fail is unrelated to your code changes, you can either ask a maintainer to re-run them for you or push an empty commit using
```
git commit --allow-empty -m "rerun tests"
git push origin <yourbranchname>
```
## TODO:
- write everything
- move some of this into the correct docs
- move the rest into its own "Beginner contributor guide" doc