shithub: rott

ref: 3e27f1b2d0d8f020e9d3509934736ee9055b8c31
dir: /src/rt_net.c/

View raw version
/*
Copyright (C) 1994-1995  Apogee Software, Ltd.
Copyright (C) 2002-2015  icculus.org, GNU/Linux port
Copyright (C) 2017-2018  Steven LeVesque

This program 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.

This program 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.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include "rt_def.h"
#include "rt_main.h"
#include "rt_net.h"
#include "rt_com.h"
#include "_rt_net.h"
#include "rt_actor.h"
#include "rt_playr.h"
#include "isr.h"
#include "z_zone.h"
#include "develop.h"
#include "rottnet.h"
#include "rt_msg.h"
#include "rt_sound.h"
#include "rt_menu.h"
#include "rt_util.h"
#include "rt_rand.h"
#include "rt_game.h"
#include "rt_draw.h"
#include "myprint.h"
#include "rt_debug.h"
#include "rt_view.h"
#include "rt_battl.h"
#include "rt_dmand.h"

#if (SYNCCHECK == 1)
int            lastsynccheck;
COM_CheckSyncType PlayerSync[MAXPLAYERS];
#endif


CommandType * LocalCmds;
CommandType * ServerCmds;


int        controlupdatestartedtime=-1;
int        controlupdatetime=-1;
int        serverupdatetime=-1;
int        controlupdatestarted=0;
boolean    GamePaused=false;

boolean    modemgame;
boolean    networkgame;
int        numplayers;
int        server;
boolean    IsServer;
boolean    standalone;
boolean    restartgame=false;
boolean    respawnactive=false;
boolean    playerdead=false;
boolean    controlschanged=true;
boolean    battlegibs=false;
boolean    remoteridicule = false;
/*
=============================================================================

					LOCAL FUNCTION PROTOTYPES and VARIABLES

=============================================================================
*/
boolean  demorecord,
         demoplayback;
byte     *demoptr,
         *lastdemoptr,
         *demobuffer=NULL;
boolean  demodone = false;
int      predemo_violence = -1;
int oldmomx;
int oldmomy;
int oldspdang;

static boolean GameCommandsStarted=false;

static int oldcontrolbuf[3];
static int oldbuttonbits;
static CommandType * PlayerCmds[MAXPLAYERS];
static CommandType * ClientCmds[MAXPLAYERS];

static boolean GotPlayersDesc[MAXPLAYERS];
static boolean PlayersReady[MAXPLAYERS];
static int     LastCommandTime[MAXPLAYERS];

static CommandStatusType * CommandState[MAXPLAYERS+1];

static boolean InProcessServer=false;
static int lastcontrolupdatetime;
static int largesttime;
static int PlayerStatus[MAXPLAYERS];
//static int syncservertime;
//static boolean FixingPackets;
static int controldivisor=1;
static int nextupdatetime;
static boolean UpdateServer=true;

void CheckForPacket ( void );
void PrepareLocalPacket ( void );
void SendSyncCheckPacket ( void );
void AddModemSubPacket(void * incoming);
void SetPlayerDescription( void * pkt );
void UpdateDemoPlayback (int time);
int GetTypeSize (int type);
int MaxSpeedForCharacter(playertype *pstate);

/*
=============================================================================

						  Game Command Section

=============================================================================
*/

//****************************************************************************
//
// ComError ()
//
//****************************************************************************

#define ComError SoftError

//****************************************************************************
//
// ConsoleIsServer()
//
//****************************************************************************
boolean ConsoleIsServer ( void )
{
    if (modemgame==true)
    {
        if (networkgame==true)
        {
            if (rottcom->client==0)
            {
                return true;
            }
        }
    }
    return false;
}

//****************************************************************************
//
// GamePacketSize()
//
//****************************************************************************
int GamePacketSize( void )
{
    if ((remoteridicule == true) || (ConsoleIsServer() == true))
    {
        return GetTypeSize(COM_SOUNDANDDELTA);
    }
    else
        return GetTypeSize(COM_TEXT);
}

//****************************************************************************
//
// InitializeGameCommands()
//
//****************************************************************************
void InitializeGameCommands( void )
{
    int i;
    int j;

    // default to player 0

    if (GameCommandsStarted==true)
        return;

    GameCommandsStarted=true;

    if (modemgame==true)
        controldivisor=rottcom->ticstep;

    standalone=false;
    IsServer=false;

    if (modemgame==true)
    {
        consoleplayer=rottcom->consoleplayer;

        if (networkgame==true)
        {
            if (rottcom->client==0)
            {
                IsServer=true;
                // turn it on absolutely for the server
                remoteridicule = true;
                if (consoleplayer==0)
                    standalone=true;
            }
            if (consoleplayer>0)
                consoleplayer--; // playernumber fixup
        }
    }

    if (standalone==false)
    {
        int size;

        size = GamePacketSize ();

        for (i=0; i<numplayers; i++)
        {
            PlayerCmds[i]=(CommandType *)SafeLevelMalloc(sizeof(CommandType));
            for (j=0; j<MAXCMDS; j++)
            {
                PlayerCommand(i,j)=SafeLevelMalloc(size);
            }
        }
    }


    // allocate local commands

    LocalCmds=(CommandType *)SafeLevelMalloc(sizeof(CommandType));
    for (j=0; j<MAXCMDS; j++)
    {
        int size;

        size = GamePacketSize();

        LocalCommand(j)=SafeLevelMalloc(size);
        memset(LocalCommand(j),COM_DELTANULL,size);
    }

    CommandState[0]=(CommandStatusType *)SafeLevelMalloc(sizeof(CommandStatusType));

    if (modemgame==true)
    {
        for (i=0; i<numplayers; i++)
        {
            PlayerStatus[i]=player_ingame;
        }
        if (networkgame==true)
        {
            server=1;

            // initialize the Server

            if (IsServer==true)
            {
                server=0;
                ServerCmds=(CommandType *)SafeMalloc(sizeof(CommandType));
                for (j=0; j<MAXCMDS; j++)
                {
                    int size;
                    size=( (numplayers * GetTypeSize(COM_TEXT)) +
                           GetTypeSize(COM_SOUNDANDDELTA) +
                           sizeof(COM_ServerHeaderType) -
                           sizeof(byte)
                         );
                    ServerCommand(j)=SafeMalloc( size );
                    memset(ServerCommand(j),COM_DELTANULL,size);
                }
                for (i=1; i<=numplayers; i++)
                {
                    CommandState[i]=(CommandStatusType *)
                                    SafeMalloc(sizeof(CommandStatusType));
                }
                for (i=0; i<numplayers; i++)
                {
                    ClientCmds[i]=(CommandType *)SafeMalloc(sizeof(CommandType));
                    for (j=0; j<MAXCMDS; j++)
                    {
                        int size;

                        size=GetTypeSize(COM_SOUNDANDDELTA);
                        ClientCommand(i,j)=SafeMalloc(size);
                        memset(ClientCommand(i,j),COM_DELTANULL,size);
                    }
                }
            }
        }
        else // must be a two player game
        {
            server=consoleplayer^1;
        }
    }
}


//****************************************************************************
//
// ShutdownGameCommands()
//
//****************************************************************************
void ShutdownGameCommands( void )
{
    int i;
    int j;

    if (GameCommandsStarted==false)
        return;

    GameCommandsStarted=false;

    // free up playercmds;
    if (standalone==false)
    {
        for (i=0; i<numplayers; i++)
        {
            for (j=0; j<MAXCMDS; j++)
            {
                if (PlayerCommand(i,j))
                {
                    SafeFree(PlayerCommand(i,j));
                    PlayerCommand(i,j)=NULL;
                }
            }
            SafeFree( PlayerCmds[i] );
            PlayerCmds[i]=NULL;
        }
    }

    // free up command status

    SafeFree(CommandState[0]);
    CommandState[0]=NULL;

    if (modemgame==true)
    {

        // free up local commands

        for (j=0; j<MAXCMDS; j++)
        {
            if (LocalCommand(j))
            {
                SafeFree(LocalCommand(j));
                LocalCommand(j)=NULL;
            }
        }
        SafeFree(LocalCmds);
        LocalCmds=NULL;


        // free up Server

        if (networkgame==true)
        {
            if (IsServer==true)
            {
                for (j=0; j<MAXCMDS; j++)
                {
                    if (ServerCommand(j))
                    {
                        SafeFree(ServerCommand(j));
                        ServerCommand(j)=NULL;
                    }
                }
                SafeFree(ServerCmds);
                ServerCmds=NULL;
                for (i=1; i<=numplayers; i++)
                {
                    SafeFree(CommandState[i]);
                    CommandState[i]=NULL;
                }
                for (i=0; i<numplayers; i++)
                {
                    for (j=0; j<MAXCMDS; j++)
                    {
                        if (ClientCommand(i,j))
                        {
                            SafeFree(ClientCommand(i,j));
                            ClientCommand(i,j)=NULL;
                        }
                    }
                    SafeFree( ClientCmds[i] );
                    ClientCmds[i]=NULL;
                }
            }
        }
    }
}




/*
=============================================================================

						  Client Controls Section

=============================================================================
*/


//****************************************************************************
//
// ShutdownClientControls ()
//
//****************************************************************************

void ShutdownClientControls ( void )
{
    int i;
    controlupdatestarted=0;
    for (i=0; i<numplayers; i++)
    {
        if (PlayerStatus[i] == player_leftgame)
            PlayerStatus[i]=player_ingame;
    }
}


//****************************************************************************
//
// StartupClientControls ()
//
//****************************************************************************

