Scripting basics

Scripting basics

You can enhance your packages by programming in JavaScript. There are multiple ways of using JavaScript in TabletopPlayground: you can attach scripts to in-game objects, object templates, or globally to a game state. You can also use a JavaScript console while creating states in the editor, or even while hosting a game!

This article contains basic information about how scripting works and what tools you can use. If you’d rather jump right in, you can start with the tutorial at https://tabletop-playground.com/knowledge-base/scripting-101-how-to-get-started/

An example scripting project can be found at https://tabletop-playground.com/knowledge-base/chess-ai-scripting-example/.

If you already have scripting experience using Lua, one of our community members has written a guide to help you get up to speed with JavaScript in Tabletop Playground quickly: https://steamcommunity.com/sharedfiles/filedetails/?id=2092593343

Writing scripts

Your scripts need to be located in the “Scripts” subfolder of your package (find your packages in your in your install directory under TabletopPlayground/PersistentDownloadDirs, or click the folder icon in the editor window for your package to open the “Templates” subfolder of the package). To interact with Tabletop Playground in your scripts, you need to import the API. For example, you can import the whole API by writing const tp = require('@tabletop-playground/api');. Alternatively, you can directly import what you need, for example const { world, globalEvents } = require('@tabletop-playground/api');. In both cases, our recommended script editor Visual Studio Code (VS Code) will pick up the API definition and provide autocompletion and show documentation in tooltips. You can find the online API documentation at https://api.tabletop-playground.com.

The API has a few variables that you can use to interact with the game: The world object has many methods for finding players and objects, including 3D queries for objects using various shapes (lines, boxes, spheres, etc.). It also has methods to draw points and lines in the world to help visualize and debug your scripts.

Then there’s globalEvents. It contains various hooks to which you can attach callback functions to trigger behavior when certain events happen in the game. For example, the following code will print a message to the JavaScript console whenever a player leaves the game: globalEvents.onPlayerLeft.add(function(player) {console.log("Didn't like " + player.getName() + " anyway");});

The last type of variable is not available in the global script, or in the console. It can only be used in scripts attached to objects in game or object templates: refObject is the referenced object. It has lots of functions to manipulate the object or get information about it. Usually, you will want your object specific code to run when something happens with the object, so refObject also provides callback hooks. These callbacks always get the referenced object as first parameter. For example, the following code prints who released an object where, whenever any player releases it: refObject.onReleased.add(function(object, player) {console.log(player.getName() + " released at " + object.getPosition());});

In addition to refObject, there are some more specific variables that are only available in scripts for certain types of objects: refCard for cards, refHolder for card holders, refContainer for containers, refDice for dice, and refMultistate for multistate objects. Each object type has additional functions and callback hooks, but refObject is always valid. You can find a TypeScript file that defines the complete API in the game folder under TabletopPlayground/node_modules/@tabletop-playground/api/index.d.ts.

Using scripting

The scripting UI elements (including the console) are active when you create game states in the editor. You can also use them in a regular game when you go to the Settings, click “Game” and then “Show advanced interface settigns”. Then activate the first option “Scripting UI in Game”.

You can attach a global script to the game state in the session options, or to every object in their object properties. Objects also have a unique ID that you can find in their properties. You can use the id to get the object in scripts or the console with world.getObjectById.

When editing an object template in the editor, you can also attach a script in the object properties pane. The script will be used by default for all objects of that type, but it can be overridden by specifying a script for a particular object when creating a state. Objects in existing states are also not affected by changing the script of their object template.

While you are editing and testing your scripts, you’ll often want to reload them quickly in game. When you are editing game states, there’s an additional entry in the context menu: “Reload Scripting” will discard and re-initialize the JavaScript state and then execute all scripts, so scripts are executed in a clean environment just like when loading a game state.

Script Execution Order

When loading a state, all Objects are created, then the global script is run, then all Object scripts are run once and all GameObject.onCreated and GlobalScriptingEvents.onObjectCreated events are executed.

Script initialization and callback execution is always run sequentially. You don’t need to worry about any race conditions.

Variable Scope in Object scripts

Note that the global scope is shared between all scripts and the in-game console. For example, any variable you globally define in an object script is shared between all objects running the same script. If you want to define a variable only for a specific object instance, you can instead assign it to the reference Object:

const { refObject } = require('@tabletop-playground/api');

// shared by all game objects running this object script
let someValue = 1; 

// private value for each game object running this script
refObject.someValue = 1;

Debugging and external consoles

When your scripts get more complex, you may want a way to step through your code in order to find errors. Or maybe you are used to the feature-packed JavaScript consoles of the Chrome Browser or VS Code? No problem! Tabletop Playground supports the V8 inspector protocol to connect external applications.

First, you have to execute world.startDebugMode(). You can run the command from the console or from a script, but remember that you should remove it from your scripts before you upload your package. Once the inspector protocol is running, you can connect the Chrome Dev Tools by going to the following URL in Chrome: devtools://devtools/bundled/inspector.html?v8only=true&ws=localhost:9229. You can now use the Chrome Dev Tools console in addition to the in-game console! Both have access to the same JavaScript context.

If you want to use the VS Code console or debugger, you have to configure the connection: go to the Debugger (bug icon in on the left), then open your launch.json by clicking on the small gear on the upper left. Paste the following configuration:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Inspector",
            "type": "node",
            "protocol": "inspector",
            "request": "attach",
            "address": "localhost",
            "port": 9229
        }
    ]
}

Save the new config, and press “Start debugging” (the green arrow next to the config button). The debug console from VS Code should now be connected. You can also set VS Code to break on exceptions, allowing you to inspect variables at the moment where an exception occurs.

Testing multiplayer

You can test how your scripts behave with multiple players without needing more than one PC or multiple copies of the game! We recommend that you set the game to windowed mode in the settings for multiplayer tests.

When you are ready to test your scripts, start a regular multiplayer game with your package and make sure to set the connection mode to direct connect. Then go to the game folder (right click the game in Steam, select Properties->Local Files->Browse Local Files…). Start TabletopPlayground.exe for each additional player you want to test with. In the new game windows, go to “Join Game”, and then click on “Direct IP Connect” at the top. Enter 127.0.0.1 as the IP and click on Join. You will appear as a new player in your own game!

Handling exceptions

If an exception occurs in your code, you will see the error message and the location in the code printed in the in-game console. If you want to handle uncaught exceptions globally, for example to send them to a server that aggregates the errors that your players see, you can use $uncaughtException. Set it to a function that accepts a string, which will contain the error type, message, and location:

$uncaughtException = (error) => {
    console.log(error);
}
Table of Contents