#include "zcommon.acs"
#library "strnghld"

/* ******************************* **
** Public defines, for maps to use **
** ******************************* */

// Player 1 tid is PLAYER_TID_START, Player 2 tid is PLAYER_TID_START+1, etc.
#libdefine	PLAYER_TID_START	5000

// Radio stuff
#libdefine	RADIO_RANDOM	0
#libdefine	RADIO_PETERSEN	1
#libdefine	RADIO_PRINCE	2
#libdefine	RADIO_WILBUR	3
#libdefine	RADIO_CLOUD	4
#libdefine	RADIO_TAYLOR	5

// Ammo Defines (used with WaveAmmo function)
#libdefine	BULLETS		1
#libdefine	SHELLS		2
#libdefine	ROCKETS		3
#libdefine	CELLS		4
#libdefine	GAS		5
#libdefine	MINES		6

// Script numbers
#libdefine	S_BF_THRUST	254
#libdefine	S_G_THRUST	253

// Game modes
#libdefine	S_DEADLINE	901
#libdefine	S_CORE_GAME	902
#libdefine	S_MILESTONE	903
#libdefine	S_LIMIT_GAME	904
#libdefine	S_OVERMIND	905
#libdefine	S_GOAL_GAME	906

#libdefine	S_GETBONUS	980
#libdefine	S_MINUTECHECKS	981
#libdefine	S_DAMAGECHECKS	982
#libdefine 	S_DETECTENDOFLVL 983

#libdefine	S_MILE_MARKER	250
#libdefine	S_LIMIT_MARKER	249

#libdefine	S_MISSION_COMPLETE	910
#libdefine	S_M_ACTIVATE_FAIL	255
#libdefine	S_MISSION_FAIL	912
#libdefine	S_JAIL_GIVEUP	252
#libdefine	S_JAIL_LEAVE	898
#libdefine	S_JAIL_OPEN	897
#libdefine	S_BRIEF_END	251
#libdefine	S_BRIEF_MAIN	925

#libdefine	S_STR52CODEGET	927

#libdefine	S_GOAL_DESTROYED	926

#libdefine	GOAL_RED	1
#libdefine	GOAL_ORANGE	2
#libdefine	GOAL_BLUE	3
#libdefine	GOAL_GREEN	4
#libdefine	GOAL_WHITE	5

#libdefine	WATCH_UNKN	0
#libdefine	WATCH_CORE	1
#libdefine	WATCH_BOSS	2
#libdefine	WATCH_BOSSTARGET	3
#libdefine	WATCH_LIMIT	4
#libdefine	WATCH_GOALS	5

#libdefine B_TIMEHALF 0
#libdefine B_TIMETHIRD 1
#libdefine B_TIMEQUARTER 2
#libdefine B_KILLS100 3
#libdefine B_KILLS200 4
#libdefine B_KILLS300 5
#libdefine B_NOMILESTONES 6
#libdefine B_NOGOALS 7
#libdefine B_CORE90 8
#libdefine B_CORE95 9
#libdefine B_CORE100 10
#libdefine B_LIMITHALF 11
#libdefine B_LIMITQUARTER 12
#libdefine B_LIMITNONE 13
#libdefine B_OVERMINDKILLER 14
#libdefine B_NOCORES 15
#libdefine B_INDIRECTKILLS1 16
#libdefine B_INDIRECTKILLS2 17
#libdefine B_INDIRECTKILLS3 18
#libdefine B_CASH1000 19
#libdefine B_CASH2000 20
#libdefine B_CASH3000 21
#libdefine B_DAMAGE100 22
#libdefine B_DAMAGE50 23
#libdefine B_NODAMAGE 24
#libdefine B_NODEATHS 25

#libdefine GAME_DEADLINE 0
#libdefine GAME_CORE 1
#libdefine GAME_MILESTONE 2
#libdefine GAME_LIMIT 3
#libdefine GAME_OVERMIND 4
#libdefine GAME_GOAL 5

// Misc
#libdefine	INT_MAX		0x7FFFFFFF
str str_null	= "NULL";

/* *************************************** **
** Public Variables that can be set by map **
** *************************************** */
int F_CallNextWave; //Cheat variable to force next wave.
int Waves;
int GameMode = GAME_DEADLINE;
int HateEpicenter;
int NumberOfTargets;
int BossMonster;
int BossTarget;
int BossHealth = 20000;
int EventScript = 0;
int GoalScript = 0;
int MileScript = 0;
int CurrentMile = 0;
int ExitPos = 0;
int PrisonSpot;
int PrisonExit;
int PrisonCamStart;
int PrisonTeleporterLineId;
int PrisonTeleporterThrustAngle;
int PrisonExitThrustAngle = -1;
int TeleporterOnLight;
int TeleporterOffLight;
int BreakTime = -1;	// Deadline default is 6*35. Can be overridden by map.
int LimitMax = 100;	// Can be overridden by map
int tics_between_monster_spawns = 5;	// Can be overridden, but highly not recommended to shorten this either.
bool GameActive = TRUE;	// When this is false, the player can't lose lives
bool GivePistol = TRUE;	// Set to false for Intermap
bool BriefSkippable = TRUE;	// If this is false, "Press <use> to skip" won't be shown
bool BriefTalking = FALSE;	// When true, talking animation in briefing happens
bool BriefActive = FALSE;	// Should be TRUE when players should be frozen (PROP_TOTALLY_FROZEN)
bool BriefNoGun = FALSE;	// The player won't be given a gun on first spawn while this is true
str BriefBackground = "TNTA0";
int BriefScript = 0;	// Set to script number of briefing script. See STR01 for example.
int DamageTaken[32];

#libdefine RADIONAME_SIZE	16
int RadioName[RADIONAME_SIZE];

#define MAX_LEVEL_IDS	255
int MapCoinReward[MAX_LEVEL_IDS];
int MapParTime[MAX_LEVEL_IDS];

// 0	= level not finished
// -1	= level finished, but par-time not met
// Positive number	= Par time met, equals high score (in tics)
global int 1:MapScores[];

global int 2:str52code[];	// Has 5 entries, one for each digit of the code

global bool 2:str52codeinit;	// Once str52code has been set, this is set to true

global bool 51:STR50Open;
global bool 53:STR52Open;
global bool 55:STR54Open;
global bool 56:STR56Open;

// These next few shouldn't be changed - pretend they're defines :P
// (They're not set as defines so that way maps don't need to recompile if they're changed.)
int BriefX		= 370.0;
int BriefY		= 365.0;
int BriefHID		= 1004;
int BriefExtraHID	= 1003;

/* **************************************************** **
** Internal defines and variables, not referenceable or **
** should not be referenced from outside this file.     **
** **************************************************** */

#define	TEMP_TID_START	4600
#define	TEMP_TID_END	4999

#define S_ENTER		669
#define S_LIVE_WATCH	668
#define S_RESPAWN	670
#define	S_DEATH		671
#define S_DISCONNECT	923
#define	S_CALLWAVE	920
#define S_MESSAGE	921
#define S_UPDATE_MONSTER_COUNT	922
#define S_BRIEF_ANIMATE	924
#define	S_HEALTH_WATCH	911
#define	S_COUNT_WATCH	914
#define	S_LIMIT_WATCH	928
#define	S_OVERMIND_COMPLETE	918
#define	S_HUD_COINS	919

#define S_HUD		915
#define	S_TIMER		916
#define	S_SYNC		917
#define	S_VAR_CHECK	970

#define	S_DEBUG		930

// Multiplier function defines
#define M_MEDIC		1
#define M_AMMO		2
#define M_POWERUP	3
#define M_ENEMIES	4
#define M_BOSSHEALTH	5

#define	SYNC_WAVE	1
#define	SYNC_HITIME	2
#define	SYNC_LOTIME	3
#define	SYNC_PARTIME	4

#define	BriefTalkBoxX	290.0
#define	BriefTalkBoxY	360.0

#define	RadioMugHID	4
#define	RadioNameHID	5
#define	RadioTextHID	6
#define	RadioBoxHID	7

#define	BriefNameHID	1005
#define	BriefTalkBoxHID	1008
#define	BriefBGHID	1010
#define	BriefSkipHID	1002

#define	EndMessageHID	1015
#define FadeOutHID	1016

#define	CoreGraphicHID	1020
#define	CorePercentHID	1021
#define	CoreLabelHID	1022

#define	CoreHIDOffset	3
// HIDs 1023-1029 reserved

#define	HUDLevelHID	1030
#define	HUDMonstersHID	1031
#define	HUDWaveHID	1032
#define	HUDTimeHID	1033
#define	HUDParTimeHID	1034
#define	HUDParTimeLabelHID	1035
#define	HUDBodyCountHID	1036

#define	HUDRightHID	1050
#define	HUDLeftHID	1051

#define	MoneyCountHID	1100
#define	MoneyBarHID	1101

#define	CoreGraphicX	20.2
#define	CoreGraphicY	390.0
#define	CoreLabelX	20.1
#define	CoreLabelY	380.0
#define	CorePercentX	58.2
#define	CorePercentY	395.0

#define	CoreYOffset	(-30.0)

#define	WatchHudSizeX	640
#define	WatchHudSizeY	430

str WatchHudFont = "BIGFONT";	// Doesn't seem to work as a define

int lastWeaponTid;
int MonstersTid;
int highestPlayerNumber = 0;
int randomRadioFace;
bool isSetup = FALSE;
int msgEndOfLine = 0;
int msgCurTurn = 0;
int TimerIsLive = FALSE;
int AllVarsLocal = FALSE;	// If this is false, then we're a client in a Skulltag client/server setup

int levelRunningTime	= 0;
int levelMinutes	= 0;
int levelSeconds	= 0;
str levelSecondsSpace	= "0";

// Value, Meaning
// 0	No par time shown
// 1	"Par Time" shown
// 2	"Best Time" shown
int parTimeMode	= 0;

int parTime	= 0;
int parTimeMinutes	= 0;
int parTimeSeconds	= 0;
str parTimeSecondsSpace	= "0";

bool jailTeleporter = FALSE;
str TeleporterOnTexture = "TELEPTY1";
str TeleporterOffTexture = "TELEPTX1";
int LimitCounter;	// Number of monsters that have crossed the limit
int curWave = 0;	// Only to be used for display purposes
bool DisableAnnounceWaveDone = FALSE;	// When TRUE, finishing a wave isn't announced. Used for Overmind mode.
bool GameFinishing = FALSE;	// When set to true, causes all currently running waves to shut down

#define	LEN_RADIO	5
str RadioData[LEN_RADIO][2] = {
{	"Cpl. S. Petersen",	"DIALOG1"	},
{	"Sgt. R. Prince",	"DIALOG1"	},
{	"Lt. J. Wilbur",	"DIALOG2"	},
{	"Col. K. Cloud",	"DIALOG1"	},
{	"Pvt. D. Taylor",	"DIALOG2"	},
};

str WaveCompleteSound = "T_WAVDON";

#define WAVE_ANNOUNCE_SOUNDS_LEN	20
str WaveAnnounceSounds[WAVE_ANNOUNCE_SOUNDS_LEN] = {"T_WAVFNL",
"T_WAVNR1","T_WAVNR2","T_WAVNR3","T_WAVNR4","T_WAVNR5",
"T_WAVNR6","T_WAVNR7","T_WAVNR8","T_WAVNR9","T_WAVN10",
"T_WAVN11","T_WAVN12","T_WAVN13","T_WAVN14","T_WAVN15",
"T_WAVN16","T_WAVN17","T_WAVN18","T_WAVN19"
};

#define WAVE_NUM_WORDS	21
str WaveNumberWord[WAVE_NUM_WORDS] = { "Final",
"First","Second","Third","Fourth","Fifth",
"Sixth","Seventh","Eighth","Ninth","Tenth",
"Eleventh","Twelfth","Thirteenth","Fourteenth","Fifteenth",
"Sixteenth","Seventeenth","Eighteenth","Nineteenth","Twentieth",
};

#define	MILE_ANNOUNCE_SOUNDS_LEN	4
str MileAnnounceSounds[MILE_ANNOUNCE_SOUNDS_LEN] = { "",
"T_MLSTN1","T_MLSTN2","T_MLSTN3"
};

#define MAXBONUSES 26
int BonusArray[MAXBONUSES][3] = {
// Icon     Name                        Description                                                Award
//-------------------------------------------------------------------------------------------------------
// Time bonuses.
{"B_TIME1", "\ceTIMESTEALER",           "Completed the mission in half the par time.",             },//500},
{"B_TIME2", "\ceTIMESPLITTER",          "Completed the mission in a third of the par time.",       },//1000},
{"B_TIME3", "\ceTIMESLICER",            "Completed the mission in a quarter of the par time.",     },//1500},
// Killing bonuses.
{"B_KILL1", "\cgCARNAGE",               "Killed 100 monsters in 1 minute.",                        },//250},
{"B_KILL2", "\cgCRUSADER",              "Killed 200 monsters in 1 minute.",                        },//500},
{"B_KILL3", "\cgCATACLYSM",             "Killed 300 monsters in 1 minute.",                        },//750},
// Defense bonuses.
{"B_MILE",  "\chMILESTONE DEFENDER",    "Didn't let the demons cross one milestone.",              },//600},
{"B_GOAL",  "\chGOAL DEFENDER",         "Didn't let the demons destroy one goal.",                 },//800},
// Core bonuses.
{"B_CORE1", "\cdCORE PROTECTOR",        "Completed the mission with the core at 90%-94%.",         },//500},
{"B_CORE2", "\cdCORE PRESERVER",        "Completed the mission with the core at 95%-99%.",         },//750},
{"B_CORE3", "\cdCORE PERFECTOR",        "Completed the mission with the core at 100%!",            },//1000},
// Limit bonuses.
{"B_LIMIT1","\cfLIMIT PROTECTOR",       "Monster limit did not exceed half.",                      },//200},
{"B_LIMIT2","\cfLIMIT PRESERVER",       "Monster limit did not exceed one third.",                 },//600},
{"B_LIMIT3","\cfLIMIT PERFECTOR",       "Didn't let a single demon pass the deadline!",            },//1000},
// Overmind bonuses.
{"B_OVER1", "\caOVERKILL",              "Killed the overmind without killing any other monsters.", },//1000},
{"B_OVER2", "\caOVERRIDE",              "Didn't let the Overmind destroy one core.",               },//1000},
// Indirect kill bonuses.
{"B_INDK1", "\ciMODERATOR",             "Killed 100 monsters indirectly.",                         },//200},
{"B_INDK2", "\ciMANIPULATOR",           "Killed 200 monsters indirectly.",                         },//600},
{"B_INDK3", "\ciMASTERMIND",            "Killed 300 monsters indirectly.",                         },//1000},
// Cash bonuses.
{"B_CASH1", "\cqMONEY MERCHANT",        "Earnt more than $1000 in one minute.",                    },//500},
{"B_CASH2", "\cqMONEY MAGNET",          "Earnt more than $2000 in one minute.",                    },//1000},
{"B_CASH3", "\cqMONEY MASTER",          "Earnt more than $3000 in one minute.",                    },//1500},
// Damage bonuses.
{"B_DAMG1", "\ctUNSTOPPABLE",           "Took less than 100 damage overall.",                      },//2500},
{"B_DAMG2", "\csINHUMAN",               "Took less than 50 damage overall.",                       },//5000},
{"B_DAMG3", "\cnGODLIKE",               "Took no damage overall.",                                 },//7500},
{"B_NODETH","\cbDOMINATOR",             "Didn't die once.",                                        },//2000},
};

int BonusAwards[MAXBONUSES] =
{500,1000,1500,250,500,750,600,800,500,750,1000,200,600,1000,1000,1000,200,600,1000,500,1000,1500,2500,5000,7500,2000};

// Spots arrays
#define MAX_WEAPON_SPOTS	32
int WeaponSpots[MAX_WEAPON_SPOTS];
int WeaponSpotsUsed = 0;

#define MAX_MEDIC_SPOTS		32
int MedicSpots[MAX_MEDIC_SPOTS];
int MedicSpotsUsed = 0;