void StartupClientControls ( void )
{
    int i,j;

    if (controlupdatestarted==1)
        return;

    controlupdatestarted=1;

    memset(oldcontrolbuf,-1,sizeof(oldcontrolbuf));
    oldbuttonbits=-1;
    controlschanged=true;

    INL_GetMouseDelta(&i,&i);


    locplayerstate->dmomx = 0;
    locplayerstate->dmomy = 0;
    locplayerstate->angle = 0;
    locplayerstate->topspeed=MaxSpeedForCharacter(locplayerstate);


    CalcTics();
    CalcTics();

//   FixingPackets=false;

    memset (controlbuf, 0, sizeof (controlbuf));
    buttonbits = 0;
    lastpolltime=-1;
    IN_ClearKeyboardQueue ();

    if (modemgame==true)
    {
        controlupdatetime=controlsynctime+(VBLCOUNTER*2);
        SoftError("Controls started at %d\n",controlupdatetime);
    }
    else if (demoplayback || demorecord)
    {
        ISR_SetTime(20);
        oldtime = 20;
        controlupdatetime=20;
    }
    else
        controlupdatetime=GetTicCount();

    controlupdatetime-=(controlupdatetime%controldivisor);

    serverupdatetime=controlupdatetime;
    oldpolltime=controlupdatetime;
    nextupdatetime=oldpolltime;
#if (SYNCCHECK == 1)
    lastsynccheck=oldpolltime+CHECKSYNCTIME;
#endif
    controlupdatestartedtime=controlupdatetime;

    for( j = 0; j < numplayers; j++ )
    {
        memset( PLAYERSTATE[ j ].buttonheld, 0,
                sizeof( PLAYERSTATE[ j ].buttonheld ) );
        memset( PLAYERSTATE[ j ].buttonstate, 0,
                sizeof( PLAYERSTATE[ j ].buttonstate ) );
    }

    for (i=0; i<MAXCMDS; i++)
    {
        ServerCommandNumberStatus( i ) = cs_notarrived;
    }

    LastCommandTime[0]=controlupdatetime-controldivisor;
    if (IsServer==true)
    {
        int size;

        UpdateServer=true;
        size=( (numplayers * GetTypeSize(COM_TEXT)) +
               GetTypeSize(COM_SOUNDANDDELTA) +
               sizeof(COM_ServerHeaderType) -
               sizeof(byte)
             );

        for (j=0; j<numplayers; j++)
        {
            for (i=0; i<MAXCMDS; i++)
            {
                ClientCommandNumberStatus( j, i ) = cs_notarrived;
            }
            LastCommandTime[j]=controlupdatetime-controldivisor;
        }
        for (i=0; i<MAXCMDS; i++)
            memset(ServerCommand(i),COM_DELTANULL,size);
    }
    else if (modemgame==true)
    {
        int nump;

        nump=numplayers;
        if (nump<2) nump=2;

        for (i=0; i<nump; i++)
        {
            LastCommandTime[i]=controlupdatetime-controldivisor;
        }
    }

    if ((demoplayback==false) && (standalone==false))
    {
        if (modemgame==true)
        {
            while (GetTicCount()<(controlupdatetime-10))
            {
                CalcTics();
            }
        }
        lastcontrolupdatetime=GetTicCount();
        largesttime=0;
        PollControls();
    }
    if (standalone==true)
        printf("Packet Server started\n");
}



//****************************************************************************
//
// UpdateClientControls ()
//
//****************************************************************************

static boolean InUCC=false;
void UpdateClientControls ( void )
{
    int time;
//   int delta;

    if (controlupdatestarted==0)
        return;

    if (InUCC)
        return;
    else
        InUCC = true;

    lastcontrolupdatetime=GetTicCount();

    if (standalone==false)
    {
        time=GetTicCount();

        // if we are a fixing the current packet stop update of deltas
        // in non-network games.
        if (
            (networkgame == false) &&
            (ServerCommandStatus(oldpolltime)==cs_fixing)
        )
        {
            time=controlupdatetime-controldivisor;
        }

        while (time>=controlupdatetime)
        {
            MoveType * Delta;
            boolean soundready;

            soundready = SD_SoundDataReady();

            if (demoplayback==true)
            {
                UpdateDemoPlayback(controlupdatetime);
            }
//         else
//            {
//            PollControls();
//            }

            if (
                (memcmp(&controlbuf[0],&oldcontrolbuf[0],sizeof(controlbuf))!=0) ||
                (buttonbits!=oldbuttonbits)
            )
            {
                controlschanged=true;
                memcpy(&oldcontrolbuf[0],&controlbuf[0],sizeof(controlbuf));
                oldbuttonbits=buttonbits;
            }
            else
            {
                controlschanged=false;
            }

            if ((controlschanged==false) && (soundready==false))
            {
                NullMoveType * NullDelta;

                NullDelta=(NullMoveType *)NextLocalCommand();
                NullDelta->type=COM_DELTANULL;
            }
            else
            {
                Delta=(MoveType *)NextLocalCommand();
                Delta->type=COM_DELTA;
                Delta->momx=(controlbuf[0]>>1);
                Delta->momy=(controlbuf[1]>>1);
                Delta->dangle=controlbuf[2]>>11;
                Delta->buttons=buttonbits;

                // See if we need to update sound packet

                if (soundready==true)
                {
                    COM_SoundType * sndpkt;
                    recordstate status;

                    if (remoteridicule == false)
                        Error("Attempt to record Remote Ridicule without adequate storage");
                    sndpkt=(COM_SoundType *)Delta->Sounddata;

                    // Turn the packet into a COM_SOUNDANDDELTA packet

                    Delta->type=COM_SOUNDANDDELTA;
                    status = SD_GetSoundData ( &(sndpkt->data[0]),
                                               COM_SOUND_BUFFERSIZE );
                    switch (status)
                    {
                    case rs_nodata:
                        Delta->type=COM_DELTA;
                        break;
                    case rs_newsound:
                        sndpkt->type=COM_SOUND_START_TRANSMISSION;
                        break;
                    case rs_endsound:
                        sndpkt->type=COM_SOUND_END_TRANSMISSION;
                        break;
                    case rs_data:
                        sndpkt->type=COM_SOUND_NORMAL_TRANSMISSION;
                        break;
                    default:
                        Error("Illegal return value for SD_GetSoundData");
                        break;
                    }
                }
                if (demorecord==true)
                    RecordDemoCmd();
            }
            PrepareLocalPacket();

            if (
                (controlupdatetime != -1) &&
                (controlupdatetime > (lastpolltime+MAXPOLLTICS)) &&
                (demoplayback==false)
            )
            {
                controlbuf[0] = controlbuf[1] = controlbuf[2] = 0;
            }
        }
    }
    if (modemgame==true)
    {
        CheckForPacket ();
    }

    if ((standalone == false) && (IsServer==true) && (UpdateServer==true))
        ProcessServer();

// take out
    if (modemgame==true)
    {
        if (PanicPressed==true)
        {
            Error("Game Aborted. Scroll Lock pressed\n");
        }
        if (Keyboard[sc_Insert] && Keyboard[sc_Q])
            Error("Game Aborted. Insert->Q pressed\n");
    }

    InUCC = false;
}

//****************************************************************************
//
// PlayerInGame()
//
//****************************************************************************
boolean PlayerInGame ( int p )
{
    if (PlayerStatus[p]!=player_ingame)
        return false;
    return true;
}

/*
=============================================================================

						  Packet Section

=============================================================================
*/

//****************************************************************************
//
// CheckForPacket()
//
//****************************************************************************
void CheckForPacket ( void )
{
    while (ReadPacket()==true)
    {
        if (badpacket==0)
        {
            ProcessPacket(&ROTTpacket[0], rottcom->remotenode);
        }
        else
            RequestPacket (LastCommandTime[rottcom->remotenode]+controldivisor, rottcom->remotenode, controldivisor);
    }
}


//****************************************************************************
//
// AddRemoteRidiculeCommand()
//
//****************************************************************************
void AddRemoteRidiculeCommand ( int player, int towho, int num )
{
    ((COM_RemoteRidiculeType *)NextLocalCommand())->type=COM_REMRID;
    ((COM_RemoteRidiculeType *)NextLocalCommand())->num=num;
    ((COM_RemoteRidiculeType *)NextLocalCommand())->player=player;
    ((COM_RemoteRidiculeType *)NextLocalCommand())->towho=towho;

    PrepareLocalPacket();
}

//****************************************************************************
//
// ProcessRemoteRidicule()
//
//****************************************************************************
void ProcessRemoteRidicule ( void * pkt )
{
    COM_RemoteRidiculeType * remrot;
    char name[ 50 ];
    int from;
    int who;

    remrot = (COM_RemoteRidiculeType *)pkt;
    from   = remrot->player;
    who    = remrot->towho;
    if ( ( who == consoleplayer ) || ( who == MSG_DIRECTED_TO_ALL ) ||
            ( ( who == MSG_DIRECTED_TO_TEAM ) && ( BATTLE_Team[ from ] ==
                    BATTLE_Team[ consoleplayer ] ) ) )
    {
        strcpy( name, "(� RR from " );
        strcat( name, PLAYERSTATE[from].codename );
        strcat( name, ")" );
        AddMessage( name, MSG_REMOTERIDICULE );

        SD_Play( SD_REMOTEM1SND + remrot->num );
    }
}

//****************************************************************************
//
// AddEndGameCommand()
//
//****************************************************************************
void AddEndGameCommand ( void )
{
    ((COM_EndGameType *)NextLocalCommand())->type=COM_ENDGAME;

    PrepareLocalPacket();
}

