/*
	This file is part of Warzone 2100.
	Copyright (C) 1999-2004  Eidos Interactive
	Copyright (C) 2005-2007  Warzone Resurrection Project

	Warzone 2100 is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	Warzone 2100 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Warzone 2100; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
 * MultiSync.c
 *
 * synching issues
 * This file handles the constant backstream of net info, checking that recvd info
 * is concurrent with the local world, and correcting as required. Magic happens here.
 *
 * All conflicts due to non-guaranteed messaging are detected/resolved here.
 *
 * Alex Lee, pumpkin Studios, bath.
 */

#include "lib/framework/frame.h"
#include "stats.h"
#include "lib/gamelib/gtime.h"
#include "map.h"
#include "objects.h"
#include "display.h"								// for checking if droid in view.
#include "order.h"
#include "action.h"
#include "hci.h"									// for byte packing funcs.
#include "display3ddef.h"							// tile size constants.
#include "console.h"
#include "text.h"									// to access strings
#include "geometry.h"								// for gettilestructure
#include "mapgrid.h"								// for move droids directly.
#include "lib/netplay/netplay.h"
#include "multiplay.h"
#include "frontend.h"								// for titlemode
#include "multistat.h"
#include "power.h"									// for power checks
// ////////////////////////////////////////////////////////////////////////////
// function definitions
BOOL		sendCheck			(VOID);							// send/recv  check info
BOOL		recvDroidCheck		(NETMSG *pMsg);
BOOL		recvStructureCheck	(NETMSG *pMsg);
BOOL		recvPowerCheck		(NETMSG *pMsg);
BOOL		recvPing			(NETMSG *pMsg);
BOOL		sendScoreCheck		(VOID);							//score

static BOOL sendStructureCheck	(VOID);							//Structure
BOOL		sendPowerCheck		(BOOL now);						//power
static void packageCheck		(UDWORD i, NETMSG *pMsg, DROID *pD);

static BOOL sendDroidCheck		(VOID);							//droids
UDWORD		averagePing			(VOID);
BOOL		sendPing			(VOID);							// send/recv Ping information

static void highLevelDroidUpdate(DROID *psDroid,
								 UDWORD x,
								 UDWORD y,
								 UDWORD state,
								 UDWORD order,
								 //UDWORD orderX,
								 //UDWORD orderY,
								 BASE_OBJECT *psTarget,
								 UDWORD	numKills);


static void onscreenUpdate		(DROID *pDroid,UDWORD dam,		// the droid and its damage
								 UDWORD x, UDWORD y,			// the ideal position
								 FRACT fx,FRACT fy,				// the ideal fractional position
								 UWORD dir,					// direction it should facing
								 DROID_ORDER order);			// what it should be doing

static void offscreenUpdate		(DROID *pDroid,UDWORD dam,
								 UDWORD x, UDWORD y,
								 FRACT fx,FRACT fy,
								 UWORD dir,
								 DROID_ORDER order);

// ////////////////////////////////////////////////////////////////////////////
// Defined numeric values
#define AV_PING_FREQUENCY	45000					// how often to update average pingtimes. in approx millisecs.
#define PING_FREQUENCY		12000					// how often to update pingtimes. in approx millisecs.
#define STRUCT_FREQUENCY	700						// how often (ms) to send a structure check.
#define DROID_FREQUENCY		1000					// how ofter (ms) to send droid checks
#define POWER_FREQUENCY		10000					// how often to send power levels
#define SCORE_FREQUENCY		25000					// how often to update global score.

#define SYNC_PANIC			40000					// maximum time before doing a dirty fix.

static UDWORD				PingSend[MAX_PLAYERS];	//stores the time the ping was called.

// ////////////////////////////////////////////////////////////////////////////
// test traffic level.
static BOOL okToSend()
{
	//update checks	& go no further if any exceeded.
	if((NETgetRecentBytesSent() + NETgetRecentBytesRecvd()) >= game.bytesPerSec)
	{
		return FALSE;
	}

	if( NETgetRecentPacketsSent() >= game.packetsPerSec )
	{
		return FALSE;
	}

	return TRUE;
}

