Fixing FitzQuake 0.85 protocol 15 support

Discuss programming topics for the various GPL'd game engine sources.
Post Reply
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Fixing FitzQuake 0.85 protocol 15 support

Post by Baker »

FitzQuake 0.85 can't actually serve a protocol 15 game to a standard Quake client. Demos recorded using protocol 15 are fine and can be played. FitzQuake 0.85 server sends a message during connection that is too long for a standard Quake client.

I noticed that Ben Jardrup's Enhanced GLQuake had a solution for this and I implemented something very similar.

A outline of my implementation:

1. sv_main.c - A global variable to control the max datagram size. MAX_MARK_V_DATAGRAM is 65526 (65535 --- the maximum signed short value --- less room for the header)

Code: Select all

#ifdef SUPPORTS_SERVER_PROTOCOL_15
int host_protocol_datagram_maxsize = MAX_MARK_V_DATAGRAM; // Baker
#endif // SUPPORTS_SERVER_PROTOCOL_15
2. quakedef.h, share the global

Code: Select all

#ifdef SUPPORTS_SERVER_PROTOCOL_15
// Baker: To share with net_dgrm.c
extern int host_protocol_datagram_maxsize;
#endif // SUPPORTS_SERVER_PROTOCOL_15
3. cl_main.c -> CL_ClearState - establish the client datagram limit (this occurs during any map change whether running as a client or server)

Code: Select all

#ifdef SUPPORTS_SERVER_PROTOCOL_15
// Baker: If not running a server, restore datagram cap for client
    if (!sv.active)
        host_protocol_datagram_maxsize = MAX_MARK_V_DATAGRAM;
#endif // SUPPORTS_SERVER_PROTOCOL_15
4. Not directly related, but helps visualize the limits in various engines

Code: Select all

typedef enum
{
//  Mark V limits ... Baker: I prefer not to do these ... but Quakespasm increased theirs (32000-> 64000).  I am increasing them slightly higher to absolute maximums.
//  QUAKESPASM_MAX_MSGLEN           = 64000, // Breaks compatibility with FitzQuake 0.85
//  QUAKESPASM_MAXMESSAGE           = 64000, // http://www.celephais.net/board/view_thread.php?id=60452&start=721

    MAX_MARK_V_MSGLEN               = 65535, // Upper limit of an signed short
    MAX_MARK_V_SIGNON               = MAX_MARK_V_MSGLEN - 2,
    NET_MARK_V_MAXMESSAGE           = 65535,
    MAX_MARK_V_DATAGRAM             = 65526,
//  MAX_MARK_V_CLIENT_MAXMESSAGE    = MAX_MARK_V_MAXMESSAGE, // 65535
    // (NETFLAG_DATA - 1 - NET_HEADERSIZE)
    //  0x0FFFF - 1 - (2 * sizeof(unsigned int))
    // 65535 - 1 - 8 = 65526

    // Quakespasm increased for an upcoming map (ijed?)

    MAX_MARK_V_ENT_LEAFS            = 32,     MAX_FITZQUAKE_WINQUAKE_ENT_LEAFS = 16,
    MAX_MARK_V_EFRAGS               = 4096,

// End Mark V limits

//  FitzQuake limit                  // Standard limit (WinQuake)
    MAX_EDICTS_PROTOCOL_666         = 32000,

    MIN_SANE_EDICTS_512             =  512,
    MAX_SANE_EDICTS_8192            =  8192,

    MAX_FITZQUAKE_MSGLEN            = 32000,  MAX_WINQUAKE_MSGLEN           =  8000,
    NET_FITZQUAKE_MAXMESSAGE        = 32000,  MAX_WINQUAKE_MAXMESSAGE       =  8192,
    MAX_FITZQUAKE_DATAGRAM_SIZE     = 32000,  MAX_WINQUAKE_DATAGRAM         =  1024,

    MAX_FITZQUAKE_SIGNON            = /* 31998 */
                MAX_FITZQUAKE_MSGLEN - 2,     MAX_WINQUAKE_SIGNON           =  MAX_WINQUAKE_MSGLEN - 2, /* 7998 */

    MAX_FITZQUAKE_DATAGRAM_MTU      = 1400,
    // ^^ FIX ME!!!!!  It is intended to be 1400

// per-level limits
    MAX_FITZQUAKE_BEAMS             =    32,  MAX_WINQUAKE_BEAMS            =    24,
    MAX_FITZQUAKE_EFRAGS            =  2048,  MAX_WINQUAKE_EFRAGS           =   600,
    MAX_FITZQUAKE_DLIGHTS           =   128,  MAX_WINQUAKE_DLIGHTS          =    32,
    MAX_FITZQUAKE_STATIC_ENTITIES   =   512,  MAX_WINQUAKE_STATIC_ENTITIES  =   128,
    MAX_FITZQUAKE_TEMP_ENTITIES     =   256,  MAX_WINQUAKE_TEMP_ENTITIES    =    64,
    MAX_FITZQUAKE_VISEDICTS         =  1024,  MAX_WINQUAKE_VISEDICTS        =   256,

    MAX_FITZQUAKE_LIGHTMAPS         =   256,  MAX_WINQUAKE_LIGHTMAPS        =    64,

    MAX_FITZQUAKE_MAX_EDICTS        = 32000,  MAX_WINQUAKE_EDICTS           =   600,
    MAX_FITZQUAKE_MODELS            =  2048,  MAX_WINQUAKE_MODELS           =   256,
    MAX_FITZQUAKE_SOUNDS            =  2048,  MAX_WINQUAKE_SOUNDS           =   256,

    MAX_FITZQUAKE_SURFACE_EXTENTS   =  2000,  MAX_WINQUAKE_SURFACE_EXTENTS  =   256,

} engine_limits;
//johnfitz -- ents past 8192 can't play sounds in the standard protocol
5. sv_main.c -> SV_SpawnServer - server must determine maximum datagram size based on protocol (i.e. NetQuake = 15 or FitzQuake 666)

Code: Select all

// allocate server memory

#ifdef SUPPORTS_SERVER_PROTOCOL_15
    switch (sv_protocol)
    {
    case PROTOCOL_NETQUAKE:
        //sv.datagram.maxsize = sv_protocol == sizeof(sv.datagram_buf); // Baker: Was unused
        sv.datagram.maxsize          = MAX_WINQUAKE_DATAGRAM        /* 1024 */ ;
        sv.signon.maxsize            = MAX_WINQUAKE_SIGNON;         // 8000 - 2 = 7998
        break;

    case PROTOCOL_FITZQUAKE:
    case PROTOCOL_FITZQUAKE_PLUS:
        if (svs.maxclients == 1 && !isDedicated)
            sv.datagram.maxsize      = MAX_MARK_V_DATAGRAM          /* 65526 */ ;
        else sv.datagram.maxsize     = MAX_FITZQUAKE_DATAGRAM_MTU   /* 1400 */ ;
        sv.signon.maxsize            = MAX_MARK_V_SIGNON;           // 65535-2  = 65533
        break;
    }

#if 0
    Con_Printf ("sv.datagram.maxsize is %d\n", sv.datagram.maxsize);
    Con_Printf ("sv.signon.maxsize is %d\n", sv.signon.maxsize);
    Con_Printf ("sv_protocol is %d\n", sv_protocol);
#endif
    sv.datagram.cursize = 0;
    sv.datagram.data = sv.datagram_buf;

    host_protocol_datagram_maxsize =  // continued ...
    sv.reliable_datagram.maxsize = sv.datagram.maxsize;
    sv.reliable_datagram.cursize = 0;
    sv.reliable_datagram.data = sv.reliable_datagram_buf;
#endif // SUPPORTS_SERVER_PROTOCOL_15
6. Numerous changes to net_dgrm.c and sv_main.c to use the global variable "host_protocol_datagram_maxsize". Marked with "#ifdef SUPPORTS_SERVER_PROTOCOL_15"

6A. net_dgrm.c

Code: Select all

/*
Copyright (C) 1996-2001 Id Software, Inc.
Copyright (C) 2002-2009 John Fitzgibbons and others
Copyright (C) 2007-2008 Kristian Duske
Copyright (C) 2009-2014 Baker and others

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.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// net_dgrm.c

// This is enables a simple IP banning mechanism
#define BAN_TEST

#include "core.h"
#include "q_stdinc.h"
#include "arch_def.h"
#include "net_sys.h"
#include "quakedef.h"
#include "net_defs.h"
#include "net_dgrm.h"

// these two macros are to make the code more readable
#define sfunc   net_landrivers[sock->landriver]
#define dfunc   net_landrivers[net_landriverlevel]

static int net_landriverlevel;

/* statistic counters */
static int  packetsSent = 0;
static int  packetsReSent = 0;
static int packetsReceived = 0;
static int receivedDuplicateCount = 0;
static int shortPacketCount = 0;
static int droppedDatagrams;

static struct
{
    unsigned int    length;
    unsigned int    sequence;
    byte            data[MAX_MARK_V_DATAGRAM];
} packetBuffer;

static int myDriverLevel;

extern cbool m_return_onerror;
extern char m_return_reason[32];


static char *StrAddr (struct qsockaddr *addr)
{
    static char buf[34];
    byte *p = (byte *)addr;
    int n;

    for (n = 0; n < 16; n++)
        sprintf (buf + n * 2, "%02x", *p++);
    return buf;
}


#ifdef BAN_TEST

static struct in_addr   banAddr;
static struct in_addr   banMask;

void NET_Ban_f (void)
{
    char    addrStr [32];
    char    maskStr [32];
    void    (*print_fn)(const char *fmt, ...)
                __fp_attribute__((__format__(__printf__,1,2)));

    if (cmd_source == src_command)
    {
        if (!sv.active)
        {
            Cmd_ForwardToServer ();
            return;
        }
        print_fn = Con_Printf;
    }
    else
    {
        if (pr_global_struct->deathmatch && !host_client->privileged)
            return;
        print_fn = SV_ClientPrintf;
    }

    switch (Cmd_Argc ())
    {
        case 1:
        if (banAddr.s_addr != INADDR_ANY)
            {
            c_strlcpy (addrStr, inet_ntoa(banAddr));
            c_strlcpy (maskStr, inet_ntoa(banMask));
                print_fn("Banning %s [%s]\n", addrStr, maskStr);
            }
            else
                print_fn("Banning not active\n");
            break;

        case 2:
            if (strcasecmp(Cmd_Argv(1), "off") == 0)
                banAddr.s_addr = INADDR_ANY;
            else
                banAddr.s_addr = inet_addr(Cmd_Argv(1));
            banMask.s_addr = INADDR_NONE;
            break;

        case 3:
            banAddr.s_addr = inet_addr(Cmd_Argv(1));
            banMask.s_addr = inet_addr(Cmd_Argv(2));
            break;

        default:
            print_fn("BAN ip_address [mask]\n");
            break;
    }
}
#endif  // BAN_TEST


int Datagram_SendMessage (qsocket_t *sock, sizebuf_t *data)
{
    unsigned int    packetLen;
    unsigned int    dataLen;
    unsigned int    eom;

#ifdef DEBUG
    if (data->cursize == 0)
        System_Error("Datagram_SendMessage: zero length message");

    if (data->cursize > NET_FITZQUAKE_MAXMESSAGE)
        System_Error("Datagram_SendMessage: message too big %u\n", data->cursize);

    if (sock->canSend == false)
        System_Error("SendMessage: called with canSend == false");
#endif

//  Con_Printf ("Sending data to %s with length of %i (max: %i) with maxsize of %i\n", sock->address,  data->cursize, data->maxsize, sv.datagram.maxsize);

    memcpy (sock->sendMessage, data->data, data->cursize);
    sock->sendMessageLength = data->cursize;

#ifdef SUPPORTS_SERVER_PROTOCOL_15
    if (data->cursize <= host_protocol_datagram_maxsize)
#endif // SUPPORTS_SERVER_PROTOCOL_15
    {
        dataLen = data->cursize;
        eom = NETFLAG_EOM;
    }
    else
    {
#ifdef SUPPORTS_SERVER_PROTOCOL_15
        dataLen = host_protocol_datagram_maxsize;
#endif // SUPPORTS_SERVER_PROTOCOL_15
        eom = 0;
    }
    packetLen = NET_HEADERSIZE + dataLen;

    packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom));
    packetBuffer.sequence = BigLong(sock->sendSequence++);
    memcpy (packetBuffer.data, sock->sendMessage, dataLen);

    sock->canSend = false;

//  Con_Printf ("Datagram sent with size %d, maxsize should be %d\n", packetLen, host_protocol_datagram_maxsize);

    if (sfunc.Write (sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1)
        return -1;

    sock->lastSendTime = net_time;
    packetsSent++;

    return 1;
}


static int SendMessageNext (qsocket_t *sock)
{
    unsigned int    packetLen;
    unsigned int    dataLen;
    unsigned int    eom;

#ifdef SUPPORTS_SERVER_PROTOCOL_15
    if (sock->sendMessageLength <= host_protocol_datagram_maxsize)
#endif // SUPPORTS_SERVER_PROTOCOL_15
    {
        dataLen = sock->sendMessageLength;
        eom = NETFLAG_EOM;
    }
    else
    {
#ifdef SUPPORTS_SERVER_PROTOCOL_15
        dataLen = host_protocol_datagram_maxsize;
#endif // SUPPORTS_SERVER_PROTOCOL_15
        eom = 0;
    }
    packetLen = NET_HEADERSIZE + dataLen;

    packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom));
    packetBuffer.sequence = BigLong(sock->sendSequence++);
    memcpy (packetBuffer.data, sock->sendMessage, dataLen);

    sock->sendNext = false;

    if (sfunc.Write (sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1)
        return -1;

    sock->lastSendTime = net_time;
    packetsSent++;

    return 1;
}


