/**
 * @namespace MATTIE.troopAPI
 * @description The api for managing troops. This api allows multi troop combat and runtime troop creation
 */
MATTIE.troopAPI = MATTIE.troopAPI || {};
MATTIE.troopAPI.config = MATTIE.troopAPI.config || {};

/**
 * @description whether the troops should be sorted on the screen
 */
MATTIE.troopAPI.config.shouldSort = true;

/**
 * @description ensure all pages do not fire
 */
MATTIE.troopAPI.config.blockAllPages = false;
//--------------------------------------
// Game_Troop overrides
//--------------------------------------

/** @description the base initalize method for a game troop */
MATTIE_RPG.TroopApi_Game_Troop_Initialize = Game_Troop.prototype.initialize;
/** @description the extended method for inializing a game troop, this also inializeds the .additionalTroops thing that we use later */
Game_Troop.prototype.initialize = function () {
	MATTIE_RPG.TroopApi_Game_Troop_Initialize.call(this);
	/**
     * @description an array of all additional troops
     * @type {MATTIE.troopAPI.RuntimeTroop[]}
     */
	if (!this._additionalTroops) this._additionalTroops = {};
};

/**
 * @description wipe all game troops clear of all possible page data
 */
Game_Troop.prototype.clearAllPages = function () {
	this.clearPages();
	this.forEachAdditionalTroop((other) => { other.clearPages(); });
};

/**
 * @description clear the pages of this troop excluding additional troops
 */
Game_Troop.prototype.clearPages = function () {
	this.cloneData();
	this.troop().pages = [];
	this._interpreter.clear();
};

/**
 * @description clones the troop data of this troop and fixes its
 */
Game_Troop.prototype.cloneData = function () {
	const clonedTroop = JsonEx.makeDeepCopy($dataTroops[this._troopId]);
	this.troop = function () {
		return clonedTroop;
	};
};

/** @description the base function to setup a game troop */
MATTIE_RPG.TroopApi_Game_Troop_Setup = Game_Troop.prototype.setup;
/**
 * @description the setup function for a game troop, extended to also support offset and additional troops
 * @param {int | int[]} troopId the troop id or an array of ids
 * @param {int} xOffset default 0, offset the entire troop by this amount
 * @param {int} yOffset default 0, offset the entire troop by this amount
 */
Game_Troop.prototype.setup = function (troopId, xOffset = 0, yOffset = 0, cb = () => {}) {
	/**
     * @description an array of all additional troops
     * @type {MATTIE.troopAPI.RuntimeTroop[]}
     * */
	if (!this._additionalTroops) this._additionalTroops = {};

	if (typeof troopId === 'number') {
		MATTIE_RPG.TroopApi_Game_Troop_Setup.call(this, troopId);
		this._interpreter.setTroop(this);
	} else {
		this.setupMultiCombat(troopId, cb);
	}
};

/**
 * @description start a fight arr and call a callback when fight ends
 * @param {Array} the array of all troops in the fight
 * @param {function} cb the callback to call when the fight ends
 * @param {function} afterSetupCb the callback that triggers when the combat is ready
 */