// ////////////////////////////////////////////////////////////////////////////
// Droid checking info. keep position and damage in sync.
BOOL sendCheck(VOID)
{
	UDWORD i;

	NETgetBytesSent();			// update stats.
	NETgetBytesRecvd();
	NETgetPacketsSent();
	NETgetPacketsRecvd();

	// dont send checks till all players are present.
	for(i=0;i<MAX_PLAYERS;i++)
	{
		if(isHumanPlayer(i) && ingame.JoiningInProgress[i])
		{
			return TRUE;
		}
	}
	// send Checks. note each send has it's own send criteria, so might not send anything.
	if(okToSend())
	{
		sendPing();
	}
	if(okToSend())
	{
		sendStructureCheck();
	}
	if(okToSend())
	{
		sendPowerCheck(FALSE);
	}
	if(okToSend())
	{
		sendScoreCheck();
	}
	if(okToSend())
	{
		sendDroidCheck();
	}
	return TRUE;
}

// ////////////////////////////////////////////////////////////////////////////
// pick a droid to send, NULL otherwise.
static DROID* pickADroid(VOID)
{
	DROID			*pD=NULL;						// current droid we're checking
	UDWORD			i;
	static UDWORD	droidnum=0;						// how far down the playerlist to go.
	static UDWORD	player=0;						// current player we're checking
	static UDWORD	maxtrys=0;

	if ( !myResponsibility(player) )				// dont send stuff that's not our problem.
	{
		player ++;									// next player next time.
		player = player%MAX_PLAYERS;
		droidnum =0;

		if(maxtrys<MAX_PLAYERS)
		{
			maxtrys++;
			return pickADroid();
		}
		else
		{
			maxtrys =0;
			return NULL;
		}
	}

	pD = apsDroidLists[player];						// get the droid to send to everyone
	for(i=0;												// CRITERIA FOR PICKADROID
			(i<droidnum)									//not yet done.
			&& (pD != NULL)									//still here.
		;i++)//
	{
		pD= pD->psNext;
	}

	if (pD == NULL)								// droid is no longer there or list end.
	{
		player ++;										// next player next time.
		player = player%MAX_PLAYERS;
		droidnum=0;

		if(maxtrys<MAX_PLAYERS)
		{
			maxtrys++;
			return pickADroid();
		}
		else
		{
			maxtrys =0;
			return NULL;
		}
	}

	droidnum++;
	maxtrys = 0;

	return pD;
}

// ///////////////////////////////////////////////////////////////////////////
// send a droid info packet.
static BOOL sendDroidCheck(VOID)
{
	DROID			*pD;
	NETMSG			msg;
	UDWORD			i=0,count=0;
	static UDWORD	lastSent=0;					// last time a struct was sent.
	UDWORD			toSend =4;
	if(lastSent > gameTime)lastSent= 0;
	if((gameTime-lastSent) < DROID_FREQUENCY)	// only send a struct send if not done recently.
	{
		return TRUE;
	}

	lastSent = gameTime;
	msg.size = 0;

	switch(game.bytesPerSec)
	{
	case IPXBYTESPERSEC:
		toSend = 6;
		break;

	case INETBYTESPERSEC:
	case CABLEBYTESPERSEC:
	case DEFAULTBYTESPERSEC:
	case MODEMBYTESPERSEC:
	default:
		toSend = 4;
		break;
	}

	while(count < toSend)	// send x droids.
	{

		pD = pickADroid();

		if(pD)
		{
			packageCheck(i,&msg,pD);
		}

		i = msg.size;
		count = count +1;
	}

	msg.type = NET_CHECK_DROID;
	NETbcast(&msg,FALSE);

	return TRUE;
}

// ////////////////////////////////////////////////////////////////////////////
// Send a Single Droid Check message
static void packageCheck(UDWORD i, NETMSG *pMsg, DROID *pD)
{
//	UDWORD packtemp;
	UWORD numkills;		//

	pMsg->body[			i+0] =		(CHAR)pD->player;
	pMsg->body[			i+1] =		(CHAR)pD->order;		// order being executed
	NetAdd2( pMsg,		i+2,		pD->id);				// droid id
	NetAdd2( pMsg,		i+6,		pD->secondaryOrder );
	NetAdd2( pMsg,		i+10,		pD->body);				// damage points
	NetAdd2( pMsg,		i+14,		pD->direction);			// direction

	if(  pD->order == DORDER_ATTACK || pD->order == DORDER_MOVE || pD->order == DORDER_RTB    || pD->order == DORDER_RTR)
	{
		NetAdd2(pMsg,	i+16,		pD->sMove.fx);			//fraction move pos
		NetAdd2(pMsg,	i+20,		pD->sMove.fy);
	}
	else
	{
		NetAdd2(pMsg,	i+16,		pD->x);					//non fractional move pos
		NetAdd2(pMsg,	i+20,		pD->y);
	}

	if(pD->order == DORDER_ATTACK)
	{
		NetAdd2(pMsg,	i+24,		pD->psTarget->id);		// target id
	}
	else if(pD->order == DORDER_MOVE)
	{
		NetAdd2(pMsg,	i+24,		pD->orderX);
		NetAdd2(pMsg,	i+26,		pD->orderY);
	}
	numkills = pD->numKills;
	NetAdd2(pMsg,		i+28,		numkills);	// droid kills

	pMsg->size =(UWORD)( pMsg->size + 30);

	return ;
}