static int ReSendMessage (qsocket_t *sock)
{
    unsigned int    packetLen;
    unsigned int    dataLen;
    unsigned int    eom;

#ifdef SUPPORTS_SERVER_PROTOCOL_15
    if (sock->sendMessageLength <= host_protocol_datagram_maxsize)
#endif // SUPPORTS_SERVER_PROTOCOL_15
    {
        dataLen = sock->sendMessageLength;
        eom = NETFLAG_EOM;
    }
    else
    {
#ifdef SUPPORTS_SERVER_PROTOCOL_15
        dataLen = host_protocol_datagram_maxsize;
#endif // SUPPORTS_SERVER_PROTOCOL_15
        eom = 0;
    }
    packetLen = NET_HEADERSIZE + dataLen;

    packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom));
    packetBuffer.sequence = BigLong(sock->sendSequence - 1);
    memcpy (packetBuffer.data, sock->sendMessage, dataLen);

    sock->sendNext = false;

    if (sfunc.Write (sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1)
        return -1;

    sock->lastSendTime = net_time;
    packetsReSent++;

    return 1;
}


cbool Datagram_CanSendMessage (qsocket_t *sock)
{
    if (sock->sendNext)
        SendMessageNext (sock);

    return sock->canSend;
}


cbool Datagram_CanSendUnreliableMessage (qsocket_t *sock)
{
    return true;
}


int Datagram_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data)
{
    int     packetLen;

#ifdef DEBUG
    if (data->cursize == 0)
        System_Error("Datagram_SendUnreliableMessage: zero length message");

    if (data->cursize > MAX_MARK_V_DATAGRAM)
        System_Error("Datagram_SendUnreliableMessage: message too big %u", data->cursize);
#endif

    packetLen = NET_HEADERSIZE + data->cursize;

    packetBuffer.length = BigLong(packetLen | NETFLAG_UNRELIABLE);
    packetBuffer.sequence = BigLong(sock->unreliableSendSequence++);
    memcpy (packetBuffer.data, data->data, data->cursize);

    if (sfunc.Write (sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1)
        return -1;

    packetsSent++;
    return 1;
}


int Datagram_GetMessage (qsocket_t *sock)
{
    unsigned int    length;
    unsigned int    flags;
    int             ret = 0;
    struct qsockaddr readaddr;
    unsigned int    sequence;
    unsigned int    count;

    if (!sock->canSend)
        if ((net_time - sock->lastSendTime) > 1.0)
            ReSendMessage (sock);

    while(1)
    {
        length = (unsigned int)sfunc.Read (sock->socket, (byte *)&packetBuffer,
#ifdef SUPPORTS_SERVER_PROTOCOL_15
            host_protocol_datagram_maxsize, &readaddr);
#endif // SUPPORTS_SERVER_PROTOCOL_15

//  if ((rand() & 255) > 220)
//      continue;

        if (length == 0)
            break;

        if (length == (unsigned int)-1)
        {
            Con_Printf ("Datagram_GetMessage: Read error\n");
            return -1;
        }

        // ProQuake opens a new sock so the address changes
        if (sfunc.AddrCompare(&readaddr, &sock->addr) != 0)
        {
            Con_Printf("Forged packet received\n");
            Con_Printf("Expected: %s\n", StrAddr (&sock->addr));
            Con_Printf("Received: %s\n", StrAddr (&readaddr));
            continue;
        }

        if (length < NET_HEADERSIZE)
        {
            shortPacketCount++;
            continue;
        }

        length = BigLong(packetBuffer.length);
        flags = length & (~NETFLAG_LENGTH_MASK);
        length &= NETFLAG_LENGTH_MASK;

//#ifdef SUPPORTS_NETWORK_FIX // Baker change +
        // From ProQuake:  fix for attack that crashes server
        if (length > NET_MARK_V_DATAGRAMSIZE)
        {
            Con_Printf ("Datagram_GetMessage: Invalid length\n");
            return -1;
        }
//#endif // Baker change +

        if (flags & NETFLAG_CTL)
            continue;

        sequence = BigLong(packetBuffer.sequence);
        packetsReceived++;

        if (flags & NETFLAG_UNRELIABLE)
        {
            if (sequence < sock->unreliableReceiveSequence)
            {
                Con_DPrintf("Got a stale datagram\n");
                ret = 0;
                break;
            }
            if (sequence != sock->unreliableReceiveSequence)
            {
                count = sequence - sock->unreliableReceiveSequence;
                droppedDatagrams += count;
                Con_DPrintf("Dropped %u datagram(s)\n", count);
            }
            sock->unreliableReceiveSequence = sequence + 1;

            length -= NET_HEADERSIZE;

            SZ_Clear (&net_message);
            SZ_Write (&net_message, packetBuffer.data, length);

            ret = 2;
            break;
        }

        if (flags & NETFLAG_ACK)
        {
            if (sequence != (sock->sendSequence - 1))
            {
                Con_DPrintf("Stale ACK received\n");
                continue;
            }
            if (sequence == sock->ackSequence)
            {
                sock->ackSequence++;
                if (sock->ackSequence != sock->sendSequence)
                    Con_DPrintf("ack sequencing error\n");
            }
            else
            {
                Con_DPrintf("Duplicate ACK received\n");
                continue;
            }
#ifdef SUPPORTS_SERVER_PROTOCOL_15
            sock->sendMessageLength -= host_protocol_datagram_maxsize;
#endif // SUPPORTS_SERVER_PROTOCOL_15
            if (sock->sendMessageLength > 0)
            {
#ifdef SUPPORTS_SERVER_PROTOCOL_15
                memmove (sock->sendMessage, sock->sendMessage + host_protocol_datagram_maxsize, sock->sendMessageLength);
#endif // SUPPORTS_SERVER_PROTOCOL_15
                sock->sendNext = true;
            }
            else
            {
                sock->sendMessageLength = 0;
                sock->canSend = true;
            }
            continue;
        }

        if (flags & NETFLAG_DATA)
        {
            packetBuffer.length = BigLong(NET_HEADERSIZE | NETFLAG_ACK);
            packetBuffer.sequence = BigLong(sequence);
            sfunc.Write (sock->socket, (byte *)&packetBuffer, NET_HEADERSIZE, &readaddr);

            if (sequence != sock->receiveSequence)
            {
                receivedDuplicateCount++;
                continue;
            }
            sock->receiveSequence++;

            length -= NET_HEADERSIZE;

            if (flags & NETFLAG_EOM)
            {
                SZ_Clear(&net_message);
                SZ_Write(&net_message, sock->receiveMessage, sock->receiveMessageLength);
                SZ_Write(&net_message, packetBuffer.data, length);
                sock->receiveMessageLength = 0;

                ret = 1;
                break;
            }

            memcpy (sock->receiveMessage + sock->receiveMessageLength, packetBuffer.data, length);
            sock->receiveMessageLength += length;
            continue;
        }
    }

    if (sock->sendNext)
        SendMessageNext (sock);

    return ret;
}


static void PrintStats(qsocket_t *s)
{
    Con_Printf("canSend = %4u   \n", s->canSend);
    Con_Printf("sendSeq = %4u   ", s->sendSequence);
    Con_Printf("recvSeq = %4u   \n", s->receiveSequence);
    Con_Printf("\n");
}

static void NET_Stats_f (void)
{
    qsocket_t   *s;

    if (Cmd_Argc () == 1)
    {
        Con_Printf("unreliable messages sent   = %i\n", unreliableMessagesSent);
        Con_Printf("unreliable messages recv   = %i\n", unreliableMessagesReceived);
        Con_Printf("reliable messages sent     = %i\n", messagesSent);
        Con_Printf("reliable messages received = %i\n", messagesReceived);
        Con_Printf("packetsSent                = %i\n", packetsSent);
        Con_Printf("packetsReSent              = %i\n", packetsReSent);
        Con_Printf("packetsReceived            = %i\n", packetsReceived);
        Con_Printf("receivedDuplicateCount     = %i\n", receivedDuplicateCount);
        Con_Printf("shortPacketCount           = %i\n", shortPacketCount);
        Con_Printf("droppedDatagrams           = %i\n", droppedDatagrams);
    }
    else if (strcmp(Cmd_Argv(1), "*") == 0)
    {
        for (s = net_activeSockets; s; s = s->next)
            PrintStats(s);
        for (s = net_freeSockets; s; s = s->next)
            PrintStats(s);
    }
    else
    {
        for (s = net_activeSockets; s; s = s->next)
        {
            if (strcasecmp (Cmd_Argv(1), s->address) == 0)
                break;
        }

        if (s == NULL)
        {
            for (s = net_freeSockets; s; s = s->next)
            {
                if (strcasecmp (Cmd_Argv(1), s->address) == 0)
                    break;
            }
        }

        if (s == NULL)
            return;

        PrintStats(s);
    }
}


// recognize ip:port (based on ProQuake)
static const char *Strip_Port (const char *host)
{
    static char noport[MAX_QPATH];
            /* array size as in Host_Connect_f() */
    char        *p;
    int     port;

    if (!host || !*host)
        return host;
    c_strlcpy (noport, host);
    if ((p = strrchr(noport, ':')) == NULL)
        return host;
    *p++ = '\0';
    port = atoi (p);
    if (port > 0 && port < 65536 && port != net_hostport)
    {
        net_hostport = port;
        Con_Printf("Port set to %d\n", net_hostport);
    }
    return noport;
}


static cbool testInProgress = false;
static int      testPollCount;
static int      testDriver;
static sys_socket_t     testSocket;

static void Test_Poll(void *);
static PollProcedure    testPollProcedure = {NULL, 0.0, Test_Poll};

static void Test_Poll(void *unused)
{
    struct qsockaddr clientaddr;
    int     control;
    int     len;
    char    name[32];
    char    address[64];
    int     colors;
    int     frags;
    int     connectTime;
    byte    playerNumber;

    net_landriverlevel = testDriver;

    while (1)
    {
        len = dfunc.Read (testSocket, net_message.data, net_message.maxsize, &clientaddr);
        if (len < (int)sizeof(int))
            break;

        net_message.cursize = len;

        MSG_BeginReading ();
        control = BigLong(*((int *)net_message.data));
        MSG_ReadLong();
        if (control == -1)
            break;
        if ((control & (~NETFLAG_LENGTH_MASK)) !=  (int) NETFLAG_CTL)
            break;
        if ((control & NETFLAG_LENGTH_MASK) != len)
            break;

        if (MSG_ReadByte() != CCREP_PLAYER_INFO)
        {
            Con_Printf("Unexpected repsonse to Player Info request\n");
            break;
        }

        playerNumber = MSG_ReadByte();
        c_strlcpy (name, MSG_ReadString());
        colors = MSG_ReadLong();
        frags = MSG_ReadLong();
        connectTime = MSG_ReadLong();
        c_strlcpy (address, MSG_ReadString());

        Con_Printf("%s\n  frags:%3i  colors:%d %d  time:%d\n  %s\n", name, frags, colors >> 4, colors & 0x0f, connectTime / 60, address);
    }

    testPollCount--;
    if (testPollCount)
    {
        SchedulePollProcedure(&testPollProcedure, 0.1);
    }
    else
    {
        dfunc.Close_Socket (testSocket);
        testInProgress = false;
    }
}

void Test_f (void)
{
    const char  *host;
    int     n;
    int     maxusers = MAX_SCOREBOARD;
    struct qsockaddr sendaddr;

    if (testInProgress)
    {
        Con_Printf ("There is already a test/rcon in progress\n");
        return;
    }

    if (Cmd_Argc() < 2)
    {
        Con_Printf ("Usage: test <host>\n");
        return;
    }

    host = Strip_Port (Cmd_Argv(1));

    if (host && hostCacheCount)
    {
        for (n = 0; n < hostCacheCount; n++)
        {
            if (strcasecmp (host, hostcache[n].name) == 0)
            {
                if (hostcache[n].driver != myDriverLevel)
                    continue;
                net_landriverlevel = hostcache[n].ldriver;
                maxusers = hostcache[n].maxusers;
                memcpy (&sendaddr, &hostcache[n].addr, sizeof(struct qsockaddr));
                break;
            }
        }
        if (n < hostCacheCount)
            goto JustDoIt;
    }

    for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
    {
        if (!net_landrivers[net_landriverlevel].initialized)
            continue;

        // see if we can resolve the host name
        if (dfunc.GetAddrFromName(host, &sendaddr) != -1)
            break;
    }

    if (net_landriverlevel == net_numlandrivers)
    {
        Con_Printf("Could not resolve %s\n", host);     // JPG 3.00 - added error message
        return;
    }

JustDoIt:
    testSocket = dfunc.Open_Socket(0);
    if (testSocket == INVALID_SOCKET)
    {
        Con_Printf("Could not open socket\n");  // JPG 3.00 - added error message
        return;
    }

    testInProgress = true;
    testPollCount = 20;
    testDriver = net_landriverlevel;

    for (n = 0; n < maxusers; n++)
    {
        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREQ_PLAYER_INFO);
        MSG_WriteByte(&net_message, n);
        *((int *)net_message.data) = BigLong(NETFLAG_CTL |  (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write (testSocket, net_message.data, net_message.cursize, &sendaddr);
    }
    SZ_Clear(&net_message);
    SchedulePollProcedure(&testPollProcedure, 0.1);
}

/* JPG 3.00 - got rid of these.  Just use test vars; only ONE outstanding test of any kind.
static cbool test2InProgress = false;
static int      test2Driver;
static sys_socket_t     test2Socket;
*/

static void Test2_Poll (void *);
static PollProcedure    test2PollProcedure = {NULL, 0.0, Test2_Poll};

static void Test2_Poll (void *unused)
{
    struct qsockaddr clientaddr;
    int     control;
    int     len;
    char    name[256];
    char    value[256];

    net_landriverlevel = testDriver;
    name[0] = 0;

    len = dfunc.Read (testSocket, net_message.data, net_message.maxsize, &clientaddr);
    if (len < (int) sizeof(int))
        goto Reschedule;

    net_message.cursize = len;

    MSG_BeginReading ();
    control = BigLong(*((int *)net_message.data));
    MSG_ReadLong();
    if (control == -1)
        goto Error;
    if ((control & (~NETFLAG_LENGTH_MASK)) !=  (int) NETFLAG_CTL)
        goto Error;
    if ((control & NETFLAG_LENGTH_MASK) != len)
        goto Error;

    if (MSG_ReadByte() != CCREP_RULE_INFO)
        goto Error;

    c_strlcpy (name, MSG_ReadString());
    if (name[0] == 0)
        goto Done;
    c_strlcpy (value, MSG_ReadString());

    Con_Printf("%-16.16s  %-16.16s\n", name, value);

    SZ_Clear(&net_message);
    // save space for the header, filled in later
    MSG_WriteLong(&net_message, 0);
    MSG_WriteByte(&net_message, CCREQ_RULE_INFO);
    MSG_WriteString(&net_message, name);
    *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
    dfunc.Write (testSocket, net_message.data, net_message.cursize, &clientaddr);
    SZ_Clear(&net_message);

Reschedule:
    // JPG 3.00 - added poll counter
    testPollCount--;
    if (testPollCount)
    {
        SchedulePollProcedure(&test2PollProcedure, 0.05);
        return;
    }
    goto Done;

Error:
    Con_Printf("Unexpected response to Rule Info request\n");

Done:
    dfunc.Close_Socket (testSocket);
    testInProgress = false;
    return;
}

void Test2_f (void)
{
    const char  *host;
    int     n;
    struct qsockaddr sendaddr;

    if (testInProgress)
    {
        Con_Printf("There is already a test/rcon in progress\n");
        return;
    }

    if (Cmd_Argc() < 2)
    {
        Con_Printf ("Usage: test2 <host>\n");
        return;
    }

    host = Strip_Port (Cmd_Argv(1));

    if (host && hostCacheCount)
    {
        for (n = 0; n < hostCacheCount; n++)
        {
            if (strcasecmp (host, hostcache[n].name) == 0)
            {
                if (hostcache[n].driver != myDriverLevel)
                    continue;
                net_landriverlevel = hostcache[n].ldriver;
                memcpy (&sendaddr, &hostcache[n].addr, sizeof(struct qsockaddr));
                break;
            }
        }

        if (n < hostCacheCount)
            goto JustDoIt;
    }

    for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
    {
        if (!net_landrivers[net_landriverlevel].initialized)
            continue;

        // see if we can resolve the host name
        if (dfunc.GetAddrFromName(host, &sendaddr) != -1)
            break;
    }

    if (net_landriverlevel == net_numlandrivers)
    {
        Con_Printf("Could not resolve %s\n", host); // JPG 3.00 - added error message
        return;
    }

JustDoIt:
    testSocket = dfunc.Open_Socket (0);
    if (testSocket == INVALID_SOCKET)
    {
        Con_Printf("Could not open socket\n"); // JPG 3.00 - added error message
        return;
    }

    testInProgress = true;              // JPG 3.00 test2InProgress->testInProgress
    testPollCount = 20;                 // JPG 3.00 added this
    testDriver = net_landriverlevel;    // JPG 3.00 test2Driver->testDriver

    SZ_Clear(&net_message);
    // save space for the header, filled in later
    MSG_WriteLong(&net_message, 0);
    MSG_WriteByte(&net_message, CCREQ_RULE_INFO);
    MSG_WriteString(&net_message, "");
    *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
    dfunc.Write (testSocket, net_message.data, net_message.cursize, &sendaddr);
    SZ_Clear(&net_message);
    SchedulePollProcedure(&test2PollProcedure, 0.05);
}


int Datagram_Init (void)
{
    int i, num_inited;
    sys_socket_t csock;

#ifdef BAN_TEST
    banAddr.s_addr = INADDR_ANY;
    banMask.s_addr = INADDR_NONE;
#endif
    myDriverLevel = net_driverlevel;

    Cmd_AddCommand ("net_stats", NET_Stats_f);

    if (COM_CheckParm("-nolan"))
        return -1;

    num_inited = 0;
    for (i = 0; i < net_numlandrivers; i++)
        {
        csock = net_landrivers[i].Init ();
        if (csock == INVALID_SOCKET)
            continue;
        net_landrivers[i].initialized = true;
        net_landrivers[i].controlSock = csock;
        num_inited++;
        }

    if (num_inited == 0)
        return -1;

    return 0;
}


void Datagram_Shutdown (void)
{
    int i;

// shutdown the lan drivers
    for (i = 0; i < net_numlandrivers; i++)
    {
        if (net_landrivers[i].initialized)
        {
            net_landrivers[i].Shutdown ();
            net_landrivers[i].initialized = false;
        }
    }
}


void Datagram_Close (qsocket_t *sock)
{
    sfunc.Close_Socket (sock->socket);
}


void Datagram_Listen (cbool state)
{
    int i;

    for (i = 0; i < net_numlandrivers; i++)
    {
        if (net_landrivers[i].initialized)
            net_landrivers[i].Listen (state);
    }
}

// JPG 3.00 - this code appears multiple times, so factor it out
qsocket_t *Datagram_Reject (const char *message, sys_socket_t acceptsock, struct qsockaddr *addr)
{
        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREP_REJECT);
        MSG_WriteString(&net_message, message);
        *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write (acceptsock, net_message.data, net_message.cursize, addr);
        SZ_Clear(&net_message);

        return NULL;
}


static qsocket_t *_Datagram_CheckNewConnections (void)
{
    struct qsockaddr clientaddr;
    struct qsockaddr newaddr;
    sys_socket_t            newsock;
    sys_socket_t            acceptsock;
    qsocket_t   *sock;
    qsocket_t   *s;
    int         len;
    int         command;
    int         control;
    int         ret;

    acceptsock = dfunc.CheckNewConnections();
    if (acceptsock == INVALID_SOCKET)
        return NULL;

    SZ_Clear(&net_message);

    len = dfunc.Read (acceptsock, net_message.data, net_message.maxsize, &clientaddr);
    if (len < (int) sizeof(int))
        return NULL;
    net_message.cursize = len;

    MSG_BeginReading ();
    control = BigLong(*((int *)net_message.data));
    MSG_ReadLong();
    if (control == -1)
        return NULL;
    if ((control & (~NETFLAG_LENGTH_MASK)) !=  (int)NETFLAG_CTL)
        return NULL;
    if ((control & NETFLAG_LENGTH_MASK) != len)
        return NULL;

    command = MSG_ReadByte();
    if (command == CCREQ_SERVER_INFO)
    {
        if (strcmp(MSG_ReadString(), "QUAKE") != 0)
            return NULL;

        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREP_SERVER_INFO);
        dfunc.GetSocketAddr(acceptsock, &newaddr);
        MSG_WriteString(&net_message, dfunc.AddrToString(&newaddr));
        MSG_WriteString(&net_message, hostname.string);
        MSG_WriteString(&net_message, sv.name);
        MSG_WriteByte(&net_message, net_activeconnections);
        MSG_WriteByte(&net_message, svs.maxclients);
        MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
        *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr);
        SZ_Clear(&net_message);

        return NULL;
    }

    if (command == CCREQ_PLAYER_INFO)
    {
        int         playerNumber;
        int         activeNumber;
        int         clientNumber;
        client_t    *client;

        playerNumber = MSG_ReadByte();
        activeNumber = -1;

        for (clientNumber = 0, client = svs.clients; clientNumber < svs.maxclients; clientNumber++, client++)
        {
            if (client->active)
            {
                activeNumber++;
                if (activeNumber == playerNumber)
                    break;
            }
        }

        if (clientNumber == svs.maxclients)
            return NULL;

        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREP_PLAYER_INFO);
        MSG_WriteByte(&net_message, playerNumber);
        MSG_WriteString(&net_message, client->name);
        MSG_WriteLong(&net_message, client->colors);
        MSG_WriteLong(&net_message, (int)client->edict->v.frags);
        MSG_WriteLong(&net_message, (int)(net_time - client->netconnection->connecttime));

