// BZII Community Project Modified MPI DLL. This DLL performs the same functions of the
// Pandemic DM DLL, and also performs teleportation functions for portals labelled
// appropriately (PortalA0, PortalB1 etc.).
 
// Deathmatch DLL code for a simple multiplay game. Requires map with
// spawnpoints, optional powerups. 
//
// Converted to new DLL interface spec by Nathan Mates 2/23/99
//

#include "..\Shared\DLLBase.h"
#include "..\Shared\PUPMgr.h"
#include <math.h>
#include <string.h>

#define VEHICLE_SPACING_DISTANCE (20.0f)

// WARNING: this can't go over 16 for now, as spawnpoints identified by team # now
#define MAX_CHECKPOINTS MAX_TEAMS

// # of vehicles we track for culling to keep the map cleared
#define MAX_VEHICLES_TRACKED ((MAX_TEAMS)+(MAX_TEAMS>>1))

#define MaxPortals 16

// How far away a new craft will be from the old one
const float RespawnMinRadiusAway=15.0f;
const float RespawnMaxRadiusAway=20.0f;

// How far allies will be from the commander's position
const float AllyMinRadiusAway=30.0f;
const float AllyMaxRadiusAway=60.0f;


// ---------- Scoring Values-- these are delta scores, added to current score --------
const int ScoreForKillingCraft = 5;
const int ScoreForKillingPerson = 5;
const int ScoreForDyingAsCraft = -1;
const int ScoreForDyingAsPerson = -1;
// -----------------------------------------------

char OutTimeString[1024];
char StaticTempMsgStr[1024];

// Temporary name for blasting ODF names into while building
// them. *not* saved, do *not* count on its contents being valid
// next time the dll is called.
static char TempODFName[64];
static bool RaceSetObjective=false; // Done separately from regularly loaded varbs, done per-machine

class Deathmatch01 : public DLLBase
{

private:
	int i_first,
		TotalGameTime,
		ElapsedGameTime,
		CurrentGameTime,
		KillLimit, // As specified from the shell
		MissionType,
		RespawnType,
		NumVehiclesTracked, 
		
		// Variables used during racing
		NextRaceCheckpoint[MAX_TEAMS],
		TotalCheckpointsCompleted[MAX_TEAMS],
		LastTeamInLead,
		TotalRaceLaps,
		// End of race varbs
		PortalCount,
		PortalGroup[MaxPortals],
		i_last;

	bool
	b_first,
		DidOneTimeInit,
		firstTime, gameWon, tugBuilt,
		Flying[MAX_TEAMS], // Flag saying we need to keep track of a specific player to build a craft when they land
		TeamIsSetUp[MAX_TEAMS],
		DMSetup,
		RaceIsSetup,
		b_last;

	float
	f_first,
		TeamPos[3*(MAX_TEAMS+1)], // Where they started out, used in respawning
		SpawnPointPos[3*(MAX_TEAMS+1)], // Used during race
		f_last;

	Handle
	h_first,
		flag1, flag2, carrier1, carrier2, base1, base2,
		VehicleHandles[MAX_VEHICLES_TRACKED],
		SpawnPointHandles[MAX_TEAMS], // Used during race
		tug, dustoff,
		Portals[MaxPortals],
		h_last;

protected:
	bool *b_array;
	int b_count;

	float *f_array;
	int f_count;

	int *h_array;
	int h_count;

	int *i_array;
	int i_count;

public:

	// Don't do much at class creation; do in InitialSetup()
	// _________________________________________________________________
	Deathmatch01(void) 
	{
		b_count = &b_last - &b_first - 1;
		b_array = &b_first + 1;

		f_array = &f_first + 1;
		f_count = &f_last - &f_first - 1;

		h_array = &h_first + 1;
		h_count = &h_last - &h_first - 1;

		i_count = &i_last - &i_first - 1;
		i_array = &i_first + 1;
	}


	// Returns true if players can eject (or bail out) of their vehicles
	// depending on the game type, false if they should be killed when
	// they try and do so.
	// _________________________________________________________________
	bool GetCanEject(void)
	{
		switch (MissionType)
			{
			case DMSubtype_Normal:
			case DMSubtype_KOH:
			case DMSubtype_Loot:
				return true;
				
			default:
				return false;
			}
	}


	// Returns true if players shouldn't be respawned as a pilot, but in
	// a piloted vehicle instead.
	// _________________________________________________________________
	bool GetRespawnInVehicle(void)
	{
		switch (MissionType)
			{
			case DMSubtype_Race2:
			case DMSubtype_Normal2:
				return true;
				
			default:
				return false;
			}
	}