// ////////////////////////////////////////////////////////////////////////////
// receive a check and update the local world state accordingly
BOOL recvDroidCheck(NETMSG *m)
{
	FRACT			fx=0,fy=0;
	UDWORD			ref,player,x = 0,y = 0,bod,target=0;//,dir;
	UWORD			dir,numkills;
	DROID_ORDER		ord;
	BOOL			onscreen;
	DROID			*pD;
	BASE_OBJECT		*psTarget=NULL;
	UDWORD			i,state, tx=0,ty=0;

	i = 0;
	while(i < m->size)
	{
		// obtain information about remote droid.
		player = m->body[i+0];
		ord = (DROID_ORDER)m->body[i+1];								// droid id.
		NetGet(m,					i+2,ref);
		NetGet(m,					i+6,state);
		NetGet(m,					i+10,bod);							// Damage update.
		NetGet(m,					i+14,dir);

		if(   ord == DORDER_ATTACK || ord == DORDER_MOVE
		   || ord == DORDER_RTB    || ord == DORDER_RTR)	// detailed position info mode
		{
			NetGet(m,				i+16,fx);
			NetGet(m,				i+20,fy);
		}
		else
		{
			NetGet(m,				i+16,x);				// dont pack since could be sending fractional vals anyway.
			NetGet(m,				i+20,y);
		}

		if(ord == DORDER_ATTACK)
		{
			NetGet(m,				i+24,target);
		}
		else if(ord == DORDER_MOVE)
		{
			NetGet(m,				i+24,tx);
			NetGet(m,				i+26,ty);
		}
		NetGet(m,					i+28,numkills);

		i = i + 30;

		//////////////////////////////////////

		if ( !(IdToDroid(ref,player,&pD)) )				// find the droid in question
		{
			NETlogEntry("Recvd Unknown droid info. val=player",0,player);
			debug( LOG_NEVER, "Received Checking Info for an unknown (As yet) droid player:%d ref:%d\n", player, ref );
			return TRUE;								//Recvd checking info for an unknown droid
		}

		if(target)
		{
			psTarget = IdToPointer(target,ANYPLAYER);	// get the target in question.
		}

		//////////////////////////////////////
		// decide how to sync it.
		if( DrawnInLastFrame(pD->sDisplay.frameNumber)
			&& (pD->sDisplay.screenX < DISP_WIDTH)
			&& (pD->sDisplay.screenY < DISP_HEIGHT)		)  // check for onscreen
		{
			if(pD->visible[selectedPlayer])
			{
				onscreen = TRUE;						// onscreen and visible
			}
			else
			{
				onscreen = FALSE;						// onscreen, but not visible.
			}
		}else{
			onscreen = FALSE;							// not onscreen.
		}


		if(ingame.PingTimes[player] > PING_LIMIT)		// if it's a big ping then don't do a smooth move.
		{
			onscreen = FALSE;
		}

//		if( pD->lastSync > gameTime)pD->lastSync =0;
//		if(  (gameTime - pD->lastSync) > SYNC_PANIC )	// if it's been a while then jump it.
//		{
//			onscreen = FALSE;
//		}


		//////////////////////////////////////
		/// now do the update.

		if (   onscreen
		    || vtolDroid(pD))
		{
			onscreenUpdate(pD,bod,x,y,fx,fy,dir,ord);
		}
		else
		{
			offscreenUpdate(pD,bod,x,y,fx,fy,dir,ord);
		}

		//////////////////////////////////////
		// now make note of how accurate the world model is for this droid.	// if droid is close then remember.
		if(    abs(x-pD->x)<(TILE_UNITS*2)
			|| abs(y- pD->y)<(TILE_UNITS*2))
		{
			pD->lastSync	= gameTime;								// note we did a reasonable job.
		}


		//////////////////////////////////////
		// now update the higher level stuff
		if(!vtolDroid(pD))
		{
			highLevelDroidUpdate(pD,x,y,state,  ord, psTarget,numkills);
		}

		// done this droid, move on to next.

	}

	return TRUE;
}

