Space Engineers

Space Engineers

31 ratings
[WIP]How to use WeaponCore's Programmable Block API
By Sigmund Froid
This guide is an attempt at explaining how to program ingame-scripts with WeaponCore.
I'll update this as I find new things to point out. If you need help ask in the comments.
Example Script: https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=2197413052
2
   
Award
Favorite
Favorited
Unfavorite
Introduction
Hey, my name is Froid, I'm a coder, and have worked a bit with the WeaponCore Programmable Block API, and decided to help others with it, documenting and explaining things. I hope this will help people, who planned to update their scripts, but aren't sure how to use this API. If you need to contact me, don't send me a friend request, contact me via comments first!

Get WeaponCore here:
https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=1918681825

Support Darkstar (Patreon)[www.patreon.com]

Disclaimer
The following is a fan-made guide about the Programmable Block API of WeaponCore.
I'm no author of WeaponCore. All findings in this guide only come from my experimentation or direct conversation with the Devs.
Because of that, there might be errors in my explanations.
Furthermore, there's always the possibility that methods change or get removed. Although I should notice such things fairly quickly, I can not guarantee that this guide will always be up to date. (I also have a life, you know)
Don't use this to harass scripters to update their script, it's their personal decision whether they want to deal with the problems of a potential changing API or not! If I find anyone doing this, they will be reported ASAP. Scripters are just human beings as you and I!

Something is missing?
If you happen to find any inconsistancies or new insights about this, feel free to let me know.
If no description is provided for a method, it's likely I haven't used it so far. Consider asking on the workshop page of WeaponCore and report with the new found knowledge back here, so I can add it.

Now that this is said, let's get started!
The API class
The API class on GitHub is basically a blueprint which you can copy into your script. I usually copy the entire class WcPbApi and change the method Activate to public, as I think it looks cleaner, but you can also just copy the contents of the class. I'll note differences later.

(Basically copy all this:)
public class WcPbApi { ... //other code }

For newest version always check the GitHub: here[github.com]

The class consists of three main parts:
  1. A vast amount of fields, all of them being of some type of Func<T>, T being any number of types.
  2. The methods Activate, ApiAssign and AssignMethod.
  3. A vast amount of methods that invoke the Func<T> stored in the fields.

Every field has an equivalent method at the bottom and an equivalent call of AssignMethod in ApiAssign. You can remove all of these for a method, if you don't need them, without your script breaking. (In case you're running out of space)

Here is an example:
https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=2197413052

EDIT(14th of August 2020): Removing methods you won't need in your script. decreases the probability that your script breaks after an API change
How to implement the class
After pasting the class in, you'll have to call it's method Activate once with Me as input value. Me references the Programmmable Block running the script, in case you didn't know.
If following my practice of pasting the class, I suggest saving an instance of the class as a static reference (I usually call it just "api") and call the method on that. When needing a method of it, just call api.<methodname> then. If you just pasted the content of the class, you don't need a static field for the instance and can just call the method as normal.

