Skip to content
Sean LeBlanc edited this page Jan 26, 2020 · 5 revisions

What's Kitsy?

Kitsy is a scripting toolkit @mildmojo created to improve bitsy hacking in a couple ways:

  1. Allows hacks to cooperate instead of rudely hijacking and overwriting each other
  2. Provides a semantic helper API for common hack techniques

How does it work?

Kitsy basically works like a singleton; it defines a global, window.kitsy, and everytime a hack uses one of Kitsy's helpers, it will check if this global exists. If not, Kitsy will initialize its state to the global before the hack uses it. If it does, it will simply add to the existing Kitsy state. Importantly, using a global allows multiple copies of the Kitsy script to be included on a page.

How do I use it?

setup

To get access to Kitsy's helpers in a hack, include the following at the top of your file:

import {
    before,
    after,
    inject,
    addDialogTag,
    addDeferredDialogTag
} from "./kitsy-scripting-toolkit.js"; 

(note that you don't have to import every helper; you can pick and choose which ones you need)

helpers

before(targetFuncName, beforeFn)

  • targetFuncName (string): Name of the global function you want your code to run before.
  • beforeFn (function): A function to run. This function should take the same arguments as the original function.

Use this function to register your own code to run before a Bitsy built-in function each time it's called. For example, if you wanted to modify Bitsy game data as it's loaded, you could write:

before("load_game", function run(game_data, startWithTitle) {
	var newGameData = game_data.replace("midnight", "12 AM");
	return [newGameData, startWithTitle];
});

In that example, the run function returns an array, so the values in the array will replace the parameters sent to the original load_game function. If you don't return anything, the original load_game will be called with the original game_data, startWithTitle parameters.

Examples:

// show an alert before loading the game
before("load_game", function () {
	alert("Loading!");
});
// convert text to uppercase before showing it
before("show_text", function (text) {
	return text.toUpperCase();
});
// context text to uppercase asychronously before showing it
before("show_text", function (text, done) {
	done(text.toUpperCase());
});

after(targetFuncName, afterFn)

  • targetFuncName (string): Name of the global function you want your code to run afterFn.
  • afterFn (function): A function to run. This function should take the same arguments as the original function.

Use this function to register your own code to run after a Bitsy built-in function each time it's called. For example, if you wanted to pop up an alertbox every time the player exits a room, you could write:

after("onExitDialog", function() {
	alert("Player exited.");
});

You might use this to clean up game state or play a sound on certain game events or send gameplay stats to an analytics provider.

inject(searchRegex, replaceString)

  • searchRegex (regular expression): A snippet of source code you want to find inside a script tag.
  • replaceString (string): JavaScript code to replace the search results with. If you want to inject code around the search results instead of replacing them, you can use capturing groups and reference them in your string.

Examples:

// plain replace
inject(/mapsize = 16/, 'mapsize = 8');

// inject after
// note that the entire regex body is wrapped in ()
// this makes it a capturing group, referenced by the $1 in the replace string
inject(/(names.sprite.set\( name, id \);)/, '$1 console.dir(names)');

Use this function to perform surgery on the Bitsy code. It's a search-and-replace. Best used to hook into portions of Bitsy which aren't exposed publicly. In general, before and after are preferable, but this is often necessary, as many Bitsy features use local variables and functions which aren't normally visible to other scripts.

addDialogTag(tagName, dialogFn)

  • tagName (string): name of the tag in the bitsy source you want to trigger the function
  • dialogFn (function): function to trigger when your tag is reached.

Use this function to add a new dialog tag to bitsy that triggers a custom function.

dialogFn expects three arguments:

  • environment: provides access to SetVariable/GetVariable (among other things, see Environment in the bitsy source for more info)
  • parameters: array containing parameters as string in first element (i.e. parameters[0])
  • onReturn: function to call with return value (just call onReturn(null); at the end of your function if your tag doesn't interact with the logic system)

e.g. to show an alert triggered by a custom tag {alert "some text"} in the bitsy source, you could write

addDialogTag('alert', function(environment, parameters, onReturn){
    alert(parameters[0]);
    onReturn(null);
});

addDeferredDialogTag(tagName, dialogFn)

  • tagName (string): name of the tag in the bitsy source you want to trigger the function
  • dialogFn (function): function to trigger after dialog including your tag is closed.

Use this function to add a new dialog tag to bitsy that triggers a custom function after a dialog is closed.

This function works almost identically to addDialogTag above, with two key differences:

  1. dialogFn isn't called immediately when the tag is shown; it is called after the dialog is closed
  2. dialogFn doesn't take a third onReturn argument

i.e. addDeferredDialogTag works like exit, and addDialogTag works like exitNow.

addDualDialogTag(tagName, dialogFn)

This is a helper which combines addDialogTag and addDeferredDialogTag; e.g. exit-from-dialog uses addDualDialogTag('exit', ...) to expose both exit and exitNow.

Asynchronous Code

Kitsy allows for before and after functions to run asynchronously. If you want your function to be treated as asynchronous, add an extra parameter to its argument list and call it when your code has finished. If you call the callback with parameters, they'll replace parameters to the original function as above.

Example:

// replace gamedata with contents from an asynchronous fetch request before loading game
before("load_game", function (game_data, startWithTitle, done) {
	fetch("http://example.com")
	.then(function(response) {
		done(response.text(), startWithTitle);
	});
});