	// Given a race identifier, get the flag odf back
	// _________________________________________________________________
	char *GetInitialFlagODF(char Race)
	{
		strcpy(TempODFName,"ioflag01");
		TempODFName[0]=Race;
		return TempODFName;
	}


	// Given a race identifier, get the flag odf back
	// _________________________________________________________________
	char *GetInitialPlayerPilotODF(char Race)
	{
		strcpy(TempODFName,"ispilo");
		TempODFName[0]=Race;
		return TempODFName;
	}


	// _________________________________________________________________
	char *GetNextVehicleODF(int TeamNum, bool Randomize)
	{
		RandomizeType RType=Randomize_None; // Default
		if(Randomize) {
			if(RespawnType==1)
				RType=Randomize_ByRace;
			else if(RespawnType==2)
				RType=Randomize_Any;
		}

		return GetPlayerODF(TeamNum,RType);
	}


	// _________________________________________________________________
	char *GetNextRandomVehicleODF(int Team)
	{
		return GetPlayerODF(Team);
	}


	// Sets up the side's commander's extra vehicles, such a recycler or
	// more. Does *not* create the player vehicle for them,
	// however. [That's to be done in SetupPlayer.] Safe to be called
	// multiple times for each player on that team. 
	//
	// If Teamplay is off, this function is called once per player.
	//
	// If Teamplay is on, this function is called only on the
	// _defensive_ team number for an alliance. 
	// _________________________________________________________________
	void SetupTeam(int Team)
	{
		// Sanity checks: don't do anything that looks invalid
		if((Team<0) || (Team>=MAX_TEAMS))
			return;
		// Also, if we've already set up this team group, don't do anything
		if((IsTeamplayOn()) && (TeamIsSetUp[Team]))
			return;

		char TeamRace=GetRaceOfTeam(Team);

		if(IsTeamplayOn()) {
			SetMPTeamRace(WhichTeamGroup(Team),TeamRace); // Lock this down to prevent changes.
		}

		Vector Where;

		if(DMIsRaceSubtype[MissionType]) // Race-- everyone starts off at spawnpoint 0's position
			Where=GetSpawnpoint(0);
		else if(MissionType == DMSubtype_CTF) // CTF-- find spawnpoint by team # 
			Where=GetSpawnpoint(Team);
		else {
			Where=GetSafestSpawnpoint();

			// And randomize around the spawnpoint slightly so we'll
			// hopefully never spawn in two pilots in the same place
			Where=GetPositionNear(Where,AllyMinRadiusAway,AllyMaxRadiusAway);
		}

		// Store position we created them at for later
		TeamPos[3*Team+0]=Where.x;
		TeamPos[3*Team+1]=Where.y;
		TeamPos[3*Team+2]=Where.z;

		// Find location to place flag at
		if (MissionType == DMSubtype_CTF) { // CTF
			// Find place to drop flag from AIPaths list
			char DesiredName[32];
			sprintf(DesiredName,"base%d",Team);

			int i,pathCount;
			char **pathNames;
			GetAiPaths(pathCount, pathNames);
			for (i=0; i<pathCount; ++i) {
				char *label = pathNames[i];
				if(strcmp(label,DesiredName)==0) {
					int FlagH=BuildObject(GetInitialFlagODF(TeamRace), Team, label);
					if(Team==1)
						flag1=FlagH;
					else if(Team==6)
						flag2=FlagH;
				}
			}
			Handle base1 = GetHandle("base1");
			if (base1)
				SetAnimation(base1, "loop", 0);
			Handle base2 = GetHandle("base2");
			if (base2)
				SetAnimation(base2, "loop", 0);
		} // CTF Flag setup

		else if ((DMIsRaceSubtype[MissionType]) && (!RaceIsSetup)) { // Race. Gotta grab spawnpoint locations
			for(int i=0;i<MAX_TEAMS;i++) {
				SpawnPointHandles[i]=GetSpawnpointHandle(i);
				_ASSERTE(SpawnPointHandles[i]);
				Vector V=GetSpawnpoint(i);
				SpawnPointPos[3*i+0]=V.x;
				SpawnPointPos[3*i+1]=V.y;
				SpawnPointPos[3*i+2]=V.z;
			}
			RaceIsSetup=true;
		}

		if(IsTeamplayOn()) 
			for(int i=GetFirstAlliedTeam(Team);i<=GetLastAlliedTeam(Team);i++) 
				if(i!=Team) {
					// Get a new position near the team's central position
					Vector NewPosition=GetPositionNear(Where,AllyMinRadiusAway,AllyMaxRadiusAway);
					
					// In teamplay, store where offense players were created for respawns later
					TeamPos[3*i+0]=NewPosition.x;
					TeamPos[3*i+1]=NewPosition.y;
					TeamPos[3*i+2]=NewPosition.z;
				} // Loop over allies not the commander

		TeamIsSetUp[Team]=true;
	}


	// _________________________________________________________________
	Handle SetupPlayer(int Team)
	{
		Handle PlayerH;
		Vector Where;

		if((Team<0) || (Team>=MAX_TEAMS))
			return 0; // Sanity check... do NOT proceed

		int TeamBlock=WhichTeamGroup(Team);

		if((!IsTeamplayOn()) || (TeamBlock<0)) {
			
			if(DMIsRaceSubtype[MissionType])
				Where=GetSpawnpoint(0); // Start at spawnpoint 0
			else
				Where=GetRandomSpawnpoint();

			// This player is their own commander; set up their equipment.
			SetupTeam(Team);
		}
		else {
			// Teamplay. Gotta put them near their commander. Also, always
			// ensure the recycler/etc has been set up for this team if we
			// know who they are
			SetupTeam(GetCommanderTeam(Team));

			// SetupTeam will fill in the TeamPos[] array of positions
			// for both the commander and offense players, so read out the
			// results
			Where.x=TeamPos[3*Team+0];
			Where.z=TeamPos[3*Team+2];
			Where.y=TerrainFindFloor(Where.x,Where.z);
		} // Teamplay setup

		if(DMIsRaceSubtype[MissionType]) { // Race. Start off near spawnpoint 0
			Where.x=SpawnPointPos[3*0+0];
			Where.y=SpawnPointPos[3*0+1];
			Where.z=SpawnPointPos[3*0+2];
			Where=GetPositionNear(Where,AllyMinRadiusAway,AllyMaxRadiusAway);
			NextRaceCheckpoint[Team]=1; // Heading towards sp 1
			TotalCheckpointsCompleted[Team]=0; // None so far
			RaceSetObjective=false;
		}

		PlayerH=BuildObject(GetPlayerODF(Team),Team,Where);
		if(!DMIsRaceSubtype[MissionType]) 
			SetRandomHeadingAngle(PlayerH);
		SetNoScrapFlagByHandle(PlayerH);

		// If on team 0 (dedicated server team), make this object gone from the world
		if(!Team)
			MakeInert(PlayerH);

		return PlayerH;
	}


	// _________________________________________________________________
	void InitialSetup(void)
	{
		DidOneTimeInit = false;
		firstTime = true;
		DMSetup = false;
	}


	// Removes entry #i from the list, shuffles rest down. If
	// ShouldKill, the craft forcibly removed first
	// _________________________________________________________________
	void DeleteTrackedVehicle(int i,bool ShouldKill)
	{
		if(ShouldKill) {
			_ASSERTE(VehicleHandles[i]);
			RemoveObject(VehicleHandles[i]);
		}

		int j;
		for(j=i;j<(MAX_VEHICLES_TRACKED-1);j++)
			VehicleHandles[j]=VehicleHandles[j+1];
		VehicleHandles[MAX_VEHICLES_TRACKED-1]=0; // Clear out this slot

		if(NumVehiclesTracked)
			NumVehiclesTracked--;
	}


	// Adds a vehicle to the tracking list, and kills the oldest unpiloted one if 
	// _________________________________________________________________
	void AddAndCullVehicles(Handle NewCraft)
	{
		_ASSERTE(NewCraft!=0);

		if(NumVehiclesTracked<MAX_VEHICLES_TRACKED)
			VehicleHandles[NumVehiclesTracked++]=NewCraft;

		// Clear out if we've got too many craft floating around.
		int CurNumPlayers=CountPlayers();
		if(NumVehiclesTracked> (CurNumPlayers+(CurNumPlayers>>1))) {
			int i;

			// Pass 1: delete all dead vehicles
			bool AnyChanges,Killable;
			do {
				AnyChanges=false;
				for(i=0;i<NumVehiclesTracked;i++) {
					if((VehicleHandles[i]==0) || (!IsAround(VehicleHandles[i]))) { //IsAround doesn't muck with the handle we pass in
						AnyChanges=true;
						DeleteTrackedVehicle(i,false);
					}
				} // Loop over all vehicles
			} while(AnyChanges);

			// Pass 2: also kill oldest non-piloted vehicle
			if(NumVehiclesTracked> (CurNumPlayers+(MAX_TEAMS>>1))) {
				bool CanKill=true;
				for(i=0;((i<NumVehiclesTracked) && (CanKill));i++) {
					_ASSERTE(VehicleHandles[i]);

					// Side effect to note: IsAliveAndPilot zeroes the handle
					// if pilot missing; that'd be bad for us here if we want
					// to manually remove it. Thus, we have a sacrificial varb
					Handle TempH=VehicleHandles[i];
					Killable=!IsAliveAndPilot(TempH); 

					if((Killable) || (TempH==0)) {
						_ASSERTE(VehicleHandles[i]);
						DeleteTrackedVehicle(i,true);
						CanKill=false;
					}
				}
			} // Gotta kill oldest vehicle
		}
	}