#define MAX_AMMO_SPOTS		32
int AmmoSpots[MAX_AMMO_SPOTS];
int AmmoSpotsUsed = 0;

#define MAX_POWERUP_SPOTS	32
int PowerupSpots[MAX_POWERUP_SPOTS];
int PowerupSpotsUsed = 0;

// Object spawn arrays

// EnemyEntries[] format is {int wave, str actor, int number}
#define MAX_ENEMY_ENTRIES	72
int EnemyEntries[MAX_ENEMY_ENTRIES][3];
int EnemyEntriesUsed = 0;

// ZoneData[] format is {int x, int y, int xlength, int yheight, int floor, int goal, int byteangle, int zheight, int lowertid, int highertid}
#define MAX_ZONES	24
#define	ZONEDATA_WIDTH	12
int ZoneData[MAX_ZONES][ZONEDATA_WIDTH];
int ZoneDataUsed = 0;

// WeaponItems[] format is {int wave, str actor}
#define MAX_WEAPON_ITEMS	32
int WeaponItems[MAX_WEAPON_ITEMS][2];
int WeaponItemsUsed = 0;

// MedicItems[] format is {int wave, str actor, int number}
#define MAX_MEDIC_ITEMS		128
int MedicItems[MAX_MEDIC_ITEMS][3];
int MedicItemsUsed = 0;

#define MAX_POWERUP_ITEMS	128
int PowerupItems[MAX_POWERUP_ITEMS][3];
int PowerupItemsUsed = 0;

// AmmoEntries[] format is {int wave, int type, int amount}
#define MAX_AMMO_ENTRIES	256
int AmmoEntries[MAX_AMMO_ENTRIES][3];
int AmmoEntriesUsed = 0;

// AmmoActors[] format is {int type, str actor, int amount}
#define MAX_AMMO_ACTORS		32
int AmmoActors[MAX_AMMO_ACTORS][3];
int AmmoActorsUsed = 0;

// MapRewards[] format is {str actor, int amount}
#define MAX_MAP_REWARDS		8
int MapRewards[MAX_MAP_REWARDS][2];
int MapRewardsUsed = 0;

// RadioMessages[] format is {int wave, int name, str message}
#define MAX_RADIO_MSGS		80
#define RADIO_MSGS_WIDTH	3
int RadioMessages[MAX_RADIO_MSGS][RADIO_MSGS_WIDTH];
int RadioMessagesUsed = 0;

// Temp storage arrays

// WaveFinishCallback[] format is {bool isFinished}
#define MAX_NORMAL_WAVES	40
int WaveFinishCallback[MAX_NORMAL_WAVES];

#define MAX_ENEMIES_PER_WAVE	512

// EnemySpawnCache[] format is {int loc, str actor}
// ENEMY_SPAWN_CACHE_LEN is the limit of how many concurrent waves can be running at once.
#define ENEMY_SPAWN_CACHE_LEN	8
int EnemySpawnCache[ENEMY_SPAWN_CACHE_LEN][MAX_ENEMIES_PER_WAVE];
int EnemySpawnCacheUsed[ENEMY_SPAWN_CACHE_LEN];
int EnemySpawnCacheLoc = 0;

// ItemFogCache[] format is {int spottid, int time}
#define ITEMFOG_CACHE_LEN	16
int ItemFogCache[ITEMFOG_CACHE_LEN][2];
int ItemFogCacheLoc = 0;

// How many tics ItemFogSpawner() has to wait before being able to do fog again at a specific tid.
// Useful to prevent it being called many times at the same spot at the same time.
#define ITEMFOG_REQUIRED_TIC_DIF	6

/* ********* **
** Functions **
** ********* */

function int abs (int x)
{
	if (x < 0)
		return -x;
	return x;
}

function int min (int a, int b)
{
	if (a < b)
		return a;
	return b;
}

function int max (int a, int b)
{
	if (a > b)
		return a;
	return b;
}

function int sqrt (int x)
{
	int r = 0;
	x = x + 1 >> 1;
	while (x > r)
		x -= r++;
	return r;
}

function int fdistancexy (int x1, int y1, int x2, int y2)
{
	int len;
	int ang;
	int x = x1 - x2;
	int y = y1 - y2;

	ang = vectorangle(x,y);
	if(((ang+0.125)%0.5) > 0.25) len = fixeddiv(y, sin(ang));
	else len = fixeddiv(x, cos(ang));

	return len;
}

// Returns fixed point value
function int floor(int fixedNumber)
{
	return fixedNumber & 0xFFFF0000;
}

// Returns fixed point value
function int fround(int fixedNumber)
{
	return floor(fixedNumber + 0.5);
}


// Function from http://zdoom.org/wiki/Getaspectratio
#define ASPECT_4_3 (4.0 / 3)
#define ASPECT_5_4 1.25
#define ASPECT_16_9 (16.0 / 9)
#define ASPECT_16_10 1.6

function int getaspectratio(void)
{
	int width = getcvar("vid_defwidth");
	int height = getcvar("vid_defheight");
	int nowidescreen = getcvar("vid_nowidescreen");
	int tft = getcvar("vid_tft");
	int aspect = getcvar("vid_aspect");
	switch(aspect)
	{
		case 1:	return ASPECT_16_9;
		case 2:	return ASPECT_16_10;
		case 3:	return ASPECT_4_3;
		case 4:	return ASPECT_5_4;
	}
	if(nowidescreen)
	{
		if(!tft)
			return ASPECT_4_3;
		if(fixedmul(height<<16, fixeddiv(5.0, 4.0)) == width<<16)
			return ASPECT_5_4;
		else
			return ASPECT_4_3;
	}
	if(abs((abs(fixedmul(height<<16, fixeddiv(16.0, 9.0)))>>16) - width) < 10)
	{
		return ASPECT_16_9;
	}
	if(abs((abs(fixedmul(height<<16, fixeddiv(16.0, 10.0)))>>16) - width) < 60)
	{
		if((width == 320 && height == 200) || (width == 640 && height == 400))
			return ASPECT_4_3;
		return ASPECT_16_10;
	}
	if(fixedmul(height<<16, fixeddiv(5.0, 4.0))>>16 == width && tft)
	{
		return ASPECT_5_4;
	}
	return ASPECT_4_3;
}

// Used to pad times and other fixed width numbers in output
// space(1,3) returns "00", space(12,3) returns "0", space(123,3) returns ""
function str zerospace(int num, int spaces)
{
	int difference = spaces-numLen(num);
	if((difference>0)&&(num>=0))
	{
		switch(difference)
		{
		case 1: return "0";
		case 2: return "00";
		case 3: return "000";
		case 4: return "0000";
		case 5: return "00000";
		case 6: return "000000";
		case 7:	return "0000000";
		default:
			return "...";
		}
	}
	return "";
}

// Returns number of characters num is
function int numLen(int num)
{
	int i = 1;
	while(abs(num)/pow(10,i-1) >= 10)
	{	i++;	}

	if(num<0) // Count the "-" sign
	{	i++;	}

	return i;
}

function int pow (int x, int n)
{
	if (n < 1)
		return 1;
	int y = x;
	while (--n)
		y *= x;
	return y;
}

// Level settings

function void AddWeaponSpot (int tid)
{
	if(WeaponSpotsUsed==MAX_WEAPON_SPOTS)
	{	PrintBold(s:"Error: WeaponSpots[] limit hit");	}

	WeaponSpots[WeaponSpotsUsed++] = tid;
}

function void AddMedicSpot (int tid)
{
	if(MedicSpotsUsed==MAX_MEDIC_SPOTS)
	{	PrintBold(s:"Error: MedicSpots[] limit hit");	}

	MedicSpots[MedicSpotsUsed++] = tid;
}

function void AddAmmoSpot (int tid)
{
	if(AmmoSpotsUsed==MAX_AMMO_SPOTS)
	{	PrintBold(s:"Error: AmmoSpots[] limit hit");	}

	AmmoSpots[AmmoSpotsUsed++] = tid;
}

function void AddPowerupSpot (int tid)
{
	if(PowerupSpotsUsed==MAX_POWERUP_SPOTS)
	{	PrintBold(s:"Error: PowerupSpots[] limit hit");	}

	PowerupSpots[PowerupSpotsUsed++] = tid;
}

function void AddZone (int bottomlefttid, int toprighttid, int patrolnode)
{
	AddZoneMile(bottomlefttid, toprighttid, patrolnode, 0);
}

function void AddZoneAlt (int bottomlefttid, int toprighttid, int patrol1, int patrol2)
{
	AddZoneBase(bottomlefttid, toprighttid, patrol1, 0, patrol2, 0, 0);
}

function void AddZoneMile (int bottomlefttid, int toprighttid, int patrolnode, int mile)
{
	AddZoneBase(bottomlefttid, toprighttid, patrolnode, mile, 0, 0, 0);
}

function void AddZoneAltMile (int bottomlefttid, int toprighttid, int patrol1, int patrol2, int mile)
{
	AddZoneBase(bottomlefttid, toprighttid, patrol1, mile, patrol2, 0, 0);
}

function void AddZoneBase (int bottomlefttid, int toprighttid, int patrol1, int mile, int patrol2, int unused1, int unused2)
{
	if(ZoneDataUsed==MAX_ZONES)
	{	PrintBold(s:"Error: ZoneData[] limit hit");	}

	// Check if this zone has already been added
	for(int i=0; i<ZoneDataUsed; i++)
	{
		if(	(ZoneData[i][5]==patrol1) &&
			(ZoneData[i][8]==min(bottomlefttid,toprighttid)) &&
			(ZoneData[i][9]==max(bottomlefttid,toprighttid)) &&
			(ZoneData[i][10]==mile) &&
			(ZoneData[i][11]==patrol2)
		)
		{
			// Zone has already been added, so do nothing.
			return;
		}
	}

	ZoneData[ZoneDataUsed][0]	= min( GetActorX(bottomlefttid), GetActorX(toprighttid) );
	ZoneData[ZoneDataUsed][1]	= min( GetActorY(bottomlefttid), GetActorY(toprighttid) );

	ZoneData[ZoneDataUsed][2]	= abs( GetActorX(bottomlefttid)-GetActorX(toprighttid) );
	ZoneData[ZoneDataUsed][3]	= abs( GetActorY(bottomlefttid)-GetActorY(toprighttid) );

	ZoneData[ZoneDataUsed][4]	= min( GetActorZ(bottomlefttid), GetActorZ(toprighttid) );

	ZoneData[ZoneDataUsed][5]	= patrol1;

	ZoneData[ZoneDataUsed][6]	= GetActorAngle(bottomlefttid) >> 8;

	ZoneData[ZoneDataUsed][7]	= abs( GetActorZ(bottomlefttid)-GetActorZ(toprighttid) );

//	Store the tids used for the mapspots so that the zone can be referenced by functions
//	such as RemoveZone() by them.
	ZoneData[ZoneDataUsed][8]	= min( bottomlefttid, toprighttid );
	ZoneData[ZoneDataUsed][9]	= max( bottomlefttid, toprighttid );

	ZoneData[ZoneDataUsed][10]	= mile;

	ZoneData[ZoneDataUsed][11]	= patrol2;

	ZoneDataUsed++;
}

function bool RemoveWeaponSpot (int tid)
{
	int i, j;
	int foundTids = 0;

	for(i=0; i<WeaponSpotsUsed; i++)
	{
		if(WeaponSpots[i]==tid)
		{
			WeaponSpotsUsed--;
			foundTids++;
			for(j=i; j<WeaponSpotsUsed; j++)
			{
				WeaponSpots[j] = WeaponSpots[j+1];
			}
			i--;
		}
	}

	if(foundTids > 0)
		return true;

	return false;
}

function bool RemoveMedicSpot (int tid)
{
	int i, j;
	int foundTids = 0;

	for(i=0; i<MedicSpotsUsed; i++)
	{
		if(MedicSpots[i]==tid)
		{
			MedicSpotsUsed--;
			foundTids++;
			for(j=i; j<MedicSpotsUsed; j++)
			{
				MedicSpots[j] = MedicSpots[j+1];
			}
			i--;
		}
	}

	if(foundTids > 0)
		return true;

	return false;
}

function bool RemoveAmmoSpot (int tid)
{
	int i, j;
	int foundTids = 0;

	for(i=0; i<AmmoSpotsUsed; i++)
	{
		if(AmmoSpots[i]==tid)
		{
			AmmoSpotsUsed--;
			foundTids++;
			for(j=i; j<AmmoSpotsUsed; j++)
			{
				AmmoSpots[j] = AmmoSpots[j+1];
			}
			i--;
		}
	}

	if(foundTids > 0)
		return true;

	return false;
}

function bool RemovePowerupSpot (int tid)
{
	int i, j;
	int foundTids = 0;

	for(i=0; i<PowerupSpotsUsed; i++)
	{
		if(PowerupSpots[i]==tid)
		{
			PowerupSpotsUsed--;
			foundTids++;
			for(j=i; j<PowerupSpotsUsed; j++)
			{
				PowerupSpots[j] = PowerupSpots[j+1];
			}
			i--;
		}
	}

	if(foundTids > 0)
		return true;

	return false;
}

function void ClearItemSpawnSpots (void)
{
	WeaponSpotsUsed	= 0;
	MedicSpotsUsed	= 0;
	AmmoSpotsUsed	= 0;
	PowerupSpotsUsed	= 0;
}

function bool RemoveZone (int bottomlefttid, int toprighttid)
{
	int i, j, z;
	int foundTids = 0;

	for(i=0; i<ZoneDataUsed; i++)
	{
		if(	(ZoneData[i][8]==min(bottomlefttid,toprighttid)) &&
			(ZoneData[i][9]==max(bottomlefttid,toprighttid)) )
		{
			ZoneDataUsed--;
			foundTids++;
			for(j=i; j<ZoneDataUsed; j++)
			{
			//	ZoneData[j] = ZoneData[j+1];
				for(z=0; z<ZONEDATA_WIDTH; z++)
				{
					ZoneData[j][z] = ZoneData[j+1][z];
				}
			}
			i--;
		}
	}

	if(foundTids > 0)
		return true;

	return false;
}

function bool ChangeZonePatrol (int bottomlefttid, int toprighttid, int newPatrol)
{
	int changed = 0;

	for(int i=0; i<ZoneDataUsed; i++)
	{
		if(	(ZoneData[i][8]==min(bottomlefttid,toprighttid)) &&
			(ZoneData[i][9]==max(bottomlefttid,toprighttid)) )
		{
			ZoneData[i][5] = newPatrol;
			changed++;
		}
	}

	if(changed > 0)
		return true;

	return false;
}

function void ClearZones (void)
{
	ZoneDataUsed = 0;
}

function void ClearSettings (void)
{
	ClearZones();
	ClearItemSpawnSpots();
}

function void AddAmmoActor (int type, str actor, int amount)
{
	if(AmmoActorsUsed==MAX_AMMO_ACTORS)
	{	PrintBold(s:"Error: AmmoActors[] limit hit");	}

	AmmoActors[AmmoActorsUsed][0]	= type;
	AmmoActors[AmmoActorsUsed][1]	= actor;
	AmmoActors[AmmoActorsUsed++][2]	= amount;
}

function void AddMapReward (str actor, int amount)
{
	if(MapRewardsUsed==MAX_MAP_REWARDS)
	{	PrintBold(s:"Error: MapRewards[] limit hit");	}

	MapRewards[MapRewardsUsed][0]	= actor;
	MapRewards[MapRewardsUsed++][1]	= amount;
}

//Added by Jimmy. Tweak if needed.

int bonuses[32];
int BonusQueue[MAXBONUSES];

function void GetBonus ( int bonustype )
{
  bonuses[playernumber()]++;
 	log(i:bonustype, s:"    ",i:bonuses[playernumber()]);
 BonusQueue[bonuses[playernumber()]] = bonustype;
  LocalAmbientSound("Cash",128);
}

Script S_DETECTENDOFLVL ENTER
{
	while (GameActive)
	{
		delay(1);
	}
	GetEndBonuses(1);
}