//****************************************************************************
//
// AddGameEndCommand()
//
//****************************************************************************
void AddGameEndCommand ( void )
{
    ((COM_GameEndType *)NextLocalCommand())->type=COM_GAMEEND;

    PrepareLocalPacket();
}

//****************************************************************************
//
// AddQuitCommand()
//
//****************************************************************************
void AddQuitCommand ( void )
{
    ((COM_QuitType *)NextLocalCommand())->type=COM_QUIT;
    PrepareLocalPacket();
}

//****************************************************************************
//
// AddExitCommand()
//
//****************************************************************************
void AddExitCommand ( void )
{
    ((COM_ExitType *)NextLocalCommand())->type=COM_EXIT;
    PrepareLocalPacket();
}

//****************************************************************************
//
// AddPauseStateCommand()
//
//****************************************************************************
void AddPauseStateCommand ( int type )
{
    ((COM_PauseType *)NextLocalCommand())->type=type;

    PrepareLocalPacket();
}


//****************************************************************************
//
// AddRespawnCommand()
//
//****************************************************************************
void AddRespawnCommand ( void )
{
    if (respawnactive==true)
        return;

    respawnactive=true;

    ((COM_RespawnType *)NextLocalCommand())->type=COM_RESPAWN;

    PrepareLocalPacket();
}


//****************************************************************************
//
// AddTextMessage()
//
//****************************************************************************
void AddTextMessage
(
    char *message,
    int   length,
    int   towho
)

{
    COM_TextType *Text;

    Text = ( COM_TextType * )NextLocalCommand();

    Text->type = COM_TEXT;
    memset( &Text->string[ 0 ], 0, COM_MAXTEXTSTRINGLENGTH );

    if ( length >= COM_MAXTEXTSTRINGLENGTH )
    {
        length = COM_MAXTEXTSTRINGLENGTH - 1;
    }

    memcpy( &Text->string[ 0 ], message, length );

    Text->towho = towho;

    PrepareLocalPacket();
}


//****************************************************************************
//
// PrepareLocalPacket
//
//****************************************************************************

void PrepareLocalPacket ( void )
{
    MoveType * pkt;

    pkt=(MoveType *)NextLocalCommand();

    pkt->time=controlupdatetime;

    if (networkgame==false) // Whether it is a modem game or not we do this
    {
        AddClientPacket (pkt, consoleplayer);
        if (modemgame==false)
        {
            ServerCommandStatus ( controlupdatetime ) = cs_ready;
        }
    }

    if (modemgame==true)
        SendPacket (pkt, server);

    controlupdatetime+=controldivisor;
}



//****************************************************************************
//
// GetPacketSize ()
//
//****************************************************************************

int GetPacketSize (void * pkt)
{
    int size;

    switch (((MoveType *)pkt)->type)
    {
    case COM_DELTA:
        size=sizeof(MoveType);
        break;
    case COM_DELTANULL:
        size=sizeof(NullMoveType);
        break;
    case COM_REQUEST:
        size=sizeof(COM_RequestType);
        break;
    case COM_FIXUP:
        size=sizeof(COM_FixupType);
        break;
    case COM_TEXT:
        size=sizeof(COM_TextType);
        break;
    case COM_PAUSE:
        size=sizeof(COM_PauseType);
        break;
    case COM_QUIT:
        size=sizeof(COM_QuitType);
        break;
    case COM_EXIT:
        size=sizeof(COM_ExitType);
        break;
    case COM_REMRID:
        size=sizeof(COM_RemoteRidiculeType);
        break;
    case COM_RESPAWN:
        size=sizeof(COM_RespawnType);
        break;
    case COM_UNPAUSE:
        size=sizeof(COM_UnPauseType);
        break;
    case COM_SERVER:
        size=sizeof(COM_ServerHeaderType);
        size-=sizeof(byte);
        break;
    case COM_GAMEDESC:
        size=sizeof(COM_GamePlayerType);
        break;
    case COM_GAMEEND:
        size=sizeof(COM_GameEndType);
        break;
    case COM_GAMEPLAY:
        size=DUMMYPACKETSIZE;
        break;
    case COM_GAMEACK:
        size=sizeof(COM_GameAckType);
        break;
    case COM_GAMEMASTER:
        size=sizeof(COM_GameMasterType);
        break;
    case COM_ENDGAME:
        size=sizeof(COM_EndGameType);
        break;
    case COM_SYNCTIME:
        size=sizeof(COM_SyncType);
        break;
#if (SYNCCHECK == 1)
    case COM_SYNCCHECK:
        size=sizeof(COM_CheckSyncType);
        break;
#endif
    case COM_SOUNDANDDELTA:
        size=sizeof(MoveType)+sizeof(COM_SoundType);
        break;
    default:
        Error("Unhandled packet type in GetPacketSize type=%d",((MoveType *)pkt)->type);
        break;
    }

    return size;
}

//****************************************************************************
//
// GetTypeSize ()
//
//****************************************************************************

int GetTypeSize (int type)
{
    byte pkt[2];

    pkt[0]=(byte)type;
    return ( GetPacketSize(&(pkt[0])) );
}

//****************************************************************************
//
// GetServerPacketSize ()
//
//****************************************************************************

int GetServerPacketSize (void * pkt)
{
    int i;
    byte * ptr;
    COM_ServerHeaderType * serverpkt;

    serverpkt=(COM_ServerHeaderType *)pkt;
    if (serverpkt->type==COM_SERVER)
    {
        ptr=&serverpkt->data;

        for (i=0; i<serverpkt->numpackets; i++)
        {
            ptr+=GetPacketSize(ptr);
        }
        return ((byte *)ptr-(byte *)pkt);
    }
    else
        return GetPacketSize(pkt);
}

//****************************************************************************
//
// SendPacket ()
//
//****************************************************************************

void SendPacket (void * pkt, int dest)
{
    if ((networkgame==false) && (PlayerStatus[dest]!=player_ingame))
        return;
    if ((IsServer==true) && (dest==server) && (standalone==false)) // must be client on top of server
        ProcessPacket(pkt,dest);
    else if ((IsServer==false) && (dest!=server) && (standalone==false)) // We shouldn't be sending as client to anyone else
        ComError("SendPacket:Problems\n");
    else
        WritePacket(pkt,GetPacketSize(pkt),dest);
}

//****************************************************************************
//
// ResetCurrentCommand ()
//
//****************************************************************************

void ResetCurrentCommand ( void )
{
    ServerCommandStatus(oldpolltime)=cs_notarrived;
}

//****************************************************************************
//
// BroadcastServerPacket ()
//
//****************************************************************************

void BroadcastServerPacket (void * pkt, int size)
{
    int i;


    for (i=0; i<numplayers; i++)
    {
        if (PlayerStatus[i]!=player_ingame)
            continue;
//      if ((standalone==false) && (i==consoleplayer))
//         ProcessPacket(pkt,i);
//      else
        WritePacket((byte *)pkt,size,i);
    }
}


//****************************************************************************
//
// ResendLocalPackets ()
//
//****************************************************************************

void ResendLocalPackets (int time, int dest, int numpackets)
{
    int cmd;
    MoveType * pkt;

    cmd = CommandAddress(time);

    if (controlupdatetime<=time)
        return;

    pkt = (MoveType *)LocalCommand(cmd);

    if (pkt->time!=time)
    {
        Error( "CLIENT: Could not find packet to resend\ntime=%d packettime=%d controlupdatetime=%d\n",
               time, pkt->time, controlupdatetime);
    }
    else
    {
        byte * tempbuf;
        byte * tempptr;
        byte * tempstart;
        COM_FixupType * fixup;
        int i;
        int starti;
        int size;
        boolean done;

        // allocate some space

        tempbuf=SafeMalloc(MAXCOMBUFFERSIZE);

        fixup=(COM_FixupType *)tempbuf;

        fixup->type=COM_FIXUP;
        tempstart=&(fixup->data);

        done=false;
        i=0;
        while (done==false)
        {
            tempptr=tempstart;
            starti=i;
            fixup->time=( (MoveType *)LocalCommand(cmd) )->time;
            for (; i<numpackets; i++)
            {
                pkt = (MoveType *)LocalCommand(cmd);
                size=GetPacketSize(pkt);

                if (((tempptr+size)-tempbuf)>MAXCOMBUFFERSIZE)
                {
                    break;
                }
                memcpy(tempptr,pkt,size);
                tempptr+=size;
                cmd = (cmd + controldivisor) & (MAXCMDS-1);
            }
            fixup->numpackets=i-starti;
            WritePacket(tempbuf,tempptr-tempbuf,dest);
            if (i==numpackets)
                done=true;
        }

        SafeFree(tempbuf);
    }
}

//****************************************************************************
//
// ResendServerPackets ()
//
//****************************************************************************

