Interaction Scripts

From Socially Distant Manual

Interaction Scripts are Socially Distant script files that describe an interaction between the player and one or more NPCs in the game's chat application. They are used to script branching dialogues within chat rooms, and are one of the main ways the game's narratiive is told. Interaction Scripts, just like mission scripts, are written in sdsh and can therefore be bundled in Content Mods. See the Modding Fundamentals page for information on how to work with these.

Script structure

Interaction Scripts have a similar structure to mission scripts. They require a metadata and main_branch function in order to be loaded by the game. Like a mission script, the metadata function is used to define attributes about the interaction while the main_branch function is executed when the interaction starts. The first line of the script must be a unique text-based identifier for the interaction, in the form of a shebang comment (#!chat_interaction_identifier).

#!chat_interaction_id

metadata() {
    type <dm|group|guild>
    guild <guild_id>
    channel <channel_id>
    actor <character_id>
    start_type <scripted|auto>
    start_message <message_text>
    condition <met|unmet> <type> <parameter>
    repeatable <true|false>
}

main_branch() {
    # Interaction goes here!
}

Note: You must not run any commands outside of a function within an interaction script. Otherwise the script will fail to load during the metadata processing stage.

Metadata

You must define metadata in the script's metadata function to tell the game how to incorporate the interaction into the rest of the narrative. There are a few mandatory metadata directives, and a few optional ones based on what values you choose for the mandatory ones.

Mandatory metadata directives

  • type <dm|group|guild> - Determines whether the interaction takes place in a Direct Message, Group DM, or Guild Channel.
  • actor <character_id> - Tells the game to include the given NPC, specified by their Narrative ID, in the interaction. DM interactions may only have one actor, while group and guild interactions can have as many as you'd like. Do not specify the player as an actor, since the game does this implicitly. All interactions must have at least one non-player actor, and the first actor specified is considered the interaction's Primary Actor.
  • start_type <auto|scripted> - Determines how the interaction is started. Auto means that it is started by the player choosing an interaction in the chat UI, while scripted means that the interaction can only be started by another C# or sdsh script.

Mandatory metadata for start_type auto interactions

  • start_message <message> - This is the prompt displayed in the player's Interact menu for this interaction. If this is not specified, or the provided text is white space, then the interaction will be treated as a scripted interaction.

Mandatory directives for Group and Guild interactions

  • channel <channel_id> - For both interaction types, this specifies the Narrative ID of the channel the interaction will take place in. The script will not load if this isn't specified or is specified as white space.
  • guild <guild_id> - For guild interactions, this specifies which guild the interaction takes place in. The script won't load if this is unspecified or white space. Note that the channel directive must also specify a channel that exists within the same guild.

Optional directives

  • repeatable <true|false> - determines whether the interaction can be repeated again when it finishes. This is only relevant for start_type auto interactions.
  • condition <met|unmet> <type> <parameter> - Specifies a simple condition that must be met before the interaction can be started. These are ignored for scripted interactions. For info on how to use these, see the Narrative Conditions section on this page.
  • conditions <policy> [parameter] - Determines the policy for how many condition statements must be satisfied in order for the interaction to be available. By default, policy is set to all.

Narrative Conditions

Narrative Conditions are used to tell the game when a given interaction can actually be performed by the player. By default, the interaction can only be triggered by the player if all specified conditions are met. Narrative Conditions are specified in the script's metadata function.

Narrative Conditions take the following syntax, condition <met|unmet> <type> <parameter>, where:

  • met: The condition must be met for the interaction to trigger
  • unmet: Opposite of met, the condition must NOT be met for the interaction to trigger
  • type: Specifies the type of the condition that needs to be met.
  • parameter: Depends on the specified condition type.

Available condition types

The following condition types are available. Descriptions of their expected parameters are also provided. Note that type names are case-insensitive.

Type Parameter Description
missionCompleted Mission ID Check that the given mission has been completed.
interactionCompleted Interaction ID Check that a given NPC interaction has been completed at least once
isInMission Mission ID Checks if the player is playing the specified mission
friendsWith Character ID Checks if the player is friends with a given NPC
blockedBy Character ID Checks if the player is blocked by a given NPC
seenProfile Character ID Checks that the player has encountered a given NPC in the world before
seenNetwork Network ID Checks that the player has seen the inside of the given local network before.
seenDevice Device ID Checks if the player has hacked into the given device before.
reachedLevel Integer between 0 and 20 Checks that the player has reached the given Skill Level
worldFlag World Flag Checks if a given world flag has been set in the current save file
lifepath Lifepath ID Checks if the player's chosen Lifepath matches the required one
missionFailed Mission ID Checks if the given mission has been failed. This is different from a mission not yet being completed, as a failed mission can't be replayed.

Condition Policies

The conditions directive is used to set the policy for how many condition statements must be met. By default, the policy is all meaning that all statements must hold true.

The following policy statements are valid:

  • conditions all - Default. All conditions must be met.
  • conditions any - At least one condition must be met.
  • conditions atleast <number> - At least number conditions of a unique type must be met.
  • conditions none - All conditions must not be met.

Actors

Actors are the characters within the interaction. All actors must be specified in the script's metadata, except for the player. The game automatically includes the player in all interactions, with the narrative ID of player.

The first non-player actor specified in the script metadata is the script's Primary Actor. This is the actor initially possessed by the game when the main_branch method is first called. For interactions that display in the player's Interact menu, the Primary Actor is the character that gets displayed for this interaction.

You can possess a new actor during the interaction using the actor <actor_id> command once the interaction has started.

Interaction Commands

Inside the scope of the main_branch function, you have several Interaction Commands at your disposal to tell a story. Many of these commands will be executed by the script's currently-possessed actor.

actor <actor_id>

Use this to switch the current actor. You may only specify actors defined in the script's metadata function, or you may specify player to switch to the player.

say <message>

Send a text message from the current actor. Typing delays and other animations will be handled for you by the game. If the current actor is the player, an animation of the message being typed out will play and the game will block any mouse/keyboard events until it is done.

action <message>

Similar to the say command, but the message will be italicized as if the character used a /me command in a real-life chat app.

shrug

Alias for the say command with the text "¯\_(ツ)_/¯" specified as the message. Yes, we're serious, you can shrug in an interaction script.

mention <character_id>

Generate an @mention link for a given character, including ones not in the chat. If the given character is the player and the player isn't viewing the conversation, they will hear a noise.

The correct way to use the mention command is as a command expansion within the say or action commands. This will embed the mention within the message's text. Like this:

# Mention the player
say "Hello, $(mention player)"

mission <mission_id>

Send a Mission Attachment message into the chat. You can only do this if the current actor is an NPC. Doing this as the player will halt the interaction with an error.

branch <id> <message>

Define a branch interaction. Branch Interactions allow the player to choose responses within the Interact menu during an NPC interaction.

The first parameter is a unique Branch Identifier, which you can use to check which branch was picked by the player later on. The second argument is the prompt displayed in the Interact menu.

When you define a branch interaction, the current actor will be the character displayed in the interaction menu. This gives the player a visual cue of who they will be responding to. You can also define branch interactions with the player being the current actor, for situations where you just want to let the player say or do something.

branch_picked <id>

Used in tandem wit the branch command.

This command checks if a given branch interaction was picked by the player, based on its ID. It will return an exit code of 0 if the branch interaction was picked, and 1 otherwise. This allows it to be used within an if or while statement inside sdsh, like so:

actor brodie
branch say_hi "Say hello"

wait_for_choices

if branch_picked say_hi;
then
    actor player
    say "Hello, $(mention brodie)!"
fi

wait_for_choices

Wait for the player to choose an interaction within the Interact menu. The selected interaction ID is echoed by the command once the player picks their choice. This means you can use this command as a command expansion and store the result in a variable.

You will need to use wait_for_choices after defining a list of branch interactions with the branch command, so you can give the player a chance to respond. This is because the branch_picked command can be used to check more than just the last-picked branch interaction, and thus doesn't wait.

exit

End the interaction.

Tricks

Linking interactions together into a narrative thread

By using Interaction Conditions and smart planning, you can create a narrative thread by chaining multiple interactions together. This appears to the player as a one single interaction, but it is split into multiple steps where you can choose to make any interaction repeatable within a step. The game will also save after each completed interaction in the thread, and the thread can span across multiple channels.

This is especially useful for writing character lore, since you can set up a set of repeatable interactions in a narrative thread that the player can ask an NPC.

The below example shows how to chain 4 separate Interaction Scripts into a 1+2+1 narrative thread.

STEP 1, INTERACTION 1
#!SCRIPT_1

metadata() {
    # All scripts in the thread must be "start_type auto"
    start_type auto
    
    # The first step can't be repeated.
    repeatable false
    
    # For the narrative thread to be cohesive, you may want to share at least one actor across all scripts.
    actor brodie
}

STEP 2, INTERACTION 1
#!script2

metadata() {
    start_type auto
    
    # "brodie" is our shared actor
    actor brodie
    
    # Interactions in Step 2 are repeatable.
    repeatable true 
    
    # Unless Step 3 has been completed.
    condition umet interactionCompleted script4
    
    # Make sure Step 1 has been completed!
    condition met interactionCompleted script1
}

STEP 2, INTERACTION 2
#!script3

metadata() {
    # Same as the other interaction in step 2
    start_type auto
    actor brodie
    repeatable true
    condition unmet interactionCompleted script4
    condition met interactionCompleted script1
}

STEP 3, INTERACTION 1
#!script4 

metadata() {
    start_type auto
    actor brodie
    
    # Once this interaction has been completed, the entire thread has been and thus
    # can't be replayed. Unless you want it to be, unless both this script and all
    # others in the final step and first step must be repeatable.
    repeatable false
    
    # Tell the game that we want to allow this interaction if any one of the conditions are met.
    # By default, all of them need to be met. In some cases you may want that instead, if you want to
    # require all interactions in the previous step to be completed before this one appears.
    conditions any
    
    # Require any of the previous step's conditions to be played.
    condition met interactionCompleted script2
    condition met interactionCompleted script3
}

Locking interactions to within missions

You may want to lock a single interaction, or an entire narrative thread, to only work during a specific mission. This is useful for creating a mission objective that requires the player to talk to someone.

To do this:

  1. Note the ID of the mission script.
  2. Make sure all interaction scripts have a condition met isInMission <MissionID> condition in their metadata.
  3. If any interactions have conditions any in their metadata, change it to conditions atleast 2. This means that at least 2 unique condition types must be met.
  4. Change the first interaction in the thread to start_type scripted. It'll be started by your mission's script with the interaction <id> command.
  5. In that same interaction script, add the below code to the start of your main_branch to emulate the behaviour of a type auto interaction.
  6. Note the ID of the final interaction in the thread. Make sure your mission objective is an interaction type and that the interaction ID is the same as the ID of the final interaction script in the thread. This makes the objective complete when the narrative thread does.
main_branch() {
    branch start "Talk to this NPC"
    wait_for_choices
}

This works because, when the game checks for available interactions to show in the Interact menu, it will only show those defined in the current interaction if there's an interaction script running in the channel. The player will therefore be forced to complete the mission interaction.

Locking branches behind skill level

The branch command supports an optional third parameter that binds it to a skill level condition. If the player hasn't reached that skill level, the interaction will be grayed out.

Introducing a character to the player

By marking an interaction script as start_type scripted, and starting it from a mission or other interaction script, you can introduce the player to a new character by having them DM the player.

This only works if the interaction introducing the character is type dm or type group. This is because DM and Group interactions automatically create their channels when the script is started, and the player will automatically get added to them.

To create an introduction interaction, simply create a scripted interaction with the following metadata, and no branch statements.

#!intro_script

metadata() {
    type dm # or group
    start_type scripted
    actor intro_character
}

main_branch() {
    say "$(mention player) Hello!"
    say "I am me."
}

This will cause the character to DM the player some messages, and then the interaction will complete. The game will then immediately save after adding the character as a friend to the player. This is because the game creates friendships any time a group or DM interaction completes.

You can then let the player interact with this NPC even further by creating start_type auto interactions that have a condition met friendsWith <character> condition.

Failing an interaction

You can fail an interaction by using exit with a non-zero exit status in main_branch. If an interaction is failed, no friendships will be created by the game If a future interaction relies on this one being completed, that interaction will no longer be reachable. The game tracks this, meaning failing an interaction can be used to fail a mission objective automatically if an interaction objective requires an unreachable interaction to complete.