// ////////////////////////////////////////////////////////////////////////////

// ////////////////////////////////////////////////////////////////////////////
// higher order droid updating. Works mainly at the order level. comes after the main sync.
static void highLevelDroidUpdate(DROID *psDroid,UDWORD x, UDWORD y,
								 //UDWORD state, UDWORD order,UDWORD orderX,UDWORD orderY,
								 UDWORD state, UDWORD order,
								 BASE_OBJECT *psTarget,UDWORD numKills)
{
	// update kill rating.
	psDroid->numKills = (UWORD)numKills;

	// remote droid is attacking, not here tho!
	if(order == DORDER_ATTACK && psDroid->order != DORDER_ATTACK && psTarget)
	{
		turnOffMultiMsg(TRUE);
		orderDroidObj(psDroid, DORDER_ATTACK, psTarget);
		turnOffMultiMsg(FALSE);
	}
	// secondary orders.
	if(psDroid->secondaryOrder != state)
	{
		psDroid->secondaryOrder = state;
	}


	// see how well the sync worked, optionally update.
	// offscreen updates will make this ok each time.
	if(psDroid->order == DORDER_NONE && order == DORDER_NONE)
	{
		if(  (abs(x- psDroid->x)>(TILE_UNITS*2))		// if more than 2 tiles wrong.
		   ||(abs(y- psDroid->y)>(TILE_UNITS*2)) )
		{
			turnOffMultiMsg(TRUE);
			orderDroidLoc(psDroid, DORDER_MOVE,x,y);
			turnOffMultiMsg(FALSE);
		}
	}

}

// ////////////////////////////////////////////////////////////////////////////
// droid on screen needs modifying
static void onscreenUpdate(DROID *psDroid,
						   UDWORD dam,
						   UDWORD x,
						   UDWORD y,
						   FRACT fx,
						   FRACT fy,
						   UWORD dir,
						   DROID_ORDER order)
{

	BASE_OBJECT *psClickedOn;
	BOOL		bMouseOver = FALSE;

	psClickedOn = mouseTarget();
	if( psClickedOn != NULL && psClickedOn->type == OBJ_DROID)
	{
		if(psClickedOn->id == psDroid->id && mouseDown(MOUSE_RMB))
		{
			bMouseOver = TRUE;						// override, so you dont see the updates.
		}
	}

	if(!bMouseOver)
	{
		psDroid->body = dam;						// update damage
	}

//	if(psDroid->order == DORDER_NONE || (psDroid->order == DORDER_GUARD && psDroid->action == DACTION_NONE) )
//	{
//		psDroid->direction	 = dir  %360;				//update rotation
//	}

	return;
}