script S_GETBONUS ( int bonustype )
{
  
  SetHudSize(800,600,1);
  
  SetFont("BigFont");
  
  str bigmessage = "Bonuses Awarded!";
  if(!bonuses[playernumber()]) bigmessage = " ";
  
  
  
  HudMessage(s:bigmessage;
  HUDMSG_FADEOUT|HUDMSG_LOG,200,CR_WHITE,400.4,392.2,4.0+bonuses[playernumber()],1.0);
  
  SetFont("ConFont");
  
  for(int i=1; i<=bonuses[playernumber()]; i++)
  {
    HudMessage(s:BonusArray[BonusQueue[i]][1], s:" BONUS \cf($",d:BonusAwards[BonusQueue[i-1]],s:")";
    HUDMSG_FADEOUT|HUDMSG_LOG,200+i,CR_UNTRANSLATED,400.4,416.2+(8.0*i),4.0+bonuses[playernumber()],1.0);
  
    HudMessage(s:BonusArray[BonusQueue[i]][2];
    HUDMSG_FADEOUT|HUDMSG_LOG,300+i,CR_WHITE,400.4,432.2+(8.0*i)+(8.0*bonuses[playernumber()]),4.0+bonuses[playernumber()],1.0);
    
    GiveInventory("CoinItem",BonusAwards[BonusQueue[i]]);
  }
  
  bonuses[playernumber()] = 0;
  
  SetFont("BigFont");
  HudMessage(s:bigmessage;
  HUDMSG_FADEINOUT,199,CR_GOLD,400.4,392.2,0.0,0.5,0.5); delay(35);
  HudMessage(s:bigmessage;
  HUDMSG_FADEINOUT,199,CR_GOLD,400.4,392.2,0.0,0.5,0.5); delay(35);
  HudMessage(s:bigmessage;
  HUDMSG_FADEINOUT,199,CR_GOLD,400.4,392.2,0.0,0.5,0.5);
}

function void GetEndBonuses ( int correct )
{
	if (!correct) //Patch code to save me the trouble of trying to go through all themaps to fix this.
	return;
	
	log(s:"Game mode - ", i:gamemode, s:"  - Activator: ", n:playernumber()+1);
  if(GameMode==GAME_MILESTONE && CurrentMile==0)
    GetBonus(B_NOMILESTONES);
  
  if(GameMode==GAME_GOAL && ThingCount(T_NONE, HateEpicenter)==NumberOfTargets)
    GetBonus(B_NOGOALS);

  if(GameMode==GAME_OVERMIND && ThingCount(T_NONE, HateEpicenter)==NumberOfTargets)
    GetBonus(B_NOCORES);

  if(GameMode==GAME_CORE)
  {
    if(GetActorProperty(HateEpicenter,AProp_Health)==1000)
      GetBonus(B_CORE100);
	else if(GetActorProperty(HateEpicenter,AProp_Health)>=950 && GetActorProperty(HateEpicenter,AProp_Health)<1000)
	  GetBonus(B_CORE95);
	else if(GetActorProperty(HateEpicenter,AProp_Health)>=900 && GetActorProperty(HateEpicenter,AProp_Health)<950)
	  GetBonus(B_CORE90);
  }
  
  if(GameMode==GAME_LIMIT)
  {
    if(LimitCounter==0) // Let no demons past the deadline.
      GetBonus(B_LIMITNONE);
	else if(LimitCounter<=LimitMax/4 /*&& LimitCounter>LimitMax/2*/)
	  GetBonus(B_LIMITQUARTER);
	else if(LimitCounter<=LimitMax/2)
	  GetBonus(B_LIMITHALF);
  }
  
  if(DamageTaken[PlayerNumber()]==0) // Took no damage.
    GetBonus(B_NODAMAGE);
  else if(DamageTaken[PlayerNumber()]>=50 && DamageTaken[PlayerNumber()]<100) // Took between 50 and 99 damage.
    GetBonus(B_DAMAGE100);
  else if(DamageTaken[PlayerNumber()]<50) //Took less than 50 damage.
    GetBonus(B_DAMAGE50);

  if(MapScores[GetLevelInfo(LEVELINFO_LEVELNUM)]==-1) // If the map is in time attack mode.
  {
	  log(s:"Time checked");
    if(levelRunningTime<=MapParTime[GetLevelInfo(LEVELINFO_LEVELNUM)]/4)
	  GetBonus(B_TIMEQUARTER);
    else if(levelRunningTime<=MapParTime[GetLevelInfo(LEVELINFO_LEVELNUM)]/3)
	  GetBonus(B_TIMETHIRD);
	else if(levelRunningTime<=MapParTime[GetLevelInfo(LEVELINFO_LEVELNUM)]/2)
	  GetBonus(B_TIMEHALF);
  }
    ACS_ExecuteAlways(S_GETBONUS,0,0);
}

script S_DAMAGECHECKS ENTER
{
  int oldhealth = 0;
  int newhealth = 0;
  while(1)
  {
    oldhealth = GetActorProperty(0,AProp_Health);
   	delay(1);
	newhealth = GetActorProperty(0,AProp_Health);
	if(newhealth<oldhealth) DamageTaken[PlayerNumber()] = DamageTaken[PlayerNumber()] + (oldhealth-newhealth);
	
  }
}

// Wave settings

function void WaveEnemy (int wave, str actor, int num)
{
	if(EnemyEntriesUsed==MAX_ENEMY_ENTRIES)
	{	PrintBold(s:"Error: EnemyEntries[] limit hit");	}

	EnemyEntries[EnemyEntriesUsed][0]	= wave;
	EnemyEntries[EnemyEntriesUsed][1]	= actor;
	EnemyEntries[EnemyEntriesUsed++][2]	= num;
}

function void ClearWaveEnemies (void)
{
	EnemyEntriesUsed	= 0;
}

function void WaveMessage (int wave, int name, str message)
{
	if(RadioMessagesUsed==MAX_RADIO_MSGS)
	{	PrintBold(s:"Error: RadioMessages[] limit hit");	}

	RadioMessages[RadioMessagesUsed][0]	= wave;
	RadioMessages[RadioMessagesUsed][1]	= name;
	RadioMessages[RadioMessagesUsed++][2]	= message;
}

// Removes all messages of a certain wave
function int RemoveWaveMessage (int wave)
{
	int i, j, z;
	int found = 0;

	for(i=0; i<RadioMessagesUsed; i++)
	{
		if( RadioMessages[i][0]==wave )
		{
			RadioMessagesUsed--;
			found++;
			for(j=i; j<RadioMessagesUsed; j++)
			{
			//	RadioMessages[j] = RadioMessages[j+1];
				for(z=0; z<RADIO_MSGS_WIDTH; z++)
				{
					RadioMessages[j][z] = RadioMessages[j+1][z];
				}
			}
			i--;
		}
	}

	return found;
}

function void ClearWaveMessages (void)
{
	RadioMessagesUsed	= 0;
}

//	This function is a little shortcut for maps to use. Good if we decide we want to change
//	the exact ending message later.
function void WaveMessageEnd (int wave, int name)
{
	WaveMessage(	wave,	name,	"Incredible job, marine!"	);
	WaveMessage(	wave,	name,	"You have successfully\ndefended this UAC\ninstallation!"	);
}

function void WaveWeapon (int wave, str actor)
{
	if(WeaponItemsUsed==MAX_WEAPON_ITEMS)
	{	PrintBold(s:"Error: WeaponItems[] limit hit");	}

	WeaponItems[WeaponItemsUsed][0]	= wave;
	WeaponItems[WeaponItemsUsed++][1]	= actor;
}

function void WavePowerup (int wave, str actor, int num)
{
	if(PowerupItemsUsed==MAX_POWERUP_ITEMS)
	{	PrintBold(s:"Error: PowerupItems[] limit hit");	}

	PowerupItems[PowerupItemsUsed][0]	= wave;
	PowerupItems[PowerupItemsUsed][1]	= actor;
	PowerupItems[PowerupItemsUsed++][2]	= num;
}

function void WaveMedic (int wave, str actor, int num)
{
	if(MedicItemsUsed==MAX_MEDIC_ITEMS)
	{	PrintBold(s:"Error: MedicItems[] limit hit");	}

	MedicItems[MedicItemsUsed][0]	= wave;
	MedicItems[MedicItemsUsed][1]	= actor;
	MedicItems[MedicItemsUsed++][2]	= num;
}

function void WaveAmmo (int wave, int type, int amount)
{
	if(AmmoEntriesUsed==MAX_AMMO_ENTRIES)
	{	PrintBold(s:"Error: AmmoEntries[] limit hit");	}

	AmmoEntries[AmmoEntriesUsed][0]	= wave;
	AmmoEntries[AmmoEntriesUsed][1]	= type;
	AmmoEntries[AmmoEntriesUsed++][2]	= amount;
}

function void ClearWaveItems (void)
{
	WeaponItemsUsed	= 0;
	PowerupItemsUsed	= 0;
	MedicItemsUsed	= 0;
	AmmoEntriesUsed	= 0;
}

function void ClearWaves (void)
{
	ClearWaveItems();
	ClearWaveMessages();
	ClearWaveEnemies();
}

function void ClearAll (void)
{
	ClearSettings();
	ClearWaves();
}

// Do stuff

function void CallWave (int wave)
{
	if( (wave>0) && (wave<MAX_NORMAL_WAVES))
		WaveFinishCallback[wave]	= FALSE;

	ACS_ExecuteAlways(S_CALLWAVE,0,wave);
}

function void SpawnStuff (int wave)
{
	SpawnAmmo(wave);
	SpawnPowerups(wave);
	SpawnWeapons(wave);
	SpawnMedic(wave);
}

function int SpawnAmmo (int wave)
{
	int counter = 0;

	if( ! AmmoSpotsUsed )
	{	return counter;	}

	Setup();

	int curAmmoQuota;
	int curAmmoActorIndex;
	int spot = 0;
	int tid = NewTempTid();

	for(int i=0; i<AmmoEntriesUsed; i++)
	{
		if(AmmoEntries[i][0]==wave)
		{
			curAmmoQuota = Multiplier( M_AMMO, AmmoEntries[i][2] );
			curAmmoActorIndex = FindAmmoActor(AmmoEntries[i][1],curAmmoQuota);

			// If we don't even have enough ammo quota in the first loop to spawn even one ammo item, spawn
			// one of the smallest size anyway. The logic is that the skill multiplier might have reduced the
			// ammo quota to less than what's needed to spawn any, but the mapper still expects some ammo
			// to be given.
			if( (curAmmoActorIndex == -1) && (curAmmoQuota > 0) )
			{
				curAmmoActorIndex = FindLeastAmmoActor(AmmoEntries[i][1]);
			}

			while(curAmmoActorIndex != -1)
			{
				SpawnSpot(AmmoActors[curAmmoActorIndex][1], AmmoSpots[spot], tid);
				ItemFogSpawner(AmmoSpots[spot]);
				ItemThruster(tid);
				Thing_ChangeTid(tid, 0);

				counter++;

				if (spot == AmmoSpotsUsed-1) {spot=0;} else {spot++;} // Cycle through each spot

				curAmmoQuota -= AmmoActors[curAmmoActorIndex][2];
				curAmmoActorIndex = FindAmmoActor(AmmoEntries[i][1],curAmmoQuota);
			}
		}
	}

	FreeTempTid(tid);
	return counter;
}

function int FindAmmoActor (int type, int amount)
{
	if(amount<=0)	// Quickly return -1 if we're given an impossible task
	{	return -1;	}

	int candidate = -1;

	for(int i=0; i<AmmoActorsUsed; i++)
	{
		if( (AmmoActors[i][0]==type) && (AmmoActors[i][2]<=amount) )
		{
			if( candidate == -1 )
			{
				candidate = i;
			} else {
				if( AmmoActors[i][2]>AmmoActors[candidate][2] )
				{	candidate = i;	}
			}
		}
	}
	return candidate;
}

function int FindLeastAmmoActor (int type)
{
	int candidate = -1;

	for(int i=0; i<AmmoActorsUsed; i++)
	{
		if( AmmoActors[i][0]==type )
		{
			if( candidate == -1 )
			{
				candidate = i;
			} else {
				if( AmmoActors[i][2]<AmmoActors[candidate][2] )
				{	candidate = i;	}
			}
		}
	}
	return candidate;
}

function int SpawnWeapons (int wave)
{
	int counter = 0;

	if( ! WeaponSpotsUsed )
	{	return counter;	}

	Setup();

	bool weaponsThrusted = FALSE;

	for(int i=0; i<WeaponItemsUsed; i++)
	{
		if(WeaponItems[i][0]==wave)
		{
			counter++;	// Only count unique weapons spawned, not total

			if( ! weaponsThrusted ) // Make sure we only thrust old weapons one time
			{
				// Thrust old weapon(s) out of the way
				WeaponThruster(lastWeaponTid);
				// Clear its TID so next weapons spawned can use it.
				Thing_ChangeTID(lastWeaponTid, 0);

				weaponsThrusted = TRUE;
			}

			for (int spot=0; spot<WeaponSpotsUsed; spot++)
			{
				// Spawn new weapon
				SpawnSpot(WeaponItems[i][1], WeaponSpots[spot], lastWeaponTid);
				ItemFogSpawner( WeaponSpots[spot] );
			}
		}
	}
	// This next line makes sure the weapons stay if "Weapons Stay" is set or the game is coop
	SetActorProperty(lastWeaponTid, APROP_Dropped, FALSE);
	return counter;
}

function int SpawnPowerups (int wave)
{
	int counter = 0;

	if( ! PowerupSpotsUsed )
	{	return counter;	}

	int spot = 0;
	int tid = NewTempTid();

	for(int i=0; i<PowerupItemsUsed; i++)
	{
		if( PowerupItems[i][0]==wave )
		{
			while( ThingCountName(PowerupItems[i][1], 0) < Multiplier( M_POWERUP, PowerupItems[i][2] ) )
			{
				SpawnSpot(PowerupItems[i][1], PowerupSpots[spot], tid);
				ItemFogSpawner(PowerupSpots[spot]);
				ItemThruster(tid);
				Thing_ChangeTid(tid, 0);

				counter++;

				if (spot == PowerupSpotsUsed-1) {spot=0;} else {spot++;} // Cycle through each spot
			}
		}
	}
	FreeTempTid(tid);
	return counter;
}

// This function works identical to SpawnPowerups()
function int SpawnMedic (int wave)
{
	int counter = 0;

	if( ! MedicSpotsUsed )
	{	return counter;	}

	int spot = 0;
	int tid = NewTempTid();

	for(int i=0; i<MedicItemsUsed; i++)
	{
		if( MedicItems[i][0]==wave )
		{
			while( ThingCountName(MedicItems[i][1], 0) < Multiplier( M_MEDIC, MedicItems[i][2] ) )
			{
				SpawnSpot(MedicItems[i][1], MedicSpots[spot], tid);
				ItemFogSpawner(MedicSpots[spot]);
				ItemThruster(tid);
				Thing_ChangeTid(tid, 0);

				counter++;

				if (spot == MedicSpotsUsed-1) {spot=0;} else {spot++;} // Cycle through each spot
			}
		}
	}
	FreeTempTid(tid);
	return counter;
}

// Note: ItemFogSpawner can only work on one thing at a time! Don't give it a tid of multiple things.
function void ItemFogSpawner( int spot )
{
	// Have we called ItemFogSpawner for this same spot not too long ago?
	// If so, do nothing and return out of the function.
	for(int i=0; i<ITEMFOG_CACHE_LEN; i++)
	{
		if(ItemFogCache[i][0]==spot)
		{
			if( (Timer() - ItemFogCache[i][1]) < ITEMFOG_REQUIRED_TIC_DIF)
			{	return;	}
		}
	}

	ItemFogCache[ItemFogCacheLoc][0]	= spot;
	ItemFogCache[ItemFogCacheLoc++][1]	= Timer();

	if(ItemFogCacheLoc == ITEMFOG_CACHE_LEN)
	{	ItemFogCacheLoc = 0;	}

	Spawn("ItemFog",
		GetActorX(Spot)+random(-8.0,8.0),
		GetActorY(Spot)+random(-8.0,8.0),
		GetActorZ(Spot)+random(8.0,16.0), 0, 0);
	ThingSound(spot, "misc/spawn", 127); // lovely flange
}

