Difference between revisions of "Programming Guide"

From Space Engineers Wiki
Jump to: navigation, search
 
(18 intermediate revisions by 7 users not shown)
Line 1: Line 1:
 
Programming in Space Engineers is done with the [[Programmable Block]] which can be given scripts written in C# (pronounced C Sharp). This can be used to make autonomous mining drones, long-range player-killing torpedoes, automated welding arms for ship construction and much more.
 
Programming in Space Engineers is done with the [[Programmable Block]] which can be given scripts written in C# (pronounced C Sharp). This can be used to make autonomous mining drones, long-range player-killing torpedoes, automated welding arms for ship construction and much more.
 +
 +
There is documentation on Github which is maintained by certain contributors and is very useful if you are already familiar with C# in general. https://github.com/malware-dev/MDK-SE/wiki
  
 
== Introduction ==
 
== Introduction ==
 
=== Editor access ===
 
=== Editor access ===
Only one player can edit the same script at time. If someone else has an editor for the current block open and someone else tries to open that block's editor, a notification will be shown that the editor is already open.
+
Only one player can edit the same script at a time. If someone else has an editor for the current block open and someone else tries to open that block's editor, a notification will be shown that the editor is already open.
  
 
=== Main method ===
 
=== Main method ===
When the editor is opened for first time, void Main() method is present inside the code editor.
 
This is entry point that will be called when executing script. If Main method is removed / renamed, the script will not run and you will be notified in the programmable block details area.
 
Custom methods/variables can be defined and used, but only the Main method will be called without reference.
 
  
=== Variables life ===
+
When the editor is opened for first time on a block, the Program constructor and the Main method are present inside the code editor. The code in the editor becomes the body of the Program class definition.
 +
 
 +
This Main method is the entry point that will be called when executing script. If Main method is removed / renamed, the script will not run and you will be notified in the programmable block details area.
 +
 
 +
Custom methods/variables can be defined and used, but only the Main method will be called by the program.
 +
 
 +
=== Variable lifetime and scoping ===
 
There are two types of variables for scripting:
 
There are two types of variables for scripting:
Local (inside the methods) these variables will keep their value only during execution of a method.
+
;Local (inside the methods) : these variables will keep their value only during execution of a method. Value will be “lost” when the method ends.
Value will be “lost” when the method ends.
+
;Global (outside the methods) : these variables will keep their values during the lifetime of script. For example, if the variable needs to keep its value between separate runs of the script, it needs to be defined outside the methods.
Global (outside the methods) - these variables will keep their values during the lifetime of script. E.g.
+
 
If the variable needs to keep value between separate runs of program, it needs to be defined outside the methods.
 
 
After pressing “Remember & Exit” or “Remember” buttons, the previous script will be overwritten and all Global variables will be lost.
 
After pressing “Remember & Exit” or “Remember” buttons, the previous script will be overwritten and all Global variables will be lost.
 +
 
All variables, local and global except for the built-in Storage variable will lose their value or return to their default value when recompiling the code and between saved game loads.
 
All variables, local and global except for the built-in Storage variable will lose their value or return to their default value when recompiling the code and between saved game loads.
The Storage variable is unique in that that it store data as string between saved seasons and recompile.
+
The Storage variable is unique in that it will store the data as a string for use between saved sessions and recompile.
  
 
=== Compiling ===
 
=== Compiling ===
Line 35: Line 40:
  
 
=== Script execution ===
 
=== Script execution ===
When “Run” button is pressed or “Run” is assigned as terminal action, script is executed. Currently “Run” needs to be called manually e.g. user need to click on “Run” button or attach it as terminal action.
+
Script can be triggered by the following means:
 +
 
 +
1. By pressing "Run" button in terminal properties of programmable block.
 +
 
 +
2. By assigning terminal action and manually pressing the action button (1-9) while controlling the grid using cockpit, control station or remote control.
 +
 
 +
3. By pressing button on a button panel with assigned action "Run".
 +
 
 +
4. By a timer with assigned action "Run".
 +
 
 +
5. By another script in another programmable block in the same grid.
 +
 
 +
