#include "..\Shared\DLLBase.h"

#include <string.h>

#define RECYCLER_INITIAL_DISTANCE_AWAY (30.0f)
#define MAX_PLAYERS 16
// # of multiple-user teams are possible in Teamplay
#define MAX_MULTIPLAYER_TEAMS 2

#define ISDF_VS_SCIONS 0

char OutTimeString[1024];
char OutMessagesString[1024];

// RDTSC data
unsigned long CheezyTicksLo;
unsigned long CheezyTicksHi;

//
// Start the cycle timer
//
void CheezyGetTicks()
{
	__asm
		{
			//  					_emit   0x0F                    // rdtsc
			//  						_emit   0x31
			//  						mov     ebx, eax
			//  						_emit   0x0F                    // rdtsc
			//  						_emit   0x31
			//  						sub     eax, ebx
			//  						mov     [oh], eax               // store overhead

			_emit   0x0F                    // rdtsc
				_emit   0x31
				mov     CheezyTicksLo, eax
				mov     CheezyTicksHi, edx
				}
}


class Strategy01 : public DLLBase
{
private:

  int
	i_first,
		TotalGameTime,
		CurrentGameTime,
		ElapsedGameTime,
	  foetimer, // add AI foe 
		CurNumPlayers, // Count of players
		DPIDs[MAX_PLAYERS], // What DPIDs are where in these slots
		NumKills[MAX_PLAYERS], // # of kills they've racked up per slot
		NumDeaths[MAX_PLAYERS], // # of kills they've racked up per slot
		PlayerImplicitTeam[MAX_PLAYERS+1], // Team used in passing to race-determining stuff
		KillLimit, // As specified from the shell
		IsTeamplayGame, // set from shell also
		DefenseTeamNums[MAX_MULTIPLAYER_TEAMS],
		OffenseMinTeamNums[MAX_MULTIPLAYER_TEAMS],
		OffenseMaxTeamNums[MAX_MULTIPLAYER_TEAMS],
		RandomSeed,
	  i_last;

	float
	f_first,
		PlayerPos[3*(MAX_PLAYERS+1)], // Where they started out, used in respawning
		f_last;