Game_Troop.prototype.setupMultiCombat = function (arr, cb = () => {}, afterSetupCb = () => {}, spiderMode = false) {
	$gameTemp.reserveCommonEvent(74); // before battle
	let turn = 0;
	BattleManager.setCantStartInputting(true);

	if ($gameParty.leader().hasSkill(MATTIE.static.skills.enGarde.id)) {
		turn = -1;
		MATTIE.troopAPI.config.blockAllPages = true;
		const Prev = Game_Troop.prototype.increaseTurn;
		this.increaseTurn = function () {
			Prev.call(this);
			if (MATTIE.troopAPI.config.blockAllPages) {
				MATTIE.troopAPI.config.blockAllPages = false;
				$gameTroop.forEachAdditionalTroop((troop) => {
					troop._turnCount = 0;
				});
				this._turnCount = 0;
			}
		};
		$gameSwitches.setValue(MATTIE.static.switch.backstab, true);
	}
	const roundIds = arr;
	const first = arr[0];
	switch (first) {
	case MATTIE.static.troops.skinGrannyId:
		// skin granny needs this to be false for her to transform
		$gameSwitches.setValue(1807, false);
		break;

	default:
		break;
	}
	if (!spiderMode) {
		BattleManager.setup(first, false, true);
	} else {
		BattleManager.setup(142, false, true);
	}

	$gamePlayer.makeEncounterCount();
	SceneManager.push(Scene_Battle);

	setTimeout(() => {
		if ($gameParty.leader().hasSkill(MATTIE.static.skills.enGarde.id)) {
			MATTIE.msgAPI.footerMsg('True Ambush Round --All Enemies cannot attack this round.');
		}
		setTimeout(() => {
			if (spiderMode) {
				/** @type {Game_Enemy} */
				const spider = $gameTroop.baseMembers()[0];

				spider.performDamage();
				spider.die();
				spider.hide();
			}

			for (let index = spiderMode ? 0 : 1; index < roundIds.length; index++) {
				$gameSwitches.setValue(MATTIE.static.switch.backstab, true);
				const additionalId = roundIds[index];
				const additionalTroop = new MATTIE.troopAPI.RuntimeTroop(additionalId);
				switch (additionalId) {
				case MATTIE.static.troops.skinGrannyId:
					// skin granny needs this to be false for her to transform
					additionalTroop.setSwitchValue(1807, false);
					break;

				default:
					break;
				}

				additionalTroop.spawn(turn);
			}
			setTimeout(() => {
				if ($gameParty.leader().hasSkill(MATTIE.static.skills.enGarde.id)) {
					$gameVariables.setValue(19, -1);
					$gameTroop.forEachAdditionalTroop((troop) => {
						troop.setVariableValue(19, -1);
						troop.baseMembers().forEach((enemy) => {
							enemy.addState(MATTIE.static.states.cantDoShitOnce); // add cant do shit one turn
						});
					});
					//
				}

				BattleManager.setCantStartInputting(false);
				setTimeout(() => {
					MATTIE.troopAPI.config.blockAllPages = false;
				}, 500);
				BattleManager.setEventCallback((n) => {
					$gameTemp.reserveCommonEvent(66); // after battle
					cb();
				});
				afterSetupCb();
			}, 500);
		}, 500);
	}, 3000);
};

/**
 * @description a method to return the members of this troop without adding any additional members from other troops that my be present
 * @returns {Game_Enemy[]} an array of all game enemies in this troop
 *
*/
Game_Troop.prototype.baseMembers = function () {
	return this._enemies;
};

/**
 * @description override the members function to include all additional members of troops that may be present
 * @returns {Game_Enemy[]} an array of all game enemies present in this combat
 *
 * */
Game_Troop.prototype.members = function () {
	let members = this._enemies;
	this.forEachAdditionalTroop((additionalTroop) => {
		members = members.concat(additionalTroop.baseMembers());
	});
	return members;
};

/**
 * @description take a member index and find the troop it exists in
 * @param {int} memberIndex the index of the member within this.members();
 * @returns the MId of the member's troop, -1 if main troop, -2 if out of range
 */
Game_Troop.prototype.mapMemberIndexToTroopId = function (memberIndex) {
	let members = this._enemies;
	let maxVal = members.length - 1;
	if (memberIndex <= maxVal) return -1;
	const keys = Object.keys(this._additionalTroops);
	for (let index = 0; index < keys.length; index++) {
		const additionalTroop = this._additionalTroops[keys[index]];
		members = members.concat(additionalTroop.baseMembers());
		maxVal = members.length - 1;
		if (memberIndex <= maxVal) return additionalTroop.getMId();
	}
	return -2;
};

/** @description proform a callback on all additional troops */
Game_Troop.prototype.forEachAdditionalTroop = function (cb) {
	const keys = Object.keys(this._additionalTroops);
	for (let index = 0; index < keys.length; index++) {
		const additionalTroop = this._additionalTroops[keys[index]];
		cb(additionalTroop);
	}
};

/**
 * @description add a troop to this object's additional troops array
 * @param {MATTIE.troopAPI.RuntimeTroop} troop the troop to add
 *
*/
Game_Troop.prototype.addRuntimeTroop = function (troop) {
	this._additionalTroops[troop.getMId()] = troop;
	this.makeUniqueNames();
};

/**
 * @description remove a troop from the additional troops array
 * @param {MATTIE.troopAPI.RuntimeTroop} troop the troop to remove
 *
*/
Game_Troop.prototype.removeRuntimeTroop = function (troop) {
	if (this._additionalTroops[troop.getMId()]) delete this._additionalTroops[troop.getMId()];
};

