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 | |
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')
-rw-r--r-- | src/cgame/cg_buildable.c | 186 | ||||
-rw-r--r-- | src/cgame/cg_draw.c | 1 | ||||
-rw-r--r-- | src/cgame/cg_drawtools.c | 35 | ||||
-rw-r--r-- | src/cgame/cg_local.h | 3 | ||||
-rw-r--r-- | src/cgame/cg_servercmds.c | 1 | ||||
-rw-r--r-- | src/cgame/cg_tutorial.c | 33 | ||||
-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 | ||||
-rw-r--r-- | src/qcommon/msg.c | 4 |
15 files changed, 585 insertions, 243 deletions
diff --git a/src/cgame/cg_buildable.c b/src/cgame/cg_buildable.c index 2f1eb239..c1cac345 100644 --- a/src/cgame/cg_buildable.c +++ b/src/cgame/cg_buildable.c @@ -697,8 +697,8 @@ static void CG_BuildableParticleEffects( centity_t *cent ) { entityState_t *es = ¢->currentState; buildableTeam_t team = BG_FindTeamForBuildable( es->modelindex ); - int health = es->generic1 & ~( B_POWERED_TOGGLEBIT | B_DCCED_TOGGLEBIT | B_SPAWNED_TOGGLEBIT ); - float healthFrac = (float)health / B_HEALTH_SCALE; + int health = es->generic1 & B_HEALTH_MASK; + float healthFrac = (float)health / B_HEALTH_MASK; if( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) ) return; @@ -736,85 +736,122 @@ static void CG_BuildableParticleEffects( centity_t *cent ) } } - -#define HEALTH_BAR_WIDTH 50.0f -#define HEALTH_BAR_HEIGHT 5.0f +#define STATUS_FADE_TIME 200 +#define STATUS_MAX_VIEW_DIST 600.0f /* ================== -CG_BuildableHealthBar +CG_BuildableStatusDisplay ================== */ -static void CG_BuildableHealthBar( centity_t *cent ) +static void CG_BuildableStatusDisplay( centity_t *cent ) { - vec3_t origin, origin2, down, right, back, downLength, rightLength; - float rimWidth = HEALTH_BAR_HEIGHT / 15.0f; - float doneWidth, leftWidth, progress; + entityState_t *es = ¢->currentState; + vec3_t origin; + float healthScale; int health; - qhandle_t shader; - entityState_t *es; - vec3_t mins, maxs; + float x, y; + char s[ MAX_STRING_CHARS ] = { 0 }; + int w; + vec4_t color = { 1.0f, 1.0f, 1.0f, 1.0f }; + qboolean powered, marked; + trace_t tr; + float d; - es = ¢->currentState; + CG_Trace( &tr, cent->lerpOrigin, NULL, NULL, cg.refdef.vieworg, + cent->currentState.number, MASK_SOLID ); + d = Distance( cent->lerpOrigin, cg.refdef.vieworg ); - health = es->generic1 & ~( B_POWERED_TOGGLEBIT | B_DCCED_TOGGLEBIT | B_SPAWNED_TOGGLEBIT ); - progress = (float)health / B_HEALTH_SCALE; + if( ( tr.fraction < 1.0f || d > STATUS_MAX_VIEW_DIST ) && + cent->buildableStatus.visible ) + { + cent->buildableStatus.visible = qfalse; + cent->buildableStatus.lastTime = cg.time; + } + else if( ( tr.fraction == 1.0f && d <= STATUS_MAX_VIEW_DIST ) && + !cent->buildableStatus.visible ) + { + cent->buildableStatus.visible = qtrue; + cent->buildableStatus.lastTime = cg.time; + } - if( progress < 0.0f ) - progress = 0.0f; - else if( progress > 1.0f ) - progress = 1.0f; + // Fade up + if( cent->buildableStatus.visible ) + { + if( cent->buildableStatus.lastTime + STATUS_FADE_TIME > cg.time ) + color[ 3 ] = (float)( cg.time - cent->buildableStatus.lastTime ) / STATUS_FADE_TIME; + } - if( progress < 0.33f ) - shader = cgs.media.redBuildShader; - else - shader = cgs.media.greenBuildShader; + // Fade down + if( !cent->buildableStatus.visible ) + { + if( cent->buildableStatus.lastTime + STATUS_FADE_TIME > cg.time ) + color[ 3 ] = 1.0f - (float)( cg.time - cent->buildableStatus.lastTime ) / STATUS_FADE_TIME; + else + return; + } + + health = es->generic1 & B_HEALTH_MASK; + healthScale = (float)health / B_HEALTH_MASK; - doneWidth = ( HEALTH_BAR_WIDTH - 2 * rimWidth ) * progress; - leftWidth = ( HEALTH_BAR_WIDTH - 2 * rimWidth ) - doneWidth; + if( healthScale < 0.0f ) + healthScale = 0.0f; + else if( healthScale > 1.0f ) + healthScale = 1.0f; - VectorCopy( cg.refdef.viewaxis[ 2 ], down ); - VectorInverse( down ); - VectorCopy( cg.refdef.viewaxis[ 1 ], right ); - VectorInverse( right ); - VectorSubtract( cg.refdef.vieworg, cent->lerpOrigin, back ); - VectorNormalize( back ); VectorCopy( cent->lerpOrigin, origin ); - BG_FindBBoxForBuildable( es->modelindex, mins, maxs ); - VectorMA( origin, 48.0f, es->origin2, origin ); - VectorMA( origin, -HEALTH_BAR_WIDTH / 2.0f, right, origin ); - VectorMA( origin, maxs[ 0 ] + 8.0f, back, origin ); - - VectorCopy( origin, origin2 ); - VectorScale( right, rimWidth + doneWidth, rightLength ); - VectorScale( down, HEALTH_BAR_HEIGHT, downLength ); - CG_DrawPlane( origin2, downLength, rightLength, shader ); - - VectorMA( origin, rimWidth + doneWidth, right, origin2 ); - VectorScale( right, leftWidth, rightLength ); - VectorScale( down, rimWidth, downLength ); - CG_DrawPlane( origin2, downLength, rightLength, shader ); - - VectorMA( origin, rimWidth + doneWidth, right, origin2 ); - VectorMA( origin2, HEALTH_BAR_HEIGHT - rimWidth, down, origin2 ); - VectorScale( right, leftWidth, rightLength ); - VectorScale( down, rimWidth, downLength ); - CG_DrawPlane( origin2, downLength, rightLength, shader ); - - VectorMA( origin, HEALTH_BAR_WIDTH - rimWidth, right, origin2 ); - VectorScale( right, rimWidth, rightLength ); - VectorScale( down, HEALTH_BAR_HEIGHT, downLength ); - CG_DrawPlane( origin2, downLength, rightLength, shader ); - - if( !( es->generic1 & B_POWERED_TOGGLEBIT ) && - BG_FindTeamForBuildable( es->modelindex ) == BIT_HUMANS ) + if( CG_WorldToScreen( origin, &x, &y ) ) + { + powered = es->generic1 & B_POWERED_TOGGLEBIT; + marked = es->generic1 & B_MARKED_TOGGLEBIT; + + //FIXME: crappy mock-up for the time being... + Com_sprintf( s, MAX_STRING_CHARS, "%d%%", (int)(healthScale * 100) ); + + if( BG_FindTeamForBuildable( es->modelindex ) == BIT_HUMANS && !powered ) + Q_strcat( s, MAX_STRING_CHARS, " P" ); + + if( marked ) + Q_strcat( s, MAX_STRING_CHARS, " X" ); + + w = CG_Text_Width( s, 0.5f, 0 ); + + CG_Text_Paint( x - w / 2, y, 0.5f, color, s, 0, 0, ITEM_TEXTSTYLE_NORMAL ); + } +} + +/* +================== +CG_DrawBuildableStatus +================== +*/ +void CG_DrawBuildableStatus( void ) +{ + int i; + centity_t *cent; + entityState_t *es; + + switch( cg.predictedPlayerState.weapon ) { - VectorMA( origin, 15.0f, right, origin2 ); - VectorMA( origin2, HEALTH_BAR_HEIGHT + 5.0f, down, origin2 ); - VectorScale( right, HEALTH_BAR_WIDTH / 2.0f - 5.0f, rightLength ); - VectorScale( down, HEALTH_BAR_WIDTH / 2.0f - 5.0f, downLength ); - CG_DrawPlane( origin2, downLength, rightLength, cgs.media.noPowerShader ); + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + case WP_HBUILD2: + for( i = 0; i < cg.snap->numEntities; i++ ) + { + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + es = ¢->currentState; + + if( es->eType == ET_BUILDABLE && + BG_FindTeamForBuildable( es->modelindex ) == + BG_FindTeamForWeapon( cg.predictedPlayerState.weapon ) ) + CG_BuildableStatusDisplay( cent ); + } + break; + + default: + break; } } @@ -993,21 +1030,6 @@ void CG_Buildable( centity_t *cent ) trap_R_AddRefEntityToScene( &turretTop ); } - switch( cg.predictedPlayerState.weapon ) - { - case WP_ABUILD: - case WP_ABUILD2: - case WP_HBUILD: - case WP_HBUILD2: - if( BG_FindTeamForBuildable( es->modelindex ) == - BG_FindTeamForWeapon( cg.predictedPlayerState.weapon ) ) - CG_BuildableHealthBar( cent ); - break; - - default: - break; - } - //weapon effects for turrets if( es->eFlags & EF_FIRING ) { @@ -1036,8 +1058,8 @@ void CG_Buildable( centity_t *cent ) trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, weapon->readySound ); } - health = es->generic1 & ~( B_POWERED_TOGGLEBIT | B_DCCED_TOGGLEBIT | B_SPAWNED_TOGGLEBIT ); - healthScale = (float)health / B_HEALTH_SCALE; + health = es->generic1 & B_HEALTH_MASK; + healthScale = (float)health / B_HEALTH_MASK; if( healthScale < cent->lastBuildableHealthScale && ( es->generic1 & B_SPAWNED_TOGGLEBIT ) ) { diff --git a/src/cgame/cg_draw.c b/src/cgame/cg_draw.c index 4bcec5b3..43f5e0fc 100644 --- a/src/cgame/cg_draw.c +++ b/src/cgame/cg_draw.c @@ -3345,6 +3345,7 @@ static void CG_Draw2D( void ) Menu_Paint( menu, qtrue ); CG_DrawCrosshair( ); + CG_DrawBuildableStatus( ); } else if( cg_drawStatus.integer ) Menu_Paint( defaultMenu, qtrue ); diff --git a/src/cgame/cg_drawtools.c b/src/cgame/cg_drawtools.c index a1b99a5f..3151f66f 100644 --- a/src/cgame/cg_drawtools.c +++ b/src/cgame/cg_drawtools.c @@ -285,8 +285,6 @@ void CG_TileClear( void ) CG_TileClearBox( right, top, w - right, bottom - top + 1, cgs.media.backTileShader ); } - - /* ================ CG_FadeColor @@ -315,3 +313,36 @@ float *CG_FadeColor( int startMsec, int totalMsec ) return color; } + +/* +================ +CG_WorldToScreen +================ +*/ +qboolean CG_WorldToScreen( vec3_t point, float *x, float *y ) +{ + vec3_t trans; + float xc, yc; + float px, py; + float z; + + px = tan( cg.refdef.fov_x * M_PI / 360.0 ); + py = tan( cg.refdef.fov_y * M_PI / 360.0 ); + + VectorSubtract( point, cg.refdef.vieworg, trans ); + + xc = 640.0f / 2.0f; + yc = 480.0f / 2.0f; + + z = DotProduct( trans, cg.refdef.viewaxis[ 0 ] ); + if( z <= 0.001f ) + return qfalse; + + if( x ) + *x = xc - DotProduct( trans, cg.refdef.viewaxis[ 1 ] ) * xc / ( z * px ); + + if( y ) + *y = yc - DotProduct( trans, cg.refdef.viewaxis[ 2 ] ) * yc / ( z * py ); + + return qtrue; +} diff --git a/src/cgame/cg_local.h b/src/cgame/cg_local.h index 2f61e4a4..613dc98a 100644 --- a/src/cgame/cg_local.h +++ b/src/cgame/cg_local.h @@ -1291,11 +1291,10 @@ typedef struct qboolean localServer; // detected on startup by checking sv_running // parsed from serverinfo - int dmflags; - int teamflags; int timelimit; int maxclients; char mapname[ MAX_QPATH ]; + qboolean markDeconstruct; // Whether or not buildables are marked int voteTime; int voteYes; diff --git a/src/cgame/cg_servercmds.c b/src/cgame/cg_servercmds.c index bb0daabb..ed677779 100644 --- a/src/cgame/cg_servercmds.c +++ b/src/cgame/cg_servercmds.c @@ -115,6 +115,7 @@ void CG_ParseServerinfo( void ) info = CG_ConfigString( CS_SERVERINFO ); cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) ); cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); + cgs.markDeconstruct = atoi( Info_ValueForKey( info, "g_markDeconstruct" ) ); mapname = Info_ValueForKey( info, "mapname" ); Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname ); } diff --git a/src/cgame/cg_tutorial.c b/src/cgame/cg_tutorial.c index 350d3156..ddc4cbcb 100644 --- a/src/cgame/cg_tutorial.c +++ b/src/cgame/cg_tutorial.c @@ -157,8 +157,8 @@ static entityState_t *CG_BuildableInRange( playerState_t *ps, float *healthFract if( healthFraction ) { - health = es->generic1 & ~( B_POWERED_TOGGLEBIT | B_DCCED_TOGGLEBIT | B_SPAWNED_TOGGLEBIT ); - *healthFraction = (float)health / B_HEALTH_SCALE; + health = es->generic1 & B_HEALTH_MASK; + *healthFraction = (float)health / B_HEALTH_MASK; } if( es->eType == ET_BUILDABLE && @@ -175,7 +175,8 @@ CG_AlienBuilderText */ static void CG_AlienBuilderText( char *text, playerState_t *ps ) { - buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT; + buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT; + entityState_t *es; if( buildable > BA_NONE ) { @@ -195,11 +196,29 @@ static void CG_AlienBuilderText( char *text, playerState_t *ps ) va( "Press %s to build a structure\n", CG_KeyNameForCommand( "+attack" ) ) ); - if( CG_BuildableInRange( ps, NULL ) ) + if( ( es = CG_BuildableInRange( ps, NULL ) ) ) { - Q_strcat( text, MAX_TUTORIAL_TEXT, - va( "Press %s to destroy this structure\n", - CG_KeyNameForCommand( "deconstruct" ) ) ); + if( cgs.markDeconstruct ) + { + if( es->generic1 & B_MARKED_TOGGLEBIT ) + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to unmark this structure\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to mark this structure\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); + } + } + else + { + Q_strcat( text, MAX_TUTORIAL_TEXT, + va( "Press %s to destroy this structure\n", + CG_KeyNameForCommand( "deconstruct" ) ) ); + } } } 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 ] += diff --git a/src/qcommon/msg.c b/src/qcommon/msg.c index 0fa7385a..46019809 100644 --- a/src/qcommon/msg.c +++ b/src/qcommon/msg.c @@ -827,7 +827,7 @@ netField_t entityStateFields[] = { NETF(modelindex), 8 }, { NETF(otherEntityNum2), GENTITYNUM_BITS }, { NETF(loopSound), 8 }, -{ NETF(generic1), 8 }, +{ NETF(generic1), 16 }, { NETF(origin2[2]), 0 }, { NETF(origin2[0]), 0 }, { NETF(origin2[1]), 0 }, @@ -1143,7 +1143,7 @@ netField_t playerStateFields[] = { PSF(damageYaw), 8 }, { PSF(damagePitch), 8 }, { PSF(damageCount), 8 }, -{ PSF(generic1), 8 }, +{ PSF(generic1), 16 }, { PSF(pm_type), 8 }, { PSF(delta_angles[0]), 16 }, { PSF(delta_angles[2]), 16 }, |