Index: g_local.h =================================================================== --- g_local.h (revision 872) +++ g_local.h (working copy) @@ -540,6 +540,7 @@ int previousTime; // so movers can back up when blocked int startTime; // level.time the map was started + int layoutTime; // time of last !layout finish int teamScores[ TEAM_NUM_TEAMS ]; int lastTeamLocationTime; // last time of client team location update @@ -645,6 +646,8 @@ int unlaggedIndex; int unlaggedTimes[ MAX_UNLAGGED_MARKERS ]; + + char layout[MAX_STRING_CHARS]; } level_locals_t; // @@ -1146,6 +1149,10 @@ extern vmCvar_t g_privateMessages; +extern vmCvar_t g_layout; +extern vmCvar_t g_layoutAuto; +extern vmCvar_t g_layoutEdit; + void trap_Printf( const char *fmt ); void trap_Error( const char *fmt ); int trap_Milliseconds( void ); Index: g_combat.c =================================================================== --- g_combat.c (revision 872) +++ g_combat.c (working copy) @@ -950,6 +950,9 @@ return; } + + // do not compute damage for clients during edit mode + if( g_layoutEdit.integer && (targ->client || attacker->client) ) return; client = targ->client; Index: g_buildable.c =================================================================== --- g_buildable.c (revision 872) +++ g_buildable.c (working copy) @@ -172,7 +172,7 @@ if( self->biteam != BIT_HUMANS ) return qfalse; - + //reactor is always powered if( self->s.modelindex == BA_H_REACTOR ) return qtrue; @@ -385,6 +385,9 @@ int distance = 0; int minDistance = 10000; vec3_t temp_v; + + //don't check creep in layout edit mode + if( g_layoutEdit.integer ) return qtrue; //don't check for creep if flying through the air if( self->s.groundEntityNum == -1 ) @@ -556,8 +559,9 @@ */ void ASpawn_Melt( gentity_t *self ) { - G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, - self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); + if( !g_layoutEdit.integer ) + G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, + self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); //start creep recession if( !( self->s.eFlags & EF_DEAD ) ) @@ -595,8 +599,9 @@ VectorCopy( self->s.origin2, dir ); //do a bit of radius damage - G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, - self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); + if( !g_layoutEdit.integer ) + G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, + self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); //pretty events and item cleanup self->s.eFlags |= EF_NODRAW; //don't draw the model once it's destroyed @@ -631,7 +636,7 @@ self->s.eFlags &= ~EF_FIRING; //prevent any firing effects - if( attacker && attacker->client ) + if( attacker && attacker->client && !g_layoutEdit.integer ) { if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) { @@ -736,7 +741,7 @@ VectorAdd( self->s.origin, range, maxs ); VectorSubtract( self->s.origin, range, mins ); - if( self->spawned && ( self->health > 0 ) ) + if( self->spawned && ( self->health > 0 ) && !g_layoutEdit.integer ) { //do some damage num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); @@ -824,8 +829,9 @@ VectorCopy( self->s.origin2, dir ); //do a bit of radius damage - G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, - self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); + if( !g_layoutEdit.integer ) + G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, + self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); //pretty events and item cleanup self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed @@ -967,7 +973,7 @@ return; } - if( self->spawned && findOvermind( self ) ) + if( self->spawned && findOvermind( self ) && !g_layoutEdit.integer ) { //do some damage num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); @@ -1029,11 +1035,11 @@ G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); return; } - + if( self->timestamp < level.time ) self->active = qfalse; //nothing has returned in HIVE_REPEAT seconds, forget about it - if( self->spawned && !self->active && findOvermind( self ) ) + if( self->spawned && !self->active && findOvermind( self ) && !g_layoutEdit.integer ) { //do some damage num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); @@ -1249,8 +1255,9 @@ VectorCopy( self->s.origin2, dir ); //do a bit of radius damage - G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, - self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); + if( !g_layoutEdit.integer ) + G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, + self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); //pretty events and item cleanup self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed @@ -1505,7 +1512,7 @@ return; } - if( self->spawned && findOvermind( self ) ) + if( self->spawned && findOvermind( self ) && !g_layoutEdit.integer ) { //if the current target is not valid find a new one if( !ATrapper_CheckTarget( self, self->enemy, range ) ) @@ -1553,7 +1560,7 @@ } } - if( G_NumberOfDependants( self ) == 0 ) + if( G_NumberOfDependants( self ) == 0 && !g_layoutEdit.integer ) { //if no dependants for x seconds then disappear if( self->count < 0 ) @@ -1609,7 +1616,7 @@ VectorAdd( self->s.origin, range, maxs ); VectorSubtract( self->s.origin, range, mins ); - if( self->spawned && ( self->health > 0 ) ) + if( self->spawned && ( self->health > 0 ) && !g_layoutEdit.integer ) { //do some damage num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); @@ -2043,7 +2050,7 @@ return; } - if( self->spawned ) + if( self->spawned && !g_layoutEdit.integer ) { //find a dcc for self self->dcced = findDCC( self ); @@ -2112,7 +2119,7 @@ return; } - if( self->spawned && self->count < level.time ) + if( self->spawned && self->count < level.time && !g_layoutEdit.integer ) { //used to mark client side effects self->s.eFlags &= ~EF_FIRING; @@ -2207,8 +2214,9 @@ self->timestamp = level.time; //do some radius damage - G_RadiusDamage( self->s.pos.trBase, self, self->splashDamage, - self->splashRadius, self, self->splashMethodOfDeath ); + if( !g_layoutEdit.integer ) + G_RadiusDamage( self->s.pos.trBase, self, self->splashDamage, + self->splashRadius, self, self->splashMethodOfDeath ); self->think = freeBuildable; self->nextthink = level.time + 100; @@ -2510,6 +2518,7 @@ qboolean invert; int contents; playerState_t *ps = &ent->client->ps; + int team; BG_FindBBoxForBuildable( buildable, mins, maxs ); @@ -2536,8 +2545,9 @@ reason = IBE_NORMAL; contents = trap_PointContents( entity_origin, -1 ); - - if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + + team = BG_FindTeamForBuildable( buildable ); + if( team == PTE_ALIENS ) { //alien criteria @@ -2553,7 +2563,7 @@ } //check there is creep near by for building on - if( BG_FindCreepTestForBuildable( buildable ) ) + if( BG_FindCreepTestForBuildable( buildable ) && !g_layoutEdit.integer ) { if( !isCreep( entity_origin ) ) reason = IBE_NOCREEP; @@ -2565,19 +2575,21 @@ reason = IBE_PERMISSION; //look for an Overmind - for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) - { - if( tempent->s.eType != ET_BUILDABLE ) - continue; - if( tempent->s.modelindex == BA_A_OVERMIND && tempent->spawned && - tempent->health > 0 ) - break; + if( !g_layoutEdit.integer ) { + for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) + { + if( tempent->s.eType != ET_BUILDABLE ) + continue; + if( tempent->s.modelindex == BA_A_OVERMIND && tempent->spawned && + tempent->health > 0 ) + break; + } + + //if none found... + if( i >= level.num_entities && buildable != BA_A_OVERMIND ) + reason = IBE_NOOVERMIND; } - //if none found... - if( i >= level.num_entities && buildable != BA_A_OVERMIND ) - reason = IBE_NOOVERMIND; - //can we only have one of these? if( BG_FindUniqueTestForBuildable( buildable ) ) { @@ -2611,10 +2623,10 @@ if( level.alienBuildPoints - BG_FindBuildPointsForBuildable( buildable ) < 0 ) reason = IBE_NOASSERT; } - else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + else if( team == PTE_HUMANS ) { //human criteria - if( !G_isPower( entity_origin ) ) + if( !g_layoutEdit.integer && !G_isPower( entity_origin ) ) { //tell player to build a repeater to provide power if( buildable != BA_H_REACTOR && buildable != BA_H_REPEATER ) @@ -2903,6 +2915,9 @@ { float dist; vec3_t origin; + + // Only players with !layout permission may build or deconstruct in edit mode + if( g_layoutEdit.integer && !G_admin_layout_allowed( ent ) ) return qfalse; dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] ); Index: g_main.c =================================================================== --- g_main.c (revision 872) +++ g_main.c (working copy) @@ -130,6 +130,10 @@ vmCvar_t g_tag; +vmCvar_t g_layout; +vmCvar_t g_layoutAuto; +vmCvar_t g_layoutEdit; + static cvarTable_t gameCvarTable[ ] = { // don't override the cheat state set by the system @@ -215,7 +219,7 @@ { &g_alienMaxStage, "g_alienMaxStage", DEFAULT_ALIEN_MAX_STAGE, 0, 0, qfalse }, { &g_alienStage2Threshold, "g_alienStage2Threshold", DEFAULT_ALIEN_STAGE2_THRESH, 0, 0, qfalse }, { &g_alienStage3Threshold, "g_alienStage3Threshold", DEFAULT_ALIEN_STAGE3_THRESH, 0, 0, qfalse }, - + { &g_unlagged, "g_unlagged", "1", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qfalse }, { &g_disabledEquipment, "g_disabledEquipment", "", CVAR_ROM, 0, qfalse }, @@ -239,12 +243,16 @@ { &g_adminParseSay, "g_adminParseSay", "1", CVAR_ARCHIVE, 0, qfalse }, { &g_adminNameProtect, "g_adminNameProtect", "1", CVAR_ARCHIVE, 0, qfalse }, { &g_adminTempBan, "g_adminTempBan", "120", CVAR_ARCHIVE, 0, qfalse }, - + { &g_privateMessages, "g_privateMessages", "1", CVAR_ARCHIVE, 0, qfalse }, - + { &g_tag, "g_tag", "main", CVAR_INIT, 0, qfalse }, - - { &g_rankings, "g_rankings", "0", 0, 0, qfalse} + + { &g_rankings, "g_rankings", "0", 0, 0, qfalse}, + + { &g_layout, "g_layout", "1", CVAR_ARCHIVE, 0, qfalse}, + { &g_layoutAuto, "g_layoutAuto", "1", CVAR_ARCHIVE, 0, qfalse}, + { &g_layoutEdit, "g_layoutEdit", "0", 0, 0, qfalse}, }; static int gameCvarTableSize = sizeof( gameCvarTable ) / sizeof( gameCvarTable[ 0 ] ); @@ -492,7 +500,7 @@ trap_SendConsoleCommand( EXEC_APPEND, va( "exec \"%s/default.cfg\"\n", g_mapConfigs.string ) ); - + trap_SendConsoleCommand( EXEC_APPEND, va( "exec \"%s/%s.cfg\"\n", g_mapConfigs.string, mapname ) ); @@ -1012,7 +1020,7 @@ int localHTP = g_humanBuildPoints.integer, localATP = g_alienBuildPoints.integer; - if( g_suddenDeathTime.integer && !level.warmupTime ) + if( g_suddenDeathTime.integer && !g_layoutEdit.integer ) { if( G_TimeTilSuddenDeath( ) <= 0 ) { @@ -1037,11 +1045,6 @@ } } } - else - { - localHTP = g_humanBuildPoints.integer; - localATP = g_alienBuildPoints.integer; - } level.humanBuildPoints = level.humanBuildPointsPowered = localHTP; level.alienBuildPoints = localATP; @@ -1825,6 +1828,9 @@ */ void CheckExitRules( void ) { + // cannot win while editing + if( g_layoutEdit.integer ) return; + // if at the intermission, wait for all non-bots to // signal ready, then go to next level if( level.intermissiontime ) @@ -1869,6 +1875,7 @@ if( level.uncondHumanWin || ( ( level.time > level.startTime + 1000 ) && + ( level.time > level.layoutTime + 1000 ) && ( level.numAlienSpawns == 0 ) && ( level.numLiveAlienClients == 0 ) ) ) { @@ -1879,6 +1886,7 @@ } else if( level.uncondAlienWin || ( ( level.time > level.startTime + 1000 ) && + ( level.time > level.layoutTime + 1000 ) && ( level.numHumanSpawns == 0 ) && ( level.numLiveHumanClients == 0 ) ) ) { @@ -2090,14 +2098,16 @@ if( level.restarted ) return; + // we cannot load the layout right away, we must wait for the map's original + // buildings to solidify into real entities first + if( level.framenum == 5 && g_layoutAuto.integer ) + G_admin_layout_random( 0 ); + level.framenum++; level.previousTime = level.time; level.time = levelTime; msec = level.time - level.previousTime; - //TA: seed the rng - srand( level.framenum ); - // get any cvar changes G_UpdateCvars( ); @@ -2190,7 +2200,7 @@ ClientEndFrame( ent ); } - // save position information for all active clients + // save position information for all active clients G_UnlaggedStore( ); // for missle impacts, move every active client one server frame time back @@ -2211,6 +2221,8 @@ end = trap_Milliseconds(); + G_admin_layout_check(); + //TA: G_CountSpawns( ); G_CalculateBuildPoints( ); @@ -2244,4 +2256,3 @@ trap_Cvar_Set( "g_listEntity", "0" ); } } - Index: g_admin.c =================================================================== --- g_admin.c (revision 872) +++ g_admin.c (working copy) @@ -7,11 +7,11 @@ This shrubbot implementation is the original work of Tony J. White. Contains contributions from Wesley van Beelen, Chris Bajumpaa, Josh Menke, -and Travis Maurer. +Travis Maurer, and Michael "Risujin" Levin. The functionality of this code mimics the behaviour of the currently inactive project shrubet (http://www.etstats.com/shrubet/index.php?ver=2) -by Ryan Mannion. However, shrubet was a closed-source project and +by Ryan Mannion. However, shrubet was a closed-source project and none of it's code has been copied, only it's functionality. Tremulous is free software; you can redistribute it @@ -36,13 +36,13 @@ static char g_bfb[ 32000 ]; // note: list ordered alphabetically -g_admin_cmd_t g_admin_cmds[ ] = +g_admin_cmd_t g_admin_cmds[ ] = { {"admintest", G_admin_admintest, "a", "display your current admin level", "" }, - + {"allready", G_admin_allready, "y", "makes everyone ready in intermission", "" @@ -69,7 +69,12 @@ "kick a player with an optional reason", "(^5reason^7)" }, - + + {"layout", G_admin_layout, "l", + "modify/save/load building layouts", + "(^5edit|random|clear|save|load|finish^7)" + }, + {"listadmins", G_admin_listadmins, "D", "display a list of all server admins and their levels", "(^5name|start admin#^7)" @@ -79,7 +84,7 @@ "display a list of players, their client numbers and their levels", "" }, - + {"lock", G_admin_lock, "K", "lock a team to prevent anyone from joining it", "[^3a|h^7]" @@ -89,7 +94,7 @@ "mute a player", "[^3name|slot#^7]" }, - + {"namelog", G_admin_namelog, "e", "display a list of names used by recently connected players", "(^5name^7)" @@ -147,7 +152,7 @@ "unbans a player specified by the slot as seen in showbans", "[^3ban slot#^7]" }, - + {"unlock", G_admin_unlock, "K", "unlock a locked team", "[^3a|h^7]" @@ -205,16 +210,16 @@ return qfalse; } // flags with significance only for individuals ( - // like ADMF_INCOGNITO and ADMF_IMMUTABLE are NOT covered + // like ADMF_INCOGNITO and ADMF_IMMUTABLE are NOT covered // by the '*' wildcard. They must be specified manually. switch( flag ) { case ADMF_INCOGNITO: case ADMF_IMMUTABLE: - return qfalse; + return qfalse; default: return qtrue; - } + } } flags++; } @@ -238,16 +243,16 @@ return qfalse; } // flags with significance only for individuals ( - // like ADMF_INCOGNITO and ADMF_IMMUTABLE are NOT covered + // like ADMF_INCOGNITO and ADMF_IMMUTABLE are NOT covered // by the '*' wildcard. They must be specified manually. switch( flag ) { case ADMF_INCOGNITO: case ADMF_IMMUTABLE: - return qfalse; + return qfalse; default: return qtrue; - } + } } flags++; } @@ -265,14 +270,14 @@ G_SanitiseName( name, name2 ); - if( !Q_stricmp( name2, "UnnamedPlayer" ) ) + if( !Q_stricmp( name2, "UnnamedPlayer" ) ) return qtrue; for( i = 0; i < level.maxclients; i++ ) { client = &level.clients[ i ]; if( client->pers.connected != CON_CONNECTING - && client->pers.connected != CON_CONNECTED ) + && client->pers.connected != CON_CONNECTED ) { continue; } @@ -485,7 +490,7 @@ s[ 0 ] = '\0'; while( t[ 0 ] ) { - if( ( s[ 0 ] == '\0' && strlen( t ) <= size ) + if( ( s[ 0 ] == '\0' && strlen( t ) <= size ) || ( strlen( t ) + strlen( s ) < size ) ) { @@ -728,7 +733,7 @@ gentity_t *vic; int l = 0; qboolean dup = qfalse; - + ADMBP_begin(); // print out all connected players regardless of level if name searching @@ -738,7 +743,7 @@ if( vic->client && vic->client->pers.connected != CON_CONNECTED ) continue; - + l = vic->client->pers.adminLevel; G_SanitiseName( vic->client->pers.netname, name ); @@ -748,8 +753,8 @@ for( j = 0; j <= 8; j++ ) guid_stub[ j ] = vic->client->pers.guid[ j + 24 ]; guid_stub[ j ] = '\0'; - - lname[ 0 ] = '\0'; + + lname[ 0 ] = '\0'; Q_strncpyz( lname_fmt, "%s", sizeof( lname_fmt ) ); for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ ) { @@ -781,7 +786,7 @@ G_SanitiseName( g_admin_admins[ i ]->name, name ); if( !strstr( name, search ) ) continue; - + // verify we don't have the same guid/name pair in connected players // since we don't want to draw the same player twice dup = qfalse; @@ -792,7 +797,7 @@ continue; G_SanitiseName( vic->client->pers.netname, name2 ); if( !Q_stricmp( vic->client->pers.guid, g_admin_admins[ i ]->guid ) - && strstr( name2, search ) ) + && strstr( name2, search ) ) { dup = qtrue; break; @@ -804,8 +809,8 @@ for( j = 0; j <= 8; j++ ) guid_stub[ j ] = g_admin_admins[ i ]->guid[ j + 24 ]; guid_stub[ j ] = '\0'; - - lname[ 0 ] = '\0'; + + lname[ 0 ] = '\0'; Q_strncpyz( lname_fmt, "%s", sizeof( lname_fmt ) ); for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ ) { @@ -862,9 +867,9 @@ int i; qtime_t qt; int t; - - *reason = '\0'; - t = trap_RealTime( &qt ); + + *reason = '\0'; + t = trap_RealTime( &qt ); if( !*userinfo ) return qfalse; ip = Info_ValueForKey( userinfo, "ip" ); @@ -1012,7 +1017,7 @@ && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ ) { G_SanitiseName( g_admin_namelog[ i ]->name[ j ], n2 ); - if( !Q_stricmp( n1, n2 ) ) + if( !Q_stricmp( n1, n2 ) ) break; } if( j == MAX_ADMIN_NAMELOG_NAMES ) @@ -1291,8 +1296,8 @@ { char n[ MAX_NAME_LENGTH ] = {""}; int i = 0; - - // max printable name length for formatting + + // max printable name length for formatting for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) { G_DecolorString( l->name, n ); @@ -1381,7 +1386,7 @@ if( numeric && id >= 0 && id < level.maxclients ) vic = &g_entities[ id ]; - if( vic && vic->client && vic->client->pers.connected == CON_CONNECTED ) + if( vic && vic->client && vic->client->pers.connected == CON_CONNECTED ) { vic = &g_entities[ id ]; Q_strncpyz( adminname, vic->client->pers.netname, sizeof( adminname ) ); @@ -1427,7 +1432,7 @@ continue; G_SanitiseName( vic->client->pers.netname, testname2 ); if( !Q_stricmp( vic->client->pers.guid, g_admin_admins[ i ]->guid ) - && strstr( testname2, name ) ) + && strstr( testname2, name ) ) { dup = qtrue; break; @@ -1493,7 +1498,7 @@ g_admin_admins[ i ] = a; } - AP( va( + AP( va( "print \"^3!setlevel: ^7%s^7 was given level %d admin rights by %s\n\"", adminname, l, ( ent ) ? ent->client->pers.netname : "console" ) ); if( vic ) @@ -1507,7 +1512,7 @@ char *guid, char *ip, int seconds, - char *reason ) + char *reason ) { g_admin_ban_t *b = NULL; qtime_t qt; @@ -1595,7 +1600,7 @@ vic->client->pers.ip, g_adminTempBan.integer, "automatic temp ban created by kick" ); } - + trap_SendServerCommand( pids[ 0 ], va( "disconnect \"You have been kicked.\n%s^7\nreason:\n%s\"", ( ent ) ? va( "admin:\n%s", ent->client->pers.netname ) : "", @@ -1683,10 +1688,10 @@ { reason = G_SayConcatArgs( 3 + skiparg ); } - + for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) { - if( !Q_stricmp( g_admin_namelog[ i ]->ip, s2 ) + if( !Q_stricmp( g_admin_namelog[ i ]->ip, s2 ) || !Q_stricmp( va( "%d", g_admin_namelog[ i ]->slot ), s2 ) ) { logmatches = 1; @@ -1702,16 +1707,16 @@ { if( logmatch != i ) logmatches++; - logmatch = i; + logmatch = i; } } } - - if( !logmatches ) + + if( !logmatches ) { ADMP( "^3!ban: ^7no player found by that name, IP, or slot number\n" ); return qfalse; - } + } else if( logmatches > 1 ) { ADMBP_begin(); @@ -1741,7 +1746,7 @@ ADMBP_end(); return qfalse; } - + G_admin_duration( ( seconds ) ? seconds : -1, duration, sizeof( duration ) ); @@ -1758,9 +1763,9 @@ g_admin_namelog[ logmatch ]->name[ 0 ], g_admin_namelog[ logmatch ]->guid, g_admin_namelog[ logmatch ]->ip, - seconds, reason ); + seconds, reason ); - if(g_admin_namelog[ logmatch ]->slot == -1 ) + if(g_admin_namelog[ logmatch ]->slot == -1 ) { // client is already disconnected so stop here AP( va( "print \"^3!ban:^7 %s^7 has been banned by %s^7 " @@ -1875,6 +1880,577 @@ return qtrue; } +static qboolean admin_layout_clear( gentity_t *ent, buildableTeam_t immune ) { + int i; + gentity_t *bent; + if( !g_layoutEdit.integer ) { + ADMP( "^3!layout: ^7call !layout edit to begin editing first\n" ); + return qfalse; + } + for ( i = 1, bent = g_entities + i; i < level.num_entities; i++, bent++ ) + if( bent->s.eType == ET_BUILDABLE && bent->biteam != immune ) + G_FreeEntity( bent ); + return qtrue; +} + +static qboolean admin_layout_finish( gentity_t *ent ) { + if( !g_layoutEdit.integer ) { + ADMP( "^3!layout: ^7call !layout edit to begin editing first\n" ); + return qfalse; + } + if( !level.numAlienSpawns ) { + ADMP( "^3!layout: ^7layout is missing Alien spawns\n" ); + return qfalse; + } + if( !level.numHumanSpawns ) { + ADMP( "^3!layout: ^7layout is missing Human spawns\n" ); + return qfalse; + } + g_layoutEdit.integer = 0; + level.layoutTime = level.time; + return qtrue; +} + +void G_admin_layout_check( void ) { + gentity_t *vic; + int j; + if( !g_layoutEdit.integer ) return; + for( j = 0; j < level.maxclients; j++ ) + { + vic = &g_entities[ j ]; + if( !vic->client || vic->client->pers.connected != CON_CONNECTED ) + continue; + if( G_admin_layout_allowed( vic ) ) return; + } + g_layoutEdit.integer = 0; + AP( "print \"^3!layout: ^7layout edit mode disabled, no more admins\n\"" ); +} + +static qboolean admin_layout_remove( gentity_t *ent, char *layout ) { + char map[ MAX_STRING_CHARS ], filename[ MAX_OSPATH ]; + char *data,*next,*token; + int length; + fileHandle_t handle; + qboolean found; + + // Does not sanitize layout name + + // Get the map layout filename + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + Com_sprintf( filename, MAX_OSPATH, "layouts-%s.dat", map ); + + // Read in the entire layout file + length = trap_FS_FOpenFile( filename, &handle, FS_READ ); + if( handle ) { + next = data = G_Alloc( length ); + trap_FS_Read( data, length, handle ); + trap_FS_FCloseFile( handle ); + } else + return qfalse; + + // Strip any sections that match our layout name and write old data + trap_FS_FOpenFile( filename, &handle, FS_WRITE ); + if( !handle ) return qfalse; + found = qfalse; + do { + token = COM_Parse( &next ); + if( !Q_stricmp( token, layout ) ) { + while( *next && *next != '}' ) next++; + if( *next ) next++; + found = qtrue; + } else if( token[ 0 ] == '{' ) { + trap_FS_Write( token, strlen( token ), handle ); + do { + trap_FS_Write( next++, 1, handle ); + } while( *next && *next != '}' ); + if( *next ) next++; + trap_FS_Write( "}\n", 2, handle ); + } else if( token[ 0 ] > 0x20 ) { + trap_FS_Write( token, strlen( token ), handle ); + trap_FS_Write( " ", 1, handle ); + } + } while( token[ 0 ] ); + G_Free( data ); + trap_FS_FCloseFile( handle ); + + return found; +} + +// "Safe" building placement +static qboolean admin_layout_place( int buildable, vec3_t pos, vec3_t ang ) { + gentity_t *bent; + if( buildable == BA_NONE ) return qfalse; + bent = G_Spawn( ); + VectorCopy( pos, bent->s.pos.trBase ); + VectorCopy( ang, bent->s.angles ); + //AP( va( "print \"^3!layout: ^7spawning '%s' at (%f %f %f) facing (%f %f %f)\n\"", + // buildable, pos[ 0 ], pos[ 1 ], pos[ 2 ], ang[ 0 ], ang[ 1 ], ang[ 2 ] ) ); + G_SpawnBuildable( bent, buildable ); + return qtrue; +} + +static qboolean admin_layout_sanitize( gentity_t *ent, char *name ) { + char *pto, *pfrom; + + // Sanitize layout name + for( pto = pfrom = name; *pfrom; pfrom++ ) { + char c = *pfrom; + if( (c>='A' && c<='Z') || (c>='a' && c<='z') || (c>='0' && c<='9') || + c=='_' || c=='-' || c=='.' ) + *(pto++) = c; + } + *pto = 0; + + if( !name[ 0 ] ) { + ADMP( "^3!layout: ^7bad layout name\n" ); + return qfalse; + } + return qtrue; +} + +static qboolean admin_layout_load( gentity_t *ent, char *layout ) { + char map[ MAX_STRING_CHARS ], filename[ MAX_OSPATH ], + buildable[ MAX_STRING_CHARS ], *data, *next, *token; + vec3_t pos, ang; + int i, length, edit_state, found; + fileHandle_t handle; + + if( !admin_layout_sanitize( ent, layout ) ) return qfalse; + + // Get the map layout filename + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + Com_sprintf( filename, MAX_OSPATH, "layouts-%s.dat", map ); + + // Read in the entire layout file + length = trap_FS_FOpenFile( filename, &handle, FS_READ ); + if( !handle ) { + ADMP( va( "^3!layout: ^7map '%s' has no layouts\n", map ) ); + return qfalse; + } + next = data = G_Alloc( length ); + trap_FS_Read( data, length, handle ); + trap_FS_FCloseFile( handle ); + + // Skip any sections that don't match our layout name + found = 0; + do { + token = COM_Parse( &next ); + if( !Q_stricmp( token, layout ) ) { + while( *next && *next != '{' ) next++; + if( *next ) next++; + found = 1; + break; + } + while( *next && *next != '}' ) next++; + if( *next ) next++; + } while( *next ); + + // Not found? + if( !found ) { + ADMP( va( "^3!layout: ^7map '%s' layout '%s' not found!\n", + map, layout ) ); + G_Free( data ); + return qfalse; + } + + // Read format "building x y z p y r\n" and spawn buildings + edit_state = g_layoutEdit.integer; + g_layoutEdit.integer = 1; + if( !admin_layout_clear( ent, BIT_NONE ) ) return qfalse; + token = COM_Parse( &next ); + for( i = 0; token[ 0 ]; i++ ) { + //Com_Printf( "token %d: %s\n", i, token ); + if( token[ 0 ] == '}' ) break; + if( !i ) Q_strncpyz( buildable, token, sizeof( buildable ) ); + if( token[ 0 ] <= ' ' ) { + ADMP( va( "^3!layout:^7 load: bad token '%s'\n", token ) ); + i = -1; + token = COM_Parse( &next ); + continue; + } + if( i > 3 ) ang[ i-4 ] = atof( token ); + else if( i > 0 ) pos[ i-1 ] = atof( token ); + if( i >= 6 ) { + if( !admin_layout_place( BG_FindBuildNumForName( buildable ), pos, ang ) ) + AP( va( "print \"^3!layout: ^7unknown buildable '%s'\n\"", buildable ) ); + i = -1; + } + token = COM_Parse( &next ); + } + G_Free( data ); + if( !edit_state ) admin_layout_finish( ent ); + + // Save layout name to level + Q_strncpyz( level.layout, layout, sizeof(level.layout ) ); + + AP( va( "print \"^3!layout: ^7loaded map '%s' layout '%s'\n\"", + map, layout, layout ) ); + AP( va( "cp \"Loaded layout '%s'\"", layout ) ); + return qtrue; +} + +qboolean G_admin_layout_allowed( gentity_t *ent ) { + if( !G_admin_permission( ent, 'l' ) ) { + ADMP( "^3!layout: ^7you do not have permission to edit the layout\n" ); + return qfalse; + } + return qtrue; +} + +static qboolean admin_layout_save( gentity_t *ent, char *layout ) { + char map[ MAX_STRING_CHARS ], filename[ MAX_OSPATH ], *data; + int length,i; + fileHandle_t handle; + + if( !admin_layout_sanitize( ent, layout ) ) return qfalse; + if( !level.numAlienSpawns ) { + ADMP( "^3!layout: ^7layout is missing Alien spawns\n" ); + return qfalse; + } + if( !level.numHumanSpawns ) { + ADMP( "^3!layout: ^7layout is missing Human spawns\n" ); + return qfalse; + } + admin_layout_remove( ent, layout ); + + // Get the map layout filename + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + Com_sprintf( filename, MAX_OSPATH, "layouts-%s.dat", map ); + + // Reopen the file for writing + length = trap_FS_FOpenFile( filename, &handle, FS_APPEND ); + if( !handle ) { + ADMP( va( "^3!layout: ^7failed to open layout script '%s' for writing\n", + filename ) ); + return qfalse; + } + + // Write down the buildable information + data = va( "%s {\n", layout ); + trap_FS_Write( data, strlen( data ), handle ); + for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + if( ent->s.eType == ET_BUILDABLE ) { + char place_cmd[ MAX_STRING_CHARS ]; + Com_sprintf( place_cmd, sizeof(place_cmd), + " %s %f %f %f %f %f %f\n", + BG_FindNameForBuildable( ent->s.modelindex ), + ent->s.origin[ 0 ], ent->s.origin[ 1 ], ent->s.origin[ 2 ], + ent->s.angles[ 0 ], ent->s.angles[ 1 ], ent->s.angles[ 2 ] ); + trap_FS_Write( place_cmd, strlen( place_cmd ), handle ); + } + trap_FS_Write( "}\n", 2, handle ); + trap_FS_FCloseFile( handle ); + + // Save layout name to level + Q_strncpyz( level.layout, layout, sizeof(level.layout ) ); + + AP( va( "print \"^3!layout: ^7saved layout '%s'\n\";", layout ) ); + AP( va( "cp \"Saved layout '%s'\"", layout ) ); + return qtrue; +} + +qboolean G_admin_layout_random( gentity_t *ent ) { + char map[ MAX_STRING_CHARS ], layout[ MAX_STRING_CHARS ], + filename[ MAX_OSPATH ]; + char *data, *next, *token; + int length, choose, layouts = 0; + fileHandle_t handle; + + // Get the map layout filename + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + Com_sprintf( filename, MAX_OSPATH, "layouts-%s.dat", map ); + + // Count all headers in the layout file + length = trap_FS_FOpenFile( filename, &handle, FS_READ ); + if( handle ) { + next = data = G_Alloc( length ); + trap_FS_Read( data, length, handle ); + do { + token = COM_Parse( &next ); + if( token[ 0 ] == '{' ) { + while( *next && *next != '}' ) next++; + if( *next ) next++; + continue; + } else if( !token[ 0 ] ) break; + layouts++; + } while( *next ); + trap_FS_FCloseFile( handle ); + } + if( !layouts ) { + AP( va( "print \"^3!layout: ^7map '%s' has no layouts\n\"", map ) ); + return qfalse; + } + + // Choose a random layout and load it + choose = (int)(random( )*layouts); + AP( va( "print \"^3!layout: ^7choosing layout %d/%d from map '%s'\n\"", + choose+1, layouts, map ) ); + next = data; + do { + token = COM_Parse( &next ); + if( token[ 0 ] == '{' ) { + while( *next && *next != '}' ) next++; + if( *next ) next++; + continue; + } else if( !token[ 0 ] ) break; + if( !choose ) { + Q_strncpyz( layout, token, sizeof( layout ) ); + admin_layout_load( ent, layout ); + break; + } + choose--; + } while( *next ); + G_Free( data ); + + return qtrue; +} + +qboolean G_admin_layout( gentity_t *ent, int skiparg ) +{ + static int old_astage,old_hstage; + char cmd[ MAX_STRING_CHARS ], map[ MAX_STRING_CHARS ], filename[ MAX_OSPATH ]; + char *data,*next,*token; + int length; + fileHandle_t handle; + + if( !G_admin_layout_allowed( ent ) ) return qfalse; + + if( !g_layout.integer ) + { + ADMP( "^3!layout: ^7set \\g_layout 1 to enable layout editing\n" ); + return qfalse; + } + + if( G_SayArgc() < 2 + skiparg ) + { + ADMP( "^3!layout: ^7usage:\n" + "^5!layout edit^7 -- begin editing the layout\n" + "^5!layout clear [alien|human]^7 -- remove all (no arguments) or just human or alien buildables\n" + "^5!layout random^7 -- load a random layout\n" + "^5!layout build (name)^7 -- build a buildable in front of you\n" + "^5!layout current^7 -- print the current layout name\n" + "^5!layout save [name]^7 -- save the current layout for this map\n" + "^5!layout load [name]^7 -- load a layout for this map\n" + "^5!layout place (building) (x) (y) (z) (y) (p) (r)^7 -- place a buildable at (x,y,z) with orientation (y,p,r)\n" + "^5!layout finish^7 -- stop editing the current layout\n" ); + return qfalse; + } + + // One argument + G_SayArgv( 1 + skiparg, cmd, sizeof( cmd ) ); + if( G_SayArgc() == 2 + skiparg ) + { + // Enter editing mode + if( !Q_stricmp( cmd, "edit" ) ) { + char *name; + if( g_layoutEdit.integer ) return qtrue; + g_layoutEdit.integer = 1; + old_astage = g_alienStage.integer; + old_hstage = g_humanStage.integer; + g_alienStage.integer = 2; + g_humanStage.integer = 2; + name = "console"; + if( ent && ent->client ) name = ent->client->pers.netname; + AP( va( "print \"^3!layout: ^7%s ^7started layout edit mode\n\"", name ) ); + AP( "cp \"Layout edit mode\"" ); + return qtrue; + } + + // Print the current layout name + else if( !Q_stricmp( cmd, "current" ) ) { + if( !level.layout[ 0 ] ) { + ADMP( "^3!layout: ^7there is no layout loaded\n" ); + return qtrue; + } + ADMP( va( "^3!layout: ^7current layout is '%s'\n", level.layout ) ); + return qtrue; + } + + // List available layouts + else if( !Q_stricmp( cmd, "list" ) ) { + + // Get the map layout filename + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + Com_sprintf( filename, MAX_OSPATH, "layouts-%s.dat", map ); + + // List all headers in the layout file + length = trap_FS_FOpenFile( filename, &handle, FS_READ ); + if( handle ) { + next = data = G_Alloc( length ); + trap_FS_Read( data, length, handle ); + ADMP( va( "^3!layout: ^7map '%s' has layouts:\n", map ) ); + do { + token = COM_Parse( &next ); + if( token[ 0 ] == '{' ) { + while( *next && *next != '}' ) next++; + if( *next ) next++; + continue; + } else if( !token[ 0 ] ) break; + ADMP( va( "%s\n", token ) ); + } while( *next ); + trap_FS_FCloseFile( handle ); + G_Free( data ); + } else + ADMP( va( "^3!layout: ^7map '%s' has no layouts\n", map ) ); + + return qtrue; + } + + // Load default layout (FIXME) + /*else if( !Q_stricmp( cmd, "reset" ) ) { + int edit_state = g_layoutEdit.integer; + g_layoutEdit.integer = 1; + admin_layout_clear( ent ); + G_SpawnEntitiesFromString( ); + if( !edit_state ) admin_layout_finish( ent ); + AP( "^3!layout: ^7map layout reset\n" ); + return qtrue; + }*/ + + // Load a random layout + else if( !Q_stricmp( cmd, "random" ) ) + return G_admin_layout_random( ent ); + + // Leave editing mode + else if( !Q_stricmp( cmd, "finish" ) ) { + g_alienStage.integer = old_astage; + g_humanStage.integer = old_hstage; + if( admin_layout_finish( ent ) ) { + char *name = "console"; + if( ent && ent->client ) name = ent->client->pers.netname; + AP( va( "print \"^3!layout: ^7%s ^7stopped layout edit mode\n\"", name ) ); + AP( "cp \"Layout modified\"" ); + return qtrue; + } + return qfalse; + } + + // Clear buildings + else if( !Q_stricmp( cmd, "clear" ) ) { + qboolean success = admin_layout_clear( ent, BIT_NONE ); + if( success ) AP( "print \"^3!layout: ^7all buildables removed\n\"" ); + return success; + } + + // Load layout from map layouts file + else if( !Q_stricmp( cmd, "load" ) ) { + if( !level.layout[ 0 ] ) { + ADMP( "^3!layout: ^7no current layout\n" ); + return qfalse; + } + return admin_layout_load( ent, level.layout ); + } + + // Save layout section to layout file + else if( !Q_stricmp( cmd, "save" ) ) { + if( !level.layout[ 0 ] ) { + ADMP( "^3!layout: ^7no current layout\n" ); + return qfalse; + } + return admin_layout_save( ent, level.layout ); + } + } + + // Two arguments + else if( G_SayArgc() == 3 + skiparg ) { + char layout[ MAX_STRING_CHARS ]; + G_SayArgv( 2 + skiparg, layout, sizeof( layout ) ); + + // Build something in front of the player + if( !Q_stricmp( cmd, "build" ) ) { + char building[ MAX_STRING_CHARS ]; + int bnum; + if( !g_layoutEdit.integer ) { + ADMP( "^3!layout: ^7call !layout edit to begin editing first\n" ); + return qfalse; + } + G_SayArgv( 2 + skiparg, building, sizeof( building ) ); + bnum = BG_FindBuildNumForName( building ); + if( bnum == BA_NONE ) { + ADMP( va( "^3!layout: ^7unknown buildable '%s'\n", building ) ); + return qfalse; + } + G_ValidateBuild( ent, bnum ); + return qtrue; + } + + // Clear buildings + else if( !Q_stricmp( cmd, "clear" ) ) { + buildableTeam_t biteam = BIT_NONE; + qboolean success; + if( !Q_stricmp( layout, "human" ) ) biteam = BIT_ALIENS; + else if( !Q_stricmp( layout, "alien" ) ) biteam = BIT_HUMANS; + else { + ADMP( va( "^3!layout: ^7clear: unknown team '%s'\n", layout ) ); + return qfalse; + } + success = admin_layout_clear( ent, biteam ); + if( success && biteam == BIT_HUMANS ) + AP( "print \"^3!layout: ^7all Alien buildables removed\n\"" ); + else if( success && biteam == BIT_ALIENS ) + AP( "print \"^3!layout: ^7all Human buildables removed\n\"" ); + return success; + } + + // Load layout from map layouts file + if( !Q_stricmp( cmd, "load" ) ) + return admin_layout_load( ent, layout ); + + // Remove a layout from map layouts file + else if( !Q_stricmp( cmd, "remove" ) ) { + if( !admin_layout_sanitize( ent, layout ) ) return qfalse; + if( admin_layout_remove( ent, layout ) ) { + AP( va( "print \"^3!layout: ^7removed layout '%s'\n\"", layout ) ); + return qfalse; + } + ADMP( va( "^3!layout: ^7layout '%s' not found\n", layout ) ); + return qfalse; + } + + // Save layout section to layout file + else if( !Q_stricmp( cmd, "save" ) ) + return admin_layout_save( ent, layout ); + } + + // Seven arguments + else if( G_SayArgc() == 9 + skiparg ) { + char building[ MAX_STRING_CHARS ], + spx[ 8 ],spy[ 8 ],spz[ 8 ], + sap[ 8 ],say[ 8 ],sar[ 8 ]; + vec3_t pos,ang; + + // Place a building + if( !Q_stricmp( cmd, "place" ) ) { + if( !g_layoutEdit.integer ) { + ADMP( "^3!layout: ^7call !layout edit to begin editing first\n" ); + return qfalse; + } + G_SayArgv( 2 + skiparg, building, sizeof( building ) ); + G_SayArgv( 3 + skiparg, spx, sizeof( spx ) ); + G_SayArgv( 4 + skiparg, spy, sizeof( spy ) ); + G_SayArgv( 5 + skiparg, spz, sizeof( spz ) ); + G_SayArgv( 6 + skiparg, sap, sizeof( say ) ); + G_SayArgv( 7 + skiparg, say, sizeof( sap ) ); + G_SayArgv( 8 + skiparg, sar, sizeof( sar ) ); + pos[ 0 ] = atof( spx ); + pos[ 1 ] = atof( spy ); + pos[ 2 ] = atof( spz ); + ang[ 0 ] = atof( sap ); + ang[ 1 ] = atof( say ); + ang[ 2 ] = atof( sar ); + if( !admin_layout_place( BG_FindBuildNumForName( building ), pos, ang ) ) { + ADMP( va( "^3!layout: ^7unknown buildable '%s'\n", building ) ); + return qfalse; + } + return qtrue; + } + } + + ADMP( va( "^3!layout: ^7unknown command '%s' with %d arguments\n", + cmd, G_SayArgc()-skiparg ) ); + return qfalse; +} + qboolean G_admin_mute( gentity_t *ent, int skiparg ) { int pids[ MAX_CLIENTS ]; @@ -1962,9 +2538,9 @@ { if( s[ i ] >= '0' && s[ i ] <= '9' ) continue; - numeric = qfalse; + numeric = qfalse; } - if( numeric ) + if( numeric ) { start = atoi( s ); if( start > 0 ) @@ -2111,7 +2687,7 @@ c, t, l, - ( *lname ) ? lname2 : "", + ( *lname ) ? lname2 : "", guid_stub, muted, p->pers.netname, @@ -2168,7 +2744,7 @@ if( start >= MAX_ADMIN_BANS || start < 0 ) start = 0; - for( i = start; i < MAX_ADMIN_BANS && g_admin_bans[ i ] + for( i = start; i < MAX_ADMIN_BANS && g_admin_bans[ i ] && ( i - start ) < MAX_ADMIN_SHOWBANS; i++ ) { G_DecolorString( g_admin_bans[ i ]->name, n1 ); @@ -2214,12 +2790,12 @@ G_DecolorString( g_admin_bans[ i ]->name, n1 ); 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 ); + 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", ( 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 ); ADMBP( va( "%4i %s^7 %-15s %-8s %s^7 %-10s\n \\__ %s\n", ( i + 1 ), @@ -2413,7 +2989,7 @@ cl->readyToExit = 1; } AP( va( "print \"^3!allready:^7 %s^7 says everyone is READY now\n\"", - ( ent ) ? ent->client->pers.netname : "console" ) ); + ( ent ) ? ent->client->pers.netname : "console" ) ); return qtrue; } @@ -2478,7 +3054,7 @@ { G_ChangeTeam( vic, PTE_NONE ); AP( va( "print \"^3!spec999: ^7%s^7 moved ^7%s^7 to spectators\n\"", - ( ent ) ? ent->client->pers.netname : "console", + ( ent ) ? ent->client->pers.netname : "console", vic->client->pers.netname ) ); } } @@ -2579,7 +3155,7 @@ if( search[0] ) { found = qfalse; - for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES && + for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ ) { G_SanitiseName( g_admin_namelog[ i ]->name[ j ], n2 ); @@ -2598,17 +3174,17 @@ guid_stub[ j ] = '\0'; if( g_admin_namelog[ i ]->slot > -1 ) ADMBP( "^3" ); - ADMBP( va( "%-2s (*%s) %15s^7", + ADMBP( va( "%-2s (*%s) %15s^7", (g_admin_namelog[ i ]->slot > -1 ) ? va( "%d", g_admin_namelog[ i ]->slot ) : "-", guid_stub, g_admin_namelog[ i ]->ip ) ); - for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES && + for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ ) { ADMBP( va( " '%s^7'", g_admin_namelog[ i ]->name[ j ] ) ); } - ADMBP( "\n" ); - } + ADMBP( "\n" ); + } ADMBP( va( "^3!namelog:^7 %d recent clients found\n", printed ) ); ADMBP_end(); return qtrue; @@ -2659,7 +3235,7 @@ ( team == PTE_ALIENS ) ? "Alien" : "Human", ( ent ) ? ent->client->pers.netname : "console" ) ); return qtrue; -} +} qboolean G_admin_unlock( gentity_t *ent, int skiparg ) { @@ -2681,7 +3257,7 @@ ADMP( va( "^3!unlock: ^7invalid team\"%c\"\n", teamName[0] ) ); return qfalse; } - + if( team == PTE_ALIENS ) { if( !level.alienTeamLocked ) @@ -2706,7 +3282,7 @@ ( team == PTE_ALIENS ) ? "Alien" : "Human", ( ent ) ? ent->client->pers.netname : "console" ) ); return qtrue; -} +} /* ================ Index: g_weapon.c =================================================================== --- g_weapon.c (revision 872) +++ g_weapon.c (working copy) @@ -772,6 +772,8 @@ else ent->client->ps.stats[ STAT_MISC ] += BG_FindBuildDelayForWeapon( ent->s.weapon ); + + if( g_layoutEdit.integer ) ent->client->ps.stats[ STAT_MISC ] = 0; ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; Index: g_admin.h =================================================================== --- g_admin.h (revision 872) +++ g_admin.h (working copy) @@ -31,7 +31,7 @@ #define ADMBP_begin() G_admin_buffer_begin() #define ADMBP_end() G_admin_buffer_end(ent) -#define MAX_ADMIN_LEVELS 32 +#define MAX_ADMIN_LEVELS 32 #define MAX_ADMIN_ADMINS 1024 #define MAX_ADMIN_BANS 1024 #define MAX_ADMIN_NAMELOGS 128 @@ -48,7 +48,7 @@ * 4 - can see team chat as a spectator * 5 - can switch teams any time, regardless of balance * 6 - does not need to specify a reason for a kick/ban - * 7 - can call a vote at any time (regardless of a vote being disabled or + * 7 - can call a vote at any time (regardless of a vote being disabled or * voting limitations) * 8 - does not need to specify a duration for a ban * 9 - can run commands from team chat @@ -146,6 +146,7 @@ qboolean G_admin_kick( 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_layout( gentity_t *ent, int skiparg ); qboolean G_admin_putteam( gentity_t *ent, int skiparg ); qboolean G_admin_listadmins( gentity_t *ent, int skiparg ); qboolean G_admin_listplayers( gentity_t *ent, int skiparg ); @@ -173,4 +174,8 @@ void G_admin_cleanup( void ); void G_admin_namelog_cleanup( void ); +void G_admin_layout_check( void ); +qboolean G_admin_layout_allowed( gentity_t *ent ); +qboolean G_admin_layout_random( gentity_t *ent ); + #endif /* ifndef _G_ADMIN_H */ Index: g_cmds.c =================================================================== --- g_cmds.c (revision 872) +++ g_cmds.c (working copy) @@ -52,7 +52,7 @@ spaces = 0; skip = qfalse; } - + if( *in == 27 || *in == '^' ) { in += 2; // skip color code @@ -67,7 +67,7 @@ *out++ = tolower( *in++ ); } - out -= spaces; + out -= spaces; *out = 0; } @@ -177,8 +177,8 @@ { gclient_t *p; int i, found = 0; - char n2[ MAX_NAME_LENGTH ] = {""}; - char s2[ MAX_NAME_LENGTH ] = {""}; + char n2[ MAX_NAME_LENGTH ] = {""}; + char s2[ MAX_NAME_LENGTH ] = {""}; qboolean is_slot = qtrue; *plist = -1; @@ -198,7 +198,7 @@ if( i >= 0 && i < level.maxclients ) { p = &level.clients[ i ]; if( p->pers.connected == CON_CONNECTED || - p->pers.connected == CON_CONNECTING ) + p->pers.connected == CON_CONNECTING ) { *plist++ = i; *plist = -1; @@ -208,7 +208,7 @@ // we must assume that if only a number is provided, it is a clientNum return 0; } - + // now look for name matches G_SanitiseName( s, s2 ); if( strlen( s2 ) < 1 ) @@ -579,7 +579,7 @@ void G_ChangeTeam( gentity_t *ent, pTeam_t newTeam ) { pTeam_t oldTeam = ent->client->pers.teamSelection; - + if( oldTeam == newTeam ) return; @@ -672,7 +672,7 @@ { trap_SendServerCommand( ent-g_entities, va( "print \"Alien team has been ^1LOCKED\n\"", s ) ); - return; + return; } else if( level.humanTeamLocked ) { @@ -698,7 +698,7 @@ { trap_SendServerCommand( ent-g_entities, va( "print \"Human team has been ^1LOCKED\n\"", s ) ); - return; + return; } else if( level.alienTeamLocked ) { @@ -789,7 +789,7 @@ if( mode == SAY_TEAM && !OnSameTeam( ent, other ) ) { if( other->client->ps.stats[ STAT_PTEAM ] != PTE_NONE ) - return; + return; if( !G_admin_permission( other, ADMF_SPEC_ALLCHAT ) ) return; @@ -885,7 +885,7 @@ other = &g_entities[ j ]; G_SayTo( ent, other, mode, color, name, text ); } - + if( g_adminParseSay.integer ) { G_admin_cmd_check ( ent, qtrue ); @@ -914,8 +914,8 @@ { args = G_SayConcatArgs(0); if( !Q_stricmpn( args, "say /m ", 7 ) || - !Q_stricmpn( args, "say_team /m ", 12 ) || - !Q_stricmpn( args, "say /mt ", 8 ) || + !Q_stricmpn( args, "say_team /m ", 12 ) || + !Q_stricmpn( args, "say /mt ", 8 ) || !Q_stricmpn( args, "say_team /mt ", 13 ) ) { G_PrivateMessage( ent ); @@ -1003,7 +1003,7 @@ } if( g_voteLimit.integer > 0 - && ent->client->pers.voteCount >= g_voteLimit.integer + && ent->client->pers.voteCount >= g_voteLimit.integer && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) ) { trap_SendServerCommand( ent-g_entities, va( @@ -1039,11 +1039,11 @@ { int clientNum; int clientNums[ MAX_CLIENTS ] = { -1 }; - + if( G_ClientNumbersFromString( arg2, clientNums ) == 1 ) { // there was one partial name match name was clientNum - clientNum = clientNums[ 0 ]; + clientNum = clientNums[ 0 ]; } else { @@ -1091,7 +1091,7 @@ Q_strncpyz( kickee, level.clients[ clientNum ].pers.netname, sizeof( kickee ) ); Q_CleanStr( kickee ); - if( g_admin.string[ 0 ] ) + if( g_admin.string[ 0 ] ) { // use ip in case this player disconnects before the vote ends Com_sprintf( level.voteString, sizeof( level.voteString ), @@ -1247,7 +1247,7 @@ } if( g_voteLimit.integer > 0 - && ent->client->pers.voteCount >= g_voteLimit.integer + && ent->client->pers.voteCount >= g_voteLimit.integer && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) ) { trap_SendServerCommand( ent-g_entities, va( @@ -1276,11 +1276,11 @@ { int clientNum; int clientNums[ MAX_CLIENTS ] = { -1 }; - + if( G_ClientNumbersFromString( arg2, clientNums ) == 1 ) { // there was one partial name match or name was clientNum - clientNum = clientNums[ 0 ]; + clientNum = clientNums[ 0 ]; } else { @@ -1296,7 +1296,7 @@ Q_strncpyz( arg1, "teamclientkick", sizeof( arg1 ) ); Q_strncpyz( arg2, va( "%d", clientNum ), sizeof( arg2 ) ); } - + if( !Q_stricmp( arg1, "teamclientkick" ) ) { int clientNum = 0; @@ -1559,43 +1559,56 @@ return; } - //check there are no humans nearby - VectorAdd( ent->client->ps.origin, range, maxs ); - VectorSubtract( ent->client->ps.origin, range, mins ); + // only admins can evolve in layout edit mode + if( g_layoutEdit.integer ) { + if( !G_admin_layout_allowed( ent ) ) { + trap_SendServerCommand( ent-g_entities, + va( "print \"You are not permitted to evolve in layout edit mode\n\"" ) ); + return; + } + numLevels = 0; + } - num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - for( i = 0; i < num; i++ ) - { - other = &g_entities[ entityList[ i ] ]; + // not in layout edit mode + else { - if( ( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) || - ( other->s.eType == ET_BUILDABLE && other->biteam == BIT_HUMANS ) ) + //check there are no humans nearby + VectorAdd( ent->client->ps.origin, range, maxs ); + VectorSubtract( ent->client->ps.origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) { - G_TriggerMenu( clientNum, MN_A_TOOCLOSE ); + other = &g_entities[ entityList[ i ] ]; + + if( ( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) || + ( other->s.eType == ET_BUILDABLE && other->biteam == BIT_HUMANS ) ) + { + G_TriggerMenu( clientNum, MN_A_TOOCLOSE ); + return; + } + } + + if( !level.overmindPresent ) + { + G_TriggerMenu( clientNum, MN_A_NOOVMND_EVOLVE ); return; } - } - if( !level.overmindPresent ) - { - G_TriggerMenu( clientNum, MN_A_NOOVMND_EVOLVE ); - return; - } + //guard against selling the HBUILD weapons exploit + if( ( currentClass == PCL_ALIEN_BUILDER0 || + currentClass == PCL_ALIEN_BUILDER0_UPG ) && + ent->client->ps.stats[ STAT_MISC ] > 0 ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"You cannot evolve until build timer expires\n\"" ) ); + return; + } - //guard against selling the HBUILD weapons exploit - if( ( currentClass == PCL_ALIEN_BUILDER0 || - currentClass == PCL_ALIEN_BUILDER0_UPG ) && - ent->client->ps.stats[ STAT_MISC ] > 0 ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"You cannot evolve until build timer expires\n\"" ) ); - return; + numLevels = BG_ClassCanEvolveFromTo( currentClass, newClass, + (short)ent->client->ps.persistant[ PERS_CREDIT ], 0 ); } - numLevels = BG_ClassCanEvolveFromTo( currentClass, - newClass, - (short)ent->client->ps.persistant[ PERS_CREDIT ], 0 ); - BG_FindBBoxForClass( currentClass, fromMins, fromMaxs, NULL, NULL, NULL ); BG_FindBBoxForClass( newClass, @@ -1623,9 +1636,10 @@ if( !tr.startsolid && tr2.fraction == 1.0f ) { //...check we can evolve to that class - if( numLevels >= 0 && - BG_FindStagesForClass( newClass, g_alienStage.integer ) && - BG_ClassIsAllowed( newClass ) ) + if( g_layoutEdit.integer || + ( numLevels >= 0 && + BG_FindStagesForClass( newClass, g_alienStage.integer ) && + BG_ClassIsAllowed( newClass ) ) ) { ent->client->pers.evolveHealthFraction = (float)ent->client->ps.stats[ STAT_HEALTH ] / (float)BG_FindHealthForClass( currentClass ); @@ -1726,6 +1740,9 @@ trace_t tr; gentity_t *traceEnt; + // Only players with !layout permission may build or deconstruct in edit mode + if( g_layoutEdit.integer && !G_admin_layout_allowed( ent ) ) return; + if( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) G_Damage( ent->client->hovel, ent, ent, forward, ent->s.origin, 10000, 0, MOD_SUICIDE ); @@ -1739,9 +1756,10 @@ if( tr.fraction < 1.0f && ( traceEnt->s.eType == ET_BUILDABLE ) && - ( traceEnt->biteam == ent->client->pers.teamSelection ) && - ( ( ent->client->ps.weapon >= WP_ABUILD ) && - ( ent->client->ps.weapon <= WP_HBUILD ) ) ) + ( g_layoutEdit.integer || + ( ( traceEnt->biteam == ent->client->pers.teamSelection ) && + ( ( ent->client->ps.weapon >= WP_ABUILD ) && + ( ent->client->ps.weapon <= WP_HBUILD ) ) ) ) ) { // Don't allow destruction of hovel with granger inside if( traceEnt->s.modelindex == BA_A_HOVEL && traceEnt->active ) @@ -1754,7 +1772,7 @@ return; } - if( ent->client->ps.stats[ STAT_MISC ] > 0 ) + if( ent->client->ps.stats[ STAT_MISC ] > 0 ) { G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum ); return; @@ -1763,13 +1781,13 @@ { G_TeamCommand( ent->client->pers.teamSelection, va( "print \"%s ^3DECONSTRUCTED^7 by %s^7\n\"", - BG_FindHumanNameForBuildable( traceEnt->s.modelindex ), + BG_FindHumanNameForBuildable( traceEnt->s.modelindex ), ent->client->pers.netname ) ); } G_LogPrintf( "Decon: %i %i 0: %s^7 deconstructed %s\n", ent->client->ps.clientNum, traceEnt->s.modelindex, - ent->client->pers.netname, + ent->client->pers.netname, BG_FindNameForBuildable( traceEnt->s.modelindex ) ); if( !deconstruct && CheatsOk( ent ) ) @@ -1777,8 +1795,9 @@ else G_FreeEntity( traceEnt ); - ent->client->ps.stats[ STAT_MISC ] += - BG_FindBuildDelayForWeapon( ent->s.weapon ) >> 2; + if( !g_layoutEdit.integer ) + ent->client->ps.stats[ STAT_MISC ] += + BG_FindBuildDelayForWeapon( ent->s.weapon ) >> 2; } } } @@ -1935,6 +1954,13 @@ if( ent->client->pers.teamSelection != PTE_HUMANS ) return; + // only admins can shop in layout edit mode + if( g_layoutEdit.integer && !G_admin_layout_allowed( ent ) ) { + trap_SendServerCommand( ent-g_entities, + va( "print \"You are not permitted to purchase items in layout edit mode\n\"" ) ); + return; + } + weapon = BG_FindWeaponNumForName( s ); upgrade = BG_FindUpgradeNumForName( s ); @@ -1944,25 +1970,28 @@ buyingEnergyAmmo = hasEnergyWeapon; } - if( buyingEnergyAmmo ) - { - //no armoury nearby - if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_REACTOR ) && - !G_BuildableRange( ent->client->ps.origin, 100, BA_H_REPEATER ) && - !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) + // purchase anywhere in layout edit mode + if( !g_layoutEdit.integer ) { + if( buyingEnergyAmmo ) { - trap_SendServerCommand( ent-g_entities, va( - "print \"You must be near a reactor, repeater or armoury\n\"" ) ); - return; + //no armoury nearby + if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_REACTOR ) && + !G_BuildableRange( ent->client->ps.origin, 100, BA_H_REPEATER ) && + !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"You must be near a reactor, repeater or armoury\n\"" ) ); + return; + } } - } - else - { - //no armoury nearby - if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) + else { - trap_SendServerCommand( ent-g_entities, va( "print \"You must be near a powered armoury\n\"" ) ); - return; + //no armoury nearby + if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) + { + trap_SendServerCommand( ent-g_entities, va( "print \"You must be near a powered armoury\n\"" ) ); + return; + } } } @@ -1976,7 +2005,8 @@ } //can afford this? - if( BG_FindPriceForWeapon( weapon ) > (short)ent->client->ps.persistant[ PERS_CREDIT ] ) + if( !g_layoutEdit.integer && + BG_FindPriceForWeapon( weapon ) > (short)ent->client->ps.persistant[ PERS_CREDIT ] ) { G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS ); return; @@ -2027,7 +2057,8 @@ ent->client->ps.stats[ STAT_MISC ] = 0; //subtract from funds - G_AddCreditToClient( ent->client, -(short)BG_FindPriceForWeapon( weapon ), qfalse ); + if( !g_layoutEdit.integer ) + G_AddCreditToClient( ent->client, -(short)BG_FindPriceForWeapon( weapon ), qfalse ); } else if( upgrade != UP_NONE ) { @@ -2039,7 +2070,8 @@ } //can afford this? - if( BG_FindPriceForUpgrade( upgrade ) > (short)ent->client->ps.persistant[ PERS_CREDIT ] ) + if( !g_layoutEdit.integer && + BG_FindPriceForUpgrade( upgrade ) > (short)ent->client->ps.persistant[ PERS_CREDIT ] ) { G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS ); return; @@ -2085,7 +2117,8 @@ G_GiveClientMaxAmmo( ent, qtrue ); //subtract from funds - G_AddCreditToClient( ent->client, -(short)BG_FindPriceForUpgrade( upgrade ), qfalse ); + if( !g_layoutEdit.integer ) + G_AddCreditToClient( ent->client, -(short)BG_FindPriceForUpgrade( upgrade ), qfalse ); } else { @@ -2123,8 +2156,16 @@ if( ent->client->pers.teamSelection != PTE_HUMANS ) return; + // only admins can shop in layout edit mode + if( g_layoutEdit.integer && !G_admin_layout_allowed( ent ) ) { + trap_SendServerCommand( ent-g_entities, + va( "print \"You are not permitted to purchase items in layout edit mode\n\"" ) ); + return; + } + //no armoury nearby - if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) + if( !g_layoutEdit.integer && + !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) { trap_SendServerCommand( ent-g_entities, va( "print \"You must be near a powered armoury\n\"" ) ); return; @@ -2156,7 +2197,8 @@ BG_RemoveWeaponFromInventory( weapon, ent->client->ps.stats ); //add to funds - G_AddCreditToClient( ent->client, (short)BG_FindPriceForWeapon( weapon ), qfalse ); + if( !g_layoutEdit.integer ) + G_AddCreditToClient( ent->client, (short)BG_FindPriceForWeapon( weapon ), qfalse ); } //if we have this weapon selected, force a new selection @@ -2180,7 +2222,8 @@ G_GiveClientMaxAmmo( ent, qtrue ); //add to funds - G_AddCreditToClient( ent->client, (short)BG_FindPriceForUpgrade( upgrade ), qfalse ); + if( !g_layoutEdit.integer ) + G_AddCreditToClient( ent->client, (short)BG_FindPriceForUpgrade( upgrade ), qfalse ); } } else if( !Q_stricmp( s, "weapons" ) ) @@ -2201,7 +2244,8 @@ BG_RemoveWeaponFromInventory( i, ent->client->ps.stats ); //add to funds - G_AddCreditToClient( ent->client, (short)BG_FindPriceForWeapon( i ), qfalse ); + if( !g_layoutEdit.integer ) + G_AddCreditToClient( ent->client, (short)BG_FindPriceForWeapon( i ), qfalse ); } //if we have this weapon selected, force a new selection @@ -2236,7 +2280,8 @@ } //add to funds - G_AddCreditToClient( ent->client, (short)BG_FindPriceForUpgrade( i ), qfalse ); + if( !g_layoutEdit.integer ) + G_AddCreditToClient( ent->client, (short)BG_FindPriceForUpgrade( i ), qfalse ); } } } @@ -2276,12 +2321,13 @@ team = ent->client->ps.stats[ STAT_PTEAM ]; if( buildable != BA_NONE && - ( ( 1 << ent->client->ps.weapon ) & BG_FindBuildWeaponForBuildable( buildable ) ) && !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) && !( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) && BG_BuildableIsAllowed( buildable ) && - ( ( team == PTE_ALIENS && BG_FindStagesForBuildable( buildable, g_alienStage.integer ) ) || - ( team == PTE_HUMANS && BG_FindStagesForBuildable( buildable, g_humanStage.integer ) ) ) ) + ( g_layoutEdit.integer || + ( ( ( 1 << ent->client->ps.weapon ) & BG_FindBuildWeaponForBuildable( buildable ) ) && + ( ( team == PTE_ALIENS && BG_FindStagesForBuildable( buildable, g_alienStage.integer ) ) || + ( team == PTE_HUMANS && BG_FindStagesForBuildable( buildable, g_humanStage.integer ) ) ) ) ) ) { dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] ); @@ -2681,7 +2727,7 @@ Cmd_Tell_f( ent ); return; } - + if( !Q_stricmp( cmd, "m" ) || !Q_stricmp( cmd, "mt" ) ) { G_PrivateMessage( ent ); @@ -2777,7 +2823,7 @@ { if( *s == ' ' ) { - s++; + s++; if( *s != ' ' ) { c++; @@ -2867,7 +2913,7 @@ } void G_DecolorString( char *in, char *out ) -{ +{ while( *in ) { if( *in == 27 || *in == '^' ) { in++; @@ -2958,9 +3004,9 @@ { trap_SendServerCommand( pids[ i ], va( "print \">> to reply, say: /m %d [your message] <<\n\"", - ( ent - g_entities ) ) ); + ( ent - g_entities ) ) ); } - trap_SendServerCommand( pids[ i ], va( + trap_SendServerCommand( pids[ i ], va( "cp \"^%cprivate message from ^7%s^7\"", color, ( ent ) ? ent->client->pers.netname : "console" ) ); } @@ -2979,4 +3025,3 @@ name, msg ); } } -