/**
 * @description get the multiplayer troop id of this obj
 * @returns the id
 * or -1 in the case that it is the global $gameTroop
 * or -2 if not defined and not the global game troop
 *
*/
Game_Troop.prototype.getMId = function () {
	const notMtroopId = !(this instanceof MATTIE.troopAPI.RuntimeTroop) ? -1 : -2;
	return this._MTroopId ? this._MTroopId : notMtroopId;
};

/**
 * @description takes a local index for baseMembers() and transforms it into an index for members()
 * @param {*} index the local index of the enemy
 * @returns the new index or the old one if the new one is undefined
 */
Game_Troop.prototype.convertLocalIndexToGlobal = function (index) {
	const newIndex = $gameTroop.members().indexOf(index);
	return newIndex > 0 ? newIndex : index;
};

/** @description the base function to setup the battle event of a game troop */
MATTIE_RPG.TroopApi_Game_Troop_Setup_Battle_Event = Game_Troop.prototype.setupBattleEvent;
/**
 * @description override the setup battle event to also setup battle events of all additional troops
 */
Game_Troop.prototype.setupBattleEvent = function () {
	MATTIE_RPG.TroopApi_Game_Troop_Setup_Battle_Event.call(this);
	this.forEachAdditionalTroop((additionalTroop) => {
		additionalTroop.setupBattleEvent();
	});
};

/** @description the base method to increase turn of a game troop */
MATTIE_RPG.TroopApi_Game_Troop_IncreaseTurn = Game_Troop.prototype.increaseTurn;
/**
 * @description override the increase turn method to also increase turn of all additional troops
 */
Game_Troop.prototype.increaseTurn = function () {
	MATTIE_RPG.TroopApi_Game_Troop_IncreaseTurn.call(this);
	this.forEachAdditionalTroop((additionalTroop) => {
		additionalTroop.increaseTurn();
	});
};

Game_Troop.prototype.isEventRunning = function () {
	if (this._interpreter.isRunning()) return true;
	const keys = Object.keys(this._additionalTroops);
	for (let index = 0; index < keys.length; index++) {
		const additionalTroop = this._additionalTroops[keys[index]];
		if (additionalTroop._interpreter.isRunning()) return true;
	}
	return false;
};

/** @description the base function to update the interpreter of a game troop */
MATTIE_RPG.TroopApi_Game_Troop_UpdateInterpreter = Game_Troop.prototype.updateInterpreter;
/**
 * @description override the update interpreter to also update battle events of all additional troops
 */
Game_Troop.prototype.updateInterpreter = function () {
	MATTIE_RPG.TroopApi_Game_Troop_UpdateInterpreter.call(this);
	this.forEachAdditionalTroop((additionalTroop) => {
		additionalTroop.updateInterpreter();
		if (additionalTroop.baseMembers().every((member) => member.isDead())) {
			additionalTroop.despawn();
		}
	});
};

/** @description  the game troops function to check conditionals */
MATTIE_RPG.TroopApi_Game_Troop_Meets_Conditions = Game_Troop.prototype.meetsConditions;
/**
 * @description the game troops function to check conditionals, override the c.enemy valid to use proper troop
 * @param {} page the page of the game troop, not the id
 */
Game_Troop.prototype.meetsConditions = function (page) {
	if (MATTIE.troopAPI.config.blockAllPages) return false;
	const c = page.conditions;
	if (!c.turnEnding && !c.turnValid && !c.enemyValid
        && !c.actorValid && !c.switchValid) {
		return false; // Conditions not set
	}

	const prevEnemyValid = c.enemyValid;
	if (c.enemyValid) {
		const enemy = this.baseMembers()[c.enemyIndex];
		if (!enemy || enemy.hpRate() * 100 > c.enemyHp) {
			return false;
		}
	}
	if (c.scriptValid) { // allow devs to pass scripts for conditions
		if (!eval(c.script)) { return false; }
	}

	const prevSwitchValid = c.switchValid;
	if (this instanceof MATTIE.troopAPI.RuntimeTroop) { // if this is a runtime troop check if a local switch exists
		if (c.switchValid) {
			if (!this.getSwitchValue(c.switchId)) return false;
		}
		c.switchValid = false;
	}

	c.enemyValid = false;
	const anyConditionsLeft = (c.turnEnding || c.turnValid || c.enemyValid || c.actorValid || c.switchValid);
	const returnVal = anyConditionsLeft ? MATTIE_RPG.TroopApi_Game_Troop_Meets_Conditions.call(this, page) : true;
	c.switchValid = prevSwitchValid;
	c.enemyValid = prevEnemyValid;
	return returnVal;
};

