Scripting basics

Table of Contents

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 dir under TabletopPlayground/PersistentDownloadDirs). 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 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 are two additional entries in the context menu: “Reload Scripts” will reload and execute all configured scripts, but not change the current JavaScript context. “Reset Scripting” also reloads and executes all scripts, but discards the entire JavaScript context before, 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 all Object scripts are run once, then the global script is run once.

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
var 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?experiments=true&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. If you use VS Code to edit your files in the Scripts folder of the package, you can also set breakpoints that will halt execution and enable you to step through the code. You may need to reload your scripts after you start debugging for breakpoints to work.

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. 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!