Functions, custom placeholders and string conditions - Membercat-Studios/Streamlabs-Integration GitHub Wiki
After a while of using the plugin, your config will get quite complex and hard to maintain. To help you organize it better, the plugin provides a few small features that can save you a lot of time when used correctly.
When using conditions, you might want to create some quite complex logic using nested condition groups. This can rather quickly result in longer config sections, which is why you can use string conditions to write complex expressions in one line. Let's take this example:
conditions:
- "{amount}>100"
- mode: OR
conditions:
- "{message}.>creeper"
- "{message}.>aw man"
Using string conditions, we can "compress" it into only two lines:
conditions:
- "[{amount}>100|({message}.>creeper|{message}.>aw man)]"
The brackets seen above are called condition groups. You can imagine them like the conditions:
property in the first example. A condition group can have two different modes, either AND
or OR
, determining how conditions will be checked. You can create an AND
group by using the []
brackets, and an OR
group by using the ()
brackets.
Conditions inside groups can be separated using |
, and condition groups can contain other condition groups, which allows our example from above to be recreated in a string condition.
Conditions are essential to limit execution on an action, but there are certain scenarios where you might want to have the same or similar logic on multiple actions. Let's say that we have a few actions, spawning mobs that viewers can specify in their donations:
spawn_zombie:
enabled: true
action: twitch_bits
conditions:
- '{message}.>zombie'
steps:
- message: '[title]<red>Spawning zombie...'
- command: 'execute at {player} run summon zombie ~ ~1 ~'
spawn_skeleton:
enabled: true
action: twitch_bits
conditions:
- '{message}.>skeleton'
steps:
- message: '[title]<red>Spawning skeleton...'
- command: 'execute at {player} run summon skeleton ~ ~1 ~'
spawn_wither:
enabled: true
action: twitch_bits
conditions:
- '{message}.>wither'
steps:
- message: '[title]<red>Spawning wither...'
- command: 'execute at {player} run summon wither ~ ~1 ~'
You can see that we have to repeat a lot, which is going to make it harder to change things like the message displayed, or the position the mob will spawn at. The idea behind custom placeholders is that you can create your own placeholders that change based on conditions. Using a custom placeholder, we can simplify all of this to one action:
actions:
spawn_mob:
enabled: true
action: twitch_bits
steps:
- message: '[title]<red>Spawning {!mob_from_message}...'
- command: 'execute at {player} run summon {!mob_from_message} ~ ~1 ~'
custom_placeholders:
mob_from_message:
default_value: 'zombie'
skeleton:
conditions:
- '{message}.>skeleton'
wither:
value: 'wither'
conditions:
- '{message}.>wither'
You can see that our custom placeholder mob_from_message
is defined in the custom_placeholders
property. Custom placeholders can have different state-based values, which are all of the values your placeholder can have. A value works just like a condition group, meaning it can have conditions, donation conditions and different modes determining if it should be used. If all conditions of a value match, the placeholder will return the name of that value.
If you want to return a string containing characters that you can't use in a yaml property name (like :
), you can use the value
property of the to override the return value (example in the wither
value above). A custom placeholder can also have a default value, which is used if none of the state-based values have matching conditions. A good thing to keep in mind as well is that the values are processed from top to bottom, meaning if the skeleton
and the wither
conditions in our example are both true, skeleton
will be used. The default value will always be used last, regardless of where it is placed.
Custom placeholders act just like any other placeholder, but look slightly different: {!my_placeholder}
(the exclamation mark differentiates them from normal placeholders). You can also use custom placeholders in the result or conditions of another custom placeholder, allowing for more complex structures.
Functions are a common concept in programming, since they allow for the creation of dynamically reusable blocks of code, meaning the same block of code can be used from different places in the program, with different options that alter the behavior of the code block. Those "options" are called parameters, which are given to the function when it's being called (executed), and can then be used anywhere in the function.
Our plugin implements functions as a separate list of steps that can use special parameter placeholders. This is how a function could look inside of a configuration file:
functions:
an_example_function:
steps:
- message: "It works!"
- message: "<red>Killing {$$player_to_kill}"
- command: "kill {$$player_to_kill}"
You'll notice that the steps of our example function use a new type of placeholder, looking like this: {$$placeholder}
. This is our special parameter placeholder, which is only available within the function. Now, how do we actually call this function and give it parameters?
actions:
test_action:
...
steps:
- function: an_example_function
parameters:
player_to_kill: "YourMinecraftUsername"
In this example, we're calling our example function from within an action using the function
step, and setting the player_to_kill
parameter to "YourMinecraftUsername". This means that the {$$player_to_kill}
placeholder will be replaced with "YourMinecraftUsername" within the function.
You might be wondering how and when functions can be used to save time and create "cleaner" configurations. We'll be going through a small example use case that'll help you understand exactly that:
actions:
gifted_memberships_message:
enabled: true
action: youtube_gift_memberships
steps:
- message:
- "<green>-----------------------------------------------------"
- ""
- ""
- "<gray><aqua>{user}</aqua>just gifted <red>{amount}</red> memberships, thank you :D!"
- ""
- ""
- "<green>-----------------------------------------------------"
- command: "execute as @a at @s run playsound minecraft:entity.player.levelup master @s ~ ~ ~ 100 1"
bits_message:
enabled: true
action: twitch_bits
steps:
- message:
- "<green>-----------------------------------------------------"
- ""
- ""
- "<gray><aqua>{user}</aqua>just donated <red>{amount}</red> bits, spawning zombies!"
- ""
- ""
- "<green>-----------------------------------------------------"
- command: "execute as @a at @s run playsound minecraft:entity.player.levelup master @s ~ ~ ~ 100 1"
- repeat:
- command: "execute at {player} run summon zombie ~ ~ ~"
amount: "{amount}"
server_thread: true
raid_message:
enabled: true
action: twitch_raid
steps:
- message:
- "<green>-----------------------------------------------------"
- ""
- ""
- "<gray><aqua>{user}</aqua>just raided the stream with <red>{amount}</red> viewers!"
- ""
- ""
- "<green>-----------------------------------------------------"
- command: "execute as @a at @s run playsound minecraft:entity.player.levelup master @s ~ ~ ~ 100 1"
In the example, we're responding to 3 different events: Bits on Twitch, gifted memberships on YouTube and raids on Twitch. For each of them, we're sending a message and playing a sound. For the bits, we're also spawning zombies for the amount of bits gifted. As you can see, the biggest part of the entire configuration is sending the message and playing the sound on every action.
Using functions, we can put all of the message & sound functionality in a separate function, and control the message displayed using parameters. Here's how our example would look with functions:
functions:
donation_effect:
steps:
- message:
- "<green>-----------------------------------------------------"
- ""
- ""
- "{$$message}"
- ""
- ""
- "<green>-----------------------------------------------------"
- command: "execute as @a at @s run playsound minecraft:entity.player.levelup master @s ~ ~ ~ 100 1"
actions:
gifted_memberships_message:
enabled: true
action: youtube_gift_memberships
steps:
- function: donation_effect
parameters:
message: "<gray><aqua>{user}</aqua>just gifted <red>{amount}</red> memberships, thank you :D!"
bits_message:
enabled: true
action: twitch_bits
steps:
- function: donation_effect
parameters:
message: "<gray><aqua>{user}</aqua>just donated <red>{amount}</red> bits, spawning zombies!"
- repeat:
- command: "execute at {player} run summon zombie ~ ~ ~"
amount: "{amount}"
server_thread: true
raid_message:
enabled: true
action: twitch_raid
steps:
- function: donation_effect
parameters:
message: "<gray><aqua>{user}</aqua>just raided the stream with <red>{amount}</red> viewers!"
As you can see, we'll have to repeat much less configuration, which allows us to save time, and can also help avoid "copy-paste-mistakes" like forgetting to change something in a block of steps that was copied to 5 different locations, since you'll only have to modify the configuration in one place. You might be able to see how functions work similar to custom placeholders, both can help avoid repetitive configuration.
The impact of adding a function in the example above might not seem that big, since it doesn't change the configuration size drastically, but this will become much more clear in a bigger configuration. On top of that, the added benefit of not having to repeat configuration can save you a lot of time!
In the same way that parameter placeholders can only be used within a function, any other placeholder created within the function will only be accessible from that function. This does mean placeholders from outside the function can still be used, but it's recommended to use parameters instead, since they provide a more flexible approach of providing values to the function.
But outside of that, this does create one issue when working with functions: How can a function return a value to the action it was called from? Say we have a function that extracts some data from a minecraft command:
functions:
get_time:
steps:
- command: "time query {$$time_mode}"
output: "raw_time"
# We need to extract the time using an expression query, since the command output looks like this:
# The time is 6000
# ^^^^ (<- Our expression is matching this part of the output)
- expression: "\\d+" # Matches any number
input: "{$raw_time}"
output: "parsed_time"
We might use the function like this in some action:
- function: get_time
parameters:
time_mode: "daytime"
This would make the function execute /time query daytime
and parse the actual time number out of the result. Now you might be asking what the issue with this is. Say we now want to check for the time being higher than 13000
, meaning it is currently night:
- function: get_time
parameters:
time_mode: "daytime"
- check:
- message: "It is night!"
else:
- message: "It is day!"
conditions:
- "{$parsed_time}>13000" # https://minecraft.wiki/w/Daylight_cycle
Here, we're using the {$parsed_time}
placeholder from the expression
query in our function to check for the time. But remember how we can only use placeholders created inside the function in that function? Well, we're using the expression
query in that function, meaning we won't be able to access the placeholder it creates. What we need is some way of "transferring" the placeholder from the function to the place it's called from. This is where the output
of a function comes into place:
functions:
get_time:
steps:
...
- expression: "\\d+" # Matches any number
input: "{$raw_time}"
output: "parsed_time"
output: "{$parsed_time}"
This special property of a function will accept any placeholder from within the function, and "transfer" it to the step it was called from. That's great, but how do we now access the data from that step?
- function: get_time
parameters:
time_mode: "daytime"
output: "the_time"
As you can see, the function
step can optionally be used as a query, just like the command step. We can now grab the output of our get_time
function (which we previously set to the {$parsed_time}
placeholder within the function) as another placeholder, in this example {$the_time}
! Now let's insert that into our previous check, and our complete configuration now looks like this:
functions:
get_time:
steps:
- command: "time query {$$time_mode}"
output: "raw_time"
# We need to extract the time using an expression query, since the command output looks like this:
# The time is 6000
# ^^^^ (<- Our expression is matching this part of the output)
- expression: "\\d+" # Matches any number
input: "{$raw_time}"
output: "parsed_time"
output: "{$parsed_time}"
actions:
test_action:
...
steps:
- function: get_time
parameters:
time_mode: "daytime"
output: "the_time"
- check:
- message: "It is night!"
else:
- message: "It is day!"
conditions:
- "{$the_time}>13000" # https://minecraft.wiki/w/Daylight_cycle