*/
#include "quakedef.h"
+#include "sv_demo.h"
+#include "image.h"
int current_skill;
cvar_t sv_cheats = {0, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"};
cvar_t sv_adminnick = {CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"};
+cvar_t sv_status_privacy = {CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"};
cvar_t rcon_password = {CVAR_PRIVATE, "rcon_password", "", "password to authenticate rcon commands"};
cvar_t rcon_address = {0, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
cvar_t team = {CVAR_USERINFO | CVAR_SAVE, "team", "none", "QW team (4 character limit, example: blue)"};
cvar_t skin = {CVAR_USERINFO | CVAR_SAVE, "skin", "", "QW player skin name (example: base)"};
cvar_t noaim = {CVAR_USERINFO | CVAR_SAVE, "noaim", "1", "QW option to disable vertical autoaim"};
+cvar_t r_fixtrans_auto = {0, "r_fixtrans_auto", "0", "automatically fixtrans textures (when set to 2, it also saves the fixed versions to a fixtrans directory)"};
qboolean allowcheats = false;
extern qboolean host_shuttingdown;
Sys_Quit (0);
}
-
/*
==================
Host_Status_f
print ("version: %s build %s\n", gamename, buildstring);
print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
print ("map: %s\n", sv.name);
+ print ("timing: %s\n", Host_TimingReport());
print ("players: %i active (%i max)\n\n", players, svs.maxclients);
for (j = 0, client = svs.clients;j < svs.maxclients;j++, client++)
{
else
hours = 0;
print ("#%-3u %-16.16s %3i %2i:%02i:%02i\n", j+1, client->name, client->frags, hours, minutes, seconds);
- print (" %s\n", client->netconnection ? client->netconnection->address : "botclient");
+ if(sv_status_privacy.integer && cmd_source != src_command)
+ print (" %s\n", client->netconnection ? "hidden" : "botclient");
+ else
+ print (" %s\n", client->netconnection ? client->netconnection->address : "botclient");
}
}
return;
}
+ // GAME_DELUXEQUAKE - clear warpmark (used by QC)
+ if (gamemode == GAME_DELUXEQUAKE)
+ Cvar_Set("warpmark", "");
+
cls.demonum = -1; // stop demo loop in case this fails
CL_Disconnect ();
#define SAVEGAME_VERSION 5
-/*
-===============
-Host_SavegameComment
-
-Writes a SAVEGAME_COMMENT_LENGTH character comment describing the current
-===============
-*/
-void Host_SavegameComment (char *text)
+void Host_Savegame_to (const char *name)
{
- int i;
- char kills[20];
+ qfile_t *f;
+ int i, lightstyles = 64;
+ char comment[SAVEGAME_COMMENT_LENGTH+1];
+ qboolean isserver;
- for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
- text[i] = ' ';
- // LordHavoc: added min() to prevent overflow
- memcpy (text, cl.levelname, min(strlen(cl.levelname), SAVEGAME_COMMENT_LENGTH));
- sprintf (kills,"kills:%3i/%3i", cl.stats[STAT_MONSTERS], cl.stats[STAT_TOTALMONSTERS]);
- memcpy (text+22, kills, strlen(kills));
+ // first we have to figure out if this can be saved in 64 lightstyles
+ // (for Quake compatibility)
+ for (i=64 ; i<MAX_LIGHTSTYLES ; i++)
+ if (sv.lightstyles[i][0])
+ lightstyles = i+1;
+
+ isserver = !strcmp(PRVM_NAME, "server");
+
+ Con_Printf("Saving game to %s...\n", name);
+ f = FS_OpenRealFile(name, "wb", false);
+ if (!f)
+ {
+ Con_Print("ERROR: couldn't open.\n");
+ return;
+ }
+
+ FS_Printf(f, "%i\n", SAVEGAME_VERSION);
+
+ memset(comment, 0, sizeof(comment));
+ if(isserver)
+ dpsnprintf(comment, sizeof(comment), "%-21.21s kills:%3i/%3i", PRVM_GetString(prog->edicts->fields.server->message), (int)prog->globals.server->killed_monsters, (int)prog->globals.server->total_monsters);
+ else
+ dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", PRVM_NAME);
// convert space to _ to make stdio happy
// LordHavoc: convert control characters to _ as well
for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
- if (text[i] <= ' ')
- text[i] = '_';
- text[SAVEGAME_COMMENT_LENGTH] = '\0';
-}
+ if (comment[i] <= ' ')
+ comment[i] = '_';
+ comment[SAVEGAME_COMMENT_LENGTH] = '\0';
+ FS_Printf(f, "%s\n", comment);
+ if(isserver)
+ {
+ for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
+ FS_Printf(f, "%f\n", svs.clients[0].spawn_parms[i]);
+ FS_Printf(f, "%d\n", current_skill);
+ FS_Printf(f, "%s\n", sv.name);
+ FS_Printf(f, "%f\n",sv.time);
+ }
+ else
+ {
+ for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
+ FS_Printf(f, "(dummy)\n");
+ FS_Printf(f, "%d\n", 0);
+ FS_Printf(f, "%s\n", "(dummy)");
+ FS_Printf(f, "%f\n", realtime);
+ }
+
+ // write the light styles
+ for (i=0 ; i<lightstyles ; i++)
+ {
+ if (isserver && sv.lightstyles[i][0])
+ FS_Printf(f, "%s\n", sv.lightstyles[i]);
+ else
+ FS_Print(f,"m\n");
+ }
+
+ PRVM_ED_WriteGlobals (f);
+ for (i=0 ; i<prog->num_edicts ; i++)
+ {
+ FS_Printf(f,"// edict %d\n", i);
+ //Con_Printf("edict %d...\n", i);
+ PRVM_ED_Write (f, PRVM_EDICT_NUM(i));
+ }
+
+#if 1
+ FS_Printf(f,"/*\n");
+ FS_Printf(f,"// DarkPlaces extended savegame\n");
+ // darkplaces extension - extra lightstyles, support for color lightstyles
+ for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
+ if (isserver && sv.lightstyles[i][0])
+ FS_Printf(f, "sv.lightstyles %i %s\n", i, sv.lightstyles[i]);
+
+ // darkplaces extension - model precaches
+ for (i=1 ; i<MAX_MODELS ; i++)
+ if (sv.model_precache[i][0])
+ FS_Printf(f,"sv.model_precache %i %s\n", i, sv.model_precache[i]);
+
+ // darkplaces extension - sound precaches
+ for (i=1 ; i<MAX_SOUNDS ; i++)
+ if (sv.sound_precache[i][0])
+ FS_Printf(f,"sv.sound_precache %i %s\n", i, sv.sound_precache[i]);
+ FS_Printf(f,"*/\n");
+#endif
+
+ FS_Close (f);
+ Con_Print("done.\n");
+}
/*
===============
void Host_Savegame_f (void)
{
char name[MAX_QPATH];
- qfile_t *f;
- int i;
- char comment[SAVEGAME_COMMENT_LENGTH+1];
- if (cls.state != ca_connected || !sv.active)
+ if (!sv.active)
{
- Con_Print("Not playing a local game.\n");
+ Con_Print("Can't save - no server running.\n");
return;
}
- if (cl.intermission)
+ if (cl.islocalgame)
{
- Con_Print("Can't save in intermission.\n");
- return;
- }
+ // singleplayer checks
+ if (cl.intermission)
+ {
+ Con_Print("Can't save in intermission.\n");
+ return;
+ }
- for (i = 0;i < svs.maxclients;i++)
- {
- if (svs.clients[i].active)
+ if (svs.clients[0].active && svs.clients[0].edict->fields.server->deadflag)
{
- if (i > 0)
- {
- Con_Print("Can't save multiplayer games.\n");
- return;
- }
- if (svs.clients[i].edict->fields.server->deadflag)
- {
- Con_Print("Can't savegame with a dead player\n");
- return;
- }
+ Con_Print("Can't savegame with a dead player\n");
+ return;
}
}
+ else
+ Con_Print("Warning: saving a multiplayer game may have strange results when restored (to properly resume, all players must join in the same player slots and then the game can be reloaded).\n");
if (Cmd_Argc() != 2)
{
strlcpy (name, Cmd_Argv(1), sizeof (name));
FS_DefaultExtension (name, ".sav", sizeof (name));
- Con_Printf("Saving game to %s...\n", name);
- f = FS_Open (name, "wb", false, false);
- if (!f)
- {
- Con_Print("ERROR: couldn't open.\n");
- return;
- }
-
- FS_Printf(f, "%i\n", SAVEGAME_VERSION);
- Host_SavegameComment (comment);
- FS_Printf(f, "%s\n", comment);
- for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
- FS_Printf(f, "%f\n", svs.clients[0].spawn_parms[i]);
- FS_Printf(f, "%d\n", current_skill);
- FS_Printf(f, "%s\n", sv.name);
- FS_Printf(f, "%f\n",sv.time);
-
- // write the light styles
- for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
- {
- if (sv.lightstyles[i][0])
- FS_Printf(f, "%s\n", sv.lightstyles[i]);
- else
- FS_Print(f,"m\n");
- }
-
SV_VM_Begin();
-
- PRVM_ED_WriteGlobals (f);
- for (i=0 ; i<prog->num_edicts ; i++)
- PRVM_ED_Write (f, PRVM_EDICT_NUM(i));
-
+ Host_Savegame_to(name);
SV_VM_End();
-
- FS_Close (f);
- Con_Print("done.\n");
}
char mapname[MAX_QPATH];
float time;
const char *start;
+ const char *end;
const char *t;
- const char *oldt;
char *text;
prvm_edict_t *ent;
int i;
Con_Printf("Loading game from %s...\n", filename);
+ // stop playing demos
+ if (cls.demoplayback)
+ CL_Disconnect ();
+
+ // remove menu
+ key_dest = key_game;
+
cls.demonum = -1; // stop demo loop in case this fails
t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL);
}
// version
- COM_ParseToken_Simple(&t, false);
+ COM_ParseToken_Simple(&t, false, false);
version = atoi(com_token);
if (version != SAVEGAME_VERSION)
{
}
// description
- COM_ParseToken_Simple(&t, false);
+ COM_ParseToken_Simple(&t, false, false);
for (i = 0;i < NUM_SPAWN_PARMS;i++)
{
- COM_ParseToken_Simple(&t, false);
+ COM_ParseToken_Simple(&t, false, false);
spawn_parms[i] = atof(com_token);
}
// skill
- COM_ParseToken_Simple(&t, false);
+ COM_ParseToken_Simple(&t, false, false);
// this silliness is so we can load 1.06 save files, which have float skill values
current_skill = (int)(atof(com_token) + 0.5);
Cvar_SetValue ("skill", (float)current_skill);
// mapname
- COM_ParseToken_Simple(&t, false);
+ COM_ParseToken_Simple(&t, false, false);
strlcpy (mapname, com_token, sizeof(mapname));
// time
- COM_ParseToken_Simple(&t, false);
+ COM_ParseToken_Simple(&t, false, false);
time = atof(com_token);
allowcheats = sv_cheats.integer != 0;
// load the light styles
+ SV_VM_Begin();
+ // -1 is the globals
+ entnum = -1;
+
for (i = 0;i < MAX_LIGHTSTYLES;i++)
{
// light style
- oldt = t;
- COM_ParseToken_Simple(&t, false);
+ start = t;
+ COM_ParseToken_Simple(&t, false, false);
// if this is a 64 lightstyle savegame produced by Quake, stop now
- // we have to check this because darkplaces saves 256 lightstyle savegames
+ // we have to check this because darkplaces may save more than 64
if (com_token[0] == '{')
{
- t = oldt;
+ t = start;
break;
}
strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
// (this is for forward compatibility, so that older versions (at
// least ones with this fix) can load savegames with extra data before the
// first brace, as might be produced by a later engine version)
- for(;;)
+ for (;;)
{
- oldt = t;
- COM_ParseToken_Simple(&t, false);
+ start = t;
+ if (!COM_ParseToken_Simple(&t, false, false))
+ break;
if (com_token[0] == '{')
{
- t = oldt;
+ t = start;
break;
}
}
// load the edicts out of the savegame file
- SV_VM_Begin();
- // -1 is the globals
- entnum = -1;
+ end = t;
for (;;)
{
start = t;
- while (COM_ParseToken_Simple(&t, false))
+ while (COM_ParseToken_Simple(&t, false, false))
if (!strcmp(com_token, "}"))
break;
- if (!COM_ParseToken_Simple(&start, false))
+ if (!COM_ParseToken_Simple(&start, false, false))
{
// end of file
break;
SV_LinkEdict (ent, false);
}
+ end = t;
entnum++;
}
Mem_Free(text);
for (i = 0;i < NUM_SPAWN_PARMS;i++)
svs.clients[0].spawn_parms[i] = spawn_parms[i];
+ // read extended data if present
+ // the extended data is stored inside a /* */ comment block, which the
+ // parser intentionally skips, so we have to check for it manually here
+ while (*end == '\r' || *end == '\n')
+ end++;
+ if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n'))
+ {
+ Con_Printf("Loading extended DarkPlaces savegame\n");
+ t = end + 2;
+ memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles));
+ memset(sv.model_precache[0], 0, sizeof(sv.model_precache));
+ memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache));
+ while (COM_ParseToken_Simple(&t, false, false))
+ {
+ if (!strcmp(com_token, "sv.lightstyles"))
+ {
+ COM_ParseToken_Simple(&t, false, false);
+ i = atoi(com_token);
+ COM_ParseToken_Simple(&t, false, false);
+ if (i >= 0 && i < MAX_LIGHTSTYLES)
+ strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
+ else
+ Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token);
+ }
+ else if (!strcmp(com_token, "sv.model_precache"))
+ {
+ COM_ParseToken_Simple(&t, false, false);
+ i = atoi(com_token);
+ COM_ParseToken_Simple(&t, false, false);
+ if (i >= 0 && i < MAX_MODELS)
+ {
+ strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i]));
+ sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, false);
+ }
+ else
+ Con_Printf("unsupported model %i \"%s\"\n", i, com_token);
+ }
+ else if (!strcmp(com_token, "sv.sound_precache"))
+ {
+ COM_ParseToken_Simple(&t, false, false);
+ i = atoi(com_token);
+ COM_ParseToken_Simple(&t, false, false);
+ if (i >= 0 && i < MAX_SOUNDS)
+ strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i]));
+ else
+ Con_Printf("unsupported sound %i \"%s\"\n", i, com_token);
+ }
+ // skip any trailing text or unrecognized commands
+ while (COM_ParseToken_Simple(&t, true, false) && strcmp(com_token, "\n"))
+ ;
+ }
+ }
+
SV_VM_End();
// make sure we're connected to loopback
- if (cls.state == ca_disconnected || !(cls.state == ca_connected && cls.netcon != NULL && LHNETADDRESS_GetAddressType(&cls.netcon->peeraddress) == LHNETADDRESSTYPE_LOOP))
+ if (sv.active && cls.state == ca_disconnected)
CL_EstablishConnection("local:1");
}
host_client->name[j++] = host_client->name[i];
host_client->name[j] = 0;
+ if(host_client->name[0] == 1 || host_client->name[0] == 2)
+ // may interfere with chat area, and will needlessly beep; so let's add a ^7
+ {
+ memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
+ host_client->name[sizeof(host_client->name) - 1] = 0;
+ host_client->name[0] = STRING_COLOR_TAG;
+ host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
+ }
+
COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
{
MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
MSG_WriteString (&sv.reliable_datagram, host_client->name);
+ SV_WriteNetnameIntoDemo(host_client);
}
}
// note this uses the chat prefix \001
if (!fromServer)
- sprintf (text, "\001%s tells you: ", host_client->name);
+ dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
else if(*(sv_adminnick.string))
- sprintf (text, "\001<%s tells you> ", sv_adminnick.string);
+ dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
else
- sprintf (text, "\001<%s tells you> ", hostname.string);
+ dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
p1 = Cmd_Args();
p2 = p1 + strlen(p1);
if (sv.loadgame)
{
// loaded games are fully initialized already
- // if this is the last client to be connected, unpause
- sv.paused = false;
-
if (prog->funcoffsets.RestoreGame)
{
Con_DPrint("Calling RestoreGame\n");
prog->globals.server->self = PRVM_EDICT_TO_PROG(host_client->edict);
PRVM_ExecuteProgram (prog->globals.server->ClientConnect, "QC function ClientConnect is missing");
- if (svs.maxclients > 1 || cls.state == ca_dedicated)
- Con_Printf("%s entered the game\n", host_client->name);
+ if (cls.state == ca_dedicated)
+ Con_Printf("%s connected\n", host_client->name);
PRVM_ExecuteProgram (prog->globals.server->PutClientInServer, "QC function PutClientInServer is missing");
}
MSG_WriteAngle (&host_client->netconnection->message, host_client->edict->fields.server->v_angle[0], sv.protocol);
MSG_WriteAngle (&host_client->netconnection->message, host_client->edict->fields.server->v_angle[1], sv.protocol);
MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
- sv.loadgame = false; // we're basically done with loading now
}
else
{
void Host_Begin_f (void)
{
host_client->spawned = true;
+
+ // LordHavoc: note: this code also exists in SV_DropClient
+ if (sv.loadgame)
+ {
+ int i;
+ for (i = 0;i < svs.maxclients;i++)
+ if (svs.clients[i].active && !svs.clients[i].spawned)
+ break;
+ if (i == svs.maxclients)
+ {
+ Con_Printf("Loaded game, everyone rejoined - unpausing\n");
+ sv.paused = sv.loadgame = false; // we're basically done with loading now
+ }
+ }
}
//===========================================================================
if (Cmd_Argc() > 2)
{
message = Cmd_Args();
- COM_ParseToken_Simple(&message, false);
+ COM_ParseToken_Simple(&message, false, false);
if (byNumber)
{
message++; // skip the #
void Host_Viewmodel_f (void)
{
prvm_edict_t *e;
- model_t *m;
+ dp_model_t *m;
if (!sv.active)
return;
{
prvm_edict_t *e;
int f;
- model_t *m;
+ dp_model_t *m;
if (!sv.active)
return;
}
-void PrintFrameName (model_t *m, int frame)
+void PrintFrameName (dp_model_t *m, int frame)
{
if (m->animscenes)
Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
void Host_Viewnext_f (void)
{
prvm_edict_t *e;
- model_t *m;
+ dp_model_t *m;
if (!sv.active)
return;
void Host_Viewprev_f (void)
{
prvm_edict_t *e;
- model_t *m;
+ dp_model_t *m;
if (!sv.active)
return;
Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
c = MAX_DEMOS;
}
- Con_Printf("%i demo(s) in loop\n", c);
+ Con_DPrintf("%i demo(s) in loop\n", c);
for (i=1 ; i<c+1 ; i++)
strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
}
mysocket = NetConn_ChooseClientSocketForAddress(&address);
+ if (!mysocket)
+ mysocket = NetConn_ChooseServerSocketForAddress(&address);
if (mysocket)
NetConn_Write(mysocket, send, out - send, &address);
}
Cmd_AddCommand_WithClientCommand ("pings", NULL, Host_Pings_f, "command sent by clients to request updated ping and packetloss of players on scoreboard (originally from QW, but also used on NQ servers)");
Cmd_AddCommand ("pingplreport", Host_PingPLReport_f, "command sent by server containing client ping and packet loss values for scoreboard, triggered by pings command from client (not used by QW servers)");
+ Cmd_AddCommand ("fixtrans", Image_FixTransparentPixels_f, "change alpha-zero pixels in an image file to sensible values, and write out a new TGA (warning: SLOW)");
+ Cvar_RegisterVariable (&r_fixtrans_auto);
+
Cvar_RegisterVariable (&team);
Cvar_RegisterVariable (&skin);
Cvar_RegisterVariable (&noaim);
Cvar_RegisterVariable(&sv_cheats);
Cvar_RegisterVariable(&sv_adminnick);
+ Cvar_RegisterVariable(&sv_status_privacy);
}
+void Host_NoOperation_f(void)
+{
+}