#if 0
        if (sv_ipmasking.value)
        {
            if (sscanf(client->netconnection->address, "%d.%d.%d", &a, &b, &c) == 3) // Baker 3.60 - engine side IP masking
            snprintf (address, sizeof(address), "%d.%d.%d.xxx", a, b, c);
            MSG_WriteString(&net_message, address);


        }
        else
#endif
        MSG_WriteString(&net_message, client->netconnection->address);

        *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr);
        SZ_Clear(&net_message);

        return NULL;
    }

    if (command == CCREQ_RULE_INFO)
    {
        const char  *prevCvarName;
        cvar_t  *var;

        // find the search start location
        prevCvarName = MSG_ReadString();
        var = Cvar_FindVarAfter (prevCvarName, CVAR_SERVERINFO);

        // send the response
        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREP_RULE_INFO);
        if (var)
        {
            MSG_WriteString(&net_message, var->name);
            MSG_WriteString(&net_message, var->string);
        }
        *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr);
        SZ_Clear(&net_message);

        return NULL;
    }

    if (command != CCREQ_CONNECT)
        return NULL;

    if (strcmp (MSG_ReadString(), "QUAKE") != 0)
        return NULL;

    if (MSG_ReadByte() != NET_PROTOCOL_VERSION)
        return Datagram_Reject("Incompatible version.\n", acceptsock, &clientaddr);

#ifdef BAN_TEST
    // check for a ban
    if (clientaddr.qsa_family == AF_INET)
    {
        in_addr_t   testAddr;
        testAddr = ((struct sockaddr_in *)&clientaddr)->sin_addr.s_addr;
        if ((testAddr & banMask.s_addr) == banAddr.s_addr)
            return Datagram_Reject("You have been banned.\n", acceptsock, &clientaddr);
    }
#endif

    // see if this guy is already connected
    for (s = net_activeSockets; s; s = s->next)
    {
        if (s->driver != net_driverlevel)
            continue;
        ret = dfunc.AddrCompare(&clientaddr, &s->addr);
        if (ret >= 0)
        {
            // is this a duplicate connection request?
            if (ret == 0 && net_time - s->connecttime < 2.0)
            {
                // yes, so send a duplicate reply
                SZ_Clear(&net_message);
                // save space for the header, filled in later
                MSG_WriteLong(&net_message, 0);
                MSG_WriteByte(&net_message, CCREP_ACCEPT);
                dfunc.GetSocketAddr(s->socket, &newaddr);
                MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr));
                *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
                dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr);
                SZ_Clear(&net_message);

                return NULL;
            }
            // it's somebody coming back in from a crash/disconnect
            // so close the old qsocket and let their retry get them back in
            //NET_Close(s);  // JPG - finally got rid of the worst mistake in Quake
            //return NULL;
        }
    }

    // allocate a QSocket
    sock = NET_NewQSocket ();
    if (sock == NULL)
        return Datagram_Reject("Server is full.\n", acceptsock, &clientaddr);

    // allocate a network socket
    newsock = dfunc.Open_Socket(0);
    if (newsock == INVALID_SOCKET)
    {
        NET_FreeQSocket(sock);
        return NULL;
    }

    // connect to the client
    if (dfunc.Connect (newsock, &clientaddr) == -1)
    {
        dfunc.Close_Socket(newsock);
        NET_FreeQSocket(sock);
        return NULL;
    }

    // everything is allocated, just fill in the details
    sock->socket = newsock;
    sock->landriver = net_landriverlevel;
    sock->addr = clientaddr;
    c_strlcpy (sock->address, dfunc.AddrToString(&clientaddr));

    // send him back the info about the server connection he has been allocated
    SZ_Clear(&net_message);
    // save space for the header, filled in later
    MSG_WriteLong(&net_message, 0);
    MSG_WriteByte(&net_message, CCREP_ACCEPT);
    dfunc.GetSocketAddr(newsock, &newaddr);
    MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr));
//  MSG_WriteString(&net_message, dfunc.AddrToString(&newaddr));
    *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
    dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr);
    SZ_Clear(&net_message);

    return sock;
}

qsocket_t *Datagram_CheckNewConnections (void)
{
    qsocket_t *ret = NULL;

    for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
    {
        if (net_landrivers[net_landriverlevel].initialized)
        {
            if ((ret = _Datagram_CheckNewConnections ()) != NULL)
                break;
        }
    }
    return ret;
}


static void _Datagram_SearchForHosts (cbool xmit)
{
    int     ret;
    int     n;
    int     i;
    struct qsockaddr readaddr;
    struct qsockaddr myaddr;
    int     control;

    dfunc.GetSocketAddr (dfunc.controlSock, &myaddr);
    if (xmit)
    {
        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREQ_SERVER_INFO);
        MSG_WriteString(&net_message, "QUAKE");
        MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
        *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Broadcast(dfunc.controlSock, net_message.data, net_message.cursize);
        SZ_Clear(&net_message);
    }

    while ((ret = dfunc.Read (dfunc.controlSock, net_message.data, net_message.maxsize, &readaddr)) > 0)
    {
        if (ret < (int) sizeof(int))
            continue;
        net_message.cursize = ret;

        // don't answer our own query
        if (dfunc.AddrCompare(&readaddr, &myaddr) >= 0)
            continue;

        // is the cache full?
        if (hostCacheCount == HOSTCACHESIZE)
            continue;

        MSG_BeginReading ();
        control = BigLong(*((int *)net_message.data));
        MSG_ReadLong();
        if (control == -1)
            continue;
        if ((control & (~NETFLAG_LENGTH_MASK)) !=  (int)NETFLAG_CTL)
            continue;
        if ((control & NETFLAG_LENGTH_MASK) != ret)
            continue;

        if (MSG_ReadByte() != CCREP_SERVER_INFO)
            continue;

        dfunc.GetAddrFromName(MSG_ReadString(), &readaddr);

        // search the cache for this server
        for (n = 0; n < hostCacheCount; n++)
        {
            if (dfunc.AddrCompare(&readaddr, &hostcache[n].addr) == 0)
                break;
        }

        // is it already there?
        if (n < hostCacheCount)
            continue;

        // add it
        hostCacheCount++;
        c_strlcpy (hostcache[n].name, MSG_ReadString());
        c_strlcpy (hostcache[n].map, MSG_ReadString());
        hostcache[n].users = MSG_ReadByte();
        hostcache[n].maxusers = MSG_ReadByte();
        if (MSG_ReadByte() != NET_PROTOCOL_VERSION)
        {
            c_strlcpy (hostcache[n].cname, hostcache[n].name);
            hostcache[n].cname[14] = 0;
            c_strlcpy (hostcache[n].name, "*");
            c_strlcat (hostcache[n].name, hostcache[n].cname);
        }
        memcpy (&hostcache[n].addr, &readaddr, sizeof(struct qsockaddr));
        hostcache[n].driver = net_driverlevel;
        hostcache[n].ldriver = net_landriverlevel;
        c_strlcpy (hostcache[n].cname, dfunc.AddrToString(&readaddr));

        // check for a name conflict
        for (i = 0; i < hostCacheCount; i++)
        {
            if (i == n)
                continue;
            if (strcasecmp (hostcache[n].name, hostcache[i].name) == 0)
            {
                i = strlen(hostcache[n].name);
                if (i < 15 && hostcache[n].name[i-1] > '8')
                {
                    hostcache[n].name[i] = '0';
                    hostcache[n].name[i+1] = 0;
                }
                else
                {
                    hostcache[n].name[i-1]++;
                }
                i = -1;
            }
        }
    }
}

