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 >": debug menu

Enable the "Debug IH mode" and the "debugMessages" options: debugmessages

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: ihlog 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: image 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 sent 2077341861, 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: image

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: image 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.

image 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: image

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: image

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 from qvars.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: image

For this example, you'll have Kaz radio in to say something when any soldier is marked. Start by giving the script a name: image

For the Trigger, use the Marker.ChangeToEnable() message from earlier. Set the StrCode32 to Marker, and the Message to ChangeToEnable: image

Author's Note: The Sender Options is now set to ANY / ALL. The Sender 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: image

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: image

Click on the second Choice, Value in the Choice List, and the menu will update to display the Choice's details: image

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: image

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: image

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: image

(The double-quotes are handled automatically, don't add them to the string)

The script should now look like this: image 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: image

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: image 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.

When you're done with your script changes, you're ready to Build the sideop and test it out. Continue to Step 4, if you haven't already.