Space Engineers

Space Engineers

Not enough ratings
Block Scripting Language (BSL)
By Math
Documentation on the BSL language for the AnimationCore mod
   
Award
Favorite
Favorited
Unfavorite
UPDATE
Update please dont use this guide for anything but legacy scripts
To get to the new WIKI go to the github wiki

Link[github.com]
About
BSL is a scripting language created for modders to allow people who may have little to no experience coding C# create fully realized animated blocks.


Why its own language?
because I've never made a language before so I wanted to try to, even if its simple


This guide will familiarize and teach you how to use the scripting to its fullest

Terminology
This guide will use very basic programming terms, if you have never programmed anything before it would be best to understand the meaning of these terms.

INT:
- INT or integer is a whole number {0, 1, 2, 3}

FLOAT:
- FLOAT is a Rational number {-2.5, 0.0, 1.2, 4.4}
- When a method requires a float the number MUST have a decimal place in it

STRING:
- STRING is a block of text surrounded by " "
- Declaring a string is as simple as "Example String"

BOOL:
- BOOL is True or False

VECTOR:
- VECTOR is a 3D point that can represent movement or axis
- Format is [x, y, z] it can consist of INTs or FLOATs { [1, 2, 1.2], [0, 1, 0], [1.1, 2, 0.1] }

FUNCTION:
- Used to describe a certain set of instructions

KEYWORD:
- A keyword exactly how it sounds. A key word, aka a special word
- Keywords MUST be alphanumeric and have no spaces { a-z _ }

The Body:
Whenever I refer to "the body" of something I am referring to something contained usually inside two curly brackets, such as { }
Getting started
This is not a guide on how to create modded blocks, so from here forth I will assume you have a working block mod.

To get started all your animation scripts will be located in the Data/Animations folder.

If you have mod that only needs one block animated then in the Animations folder create a file called Main.bsl, this is all you will need to start.


If you have a mod that need multiple blocks animated then in the Animations folder create a file called Main.info, this file will contain the paths to the other scripts. see example below

Data/Animation/
- main.info
- block1.bsl
- block2.bsl

main.info
Animation block1
Animation block2


See example mod for file layout
- first subscribe to Animated-Cockpits and let steam download it
- then navigate to folder 'C:\Program Files (x86)\Steam\steamapps\workshop\content\244850\2900331965\Data\Animation' (or wherever you have steam installed to)
Starting the script
A script can be broken down into 4 basic components

The Header
- headers contain information about the script

The Objects
- objects are your subparts on your block

The Functions
- functions are little bits of code that can help you do things faster

The Actions
- this is where the meat of the script lies

Below sections will cover each of these in more detail.
The Header
The header of the script contains information for the mod to determine what block to use this script on.

All scripts require these two headers
@BlockID STRING @Version 1

Version is always 1, for now at least. this is just some futureproofing.

Optional headers include
@Author STRING

An example of a header would be
@Author "Math" @BlockID "large_halfblock_sentry_turret" @Version 1


That's all there is to headers, its quite trivial
The Objects
Objects are subparts or dummys that have special attributes.

Declaring objects is done below the header, the format for objects consists of
using KEYWORD as OBJECT()

the KEYWORD is the name of the subpart or dummy name, if it is a subpart such as subpart_xyz then the name of the object would JUST be xyz. The result would be using xyz as Subpart(). if it is a dummy this does not apply and you will use the entire name, this is unless you are using a keen interactive name such as conveyor_xyz then the dummy name is just xyz

Valid objects and their declaration formats are listed below
    Subpart()
  • nothing more to do
    Button(STRING)
  • STRING: the interaction dummy name
    Lever(STRING, VECTOR, INT, FLOAT)
  • STRING: the interaction dummy name
  • VECTOR: the axis to rotate the lever on
  • INT: the time it takes to visually move
  • FLOAT: the angle to rotate the lever
    Emissive(STRING)
  • STRING: Emissive material name
    Emitter(STRING)
  • STRING: dummy name

An example would be the following
using barrel as Subpart() using btnEnable as Button("btnDummy") using lvrOnOff as Lever("lvrDummy", [0, 1, 0], 10, 179.0) using emissive1 as Emissive("Emissive1") using emit1 as Emitter("muzzleFlash01")