void Datagram_SearchForHosts (cbool xmit)
{
    for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
    {
        if (hostCacheCount == HOSTCACHESIZE)
            break;
        if (net_landrivers[net_landriverlevel].initialized)
            _Datagram_SearchForHosts (xmit);
    }
}


static qsocket_t *_Datagram_Connect (const char *host)
{
    struct qsockaddr sendaddr;
    struct qsockaddr readaddr;
    qsocket_t   *sock;
    sys_socket_t            newsock;

    int         len; // ProQuake full NAT connect (2/6)
    int         ret;
    int         reps;
    double      start_time;
    int         control;
    const char  *reason;

    // see if we can resolve the host name
    if (dfunc.GetAddrFromName(host, &sendaddr) == -1)
    {
        Con_Printf("Could not resolve %s\n", host); // JPG 3.20 - added this
        return NULL;
    }

    newsock = dfunc.Open_Socket (0);
    if (newsock == INVALID_SOCKET)
        return NULL;

    sock = NET_NewQSocket ();
    if (sock == NULL)
        goto ErrorReturn2;

    sock->socket = newsock;
    sock->landriver = net_landriverlevel;

    // connect to the host
    if (dfunc.Connect (newsock, &sendaddr) == -1)
        goto ErrorReturn;

    // send the connection request
    Con_Printf("trying...\n");
    SCR_UpdateScreen ();
    start_time = net_time;

    for (reps = 0; reps < 3; reps++)
    {
        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREQ_CONNECT);
        MSG_WriteString(&net_message, "QUAKE");
        MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);

// ProQuake full NAT connect (3/6)
#if 1
#define MOD_PROQUAKE        0x01
#define PROQUAKE_SERIES_VERSION     5.50
        MSG_WriteByte(&net_message, MOD_PROQUAKE);          // JPG - added this
        MSG_WriteByte(&net_message, PROQUAKE_SERIES_VERSION * 10);  // JPG 3.00 - added this
        MSG_WriteByte(&net_message, 0);                     // JPG 3.00 - added this (flags)
        MSG_WriteLong(&net_message, 0);     // JPG 3.00 - password protected servers
#endif
        *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write (newsock, net_message.data, net_message.cursize, &sendaddr);
        SZ_Clear(&net_message);
        do
        {
            ret = dfunc.Read (newsock, net_message.data, net_message.maxsize, &readaddr);
            // if we got something, validate it
            if (ret > 0)
            {
                // is it from the right place?
                if (sfunc.AddrCompare(&readaddr, &sendaddr) != 0)
                {
                    Con_Printf("wrong reply address\n");
                    Con_Printf("Expected: %s | %s\n", dfunc.AddrToString (&sendaddr), StrAddr(&sendaddr));
                    Con_Printf("Received: %s | %s\n", dfunc.AddrToString (&readaddr), StrAddr(&readaddr));
                    SCR_UpdateScreen ();
                    ret = 0;
                    continue;
                }

                if (ret < (int) sizeof(int))
                {
                    ret = 0;
                    continue;
                }

                net_message.cursize = ret;
                MSG_BeginReading ();

                control = BigLong(*((int *)net_message.data));
                MSG_ReadLong();
                if (control == -1)
                {
                    ret = 0;
                    continue;
                }
                if ((control & (~NETFLAG_LENGTH_MASK)) !=  (int)NETFLAG_CTL)
                {
                    ret = 0;
                    continue;
                }
                if ((control & NETFLAG_LENGTH_MASK) != ret)
                {
                    ret = 0;
                    continue;
                }
            }
        }
        while (ret == 0 && (SetNetTime() - start_time) < 2.5);

        if (ret)
            break;

        Con_Printf("still trying...\n");
        SCR_UpdateScreen ();
        start_time = SetNetTime();
    }

    if (ret == 0)
    {
        reason = "No Response";
        Con_Printf("%s\n", reason);
        c_strlcpy (m_return_reason, reason);
        goto ErrorReturn;
    }

    if (ret == -1)
    {
        reason = "Network Error";
        Con_Printf("%s\n", reason);
        c_strlcpy (m_return_reason, reason);
        goto ErrorReturn;
    }
#if 1
    len = ret; // JPG - save length for ProQuake connections
#endif
    ret = MSG_ReadByte();
    if (ret == CCREP_REJECT)
    {
        reason = MSG_ReadString();
        Con_Printf("%s\n", reason);
        c_strlcpy (m_return_reason, reason);
        goto ErrorReturn;
    }

    if (ret == CCREP_ACCEPT)
    {
        memcpy (&sock->addr, &sendaddr, sizeof(struct qsockaddr));
        dfunc.SetSocketPort (&sock->addr, MSG_ReadLong());
#if 1
        Con_Printf ("Client port is %s\n", dfunc.AddrToString(&sock->addr));
        // Client has received CCREP_ACCEPT meaning client may connect
        // Now find out if this is a ProQuake server ...
        sock->proquake_connection = (len > 9 && MSG_ReadByte () == 1) ? 1 : 0;
#endif
    }
    else
    {
        reason = "Bad Response";
        Con_Printf("%s\n", reason);
        c_strlcpy (m_return_reason, reason);
        goto ErrorReturn;
    }

    dfunc.GetNameFromAddr (&sendaddr, sock->address, sizeof(sock->address) );

    Con_Printf ("Connection accepted\n");
    sock->lastMessageTime = SetNetTime();

    // switch the connection to the specified address
    if (dfunc.Connect (newsock, &sock->addr) == -1)
    {
        reason = "Connect to Game failed";
        Con_Printf("%s\n", reason);
        c_strlcpy (m_return_reason, reason);

        goto ErrorReturn;
    }

    m_return_onerror = false;
    return sock;

ErrorReturn:
    NET_FreeQSocket(sock);

ErrorReturn2:
    dfunc.Close_Socket(newsock);
    if (m_return_onerror)
    {
        Key_SetDest (key_menu); m_state = m_return_state; // Baker: A menu keydest needs to know menu item
        m_return_onerror = false;
    }

    return NULL;
}

qsocket_t *Datagram_Connect (const char *host)
{
    qsocket_t *ret = NULL;

    host = Strip_Port (host);
    for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
    {
        if (net_landrivers[net_landriverlevel].initialized)
        {
            if ((ret = _Datagram_Connect (host)) != NULL)
                break;
        }
    }
    return ret;
}
6B. sv_main.c - follows in following post due to limits on post size ....
Last edited by Baker on Sun Dec 14, 2014 12:52 am, edited 2 times in total.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Fixing FitzQuake 0.85 protocol 15 support

Post by Baker »

6B. sv_main.c -- last piece, changes marked the same as net_dgrm.c

Code: Select all

/*
Copyright (C) 1996-2001 Id Software, Inc.
Copyright (C) 2002-2009 John Fitzgibbons and others
Copyright (C) 2009-2014 Baker and others

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
...

*/
// sv_main.c -- server main program

#include "quakedef.h"

server_t        sv;
server_static_t svs;
cbool       isDedicated;

static char localmodels[MAX_FITZQUAKE_MODELS][8];           // inline model names for precache

int sv_protocol = PROTOCOL_FITZQUAKE; //johnfitz
#ifdef SUPPORTS_SERVER_PROTOCOL_15
int host_protocol_datagram_maxsize = MAX_MARK_V_DATAGRAM; // Baker
#endif // SUPPORTS_SERVER_PROTOCOL_15

extern cbool        pr_alpha_supported; //johnfitz

//============================================================================

/*
===============
SV_Protocol_f
===============
*/
void SV_Protocol_f (void)
{
    int i;

    switch (Cmd_Argc())
    {
    case 1:
        Con_Printf ("\"sv_protocol\" is \"%i\"\n", sv_protocol);
        break;
    case 2:
        i = atoi(Cmd_Argv(1));

        if (i != PROTOCOL_NETQUAKE && i != PROTOCOL_FITZQUAKE && i != PROTOCOL_FITZQUAKE_PLUS)
            Con_Printf ("sv_protocol must be %i, %i or %i\n", PROTOCOL_NETQUAKE, PROTOCOL_FITZQUAKE, PROTOCOL_FITZQUAKE_PLUS);
        else
        {
            sv_protocol = i;

            if (sv.active)
                Con_Printf ("changes will not take effect until the next map load.\n");
        }

        break;
    default:
        Con_SafePrintf ("usage: sv_protocol <protocol>\n");
        break;
    }
}



// SV_Cheats didn't notify that a level change or map restart was needed
void SV_Cheats_f (cvar_t *var)
{
    if (sv.active)
        Con_Printf ("sv_cheats change will take effect on map restart/change.\n");

}


/*
===============
SV_Init
===============
*/
void SV_Init (void)
{
    int     i;

    Cmd_AddCommands (SV_Init);

    for (i=0 ; i<MAX_FITZQUAKE_MODELS ; i++)
        c_snprintf (localmodels[i], "*%i", i);

    Con_SafePrintf ("Exe: "__TIME__" "__DATE__"\n");
    Con_SafePrintf ("%4.1f megabyte heap\n", host_parms.memsize / (1024 * 1024.0)); // Baker: Funny, not 2009 SI MB
}

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

EVENT MESSAGES

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

/*
==================
SV_StartParticle

Make sure the event gets sent to all clients
==================
*/
void SV_StartParticle (vec3_t org, vec3_t dir, int color, int count)
{
    int     i, v;

#ifdef SUPPORTS_SERVER_PROTOCOL_15
    if (sv.datagram.cursize > host_protocol_datagram_maxsize - 16) // Baker: 16 is size of data we would be adding in this function
#endif // SUPPORTS_SERVER_PROTOCOL_15
        return;

    MSG_WriteByte (&sv.datagram, svc_particle);
    MSG_WriteCoord (&sv.datagram, org[0]);
    MSG_WriteCoord (&sv.datagram, org[1]);
    MSG_WriteCoord (&sv.datagram, org[2]);
    for (i=0 ; i<3 ; i++)
    {
        v = dir[i]*16;

        if (v > 127)
            v = 127;
        else if (v < -128)
            v = -128;

        MSG_WriteChar (&sv.datagram, v);
    }

    MSG_WriteByte (&sv.datagram, count);
    MSG_WriteByte (&sv.datagram, color);
}

/*
==================
SV_StartSound

Each entity can have eight independant sound sources, like voice,
weapon, feet, etc.

Channel 0 is an auto-allocate channel, the others override anything
already running on that entity/channel pair.

An attenuation of 0 will play full volume everywhere in the level.
Larger attenuations will drop off.  (max 4 attenuation)

==================
*/
void SV_StartSound (edict_t *entity, int channel, const char *sample, int volume, float attenuation)
{
    int         sound_num, ent;
    int         i, field_mask;

    if (volume < 0 || volume > 255)
        Host_Error ("SV_StartSound: volume = %i", volume);

    if (attenuation < 0 || attenuation > 4)
        Host_Error ("SV_StartSound: attenuation = %f", attenuation);

    if (channel < 0 || channel > 7)
        Host_Error ("SV_StartSound: channel = %i", channel);

#ifdef SUPPORTS_SERVER_PROTOCOL_15
    if (sv.datagram.cursize > host_protocol_datagram_maxsize - 16)
#endif // SUPPORTS_SERVER_PROTOCOL_15
        return;

// find precache number for sound
    for (sound_num = 1 ; sound_num < MAX_FITZQUAKE_SOUNDS && sv.sound_precache[sound_num] ; sound_num++)
    {
        if (!strcmp(sample, sv.sound_precache[sound_num]))
            break;
    }

    if ( sound_num == MAX_FITZQUAKE_SOUNDS || !sv.sound_precache[sound_num] )
    {
        Con_Printf ("SV_StartSound: %s not precacheed\n", sample);
        return;
    }

    ent = NUM_FOR_EDICT(entity);

    field_mask = 0;

    if (volume != DEFAULT_SOUND_PACKET_VOLUME)
        field_mask |= SND_VOLUME;

    if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION)
        field_mask |= SND_ATTENUATION;

    //johnfitz -- PROTOCOL_FITZQUAKE
    if (ent >= 8192)
    {
        if (sv.protocol == PROTOCOL_NETQUAKE)
            return; //don't send any info protocol can't support
        else field_mask |= SND_LARGEENTITY;
    }

    if (sound_num >= 256 || channel >= 8)
    {
        if (sv.protocol == PROTOCOL_NETQUAKE)
            return; //don't send any info protocol can't support
        else field_mask |= SND_LARGESOUND;
    }

    //johnfitz

