thirtyflightsofloving/server/sv_init.c
Knightmare66 1b4eb2b6d7 Refactored server, qcommon, and Win32 code to use cvar integer field where applicable.
Commented out removal of "sethud" command in cl_screen.c.
2021-03-05 16:46:22 -05:00

530 lines
14 KiB
C

/*
===========================================================================
Copyright (C) 1997-2001 Id Software, Inc.
This file is part of Quake 2 source code.
Quake 2 source code 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.
Quake 2 source code 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 Quake 2 source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "server.h"
server_static_t svs; // persistant server info
server_t sv; // local server
/*
================
SV_FindIndex
================
*/
int SV_FindIndex (char *name, int start, int max, qboolean create)
{
int i;
if (!name || !name[0])
return 0;
for (i = 1; i < max && sv.configstrings[start+i][0]; i++)
if (!strcmp(sv.configstrings[start+i], name))
return i;
if (!create)
return 0;
// Knightmare 12/23/2001
// Output a more useful error message to tell user what overflowed
// And don't bomb out, either- instead, return last possible index
if (i == max)
// Com_Error (ERR_DROP, "*Index: overflow");
{
if (start == CS_MODELS)
Com_Printf (S_COLOR_YELLOW"Warning: Index overflow for models\n");
else if (start == CS_SOUNDS)
Com_Printf (S_COLOR_YELLOW"Warning: Index overflow for sounds\n");
else if (start == CS_IMAGES)
Com_Printf (S_COLOR_YELLOW"Warning: Index overflow for images\n");
return (max-1); // return the last possible index
}
// end Knightmare
strncpy (sv.configstrings[start+i], name, sizeof(sv.configstrings[i]));
if (sv.state != ss_loading)
{ // send the update to everyone
SZ_Clear (&sv.multicast);
MSG_WriteChar (&sv.multicast, svc_configstring);
MSG_WriteShort (&sv.multicast, start+i);
MSG_WriteString (&sv.multicast, name);
SV_Multicast (vec3_origin, MULTICAST_ALL_R);
}
return i;
}
int SV_ModelIndex (char *name)
{
return SV_FindIndex (name, CS_MODELS, MAX_MODELS, true);
}
int SV_SoundIndex (char *name)
{
return SV_FindIndex (name, CS_SOUNDS, MAX_SOUNDS, true);
}
int SV_ImageIndex (char *name)
{
return SV_FindIndex (name, CS_IMAGES, MAX_IMAGES, true);
}
/*
================
SV_CreateBaseline
Entity baselines are used to compress the update messages
to the clients -- only the fields that differ from the
baseline will be transmitted
================
*/
void SV_CreateBaseline (void)
{
edict_t *svent;
int entnum;
for (entnum = 1; entnum < ge->num_edicts ; entnum++)
{
svent = EDICT_NUM(entnum);
if (!svent->inuse)
continue;
if (!svent->s.modelindex && !svent->s.sound && !svent->s.effects)
continue;
svent->s.number = entnum;
//
// take current state as baseline
//
VectorCopy (svent->s.origin, svent->s.old_origin);
sv.baselines[entnum] = svent->s;
}
}
/*
=================
SV_CheckForSavegame
=================
*/
void SV_CheckForSavegame (void)
{
char name[MAX_OSPATH];
FILE *f;
int i;
// if (sv_noreload->value)
if (sv_noreload->integer)
return;
if (Cvar_VariableValue ("deathmatch"))
return;
// Com_sprintf (name, sizeof(name), "%s/save/current/%s.sav", FS_Savegamedir(), sv.name); // was FS_Gamedir()
Com_sprintf (name, sizeof(name), "%s/"SAVEDIRNAME"/current/%s.sav", FS_Savegamedir(), sv.name); // was FS_Gamedir()
f = fopen (name, "rb");
if (!f)
#ifdef COMPRESSED_SAVEGAMES
{
// Com_sprintf (name, sizeof(name), "%s/save/current/%s.savz", FS_Savegamedir(), sv.name); // was FS_Gamedir()
Com_sprintf (name, sizeof(name), "%s/"SAVEDIRNAME"/current/%s.savz", FS_Savegamedir(), sv.name); // was FS_Gamedir()
f = fopen (name, "rb");
if (!f)
return; // no savegame
}
#else // COMPRESSED_SAVEGAMES
return; // no savegame
#endif // COMPRESSED_SAVEGAMES
fclose (f);
SV_ClearWorld ();
// get configstrings and areaportals
SV_ReadLevelFile ();
if (!sv.loadgame)
{ // coming back to a level after being in a different
// level, so run it for ten seconds
// rlava2 was sending too many lightstyles, and overflowing the
// reliable data. temporarily changing the server state to loading
// prevents these from being passed down.
server_state_t previousState; // PGM
previousState = sv.state; // PGM
sv.state = ss_loading; // PGM
for (i=0 ; i<100 ; i++)
ge->RunFrame ();
sv.state = previousState; // PGM
}
}
/*
================
SV_SpawnServer
Change the server to a new map, taking all connected
clients along with it.
================
*/
void SV_SpawnServer (char *server, char *spawnpoint, server_state_t serverstate, qboolean attractloop, qboolean loadgame)
{
int i;
unsigned checksum;
// fileHandle_t f;
if (attractloop)
Cvar_Set ("paused", "0");
Com_Printf ("------- Server Initialization -------\n");
Com_DPrintf ("SpawnServer: %s\n",server);
if (sv.demofile)
FS_FCloseFile (sv.demofile);
svs.spawncount++; // any partially connected client will be
// restarted
sv.state = ss_dead;
Com_SetServerState (sv.state);
// wipe the entire per-level structure
memset (&sv, 0, sizeof(sv));
svs.realtime = 0;
sv.loadgame = loadgame;
sv.attractloop = attractloop;
// save name for levels that don't set message
// strncpy (sv.configstrings[CS_NAME], server);
Q_strncpyz (sv.configstrings[CS_NAME], sizeof(sv.configstrings[CS_NAME]), server);
if (Cvar_VariableValue ("deathmatch"))
{
Com_sprintf(sv.configstrings[CS_AIRACCEL], sizeof(sv.configstrings[CS_AIRACCEL]), "%g", sv_airaccelerate->value);
pm_airaccelerate = sv_airaccelerate->value;
}
else
{
// strncpy(sv.configstrings[CS_AIRACCEL], "0");
Q_strncpyz(sv.configstrings[CS_AIRACCEL], sizeof(sv.configstrings[CS_AIRACCEL]), "0");
pm_airaccelerate = 0;
}
SZ_Init (&sv.multicast, sv.multicast_buf, sizeof(sv.multicast_buf));
// strncpy (sv.name, server);
Q_strncpyz (sv.name, sizeof(sv.name), server);
// leave slots at start for clients only
// for (i=0 ; i<maxclients->value ; i++)
for (i=0 ; i<maxclients->integer ; i++)
{
// needs to reconnect
if (svs.clients[i].state > cs_connected)
svs.clients[i].state = cs_connected;
svs.clients[i].lastframe = -1;
}
sv.time = 1000;
// strncpy (sv.name, server);
// strncpy (sv.configstrings[CS_NAME], server);
Q_strncpyz (sv.name, sizeof(sv.name), server);
Q_strncpyz (sv.configstrings[CS_NAME], sizeof(sv.configstrings[CS_NAME]), server);
if (serverstate != ss_game)
{
sv.models[1] = CM_LoadMap ("", false, &checksum); // no real map
}
else
{
Com_sprintf (sv.configstrings[CS_MODELS+1],sizeof(sv.configstrings[CS_MODELS+1]),
"maps/%s.bsp", server);
// resolve CS_PAKFILE, hack by Jay Dolan
// FS_FOpenFile(sv.configstrings[CS_MODELS + 1], &f, FS_READ);
FS_FileExists (sv.configstrings[CS_MODELS + 1]);
if (!file_from_protected_pak) { // protected pak check from Yamagi Q2
// strncpy(sv.configstrings[CS_PAKFILE], (last_pk3_name ? last_pk3_name : ""));
Q_strncpyz(sv.configstrings[CS_PAKFILE], sizeof(sv.configstrings[CS_PAKFILE]), ((strlen(last_pk3_name) > 0) ? last_pk3_name : ""));
// Com_Printf ("SV_SpawnServer: CS_PAKFILE set to %s\n", last_pk3_name);
// FS_FCloseFile(f);
}
sv.models[1] = CM_LoadMap (sv.configstrings[CS_MODELS+1], false, &checksum);
}
Com_sprintf (sv.configstrings[CS_MAPCHECKSUM],sizeof(sv.configstrings[CS_MAPCHECKSUM]),
"%i", checksum);
//
// clear physics interaction links
//
SV_ClearWorld ();
for (i=1 ; i< CM_NumInlineModels() ; i++)
{
Com_sprintf (sv.configstrings[CS_MODELS+1+i], sizeof(sv.configstrings[CS_MODELS+1+i]),
"*%i", i);
sv.models[i+1] = CM_InlineModel (sv.configstrings[CS_MODELS+1+i]);
}
//
// spawn the rest of the entities on the map
//
// precache and static commands can be issued during
// map initialization
sv.state = ss_loading;
Com_SetServerState (sv.state);
// load and spawn all other entities
ge->SpawnEntities ( sv.name, CM_EntityString(), spawnpoint );
// run two frames to allow everything to settle
ge->RunFrame ();
ge->RunFrame ();
// all precaches are complete
sv.state = serverstate;
Com_SetServerState (sv.state);
// create a baseline for more efficient communications
SV_CreateBaseline ();
// check for a savegame
SV_CheckForSavegame ();
// set serverinfo variable
Cvar_FullSet ("mapname", sv.name, CVAR_SERVERINFO | CVAR_NOSET);
Com_Printf ("-------------------------------------\n");
}
/*
==============
SV_InitGame
A brand new game has been started
==============
*/
void PF_Configstring (int index, char *val);
void SV_InitGame (void)
{
int i;
edict_t *ent;
char idmaster[32];
if (svs.initialized)
{
// cause any connected clients to reconnect
SV_Shutdown ("Server restarted\n", true);
}
else
{
// make sure the client is down
CL_Drop ();
SCR_BeginLoadingPlaque ();
}
// get any latched variable changes (maxclients, etc)
Cvar_GetLatchedVars ();
svs.initialized = true;
if (Cvar_VariableValue ("coop") && Cvar_VariableValue ("deathmatch"))
{
Com_Printf("Deathmatch and Coop both set, disabling Coop\n");
Cvar_FullSet ("coop", "0", CVAR_SERVERINFO | CVAR_LATCH);
}
// dedicated servers can't be single player and are usually DM
// so unless they explicity set coop, force it to deathmatch
// if (dedicated->value)
if (dedicated->integer)
{
if (!Cvar_VariableValue ("coop"))
Cvar_FullSet ("deathmatch", "1", CVAR_SERVERINFO | CVAR_LATCH);
}
// init clients
if (Cvar_VariableValue ("deathmatch"))
{
// if (maxclients->value <= 1)
if (maxclients->integer <= 1)
Cvar_FullSet ("maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH);
// else if (maxclients->value > MAX_CLIENTS)
else if (maxclients->integer > MAX_CLIENTS)
Cvar_FullSet ("maxclients", va("%i", MAX_CLIENTS), CVAR_SERVERINFO | CVAR_LATCH);
}
else if (Cvar_VariableValue ("coop"))
{
// if (maxclients->value <= 1 || maxclients->value > 4)
if (maxclients->integer <= 1 || maxclients->integer > 4)
Cvar_FullSet ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH);
}
else // non-deathmatch, non-coop is one player
{
Cvar_FullSet ("maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH);
}
svs.spawncount = rand();
// svs.clients = Z_Malloc (sizeof(client_t)*maxclients->value);
svs.clients = Z_Malloc (sizeof(client_t) * maxclients->integer);
// svs.num_client_entities = maxclients->value*UPDATE_BACKUP*64;
svs.num_client_entities = maxclients->integer * UPDATE_BACKUP * 64;
svs.client_entities = Z_Malloc (sizeof(entity_state_t)*svs.num_client_entities);
// init network stuff
// NET_Config ( (maxclients->value > 1) );
NET_Config ( (maxclients->integer > 1) );
// heartbeats will always be sent to the id master
svs.last_heartbeat = -99999; // send immediately
Com_sprintf(idmaster, sizeof(idmaster), "192.246.40.37:%i", PORT_MASTER);
NET_StringToAdr (idmaster, &master_adr[0]);
// init game
SV_InitGameProgs ();
// for (i = 0; i < maxclients->value; i++)
for (i = 0; i < maxclients->integer; i++)
{
ent = EDICT_NUM(i+1);
ent->s.number = i+1;
svs.clients[i].edict = ent;
memset (&svs.clients[i].lastcmd, 0, sizeof(svs.clients[i].lastcmd));
}
}
/*
======================
SV_Map
the full syntax is:
map [*]<map>$<startspot>+<nextserver>
command from the console or progs.
Map can also be a.cin, .pcx, or .dm2 file
Nextserver is used to allow a cinematic to play, then proceed to
another level:
map tram.cin+jail_e3
======================
*/
void SV_Map (qboolean attractloop, char *levelstring, qboolean loadgame)
{
char level[MAX_QPATH];
char *ch;
int l;
char spawnpoint[MAX_QPATH];
sv.loadgame = loadgame;
sv.attractloop = attractloop;
if (sv.state == ss_dead && !sv.loadgame)
SV_InitGame (); // the game is just starting
// r1ch fix: buffer overflow
// strncpy (level, levelstring);
if (levelstring[0] == '*')
Q_strncpyz (level, sizeof(level), levelstring+1);
else
Q_strncpyz (level, sizeof(level), levelstring);
// if there is a + in the map, set nextserver to the remainder
ch = strstr(level, "+");
if (ch)
{
*ch = 0;
Cvar_Set ("nextserver", va("gamemap \"%s\"", ch+1));
}
else
Cvar_Set ("nextserver", "");
//ZOID special hack for end game screen in coop mode
if (Cvar_VariableValue ("coop") && !Q_stricmp(level, "victory.pcx"))
Cvar_Set ("nextserver", "gamemap \"*base1\"");
// if there is a $, use the remainder as a spawnpoint
ch = strstr(level, "$");
if (ch)
{
*ch = 0;
// strncpy (spawnpoint, ch+1);
Q_strncpyz (spawnpoint, sizeof(spawnpoint), ch+1);
}
else
spawnpoint[0] = 0;
// skip the end-of-unit flag if necessary
// if (level[0] == '*')
// strncpy (level, level+1);
l = (int)strlen(level);
#ifdef ROQ_SUPPORT
if (l > 4 && (!strcmp (level+l-4, ".cin") || !strcmp (level+l-4, ".roq")) )
#else
if (l > 4 && !strcmp (level+l-4, ".cin") )
#endif // ROQ_SUPPORT
{
// if (!dedicated->value)
if (!dedicated->integer)
SCR_BeginLoadingPlaque (); // for local system
SV_BroadcastCommand ("changing\n");
SV_SpawnServer (level, spawnpoint, ss_cinematic, attractloop, loadgame);
}
else if (l > 4 && !strcmp (level+l-4, ".dm2") )
{
// if (!dedicated->value)
if (!dedicated->integer)
SCR_BeginLoadingPlaque (); // for local system
SV_BroadcastCommand ("changing\n");
SV_SpawnServer (level, spawnpoint, ss_demo, attractloop, loadgame);
}
else if (l > 4 && !strcmp (level+l-4, ".pcx"))
{
// if (!dedicated->value)
if (!dedicated->integer)
SCR_BeginLoadingPlaque (); // for local system
SV_BroadcastCommand ("changing\n");
SV_SpawnServer (level, spawnpoint, ss_pic, attractloop, loadgame);
}
else
{
// if (!dedicated->value)
if (!dedicated->integer)
SCR_BeginLoadingPlaque (); // for local system
SV_BroadcastCommand ("changing\n");
SV_SendClientMessages ();
SV_SpawnServer (level, spawnpoint, ss_game, attractloop, loadgame);
Cbuf_CopyToDefer ();
}
SV_BroadcastCommand ("reconnect\n");
}