	// _________________________________________________________________
	void Init(void)
	{
		if(i_array)
			memset(i_array,0,i_count*sizeof(int));
		if(f_array)
			memset(f_array,0,f_count*sizeof(float));
		if(h_array)
			memset(h_array,0,h_count*sizeof(Handle));
		if(b_array)
			memset(b_array,0,b_count*sizeof(bool));

		RaceSetObjective=false;

		DidOneTimeInit=true;
		_ASSERTE(IsNetworkOn());
		LastTeamInLead=DPID_UNKNOWN;

		KillLimit=GetVarItemInt("network.session.ivar0");
		TotalGameTime=GetVarItemInt("network.session.ivar1");
		// Skip ivar2-- player limit. Assume the netmgr takes care of that.
		int MissionTypePrefs=GetVarItemInt("network.session.ivar7");
		TotalRaceLaps=GetVarItemInt("network.session.ivar9"); // Just in case we're using this
		MissionType=MissionTypePrefs&0xFF;
		RespawnType=(MissionTypePrefs>>8) &0xFF;

		Handle PlayerH,playerEntryH;
		int LocalTeamNum=GetLocalPlayerTeamNumber(); // Query this from game

		// As the .bzn has a vehicle which may not be appropriate, we
		// must zap that player object and recreate them the way we want
		// them when the game starts up.
		playerEntryH=GetPlayerHandle();
		if(playerEntryH) 
			RemoveObject(playerEntryH);

		// Do One-time server side init of varbs for everyone
		if((ImServer()) || (!IsNetworkOn())) {
			if(!CurrentGameTime)
				CurrentGameTime=TotalGameTime*60*10; // convert minutes to 1/10 seconds

			// And build local player
			PlayerH=SetupPlayer(LocalTeamNum);
			SetAsUser(PlayerH,LocalTeamNum);
			AddPilotByHandle(PlayerH);
		} // Server or no network

		PUPMgr::Init();
		firstTime = false;

		if(0)
			ClearTeamColors();  //Added to get rid of the team colors for the demo.

		PortalInit();
	}


	// Flags the appropriately 'next' spawnpoint as the objective
	// _________________________________________________________________
	void ObjectifyRacePoint(void)
	{
		static int LastObjectified=-1;
		// First, clear all previous objectives
		for(int i=0;i<MAX_CHECKPOINTS;i++)
			SetObjectiveOff(SpawnPointHandles[i]); // Ensure these are all cleared off.
		int Idx=GetLocalPlayerTeamNumber();
		if(Idx>=0) {
#ifdef _DEBUG
			if(LastObjectified!=NextRaceCheckpoint[Idx]) {
				LastObjectified=NextRaceCheckpoint[Idx];
				sprintf(StaticTempMsgStr,"Objectifying %d",LastObjectified);
				AddToMessagesBox(StaticTempMsgStr);
			}
#endif

			Handle NextCheckpoint=SpawnPointHandles[NextRaceCheckpoint[Idx]];
			if(NextCheckpoint)
				SetObjectiveOn(NextCheckpoint);
			SetObjectiveName(NextCheckpoint, "Checkpoint");
		}
	}