// directed messages go only to the entity the are targeted on
    MSG_WriteByte (&sv.datagram, svc_sound);
    MSG_WriteByte (&sv.datagram, field_mask);

    if (field_mask & SND_VOLUME) MSG_WriteByte (&sv.datagram, volume);
    if (field_mask & SND_ATTENUATION) MSG_WriteByte (&sv.datagram, attenuation*64);

    //johnfitz -- PROTOCOL_FITZQUAKE
    if (field_mask & SND_LARGEENTITY)
    {
        MSG_WriteShort (&sv.datagram, ent);
        MSG_WriteByte (&sv.datagram, channel);
    }
    else MSG_WriteShort (&sv.datagram, (ent<<3) | channel);

    if (field_mask & SND_LARGESOUND)
        MSG_WriteShort (&sv.datagram, sound_num);
    else MSG_WriteByte (&sv.datagram, sound_num);

    //johnfitz

    for (i=0 ; i<3 ; i++)
    {
        MSG_WriteCoord
        (
            &sv.datagram,
            entity->v.origin[i] + 0.5 * (entity->v.mins[i]+entity->v.maxs[i])
        );
    }
}


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

CLIENT SPAWNING

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

/*
================
SV_SendServerinfo

Sends the first message from the server to a connected client.
This will be sent on the initial connection and upon each server load.
================
*/

void SV_Hints_List_f (void)
{
    hint_type_t hint_num;
    if (sv.active)
    {
        Con_Printf ("Server hints:\n");
        for (hint_num = 0; hint_num < MAX_NUM_HINTS; hint_num ++)
        {
            const char *hintname = hintnames[hint_num].keystring;
            const char *hintvalue = sv.hintstrings[hint_num];

            Con_Printf ("%-10s: %s\n", hintname, hintvalue);
        }
    } else Con_Printf ("no active server\n");
}

static void SV_HintStrings_NewMap (void)
{
    const char *gamedir_name = File_URL_SkipPath (com_gamedir);
    const char *hud_style_name = Gamedir_TypeName ();

    hint_type_t hint_num;

    for (hint_num = 0; hint_num < MAX_NUM_HINTS; hint_num ++)
    {
        char *sv_make_hint_string = sv.hintstrings[hint_num];
        switch (hint_num)
        {
        case hint_game:
            c_snprintfc (sv_make_hint_string, MAX_HINT_BUF_64, "%s %s", gamedir_name, hud_style_name);
            break;
        case hint_skill:
//          if (!pr_global_struct->deathmatch) // Don't bother with a deathmatch skill hint
                c_snprintfc (sv_make_hint_string, MAX_HINT_BUF_64, "%d", sv.current_skill );
            break;
        default:
            Host_Error ("SV_HintStrings: Unknown hint_num %d", hint_num);
        }
    }

}

// If early, send the game only.
// If late, send everything.  Even the game again.  Client clears that on new map.
// Which occurs real early.
static void SV_InsertHints (client_t *client, cbool early)
{
    const char *sv_hint_string;
    int hstart = 0, hlast_plus_one = MAX_NUM_HINTS;

    hint_type_t hint_num;
    Con_DPrintf ("Send hints ... ones = %i\n", early);

    // Must send gamedir change very early.
    // Other hints must occur AFTER
    if (early)
        hstart = hint_game /* 0 */, hlast_plus_one = 1;
    else hstart = 0 /* 1 */, hlast_plus_one = MAX_NUM_HINTS;

    for (hint_num = hstart; hint_num < hlast_plus_one; hint_num ++)
    {
        if (sv.hintstrings[hint_num][0] == 0)
            continue; // omitted

        // Issue a commented out svc_stufftext like "//hint game warp -quoth"
        sv_hint_string = va("%s%s %s\n", HINT_MESSAGE_PREIX, hintnames[hint_num].keystring, &sv.hintstrings[hint_num][0]);
        Con_DPrintf ("Sending: %s", sv_hint_string); // No newline, hint_string already has one
        MSG_WriteByte (&client->message, svc_stufftext);
        MSG_WriteString (&client->message, sv_hint_string);
    }
}



void SV_SendServerinfo (client_t *client)
{
    const char      **s;
    char            message[2048];
    int             i; //johnfitz

    MSG_WriteByte (&client->message, svc_print);
    c_snprintf4 (message, "%c\n%s %1.2f SERVER (%i CRC)\n", 2, ENGINE_NAME, ENGINE_VERSION, pr_crc); //johnfitz -- include fitzquake version
    MSG_WriteString (&client->message, message);

    SV_InsertHints (client, true /* early hints */); // Baker -- throw some extra hints to client here.

    MSG_WriteByte (&client->message, svc_serverinfo);
    MSG_WriteLong (&client->message, sv.protocol); //johnfitz -- sv.protocol instead of PROTOCOL_VERSION
    MSG_WriteByte (&client->message, svs.maxclients);


/* // #pragma message ("Baker: Shouldn't coop and deathmatch use global struct???")
    if (!pr_coop.value && pr_deathmatch.value)
        MSG_WriteByte (&client->message, GAME_DEATHMATCH);
    else MSG_WriteByte (&client->message, GAME_COOP);
*/
    
    // Baker: modification, indicate the scoreboard type using flag set at map spawn
    if (!pr_global_struct->coop && pr_global_struct->deathmatch)
        MSG_WriteByte (&client->message, GAME_DEATHMATCH);
    else MSG_WriteByte (&client->message, GAME_COOP);

    c_strlcpy (message, PR_GetString(sv.edicts->v.message));

    MSG_WriteString (&client->message, message);

    //johnfitz -- only send the first 256 model and sound precaches if protocol is 15
    for (i=0,s = sv.model_precache+1 ; *s; s++,i++)
        if (sv.protocol != PROTOCOL_NETQUAKE || i < 256)
            MSG_WriteString (&client->message, *s);

    MSG_WriteByte (&client->message, 0);

    for (i=0,s = sv.sound_precache+1 ; *s ; s++,i++)
        if (sv.protocol != PROTOCOL_NETQUAKE || i < 256)
            MSG_WriteString (&client->message, *s);

    MSG_WriteByte (&client->message, 0);
    //johnfitz

// send music
    MSG_WriteByte (&client->message, svc_cdtrack);
    MSG_WriteByte (&client->message, sv.edicts->v.sounds);
    MSG_WriteByte (&client->message, sv.edicts->v.sounds);

// set view
    MSG_WriteByte (&client->message, svc_setview);
    MSG_WriteShort (&client->message, NUM_FOR_EDICT(client->edict));

// send late hints
    Con_DPrintf ("Ready to send late hints\n");
    SV_InsertHints (client, false /* late hints */); // Baker -- throw some extra hints to client here.


    MSG_WriteByte (&client->message, svc_signonnum);
    MSG_WriteByte (&client->message, 1);

    client->sendsignon = true;
    client->spawned = false;        // need prespawn, spawn, etc
}

/*
================
SV_ConnectClient

Initializes a client_t for a new net connection.  This will only be called
once for a player each game, not once for each level change.
================
*/
void SV_ConnectClient (int clientnum)
{
    edict_t         *ent;
    client_t        *client;
    int             edictnum;
    struct qsocket_s *netconnection;
    int             i;
    float           spawn_parms[NUM_SPAWN_PARMS];

    client = svs.clients + clientnum;

    Con_DPrintf ("Client %s connected\n", NET_QSocketGetAddressString(client->netconnection));

    edictnum = clientnum+1;

    ent = EDICT_NUM(edictnum);

// set up the client_t
    netconnection = client->netconnection;

    if (sv.loadgame)
        memcpy (spawn_parms, client->spawn_parms, sizeof(spawn_parms));

    memset (client, 0, sizeof(*client));
    client->netconnection = netconnection;

    c_strlcpy (client->name, "unconnected");
    client->active = true;
    client->spawned = false;
    client->edict = ent;
    client->message.data = client->msgbuf;
    client->message.maxsize = sizeof(client->msgbuf);
    client->message.allowoverflow = true;       // we can catch it
    client->privileged = false;

    if (sv.loadgame)
    {
        memcpy (client->spawn_parms, spawn_parms, sizeof(spawn_parms));
    }
    else
    {
        // call the progs to get default spawn parms for the new client
        PR_ExecuteProgram (pr_global_struct->SetNewParms);

        for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
            client->spawn_parms[i] = (&pr_global_struct->parm1)[i];
    }

    SV_SendServerinfo (client);
}


/*
===================
SV_CheckForNewClients

===================
*/
void SV_CheckForNewClients (void)
{
    struct qsocket_s    *ret;
    int             i;

// check for new connections
    while (1)
    {
        ret = NET_CheckNewConnections ();

        if (!ret)
            break;

    // init a new client structure
        for (i=0 ; i<svs.maxclients ; i++)
            if (!svs.clients[i].active)
                break;

        if (i == svs.maxclients)
            Host_Error ("SV_CheckForNewClients: no free clients");

        svs.clients[i].netconnection = ret;
        SV_ConnectClient (i);

        net_activeconnections++;
    }
}



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

FRAME UPDATES

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

/*
==================
SV_ClearDatagram

==================
*/
void SV_ClearDatagram (void)
{
    SZ_Clear (&sv.datagram);
}

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

The PVS must include a small area around the client to allow head bobbing
or other small motion on the client side.  Otherwise, a bob might cause an
entity that should be visible to not show up, especially when the bob
crosses a waterline.

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

static int  fatbytes;
static byte fatpvs[MAX_MAP_LEAFS/8];

void SV_AddToFatPVS (vec3_t org, mnode_t *node, qmodel_t *worldmodel) //johnfitz -- added worldmodel as a parameter
{
    int     i;
    byte    *pvs;
    mplane_t    *plane;
    float   d;

    while (1)
    {
    // if this is a leaf, accumulate the pvs bits
        if (node->contents < 0)
        {
            if (node->contents != CONTENTS_SOLID)
            {
                pvs = Mod_LeafPVS ( (mleaf_t *)node, worldmodel); //johnfitz -- worldmodel as a parameter

                for (i=0 ; i<fatbytes ; i++)
                    fatpvs[i] |= pvs[i];
            }

            return;
        }

        plane = node->plane;
        d = DotProduct (org, plane->normal) - plane->dist;

        if (d > 8)
            node = node->children[0];
        else if (d < -8)
            node = node->children[1];
        else
        {
            // go down both
            SV_AddToFatPVS (org, node->children[0], worldmodel); //johnfitz -- worldmodel as a parameter
            node = node->children[1];
        }
    }
}

/*
=============
SV_FatPVS

Calculates a PVS that is the inclusive or of all leafs within 8 pixels of the
given point.
=============
*/
byte *SV_FatPVS (vec3_t org, qmodel_t *worldmodel) //johnfitz -- added worldmodel as a parameter
{
    fatbytes = (worldmodel->numleafs+31)>>3;
    memset (fatpvs, 0, fatbytes);
    SV_AddToFatPVS (org, worldmodel->nodes, worldmodel); //johnfitz -- worldmodel as a parameter
    return fatpvs;
}


cbool Q1BSP_Trace (qmodel_t *model, int forcehullnum, int frame, const vec3_t start, const vec3_t end, const vec3_t mins, const vec3_t maxs, trace_t *trace)
{
    hull_t *hull;
    vec3_t size;
    vec3_t start_l, end_l;
    vec3_t offset;

    memset (trace, 0, sizeof(trace_t));
    trace->fraction = 1;
    trace->allsolid = true;

    VectorSubtract (maxs, mins, size);
    if (forcehullnum >= 1 && forcehullnum <= MAX_MAP_HULLS && model->hulls[forcehullnum-1].available)
        hull = &model->hulls[forcehullnum-1];
    else
    {
        if (size[0] < 3 || !model->hulls[1].available)
            hull = &model->hulls[0];
        else if (size[0] <= 32)
        {
            if (size[2] < 54 && model->hulls[3].available)
                hull = &model->hulls[3]; // 32x32x36 (half-life's crouch)
            else
                hull = &model->hulls[1];
        }
        else
            hull = &model->hulls[2];
    }

// calculate an offset value to center the origin
    VectorSubtract (hull->clip_mins, mins, offset);
    VectorSubtract(start, offset, start_l);
    VectorSubtract(end, offset, end_l);
    SV_RecursiveHullCheck(hull, hull->firstclipnode, 0, 1, start_l, end_l, trace);
    if (trace->fraction == 1)
    {
        VectorCopy (end, trace->endpos);
    }
    else
    {
        VectorAdd (trace->endpos, offset, trace->endpos);
    }

    return trace->fraction != 1;
}


cbool SV_InvisibleToClient(edict_t *viewer, edict_t *seen)
{
    int i;
    trace_t tr;
    vec3_t start;
    vec3_t end;

//  if (seen->v->solid == SOLID_BSP)
//      return false;   //bsp ents are never culled this way

    //stage 1: check against their origin
    VectorAdd(viewer->v.origin, viewer->v.view_ofs, start);
    tr.fraction = 1;

    if (!Q1BSP_Trace (sv.worldmodel, 1, 0, start, seen->v.origin, vec3_origin, vec3_origin, &tr))
        return false;   //wasn't blocked


    //stage 2: check against their bbox
    for (i = 0; i < 8; i++)
    {
        end[0] = seen->v.origin[0] + ((i&1)? seen->v.mins[0]: seen->v.maxs[0]);
        end[1] = seen->v.origin[1] + ((i&2)? seen->v.mins[1]: seen->v.maxs[1]);
        end[2] = seen->v.origin[2] + ((i&4)? seen->v.mins[2]+0.1: seen->v.maxs[2]);

        tr.fraction = 1;
        if (!Q1BSP_Trace (sv.worldmodel, 1, 0, start, end, vec3_origin, vec3_origin, &tr))
            return false;   //this trace went through, so don't cull
    }

    return true;
}


//=============================================================================

