diff --git a/src/game/g_active.c b/src/game/g_active.c index 4360330..ab2ca47 100644 --- a/src/game/g_active.c +++ b/src/game/g_active.c @@ -935,6 +935,12 @@ void ClientTimerActions( gentity_t *ent, int msec ) } } } + + // spree decay + if( ent->client->pers.statscounters.spreefeeds > 1 ) + ent->client->pers.statscounters.spreefeeds -= 2; + if( ent->client->pers.statscounters.spreekills > 1 ) + ent->client->pers.statscounters.spreekills -= 2; // turn off life support when a team admits defeat if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && diff --git a/src/game/g_admin.c b/src/game/g_admin.c index 8476448..159f0a0 100644 --- a/src/game/g_admin.c +++ b/src/game/g_admin.c @@ -104,7 +104,7 @@ g_admin_cmd_t g_admin_cmds[ ] = "[^3name|slot#^7] [^3#^7]" }, - {"demo", G_admin_demo, "W", + {"demo", G_admin_demo, "~", "turn admin chat off for the caller so it does not appear in demos. " "this is a toggle use !demo again to turn warnings back on", "" @@ -144,7 +144,12 @@ g_admin_cmd_t g_admin_cmds[ ] = "cause a player to explode. Caution: damages surrounding players and structures.", "[^3name|slot#^7]" }, - + + {"flag", G_admin_flag, "f", + "add an admin flag to a player, prefix flag with '-' to disallow", + "[^5slot#^7] [^3-^7][^3flag^7]" + }, + {"forcespec", G_admin_forcespec, "F", "disable joining of teams for a player", "[^3name|slot#^7]" @@ -281,6 +286,11 @@ g_admin_cmd_t g_admin_cmds[ ] = "(^5xnum^7) (^5#ID^7) (^5-name|num^7) (^5a|h^7)" }, + {"seen", G_admin_seen, "D", + "find the last time a player was on the server", + "[^3name|admin#^7]" + }, + {"setlevel", G_admin_setlevel, "s", "sets the admin level of a player", "[^3name|slot#|admin#^7] [^3level^7]" @@ -347,6 +357,11 @@ g_admin_cmd_t g_admin_cmds[ ] = "revoke designated builder privileges", "[^3name|slot#^7]" }, + + {"unflag", G_admin_flag, "f", + "clears an admin flag from a player", + "[^5slot#^7] [^3flag^7]" + }, {"unforcespec", G_admin_unforcespec, "F", "enable joining of teams for a player", @@ -784,6 +799,219 @@ static void admin_readconfig_int( char **cnf, int *v ) *v = atoi( t ); } +void G_admin_chat_writeconfig( void ) +{ + fileHandle_t f; + int len; + char keybuf[ 16 ]; + int i, j; + qboolean found; + + if( !g_chat.string[ 0 ] ) + { + G_Printf( "WARNING: g_chat is not set. " + " channel subscriptions will not be saved to a file.\n" ); + return; + } + len = trap_FS_FOpenFile( g_chat.string, &f, FS_WRITE ); + if( len < 0 ) + { + G_Printf( "chat_writeconfig: could not open g_chat file \"%s\"\n", + g_chat.string ); + return; + } + for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) + { + // don't write level 0 users + if( g_admin_admins[ i ]->level < 1 ) + continue; + + // don't write users not joined to a channel + found = qfalse; + for( j = 0; j < CHAT_MAXCHAN; j++ ) + { + if( g_admin_admins[ i ]->chat[ j ][ 0 ] ) + found = qtrue; + } + if( !found && !g_admin_admins[ i ]->seen ) + continue; + + trap_FS_Write( "[chat]\n", 7, f ); + trap_FS_Write( "guid = ", 10, f ); + admin_writeconfig_string( g_admin_admins[ i ]->guid, f ); + trap_FS_Write( "seen = ", 10, f ); + admin_writeconfig_int( g_admin_admins[ i ]->seen, f ); + + for( j = 0 ; j < CHAT_MAXCHAN; j++ ) + { + if( g_admin_admins[ i ]->chat[ j ][ 0 ] ) + { + Com_sprintf( keybuf, sizeof( keybuf ), "%d = ", j ); + trap_FS_Write( keybuf, 10, f ); + admin_writeconfig_string( g_admin_admins[ i ]->chat[ j ], f ); + } + } + + trap_FS_Write( "\n", 1, f ); + } + trap_FS_FCloseFile( f ); +} + +qboolean G_admin_chat_readconfig( gentity_t *ent ) +{ + g_admin_admin_t *a = NULL; + fileHandle_t f; + int len; + char *cnf, *cnf2; + char *t; + int uc = 0, cc = 0; + qboolean chat_open; + char guid[ 33 ]; + int i; + + if( !g_chat.string[ 0 ] ) + { + ADMP( "chat_readconfig: g_chat is not set, not loading channel subscriptions " + "from a file\n" ); + return qfalse; + } + + len = trap_FS_FOpenFile( g_chat.string, &f, FS_READ ) ; + if( len < 0 ) + { + ADMP( va( "chat_readconfig: could not open chat config file %s\n", + g_chat.string ) ); + return qfalse; + } + cnf = G_Alloc( len + 1 ); + cnf2 = cnf; + trap_FS_Read( cnf, len, f ); + *( cnf + len ) = '\0'; + trap_FS_FCloseFile( f ); + + t = COM_Parse( &cnf ); + chat_open = qfalse; + while( *t ) + { + if( !Q_stricmp( t, "[chat]" ) ) + { + chat_open = qtrue; + a = NULL; + } + else if( chat_open ) + { + int chan; + + if( !Q_stricmp( t, "guid" ) ) + { + admin_readconfig_string( &cnf, guid, sizeof( guid ) ); + for( i = 0 ; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) + { + if( !Q_stricmp( guid, g_admin_admins[ i ]->guid ) ) + { + a = g_admin_admins[ i ]; + memset( a->chat, 0, sizeof( a->chat) ); + uc++; + break; + } + } + } + else if( !Q_stricmp( t, "seen" ) ) + { + admin_readconfig_int( &cnf, &a->seen ); + } + else if( *t >= '0' && *t <= '9' ) + { + chan = atoi( t ); + if( chan >= 0 && chan < CHAT_MAXCHAN && a ) + { + admin_readconfig_string( &cnf, a->chat[ chan ], sizeof( a->chat[ chan ] ) ); + cc++; + } + } + else + { + ADMP( va( "chat_readconfig: [chat] parse error near %s on line %d\n", + t, COM_GetCurrentParseLine() ) ); + } + } + + t = COM_Parse( &cnf ); + } + + G_Free( cnf2 ); + ADMP( va( "chat_readconfig: loaded %d users with %d channels\n", uc, cc ) ); + return qtrue; +} + +void G_admin_chat_sync( gentity_t *ent ) +{ + gentity_t *target; + int i, j; + qboolean rejoin = qfalse; + + if( !ent || !ent->client || ent->client->pers.adminLevel < 1 ) + return; + + for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] ; i++ ) + { + if( !Q_stricmp( ent->client->pers.guid, g_admin_admins[ i ]->guid ) ) + { + for( j = 0; j < CHAT_MAXCHAN; j++ ) + { + Q_strncpyz( ent->client->pers.chat[ j ], + g_admin_admins[ i ]->chat[ j ], + sizeof( g_admin_admins[ i ]->chat[ j ] ) ); + } + rejoin = qtrue; + break; + } + } + + if( !rejoin ) + return; + + for( j = 0; j < CHAT_MAXCHAN; j++ ) + { + if( !ent->client->pers.chat[ j ][ 0 ] ) + continue; + + for( i = 0; i < level.maxclients; i++ ) + { + target = &g_entities[ i ]; + if( target && target->client && + target->client->pers.connected == CON_CONNECTED && + !Q_stricmp( target->client->pers.chat[ j ], ent->client->pers.chat[ j ] ) ) + { + trap_SendServerCommand( i, va( "print \"join: %s^7 has rejoined channel #%d\n\"", + ent->client->pers.netname, j ) ); + } + } + } +} + +void G_admin_chat_update( gentity_t *ent, int chan ) +{ + int i; + + if( !ent || !ent->client || ent->client->pers.adminLevel < 1 ) + return; + + if( chan < 0 || chan > CHAT_MAXCHAN - 1 ) + return; + + for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] ; i++ ) + { + if( !Q_stricmp( ent->client->pers.guid, g_admin_admins[ i ]->guid ) ) + { + Q_strncpyz( g_admin_admins[ i ]->chat[ chan ], + ent->client->pers.chat[ chan ], + sizeof( g_admin_admins[ i ]->chat[ chan ] ) ); + return; + } + } +} + // if we can't parse any levels from readconfig, set up default // ones to make new installs easier for admins static void admin_default_levels( void ) @@ -1372,6 +1600,8 @@ void G_admin_namelog_update( gclient_t *client, qboolean disconnect ) char n2[ MAX_NAME_LENGTH ]; int clientNum = ( client - level.clients ); + G_admin_seen_update( client->pers.guid ); + G_SanitiseName( client->pers.netname, n1 ); for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) { @@ -1637,6 +1867,7 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg ) *a->guid = '\0'; a->level = 0; *a->flags = '\0'; + a->seen = 0; admin_open = qtrue; } else if( !Q_stricmp( t, "[ban]" ) ) @@ -1695,6 +1926,9 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg ) admin_level_maxname = strlen( n ); } } + + G_admin_chat_readconfig( ent ); + return qtrue; } @@ -3604,6 +3838,10 @@ void G_admin_info_parse( char *unparsed, char *dest, int maxlength ) cursor++; //skip me } } + else if( unparsed[cursor] == '\r' ) + { + i--; // skip unprintable characters for this cycle + } else if( unparsed[cursor] == '$' ) { //read variable if( unparsed[1+cursor++] == '{' ) cursor++; // skip $ and { if there @@ -5748,6 +5986,126 @@ qboolean G_admin_L0(gentity_t *ent, int skiparg ){ return qtrue; } +qboolean G_admin_seen(gentity_t *ent, int skiparg ) +{ + char name[ MAX_NAME_LENGTH ]; + char search[ MAX_NAME_LENGTH ]; + char sduration[ 32 ]; + qboolean numeric = qtrue; + int i, j; + int id = -1; + int count = 0; + int t; + qtime_t qt; + gentity_t *vic; + qboolean ison; + + if( G_SayArgc() < 2 + skiparg ) + { + ADMP( "^3!seen: ^7usage: !seen [name|admin#]\n" ); + return qfalse; + } + G_SayArgv( 1 + skiparg, name, sizeof( name ) ); + G_SanitiseName( name, search ); + for( i = 0; i < sizeof( search ) && search[ i ] ; i++ ) + { + if( search[ i ] < '0' || search[ i ] > '9' ) + { + numeric = qfalse; + break; + } + } + + if( numeric ) + { + id = atoi( name ); + search[ 0 ] = '\0'; + } + + ADMBP_begin(); + t = trap_RealTime( &qt ); + + for( i = 0; i < level.maxclients && count < 10; i ++ ) + { + vic = &g_entities[ i ]; + + if( !vic->client || vic->client->pers.connected != CON_CONNECTED ) + continue; + + G_SanitiseName( vic->client->pers.netname, name ); + + if( i == id || (search[ 0 ] && strstr( name, search ) ) ) + { + ADMBP( va( "%4d %s^7 is currently playing\n", i, vic->client->pers.netname ) ); + count++; + } + } + for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] && count < 10; i++ ) + { + G_SanitiseName( g_admin_admins[ i ]->name, name ); + if( i + MAX_CLIENTS == id || (search[ 0 ] && strstr( name, search ) ) ) + { + ison = qfalse; + for( j = 0; j < level.maxclients; j++ ) + { + vic = &g_entities[ j ]; + if( !vic->client || vic->client->pers.connected != CON_CONNECTED ) + continue; + G_SanitiseName( vic->client->pers.netname, name ); + if( !Q_stricmp( vic->client->pers.guid, g_admin_admins[ i ]->guid ) + && strstr( name, search ) ) + { + ison = qtrue; + break; + } + } + + if( ison ) + { + if( id == -1 ) + continue; + ADMBP( va( "%4d %s^7 is currently playing\n", + i + MAX_CLIENTS, g_admin_admins[ i ]->name ) ); + } + else + { + G_admin_duration( t - g_admin_admins[ i ]->seen, + sduration, sizeof( sduration ) ); + ADMBP( va( "%4d %s^7 last seen %s%s\n", + i + MAX_CLIENTS, g_admin_admins[ i ]->name , + ( g_admin_admins[ i ]->seen ) ? sduration : "", + ( g_admin_admins[ i ]->seen ) ? " ago" : "time is unknown" ) ); + } + count++; + } + } + + if( search[ 0 ] ) + ADMBP( va( "^3!seen:^7 found %d player%s matching '%s'\n", + count, (count == 1) ? "" : "s", search ) ); + else if ( !count ) + ADMBP( "^3!seen:^7 no one connectd by that slot number\n" ); + + ADMBP_end(); + return qtrue; +} + +void G_admin_seen_update( char *guid ) +{ + int i; + + for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] ; i++ ) + { + if( !Q_stricmp( g_admin_admins[ i ]->guid, guid ) ) + { + qtime_t qt; + + g_admin_admins[ i ]->seen = trap_RealTime( &qt ); + return; + } + } +} + void G_admin_adminlog_cleanup( void ) { int i; @@ -6278,31 +6636,125 @@ qboolean G_admin_listmaps( gentity_t *ent, int skiparg ) return qtrue; } -qboolean G_admin_immunity( gentity_t *ent, int skiparg ) +const char *G_admin_user_flag( int id, char flag, qboolean add, qboolean clear ) { - char command[ MAX_ADMIN_CMD_LEN ]; - char *cmd, *p, *action; - char guid[ 33 ]; - int id; - int i, j; - char c; - gentity_t *vic; - char adminname[ MAX_NAME_LENGTH ] = {""}; - char oldflags[ MAX_ADMIN_FLAGS ]; - char *newflags; - qboolean numeric = qtrue; + char add_flags[ MAX_ADMIN_FLAGS ]; + char sub_flags[ MAX_ADMIN_FLAGS ]; + char *flags; + int add_count = 0; + int sub_count = 0; + qboolean wildcard = qfalse; + qboolean allowed = qtrue; - if( G_SayArgc() < 2 + skiparg ) + if( id < 0 || id >= MAX_ADMIN_ADMINS + || g_admin_admins[ id ] == NULL ) { - ADMP( "^3!immunity: ^7usage: immunity [+|-]slot#\n" ); - return qfalse; + return "admin id out of range"; } - G_SayArgv( 1 + skiparg, command, sizeof( command ) ); - cmd = command; - action = command; - if( *cmd == '+' || *cmd == '-' ) cmd++; - p = cmd; + if( flag == '-' || flag == '+' || flag == '*') + { + return "invalid admin flag"; + } + + flags = g_admin_admins[ id ]->flags; + while( *flags ) + { + if( *flags == '*' ) + { + wildcard = qtrue; + allowed = qfalse; + } + else if( *flags == '-' ) + { + allowed = qfalse; + } + else if( *flags == '+' ) + { + allowed = qtrue; + } + else if( *flags != flag ) + { + if( allowed ) + { + if( add_count < MAX_ADMIN_FLAGS - 1 ) + { + add_flags[ add_count ] = *flags; + add_count++; + } + } + else + { + if( sub_count < MAX_ADMIN_FLAGS - 1 ) + { + sub_flags[ sub_count ] = *flags; + sub_count++; + } + } + } + + flags++; + } + + if( !clear ) + { + if( add ) + { + if( flag == '*' ) + { + wildcard = qtrue; + } + else if( add_count < MAX_ADMIN_FLAGS - 1 ) + { + add_flags[ add_count ] = flag; + add_count++; + } + } + else + { + if( flag == '*' ) + { + wildcard = qfalse; + } + else if( sub_count < MAX_ADMIN_FLAGS - 1 ) + { + sub_flags[ sub_count ] = flag; + sub_count++; + } + } + } + + add_flags[ add_count ] = '\0'; + sub_flags[ sub_count ] = '\0'; + + if( add_count + sub_count + ( (wildcard || sub_count) ? 1 : 0 ) + 1 > MAX_ADMIN_FLAGS ) + { + return "maximum admin flags exceeded"; + } + + Com_sprintf( g_admin_admins[ id ]->flags, sizeof(g_admin_admins[ id ]->flags), + "%s%s%s%s", + add_flags, + ( wildcard ) ? "*" : "", + ( sub_count && !wildcard ) ? "-" : "", + sub_flags ); + + return NULL; +} + +int G_admin_find_slot( gentity_t *ent, char *cmd, char *name, char *nick, int nick_len ) +{ + char guid[ 33 ]; + char *p; + int id; + int i; + qboolean numeric = qtrue; + gentity_t *vic; + + if ( nick ) + nick[ 0 ] = '\0'; + + p = name; while ( *p ) { if( *p < '0' || *p > '9' ) @@ -6312,26 +6764,26 @@ qboolean G_admin_immunity( gentity_t *ent, int skiparg ) } p++; } - if( !numeric || *cmd == '\0' ) + if( !numeric || name[ 0 ] == '\0' ) { - ADMP( "^3!immunity:^7 invalid slot number\n." ); - return qfalse; + ADMP( va( "^3!%s:^7 invalid slot number\n.", cmd ) ); + return -1; } - id = atoi( cmd ); - + guid[ 0 ] = '\0'; + id = atoi( name ); if( id >= 0 && id < level.maxclients ) { vic = &g_entities[ id ]; if( !vic || !(vic->client) || vic->client->pers.connected != CON_CONNECTED ) { - ADMP( "^3!immunity:^7 no one connectd by that slot number\n" ); + ADMP( va( "^3!%s:^7 no one connected by that slot number\n", cmd ) ); return qfalse; } Q_strncpyz( guid, vic->client->pers.guid, sizeof( guid ) ); - if( *guid == '\0' ) + if( *guid == 'X' ) { - ADMP( va( "^3!immunity:^7 player in slot %d has no GUID.\n", id ) ); + ADMP( va( "^3!%s:^7 player in slot %d has no GUID.\n", cmd, id ) ); return qfalse; } for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) @@ -6339,7 +6791,8 @@ qboolean G_admin_immunity( gentity_t *ent, int skiparg ) if( !Q_stricmp( guid, g_admin_admins[ i ]->guid ) ) { id = i + MAX_CLIENTS; - Q_strncpyz( adminname, vic->client->pers.netname, sizeof( adminname ) ); + if( nick ) + Q_strncpyz( nick, vic->client->pers.netname, nick_len ); break; } } @@ -6347,17 +6800,140 @@ qboolean G_admin_immunity( gentity_t *ent, int skiparg ) if( id < MAX_CLIENTS || id >= MAX_CLIENTS + MAX_ADMIN_ADMINS || g_admin_admins[ id - MAX_CLIENTS ] == NULL ) { - ADMP( "^3!immunity:^7 no match. use !listplayers or !listadmins to " - "find an appropriate slot # to use.\n" ); + if( *guid ) + ADMP( va( "^3!%s:^7 player is not !registered\n", cmd ) ); + else + ADMP( va( "^3!%s:^7 no match. use !listplayers or !listadmins to " + "find an appropriate slot # to use.\n", cmd ) ); + return -1; + } + id -= MAX_CLIENTS; + + if ( nick && !nick[ 0 ] ) + { + Q_strncpyz( nick, g_admin_admins[ id ]->name, nick_len ); + } + + return id; +} + +qboolean G_admin_flag( gentity_t *ent, int skiparg ) +{ + char command[ MAX_ADMIN_CMD_LEN ], *cmd; + char name[ MAX_NAME_LENGTH ]; + char flagbuf[ 4 ], *flag; + int id; + char adminname[ MAX_NAME_LENGTH ] = {""}; + const char *result; + qboolean add = qtrue; + qboolean clear = qfalse; + + G_SayArgv( skiparg, command, sizeof( command ) ); + cmd = command; + if( *cmd == '!' ) + cmd++; + + if( G_SayArgc() < 2 + skiparg ) + { + ADMP( va( "^3!%s: ^7usage: !%s slot# flag\n", cmd, cmd ) ); return qfalse; } - id -= MAX_CLIENTS; - if ( !(*adminname) ) - { - Q_strncpyz( adminname, g_admin_admins[ id ]->name, sizeof( adminname ) ); + G_SayArgv( 1 + skiparg, name, sizeof( name ) ); + id = G_admin_find_slot( ent, cmd, name, adminname, sizeof( adminname ) ); + if( id < 0 ) + return qfalse; + if( ent && !admin_higher_guid( ent->client->pers.guid, g_admin_admins[ id ]->guid ) ) + { + ADMP( va( "^3%s:^7 your intended victim has a higher admin level then you\n", cmd ) ); + return qfalse; } + if( G_SayArgc() < 3 + skiparg ) + { + ADMP( va( "^3%s:^7 flags for %s^7 are '^3%s^7'\n", + cmd, adminname, g_admin_admins[ id ]->flags ) ); + return qtrue; + } + + G_SayArgv( 2 + skiparg, flagbuf, sizeof( flagbuf ) ); + flag = flagbuf; + if( flag[ 0 ] == '-' && flag[ 1 ] != '\0' ) + { + add = qfalse; + flag++; + } + if( ent && !Q_stricmp( ent->client->pers.guid, g_admin_admins[ id ]->guid ) ) + { + ADMP( va( "^3%s:^7 you may not change your own flags (use rcon)\n", cmd ) ); + return qfalse; + } + if( !G_admin_permission( ent, flag[ 0 ] ) ) + { + ADMP( va( "^3%s:^7 you can only change flags that you also have\n", cmd ) ); + return qfalse; + } + + if( !Q_stricmp( cmd, "unflag" ) ) + { + clear = qtrue; + } + + result = G_admin_user_flag( id, flag[ 0 ], add, clear ); + if( result ) + { + ADMP( va( "^3!immunity: ^7an error occured setting flag '^3%c^7', %s\n", + flag[ 0 ], result ) ); + return qfalse; + } + + if( !Q_stricmp( cmd, "flag" ) ) + { + AP( va( + "print \"^3!%s: ^7%s^7 was %s admin flag '%c' by %s\n\"", + cmd, adminname, + ( add ) ? "given" : "denied", + flag[ 0 ], + ( ent ) ? ent->client->pers.netname : "console" ) ); + } + else + { + AP( va( + "print \"^3!%s: ^7admin flag '%c' for %s^7 cleared by %s\n\"", + cmd, flag[ 0 ], adminname, + ( ent ) ? ent->client->pers.netname : "console" ) ); + } + + if( !g_admin.string[ 0 ] ) + ADMP( va( "^3!%s: ^7WARNING g_admin not set, not saving admin record " + "to a file\n", cmd ) ); + else + admin_writeconfig(); + + return qtrue; +} + +qboolean G_admin_immunity( gentity_t *ent, int skiparg ) +{ + char command[ MAX_ADMIN_CMD_LEN ]; + char *cmd, *action; + int id; + char adminname[ MAX_NAME_LENGTH ] = {""}; + const char *result; + + if( G_SayArgc() < 2 + skiparg ) + { + ADMP( "^3!immunity: ^7usage: !immunity [+|-]slot#\n" ); + return qfalse; + } + G_SayArgv( 1 + skiparg, command, sizeof( command ) ); + cmd = command; + action = command; + if( *cmd == '+' || *cmd == '-' ) cmd++; + + id = G_admin_find_slot( ent, "immunity", cmd, adminname, sizeof( adminname ) ); + if( id < 0 ) + return qfalse; if( *action != '+' && *action != '-' ) { @@ -6367,32 +6943,12 @@ qboolean G_admin_immunity( gentity_t *ent, int skiparg ) return qfalse; } - Q_strncpyz( oldflags, g_admin_admins[ id ]->flags, sizeof( oldflags ) ); - newflags = g_admin_admins[ id ]->flags; - i = j = 0; - while( i < MAX_ADMIN_FLAGS - 1 && oldflags[ i ] ) + result = G_admin_user_flag( id, ADMF_BANIMMUNITY, qtrue, ( *action != '+' ) ); + if( result ) { - c = oldflags[ i ]; - if( c != ADMF_BANIMMUNITY ) - { - newflags[ j ] = c; - j++; - } - i++; + ADMP( va( "^3!immunity: ^7an error occured setting flag, %s\n", result ) ); + return qfalse; } - if( *action == '+' ) - { - if( j < MAX_ADMIN_FLAGS - 1 ) - { - newflags[ j ] = ADMF_BANIMMUNITY; - j++; - } - else - { - ADMP( "^3!immunity:^7 flag count exceeded, no actual ban immunity.\n" ); - } - } - newflags[ j ] = '\0'; if( *action == '+' ) { @@ -6408,7 +6964,7 @@ qboolean G_admin_immunity( gentity_t *ent, int skiparg ) } if( !g_admin.string[ 0 ] ) - ADMP( "^3!setlevel: ^7WARNING g_admin not set, not saving admin record " + ADMP( "^3!immunity: ^7WARNING g_admin not set, not saving admin record " "to a file\n" ); else admin_writeconfig(); @@ -6486,6 +7042,12 @@ qboolean G_admin_suspendban( gentity_t *ent, int skiparg ) qboolean G_admin_demo( gentity_t *ent, int skiparg ) { + if( !ent ) + { + ADMP( "!demo: console can not use demo.\n"); + return qfalse; + } + ent->client->pers.ignoreAdminWarnings = !( ent->client->pers.ignoreAdminWarnings ); ADMP( va( "^3!demo: ^7your visibility of admin chat is now %s\n", diff --git a/src/game/g_admin.h b/src/game/g_admin.h index b1c4489..f100114 100644 --- a/src/game/g_admin.h +++ b/src/game/g_admin.h @@ -45,6 +45,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define MAX_ADMIN_BANSUSPEND_DAYS 14 +#define CHAT_MAXCHAN 10 +#define CHAT_MAXPASS 12 + /* * 1 - cannot be vote kicked, vote muted * 2 - cannot be censored or flood protected TODO @@ -114,6 +117,8 @@ typedef struct g_admin_admin char name[ MAX_NAME_LENGTH ]; int level; char flags[ MAX_ADMIN_FLAGS ]; + int seen; + char chat[ CHAT_MAXCHAN ][ CHAT_MAXPASS ]; } g_admin_admin_t; @@ -214,7 +219,10 @@ qboolean G_admin_buildlog( gentity_t *ent, int skiparg ); qboolean G_admin_revert( gentity_t *ent, int skiparg ); qboolean G_admin_slap( gentity_t *ent, int skiparg ); qboolean G_admin_L0( gentity_t *ent, int skiparg ); +qboolean G_admin_seen(gentity_t *ent, int skiparg ); +void G_admin_seen_update( char *guid ); qboolean G_admin_listmaps( gentity_t *ent, int skiparg ); +qboolean G_admin_flag( gentity_t *ent, int skiparg ); qboolean G_admin_immunity( gentity_t *ent, int skiparg ); qboolean G_admin_suspendban( gentity_t *ent, int skiparg ); qboolean G_admin_demo( gentity_t *ent, int skiparg ); @@ -224,6 +232,12 @@ void G_admin_adminlog_log( gentity_t *ent, char *command, char *args, int skipar qboolean G_admin_maplog( gentity_t *ent, int skiparg ); void G_admin_maplog_update( void ); void G_admin_maplog_result( char *flag ); + +void G_admin_chat_writeconfig( void ); +qboolean G_admin_chat_readconfig( gentity_t *ent ); +void G_admin_chat_sync( gentity_t *ent ); +void G_admin_chat_update( gentity_t *ent, int chan ); + //all of my commands WEEEEEEEEEE qboolean G_admin_switch( gentity_t *ent, int skiparg ); qboolean G_admin_mix( gentity_t *ent, int skiparg ); diff --git a/src/game/g_client.c b/src/game/g_client.c index a8a8359..6ea2b07 100644 --- a/src/game/g_client.c +++ b/src/game/g_client.c @@ -1532,6 +1532,9 @@ void ClientBegin( int clientNum ) // name can change between ClientConnect() and ClientBegin() G_admin_namelog_update( client, qfalse ); + // rejoin any saved chat channels + G_admin_chat_sync( ent ); + // request the clients PTR code trap_SendServerCommand( ent - g_entities, "ptrcrequest" ); diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c index ccb6b54..a5f6703 100644 --- a/src/game/g_cmds.c +++ b/src/game/g_cmds.c @@ -1375,6 +1375,223 @@ static void Cmd_Tell_f( gentity_t *ent ) G_Say( ent, ent, SAY_TELL, p ); } +void Cmd_Join_f( gentity_t *ent ) +{ + char pass[CHAT_MAXPASS]; + char arg[MAX_TOKEN_CHARS]; + int chan; + int i; + gentity_t *target; + + if( g_floodMinTime.integer && + G_Flood_Limited( ent ) ) + { + trap_SendServerCommand( ent-g_entities, + "print \"Your chat is flood-limited; wait before chatting again\n\"" ); + return; + } + + if( trap_Argc( ) < 2 ) + { + char message[ 64 ]; + int n; + + n = 0; + for( i = 0; i < CHAT_MAXCHAN && n < 64 - 3; i++ ) + { + if( ent->client->pers.chat[i][0] ) + { + message[ n ] = ' '; n++; + message[ n ] = '0' + i; n++; + } + } + if( n == 0) + Com_sprintf( message, sizeof( message ), " no channels, use: /join [0-%d] (password)", CHAT_MAXCHAN - 1 ); + else + message[ n ] = '\0'; + + trap_SendServerCommand( ent-g_entities, va( "print \"joined in:%s\n\"", message ) ); + return; + } + + trap_Argv( 1, arg, sizeof( arg ) ); + chan = atoi( arg ); + if( chan < 0 || chan >= CHAT_MAXCHAN ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"^3/join^7: invalid channel, usage: /join [0-%d] (password)\n\"", CHAT_MAXCHAN - 1 ) ); + return; + } + + trap_Argv( 2, pass, sizeof( pass ) ); + if( pass[0] == '\0' ) + Q_strncpyz( pass, "default", sizeof( pass ) ); + + if( !Q_stricmp( ent->client->pers.chat[chan], pass ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"^3/join^7: already join to channel %d\n\"", chan ) ); + return; + } + + G_LogPrintf( "join: channel %d %s^7\n", chan, ent->client->pers.netname ); + Q_strncpyz( ent->client->pers.chat[chan], pass, sizeof( ent->client->pers.chat[chan] ) ); + G_admin_chat_update( ent, chan ); + + for( i = 0; i < level.maxclients; i++ ) + { + target = &g_entities[ i ]; + if( target && target->client && + target->client->pers.connected == CON_CONNECTED && + !Q_stricmp( target->client->pers.chat[ chan ], pass ) ) + { + trap_SendServerCommand( i, va( "print \"join: %s^7 has joined channel #%d\n\"", + ent->client->pers.netname, chan ) ); + } + } +} + +void Cmd_Part_f( gentity_t *ent ) +{ + char arg[MAX_TOKEN_CHARS]; + int chan; + int i; + gentity_t *target; + + if( trap_Argc( ) < 2 ) + { + trap_SendServerCommand( ent-g_entities, va( "print \"^3/part^7 usage: /part [0-%d]\n\"", CHAT_MAXCHAN - 1 ) ); + return; + } + + if( g_floodMinTime.integer && + G_Flood_Limited( ent ) ) + { + trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" ); + return; + } + + trap_Argv( 1, arg, sizeof( arg ) ); + chan = atoi( arg ); + if( chan < 0 || chan >= CHAT_MAXCHAN ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"^3/part^7: invalid channel, available channels are 0 to %d\n\"", CHAT_MAXCHAN - 1 ) ); + return; + } + + if( ent->client->pers.chat[chan][0] == '\0' ) + { + trap_SendServerCommand( ent-g_entities, + "print \"^3/part^7: not in that channel\n\"" ); + return; + } + + for( i = 0; i < level.maxclients; i++ ) + { + target = &g_entities[ i ]; + if( target && target->client && + target->client->pers.connected == CON_CONNECTED && + !Q_stricmp( target->client->pers.chat[ chan ], ent->client->pers.chat[ chan ] ) ) + { + trap_SendServerCommand( i, va( "print \"part: %s^7 has left channel #%d\n\"", + ent->client->pers.netname, chan ) ); + } + } + + G_LogPrintf( "part: channel %d %s^7\n", chan, ent->client->pers.netname ); + Q_strncpyz( ent->client->pers.chat[chan], "", sizeof( ent->client->pers.chat[chan] ) ); + G_admin_chat_update( ent, chan ); +} + +void Cmd_Channel_f( gentity_t *ent ) +{ + char arg[MAX_TOKEN_CHARS]; + char str[MAX_STRING_CHARS]; + char *p; + char location[ 64 ]; + char escaped[ 64 ]; + int chan; + int i; + int num = 0; + qboolean who = qfalse; + gentity_t *target; + + if( g_floodMinTime.integer && + G_Flood_Limited( ent ) ) + { + trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" ); + return; + } + + trap_Argv( 0, arg, sizeof( arg ) ); + chan = atoi( arg ); + if( chan < 0 || chan >= CHAT_MAXCHAN || + ent->client->pers.chat[chan][0] == '\0' ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"^3/%s^7: you are not joined to that channel, use /join\n\"", arg ) ); + return; + } + + p = ConcatArgs( 1 ); + if( !p || p[0] == '\0' ) + who = qtrue; + + if( Team_GetLocationMsg( ent, location, sizeof( location ) ) ) + { + Com_sprintf( escaped, sizeof( escaped ), " (^4%s^7)", location ); + } + else + { + escaped[ 0 ] = '\0'; + } + + str[ 0 ] = '\0'; + for( i = 0; i < level.maxclients; i++ ) + { + target = &g_entities[ i ]; + if( target && target->client && + target->client->pers.connected == CON_CONNECTED && + !Q_stricmp( target->client->pers.chat[ chan ], ent->client->pers.chat[ chan ] ) ) + { + if( num > 0 ) + Q_strcat( str, sizeof( str ), "^7, " ); + Q_strcat( str, sizeof( str ), target->client->pers.netname ); + num++; + } + } + + if( who ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"channel ^3%d^7 has %s%s and contains %d player%s%s%s\n\"", + chan, + ( !Q_stricmp( ent->client->pers.chat[ chan ], "default" ) ) ? "no password" : "password ", + ( !Q_stricmp( ent->client->pers.chat[ chan ], "default" ) ) ? "" : ent->client->pers.chat[ chan ], + num, + ( num == 1 ) ? "" : "s", + ( num ) ? ": " : "", + str ) ); + return; + } + + G_LogPrintf( "channel%d: %s^7 %s\n", chan, ent->client->pers.netname, p ); + for( i = 0; i < level.maxclients; i++ ) + { + target = &g_entities[ i ]; + if( target && target->client && + target->client->pers.connected == CON_CONNECTED && + !Q_stricmp( target->client->pers.chat[ chan ], ent->client->pers.chat[ chan ] ) ) + { + trap_SendServerCommand( i, va( "print \"(^1#%d^7 %d) [%s^7]%s: ^1%s^7\n\"", + chan, num, ent->client->pers.netname, + ( OnSameTeam( target, ent ) ) ? escaped : "", + p ) ); + } + } +} + /* ================== Cmd_Where_f @@ -2650,7 +2867,39 @@ void Cmd_Destroy_f( gentity_t *ent ) BG_FindHumanNameForBuildable( traceEnt->s.modelindex ) ) ); return; } - + + // Prevent destruction of critical buildables (per g_deconTime) + if( g_deconTime.integer && + ( traceEnt->s.modelindex == BA_A_OVERMIND || traceEnt->s.modelindex == BA_H_REACTOR ) ) + { + if( g_deconTime.integer > 0 && + level.time - level.startTime < g_deconTime.integer * 1000 ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s can't be moved until %d:%02d into the game\n\"", + BG_FindHumanNameForBuildable( traceEnt->s.modelindex ), + g_deconTime.integer / 60, g_deconTime.integer % 60 ) ); + return; + } + if( g_deconTime.integer < 0 && + level.time - level.startTime < 60 * 1000 && level.numConnectedClients ) + { + int numTeammates; + + if( ent->client->pers.teamSelection == PTE_HUMANS ) + numTeammates = level.numHumanClients; + else + numTeammates = level.numAlienClients; + + if( numTeammates * 100 / level.numConnectedClients < 33 ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s can't be moved until more players join, or after 1:00\n\"", + BG_FindHumanNameForBuildable( traceEnt->s.modelindex ) ) ); + return; + } + } + } // Prevent destruction of the last spawn if( g_markDeconstruct.integer != 1 ) @@ -2812,6 +3061,39 @@ void Cmd_Mark_f( gentity_t *ent ) return; } + // Prevent marking of critical buildables (per g_deconTime) + if( g_deconTime.integer && + ( traceEnt->s.modelindex == BA_A_OVERMIND || traceEnt->s.modelindex == BA_H_REACTOR ) ) + { + if( g_deconTime.integer > 0 && + level.time - level.startTime < g_deconTime.integer * 1000 ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s can't be moved until %d:%02d into the game\n\"", + BG_FindHumanNameForBuildable( traceEnt->s.modelindex ), + g_deconTime.integer / 60, g_deconTime.integer % 60 ) ); + return; + } + if( g_deconTime.integer < 0 && + level.time - level.startTime < 60 * 1000 && level.numConnectedClients ) + { + int numTeammates; + + if( ent->client->pers.teamSelection == PTE_HUMANS ) + numTeammates = level.numHumanClients; + else + numTeammates = level.numAlienClients; + + if( numTeammates * 100 / level.numConnectedClients < 33 ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"%s can't be moved until more players join, or after 1:00\n\"", + BG_FindHumanNameForBuildable( traceEnt->s.modelindex ) ) ); + return; + } + } + } + // Don't allow marking of buildables that cannot be rebuilt if(g_suddenDeath.integer && traceEnt->health > 0 && ( ( g_suddenDeathMode.integer == SDMODE_SELECTIVE && @@ -4366,6 +4648,20 @@ commands_t cmds[ ] = { { "me", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, { "me_team", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, + // channel chat + { "join", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Join_f }, + { "part", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Part_f }, + { "0", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Channel_f }, + { "1", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Channel_f }, + { "2", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Channel_f }, + { "3", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Channel_f }, + { "4", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Channel_f }, + { "5", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Channel_f }, + { "6", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Channel_f }, + { "7", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Channel_f }, + { "8", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Channel_f }, + { "9", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Channel_f }, + { "score", CMD_INTERMISSION, ScoreboardMessage }, { "mystats", CMD_TEAM|CMD_INTERMISSION, Cmd_MyStats_f }, diff --git a/src/game/g_combat.c b/src/game/g_combat.c index 4bd0918..b922fd0 100644 --- a/src/game/g_combat.c +++ b/src/game/g_combat.c @@ -140,6 +140,7 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int float percentDamage = 0.0f; gentity_t *player; qboolean tk = qfalse; + int spreeRate = 0; if( self->client->ps.pm_type == PM_DEAD ) @@ -207,6 +208,16 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int //TA: deactivate all upgrades for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) BG_DeactivateUpgrade( i, self->client->ps.stats ); + + // killing spree over + if( self->client->pers.statscounters.spreekills == -1 ) + { + spreeRate = 2; + trap_SendServerCommand( -1, + va( "print \"%s^7's killing spree has come to an end\n\"", + self->client->pers.netname ) ); + } + self->client->pers.statscounters.spreekills = 0; if( meansOfDeath == MOD_SLAP ) { @@ -314,6 +325,19 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int { level.humanStatsCounters.kills++; } + + if( g_killingSpree.integer > 2 ) + { + if( attacker->client->pers.statscounters.spreekills >= 0 ) + attacker->client->pers.statscounters.spreekills += 60; + if( attacker->client->pers.statscounters.spreekills > ( g_killingSpree.integer - 1 ) * 60 ) + { + attacker->client->pers.statscounters.spreekills = -1; + trap_SendServerCommand( -1, + va( "print \"%s^3 is on a killing spree! killer gets a double reward bonus\n\"", + attacker->client->pers.netname ) ); + } + } } if( attacker == self ) @@ -456,6 +480,13 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int { self->client->pers.statscounters.feeds++; level.humanStatsCounters.feeds++; + + if( g_feedingSpree.integer && + level.reactorPresent && + !G_BuildableRange( self->client->ps.origin, 900, BA_H_REACTOR ) ) + { + self->client->pers.statscounters.spreefeeds += 60; + } } } else if( self->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) @@ -465,6 +496,13 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int { self->client->pers.statscounters.feeds++; level.alienStatsCounters.feeds++; + + if( g_feedingSpree.integer && + level.overmindPresent && + !G_BuildableRange( self->client->ps.origin, 900, BA_A_OVERMIND ) ) + { + self->client->pers.statscounters.spreefeeds += 60; + } } } } @@ -497,6 +535,9 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int } + if( spreeRate && player == attacker ) + percentDamage *= (float)spreeRate; + //add credit G_AddCreditToClient( player->client, (int)( classValue * percentDamage ), qtrue ); @@ -539,7 +580,10 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int if( frags > 0 ) { //add kills - G_AddCreditToClient( player->client, frags, qtrue ); + if( spreeRate && player == attacker ) + G_AddCreditToClient( player->client, frags * spreeRate, qtrue ); + else + G_AddCreditToClient( player->client, frags, qtrue ); //can't revist this account later self->credits[ i ] = 0; @@ -580,7 +624,10 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int player = g_entities + topClient; //add kills - G_AddCreditToClient( player->client, 1, qtrue ); + if( spreeRate && player == attacker ) + G_AddCreditToClient( player->client, spreeRate, qtrue ); + else + G_AddCreditToClient( player->client, 1, qtrue ); //can't revist this account again self->credits[ topClient ] = 0; @@ -633,6 +680,20 @@ finish_dying: // g_forcerespawn may force spawning at some later time self->client->respawnTime = level.time + 1700; + if( g_feedingSpree.integer > 2 ) + { + int maxfeed; + + maxfeed = (g_feedingSpree.integer - 1) * 60; + if( self->client->pers.statscounters.spreefeeds > maxfeed ) + { + self->client->respawnTime += 100 * (self->client->pers.statscounters.spreefeeds - maxfeed ); + trap_SendServerCommand( self->client->ps.clientNum, + va( "print \"You are on a feeding spree! respawn delayed %d seconds\n\"", + (self->client->respawnTime - level.time) / 1000 ) ); + } + } + // remove powerups memset( self->client->ps.powerups, 0, sizeof( self->client->ps.powerups ) ); diff --git a/src/game/g_local.h b/src/game/g_local.h index 3548540..ffb05d8 100644 --- a/src/game/g_local.h +++ b/src/game/g_local.h @@ -337,6 +337,8 @@ typedef struct int headshots; int hits; int hitslocational; + int spreekills; + int spreefeeds; int teamkills; int dretchbasytime; int jetpackusewallwalkusetime; @@ -394,6 +396,7 @@ typedef struct statsCounters_t statscounters; qboolean cusgrav; int cusgravlvl; + char chat[ CHAT_MAXCHAN ][ CHAT_MAXPASS ]; qboolean ignoreAdminWarnings; qboolean firstConnect; @@ -1337,6 +1340,7 @@ extern vmCvar_t g_adminRegisterAdminPass; extern vmCvar_t g_adminRegisterAdminLevel; extern vmCvar_t g_minLevelToJoinTeam; extern vmCvar_t g_minLevelToSpecMM1; +extern vmCvar_t g_chat; extern vmCvar_t g_KillerHP; @@ -1348,6 +1352,9 @@ extern vmCvar_t g_publicSayadmins; extern vmCvar_t g_myStats; extern vmCvar_t g_antiSpawnBlock; +extern vmCvar_t g_killingSpree; +extern vmCvar_t g_feedingSpree; + extern vmCvar_t g_sayAreaLocations; extern vmCvar_t g_dretchPunt; @@ -1377,6 +1384,8 @@ extern vmCvar_t g_banNotice; extern vmCvar_t g_voteMinTime; extern vmCvar_t g_mapvoteMaxTime; +extern vmCvar_t g_deconTime; + extern vmCvar_t g_specAspec; extern vmCvar_t g_modBuildableHealth; // Buildable health diff --git a/src/game/g_main.c b/src/game/g_main.c index 474090f..4ac34ef 100644 --- a/src/game/g_main.c +++ b/src/game/g_main.c @@ -159,6 +159,7 @@ vmCvar_t g_adminRegisterLevel; vmCvar_t g_adminRegisterAdminPass; vmCvar_t g_adminRegisterAdminLevel; vmCvar_t g_minLevelToJoinTeam; +vmCvar_t g_chat; vmCvar_t g_privateMessages; vmCvar_t g_buildLogMaxLength; @@ -167,6 +168,9 @@ vmCvar_t g_publicSayadmins; vmCvar_t g_myStats; vmCvar_t g_antiSpawnBlock; +vmCvar_t g_killingSpree; +vmCvar_t g_feedingSpree; + vmCvar_t g_KillerHP; vmCvar_t g_decolorLogfiles; @@ -206,6 +210,8 @@ vmCvar_t g_banNotice; vmCvar_t g_voteMinTime; vmCvar_t g_mapvoteMaxTime; +vmCvar_t g_deconTime; + vmCvar_t g_specAspec; vmCvar_t g_modBuildableHealth; @@ -386,19 +392,24 @@ static cvarTable_t gameCvarTable[ ] = { &g_adminRegisterAdminPass, "g_adminRegisterAdminPass", "", CVAR_ARCHIVE, 0, qfalse }, { &g_adminRegisterAdminLevel, "g_adminRegisterAdminLevel", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_privateMessages, "g_privateMessages", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_chat, "g_chat", "chat.dat", CVAR_ARCHIVE, 0, qfalse }, { &g_radiationDamage, "g_radiationDamage", "1", CVAR_ARCHIVE, 0, qfalse }, { &g_radiationTime, "g_radiationTime", "60", CVAR_ARCHIVE, 0, qfalse }, { &g_radiationCredits, "g_radiationCredits", "1000", CVAR_ARCHIVE, 0, qfalse }, { &g_radiationCreditsCount, "g_radiationCreditsCount", "2", CVAR_ARCHIVE, 0, qfalse }, - - + + { &g_deconTime, "g_deconTime", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_buildLogMaxLength, "g_buildLogMaxLength", "25", CVAR_ARCHIVE, 0, qfalse }, { &g_myStats, "g_myStats", "1", CVAR_ARCHIVE, 0, qfalse }, { &g_publicSayadmins, "g_publicSayadmins", "1", CVAR_ARCHIVE, 0, qfalse }, { &g_minLevelToSpecMM1, "g_minLevelToSpecMM1", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_antiSpawnBlock, "g_antiSpawnBlock", "0", CVAR_ARCHIVE, 0, qfalse }, + + { &g_killingSpree, "g_killingSpree", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_feedingSpree, "g_feedingSpree", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_KillerHP, "g_KillerHP", "0", CVAR_ARCHIVE, 0, qtrue }, @@ -885,6 +896,8 @@ void G_ShutdownGame( int restart ) // write all the client session data so we can get it back G_WriteSessionData( ); + G_admin_chat_writeconfig(); + G_admin_cleanup( ); G_admin_namelog_cleanup( ); G_admin_adminlog_cleanup( );