function void WeaponThruster (int tid)
{
	ThrustThing(random(0,255), random(3,4), 0, tid);
	ThrustThingZ(tid, random(24,30), 0, 0);
}

function void ItemThruster (int tid)
{
	ThrustThing(random(0,255), random(2,3), 0, tid);
	ThrustThingZ(tid, random(16,24), 0, 0);
}

// Internal, probably shouldn't be called by a map directly

function void GiveMapRewards (void)
{
	for(int i=0; i<=highestPlayerNumber; i++)
	{
		for(int j=0; j<MapRewardsUsed; j++)
		{
			GiveActorInventory(PLAYER_TID_START+i,MapRewards[j][0],MapRewards[j][1]);
		}
	}
}

function void AnnounceNewWave (int wave)
{
	if((wave>0)&&(wave<=Waves)) // Don't announce for special negative waves or post-final wave
	{
		curWave = wave;
		UpdateWaveTrackers();
		UpdateTime();

	//	if(wave>1) // Don't announce for wave 1
	//	{
			SetHudSize(512,384,1);
			SetFont("BigFont");

			if(wave==Waves)
			{	wave = 0;	} // Index 0 points to "Final"

			if(wave<WAVE_NUM_WORDS)
			{
				HudmessageBold(s:WaveNumberWord[wave],s:"\cc Wave!";
					HUDMSG_FADEINOUT|HUDMSG_LOG,0,CR_RED,256.0,128.1,3.0,1.0,1.0);
			} else {
				HudMessageBold(s:"Wave ",d:wave,s:"\cc!";
					HUDMSG_FADEINOUT|HUDMSG_LOG,0,CR_RED,256.0,128.1,3.0,1.0,1.0);
			}

			if( wave<WAVE_ANNOUNCE_SOUNDS_LEN )
			{	AmbientSound(WaveAnnounceSounds[wave],127);	}
	//	}
	}
}

function void AnnounceWaveDone (int wave)
{
	if(DisableAnnounceWaveDone)
	{	return;	}

	if((wave>0)&&(wave<=Waves)) // Don't announce for special negative waves or post-final wave
	{
		SetHudSize(512,384,1);
		SetFont("BigFont");

		if(wave==Waves)
		{	wave = 0;	} // Index 0 points to "Final"

		if(wave<WAVE_NUM_WORDS)
		{
			HudmessageBold(s:WaveNumberWord[wave],s:" Wave \ccComplete!";
				HUDMSG_FADEINOUT|HUDMSG_LOG,0,CR_RED,256.0,128.1,3.0,1.0,1.0);
		} else {
			HudmessageBold(s:"Wave ",d:wave,s:" \ccComplete!";
				HUDMSG_FADEINOUT|HUDMSG_LOG,0,CR_RED,256.0,128.1,3.0,1.0,1.0);
		}

		AmbientSound(WaveCompleteSound,127);
	}
}

function void AnnounceNewMile (int mile)
{
	SetHudSize(640,480,1);
	SetFont("BigFont");

	if(mile<WAVE_NUM_WORDS)
	{
		HudmessageBold(s:WaveNumberWord[mile],s:" Milestone \cchas been breached!";
			HUDMSG_FADEINOUT|HUDMSG_LOG,0,CR_BLACK,320.0,130.1,3.0,1.0,1.0);
	} else {
		HudmessageBold(s:"Milestone ",d:mile,s:" \cchas been breached!";
			HUDMSG_FADEINOUT|HUDMSG_LOG,0,CR_BLACK,320.0,130.1,3.0,1.0,1.0);
	}

	if( mile<MILE_ANNOUNCE_SOUNDS_LEN )
	{	AmbientSound(MileAnnounceSounds[mile],127);	}
}

function int RandomCoords (int zone, int axis)	// 0 is x, 1 is y, 2 is z
{
	int result;
	switch (axis)
	{
	case 0:
		result = random( ZoneData[zone][0], ZoneData[zone][0]+ZoneData[zone][2] ); // origin x, origin x + xlength
		break;
	case 1:
		result = random( ZoneData[zone][1], ZoneData[zone][1]+ZoneData[zone][3] ); // origin y, origin y + yheight
		break;
	case 2:
		result = random( ZoneData[zone][4], ZoneData[zone][4]+ZoneData[zone][7] ); // floor z, floor z + zheight
		break;
	default:
		PrintBold(s:"Error: RandomCoords() called without a valid axis");
	}
	return result;
}

function void GiveDefaults (int tid, int patrolnode, int hatetarget)
{
	Thing_SetGoal( tid, patrolnode, 0, 1 );
	if(hatetarget)
	{	Thing_Hate( tid, hatetarget, 3);	}
}

function bool MapIsMenu (void)
{
	if(GetLevelInfo(LEVELINFO_LEVELNUM)==999)
		return true;

	return false;
}

function void UpdateJailTrackers (void)
{
	int inJail = 0;
	int i, curTrackers;

	// Count how many players are in jail
	for(i=0; i<=highestPlayerNumber; i++)
	{
		inJail += CheckActorInventory(PLAYER_TID_START+i,"JailTracker");
	}

	for(i=0; i<=highestPlayerNumber; i++)
	{
		curTrackers = CheckActorInventory(PLAYER_TID_START+i,"JailTrackerAllPlayers");
		if(curTrackers<inJail)
		{
			GiveActorInventory(PLAYER_TID_START+i,"JailTrackerAllPlayers",inJail-curTrackers);
		} else if(curTrackers>inJail)
		{
			TakeActorInventory(PLAYER_TID_START+i,"JailTrackerAllPlayers",curTrackers-inJail);
		}
	}
}

function bool mapRangeFinished (int first, int last)
{
	for(int i=first; i<=last; i++)
	{
		if(MapScores[i]==0) // Map isn't finished
		{	return false;	}
	}
	return true;
}

function void Setup (void)
{
	if( ! isSetup )
	{
		isSetup = TRUE;
		lastWeaponTid = NewTempTid();
		MonstersTid = NewTempTid();
		randomRadioFace = random(0,LEN_RADIO-1);

		if(PrisonCamStart)
		{
			SetCameraToTexture(PrisonCamStart, "CT_1", 90 );
			SetCameraToTexture(PrisonCamStart+1, "CT_2", 90 );
			SetCameraToTexture(PrisonCamStart+2, "CT_3", 90 );
			SetCameraToTexture(PrisonCamStart+3, "CT_4", 90 );
			SetCameraToTexture(PrisonCamStart+4, "CT_5", 90 );
			SetCameraToTexture(PrisonCamStart+5, "CT_6", 90 );
		}

		if(PrisonTeleporterLineId)
		{
			SetLineSpecial(PrisonTeleporterLineId, ACS_ExecuteAlways, S_JAIL_LEAVE, 0, PrisonTeleporterThrustAngle );
			SetJailTeleporter( FALSE );
		}

		AddAmmoActor(	BULLETS,	"Clip",		10	);
		AddAmmoActor(	BULLETS,	"ClipBox",	50	);
		AddAmmoActor(	SHELLS,		"Shell",	4	);
		AddAmmoActor(	SHELLS,		"ShellBox",	20	);
		AddAmmoActor(	ROCKETS,	"RocketAmmo",	1	);
		AddAmmoActor(	ROCKETS,	"RocketBox",	5	);
		AddAmmoActor(	CELLS,		"Cell",		20	);
		AddAmmoActor(	CELLS,		"CellPack",	100	);
		AddAmmoActor(	GAS,		"Gas",		24	);
		AddAmmoActor(	GAS,		"Fuel",		96	);
		AddAmmoActor(	MINES,		"Mines",	2	);

		// All Level $$ Rewards go here.
		// Number corresponds to level number
		MapCoinReward[1]	= 0;
		MapCoinReward[2]	= 200;
		MapCoinReward[3]	= 300;
		MapCoinReward[4]	= 400;
		MapCoinReward[5]	= 500;
		MapCoinReward[6]	= 600;
		MapCoinReward[7]	= 700;
		MapCoinReward[8]	= 800;
		MapCoinReward[9]	= 900;
		MapCoinReward[10]	= 1000;
		MapCoinReward[11]	= 1100;
		MapCoinReward[12]	= 1200;
		MapCoinReward[13]	= 1300;
		MapCoinReward[14]	= 1400;
		MapCoinReward[15]	= 1500;
		MapCoinReward[16]	= 1600;
		MapCoinReward[17]	= 1700;
		MapCoinReward[18]	= 1800;
		MapCoinReward[19]	= 1900;
		MapCoinReward[20]	= 2000;
		MapCoinReward[21]	= 2100;
		MapCoinReward[22]	= 2200;
		MapCoinReward[23]	= 2300;
		MapCoinReward[24]	= 2400;
		MapCoinReward[25]	= 2500;
		MapCoinReward[26]	= 2600;
		MapCoinReward[27]	= 2700;
		MapCoinReward[28]	= 2800;
		MapCoinReward[29]	= 2900;
		MapCoinReward[30]	= 3000;
		MapCoinReward[31]	= 3100;
		MapCoinReward[32]	= 3200;
		MapCoinReward[33]	= 3300;
		MapCoinReward[34]	= 3400;
		MapCoinReward[35]	= 3500;
		MapCoinReward[50]	= 3000;
		MapCoinReward[52]	= 3000;
		MapCoinReward[54]	= 3000;
		MapCoinReward[56]	= 3000;

		// All level par times here, in seconds
		// 0 means no set par time (yet)
		// Hint: 5min 20sec can be shown as 5*60+20
		MapParTime[1]	= 10*60;
		MapParTime[2]	= 10*60;
		MapParTime[3]	= 0;
		MapParTime[4]	= 0;
		MapParTime[5]	= 0;
		MapParTime[6]	= 0;
		MapParTime[7]	= 0;
		MapParTime[8]	= 0;
		MapParTime[9]	= 0;
		MapParTime[10]	= 0;
		MapParTime[11]	= 0;
		MapParTime[12]	= 0;
		MapParTime[13]	= 0;
		MapParTime[14]	= 0;
		MapParTime[15]	= 0;
		MapParTime[16]	= 0;
		MapParTime[17]	= 0;
		MapParTime[18]	= 0;
		MapParTime[19]	= 0;
		MapParTime[20]	= 0;
		MapParTime[21]	= 0;
		MapParTime[22]	= 0;
		MapParTime[23]	= 0;
		MapParTime[24]	= 0;
		MapParTime[25]	= 0;
		MapParTime[26]	= 0;
		MapParTime[27]	= 0;
		MapParTime[28]	= 0;
		MapParTime[29]	= 0;
		MapParTime[30]	= 0;
		MapParTime[31]	= 0;
		MapParTime[32]	= 0;
		MapParTime[33]	= 0;
		MapParTime[34]	= 0;
		MapParTime[35]	= 0;
		MapParTime[50]	= 0;
		MapParTime[52]	= 0;
		MapParTime[54]	= 0;
		MapParTime[56]	= 0;

		AddMapReward("CoinItem", MapCoinReward[GetLevelInfo(LEVELINFO_LEVELNUM)]);

		if ( MapScores[GetLevelInfo(LEVELINFO_LEVELNUM)] == -1 )
		{
			setParTime(MapParTime[GetLevelInfo(LEVELINFO_LEVELNUM)], 0);
		}
		else if ( MapScores[GetLevelInfo(LEVELINFO_LEVELNUM)] > 0 )
		{
			setParTime(MapScores[GetLevelInfo(LEVELINFO_LEVELNUM)]/35, 1);
		}
	}
}

// This function applies the multiplier for the given type to amount, and returns the result.
// It handles fixed point conversion and making sure the result is rounded nicely.
// Also, if given a negative amount, it will return it positive and unmultiplied
function int Multiplier (int type, int amount)
{
	if(amount < 0)
	{	return 0 - amount;	}

	int result = amount * RawMultiplier(type);

	if( (result>0.0) && (result<1.0) )	// Make sure our answer is at least one
	{	result = 1;	}
	else
	{	result = result / 1.0;	}	// Make result an integer

	return result;
}

int SkillScaler[5] =	{ 0.6, 0.75, 1.0, 1.5, 2.0 };

// This function returns the multiplier as a fixed point value.
// If the multiplier needs to be tweaked, this is the function to do it in.
function int RawMultiplier (int type)
{
	int result = 1.0;
	switch(type)
	{
	case M_POWERUP:
		result = 1.0*PlayerCount();
		break;
	case M_MEDIC:
	case M_AMMO:
		result = 0.3 + 0.71*PlayerCount();	// Slightly above 1.0 for one player to account for rounding errors
		break;
	case M_ENEMIES:
		result = FixedMul(0.5 + 0.51*PlayerCount(), SkillScaler[GameSkill()]);
		break;
	case M_BOSSHEALTH:
		// 1 player	= 1x
		// 8 players	= 3x
		// result = 2/7 * playercount + 5/7
		// result = (2*playercount + 5) / 7
		result = ( 2.0*PlayerCount() + 5.0 ) / 7; // Dividing fixed by int yields a fixed
		break;
	default:
		PrintBold(s:"Error: Function Multiplier() called without a valid type argument");
	}
	return result;
}

function void ScreenFadeOut (int inTime) // time is a float value in seconds
{
	SetFont("BLACK");
	SetHudSize(4,4,1);
	HudMessageBold (s:"a"; HUDMSG_FADEINOUT, FadeOutHID, CR_UNTRANSLATED,
			2.0, 2.0,
			1000.0, inTime, 1.0);	// holdTime, inTime, outTime
	SetHudSize(0,0,0);
}

/* ******* **
** Scripts **
** ******* */

script S_MISSION_COMPLETE (void)
{
	Setup();

	if(GameActive)
	{
		
		if(MapScores[GetLevelInfo(LEVELINFO_LEVELNUM)]==0)
			{
				MapScores[GetLevelInfo(LEVELINFO_LEVELNUM)] = -1;
			}
		//GetEndBonuses();
		GameActive = FALSE;

		StopTimer();

		SetMusic("");
		SetActorProperty(HateEpicenter, APROP_Invulnerable, true);
			// Core is Invulnerable, so it can't be destroyed anymore after winning.
		AmbientSound("TB_VICT", 127);

		SetHudSize (640, 480, 1);

		SetFont("HUDMC");
		HudMessageBold (s:"a"; HUDMSG_FADEINOUT, EndMessageHID, CR_UNTRANSLATED,
			320.0, 240.0,
			4.0, 0.5, 0.5);

		delay(3*35);

		ScreenFadeOut(3.0);

		delay(5*35);

	
		if (MapScores[GetLevelInfo(LEVELINFO_LEVELNUM)]>0)
		{
			SetPlayerProperty(1, true, PROP_TOTALLYFROZEN);

			SetHudSize (480, 360, 1);
			SetFont("BigFont");

			{	// Ideally, these brackets should create a new scope, but ACC doesn't support this :(
				int lseconds = (levelRunningTime/35)%60;
				int lminutes = (levelRunningTime/35)/60;

				HudMessageBold(s:"Time Taken: \cg",s:zerospace(lminutes,2),d:lminutes,s:":",s:zerospace(lseconds,2),d:lseconds;
					HUDMSG_FADEINOUT|HUDMSG_LOG, 0, CR_WHITE, 240.0, 120.1, 7.0, 1.5, 1.0);
			}

			//delay(35/2);

			bool newRecord = false;

			{
				int mseconds = MapParTime[GetLevelInfo(LEVELINFO_LEVELNUM)]%60;
				int mminutes = MapParTime[GetLevelInfo(LEVELINFO_LEVELNUM)]/60;

				HudMessageBold(s:"Par Time: \cg",s:zerospace(mminutes,2),d:mminutes,s:":",s:zerospace(mseconds,2),d:mseconds;
					HUDMSG_FADEINOUT|HUDMSG_LOG, 0, CR_WHITE, 240.0, 160.1, 7.0, 1.5, 1.0);

				newRecord = ( levelRunningTime < MapParTime[GetLevelInfo(LEVELINFO_LEVELNUM)]*35 );
			}

			if(MapScores[GetLevelInfo(LEVELINFO_LEVELNUM)] > 0)
			{
				int bseconds = (MapScores[GetLevelInfo(LEVELINFO_LEVELNUM)]/35)%60;
				int bminutes = (MapScores[GetLevelInfo(LEVELINFO_LEVELNUM)]/35)/60;

				HudMessageBold(s:"Best Time: \cg",s:zerospace(bminutes,2),d:bminutes,s:":",s:zerospace(bseconds,2),d:bseconds;
					HUDMSG_FADEINOUT|HUDMSG_LOG, 0, CR_WHITE, 240.0, 200.1, 7.0, 1.5, 1.0);

				newRecord = ( levelRunningTime < MapScores[GetLevelInfo(LEVELINFO_LEVELNUM)] );
			}

			if(newRecord)
			{
				MapScores[GetLevelInfo(LEVELINFO_LEVELNUM)] = levelRunningTime;

				HudMessageBold(s:"\cdA new record!";
					HUDMSG_FADEINOUT|HUDMSG_LOG, 0, CR_UNTRANSLATED, 240.0, 240.1, 6.0, 2.5, 1.0);
			} else {
				HudMessageBold(s:"\ciRecord not beaten";
					HUDMSG_FADEINOUT|HUDMSG_LOG, 0, CR_UNTRANSLATED, 240.0, 240.1, 6.0, 2.5, 1.0);
			}

			delay(10*35);

			SetPlayerProperty(1, false, PROP_TOTALLYFROZEN);
		}

		GiveMapRewards();

		Exit_Normal(ExitPos);
	}
}

