Step 3: Scripting - morbidslinky/SOC GitHub Wiki
Scripting Page - Creating Custom Scripts For The Sideop
Scripting offers the most amount of customization in a sideop, by far. However, it also requires the most learning. If you are not interested in further customizing your sideop, or if you feel too intimidated by scripting out events, just click the "Build" Button in the bottom-right corner and continue to the next step. The default scripts will handle the logic for winning or failing a sideop, without any extra tinkering.
ih_log.txt OnMessage[]
The best way to learn about scripting is to first learn about the StrCode32 Message events that the game fires off during the sideop. These events can be logged and studied using Infinite Heaven. In the ACC, open the IH menu and navigate to "Debug menu >":
Enable the "Debug IH mode" and the "debugMessages" options:
Now, the event messages will be logged to ih_log.txt while you play the game. Anything marked with "OnMessage[]" is an event that was fired off in association with something happening in the game:
In this example, you'll see references to "Mission.OnTargetDeath()", "GameObject.Damage()", "GameObject.Down()" and "Player.PlayerHoldWeapon()". These are messages that were fired when a sideop target was killed, when an soldier was hurt or downed, and when the player readied a weapon. All of these events can be utilized to trigger a script for the sideop. Scripts like winning or failing the mission, or setting the weather, or triggering an alert- the possibilities are numerous.
Before triggering an event, however, let's break down the anatomy of one of these message signatures:
In this message:
OnMessage[]:
Marker.ChangeToEnable(
--[instanceName:str32:sol_quest_0003](/morbidslinky/SOC/wiki/instanceName:str32:sol_quest_0003) 2077341861,
--[markerType:str32:~](/morbidslinky/SOC/wiki/markerType:str32:~) 2884920305,
--[gameId:~:sol_quest_0003](/morbidslinky/SOC/wiki/gameId:~:sol_quest_0003) 1289,
--[markedBy:str32?:Player](/morbidslinky/SOC/wiki/markedBy:str32?:Player) 3087473413
)
Marker
is the message's "Class".ChangeToEnable
is the message itself.Sender
is typically the GameObject that sent the message, but it's not explicitly logged in the Infinite Heaven log. More on that later.--[instanceName:str32:sol_quest_0003](/morbidslinky/SOC/wiki/instanceName:str32:sol_quest_0003) 2077341861,
this is the first argument (arg1) that was sent by the message. The message sent2077341861
, and Infinite Heaven extrapolated that this was the instanceName of sol_quest_0003, formatted as an str32 number.--[markerType:str32:~](/morbidslinky/SOC/wiki/markerType:str32:~) 2884920305,
this is the second argument (arg2). 2884920305 is also an str32 number, but Infinite Heaven was not able to reverse the string (indicated by the ~).--[gameId:~:sol_quest_0003](/morbidslinky/SOC/wiki/gameId:~:sol_quest_0003) 1289,
is arg3, which is the GameObject ID for sol_quest_0003,1289
--[markedBy:str32?:Player](/morbidslinky/SOC/wiki/markedBy:str32?:Player) 3087473413
is arg4,3087473413
, which is "Player" as an str32 number.
The arguments are all numbers, but their formats are varied. Some of the argument types are strings that were converted into str32 numbers. Other are GameObject IDs. Others are just plain numbers. Infinite Heaven has documented these messages and their arguments in the InfLookup.lua
For example, this is the documentation for Marker.ChangeToEnable:
Using this documentation, we can plan an event to occur in the sideop when the game fires this message.
SOC Scripts
Returning to the Scripting Page, you'll notice that a nummber of scripts have already been generated:
These are 8 scripts (and one
Total_Targets
variable) that SOC imported by default from the SOCassets\ScriptAssets\Script Library\Default.xml
file. These are used for checking the progress of the sideop as targets are extracted or killed.
By clicking on one of the scripts (a line with a bold font), you can see details about when it occurs, if it should occur, and what it do. Each script can be described as a When -> If -> Do
event. When a Trigger (the messages from ih_log.txt) is fired, the script will check Preconditions, if any. If all of the Preconditions are all met, the Operations are executed.
For this
Extract_Get_Total_Target_Count
script, The Trigger is mapped to Mission.OnTargetExtraction(). It contains no Preconditions, and the only operation listed is "Get Total Target Count". This means that, any time the Mission.OnTargetExtraction() message is fired, this script will execute the operation for "Get Total Target Count", since there are no preconditions stipulated.
SOC Scriptals and Scriptal Choices
Preconditions and Operations use the same "Scriptal" template system. Scriptals are piecemeal lua functions that serve as the building blocks for Scripts. You can inject custom values into these Scriptals, depending on the "Choice" options that the Scriptal template contains. These Choices are baked into the function, which is then baked into the script, which is then baked into the sideop.
Double-clicking on the "Get Total Target Count" operation will take you directly to the Operation Scriptal:
In this Scriptal menu, you can see the underlying lua code that the sideop will run, along with a Choice that can be filled in with a "Value". In this particular example, the choice has already been configured to the "Total_Targets" Custom Variable. In the Template Description, you'll notice the following:
Updates a variable to the number of total target objectives in the sideop.
Event Function:
|[1|NUMBER]| = qvars.GetTargetCount()
Provided qvars Definitions:
...
The Event Function is the operation's lua template: |[1|NUMBER]| = qvars.GetTargetCount()
. In particular, |[1|NUMBER]|
is the placeholder for the 1st Choice, and it expects a Number value type. More expectations for this Choice is described in the text boxes to the right of the description:
This particular Choice is "Number Variable". The description reads:
The variable to copy the table count to.
Template Restrictions:
Index|Type Safety: |[1|NUMBER]|
Allow User Edits: True
Allow User Vars: True
Allow Literals: False
Allow Value Sets:
|[1|NUMBER]|
refers back to the placeholder in the Event Function.Allow User Edits: True
means that you are able to make changes to this particular Choice. Some choices don't allow for user edits, for one reason or another.Allow User Vars: True
means that this choice can be filled by a Custom Variable. Custom Variables provide a way of storing values across scripts. In this case, the Total_Targets variable is storing the value fromqvars.GetTargetCount()
, which is a helper function for tallying all of the GameObjects that were marked as targets in previous Details Page.Allow Literals: False
means that this choice cannot be filled directly. Many Scriptals do allow for direct values, but in this case, the operation needs to store a number value, so a literal value wouldn't make sense.Allow Value Sets:
means that the Scriptal doesn't allow for any additional preset Value Sets. Some Scriptals have their own embedded value sets, to limit choices to specific values. More on that later.
Based on these restrictions, only Custom Variables are allowed to be set for this choice. As such, the placeholder is mapped to the Total_Targets variable, and the operation effectively boils down to Total_Targets = qvars.GetTargetCount()
Creating a New Script Event
Click on Add New Script Event
, under the Custom Scripts tree. A new event will be created, ready for configuring:
For this example, you'll have Kaz radio in to say something when any soldier is marked. Start by giving the script a name:
For the Trigger, use the Marker.ChangeToEnable() message from earlier. Set the StrCode32 to Marker
, and the Message to ChangeToEnable
:
Author's Note: The Sender Options is now set to
ANY / ALL
. TheSender
acts as a filter for some messages to denote what it was that "sent" the message. This is particularly useful for plaintext Timers and GeoTrap names. Marker.ChangeToEnable() will also allow for a plaintext Sender filter, so if you only wanted this Script to run for a specific soldier/hostage/etc., setting the Sender Options to the name of the game object will work. However, not all messages have senders that are in plaintext. Some are str32, some are GameObjectIds. I would recommend to only use Sender Options with Timers, Traps and Markers (unless you know what you're doing) until I can find a better way of utilizing the filter. The more reliable way to filter against specific game objects is to use the InfLookup arg documentation, and create a precondition for the arg rather than using the Sender.
Click on the Always True
Precondition. Under Template
, choose the Check GameObject Type
option:
The Template Description has changed, but the template will not be updated until you press Apply >>
. This will allow you to preview the templates without immediately changing the script. Click Apply >>
and two choices will appear in the Choices List: Type Check
and Value
.
In this Scriptal, the Type Check
determines which types of GameObjects will fulfill the precondition. The Value Set is locked to Type Check Functions
, since Allow User Vars
and Allow Literals
are both false. The values provided in the Type Check Functions
are all embedded in the Check GameObject Type
Scriptal Template, limiting your choices to only values that make sense in the context of the Scriptal. Select Tpp.IsSoldier:
Click on the second Choice, Value
in the Choice List, and the menu will update to display the Choice's details:
For this Choice, the Value Set options allow for literals and variables, but to check against the arguments that were supplied by the message, you should keep this set to the Event Default Arguments
Value set. Referring back to the InfLookup documentation, the 3rd argument from Marker.ChangeToEnable contained the GameId of the game object that was marked, so in order to check if the marked game object is a soldier, set the choice value to arg3:
Now, only soldiers will fulfill the preconditions for this script. Click on the Do Nothing
Operation next. There are a lot of operation templates to choose from, so finding "Play Support Radio Call" can be exhausting if the Template Category Filter is set to "All". Instead, set the category filter to "SOUND EFFECTS" to narrow down the search:
Select Play Support Radio Call
and hit Apply >>
. For the purposes of this demo, don't worry about the Delay Time (Seconds)
or the Is Enqueued
Choices. Just choose any radio call from the list of values provided by the Radio Calls
. Alternatively, you could set the Value Set
to String Literal
and pull one of the strings from the Radio Identifiers documentation like so:
(The double-quotes are handled automatically, don't add them to the string)
The script should now look like this:
And now, with "f1000_mprg0050" as the string literal, Kaz will repeat a line about finding an english-speaking soldier in the region every time a soldier is marked.
Importing and Exporting Script Sets
Scripts can be reused for other sideops, and SOC comes with a number of reusable scripts that you can import directly into your sideop. By clicking on the Quest Script Tables
node (or any non-script, non-scriptal nodes), an Import/Export menu will appear:
The KazCallsAllTheTime
script from earlier is listed in the Custom Scripts, as well as all of the other variables and scripts for the sideop. Checking the box next to KazCallsAllTheTime
will enable the option for Exporting Variables and Scripts. By default, these scripts will be saved to the SOCassets\ScriptAssets\Script Library
folder, next to Default.xml
(the Total_Targets variable and the 8 scripts that are loaded into a sideop by default is stored in that file).
You'll also notice a Templates
folder in the Script Library. This contains a number of out-of-the-box script sets that you can use for your sideops. For example, by clicking the Import Variable(s) / Script(s) From Xml...
button and opening the AutoSendHelicopterForInjuredTarget.xml
, 2 Variables and 2 Scripts will be added to the sideop:
In this script, Kaz will automatically call a support helicopter when the player is close enough to the
Injured_Target_Name
game object (Hostage_0). It also disables Kaz's "That's The Target" default radio dialogue that plays on marking the first sideop target.
There are a lot of templates to choose from, and they can be configured and reconfigured to fit most sideops. They're also a good way to familiarize yourself with sideop scripting concepts, like looping and daisy-chaining. You can also load the sideop examples to see their underlying scripts.