void ResendServerPackets (int time, int dest, int numpackets)
{
    int cmd;
    COM_ServerHeaderType * serverpkt;


    cmd = CommandAddress(time);

    if (serverupdatetime<=time)
        return;

    serverpkt = (COM_ServerHeaderType *)ServerCommand(cmd);

    if (serverpkt->time!=time)
    {
        Error( "SERVER: Could not find packet to resend\ntime=%d packettime=%d serverupdatetime=%d\n",
               time, serverpkt->time,serverupdatetime);
    }
    else
    {
        byte * tempbuf;
        byte * tempptr;
        byte * tempstart;
        COM_FixupType * fixup;
        int i;
        int starti;
        int size;
        boolean done;

        // allocate some space

        tempbuf=SafeMalloc(MAXCOMBUFFERSIZE);

        fixup=(COM_FixupType *)tempbuf;

        fixup->type=COM_FIXUP;
        tempstart=&(fixup->data);

        done=false;
        i=0;
        while (done==false)
        {
            tempptr=tempstart;
            starti=i;
            fixup->time=( (MoveType *)ServerCommand(cmd) )->time;
            for (; i<numpackets; i++)
            {
                serverpkt = (COM_ServerHeaderType *)ServerCommand(cmd);
                size=GetServerPacketSize(serverpkt);

                if (((tempptr+size)-tempbuf)>MAXCOMBUFFERSIZE)
                {
                    break;
                }
                memcpy(tempptr,serverpkt,size);
                tempptr+=size;
                cmd = (cmd + controldivisor) & (MAXCMDS-1);
            }
            fixup->numpackets=i-starti;
            WritePacket(tempbuf,tempptr-tempbuf,dest);
            if (i==numpackets)
                done=true;
        }

        SafeFree(tempbuf);
    }
}


//****************************************************************************
//
// ResendPacket (incoming packet, whoever requested it)
//
//****************************************************************************

void ResendPacket (void * pkt, int dest)
{
    int time;
    COM_RequestType * request;

    if ((networkgame==false) && (PlayerStatus[dest]!=player_ingame))
        return;

    request=(COM_RequestType * )pkt;
    time=request->time;

    ComError( "RESEND request received at %d\n packet time=%d dest=%d numpackets=%d\n",
              GetTicCount(), time, dest, request->numpackets);

    if (IsServer==true)
    {
        if ((dest==server) && (standalone==false))
            Error("Trying to resend packets to client on top of server\n");
        ComError( "RESEND SERVER serverupdatetime=%d\n",serverupdatetime);
        if (IsServerCommandReady ( time ) == true)
            ResendServerPackets(time,dest,request->numpackets);
        else
            ComError( "RESEND SERVER time=%d is not ready\n",time);
    }
    else
    {
        ResendLocalPackets(time,dest,request->numpackets);
    }
}

//****************************************************************************
//
// FixupPacket ()
//
//****************************************************************************

void FixupPacket (void * pkt, int src)
{
    COM_FixupType * fix;
    int i;
    int time;
    byte * ptr;

    fix=(COM_FixupType *)pkt;

    ComError( "Fixup received at %d, time=%d numpackets=%d\n", GetTicCount(), fix->time, fix->numpackets);
    time=fix->time;
    ptr=&(fix->data);

    for (i=0; i<fix->numpackets; i++,time+=controldivisor)
    {
        if (time == (LastCommandTime[src]+controldivisor))
            LastCommandTime[src]=time;

        if (IsServer==true)
        {
            if (ClientCommandStatus(src, time)!=cs_fixing)
            {
                ComError("Server Received fixup with no bad packet time=%d from %d\n",time,src);
            }
            else
            {
                AddSubPacket(ptr, src);
            }
            ptr+=GetPacketSize(ptr);
        }
        else
        {
            if (ServerCommandStatus(time)!=cs_fixing)
            {
                ComError("Client Received fixup with no bad packet time=%d from %d\n",time,src);
            }
            else
            {
                if (networkgame==true)
                {
                    AddServerSubPacket( (COM_ServerHeaderType *)ptr );
                }
                else
                {
                    AddModemSubPacket(ptr);
                }
            }
            ptr+=GetServerPacketSize(ptr);
        }
    }
}

#if (SYNCCHECK == 1)
//****************************************************************************
//
// CheckForSyncCheck
//
//****************************************************************************

void CheckForSyncCheck ( void )
{
    int i;


    if (modemgame==true)
    {
        if (oldpolltime==lastsynccheck)
        {
            for (i=0; i<numplayers; i++)
            {
                PlayerSync[i].x=PLAYER[i]->x;
                PlayerSync[i].y=PLAYER[i]->y;
                PlayerSync[i].z=PLAYER[i]->z;
                PlayerSync[i].angle=PLAYER[i]->angle;
            }
            PlayerSync[0].randomindex=GetRNGindex();
            PlayerSync[0].synctime=lastsynccheck;
            SendSyncCheckPacket();
            lastsynccheck+=CHECKSYNCTIME;
        }
        if (oldpolltime>lastsynccheck)
        {
            Error("Missed a player sync check time=%d\n",oldpolltime);
        }
    }
}
#endif

//****************************************************************************
//
// ProcessSyncTimePacket
//
//****************************************************************************

void ProcessSyncTimePacket (void * pkt)
{
    COM_SyncType * sync;

    sync=(COM_SyncType *)pkt;
    ISR_SetTime(sync->synctime);
}

#if (SYNCCHECK == 1)
//****************************************************************************
//
// ProcessSyncCheckPacket
//
//****************************************************************************

void ProcessSyncCheckPacket (void * pkt, int src)
{
    COM_CheckSyncType * sync;

    sync=(COM_CheckSyncType *)pkt;
//   SoftError("Sync packet time=%ld\n",sync->synctime);
    if (sync->synctime!=PlayerSync[0].synctime)
    {
        SoftError("Old sync packet received\n");
        return;
    }
    if (sync->randomindex!=PlayerSync[0].randomindex)
    {
        Error("Player %d is unsynced localindex=%d remoteindex=%d\n"
              "Unsynced Player x=%x y=%x a=%d z=%d name=%s\n",
              src, PlayerSync[0].randomindex, sync->randomindex,
              PlayerSync[src].x, PlayerSync[src].y, PlayerSync[src].angle,
              PlayerSync[src].z,PLAYERSTATE[src].codename);
    }
    if (sync->x!=PlayerSync[src].x)
    {
        Error("Player %d is unsynced local x=%d remote x=%d\n"
              "Unsynced Player x=%x y=%x a=%d z=%d name=%s\n",
              src,PlayerSync[src].x,sync->x,
              PlayerSync[src].x, PlayerSync[src].y, PlayerSync[src].angle,
              PlayerSync[src].z,PLAYERSTATE[src].codename);
    }
    if (sync->y!=PlayerSync[src].y)
    {
        Error("Player %d is unsynced local y=%d remote y=%d\n"
              "Unsynced Player x=%x y=%x a=%d z=%d name=%s\n",
              src,PlayerSync[src].y,sync->y,
              PlayerSync[src].x, PlayerSync[src].y, PlayerSync[src].angle,
              PlayerSync[src].z,PLAYERSTATE[src].codename);
    }
    if (sync->z!=PlayerSync[src].z)
    {
        Error("Player %d is unsynced local z=%d remote z=%d\n"
              "Unsynced Player x=%x y=%x a=%d z=%d name=%s\n",
              src,PlayerSync[src].z,sync->z,
              PlayerSync[src].x, PlayerSync[src].y, PlayerSync[src].angle,
              PlayerSync[src].z,PLAYERSTATE[src].codename);
    }
    if (sync->angle!=PlayerSync[src].angle)
    {
        Error("Player %d is unsynced local angle=%d remote angle=%d\n"
              "Unsynced Player x=%x y=%x a=%d z=%d name=%s\n",
              src,PlayerSync[src].angle,sync->angle,
              PlayerSync[src].x, PlayerSync[src].y, PlayerSync[src].angle,
              PlayerSync[src].z,PLAYERSTATE[src].codename);
    }
}

//****************************************************************************
//
// SendSyncCheckPacket
//
//****************************************************************************

void SendSyncCheckPacket ( void )
{
    ((COM_CheckSyncType *)NextLocalCommand())->type=COM_SYNCCHECK;
    ((COM_CheckSyncType *)NextLocalCommand())->synctime=PlayerSync[0].synctime;
    ((COM_CheckSyncType *)NextLocalCommand())->x=PlayerSync[consoleplayer].x;
    ((COM_CheckSyncType *)NextLocalCommand())->y=PlayerSync[consoleplayer].y;
    ((COM_CheckSyncType *)NextLocalCommand())->z=PlayerSync[consoleplayer].z;
    ((COM_CheckSyncType *)NextLocalCommand())->angle=PlayerSync[consoleplayer].angle;
    ((COM_CheckSyncType *)NextLocalCommand())->randomindex=PlayerSync[0].randomindex;

    PrepareLocalPacket();
}
#endif

//****************************************************************************
//
// ProcessSoundAndDeltaPacket
//
//****************************************************************************

void ProcessSoundAndDeltaPacket (void * pkt, int src)
{
    MoveType * packet;
    COM_SoundType * sndpkt;
    byte oldtype;

    packet = (MoveType *)pkt;

    // Trick packet into being a normal delta packet

    oldtype=packet->type;
    packet->type=COM_DELTA;
    AddClientPacket (pkt,src);
    packet->type=oldtype;

    // Don't process sound if it is from us
    if (src==consoleplayer)
        return;

    sndpkt = (COM_SoundType *) (packet->Sounddata);

    if (sndpkt->type==COM_SOUND_START_TRANSMISSION)
    {
        SD_StartIncomingSound ();
    }
    if (sndpkt->type==COM_SOUND_END_TRANSMISSION)
    {
        SD_StopIncomingSound();
    }
    else
    {
        SD_UpdateIncomingSound (&(sndpkt->data[0]), COM_SOUND_BUFFERSIZE);
    }
}
//****************************************************************************
//
// SyncToServer
//
//****************************************************************************
#define NETWORKTIMEAHEADOFSERVER (1)
#define MODEMTIMEAHEADOFSERVER (2)
void SyncToServer( void )
{
    int diff;

    if ((networkgame==false) && (consoleplayer==0))
        return;
    if (IsServer==true)
        return;
//   if (networkgame==true)
//      {
//      diff = (GetTicCount()-controldivisor-LastCommandTime[0])/controldivisor;
//      SoftError("diff=%ld\n",diff);
//      if (abs(diff)>1)
//         ISR_SetTime(GetTicCount()-diff);
//      }
//   else
//      {
    diff = (GetTicCount()-controldivisor-LastCommandTime[server])/controldivisor;
    if (abs(diff)>0)
        ISR_SetTime(GetTicCount()-diff);
//      }
}