// Placed on lines so living, enemy monsters can cause the mission to fail
script S_M_ACTIVATE_FAIL (void)
{
	// Don't continue if the activator is a dead monster or friendly
	if(	GameActive &&
		( (ClassifyActor(0) & ( ACTOR_MONSTER | ACTOR_DEAD )) != ( ACTOR_MONSTER | ACTOR_DEAD ) ) &&
		( !GetActorProperty(0, APROP_Friendly) ) )
	{
		ACS_Execute(S_MISSION_FAIL, 0, 0);
	}
}

script S_MISSION_FAIL (void)
{
	if(!GameActive)
	{	Terminate;	}

	GameActive = FALSE;

	StopTimer();

	SetMusic("");
	AmbientSound("TB_LOST", 127);

	SetHudSize (640, 480, 1);

	SetFont("HUDGO");
	HudMessageBold (s:"a"; HUDMSG_FADEINOUT, EndMessageHID, CR_UNTRANSLATED,
			320.0, 240.0,
			5.0, 0.5, 0.5);

	ScreenFadeOut( 5.0 );

	delay(35 * 3);
	AmbientSound("J_FAIL", 127); // "You have failed to defend your Stronghold"
	delay(35 * 4);

	Exit_Normal(ExitPos);
}

script S_DEADLINE (void)
{
	if(BreakTime == -1)	// Default setting
	{	BreakTime	= 3*35;	}

	StartTimer();

	int ThisWave;
	for(ThisWave=1; ThisWave<=Waves; ThisWave++)
	{
		if(!F_CallNextWave)
			CallWave(ThisWave);
		
		while(! WaveFinishCallback[ThisWave] && !F_CallNextWave)
		{	delay(1);	}

		if(ThisWave==Waves)
		{	StopTimer();	}	// Stop timer soon as last wave is over

		if (F_CallNextWave) 
		{  
			//WaveFinishCallback[ThisWave] = TRUE;
			delay(BreakTime);
			if (F_CallNextWave > 1 && (F_CallNextWave - 1) + ThisWave < Waves)
				ThisWave += (F_CallNextWave - 1); 
			Else If (F_CallNextWave > 1 && (F_CallNextWave - 1) + ThisWave >= Waves)
				{
					acs_execute(910,0,0,0,0);
					terminate;
				}
			if (F_CallNextWave < 0 && (F_CallNextWave) + ThisWave >= 1)
				ThisWave += (F_CallNextWave);
			Else If (F_CallNextWave < 0 && (F_CallNextWave) + ThisWave <= 0)
				ThisWave = 0;
			
			F_CallNextWave = 0; 
			
		}
		else
		Delay(5*35);
	}

	StopTimer();

	// Call the last wave, to spawn maybe extra items, do a special map action, or a message, but don't wait on it.
	CallWave(ThisWave);

	Delay(3*35);	// Wait a moment before ending the script, so the ending screen or whatever the map does next
			// isn't too immediate.
}

script S_CORE_GAME (void)
{
	GameMode = GAME_CORE;
	HealthWatch(	HateEpicenter,
			GetActorProperty(HateEpicenter, APROP_SpawnHealth),
			S_MISSION_FAIL,
			WATCH_CORE	);

	ACS_ExecuteWait(S_DEADLINE,0,0);
}

#define	MAX_WATCH_SLOTS	3
int WatchSlot[MAX_WATCH_SLOTS];

function int reserveOpenWatchSlot (void)
{
	for(int i=0; i<MAX_WATCH_SLOTS; i++)
	{
		if(!WatchSlot[i])
		{
			WatchSlot[i] = TRUE;
			return i;
		}
	}
	PrintBold(s:"Error: Out of Watch Slots");
	return 0;
}

function void ClearWatchHUD (int offset)
{
	HudMessageBold(s:""; HUDMSG_PLAIN, CoreLabelHID+CoreHIDOffset*offset,	CR_UNTRANSLATED, 0.0, 0.0, 0.1);
	HudMessageBold(s:""; HUDMSG_PLAIN, CoreGraphicHID+CoreHIDOffset*offset,	CR_UNTRANSLATED, 0.0, 0.0, 0.1);
	HudMessageBold(s:""; HUDMSG_PLAIN, CorePercentHID+CoreHIDOffset*offset,	CR_UNTRANSLATED, 0.0, 0.0, 0.1);

	WatchSlot[offset] = FALSE;
}

function void WatchTitle(int type, int offset)
{
	str name = "Unknown";

	// Names
	switch(type)
	{
	case WATCH_CORE:
		name = "Core";
		break;
	case WATCH_BOSS:
		name = "Boss";
		break;
	case WATCH_BOSSTARGET:
		name = "Boss Target";
		break;
	case WATCH_LIMIT:
		name = "Limit";
		break;
	case WATCH_GOALS:
		name = "Goals";
		break;
	}

	str graphic = "HUDGFXC";

	switch(type)
	{
	case WATCH_GOALS:
		graphic = "HUDTRGT";
		break;
	}

	SetHudSize(640, 430, 0);
	SetFont("SMALLFONT");
	HudMessageBold(s:name; HUDMSG_PLAIN, CoreLabelHID+CoreHIDOffset*offset, CR_GRAY, CoreLabelX, CoreLabelY+CoreYOffset*offset, 0.0);
	SetFont(graphic);
	HudMessageBold(s:"a"; HUDMSG_PLAIN, CoreGraphicHID+CoreHIDOffset*offset, CR_UNTRANSLATED, CoreGraphicX, CoreGraphicY+CoreYOffset*offset, 0.0);

	SetHudSize(0, 0, 0);	// Reset this so the calling script is back to the defaults
}

function void WatchAnnounceChange(int type, int value, int maxnum)
{
	// Jump out now if we know we're doing nothing.
	switch(type)
	{
	case WATCH_CORE:
	case WATCH_BOSS:
	case WATCH_GOALS:	// Handled by S_GOAL_DESTROYED
		return;
	}

	SetFont("SMALLFONT");

	switch(type)
	{
	case WATCH_BOSSTARGET:
		if(value == maxnum)	// Don't announce when we have all cores still left
		{	break;	}

		AmbientSound("amb16",127);
		if(value == 0)
		{
			PrintBold(s:"\cgAll \cccores have been destroyed!");
		}
		else
		{
			PrintBold(s:"\cg",d:value,s:" / ",d:maxnum,s:" \cccores remaining!");
		}
		break;
	case WATCH_LIMIT:
		if(value > 0)
		{
			PrintBold(s:"\cg",d:value,s:" / ",d:maxnum,s:"\cc monsters have hit the limit!");
		}
		break;
	}

//	SetHudSize(WatchHudSizeX, WatchHudSizeY, 0);
	SetFont(WatchHudFont);
}

// Scripts can't have strings passed to them, so handle the string in a wrapper function
function void HealthWatch(int tid, int maxhealth, int deadscript, int type)
{
	int offset = reserveOpenWatchSlot();

	WatchTitle(type, offset);

	ACS_Execute( S_HEALTH_WATCH, 0, tid, maxhealth, deadscript | (offset<<12) | (type<<16) );
}

script S_HEALTH_WATCH (int tid, int maxhealth, int data)
{
	int deadscript	= (data&0x00000FFF);
	int offset	= (data&0x0000F000)>>12;
	int type	= (data&0x000F0000)>>16;

	int color;
	int curpercent;

	SetHudSize(WatchHudSizeX, WatchHudSizeY, 0);
	SetFont(WatchHudFont);

	while(true)
	{
		curpercent = GetPercent( GetActorProperty(tid, APROP_Health), maxhealth );

		color = GetPercentColor( curpercent );

		HudMessageBold(d:curpercent,s:"%";
			HUDMSG_PLAIN, CorePercentHID+CoreHIDOffset*offset, color, CorePercentX, CorePercentY+CoreYOffset*offset, 0.0);

		WatchAnnounceChange(type,curpercent,100);

		if(GetActorProperty(tid, APROP_HEALTH) <= 0)
		{	break;	}

		delay(15);

		// We don't need to update the screen until the health changes enough to change the
		// actual percent display value	
		while( GetPercent( GetActorProperty(tid, APROP_HEALTH), maxhealth ) == curpercent )
		{	delay(5);	}
	}

	delay(45);

	if(deadscript)
	{	ACS_Execute(deadscript, 0, 0);	}

	delay(5*35 + 10); // A bit over 5 seconds

	ClearWatchHUD(offset);
}

// Scripts can't have strings passed to them, so handle the string in a wrapper function
function void CountWatch(int tid, int maxnum, int deadscript, int type)
{
	int offset = reserveOpenWatchSlot();

	WatchTitle(type, offset);

	ACS_Execute( S_COUNT_WATCH, 0, tid, maxnum, deadscript | (offset<<12) | (type<<16) );
}

script S_COUNT_WATCH (int tid, int maxnum, int data)
{
	int deadscript	= (data&0x00000FFF);
	int offset	= (data&0x0000F000)>>12;
	int type	= (data&0x000F0000)>>16;

	int color;
	int curcount;

	SetHudSize(WatchHudSizeX, WatchHudSizeY, 0);
	SetFont(WatchHudFont);

	while(true)
	{
		curcount = ThingCount( T_NONE, tid );

		color = GetPercentColor( GetPercent( curcount, maxnum ) );

		HudMessageBold(d:curcount,s:"/",d:maxnum;
			HUDMSG_PLAIN, CorePercentHID+CoreHIDOffset*offset, color, CorePercentX, CorePercentY+CoreYOffset*offset, 0.0);

		WatchAnnounceChange(type,curcount,maxnum);

		if(curcount <= 0)
		{	break;	}

		delay(5);

		// We don't need to update the screen until the count changes
		while( ThingCount( T_NONE, tid ) == curcount )
		{	delay(5);	}
	}

	delay(45);

	if(deadscript)
	{	ACS_Execute(deadscript, 0, 0);	}

	delay(5*35 + 10); // A bit over 5 seconds

	ClearWatchHUD(offset);
}

// Scripts can't have strings passed to them, so handle the string in a wrapper function
function void LimitWatch(int maxnum, int deadscript, int type)
{
	int offset = reserveOpenWatchSlot();

	WatchTitle(type, offset);

	ACS_Execute( S_LIMIT_WATCH, 0, maxnum, deadscript | (offset<<12) | (type<<16) );
}

script S_LIMIT_WATCH (int maxnum, int data)
{
	int deadscript	= (data&0x00000FFF);
	int offset	= (data&0x0000F000)>>12;
	int type	= (data&0x000F0000)>>16;

	int color;
	int curcount;

	SetHudSize(WatchHudSizeX, WatchHudSizeY, 0);
	SetFont(WatchHudFont);

	while(true)
	{
		curcount = LimitCounter;

		// We want the color to match the percent of limits not reached yet
		color = GetPercentColor( GetPercent( maxnum-curcount, maxnum ) );

		HudMessageBold(d:curcount,s:"\n/",d:maxnum;
			HUDMSG_PLAIN, CorePercentHID+CoreHIDOffset*offset, color, CorePercentX, CorePercentY+CoreYOffset*offset, 0.0);

		WatchAnnounceChange(type,curcount,maxnum);

		if(curcount >= maxnum)
		{	break;	}

		// We don't need to update the screen until the count changes
		while( LimitCounter == curcount )
		{	delay(1);	}
	}

	delay(45);

	if(deadscript)
	{	ACS_Execute(deadscript, 0, 0);	}

	delay(5*35 + 10); // A bit over 5 seconds

	ClearWatchHUD(offset);
}

script S_MILESTONE (void)
{
	GameMode = GAME_MILESTONE;
	ACS_ExecuteWait(S_DEADLINE,0,0);
}

script S_LIMIT_GAME (void)
{
	GameMode = GAME_LIMIT;
	if(BreakTime == -1)	// Default setting
	{	BreakTime	= 12*35;	}

	LimitCounter = 0;

	LimitWatch(	LimitMax,
			S_MISSION_FAIL,
			WATCH_LIMIT	);

	ACS_ExecuteWait(S_DEADLINE,0,0);
}

script S_OVERMIND (void)
{
	// Set up boss monster
	GameMode = GAME_OVERMIND;
	int OriginalHealth = Multiplier( M_BOSSHEALTH, BossHealth );
	SetActorProperty(BossMonster, APROP_Health, OriginalHealth);
	Thing_Hate(BossMonster, BossTarget, 2);
	Thing_Activate(BossMonster);
	
	NumberOfTargets = ThingCount( T_NONE, BossTarget );

	HealthWatch(	BossMonster,
			OriginalHealth,
			S_OVERMIND_COMPLETE,
			WATCH_BOSS	);

	CountWatch(	BossTarget,
			ThingCount( T_NONE, BossTarget ),
			S_MISSION_FAIL,
			WATCH_BOSSTARGET	);

	DisableAnnounceWaveDone = TRUE;

	StartTimer();

	int ThisWave;
	for(ThisWave=1; ThisWave<=Waves; ThisWave++)
	{
		CallWave(ThisWave);

		Delay(4*35);

		while( GetActorProperty(BossMonster, APROP_Health) > OriginalHealth * (Waves-ThisWave) / Waves )
		{	delay(5);	}
	}

	Suspend;
	// Gameplay control continues in S_OVERMIND_COMPLETE.
	// This script left suspended so that it can be terminated later, and level control continues.
}

script S_OVERMIND_COMPLETE (void)
{
	if(MonstersTid)
	{	Thing_Damage(MonstersTid, INT_MAX);	}

	GameFinishing = TRUE;	// Stop all running waves
	ACS_Suspend( S_OVERMIND, 0 );	// Pause Overmind script so it doesn't continue launching waves

	StopTimer();

	// Call the last wave, to spawn maybe extra items, do a special map action, or a message, but don't wait on it.
	CallWave(Waves+1);

	Delay(3*35);	// Wait a moment before ending the script, so the ending screen or whatever the map does next
			// isn't too immediate.

	ACS_Terminate( S_OVERMIND, 0 );
}

