summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTim Angus <tim@ngus.net>2006-12-29 15:07:52 +0000
committerTim Angus <tim@ngus.net>2006-12-29 15:07:52 +0000
commitdf4de6463251019ecf150d835c3693bf7502ccd1 (patch)
tree0883015c9176f5a924efdaf73e71744a0cb4f537 /src
parent210115d7ef5f8be82036458359bdf9f00c2bfb67 (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.c186
-rw-r--r--src/cgame/cg_draw.c1
-rw-r--r--src/cgame/cg_drawtools.c35
-rw-r--r--src/cgame/cg_local.h3
-rw-r--r--src/cgame/cg_servercmds.c1
-rw-r--r--src/cgame/cg_tutorial.c33
-rw-r--r--src/game/bg_public.h11
-rw-r--r--src/game/g_active.c4
-rw-r--r--src/game/g_buildable.c421
-rw-r--r--src/game/g_client.c4
-rw-r--r--src/game/g_cmds.c70
-rw-r--r--src/game/g_local.h19
-rw-r--r--src/game/g_main.c30
-rw-r--r--src/game/g_weapon.c6
-rw-r--r--src/qcommon/msg.c4
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 = &cent->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 = &cent->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 = &cent->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 = &cent->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 },