diff options
author | Tim Angus <tim@ngus.net> | 2006-12-29 15:07:52 +0000 |
---|---|---|
committer | Tim Angus <tim@ngus.net> | 2006-12-29 15:07:52 +0000 |
commit | df4de6463251019ecf150d835c3693bf7502ccd1 (patch) | |
tree | 0883015c9176f5a924efdaf73e71744a0cb4f537 /src/game | |
parent | 210115d7ef5f8be82036458359bdf9f00c2bfb67 (diff) |
* Buildable destruction marking (via g_markDeconstruct)
* It's now impossible to destroy the last spawn
* Start of a new client side buildable status display
Diffstat (limited to 'src/game')
-rw-r--r-- | src/game/bg_public.h | 11 | ||||
-rw-r--r-- | src/game/g_active.c | 4 | ||||
-rw-r--r-- | src/game/g_buildable.c | 421 | ||||
-rw-r--r-- | src/game/g_client.c | 4 | ||||
-rw-r--r-- | src/game/g_cmds.c | 70 | ||||
-rw-r--r-- | src/game/g_local.h | 19 | ||||
-rw-r--r-- | src/game/g_main.c | 30 | ||||
-rw-r--r-- | src/game/g_weapon.c | 6 |
8 files changed, 417 insertions, 148 deletions
diff --git a/src/game/bg_public.h b/src/game/bg_public.h index 69a44072..272603c2 100644 --- a/src/game/bg_public.h +++ b/src/game/bg_public.h @@ -454,12 +454,13 @@ typedef enum BIT_NUM_TEAMS } buildableTeam_t; -#define B_HEALTH_BITS 5 -#define B_HEALTH_SCALE (float)((1<<B_HEALTH_BITS)-1) +#define B_HEALTH_BITS 12 +#define B_HEALTH_MASK ((1<<B_HEALTH_BITS)-1) -#define B_SPAWNED_TOGGLEBIT 0x00000020 -#define B_POWERED_TOGGLEBIT 0x00000040 -#define B_DCCED_TOGGLEBIT 0x00000080 +#define B_MARKED_TOGGLEBIT 0x00001000 +#define B_SPAWNED_TOGGLEBIT 0x00002000 +#define B_POWERED_TOGGLEBIT 0x00004000 +#define B_DCCED_TOGGLEBIT 0x00008000 // reward sounds (stored in ps->persistant[PERS_PLAYEREVENTS]) diff --git a/src/game/g_active.c b/src/game/g_active.c index d62461bd..508da305 100644 --- a/src/game/g_active.c +++ b/src/game/g_active.c @@ -674,7 +674,7 @@ void ClientTimerActions( gentity_t *ent, int msec ) int dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] ); vec3_t dummy; - if( G_itemFits( ent, client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT, + if( G_CanBuild( ent, client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT, dist, dummy ) == IBE_NONE ) client->ps.stats[ STAT_BUILDABLE ] |= SB_VALID_TOGGLEBIT; else @@ -1580,7 +1580,7 @@ void ClientThink_real( gentity_t *ent ) client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING; //hovel is empty - G_setBuildableAnim( hovel, BANIM_ATTACK2, qfalse ); + G_SetBuildableAnim( hovel, BANIM_ATTACK2, qfalse ); hovel->active = qfalse; } else diff --git a/src/game/g_buildable.c b/src/game/g_buildable.c index b7abfbe6..a29fce6f 100644 --- a/src/game/g_buildable.c +++ b/src/game/g_buildable.c @@ -28,12 +28,12 @@ extern char *modNames[ ]; /* ================ -G_setBuildableAnim +G_SetBuildableAnim Triggers an animation client side ================ */ -void G_setBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim, qboolean force ) +void G_SetBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim, qboolean force ) { int localAnim = anim; @@ -47,12 +47,12 @@ void G_setBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim, qboolean fo /* ================ -G_setIdleBuildableAnim +G_SetIdleBuildableAnim Set the animation to use whilst no other animations are running ================ */ -void G_setIdleBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim ) +void G_SetIdleBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim ) { ent->s.torsoAnim = anim; } @@ -156,12 +156,12 @@ static int G_NumberOfDependants( gentity_t *self ) /* ================ -findPower +G_FindPower attempt to find power for self, return qtrue if successful ================ */ -static qboolean findPower( gentity_t *self ) +static qboolean G_FindPower( gentity_t *self ) { int i; gentity_t *ent; @@ -220,12 +220,12 @@ static qboolean findPower( gentity_t *self ) /* ================ -G_isPower +G_IsPowered -Simple wrapper to findPower to check if a location has power +Simple wrapper to G_FindPower to check if a location has power ================ */ -qboolean G_isPower( vec3_t origin ) +qboolean G_IsPowered( vec3_t origin ) { gentity_t dummy; @@ -234,17 +234,17 @@ qboolean G_isPower( vec3_t origin ) dummy.s.modelindex = BA_NONE; VectorCopy( origin, dummy.s.origin ); - return findPower( &dummy ); + return G_FindPower( &dummy ); } /* ================ -findDCC +G_FindDCC attempt to find a controlling DCC for self, return qtrue if successful ================ */ -static qboolean findDCC( gentity_t *self ) +static qboolean G_FindDCC( gentity_t *self ) { int i; gentity_t *ent; @@ -284,7 +284,7 @@ static qboolean findDCC( gentity_t *self ) } } - //if there were no power items nearby give up + //if there was no nearby DCC give up if( !foundDCC ) return qfalse; @@ -295,12 +295,12 @@ static qboolean findDCC( gentity_t *self ) /* ================ -G_isDCC +G_IsDCCBuilt -simple wrapper to findDCC to check for a dcc +simple wrapper to G_FindDCC to check for a dcc ================ */ -qboolean G_isDCC( void ) +qboolean G_IsDCCBuilt( void ) { gentity_t dummy; @@ -309,17 +309,17 @@ qboolean G_isDCC( void ) dummy.dccNode = NULL; dummy.biteam = BIT_HUMANS; - return findDCC( &dummy ); + return G_FindDCC( &dummy ); } /* ================ -findOvermind +G_FindOvermind Attempt to find an overmind for self ================ */ -static qboolean findOvermind( gentity_t *self ) +static qboolean G_FindOvermind( gentity_t *self ) { int i; gentity_t *ent; @@ -353,12 +353,12 @@ static qboolean findOvermind( gentity_t *self ) /* ================ -G_isOvermind +G_IsOvermindBuilt -Simple wrapper to findOvermind to check if a location has an overmind +Simple wrapper to G_FindOvermind to check if a location has an overmind ================ */ -qboolean G_isOvermind( void ) +qboolean G_IsOvermindBuilt( void ) { gentity_t dummy; @@ -367,17 +367,17 @@ qboolean G_isOvermind( void ) dummy.overmindNode = NULL; dummy.biteam = BIT_ALIENS; - return findOvermind( &dummy ); + return G_FindOvermind( &dummy ); } /* ================ -findCreep +G_FindCreep attempt to find creep for self, return qtrue if successful ================ */ -static qboolean findCreep( gentity_t *self ) +static qboolean G_FindCreep( gentity_t *self ) { int i; gentity_t *ent; @@ -426,12 +426,12 @@ static qboolean findCreep( gentity_t *self ) /* ================ -isCreep +G_IsCreepHere -simple wrapper to findCreep to check if a location has creep +simple wrapper to G_FindCreep to check if a location has creep ================ */ -static qboolean isCreep( vec3_t origin ) +static qboolean G_IsCreepHere( vec3_t origin ) { gentity_t dummy; @@ -441,17 +441,17 @@ static qboolean isCreep( vec3_t origin ) dummy.s.modelindex = BA_NONE; VectorCopy( origin, dummy.s.origin ); - return findCreep( &dummy ); + return G_FindCreep( &dummy ); } /* ================ -creepSlow +G_CreepSlow Set any nearby humans' SS_CREEPSLOWED flag ================ */ -static void creepSlow( gentity_t *self ) +static void G_CreepSlow( gentity_t *self ) { int entityList[ MAX_GENTITIES ]; vec3_t range; @@ -618,8 +618,8 @@ Called when an alien spawn dies */ void ASpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { - G_setBuildableAnim( self, BANIM_DESTROY1, qtrue ); - G_setIdleBuildableAnim( self, BANIM_DESTROYED ); + G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); + G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); self->die = nullDieFunction; self->think = ASpawn_Blast; @@ -687,7 +687,7 @@ void ASpawn_Think( gentity_t *self ) } } - creepSlow( self ); + G_CreepSlow( self ); self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); } @@ -701,7 +701,7 @@ pain function for Alien Spawn */ void ASpawn_Pain( gentity_t *self, gentity_t *attacker, int damage ) { - G_setBuildableAnim( self, BANIM_PAIN1, qfalse ); + G_SetBuildableAnim( self, BANIM_PAIN1, qfalse ); } @@ -749,7 +749,7 @@ void AOvermind_Think( gentity_t *self ) self->timestamp = level.time; G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, self->splashRadius, self, MOD_OVERMIND, PTE_ALIENS ); - G_setBuildableAnim( self, BANIM_ATTACK1, qfalse ); + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); } } @@ -779,7 +779,7 @@ void AOvermind_Think( gentity_t *self ) else self->overmindSpawnsTimer = level.time + OVERMIND_SPAWNS_PERIOD; - creepSlow( self ); + G_CreepSlow( self ); self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); } @@ -805,9 +805,9 @@ pain function for Alien Spawn void ABarricade_Pain( gentity_t *self, gentity_t *attacker, int damage ) { if( rand( ) % 1 ) - G_setBuildableAnim( self, BANIM_PAIN1, qfalse ); + G_SetBuildableAnim( self, BANIM_PAIN1, qfalse ); else - G_setBuildableAnim( self, BANIM_PAIN2, qfalse ); + G_SetBuildableAnim( self, BANIM_PAIN2, qfalse ); } /* @@ -847,8 +847,8 @@ Called when an alien spawn dies */ void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { - G_setBuildableAnim( self, BANIM_DESTROY1, qtrue ); - G_setIdleBuildableAnim( self, BANIM_DESTROYED ); + G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); + G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); self->die = nullDieFunction; self->think = ABarricade_Blast; @@ -886,13 +886,13 @@ Think function for Alien Barricade void ABarricade_Think( gentity_t *self ) { //if there is no creep nearby die - if( !findCreep( self ) ) + if( !G_FindCreep( self ) ) { G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); return; } - creepSlow( self ); + G_CreepSlow( self ); self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); } @@ -937,7 +937,7 @@ void AAcidTube_Damage( gentity_t *self ) self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); } - creepSlow( self ); + G_CreepSlow( self ); self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); } @@ -961,13 +961,13 @@ void AAcidTube_Think( gentity_t *self ) VectorSubtract( self->s.origin, range, mins ); //if there is no creep nearby die - if( !findCreep( self ) ) + if( !G_FindCreep( self ) ) { G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); return; } - if( self->spawned && findOvermind( self ) ) + if( self->spawned && G_FindOvermind( self ) ) { //do some damage num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); @@ -983,13 +983,13 @@ void AAcidTube_Think( gentity_t *self ) self->timestamp = level.time; self->think = AAcidTube_Damage; self->nextthink = level.time + 100; - G_setBuildableAnim( self, BANIM_ATTACK1, qfalse ); + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); return; } } } - creepSlow( self ); + G_CreepSlow( self ); self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); } @@ -1024,7 +1024,7 @@ void AHive_Think( gentity_t *self ) VectorSubtract( self->s.origin, range, mins ); //if there is no creep nearby die - if( !findCreep( self ) ) + if( !G_FindCreep( self ) ) { G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); return; @@ -1033,7 +1033,7 @@ void AHive_Think( gentity_t *self ) 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 && G_FindOvermind( self ) ) { //do some damage num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); @@ -1059,13 +1059,13 @@ void AHive_Think( gentity_t *self ) //fire at target FireWeapon( self ); - G_setBuildableAnim( self, BANIM_ATTACK1, qfalse ); + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); return; } } } - creepSlow( self ); + G_CreepSlow( self ); } @@ -1166,7 +1166,7 @@ void AHovel_Use( gentity_t *self, gentity_t *other, gentity_t *activator ) { vec3_t hovelOrigin, hovelAngles, inverseNormal; - if( self->spawned && findOvermind( self ) ) + if( self->spawned && G_FindOvermind( self ) ) { if( self->active ) { @@ -1185,7 +1185,7 @@ void AHovel_Use( gentity_t *self, gentity_t *other, gentity_t *activator ) } self->active = qtrue; - G_setBuildableAnim( self, BANIM_ATTACK1, qfalse ); + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); //prevent lerping activator->client->ps.eFlags ^= EF_TELEPORT_BIT; @@ -1225,12 +1225,12 @@ void AHovel_Think( gentity_t *self ) if( self->spawned ) { if( self->active ) - G_setIdleBuildableAnim( self, BANIM_IDLE2 ); + G_SetIdleBuildableAnim( self, BANIM_IDLE2 ); else - G_setIdleBuildableAnim( self, BANIM_IDLE1 ); + G_SetIdleBuildableAnim( self, BANIM_IDLE1 ); } - creepSlow( self ); + G_CreepSlow( self ); self->nextthink = level.time + 200; } @@ -1330,7 +1330,7 @@ void ABooster_Touch( gentity_t *self, gentity_t *other, trace_t *trace ) if( !self->spawned ) return; - if( !findOvermind( self ) ) + if( !G_FindOvermind( self ) ) return; if( !client ) @@ -1406,7 +1406,7 @@ void ATrapper_FireOnEnemy( gentity_t *self, int firespeed, float range ) //fire at target FireWeapon( self ); - G_setBuildableAnim( self, BANIM_ATTACK1, qfalse ); + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); self->count = level.time + firespeed; } @@ -1494,18 +1494,18 @@ void ATrapper_Think( gentity_t *self ) int range = BG_FindRangeForBuildable( self->s.modelindex ); int firespeed = BG_FindFireSpeedForBuildable( self->s.modelindex ); - creepSlow( self ); + G_CreepSlow( self ); self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); //if there is no creep nearby die - if( !findCreep( self ) ) + if( !G_FindCreep( self ) ) { G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); return; } - if( self->spawned && findOvermind( self ) ) + if( self->spawned && G_FindOvermind( self ) ) { //if the current target is not valid find a new one if( !ATrapper_CheckTarget( self, self->enemy, range ) ) @@ -1634,7 +1634,7 @@ void HReactor_Think( gentity_t *self ) //reactor under attack if( self->health < self->lastHealth && - level.time > level.humanBaseAttackTimer && G_isDCC( ) ) + level.time > level.humanBaseAttackTimer && G_IsDCCBuilt( ) ) { level.humanBaseAttackTimer = level.time + DCC_ATTACK_PERIOD; G_BroadcastEvent( EV_DCC_ATTACK, 0 ); @@ -1685,7 +1685,7 @@ void HArmoury_Think( gentity_t *self ) //make sure we have power self->nextthink = level.time + POWER_REFRESH_TIME; - self->powered = findPower( self ); + self->powered = G_FindPower( self ); } @@ -1709,7 +1709,7 @@ void HDCC_Think( gentity_t *self ) //make sure we have power self->nextthink = level.time + POWER_REFRESH_TIME; - self->powered = findPower( self ); + self->powered = G_FindPower( self ); } @@ -1735,7 +1735,7 @@ void HMedistat_Think( gentity_t *self ) self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); //make sure we have power - if( !( self->powered = findPower( self ) ) ) + if( !( self->powered = G_FindPower( self ) ) ) { self->nextthink = level.time + POWER_REFRESH_TIME; return; @@ -1751,7 +1751,7 @@ void HMedistat_Think( gentity_t *self ) //if active use the healing idle if( self->active ) - G_setIdleBuildableAnim( self, BANIM_IDLE2 ); + G_SetIdleBuildableAnim( self, BANIM_IDLE2 ); //check if a previous occupier is still here num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); @@ -1787,7 +1787,7 @@ void HMedistat_Think( gentity_t *self ) //start the heal anim if( !self->active ) { - G_setBuildableAnim( self, BANIM_ATTACK1, qfalse ); + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); self->active = qtrue; } } @@ -1800,8 +1800,8 @@ void HMedistat_Think( gentity_t *self ) //nothing left to heal so go back to idling if( !self->enemy && self->active ) { - G_setBuildableAnim( self, BANIM_CONSTRUCT2, qtrue ); - G_setIdleBuildableAnim( self, BANIM_IDLE1 ); + G_SetBuildableAnim( self, BANIM_CONSTRUCT2, qtrue ); + G_SetIdleBuildableAnim( self, BANIM_IDLE1 ); self->active = qfalse; } @@ -2037,7 +2037,7 @@ void HMGTurret_Think( gentity_t *self ) self->s.eFlags &= ~EF_FIRING; //if not powered don't do anything and check again for power next think - if( !( self->powered = findPower( self ) ) ) + if( !( self->powered = G_FindPower( self ) ) ) { self->nextthink = level.time + POWER_REFRESH_TIME; return; @@ -2046,7 +2046,7 @@ void HMGTurret_Think( gentity_t *self ) if( self->spawned ) { //find a dcc for self - self->dcced = findDCC( self ); + self->dcced = G_FindDCC( self ); //if the current target is not valid find a new one if( !HMGTurret_CheckTarget( self, self->enemy, qfalse ) ) @@ -2071,7 +2071,7 @@ void HMGTurret_Think( gentity_t *self ) self->s.eFlags |= EF_FIRING; G_AddEvent( self, EV_FIRE_WEAPON, 0 ); - G_setBuildableAnim( self, BANIM_ATTACK1, qfalse ); + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); self->count = level.time + firespeed; } @@ -2105,7 +2105,7 @@ void HTeslaGen_Think( gentity_t *self ) self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); //if not powered don't do anything and check again for power next think - if( !( self->powered = findPower( self ) ) || !( self->dcced = findDCC( self ) ) ) + if( !( self->powered = G_FindPower( self ) ) || !( self->dcced = G_FindDCC( self ) ) ) { self->s.eFlags &= ~EF_FIRING; self->nextthink = level.time + POWER_REFRESH_TIME; @@ -2144,7 +2144,7 @@ void HTeslaGen_Think( gentity_t *self ) G_AddEvent( self, EV_FIRE_WEAPON, 0 ); //doesn't really need an anim - //G_setBuildableAnim( self, BANIM_ATTACK1, qfalse ); + //G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); self->count = level.time + TESLAGEN_REPEAT; } @@ -2228,8 +2228,8 @@ Called when a human spawn dies void HSpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { //pretty events and cleanup - G_setBuildableAnim( self, BANIM_DESTROY1, qtrue ); - G_setIdleBuildableAnim( self, BANIM_DESTROYED ); + G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); + G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); self->die = nullDieFunction; self->powered = qfalse; //free up power @@ -2282,7 +2282,7 @@ void HSpawn_Think( gentity_t *self ) gentity_t *ent; //make sure we have power - self->powered = findPower( self ); + self->powered = G_FindPower( self ); if( self->spawned ) { @@ -2306,7 +2306,7 @@ void HSpawn_Think( gentity_t *self ) //spawn under attack if( self->health < self->lastHealth && - level.time > level.humanBaseAttackTimer && G_isDCC( ) ) + level.time > level.humanBaseAttackTimer && G_IsDCCBuilt( ) ) { level.humanBaseAttackTimer = level.time + DCC_ATTACK_PERIOD; G_BroadcastEvent( EV_DCC_ATTACK, 0 ); @@ -2405,7 +2405,7 @@ void G_BuildableThink( gentity_t *ent, int msec ) ent->spawned = qtrue; } - ent->s.generic1 = (int)( ( (float)ent->health / (float)bHealth ) * B_HEALTH_SCALE ); + ent->s.generic1 = (int)( ( (float)ent->health / (float)bHealth ) * B_HEALTH_MASK ); if( ent->s.generic1 < 0 ) ent->s.generic1 = 0; @@ -2419,6 +2419,9 @@ void G_BuildableThink( gentity_t *ent, int msec ) if( ent->spawned ) ent->s.generic1 |= B_SPAWNED_TOGGLEBIT; + if( ent->deconstruct ) + ent->s.generic1 |= B_MARKED_TOGGLEBIT; + ent->time1000 += msec; if( ent->time1000 >= 1000 ) @@ -2489,15 +2492,216 @@ qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable ) return qfalse; } +/* +=============== +G_CompareBuildablesForRemoval + +qsort comparison function for a buildable removal list +=============== +*/ +static int G_CompareBuildablesForRemoval( const void *a, const void *b ) +{ + int precedence[ ] = + { + BA_NONE, + + BA_A_BARRICADE, + BA_A_ACIDTUBE, + BA_A_TRAPPER, + BA_A_HIVE, + BA_A_BOOSTER, + BA_A_HOVEL, + BA_A_SPAWN, + BA_A_OVERMIND, + + BA_H_MGTURRET, + BA_H_TESLAGEN, + BA_H_DCC, + BA_H_MEDISTAT, + BA_H_ARMOURY, + BA_H_SPAWN, + BA_H_REPEATER, + BA_H_REACTOR + }; + + gentity_t *buildableA, *buildableB; + int i; + int aPrecedence = 0, bPrecedence = 0; + + buildableA = *(gentity_t **)a; + buildableB = *(gentity_t **)b; + + // If they're the same type then pick the one marked earliest + if( buildableA->s.modelindex == buildableB->s.modelindex ) + return buildableA->deconstructTime - buildableB->deconstructTime; + + for( i = 0; i < sizeof( precedence ) / sizeof( precedence[ 0 ] ); i++ ) + { + if( buildableA->s.modelindex == precedence[ i ] ) + aPrecedence = i; + + if( buildableB->s.modelindex == precedence[ i ] ) + bPrecedence = i; + } + + return aPrecedence - bPrecedence; +} + +static gentity_t *markedBuildables[ MAX_GENTITIES ]; +static int numBuildablesForRemoval = 0; + +/* +=============== +G_FreeMarkedBuildables + +Free up build points for a team by deconstructing marked buildables +=============== +*/ +void G_FreeMarkedBuildables( void ) +{ + int i; + gentity_t *ent; + + if( !g_markDeconstruct.integer ) + return; // Not enabled, can't deconstruct anything + + for( i = 0; i < numBuildablesForRemoval; i++ ) + { + ent = markedBuildables[ i ]; + + G_FreeEntity( ent ); + } +} + +/* +=============== +G_SufficientBPAvailable + +Determine if enough build points can be released for the buildable +and list the buildables that much be destroyed if this is the case +=============== +*/ +static qboolean G_SufficientBPAvailable( buildableTeam_t team, + int buildPoints, + buildable_t buildable ) +{ + int i; + int numBuildables = 0; + int pointsYielded = 0; + gentity_t *ent; + qboolean unique = BG_FindUniqueTestForBuildable( buildable ); + int remainingBP, remainingSpawns; + + if( team == BIT_ALIENS ) + { + remainingBP = level.alienBuildPoints; + remainingSpawns = level.numAlienSpawns; + } + else if( team == BIT_HUMANS ) + { + remainingBP = level.humanBuildPoints; + remainingSpawns = level.numHumanSpawns; + } + else + return qfalse; + + // Simple non-marking case + if( !g_markDeconstruct.integer ) + { + if( remainingBP - buildPoints < 0 ) + return qfalse; + else + return qtrue; + } + + // Set buildPoints to the number extra that are required + buildPoints -= remainingBP; + + numBuildablesForRemoval = 0; + + // Build a list of buildable entities + for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( !ent->inuse ) + continue; + + if( ent->health <= 0 ) + 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 ) + markedBuildables[ numBuildables++ ] = ent; + } + + // We still need build points, but have no candidates for removal + if( buildPoints > 0 && numBuildables == 0 ) + return qfalse; + + // Sort the list + qsort( markedBuildables, numBuildables, sizeof( markedBuildables[ 0 ] ), + G_CompareBuildablesForRemoval ); + + // Do a pass looking for a buildable of the same type that we're + // building and mark it (and only it) for destruction if found + for( i = 0; i < numBuildables; i++ ) + { + ent = markedBuildables[ i ]; + + if( ent->s.modelindex == buildable ) + { + // If we're removing what we're building this will always work + markedBuildables[ 0 ] = ent; + numBuildablesForRemoval = 1; + + return qtrue; + } + } + + // Determine if there are enough markees to yield the required BP + for( ; pointsYielded < buildPoints && numBuildablesForRemoval < numBuildables; + numBuildablesForRemoval++ ) + { + ent = markedBuildables[ numBuildablesForRemoval ]; + pointsYielded += BG_FindBuildPointsForBuildable( ent->s.modelindex ); + } + + // Not enough points yielded + if( pointsYielded < buildPoints ) + { + numBuildablesForRemoval = 0; + return qfalse; + } + else + { + return qtrue; + } +} /* ================ -G_itemFits +G_CanBuild -Checks to see if an item fits in a specific area +Checks to see if a buildable can be built ================ */ -itemBuildError_t G_itemFits( gentity_t *ent, buildable_t buildable, int distance, vec3_t origin ) +itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance, vec3_t origin ) { vec3_t angles; vec3_t entity_origin, normal; @@ -2510,6 +2714,7 @@ itemBuildError_t G_itemFits( gentity_t *ent, buildable_t buildable, int distance qboolean invert; int contents; playerState_t *ps = &ent->client->ps; + int buildPoints; BG_FindBBoxForBuildable( buildable, mins, maxs ); @@ -2536,6 +2741,7 @@ itemBuildError_t G_itemFits( gentity_t *ent, buildable_t buildable, int distance reason = IBE_NORMAL; contents = trap_PointContents( entity_origin, -1 ); + buildPoints = BG_FindBuildPointsForBuildable( buildable ); if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { @@ -2555,7 +2761,7 @@ itemBuildError_t G_itemFits( gentity_t *ent, buildable_t buildable, int distance //check there is creep near by for building on if( BG_FindCreepTestForBuildable( buildable ) ) { - if( !isCreep( entity_origin ) ) + if( !G_IsCreepHere( entity_origin ) ) reason = IBE_NOCREEP; } @@ -2586,7 +2792,7 @@ itemBuildError_t G_itemFits( gentity_t *ent, buildable_t buildable, int distance if( tempent->s.eType != ET_BUILDABLE ) continue; - if( tempent->s.modelindex == buildable ) + if( tempent->s.modelindex == buildable && !tempent->deconstruct ) { switch( buildable ) { @@ -2608,13 +2814,13 @@ itemBuildError_t G_itemFits( gentity_t *ent, buildable_t buildable, int distance } } - if( level.alienBuildPoints - BG_FindBuildPointsForBuildable( buildable ) < 0 ) + if( !G_SufficientBPAvailable( BIT_ALIENS, buildPoints, buildable ) ) reason = IBE_NOASSERT; } else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) { //human criteria - if( !G_isPower( entity_origin ) ) + if( !G_IsPowered( entity_origin ) ) { //tell player to build a repeater to provide power if( buildable != BA_H_REACTOR && buildable != BA_H_REPEATER ) @@ -2622,7 +2828,7 @@ itemBuildError_t G_itemFits( gentity_t *ent, buildable_t buildable, int distance } //this buildable requires a DCC - if( BG_FindDCCTestForBuildable( buildable ) && !G_isDCC( ) ) + if( BG_FindDCCTestForBuildable( buildable ) && !G_IsDCCBuilt( ) ) reason = IBE_NODCC; //check that there is a parent reactor when building a repeater @@ -2658,7 +2864,7 @@ itemBuildError_t G_itemFits( gentity_t *ent, buildable_t buildable, int distance if( reason == IBE_NONE ) reason = IBE_RPTWARN; } - else if( G_isPower( entity_origin ) ) + else if( G_IsPowered( entity_origin ) ) reason = IBE_RPTWARN2; } @@ -2675,7 +2881,7 @@ itemBuildError_t G_itemFits( gentity_t *ent, buildable_t buildable, int distance if( tempent->s.eType != ET_BUILDABLE ) continue; - if( tempent->s.modelindex == BA_H_REACTOR ) + if( tempent->s.modelindex == BA_H_REACTOR && !tempent->deconstruct ) { reason = IBE_REACTOR; break; @@ -2683,7 +2889,7 @@ itemBuildError_t G_itemFits( gentity_t *ent, buildable_t buildable, int distance } } - if( level.humanBuildPoints - BG_FindBuildPointsForBuildable( buildable ) < 0 ) + if( !G_SufficientBPAvailable( BIT_HUMANS, buildPoints, buildable ) ) reason = IBE_NOPOWER; } @@ -2697,16 +2903,19 @@ itemBuildError_t G_itemFits( gentity_t *ent, buildable_t buildable, int distance /* ================ -G_buildItem +G_Build Spawns a buildable ================ */ -gentity_t *G_buildItem( gentity_t *builder, buildable_t buildable, vec3_t origin, vec3_t angles ) +static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t origin, vec3_t angles ) { gentity_t *built; vec3_t normal; + // Free existing buildables + G_FreeMarkedBuildables( ); + //spawn the buildable built = G_Spawn(); @@ -2867,15 +3076,15 @@ gentity_t *G_buildItem( gentity_t *builder, buildable_t buildable, vec3_t origin VectorSet( normal, 0.0f, 0.0f, 1.0f ); built->s.generic1 = (int)( ( (float)built->health / - (float)BG_FindHealthForBuildable( buildable ) ) * B_HEALTH_SCALE ); + (float)BG_FindHealthForBuildable( buildable ) ) * B_HEALTH_MASK ); if( built->s.generic1 < 0 ) built->s.generic1 = 0; - if( ( built->powered = findPower( built ) ) ) + if( ( built->powered = G_FindPower( built ) ) ) built->s.generic1 |= B_POWERED_TOGGLEBIT; - if( ( built->dcced = findDCC( built ) ) ) + if( ( built->dcced = G_FindDCC( built ) ) ) built->s.generic1 |= B_DCCED_TOGGLEBIT; built->s.generic1 &= ~B_SPAWNED_TOGGLEBIT; @@ -2884,10 +3093,10 @@ gentity_t *G_buildItem( gentity_t *builder, buildable_t buildable, vec3_t origin G_AddEvent( built, EV_BUILD_CONSTRUCT, 0 ); - G_setIdleBuildableAnim( built, BG_FindAnimForBuildable( buildable ) ); + G_SetIdleBuildableAnim( built, BG_FindAnimForBuildable( buildable ) ); if( built->builtBy >= 0 ) - G_setBuildableAnim( built, BANIM_CONSTRUCT1, qtrue ); + G_SetBuildableAnim( built, BANIM_CONSTRUCT1, qtrue ); trap_LinkEntity( built ); @@ -2896,20 +3105,20 @@ gentity_t *G_buildItem( gentity_t *builder, buildable_t buildable, vec3_t origin /* ================= -G_ValidateBuild +G_BuildIfValid ================= */ -qboolean G_ValidateBuild( gentity_t *ent, buildable_t buildable ) +qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable ) { float dist; vec3_t origin; dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] ); - switch( G_itemFits( ent, buildable, dist, origin ) ) + switch( G_CanBuild( ent, buildable, dist, origin ) ) { case IBE_NONE: - G_buildItem( ent, buildable, origin, ent->s.apos.trBase ); + G_Build( ent, buildable, origin, ent->s.apos.trBase ); return qtrue; case IBE_NOASSERT: @@ -2975,17 +3184,17 @@ qboolean G_ValidateBuild( gentity_t *ent, buildable_t buildable ) case IBE_SPWNWARN: G_TriggerMenu( ent->client->ps.clientNum, MN_A_SPWNWARN ); - G_buildItem( ent, buildable, origin, ent->s.apos.trBase ); + G_Build( ent, buildable, origin, ent->s.apos.trBase ); return qtrue; case IBE_TNODEWARN: G_TriggerMenu( ent->client->ps.clientNum, MN_H_TNODEWARN ); - G_buildItem( ent, buildable, origin, ent->s.apos.trBase ); + G_Build( ent, buildable, origin, ent->s.apos.trBase ); return qtrue; case IBE_RPTWARN: G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN ); - G_buildItem( ent, buildable, origin, ent->s.apos.trBase ); + G_Build( ent, buildable, origin, ent->s.apos.trBase ); return qtrue; case IBE_RPTWARN2: @@ -3014,7 +3223,7 @@ void FinishSpawningBuildable( gentity_t *ent ) gentity_t *built; buildable_t buildable = ent->s.modelindex; - built = G_buildItem( ent, buildable, ent->s.pos.trBase, ent->s.angles ); + built = G_Build( ent, buildable, ent->s.pos.trBase, ent->s.angles ); G_FreeEntity( ent ); built->takedamage = qtrue; diff --git a/src/game/g_client.c b/src/game/g_client.c index 88d752bc..e2bbf948 100644 --- a/src/game/g_client.c +++ b/src/game/g_client.c @@ -1141,7 +1141,7 @@ void ClientUserinfoChanged( int clientNum ) // print scoreboards, display models, and play custom sounds Com_sprintf( userinfo, sizeof( userinfo ), - "n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\g_redteam\\humans\\g_blueteam\\aliens" + "n\\%s\\t\\%i\\model\\%s\\hmodel\\%s" "\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d", client->pers.netname, team, model, model, c1, c2, client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader ); @@ -1399,7 +1399,7 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles if( ent != spawn ) { //start spawn animation on spawnPoint - G_setBuildableAnim( spawnPoint, BANIM_SPAWN1, qtrue ); + G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, qtrue ); if( spawnPoint->biteam == PTE_ALIENS ) spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c index 3471955a..a52bc37d 100644 --- a/src/game/g_cmds.c +++ b/src/game/g_cmds.c @@ -1743,6 +1743,30 @@ void Cmd_Destroy_f( gentity_t *ent, qboolean deconstruct ) ( ( ent->client->ps.weapon >= WP_ABUILD ) && ( ent->client->ps.weapon <= WP_HBUILD ) ) ) { + // Cancel deconstruction + if( g_markDeconstruct.integer && traceEnt->deconstruct ) + { + traceEnt->deconstruct = qfalse; + return; + } + + // 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 ) return; @@ -1759,26 +1783,36 @@ void Cmd_Destroy_f( gentity_t *ent, qboolean deconstruct ) G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum ); return; } + if( traceEnt->health > 0 ) { - G_TeamCommand( ent->client->pers.teamSelection, - va( "print \"%s ^3DECONSTRUCTED^7 by %s^7\n\"", - BG_FindHumanNameForBuildable( traceEnt->s.modelindex ), - ent->client->pers.netname ) ); + if( g_markDeconstruct.integer ) + { + traceEnt->deconstruct = qtrue; // Mark buildable for deconstruction + traceEnt->deconstructTime = level.time; + } + else + { + G_TeamCommand( ent->client->pers.teamSelection, + va( "print \"%s ^3DECONSTRUCTED^7 by %s^7\n\"", + 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, + BG_FindNameForBuildable( traceEnt->s.modelindex ) ); + + if( !deconstruct && CheatsOk( ent ) ) + G_Damage( traceEnt, ent, ent, forward, tr.endpos, 10000, 0, MOD_SUICIDE ); + else + G_FreeEntity( traceEnt ); + + ent->client->ps.stats[ STAT_MISC ] += + BG_FindBuildDelayForWeapon( ent->s.weapon ) >> 2; + } } - G_LogPrintf( "Decon: %i %i 0: %s^7 deconstructed %s\n", - ent->client->ps.clientNum, - traceEnt->s.modelindex, - ent->client->pers.netname, - BG_FindNameForBuildable( traceEnt->s.modelindex ) ); - - if( !deconstruct && CheatsOk( ent ) ) - G_Damage( traceEnt, ent, ent, forward, tr.endpos, 10000, 0, MOD_SUICIDE ); - else - G_FreeEntity( traceEnt ); - - ent->client->ps.stats[ STAT_MISC ] += - BG_FindBuildDelayForWeapon( ent->s.weapon ) >> 2; } } } @@ -2286,7 +2320,7 @@ void Cmd_Build_f( gentity_t *ent ) dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] ); //these are the errors displayed when the builder first selects something to use - switch( G_itemFits( ent, buildable, dist, origin ) ) + switch( G_CanBuild( ent, buildable, dist, origin ) ) { case IBE_NONE: case IBE_TNODEWARN: diff --git a/src/game/g_local.h b/src/game/g_local.h index 6cf5db75..83b0b11e 100644 --- a/src/game/g_local.h +++ b/src/game/g_local.h @@ -205,6 +205,8 @@ struct gentity_s qboolean spawned; // whether or not this buildable has finished spawning int buildTime; // when this buildable was built int time1000; // timer evaluated every second + qboolean deconstruct; // deconstruct if no BP left + int deconstructTime; // time at which structure marked int overmindAttackTimer; int overmindDyingTimer; int overmindSpawnsTimer; @@ -717,17 +719,16 @@ qboolean AHovel_Blocked( gentity_t *hovel, gentity_t *player, qboolean gentity_t *G_CheckSpawnPoint( int spawnNum, vec3_t origin, vec3_t normal, buildable_t spawn, vec3_t spawnOrigin ); -qboolean G_isPower( vec3_t origin ); -qboolean G_isDCC( void ); -qboolean G_isOvermind( void ); +qboolean G_IsPowered( vec3_t origin ); +qboolean G_IsDCCBuilt( void ); +qboolean G_IsOvermindBuilt( void ); void G_BuildableThink( gentity_t *ent, int msec ); qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable ); -itemBuildError_t G_itemFits( gentity_t *ent, buildable_t buildable, int distance, vec3_t origin ); -gentity_t *G_buildItem( gentity_t *builder, buildable_t buildable, vec3_t origin, vec3_t angles ); -qboolean G_ValidateBuild( 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 ); +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 ); @@ -1128,6 +1129,8 @@ extern vmCvar_t g_disabledEquipment; extern vmCvar_t g_disabledClasses; extern vmCvar_t g_disabledBuildables; +extern vmCvar_t g_markDeconstruct; + extern vmCvar_t g_debugMapRotation; extern vmCvar_t g_currentMapRotation; extern vmCvar_t g_currentMap; diff --git a/src/game/g_main.c b/src/game/g_main.c index 4e59e867..b4eea97b 100644 --- a/src/game/g_main.c +++ b/src/game/g_main.c @@ -224,7 +224,7 @@ static cvarTable_t gameCvarTable[ ] = { &g_chatTeamPrefix, "g_chatTeamPrefix", "0", CVAR_ARCHIVE }, - { &g_markDeconstruct, "g_markDeconstruct", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_markDeconstruct, "g_markDeconstruct", "1", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qfalse }, { &g_debugMapRotation, "g_debugMapRotation", "0", 0, 0, qfalse }, { &g_currentMapRotation, "g_currentMapRotation", "-1", 0, 0, qfalse }, // -1 = NOT_ROTATING @@ -2012,17 +2012,39 @@ CheckCvars */ void CheckCvars( void ) { - static int lastMod = -1; + static int lastPasswordModCount = -1; + static int lastMarkDeconModCount = -1; - if( g_password.modificationCount != lastMod ) + if( g_password.modificationCount != lastPasswordModCount ) { - lastMod = g_password.modificationCount; + lastPasswordModCount = g_password.modificationCount; if( *g_password.string && Q_stricmp( g_password.string, "none" ) ) trap_Cvar_Set( "g_needpass", "1" ); else trap_Cvar_Set( "g_needpass", "0" ); } + + // Unmark any structures for deconstruction when + // the server setting is changed + if( g_markDeconstruct.modificationCount != lastMarkDeconModCount ) + { + int i; + gentity_t *ent; + + lastMarkDeconModCount = g_markDeconstruct.modificationCount; + + for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ ) + { + if( !ent->inuse ) + continue; + + if( ent->s.eType != ET_BUILDABLE ) + continue; + + ent->deconstruct = qfalse; + } + } } /* diff --git a/src/game/g_weapon.c b/src/game/g_weapon.c index ff411a03..e6605743 100644 --- a/src/game/g_weapon.c +++ b/src/game/g_weapon.c @@ -757,14 +757,14 @@ void buildFire( gentity_t *ent, dynMenu_t menu ) return; } - if( G_ValidateBuild( ent, ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) ) + if( G_BuildIfValid( ent, ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) ) { - if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && !G_isOvermind( ) ) + if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && !G_IsOvermindBuilt( ) ) { ent->client->ps.stats[ STAT_MISC ] += BG_FindBuildDelayForWeapon( ent->s.weapon ) * 2; } - else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && !G_isPower( muzzle ) && + else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && !G_IsPowered( muzzle ) && ( ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) != BA_H_REPEATER ) //hack { ent->client->ps.stats[ STAT_MISC ] += |