//--------------------------------------
// Run Time Troop Class
//--------------------------------------

/**
 * @description a class that handles adding troops to the current combat at runtime
 * @param {int} troopId, the id of the troop this class represents
 * @param {int} xOffset, screen x offset for entire troop
 * @param {int} yOffset, screen y offset for entire troop
 * @returns {MATTIE.troopAPI.RuntimeTroop}
 * @class
 * */
MATTIE.troopAPI.RuntimeTroop = function () {
	this.initialize.apply(this, arguments);
};

MATTIE.troopAPI.RuntimeTroop.prototype = Object.create(Game_Troop.prototype);
MATTIE.troopAPI.RuntimeTroop.prototype.constructor = MATTIE.troopAPI.RuntimeTroop;

MATTIE.troopAPI.RuntimeTroop.prototype.getSwitchValue = function (id) {
	switch (id) {
	case MATTIE.static.switch.talk:
	case MATTIE.static.switch.toughEnemyMode: // is_enemy_tough_mode should always return its global value
		// enemies should be able to all see this
	case MATTIE.static.switch.backstab: // backstab should apply to all enemies
		return $gameSwitches.value(id);

	case MATTIE.static.switch.justGuard:
		return $gameSwitches.value(id) || this.localSwitches[id]; // gaurd can come local or global

	default:
		break;
	}

	if (typeof this.localSwitches[id] !== 'undefined') {
		return this.localSwitches[id];
	}

	return false; // $gameSwitches.value(id);
};

MATTIE.troopAPI.RuntimeTroop.prototype.setSwitchValue = function (id, bool) {
	this.localSwitches[id] = bool;
	return this.localSwitches[id];
};

MATTIE.troopAPI.RuntimeTroop.prototype.getVariableValue = function (id) {
	if (typeof this.localVars[id] !== 'undefined') {
		return this.localVars[id];
	}

	return $gameVariables.value(id);
};

MATTIE.troopAPI.RuntimeTroop.prototype.setVariableValue = function (id, bool) {
	this.localVars[id] = bool;
	return this.localVars[id];
};

MATTIE.troopAPI.RuntimeTroop.prototype.getLocalSwitches = function () {
	return this.localSwitches;
};
/**
 * @description the initialize method for a runtime troop. This sets up all values we might need.
 * @param {int} troopId, the id of the troop this class represents
 * @param {int} xOffset, screen x offset for entire troop
 * @param {int} yOffset, screen y offset for entire troop
 * @returns {MATTIE.troopAPI.RuntimeTroop}
 * */
MATTIE.troopAPI.RuntimeTroop.prototype.initialize = function (troopId, xOffset = 0, yOffset = 0) {
	Game_Troop.prototype.initialize.call(this);
	this._interpreter.setTroop(this);
	this.clear();
	this.setup(troopId, xOffset, yOffset);
	this._interpreter.setTroop(this);
	/** @description an array of the sprites of the enemies in this troop */
	this._sprites = [];

	/** @description any local switches that exist */
	this.localSwitches = {};

	/** @description any local vars that are set */
	this.localVars = {};

	/**
     * @description the current battle spriteset
     * @type {Spriteset_Battle}
     *
    */
	this.spriteSet = SceneManager._scene._spriteset;
	this.setupTroopId();
};

/**
 * @description get all sprites associated with this troop
 * @returns {Sprite_Enemy[]} a list of the enemy sprites
 */
MATTIE.troopAPI.RuntimeTroop.prototype.sprites = function () {
	return this._sprites;
};

/** @description generate internal troop id for sorting */
MATTIE.troopAPI.RuntimeTroop.prototype.setupTroopId = function () {
	let id = JSON.stringify(this._troopId);
	if (this.spriteSet.additionalEnemyTroops) {
		while (Object.keys(this.spriteSet.additionalEnemyTroops).includes(id)) {
			id += '(1)';
		}
	}
	this._MTroopId = id;
};

/**
 * @description add the sprites of this troop to the current battle sprite sheet
 */