// ////////////////////////////////////////////////////////////////////////////
// droid offscreen needs modyfying.
static void offscreenUpdate(DROID *psDroid,
							UDWORD dam,
							UDWORD x,
							UDWORD y,
							FRACT fx,
							FRACT fy,
							UWORD dir,
							DROID_ORDER order)
{
	UDWORD				oldx,oldy;
	PROPULSION_STATS	*psPropStats;
 	SDWORD			xdiff,ydiff, distSq;

	// stage one, update the droids position & info, LOW LEVEL STUFF.
	if(	   order == DORDER_ATTACK
		|| order == DORDER_MOVE
		|| order ==	DORDER_RTB
		|| order == DORDER_RTR)	// move order
	{

		// calculate difference between remote and local
		xdiff = psDroid->x - (UWORD)fx;
	    ydiff = psDroid->y - (UWORD)fy;
	    distSq = (xdiff*xdiff) + (ydiff*ydiff);

		// if more than  2 squares, jump it.
		if(distSq > (2*TILE_UNITS)*(2*TILE_UNITS) )
		{
			if( ((UDWORD)fx != 0) && ((UDWORD)fy != 0) )
			{
				oldx = psDroid->x;
				oldy = psDroid->y;

				psDroid->sMove.fx = fx;							//update x
				psDroid->sMove.fy = fy;							//update y

				psDroid->x		 = (UWORD) fx;					//update move progress
				psDroid->y		 = (UWORD) fy;
				gridMoveObject((BASE_OBJECT *)psDroid, (SDWORD)oldx,(SDWORD)oldy);

				psDroid->direction	= (UWORD)(dir %360);		// update rotation

				// reroute the droid.
				turnOffMultiMsg(TRUE);
				moveDroidTo(psDroid, psDroid->sMove.DestinationX,psDroid->sMove.DestinationY);
				turnOffMultiMsg(FALSE);

			}
		}
	}
	else
	{
		oldx = psDroid->x;
		oldy = psDroid->y;
		psDroid->x		 = (UWORD)x;						//update x
		psDroid->y		 = (UWORD)y;						//update y
		gridMoveObject((BASE_OBJECT *)psDroid, (SDWORD)oldx,(SDWORD)oldy);
		psDroid->direction	= (UWORD)(dir %360);				// update rotation
	}

	psDroid->body		= dam;								// update damage

	// stop droid if remote droid has stopped.
	if(		(order == DORDER_NONE		   || order == DORDER_GUARD)
		&& !(psDroid->order == DORDER_NONE || psDroid->order == DORDER_GUARD )
	   )
	{
		turnOffMultiMsg(TRUE);
		moveStopDroid(psDroid);
		turnOffMultiMsg(FALSE);
	}


	// snap droid(if on ground)  to terrain level at x,y.
	psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat;
	ASSERT( PTRVALID(psPropStats, sizeof(PROPULSION_STATS)),"offscreenUpdate: invalid propulsion stats pointer" );
	if(	psPropStats->propulsionType != LIFT )		// if not airborne.
	{
		psDroid->z = map_Height(psDroid->x, psDroid->y);
	}
	return;
}





// ////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////
// Structure Checking, to ensure smoke and stuff is consistent across machines.
// this func is recursive!
static  STRUCTURE *pickAStructure(void)
{
	static UDWORD	player=0;					// player currently checking.
	static UDWORD	snum=0;						// structure index for this player.
	STRUCTURE		*pS=NULL;
	static UDWORD	maxtrys = 0;				// don't loop forever if failing/.
	UDWORD			i;

	if ( !myResponsibility(player) )			// dont send stuff that's not our problem.
	{
		player ++;								// next player next time.
		player = player%MAX_PLAYERS;
		snum =0;

		if(maxtrys<MAX_PLAYERS)
		{
			maxtrys ++;
			return pickAStructure();
		}
		else
		{
			maxtrys = 0;
			return NULL;
		}
	}

	pS = apsStructLists[player];				// find the strucutre
	for(i=0; ((i<snum) && (pS != NULL)) ;i++)
	{
		pS= pS->psNext;
	}


	if (pS == NULL)								// last structure or no structures at all
	{
		player ++;								// go onto the next player
		player = player%MAX_PLAYERS;
		snum=0;

		if(maxtrys<MAX_PLAYERS)
		{
			maxtrys ++;
			return pickAStructure();
		}
		else
		{
			maxtrys = 0;
			return NULL;
		}
	}
	snum ++;										// next structure next time

	maxtrys = 0;
	return pS;
}

// ////////////////////////////////////////////////////////////////////////
// Send structure information.
static BOOL sendStructureCheck(VOID)
{
	static UDWORD	lastSent=0;					// last time a struct was sent.
	NETMSG			m;
	STRUCTURE		*pS;
        UBYTE capacity;

	if(lastSent > gameTime)lastSent= 0;
	if((gameTime-lastSent) < STRUCT_FREQUENCY)	// only send a struct send if not done recently.
	{
		return TRUE;
	}
	lastSent = gameTime;


	pS = pickAStructure();
	if(pS && (pS->status == SS_BUILT))			// only send info about complete buildings.
	{
		m.body[0] = (char)pS->player;			// send struct details
		NetAdd(m,1,pS->id);

		NetAdd(m,5,pS->body);					// damage
		NetAdd(m,7,pS->pStructureType->ref);	// building type.
		NetAdd(m,11,pS->x);						//position
		NetAdd(m,13,pS->y);
		NetAdd(m,15,pS->z);
		NetAdd(m,17,pS->direction);

	    m.type = NET_CHECK_STRUCT;
	    m.size = 19;

		// functionality.
		if (pS->pStructureType->type == REF_RESEARCH)
		{
			capacity = (UBYTE)((RESEARCH_FACILITY*)pS->pFunctionality)->capacity;
			NetAdd(m,19,capacity);
			m.size +=1;
		}
		if (pS->pStructureType->type == REF_FACTORY OR
//			pS->pStructureType->type == REF_CYBORG_FACTORY OR
			pS->pStructureType->type == REF_VTOL_FACTORY)
		{
			capacity = (UBYTE)((FACTORY*)pS->pFunctionality)->capacity;
			NetAdd(m,19,capacity);
			m.size +=1;

		}
		if (pS->pStructureType->type == REF_POWER_GEN)
		{
			capacity = (UBYTE)((POWER_GEN*)pS->pFunctionality)->capacity;
			NetAdd(m,19,capacity);
			m.size +=1;
		}

		NETbcast(&m,FALSE);
	}
	return TRUE;
}