	// Race-specific execute stuff.
	// _________________________________________________________________
	void ExecuteRace(void)
	{
		if((!RaceSetObjective) || (!(ElapsedGameTime%10))) { // Race. Gotta set objectives properly
			RaceSetObjective=true;
			ObjectifyRacePoint();
		} // Periodic re-objectifying

		// Also, check if everyone's near their next checkpoint
		bool Advanced[MAX_TEAMS];
		bool AnyAdvanced=false;
		int i,j;

		for(i=0;i<MAX_TEAMS;i++) {
			Advanced[i]=false;
			Handle PlayerH=GetPlayerHandle(i);
			if(!PlayerH)
				continue; // Do nothing for them.

			Handle NextCheckpoint=SpawnPointHandles[NextRaceCheckpoint[i]];
			if(NextCheckpoint) {
				if(GetDistance(PlayerH, NextCheckpoint) < (35.0f)) {
					NextRaceCheckpoint[i]=(NextRaceCheckpoint[i]+1) & (MAX_CHECKPOINTS-1);
					ObjectifyRacePoint();
					AnyAdvanced=Advanced[i]=true;
					TotalCheckpointsCompleted[i]++;

					// Print out a message for local player upon lap completion
					if((!NextRaceCheckpoint[i]) && (i == GetLocalPlayerTeamNumber())) {
						if(TotalRaceLaps) 
							sprintf(StaticTempMsgStr,"Lap %d/%d Completed",(TotalCheckpointsCompleted[i]+1)/MAX_CHECKPOINTS,TotalRaceLaps);
						else
							sprintf(StaticTempMsgStr,"Lap %d Completed",(TotalCheckpointsCompleted[i]+1)/MAX_CHECKPOINTS);

						AddToMessagesBox(StaticTempMsgStr);
					}
				}
			} // NextCheckpoint exists
		} // Loop over all players

		// Give a point to someone if they made it to a checkpoint before anyone else did.
		if(AnyAdvanced) {
			for(i=0;i<MAX_TEAMS;i++) 
				if(Advanced[i]) {
					Handle PlayerH=GetPlayerHandle(i);

					bool LeadingPlayer=true;
					for(j=0;j<MAX_TEAMS;j++)
						if((i!=j) && (TotalCheckpointsCompleted[i]<=TotalCheckpointsCompleted[j]))
							LeadingPlayer=false;
					if(LeadingPlayer) {
						AddScore(PlayerH,1);
							
						if(i != LastTeamInLead) {
							sprintf(StaticTempMsgStr,"%s takes the lead",GetPlayerName(PlayerH));
							AddToMessagesBox(StaticTempMsgStr);
							LastTeamInLead=i;
						}
					}

					// Also check if leader completed a full lap
					LeadingPlayer=true;
					for(j=0;j<MAX_TEAMS;j++)
						if((i!=j) && (TotalCheckpointsCompleted[i]<TotalCheckpointsCompleted[j]))
							LeadingPlayer=false;
					if((LeadingPlayer) && (!NextRaceCheckpoint[i])) {
						int NumLapsCompleted=(TotalCheckpointsCompleted[i]+1)/MAX_CHECKPOINTS;

						if(TotalRaceLaps) 
							sprintf(StaticTempMsgStr,"Lap %d/%d completed by leader %s",
											NumLapsCompleted,TotalRaceLaps,GetPlayerName(PlayerH));
						else
							sprintf(StaticTempMsgStr,"Lap %d completed by leader %s",
											NumLapsCompleted,GetPlayerName(PlayerH));

						AddToMessagesBox(StaticTempMsgStr);
						if(NumLapsCompleted==TotalRaceLaps) {
							AddScore(PlayerH,100);
							NoteGameoverByScore(PlayerH);
							DoGameover(10.0f);
						}
							
					}
				}
		}
	} // ExecuteRace()



	// Called via execute, 1/10 of a second has elapsed. Update everything.
	// _________________________________________________________________
	void UpdateGameTime(void)
	{
		ElapsedGameTime++;

		// Are we in a time limited game?
		if(CurrentGameTime>0) {
			CurrentGameTime--;
			if(!(CurrentGameTime%10)) {
				int MinutesLeft=(CurrentGameTime/(10*60));
				int SecondsLeft=(CurrentGameTime/10)%60;
				sprintf(OutTimeString,"Time Left %02d:%02d\n",MinutesLeft,SecondsLeft);
				SetTimerBox(OutTimeString);

				// Also print this out more visibly at important times....
				if((!SecondsLeft) && ((MinutesLeft<=10) || (!(MinutesLeft%5))))
					AddToMessagesBox(OutTimeString);
				else if((!MinutesLeft) && (!(SecondsLeft%5)))
					AddToMessagesBox(OutTimeString);

			}

			// Game over due to timeout?
			if(!CurrentGameTime) {
				NoteGameoverByTimelimit();
				DoGameover(10.0f);
			}

		}
		else { // Infinite time game
			if(!(ElapsedGameTime%10)) {

				sprintf(OutTimeString,"Mission Time %02d:%02d\n",(ElapsedGameTime/(10*60)),(ElapsedGameTime/10)%60);
				SetTimerBox(OutTimeString);
			}
		}
	}