MATTIE.troopAPI.RuntimeTroop.prototype.addSpritesToCurrentBattleSet = function () {
	const members = this.baseMembers();
	for (let index = 0; index < members.length; index++) {
		/** @type {Sprite_Enemy} */
		const gameEnemy = members[index];
		this._sprites.push(this.spriteSet.addAdditionalEnemy(gameEnemy, this._MTroopId));
	}
	this.spriteSet.visualSort();
	if (MATTIE.troopAPI.config.shouldSort) { this.spriteSet.refreshSpacing(true); }
};

/**
 * @description remove all sprites associated with the troop from the current battle
 * @param {MATTIE.troopAPI.RuntimeTroop} troop
 */
MATTIE.troopAPI.RuntimeTroop.prototype.removeSpritesFromCurrentBattleSet = function (troop) {
	if (this.spriteSet.additionalEnemyTroops) {
		this.spriteSet.additionalEnemyTroops[troop.getMId()].forEach((sprite) => {
			this.spriteSet._battleField.removeChild(sprite);
			this.spriteSet.additionalEnemySprites.splice(this.spriteSet.additionalEnemySprites.indexOf(sprite), 1);
		});

		delete this.spriteSet.additionalEnemyTroops[troop.getMId()];
		this.spriteSet.visualSort();
		if (MATTIE.troopAPI.config.shouldSort) { this.spriteSet.refreshSpacing(true); }
	}
};

/**
 * @description spawn the event into the actual game. Until this is called the event is meaningless
 * @returns {MATTIE.troopAPI.RuntimeTroop} returns itself so that you can initialize and spawn in one line if you would like.
 */
MATTIE.troopAPI.RuntimeTroop.prototype.spawn = function () {
	if ($gameParty.inBattle()) { // check if the game party is in battler
		if ($gameTroop) { // check if there is a current troop;
			this.setupTroopId();
			$gameTroop.addRuntimeTroop(this); // pass this runtime troop to the active troop

			this.addSpritesToCurrentBattleSet();
			this._interpreter.setTroop(this);
		}
	}
	return this;
};

/**
 * @description remove the troop from the active battle if it is present
 * @returns {MATTIE.troopAPI.RuntimeTroop} returns itself so that you can chain commands
 */
MATTIE.troopAPI.RuntimeTroop.prototype.despawn = function () {
	if ($gameParty.inBattle()) { // check if the game party is in battler
		if ($gameTroop) { // check if there is a current troop;
			$gameTroop.removeRuntimeTroop(this); // pass this runtime troop to the active troop
			this.removeSpritesFromCurrentBattleSet(this);
		}
	}
	return this;
};

//--------------------------------------
// game interpreter overrides
//--------------------------------------

/**
 * @description set the target troop of this interpreter
 * if this is not set the game interpreter will target $gameTroop instead
 * @param {Game_Troop} gameTroop
 * */
Game_Interpreter.prototype.setTroop = function (gameTroop) {
	this.$gameTroop = gameTroop;
};

/**
 * @description get the game troop of this interpreter or global if not set
 * @returns {Game_Troop} the normal game troop or the current game troop
 */
Game_Interpreter.prototype.getTroop = function () {
	return this.$gameTroop || $gameTroop;
};

/**
 *  @description iterate enemy index of local troop only
 *  @param {int} param apears to be enemy id
 *  @param {Function} callback, the call back to execute
 * */
Game_Interpreter.prototype.iterateEnemyIndex = function (param, callback) {
	if (param < 0) {
		this.getTroop().baseMembers().forEach(callback);
	} else {
		const enemy = this.getTroop().baseMembers()[param];
		if (enemy) {
			callback(enemy);
		}
	}
};

Game_Interpreter.prototype.setupReservedCommonEvent = function () {
	if ($gameTemp.isCommonEventReserved(this.getTroop().getMId())) {
		this.setup($gameTemp.reservedCommonEvent().list);
		$gameTemp.clearCommonEvent();
		return true;
	}
	return false;
};

// Control Switches
MATTIE_RPG.TroopApi_Game_Interpreter_Command121 = Game_Interpreter.prototype.command121;
Game_Interpreter.prototype.command121 = function () {
	if (this.getTroop() instanceof MATTIE.troopAPI.RuntimeTroop) { // if the interpreter is targeting a runtime troop
		for (let i = this._params[0]; i <= this._params[1]; i++) {
			const bool = this._params[2] === 0;
			this.getTroop().setSwitchValue(i, bool);
		}
		return true;
	} // if the interpreter is not targeting a RuntimeTroop
	const returnVal = MATTIE_RPG.TroopApi_Game_Interpreter_Command121.call(this);
	return returnVal;
};

