2016-03-27 11:49:47 +02:00
|
|
|
/*
|
|
|
|
===========================================================================
|
|
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
|
|
|
|
|
|
This file is part of Quake III Arena source code.
|
|
|
|
|
|
|
|
Quake III Arena 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 III Arena 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 III Arena source code; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
===========================================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "server.h"
|
|
|
|
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
|
|
|
# include "../client/client.h"
|
|
|
|
# include "../client/snd_local.h"
|
|
|
|
# include "../uilib/ui_public.h"
|
|
|
|
#endif
|
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
/*
|
|
|
|
===============================================================================
|
|
|
|
|
|
|
|
OPERATOR CONSOLE ONLY COMMANDS
|
|
|
|
|
|
|
|
These commands can only be entered from stdin or by a remote operator datagram
|
|
|
|
===============================================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_GetPlayerByHandle
|
|
|
|
|
|
|
|
Returns the player with player id or name from Cmd_Argv(1)
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static client_t *SV_GetPlayerByHandle( void ) {
|
|
|
|
client_t *cl;
|
|
|
|
int i;
|
|
|
|
char *s;
|
|
|
|
char cleanName[64];
|
|
|
|
|
|
|
|
// make sure server is running
|
|
|
|
if ( !com_sv_running->integer ) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( Cmd_Argc() < 2 ) {
|
|
|
|
Com_Printf( "No player specified.\n" );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
s = Cmd_Argv(1);
|
|
|
|
|
|
|
|
// Check whether this is a numeric player handle
|
|
|
|
for(i = 0; s[i] >= '0' && s[i] <= '9'; i++);
|
|
|
|
|
|
|
|
if(!s[i])
|
|
|
|
{
|
|
|
|
int plid = atoi(s);
|
|
|
|
|
|
|
|
// Check for numeric playerid match
|
|
|
|
if(plid >= 0 && plid < sv_maxclients->integer)
|
|
|
|
{
|
|
|
|
cl = &svs.clients[plid];
|
|
|
|
|
|
|
|
if(cl->state)
|
|
|
|
return cl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for a name match
|
|
|
|
for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
|
|
|
|
if ( !cl->state ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ( !Q_stricmp( cl->name, s ) ) {
|
|
|
|
return cl;
|
|
|
|
}
|
|
|
|
|
|
|
|
Q_strncpyz( cleanName, cl->name, sizeof(cleanName) );
|
|
|
|
Q_CleanStr( cleanName );
|
|
|
|
if ( !Q_stricmp( cleanName, s ) ) {
|
|
|
|
return cl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Com_Printf( "Player %s is not on the server\n", s );
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_GetPlayerByNum
|
|
|
|
|
|
|
|
Returns the player with idnum from Cmd_Argv(1)
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static client_t *SV_GetPlayerByNum( void ) {
|
|
|
|
client_t *cl;
|
|
|
|
int i;
|
|
|
|
int idnum;
|
|
|
|
char *s;
|
|
|
|
|
|
|
|
// make sure server is running
|
|
|
|
if ( !com_sv_running->integer ) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( Cmd_Argc() < 2 ) {
|
|
|
|
Com_Printf( "No player specified.\n" );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
s = Cmd_Argv(1);
|
|
|
|
|
|
|
|
for (i = 0; s[i]; i++) {
|
|
|
|
if (s[i] < '0' || s[i] > '9') {
|
|
|
|
Com_Printf( "Bad slot number: %s\n", s);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
idnum = atoi( s );
|
|
|
|
if ( idnum < 0 || idnum >= sv_maxclients->integer ) {
|
|
|
|
Com_Printf( "Bad client slot: %i\n", idnum );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
cl = &svs.clients[idnum];
|
|
|
|
if ( !cl->state ) {
|
|
|
|
Com_Printf( "Client %i is not active\n", idnum );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return cl;
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_Map_f
|
|
|
|
|
|
|
|
Restart the server on a different map
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static void SV_Map_f( void ) {
|
|
|
|
const char *cmd;
|
|
|
|
char expanded[ MAX_QPATH ];
|
|
|
|
char map[ MAX_QPATH ];
|
|
|
|
char mapname[ MAX_QPATH ];
|
|
|
|
const char *spawnpos;
|
|
|
|
|
|
|
|
if( Cmd_Argc() != 2 ) {
|
|
|
|
Com_Printf( "USAGE: map <map>\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( developer->integer && *fs_mapdir->string )
|
|
|
|
{
|
|
|
|
Com_sprintf( mapname, sizeof( mapname ), "%s/%s", fs_mapdir->string, Cmd_Argv( 1 ) );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Com_sprintf( mapname, sizeof( mapname ), "%s", Cmd_Argv( 1 ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
Com_BackslashToSlash( mapname );
|
|
|
|
|
|
|
|
spawnpos = strchr( mapname, '$' );
|
|
|
|
if( spawnpos )
|
|
|
|
{
|
|
|
|
Q_strncpyz( map, mapname, spawnpos - mapname + 1 );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
strcpy( map, mapname );
|
|
|
|
}
|
|
|
|
|
|
|
|
// make sure the level exists before trying to change, so that
|
|
|
|
// a typo at the server console won't end the game
|
|
|
|
Com_sprintf( expanded, sizeof( expanded ), "maps/%s.bsp", map );
|
|
|
|
if( FS_ReadFile( expanded, NULL ) == -1 ) {
|
|
|
|
Com_Printf( "Can't find map %s\n", expanded );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd = Cmd_Argv( 0 );
|
|
|
|
|
|
|
|
if( !Q_stricmpn( cmd, "sp", 2 ) )
|
|
|
|
{
|
|
|
|
Cvar_Set( "g_gametype", "0" );
|
|
|
|
Cvar_Set( "sv_maxclients", "1" );
|
|
|
|
}
|
|
|
|
|
|
|
|
Cvar_Get( "g_gametype", "0", CVAR_LATCH | CVAR_SERVERINFO );
|
2023-12-31 01:17:16 +01:00
|
|
|
// FIXME
|
|
|
|
// Commenting out this line below as this sets sv_maxclients
|
|
|
|
// before actually allocating clients
|
|
|
|
//Cvar_Get( "sv_maxclients", "1", CVAR_LATCH | CVAR_SERVERINFO );
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-05-08 14:33:37 +02:00
|
|
|
if( !Q_stricmpn( map, "dm/", 3 ) && g_gametype->integer == GT_OBJECTIVE )
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
|
|
|
Com_Printf( "Can't load regular dm map in objective game type\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !developer->integer )
|
|
|
|
{
|
|
|
|
if( svs.iNumClients == 1 )
|
|
|
|
{
|
|
|
|
Cvar_Set( "cheats", "1" );
|
|
|
|
}
|
|
|
|
else if( strstr( cmd, "devmap" ) )
|
|
|
|
{
|
|
|
|
Cvar_Set( "cheats", " 1" );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Cvar_Set( "cheats", "0" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Cvar_SaveGameRestart_f();
|
|
|
|
|
2023-10-01 23:08:07 +02:00
|
|
|
//
|
|
|
|
// in 2.0 and above, ignore training / mohaa mission maps
|
|
|
|
//
|
|
|
|
// FIXME: is this really necessary?
|
|
|
|
/*
|
|
|
|
if (com_target_game->integer != TG_MOH && g_gametype->integer == GT_SINGLE_PLAYER) {
|
|
|
|
if (mapname[0] == 'm' && mapname[1] - '1' <= 5) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Q_stricmp(mapname, "training")) {
|
|
|
|
return;
|
|
|
|
} else if (!Q_stricmp(mapname, "training.bsp")) {
|
|
|
|
return;
|
|
|
|
} else if (!Q_stricmp(mapname, "void")) {
|
|
|
|
return;
|
|
|
|
} else if (!Q_stricmp(mapname, "void.bsp")) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
// start up the map
|
|
|
|
SV_SpawnServer( mapname, qfalse, qfalse, qfalse );
|
|
|
|
|
2023-10-01 23:08:07 +02:00
|
|
|
if( g_gametype->integer == GT_SINGLE_PLAYER ) {
|
2016-03-27 11:49:47 +02:00
|
|
|
svs.autosave = qtrue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_GameMap_f
|
|
|
|
|
|
|
|
Restart the server on a different map
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static void SV_GameMap_f( void ) {
|
|
|
|
const char *map;
|
|
|
|
qboolean bTransition;
|
|
|
|
|
|
|
|
if( Cmd_Argc() != 2 ) {
|
|
|
|
Com_Printf( "USAGE: gamemap <map>\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
map = Cmd_Argv( 1 );
|
|
|
|
Com_DPrintf( "SV_GameMap(%s)\n", map );
|
|
|
|
|
|
|
|
strcpy( svs.gameName, "current" );
|
|
|
|
Cvar_SaveGameRestart_f();
|
|
|
|
|
|
|
|
bTransition = sv.state == SS_GAME;
|
|
|
|
|
|
|
|
// save persistant data
|
|
|
|
if( bTransition ) {
|
|
|
|
SV_ArchivePersistantFile( qfalse );
|
|
|
|
}
|
|
|
|
|
|
|
|
SV_SpawnServer( map, qfalse, qfalse, bTransition );
|
|
|
|
|
|
|
|
if( !g_gametype->integer ) {
|
|
|
|
svs.autosave = qtrue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
SV_MapRestart_f
|
|
|
|
|
|
|
|
Completely restarts a level, but doesn't send a new gamestate to the clients.
|
|
|
|
This allows fair starts with variable load times.
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
static void SV_MapRestart_f( void ) {
|
|
|
|
if( com_frameTime == sv.serverId ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !com_sv_running->integer ) {
|
|
|
|
Com_Printf( "Server is not running.\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Cvar_SaveGameRestart_f();
|
|
|
|
SV_SpawnServer( svs.rawServerName, qfalse, qtrue, qfalse );
|
|
|
|
}
|
|
|
|
|
|
|
|
//===============================================================
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_Kick_f
|
|
|
|
|
|
|
|
Kick a user off of the server FIXME: move to game
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static void SV_Kick_f( void ) {
|
|
|
|
client_t *cl;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
// make sure server is running
|
|
|
|
if ( !com_sv_running->integer ) {
|
|
|
|
Com_Printf( "Server is not running.\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( Cmd_Argc() != 2 ) {
|
|
|
|
Com_Printf ("Usage: kick <player name>\nkick all = kick everyone\nkick allbots = kick all bots\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cl = SV_GetPlayerByHandle();
|
|
|
|
if ( !cl ) {
|
|
|
|
if ( !Q_stricmp(Cmd_Argv(1), "all") ) {
|
|
|
|
for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
|
|
|
|
if ( !cl->state ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
SV_DropClient( cl, "was kicked" );
|
|
|
|
cl->lastPacketTime = svs.time; // in case there is a funny zombie
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ( !Q_stricmp(Cmd_Argv(1), "allbots") ) {
|
|
|
|
for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
|
|
|
|
if ( !cl->state ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if( cl->netchan.remoteAddress.type != NA_BOT ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
SV_DropClient( cl, "was kicked" );
|
|
|
|
cl->lastPacketTime = svs.time; // in case there is a funny zombie
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
|
|
|
|
SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SV_DropClient( cl, "was kicked" );
|
|
|
|
cl->lastPacketTime = svs.time; // in case there is a funny zombie
|
|
|
|
}
|
2023-11-14 17:30:17 +01:00
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_KickAll_f
|
|
|
|
|
|
|
|
Kick all users off of the server
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static void SV_KickAll_f( void ) {
|
|
|
|
client_t *cl;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
// make sure server is running
|
|
|
|
if( !com_sv_running->integer ) {
|
|
|
|
Com_Printf( "Server is not running.\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) {
|
|
|
|
if( !cl->state ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
SV_DropClient( cl, "was kicked" );
|
|
|
|
cl->lastPacketTime = svs.time; // in case there is a funny zombie
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_KickNum_f
|
|
|
|
|
|
|
|
Kick a user off of the server
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static void SV_KickNum_f( void ) {
|
|
|
|
client_t *cl;
|
|
|
|
|
|
|
|
// make sure server is running
|
|
|
|
if ( !com_sv_running->integer ) {
|
|
|
|
Com_Printf( "Server is not running.\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( Cmd_Argc() != 2 ) {
|
|
|
|
Com_Printf ("Usage: %s <client number>\n", Cmd_Argv(0));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cl = SV_GetPlayerByNum();
|
|
|
|
if ( !cl ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
|
|
|
|
Com_Printf("Cannot kick host player\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SV_DropClient( cl, "was kicked" );
|
|
|
|
cl->lastPacketTime = svs.time; // in case there is a funny zombie
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef STANDALONE
|
|
|
|
// these functions require the auth server which of course is not available anymore for stand-alone games.
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_Ban_f
|
|
|
|
|
|
|
|
Ban a user from being able to play on this server through the auth
|
|
|
|
server
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static void SV_Ban_f( void ) {
|
|
|
|
client_t *cl;
|
|
|
|
|
|
|
|
// make sure server is running
|
|
|
|
if ( !com_sv_running->integer ) {
|
|
|
|
Com_Printf( "Server is not running.\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( Cmd_Argc() != 2 ) {
|
|
|
|
Com_Printf ("Usage: banUser <player name>\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cl = SV_GetPlayerByHandle();
|
|
|
|
|
|
|
|
if (!cl) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
|
2023-11-14 17:30:17 +01:00
|
|
|
Com_Printf("Cannot kick host player\n");
|
2016-03-27 11:49:47 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// look up the authorize server's IP
|
|
|
|
if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) {
|
|
|
|
Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME );
|
2023-05-28 20:19:35 +02:00
|
|
|
if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress, NA_IP ) ) {
|
2016-03-27 11:49:47 +02:00
|
|
|
Com_Printf( "Couldn't resolve address\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE );
|
|
|
|
Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME,
|
|
|
|
svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1],
|
|
|
|
svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3],
|
|
|
|
BigShort( svs.authorizeAddress.port ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
// otherwise send their ip to the authorize server
|
|
|
|
if ( svs.authorizeAddress.type != NA_BAD ) {
|
|
|
|
NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress,
|
|
|
|
"banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1],
|
|
|
|
cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] );
|
|
|
|
Com_Printf("%s was banned from coming back\n", cl->name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_BanNum_f
|
|
|
|
|
|
|
|
Ban a user from being able to play on this server through the auth
|
|
|
|
server
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static void SV_BanNum_f( void ) {
|
|
|
|
client_t *cl;
|
|
|
|
|
|
|
|
// make sure server is running
|
|
|
|
if ( !com_sv_running->integer ) {
|
|
|
|
Com_Printf( "Server is not running.\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( Cmd_Argc() != 2 ) {
|
|
|
|
Com_Printf ("Usage: banClient <client number>\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cl = SV_GetPlayerByNum();
|
|
|
|
if ( !cl ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
|
2023-11-14 17:30:17 +01:00
|
|
|
Com_Printf("Cannot kick host player\n");
|
2016-03-27 11:49:47 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// look up the authorize server's IP
|
|
|
|
if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) {
|
|
|
|
Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME );
|
2023-05-28 20:19:35 +02:00
|
|
|
if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress, NA_IP ) ) {
|
2016-03-27 11:49:47 +02:00
|
|
|
Com_Printf( "Couldn't resolve address\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE );
|
|
|
|
Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME,
|
|
|
|
svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1],
|
|
|
|
svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3],
|
|
|
|
BigShort( svs.authorizeAddress.port ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
// otherwise send their ip to the authorize server
|
|
|
|
if ( svs.authorizeAddress.type != NA_BAD ) {
|
|
|
|
NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress,
|
|
|
|
"banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1],
|
|
|
|
cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] );
|
|
|
|
Com_Printf("%s was banned from coming back\n", cl->name);
|
|
|
|
}
|
|
|
|
}
|
2023-11-14 17:30:17 +01:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
2023-11-14 17:30:17 +01:00
|
|
|
SV_RehashBans_f
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-11-14 17:30:17 +01:00
|
|
|
Load saved bans from file.
|
2016-03-27 11:49:47 +02:00
|
|
|
==================
|
|
|
|
*/
|
2023-11-14 17:30:17 +01:00
|
|
|
static void SV_RehashBans_f(void)
|
|
|
|
{
|
|
|
|
int index, filelen;
|
|
|
|
fileHandle_t readfrom;
|
|
|
|
char *textbuf, *curpos, *maskpos, *newlinepos, *endpos;
|
|
|
|
char filepath[MAX_QPATH];
|
|
|
|
|
|
|
|
// make sure server is running
|
|
|
|
if ( !com_sv_running->integer ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
serverBansCount = 0;
|
|
|
|
|
|
|
|
if(!sv_banFile->string || !*sv_banFile->string)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Com_sprintf(filepath, sizeof(filepath), "%s/%s", FS_GetCurrentGameDir(), sv_banFile->string);
|
|
|
|
|
|
|
|
if((filelen = FS_SV_FOpenFileRead(filepath, &readfrom)) >= 0)
|
|
|
|
{
|
|
|
|
if(filelen < 2)
|
|
|
|
{
|
|
|
|
// Don't bother if file is too short.
|
|
|
|
FS_FCloseFile(readfrom);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
curpos = textbuf = Z_Malloc(filelen);
|
|
|
|
|
|
|
|
filelen = FS_Read(textbuf, filelen, readfrom);
|
|
|
|
FS_FCloseFile(readfrom);
|
|
|
|
|
|
|
|
endpos = textbuf + filelen;
|
|
|
|
|
|
|
|
for(index = 0; index < SERVER_MAXBANS && curpos + 2 < endpos; index++)
|
|
|
|
{
|
|
|
|
// find the end of the address string
|
|
|
|
for(maskpos = curpos + 2; maskpos < endpos && *maskpos != ' '; maskpos++);
|
|
|
|
|
|
|
|
if(maskpos + 1 >= endpos)
|
|
|
|
break;
|
|
|
|
|
|
|
|
*maskpos = '\0';
|
|
|
|
maskpos++;
|
|
|
|
|
|
|
|
// find the end of the subnet specifier
|
|
|
|
for(newlinepos = maskpos; newlinepos < endpos && *newlinepos != '\n'; newlinepos++);
|
|
|
|
|
|
|
|
if(newlinepos >= endpos)
|
|
|
|
break;
|
|
|
|
|
|
|
|
*newlinepos = '\0';
|
|
|
|
|
|
|
|
if(NET_StringToAdr(curpos + 2, &serverBans[index].ip, NA_UNSPEC))
|
|
|
|
{
|
|
|
|
serverBans[index].isexception = (curpos[0] != '0');
|
|
|
|
serverBans[index].subnet = atoi(maskpos);
|
|
|
|
|
|
|
|
if(serverBans[index].ip.type == NA_IP &&
|
|
|
|
(serverBans[index].subnet < 1 || serverBans[index].subnet > 32))
|
|
|
|
{
|
|
|
|
serverBans[index].subnet = 32;
|
|
|
|
}
|
|
|
|
else if(serverBans[index].ip.type == NA_IP6 &&
|
|
|
|
(serverBans[index].subnet < 1 || serverBans[index].subnet > 128))
|
|
|
|
{
|
|
|
|
serverBans[index].subnet = 128;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
curpos = newlinepos + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
serverBansCount = index;
|
|
|
|
|
|
|
|
Z_Free(textbuf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_WriteBans
|
|
|
|
|
|
|
|
Save bans to file.
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static void SV_WriteBans(void)
|
|
|
|
{
|
|
|
|
int index;
|
|
|
|
fileHandle_t writeto;
|
|
|
|
char filepath[MAX_QPATH];
|
|
|
|
|
|
|
|
if(!sv_banFile->string || !*sv_banFile->string)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Com_sprintf(filepath, sizeof(filepath), "%s/%s", FS_GetCurrentGameDir(), sv_banFile->string);
|
|
|
|
|
|
|
|
if((writeto = FS_SV_FOpenFileWrite(filepath)))
|
|
|
|
{
|
|
|
|
char writebuf[128];
|
|
|
|
serverBan_t *curban;
|
|
|
|
|
|
|
|
for(index = 0; index < serverBansCount; index++)
|
|
|
|
{
|
|
|
|
curban = &serverBans[index];
|
|
|
|
|
|
|
|
Com_sprintf(writebuf, sizeof(writebuf), "%d %s %d\n",
|
|
|
|
curban->isexception, NET_AdrToString(curban->ip), curban->subnet);
|
|
|
|
FS_Write(writebuf, strlen(writebuf), writeto);
|
|
|
|
}
|
|
|
|
|
|
|
|
FS_FCloseFile(writeto);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_DelBanEntryFromList
|
|
|
|
|
|
|
|
Remove a ban or an exception from the list.
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
|
|
|
|
static qboolean SV_DelBanEntryFromList(int index)
|
|
|
|
{
|
|
|
|
if(index == serverBansCount - 1)
|
|
|
|
serverBansCount--;
|
|
|
|
else if(index < ARRAY_LEN(serverBans) - 1)
|
|
|
|
{
|
|
|
|
memmove(serverBans + index, serverBans + index + 1, (serverBansCount - index - 1) * sizeof(*serverBans));
|
|
|
|
serverBansCount--;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return qtrue;
|
|
|
|
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_ParseCIDRNotation
|
|
|
|
|
|
|
|
Parse a CIDR notation type string and return a netadr_t and suffix by reference
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
|
|
|
|
static qboolean SV_ParseCIDRNotation(netadr_t *dest, int *mask, char *adrstr)
|
|
|
|
{
|
|
|
|
char *suffix;
|
|
|
|
|
|
|
|
suffix = strchr(adrstr, '/');
|
|
|
|
if(suffix)
|
|
|
|
{
|
|
|
|
*suffix = '\0';
|
|
|
|
suffix++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!NET_StringToAdr(adrstr, dest, NA_UNSPEC))
|
|
|
|
return qtrue;
|
|
|
|
|
|
|
|
if(suffix)
|
|
|
|
{
|
|
|
|
*mask = atoi(suffix);
|
|
|
|
|
|
|
|
if(dest->type == NA_IP)
|
|
|
|
{
|
|
|
|
if(*mask < 1 || *mask > 32)
|
|
|
|
*mask = 32;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(*mask < 1 || *mask > 128)
|
|
|
|
*mask = 128;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(dest->type == NA_IP)
|
|
|
|
*mask = 32;
|
|
|
|
else
|
|
|
|
*mask = 128;
|
|
|
|
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_AddBanToList
|
|
|
|
|
|
|
|
Ban a user from being able to play on this server based on his ip address.
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void SV_AddBanToList(qboolean isexception)
|
|
|
|
{
|
|
|
|
char *banstring;
|
|
|
|
char addy2[NET_ADDRSTRMAXLEN];
|
|
|
|
netadr_t ip;
|
|
|
|
int index, argc, mask;
|
|
|
|
serverBan_t *curban;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
// make sure server is running
|
|
|
|
if ( !com_sv_running->integer ) {
|
|
|
|
Com_Printf( "Server is not running.\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-11-14 17:30:17 +01:00
|
|
|
argc = Cmd_Argc();
|
|
|
|
|
|
|
|
if(argc < 2 || argc > 3)
|
|
|
|
{
|
|
|
|
Com_Printf ("Usage: %s (ip[/subnet] | clientnum [subnet])\n", Cmd_Argv(0));
|
2016-03-27 11:49:47 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-11-14 17:30:17 +01:00
|
|
|
if(serverBansCount >= ARRAY_LEN(serverBans))
|
|
|
|
{
|
|
|
|
Com_Printf ("Error: Maximum number of bans/exceptions exceeded.\n");
|
2016-03-27 11:49:47 +02:00
|
|
|
return;
|
|
|
|
}
|
2023-11-14 17:30:17 +01:00
|
|
|
|
|
|
|
banstring = Cmd_Argv(1);
|
|
|
|
|
|
|
|
if(strchr(banstring, '.') || strchr(banstring, ':'))
|
|
|
|
{
|
|
|
|
// This is an ip address, not a client num.
|
|
|
|
|
|
|
|
if(SV_ParseCIDRNotation(&ip, &mask, banstring))
|
|
|
|
{
|
|
|
|
Com_Printf("Error: Invalid address %s\n", banstring);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
client_t *cl;
|
|
|
|
|
|
|
|
// client num.
|
|
|
|
|
|
|
|
cl = SV_GetPlayerByNum();
|
|
|
|
|
|
|
|
if(!cl)
|
|
|
|
{
|
|
|
|
Com_Printf("Error: Playernum %s does not exist.\n", Cmd_Argv(1));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ip = cl->netchan.remoteAddress;
|
|
|
|
|
|
|
|
if(argc == 3)
|
|
|
|
{
|
|
|
|
mask = atoi(Cmd_Argv(2));
|
|
|
|
|
|
|
|
if(ip.type == NA_IP)
|
|
|
|
{
|
|
|
|
if(mask < 1 || mask > 32)
|
|
|
|
mask = 32;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(mask < 1 || mask > 128)
|
|
|
|
mask = 128;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
mask = (ip.type == NA_IP6) ? 128 : 32;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(ip.type != NA_IP && ip.type != NA_IP6)
|
|
|
|
{
|
|
|
|
Com_Printf("Error: Can ban players connected via the internet only.\n");
|
2016-03-27 11:49:47 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-11-14 17:30:17 +01:00
|
|
|
// first check whether a conflicting ban exists that would supersede the new one.
|
|
|
|
for(index = 0; index < serverBansCount; index++)
|
|
|
|
{
|
|
|
|
curban = &serverBans[index];
|
|
|
|
|
|
|
|
if(curban->subnet <= mask)
|
|
|
|
{
|
|
|
|
if((curban->isexception || !isexception) && NET_CompareBaseAdrMask(curban->ip, ip, curban->subnet))
|
|
|
|
{
|
|
|
|
Q_strncpyz(addy2, NET_AdrToString(ip), sizeof(addy2));
|
|
|
|
|
|
|
|
Com_Printf("Error: %s %s/%d supersedes %s %s/%d\n", curban->isexception ? "Exception" : "Ban",
|
|
|
|
NET_AdrToString(curban->ip), curban->subnet,
|
|
|
|
isexception ? "exception" : "ban", addy2, mask);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(curban->subnet >= mask)
|
|
|
|
{
|
|
|
|
if(!curban->isexception && isexception && NET_CompareBaseAdrMask(curban->ip, ip, mask))
|
|
|
|
{
|
|
|
|
Q_strncpyz(addy2, NET_AdrToString(curban->ip), sizeof(addy2));
|
|
|
|
|
|
|
|
Com_Printf("Error: %s %s/%d supersedes already existing %s %s/%d\n", isexception ? "Exception" : "Ban",
|
|
|
|
NET_AdrToString(ip), mask,
|
|
|
|
curban->isexception ? "exception" : "ban", addy2, curban->subnet);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// now delete bans that are superseded by the new one
|
|
|
|
index = 0;
|
|
|
|
while(index < serverBansCount)
|
|
|
|
{
|
|
|
|
curban = &serverBans[index];
|
|
|
|
|
|
|
|
if(curban->subnet > mask && (!curban->isexception || isexception) && NET_CompareBaseAdrMask(curban->ip, ip, mask))
|
|
|
|
SV_DelBanEntryFromList(index);
|
|
|
|
else
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
serverBans[serverBansCount].ip = ip;
|
|
|
|
serverBans[serverBansCount].subnet = mask;
|
|
|
|
serverBans[serverBansCount].isexception = isexception;
|
|
|
|
|
|
|
|
serverBansCount++;
|
|
|
|
|
|
|
|
SV_WriteBans();
|
|
|
|
|
|
|
|
Com_Printf("Added %s: %s/%d\n", isexception ? "ban exception" : "ban",
|
|
|
|
NET_AdrToString(ip), mask);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_DelBanFromList
|
|
|
|
|
|
|
|
Remove a ban or an exception from the list.
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void SV_DelBanFromList(qboolean isexception)
|
|
|
|
{
|
|
|
|
int index, count = 0, todel, mask;
|
|
|
|
netadr_t ip;
|
|
|
|
char *banstring;
|
|
|
|
|
|
|
|
// make sure server is running
|
|
|
|
if ( !com_sv_running->integer ) {
|
|
|
|
Com_Printf( "Server is not running.\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(Cmd_Argc() != 2)
|
|
|
|
{
|
|
|
|
Com_Printf ("Usage: %s (ip[/subnet] | num)\n", Cmd_Argv(0));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
banstring = Cmd_Argv(1);
|
|
|
|
|
|
|
|
if(strchr(banstring, '.') || strchr(banstring, ':'))
|
|
|
|
{
|
|
|
|
serverBan_t *curban;
|
|
|
|
|
|
|
|
if(SV_ParseCIDRNotation(&ip, &mask, banstring))
|
|
|
|
{
|
|
|
|
Com_Printf("Error: Invalid address %s\n", banstring);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
index = 0;
|
|
|
|
|
|
|
|
while(index < serverBansCount)
|
|
|
|
{
|
|
|
|
curban = &serverBans[index];
|
|
|
|
|
|
|
|
if(curban->isexception == isexception &&
|
|
|
|
curban->subnet >= mask &&
|
|
|
|
NET_CompareBaseAdrMask(curban->ip, ip, mask))
|
|
|
|
{
|
|
|
|
Com_Printf("Deleting %s %s/%d\n",
|
|
|
|
isexception ? "exception" : "ban",
|
|
|
|
NET_AdrToString(curban->ip), curban->subnet);
|
|
|
|
|
|
|
|
SV_DelBanEntryFromList(index);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
todel = atoi(Cmd_Argv(1));
|
|
|
|
|
|
|
|
if(todel < 1 || todel > serverBansCount)
|
|
|
|
{
|
|
|
|
Com_Printf("Error: Invalid ban number given\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(index = 0; index < serverBansCount; index++)
|
|
|
|
{
|
|
|
|
if(serverBans[index].isexception == isexception)
|
|
|
|
{
|
|
|
|
count++;
|
|
|
|
|
|
|
|
if(count == todel)
|
|
|
|
{
|
|
|
|
Com_Printf("Deleting %s %s/%d\n",
|
|
|
|
isexception ? "exception" : "ban",
|
|
|
|
NET_AdrToString(serverBans[index].ip), serverBans[index].subnet);
|
|
|
|
|
|
|
|
SV_DelBanEntryFromList(index);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SV_WriteBans();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_ListBans_f
|
|
|
|
|
|
|
|
List all bans and exceptions on console
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void SV_ListBans_f(void)
|
|
|
|
{
|
|
|
|
int index, count;
|
|
|
|
serverBan_t *ban;
|
|
|
|
|
|
|
|
// make sure server is running
|
|
|
|
if ( !com_sv_running->integer ) {
|
|
|
|
Com_Printf( "Server is not running.\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// List all bans
|
|
|
|
for(index = count = 0; index < serverBansCount; index++)
|
|
|
|
{
|
|
|
|
ban = &serverBans[index];
|
|
|
|
if(!ban->isexception)
|
|
|
|
{
|
|
|
|
count++;
|
|
|
|
|
|
|
|
Com_Printf("Ban #%d: %s/%d\n", count,
|
|
|
|
NET_AdrToString(ban->ip), ban->subnet);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// List all exceptions
|
|
|
|
for(index = count = 0; index < serverBansCount; index++)
|
|
|
|
{
|
|
|
|
ban = &serverBans[index];
|
|
|
|
if(ban->isexception)
|
|
|
|
{
|
|
|
|
count++;
|
|
|
|
|
|
|
|
Com_Printf("Except #%d: %s/%d\n", count,
|
|
|
|
NET_AdrToString(ban->ip), ban->subnet);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_FlushBans_f
|
|
|
|
|
|
|
|
Delete all bans and exceptions.
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void SV_FlushBans_f(void)
|
|
|
|
{
|
|
|
|
// make sure server is running
|
|
|
|
if ( !com_sv_running->integer ) {
|
|
|
|
Com_Printf( "Server is not running.\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
serverBansCount = 0;
|
|
|
|
|
|
|
|
// empty the ban file.
|
|
|
|
SV_WriteBans();
|
|
|
|
|
|
|
|
Com_Printf("All bans and exceptions have been deleted.\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void SV_BanAddr_f(void)
|
|
|
|
{
|
|
|
|
SV_AddBanToList(qfalse);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void SV_ExceptAddr_f(void)
|
|
|
|
{
|
|
|
|
SV_AddBanToList(qtrue);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void SV_BanDel_f(void)
|
|
|
|
{
|
|
|
|
SV_DelBanFromList(qfalse);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void SV_ExceptDel_f(void)
|
|
|
|
{
|
|
|
|
SV_DelBanFromList(qtrue);
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2023-08-30 19:36:38 +02:00
|
|
|
** SV_Strlen -- skips color escape codes
|
2016-03-27 11:49:47 +02:00
|
|
|
*/
|
2023-08-30 19:36:38 +02:00
|
|
|
static int SV_Strlen( const char *str ) {
|
|
|
|
const char *s = str;
|
|
|
|
int count = 0;
|
|
|
|
|
|
|
|
while ( *s ) {
|
2024-09-08 18:09:33 +02:00
|
|
|
//if ( Q_IsColorString( s ) ) {
|
|
|
|
// s += 2;
|
|
|
|
//} else {
|
2023-08-30 19:36:38 +02:00
|
|
|
count++;
|
|
|
|
s++;
|
2024-09-08 18:09:33 +02:00
|
|
|
//}
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-30 19:36:38 +02:00
|
|
|
return count;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-30 19:36:38 +02:00
|
|
|
/*
|
|
|
|
================
|
|
|
|
SV_Status_f
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
static void SV_Status_f(void) {
|
|
|
|
int i;
|
|
|
|
size_t j, l;
|
|
|
|
client_t* cl;
|
|
|
|
playerState_t* ps;
|
|
|
|
const char* s;
|
2024-09-13 19:47:05 +02:00
|
|
|
const char *colName[8];
|
|
|
|
size_t colSize[8];
|
|
|
|
size_t len;
|
2023-08-30 19:36:38 +02:00
|
|
|
int ping;
|
2024-09-13 19:47:05 +02:00
|
|
|
char padding[64];
|
2023-08-30 19:36:38 +02:00
|
|
|
|
|
|
|
// make sure server is running
|
|
|
|
if (!com_sv_running->integer) {
|
|
|
|
Com_Printf("Server is not running.\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-09-13 19:47:05 +02:00
|
|
|
//
|
|
|
|
// Added in OPM
|
|
|
|
// Dynamic column size.
|
|
|
|
//
|
|
|
|
// Some programs use fixed-size columns
|
|
|
|
// and may not parse information correctly
|
|
|
|
//
|
|
|
|
|
|
|
|
colName[0] = "num";
|
|
|
|
colSize[0] = strlen(colName[0]);
|
|
|
|
colName[1] = "score";
|
|
|
|
colSize[1] = strlen(colName[1]);
|
|
|
|
colName[2] = "ping";
|
|
|
|
colSize[2] = strlen(colName[2]);
|
|
|
|
colName[3] = "name";
|
|
|
|
colSize[3] = 15;
|
|
|
|
colName[4] = "lastmsg";
|
|
|
|
colSize[4] = 7;
|
|
|
|
colName[5] = "address";
|
|
|
|
colSize[5] = 21;
|
|
|
|
colName[6] = "qport";
|
|
|
|
colSize[6] = 5;
|
|
|
|
colName[7] = "rate";
|
|
|
|
colSize[7] = 5;
|
|
|
|
|
|
|
|
//
|
|
|
|
// Find IPv6 clients and adjust the IP address column size
|
|
|
|
//
|
|
|
|
|
|
|
|
for (i = 0, cl = svs.clients; i < svs.iNumClients; i++, cl++)
|
|
|
|
{
|
|
|
|
if (cl->state == CS_FREE) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cl->netchan.remoteAddress.type == NA_IP6) {
|
|
|
|
colSize[5] = 38;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-30 19:36:38 +02:00
|
|
|
Com_Printf("map: %s\n", sv_mapname->string);
|
|
|
|
|
2024-09-13 19:47:05 +02:00
|
|
|
//
|
|
|
|
// Print column header name
|
|
|
|
//
|
|
|
|
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
|
|
// Column name
|
|
|
|
len = strlen(colName[i]);
|
|
|
|
Com_Printf("%s", colName[i]);
|
|
|
|
|
|
|
|
// Padding
|
|
|
|
for (j = 0; j < (ARRAY_LEN(padding) - 1) && (colSize[i] - j >= len); j++) {
|
|
|
|
padding[j] = ' ';
|
|
|
|
}
|
|
|
|
padding[j] = 0;
|
|
|
|
|
|
|
|
if (j > 0) {
|
|
|
|
Com_Printf("%s", padding);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Com_Printf("\n");
|
|
|
|
|
|
|
|
//
|
|
|
|
// Print column header padding
|
|
|
|
//
|
|
|
|
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
|
|
for (j = 0; j < (ARRAY_LEN(padding) - 1) && (j < colSize[i]); j++) {
|
|
|
|
padding[j] = '-';
|
|
|
|
}
|
|
|
|
padding[j] = 0;
|
|
|
|
|
|
|
|
Com_Printf("%s ", padding);
|
|
|
|
}
|
|
|
|
Com_Printf("\n");
|
|
|
|
|
|
|
|
//
|
|
|
|
// Print values
|
|
|
|
//
|
|
|
|
|
2024-09-08 18:09:33 +02:00
|
|
|
for (i = 0, cl = svs.clients; i < svs.iNumClients; i++, cl++)
|
2023-08-30 19:36:38 +02:00
|
|
|
{
|
2024-09-13 19:47:05 +02:00
|
|
|
if (cl->state == CS_FREE) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
Com_Printf("%*i ", colSize[0], i);
|
2023-08-30 19:36:38 +02:00
|
|
|
ps = SV_GameClientNum(i);
|
|
|
|
// su44: ps->persistant is not avaible in mohaa
|
|
|
|
//Com_Printf ("%5i ", ps->persistant[PERS_SCORE]);
|
2024-09-13 19:47:05 +02:00
|
|
|
Com_Printf("%*i ", colSize[1], ps->stats[STAT_KILLS]);
|
2023-08-30 19:36:38 +02:00
|
|
|
|
|
|
|
if (cl->state == CS_CONNECTED)
|
|
|
|
Com_Printf("CNCT ");
|
|
|
|
else if (cl->state == CS_ZOMBIE)
|
|
|
|
Com_Printf("ZMBI ");
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ping = cl->ping < 9999 ? cl->ping : 9999;
|
2024-09-13 19:47:05 +02:00
|
|
|
Com_Printf("%*i ", colSize[2], ping);
|
2023-08-30 19:36:38 +02:00
|
|
|
}
|
|
|
|
|
2024-09-13 19:47:05 +02:00
|
|
|
Com_Printf("%s ", cl->name);
|
2023-08-30 19:36:38 +02:00
|
|
|
l = SV_Strlen(cl->name);
|
2024-09-13 19:47:05 +02:00
|
|
|
if (l < colSize[3]) {
|
|
|
|
l = colSize[3] - l;
|
|
|
|
for (j = 0; j < l; j++) {
|
2023-08-30 19:36:38 +02:00
|
|
|
Com_Printf(" ");
|
2024-09-13 19:47:05 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2024-09-13 19:47:05 +02:00
|
|
|
Com_Printf("%*i ", colSize[4], svs.time - cl->lastPacketTime);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-30 19:36:38 +02:00
|
|
|
s = NET_AdrToString(cl->netchan.remoteAddress);
|
2024-09-13 19:47:05 +02:00
|
|
|
Com_Printf("%s ", s);
|
|
|
|
|
|
|
|
len = strlen(s);
|
|
|
|
if (len < colSize[5]) {
|
|
|
|
l = colSize[5] - strlen(s);
|
|
|
|
for (j = 0; j < l; j++) {
|
|
|
|
Com_Printf(" ");
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2024-09-13 19:47:05 +02:00
|
|
|
Com_Printf( "%*i", colSize[6], cl->netchan.qport );
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2024-09-13 19:47:05 +02:00
|
|
|
Com_Printf(" %*i", colSize[7], cl->rate);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-30 19:36:38 +02:00
|
|
|
Com_Printf("\n");
|
|
|
|
}
|
|
|
|
Com_Printf("\n");
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_ConSay_f
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static void SV_ConSay_f(void) {
|
|
|
|
char *p;
|
|
|
|
char text[1024];
|
|
|
|
|
|
|
|
// make sure server is running
|
|
|
|
if ( !com_sv_running->integer ) {
|
|
|
|
Com_Printf( "Server is not running.\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( Cmd_Argc () < 2 ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
strcpy (text, "console: ");
|
|
|
|
p = Cmd_Args();
|
|
|
|
|
|
|
|
if ( *p == '"' ) {
|
|
|
|
p++;
|
|
|
|
p[strlen(p)-1] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
strcat(text, p);
|
|
|
|
|
2023-11-14 17:30:17 +01:00
|
|
|
Com_Printf("%s\n", text);
|
|
|
|
SV_SendServerCommand(NULL, "print \"" HUD_MESSAGE_CHAT_WHITE "%s\n\"", text);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_ConTell_f
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static void SV_ConTell_f(void) {
|
|
|
|
char *p;
|
|
|
|
char text[1024];
|
|
|
|
client_t *cl;
|
|
|
|
|
|
|
|
// make sure server is running
|
|
|
|
if ( !com_sv_running->integer ) {
|
|
|
|
Com_Printf( "Server is not running.\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( Cmd_Argc() < 3 ) {
|
|
|
|
Com_Printf ("Usage: tell <client number> <text>\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cl = SV_GetPlayerByNum();
|
|
|
|
if ( !cl ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
strcpy (text, "(private) console: ");
|
|
|
|
p = Cmd_ArgsFrom(2);
|
|
|
|
|
|
|
|
if ( *p == '"' ) {
|
|
|
|
p++;
|
|
|
|
p[strlen(p)-1] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
strcat(text, p);
|
|
|
|
|
|
|
|
Com_Printf("%s\n", text);
|
|
|
|
SV_SendServerCommand(cl, "print \"" HUD_MESSAGE_CHAT_WHITE "%s\n\"", text);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_ConSayto_f
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static void SV_ConSayto_f(void) {
|
|
|
|
char *p;
|
|
|
|
char text[1024];
|
|
|
|
client_t *cl;
|
|
|
|
char *rawname;
|
|
|
|
char name[MAX_NAME_LENGTH];
|
|
|
|
char cleanName[MAX_NAME_LENGTH];
|
|
|
|
client_t *saytocl;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
// make sure server is running
|
|
|
|
if ( !com_sv_running->integer ) {
|
|
|
|
Com_Printf( "Server is not running.\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( Cmd_Argc() < 3 ) {
|
|
|
|
Com_Printf ("Usage: sayto <player name> <text>\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
rawname = Cmd_Argv(1);
|
|
|
|
|
|
|
|
//allowing special characters in the console
|
|
|
|
//with hex strings for player names
|
|
|
|
Com_FieldStringToPlayerName( name, MAX_NAME_LENGTH, rawname );
|
|
|
|
|
|
|
|
saytocl = NULL;
|
|
|
|
for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
|
|
|
|
if ( !cl->state ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
Q_strncpyz( cleanName, cl->name, sizeof(cleanName) );
|
|
|
|
Q_CleanStr( cleanName );
|
|
|
|
|
|
|
|
if ( !Q_stricmp( cleanName, name ) ) {
|
|
|
|
saytocl = cl;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if( !saytocl )
|
|
|
|
{
|
|
|
|
Com_Printf ("No such player name: %s.\n", name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
strcpy (text, "(private) console: ");
|
|
|
|
p = Cmd_ArgsFrom(2);
|
|
|
|
|
|
|
|
if ( *p == '"' ) {
|
|
|
|
p++;
|
|
|
|
p[strlen(p)-1] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
strcat(text, p);
|
|
|
|
|
|
|
|
Com_Printf("%s\n", text);
|
|
|
|
SV_SendServerCommand(saytocl, "print \"" HUD_MESSAGE_CHAT_WHITE "%s\n\"", text);
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_Heartbeat_f
|
|
|
|
|
|
|
|
Also called by SV_DropClient, SV_DirectConnect, and SV_SpawnServer
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_Heartbeat_f( void ) {
|
|
|
|
svs.nextHeartbeatTime = -9999999;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
===========
|
|
|
|
SV_Serverinfo_f
|
|
|
|
|
|
|
|
Examine the serverinfo string
|
|
|
|
===========
|
|
|
|
*/
|
|
|
|
static void SV_Serverinfo_f( void ) {
|
|
|
|
Com_Printf ("Server info settings:\n");
|
|
|
|
Info_Print ( Cvar_InfoString( CVAR_SERVERINFO ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
===========
|
|
|
|
SV_Systeminfo_f
|
|
|
|
|
|
|
|
Examine or change the serverinfo string
|
|
|
|
===========
|
|
|
|
*/
|
|
|
|
static void SV_Systeminfo_f( void ) {
|
|
|
|
Com_Printf ("System info settings:\n");
|
|
|
|
Info_Print ( Cvar_InfoString( CVAR_SYSTEMINFO ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
===========
|
|
|
|
SV_DumpUser_f
|
|
|
|
|
|
|
|
Examine all a users info strings FIXME: move to game
|
|
|
|
===========
|
|
|
|
*/
|
|
|
|
static void SV_DumpUser_f( void ) {
|
|
|
|
client_t *cl;
|
|
|
|
|
|
|
|
// make sure server is running
|
|
|
|
if ( !com_sv_running->integer ) {
|
|
|
|
Com_Printf( "Server is not running.\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( Cmd_Argc() != 2 ) {
|
|
|
|
Com_Printf ("Usage: info <userid>\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
cl = SV_GetPlayerByHandle();
|
|
|
|
if ( !cl ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Com_Printf( "userinfo\n" );
|
|
|
|
Com_Printf( "--------\n" );
|
|
|
|
Info_Print( cl->userinfo );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
SV_KillServer
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
static void SV_KillServer_f( void ) {
|
|
|
|
SV_Shutdown( "killserver" );
|
|
|
|
}
|
|
|
|
|
2023-11-14 17:30:17 +01:00
|
|
|
|
|
|
|
|
|
|
|
//===========================================================
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_CompleteMapName
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static void SV_CompleteMapName( char *args, int argNum ) {
|
|
|
|
if( argNum == 2 ) {
|
|
|
|
Field_CompleteFilename( "maps", "bsp", qtrue, qfalse );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_CompletePlayerName
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static void SV_CompletePlayerName( char *args, int argNum ) {
|
|
|
|
if( argNum == 2 ) {
|
|
|
|
char names[MAX_CLIENTS][MAX_NAME_LENGTH];
|
|
|
|
const char *namesPtr[MAX_CLIENTS];
|
|
|
|
client_t *cl;
|
|
|
|
int i;
|
|
|
|
int nameCount;
|
|
|
|
int clientCount;
|
|
|
|
|
|
|
|
nameCount = 0;
|
|
|
|
clientCount = sv_maxclients->integer;
|
|
|
|
|
|
|
|
for ( i=0, cl=svs.clients ; i < clientCount; i++,cl++ ) {
|
|
|
|
if ( !cl->state ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if( i >= MAX_CLIENTS ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Q_strncpyz( names[nameCount], cl->name, sizeof(names[nameCount]) );
|
|
|
|
Q_CleanStr( names[nameCount] );
|
|
|
|
|
|
|
|
namesPtr[nameCount] = names[nameCount];
|
|
|
|
|
|
|
|
nameCount++;
|
|
|
|
}
|
|
|
|
qsort( (void*)namesPtr, nameCount, sizeof( namesPtr[0] ), Com_strCompare );
|
|
|
|
|
|
|
|
Field_CompletePlayerName( namesPtr, nameCount );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
/*
|
|
|
|
=================
|
|
|
|
SV_EasyMode_f
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
void SV_EasyMode_f( void )
|
|
|
|
{
|
|
|
|
Cvar_Set( "g_maxplayerhealth", "1500" );
|
|
|
|
Cvar_Set( "skill", "0" );
|
|
|
|
Com_Printf( "You are now setup for easy mode.\n" );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
SV_MediumMode_f
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
void SV_MediumMode_f( void )
|
|
|
|
{
|
|
|
|
Cvar_Set( "g_maxplayerhealth", "750" );
|
|
|
|
Cvar_Set( "skill", "1" );
|
|
|
|
Com_Printf( "You are now setup for medium mode.\n" );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
SV_HardMode_f
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
void SV_HardMode_f( void )
|
|
|
|
{
|
|
|
|
Cvar_Set( "g_maxplayerhealth", "250" );
|
|
|
|
Cvar_Set( "skill", "2" );
|
|
|
|
Com_Printf( "You are now setup for hard mode.\n" );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
SV_LoadLastGame_f
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
void SV_LoadLastGame_f( void )
|
|
|
|
{
|
|
|
|
Cbuf_AddText( va( "loadgame %s\n", Cvar_Get( "g_lastsave", "", 0 )->string ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
SV_TIKI_f
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
static void SV_TIKI_f( void ) {
|
|
|
|
char *fname;
|
|
|
|
fname = Cmd_Argv(1);
|
|
|
|
if(TIKI_RegisterModel(fname)==0)
|
|
|
|
{
|
|
|
|
char tmp[128];
|
|
|
|
strcpy(tmp,"models/");
|
|
|
|
strcat(tmp,fname);
|
|
|
|
TIKI_RegisterModel(tmp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
SV_TIKI_DumpBones_f
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
static void SV_TIKI_DumpBones_f( void ) {
|
|
|
|
char *fname;
|
|
|
|
tiki_t *tiki;
|
|
|
|
int i;
|
|
|
|
int filter = -1;
|
|
|
|
|
|
|
|
fname = Cmd_Argv(1);
|
|
|
|
tiki = TIKI_RegisterModel(fname);
|
|
|
|
if(tiki==0)
|
|
|
|
{
|
|
|
|
char tmp[128];
|
|
|
|
strcpy(tmp,"models/");
|
|
|
|
strcat(tmp,fname);
|
|
|
|
tiki = TIKI_RegisterModel(tmp);
|
|
|
|
if(!tiki)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(Cmd_Argc() > 2) {
|
|
|
|
filter = atoi(Cmd_Argv(2));
|
|
|
|
if(filter < 0 || filter > 6) {
|
|
|
|
filter = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for(i = 0; i < tiki->numBones; i++) {
|
|
|
|
// *((int*)tiki->bones[i]) is a boneType
|
|
|
|
if(filter != -1 && *((int*)tiki->bones[i]) != filter)
|
|
|
|
continue;
|
|
|
|
Com_Printf("Bone %i of %i ",i,tiki->numBones);
|
|
|
|
TIKI_PrintBoneInfo(tiki,i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//===========================================================
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_AddOperatorCommands
|
|
|
|
==================
|
|
|
|
*/
|
2023-11-14 17:30:17 +01:00
|
|
|
void SV_AddOperatorCommands(void) {
|
|
|
|
static qboolean initialized;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-11-14 17:30:17 +01:00
|
|
|
if (initialized) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
initialized = qtrue;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-11-14 17:30:17 +01:00
|
|
|
Cmd_AddCommand("heartbeat", SV_Heartbeat_f);
|
|
|
|
Cmd_AddCommand("kick", SV_Kick_f);
|
|
|
|
#ifndef STANDALONE
|
|
|
|
if (!com_standalone->integer)
|
|
|
|
{
|
|
|
|
Cmd_AddCommand("banUser", SV_Ban_f);
|
|
|
|
Cmd_AddCommand("banClient", SV_BanNum_f);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
Cmd_AddCommand("kickall", SV_KickAll_f);
|
|
|
|
Cmd_AddCommand("kicknum", SV_KickNum_f);
|
|
|
|
Cmd_AddCommand("clientkick", SV_KickNum_f); // Legacy command
|
|
|
|
Cmd_AddCommand("status", SV_Status_f);
|
|
|
|
Cmd_AddCommand("serverinfo", SV_Serverinfo_f);
|
|
|
|
Cmd_AddCommand("systeminfo", SV_Systeminfo_f);
|
|
|
|
Cmd_AddCommand("dumpuser", SV_DumpUser_f);
|
|
|
|
Cmd_AddCommand("restart", SV_MapRestart_f);
|
|
|
|
Cmd_AddCommand("sectorlist", SV_SectorList_f);
|
|
|
|
Cmd_AddCommand("spmap", SV_Map_f);
|
|
|
|
Cmd_AddCommand("spdevmap", SV_Map_f);
|
|
|
|
Cmd_AddCommand("map", SV_Map_f);
|
|
|
|
Cmd_AddCommand("devmap", SV_Map_f);
|
|
|
|
Cmd_AddCommand("gamemap", SV_GameMap_f);
|
|
|
|
|
|
|
|
Cmd_AddCommand("killserver", SV_KillServer_f);
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2023-11-14 17:30:17 +01:00
|
|
|
Cmd_AddCommand("savegame", SV_Savegame_f);
|
|
|
|
Cmd_AddCommand("autosavegame", SV_Autosavegame_f);
|
|
|
|
Cmd_AddCommand("loadgame", SV_Loadgame_f);
|
|
|
|
Cmd_AddCommand("loadlastgame", SV_LoadLastGame_f);
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-11-14 17:30:17 +01:00
|
|
|
if (com_dedicated->integer) {
|
|
|
|
Cmd_AddCommand("say", SV_ConSay_f);
|
|
|
|
Cmd_AddCommand("tell", SV_ConTell_f);
|
|
|
|
Cmd_AddCommand("sayto", SV_ConSayto_f);
|
2024-01-30 22:54:13 +00:00
|
|
|
Cmd_SetCommandCompletionFunc("sayto", (completionFunc_t)SV_CompletePlayerName);
|
2023-11-14 17:30:17 +01:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-11-14 17:30:17 +01:00
|
|
|
Cmd_AddCommand("rehashbans", SV_RehashBans_f);
|
|
|
|
Cmd_AddCommand("listbans", SV_ListBans_f);
|
|
|
|
Cmd_AddCommand("banaddr", SV_BanAddr_f);
|
|
|
|
Cmd_AddCommand("exceptaddr", SV_ExceptAddr_f);
|
|
|
|
Cmd_AddCommand("bandel", SV_BanDel_f);
|
|
|
|
Cmd_AddCommand("exceptdel", SV_ExceptDel_f);
|
|
|
|
Cmd_AddCommand("flushbans", SV_FlushBans_f);
|
|
|
|
|
|
|
|
Cmd_AddCommand("difficultyEasy", SV_EasyMode_f);
|
|
|
|
Cmd_AddCommand("difficultyMedium", SV_MediumMode_f);
|
|
|
|
Cmd_AddCommand("difficultyHard", SV_HardMode_f);
|
|
|
|
|
|
|
|
if (developer->integer) {
|
|
|
|
SV_MediumMode_f();
|
|
|
|
} else {
|
|
|
|
SV_EasyMode_f();
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_RemoveOperatorCommands
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_RemoveOperatorCommands( void ) {
|
|
|
|
#if 0
|
|
|
|
// removing these won't let the server start again
|
|
|
|
Cmd_RemoveCommand ("heartbeat");
|
|
|
|
Cmd_RemoveCommand ("kick");
|
|
|
|
Cmd_RemoveCommand ("banUser");
|
|
|
|
Cmd_RemoveCommand ("banClient");
|
|
|
|
Cmd_RemoveCommand ("status");
|
|
|
|
Cmd_RemoveCommand ("serverinfo");
|
|
|
|
Cmd_RemoveCommand ("systeminfo");
|
|
|
|
Cmd_RemoveCommand ("dumpuser");
|
|
|
|
Cmd_RemoveCommand ("map_restart");
|
|
|
|
Cmd_RemoveCommand ("sectorlist");
|
|
|
|
Cmd_RemoveCommand ("say");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_ArchiveHudDrawElements
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_ArchiveHudDrawElements( qboolean loading )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
int i;
|
|
|
|
|
|
|
|
for( i = 0; i < MAX_HUDDRAW_ELEMENTS; i++ )
|
|
|
|
{
|
|
|
|
ge->ArchiveString( cls.HudDrawElements[ i ].shaderName );
|
|
|
|
ge->ArchiveInteger( &cls.HudDrawElements[ i ].iX );
|
|
|
|
ge->ArchiveInteger( &cls.HudDrawElements[ i ].iY );
|
|
|
|
ge->ArchiveInteger( &cls.HudDrawElements[ i ].iWidth );
|
2024-08-31 16:38:50 +02:00
|
|
|
ge->ArchiveInteger( &cls.HudDrawElements[ i ].iHeight );
|
2016-03-27 11:49:47 +02:00
|
|
|
ge->ArchiveFloat( &cls.HudDrawElements[ i ].vColor[ 0 ] );
|
|
|
|
ge->ArchiveFloat( &cls.HudDrawElements[ i ].vColor[ 1 ] );
|
|
|
|
ge->ArchiveFloat( &cls.HudDrawElements[ i ].vColor[ 2 ] );
|
|
|
|
ge->ArchiveFloat( &cls.HudDrawElements[ i ].vColor[ 3 ] );
|
|
|
|
ge->ArchiveInteger( &cls.HudDrawElements[ i ].iHorizontalAlign );
|
|
|
|
ge->ArchiveInteger( &cls.HudDrawElements[ i ].iVerticalAlign );
|
|
|
|
ge->ArchiveInteger( &cls.HudDrawElements[ i ].bVirtualScreen );
|
|
|
|
ge->ArchiveString( cls.HudDrawElements[ i ].fontName );
|
|
|
|
ge->ArchiveString( cls.HudDrawElements[ i ].string );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( loading ) {
|
|
|
|
if( cge ) {
|
|
|
|
cge->CG_RefreshHudDrawElements();
|
|
|
|
}
|
2023-09-05 20:22:01 +02:00
|
|
|
}
|
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_HudDrawShader
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_HudDrawShader( int iInfo, char *name )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
strcpy( cls.HudDrawElements[ iInfo ].shaderName, name );
|
|
|
|
cls.HudDrawElements[ iInfo ].string[ 0 ] = 0;
|
|
|
|
cls.HudDrawElements[ iInfo ].pFont = NULL;
|
|
|
|
cls.HudDrawElements[ iInfo ].fontName[ 0 ] = 0;
|
|
|
|
|
|
|
|
if( cge ) {
|
|
|
|
cge->CG_HudDrawShader( iInfo );
|
|
|
|
}
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_HudDrawAlign
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_HudDrawAlign( int iInfo, int iHorizontalAlign, int iVerticalAlign )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
cls.HudDrawElements[ iInfo ].iHorizontalAlign = iHorizontalAlign;
|
|
|
|
cls.HudDrawElements[ iInfo ].iVerticalAlign = iVerticalAlign;
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_HudDrawRect
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_HudDrawRect( int iInfo, int iX, int iY, int iWidth, int iHeight )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
cls.HudDrawElements[ iInfo ].iX = iX;
|
|
|
|
cls.HudDrawElements[ iInfo ].iY = iY;
|
|
|
|
cls.HudDrawElements[ iInfo ].iWidth = iWidth;
|
|
|
|
cls.HudDrawElements[ iInfo ].iHeight = iHeight;
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_HudDrawVirtualSize
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_HudDrawVirtualSize( int iInfo, qboolean bVirtualScreen )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
cls.HudDrawElements[ iInfo ].bVirtualScreen = bVirtualScreen;
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_HudDrawColor
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_HudDrawColor( int iInfo, vec3_t vColor )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
VectorCopy( vColor, cls.HudDrawElements[ iInfo ].vColor );
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_HudDrawAlpha
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_HudDrawAlpha( int iInfo, float alpha )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
cls.HudDrawElements[ iInfo ].vColor[ 3 ] = alpha;
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_HudDrawString
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_HudDrawString( int iInfo, const char *string )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
cls.HudDrawElements[ iInfo ].hShader = 0;
|
|
|
|
strcpy( cls.HudDrawElements[ iInfo ].string, string );
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_HudDrawFont
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_HudDrawFont( int iInfo, const char *name )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
strcpy( cls.HudDrawElements[ iInfo ].fontName, name );
|
|
|
|
cls.HudDrawElements[ iInfo ].hShader = 0;
|
|
|
|
cls.HudDrawElements[ iInfo ].shaderName[ 0 ] = 0;
|
|
|
|
|
|
|
|
if( cge ) {
|
|
|
|
cge->CG_HudDrawFont( iInfo );
|
|
|
|
}
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_ArchiveViewModelAnimation
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_ArchiveViewModelAnimation( qboolean loading )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2023-09-05 20:00:54 +02:00
|
|
|
int i;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-09-05 20:00:54 +02:00
|
|
|
for (i = 0; i < MAX_FRAMEINFOS; i++)
|
|
|
|
{
|
|
|
|
ge->ArchiveInteger(&cls.anim.g_VMFrameInfo[i].index);
|
|
|
|
ge->ArchiveFloat(&cls.anim.g_VMFrameInfo[i].time);
|
|
|
|
ge->ArchiveFloat(&cls.anim.g_VMFrameInfo[i].weight);
|
|
|
|
}
|
|
|
|
|
|
|
|
ge->ArchiveInteger(&cls.anim.g_iLastVMAnim);
|
|
|
|
ge->ArchiveInteger(&cls.anim.g_iLastVMAnimChanged);
|
|
|
|
ge->ArchiveInteger(&cls.anim.g_iCurrentVMAnimSlot);
|
|
|
|
ge->ArchiveInteger(&cls.anim.g_iCurrentVMDuration);
|
|
|
|
ge->ArchiveInteger(&cls.anim.g_bCrossblending);
|
|
|
|
ge->ArchiveInteger(&cls.anim.g_iLastEquippedWeaponStat);
|
|
|
|
ge->ArchiveString(cls.anim.g_szLastActiveItem);
|
|
|
|
ge->ArchiveInteger(&cls.anim.g_iLastAnimPrefixIndex);
|
|
|
|
ge->ArchiveFloat(&cls.anim.g_vCurrentVMPosOffset[0]);
|
|
|
|
ge->ArchiveFloat(&cls.anim.g_vCurrentVMPosOffset[1]);
|
|
|
|
ge->ArchiveFloat(&cls.anim.g_vCurrentVMPosOffset[2]);
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_ArchiveStopwatch
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_ArchiveStopwatch( qboolean loading )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
ge->ArchiveSvsTime( &cls.stopwatch.iStartTime );
|
|
|
|
ge->ArchiveSvsTime( &cls.stopwatch.iEndTime );
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_ArchivePersistantFile
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_ArchivePersistantFile( qboolean loading )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
const char *name;
|
|
|
|
|
|
|
|
Com_DPrintf( "SV_ArchivePersistantFile()\n" );
|
|
|
|
name = Com_GetArchiveFileName( svs.gameName, "spv" );
|
|
|
|
ge->ArchivePersistant( name, loading );
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_ArchiveLevel
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_ArchiveLevel( qboolean loading )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
SV_ArchiveHudDrawElements( loading );
|
|
|
|
SV_ArchiveViewModelAnimation( loading );
|
|
|
|
SV_ArchiveStopwatch( loading );
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_ArchiveLevelFile
|
|
|
|
==================
|
|
|
|
*/
|
2023-09-05 19:51:31 +02:00
|
|
|
qboolean SV_ArchiveLevelFile(qboolean loading, qboolean autosave)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2023-09-05 19:51:31 +02:00
|
|
|
const char* name;
|
|
|
|
fileHandle_t f;
|
|
|
|
savegamestruct_t save;
|
|
|
|
soundsystemsavegame_t SSsave;
|
|
|
|
|
|
|
|
Com_DPrintf("SV_ArchiveLevelFile()\n");
|
|
|
|
name = Com_GetArchiveFileName(svs.gameName, "sav");
|
|
|
|
if (loading)
|
|
|
|
{
|
2024-08-20 22:30:18 +10:00
|
|
|
if (!ge->ReadLevel(name, (byte**)&cls.savedCgameState, &cls.savedCgameStateSize)) {
|
2023-09-05 20:33:02 +02:00
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
name = Com_GetArchiveFileName(svs.gameName, "ssv");
|
|
|
|
FS_FOpenFileRead(name, &f, qfalse, qtrue);
|
|
|
|
if (f)
|
2023-09-05 19:51:31 +02:00
|
|
|
{
|
2023-09-05 20:33:02 +02:00
|
|
|
FS_Read(&save, sizeof(savegamestruct_t), f);
|
2024-08-23 21:09:12 +02:00
|
|
|
Com_SwapSaveStruct(&save);
|
2023-11-26 20:33:07 +01:00
|
|
|
if (save.version != SAVEGAME_STRUCT_VERSION)
|
2023-09-05 19:51:31 +02:00
|
|
|
{
|
|
|
|
FS_FCloseFile(f);
|
2023-09-05 20:33:02 +02:00
|
|
|
return qfalse;
|
2023-09-05 19:51:31 +02:00
|
|
|
}
|
2023-09-05 20:33:02 +02:00
|
|
|
|
|
|
|
FS_Read(&SSsave, sizeof(soundsystemsavegame_t), f);
|
|
|
|
CM_ReadPortalState(f);
|
|
|
|
FS_FCloseFile(f);
|
2023-09-05 19:51:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-09-05 20:33:02 +02:00
|
|
|
cls.savedCgameStateSize = cge->CG_SaveStateToBuffer(&cls.savedCgameState, svs.time);
|
2024-08-20 22:30:18 +10:00
|
|
|
ge->WriteLevel(name, autosave, (byte**)&cls.savedCgameState, &cls.savedCgameStateSize);
|
2023-09-05 20:33:02 +02:00
|
|
|
Z_Free(cls.savedCgameState);
|
|
|
|
cls.savedCgameState = NULL;
|
2023-09-05 19:51:31 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-09-05 20:33:02 +02:00
|
|
|
return qtrue;
|
2023-09-05 20:22:01 +02:00
|
|
|
#else
|
|
|
|
return qfalse;
|
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
S_Save
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void S_Save( fileHandle_t f )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
soundsystemsavegame_t save;
|
|
|
|
S_SaveData( &save );
|
|
|
|
FS_Write( &save, sizeof( soundsystemsavegame_t ), f );
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
S_Load
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void S_Load( fileHandle_t f )
|
|
|
|
{
|
2023-08-16 18:13:30 +02:00
|
|
|
#ifndef DEDICATED
|
|
|
|
FS_Read( &svs.soundSystem, sizeof( svs.soundSystem ), f );
|
|
|
|
S_LoadData(&svs.soundSystem);
|
2016-03-27 11:49:47 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_ArchiveServerFile
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
qboolean SV_ArchiveServerFile( qboolean loading, qboolean autosave )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
fileHandle_t f;
|
|
|
|
const char *name;
|
|
|
|
char comment[ 64 ];
|
|
|
|
savegamestruct_t save;
|
|
|
|
char cmdString[ 256 ];
|
|
|
|
time_t aclock;
|
|
|
|
|
|
|
|
Com_DPrintf( "SV_ArchiveServerFile(%s)\n", autosave ? "true" : "false" );
|
2024-07-22 20:07:32 +02:00
|
|
|
memset(&save, 0, sizeof(save));
|
2016-03-27 11:49:47 +02:00
|
|
|
name = Com_GetArchiveFileName( svs.gameName, "ssv" );
|
|
|
|
|
|
|
|
if( !loading )
|
|
|
|
{
|
|
|
|
f = FS_FOpenFileWrite( name );
|
|
|
|
if( !f )
|
|
|
|
{
|
|
|
|
Com_Printf( "Couldn't write %s\n", name );
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
time( &aclock );
|
|
|
|
|
|
|
|
if( autosave )
|
|
|
|
{
|
|
|
|
Com_sprintf( comment, sizeof( comment ), "%s - Starting", sv.configstrings[ CS_MESSAGE ] );
|
|
|
|
}
|
|
|
|
else if( sv.configstrings[ CS_SAVENAME ] && *sv.configstrings[ CS_SAVENAME ] )
|
|
|
|
{
|
|
|
|
Com_sprintf( comment, sizeof( comment ), "%s - %s", sv.configstrings[ CS_MESSAGE ], sv.configstrings[ CS_SAVENAME ] );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Com_sprintf( comment, sizeof( comment ), "%s", sv.configstrings[ CS_MESSAGE ] );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( strstr( name, "quick.ssv" ) ) {
|
|
|
|
Com_sprintf( save.comment, sizeof( save.comment ), "QuickSave - %s", comment );
|
|
|
|
} else {
|
2023-12-30 19:09:31 +01:00
|
|
|
strcpy( save.comment, comment );
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
SV_SetConfigstring( CS_SAVENAME, "" );
|
|
|
|
|
2023-11-26 20:33:07 +01:00
|
|
|
save.version = SAVEGAME_STRUCT_VERSION;
|
2024-08-23 22:25:16 +02:00
|
|
|
save.type = com_target_game->integer;
|
|
|
|
save.flags = 0;
|
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
save.time = aclock;
|
|
|
|
strncpy( save.mapName, svs.mapName, sizeof( save.mapName ) );
|
|
|
|
strncpy( save.saveName, svs.gameName, sizeof( save.saveName ) );
|
|
|
|
save.mapTime = svs.time - svs.startTime;
|
|
|
|
|
2024-07-22 20:07:32 +02:00
|
|
|
name = S_GetMusicFilename();
|
|
|
|
if (name) {
|
|
|
|
Q_strncpyz(save.tm_filename, name, sizeof(save.tm_filename));
|
|
|
|
save.tm_offset = S_GetMusicOffset();
|
|
|
|
save.tm_loopcount = S_GetMusicLoopCount();
|
|
|
|
} else {
|
|
|
|
save.tm_filename[0] = 0;
|
|
|
|
save.tm_offset = 0;
|
|
|
|
save.tm_loopcount = 0;
|
|
|
|
}
|
|
|
|
|
2024-08-23 21:09:12 +02:00
|
|
|
Com_SwapSaveStruct(&save);
|
2016-03-27 11:49:47 +02:00
|
|
|
FS_Write( &save, sizeof( savegamestruct_t ), f );
|
|
|
|
S_Save( f );
|
|
|
|
CM_WritePortalState( f );
|
|
|
|
FS_FCloseFile( f );
|
|
|
|
|
|
|
|
name = Com_GetArchiveFileName( svs.gameName, "tga" );
|
|
|
|
Com_sprintf( cmdString, sizeof( cmdString ), "saveshot %s 256 256\n", name );
|
2024-07-20 16:07:02 +02:00
|
|
|
Cbuf_ExecuteText( svs.autosave ? EXEC_INSERT : EXEC_NOW, cmdString );
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
FS_FOpenFileRead( name, &f, qfalse, qtrue );
|
|
|
|
if( !f )
|
|
|
|
{
|
|
|
|
Com_Printf( "Couldn't read %s\n", name );
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
2023-11-26 20:33:07 +01:00
|
|
|
FS_Read(&save, sizeof(savegamestruct_t), f);
|
2024-08-23 21:09:12 +02:00
|
|
|
Com_SwapSaveStruct(&save);
|
2023-11-26 20:33:07 +01:00
|
|
|
if (save.version != SAVEGAME_STRUCT_VERSION)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-02-01 00:28:40 +01:00
|
|
|
Com_Printf("Invalid or Old Server SaveGame Version\n");
|
2016-03-27 11:49:47 +02:00
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
2023-09-05 20:00:54 +02:00
|
|
|
S_StopAllSounds2( qtrue );
|
2016-03-27 11:49:47 +02:00
|
|
|
S_Load( f );
|
|
|
|
strncpy( svs.mapName, save.mapName, sizeof( svs.mapName ) );
|
|
|
|
svs.mapTime = save.mapTime;
|
|
|
|
svs.areabits_warning_time = 0;
|
|
|
|
strcpy( svs.tm_filename, save.tm_filename );
|
|
|
|
svs.tm_loopcount = save.tm_loopcount;
|
|
|
|
svs.tm_offset = save.tm_offset;
|
|
|
|
FS_FCloseFile( f );
|
|
|
|
}
|
|
|
|
|
|
|
|
return qtrue;
|
2023-09-05 20:22:01 +02:00
|
|
|
#else
|
|
|
|
return qfalse;
|
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_Loadgame_f
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_Loadgame_f( void )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
int length;
|
|
|
|
const char *name;
|
|
|
|
const char *archive_name;
|
|
|
|
qboolean bStartedGame;
|
|
|
|
|
|
|
|
if( com_cl_running && com_cl_running->integer &&
|
2023-05-26 20:53:00 +02:00
|
|
|
clc.state != CA_DISCONNECTED && cg_gametype->integer ||
|
2016-03-27 11:49:47 +02:00
|
|
|
com_sv_running && com_sv_running->integer && g_gametype->integer )
|
|
|
|
{
|
|
|
|
Com_Printf( "Can't loadgame in a multiplayer game\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Cvar_Set( "g_gametype", "0" );
|
|
|
|
Cvar_Set( "sv_maxclients", "1" );
|
|
|
|
Cvar_Get( "g_gametype", "0", CVAR_LATCH | CVAR_SERVERINFO );
|
|
|
|
Cvar_Get( "sv_maxclients", "0", CVAR_LATCH | CVAR_SERVERINFO );
|
|
|
|
|
|
|
|
if( developer->integer )
|
|
|
|
{
|
|
|
|
if( svs.iNumClients == 1 )
|
|
|
|
{
|
|
|
|
Cvar_Set( "cheats", "1" );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Cvar_Set( "cheats", "0" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( sv.state == SS_LOADING || sv.state == SS_LOADING2 )
|
|
|
|
{
|
|
|
|
Com_Printf( "Can't load game when loading\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( Cmd_Argc() != 2 )
|
|
|
|
{
|
|
|
|
Com_Printf( "USAGE: loadgame <name>\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
name = Cmd_Argv( 1 );
|
|
|
|
Com_Printf( "Loading game...%s\n", name );
|
|
|
|
if( strstr( name, ".." ) || strchr( name, '/' ) || strchr( name, '\\' ) ) {
|
|
|
|
Com_Printf( "Bad savedir.\n" );
|
|
|
|
}
|
|
|
|
|
|
|
|
strcpy( svs.gameName, name );
|
|
|
|
archive_name = Com_GetArchiveFileName( name, "sav" );
|
|
|
|
Cvar_SaveGameRestart_f();
|
|
|
|
|
|
|
|
length = FS_ReadFileEx( archive_name, NULL, qtrue );
|
|
|
|
if( length == -1 )
|
|
|
|
{
|
|
|
|
Com_Printf( "Savegame not found.\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-08-30 18:56:01 +02:00
|
|
|
bStartedGame = qfalse;
|
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
if( !ge )
|
|
|
|
{
|
|
|
|
SV_InitGameProgs();
|
|
|
|
bStartedGame = qtrue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( ge->LevelArchiveValid( archive_name ) && SV_ArchiveServerFile( qtrue, qfalse ) )
|
|
|
|
{
|
|
|
|
SV_SpawnServer( svs.mapName, qtrue, qfalse, qfalse );
|
|
|
|
svs.soundsNeedLoad = qtrue;
|
|
|
|
}
|
|
|
|
else if( bStartedGame )
|
|
|
|
{
|
|
|
|
SV_ShutdownGameProgs();
|
|
|
|
}
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_SavegameFilename
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_SavegameFilename( const char *name, char *fileName, int length )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
int num;
|
|
|
|
const char *fname;
|
|
|
|
int lastNumber;
|
|
|
|
int len;
|
|
|
|
int a;
|
|
|
|
int b;
|
|
|
|
int c;
|
|
|
|
|
|
|
|
for( num = 0; num < 10000; num++ )
|
|
|
|
{
|
|
|
|
a = num / 1000;
|
|
|
|
b = num % 1000 / 100;
|
|
|
|
c = num % 1000 % 100 / 10;
|
|
|
|
lastNumber = num % 1000 % 100 % 10;
|
|
|
|
Com_sprintf( fileName, length, "%s%i%i%i%i", name, a, b, c, lastNumber );
|
|
|
|
fname = Com_GetArchiveFileName( fileName, "ssv" );
|
|
|
|
len = FS_ReadFileEx( fname, NULL, qtrue );
|
|
|
|
if( len <= 0 ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_AllowSaveGame
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
qboolean SV_AllowSaveGame( void )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
if( !com_sv_running || !com_sv_running->integer )
|
|
|
|
{
|
|
|
|
Com_DPrintf( "You must be in a game with a server to save.\n" );
|
2023-08-13 20:18:46 +02:00
|
|
|
return qfalse;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
else if( !com_cl_running || !com_cl_running->integer )
|
|
|
|
{
|
|
|
|
Com_DPrintf( "You must be in a game with a client to save.\n" );
|
2023-08-13 20:18:46 +02:00
|
|
|
return qfalse;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
else if( sv.state != SS_GAME )
|
|
|
|
{
|
|
|
|
Com_DPrintf( "You must be in game to save.\n" );
|
2023-08-13 20:18:46 +02:00
|
|
|
return qfalse;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
2023-05-26 20:53:00 +02:00
|
|
|
else if( clc.state != CA_DISCONNECTED && cg_gametype->integer )
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
|
|
|
Com_DPrintf( "Can't savegame in a multiplayer game\n" );
|
2023-08-13 20:18:46 +02:00
|
|
|
return qfalse;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
else if( g_gametype->integer )
|
|
|
|
{
|
|
|
|
Com_DPrintf( "Can't savegame in a multiplayer game\n" );
|
2023-08-13 20:18:46 +02:00
|
|
|
return qfalse;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
2023-08-13 20:18:46 +02:00
|
|
|
else if( !svs.clients || svs.clients->gentity == NULL || svs.clients->gentity->client == NULL || !svs.clients->gentity->client->ps.stats[ 0 ] )
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
|
|
|
Com_DPrintf( "Can't savegame when dead\n" );
|
2023-08-13 20:18:46 +02:00
|
|
|
return qfalse;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
else if( sv.state == SS_LOADING || sv.state == SS_LOADING2 )
|
|
|
|
{
|
|
|
|
Com_DPrintf( "Can't save game when loading\n" );
|
2023-08-13 20:18:46 +02:00
|
|
|
return qfalse;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return qtrue;
|
2023-09-05 20:22:01 +02:00
|
|
|
#else
|
|
|
|
return qfalse;
|
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_DoSaveGame
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static qboolean bSavegame;
|
|
|
|
|
|
|
|
qboolean SV_DoSaveGame()
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
if( bSavegame )
|
|
|
|
{
|
|
|
|
if( SV_AllowSaveGame() ) {
|
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
bSavegame = qfalse;
|
|
|
|
}
|
|
|
|
return qfalse;
|
2023-09-05 20:22:01 +02:00
|
|
|
#else
|
|
|
|
return qfalse;
|
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_SaveGame
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_SaveGame( const char *gamename, qboolean autosave )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
char *ptr;
|
|
|
|
char name[ 64 ];
|
|
|
|
char mname[ 64 ];
|
|
|
|
|
|
|
|
if( !SV_AllowSaveGame() ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( gamename )
|
|
|
|
{
|
|
|
|
strcpy( name, gamename );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
strcpy( mname, svs.mapName );
|
|
|
|
for( ptr = strchr( mname, '/' ); ptr != NULL; ptr = strchr( mname, '/' ) )
|
|
|
|
{
|
|
|
|
*ptr = '_';
|
|
|
|
}
|
|
|
|
SV_SavegameFilename( mname, name, sizeof( name ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( strstr( name, ".." ) || strchr( name, '/' ) || strchr( name, '\\' ) )
|
|
|
|
{
|
|
|
|
Com_DPrintf( "Bad savedir.\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Com_Printf( "Saving to %s", name );
|
|
|
|
if( autosave ) {
|
2024-07-01 14:15:00 +02:00
|
|
|
Com_Printf( " (autosave)...\n" );
|
2016-03-27 11:49:47 +02:00
|
|
|
} else {
|
2024-07-01 14:15:00 +02:00
|
|
|
Com_Printf( "...\n" );
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
strcpy( svs.gameName, name );
|
2024-07-15 23:45:32 +02:00
|
|
|
|
|
|
|
if (!SV_ArchiveLevelFile(qfalse, autosave)) {
|
|
|
|
if (cls.savedCgameState) {
|
|
|
|
Z_Free(cls.savedCgameState);
|
|
|
|
cls.savedCgameState = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
SV_ArchiveServerFile( qfalse, autosave );
|
2024-07-15 23:45:32 +02:00
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
Com_Printf( "Done.\n" );
|
2024-07-15 23:45:32 +02:00
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
strcpy( svs.gameName, "current" );
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_Savegame_f
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
static char savegame_name[ 64 ];
|
|
|
|
|
|
|
|
void SV_Savegame_f( void )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
char *s;
|
|
|
|
|
|
|
|
if( !SV_AllowSaveGame() ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( Cmd_Argc() == 2 )
|
|
|
|
{
|
|
|
|
|
|
|
|
s = Cmd_Argv( 1 );
|
|
|
|
if( strlen( s ) >= sizeof( savegame_name ) ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
strcpy( savegame_name, s );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
savegame_name[ 0 ] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bSavegame = qtrue;
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
SV_CheckSaveGame
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void SV_CheckSaveGame( void )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
if( !SV_DoSaveGame() ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( cl.serverTime >= svs.time )
|
|
|
|
{
|
|
|
|
bSavegame = qfalse;
|
|
|
|
SV_SaveGame( savegame_name[ 0 ] ? savegame_name : NULL, qfalse );
|
|
|
|
UI_SetupFiles();
|
|
|
|
}
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void SV_Autosavegame_f( void )
|
|
|
|
{
|
2023-09-05 20:22:01 +02:00
|
|
|
#ifndef DEDICATED
|
2016-03-27 11:49:47 +02:00
|
|
|
SV_SaveGame( NULL, qtrue );
|
2023-09-05 20:22:01 +02:00
|
|
|
#endif
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|