//****************************************************************************
//
// ProcessPacket
//
//****************************************************************************

void ProcessPacket (void * pkt, int src)
{
    switch (((MoveType *)pkt)->type)
    {
    case COM_DELTA:
    case COM_DELTANULL:
    case COM_TEXT:
    case COM_PAUSE:
    case COM_QUIT:
    case COM_EXIT:
    case COM_REMRID:
    case COM_RESPAWN:
    case COM_UNPAUSE:
    case COM_ENDGAME:
#if (SYNCCHECK == 1)
    case COM_SYNCCHECK:
#endif
//         if (FixingPackets==false)
        AddPacket(pkt,src);
        break;
    case COM_SOUNDANDDELTA:
        if (remoteridicule == false )
        {
            ((MoveType *)pkt)->type = COM_DELTA;
        }
        AddPacket(pkt,src);
        break;
    case COM_SERVER:
        AddServerPacket(pkt,src);
        break;

    case COM_REQUEST:
        ResendPacket(pkt, src);
        break;

    case COM_FIXUP:
        FixupPacket(pkt, src);
        break;

    case COM_SYNCTIME:
        ProcessSyncTimePacket(pkt);
        break;

    case COM_GAMEEND:
    case COM_GAMEDESC:
    case COM_GAMEACK:
    case COM_GAMEMASTER:
        if (standalone==true)
            restartgame=true;
        break;

    case COM_START:
        break;

    default:
        Error("ProcessPacket: Unknown packet type=%d\n",((MoveType *)pkt)->type);
    }
}


//****************************************************************************
//
// AddServerSubPacket
//
//****************************************************************************

void AddServerSubPacket(COM_ServerHeaderType * serverpkt)
{
    byte * pkt;
    int i;

    ServerCommandStatus(serverpkt->time)=cs_ready;

    pkt=&serverpkt->data;
    for (i=0; i<serverpkt->numpackets; i++)
    {
        AddClientPacket(pkt,i);
        pkt+=GetPacketSize(pkt);
    }
}

//****************************************************************************
//
// AddModemSubPacket
//
//****************************************************************************

void AddModemSubPacket(void * incoming)
{
    MoveType * pkt;

    pkt=(MoveType *)incoming;
    ServerCommandStatus(pkt->time)=cs_ready;

    AddClientPacket(incoming,server);
}

//****************************************************************************
//
// AddServerPacket
//
//****************************************************************************

void AddServerPacket(void * pkt, int src)
{
    COM_ServerHeaderType * serverpkt;

    // The server uses the client's lgts for communicating

    // Last good time can be set even for the client/server combo

    if (standalone==true)
    {
        Error("standalone should not be here\n");
    }

    if (src!=server)
    {
        Error("Received server packet from non-server src=%d\n",src);
    }

    serverpkt=(COM_ServerHeaderType *)pkt;

//   if (networkgame==false)
//      SyncToServer(serverpkt->time);

    LastCommandTime[src]+=controldivisor;

    if (serverpkt->time != LastCommandTime[src])
    {
        int numpackets;

        numpackets=serverpkt->time-LastCommandTime[src];
        if (ServerCommandStatus(LastCommandTime[src])!=cs_fixing)
        {
            RequestPacket ( LastCommandTime[src], src, numpackets );

            ComError("AddServerPacket: Request packet time=%d lct=%d numpackets=%d\n",
                     serverpkt->time, LastCommandTime[src], numpackets
                    );
        }

        LastCommandTime[src]+=numpackets;
    }

    AddServerSubPacket( serverpkt );
}

//****************************************************************************
//
// AddClientPacket
//
//****************************************************************************

void AddClientPacket (void * pkt, int src)
{
    int size;
    MoveType * packet;

    packet=(MoveType *)pkt;

    switch (packet->type)
    {
    case COM_DELTA:
    case COM_DELTANULL:
    case COM_TEXT:
    case COM_REMRID:
    case COM_PAUSE:
    case COM_QUIT:
    case COM_EXIT:
    case COM_RESPAWN:
    case COM_UNPAUSE:
#if (SYNCCHECK == 1)
    case COM_SYNCCHECK:
#endif
    case COM_ENDGAME:
        size=GetPacketSize(packet);
        memcpy(PlayerCommand(src,CommandAddress(packet->time)),packet,size);
        break;
    case COM_SOUNDANDDELTA:
        ProcessSoundAndDeltaPacket(packet, src);
        break;
    default:
        Error("AddClientPacket: Unknown packet type = %d\n",packet->type);
    }
}

//****************************************************************************
//
// AddSubPacket
//
//****************************************************************************

void AddSubPacket (void * pkt, int src)
{
    MoveType * packet;

    if (networkgame==false)
        Error("Modem game should not be here in AddSubPacket\n");

    packet = (MoveType *) pkt;

    ClientCommandStatus(src, packet->time)=cs_ready;

    memcpy (
        ClientTimeCommand(src,packet->time),
        pkt,
        GetPacketSize(packet)
    );
}

//****************************************************************************
//
// AddPacket
//
//****************************************************************************

void AddPacket (void * pkt, int src)
{
    MoveType * packet;

    // should only be called by server in network game
    // in modem game we fall through the first condition
    // all packets should be sequential

    if ((IsServer==true) && (PlayerStatus[src]!=player_ingame))
        return;
    packet = (MoveType *) pkt;

//   if ((networkgame==false) && (consoleplayer!=0))
//      SyncToServer();

    if (!((src==server) && (standalone==false) && (IsServer==true)))
    {
        LastCommandTime[src]+=controldivisor;

        if (packet->time != LastCommandTime[src])
        {
            int numpackets;

            numpackets=packet->time-LastCommandTime[src];
            if ( ( (networkgame==false) &&
                    (ServerCommandStatus(LastCommandTime[src])!=cs_fixing)
                 )
                    ||
                    ( (networkgame==true) &&
                      (ClientCommandStatus(src,LastCommandTime[src])!=cs_fixing)
                    )
               )
            {
                RequestPacket ( LastCommandTime[src], src, numpackets );

                ComError("AddPacket: Request packet time=%d lct=%d numpackets=%d\n",
                         packet->time, LastCommandTime[src], numpackets
                        );
            }

            LastCommandTime[src]+=numpackets;
        }
    }

    if (networkgame==true)
    {
        AddSubPacket ( packet, src );
    }
    else
    {
        AddModemSubPacket(packet);
    }
}


//****************************************************************************
//
// RequestPacket ( int time, int dest )
//
//****************************************************************************

void RequestPacket (int time, int dest, int numpackets)
{
    COM_RequestType request;
    int i;

    request.type=COM_REQUEST;
    request.time=time;
    request.numpackets=numpackets/controldivisor;

    if (IsServer==true)
    {
        if ((dest==server) && (standalone==false))
        {
            Error("Requesting packet from client on top of server\n");
        }
        if (PlayerStatus[dest]!=player_ingame)
            return;
        for (i=0; i<numpackets; i+=controldivisor)
        {
            ClientCommandStatus( dest, (time+i) ) = cs_fixing;
        }
    }
    else
    {
        if ((networkgame==false) && (PlayerStatus[dest]!=player_ingame))
            return;
        for (i=0; i<numpackets; i+=controldivisor)
        {
            ServerCommandStatus( (time+i) ) = cs_fixing;
        }
    }
//   if (networkgame==false)
//      FixingPackets=true;

    // send out the packet

    WritePacket (&request, GetPacketSize(&request), dest);
}

//****************************************************************************
//
// IsServerCommandReady ()
//
//****************************************************************************
boolean IsServerCommandReady ( int time )
{

    if (
        (
            (COM_ServerHeaderType *)
            ServerCommand(CommandAddress (time) ) )->time==time)
        return true;
    else
    {
        return false;
    }
}

//****************************************************************************
//
// AreClientsReady ()
//
//****************************************************************************
boolean AreClientsReady ( void )
{
    int i;
    int timeindex;
    int status;

    timeindex=CommandAddress(serverupdatetime);

    for (i=0; i<numplayers; i++)
    {
        if (PlayerStatus[i]!=player_ingame)
            continue;
        status=ClientCommandStatus(i, serverupdatetime);
        if (status==cs_notarrived)
            return false;
        else if (status==cs_fixing)
        {
//         RequestPacket ( serverupdatetime , i , controldivisor );
            return false;
        }
        else if (((MoveType *)ClientCommand(i, timeindex))->time != serverupdatetime)
            return false;
    }
    return true;
}

//****************************************************************************
//
// IsPlayerCommandReady ()
//
//****************************************************************************
boolean IsPlayerCommandReady (int num, int time)
{
    MoveType * cmd;

    cmd=(MoveType *)PlayerCommand(num,CommandAddress(time));

    if (cmd->time==time)
        return true;
    else
        return false;
}

//****************************************************************************
//
// ResetClientCommands ()
//
//****************************************************************************
void ResetClientCommands ( int player )
{
    int j;

    for (j=0; j<MAXCMDS; j++)
    {
        memset(ClientCommand(player,j),COM_DELTA,GamePacketSize());
    }
}