/** @description the default operatevariable method of the game interpreter */
MATTIE_RPG.TroopApi_Game_Interpreter_OperateVariable = Game_Interpreter.prototype.operateVariable;
/** @description the opperate variable method overriden such that it will set local variables if on a local troop */
Game_Interpreter.prototype.operateVariable = function (variableId, operationType, value) {
	if (this.getTroop() instanceof MATTIE.troopAPI.RuntimeTroop) { // if the interpreter is targeting a runtime troop
		try {
			let oldValue = this.getTroop().getVariableValue(variableId);
			switch (operationType) {
			case 0: // Set
				this.getTroop().setVariableValue(variableId, oldValue = value);
				break;
			case 1: // Add
				this.getTroop().setVariableValue(variableId, oldValue + value);
				break;
			case 2: // Sub
				this.getTroop().setVariableValue(variableId, oldValue - value);
				break;
			case 3: // Mul
				this.getTroop().setVariableValue(variableId, oldValue * value);
				break;
			case 4: // Div
				this.getTroop().setVariableValue(variableId, oldValue / value);
				break;
			case 5: // Mod
				this.getTroop().setVariableValue(variableId, oldValue % value);
				break;
			}
		} catch (e) {
			this.getTroop().setVariableValue(variableId, 0);
		}
		return true;
	} // if the interpreter is not targeting a RuntimeTroop
	return MATTIE_RPG.TroopApi_Game_Interpreter_OperateVariable.call(this, variableId, operationType, value);
};

/** * @description the game interpreter's conditional branch */
MATTIE_RPG.TroopApi_Game_Interpreter_Command111 = Game_Interpreter.prototype.command111;
/**
 * @description the game interpreters conditional branch
 * we are overriding the enemy case to use the proper troop and leaving the rest the same
 * @returns {boolean} sucsess
 */
Game_Interpreter.prototype.command111 = function () {
	let result = false;
	switch (this._params[0]) {
	case 0: // Switch
		if (this.getTroop() instanceof MATTIE.troopAPI.RuntimeTroop) {
			result = (this.getTroop().getSwitchValue(this._params[1]) === (this._params[2] === 0));
		} else {
			result = ($gameSwitches.value(this._params[1]) === (this._params[2] === 0));
		}
		break;
	case 1: // Variable
		var value1;
		var value2;
		if (this.getTroop() instanceof MATTIE.troopAPI.RuntimeTroop) { // if additional troop
			value1 = this.getTroop().getVariableValue(this._params[1]);
			if (this._params[2] === 0) {
				value2 = this._params[3];
			} else {
				value2 = this.getTroop().getVariableValue(this._params[3]);
			}
		} else { // if normal troop
			value1 = $gameVariables.value(this._params[1]);
			if (this._params[2] === 0) {
				value2 = this._params[3];
			} else {
				value2 = $gameVariables.value(this._params[3]);
			}
		}
		switch (this._params[4]) {
		case 0: // Equal to
			result = (value1 === value2);
			break;
		case 1: // Greater than or Equal to
			result = (value1 >= value2);
			break;
		case 2: // Less than or Equal to
			result = (value1 <= value2);
			break;
		case 3: // Greater than
			result = (value1 > value2);
			break;
		case 4: // Less than
			result = (value1 < value2);
			break;
		case 5: // Not Equal to
			result = (value1 !== value2);
			break;
		}

		break;
	case 2: // Self Switch
		if (this._eventId > 0) {
			const key = [this._mapId, this._eventId, this._params[1]];
			result = ($gameSelfSwitches.value(key) === (this._params[2] === 0));
		}
		break;
	case 3: // Timer
		if ($gameTimer.isWorking()) {
			if (this._params[2] === 0) {
				result = ($gameTimer.seconds() >= this._params[1]);
			} else {
				result = ($gameTimer.seconds() <= this._params[1]);
			}
		}
		break;
	case 4: // Actor
		var actor = $gameActors.actor(this._params[1]);
		if (actor) {
			const n = this._params[3];
			switch (this._params[2]) {
			case 0: // In the Party
				result = $gameParty.members().contains(actor);
				break;
			case 1: // Name
				result = (actor.name() === n);
				break;
			case 2: // Class
				result = actor.isClass($dataClasses[n]);
				break;
			case 3: // Skill
				result = actor.hasSkill(n);
				break;
			case 4: // Weapon
				result = actor.hasWeapon($dataWeapons[n]);
				break;
			case 5: // Armor
				result = actor.hasArmor($dataArmors[n]);
				break;
			case 6: // State
				result = actor.isStateAffected(n);
				break;
			}
		}
		break;
	case 5: // Enemy
		var enemy = this.getTroop().baseMembers()[this._params[1]];
		if (enemy) {
			switch (this._params[2]) {
			case 0: // Appeared
				result = enemy.isAlive();
				break;
			case 1: // State
				result = enemy.isStateAffected(this._params[3]);
				break;
			}
		}
		break;
	case 6: // Character
		var character = this.character(this._params[1]);
		if (character) {
			result = (character.direction() === this._params[2]);
		}
		break;
	case 7: // Gold
		switch (this._params[2]) {
		case 0: // Greater than or equal to
			result = ($gameParty.gold() >= this._params[1]);
			break;
		case 1: // Less than or equal to
			result = ($gameParty.gold() <= this._params[1]);
			break;
		case 2: // Less than
			result = ($gameParty.gold() < this._params[1]);
			break;
		}
		break;
	case 8: // Item
		result = $gameParty.hasItem($dataItems[this._params[1]]);
		break;
	case 9: // Weapon
		result = $gameParty.hasItem($dataWeapons[this._params[1]], this._params[2]);
		break;
	case 10: // Armor
		result = $gameParty.hasItem($dataArmors[this._params[1]], this._params[2]);
		break;
	case 11: // Button
		result = Input.isPressed(this._params[1]);
		break;
	case 12: // Script
		result = !!eval(this._params[1]);
		break;
	case 13: // Vehicle
		result = ($gamePlayer.vehicle() === $gameMap.vehicle(this._params[1]));
		break;
	}
	this._branch[this._indent] = result;
	if (this._branch[this._indent] === false) {
		this.skipBranch();
	}
	return true;
};