script S_MILE_MARKER (int mile)
{
	if(mile==0)
	{
		// There's no reason to call this script with mile==0, so this was most likely
		// a mapping error.
		PrintBold(s:"Error: S_MILE_MARKER script #250 called with argument 0");
	}

	// Don't continue if the activator is a dead monster or friendly
	if(	(mile>CurrentMile) &&
		( (ClassifyActor(0) & ( ACTOR_MONSTER | ACTOR_DEAD )) != ( ACTOR_MONSTER | ACTOR_DEAD ) ) &&
		( !GetActorProperty(0, APROP_Friendly) ) )
	{
		AnnounceNewMile(mile);

		if(MileScript)
		{
			// A milestone might've possibly been skipped. Bad level design? Maybe.
			// But let's execute the MileScript for each time
			for(int i=CurrentMile+1; i<=mile; i++)
			{
				// Delay between calling scripts so that they're called in the right order.
				// No need to delay on the first iteration.
				if(i != CurrentMile+1)
				{	delay(1);	}

				ACS_ExecuteAlways(MileScript, 0, i);
			}
		}

		CurrentMile = mile;
	}
}

script S_LIMIT_MARKER (void)
{
	// Don't continue if the activator is a dead monster or friendly
	if(	GameActive &&
		( (ClassifyActor(0) & ACTOR_MONSTER) == ACTOR_MONSTER ) &&
		( (ClassifyActor(0) & ( ACTOR_MONSTER | ACTOR_DEAD )) != ( ACTOR_MONSTER | ACTOR_DEAD ) ) &&
		( !GetActorProperty(0, APROP_Friendly) ) )
	{
		Spawn("TeleportFog",
			GetActorX(0)+random(-8.0,8.0),
			GetActorY(0)+random(-8.0,8.0),
			GetActorZ(0)+random(8.0,16.0) );

		Thing_Remove(0);

		LimitCounter++;
	}
}

script S_GOAL_GAME (void)
{
	GameMode = GAME_GOAL;
	CountWatch(	HateEpicenter,
			ThingCount( T_NONE, HateEpicenter ),
			S_MISSION_FAIL,
			WATCH_GOALS	);

	NumberOfTargets = ThingCount( T_NONE, HateEpicenter );
	
	ACS_ExecuteWait(S_DEADLINE,0,0);
}

script S_GOAL_DESTROYED (int color)
{
	switch(color)
	{
	case GOAL_RED:
		printbold(s:"\cARed \cGGoal has been destroyed!");
		break;
	case GOAL_ORANGE:
		printbold(s:"\cIOrange \cGGoal has been destroyed!");
		break;
	case GOAL_BLUE:
		printbold(s:"\cHBlue \cGGoal has been destroyed!");
		break;
	case GOAL_GREEN:
		printbold(s:"\cDGreen \cGGoal has been destroyed!");
		break;
	case GOAL_WHITE:
		printbold(s:"\cJWhite \cGGoal has been destroyed!");
		break;
	default:
		printbold(s:"Error: Invalid goal color");
		Terminate;
	}

	if(GoalScript)
	{	ACS_ExecuteAlways(GoalScript, 0, ThingCount(T_NONE, HateEpicenter) );	}
}

function int GetPercent (int value, int maxval)
{
	int candidate = (value*100)/maxval;

	// Check if we rounded down to zero; return at least 1 if we did
	if( (candidate==0) && (value>0) )
	{
		return 1;
	}

	// Don't give a negative percent
	return max( candidate, 0 );
}

function int GetPercentColor (int percent)
{
	if (percent > 75)			{	return CR_GREEN;	}
	else if (percent <= 75 && percent > 50)	{	return CR_YELLOW;	}
	else if (percent <= 50 && percent > 25)	{	return CR_ORANGE;	}
//	else if (percent <= 25)			{	return CR_RED;	}

	return CR_RED;
}

// Don't call this script directly, use CallWave() function
// This is so that the WaveFinishCallback can be set to false synchronously, so that way it can be
// waited on.
script S_CALLWAVE (int wave)
{
	Setup();

	if(EventScript)
	{	ACS_ExecuteAlways(EventScript,0,wave);	}

	Delay(3);	// Not only a slight pause, but lets the EventScript make changes first.
			// Called even when there isn't an EventScript just for consistency.

	SpawnStuff(wave);

	// Give player time to grab items before wave messages and enemies spawn.
	Delay( BreakTime );

	AnnounceNewWave(wave);

	ACS_ExecuteAlways(S_MESSAGE,0,wave);

	Delay( 5*35 );


	// Monster spawning
	if( ZoneDataUsed )
	{
		int cacheUsed = 0;

		int cacheId = EnemySpawnCacheLoc++;
		if(EnemySpawnCacheLoc == ENEMY_SPAWN_CACHE_LEN)
		{	EnemySpawnCacheLoc = 0;	}

		// Load all monsters to spawn this wave into EnemySpawnCache[cacheId]
		for(int i=0; i<EnemyEntriesUsed; i++)
		{
			if(EnemyEntries[i][0]==wave)
			{
				// Every monster instance is given its own slot inside the EnemySpawnCache
				for(int j=0; j<Multiplier( M_ENEMIES, EnemyEntries[i][2] ); j++)
				{	
					EnemySpawnCache[cacheId][cacheUsed++] = EnemyEntries[i][1];
				}
			}
		}

		if(cacheUsed>MAX_ENEMIES_PER_WAVE)
		{
			PrintBold(s:"Error: Wave ",d:wave,s:" exceeds MAX_ENEMIES_PER_WAVE");
			cacheUsed = MAX_ENEMIES_PER_WAVE;
		}

		int cacheUsedCounter = cacheUsed;
		int curEnemyId;
		int curEnemyName;
		int spawned;
		int tid = NewTempTid();
		int zone = PickZone(CurrentMile);

		// While we still have monsters left to spawn, spawn them
		while(cacheUsedCounter > 0)
		{
			spawned = FALSE;
			curEnemyId = random(0,cacheUsedCounter-1);
			for(int k=0; k <= curEnemyId; k++)
			{
				// Every time we hit a null entry, skip over it and pretend we didn't
				// see it.
				//	Q: Why not just pick the entry first, check if it's null,
				//	and then just go forward to the next non-null one?
				//	A: If we did that, then a monster entry proceeding a series of
				//	nulls would have a higher chance of being picked than others.

				if(EnemySpawnCache[cacheId][k]==str_null)
				{	curEnemyId++;	}
			}

		// We want to repeatedly try to spawn the monster until it has spawned.
		// Note that while attempting to spawn the monster, no monsters spawn
		// at other zones. Not sure if this is worth fixing or to be considered
		// a gameplay "feature".
		//	Possible solutions:
		//	* If monster doesn't spawn, try to spawn at next zone.
		//		- One zone can have (possibly significantly) more monsters spawned.
		//	* Split up EnemySpawnCache between zones, and give each zone its own
		//	thread/script to control its spawning.
		//		- A zone can take longer to spawn its monsters than other zones.
			while(! spawned)
			{
				if( (!GameActive) || GameFinishing || F_CallNextWave)
				{	Terminate;	}

				spawned = Spawn( EnemySpawnCache[cacheId][curEnemyId],
					RandomCoords(zone,0), RandomCoords(zone,1),
					RandomCoords(zone,2), tid, ZoneData[zone][6]);

				if( spawned )
				{
					SpawnSpot("TeleportFog", tid);

					// If the zone has no second patrol, or random==TRUE, pick first patrol
					if((!ZoneData[zone][11]) || (random(0,1)))
					{
						GiveDefaults(tid, ZoneData[zone][5], HateEpicenter);
					} else {
						GiveDefaults(tid, ZoneData[zone][11], HateEpicenter);
					}

					Thing_ChangeTID(tid, MonstersTid);
				}

				delay( tics_between_monster_spawns );
			}

			// Remove the enemy just spawned from the cache
			EnemySpawnCache[cacheId][curEnemyId] = str_null;

			cacheUsedCounter--;

			zone = GetNextZone(zone, CurrentMile); // Cycle through each zone
		}
		FreeTempTid(tid);
	}

	// Wait until all monsters are dead or if the next level cheat is called
	while(GetLevelInfo(LEVELINFO_TOTAL_MONSTERS)-GetLevelInfo(LEVELINFO_KILLED_MONSTERS) > 0)
	{	delay(1);	}

	AnnounceWaveDone(wave);

	WaveFinished(wave);
}

function void WaveFinished(int wave)
{
	if( (wave>0) && (wave<MAX_NORMAL_WAVES))
		WaveFinishCallback[wave]	= TRUE;
}

// Pick the first zone that is for the current mile
function int PickZone (int mile)
{
	int candidate = -1;

	for(int i=0; i<ZoneDataUsed; i++)
	{
		if(ZoneData[i][10]==mile)
		{
			candidate = i;
			break;
		}
	}

	if(candidate == -1)
	{
		PrintBold(s:"Error: No zones for mile ",d:mile);
		return 0;
	}
	return candidate;
}

function int GetNextZone (int curZone, int mile)
{
	int candidate = -1;

	for(int i=curZone+1; i<ZoneDataUsed; i++)
	{
		if(ZoneData[i][10]==mile)
		{
			candidate = i;
			break;
		}
	}

	if(candidate == -1)
	{
		// We reached the end without finding a zone, so pick one from the beginning
		return PickZone(mile);
	}
	return candidate;
}

// Always use ACS_ExecuteAlways to call this script, and it will queue up messages if needed
script S_MESSAGE (int wave)
{
	Setup();

	int ticketNumber = msgEndOfLine++;
	while(ticketNumber != msgCurTurn)
	{	delay(1);	}

	int curFace;

	for(int i=0; i<RadioMessagesUsed; i++)
	{
		if(RadioMessages[i][0]==wave)
		{
			if(RadioName[ RadioMessages[i][1] ]==RADIO_RANDOM)
			{	curFace = randomRadioFace;	}
			else
			{	curFace = RadioName[i] - 1;	}

			// Show Mug shot of speaker
			SetHudSize(512,384,1);
			SetFont(RadioData[curFace][1]);
			HudmessageBold(s:"A"; HUDMSG_PLAIN, RadioMugHID, -1, 25.1, 250.0, 4.5);

			// Show his name
			SetFont("ConFont");
			HudmessageBold(s:RadioData[curFace][0]; HUDMSG_PLAIN, RadioNameHID, CR_WHITE, 70.1, 250.0, 4.5);

			// Show message text
			SetHudSize(640,480,1);
			HudmessageBold(s:RadioMessages[i][2]; HUDMSG_TYPEON|HUDMSG_LOG, RadioTextHID, CR_GOLD, 44.1, 360.1, 99.0, 0.035, 0.5);

			// Show the dialog box
			SetFont("DIALOGBX");
			HudmessageBold(s:"A"; HUDMSG_PLAIN, RadioBoxHID, CR_WHITE, 32.1, 384.0, 4.5);

			delay((4.5*35)/1.0);
		}
	}
	HudmessageBold(s:""; HUDMSG_PLAIN, RadioTextHID, CR_UNTRANSLATED, 0.0, 0.0, 0.1);

	delay((0.7*35)/1.0); // Wait about half a second before letting any queued messages go to avoid a quick flicker

	msgCurTurn++;	// Let next message in line go
}

script S_ENTER ENTER
{
	Thing_ChangeTID(0, PLAYER_TID_START + PlayerNumber());

	highestPlayerNumber = max( PlayerNumber(), highestPlayerNumber );

	// Weapons
	TakeInventory("DEChainsaw", INT_MAX);
	TakeInventory("AutoShotgun", INT_MAX);
	TakeInventory("DESuperShotgun", INT_MAX);
	TakeInventory("SuperShotgun", INT_MAX);
	TakeInventory("DEShotgun", INT_MAX);
	TakeInventory("Shotgun", INT_MAX);
	TakeInventory("DEChaingun", INT_MAX);
	TakeInventory("Chaingun", INT_MAX);
	TakeInventory("HRL", INT_MAX);
	TakeInventory("LandMineLayer", INT_MAX);
	TakeInventory("DERLauncher", INT_MAX);
	TakeInventory("RocketLauncher", INT_MAX);
	TakeInventory("Railgun", INT_MAX);
	TakeInventory("StunnerRifle", INT_MAX);
	TakeInventory("DEPlasmaRifle", INT_MAX);
	TakeInventory("PlasmaRifle", INT_MAX);
	TakeInventory("DEBFG9000", INT_MAX);
	TakeInventory("BFG9000", INT_MAX);
	TakeInventory("PyroCannon", INT_MAX);
	TakeInventory("Flamer", INT_MAX);
	TakeInventory("Devastator", INT_MAX);
	TakeInventory("Repeater", INT_MAX);
	TakeInventory("Pistol", INT_MAX);
	TakeInventory("DEPistol", INT_MAX);
	TakeInventory("DEFist", INT_MAX);
	TakeInventory("DEShrinkFist", INT_MAX);
	TakeInventory("BruiserWeapon", INT_MAX);
	// Trackers (for SBARINFO)
	TakeInventory("JailTracker", INT_MAX);
	TakeInventory("JailTrackerAllPlayers", INT_MAX);
	TakeInventory("HUDCredits", INT_MAX);
	TakeInventory("ThrustDummy", INT_MAX);
	// Ammo
	TakeInventory("Clip", INT_MAX);
	TakeInventory("Cell", INT_MAX);
	TakeInventory("RocketAmmo", INT_MAX);
	TakeInventory("Gas", INT_MAX);
	TakeInventory("Shell", INT_MAX);
	TakeInventory("Mines", INT_MAX);

	UpdateJailTrackers();
	UpdateWaveTrackers();
	UpdateTime();

	// We need to reset the player's speed just in case he ended a map with it modified.
	// (The thrusting script can modify it.)
	SetActorProperty(0, APROP_Speed, 1.0);

	if(BriefActive)
	{
		SetPlayerProperty (1, true, PROP_TOTALLYFROZEN);
	} else {
		SetPlayerProperty (0, false, PROP_TOTALLYFROZEN);
	}

	delay(1);

	// Pause this script while Brief is active, so that way the player gets his gun
	// after it's finished. The Briefing scripts handle unfreezing the player.
	while(BriefNoGun)
	{	delay(1);	}

	if( MapIsMenu() )
	{
		ACS_ExecuteAlways(S_HUD_COINS, 0, 0);
		GiveInventory("DEShrinkFist", 1);
	//	GiveInventory("HUDCredits", 1);	// Enables intermission hud parts

		while( true )
		{
			delay(35);	// Placed at beginning of loop, so that there is no chance this
					// becomes a runaway script

			if(!CheckWeapon("DEShrinkFist"))
			{
				Print(s:"You're lucky you didn't get shot for holding a loaded weapon here!");
				restart;	// Clear inventory again
			}
		}
	}

	switch (GameSkill())
	{
	case SKILL_VERY_EASY:
	case SKILL_VERY_HARD:
		GiveInventory("Clip", 25);
		break;
	default:
		GiveInventory("Clip", 50);
	}

	GiveInventory("DEFist", 1);
	GiveInventory("DEPistol", 1);

	SetWeapon("DEPistol");
}

// Displays amount of user's coins at top of screen in the intermap
script S_HUD_COINS (void)
{
	sethudsize(640,480,0);
	setfont("HUDINT");
	HudMessage(s:"a"; HUDMSG_PLAIN, MoneyBarHID, CR_UNTRANSLATED, 320.0, 240.0, 0.0);
	SetFont("SMALLFONT");

	int lastCoinAmount = -1;
	while(true)
	{
		if( CheckInventory("CoinItem") != lastCoinAmount )
		{
			HudMessage(s:"\cGCredits\n$",i:CheckInventory("CoinItem");
				HUDMSG_PLAIN, MoneyCountHID, CR_UNTRANSLATED, 570.0, 30.0, 0.0);
			lastCoinAmount = CheckInventory("CoinItem");
		}
		delay(25);
	}
}

// If player has zero lives, alert him, then send to jail
// For if a player drops his last life while alive (respawn script handles spawning without a life)
script S_LIVE_WATCH ENTER
{
	while(true)
	{
		// If the player has a JailTracker, then the respawn script is handling this, or he's in jail already
		if ((CheckInventory("LifeItem") == 0) && (CheckInventory("JailTracker") == 0) && (CheckInventory("Health") > 0))
		{
			if(PrisonSpot)
			{
				Print(s:"Warning: You are out of lives!\n\nYou are about to be teleported to jail");

				delay(35*6);	// Give the player a moment, so he can pick the life back
						// up if he accidentally dropped it.

				ACS_ExecuteAlways(S_RESPAWN, 0, 0);
			} else {
				Print(s:"Warning: You are out of lives!\n\nBut this map doesn't use a Jail, so you're free for now.");

				delay(20*35); // Wait a bit so that this message isn't constant
			}
		}
		delay(5);
	}
}