/*
=============
SV_WriteEntitiesToClient

=============
*/
void SV_WriteEntitiesToClient (edict_t  *clent, sizebuf_t *msg)
{
    int     e, i;
    int     bits;
    byte    *pvs;
    vec3_t  org;
    float   miss;
    edict_t *ent;


// find the client's PVS
    VectorAdd (clent->v.origin, clent->v.view_ofs, org);
    pvs = SV_FatPVS (org, sv.worldmodel);

// send over all entities (excpet the client) that touch the pvs
    ent = NEXT_EDICT(sv.edicts);
    for (e = 1 ; e < sv.num_edicts ; e++, ent = NEXT_EDICT(ent))
    {
// ignore if not touching a PV leaf
        if (ent != clent)   // clent is ALWAYS sent
        {
            // ignore ents without visible models
            if (!ent->v.modelindex || !(const char*)(PR_GetString(ent->v.model))[0] ) // Baker: Future question, why did I const char *that
                continue;

#pragma message ("Baker: Above statement might want to convey ents without models if they have effects, but do it with a different protocol")

            //johnfitz -- don't send model>255 entities if protocol is 15
            if (sv.protocol == PROTOCOL_NETQUAKE && (int)ent->v.modelindex & 0xFF00)
                continue;

            if (!sv_novis.value)
            {
                // Baker: Trivial accept from Spike and MH
                if (ent->num_leafs <= MAX_MARK_V_ENT_LEAFS)
                {
                    // ignore if not touching a PV leaf
                    for (i=0 ; i < ent->num_leafs ; i++)
                        if (pvs[ent->leafnums[i] >> 3] & (1 << (ent->leafnums[i]&7) ))
                            break;
                    if (i == ent->num_leafs)
                        continue;       // not visible
                } // else Con_Printf ("Trivial accept %i\n", e); // Else Trivial accept
            }

            if (sv_cullentities.value) // 1 = players.  2 = everything.
                if (e <= svs.maxclients || sv_cullentities.value > 1 ) // A player or we are checking everything
                    if (ent->v.movetype != MOVETYPE_PUSH && SV_InvisibleToClient(clent, ent)) // Baker: R00k don't cull doors, lifts, etc. which are MOVETYPE_PUSH
                        continue; // Entity cannot be seen, don't send it
        }

        //johnfitz -- max size for protocol 15 is 18 bytes, not 16 as originally
        //assumed here.  And, for protocol 85 the max size is actually 24 bytes.
        if (msg->cursize + 24 > msg->maxsize)
        {
            //johnfitz -- less spammy overflow message
            if (!dev_overflows.packetsize || dev_overflows.packetsize + CONSOLE_RESPAM_TIME < realtime )
            {
                Con_Printf ("Packet overflow!\n");
                dev_overflows.packetsize = realtime;
            }

            goto stats;
            //johnfitz
        }

// send an update
        bits = 0;

        for (i=0 ; i<3 ; i++)
        {
            miss = ent->v.origin[i] - ent->baseline.origin[i];
            if ( miss < -0.1 || miss > 0.1 )
                bits |= U_ORIGIN1<<i;
        }

        if ( ent->v.angles[0] != ent->baseline.angles[0] ) bits |= U_ANGLE1;
        if ( ent->v.angles[1] != ent->baseline.angles[1] ) bits |= U_ANGLE2;
        if ( ent->v.angles[2] != ent->baseline.angles[2] ) bits |= U_ANGLE3;

        if (ent->v.movetype == MOVETYPE_STEP) bits |= U_STEP;   // don't mess up the step animation
        if (ent->baseline.colormap != ent->v.colormap) bits |= U_COLORMAP;
        if (ent->baseline.skin != ent->v.skin) bits |= U_SKIN;
        if (ent->baseline.frame != ent->v.frame) bits |= U_FRAME;
        if (ent->baseline.effects != ent->v.effects) bits |= U_EFFECTS;
        if (ent->baseline.modelindex != ent->v.modelindex) bits |= U_MODEL;

        //johnfitz -- alpha
        if (pr_alpha_supported)
        {
            // TODO: find a cleaner place to put this code
            eval_t  *val;
            val = GETEDICTFIELDVALUE(ent, eval_alpha);
            if (val)
                ent->alpha = ENTALPHA_ENCODE(val->_float);
        }

        //don't send invisible entities unless they have effects
        if (ent->alpha == ENTALPHA_ZERO && !ent->v.effects)
        {
            continue;
        }

        //johnfitz -- PROTOCOL_FITZQUAKE
        if (sv.protocol != PROTOCOL_NETQUAKE)
        {
            if (ent->baseline.alpha != ent->alpha) bits |= U_ALPHA;
            if (bits & U_FRAME && (int)ent->v.frame & 0xFF00) bits |= U_FRAME2;
            if (bits & U_MODEL && (int)ent->v.modelindex & 0xFF00) bits |= U_MODEL2;
            if (ent->sendinterval) bits |= U_LERPFINISH;
            if (bits >= 65536) bits |= U_EXTEND1;
            if (bits >= 16777216) bits |= U_EXTEND2;
        }
        //johnfitz

        if (e >= 256) bits |= U_LONGENTITY;
        if (bits >= 256) bits |= U_MOREBITS;

        // write the message
        MSG_WriteByte (msg, bits | U_SIGNAL);

        if (bits & U_MOREBITS) MSG_WriteByte (msg, bits>>8);

        //johnfitz -- PROTOCOL_FITZQUAKE
        if (bits & U_EXTEND1) MSG_WriteByte(msg, bits>>16);
        if (bits & U_EXTEND2) MSG_WriteByte(msg, bits>>24);
        //johnfitz

        if (bits & U_LONGENTITY)
            MSG_WriteShort (msg,e);
        else MSG_WriteByte (msg,e);

        if (bits & U_MODEL) MSG_WriteByte (msg, ent->v.modelindex);
        if (bits & U_FRAME) MSG_WriteByte (msg, ent->v.frame);
        if (bits & U_COLORMAP) MSG_WriteByte (msg, ent->v.colormap);
        if (bits & U_SKIN) MSG_WriteByte (msg, ent->v.skin);
        if (bits & U_EFFECTS) MSG_WriteByte (msg, ent->v.effects);
        if (bits & U_ORIGIN1) MSG_WriteCoord (msg, ent->v.origin[0]);
        if (bits & U_ANGLE1)
        {
            if (sv.protocol == PROTOCOL_FITZQUAKE_PLUS)
                MSG_WriteAngle16(msg, ent->v.angles[0]);
            else
                MSG_WriteAngle(msg, ent->v.angles[0]);
        }
        if (bits & U_ORIGIN2) MSG_WriteCoord (msg, ent->v.origin[1]);
        if (bits & U_ANGLE2)
        {
            if (sv.protocol == PROTOCOL_FITZQUAKE_PLUS)
                MSG_WriteAngle16(msg, ent->v.angles[1]);
            else
                MSG_WriteAngle(msg, ent->v.angles[1]);
        }
        if (bits & U_ORIGIN3) MSG_WriteCoord (msg, ent->v.origin[2]);
        if (bits & U_ANGLE3)
        {
            if (sv.protocol == PROTOCOL_FITZQUAKE_PLUS)
                MSG_WriteAngle16(msg, ent->v.angles[2]);
            else
                MSG_WriteAngle(msg, ent->v.angles[2]);
        }

        //johnfitz -- PROTOCOL_FITZQUAKE
        if (bits & U_ALPHA) MSG_WriteByte(msg, ent->alpha);
        if (bits & U_FRAME2) MSG_WriteByte(msg, (int)ent->v.frame >> 8);
        if (bits & U_MODEL2) MSG_WriteByte(msg, (int)ent->v.modelindex >> 8);
        if (bits & U_LERPFINISH) MSG_WriteByte(msg, (byte)(c_rint((ent->v.nextthink - sv.time) * 255)));
        //johnfitz
    }

    //johnfitz -- devstats
stats:
    if (msg->cursize > MAX_WINQUAKE_DATAGRAM /*1024*/ && dev_peakstats.packetsize <= MAX_WINQUAKE_DATAGRAM /*1024*/)
        Con_Warning ("%i byte packet exceeds standard limit of %d.\n", msg->cursize, MAX_WINQUAKE_DATAGRAM /*1024*/);
    dev_stats.packetsize = msg->cursize;
    dev_peakstats.packetsize = c_max(msg->cursize, dev_peakstats.packetsize);
    //johnfitz
}

/*
=============
SV_CleanupEnts

=============
*/
void SV_CleanupEnts (void)
{
    int     e;
    edict_t *ent;

    ent = NEXT_EDICT(sv.edicts);

    for (e = 1 ; e < sv.num_edicts ; e++, ent = NEXT_EDICT(ent))
    {
        ent->v.effects = (int)ent->v.effects & ~EF_MUZZLEFLASH;
    }

}

/*
==================
SV_WriteClientdataToMessage

==================
*/
void SV_WriteClientdataToMessage (edict_t *ent, sizebuf_t *msg)
{
    int     bits;
    int     i;
    edict_t *other;
    int     items;
    eval_t  *val;

// send a damage message
    if (ent->v.dmg_take || ent->v.dmg_save)
    {
        other = PROG_TO_EDICT(ent->v.dmg_inflictor);
        MSG_WriteByte (msg, svc_damage);
        MSG_WriteByte (msg, ent->v.dmg_save);
        MSG_WriteByte (msg, ent->v.dmg_take);
        for (i=0 ; i<3 ; i++)
            MSG_WriteCoord (msg, other->v.origin[i] + 0.5 * (other->v.mins[i] + other->v.maxs[i]));

        ent->v.dmg_take = 0;
        ent->v.dmg_save = 0;
    }

// send the current viewpos offset from the view entity
    SV_SetIdealPitch ();        // how much to look up / down ideally

// a fixangle might get lost in a dropped packet.  Oh well.
    if ( ent->v.fixangle )
    {
        MSG_WriteByte (msg, svc_setangle);

        MSG_WriteAngle (msg, ent->v.angles[0] );
        MSG_WriteAngle (msg, ent->v.angles[1] );
        MSG_WriteAngle (msg, ent->v.angles[2] );

        ent->v.fixangle = 0;
    }

    bits = 0;

    if (ent->v.view_ofs[2] != DEFAULT_VIEWHEIGHT) bits |= SU_VIEWHEIGHT;
    if (ent->v.idealpitch) bits |= SU_IDEALPITCH;

// stuff the sigil bits into the high bits of items for sbar, or else
// mix in items2
    val = GETEDICTFIELDVALUE(ent, eval_items2);

    if (val)
        items = (int)ent->v.items | ((int)val->_float << 23);
    else items = (int)ent->v.items | ((int)pr_global_struct->serverflags << 28);

    bits |= SU_ITEMS;

    if ( (int)ent->v.flags & FL_ONGROUND)
        bits |= SU_ONGROUND;

    if ( ent->v.waterlevel >= 2)
        bits |= SU_INWATER;

    for (i=0 ; i<3 ; i++)
    {
        if (ent->v.punchangle[i]) bits |= (SU_PUNCH1<<i);
        if (ent->v.velocity[i]) bits |= (SU_VELOCITY1<<i);
    }

    if (ent->v.weaponframe) bits |= SU_WEAPONFRAME;
    if (ent->v.armorvalue) bits |= SU_ARMOR;

//  if (ent->v.weapon)
        bits |= SU_WEAPON;

    //johnfitz -- PROTOCOL_FITZQUAKE
    if (sv.protocol != PROTOCOL_NETQUAKE)
    {
        if (bits & SU_WEAPON && SV_ModelIndex(PR_GetString(ent->v.weaponmodel)) & 0xFF00) bits |= SU_WEAPON2;
        if ((int)ent->v.armorvalue & 0xFF00) bits |= SU_ARMOR2;
        if ((int)ent->v.currentammo & 0xFF00) bits |= SU_AMMO2;
        if ((int)ent->v.ammo_shells & 0xFF00) bits |= SU_SHELLS2;
        if ((int)ent->v.ammo_nails & 0xFF00) bits |= SU_NAILS2;
        if ((int)ent->v.ammo_rockets & 0xFF00) bits |= SU_ROCKETS2;
        if ((int)ent->v.ammo_cells & 0xFF00) bits |= SU_CELLS2;
        if (bits & SU_WEAPONFRAME && (int)ent->v.weaponframe & 0xFF00) bits |= SU_WEAPONFRAME2;
        if (bits & SU_WEAPON && ent->alpha != ENTALPHA_DEFAULT) bits |= SU_WEAPONALPHA; //for now, weaponalpha = client entity alpha
        if (bits >= 65536) bits |= SU_EXTEND1;
        if (bits >= 16777216) bits |= SU_EXTEND2;
    }

    //johnfitz

// send the data

    MSG_WriteByte (msg, svc_clientdata);
    MSG_WriteShort (msg, bits);

    //johnfitz -- PROTOCOL_FITZQUAKE
    if (bits & SU_EXTEND1) MSG_WriteByte(msg, bits>>16);
    if (bits & SU_EXTEND2) MSG_WriteByte(msg, bits>>24);

    //johnfitz

    if (bits & SU_VIEWHEIGHT) MSG_WriteChar (msg, ent->v.view_ofs[2]);
    if (bits & SU_IDEALPITCH) MSG_WriteChar (msg, ent->v.idealpitch);

    for (i=0 ; i<3 ; i++)
    {
        if (bits & (SU_PUNCH1<<i)) MSG_WriteChar (msg, ent->v.punchangle[i]);
        if (bits & (SU_VELOCITY1<<i)) MSG_WriteChar (msg, ent->v.velocity[i]/16);
    }

// [always sent]    if (bits & SU_ITEMS)
    MSG_WriteLong (msg, items);

    if (bits & SU_WEAPONFRAME) MSG_WriteByte (msg, ent->v.weaponframe);
    if (bits & SU_ARMOR) MSG_WriteByte (msg, ent->v.armorvalue);
    if (bits & SU_WEAPON) MSG_WriteByte (msg, SV_ModelIndex(PR_GetString(ent->v.weaponmodel)));

    MSG_WriteShort (msg, ent->v.health);
    MSG_WriteByte (msg, ent->v.currentammo);
    MSG_WriteByte (msg, ent->v.ammo_shells);
    MSG_WriteByte (msg, ent->v.ammo_nails);
    MSG_WriteByte (msg, ent->v.ammo_rockets);
    MSG_WriteByte (msg, ent->v.ammo_cells);

    // Baker: If hipnotic or rogue, written message varies (hipnotic, rogue)
    if (standard_quake)
    {
        MSG_WriteByte (msg, ent->v.weapon);
    }
    else
    {
        for(i=0;i<32;i++)
        {
            if ( ((int)ent->v.weapon) & (1<<i) )
            {
                MSG_WriteByte (msg, i);
                break;
            }
        }
    }

    //johnfitz -- PROTOCOL_FITZQUAKE
    if (bits & SU_WEAPON2) MSG_WriteByte (msg, SV_ModelIndex(PR_GetString(ent->v.weaponmodel)) >> 8);
    if (bits & SU_ARMOR2) MSG_WriteByte (msg, (int)ent->v.armorvalue >> 8);
    if (bits & SU_AMMO2) MSG_WriteByte (msg, (int)ent->v.currentammo >> 8);
    if (bits & SU_SHELLS2) MSG_WriteByte (msg, (int)ent->v.ammo_shells >> 8);
    if (bits & SU_NAILS2) MSG_WriteByte (msg, (int)ent->v.ammo_nails >> 8);
    if (bits & SU_ROCKETS2) MSG_WriteByte (msg, (int)ent->v.ammo_rockets >> 8);
    if (bits & SU_CELLS2) MSG_WriteByte (msg, (int)ent->v.ammo_cells >> 8);
    if (bits & SU_WEAPONFRAME2) MSG_WriteByte (msg, (int)ent->v.weaponframe >> 8);
    if (bits & SU_WEAPONALPHA) MSG_WriteByte (msg, ent->alpha); //for now, weaponalpha = client entity alpha
    //johnfitz
}

