Index: src/game/g_local.h =================================================================== --- src/game/g_local.h (revision 829) +++ src/game/g_local.h (working copy) @@ -349,6 +349,7 @@ char ip[ 16 ]; qboolean muted; int adminLevel; + qboolean designatedBuilder; } clientPersistant_t; // this structure is cleared on each ClientSpawn(), @@ -712,6 +713,7 @@ void G_setIdleBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim ); void G_SpawnBuildable(gentity_t *ent, buildable_t buildable); void FinishSpawningBuildable( gentity_t *ent ); +void G_CheckDBProtection( void ); // // g_utils.c Index: src/game/g_buildable.c =================================================================== --- src/game/g_buildable.c (revision 829) +++ src/game/g_buildable.c (working copy) @@ -2180,7 +2180,8 @@ self->die = nullDieFunction; self->powered = qfalse; //free up power - self->s.eFlags &= ~EF_FIRING; //prevent any firing effects + //prevent any firing effects and cancel structure protection + self->s.eFlags &= ~( EF_FIRING | EF_DBUILDER ); if( self->spawned ) { @@ -2772,7 +2773,14 @@ built->s.weapon = BG_FindProjTypeForBuildable( buildable ); if( builder->client ) + { built->builtBy = builder->client->ps.clientNum; + + if( builder->client->pers.designatedBuilder ) + { + built->s.eFlags |= EF_DBUILDER; // designated builder protection + } + } else built->builtBy = -1; @@ -2999,3 +3007,56 @@ ent->nextthink = level.time + FRAMETIME * 2; ent->think = FinishSpawningBuildable; } + +/* +============ +G_CheckDBProtection + +Count how many designated builders are in both teams and +if none found in some team, cancel protection for all +structures of that team +============ +*/ + +void G_CheckDBProtection( void ) +{ + int alienDBs = 0, humanDBs = 0, i; + gentity_t *ent; + + // count designated builders + for( i = 0, ent = g_entities + i; i < level.maxclients; i++, ent++) + { + if( !ent->client || ( ent->client->pers.connected != CON_CONNECTED ) ) + continue; + + if( ent->client->pers.designatedBuilder) + { + if( ent->client->pers.teamSelection == PTE_HUMANS ) + { + humanDBs++; + } + else if( ent->client->pers.teamSelection == PTE_ALIENS ) + { + alienDBs++; + } + } + } + + // both teams have designate builders, we're done + if( alienDBs > 0 && humanDBs > 0 ) + return; + + // cancel protection if needed + for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++) + { + if( ent->s.eType != ET_BUILDABLE) + continue; + + if( ( !alienDBs && ent->biteam == BIT_ALIENS ) || + ( !humanDBs && ent->biteam == BIT_HUMANS ) ) + { + ent->s.eFlags &= ~EF_DBUILDER; + } + } +} + Index: src/game/g_admin.c =================================================================== --- src/game/g_admin.c (revision 829) +++ src/game/g_admin.c (working copy) @@ -60,6 +60,11 @@ "" }, + {"designate", G_admin_designate, "d", + "give the player designated builder privileges", + "[^3name|slot#^7]" + }, + {"help", G_admin_help, "h", "display commands available to you or help on a specific command", "(^5command^7)" @@ -153,6 +158,11 @@ "[^3a|h^7]" }, + {"undesignate", G_admin_designate, "d", + "revoke designated builder privileges", + "[^3name|slot#^7]" + }, + {"unmute", G_admin_mute, "m", "unmute a muted player", "[^3name|slot#^7]" @@ -2022,6 +2032,7 @@ char lname2[ MAX_NAME_LENGTH ]; char guid_stub[ 9 ]; char muted[ 2 ]; + char dbuilder[ 2 ]; int l; char lname_fmt[ 5 ]; @@ -2064,6 +2075,20 @@ Q_strncpyz( muted, "M", sizeof( muted ) ); } + dbuilder[ 0 ] = '\0'; + if( p->pers.designatedBuilder ) + { + if( !G_admin_permission( &g_entities[ i ], ADMF_INCOGNITO ) && + G_admin_permission( &g_entities[ i ], ADMF_DBUILDER ) ) + { + Q_strncpyz( dbuilder, "P", sizeof( dbuilder ) ); + } + else + { + Q_strncpyz( dbuilder, "D", sizeof( dbuilder ) ); + } + } + l = 0; G_SanitiseName( p->pers.netname, n2 ); n[ 0 ] = '\0'; @@ -2105,7 +2130,7 @@ } - ADMBP( va( "%2i %s%s^7 %-2i %s^7 (*%s) ^1%1s^7 %s^7 %s%s^7%s\n", + ADMBP( va( "%2i %s%s^7 %-2i %s^7 (*%s) ^1%1s^3 %1s^7 %s^7 %s%s^7%s\n", i, c, t, @@ -2113,6 +2138,7 @@ ( *lname ) ? lname2 : "", guid_stub, muted, + dbuilder, p->pers.netname, ( *n ) ? "(a.k.a. " : "", n, @@ -2705,6 +2731,69 @@ return qtrue; } +qboolean G_admin_designate( gentity_t *ent, int skiparg ) +{ + int pids[ MAX_CLIENTS ]; + char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; + char command[ MAX_ADMIN_CMD_LEN ], *cmd; + gentity_t *vic; + + if( G_SayArgc() < 2 + skiparg ) + { + ADMP( "^3!designate: ^7usage: designate [name|slot#]\n" ); + return qfalse; + } + G_SayArgv( skiparg, command, sizeof( command ) ); + cmd = command; + if( cmd && *cmd == '!' ) + cmd++; + G_SayArgv( 1 + skiparg, name, sizeof( name ) ); + if( G_ClientNumbersFromString( name, pids ) != 1 ) + { + G_MatchOnePlayer( pids, err, sizeof( err ) ); + ADMP( va( "^3!designate: ^7%s\n", err ) ); + return qfalse; + } + if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) && + !Q_stricmp( cmd, "undesignate" ) ) + { + ADMP( "^3!mute: ^7sorry, but your intended victim has a higher admin" + " level than you\n" ); + return qfalse; + } + vic = &g_entities[ pids[ 0 ] ]; + if( vic->client->pers.designatedBuilder == qtrue ) + { + if( !Q_stricmp( cmd, "designate" ) ) + { + ADMP( "^3!designate: ^7player is already designated builder\n" ); + return qtrue; + } + vic->client->pers.designatedBuilder = qfalse; + CPx( pids[ 0 ], "cp \"^1Your designation has been revoked\"" ); + AP( va( + "print \"^3!designate: ^7%s^7's designation has been revoked by %s\n\"", + vic->client->pers.netname, + ( ent ) ? ent->client->pers.netname : "console" ) ); + G_CheckDBProtection( ); + } + else + { + if( !Q_stricmp( cmd, "undesignate" ) ) + { + ADMP( "^3!undesignate: ^7player is not currently designated builder\n" ); + return qtrue; + } + vic->client->pers.designatedBuilder = qtrue; + CPx( pids[ 0 ], "cp \"^1You've been designated\"" ); + AP( va( "print \"^3!designate: ^7%s^7 has been designated by ^7%s\n\"", + vic->client->pers.netname, + ( ent ) ? ent->client->pers.netname : "console" ) ); + } + ClientUserinfoChanged( pids[ 0 ] ); + return qtrue; +} + /* * This function facilitates the TP define. ADMP() is similar to CP except that * it prints the message to the server console if ent is not defined. Index: src/game/g_admin.h =================================================================== --- src/game/g_admin.h (revision 829) +++ src/game/g_admin.h (working copy) @@ -55,6 +55,7 @@ * 0 - inactivity rules do not apply to them * ! - admin commands cannot be used on them * @ - does not show up as an admin in !listplayers + * # - permanent designated builder */ #define ADMF_IMMUNITY '1' #define ADMF_NOCENSORFLOOD '2' /* TODO */ @@ -69,6 +70,7 @@ #define ADMF_IMMUTABLE '!' #define ADMF_INCOGNITO '@' +#define ADMF_DBUILDER '#' #define MAX_ADMIN_LISTITEMS 20 #define MAX_ADMIN_SHOWBANS 10 @@ -163,6 +165,7 @@ 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_designate( gentity_t *ent, int skiparg ); void G_admin_print( gentity_t *ent, char *m ); void G_admin_buffer_print( gentity_t *ent, char *m ); Index: src/game/bg_public.h =================================================================== --- src/game/bg_public.h (revision 829) +++ src/game/bg_public.h (working copy) @@ -295,6 +295,7 @@ #define EF_TEAMVOTED 0x00010000 // already cast a vote #define EF_BLOBLOCKED 0x00020000 // TA: caught by a trapper #define EF_REAL_LIGHT 0x00040000 // TA: light sprites according to ambient light +#define EF_DBUILDER 0x00080000 // designated builder protection typedef enum { Index: src/game/g_cmds.c =================================================================== --- src/game/g_cmds.c (revision 829) +++ src/game/g_cmds.c (working copy) @@ -638,6 +638,22 @@ ent->client->pers.savedCredit = 0; } + if( G_admin_permission( ent, ADMF_DBUILDER ) ) + { + if( !ent->client->pers.designatedBuilder ) + { + ent->client->pers.designatedBuilder = qtrue; + trap_SendServerCommand( ent-g_entities, + "print \"Your designation has been restored\n\"" ); + } + } + else if( ent->client->pers.designatedBuilder ) + { + ent->client->pers.designatedBuilder = qfalse; + trap_SendServerCommand( ent-g_entities, + "print \"You have lost designation due to teamchange\n\"" ); + } + ent->client->pers.classSelection = PCL_NONE; ClientSpawn( ent, NULL, NULL, NULL ); @@ -646,6 +662,7 @@ //update ClientInfo ClientUserinfoChanged( ent->client->ps.clientNum ); + G_CheckDBProtection( ); } /* @@ -1327,11 +1344,116 @@ sizeof( level.teamVoteDisplayString[ cs_offset ] ), "Kick player \'%s\'", kickee ); } + else if( !Q_stricmp( arg1, "designate" ) || + !Q_stricmp( arg1, "undesignate" ) ) + { + int clientNum = 0; + int designate = Q_stricmp( arg1, "undesignate" ); + int clientNums[ MAX_CLIENTS ] = { -1 }; + char name[ MAX_NETNAME ]; + + if( !g_admin.string[ 0 ] ) + { + trap_SendServerCommand( ent-g_entities, + "print \"Admin system is not enabled\n\"" ); + return; + } + + //check arg2 is a number + for( i = 0; arg2[ i ]; i++ ) + { + if( arg2[ i ] < '0' || arg2[ i ] > '9' ) + { + clientNum = -1; + break; + } + } + + if( clientNum >= 0 && clientNum < level.maxclients ) + { + clientNum = atoi( arg2 ); + } + + if( clientNum < 0 || clientNum >= level.maxclients ) + { + if( G_ClientNumbersFromString( arg2, clientNums ) == 1 ) + { + // there was one partial name match or name was clientNum + clientNum = clientNums[ 0 ]; + } + else + { + // look for an exact name match before bailing out + clientNum = G_ClientNumberFromString( ent, arg2 ); + if( clientNum == -1 ) + { + trap_SendServerCommand( ent-g_entities, + "print \"callvote: invalid player\n\"" ); + return; + } + } + } + + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) + continue; + + if( level.clients[ i ].ps.stats[ STAT_PTEAM ] != team ) + continue; + + if( level.clients[ i ].ps.clientNum == clientNum ) + break; + } + + if( i >= level.maxclients ) + { + trap_SendServerCommand( ent-g_entities, va( "print \"client %s " + S_COLOR_WHITE "is not a valid player on your team\n\"", arg2 ) ); + return; + } + + if( designate && level.clients[ i ].pers.designatedBuilder ) + { + trap_SendServerCommand( ent-g_entities, va( "print \"%s " + S_COLOR_WHITE "is already designated builder\n\"", + level.clients[ clientNum ].pers.netname ) ); + return; + } + else if( !designate && !level.clients[ i ].pers.designatedBuilder ) + { + trap_SendServerCommand( ent-g_entities, va( "print \"%s " + S_COLOR_WHITE "is not a designated builder\n\"", + level.clients[ clientNum ].pers.netname ) ); + return; + } + + if( !designate && + G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) + { + trap_SendServerCommand( ent-g_entities, + "print \"callteamvote: admin is immune from vote undesignation\n\"" ); + return; + } + + Q_strncpyz( name, level.clients[ clientNum ].pers.netname, + sizeof( name ) ); + Q_CleanStr( name ); + + Com_sprintf( level.teamVoteString[ cs_offset ], + sizeof( level.teamVoteString[ cs_offset ] ), + "!%s %d", arg1, clientNum ); + Com_sprintf( level.teamVoteDisplayString[ cs_offset ], + sizeof( level.teamVoteDisplayString[ cs_offset ] ), + "%s player \'%s\'", designate ? "Designate" : "Undesignate", name ); + } else { trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); - trap_SendServerCommand( ent-g_entities, "print \"Valid team vote commands are: teamkick , " - "teamclientkick \n\"" ); + trap_SendServerCommand( ent-g_entities, + "print \"Valid team vote commands are: teamkick , " + "teamclientkick , designate , " + "undesignate \n\"" ); return; } ent->client->pers.voteCount++; @@ -1683,7 +1805,29 @@ } } +/* +================= +DBCommand +Send command to all designated builders of selected team +================= +*/ +void DBCommand( pTeam_t team, const char *text ) +{ + int i; + gentity_t *ent; + + 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 ) + continue; + + trap_SendServerCommand( i, text ); + } +} + /* ================= Cmd_Destroy_f @@ -1696,7 +1840,20 @@ gentity_t *traceEnt; if( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) - G_Damage( ent->client->hovel, ent, ent, forward, ent->s.origin, 10000, 0, MOD_SUICIDE ); + { + 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 ) ); + return; + } + G_Damage( ent->client->hovel, ent, ent, forward, ent->s.origin, + 10000, 0, MOD_SUICIDE ); + } if( !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) ) { @@ -1712,13 +1869,24 @@ ( ( ent->client->ps.weapon >= WP_ABUILD ) && ( ent->client->ps.weapon <= WP_HBUILD ) ) ) { + 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 ) ); + return; + } + // Don't allow destruction of hovel with granger inside if( traceEnt->s.modelindex == BA_A_HOVEL && traceEnt->active ) return; // Don't allow destruction of buildables that cannot be rebuilt if( g_suddenDeathTime.integer && ( level.time - level.startTime >= - g_suddenDeathTime.integer * 60000 ) && + g_suddenDeathTime.integer * 60000 ) && ( traceEnt->health > 0 ) && BG_FindBuildPointsForBuildable( traceEnt->s.modelindex ) ) { return; @@ -2310,12 +2478,60 @@ /* ================= +Cmd_Protect_f +================= +*/ +void Cmd_Protect_f( gentity_t *ent ) +{ + vec3_t forward, end; + trace_t tr; + gentity_t *traceEnt; + + if( !ent->client->pers.designatedBuilder ) + { + trap_SendServerCommand( ent-g_entities, "print \"Only designated" + " builders can toggle structure protection.\n\"" ); + return; + } + + 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->biteam == ent->client->pers.teamSelection ) ) + { + if( traceEnt->s.eFlags & EF_DBUILDER ) + { + trap_SendServerCommand( ent-g_entities, + "print \"Structure protection removed\n\"" ); + traceEnt->s.eFlags &= ~EF_DBUILDER; + } + else + { + trap_SendServerCommand( ent-g_entities, + "print \"Structure protection applied\n\"" ); + traceEnt->s.eFlags |= EF_DBUILDER; + } + } +} + +/* +================= Cmd_Reload_f ================= */ void Cmd_Reload_f( gentity_t *ent ) { - if( ent->client->ps.weaponstate != WEAPON_RELOADING ) + if( ( ent->client->ps.weapon >= WP_ABUILD ) && + ( ent->client->ps.weapon <= WP_HBUILD ) ) + { + Cmd_Protect_f( ent ); + } + else if( ent->client->ps.weaponstate != WEAPON_RELOADING ) ent->client->ps.pm_flags |= PMF_WEAPON_RELOAD; } @@ -2585,7 +2801,28 @@ ent->client->lastPoisonClient = ent;*/ } +/* +================= +Cmd_Resign_f +================= +*/ +void Cmd_Resign_f( gentity_t *ent ) +{ + if( !ent->client->pers.designatedBuilder ) + { + trap_SendServerCommand( ent-g_entities, + "print \"You are not a designated builder\n\"" ); + return; + } + ent->client->pers.designatedBuilder = qfalse; + trap_SendServerCommand( -1, va( + "print \"%s" S_COLOR_WHITE " has resigned\n\"", + ent->client->pers.netname ) ); + G_CheckDBProtection( ); +} + + /* ================= ClientCommand @@ -2665,6 +2902,8 @@ Cmd_Destroy_f( ent, qfalse ); else if( Q_stricmp( cmd, "deconstruct" ) == 0 ) Cmd_Destroy_f( ent, qtrue ); + else if( Q_stricmp( cmd, "protect" ) == 0 ) + Cmd_Protect_f( ent ); else if( Q_stricmp( cmd, "reload" ) == 0 ) Cmd_Reload_f( ent ); else if( Q_stricmp( cmd, "boost" ) == 0 ) @@ -2693,6 +2932,8 @@ Cmd_PTRCRestore_f( ent ); else if( Q_stricmp( cmd, "test" ) == 0 ) Cmd_Test_f( ent ); + else if( Q_stricmp( cmd, "resign" ) == 0 ) + Cmd_Resign_f( ent ); else trap_SendServerCommand( clientNum, va( "print \"unknown cmd %s\n\"", cmd ) ); }