text: /**
  📝
  @file edit dialog from dialog
  @summary edit dialog from dialog (yes really)
  @license MIT
  @version 1.1.4
  @author Sean S. LeBlanc
  
  @description
  You can use this to edit the dialog of sprites/items through dialog.
  
  (dialog "map, target, newDialog")
  Parameters:
  	map:       Type of image (SPR or ITM)
  	target:    id/name of image to edit
  	newDialog: id/name of image to edit
  
  Note: this hack disables bitsy's script caching.
  
  HOW TO USE:
  	Copy-paste this script into a new script tag after the Bitsy source code.
  
  TIPS:
  	- The player avatar is always a sprite with id "A"; you can edit your gamedata to give them a name for clarity
  	- You can use the full names or shorthand of image types (e.g. "SPR" and "sprite" will both work)
  */
  (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 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 = Object.prototype.hasOwnProperty.call(map, 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.1
  @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
  	});
  }
  
  // Ex: before('load_game', function run() { alert('Loading!'); });
  //     before('show_text', function run(text) { return text.toUpperCase(); });
  //     before('show_text', function run(text, done) { done(text.toUpperCase()); });
  function before(targetFuncName, beforeFn) {
  	var kitsy = kitsyInit();
  	kitsy.queuedBeforeScripts[targetFuncName] = kitsy.queuedBeforeScripts[targetFuncName] || [];
  	kitsy.queuedBeforeScripts[targetFuncName].push(beforeFn);
  }
  
  // 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 = [].slice.call(arguments);
  		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();
  }
  
  // Rewrite custom functions' parentheses to curly braces for Bitsy's
  // interpreter. Unescape escaped parentheticals, too.
  function convertDialogTags(input, tag) {
  	return input
  		.replace(new RegExp('\\\\?\\((' + tag + '(\\s+(".+?"|.+?))?)\\\\?\\)', 'g'), function(match, group){
  			if(match.substr(0,1) === '\\') {
  				return '('+ group + ')'; // Rewrite \(tag "..."|...\) to (tag "..."|...)
  			}
  			return '{'+ group + '}'; // Rewrite (tag "..."|...) to {tag "..."|...}
  		});
  }
  
  
  function addDialogFunction(tag, fn) {
  	var kitsy = kitsyInit();
  	kitsy.dialogFunctions = kitsy.dialogFunctions || {};
  	if (kitsy.dialogFunctions[tag]) {
  		throw new Error('The dialog function "' + tag + '" already exists.');
  	}
  
  	// Hook into game load and rewrite custom functions in game data to Bitsy format.
  	before('parseWorld', function (game_data) {
  		return [convertDialogTags(game_data, tag)];
  	});
  
  	kitsy.dialogFunctions[tag] = fn;
  }
  
  /**
   * Adds a custom dialog tag which executes the provided function.
   * For ease-of-use with the bitsy editor, tags can be written as
   * (tagname "parameters") in addition to the standard {tagname "parameters"}
   * 
   * Function is executed after the dialog box.
   *
   * @param {string}   tag Name of tag
   * @param {Function} fn  Function to execute, with signature `function(environment, parameters){}`
   *                       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]`)
   */
  function addDeferredDialogTag(tag, fn) {
  	addDialogFunction(tag, fn);
  	bitsy.kitsy.deferredDialogFunctions = bitsy.kitsy.deferredDialogFunctions || {};
  	var deferred = bitsy.kitsy.deferredDialogFunctions[tag] = [];
  	inject$1(
  		/(var functionMap = new Map\(\);)/,
  		'$1functionMap.set("' + tag + '", function(e, p, o){ kitsy.deferredDialogFunctions.' + tag + '.push({e:e,p:p}); o(null); });'
  	);
  	// Hook into the dialog finish event and execute the actual function
  	after('onExitDialog', function () {
  		while (deferred.length) {
  			var args = deferred.shift();
  			bitsy.kitsy.dialogFunctions[tag](args.e, args.p, args.o);
  		}
  	});
  	// Hook into the game reset and make sure data gets cleared
  	after('clearGameData', function () {
  		deferred.length = 0;
  	});
  }
  
  
  
  // map of maps
  var maps;
  after('load_game', function () {
  	maps = {
  		spr: bitsy.sprite,
  		sprite: bitsy.sprite,
  		itm: bitsy.item,
  		item: bitsy.item,
  	};
  });
  
  function editDialog(environment, parameters) {
  	// parse parameters
  	var params = parameters[0].split(/,\s?/);
  	params[0] = (params[0] || "").toLowerCase();
  	var mapId = params[0];
  	var tgtId = params[1];
  	var newDialog = params[2] || "";
  
  	if (!mapId || !tgtId) {
  		throw new Error('Image expects three parameters: "map, target, newDialog", but received: "' + params.join(', ') + '"');
  	}
  
  	// get objects
  	var mapObj = maps[mapId];
  	if (!mapObj) {
  		throw new Error('Invalid map "' + mapId + '". Try "SPR" or "ITM" instead.');
  	}
  	var tgtObj = getImage(tgtId, mapObj);
  	if (!tgtObj) {
  		throw new Error('Target "' + tgtId + '" was not the id/name of a ' + mapId + '.');
  	}
  	bitsy.dialog[tgtObj.dlg] = newDialog;
  }
  
  // hook up the dialog tag
  addDeferredDialogTag('dialog', editDialog);
  
  // disable bitsy's dialog caching
  inject(/startDialog\(dialogStr,dialogId\);/g, 'startDialog(dialogStr);');
  
  }(window));
  