LOTS OF CHANGES. was hoping to get revision 5000 perfect, but really that's never going to happen. this has gone on for too long now.
vulkan, wasapi, quake injector features added. irc, avplug, cef plugins/drivers reworked/updated/added openal reverb, doppler effects added. 'dir' console command now attempts to view clicked files. lots of warning fixes, should now only be deprecation warnings for most targets (depending on compiler version anyway...). SendEntity finally reworked to use flags properly. effectinfo improved, other smc-targetted fixes. mapcluster stuff now has support for linux. .basebone+.baseframe now exist in ssqc. qcc: -Fqccx supports qccx syntax, including qccx hacks. don't expect these to work in fteqw nor dp though. qcc: rewrote function call handling to use refs rather than defs. this makes struct passing more efficient and makes the __out keyword usable with fields etc. qccgui: can cope a little better with non-unicode files. can now represent most quake chars. qcc: suppressed warnings from *extensions.qc git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5000 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
parent
5920bf05fb
commit
27a59a0cbc
271 changed files with 101001 additions and 64352 deletions
|
@ -98,12 +98,12 @@ cvar_t sv_serverip = CVARD("sv_serverip", "", "Set this cvar to the server's pub
|
|||
cvar_t sv_public = CVAR("sv_public", "0");
|
||||
cvar_t sv_listen_qw = CVARAF("sv_listen_qw", "1", "sv_listen", 0);
|
||||
cvar_t sv_listen_nq = CVARD("sv_listen_nq", "2", "Allow new (net)quake clients to connect to the server.\n0 = don't let them in.\n1 = allow them in (WARNING: this allows 'qsmurf' DOS attacks).\n2 = accept (net)quake clients by emulating a challenge (as secure as QW/Q2 but does not fully conform to the NQ protocol).");
|
||||
cvar_t sv_listen_dp = CVAR("sv_listen_dp", "0"); /*kinda fucked right now*/
|
||||
cvar_t sv_listen_dp = CVARD("sv_listen_dp", "0", "Allows the server to respond with the DP-specific handshake protocol.\nWarning: this can potentially get confused with quake2, and results in race conditions with both vanilla netquake and quakeworld protocols.\nOn the plus side, DP clients can usually be identified correctly, enabling a model+sound limit boost.");
|
||||
cvar_t sv_listen_q3 = CVAR("sv_listen_q3", "0");
|
||||
cvar_t sv_reportheartbeats = CVAR("sv_reportheartbeats", "1");
|
||||
cvar_t sv_highchars = CVAR("sv_highchars", "1");
|
||||
cvar_t sv_maxrate = CVAR("sv_maxrate", "30000");
|
||||
cvar_t sv_maxdrate = CVARAF("sv_maxdrate", "100000",
|
||||
cvar_t sv_maxdrate = CVARAF("sv_maxdrate", "500000",
|
||||
"sv_maxdownloadrate", 0);
|
||||
cvar_t sv_minping = CVARF("sv_minping", "", CVAR_SERVERINFO);
|
||||
|
||||
|
@ -151,12 +151,10 @@ cvar_t coop = CVARF("coop", "" , CVAR_SERVERINFO);
|
|||
cvar_t skill = CVARF("skill", "" , CVAR_SERVERINFO); // 0, 1, 2 or 3
|
||||
cvar_t spawn = CVARF("spawn", "" , CVAR_SERVERINFO);
|
||||
cvar_t watervis = CVARF("watervis", "" , CVAR_SERVERINFO);
|
||||
cvar_t rearview = CVARF("rearview", "" , CVAR_SERVERINFO);
|
||||
#pragma warningmsg("Remove this some time")
|
||||
cvar_t allow_skybox = CVARF("allow_skybox", "", CVAR_SERVERINFO);
|
||||
cvar_t sv_allow_splitscreen = CVARF("allow_splitscreen","",CVAR_SERVERINFO);
|
||||
cvar_t sv_allow_splitscreen = CVARFD("allow_splitscreen","",CVAR_SERVERINFO, "Specifies whether clients can use splitscreen extensions to dynamically add additional clients. This only affects remote clients and not the built-in client.\nClients may need to reconnect in order to add seats when this is changed.");
|
||||
cvar_t fbskins = CVARF("fbskins", "", CVAR_SERVERINFO); //to get rid of lame fuhquake fbskins
|
||||
cvar_t mirrors = CVARF("mirrors", "" , CVAR_SERVERINFO);
|
||||
|
||||
cvar_t sv_motd[] ={ CVAR("sv_motd1", ""),
|
||||
CVAR("sv_motd2", ""),
|
||||
|
@ -182,8 +180,6 @@ vfsfile_t *sv_fraglogfile;
|
|||
void SV_FixupName(char *in, char *out, unsigned int outlen);
|
||||
void SV_AcceptClient (netadr_t *adr, int userid, char *userinfo);
|
||||
void PRH2_SetPlayerClass(client_t *cl, int classnum, qboolean fromqc);
|
||||
char *SV_BannedReason (netadr_t *a);
|
||||
void SV_EvaluatePenalties(client_t *cl);
|
||||
|
||||
#ifdef SQL
|
||||
void PR_SQLCycle();
|
||||
|
@ -217,7 +213,7 @@ void SV_Shutdown (void)
|
|||
SV_UnspawnServer();
|
||||
|
||||
if (sv.mvdrecording)
|
||||
SV_MVDStop (0, false);
|
||||
SV_MVDStop (MVD_CLOSE_STOPPED, false);
|
||||
|
||||
if (svs.entstatebuffer.entities)
|
||||
{
|
||||
|
@ -385,18 +381,24 @@ void SV_FinalMessage (char *message)
|
|||
{
|
||||
int i;
|
||||
client_t *cl;
|
||||
sizebuf_t buf;
|
||||
char bufdata[1024];
|
||||
|
||||
SZ_Clear (&sv.datagram);
|
||||
MSG_WriteByte (&sv.datagram, svc_print);
|
||||
MSG_WriteByte (&sv.datagram, PRINT_HIGH);
|
||||
MSG_WriteString (&sv.datagram, message);
|
||||
MSG_WriteByte (&sv.datagram, svc_disconnect);
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
buf.data = bufdata;
|
||||
buf.maxsize = sizeof(bufdata);
|
||||
|
||||
SZ_Clear (&buf);
|
||||
MSG_WriteByte (&buf, svc_print);
|
||||
MSG_WriteByte (&buf, PRINT_HIGH);
|
||||
MSG_WriteString (&buf, message);
|
||||
MSG_WriteByte (&buf, svc_disconnect);
|
||||
|
||||
for (i=0, cl = svs.clients ; i<svs.allocated_client_slots ; i++, cl++)
|
||||
if (cl->state >= cs_spawned)
|
||||
if (ISNQCLIENT(cl) || ISQWCLIENT(cl))
|
||||
Netchan_Transmit (&cl->netchan, sv.datagram.cursize
|
||||
, sv.datagram.data, 10000);
|
||||
Netchan_Transmit (&cl->netchan, buf.cursize
|
||||
, buf.data, 10000);
|
||||
}
|
||||
|
||||
|
||||
|
@ -436,7 +438,7 @@ void SV_DropClient (client_t *drop)
|
|||
break;
|
||||
case SCP_QUAKEWORLD:
|
||||
case SCP_NETQUAKE:
|
||||
case SCP_PROQUAKE:
|
||||
case SCP_BJP3:
|
||||
case SCP_FITZ666:
|
||||
case SCP_DARKPLACES6:
|
||||
case SCP_DARKPLACES7:
|
||||
|
@ -626,7 +628,8 @@ void SV_DropClient (client_t *drop)
|
|||
}
|
||||
drop->laggedpacket_last = NULL;
|
||||
|
||||
drop->pendingentbits = NULL;
|
||||
drop->pendingdeltabits = NULL;
|
||||
drop->pendingcsqcbits = NULL;
|
||||
if (drop->frameunion.frames) //union of the same sort of structure
|
||||
{
|
||||
Z_Free(drop->frameunion.frames);
|
||||
|
@ -644,12 +647,6 @@ void SV_DropClient (client_t *drop)
|
|||
drop->statss[i] = NULL;
|
||||
}
|
||||
|
||||
if (drop->csqcentversions)
|
||||
Z_Free(drop->csqcentversions);
|
||||
drop->csqcentversions = NULL;
|
||||
if (drop->csqcentsequence)
|
||||
Z_Free(drop->csqcentsequence);
|
||||
drop->csqcentsequence = NULL;
|
||||
drop->csqcactive = false;
|
||||
|
||||
memset(&termmsg, 0, sizeof(termmsg));
|
||||
|
@ -882,7 +879,7 @@ int SV_CalcPing (client_t *cl, qboolean forcecalc)
|
|||
case SCP_DARKPLACES6:
|
||||
case SCP_DARKPLACES7:
|
||||
case SCP_NETQUAKE:
|
||||
case SCP_PROQUAKE:
|
||||
case SCP_BJP3:
|
||||
case SCP_FITZ666:
|
||||
case SCP_QUAKEWORLD:
|
||||
{
|
||||
|
@ -1418,7 +1415,7 @@ flood the server with invalid connection IPs. With a
|
|||
challenge, they must give a valid IP address.
|
||||
=================
|
||||
*/
|
||||
void SVC_GetChallenge (void)
|
||||
void SVC_GetChallenge (qboolean nodpresponse)
|
||||
{
|
||||
#ifdef HUFFNETWORK
|
||||
int compressioncrc;
|
||||
|
@ -1441,11 +1438,11 @@ void SVC_GetChallenge (void)
|
|||
else
|
||||
#endif
|
||||
#ifdef Q2SERVER
|
||||
if (svs.gametype == GT_QUAKE2) //quake 2 servers give a different challenge responce
|
||||
buf = va("challenge %i", challenge);
|
||||
if (svs.gametype == GT_QUAKE2)
|
||||
buf = va("challenge %i", challenge); //quake 2 servers give a different challenge response
|
||||
else
|
||||
#endif
|
||||
buf = va("%c%i", S2C_CHALLENGE, challenge);
|
||||
buf = va("%c%i", S2C_CHALLENGE, challenge); //quakeworld's response is a bit poo.
|
||||
|
||||
over = buf + strlen(buf) + 1;
|
||||
|
||||
|
@ -1506,10 +1503,16 @@ void SVC_GetChallenge (void)
|
|||
#endif
|
||||
}
|
||||
|
||||
if (sv_listen_dp.value && (sv_listen_nq.value || sv_bigcoords.value || !sv_listen_qw.value))
|
||||
if (progstype == PROG_H2)
|
||||
nodpresponse = true;
|
||||
|
||||
if (!nodpresponse && sv_listen_dp.value && (sv_listen_nq.value || sv_bigcoords.value || !sv_listen_qw.value))
|
||||
{
|
||||
//dp (protocol6 upwards) can respond to this (and fte won't get confused because the challenge will be wrong)
|
||||
char *dp = va("challenge "DISTRIBUTION"%i", challenge);
|
||||
char *dp;
|
||||
if (sv_listen_qw.value)
|
||||
dp = va("challenge FTE%i", challenge); //an FTE prefix will cause FTE clients to ignore the packet, to give preference to the qw challenge + protocols
|
||||
else
|
||||
dp = va("challenge %iFTE", challenge); //we still need to add a postfix to prevent it from being interpreted as a Q2 server
|
||||
Netchan_OutOfBand(NS_SERVER, &net_from, strlen(dp)+1, dp);
|
||||
}
|
||||
|
||||
|
@ -1618,8 +1621,8 @@ void VARGS SV_RejectMessage(int protocol, char *format, ...)
|
|||
{
|
||||
#ifdef NQPROT
|
||||
case SCP_NETQUAKE:
|
||||
case SCP_BJP3:
|
||||
case SCP_FITZ666:
|
||||
case SCP_PROQUAKE:
|
||||
string[4] = CCREP_REJECT;
|
||||
vsnprintf (string+5,sizeof(string)-1-5, format,argptr);
|
||||
len = strlen(string+4)+1+4;
|
||||
|
@ -1654,7 +1657,7 @@ void VARGS SV_RejectMessage(int protocol, char *format, ...)
|
|||
Netchan_OutOfBand (NS_SERVER, &net_from, len, (qbyte *)string);
|
||||
}
|
||||
|
||||
void SV_AcceptMessage(int protocol)
|
||||
void SV_AcceptMessage(client_t *newcl)
|
||||
{
|
||||
char string[8192];
|
||||
sizebuf_t sb;
|
||||
|
@ -1667,12 +1670,12 @@ void SV_AcceptMessage(int protocol)
|
|||
sb.maxsize = sizeof(string);
|
||||
sb.data = string;
|
||||
|
||||
switch(protocol)
|
||||
switch(newcl->protocol)
|
||||
{
|
||||
#ifdef NQPROT
|
||||
case SCP_NETQUAKE:
|
||||
case SCP_BJP3:
|
||||
case SCP_FITZ666:
|
||||
case SCP_PROQUAKE:
|
||||
// if (net_from.type != NA_LOOPBACK)
|
||||
{
|
||||
SZ_Clear(&sb);
|
||||
|
@ -1680,8 +1683,11 @@ void SV_AcceptMessage(int protocol)
|
|||
MSG_WriteByte(&sb, CCREP_ACCEPT);
|
||||
NET_LocalAddressForRemote(svs.sockets, &net_from, &localaddr, 0);
|
||||
MSG_WriteLong(&sb, ShortSwap(localaddr.port));
|
||||
MSG_WriteByte(&sb, (protocol==SCP_NETQUAKE)?0:1/*MOD_PROQUAKE*/);
|
||||
MSG_WriteByte(&sb, 10 * 3.50/*MOD_PROQUAKE_VERSION*/);
|
||||
if (newcl->proquake_angles_hack)
|
||||
{
|
||||
MSG_WriteByte(&sb, 1/*MOD_PROQUAKE*/);
|
||||
MSG_WriteByte(&sb, 10 * 3.50/*MOD_PROQUAKE_VERSION*/);
|
||||
}
|
||||
*(int*)sb.data = BigLong(NETFLAG_CTL|sb.cursize);
|
||||
NET_SendPacket(NS_SERVER, sb.cursize, sb.data, &net_from);
|
||||
return;
|
||||
|
@ -1780,16 +1786,16 @@ void SV_ClientProtocolExtensionsChanged(client_t *client)
|
|||
{
|
||||
client->max_net_clients = 255;
|
||||
client->max_net_ents = bound(512, pr_maxedicts.ival, 32768);
|
||||
client->maxmodels = 1024; //protocol limit of 16 bits. 15 bits for late precaches. client limit of 1k
|
||||
client->maxmodels = MAX_PRECACHE_MODELS; //protocol limit of 16 bits. 15 bits for late precaches. client limit of 1k
|
||||
|
||||
client->datagram.maxsize = sizeof(host_client->datagram_buf);
|
||||
}
|
||||
else if (client->protocol == SCP_FITZ666)
|
||||
else if (client->protocol == SCP_BJP3 || client->protocol == SCP_FITZ666)
|
||||
{
|
||||
client->max_net_clients = NQMAX_CLIENTS;
|
||||
client->max_net_ents = bound(512, pr_maxedicts.ival, 32768); //fitzquake supports 65535, but our writeentity builtin works differently, which causes problems.
|
||||
client->maxmodels = 1024;
|
||||
maxpacketentities = 512;
|
||||
client->maxmodels = MAX_PRECACHE_MODELS;
|
||||
maxpacketentities = 65535;
|
||||
|
||||
client->datagram.maxsize = sizeof(host_client->datagram_buf);
|
||||
}
|
||||
|
@ -1797,7 +1803,7 @@ void SV_ClientProtocolExtensionsChanged(client_t *client)
|
|||
{
|
||||
client->max_net_clients = NQMAX_CLIENTS;
|
||||
client->datagram.maxsize = MAX_NQDATAGRAM; //vanilla limit
|
||||
if (client->protocol == SCP_PROQUAKE)
|
||||
if (client->proquake_angles_hack)
|
||||
client->max_net_ents = bound(512, pr_maxedicts.ival, 8192);
|
||||
else
|
||||
client->max_net_ents = bound(512, pr_maxedicts.ival, 600);
|
||||
|
@ -1808,7 +1814,8 @@ void SV_ClientProtocolExtensionsChanged(client_t *client)
|
|||
|
||||
client->max_net_clients = min(client->max_net_clients, MAX_CLIENTS);
|
||||
|
||||
client->pendingentbits = NULL;
|
||||
client->pendingdeltabits = NULL;
|
||||
client->pendingcsqcbits = NULL;
|
||||
|
||||
//initialise the client's frames, based on that client's protocol
|
||||
switch(client->protocol)
|
||||
|
@ -1845,25 +1852,25 @@ void SV_ClientProtocolExtensionsChanged(client_t *client)
|
|||
if (maxents > client->max_net_ents)
|
||||
maxents = client->max_net_ents;
|
||||
ptr = Z_Malloc( sizeof(client_frame_t)*UPDATE_BACKUP+
|
||||
sizeof(*client->pendingentbits)*client->max_net_ents+
|
||||
sizeof(unsigned int)*maxents*UPDATE_BACKUP+
|
||||
sizeof(unsigned int)*maxents*UPDATE_BACKUP);
|
||||
sizeof(*client->pendingdeltabits)*client->max_net_ents+
|
||||
sizeof(*client->pendingcsqcbits)*client->max_net_ents+
|
||||
sizeof(*client->frameunion.frames[i].resend)*maxents*UPDATE_BACKUP);
|
||||
client->frameunion.frames = (void*)ptr;
|
||||
ptr += sizeof(*client->frameunion.frames)*UPDATE_BACKUP;
|
||||
client->pendingentbits = (void*)ptr;
|
||||
ptr += sizeof(*client->pendingentbits)*client->max_net_ents;
|
||||
client->pendingdeltabits = (void*)ptr;
|
||||
ptr += sizeof(*client->pendingdeltabits)*client->max_net_ents;
|
||||
client->pendingcsqcbits = (void*)ptr;
|
||||
ptr += sizeof(*client->pendingcsqcbits)*client->max_net_ents;
|
||||
for (i = 0; i < UPDATE_BACKUP; i++)
|
||||
{
|
||||
client->frameunion.frames[i].entities.max_entities = maxents;
|
||||
client->frameunion.frames[i].resendentnum = (void*)ptr;
|
||||
ptr += sizeof(*client->frameunion.frames[i].resendentnum)*maxents;
|
||||
client->frameunion.frames[i].resendentbits = (void*)ptr;
|
||||
ptr += sizeof(*client->frameunion.frames[i].resendentbits)*maxents;
|
||||
client->frameunion.frames[i].resend = (void*)ptr;
|
||||
ptr += sizeof(*client->frameunion.frames[i].resend)*maxents;
|
||||
client->frameunion.frames[i].senttime = realtime;
|
||||
}
|
||||
|
||||
//make sure the reset is sent.
|
||||
client->pendingentbits[0] = UF_REMOVE;
|
||||
client->pendingdeltabits[0] = UF_REMOVE;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1889,7 +1896,7 @@ void SV_ClientProtocolExtensionsChanged(client_t *client)
|
|||
|
||||
|
||||
//void NET_AdrToStringResolve (netadr_t *adr, void (*resolved)(void *ctx, void *data, size_t a, size_t b), void *ctx, size_t a, size_t b);
|
||||
void SV_UserDNSResolved(void *ctx, void *data, size_t idx, size_t uid)
|
||||
/*static void SV_UserDNSResolved(void *ctx, void *data, size_t idx, size_t uid)
|
||||
{
|
||||
if (idx < svs.allocated_client_slots)
|
||||
{
|
||||
|
@ -1902,7 +1909,7 @@ void SV_UserDNSResolved(void *ctx, void *data, size_t idx, size_t uid)
|
|||
}
|
||||
Con_DPrintf("stale dns lookup result: %s\n", (char*)data);
|
||||
Z_Free(data);
|
||||
}
|
||||
}*/
|
||||
|
||||
client_t *SV_AddSplit(client_t *controller, char *info, int id)
|
||||
{
|
||||
|
@ -1932,7 +1939,7 @@ client_t *SV_AddSplit(client_t *controller, char *info, int id)
|
|||
//only allow splitscreen if its explicitly allowed. unless its the local client in which case its always allowed.
|
||||
//wouldn't it be awesome if we could always allow it for spectators? the join command makes that awkward, though I suppose we could just drop the extras in that case.
|
||||
if (!sv_allow_splitscreen.ival && controller->netchan.remote_address.type != NA_LOOPBACK)
|
||||
return NULL;
|
||||
return NULL; //FIXME: allow spectators to do this anyway?
|
||||
|
||||
for (i=0,cl=svs.clients ; i<sv.allocated_client_slots ; i++,cl++)
|
||||
{
|
||||
|
@ -1949,6 +1956,7 @@ client_t *SV_AddSplit(client_t *controller, char *info, int id)
|
|||
|
||||
cl->spectator = controller->spectator;
|
||||
cl->netchan.remote_address = controller->netchan.remote_address;
|
||||
cl->netchan.message.prim = controller->netchan.message.prim;
|
||||
cl->zquake_extensions = controller->zquake_extensions;
|
||||
cl->fteprotocolextensions = controller->fteprotocolextensions;
|
||||
cl->fteprotocolextensions2 = controller->fteprotocolextensions2;
|
||||
|
@ -1958,8 +1966,10 @@ client_t *SV_AddSplit(client_t *controller, char *info, int id)
|
|||
cl->max_net_clients = controller->max_net_clients;
|
||||
cl->max_net_ents = controller->max_net_ents;
|
||||
|
||||
|
||||
Q_strncatz(cl->guid, va("%s:%i", controller->guid, curclients), sizeof(cl->guid));
|
||||
if (*controller->guid)
|
||||
Q_snprintfz(cl->guid, sizeof(cl->guid), "%s:%i", controller->guid, curclients);
|
||||
else
|
||||
Q_strncpyz(cl->guid, "", sizeof(cl->guid));
|
||||
cl->name = cl->namebuf;
|
||||
cl->team = cl->teambuf;
|
||||
|
||||
|
@ -1967,7 +1977,8 @@ client_t *SV_AddSplit(client_t *controller, char *info, int id)
|
|||
cl->userid = nextuserid;
|
||||
|
||||
cl->playerclass = 0;
|
||||
cl->pendingentbits = NULL;
|
||||
cl->pendingdeltabits = NULL;
|
||||
cl->pendingcsqcbits = NULL;
|
||||
|
||||
cl->edict = NULL;
|
||||
#ifdef Q2SERVER
|
||||
|
@ -2076,6 +2087,8 @@ client_t *SVC_DirectConnect(void)
|
|||
int numssclients = 1;
|
||||
|
||||
int protocol;
|
||||
qboolean proquakeanglehack = false;
|
||||
unsigned int supportedprotocols = 0;
|
||||
|
||||
unsigned int protextsupported=0;
|
||||
unsigned int protextsupported2=0;
|
||||
|
@ -2135,6 +2148,13 @@ client_t *SVC_DirectConnect(void)
|
|||
Con_TPrintf ("* rejected connect from dp client\n");
|
||||
return NULL;
|
||||
}
|
||||
if (progstype == PROG_H2)
|
||||
{
|
||||
if (!sv_listen_nq.value)
|
||||
SV_RejectMessage (SCP_DARKPLACES6, "NQ protocols are not supported with hexen2 gamecode.\n", version_string());
|
||||
Con_TPrintf ("* rejected connect from dp client (because of hexen2)\n");
|
||||
return NULL;
|
||||
}
|
||||
Q_strncpyz (userinfo[0], net_message.data + 11, sizeof(userinfo[0])-1);
|
||||
|
||||
if (strcmp(Info_ValueForKey(userinfo[0], "protocol"), "darkplaces 3"))
|
||||
|
@ -2146,21 +2166,54 @@ client_t *SVC_DirectConnect(void)
|
|||
//it's a darkplaces client.
|
||||
|
||||
s = Info_ValueForKey(userinfo[0], "protocols");
|
||||
if (svs.netprim.coordsize != 4)
|
||||
{ //we allow nq with sv_listen_nq 0...
|
||||
//reason: dp is too similar for concerns about unsupported code, while the main reason why we disable nq is because of the lack of challenges
|
||||
//(and no, this isn't a way to bypass invalid challenges)
|
||||
protocol = SCP_NETQUAKE;
|
||||
Con_TPrintf ("* DP without sv_bigcoords 1\n");
|
||||
|
||||
while(s && *s)
|
||||
{
|
||||
static const struct
|
||||
{
|
||||
char *name;
|
||||
unsigned int bits;
|
||||
} dpnames[] =
|
||||
{
|
||||
{"FITZ", 1u<<SCP_FITZ666}, //dp doesn't support this, but this is for potential compat if other engines use this handshake
|
||||
{"666", 1u<<SCP_FITZ666}, //dp doesn't support this, but this is for potential compat if other engines use this handshake
|
||||
{"DP7", 1u<<SCP_DARKPLACES7},
|
||||
{"DP6", 1u<<SCP_DARKPLACES6},
|
||||
{"DP5", 0},
|
||||
{"DP4", 0},
|
||||
{"DP3", 0},
|
||||
{"DP2", 0},
|
||||
{"DP1", 0},
|
||||
{"QUAKEDP", 1u<<SCP_NETQUAKE},
|
||||
{"QUAKE", 1u<<SCP_NETQUAKE},
|
||||
{"QW", 0}, //mixing protocols doesn't make sense, and would just confuse the client.
|
||||
{"NEHAHRAMOVIE", 0},
|
||||
{"NEHAHRABJP", 0},
|
||||
{"NEHAHRABJP2", 0},
|
||||
{"NEHAHRABJP3", 1u<<SCP_BJP3},
|
||||
{"DP7DP6", (1u<<SCP_DARKPLACES7)|(1u<<SCP_DARKPLACES6)}, //stupid shitty crappy client
|
||||
};
|
||||
int p;
|
||||
|
||||
s = COM_Parse(s);
|
||||
for (p = 0; p < countof(dpnames); p++)
|
||||
{
|
||||
if (!Q_strcasecmp(dpnames[p].name, com_token))
|
||||
{
|
||||
supportedprotocols |= dpnames[p].bits;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (p == countof(dpnames))
|
||||
Con_DPrintf("DP client reporting unknown protocol \"%s\"\n", com_token);
|
||||
}
|
||||
else if (strstr(s, "DP7"))
|
||||
protocol = SCP_DARKPLACES7;
|
||||
else
|
||||
protocol = SCP_DARKPLACES6;
|
||||
proquakeanglehack = false;
|
||||
|
||||
protocol = SCP_DARKPLACES7;
|
||||
|
||||
s = Info_ValueForKey(userinfo[0], "challenge");
|
||||
if (!strncmp(s, DISTRIBUTION, strlen(DISTRIBUTION)))
|
||||
challenge = atoi(s+strlen(DISTRIBUTION));
|
||||
if (!strncmp(s, "FTE", strlen("FTE"))) //cope with our mangling of the challenge.
|
||||
challenge = atoi(s+strlen("FTE"));
|
||||
else
|
||||
challenge = atoi(s);
|
||||
|
||||
|
@ -2173,6 +2226,7 @@ client_t *SVC_DirectConnect(void)
|
|||
Info_SetValueForKey(userinfo[0], "name", "CONNECTING", sizeof(userinfo[0]));
|
||||
|
||||
qport = 0;
|
||||
proquakeanglehack = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2197,10 +2251,22 @@ client_t *SVC_DirectConnect(void)
|
|||
{
|
||||
numssclients = 1;
|
||||
protocol = SCP_NETQUAKE; //because we can
|
||||
if (atoi(Info_ValueForKey(Cmd_Argv(4), "mod")) == 1)
|
||||
protocol = SCP_PROQUAKE;
|
||||
else if (atoi(Info_ValueForKey(Cmd_Argv(4), "mod")) == 666)
|
||||
switch(atoi(Info_ValueForKey(Cmd_Argv(4), "mod")))
|
||||
{
|
||||
case 1:
|
||||
proquakeanglehack = true;
|
||||
break;
|
||||
#ifdef NQPROT
|
||||
case PROTOCOL_VERSION_FITZ:
|
||||
case PROTOCOL_VERSION_RMQ:
|
||||
protocol = SCP_FITZ666;
|
||||
break;
|
||||
case PROTOCOL_VERSION_BJP3:
|
||||
protocol = SCP_BJP3;
|
||||
proquakeanglehack = true;
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else if (version != PROTOCOL_VERSION_QW)
|
||||
{
|
||||
|
@ -2242,6 +2308,24 @@ client_t *SVC_DirectConnect(void)
|
|||
}
|
||||
}
|
||||
|
||||
if (sv.msgfromdemo || net_from.type == NA_LOOPBACK) //normal rules don't apply
|
||||
;
|
||||
else
|
||||
{
|
||||
// see if the challenge is valid
|
||||
if (!SV_ChallengePasses(challenge))
|
||||
{
|
||||
if (sv_listen_dp.ival && !challenge && protocol == SCP_QUAKEWORLD)
|
||||
{
|
||||
//dp replies with 'challenge'. which vanilla quakeworld interprets as: c<CHALLENGEID><ignored junk 'hallenge'>
|
||||
//so just silence that error.
|
||||
return NULL;
|
||||
}
|
||||
SV_RejectMessage (protocol, "Bad challenge.\n");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (sv_banproxies.ival)
|
||||
{
|
||||
//FIXME: allow them to spectate but not join
|
||||
|
@ -2317,18 +2401,6 @@ client_t *SVC_DirectConnect(void)
|
|||
if (!(protextsupported & PEXT_SPLITSCREEN))
|
||||
numssclients = 1;
|
||||
|
||||
if (sv.msgfromdemo || net_from.type == NA_LOOPBACK) //normal rules don't apply
|
||||
i=0;
|
||||
else
|
||||
{
|
||||
// see if the challenge is valid
|
||||
if (!SV_ChallengePasses(challenge))
|
||||
{
|
||||
SV_RejectMessage (protocol, "Bad challenge.\n");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (MSV_ClusterLogin(guid, userinfo[0], sizeof(userinfo[0])))
|
||||
return NULL;
|
||||
|
||||
|
@ -2376,26 +2448,30 @@ client_t *SVC_DirectConnect(void)
|
|||
memset (newcl, 0, sizeof(client_t));
|
||||
|
||||
#ifdef NQPROT
|
||||
if (protocol >= SCP_NETQUAKE && protocol < SCP_DARKPLACES6)
|
||||
if (!supportedprotocols && protocol == SCP_NETQUAKE)
|
||||
{ //NQ protocols lack stuff like protocol extensions.
|
||||
//its the wild west where nothing is known about the client and everything breaks.
|
||||
if (!strcmp(sv_protocol_nq.string, "fitz"))
|
||||
protocol = SCP_FITZ666;
|
||||
else if (!strcmp(sv_protocol_nq.string, "bjp") || !strcmp(sv_protocol_nq.string, "bjp3"))
|
||||
protocol = SCP_BJP3;
|
||||
else if (!strcmp(sv_protocol_nq.string, "dp6"))
|
||||
protocol = SCP_DARKPLACES6;
|
||||
else if (!strcmp(sv_protocol_nq.string, "dp7"))
|
||||
protocol = SCP_DARKPLACES6;
|
||||
else if (!strcmp(sv_protocol_nq.string, "id") || !strcmp(sv_protocol_nq.string, "vanilla"))
|
||||
protocol = (protocol==SCP_PROQUAKE)?SCP_PROQUAKE:SCP_NETQUAKE;
|
||||
protocol = SCP_NETQUAKE;
|
||||
else switch(sv_protocol_nq.ival)
|
||||
{
|
||||
case PROTOCOL_VERSION_RMQ:
|
||||
case PROTOCOL_VERSION_FITZ:
|
||||
protocol = SCP_FITZ666;
|
||||
break;
|
||||
case PROTOCOL_VERSION_BJP3:
|
||||
protocol = SCP_BJP3;
|
||||
break;
|
||||
case 15:
|
||||
//don't trip up on proquake's angle change.
|
||||
protocol = (protocol==SCP_PROQUAKE)?SCP_PROQUAKE:SCP_NETQUAKE;
|
||||
protocol = SCP_NETQUAKE;
|
||||
break;
|
||||
case PROTOCOL_VERSION_DP6:
|
||||
protocol = SCP_DARKPLACES6;
|
||||
|
@ -2413,8 +2489,10 @@ client_t *SVC_DirectConnect(void)
|
|||
#endif
|
||||
|
||||
newcl->userid = nextuserid;
|
||||
newcl->supportedprotocols = supportedprotocols;
|
||||
newcl->fteprotocolextensions = protextsupported;
|
||||
newcl->fteprotocolextensions2 = protextsupported2;
|
||||
newcl->proquake_angles_hack = proquakeanglehack;
|
||||
newcl->protocol = protocol;
|
||||
Q_strncpyz(newcl->guid, guid, sizeof(newcl->guid));
|
||||
|
||||
|
@ -2797,8 +2875,10 @@ client_t *SVC_DirectConnect(void)
|
|||
|
||||
newcl->realip_ping = (((rand()^(rand()<<8) ^ *(int*)&realtime)&0xffffff)<<8) | (newcl-svs.clients);
|
||||
|
||||
#ifdef HEXEN2
|
||||
if (newcl->istobeloaded && newcl->edict)
|
||||
newcl->playerclass = newcl->edict->xv->playerclass;
|
||||
#endif
|
||||
|
||||
// parse some info from the info strings
|
||||
SV_ExtractFromUserinfo (newcl, true);
|
||||
|
@ -2904,7 +2984,7 @@ client_t *SVC_DirectConnect(void)
|
|||
|
||||
if (!newcl->wasrecorded)
|
||||
{
|
||||
SV_AcceptMessage (protocol);
|
||||
SV_AcceptMessage (newcl);
|
||||
|
||||
newcl->state = cs_free;
|
||||
if (ISNQCLIENT(newcl))
|
||||
|
@ -2981,68 +3061,7 @@ client_t *SVC_DirectConnect(void)
|
|||
for (clients = 1; clients < numssclients; clients++)
|
||||
SV_AddSplit(newcl, userinfo[clients], clients);
|
||||
}
|
||||
#if 0
|
||||
for (clients = 1; clients < numssclients; clients++)
|
||||
{
|
||||
for (i=0,cl=svs.clients ; i<sv.allocated_client_slots ; i++,cl++)
|
||||
{
|
||||
if (cl->state == cs_free)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == sv.allocated_client_slots)
|
||||
break;
|
||||
|
||||
temp.frameunion.frames = cl->frameunion.frames; //don't touch these.
|
||||
temp.edict = cl->edict;
|
||||
memcpy(cl, newcl, sizeof(client_t));
|
||||
Q_strncatz(cl->guid, va("%s:%i", guid, clients), sizeof(cl->guid));
|
||||
cl->name = cl->namebuf;
|
||||
cl->team = cl->teambuf;
|
||||
|
||||
nextuserid++; // so every client gets a unique id
|
||||
cl->userid = nextuserid;
|
||||
|
||||
cl->playerclass = 0;
|
||||
cl->frameunion.frames = temp.frameunion.frames;
|
||||
cl->pendingentbits = NULL;
|
||||
cl->edict = EDICT_NUM(svprogfuncs, i+1);
|
||||
|
||||
cl->fteprotocolextensions |= PEXT_SPLITSCREEN;
|
||||
|
||||
if (newcl->controller)
|
||||
{
|
||||
newcl->controller->controlled = cl;
|
||||
newcl->controller = cl;
|
||||
}
|
||||
else
|
||||
{
|
||||
newcl->controlled = cl;
|
||||
newcl->controller = cl;
|
||||
}
|
||||
cl->controller = newcl;
|
||||
cl->controlled = NULL;
|
||||
|
||||
Q_strncpyS (cl->userinfo, userinfo[clients], sizeof(cl->userinfo)-1);
|
||||
cl->userinfo[sizeof(cl->userinfo)-1] = '\0';
|
||||
|
||||
if (spectator)
|
||||
{
|
||||
Info_RemoveKey (cl->userinfo, "spectator");
|
||||
Info_SetValueForStarKey (cl->userinfo, "*spectator", "1", sizeof(cl->userinfo));
|
||||
}
|
||||
else
|
||||
Info_RemoveKey (cl->userinfo, "*spectator");
|
||||
|
||||
SV_ExtractFromUserinfo (cl, true);
|
||||
|
||||
// if (!preserveparms)
|
||||
SV_GetNewSpawnParms(cl);
|
||||
|
||||
SV_EvaluatePenalties(cl);
|
||||
}
|
||||
#endif
|
||||
newcl->controller = NULL;
|
||||
|
||||
if (!redirect)
|
||||
|
@ -3412,11 +3431,11 @@ qboolean SV_ConnectionlessPacket (void)
|
|||
}
|
||||
else if (!strcmp(c,"\xad\xad\xad\xad""getchallenge"))
|
||||
{
|
||||
SVC_GetChallenge ();
|
||||
SVC_GetChallenge (true);
|
||||
}
|
||||
else if (!strcmp(c,"getchallenge"))
|
||||
{
|
||||
SVC_GetChallenge ();
|
||||
SVC_GetChallenge (false);
|
||||
}
|
||||
#ifdef NQPROT
|
||||
/*for DP*/
|
||||
|
@ -3606,7 +3625,6 @@ qboolean SVNQ_ConnectionlessPacket(void)
|
|||
return false; //not our version...
|
||||
}
|
||||
|
||||
|
||||
mod = MSG_ReadByte();
|
||||
modver = MSG_ReadByte();
|
||||
flags = MSG_ReadByte();
|
||||
|
@ -3615,12 +3633,22 @@ qboolean SVNQ_ConnectionlessPacket(void)
|
|||
if (!strncmp(MSG_ReadString(), "getchallenge", 12) && (sv_listen_qw.ival || sv_listen_dp.ival))
|
||||
{
|
||||
/*dual-stack client, supporting either DP or QW protocols*/
|
||||
SVC_GetChallenge ();
|
||||
SVC_GetChallenge (true);
|
||||
}
|
||||
else if (SV_ChallengeRecent())
|
||||
return true;
|
||||
else
|
||||
{
|
||||
if (progstype == PROG_H2)
|
||||
{
|
||||
SZ_Clear(&sb);
|
||||
MSG_WriteLong(&sb, 0);
|
||||
MSG_WriteByte(&sb, CCREP_REJECT);
|
||||
MSG_WriteString(&sb, "NQ clients are not supported with hexen2 gamecode\n");
|
||||
*(int*)sb.data = BigLong(NETFLAG_CTL+sb.cursize);
|
||||
NET_SendPacket(NS_SERVER, sb.cursize, sb.data, &net_from);
|
||||
return false; //not our version...
|
||||
}
|
||||
if (sv_listen_nq.ival == 2)
|
||||
{
|
||||
SZ_Clear(&sb);
|
||||
|
@ -3922,7 +3950,7 @@ qboolean SV_ReadPackets (float *delay)
|
|||
#endif
|
||||
{
|
||||
// check for connectionless packet (0xffffffff) first
|
||||
if (*(int *)net_message.data == -1)
|
||||
if (*(unsigned int *)net_message.data == ~0)
|
||||
{
|
||||
banreason = SV_BannedReason (&net_from);
|
||||
if (banreason)
|
||||
|
@ -4127,22 +4155,39 @@ void SV_CheckTimeouts (void)
|
|||
{
|
||||
if (cl->istobeloaded)
|
||||
{
|
||||
if (cl->istobeloaded == 1)
|
||||
if (1)//svs.gametype != GT_PROGS)
|
||||
{
|
||||
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, cl->edict);
|
||||
PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientDisconnect);
|
||||
cl->netchan.remote_address.type = NA_INVALID; //make it look like a bot.
|
||||
if (cl->istobeloaded == 1)
|
||||
cl->state = cs_spawned; //client has an entity, apparently.
|
||||
else
|
||||
cl->state = cs_connected; //not actually on yet
|
||||
cl->istobeloaded = false;
|
||||
if (*cl->name)
|
||||
SV_BroadcastTPrintf (PRINT_HIGH, "LoadZombie %s timed out\n", cl->name);
|
||||
else
|
||||
SV_BroadcastTPrintf (PRINT_HIGH, "LoadZombie timed out\n");
|
||||
SV_DropClient (cl);
|
||||
}
|
||||
sv.spawned_client_slots--;
|
||||
else
|
||||
{
|
||||
if (cl->istobeloaded == 1)
|
||||
{
|
||||
pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, cl->edict);
|
||||
PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientDisconnect);
|
||||
if (*cl->name)
|
||||
SV_BroadcastTPrintf (PRINT_HIGH, "LoadZombie %s timed out\n", cl->name);
|
||||
else
|
||||
SV_BroadcastTPrintf (PRINT_HIGH, "LoadZombie timed out\n");
|
||||
}
|
||||
sv.spawned_client_slots--;
|
||||
|
||||
cl->istobeloaded=false;
|
||||
cl->istobeloaded=false;
|
||||
|
||||
//must go through a zombie phase for 2 secs when the zombie gets removed.
|
||||
cl->state = cs_zombie; // the real zombieness starts now
|
||||
cl->connection_started = realtime;
|
||||
//must go through a zombie phase for 2 secs when the zombie gets removed.
|
||||
cl->state = cs_zombie; // the real zombieness starts now
|
||||
cl->connection_started = realtime;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -4212,7 +4257,7 @@ int SV_RateForClient(client_t *cl)
|
|||
rate = cl->rate;
|
||||
if (sv_maxrate.ival)
|
||||
{
|
||||
if (rate > sv_maxrate.value)
|
||||
if (!rate || rate > sv_maxrate.value)
|
||||
rate = sv_maxrate.value;
|
||||
else if (rate < MINRATE)
|
||||
rate = MINRATE;
|
||||
|
@ -4407,7 +4452,7 @@ float SV_Frame (void)
|
|||
/*server is effectively paused if there are no clients*/
|
||||
// if (sv.spawned_client_slots == 0 && sv.spawned_observer_slots == 0 && (cls.state != ca_connected))
|
||||
// isidle = true;
|
||||
if ((sv.paused & 4) != (isidle?4:0))
|
||||
if ((sv.paused & 4) != ((isidle||(sv.spawned_client_slots==0&&!deathmatch.ival))?4:0))
|
||||
sv.paused ^= 4;
|
||||
#endif
|
||||
|
||||
|
@ -4712,8 +4757,6 @@ void SV_InitLocal (void)
|
|||
|
||||
//arguably cheats. Must be switched on to use.
|
||||
Cvar_Register (&watervis, cvargroup_serverinfo);
|
||||
Cvar_Register (&rearview, cvargroup_serverinfo);
|
||||
Cvar_Register (&mirrors, cvargroup_serverinfo);
|
||||
Cvar_Register (&allow_skybox, cvargroup_serverinfo);
|
||||
Cvar_Register (&sv_allow_splitscreen, cvargroup_serverinfo);
|
||||
Cvar_Register (&fbskins, cvargroup_serverinfo);
|
||||
|
@ -5058,27 +5101,38 @@ void SV_ExtractFromUserinfo (client_t *cl, qboolean verbose)
|
|||
{
|
||||
if (!(cl->penalties & BAN_STEALTH))
|
||||
SV_ClientTPrintf (cl, PRINT_HIGH, "Muted players may not change their names\n");
|
||||
|
||||
Q_strncpyz (newname, cl->name, sizeof(newname));
|
||||
}
|
||||
else
|
||||
|
||||
if (!sv.paused && *cl->name)
|
||||
{
|
||||
|
||||
Info_SetValueForKey (cl->userinfo, "name", newname, sizeof(cl->userinfo));
|
||||
if (!sv.paused && *cl->name)
|
||||
if (!cl->lastnametime || realtime - cl->lastnametime > 5)
|
||||
{
|
||||
if (!cl->lastnametime || realtime - cl->lastnametime > 5)
|
||||
{
|
||||
cl->lastnamecount = 0;
|
||||
cl->lastnametime = realtime;
|
||||
}
|
||||
else if (cl->lastnamecount++ > 4 && verbose)
|
||||
{
|
||||
SV_BroadcastTPrintf (PRINT_HIGH, "%s was kicked for name spamming\n", cl->name);
|
||||
SV_ClientTPrintf (cl, PRINT_HIGH, "You were kicked for name spamming\n");
|
||||
SV_DropClient (cl);
|
||||
return;
|
||||
}
|
||||
cl->lastnamecount = 0;
|
||||
cl->lastnametime = realtime;
|
||||
}
|
||||
else if (cl->lastnamecount++ > 4 && verbose)
|
||||
{
|
||||
SV_AutoAddPenalty (cl, BAN_MUTE, 60*5, "Muted for name spam");
|
||||
Q_strncpyz (newname, cl->name, sizeof(newname));
|
||||
}
|
||||
}
|
||||
|
||||
//try and actually set that new name, if it differs from what they asked for.
|
||||
if (strcmp(val, newname))
|
||||
{
|
||||
Info_SetValueForKey (cl->userinfo, "name", newname, sizeof(cl->userinfo));
|
||||
val = Info_ValueForKey (cl->userinfo, "name");
|
||||
if (!*val)
|
||||
{
|
||||
SV_BroadcastTPrintf (PRINT_HIGH, "corrupt userinfo for player %s\n", cl->name);
|
||||
cl->drop = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (strncmp(val, cl->name, sizeof(cl->namebuf)-1))
|
||||
{
|
||||
if (*cl->name && cl->state >= cs_spawned && !cl->spectator && verbose)
|
||||
{
|
||||
SV_BroadcastTPrintf (PRINT_HIGH, "%s changed their name to %s\n", cl->name, newname);
|
||||
|
@ -5098,8 +5152,6 @@ void SV_ExtractFromUserinfo (client_t *cl, qboolean verbose)
|
|||
}
|
||||
}
|
||||
|
||||
Info_SetValueForKey(cl->userinfo, "name", newname, sizeof(cl->userinfo));
|
||||
|
||||
val = Info_ValueForKey (cl->userinfo, "lang");
|
||||
cl->language = *val?TL_FindLanguage(val):svs.language;
|
||||
|
||||
|
@ -5111,7 +5163,7 @@ void SV_ExtractFromUserinfo (client_t *cl, qboolean verbose)
|
|||
if (strlen(val))
|
||||
cl->rate = atoi(val);
|
||||
else
|
||||
cl->rate = ISNQCLIENT(cl)?10000:2500; //an nq client cannot cope with quakeworld's default rate, and typically doesn't have rate set either.
|
||||
cl->rate = 0;//0 means no specific limit, limited only by sv_maxrate.
|
||||
|
||||
val = Info_ValueForKey (cl->userinfo, "dupe");
|
||||
cl->netchan.dupe = bound(0, atoi(val), 5);
|
||||
|
@ -5152,14 +5204,17 @@ void SV_ExtractFromUserinfo (client_t *cl, qboolean verbose)
|
|||
bottom &= 15;
|
||||
if (bottom > 13)
|
||||
bottom = 13;
|
||||
cl->playercolor = top*16 + bottom;
|
||||
if (svs.gametype == GT_PROGS || svs.gametype == GT_Q1QVM)
|
||||
if (cl->playercolor != top*16 + bottom)
|
||||
{
|
||||
if (cl->edict)
|
||||
cl->edict->xv->clientcolors = cl->playercolor;
|
||||
MSG_WriteByte (&sv.nqreliable_datagram, svc_updatecolors);
|
||||
MSG_WriteByte (&sv.nqreliable_datagram, cl-svs.clients);
|
||||
MSG_WriteByte (&sv.nqreliable_datagram, cl->playercolor);
|
||||
cl->playercolor = top*16 + bottom;
|
||||
if (svs.gametype == GT_PROGS || svs.gametype == GT_Q1QVM)
|
||||
{
|
||||
if (cl->edict)
|
||||
cl->edict->xv->clientcolors = cl->playercolor;
|
||||
MSG_WriteByte (&sv.nqreliable_datagram, svc_updatecolors);
|
||||
MSG_WriteByte (&sv.nqreliable_datagram, cl-svs.clients);
|
||||
MSG_WriteByte (&sv.nqreliable_datagram, cl->playercolor);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -5296,6 +5351,7 @@ void SV_Init (quakeparms_t *parms)
|
|||
if (isDedicated)
|
||||
#endif
|
||||
{
|
||||
int manarg;
|
||||
PM_Init ();
|
||||
|
||||
#ifdef PLUGINS
|
||||
|
@ -5305,7 +5361,16 @@ void SV_Init (quakeparms_t *parms)
|
|||
host_initialized = true;
|
||||
|
||||
|
||||
FS_ChangeGame(NULL, true, true);
|
||||
manarg = COM_CheckParm("-manifest");
|
||||
if (manarg && manarg < com_argc-1 && com_argv[manarg+1])
|
||||
{
|
||||
char *man = FS_MallocFile(com_argv[manarg+1], FS_SYSTEM, NULL);
|
||||
|
||||
FS_ChangeGame(FS_Manifest_Parse(NULL, man), true, true);
|
||||
BZ_Free(man);
|
||||
}
|
||||
else
|
||||
FS_ChangeGame(NULL, true, true);
|
||||
|
||||
Cmd_StuffCmds();
|
||||
Cbuf_Execute ();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue