Table of Contents

The Basics of sdsh

Socially Distant is heavily narrative-driven. Almost all of the game's main story is told through encounters with non-player characters, as well as through missions. These in-game encounters are written in a subset of Bash called sdsh. This is also the same language used to parse commands in the in-game command shell.

Why would I want to write sdsh?

Consider a mission where the player must hack into a network, download a file from a device inside the network, then delete the file from the hacked device.

There are three theoretical ways this mission could be programmed into the game. It could be written in C# directly, allowing it to have full access to the game's API. It could be written in a data markup language like YAML or JSON, and thus written in a data-driven way. Or, it could be written in an embedded scripting language like Lua, or, well, sdsh.

Missions can be highly dynamic, meaning data-driven design can be a lot more challenging to deal with. Allowing a mission to be written in C# means it has to be compiled into the game, making it harder to iterate on the game's story telling. Using an embedded scripting language means the mission can be re-loaded on the fly after a change, while also allowing the mission to do basic programmer things within reason.

Furthermore, the game already needs sdsh because a command shell is an integral part of gameplay.

Missions, as well as other in-game encounters, are also long-lasting tasks the game needs to execute over a long period of time. This means they either need to be asynchronous or run on a background thread. The sdsh interpreter is designed to be ran asynchronously, while the language itself hides this away from you as a writer or programmer. This makes sdsh perfect for scripting missions.

How to write sdsh

Use the in-game terminal to play around with these code examples.

Run a command

Commands are everything! They tell the game to do things.

For example, this command opens this website in the user's browser:

man

Run a command with parameters

Many commands accept or require you to specify parameters that control their behaviour. Each parameter is separated by whitespace.

echo Hello world!

String literals

Strings of text in sdsh are surrounded in apostrophes (single-quotes).

echo 'This is a string of text.'

If you need to use an apostrophe within a string, you can escape it with a backslash.

echo 'I\'m Ritchie.'

Text expressions

Text expressions allow you to insert variables and other expressions inside a string of text. Text expressions are surrounded by double-quotes.

USER='ritchie'
echo "Hello, $USER!"

Variables

You can define a variable like this: NAME=expression

VAR1=ritchie
VAR2=is
VAR3=here

And you can access the value of a variable like this $NAME

echo $VAR1 $VAR2 $VAR3

You can also access a variable's value like this: ${NAME}

echo ${NAME}s

Writing to a file

You can write the output of a command to a file, like this:

echo Hello world! > ~/hello.txt

Or, you can write to the end of an existing file, like this:

echo Hello world! >> ~/hello.txt

Piping

You can use the output of one command as the input of another command. This is called piping. You can use piping to make a cow say some wise words.

fortune | cowsay

Reading a file and using it as input

You can also use the contents of a file as the input of a command. You can have the contents of a file read aloud to you, like this:

speak < ~/hello.txt

Command expansion

Command expansion allows you to treat the output of a command as if it were text. You can then store this text as a variable, or pass it as a parameter to another command.

COWFORTUNE=$(fortune | cowsay)
echo $COWFORTUNE

Environment variables

You can export any variable as an environment variable using the export keyword. Use this to customize your prompt.

export PS1='My Cool Prompt >>> '

Functions

Use functions to group a list of commands together into a single command you can use later.

function cowfortune() {
  fortune | cowsay
} 

cowfortune

You don't need the function keyword, adding it is a stylistic choice.

cowfortune() { fortune | cowsay }

Function Parameters

You can pass parameters to functions just like you do other commands. Function parameters are accessed by numbered variables, $0 is the name of the function itself.

function hello() {
  echo "Hello, $1!";
}

hello ritchie

Run multiple things at once

You can run two commands at once with the & operator. This command makes the game wait 5 seconds while reading some text from a file.

(speak < ~/hello.txt) & sleep 5000

Run one command after another, on the same line

You can use a semi-colon to separate two commands on the same line. This code saves a random fortune to a file, prints the file to the screen, then reads it aloud.

fortune > ~/hello.txt; cat ~/hello.txt; speak < ~/hello.txt

Run a command only if a previous command succeeds

You can use the && operator to run the right-hand command if the left-hand command completed without error.

fortune > ~/hello.txt && cat ~/hello.txt && speak < ~/hello.txt

Conditional expressions

Use the if command to run a set of commands if a given command runs successfully.

if file ~/hello.txt
then
  speak < ~/hello.txt
fi

If you prefer the then keyword on the same line as the condition, don't forget a semi-colon before then.

if file ~/hello.txt; then
  speak < ~/hello.txt
fi

You can also check if the condition command didn't succeed:

if ! file ~/hello.txt
then
  echo 'File not found!'
fi

Or, run one set of commands on success and another on failure:

if file ~/hello.txt
then
  speak < ~/hello.txt
else
  echo 'File not found!'
fi

You can use the elif command in a similar way to else, except it will only run its commands if this secondary condition succeeds.

if file ~/hello.txt
then
  speak < ~/hello.txt
elif file ~/world.txt
  speak < ~/world.txt
else
  echo 'File not found!'
fi

Loops

You can repeat a set of commands while a condition is true, using the while command.

while true;
do
  echo "Infinite loop."
done

Text matching

You can use the case command to match a string of text against a set of patterns, running a set of commands based on what pattern matches first.

VARIABLE=ritchie
case $VARIABLE
  ritchie)
    echo Ritchie is here
    ;;
  hari)
    echo A wild evil skeleton appears...
    ;;
  *)
    echo Someone unknown showed up. Their name is $VARIABLE
    ;;
esac

When defining a pattern, preceding whitespace is ignored and a closing parenthesis marks the end of the pattern. When defining the commands to run for a given pattern, use the double-semi-colon (;;) to mark the end of the pattern's commands and the start of a new pattern. Use esac after the end of the last pattern to mark the end of the case command.

An asterisk (*) acts as a wildcard inside patterns.