/** * @description the game interpreter's tint screen command */
MATTIE_RPG.TroopApi_Game_Interpreter_Command223 = Game_Interpreter.prototype.command223;
/** @description only the main troop controller is allowed to tint the screen */
Game_Interpreter.prototype.command223 = function () {
	if (!(this.getTroop() instanceof MATTIE.troopAPI.RuntimeTroop)) return MATTIE_RPG.TroopApi_Game_Interpreter_Command223.call(this);
	return true;
};

/**
 * @description the enemy appear command, overridden to use correct troop
 * @returns {boolean} was operation successful (always true)
 */
Game_Interpreter.prototype.command335 = function () {
	this.iterateEnemyIndex(this._params[0], (enemy) => {
		enemy.appear();
		this.getTroop().makeUniqueNames();
	});
	return true;
};

/**
 * @description the enemy transform command, overridden to use correct troop
 * @returns {boolean} was operation successful (always true)
 */
Game_Interpreter.prototype.command336 = function () {
	this.iterateEnemyIndex(this._params[0], (enemy) => {
		enemy.transform(this._params[1]);
		this.getTroop().makeUniqueNames();
	});
	return true;
};

//--------------------------------------
// Spriteset_Battle
//--------------------------------------

// The code below needs some work --we need to allow auto placement and arrangement of battlers

/**
 * @description controls the padding on the left and right of the screen
 * @default .1, 10% on both sides so 20%
 */
MATTIE.troopAPI.config.screenWidthPadding = 0.2;
/**
 * @description add an additional enemy to the battle enemies sprites at runtime
 * @param {Game_Enemy} gameEnemy
 * @returns {Sprite} pixi sprite of the new enemy
 */
Spriteset_Battle.prototype.addAdditionalEnemy = function (gameEnemy, troopId = 'defaultTroop') {
	if (!this.additionalEnemySprites) this.additionalEnemySprites = [];
	if (!this.additionalEnemyTroops) this.additionalEnemyTroops = {};
	const sprite = new Sprite_Enemy(gameEnemy);
	this._battleField.addChild(sprite);
	this.additionalEnemySprites.push(sprite);
	if (!this.additionalEnemyTroops[troopId]) this.additionalEnemyTroops[troopId] = [];
	this.additionalEnemyTroops[troopId].push(sprite);

	return sprite;
};

Spriteset_Battle.prototype.battlerSprites = function () {
	return this._enemySprites.concat(this._actorSprites);
};

/**
 * @description try to space out the additional enemies as much as possible
 */