// receive checking info about a structure and update local world state
BOOL recvStructureCheck( NETMSG *m)
{
	UWORD	x,y,z,dir;
	UDWORD ref,type,j;
	UBYTE	i,player;
	UBYTE	cap;
	STRUCTURE *pS;
	STRUCTURE_STATS *psStats;

	player = m->body[0];
	NetGet(m,1,ref);

	pS = IdToStruct(ref,player);
	if(pS)
	{
		NetGet(m,5,pS->body);							// Damage update.
		NetGet(m,17,pS->direction);
	}
	else												// structure wasn't found, create it.
	{
		NetGet(m,7,type);
		NetGet(m,11,x);
		NetGet(m,13,y);
		NetGet(m,15,z);
		NetGet(m,17,dir);

		NETlogEntry("scheck:structure check failed, adding struct. val=type",0,type-REF_STRUCTURE_START);

		for(i=0; ( i<numStructureStats ) && (asStructureStats[i].ref != type); i++);
		psStats = &(asStructureStats[i]);

		// check for similar buildings, to avoid overlaps
		if( TILE_HAS_STRUCTURE(mapTile(x>>TILE_SHIFT,y>>TILE_SHIFT))   )
		{
			NETlogEntry("scheck:Tile has structure val=player",0,player);

			// if correct type && player then complete & modify
			pS = getTileStructure(x>>TILE_SHIFT, y>>TILE_SHIFT);

			if( pS
				&& (pS->pStructureType->type == type )
				&& (pS->player == player )
			  )
			{
				pS->direction = dir;
				pS->id		= ref;
				if(pS->status != SS_BUILT)
				{
					pS->status	= SS_BUILT;
					buildingComplete(pS);
				}
				NETlogEntry("scheck: fixed?",0,player);
			}
			// wall becoming a cornerwall
			else if(pS->pStructureType->type == REF_WALL )
			{
				if( psStats->type == REF_WALLCORNER)
				{
					NETlogEntry("scheck: fixed wall->cornerwall",0,0);
					removeStruct(pS, TRUE);

					powerCalc(FALSE);	// turn off power
					pS=buildStructure((STRUCTURE_STATS *)psStats, x , y ,player,TRUE);
					powerCalc(TRUE);	//turn on power

					if(pS)
					{
						pS->id = ref;
					}
					else
					{
						NETlogEntry("scheck: failed to upgrade wall!",0,player);
						return FALSE;
					}
				}
			}
			else
			{
				NETlogEntry("scheck:Tile did not have correct type or player val=player",0,player);
				return FALSE;
		    }
			// else remove local copy. with a bang (make it look like an explosion, itll update next time around).
			// ?? dunno if we should do this!
	//		return TRUE;					// structure exists already there....
		}
		else
		{
			NETlogEntry("scheck: didn't find structure at all, building it",0,0);

//			buildFlatten(psStats, x,y,z);
//			levelGround(x,y,z,
//				((STRUCTURE_STATS *)psStats)->baseWidth,
//				((STRUCTURE_STATS *)psStats)->baseBreadth);

			powerCalc(FALSE);	// turn off power
			pS = buildStructure((STRUCTURE_STATS *)psStats, x , y ,player,TRUE);
			powerCalc(TRUE);	//turn on power

		}
	}

	if(pS)
	{

		if( pS->status != SS_BUILT)							// check its finished
		{
			pS->direction = dir;
			pS->id = ref;
			pS->status = SS_BUILT;
			buildingComplete(pS);
		}

		if(m->size > 19)									// capacity
		{
			NetGet(m,19,i);

			switch(pS->pStructureType->type)				// current capacity
			{
				case REF_RESEARCH:
					cap = (UBYTE)((RESEARCH_FACILITY*)pS->pFunctionality)->capacity;
					break;
				case REF_FACTORY:
				case REF_VTOL_FACTORY:
	//			case REF_CYBORG_FACTORY:
					cap = (UBYTE)((FACTORY*)pS->pFunctionality)->capacity;
					break;
				case REF_POWER_GEN:
					cap = (UBYTE)((POWER_GEN*)pS->pFunctionality)->capacity;
					break;
				default:
					NETlogEntry("unknown upgrade error in recv struct check",0,0);
					cap =0;
					break;
			}

			if(cap != i)									// compare and upgrade
			{
				switch(pS->pStructureType->type)
				{
				case REF_RESEARCH:
					//for (j = 0; (j<numStructureStats) && (asStructureStats[j].type != REF_RESEARCH_MODULE);j++);
					j = researchModuleStat;
					break;

				case REF_FACTORY:
				case REF_VTOL_FACTORY:
	//			case REF_CYBORG_FACTORY:
					//for (j = 0; (j<numStructureStats) && (asStructureStats[j].type != REF_FACTORY_MODULE);j++);
					j = factoryModuleStat;
					break;

				case REF_POWER_GEN:
					//for (j = 0; (j<numStructureStats) && (asStructureStats[j].type != REF_POWER_MODULE);j++);
					j = powerModuleStat;
					break;

				default:
					j=0;
					ASSERT( FALSE,"Unknown Upgrade in structure checking!" );
					return TRUE;
					break;
				}

				i = (UBYTE)(i - cap);
				while(i>0)
				{
					buildStructure(&asStructureStats[j],pS->x,pS->y,pS->player,FALSE);
					if(pS && pS->status != SS_BUILT)					// check its finished again.
					{
						pS->id = ref;
						pS->status = SS_BUILT;
						buildingComplete(pS);
					}
					i = (UBYTE)(i - 1);
				}

			}
		}
	}


	return TRUE;
}