// Player loses a life
script S_DEATH DEATH
{
	TakeInventory("ThrustDummy", 1);

	if ( GameActive && (!MapIsMenu()) )
	{
		if (CheckInventory("LifeItem") > 0)
		{
			TakeInventory("LifeItem", 1);
		}
	}
}

script S_RESPAWN RESPAWN
{
	// Make sure our old dead body doesn't still have our tid
	Thing_ChangeTid(PLAYER_TID_START+PlayerNumber(),0);
	Thing_ChangeTid(0,PLAYER_TID_START+PlayerNumber());

	TakeInventory("JailTracker", INT_MAX);
	TakeInventory("ThrustDummy", 1);

	if (CheckInventory("LifeItem") == 0)
	{
		if(PrisonSpot)
		{
			Print(s:"You are out of lives!");

			GiveInventory("JailTracker", 1);
			UpdateJailTrackers();

			While(! Teleport_NoFog(PrisonSpot, true, 0))
			{	delay(5);	}
		}
	} else {
		UpdateJailTrackers();
	}

	SetActorProperty(0, APROP_Speed, 1.0);

	if(BriefActive)
	{
		SetPlayerProperty (1, true, PROP_TOTALLYFROZEN);
	} else {
		SetPlayerProperty (0, false, PROP_TOTALLYFROZEN);
	}
}

script S_DISCONNECT (int gone) DISCONNECT
{
	UpdateJailTrackers();
}

function void SetJailTeleporter (bool setting)
{
	jailTeleporter = setting;
	if( jailTeleporter )
	{
		thing_deactivate(TeleporterOffLight);	// Dynamic Lights
		thing_activate(TeleporterOnLight);
		setlinetexture(PrisonTeleporterLineId, SIDE_FRONT, TEXTURE_MIDDLE, TeleporterOnTexture);
	} else {
		thing_activate(TeleporterOffLight);
		thing_deactivate(TeleporterOnLight);
		setlinetexture(PrisonTeleporterLineId, SIDE_FRONT, TEXTURE_MIDDLE, TeleporterOffTexture);
	}
}

script S_JAIL_LEAVE (int angle)
{
	if( jailTeleporter )
	{
		if( Teleport(PrisonExit, 0, 0) )
		{
			SetJailTeleporter( FALSE );
			TakeInventory("JailTracker", 1);
			GiveInventory("LifeItem", 1);
			UpdateJailTrackers();
		} else {
			ThrustThing(angle, 30, 0);
			Print(s:"Teleporter blocked, try again");
		}
	} else {
		ThrustThing(angle, 30, 0);
		Print(s:"Teleporter is off");
	}
}

script S_JAIL_GIVEUP (void)
{
	if(CheckInventory("JailTrackerAllPlayers")==PlayerCount())
	{
		Exit_Normal(ExitPos);
	} else {
		Print(s:"\cGThere are still \cCplayers \cGon the battlefield!");
		if(PrisonExitThrustAngle==-1)
		{
			ACS_ExecuteAlways(S_G_THRUST,0,0);
		} else {
			ThrustThing(PrisonExitThrustAngle, 30, 0);
		}
	}
}

script S_JAIL_OPEN (void) // This is the script called if you use a LifeItem
{
	// If the player is in jail, he can use his last LifeItem.
	// (...This is impossible to do without cheats.)
	if( (CheckInventory("LifeItem") == 1) && (CheckInventory("JailTracker") == 0) )
	{
		Print(s:"You can't use your last Life to open the jail teleporter!");
		SetResultValue( FALSE );
		Terminate;
	}

	if( PrisonTeleporterLineId )
	{
		if(CheckInventory("JailTrackerAllPlayers") > 0)
		{
			if( ! jailTeleporter )
			{
				PrintBold(s:"Prison Teleporter Opened");
				SetJailTeleporter( TRUE );
				SetResultValue( TRUE );
			} else {
				Print(s:"Prison Teleporter already Open");
				SetResultValue( FALSE );
			}
		} else {
			Print(s:"No players are in jail!");
			SetResultValue( FALSE );
		}
	} else {
		Print(s:"Not usable here!\n\nThis map does not have a jail teleporter to open.");
		SetResultValue( FALSE );
	}
}

script S_BRIEF_MAIN (void)
{
	ACS_Execute(S_BRIEF_ANIMATE,0,0);

	BriefActive = TRUE;
	BriefNoGun = TRUE;
	SetPlayerProperty (1, TRUE, PROP_TOTALLYFROZEN);

	SetFont("BLACK");
	SetHudSize(4,4,0);
	HudMessageBold (s:"a"; HUDMSG_PLAIN, FadeOutHID, CR_UNTRANSLATED,
			2.0, 2.0, 0.0);

	SetHudSize(640, 480, 0);
	SetFont(BriefBackground);
	HudMessageBold (s:"a"; HUDMSG_PLAIN, BriefBGHID, 0, 320.0, 240.0, 0.0);

	SetHudSize(640, 480, 1);
	if(BriefSkippable) {
		SetFont("SMALLFONT");
		HudMessageBold(s:"\cCHit \cG<Use> \cCto skip the introduction";
			HUDMSG_FADEINOUT, BriefSkipHID, 0, 320.0, 465.0, 2.0, 6.0, 2.0);
	}

	if(BriefScript)
	{
		ACS_ExecuteWait(BriefScript,0,0);
	}

	ACS_Execute(S_BRIEF_END, 0, 0);
}

script S_BRIEF_ANIMATE (void)
{
	sethudsize(640, 480, 1);

	setfont("BIGFONT");
	hudmessagebold (s:"\cGInstructor Lt. Carmack:\n";HUDMSG_PLAIN, BriefNameHID, CR_UNTRANSLATED, 345.0, 310.0, 0.0);

	while( TRUE )
	{
		if (!BriefTalking)
		{
			setfont("BRIEFTN2");
			HudMessageBold (s:"a"; HUDMSG_PLAIN, BriefTalkBoxHID, 0, BriefTalkBoxX, BriefTalkBoxY, 0.0);
			delay(25);

			while(!BriefTalking)
			{	delay(1);	}
		} else {
			setfont("BRIEFTN2");
			HudMessageBold (s:"a"; HUDMSG_PLAIN, BriefTalkBoxHID, 0, BriefTalkBoxX, BriefTalkBoxY, 2.0);
			delay(10);
			setfont("BRIEFTY2");
			HudMessageBold (s:"a"; HUDMSG_PLAIN, BriefTalkBoxHID, 0, BriefTalkBoxX, BriefTalkBoxY, 2.0);
			delay(10);
			setfont("BRIEFTY1");
			HudMessageBold (s:"a"; HUDMSG_PLAIN, BriefTalkBoxHID, 0, BriefTalkBoxX, BriefTalkBoxY, 2.0);
			delay(10);
			setfont("BRIEFTY2");
			HudMessageBold (s:"a"; HUDMSG_PLAIN, BriefTalkBoxHID, 0, BriefTalkBoxX, BriefTalkBoxY, 2.0);
			delay(10);
			setfont("BRIEFTN2");
			HudMessageBold (s:"a"; HUDMSG_PLAIN, BriefTalkBoxHID, 0, BriefTalkBoxX, BriefTalkBoxY, 2.0);
			delay(10);
			setfont("BRIEFTY1");
			HudMessageBold (s:"a"; HUDMSG_PLAIN, BriefTalkBoxHID, 0, BriefTalkBoxX, BriefTalkBoxY, 2.0);
			delay(10);
			setfont("BRIEFTY2");
			HudMessageBold (s:"a"; HUDMSG_PLAIN, BriefTalkBoxHID, 0, BriefTalkBoxX, BriefTalkBoxY, 2.0);
			delay(10);
			setfont("BRIEFTY1");
			HudMessageBold (s:"a"; HUDMSG_PLAIN, BriefTalkBoxHID, 0, BriefTalkBoxX, BriefTalkBoxY, 2.0);
			delay(10);
		}
	}
}

script S_BRIEF_END (void)	// #251
{
	if(!BriefActive)
	{	terminate;	}

	ACS_Terminate(S_BRIEF_MAIN,0);
	ACS_Terminate(S_BRIEF_ANIMATE,0);

	if(BriefScript)
	{	ACS_Terminate(BriefScript,0);	}

	HudMessageBold(s:""; HUDMSG_PLAIN, BriefSkipHID, CR_UNTRANSLATED, 0.0, 0.0, 1.0); // Press use to skip
	HudMessageBold(s:""; HUDMSG_PLAIN, BriefExtraHID,	CR_UNTRANSLATED, 0.0, 0.0, 1.0); // Extra pictures
	HudMessageBold(s:""; HUDMSG_PLAIN, BriefHID,		CR_UNTRANSLATED, 0.0, 0.0, 1.0); // Message Text

	SetFont("BLACK");
	SetHudSize(4,4,0);
	HudMessageBold (s:"a"; HUDMSG_FADEOUT, FadeOutHID, CR_UNTRANSLATED,
			2.0, 2.0,
			0.0, 2.0);	// holdTime, outTime

	SetHudSize(640, 480, 0);
	SetFont(BriefBackground);
	HudMessageBold(s:"a"; HUDMSG_FADEOUT, BriefBGHID, 0, 320.0, 240.0, 0.0, 2.0);

	SetHudSize(640, 480, 1);
	SetFont("BIGFONT");
	HudMessageBold(s:"\cGInstructor Lt. Carmack:\n";
		HUDMSG_FADEOUT, BriefNameHID, CR_UNTRANSLATED, 345.0, 310.0, 0.0, 0.5);

	SetFont("BRIEFTN2");
	HudMessageBold (s:"a"; HUDMSG_FADEOUT, BriefTalkBoxHID, 0, BriefTalkBoxX, BriefTalkBoxY, 0.2, 0.5);

	Delay(1*35);
	BriefNoGun = FALSE;	// This lets the player spawn with his gun a bit before unfreezing

	Delay(2*35);
	BriefActive = FALSE;
	SetPlayerProperty (1, FALSE, PROP_TOTALLYFROZEN);
}

// Standard Battlefield Border Script
script S_BF_THRUST (int byte_angle, int focustid) // #254
{
	Print(s:"\cGYou must not leave the battlefield!");
//	ThrustThing(byte_angle, 30, 0, 0);
	ACS_ExecuteAlways(S_G_THRUST,0,focustid);
}

// Generic Thrust script, no angle needed
// Optional arg, focustid: Thrust is towards it
script S_G_THRUST (int focustid)
{
	if((LineSide() == LINE_FRONT) && (CheckInventory("ThrustDummy")==0))
	{
		GiveInventory("ThrustDummy", 1);

		int oldx = GetActorX(0);
		int oldy = GetActorY(0);
		int oldz = GetActorZ(0);

		int oldSpeed = GetActorProperty(0, APROP_Speed);

		SetActorProperty(0, APROP_Speed, 0.0);

		int byte_angle;
		if(!focustid)
		{
			delay(1);

			int newx = GetActorX(0);
			int newy = GetActorY(0);

			// Gets angle of opposite of current momentum
			byte_angle = VectorAngle( oldx-newx, oldy-newy ) >> 8;

			SetActorPosition(0, oldx, oldy, oldz, false);
		} else {
			int focusx = GetActorX(focustid);
			int focusy = GetActorY(focustid);

			byte_angle = VectorAngle( focusx-oldx, focusy-oldy ) >> 8;
		}

		Thing_Stop(0);

		int i = 0;
		do
		{
			ThrustThing(byte_angle, max( min(i*3,30), 5), 0, 0);

			if( (i%10) == 0)
			{
				if( i>35*13 )
				{	Thing_Damage(0,100000,MOD_UNKNOWN);	}
				else if( i>35*5 )
				{	Thing_Damage(0,20,MOD_UNKNOWN);	}
			}

			i++;
			delay(1);
		} while ( (CheckInventory("ThrustDummy")>0) && (GetActorProperty(0, APROP_Health)>0) );

		SetActorProperty(0, APROP_Speed, oldSpeed);
	}

	if(LineSide() == LINE_BACK)
	{
		TakeInventory("ThrustDummy", 1);
	}
}

// Does some sanity checks on the map
script S_DEBUG (int wavemanip /*void*/) // 'puke 930'
{
	if (wavemanip)
	{
		F_CallNextWave = wavemanip;
		terminate;
	}
	PrintBold(s:"Debug script starting");

	int i, j, count;
	int errors = 0;
	for(i=0; i<WeaponSpotsUsed; i++)
	{
		count = ThingCount(T_NONE, WeaponSpots[i]);
		if(count != 1)
		{
			PrintBold(	s:"Error ",d:++errors,s:": There are ",d:count,
					s:" weapon spots with tid ",d:WeaponSpots[i]	);
		}
	}

	for(i=0; i<MedicSpotsUsed; i++)
	{
		count = ThingCount(T_NONE, MedicSpots[i]);
		if(count != 1)
		{
			PrintBold(	s:"Error ",d:++errors,s:": There are ",d:count,
					s:" medic spots with tid ",d:MedicSpots[i]	);
		}
	}

	for(i=0; i<AmmoSpotsUsed; i++)
	{
		count = ThingCount(T_NONE, AmmoSpots[i]);
		if(count != 1)
		{
			PrintBold(	s:"Error ",d:++errors,s:": There are ",d:count,
					s:" ammo spots with tid ",d:AmmoSpots[i]	);
		}
	}

	for(i=0; i<PowerupSpotsUsed; i++)
	{
		count = ThingCount(T_NONE, PowerupSpots[i]);
		if(count != 1)
		{
			PrintBold(	s:"Error ",d:++errors,s:": There are ",d:count,
					s:" powerup spots with tid ",d:PowerupSpots[i]	);
		}
	}

	for(i=0; i<ZoneDataUsed; i++)
	{
		for(j=8; j<=9; j++)
		{
			count = ThingCount(T_NONE, ZoneData[i][j]);
			if(count != 1)
			{
				PrintBold(	s:"Error ",d:++errors,s:": There are ",d:count,
						s:" zone mapspots with tid ",d:ZoneData[i][j]	);
			}
		}
	}

	// A mapspot with tid 900 or 901 used to be used as "TEST_TID" but is now useless.
	// Removing it helps remove some clutter.
	for(i=900; i<=901; i++)
	{
		count = ThingCount(T_NONE, i);
		if(count > 0)
		{
			PrintBold(	s:"Error ",d:++errors,s:": There are ",d:count,
					s:" extra mapspots with tid ",d:i	);
		}
	}

	if(PrisonSpot)
	{
		count = ThingCount(T_NONE, PrisonSpot);
		if(count < 8)
		{
			PrintBold(	s:"Error ",d:++errors,s:": There are only ",d:count,
					s:" prison spots, with tid ",d:PrisonSpot,s:". Suggested to have at least 8."	);
		}

		count = ThingCount(T_NONE, PrisonExit);
		if(!count)
		{
			PrintBold(	s:"Error ",d:++errors,s:": There is no PrisonExit with tid ",
					d:PrisonExit,s:". Required to have at least 1."	);
		}

		if(TeleporterOnLight)
		{
			count = ThingCount(T_NONE, TeleporterOnLight);
			if(count == 0)
			{
				PrintBold(	s:"Error ",d:++errors,s:": There is no",
						s:" TeleporterOnLight, expecting tid ",d:TeleporterOnLight	);
			}
		}

		if(TeleporterOffLight)
		{
			count = ThingCount(T_NONE, TeleporterOffLight);
			if(count == 0)
			{
				PrintBold(	s:"Error ",d:++errors,s:": There is no",
						s:" TeleporterOffLight, expecting tid ",d:TeleporterOffLight	);
			}
		}
	}

	if(HateEpicenter)
	{
		// The HateEpicenter should be a shootable thing.
		// Can't directly check for that, so I check if the HateEpicenter is alive or is a HateTarget.
		if(	!( ClassifyActor(HateEpicenter) & ACTOR_ALIVE ) &&
			!( GetActorProperty(HateEpicenter, APROP_Health) ) )
		{
			PrintBold(	s:"Error ",d:++errors,s:": There is no",
					s:" HateTarget or living HateEpicenter, expecting tid ",d:HateEpicenter,
					s:".\nRemove the HateEpicenter line from the map's script if you don't want one."	);
		}
	}

	if(BossMonster)
	{
		if( ThingCount(T_NONE,BossMonster) != 1 )
		{
			PrintBold(	s:"Error ",d:++errors,s:": There is more or less",
					s:" than 1 actor with the BossMonster Tid."	);
		}
	}

	if(errors > 0)
	{
		PrintBold(s:"Debug script completed, with ",d:errors,s:" error(s)\n\nCheck your console");
	} else {
		PrintBold(s:"Debug script completed with no errors");
	}
}