//****************************************************************************
//
// SendFullServerPacket ()
//
//****************************************************************************
void SendFullServerPacket ( void )
{
    int i;
    int size;
    byte * pkt;
    COM_ServerHeaderType * spkt;
    int timeindex;
    int playerstatus[MAXPLAYERS];

    timeindex=CommandAddress(serverupdatetime);

    spkt=(COM_ServerHeaderType *)ServerCommand(timeindex);

    pkt=&spkt->data;
    spkt->time=serverupdatetime;
    spkt->type=COM_SERVER;
    spkt->numpackets=numplayers;


    memset(playerstatus,-1,sizeof(playerstatus));
    for (i=0; i<numplayers; i++)
    {
        size=GetPacketSize(ClientCommand(i,timeindex));
        if (((MoveType *)ClientCommand(i,timeindex))->type == COM_QUIT)
        {
            playerstatus[i]=player_quitgame;
        }
        if (((MoveType *)ClientCommand(i,timeindex))->type == COM_ENDGAME)
        {
            playerstatus[i]=player_leftgame;
        }
        memcpy(pkt,
               ClientCommand(i,timeindex),
               size
              );
        pkt+=size;
        ClientCommandNumberStatus(i,timeindex)=cs_notarrived;
    }
    BroadcastServerPacket((void *)spkt,(pkt-(byte *)spkt));
    serverupdatetime+=controldivisor;

    for (i=0; i<numplayers; i++)
    {
        if (playerstatus[i]!=-1)
        {
            if ((standalone==false) && (consoleplayer==i))
            {
                UpdateServer=false;
            }
            else
            {
                ResetClientCommands(i);
                PlayerStatus[i]=playerstatus[i];
            }
        }
    }
}



//****************************************************************************
//
// ProcessServer ()
//
//****************************************************************************

void ProcessServer ( void )
{
    boolean done;
    boolean exit;
    int i;
    int time;

    if (InProcessServer==true)
        return;

    InProcessServer=true;

    if (GetTicCount()<serverupdatetime)
        goto exitProcessServer;

    time=GetTicCount();
    exit=false;

    while (time>=serverupdatetime)
    {
        int savetime;

        savetime=GetTicCount()+NETWORKTIMEOUT;
        done = false;
        while (done == false)
        {
            if (standalone==true)
                AbortCheck("GameServer aborted\n");

            done = AreClientsReady ();

            if ( (standalone==false) && (serverupdatetime>=(controlupdatetime-controldivisor)) && (done==false) )
                break;

            CheckForPacket ();

            if (standalone==false)
                UpdateClientControls();

            if (restartgame==true)
                break;
            if (GetTicCount()>savetime)
            {
                for (i=0; i<numplayers; i++)
                {
                    int val;

                    val=ClientCommandStatus(i, serverupdatetime);
                    if ((val!=cs_ready) && (PlayerStatus[i]==player_ingame))
                    {
                        SoftError("Server timeout\n");
                        RequestPacket(serverupdatetime, i, controldivisor);
                    }
                }
                savetime=GetTicCount()+NETWORKTIMEOUT;
            }
            if ((standalone==false) && (done==false))
            {
                exit=true;
                done=true;
            }
        }
        if (exit==true)
            break;
        if ( (serverupdatetime>=(controlupdatetime-controldivisor)) && (standalone==false))
            break;
        if (restartgame==true)
            break;
        SendFullServerPacket();
    }
exitProcessServer:
    InProcessServer=false;
}


//****************************************************************************
//
// SetupCheckForPacket()
//
//****************************************************************************
int SetupCheckForPacket ( void )
{
    int retval=scfp_nodata;

    if ((ReadPacket()==true) && (badpacket==0))
    {
        MoveType * pkt;

        retval=scfp_data;
        pkt=(MoveType *)&ROTTpacket[0];
        if ((IsServer==true) && (standalone==true))
        {
            switch (pkt->type)
            {
            case COM_GAMEEND:
                break;
            case COM_GAMEDESC:
                if (standalone==true)
                    printf("Received GameDescription from player#%ld\n",(long int)rottcom->remotenode);
                WritePacket(&ROTTpacket[0],GetPacketSize(pkt),0); // Send to player 0
                break;
            case COM_GAMEACK:
                if (standalone==true)
                    printf("Received GameAcknowledgement from player#%ld\n",(long int)rottcom->remotenode);
                WritePacket(&ROTTpacket[0],GetPacketSize(pkt),0); // Send to player 0
                break;
            case COM_GAMEMASTER:
                if (standalone==true)
                    printf("Received GameMasterPacket from player#%ld\n",(long int)rottcom->remotenode);
                BroadcastServerPacket(&ROTTpacket[0],GetPacketSize(pkt)); // Send to all
                break;
            case COM_GAMEPLAY:
                if (standalone==true)
                    printf("Received StartGamePacket from player#%ld\n",(long int)rottcom->remotenode);
                BroadcastServerPacket(&ROTTpacket[0],GetPacketSize(pkt)); // Send to all
                retval=scfp_done;
                break;
            default:
                ComError("Server received unknown packet in Game preamble\n");
                break;
            }
        }
        else
        {
            switch (pkt->type)
            {
            case COM_GAMEPLAY:
                retval=scfp_done;
                break;
            case COM_GAMEMASTER:
                SetGameDescription(pkt);
                retval=scfp_gameready;
                break;
            case COM_GAMEACK:
                PlayersReady[((COM_GameAckType *)pkt)->player]=true;
                break;
            case COM_GAMEDESC:
                GotPlayersDesc[((COM_GamePlayerType *)pkt)->player]=true;
                SetPlayerDescription(pkt);
                break;
            }
        }
    }
    return retval;
}


//****************************************************************************
//
// ServerLoop ()
//
//****************************************************************************
void ServerLoop( void )
{
    boolean done;

    while (1)
    {
        ShutdownClientControls();
        restartgame=false;

        done=false;
        while (done==false)
        {
            AbortCheck("SetupGameServer aborted\n");

            if (SetupCheckForPacket()==scfp_done)
                done=true;
        }
        ComSetTime();
        StartupClientControls();
        while(1)
        {
            ProcessServer();
            CalcTics();
            if (restartgame==true)
                break;
        }
    }
}

//****************************************************************************
//
// ProcessPlayerCommand()
//
//****************************************************************************
void ProcessPlayerCommand( int player )
{
    MoveType * cmd;

    cmd=(MoveType *)PlayerCommand(player,CommandAddress(oldpolltime));

    if (cmd->type==COM_DELTA)
    {
        UpdatePlayerObj(player);
    }
    else if (cmd->type==COM_RESPAWN)
    {
        if (player==consoleplayer) // reset spawn state
            respawnactive=false;
        RespawnPlayerobj(PLAYER[player]);
    }
    else if (cmd->type==COM_ENDGAME)
    {
        playstate = ex_battledone;
    }
    else if (cmd->type==COM_QUIT)
    {
        if (player==consoleplayer)
            QuitGame();
        else
        {
            char str[50]="Player #";
            char str2[10];

            strcat(str,itoa(player+1,str2,10));
            strcat(str,", ");
            strcat(str,PLAYERSTATE[player].codename);
            strcat(str," has left the game.");
            AddMessage(str,MSG_REMOTE);
            PlayerStatus[player]=player_quitgame;
        }
    }
    else if (cmd->type==COM_EXIT)
    {
        QuitGame();
    }
    else if (cmd->type==COM_REMRID)
    {
        ProcessRemoteRidicule (cmd);
    }
    else if (cmd->type==COM_TEXT)
    {
        int who;

        who = ( ( COM_TextType * )cmd )->towho;
        if ( ( who == consoleplayer ) ||
                ( who == MSG_DIRECTED_TO_ALL ) ||
                ( ( who == MSG_DIRECTED_TO_TEAM ) &&
                  ( BATTLE_Team[ player ] == BATTLE_Team[ consoleplayer ] ) ) )
        {
            char string[ 50 ];

            strcpy( string, "\\N9" );
            strcat( string, PLAYERSTATE[player].codename );
            strcat( string, ":\\NF" );
            strcat( string, ((COM_TextType *)cmd)->string );
            SD_PlayPitchedSound ( SD_ENDBONUS1SND, 255, 1200 );

            AddMessage( string, MSG_REMOTE );
        }
    }
#if (SYNCCHECK == 1)
    else if (cmd->type==COM_SYNCCHECK)
    {
        ProcessSyncCheckPacket(cmd, player);
    }
#endif
    else if (cmd->type==COM_PAUSE)
    {
        MUSIC_Pause();
        GamePaused=true;
        pausedstartedticcount = oldpolltime;
    }
    else if (cmd->type==COM_UNPAUSE)
    {
        GamePaused=false;
        MUSIC_Continue ();
        if (RefreshPause == false)       // screen is blanked
        {
            ShutdownScreenSaver();
            SetupScreen (true);
            RefreshPause = true;
        }
    }
}

//****************************************************************************
//
// CheckUnPause ()
//
//****************************************************************************
void CheckUnPause ( void )
{
    if (oldpolltime==nextupdatetime)
    {
        nextupdatetime=oldpolltime+controldivisor;
        while (1)
        {
            if (ServerCommandStatus(oldpolltime)==cs_ready)
            {
                int j;

                for (j=0; j<numplayers; j++)
                {
                    if (PlayerStatus[j]==player_ingame)
                        ProcessPlayerCommand( j );
                }
                break;
            }
            else
            {
                UpdateClientControls();
            }
        }
    }
}


