diff options
-rw-r--r-- | src/game/bg_lib.c | 8 | ||||
-rw-r--r-- | src/game/bg_misc.c | 134 | ||||
-rw-r--r-- | src/game/bg_pmove.c | 10 | ||||
-rw-r--r-- | src/game/bg_public.h | 21 | ||||
-rw-r--r-- | src/game/g_active.c | 201 | ||||
-rw-r--r-- | src/game/g_admin.c | 2907 | ||||
-rw-r--r-- | src/game/g_admin.h | 60 | ||||
-rw-r--r-- | src/game/g_buildable.c | 1038 | ||||
-rw-r--r-- | src/game/g_client.c | 144 | ||||
-rw-r--r-- | src/game/g_cmds.c | 1040 | ||||
-rw-r--r-- | src/game/g_combat.c | 238 | ||||
-rw-r--r-- | src/game/g_local.h | 156 | ||||
-rw-r--r-- | src/game/g_main.c | 708 | ||||
-rw-r--r-- | src/game/g_maprotation.c | 171 | ||||
-rw-r--r-- | src/game/g_mem.c | 2 | ||||
-rw-r--r-- | src/game/g_missile.c | 31 | ||||
-rw-r--r-- | src/game/g_session.c | 2 | ||||
-rw-r--r-- | src/game/g_weapon.c | 17 | ||||
-rw-r--r-- | src/game/tremulous.h | 3 |
19 files changed, 4958 insertions, 1933 deletions
diff --git a/src/game/bg_lib.c b/src/game/bg_lib.c index 69bca48..b9c8762 100644 --- a/src/game/bg_lib.c +++ b/src/game/bg_lib.c @@ -67,7 +67,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; #endif static const char rcsid[] = - "$Id: bg_lib.c 965 2007-08-09 13:54:12Z msk $"; + "$Id: bg_lib.c,v 1.1.1.1.4.1 2009/02/17 02:38:26 johne Exp $"; #endif /* LIBC_SCCS and not lint */ // bk001127 - needed for DLL's @@ -1663,7 +1663,6 @@ unsigned int _hextoi( const char **stringPtr ) #define SHORTINT 0x00000040 /* short integer */ #define ZEROPAD 0x00000080 /* zero (as opposed to blank) pad */ #define FPT 0x00000100 /* floating point number */ -#define UNSIGNED 0x00000200 /* unsigned integer */ #define to_digit(c) ((c) - '0') #define is_digit(c) ((unsigned)to_digit(c) <= 9) @@ -1677,9 +1676,6 @@ void AddInt( char **buf_p, int val, int width, int flags ) digits = 0; - if( flags & UNSIGNED ) - val = (unsigned) val; - if( flags & HEX ) { char c; @@ -1944,8 +1940,6 @@ reswitch: arg++; break; - case 'u': - flags |= UNSIGNED; case 'd': case 'i': AddInt( &buf_p, *arg, width, flags ); diff --git a/src/game/bg_misc.c b/src/game/bg_misc.c index 79a8e2a..1a55ed0 100644 --- a/src/game/bg_misc.c +++ b/src/game/bg_misc.c @@ -32,6 +32,8 @@ void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); void trap_FS_FCloseFile( fileHandle_t f ); void trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin ); // fsOrigin_t +modExtremeType_t modEntry[ MOD_BG_COUNT ]; + buildableAttributes_t bg_buildableList[ ] = { { @@ -5730,4 +5732,136 @@ void BG_ClientListParse( clientList_t *list, const char *s ) sscanf( s, "%x%x", &list->hi, &list->lo ); } +void BG_MOD_set( modExtremeType_t entry, int value ) +{ + if( entry >= 0 && entry < MOD_BG_COUNT ) + { + if( value < 0 ) + value = 0; + + modEntry[ entry ] = value; + } +} + +int BG_MOD_get( modExtremeType_t entry) +{ + if( entry >= 0 && entry < MOD_BG_COUNT ) + return modEntry[ entry ]; + + return 0; +} + +void BG_MOD_update( void ) +{ + static qboolean updated = qfalse; + int i; + + if( updated ) return; + updated = qtrue; + + for( i = 0; i < bg_numBuildables; i++ ) + { + if( modEntry[ MOD_BG_BUILDABLE_HEALTH ] ) + { + bg_buildableList[ i ].health = + bg_buildableList[ i ].health * modEntry[ MOD_BG_BUILDABLE_HEALTH ] / 100; + } + + if( modEntry[ MOD_BG_BUILDABLE_SPEED ] ) + { + bg_buildableList[ i ].turretFireSpeed = + bg_buildableList[ i ].turretFireSpeed * 100 / modEntry[ MOD_BG_BUILDABLE_SPEED ]; + } + + if( modEntry[ MOD_BG_TURRET_ANGLE ] && + ( bg_buildableList[ i ].buildNum == BA_H_MGTURRET || + bg_buildableList[ i ].buildNum == BA_H_TESLAGEN ) ) + { + bg_buildableList[ i ].minNormal = 0.0f; + } + } + + for( i = 0; i < bg_numPclasses; i++ ) + { + if( bg_classList[ i ].classNum == PCL_HUMAN || + bg_classList[ i ].classNum == PCL_HUMAN_BSUIT ) + { + if( modEntry[ MOD_BG_HUMAN_HEALTH ] ) + { + bg_classList[ i ].health = + bg_classList[ i ].health * modEntry[ MOD_BG_HUMAN_HEALTH ] / 100; + } + } + else + { + if( modEntry[ MOD_BG_ALIEN_HEALTH ] ) + bg_classList[ i ].health = + bg_classList[ i ].health * modEntry[ MOD_BG_ALIEN_HEALTH ] / 100; + } + } + + for( i = 0; i < bg_numWeapons; i++ ) + { + if( modEntry[ MOD_BG_WEAPON_AMMO ] && + bg_weapons[ i ].weaponNum != WP_ALEVEL3_UPG ) + { + bg_weapons[ i ].maxAmmo = + bg_weapons[ i ].maxAmmo * modEntry[ MOD_BG_WEAPON_AMMO ] / 100; + } + + if( modEntry[ MOD_BG_ALIEN_RATE ] && + bg_weapons[ i ].weaponNum >= WP_ALEVEL0 && + bg_weapons[ i ].weaponNum <= WP_ALEVEL4 ) + { + bg_weapons[ i ].repeatRate1 = + bg_weapons[ i ].repeatRate1 * 100 / modEntry[ MOD_BG_ALIEN_RATE ]; + bg_weapons[ i ].repeatRate2 = + bg_weapons[ i ].repeatRate2 * 100 / modEntry[ MOD_BG_ALIEN_RATE ]; + bg_weapons[ i ].repeatRate3 = + bg_weapons[ i ].repeatRate3 * 100 / modEntry[ MOD_BG_ALIEN_RATE ]; + } + + if( modEntry[ MOD_BG_HUMAN_RATE ] && + bg_weapons[ i ].weaponNum >= WP_BLASTER && + bg_weapons[ i ].weaponNum <= WP_LUCIFER_CANNON ) + { + bg_weapons[ i ].repeatRate1 = + bg_weapons[ i ].repeatRate1 * 100 / modEntry[ MOD_BG_HUMAN_RATE ]; + bg_weapons[ i ].repeatRate2 = + bg_weapons[ i ].repeatRate2 * 100 / modEntry[ MOD_BG_HUMAN_RATE ]; + bg_weapons[ i ].repeatRate3 = + bg_weapons[ i ].repeatRate3 * 100 / modEntry[ MOD_BG_HUMAN_RATE ]; + } + + if( modEntry[ MOD_BG_BUILDABLE_HEALTH ] && + modEntry[ MOD_BG_HUMAN_RATE ] && + ( bg_weapons[ i ].weaponNum == WP_ABUILD || bg_weapons[ i ].weaponNum == WP_ABUILD2 ) ) + { + int avg; + + avg = ( modEntry[ MOD_BG_BUILDABLE_HEALTH ] + MOD_BG_HUMAN_RATE ) / 2; + bg_weapons[ i ].repeatRate1 = bg_weapons[ i ].repeatRate1 * 100 / avg; + bg_weapons[ i ].repeatRate2 = bg_weapons[ i ].repeatRate2 * 100 / avg; + bg_weapons[ i ].repeatRate3 = bg_weapons[ i ].repeatRate3 * 100 / avg; + } + + if( modEntry[ MOD_BG_BUILDABLE_HEALTH ] && + modEntry[ MOD_BG_ALIEN_RATE ] && + ( bg_weapons[ i ].weaponNum == WP_HBUILD || bg_weapons[ i ].weaponNum == WP_HBUILD2 ) ) + { + int avg; + + avg = ( modEntry[ MOD_BG_BUILDABLE_HEALTH ] + MOD_BG_ALIEN_RATE ) / 2; + bg_weapons[ i ].repeatRate1 = bg_weapons[ i ].repeatRate1 * 100 / avg; + bg_weapons[ i ].repeatRate2 = bg_weapons[ i ].repeatRate2 * 100 / avg; + bg_weapons[ i ].repeatRate3 = bg_weapons[ i ].repeatRate3 * 100 / avg; + } + + if( modEntry[ MOD_BG_WEAPON_RELOAD ] ) + { + bg_weapons[ i ].reloadTime = + bg_weapons[ i ].reloadTime * 100 / modEntry[ MOD_BG_WEAPON_RELOAD ]; + } + } +} diff --git a/src/game/bg_pmove.c b/src/game/bg_pmove.c index b0e5c96..88707bd 100644 --- a/src/game/bg_pmove.c +++ b/src/game/bg_pmove.c @@ -714,7 +714,15 @@ static qboolean PM_CheckJump( void ) //TA: take some stamina off if( pm->ps->stats[ STAT_PTEAM ] == PTE_HUMANS ) - pm->ps->stats[ STAT_STAMINA ] -= 500; + { + int rate; + + rate = BG_MOD_get( MOD_BG_HUMAN_STAMINA ); + if( rate ) + pm->ps->stats[ STAT_STAMINA ] -= 500 * 100 / rate; + else + pm->ps->stats[ STAT_STAMINA ] -= 500; + } pm->ps->groundEntityNum = ENTITYNUM_NONE; diff --git a/src/game/bg_public.h b/src/game/bg_public.h index 6642d91..e0e6233 100644 --- a/src/game/bg_public.h +++ b/src/game/bg_public.h @@ -1330,3 +1330,24 @@ void BG_ClientListParse( clientList_t *list, const char *s ); #define FFF_ALIENS 2 #define FFF_BUILDABLES 4 +// value mods equal to g_mod cvars on server +typedef enum +{ + MOD_BG_BUILDABLE_HEALTH, // Buildable health + MOD_BG_BUILDABLE_SPEED, // Buildable fire rate + MOD_BG_HUMAN_STAMINA, // Human stamina + MOD_BG_HUMAN_HEALTH, // Human health + MOD_BG_ALIEN_HEALTH, // Alien health + MOD_BG_HUMAN_RATE, // Human fire rate + MOD_BG_ALIEN_RATE, // Alien fire rate + MOD_BG_WEAPON_AMMO, // Weapon ammo per clip + MOD_BG_WEAPON_RELOAD, // Weapon reload time + MOD_BG_TURRET_ANGLE, // Allow high turret build angles + MOD_BG_STAGE3_STRENGTH, // alter stage 3 buildables + MOD_BG_COUNT // number of entries +} modExtremeType_t; + +void BG_MOD_set( modExtremeType_t entry, int value ); +int BG_MOD_get( modExtremeType_t entry); +void BG_MOD_update( void ); + diff --git a/src/game/g_active.c b/src/game/g_active.c index 57f58fe..2c18f00 100644 --- a/src/game/g_active.c +++ b/src/game/g_active.c @@ -428,7 +428,7 @@ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) if( ( client->pers.teamSelection == PTE_ALIENS && G_SearchSpawnQueue( &level.alienSpawnQueue, ent-g_entities ) ) || ( client->pers.teamSelection == PTE_HUMANS && - G_SearchSpawnQueue( &level.humanSpawnQueue, ent-g_entities ) ) ) + G_SearchSpawnQueue( &level.alienSpawnQueue, ent-g_entities ) ) ) { client->ps.pm_flags |= PMF_QUEUED; } @@ -521,8 +521,12 @@ ClientInactivityTimer Returns qfalse if the client is dropped ================= */ -qboolean ClientInactivityTimer( gclient_t *client ) +qboolean ClientInactivityTimer( gentity_t *ent ) { + gclient_t *client; + + client = ent->client; + if( ! g_inactivity.integer ) { // give everyone some time, so if the operator sets g_inactivity during @@ -542,6 +546,22 @@ qboolean ClientInactivityTimer( gclient_t *client ) { if( level.time > client->inactivityTime ) { + if( g_inactivityMode.integer == 1 ) + { + char buf[ MAX_STRING_CHARS ]; + + Com_sprintf( buf, sizeof( buf ), + "%s^7 moved from %s to spectators due to inactivity\n", + client->pers.netname, + ( client->pers.teamSelection == PTE_ALIENS ) ? "aliens" : "humans" ); + trap_SendServerCommand( -1, va( "print \"%s\"", buf ) ); + G_LogOnlyPrintf( "Inactivity: %s", buf ); + + G_ChangeTeam( ent, PTE_NONE ); + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->inactivityWarning = qfalse; + return qtrue; + } trap_DropClient( client - level.clients, "Dropped due to inactivity" ); return qfalse; } @@ -549,13 +569,77 @@ qboolean ClientInactivityTimer( gclient_t *client ) if( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) { client->inactivityWarning = qtrue; - trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); + if( g_inactivityMode.integer == 1 ) + trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity spectate!\n\"" ); + else + trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); } } return qtrue; } +static void ClientSpreeDecay( gentity_t *ent, int msec ) +{ + gclient_t *client; + + client = ent->client; + client->pers.spreeTime1000 += msec; + + while( client->pers.spreeTime1000 >= 1000 ) + { + client->pers.spreeTime1000 -= 1000; + + // spree decay + if( ent->client->pers.statscounters.spreefeeds ) + { + ent->client->pers.statscounters.spreefeeds -= SPREE_FEED_FADE; + if( ent->client->pers.statscounters.spreefeeds < 0 ) + ent->client->pers.statscounters.spreefeeds = 0; + } + if( ent->client->pers.statscounters.spreekills > 1 ) + { + ent->client->pers.statscounters.spreekills -= 2; + } + if( ent->client->pers.statscounters.spreebleeds || + ent->client->pers.bleeder ) + { + if( ent->client->pers.bleeder ) + ent->client->pers.statscounters.spreebleeds -= 10; + else + ent->client->pers.statscounters.spreebleeds -= 4; + if( ent->client->pers.statscounters.spreebleeds < 0 ) + ent->client->pers.statscounters.spreebleeds = 0; + if( ent->client->pers.bleeder ) + { + if( !ent->client->pers.statscounters.spreebleeds ) + { + ent->client->pers.bleeder = qfalse; + trap_SendServerCommand( -1, + va( "print \"%s^7 has been ^2forgiven ^7by their base\n\"", ent->client->pers.netname ) ); + if( client->sess.sessionTeam != TEAM_SPECTATOR || + client->sess.spectatorState != SPECTATOR_SCOREBOARD ) + trap_SendServerCommand( ent-g_entities, "cp \"^2Your base has forgiven you\"" ); + if( level.bleeders ) + level.bleeders--; + } + else if( client->sess.sessionTeam != TEAM_SPECTATOR && + ent->client->ps.stats[ STAT_HEALTH ] <= 0 ) + { + int spreeLength; + + spreeLength = ent->client->pers.statscounters.spreebleeds / 10; + trap_SendServerCommand( ent-g_entities, + va( "cp \"^3Bleeding made you an enemy of your own base!\n\n^7for %d:%02d\n\n^1Respawn delayed %d seconds\"", + spreeLength / 60, spreeLength % 60, + ( ent->client->respawnTime - level.time) / 1000 ) ); + } + } + } + + } +} + /* ================== ClientTimerActions @@ -568,9 +652,9 @@ void ClientTimerActions( gentity_t *ent, int msec ) gclient_t *client; usercmd_t *ucmd; int aForward, aRight; - qboolean walking = qfalse, stopped = qfalse, - crouched = qfalse, jumping = qfalse, - strafing = qfalse; + int staminaLarmour = STAMINA_LARMOUR_TAKE; + int staminaSprint = STAMINA_SPRINT_TAKE; + int timestep10000; ucmd = &ent->client->pers.cmd; @@ -582,43 +666,38 @@ void ClientTimerActions( gentity_t *ent, int msec ) client->time1000 += msec; client->time10000 += msec; - if( aForward == 0 && aRight == 0 ) - stopped = qtrue; - else if( aForward <= 64 && aRight <= 64 ) - walking = qtrue; - - if( aRight > 0 ) - strafing = qtrue; - - if( ucmd->upmove > 0 ) - jumping = qtrue; - else if( ent->client->ps.pm_flags & PMF_DUCKED ) - crouched = qtrue; + if( ( client->ps.stats[ STAT_STATE ] & SS_SPEEDBOOST ) && + g_modHumanStamina.integer > 0 ) + { + staminaLarmour = staminaLarmour * 100 / g_modHumanStamina.integer; + staminaSprint = staminaSprint * 100 / g_modHumanStamina.integer; + } while ( client->time100 >= 100 ) { client->time100 -= 100; //if not trying to run then not trying to sprint - if( walking || stopped ) + if( aForward <= 64 ) client->ps.stats[ STAT_STATE ] &= ~SS_SPEEDBOOST; if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) client->ps.stats[ STAT_STATE ] &= ~SS_SPEEDBOOST; - if( ( client->ps.stats[ STAT_STATE ] & SS_SPEEDBOOST ) && !crouched ) + if( ( client->ps.stats[ STAT_STATE ] & SS_SPEEDBOOST ) && + !( client->ps.stats[ STAT_STATE ] & PMF_DUCKED ) ) { //subtract stamina if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) ) - client->ps.stats[ STAT_STAMINA ] -= STAMINA_LARMOUR_TAKE; + client->ps.stats[ STAT_STAMINA ] -= staminaLarmour; else - client->ps.stats[ STAT_STAMINA ] -= STAMINA_SPRINT_TAKE; + client->ps.stats[ STAT_STAMINA ] -= staminaSprint; if( client->ps.stats[ STAT_STAMINA ] < -MAX_STAMINA ) client->ps.stats[ STAT_STAMINA ] = -MAX_STAMINA; } - if( walking || crouched ) + if( ( aForward <= 64 && aForward > 5 ) || ( aRight <= 64 && aRight > 5 ) ) { //restore stamina client->ps.stats[ STAT_STAMINA ] += STAMINA_WALK_RESTORE; @@ -626,7 +705,7 @@ void ClientTimerActions( gentity_t *ent, int msec ) if( client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA ) client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA; } - else if( stopped ) + else if( aForward <= 5 && aRight <= 5 ) { //restore stamina faster client->ps.stats[ STAT_STAMINA ] += STAMINA_STOP_RESTORE; @@ -769,6 +848,13 @@ void ClientTimerActions( gentity_t *ent, int msec ) { int remainingStartupTime = MEDKIT_STARTUP_TIME - ( level.time - client->lastMedKitTime ); + if( level.vampireDeath ) + { + ent->client->medKitHealthToRestore = 0; + trap_SendServerCommand( ent-g_entities, + "print \"Medkit is disabled during Vampire Sudden Death\n\"" ); + } + if( remainingStartupTime < 0 ) { if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] && @@ -830,7 +916,7 @@ void ClientTimerActions( gentity_t *ent, int msec ) //replenish alien health if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && - level.surrenderTeam != PTE_ALIENS ) + level.surrenderTeam != PTE_ALIENS && !level.vampireDeath ) { int entityList[ MAX_GENTITIES ]; vec3_t range = { LEVEL4_REGEN_RANGE, LEVEL4_REGEN_RANGE, LEVEL4_REGEN_RANGE }; @@ -866,7 +952,32 @@ void ClientTimerActions( gentity_t *ent, int msec ) !level.paused && ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time ) { - ent->health += BG_FindRegenRateForClass( client->ps.stats[ STAT_PCLASS ] ) * modifier; + float dist_mod = 1.0f; + float hadd; + + if( g_modAlienRegenRange.integer ) + { + float om; + + om = G_BuildableRangeClosest( client->ps.origin, CREEP_BASESIZE * 2, BA_A_OVERMIND ); + if( om > 0.0 ) + { + dist_mod = ( ( CREEP_BASESIZE * 2 ) - om ) / ( CREEP_BASESIZE * 2 ) + 0.25; + if( dist_mod < 0.25 ) + dist_mod = 0.25; + if( dist_mod > 1.0 ) + dist_mod = 1.0; + } + else + { + dist_mod = 0.10f; + } + } + + hadd = (float)(BG_FindRegenRateForClass( client->ps.stats[ STAT_PCLASS ] ) * modifier * dist_mod); + if( hadd < 1.0 && hadd >= 0.5 ) + hadd = 1.0; + ent->health += hadd; // if completely healed, cancel retribution if( ent->health >= client->ps.stats[ STAT_MAX_HEALTH ] ) @@ -884,10 +995,12 @@ void ClientTimerActions( gentity_t *ent, int msec ) if( ent->client->ps.stats[ STAT_HEALTH ] > 0 && ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { ent->client->pers.statscounters.timealive++; + ent->client->pers.karma += 1; level.alienStatsCounters.timealive++; if( G_BuildableRange( ent->client->ps.origin, 900, BA_A_OVERMIND ) ) { ent->client->pers.statscounters.timeinbase++; + ent->client->pers.karma -= 1; level.alienStatsCounters.timeinbase++; } if( BG_ClassHasAbility( ent->client->ps.stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) ) @@ -904,10 +1017,12 @@ void ClientTimerActions( gentity_t *ent, int msec ) else if( ent->client->ps.stats[ STAT_HEALTH ] > 0 && ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) { ent->client->pers.statscounters.timealive++; + ent->client->pers.karma += 1; level.humanStatsCounters.timealive++; if( G_BuildableRange( ent->client->ps.origin, 900, BA_H_REACTOR ) ) { ent->client->pers.statscounters.timeinbase++; + ent->client->pers.karma -= 1; level.humanStatsCounters.timeinbase++; } if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) ) @@ -943,7 +1058,7 @@ void ClientTimerActions( gentity_t *ent, int msec ) BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats ); } else if( client->jetpackfuel < 10.0f && client->jetpackfuel > 0.0f) { client->jetpackfuel = client->jetpackfuel - mod_jetpackConsume.value; - trap_SendServerCommand( client - level.clients, "cp \"^3Fuel ^1Low!!!!!^7\nLand now.\"" ); + trap_SendServerCommand( client - level.clients, "cp \"^3Fuel ^1Low!^7\nLand now.\"" ); } else { client->jetpackfuel = client->jetpackfuel - mod_jetpackConsume.value; } @@ -961,9 +1076,23 @@ void ClientTimerActions( gentity_t *ent, int msec ) } } - while( client->time10000 >= 10000 ) + timestep10000 = 10000; + if( g_modAlienRate.integer > 0 ) + { + timestep10000 = timestep10000 * 100 / g_modAlienRate.integer; + if( timestep10000 < 1000 ) + timestep10000 = 1000; + } + if( g_modBuildableHealth.integer > 0 ) + { + timestep10000 = timestep10000 * 100 / g_modBuildableHealth.integer; + if( timestep10000 < 1000 ) + timestep10000 = 1000; + } + + while( client->time10000 >= timestep10000 ) { - client->time10000 -= 10000; + client->time10000 -= timestep10000; if( client->ps.weapon == WP_ALEVEL3_UPG ) { @@ -1500,6 +1629,9 @@ void ClientThink_real( gentity_t *ent ) return; } + //spree decay + ClientSpreeDecay( ent, msec ); + if( client->pers.teamSelection != PTE_NONE && client->pers.joinedATeam ) G_UpdatePTRConnection( client ); @@ -1514,7 +1646,7 @@ void ClientThink_real( gentity_t *ent ) } // check for inactivity timer, but never drop the local client of a non-dedicated server - if( !ClientInactivityTimer( client ) ) + if( !ClientInactivityTimer( ent ) ) return; // calculate where ent is currently seeing all the other active clients @@ -1567,6 +1699,15 @@ void ClientThink_real( gentity_t *ent ) client->ps.gravity = g_gravity.value; + if( client->pers.bubbleTime && client->pers.bubbleTime < level.time ) + { + gentity_t *bubble; + + client->pers.bubbleTime = level.time + 500; + bubble = G_TempEntity( client->ps.origin, EV_PLAYER_TELEPORT_OUT ); + bubble->s.clientNum = ent->s.clientNum; + } + if( BG_InventoryContainsUpgrade( UP_MEDKIT, client->ps.stats ) && BG_UpgradeIsActive( UP_MEDKIT, client->ps.stats ) ) { diff --git a/src/game/g_admin.c b/src/game/g_admin.c index f9c5f84..de59a47 100644 --- a/src/game/g_admin.c +++ b/src/game/g_admin.c @@ -80,6 +80,16 @@ g_admin_cmd_t g_admin_cmds[ ] = "[^3name|slot#|IP^7] (^5time^7) (^5reason^7)" }, + {"bring", G_admin_bring, "bring", + "This will bring you to a players location.", + "[^3name|slot#^7]" + }, + + {"bubble", G_admin_bubble, "bubble", + "draw attention to a player with bubbles", + "[^3name|slot#^7]" + }, + {"buildlog", G_admin_buildlog, "buildlog", "display a list of recent builds and deconstructs, optionally specifying" " a team", @@ -108,26 +118,31 @@ g_admin_cmd_t g_admin_cmds[ ] = "[^3name|slot#^7]" }, + {"denyweapon", G_admin_denyweapon, "denyweapon", + "take away a player's ability to use a weapon or class", + "[^3name|slot#^7] [^3class|weapon^7]" + }, + {"designate", G_admin_designate, "designate", "give the player designated builder privileges", "[^3name|slot#^7]" }, - + {"devmap", G_admin_devmap, "devmap", "load a map with cheats (and optionally force layout)", "[^3mapname^7] (^5layout^7)" }, - {"denyweapon", G_admin_denyweapon, "denyweapon", - "take away a player's ability to use a weapon or class", - "[^3name|slot#^7] [^3class|weapon^7]" - }, - {"drop", G_admin_drop, "drop", "kick a client from the server without log", "[^3name|slot#^7] [^3message^7]" }, + {"expire", G_admin_expire, "expire", + "Expires up to 5 level 1 admins with !seen times older than g_adminExpireTime", + "[^5confirm^7]" + }, + {"flag", G_admin_flag, "flag", "add an admin flag to a player, prefix flag with '-' to disallow the flag. " "console can use this command on admin levels by prefacing a '*' to the admin level value.", @@ -138,7 +153,7 @@ g_admin_cmd_t g_admin_cmds[ ] = "list all flags understood by this server", "" }, - + {"help", G_admin_help, "help", "display commands available to you or help on a specific command", "(^5command^7)" @@ -148,12 +163,17 @@ g_admin_cmd_t g_admin_cmds[ ] = "display the contents of server info files", "(^5subject^7)" }, - + + {"immunity", G_admin_immunity, "immunity", + "give a player ban immunity", + "[^3+|-^7](^5slot#^7)" + }, + {"invisible", G_admin_invisible, "invisible", "hides a player so they cannot be seen in playerlists", "" }, - + {"kick", G_admin_kick, "kick", "kick a player with an optional reason", "[^3name|slot#^7] (^5reason^7)" @@ -224,9 +244,11 @@ g_admin_cmd_t g_admin_cmds[ ] = "" }, - {"nobuild", G_admin_nobuild, "nobuild", - "set nobuild markers to prevent players from building in an area", - "(^5area^7) (^5height^7)" + {"outlaw", G_admin_outlaw, "outlaw", + "adjust a player's bleed counter, usually to make their base turn on them." + " bleed value can be '?' to query their current value, a number to add and activate bleed status," + " +num or -num will silently adjust their current bleed value, 0 will pardon them", + "(^5name|slot^7) (^5bleed value)" }, {"passvote", G_admin_passvote, "passvote", @@ -240,6 +262,12 @@ g_admin_cmd_t g_admin_cmds[ ] = "(^5name|slot|*^7)" }, + {"practice", G_admin_practice, "practice", + "set practice mode for player names with 'clan tag', " + "these players will be allowed to join any team regardless of balance. " + "'map count' is number of maps to maintain practice mode", + "[^3clan tag|off^7] [^3map count^7])" + }, {"putteam", G_admin_putteam, "putteam", "move a player to a specified team", @@ -253,7 +281,7 @@ g_admin_cmd_t g_admin_cmds[ ] = {"register", G_admin_register, "register", "Registers your name to protect it from being used by others or updates your admin name to your current name.", - "" + "[^3level^7] [^3password^7]" }, {"rename", G_admin_rename, "rename", @@ -299,12 +327,13 @@ g_admin_cmd_t g_admin_cmds[ ] = {"spec999", G_admin_spec999, "spec999", "move 999 pingers to the spectator team", - "" - }, - + ""}, + + //kev: a bit of a hack, but there is no real point to + //creating a new admin flag for this, so i stole it from !help {"specme", G_admin_putmespec, "specme", - "moves you to the spectators", - "" + "moves you to the spectators (can be done silently with the 's' argument)", + "(^5s^7)" }, {"subnetban", G_admin_subnetban, "subnetban", @@ -316,7 +345,7 @@ g_admin_cmd_t g_admin_cmds[ ] = "\n ^1WARNING:^7 Use of this command may make your admin.dat incompatible with other game.qvms" }, - {"suspendban", G_admin_suspendban, "ban", + {"suspendban", G_admin_suspendban, "suspendban", "suspend a ban for a length of time. time is specified as numbers " "followed by units 'w' (weeks), 'd' (days), 'h' (hours) or 'm' (minutes)," " or seconds if no units are specified", @@ -325,7 +354,11 @@ g_admin_cmd_t g_admin_cmds[ ] = {"time", G_admin_time, "time", "show the current local server time", - "" + ""}, + + {"tklog", G_admin_tklog, "tklog", + "list recent teamkill activity", + "(^5start id#|name|-skip#^7) (^5search skip#^7)" }, {"unban", G_admin_unban, "ban", @@ -337,13 +370,13 @@ g_admin_cmd_t g_admin_cmds[ ] = "revoke designated builder privileges", "[^3name|slot#^7]" }, - + {"unflag", G_admin_flag, "flag", "clears an admin flag from a player. " "console can use this command on admin levels by prefacing a '*' to the admin level value.", "[^3name|slot#|admin#|*adminlevel^7] (^5+^7|^5-^7)[^3flag^7]" }, - + {"unlock", G_admin_unlock, "lock", "unlock a locked team", "[^3a|h^7]" @@ -354,6 +387,11 @@ g_admin_cmd_t g_admin_cmds[ ] = "[^3name|slot#^7]" }, + {"nobuild", G_admin_nobuild, "nobuild", + "Enable and control nobuild mode.", + "[^3on|off|save|add|del|list|mode|zone|+|-|go^7]" + }, + {"unpause", G_admin_pause, "pause", "allow a player to interact with the game." " * will unpause all players, using no argument will unpause game clock", @@ -376,6 +414,12 @@ g_admin_ban_t *g_admin_bans[ MAX_ADMIN_BANS ]; g_admin_command_t *g_admin_commands[ MAX_ADMIN_COMMANDS ]; g_admin_namelog_t *g_admin_namelog[ MAX_ADMIN_NAMELOGS ]; +static int admin_adminlog_index = 0; +g_admin_adminlog_t *g_admin_adminlog[ MAX_ADMIN_ADMINLOGS ]; + +static int admin_tklog_index = 0; +g_admin_tklog_t *g_admin_tklog[ MAX_ADMIN_TKLOGS ]; + // match a certain flag within these flags // return state of whether flag was found or not, // set *perm to indicate whether found flag was + or - @@ -404,16 +448,12 @@ static qboolean admin_permission( char *flags, const char *flag, qboolean *perm *perm = base_perm; return qtrue; } - return qfalse; } -static int admin_adminlog_index = 0; -g_admin_adminlog_t *g_admin_adminlog[ MAX_ADMIN_ADMINLOGS ]; - // This function should only be used directly when the client is connecting and thus has no GUID. // Else, use G_admin_permission() -qboolean G_admin_permission_guid( char *guid, const char* flag ) +qboolean G_admin_permission_guid( char *guid, const char *flag ) { int i; int l = 0; @@ -436,8 +476,7 @@ qboolean G_admin_permission_guid( char *guid, const char* flag ) for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) { if( g_admin_levels[ i ]->level == l ) - return admin_permission( g_admin_levels[ i ]->flags, flag, &perm ) && - perm; + return admin_permission( g_admin_levels[ i ]->flags, flag, &perm ) && perm; } return qfalse; } @@ -542,18 +581,13 @@ static qboolean admin_higher_guid( char *admin_guid, char *victim_guid ) { int i; int alevel = 0; - int alevel2 = 0; + qboolean perm = qfalse; for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) { if( !Q_stricmp( admin_guid, g_admin_admins[ i ]->guid ) ) { alevel = g_admin_admins[ i ]->level; - - // Novelty Levels should be equivelant to level 1 - if( alevel > 9 ) - alevel = 1; - break; } } @@ -561,17 +595,9 @@ static qboolean admin_higher_guid( char *admin_guid, char *victim_guid ) { if( !Q_stricmp( victim_guid, g_admin_admins[ i ]->guid ) ) { - alevel2 = g_admin_admins[ i ]->level; - - // Novelty Levels should be equivelant to level 1 - if( alevel2 > 9 ) - alevel2 = 1; - - if( alevel < alevel2 ) - return qfalse; - - if( strstr( g_admin_admins[ i ]->flags, va( "%s", ADMF_IMMUTABLE ) ) ) + if( alevel < g_admin_admins[ i ]->level ) return qfalse; + return ( !admin_permission( g_admin_admins[ i ]->flags, ADMF_IMMUTABLE, &perm ) || !perm ); } } return qtrue; @@ -619,7 +645,7 @@ static void admin_writeconfig( void ) { fileHandle_t f; int len, i; - int t, expiretime; + int t; char levels[ MAX_STRING_CHARS ] = {""}; if( !g_admin.string[ 0 ] ) @@ -650,19 +676,9 @@ static void admin_writeconfig( void ) 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 ) + if( g_admin_admins[ i ]->level == 0 ) continue; - //if set dont write admins that havent been seen in a while - expiretime = G_admin_parse_time( g_adminExpireTime.string ); - if( expiretime > 0 ) { - //only expire level 1 people - if( t - expiretime > g_admin_admins[ i ]->seen && g_admin_admins[ i ]->level == 1 ) { - G_Printf("Admin %s has been expired.\n", g_admin_admins[ i ]->name ); - continue; - } - } - trap_FS_Write( "[admin]\n", 8, f ); trap_FS_Write( "name = ", 10, f ); admin_writeconfig_string( g_admin_admins[ i ]->name, f ); @@ -672,8 +688,6 @@ static void admin_writeconfig( void ) admin_writeconfig_int( g_admin_admins[ i ]->level, f ); trap_FS_Write( "flags = ", 10, f ); admin_writeconfig_string( g_admin_admins[ i ]->flags, f ); - trap_FS_Write( "seen = ", 10, f ); - admin_writeconfig_int( g_admin_admins[ i ]->seen, f ); trap_FS_Write( "\n", 1, f ); } for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ ) @@ -697,7 +711,8 @@ static void admin_writeconfig( void ) admin_writeconfig_string( g_admin_bans[ i ]->made, f ); trap_FS_Write( "expires = ", 10, f ); admin_writeconfig_int( g_admin_bans[ i ]->expires, f ); - if( g_admin_bans[ i ]->suspend > t ) { + if( g_admin_bans[ i ]->suspend > t ) + { trap_FS_Write( "suspend = ", 10, f ); admin_writeconfig_int( g_admin_bans[ i ]->suspend, f ); } @@ -778,6 +793,253 @@ 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; + + G_admin_karma_sync( ); + + if( !g_chat.string[ 0 ] ) + { + G_Printf( "WARNING: g_chat is not set. " + " channel subscriptions will not be saved to a file.\n" ); + return; + } + + // check that there is something to save + found = qfalse; + for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] && !found; i++ ) + { + if( g_admin_admins[ i ]->level == 0 ) + continue; + for( j = 0; j < CHAT_MAXCHAN; j++ ) + { + if( g_admin_admins[ i ]->chat[ j ][ 0 ] ) + found = qtrue; + } + if(g_admin_admins[ i ]->seen ) + found = qtrue; + } + if( !found ) + { + G_Printf( "Cowardly refusing to create an empty chat 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 == 0 ) + 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 ); + trap_FS_Write( "karma = ", 10, f ); + admin_writeconfig_int( g_admin_admins[ i ]->karma, 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( a == NULL ) + { + // no guid match, ignored + } + else if( !Q_stricmp( t, "seen" ) ) + { + admin_readconfig_int( &cnf, &a->seen ); + } + else if( !Q_stricmp( t, "karma" ) ) + { + admin_readconfig_int( &cnf, &a->karma ); + } + 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 ) ) + { + ent->client->pers.karma = g_admin_admins[ i ]->karma; + 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 ) @@ -828,7 +1090,7 @@ static void admin_default_levels( void ) sizeof( l->name ) ); Q_strncpyz( g_admin_levels[ 4 ]->flags, "listplayers admintest help specme time putteam spec999 kick mute showbans " - "ban namelog warn denybuild ADMINCHAT SEESFULLLISTPLAYERS", + "ban namelog warn denybuild invisible ADMINCHAT SEESFULLLISTPLAYERS", sizeof( l->flags ) ); Q_strncpyz( g_admin_levels[ 5 ]->name, "^1Server Operator", @@ -897,7 +1159,7 @@ void G_admin_set_adminname( gentity_t *ent ) } } -// Get an admin's registered name +// get an admin's realname const char *G_admin_get_adminname( gentity_t *ent ) { int i; @@ -914,8 +1176,6 @@ const char *G_admin_get_adminname( gentity_t *ent ) return ent->client->pers.netname; } -// Get an admin's name to print as the user of various commands, -// obeying admin stealth settings when necessary char* G_admin_adminPrintName( gentity_t *ent ) { char *out; @@ -1110,26 +1370,27 @@ static int admin_listadmins( gentity_t *ent, int start, char *search, int minlev break; } } - ADMBP( va( "%4i %4i %s^7 (*%s) %s^7\n", + ADMBP( va( "%4i %4i %s^7 (*%s) ^1%1s ^7%s^7\n", i, l, lname, guid_stub, + ( G_admin_permission( &g_entities[ i ], ADMF_BAN_IMMUNITY ) ) ? "I" : "", vic->client->pers.netname ) ); drawn++; } for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] && drawn < MAX_ADMIN_LISTITEMS; i++ ) - if( g_admin_admins[ i ]->level >= minlevel ) + if( ( minlevel > 0 && g_admin_admins[ i ]->level >= minlevel ) || + ( minlevel < 0 && g_admin_admins[ i ]->level <= minlevel ) || + ( minlevel == 0 && g_admin_admins[ i ]->level != 0 ) ) { - if( start ) { start--; continue; } - if( search[ 0 ] ) { G_SanitiseString( g_admin_admins[ i ]->name, name, sizeof( name ) ); @@ -1174,11 +1435,12 @@ static int admin_listadmins( gentity_t *ent, int start, char *search, int minlev break; } } - ADMBP( va( "%4i %4i %s^7 (*%s) %s^7\n", + ADMBP( va( "%4i %4i %s^7 (*%s) ^1%1s ^7%s^7\n", ( i + MAX_CLIENTS ), g_admin_admins[ i ]->level, lname, guid_stub, + ( G_admin_permission_guid( g_admin_admins[ i ]->guid, ADMF_BAN_IMMUNITY ) ) ? "I" : "", g_admin_admins[ i ]->name ) ); drawn++; } @@ -1223,6 +1485,7 @@ qboolean G_admin_ban_check( char *userinfo, char *reason, int rlen ) int t; char notice[51]; qboolean ignoreIP = qfalse; + qboolean banned = qfalse; trap_Cvar_VariableStringBuffer( "g_banNotice", notice, sizeof( notice ) ); @@ -1252,17 +1515,15 @@ qboolean G_admin_ban_check( char *userinfo, char *reason, int rlen ) if(!IP[k]) continue; userIP |= IP[k] << 8*(k-1); } - ignoreIP = G_admin_permission_guid( guid , ADMF_BAN_IMMUNITY ); + ignoreIP = G_admin_permission_guid( guid, ADMF_BAN_IMMUNITY ); for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ ) { // 0 is for perm ban if( g_admin_bans[ i ]->expires != 0 && ( g_admin_bans[ i ]->expires - t ) < 1 ) continue; - //if suspend time is over continue if( g_admin_bans[ i ]->suspend >= t ) continue; - if( !ignoreIP ) { tempIP = userIP; @@ -1295,47 +1556,38 @@ qboolean G_admin_ban_check( char *userinfo, char *reason, int rlen ) if( intIP == tempIP || mask == 0 ) { - char duration[ 32 ]; - G_admin_duration( ( g_admin_bans[ i ]->expires - t ), - duration, sizeof( duration ) ); - - // flood protected - if( t - lastConnectTime >= 300 || - Q_stricmp( lastConnectIP, ip ) ) - { - lastConnectTime = t; - Q_strncpyz( lastConnectIP, ip, sizeof( lastConnectIP ) ); - - G_WarningsPrintf( - "ban", - "Banned player %s^7 (%s^7) tried to connect (ban #%i on %s by %s^7 expires %s reason: %s^7 )\n", - Info_ValueForKey( userinfo, "name" ), - g_admin_bans[ i ]->name, - i+1, - ip, - g_admin_bans[ i ]->banner, - duration, - g_admin_bans[ i ]->reason ); - } - - Com_sprintf( - reason, - rlen, - "You have been banned by %s^7 reason: %s^7 expires: %s %s", - g_admin_bans[ i ]->banner, - g_admin_bans[ i ]->reason, - duration, - notice - ); G_LogPrintf("Banned player tried to connect from IP %s\n", ip); - return qtrue; + banned = qtrue; } } - if( *guid && !Q_stricmp( g_admin_bans[ i ]->guid, guid ) ) + if( !banned && *guid && !Q_stricmp( g_admin_bans[ i ]->guid, guid ) ) + { + G_LogPrintf("Banned player tried to connect with GUID %s\n", guid); + banned = qtrue; + } + if( banned ) { char duration[ 32 ]; G_admin_duration( ( g_admin_bans[ i ]->expires - t ), duration, sizeof( duration ) ); + + // flood protected + if( t - lastConnectTime >= 300 || + Q_stricmp( lastConnectIP, ip ) ) + { + lastConnectTime = t; + Q_strncpyz( lastConnectIP, ip, sizeof( lastConnectIP ) ); + + G_AdminsPrintf( + "Banned player %s^7 (%s^7) tried to connect (ban #%i on %s by %s^7 expires %s reason: %s^7 )\n", + Info_ValueForKey( userinfo, "name" ), + g_admin_bans[ i ]->name, + i+1, + ip, + g_admin_bans[ i ]->banner, + duration, + g_admin_bans[ i ]->reason ); + } Com_sprintf( reason, rlen, @@ -1344,7 +1596,6 @@ qboolean G_admin_ban_check( char *userinfo, char *reason, int rlen ) g_admin_bans[ i ]->reason, duration ); - G_Printf("Banned player tried to connect with GUID %s\n", guid); return qtrue; } } @@ -1469,10 +1720,7 @@ void G_admin_namelog_update( gclient_t *client, qboolean disconnect ) char n2[ MAX_NAME_LENGTH ]; int clientNum = ( client - level.clients ); - if ( client->sess.invisible == qfalse ) - { - G_admin_seen_update( client->pers.guid ); - } + G_admin_seen_update( client, disconnect ); G_SanitiseString( client->pers.netname, n1, sizeof( n1 ) ); for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) @@ -1539,11 +1787,6 @@ void G_admin_namelog_update( gclient_t *client, qboolean disconnect ) G_AdminsPrintf( "^7%s^7's Putteam spectator has been restored\n", client->pers.netname ); g_admin_namelog[ i ]->specExpires = 0; } - if( g_admin_namelog[ i ]->voteCount > 0 ) - { - client->pers.voteCount = g_admin_namelog[ i ]->voteCount; - g_admin_namelog[ i ]->voteCount = 0; - } } else { @@ -1573,12 +1816,9 @@ void G_admin_namelog_update( gclient_t *client, qboolean disconnect ) { g_admin_namelog[ i ]->specExpires = client->pers.specExpires; } - if( client->pers.voteCount > 0 ) - { - g_admin_namelog[ i ]->voteCount = client->pers.voteCount; - } } + return; } } @@ -1698,10 +1938,6 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg ) { admin_readconfig_string( &cnf, a->flags, sizeof( a->flags ) ); } - else if( !Q_stricmp( t, "seen" ) ) - { - admin_readconfig_int( &cnf, &a->seen ); - } else { ADMP( va( "^3!readconfig: ^7[admin] parse error near %s on line %d\n", @@ -1863,6 +2099,9 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg ) for( i = 0; i < level.maxclients; i++ ) if( level.clients[ i ].pers.connected != CON_DISCONNECTED ) level.clients[ i ].pers.adminLevel = G_admin_level( &g_entities[ i ] ); + + G_admin_chat_readconfig( ent ); + return qtrue; } @@ -1874,7 +2113,6 @@ qboolean G_admin_time( gentity_t *ent, int skiparg ) t = trap_RealTime( &qt ); ADMP( va( "^3!time: ^7local time is %02i:%02i:%02i\n", qt.tm_hour, qt.tm_min, qt.tm_sec ) ); - return qtrue; } @@ -2138,379 +2376,6 @@ qboolean G_admin_setlevel( gentity_t *ent, int skiparg ) return qtrue; } -static int SortFlags( const void *pa, const void *pb ) -{ - char *a = (char *)pa; - char *b = (char *)pb; - - if( *a == '-' || *a == '+' ) - a++; - if( *b == '-' || *b == '+' ) - b++; - return strcmp(a, b); -} - -#define MAX_USER_FLAGS 200 -const char *G_admin_user_flag( char *oldflags, char *flag, qboolean add, qboolean clear, - char *newflags, int size ) -{ - char *token, *token_p; - char *key; - char head_flags[ MAX_USER_FLAGS ][ MAX_ADMIN_FLAG_LEN ]; - char tail_flags[ MAX_USER_FLAGS ][ MAX_ADMIN_FLAG_LEN ]; - char allflag[ MAX_ADMIN_FLAG_LEN ]; - char newflag[ MAX_ADMIN_FLAG_LEN ]; - int head_count = 0; - int tail_count = 0; - qboolean flagset = qfalse; - int i; - - if( !flag[ 0 ] ) - { - return "invalid admin flag"; - } - - allflag[ 0 ] = '\0'; - token_p = oldflags; - while( *( token = COM_Parse( &token_p ) ) ) - { - key = token; - if( *key == '-' || *key == '+' ) - key++; - - if( !strcmp( key, flag ) ) - { - if( flagset ) - continue; - flagset = qtrue; - if( clear ) - { - // clearing ALLFLAGS will result in clearing any following flags - if( !strcmp( key, ADMF_ALLFLAGS ) ) - break; - else - continue; - } - Com_sprintf( newflag, sizeof( newflag ), "%s%s", - ( add ) ? "+" : "-", key ); - } - else - { - Q_strncpyz( newflag, token, sizeof( newflag ) ); - } - - if( !strcmp( key, ADMF_ALLFLAGS ) ) - { - if( !allflag[ 0 ] ) - Q_strncpyz( allflag, newflag, sizeof( allflag ) ); - continue; - } - - if( !allflag[ 0 ] ) - { - if( head_count < MAX_USER_FLAGS ) - { - Q_strncpyz( head_flags[ head_count ], newflag, - sizeof( head_flags[ head_count ] ) ); - head_count++; - } - } - else - { - if( tail_count < MAX_USER_FLAGS ) - { - Q_strncpyz( tail_flags[ tail_count ], newflag, - sizeof( tail_flags[ tail_count ] ) ); - tail_count++; - } - } - } - - if( !flagset && !clear ) - { - if( !strcmp( flag, ADMF_ALLFLAGS ) ) - { - Com_sprintf( allflag, sizeof( allflag ), "%s%s", - ( add ) ? "" : "-", ADMF_ALLFLAGS ); - } - else if( !allflag[ 0 ] ) - { - if( head_count < MAX_USER_FLAGS ) - { - Com_sprintf( head_flags[ head_count ], sizeof( head_flags[ head_count ] ), - "%s%s", ( add ) ? "" : "-", flag ); - head_count++; - } - } - else - { - if( tail_count < MAX_USER_FLAGS ) - { - Com_sprintf( tail_flags[ tail_count ], sizeof( tail_flags[ tail_count ] ), - "%s%s", ( add ) ? "+" : "-", flag ); - tail_count++; - } - } - } - - qsort( head_flags, head_count, sizeof( head_flags[ 0 ] ), SortFlags ); - qsort( tail_flags, tail_count, sizeof( tail_flags[ 0 ] ), SortFlags ); - - // rebuild flags - newflags[ 0 ] = '\0'; - for( i = 0; i < head_count; i++ ) - { - Q_strcat( newflags, size, - va( "%s%s", ( i ) ? " ": "", head_flags[ i ] ) ); - } - if( allflag[ 0 ] ) - { - Q_strcat( newflags, size, - va( "%s%s", ( newflags[ 0 ] ) ? " ": "", allflag ) ); - - for( i = 0; i < tail_count; i++ ) - { - Q_strcat( newflags, size, - va( " %s", tail_flags[ i ] ) ); - } - } - - return NULL; -} - -typedef struct { - char *flag; - char *description; -} AdminFlagListEntry_t; -static AdminFlagListEntry_t adminFlagList[] = -{ - { ADMF_ACTIVITY, "inactivity rules do not apply" }, - { ADMF_ADMINCHAT, "can see and use admin chat" }, - { ADMF_ALLFLAGS, "has all flags and can use any command" }, - { ADMF_BAN_IMMUNITY, "immune from IP bans" }, - { ADMF_CAN_PERM_BAN, "can permanently ban players" }, - { ADMF_DBUILDER, "permanent designated builder" }, - { ADMF_FORCETEAMCHANGE, "team balance rules do not apply" }, - { ADMF_INCOGNITO, "does not show as admin in !listplayers" }, - { ADMF_IMMUNITY, "cannot be vote kicked or muted" }, - { ADMF_IMMUTABLE, "admin commands cannot be used on them" }, - { ADMF_NOCENSORFLOOD, "no flood protection" }, - { ADMF_NO_VOTE_LIMIT, "vote limitations do not apply" }, - { ADMF_SEESFULLLISTPLAYERS, "sees all info in !listplayers" }, - { ADMF_SPEC_ALLCHAT, "can see team chat as spectator" }, - { ADMF_ADMINSTEALTH, "uses admin stealth" }, - { ADMF_TEAMCHANGEFREE, "keeps credits on team switch" }, - { ADMF_TEAMCHAT_CMD, "can run commands from team chat" }, - { ADMF_UNACCOUNTABLE, "does not need to specify reason for kick/ban" }, - { ADMF_NO_CHAT, "can not talk" }, - { ADMF_NO_VOTE, "can not call votes" } -}; -static int adminNumFlags= sizeof( adminFlagList ) / sizeof( adminFlagList[ 0 ] ); - -#define MAX_LISTCOMMANDS 128 -qboolean G_admin_flaglist( gentity_t *ent, int skiparg ) -{ - qboolean shown[ MAX_LISTCOMMANDS ]; - int i, j; - int count = 0; - - ADMBP_begin(); - - ADMBP( "^3Ability flags:\n" ); - - for( i = 0; i < adminNumFlags; i++ ) - { - ADMBP( va( " %s%-20s ^7%s\n", - ( adminFlagList[ i ].flag[ 0 ] != '.' ) ? "^5" : "^1", - adminFlagList[ i ].flag, - adminFlagList[ i ].description ) ); - } - - ADMBP( "^3Command flags:\n" ); - - memset( shown, 0, sizeof( shown ) ); - for( i = 0; i < adminNumCmds; i++ ) - { - if( i < MAX_LISTCOMMANDS && shown[ i ] ) - continue; - ADMBP( va( " ^5%-20s^7", g_admin_cmds[ i ].flag ) ); - for( j = i; j < adminNumCmds; j++ ) - { - if( !strcmp( g_admin_cmds[ j ].flag, g_admin_cmds[ i ].flag ) ) - { - ADMBP( va( " %s", g_admin_cmds[ j ].keyword ) ); - if( j < MAX_LISTCOMMANDS ) - shown[ j ] = qtrue; - } - } - ADMBP( "\n" ); - count++; - } - - ADMBP( va( "^3!flaglist: ^7listed %d abilities and %d command flags\n", - adminNumFlags, count ) ); - - ADMBP_end(); - - return qtrue; -} - -qboolean G_admin_flag( gentity_t *ent, int skiparg ) -{ - char command[ MAX_ADMIN_CMD_LEN ], *cmd; - char name[ MAX_NAME_LENGTH ]; - char flagbuf[ MAX_ADMIN_FLAG_LEN ]; - char *flag; - int id; - char adminname[ MAX_NAME_LENGTH ] = {""}; - const char *result; - qboolean add = qtrue; - qboolean clear = qfalse; - int admin_level = -1; - int i, level; - - 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; - } - - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - if( name[ 0 ] == '*' ) - { - if( ent ) - { - ADMP( va( "^3!%s: only console can change admin level flags\n", cmd ) ); - return qfalse; - } - id = atoi( name + 1 ); - for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) - { - if( g_admin_levels[ i ]->level == id ) - { - admin_level = i; - break; - } - } - if( admin_level < 0 ) - { - ADMP( va( "^3!%s: admin level %d does not exist\n", cmd, id ) ); - return qfalse; - } - Com_sprintf( adminname, sizeof( adminname ), "admin level %d", id ); - } - else - { - id = G_admin_find_admin_slot( ent, name, cmd, 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 than you\n", cmd ) ); - return qfalse; - } - } - - if( G_SayArgc() < 3 + skiparg ) - { - flag = ""; - level = 0; - if( admin_level < 0 ) - { - for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) - { - if( g_admin_admins[ id ]->level == g_admin_levels[ i ]->level ) - { - flag = g_admin_levels[ i ]->flags; - level = g_admin_admins[ id ]->level; - break; - } - } - ADMP( va( "^3%s:^7 flags for %s^7 are '^3%s^7'\n", - cmd, adminname, g_admin_admins[ id ]->flags) ); - } - else - { - flag = g_admin_levels[ admin_level ]->flags; - level = g_admin_levels[ admin_level ]->level; - } - ADMP( va( "^3%s:^7 level %d flags are '%s'\n", - cmd, level, flag ) ); - - return qtrue; - } - - G_SayArgv( 2 + skiparg, flagbuf, sizeof( flagbuf ) ); - flag = flagbuf; - if( flag[ 0 ] == '-' || flag[ 0 ] == '+' ) - { - add = ( flag[ 0 ] == '+' ); - 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( flag[ 0 ] != '.' && !G_admin_permission( ent, flag ) ) - { - 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; - } - - if( admin_level < 0 ) - { - result = G_admin_user_flag( g_admin_admins[ id ]->flags, flag, add, clear, - g_admin_admins[ id ]->flags, sizeof( g_admin_admins[ id ]->flags ) ); - } - else - { - result = G_admin_user_flag( g_admin_levels[ admin_level ]->flags, flag, add, clear, - g_admin_levels[ admin_level ]->flags, - sizeof( g_admin_levels[ admin_level ]->flags ) ); - } - if( result ) - { - ADMP( va( "^3!flag: ^7an error occured setting flag '^3%s^7', %s\n", - flag, result ) ); - return qfalse; - } - - if( !Q_stricmp( cmd, "flag" ) ) - { - G_AdminsPrintf( "^3!%s: ^7%s^7 was %s admin flag '%s' by %s\n", - cmd, adminname, - ( add ) ? "given" : "denied", - flag, - ( ent ) ? ent->client->pers.netname : "console" ); - } - else - { - G_AdminsPrintf( "^3!%s: ^7admin flag '%s' for %s^7 cleared by %s\n", - cmd, flag, 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; -} - int G_admin_parse_time( const char *time ) { int seconds = 0, num = 0; @@ -2544,6 +2409,49 @@ int G_admin_parse_time( const char *time ) return seconds; } +static void admin_check_duplicate_ban( int ban ) +{ + qtime_t qt; + int t; + int i, j; + qboolean immune; + + if ( ban < 0 || ban >= MAX_ADMIN_BANS || !g_admin_bans[ ban ] ) + return; + + t = trap_RealTime( &qt ); + for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ ) + { + if( g_admin_bans[ i ]->expires != 0 + && ( g_admin_bans[ i ]->expires - t ) < 1 ) + { + continue; + } + + if( i != ban && + strstr( g_admin_bans[ ban ]->ip, g_admin_bans[ i ]->ip ) == g_admin_bans[ ban ]->ip ) + { + immune = qfalse; + + for( j = 0; j < MAX_ADMIN_ADMINS && g_admin_admins[ j ]; j++ ) + { + if( !Q_stricmp( g_admin_bans[ ban ]->guid, g_admin_admins[ j ]->guid ) && + G_admin_permission_guid( g_admin_admins[ j ]->guid, ADMF_BAN_IMMUNITY ) ) + { + immune = qtrue; + } + } + + G_AdminsPrintf( "new ban #%d duplicates %sban #%d (%s^7)%s.\n", + ban + 1, + ( g_admin_bans[ i ]->suspend > t ) ? "SUSPENDED " : "", + i + 1, + g_admin_bans[ i ]->name, + ( immune ) ? ", player has immunity" : "" ); + } + } +} + static qboolean admin_create_ban( gentity_t *ent, char *netname, char *guid, @@ -2597,6 +2505,82 @@ static qboolean admin_create_ban( gentity_t *ent, return qfalse; } g_admin_bans[ i ] = b; + + admin_check_duplicate_ban( i ); + + return qtrue; +} + +static qboolean admin_create_ban_check_repeats( gentity_t *ent, + char *netname, + char *guid, + char *ip, + int seconds, + char *reason ) +{ + qboolean repeatBan = qfalse; + qboolean isKick = qfalse; + qtime_t qt; + int t; + int i; + + t = trap_RealTime( &qt ); + + if( seconds >= G_admin_parse_time( g_adminTempBan.string ) - 60 && + seconds <= G_admin_parse_time( g_adminTempBan.string ) + 60 ) + isKick = qtrue; + + if( g_adminBanRepeatKicks.integer && isKick ) + { + for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ] && !repeatBan; i++ ) + { + if( g_admin_bans[ i ]->expires != 0 && + g_admin_bans[ i ]->expires - t < 1 ) + continue; + + if( !Q_stricmp( ip, g_admin_bans[ i ]->ip ) || + ( guid[0] != 'X' && !Q_stricmp( guid, g_admin_bans[ i ]->guid) )) + { + char duration[ 32 ]; + + g_admin_bans[ i ]->suspend = 0; + g_admin_bans[ i ]->expires += g_adminBanRepeatKicks.integer * 60 * 60; + repeatBan = qtrue; + + G_admin_duration( ( g_admin_bans[ i ]->expires - t ), duration, sizeof( duration ) ); + trap_SendServerCommand( -1, + va( "print \"^3autoban: ^7%s^7 has been auto-banned. duration: %s, reason: repeated kicks\n\"", + netname, duration ) ); + + return qtrue; + } + } + } + + if( !admin_create_ban( ent, netname, guid, ip, seconds, reason ) ) + return qfalse; + + if( g_adminBanRepeatKicks.integer && seconds > 0 && isKick ) + { + int newestBan = -1; + int length; + + length = g_adminBanRepeatKicks.integer * 60 * 60; + if( admin_create_ban( ent, netname, guid, ip, length, reason ) ) + { + for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ ) + { + newestBan = i; + } + if( newestBan >= 0 && + !Q_stricmp( ip, g_admin_bans[ newestBan ]->ip ) ) + { + g_admin_bans[ newestBan ]->suspend = t + length; + Q_strncpyz( g_admin_bans[ newestBan ]->banner, "^3auto-banner", sizeof( g_admin_bans[ newestBan ]->banner ) ); + } + } + } + return qtrue; } @@ -2634,7 +2618,8 @@ qboolean G_admin_kick( gentity_t *ent, int skiparg ) return qfalse; } vic = &g_entities[ pids[ 0 ] ]; - admin_create_ban( ent, + G_admin_autorevert( vic ); + admin_create_ban_check_repeats( ent, vic->client->pers.netname, vic->client->pers.guid, vic->client->pers.ip, G_admin_parse_time( g_adminTempBan.string ), @@ -2642,10 +2627,20 @@ qboolean G_admin_kick( gentity_t *ent, int skiparg ) if( g_admin.string[ 0 ] ) admin_writeconfig(); + vic->client->pers.karma -= 5000; + trap_SendServerCommand( pids[ 0 ], va( "disconnect \"You have been kicked.\n%s^7\nreason:\n%s\n%s\"", ( ent ) ? va( "admin:\n%s", G_admin_adminPrintName( ent ) ) : "admin\nconsole", ( *reason ) ? reason : "kicked by admin", notice ) ); + + G_LogPrintf( "kick: %i %i [%s] (%s) %s^7 %s^7\n", + vic->client->ps.clientNum, + G_admin_parse_time( g_adminTempBan.string ), + vic->client->pers.ip, + vic->client->pers.guid, + vic->client->pers.netname, + ( *reason ) ? reason : "automatic temp ban created by kick" ); trap_DropClient( pids[ 0 ], va( "kicked%s^7, reason: %s", ( ent ) ? va( " by %s", G_admin_adminPrintName( ent ) ) : " by console", @@ -2669,6 +2664,7 @@ qboolean G_admin_ban( gentity_t *ent, int skiparg ) char s2[ MAX_NAME_LENGTH ]; char guid_stub[ 9 ]; char notice[51]; + gentity_t *vic; trap_Cvar_VariableStringBuffer( "g_banNotice", notice, sizeof( notice ) ); @@ -2823,7 +2819,7 @@ qboolean G_admin_ban( gentity_t *ent, int skiparg ) return qfalse; } - admin_create_ban( ent, + admin_create_ban_check_repeats( ent, g_admin_namelog[ logmatch ]->name[ 0 ], g_admin_namelog[ logmatch ]->guid, g_admin_namelog[ logmatch ]->ip, @@ -2836,6 +2832,13 @@ qboolean G_admin_ban( gentity_t *ent, int skiparg ) else admin_writeconfig(); + G_LogPrintf( "ban: %i %i [%s] (%s) %s^7 %s^7\n", + g_admin_namelog[ logmatch ]->slot, seconds, + g_admin_namelog[ logmatch ]->ip, + g_admin_namelog[ logmatch ]->guid, + g_admin_namelog[ logmatch ]->name[ 0 ], + ( *reason ) ? reason : "banned by admin" ); + if( g_admin_namelog[ logmatch ]->slot == -1 ) { // client is already disconnected so stop here @@ -2847,6 +2850,11 @@ qboolean G_admin_ban( gentity_t *ent, int skiparg ) ( *reason ) ? reason : "banned by admin" ) ); return qtrue; } + vic = &g_entities[ g_admin_namelog[ logmatch ]->slot ]; + G_admin_autorevert( vic ); + + if( g_karma.integer ) + vic->client->pers.karma -= 5000; trap_SendServerCommand( g_admin_namelog[ logmatch ]->slot, va( "disconnect \"You have been banned.\n" @@ -3022,6 +3030,7 @@ qboolean G_admin_subnetban( gentity_t *ent, int skiparg ) ADMP( "^3!subnetban: ^7Only console may ban such a large network. Regular admins may only ban >=16.\n" ); return qfalse; } + if( strcmp(exl, "!") ) { ADMP( "^3!subnetban: ^1WARNING:^7 you are about to ban a large network, use !subnetban [ban] [mask] ! to force^7\n" ); @@ -3065,12 +3074,12 @@ qboolean G_admin_subnetban( gentity_t *ent, int skiparg ) { Q_strncpyz( cIPRlow, - va("%u.%u.%u.%u", (IPRlow & (255 << 24)) >> 24, (IPRlow & (255 << 16)) >> 16, (IPRlow & (255 << 8)) >> 8, IPRlow & 255), + va("%i.%i.%i.%i", (IPRlow & (255 << 24)) >> 24, (IPRlow & (255 << 16)) >> 16, (IPRlow & (255 << 8)) >> 8, IPRlow & 255), sizeof( cIPRlow ) ); Q_strncpyz( cIPRhigh, - va("%u.%u.%u.%u", (IPRhigh & (255 << 24)) >> 24, (IPRhigh & (255 << 16)) >> 16, (IPRhigh & (255 << 8)) >> 8, IPRhigh & 255), + va("%i.%i.%i.%i", (IPRhigh & (255 << 24)) >> 24, (IPRhigh & (255 << 16)) >> 16, (IPRhigh & (255 << 8)) >> 8, IPRhigh & 255), sizeof( cIPRhigh ) ); } @@ -3097,16 +3106,15 @@ qboolean G_admin_subnetban( gentity_t *ent, int skiparg ) return qtrue; } + qboolean G_admin_suspendban( gentity_t *ent, int skiparg ) { int bnum; int length; - int timenow = 0; int expires = 0; char *arg; char bs[ 5 ]; char duration[ 32 ]; - qtime_t qt; if( G_SayArgc() < 3 + skiparg ) { @@ -3127,9 +3135,7 @@ qboolean G_admin_suspendban( gentity_t *ent, int skiparg ) } arg = G_SayConcatArgs( 2 + skiparg ); - timenow = trap_RealTime( &qt ); length = G_admin_parse_time( arg ); - if( length < 0 ) { ADMP( "^3!suspendban: ^7invalid length\n" ); @@ -3140,16 +3146,13 @@ qboolean G_admin_suspendban( gentity_t *ent, int skiparg ) length = MAX_ADMIN_BANSUSPEND_DAYS * 24 * 60 * 60; ADMP( va( "^3!suspendban: ^7maximum ban suspension is %d days\n", MAX_ADMIN_BANSUSPEND_DAYS ) ); - } else if( g_admin_bans[ bnum - 1 ]->expires > 0 && length + timenow > g_admin_bans[ bnum - 1 ]->expires ) { - length = g_admin_bans[ bnum - 1 ]->expires - timenow; - G_admin_duration( length , duration, sizeof( duration ) ); - ADMP( va( "^3!suspendban: ^7Suspension Duration trimmed to Ban duration: %s\n", - duration ) ); } if ( length > 0 ) { - expires = timenow + length; + qtime_t qt; + + expires = trap_RealTime( &qt ) + length; } if( g_admin_bans[ bnum - 1 ]->suspend == expires ) { @@ -3225,93 +3228,6 @@ qboolean G_admin_unban( gentity_t *ent, int skiparg ) return qtrue; } -qboolean G_admin_putteam( gentity_t *ent, int skiparg ) -{ - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ], team[ 7 ], err[ MAX_STRING_CHARS ]; - gentity_t *vic; - pTeam_t teamnum = PTE_NONE; - char teamdesc[ 32 ] = {"spectators"}; - char secs[ 7 ]; - int seconds = 0; - qboolean useDuration = qfalse; - - G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - G_SayArgv( 2 + skiparg, team, sizeof( team ) ); - if( G_SayArgc() < 3 + skiparg ) - { - ADMP( "^3!putteam: ^7usage: !putteam [name] [h|a|s] (duration)\n" ); - return qfalse; - } - - if( G_ClientNumbersFromString( name, pids ) != 1 ) - { - G_MatchOnePlayer( pids, err, sizeof( err ) ); - ADMP( va( "^3!putteam: ^7%s\n", err ) ); - return qfalse; - } - if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) - { - ADMP( "^3!putteam: ^7sorry, but your intended victim has a higher " - " admin level than you\n" ); - return qfalse; - } - vic = &g_entities[ pids[ 0 ] ]; - - if ( vic->client->sess.invisible == qtrue ) - { - ADMP( "^3!putteam: ^7invisible players cannot join a team\n" ); - return qfalse; - } - - switch( team[ 0 ] ) - { - case 'a': - teamnum = PTE_ALIENS; - Q_strncpyz( teamdesc, "aliens", sizeof( teamdesc ) ); - break; - case 'h': - teamnum = PTE_HUMANS; - Q_strncpyz( teamdesc, "humans", sizeof( teamdesc ) ); - break; - case 's': - teamnum = PTE_NONE; - break; - default: - ADMP( va( "^3!putteam: ^7unknown team %c\n", team[ 0 ] ) ); - return qfalse; - } - //duration code - if( G_SayArgc() > 3 + skiparg ) { - //can only lock players in spectator - if ( teamnum != PTE_NONE ) - { - ADMP( "^3!putteam: ^7You can only lock a player into the spectators team\n" ); - return qfalse; - } - G_SayArgv( 3 + skiparg, secs, sizeof( secs ) ); - seconds = G_admin_parse_time( secs ); - useDuration = qtrue; - } - - if( vic->client->pers.teamSelection == teamnum && teamnum != PTE_NONE ) - { - ADMP( va( "^3!putteam: ^7%s ^7is already on the %s team\n", vic->client->pers.netname, teamdesc ) ); - return qfalse; - } - - if( useDuration == qtrue && seconds > 0 ) { - vic->client->pers.specExpires = level.time + ( seconds * 1000 ); - } - G_ChangeTeam( vic, teamnum ); - - AP( va( "print \"^3!putteam: ^7%s^7 put %s^7 on to the %s team%s\n\"", - ( ent ) ? G_admin_adminPrintName( ent ) : "console", - vic->client->pers.netname, teamdesc, - ( seconds ) ? va( " for %i seconds", seconds ) : "" ) ); - return qtrue; -} - qboolean G_admin_seen(gentity_t *ent, int skiparg ) { char name[ MAX_NAME_LENGTH ]; @@ -3362,11 +3278,8 @@ qboolean G_admin_seen(gentity_t *ent, int skiparg ) if( i == id || (search[ 0 ] && strstr( name, search ) ) ) { - if ( vic->client->sess.invisible == qfalse ) - { - ADMBP( va( "^3%4d ^7%s^7 is currently playing\n", i, vic->client->pers.netname ) ); - count++; - } + 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++ ) @@ -3384,11 +3297,8 @@ qboolean G_admin_seen(gentity_t *ent, int skiparg ) if( !Q_stricmp( vic->client->pers.guid, g_admin_admins[ i ]->guid ) && strstr( name, search ) ) { - if ( vic->client->sess.invisible == qfalse ) - { - ison = qtrue; - break; - } + ison = qtrue; + break; } } @@ -3396,7 +3306,7 @@ qboolean G_admin_seen(gentity_t *ent, int skiparg ) { if( id == -1 ) continue; - ADMBP( va( "^3%4d ^7%s^7 is currently playing\n", + ADMBP( va( "%4d %s^7 is currently playing\n", i + MAX_CLIENTS, g_admin_admins[ i ]->name ) ); } else @@ -3422,10 +3332,42 @@ qboolean G_admin_seen(gentity_t *ent, int skiparg ) return qtrue; } -void G_admin_seen_update( char *guid ) +void G_admin_karma_sync( void ) { + gclient_t *p; + int i, j; + + if( !g_karma.integer ) + return; + + for( i = 0; i < level.maxclients; i++ ) + { + p = &level.clients[ i ]; + + if( p->pers.connected != CON_CONNECTED && + p->pers.connected != CON_CONNECTING ) + continue; + if( p->pers.adminLevel < 1 ) + continue; + + for( j = 0; j < MAX_ADMIN_ADMINS && g_admin_admins[ j ]; j++ ) + { + if( !Q_stricmp( g_admin_admins[ j ]->guid, p->pers.guid ) ) + { + if( p->pers.karma ) + g_admin_admins[ j ]->karma = p->pers.karma; + break; + } + } + } +} + +void G_admin_seen_update( gclient_t *client, qboolean disconnect ) +{ + char *guid; int i; + guid = client->pers.guid; for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] ; i++ ) { if( !Q_stricmp( g_admin_admins[ i ]->guid, guid ) ) @@ -3433,11 +3375,198 @@ void G_admin_seen_update( char *guid ) qtime_t qt; g_admin_admins[ i ]->seen = trap_RealTime( &qt ); + if ( disconnect && client->pers.karma ) + g_admin_admins[ i ]->karma = client->pers.karma; return; } } } +typedef struct +{ + g_admin_admin_t *admin; + int id; +} +g_admin_sort_t; + +static int SortSeenTimes( const void *av, const void *bv) +{ + const g_admin_sort_t *a = av; + const g_admin_sort_t *b = bv; + + if( !a->admin || !b->admin ) + return 0; + + if( a->admin->seen > b->admin->seen ) + return 1; + if( a->admin->seen < b->admin->seen ) + return -1; + + return 0; +} + +qboolean G_admin_expire( gentity_t *ent, int skiparg ) +{ + g_admin_sort_t sort_list[ MAX_ADMIN_ADMINS ]; + char arg[ MAX_ADMIN_CMD_LEN ]; + qboolean confirm = qfalse; + qtime_t qt; + int t; + int count = 0; + int max = 5; + int i; + + if( g_adminExpireTime.integer < 1 ) + { + ADMP( "^3!expire: ^7expire is disabled\n" ); + return qfalse; + } + + if( G_SayArgc() > 1 + skiparg ) + { + G_SayArgv( skiparg + 1, arg, sizeof( arg ) ); + if( !Q_stricmp( arg, "confirm" ) ) + confirm = qtrue; + } + + t = trap_RealTime( &qt ); + + memset( sort_list, 0, sizeof( sort_list ) ); + for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] ; i++ ) + { + sort_list[ i ].admin = g_admin_admins[ i ]; + sort_list[ i ].id = MAX_CLIENTS + i; + count++; + } + + qsort( sort_list, count, sizeof( sort_list[ 0 ] ), SortSeenTimes ); + + ADMBP_begin(); + for( i = 0; i < count && max > 0 ; i++ ) + { + if( !sort_list[ i ].admin ) + continue; + if( sort_list[ i ].admin->level != 1 ) + continue; + if( t - sort_list[ i ].admin->seen > g_adminExpireTime.integer * 86400 ) + { + char sduration[ 32 ]; + + max--; + G_admin_duration( t - sort_list[ i ].admin->seen, sduration, sizeof( sduration ) ); + + if( confirm ) + { + trap_SendConsoleCommand( EXEC_APPEND, va( "!setlevel %d 0;", sort_list[ i ].id ) ); + } + else + { + ADMBP( va( " ^7%d %s^7 last seen %s%s\n", + sort_list[ i ].id, + sort_list[ i ].admin->name, + (sort_list[ i ].admin->seen ) ? sduration : "", + (sort_list[ i ].admin->seen ) ? " ago" : "time is unknown" ) ); + } + } + } + + ADMBP( va( "^3!expire: ^7%s %d level 1 admin(s) older than %d days\n", + ( confirm ) ? "expired" : "listed", + 5 - max, g_adminExpireTime.integer ) ); + if ( !confirm ) + ADMBP( "^3!expire: ^7to make this permanent use '^2!expire confirm^7'\n" ); + + ADMBP_end(); + return qtrue; +} + +qboolean G_admin_putteam( gentity_t *ent, int skiparg ) +{ + int pids[ MAX_CLIENTS ]; + char name[ MAX_NAME_LENGTH ], team[ 7 ], err[ MAX_STRING_CHARS ]; + gentity_t *vic; + pTeam_t teamnum = PTE_NONE; + char teamdesc[ 32 ] = {"spectators"}; + char secs[ 7 ]; + int seconds = 0; + qboolean useDuration = qfalse; + + G_SayArgv( 1 + skiparg, name, sizeof( name ) ); + G_SayArgv( 2 + skiparg, team, sizeof( team ) ); + if( G_SayArgc() < 3 + skiparg ) + { + ADMP( "^3!putteam: ^7usage: !putteam [name] [h|a|s] (duration)\n" ); + return qfalse; + } + + if( G_ClientNumbersFromString( name, pids ) != 1 ) + { + G_MatchOnePlayer( pids, err, sizeof( err ) ); + ADMP( va( "^3!putteam: ^7%s\n", err ) ); + return qfalse; + } + if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) + { + ADMP( "^3!putteam: ^7sorry, but your intended victim has a higher " + " admin level than you\n" ); + return qfalse; + } + vic = &g_entities[ pids[ 0 ] ]; + + if ( vic->client->sess.invisible == qtrue ) + { + ADMP( "^3!putteam: ^7invisible players cannot join a team\n" ); + return qfalse; + } + + switch( team[ 0 ] ) + { + case 'a': + teamnum = PTE_ALIENS; + Q_strncpyz( teamdesc, "aliens", sizeof( teamdesc ) ); + break; + case 'h': + teamnum = PTE_HUMANS; + Q_strncpyz( teamdesc, "humans", sizeof( teamdesc ) ); + break; + case 's': + teamnum = PTE_NONE; + break; + default: + ADMP( va( "^3!putteam: ^7unknown team %c\n", team[ 0 ] ) ); + return qfalse; + } + //duration code + if( G_SayArgc() > 3 + skiparg ) { + //can only lock players in spectator + if ( teamnum != PTE_NONE ) + { + ADMP( "^3!putteam: ^7You can only lock a player into the spectators team\n" ); + return qfalse; + } + G_SayArgv( 3 + skiparg, secs, sizeof( secs ) ); + seconds = G_admin_parse_time( secs ); + useDuration = qtrue; + } + + if( vic->client->pers.teamSelection == teamnum && teamnum != PTE_NONE ) + { + ADMP( va( "^3!putteam: ^7%s ^7is already on the %s team\n", vic->client->pers.netname, teamdesc ) ); + return qfalse; + } + + if( useDuration == qtrue && seconds > 0 ) { + vic->client->pers.specExpires = level.time + ( seconds * 1000 ); + } + G_ChangeTeam( vic, teamnum ); + + AP( va( "print \"^3!putteam: ^7%s^7 put %s^7 on to the %s team%s\n\"", + ( ent ) ? G_admin_adminPrintName( ent ) : "console", + vic->client->pers.netname, teamdesc, + ( seconds ) ? va( " for %i seconds", seconds ) : "" ) ); + return qtrue; +} + void G_admin_adminlog_cleanup( void ) { int i; @@ -3469,7 +3598,6 @@ void G_admin_adminlog_log( gentity_t *ent, char *command, char *args, int skipar !Q_stricmp( command, "listplayers" ) || !Q_stricmp( command, "namelog" ) || !Q_stricmp( command, "showbans" ) || - !Q_stricmp( command, "seen" ) || !Q_stricmp( command, "time" ) ) return; @@ -3722,6 +3850,261 @@ qboolean G_admin_adminlog( gentity_t *ent, int skiparg ) return qtrue; } +void G_admin_tklog_cleanup( void ) +{ + int i; + + for( i = 0; i < MAX_ADMIN_TKLOGS && g_admin_tklog[ i ]; i++ ) + { + G_Free( g_admin_tklog[ i ] ); + g_admin_tklog[ i ] = NULL; + } + + admin_tklog_index = 0; +} + +void G_admin_tklog_log( gentity_t *attacker, gentity_t *victim, int meansOfDeath ) +{ + g_admin_tklog_t *tklog; + int previous; + int count = 1; + + if( !attacker ) + return; + + previous = admin_tklog_index - 1; + if( previous < 0 ) + previous = MAX_ADMIN_TKLOGS - 1; + + if( g_admin_tklog[ previous ] ) + count = g_admin_tklog[ previous ]->id + 1; + + if( g_admin_tklog[ admin_tklog_index ] ) + tklog = g_admin_tklog[ admin_tklog_index ]; + else + tklog = G_Alloc( sizeof( g_admin_tklog_t ) ); + + memset( tklog, 0, sizeof( g_admin_tklog_t ) ); + tklog->id = count; + tklog->time = level.time - level.startTime; + Q_strncpyz( tklog->name, attacker->client->pers.netname, sizeof( tklog->name ) ); + + if( victim ) + { + Q_strncpyz( tklog->victim, victim->client->pers.netname, sizeof( tklog->victim ) ); + tklog->damage = victim->client->tkcredits[ attacker->s.number ]; + tklog->value = victim->client->ps.stats[ STAT_MAX_HEALTH ]; + } + else + { + Q_strncpyz( tklog->victim, "^3BLEEDING", sizeof( tklog->victim ) ); + tklog->damage = attacker->client->pers.statscounters.spreebleeds; + tklog->value = g_bleedingSpree.integer * 100; + } + + tklog->team = attacker->client->ps.stats[ STAT_PTEAM ]; + if( meansOfDeath == MOD_GRENADE ) + tklog->weapon = WP_GRENADE; + else if( tklog->team == PTE_HUMANS ) + tklog->weapon = attacker->s.weapon; + else + tklog->weapon = attacker->client->ps.stats[ STAT_PCLASS ]; + + g_admin_tklog[ admin_tklog_index ] = tklog; + admin_tklog_index++; + if( admin_tklog_index >= MAX_ADMIN_TKLOGS ) + admin_tklog_index = 0; +} + +qboolean G_admin_tklog( gentity_t *ent, int skiparg ) +{ + g_admin_tklog_t *results[ 10 ]; + int result_index = 0; + char *search_name = NULL; + int index; + int skip = 0; + int skipped = 0; + int checked = 0; + char n1[ MAX_NAME_LENGTH ]; + char fmt_name[ 16 ]; + char argbuf[ 32 ]; + char *weaponName; + int name_length = 10; + int max_id = 0; + int i; + qboolean match; + + memset( results, 0, sizeof( results ) ); + + index = admin_tklog_index; + for( i = 0; i < 10; i++ ) + { + int prev; + + prev = index - 1; + if( prev < 0 ) + prev = MAX_ADMIN_TKLOGS - 1; + if( !g_admin_tklog[ prev ] ) + break; + if( g_admin_tklog[ prev ]->id > max_id ) + max_id = g_admin_tklog[ prev ]->id; + index = prev; + } + + if( G_SayArgc() > 1 + skiparg ) + { + G_SayArgv( 1 + skiparg, argbuf, sizeof( argbuf ) ); + if( ( *argbuf >= '0' && *argbuf <= '9' ) || *argbuf == '-' ) + { + int id; + + id = atoi( argbuf ); + if( id < 0 ) + id += ( max_id - 9 ); + else if( id <= max_id - MAX_ADMIN_TKLOGS ) + id = max_id - MAX_ADMIN_TKLOGS + 1; + + if( id + 9 >= max_id ) + id = max_id - 9; + if( id < 1 ) + id = 1; + for( i = 0; i < MAX_ADMIN_TKLOGS; i++ ) + { + if( g_admin_tklog[ i ]->id == id ) + { + index = i; + break; + } + } + } + else + { + search_name = argbuf; + } + + if( G_SayArgc() > 2 + skiparg && ( search_name ) ) + { + char skipbuf[ 4 ]; + G_SayArgv( 2 + skiparg, skipbuf, sizeof( skipbuf ) ); + skip = atoi( skipbuf ); + } + } + + if( search_name ) + { + g_admin_tklog_t *result_swap[ 10 ]; + + memset( &result_swap, 0, sizeof( result_swap ) ); + + index = admin_tklog_index - 1; + if( index < 0 ) + index = MAX_ADMIN_TKLOGS - 1; + + while( g_admin_tklog[ index ] && + checked < MAX_ADMIN_TKLOGS && + result_index < 10 ) + { + match = qfalse; + + G_SanitiseString( g_admin_tklog[ index ]->name, n1, sizeof( n1 ) ); + if( strstr( n1, search_name ) ) + match = qtrue; + + if( match && skip > 0 ) + { + match = qfalse; + skip--; + skipped++; + } + if( match ) + { + result_swap[ result_index ] = g_admin_tklog[ index ]; + result_index++; + } + + checked++; + index--; + if( index < 0 ) + index = MAX_ADMIN_TKLOGS - 1; + } + // search runs backwards, turn it around + for( i = 0; i < result_index; i++ ) + results[ i ] = result_swap[ result_index - i - 1 ]; + } + else + { + while( g_admin_tklog[ index ] && result_index < 10 ) + { + results[ result_index ] = g_admin_tklog[ index ]; + result_index++; + index++; + if( index >= MAX_ADMIN_TKLOGS ) + index = 0; + } + } + + for( i = 0; results[ i ] && i < 10; i++ ) + { + int l; + + G_DecolorString( results[ i ]->name, n1 ); + l = strlen( n1 ); + if( l > name_length ) + name_length = l; + } + ADMBP_begin( ); + for( i = 0; results[ i ] && i < 10; i++ ) + { + int t; + + t = results[ i ]->time / 1000; + + G_DecolorString( results[ i ]->name, n1 ); + Com_sprintf( fmt_name, sizeof( fmt_name ), "%%%ds", + ( name_length + strlen( results[ i ]->name ) - strlen( n1 ) ) ); + Com_sprintf( n1, sizeof( n1 ), fmt_name, results[ i ]->name ); + + if( results[ i ]->team == PTE_HUMANS ) + weaponName = BG_FindNameForWeapon( results[ i ]->weapon ); + else + weaponName = BG_FindNameForClassNum( results[ i ]->weapon ); + + ADMBP( va( "^7%3d %3d:%02d %s^7 %3d / %3d %10s %s^7\n", + results[ i ]->id, + t / 60, t % 60, + n1, + results[ i ]->damage, + results[ i ]->value, + weaponName, + results[ i ]->victim ) ); + } + if( search_name ) + { + ADMBP( va( "^3!tklog:^7 Showing %d matches for '%s^7'.", + result_index, + argbuf ) ); + if( checked < MAX_ADMIN_TKLOGS && g_admin_tklog[ checked ] ) + ADMBP( va( " run '!tklog %s^7 %d' to see more", + argbuf, + skipped + result_index ) ); + ADMBP( "\n" ); + } + else if ( results[ 0 ] ) + { + ADMBP( va( "^3!tklog:^7 Showing %d - %d of %d.\n", + results[ 0 ]->id, + results[ result_index - 1 ]->id, + max_id ) ); + } + else + { + ADMBP( "^3!tklog:^7 log is empty.\n" ); + } + ADMBP_end( ); + + return qtrue; +} + qboolean G_admin_map( gentity_t *ent, int skiparg ) { char map[ MAX_QPATH ]; @@ -3947,6 +4330,9 @@ qboolean G_admin_maplog( gentity_t *ent, int skiparg ) case 'M': result = "^6admin changed map"; break; + case 'l': + result = "^2layout vote"; + break; case 'D': result = "^6admin loaded devmap"; break; @@ -3986,6 +4372,10 @@ qboolean G_admin_maplog( gentity_t *ent, int skiparg ) ptr = end; count++; } + + if( g_nextMap.string[ 0 ] ) + ADMBP( va( "^5NextMap override: %s\n", g_nextMap.string ) ); + ADMBP_end( ); return qtrue; @@ -4013,7 +4403,7 @@ qboolean G_admin_demo( gentity_t *ent, int skiparg ) { if( !ent ) { - ADMP( "!demo: console can not use demo.\n" ); + ADMP( "!demo: console can not use demo.\n"); return qfalse; } @@ -4415,29 +4805,34 @@ qboolean G_admin_listadmins( gentity_t *ent, int skiparg ) int start = 0; qboolean numeric = qtrue; int drawn = 0; - int minlevel = 1; + int minlevel = 0; + char *direction = "(all levels)"; if( G_SayArgc() == 3 + skiparg ) { G_SayArgv( 2 + skiparg, s, sizeof( s ) ); - if( s[ 0 ] >= '0' && s[ 0] <= '9' ) + if( ( s[ 0 ] >= '0' && s[ 0] <= '9' ) || s[ 0 ] == '-' ) { minlevel = atoi( s ); - if( minlevel < 1 ) - minlevel = 1; + if( minlevel > 0 ) + direction = "or greater"; + else if( minlevel < 0) + direction = "or lesser"; } } for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) { - if( g_admin_admins[ i ]->level >= minlevel ) + if( ( minlevel == 0 && g_admin_admins[ i ]->level != 0 ) || + ( minlevel > 0 && g_admin_admins[ i ]->level >= minlevel ) || + ( minlevel < 0 && g_admin_admins[ i ]->level <= minlevel ) ) found++; } if( !found ) { - if( minlevel > 1 ) + if( minlevel != 0 ) { - ADMP( va( "^3!listadmins: ^7no admins level %i or greater found\n", minlevel ) ); + ADMP( va( "^3!listadmins: ^7no admins level %i %s found\n", minlevel, direction ) ); } else { @@ -4476,27 +4871,28 @@ qboolean G_admin_listadmins( gentity_t *ent, int skiparg ) { if( drawn <= 20 ) { - ADMP( va( "^3!listadmins:^7 found %d admins level %i or greater matching '%s^7'\n", - drawn, minlevel, search ) ); + ADMP( va( "^3!listadmins:^7 found %d admins level %i %s matching '%s^7'\n", + drawn, minlevel, direction, search ) ); } else { - ADMP( va( "^3!listadmins:^7 found >20 admins level %i or greater matching '%s^7. Try a more narrow search.'\n", - minlevel, search ) ); + ADMP( va( "^3!listadmins:^7 found >20 admins level %i %s matching '%s^7. Try a more narrow search.'\n", + minlevel, direction, search ) ); } } else { ADMBP_begin(); - ADMBP( va( "^3!listadmins:^7 showing admins level %i or greater %d - %d of %d. ", + ADMBP( va( "^3!listadmins:^7 showing admins level %i %s %d - %d of %d. ", minlevel, + direction, ( found ) ? ( start + 1 ) : 0, ( ( start + MAX_ADMIN_LISTITEMS ) > found ) ? found : ( start + MAX_ADMIN_LISTITEMS ), found ) ); if( ( start + MAX_ADMIN_LISTITEMS ) < found ) { - if( minlevel > 1) + if( minlevel != 0 ) { ADMBP( va( "run '!listadmins %d %d' to see more", ( start + MAX_ADMIN_LISTITEMS + 1 ), minlevel ) ); @@ -4564,27 +4960,29 @@ qboolean G_admin_listplayers( gentity_t *ent, int skiparg ) char lname[ MAX_NAME_LENGTH ]; char lname2[ MAX_NAME_LENGTH ]; char guid_stub[ 9 ]; - char muted[ 2 ], denied[ 2 ], dbuilder[ 2 ], misc[ 2 ]; + char muted[ 2 ], denied[ 2 ], dbuilder[ 2 ], immune[ 2 ]; int l; char lname_fmt[ 5 ]; + char karma[ 8 ]; + //get amount of invisible players for( i = 0; i < level.maxclients; i++ ) { p = &level.clients[ i ]; if ( p->sess.invisible == qtrue ) invisiblePlayers++; } - + ADMBP_begin(); - ADMBP( va( "^3!listplayers^7: %i players connected:\n", + ADMBP( va( "^3!listplayers^7: %d players connected:\n", level.numConnectedClients - invisiblePlayers ) ); for( i = 0; i < level.maxclients; i++ ) { p = &level.clients[ i ]; - + // Ignore invisible players if ( p->sess.invisible == qtrue ) continue; - + Q_strncpyz( t, "S", sizeof( t ) ); Q_strncpyz( c, S_COLOR_YELLOW, sizeof( c ) ); if( p->pers.teamSelection == PTE_HUMANS ) @@ -4613,6 +5011,10 @@ qboolean G_admin_listplayers( gentity_t *ent, int skiparg ) guid_stub[ j ] = '\0'; muted[ 0 ] = '\0'; + if( G_admin_permission( &g_entities[ i ], ADMF_NO_VOTE ) ) + { + Q_strncpyz( muted, "V", sizeof( muted ) ); + } if( p->pers.muted ) { Q_strncpyz( muted, "M", sizeof( muted ) ); @@ -4641,15 +5043,16 @@ qboolean G_admin_listplayers( gentity_t *ent, int skiparg ) Q_strncpyz( dbuilder, "D", sizeof( dbuilder ) ); } } - - misc[ 0 ] = '\0'; + immune[ 0 ] = '\0'; if( G_admin_permission( &g_entities[ i ], ADMF_BAN_IMMUNITY ) ) { - // use Misc slot for Immunity player status - Q_strncpyz( misc, "I", sizeof( misc ) ); - } else if( p->pers.paused ) { - // use Misc slot for paused player status - Q_strncpyz( misc, "P", sizeof( misc ) ); + Q_strncpyz( immune, "I", sizeof( immune ) ); + } + + if( p->pers.paused ) + { + // use immunity slot for paused player status + Q_strncpyz( immune, "L", sizeof( immune ) ); } l = 0; @@ -4693,19 +5096,25 @@ qboolean G_admin_listplayers( gentity_t *ent, int skiparg ) } + if( g_karma.integer ) + Com_sprintf( karma, sizeof( karma ), " %4i", p->pers.karma / 1000 ); + else + karma[ 0 ] = '\0'; + if( G_admin_permission(ent, ADMF_SEESFULLLISTPLAYERS ) ) { - ADMBP( va( "%2i %s%s^7 %-2i %s^7 (*%s) ^1%1s%1s%1s%1s^7 %s^7 %s%s^7%s\n", + ADMBP( va( "%2i %s%s^7%s %-2i %s^7 (*%s) ^1%1s%1s%1s%1s^7 %s^7 %s%s^7%s\n", i, c, t, + karma, l, ( *lname ) ? lname2 : "", guid_stub, + immune, muted, dbuilder, denied, - misc, p->pers.netname, ( *n ) ? "(a.k.a. " : "", n, @@ -4714,10 +5123,11 @@ qboolean G_admin_listplayers( gentity_t *ent, int skiparg ) } else { - ADMBP( va( "%2i %s%s^7 ^1%1s%1s%1s^7 %s^7\n", + ADMBP( va( "%2i %s%s^7%s ^1%1s%1s%1s^7 %s^7\n", i, c, t, + karma, muted, dbuilder, denied, @@ -4870,52 +5280,6 @@ qboolean G_admin_listrotation( gentity_t *ent, int skiparg ) statusColor = 7; status = "vote"; } - } else if( !Q_stricmp( mapRotations.rotations[ i ].maps[ j ].name, "*RANDOM*" ) ) - { - char slotMap[ 64 ]; - int lineLen = 0; - int k; - - trap_Cvar_VariableStringBuffer( "mapname", slotMap, sizeof( slotMap ) ); - mapnames[ 0 ] = '\0'; - for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numConditions; k++ ) - { - char *thisMap; - int mc = 7; - - if( mapRotations.rotations[ i ].maps[ j ].conditions[ k ].lhs != MCV_SELECTEDRANDOM ) - continue; - - thisMap = mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest; - lineLen += strlen( thisMap ) + 1; - - if( currentMap == j && !Q_stricmp( thisMap, slotMap ) ) - mc = 3; - Q_strcat( mapnames, sizeof( mapnames ), va( "^7%s%s^%i%s", - ( k ) ? ", " : "", - ( lineLen > 50 ) ? "\n " : "", - mc, thisMap ) ); - if( lineLen > 50 ) - lineLen = strlen( thisMap ) + 2; - else - lineLen++; - } - - if( currentMap == j ) - { - statusColor = 3; - status = "current slot"; - } - else if( !k ) - { - statusColor = 1; - status = "empty Random"; - } - else - { - statusColor = 7; - status = "Random"; - } } else if ( currentMap == j ) { @@ -4980,16 +5344,13 @@ qboolean G_admin_showbans( gentity_t *ent, int skiparg ) char name_match[ MAX_NAME_LENGTH ] = {""}; int show_count = 0; qboolean subnetfilter = qfalse; + int line_color; + int dummy; t = trap_RealTime( NULL ); for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ ) { - if( g_admin_bans[ i ]->expires != 0 - && ( g_admin_bans[ i ]->expires - t ) < 1 ) - { - continue; - } found++; } @@ -5067,7 +5428,6 @@ qboolean G_admin_showbans( gentity_t *ent, int skiparg ) else { int mask = -1; - int dummy; int scanflen = 0; scanflen = sscanf( g_admin_bans[ i ]->ip, "%d.%d.%d.%d/%d", &dummy, &dummy, &dummy, &dummy, &mask ); if( scanflen == 5 && mask < 32 ) @@ -5114,7 +5474,6 @@ qboolean G_admin_showbans( gentity_t *ent, int skiparg ) else { int mask = -1; - int dummy; int scanflen = 0; scanflen = sscanf( g_admin_bans[ i ]->ip, "%d.%d.%d.%d/%d", &dummy, &dummy, &dummy, &dummy, &mask ); if( scanflen != 5 || mask >= 32 ) @@ -5141,42 +5500,54 @@ qboolean G_admin_showbans( gentity_t *ent, int skiparg ) made++; } - if( g_admin_bans[ i ]->expires != 0 - && ( g_admin_bans[ i ]->expires - t ) < 1 ) + if( g_admin_bans[ i ]->expires == 0 || + g_admin_bans[ i ]->expires > t ) { - Com_sprintf( duration, sizeof( duration ), "^1*EXPIRED*^7" ); - } else { secs = ( g_admin_bans[ i ]->expires - t ); G_admin_duration( secs, duration, sizeof( duration ) ); + if( sscanf( g_admin_bans[ i ]->ip, "%d.%d.%d.%d/%d", &dummy, &dummy, &dummy, &dummy, &dummy ) != 4 ) + line_color = 1; + else if( g_admin_bans[ i ]->suspend > t ) + line_color = 5; + else + line_color = 7; + } + else + { + Q_strncpyz( duration, "expired", sizeof( duration ) ); + line_color = 5; } suspended[ 0 ] = '\0'; if( g_admin_bans[ i ]->suspend > t ) { G_admin_duration( g_admin_bans[ i ]->suspend - t, sduration, sizeof( sduration ) ); - Com_sprintf( suspended, sizeof( suspended ), "^3*SUSPENDED*^7 for %s^7", + Com_sprintf( suspended, sizeof( suspended ), "^5 | - SUSPENDED for %s\n", sduration ); } G_DecolorString( g_admin_bans[ i ]->name, n1 ); - Com_sprintf( name_fmt, sizeof( name_fmt ), "%%%is", + Com_sprintf( name_fmt, sizeof( name_fmt ), "%%-%is", ( max_name + strlen( g_admin_bans[ i ]->name ) - strlen( n1 ) ) ); Com_sprintf( n1, sizeof( n1 ), name_fmt, g_admin_bans[ i ]->name ); G_DecolorString( g_admin_bans[ i ]->banner, n2 ); - Com_sprintf( banner_fmt, sizeof( banner_fmt ), "%%%is", + Com_sprintf( banner_fmt, sizeof( banner_fmt ), "%%-%is", ( max_banner + strlen( g_admin_bans[ i ]->banner ) - strlen( n2 ) ) ); - Com_sprintf( n2, sizeof( n2 ), banner_fmt, g_admin_bans[ i ]->banner ); + Com_sprintf( n2, sizeof( n2 ), banner_fmt, g_admin_bans[ i ]->banner ); bannerslevel = g_admin_bans[ i ]->bannerlevel; - ADMBP( va( "%4i %s^7 %-15s %-8s %-10s\n | %-15s^7 Level:%2i\n | %s\n \\__ %s\n", + ADMBP( va( "^%i%4i ^7%s^%i %-15s %-8s ^7%s^7 %2i^%i %-10s\n%s^5 \\__ %s\n", + line_color, ( i + 1 ), n1, + line_color, g_admin_bans[ i ]->ip, date, - duration, n2, bannerslevel, + line_color, + duration, suspended, g_admin_bans[ i ]->reason ) ); @@ -5209,8 +5580,9 @@ qboolean G_admin_showbans( gentity_t *ent, int skiparg ) if( ( start + MAX_ADMIN_SHOWBANS ) < found ) { - ADMBP( va( "run !showbans %d %s to see more", + ADMBP( va( "run !showbans %d%s%s to see more", ( start + MAX_ADMIN_SHOWBANS + 1 ), + (filter[0]) ? " " : "", (filter[0]) ? filter : "" ) ); } ADMBP( "\n" ); @@ -5221,8 +5593,7 @@ qboolean G_admin_showbans( gentity_t *ent, int skiparg ) qboolean G_admin_help( gentity_t *ent, int skiparg ) { int i; - int count = 0; - int commandsPerLine = 6; + char additional[ MAX_STRING_CHARS ] = ""; if( G_SayArgc() < 2 + skiparg ) { @@ -5259,102 +5630,39 @@ qboolean G_admin_help( gentity_t *ent, int skiparg ) j = 0; } } - + + if( ent && g_markDeconstruct.integer == 2 ) + strcat( additional, " /mark" ); + if( ent ) + strcat( additional, " /builder /say_area" ); + if( g_publicSayadmins.integer || G_admin_permission( ent, ADMF_ADMINCHAT ) ) + strcat( additional, " /a /say_admins" ); + if( g_privateMessages.integer ) + strcat( additional, " /m" ); + if( ent && g_actionPrefix.string[0] ) + strcat( additional, " /me /mt /me_team" ); + if( ent && g_myStats.integer ) + strcat( additional, " /mystats" ); + if( ent && g_teamStatus.integer ) + strcat( additional, " /teamstatus" ); + if( ent && ent->client ) + { + if( ent->client->pers.designatedBuilder ) + { + strcat( additional, " /protect /resign" ); + } + } + if( ent && g_allowShare.integer ) + strcat( additional, " /share /donate" ); + if( count ) ADMBP( "\n" ); ADMBP( va( "^3!help: ^7%i available commands\n", count ) ); ADMBP( "run !help [^3command^7] for help with a specific command.\n" ); - ADMBP( "The following non-standard /commands may also be available to you: \n" ); - count = 1; - - if( ent && g_AllStats.integer ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "allstats" ) ); - count++; - } - if ( ent ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "builder" ) ); - count++; - } - if( ent && g_allowVote.integer && G_admin_permission( ent, ADMF_NO_VOTE ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "callvote" ) ); - count++; - } - if( ent && g_allowVote.integer && G_admin_permission( ent, ADMF_NO_VOTE ) && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "callteamvote" ) ); - count++; - } - if( ent && g_allowShare.integer && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "donate" ) ); - count++; - } - if( g_privateMessages.integer ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "m" ) ); - count++; - } - if( ent && g_markDeconstruct.integer == 2 && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "mark" ) ); - count++; - } - if( ent && g_actionPrefix.string[0] ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "me" ) ); - count++; - } - if( ent && g_actionPrefix.string[0] ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "me_team" ) ); - count++; - } - if( ent && g_actionPrefix.string[0] && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "mt" ) ); - count++; - } - if( ent && g_myStats.integer && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "mystats" ) ); - count++; - } - if( ent->client->pers.designatedBuilder && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "protect" ) ); - count++; - } - if( ent->client->pers.designatedBuilder && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "resign" ) ); - count++; - } - if( g_publicSayadmins.integer || G_admin_permission( ent, ADMF_ADMINCHAT ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "say_admins" ) ); - count++; - } - if( ent && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "say_area" ) ); - count++; - } - if( ent && g_allowShare.integer && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "share" ) ); - count++; - } - if( ent && g_teamStatus.integer && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { - if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); - ADMBP( va( "^5/%-12s", "teamstatus" ) ); - count++; - } - ADMBP( "\n" ); + ADMBP( va( "\nThe following non-standard /commands may also be available to you: \n^3%s\n", + additional ) ); ADMBP_end(); - + return qtrue; } else @@ -5490,6 +5798,10 @@ qboolean G_admin_cancelvote( gentity_t *ent, int skiparg ) ADMP( "^3!cancelvote^7: no vote in progress\n" ); return qfalse; } + if( !Q_strncmp( level.voteDisplayString, "Extend", 6 ) && + level.extend_vote_count > 0 ) + level.extend_vote_count--; + level.voteNo = level.numConnectedClients; level.voteYes = 0; CheckVote( ); @@ -5679,11 +5991,81 @@ qboolean G_admin_pause( gentity_t *ent, int skiparg ) vic->client->pers.netname, ( ent ) ? ent->client->pers.netname : "console" ) ); } - ClientUserinfoChanged( pids[ i ], qfalse ); } return qtrue; } +qboolean G_admin_practice( gentity_t *ent, int skiparg ) +{ + char clantag[ MAX_NAME_LENGTH ]; + char mapsarg[ 8 ]; + int maps; + + if( G_SayArgc() < 2 + skiparg ) + { + if( g_practiceCount.integer ) + { + AP( va( "print \"^3practice:^7 practice mode is in effect for the next %d maps\n\"", + g_practiceCount.integer ) ); + ADMP( va( "^3!practice: ^7practice mode set to %s^7 for next %d maps\n", + g_practiceText.string, g_practiceCount.integer ) ); + } + else + { + ADMP( "^3!practice: ^7practice mode is off\n" ); + } + return qfalse; + } + + G_SayArgv( 1 + skiparg, clantag, sizeof( clantag ) ); + + if( G_SayArgc() < 3 + skiparg ) + { + if( !Q_stricmp( clantag, "off" ) ) + { + if( g_practiceCount.integer ) + { + trap_Cvar_Set( "g_practiceCount", "0" ); + AP( va ("print \"^3!practice: ^7 practice mode turned off by %s^7\n\"", + ( ent ) ? ent->client->pers.netname : "console" ) ); + ADMP( "^3!practice: ^7practice mode set to off\n" ); + return qtrue; + } + else + { + ADMP( "^3!practice: ^7practice mode already off\n" ); + } + } + else + { + ADMP( "^3!practice: ^7usage: practice [clan tag] [map count]\n" ); + } + return qfalse; + } + G_SayArgv( 2 + skiparg, mapsarg, sizeof( mapsarg ) ); + maps = atoi( mapsarg ); + + if( !clantag[ 0 ] ) + { + ADMP( "^3!practice: ^7no clan tag specified\n" ); + return qfalse; + } + if( maps < 1 || maps > 8 ) + { + ADMP( "^3!practice: ^7map count must be between 1 and 8\n" ); + return qfalse; + } + + trap_Cvar_Set( "g_practiceText", clantag ); + trap_Cvar_Set( "g_practiceCount", va( "%d", maps ) ); + + AP( va( "print \"^3practice:^7 %s^7 has activated practice mode for the next %d maps\n\"", + ( ent ) ? ent->client->pers.netname : "console", + maps ) ); + + return qtrue; +} + qboolean G_admin_spec999( gentity_t *ent, int skiparg ) { int i; @@ -5711,13 +6093,47 @@ qboolean G_admin_spec999( gentity_t *ent, int skiparg ) qboolean G_admin_register(gentity_t *ent, int skiparg ){ int level = 0; + int max; + char buffer [ 64 ]; if( !ent ) return qtrue; level = G_admin_level(ent); - if( level == 0 ) - level = 1; + if( G_SayArgc() > 1 + skiparg ) + { + G_SayArgv( 1 + skiparg, buffer, sizeof( buffer ) ); + max = atoi( buffer ); + + if( G_SayArgc() > 2 + skiparg ) + { + G_SayArgv( 2 + skiparg, buffer, sizeof( buffer ) ); + if( g_adminRegisterAdminPass.string[ 0 ] && Q_stricmp( g_adminRegisterAdminPass.string, "none" ) && + Q_stricmp( g_adminRegisterAdminPass.string, buffer) ) + { + ADMP( "Invalid password.\n" ); + return qfalse; + } + if (max > g_adminRegisterAdminLevel.integer) + max = g_adminRegisterAdminLevel.integer; + if( level <= g_adminRegisterAdminLevel.integer ) + level = max; + } + else + { + if( max > g_adminRegisterLevel.integer ) + max = g_adminRegisterLevel.integer; + if( max >= 0 && level >= 0 && level <= g_adminRegisterLevel.integer ) + level = max; + } + } + else + { + max = 1; + } + + if( level >= 0 && level < max ) + level = max; if( !Q_stricmp( ent->client->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) ) { @@ -5886,140 +6302,6 @@ qboolean G_admin_restart( gentity_t *ent, int skiparg ) return qtrue; } -qboolean G_admin_nobuild( gentity_t *ent, int skiparg ) -{ - char buffer[ MAX_STRING_CHARS ]; - int i, tmp; - - if( G_SayArgc() < 2 + skiparg ) - { - ADMP( "^3!nobuild: ^7usage: !nobuild (^5enable / disable / log / remove / save^7)\n" ); - return qfalse; - } - - G_SayArgv( 1 + skiparg, buffer, sizeof( buffer ) ); - - if( !Q_stricmp( buffer, "enable" ) || !Q_stricmp( buffer, "Enable" ) ) - { - if( G_SayArgc() < 4 + skiparg ) - { - ADMP( "^3!nobuild: ^7usage: !nobuild enable (^5area^7) (^5height^7)\n" ); - return qfalse; - } - - if( !level.noBuilding ) - { - - level.noBuilding = qtrue; - - // Grab and set the area - G_SayArgv( 2 + skiparg, buffer, sizeof( buffer ) ); - tmp = atoi( buffer ); - level.nbArea = tmp; - - // Grab and set the height - G_SayArgv( 3 + skiparg, buffer, sizeof( buffer ) ); - tmp = atoi( buffer ); - level.nbHeight = tmp; - - ADMP( "^3!nobuild: ^7nobuild is now enabled, please place a buildable to spawn a marker\n" ); - return qtrue; - } - else - { - ADMP( "^3!nobuild: ^7nobuild is already enabled. type !nobuild disable to disable nobuild mode.\n" ); - return qfalse; - } - } - - if( !Q_stricmp( buffer, "disable" ) || !Q_stricmp( buffer, "Disable" ) ) - { - if( level.noBuilding ) - { - level.noBuilding = qfalse; - level.nbArea = 0.0f; - level.nbHeight = 0.0f; - ADMP( "^3!nobuild: ^7nobuild is now disabled\n" ); - return qtrue; - } - else - { - ADMP( "^3!nobuild: ^7nobuild is disabled. type !nobuild enable (^5area^7) (^5height^7) to enable nobuild mode.\n" ); - return qfalse; - } - } - - if( !Q_stricmp( buffer, "log" ) || !Q_stricmp( buffer, "Log" ) ) - { - ADMBP_begin(); - - tmp = 0; - for( i = 0; i < MAX_GENTITIES; i++ ) - { - if( level.nbMarkers[ i ].Marker == NULL ) - continue; - - // This will display a start at 1, and not 0. This is to stop confusion by server ops - ADMBP( va( "^7#%i at origin (^5%f %f %f^7)^7\n", i + 1, level.nbMarkers[ i ].Origin[0], level.nbMarkers[ i ].Origin[1], level.nbMarkers[ i ].Origin[2] ) ); - tmp++; - } - ADMBP( va( "^3!nobuild:^7 displaying %i marker(s)\n", tmp ) ); - - ADMBP_end(); - return qtrue; - } - - if( !Q_stricmp( buffer, "remove" ) || !Q_stricmp( buffer, "Remove" ) ) - { - if( G_SayArgc() < 3 + skiparg ) - { - ADMP( "^3!nobuild: ^7usage: !nobuild remove (^5marker #^7)\n" ); - return qfalse; - } - - G_SayArgv( 2 + skiparg, buffer, sizeof( buffer ) ); - tmp = atoi( buffer ) - 1; - // ^ that was to work with the display number... - - if( level.nbMarkers[ tmp ].Marker == NULL ) - { - ADMP( "^3!nobuild: ^7that is not a valid marker number\n" ); - return qfalse; - } - // Donno why im doing this, but lets clear this... - level.nbMarkers[ tmp ].Marker->noBuild.isNB = qfalse; - level.nbMarkers[ tmp ].Marker->noBuild.Area = 0.0f; - level.nbMarkers[ tmp ].Marker->noBuild.Height = 0.0f; - - // Free the entitiy and null it out... - G_FreeEntity( level.nbMarkers[ tmp ].Marker ); - level.nbMarkers[ tmp ].Marker = NULL; - - // That is to work with the display number... - ADMP( va( "^3!nobuild:^7 marker %i has been removed\n", tmp + 1 ) ); - return qtrue; - } - - if( !Q_stricmp( buffer, "save" ) || !Q_stricmp( buffer, "Save" ) ) - { - int i, tmp; - - G_NobuildSave( ); - - tmp = 0; - for( i = 0; i < MAX_GENTITIES; i++ ) - { - if( level.nbMarkers[ i ].Marker == NULL ) - continue; - - tmp++; - } - ADMP( va( "^3!nobuild:^7 %i nobuild markers have been saved\n", tmp ) ); - return qtrue; - } - return qfalse; -} - qboolean G_admin_nextmap( gentity_t *ent, int skiparg ) { AP( va( "print \"^3!nextmap: ^7%s^7 decided to load the next map\n\"", @@ -6244,6 +6526,441 @@ qboolean G_admin_designate( gentity_t *ent, int skiparg ) return qtrue; } +static int SortFlags( const void *pa, const void *pb ) +{ + char *a = (char *)pa; + char *b = (char *)pb; + + if( *a == '-' || *a == '+' ) + a++; + if( *b == '-' || *b == '+' ) + b++; + return strcmp(a, b); +} + +#define MAX_USER_FLAGS 200 +const char *G_admin_user_flag( char *oldflags, char *flag, qboolean add, qboolean clear, + char *newflags, int size ) +{ + char *token, *token_p; + char *key; + char head_flags[ MAX_USER_FLAGS ][ MAX_ADMIN_FLAG_LEN ]; + char tail_flags[ MAX_USER_FLAGS ][ MAX_ADMIN_FLAG_LEN ]; + char allflag[ MAX_ADMIN_FLAG_LEN ]; + char newflag[ MAX_ADMIN_FLAG_LEN ]; + int head_count = 0; + int tail_count = 0; + qboolean flagset = qfalse; + int i; + + if( !flag[ 0 ] ) + { + return "invalid admin flag"; + } + + allflag[ 0 ] = '\0'; + token_p = oldflags; + while( *( token = COM_Parse( &token_p ) ) ) + { + key = token; + if( *key == '-' || *key == '+' ) + key++; + + if( !strcmp( key, flag ) ) + { + if( flagset ) + continue; + flagset = qtrue; + if( clear ) + { + // clearing ALLFlAGS will result in clearing any following flags + if( !strcmp( key, ADMF_ALLFLAGS ) ) + break; + else + continue; + } + Com_sprintf( newflag, sizeof( newflag ), "%s%s", + ( add ) ? "+" : "-", key ); + } + else + { + Q_strncpyz( newflag, token, sizeof( newflag ) ); + } + + if( !strcmp( key, ADMF_ALLFLAGS ) ) + { + if( !allflag[ 0 ] ) + Q_strncpyz( allflag, newflag, sizeof( allflag ) ); + continue; + } + + if( !allflag[ 0 ] ) + { + if( head_count < MAX_USER_FLAGS ) + { + Q_strncpyz( head_flags[ head_count ], newflag, + sizeof( head_flags[ head_count ] ) ); + head_count++; + } + } + else + { + if( tail_count < MAX_USER_FLAGS ) + { + Q_strncpyz( tail_flags[ tail_count ], newflag, + sizeof( tail_flags[ tail_count ] ) ); + tail_count++; + } + } + } + + if( !flagset && !clear ) + { + if( !strcmp( flag, ADMF_ALLFLAGS ) ) + { + Com_sprintf( allflag, sizeof( allflag ), "%s%s", + ( add ) ? "" : "-", ADMF_ALLFLAGS ); + } + else if( !allflag[ 0 ] ) + { + if( head_count < MAX_USER_FLAGS ) + { + Com_sprintf( head_flags[ head_count ], sizeof( head_flags[ head_count ] ), + "%s%s", ( add ) ? "" : "-", flag ); + head_count++; + } + } + else + { + if( tail_count < MAX_USER_FLAGS ) + { + Com_sprintf( tail_flags[ tail_count ], sizeof( tail_flags[ tail_count ] ), + "%s%s", ( add ) ? "+" : "-", flag ); + tail_count++; + } + } + } + + qsort( head_flags, head_count, sizeof( head_flags[ 0 ] ), SortFlags ); + qsort( tail_flags, tail_count, sizeof( tail_flags[ 0 ] ), SortFlags ); + + // rebuild flags + newflags[ 0 ] = '\0'; + for( i = 0; i < head_count; i++ ) + { + Q_strcat( newflags, size, + va( "%s%s", ( i ) ? " ": "", head_flags[ i ] ) ); + } + if( allflag[ 0 ] ) + { + Q_strcat( newflags, size, + va( "%s%s", ( newflags[ 0 ] ) ? " ": "", allflag ) ); + + for( i = 0; i < tail_count; i++ ) + { + Q_strcat( newflags, size, + va( " %s", tail_flags[ i ] ) ); + } + } + + return NULL; +} + + +typedef struct { + char *flag; + char *description; +} AdminFlagListEntry_t; +static AdminFlagListEntry_t adminFlagList[] = +{ + { ADMF_ACTIVITY, "inactivity rules do not apply" }, + { ADMF_ADMINCHAT, "can see and use admin chat" }, + { ADMF_ALLFLAGS, "has all flags and can use any command" }, + { ADMF_BAN_IMMUNITY, "immune from IP bans" }, + { ADMF_CAN_PERM_BAN, "can permanently ban players" }, + { ADMF_DBUILDER, "permanent designated builder" }, + { ADMF_FORCETEAMCHANGE, "team balance rules do not apply" }, + { ADMF_INCOGNITO, "does not show as admin in !listplayers" }, + { ADMF_IMMUNITY, "cannot be vote kicked or muted" }, + { ADMF_IMMUTABLE, "admin commands cannot be used on them" }, + { ADMF_NOCENSORFLOOD, "no flood protection" }, + { ADMF_NO_VOTE_LIMIT, "vote limitations do not apply" }, + { ADMF_SEESFULLLISTPLAYERS, "sees all info in !listplayers" }, + { ADMF_SPEC_ALLCHAT, "can see team chat as spectator" }, + { ADMF_ADMINSTEALTH, "uses admin stealth" }, + { ADMF_TEAMCHANGEFREE, "keeps credits on team switch" }, + { ADMF_TEAMCHAT_CMD, "can run commands from team chat" }, + { ADMF_UNACCOUNTABLE, "does not need to specify reason for kick/ban" }, + { ADMF_NO_CHAT, "can not talk" }, + { ADMF_NO_VOTE, "can not call votes" } +}; +static int adminNumFlags= sizeof( adminFlagList ) / sizeof( adminFlagList[ 0 ] ); + +#define MAX_LISTCOMMANDS 128 +qboolean G_admin_flaglist( gentity_t *ent, int skiparg ) +{ + qboolean shown[ MAX_LISTCOMMANDS ]; + int i, j; + int count = 0; + + ADMBP_begin(); + + ADMBP( "^3Ability flags:\n" ); + + for( i = 0; i < adminNumFlags; i++ ) + { + ADMBP( va( " %s%-20s ^7%s\n", + ( adminFlagList[ i ].flag[ 0 ] != '.' ) ? "^5" : "^1", + adminFlagList[ i ].flag, + adminFlagList[ i ].description ) ); + } + + ADMBP( "^3Command flags:\n" ); + + memset( shown, 0, sizeof( shown ) ); + for( i = 0; i < adminNumCmds; i++ ) + { + if( i < MAX_LISTCOMMANDS && shown[ i ] ) + continue; + ADMBP( va( " ^5%-20s^7", g_admin_cmds[ i ].flag ) ); + for( j = i; j < adminNumCmds; j++ ) + { + if( !strcmp( g_admin_cmds[ j ].flag, g_admin_cmds[ i ].flag ) ) + { + ADMBP( va( " %s", g_admin_cmds[ j ].keyword ) ); + if( j < MAX_LISTCOMMANDS ) + shown[ j ] = qtrue; + } + } + ADMBP( "\n" ); + count++; + } + + ADMBP( va( "^3!flaglist: ^7listed %d abilities and %d command flags\n", + adminNumFlags, count ) ); + + ADMBP_end(); + + return qtrue; +} + +qboolean G_admin_flag( gentity_t *ent, int skiparg ) +{ + char command[ MAX_ADMIN_CMD_LEN ], *cmd; + char name[ MAX_NAME_LENGTH ]; + char flagbuf[ MAX_ADMIN_FLAG_LEN ]; + char *flag; + int id; + char adminname[ MAX_NAME_LENGTH ] = {""}; + const char *result; + qboolean add = qtrue; + qboolean clear = qfalse; + int admin_level = -1; + int i, level; + + 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; + } + + G_SayArgv( 1 + skiparg, name, sizeof( name ) ); + if( name[ 0 ] == '*' ) + { + if( ent ) + { + ADMP( va( "^3!%s: only console can change admin level flags\n", cmd ) ); + return qfalse; + } + id = atoi( name + 1 ); + for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) + { + if( g_admin_levels[ i ]->level == id ) + { + admin_level = i; + break; + } + } + if( admin_level < 0 ) + { + ADMP( va( "^3!%s: admin level %d does not exist\n", cmd, id ) ); + return qfalse; + } + Com_sprintf( adminname, sizeof( adminname ), "admin level %d", id ); + } + else + { + id = G_admin_find_admin_slot( ent, name, cmd, 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 than you\n", cmd ) ); + return qfalse; + } + } + + if( G_SayArgc() < 3 + skiparg ) + { + flag = ""; + level = 0; + if( admin_level < 0 ) + { + for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) + { + if( g_admin_admins[ id ]->level == g_admin_levels[ i ]->level ) + { + flag = g_admin_levels[ i ]->flags; + level = g_admin_admins[ id ]->level; + break; + } + } + ADMP( va( "^3%s:^7 flags for %s^7 are '^3%s^7'\n", + cmd, adminname, g_admin_admins[ id ]->flags) ); + } + else + { + flag = g_admin_levels[ admin_level ]->flags; + level = g_admin_levels[ admin_level ]->level; + } + ADMP( va( "^3%s:^7 level %d flags are '%s'\n", + cmd, level, flag ) ); + + return qtrue; + } + + G_SayArgv( 2 + skiparg, flagbuf, sizeof( flagbuf ) ); + flag = flagbuf; + if( flag[ 0 ] == '-' || flag[ 0 ] == '+' ) + { + add = ( flag[ 0 ] == '+' ); + 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( flag[ 0 ] != '.' && !G_admin_permission( ent, flag ) ) + { + 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; + } + + if( admin_level < 0 ) + { + result = G_admin_user_flag( g_admin_admins[ id ]->flags, flag, add, clear, + g_admin_admins[ id ]->flags, sizeof( g_admin_admins[ id ]->flags ) ); + } + else + { + result = G_admin_user_flag( g_admin_levels[ admin_level ]->flags, flag, add, clear, + g_admin_levels[ admin_level ]->flags, + sizeof( g_admin_levels[ admin_level ]->flags ) ); + } + if( result ) + { + ADMP( va( "^3!flag: ^7an error occured setting flag '^3%s^7', %s\n", + flag, result ) ); + return qfalse; + } + + if( !Q_stricmp( cmd, "flag" ) ) + { + G_AdminsPrintf( "^3!%s: ^7%s^7 was %s admin flag '%s' by %s\n", + cmd, adminname, + ( add ) ? "given" : "denied", + flag, + ( ent ) ? ent->client->pers.netname : "console" ); + } + else + { + G_AdminsPrintf( "^3!%s: ^7admin flag '%s' for %s^7 cleared by %s\n", + cmd, flag, 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_admin_slot( ent, cmd, "immunity", adminname, sizeof( adminname ) ); + if( id < 0 ) + return qfalse; + + if( *action != '+' && *action != '-' ) + { + ADMP( va( "^3immunity:^7 ban immunity for %s^7 is %s, prepend + or - to the slot number to change.\n", + adminname, + ( G_admin_permission_guid( g_admin_admins[ id ]->guid, ADMF_BAN_IMMUNITY ) ) ? "on" : "off" ) ); + return qfalse; + } + + result = G_admin_user_flag( g_admin_admins[ id ]->flags, + ADMF_BAN_IMMUNITY, qtrue, ( *action != '+' ), + g_admin_admins[ id ]->flags, sizeof( g_admin_admins[ id ]->flags ) ); + if( result ) + { + ADMP( va( "^3!immunity: ^7an error occured setting flag, %s\n", result ) ); + return qfalse; + } + + if( *action == '+' ) + { + AP( va( + "print \"^3!immunity: ^7%s^7 was given ban immunity by %s\n\"", + adminname, ( ent ) ? ent->client->pers.netname : "console" ) ); + } + else + { + AP( va( + "print \"^3!immunity: ^7ban immunity for %s^7 removed by %s\n\"", + adminname, ( ent ) ? ent->client->pers.netname : "console" ) ); + } + + if( !g_admin.string[ 0 ] ) + ADMP( "^3!immunity: ^7WARNING g_admin not set, not saving admin record " + "to a file\n" ); + else + admin_writeconfig(); + + return qtrue; +} + //!Warn by Gate (Daniel Evans) qboolean G_admin_warn( gentity_t *ent, int skiparg ) {//mostly copy and paste with the proper lines altered from !mute and !kick @@ -6284,9 +7001,47 @@ qboolean G_admin_warn( gentity_t *ent, int skiparg ) ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );//console announcement return qtrue; } + +qboolean G_admin_bring( gentity_t *ent, int skiparg ) +{ + int pids[ MAX_CLIENTS ]; + char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; + gentity_t *vic; + + if( !ent ) + { + ADMP( "^3!bring: ^7console cannot use this command\n" ); + return qfalse; + } + if( ent->client->pers.teamSelection != PTE_NONE ) + { + ADMP( "^3!bring: ^7you can only use this command from spectator\n" ); + return qfalse; + } + if( G_SayArgc() < 2 + skiparg ) + { + ADMP( "^3!bring: ^7usage: !bring [name|slot#]\n" ); + return qfalse; + } + + G_SayArgv( 1 + skiparg, name, sizeof( name ) ); + if( G_ClientNumbersFromString( name, pids ) != 1 ) + { + G_MatchOnePlayer( pids, err, sizeof( err ) ); + ADMP( va( "^3!bring: ^7%s\n", err ) ); + return qfalse; + } + + vic = &g_entities[ pids[ 0 ] ]; + VectorCopy( vic->client->ps.origin, ent->client->ps.origin ); + + return qtrue; +} qboolean G_admin_putmespec( gentity_t *ent, int skiparg ) { + int cs_offset; + if( !ent ) { ADMP( "!specme: sorry, but console isn't allowed on the spectators team\n"); @@ -6302,6 +7057,15 @@ qboolean G_admin_putmespec( gentity_t *ent, int skiparg ) if(ent->client->pers.teamSelection == PTE_NONE) return qfalse; + if( ent->client->pers.teamSelection == PTE_ALIENS ) + cs_offset = 1; + else + cs_offset = 0; + if( level.teamVoteTime[ cs_offset ] ) + { + trap_SendServerCommand( ent-g_entities, "print \"Can not leave team during a team vote\n\"" ); + return qfalse; + } //guard against build timer exploit if( ent->client->pers.teamSelection != PTE_NONE && ent->client->sess.sessionTeam != TEAM_SPECTATOR && ( ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 || @@ -6315,10 +7079,119 @@ qboolean G_admin_putmespec( gentity_t *ent, int skiparg ) } G_ChangeTeam( ent, PTE_NONE ); + + // check for silent '!specme s' - requires !kick permission + if( G_SayArgc() > 1 + skiparg ) + { + char arg[ 2 ]; + + G_SayArgv( 1 + skiparg, arg, sizeof( arg ) ); + if( ( arg[ 0 ] == 's' || arg[ 0 ] == 'S' ) && G_admin_permission( ent, "kick" ) ) + { + ADMP("^3!specme: ^7 You have silently joined the spectators\n"); + return qtrue; + } + } + AP( va("print \"^3!specme: ^7%s^7 decided to join the spectators\n\"", ent->client->pers.netname ) ); return qtrue; } +qboolean G_admin_outlaw( gentity_t *ent, int skiparg ) +{ + int pids[ MAX_CLIENTS ]; + char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; + char valuebuf[ 8 ]; + gentity_t *vic; + int points; + qboolean activate = qtrue; + + if( !g_bleedingSpree.integer ) + { + ADMP( "^3!outlaw: ^7bleeding sprees are disabled\n" ); + return qfalse; + } + + if( G_SayArgc() < 2 + skiparg ) + { + ADMP( "^3!outlaw: ^7usage: !outlaw [name|slot#] (value)\n" ); + return qfalse; + } + G_SayArgv( 1 + skiparg, name, sizeof( name ) ); + if( G_ClientNumbersFromString( name, pids ) != 1 ) + { + G_MatchOnePlayer( pids, err, sizeof( err ) ); + ADMP( va( "^3!mute: ^7%s\n", err ) ); + return qfalse; + } + vic = &g_entities[ pids[ 0 ] ]; + + if( G_SayArgc() > 2 + skiparg ) + { + G_SayArgv( 2 + skiparg, valuebuf, sizeof( valuebuf ) ); + if( valuebuf[ 0 ] == '?' ) + { + ADMP( va( "^3!outlaw: ^7%s^7's bleeder value is %d\n", + vic->client->pers.netname, + vic->client->pers.statscounters.spreebleeds ) ); + return qtrue; + } + if( valuebuf[ 0 ] == '+' || valuebuf[ 0 ] == '-' ) + { + activate = qfalse; + } + points = atoi( valuebuf ); + } + else + { + points = ( g_bleedingSpree.integer + 1 ) * 100; + } + + if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) + { + ADMP( "^3!outlaw: ^7sorry, but your intended victim has a higher admin" + " level than you\n" ); + return qfalse; + } + if( points ) + { + vic->client->pers.statscounters.spreebleeds += points; + if( vic->client->pers.statscounters.spreebleeds < 1 ) + vic->client->pers.statscounters.spreebleeds = 1; + if( activate && !vic->client->pers.bleeder ) + { + vic->client->pers.bleeder = qtrue; + level.bleeders++; + + AP( va( "print \"^3!outlaw: ^7%s^7 has been designated an outlaw by ^7%s\n\"", + vic->client->pers.netname, + ( ent ) ? ent->client->pers.netname : "console" ) ); + } + else + { + AP( va( "print \"^3!outlaw: ^7%s^7 bleeder value has been adjusted by ^7%s\n\"", + vic->client->pers.netname, + ( ent ) ? ent->client->pers.netname : "console" ) ); + ADMP( va( "^3!outlaw: ^7%s^7's bleeder value is now %d\n", + vic->client->pers.netname, + vic->client->pers.statscounters.spreebleeds ) ); + } + } + else + { + vic->client->pers.statscounters.spreebleeds = 0; + if( vic->client->pers.bleeder ) + vic->client->pers.bleeder = qfalse; + if( level.bleeders ) + level.bleeders--; + + AP( va( "print \"^3!outlaw: ^7%s^7 has been pardoned by ^7%s\n\"", + vic->client->pers.netname, + ( ent ) ? ent->client->pers.netname : "console" ) ); + } + return qtrue; +} + qboolean G_admin_slap( gentity_t *ent, int skiparg ) { int pids[ MAX_CLIENTS ]; @@ -6452,6 +7325,45 @@ qboolean G_admin_drop( gentity_t *ent, int skiparg ) return qtrue; } +qboolean G_admin_bubble( gentity_t *ent, int skiparg ) +{ + int pids[ MAX_CLIENTS ]; + char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; + gentity_t *vic; + + if( G_SayArgc() < 2 + skiparg ) + { + ADMP( "^3!bubble: ^7usage: !bubble [name|slot#]\n" ); + return qfalse; + } + G_SayArgv( 1 + skiparg, name, sizeof( name ) ); + if( G_ClientNumbersFromString( name, pids ) != 1 ) + { + G_MatchOnePlayer( pids, err, sizeof( err ) ); + ADMP( va( "^3!bubble: ^7%s\n", err ) ); + return qfalse; + } + if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) + { + ADMP( "^3!bubble: ^7sorry, but your intended victim has a higher admin" + " level than you\n" ); + return qfalse; + } + + vic = &g_entities[ pids[ 0 ] ]; + if( vic->client->pers.bubbleTime ) + vic->client->pers.bubbleTime = 0; + else + vic->client->pers.bubbleTime = level.time + 500; + + AP( va( "print \"^3!bubble: ^7bubbles %s for %s^7 by %s\n\"", + ( vic->client->pers.bubbleTime ) ? "enabled" : "disabled", + vic->client->pers.netname, + ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); + + return qtrue; +} + qboolean G_admin_buildlog( gentity_t *ent, int skiparg ) { #define LOG_DISPLAY_LENGTH 10 @@ -6663,6 +7575,34 @@ qboolean G_admin_buildlog( gentity_t *ent, int skiparg ) return qtrue; } +int G_admin_autorevert( gentity_t *ent ) +{ + int count = 0; + int max; + buildHistory_t *ptr; + + if( !g_autoRevert.integer ) + return 0; + if( !g_buildLogMaxLength.integer ) + return 0; + + max = g_autoRevert.integer; + ptr = level.buildHistory; + while( ptr && count < max ) + { + if( ptr->ent == ent && ptr->time > level.time - (60000 * 5) && + ( ptr->fate == BF_TEAMKILLED || ptr->fate == BF_DECONNED ) ) + { + trap_SendConsoleCommand( EXEC_APPEND, va("!revert #%d\n", ptr->ID) ); + count++; + } + + ptr = ptr->next; + } + + return count; +} + qboolean G_admin_revert( gentity_t *ent, int skiparg ) { int i = 0, j = 0, repeat = 1, ID = 0, len, matchlen=0; @@ -6805,33 +7745,6 @@ qboolean G_admin_revert( gentity_t *ent, int skiparg ) return qfalse; } } - // Prevent teleport glitch when reverting an occupied hovel - if( targ->s.modelindex == BA_A_HOVEL && - targ->active ) - { - gentity_t *builder = targ->builder; - vec3_t newOrigin; - vec3_t newAngles; - - VectorCopy( targ->s.angles, newAngles ); - newAngles[ ROLL ] = 0; - - VectorCopy( targ->s.origin, newOrigin ); - VectorMA( newOrigin, 1.0f, targ->s.origin2, newOrigin ); - - //prevent lerping - builder->client->ps.eFlags ^= EF_TELEPORT_BIT; - builder->client->ps.eFlags &= ~EF_NODRAW; - G_UnlaggedClear( builder ); - - G_SetOrigin( builder, newOrigin ); - VectorCopy( newOrigin, builder->client->ps.origin ); - G_SetClientViewAngle( builder, newAngles ); - - //client leaves hovel - builder->client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING; - } - // if we haven't returned yet then we're good to go, free it G_FreeEntity( targ ); // put the marked buildables back and mark them again @@ -7225,6 +8138,76 @@ qboolean G_admin_L1(gentity_t *ent, int skiparg ){ return qtrue; } +qboolean G_admin_nobuild(gentity_t *ent, int skiparg ) +{ + char func[ 32 ]; + + if( ent && ent->client->pers.teamSelection != PTE_NONE ) + { + ADMP( "^3!bring: ^7you can only use this command from spectator\n" ); + return qfalse; + } + + if( G_SayArgc() < 2 + skiparg ) + { + ADMP( "^3!nobuild: ^7usage: !nobuild [on|off|save|add|del|list|mode|zone|+|-|go]\n" ); + return qfalse; + } + G_SayArgv( 1 + skiparg, func, sizeof( func ) ); + + if( !Q_stricmp( func, "on" ) ) + { + nobuild_set( qtrue, ent ); + } + else if( !Q_stricmp( func, "off" ) ) + { + nobuild_set( qfalse, ent ); + } + else if( !Q_stricmp( func, "save" ) ) + { + nobuild_save( ); + } + else if( !Q_stricmp( func, "list" ) ) + { + nobuild_list( ent ); + } + else if( !Q_stricmp( func, "add" ) ) + { + nobuild_add( ent ); + } + else if( !Q_stricmp( func, "del" ) ) + { + nobuild_del( ent ); + } + else if( !Q_stricmp( func, "zone" ) ) + { + nobuild_command( ent, qtrue, qfalse, 0.0f ); + } + else if( !Q_stricmp( func, "mode" ) ) + { + nobuild_command( ent, qfalse, qtrue, 0.0f ); + } + else if( !Q_stricmp( func, "+" ) ) + { + nobuild_command( ent, qfalse, qfalse, 8.0f ); + } + else if( !Q_stricmp( func, "-" ) ) + { + nobuild_command( ent, qfalse, qfalse, -8.0f ); + } + else if( !Q_stricmp( func, "go" ) ) + { + nobuild_go( ent ); + } + else + { + ADMP( "^3!nobuild: ^7usage: !nobuild [on|off|save|add|del|list|mode|zone|+|-|go]\n" ); + return qfalse; + } + + return qtrue; +} + qboolean G_admin_invisible( gentity_t *ent, int skiparg ) { if( !ent ) diff --git a/src/game/g_admin.h b/src/game/g_admin.h index 33103ff..6e6e57a 100644 --- a/src/game/g_admin.h +++ b/src/game/g_admin.h @@ -32,19 +32,24 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define ADMBP_end() G_admin_buffer_end(ent) #define MAX_ADMIN_LEVELS 32 -#define MAX_ADMIN_ADMINS 1024 +#define MAX_ADMIN_ADMINS 2048 #define MAX_ADMIN_BANS 1024 #define MAX_ADMIN_NAMELOGS 128 #define MAX_ADMIN_NAMELOG_NAMES 5 #define MAX_ADMIN_ADMINLOGS 128 #define MAX_ADMIN_ADMINLOG_ARGS 50 +#define MAX_ADMIN_TKLOGS 64 #define MAX_ADMIN_FLAG_LEN 20 #define MAX_ADMIN_FLAGS 1024 #define MAX_ADMIN_COMMANDS 64 #define MAX_ADMIN_CMD_LEN 20 #define MAX_ADMIN_BAN_REASON 50 + #define MAX_ADMIN_BANSUSPEND_DAYS 14 +#define CHAT_MAXCHAN 10 +#define CHAT_MAXPASS 12 + /* * IMMUNITY - cannot be vote kicked, vote muted * NOCENSORFLOOD - cannot be censored or flood protected @@ -63,14 +68,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * SEESFULLLISTPLAYERS - sees all information in !listplayers * DBUILDER - permanent designated builder * STEALTH - uses admin stealth - * SPECIAL - allows some special permissions (unlimited votes etc) - * SPECIALNAME - allows black text in name - * .NOCHAT - mutes a player on connect - * .NOVOTE - disallows voting by a player + * BANIMMUNITY - immune from IP based bans * ALLFLAGS - all flags (including command flags) apply to this player */ - #define ADMF_IMMUNITY "IMMUNITY" #define ADMF_NOCENSORFLOOD "NOCENSORFLOOD" #define ADMF_TEAMCHANGEFREE "TEAMCHANGEFREE" @@ -92,14 +93,12 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define ADMF_BAN_IMMUNITY "BANIMMUNITY" -#define ADMF_SPECIAL "SPECIAL" -#define ADMF_SPECIALNAME "SPECIALNAME" - #define ADMF_NO_CHAT ".NOCHAT" #define ADMF_NO_VOTE ".NOVOTE" #define MAX_ADMIN_LISTITEMS 20 #define MAX_ADMIN_SHOWBANS 10 + #define MAX_ADMIN_MAPLOG_LENGTH 5 // important note: QVM does not seem to allow a single char to be a @@ -129,6 +128,8 @@ typedef struct g_admin_admin int level; char flags[ MAX_ADMIN_FLAGS ]; int seen; + char chat[ CHAT_MAXCHAN ][ CHAT_MAXPASS ]; + int karma; } g_admin_admin_t; @@ -168,7 +169,6 @@ typedef struct g_admin_namelog int denyHumanWeapons; int denyAlienClasses; int specExpires; - int voteCount; } g_admin_namelog_t; @@ -184,6 +184,19 @@ typedef struct g_admin_adminlog } g_admin_adminlog_t; +typedef struct g_admin_tklog +{ + char name[ MAX_NAME_LENGTH ]; + char victim[ MAX_NAME_LENGTH ]; + int id; + int time; + int damage; + int value; + int team; + int weapon; +} +g_admin_tklog_t; + qboolean G_admin_ban_check( char *userinfo, char *reason, int rlen ); qboolean G_admin_cmd_check( gentity_t *ent, qboolean say ); qboolean G_admin_readconfig( gentity_t *ent, int skiparg ); @@ -195,24 +208,31 @@ int G_admin_level( gentity_t *ent ); void G_admin_set_adminname( gentity_t *ent ); char* G_admin_adminPrintName( gentity_t *ent ); -qboolean G_admin_seen(gentity_t *ent, int skiparg ); -void G_admin_seen_update( char *guid ); +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 ); // ! command functions qboolean G_admin_time( gentity_t *ent, int skiparg ); qboolean G_admin_setlevel( gentity_t *ent, int skiparg ); -qboolean G_admin_flaglist( gentity_t *ent, int skiparg ); -qboolean G_admin_flag( gentity_t *ent, int skiparg ); qboolean G_admin_kick( gentity_t *ent, int skiparg ); qboolean G_admin_adjustban( gentity_t *ent, int skiparg ); qboolean G_admin_subnetban( gentity_t *ent, int skiparg ); qboolean G_admin_suspendban( gentity_t *ent, int skiparg ); qboolean G_admin_ban( gentity_t *ent, int skiparg ); qboolean G_admin_unban( gentity_t *ent, int skiparg ); +qboolean G_admin_seen(gentity_t *ent, int skiparg ); +void G_admin_karma_sync( void ); +void G_admin_seen_update( gclient_t *client, qboolean disconnect ); +qboolean G_admin_expire( gentity_t *ent, int skiparg ); qboolean G_admin_putteam( gentity_t *ent, int skiparg ); qboolean G_admin_adminlog( gentity_t *ent, int skiparg ); void G_admin_adminlog_cleanup( void ); void G_admin_adminlog_log( gentity_t *ent, char *command, char *args, int skiparg, qboolean success ); +qboolean G_admin_tklog( gentity_t *ent, int skiparg ); +void G_admin_tklog_cleanup( void ); +void G_admin_tklog_log( gentity_t *attacker, gentity_t *victim, int meansOfDeath ); qboolean G_admin_listadmins( gentity_t *ent, int skiparg ); qboolean G_admin_listlayouts( gentity_t *ent, int skiparg ); qboolean G_admin_listplayers( gentity_t *ent, int skiparg ); @@ -237,25 +257,33 @@ qboolean G_admin_spec999( gentity_t *ent, int skiparg ); qboolean G_admin_register( gentity_t *ent, int skiparg ); qboolean G_admin_rename( gentity_t *ent, int skiparg ); qboolean G_admin_restart( gentity_t *ent, int skiparg ); -qboolean G_admin_nobuild( gentity_t *ent, int skiparg ); qboolean G_admin_nextmap( gentity_t *ent, int skiparg ); qboolean G_admin_namelog( gentity_t *ent, int skiparg ); qboolean G_admin_lock( gentity_t *ent, int skiparg ); qboolean G_admin_unlock( gentity_t *ent, int skiparg ); qboolean G_admin_info( gentity_t *ent, int skiparg ); +qboolean G_admin_nobuild(gentity_t *ent, int skiparg ); qboolean G_admin_buildlog( gentity_t *ent, int skiparg ); qboolean G_admin_revert( gentity_t *ent, int skiparg ); +int G_admin_autorevert( gentity_t *ent ); qboolean G_admin_pause( gentity_t *ent, int skiparg ); +qboolean G_admin_practice( gentity_t *ent, int skiparg ); qboolean G_admin_L0( gentity_t *ent, int skiparg ); qboolean G_admin_L1( gentity_t *ent, int skiparg ); +qboolean G_admin_bring( gentity_t *ent, int skiparg ); qboolean G_admin_putmespec( gentity_t *ent, int skiparg ); +qboolean G_admin_outlaw( gentity_t *ent, int skiparg ); qboolean G_admin_warn( gentity_t *ent, int skiparg ); qboolean G_admin_designate( gentity_t *ent, int skiparg ); +qboolean G_admin_flaglist( 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_cp( gentity_t *ent, int skiparg ); +qboolean G_admin_invisible( gentity_t *ent, int skiparg ); qboolean G_admin_slap( gentity_t *ent, int skiparg ); qboolean G_admin_drop( gentity_t *ent, int skiparg ); -qboolean G_admin_invisible( gentity_t *ent, int skiparg ); +qboolean G_admin_bubble( gentity_t *ent, int skiparg ); void G_admin_print( gentity_t *ent, char *m ); void G_admin_buffer_print( gentity_t *ent, char *m ); diff --git a/src/game/g_buildable.c b/src/game/g_buildable.c index 3290ccc..5a2698b 100644 --- a/src/game/g_buildable.c +++ b/src/game/g_buildable.c @@ -513,6 +513,54 @@ static void freeBuildable( gentity_t *self ) G_FreeEntity( self ); } +/* +================ +G_RecoverBuildPoints + +Schedule build points for delayed recovery when a buildable dies + credit to Mercenaries Guild dev team (mgdev) for the whole idea +================ +*/ +void G_RecoverBuildPoints( gentity_t *self ) +{ + int team; + int value; + + if( g_buildPointsRecoverRate.integer < 1 ) + return; + + if( self->killedBy != ENTITYNUM_NONE ) + return; + + team = BG_FindTeamForBuildable( self->s.modelindex ); + value = BG_FindBuildPointsForBuildable( self->s.modelindex ); + if( team == BIT_ALIENS ) + { + if( !level.alienRecoverBuildPoints ) + level.alienRecoverTime = level.time + 60000 / g_buildPointsRecoverRate.integer; + level.alienRecoverBuildPoints += value; + } + else if( team == BIT_HUMANS ) + { + if( !level.humanRecoverBuildPoints ) + level.humanRecoverTime = level.time + 60000 / g_buildPointsRecoverRate.integer; + level.humanRecoverBuildPoints += value; + } + + self->killedBy = ENTITYNUM_NONE; +} + +static void G_RecoverSetKiller( gentity_t *self, gentity_t *attacker ) +{ + if( g_buildPointsRecoverRate.integer < 1 ) + return; + + // note attacker if teamkill + if( attacker && attacker->client && + attacker->client->ps.stats[ STAT_PTEAM ] == BG_FindTeamForBuildable( self->s.modelindex ) ) + self->killedBy = attacker - g_entities; +} + //================================================================================== @@ -531,6 +579,7 @@ void A_CreepRecede( gentity_t *self ) if( !( self->s.eFlags & EF_DEAD ) ) { self->s.eFlags |= EF_DEAD; + G_RecoverBuildPoints( self ); G_AddEvent( self, EV_BUILD_DESTROY, 0 ); if( self->spawned ) @@ -573,6 +622,7 @@ void ASpawn_Melt( gentity_t *self ) if( !( self->s.eFlags & EF_DEAD ) ) { self->s.eFlags |= EF_DEAD; + G_RecoverBuildPoints( self ); G_AddEvent( self, EV_BUILD_DESTROY, 0 ); if( self->spawned ) @@ -631,6 +681,7 @@ void ASpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int buildHistory_t *new; new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; + new->time = level.time; new->ent = ( attacker && attacker->client ) ? attacker : NULL; if( new->ent ) new->name[ 0 ] = 0; @@ -658,6 +709,8 @@ void ASpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int self->s.eFlags &= ~EF_FIRING; //prevent any firing effects + G_RecoverSetKiller( self, attacker ); + if( attacker && attacker->client ) { if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) @@ -796,6 +849,18 @@ void AOvermind_Think( gentity_t *self ) vec3_t mins, maxs; int i, num; gentity_t *enemy; + float om_range = OVERMIND_ATTACK_RANGE; + float om_damage = self->splashDamage; + float om_splash = self->splashRadius; + + if( g_modMainStrength.integer > 0 ) + { + om_range = om_range * g_modMainStrength.value / 100; + range[0] = range[1] = range[2] = om_range; + + om_damage = om_damage * g_modMainStrength.value / 100; + om_splash = om_splash * g_modMainStrength.value / 100; + } VectorAdd( self->s.origin, range, maxs ); VectorSubtract( self->s.origin, range, mins ); @@ -814,8 +879,8 @@ void AOvermind_Think( gentity_t *self ) if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) { self->timestamp = level.time; - G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, - self->splashRadius, self, MOD_OVERMIND, PTE_ALIENS ); + G_SelectiveRadiusDamage( self->s.pos.trBase, self, om_damage, + om_splash, self, MOD_OVERMIND, PTE_ALIENS ); G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); } } @@ -940,6 +1005,7 @@ void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, buildHistory_t *new; new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; + new->time = level.time; new->ent = ( attacker && attacker->client ) ? attacker : NULL; if( new->ent ) new->name[ 0 ] = 0; @@ -966,6 +1032,8 @@ void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, else self->nextthink = level.time; //blast immediately + G_RecoverSetKiller( self, attacker ); + if( attacker && attacker->client ) { if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) @@ -1031,13 +1099,18 @@ void AAcidTube_Damage( gentity_t *self ) { if( self->spawned ) { + int repeatRate = ACIDTUBE_REPEAT; + if( !( self->s.eFlags & EF_FIRING ) ) { self->s.eFlags |= EF_FIRING; G_AddEvent( self, EV_ALIEN_ACIDTUBE, DirToByte( self->s.origin2 ) ); } - if( ( self->timestamp + ACIDTUBE_REPEAT ) > level.time ) + if( g_modBuildableSpeed.integer > 0 ) + repeatRate = repeatRate * 100 / g_modBuildableSpeed.integer; + + if( ( self->timestamp + repeatRate ) > level.time ) self->think = AAcidTube_Damage; else { @@ -1096,7 +1169,8 @@ void AAcidTube_Think( gentity_t *self ) if( !G_Visible( self, enemy ) ) continue; - if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( enemy->client && + ( enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS || enemy->client->pers.bleeder ) ) { if( level.paused || enemy->client->pers.paused ) continue; @@ -1389,6 +1463,7 @@ void AHovel_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int buildHistory_t *new; new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; + new->time = level.time; new->ent = ( attacker && attacker->client ) ? attacker : NULL; if( new->ent ) new->name[ 0 ] = 0; @@ -1447,6 +1522,8 @@ void AHovel_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int self->r.contents = 0; //stop collisions... trap_LinkEntity( self ); //...requires a relink + G_RecoverSetKiller( self, attacker ); + if( attacker && attacker->client ) { if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) @@ -1502,6 +1579,18 @@ void ABooster_Touch( gentity_t *self, gentity_t *other, trace_t *trace ) if( client && client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) return; + if( client->pers.bleeder ) + { + if( !(client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED ) ) + { + client->ps.stats[ STAT_STATE ] |= SS_POISONCLOUDED; + client->lastPoisonCloudedTime = level.time; + trap_SendServerCommand( client->ps.clientNum, "poisoncloud" ); + trap_SendServerCommand( client->ps.clientNum, "print \"Your booster has poisoned you\n\"" ); + } + return; + } + //only allow boostage once every 30 seconds if( client->lastBoostedTime + BOOSTER_INTERVAL > level.time ) return; @@ -1772,6 +1861,15 @@ void HReactor_Think( gentity_t *self ) vec3_t mins, maxs; int i, num; gentity_t *enemy, *tent; + float rc_range = REACTOR_ATTACK_RANGE; + float rc_damage = REACTOR_ATTACK_DAMAGE; + + if( g_modMainStrength.integer > 0 ) + { + rc_range = rc_range * g_modMainStrength.value / 100; + rc_damage = rc_damage * g_modMainStrength.value / 100; + range[0] = range[1] = range[2] = rc_range; + } VectorAdd( self->s.origin, range, maxs ); VectorSubtract( self->s.origin, range, mins ); @@ -1792,8 +1890,8 @@ void HReactor_Think( gentity_t *self ) if( level.paused || enemy->client->pers.paused ) continue; self->timestamp = level.time; - G_SelectiveRadiusDamage( self->s.pos.trBase, self, REACTOR_ATTACK_DAMAGE, - REACTOR_ATTACK_RANGE, self, MOD_REACTOR, PTE_HUMANS ); + G_SelectiveRadiusDamage( self->s.pos.trBase, self, rc_damage, + rc_range, self, MOD_REACTOR, PTE_HUMANS ); tent = G_TempEntity( enemy->s.pos.trBase, EV_TESLATRAIL ); @@ -1943,6 +2041,7 @@ void HMedistat_Think( gentity_t *self ) { if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] && player->client->ps.pm_type != PM_DEAD && + !player->client->pers.bleeder && self->enemy == player ) occupied = qtrue; } @@ -1960,7 +2059,8 @@ void HMedistat_Think( gentity_t *self ) if( player->flags & FL_NOTARGET ) continue; // notarget cancels even beneficial effects? - if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && + !player->client->pers.bleeder ) { if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] && player->client->ps.pm_type != PM_DEAD ) @@ -1980,6 +2080,34 @@ void HMedistat_Think( gentity_t *self ) } } + // bleeding spree retribution + if( level.bleeders && !self->enemy ) + { + //look for something to hurt + for( i = 0; i < num; i++ ) + { + player = &g_entities[ entityList[ i ] ]; + + if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && player->client->pers.bleeder ) + { + if( player->health > 0 && + player->client->ps.pm_type != PM_DEAD ) + { + self->enemy = player; + + //start the heal anim + if( !self->active ) + { + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); + self->active = qtrue; + } + } + if( BG_InventoryContainsUpgrade( UP_MEDKIT, player->client->ps.stats ) ) + BG_RemoveUpgradeFromInventory( UP_MEDKIT, player->client->ps.stats ); + } + } + } + //nothing left to heal so go back to idling if( !self->enemy && self->active ) { @@ -1990,6 +2118,12 @@ void HMedistat_Think( gentity_t *self ) } else if( self->enemy ) //heal! { + if( self->enemy->client->pers.bleeder ) + { + G_Damage( self->enemy, NULL, NULL, NULL, NULL, 10, 0, MOD_SLIME ); + return; + } + if( self->enemy->client && self->enemy->client->ps.stats[ STAT_STATE ] & SS_POISONED ) self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; @@ -2147,7 +2281,8 @@ qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target, qboolean ign if( !traceEnt->client ) return qfalse; - if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) + if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS && + !traceEnt->client->pers.bleeder ) return qfalse; return qtrue; @@ -2211,6 +2346,24 @@ void HMGTurret_FindEnemy( gentity_t *self ) } } + // bleeder retribution + if( level.bleeders ) + { + for( i = 0; i < num; i++ ) + { + target = &g_entities[ entityList[ i ] ]; + + if( target->client && target->client->pers.bleeder ) + { + if( !HMGTurret_CheckTarget( self, target, qfalse ) ) + continue; + + self->enemy = target; + return; + } + } + } + //couldn't find a target self->enemy = NULL; } @@ -2239,7 +2392,7 @@ void HMGTurret_Think( gentity_t *self ) //if not powered don't do anything and check again for power next think if( !( self->powered = G_FindPower( self ) ) ) { - if( self->spawned ) + if( g_turretAim.integer && self->spawned ) { // unpowered turret barrel falls to bottom of range float droop; @@ -2254,7 +2407,7 @@ void HMGTurret_Think( gentity_t *self ) return; } } - + self->nextthink = level.time + POWER_REFRESH_TIME; return; } @@ -2275,9 +2428,24 @@ void HMGTurret_Think( gentity_t *self ) //if a new target cannot be found don't do anything if( !self->enemy ) + { + if( g_turretAim.integer && + self->rotatorAngle >= 360.0f && + level.time > self->last_move_time + TURRET_REST_TIME ) + { + float diff; + + diff = AngleSubtract( self->s.angles2[ YAW ], self->rotatorAngle - 360.0f ); + if( diff < -TURRET_REST_TOLERANCE ) + self->s.angles2[ YAW ] += TURRET_REST_SPEED; + else if ( diff > TURRET_REST_TOLERANCE ) + self->s.angles2[ YAW ] -= TURRET_REST_SPEED; + } return; + } self->enemy->targeted = self; + self->last_move_time = level.time; //if we are pointing at our target and we can fire shoot it if( HMGTurret_TrackEnemy( self ) && ( self->count < level.time ) ) @@ -2346,7 +2514,8 @@ void HTeslaGen_Think( gentity_t *self ) if( enemy->flags & FL_NOTARGET ) continue; - if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && + if( enemy->client && + ( enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || enemy->client->pers.bleeder ) && enemy->health > 0 && !level.paused && !enemy->client->pers.paused && Distance( enemy->s.pos.trBase, self->s.pos.trBase ) <= TESLAGEN_RANGE ) @@ -2404,6 +2573,9 @@ void HSpawn_Disappear( gentity_t *self ) self->r.contents = 0; //stop collisions... trap_LinkEntity( self ); //...requires a relink + + self->s.eFlags |= EF_DEAD; + G_RecoverBuildPoints( self ); } @@ -2436,6 +2608,9 @@ void HSpawn_Blast( gentity_t *self ) self->r.contents = 0; //stop collisions... trap_LinkEntity( self ); //...requires a relink + + self->s.eFlags |= EF_DEAD; + G_RecoverBuildPoints( self ); } @@ -2451,6 +2626,7 @@ void HSpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int buildHistory_t *new; new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; + new->time = level.time; new->ent = ( attacker && attacker->client ) ? attacker : NULL; if( new->ent ) new->name[ 0 ] = 0; @@ -2485,6 +2661,8 @@ void HSpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int self->nextthink = level.time; //blast immediately } + G_RecoverSetKiller( self, attacker ); + if( attacker && attacker->client ) { if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) @@ -2769,6 +2947,50 @@ qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable ) return qfalse; } +/* +=============== +G_BuildableRangeClosest + +Find Closest distance to a type of buildable, -1 +=============== +*/ +float G_BuildableRangeClosest( vec3_t origin, float r, buildable_t buildable ) +{ + int entityList[ MAX_GENTITIES ]; + vec3_t range; + vec3_t mins, maxs; + int i, num; + gentity_t *ent; + float found = -1.0; + + VectorSet( range, r, r, r ); + VectorAdd( origin, range, maxs ); + VectorSubtract( origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + ent = &g_entities[ entityList[ i ] ]; + + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( ent->biteam == BIT_HUMANS && !ent->powered ) + continue; + + if( ent->s.modelindex == buildable && ent->spawned ) + { + float dist; + + dist = Distance( origin, ent->s.origin ); + if( found < 0.0 || dist < found ) + found = dist; + } + } + + return found; +} + static qboolean G_BoundsIntersect(const vec3_t mins, const vec3_t maxs, const vec3_t mins2, const vec3_t maxs2) { @@ -2851,7 +3073,6 @@ static int G_CompareBuildablesForRemoval( const void *a, const void *b ) buildableA = *(gentity_t **)a; buildableB = *(gentity_t **)b; - // Prefer the one that collides with the thing we're building aMatches = G_BuildablesIntersect( cmpBuildable, cmpOrigin, buildableA->s.modelindex, buildableA->s.origin ); @@ -2909,6 +3130,7 @@ void G_FreeMarkedBuildables( void ) new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = -1; + new->time = 0; new->ent = NULL; Q_strncpyz( new->name, "<markdecon>", 12 ); new->buildable = ent->s.modelindex; @@ -3035,12 +3257,7 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable, // Don't allow destruction of hovel with granger inside if( ent->s.modelindex == BA_A_HOVEL && ent->active ) - { - if( buildable == BA_A_HOVEL ) - return IBE_HOVEL; - else - continue; - } + continue; // Explicitly disallow replacement of the core buildable with anything // other than the core buildable @@ -3163,18 +3380,17 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance { vec3_t angles; vec3_t entity_origin, normal; - vec3_t mins, maxs, nbmins, nbmaxs; - vec3_t nbVect; + vec3_t mins, maxs; trace_t tr1, tr2, tr3; int i; itemBuildError_t reason = IBE_NONE; + itemBuildError_t old_reason = IBE_NONE; gentity_t *tempent; float minNormal; qboolean invert; int contents; playerState_t *ps = &ent->client->ps; int buildPoints; - gentity_t *tmp; itemBuildError_t tempReason; // Stop all buildables from interacting with traces @@ -3200,28 +3416,13 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance if( tr1.entityNum != ENTITYNUM_WORLD ) reason = IBE_NORMAL; + // check nobuild zones + if( nobuild_check( entity_origin ) >= 0 ) + reason = IBE_PERMISSION; + contents = trap_PointContents( entity_origin, -1 ); buildPoints = BG_FindBuildPointsForBuildable( buildable ); - - //check if we are near a nobuild marker, if so, can't build here... - for( i = 0; i < MAX_GENTITIES; i++ ) - { - tmp = &g_entities[ i ]; - - if( !tmp->noBuild.isNB ) - continue; - - nbVect[0] = tmp->noBuild.Area; - nbVect[1] = tmp->noBuild.Area; - nbVect[2] = tmp->noBuild.Height; - - VectorSubtract( origin, nbVect, nbmins ); - VectorAdd( origin, nbVect, nbmaxs ); - - if( trap_EntityContact( nbmins, nbmaxs, tmp ) ) - reason = IBE_PERMISSION; - - } + if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { //alien criteria @@ -3276,6 +3477,7 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance switch( buildable ) { case BA_A_OVERMIND: + old_reason = reason; reason = IBE_OVERMIND; break; @@ -3359,6 +3561,7 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance if( tempent->s.modelindex == BA_H_REACTOR && !tempent->deconstruct ) { + old_reason = reason; reason = IBE_REACTOR; break; } @@ -3369,6 +3572,38 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance if( ( tempReason = G_SufficientBPAvailable( buildable, origin ) ) != IBE_NONE ) reason = tempReason; + if( g_modBuildableCount.integer > 1 && old_reason == IBE_NONE ) + { + if( reason == IBE_REACTOR ) + { + int rcs = 0; + + for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) + { + if( tempent->s.eType == ET_BUILDABLE && + tempent->s.modelindex == BA_H_REACTOR && + !tempent->deconstruct ) + rcs++; + } + if( rcs < g_modBuildableCount.integer ) + reason = IBE_NONE; + } + else if( reason == IBE_OVERMIND ) + { + int oms = 0; + + for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) + { + if( tempent->s.eType == ET_BUILDABLE && + tempent->s.modelindex == BA_A_OVERMIND && + !tempent->deconstruct ) + oms++; + } + if( oms < g_modBuildableCount.integer ) + reason = IBE_NONE; + } + } + // Relink buildables G_SetBuildableLinkState( qtrue ); @@ -3498,8 +3733,10 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori built->buildTime = built->s.time = level.time; built->spawnBlockTime = 0; + built->killedBy = ENTITYNUM_NONE; + // build instantly in cheat mode - if( builder->client && g_cheats.integer ) + if( builder->client && g_cheats.integer || g_instantBuild.integer ) { built->health = BG_FindHealthForBuildable( buildable ); built->buildTime = built->s.time = @@ -3704,6 +3941,7 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori { new = level.buildHistory; new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; + new->time = level.time; new->ent = builder; new->name[ 0 ] = 0; new->buildable = buildable; @@ -3722,39 +3960,6 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori return built; } -static void G_SpawnMarker( vec3_t origin ) -{ - gentity_t *nb; - int i; - - // Make the marker... - nb = G_Spawn( ); - nb->s.modelindex = 0; //Coder humor is win - VectorCopy( origin, nb->s.pos.trBase ); - VectorCopy( origin, nb->r.currentOrigin ); - nb->noBuild.isNB = qtrue; - nb->noBuild.Area = level.nbArea; - nb->noBuild.Height = level.nbHeight; - trap_LinkEntity( nb ); - - // Log markers made... - for( i = 0; i < MAX_GENTITIES; i++ ) - { - if( level.nbMarkers[ i ].Marker != NULL ) - continue; - - level.nbMarkers[ i ].Marker = nb; - VectorCopy( origin, level.nbMarkers[ i ].Origin ); - SnapVector( level.nbMarkers[ i ].Origin ); - break; - } - - // End nobuild mode... - level.noBuilding = qfalse; - level.nbArea = 0.0f; - level.nbHeight = 0.0f; -} - /* ================= G_BuildIfValid @@ -3770,15 +3975,6 @@ qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable ) switch( G_CanBuild( ent, buildable, dist, origin ) ) { case IBE_NONE: - if( level.noBuilding ) - { - vec3_t mins; - BG_FindBBoxForBuildable( buildable, mins, NULL ); - origin[2] += mins[2]; - - G_SpawnMarker( origin ); - return qtrue; - } G_Build( ent, buildable, origin, ent->s.apos.trBase ); return qtrue; @@ -3844,40 +4040,16 @@ qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable ) return qfalse; case IBE_SPWNWARN: - if( level.noBuilding ) - { - vec3_t mins; - BG_FindBBoxForBuildable( buildable, mins, NULL ); - origin[2] += mins[2]; - G_SpawnMarker( origin ); - return qtrue; - } G_TriggerMenu( ent->client->ps.clientNum, MN_A_SPWNWARN ); G_Build( ent, buildable, origin, ent->s.apos.trBase ); return qtrue; case IBE_TNODEWARN: - if( level.noBuilding ) - { - vec3_t mins; - BG_FindBBoxForBuildable( buildable, mins, NULL ); - origin[2] += mins[2]; - G_SpawnMarker( origin ); - return qtrue; - } G_TriggerMenu( ent->client->ps.clientNum, MN_H_TNODEWARN ); G_Build( ent, buildable, origin, ent->s.apos.trBase ); return qtrue; case IBE_RPTWARN: - if( level.noBuilding ) - { - vec3_t mins; - BG_FindBBoxForBuildable( buildable, mins, NULL ); - origin[2] += mins[2]; - G_SpawnMarker( origin ); - return qtrue; - } G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN ); G_Build( ent, buildable, origin, ent->s.apos.trBase ); return qtrue; @@ -4583,128 +4755,624 @@ void G_BaseSelfDestruct( pTeam_t team ) return "<buildlog entry expired>"; } + /* -============ -G_NobuildLoad + * ======= + * nobuild + * ======= + */ -load the nobuild markers that were previously saved (if there are any). -============ -*/ -void G_NobuildLoad( void ) +#define MAX_NOBUILD_ZONES 16 +#define NOBUILD_THINK_TIME 200 + +typedef struct +{ + qboolean in_use; + vec3_t origin; + vec3_t size; +} +nobuild_t; + +typedef struct +{ + vec3_t dir; + float yaw; +} +nobuild_corner_t; + +static nobuild_corner_t nobuildCorner[] = { + { { -1, -1, -1 }, 0 }, + { { -1, 1, -1 }, 270 }, + { { 1, -1, -1 }, 90 }, + { { 1, 1, -1 }, 180 }, + + { { -1, -1, 1 }, 270 }, + { { -1, 1, 1 }, 180 }, + { { 1, -1, 1 }, 0 }, + { { 1, 1, 1 }, 90 } +}; +#define MAX_NOBUILD_CORNERS ( sizeof( nobuildCorner ) / sizeof( nobuild_corner_t ) ) + +typedef enum +{ + NB_ORIGIN_X, + NB_ORIGIN_Y, + NB_ORIGIN_Z, + NB_SIZE_X, + NB_SIZE_Y, + NB_SIZE_Z +} +nobuild_sizer_t; +#define NB_COUNT ( NB_SIZE_Z + 1 ) + +static nobuild_t noBuildZones[ MAX_NOBUILD_ZONES ]; + +static qboolean noBuildActive = qfalse; +static int noBuildEditZone = 0; +static nobuild_sizer_t noBuildEditType = NB_ORIGIN_X; + +void nobuild_init( void ) { - fileHandle_t f; - int len; - char *nobuild; char map[ MAX_QPATH ]; - vec3_t origin = { 0.0f, 0.0f, 0.0f }; char line[ MAX_STRING_CHARS ]; - int i = 0; - int i2; - gentity_t *nb; - float area; - float height; + char *data, *pool; + fileHandle_t f; + int len; + int lcount = 0; + int zonecount = 0; + int i; + int id; + vec3_t origin, size; + + memset( noBuildZones, 0, sizeof( noBuildZones ) ); trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); - len = trap_FS_FOpenFile( va( "nobuild/%s.dat", map ), - &f, FS_READ ); + len = trap_FS_FOpenFile( va( "layouts/%s/nobuild.nbd", map ), &f, FS_READ ); if( len < 0 ) { - // This isn't needed since nobuild is pretty much optional... - //G_Printf( "ERROR: nobuild for %s could not be opened\n", map ); return; } - nobuild = G_Alloc( len + 1 ); - trap_FS_Read( nobuild, len, f ); - *( nobuild + len ) = '\0'; + data = pool = G_Alloc( len + 1 ); + trap_FS_Read( data, len, f ); + *( data + len ) = '\0'; trap_FS_FCloseFile( f ); - while( *nobuild ) + + i = 0; + while( *data ) { if( i >= sizeof( line ) - 1 ) { - return; + G_Printf( S_COLOR_RED "ERROR: line overflow in %s before \"%s\"\n", + va( "layouts/%s/nobuild.nbd", map ), line ); } - - line[ i++ ] = *nobuild; + line[ i++ ] = *data; line[ i ] = '\0'; - if( *nobuild == '\n' ) + if( *data == '\n' ) { - i = 0; - sscanf( line, "%f %f %f %f %f\n", - &origin[ 0 ], &origin[ 1 ], &origin[ 2 ], &area, &height ); - - // Make the marker... - nb = G_Spawn( ); - nb->s.modelindex = 0; - VectorCopy( origin, nb->s.pos.trBase ); - VectorCopy( origin, nb->r.currentOrigin ); - nb->noBuild.isNB = qtrue; - nb->noBuild.Area = area; - nb->noBuild.Height = height; - trap_LinkEntity( nb ); - - // Log markers made... - for( i = 0; i < MAX_GENTITIES; i++ ) - { - if( level.nbMarkers[ i ].Marker != NULL ) - continue; - - level.nbMarkers[ i ].Marker = nb; - VectorCopy( origin, level.nbMarkers[ i ].Origin ); - SnapVector( level.nbMarkers[ i ].Origin ); - break; - } - + i = 0; + lcount++; + id = -1; + if( sscanf( line, "%d %f %f %f %f %f %f\n", + &id, + &origin[ 0 ], &origin[ 1 ], &origin[ 2 ], + &size[ 0 ], &size[ 1 ], &size[ 2 ]) == 7 ) + { + if( zonecount >= MAX_NOBUILD_ZONES ) + { + G_Printf( S_COLOR_YELLOW "WARNING: nobuild zone count exceeded on line %d\n", lcount ); + break; + } + noBuildZones[ zonecount ].in_use = qtrue; + VectorCopy( origin, noBuildZones[ zonecount ].origin ); + VectorCopy( size, noBuildZones[ zonecount ].size ); + zonecount++; + } + else + { + G_Printf( S_COLOR_YELLOW "WARNING: nobuild data parse error on line %d\n", lcount ); + } } - nobuild++; + data++; } + + G_Free( pool ); + + if( zonecount ) + G_Printf( "nobuild: loaded %d zones\n", zonecount ); } -/* -============ -G_NobuildSave -Save all currently placed nobuild markers into the "nobuild" folder -============ -*/ -void G_NobuildSave( void ) +static void nobuild_sizer_think( gentity_t *self ) +{ + int zone, size; + vec3_t origin; + + if( !noBuildActive ) + { + G_FreeEntity( self ); + return; + } + + self->nextthink = level.time + NOBUILD_THINK_TIME; + + zone = noBuildEditZone; + if( zone < 0 || zone >= MAX_NOBUILD_ZONES || !noBuildZones[ zone ].in_use ) + { + if( self->r.linked ) + trap_UnlinkEntity( self ); + return; + } + + size = self->damage; + VectorCopy( noBuildZones[ zone ].origin, origin ); + switch ( noBuildEditType ) + { + case NB_ORIGIN_X: + case NB_SIZE_X: + origin[ 0 ] += size; + break; + case NB_ORIGIN_Y: + case NB_SIZE_Y: + origin[ 1 ] += size; + break; + case NB_ORIGIN_Z: + case NB_SIZE_Z: + origin[ 2 ] += size; + break; + } + + VectorCopy( origin, self->s.origin ); + VectorCopy( origin, self->s.pos.trBase ); + VectorCopy( origin, self->r.currentOrigin ); + + if( self->watertype != noBuildEditType ) + { + self->watertype = noBuildEditType; + switch( noBuildEditType ) + { + case NB_ORIGIN_X: + case NB_ORIGIN_Y: + case NB_ORIGIN_Z: + self->s.modelindex = G_ModelIndex( "models/players/human_base/jetpack.md3" ); + break; + case NB_SIZE_X: + case NB_SIZE_Y: + case NB_SIZE_Z: + self->s.modelindex = G_ModelIndex( "models/players/human_base/battpack.md3" ); + break; + } + } + + if( !self->r.linked ) + trap_LinkEntity( self ); +} + +static gentity_t *nobuild_sizer( float distance ) +{ + gentity_t *self; + + self = G_Spawn( ); + self->classname = "nobuild_sizer"; + self->s.eType = ET_GENERAL; + + self->s.time = level.time; + self->s.pos.trType = TR_STATIONARY; + self->s.pos.trTime = level.time; + + self->r.svFlags = SVF_BROADCAST; + + self->damage = distance; + self->watertype = -1; + self->think = nobuild_sizer_think; + + // run a think to set positions + nobuild_sizer_think( self ); + + return self; +} + +static void nobuild_corner_think( gentity_t *self ) +{ + int zone, corner; + vec3_t origin; + + zone = self->count; + corner = self->damage; + + if( !noBuildActive || !noBuildZones[ zone ].in_use ) + { + G_FreeEntity( self ); + return; + } + + self->nextthink = level.time + NOBUILD_THINK_TIME; + + VectorCopy( noBuildZones[ zone ].origin, origin ); + origin[ 0 ] += noBuildZones[ zone ].size[ 0 ] * nobuildCorner[ corner ].dir[ 0 ]; + origin[ 1 ] += noBuildZones[ zone ].size[ 1 ] * nobuildCorner[ corner ].dir[ 1 ]; + origin[ 2 ] += noBuildZones[ zone ].size[ 2 ] * nobuildCorner[ corner ].dir[ 2 ]; + + VectorCopy( origin, self->s.origin ); + VectorCopy( origin, self->s.pos.trBase ); + VectorCopy( origin, self->r.currentOrigin ); +} + +static gentity_t *nobuild_corner( int zone, int corner ) +{ + gentity_t *self; + + if( zone < 0 || zone >= MAX_NOBUILD_ZONES ) + return NULL; + if( !noBuildZones[ zone ].in_use ) + return NULL; + if( corner < 0 || corner >= MAX_NOBUILD_CORNERS ) + return NULL; + + self = G_Spawn( ); + self->classname = "nobuild_corner"; + self->s.eType = ET_GENERAL; + self->s.modelindex = 9999; + + self->s.time = level.time; + self->s.pos.trType = TR_STATIONARY; + self->s.pos.trTime = level.time; + + self->count = zone; + self->damage = corner; + self->think = nobuild_corner_think; + + // run a think to set positions + nobuild_corner_think( self ); + + self->s.apos.trBase[ YAW ] = nobuildCorner[ corner ].yaw; + if( nobuildCorner[ corner ].dir[ 2 ] > 0.0f ) + self->s.apos.trBase[ PITCH ] = 180.0f; + + trap_LinkEntity( self ); + + return self; +} + +static void nobuild_think( gentity_t *self ) +{ + int zone; + + zone = self->count; + + if( !noBuildActive || !noBuildZones[ zone ].in_use ) + { + G_FreeEntity( self ); + return; + } + + self->nextthink = level.time + NOBUILD_THINK_TIME; + VectorCopy( noBuildZones[ zone ].origin, self->s.origin ); + VectorCopy( noBuildZones[ zone ].origin, self->s.pos.trBase ); + VectorCopy( noBuildZones[ zone ].origin, self->r.currentOrigin ); +} + +static gentity_t *nobuild_spawn( int zone ) +{ + gentity_t *self; + int i; + + if( zone < 0 || zone >= MAX_NOBUILD_ZONES ) + return NULL; + if( !noBuildZones[ zone ].in_use ) + return NULL; + + self = G_Spawn( ); + self->classname = "nobuild_zone"; + self->s.eType = ET_GENERAL; + self->s.modelindex = G_ModelIndex( "models/buildables/repeater/repeater.md3" ); + + self->s.time = level.time; + self->s.pos.trType = TR_STATIONARY; + self->s.pos.trTime = level.time; + + self->think = nobuild_think; + self->nextthink = level.time + NOBUILD_THINK_TIME; + + VectorCopy( noBuildZones[ zone ].origin, self->s.origin ); + VectorCopy( noBuildZones[ zone ].origin, self->s.pos.trBase ); + VectorCopy( noBuildZones[ zone ].origin, self->r.currentOrigin ); + + self->s.apos.trBase[ PITCH ] = 180.0f; + + self->count = zone; + + trap_LinkEntity( self ); + + for( i = 0; i < MAX_NOBUILD_CORNERS; i++ ) + nobuild_corner( zone, i ); + + return self; +} + +static void nobuild_on( gentity_t *ent ) +{ + int i; + + if( noBuildActive ) + return; + + noBuildActive = qtrue; + noBuildEditZone = MAX_NOBUILD_ZONES - 1; + noBuildEditType = NB_ORIGIN_X; + + nobuild_command( ent, qtrue, qfalse, 0.0f ); + + for( i = 0; i < MAX_NOBUILD_ZONES; i++ ) + { + nobuild_spawn( i ); + } + + nobuild_sizer( 32.0f ); + nobuild_sizer( -32.0f ); + + trap_SendServerCommand( -1, + va( "print \"^3!nobuild ^7edit mode enabled by %s\n\"", + ( ent ) ? ent->client->pers.netname : "console " ) ); +} + +static void nobuild_off( gentity_t *ent ) +{ + if( noBuildActive ) + { + noBuildActive = qfalse; + + trap_SendServerCommand( -1, + va( "print \"^3!nobuild ^7edit mode disabled by %s\n\"", + ( ent ) ? ent->client->pers.netname : "console " ) ); + } +} + +void nobuild_add( gentity_t *ent ) +{ + int i; + + if( !noBuildActive ) + { + ADMP( "!nobuild is not on, use '!nobuild on' to enable it\n" ); + return; + } + + if( !ent ) + return; + + for( i = 0; i < MAX_NOBUILD_ZONES; i++ ) + { + if( noBuildZones[ i ].in_use ) + continue; + + noBuildZones[ i ].in_use = qtrue; + VectorCopy( ent->s.origin, noBuildZones[ i ].origin ); + VectorSet( noBuildZones[ i ].size, 64.0f, 64.0f, 64.0f ); + + noBuildEditZone = i; + nobuild_spawn( i ); + + ADMP( va( "added nobuild zone #%d\n", i ) ); + return; + } + + ADMP( "maximum nobuild zones reached, can not add another zone.\n" ); +} + +void nobuild_del( gentity_t *ent ) +{ + int zone; + + if( !noBuildActive ) + { + ADMP( "!nobuild is not on, use '!nobuild on' to enable it\n" ); + return; + } + + if( noBuildEditZone < 0 || noBuildEditZone >= MAX_NOBUILD_ZONES || + !noBuildZones[ noBuildEditZone ].in_use ) + { + ADMP( "a nobuild zone is not selected\n" ); + return; + } + + zone = noBuildEditZone; + nobuild_command( ent, qtrue, qfalse, 0.0f ); + noBuildZones[ zone ].in_use = qfalse; + if( noBuildEditZone == zone ) + noBuildEditZone = -1; + + ADMP( va( "removed nobuild zone #%d\n", zone ) ); +} + +#define SIZE_MIN( a, b ) ( a = ( a + b < 32.0 ? a = 32.0 : a + b ) ) + +void nobuild_command( gentity_t *ent, qboolean next_zone, qboolean next_type, float size ) +{ + int n; + + if( !noBuildActive ) + { + ADMP( "!nobuild is not on, use '!nobuild on' to enable it\n" ); + return; + } + + if( next_zone ) + { + n = noBuildEditZone + 1; + while( n != noBuildEditZone ) + { + if( n >= MAX_NOBUILD_ZONES ) + n = 0; + if( noBuildZones[ n ].in_use ) + { + noBuildEditZone = n; + ADMP( va ( "nobuild zone %d selected\n", n ) ); + return; + } + n++; + } + } + else if( next_type ) + { + noBuildEditType++; + if ( noBuildEditType >= NB_COUNT ) + noBuildEditType = 0; + } + else if( size ) + { + switch( noBuildEditType ) + { + case NB_ORIGIN_X: + noBuildZones[ noBuildEditZone ].origin[ 0 ] += size; + break; + case NB_ORIGIN_Y: + noBuildZones[ noBuildEditZone ].origin[ 1 ] += size; + break; + case NB_ORIGIN_Z: + noBuildZones[ noBuildEditZone ].origin[ 2 ] += size; + break; + case NB_SIZE_X: + SIZE_MIN( noBuildZones[ noBuildEditZone ].size[ 0 ], size ); + break; + case NB_SIZE_Y: + SIZE_MIN( noBuildZones[ noBuildEditZone ].size[ 1 ], size ); + break; + case NB_SIZE_Z: + SIZE_MIN( noBuildZones[ noBuildEditZone ].size[ 2 ], size ); + break; + } + } +} + +void nobuild_go( gentity_t *ent ) +{ + if( !ent ) + return; + + if( !noBuildActive ) + { + ADMP( "!nobuild is not on, use '!nobuild on' to enable it\n" ); + return; + } + if( noBuildEditZone < 0 || noBuildEditZone >= MAX_NOBUILD_ZONES || + !noBuildZones[ noBuildEditZone ].in_use ) + { + ADMP( "a nobuild zone is not selected\n" ); + return; + } + + VectorCopy( noBuildZones[ noBuildEditZone ].origin, ent->client->ps.origin ); +} + +void nobuild_set( qboolean enable, gentity_t *ent ) +{ + if( !ent && enable ) + { + ADMP( "only a connected client can enable nobuild mode\n" ); + return; + } + if( enable ) + { + nobuild_on( ent ); + } + else + { + nobuild_off( ent ); + } +} + +void nobuild_save( void ) { char map[ MAX_QPATH ]; char fileName[ MAX_OSPATH ]; fileHandle_t f; int len; + int count = 0; int i; - gentity_t *ent; char *s; trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); if( !map[ 0 ] ) { - G_Printf( "NobuildSave( ): no map is loaded\n" ); + G_Printf( "LayoutSave( ): no map is loaded\n" ); return; } - Com_sprintf( fileName, sizeof( fileName ), "nobuild/%s.dat", map ); - + Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/nobuild.nbd", map ); len = trap_FS_FOpenFile( fileName, &f, FS_WRITE ); - if( len < 0 ) + if( len < 0 ) { - G_Printf( "nobuildsave: could not open %s\n", fileName ); + G_Printf( "layoutsave: could not open %s\n", fileName ); return; } - G_Printf("nobuildsave: saving nobuild to %s\n", fileName ); + G_Printf("layoutsave: saving layout to %s\n", fileName ); - for( i = 0; i < MAX_GENTITIES; i++ ) + for( i = 0; i < MAX_NOBUILD_ZONES; i++ ) { - ent = &level.gentities[ i ]; - if( ent->noBuild.isNB != qtrue ) + if( !noBuildZones[ i ].in_use ) continue; - s = va( "%f %f %f %f %f\n", - ent->r.currentOrigin[ 0 ], - ent->r.currentOrigin[ 1 ], - ent->r.currentOrigin[ 2 ], - ent->noBuild.Area, - ent->noBuild.Height ); + s = va( "%d %f %f %f %f %f %f\n", + count, + noBuildZones[ i ].origin[ 0 ], + noBuildZones[ i ].origin[ 1 ], + noBuildZones[ i ].origin[ 2 ], + noBuildZones[ i ].size[ 0 ], + noBuildZones[ i ].size[ 1 ], + noBuildZones[ i ].size[ 2 ] ); trap_FS_Write( s, strlen( s ), f ); + count++; } + trap_FS_FCloseFile( f ); + G_Printf( "saved %d nobuild zones\n", count ); } + +void nobuild_list( gentity_t *ent ) +{ + int count = 0; + int i; + + ADMBP_begin(); + for( i = 0; i < MAX_NOBUILD_ZONES; i++ ) + { + if( !noBuildZones[ i ].in_use ) + continue; + + ADMBP( va( "%2d @ x: %6.2f, y: %6.2f, z: %6.2f - %4.2f x %4.2f x %4.2f\n", + i, + noBuildZones[ i ].origin[ 0 ], + noBuildZones[ i ].origin[ 1 ], + noBuildZones[ i ].origin[ 2 ], + noBuildZones[ i ].size[ 0 ], + noBuildZones[ i ].size[ 1 ], + noBuildZones[ i ].size[ 2 ] ) ); + count++; + } + + ADMBP( va( "nobuild contains %d zones\n", count ) ); + + ADMBP_end(); +} + +int nobuild_check( vec3_t origin ) +{ + int i; + + for( i = 0; i < MAX_NOBUILD_ZONES; i ++ ) + { + if( !noBuildZones[ i ].in_use ) + continue; + + if( origin[ 0 ] > noBuildZones[ i ].origin[ 0 ] - noBuildZones[ i ].size[ 0 ] && + origin[ 0 ] < noBuildZones[ i ].origin[ 0 ] + noBuildZones[ i ].size[ 0 ] && + origin[ 1 ] > noBuildZones[ i ].origin[ 1 ] - noBuildZones[ i ].size[ 1 ] && + origin[ 1 ] < noBuildZones[ i ].origin[ 1 ] + noBuildZones[ i ].size[ 1 ] && + origin[ 2 ] > noBuildZones[ i ].origin[ 2 ] - noBuildZones[ i ].size[ 2 ] && + origin[ 2 ] < noBuildZones[ i ].origin[ 2 ] + noBuildZones[ i ].size[ 2 ] ) + return i; + } + + return -1; +} + diff --git a/src/game/g_client.c b/src/game/g_client.c index 3d274ee..abdfa8e 100644 --- a/src/game/g_client.c +++ b/src/game/g_client.c @@ -910,7 +910,7 @@ team_t TeamCount( int ignoreClientNum, int team ) ClientCleanName ============ */ -static void ClientCleanName( const char *in, char *out, int outSize, qboolean special ) +static void ClientCleanName( const char *in, char *out, int outSize ) { int len, colorlessLen; char ch; @@ -957,8 +957,8 @@ static void ClientCleanName( const char *in, char *out, int outSize, qboolean sp break; } - // don't allow black in a name, unless if special - if( ColorIndex( *in ) == 0 && !special ) + // don't allow black in a name, period + if( ColorIndex( *in ) == 0 ) *out++ = COLOR_WHITE; else *out++ = *in; @@ -1154,11 +1154,7 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName ) // set name Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) ); s = Info_ValueForKey( userinfo, "name" ); - - if ( !G_admin_permission( ent, ADMF_SPECIALNAME ) ) - ClientCleanName( s, newname, sizeof( newname ), qfalse ); - else - ClientCleanName( s, newname, sizeof( newname ), qtrue ); + ClientCleanName( s, newname, sizeof( newname ) ); if( strcmp( oldname, newname ) ) { @@ -1166,10 +1162,7 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName ) showRenameMsg = qfalse; // in case we need to revert and there's no oldname - if ( !G_admin_permission( ent, ADMF_SPECIALNAME ) ) - ClientCleanName( va( "%s", client->pers.netname ), oldname, sizeof( oldname ), qfalse ); - else - ClientCleanName( va( "%s", client->pers.netname ), oldname, sizeof( oldname ), qtrue ); + ClientCleanName( va( "%s", client->pers.netname ), oldname, sizeof( oldname ) ); if( g_newbieNumbering.integer ) { @@ -1198,8 +1191,7 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName ) revertName = qtrue; } else if( g_maxNameChanges.integer > 0 - && client->pers.nameChanges >= g_maxNameChanges.integer - && !G_admin_permission( ent, ADMF_SPECIAL ) ) + && client->pers.nameChanges >= g_maxNameChanges.integer ) { trap_SendServerCommand( ent - g_entities, va( "print \"Maximum name changes reached (g_maxNameChanges = %d)\n\"", @@ -1248,7 +1240,8 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName ) //dont show if players invisible if( client->sess.invisible != qtrue ) trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE - " renamed to %s^7\n\"", oldname, client->pers.netname ) ); + " renamed to %s^7\n\"", oldname, client->pers.netname ) ); + if( g_decolourLogfiles.integer) { char decoloured[ MAX_STRING_CHARS ] = ""; @@ -1367,17 +1360,18 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName ) if ( client->sess.invisible != qtrue ) { Com_sprintf( userinfo, sizeof( userinfo ), - "n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\" - "hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\" - "tl\\%d\\ig\\%16s", - client->pers.netname, team, model, model, c1, c2, - client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, - teamLeader, BG_ClientListString( &client->sess.ignoreList ) ); + "n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\" + "hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\" + "tl\\%d\\ig\\%16s", + client->pers.netname, team, model, model, c1, c2, + client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, + teamLeader, BG_ClientListString( &client->sess.ignoreList ) ); trap_SetConfigstring( CS_PLAYERS + clientNum, userinfo ); } else { trap_SetConfigstring( CS_PLAYERS + clientNum, "" ); } + /*G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, userinfo );*/ } @@ -1426,6 +1420,7 @@ char *ClientConnect( int clientNum, qboolean firstTime ) return va( "%s", reason ); } + // IP filtering // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500 // recommanding PB based IP / GUID banning, the builtin system is pretty limited @@ -1443,13 +1438,38 @@ char *ClientConnect( int clientNum, qboolean firstTime ) if( G_FilterPacket( value ) ) return "You are banned from this server."; - if( ip[ 0 ] == 0 ||strlen( ip ) < 7 ) + if( strlen( ip ) < 7 ) { G_AdminsPrintf( "Connect from client with invalid IP: '%s' NAME: '%s^7'\n", - ip, Info_ValueForKey( userinfo, "name" ) ); + ip, Info_ValueForKey( userinfo, "name" ) ); return "Invalid client data"; } + // limit max clients per IP + if( g_maxGhosts.integer > 1 ) + { + gclient_t *other; + int count = 0; + + for( i = 0 ; i < level.maxclients; i++ ) + { + other = &level.clients[ i ]; + if( other && + ( other->pers.connected == CON_CONNECTED || other->pers.connected == CON_CONNECTING ) && + strcmp( ip, other->pers.ip ) == 0 ) + { + count++; + } + } + + if( count + 1 > g_maxGhosts.integer ) + { + G_AdminsPrintf( "Connect from client exceeds %d maximum connections per IP: '%s' NAME: '%s^7'\n", + g_maxGhosts.integer, ip, Info_ValueForKey( userinfo, "name" ) ); + return "Maximum simultaneous clients exceeded"; + } + } + // check for a password value = Info_ValueForKey( userinfo, "password" ); @@ -1464,7 +1484,7 @@ char *ClientConnect( int clientNum, qboolean firstTime ) memset( client, 0, sizeof(*client) ); // add guid to session so we don't have to keep parsing userinfo everywhere - if( !guid[ 0 ] ) + if( !guid[0] ) { Q_strncpyz( client->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", sizeof( client->pers.guid ) ); @@ -1473,23 +1493,9 @@ char *ClientConnect( int clientNum, qboolean firstTime ) { Q_strncpyz( client->pers.guid, guid, sizeof( client->pers.guid ) ); } - Q_strncpyz( client->pers.ip, ip, sizeof( client->pers.ip ) ); client->pers.adminLevel = G_admin_level( ent ); - // do autoghost now so that there won't be any name conflicts later on - if ( g_autoGhost.integer && client->pers.guid[ 0 ] != 'X' ) - { - for ( i = 0; i < MAX_CLIENTS; i++ ) - { - if ( i != ent - g_entities && g_entities[i].client && g_entities[i].client->pers.connected != CON_DISCONNECTED && !Q_stricmp( g_entities[i].client->pers.guid, client->pers.guid ) ) - { - trap_SendServerCommand( i, "disconnect \"You may not be connected to this server multiple times\"" ); - trap_DropClient( i, "disconnected" ); - } - } - } - client->pers.connected = CON_CONNECTING; // read or initialize the session data @@ -1545,7 +1551,6 @@ char *ClientConnect( int clientNum, qboolean firstTime ) CalculateRanks( ); G_admin_namelog_update( client, qfalse ); } - // if this is after !restart keepteams or !restart switchteams, apply said selection if ( client->sess.restartTeam != PTE_NONE ) { @@ -1602,35 +1607,58 @@ void ClientBegin( int clientNum ) // locate ent at a spawn point ClientSpawn( ent, NULL, NULL, NULL ); - + // Ignore invisible players for this section: if ( client->sess.invisible != qtrue ) { trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname ) ); - // auto mute flag - if( G_admin_permission( ent, ADMF_NO_CHAT ) ) - client->pers.muted = qtrue; - // 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" ); } + + // auto mute flag + if( G_admin_permission( ent, ADMF_NO_CHAT ) ) + client->pers.muted = qtrue; + + if( g_karma.integer ) + { + if( g_karma.integer > 1 && + client->pers.adminLevel == 0 && + client->pers.guid[0] != 'X' ) + { + if( !(g_newbieNumbering.integer && g_newbieNamePrefix.string[ 0 ] && + Q_stricmpn ( client->pers.netname, g_newbieNamePrefix.string, + strlen(g_newbieNamePrefix.string ) ) == 0 ) ) + { + trap_SendConsoleCommand( EXEC_APPEND, va( "!l1 %d", clientNum ) ); + trap_SendServerCommand( client->ps.clientNum, + "print \"^5The karma feature automatically !registers all players.\n\"" ); + } + } + else if( client->pers.adminLevel > 0 && + level.time - level.startTime > 60000 ) + { + trap_SendServerCommand( client->ps.clientNum, + va( "print \"^5Welcome back, your karma is %d\n\"", client->pers.karma / 1000 ) ); + } + } + G_LogPrintf( "ClientBegin: %i\n", clientNum ); if( g_clientUpgradeNotice.integer ) { if( !Q_stricmp( ent->client->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) ) { - trap_SendServerCommand( client->ps.clientNum, va( "print \"^3Your client is out of date. Updating your client will allow you to " - "become an admin on servers and download maps much more quickly. Please replace your client executable with a newer client. \n\"" ) ); - - trap_SendServerCommand( client->ps.clientNum, va("print \"^3Some available clients: \n" - "^2TremFusion^7- ^3http://www.tremfusion.net/download/^7\n" - "^2FSM-Trem^7 - ^3http://code.google.com/p/fsm-trem/^7\n" - "^2MGClient^7 - ^3http://releases.mercenariesguild.net/client/^7\n\"" ) ); + trap_SendServerCommand( client->ps.clientNum, va( "print \"^1Your client is out of date. Updating your client will allow you to " + "become an admin on servers and download maps much more quickly. Please replace your client executable with the one " + "at ^2http://trem.tjw.org/backport/^1 and reconnect. \n\"" ) ); } } @@ -1847,6 +1875,15 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles client->jetpackfuel = mod_jetpackFuel.value; } + //free credits + if( g_freeCredits.integer && ent != spawn ) + { + if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + client->ps.persistant[ PERS_CREDIT ] = ALIEN_MAX_KILLS; + else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + client->ps.persistant[ PERS_CREDIT ] = HUMAN_MAX_CREDITS; + } + G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); @@ -1999,7 +2036,8 @@ void ClientDisconnect( int clientNum ) Q_strncpyz( ptr->name, ent->client->pers.netname, MAX_NETNAME ); } } - + + //update namelog only if they are not invisible if ( ent->client->sess.invisible != qtrue ) G_admin_namelog_update( ent->client, qtrue ); G_LeaveTeam( ent ); diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c index 3857ef1..b9cca49 100644 --- a/src/game/g_cmds.c +++ b/src/game/g_cmds.c @@ -760,19 +760,47 @@ void Cmd_Team_f( gentity_t *ent ) // stop team join spam if( level.time - ent->client->pers.teamChangeTime < 1000 ) return; + + if( ent->client->pers.teamSelection != PTE_NONE ) + { + int cs_offset; + + if( ent->client->pers.teamSelection == PTE_ALIENS ) + cs_offset = 1; + else + cs_offset = 0; + if( level.teamVoteTime[ cs_offset ] ) + { + trap_SendServerCommand( ent-g_entities, "print \"Can not leave team during a team vote\n\"" ); + return; + } + } + // Prevent invisible players from joining a team - if ( ent->client->sess.invisible == qtrue ) + if( ent->client->sess.invisible == qtrue ) { trap_SendServerCommand( ent-g_entities, - va( "print \"You cannot join a team while invisible\n\"" ) ); + va( "print \"You cannot join a team while invisible\n\"" ) ); return; } - + if( oldteam == PTE_ALIENS ) aliens--; else if( oldteam == PTE_HUMANS ) humans--; + // practice mode + if( !force && g_practiceCount.integer ) + { + char name[ MAX_NAME_LENGTH ]; + + G_DecolorString( ent->client->pers.netname, name ); + if( strstr( name, g_practiceText.string ) ) + { + force = qtrue; + } + } + // do warm up if( g_doWarmup.integer && g_warmupMode.integer == 1 && level.time - level.startTime < g_warmup.integer * 1000 ) @@ -938,6 +966,10 @@ void Cmd_Team_f( gentity_t *ent ) Com_sprintf( buf, sizeof( buf ), "%s^7 abandoned humans and joined the aliens.", ent->client->pers.netname ); else Com_sprintf( buf, sizeof( buf ), "%s^7 joined the aliens.", ent->client->pers.netname ); + + if( g_modAlienRegenRange.integer ) + trap_SendServerCommand( ent-g_entities, + va( "print \"Reminder: aliens must be near base to regenerate health.\n\"" ) ); } else if( team == PTE_HUMANS ) { if ( oldteam == PTE_ALIENS ) @@ -991,15 +1023,12 @@ static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, cons } if( mode == SAY_ADMINS && - (!G_admin_permission( other, ADMF_ADMINCHAT ) || other->client->pers.ignoreAdminWarnings ) ) + (!G_admin_permission( other, ADMF_ADMINCHAT) || other->client->pers.ignoreAdminWarnings ) ) return; if( BG_ClientListTest( &other->client->sess.ignoreList, ent-g_entities ) ) ignore = qtrue; - - if ( ignore && g_fullIgnore.integer ) - return; - + trap_SendServerCommand( other-g_entities, va( "%s \"%s%s%s%c%c%s\"", ( mode == SAY_TEAM || mode == SAY_ACTION_T ) ? "tchat" : "chat", ( ignore ) ? "[skipnotify]" : "", @@ -1032,24 +1061,6 @@ void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) return; } - // Spam limit: If they said this message recently, ignore it. - if( g_spamTime.integer ) - if ( ( level.time - ent->client->pers.lastMessageTime ) < ( g_spamTime.integer * 1000 ) && - !Q_stricmp( ent->client->pers.lastMessage, chatText) && - !G_admin_permission( ent, ADMF_NOCENSORFLOOD ) && - ent->client->pers.floodDemerits <= g_floodMaxDemerits.integer ) - { - trap_SendServerCommand( ent-g_entities, "print \"Your message has been ignored to prevent spam\n\"" ); - return; - } - else - { - ent->client->pers.lastMessageTime = level.time; - - Q_strncpyz( ent->client->pers.lastMessage, chatText, - sizeof( ent->client->pers.lastMessage ) ); - } - // Flood limit. If they're talking too fast, determine that and return. if( g_floodMinTime.integer ) if ( G_Flood_Limited( ent ) ) @@ -1057,7 +1068,7 @@ void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" ); return; } - + if (g_chatTeamPrefix.integer && ent && ent->client ) { switch( ent->client->pers.teamSelection) @@ -1158,16 +1169,6 @@ void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) Com_sprintf( text, sizeof( text ), "%s^7", chatText ); - if( ent && ent->client && g_aimbotAdvertBan.integer && ( Q_stricmp( text, "^1N^7ullify for ^1T^7remulous [beta] | Get it at CheatersUtopia.com^7" ) == 0 ) ) - { - trap_SendConsoleCommand( 0, - va( "!ban %s %s %s\n", - ent->client->pers.ip, - ( g_aimbotAdvertBanTime.string && Q_stricmp( g_aimbotAdvertBanTime.string, "0" ) == 1 ) ? g_aimbotAdvertBanTime.string : "" , - g_aimbotAdvertBanReason.string ) ); - Q_strncpyz( text, "^7has been caught hacking and will be dealt with.", sizeof( text ) ); - } - if( target ) { G_SayTo( ent, target, mode, color, name, text, prefix ); @@ -1215,11 +1216,14 @@ static void Cmd_SayArea_f( gentity_t *ent ) int num, i; int color = COLOR_BLUE; const char *prefix; - vec3_t range = { HELMET_RANGE, HELMET_RANGE, HELMET_RANGE }; + vec3_t range = { 1000.0f, 1000.0f, 1000.0f }; vec3_t mins, maxs; char *msg = ConcatArgs( 1 ); char name[ 64 ]; + for(i = 0; i < 3; i++ ) + range[ i ] = g_sayAreaRange.value; + if( g_floodMinTime.integer ) if ( G_Flood_Limited( ent ) ) { @@ -1373,6 +1377,31 @@ static void Cmd_Say_f( gentity_t *ent ) } } + args = G_SayConcatArgs(0); + if( !Q_stricmpn( args, "say /join", 9 ) ) + { + Cmd_Join_f( ent ); + return; + } + if( !Q_stricmpn( args, "say /part", 9 ) ) + { + Cmd_Part_f( ent ); + return; + } + if( !Q_stricmpn( args, "say /0", 6 ) || + !Q_stricmpn( args, "say /1", 6 ) || + !Q_stricmpn( args, "say /2", 6 ) || + !Q_stricmpn( args, "say /3", 6 ) || + !Q_stricmpn( args, "say /4", 6 ) || + !Q_stricmpn( args, "say /5", 6 ) || + !Q_stricmpn( args, "say /6", 6 ) || + !Q_stricmpn( args, "say /7", 6 ) || + !Q_stricmpn( args, "say /8", 6 ) || + !Q_stricmpn( args, "say /9", 6 ) ) + { + Cmd_Channel_f( ent ); + return; + } if( trap_Argc( ) < 2 ) return; @@ -1417,6 +1446,244 @@ 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 skipargs = 0; + 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; + } + + G_SayArgv( skipargs, arg, sizeof( arg ) ); + if( !Q_stricmp( arg, "say" ) ) + skipargs++; + + if( G_SayArgc( ) < 2 + skipargs ) + { + 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; + } + + G_SayArgv( 1 + skipargs, 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; + } + + pass[ 0 ] = '\0'; + if ( G_SayArgc( ) >= 2 + skipargs ) + G_SayArgv( 2 + skipargs, 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 skipargs = 0; + int chan; + int i; + gentity_t *target; + + G_SayArgv( skipargs, arg, sizeof( arg ) ); + if( !Q_stricmp( arg, "say" ) ) + skipargs++; + + if( G_SayArgc( ) < 2 + skipargs ) + { + 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; + } + + G_SayArgv( 1 + skipargs, 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 *cmd, *p; + char location[ 64 ]; + char escaped[ 64 ]; + int chan; + int i; + int num = 0; + int skipargs = 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; + } + + G_SayArgv( skipargs, arg, sizeof( arg ) ); + if( !Q_stricmp( arg, "say" ) ) + { + skipargs++; + G_SayArgv( skipargs, arg, sizeof( arg ) ); + } + cmd = arg; + if( *cmd == '/' ) + cmd++; + chan = atoi( cmd ); + 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\"", cmd ) ); + return; + } + + p = G_SayConcatArgs( 1 + skipargs ); + 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( "chat \"(^1#%d^7 %d) [%s^7]%s: ^1%s^7\"", + chan, num, ent->client->pers.netname, + ( OnSameTeam( target, ent ) ) ? escaped : "", + p ) ); + } + } +} + /* ================== Cmd_Where_f @@ -1427,6 +1694,24 @@ void Cmd_Where_f( gentity_t *ent ) trap_SendServerCommand( ent-g_entities, va( "print \"%s\n\"", vtos( ent->s.origin ) ) ); } +static int map_vote_percent( const char *map, int fallback ) +{ + char maps[ MAX_CVAR_VALUE_STRING ]; + char *token, *token_p; + + if( !g_popularMapsVotePercent.integer ) + return fallback; + + Q_strncpyz( maps, g_popularMaps.string, sizeof( maps ) ); + token_p = maps; + while( *( token = COM_Parse( &token_p ) ) ) + { + if( !Q_stricmp( token, map ) ) + return g_popularMapsVotePercent.integer; + } + + return fallback; +} static qboolean map_is_votable( const char *map ) { @@ -1469,7 +1754,7 @@ void Cmd_CallVote_f( gentity_t *ent ) arg1plus = G_SayConcatArgs( 1 ); arg2plus = G_SayConcatArgs( 2 ); - + // Invisible players cannot call votes if( ent->client->sess.invisible == qtrue ) { @@ -1483,6 +1768,12 @@ void Cmd_CallVote_f( gentity_t *ent ) return; } + if( G_admin_permission( ent, ADMF_NO_VOTE ) ) + { + trap_SendServerCommand( ent-g_entities, "print \"You have no voting rights\n\"" ); + return; + } + // Flood limit. If they're talking too fast, determine that and return. if( g_floodMinTime.integer ) if ( G_Flood_Limited( ent ) ) @@ -1491,13 +1782,6 @@ void Cmd_CallVote_f( gentity_t *ent ) return; } - //see if they can vote - if( G_admin_permission( ent, ADMF_NO_VOTE ) ) - { - trap_SendServerCommand( ent-g_entities, "print \"You have no voting rights\n\"" ); - return; - } - if( g_voteMinTime.integer && ent->client->pers.firstConnect && level.time - ent->client->pers.enterTime < g_voteMinTime.integer * 1000 @@ -1554,6 +1838,10 @@ void Cmd_CallVote_f( gentity_t *ent ) { G_admin_maplog_result( "m" ); } + else if( !Q_stricmpn( level.voteString, "!restart", 8 ) ) + { + G_admin_maplog_result( "l" ); + } level.voteExecuteTime = 0; trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.voteString ) ); @@ -1628,7 +1916,7 @@ void Cmd_CallVote_f( gentity_t *ent ) } if( clientNum != -1 && - level.clients[ clientNum ].pers.connected != CON_CONNECTED ) + level.clients[ clientNum ].pers.connected == CON_DISCONNECTED ) { clientNum = -1; } @@ -1659,6 +1947,8 @@ void Cmd_CallVote_f( gentity_t *ent ) if( !Q_stricmp( arg1, "kick" ) ) { + char n1[ MAX_NAME_LENGTH ]; + if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) { trap_SendServerCommand( ent-g_entities, @@ -1673,9 +1963,13 @@ void Cmd_CallVote_f( gentity_t *ent ) "!ban %s \"%s\" vote kick", level.clients[ clientNum ].pers.ip, g_adminTempBan.string ); if ( reason[0]!='\0' ) - Q_strcat( level.voteString, sizeof( level.voteDisplayString ), va( "(%s^7)", reason ) ); + Q_strcat( level.voteString, sizeof( level.voteString ), va( ": %s^7", reason ) ); + G_SanitiseString( ent->client->pers.netname, n1, sizeof( n1 ) ); + Q_strcat( level.voteString, sizeof( level.voteString ), va( ", %s", n1 ) ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "Kick player \'%s\'", name ); + + level.votePassThreshold = g_kickVotesPercent.integer; } else if( !Q_stricmp( arg1, "spec" ) ) { @@ -1772,7 +2066,7 @@ void Cmd_CallVote_f( gentity_t *ent ) Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s", arg1, arg2 ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "Change to map '%s'", arg2 ); - level.votePassThreshold = g_mapVotesPercent.integer; + level.votePassThreshold = map_vote_percent( arg2, g_mapVotesPercent.integer ); } else if( !Q_stricmp( arg1, "nextmap" ) ) { @@ -1808,7 +2102,7 @@ void Cmd_CallVote_f( gentity_t *ent ) "set g_nextMap %s", arg2 ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "Set the next map to '%s^7'", arg2 ); - level.votePassThreshold = g_mapVotesPercent.integer; + level.votePassThreshold = map_vote_percent( arg2, g_mapVotesPercent.integer ); } else if( !Q_stricmp( arg1, "draw" ) ) { @@ -1817,16 +2111,53 @@ void Cmd_CallVote_f( gentity_t *ent ) "End match in a draw" ); level.votePassThreshold = g_mapVotesPercent.integer; } + else if( !Q_stricmp( arg1, "layout" ) ) + { + char map[ 64 ]; + + if( g_mapvoteMaxTime.integer + && (( level.time - level.startTime ) >= g_mapvoteMaxTime.integer * 1000 ) + && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) + && (level.numPlayingClients > 0 && level.numConnectedClients>1) ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"You cannot call for a layout change after %d seconds\n\"", + g_mapvoteMaxTime.integer ) ); + G_admin_adminlog_log( ent, "vote", NULL, 0, qfalse ); + return; + } + + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + if( Q_stricmp( arg2, "*BUILTIN*" ) && + !trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, arg2 ), NULL, FS_READ ) ) + { + trap_SendServerCommand( ent - g_entities, va( "print \"callvote: " + "layout '%s' could not be found on the server\n\"", arg2 ) ); + return; + } + Com_sprintf( level.voteString, sizeof( level.voteString ), "!restart %s", arg2 ); + Com_sprintf( level.voteDisplayString, + sizeof( level.voteDisplayString ), "Change to map layout '%s'", arg2 ); + level.votePassThreshold = g_mapVotesPercent.integer; + } else if( !Q_stricmp( arg1, "poll" ) ) { - if( arg2plus[ 0 ] == '\0' ) + if(!g_pollVotes.integer) + { + trap_SendServerCommand( ent-g_entities, "print \"Poll Votes have been disabled\n\"" ); + return; + } + else if( arg2plus[ 0 ] == '\0' ) { trap_SendServerCommand( ent-g_entities, "print \"callvote: You forgot to specify what people should vote on.\n\"" ); return; } - Com_sprintf( level.voteString, sizeof( level.voteString ), nullstring); - Com_sprintf( level.voteDisplayString, - sizeof( level.voteDisplayString ), "[Poll] \'%s\'", arg2plus ); + else + { + Com_sprintf( level.voteString, sizeof( level.voteString ), nullstring); + Com_sprintf( level.voteDisplayString, + sizeof( level.voteDisplayString ), "[Poll] \'%s" S_COLOR_WHITE "\'", arg2plus ); + } } else if( !Q_stricmp( arg1, "sudden_death" ) || !Q_stricmp( arg1, "suddendeath" ) ) @@ -1858,41 +2189,36 @@ void Cmd_CallVote_f( gentity_t *ent ) } } - else if( !Q_stricmp( arg1, "extend" ) ) - { - if( !g_extendVotesPercent.integer ) - { - trap_SendServerCommand( ent-g_entities, "print \"Extend votes have been disabled\n\"" ); - return; - } - if( g_extendVotesCount.integer - && level.extend_vote_count >= g_extendVotesCount.integer ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"callvote: Maximum number of %d extend votes has been reached\n\"", - g_extendVotesCount.integer ) ); - return; - } - if( !g_timelimit.integer ) { - trap_SendServerCommand( ent-g_entities, - "print \"This match has no timelimit so extend votes wont work\n\"" ); - return; - } - if( level.time - level.startTime < - ( g_timelimit.integer - g_extendVotesTime.integer / 2 ) * 60000 ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"callvote: Extend votes only allowed with less than %d minutes remaining\n\"", - g_extendVotesTime.integer / 2 ) ); - return; - } - level.extend_vote_count++; - level.votePassThreshold = g_extendVotesPercent.integer; - Com_sprintf( level.voteString, sizeof( level.voteString ), - "timelimit %i", g_timelimit.integer + g_extendVotesTime.integer ); - Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), - "Extend the timelimit by %d minutes", g_extendVotesTime.integer ); - } + else if( !Q_stricmp( arg1, "extend" ) ) + { + if( !g_extendVotesPercent.integer ) + { + trap_SendServerCommand( ent-g_entities, "print \"Extend votes have been disabled\n\"" ); + return; + } + if( g_extendVotesCount.integer + && level.extend_vote_count >= g_extendVotesCount.integer ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"callvote: Maximum number of %d extend votes has been reached\n\"", + g_extendVotesCount.integer ) ); + return; + } + if( level.time - level.startTime < + ( g_timelimit.integer - g_extendVotesTime.integer / 2 ) * 60000 ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"callvote: Extend votes only allowed with less than %d minutes remaining\n\"", + g_extendVotesTime.integer / 2 ) ); + return; + } + level.extend_vote_count++; + level.votePassThreshold = g_extendVotesPercent.integer; + Com_sprintf( level.voteString, sizeof( level.voteString ), + "timelimit %i", g_timelimit.integer + g_extendVotesTime.integer ); + Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), + "Extend the timelimit by %d minutes", g_extendVotesTime.integer ); + } else { qboolean match = qfalse; @@ -1976,13 +2302,13 @@ void Cmd_CallVote_f( gentity_t *ent ) match = qtrue; break; } - } + } if( !match ) { trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); trap_SendServerCommand( ent-g_entities, "print \"Valid vote commands are: " - "map, map_restart, draw, extend, nextmap, kick, spec, mute, unmute, poll, and sudden_death\n" ); + "map, map_restart, nextmap, layout, draw, extend, kick, spec, mute, unmute, poll, and sudden_death\n" ); if( customVoteKeys[ 0 ] != '\0' ) trap_SendServerCommand( ent-g_entities, va( "print \"Additional custom vote commands: %s\n\"", customVoteKeys ) ); @@ -1992,7 +2318,7 @@ void Cmd_CallVote_f( gentity_t *ent ) if( level.votePassThreshold!=50 ) { - Q_strcat( level.voteDisplayString, sizeof( level.voteDisplayString ), va( "^7 (Needs > %d percent)", level.votePassThreshold ) ); + Q_strcat( level.voteDisplayString, sizeof( level.voteDisplayString ), va( " (Needs > %d percent)", level.votePassThreshold ) ); } if ( reason[0]!='\0' ) @@ -2002,6 +2328,7 @@ void Cmd_CallVote_f( gentity_t *ent ) trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " called a vote: %s" S_COLOR_WHITE "\n\"", ent->client->pers.netname, level.voteDisplayString ) ); + trap_SendServerCommand( -1, "cp \"A vote has been called\n^2F1: Yes^7, ^1F2: No^7\"" ); G_LogPrintf("Vote: %s^7 called a vote: %s^7\n", ent->client->pers.netname, level.voteDisplayString ); @@ -2138,22 +2465,21 @@ void Cmd_CallTeamVote_f( gentity_t *ent ) else if(team==PTE_HUMANS) numVoters = level.numHumanClients; - if( !g_allowVote.integer ) + if( !g_allowVote.integer || g_allowVote.integer == 2 ) { - trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here\n\"" ); + trap_SendServerCommand( ent-g_entities, "print \"Team voting not allowed here\n\"" ); return; } - if( level.teamVoteTime[ cs_offset ] ) + if( G_admin_permission( ent, ADMF_NO_VOTE ) ) { - trap_SendServerCommand( ent-g_entities, "print \"A team vote is already in progress\n\"" ); + trap_SendServerCommand( ent-g_entities, "print \"You have no voting rights\n\"" ); return; } - //see if they can vote - if( G_admin_permission( ent, ADMF_NO_VOTE ) ) + if( level.teamVoteTime[ cs_offset ] ) { - trap_SendServerCommand( ent-g_entities, "print \"You have no voting rights\n\"" ); + trap_SendServerCommand( ent-g_entities, "print \"A team vote is already in progress\n\"" ); return; } @@ -2300,8 +2626,12 @@ void Cmd_CallTeamVote_f( gentity_t *ent ) } } + level.teamVotePassThreshold[ cs_offset ] = 50; + if( !Q_stricmp( arg1, "kick" ) ) { + char n1[ MAX_NAME_LENGTH ]; + if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) { trap_SendServerCommand( ent-g_entities, @@ -2311,15 +2641,28 @@ void Cmd_CallTeamVote_f( gentity_t *ent ) return; } + if( g_allowVote.integer == 3 ) + { + trap_SendServerCommand( ent-g_entities, "print \"Team kick votes are disabled\n\"" ); + return; + } // use ip in case this player disconnects before the vote ends Com_sprintf( level.teamVoteString[ cs_offset ], sizeof( level.teamVoteString[ cs_offset ] ), "!ban %s \"%s\" team vote kick", level.clients[ clientNum ].pers.ip, g_adminTempBan.string ); + if( reason[0] ) + Q_strcat( level.teamVoteString[ cs_offset ], sizeof( level.teamVoteString[ cs_offset ] ), + va( ": %s", reason ) ); + G_SanitiseString( ent->client->pers.netname, n1, sizeof( n1 ) ); + Q_strcat( level.teamVoteString[ cs_offset ], sizeof( level.teamVoteString[ cs_offset ] ), + va( ", %s", n1 ) ); Com_sprintf( level.teamVoteDisplayString[ cs_offset ], sizeof( level.teamVoteDisplayString[ cs_offset ] ), "Kick player '%s'", name ); + + level.teamVotePassThreshold[ cs_offset ] = g_kickVotesPercent.integer; } else if( !Q_stricmp( arg1, "denybuild" ) ) { @@ -2405,6 +2748,16 @@ void Cmd_CallTeamVote_f( gentity_t *ent ) } else if( !Q_stricmp( arg1, "admitdefeat" ) ) { + if( g_defeatVoteMinTime.integer && + level.time - level.startTime < abs( g_defeatVoteMinTime.integer ) * 1000 && + ( !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) || g_defeatVoteMinTime.integer < 0 ) ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"You cannot admit defeat until after %d seconds\n\"", + g_defeatVoteMinTime.integer ) ); + G_admin_adminlog_log( ent, "vote", NULL, 0, qfalse ); + return; + } if( numVoters <=1 ) { trap_SendServerCommand( ent-g_entities, @@ -2440,6 +2793,12 @@ void Cmd_CallTeamVote_f( gentity_t *ent ) ent->client->pers.voteCount++; G_admin_adminlog_log( ent, "teamvote", arg1, 0, qtrue ); + + if( level.teamVotePassThreshold[ cs_offset ] != 50 ) + { + Q_strcat( level.teamVoteDisplayString[ cs_offset ], sizeof( level.teamVoteDisplayString[ cs_offset ] ), + va( " (Needs > %d percent)", level.teamVotePassThreshold[ cs_offset ] ) ); + } if ( reason[0]!='\0' ) Q_strcat( level.teamVoteDisplayString[ cs_offset ], sizeof( level.teamVoteDisplayString[ cs_offset ] ), va( " Reason: '%s'^7", reason ) ); @@ -2453,6 +2812,7 @@ void Cmd_CallTeamVote_f( gentity_t *ent ) { trap_SendServerCommand( i, va("print \"%s " S_COLOR_WHITE "called a team vote: %s^7 \n\"", ent->client->pers.netname, level.teamVoteDisplayString[ cs_offset ] ) ); + trap_SendServerCommand( i, "cp \"A team vote has been called\n^2F1: Yes^7, ^1F2: No^7\"" ); } else if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) && ( ( !Q_stricmp( arg1, "kick" ) || !Q_stricmp( arg1, "denybuild" ) ) || @@ -2656,7 +3016,6 @@ void Cmd_Class_f( gentity_t *ent ) vec3_t mins, maxs; int num; gentity_t *other; - qboolean humanNear = qfalse; clientNum = ent->client - level.clients; @@ -2779,21 +3138,11 @@ void Cmd_Class_f( gentity_t *ent ) if( ( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) || ( other->s.eType == ET_BUILDABLE && other->biteam == BIT_HUMANS ) ) { - humanNear = qtrue; - } - //If its the OM, then ignore all humans. - if(other->s.eType == ET_BUILDABLE && other->s.modelindex == BA_A_OVERMIND) - { - humanNear = qfalse; - break; + G_TriggerMenu( clientNum, MN_A_TOOCLOSE ); + return; } } - if(humanNear == qtrue) { - G_TriggerMenu( clientNum, MN_A_TOOCLOSE ); - return; - } - if( !level.overmindPresent ) { G_TriggerMenu( clientNum, MN_A_NOOVMND_EVOLVE ); @@ -2902,19 +3251,33 @@ DBCommand Send command to all designated builders of selected team ================= */ -void DBCommand( pTeam_t team, const char *text ) +void DBCommand( gentity_t *builder, pTeam_t team, const char *text ) { int i; gentity_t *ent; + if( g_floodMinTime.integer && G_Flood_Limited( builder ) ) + { + trap_SendServerCommand( builder-g_entities, + "print \"Your deconstruct attempt is flood-limited; wait before trying again\n\"" ); + return; + } + + trap_SendServerCommand( builder-g_entities, + "print \"This structure is protected by designated builder\n\"" ); + for( i = 0, ent = g_entities + i; i < level.maxclients; i++, ent++ ) { - if( !ent->client || ( ent->client->pers.connected != CON_CONNECTED ) || - ( ent->client->pers.teamSelection != team ) || - !ent->client->pers.designatedBuilder ) + if( !ent->client || ent->client->pers.connected != CON_CONNECTED ) continue; - trap_SendServerCommand( i, text ); + if( ( ent->client->pers.teamSelection == team && + ent->client->pers.designatedBuilder ) || + ( ent->client->pers.teamSelection == PTE_NONE && + G_admin_permission( ent, ADMF_SPEC_ALLCHAT ) ) ) + { + trap_SendServerCommand( i, text ); + } } } @@ -2937,6 +3300,23 @@ void Cmd_Destroy_f( gentity_t *ent ) "print \"Your building rights have been revoked\n\"" ); return; } + if( ent->client->pers.paused ) + { + trap_SendServerCommand( ent-g_entities, + "print \"You may not deconstruct while paused\n\"" ); + return; + } + + if( g_newbieNoBuild.integer + && g_newbieNamePrefix.string[ 0 ] + && Q_stricmpn ( ent->client->pers.netname, g_newbieNamePrefix.string, strlen(g_newbieNamePrefix.string ) ) == 0 ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"You cannot build until you set a name\n%s%s\"", + ( g_newbieNoBuildMessage.string[ 0 ] ) ? g_newbieNoBuildMessage.string : "", + ( g_newbieNoBuildMessage.string[ 0 ] ) ? "\n" : "" ) ); + return; + } trap_Argv( 0, cmd, sizeof( cmd ) ); if( Q_stricmp( cmd, "destroy" ) == 0 ) @@ -2947,11 +3327,10 @@ void Cmd_Destroy_f( gentity_t *ent ) if( ( ent->client->hovel->s.eFlags & EF_DBUILDER ) && !ent->client->pers.designatedBuilder ) { - trap_SendServerCommand( ent-g_entities, - "print \"This structure is protected by designated builder\n\"" ); - DBCommand( ent->client->pers.teamSelection, - va( "print \"%s^3 has attempted to decon a protected structure!\n\"", - ent->client->pers.netname ) ); + DBCommand( ent, ent->client->pers.teamSelection, + va( "print \"%s^3 has attempted to decon a protected %s!\n\"", + ent->client->pers.netname, + BG_FindHumanNameForBuildable( ent->client->hovel->s.modelindex ) ) ); return; } G_Damage( ent->client->hovel, ent, ent, forward, ent->s.origin, @@ -2981,21 +3360,13 @@ void Cmd_Destroy_f( gentity_t *ent ) if( ( traceEnt->s.eFlags & EF_DBUILDER ) && !ent->client->pers.designatedBuilder ) { - trap_SendServerCommand( ent-g_entities, - "print \"This structure is protected by designated builder\n\"" ); - DBCommand( ent->client->pers.teamSelection, - va( "print \"%s^3 has attempted to decon a protected structure!\n\"", - ent->client->pers.netname ) ); + DBCommand( ent, ent->client->pers.teamSelection, + va( "print \"%s^3 has attempted to decon a protected %s!\n\"", + ent->client->pers.netname, + BG_FindHumanNameForBuildable( traceEnt->s.modelindex ) ) ); return; } - // Check the minimum level to deconstruct - if ( G_admin_level( ent ) < g_minDeconLevel.integer && !ent->client->pers.designatedBuilder ) - { - trap_SendServerCommand( ent-g_entities, - "print \"You do not have deconstructuction rights.\n\"" ); - return; - } // Prevent destruction of the last spawn if( g_markDeconstruct.integer != 1 && !g_cheats.integer ) @@ -3024,8 +3395,7 @@ void Cmd_Destroy_f( gentity_t *ent ) !BG_FindReplaceableTestForBuildable( traceEnt->s.modelindex ) ) || ( g_suddenDeathMode.integer == SDMODE_BP && BG_FindBuildPointsForBuildable( traceEnt->s.modelindex ) ) || - g_suddenDeathMode.integer == SDMODE_NO_BUILD || - g_suddenDeathMode.integer == SDMODE_NO_DECON ) ) + g_suddenDeathMode.integer == SDMODE_NO_BUILD ) ) { trap_SendServerCommand( ent-g_entities, "print \"During Sudden Death you can only decon buildings that " @@ -3055,6 +3425,7 @@ void Cmd_Destroy_f( gentity_t *ent ) new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; + new->time = level.time; new->ent = ent; new->name[ 0 ] = 0; new->buildable = traceEnt->s.modelindex; @@ -3078,6 +3449,8 @@ void Cmd_Destroy_f( gentity_t *ent ) ent->client->pers.netname, BG_FindNameForBuildable( traceEnt->s.modelindex ) ); } + else if( deconstruct ) + G_RecoverBuildPoints( traceEnt ); if( !deconstruct ) G_Damage( traceEnt, ent, ent, forward, tr.endpos, 10000, 0, MOD_SUICIDE ); @@ -3112,12 +3485,21 @@ void Cmd_Mark_f( gentity_t *ent ) "print \"Your building rights have been revoked\n\"" ); return; } + if( ent->client->pers.paused ) + { + trap_SendServerCommand( ent-g_entities, + "print \"You may not mark while paused\n\"" ); + return; + } - // Check the minimum level to deconstruct - if ( G_admin_level( ent ) < g_minDeconLevel.integer && !ent->client->pers.designatedBuilder && g_minDeconAffectsMark.integer > 0 ) + if( g_newbieNoBuild.integer + && g_newbieNamePrefix.string[ 0 ] + && Q_stricmpn ( ent->client->pers.netname, g_newbieNamePrefix.string, strlen(g_newbieNamePrefix.string ) ) == 0 ) { trap_SendServerCommand( ent-g_entities, - "print \"You do not have deconstructuction rights.\n\"" ); + va( "print \"You cannot build until you set a name\n%s%s\"", + ( g_newbieNoBuildMessage.string[ 0 ] ) ? g_newbieNoBuildMessage.string : "", + ( g_newbieNoBuildMessage.string[ 0 ] ) ? "\n" : "" ) ); return; } @@ -3164,8 +3546,7 @@ void Cmd_Mark_f( gentity_t *ent ) !BG_FindReplaceableTestForBuildable( traceEnt->s.modelindex ) ) || ( g_suddenDeathMode.integer == SDMODE_BP && BG_FindBuildPointsForBuildable( traceEnt->s.modelindex ) ) || - g_suddenDeathMode.integer == SDMODE_NO_BUILD || - g_suddenDeathMode.integer == SDMODE_NO_DECON ) ) + g_suddenDeathMode.integer == SDMODE_NO_BUILD ) ) { trap_SendServerCommand( ent-g_entities, "print \"During Sudden Death you can only mark buildings that " @@ -3186,6 +3567,65 @@ void Cmd_Mark_f( gentity_t *ent ) } } +void Cmd_Aim_f( gentity_t *ent ) +{ + vec3_t forward, end; + trace_t tr; + gentity_t *traceEnt; + + if( !g_turretAim.integer ) + { + trap_SendServerCommand( ent-g_entities, + "print \"Turret aim is disabled\n\"" ); + return; + } + + if( ent->client->pers.denyBuild || + ent->client->pers.paused ) + return; + + if( g_newbieNoBuild.integer && + g_newbieNamePrefix.string[ 0 ] && + !Q_stricmpn ( ent->client->pers.netname, g_newbieNamePrefix.string, strlen(g_newbieNamePrefix.string ) ) ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"You cannot use /aim until you set a name\n%s%s\"", + ( g_newbieNoBuildMessage.string[ 0 ] ) ? g_newbieNoBuildMessage.string : "", + ( g_newbieNoBuildMessage.string[ 0 ] ) ? "\n" : "" ) ); + return; + } + + if( !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) ) + { + AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); + VectorMA( ent->client->ps.origin, 100, forward, end ); + + trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); + traceEnt = &g_entities[ tr.entityNum ]; + + if( tr.fraction < 1.0f && + ( traceEnt->s.eType == ET_BUILDABLE ) && + ( traceEnt->s.modelindex == BA_H_MGTURRET ) && + ( traceEnt->biteam == ent->client->pers.teamSelection ) && + ( ( ent->client->ps.weapon >= WP_ABUILD ) && + ( ent->client->ps.weapon <= WP_HBUILD ) ) ) + { + if( traceEnt->rotatorAngle >= 360.0f ) + { + traceEnt->rotatorAngle = 0.0f; + trap_SendServerCommand( ent-g_entities, "print \"Turret aim direction disabled\n\"" ); + } + else + { + traceEnt->rotatorAngle = ent->client->ps.viewangles[ YAW ]; + while( traceEnt->rotatorAngle < 360.0f ) + traceEnt->rotatorAngle += 360.0f; + trap_SendServerCommand( ent-g_entities, "print \"Turret aim direction enabled\n\"" ); + } + } + } +} + /* ================= @@ -3507,12 +3947,6 @@ void Cmd_Buy_f( gentity_t *ent ) if( !Q_stricmp( s, "retrigger" ) ) ent->client->retriggerArmouryMenu = level.framenum + RAM_FRAMES; } - if( ent->client->pers.paused ) - { - trap_SendServerCommand( ent-g_entities, - "print \"You may not deconstruct while paused\n\"" ); - return; - } //update ClientInfo ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); @@ -3688,7 +4122,18 @@ void Cmd_Build_f( gentity_t *ent ) if( ent->client->pers.paused ) { trap_SendServerCommand( ent-g_entities, - "print \"You may not mark while paused\n\"" ); + "print \"You may not build while paused\n\"" ); + return; + } + + if( g_newbieNoBuild.integer + && g_newbieNamePrefix.string[ 0 ] + && Q_stricmpn ( ent->client->pers.netname, g_newbieNamePrefix.string, strlen(g_newbieNamePrefix.string ) ) == 0 ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"You cannot build until you set a name\n%s%s\"", + ( g_newbieNoBuildMessage.string[ 0 ] ) ? g_newbieNoBuildMessage.string : "", + ( g_newbieNoBuildMessage.string[ 0 ] ) ? "\n" : "" ) ); return; } @@ -3722,6 +4167,13 @@ void Cmd_Build_f( gentity_t *ent ) } } + if( g_vampireDeath.integer ) + { + trap_SendServerCommand( ent-g_entities, + "print \"Building is not allowed during Vampire Sudden Death\n\"" ); + return; + } + team = ent->client->ps.stats[ STAT_PTEAM ]; if( buildable != BA_NONE && @@ -4005,113 +4457,6 @@ char *G_statsString( statsCounters_t *sc, pTeam_t *pt ) return s; } - /* - ================= - Cmd_AllStats_f - ================= - */ - void Cmd_AllStats_f( gentity_t *ent ) - { - int i; - int NextViewTime; - int NumResults = 0; - int Teamcolor = 3; - gentity_t *tmpent; - - //check if ent exists - if(!ent) return; - - NextViewTime = ent->client->pers.statscounters.AllstatstimeLastViewed + g_AllStatsTime.integer * 1000; - //check if you can use the cmd at this time - if( !level.intermissiontime && level.time < NextViewTime) - { - ADMP( va("You may only check your stats every %i Seconds and during intermission. Next valid time is %d:%02d\n",( g_AllStatsTime.integer ) ? ( g_AllStatsTime.integer ) : 60, ( NextViewTime / 60000 ), ( NextViewTime / 1000 ) % 60 ) ); - return; - } - //see if allstats is enabled - if( !g_AllStats.integer ) - { - ADMP( "AllStats has been disabled\n"); - return; - } - ADMP("^3K^2=^7Kills ^3A^2=^7Assists ^3SK^2=^7StructKills\n^3D^2=^7Deaths ^3F^2=^7Feeds ^3S^2=^7Suicides ^3TK^2=^7Teamkills\n^3DD^2=^7Damage done ^3TDD^2=^7Team Damage done\n^3SB^2=^7Structs Built\n\n" ); - //display a header describing the data - ADMP( "^3 #| K A SK| D F S TK| DD TDD| SB| Name\n" ); - - //loop through the clients that are connected - for( i = 0; i < level.numConnectedClients; i++ ) - { - //assign a tempent 4 the hell of it - tmpent = &g_entities[ level.sortedClients[ i ] ]; - - //check for what mode we are working in and display relevent data - if( g_AllStats.integer == 1 ) - { - //check if client is connected and on same team - if( tmpent->client && tmpent->client->pers.connected == CON_CONNECTED && tmpent->client->pers.teamSelection == ent->client->pers.teamSelection && tmpent->client->pers.teamSelection != PTE_NONE ) - { - NumResults++; - if( tmpent->client->pers.teamSelection == PTE_ALIENS ) Teamcolor = 1; - if( tmpent->client->pers.teamSelection == PTE_HUMANS ) Teamcolor = 5; - ADMP( va( "^%i%2i^3|^%i%3i %3i %3i^3|^%i%3i %3i %3i %3i^3|^%i%5i %5i^3|^%i%3i^3|^7 %s\n", - Teamcolor, - NumResults, - Teamcolor, - ( tmpent->client->pers.statscounters.kills ) ? tmpent->client->pers.statscounters.kills : 0, - ( tmpent->client->pers.statscounters.assists ) ? tmpent->client->pers.statscounters.assists : 0, - ( tmpent->client->pers.statscounters.structskilled ) ? tmpent->client->pers.statscounters.structskilled : 0, - Teamcolor, - ( tmpent->client->pers.statscounters.deaths ) ? tmpent->client->pers.statscounters.deaths : 0, - ( tmpent->client->pers.statscounters.feeds ) ? tmpent->client->pers.statscounters.feeds : 0, - ( tmpent->client->pers.statscounters.suicides ) ? tmpent->client->pers.statscounters.suicides : 0, - ( tmpent->client->pers.statscounters.teamkills ) ? tmpent->client->pers.statscounters.teamkills : 0, - Teamcolor, - ( tmpent->client->pers.statscounters.dmgdone ) ? tmpent->client->pers.statscounters.dmgdone : 0, - ( tmpent->client->pers.statscounters.ffdmgdone ) ? tmpent->client->pers.statscounters.ffdmgdone : 0, - Teamcolor, - ( tmpent->client->pers.statscounters.structsbuilt ) ? tmpent->client->pers.statscounters.structsbuilt : 0, - ( tmpent->client->pers.netname ) ? tmpent->client->pers.netname : "Unknown" ) ); - } - } - else if( g_AllStats.integer == 2 ) - { - //check if client is connected and has some stats or atleast is on a team - if( tmpent->client && tmpent->client->pers.connected == CON_CONNECTED && ( tmpent->client->pers.teamSelection != PTE_NONE ) ) - { - NumResults++; - if( tmpent->client->pers.teamSelection == PTE_ALIENS ) Teamcolor = 1; - if( tmpent->client->pers.teamSelection == PTE_HUMANS ) Teamcolor = 5; - ADMP( va( "^%i%2i^3|^%i%3i %3i %3i^3|^%i%3i %3i %3i %3i^3|^%i%5i %5i^3|^%i%3i^3|^7 %s\n", - Teamcolor, - NumResults, - Teamcolor, - ( tmpent->client->pers.statscounters.kills ) ? tmpent->client->pers.statscounters.kills : 0, - ( tmpent->client->pers.statscounters.assists ) ? tmpent->client->pers.statscounters.assists : 0, - ( tmpent->client->pers.statscounters.structskilled ) ? tmpent->client->pers.statscounters.structskilled : 0, - Teamcolor, - ( tmpent->client->pers.statscounters.deaths ) ? tmpent->client->pers.statscounters.deaths : 0, - ( tmpent->client->pers.statscounters.feeds ) ? tmpent->client->pers.statscounters.feeds : 0, - ( tmpent->client->pers.statscounters.suicides ) ? tmpent->client->pers.statscounters.suicides : 0, - ( tmpent->client->pers.statscounters.teamkills ) ? tmpent->client->pers.statscounters.teamkills : 0, - Teamcolor, - ( tmpent->client->pers.statscounters.dmgdone ) ? tmpent->client->pers.statscounters.dmgdone : 0, - ( tmpent->client->pers.statscounters.ffdmgdone ) ? tmpent->client->pers.statscounters.ffdmgdone : 0, - Teamcolor, - ( tmpent->client->pers.statscounters.structsbuilt ) ? tmpent->client->pers.statscounters.structsbuilt : 0, - ( tmpent->client->pers.netname ) ? tmpent->client->pers.netname : "Unknown" ) ); - } - } - } - if( NumResults == 0 ) { - ADMP( " ^3EMPTY!\n" ); - } else { - ADMP( va( "^7 %i Players found!\n", NumResults ) ); - } - //update time last viewed - - ent->client->pers.statscounters.AllstatstimeLastViewed = level.time; - return; -} /* ================= @@ -4412,12 +4757,6 @@ void Cmd_Follow_f( gentity_t *ent ) trap_SendServerCommand( ent - g_entities, "print \"follow: You cannot follow unless you are dead or on the spectators.\n\"" ); return; } - if( ent->client->pers.paused ) - { - trap_SendServerCommand( ent-g_entities, - "print \"You may not build while paused\n\"" ); - return; - } if( trap_Argc( ) != 2 ) { @@ -4569,30 +4908,8 @@ void Cmd_PTRCRestore_f( gentity_t *ent ) connection = ent->client->pers.connection; if( connection && connection->ptrCode == code ) { - // Set the correct team - if( !( ent->client->pers.specExpires > level.time ) ) - { - // Check if the alien team is full - if( connection->clientTeam == PTE_ALIENS && - !G_admin_permission(ent, ADMF_FORCETEAMCHANGE) && - g_teamForceBalance.integer && - level.numAlienClients > level.numHumanClients ) - { - G_TriggerMenu( ent - g_entities, MN_A_TEAMFULL ); - } - // Check if the human team is full - else if( connection->clientTeam == PTE_HUMANS && - !G_admin_permission(ent, ADMF_FORCETEAMCHANGE) && - g_teamForceBalance.integer && - level.numHumanClients > level.numAlienClients ) - { - G_TriggerMenu( ent - g_entities, MN_H_TEAMFULL ); - } - else - { - G_ChangeTeam( ent, connection->clientTeam ); - } - } + // set the correct team + G_ChangeTeam( ent, connection->clientTeam ); // set the correct credit etc. ent->client->ps.persistant[ PERS_CREDIT ] = 0; @@ -4688,39 +5005,39 @@ static void Cmd_Ignore_f( gentity_t *ent ) char arg1[ MAX_STRING_TOKENS ]; char arg2[ MAX_STRING_TOKENS ]; pTeam_t team; - + if( !ent || !ent->client || ( ent->client->pers.teamSelection == PTE_NONE ) ) { return; } - + if( !g_allowShare.integer ) { trap_SendServerCommand( ent-g_entities, "print \"Share has been disabled.\n\"" ); return; } - + if( g_floodMinTime.integer ) if ( G_Flood_Limited( ent ) ) { trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" ); return; } - + team = ent->client->pers.teamSelection; - + G_SayArgv( 0, cmd, sizeof( cmd ) ); if( !Q_stricmp( cmd, "say" ) || !Q_stricmp( cmd, "say_team" ) ) { skipargs = 1; G_SayArgv( 1, cmd, sizeof( cmd ) ); } - + // target player name is in arg1 G_SayArgv( 1+skipargs, arg1, sizeof( arg1 ) ); // amount to be shared is in arg2 G_SayArgv( 2+skipargs, arg2, sizeof( arg2 ) ); - + if( arg1[0] && !strchr( arg1, ';' ) && Q_stricmp( arg1, "target_in_aim" ) ) { //check arg1 is a number @@ -4732,7 +5049,7 @@ static void Cmd_Ignore_f( gentity_t *ent ) break; } } - + if( clientNum >= 0 ) { clientNum = atoi( arg1 ); @@ -4759,14 +5076,15 @@ static void Cmd_Ignore_f( gentity_t *ent ) vec3_t forward, end; trace_t tr; gentity_t *traceEnt; - + + // trace a teammate AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); VectorMA( ent->client->ps.origin, 8192 * 16, forward, end ); - + trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); traceEnt = &g_entities[ tr.entityNum ]; - + if( tr.fraction < 1.0f && traceEnt->client && ( traceEnt->client->pers.teamSelection == team ) ) { @@ -4780,7 +5098,7 @@ static void Cmd_Ignore_f( gentity_t *ent ) return; } } - + // verify target player team if( ( clientNum < 0 ) || ( clientNum >= level.maxclients ) || ( level.clients[ clientNum ].pers.teamSelection != team ) ) @@ -4789,7 +5107,7 @@ static void Cmd_Ignore_f( gentity_t *ent ) "print \"share: not a valid player of your team.\n\"" ); return; } - + if( !arg2[0] || strchr( arg2, ';' ) ) { // default credit count @@ -4814,11 +5132,11 @@ static void Cmd_Ignore_f( gentity_t *ent ) return; } } - + // credit count from parameter creds = atoi( arg2 ); } - + // player specified "0" to transfer if( creds <= 0 ) { @@ -4826,13 +5144,13 @@ static void Cmd_Ignore_f( gentity_t *ent ) "print \"Ooh, you are a generous one, indeed!\n\"" ); return; } - + // transfer only credits the player really has if( creds > ent->client->pers.credit ) { creds = ent->client->pers.credit; } - + // player has no credits if( creds <= 0 ) { @@ -4840,7 +5158,7 @@ static void Cmd_Ignore_f( gentity_t *ent ) "print \"Earn some first, lazy gal!\n\"" ); return; } - + // allow transfers only up to the credit/evo limit if( ( team == PTE_HUMANS ) && ( creds > HUMAN_MAX_CREDITS - level.clients[ clientNum ].pers.credit ) ) @@ -4852,7 +5170,7 @@ static void Cmd_Ignore_f( gentity_t *ent ) { creds = ALIEN_MAX_KILLS - level.clients[ clientNum ].pers.credit; } - + // target cannot take any more credits if( creds <= 0 ) { @@ -4861,7 +5179,7 @@ static void Cmd_Ignore_f( gentity_t *ent ) ( team == PTE_HUMANS ) ? "credits" : "evolvepoints" ) ); return; } - + // transfer credits G_AddCreditToClient( ent->client, -creds, qfalse ); trap_SendServerCommand( ent-g_entities, @@ -4873,7 +5191,7 @@ static void Cmd_Ignore_f( gentity_t *ent ) va( "print \"You have received %d %s from %s^7.\n\"", creds, ( team == PTE_HUMANS ) ? "credits" : "evolvepoints", ent->client->pers.netname ) ); - + G_LogPrintf( "Share: %i %i %i %d: %s^7 transferred %d%s to %s^7\n", ent->client->ps.clientNum, clientNum, @@ -4884,35 +5202,36 @@ static void Cmd_Ignore_f( gentity_t *ent ) ( team == PTE_HUMANS ) ? "c" : "e", level.clients[ clientNum ].pers.netname ); } - + /* ================= Cmd_Donate_f - + Alms for the poor ================= */ void Cmd_Donate_f( gentity_t *ent ) { char s[ MAX_TOKEN_CHARS ] = "", *type = "evo(s)"; int i, value, divisor, portion, new_credits, total=0, - max = ALIEN_MAX_KILLS, *amounts, *totals; + max = ALIEN_MAX_KILLS, *amounts; qboolean donated = qtrue; - + if( !ent->client ) return; - + if( !g_allowShare.integer ) { trap_SendServerCommand( ent-g_entities, "print \"Donate has been disabled.\n\"" ); return; } - + if( g_floodMinTime.integer ) if ( G_Flood_Limited( ent ) ) { trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" ); return; } - + + if( ent->client->pers.teamSelection == PTE_ALIENS ) divisor = level.numAlienClients-1; else if( ent->client->pers.teamSelection == PTE_HUMANS ) { @@ -4924,13 +5243,13 @@ static void Cmd_Ignore_f( gentity_t *ent ) va( "print \"donate: spectators cannot be so gracious\n\"" ) ); return; } - + if( divisor < 1 ) { trap_SendServerCommand( ent-g_entities, "print \"donate: get yourself some teammates first\n\"" ); return; } - + trap_Argv( 1, s, sizeof( s ) ); value = atoi(s); if( value <= 0 ) { @@ -4940,15 +5259,11 @@ static void Cmd_Ignore_f( gentity_t *ent ) } if( value > ent->client->pers.credit) value = ent->client->pers.credit; - + // allocate memory for distribution amounts amounts = G_Alloc( level.maxclients * sizeof( int ) ); - totals = G_Alloc( level.maxclients * sizeof( int ) ); - for( i = 0; i < level.maxclients; i++ ) { - amounts[ i ] = 0; - totals[ i ] = 0; - } - + for( i = 0; i < level.maxclients; i++ ) amounts[ i ] = 0; + // determine donation amounts for each client total = value; while( donated && value ) { @@ -4962,10 +5277,8 @@ static void Cmd_Ignore_f( gentity_t *ent ) ent->client->pers.teamSelection ) { new_credits = level.clients[ i ].pers.credit + portion; amounts[ i ] = portion; - totals[ i ] += portion; if( new_credits > max ) { amounts[ i ] -= new_credits - max; - totals[ i ] -= new_credits - max; new_credits = max; } if( amounts[ i ] ) { @@ -4976,23 +5289,23 @@ static void Cmd_Ignore_f( gentity_t *ent ) } } } - + // transfer funds G_AddCreditToClient( ent->client, value - total, qtrue ); for( i = 0; i < level.maxclients; i++ ) - if( totals[ i ] ) { + if( amounts[ i ] ) { trap_SendServerCommand( i, va( "print \"%s^7 donated %d %s to you, don't forget to say 'thank you'!\n\"", - ent->client->pers.netname, totals[ i ], type ) ); + ent->client->pers.netname, amounts[ i ], type ) ); } - + G_Free( amounts ); - G_Free( totals ); - + trap_SendServerCommand( ent-g_entities, va( "print \"Donated %d %s to the cause.\n\"", total-value, type ) ); } + commands_t cmds[ ] = { // normal commands @@ -5016,9 +5329,22 @@ 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 }, - { "allstats", 0|CMD_INTERMISSION, Cmd_AllStats_f }, { "teamstatus", CMD_TEAM, Cmd_TeamStatus_f }, // cheats @@ -5047,6 +5373,7 @@ commands_t cmds[ ] = { { "build", CMD_TEAM|CMD_LIVING, Cmd_Build_f }, { "deconstruct", CMD_TEAM|CMD_LIVING, Cmd_Destroy_f }, { "mark", CMD_TEAM|CMD_LIVING, Cmd_Mark_f }, + { "aim", CMD_HUMAN|CMD_LIVING, Cmd_Aim_f }, { "buy", CMD_HUMAN|CMD_LIVING, Cmd_Buy_f }, { "sell", CMD_HUMAN|CMD_LIVING, Cmd_Sell_f }, @@ -5403,15 +5730,15 @@ void G_PrivateMessage( gentity_t *ent ) if( teamonly && !OnSameTeam( ent, tmpent ) ) continue; - + // Ignore sending to invisible players if( tmpent->client->sess.invisible == qtrue && !G_admin_permission( ent, "invisible" ) ) continue; - + // Ignore sending to non-invisible-capable players while invisible if( ent->client->sess.invisible == qtrue && !G_admin_permission( tmpent, "invisible" ) ) continue; - + if( BG_ClientListTest( &tmpent->client->sess.ignoreList, ent-g_entities ) ) { @@ -5662,48 +5989,3 @@ qboolean G_IsMuted( gclient_t *client ) return muteState; } - -/* -================== -G_TeamKill_Repent - -Determine whether a players team kill activity is high -================== -*/ - -qboolean G_TeamKill_Repent( gentity_t *ent ) -{ - int millisSinceLastTeamKill; - - // Doesn't work if g_teamKillThreshold isn't set - if( !g_teamKillThreshold.integer || - g_teamKillThreshold.integer == 0 ) - return qfalse; - - // Doesn't work when game is paused - if( level.paused ) - return qfalse; - - millisSinceLastTeamKill = level.time - ent->client->pers.lastTeamKillTime; - if( millisSinceLastTeamKill < 30000 ) - ent->client->pers.teamKillDemerits++; - else - { - ent->client->pers.teamKillDemerits--; - if( ent->client->pers.teamKillDemerits < 0 ) - ent->client->pers.teamKillDemerits = 0; - } - - ent->client->pers.lastTeamKillTime = level.time; - - if ( ent->client->pers.teamKillDemerits >= ( g_teamKillThreshold.integer + 2 ) ) - trap_SendConsoleCommand( 0, va( "!ban %s 30m team killing\n", ent->client->pers.ip ) ); - else if ( ent->client->pers.teamKillDemerits == ( g_teamKillThreshold.integer + 1 ) ) - trap_SendConsoleCommand( 0, va( "!warn %i team killing\n", ent->client->ps.clientNum ) ); - else if ( ent->client->pers.teamKillDemerits == g_teamKillThreshold.integer ) - G_AdminsPrintf( "Team killer %s^7 has team killed ^6%i^7 times.\n", - ent->client->pers.netname, - ent->client->pers.statscounters.teamkills ); - - return qfalse; -} diff --git a/src/game/g_combat.c b/src/game/g_combat.c index 0cc079a..0d8d013 100644 --- a/src/game/g_combat.c +++ b/src/game/g_combat.c @@ -138,6 +138,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 ) @@ -163,6 +164,7 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int if( attacker != self && attacker->client->ps.stats[ STAT_PTEAM ] == self->client->ps.stats[ STAT_PTEAM ] ) { attacker->client->pers.statscounters.teamkills++; + attacker->client->pers.karma -= 300; if( attacker->client->pers.teamSelection == PTE_ALIENS ) { level.alienStatsCounters.teamkills++; @@ -205,6 +207,16 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int 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 ) { trap_SendServerCommand( -1, @@ -233,13 +245,14 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int va( "cp \"You killed ^1TEAMMATE^7 %s\"", self->client->pers.netname ) ); G_LogOnlyPrintf("%s^7 was killed by ^1TEAMMATE^7 %s^7 (Did %d damage to %d max)\n", self->client->pers.netname, attacker->client->pers.netname, self->client->tkcredits[ attacker->s.number ], self->client->ps.stats[ STAT_MAX_HEALTH ] ); - G_TeamKill_Repent( attacker ); + G_admin_tklog_log( attacker, self, meansOfDeath ); } self->enemy = attacker; self->client->ps.persistant[ PERS_KILLED ]++; self->client->pers.statscounters.deaths++; + self->client->pers.karma -= 10; if( self->client->pers.teamSelection == PTE_ALIENS ) { level.alienStatsCounters.deaths++; @@ -280,6 +293,7 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int attacker->client->lastKillTime = level.time; attacker->client->pers.statscounters.kills++; + attacker->client->pers.karma += 50; if( attacker->client->pers.teamSelection == PTE_ALIENS ) { level.alienStatsCounters.kills++; @@ -288,11 +302,26 @@ 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; + attacker->client->pers.karma += 50; + 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 ) { attacker->client->pers.statscounters.suicides++; + attacker->client->pers.karma -= 100; if( attacker->client->pers.teamSelection == PTE_ALIENS ) { level.alienStatsCounters.suicides++; @@ -441,7 +470,16 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int if( g_alienStage.integer < 2 ) { self->client->pers.statscounters.feeds++; + self->client->pers.karma -= 25; level.humanStatsCounters.feeds++; + + if( g_feedingSpree.integer && + level.reactorPresent && + !G_BuildableRange( self->client->ps.origin, 600, BA_H_REACTOR ) && + !G_BuildableRange( self->client->ps.origin, 200, BA_H_SPAWN ) ) + { + self->client->pers.statscounters.spreefeeds += SPREE_FEED_VALUE; + } } } else if( self->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) @@ -450,7 +488,16 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int if( g_humanStage.integer < 2 ) { self->client->pers.statscounters.feeds++; + self->client->pers.karma -= 25; level.alienStatsCounters.feeds++; + + if( g_feedingSpree.integer && + level.overmindPresent && + !G_BuildableRange( self->client->ps.origin, 600, BA_A_OVERMIND ) && + !G_BuildableRange( self->client->ps.origin, 200, BA_A_SPAWN ) ) + { + self->client->pers.statscounters.spreefeeds += SPREE_FEED_VALUE; + } } } } @@ -482,6 +529,9 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int level.humanStatsCounters.assists++; } + if( spreeRate && player == attacker ) + percentDamage *= (float)spreeRate; + //add credit G_AddCreditToClient( player->client, (int)( classValue * percentDamage ), qtrue ); @@ -516,6 +566,7 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int if( percentDamage > 0 && percentDamage < 1) { player->client->pers.statscounters.assists++; + player->client->pers.karma += 25; level.alienStatsCounters.assists++; } @@ -524,7 +575,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; @@ -565,7 +619,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; @@ -618,6 +675,38 @@ finish_dying: // from MOD_SLAP // g_forcerespawn may force spawning at some later time self->client->respawnTime = level.time + 1700; + if( g_feedingSpree.integer ) + { + int maxfeed; + + maxfeed = g_feedingSpree.integer * SPREE_FEED_VALUE; + if( self->client->pers.statscounters.spreefeeds > maxfeed ) + { + self->client->respawnTime += SPREE_FEED_DELAY * (self->client->pers.statscounters.spreefeeds - maxfeed ); + self->client->pers.karma -= 20; + 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 ) ); + } + } + + if( self->client->pers.bleeder ) + { + int spreeLength; + + spreeLength = self->client->pers.statscounters.spreebleeds / 10; + self->client->respawnTime += 9 * 1000; + + trap_SendServerCommand( self->client->ps.clientNum, + va( "print \"^3Bleeding has made you an enemy of your own base for ^7%d:%02d\n^1Respawn delayed ^7%d^1 seconds\n", + spreeLength / 60, spreeLength % 60, + ( self->client->respawnTime - level.time) / 1000 ) ); + trap_SendServerCommand( self->client->ps.clientNum, + va( "cp \"^3Bleeding made you an enemy of your own base!\n\n^7for %d:%02d\n\n^1Respawn delayed %d seconds\"", + spreeLength / 60, spreeLength % 60, + ( self->client->respawnTime - level.time) / 1000 ) ); + } + // remove powerups memset( self->client->ps.powerups, 0, sizeof( self->client->ps.powerups ) ); @@ -1013,6 +1102,7 @@ static float G_CalcDamageModifier( vec3_t point, gentity_t *targ, gentity_t *att if( attacker && attacker->client && modifier == 2 ) { attacker->client->pers.statscounters.headshots++; + attacker->client->pers.karma += 5; level.alienStatsCounters.headshots++; } @@ -1149,7 +1239,7 @@ dflags these flags are used to control how T_Damage works void G_SelectiveDamage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod, int team ) { - if( targ->client && ( team != targ->client->ps.stats[ STAT_PTEAM ] ) ) + if( targ->client && ( team != targ->client->ps.stats[ STAT_PTEAM ] || targ->client->pers.bleeder ) ) G_Damage( targ, inflictor, attacker, dir, point, damage, dflags, mod ); } @@ -1398,6 +1488,7 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, targ->client->lastPoisonTime = level.time; targ->client->lastPoisonClient = attacker; attacker->client->pers.statscounters.repairspoisons++; + attacker->client->pers.karma += 1; level.alienStatsCounters.repairspoisons++; } } @@ -1429,6 +1520,8 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, if( targ->biteam == attacker->client->pers.teamSelection || OnSameTeam( targ, attacker ) ) { attacker->client->pers.statscounters.ffdmgdone += takeNoOverkill; + if( attacker != targ ) + attacker->client->pers.karma -= take; if( attacker->client->pers.teamSelection == PTE_ALIENS ) { level.alienStatsCounters.ffdmgdone+=takeNoOverkill; @@ -1437,10 +1530,88 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, { level.humanStatsCounters.ffdmgdone+=takeNoOverkill; } + + // bleeding spree + if( g_bleedingSpree.integer > 1 && attacker != targ && + mod != MOD_SUICIDE && mod != MOD_TELEFRAG ) + { + attacker->client->pers.statscounters.spreebleeds += take; + + if( g_bleedingSpreeKick.integer == 2 ) + { + if( attacker->client->pers.statscounters.spreebleeds > g_bleedingSpree.integer * 100 ) + { + char buf[ MAX_STRING_CHARS ]; + + G_admin_autorevert( attacker ); + + Com_sprintf( buf, sizeof( buf ), + "%s^7 moved from %s to spectators due to excessive team damage\n", + attacker->client->pers.netname, + ( attacker->client->pers.teamSelection == PTE_ALIENS ) ? "aliens" : "humans" ); + trap_SendServerCommand( -1, va( "print \"%s\"", buf ) ); + G_LogOnlyPrintf( "Teamkilling: %s", buf ); + + G_admin_tklog_log( attacker, NULL, mod ); + + trap_SendConsoleCommand( EXEC_APPEND, va( "!putteam %d s %s\n", attacker - g_entities, g_adminTempSpec.string ) ); + attacker->client->pers.statscounters.spreebleeds = 0; + } + else if( attacker->client->pers.statscounters.spreebleeds > g_bleedingSpree.integer * 66 && + attacker->client->pers.bleederLastWarn + 20 * 1000 < level.time ) + { + attacker->client->pers.bleederLastWarn = level.time; + trap_SendServerCommand( attacker - g_entities, + "print \"^3Please do not damage your teammates or your base\n\"" ); + trap_SendServerCommand( attacker - g_entities, + "cp \"^1Please do not damage your teammates or your base\"" ); + } + } + else if( g_bleedingSpreeKick.integer && attacker->client->pers.bleeder && + attacker->client->pers.statscounters.spreebleeds > g_bleedingSpree.integer * 100 + g_bleedingSpree.integer * 20 ) + { + trap_SendConsoleCommand( EXEC_APPEND, + va("!kick %d auto-kick for team bleeding\n", ( attacker - g_entities )) ); + attacker->client->pers.statscounters.spreebleeds = -9000; + } + else if( attacker->client->pers.statscounters.spreebleeds > g_bleedingSpree.integer * 100 && + !attacker->client->pers.bleeder ) + { + attacker->client->pers.bleeder = qtrue; + attacker->client->pers.karma -= 500; + level.bleeders++; + trap_SendServerCommand( -1, + va( "print \"%s^3 has become an enemy of their own base\n\"", + attacker->client->pers.netname ) ); + trap_SendServerCommand( attacker - g_entities, + "cp \"^1Your team bleeding has irritated your base!\"" ); + + G_admin_tklog_log( attacker, NULL, mod ); + + if( g_bleedingSpreeKick.integer ) + trap_SendServerCommand( attacker - g_entities, + "print \"^1Continued bleeding will result in kick\n\"" ); + } + else if( attacker->client->pers.statscounters.spreebleeds > g_bleedingSpree.integer * 66 && + !attacker->client->pers.bleeder && + attacker->client->pers.bleederLastWarn + 20 * 1000 < level.time ) + { + attacker->client->pers.bleederLastWarn = level.time; + trap_SendServerCommand( attacker - g_entities, + "print \"^3Your bleeding is close to aggravating your base!\n\"" ); + trap_SendServerCommand( attacker - g_entities, + "cp \"^1Your bleeding is close to aggravating your base!\"" ); + + if( g_bleedingSpreeKick.integer ) + trap_SendServerCommand( attacker - g_entities, + "print \"^1Continued bleeding will result in kick\n\"" ); + } + } } else if( targ->s.eType == ET_BUILDABLE ) { attacker->client->pers.statscounters.structdmgdone += takeNoOverkill; + attacker->client->pers.karma += ( take / 10 ); if( attacker->client->pers.teamSelection == PTE_ALIENS ) { @@ -1454,6 +1625,7 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, if( targ->health > 0 && ( targ->health - take ) <=0 ) { attacker->client->pers.statscounters.structskilled++; + attacker->client->pers.karma += 10; if( attacker->client->pers.teamSelection == PTE_ALIENS ) { level.alienStatsCounters.structskilled++; @@ -1461,12 +1633,27 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, else if( attacker->client->pers.teamSelection == PTE_HUMANS ) { level.humanStatsCounters.structskilled++; + + if( attacker->client->pers.statscounters.spreefeeds ) + { + attacker->client->pers.statscounters.spreefeeds -= take; + if( attacker->client->pers.statscounters.spreefeeds < 0 ) + attacker->client->pers.statscounters.spreefeeds = 0; + } + + if( attacker->client->pers.statscounters.spreebleeds > 10 ) + { + attacker->client->pers.statscounters.spreebleeds -= take / 3; + if( attacker->client->pers.statscounters.spreebleeds < 10 ) + attacker->client->pers.statscounters.spreebleeds = 10; + } } } } else if( targ->client ) { attacker->client->pers.statscounters.dmgdone +=takeNoOverkill; + attacker->client->pers.karma += ( take / 10 ); attacker->client->pers.statscounters.hits++; if( attacker->client->pers.teamSelection == PTE_ALIENS ) { @@ -1476,9 +1663,52 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, { level.humanStatsCounters.dmgdone+=takeNoOverkill; } + + if( attacker->client->pers.statscounters.spreefeeds ) + { + attacker->client->pers.statscounters.spreefeeds -= take; + if( attacker->client->pers.statscounters.spreefeeds < 0 ) + attacker->client->pers.statscounters.spreefeeds = 0; + } + + if( attacker->client->pers.statscounters.spreebleeds > 10 ) + { + attacker->client->pers.statscounters.spreebleeds -= take / 3; + if( attacker->client->pers.statscounters.spreebleeds < 10 ) + attacker->client->pers.statscounters.spreebleeds = 10; + } } } + if( g_vampireDeath.integer && targ->client && attacker->client && targ != attacker ) + { + if( OnSameTeam( targ, attacker ) ) + { + if( mod == MOD_GRENADE ) + return; + + if( take > attacker->health ) + take = attacker->health; + + attacker->health -= take; + attacker->client->ps.stats[ STAT_HEALTH ] = attacker->health; + attacker->lastDamageTime = level.time; + + if( attacker->health <= 0 ) + { + if( attacker->health < -999 ) + attacker->health = -999; + attacker->die( attacker, attacker, attacker, take, MOD_SUICIDE ); + } + + take = 0 - take; + } + else + { + attacker->health += takeNoOverkill; + attacker->client->ps.stats[ STAT_HEALTH ] = attacker->health; + } + } //Do the damage targ->health = targ->health - take; diff --git a/src/game/g_local.h b/src/game/g_local.h index 8c65998..2294f6f 100644 --- a/src/game/g_local.h +++ b/src/game/g_local.h @@ -53,19 +53,6 @@ typedef struct gclient_s gclient_t; #define FL_NO_HUMANS 0x00004000 // spawn point just for bots #define FL_FORCE_GESTURE 0x00008000 // spawn point just for bots -typedef struct -{ - qboolean isNB; - float Area; - float Height; -} noBuild_t; - -typedef struct -{ - gentity_t *Marker; - vec3_t Origin; -} nbMarkers_t; - // movers are things like doors, plats, buttons, etc typedef enum { @@ -256,9 +243,6 @@ struct gentity_s int lastDamageTime; int bdnumb; // buildlog entry ID - - // For nobuild! - noBuild_t noBuild; }; typedef enum @@ -319,8 +303,8 @@ typedef struct spectatorState_t spectatorState; int spectatorClient; // for chasecam and follow mode int wins, losses; // tournament stats - qboolean invisible; // for being invisible on the server - ghosts! qboolean teamLeader; // true when this client is a team leader + qboolean invisible; // for being invisible on the server - ghosts! clientList_t ignoreList; } clientSession_t; @@ -356,11 +340,13 @@ typedef struct short headshots; int hits; int hitslocational; + short spreekills; + short spreefeeds; + int spreebleeds; short teamkills; int dretchbasytime; int jetpackusewallwalkusetime; int timeLastViewed; - int AllstatstimeLastViewed; } statsCounters_t; typedef struct @@ -426,11 +412,11 @@ typedef struct int lastFloodTime; // level.time of last flood-limited command int floodDemerits; // number of flood demerits accumulated - char lastMessage[ MAX_SAY_TEXT ]; // last message said by this player - int lastMessageTime; // level.time of last message said by this player + int vampireSuckFraction; - int lastTeamKillTime; // level.time of last team kill - int teamKillDemerits; // number of team kill demerits accumulated + int spreeTime1000; + qboolean bleeder; + int bleederLastWarn; vec3_t lastDeathLocation; char guid[ 33 ]; @@ -446,6 +432,9 @@ typedef struct int adminLevel; char adminName[ MAX_NETNAME ]; qboolean designatedBuilder; + char chat[ CHAT_MAXCHAN ][ CHAT_MAXPASS ]; + int karma; + int bubbleTime; qboolean firstConnect; // This is the first map since connect qboolean useUnlagged; statsCounters_t statscounters; @@ -615,6 +604,7 @@ typedef struct armourRegion_s typedef enum { TW_NOT = 0, + TW_SOON, TW_IMMINENT, TW_PASSED } timeWarning_t; @@ -633,6 +623,7 @@ typedef struct buildHistory_s buildHistory_t; struct buildHistory_s { int ID; // persistent ID to aid in specific reverting + int time; // time the event occured gentity_t *ent; // who, NULL if they've disconnected (or aren't an ent) char name[ MAX_NETNAME ]; // who, saves name if ent is NULL int buildable; // what @@ -702,6 +693,7 @@ typedef struct char teamVoteString[ 2 ][ MAX_STRING_CHARS ]; char teamVoteDisplayString[ 2 ][ MAX_STRING_CHARS ]; int teamVoteTime[ 2 ]; // level.time vote was called + int teamVotePassThreshold[ 2 ]; int teamVoteYes[ 2 ]; int teamVoteNo[ 2 ]; int numteamVotingClients[ 2 ]; // set by CalculateRanks @@ -747,6 +739,14 @@ typedef struct int humanBuildPoints; int humanBuildPointsPowered; + int alienRecoverBuildPoints; + int alienRecoverTime; + int humanRecoverBuildPoints; + int humanRecoverTime; + + int defaultAlienBuildPoints; + int defaultHumanBuildPoints; + gentity_t *markedBuildables[ MAX_GENTITIES ]; int numBuildablesForRemoval; @@ -769,6 +769,10 @@ typedef struct timeWarning_t timelimitWarning; int extend_vote_count; + int vampireDeath; + int vampireDeathBeginTime; + timeWarning_t vampireDeathWarning; + spawnQueue_t alienSpawnQueue; spawnQueue_t humanSpawnQueue; @@ -789,6 +793,8 @@ typedef struct int pause_ff; int pause_ffb; + int bleeders; + int lastCreditedAlien; int lastCreditedHuman; @@ -807,12 +813,6 @@ typedef struct statsCounters_level alienStatsCounters; statsCounters_level humanStatsCounters; - - qboolean noBuilding; - float nbArea; - float nbHeight; - - nbMarkers_t nbMarkers[ MAX_GENTITIES ]; } level_locals_t; #define CMD_CHEAT 0x01 @@ -863,6 +863,9 @@ void G_LeaveTeam( gentity_t *self ); void G_ChangeTeam( gentity_t *ent, pTeam_t newTeam ); void G_SanitiseString( char *in, char *out, int len ); void G_PrivateMessage( gentity_t *ent ); +void Cmd_Join_f( gentity_t *ent ); +void Cmd_Part_f( gentity_t *ent ); +void Cmd_Channel_f( gentity_t *ent ); char *G_statsString( statsCounters_t *sc, pTeam_t *pt ); void Cmd_Share_f( gentity_t *ent ); void Cmd_Donate_f( gentity_t *ent ); @@ -871,7 +874,6 @@ void Cmd_Builder_f( gentity_t *ent ); void G_WordWrap( char *buffer, int maxwidth ); void G_CP( gentity_t *ent ); qboolean G_IsMuted( gclient_t *ent ); -qboolean G_TeamKill_Repent( gentity_t *ent ); // // g_physics.c @@ -919,8 +921,11 @@ qboolean G_IsPowered( vec3_t origin ); qboolean G_IsDCCBuilt( void ); qboolean G_IsOvermindBuilt( void ); +void G_RecoverBuildPoints( gentity_t *self ); + void G_BuildableThink( gentity_t *ent, int msec ); qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable ); +float G_BuildableRangeClosest( vec3_t origin, float r, buildable_t buildable ); itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance, vec3_t origin ); qboolean G_BuildingExists( int bclass ) ; qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable ); @@ -941,8 +946,16 @@ qboolean G_RevertCanFit( buildHistory_t *bh ); int G_LogBuild( buildHistory_t *new ); int G_CountBuildLog( void ); char *G_FindBuildLogName( int id ); -void G_NobuildSave( void ); -void G_NobuildLoad( void ); + +void nobuild_init( void ); +void nobuild_add( gentity_t *ent ); +void nobuild_del( gentity_t *ent ); +void nobuild_command( gentity_t *ent, qboolean next_zone, qboolean next_type, float size ); +void nobuild_set( qboolean enable, gentity_t *ent ); +void nobuild_save( void ); +void nobuild_go( gentity_t *ent ); +void nobuild_list( gentity_t *ent ); +int nobuild_check( vec3_t origin ); // // g_utils.c @@ -1131,7 +1144,6 @@ void QDECL G_LogPrintf( const char *fmt, ... ); void QDECL G_LogPrintfColoured( const char *fmt, ... ); void QDECL G_LogOnlyPrintf( const char *fmt, ... ); void QDECL G_AdminsPrintf( const char *fmt, ... ); -void QDECL G_WarningsPrintf( char *flag, const char *fmt, ... ); void QDECL G_LogOnlyPrintf( const char *fmt, ... ); void SendScoreboardMessageToAllClients( void ); void QDECL G_Printf( const char *fmt, ... ); @@ -1140,6 +1152,7 @@ void CheckVote( void ); void CheckTeamVote( int teamnum ); void LogExit( const char *string ); int G_TimeTilSuddenDeath( void ); +int G_TimeTilVampireDeath( void ); void CheckMsgTimer( void ); qboolean G_Flood_Limited( gentity_t *ent ); @@ -1205,8 +1218,7 @@ typedef enum MCV_RANDOM, MCV_NUMCLIENTS, MCV_LASTWIN, - MCV_VOTE, - MCV_SELECTEDRANDOM + MCV_VOTE } mapConditionVariable_t; typedef enum @@ -1268,6 +1280,7 @@ qboolean G_AdvanceMapRotation( void ); qboolean G_StartMapRotation( char *name, qboolean changeMap ); void G_StopMapRotation( void ); qboolean G_MapRotationActive( void ); +qboolean G_CurrentMapIsRotation( void ); void G_InitMapRotations( void ); qboolean G_MapExists( char *name ); int G_GetCurrentMap( int rotation ); @@ -1277,7 +1290,6 @@ qboolean G_IntermissionMapVoteWinner( void ); void G_IntermissionMapVoteMessage( gentity_t *ent ); void G_IntermissionMapVoteMessageAll( void ); void G_IntermissionMapVoteCommand( gentity_t *ent, qboolean next, qboolean choose ); -static qboolean G_GetRandomMap( char *name, int size, int rotation, int map ); // // g_ptr.c @@ -1296,6 +1308,11 @@ extern gentity_t g_entities[ MAX_GENTITIES ]; #define FOFS(x) ((int)&(((gentity_t *)0)->x)) +//spree values +#define SPREE_FEED_VALUE 120 +#define SPREE_FEED_FADE 3 +#define SPREE_FEED_DELAY 50 + extern vmCvar_t g_dedicated; extern vmCvar_t g_cheats; extern vmCvar_t g_maxclients; // allow this many total, including spectators @@ -1307,11 +1324,16 @@ extern vmCvar_t g_minNameChangePeriod; extern vmCvar_t g_maxNameChanges; extern vmCvar_t g_newbieNumbering; extern vmCvar_t g_newbieNamePrefix; +extern vmCvar_t g_newbieNoBuild; +extern vmCvar_t g_newbieNoBuildMessage; extern vmCvar_t g_timelimit; extern vmCvar_t g_suddenDeathTime; extern vmCvar_t g_suddenDeath; extern vmCvar_t g_suddenDeathMode; +extern vmCvar_t g_vampireDeathTime; +extern vmCvar_t g_vampireDeath; +extern vmCvar_t g_vampireDeathInfo; extern vmCvar_t g_friendlyFire; extern vmCvar_t g_friendlyFireHumans; extern vmCvar_t g_friendlyFireAliens; @@ -1325,6 +1347,7 @@ extern vmCvar_t g_speed; extern vmCvar_t g_knockback; extern vmCvar_t g_quadfactor; extern vmCvar_t g_inactivity; +extern vmCvar_t g_inactivityMode; extern vmCvar_t g_debugMove; extern vmCvar_t g_debugAlloc; extern vmCvar_t g_debugDamage; @@ -1339,6 +1362,7 @@ extern vmCvar_t g_blood; extern vmCvar_t g_allowVote; extern vmCvar_t g_requireVoteReasons; extern vmCvar_t g_voteLimit; +extern vmCvar_t g_pollVotes; extern vmCvar_t g_suddenDeathVotePercent; extern vmCvar_t g_suddenDeathVoteDelay; extern vmCvar_t g_extendVotesPercent; @@ -1357,13 +1381,12 @@ extern vmCvar_t g_customVote8; extern vmCvar_t g_customVotePercent; extern vmCvar_t g_mapVotesPercent; extern vmCvar_t g_mapRotationVote; -extern vmCvar_t g_extendVotesPercent; -extern vmCvar_t g_extendVotesTime; -extern vmCvar_t g_extendVotesCount; extern vmCvar_t g_readyPercent; extern vmCvar_t g_designateVotes; extern vmCvar_t g_teamAutoJoin; extern vmCvar_t g_teamForceBalance; +extern vmCvar_t g_popularMaps; +extern vmCvar_t g_popularMapsVotePercent; extern vmCvar_t g_banIPs; extern vmCvar_t g_filterBan; extern vmCvar_t g_smoothClients; @@ -1396,6 +1419,9 @@ extern vmCvar_t g_unlagged; extern vmCvar_t g_disabledEquipment; extern vmCvar_t g_disabledClasses; extern vmCvar_t g_disabledBuildables; +extern vmCvar_t g_buildPointsRecoverRate; +extern vmCvar_t g_dynamicBuildPoints; +extern vmCvar_t g_instantBuild; extern vmCvar_t g_markDeconstruct; extern vmCvar_t g_markDeconstructMode; @@ -1405,12 +1431,13 @@ extern vmCvar_t g_debugMapRotation; extern vmCvar_t g_currentMapRotation; extern vmCvar_t g_currentMap; extern vmCvar_t g_nextMap; +extern vmCvar_t g_idleMapSwitch; extern vmCvar_t g_initialMapRotation; extern vmCvar_t g_chatTeamPrefix; +extern vmCvar_t g_sayAreaRange; extern vmCvar_t g_actionPrefix; extern vmCvar_t g_floodMaxDemerits; extern vmCvar_t g_floodMinTime; -extern vmCvar_t g_spamTime; extern vmCvar_t g_shove; @@ -1426,27 +1453,39 @@ extern vmCvar_t g_adminSayFilter; extern vmCvar_t g_adminNameProtect; extern vmCvar_t g_adminTempMute; extern vmCvar_t g_adminTempBan; +extern vmCvar_t g_adminBanRepeatKicks; extern vmCvar_t g_adminMaxBan; extern vmCvar_t g_adminTempSpec; extern vmCvar_t g_adminMapLog; +extern vmCvar_t g_adminRegisterLevel; +extern vmCvar_t g_adminRegisterAdminPass; +extern vmCvar_t g_adminRegisterAdminLevel; extern vmCvar_t g_minLevelToJoinTeam; -extern vmCvar_t g_minDeconLevel; -extern vmCvar_t g_minDeconAffectsMark; extern vmCvar_t g_forceAutoSelect; extern vmCvar_t g_minLevelToSpecMM1; extern vmCvar_t g_banNotice; +extern vmCvar_t g_karma; +extern vmCvar_t g_chat; +extern vmCvar_t g_adminExpireTime; extern vmCvar_t g_devmapKillerHP; extern vmCvar_t g_killerHP; +extern vmCvar_t g_maxGhosts; + extern vmCvar_t g_privateMessages; -extern vmCvar_t g_fullIgnore; extern vmCvar_t g_decolourLogfiles; extern vmCvar_t g_publicSayadmins; extern vmCvar_t g_myStats; extern vmCvar_t g_teamStatus; extern vmCvar_t g_antiSpawnBlock; +extern vmCvar_t g_killingSpree; +extern vmCvar_t g_feedingSpree; +extern vmCvar_t g_bleedingSpree; +extern vmCvar_t g_bleedingSpreeKick; +extern vmCvar_t g_autoRevert; + extern vmCvar_t g_dretchPunt; extern vmCvar_t g_devmapNoGod; @@ -1458,6 +1497,11 @@ extern vmCvar_t g_slapDamage; extern vmCvar_t g_voteMinTime; extern vmCvar_t g_mapvoteMaxTime; extern vmCvar_t g_votableMaps; +extern vmCvar_t g_defeatVoteMinTime; + +extern vmCvar_t g_practiceText; +extern vmCvar_t g_practiceCount; +extern vmCvar_t g_freeCredits; extern vmCvar_t g_msg; extern vmCvar_t g_msgTime; @@ -1466,23 +1510,27 @@ extern vmCvar_t g_welcomeMsgTime; extern vmCvar_t g_buildLogMaxLength; -extern vmCvar_t g_AllStats; -extern vmCvar_t g_AllStatsTime; +extern vmCvar_t g_turretAim; + +extern vmCvar_t g_modBuildableHealth; // Buildable health +extern vmCvar_t g_modBuildableSpeed; // Buildable fire rate +extern vmCvar_t g_modBuildableCount; // Number of OMs/RCs allowed +extern vmCvar_t g_modHumanStamina; // Human stamina +extern vmCvar_t g_modHumanHealth; // Human health +extern vmCvar_t g_modAlienHealth; // Alien health +extern vmCvar_t g_modAlienRegenRange; // Alien regen rate is based on distance from base +extern vmCvar_t g_modHumanRate; // Human fire rate +extern vmCvar_t g_modAlienRate; // Alien fire rate +extern vmCvar_t g_modWeaponAmmo; // Weapon ammo per clip +extern vmCvar_t g_modWeaponReload; // Weapon reload time +extern vmCvar_t g_modTurretAngle; // Allow high turret build angles +extern vmCvar_t g_modStage3Strength; // Alter stage 3 buildables +extern vmCvar_t g_modMainStrength; // Damage/range modifier for Reactor and Overmind extern vmCvar_t mod_jetpackFuel; extern vmCvar_t mod_jetpackConsume; extern vmCvar_t mod_jetpackRegen; -extern vmCvar_t g_adminExpireTime; - -extern vmCvar_t g_autoGhost; - -extern vmCvar_t g_teamKillThreshold; - -extern vmCvar_t g_aimbotAdvertBan; -extern vmCvar_t g_aimbotAdvertBanTime; -extern vmCvar_t g_aimbotAdvertBanReason; - void trap_Printf( const char *fmt ); void trap_Error( const char *fmt ); int trap_Milliseconds( void ); diff --git a/src/game/g_main.c b/src/game/g_main.c index b946238..732fecd 100644 --- a/src/game/g_main.c +++ b/src/game/g_main.c @@ -23,8 +23,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "g_local.h" -#define QVM_NAME "Slackers QVM" " (Lakitu7 5.5)" -#define QVM_VERSIONNUM "1.1+" +#define QVM_NAME "cQVM" +#define MAJ_VERSION "5.5" + +#ifdef SVN_ID + #define QVM_VERSIONNUM MAJ_VERSION " svn" SVN_ID +#else + #define QVM_VERSIONNUM MAJ_VERSION +#endif level_locals_t level; @@ -47,6 +53,9 @@ vmCvar_t g_timelimit; vmCvar_t g_suddenDeathTime; vmCvar_t g_suddenDeath; vmCvar_t g_suddenDeathMode; +vmCvar_t g_vampireDeathTime; +vmCvar_t g_vampireDeath; +vmCvar_t g_vampireDeathInfo; vmCvar_t g_capturelimit; vmCvar_t g_friendlyFire; vmCvar_t g_friendlyFireAliens; @@ -65,6 +74,7 @@ vmCvar_t g_cheats; vmCvar_t g_knockback; vmCvar_t g_quadfactor; vmCvar_t g_inactivity; +vmCvar_t g_inactivityMode; vmCvar_t g_debugMove; vmCvar_t g_debugDamage; vmCvar_t g_debugAlloc; @@ -85,6 +95,7 @@ vmCvar_t g_podiumDrop; vmCvar_t g_allowVote; vmCvar_t g_requireVoteReasons; vmCvar_t g_voteLimit; +vmCvar_t g_pollVotes; vmCvar_t g_suddenDeathVotePercent; vmCvar_t g_suddenDeathVoteDelay; vmCvar_t g_extendVotesPercent; @@ -101,14 +112,13 @@ vmCvar_t g_customVote7; vmCvar_t g_customVote8; vmCvar_t g_customVotePercent; vmCvar_t g_mapVotesPercent; -vmCvar_t g_extendVotesPercent; -vmCvar_t g_extendVotesTime; -vmCvar_t g_extendVotesCount; vmCvar_t g_mapRotationVote; vmCvar_t g_readyPercent; vmCvar_t g_designateVotes; vmCvar_t g_teamAutoJoin; vmCvar_t g_teamForceBalance; +vmCvar_t g_popularMaps; +vmCvar_t g_popularMapsVotePercent; vmCvar_t g_banIPs; vmCvar_t g_filterBan; vmCvar_t g_smoothClients; @@ -122,6 +132,8 @@ vmCvar_t g_minNameChangePeriod; vmCvar_t g_maxNameChanges; vmCvar_t g_newbieNumbering; vmCvar_t g_newbieNamePrefix; +vmCvar_t g_newbieNoBuild; +vmCvar_t g_newbieNoBuildMessage; vmCvar_t g_humanBuildPoints; vmCvar_t g_alienBuildPoints; @@ -142,6 +154,9 @@ vmCvar_t g_unlagged; vmCvar_t g_disabledEquipment; vmCvar_t g_disabledClasses; vmCvar_t g_disabledBuildables; +vmCvar_t g_buildPointsRecoverRate; +vmCvar_t g_dynamicBuildPoints; +vmCvar_t g_instantBuild; vmCvar_t g_markDeconstruct; vmCvar_t g_markDeconstructMode; @@ -151,16 +166,17 @@ vmCvar_t g_debugMapRotation; vmCvar_t g_currentMapRotation; vmCvar_t g_currentMap; vmCvar_t g_nextMap; +vmCvar_t g_idleMapSwitch; vmCvar_t g_initialMapRotation; vmCvar_t g_shove; vmCvar_t g_mapConfigs; vmCvar_t g_chatTeamPrefix; +vmCvar_t g_sayAreaRange; vmCvar_t g_actionPrefix; vmCvar_t g_floodMaxDemerits; vmCvar_t g_floodMinTime; -vmCvar_t g_spamTime; vmCvar_t g_layouts; vmCvar_t g_layoutAuto; @@ -172,31 +188,42 @@ vmCvar_t g_adminSayFilter; vmCvar_t g_adminNameProtect; vmCvar_t g_adminTempMute; vmCvar_t g_adminTempBan; +vmCvar_t g_adminBanRepeatKicks; vmCvar_t g_adminMaxBan; vmCvar_t g_adminTempSpec; vmCvar_t g_adminMapLog; +vmCvar_t g_adminRegisterLevel; +vmCvar_t g_adminRegisterAdminPass; +vmCvar_t g_adminRegisterAdminLevel; vmCvar_t g_minLevelToJoinTeam; -vmCvar_t g_minDeconLevel; -vmCvar_t g_minDeconAffectsMark; vmCvar_t g_forceAutoSelect; vmCvar_t g_privateMessages; -vmCvar_t g_fullIgnore; vmCvar_t g_decolourLogfiles; vmCvar_t g_minLevelToSpecMM1; vmCvar_t g_publicSayadmins; vmCvar_t g_myStats; -vmCvar_t g_AllStats; -vmCvar_t g_AllStatsTime; vmCvar_t g_teamStatus; vmCvar_t g_antiSpawnBlock; + +vmCvar_t g_killingSpree; +vmCvar_t g_feedingSpree; +vmCvar_t g_bleedingSpree; +vmCvar_t g_bleedingSpreeKick; +vmCvar_t g_autoRevert; + vmCvar_t g_banNotice; +vmCvar_t g_karma; +vmCvar_t g_chat; +vmCvar_t g_adminExpireTime; vmCvar_t g_devmapKillerHP; vmCvar_t g_killerHP; vmCvar_t g_buildLogMaxLength; +vmCvar_t g_maxGhosts; + vmCvar_t g_tag; vmCvar_t g_dretchPunt; @@ -213,27 +240,38 @@ vmCvar_t g_slapDamage; vmCvar_t g_voteMinTime; vmCvar_t g_mapvoteMaxTime; vmCvar_t g_votableMaps; +vmCvar_t g_defeatVoteMinTime; + +vmCvar_t g_practiceText; +vmCvar_t g_practiceCount; +vmCvar_t g_freeCredits; vmCvar_t g_msg; vmCvar_t g_msgTime; vmCvar_t g_welcomeMsg; vmCvar_t g_welcomeMsgTime; +vmCvar_t g_turretAim; + +vmCvar_t g_modBuildableHealth; +vmCvar_t g_modBuildableSpeed; +vmCvar_t g_modBuildableCount; +vmCvar_t g_modHumanStamina; +vmCvar_t g_modHumanHealth; +vmCvar_t g_modAlienHealth; +vmCvar_t g_modAlienRegenRange; +vmCvar_t g_modHumanRate; +vmCvar_t g_modAlienRate; +vmCvar_t g_modWeaponAmmo; +vmCvar_t g_modWeaponReload; +vmCvar_t g_modTurretAngle; +vmCvar_t g_modStage3Strength; +vmCvar_t g_modMainStrength; vmCvar_t mod_jetpackFuel; vmCvar_t mod_jetpackConsume; vmCvar_t mod_jetpackRegen; -vmCvar_t g_adminExpireTime; - -vmCvar_t g_autoGhost; - -vmCvar_t g_teamKillThreshold; - -vmCvar_t g_aimbotAdvertBan; -vmCvar_t g_aimbotAdvertBanTime; -vmCvar_t g_aimbotAdvertBanReason; - static cvarTable_t gameCvarTable[ ] = { // don't override the cheat state set by the system @@ -260,6 +298,10 @@ static cvarTable_t gameCvarTable[ ] = { &g_suddenDeathMode, "g_suddenDeathMode", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, { &g_suddenDeath, "g_suddenDeath", "0", CVAR_SERVERINFO | CVAR_NORESTART, 0, qtrue }, + { &g_vampireDeathTime, "g_vampireDeathTime", "0", CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, + { &g_vampireDeath, "g_vampireDeath", "0", CVAR_NORESTART, 0, qtrue }, + { &g_vampireDeathInfo, "g_vampireDeathInfo", "( !info vampire )", CVAR_ARCHIVE, 0, qtrue }, + { &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse }, { &g_friendlyFire, "g_friendlyFire", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qtrue }, @@ -272,11 +314,14 @@ static cvarTable_t gameCvarTable[ ] = { &g_devmapNoStructDmg, "g_devmapNoStructDmg", "0", CVAR_ARCHIVE, 0, qtrue }, { &g_slapKnockback, "g_slapKnockback", "200", CVAR_ARCHIVE, 0, qfalse}, - { &g_slapDamage, "g_slapDamage", "0", CVAR_ARCHIVE, 0, qfalse}, + { &g_slapDamage, "g_slapDamage", "5", CVAR_ARCHIVE, 0, qfalse}, { &g_teamAutoJoin, "g_teamAutoJoin", "0", CVAR_ARCHIVE }, { &g_teamForceBalance, "g_teamForceBalance", "1", CVAR_ARCHIVE }, + { &g_popularMaps, "g_popularMaps", "arachnid2 atcs karith nexus6 niveus transit tremor", CVAR_ARCHIVE }, + { &g_popularMapsVotePercent, "g_popularMapsVotePercent", "0", CVAR_ARCHIVE }, + { &g_warmup, "g_warmup", "10", CVAR_ARCHIVE, 0, qtrue }, { &g_warmupMode, "g_warmupMode", "1", CVAR_ARCHIVE, 0, qtrue }, { &g_doWarmup, "g_doWarmup", "1", CVAR_ARCHIVE, 0, qtrue }, @@ -289,8 +334,6 @@ static cvarTable_t gameCvarTable[ ] = { &g_filterBan, "g_filterBan", "1", CVAR_ARCHIVE, 0, qfalse }, { &g_needpass, "g_needpass", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, - - { &g_autoGhost, "g_autoGhost", "1", CVAR_SERVERINFO, 0, qfalse }, { &g_dedicated, "dedicated", "0", 0, 0, qfalse }, @@ -301,6 +344,7 @@ static cvarTable_t gameCvarTable[ ] = { &g_weaponRespawn, "g_weaponrespawn", "5", 0, 0, qtrue }, { &g_weaponTeamRespawn, "g_weaponTeamRespawn", "30", 0, 0, qtrue }, { &g_inactivity, "g_inactivity", "0", 0, 0, qtrue }, + { &g_inactivityMode, "g_inactivityMode", "0", 0, 0, qfalse }, { &g_debugMove, "g_debugMove", "0", 0, 0, qfalse }, { &g_debugDamage, "g_debugDamage", "0", 0, 0, qfalse }, { &g_debugAlloc, "g_debugAlloc", "0", 0, 0, qfalse }, @@ -312,12 +356,19 @@ static cvarTable_t gameCvarTable[ ] = { &g_allowVote, "g_allowVote", "1", CVAR_ARCHIVE, 0, qfalse }, { &g_requireVoteReasons, "g_requireVoteReasons", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_pollVotes, "g_pollVotes", "1", CVAR_ARCHIVE, 0, qfalse }, { &g_voteLimit, "g_voteLimit", "5", CVAR_ARCHIVE, 0, qfalse }, { &g_voteMinTime, "g_voteMinTime", "120", CVAR_ARCHIVE, 0, qfalse }, { &g_mapvoteMaxTime, "g_mapvoteMaxTime", "240", CVAR_ARCHIVE, 0, qfalse }, { &g_votableMaps, "g_votableMaps", "", CVAR_ARCHIVE, 0, qtrue }, + { &g_defeatVoteMinTime, "g_defeatVoteMinTime", "60", CVAR_ARCHIVE, 0, qfalse }, { &g_suddenDeathVotePercent, "g_suddenDeathVotePercent", "74", CVAR_ARCHIVE, 0, qfalse }, { &g_suddenDeathVoteDelay, "g_suddenDeathVoteDelay", "180", CVAR_ARCHIVE, 0, qfalse }, + { &g_extendVotesPercent, "g_extendVotesPercent", "74", CVAR_ARCHIVE, 0, qfalse }, + { &g_extendVotesTime, "g_extendVotesTime", "10", CVAR_ARCHIVE, 0, qfalse }, + { &g_extendVotesCount, "g_extendVotesCount", "2", CVAR_ARCHIVE, 0, qfalse }, + { &g_kickVotesPercent, "g_kickVotesPercent", "50", CVAR_ARCHIVE, 0, qtrue }, + { &g_customVote1, "g_customVote1", "", CVAR_ARCHIVE, 0, qfalse }, { &g_customVote2, "g_customVote2", "", CVAR_ARCHIVE, 0, qfalse }, { &g_customVote3, "g_customVote3", "", CVAR_ARCHIVE, 0, qfalse }, @@ -327,13 +378,15 @@ static cvarTable_t gameCvarTable[ ] = { &g_customVote7, "g_customVote7", "", CVAR_ARCHIVE, 0, qfalse }, { &g_customVote8, "g_customVote8", "", CVAR_ARCHIVE, 0, qfalse }, { &g_customVotePercent, "g_customVotePercent", "50", CVAR_ARCHIVE, 0, qfalse }, + { &g_mapVotesPercent, "g_mapVotesPercent", "50", CVAR_ARCHIVE, 0, qfalse }, - { &g_extendVotesPercent, "g_extendVotesPercent", "74", CVAR_ARCHIVE, 0, qfalse }, - { &g_extendVotesTime, "g_extendVotesTime", "10", CVAR_ARCHIVE, 0, qfalse }, - { &g_extendVotesCount, "g_extendVotesCount", "2", CVAR_ARCHIVE, 0, qfalse }, { &g_mapRotationVote, "g_mapRotationVote", "15", CVAR_ARCHIVE, 0, qfalse }, { &g_readyPercent, "g_readyPercent", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_designateVotes, "g_designateVotes", "0", CVAR_ARCHIVE, 0, qfalse }, + + { &g_practiceText, "g_practiceText", "", 0, 0, qfalse}, + { &g_practiceCount, "g_practiceCount", "0", 0, 0, qfalse}, + { &g_freeCredits, "g_freeCredits", "0", CVAR_ARCHIVE, 0, qtrue }, { &g_listEntity, "g_listEntity", "0", 0, 0, qfalse }, { &g_minCommandPeriod, "g_minCommandPeriod", "500", 0, 0, qfalse}, @@ -341,6 +394,9 @@ static cvarTable_t gameCvarTable[ ] = { &g_maxNameChanges, "g_maxNameChanges", "5", 0, 0, qfalse}, { &g_newbieNumbering, "g_newbieNumbering", "0", CVAR_ARCHIVE, 0, qfalse}, { &g_newbieNamePrefix, "g_newbieNamePrefix", "Newbie#", CVAR_ARCHIVE, 0, qfalse}, + { &g_newbieNoBuild, "g_newbieNoBuild", "0", CVAR_ARCHIVE, 0, qfalse}, + { &g_newbieNoBuildMessage, "g_newbieNoBuildMessage", + "Set a name by pressing Escape and choosing Options", CVAR_ARCHIVE, 0, qfalse}, { &g_smoothClients, "g_smoothClients", "1", 0, 0, qfalse}, { &g_clientUpgradeNotice, "g_clientUpgradeNotice", "1", 0, 0, qfalse}, @@ -362,17 +418,20 @@ static cvarTable_t gameCvarTable[ ] = { &g_teamImbalanceWarnings, "g_teamImbalanceWarnings", "30", CVAR_ARCHIVE, 0, qfalse }, - { &g_unlagged, "g_unlagged", "1", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, + { &g_unlagged, "g_unlagged", "1", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qfalse }, { &g_disabledEquipment, "g_disabledEquipment", "", CVAR_ROM, 0, qfalse }, { &g_disabledClasses, "g_disabledClasses", "", CVAR_ROM, 0, qfalse }, { &g_disabledBuildables, "g_disabledBuildables", "", CVAR_ROM, 0, qfalse }, + { &g_buildPointsRecoverRate, "g_buildPointsRecoverRate", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_dynamicBuildPoints, "g_dynamicBuildPoints", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_instantBuild, "g_instantBuild", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_chatTeamPrefix, "g_chatTeamPrefix", "1", CVAR_ARCHIVE }, + { &g_sayAreaRange, "g_sayAreaRange", "1000", CVAR_ARCHIVE, 0, qtrue }, { &g_actionPrefix, "g_actionPrefix", "* ", CVAR_ARCHIVE, 0, qfalse }, { &g_floodMaxDemerits, "g_floodMaxDemerits", "5000", CVAR_ARCHIVE, 0, qfalse }, { &g_floodMinTime, "g_floodMinTime", "2000", CVAR_ARCHIVE, 0, qfalse }, - { &g_spamTime, "g_spamTime", "2", CVAR_ARCHIVE, 0, qfalse }, { &g_markDeconstruct, "g_markDeconstruct", "0", CVAR_ARCHIVE, 0, qtrue }, { &g_markDeconstructMode, "g_markDeconstructMode", "0", CVAR_ARCHIVE, 0, qfalse }, @@ -382,6 +441,7 @@ static cvarTable_t gameCvarTable[ ] = { &g_currentMapRotation, "g_currentMapRotation", "-1", 0, 0, qfalse }, // -1 = NOT_ROTATING { &g_currentMap, "g_currentMap", "0", 0, 0, qfalse }, { &g_nextMap, "g_nextMap", "", 0 , 0, qtrue }, + { &g_idleMapSwitch, "g_idleMapSwitch", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_initialMapRotation, "g_initialMapRotation", "", CVAR_ARCHIVE, 0, qfalse }, { &g_shove, "g_shove", "15", CVAR_ARCHIVE, 0, qfalse }, { &g_mapConfigs, "g_mapConfigs", "", CVAR_ARCHIVE, 0, qfalse }, @@ -397,53 +457,74 @@ static cvarTable_t gameCvarTable[ ] = { &g_adminNameProtect, "g_adminNameProtect", "1", CVAR_ARCHIVE, 0, qfalse }, { &g_adminTempMute, "g_adminTempMute", "5m", CVAR_ARCHIVE, 0, qfalse }, { &g_adminTempBan, "g_adminTempBan", "2m", CVAR_ARCHIVE, 0, qfalse }, + { &g_adminBanRepeatKicks, "g_adminBanRepeatKicks", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_adminMaxBan, "g_adminMaxBan", "2w", CVAR_ARCHIVE, 0, qfalse }, { &g_adminTempSpec, "g_adminTempSpec", "2m", CVAR_ARCHIVE, 0, qfalse }, { &g_adminMapLog, "g_adminMapLog", "", CVAR_ROM, 0, qfalse }, + { &g_adminRegisterLevel, "g_adminRegisterLevel", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_adminRegisterAdminPass, "g_adminRegisterAdminPass", "", CVAR_ARCHIVE, 0, qfalse }, + { &g_adminRegisterAdminLevel, "g_adminRegisterAdminLevel", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_minLevelToJoinTeam, "g_minLevelToJoinTeam", "0", CVAR_ARCHIVE, 0, qfalse }, - { &g_minDeconLevel, "g_minDeconLevel", "0", CVAR_ARCHIVE, 0, qfalse}, - { &g_minDeconAffectsMark, "g_minDeconAffectsMark", "0", CVAR_ARCHIVE, 0, qfalse}, { &g_forceAutoSelect, "g_forceAutoSelect", "0", CVAR_ARCHIVE, 0, qtrue }, - { &g_adminExpireTime, "g_adminExpireTime", "0", CVAR_ARCHIVE, 0, qfalse }, + + { &g_maxGhosts, "g_maxGhosts", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_privateMessages, "g_privateMessages", "1", CVAR_ARCHIVE, 0, qfalse }, - { &g_fullIgnore, "g_fullIgnore", "1", CVAR_ARCHIVE, 0, qtrue }, { &g_decolourLogfiles, "g_decolourLogfiles", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_buildLogMaxLength, "g_buildLogMaxLength", "50", CVAR_ARCHIVE, 0, qfalse }, { &g_myStats, "g_myStats", "1", CVAR_ARCHIVE, 0, qtrue }, - { &g_AllStats, "g_AllStats", "0", CVAR_ARCHIVE, 0, qtrue }, - { &g_AllStatsTime, "g_AllStatsTime", "60", CVAR_ARCHIVE, 0, qfalse }, - { &g_teamStatus, "g_teamStatus", "0", CVAR_ARCHIVE, 0, qtrue }, + { &g_teamStatus, "g_teamStatus", "0", 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_karma, "g_karma", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_chat, "g_chat", "chat.dat", CVAR_ARCHIVE, 0, qfalse }, + { &g_adminExpireTime, "g_adminExpireTime", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_devmapKillerHP, "g_devmapKillerHP", "0", CVAR_ARCHIVE, 0, qtrue }, - { &g_killerHP, "g_killerHP", "0", CVAR_ARCHIVE, 0, qtrue }, + { &g_killerHP, "g_killerHP", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_tag, "g_tag", "main", CVAR_INIT, 0, qfalse }, { &g_dretchPunt, "g_dretchPunt", "1", CVAR_ARCHIVE, 0, qfalse }, + + { &g_killingSpree, "g_killingSpree", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_feedingSpree, "g_feedingSpree", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_bleedingSpree, "g_bleedingSpree", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_bleedingSpreeKick, "g_bleedingSpreeKick", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_autoRevert, "g_autoRevert", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_msg, "g_msg", "", CVAR_ARCHIVE, 0, qfalse }, { &g_msgTime, "g_msgTime", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_welcomeMsg, "g_welcomeMsg", "", CVAR_ARCHIVE, 0, qfalse }, { &g_welcomeMsgTime, "g_welcomeMsgTime", "0", CVAR_ARCHIVE, 0, qfalse }, + + { &g_turretAim, "g_turretAim", "0", CVAR_ARCHIVE, 0, qfalse }, + + { &g_modBuildableHealth, "g_modBuildableHealth", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_modBuildableSpeed, "g_modBuildableSpeed", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_modBuildableCount, "g_modBuildableCount", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_modHumanStamina, "g_modHumanStamina", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_modHumanHealth, "g_modHumanHealth", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_modAlienHealth, "g_modAlienHealth", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_modAlienRegenRange, "g_modAlienRegenRange", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_modHumanRate, "g_modHumanRate", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_modAlienRate, "g_modAlienRate", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_modWeaponAmmo, "g_modWeaponAmmo", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_modWeaponReload, "g_modWeaponReload", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_modTurretAngle, "g_modTurretAngle", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_modStage3Strength, "g_modStage3Strength", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_modMainStrength, "g_modMainStrength", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_rankings, "g_rankings", "0", 0, 0, qfalse }, { &g_allowShare, "g_allowShare", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse}, { &g_creditOverflow, "g_creditOverflow", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse}, { &g_banNotice, "g_banNotice", "", CVAR_ARCHIVE, 0, qfalse }, - { &mod_jetpackFuel, "mod_jetpackFuel", "0", CVAR_ARCHIVE, 0, qtrue }, + { &mod_jetpackFuel, "mod_jetpackFuel", "0", CVAR_ARCHIVE, 0, qfalse }, { &mod_jetpackConsume, "mod_jetpackConsume", "2", CVAR_ARCHIVE, 0, qfalse }, - { &mod_jetpackRegen, "mod_jetpackRegen", "3", CVAR_ARCHIVE, 0, qfalse }, - - { &g_teamKillThreshold, "g_teamKillThreshold", "0", CVAR_ARCHIVE, 0, qfalse }, + { &mod_jetpackRegen, "mod_jetpackRegen", "3", CVAR_ARCHIVE, 0, qfalse } - { &g_aimbotAdvertBan, "g_aimbotAdvertBan", "0", CVAR_ARCHIVE, 0, qfalse }, - { &g_aimbotAdvertBanTime, "g_aimbotAdvertBanTime", "0", CVAR_ARCHIVE, 0, qfalse }, - { &g_aimbotAdvertBanReason, "g_aimbotAdvertBanReason", "AUTOBAN: AIMBOT", CVAR_ARCHIVE, 0, qfalse } }; static int gameCvarTableSize = sizeof( gameCvarTable ) / sizeof( gameCvarTable[ 0 ] ); @@ -676,6 +757,23 @@ void G_UpdateCvars( void ) G_RemapTeamShaders( ); } +static void G_InitModCvars( void ) +{ + BG_MOD_set( MOD_BG_BUILDABLE_HEALTH, g_modBuildableHealth.integer ); + BG_MOD_set( MOD_BG_BUILDABLE_SPEED, g_modBuildableSpeed.integer ); + BG_MOD_set( MOD_BG_HUMAN_STAMINA, g_modHumanStamina.integer ); + BG_MOD_set( MOD_BG_HUMAN_HEALTH, g_modHumanHealth.integer ); + BG_MOD_set( MOD_BG_ALIEN_HEALTH, g_modAlienHealth.integer ); + BG_MOD_set( MOD_BG_HUMAN_RATE, g_modHumanRate.integer ); + BG_MOD_set( MOD_BG_ALIEN_RATE, g_modAlienRate.integer ); + BG_MOD_set( MOD_BG_WEAPON_AMMO, g_modWeaponAmmo.integer ); + BG_MOD_set( MOD_BG_WEAPON_RELOAD, g_modWeaponReload.integer ); + BG_MOD_set( MOD_BG_TURRET_ANGLE, g_modTurretAngle.integer ); + BG_MOD_set( MOD_BG_STAGE3_STRENGTH, g_modStage3Strength.integer ); + + BG_MOD_update( ); +} + /* ================= G_MapConfigs @@ -813,13 +911,29 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) // load up a custom building layout if there is one G_LayoutLoad( ); - - // load any nobuild markers that have been saved - G_NobuildLoad( ); + + // load up nobuild zones + nobuild_init( ); // the map might disable some things BG_InitAllowedGameElements( ); + // practice counter + if( g_practiceCount.integer > 0 ) + { + trap_Cvar_Set( "g_practiceCount", va( "%d", g_practiceCount.integer - 1 ) ); + } + + // free credits expiration + if( g_freeCredits.integer > 1 ) + { + trap_Cvar_Set( "g_freeCredits", + va( "%d", ( g_freeCredits.integer > 2 ) ? g_freeCredits.integer - 1 : 0 ) ); + } + + // syncronize mod cvars + G_InitModCvars( ); + // general initialization G_FindTeams( ); @@ -841,6 +955,7 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) trap_Cvar_Set( "g_humanKills", 0 ); trap_Cvar_Set( "g_suddenDeath", 0 ); level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000; + trap_Cvar_Set( "g_vampireDeath", 0 ); G_Printf( "-----------------------------------\n" ); @@ -891,6 +1006,8 @@ void G_ShutdownGame( int restart ) G_Printf( "==== ShutdownGame ====\n" ); + G_admin_chat_writeconfig(); + if( level.logFile ) { G_LogPrintf( "ShutdownGame:\n" ); @@ -898,15 +1015,13 @@ void G_ShutdownGame( int restart ) trap_FS_FCloseFile( level.logFile ); } - // write admin.dat for !seen data - admin_writeconfig(); - // write all the client session data so we can get it back G_WriteSessionData( ); G_admin_cleanup( ); G_admin_namelog_cleanup( ); G_admin_adminlog_cleanup( ); + G_admin_tklog_cleanup( ); level.restarted = qfalse; level.surrenderTeam = PTE_NONE; @@ -1281,6 +1396,15 @@ int G_TimeTilSuddenDeath( void ) return ( ( level.suddenDeathBeginTime ) - ( level.time - level.startTime ) ); } +int G_TimeTilVampireDeath( void ) +{ + if( !g_vampireDeathTime.integer ) + return 3600000; // Always some time away + + return ( g_vampireDeathTime.integer * 60000 ) - + ( level.time - level.startTime ); +} + #define PLAYER_COUNT_MOD 5.0f @@ -1366,6 +1490,43 @@ void G_CalculateBuildPoints( void ) } } + //vampire death + if( !g_vampireDeath.integer && level.vampireDeath ) + { + level.vampireDeath = 0; + level.vampireDeathWarning = 0; + } + if( !level.vampireDeath ) + { + if( g_vampireDeath.integer || G_TimeTilVampireDeath( ) <= 0 ) //Conditions to enter VD + { + if( level.vampireDeathWarning < TW_PASSED ) + { + trap_SendServerCommand( -1, "cp \"^1Vampire Sudden Death!\"" ); + level.vampireDeath = 1; + level.vampireDeathBeginTime = level.time; + level.vampireDeathWarning = TW_PASSED; + g_vampireDeath.integer = 1; + } + } + else if( G_TimeTilVampireDeath( ) <= 60000 && level.vampireDeathWarning < TW_IMMINENT ) + { + trap_SendServerCommand( -1, + va( "cp \"^1Vampire\n^7Sudden Death in 60 seconds!%s%s\"", + ( g_vampireDeathInfo.string[ 0 ] ) ? "\n\n" : "", + g_vampireDeathInfo.string ) ); + level.vampireDeathWarning = TW_IMMINENT; + } + else if( G_TimeTilVampireDeath( ) <= 300000 && level.vampireDeathWarning < TW_SOON ) + { + trap_SendServerCommand( -1, + va ("cp \"^1Vampire\n^7Sudden Death in 5 minutes!%s%s\"", + ( g_vampireDeathInfo.string[ 0 ] ) ? "\n\n" : "", + g_vampireDeathInfo.string ) ); + level.vampireDeathWarning = TW_SOON; + } + } + //set BP at each cycle if( g_suddenDeath.integer ) { @@ -1378,8 +1539,34 @@ void G_CalculateBuildPoints( void ) localATP = g_alienBuildPoints.integer; } - level.humanBuildPoints = level.humanBuildPointsPowered = localHTP; - level.alienBuildPoints = localATP; + // Build Point Recovery + // each gained stage adds 50% delay + if( g_buildPointsRecoverRate.integer ) + { + while( level.humanRecoverBuildPoints > 0 && + level.humanRecoverTime < level.time ) + { + level.humanRecoverBuildPoints--; + level.humanRecoverTime += + ( 60000 + g_humanStage.integer * 30000 ) / g_buildPointsRecoverRate.integer; + } + while( level.alienRecoverBuildPoints > 0 && + level.alienRecoverTime < level.time ) + { + level.alienRecoverBuildPoints--; + level.alienRecoverTime += + ( 60000 + g_alienStage.integer * 30000 ) / g_buildPointsRecoverRate.integer; + } + } + else + { + // recover may have been turned off, reset + level.humanRecoverBuildPoints = 0; + level.alienRecoverBuildPoints = 0; + } + + level.humanBuildPoints = level.humanBuildPointsPowered = localHTP - level.humanRecoverBuildPoints; + level.alienBuildPoints = localATP - level.alienRecoverBuildPoints; level.reactorPresent = qfalse; level.overmindPresent = qfalse; @@ -1392,6 +1579,9 @@ void G_CalculateBuildPoints( void ) if( ent->s.eType != ET_BUILDABLE ) continue; + if( ent->s.eFlags & EF_DEAD ) + continue; + buildable = ent->s.modelindex; if( buildable != BA_NONE ) @@ -1612,6 +1802,7 @@ void CalculateRanks( void ) level.numHumanClients = 0; level.numLiveAlienClients = 0; level.numLiveHumanClients = 0; + level.bleeders = 0; for( i = 0; i < level.maxclients; i++ ) { @@ -1644,6 +1835,9 @@ void CalculateRanks( void ) if( level.clients[ i ].sess.sessionTeam != TEAM_SPECTATOR ) level.numLiveHumanClients++; } + + if( level.clients[ i ].pers.bleeder ) + level.bleeders++; } } } @@ -1940,7 +2134,7 @@ void QDECL G_AdminsPrintf( const char *fmt, ... ) for( j = 0; j < level.maxclients; j++ ) { tempent = &g_entities[ j ]; - if( G_admin_permission( tempent, ADMF_ADMINCHAT ) && + if( G_admin_permission( tempent, ADMF_ADMINCHAT) && !tempent->client->pers.ignoreAdminWarnings ) { trap_SendServerCommand(tempent-g_entities,va( "print \"^6[Admins]^7 %s\"", string) ); @@ -1950,37 +2144,7 @@ void QDECL G_AdminsPrintf( const char *fmt, ... ) G_LogPrintf("%s",string); } -/* -================= -G_WarningsPrintf - -Print to everyone with a certain flag, and the logfile with a time stamp if it is open, and to the console -(just a copy of the G_AdminsPrintf with flag suport) -================= -*/ -void QDECL G_WarningsPrintf( char *flag, const char *fmt, ... ) -{ - va_list argptr; - char string[ 1024 ]; - gentity_t *tempent; - int j; - - va_start( argptr, fmt ); - vsprintf( string, fmt,argptr ); - va_end( argptr ); - for( j = 0; j < level.maxclients; j++ ) - { - tempent = &g_entities[ j ]; - if( G_admin_permission( tempent, flag ) ) - { - trap_SendServerCommand(tempent-g_entities,va( "print \"^6[Warnings]^7 %s\"", string) ); - } - } - - G_LogPrintf("%s",string); - -} /* ================= G_LogPrintf @@ -2148,7 +2312,7 @@ void G_SendGameStat( pTeam_t team ) int ping; cl = &level.clients[ level.sortedClients[ i ] ]; - + // Ignore invisible players if ( cl->sess.invisible == qtrue ) continue; @@ -2474,6 +2638,8 @@ void CheckVote( void ) { int votePassThreshold=level.votePassThreshold; int voteYesPercent; + int minVotes = 0; + qboolean pass = qfalse; if( level.voteExecuteTime && level.voteExecuteTime < level.time ) { @@ -2487,7 +2653,10 @@ void CheckVote( void ) { G_admin_maplog_result( "m" ); } - + else if( !Q_stricmpn( level.voteString, "!restart", 8 ) ) + { + G_admin_maplog_result( "l" ); + } if( !Q_stricmp( level.voteString, "suddendeath" ) ) { @@ -2512,6 +2681,13 @@ void CheckVote( void ) if( !level.voteTime ) return; + if( !Q_stricmpn( level.voteString, "ban", 3 ) ) + { + minVotes = 3; + if( level.numConnectedClients < minVotes ) + minVotes = level.numConnectedClients; + } + if( level.voteYes + level.voteNo > 0 ) voteYesPercent = (int)( 100 * ( level.voteYes ) / ( level.voteYes + level.voteNo ) ); else @@ -2520,20 +2696,22 @@ void CheckVote( void ) if( ( level.time - level.voteTime >= VOTE_TIME ) || ( level.voteYes + level.voteNo == level.numConnectedClients ) ) { - if( voteYesPercent> votePassThreshold || level.voteNo == 0 ) + if( level.voteYes + level.voteNo < minVotes ) + { + // not enough voters + trap_SendServerCommand( -1, va( "print \"This vote type requires at least %d voters\n\"", minVotes ) ); + pass = qfalse; + } + else if( voteYesPercent > votePassThreshold ) { // execute the command, then remove the vote - trap_SendServerCommand( -1, va("print \"Vote passed (%d - %d)\n\"", - level.voteYes, level.voteNo ) ); - G_LogPrintf( "Vote: Vote passed (%d-%d)\n", level.voteYes, level.voteNo ); level.voteExecuteTime = level.time + 3000; + pass = qtrue; } else { // same behavior as a timeout - trap_SendServerCommand( -1, va("print \"Vote failed (%d - %d)\n\"", - level.voteYes, level.voteNo ) ); - G_LogPrintf( "Vote: Vote failed (%d - %d)\n", level.voteYes, level.voteNo ); + pass = qfalse; } } else @@ -2542,18 +2720,14 @@ void CheckVote( void ) ( (double) votePassThreshold/100.0 ) ) ) { // execute the command, then remove the vote - trap_SendServerCommand( -1, va("print \"Vote passed (%d - %d)\n\"", - level.voteYes, level.voteNo ) ); - G_LogPrintf( "Vote: Vote passed (%d - %d)\n", level.voteYes, level.voteNo ); level.voteExecuteTime = level.time + 3000; + pass = qtrue; } else if( level.voteNo > (int)( (double) level.numConnectedClients * ( (double) ( 100.0-votePassThreshold )/ 100.0 ) ) ) { // same behavior as a timeout - trap_SendServerCommand( -1, va("print \"Vote failed (%d - %d)\n\"", - level.voteYes, level.voteNo ) ); - G_LogPrintf("Vote failed\n"); + pass = qfalse; } else { @@ -2562,6 +2736,22 @@ void CheckVote( void ) } } + trap_SendServerCommand( -1, + va( "print \"Vote %s^7 (^2Y:%d^7-^1N:%d^7, %d percent) (%s)\n\"", + ( pass ) ? "^2passed" : "^1failed", + level.voteYes, level.voteNo, voteYesPercent, + level.voteDisplayString ) ); + + G_LogPrintf( "Vote: Vote %s (%d - %d)\n", + ( pass ) ? "passed" : "failed", + level.voteYes, level.voteNo ); + + G_admin_adminlog_log( NULL, "vote", + va( "%s^7 (^2Y:%d^7-^1N:%d^7, %d percent)", + ( pass ) ? "^2passed" : "^1failed", + level.voteYes, level.voteNo, voteYesPercent ), + 0, pass ); + level.voteTime = 0; trap_SetConfigstring( CS_VOTE_TIME, "" ); trap_SetConfigstring( CS_VOTE_STRING, "" ); @@ -2576,6 +2766,9 @@ CheckTeamVote void CheckTeamVote( int team ) { int cs_offset; + int votePassThreshold; + int voteYesPercent = 0; + qboolean pass = qfalse; if ( team == PTE_HUMANS ) cs_offset = 0; @@ -2584,38 +2777,44 @@ void CheckTeamVote( int team ) else return; + votePassThreshold = level.teamVotePassThreshold[ cs_offset ]; + if( !level.teamVoteTime[ cs_offset ] ) return; + if( ( level.teamVoteYes[ cs_offset ] + level.teamVoteNo[ cs_offset ] ) ) + { + voteYesPercent = (100 * level.teamVoteYes[ cs_offset ]) / + (level.teamVoteYes[ cs_offset ] + level.teamVoteNo[ cs_offset ]); + } + if( level.time - level.teamVoteTime[ cs_offset ] >= VOTE_TIME ) { - if( level.teamVoteYes[ cs_offset ] > level.teamVoteNo[ cs_offset ] && level.teamVoteYes[ cs_offset ] >= 2 ) + if( voteYesPercent > votePassThreshold && level.teamVoteYes[ cs_offset ] >= 2 ) { // execute the command, then remove the vote - trap_SendServerCommand( -1, va("print \"Team vote passed (%d - %d)\n\"", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ) ); trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.teamVoteString[ cs_offset ] ) ); + pass = qtrue; } else { - trap_SendServerCommand( -1, va("print \"Team vote failed (%d - %d)\n\"", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ) ); - G_LogPrintf( "Teamvote: Team vote failed (%d - %d)\n", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ); + pass = qfalse; } } else { - if( level.teamVoteYes[ cs_offset ] > level.numteamVotingClients[ cs_offset ] / 2 ) + if( level.teamVoteYes[ cs_offset ] > (int)((double)level.numteamVotingClients[ cs_offset ] * + (double)votePassThreshold/100.0) ) { // execute the command, then remove the vote - trap_SendServerCommand( -1, va("print \"Team vote passed (%d - %d)\n\"", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ) ); - G_LogPrintf( "Teamvote: Team vote passed (%d - %d)\n", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ); - // trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.teamVoteString[ cs_offset ] ) ); + pass = qtrue; } - else if( level.teamVoteNo[ cs_offset ] >= level.numteamVotingClients[ cs_offset ] / 2 ) + else if( level.teamVoteNo[ cs_offset ] > (int)((double)level.numteamVotingClients[ cs_offset ] * + ((double)(100.0-votePassThreshold)/100.0)) ) { // same behavior as a timeout - trap_SendServerCommand( -1, va("print \"Team vote failed (%d - %d)\n\"", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ) ); - G_LogPrintf( "Teamvote: Team vote failed (%d - %d)\n", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ); + pass = qfalse; } else { @@ -2624,11 +2823,70 @@ void CheckTeamVote( int team ) } } + trap_SendServerCommand( -1, + va( "print \"Team vote %s^7 (^2Y:%d^7-^1N:%d^7, %d percent)\n\"", + ( pass ) ? "^2passed" : "^1failed", + level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ], voteYesPercent ) ); + + G_LogPrintf( "Teamvote: Team vote %s (%d - %d)\n", + ( pass ) ? "passed" : "failed", + level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ); + + G_admin_adminlog_log( NULL, "teamvote", + va( "%s^7 (^2Y:%d^7-^1N:%d^7, %d percent)", + ( pass ) ? "^2passed" : "^1failed", + level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ], voteYesPercent ), + 0, pass ); + level.teamVoteTime[ cs_offset ] = 0; trap_SetConfigstring( CS_TEAMVOTE_TIME + cs_offset, "" ); trap_SetConfigstring( CS_TEAMVOTE_STRING + cs_offset, "" ); } + +/* +================== +CheckIdleMap +================== +*/ +void CheckIdleMap( void ) +{ + static int nextCheck = 0; + static int idleCount = 0; + static qboolean triggered = qfalse; + + if( !g_idleMapSwitch.integer ) + return; + + if( triggered || + nextCheck > level.time ) + return; + + nextCheck = level.time + 60000; + if( !level.numPlayingClients ) + idleCount++; + else + idleCount = 0; + + if( idleCount > g_idleMapSwitch.integer && + !G_CurrentMapIsRotation() ) + { + int newTL; + + triggered = qtrue; + + newTL = ( level.time - level.startTime ) / 60000; + newTL += 5; + if( !g_timelimit.integer || g_timelimit.integer > newTL ) + { + trap_Cvar_Set( "timelimit", va( "%d", newTL ) ); + trap_SendServerCommand( -1, va( "print \"Timelimit reduced due to idle server\n\"" ) ); + G_LogPrintf( "Server is idle, timelimit reduced to %d\n", newTL ); + } + } +} + + /* ================== CheckMsgTimer @@ -2723,6 +2981,60 @@ void CheckCountdown( void ) /* ================== +G_CalculateDynamicBuildPoints +================== +*/ +static void G_CalculateDynamicBuildPoints( void ) +{ + static int LastTime = 0; + int count = 0; + gentity_t *ent; + int bps; + int i; + + if( !g_dynamicBuildPoints.integer ) + return; + + if( level.time - LastTime < 10000 ) + return; + + if( level.time - level.startTime < 30000 || + !level.defaultAlienBuildPoints ) + { + // remember default build points, after some time for exec commands + if( level.time - level.startTime > 1000 && + level.defaultAlienBuildPoints == 0 ) + { + level.defaultAlienBuildPoints = g_alienBuildPoints.integer; + level.defaultHumanBuildPoints = g_humanBuildPoints.integer; + } + return; + } + + LastTime = level.time; + + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + if( ent && ent->client && + ent->client->pers.connected == CON_CONNECTED && + (ent->client->pers.teamSelection == PTE_ALIENS || ent->client->pers.teamSelection == PTE_HUMANS) ) + { + count++; + } + } + + bps = level.defaultAlienBuildPoints + count * g_dynamicBuildPoints.integer; + if( g_alienBuildPoints.integer < bps ) + trap_Cvar_Set( "g_alienBuildPoints", va( "%d", bps ) ); + + bps = level.defaultHumanBuildPoints + count * g_dynamicBuildPoints.integer; + if( g_humanBuildPoints.integer < bps ) + trap_Cvar_Set( "g_humanBuildPoints", va( "%d", bps ) ); +} + +/* +================== CheckCvars ================== */ @@ -2773,6 +3085,179 @@ void CheckCvars( void ) } /* +================== +G_CheckVampireDeathBuildables +================== +*/ +static int G_VampireDeathKillBuildable( buildable_t buildable, int count ) +{ + int i; + gentity_t *ent; + int n = 0; + + for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( ent->s.modelindex == buildable && ent->health > 0 ) + { + G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); + n++; + + if( count && n >= count ) + return n; + } + } + + return n; +} + +static void G_CheckVampireDeathBuildables( void ) +{ + static int LastAmmoTime = 0; + static int LastSuckTime = 0; + static int LastDestructTime = 0; + int i; + gentity_t *ent; + qboolean done = qtrue; + + if( !level.vampireDeath ) + return; + + if( level.intermissionQueued || + level.intermissiontime ) + return; + + // humans get more ammo every 20 seconds + if( level.time - LastAmmoTime >= 20000 ) + { + LastAmmoTime = level.time; + + for( i = 0; i < level.maxclients; i++ ) + { + ent = g_entities + i; + if( !ent->inuse || ent->health <= 0 || + ent->client->pers.connected != CON_CONNECTED) + continue; + + if( ent->client->pers.teamSelection == PTE_HUMANS ) + { + int ammo, clips, maxClips, maxAmmo; + + BG_FindAmmoForWeapon( ent->client->ps.weapon, &maxAmmo, &maxClips ); + BG_UnpackAmmoArray( ent->client->ps.weapon, ent->client->ps.ammo, + ent->client->ps.powerups, &ammo, &clips ); + + if( maxClips ) + { + if( clips < maxClips ) + clips++; + } + else + { + ammo += maxAmmo / 5; + if( ammo > maxAmmo ) + ammo = maxAmmo; + } + + BG_PackAmmoArray( ent->client->ps.weapon, ent->client->ps.ammo, + ent->client->ps.powerups, ammo, clips ); + } + } + } + + // health countdown + if( level.time - LastSuckTime >= 3000 ) + { + int value; + int hp_rate; + int damage; + + LastSuckTime = level.time; + + // increase health removal each minute after 1 + hp_rate = 1 + ( level.time - level.vampireDeathBeginTime ) / 60000; + if( hp_rate < 1 ) hp_rate = 1; + if( hp_rate > 10) hp_rate = 10; + + for( i = 0; i < level.maxclients; i++ ) + { + ent = g_entities + i; + if( !ent->inuse || + ent->health <= 0 || + !ent->client || + ent->client->pers.connected != CON_CONNECTED || + ent->client->sess.sessionTeam == TEAM_SPECTATOR || + ( ent->client->pers.teamSelection != PTE_ALIENS && ent->client->pers.teamSelection != PTE_HUMANS ) ) + continue; + + value = BG_FindHealthForClass( ent->client->pers.classSelection ); + if( value < 1 ) + value = 1; + // death from full HP in 60s with damage every 3s, 1000 / 60 * 3 = 50 + ent->client->pers.vampireSuckFraction += value * 50 * hp_rate; + + damage = ent->client->pers.vampireSuckFraction / 1000; + ent->client->pers.vampireSuckFraction -= damage * 1000; + + if ( ent->health > damage ) + ent->health -= damage; + else + G_Damage( ent, NULL, NULL, NULL, NULL, damage, + DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_PROTECTION|DAMAGE_NO_LOCDAMAGE, MOD_SUICIDE ); + } + } + + // base self destruct + if( level.vampireDeath == 2 ) + return; + + if( level.time - LastDestructTime < 1000 ) + return; + + LastDestructTime = level.time; + + if( level.numLiveAlienClients == 0 && + level.numLiveHumanClients == 0 ) + { + level.lastWin = PTE_NONE; + trap_SendServerCommand( -1, "print \"Timelimit hit\n\"" ); + trap_SetConfigstring( CS_WINNER, "Stalemate" ); + LogExit( "Timelimit hit." ); + G_admin_maplog_result( "t" ); + } + + if( G_VampireDeathKillBuildable( BA_A_ACIDTUBE, 1 ) || + G_VampireDeathKillBuildable( BA_A_HIVE, 1 ) || + G_VampireDeathKillBuildable( BA_A_TRAPPER, 1 ) || + G_VampireDeathKillBuildable( BA_A_BARRICADE, 1 ) || + G_VampireDeathKillBuildable( BA_A_BOOSTER, 1 ) || + G_VampireDeathKillBuildable( BA_A_SPAWN, 1 ) || + G_VampireDeathKillBuildable( BA_A_OVERMIND, 1 ) ) + { + done = qfalse; + } + + if( G_VampireDeathKillBuildable( BA_H_MGTURRET, 1 ) || + G_VampireDeathKillBuildable( BA_H_TESLAGEN, 1 ) || + G_VampireDeathKillBuildable( BA_H_DCC, 1 ) || + G_VampireDeathKillBuildable( BA_H_REPEATER, 1 ) || + G_VampireDeathKillBuildable( BA_H_MEDISTAT, 1 ) || + G_VampireDeathKillBuildable( BA_H_ARMOURY, 1 ) || + G_VampireDeathKillBuildable( BA_H_SPAWN, 1 ) || + G_VampireDeathKillBuildable( BA_H_REACTOR, 1 ) ) + { + done = qfalse; + } + + if( done ) + { + level.vampireDeath = 2; + } +} + +/* ============= G_RunThink @@ -2960,6 +3445,8 @@ void G_RunFrame( int levelTime ) ClientEndFrame( ent ); } + G_CheckVampireDeathBuildables(); + // save position information for all active clients G_UnlaggedStore( ); @@ -2974,8 +3461,11 @@ void G_RunFrame( int levelTime ) G_CalculateAvgPlayers( ); G_UpdateZaps( msec ); + G_CalculateDynamicBuildPoints( ); + // see if it is time to end the level CheckExitRules( ); + CheckIdleMap( ); // update to team status? CheckTeamStatus( ); diff --git a/src/game/g_maprotation.c b/src/game/g_maprotation.c index 60fd696..eb76d70 100644 --- a/src/game/g_maprotation.c +++ b/src/game/g_maprotation.c @@ -334,68 +334,6 @@ static qboolean G_ParseMapRotation( mapRotation_t *mr, char **text_p ) continue; } - else if( !Q_stricmp( token, "*RANDOM*" ) ) - { - if( mr->numMaps == MAX_MAP_ROTATION_MAPS ) - { - G_Printf( S_COLOR_RED "ERROR: maximum number of maps in one rotation (%d) reached\n", - MAX_MAP_ROTATION_MAPS ); - return qfalse; - } - mre = &mr->maps[ mr->numMaps ]; - Q_strncpyz( mre->name, token, sizeof( mre->name ) ); - - token = COM_Parse( text_p ); - - if( !Q_stricmp( token, "{" ) ) - { - while( 1 ) - { - token = COM_Parse( text_p ); - - if( !token ) - break; - - if( !Q_stricmp( token, "}" ) ) - { - break; - } - else - { - if( mre->numConditions < MAX_MAP_ROTATION_CONDS ) - { - mrc = &mre->conditions[ mre->numConditions ]; - mrc->lhs = MCV_SELECTEDRANDOM; - mrc->unconditional = qfalse; - Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) ); - - mre->numConditions++; - } - else - { - G_Printf( S_COLOR_YELLOW "WARNING: maximum number of maps for one Random Slot (%d) reached\n", - MAX_MAP_ROTATION_CONDS ); - } - } - } - if( !mre->numConditions ) - { - G_Printf( S_COLOR_YELLOW "WARNING: no maps in *RANDOM* section\n" ); - } - else - { - mr->numMaps++; - mnSet = qtrue; - } - } - else - { - G_Printf( S_COLOR_RED "ERROR: *RANDOM* with no section\n" ); - return qfalse; - } - - continue; - } else if( !Q_stricmp( token, "}" ) ) return qtrue; //reached the end of this map rotation @@ -526,7 +464,6 @@ static qboolean G_ParseMapRotationFile( const char *fileName ) for( j = 0; j < mapRotations.rotations[ i ].numMaps; j++ ) { if( Q_stricmp( mapRotations.rotations[ i ].maps[ j ].name, "*VOTE*") != 0 && - Q_stricmp( mapRotations.rotations[ i ].maps[ j ].name, "*RANDOM*") != 0 && !G_MapExists( mapRotations.rotations[ i ].maps[ j ].name ) ) { G_Printf( S_COLOR_RED "ERROR: map \"%s\" doesn't exist\n", @@ -696,21 +633,6 @@ static void G_IssueMapChange( int rotation ) return; } } - else if(!Q_stricmp( newmap, "*RANDOM*") ) - { - fileHandle_t f; - - G_GetRandomMap( newmap, sizeof( newmap ), rotation, map ); - if( trap_FS_FOpenFile( va("maps/%s.bsp", newmap), &f, FS_READ ) > 0 ) - { - trap_FS_FCloseFile( f ); - } - else - { - G_AdvanceMapRotation(); - return; - } - } // allow a manually defined g_layouts setting to override the maprotation if( !g_layouts.string[ 0 ] && @@ -720,8 +642,7 @@ static void G_IssueMapChange( int rotation ) mapRotations.rotations[ rotation ].maps[ map ].layouts ); } - trap_SendConsoleCommand( EXEC_APPEND, va( "map %s\n", - newmap ) ); + trap_SendConsoleCommand( EXEC_APPEND, va( "map %s\n", newmap ) ); // load up map defaults if g_mapConfigs is set G_MapConfigs( newmap ); @@ -808,9 +729,6 @@ static qboolean G_EvaluateMapCondition( mapRotationCondition_t *mrc ) case MCV_VOTE: // ignore vote for conditions; break; - case MCV_SELECTEDRANDOM: - // ignore vote for conditions; - break; default: case MCV_ERR: @@ -940,6 +858,51 @@ qboolean G_MapRotationActive( void ) /* =============== +G_CurrentMapIsRotation + +Test if the current map is from the rotation +=============== +*/ + +qboolean G_CurrentMapIsRotation( void ) +{ + char mapname[ 64 ]; + int map, rotation; + int i; + + // Check for an active map rotation, + // only return false if rotation is running and current map is not from it + if ( !G_MapRotationActive() || g_currentMapRotation.integer == NOT_ROTATING ) + return qtrue; + + rotation = g_currentMapRotation.integer; + if( !( rotation >= 0 && rotation < mapRotations.numRotations ) ) + return qtrue; + + map = G_GetCurrentMap( rotation ); + if( !(map >= 0 && map < mapRotations.rotations[ rotation ].numMaps ) ) + return qtrue; + + trap_Cvar_VariableStringBuffer( "mapname", mapname, sizeof( mapname ) ); + + if( !Q_stricmp( mapRotations.rotations[ rotation ].maps[ map ].name, mapname ) ) + return qtrue; + + if( !Q_stricmp( mapRotations.rotations[ rotation ].maps[ map ].name, "*VOTE*" ) ) + { + for( i = 0; i < mapRotations.rotations[ rotation ].maps[ map ].numConditions; i++ ) + { + if( mapRotations.rotations[ rotation ].maps[ map ].conditions[ i ].lhs == MCV_VOTE && + !Q_stricmp( mapRotations.rotations[ rotation ].maps[ map ].conditions[ i ].dest, mapname ) ) + return qtrue; + } + } + + return qfalse; +} + +/* +=============== G_InitMapRotations Load and intialise the map rotations @@ -1173,9 +1136,6 @@ static void G_IntermissionMapVoteMessageReal( gentity_t *ent, int winner, int wi "^7+------------------+\n", sizeof( string ) ); for( i = 0; i < rotationVoteLen; i++ ) { - if( !G_MapExists( rotationVoteList[ i ] ) ) - continue; - if( i == selection ) color = "^5"; else if( i == index ) @@ -1275,42 +1235,3 @@ void G_IntermissionMapVoteCommand( gentity_t *ent, qboolean next, qboolean choos G_IntermissionMapVoteMessage( ent ); } -static qboolean G_GetRandomMap( char *name, int size, int rotation, int map ) -{ - mapRotation_t *mr; - mapRotationEntry_t *mre; - mapRotationCondition_t *mrc; - int i, nummaps; - int randompick = 0; - int maplist[ 32 ]; - - mr = &mapRotations.rotations[ rotation ]; - mre = &mr->maps[ map ]; - - nummaps = 0; - //count the number of map votes - for( i = 0; i < mre->numConditions; i++ ) - { - mrc = &mre->conditions[ i ]; - - if( mrc->lhs == MCV_SELECTEDRANDOM ) - { - //map doesnt exist - if( !G_MapExists( mrc->dest ) ) { - continue; - } - maplist[ nummaps ] = i; - nummaps++; - } - } - - if( nummaps == 0 ) { - return qfalse; - } - - randompick = (int)( random() * nummaps ); - - Q_strncpyz( name, mre->conditions[ maplist[ randompick ] ].dest, size ); - - return qtrue; -} diff --git a/src/game/g_mem.c b/src/game/g_mem.c index 6935194..ea8e438 100644 --- a/src/game/g_mem.c +++ b/src/game/g_mem.c @@ -23,7 +23,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "g_local.h" -#define POOLSIZE ( 1024 * 1024 ) +#define POOLSIZE ( 1024 * 1024 * 4 ) #define FREEMEMCOOKIE ((int)0xDEADBE3F) // Any unlikely to be used value #define ROUNDBITS 31 // Round to 32 bytes diff --git a/src/game/g_missile.c b/src/game/g_missile.c index 6317636..71b5432 100644 --- a/src/game/g_missile.c +++ b/src/game/g_missile.c @@ -571,6 +571,12 @@ void AHive_ReturnToHive( gentity_t *self ) self->think = G_ExplodeMissile; self->nextthink = level.time + 15000; } + + if( g_modStage3Strength.integer > 0 ) + { + int nt = self->nextthink - level.time; + self->nextthink = level.time + nt * 100 / g_modStage3Strength.integer; + } } /* @@ -584,6 +590,7 @@ void AHive_SearchAndDestroy( gentity_t *self ) { vec3_t dir; trace_t tr; + int speed; trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs, self->target_ent->r.currentOrigin, self->r.ownerNum, self->clipmask ); @@ -603,13 +610,26 @@ void AHive_SearchAndDestroy( gentity_t *self ) VectorSubtract( self->target_ent->r.currentOrigin, self->r.currentOrigin, dir ); VectorNormalize( dir ); + if( g_modStage3Strength.integer > 0 ) + speed = HIVE_SPEED * g_modStage3Strength.integer / 100; + else + speed = HIVE_SPEED; + //change direction towards the player - VectorScale( dir, HIVE_SPEED, self->s.pos.trDelta ); + VectorScale( dir, speed, self->s.pos.trDelta ); SnapVector( self->s.pos.trDelta ); // save net bandwidth VectorCopy( self->r.currentOrigin, self->s.pos.trBase ); self->s.pos.trTime = level.time; self->nextthink = level.time + HIVE_DIR_CHANGE_PERIOD; + + if( g_modBuildableSpeed.integer > 0) + self->nextthink = level.time + HIVE_DIR_CHANGE_PERIOD * 100 / g_modBuildableSpeed.integer; + if( g_modStage3Strength.integer > 0) + { + int nt = self->nextthink - level.time; + self->nextthink = level.time + nt * 100 / g_modStage3Strength.integer; + } } } @@ -642,6 +662,12 @@ gentity_t *fire_hive( gentity_t *self, vec3_t start, vec3_t dir ) bolt->clipmask = MASK_SHOT; bolt->target_ent = self->target_ent; + if( g_modBuildableSpeed.integer > 0) + { + bolt->nextthink = level.time + HIVE_DIR_CHANGE_PERIOD * 100 / g_modBuildableSpeed.integer; + bolt->damage = bolt->damage * g_modBuildableSpeed.integer / 100; + } + bolt->s.pos.trType = TR_LINEAR; bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame VectorCopy( start, bolt->s.pos.trBase ); @@ -805,6 +831,9 @@ gentity_t *fire_bounceBall( gentity_t *self, vec3_t start, vec3_t dir ) VectorCopy( start, bolt->r.currentOrigin ); /*bolt->s.eFlags |= EF_BOUNCE;*/ + if( g_modAlienRate.integer > 0 ) + bolt->damage = bolt->damage * g_modAlienRate.integer / 100; + return bolt; } diff --git a/src/game/g_session.c b/src/game/g_session.c index ef78e8a..e1705ea 100644 --- a/src/game/g_session.c +++ b/src/game/g_session.c @@ -78,10 +78,10 @@ void G_ReadSessionData( gclient_t *client ) // bk001205 - format int teamLeader; + int invisible; int spectatorState; int sessionTeam; int restartTeam; - int invisible; var = va( "session%i", client - level.clients ); trap_Cvar_VariableStringBuffer( var, s, sizeof(s) ); diff --git a/src/game/g_weapon.c b/src/game/g_weapon.c index a9e04e6..4da6205 100644 --- a/src/game/g_weapon.c +++ b/src/game/g_weapon.c @@ -701,7 +701,8 @@ void teslaFire( gentity_t *ent ) if( !traceEnt->client ) return; - if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) + if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS && + !traceEnt->client->pers.bleeder ) return; //so the client side knows @@ -709,8 +710,13 @@ void teslaFire( gentity_t *ent ) if( traceEnt->takedamage ) { + int dmg = TESLAGEN_DMG; + + if( g_modStage3Strength.integer > 0 ) + dmg = dmg * g_modStage3Strength.integer / 100; + G_Damage( traceEnt, ent, ent, forward, tr.endpos, - TESLAGEN_DMG, 0, MOD_TESLAGEN ); + dmg, 0, MOD_TESLAGEN ); } // snap the endpos to integers to save net bandwidth, but nudged towards the line @@ -781,6 +787,7 @@ void cancelBuildFire( gentity_t *ent ) traceEnt->health += HBUILD_HEALRATE; ent->client->pers.statscounters.repairspoisons++; + ent->client->pers.karma += 1; level.humanStatsCounters.repairspoisons++; if( traceEnt->health > bHealth ) @@ -814,7 +821,7 @@ void buildFire( gentity_t *ent, dynMenu_t menu ) if( G_BuildIfValid( ent, ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) ) { - if( g_cheats.integer ) + if( g_cheats.integer || g_instantBuild.integer ) { ent->client->ps.stats[ STAT_MISC ] = 0; } @@ -1184,6 +1191,8 @@ static void G_CreateNewZap( gentity_t *creator, gentity_t *target ) zap->used = qtrue; zap->timeToLive = LEVEL2_AREAZAP_TIME; + if( g_modAlienRate.integer > 0 ) + zap->timeToLive = zap->timeToLive * g_modAlienRate.integer / 100; zap->damageUsed = 0; zap->creator = creator; @@ -1277,6 +1286,8 @@ void G_UpdateZaps( int msec ) { G_Damage( target, source, zap->creator, forward, target->s.origin, damage, DAMAGE_NO_KNOCKBACK | DAMAGE_NO_LOCDAMAGE, MOD_LEVEL2_ZAP ); + if( g_modAlienRate.integer > 0 ) + damage = damage * 100 / g_modAlienRate.integer; zap->damageUsed += damage; } } diff --git a/src/game/tremulous.h b/src/game/tremulous.h index 0d2d1c1..c007489 100644 --- a/src/game/tremulous.h +++ b/src/game/tremulous.h @@ -620,7 +620,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA //do to increment the stage kill counters // g_suddenDeathMode settings -#define SDMODE_BP 0 +#define SDMODE_BP 0 #define SDMODE_NO_BUILD 1 #define SDMODE_SELECTIVE 2 -#define SDMODE_NO_DECON 3 |