// Randomizes the STR52 code if needed, and then prints the correct sequence to the screen.
script S_STR52CODEGET (void)	// Script 927
{
	// Has the code been set yet? Set a code if not
	if(!str52codeinit)
	{
		for(int i=0; i<5; i++)
			str52code[i] = random(0,9);
		
		str52codeinit = TRUE;
	}

	sethudsize(640,480,0);
	setFont("BigFont");
	HudMessage(	d:str52code[0],s:" ",
			d:str52code[1],s:" ",
			d:str52code[2],s:" ",
			d:str52code[3],s:" ",
			d:str52code[4],s:" ";
		HUDMSG_PLAIN | HUDMSG_LOG, 11, CR_WHITE, 60.0, 400.0, 9.0);

	delay(35*8);
}

// HUD Stuff

script S_HUD OPEN clientside
{
	if( MapIsMenu() )
	{	Terminate;	}

	// Launch a copy of the timer script clientside
	ACS_Execute( S_TIMER, 0, 0 );

	SetHudSize(640,480,1);

	int aspect, adjWidth, left, right;
	int showedPar	= 0;

	while(true)
	{
		// If set, turn off hud, and wait for the value to be toggled again
		if(getcvar("sthd_hud_disable"))
		{
			ClearHud();

			while(getcvar("sthd_hud_disable"))
			{	delay(1);	}
		}
		// Code to disable HUD when regular HUD is gone
/*		if(getcvar("screenblocks")==12)
		{
			ClearHud();

			while(getcvar("screenblocks")==12)
			{	delay(1);	}
		}
*/
		aspect = getaspectratio();

		adjWidth = aspect * 480;

		left = fround((640.0-adjWidth)/2);
		right = fround((adjWidth+640.0)/2);

		SetFont("HUDLEFT");
		HudMessageBold(s:"A"; HUDMSG_PLAIN, HUDLeftHID, CR_UNTRANSLATED,
			left+0.1, 0.1,
			0.0 );

		SetFont("HUDRIGHT");
		HudMessageBold(s:"A"; HUDMSG_PLAIN, HUDRightHID, CR_UNTRANSLATED,
			right+0.2, 0.1,
			0.0 );

		SetFont("BIGFONT");

		HudMessageBold (d:GetLevelInfo(LEVELINFO_LEVELNUM); HUDMSG_PLAIN, HUDLevelHID, CR_RED,
			left+83.0, 15.0,
			0.0 );

		// Show Par Time
		if(parTimeMode != 0)
		{
			// Time
			HudMessageBold (d:parTimeMinutes,s:":",s:parTimeSecondsSpace,d:parTimeSeconds;
				HUDMSG_PLAIN, HUDParTimeHID, CR_GREEN,
				right-38.0, 60.0,
				0.0 );

			// Label
			if(parTimeMode==1)
			{
				HudMessageBold (s:"Par Time";
					HUDMSG_PLAIN, HUDParTimeLabelHID, CR_GREEN,
					right-70.0+0.2, 60.0,
					0.0 );
			}
			else if (parTimeMode==2)
			{
				HudMessageBold (s:"Best Time";
					HUDMSG_PLAIN, HUDParTimeLabelHID, CR_GREEN,
					right-70.0+0.2, 60.0,
					0.0 );
			}
		} else {
			HudMessageBold(s:""; HUDMSG_PLAIN, HUDParTimeHID, CR_UNTRANSLATED, 0.0, 0.0, 0.5 );
			HudMessageBold(s:""; HUDMSG_PLAIN, HUDParTimeLabelHID, CR_UNTRANSLATED, 0.0, 0.0, 0.5 );
		}
		showedPar = parTimeMode;

		do
		{
			// Wave
			HudMessageBold (d:curWave; HUDMSG_PLAIN, HUDWaveHID, CR_RED,
				left+192.0, 15.0,
				0.0 );

			// Body count
			HudMessageBold(d:GetLevelInfo(LEVELINFO_KILLED_MONSTERS); HUDMSG_PLAIN, HUDBodyCountHID, CR_RED,
				right-38.0, 39.0,
				0.0 );

			// Monsters left
			HudMessageBold (d:GetLevelInfo(LEVELINFO_TOTAL_MONSTERS)-GetLevelInfo(LEVELINFO_KILLED_MONSTERS);
				HUDMSG_PLAIN, HUDMonstersHID, CR_RED,
				left+180.0, 39.0,
				0.0 );

			// Time Spent
			HudMessageBold (d:levelMinutes,s:":",s:levelSecondsSpace,d:levelSeconds;
				HUDMSG_PLAIN, HUDTimeHID, CR_RED,
				right-38.0, 15.0,
				0.0 );

			delay(1);
		}
		while( (aspect == getaspectratio()) && (showedPar == parTimeMode) && (!getcvar("sthd_hud_disable")) /* && (getcvar("screenblocks")!=12) */ );
	}
}

function void clearHud (void)
{
	HudMessageBold(s:""; HUDMSG_PLAIN, HUDLeftHID, CR_UNTRANSLATED, 0.0, 0.0, 0.5 );
	HudMessageBold(s:""; HUDMSG_PLAIN, HUDRightHID, CR_UNTRANSLATED, 0.0, 0.0, 0.5 );
	HudMessageBold(s:""; HUDMSG_PLAIN, HUDLevelHID, CR_UNTRANSLATED, 0.0, 0.0, 0.5 );
	HudMessageBold(s:""; HUDMSG_PLAIN, HUDWaveHID, CR_UNTRANSLATED, 0.0, 0.0, 0.5 );
	HudMessageBold(s:""; HUDMSG_PLAIN, HUDBodyCountHID, CR_UNTRANSLATED, 0.0, 0.0, 0.5 );
	HudMessageBold(s:""; HUDMSG_PLAIN, HUDMonstersHID, CR_UNTRANSLATED, 0.0, 0.0, 0.5 );
	HudMessageBold(s:""; HUDMSG_PLAIN, HUDTimeHID, CR_UNTRANSLATED, 0.0, 0.0, 0.5 );
	HudMessageBold(s:""; HUDMSG_PLAIN, HUDParTimeHID, CR_UNTRANSLATED, 0.0, 0.0, 0.5 );
	HudMessageBold(s:""; HUDMSG_PLAIN, HUDParTimeLabelHID, CR_UNTRANSLATED, 0.0, 0.0, 0.5 );
}

script S_TIMER open
{
	while( TRUE )
	{
		while( TimerIsLive )
		{
			delay( 1 );
			levelRunningTime++;

			if( levelRunningTime % 35 == 0 )
			{
				levelMinutes = (levelRunningTime/35)/60;
				levelSeconds = (levelRunningTime/35)%60;

				levelSecondsSpace = zerospace(levelSeconds,2);
			}
		}
		delay(1);
	}
}

function void StartTimer (void)
{
	TimerIsLive = TRUE;
	UpdateTime();
}

function void StopTimer (void)
{
	TimerIsLive = FALSE;
	UpdateTime();
}

function void UpdateWaveTrackers (void)
{
//	PrintBold(s:"UpdateWaveTrackers - Curwave = ",d:Curwave);

	ACS_ExecuteAlways( S_SYNC, 0, SYNC_WAVE, Curwave );
}

// Time is tricky to update, because levelRunningTime is a 4 byte integer, and
// we can only send 3 bytes at a time in a script's arguments.
function void UpdateTime (void)
{
//	PrintBold(s:"UpdateTime - levelRunningTime = ",d:levelRunningTime);

//	Scripts in reverse order because they'll probably be sent together in the same
//	packet, and executed backwards by clients.
	ACS_ExecuteAlways( S_SYNC, 0, SYNC_LOTIME | (TimerIsLive<<7),
		(levelRunningTime>>8)%256,
		levelRunningTime%256
		);

	ACS_ExecuteAlways( S_SYNC, 0, SYNC_HITIME,
		(levelRunningTime>>24),	// Highest byte doesn't need to be mod 256
		(levelRunningTime>>16)%256
		);

	// Maybe not the best place for sending the par time to clients, but it ensures no clients never get it
	if(parTimeMode != 0)
	{
		ACS_ExecuteAlways( S_SYNC, 0, SYNC_PARTIME | ((parTimeMode-1)<<7),
			parTime>>8,
			parTime%256
			);
	}
}

// Most significant byte first
function int unpack(int byte1, int byte2, int byte3, int byte4)
{
	return (byte1<<24) + (byte2<<16) + (byte3<<8) + byte4;
}

bool hiTimeBytesReady = FALSE;
int hiTimeByte1, hiTimeByte2;

script S_SYNC (int flags, int byte1, int byte2) clientside
{
//	PrintBold(s:"S_SYNC: flags = ",x:flags,s:", byte1 = ",d:byte1,s:", byte2 = ",d:byte2);

	if(AllVarsLocal)
	{	Terminate;	}

	switch(flags & 0x0000007f)
	{
		case SYNC_WAVE:
			curWave = byte1;
			break;
		case SYNC_HITIME:
			hiTimeByte1 = byte1;
			hiTimeByte2 = byte2;
			hiTimeBytesReady = TRUE;
			break;
		case SYNC_LOTIME:
			// Wait until SYNC_HITIME call has been made
			while( !hiTimeBytesReady )
			{	delay(1);	}
			hiTimeBytesReady = FALSE;

			levelRunningTime = unpack(hiTimeByte1, hiTimeByte2, byte1, byte2);
			TimerIsLive = (flags&0x00000080)>>7;
			break;
		case SYNC_PARTIME:
			setParTime( unpack(0,0,byte1,byte2), (flags&0x00000080)>>7 );
			break;
		default:
			PrintBold(s:"Error: S_SYNC called with invalid flags ",x:flags);
	}
}

// par in seconds, type=0 for "Par Time", type=1 for "Best Time"
function void setParTime (int par, int type)
{
	parTime	= par;
	if ( parTime > 0 )
	{
		parTimeMode	= type+1;

		parTimeMinutes	= parTime / 60;
		parTimeSeconds	= parTime % 60;
		parTimeSecondsSpace	= zerospace(parTimeSeconds,2);
	} else {
		parTimeMode	= 0;
	}
}

script S_VAR_CHECK open
{
	AllVarsLocal = TRUE;
}


/* *************************************************************************************** **
**         TempTid Functions                                                               **
** These two functions are massive and intimidating, but work simply and very efficiently. **
** They allow tids to be given dynamically and recycled, instead of needing a billion      **
** different and agreed predefined tids for certain script actions. Take a look at         **
** other functions in this file to see how they're used.             ~Agent ME             **
** *************************************************************************************** */

// Don't touch these
#define UINT_MAX		0xFFFFFFFF
#define UINT_HIGHEST_BIT	0x80000000
#define UINT_BITS		32
#define TEMP_TID_ARRAY_SIZE	(TEMP_TID_END-TEMP_TID_START+1)/UINT_BITS+1 // +1 at end in case of (likely) rounding error
int TempTids[TEMP_TID_ARRAY_SIZE];

int lastindex = 0;
int lastbit = 1;
function int NewTempTid (void)
{
//	Emulating boolean arrays is messy, but very fast. Lots of bit operations are to follow, so be warned.
//	Also, a lot of this code is optimizations.

	int done = FALSE;

	int isSecondTry;
	if( lastindex != 0 )
	{	isSecondTry = FALSE;	}
	else
	{	isSecondTry = TRUE;	} // If we're starting from the beginning, there won't be a reason to search twice

	int beenherealready = lastindex;	// If we loop to the last index, and back to 0, don't go to this value index again

	while( ! done )
	{
		int index = lastindex;
		int bit = lastbit;
		while( TempTids[index] & bit )
		{
			if( bit == 1 ) // Are we just starting this loop?
			{
				if( TempTids[index] == UINT_MAX ) // Is this next 32-bit section full?
				{	bit = UINT_HIGHEST_BIT;	} // If it is, say we searched through it already

			//	This section makes the code run more efficiently, but could probably
			//	be coded a bit cleaner. There's still room for more efficiency.
				else if( (TempTids[index] & 0x0000FFFF) == 0x0000FFFF )	// Is this 16-bit section full?
				{
					if( (TempTids[index] & 0x00FF0000) == 0x00FF0000 )	// What about the next 8-bits?
					{	bit = 0x00800000;	}	// Sure, skip them
					else
					{	bit = 0x00008000;	}	// Not yet, tiger
				}

				else if( (TempTids[index] & 0x000000FF) == 0x000000FF ) // How about this 8-bit section?
				{	bit = 0x00000080;	}
			}

			if( bit == UINT_HIGHEST_BIT )	// We now need to figure out what our next index is
			{
				bit = 1;

				if( index == (TEMP_TID_ARRAY_SIZE-1) )
				{
					index = 0;	// Loop back to start, so we can see if anything has been freed up
				} else
				{	index++;	}	// Otherwise just go to next index

				if( index == beenherealready )
				{
					// Bad, we've already been here before
					PrintBold(s:"Error: TempTid overflow");
					return TEMP_TID_END;
				}
			} else {
				bit <<= 1;
			}
		}
		TempTids[index] |= bit;

		// Figure out what position bit was in
		int bitpos;
		int dbit = bit;	// destructable bit - this next loop changes the value of dbit, want original to stay same
		for (bitpos = 0; !(dbit & 1); dbit >>= 1)
		{	bitpos++;	}

		int tid = TEMP_TID_START+index*UINT_BITS+bitpos;

		if(tid>TEMP_TID_END)
		{
			if( isSecondTry )
			{
				PrintBold(s:"Error: TempTid overflow");
				return TEMP_TID_END;
			}
			isSecondTry = TRUE;
			beenherealready = lastindex;
			lastbit = 1;
			lastindex = 0;	// Why are we doing this again?
					// If we got a tid that's above our end tid, that means we set a bit that
					// represents a value past our max. We need to try to find a free bit somewhere else,
					// by starting at the beginning.
		} else {
			done = TRUE;

			// Set lastbit to reference the next bit to use
			if( bit != UINT_HIGHEST_BIT )
			{
				lastindex = index;
				lastbit = bit << 1;
			} else {
				if( index == (TEMP_TID_ARRAY_SIZE-1) )
				{	lastindex = 0;	}
				else
				{	lastindex = index+1;	}

				lastbit = 1;
			}
		}
	}

	if(ThingCount(T_NONE, tid)>0)
	{
		PrintBold(s:"Error: Tid ",d:tid,s:" already taken");
	}
	return tid;
}

function int FreeTempTid (int tid)
{
	if(tid<TEMP_TID_START || tid>TEMP_TID_END)
	{
		PrintBold(s:"Error: Attempted to free out of range Tid ",d:tid);
		return FALSE;
	}

	int index = (tid-TEMP_TID_START)/UINT_BITS;
	int bitpos = (tid-TEMP_TID_START)%UINT_BITS;
	int bit = 1;
	for (int i = 0; i < bitpos; i++)
	{	bit <<= 1;	}

	if(TempTids[index] & bit) // If this tid is reserved
	{
		TempTids[index] &= ~bit; // Set the tid's entry to 0
		Thing_ChangeTID(tid, 0); // Just in case there was something left with the tid
	} else {
		PrintBold(s:"Error: TempTid ",d:tid,s:" double freed");
		return FALSE;
	}
	return TRUE;
}