/*
=======================
SV_SendClientDatagram
=======================
*/
cbool SV_SendClientDatagram (client_t *client)
{
    byte        buf[MAX_MARK_V_DATAGRAM];
    sizebuf_t   msg;

    msg.data = buf;
#ifdef SUPPORTS_SERVER_PROTOCOL_15
    msg.maxsize = host_protocol_datagram_maxsize;
#endif // SUPPORTS_SERVER_PROTOCOL_15
    msg.cursize = 0;

    //johnfitz

    MSG_WriteByte (&msg, svc_time);
    MSG_WriteFloat (&msg, sv.time);

// add the client specific data to the datagram
    SV_WriteClientdataToMessage (client->edict, &msg);

    SV_WriteEntitiesToClient (client->edict, &msg);

// copy the server datagram if there is space
    if (msg.cursize + sv.datagram.cursize < msg.maxsize)
        SZ_Write (&msg, sv.datagram.data, sv.datagram.cursize);

// send the datagram
    if (NET_SendUnreliableMessage (client->netconnection, &msg) == -1)
    {
        SV_DropClient (true);// if the message couldn't send, kick off
        return false;
    }

    return true;
}

/*
=======================
SV_UpdateToReliableMessages
=======================
*/
void SV_UpdateToReliableMessages (void)
{
    int         i, j;
    client_t *client;

// check for changes to be sent over the reliable streams
    for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
    {
        if (host_client->old_frags != host_client->edict->v.frags)
        {
            for (j=0, client = svs.clients ; j<svs.maxclients ; j++, client++)
            {
                if (!client->active)
                    continue;

                MSG_WriteByte (&client->message, svc_updatefrags);
                MSG_WriteByte (&client->message, i);
                MSG_WriteShort (&client->message, host_client->edict->v.frags);
            }

            host_client->old_frags = host_client->edict->v.frags;
        }
    }

    for (j=0, client = svs.clients ; j<svs.maxclients ; j++, client++)
    {
        if (!client->active)
            continue;

        SZ_Write (&client->message, sv.reliable_datagram.data, sv.reliable_datagram.cursize);
    }

    SZ_Clear (&sv.reliable_datagram);
}


/*
=======================
SV_SendNop

Send a nop message without trashing or sending the accumulated client
message buffer
=======================
*/
void SV_SendNop (client_t *client)
{
    sizebuf_t   msg;
    byte        buf[4];

    msg.data = buf;
    msg.maxsize = sizeof(buf);
    msg.cursize = 0;

    MSG_WriteChar (&msg, svc_nop);

    if (NET_SendUnreliableMessage (client->netconnection, &msg) == -1)
        SV_DropClient (true);   // if the message couldn't send, kick off

    client->last_message = realtime;
}

/*
=======================
SV_SendClientMessages
=======================
*/
void SV_SendClientMessages (void)
{
    int         i;

// update frags, names, etc
    SV_UpdateToReliableMessages ();

// build individual updates
    for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
    {
        if (!host_client->active)
            continue;

        if (host_client->spawned)
        {
            if (!SV_SendClientDatagram (host_client))
                continue;
        }
        else
        {
        // the player isn't totally in the game yet
        // send small keepalive messages if too much time has passed
        // send a full message when the next signon stage has been requested
        // some other message data (name changes, etc) may accumulate
        // between signon stages
            if (!host_client->sendsignon)
            {
                if (realtime - host_client->last_message > 5)
                    SV_SendNop (host_client);

                continue;   // don't send out non-signon messages
            }
        }

        // check for an overflowed message.  Should only happen
        // on a very fucked up connection that backs up a lot, then
        // changes level
        if (host_client->message.overflowed)
        {
            SV_DropClient (true);
            host_client->message.overflowed = false;
            continue;
        }

        if (host_client->message.cursize || host_client->dropasap)
        {
            if (!NET_CanSendMessage (host_client->netconnection))
            {
//              I_Printf ("can't write\n");
                continue;
            }

            if (host_client->dropasap)
            {
                SV_DropClient (false);  // went to another level
            }
            else
            {
                if (NET_SendMessage (host_client->netconnection, &host_client->message) == -1)
                    SV_DropClient (true);   // if the message couldn't send, kick off

                SZ_Clear (&host_client->message);
                host_client->last_message = realtime;
                host_client->sendsignon = false;
            }
        }
    }

// clear muzzle flashes
    SV_CleanupEnts ();
}


void SV_Autosave_Think (void)
{
    if (!sv_autosave.value)
        return;

    if (isDedicated)
        return;

    if (!sv.active)
        return;

    // Don't rush to auto-save on starting a map.
    if (sv.time < sv.auto_save_time)
        return;

    // Determine if auto-save should occur
    if (svs.maxclients > 1)
        return;

    // Yuck, client variable.
    if (cls.state != ca_connected)
        return;

    // Baker: Yuck!  A client variable, but the server doesn't seem to know.
    // And I'm not messing around trying to find out what bytes a progs is sending.
    // Besides, right now we can't load multiplayer save games yet.
    if (cl.intermission)
        return;

    // Dead
    if (svs.clients[0].edict->v.health <= 0)
        return;

    // If in a cheat-mode, why bother.
    if (((int)svs.clients[0].edict->v.flags & FL_GODMODE) ||
        ((int)svs.clients[0].edict->v.flags & FL_NOTARGET) ||
        svs.clients[0].edict->v.movetype == MOVETYPE_NOCLIP || sv.frozen)
    {
//      Con_Printf ("Didn't autosave because of a cheat\n");
        return;
    }

    // Rename old ones
    {
        char autosave2[MAX_OSPATH];
        char autosave1[MAX_OSPATH];
        char autosave0[MAX_OSPATH];
        int checkit;

        c_snprintf (autosave2, "%s/a2.sav", com_gamedir);
        c_snprintf (autosave1, "%s/a1.sav", com_gamedir);
        c_snprintf (autosave0, "%s/a0.sav", com_gamedir);

        if (File_Exists (autosave2))
            checkit = File_Delete (autosave2);
        if (File_Exists (autosave1))
            checkit = File_Rename (autosave1, autosave2);
        if (File_Exists (autosave0))
            checkit = File_Rename (autosave0, autosave1);
    }

    Host_Savegame ("a0", false);
    Con_DPrintf ("Autosaved %d:%02d\n", Time_Minutes((int)sv.time), Time_Seconds((int)sv.time) );

    // Set the next auto save time
    sv.auto_save_time = sv.time + AUTO_SAVE_INTERVAL;
}

/*
==================
Host_ServerFrame
==================
*/
void SV_UpdateServer (double frametime)
{

// run the world state
    pr_global_struct->frametime = frametime;

// set the time and clear the general datagram
    SV_ClearDatagram ();

// check for new clients
    SV_CheckForNewClients ();

// read client messages
    SV_RunClients (frametime);

// move things around and think
// always pause in single player if in console or menus
    if (!sv.paused && (svs.maxclients > 1 || key_dest == key_game) )
    {
        SV_Physics (frametime);
        SV_Autosave_Think ();
    }

//johnfitz -- devstats
    if (cls.signon == SIGNONS)
    {
        int     i, active; //johnfitz
        edict_t *ent; //johnfitz

        for (i=0, active=0; i < sv.num_edicts; i++)
        {
            ent = EDICT_NUM(i);
            if (!ent->free)
                active++;
        }
        if (active > MAX_WINQUAKE_EDICTS && dev_peakstats.edicts <= MAX_WINQUAKE_EDICTS)
            Con_Warning ("%i edicts exceeds standard limit of %d.\n", active, MAX_WINQUAKE_EDICTS); // 600
        dev_stats.edicts = active;
        dev_peakstats.edicts = c_max(active, dev_peakstats.edicts);
    }
//johnfitz

// send all messages to the clients
    SV_SendClientMessages ();
}


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

SERVER SPAWNING

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

/*
================
SV_ModelIndex

================
*/
int SV_ModelIndex (const char *name)
{
    int     i;

    if (!name || !name[0])
        return 0;

    for (i=0 ; i < MAX_FITZQUAKE_MODELS && sv.model_precache[i] ; i++)
        if (!strcmp(sv.model_precache[i], name))
            return i;

    if (i==MAX_FITZQUAKE_MODELS || !sv.model_precache[i])
        Host_Error ("SV_ModelIndex: model %s not precached", name);

    return i;
}

/*
================
SV_CreateBaseline
================
*/
void SV_CreateBaseline (void)
{
    edict_t     *svent;
    int         entnum;
    int         bits; //johnfitz -- PROTOCOL_FITZQUAKE

    for (entnum = 0; entnum < sv.num_edicts ; entnum++)
    {
    // get the current server version
        svent = EDICT_NUM(entnum);

        if (svent->free) continue;
        if (entnum > svs.maxclients && !svent->v.modelindex) continue;

    //
    // create entity baseline
    //
        VectorCopy (svent->v.origin, svent->baseline.origin);
        VectorCopy (svent->v.angles, svent->baseline.angles);
        svent->baseline.frame = svent->v.frame;
        svent->baseline.skin = svent->v.skin;

        if (entnum > 0 && entnum <= svs.maxclients)
        {
            svent->baseline.colormap = entnum;
            svent->baseline.modelindex = SV_ModelIndex("progs/player.mdl");
            svent->baseline.alpha = ENTALPHA_DEFAULT; //johnfitz -- alpha support
        }
        else
        {
            svent->baseline.colormap = 0;
            svent->baseline.modelindex = SV_ModelIndex(PR_GetString(svent->v.model));
            svent->baseline.alpha = svent->alpha; //johnfitz -- alpha support
        }

        //johnfitz -- PROTOCOL_FITZQUAKE
        bits = 0;

        if (sv.protocol == PROTOCOL_NETQUAKE) //still want to send baseline in PROTOCOL_NETQUAKE, so reset these values
        {
            if (svent->baseline.modelindex & 0xFF00) svent->baseline.modelindex = 0;
            if (svent->baseline.frame & 0xFF00) svent->baseline.frame = 0;

            svent->baseline.alpha = ENTALPHA_DEFAULT;
        }
        else //decide which extra data needs to be sent
        {
            if (svent->baseline.modelindex & 0xFF00) bits |= B_LARGEMODEL;
            if (svent->baseline.frame & 0xFF00) bits |= B_LARGEFRAME;
            if (svent->baseline.alpha != ENTALPHA_DEFAULT) bits |= B_ALPHA;
        }
        //johnfitz

    //
    // add to the message
    //
        //johnfitz -- PROTOCOL_FITZQUAKE
        if (bits)
            MSG_WriteByte (&sv.signon, svc_spawnbaseline2);
        else MSG_WriteByte (&sv.signon, svc_spawnbaseline);
        //johnfitz

        MSG_WriteShort (&sv.signon,entnum);

        //johnfitz -- PROTOCOL_FITZQUAKE
        if (bits) MSG_WriteByte (&sv.signon, bits);

        if (bits & B_LARGEMODEL)
            MSG_WriteShort (&sv.signon, svent->baseline.modelindex);
        else MSG_WriteByte (&sv.signon, svent->baseline.modelindex);

        if (bits & B_LARGEFRAME)
            MSG_WriteShort (&sv.signon, svent->baseline.frame);
        else MSG_WriteByte (&sv.signon, svent->baseline.frame);
        //johnfitz

        MSG_WriteByte (&sv.signon, svent->baseline.colormap);
        MSG_WriteByte (&sv.signon, svent->baseline.skin);

        MSG_WriteCoord(&sv.signon, svent->baseline.origin[0]);
        MSG_WriteAngle(&sv.signon, svent->baseline.angles[0]);
        MSG_WriteCoord(&sv.signon, svent->baseline.origin[1]);
        MSG_WriteAngle(&sv.signon, svent->baseline.angles[1]);
        MSG_WriteCoord(&sv.signon, svent->baseline.origin[2]);
        MSG_WriteAngle(&sv.signon, svent->baseline.angles[2]);


        //johnfitz -- PROTOCOL_FITZQUAKE
        if (bits & B_ALPHA) MSG_WriteByte (&sv.signon, svent->baseline.alpha);
        //johnfitz
    }
}


