Tutorial:Scripting

From AI Dungeon Wiki
Jump to navigation Jump to search

Introduction

This tutorial will provide a step-by-step explanation and guide to setup, debug, and utilize the basic tools and concepts of AI Dungeon scripting.
AI Dungeon scripting allows for scripted manipulation of the user-input, the AI's output, World Info, and other elements such as frontMemory.
It's highly encouraged to read yourself up on the scripting functionalities, and be aware of its existence as reference material; these will all be relevant in the following tutorial.

Getting Started

Don't know how to script yet?

Custom scripts are written in JavaScript, though some of this functionality is locked down for security reasons. As such, you can get by with a basic understanding of JavaScript and its capabilities.

A list of js resources:

Other useful stuff to know:

Read some examples!

The developers have periodically added and updated examples which include almost all of the AI Dungeon-unique functions as a showcase for what they can do, which you can see here.

Additionally you can look at this Wiki's Scripting Repository for examples of community made scripts.

How to create a script

  • Navigate to the scenario creation screen via My Stuff in the left-side panel of AI Dungeon's interface then press Create Scenario at the top of the interface or enter an existing scenario from your list of scenarios.
  • At the bottom of the scenario creation screen there's a button labeled Scripts (Experimental) that brings you to the scripting interface.
  • In the scripting interface screen you will be presented with two tabs labeled Input Modifier and Output Modifier.
    • Replace or modify the contents of the panels with your own JavaScript then hit Save in the top-right corner to attach the script(s) to the scenario you are in.
    • Adventures played from this scenario will now be affected by the attached script(s)

What scripting can and cannot do

Scripting allows full control of many features, but it still has some major limitations. For instance, it cannot do undo, alter, retry, or continue actions. This makes scripting more of an accessory to stories than their main driving element. It is also really hard for scripts to tell what is happening in the story, so most scripts rely on pre-set commands or random intervals to do their actions.

Editing Tips

Because you can't share in a scenario, it is recommended that you use external storage for your scripts. This also helps in cases where a bug causes a script to be deleted. If you use Source Control such as git you can also store the files locally allowing you to use an IDE to get better editing features than the web editor can provide. However, the web editor also does have useful features for instance you can ctrl+click to have multiple cursors to edit multiple names at once. On the left side there are arrows you can use to fold code to make it easier to read. Unfortunately find and replace does not work. You should also know about ctrl+arrows and ctrl+shift+arrows for their use in highlighting and moving around code, because they are incredibly powerful when combined with multicursor.

Explaining an example script

For this example I will be explaining Zynj's CommandManager script. You will need a working knowledge of js, regex, and Scripting functions. This script is an inputModifier, so it does not affect the output.

Setup

state.config = {
prefix: "\n/",
}
state.commandList = { // Store a function in state with the intention of being able to call from both input / output script without duplicating code.
printMod: // Identifier and name of function
{name: 'printMod',
description: 'Displays a message on the HUD.',
args: true,
usage: '<text>',
execute:
(args) =>
{
const textToDisplay = args.join(' ');
state.message = textToDisplay; // Message to let us know which part of the script is calling it.
},
},
};

In setup, the script first sets up a command prefix of \n/ which means the text must start with a newline then a slash. This means you must be in story mode for this to work other wise the text would include a > You This also means you must start with a newline, so it makes it hard to use on mobile. To fix this replace prefix: "\n/", with prefix: "/", so it no longer checks for the newline. The newline can be useful for do and say mode because they automatically add a newline.

The script also defines its commands in setup. In this case there is one command /printMod which combines its arguments with spaces using .join(' ') and sets state.message to the result, allowing the user to override state.message. You could use similar code to overwrite state.memory.context,state.memory.frontMemory, or state.memory.authorsNote.

commandList and config.prefix are both added into state so they can be stored in multiple turns. Interestingly the description is never used, but could be if someone implemented a help command that displayed the list of commands.

Initialization

const { commandList } = state;
const { prefix } = state.config;

Destructure the values from state into 'aliases' for ease of readability in the code.

  • For clarification, const { commandList } = state is equivalent to const commandList = state.commandList

Main

const modifier = (text) =>
{
if (text.startsWith(prefix))
{

This makes it so the script only affects anything when starting with the prefix. This prevents accidental activation.

state.message = `Text startsWith: ${prefix}`;
const args = text.slice(prefix.length).split(/ +/); // Create a list of the words

Cut out the prefix and split it into arguments

const commandName = args.shift();

Fetch and remove the actual command from the list of arguments, this also removes the command from the arguments

if (!(commandName in commandList)) {state.message = "Invalid Command!"; return "";}

Command is not in the list, cancel script by returning "" and tell the user the command is invalid.

const command = commandList[commandName];

Get the actual command

if (command.args && !args.length
{
let reply = `You didn't provide any arguments!\n`
if (command.usage) {reply += `Example: \`${prefix}${command.name} ${command.usage}\``;} // Provide instructions for how to use the command if provided.
state.message = reply;
return "";
}

If the command requires arguments, but there are none, the script should tell the user of their improper usage and exit gracefully

try{command.execute(args);}
catch (error) {state.message = `There was an error!\n${error}`;}

You should execute the command, but if there is an error, still exit gracefully.

End

}
return {text};
}
modifier(text);

This is the required end of all scripts, otherwise it will cause problems. One thing to note is that the text the script outputs should always be in the style of the story, or else the AI will start messing up. This is why state.message rather than the text is used to communicate with the player.

Debugging

Debugging is always a necessary part of programming, and so AID scripting has a couple of tools you can use to help you. Below the editor there are boxes for input and state, using the send button you can test whichever script(input or output) you are focused on. Note that this does not support do or say modes so it is equivalent to only using story. This also outputs state, which can then be copy pasted into the state input box to test multiple actions. console.log also works in this mode so it can be the best way to test many scripts. If the script causes an error it will show up in output and give the line number of the error after vm.js:

The other way to test scripts is to play the scenario they are attached to. This is sometimes less preferable because it creates a large amount of adventures. However, it can be the only effective way to tests scripts, especially if they use features like frontMemory. You can also use Last Model Input(see scripting functionalities) to see the active context of the AI, the state, and the console in a separate scripting tab or window.