//****************************************************************************
//
// ControlPlayerObj ()
//
//****************************************************************************
void ControlPlayerObj (objtype * ob)
{
    playertype * pstate;
    int num;
    int savetime;
//   boolean asked;

//   if (GamePaused==true)
//      return;

    M_LINKSTATE(ob,pstate);

    // get player number

    num=ob->dirchoosetime;

    memcpy (pstate->buttonheld, pstate->buttonstate, sizeof(pstate->buttonstate));

    if (oldpolltime==nextupdatetime)
    {
        if (num==numplayers-1)
            nextupdatetime=oldpolltime+controldivisor;
        if (networkgame==true)
            savetime=GetTicCount()+NETWORKTIMEOUT;
        else
            savetime=GetTicCount()+MODEMTIMEOUT;

        if (PlayerStatus[num]!=player_ingame)
            return;

        //   asked=false;

        // copy previous state of buttons


        while (1)
        {
            if (ServerCommandStatus(oldpolltime)==cs_ready)
            {
                ProcessPlayerCommand (num);
                if (demoplayback||demorecord) {
                    SoftError("x=%4x y=%4x a=%4x time=%5d\n",player->x,player->y,player->angle,oldpolltime);
                }
                break;
            }
            //      else if ((ServerCommandStatus(oldpolltime)==cs_fixing) &&
            //               (networkgame==false) &&
            //               (asked==false)
            //              )
            //         {
            //         asked=true;
            //         RequestPacket(oldpolltime, server, controldivisor);
            //         }
            else
            {
                UpdateClientControls();
            }

            if (GetTicCount()>savetime)
            {
                SoftError("Client timeout oldpolltime=%d\n",oldpolltime);
                if (IsServer==false)
                    RequestPacket(oldpolltime, server, controldivisor);
                if (networkgame==true)
                    savetime=GetTicCount()+NETWORKTIMEOUT;
                else
                    savetime=GetTicCount()+MODEMTIMEOUT;
            }
        }
    }

    if (!(ob->flags&FL_DYING))
    {
        if (ob->flags&FL_PUSHED)
        {
            ob->flags&=~FL_PUSHED;
            if (abs(ob->momentumx+pstate->dmomx)>=abs(ob->momentumx))
            {
                ob->momentumx += pstate->dmomx;
            }
            if (abs(ob->momentumy+pstate->dmomy)>=abs(ob->momentumy))
            {
                ob->momentumy += pstate->dmomy;
            }
        }
        else
        {
            ob->momentumx += pstate->dmomx;
            ob->momentumy += pstate->dmomy;
        }
    }
}

//****************************************************************************
//
// MaxSpeedForCharacter ()
//
//****************************************************************************

int MaxSpeedForCharacter(playertype*pstate)
{
    if (BATTLEMODE && (gamestate.BattleOptions.Speed == bo_fast_speed))
    {
        return( FASTSPEED );
    }
    else
    {
        if (pstate->buttonstate[bt_run])
            return (characters[pstate->player].toprunspeed);
        else
            return (characters[pstate->player].topspeed);
    }
}

//****************************************************************************
//
// UpdatePlayerObj ()
//
//****************************************************************************

void UpdatePlayerObj ( int player )
{
    int i, buttonbits;
    playertype * pstate;
    MoveType * MoveCmd;

    MoveCmd=(MoveType *)PlayerCommand(player,CommandAddress(oldpolltime));

    pstate=&PLAYERSTATE[player];

    buttonbits = MoveCmd->buttons;
    for (i = 0; i < NUMTXBUTTONS; i++)
    {
        pstate->buttonstate[i] = buttonbits & 1;
        buttonbits   >>= 1;
    }

    pstate->dmomx = (int)(MoveCmd->momx)<<1;
    pstate->dmomy = (int)(MoveCmd->momy)<<1;
    pstate->angle = MoveCmd->dangle;
    pstate->angle <<= 11;
    pstate->topspeed=MaxSpeedForCharacter(pstate);

    if (demoplayback||demorecord) {
        SoftError("  dmx=%4x dmy=%4x da=%4x time=%5d\n",pstate->dmomx,pstate->dmomy,pstate->angle>>11,oldpolltime);
    }
}


//****************************************************************************
//
// SendPlayerDescription ()
//
//****************************************************************************

void SendPlayerDescription( void )
{
    byte * temp;
    COM_GamePlayerType * desc;
    int length;

    length=sizeof(COM_GamePlayerType);
    temp=SafeMalloc(length);

    memset(temp,0,length);

    desc=(COM_GamePlayerType *)temp;
    desc->type=(byte)COM_GAMEDESC;
    desc->player=consoleplayer;
    desc->violence=gamestate.violence;
    desc->Version = gamestate.Version;
    desc->Product = gamestate.Product;
    desc->playerdescription.character=locplayerstate->player;
    desc->playerdescription.uniformcolor=locplayerstate->uniformcolor;
    strcpy(&(desc->playerdescription.codename[0]),
           &locplayerstate->codename[0]);

    WritePacket(temp,length,server);

    SafeFree(temp);
}

//****************************************************************************
//
// SendGameDescription ()
//
//****************************************************************************

void SendGameDescription( void )
{
    byte * temp;
    COM_GameMasterType * desc;
    int length;
    int i;

    length=sizeof(COM_GameMasterType);
    temp=SafeMalloc(length);

    memset(temp,0,length);

    desc=(COM_GameMasterType *)temp;
    desc->type=(byte)COM_GAMEMASTER;
    desc->level=gamestate.mapon;
    desc->mapcrc=GetMapCRC (gamestate.mapon);
    desc->mode=gamestate.battlemode;
    desc->violence=gamestate.violence;
    desc->Version = gamestate.Version;
    desc->Product = gamestate.Product;
    desc->teamplay = gamestate.teamplay;
    memcpy( &desc->SpecialsTimes, &gamestate.SpecialsTimes, sizeof( specials ) );
    BATTLE_GetOptions( &( desc->options ) );
    GetMapFileName( &(desc->battlefilename[0]) );
    desc->randomseed=GetRNGindex ( );
    gamestate.randomseed=desc->randomseed;
    desc->ludicrousgibs=battlegibs;
    ludicrousgibs=battlegibs;
//   SetRNGindex ( gamestate.randomseed );
    for (i=0; i<numplayers; i++)
    {
        if (gamestate.Product == ROTT_SHAREWARE)
            PLAYERSTATE[i].player = 0;
        desc->players[i].character    =PLAYERSTATE[i].player;
        desc->players[i].uniformcolor =PLAYERSTATE[i].uniformcolor;
        strcpy ( &(desc->players[i].codename[0]),&(PLAYERSTATE[i].codename[0]));
    }

    if (!networkgame)
        AssignTeams();

    if (IsServer==false)
    {
        WritePacket(temp,length,server);
    }
    else
    {
        BroadcastServerPacket(temp,length); // Send to all
    }

    SafeFree(temp);
}

//****************************************************************************
//
// SetGameDescription ()
//
//****************************************************************************

void SetGameDescription( void * pkt )
{
    COM_GameMasterType * desc;
    word localcrc;
    int i;

    desc=(COM_GameMasterType *)pkt;
    gamestate.mapon=desc->level;
    gamestate.battlemode=desc->mode;
    gamestate.violence=desc->violence;
    gamestate.Version = desc->Version;
    gamestate.Product = desc->Product;
    gamestate.teamplay = desc->teamplay;
    memcpy( &gamestate.SpecialsTimes, &desc->SpecialsTimes, sizeof( specials ) );
    BATTLE_SetOptions( &( desc->options ) );
    gamestate.randomseed=desc->randomseed;
    SetRNGindex ( gamestate.randomseed );
    SetBattleMapFileName( &(desc->battlefilename[0]) );
    localcrc=GetMapCRC (gamestate.mapon);
    ludicrousgibs=desc->ludicrousgibs;
    if (localcrc!=desc->mapcrc)
        Error("You have different maps on your system\n");
    for (i=0; i<numplayers; i++)
    {
        PLAYERSTATE[i].player=desc->players[i].character;
        PLAYERSTATE[i].uniformcolor=desc->players[i].uniformcolor;
        strcpy ( &(PLAYERSTATE[i].codename[0]),
                 &(desc->players[i].codename[0])
               );
    }
    AssignTeams();
}

//****************************************************************************
//
// SetPlayerDescription ()
//
//****************************************************************************

void SetPlayerDescription( void * pkt )
{
    COM_GamePlayerType * desc;

    desc=(COM_GamePlayerType *)pkt;
    PLAYERSTATE[desc->player].player=desc->playerdescription.character;
    PLAYERSTATE[desc->player].uniformcolor=desc->playerdescription.uniformcolor;
    strcpy ( &(PLAYERSTATE[desc->player].codename[0]),
             &(desc->playerdescription.codename[0])
           );
    if ( gamestate.Version != desc->Version )
    {
        Error("Player %s is using a different version of ROTT\n",PLAYERSTATE[desc->player].codename);
//      gamestate.Version = desc->Version;
    }

    if ( gamestate.violence > desc->violence )
    {
        gamestate.violence = desc->violence;
    }

    if ( gamestate.Product > desc->Product )
    {
        gamestate.Product = desc->Product;
    }
}

//****************************************************************************
//
// SendGameAck ()
//
//****************************************************************************

void SendGameAck( void )
{
    byte * temp;
    int length;
    COM_GameAckType * desc;

    length=sizeof(COM_GameAckType);
    temp=SafeMalloc(length);
    desc=(COM_GameAckType *)temp;
    desc->type=COM_GAMEACK;
    desc->player=consoleplayer;

    WritePacket(temp,length,server);

    SafeFree(temp);
}

