@bitsy/hecks
Version:
a collection of re-usable scripts for bitsy game maker
372 lines (316 loc) ⢠9.96 kB
JavaScript
/**
ā
@file directional avatar
@summary flips the player's sprite based on directional movement
@license MIT
@version 1.1.2
@requires 5.3
@author Sean S. LeBlanc
@description
Flips the player's sprite based on directional movement.
HOW TO USE:
1. Copy-paste into a script tag after the bitsy source
2. Edit `horizontalFlipAllowed` and `verticalFlipAllowed` below as needed
*/
this.hacks = this.hacks || {};
this.hacks.directional_avatar = (function (exports,bitsy) {
;
var hackOptions = {
// If `horizontalFlipAllowed` is true:
// pressing left will make the player's sprite face backwards
// pressing right will make the player's sprite face forwards
horizontalFlipAllowed: true,
// If `verticalFlipAllowed` is true:
// pressing down will make the player's sprite upside-down
// pressing up will make the player's sprite right-side up
verticalFlipAllowed: false
};
bitsy = bitsy && bitsy.hasOwnProperty('default') ? bitsy['default'] : bitsy;
/**
@file utils
@summary miscellaneous bitsy utilities
@author Sean S. LeBlanc
*/
/*
Helper used to replace code in a script tag based on a search regex
To inject code without erasing original string, using capturing groups; e.g.
inject(/(some string)/,'injected before $1 injected after')
*/
function inject(searchRegex, replaceString) {
// find the relevant script tag
var scriptTags = document.getElementsByTagName('script');
var scriptTag;
var code;
for (var i = 0; i < scriptTags.length; ++i) {
scriptTag = scriptTags[i];
var matchesSearch = scriptTag.textContent.search(searchRegex) !== -1;
var isCurrentScript = scriptTag === document.currentScript;
if (matchesSearch && !isCurrentScript) {
code = scriptTag.textContent;
break;
}
}
// error-handling
if (!code) {
throw 'Couldn\'t find "' + searchRegex + '" in script tags';
}
// modify the content
code = code.replace(searchRegex, replaceString);
// replace the old script tag with a new one using our modified code
var newScriptTag = document.createElement('script');
newScriptTag.textContent = code;
scriptTag.insertAdjacentElement('afterend', newScriptTag);
scriptTag.remove();
}
/*
Helper for getting image by name or id
Args:
name: id or name of image to return
map: map of images (e.g. `sprite`, `tile`, `item`)
Returns: the image in the given map with the given name/id
*/
function getImage(name, map) {
var id = map.hasOwnProperty(name) ? name : Object.keys(map).find(function (e) {
return map[e].name == name;
});
return map[id];
}
/**
* Helper for getting an array with unique elements
* @param {Array} array Original array
* @return {Array} Copy of array, excluding duplicates
*/
function unique(array) {
return array.filter(function (item, idx) {
return array.indexOf(item) === idx;
});
}
/**
@file kitsy-script-toolkit
@summary makes it easier and cleaner to run code before and after Bitsy functions or to inject new code into Bitsy script tags
@license WTFPL (do WTF you want)
@version 4.0.0
@requires Bitsy Version: 4.5, 4.6
@author @mildmojo
@description
HOW TO USE:
import {before, after, inject, addDialogTag, addDeferredDialogTag} from "./helpers/kitsy-script-toolkit";
before(targetFuncName, beforeFn);
after(targetFuncName, afterFn);
inject(searchRegex, replaceString);
addDialogTag(tagName, dialogFn);
addDeferredDialogTag(tagName, dialogFn);
For more info, see the documentation at:
https://github.com/seleb/bitsy-hacks/wiki/Coding-with-kitsy
*/
// Ex: after('load_game', function run() { alert('Loaded!'); });
function after(targetFuncName, afterFn) {
var kitsy = kitsyInit();
kitsy.queuedAfterScripts[targetFuncName] = kitsy.queuedAfterScripts[targetFuncName] || [];
kitsy.queuedAfterScripts[targetFuncName].push(afterFn);
}
function kitsyInit() {
// return already-initialized kitsy
if (bitsy.kitsy) {
return bitsy.kitsy;
}
// Initialize kitsy
bitsy.kitsy = {
queuedInjectScripts: [],
queuedBeforeScripts: {},
queuedAfterScripts: {}
};
var oldStartFunc = bitsy.startExportedGame;
bitsy.startExportedGame = function doAllInjections() {
// Only do this once.
bitsy.startExportedGame = oldStartFunc;
// Rewrite scripts and hook everything up.
doInjects();
applyAllHooks();
// Start the game
bitsy.startExportedGame.apply(this, arguments);
};
return bitsy.kitsy;
}
function doInjects() {
bitsy.kitsy.queuedInjectScripts.forEach(function (injectScript) {
inject(injectScript.searchRegex, injectScript.replaceString);
});
_reinitEngine();
}
function applyAllHooks() {
var allHooks = unique(Object.keys(bitsy.kitsy.queuedBeforeScripts).concat(Object.keys(bitsy.kitsy.queuedAfterScripts)));
allHooks.forEach(applyHook);
}
function applyHook(functionName) {
var functionNameSegments = functionName.split('.');
var obj = bitsy;
while (functionNameSegments.length > 1) {
obj = obj[functionNameSegments.shift()];
}
var lastSegment = functionNameSegments[0];
var superFn = obj[lastSegment];
var superFnLength = superFn ? superFn.length : 0;
var functions = [];
// start with befores
functions = functions.concat(bitsy.kitsy.queuedBeforeScripts[functionName] || []);
// then original
if (superFn) {
functions.push(superFn);
}
// then afters
functions = functions.concat(bitsy.kitsy.queuedAfterScripts[functionName] || []);
// overwrite original with one which will call each in order
obj[lastSegment] = function () {
var returnVal;
var args;
var i = 0;
function runBefore() {
// All outta functions? Finish
if (i === functions.length) {
return returnVal;
}
// Update args if provided.
if (arguments.length > 0) {
args = [].slice.call(arguments);
}
if (functions[i].length > superFnLength) {
// Assume funcs that accept more args than the original are
// async and accept a callback as an additional argument.
return functions[i++].apply(this, args.concat(runBefore.bind(this)));
} else {
// run synchronously
returnVal = functions[i++].apply(this, args);
if (returnVal && returnVal.length) {
args = returnVal;
}
return runBefore.apply(this, args);
}
}
return runBefore.apply(this, arguments);
};
}
function _reinitEngine() {
// recreate the script and dialog objects so that they'll be
// referencing the code with injections instead of the original
bitsy.scriptModule = new bitsy.Script();
bitsy.scriptInterpreter = bitsy.scriptModule.CreateInterpreter();
bitsy.dialogModule = new bitsy.Dialog();
bitsy.dialogRenderer = bitsy.dialogModule.CreateRenderer();
bitsy.dialogBuffer = bitsy.dialogModule.CreateBuffer();
}
/**
@file edit image at runtime
@summary API for updating image data at runtime.
@author Sean S. LeBlanc
@description
Adds API for updating sprite, tile, and item data at runtime.
Individual frames of image data in bitsy are 8x8 1-bit 2D arrays in yx order
e.g. the default player is:
[
[0,0,0,1,1,0,0,0],
[0,0,0,1,1,0,0,0],
[0,0,0,1,1,0,0,0],
[0,0,1,1,1,1,0,0],
[0,1,1,1,1,1,1,0],
[1,0,1,1,1,1,0,1],
[0,0,1,0,0,1,0,0],
[0,0,1,0,0,1,0,0]
]
*/
/*
Args:
id: string id or name
frame: animation frame (0 or 1)
map: map of images (e.g. `sprite`, `tile`, `item`)
Returns: a single frame of a image data
*/
function getImageData(id, frame, map) {
return bitsy.renderer.GetImageSource(getImage(id, map).drw)[frame];
}
function getSpriteData(id, frame) {
return getImageData(id, frame, bitsy.sprite);
}
/*
Updates a single frame of image data
Args:
id: string id or name
frame: animation frame (0 or 1)
map: map of images (e.g. `sprite`, `tile`, `item`)
newData: new data to write to the image data
*/
function setImageData(id, frame, map, newData) {
var drawing = getImage(id, map);
var drw = drawing.drw;
var img = bitsy.renderer.GetImageSource(drw).slice();
img[frame] = newData;
bitsy.renderer.SetImageSource(drw, img);
}
function setSpriteData(id, frame, newData) {
setImageData(id, frame, bitsy.sprite, newData);
}
// helper function to flip sprite data
function flip(spriteData, v, h) {
var x, y, x2, y2, col, tmp;
var s = spriteData.slice();
if (v && hackOptions.verticalFlipAllowed) {
for (y = 0; y < s.length / 2; ++y) {
y2 = s.length - y - 1;
tmp = s[y];
s[y] = s[y2];
s[y2] = tmp;
}
}
if (h && hackOptions.horizontalFlipAllowed) {
for (y = 0; y < s.length; ++y) {
col = s[y] = s[y].slice();
for (x = 0; x < col.length / 2; ++x) {
x2 = col.length - x - 1;
tmp = col[x];
col[x] = col[x2];
col[x2] = tmp;
}
}
}
return s;
}
var hflip = false;
var vflip = false;
var originalAnimation;
after('onPlayerMoved', function () {
var i;
// save the original frames
if (!originalAnimation || originalAnimation.referenceFrame !== getSpriteData(bitsy.playerId, 0)) {
originalAnimation = {
frames: []
};
for (i = 0; i < bitsy.player().animation.frameCount; ++i) {
originalAnimation.frames.push(getSpriteData(bitsy.playerId, i));
}
}
// determine which directions need flipping
switch (bitsy.curPlayerDirection) {
case bitsy.Direction.Up:
vflip = false;
break;
case bitsy.Direction.Down:
vflip = true;
break;
case bitsy.Direction.Left:
hflip = true;
break;
case bitsy.Direction.Right:
hflip = false;
break;
default:
break;
}
// update sprite with flipped frames
for (i = 0; i < originalAnimation.frames.length; ++i) {
setSpriteData(bitsy.playerId, i, flip(originalAnimation.frames[i], vflip, hflip));
}
originalAnimation.referenceFrame = getSpriteData(bitsy.playerId, 0);
});
exports.hackOptions = hackOptions;
return exports;
}({},window));