// ////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////
// Power Checking. Send a power level check every now and again.
BOOL sendPowerCheck(BOOL now)
{
	NETMSG	m;
	static UDWORD lastsent = 0;

	if(!now)
	{
		if(lastsent > gameTime)lastsent= 0;
		if((gameTime-lastsent) < POWER_FREQUENCY )	// only send if not done recently.
		{
			return TRUE;
		}
	}

	lastsent = gameTime;

	// ok send a power check.
	m.body[0] = (char)selectedPlayer;

	NetAdd(m,1, asPower[selectedPlayer]->currentPower );

	m.size = 5;
	m.type = NET_CHECK_POWER;
	return(NETbcast(&m,FALSE));
}

BOOL recvPowerCheck(NETMSG *pMsg)
{
	UDWORD player,b;

	player = pMsg->body[0];
	NetGet(pMsg,1, b);
	setPower(player,b);

	return TRUE;
}

// ////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////
// Score
BOOL sendScoreCheck(VOID)
{
	static UDWORD	lastsent = 0;
	UDWORD			i;
	PLAYERSTATS		stats;
	NETMSG			m;

	if(lastsent > gameTime)lastsent= 0;
	if((gameTime-lastsent) < SCORE_FREQUENCY )	// only send if not done recently.
	{
		return TRUE;
	}
	lastsent = gameTime;

	// syncronise scores.

	// update local score
	stats = getMultiStats(selectedPlayer,TRUE);

	stats.recentKills += stats.killsToAdd;		// add recently scored points.
	stats.totalKills  += stats.killsToAdd;
	stats.recentScore += stats.scoreToAdd;
	stats.totalScore  += stats.scoreToAdd;
	stats.killsToAdd =0;
	stats.scoreToAdd =0;

	setMultiStats(player2dpid[selectedPlayer],stats,TRUE);	// store local version.
	setMultiStats(player2dpid[selectedPlayer],stats,FALSE);	// send score to the ether

	// broadcast any changes in other players, but not in FRONTEND!!!
	if(titleMode != MULTIOPTION && titleMode != MULTILIMIT)
	{
		m.size =0;
		for(i = 0; i<MAX_PLAYERS; i++)
		{
			if(isHumanPlayer(i) && (i!=selectedPlayer))
			{
				stats=getMultiStats(i,TRUE);
				if(stats.killsToAdd || stats.scoreToAdd  )
				{
					m.body[m.size] = (UBYTE)i;
					m.size += 1;
					NetAdd(m,m.size,stats.killsToAdd);
					m.size += sizeof(stats.killsToAdd);
					NetAdd(m,m.size,stats.scoreToAdd);
					m.size += sizeof(stats.scoreToAdd);
				}
			}
		}
		if(m.size != 0)
		{
			m.body[m.size] = ANYPLAYER;	// terminate msg.
			m.size += 1;
			m.type = NET_SCORESUBMIT;
			NETbcast(&m,FALSE);
		}
	}

	// get global versions of scores.
	for(i = 0; i<MAX_PLAYERS; i++)
	{
		if(isHumanPlayer(i) )
		{
			setMultiStats(player2dpid[i], getMultiStats(i,FALSE) ,TRUE);
		}
	}
	return TRUE;
}