//****************************************************************************
//
// SendGameStart ()
//
//****************************************************************************

void SendGameStart( void )
{
    byte * temp;
    int length;

    length=DUMMYPACKETSIZE;
    temp=SafeMalloc(length);
    *(temp)=(byte)COM_GAMEPLAY;

    if (IsServer==false)
    {
        WritePacket(temp,length,server);
    }
    else
    {
        BroadcastServerPacket(temp,length); // Send to all
    }

    SafeFree(temp);
}

//****************************************************************************
//
// SetupGamePlayer ()
//
//****************************************************************************
void SetupGamePlayer ( void )
{
    int savetime;
    boolean done;
    boolean gameready;

    savetime=GetTicCount();

    done=false;
    gameready=false;

    while (done==false)
    {
        // Setup individual player
        AbortCheck("SetupGamePlayer aborted\n");

        // send Player Description
        if (GetTicCount() >= savetime)
        {
            savetime=GetTicCount()+SETUPTIME;
            if (gameready==false)
                SendPlayerDescription();
            else
                SendGameAck();
        }
        switch (SetupCheckForPacket())
        {
        case scfp_done:
            done=true;
            break;
        case scfp_gameready:
            gameready=true;
            break;
        }
    }
    savetime=GetTicCount()+(VBLCOUNTER/2);

    while (GetTicCount()<savetime)
    {
        SetupCheckForPacket ();
    }
}

//****************************************************************************
//
// AllPlayersReady ()
//
//****************************************************************************
boolean AllPlayersReady ( void )
{
    int i;

    for (i=0; i<numplayers; i++)
        if ((PlayersReady[i]==false) && (PlayerStatus[i]==player_ingame))
            return false;

    return true;
}

//****************************************************************************
//
// GotAllPlayerDescriptions ()
//
//****************************************************************************
boolean GotAllPlayerDescriptions ( void )
{
    int i;

    for (i=0; i<numplayers; i++)
        if ((GotPlayersDesc[i]==false) && (PlayerStatus[i]==player_ingame))
            return false;

    return true;
}

//****************************************************************************
//
// SetupGameMaster ()
//
//****************************************************************************
void SetupGameMaster ( void )
{
    int savetime;
    boolean done;

    memset(GotPlayersDesc,false,sizeof(GotPlayersDesc));
    GotPlayersDesc[consoleplayer]=true;

    memset(PlayersReady,false,sizeof(PlayersReady));
    PlayersReady[consoleplayer]=true;

    savetime=GetTicCount();

    done=false;

    InitializeRNG ();

    while (done==false)
    {
        // Setup individual player

        AbortCheck("SetupGameMaster aborted\n");

        // send Game Description
        if (GetTicCount() >= savetime)
        {
            savetime=GetTicCount()+SETUPTIME;
            if (GotAllPlayerDescriptions()==true)
                SendGameDescription();
        }
        if (AllPlayersReady ()==true)
        {
            SendGameStart();
            SendGameStart();
            done=true;
        }
        SetupCheckForPacket();
    }
    savetime=GetTicCount()+(VBLCOUNTER/2);

    while (GetTicCount()<savetime)
    {
        SetupCheckForPacket ();
    }
}










/*
=============================================================================

   DEMO CODE

=============================================================================
*/
//****************************************************************************
//
// GetDemoFilename ()
//
//****************************************************************************

void GetDemoFilename (int demonumber, char * filename)
{
    strcpy(filename,DATADIR "DEMO0_0.DMO\0");

    filename[4 + strlen(DATADIR)] = (char)('0' + (byte)demonumber);
    filename[6 + strlen(DATADIR)] = (char)('0' + (byte)gamestate.violence);
    FixFilePath(filename);
}
//****************************************************************************
//
// DemoExists ()
//
//****************************************************************************

boolean DemoExists (int demonumber)
{
    char demo[20 + sizeof(DATADIR)];

    GetDemoFilename (demonumber, &demo[0]);
    if (access (demo, F_OK) == 0)
        return true;
    else
    {
        /* Saves the users violence level, only do this once, otherwise
           we might override the saved level with one already modified by us */
        if (predemo_violence == -1)
            predemo_violence = gamestate.violence;
        /* The demos distributed with rott are all for a violence level of 3 */
        gamestate.violence = 3;
        GetDemoFilename (demonumber, &demo[0]);
        if (access (demo, F_OK) == 0)
            return true;
        else
            return false;
    }
}

//****************************************************************************
//
// SaveDemo ()
//
//****************************************************************************

void SaveDemo (int demonumber)
{
    char demo[20 + sizeof(DATADIR)];

    RecordDemoCmd ();
    GetDemoFilename (demonumber, &demo[0]);
    SaveFile (demo, demobuffer, (demoptr-demobuffer));
    FreeDemo();
}

//****************************************************************************
//
// LoadDemo ()
//
//****************************************************************************

void LoadDemo (int demonumber)
{
    char demo[20 + sizeof(DATADIR)];
    int size;

    GetDemoFilename (demonumber, demo);
    if (demobuffer!=NULL)
        FreeDemo();
    size = LoadFile (demo, (void **)&demobuffer);
    playstate = ex_demoplayback;
    demoptr = demobuffer;
    lastdemoptr = (demoptr+size);
    locplayerstate->player=0;
    InitializeWeapons(locplayerstate);
    ResetPlayerstate(locplayerstate);
    InitCharacter();
}

//****************************************************************************
//
// RecordDemo ()
//
//****************************************************************************

void RecordDemo ( void )
{
    DemoHeaderType * DemoHeader;
    int level;

    if (demobuffer!=NULL)
        FreeDemo();
    godmode=0;
    demobuffer = SafeMalloc (DEMOBUFFSIZE);
    demoptr = demobuffer;
    lastdemoptr = demobuffer+DEMOBUFFSIZE;

    // Save off level number

    DemoHeader=(DemoHeaderType *)demoptr;
    demoptr+=sizeof(gamestate);
    memcpy(&(DemoHeader->demostate),&gamestate,sizeof(gamestate));
    demorecord = true;
    locplayerstate->player=0;
    InitializeWeapons(locplayerstate);
    ResetPlayerstate(locplayerstate);
    level=gamestate.mapon;
    InitCharacter();
    gamestate.mapon=level;
    SoftError(">>>>>>>>>>>>Start demo record\n");
}

//****************************************************************************
//
// SetupDemo ()
//
//****************************************************************************

void SetupDemo ( void )
{
    DemoHeaderType * DemoHeader;

    demoplayback = true;
    godmode=0;

    DemoHeader=(DemoHeaderType *)demoptr;
    demoptr+=sizeof(gamestate);
//   if (gamestate.violence!=DemoHeader->demostate.violence)
//      Error ("This demo has a different difficulty level than your current settings\n");
    memcpy(&gamestate,&(DemoHeader->demostate),sizeof(gamestate));
    SoftError(">>>>>>>>>>>>Start demo playback\n");
}

//****************************************************************************
//
// FreeDemo ()
//
//****************************************************************************

void FreeDemo ( void )
{
    demoplayback = false;
    demorecord = false;
    SafeFree (demobuffer);
    demobuffer=NULL;
}

//****************************************************************************
//
// CheckForDemoDone ()
//
//****************************************************************************

void CheckForDemoDone ( void )
{
    if ((demoplayback==true) && (demoptr >= lastdemoptr))
    {
        FreeDemo();
        playstate = ex_demodone;
    }
}

//****************************************************************************
//
// CheckForDemoOverflowed ()
//
//****************************************************************************

void CheckForDemoOverflowed ( void )
{
    if (demoptr >= (lastdemoptr-sizeof(DemoType)))
    {
        playstate = ex_completed;     // demo is done
        EndDemo();
    }
}

//****************************************************************************
//
// RecordDemoCmd ()
//
//****************************************************************************

void RecordDemoCmd (void)
{
    DemoType * dtime;

    SoftError("Demo command recorded at %d\n",controlupdatetime);
    dtime=(DemoType *)demoptr;
    dtime->time = controlupdatetime;
    dtime->momx = (controlbuf[0]>>1);
    dtime->momy = (controlbuf[1]>>1);
    dtime->dangle = controlbuf[2]>>11;
    dtime->buttons = buttonbits;

    demoptr+=sizeof(DemoType);

    CheckForDemoOverflowed();
}

//****************************************************************************
//
// AddDemoCmd ()
//
//****************************************************************************

void AddDemoCmd (void)
{
    DemoType * dtime;

    //
    // get info from demo buffer
    //

    SoftError("Demo command played at %d\n",controlupdatetime);
    if (demoplayback==true)
    {
        dtime=(DemoType *)demoptr;
        controlbuf[0]=dtime->momx<<1;
        controlbuf[1]=dtime->momy<<1;
        controlbuf[2]=dtime->dangle<<11;
        buttonbits   =dtime->buttons;
        demoptr+=sizeof(DemoType);
    }
}

//****************************************************************************
//
// GetNextDemoTime ()
//
//****************************************************************************

int GetNextDemoTime (void)
{
    DemoType * dtime;

    CheckForDemoDone();
    dtime=(DemoType *)demoptr;
    if (demoplayback)
        return dtime->time;
    else
        return -1;
}

//****************************************************************************
//
// UpdateDemoPlayback ()
//
//****************************************************************************

void UpdateDemoPlayback (int time)
{
    if (demoplayback)
    {
        if (GetNextDemoTime()==time)
            AddDemoCmd();
    }
}