  bool
	b_first,
	  didInit,
	  b_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()
	Strategy01(void) {
		b_count = &b_last - &b_first - 1;
		b_array = &b_first + 1;

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

		h_count = 0;
		h_array = NULL;

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

	char *GetNextRandomVehicleODF(DPID id)
	{
		// HACK! GetInitialPlayerODF does this work right now
		return GetInitialPlayerVehicleODF(1);
	}

	void SetRandomSeed(unsigned long seed)
	{
		RandomSeed=(int)seed;
	}

	void InitialSetup(void)
	{
		didInit = false;
		PreloadODF("avrecy");
		PreloadODF("ivrecy");
		PreloadODF("fvrecy");
		PreloadODF("fvtank");
		PreloadODF("player");
		PreloadODF("ivtank");
		Init();
	}

	// Bogus-- should be set from NetShell?
	char *GetInitialPlayerVehicleODF(int TheirTeam)
	{
#if ISDF_VS_SCIONS
		if(!(TheirTeam&0x01))
			return "fvscout";
		else
#endif
			return "ivscout";
	}

	// Bogus-- should be set from NetShell?
	char *GetInitialPlayerPilotODF(int TheirTeam)
	{
#if ISDF_VS_SCIONS
		if(!(TheirTeam&0x01))
			return "fspilo";
		else
#endif
			return "ispilo";
	}

	// Bogus-- should be set from NetShell?
	char *GetInitialRecyclerODF(int TheirTeam)
	{
#if ISDF_VS_SCIONS
		if(!(TheirTeam&0x01))
			return "fvrecy";
		else
#endif
			return "ivrecy";
	}

	// Bogus-- should be set from NetShell?
	char *GetInitialScavengerODF(int TheirTeam)
	{
#if ISDF_VS_SCIONS
		if(!(TheirTeam&0x01))
			return "fvscav";
		else
#endif
			return "ivscav";
	}

	// Bogus-- should be set from NetShell?
	char *GetInitialConstructorODF(int TheirTeam)
	{
#if ISDF_VS_SCIONS
		if(!(TheirTeam&0x01))
			return "fvcons";
		else
#endif
			return "ivcons";
	}

	Handle SetupPlayer(int TheirRealTeamNum)
	{
		Handle PlayerH,VehicleH;
		Matrix mat={1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1};
		bool IsOffensivePlayer=false;
		int TheirTeamNum=TheirRealTeamNum;

		if(!IsTeamplayGame) 
			GetSafestSpawnpoint(mat);
		else {
			// Teamplay. Gotta put them near their defensive player
			int i,WhichTeamBlock=-1;
			for(i=0; i<MAX_MULTIPLAYER_TEAMS;i++) {
#ifdef _DEBUG
				if(i<(MAX_MULTIPLAYER_TEAMS-1))
				_ASSERTE(DefenseTeamNums[i] < DefenseTeamNums[i+1]);
#endif
				if(TheirRealTeamNum >=DefenseTeamNums[i])
					WhichTeamBlock=i;
			}
			_ASSERTE(WhichTeamBlock != -1);

			// See if the defensive player for this team hasn't been assigned yet
			if((PlayerPos[3*(DefenseTeamNums[WhichTeamBlock])+0] == 0) &&
				 (PlayerPos[3*(DefenseTeamNums[WhichTeamBlock])+1] == 0) &&
				 (PlayerPos[3*(DefenseTeamNums[WhichTeamBlock])+2] == 0)) {
				GetSafestSpawnpoint(mat);
				PlayerPos[3*(DefenseTeamNums[WhichTeamBlock])+0]=mat.posit.x;
				PlayerPos[3*(DefenseTeamNums[WhichTeamBlock])+1]=mat.posit.y;
				PlayerPos[3*(DefenseTeamNums[WhichTeamBlock])+2]=mat.posit.z;
			}

			// Now put offensive players near their defensive player's position
			if(TheirTeamNum != DefenseTeamNums[WhichTeamBlock]) {
				IsOffensivePlayer=true;

				TheirTeamNum=DefenseTeamNums[WhichTeamBlock]; // For determining race

				mat.posit.x=PlayerPos[3*(DefenseTeamNums[WhichTeamBlock])+0]+(RECYCLER_INITIAL_DISTANCE_AWAY* (OffenseMinTeamNums[WhichTeamBlock]-TheirTeamNum-2));
				mat.posit.z=PlayerPos[3*(DefenseTeamNums[WhichTeamBlock])+2]-RECYCLER_INITIAL_DISTANCE_AWAY;
				mat.posit.y=TerrainFindFloor(mat.posit.x,mat.posit.z);
			}
			else {
				// Defensive player. Re-copy in archived position for them
				mat.posit.x=PlayerPos[3*(DefenseTeamNums[WhichTeamBlock])+0];
				mat.posit.z=PlayerPos[3*(DefenseTeamNums[WhichTeamBlock])+2];
				mat.posit.y=TerrainFindFloor(mat.posit.x,mat.posit.z);
			}
		}

		// In all cases, store where they were created for respawns later
		if((TheirRealTeamNum>0) && ((TheirRealTeamNum)<MAX_PLAYERS)) {
			PlayerPos[3*(TheirRealTeamNum)+0]=mat.posit.x;
			PlayerPos[3*(TheirRealTeamNum)+1]=mat.posit.y;
			PlayerPos[3*(TheirRealTeamNum)+2]=mat.posit.z;
			PlayerImplicitTeam[TheirRealTeamNum]=TheirTeamNum;
		}

		PlayerH=BuildObject(GetInitialPlayerVehicleODF(TheirTeamNum),TheirRealTeamNum,mat);

		if(!IsOffensivePlayer) {

			// Build recycler some distance away
			mat.posit.x+=RECYCLER_INITIAL_DISTANCE_AWAY;
			mat.posit.z+=RECYCLER_INITIAL_DISTANCE_AWAY;
			mat.posit.y=TerrainFindFloor(mat.posit.x,mat.posit.z);
			VehicleH=BuildObject(GetInitialRecyclerODF(TheirTeamNum),TheirRealTeamNum,mat);
			SetGroup(VehicleH,0);

#if 0//def _DEBUG
			mat.posit.x-=RECYCLER_INITIAL_DISTANCE_AWAY;
			mat.posit.y=TerrainFindFloor(mat.posit.x,mat.posit.z);
			VehicleH=BuildObject(GetInitialScavengerODF(TheirTeamNum),TheirRealTeamNum,mat);
			SetGroup(VehicleH,1);

			mat.posit.x-=RECYCLER_INITIAL_DISTANCE_AWAY;
			mat.posit.z+=RECYCLER_INITIAL_DISTANCE_AWAY;
			mat.posit.y=TerrainFindFloor(mat.posit.x,mat.posit.z);
			VehicleH=BuildObject(GetInitialConstructorODF(TheirTeamNum),TheirRealTeamNum,mat);
			SetGroup(VehicleH,2);
#endif

			SetScrap(TheirTeamNum,40); 
		}
		return PlayerH;
	}

	void Init(void)
	{
		int i;
		Handle PlayerH,PlayerEntryH;

		// Ensure variables pre-init'd to zero at the start
		memset(&i_array[0],0x00,i_count*sizeof(int));
		memset(&f_array[0],0x00,f_count*sizeof(float));
		memset(&b_array[0],0x00,b_count*sizeof(bool));

		// Set up some variables based on how things appear in the world
		didInit=true;
		CheezyGetTicks();
		RandomSeed=((CheezyTicksLo & 0x7FFFFFFF)/17);
		TotalGameTime=GetVarItemInt("network.session.ivar1");
		KillLimit=GetVarItemInt("network.session.ivar0");
		IsTeamplayGame=GetVarItemInt("network.session.ivar3");
		if(IsTeamplayGame) {
			for(i=0;i<MAX_MULTIPLAYER_TEAMS;i++)
				GetTeamplayRanges(i,DefenseTeamNums[i],OffenseMinTeamNums[i],OffenseMaxTeamNums[i]);
		}
		int LocalTeamNum=GetLocalPlayerTeamNumber(); // Query this from game

		// The BZN has a player in the world. We need to delete them, as
		// this code (either on this machine or remote machines) handles
		// creation of the proper vehicles in the right places for
		// everyone.
		PlayerEntryH=GetPlayerHandle();
		int PlayerDPID=(int)GetLocalPlayerDPID();
		if(PlayerEntryH) 
			RemoveObject(PlayerEntryH);

		// Do all the one-time server side init of varbs. These varbs are
		// saved out and read in on clientside, if saved in the proper
		// place above. This needs to be done after toasting the initial
		// vehicle
		if((ImServer()) || (!IsNetworkOn()))
		{
			ElapsedGameTime=0;
			if(!CurrentGameTime)
				CurrentGameTime=TotalGameTime*60*10; // convert minutes to 1/10 seconds
			for(int i=0;i<MAX_PLAYERS;i++)
			{
				DPIDs[i]=DPID_UNKNOWN;
				NumKills[i]=0;
				NumDeaths[i]=0;
			}

			if ( ImDedicatedServer() )
			{
				CurNumPlayers=0;
			}
			else
			{
				// And stuff in local player
				DPIDs[0]=PlayerDPID;
				_ASSERTE(DPIDs[0] != DPID_UNKNOWN);
				CurNumPlayers=1;

				// Now create the server's player vehicle.
				PlayerH=SetupPlayer(LocalTeamNum);
				SetAsLocalUserByHandle(PlayerH); 
				AddPilotByHandle(PlayerH);
			}
		}
	}

	void Execute(void)
	{

		ElapsedGameTime++;
		if(CurrentGameTime>0) {
			CurrentGameTime--;
			if(!(CurrentGameTime%10)) {
				sprintf(OutTimeString,"Time Left %02d:%02d\n",(CurrentGameTime/(10*60)),(CurrentGameTime/10)%60);
				StatsPanelClearTimerBox();
				StatsPanelAddToTimerBox(OutTimeString);
			}
			if(!CurrentGameTime)
				SucceedMission(GetTime(),NULL);
		}
		else {
			if(!(ElapsedGameTime%10)) {
				sprintf(OutTimeString,"Mission Time %02d:%02d\n",(ElapsedGameTime/(10*60)),(ElapsedGameTime/10)%60);
				StatsPanelClearTimerBox();
				StatsPanelAddToTimerBox(OutTimeString);
			}
		}
	}

	bool AddPlayer(DPID id, bool IsNewPlayer)
	{
		if (!didInit) {
			Init();
		}
		// Always get this value
		int theirteam=DPIDToTeamNumber(id);

		if(CurNumPlayers == MAX_PLAYERS) {
			StatsPanelAddToMessagesBox("ERROR: DLL out of player slots");
			return false; // Can't add this guy
		}

		// Put them in the kill trackings slots
		int PlayerSlot=-1;
		int iId=(int)id;
		for(int i=0;i<CurNumPlayers;i++)
			if(DPIDs[i]==iId)
				PlayerSlot=i;
		if(PlayerSlot==-1) { // Only add if 'new'
			DPIDs[CurNumPlayers]=iId;
			NumKills[CurNumPlayers]=0;
			NumDeaths[CurNumPlayers]=0;
			CurNumPlayers++;
		}

		if (IsNewPlayer) {
			Handle PlayerH=SetupPlayer(theirteam);
			SetAsRemoteUserByHandle(PlayerH,id);
			AddPilotByHandle(PlayerH);
		}

		sprintf(OutMessagesString,"Player %s has entered the world.\n",DPIDToPlayerName(id));
		StatsPanelAddToMessagesBox(OutMessagesString);

		return 1; // BOGUS: always assume successful
	}

	void DeletePlayer(DPID id)
	{
		int i,PlayerSlot=-1;
		int iId=(int)id;
		for(i=0;i<CurNumPlayers;i++)
			if(DPIDs[i]==iId)
				PlayerSlot=i;
		if(PlayerSlot!=-1) { // Only delete if found
			for(i=PlayerSlot;i<(MAX_PLAYERS-1);i++) {
				DPIDs[i]=DPIDs[i+1];
				NumKills[i]=NumKills[i+1];
				NumDeaths[i]=NumDeaths[i+1];
			}
			DPIDs[MAX_PLAYERS-1]=DPID_UNKNOWN;
			NumKills[MAX_PLAYERS-1]=NumDeaths[MAX_PLAYERS-1]=0;
			CurNumPlayers--;
		}
	}

	EjectKillRetCodes PlayerEjected(void)
	{
		return DoEjectPilot; //DoRandomRespawnSafest;
	}

	EjectKillRetCodes ObjectKilled(int DeadObjectHandle,int KillersHandle)
	{
		if(GetCurWorld() != 0) {
			return DoEjectPilot;
		}

		int TheirTeam=-1;

		DPID PDPID=HandleToDPID(DeadObjectHandle);
		int iPDPID=(int)PDPID;
		DPID KDPID=HandleToDPID(KillersHandle);
		int iKDPID=(int)KDPID;

		// We don't care about dead craft, only dead pilots
		if(!IsPerson(DeadObjectHandle)) {
			return DoEjectPilot;
		}

		int i,PlayerSlot=-1;
		for(i=0;i<CurNumPlayers;i++)
			if(DPIDs[i]==iKDPID)
				PlayerSlot=i;

		if(PDPID != KDPID)
			NumKills[PlayerSlot]++;
		else
			NumKills[PlayerSlot]--; // Suicide counts as -1 kill

		PlayerSlot=-1;
		for(i=0;i<CurNumPlayers;i++)
			if(DPIDs[i]==iPDPID)
				PlayerSlot=i;
		if(PlayerSlot!=-1)
			NumDeaths[PlayerSlot]++;

		if(PDPID!=DPID_UNKNOWN)
			TheirTeam=DPIDToTeamNumber(PDPID);

		// Check to see if we have a winner!
		if((KillLimit) && (NumKills[PlayerSlot]==KillLimit))
			SucceedMission(GetTime(),NULL);

		if(PDPID != KDPID)
			sprintf(OutMessagesString,"Player %s killed by %s.",DPIDToPlayerName(PDPID),
							DPIDToPlayerName(KDPID));
		else 
			sprintf(OutMessagesString,"Player %s terminates theirself.",DPIDToPlayerName(PDPID));
		StatsPanelAddToMessagesBox(OutMessagesString);

		for(i=0;i<CurNumPlayers;i++) {
			if(KillLimit && ((NumKills[PlayerSlot]+1)==KillLimit))
				sprintf(OutMessagesString,"Player %s Deaths %d Kills %d (Match Point)",
								DPIDToPlayerName(DPIDs[i]), NumDeaths[i],NumKills[i]);
			else
				sprintf(OutMessagesString,"Player %s Deaths %d Kills %d",DPIDToPlayerName(DPIDs[i]),
								NumDeaths[i],NumKills[i]);
			StatsPanelAddToMessagesBox(OutMessagesString);
		}

		// Time to respawn a pilot
		if(TheirTeam!=-1) {
			Vector Pos;
			Pos.x=PlayerPos[3*(TheirTeam)+0];
			Pos.z=PlayerPos[3*(TheirTeam)+2];
			Pos.y=TerrainFindFloor(Pos.x,Pos.z)+10.0f;

			Handle PlayerH=BuildObject(GetInitialPlayerPilotODF(PlayerImplicitTeam[TheirTeam]),TheirTeam,Pos);
			if(TheirTeam==GetLocalPlayerTeamNumber())
				SetAsLocalUserByHandle(PlayerH); 
			else
				SetAsRemoteUserByHandle(PlayerH,PDPID);
			AddPilotByHandle(PlayerH);
			return DLLHandled;
		}
		else return DoEjectPilot;
	}

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

			if (didInit)
				return true;

			// 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] = 99999.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)
			Read(b_array, b_count);

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

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

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

		return ret;
	}

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

		bool ret = true;

		ConvertHandles(h_array, h_count);

		return ret;
	}

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

		bool ret = true;

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

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

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

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

		return ret;
	}
};

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