BOOL recvScoreSubmission(NETMSG *pMsg)
{
	UDWORD		player=0,kil,index;
	SDWORD		sco;
	PLAYERSTATS stats;

	// process msg, add to next score addition.
	index = 0;
	while(player != ANYPLAYER)
	{
		player = pMsg->body[index];
		index+=1;
		if(player != ANYPLAYER)
		{
			NetGet(pMsg,index,kil);
			index+=sizeof(kil);
			NetGet(pMsg,index,sco);
			index+=sizeof(sco);

			// do the update.
			if(player == selectedPlayer)
			{
				stats = getMultiStats(player,TRUE);
				stats.killsToAdd += kil;
				stats.scoreToAdd += sco;
				setMultiStats(player2dpid[player],stats,TRUE);	// store local version.
			}

		}
	}
	return TRUE;
}

// ////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////
// Pings

UDWORD averagePing(VOID)
{
	UDWORD i,count,total;

	count =0;
	total =0;

	for(i=0;i<MAX_PLAYERS;i++)
	{
		if(isHumanPlayer(i))
		{
			total += ingame.PingTimes[i];
			count ++;
		}
	}
	return total/count;
}

BOOL sendPing(VOID)
{
	NETMSG	ping;
	UDWORD	i;
	static UDWORD	lastPing=0;						// last time we sent a ping.
	static UDWORD	lastav=0;						// last time we updated average.

	// only ping every so often.
	if(lastPing > gameTime)lastPing= 0;
	if((gameTime-lastPing) < PING_FREQUENCY)
	{
		return TRUE;
	}
	lastPing = gameTime;


	/// if host, also update the average ping stat for joiners.
	if(NetPlay.bHost)
	{
		if(lastav > gameTime)lastav= 0;
		if((gameTime-lastav) > AV_PING_FREQUENCY)
		{
			NETsetGameFlags(2,averagePing());
			lastav=gameTime;
		}
	}

	// before we send the ping, if any player failed to respond to the last one
	// we should re-enumerate the players.

	for (i=0; i<MAX_PLAYERS;i++)
	{
		if( isHumanPlayer(i) && PingSend[i] && ingame.PingTimes[i] && (i!= selectedPlayer) )
		{
	//		CONPRINTF(ConsoleString,(ConsoleString,strresGetString(psStringRes,STR_MUL_RESPOND),getPlayerName(i) ));
			ingame.PingTimes[i] = PING_LIMIT;
		}
		else if( !isHumanPlayer(i) && PingSend[i] && ingame.PingTimes[i] && (i!= selectedPlayer) )
		{
			ingame.PingTimes[i] = 0;
		}
	}

	ping.body[0] = (char)selectedPlayer;
	ping.body[1] = 1;
	ping.size	 = 2;
	ping.type	 = NET_PING;

	for(i=0;i<MAX_PLAYERS;i++)
	{
		PingSend[i] = gameTime2;//clock();
	}
	return(NETbcast(&ping,FALSE));
}

// accept and process incoming ping messages.
BOOL recvPing(NETMSG *ping)
{
	NETMSG reply;

	if (ping->body[1] ==1)									// if this is a new ping
	{
		reply.body[0]	= (char) selectedPlayer;
		reply.body[1]	= 0;
		reply.size		= 2;
		reply.type		= NET_PING;

		NETsend(&reply, player2dpid[(int) ping->body[0]], FALSE);	// reply to it
	}
	else													// else it's returned, so store it.
	{
		ingame.PingTimes[(int) ping->body[0]]	= (gameTime2 - PingSend[(int) ping->body[0]] ) /2 ;
		PingSend[(int) ping->body[0]]		= 0;					// note we've recvd it!
	}
	return TRUE;
}
