code:
package classes
{
	import classes.internals.ButtonData;
	import classes.internals.GuiOutput;
	import classes.internals.*;
	import flash.utils.setTimeout;
	import coc.view.CoCButton;

	/**
	 * Class to replace the old and somewhat outdated output-system, which mostly uses the include-file includes/engineCore.as
	 * @since  08.08.2016
	 * @author Stadler76
	 */
	public class Output extends Utils implements GuiOutput
	{
		public static const MAX_BUTTON_INDEX:int = 14;

		import flash.text.TextFormat;
		import classes.GlobalFlags.kGAMECLASS;
		import classes.GlobalFlags.kFLAGS;
		import coc.view.MainView;

		private static var _instance:Output = new Output();
		private static const HISTORY_MAX:int = 20;

		public function Output()
		{
			if (_instance != null)
			{
				throw new Error("Output can only be accessed through Output.init()");
			}
		}

		public static function init():Output { return _instance; }

		protected var _currentText:String = "";
		protected var _history:Array = [""];

		public function forceUpdate():void { kGAMECLASS.forceUpdate(); }

		/**
		 * Passes the text through the parser and adds it to the output-buffer
		 *
		 * If you want to clear the output before adding text, use clear(true) or just clear()
		 *
		 * @param   text    The text to be added
		 * @return  The instance of the class to support the 'Fluent interface' aka method-chaining
		 */
		public function text(text:String):GuiOutput
		{
			// This is cleaup in case someone hits the Data or new-game button when the event-test window is shown.
			// It's needed since those buttons are available even when in the event-tester
			kGAMECLASS.mainView.hideTestInputPanel();

			text = kGAMECLASS.parser.recursiveParser(text);
			record(text);
			_currentText += text;

			return this;
		}

		/**
		 * Flushes the buffered output to the GUI aka displaying it
		 *
		 * This doesn't clear the output buffer, so you can add more text after that and flush it again.
		 * flush() always ends a method chain, so you need to start a new one.
		 *
		 * Note: Calling this with open formatting tags can result in strange behaviour,
		 * e.g. all text will be formatted instead of only a section.
		 */
		public function flush():void
		{
			kGAMECLASS.mainViewManager.setText(_currentText);
			//kGAMECLASS.credits.show();
		}

		/**
		 * Adds a formatted headline to the output-buffer
		 *
		 * @param	headLine    The headline to be formatted and added
		 * @return  The instance of the class to support the 'Fluent interface' aka method-chaining
		 */
		public function header(headLine:String):GuiOutput
		{
			return text(formatHeader(headLine));
		}

		/**
		 * Returns the formatted headline
		 *
		 * @param	headLine    The headline to be formatted
		 * @return  The formatted headline
		 */
		public function formatHeader(headLine:String):String
		{
			return "" + headLine + "\n";
		}

		/**
		 * Clears the output-buffer
		 *
		 * @param   hideMenuButtons   Set this to true to hide the menu-buttons (rarely used)
		 * @return  The instance of the class to support the 'Fluent interface' aka method-chaining
		 */
		public function clear(hideMenuButtons:Boolean = false):GuiOutput
		{
			if (hideMenuButtons) {
				forceUpdate();
				if (kGAMECLASS.gameState != 3) kGAMECLASS.mainView.hideMenuButton(MainView.MENU_DATA);
				kGAMECLASS.mainView.hideMenuButton(MainView.MENU_APPEARANCE);
				kGAMECLASS.mainView.hideMenuButton(MainView.MENU_LEVEL);
				kGAMECLASS.mainView.hideMenuButton(MainView.MENU_PERKS);
				kGAMECLASS.mainView.hideMenuButton(MainView.MENU_STATS);
			}
			nextEntry();
			_currentText = "";
			kGAMECLASS.mainView.clearOutputText();
			kGAMECLASS.mainView.nameBox.visible = false;
			kGAMECLASS.mainView.aCb.visible = false;
			kGAMECLASS.mainView.resetMainFocus();
			//kGAMECLASS.credits.clear();
			return this;
		}

		public function clearText():GuiOutput
		{
			nextEntry();
			_currentText = "";
			kGAMECLASS.mainView.clearOutputText();
			//kGAMECLASS.credits.clear();
			return this;
		}

		/**
		 * Adds raw text to the output without passing it through the parser
		 *
		 * If you want to clear the output before adding raw text, use clear(true) or just clear()
		 * The second param `purgeText:Boolean = false` is not supported anymore in favor of using clear() and will never return.
		 *
		 * @param   text    The text to be added to the output-buffer
		 * @return  The instance of the class to support the 'Fluent interface' aka method-chaining
		 */
		public function raw(text:String):Output
		{
			_currentText += text;
			record(text);
			//mainView.setOutputText(_currentText);
			return this;
		}

		/**
		 * Appends a raw text to history, appending to last entry
		 * @param text The text to be added to history
		 * @return this
		 */
		public function record(text:String):Output {
			if (_history.length==0) _history=[""];
			_history[_history.length-1] += text;
			return this;
		}
		/**
		 * Appends a new text entry to the history
		 * @param text The text to be added to history as a separate and complete entry
		 * @return this
		 */
		public function entry(text:String):Output {
			nextEntry();
			record(text);
			nextEntry();
			return this;
		}

		/**
		 * Finishes the history entry, starting a new one and removing old entries as
		 * history grows
		 * @return this
		 */
		public function nextEntry():Output {
			if (_history.length==0) _history=[""];
			if (_history[_history.length-1].length>0) _history.push("");
			while(_history.length > HISTORY_MAX) _history.shift();
			return this;
		}

		/**
		 * Clears current history enter -- removes everything recorded since last 'clearOutput', 'entry', or 'nextEntry'
		 * @return this
		 */
		public function clearCurrentEntry():Output {
			if (_history.length==0) _history=[""];
			_history[_history.length-1] = "";
			return this;
		}
		/**
		 * @return this
		 */
		public function clearHistory():Output {
			_history = [""];
			return this;
		}

		/**
		 * Displays all recorded history (with current text at the end), and scrolls to the bottom.
		 * @return this
		 */
		public function showHistory():Output
		{
			clear();
			var txt:String = _history.join("
");
			nextEntry();
			raw(txt);
			clearCurrentEntry();
			// On the next animation frame
			setTimeout(function():void {
				kGAMECLASS.mainView.scrollBar.value = kGAMECLASS.mainView.scrollBar.maximum;
			},0);
			return this;
		}

		/**
		 * Get the old stats variable from CoC.
		 * @return legacy oldstats variable
		 */
		private function getOldStats():Object {
			return kGAMECLASS.oldStats;
		}

		public function showUpDown():void {
			var oldStats:Object = getOldStats();

			function _oldStatNameFor(statName:String):String {
				return 'old' + statName.charAt(0).toUpperCase() + statName.substr(1);
			}

			var statName:String;
			var	oldStatName:String;
			var allStats:Array;

			allStats = ["str", "tou", "spe", "inte", "lib", "sens", "cor", "HP", "lust", "fatigue", "hunger"];

			for each(statName in allStats) {
				oldStatName = _oldStatNameFor(statName);

				if (kGAMECLASS.player[statName] > oldStats[oldStatName]) {
					kGAMECLASS.mainView.statsView.showStatUp(statName);
				}
				if (kGAMECLASS.player[statName] < oldStats[oldStatName]) {
					kGAMECLASS.mainView.statsView.showStatDown(statName);
				}
			}
		}

		public function buttonIsVisible(index:int):Boolean {
			if ( index < 0 || index > MAX_BUTTON_INDEX ) {
				return undefined;
			}
			else {
				return kGAMECLASS.output.button(index).visible;
			}
		}

		public function buttonTextIsOneOf(index:int, possibleLabels:Array):Boolean {
			var label:String;
			var buttonText:String;

			buttonText = this.getButtonText(index);

			return (possibleLabels.indexOf(buttonText) != -1);
		}

		public function getButtonText(index:int):String {
			var matches:*;

			if (index < 0 || index > MAX_BUTTON_INDEX) {
				return '';
			}
			else {
				return kGAMECLASS.output.button(index).labelText;
			}
		}

		public function generateMenu(buttonDataList:Array,returnFunc:Function = null, startIndex:int = 0):void {
			menu();
			if(returnFunc == null) returnFunc = kGAMECLASS.playerMenu;
			var i:int;
			for (i = startIndex; i < buttonDataList.length; i++){
				if (i - startIndex == 13) {
					if (buttonDataList.length == 14) addButton(14, "Next", generateMenu, buttonDataList,curry(generateMenu,buttonDataList,returnFunc,startIndex), startIndex+13);
					else continue;
				}
				else if (i - startIndex == 14 && buttonDataList.length > 14 + startIndex){
					addButton(14, "Next", generateMenu, buttonDataList,curry(generateMenu,buttonDataList,returnFunc,startIndex), startIndex+13);
					break;
				}
				else {
					if (buttonDataList[i].disabled) addButtonDisabled(i-startIndex, buttonDataList[i].text, buttonDataList[i].toolTipText, buttonDataList[i].toolTipHeader).colorize(buttonDataList[i].color || [1,1,1]);
					else addButton(i-startIndex, buttonDataList[i].text, buttonDataList[i].callback, buttonDataList[i].arg1, buttonDataList[i].arg2, buttonDataList[i].arg3, buttonDataList[i].toolTipText, buttonDataList[i].toolTipHeader).colorize(buttonDataList[i].color || [1,1,1]);
				}
			}
			if (buttonDataList.length < 14) addButton(14, "Back", returnFunc);
			else addButton(13, "Back", returnFunc);
		}

		/**
		 * Adds a button.
		 * @param	pos Determines the position. Starts at 0. (First row is 0-4, second row is 5-9, third row is 10-14.)
		 * @param	text Determines the text that will appear on button.
		 * @param	func1 Determines what function to trigger.
		 * @param	arg1 Pass argument #1 to func1 parameter.
		 * @param	arg2 Pass argument #1 to func1 parameter.
		 * @param	arg3 Pass argument #1 to func1 parameter.
		 * @param	toolTipText The text that will appear on tooltip when the mouse goes over the button.
		 * @param	toolTipHeader The text that will appear on the tooltip header. If not specified, it defaults to button text.
		 */
		public function addButton(pos:int, text:String = "", func1:Function = null, arg1:* = -9000, arg2:* = -9000, arg3:* = -9000, toolTipText:String = "", toolTipHeader:String = ""):CoCButton {
			var btn:CoCButton = kGAMECLASS.output.button(pos);
			if (func1==null) {
				return btn.hide();
			}
			var callback:Function;

			//Removes sex-related button in SFW mode.
			if (kGAMECLASS.flags[kFLAGS.SFW_MODE] > 0) {
				if (text.indexOf("Sex") != -1 || text.indexOf("Threesome") != -1 ||  text.indexOf("Foursome") != -1 || text == "Watersports" || text == "Make Love" || text == "Use Penis" || text == "Use Vagina" || text.indexOf("Fuck") != -1 || text.indexOf("Ride") != -1 || (text.indexOf("Mount") != -1 && text.indexOf("Mountain") == -1) || text.indexOf("Vagina") != -1) {
					return btn.hide();
				}
			}
			callback = kGAMECLASS.createCallBackFunction(func1, arg1, arg2, arg3);

			btn.show(text,callback, toolTipText, toolTipHeader);
			flush();
			return btn;
		}

		/**
		 * Adds a button at the next unused (not currently visible) button position. Otherwise identical to addButton.
		 * @param	text Determines the text that will appear on button.
		 * @param	func1 Determines what function to trigger.
		 * @param	arg1 Pass argument #1 to func1 parameter.
		 * @param	arg2 Pass argument #1 to func1 parameter.
		 * @param	arg3 Pass argument #1 to func1 parameter.
		 * @param	toolTipText The text that will appear on tooltip when the mouse goes over the button.
		 * @param	toolTipHeader The text that will appear on the tooltip header. If not specified, it defaults to button text.
		 */
		public function addNextButton(text:String = "", func1:Function = null, arg1:* = -9000, arg2:* = -9000, arg3:* = -9000, toolTipText:String = "", toolTipHeader:String = ""):CoCButton {
			var pos:int = -1;
			for (var i:int = 0; i <= MAX_BUTTON_INDEX; i++) {
				if (!kGAMECLASS.mainView.bottomButtons[i].visible) {
					pos = i;
					break;
				}
			}
			if (pos == -1) return null;

			var btn:CoCButton = kGAMECLASS.output.button(pos);
			if (func1==null) {
				return btn.hide();
			}
			var callback:Function;

			//Removes sex-related button in SFW mode.
			if (kGAMECLASS.flags[kFLAGS.SFW_MODE] > 0) {
				if (text.indexOf("Sex") != -1 || text.indexOf("Threesome") != -1 ||  text.indexOf("Foursome") != -1 || text == "Watersports" || text == "Make Love" || text == "Use Penis" || text == "Use Vagina" || text.indexOf("Fuck") != -1 || text.indexOf("Ride") != -1 || (text.indexOf("Mount") != -1 && text.indexOf("Mountain") == -1) || text.indexOf("Vagina") != -1) {
					return btn.hide();
				}
			}
			callback = kGAMECLASS.createCallBackFunction(func1, arg1, arg2, arg3);

			btn.show(text,callback, toolTipText, toolTipHeader);
			flush();
			return btn;
		}

		public function addNextButtonDisabled(text:String = "", toolTipText:String = "", toolTipHeader:String = ""):CoCButton {
			var pos:int = -1;
			for (var i:int = 0; i <= MAX_BUTTON_INDEX; i++) {
				if (!kGAMECLASS.mainView.bottomButtons[i].visible) {
					pos = i;
					break;
				}
			}
			if (pos == -1) return null;

			var btn:CoCButton = kGAMECLASS.output.button(pos);
			//Removes sex-related button in SFW mode.
			if (kGAMECLASS.flags[kFLAGS.SFW_MODE] > 0) {
				if (text.indexOf("Sex") != -1 || text.indexOf("Threesome") != -1 ||  text.indexOf("Foursome") != -1 || text == "Watersports" || text == "Make Love" || text == "Use Penis" || text == "Use Vagina" || text.indexOf("Fuck") != -1 || text.indexOf("Ride") != -1 || (text.indexOf("Mount") != -1 && text.indexOf("Mountain") == -1) || text.indexOf("Vagina") != -1) {
					//trace("Button removed due to SFW mode.");
					return btn.hide();
				}
			}

			btn.showDisabled(text,toolTipText,toolTipHeader);
			return btn;
		}

		public function addButtonDisabled(pos:int, text:String = "", toolTipText:String = "", toolTipHeader:String = ""):CoCButton {
			var btn:CoCButton = kGAMECLASS.output.button(pos);
			//Removes sex-related button in SFW mode.
			if (kGAMECLASS.flags[kFLAGS.SFW_MODE] > 0) {
				if (text.indexOf("Sex") != -1 || text.indexOf("Threesome") != -1 ||  text.indexOf("Foursome") != -1 || text == "Watersports" || text == "Make Love" || text == "Use Penis" || text == "Use Vagina" || text.indexOf("Fuck") != -1 || text.indexOf("Ride") != -1 || (text.indexOf("Mount") != -1 && text.indexOf("Mountain") == -1) || text.indexOf("Vagina") != -1) {
					//trace("Button removed due to SFW mode.");
					return btn.hide();
				}
			}

			btn.showDisabled(text,toolTipText,toolTipHeader);
			return btn;
		}

		public function button(pos:int):CoCButton {
			return kGAMECLASS.mainView.bottomButtons[pos];
		}

		/**
		 * Removes a button.
		 * @param	arg The position to remove a button. (First row is 0-4, second row is 5-9, third row is 10-14.)
		 */
		public function removeButton(arg:*):void {
			var buttonToRemove:int = 0;
			if (arg is String) {
				buttonToRemove = kGAMECLASS.mainView.indexOfButtonWithLabel( arg as String );
			}
			if (arg is Number) {
				if (arg < 0 || arg > MAX_BUTTON_INDEX) return;
				buttonToRemove = int(arg);
			}
			kGAMECLASS.mainView.hideBottomButton( buttonToRemove );
		}

		/**
		 * Hides all bottom buttons.
		 *
		 * Note: Calling this with open formatting tags can result in strange behaviour,
		 * e.g. all text will be formatted instead of only a section.
		 */
		public function menu():void { //The newer, simpler menu - blanks all buttons so addButton can be used
			for (var i:int = 0; i <= MAX_BUTTON_INDEX; i++) {
				kGAMECLASS.mainView.hideBottomButton(i);
			}
			flush();
		}

		/**
		 * Clears all button and adds a 'Yes' and a 'No' button.
		 * @param	eventYes The event parser or function to call if 'Yes' button is pressed.
		 * @param	eventNo The event parser or function to call if 'No' button is pressed.
		 */
		public function doYesNo(eventYes:Function, eventNo:Function):void { //New typesafe version
			menu();
			addButton(0, "Yes", eventYes);
			addButton(1, "No", eventNo);
		}

		/**
		 * Clears all button and adds a 'Next' button.
		 * @param	event The event function to call if the button is pressed.
		 */
		public function doNext(event:Function):void { //Now typesafe
			//Prevent new events in combat from automatically overwriting a game over.
			if (kGAMECLASS.mainView.getButtonText(0).indexOf("Game Over") != -1) {
				return;
			}
			menu();
			addButton(0, "Next", event);
		}

		/**
		 * Used to update the display of statistics
		 */
		public function statScreenRefresh():void {
			kGAMECLASS.mainView.statsView.show(); // show() method refreshes.
			kGAMECLASS.mainViewManager.refreshStats();
		}

		/**
		 * Show the stats pane. (Name, stats and attributes)
		 */
		public function showStats():void {
			kGAMECLASS.mainView.statsView.show();
			kGAMECLASS.mainViewManager.refreshStats();
			kGAMECLASS.mainViewManager.tweenInStats();
		}

		/**
		 * Hide the stats pane. (Name, stats and attributes)
		 */
		public function hideStats():void {
			if (!kGAMECLASS.mainViewManager.buttonsTweened) {
				kGAMECLASS.mainView.statsView.hide();
			}

			kGAMECLASS.mainViewManager.tweenOutStats();
		}

		/**
		 * Hide the top buttons.
		 */
		public function hideMenus():void {
			kGAMECLASS.mainView.hideAllMenuButtons();
		}

		/**
		 * Hides the up/down arrow on stats pane.
		 */
		public function hideUpDown():void {
			kGAMECLASS.mainView.statsView.hideUpDown();
			//Clear storage values so up/down arrows can be properly displayed
			kGAMECLASS.oldStats.oldStr = 0;
			kGAMECLASS.oldStats.oldTou = 0;
			kGAMECLASS.oldStats.oldSpe = 0;
			kGAMECLASS.oldStats.oldInte = 0;
			kGAMECLASS.oldStats.oldLib = 0;
			kGAMECLASS.oldStats.oldSens = 0;
			kGAMECLASS.oldStats.oldCor = 0;
			kGAMECLASS.oldStats.oldHP = 0;
			kGAMECLASS.oldStats.oldLust = 0;
			kGAMECLASS.oldStats.oldFatigue = 0;
			kGAMECLASS.oldStats.oldHunger = 0;
		}
	}
}


code:
package classes.Parser
{
	import classes.GlobalFlags.kGAMECLASS;
	import classes.StatusEffects;
	import mx.logging.ILogger;
	import classes.internals.*;

	public class Parser
	{
		private static const LOGGER:ILogger = LoggerFactory.getLogger(Parser);

		private var _ownerClass:*;			// main game class. Variables are looked-up in this class.
		private var _settingsClass:*;		// global static class used for shoving conf vars around

		public var sceneParserDebug:Boolean = false;

		public var mainParserDebug:Boolean = false;
		public var lookupParserDebug:Boolean = false;
		public var conditionalDebug:Boolean = false;
		public var printCcntentDebug:Boolean = false;
		public var printConditionalEvalDebug:Boolean = false;
		public var printIntermediateParseStateDebug:Boolean = false;
		public var logErrors:Boolean = true;

		public function Parser(ownerClass:*, settingsClass:*)
		{
			this._ownerClass = ownerClass;
			this._settingsClass = settingsClass;
		}

		/*
		Parser Syntax:

		// Querying simple PC stat nouns:
			[noun]

		Conditional statements:
		// Simple if statement:
			[if (condition) OUTPUT_IF_TRUE]
		// If-Else statement
			[if (condition) OUTPUT_IF_TRUE | OUTPUT_IF_FALSE]
			// Note - Implicit else indicated by presence of the "|"

		// Object aspect descriptions
			[object aspect]
			// gets the description of aspect "aspect" of object/NPC/PC "object"
			// Eventually, I want this to be able to use introspection to access class attributes directly
			// Maybe even manipulate them, though I haven't thought that out much at the moment.

		// Gender Pronoun Weirdness:
		// PRONOUNS: The parser uses Elverson/Spivak Pronouns specifically to allow characters to be written with non-specific genders.
		// http://en.wikipedia.org/wiki/Spivak_pronoun
		//
		// Cheat Table:
		//           | Subject    | Object       | Possessive Adjective | Possessive Pronoun | Reflexive         |
		// Agendered | ey laughs  | I hugged em  | eir heart warmed     | that is eirs       | ey loves emself   |
		// Masculine | he laughs  | I hugged him | his heart warmed     | that is his        | he loves himself  |
		// Feminine  | she laughs | I hugged her | her heart warmed     | that is hers       | she loves herself |

		[screen (SCREEN_NAME) | screen text]
			// creates a new screen/page.

		[button (SCREEN_NAME)| button_text]
			// Creates a button which jumps to SCREEN_NAME when clicked

		*/

		// this.parserState is used to store the scene-parser state.
		// it is cleared every time recursiveParser is called, and then any scene tags are added
		// as parserState["sceneName"] = "scene content"

		public var parserState:Object = new Object();

		// provides singleArgConverters
		include "./singleArgLookups.as";

		// Does lookup of single argument tags ("[cock]", "[armor]", etc...) in singleArgConverters
		// Supported variables are the options listed in the above
		// singleArgConverters object. If the passed argument is found in the above object,
		// the corresponding anonymous function is called, and it's return-value is returned.
		// If the arg is not present in the singleArgConverters object, an error message is
		// returned.
		// ALWAYS returns a string
		private function convertSingleArg(arg:String):String
		{
			var argResult:String = null;
			var capitalize:Boolean = isUpperCase(arg.charAt(0));

			var argLower:String;
			argLower = arg.toLowerCase()
			if (argLower in singleArgConverters)
			{
				//if (logErrors) trace("WARNING: Found corresponding anonymous function");
				argResult = singleArgConverters[argLower]();

				if (lookupParserDebug) LOGGER.warn("WARNING: Called, return = ", argResult);

				if (capitalize)
					argResult = capitalizeFirstWord(argResult);

				return argResult;
			}
			else
			{

				// ---------------------------------------------------------------------------------
				// TODO: Get rid of this shit.
				// UGLY hack to patch legacy functionality in TiTS
				// This needs to go eventually

				var descriptorArray:Array = arg.split(".");

				obj = this.getObjectFromString(this._ownerClass, descriptorArray[0]);
				if (obj == null)		// Completely bad tag
				{
					if (lookupParserDebug || logErrors) LOGGER.warn("WARNING: Unknown subject in " + arg);
					return "!Unknown subject in \"" + arg + "\"!";
				}
				if (obj.hasOwnProperty("getDescription") && arg.indexOf(".") > 0)
				{
					return obj.getDescription(descriptorArray[1], "");
				}
				// end hack
				// ---------------------------------------------------------------------------------

				if (lookupParserDebug) LOGGER.warn("WARNING: Lookup Arg = ", arg);
				var obj:*;
				obj = this.getObjectFromString(this._ownerClass, arg);
				if (obj != null)
				{
					if (obj is Function)
					{
						if (lookupParserDebug) LOGGER.warn("WARNING: Found corresponding function in owner class");
						return obj();
					}
					else
					{
						if (lookupParserDebug) LOGGER.warn("WARNING: Found corresponding aspect in owner class");
						return String(obj); 	// explicit cast probably not needed
					}
				}
				else
				{
					if (lookupParserDebug || logErrors) LOGGER.warn("WARNING: No lookup found for", arg, " search result is: ", obj);
					return "!Unknown tag \"" + arg + "\"!";
				}
			}

		}

		// provides twoWordNumericTagsLookup and twoWordTagsLookup, which use
		// cockLookups/cockHeadLookups, and rubiLookups/arianLookups respectively
		include "./doubleArgLookups.as";

		private function convertDoubleArg(inputArg:String):String
		{
			var argResult:String = null;

			var thing:*;

			var argTemp:Array = inputArg.split(" ");
			if (argTemp.length != 2)
			{
				if (logErrors) LOGGER.warn("WARNING: Not actually a two word tag! " + inputArg);
				return "!Not actually a two-word tag!\"" + inputArg + "\"!"
			}
			var subject:String = argTemp[0];
			var aspect:* = argTemp[1];
			var subjectLower:String = argTemp[0].toLowerCase();
			var aspectLower:* = argTemp[1].toLowerCase();

			if (lookupParserDebug) LOGGER.warn("WARNING: Doing lookup for subject", subject, " aspect ", aspect);

			// Figure out if we need to capitalize the resulting text
			var capitalize:Boolean = isUpperCase(aspect.charAt(0));

			// Only perform lookup in twoWordNumericTagsLookup if aspect can be cast to a valid number

			if ((subjectLower in twoWordNumericTagsLookup) && !isNaN(Number(aspect)))
			{
				aspectLower = Number(aspectLower);

				if (lookupParserDebug) LOGGER.warn("WARNING: Found corresponding anonymous function");
				argResult = twoWordNumericTagsLookup[subjectLower](aspectLower);
				if (capitalize)
					argResult = capitalizeFirstWord(argResult);
				if (lookupParserDebug) LOGGER.warn("WARNING: Called two word numeric lookup, return = ", argResult);
				return argResult;
			}

			// aspect isn't a number. Look for subject in the normal twoWordTagsLookup
			if (subjectLower in twoWordTagsLookup)
			{
				if (aspectLower in twoWordTagsLookup[subjectLower])
				{

					if (lookupParserDebug) LOGGER.warn("WARNING: Found corresponding anonymous function");
					argResult = twoWordTagsLookup[subjectLower][aspectLower]();
					if (capitalize)
						argResult = capitalizeFirstWord(argResult);
					if (lookupParserDebug) LOGGER.warn("WARNING: Called two word lookup, return = ", argResult);
					return argResult;
				}
				else
				{
					if (logErrors) LOGGER.warn("WARNING: Unknown aspect in two-word tag. Arg: " + inputArg + " Aspect: " + aspectLower);
					return "!Unknown aspect in two-word tag \"" + inputArg + "\"! ASCII Aspect = \"" + aspectLower + "\"";
				}

			}

			if (lookupParserDebug) LOGGER.warn("WARNING: trying to look-up two-word tag in parent")

			// ---------------------------------------------------------------------------------
			// TODO: Get rid of this shit.
			// UGLY hack to patch legacy functionality in TiTS
			// This needs to go eventually

			var descriptorArray:Array = subject.split(".");

			thing = this.getObjectFromString(this._ownerClass, descriptorArray[0]);
			if (thing == null)		// Completely bad tag
			{
				if (logErrors) LOGGER.warn("WARNING: Unknown subject in " + inputArg);
				return "!Unknown subject in \"" + inputArg + "\"!";
			}
			if (thing.hasOwnProperty("getDescription") && subject.indexOf(".") > 0)
			{
				if (argTemp.length > 1) {
					return thing.getDescription(descriptorArray[1], aspect);
				}
				else {
					return thing.getDescription(descriptorArray[1], "");
				}
			}
			// end hack
			// ---------------------------------------------------------------------------------

			var aspectLookup:* = this.getObjectFromString(this._ownerClass, aspect);

			if (thing != null)
			{
				if (thing is Function)
				{
					if (lookupParserDebug) LOGGER.warn("WARNING: Found corresponding function in owner class");
					return thing(aspect);
				}
				else if (thing is Array)
				{
					var indice:Number = Number(aspectLower);
					if (isNaN(indice))
					{
						if (logErrors) LOGGER.warn("WARNING: Cannot use non-number as indice to Array. Arg " + inputArg + " Subject: " + subject + " Aspect: " + aspect);
						return "Cannot use non-number as indice to Array \"" + inputArg + "\"! Subject = \"" + subject + ", Aspect = " + aspect + "\";
					}
					else
						return thing[indice]
				}
				else if (thing is Object)
				{

					if (thing.hasOwnProperty(aspectLookup))
						return thing[aspectLookup]

					else if (thing.hasOwnProperty(aspect))
						return thing[aspect]
					else
					{
						if (logErrors) LOGGER.warn("WARNING: Object does not have aspect as a member. Arg: " + inputArg + " Subject: " + subject + " Aspect:" + aspect + " or " + aspectLookup);
						return "Object does not have aspect as a member \"" + inputArg + "\"! Subject = \"" + subject + ", Aspect = " + aspect + " or " + aspectLookup + "\";
					}
				}
				else
				{
					// This will work, but I don't know why you'd want to
					// the aspect is just ignored
					if (lookupParserDebug) LOGGER.warn("WARNING: Found corresponding aspect in owner class");
					return String(thing);
				}
			}

			if (lookupParserDebug || logErrors) LOGGER.warn("WARNING: No lookup found for", inputArg, " search result is: ", thing);
			return "!Unknown subject in two-word tag \"" + inputArg + "\"! Subject = \"" + subject + ", Aspect = " + aspect + "\";
			// return "!Unknown tag \"" + arg + "\"!";

			return argResult;
		}

		// has to be done in this weird way because Number && Boolean = Boolean, but you need the actual number sometimes since the result might be used in some expression on a parent node.
		private function setReturnVal(arg:*,negate:Boolean):*{
			if(negate){
				return !arg;
			}else{
				return arg;
			}
		}

		// Provides the conditionalOptions object
		include "./conditionalConverters.as";

		// converts a single argument to a conditional to
		// the relevant value, either by simply converting to a Number, or
		// through lookup in the above conditionalOptions oject, and then calling the
		// relevant function
		// Realistally, should only return either boolean or numbers.
		private function convertConditionalArgumentFromStr(arg:String):*
		{
			// convert the string contents of a conditional argument into a meaningful variable.
			var argLower:* = arg.toLowerCase()
			var argResult:* = -1;
			var negate:Boolean = arg.indexOf('!') == 0;
			// Note: Case options MUST be ENTIRELY lower case. The comparaison string is converted to
			// lower case before the switch:case section

			// Try to cast to a number. If it fails, go on with the switch/case statement.
			if (!isNaN(Number(arg)))
			{
				if (printConditionalEvalDebug) LOGGER.warn("WARNING: Converted to float. Number = ", Number(arg))
				return setReturnVal(Number(arg),negate);
			}
			if (argLower in conditionalOptions)
			{
				if (printConditionalEvalDebug) LOGGER.warn("WARNING: Found corresponding anonymous function");
				argResult = conditionalOptions[argLower]();
				if (printConditionalEvalDebug) LOGGER.warn("WARNING: Called, return = ", argResult);
				return setReturnVal(argResult,negate);
			}

			var obj:* = this.getObjectFromString(this._ownerClass, arg);

			if (printConditionalEvalDebug) LOGGER.warn("WARNING: Looked up ", arg, " in ", this._ownerClass, "Result was:", obj);
			if (obj != null)
			{
				if (printConditionalEvalDebug) LOGGER.warn("WARNING: Found corresponding function for conditional argument lookup.");

				if (obj is Function)
				{
					if (printConditionalEvalDebug) LOGGER.warn("WARNING: Found corresponding function in owner class");
					argResult = Number(obj());
					return setReturnVal(argResult,negate);
				}
				else
				{
					if (printConditionalEvalDebug) LOGGER.warn("WARNING: Found corresponding aspect in owner class");
					argResult = Number(obj);
					return setReturnVal(argResult,negate);
				}
			}
			else
			{
				if (printConditionalEvalDebug || logErrors) LOGGER.warn("WARNING: No lookups found!");
				return null
			}

			if (printConditionalEvalDebug || LogErrors) LOGGER.warn("WARNING: Could not convert to number. Evaluated ", arg, " as", argResult)
			return argResult;
		}

		// Evaluates the conditional section of an if-statement.
		// Does the proper parsing and look-up of any of the special nouns
		// which can be present in the conditional
		private function evalConditionalStatementStr(textCond:String):Boolean
		{
			// Evaluates a conditional statement:
			// (varArg1 [conditional] varArg2)
			// varArg1 & varArg2 can be either numbers, or any of the
			// strings in the "conditionalOptions" object above.
			// numbers (which are in string format) are converted to a Number type
			// prior to comparison.

			// supports multiple comparison operators:
			// "=", "=="  - Both are Equals or equivalent-to operators
			// "<", ">    - Less-Than and Greater-Than
			// "<=", ">=" - Less-than or equal, greater-than or equal
			// "!="       - Not equal

			// proper, nested parsing of statements is a WIP
			// and not supported at this time.

			var isExp:RegExp = /([\w\.]+)\s?(==|=|!=|<|>|<=|>=)\s?([\w\.]+)/;
			var expressionResult:Object = isExp.exec(textCond);
			if (!expressionResult)
			{
				var condArg:* = convertConditionalArgumentFromStr(textCond);
				if (condArg != null)
				{
					if (printConditionalEvalDebug) LOGGER.warn("WARNING: Conditional \"", textCond, "\" Evaluated to: \"", condArg, "\"")
					return condArg
				}
				else
				{
					if (logErrors) LOGGER.warn("WARNING: Invalid conditional! \"(", textCond, ")\" Conditionals must be in format:")
					if (logErrors) LOGGER.warn("WARNING:  \"({statment1} (==|=|!=|<|>|<=|>=) {statement2})\" or \"({valid variable/function name})\". ")
					return false
				}
			}
			if (printConditionalEvalDebug) LOGGER.warn("WARNING: Expression = ", textCond, "Expression result = [", expressionResult, "], length of = ", expressionResult.length);

			var condArgStr1:String    = expressionResult[1];
			var operator:String       = expressionResult[2];
			var condArgStr2:String    = expressionResult[3];

			var retVal:Boolean = false;

			var condArg1:* = convertConditionalArgumentFromStr(condArgStr1);
			var condArg2:* = convertConditionalArgumentFromStr(condArgStr2);

			//Perform check
			if (operator == "=")
				retVal = (condArg1 == condArg2);
			else if (operator == ">")
				retVal = (condArg1 > condArg2);
			else if (operator == "==")
				retVal = (condArg1 == condArg2);
			else if (operator == "<")
				retVal = (condArg1 < condArg2);
			else if (operator == ">=")
				retVal = (condArg1 >= condArg2);
			else if (operator == "<=")
				retVal = (condArg1 <= condArg2);
			else if (operator == "!=")
				retVal = (condArg1 != condArg2);
			else
				retVal = (condArg1 != condArg2);

			if (printConditionalEvalDebug) LOGGER.warn("WARNING: Check: " + condArg1 + " " + operator + " " + condArg2 + " result: " + retVal);

			return retVal;
		}

		// Splits the result from an if-statement.
		// ALWAYS returns an array with two strings.
		// if there is no else, the second string is empty.
		private function splitConditionalResult(textCtnt:String): Array
		{
			// Splits the conditional section of an if-statemnt in to two results:
			// [if (condition) OUTPUT_IF_TRUE]
			//                 ^ This Bit   ^
			// [if (condition) OUTPUT_IF_TRUE | OUTPUT_IF_FALSE]
			//                 ^          This Bit            ^
			// If there is no OUTPUT_IF_FALSE, returns an empty string for the second option.
			if (conditionalDebug) LOGGER.warn("WARNING: ------------------4444444444444444444444444444444444444444444444444444444444-----------------------")
			if (conditionalDebug) LOGGER.warn("WARNING: Split Conditional input string: ", textCtnt)
			if (conditionalDebug) LOGGER.warn("WARNING: ------------------4444444444444444444444444444444444444444444444444444444444-----------------------")

			var ret:Array = new Array("", "");

			var i:int;

			var sectionStart:int = 0;
			var section:int = 0;
			var nestLevel:int = 0;

			for (i = 0; i < textCtnt.length; i += 1)
			{
				switch (textCtnt.charAt(i))
				{
					case "[":    //Statement is nested one level deeper
						nestLevel += 1;
						break;

					case "]":    //exited one level of nesting.
						nestLevel -= 1;
						break;

					case "|":                  // At a conditional split
						if (nestLevel == 0)   // conditional split is only valid in this context if we're not in a nested bracket.
						{
							if (section == 1)  // barf if we hit a second "|" that's not in brackets
							{
								if (this._settingsClass.haltOnErrors) throw new Error("Nested IF statements still a WIP")
								ret = ["Error! Too many options in if statement!",
									"Error! Too many options in if statement!"];
							}
							else
							{
								ret[section] = textCtnt.substring(sectionStart, i);
								sectionStart = i + 1
								section += 1
							}
						}
						break;

					default:
						break;
				}

			}
			ret[section] = textCtnt.substring(sectionStart, textCtnt.length);

			if (conditionalDebug) LOGGER.warn("WARNING: ------------------5555555555555555555555555555555555555555555555555555555555-----------------------")
			if (conditionalDebug) LOGGER.warn("WARNING: Outputs: ", ret)
			if (conditionalDebug) LOGGER.warn("WARNING: ------------------5555555555555555555555555555555555555555555555555555555555-----------------------")

			return ret;
		}

		// Called to evaluate a if statment string, and return the evaluated result.
		// Returns an empty string ("") if the conditional rvaluates to false, and there is no else
		// option.
		private function parseConditional(textCtnt:String, depth:int):String
		{
			// NOTE: enclosing brackets are *not* included in the actual textCtnt string passed into this function
			// they're shown in the below examples simply for clarity's sake.
			// And because that's what the if-statements look like in the raw string passed into the parser
			// The brackets are actually removed earlier on by the recParser() step.

			// parseConditional():
			// Takes the contents of an if statement:
			// [if (condition) OUTPUT_IF_TRUE]
			// [if (condition) OUTPUT_IF_TRUE | OUTPUT_IF_FALSE]
			// splits the contents into an array as such:
			// ["condition", "OUTPUT_IF_TRUE"]
			// ["condition", "OUTPUT_IF_TRUE | OUTPUT_IF_FALSE"]
			// Finally, evalConditionalStatementStr() is called on the "condition", the result
			// of which is used to determine which content-section is returned
			//

			// TODO: (NOT YET) Allows nested condition parenthesis, because I'm masochistic

			// POSSIBLE BUG: A actual statement starting with "if" could be misinterpreted as an if-statement
			// It's unlikely, but I *could* see it happening.
			// I need to do some testing
			// ~~~~Fake-Name

			if (conditionalDebug) LOGGER.warn("WARNING: ------------------2222222222222222222222222222222222222222222222222222222222-----------------------")
			if (conditionalDebug) LOGGER.warn("WARNING: If input string: ", textCtnt)
			if (conditionalDebug) LOGGER.warn("WARNING: ------------------2222222222222222222222222222222222222222222222222222222222-----------------------")

			var ret:Array = new Array("", "", "");	// first string is conditional, second string is the output

			var i:Number = 0;
			var parenthesisCount:Number = 0;

			//var ifText;
			var conditional:*;
			var output:*;

			var condStart:Number = textCtnt.indexOf("(");

			if (condStart != -1)		// If we have any open parenthesis
			{
				for (i = condStart; i < textCtnt.length; i += 1)
				{
					if (textCtnt.charAt(i) == "(")
					{
						parenthesisCount += 1;
					}
					else if (textCtnt.charAt(i) == ")")
					{
						parenthesisCount -= 1;
					}
					if (parenthesisCount == 0)	// We've found the matching closing bracket for the opening bracket at textCtnt[condStart]
					{
						// Pull out the conditional, and then evaluate it.
						conditional = textCtnt.substring(condStart+1, i);
						conditional = setReturnVal(evalConditionalStatementStr(conditional),textCtnt.substring(condStart-1).indexOf("!") == 0);

						// Make sure the contents of the if-statement have been evaluated to a plain-text string before trying to
						// split the base-level if-statement on the "|"
						output = textCtnt.substring(i+1, textCtnt.length)

						// And now do the actual splitting.
						output = splitConditionalResult(output);

						// LOTS of debugging
						if (conditionalDebug) LOGGER.warn("WARNING: prefix = '", ret[0], "' conditional = ", conditional, " content = ", output);
						if (conditionalDebug) LOGGER.warn("WARNING: -0--------------------------------------------------");
						if (conditionalDebug) LOGGER.warn("WARNING: Content Item 1 = ", output[0])
						if (conditionalDebug) LOGGER.warn("WARNING: -1--------------------------------------------------");
						if (conditionalDebug) LOGGER.warn("WARNING: Item 2 = ", output[1]);
						if (conditionalDebug) LOGGER.warn("WARNING: -2--------------------------------------------------");

						if (conditional)
							return recParser(output[0], depth);
						else
							return recParser(output[1], depth);

					}
				}
			}
			else
			{
				if (this._settingsClass.haltOnErrors) throw new Error("Invalid if statement!", textCtnt);
				return "Invalid IF Statement" + textCtnt;
			}
			return "";
		}

		// ---------------------------------------------------------------------------------------------------------------------------------------
		// SCENE PARSING ---------------------------------------------------------------------------------------------------------------
		// ---------------------------------------------------------------------------------------------------------------------------------------

		// attempt to return function "inStr" that is a member of "localThis"
		// Properly handles nested classes/objects, e.g. localThis.herp.derp
		// is returned by getFuncFromString(localThis, "herp.derp");
		// returns the relevant function if it exists, null if it does not.
		private function getObjectFromString(localThis:Object, inStr:String):*
		{
			if (inStr in localThis)
			{
				if (lookupParserDebug) LOGGER.warn("WARNING: item: ", inStr, " in: ", localThis);
				return localThis[inStr];
			}

			if (inStr.indexOf('.') > 0) // *should* be > -1, but if the string starts with a dot, it can't be a valid reference to a nested class anyways.
			{
				var localReference:String;
				var itemName:String;
				localReference = inStr.substr(0, inStr.indexOf('.'));
				itemName = inStr.substr(inStr.indexOf('.')+1);

				// Debugging, what debugging?
				if (lookupParserDebug) LOGGER.warn("WARNING: localReference = ", localReference);
				if (lookupParserDebug) LOGGER.warn("WARNING: itemName = ", itemName);
				if (lookupParserDebug) LOGGER.warn("WARNING: localThis = \"", localThis, "\"");
				if (lookupParserDebug) LOGGER.warn("WARNING: dereferenced = ", localThis[localReference]);

				// If we have the localReference as a member of the localThis, call this function again to further for
				// the item itemName in localThis[localReference]
				// This allows arbitrarily-nested data-structures, by recursing over the . structure in inStr
				if (localReference in localThis)
				{
					if (lookupParserDebug) LOGGER.warn("WARNING: have localReference:", localThis[localReference]);
					return getObjectFromString(localThis[localReference], itemName);
				}
				else
				{
					return null;
				}

			}

			if (lookupParserDebug) LOGGER.warn("WARNING: item: ", inStr, " NOT in: ", localThis);

			return null;

		}

		private function getSceneSectionToInsert(inputArg:String):String
		{
			var argResult:String = null;

			var argTemp:Array = inputArg.split(" ");
			if (argTemp.length != 2)
			{
				return "!Not actually a valid insertSection tag:!\"" + inputArg + "\"!";
			}
			var callName:String = argTemp[0];
			var sceneName:* = argTemp[1];
			var callNameLower:String = argTemp[0].toLowerCase();

			if (sceneParserDebug) LOGGER.warn("WARNING: Doing lookup for sceneSection tag:", callName, " scene name: ", sceneName);

			// this should have been checked before calling.
			if (callNameLower != "insertsection")
				throw new Error("Wat?");

			if (sceneName in this.parserState)
			{
				if (sceneParserDebug) LOGGER.warn("WARNING: Have sceneSection \""+sceneName+"\". Parsing and setting up menu");

				buttonNum = 0;		// Clear the button number, so we start adding buttons from button 0

				// Split up into multiple variables for debugging (it was crashing at one point. Separating the calls let me delineate what was crashing)
				var tmp1:String = this.parserState[sceneName];
				var tmp2:String = recParser(tmp1, 0);			// we have to actually parse the scene now
				//var tmp3:String = Showdown.makeHtml(tmp2)

				return ""; //Fuck Showdown
				//return tmp3;			// and then stick it on the display

				//if (sceneParserDebug) trace("WARNING: Scene contents: \"" + tmp1 + "\" as parsed: \"" + tmp2 + "\"")
			}
			else
			{
				return "Insert sceneSection called with unknown arg \""+sceneName+"\". falling back to the debug pane";

			}
		}

		private var buttonNum:Number;

		// TODO: Make failed scene button lookups fail in a debuggable manner!

		// Parser button event handler
		// This is the event bound to all button events, as well as the function called
		// to enter the parser's cached scenes. If you pass recursiveParser a set of scenes including a scene named
		// "startup", the parser will not exit normally, and will instead enter the "startup" scene at the completion of parsing the
		// input string.
		//
		// the passed seneName string is preferentially looked-up in the cached scene array, and if there is not a cached scene of name sceneName
		// in the cache, it is then looked for as a member of _ownerClass.
		// if the function name is not found in either context, an error *should* be thrown, but at the moment,
		// it just returns to the debugPane
		//
		public function enterParserScene(sceneName:String):String
		{
			var ret:String = "";

			if (sceneParserDebug) LOGGER.warn("WARNING: Entering parser scene: \""+sceneName+"\"");
			if (sceneParserDebug) LOGGER.warn("WARNING: Do we have the scene name? ", sceneName in this.parserState)
			if (sceneName == "exit")
			{
				if (sceneParserDebug) LOGGER.warn("WARNING: Enter scene called to exit");
				// TODO:
				// This needs to change to something else anyways. I need to add the ability to
				// tell the parser where to exit to at some point
				_ownerClass.debugPane();
			}
			else if (sceneName in this.parserState)
			{
				if (sceneParserDebug) LOGGER.warn("WARNING: Have scene \""+sceneName+"\". Parsing and setting up menu");
				_ownerClass.menu();

				buttonNum = 0;		// Clear the button number, so we start adding buttons from button 0

				var tmp1:String = this.parserState[sceneName];
				var tmp2:String = recParser(tmp1, 0);		// we have to actually parse the scene now

				if (sceneParserDebug) LOGGER.warn("WARNING: Scene contents after markdown: \"" + ret + "\"");

				ret = tmp2;
			}
			else if (this.getObjectFromString(_ownerClass, sceneName) != null)
			{
				if (sceneParserDebug) LOGGER.warn("WARNING: Have function \""+sceneName+"\" in this!. Calling.");
				this.getObjectFromString(_ownerClass, sceneName)();
			}
			else
			{
				LOGGER.warn("WARNING: Enter scene called with unknown arg/function \""+sceneName+"\". falling back to the debug pane");
				_ownerClass.doNext(_ownerClass.debugPane);
			}
			return ret
		}

		// Parses the contents of a scene tag, shoves the unprocessed text in the scene object (this.parserState)
		// under the proper name.
		// Scenes tagged as such:
		//
		// [sceneName | scene contents blaugh]
		//
		// This gets placed in this.parserState so this.parserState["sceneName"] == "scene contents blaugh"
		//
		// Note that parsing of the actual scene contents is deferred untill it's actually called for display.
		private function parseSceneTag(textCtnt:String):void
		{
			var sceneName:String;
			var sceneCont:String;

			sceneName = textCtnt.substring(textCtnt.indexOf(' ') ,textCtnt.indexOf('|'));
			sceneCont = textCtnt.substr(textCtnt.indexOf('|')+1);

			sceneName = stripStr(sceneName);
			if (sceneParserDebug) LOGGER.warn("WARNING: Adding scene with name \"" + sceneName + "\"")

			// Cleanup the scene content from spurious leading and trailing space.
			sceneCont = trimStr(sceneCont, "\n");
			sceneCont = trimStr(sceneCont, "	");

			this.parserState[sceneName] = stripStr(sceneCont);

		}

		// Evaluates the contents of a button tag, and instantiates the relevant button
		// Current syntax:
		//
		// [button function_name | Button Name]
		// where "button" is a constant string, "function_name" is the name of the function pressing the button will call,
		// and "Button Name" is the text that will be shown on the button.
		// Note that the function name cannot contain spaces (actionscript requires this), and is case-sensitive
		// "Button name" can contain arbitrary spaces or characters, excepting "]", "[" and "|"
		private function parseButtonTag(textCtnt:String):void
		{
			// TODO: Allow button positioning!

			var arr:Array = textCtnt.split("|")
			if (arr.len > 2)
			{
				if (this._settingsClass.haltOnErrors) throw new Error("");
				throw new Error("Too many items in button")
			}

			var buttonName:String = stripStr(arr[1]);
			var buttonFunc:String = stripStr(arr[0].substring(arr[0].indexOf(' ')));
			//trace("WARNING: adding a button with name\"" + buttonName + "\" and function \"" + buttonFunc + "\"");
			_ownerClass.addButton(buttonNum, buttonName, this.enterParserScene, buttonFunc);
			buttonNum += 1;
		}

		// pushes the contents of the passed string into the scene list object if it's a scene, or instantiates the named button if it's a button
		// command and returns an empty string.
		// if the contents are not a button or scene contents, returns the contents.
		private function evalForSceneControls(textCtnt:String):String
		{

			if (sceneParserDebug) LOGGER.warn("WARNING: Checking for scene tags.");
			if (textCtnt.toLowerCase().indexOf("screen") == 0)
			{
				if (sceneParserDebug) LOGGER.warn("WARNING: It's a scene");
				parseSceneTag(textCtnt);
				return "";
			}
			else if (textCtnt.toLowerCase().indexOf("button") == 0)
			{
				if (sceneParserDebug) LOGGER.warn("WARNING: It's a button add statement");
				parseButtonTag(textCtnt);
				return "";
			}
			return textCtnt;
		}

		private function isIfStatement(textCtnt:String):Boolean
		{
			return textCtnt.toLowerCase().indexOf("if") == 0;
		}

		private function isSpeechStatement(textCtnt:String):Boolean
		{
			return textCtnt.toLowerCase().indexOf("say: ") == 0;
		}

		private function parseSpeech(textCtnt:String):String{
			return "\u201c" + textCtnt.substring(5,textCtnt.length + 1) + "\u201d";
		}

		private function isFormatStatement(textCtnt:String):Boolean {
			return textCtnt.search(/[biu]{1,3}: /i) == 0;
		}

		private function parseFormat(textCtnt:String):String {
			var formatStr:String = textCtnt.substring(0, textCtnt.search(":")).toLowerCase();
			var returnStr:String = textCtnt.substring(textCtnt.search(":")+2);
			var isBold:Boolean = formatStr.indexOf("b") >= 0;
			var isItalic:Boolean = formatStr.indexOf("i") >= 0;
			var isUnderline:Boolean = formatStr.indexOf("u") >= 0;

			if (isBold) returnStr = "" + returnStr + "";
			if (isItalic) returnStr = "" + returnStr + "";
			if (isUnderline) returnStr = "" + returnStr + "";
			return returnStr;
		}

		// Called to determine if the contents of a bracket are a parseable statement or not
		// If the contents *are* a parseable, it calls the relevant function to evaluate it
		// if not, it simply returns the contents as passed
		private function parseNonIfStatement(textCtnt:String, depth:int):String
		{

			var retStr:String = "";
			if (printCcntentDebug) LOGGER.warn("WARNING: Parsing content string: ", textCtnt);

			if (mainParserDebug) LOGGER.warn("WARNING: Not an if statement")
				// Match a single word, with no leading or trailing space
			var singleWordTagRegExp:RegExp = /^[\w\.]+$/;
			var doubleWordTagRegExp:RegExp = /^[\w\.]+\s[\w\.]+$/;

			if (mainParserDebug) LOGGER.warn("WARNING: string length = ", textCtnt.length);

			if (textCtnt.toLowerCase().indexOf("insertsection") == 0)
			{
				if (sceneParserDebug) LOGGER.warn("WARNING: It's a scene section insert tag!");
				retStr = this.getSceneSectionToInsert(textCtnt)
			}
			else if (singleWordTagRegExp.exec(textCtnt))
			{
				if (mainParserDebug) LOGGER.warn("WARNING: It's a single word!");
				retStr += convertSingleArg(textCtnt);
			}
			else if (doubleWordTagRegExp.exec(textCtnt))
			{
				if (mainParserDebug) LOGGER.warn("WARNING: Two-word tag!")
				retStr += convertDoubleArg(textCtnt);
			}
			else
			{
				if (mainParserDebug) LOGGER.warn("WARNING: Cannot parse content. What?", textCtnt)
				retStr += "!Unknown multi-word tag \"" + retStr + "\"!";
			}

			return retStr;
		}

		import flash.utils.getQualifiedClassName;

		// Actual internal parser function.
		// textCtnt is the text you want parsed, depth is a number that reflects the current recursion depth
		// You pass in the string you want parsed, and the parsed result is returned as a string.
		private function recParser(textCtnt:String, depth:Number):String
		{
			if (mainParserDebug) LOGGER.warn("WARNING: Recursion call", depth, "---------------------------------------------+++++++++++++++++++++")
			if (printIntermediateParseStateDebug) LOGGER.warn("WARNING: Parsing contents = ", textCtnt)
			// Depth tracks our recursion depth
			// Basically, we need to handle things differently on the first execution, so we don't mistake single-word print-statements for
			// a tag. Therefore, every call of recParser increments depth by 1

			depth += 1;
			textCtnt = String(textCtnt);
			if (textCtnt.length == 0)	// Short circuit if we've been passed an empty string
				return "";

			var i:Number = 0;

			var bracketCnt:Number = 0;

			var lastBracket:Number = -1;

			var retStr:String = "";

			do
			{
				lastBracket = textCtnt.indexOf("[", lastBracket+1);
				if (textCtnt.charAt(lastBracket-1) == "\\")
				{
					// LOGGER.warn("WARNING: bracket is escaped 1", lastBracket);
				}
				else if (lastBracket != -1)
				{
					// LOGGER.warn("WARNING: need to parse bracket", lastBracket);
					break;
				}

			} while (lastBracket != -1)

			if (lastBracket != -1)		// If we have any open brackets
			{
				for (i = lastBracket; i < textCtnt.length; i += 1)
				{
					if (textCtnt.charAt(i) == "[")
					{
						if (textCtnt.charAt(i-1) != "\\")
						{
							//LOGGER.warn("WARNING: bracket is not escaped - 2");
							bracketCnt += 1;
						}
					}
					else if (textCtnt.charAt(i) == "]")
					{
						if (textCtnt.charAt(i-1) != "\\")
						{
							//LOGGER.warn("WARNING: bracket is not escaped - 3");
							bracketCnt -= 1;
						}
					}
					if (bracketCnt == 0)	// We've found the matching closing bracket for the opening bracket at textCtnt[lastBracket]
					{
						var prefixTmp:String, postfixTmp:String;

						// Only prepend the prefix if it actually has content.
						prefixTmp = textCtnt.substring(0, lastBracket);
						if (mainParserDebug) LOGGER.warn("WARNING: prefix content = ", prefixTmp);
						if (prefixTmp)
							retStr += prefixTmp
						// We know there aren't any brackets in the section before the first opening bracket.
						// therefore, we just add it to the returned string

						var tmpStr:String = textCtnt.substring(lastBracket+1, i);
						tmpStr = evalForSceneControls(tmpStr);
						// evalForSceneControls swallows scene controls, so they won't get parsed further now.
						// therefore, you could *theoretically* have nested scene pages, though I don't know WHY you'd ever want that.

						if (isIfStatement(tmpStr))
						{
							if (conditionalDebug) LOGGER.warn("WARNING: early eval as if")
							retStr += parseConditional(tmpStr, depth)
							if (conditionalDebug) LOGGER.warn("WARNING: ------------------0000000000000000000000000000000000000000000000000000000000000000-----------------------")
							//LOGGER.warn("WARNING: Parsed Ccnditional - ", retStr)
						}
						else if (isSpeechStatement(tmpStr))
						{
							retStr += parseSpeech(recParser(tmpStr,depth));
						}
                        else if (isFormatStatement(tmpStr))
                        {
                            retStr += parseFormat(recParser(tmpStr,depth));
                        }
						else if (tmpStr)
						{

							if (printCcntentDebug) LOGGER.warn("WARNING: Parsing bracket contents = ", tmpStr);
							retStr += parseNonIfStatement(recParser(tmpStr, depth), depth);

						}

						// First parse into the text in the brackets (to resolve any nested brackets)
						// then, eval their contents, in case they're an if-statement or other control-flow thing
						// I haven't implemented yet

						// Only parse the trailing string if it has brackets in it.
						// if not, we need to just return the string as-is.
						// Parsing the trailing string if it doesn't have brackets could lead to it being
						// incorrectly interpreted as a multi-word tag (and shit would asplode and shit)

						postfixTmp = textCtnt.substring(i+1, textCtnt.length);
						if (postfixTmp.indexOf("[") != -1)
						{
							if (printCcntentDebug) LOGGER.warn("WARNING: Need to parse trailing text", postfixTmp)
							retStr += recParser(postfixTmp, depth);	// Parse the trailing text (if any)
							// Note: This leads to LOTS of recursion. Since we basically call recParser once per
							// tag, it means that if a body of text has 30 tags, we'll end up recursing at least
							// 29 times before finishing.
							// Making this tail-call reursive, or just parsing it flatly may be a much better option in
							// the future, if this does become an issue.
						}
						else
						{
							if (printCcntentDebug) LOGGER.warn("WARNING: No brackets in trailing text", postfixTmp)
							retStr += postfixTmp;
						}

						return retStr;
						// and return the parsed string
					}
				}
			}
			else
			{
				// DERP. We should never have brackets around something that ISN'T a tag intended to be parsed. Therefore, we just need
				// to determine what type of parsing should be done do the tag.
				if (printCcntentDebug) LOGGER.warn("WARNING: No brackets present in text passed to recParse", textCtnt);

				retStr += textCtnt;

			}

			return retStr;
		}

		// Main parser function.
		// textCtnt is the text you want parsed, depth is a number, which should be 0
		// or not passed at all.
		// You pass in the string you want parsed, and the parsed result is returned as a string.
		public function recursiveParser(contents:String):String
		{
			if (mainParserDebug) LOGGER.warn("WARNING: ------------------ Parser called on string -----------------------");
			// Eventually, when this goes properly class-based, we'll add a period, and have this.parserState.

			// Reset the parser's internal state, since we're parsing a new string:
			this.parserState = new Object();

			var ret:String = "";
			if (contents == null) return ret;
			// Run through the parser
			contents = contents.replace(/\r\n?|\\n/g, "\n")
			ret = recParser(contents, 0);
			if (printIntermediateParseStateDebug) LOGGER.warn("WARNING: Parser intermediate contents = ", ret)
			// Currently, not parsing text as markdown by default because it's fucking with the line-endings.

			// Convert quotes to prettyQuotes
			ret = this.makeQuotesPrettah(ret);

			// cleanup escaped brackets
			ret = ret.replace(/\\\]/g, "]")
			ret = ret.replace(/\\\[/g, "[")

			//Stupid thing
			if (kGAMECLASS.seasons.isItAprilFools() && kGAMECLASS.silly()) ret = fixTerminology(ret);
		    if(kGAMECLASS.player.hasStatusEffect(StatusEffects.kitsuneVision)){
				var kitsunedString:Array = ret.split(" ");
				ret = "";
				for each(var s:String in kitsunedString){
					s = " " + s + " ";
					s = s.replace(/ [a-z]+(e)(d) | [a-z]+(ing) | [a-z]+(s) /g, Math.random() >= .5 ? " kitsune$2$3$4 " : " fluffy tail$1$2$3$4 ");
					ret += s;

				}
				ret = ret.substr(1, ret.length - 1);
				//ret = ret.replace(/ [a-z]+(e)(d) | [a-z]+(ing) | [a-z]+(s) /g, Math.random() >= .5 ? " kitsune$2$3$4 " : " fluffy tail$1$2$3$4 ");
			}
			// And repeated spaces (this has to be done after markdown processing)
			ret = ret.replace(/  +/g, " ");

			function weebify():String {
				if (Math.random() >= .9) return arguments[1] + "Without even an 'itadakimasu', " + arguments[2].toLowerCase();
				return arguments[0];
			}
			if (kGAMECLASS.seasons.isItAprilFools() && kGAMECLASS.silly()) {
				ret = ret.replace(/(^|\. )(She|He|You)\b/g, weebify);
				if (!kGAMECLASS.noFur()) ret = ret.replace(/\b[a-z]+-morph/g, "furfag");
			}

			// Finally, if we have a parser-based scene. enter the "startup" scene.
			if ("startup" in this.parserState)
			{
				ret = enterParserScene("startup");

				// HORRIBLE HACK
				// since we're initially called via a outputText command, the content of the first page's text will be overwritten
				// when we return. Therefore, in a horrible hack, we return the contents of mainTest.htmlText as the ret value, so
				// the outputText call overwrites the window content with the exact same content.

				LOGGER.warn("Returning: {0}", ret);
			}

			return ret
		}

		// ---------------------------------------------------------------------------------------------------------------------------------------
		// ---------------------------------------------------------------------------------------------------------------------------------------
		// ---------------------------------------------------------------------------------------------------------------------------------------

		// Make shit look nice

		private function makeQuotesPrettah(inStr:String):String
		{

			inStr = inStr.replace(/(\w)'(\w)/g,										        "$1\u2019$2")	// Apostrophes
						 .replace(/(\))"(\))/g,                                             "$1\u201d$2")	// Special case, very ugly
			             .replace(/(^|[\r\n 	\.\!\,\?\(\)])"([a-zA-Z<>\.\!\,\?\(\)])/g,	"$1\u201c$2")	// Opening doubles
			             .replace(/([a-zA-Z<>\.\!\,\?\(\)])"([\r\n 	\.\!\,\?\(\)]|$)/g,		"$1\u201d$2")	// Closing doubles
			             .replace(/--/g,  											        "\u2014");		// Em-dashes
			return inStr;
		}

		// ---------------------------------------------------------------------------------------------------------------------------------------
		// ---------------------------------------------------------------------------------------------------------------------------------------
		// ---------------------------------------------------------------------------------------------------------------------------------------

		// Stupid string utility functions, because actionscript doesn't have them (WTF?)

		public function stripStr(str:String):String
		{
			return trimStrBack(trimStrFront(str, " "), " ");
			return trimStrBack(trimStrFront(str, "	"), "	");
		}

		public function trimStr(str:String, char:String = " "):String
		{
			return trimStrBack(trimStrFront(str, char), char);
		}

		public function trimStrFront(str:String, char:String = " "):String
		{
			char = stringToCharacter(char);
			if (str.charAt(0) == char) {
				str = trimStrFront(str.substring(1), char);
			}
			return str;
		}

		public function trimStrBack(str:String, char:String = " "):String
		{
			char = stringToCharacter(char);
			if (str.charAt(str.length - 1) == char) {
				str = trimStrBack(str.substring(0, str.length - 1), char);
			}
			return str;
		}
		public function stringToCharacter(str:String):String
		{
			if (str.length == 1)
			{
				return str;
			}
			return str.slice(0, 1);
		}

		public function isUpperCase(char:String):Boolean
		{
			if (!isNaN(Number(char)))
			{
				return false;
			}
			else if (char == char.toUpperCase())
			{
				return true;
			}
			return false;
		}

		public function capitalizeFirstWord(str:String):String
		{

			str = str.charAt(0).toUpperCase()+str.slice(1);
			return str;
		}

		private var sillyType:int = Utils.rand(4); //Set once on init, so it will only be rerolled if you close and reopen the game

		private function fixTerminology(str:String):String {
			var dickPattern:RegExp = /\b(cock|dick|dong|endowment|mast|member|pecker|penis|prick|shaft|tool|erection|manhood)\b/g;
			var vaginaPattern:RegExp = /\b(vagina|pussy|cooter|twat|cunt|snatch|fuck-hole|muff|nether-?lips|slit)\b/g;
			var titPattern:RegExp = /\b(breast|tit|boob|jug|udder|love-pillow)(s?)\b/g;
			var nipplePattern:RegExp = /\b(nipple|nub|nip|teat)(s?)\b/g;
			var clitPattern:RegExp = /\b(clit|clitty|button|pleasure-buzzer)\b/g;
			var cumPattern:RegExp = /\b(pre-cum|pre|cum|semen|spooge|jizz|jism|jizm)\b/g;
			var assPattern:RegExp = /\b(butt(?!(-|\s)?cheek)|ass(?!(-|\s)?cheek)|rump|rear end|derriere)\b/g;
			var anusPattern:RegExp = /\b(anus|asshole|butthole|pucker)\b/g;

			if (str.search(dickPattern) != -1) {
				str = str.replace(dickPattern, dickWords);
				return str;
			}
			if (str.search(vaginaPattern) != -1) {
				str = str.replace(vaginaPattern, vaginaWords);
				return str;
			}
			if (str.search(titPattern) != -1) {
				str = str.replace(titPattern, titWords);
				return str;
			}
			if (str.search(nipplePattern) != -1) {
				str = str.replace(nipplePattern, nippleWords);
				return str;
			}
			if (str.search(clitPattern) != -1) {
				str = str.replace(clitPattern, clitWords);
				return str;
			}
			if (str.search(cumPattern) != -1) {
				str = str.replace(cumPattern, cumWords);
				return str;
			}
			if (str.search(assPattern) != -1) {
				str = str.replace(assPattern, assWords);
				return str;
			}
			if (str.search(anusPattern) != -1) {
				str = str.replace(anusPattern, anusWords);
				return str;
			}
			return str;
		}
		private function dickWords():String {
			switch (sillyType) {
				case 0: return "junk";
				case 1: return "stuff";
				case 2: return "peepee";
				case 3: return Utils.randomChoice("junior captain", "Excalibur", "wiffle-ball bat", "baby-batter chucker", "conquest stick", "semen-rifle", "all-beef thermometer", "womb ferret", "hyper weapon", "bengis");
				default: return arguments[0];
			}
		}
		private function vaginaWords():String {
			switch (sillyType) {
				case 0: return "junk";
				case 1: return "stuff";
				case 2: return "cunny";
				case 3: return Utils.randomChoice("front-butt", "sparkle-box", "faerie cave", "wonder tunnel", "hot pocket", "meat purse", "undermuffin", "chaotic pleasure-scape");
				default: return arguments[0];
			}
		}
		private function titWords():String {
			var s:String = arguments[2];
			switch (sillyType) {
				case 0: return "junk";
				case 1: return "stuff";
				case 2: return s ? "boobies" : "booby";
				case 3: return s ? Utils.randomChoice("chest puppies", "sweater puppies", "twin peaks", "chesticles") : Utils.randomChoice("chest puppy", "sweater puppy", "chesticle");
				default: return arguments[0];
			}
		}
		private function nippleWords():String {
			var s:String = arguments[2];
			switch (sillyType) {
				case 0: return "junk";
				case 1: return "stuff";
				case 3: return Utils.randomChoice("tater tot", "boobwart", "milk dud", "mosquito bite") + s;
				default: return arguments[0];
			}
		}
		private function clitWords():String {
			switch (sillyType) {
				case 0: return "junk";
				case 1: return "stuff";
				case 2: return "clitty";
				case 3: return Utils.randomChoice("doorbell", "pleasure pager", "hood ornament");
				default: return arguments[0];
			}
		}
		private function cumWords():String {
			switch (sillyType) {
				case 0: return "junk";
				case 1: return "stuff";
				case 2: return "salty milk";
				case 3: return Utils.randomChoice("alabaster baby-batter", "love mayonnaise", "dick snot", "gonad glaze");
				default: return arguments[0];
			}
		}
		private function assWords():String {
			switch (sillyType) {
				case 0: return "junk";
				case 1: return "stuff";
				case 2: return "butt";
				case 3: return Utils.randomChoice("caboose", "spunk trunk", "shit box");
				default: return arguments[0];
			}
		}
		private function anusWords():String {
			switch (sillyType) {
				case 0: return "junk";
				case 1: return "stuff";
				case 2: return "butthole";
				case 3: return Utils.randomChoice("brown eye", "poop chute", "asspussy", "semen sock");
				default: return arguments[0];
			}
		}
	}
}

license: unknown


code:
		//Calls are now made through kGAMECLASS rather than thisPtr. This allows the compiler to detect if/when a function is inaccessible.
		import classes.GlobalFlags.kFLAGS;
		import classes.GlobalFlags.kGAMECLASS;
		import classes.Items.ArmorLib;
		import classes.Items.WeaponLib;
		import classes.Items.UndergarmentLib;

		/**
		 * Possible text arguments in the conditional of a if statement
		 * First, there is an attempt to cast the argument to a Number. If that fails,
		 * a dictionary lookup is performed to see if the argument is in the conditionalOptions[]
		 * object. If that fails, we just fall back to returning 0
		 */
		public var conditionalOptions:Object =
		{
				"strength"			: function():* {return  kGAMECLASS.player.str;},
				"toughness"			: function():* {return  kGAMECLASS.player.tou;},
				"speed"				: function():* {return  kGAMECLASS.player.spe;},
				"intelligence"		: function():* {return  kGAMECLASS.player.inte;},
				"libido"			: function():* {return  kGAMECLASS.player.lib;},
				"sensitivity"		: function():* {return  kGAMECLASS.player.sens;},
				"corruption"		: function():* {return  kGAMECLASS.player.cor;},
				"fatigue"			: function():* {return  kGAMECLASS.player.fatigue;},
				"hp"				: function():* {return  kGAMECLASS.player.HP;},
				"hunger"			: function():* {return  kGAMECLASS.player.hunger;},
				"minute"			: function():* {return  kGAMECLASS.time.minutes;},
				"hour"				: function():* {return  kGAMECLASS.time.hours;},
				"days"				: function():* {return  kGAMECLASS.time.days;},
				"hasarmor"			: function():* {return  kGAMECLASS.player.armor != ArmorLib.NOTHING;},
				"haslowergarment"	: function():* {return  kGAMECLASS.player.lowerGarment != UndergarmentLib.NOTHING;},
				"hasuppergarment"	: function():* {return	kGAMECLASS.player.upperGarment != UndergarmentLib.NOTHING;},
				"hasweapon"			: function():* {return  kGAMECLASS.player.weapon != WeaponLib.FISTS;},
				"tallness"			: function():* {return  kGAMECLASS.player.tallness;},
				"hairlength"		: function():* {return  kGAMECLASS.player.hair.length;},
				"femininity"		: function():* {return  kGAMECLASS.player.femininity;},
				"masculinity"		: function():* {return  100 - kGAMECLASS.player.femininity;},
				"cocks"				: function():* {return  kGAMECLASS.player.cockTotal();},
				"breastrows"		: function():* {return  kGAMECLASS.player.bRows();},
				"biggesttitsize"	: function():* {return  kGAMECLASS.player.biggestTitSize();},
				"vagcapacity"		: function():* {return  kGAMECLASS.player.vaginalCapacity();},
				"analcapacity"		: function():* {return  kGAMECLASS.player.analCapacity();},
				"balls"				: function():* {return  kGAMECLASS.player.balls;},
				"ballsize"			: function():* {return  kGAMECLASS.player.ballSize;},
				"cumquantity"		: function():* {return  kGAMECLASS.player.cumQ();},
				"milkquantity"		: function():* {return  kGAMECLASS.player.lactationQ();},
				"biggestlactation"	: function():* {return  kGAMECLASS.player.biggestLactation();},
				"hasvagina"			: function():* {return  kGAMECLASS.player.hasVagina();},
				"istaur"			: function():* {return  kGAMECLASS.player.isTaur();},
				"ishoofed"			: function():* {return  kGAMECLASS.player.isHoofed();},
				"iscentaur"			: function():* {return  kGAMECLASS.player.isCentaur();},
				"isnaga"			: function():* {return  kGAMECLASS.player.isNaga();},
				"isgoo"				: function():* {return  kGAMECLASS.player.isGoo();},
				"isbiped"			: function():* {return  kGAMECLASS.player.isBiped();},
				"hasbreasts"		: function():* {return  (kGAMECLASS.player.biggestTitSize() >= 1);},
				"hasballs"			: function():* {return  (kGAMECLASS.player.balls > 0);},
				"hascock"			: function():* {return  kGAMECLASS.player.hasCock();},
				"hassheath"			: function():* {return  kGAMECLASS.player.hasSheath();},
				"hasbeak"			: function():* {return  kGAMECLASS.player.hasBeak();},
				"hascateyes"		: function():* {return  kGAMECLASS.player.hasCatEyes();},
				"hascatface"		: function():* {return  kGAMECLASS.player.hasCatFace();},
				"hasclaws"			: function():* {return  kGAMECLASS.player.hasClaws();},
				"hasdragonneck"		: function():* {return  kGAMECLASS.player.hasDragonNeck();},
				"hastail"			: function():* {return  kGAMECLASS.player.hasTail();},
				"neckpos"			: function():* {return  kGAMECLASS.player.neck.pos;},
				"hasplainskin"		: function():* {return  kGAMECLASS.player.hasPlainSkin();},
				"hasgooskin"		: function():* {return  kGAMECLASS.player.hasGooSkin();},
				"hasfur"			: function():* {return  kGAMECLASS.player.hasFur();},
				"haswool"			: function():* {return  kGAMECLASS.player.hasWool();},
				"hasfeathers"		: function():* {return  kGAMECLASS.player.hasFeathers();},
				"hasfurryunderbody"	: function():* {return  kGAMECLASS.player.hasFurryUnderBody();},
				"haslongtongue"		: function():* {return  kGAMECLASS.player.hasLongTongue();},
				"isfurry"			: function():* {return  kGAMECLASS.player.isFurry();},
				"isfluffy"			: function():* {return  kGAMECLASS.player.isFluffy();},
				"isgenderless"		: function():* {return  (kGAMECLASS.player.isGenderless());},
				"ismale"			: function():* {return  (kGAMECLASS.player.isMale());},
				"isfemale"			: function():* {return  (kGAMECLASS.player.isFemale());},
				"isherm"			: function():* {return  (kGAMECLASS.player.isHerm());},
				"ismaleorherm"		: function():* {return  (kGAMECLASS.player.isMaleOrHerm());},
				"isfemaleorherm"	: function():* {return  (kGAMECLASS.player.isFemaleOrHerm());},
            	"silly"				: function():* {return  (kGAMECLASS.silly());},
				"cumnormal"			: function():* {return  (kGAMECLASS.player.cumQ() <= 150);},
				"cummedium"			: function():* {return  (kGAMECLASS.player.cumQ() > 150 && kGAMECLASS.player.cumQ() <= 350);},
				"cumhigh"			: function():* {return  (kGAMECLASS.player.cumQ() > 350 && kGAMECLASS.player.cumQ() <= 1000);},
				"cumveryhigh"		: function():* {return  (kGAMECLASS.player.cumQ() > 1000 && kGAMECLASS.player.cumQ() <= 2500);},
				"cumextreme"		: function():* {return  (kGAMECLASS.player.cumQ() > 2500);},
				"cummediumleast"	: function():* {return  (kGAMECLASS.player.cumQ() > 150);},
				"cumhighleast"		: function():* {return  (kGAMECLASS.player.cumQ() > 350);},
				"cumveryhighleast"	: function():* {return  (kGAMECLASS.player.cumQ() > 1000);},
				"issquirter"		: function():* {return  (kGAMECLASS.player.wetness() >= 4);},
				"vaginalwetness"	: function():* {return  kGAMECLASS.player.wetness();},
				"vaginallooseness"	: function():* {return kGAMECLASS.player.vaginas[0].vaginalLooseness;},
				"anallooseness"		: function():* {return  kGAMECLASS.player.ass.analLooseness;},
				"buttrating"		: function():* {return  kGAMECLASS.player.butt.rating;},
				"ispregnant"		: function():* {return  (kGAMECLASS.player.pregnancyIncubation > 0);},
				"isbuttpregnant"	: function():* {return  (kGAMECLASS.player.buttPregnancyIncubation > 0);},
				"hasnipplecunts"	: function():* {return  kGAMECLASS.player.hasFuckableNipples();},
				"totalnipples"		: function():* {return  kGAMECLASS.player.totalNipples();},
				"canfly"			: function():* {return  kGAMECLASS.player.canFly();},
				"isadult"           : function(thisPtr:*):* {return  (kGAMECLASS.player.age == 0);},
				"hasovipositor"		: function():* {return  kGAMECLASS.player.hasOvipositor();},
				"ischild"           : function(thisPtr:*):* {return  (kGAMECLASS.player.age == 1);},
				"isteen"            : function(thisPtr:*):* {return  (kGAMECLASS.player.age == 2);},
				"iselder"           : function(thisPtr:*):* {return  (kGAMECLASS.player.age == 3);},
				"islactating"		: function():* {return  (kGAMECLASS.player.lactationQ() > 0);},
				"isflat"			: function():* {return  (kGAMECLASS.player.biggestTitSize() == 0);},
				"isbimbo"			: function():* {return  kGAMECLASS.player.isBimbo();},
				"true"				: function():* {return  true;},
				"false"				: function():* {return  false;},
				"isnaked"			: function():* {return kGAMECLASS.player.isNaked(); },
				"isnakedlower"		: function():* {return kGAMECLASS.player.isNakedLower(); },
				"singleleg"			: function():* {return kGAMECLASS.player.lowerBody.legCount == 1},
				"haslegs"			: function():* {return kGAMECLASS.player.lowerBody.legCount > 1},
				"metric"			: function():* {return kGAMECLASS.flags[kFLAGS.USE_METRICS] > 0; },
				"nofur"				: function():* {return kGAMECLASS.noFur();},
				//Prison
				"esteem"			: function():* {return  kGAMECLASS.player.esteem; },
				"obey"				: function():* {return  kGAMECLASS.player.obey; },
				"will"				: function():* {return  kGAMECLASS.player.will; },

				//---[NPCs]---
				//Joy
				"joyhascock"		: function():* {return  kGAMECLASS.joyScene.joyHasCock(); },

				//Tel Adre
				"bakerytalkedroot"	: function():* {return  kGAMECLASS.flags[kFLAGS.MINO_CHEF_TALKED_RED_RIVER_ROOT] > 0; }
			};


code:
// provides rubiLookups and arianLookups
		// note that these are only used in doubleArgLookups, not in Parser.as itself
		//
		// =!= NOTE: MUST BE IMPORTED BEFORE "./doubleArgLookups.as" =!=
		//
		//Calls are now made through kGAMECLASS rather than thisPtr. This allows the compiler to detect if/when a function is inaccessible.
		import classes.GlobalFlags.kGAMECLASS;
		include "./npcLookups.as";

		// PC ASCII Aspect lookups

		public var cockLookups:Object = // For subject: "cock"
		{
			"all"		: function():*{ return kGAMECLASS.player.multiCockDescriptLight(); },
			"each"		: function():*{ return kGAMECLASS.player.sMultiCockDesc(); },
			"one"		: function():*{ return kGAMECLASS.player.oMultiCockDesc(); },
			"largest"	: function():*{ return kGAMECLASS.player.cockDescript(kGAMECLASS.player.biggestCockIndex()); },
			"biggest"	: function():*{ return kGAMECLASS.player.cockDescript(kGAMECLASS.player.biggestCockIndex()); },
			"biggest2"	: function():*{ return kGAMECLASS.player.cockDescript(kGAMECLASS.player.biggestCockIndex2()); },
			"biggest3"  : function():*{ return kGAMECLASS.player.cockDescript(kGAMECLASS.player.biggestCockIndex3()); },
			"smallest"	: function():*{ return kGAMECLASS.player.cockDescript(kGAMECLASS.player.smallestCockIndex()); },
			"smallest2" : function():*{ return kGAMECLASS.player.cockDescript(kGAMECLASS.player.smallestCockIndex2()); },
			"longest"	: function():*{ return kGAMECLASS.player.cockDescript(kGAMECLASS.player.longestCock()); },
			"shortest"	: function():*{ return kGAMECLASS.player.cockDescript(kGAMECLASS.player.shortestCockIndex()); }
		}

		public var cockHeadLookups:Object = // For subject: "cockHead"
		{
			"biggest"	: function():*{ return kGAMECLASS.player.cockHead(kGAMECLASS.player.biggestCockIndex()); },
			"biggest2"	: function():*{ return kGAMECLASS.player.cockHead(kGAMECLASS.player.biggestCockIndex2()); },
			"biggest3"	: function():*{ return kGAMECLASS.player.cockHead(kGAMECLASS.player.biggestCockIndex3()); },
			"largest"	: function():*{ return kGAMECLASS.player.cockHead(kGAMECLASS.player.biggestCockIndex()); },
			"smallest"	: function():*{ return kGAMECLASS.player.cockHead(kGAMECLASS.player.smallestCockIndex()); },
			"smallest2"	: function():*{ return kGAMECLASS.player.cockHead(kGAMECLASS.player.smallestCockIndex2()); },
			"longest"	: function():*{ return kGAMECLASS.player.cockHead(kGAMECLASS.player.longestCock()); },			// the *head* of a cock has a length? Wut?
			"shortest"	: function():*{ return kGAMECLASS.player.cockHead(kGAMECLASS.player.shortestCockIndex()); }
		}

		// These tags take a two-word tag with a **numberic** attribute for lookup.
		// [object NUMERIC-attribute]
		// if "NUMERIC-attribute" can be cast to a Number, the parser looks for "object" in twoWordNumericTagsLookup.
		// If it finds twoWordNumericTagsLookup["object"], it calls the anonymous function stored with said key "object"
		// like so: twoWordNumericTagsLookup["object"](Number("NUMERIC-attribute"))
		//
		// if attribute cannot be case to a number, the parser looks for "object" in twoWordTagsLookup.
		public var twoWordNumericTagsLookup:Object =
		{
				"cockfit":
					function(aspect:*):*
					{
						if (!kGAMECLASS.player.hasCock()) return "(Attempt to parse cock when none present.)";
						else
						{
							if (kGAMECLASS.player.cockThatFits(aspect) >= 0) return kGAMECLASS.player.cockDescript(kGAMECLASS.player.cockThatFits(aspect));
							else return kGAMECLASS.player.cockDescript(kGAMECLASS.player.smallestCockIndex());
						}
					},
				"cockfit2":
					function(aspect:*):*
					{
						if (!kGAMECLASS.player.hasCock()) return "(Attempt to parse cock when none present.)";
						else {
							if (kGAMECLASS.player.cockThatFits2(aspect) >= 0) return kGAMECLASS.player.cockDescript(kGAMECLASS.player.cockThatFits2(aspect));
							else return kGAMECLASS.player.cockDescript(kGAMECLASS.player.smallestCockIndex());
						}
					},
				"cockheadfit":
					function(aspect:*):*
					{
						if (!kGAMECLASS.player.hasCock())
						{
							return "(Attempt to parse cockhead when none present.)";
						}
						else {
							if (kGAMECLASS.player.cockThatFits(aspect) >= 0) return kGAMECLASS.player.cockHead(kGAMECLASS.player.cockThatFits(aspect));
							else return kGAMECLASS.player.cockHead(kGAMECLASS.player.smallestCockIndex());
						}
					},
				"cockheadfit2":
					function(aspect:*):*
					{
						if (!kGAMECLASS.player.hasCock()) return "(Attempt to parse cockhead when none present.)";
						else {
							if (kGAMECLASS.player.cockThatFits2(aspect) >= 0) return kGAMECLASS.player.cockHead(kGAMECLASS.player.cockThatFits2(aspect));
							else return kGAMECLASS.player.cockHead(kGAMECLASS.player.smallestCockIndex());
						}
					},
				"cock":
					function(aspect:*):*
					{
						if (!kGAMECLASS.player.hasCock()) return "(Attempt to parse cock when none present.)";
						else
						{
							if (aspect-1 >= 0 && aspect-1 < kGAMECLASS.player.cockTotal()) return kGAMECLASS.player.cockDescript(aspect - 1);
							else return "(Attempt To Parse player.cockDescript for Invalid Cock)";
						}
					},
				"cockhead":
					function(aspect:*):*
					{
						if (!kGAMECLASS.player.hasCock()) return "(Attempt to parse cockHead when none present.)";
						else
						{
							var intAspect:int = int(aspect - 1);
							if (intAspect >= 0 && intAspect < kGAMECLASS.player.cockTotal()) return kGAMECLASS.player.cockHead(intAspect);
							else return "(Attempt To Parse CockHeadDescript for Invalid Cock)";
						}
					}
		}

		// These tags take an ascii attribute for lookup.
		// [object attribute]
		// if attribute cannot be cast to a number, the parser looks for "object" in twoWordTagsLookup,
		// and then uses the corresponding object to determine the value of "attribute", by looking for
		// "attribute" twoWordTagsLookup["object"]["attribute"]
		public var twoWordTagsLookup:Object =
		{
			// NPCs:
			"rubi"		: rubiLookups,
			"arian"		: arianLookups,
			"ember"		: emberLookups,
			"benoit"	: benoitLookups,

			// PC Attributes:

			"cock"		: cockLookups,
			"cockhead"	: cockHeadLookups
		}


code:
		//Calls are now made through kGAMECLASS rather than thisPtr. This allows the compiler to detect if/when a function is inaccessible.
		import classes.GlobalFlags.kGAMECLASS;
		import classes.GlobalFlags.kFLAGS;
// PRONOUNS: The parser uses Elverson/Spivak Pronouns specifically to allow characters to be written with non-specific genders.
		// http://en.wikipedia.org/wiki/Spivak_pronoun
		//
		// Cheat Table:
		//           | Subject    | Object       | Possessive Adjective | Possessive Pronoun | Reflexive         |
		// Agendered | ey laughs  | I hugged em  | eir heart warmed     | that is eirs       | ey loves emself   |
		// Masculine | he laughs  | I hugged him | his heart warmed     | that is his        | he loves himself  |
		// Feminine  | she laughs | I hugged her | her heart warmed     | that is hers       | she loves herself |

		// (Is it bad that half my development time so far has been researching non-gendered nouns? ~~~~Fake-Name)

		public var arianLookups:Object = // For subject: "arian"
		{
			"man"		: function():String {return kGAMECLASS.arianScene.arianMF("man","woman")},
			// argh! "Man" is the mass-noun for humanity, and I'm loathe to choose an even more esoteric variant.
			// Elverson/Spivak terminology is already esoteric enough, and it lacks a ungendered mass noun.

			"ey"		: function():String {return kGAMECLASS.arianScene.arianMF("he","she")},
			"em"		: function():String {return kGAMECLASS.arianScene.arianMF("him","her")},
			"eir"		: function():String {return kGAMECLASS.arianScene.arianMF("his","her")},
			"eirs"		: function():String {return kGAMECLASS.arianScene.arianMF("his","hers")},
			"emself"	: function():String {return kGAMECLASS.arianScene.arianMF("himself","herself")},

			"chestadj"	: function():String {return kGAMECLASS.arianScene.arianChestAdjective()},
			"chest"		: function():String {return kGAMECLASS.arianScene.arianChest()}
		}
		// Arian unhandled terms (I have not decided how to support them yet):
		// arianMF("mas","mis")
		// arianMF("master","mistress")
		// arianMF("male","girly")

		public var emberLookups:Object = // For subject: "ember"
		{
			"man"		: function():String { return kGAMECLASS.arianScene.emberMF("man", "woman") },

			"ey"		: function():String {return kGAMECLASS.emberScene.emberMF("he","she")},
			"em"		: function():String {return kGAMECLASS.emberScene.emberMF("him","her")},
			"eir"		: function():String {return kGAMECLASS.emberScene.emberMF("his","her")},
			"eirs"		: function():String {return kGAMECLASS.emberScene.emberMF("his","hers")},
			"emself"	: function():String {return kGAMECLASS.emberScene.emberMF("himself", "herself")},
			"short" : function():String {return kGAMECLASS.emberScene.emberMF(kGAMECLASS.flags[kFLAGS.EMBER_ROUNDFACE] == 0 ? "dragon" : "dragon-morph",kGAMECLASS.flags[kFLAGS.EMBER_ROUNDFACE] == 0 ? "dragoness" : "dragon-girl")}
		}

		public var rubiLookups:Object = // For subject: "rubi"
		{
			"man"		: function():String {return kGAMECLASS.telAdre.rubi.rubiMF("man","woman")},

			"ey"		: function():String {return kGAMECLASS.telAdre.rubi.rubiMF("he","she")},
			"em"		: function():String {return kGAMECLASS.telAdre.rubi.rubiMF("him","her")},
			"eir"		: function():String {return kGAMECLASS.telAdre.rubi.rubiMF("his","her")},
			"eirs"		: function():String {return kGAMECLASS.telAdre.rubi.rubiMF("his","hers")},
			"emself"	: function():String {return kGAMECLASS.telAdre.rubi.rubiMF("himself","herself")},

			"cock"		: function():String {return kGAMECLASS.telAdre.rubi.rubiCock()},
			"breasts"	: function():String {return kGAMECLASS.telAdre.rubi.rubiBreasts()}

		}
		//Rubi unhandled terms :
		// rubiMF("boy","girl")
		// rubiMF("demon","demoness")
		// rubiMF("gentleman","lady")

		public var benoitLookups:Object = // For subject: "benoit"
		{
			"man"		: function():String {return kGAMECLASS.bazaar.benoit.benoitMF("man", "woman")},
			"name"		: function():String {return kGAMECLASS.bazaar.benoit.benoitMF("Benoit", "Benoite")},

			"ey"		: function():String {return kGAMECLASS.bazaar.benoit.benoitMF("he", "she")},
			"em"		: function():String {return kGAMECLASS.bazaar.benoit.benoitMF("him", "her")},
			"eir"		: function():String {return kGAMECLASS.bazaar.benoit.benoitMF("his", "her")},
			"eirs"		: function():String {return kGAMECLASS.bazaar.benoit.benoitMF("his", "hers")},
			"emself"	: function():String {return kGAMECLASS.bazaar.benoit.benoitMF("himself", "herself")}
		}