UNPKG

@bitsy/hecks

Version:

a collection of re-usable scripts for bitsy game maker

453 lines (400 loc) • 11.6 kB
/** šŸ”€ @file dialog choices @summary binary dialog choices @license MIT @version 2.0.0 @requires 5.3 @author Sean S. LeBlanc @description Adds a dialog tag which allows you to present the player with binary dialog choices. Usage: {choice - option one result of picking option - option two result of picking option } Recommended uses: DLG_simple_response """ Greeting text {choice - Response one answer to response one - Response two answer to response two } """ DLG_complex_response """ Greeting text {choice - Response one {a = 1} - Response two {a = 2} } constant part of answer{ - a == 1 ? custom part based on response one - a == 2 ? custom part based on response two } """ Note: it's recommended you combine this hack with the dialog jump hack for complex cases. Limitations: Each option must fit on a single line, or the interaction will break. Checking the value of a variable set in an option *immediately after the choice* will not work, as it will evaluate before the player has selected an option if there is no text inbetween the two. e.g. """ {a = 1} {choice - Response one {a = 2} - Response two {a = 3} } { - a == 1 ? this will print - a == 2 ? these will not - a == 3 ? these will not } """ HOW TO USE: Copy-paste into a script tag after the bitsy source */ this.hacks = this.hacks || {}; (function (bitsy) { 'use strict'; 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 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: inject(/(names.sprite.set\( name, id \);)/, '$1console.dir(names)'); function inject$1(searchRegex, replaceString) { var kitsy = kitsyInit(); kitsy.queuedInjectScripts.push({ searchRegex: searchRegex, replaceString: replaceString }); } 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(); } /** * Helper for printing a paragraph break inside of a dialog function. * Adds the function `AddParagraphBreak` to `DialogBuffer` */ inject$1(/(this\.AddLinebreak = )/, 'this.AddParagraphBreak = function() { buffer.push( [[]] ); isActive = true; };\n$1'); var dialogChoices = { choice: 0, choices: [], choicesActive: false, handleInput: function (dialogBuffer) { // navigate if ( bitsy.input.isKeyDown(bitsy.key.up) || bitsy.input.isKeyDown(bitsy.key.w) || bitsy.input.swipeUp() ) { this.choice -= 1; if (this.choice < 0) { this.choice += this.choices.length; } return false; } if ( bitsy.input.isKeyDown(bitsy.key.down) || bitsy.input.isKeyDown(bitsy.key.s) || bitsy.input.swipeDown() ) { this.choice = (this.choice + 1) % this.choices.length; return false; } // select if ( this.choicesActive && ( bitsy.input.isKeyDown(bitsy.key.right) || bitsy.input.isKeyDown(bitsy.key.d) || bitsy.input.isKeyDown(bitsy.key.enter) || bitsy.input.isKeyDown(bitsy.key.space) || bitsy.input.swipeRight() ) ) { // evaluate choice this.choices[this.choice](); // reset this.choice = 0; this.choices = []; this.choicesActive = false; // get back into the regular dialog flow if (dialogBuffer.Continue()) { dialogBuffer.Update(0); // make sure to close dialog if there's nothing to say // after the choice has been made if (!dialogBuffer.CurCharCount()) { dialogBuffer.Continue(); } } return true; } return false; } }; bitsy.dialogChoices = dialogChoices; // parsing // (adds a new sequence node type) inject$1(/(\|\| str === "shuffle")/, '$1 || str === "choice"'); inject$1(/(state\.curNode\.AddChild\( new ShuffleNode\( options \) \);)/, `$1 else if(sequenceType === "choice") state.curNode.AddChild( new ChoiceNode( options ) ); `); inject$1(/(var ShuffleNode = )/,` var ChoiceNode = function(options) { Object.assign( this, new TreeRelationship() ); Object.assign( this, new SequenceBase() ); this.type = "choice"; this.options = options; options.forEach(function(option){ var br = option.children.find(function(child){ return child.name === 'br'; }); if (!br) { option.onSelect = []; return; } var idx = option.children.indexOf(br); option.onSelect = option.children.slice(idx+1); option.children = option.children.slice(0, idx); }); this.Eval = function(environment,onReturn) { var lastVal = null; var i = 0; function evalChildren(children,done) { if(i < children.length) { children[i].Eval(environment, function(val) { environment.GetDialogBuffer().AddLinebreak(); lastVal = val; i++; evalChildren(children,done); }); } else { done(); } } window.dialogChoices.choices = this.options.map(function(option){ return function(){ option.onSelect.forEach(function(child){ child.Eval(environment, function(){}); }); }; }); if (environment.GetDialogBuffer().CurCharCount() > 0) { environment.GetDialogBuffer().AddParagraphBreak(); } evalChildren(this.options, function() { environment.GetDialogBuffer().AddParagraphBreak(); onReturn(lastVal); window.dialogChoices.choicesActive = true; }); } } $1`); // rendering // (re-uses existing arrow image data, // but draws rotated to point at text) inject$1(/(this\.DrawNextArrow = )/, ` this.DrawChoiceArrow = function() { var top = (3 + window.dialogChoices.choice * 6) * scale; var left = 1 * scale; for (var y = 0; y < 3; y++) { for (var x = 0; x < 5; x++) { var i = (y * 5) + x; if (arrowdata[i] == 1) { //scaling nonsense for (var sy = 0; sy < scale; sy++) { for (var sx = 0; sx < scale; sx++) { var pxl = 4 * ( ((top+(x*scale)+sy) * (textboxInfo.width*scale)) + (left+(y*scale)+sx) ); textboxInfo.img.data[pxl+0] = 255; textboxInfo.img.data[pxl+1] = 255; textboxInfo.img.data[pxl+2] = 255; textboxInfo.img.data[pxl+3] = 255; } } } } } }; $1`); inject$1(/(this\.DrawTextbox\(\);)/, ` if (window.dialogChoices.choicesActive) { this.DrawChoiceArrow(); } $1`); // interaction // (overrides the dialog skip/page flip) inject$1(/(\/\* CONTINUE DIALOG \*\/)/, `$1 if(window.dialogChoices.handleInput(dialogBuffer)) { return; } else `); inject$1(/(this\.CanContinue = function\(\) {)/, `$1 if(window.dialogChoices.choicesActive){ return false; }`); }(window));