6. By antenna with assigned programmable block, when recieved message from another antenna. (see [[Antenna#Programming]])
 +
 
 +
7. By the script itself, by assigning a value to Runtime.UpdateFrequency variable. In this case, '''no argument can be specified''', however, you can use the following Main method signature: void Main(string argument, UpdateType updateSource) to gain acces to the information about what exactly triggered the script and so make the script "know" if it was triggered by Update1, Update10, Update100 events or manually or whatever event was the reason of trigger execution.
 +
 
 
Script is executed only on server even if it’s triggered from client. If there is any exception during script execution, all clients will be notified in programmable block details area about failure.
 
Script is executed only on server even if it’s triggered from client. If there is any exception during script execution, all clients will be notified in programmable block details area about failure.
 
In case of exception during script execution, script will not run again unless User opens editor and change script.
 
In case of exception during script execution, script will not run again unless User opens editor and change script.
A timer can also continuesly run the script by having a run action (you may be prompt to input an argument) and then starting/triggering itself or being started/triggered via script code (if going by the letter the timer will stop if the script crash).
 
  
 
=== Counting of instructions ===
 
=== Counting of instructions ===
Every time script is executed, every instruction of script is counted. If script executes more instruction than limit, execution is stopped and user is notified that script is too complex for execution. This prevents scripts to “freeze” game.
+
Every time script is executed, every instruction of script is counted. If script executes more instruction than limit, execution is stopped and user is notified that script is too complex for execution. This prevents scripts from freezing the game. It is similar to the idea of a stack overflow exception, and is unrealistically high. If you get this error, it is more likely you have an infinite loop than that you are trying to do too much. The count can be accessed on <code>this.Runtime.CurrentInstructionCount</code> to narrow down the problem.
  
 
=== Whitelist ===
 
=== Whitelist ===
Line 56: Line 75:
  
 
=== Same block class for different SubTypeID ===
 
=== Same block class for different SubTypeID ===
Some blocks have same parent (e.g. <TypeId> in cubeblocks.sbc) and differs only by subtype (e.g. <SubtypeId>). This means there is no distinction between these block in code. <br />
+
Some blocks have the same parent (e.g. <TypeId> in cubeblocks.sbc) and differs only by subtype (e.g. <SubtypeId>). This means there is no distinction between these blocks in code.
Example of these blocks is the Cargo Container: there are 3 types of cargo containers in the game: small, medium and large. These three types differ only by subtype and Type is same for them e.g. large cargo container id is: <br />
+
 
<Id><br />
+
Example of these blocks is the Cargo Container: there are 3 types of cargo containers in the game: small, medium and large. These three types differ only by the Subtype and Type is the same for them e.g. large cargo container id is:
<TypeId>CargoContainer</TypeId><br />
+
 
<SubtypeId>LargeBlockLargeContainer</SubtypeId><br />
+
<Id>
</Id><br />
+
  <TypeId>CargoContainer</TypeId>
Medium is:<br />
+
  <SubtypeId>LargeBlockLargeContainer</SubtypeId>
<Id><br />
+
</Id>
<TypeId>CargoContainer</TypeId><br />
+
 
<SubtypeId>SmallBlockMediumContainer</SubtypeId><br />
+
Medium is:
</Id><br />
+
 
And small is:<br />
+
<Id>
<Id><br />
+
  <TypeId>CargoContainer</TypeId>
<TypeId>CargoContainer</TypeId><br />
+
  <SubtypeId>SmallBlockMediumContainer</SubtypeId>
<SubtypeId>LargeBlockSmallContainer</SubtypeId><br />
+
</Id>
</Id><br />
+
 
 +
And small is:
 +
 
 +
<Id>
 +
  <TypeId>CargoContainer</TypeId>
 +
  <SubtypeId>LargeBlockSmallContainer</SubtypeId>
 +
</Id>
  
 
In this case there is only one class IMyCargoContainer for all types of cargo containers.
 
In this case there is only one class IMyCargoContainer for all types of cargo containers.
Line 101: Line 126:
  
 
  List<MyDetectedEntityInfo> entity_list = new List<MyDetectedEntityInfo>();  
 
  List<MyDetectedEntityInfo> entity_list = new List<MyDetectedEntityInfo>();  
 
 
  public Program()
 
  public Program()
 
  {
 
  {
Line 107: Line 131:
 
     //This makes the program automatically run every 10 ticks.
 
     //This makes the program automatically run every 10 ticks.
 
  }
 
  }
 
 
  public void Main()
 
  public void Main()
 
  {
 
  {
Line 120: Line 143:
  
 
For this script to work, the sensor must be named "Door Sensor 1" and the door must be named "Door 1". If you configure the sensor to open the door, the door will automatically open when the player enters the sensor range and close when the player leaves the sensor range.
 
For this script to work, the sensor must be named "Door Sensor 1" and the door must be named "Door 1". If you configure the sensor to open the door, the door will automatically open when the player enters the sensor range and close when the player leaves the sensor range.
 +
 +
=== Firing Thrusters ===
 +
 +
// ship remote control or cockpit
 +
IMyShipController myRemote;
 +
// List of thrusters
 +
List<IMyThrust> myThrusters;
 +
// ship velocity
 +
Vector3D velocity { get { return myRemote.GetShipVelocities().LinearVelocity; } }
 +
// ship center of mass (so rotation doesn't make us change position)
 +
Vector3D position { get { return myRemote.CenterOfMass; } }
 +
// ship gravity, including any artificial gravity fields
 +
Vector3D gravity { get { return myRemote.GetTotalGravity(); } }
 +
// physical mass includes multipliers etc.
 +
double mass { get {return myRemote.CalculateShipMass().PhysicalMass; } }
 +
public void Move(double speed, Vector3D targetPos)
 +
{
 +
  // get a vector from out current position to our desired position
 +
  // use ClampToSphere so we aren't constantly at full thrust like the stock autopilot
 +
  //Vector3D desiredVelocity = Vector3D.ClampToSphere(targetPos - position, 1) * speed;
 +
  // get a vector from our current velocity to our desired velocity
 +
  //Vector3D travelVec = desiredVelocity - velocity;
 +
  // apply the thrust to these thrusters
 +
  ApplyThrust(myThrusters, Vector3D.Zero /* travelVec */);
 +
}
 +
public void ApplyThrust(List<IMyThrust thrusters, Vector3D move) {
 +
  move += -gravity;
 +
  move *= mass;
 +
  double totalThrust = 0;
 +
 
 +
  // this can be optimized later. There are faster ways.
 +
  foreach(var thruster in thrusters)
 +
    totalThrust += Math.Max(0, Vector3D.Dot(thruster.WorldMatrix.Backward, move));
 +
 +
  foreach(var thruster in thrusters) {
 +
    // Thrust override is using MaxThrust so we need to convert to that
 +
    var comp = thruster.MaxThrust / thruster.MaxEffectiveThrust;
 +
    // calculate how much this thrust helps with the total thrust
 +
    var part = thruster.MaxEffectiveThrust / totalThrust;
 +
    // Dot product with the travel direction of the thruster, not the flame direction
 +
    var thrust = Vector3D.Dot(thruster.WorldMatrix.Backward, move);
 +
    // set the thrust override based on all the parameters
 +
    thruster.ThrustOverride = (float)(thrust > 0 ? thrust * part * comp : 0.001);
 +
  }
 +
}
 +
 +
== Compilation errors ==
 +
This is a list (in progress) of known compilation errors and what causes them.
 +
 +
* Method name expected: The compiler found parentheses when it wasn't expecting them. You could be missing a method name before the parentheses, or you might be inappropriately using parentheses instead of square or curly brackets, depending on what you're trying to do.
 +
 +
== See also ==
 +
* [[API:Sandbox.ModAPI.Ingame.MyGridTerminalSystem|MyGridTerminalSystem]] - Methods for getting object references to your various ship components.
 +
* [[Programming Guide/Action List]] - Actions you can apply to objects via the Object.ApplyAction method. Also includes some of the object properties. Data appears incomplete as of March 19th, 2018.
  
 
== External links ==
 
== External links ==
 +
* [https://github.com/malware-dev/MDK-SE/wiki/Quick-Introduction-to-Space-Engineers-Ingame-Scripts Quick Introduction to Space Engineers Ingame Scripts · malware-dev/MDK-SE Wiki] - Autogenerated API listing, tutorials, and more.
 
* [https://forums.keenswh.com/threads/guide-programmable-block-c-101-for-space-engineers.7225150/ <nowiki>[Guide]</nowiki> Programmable Block - C# 101 For Space Engineers] - Basic intro to C# programming for Space Engineers.
 
* [https://forums.keenswh.com/threads/guide-programmable-block-c-101-for-space-engineers.7225150/ <nowiki>[Guide]</nowiki> Programmable Block - C# 101 For Space Engineers] - Basic intro to C# programming for Space Engineers.
 +
* [https://forum.keenswh.com/threads/guide-programmable-block-c-102-for-space-engineers-loops-strings-and-other-things.7229828/ <nowiki>[Guide]</nowiki> Programmable Block - C# 102 for Space Engineers: Loops, Strings, and Other Things] - Basic intro to using loops and strings in C#
 +
* [https://forum.keenswh.com/threads/guide-programmable-block-c-103-for-space-engineers-math-class.7231429/ <nowiki>[Guide]</nowiki> Programmable Block - C# 103 for Space Engineers - Math Class] - Basic intro to using the Math library in C#
 
* [https://forums.keenswh.com/threads/programmable-block-inter-grid-communication-guide.7392031/ Programmable Block Inter-Grid Communication Guide] - Using antennas to enable programming blocks to remotely communicate with each other.
 
* [https://forums.keenswh.com/threads/programmable-block-inter-grid-communication-guide.7392031/ Programmable Block Inter-Grid Communication Guide] - Using antennas to enable programming blocks to remotely communicate with each other.
 
* [https://github.com/malware-dev/MDK-SE/wiki/Continuous-Running-No-Timers-Needed Continuous Running No Timers Needed] - Configuring programming blocks to run automatically without needing to use a timer.
 
* [https://github.com/malware-dev/MDK-SE/wiki/Continuous-Running-No-Timers-Needed Continuous Running No Timers Needed] - Configuring programming blocks to run automatically without needing to use a timer.
 +
* [https://forum.keenswh.com/threads/tutorial-constructor-and-save.7382338/ Tutorial: Constructor And Save] - Saving and reloading data
 +
* [https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/ C# Operators] - C# language reference for all operators, such as and, or, greater than, less than, etc.
 +
* [https://fresc81.github.io/SpaceEngineers/index.html Space Engineers API documentation]
 +
* [https://bloc97.github.io/SpaceEngineersModAPIDocs/html/b2d609dc-672a-3d90-cdc0-3753ce60d06f.htm Space Engineers ModAPI Documentation] - Includes many API methods and properties which can be used by Programmable Blocks.
  
 
[[Category:Game Mechanics]]
 
[[Category:Game Mechanics]]

Latest revision as of 04:52, 3 June 2022

Programming in Space Engineers is done with the Programmable Block which can be given scripts written in C# (pronounced C Sharp). This can be used to make autonomous mining drones, long-range player-killing torpedoes, automated welding arms for ship construction and much more.

There is documentation on Github which is maintained by certain contributors and is very useful if you are already familiar with C# in general. https://github.com/malware-dev/MDK-SE/wiki

Introduction

Editor access

Only one player can edit the same script at a time. If someone else has an editor for the current block open and someone else tries to open that block's editor, a notification will be shown that the editor is already open.

Main method

When the editor is opened for first time on a block, the Program constructor and the Main method are present inside the code editor. The code in the editor becomes the body of the Program class definition.

This Main method is the entry point that will be called when executing script. If Main method is removed / renamed, the script will not run and you will be notified in the programmable block details area.

Custom methods/variables can be defined and used, but only the Main method will be called by the program.

Variable lifetime and scoping

There are two types of variables for scripting:

Local (inside the methods) 
these variables will keep their value only during execution of a method. Value will be “lost” when the method ends.
Global (outside the methods) 
these variables will keep their values during the lifetime of script. For example, if the variable needs to keep its value between separate runs of the script, it needs to be defined outside the methods.

After pressing “Remember & Exit” or “Remember” buttons, the previous script will be overwritten and all Global variables will be lost.

All variables, local and global except for the built-in Storage variable will lose their value or return to their default value when recompiling the code and between saved game loads. The Storage variable is unique in that it will store the data as a string for use between saved sessions and recompile.

Compiling

When the “Check code” button is pressed, the code will be compiled and the result of the compilation will be shown. There are two steps of the compilation process: First the code inside editor is compiled by c# compiler for language errors. If there are any errors during compilation the following dialog is shown: It this case “aaa” string is placed before Main method. This is the wrong language construction and the compilation failed. In the error dialog the Line number error and description of the error is shown.

After compilation, the code is checked for usage of disallowed namespaces and types. In case that check fails, the following dialog is shown: In this case System.IO.Directory was used to delete some directory. This is forbidden and error is shown that “Not allowed type was used in script”.

If compilation and checks pass, a dialog is shown, confirming the checks passed, and the code is saved.

Script execution

Script can be triggered by the following means:

1. By pressing "Run" button in terminal properties of programmable block.

2. By assigning terminal action and manually pressing the action button (1-9) while controlling the grid using cockpit, control station or remote control.

3. By pressing button on a button panel with assigned action "Run".

4. By a timer with assigned action "Run".

5. By another script in another programmable block in the same grid.

6. By antenna with assigned programmable block, when recieved message from another antenna. (see Antenna#Programming)

7. By the script itself, by assigning a value to Runtime.UpdateFrequency variable. In this case, no argument can be specified, however, you can use the following Main method signature: void Main(string argument, UpdateType updateSource) to gain acces to the information about what exactly triggered the script and so make the script "know" if it was triggered by Update1, Update10, Update100 events or manually or whatever event was the reason of trigger execution.

Script is executed only on server even if it’s triggered from client. If there is any exception during script execution, all clients will be notified in programmable block details area about failure. In case of exception during script execution, script will not run again unless User opens editor and change script.

Counting of instructions

Every time script is executed, every instruction of script is counted. If script executes more instruction than limit, execution is stopped and user is notified that script is too complex for execution. This prevents scripts from freezing the game. It is similar to the idea of a stack overflow exception, and is unrealistically high. If you get this error, it is more likely you have an infinite loop than that you are trying to do too much. The count can be accessed on this.Runtime.CurrentInstructionCount to narrow down the problem.

Whitelist

The types and classes allowed in scripts are restricted. Refer to the Scripting Whitelist to see what you are allowed to use.

Available interfaces

Possible Actions

Currently only terminal actions can be triggered inside scripts. User can access terminal system for grid on which programmable block is located and trigger any terminal action on any block at grid.

Block Classes (Action List)

Same block class for different SubTypeID

Some blocks have the same parent (e.g. <TypeId> in cubeblocks.sbc) and differs only by subtype (e.g. <SubtypeId>). This means there is no distinction between these blocks in code.

Example of these blocks is the Cargo Container: there are 3 types of cargo containers in the game: small, medium and large. These three types differ only by the Subtype and Type is the same for them e.g. large cargo container id is:

<Id>
  <TypeId>CargoContainer</TypeId>
  <SubtypeId>LargeBlockLargeContainer</SubtypeId>
</Id>

Medium is:

<Id>
  <TypeId>CargoContainer</TypeId>
  <SubtypeId>SmallBlockMediumContainer</SubtypeId>
</Id>

And small is:

<Id>
  <TypeId>CargoContainer</TypeId>
  <SubtypeId>LargeBlockSmallContainer</SubtypeId>
</Id>

In this case there is only one class IMyCargoContainer for all types of cargo containers.

Example programs

Hello world

The standard Hello World program in Space Engineers can be written as such:

public void Main()
{
   Echo ("Hello, world!");
}

If this program is entered into a programmable block and run, it will result in "Hello, world!" being displayed in the programmable block's interface on the lower right hand side of the screen.

Getting your position

This program will show the current GPS coordinates of your programming block's position in the world.

public void Main()
{
    var pos = Me.GetPosition();
    Echo (pos.ToString());
}


Checking a sensor

It's easy to get a sensor to open a door or trigger some other action even without any programming if you just place that action in the sensor's "Setup actions" list. However, triggering an action when a sensor does not detect something is more difficult, and cannot be done with timer blocks. This program will automatically check a sensor every 10 ticks (working out to about 6 times per second) and close a door if the sensor does not detect anything. This can easily be applied to other purposes, like turning off drills when asteroids are not in sensor range.

List<MyDetectedEntityInfo> entity_list = new List<MyDetectedEntityInfo>(); 
public Program()
{
    Runtime.UpdateFrequency = UpdateFrequency.Update10;
    //This makes the program automatically run every 10 ticks.
}
public void Main()
{
    var door_sensor = GridTerminalSystem.GetBlockWithName("Door Sensor 1") as IMySensorBlock;
    door_sensor.DetectedEntities (entity_list);
    if (entity_list.Count == 0)
    {
        var door = GridTerminalSystem.GetBlockWithName("Door 1") as IMyDoor;
        door.ApplyAction ("Open_Off");
    }
}

For this script to work, the sensor must be named "Door Sensor 1" and the door must be named "Door 1". If you configure the sensor to open the door, the door will automatically open when the player enters the sensor range and close when the player leaves the sensor range.

Firing Thrusters

// ship remote control or cockpit
IMyShipController myRemote;
// List of thrusters
List<IMyThrust> myThrusters;
// ship velocity
Vector3D velocity { get { return myRemote.GetShipVelocities().LinearVelocity; } }
// ship center of mass (so rotation doesn't make us change position)
Vector3D position { get { return myRemote.CenterOfMass; } }
// ship gravity, including any artificial gravity fields
Vector3D gravity { get { return myRemote.GetTotalGravity(); } }
// physical mass includes multipliers etc.
double mass { get {return myRemote.CalculateShipMass().PhysicalMass; } }
public void Move(double speed, Vector3D targetPos)
{
  // get a vector from out current position to our desired position
  // use ClampToSphere so we aren't constantly at full thrust like the stock autopilot
  //Vector3D desiredVelocity = Vector3D.ClampToSphere(targetPos - position, 1) * speed;
  // get a vector from our current velocity to our desired velocity
  //Vector3D travelVec = desiredVelocity - velocity;
  // apply the thrust to these thrusters
  ApplyThrust(myThrusters, Vector3D.Zero /* travelVec */);
}
public void ApplyThrust(List<IMyThrust thrusters, Vector3D move) {
  move += -gravity;
  move *= mass;
  double totalThrust = 0;
  
  // this can be optimized later. There are faster ways. 
  foreach(var thruster in thrusters)
    totalThrust += Math.Max(0, Vector3D.Dot(thruster.WorldMatrix.Backward, move));

  foreach(var thruster in thrusters) {
    // Thrust override is using MaxThrust so we need to convert to that
    var comp = thruster.MaxThrust / thruster.MaxEffectiveThrust;
    // calculate how much this thrust helps with the total thrust
    var part = thruster.MaxEffectiveThrust / totalThrust;
    // Dot product with the travel direction of the thruster, not the flame direction
    var thrust = Vector3D.Dot(thruster.WorldMatrix.Backward, move);
    // set the thrust override based on all the parameters
    thruster.ThrustOverride = (float)(thrust > 0 ? thrust * part * comp : 0.001);
  }
}

Compilation errors

This is a list (in progress) of known compilation errors and what causes them.

  • Method name expected: The compiler found parentheses when it wasn't expecting them. You could be missing a method name before the parentheses, or you might be inappropriately using parentheses instead of square or curly brackets, depending on what you're trying to do.

See also

  • MyGridTerminalSystem - Methods for getting object references to your various ship components.
  • Programming Guide/Action List - Actions you can apply to objects via the Object.ApplyAction method. Also includes some of the object properties. Data appears incomplete as of March 19th, 2018.

External links