1147 lines
28 KiB
C
1147 lines
28 KiB
C
/*
|
|
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 "lib/framework/input.h"
|
|
#include "lib/framework/strres.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 "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
|
|
|
|
static BOOL sendStructureCheck (void); //Structure
|
|
static void packageCheck (UDWORD i, NETMSG *pMsg, DROID *pD);
|
|
static BOOL sendDroidCheck (void); //droids
|
|
|
|
static void highLevelDroidUpdate(DROID *psDroid,
|
|
UDWORD x,
|
|
UDWORD y,
|
|
UDWORD state,
|
|
UDWORD order,
|
|
BASE_OBJECT *psTarget,
|
|
UDWORD numKills);
|
|
|
|
|
|
static void onscreenUpdate (DROID *pDroid,UDWORD dam, // the droid and its damage
|
|
UDWORD x, UDWORD y, // the ideal position
|
|
float fx,float 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,
|
|
float fx,float 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(void)
|
|
{
|
|
//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 = 6;
|
|
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;
|
|
|
|
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; //
|
|
uint16_t direction = pD->direction * 32; // preserve some precision
|
|
|
|
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, 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->pos.x); //non fractional move pos
|
|
NetAdd2(pMsg, i+20, pD->pos.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->experience;
|
|
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)
|
|
{
|
|
float 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 < pie_GetVideoBufferWidth())
|
|
&& (pD->sDisplay.screenY < pie_GetVideoBufferHeight()) ) // 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->pos.x)<(TILE_UNITS*2)
|
|
|| abs(y- pD->pos.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->experience = (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->pos.x)>(TILE_UNITS*2)) // if more than 2 tiles wrong.
|
|
||(abs(y- psDroid->pos.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,
|
|
float fx,
|
|
float 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,
|
|
float fx,
|
|
float 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->pos.x - (UWORD)fx;
|
|
ydiff = psDroid->pos.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->pos.x;
|
|
oldy = psDroid->pos.y;
|
|
|
|
psDroid->sMove.fx = fx; //update x
|
|
psDroid->sMove.fy = fy; //update y
|
|
|
|
psDroid->pos.x = (UWORD) fx; //update move progress
|
|
psDroid->pos.y = (UWORD) fy;
|
|
gridMoveObject((BASE_OBJECT *)psDroid, (SDWORD)oldx,(SDWORD)oldy);
|
|
|
|
psDroid->direction = dir % 360; // update rotation
|
|
|
|
// reroute the droid.
|
|
turnOffMultiMsg(TRUE);
|
|
moveDroidTo(psDroid, psDroid->sMove.DestinationX,psDroid->sMove.DestinationY);
|
|
turnOffMultiMsg(FALSE);
|
|
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
oldx = psDroid->pos.x;
|
|
oldy = psDroid->pos.y;
|
|
psDroid->pos.x = (UWORD)x; //update x
|
|
psDroid->pos.y = (UWORD)y; //update y
|
|
gridMoveObject((BASE_OBJECT *)psDroid, (SDWORD)oldx,(SDWORD)oldy);
|
|
psDroid->direction = 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( psPropStats != NULL, "offscreenUpdate: invalid propulsion stats pointer" );
|
|
if( psPropStats->propulsionType != LIFT ) // if not airborne.
|
|
{
|
|
psDroid->pos.z = map_Height(psDroid->pos.x, psDroid->pos.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.
|
|
{
|
|
uint16_t direction = pS->direction * 32; // save some precision by multiplying by 32
|
|
|
|
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->pos.x); //position
|
|
NetAdd(m,13,pS->pos.y);
|
|
NetAdd(m,15,pS->pos.z);
|
|
NetAdd(m, 17, 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 ||
|
|
// pS->pStructureType->type == REF_CYBORG_FACTORY ||
|
|
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;
|
|
UDWORD ref,type,j;
|
|
UBYTE i,player;
|
|
UBYTE cap;
|
|
STRUCTURE *pS;
|
|
STRUCTURE_STATS *psStats;
|
|
uint16_t direction;
|
|
|
|
player = m->body[0];
|
|
NetGet(m,1,ref);
|
|
|
|
pS = IdToStruct(ref,player);
|
|
if(pS)
|
|
{
|
|
NetGet(m,5,pS->body); // Damage update.
|
|
NetGet(m, 17, direction);
|
|
pS->direction = (float)direction / 32; // preserve some precision
|
|
}
|
|
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, direction);
|
|
|
|
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(map_coord(x), map_coord(y))))
|
|
{
|
|
NETlogEntry("scheck:Tile has structure val=player",0,player);
|
|
|
|
// if correct type && player then complete & modify
|
|
pS = getTileStructure(map_coord(x), map_coord(y));
|
|
|
|
if( pS
|
|
&& (pS->pStructureType->type == type )
|
|
&& (pS->player == player )
|
|
)
|
|
{
|
|
pS->direction = (float)direction / 32;
|
|
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);
|
|
|
|
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 = (float)direction / 32;
|
|
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->pos.x,pS->pos.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,_("%s is Not Respoding"),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;
|
|
}
|