The Functions
Functions are short bits of named code sections that you can call to do work easier

Functions are very simple to declare the format is as follows
func KEYWORD() { }

Caveats:
A function must have exactly one { and one } in that order.
Two functions cannot have the same name nor can a function call itself.


An example of a function would be
func OpenGate() { ResetGateFunc() panel_01.translate([-1, 0, 0], 20, cubic) panel_02.translate([1, 0, 0], 20, cubic) }
The Actions
Actions are bits of code that are triggered when certain things happen. This can be anything from player proximity, to idle loops.

They may look intimidating but can also be broken down into two parts. The parent and the children. The formatting is simple but I will refer to the outer part as the parent and the inner part as the children.


Actions are by far the most complex syntax wise, the parent format is declared in the following format
action KEYWORD(...) { }

Inside the body of the action you define smaller events, the children format is the following.
KEYWORD(...) { }

When making a full action the result will look something like this
action KEYWORD(...) { KEYWORD(...) { } KEYWORD(...) { } }

... represents possible input

To see the full list of Actions please see the section called Action Lexicon

An example of an action would be
action BlockAction() { Create() { } Working() { } WorkingLoop(200) { } NotWorking() { } }
Interacting with Objects
When interacting with objects you can break it down into two parts, the object and the calls.
Think of a line to ride an amusement park ride. The ride is the object and the people in line are the calls.


When making a object call the format will be the following
OBJECT.KEYWORD(...).KEYWORD(...)...

Object in this case represents the keyword that you declared
An example of moving a subpart would be the following
using part01 as Subpart() func MoveRight() { part01.transform([1, 0, 0], 10, Cubic) }

Much like the line to the amusement park calls can be behind one another.
Using the example from above we can also declare
func MoveLeftAndRotate() { part01.transform([-1, 0, 0], 10, Cubic).rotate([0, 1, 0], -30.0, 10, Cubic) }

There are many different calls, each one is specific and has to follow a certain format.
For all the formats please see the section on Object Lexicon



The last most important part about object calls is the Delay(INT) call.
The delay call is a unique call that will delay all methods infront of it by INT ticks.
This is a essential call to making good looking animations.
Explaining how the delay is applied might get a bit confusing, below is a basic visual on how the delay call works. This is using the previous example but now with added delays.

What this means is that after 20 ticks the rotate function will be executed,
then after 10 more ticks (30 ticks total) the transform function will be executed.

Reminder that 60 ticks are in 1 second

Action Lexicon
All parent and child names must be the same as below.

When declaring a action you do not need to declare each child. you can pick and choose what children you need.

    BlockAction()
  • Create()
  • Build()
  • Working()
  • WorkingLoop(INT) #loop time in ticks
  • NotWorking()
    ButtonAction(KEYWORD) #button object name
  • PressedOn()
  • PressedOff()
  • Pressed()
    LeverAction(KEYWORD) #lever object name
  • SwitchedOn()
  • SwitchedOff()
  • Switched()
    ProductionAction()
  • StartProducing()
  • ProducingLoop(INT) #loop time in ticks
  • StopProducing()
    DistanceAction(FLOAT) #distance in meters from center of block
  • Arrive()
  • Leave()

Block specific actions
    DoorAction()
  • Open()
  • Close()
    CockpitAction()
  • Enter()
  • Exit()
    LandingGearAction
  • Lock()
  • UnLock()
  • ReadyLock()