Spriteset_Battle.prototype.refreshSpacing = function (shouldAffectBase = true) {
	if (!this.additionalEnemyTroops) this.additionalEnemyTroops = {};
	const dict = this.additionalEnemyTroops;
	if (shouldAffectBase && $gameTroop._troopId != MATTIE.static.troops.tripleCrow) { dict[-1] = this._enemySprites; }

	const keys = Object.keys(dict);
	const minX = -(Graphics.width / 3);
	const maxX = (Graphics.width / 3);
	const v = maxX - minX;
	const bestX = ((index) => {
		const t = index / (keys.length - 1);
		const x = (minX + v * t); // parametric form in 1d
		return x;
	});

	for (let index = 0; index < keys.length; index++) {
		const key = keys[index];
		const enemyList = dict[key]; // a list of sprites
		let actorId;
		if (MATTIE.multiplayer.pvp) { actorId = MATTIE.multiplayer.pvp.PvpController.mapTroopToActor(key); }
		let xOffset = bestX(index);
		let yOffset = 0;

		/** @type {Sprite_Battler} */
		enemyList.forEach((sprite) => {
			yOffset = 0;
			if (sprite) {
				if (!sprite.baseX) sprite.baseX = sprite._homeX;
				if (!sprite.baseY) sprite.baseY = sprite._homeY;

				// handle pvp spacing for leaders
				if (MATTIE.multiplayer.pvp) {
					if (MATTIE.multiplayer.pvp.inPVP) {
						if (MATTIE.multiplayer.getCurrentNetController().player.pvpCombatArr.some(
							(netKey) => {
								const netCont = MATTIE.multiplayer.getCurrentNetController();

								const player = MATTIE.multiplayer.getCurrentNetController().netPlayers[netKey];
								if (player) return player.actorId == actorId;
								return false;
							},

						)) {
							yOffset -= 35;
							xOffset = 20;
						}
					}
				}

				sprite.setHome((sprite.baseX + xOffset), sprite._homeY + yOffset);
			}
		});
	}
};

/**
 * @description sort the layers
 */
Spriteset_Battle.prototype.visualSort = function () {
	const list = this._enemySprites.concat(this.additionalEnemySprites);// .concat(this._actorSprites);
	list.sort(this.compareEnemySprite.bind(this));
	for (let index = 0; index < list.length; index++) {
		const sprite = list[index];
		this._battleField.removeChild(sprite);
	}

	for (let index = 0; index < list.length; index++) {
		const sprite = list[index];
		sprite.spriteId = index;
		this._battleField.addChildAt(sprite, 0);
	}
};

//------------------------------
// Game_Temp
//-------------------------------
/* @description override the reserve common event function to also take a troopName */
MATTIE_RPG.TroopApi_Game_Temp_reserveCommonEvent = Game_Temp.prototype.reserveCommonEvent;
Game_Temp.prototype.reserveCommonEvent = function (commonEventId) {
	MATTIE_RPG.TroopApi_Game_Temp_reserveCommonEvent.call(this, commonEventId);
	this._troopId = BattleManager.getCurrentTroopMId();
};

/** @description the base is common event reserved method */
MATTIE_RPG.TroopApi_Game_Temp_isCommonEventReserved = Game_Temp.prototype.isCommonEventReserved;
/** @description override the method to check if a troopId has been specified and only return true if the id matches the troop that assigned it */
Game_Temp.prototype.isCommonEventReserved = function (id = undefined) {
	if (this._troopId && id) {
		return MATTIE_RPG.TroopApi_Game_Temp_isCommonEventReserved.call(this) && id == this._troopId;
	}
	return MATTIE_RPG.TroopApi_Game_Temp_isCommonEventReserved.call(this);
};

BattleManager.getCurrentTroopMId = function () {
	const allMembers = $gameTroop.members();
	const indexOfCurrentSubject = allMembers.indexOf(this._subject);
	const mId = $gameTroop.mapMemberIndexToTroopId(indexOfCurrentSubject);
	return mId;
};
BattleManager.cantStartInputting = function () {
	return this._cantStart;
};

BattleManager.setCantStartInputting = function (bool) {
	this._cantStart = bool;
};

/** @description the base is common event reserved method */
MATTIE_RPG.TroopApi_battleMan_startInput = BattleManager.startInput;
BattleManager.startInput = function () {
	if (!this.cantStartInputting()) MATTIE_RPG.TroopApi_battleMan_startInput.call(this);
};