	// _________________________________________________________________
	void Execute(void)
	{
		// Always ensure we did this
		if (!DidOneTimeInit)
			Init();

		if(DMIsRaceSubtype[MissionType]) 
			ExecuteRace();

		// Do this as well...
		UpdateGameTime();


		// Keep powerups going, etc
		PUPMgr::Execute();

		// Portal related stuff
		PortalFunctions();

		// Check to see if someone was flagged as flying, and if they've
		// landed, build a new craft for them
		int i;
		for(i=0;i<MAX_TEAMS;i++)
			if(Flying[i]) {
				Handle PlayerH=GetPlayerHandle(i);
				if((PlayerH) && (!IsFlying(PlayerH))) {
					Flying[i]=false; // clear flag so we don't check until they're dead
					char *ODF=GetNextVehicleODF(i,true);
					Handle h=BuildEmptyCraftNear(PlayerH, ODF, i, RespawnMinRadiusAway, RespawnMaxRadiusAway);

					if (IsPerson(h))
					{
						Handle CurHandle = GetPlayerHandle(i);
						Matrix PlayerPosition;
						GetPosition(CurHandle, PlayerPosition);
						SetAsUser(h,i);
						SetPosition(h, PlayerPosition);
						RemoveObject(CurHandle);
					}

					AddAndCullVehicles(h); // Clean things up if there are too many around

				}
			}

		// Mission scoring for KOH/CTF now done in main game, so we
		// don't need to do anything here

	}


	// _________________________________________________________________
	bool AddPlayer(DPID id, int Team, bool IsNewPlayer)
	{
		if (!DidOneTimeInit)
			Init();

		// Server does all building; client doesn't need to do anything
		if (IsNewPlayer) {
			Handle PlayerH=SetupPlayer(Team);
			SetAsUser(PlayerH,Team);
			AddPilotByHandle(PlayerH);
			SetNoScrapFlagByHandle(PlayerH);
		}

		return 1; // BOGUS: always assume successful
	}


	// _________________________________________________________________
	void DeletePlayer(DPID id)
	{
	}



	// Rebuilds pilot
	// _________________________________________________________________
	EjectKillRetCodes RespawnPilot(Handle DeadObjectHandle,int Team)
	{
		Vector Where; // Where they
		bool RespawnInVehicle=GetRespawnInVehicle();

		if(DMIsRaceSubtype[MissionType]) { // Race-- spawn back at last spawnpoint they were at.
			int LastSpawnAt=(NextRaceCheckpoint[Team]-1) & (MAX_CHECKPOINTS-1);
			Where.x=SpawnPointPos[3*LastSpawnAt+0];
			Where.y=SpawnPointPos[3*LastSpawnAt+1];
			Where.z=SpawnPointPos[3*LastSpawnAt+2];
		}
		else if((!IsTeamplayOn()) || (Team<1)) {
			Where=GetSafestSpawnpoint();
		}
		else {
			// Place them back where originally created
			Where.x=TeamPos[3*Team+0];
			Where.y=TeamPos[3*Team+1];
			Where.z=TeamPos[3*Team+2];
		}

		// Randomize starting position somewhat
		Where=GetPositionNear(Where,RespawnMinRadiusAway,RespawnMaxRadiusAway);

		if(!RespawnInVehicle)
			Where.y+=+25.0f; // Bounce them in the air to prevent multi-kills

		Handle NewPerson;
		if(RespawnInVehicle) 
			NewPerson=BuildObject(GetNextVehicleODF(Team,true),Team,Where);
		else {
			char Race=GetRaceOfTeam(Team);
			NewPerson=BuildObject(GetInitialPlayerPilotODF(Race),Team,Where);
		}

		SetAsUser(NewPerson,Team);

		AddPilotByHandle(NewPerson);
		SetRandomHeadingAngle(NewPerson);
		if(!RespawnInVehicle) 
			Flying[Team]=true; // build a craft when they land.

		// If on team 0 (dedicated server team), make this object gone from the world
		if(!Team)
			MakeInert(NewPerson);

		return DLLHandled; // Dead pilots get handled by DLL
	}