Function Lexicon
Below is the calls that you can call on certain types of Object

    Block.
  • log(STRING)
  • poweron()
  • poweroff()
  • togglePower()
  • Translate(...) #see Subpart.Translate
  • Rotate(...) #see Subpart.Rotate
  • Spin(...) #see Subpart.Spin
  • Vibrate(...) #see Subpart.Vibrate
  • RotateAround(...) #see Subpart.RotateAround
    (If block is a door \/)
  • openDoor()
  • closeDoor()
  • toggleDoor()
    (If block is a landingGear \/)
  • lockOn()
  • lockOff()
  • toggleLock()
    (If block is a cockpit \/)
  • pilotTranslate(...) #see Subpart.Translate
  • pilotRotate(...) #see Subpart.Rotate
  • pilotSpin(...) #see Subpart.Spin
  • pilotVibrate(...) #see Subpart.Vibrate
  • pilotRotateAround(...) #see Subpart.RotateAround

    Subpart.
  • reset()
  • resetPos()
  • setResetPos()
  • rotateAround(VECTOR, VECTOR, FLOAT, INT, LERP) #axis, point, angle, time, lerp
  • translate(VECTOR, INT, LERP) #amount, time, lerp
  • rotate(VECTOR, FLOAT, INT, LERP) #axis, angle, time, lerp
  • spin(VECTOR, FLOAT, INT) #axis, speed, time
  • vibrate(FLOAT, INT) #scale, time
  • scale(FLOAT) #scale
  • setVisible(BOOL) #visible true/false

    Button/Lever.
  • enabled(BOOL) #true/false
  • interactable(BOOL) #true/false

    Emissive.
  • setColor(INT, INT, INT, FLOAT) #r, g, b, brightness

    Emitter.
  • playparticle(STRING, FLOAT, FLOAT, VECTOR, INT, INT, INT) #name, scale, life, velocity, r, g, b
  • playparticle(STRING, FLOAT, FLOAT, VECTOR) #name, scale, life, velocity
  • playparticle(STRING, FLOAT, FLOAT) #name, scale, life
  • stopparticle()
  • playsound(STRING) #soundID
  • stopsound()

    Light.
  • setcolor(INT, INT, INT) #r, g, b
  • lighton()
  • lightoff()
  • togglelight()
Lerp Lexicon
Below are the possible lerps that you can use

To better see them visually visit this website[easings.net]
  • Instant
  • Linear

  • InBack
  • OutBack
  • InOutBack

  • InBounce
  • OutBounce
  • InOutBounce

  • InElastic
  • OutElastic
  • InOutElastic

  • InSine
  • OutSine
  • InOutSine

  • InQuad
  • OutQuad
  • InOutQuad

  • InCubic
  • OutCubic
  • InOutCubic

  • InQuart
  • OutQuart
  • InOutQuart

  • InQuint
  • OutQuint
  • InOutQuint

  • InExpo
  • OutExpo
  • InOutExpo

  • InCirc
  • OutCirc
  • InOutCirc
Full Example
@Author Math @BlockID testing @Version 1 using dummy as Subpart using dummy01 as Subpart using dummy02 as Subpart func SpinParts() { dummy.spin([0, 1, 0], 1.0, 200) dummy01.spin([0, 1, 0], -0.8, 200) dummy02.spin([0, 1, 0], -0.8, 200) } action BlockAction() { Create() { #this subpart was rotated strangly, re-orientate it dummy01.translate([1, 0, 0], 0, Instant).rotate([0, 0, 1], -36.0, 0, Instant) } WorkingLoop(200) { SpinParts() dummy02.translate([0, 1.5, 0], 50, InOutExpo) dummy02.translate([0, -3, 0], 100, InOutExpo).delay(50) dummy02.translate([0, 1.5, 0], 50, InOutExpo).delay(150) dummy01.translate([0, -1.5, 0], 50, InOutExpo) dummy01.translate([0, 3, 0], 100, InOutExpo).delay(50) dummy01.translate([0, -1.5, 0], 50, InOutExpo).delay(150) } }
5 Comments
Uzar 28 Feb, 2023 @ 6:32am 
Really hot stuff. One question: Is there an option to use power usage for an action?
A specific use case: Steam turbine shaft connected to a ship propeller through transmission gears. Several parts, all different spin speeds, all scaled with power usage
Math  [author] 1 Jan, 2023 @ 7:30pm 
when reloading the world changes will apply
Rusted Droid 30 Dec, 2022 @ 1:10pm 
Looks like fun.
Do changes apply only by restarting game?
Akiad 31 Oct, 2022 @ 7:50pm 
This is amazing!
Chipstix213 27 Oct, 2022 @ 2:04pm 
BSL is also British signing language 😂