/*
================
SV_SendReconnect

Tell all the clients that the server is changing levels
================
*/
void SV_SendReconnect (void)
{
    byte    data[128];
    sizebuf_t   msg;

    msg.data = data;
    msg.cursize = 0;
    msg.maxsize = sizeof(data);

    MSG_WriteChar (&msg, svc_stufftext);
    MSG_WriteString (&msg, "reconnect\n");
    NET_SendToAll (&msg, 5.0);

    if (!isDedicated)
        Cmd_ExecuteString ("reconnect\n", src_command);
}


/*
================
SV_SaveSpawnparms

Grabs the current state of each client for saving across the
transition to another level
================
*/
void SV_SaveSpawnparms (void)
{
    int     i, j;

    svs.serverflags = pr_global_struct->serverflags;

    for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
    {
        if (!host_client->active)
            continue;

    // call the progs to get default spawn parms for the new client
        pr_global_struct->self = EDICT_TO_PROG(host_client->edict);
        PR_ExecuteProgram (pr_global_struct->SetChangeParms);
        for (j=0 ; j<NUM_SPAWN_PARMS ; j++)
            host_client->spawn_parms[j] = (&pr_global_struct->parm1)[j];
    }
}


/*
================
SV_SpawnServer

This is called at the start of each level
================
*/
typedef struct
{
    const char *modelname;          // "maps/e1m1.bsp"
    const char *checkstring;        // This string must be found in the entities (i.e. the problem to address)
    const char *patch_before_this;  // Insert before this text
    const char *patch;              // The "FIX"
} map_patches_t;

map_patches_t map_patches[] =
{
    {   "maps/apsp1.bsp",
        "{\n\"classname\" \"func_door\"\n\"targetname\" \"t4\"\n\"angle\" \"-2\"\n\"spawnflags\" \"1\"\n\"sounds\" \"2\"\n\"model\" \"*15\"\n}",
        "\"model\" \"*15\"",
        "\"lip\" \"-4\"\n\"origin\" \"0 0 -2\"\n"
    },

    {   "maps/e1m1.bsp",
        "{\n\"classname\" \"func_door\"\n\"targetname\" \"t4\"\n\"angle\" \"-2\"\n\"spawnflags\" \"1\"\n\"sounds\" \"2\"\n\"model\" \"*15\"\n}",
        "\"model\" \"*15\"",
        "\"lip\" \"-4\"\n\"origin\" \"0 0 -2\"\n"
    },

    {
        "maps/e1m2.bsp",
        "{\n\"sounds\" \"3\"\n\"classname\" \"func_door\"\n\"angle\" \"270\"\n\"wait\" \"-1\"\n\"model\" \"*34\"\n}\n",
        "\"model\" \"*34\"",
        "\"origin\" \"-3 0 0\"\n"
    },

    {
        "maps/e1m2.bsp",
        "{\n\"classname\" \"func_door\"\n\"angle\" \"90\"\n\"targetname\" \"t110\"\n\"wait\" \"-1\"\n\"model\" \"*33\"\n}\n",
        "\"model\" \"*33\"",
        "\"origin\" \"-3 0 0\"\n"
    },
    { NULL }, // Terminator
};

static char *MapPatch (const char *modelname, char *entstring)
{
    char *patched_entstring = NULL;
    char *insertion_point;

    map_patches_t* map_patch;

    Con_DPrintf ("Map patch check\n");
    for (map_patch = &map_patches[0]; map_patch->modelname; map_patch++)
    {
        if (strcasecmp (map_patch->modelname, modelname) != 0)
            continue; // Model name doesn't match

        if (strstr (entstring, map_patch->checkstring) == NULL)
            continue;

        insertion_point = strstr (entstring, map_patch->patch_before_this);

        if (insertion_point)
        {
            // Perform the operation
            int entstrlen = strlen (entstring);
            int patchstrlen = strlen (map_patch->patch);
            int newbufsize = entstrlen + patchstrlen + 1; // +1 for null term

            char *newbuf = malloc ( newbufsize);
            char temp = insertion_point[0];

            insertion_point[0] = 0;

        strlcpy (newbuf, entstring, newbufsize);
        strlcat (newbuf, map_patch->patch, newbufsize);
        insertion_point[0] = temp;
        strlcat (newbuf, insertion_point, newbufsize);

            // Already did one patch so free before replacing
            if (patched_entstring)
                free (patched_entstring);

            entstring = patched_entstring = newbuf;
//          Con_DPrintf ("Patch performed\n");
        }
    }

    return patched_entstring;
}

char *entity_string = NULL;

void SV_SpawnServer (const char *server)
{
    edict_t     *ent;
    int         i;

    // let's not have any servers with no name
    if (hostname.string[0] == 0)
        Cvar_SetQuick (&hostname, "UNNAMED");

    Con_DPrintf ("SpawnServer: %s\n",server);
    svs.changelevel_issued = false;     // now safe to issue another

// tell all connected clients that we are going to a new level
    if (sv.active) SV_SendReconnect ();

// make cvars consistent
    if (pr_coop.value) Cvar_SetValueQuick (&pr_deathmatch, 0);

// set up the new server
    Host_ClearMemory ();

    //memset (&sv, 0, sizeof(sv)); // Baker: Host_ClearMemory does this

// Baker: Clear level information prior to loading world.
    memset (&level, 0, sizeof(level));

    // Baker: These have to be done after Host_ClearMemory because sv gets cleared
    c_strlcpy (sv.name, server);
    sv.disallow_major_cheats = sv_cheats.value <= 0;    // sv_cheats 0 or -1 disallow major cheats (god, noclip, fly, notarget)
    sv.disallow_minor_cheats = !sv_cheats.value;        // sv_cheats -1 allows "give" because some coop maps supply too little ammo/health
    sv.current_skill = (int)(pr_skill.value + 0.5);
    sv.current_skill = CLAMP (0, sv.current_skill, 3);
    sv.protocol = sv_protocol; // johnfitz

// Construct the hint strings now that we know the skill level
    SV_HintStrings_NewMap ();

// load progs to get entity field count
    PR_LoadProgs ();

// allocate server memory

#ifdef SUPPORTS_SERVER_PROTOCOL_15
    switch (sv_protocol)
    {
    case PROTOCOL_NETQUAKE:
        //sv.datagram.maxsize = sv_protocol == sizeof(sv.datagram_buf); // Baker: Was unused
        sv.datagram.maxsize          = MAX_WINQUAKE_DATAGRAM        /* 1024 */ ;
        sv.signon.maxsize            = MAX_WINQUAKE_SIGNON;         // 8000 - 2 = 7998
        break;

    case PROTOCOL_FITZQUAKE:
    case PROTOCOL_FITZQUAKE_PLUS:
        if (svs.maxclients == 1 && !isDedicated)
            sv.datagram.maxsize      = MAX_MARK_V_DATAGRAM          /* 65526 */ ;
        else sv.datagram.maxsize     = MAX_FITZQUAKE_DATAGRAM_MTU   /* 1400 */ ;
        sv.signon.maxsize            = MAX_MARK_V_SIGNON;           // 65535-2  = 65533
        break;
    }

#if 0
    Con_Printf ("sv.datagram.maxsize is %d\n", sv.datagram.maxsize);
    Con_Printf ("sv.signon.maxsize is %d\n", sv.signon.maxsize);
    Con_Printf ("sv_protocol is %d\n", sv_protocol);
#endif
    sv.datagram.cursize = 0;
    sv.datagram.data = sv.datagram_buf;

    host_protocol_datagram_maxsize =  // continued ...
    sv.reliable_datagram.maxsize = sv.datagram.maxsize;
    sv.reliable_datagram.cursize = 0;
    sv.reliable_datagram.data = sv.reliable_datagram_buf;
#endif // SUPPORTS_SERVER_PROTOCOL_15
    sv.signon.cursize = 0;
    sv.signon.data = sv.signon_buf;


    c_strlcpy (sv.name, server);
    c_snprintf (sv.modelname, "maps/%s.bsp", server);
    sv.worldmodel = Mod_ForName (sv.modelname, false);
    if (!sv.worldmodel)
    {
        // Baker: This is a rather irregular way for the server to be shutdown.
        // It is sort of a "CL_Disconnect lite".  Doesn't stop demo recording, although
        // I'm not sure it should.  My guess is yes.
        // In single player, this isn't setting ca_disconnected or signons to 0.
        // Isn't setting key_dest to console.
#pragma message ("This sv map not found couldn't spawn server needs love")
        Con_Printf ("Couldn't spawn server %s\n", sv.modelname);
        SCR_EndLoadingPlaque (); // Baker: any disconnect state should end the loading plague, right?
        sv.active = false;
        return;
    }




// Read entity_string from memory
    {
        int mark = Hunk_LowMark ();
        char *patched;

        entity_string = sv.worldmodel->entities; // Point it to the standard entities string

        patched = MapPatch(sv.modelname, entity_string);
        if (patched)
        {
            Hunk_FreeToLowMark (mark);
            entity_string = Hunk_Strdup (patched, "ent patch string");

            free (patched);
            Con_DPrintf ("Map entity string patched.\n");
        }
    }


// determine max edicts to use
    switch (sv_protocol)
    {
    case PROTOCOL_NETQUAKE:
        sv.max_edicts = MAX_WINQUAKE_EDICTS;
        break;

    case PROTOCOL_FITZQUAKE:
    case PROTOCOL_FITZQUAKE_PLUS:
        {
            int classname_count = String_Count_String (entity_string, "classname");
            int predicted_count = classname_count * 1.5;

            Con_DPrintf ("MAX_EDICTS: Predict %d on classname count of %i\n", predicted_count, classname_count);

            if (host_max_edicts.value < 0) // -2048 = force 2048 max_edicts
                sv.max_edicts = abs(host_max_edicts.value);
            else if (host_max_edicts.value == 0 || host_max_edicts.value < predicted_count)
            {
                sv.max_edicts = predicted_count;
                Con_DPrintf ("Setting server edicts maximum to %i edicts on classname count of %i\n", sv.max_edicts, classname_count);
            }
            else sv.max_edicts = host_max_edicts.value;
            sv.max_edicts = CLAMP(MIN_SANE_EDICTS_512, sv.max_edicts, MAX_SANE_EDICTS_8192);
            Con_DPrintf ("MAX_EDICTS: After clamp, server edicts set %d edicts\n", sv.max_edicts);
        }
        break;
    }

// allocate the memory
    sv.edicts = Hunk_AllocName (sv.max_edicts * pr_edict_size, "edicts");

// leave slots at start for clients only
    sv.num_edicts = svs.maxclients + 1;
    for (i=0 ; i<svs.maxclients ; i++)
    {
        ent = EDICT_NUM(i+1);
        svs.clients[i].edict = ent;
    }

    sv.state = ss_loading;
    sv.paused = false;
    sv.time = 1.0;
    sv.models[1] = sv.worldmodel;

// clear world interaction links
    SV_ClearWorld ();

    sv.sound_precache[0] = pr_strings;
    sv.model_precache[0] = pr_strings;
    sv.model_precache[1] = sv.modelname;

    for (i = 1 ; i < sv.worldmodel->numsubmodels ; i++)
    {
        if (i >= MAX_FITZQUAKE_MODELS)
            Host_Error ("World submodels > MAX_FITZQUAKE_MODELS (%i%)\n", MAX_FITZQUAKE_MODELS);

        if (sv.protocol == PROTOCOL_NETQUAKE && i >= MAX_WINQUAKE_MODELS)
            Host_Error ("Too many models for protocol 15, limit %i", MAX_WINQUAKE_MODELS);

        sv.model_precache[i + 1] = localmodels[i];
        sv.models[i+1] = Mod_ForName (localmodels[i], false);
    }

// load the rest of the entities
    ent = EDICT_NUM(0);
    memset (&ent->v, 0, progs->entityfields * 4);
    ent->free = false;
    ent->v.model = PR_SetEngineString(sv.worldmodel->name);
    ent->v.modelindex = 1;      // world model
    ent->v.solid = SOLID_BSP;
    ent->v.movetype = MOVETYPE_PUSH;

    // these are needed here so that we can filter edicts in or out depending on the game type
    if (pr_coop.value)
        pr_global_struct->coop = pr_coop.value;
    else pr_global_struct->deathmatch = pr_deathmatch.value;

    pr_global_struct->mapname = PR_SetEngineString(sv.name);

// serverflags are for cross level information (sigils)
    pr_global_struct->serverflags = svs.serverflags;

    ED_LoadFromFile (entity_string); // Baker loads from memory, not file.

    sv.active = true;

    // Baker: If a game is loading, this will get set again after real sv.time is set.
    sv.auto_save_time = sv.time + AUTO_SAVE_MINIMUM_TIME;

// all setup is completed, any further precache statements are errors
    sv.state = ss_active;

// run two frames to allow everything to settle
    SV_Physics (0.1); // frame 1
    SV_Physics (0.1); // frame 2

// create a baseline for more efficient communications
    SV_CreateBaseline ();

    //johnfitz -- warn if signon buffer larger than standard server can handle
//  Con_Printf ("Signon size is %i\n", sv.signon.cursize);
    if (sv.signon.cursize > (MAX_WINQUAKE_MSGLEN /*8000*/ - 2) ) //max size that will fit into 8000-sized client->message buffer with 2 extra bytes on the end
//      Con_Warning ("%i byte signon buffer exceeds standard limit of 7998.\n", sv.signon.cursize);
        Con_Warning ("%i byte signon buffer exceeds standard limit of %d.\n", sv.signon.cursize, (MAX_WINQUAKE_MSGLEN /*8000*/ -2 ));
    //johnfitz

// send serverinfo to all connected clients
    for (i=0,host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
        if (host_client->active)
            SV_SendServerinfo (host_client);

    Con_DPrintf ("Server spawned.\n");
}
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Post Reply