	// _________________________________________________________________
	EjectKillRetCodes DeadObject(int DeadObjectHandle, int KillersHandle, bool WasDeadPerson)
	{
		// Give points to killer, depending on whether they killed enemy or ally
		if((DeadObjectHandle != KillersHandle) && (!IsAlly(DeadObjectHandle,KillersHandle))) {
			// Killed enemy...
			AddKills(KillersHandle,1); // Give them a kill
			if(WasDeadPerson)
				AddScore(KillersHandle,ScoreForKillingPerson);
			else
				AddScore(KillersHandle,ScoreForKillingCraft);
		}
		else {
			AddKills(KillersHandle,-1); // Suicide or teamkill counts as -1 kill
			if(WasDeadPerson)
				AddScore(KillersHandle,-ScoreForKillingPerson);
			else
				AddScore(KillersHandle,-ScoreForKillingCraft);
		}
		
		// Give points to killee-- this always increases
		AddDeaths(DeadObjectHandle,1);
		if(WasDeadPerson)
			AddScore(DeadObjectHandle,ScoreForDyingAsPerson);
		else
			AddScore(DeadObjectHandle,ScoreForDyingAsCraft);

		// Check to see if we have a KillLimit winner
		if((KillLimit) && (GetKills(KillersHandle)>=KillLimit)) {
			NoteGameoverByKillLimit(KillersHandle);
			DoGameover(10.0f);
		}

		// Get team number of who got waxed.
		int DeadTeam=GetTeamNum(DeadObjectHandle);
		if(DeadTeam==0)
			return DoEjectPilot; // Someone on neutral team always gets default behavior

		// For a player-piloted craft, always respawn a new craft;
		// respawn pilot if needed as well.
		if(WasDeadPerson) {
			return RespawnPilot(DeadObjectHandle,DeadTeam);
		}
		else {
			// Don't build anything for them until they land.
			Flying[DeadTeam]=true;
			return DoEjectPilot;
		}
	}


	// _________________________________________________________________
	EjectKillRetCodes PlayerEjected(Handle DeadObjectHandle)
	{
		int DeadTeam=GetTeamNum(DeadObjectHandle);
		if(DeadTeam==0)
			return DLLHandled; // Invalid team. Do nothing

		// Update Deaths, Kills, Score for this player
		AddDeaths(DeadObjectHandle,1);
		AddKills(DeadObjectHandle,-1);
		AddScore(DeadObjectHandle,ScoreForDyingAsCraft-ScoreForKillingCraft);

		if(GetCanEject()) { // Flags saying if they can eject or not
			Flying[DeadTeam]=true; // They're flying; create craft when they land
			return DoEjectPilot; 
		}
		else {
			// Can't eject, so put back at base by forcing a insta-kill as pilot
			return DeadObject(DeadObjectHandle,DeadObjectHandle,true);
		}
	}


	// _________________________________________________________________
	EjectKillRetCodes ObjectKilled(int DeadObjectHandle, int KillersHandle)
	{
		// We don't care about dead craft, only dead pilots, and also only
		// care about things in the lockstep world
		if(GetCurWorld() != 0) {
			return DoEjectPilot;
		}

		bool WasDeadPerson=IsPerson(DeadObjectHandle);
		if(GetRespawnInVehicle()) // CTF-- force a "kill" back to base
			WasDeadPerson=true;

		return DeadObject(DeadObjectHandle,KillersHandle,WasDeadPerson);
	}


	// _________________________________________________________________
	EjectKillRetCodes ObjectSniped(int DeadObjectHandle, int KillersHandle)
	{
		// We don't care about dead craft, only dead pilots, and also only
		// care about things in the lockstep world
		if(GetCurWorld() != 0) {
			return DoRespawnSafest;
		}

		// Dead person means we must always respawn a new person
		return DeadObject(DeadObjectHandle,KillersHandle,true);
	}


	// _________________________________________________________________
	bool Load(bool missionSave)
	{
		if (missionSave) {
			int i;

			// init bools
			if((b_array) && (b_count))
				for (i = 0; i < b_count; i++)
					b_array[i] = false;

			// init floats
			if((f_array) && (f_count))
				for (i = 0; i < f_count; i++)
					f_array[i] = 0.0f;

			// init handles
			if((h_array) && (h_count))
				for (i = 0; i < h_count; i++)
					h_array[i] = 0;

			// init ints
			if((i_array) && (i_count))
				for (i = 0; i < i_count; i++)
					i_array[i] = 0;

			return true;
		}

		bool ret = true;

		// bools
		if (b_array != NULL)
			ret=ret && Read(b_array, b_count);

		// floats
		if (f_array != NULL)
			ret=ret && Read(f_array, f_count);

		// Handles
		if (h_array != NULL)
			ret=ret && Read(h_array, h_count);

		// ints
		if (i_array != NULL)
			ret=ret && Read(i_array, i_count);

		PUPMgr::Load(missionSave);
		return ret;
	}