Variant 1:
public static WcPbApi api; ... Program(){ ... api = new WcPbApi(); api.Activate(Me); ... api.<methodname>(<params>); ... } ... public class WcPbApi { ... //api code }
Variant 2:
Program(){ ... Activate(Me); ... <methodname>(<params>); ... } ... ...//api code
Activate, ApiAssign and MethodAssign
First of all, you should never edit Activate and MethodAssign, unless you know what you're doing. In ApiAssign, only touch the calls to MethodAssign of methods you don't need.

The method Activate
Activate returns a boolean that indicates whether or not the API was able to initialize through the call to ApiAssign. There are two main reasons, why the API could fail to initialize, in which case the code will throw an exception:
  1. The world doesn't have WeaponCore or any mod using it in the modlist. You'll need WeaponCore, if you're trying to program with it.
  2. The IMyTerminalBlock entered as input wasn't a Programmable Block. Well wouldn't make sense to make it accessible elsewhere, would it?

The method ApiAssign
This is the juicy bit in which all the magic happens. The input delegates is a reference table in ApiBackend[github.com] which saves the internal api methods, with a string key. This method calls MethodAssign with the dictionary and a string key, which should match one in the dictionary, and a field to store the api method in. These should be the matching fields on top. Should the dictionary be null for what ever reason, this method will return false to indicate the API failling to initialize.

The method MethodAssign
This method will assign the value stored in the dictionary with the given key to the input field.
It also throws two very helpful exceptions in case something goes wrong.
  1. $"{GetType() . Name} :: Couldn't find {name} delegate of type {typeof(T)}"
    The key {name} doesn't match any key in the dictionary storing a value with the same type as T. Most likely you misspelled the key.
  2. $"{GetType() . Name} :: Delegate {name} is not type {typeof(T)}, instead it's: {del.GetType()}"
    The type of your field doesn't match the type of the delegate stored with this key.
Note that {GetType() . Name}, {name}, {typeof(T)} and {del.GetType()} will be replaced by their respective value.
The weaponId
The weaponId is an int referred to in multiple methods, which seems to influence which weapon of a block is referred to, so it's probably connected to multiturret blocks or something along those lines. However I can't confirm it so far.
The methods (Part 1: Get Weapons)
First of all a small headsup: You can't use vanilla weapon classes to get WeaponCore weapons. So this won't work:
GridTerminalSystem.GetBlocksOfType<IMyLargeTurretBase>(list);

For this purpose WeaponCore exposes three methods:
  1. GetAllCoreWeapons
  2. GetAllCoreStaticLaunchers
  3. GetAllCoreTurrets
They all output a List of DefinitionIds into a inputed Collection of DefinitionIds. (In most cases an empty List made by you)
DefinitionId
As far as I understand it, (with my limited knowledge about this game's API) the DefinitionId of a block is more or less a blueprint for the game, which it uses to create the blocks on initializion.
DefinitionIds however aren't IMyTerminalBlocks and can't be converted to them. Some of you might know that blocks have the method BlockDefinition which returns the SerializeableDefinitionId of the block. This can't be converted to DefinitionId, though. However, both save the SubtypeId of the block!
(They also save the TypeId, that would be to generic though.)

Here is my approach to the problem, if you find any faster/less complicated way, please let me know.

List<IMyTerminalBlock> TURRETS = new List<IMyTerminalBlock>(); List<MyDefinitionId> TEMP_TUR = new List<MyDefinitionId>(); api.GetAllCoreTurrets(TEMP_TUR); List<string> definitionSubIds = new List<string>(); TEMP_TUR.ForEach(d => definitionSubIds.Add(d.SubtypeName)); GridTerminalSystem.GetBlocksOfType<IMyTerminalBlock>(TURRETS, b => b.CubeGrid == Me.CubeGrid && definitionSubIds.Contains(b.BlockDefinition.SubtypeName));

This has been by far the most confusing part of the API I've been working with and because of that, has also been part of the reason why I wanted to make this guide, so nobody else will have to dig through this.
The methods (Part 2: Electric Boogaloo)
This part contains some methods I haven't used so far and can only make educated guesses looking at the code in ApiBackend.cs. Feel free to correct me, if you know more about it.

GetBlockWeaponMap
Input: a weapon as IMyTerminalBlock, a Dictionary with string keys and int values as output
Looking at the code it appears the method will return a Dictionary of all weapons of that type with their names as keys and their weaponId stored.
Returns false, if the weapon was no WeaponCore weapon, returns true otherwise.

GetProjectilesLockedOn
Input: a targetId as long, preferably enemy gird
Returns a Tuple consisting of three values:
  1. whether or not there is a GridAi on the grid of the ProgrammableBlock (bool)
  2. how many projectiles have locked on (int)
  3. since when they're active (int)
If either the entity with the targetId was no grid or no GridAi was found the method returns a Tuple with the values false,0,-1.

GetSortedThreats
Input: pbBlock as IMyTerminalBlock, a Dictionary with MyDetectedEntityInfo keys and float as output
Returns a sorted List of targetable grids, sorted by their threat level. NEW FINDINGS 04/09/20 More info below! The dictionary is sorted from highest threat to lowest.

The floats in the dictionary represent the offenseRating of the grid. The offenseRating is an in code variable used to calculate the threat level, as follows:
offenseRating of 5 or higher -> threat level 9
offenseRating of 4 to 5 -> threat level 8
offenseRating of 3 to 4 -> threat level 7
offenseRating of 2 to 3 -> threat level 6
offenseRating of 1 to 2 -> threat level 5
offenseRating of 0.5 to 1 -> threat level 4
offenseRating of 0.25 to 0.5 -> threat level 3
offenseRating of 0.125 to 0.25 -> threat level 2
offenseRating of 0.0625 to 0.125 -> threat level 1
offenseRating of 0 to 0.0625 -> threat level 0
(In programming you start counting from 0 -> 0=1 on the HUD, 1=2, etc.)
All of these get a bonus based on the relative shield strength.
if both ships have shields:
if the enemy has stronger shields add 1
else subtract 1
if only the enemy has a shield add 1
if only you have a shield subtract 1
else add nothing.
To calculate the shield bonus you have to also interface with its API. (I might also add a guide for that if I have the time)
The methods (Part 3: AI Focus and Weapon Targets)
These methods are quite straight forward so there shouldn't be any major errors.

GetAiFocus
Inputs: shooterId as long, priority as int defaults to 0
Returns the target as MyDetectedEntityInfo with the given priority of the GridAi of the shooter. Returns null, if either the shooter is no grid or has no GridAi.

SetAiFocus
Inputs: pbBlock as IMyTerminalBlock, targeId as long, priority as int defaults to 0
Sets the target with the given priority of the GridAi of the grid of the PB to the specified target.
Returns true, if reassignment was successful, returns false otherwise.

GetWeaponTarget
Inputs: a weapon as IMyTerminalBlock, a weaponId as int defaults to 0
Returns the target of the entered weapon as MyDetectedEntityInfo. Returns null, if the target is fake(?), a projectile, the weapon is no WeaponCore weapon or the weaponId exceeds the amount of weapons on the block. Seems to always return null for static weapons.

SetWeaponTarget
Inputs: a weapon as IMyTerminalBlock, a targetId as long, a weaponId as int defaults to 0
Sets the target of the specified weapon to the target specified.
No return value. (Possible: Later change to true on success and false otherwise)
The methods (Part 4: Shoot!)
This section contains the methods you need to make the weapons fire.

FireWeaponOnce
Inputs: a weapon as IMyTerminalBlock, allWeapons as bool defaults to true, a weaponId as int defaults to 0
If possible this will make the specified weapon fire once. If allWeapons is true, the code will ignore the weaponId and fire all weapons on the block.
No return value.

ToggleWeaponFire
Inputs: a weapon as IMyTerminalBlock, on as bool, allWeapons as bool defaults to true, a weaponId as int defaults to 0
If possible sets the specified weapon to autofire depending on the value of on. (true->autofires/
false->no autofire) If allWeapons is true, the code will ignore the weaponId and set all weapons to the specified value of on.
No return value.

IsWeaponReadyToFire
Inputs: a weapon as IMyTerminalBlock, a weaponId as int defaults to 0, anyWeaponReady as bool defaults to true, shotReady as bool defaults to false appears to go unused(?)
Returns whether or not the specified weapon is ready. If anyWeaponReady is true, the code will ignore the weaponId and will return true, if any shot can be fired.
The methods (Part 5: Targeting)
This section contains methods that tell us about what the turret is able to target.

GetMaxWeaponRange
Inputs: a weapon as IMyTerminalBlock, a weaponId as int
Returns the maxRange of the weapon as float. Returns 0f, if the weapon is no WeaponCore weapon or the weaponId is invalid.

GetTurretTargetTypes
Inputs: a weapon as IMyTerminalBlock, a Collection with input type string as output, a weaponId defaults to 0
Stores the types of targets the turret will target i.e. Grids, Projectiles, Players etc. into the Collection
Returns false, if the weapon isn't a WeaponCore weapon or the weaponId is invalid, returns true otherwise.

SetTurretTargetTypes
Inputs: a weapon as IMyTerminalBlock, a Collection with input type string, a weaponId defaults to 0
It's ... empty?
No return value

SetBlockTrackingRange
Inputs: a weapon as IMyTerminalBlock, the desired range as float
Sets the targeting range of the weapon, if it's a WeaponCore weapon, to the given range or the maxRange of the weapon, if the range is bigger than the maxRange.
No return value

IsTargetAligned
Inputs: a weapon as IMyTerminalBlock, the targetId as long, a weaponId
Returns true, if the non-null target can be shot at (?) and the weaponId is valid. Returns false otherwise.

IsTargetAlignedExtended
Inputs: a weapon as IMyTerminalBlock, the targetId as long, a weaponId
Returns a MyTuple<bool,Vector3D>. the boolean matches the return value of IsTargetAligned, the Vector is the position of the target. If IsTargetAligned fails this will return <false,null>

CanShootTarget
Inputs: a weapon as IMyTerminalBlock, the targetId as long, a weaponId
Like IsTargetAligned, but takes target velocity and acceleration into account.

GetPredictedTargetPosition
Inputs: a weapon as IMyTerminalBlock, the targetId as long, a weaponId
Returns the position isTargetAligned calculates. If IsTargetAligned would return false, the code returns null instead.
The methods (Part 6: Utility Functions)
Some heat and power funtions. Some seem to be broken right now.

GetHeatLevel
Inputs: a weapon as IMyTerminalBlock
Should return the current heat of the weapon as float, returns 0f, if the weapon can't heat up, the weapon is no WeaponCore weapon, or just always, as it is currently broken. (literally "//fix me" at the return value)

I might be wrong on the following two. //fix me

GetCurrentPower
Inputs: a weapon as IMyTerminalBlock
Returns the power currently drawn by this weapon, returns 0f, if the weapon isn't a WeaponCore weapon.

GetMaxPower
Inputs: a weapon as MyDefinitionId
Returns the max. amount of power a weapon of this type can draw. Seems to be broken as it always returns 0f.
The methods (Part 7: Utility Functions 2)
This section contains functions which control some minor things, give general informations or help avoiding null pointer exceptions.

HasGridAi
Inputs: entityId as long
Returns true, if the grid has a GridAi. Returns false otherwise.

HasCoreWeapon
Inputs: a weapon as IMyTerminalBlock
Returns true, if the block is actually a weapon(?). Returns false otherwise.

GetOptimalDps
Inputs: entityId as long
Returns the peakDps of the block, if entity is a block, else returns the optimalDps of the grid, as a float. If the entity is neither a grid nor a WeaponCore weapon, the code will return 0f instead.

GetActiveAmmo
Inputs: a weapon as IMyTerminalBlock, a weaponId as int
Returns the name of the active ammo in the specified weapon as string. If the block isn't a WeaponCore weapon or the weaponId is invalid, the code will return null.

SetActiveAmmo
Inputs: a weapon as IMyTerminalBlock, a weaponId as int, a ammoName as string
Sets the ammo of the specified weapon, if the weapon is a WeaponCore weapon and the weaponId is valid.
No return value.

RegisterProjectileAddedCallback
Inputs: a callback as Action with input types Vector3 and float
The Vector3 apears to be the location of the projectile, while the float value seems to be its health
Currently not working.
No return value.

UnRegisterProjectileAddedCallback
Inputs: a callback as Action with input types Vector3 and float
The Vector3 apears to be the location of the projectile, while the float value seems to be its health, throws an exception, if the callback wasn't registered before.
Currently not working.
No return value.

GetConstructEffectiveDps
Inputs: entityId as long
Returns the effectiveDps of the grid, as a float. If the entity isn't a grid or has no GridAi, the code will return 0f instead.
The methods (Part 8: Recent Additions)
This section contains methods that were recently requested and made it into the API already.
(Sorry Mike Dude)

GetPlayerController
Inputs: a weapon as IMyTerminalBlock
Returns the Id of the player currently controlling the weapon as a long. Returns -1, if the weapon isn't a WeaponCore weapon or is uncontrolled.

GetWeaponAzimuthMatrix
Inputs: a weapon as IMyTerminalBlock, a weaponId as int
Returns the rotation matrix of the specified weapons azimuth part, returns the zero matrix if no azimuth part is availabe or the weapon isn't a WeaponCore weapon.

GetWeaponElevationMatrix
Inputs: a weapon as IMyTerminalBlock, a weaponId as int
Returns the rotation matrix of the specified weapons elevation part, returns the zero matrix if no elevation part is availabe or the weapon isn't a WeaponCore weapon.

IsTargetAlignedExtended
See Part 5: Targeting

IsTargetValid
Inputs: a weapon as IMyTerminalBlock, a target as IMyEntity, onlyThreats as a bool, checkRelations as a bool
Returns true if the target is valid. the function can have the additional conditions for the entity being a threat (offenseRating>0) and/or it being in a hostile (or selected to target) owner relationship using the respective booleans

GetWeaponScope
Inputs: a weapon as IMyTerminalBlock, a weaponId as int
Returns a tuple with the position and direction of the selected scope

IsInRange
Inputs: a weapon as IMyTerminalBlock
Returns a tuple two bools representing whether a threat and whether "other" (couldn't find where this flag is set) is in range
The methods (Part 9: Current Requests)
I can't guarantee I'll be able to keep this up to date all the time, but for what it's worth: current requested additions. (Actual input lists are only my speculation)

GetTargetOrientationMatrix
Inputs: target as IMyEntity
Returns the rotation matrix of the target ship, probably returns the zero matrix if the target is no grid.
Recent Breaks
This is a collection of breaking API changes since the API was functional. The purpose of this is to make it easier for others in the future to fix breaks when the script hasn't been updated in a while. This list may not be complete at any given time as I might not notice an API break immediately.

27th of July 2020 - Fixed Pb API
Changed
public float GetPlayerController(IMyTerminalBlock weapon) => _getPlayerController?.Invoke(weapon) ?? 0;
to
public long GetPlayerController(IMyTerminalBlock weapon) => _getPlayerController?.Invoke(weapon) ?? -1;

Changed
Func<IMyTerminalBlock, float> _getPlayerController;
to
private Func<IMyTerminalBlock, long> _getPlayerController;

14th of August 2020 - Fixed Pb API issue with get sorted targets
Changed
GetSortedThreats(IMyEntity shooter, ICollection<MyTuple<IMyEntity, float>> collection)
to
GetSortedThreats(IMyEntity shooter, IDictionary<IMyEntity, float> collection)

Changed
Action<IMyEntity, ICollection<MyTuple<IMyEntity, float> >_getSortedThreats;
to
Action<IMyEntity, IDictionary<IMyEntity, float>> _getSortedThreats;

11th of October 2020 - Fixed Pb API issue
Changed
Func<IMyEntity, int, IMyEntity> _getAiFocus;
to
Func<IMyEntity, int, MyDetectedEntityInfo> _getAiFocus;

Changed
IMyEntity GetAiFocus(IMyEntity shooter, int priority = 0)
to
MyDetectedEntityInfo? GetAiFocus(VRage.Game.ModAPI.Ingame.IMyEntity shooter, int priority = 0)

Changed
Func<IMyTerminalBlock, int, IMyEntity> _getWeaponTarget;
to
Func<IMyTerminalBlock, int, MyEntityDetectedEntityInfo> _getWeaponTarget;

Changed
IMyEntity GetWeaponTarget(IMyTerminalBlock weapon, int weaponId = 0)
to
MyDetectedEntityInfo? GetWeaponTarget(Sandbox.ModAPI.Ingame.IMyTerminalBlock weapon, int weaponId = 0)

13th of October 2020 - Converted all PB API IMyEntity method calls to use and return EntityId longs instead of IMyEntity references... this was required for security reasons... sorry for the breakage.
DarkStars changelog above pretty much sums it up.
And that's it
As this is my first guide (and a big one at that), any constructive feedback is appreciated! I hope this helps some of you to write new scripts or update old ones and have fun with it!

Sorry for my spelling and weird grammar, I'm no native speaker and have some stupid manners of speaking. If you don't understand something, tell me in the comments, and I'll try and fix it. If you need to contact me, don't send me a friend request, contact me via comments first!

My Workshop
https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=2197435489

A seperate repository only including the APIs[github.com]

Support Darkstar (Patreon)[www.patreon.com]

IMPORTANT NOTE
Don't use this to harass scripters to update their script, it's their personal decision whether they want to deal with the problems of a potential changing API or not! If I find anyone doing this they will be reported ASAP. Scripters are just human beings as you and I!


21 Comments
Hobnob 15 Apr, 2024 @ 9:42pm 
Your method for locating all weapons on a grid was returning strange results for me so I did some digging and I think i've come up with a simpler solution:

List<MyDefinitionId> wepDefs = new List<MyDefinitionId>();
api.GetAllCoreWeapons(wepDefs);
List<IMyTerminalBlock> weapons = new List<IMyTerminalBlock>();
GridTerminalSystem.GetBlocksOfType<IMyTerminalBlock>(weapons, b => wepDefs.Contains(b.BlockDefinition));
Shroopy 19 Dec, 2023 @ 8:50pm 
Is GetAiFocus actually for Grid AI? The weaponcore discord says that it's only for manually locked targets, which is very confusing
Sigmund Froid  [author] 3 Aug, 2023 @ 6:35am 
I see... I think I prefer keeping the old link for now as ApiBackend is already out of sync for the two repositories and the files in the main repo should match the current released build or additions that will be included in the next. If I would know that the dedicated repository would receive all updates asap I might switch but the updates of the last month are already not included. I will add it as a note though for future reference.
Aristeas 2 Aug, 2023 @ 8:47pm 
That one goes to the WeaponCore repo, which has the exact same file... because reasons
Sigmund Froid  [author] 2 Aug, 2023 @ 6:03pm 
@Aristeas This guide does already link to that git repo in the second chapter.
Aristeas 2 Aug, 2023 @ 12:03pm 
Looks like Darkstar moved the API into a separate git repo, found here:
https://github.com/sstixrud/CoreSystems/
SentinelX101 4 Oct, 2022 @ 5:05pm 
Thanks for the guide, very useful!
RakkenTi 13 Jul, 2022 @ 11:22am 
Thanks
Sigmund Froid  [author] 13 Jul, 2022 @ 2:31am 
Thanks for the reminder! The links should be up-to-date again.
RakkenTi 12 Jul, 2022 @ 11:07pm 
Hey whats the updated github link?