0,0 → 1,958 |
/* |
Copyright (C) 1996-1997 Id Software, Inc. |
|
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. |
|
*/ |
// host.c -- coordinates spawning and killing of local servers |
|
#include "quakedef.h" |
#include "r_local.h" |
|
/* |
|
A server can allways be started, even if the system started out as a client |
to a remote system. |
|
A client can NOT be started if the system started as a dedicated server. |
|
Memory is cleared / released when a server or client begins, not when they end. |
|
*/ |
|
quakeparms_t host_parms; |
|
qboolean host_initialized; // true if into command execution |
|
double host_frametime; |
double host_time; |
double realtime; // without any filtering or bounding |
double oldrealtime; // last frame run |
int host_framecount; |
|
int host_hunklevel; |
|
int minimum_memory; |
|
client_t *host_client; // current client |
|
jmp_buf host_abortserver; |
|
byte *host_basepal; |
byte *host_colormap; |
|
cvar_t host_framerate = {"host_framerate","0"}; // set for slow motion |
cvar_t host_speeds = {"host_speeds","0"}; // set for running times |
|
cvar_t sys_ticrate = {"sys_ticrate","0.05"}; |
cvar_t serverprofile = {"serverprofile","0"}; |
|
cvar_t fraglimit = {"fraglimit","0",false,true}; |
cvar_t timelimit = {"timelimit","0",false,true}; |
cvar_t teamplay = {"teamplay","0",false,true}; |
|
cvar_t samelevel = {"samelevel","0"}; |
cvar_t noexit = {"noexit","0",false,true}; |
|
#ifdef QUAKE2 |
cvar_t developer = {"developer","1"}; // should be 0 for release! |
#else |
cvar_t developer = {"developer","0"}; |
#endif |
|
cvar_t skill = {"skill","1"}; // 0 - 3 |
cvar_t deathmatch = {"deathmatch","0"}; // 0, 1, or 2 |
cvar_t coop = {"coop","0"}; // 0 or 1 |
|
cvar_t pausable = {"pausable","1"}; |
|
cvar_t temp1 = {"temp1","0"}; |
|
|
/* |
================ |
Host_EndGame |
================ |
*/ |
void Host_EndGame (char *message, ...) |
{ |
va_list argptr; |
char string[1024]; |
|
va_start (argptr,message); |
vsprintf (string,message,argptr); |
va_end (argptr); |
Con_DPrintf ("Host_EndGame: %s\n",string); |
|
if (sv.active) |
Host_ShutdownServer (false); |
|
if (cls.state == ca_dedicated) |
Sys_Error ("Host_EndGame: %s\n",string); // dedicated servers exit |
|
if (cls.demonum != -1) |
CL_NextDemo (); |
else |
CL_Disconnect (); |
|
longjmp (host_abortserver, 1); |
} |
|
/* |
================ |
Host_Error |
|
This shuts down both the client and server |
================ |
*/ |
void Host_Error (char *error, ...) |
{ |
va_list argptr; |
char string[1024]; |
static qboolean inerror = false; |
|
if (inerror) |
Sys_Error ("Host_Error: recursively entered"); |
inerror = true; |
|
SCR_EndLoadingPlaque (); // reenable screen updates |
|
va_start (argptr,error); |
vsprintf (string,error,argptr); |
va_end (argptr); |
Con_Printf ("Host_Error: %s\n",string); |
|
if (sv.active) |
Host_ShutdownServer (false); |
|
if (cls.state == ca_dedicated) |
Sys_Error ("Host_Error: %s\n",string); // dedicated servers exit |
|
CL_Disconnect (); |
cls.demonum = -1; |
|
inerror = false; |
|
longjmp (host_abortserver, 1); |
} |
|
/* |
================ |
Host_FindMaxClients |
================ |
*/ |
void Host_FindMaxClients (void) |
{ |
int i; |
|
svs.maxclients = 1; |
|
i = COM_CheckParm ("-dedicated"); |
if (i) |
{ |
cls.state = ca_dedicated; |
if (i != (com_argc - 1)) |
{ |
svs.maxclients = Q_atoi (com_argv[i+1]); |
} |
else |
svs.maxclients = 8; |
} |
else |
cls.state = ca_disconnected; |
|
i = COM_CheckParm ("-listen"); |
if (i) |
{ |
if (cls.state == ca_dedicated) |
Sys_Error ("Only one of -dedicated or -listen can be specified"); |
if (i != (com_argc - 1)) |
svs.maxclients = Q_atoi (com_argv[i+1]); |
else |
svs.maxclients = 8; |
} |
if (svs.maxclients < 1) |
svs.maxclients = 8; |
else if (svs.maxclients > MAX_SCOREBOARD) |
svs.maxclients = MAX_SCOREBOARD; |
|
svs.maxclientslimit = svs.maxclients; |
if (svs.maxclientslimit < 4) |
svs.maxclientslimit = 4; |
svs.clients = Hunk_AllocName (svs.maxclientslimit*sizeof(client_t), "clients"); |
|
if (svs.maxclients > 1) |
Cvar_SetValue ("deathmatch", 1.0); |
else |
Cvar_SetValue ("deathmatch", 0.0); |
} |
|
|
/* |
======================= |
Host_InitLocal |
====================== |
*/ |
void Host_InitLocal (void) |
{ |
Host_InitCommands (); |
|
Cvar_RegisterVariable (&host_framerate); |
Cvar_RegisterVariable (&host_speeds); |
|
Cvar_RegisterVariable (&sys_ticrate); |
Cvar_RegisterVariable (&serverprofile); |
|
Cvar_RegisterVariable (&fraglimit); |
Cvar_RegisterVariable (&timelimit); |
Cvar_RegisterVariable (&teamplay); |
Cvar_RegisterVariable (&samelevel); |
Cvar_RegisterVariable (&noexit); |
Cvar_RegisterVariable (&skill); |
Cvar_RegisterVariable (&developer); |
Cvar_RegisterVariable (&deathmatch); |
Cvar_RegisterVariable (&coop); |
|
Cvar_RegisterVariable (&pausable); |
|
Cvar_RegisterVariable (&temp1); |
|
Host_FindMaxClients (); |
|
host_time = 1.0; // so a think at time 0 won't get called |
} |
|
|
/* |
=============== |
Host_WriteConfiguration |
|
Writes key bindings and archived cvars to config.cfg |
=============== |
*/ |
void Host_WriteConfiguration (void) |
{ |
FILE *f; |
|
// dedicated servers initialize the host but don't parse and set the |
// config.cfg cvars |
if (host_initialized & !isDedicated) |
{ |
f = fopen (va("%s/config.cfg",com_gamedir), "w"); |
if (!f) |
{ |
Con_Printf ("Couldn't write config.cfg.\n"); |
return; |
} |
|
Key_WriteBindings (f); |
Cvar_WriteVariables (f); |
|
fclose (f); |
} |
} |
|
|
/* |
================= |
SV_ClientPrintf |
|
Sends text across to be displayed |
FIXME: make this just a stuffed echo? |
================= |
*/ |
void SV_ClientPrintf (char *fmt, ...) |
{ |
va_list argptr; |
char string[1024]; |
|
va_start (argptr,fmt); |
vsprintf (string, fmt,argptr); |
va_end (argptr); |
|
MSG_WriteByte (&host_client->message, svc_print); |
MSG_WriteString (&host_client->message, string); |
} |
|
/* |
================= |
SV_BroadcastPrintf |
|
Sends text to all active clients |
================= |
*/ |
void SV_BroadcastPrintf (char *fmt, ...) |
{ |
va_list argptr; |
char string[1024]; |
int i; |
|
va_start (argptr,fmt); |
vsprintf (string, fmt,argptr); |
va_end (argptr); |
|
for (i=0 ; i<svs.maxclients ; i++) |
if (svs.clients[i].active && svs.clients[i].spawned) |
{ |
MSG_WriteByte (&svs.clients[i].message, svc_print); |
MSG_WriteString (&svs.clients[i].message, string); |
} |
} |
|
/* |
================= |
Host_ClientCommands |
|
Send text over to the client to be executed |
================= |
*/ |
void Host_ClientCommands (char *fmt, ...) |
{ |
va_list argptr; |
char string[1024]; |
|
va_start (argptr,fmt); |
vsprintf (string, fmt,argptr); |
va_end (argptr); |
|
MSG_WriteByte (&host_client->message, svc_stufftext); |
MSG_WriteString (&host_client->message, string); |
} |
|
/* |
===================== |
SV_DropClient |
|
Called when the player is getting totally kicked off the host |
if (crash = true), don't bother sending signofs |
===================== |
*/ |
void SV_DropClient (qboolean crash) |
{ |
int saveSelf; |
int i; |
client_t *client; |
|
if (!crash) |
{ |
// send any final messages (don't check for errors) |
if (NET_CanSendMessage (host_client->netconnection)) |
{ |
MSG_WriteByte (&host_client->message, svc_disconnect); |
NET_SendMessage (host_client->netconnection, &host_client->message); |
} |
|
if (host_client->edict && host_client->spawned) |
{ |
// call the prog function for removing a client |
// this will set the body to a dead frame, among other things |
saveSelf = pr_global_struct->self; |
pr_global_struct->self = EDICT_TO_PROG(host_client->edict); |
PR_ExecuteProgram (pr_global_struct->ClientDisconnect); |
pr_global_struct->self = saveSelf; |
} |
|
Sys_Printf ("Client %s removed\n",host_client->name); |
} |
|
// break the net connection |
NET_Close (host_client->netconnection); |
host_client->netconnection = NULL; |
|
// free the client (the body stays around) |
host_client->active = false; |
host_client->name[0] = 0; |
host_client->old_frags = -999999; |
net_activeconnections--; |
|
// send notification to all clients |
for (i=0, client = svs.clients ; i<svs.maxclients ; i++, client++) |
{ |
if (!client->active) |
continue; |
MSG_WriteByte (&client->message, svc_updatename); |
MSG_WriteByte (&client->message, host_client - svs.clients); |
MSG_WriteString (&client->message, ""); |
MSG_WriteByte (&client->message, svc_updatefrags); |
MSG_WriteByte (&client->message, host_client - svs.clients); |
MSG_WriteShort (&client->message, 0); |
MSG_WriteByte (&client->message, svc_updatecolors); |
MSG_WriteByte (&client->message, host_client - svs.clients); |
MSG_WriteByte (&client->message, 0); |
} |
} |
|
/* |
================== |
Host_ShutdownServer |
|
This only happens at the end of a game, not between levels |
================== |
*/ |
void Host_ShutdownServer(qboolean crash) |
{ |
int i; |
int count; |
sizebuf_t buf; |
char message[4]; |
double start; |
|
if (!sv.active) |
return; |
|
sv.active = false; |
|
// stop all client sounds immediately |
if (cls.state == ca_connected) |
CL_Disconnect (); |
|
// flush any pending messages - like the score!!! |
start = Sys_FloatTime(); |
do |
{ |
count = 0; |
for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++) |
{ |
if (host_client->active && host_client->message.cursize) |
{ |
if (NET_CanSendMessage (host_client->netconnection)) |
{ |
NET_SendMessage(host_client->netconnection, &host_client->message); |
SZ_Clear (&host_client->message); |
} |
else |
{ |
NET_GetMessage(host_client->netconnection); |
count++; |
} |
} |
} |
if ((Sys_FloatTime() - start) > 3.0) |
break; |
} |
while (count); |
|
// make sure all the clients know we're disconnecting |
buf.data = message; |
buf.maxsize = 4; |
buf.cursize = 0; |
MSG_WriteByte(&buf, svc_disconnect); |
count = NET_SendToAll(&buf, 5); |
if (count) |
Con_Printf("Host_ShutdownServer: NET_SendToAll failed for %u clients\n", count); |
|
for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++) |
if (host_client->active) |
SV_DropClient(crash); |
|
// |
// clear structures |
// |
memset (&sv, 0, sizeof(sv)); |
memset (svs.clients, 0, svs.maxclientslimit*sizeof(client_t)); |
} |
|
|
/* |
================ |
Host_ClearMemory |
|
This clears all the memory used by both the client and server, but does |
not reinitialize anything. |
================ |
*/ |
void Host_ClearMemory (void) |
{ |
Con_DPrintf ("Clearing memory\n"); |
D_FlushCaches (); |
Mod_ClearAll (); |
if (host_hunklevel) |
Hunk_FreeToLowMark (host_hunklevel); |
|
cls.signon = 0; |
memset (&sv, 0, sizeof(sv)); |
memset (&cl, 0, sizeof(cl)); |
} |
|
|
//============================================================================ |
|
|
/* |
=================== |
Host_FilterTime |
|
Returns false if the time is too short to run a frame |
=================== |
*/ |
qboolean Host_FilterTime (float time) |
{ |
realtime += time; |
|
if (!cls.timedemo && realtime - oldrealtime < 1.0/72.0) |
return false; // framerate is too high |
|
host_frametime = realtime - oldrealtime; |
oldrealtime = realtime; |
|
if (host_framerate.value > 0) |
host_frametime = host_framerate.value; |
else |
{ // don't allow really long or short frames |
if (host_frametime > 0.1) |
host_frametime = 0.1; |
if (host_frametime < 0.001) |
host_frametime = 0.001; |
} |
|
return true; |
} |
|
|
/* |
=================== |
Host_GetConsoleCommands |
|
Add them exactly as if they had been typed at the console |
=================== |
*/ |
void Host_GetConsoleCommands (void) |
{ |
char *cmd; |
|
while (1) |
{ |
cmd = Sys_ConsoleInput (); |
if (!cmd) |
break; |
Cbuf_AddText (cmd); |
} |
} |
|
|
/* |
================== |
Host_ServerFrame |
|
================== |
*/ |
#ifdef FPS_20 |
|
void _Host_ServerFrame (void) |
{ |
// run the world state |
pr_global_struct->frametime = host_frametime; |
|
// read client messages |
SV_RunClients (); |
|
// 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 (); |
} |
|
void Host_ServerFrame (void) |
{ |
float save_host_frametime; |
float temp_host_frametime; |
|
// run the world state |
pr_global_struct->frametime = host_frametime; |
|
// set the time and clear the general datagram |
SV_ClearDatagram (); |
|
// check for new clients |
SV_CheckForNewClients (); |
|
temp_host_frametime = save_host_frametime = host_frametime; |
while(temp_host_frametime > (1.0/72.0)) |
{ |
if (temp_host_frametime > 0.05) |
host_frametime = 0.05; |
else |
host_frametime = temp_host_frametime; |
temp_host_frametime -= host_frametime; |
_Host_ServerFrame (); |
} |
host_frametime = save_host_frametime; |
|
// send all messages to the clients |
SV_SendClientMessages (); |
} |
|
#else |
|
void Host_ServerFrame (void) |
{ |
// run the world state |
pr_global_struct->frametime = host_frametime; |
|
// set the time and clear the general datagram |
SV_ClearDatagram (); |
|
// check for new clients |
SV_CheckForNewClients (); |
|
// read client messages |
SV_RunClients (); |
|
// 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 (); |
|
// send all messages to the clients |
SV_SendClientMessages (); |
} |
|
#endif |
|
|
/* |
================== |
Host_Frame |
|
Runs all active servers |
================== |
*/ |
void _Host_Frame (float time) |
{ |
static double time1 = 0; |
static double time2 = 0; |
static double time3 = 0; |
int pass1, pass2, pass3; |
|
if (setjmp (host_abortserver) ) |
return; // something bad happened, or the server disconnected |
|
// keep the random time dependent |
rand (); |
|
// decide the simulation time |
if (!Host_FilterTime (time)) |
return; // don't run too fast, or packets will flood out |
|
// get new key events |
Sys_SendKeyEvents (); |
|
// allow mice or other external controllers to add commands |
IN_Commands (); |
|
// process console commands |
Cbuf_Execute (); |
|
NET_Poll(); |
|
// if running the server locally, make intentions now |
if (sv.active) |
CL_SendCmd (); |
|
//------------------- |
// |
// server operations |
// |
//------------------- |
|
// check for commands typed to the host |
Host_GetConsoleCommands (); |
|
if (sv.active) |
Host_ServerFrame (); |
|
//------------------- |
// |
// client operations |
// |
//------------------- |
|
// if running the server remotely, send intentions now after |
// the incoming messages have been read |
if (!sv.active) |
CL_SendCmd (); |
|
host_time += host_frametime; |
|
// fetch results from server |
if (cls.state == ca_connected) |
{ |
CL_ReadFromServer (); |
} |
|
// update video |
if (host_speeds.value) |
time1 = Sys_FloatTime (); |
|
SCR_UpdateScreen (); |
|
if (host_speeds.value) |
time2 = Sys_FloatTime (); |
|
// update audio |
if (cls.signon == SIGNONS) |
{ |
S_Update (r_origin, vpn, vright, vup); |
CL_DecayLights (); |
} |
else |
S_Update (vec3_origin, vec3_origin, vec3_origin, vec3_origin); |
|
CDAudio_Update(); |
|
if (host_speeds.value) |
{ |
pass1 = (time1 - time3)*1000; |
time3 = Sys_FloatTime (); |
pass2 = (time2 - time1)*1000; |
pass3 = (time3 - time2)*1000; |
Con_Printf ("%3i tot %3i server %3i gfx %3i snd\n", |
pass1+pass2+pass3, pass1, pass2, pass3); |
} |
|
host_framecount++; |
} |
|
void Host_Frame (float time) |
{ |
double time1, time2; |
static double timetotal; |
static int timecount; |
int i, c, m; |
|
if (!serverprofile.value) |
{ |
_Host_Frame (time); |
return; |
} |
|
time1 = Sys_FloatTime (); |
_Host_Frame (time); |
time2 = Sys_FloatTime (); |
|
timetotal += time2 - time1; |
timecount++; |
|
if (timecount < 1000) |
return; |
|
m = timetotal*1000/timecount; |
timecount = 0; |
timetotal = 0; |
c = 0; |
for (i=0 ; i<svs.maxclients ; i++) |
{ |
if (svs.clients[i].active) |
c++; |
} |
|
Con_Printf ("serverprofile: %2i clients %2i msec\n", c, m); |
} |
|
//============================================================================ |
|
|
extern int vcrFile; |
#define VCR_SIGNATURE 0x56435231 |
// "VCR1" |
|
void Host_InitVCR (quakeparms_t *parms) |
{ |
int i, len, n; |
char *p; |
|
if (COM_CheckParm("-playback")) |
{ |
if (com_argc != 2) |
Sys_Error("No other parameters allowed with -playback\n"); |
|
Sys_FileOpenRead("quake.vcr", &vcrFile); |
if (vcrFile == -1) |
Sys_Error("playback file not found\n"); |
|
Sys_FileRead (vcrFile, &i, sizeof(int)); |
if (i != VCR_SIGNATURE) |
Sys_Error("Invalid signature in vcr file\n"); |
|
Sys_FileRead (vcrFile, &com_argc, sizeof(int)); |
com_argv = malloc(com_argc * sizeof(char *)); |
com_argv[0] = parms->argv[0]; |
for (i = 0; i < com_argc; i++) |
{ |
Sys_FileRead (vcrFile, &len, sizeof(int)); |
p = malloc(len); |
Sys_FileRead (vcrFile, p, len); |
com_argv[i+1] = p; |
} |
com_argc++; /* add one for arg[0] */ |
parms->argc = com_argc; |
parms->argv = com_argv; |
} |
|
if ( (n = COM_CheckParm("-record")) != 0) |
{ |
vcrFile = Sys_FileOpenWrite("quake.vcr"); |
|
i = VCR_SIGNATURE; |
Sys_FileWrite(vcrFile, &i, sizeof(int)); |
i = com_argc - 1; |
Sys_FileWrite(vcrFile, &i, sizeof(int)); |
for (i = 1; i < com_argc; i++) |
{ |
if (i == n) |
{ |
len = 10; |
Sys_FileWrite(vcrFile, &len, sizeof(int)); |
Sys_FileWrite(vcrFile, "-playback", len); |
continue; |
} |
len = Q_strlen(com_argv[i]) + 1; |
Sys_FileWrite(vcrFile, &len, sizeof(int)); |
Sys_FileWrite(vcrFile, com_argv[i], len); |
} |
} |
|
} |
|
/* |
==================== |
Host_Init |
==================== |
*/ |
void Host_Init (quakeparms_t *parms) |
{ |
|
if (standard_quake) |
minimum_memory = MINIMUM_MEMORY; |
else |
minimum_memory = MINIMUM_MEMORY_LEVELPAK; |
|
if (COM_CheckParm ("-minmemory")) |
parms->memsize = minimum_memory; |
|
host_parms = *parms; |
|
if (parms->memsize < minimum_memory) |
Sys_Error ("Only %4.1f megs of memory available, can't execute game", parms->memsize / (float)0x100000); |
|
com_argc = parms->argc; |
com_argv = parms->argv; |
|
Memory_Init (parms->membase, parms->memsize); |
Cbuf_Init (); |
Cmd_Init (); |
V_Init (); |
Chase_Init (); |
Host_InitVCR (parms); |
COM_Init (parms->basedir); |
Host_InitLocal (); |
W_LoadWadFile ("gfx.wad"); |
Key_Init (); |
Con_Init (); |
M_Init (); |
PR_Init (); |
Mod_Init (); |
NET_Init (); |
SV_Init (); |
|
Con_Printf ("Exe: "__TIME__" "__DATE__"\n"); |
Con_Printf ("%4.1f megabyte heap\n",parms->memsize/ (1024*1024.0)); |
|
R_InitTextures (); // needed even for dedicated servers |
|
if (cls.state != ca_dedicated) |
{ |
host_basepal = (byte *)COM_LoadHunkFile ("gfx/palette.lmp"); |
if (!host_basepal) |
Sys_Error ("Couldn't load gfx/palette.lmp"); |
host_colormap = (byte *)COM_LoadHunkFile ("gfx/colormap.lmp"); |
if (!host_colormap) |
Sys_Error ("Couldn't load gfx/colormap.lmp"); |
|
#ifndef _WIN32 // on non win32, mouse comes before video for security reasons |
IN_Init (); |
#endif |
VID_Init (host_basepal); |
|
Draw_Init (); |
SCR_Init (); |
R_Init (); |
#ifndef _WIN32 |
// on Win32, sound initialization has to come before video initialization, so we |
// can put up a popup if the sound hardware is in use |
S_Init (); |
#else |
|
#ifdef GLQUAKE |
// FIXME: doesn't use the new one-window approach yet |
S_Init (); |
#endif |
|
#endif // _WIN32 |
CDAudio_Init (); |
Sbar_Init (); |
CL_Init (); |
#ifdef _WIN32 // on non win32, mouse comes before video for security reasons |
IN_Init (); |
#endif |
} |
|
Cbuf_InsertText ("exec quake.rc\n"); |
|
Hunk_AllocName (0, "-HOST_HUNKLEVEL-"); |
host_hunklevel = Hunk_LowMark (); |
|
host_initialized = true; |
|
Sys_Printf ("========Quake Initialized=========\n"); |
} |
|
|
/* |
=============== |
Host_Shutdown |
|
FIXME: this is a callback from Sys_Quit and Sys_Error. It would be better |
to run quit through here before the final handoff to the sys code. |
=============== |
*/ |
void Host_Shutdown(void) |
{ |
static qboolean isdown = false; |
|
if (isdown) |
{ |
printf ("recursive shutdown\n"); |
return; |
} |
isdown = true; |
|
// keep Con_Printf from trying to update the screen |
scr_disabled_for_loading = true; |
|
Host_WriteConfiguration (); |
|
CDAudio_Shutdown (); |
NET_Shutdown (); |
S_Shutdown(); |
IN_Shutdown (); |
|
if (cls.state != ca_dedicated) |
{ |
VID_Shutdown(); |
} |
} |
|