	// _________________________________________________________________
	bool PostLoad(bool missionSave)
	{
		if (missionSave)
			return true;

		bool ret = true;

		ConvertHandles(h_array, h_count);

		ret = ret && PUPMgr::PostLoad(missionSave);

		if (DMIsRaceSubtype[MissionType]) {
			for(int i=0;i<MAX_TEAMS;i++) {
				SpawnPointHandles[i]=GetSpawnpointHandle(i);
				_ASSERTE(SpawnPointHandles[i]);
				Vector V=GetSpawnpoint(i);
				SpawnPointPos[3*i+0]=V.x;
				SpawnPointPos[3*i+1]=V.y;
				SpawnPointPos[3*i+2]=V.z;
			}
		}

		// ret = ret && PUPMgr::PostLoad(missionSave);
		return ret;
	}

	// _________________________________________________________________
	bool Save(bool missionSave)
	{
		if (missionSave)
			return true;

		bool ret = true;

		// bools
		if (b_array != NULL)
			ret=ret && Write(b_array, b_count);

		// floats
		if (f_array != NULL)
			ret=ret && Write(f_array, f_count);

		// Handles
		if (h_array != NULL)
			ret=ret && Write(h_array, h_count);

		// ints
		if (i_array != NULL)
			ret=ret && Write(i_array, i_count);

		ret=ret && PUPMgr::Save(missionSave);

		return ret;
	}


	// This function initializes all portals that are labelled PortalA0 ,
	// PortalB1 etc. up to PortalF9.
	// Function by BS-er
	// _________________________________________________________________
	void PortalInit()
	{
		char PortalLabel[10] = "PortalA0";
		Handle TempHandle;
		PortalCount = 0;

		for (int i=0; i<10; i++)
		{
			for (int j=0; j<6; j++)
			{
				PortalLabel[6] = 'A' + j;
				TempHandle = GetHandle(PortalLabel);
				if (IsAround(TempHandle))
				{
					Vector PortalPos;
					GetPosition(TempHandle, PortalPos);
					Handle Nav = BuildObject("ibnav",0,PortalPos);
					SetObjectiveName(Nav, "Portal");
					Portals[PortalCount] = TempHandle;
					PortalGroup[PortalCount++] = j;
					if (PortalCount >= MaxPortals)
					{
						return;
					}
				}
			}
			PortalLabel[6] = 'A';
			PortalLabel[7]++;
		}
	}


	// This function teleports any object within range of a registered portal.
	// It must be called by the Execute function in order to teleport
	// objects as necessary.  It is specifically designed to not teleport
	// pilots or empty vehicles.
	// Function by BS-er
	// _________________________________________________________________
	void PortalFunctions()
	{
		for (int i=0; i < PortalCount; i++)
		{
			if (IsAround(Portals[i]))
			{
				int PortalTeam = GetTeamNum(Portals[i]);
				Handle NearVehicle = GetNearestVehicle(Portals[i]);
				if (IsWithin(NearVehicle,Portals[i], 14))
				{
					Vector PortalPos;
					Vector ObjectPos;
					Vector Front;

					GetPosition(NearVehicle, ObjectPos);
					GetPosition(Portals[i], PortalPos);

					if (ObjectPos.y < PortalPos.y + 7.0)
					{
						int j = i;

						for (int k=0; k<PortalCount-1; k++)
						{
							if (++j >= PortalCount)
							{
								j=0;
							}

							if (PortalGroup[i] == PortalGroup[j] && IsAround(Portals[j]))
							{
								int NearVehicleTeam = GetTeamNum(NearVehicle);

								GetPosition(Portals[j], PortalPos);

								GetFront(NearVehicle, Front);
								PortalPos.x += Front.x * 25.0f;
								PortalPos.z += Front.z * 25.0f;

								Front.x *= 8.0f;
								Front.z *= 8.0f;

								if ((PortalTeam == 0) || (PortalTeam == NearVehicleTeam) ||
									IsAlly(NearVehicle, Portals[i]))
								{
									SetVectorPosition(NearVehicle, PortalPos);
								}
								else
								{
									Front.x *= -3;
									Front.z *= -3;
								}
								SetVelocity(NearVehicle, Front);

								if (IsPlayer(NearVehicle))
								{
									if (GetLocalPlayerTeamNumber() == NearVehicleTeam)
									{
										SetColorFade(1.0,1.0,32767);
										StartSoundEffect("teleport.wav");
									}
								}
								else
								{
									Stop(NearVehicle,0);
								}
								break; // Force this loop to terminate
							}
						}
					}
				}
			}
		}
	}
};

DLLBase *BuildMission(void)
{
	return new Deathmatch01;
}

