Index: src/game/g_local.h =================================================================== --- src/game/g_local.h (revision 1006) +++ src/game/g_local.h (working copy) @@ -357,6 +357,7 @@ qboolean muted; qboolean denyBuild; int adminLevel; + qboolean designatedBuilder; } clientPersistant_t; #define MAX_UNLAGGED_MARKERS 10 @@ -662,14 +663,15 @@ pTeam_t surrenderTeam; } level_locals_t; -#define CMD_CHEAT 0x01 -#define CMD_MESSAGE 0x02 // sends message to others (skip when muted) -#define CMD_TEAM 0x04 // must be on a team -#define CMD_NOTEAM 0x08 // must not be on a team -#define CMD_ALIEN 0x10 -#define CMD_HUMAN 0x20 -#define CMD_LIVING 0x40 -#define CMD_INTERMISSION 0x80 // valid during intermission +#define CMD_CHEAT 0x001 +#define CMD_MESSAGE 0x002 // sends message to others (skip when muted) +#define CMD_TEAM 0x004 // must be on a team +#define CMD_NOTEAM 0x008 // must not be on a team +#define CMD_ALIEN 0x010 +#define CMD_HUMAN 0x020 +#define CMD_LIVING 0x040 +#define CMD_INTERMISSION 0x080 // valid during intermission +#define CMD_DBUILDER 0x100 // designated builder only typedef struct { @@ -693,6 +695,7 @@ // g_cmds.c // void Cmd_Score_f( gentity_t *ent ); +void DBCommand( pTeam_t team, const char *text ); void G_StopFollowing( gentity_t *ent ); qboolean G_FollowNewClient( gentity_t *ent, int dir ); void G_ToggleFollow( gentity_t *ent ); @@ -755,12 +758,16 @@ void G_BuildableThink( gentity_t *ent, int msec ); qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable ); +qboolean G_FreeBuildableAllowed( gentity_t *player, gentity_t *buildable ); +qboolean G_DeconstructAllowed( gentity_t *player, gentity_t *buildable ); itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance, vec3_t origin ); qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable ); void G_SetBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim, qboolean force ); 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 ); + void G_LayoutSave( char *name ); int G_LayoutList( const char *map, char *list, int len ); void G_LayoutSelect( void ); Index: src/game/g_buildable.c =================================================================== --- src/game/g_buildable.c (revision 1006) +++ src/game/g_buildable.c (working copy) @@ -2272,7 +2272,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 ) { @@ -2588,6 +2589,89 @@ /* =============== +G_FreeBuildableAllowed + +Test if the player is allowed to remove a marked buildable +=============== +*/ +qboolean G_FreeBuildableAllowed( gentity_t *player, gentity_t *buildable ) { + // sanity check + if( buildable->s.eType != ET_BUILDABLE || !player->client || + buildable->biteam != player->client->pers.teamSelection ) { + return qfalse; + } + + // hovel with granger inside, no more granger astronauts + if( buildable->s.modelindex == BA_A_HOVEL && buildable->active ) + return qfalse; + + // last spawn?! NO WAY! + if( player->client->pers.teamSelection == PTE_ALIENS && + buildable->s.modelindex == BA_A_SPAWN && level.numAlienSpawns <= 1 ) { + return qfalse; + } + else if( player->client->pers.teamSelection == PTE_HUMANS && + buildable->s.modelindex == BA_H_SPAWN && level.numHumanSpawns <= 1 ) { + return qfalse; + } + + // not marked, leave alone + if( g_markDeconstruct.integer && !buildable->deconstruct ) { + return qfalse; + } + + // this one is silent + if( ( buildable->s.eFlags & EF_DBUILDER ) && + !player->client->pers.designatedBuilder ) + { + return qfalse; + } + + return qtrue; +} + +/* +=============== +G_DeconstructAllowed + +Test if the player is allowed to decon/mark/unmark a buildable +=============== +*/ +qboolean G_DeconstructAllowed( gentity_t *player, gentity_t *buildable ) { + // sanity check + if( buildable->s.eType != ET_BUILDABLE || !player->client || + buildable->biteam != player->client->pers.teamSelection ) { + return qfalse; + } + + // no deconstruction of what you can't rebuild, it's sudden death! + if( G_TimeTilSuddenDeath( ) <= 0 && buildable->health > 0 && + BG_FindBuildPointsForBuildable( buildable->s.modelindex ) ) + { + return qfalse; + } + + // this one is loud + if( ( buildable->s.eFlags & EF_DBUILDER ) && + !player->client->pers.designatedBuilder ) + { + trap_SendServerCommand( player-g_entities, + "print \"This structure is protected by designated builder\n\"" ); + DBCommand( player->client->pers.teamSelection, + va( "print \"%s^3 has attempted to decon a protected structure!\n\"", + player->client->pers.netname ) ); + return qfalse; + } + + // mark decon disabled => decon = free + if( !g_markDeconstruct.integer ) + return G_FreeBuildableAllowed( player, buildable); + + return qtrue; // it's ok +} + +/* +=============== G_FreeMarkedBuildables Free up build points for a team by deconstructing marked buildables @@ -2617,10 +2701,11 @@ and list the buildables that much be destroyed if this is the case =============== */ -static qboolean G_SufficientBPAvailable( buildableTeam_t team, +static qboolean G_SufficientBPAvailable( gentity_t *player, int buildPoints, buildable_t buildable ) { + buildableTeam_t team = player->client->pers.teamSelection; int i; int numBuildables = level.numBuildablesForRemoval; int pointsYielded = 0; @@ -2659,32 +2744,18 @@ // Build a list of buildable entities for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) { - if( !ent->inuse ) + if( !ent->inuse || ent->health <= 0 ) continue; - if( ent->health <= 0 ) + // check buildable free permission + if( !G_FreeBuildableAllowed( player, ent ) ) continue; - // Don't allow destruction of hovel with granger inside - if( ent->s.modelindex == BA_A_HOVEL && ent->active ) - continue; - - if( ent->biteam != team ) - continue; - - // Prevent destruction of the last spawn - if( remainingSpawns <= 1 ) - { - if( ent->s.modelindex == BA_A_SPAWN || ent->s.modelindex == BA_H_SPAWN ) - continue; - } - // If it's a unique buildable, it can only be replaced by the same type if( unique && ent->s.modelindex != buildable ) continue; - if( ent->deconstruct ) - level.markedBuildables[ numBuildables++ ] = ent; + level.markedBuildables[ numBuildables++ ] = ent; } // We still need build points, but have no candidates for removal @@ -2801,49 +2872,29 @@ for(i = 0; i < num; i++) { gentity_t *tent = &g_entities[ entitylist[ i ] ]; - if( tent->s.eType == ET_PLAYER ) + if( !G_FreeBuildableAllowed( ent, tent ) ) { reason = IBE_NOROOM; break; } - else if( tent->biteam != ent->client->ps.stats[ STAT_PTEAM ] ) + else if( tent->s.modelindex == BA_H_REACTOR && buildable != BA_H_REACTOR ) { reason = IBE_NOROOM; + break; } - else if( tent->s.eType == ET_BUILDABLE && !tent->deconstruct ) + else if( tent->s.modelindex == BA_A_OVERMIND && buildable != BA_A_OVERMIND ) { reason = IBE_NOROOM; break; } - else - { - if( tent->s.modelindex == BA_H_SPAWN && level.numHumanSpawns <= 1 ) - { - reason = IBE_NOROOM; - break; - } - else if( tent->s.modelindex == BA_A_SPAWN && level.numAlienSpawns <= 1 ) - { - reason = IBE_NOROOM; - break; - } - else if( tent->s.modelindex == BA_H_REACTOR && buildable != BA_H_REACTOR ) - { - reason = IBE_NOROOM; - break; - } - else if( tent->s.modelindex == BA_A_OVERMIND && buildable != BA_A_OVERMIND ) - { - reason = IBE_NOROOM; - break; - } - level.markedBuildables[ level.numBuildablesForRemoval++ ] = tent; - } + level.markedBuildables[ level.numBuildablesForRemoval++ ] = tent; } + if( reason != IBE_NONE ) { level.numBuildablesForRemoval = 0; } + if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { //alien criteria @@ -2893,7 +2944,8 @@ if( tempent->s.eType != ET_BUILDABLE ) continue; - if( tempent->s.modelindex == buildable && !tempent->deconstruct ) + if( tempent->s.modelindex == buildable && + !G_FreeBuildableAllowed( ent, tempent ) ) { switch( buildable ) { @@ -2912,20 +2964,15 @@ break; } - if( tempent->s.modelindex == BA_A_HOVEL && - buildable == BA_A_HOVEL && - tempent->active ) + + if( tempent->s.modelindex == buildable ) { - reason = IBE_HOVEL; - } - else if( tempent->s.modelindex == buildable ) - { level.markedBuildables[ level.numBuildablesForRemoval++ ] = tempent; } } } - if( !G_SufficientBPAvailable( BIT_ALIENS, buildPoints, buildable ) ) + if( !G_SufficientBPAvailable( ent, buildPoints, buildable ) ) reason = IBE_NOASSERT; } else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) @@ -2992,7 +3039,8 @@ if( tempent->s.eType != ET_BUILDABLE ) continue; - if( tempent->s.modelindex == BA_H_REACTOR && !tempent->deconstruct ) + if( tempent->s.modelindex == BA_H_REACTOR && + !G_FreeBuildableAllowed( ent, tempent ) ) { reason = IBE_REACTOR; break; @@ -3002,7 +3050,7 @@ } } - if( !G_SufficientBPAvailable( BIT_HUMANS, buildPoints, buildable ) ) + if( !G_SufficientBPAvailable( ent, buildPoints, buildable ) ) reason = IBE_NOPOWER; } @@ -3207,7 +3255,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; @@ -3435,6 +3490,57 @@ /* ============ +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; + } + } +} + +/* +============ G_LayoutSave ============ Index: src/game/g_admin.c =================================================================== --- src/game/g_admin.c (revision 1006) +++ src/game/g_admin.c (working copy) @@ -65,6 +65,11 @@ "" }, + {"designate", G_admin_designate, "d", + "give the player designated builder privileges", + "[^3name|slot#^7]" + }, + {"denybuild", G_admin_denybuild, "d", "take away a player's ability to build", "[^3name|slot#^7]" @@ -173,6 +178,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]" @@ -2300,6 +2310,18 @@ { Q_strncpyz( denied, "B", sizeof( denied ) ); } + else if( p->pers.designatedBuilder ) + { + if( !G_admin_permission( &g_entities[ i ], ADMF_INCOGNITO ) && + G_admin_permission( &g_entities[ i ], ADMF_DBUILDER ) ) + { + Q_strncpyz( denied, "P", sizeof( denied ) ); + } + else + { + Q_strncpyz( denied, "D", sizeof( denied ) ); + } + } l = 0; G_SanitiseName( p->pers.netname, n2 ); @@ -2967,6 +2989,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; +} + /* ================ G_admin_print Index: src/game/g_admin.h =================================================================== --- src/game/g_admin.h (revision 1006) +++ 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 + * # - permanently 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 @@ -167,6 +169,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 1006) +++ src/game/bg_public.h (working copy) @@ -292,6 +292,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 1006) +++ src/game/g_cmds.c (working copy) @@ -632,6 +632,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 ); @@ -640,6 +656,7 @@ //update ClientInfo ClientUserinfoChanged( ent->client->ps.clientNum ); + G_CheckDBProtection( ); } /* @@ -1270,7 +1287,9 @@ // detect clientNum for partial name match votes if( !Q_stricmp( arg1, "kick" ) || !Q_stricmp( arg1, "denybuild" ) || - !Q_stricmp( arg1, "allowbuild" ) ) + !Q_stricmp( arg1, "allowbuild" ) || + !Q_stricmp( arg1, "designate" ) || + !Q_stricmp( arg1, "undesignate" ) ) { int clientNums[ MAX_CLIENTS ] = { -1 }; @@ -1375,6 +1394,41 @@ sizeof( level.teamVoteDisplayString[ cs_offset ] ), "Allow '%s' to build", name ); } + else if( !Q_stricmp( arg1, "designate" ) || + !Q_stricmp( arg1, "undesignate" ) ) + { + int designate = Q_stricmp( arg1, "undesignate" ); + + if( designate && level.clients[ clientNum ].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[ clientNum ].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; + } + + 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 if( !Q_stricmp( arg1, "admitdefeat" ) ) { Com_sprintf( level.teamVoteString[ cs_offset ], @@ -1388,7 +1442,7 @@ trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); trap_SendServerCommand( ent-g_entities, "print \"Valid team vote commands are: " - "kick, denybuild, allowbuild and admitdefeat\n\"" ); + "kick, denybuild, allowbuild, designate, undesignate and admitdefeat\n\"" ); return; } ent->client->pers.voteCount++; @@ -1737,7 +1791,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 @@ -1763,7 +1839,19 @@ deconstruct = qfalse; if( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) + { + 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 ) ) { @@ -1779,39 +1867,25 @@ ( ( ent->client->ps.weapon >= WP_ABUILD ) && ( ent->client->ps.weapon <= WP_HBUILD ) ) ) { + // test decon permission + if( !G_DeconstructAllowed( ent, traceEnt ) ) + return; + // Cancel deconstruction if( g_markDeconstruct.integer && traceEnt->deconstruct ) { traceEnt->deconstruct = qfalse; - return; - } + G_TeamCommand( ent->client->pers.teamSelection, + va( "print \"%s's deconstruction mark cleared by %s^7\n\"", + BG_FindHumanNameForBuildable( traceEnt->s.modelindex ), + ent->client->pers.netname ) ); - // Prevent destruction of the last spawn - if( !g_markDeconstruct.integer ) - { - if( ent->client->pers.teamSelection == PTE_ALIENS && - traceEnt->s.modelindex == BA_A_SPAWN ) - { - if( level.numAlienSpawns <= 1 ) - return; - } - else if( ent->client->pers.teamSelection == PTE_HUMANS && - traceEnt->s.modelindex == BA_H_SPAWN ) - { - if( level.numHumanSpawns <= 1 ) - return; - } - } - - // Don't allow destruction of hovel with granger inside - if( traceEnt->s.modelindex == BA_A_HOVEL && traceEnt->active ) + G_LogPrintf( "Decon mark cleared: %i %i 0: %s unmarked %s\n", + ent->client->ps.clientNum, + traceEnt->s.modelindex, + ent->client->pers.netname, + BG_FindNameForBuildable( traceEnt->s.modelindex ) ); return; - - // Don't allow destruction of buildables that cannot be rebuilt - if( G_TimeTilSuddenDeath( ) <= 0 && - BG_FindBuildPointsForBuildable( traceEnt->s.modelindex ) ) - { - return; } if( ent->client->ps.stats[ STAT_MISC ] > 0 ) @@ -1826,6 +1900,16 @@ { traceEnt->deconstruct = qtrue; // Mark buildable for deconstruction traceEnt->deconstructTime = level.time; + G_TeamCommand( ent->client->pers.teamSelection, + va( "print \"%s ^3MARKED FOR DECONSTRUCTION^7 by %s^7\n\"", + BG_FindHumanNameForBuildable( traceEnt->s.modelindex ), + ent->client->pers.netname ) ); + + G_LogPrintf( "Decon mark: %i %i 0: %s marked %s\n", + ent->client->ps.clientNum, + traceEnt->s.modelindex, + ent->client->pers.netname, + BG_FindNameForBuildable( traceEnt->s.modelindex ) ); } else { @@ -2459,12 +2543,53 @@ /* ================= +Cmd_Protect_f +================= +*/ +void Cmd_Protect_f( gentity_t *ent ) +{ + vec3_t forward, end; + trace_t tr; + gentity_t *traceEnt; + + 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; } @@ -2737,6 +2862,20 @@ } } +/* +================= +Cmd_Resign_f +================= +*/ +void Cmd_Resign_f( gentity_t *ent ) +{ + ent->client->pers.designatedBuilder = qfalse; + trap_SendServerCommand( -1, va( + "print \"%s" S_COLOR_WHITE " has resigned\n\"", + ent->client->pers.netname ) ); + G_CheckDBProtection( ); +} + static void Cmd_Ignore_f( gentity_t *ent ) { int pids[ MAX_CLIENTS ]; @@ -2846,9 +2985,11 @@ { "where", CMD_TEAM, Cmd_Where_f }, { "teamvote", CMD_TEAM, Cmd_TeamVote_f }, { "class", CMD_TEAM, Cmd_Class_f }, + { "resign", CMD_TEAM|CMD_DBUILDER, Cmd_Resign_f }, { "build", CMD_TEAM|CMD_LIVING, Cmd_Build_f }, { "deconstruct", CMD_TEAM|CMD_LIVING, Cmd_Destroy_f }, + { "protect", CMD_TEAM|CMD_LIVING|CMD_DBUILDER, Cmd_Protect_f }, { "buy", CMD_HUMAN|CMD_LIVING, Cmd_Buy_f }, { "sell", CMD_HUMAN|CMD_LIVING, Cmd_Sell_f }, @@ -2946,6 +3087,13 @@ return; } + if( cmds[ i ].cmdFlags & CMD_DBUILDER && !ent->client->pers.designatedBuilder ) + { + trap_SendServerCommand( clientNum, + "print \"You are not a designated builder\n\"" ); + return; + } + cmds[ i ].cmdHandler( ent ); } Index: src/ui/ui_main.c =================================================================== --- src/ui/ui_main.c (revision 1006) +++ src/ui/ui_main.c (working copy) @@ -4210,6 +4210,22 @@ uiInfo.teamClientNums[ uiInfo.teamIndex ] ) ); } } + else if( Q_stricmp( name, "voteTeamDesignate" ) == 0 ) + { + if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount ) + { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote designate %d\n", + uiInfo.teamClientNums[ uiInfo.teamIndex ] ) ); + } + } + else if( Q_stricmp( name, "voteTeamUndesignate" ) == 0 ) + { + if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount ) + { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote undesignate %d\n", + uiInfo.teamClientNums[ uiInfo.teamIndex ] ) ); + } + } else if( Q_stricmp( name, "voteTeamDenyBuild" ) == 0 ) { if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount )