// Monster Ai Behavior.
// Worthless Bums 2012.


function scheduleMonsterTurn ()
{//echo("scheduleMonsterTurn()");
	%monsterTurnDelay = 50;		// Reducing this to a low number lags firing!
	$monsterTurnEvent = schedule(%monsterTurnDelay, 0, "monsterTurn");
	return true;
}
function isHulkSpawned ()
{//echo("isHulkSpawned()");
	for (%i = 0; %i < $monsterList.getCount(); %i++)
	{
		if (($monsterList.getObject(%i)).name $= "Hulk")
		{
			return true;
		}
	}
	return false;
}
function isRevenantSpawned ()
{//echo("isRevenantSpawned()");
	for (%i = 0; %i < $monsterList.getCount(); %i++)
	{
		if (($monsterList.getObject(%i)).name $= "Revenant")
		{
			return true;
		}
	}
	return false;
}
function isBrainienSpawned ()
{//echo("isBrainienSpawned()");
	for (%i = 0; %i < $monsterList.getCount(); %i++)
	{
		if (($monsterList.getObject(%i)).name $= "Brainien")
		{
			return true;
		}
	}
	return false;
}
function isGunshipSpawned ()
{//echo("isGunshipSpawned()");
	for (%i = 0; %i < $monsterList.getCount(); %i++)
	{
		if (($monsterList.getObject(%i)).name $= "Gunship")
		{
			return true;
		}
	}
	return false;
}
function engageMonsterTurn ()
{//echo("engageMonsterTurn()");
	$monsterTurnOngoing = true;
}
function releaseMonsterTurn()
{//echo("releaseMonsterTurn()");
	$monsterTurnOngoing = false;
}
function getClosestDistanceFromSquad (%monster)
{
	%closestDistance = 99999.9;
	if ($leaderPersist	&&
		isObject($playerSquad[0]))
	{
		%distance = VectorDist(%monster.getPosition(), $playerSquad[0].getPosition());
		if (%distance < %closestDistance)
		{
			%closestDistance = %distance;
		}
	}
	if ($scoutPersist	&&
		isObject($playerSquad[1]))
	{
		%distance = VectorDist(%monster.getPosition(), $playerSquad[1].getPosition());
		if (%distance < %closestDistance)
		{
			%closestDistance = %distance;
		}
	}
	if ($grenadierPersist	&&
		isObject($playerSquad[2]))
	{
		%distance = VectorDist(%monster.getPosition(), $playerSquad[2].getPosition());
		if (%distance < %closestDistance)
		{
			%closestDistance = %distance;
		}
	}
	if ($supportPersist	&&
		isObject($playerSquad[3]))
	{
		%distance = VectorDist(%monster.getPosition(), $playerSquad[3].getPosition());
		if (%distance < %closestDistance)
		{
			%closestDistance = %distance;
		}
	}
	return %closestDistance;
}
function randomlyDamageCharacterGear (%characterIndex, %damage, %monster)
{//echo("randomlyDamageCharacterGear()");
	%armor = $playerSquad[%characterIndex].armor;
	if (%armor.armorRating > 0)
	{
		%armor.armorRating--;
		%armor.armorType = setWord(%armor.armorType, 2, %armor.armorRating);
	}
	updateEventText($squadColor @ $playerSquad[%characterIndex].name SPC $hostileActionColor @ "loses 1 armor" @ $defaultColor @ "!");
}
function impulseCharacter (%characterIndex)
{//echo("impulseCharacter()");
	%position = $playerSquad[%characterIndex].getPosition();
	%shakeTimeBlock = 40;
	%shakeForce = 30;
	$playerSquad[%characterIndex].setImpulseForce(%shakeForce, 0);
	$playerSquad[%characterIndex].schedule(%shakeTimeBlock, "setImpulseForce", -2 * %shakeForce, 0);
	$playerSquad[%characterIndex].schedule(%shakeTimeBlock * 2, "setImpulseForce", %shakeForce, 0);
	// Stop it!
	$playerSquad[%characterIndex].schedule(%shakeTimeBlock * 3, "setImpulseForce", 0, 0);
	$playerSquad[%characterIndex].schedule(%shakeTimeBlock * 4, "setPosition", %position);
	// Enforce proper position!
	schedule(%shakeTimeBlock * 5, 0, "enforceSquadIndices");
}
function characterHit (%characterIndex, %damage, %monster, %damageType, %hitType)
{//echo("characterHit()");
	// Impulse character!
	//impulseCharacter(%characterIndex);	// Don't use this!
	shrinkAndGrowUnit($playerSquad[%characterIndex], 8.0, 120.0);
	triggerSlowdownHitEffect();
	// Grant action points from the Power stat.
	if ($playerSquad[%characterIndex].power > 0)
	{
		$playerSquad[%characterIndex].currentActionPoints += $playerSquad[%characterIndex].power;
		updateSquadMemberFloatingActionPoints();
	}
	// Apply passive talents that reduce damage
	%damage = applyPassiveTalentDamageReduction(%characterIndex, %damage, %damageType);
	$GlobalStats.totalDamageTaken += %damage;
	$playerSquad[%characterIndex].setBlendColor("0 0 0 1");
	$playerSquad[%characterIndex].schedule(300, "setBlendColor", "1 1 1 1");
	randomlyDamageCharacterGear(%characterIndex, %damage, %monster);
	scrollFloatingText($playerSquad[%characterIndex], %damage, %damageType, %hitType);
	%bloodSplatter = new Sprite()
	{
		SceneLayer = 0;
		scenegraph = levelSceneGraph;
		image = "Sprites:BloodSplatterFloor";
		position = $playerSquad[%characterIndex].getPosition();
		size = "4 4";
		visible = true;
	};
	addObjectToScene(%bloodSplatter);
	%bloodSplatter.schedule(250, "safeDelete");
	sceneWindow2D.startCameraShake(10, 0.3);
	schedule(10, 0, "updateFloatingHealthPointText", %characterIndex);
	return %damage;
}
function impulseEnemy (%unit, %direction)
{//echo("impulseEnemy()");//echo("%unit.name =" SPC %unit.name);echo("%direction =" SPC %direction);
	if ($EnemyKnockBack	||
		!isObject(%unit))
	{
		return;
	}
	$enemyImpulse = true;
	%position = %unit.getPosition();
	%shakeTimeBlock = 40;
	%shakeForceX = 20;
	%shakeForceY = 20;
	switch (%direction)
	{
		case $northDirection:
			%shakeForceX = 0;
			%shakeForceY = -30;
		case $southDirection:
			%shakeForceX = 0;
			%shakeForceY = 30;
		case $eastDirection:
			%shakeForceX = 30;
			%shakeForceY = 0;
		case $westDirection:
			%shakeForceX = -30;
			%shakeForceY = 0;
	}
	%unit.setImpulseForce(%shakeForceX, %shakeForceY);
	%unit.schedule(%shakeTimeBlock, "setImpulseForce", -1 * %shakeForceX, -1 * %shakeForceY);
	%unit.schedule(%shakeTimeBlock * 2, "setImpulseForce", 0.75 * %shakeForceX, 0.75 * %shakeForceY);
	// Stop it!
	%unit.schedule(%shakeTimeBlock * 3, "setImpulseForce", 0, 0);
	%unit.schedule(%shakeTimeBlock * 3, "setPosition", %position);
	// Enforce proper position!
	schedule(%shakeTimeBlock * 4, 0, "enforceTargetMonsterPosition", %unit);
	
	//%unit.schedule(%shakeTimeBlock * 8, "setPosition", getWord(%position, 0) SPC getWord(%position, 1) + 4);	// Works!
	$enemyImpulse = false;
}
function monsterHit (%monsterIndex, %damage, %direction, %damageType)
{//echo("monsterHit()");//echo("%monsterIndex =" SPC %monsterIndex);echo("%damageType =" SPC %damageType);
	if (%monsterIndex < 0)
	{
		return;
	}
	$GlobalStats.totalDamageDealt += %damage;
	triggerSlowdownHitEffect();
	%monster = $monsterList.getObject(%monsterIndex);
	if (!isObject(%monster))
	{
		return;
	}
	//impulseEnemy(%monster, %direction);
	shrinkAndGrowUnit(%monster, 8.0, 120.0);
	if (%monster.name $= "Revenant"	||
		%monster.name $= "Revenion")
	{
		spawnRevenion(%monster);
	}
	// FROM BEHIND.
	// Deal additional damage if the monster is hit from behind!
	if (%monster.facing == %direction)
	{//echo("crit!");
		scrollFloatingText(%monster, %damage, %damageType, "crit");
	}
	else
	{
		scrollFloatingText(%monster, %damage, %damageType);
	}
	// FLANKED.
	%flankedHit = false;
	if (%direction == $northDirection	||
		%direction == $southDirection)
	{
		if (%monster.facing == $westDirection	||
			%monster.facing == $eastDirection)
		{
			%flankedHit = true;
		}
	}
	else
	{
		if (%monster.facing == $northDirection	||
			%monster.facing == $southDirection)
		{
			%flankedHit = true;
		}
	}
	if (%flankedHit)
	{
		// Handle floating text.
		scrollFloatingText(%monster, "flank", "flank");
		// Turn the monster toward the shot (opposite direction from fire direction).
		%monsterFacing = getReverseDirection(%direction);
		setMonsterFacing(%monsterFacing, %monster);
	}
	// NOTE: Need to branch this later for non-mechanical monsters!
	createElectricalSparksAnimation($monsterList.getObject(%monsterIndex));
	handleHealthIndicators();
	// NOTE: Should we always run this? It -does- help because it will also wake up nearby monsters...
	monsterAttackedReaction($monsterList.getObject(%monsterIndex));
	// Check for squad member weapon status effects!
	// Grenades and melee (and Scout class talent "Garrote") don't enable equipped weapon status effects!
	if (%damageType !$= "grenade"	&&
		%damageType !$= "garrote")
	{
		checkWeaponStatusEffects(%monsterIndex);
	}
	checkActiveTalents(%monster);
	//echo("%monster.currentHealth =" SPC %monster.currentHealth);
	if (%monster.currentHealth <= 0)
	{
		if (%monster.isRobot)
		{
			triggerArmorRepair();
		}
		updateKillRecord($activeCharacter, %monster.name, %monster.level);
		handleMonsterDeath($activeCharacter, %monsterIndex);
	}
	updateMonsterFloatingHealthPointText(getMonsterIndex(%monster));
	// Pause the game briefly to "sell" the hit!
	//triggerSlowdownHitEffect();
}
function objectHit (%objectHit, %damage)
{//echo("objectHit()");
	scrollFloatingText(%objectHit, %damage, "damage");
	if (isSquadMember(%objectHit))
	{
		%bloodSplatter = new Sprite()
		{
			SceneLayer = 0;
			scenegraph = levelSceneGraph;
			image = "Sprites:BloodSplatterFloor";
			position = %objectHit.getPosition();
			size = "4 4";
			visible = true;
		};
		addObjectToScene(%bloodSplatter);
		%bloodSplatter.schedule(250, "safeDelete");
	}
	else
	{
		createElectricalSparksAnimation(%objectHit);
	}
	checkActiveTalents(%objectHit);
}
function monstersHaveActionPoints ()
{//echo("monstersHaveActionPoints()");
	for (%i = 0; %i < $monsterList.getCount(); %i++)
	{
		%monster = $monsterList.getObject(%i);
		if (%monster.currentActionPoints > 0)
		{
			return true;
		}
	}
	return false;
}
function monstersRefundActionPoints ()
{//echo("monstersRefundActionPoints()");
	for (%i = 0; %i < $monsterList.getCount(); %i++)
	{
		%monster = $monsterList.getObject(%i);
		%monster.currentActionPoints = %monster.maximumActionPoints;
	}
}
function executeMonsterInterTurnSteps ()
{//echo("executeMonsterInterTurnSteps()");
	%monsterActCount = $monstersThatAct.getCount();
	if (%monsterActCount <= 0)
	{
		delayMonsterTurnEnd();
		return;
	}
	%monster = $monstersThatAct.getObject(0);
	%delay = 0;
	%delayStep1 = 60;//120;
	%delayStep2 = 120;//125;//250;
	%delayStep3 = 180;//200;//400;
	%delayStep4 = 240;//250;//500;
	%limit = %monster.currentActionPoints;
	for (%i = 1; %i <= %limit; %i++)
	{//echo("%monster.currentActionPoints =" SPC %monster.currentActionPoints);
		if (%monster.currentActionPoints <= 0)
		{
			%monster.currentActionPoints = 0;
			break;
		}
		if (%monster.stasis)
		{
			%monster.currentActionPoints = 0;
			break;	// Remove stasis at the END of monster turn; e.g. not here!
		}
		%delay = %i * $monsterStepDelay;	// getScaledMonsterStepDelay() scales $monsterStepDelay for timing actions!
		// We do this because it is jarring to lose the camera tracking when an enemy unit enters fog of war!
		if ($Game::EnemyCameraLock)
		{
			sceneWindow2D.schedule(%delay / 2, "mount", %monster, $sceneWindow2DxOffset SPC $sceneWindow2DyOffset, $cameraMountForce, false);
		}
		schedule(%delay      		 , 0, "monsterAction", %monster);
		schedule(%delay + %delayStep1, 0, "updateMonsterFloatingText");
		schedule(%delay + %delayStep2, 0, "executeGuardModeActions", %monster);		// *THIS MUST OCCUR BEFORE...
		schedule(%delay + %delayStep3, 0, "updateSentryTurrets");
	}
	$monstersThatAct.remove(%monster);
	if ($monstersThatAct.getCount() > 0)
	{
		%monsterInterTurnDelay = %delay;	// This timing should be fairly precise or we'll have monster inter-turn overlap or long waits in-between!
		schedule(%monsterInterTurnDelay, 0, "executeMonsterInterTurnSteps");
	}
	else
	{//echo("MONSTER TURN ENDED, DELAY TO ACTUAL END IS " @ %delay * %limit + 800 @ " MS!");
		//schedule(%delay * %limit + 800, 0, "delayMonsterTurnEnd");					// **...THIS FOR GUARD MODE TO TRIGGER ON THE FINAL MONSTER AP OF THE TURN!
		//schedule(%delayStep4, 0, "delayMonsterTurnEnd");					// **...THIS FOR GUARD MODE TO TRIGGER ON THE FINAL MONSTER AP OF THE TURN!
		schedule(%delay + %delayStep4, 0, "delayMonsterTurnEnd");					// **...THIS FOR GUARD MODE TO TRIGGER ON THE FINAL MONSTER AP OF THE TURN!
		sceneWindow2D.schedule(%delay + $monsterStepDelay, "mount", $playerSquad[$activeCharacter], $sceneWindow2DxOffset SPC $sceneWindow2DyOffset, $cameraMountForce, false);
	}
}
// Called when the player completes actions.
function monsterTurn ()
{//echo("monsterTurn()");
	updateMonsterTargetPanelInfo();
	enforceElevatorPoweredState();
	
	// NOTE: Should be handled by function scheduleMonsterTurn() now!
	if (isEventPending($monsterTurnEvent))
	{
		cancel($monsterTurnEvent);
	}
	
	// Make sure we operate on a clean state of data!
	enforceGameState();
	updateSquadMemberFloatingText();
	
	// Adds a small delay before running monster turn after squad is out of action points.
	if (isObject($rifleMuzzleFlashAnimation))
	{
		if (!isEventPending($monsterTurnEvent))
		{
			scheduleMonsterTurn();
		}
		return false;
	}
	
	// Run squad action point check.
	if (playerHasActionPoints())
	{
		autoSwitchSquadmember();
		return false;
	}
	// Player has no action points - auto end player turn and execute monster turn!
	else
	{
		if (!$monsterTurnOngoing	&&
			!$autoEndTurns			&&
			!$playerManuallyEndedTurn)
		{
			return false;
		}
		clearTargetIndicators(false);
		// Update stuff that only happens once per entire monster turn!
		if (!$monsterTurnOngoing)
		{
			$GravityWellOn = false;	// Only lasts for one turn!
			executeRandomWeatherEvent();
			hideHud();
			// Reset the tapping wall!
			$TappingWallTileX = -1;
			$TappingWallTileY = -1;
			
			squadRefundActionPoints();
			$GlobalStats.currentTurn++;
			updateSquadPowerCooldowns();
			//updateSentryTurrets();
			
			// DO INTER-TURN STUFF HERE!
			// Squad Power Shield only lasts for one turn!
			if ($characterWithShield != -1)
			{
				$characterWithShield = -1;
				($playerSquad[$activeCharacter].armor).armorRating = ($playerSquad[$activeCharacter].armor).armorRating / 2;
				$shieldAnimation.safeDelete();
				updateSquadInfoGui();
			}
			
			// Only log end turn message if turn did not end manually by player.
			if (!$InputManualEndTurn)
			{//echo("entering autoEndTurnCleanup()");
				autoEndTurnCleanup();
			}
			$InputManualEndTurn = false;
		}
	}
	// Make sure monster list is not empty!
	%monstersActed = 0;
	if ($monsterList.getCount() > 0)
	{
		if (!$monsterTurnOngoing)
		{
			engageMonsterTurn();
			// Collect all monsters that act this turn step!
			$monstersThatAct = new SimSet();
			// Loop through all monsters that have detected the player!
			for (%i = 0; %i < $monsterList.getCount(); %i++)
			{
				// Get a monster to test pathfinding with.
				// Note: We should loop through $monsterList (generated in spawnMonsters.cs) and check for line-of-sight/proximity and only move them when they can detect the player!
				%monster = $monsterList.getObject(%i);
				
				// If the monster is not close to any squad member, don't do anything!
				// NOTE: Use spotting xp to flag monster as active!
				//if (%monster.hasBeenSpotted)
				if ((%monster.isActive										&&
					 %monster.hasBeenSpotted								&&
					 $fogOfwar.isClear(%monster.xIndex, %monster.yIndex))				||
					 getClosestDistanceFromSquad(%monster) <= $MaxMonsterSneakDistance	||	// Close monsters still act!
					 %monster.name $= "Hulk"		||	// Bosses always act!
					 %monster.name $= "Revenant"	||
					 %monster.name $= "Brainien"	||
					 %monster.name $= "Gunship"		||
					 %monster.name $= "Matrix"		||
					 %monster.name $= "Patriarch")
				{
					$monstersThatAct.add(%monster);
					%monstersActed++;
				}
				else if (%monster.isActive	&&
						 !%monster.hasBeenSpotted)
				{
					sneakUpOnSquad(%monster);			// Special actions for out-of-sight monsters!
				}
				else
				{
					%monster.currentActionPoints = 0;
				}
			}
			
			// Execute monster actions.
			executeMonsterInterTurnSteps();
			
			// If no monsters acted this turn, have a chance to make tapping noises on the walls, moving the camera to each position!
			if ($MonsterList.getCount() > 0)
			{
				if (%monstersActed <= 0	&&
					getRandom(1, 100) <= $TappingMonsterChance)
				{
					engageMonsterTurn();
					schedule(1200, 0, "releaseMonsterTurn");
					
					// Find a wall tile in sight of the squad adjacent to a space tile!
					// NOTE: We should randomize this more, otherwise it just picks the most NW tile that qualifies!
					%characterIndex = getRandom(0, 3);
					if (isObject($playerSquad[%characterIndex]))
					{
						for (%i = (-1 * $playerSquad[%characterIndex].sight) + $playerSquad[%characterIndex].xIndex;
							 %i <= $playerSquad[%characterIndex].sight + $playerSquad[%characterIndex].xIndex;
							 %i++)
						{
							for (%j = (-1 * $playerSquad[%characterIndex].sight) + $playerSquad[%characterIndex].yIndex;
								 %j <= $playerSquad[%characterIndex].sight + $playerSquad[%characterIndex].yIndex;
								 %j++)
							{
								if (getRandom(1, 100) <= $TappingWallChance)		// A small chance to select this tile to tap!
								{
									if (%i >= 0 && %i < $mazeDimension	&&	// Bounds check.
										%j >= 0 && %j < $mazeDimension)		// Bounds check.
									{
										if (isObject($mazeRoom[%i, %j])		&&
											$mazeRoom[%i, %j].status == -2	&&	// This tile is a wall (or a door).
											(!isObject($mazeRoom[%i + 1, %j])	||
											 !isObject($mazeRoom[%i - 1, %j])	||
											 !isObject($mazeRoom[%i, %j + 1])	||
											 !isObject($mazeRoom[%i, %j - 1])))		// At least one adjacent tile is a floor.
										{
											if ($fogOfWar.isClear(%i, %j))	// Is within sight of the squad (clear fog of war).
											{
												if ($mazeRoom[%i + 1, %j].status == -1	||	// Ignore bounds checking here - not necessary.
													$mazeRoom[%i - 1, %j].status == -1	||
													$mazeRoom[%i, %j + 1].status == -1	||
													$mazeRoom[%i, %j - 1].status == -1)		// At least one adjacent tile is an outer space tile.
												{
													$TappingWallTileX = %i;
													$TappingWallTileY = %j;
													sceneWindow2D.dismount();
													sceneWindow2D.mount($mazeRoom[%i, %j], $sceneWindow2DxOffset SPC $sceneWindow2DyOffset, $cameraMountForce, false);
													schedule(500, 0, "playMetalLightSfx");
													schedule(1000, 0, "playMetalLightSfx");
													schedule(750, 0, "setIndividualDialoguePopup", $playerSquad[%characterIndex], "What was that noise?", "dialogue");
													sceneWindow2D.schedule(1250, 0, "dismount");
													sceneWindow2D.schedule(1500, "mount", $playerSquad[$activeCharacter], $sceneWindow2DxOffset SPC $sceneWindow2DyOffset, $cameraMountForce, false);
													// Set breakout conditions after this loop iteration.
													%i = $playerSquad[%characterIndex].sight + $playerSquad[%characterIndex].xIndex;
													%j = $playerSquad[%characterIndex].sight + $playerSquad[%characterIndex].yIndex;
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}
	else
	{
		clearGuardModeAnimations();
		resetSquadGuardModes();
		clearSquadMemberLabels();
		updateSquadInfoGui();
		schedule( 500, 0, "saveCurrentGame");
		schedule(1000, 0, "showHud");
		schedule(1050, 0, "triggerRandomEncounter");
	}
	// Spawn a grating monster!
	if (($overWorldLocation $= "Command Deck"	&&	$dungeonLevel > 1)	||
		$overWorldLocation !$= "Command Deck")
	{
		if ($MonsterList.getCount() > 0)
		{
			%gratingMonsterRoll = getRandom(1, 100);
			if (%gratingMonsterRoll <= $gratingMonsterChance)
			{
				spawnGratingMonster();
			}
			// Spawn a wall grating monster?
			%gratingMonsterRoll = getRandom(1, 100);
			if (%gratingMonsterRoll <= $gratingMonsterChance)
			{
				spawnWallGratingMonster();
			}
		}
	}
	return true;
}
// Called only after all monster actions are taken!
function delayMonsterTurnEnd ()
{//echo("delayMonsterTurnEnd()");
	$monsterTurnOngoing = false;
	clearGuardModeAnimations();
	clearSquadMemberLabels();
	updateAllStatusEffects();
	monstersRefundActionPoints();
	enforceMonsterPositions();	// NOTE: This has no correction for incorrect positions - it just synchronizes indices from the current position!
	updateEnvironmentEffects();
	initializeReadout();
	autoSwitchSquadMember();
	$fogOfWar.showFogOfWar();
	clearSquadEffects();
	updateTalentBarIcons();
	// Need to "wait" for all multi-action (action point) monsters to complete first round of actions!
	// Schedule next monster turn step.
	$monsterLastRound = true;
	$lastMonsterRoundFlagEvent = schedule($monsterStepDelay, 0, "monsterLastTurnFlag");
	monsterLastTurn();
	schedule(50, 0, "enforceSquadPosition");	// Really make sure this "sticks"!
	updateSquadMemberFloatingText();
	$playerManuallyEndedTurn = false;
	resetSquadGuardModes();
	updateInventoryTabInformation();
	if (!$leaderPersist		&&
		!$scoutPersist		&&
		!$grenadierPersist	&&
		!$supportPersist)
	{
		hideHud();
		cancel($enforceGameStateEvent);
		return;
	}
	$fogOfWar.updateMonsterVisibility();
	schedule(100, 0, "enforceMonsterPositions");
	schedule(1000, 0, "saveCurrentGame");
	SavingPopup.setPosition(getWord($Game::Resolution, 0) - 100, 16);
	SavingPopup.schedule(2000, "setVisible", true);
	SavingPopup.schedule(4000, "setVisible", false);
	schedule(2000, 0, "showHud");
	schedule(2050, 0, "triggerRandomEncounter");
}
// Placeholder scheduled function to delay for last monster turn action round.
function monsterLastTurn ()
{//echo("monsterLastTurn()");
	if ($monsterLastRound)
	{
		schedule($monsterStepDelay, 0, "monsterLastTurn");
		schedule($monsterStepDelay, 0, "updateMonsterFloatingText");
	}
}
// Placeholder scheduled flag function to delay for last monster turn action round.
function monsterLastTurnFlag ()
{//echo("monsterLastTurnFlag()");
	$monsterLastRound = false;
}

// Performs actions for a single monster.
function monsterAction (%monster)
{//echo("monsterAction()");
	if (isObject(%monster.sleepAnimation))
	{
		%monster.currentActionPoints = 0;
		return false;
	}
	if (%monster.currentActionPoints <= 0)
	{
		return false;
	}
	// Loop through the player squad and get the distance between each squad member and this monster.
	%distance = 9999999;			// Outrageously large number.
	%index = -1;
	for (%i = 0; %i < $maxSquadSize; %i++)
	{
		if (isObject($playerSquad[%i]))
		{
			%compDistance = VectorDist(%monster.xIndex SPC %monster.yIndex, $playerSquad[%i].xIndex SPC $playerSquad[%i].yIndex);
			if (%compDistance < %distance)
			{
				%distance = %compDistance;
				%index = %i;
			}
			// No run/select non-closest squad member code!
		}
	}
	if (%distance == 9999999)	// No squad member to path to!
	{
		return false;	
	}
	else
	{
		// Get ranged status calculation.
		%rangedStatus = calculateMonsterRangedStatus(%monster, %index);
		if (!%rangedStatus)
		{
			return false;
		}
		// Check again since the monster may have range attacked above!
		// If the monster has no action points then leave.
		if (%monster.currentActionPoints <= 0)
		{
			return false;
		}
		else
		{
			// We deduct points here since even not moving costs the monster AI action points!
			// Deduct action points.
			%monster.currentActionPoints--;		// Monsters only require 1 Action Point to range attack.
			// Sanity check.
			if (%monster.currentActionPoints < 0)
			{
				%monster.currentActionPoints = 0;
			}
		}
		// Enemies without ranged attacks!
		// Don't activate monster unless a squad member is close!
		if (%distance <= 20.0)
		{
			// Find move position from a path.
			%movePosition = findPath(%monster, $playerSquad[%index].xIndex, $playerSquad[%index].yIndex);
			// Sanity check - if the move position is too far away from the monster's current position, abort the action.
			%distance = VectorDist(getTilePositionFromIndices(getWord(%movePosition, 0), getWord(%movePosition, 1)), %monster.getPosition());
			%distance = mAbs(%distance);
			if (%distance > 10.0)
			{
				return false;
			}
		}
		else
		{//echo("monster too far to path!");
			return false;
		}
	}
	
	// Parse into x and y coordinates for monster position matching.
	%x = getWord(%movePosition, 0);
	%y = getWord(%movePosition, 1);
	// Sanity check - make sure the move position is within bounds!
	if (%x < 0 || %x > $mazeDimension - 1	||
		%y < 0 || %y > $mazeDimension - 1)
	{// Out of bounds pathing!
		return false;
	}
	// Check if findPath() returned the start position which indicates a no move situation.
	if (%x == %monster.xIndex	&&
		%y == %monster.yIndex)
	{// Don't move to the same position.
		return false;
	}
	
	// Check if the monster is attempting to move into some environment object!
	// Check barrels!
	%barrelIndex = hasBarrel(%x, %y);
	if (%barrelIndex != -1)
	{//echo("monster found barrel!");
		%monsterDirection = getDirectionBetweenPoints(%monster.xIndex, %monster.yIndex, %x, %y);
		setMonsterFacing(%monsterDirection, %monster);
		smashBarrel(%barrelIndex);
		inPlaceMonsterActionGrowAndShrink(%monster);
		return false;
	}
	
	// Check dispensers!
	%dispenserIndex = hasDispenser(%x, %y);
	if (%dispenserIndex != -1)
	{//echo("monster found barrel!");
		%monsterDirection = getDirectionBetweenPoints(%monster.xIndex, %monster.yIndex, %x, %y);
		setMonsterFacing(%monsterDirection, %monster);
		%dispenser = $dispenserList.getObject(%dispenserIndex);
		%dispenser.safeDelete();
		playRandomMetalWallCollapseSfx();
		inPlaceMonsterActionGrowAndShrink(%monster);
		return false;
	}
	
	// Check if the monster is moving into (attacking) a squad member tile!
	for (%i = 0; %i < $maxSquadSize; %i++)
	{
		// Check collision by tile index.
		if (isObject($playerSquad[%i])		&&
			%x == $playerSquad[%i].xIndex	&&
			%y == $playerSquad[%i].yIndex)
		{//echo("RAWRAWRAWRAWWRR! Attack player!");
			// MELEE ATTACK BRANCH.
			// Different melee attacks for different monsters!
			if (%monster.name $= "Mummy")
			{
				// Store name of last squad member attacked in case this attack kills the member!
				// NOTE: Should this just be passed to paralyzeSquadMember()?
				$lastHitSquadMemberName = $playerSquad[%i].name;
				// Mummy standard melee attack.
				%attackLanded = monsterRangeCharacter(%monster, %i, true);
				// Paralyze what it touches!
				if (%attackLanded)
				{
					paralyzeSquadMember(%i);
				}
				return false;
			}
			else if (%monster.name $= "Brute")
			{
				// Standard melee attack.
				%attackLanded = monsterMeleeCharacter(%monster, %i);
				return false;
			}
			else if (%monster.name $= "Acidjack")
			{
				// Standard melee attack.
				%attackLanded = monsterMeleeCharacter(%monster, %i);
				// Reduce armor rating of target!
				if (%attackLanded)
				{
					reduceArmorSquadMember(%i);
				}
				return false;
			}
			else if (%monster.name $= "Bucket")
			{
				// Standard melee attack.
				%attackLanded = monsterMeleeCharacter(%monster, %i);
				// Reduce action points of target!
				if (%attackLanded)
				{
					reduceActionPointSquadMember(%i);
				}
				return false;
			}
			else if (%monster.name $= "Brainien")
			{//echo("Brainien melee branch!");
				%attackLanded = monsterMeleeCharacter(%monster, %i);
				if (%attackLanded)
				{
					if (getRandom(1, 100) <= 80)
					{
						// Reduce armor rating of target!
						%squadMember = $playerSquad[%i];
						if (%squadMember)
						{
							%armorRating = 4;
							%squadMember.armor.armorRating -= %armorRating;
							if (%squadMember.armor.armorRating < 0)
							{
								%squadMember.armor.armorRating = 0;
							}
							(%squadMember.armor).armorType = setWord((%squadMember.armor).armorType, 2, %squadMember.armor.armorRating);
							scrollInfoDescriptionText(%squadMember, "Lost " @ %armorRating @ " Armor!");
							scrollInfoText(%squadMember, "Disintegration!");
						}
					}
					else
					{
						// Spawn enemy unit nearby!
						%spawnAttempts = 20;
						for (%i = 0; %i < %spawnAttempts; %i++)
						{
							// Randomly choose an adjacent tile to spawn on.
							%direction = getRandom(1, 4);
							%directionXIndex = %monster.xIndex;
							%directionYIndex = %monster.yIndex;
							switch (%direction)
							{
								case $northDirection:	%directionYIndex--;
								case $southDirection:	%directionYIndex++;
								case $eastDirection:	%directionXIndex++;
								case $westDirection:	%directionXIndex--;
							}
							// Look for a free tile adjacent to the hit Revenant.
							if (tileIsClearOfObstructions(%directionXIndex, %directionYIndex)	&&
								tileIsClearOfUnits(%directionXIndex, %directionYIndex))
							{//echo("Spawn tile clear!");
								%monsterType = getRandom(7, 14);	// Ripsaw through Torgon units.
								%spawn = spawnMonster(%monsterType, %directionXIndex, %directionYIndex);
								createUnitShadow(%spawn);
								$monsterList.add(%spawn);
								updateMonsterFloatingText();
							}
						}
					}
				}
				return false;
			}
			else
			{
				monsterRangeCharacter(%monster, %i, true);
				return false;
			}
		}
	}
	
	// Check the move index to see if another monster is there (don't move into other monsters).
	// Loop through the list of monsters and their positions.
	for (%i = 0; %i < $monsterList.getCount(); %i++)
	{
		// If the intended move position matches, the monster is being blocked by another monster!
		if (%x == ($monsterList.getObject(%i)).xIndex	&&
			%y == ($monsterList.getObject(%i)).yIndex)
		{//echo("Monsters don't move into other monsters!");
			return false;
		}
	}
	%monsterDirection = getDirectionBetweenPoints(%monster.xIndex, %monster.yIndex, %x, %y);
	setMonsterFacing(%monsterDirection, %monster);

	
	// HACK: function getUnitPosition() uses the parameter's x and y indices to calculate position!
	%monster.xIndex = %x;
	%monster.yIndex = %y;
	%targetPosition = getUnitPosition(%monster);
	// END HACK

	
	%moveSpeed = $monsterMoveSpeed;
	if (!%monster.visible)
	{
		%monster.position = %targetPosition;
	}
	else
	{
		schedule($footstepSfxDelay, 0, "playRandomFootstepSfx");
		if (monsterIsHeavy(%monster.name))
		{
			schedule($footstepSfxDelay, 0, "playRandomHeavyMetalSfx");
		}
		else
		{
			schedule($footstepSfxDelay, 0, "playRandomFootstepSfx");
		}
		%monsterMoveSpeed = getAIDelayMoveSpeed($monsterMoveSpeed);
		moveUnit(%monster, getWord(%targetPosition, 0), getWord(%targetPosition, 1) + %yPadding, %monsterMoveSpeed, true, true, true, 0.0, false);
	}
	$fogOfWar.updateTargetMonsterVisibility(%monster);
	characterOnTrap(%monster);
	handleHealthIndicators();
	return true;
}
function getAIDelayMoveSpeed (%monsterMoveSpeed)
{
	getScaledMonsterStepDelay();
	return %monsterMoveSpeed;
}

// Calculates monster attack on player.
// NOTE: This function is used for range attacks! Use function monsterMeleeCharacter() for melee!
function monsterRangeCharacter (%monster, %characterIndex, %inPlaceGroAndShrink)
{//echo("monsterRangeCharacter()");
	// Set monster facing.
	%direction = getDirectionBetweenUnits(%monster, $playerSquad[%characterIndex]);
	setMonsterFacing(%direction, %monster);
	if (%inPlaceGroAndShrink)
	{
		inPlaceMonsterActionGrowAndShrink(%monster);
	}
	%i = %characterIndex;
	%monsterAttackModifier = %monster.attackModifier - $playerSquad[%i].evasion;// - $playerSquad[%i].speed * 2;			// For testing purposes default monster attack modifier to 50.
	// Factor in passive evasion effects.
	if ($playerSquad[$activeCharacter].characterClass == 0)	// Leader.
	{
		%talents = $playerSquad[%currentCharacter].talents;
		if ($playerSquad[$activeCharacter].characterClass == 1	&&
			getWord(%talents, 7) == 1)								// Vigilance.
		{
			%monsterAttackModifier -= $vigilanceEvasion;
		}
	}
	if ($playerSquad[$activeCharacter].characterClass == 1)	// Scout.
	{
		%talents = $playerSquad[%currentCharacter].talents;
		if ($playerSquad[$activeCharacter].characterClass == 1	&&
			getWord(%talents, 3) == 1)								// Stealthy.
		{
			%monsterAttackModifier -= $stealthyEvasion;
		}
	}
	// Floor attack modifier at 0.
	if (%monsterAttackModifier < 0)
	{
		%monsterAttackModifier = 0;
	}
	%attackRoll = getRandom(1, 100);
	// If the attack roll is greater than the attack modifier, the monster's attack succeeds.
	if (%attackRoll <= %monsterAttackModifier)
	{
		// Dispatch by range damage type.
		%armorMitigation = 0;
		switch$ (%monster.rangeDamageType)
		{
			case "physical":	%armorMitigation = ($playerSquad[%i].armor).armorRating;
			case "fire":		%armorMitigation = ($playerSquad[%i].armor).fireResistance;
			case "frost":		%armorMitigation = ($playerSquad[%i].armor).frostResistance;
			case "acid":		%armorMitigation = ($playerSquad[%i].armor).acidResistance;
			case "explosive":	%armorMitigation = ($playerSquad[%i].armor).explosiveResistance;
		}
		// Increase damage mitigation if character is Scout and $squadDefensePlan is active!
		// NOTE: Should his be a separate function?
		if ($squadDefensePlan			&&
			$squadDefensePlanPoints > 0	&&
			%i == 1)
		{
			%armorMitigation += $squadDefensePlanPoints;
		}
		// Negate damage from monster's base range damage.
		%damage = %monster.baseRangeDamage - %armorMitigation;
		// FROM BEHIND.
		%hitType = "damage";
		%direction = getDirectionBetweenUnits(%monster, $playerSquad[%i]);
		// Deal additional damage if the character is hit from behind!
		if ($playerSquad[%i].facing == %direction)
		{	
			%damage *= 2;
			%hitType = "crit";
		}
		// Sanity check - bound the damage done to the player. Negative numbers do not "heal" the character!
		if (%damage < 0)
		{
			%damage = 0;
		}
		// Handle floating text.
		scrollFloatingText($playerSquad[%characterIndex], %damage, %hitType);
		// Make sure squad member health does not go below zero.
		// Save character name in case character dies before event log is updated!
		%characterName = $playerSquad[%characterIndex].name;
		if (%damage >= $playerSquad[%characterIndex].currentHealth)
		{
			$playerSquad[%characterIndex].currentHealth = 0;
			// Squad member death!
			executeSquadMemberDeath(%characterIndex, true, %monster.name);
		}
		else
		{
			$playerSquad[%characterIndex].currentHealth -= %damage;
		}
		// FLANKED.
		// Deal additional damage if the monster is hit from behind!
		%flankedHit = false;
		if (%direction == $northDirection	||
			%direction == $southDirection)
		{
			if ($playerSquad[%i].facing == $westDirection	||
				$playerSquad[%i].facing == $eastDirection)
			{
				%flankedHit = true;
			}
		}
		else
		{
			if ($playerSquad[%i].facing == $northDirection	||
				$playerSquad[%i].facing == $southDirection)
			{
				%flankedHit = true;
			}
		}
		if (%flankedHit)
		{
			// Update event log.
			updateEventText($squadColor @ $playerSquad[%characterIndex].name SPC $damageColor @ "is flanked" @ $defaultColor @ "!");
			
			// Turn the squad member toward the shot (opposite direction from fire direction).
			%squadMemberFacing = getReverseDirection(%direction);
			setSquadMemberFacing(%squadMemberFacing, $playerSquad[%characterIndex]);
		}
		// Save the player's name for after he is executed...
		%playerName = $playerSquad[%characterIndex].name;
		// Hit the character.
		%hitType = "normal";
		%damage = characterHit(%characterIndex, %damage, %monster, %monster.rangeDamageType, %hitType);
		// Mechanical monster hit player sound effects!
		%randomSfx = getRandom(1, 3);
		switch (%randomSfx)
		{
			case 1:	playAudio("Audio:bash_metal_01", EffectsAudioVolume.value);
			case 2:	playAudio("Audio:bash_metal_02", EffectsAudioVolume.value);
			case 3:	playAudio("Audio:bash_metal_03", EffectsAudioVolume.value);
		}
		initializeReadout();
		return true;
	}
	// Attack missed, update the event log.
	else
	{
		updateEventText($monsterColor @ %monster.name SPC $hostileActionColor @ "misses" SPC $squadColor @ $playerSquad[%characterIndex].name @ $defaultColor @ "!");
		return false;
	}
}

// Calculates monster bash on player.
// NOTE: This function is used for melee attacks! Use function monsterRangeCharacter() for range!
function monsterMeleeCharacter (%monster, %characterIndex)
{//echo("monsterMeleeCharacter()");
	%i = %characterIndex;
	// "Dummy" values for now:
	%monsterAttackModifier = %monster.attackModifier - $playerSquad[%i].evasion;
	%attackRoll = getRandom(1, 100);
	inPlaceMonsterActionGrowAndShrink(%monster);
	playAudio("Audio:monsterMelee", EffectsAudioVolume.value);
	// If the attack roll is greater than the attack modifier, the monster's attack succeeds.
	//if (%attackRoll <= %monsterAttackModifier)
	if (true)
	{
		// Save the player's name for after he is executed...
		%playerName = $playerSquad[%i].name;
		// Calculate tile just before targeted character.
		%xPos = getWord($playerSquad[%characterIndex].getPosition(), 0);
		%yPos = getWord($playerSquad[%characterIndex].getPosition(), 1);
		%mxPos = getWord(%monster.getPosition(), 0);
		%myPos = getWord(%monster.getPosition(), 1);
		%xDiff = 4;
		%yDiff = 4;
		// Moving right.
		if (%yPos == %myPos)
		{
			%yDiff = 0;
			if (%xPos > %mxPos)
			{
				%xDiff = -4;
			}
		}
		// Moving down.
		else if (%xPos == %mxPos)
		{
			%xDiff = 0;
			if (%yPos > %myPos)
			{
				%yDiff = -4;
			}
		}
		
		// Dispatch by range damage type.
		%armorMitigation = 0;
		switch$ (%monster.rangeDamageType)
		{
			case "physical":
				%armorMitigation = ($playerSquad[%i].armor).armorRating;
				
			case "fire":
				%armorMitigation = ($playerSquad[%i].armor).fireResistance;
				
			case "frost":
				%armorMitigation = ($playerSquad[%i].armor).frostResistance;
				
			case "acid":
				%armorMitigation = ($playerSquad[%i].armor).acidResistance;
				
			case "explosive":
				%armorMitigation = ($playerSquad[%i].armor).explosiveResistance;
		}
		
		// Increase damage mitigation if character is Scout and $squadDefensePlan is active!
		// NOTE: Should his be a separate function?
		if ($squadDefensePlan			&&
			$squadDefensePlanPoints > 0	&&
			$playerSquad[%i].characterClass == 1)
		{
			%armorMitigation += $squadDefensePlanPoints;
		}
		
		// Negate damage from monster's base melee damage.
		%damage = %monster.baseMeleeDamage - %armorMitigation;
		
		// Sanity check - bound the damage done to the player. Negative numbers do not "heal" the character!
		if (%damage < 0)
		{
			%damage = 0;
		}
		
		// FROM BEHIND.
		%hitType = "normal";
		%direction = getDirectionBetweenUnits(%monster, $playerSquad[%i]);
		// Deal additional damage if the character is hit from behind!
		if ($playerSquad[%i].facing == %direction)
		{
			%damage *= 2;
			%hitType = "crit";
		}
		
		// Make sure squad member health does not go below zero.
		// Save character name in case character dies before event log is updated!
		%characterName = $playerSquad[%characterIndex].name;
		if (%damage >= $playerSquad[%characterIndex].currentHealth)
		{
			$playerSquad[%characterIndex].currentHealth = 0;
			// Squad member death!
			executeSquadMemberDeath(%characterIndex, true, %monster.name);
		}
		else
		{
			$playerSquad[%characterIndex].currentHealth -= %damage;
		}
		
		// FLANKED.
		// Deal additional damage if the monster is hit from behind!
		%flankedHit = false;
		if (%direction == $northDirection	||
			%direction == $southDirection)
		{
			if ($playerSquad[%i].facing == $westDirection	||
				$playerSquad[%i].facing == $eastDirection)
			{
				%flankedHit = true;
			}
		}
		else
		{
			if ($playerSquad[%i].facing == $northDirection	||
				$playerSquad[%i].facing == $southDirection)
			{
				%flankedHit = true;
			}
		}
		if (%flankedHit)
		{
			// Update event log.
			updateEventText($squadColor @ $playerSquad[%i].name SPC $damageColor @ "is flanked" @ $defaultColor @ "!");
			
			// Turn the squad member toward the shot (opposite direction from fire direction).
			%squadMemberFacing = getReverseDirection(%direction);
			setSquadMemberFacing(%squadMemberFacing, $playerSquad[%i]);
		}
		%damage = characterHit(%i, %damage, %monster, %monster.meleeDamageType, %hitType);
		setMonsterFacing(%direction, %monster);
		handleBloodSplatter($playerSquad[%characterIndex].xIndex, $playerSquad[%characterIndex].yIndex);
		initializeReadout();
		updateEventText($monsterColor @ %monster.name SPC $hostileActionColor @ "melees" SPC $squadColor @ $playerSquad[%i].name SPC $defaultColor @ "for" SPC $damageColor @ %damage SPC "damage" @ $defaultColor @ "!");
		return true;
	}
	// Attack missed, update the event log.
	else
	{
		%direction = getDirectionBetweenUnits(%monster, $playerSquad[%i]);
		setMonsterFacing(%direction, %monster);
		updateEventText($monsterColor @ %monster.name SPC $hostileActionColor @ "misses" SPC $squadColor @ $playerSquad[%i].name @ $defaultColor @ "!");
		return false;
	}
}
// NOTE: This function is used for bash (knockback) melee attacks!
function monsterBashCharacter (%monster, %characterIndex)
{//echo("monsterBashCharacter()");
	%i = %characterIndex;
	%monsterAttackModifier = %monster.attackModifier - $playerSquad[%i].speed;			// For testing purposes default monster attack modifier to 50.
	%attackRoll = getRandom(1, 100);
	inPlaceMonsterActionGrowAndShrink(%monster);
	playAudio("Audio:monsterMelee", EffectsAudioVolume.value);
	if (%attackRoll <= %monsterAttackModifier)
	{
		%damage = %monster.baseMeleeDamage - ($playerSquad[%i].armor).armorRating;
		// Sanity check - bound the damage done to the player. Negative numbers do not "heal" the character!
		if (%damage < 0)
		{
			%damage = 0;
		}
		// Save the player's name for after he is executed...
		%playerName = $playerSquad[%i].name;
		// Calculate tile just before targeted character.
		%xPos = getWord($playerSquad[%characterIndex].getPosition(), 0);
		%yPos = getWord($playerSquad[%characterIndex].getPosition(), 1);
		%mxPos = getWord(%monster.getPosition(), 0);
		%myPos = getWord(%monster.getPosition(), 1);
		%xDiff = 4;
		%yDiff = 4;
		// Moving right.
		if (%yPos == %myPos)
		{
			%yDiff = 0;
			if (%xPos > %mxPos)
			{
				%xDiff = -4;
				//%newXIndex--;
			}
		}
		// Moving down.
		else if (%xPos == %mxPos)
		{
			%xDiff = 0;
			if (%yPos > %myPos)
			{
				%yDiff = -4;
				//%newYIndex--;
			}
		}
		// Dispatch by range damage type.
		%armorMitigation = 0;
		switch$ (%monster.rangeDamageType)
		{
			case "physical":
				%armorMitigation = ($playerSquad[%i].armor).armorRating;
				
			case "fire":
				%armorMitigation = ($playerSquad[%i].armor).fireResistance;
				
			case "frost":
				%armorMitigation = ($playerSquad[%i].armor).frostResistance;
				
			case "acid":
				%armorMitigation = ($playerSquad[%i].armor).acidResistance;
				
			case "explosive":
				%armorMitigation = ($playerSquad[%i].armor).explosiveResistance;
		}
		// Increase damage mitigation if character is Scout and $squadDefensePlan is active!
		// NOTE: Should his be a separate function?
		if ($squadDefensePlan			&&
			$squadDefensePlanPoints > 0	&&
			$playerSquad[%i].characterClass == 1)
		{
			%armorMitigation += $squadDefensePlanPoints;
		}
		%damage = %monster.baseMeleeDamage - %armorMitigation;
		%hitType = "normal";
		%damage = characterHit(%i, %damage, %monster, %monster.meleeDamageType, %hitType);
		// Make sure squad member health does not go below zero.
		// Save character name in case character dies before event log is updated!
		%characterName = $playerSquad[%characterIndex].name;
		if (%damage >= $playerSquad[%characterIndex].currentHealth)
		{
			$playerSquad[%characterIndex].currentHealth = 0;
			// Squad member death!
			executeSquadMemberDeath(%characterIndex, true, %monster.name);
		}
		else
		{
			$playerSquad[%characterIndex].currentHealth -= %damage;
		}
		handleBloodSplatter($playerSquad[%characterIndex].xIndex, $playerSquad[%characterIndex].yIndex);
		playAudio("Audio:trollCharge", EffectsAudioVolume.value);
		// Bash knockback effect.
		// Calculate indices to move to.
		%indices = getIndicesFromTilePosition(%xPos - %xDiff, %yPos - %yDiff);
		%xIndex = getWord(%indices, 0);
		%yIndex = getWord(%indices, 1);
		// Bounds check indices.
		if (%xIndex >= 0				&&
			%xIndex < $mazeDimension	&&
			%yIndex >= 0				&&
			%yIndex < $mazeDimension)
		{
			// Make sure the tile is clear.
			if (tileIsClear(%xIndex, %yIndex))
			{
				// Handle knockback of character.
				$playerSquad[%characterIndex].moveTo(%xPos - %xDiff, %yPos - %yDiff, 60.0, true, false);
				$playerSquad[%characterIndex].xIndex = %xIndex;
				$playerSquad[%characterIndex].yIndex = %yIndex;
			}
		}
		initializeReadout();
		updateEventText($monsterColor @ %monster.name SPC $hostileActionColor @ "bashes" SPC $squadColor @ $playerSquad[%i].name SPC $defaultColor @ "for" SPC $damageColor @ %damage SPC "damage" @ $defaultColor @ "!");
	}
	// Attack missed, update the event log.
	else
	{
		updateEventText($monsterColor @ %monster.name SPC $hostileActionColor @ "misses" SPC $squadColor @ $playerSquad[%i].name @ $defaultColor @ "!");
	}
}
function canMonsterMoveToMapPosition (%monster, %x, %y)
{//echo("canMonsterMoveToMapPosition();
	if (%x < 0 ||
		%y < 0)
	{//echo("Invalid index!");
		return 0;
	}
	if ($monsterList.getCount() > 0)
	{//echo("MONSTER LIST NOT EMPTY!");
		// Check if a monster is in the room position.
		for (%i = 0; %i < $monsterList.getCount(); %i++)
		{
			// Compose the tile's position coordinates.
			%monsterXIndex = ($monsterList.getObject(%i)).xIndex;
			%monsterYIndex = ($monsterList.getObject(%i)).yIndex;
			// If the intended move position matches, the monster is being blocked by another monster!
			if (%x == %monsterXIndex	&&
				%y == %monsterYIndex)
			{//echo("Monsters don't move into other monsters!");
				// Return false - a monster is blocking the way!
				return false;
			}
		}
	}
	// Index the room position status to determine if we can move to that position.
	if (!isObject($mazeRoom[%x, %y]))
	{
		%roomPositionStatus = 1;
	}
	else
	{
		%roomPositionStatus = $mazeRoom[%x, %y].status;
	}
	//echo("%roomPositionStatus =" SPC %roomPositionStatus);
	// Branch based on room position status.
	switch (%roomPositionStatus)
	{
		case -4:	// Door is opened.
			return 1;	// Can move onto open door tile.
			
		case -3:	// Door is closed.
			// Open the door.
			$mazeRoom[%x, %y].status = -4;		// Flag as open door.
			//%image = getImageFromOverworldLocation();
			switch ($mazeRoom[%x, %y].positionFlag)
			{
				case 0:	%frame = 23;
				case 1:	%frame = 25;
			}
			$mazeRoom[%x, %y].frame = %frame;
			playAudio("Audio:sliding_door_open_01", EffectsAudioVolume.value);
			openDoorTrigger(%x, %y, %monster);
			inPlaceMonsterActionGrowAndShrink(%monster);
			$fogOfWar.showFogOfWar();
			$fogOfWar.updateMonsterVisibility();
			%monster.currentActionPoints--;
			if (%monster.currentActionPoints < 0)
			{
				%monster.currentActionPoints = 0;
			}
			return 1;
			
		case -2:
			return 0;
		case -1:
			return 0;
		case 0:	//echo("case 0");
			// Randomly determine if the monster destroys a wall to reach the player!
			%destroyWall = getRandom(0, 100);
			if (%destroyWall <= 75)
			{
				// Can't walk there, but destroy the wall!
				// Character bash knocks down walls!
				destroyTerrainTile(%x, %y);
			}
			return 0;
			
		case 1:	//echo("case 1");
			return 1;
	}
}

// Make the monster random walk.
function monsterRandomWalk (%monster, %startX, %startY, %endX, %endY)
{//echo("monsterRandomWalk()");
	%randomDirection = getRandom(0, 3);
	%walkPosition = -1 SPC -1;			// Invalid position.
	%randomWalkAttempts = 0;
	
	while (%walkPosition == "-1 -1"	&&
		   %randomWalkAttempts < 8)
	{
		// Dispatch randomly.
		switch (%randomDirection)
		{
			case 0:	// Up.
				// Bound check.
				if (%startY - 1 > 0)
				{
					if (canMonsterMoveToMapPosition(%monster, %startX, %startY - 1))
					{
						%walkPosition = %startX SPC %startY - 1;
					}
				}
			
			case 1:	// Down.
				// Bound check.
				if (%startY + 1 < $mazeDimension)
				{
					if (canMonsterMoveToMapPosition(%monster, %startX, %startY + 1))
					{
						%walkPosition = %startX SPC %startY + 1;
					}
				}
			
			case 2:	// Left.
				// Bound check.
				if (%startX - 1 > 0)
				{
					if (canMonsterMoveToMapPosition(%monster, %startX - 1, %startY))
					{
						%walkPosition = %startX - 1 SPC %startY;
					}
				}
			
			case 3:	// Right.
				// Bound check.
				if (%startX + 1 < $mazeDimension)
				{
					if (canMonsterMoveToMapPosition(%monster, %startX + 1, %startY))
					{
						%walkPosition = %startX + 1 SPC %startY;
					}
				}
		}
		%randomWalkAttempts++;
	}
	
	
	// Return the position if walkable, otherwise return %startX SPC %startY;
	if (%walkPosition == "-1 -1")
	{
		return %startX SPC %startY;
	}
	else
	{
		return %walkPosition;
	}
}

// Finds path from startPosition to endPosition.
function findPath (%monster, /*%startX, %startY,*/ %endX, %endY)
{//echo("findPath()");
	%startX = %monster.xIndex;
	%startY = %monster.yIndex;
	// Make sure the start and end points are not the same!
	if (%startX == %endX	&&
		%startY == %endY)
	{//echo("Start is the same as end!");
		// Return the start position.
		return %startX SPC %startY;
	}

	// Determine directions from start to end.
	%xDiff = %endX - %startX;
	%yDiff = %endY - %startY;
	
	// Branch based on diffs.
	//
	// Vertical movement only.
	if (%xDiff == 0)
	{
		// Move down.
		if (%yDiff > 0)
		{
			if (canMonsterMoveToMapPosition(%monster, %startX, %startY + 1))
			{
				return %startX SPC (%startY + 1);
			}
		}
		// Move up.
		else
		{
			if (canMonsterMoveToMapPosition(%monster, %startX, %startY - 1))
			{
				return %startX SPC (%startY - 1);
			}
		}
		
		// Don't move otherwise.
		return monsterRandomWalk(%monster, %startX, %startY, %endX, %endY);
	}
	// Horizontal movement only.
	else if (%yDiff == 0)
	{
		// Move right.
		if (%xDiff > 0)
		{
			if (canMonsterMoveToMapPosition(%monster, %startX + 1, %startY))
			{
				return (%startX + 1) SPC %startY;
			}
		}
		// Move left.
		else
		{
			if (canMonsterMoveToMapPosition(%monster, %startX - 1, %startY))
			{
				return (%startX - 1) SPC %startY;
			}
		}
		
		// Don't move otherwise.
		return monsterRandomWalk(%monster, %startX, %startY, %endX, %endY);
	}
	// Both horizontal and vertical movement
	else
	{
		// Randomize checking horizontal or vertical movement first!
		%upOrDown = getRandom(0, 1);
		
		// Horizontal first.
		if (%upOrDown)
		{
			// Try to move in the direction of the target.
			if (%xDiff > 0)
			{
				if (canMonsterMoveToMapPosition(%monster, %startX + 1, %startY))
				{
					return (%startX + 1) SPC %startY;
				}
			}
			else if (%xDiff < 0)
			{
				if (canMonsterMoveToMapPosition(%monster, %startX - 1, %startY))
				{
					return (%startX - 1) SPC %startY;
				}
			}
			
			if (%yDiff > 0)
			{
				if (canMonsterMoveToMapPosition(%monster, %startX, %startY + 1))
				{
					return %startX SPC (%startY + 1);
				}
			}
			else if (%yDiff < 0)
			{
				if (canMonsterMoveToMapPosition(%monster, %startX, %startY - 1))
				{
					return %startX SPC (%startY - 1);
				}
			}
		}
		// Vertical first.
		else
		{
			// Try to move in the direction of the target.
			if (%yDiff > 0)
			{
				if (canMonsterMoveToMapPosition(%monster, %startX, %startY + 1))
				{
					return %startX SPC (%startY + 1);
				}
			}
			else if (%yDiff < 0)
			{
				if (canMonsterMoveToMapPosition(%monster, %startX, %startY - 1))
				{
					return %startX SPC (%startY - 1);
				}
			}
			if (%xDiff > 0)
			{
				if (canMonsterMoveToMapPosition(%monster, %startX + 1, %startY))
				{
					return (%startX + 1) SPC %startY;
				}
			}
			else if (%xDiff < 0)
			{
				if (canMonsterMoveToMapPosition(%monster, %startX - 1, %startY))
				{
					return (%startX - 1) SPC %startY;
				}
			}
		}
		
		// Randomize a direction.
		%randomDirection = getRandom(1, 4);
		
		switch (%randomDirection)
		{
			case 1:	// Up.
					if (canMonsterMoveToMapPosition(%monster, %startX, %startY + 1))
					{
						return %startX SPC (%startY + 1);
					}
			
			case 2: // Down.
					if (canMonsterMoveToMapPosition(%monster, %startX, %startY + 1))
					{
						return %startX SPC (%startY + 1);
					}
			
			case 3: // Left.
					if (canMonsterMoveToMapPosition(%monster, %startX - 1, %startY))
					{
						return (%startX - 1) SPC %startY;
					}
			
			case 4: // Right.
					if (canMonsterMoveToMapPosition(%monster, %startX + 1, %startY))
					{
						return (%startX + 1) SPC %startY;
					}
		}
	}
	return monsterRandomWalk(%monster, %startX, %startY, %endX, %endY);
}
function handleMonsterObjectDeath (%characterIndex, %monster)
{//echo("handleMonsterObjectDeath()");
	enemyDownChatter(%characterIndex, %monster.isHuman, %monster.isRobot, %monster.isAlien);
	if (%monster.isRobot)
	{
		createGearExplosionEffect(%monster.xIndex, %monster.yIndex);
	}
	// Randomize an item - no weights yet!
	%itemType = -1;
	%roll = getRandom(1, 100);		// Currently only 0, 5, 7, and 10 are implemented!
	if (%roll <= $monsterKillItemSpawnChance)
	{
		%itemType = 10;	// Grenade.
	}
	if (%itemType != -1)
	{
		%item = createRandomItem(%itemType, %monster.getPosition(), %monster.xIndex, %monster.yIndex);
		$mapItems.add(%item);
	}
	// Handle special events on monster death here!
	if (%monster.name $= "Revenant")
	{
		updateEventText($monsterColor @ %monster.name SPC $hostileActionColor @ "explodes in death" @ $defaultColor @ "!");
		handleBloatDeathExplosion(%monster.xIndex, %monster.yIndex);
		grenadeEmptyAoePathDamage(%monster.xIndex, %monster.yIndex, %monster.facing);		// BAD! Should really be its own function call!
	}
	if (isObject(%monster.liftedTileMarker))
	{
		(%monster.liftedTileMarker).safeDelete();
	}
	%monster.safeDelete();
	updateMissionObjectives();
	schedule(100, 0, "updateMissionObjectives");
	autoTargetEnemyUnit($autoTargetDelay);
}
function handleSpecialMonsterDeath (%monster)
{//echo("handleSpecialMonsterDeath()");
	if (%monster.name $= "Hulk")
	{
		%item = createRandomItem(21, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// Hulk Suit.
		$mapItems.add(%item);
		powerElevatorOn();
		updateElevatorArrowAnimation();
		fadeOut(0);
		schedule(1000, 0, "stopBGMOgg");
		schedule(1000, 0, "playLevelMusic");
		schedule(1500, 0, "fadeIn", 0);
	}
	if (%monster.name $= "Revenant")
	{
		%item = createRandomItem(23, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// Laser Mod.
		$mapItems.add(%item);
		powerElevatorOn();
		updateElevatorArrowAnimation();
		fadeOut(0);
		schedule(1000, 0, "stopBGMOgg");
		schedule(1000, 0, "playLevelMusic");
		schedule(1500, 0, "fadeIn", 0);
	}
	if (%monster.name $= "Brainien")
	{
		%item1 = createRandomItem(17, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// Laser Shot.
		$mapItems.add(%item1);
		%item2 = createRandomItem(getRandom(23, 24), getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);	// Laser/Plasma Mod.
		$mapItems.add(%item2);
		%item3 = createRandomItem(0, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// Medkit.
		$mapItems.add(%item3);
		%item4 = createRandomItem(0, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// Medkit.
		$mapItems.add(%item4);
		if (getRandom(1, 100) <= 50)
		{
			%item5 = createRandomItem(0, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);				// Medkit.
			$mapItems.add(%item5);
		}
		if (getRandom(1, 100) <= 50)
		{
			%item6 = createRandomItem(0, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);				// Medkit.
			$mapItems.add(%item6);
		}
		%item7 = createRandomItem(25, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// MedStim.
		$mapItems.add(%item7);
		if (getRandom(1, 100) <= 50)
		{
			%item8 = createRandomItem(25, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);				// MedStim.
			$mapItems.add(%item8);
		}
		powerElevatorOn();
		updateElevatorArrowAnimation();
		fadeOut(0);
		schedule(1000, 0, "stopBGMOgg");
		schedule(1000, 0, "playLevelMusic");
		schedule(1500, 0, "fadeIn", 0);
	}
	if (%monster.name $= "Gunship")
	{
		%item1 = createRandomItem(17, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// Laser Shot.
		$mapItems.add(%item1);
		%item2 = createRandomItem(getRandom(23, 24), getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);	// Laser/Plasma Mod.
		$mapItems.add(%item2);
		%item3 = createRandomItem(getRandom(23, 24), getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);	// Laser/Plasma Mod.
		$mapItems.add(%item3);
		%item4 = createRandomItem(getRandom(23, 24), getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);	// Laser/Plasma Mod.
		$mapItems.add(%item4);
		%item5 = createRandomItem(getRandom(23, 24), getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);	// Laser/Plasma Mod.
		$mapItems.add(%item5);
		%item6 = createRandomItem(25, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// Medstim.
		$mapItems.add(%item6);
		%item7 = createRandomItem(21, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// Hulk Suit.
		$mapItems.add(%item7);
		%item8 = createRandomItem(1, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// Ammo Crate.
		$mapItems.add(%item8);
		if (getRandom(1, 100) <= 50)
		{
			%item9 = createRandomItem(21, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);				// Hulk Suit.
			$mapItems.add(%item9);
		}
		if (getRandom(1, 100) <= 50)
		{
			%item10 = createRandomItem(25, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);				// MedStim.
			$mapItems.add(%item10);
		}
		if (getRandom(1, 100) <= 50)
		{
			%item11 = createRandomItem(16, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);				// Armor Kit.
			$mapItems.add(%item11);
		}
		powerElevatorOn();
		updateElevatorArrowAnimation();
		fadeOut(0);
		schedule(1000, 0, "stopBGMOgg");
		schedule(1000, 0, "playLevelMusic");
		schedule(1500, 0, "fadeIn", 0);
		// Handle death animation.
		return createGunshipDeathAnimation(%monster.xIndex, %monster.yIndex);
	}
	if (%monster.name $= "Matrix")
	{
		%item1 = createRandomItem(17, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// Laser Shot.
		$mapItems.add(%item1);
		%item2 = createRandomItem(getRandom(23, 24), getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);	// Laser/Plasma Mod.
		$mapItems.add(%item2);
		%item3 = createRandomItem(getRandom(23, 24), getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);	// Laser/Plasma Mod.
		$mapItems.add(%item3);
		%item4 = createRandomItem(getRandom(23, 24), getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);	// Laser/Plasma Mod.
		$mapItems.add(%item4);
		%item5 = createRandomItem(getRandom(23, 24), getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);	// Laser/Plasma Mod.
		$mapItems.add(%item5);
		%item6 = createRandomItem(25, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// Medstim.
		$mapItems.add(%item6);
		%item7 = createRandomItem(21, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// Hulk Suit.
		$mapItems.add(%item7);
		%item8 = createRandomItem(1, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// Ammo Crate.
		$mapItems.add(%item8);
		if (getRandom(1, 100) <= 50)
		{
			%item9 = createRandomItem(21, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);				// Hulk Suit.
			$mapItems.add(%item9);
		}
		if (getRandom(1, 100) <= 50)
		{
			%item10 = createRandomItem(25, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);				// MedStim.
			$mapItems.add(%item10);
		}
		if (getRandom(1, 100) <= 50)
		{
			%item11 = createRandomItem(16, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);				// Armor Kit.
			$mapItems.add(%item11);
		}
		powerElevatorOn();
		updateElevatorArrowAnimation();
		fadeOut(0);
		schedule(1000, 0, "stopBGMOgg");
		schedule(1000, 0, "playLevelMusic");
		schedule(1500, 0, "fadeIn", 0);
		// Handle death animation.
		return createMatrixDeathAnimation(%monster.xIndex, %monster.yIndex);
	}
	if (%monster.name $= "Patriarch")
	{
		%item1 = createRandomItem(17, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// Laser Shot.
		$mapItems.add(%item1);
		%item2 = createRandomItem(getRandom(26, 31), getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);	// Statbooster to Hyper Action.
		$mapItems.add(%item2);
		%item3 = createRandomItem(getRandom(26, 31), getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);	// Statbooster to Hyper Action.
		$mapItems.add(%item3);
		%item4 = createRandomItem(getRandom(26, 31), getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);	// Statbooster to Hyper Action.
		$mapItems.add(%item4);
		%item5 = createRandomItem(getRandom(26, 31), getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);	// Statbooster to Hyper Action.
		$mapItems.add(%item5);
		%item6 = createRandomItem(25, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// Medstim.
		$mapItems.add(%item6);
		%item7 = createRandomItem(21, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// Hulk Suit.
		$mapItems.add(%item7);
		%item8 = createRandomItem(1, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);					// Ammo Crate.
		$mapItems.add(%item8);
		if (getRandom(1, 100) <= 50)
		{
			%item9 = createRandomItem(21, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);				// Hulk Suit.
			$mapItems.add(%item9);
		}
		if (getRandom(1, 100) <= 50)
		{
			%item10 = createRandomItem(25, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);				// MedStim.
			$mapItems.add(%item10);
		}
		if (getRandom(1, 100) <= 50)
		{
			%item11 = createRandomItem(16, getTilePositionFromIndices(%monster.xIndex, %monster.yIndex), %monster.xIndex, %monster.yIndex);				// Armor Kit.
			$mapItems.add(%item11);
		}
		powerElevatorOn();
		updateElevatorArrowAnimation();
		fadeOut(0);
		schedule(1000, 0, "stopBGMOgg");
		schedule(1000, 0, "playLevelMusic");
		schedule(1500, 0, "fadeIn", 0);
	}
	return null;
}
function handleMonsterDeath (%characterIndex, %monsterIndex)
{//echo("handleMonsterDeath()");
	if (%monsterIndex < 0)
	{
		return;
	}
	%monster = $monsterList.getObject(%monsterIndex);
	if (!isObject(%monster))
	{
		return;
	}
	if (isObject(%monster.liftedTileMarker))
	{
		(%monster.liftedTileMarker).safeDelete();
	}
	if (%monster.isHuman)
	{
		spawnCorpse(%monster.xIndex, %monster.yIndex, 0);
	}
	else if (%monster.isRobot)
	{
		spawnCorpse(%monster.xIndex, %monster.yIndex, 1);
	}
	else if (%monster.isAlien)
	{
		spawnCorpse(%monster.xIndex, %monster.yIndex, 2);
	}
	handleSpecialMonsterDeath(%monster);
	enemyDownChatter(%characterIndex, %monster.isHuman, %monster.isRobot, %monster.isAlien);
	if (%monster.name $= "Sapper"	&&
		!%monster.deathFlag)
	{
		%monster.deathFlag = true;
		handleGrenadierExplosion(%monster.xIndex, %monster.yIndex);
		grenadeEmptyAoePathDamage(%monster.xIndex, %monster.yIndex, $northDirection);	// Direction is irrelevant at the moment!
	}
	if (%monster.isRobot)
	{
		createGearExplosionEffect(%monster.xIndex, %monster.yIndex);
	}
	// Randomize an item - no weights yet!
	%itemType = -1;
	%roll = getRandom(1, 100);		// Currently only 0, 5, 7, and 10 are implemented!
	// Check roll.
	if (%roll <= $monsterKillItemSpawnChance)
	{
		%itemTypeRoll = getRandom(0, 2);
		switch (%itemTypeRoll)
		{
			case 0:	%itemType = 7;	// Canteen.
			case 1:	%itemType = 10;	// Grenade.
			case 2:	%itemType = 1;	// Ammo Crate.
		}
	}
	if (%itemType != -1)
	{
		%item = createRandomItem(%itemType, %monster.getPosition(), %monster.xIndex, %monster.yIndex);
		$mapItems.add(%item);
	}
	// Handle special events on monster death here!
	if (%monster.name $= "Revenant")
	{
		updateEventText($monsterColor @ %monster.name SPC $hostileActionColor @ "explodes in death" @ $defaultColor @ "!");
		handleBloatDeathExplosion(%monster.xIndex, %monster.yIndex);
		grenadeEmptyAoePathDamage(%monster.xIndex, %monster.yIndex, %monster.facing);		// BAD! Should really be its own function call!
	}
	
	// Delete HP/AP floating text.
	%monster.floatingHPText.safeDelete();
	%monster.floatingAPText.safeDelete();
	
	%monster.safeDelete();
	updateMissionObjectives();
	schedule(100, 0, "updateMissionObjectives");
	schedule($autoTargetDelay, 0, "setAutoTargetPending");
}
function monsterAttackedReaction (%monster)
{//echo("monsterAttackedReaction()");
	monsterStatusToAttack(%monster);
	if (%monster.armorRating > 0)
	{
		%monster.armorRating--;
	}
	if (%monster.isRobot)
	{
		if (getRandom(1, 100) <= 50)
		{
			schedule(100, 0, "createSteamCloudAnimation", %monster);
		}
	}
}
function monsterStatusToAttack (%monster)
{//echo("monsterStatusToAttack()");
	if (%monster.name $= "Thopter")
	{
		if (%monster.currentActionPoints > 0)
		{
			%monster.currentActionPoints--;
			updateMonsterFloatingText();
		}
	}
}
function wakeMonster (%monster)
{//echo("wakeMonster()");
	if (!isObject(%monster)	||
		!isObject((%monster.sleepAnimation)))
	{
		return;
	}
	(%monster.sleepAnimation).setLifetime(0.0);
	(%monster.sleepAnimation).safeDelete();
	%monster.sleepAnimation = null;
	%range = 10.0;		// Rather arbitrary. Randomize? Define by monster type?
	schedule(50, 0, "wakeNearbyMonsters", %monster.getPosition(), %range);
}

// Wakes monsters near specified tile indices.
function wakeNearbyMonsters (%position, %range)
{//echo("wakeNearbyMonsters()");
return;		// No sleeping monsters atm!
	// Search for monsters within a certain range of the specified tile.
	for (%i = 0; %i < $monsterList.getCount(); %i++)
	{
		%monster = $monsterList.getObject(%i);
		%distance = VectorDist(%position, %monster.getPosition());
		if (%distance <= %range)
		{
			if (isObject(%monster.sleepAnimation))
			{
				monsterAttackedReaction(%monster);
				updateEventText($monsterColor @ %monster.name @ $whiteColor @ " wakes up" @ $defaultColor @ "!");
			}
		}
	}
}
function isMonster (%unit)
{//echo("isMonster()");
	for (%i = 0; %i < $monsterList.getCount(); %i++)
	{
		%monster = $monsterList.getObject(%i);
		if (%monster == %unit)
		{
			return true;
		}
	}
	return false;
}
function monsterIsHeavy (%monsterName)
{//echo("monsterIsHeavy()");
	switch$ (%monsterName)
	{
		case "Ripsaw":		return true;
		case "Amputator":	return true;
	}
	return false;
}
function directionsFlank (%srcFacing, %dstFacing)
{//echo("directionsFlank()");
	if (%srcFacing == $northDirection	||
		%srcFacing == $southDirection)
	{
		if (%dstFacing == $westDirection	||
			%dstFacing == $eastDirection)
		{
			return true;
		}
	}
	else
	{
		if (%dstFacing == $northDirection	||
			%dstFacing == $southDirection)
		{
			return true;
		}
	}
	return false;
}