summaryrefslogtreecommitdiff
path: root/src/game
diff options
context:
space:
mode:
Diffstat (limited to 'src/game')
-rw-r--r--src/game/bg_misc.c23
-rw-r--r--src/game/bg_pmove.c215
-rw-r--r--src/game/bg_public.h33
-rw-r--r--src/game/g_active.c390
-rw-r--r--src/game/g_buildable.c550
-rw-r--r--src/game/g_client.c126
-rw-r--r--src/game/g_cmds.c356
-rw-r--r--src/game/g_combat.c71
-rw-r--r--src/game/g_local.h58
-rw-r--r--src/game/g_main.c73
-rw-r--r--src/game/g_misc.c2
-rw-r--r--src/game/g_missile.c16
-rw-r--r--src/game/g_ptr.c2
-rw-r--r--src/game/g_weapon.c667
-rw-r--r--src/game/tremulous.h175
15 files changed, 1659 insertions, 1098 deletions
diff --git a/src/game/bg_misc.c b/src/game/bg_misc.c
index 8a4e7a8c..04ac117d 100644
--- a/src/game/bg_misc.c
+++ b/src/game/bg_misc.c
@@ -500,7 +500,7 @@ buildableAttributes_t bg_buildableList[ ] =
qfalse, //qboolean invertNormal;
qfalse, //qboolean creepTest;
0, //int creepSize;
- qtrue, //qboolean dccTest;
+ qfalse, //qboolean dccTest;
qtrue, //qboolean transparentTest;
qfalse //qboolean reactorTest;
},
@@ -607,7 +607,7 @@ buildableAttributes_t bg_buildableList[ ] =
BIT_HUMANS, //int team;
( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ), //weapon_t buildWeapon;
BANIM_IDLE1, //int idleAnim;
- REACTOR_ATTACK_REPEAT, //int nextthink;
+ REACTOR_ATTACK_DCC_REPEAT, //int nextthink;
REACTOR_BT, //int buildTime;
qtrue, //qboolean usable;
0, //int turretRange;
@@ -1752,7 +1752,7 @@ classAttributes_t bg_classList[ ] =
1.0f, //float airAcceleration;
6.0f, //float friction;
300.0f, //float stopSpeed;
- 270.0f, //float jumpMagnitude;
+ 310.0f, //float jumpMagnitude;
1.2f, //float knockbackScale;
{ PCL_ALIEN_LEVEL2, PCL_ALIEN_LEVEL1_UPG, PCL_NONE }, //int children[ 3 ];
LEVEL1_COST, //int cost;
@@ -1794,7 +1794,7 @@ classAttributes_t bg_classList[ ] =
1.0f, //float airAcceleration;
6.0f, //float friction;
300.0f, //float stopSpeed;
- 270.0f, //float jumpMagnitude;
+ 310.0f, //float jumpMagnitude;
1.1f, //float knockbackScale;
{ PCL_ALIEN_LEVEL2, PCL_NONE, PCL_NONE }, //int children[ 3 ];
LEVEL1_UPG_COST, //int cost;
@@ -1936,7 +1936,7 @@ classAttributes_t bg_classList[ ] =
"default", //char *skinname;
1.0f, //float shadowScale;
"alien_general_hud", //char *hudname;
- ( 1 << S3 ), //int stages
+ ( 1 << S2 )|( 1 << S3 ), //int stages
{ -32, -32, -21 }, //vec3_t mins;
{ 32, 32, 21 }, //vec3_t maxs;
{ 32, 32, 21 }, //vec3_t crouchmaxs;
@@ -2024,7 +2024,7 @@ classAttributes_t bg_classList[ ] =
{ 15, 15, 16 }, //vec3_t crouchmaxs;
{ -15, -15, -4 }, //vec3_t deadmins;
{ 15, 15, 4 }, //vec3_t deadmaxs;
- 0.0f, //float zOffset
+ -2.0f, //float zOffset
26, 12, //int viewheight, crouchviewheight;
100, //int health;
1.0f, //float fallDamage;
@@ -2082,7 +2082,7 @@ classAttributes_t bg_classList[ ] =
1.0f, //float airAcceleration;
6.0f, //float friction;
100.0f, //float stopSpeed;
- 270.0f, //float jumpMagnitude;
+ 220.0f, //float jumpMagnitude;
1.0f, //float knockbackScale;
{ PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ];
0, //int cost;
@@ -3514,10 +3514,10 @@ weaponAttributes_t bg_weapons[ ] =
qtrue, //int infiniteAmmo;
qfalse, //int usesEnergy;
ABUILDER_BUILD_REPEAT,//int repeatRate1;
- ABUILDER_BUILD_REPEAT,//int repeatRate2;
+ ABUILDER_CLAW_REPEAT, //int repeatRate2;
0, //int repeatRate3;
0, //int reloadTime;
- 0.0f, //float knockbackScale;
+ ABUILDER_CLAW_K_SCALE,//float knockbackScale;
qtrue, //qboolean hasAltMode;
qfalse, //qboolean hasThirdMode;
qfalse, //qboolean canZoom;
@@ -4837,8 +4837,8 @@ char *eventnames[ ] =
"EV_BULLET", // otherEntity is the shooter
"EV_LEV1_GRAB",
- "EV_LEV4_CHARGE_PREPARE",
- "EV_LEV4_CHARGE_START",
+ "EV_LEV4_TRAMPLE_PREPARE",
+ "EV_LEV4_TRAMPLE_START",
"EV_PAIN",
"EV_DEATH1",
@@ -5856,4 +5856,3 @@ void BG_ClientListParse( clientList_t *list, const char *s )
sscanf( s, "%x%x", &list->hi, &list->lo );
}
-
diff --git a/src/game/bg_pmove.c b/src/game/bg_pmove.c
index 80ca7715..e7915b96 100644
--- a/src/game/bg_pmove.c
+++ b/src/game/bg_pmove.c
@@ -260,9 +260,15 @@ static void PM_Friction( void )
if( !( pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) )
{
float stopSpeed = BG_FindStopSpeedForClass( pm->ps->stats[ STAT_PCLASS ] );
+ float friction = BG_FindFrictionForClass( pm->ps->stats[ STAT_PCLASS ] );
+
+ // when landing a dodge, extra friction
+ if( pm->ps->pm_flags & PMF_TIME_LAND )
+ friction *= 1.f + HUMAN_LAND_FRICTION *
+ pm->ps->pm_time / HUMAN_DODGE_TIMEOUT;
control = speed < stopSpeed ? stopSpeed : speed;
- drop += control * BG_FindFrictionForClass( pm->ps->stats[ STAT_PCLASS ] ) * pml.frametime;
+ drop += control * friction * pml.frametime;
}
}
}
@@ -387,11 +393,19 @@ static float PM_CmdScale( usercmd_t *cmd )
else
modifier *= CREEP_MODIFIER;
}
+ if( pm->ps->stats[ STAT_STATE ] & SS_POISONCLOUDED )
+ {
+ if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, pm->ps->stats ) ||
+ BG_InventoryContainsUpgrade( UP_BATTLESUIT, pm->ps->stats ) )
+ modifier *= PCLOUD_ARMOUR_MODIFIER;
+ else
+ modifier *= PCLOUD_MODIFIER;
+ }
}
if( pm->ps->weapon == WP_ALEVEL4 && pm->ps->pm_flags & PMF_CHARGE )
- modifier *= ( 1.0f + ( pm->ps->stats[ STAT_MISC ] / (float)LEVEL4_CHARGE_TIME ) *
- ( LEVEL4_CHARGE_SPEED - 1.0f ) );
+ modifier *= ( 1.0f + ( pm->ps->stats[ STAT_MISC ] /
+ (float)LEVEL4_TRAMPLE_CHARGE_MAX ) * ( LEVEL4_TRAMPLE_SPEED - 1.0f ) );
//slow player if charging up for a pounce
if( ( pm->ps->weapon == WP_ALEVEL3 || pm->ps->weapon == WP_ALEVEL3_UPG ) &&
@@ -510,12 +524,22 @@ static qboolean PM_CheckPounce( void )
pm->ps->weapon != WP_ALEVEL3_UPG )
return qfalse;
+ // we were pouncing, but we've landed
+ if( pm->ps->groundEntityNum != ENTITYNUM_NONE &&
+ ( pm->ps->pm_flags & PMF_CHARGE ) )
+ {
+ pm->ps->pm_flags &= ~PMF_CHARGE;
+ return qfalse;
+ }
+
+ // we're building up for a pounce
if( pm->cmd.buttons & BUTTON_ATTACK2 )
{
pm->ps->pm_flags &= ~PMF_CHARGE;
return qfalse;
}
+ // already a pounce in progress
if( pm->ps->pm_flags & PMF_CHARGE )
return qfalse;
@@ -660,6 +684,10 @@ static qboolean PM_CheckJump( void )
{
vec3_t normal;
+ // don't rejump after a dodge or jump
+ if( pm->ps->pm_flags & PMF_TIME_LAND )
+ return qfalse;
+
if( BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] ) == 0.0f )
return qfalse;
@@ -709,7 +737,7 @@ static qboolean PM_CheckJump( void )
// take some stamina off
if( pm->ps->stats[ STAT_PTEAM ] == PTE_HUMANS )
- pm->ps->stats[ STAT_STAMINA ] -= 500;
+ pm->ps->stats[ STAT_STAMINA ] -= STAMINA_JUMP_TAKE;
pm->ps->groundEntityNum = ENTITYNUM_NONE;
@@ -789,6 +817,108 @@ static qboolean PM_CheckWaterJump( void )
return qtrue;
}
+/*
+==================
+PM_CheckDodge
+
+Starts a human dodge or sprint
+==================
+*/
+static qboolean PM_CheckDodge( void )
+{
+ vec3_t right, forward, velocity = { 0.0f, 0.0f, 0.0f };
+ float jump;
+ int i;
+
+ if( pm->ps->stats[ STAT_PTEAM ] != PTE_HUMANS )
+ return qfalse;
+
+ // Landed a dodge
+ if( ( pm->ps->pm_flags & PMF_CHARGE ) &&
+ pm->ps->groundEntityNum != ENTITYNUM_NONE )
+ {
+ pm->ps->pm_flags = ( pm->ps->pm_flags & ~PMF_CHARGE ) | PMF_TIME_LAND;
+ pm->ps->pm_time = HUMAN_DODGE_TIMEOUT;
+ }
+
+ // Reasons to stop a sprint
+ if( pm->cmd.forwardmove <= 64 ||
+ pm->cmd.upmove < 0 ||
+ pm->ps->pm_type != PM_NORMAL )
+ pm->ps->stats[ STAT_STATE ] &= ~SS_SPEEDBOOST;
+
+ // Reasons why we can't start a dodge or sprint
+ if( !( pm->cmd.buttons & BUTTON_DODGE ) ||
+ pm->ps->pm_type != PM_NORMAL ||
+ ( pm->ps->pm_flags & PMF_CROUCH_HELD ) ||
+ pm->ps->stats[ STAT_STAMINA ] < 0 )
+ return qfalse;
+
+ // Start a sprint instead of forward leaps
+ if( pm->cmd.forwardmove > 0 )
+ {
+ if( pm->cmd.buttons & BUTTON_WALKING )
+ return qfalse;
+ pm->ps->stats[ STAT_STATE ] |= SS_SPEEDBOOST;
+ return qfalse;
+ }
+
+ // Reasons why we can't start a dodge only
+ if( pm->ps->pm_flags & ( PMF_TIME_LAND | PMF_CHARGE ) ||
+ pm->ps->groundEntityNum == ENTITYNUM_NONE )
+ return qfalse;
+
+ // Dodge direction specified with movement keys
+ if( ( !pm->cmd.rightmove && !pm->cmd.forwardmove ) || pm->cmd.upmove )
+ return qfalse;
+ AngleVectors( pm->ps->viewangles, NULL, right, NULL );
+ forward[ 0 ] = -right[ 1 ];
+ forward[ 1 ] = right[ 0 ];
+ forward[ 2 ] = 0.0f;
+
+ // Dodge magnitude is based on the jump magnitude scaled by the modifiers
+ jump = BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] );
+ if( pm->cmd.rightmove && pm->cmd.forwardmove )
+ jump *= ( 0.5f * M_SQRT2 );
+
+ // The dodge sets minimum velocity
+ if( pm->cmd.rightmove )
+ {
+ if( pm->cmd.rightmove < 0 )
+ VectorNegate( right, right );
+ VectorMA( velocity, jump * HUMAN_DODGE_SIDE_MODIFIER, right, velocity );
+ }
+ if( pm->cmd.forwardmove )
+ {
+ if( pm->cmd.forwardmove < 0 )
+ VectorNegate( forward, forward );
+ VectorMA( velocity, jump * HUMAN_DODGE_SIDE_MODIFIER, forward, velocity );
+ }
+ velocity[ 2 ] = jump * HUMAN_DODGE_UP_MODIFIER;
+
+ // Make sure client has minimum velocity
+ for( i = 0; i < 3; i++ )
+ {
+ if( ( velocity[ i ] < 0.0f &&
+ pm->ps->velocity[ i ] > velocity[ i ] ) ||
+ ( velocity[ i ] > 0.0f &&
+ pm->ps->velocity[ i ] < velocity[ i ] ) )
+ pm->ps->velocity[ i ] = velocity[ i ];
+ }
+
+ // Jumped away
+ pml.groundPlane = qfalse;
+ pml.walking = qfalse;
+ pm->ps->groundEntityNum = ENTITYNUM_NONE;
+ pm->ps->pm_flags |= PMF_CHARGE;
+ pm->ps->stats[ STAT_STAMINA ] -= STAMINA_DODGE_TAKE;
+ pm->ps->legsAnim = ( ( pm->ps->legsAnim & ANIM_TOGGLEBIT ) ^
+ ANIM_TOGGLEBIT ) | LEGS_JUMP;
+ PM_AddEvent( EV_JUMP );
+
+ return qtrue;
+}
+
//============================================================================
@@ -1176,8 +1306,7 @@ static void PM_WalkMove( void )
return;
}
-
- if( PM_CheckJump( ) || PM_CheckPounce( ) )
+ if( PM_CheckJump( ) || PM_CheckPounce( ) || PM_CheckDodge( ) )
{
// jumped away
if( pm->waterlevel > 1 )
@@ -1529,10 +1658,6 @@ static void PM_CrashLand( void )
delta = vel + t * acc;
delta = delta*delta * 0.0001;
- // ducking while falling doubles damage
- if( pm->ps->pm_flags & PMF_DUCKED )
- delta *= 2;
-
// never take falling damage if completely underwater
if( pm->waterlevel == 3 )
return;
@@ -1964,7 +2089,7 @@ static void PM_GroundClimbTrace( void )
// hitting solid ground will end a waterjump
if( pm->ps->pm_flags & PMF_TIME_WATERJUMP )
{
- pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND);
+ pm->ps->pm_flags &= ~PMF_TIME_WATERJUMP;
pm->ps->pm_time = 0;
}
@@ -2107,8 +2232,7 @@ static void PM_GroundTrace( void )
VectorMA( pm->ps->origin, 0.25f, movedir, point );
pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask );
- if( trace.fraction < 1.0f && !( trace.surfaceFlags & ( SURF_SKY | SURF_SLICK ) ) &&
- ( trace.entityNum == ENTITYNUM_WORLD ) )
+ if( trace.fraction < 1.0f && !( trace.surfaceFlags & ( SURF_SKY | SURF_SLICK ) ) )
{
if( !VectorCompare( trace.plane.normal, pm->ps->grapplePoint ) )
{
@@ -2174,7 +2298,7 @@ static void PM_GroundTrace( void )
// hitting solid ground will end a waterjump
if( pm->ps->pm_flags & PMF_TIME_WATERJUMP )
{
- pm->ps->pm_flags &= ~( PMF_TIME_WATERJUMP | PMF_TIME_LAND );
+ pm->ps->pm_flags &= ~PMF_TIME_WATERJUMP;
pm->ps->pm_time = 0;
}
@@ -2184,17 +2308,12 @@ static void PM_GroundTrace( void )
if( pm->debugLevel )
Com_Printf( "%i:Land\n", c_pmove );
+ // communicate the fall velocity to the server
+ pm->pmext->fallVelocity = pml.previous_velocity[ 2 ];
+
if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_TAKESFALLDAMAGE ) )
PM_CrashLand( );
-
- // don't do landing time if we were just going down a slope
- if( pml.previous_velocity[ 2 ] < -200 )
- {
- // don't allow another jump for a little while
- pm->ps->pm_flags |= PMF_TIME_LAND;
- pm->ps->pm_time = 250;
}
- }
pm->ps->groundEntityNum = trace.entityNum;
@@ -2282,7 +2401,7 @@ static void PM_CheckDuck (void)
if( pm->ps->pm_type == PM_DEAD )
{
pm->maxs[ 2 ] = -8;
- pm->ps->viewheight = DEAD_VIEWHEIGHT;
+ pm->ps->viewheight = PCmins[ 2 ] + DEAD_VIEWHEIGHT;
return;
}
@@ -2678,6 +2797,9 @@ static void PM_Weapon( void )
if( pm->ps->stats[ STAT_STATE ] & SS_HOVELING )
return;
+ if( pm->ps->stats[ STAT_STATE ] & SS_CHARGING )
+ return;
+
// check for dead player
if( pm->ps->stats[ STAT_HEALTH ] <= 0 )
{
@@ -2685,9 +2807,24 @@ static void PM_Weapon( void )
return;
}
+ // no bite during pounce
+ if( ( pm->ps->weapon == WP_ALEVEL3 || pm->ps->weapon == WP_ALEVEL3_UPG )
+ && ( pm->cmd.buttons & BUTTON_ATTACK )
+ && ( pm->ps->pm_flags & PMF_CHARGE ) )
+ {
+ return;
+ }
+
// make weapon function
if( pm->ps->weaponTime > 0 )
pm->ps->weaponTime -= pml.msec;
+ if( pm->ps->weaponTime < 0 )
+ pm->ps->weaponTime = 0;
+
+ if( pm->ps->stats[ STAT_MISC2 ] > 0 )
+ pm->ps->stats[ STAT_MISC2 ] -= pml.msec;
+ if( pm->ps->stats[ STAT_MISC2 ] < 0 )
+ pm->ps->stats[ STAT_MISC2 ] = 0;
// check for weapon change
// can't change if weapon is firing, but can change
@@ -2733,6 +2870,10 @@ static void PM_Weapon( void )
if( pm->ps->weaponTime > 0 )
return;
+ // luci uses STAT_MISC2 as an alternate weaponTime
+ if( pm->ps->weapon == WP_LUCIFER_CANNON && pm->ps->stats[ STAT_MISC2 ] > 0 )
+ return;
+
// change weapon if time
if( pm->ps->weaponstate == WEAPON_DROPPING )
{
@@ -2812,15 +2953,7 @@ static void PM_Weapon( void )
{
case WP_ALEVEL0:
//venom is only autohit
- attack1 = attack2 = attack3 = qfalse;
-
- if( !pm->autoWeaponHit[ pm->ps->weapon ] )
- {
- pm->ps->weaponTime = 0;
- pm->ps->weaponstate = WEAPON_READY;
- return;
- }
- break;
+ return;
case WP_ALEVEL3:
case WP_ALEVEL3_UPG:
@@ -2829,12 +2962,9 @@ static void PM_Weapon( void )
attack2 = pm->cmd.buttons & BUTTON_ATTACK2;
attack3 = pm->cmd.buttons & BUTTON_USE_HOLDABLE;
- if( !pm->autoWeaponHit[ pm->ps->weapon ] && !attack1 && !attack2 && !attack3 )
- {
- pm->ps->weaponTime = 0;
- pm->ps->weaponstate = WEAPON_READY;
+ // pounce is autohit
+ if( !attack1 && !attack2 && !attack3 )
return;
- }
break;
case WP_LUCIFER_CANNON:
@@ -2842,7 +2972,9 @@ static void PM_Weapon( void )
attack2 = pm->cmd.buttons & BUTTON_ATTACK2;
attack3 = qfalse;
- if( attack1 )
+ if( attack1 || pm->ps->stats[ STAT_MISC ] > 0 )
+ attack2 = qfalse;
+ if( ( attack1 || pm->ps->stats[ STAT_MISC ] == 0 ) && !attack2 && !attack3 )
{
attack2 = qfalse;
@@ -3310,7 +3442,9 @@ void PmoveSingle( pmove_t *pmove )
pmove->cmd.buttons = BUTTON_TALK;
pmove->cmd.forwardmove = 0;
pmove->cmd.rightmove = 0;
- pmove->cmd.upmove = 0;
+
+ if( pmove->cmd.upmove > 0 )
+ pmove->cmd.upmove = 0;
}
// clear all pmove local vars
@@ -3485,6 +3619,9 @@ void Pmove( pmove_t *pmove )
msec = 66;
}
+ // force crouch
+ if( pmove->ps->pm_flags & PMF_FORCE_CROUCH )
+ pmove->cmd.upmove = -127;
pmove->cmd.serverTime = pmove->ps->commandTime + msec;
PmoveSingle( pmove );
diff --git a/src/game/bg_public.h b/src/game/bg_public.h
index 9be0d73b..8cbeb64d 100644
--- a/src/game/bg_public.h
+++ b/src/game/bg_public.h
@@ -37,7 +37,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define MINS_Z -24
#define DEFAULT_VIEWHEIGHT 26
#define CROUCH_VIEWHEIGHT 12
-#define DEAD_VIEWHEIGHT -14 // watch for mins[ 2 ] less than this causing
+#define DEAD_VIEWHEIGHT 4 // height from ground
//
// config strings are a general means of communicating variable length strings
@@ -135,6 +135,7 @@ typedef enum
#define PMF_BACKWARDS_RUN 16 // coast down to backwards run
#define PMF_TIME_LAND 32 // pm_time is time before rejump
#define PMF_TIME_KNOCKBACK 64 // pm_time is an air-accelerate only time
+#define PMF_FORCE_CROUCH 128 // force the player to crouch
#define PMF_TIME_WATERJUMP 256 // pm_time is waterjump
#define PMF_RESPAWNED 512 // clear after attack and jump buttons come up
#define PMF_USE_ITEM_HELD 1024
@@ -151,10 +152,11 @@ typedef enum
typedef struct
{
int pouncePayload;
+ float fallVelocity;
} pmoveExt_t;
#define MAXTOUCH 32
-typedef struct
+typedef struct pmove_s
{
// state (in / out)
playerState_t *ps;
@@ -214,7 +216,8 @@ typedef enum
STAT_PTEAM, // player team
STAT_STAMINA, // stamina (human only)
STAT_STATE, // client states e.g. wall climbing
- STAT_MISC, // for uh...misc stuff
+ STAT_MISC, // for uh...misc stuff (pounce, trample, lcannon)
+ STAT_MISC2, // more uh...misc stuff (booster, lcannon repeat)
STAT_BUILDABLE, // which ghost model to display for building
STAT_FALLDIST, // the distance the player fell
STAT_VIEWLOCK // direction to lock the view in
@@ -521,8 +524,8 @@ typedef enum
EV_BULLET, // otherEntity is the shooter
EV_LEV1_GRAB,
- EV_LEV4_CHARGE_PREPARE,
- EV_LEV4_CHARGE_START,
+ EV_LEV4_TRAMPLE_PREPARE,
+ EV_LEV4_TRAMPLE_START,
EV_PAIN,
EV_DEATH1,
@@ -556,7 +559,10 @@ typedef enum
EV_DCC_ATTACK, // dcc under attack
- EV_RPTUSE_SOUND // trigger a sound
+ EV_MGTURRET_SPINUP, // turret spinup sound should play
+
+ EV_RPTUSE_SOUND, // trigger a sound
+ EV_LEV2_ZAP
} entity_event_t;
typedef enum
@@ -564,8 +570,6 @@ typedef enum
MN_TEAM,
MN_A_TEAMFULL,
MN_H_TEAMFULL,
- MN_A_TEAMCHANGEBUILDTIMER,
- MN_H_TEAMCHANGEBUILDTIMER,
//alien stuff
MN_A_CLASS,
@@ -576,7 +580,6 @@ typedef enum
MN_A_NOEROOM,
MN_A_TOOCLOSE,
MN_A_NOOVMND_EVOLVE,
- MN_A_EVOLVEBUILDTIMER,
//alien build
MN_A_SPWNWARN,
@@ -588,6 +591,8 @@ typedef enum
MN_A_NORMAL,
MN_A_HOVEL,
MN_A_HOVEL_EXIT,
+ MN_A_TEAMCHANGEBUILDTIMER,
+ MN_A_EVOLVEBUILDTIMER,
//human stuff
MN_H_SPAWN,
@@ -600,7 +605,6 @@ typedef enum
MN_H_NOARMOURYHERE,
MN_H_NOROOMBSUITON,
MN_H_NOROOMBSUITOFF,
- MN_H_ARMOURYBUILDTIMER,
//human build
MN_H_REPEATER,
@@ -612,7 +616,9 @@ typedef enum
MN_H_NORMAL,
MN_H_TNODEWARN,
MN_H_RPTWARN,
- MN_H_RPTWARN2
+ MN_H_RPTWARN2,
+ MN_H_TEAMCHANGEBUILDTIMER,
+ MN_H_ARMOURYBUILDTIMER
} dynMenu_t;
// animations
@@ -853,7 +859,8 @@ typedef enum
MOD_LEVEL2_CLAW,
MOD_LEVEL2_ZAP,
MOD_LEVEL4_CLAW,
- MOD_LEVEL4_CHARGE,
+ MOD_LEVEL4_TRAMPLE,
+ MOD_LEVEL4_CRUSH,
MOD_SLOWBLOB,
MOD_POISON,
@@ -1010,6 +1017,7 @@ typedef struct
qboolean dccTest;
qboolean transparentTest;
qboolean reactorTest;
+ qboolean replaceable;
} buildableAttributes_t;
typedef struct
@@ -1251,7 +1259,6 @@ typedef enum
ET_ANIMMAPOBJ,
ET_MODELDOOR,
ET_LIGHTFLARE,
- ET_LEV2_ZAP_CHAIN,
ET_EVENTS // any of the EV_* events can be added freestanding
// by setting eType to ET_EVENTS + eventNum
diff --git a/src/game/g_active.c b/src/game/g_active.c
index ef6330d4..a55e9e3b 100644
--- a/src/game/g_active.c
+++ b/src/game/g_active.c
@@ -274,11 +274,13 @@ void ClientImpacts( gentity_t *ent, pmove_t *pm )
if( other->client && other->client->unlaggedCalc.used )
other->client->unlaggedCalc.used = qfalse;
- //charge attack
- if( ent->client->ps.weapon == WP_ALEVEL4 &&
- ent->client->ps.stats[ STAT_MISC ] > 0 &&
- ent->client->charging )
- ChargeAttack( ent, other );
+ // tyrant impact attacks
+ if( ent->client->ps.weapon == WP_ALEVEL4 )
+ {
+ G_ChargeAttack( ent, other );
+ G_CrushAttack( ent, other,
+ ( pm->cmd.serverTime - pm->ps->commandTime ) * 0.001f );
+ }
if( ent->client && other->client )
G_ClientShove( ent, other );
@@ -375,12 +377,18 @@ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd )
{
pmove_t pm;
gclient_t *client;
+ qboolean attack1, attack3;
client = ent->client;
client->oldbuttons = client->buttons;
client->buttons = ucmd->buttons;
+ attack1 = ( ( client->buttons & BUTTON_ATTACK ) &&
+ !( client->oldbuttons & BUTTON_ATTACK ) );
+ attack3 = ( ( client->buttons & BUTTON_USE_HOLDABLE ) &&
+ !( client->oldbuttons & BUTTON_USE_HOLDABLE ) );
+
if( client->sess.spectatorState != SPECTATOR_FOLLOW )
{
if( client->sess.spectatorState == SPECTATOR_LOCKED )
@@ -388,6 +396,15 @@ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd )
else
client->ps.pm_type = PM_SPECTATOR;
+ // in case the client entered the queue while following a teammate
+ if( ( client->pers.teamSelection == PTE_ALIENS &&
+ G_SearchSpawnQueue( &level.alienSpawnQueue, ent-g_entities ) ) ||
+ ( client->pers.teamSelection == PTE_HUMANS &&
+ G_SearchSpawnQueue( &level.alienSpawnQueue, ent-g_entities ) ) )
+ {
+ client->ps.pm_flags |= PMF_QUEUED;
+ }
+
client->ps.speed = BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] );
client->ps.stats[ STAT_STAMINA ] = 0;
@@ -413,28 +430,25 @@ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd )
G_TouchTriggers( ent );
trap_UnlinkEntity( ent );
- if( ( client->buttons & BUTTON_ATTACK ) && !( client->oldbuttons & BUTTON_ATTACK ) )
+ if( ( attack1 || attack3 ) && ( client->ps.pm_flags & PMF_QUEUED ) )
{
- //if waiting in a queue remove from the queue
- if( client->ps.pm_flags & PMF_QUEUED )
- {
- if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
- G_RemoveFromSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum );
- else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
- G_RemoveFromSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum );
+ if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ G_RemoveFromSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum );
+ else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ G_RemoveFromSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum );
- client->pers.classSelection = PCL_NONE;
- client->ps.stats[ STAT_PCLASS ] = PCL_NONE;
- }
- else if( client->pers.classSelection == PCL_NONE )
- {
- if( client->pers.teamSelection == PTE_NONE )
- G_TriggerMenu( client->ps.clientNum, MN_TEAM );
- else if( client->pers.teamSelection == PTE_ALIENS )
- G_TriggerMenu( client->ps.clientNum, MN_A_CLASS );
- else if( client->pers.teamSelection == PTE_HUMANS )
- G_TriggerMenu( client->ps.clientNum, MN_H_SPAWN );
- }
+ client->pers.classSelection = PCL_NONE;
+ client->ps.stats[ STAT_PCLASS ] = PCL_NONE;
+ }
+
+ if( attack1 && client->pers.classSelection == PCL_NONE )
+ {
+ if( client->pers.teamSelection == PTE_NONE )
+ G_TriggerMenu( client->ps.clientNum, MN_TEAM );
+ else if( client->pers.teamSelection == PTE_ALIENS )
+ G_TriggerMenu( client->ps.clientNum, MN_A_CLASS );
+ else if( client->pers.teamSelection == PTE_HUMANS )
+ G_TriggerMenu( client->ps.clientNum, MN_H_SPAWN );
}
//set the queue position for the client side
@@ -452,9 +466,22 @@ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd )
}
}
}
+ else if( attack1 && ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
+ {
+ G_StopFollowing( ent );
+ client->pers.classSelection = PCL_NONE;
+ if( client->pers.teamSelection == PTE_NONE )
+ G_TriggerMenu( ent-g_entities, MN_TEAM );
+ else if( client->pers.teamSelection == PTE_ALIENS )
+ G_TriggerMenu( ent-g_entities, MN_A_CLASS );
+ else if( client->pers.teamSelection == PTE_HUMANS )
+ G_TriggerMenu( ent-g_entities, MN_H_SPAWN );
+ }
- if( ( client->buttons & BUTTON_USE_HOLDABLE ) && !( client->oldbuttons & BUTTON_USE_HOLDABLE ) )
+ if( attack3 )
+ {
G_ToggleFollow( ent );
+ }
}
@@ -545,41 +572,18 @@ void ClientTimerActions( gentity_t *ent, int msec )
{
client->time100 -= 100;
- //if not trying to run then not trying to sprint
- if( walking || stopped )
- client->ps.stats[ STAT_STATE ] &= ~SS_SPEEDBOOST;
-
- if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) )
- client->ps.stats[ STAT_STATE ] &= ~SS_SPEEDBOOST;
-
- if( ( client->ps.stats[ STAT_STATE ] & SS_SPEEDBOOST ) && !crouched )
- {
- //subtract stamina
- if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) )
- client->ps.stats[ STAT_STAMINA ] -= STAMINA_LARMOUR_TAKE;
- else
- client->ps.stats[ STAT_STAMINA ] -= STAMINA_SPRINT_TAKE;
-
- if( client->ps.stats[ STAT_STAMINA ] < -MAX_STAMINA )
- client->ps.stats[ STAT_STAMINA ] = -MAX_STAMINA;
- }
-
- if( walking || crouched )
- {
- //restore stamina
+ // Restore or subtract stamina
+ if( client->ps.stats[ STAT_STATE ] & SS_SPEEDBOOST )
+ client->ps.stats[ STAT_STAMINA ] -= STAMINA_SPRINT_TAKE;
+ else if( walking || crouched )
client->ps.stats[ STAT_STAMINA ] += STAMINA_WALK_RESTORE;
-
- if( client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA )
- client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA;
- }
else if( stopped )
- {
- //restore stamina faster
client->ps.stats[ STAT_STAMINA ] += STAMINA_STOP_RESTORE;
- if( client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA )
- client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA;
- }
+ if( client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA )
+ client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA;
+ else if( client->ps.stats[ STAT_STAMINA ] < -MAX_STAMINA )
+ client->ps.stats[ STAT_STAMINA ] = -MAX_STAMINA;
//client is charging up for a pounce
if( client->ps.weapon == WP_ALEVEL3 || client->ps.weapon == WP_ALEVEL3_UPG )
@@ -594,12 +598,6 @@ void ClientTimerActions( gentity_t *ent, int msec )
if( client->ps.stats[ STAT_MISC ] < pounceSpeed && ucmd->buttons & BUTTON_ATTACK2 )
client->ps.stats[ STAT_MISC ] += ( 100.0f / (float)LEVEL3_POUNCE_CHARGE_TIME ) * pounceSpeed;
- if( !( ucmd->buttons & BUTTON_ATTACK2 ) )
- {
- if( client->pmext.pouncePayload > 0 )
- client->allowedToPounce = qtrue;
- }
-
if( client->ps.stats[ STAT_MISC ] > pounceSpeed )
client->ps.stats[ STAT_MISC ] = pounceSpeed;
}
@@ -607,8 +605,8 @@ void ClientTimerActions( gentity_t *ent, int msec )
//client is charging up for a... charge
if( client->ps.weapon == WP_ALEVEL4 )
{
- if( client->ps.stats[ STAT_MISC ] < LEVEL4_CHARGE_TIME && ucmd->buttons & BUTTON_ATTACK2 &&
- !client->charging )
+ if( client->ps.stats[ STAT_MISC ] < LEVEL4_TRAMPLE_CHARGE_TRIGGER &&
+ ( ucmd->buttons & BUTTON_ATTACK2 ) && !client->charging )
{
client->charging = qfalse; //should already be off, just making sure
client->ps.stats[ STAT_STATE ] &= ~SS_CHARGING;
@@ -617,26 +615,24 @@ void ClientTimerActions( gentity_t *ent, int msec )
{
//trigger charge sound...is quite annoying
//if( client->ps.stats[ STAT_MISC ] <= 0 )
- // G_AddEvent( ent, EV_LEV4_CHARGE_PREPARE, 0 );
+ // G_AddEvent( ent, EV_LEV4_TRAMPLE_PREPARE, 0 );
- client->ps.stats[ STAT_MISC ] += (int)( 100 * (float)LEVEL4_CHARGE_CHARGE_RATIO );
+ client->ps.stats[ STAT_MISC ] += 100 * LEVEL4_TRAMPLE_CHARGE_RATE;
- if( client->ps.stats[ STAT_MISC ] > LEVEL4_CHARGE_TIME )
- client->ps.stats[ STAT_MISC ] = LEVEL4_CHARGE_TIME;
}
else
client->ps.stats[ STAT_MISC ] = 0;
}
if( !( ucmd->buttons & BUTTON_ATTACK2 ) || client->charging ||
- client->ps.stats[ STAT_MISC ] == LEVEL4_CHARGE_TIME )
+ client->ps.stats[ STAT_MISC ] >= LEVEL4_TRAMPLE_CHARGE_TRIGGER )
{
- if( client->ps.stats[ STAT_MISC ] > LEVEL4_MIN_CHARGE_TIME )
+ if( client->ps.stats[ STAT_MISC ] > LEVEL4_TRAMPLE_CHARGE_MIN )
{
- client->ps.stats[ STAT_MISC ] -= 100;
+ client->ps.stats[ STAT_MISC ] -= 100 * LEVEL4_TRAMPLE_DISCHARGE_RATE;
if( client->charging == qfalse )
- G_AddEvent( ent, EV_LEV4_CHARGE_START, 0 );
+ G_AddEvent( ent, EV_LEV4_TRAMPLE_START, 0 );
client->charging = qtrue;
client->ps.stats[ STAT_STATE ] |= SS_CHARGING;
@@ -648,6 +644,9 @@ void ClientTimerActions( gentity_t *ent, int msec )
//can't charge backwards
if( ucmd->forwardmove < 0 )
client->ps.stats[ STAT_MISC ] = 0;
+
+ if( client->ps.stats[ STAT_MISC ] > LEVEL4_TRAMPLE_CHARGE_MAX )
+ client->ps.stats[ STAT_MISC ] = LEVEL4_TRAMPLE_CHARGE_MAX;
}
else
client->ps.stats[ STAT_MISC ] = 0;
@@ -663,9 +662,14 @@ void ClientTimerActions( gentity_t *ent, int msec )
}
//client is charging up an lcannon
- if( client->ps.weapon == WP_LUCIFER_CANNON )
+ if( client->ps.weapon == WP_LUCIFER_CANNON &&
+ ( ucmd->buttons & BUTTON_ATTACK ) &&
+ client->ps.stats[ STAT_MISC2 ] <= 0 )
{
- if( client->ps.stats[ STAT_MISC ] < LCANNON_TOTAL_CHARGE && ucmd->buttons & BUTTON_ATTACK )
+ if( client->ps.stats[ STAT_MISC ] <= 0 )
+ client->lcannonStartTime = level.time;
+
+ if( client->ps.stats[ STAT_MISC ] < LCANNON_TOTAL_CHARGE )
client->ps.stats[ STAT_MISC ] += ( 100.0f / LCANNON_CHARGE_TIME ) * LCANNON_TOTAL_CHARGE;
if( client->ps.stats[ STAT_MISC ] > LCANNON_TOTAL_CHARGE )
@@ -675,6 +679,18 @@ void ClientTimerActions( gentity_t *ent, int msec )
client->ps.stats[ STAT_MISC ] = client->ps.ammo * LCANNON_TOTAL_CHARGE / 10;
}
+ if( client->ps.weapon == WP_ABUILD || client->ps.weapon == WP_ABUILD2 ||
+ BG_InventoryContainsWeapon( WP_HBUILD, client->ps.stats ) ||
+ BG_InventoryContainsWeapon( WP_HBUILD2, client->ps.stats ) )
+ {
+ //update build timer
+ if( client->ps.stats[ STAT_MISC ] > 0 )
+ client->ps.stats[ STAT_MISC ] -= 100;
+
+ if( client->ps.stats[ STAT_MISC ] < 0 )
+ client->ps.stats[ STAT_MISC ] = 0;
+ }
+
switch( client->ps.weapon )
{
case WP_ABUILD:
@@ -708,12 +724,6 @@ void ClientTimerActions( gentity_t *ent, int msec )
client->ps.misc[ i ] = 0;
}
- //update build timer
- if( client->ps.stats[ STAT_MISC ] > 0 )
- client->ps.stats[ STAT_MISC ] -= 100;
-
- if( client->ps.stats[ STAT_MISC ] < 0 )
- client->ps.stats[ STAT_MISC ] = 0;
break;
default:
@@ -762,31 +772,24 @@ void ClientTimerActions( gentity_t *ent, int msec )
{
client->time1000 -= 1000;
- //client is poison clouded
- if( client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED )
- G_Damage( ent, client->lastPoisonCloudedClient, client->lastPoisonCloudedClient, NULL, NULL,
- LEVEL1_PCLOUD_DMG, 0, MOD_LEVEL1_PCLOUD );
//client is poisoned
if( client->ps.stats[ STAT_STATE ] & SS_POISONED )
{
- int i;
- int seconds = ( ( level.time - client->lastPoisonTime ) / 1000 ) + 1;
- int damage = ALIEN_POISON_DMG, damage2 = 0;
+ int damage = ALIEN_POISON_DMG;
- for( i = 0; i < seconds; i++ )
- {
- if( i == seconds - 1 )
- damage2 = damage;
+ if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, client->ps.stats ) )
+ damage -= BSUIT_POISON_PROTECTION;
- damage *= ALIEN_POISON_DIVIDER;
- }
+ if( BG_InventoryContainsUpgrade( UP_HELMET, client->ps.stats ) )
+ damage -= HELMET_POISON_PROTECTION;
- damage = damage2 - damage;
+ if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) )
+ damage -= LIGHTARMOUR_POISON_PROTECTION;
- G_Damage( ent, client->lastPoisonClient, client->lastPoisonClient, NULL, NULL,
- damage, 0, MOD_POISON );
- }
+ G_Damage( ent, client->lastPoisonClient, client->lastPoisonClient, NULL,
+ 0, damage, 0, MOD_POISON );
+ }
//replenish alien health
if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS &&
@@ -798,6 +801,7 @@ void ClientTimerActions( gentity_t *ent, int msec )
int i, num;
gentity_t *boostEntity;
float modifier = 1.0f;
+ qboolean modified = qfalse;
VectorAdd( client->ps.origin, range, maxs );
VectorSubtract( client->ps.origin, range, mins );
@@ -807,24 +811,49 @@ void ClientTimerActions( gentity_t *ent, int msec )
{
boostEntity = &g_entities[ entityList[ i ] ];
- if( boostEntity->client && boostEntity->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS &&
- boostEntity->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL4 )
- {
- modifier = LEVEL4_REGEN_MOD;
- break;
- }
- else if( boostEntity->s.eType == ET_BUILDABLE &&
+ if( boostEntity->s.eType == ET_BUILDABLE &&
boostEntity->s.modelindex == BA_A_BOOSTER &&
boostEntity->spawned && boostEntity->health > 0)
{
modifier = BOOSTER_REGEN_MOD;
+ modified = qtrue;
+ break;
+ }
+ else if( boostEntity->client && boostEntity->health > 0 &&
+ boostEntity->client->pers.teamSelection == PTE_ALIENS &&
+ ( boostEntity->client->pers.classSelection == PCL_ALIEN_LEVEL1 ||
+ boostEntity->client->pers.classSelection == PCL_ALIEN_LEVEL1_UPG ) )
+ {
+ if( boostEntity->client->pers.classSelection == PCL_ALIEN_LEVEL1_UPG )
+ modifier = LEVEL1_UPG_REGEN_MOD;
+ else
+ modifier = LEVEL1_REGEN_MOD;
+
+ modified = qtrue;
break;
}
}
- if( ent->health > 0 && ent->health < client->ps.stats[ STAT_MAX_HEALTH ] &&
+ if( ent->health > 0 &&
+ ent->health < client->ps.stats[ STAT_MAX_HEALTH ] &&
( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time )
- ent->health += BG_FindRegenRateForClass( client->ps.stats[ STAT_PCLASS ] ) * modifier;
+ {
+ if( !modified && !G_FindCreep( ent ) )
+ {
+ if( ( ent->lastRegenTime + ALIEN_REGEN_NOCREEP_TIME ) < level.time )
+ {
+ ent->health +=
+ BG_FindRegenRateForClass( client->ps.stats[ STAT_PCLASS ] );
+ ent->lastRegenTime = level.time;
+ }
+ }
+ else
+ {
+ ent->health += modifier *
+ BG_FindRegenRateForClass( client->ps.stats[ STAT_PCLASS ] );
+ ent->lastRegenTime = level.time;
+ }
+ }
if( ent->health > client->ps.stats[ STAT_MAX_HEALTH ] )
ent->health = client->ps.stats[ STAT_MAX_HEALTH ];
@@ -857,9 +886,9 @@ void ClientTimerActions( gentity_t *ent, int msec )
if( client->ps.ammo < maxAmmo )
client->ps.ammo++;
- }
}
}
+}
/*
====================
@@ -1281,6 +1310,37 @@ static void G_UnlaggedDetectCollisions( gentity_t *ent )
/*
==============
+G_CheckZap
+==============
+*/
+static void G_CheckZap( gentity_t *ent )
+{
+ int i;
+
+ if( !ent->zapping )
+ {
+ // clear out established targets
+ for( i = 0; i < LEVEL2_AREAZAP_MAX_TARGETS; i++ )
+ {
+ ent->zapTargets[ i ] = -1;
+ }
+ ent->zapDmg = 0.0f;
+ }
+ ent->wasZapping = ent->zapping;
+ ent->zapping = qfalse;
+
+ if( ent->client->ps.weapon == WP_ALEVEL2_UPG &&
+ ( ent->client->pers.cmd.buttons & BUTTON_ATTACK2 ) )
+ {
+ ent->zapping = qtrue;
+ }
+
+ if( ent->wasZapping && !ent->zapping )
+ ent->client->ps.weaponTime = LEVEL2_AREAZAP_REPEAT;
+}
+
+/*
+==============
ClientThink
This will be called once for each client frame, which will
@@ -1320,6 +1380,19 @@ void ClientThink_real( gentity_t *ent )
// G_Printf("serverTime >>>>>\n" );
}
+ // ucmd->serverTime is a client predicted value, but it works for making a
+ // replacement for client->ps.ping when in SPECTATOR_FOLLOW
+ client->pers.ping = level.time - ucmd->serverTime;
+
+ // account for the one frame of delay on client side
+ client->pers.ping -= level.time - level.previousTime;
+
+ // account for the time that's elapsed since the last ClientEndFrame()
+ client->pers.ping += trap_Milliseconds() - level.frameMsec;
+
+ if( client->pers.ping < 0 )
+ client->pers.ping = 0;
+
msec = ucmd->serverTime - client->ps.commandTime;
// following others may result in bad times, but we still want
// to check for follow toggles
@@ -1399,12 +1472,26 @@ void ClientThink_real( gentity_t *ent )
client->ps.stats[ STAT_STATE ] &= ~SS_SLOWLOCKED;
if( client->ps.stats[ STAT_STATE ] & SS_BOOSTED &&
- client->lastBoostedTime + BOOST_TIME < level.time )
+ client->ps.stats[ STAT_MISC2 ] <= 0 )
client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTED;
- if( client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED &&
- client->lastPoisonCloudedTime + LEVEL1_PCLOUD_TIME < level.time )
- client->ps.stats[ STAT_STATE ] &= ~SS_POISONCLOUDED;
+ if( client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED )
+ {
+ int timeLeft = LEVEL1_PCLOUD_TIME -
+ ( level.time - client->lastPoisonCloudedTime );
+
+ if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, client->ps.stats ) )
+ timeLeft -= BSUIT_PCLOUD_PROTECTION;
+
+ if( BG_InventoryContainsUpgrade( UP_HELMET, client->ps.stats ) )
+ timeLeft -= HELMET_PCLOUD_PROTECTION;
+
+ if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) )
+ timeLeft -= LIGHTARMOUR_PCLOUD_PROTECTION;
+
+ if( timeLeft <= 0 )
+ client->ps.stats[ STAT_STATE ] &= ~SS_POISONCLOUDED;
+ }
if( client->ps.stats[ STAT_STATE ] & SS_POISONED &&
client->lastPoisonTime + ALIEN_POISON_TIME < level.time )
@@ -1483,31 +1570,6 @@ void ClientThink_real( gentity_t *ent )
memset( &pm, 0, sizeof( pm ) );
- if( !( ucmd->buttons & BUTTON_TALK ) && !( client->ps.pm_flags & PMF_RESPAWNED ) )
- {
- switch( client->ps.weapon )
- {
- case WP_ALEVEL0:
- if( client->ps.weaponTime <= 0 )
- pm.autoWeaponHit[ client->ps.weapon ] = CheckVenomAttack( ent );
- break;
-
- case WP_ALEVEL1:
- case WP_ALEVEL1_UPG:
- CheckGrabAttack( ent );
- break;
-
- case WP_ALEVEL3:
- case WP_ALEVEL3_UPG:
- if( client->ps.weaponTime <= 0 )
- pm.autoWeaponHit[ client->ps.weapon ] = CheckPounceAttack( ent );
- break;
-
- default:
- break;
- }
- }
-
if( ent->flags & FL_FORCE_GESTURE )
{
ent->flags &= ~FL_FORCE_GESTURE;
@@ -1542,6 +1604,13 @@ void ClientThink_real( gentity_t *ent )
if( !ent->client->noclip )
G_TouchTriggers( ent );
+ // Tyrant crush
+ pm.pmext->fallVelocity = 0;
+ if( ent->client->forceCrouchTime + 500 > level.time )
+ client->ps.pm_flags |= PMF_FORCE_CROUCH;
+ else
+ client->ps.pm_flags &= ~PMF_FORCE_CROUCH;
+
Pmove( &pm );
G_UnlaggedDetectCollisions( ent );
@@ -1555,6 +1624,42 @@ void ClientThink_real( gentity_t *ent )
else
BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue );
+ switch( client->ps.weapon )
+ {
+ case WP_ALEVEL0:
+ if( !CheckVenomAttack( ent ) )
+ {
+ client->ps.weaponstate = WEAPON_READY;
+ }
+ else
+ {
+ client->ps.generic1 = WPM_PRIMARY;
+ G_AddEvent( ent, EV_FIRE_WEAPON, 0 );
+ }
+ break;
+
+ case WP_ALEVEL1:
+ case WP_ALEVEL1_UPG:
+ CheckGrabAttack( ent );
+ break;
+
+ case WP_ALEVEL3:
+ case WP_ALEVEL3_UPG:
+ if( !CheckPounceAttack( ent ) )
+ {
+ client->ps.weaponstate = WEAPON_READY;
+ }
+ else
+ {
+ client->ps.generic1 = WPM_SECONDARY;
+ G_AddEvent( ent, EV_FIRE_WEAPON2, 0 );
+ }
+ break;
+
+ default:
+ break;
+ }
+
SendPendingPredictableEvents( &ent->client->ps );
if( !( ent->client->ps.eFlags & EF_FIRING ) )
@@ -1574,6 +1679,8 @@ void ClientThink_real( gentity_t *ent )
// touch other objects
ClientImpacts( ent, &pm );
+ G_CheckZap( ent );
+
// execute client events
ClientEvents( ent, oldEventSequence );
@@ -1597,7 +1704,7 @@ void ClientThink_real( gentity_t *ent )
client->buttons = ucmd->buttons;
client->latched_buttons |= client->buttons & ~client->oldbuttons;
- if( ( client->buttons & BUTTON_GETFLAG ) && !( client->oldbuttons & BUTTON_GETFLAG ) &&
+ if( ( client->buttons & BUTTON_USE_EVOLVE ) && !( client->oldbuttons & BUTTON_USE_EVOLVE ) &&
client->ps.stats[ STAT_HEALTH ] > 0 )
{
trace_t trace;
@@ -1797,6 +1904,10 @@ void ClientEndFrame( gentity_t *ent )
pers = &ent->client->pers;
+ // save a copy of things from playerState in case of SPECTATOR_FOLLOW
+ pers->score = ent->client->ps.persistant[ PERS_SCORE ];
+ pers->credit = ent->client->ps.persistant[ PERS_CREDIT ];
+
//
// If the end of unit layout is displayed, don't give
// the player any normal movement attributes
@@ -1824,6 +1935,8 @@ void ClientEndFrame( gentity_t *ent )
G_SetClientSound( ent );
+ G_UpdateZaps( ent );
+
// set the latest infor
if( g_smoothClients.integer )
BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue );
@@ -1833,4 +1946,3 @@ void ClientEndFrame( gentity_t *ent )
SendPendingPredictableEvents( &ent->client->ps );
}
-
diff --git a/src/game/g_buildable.c b/src/game/g_buildable.c
index c56dec3c..ad2ece9e 100644
--- a/src/game/g_buildable.c
+++ b/src/game/g_buildable.c
@@ -192,7 +192,7 @@ static qboolean G_FindPower( gentity_t *self )
//if entity is a power item calculate the distance to it
if( ( ent->s.modelindex == BA_H_REACTOR || ent->s.modelindex == BA_H_REPEATER ) &&
- ent->spawned )
+ ent->spawned && ent->health > 0 )
{
VectorSubtract( self->s.origin, ent->s.origin, temp_v );
distance = VectorLength( temp_v );
@@ -268,28 +268,19 @@ buildable_t G_IsPowered( vec3_t origin )
================
G_FindDCC
-attempt to find a controlling DCC for self, return qtrue if successful
+attempt to find a controlling DCC for self, return number found
================
*/
-static qboolean G_FindDCC( gentity_t *self )
+int G_FindDCC( gentity_t *self )
{
int i;
gentity_t *ent;
- gentity_t *closestDCC = NULL;
int distance = 0;
- int minDistance = 10000;
vec3_t temp_v;
- qboolean foundDCC = qfalse;
+ int foundDCC = 0;
if( self->biteam != BIT_HUMANS )
- return qfalse;
-
- //if this already has dcc then stop now
- if( self->dccNode && self->dccNode->powered )
- return qtrue;
-
- //reset parent
- self->dccNode = NULL;
+ return 0;
//iterate through entities
for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
@@ -302,22 +293,14 @@ static qboolean G_FindDCC( gentity_t *self )
{
VectorSubtract( self->s.origin, ent->s.origin, temp_v );
distance = VectorLength( temp_v );
- if( distance < minDistance && ent->powered )
+ if( distance < DC_RANGE && ent->powered )
{
- closestDCC = ent;
- minDistance = distance;
- foundDCC = qtrue;
+ foundDCC++;
}
}
}
- //if there was no nearby DCC give up
- if( !foundDCC )
- return qfalse;
-
- self->dccNode = closestDCC;
-
- return qtrue;
+ return foundDCC;
}
/*
@@ -333,7 +316,6 @@ qboolean G_IsDCCBuilt( void )
memset( &dummy, 0, sizeof( gentity_t ) );
- dummy.dccNode = NULL;
dummy.biteam = BIT_HUMANS;
return G_FindDCC( &dummy );
@@ -404,7 +386,7 @@ G_FindCreep
attempt to find creep for self, return qtrue if successful
================
*/
-static qboolean G_FindCreep( gentity_t *self )
+qboolean G_FindCreep( gentity_t *self )
{
int i;
gentity_t *ent;
@@ -418,9 +400,9 @@ static qboolean G_FindCreep( gentity_t *self )
return qtrue;
//if self does not have a parentNode or it's parentNode is invalid find a new one
- if( ( self->parentNode == NULL ) || !self->parentNode->inuse )
+ if( self->client || ( self->parentNode == NULL ) || !self->parentNode->inuse )
{
- for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ )
+ for ( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ )
{
if( ent->s.eType != ET_BUILDABLE )
continue;
@@ -440,13 +422,17 @@ static qboolean G_FindCreep( gentity_t *self )
if( minDistance <= CREEP_BASESIZE )
{
- self->parentNode = closestSpawn;
+ if( !self->client )
+ self->parentNode = closestSpawn;
return qtrue;
}
else
return qfalse;
}
+ if( self->client )
+ return qfalse;
+
//if we haven't returned by now then we must already have a valid parent
return qtrue;
}
@@ -845,29 +831,15 @@ void AOvermind_Think( gentity_t *self )
-/*
-================
-ABarricade_Pain
-
-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 );
- else
- G_SetBuildableAnim( self, BANIM_PAIN2, qfalse );
-}
/*
================
-ABarricade_Blast
+AGeneric_Blast
-Called when an alien spawn dies
+Called when an Alien buildable explodes after dead state
================
*/
-void ABarricade_Blast( gentity_t *self )
+void AGeneric_Blast( gentity_t *self )
{
vec3_t dir;
@@ -890,18 +862,19 @@ void ABarricade_Blast( gentity_t *self )
/*
================
-ABarricade_Die
+AGeneric_Die
-Called when an alien spawn dies
+Called when an Alien buildable is killed and enters a brief dead state prior to
+exploding.
================
*/
-void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+void AGeneric_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 );
self->die = nullDieFunction;
- self->think = ABarricade_Blast;
+ self->think = AGeneric_Blast;
self->s.eFlags &= ~EF_FIRING; //prevent any firing effects
if( self->spawned )
@@ -928,12 +901,12 @@ void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker,
/*
================
-ABarricade_Think
+AGeneric_Think
-Think function for Alien Barricade
+A generic think function for Alien buildables
================
*/
-void ABarricade_Think( gentity_t *self )
+void AGeneric_Think( gentity_t *self )
{
self->powered = G_IsOvermindBuilt( );
@@ -950,6 +923,21 @@ void ABarricade_Think( gentity_t *self )
self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
}
+/*
+================
+AGeneric_Pain
+
+A generic pain function for Alien buildables
+================
+*/
+void AGeneric_Pain( gentity_t *self, gentity_t *attacker, int damage )
+{
+ if( rand( ) % 1 )
+ G_SetBuildableAnim( self, BANIM_PAIN1, qfalse );
+ else
+ G_SetBuildableAnim( self, BANIM_PAIN2, qfalse );
+}
+
@@ -958,6 +946,153 @@ void ABarricade_Think( gentity_t *self )
+
+/*
+================
+ABarricade_Pain
+
+Barricade pain animation depends on shrunk state
+================
+*/
+void ABarricade_Pain( gentity_t *self, gentity_t *attacker, int damage )
+{
+ if( !self->shrunkTime )
+ G_SetBuildableAnim( self, BANIM_PAIN1, qfalse );
+ else
+ G_SetBuildableAnim( self, BANIM_PAIN2, qfalse );
+}
+
+/*
+================
+ABarricade_Shrink
+
+Set shrink state for a barricade. When unshrinking, checks to make sure there
+is enough room.
+================
+*/
+void ABarricade_Shrink( gentity_t *self, qboolean shrink )
+{
+ if ( !self->spawned || self->health <= 0 )
+ shrink = qtrue;
+ if ( shrink && self->shrunkTime )
+ {
+ int anim;
+
+ // We need to make sure that the animation has been set to shrunk mode
+ // because we start out shrunk but with the construct animation when built
+ self->shrunkTime = level.time;
+ anim = self->s.torsoAnim & ~( ANIM_FORCEBIT | ANIM_TOGGLEBIT );
+ if ( self->spawned && self->health > 0 && anim != BANIM_DESTROYED )
+ {
+ G_SetIdleBuildableAnim( self, BANIM_DESTROYED );
+ G_SetBuildableAnim( self, BANIM_ATTACK1, qtrue );
+ }
+ return;
+ }
+ if ( !shrink &&
+ ( !self->shrunkTime ||
+ level.time < self->shrunkTime + BARRICADE_SHRINKTIMEOUT ) )
+ return;
+ BG_FindBBoxForBuildable( BA_A_BARRICADE, self->r.mins, self->r.maxs );
+ if ( shrink )
+ {
+ self->r.maxs[ 2 ] = (int)( self->r.maxs[ 2 ] * BARRICADE_SHRINKPROP );
+ self->shrunkTime = level.time;
+
+ // shrink animation, the destroy animation is used
+ if ( self->spawned && self->health > 0 )
+ {
+ G_SetBuildableAnim( self, BANIM_ATTACK1, qtrue );
+ G_SetIdleBuildableAnim( self, BANIM_DESTROYED );
+ }
+ }
+ else
+ {
+ trace_t tr;
+ int anim;
+
+ trap_Trace( &tr, self->s.origin, self->r.mins, self->r.maxs,
+ self->s.origin, self->s.number, MASK_PLAYERSOLID );
+ if ( tr.startsolid || tr.fraction < 1.f )
+ {
+ self->r.maxs[ 2 ] = (int)( self->r.maxs[ 2 ] * BARRICADE_SHRINKPROP );
+ return;
+ }
+ self->shrunkTime = 0;
+
+ // unshrink animation, IDLE2 has been hijacked for this
+ anim = self->s.legsAnim & ~( ANIM_FORCEBIT | ANIM_TOGGLEBIT );
+ if ( self->spawned && self->health > 0 &&
+ anim != BANIM_CONSTRUCT1 && anim != BANIM_CONSTRUCT2 )
+ {
+ G_SetIdleBuildableAnim( self, BG_FindAnimForBuildable( BA_A_BARRICADE ) );
+ G_SetBuildableAnim( self, BANIM_ATTACK2, qtrue );
+ }
+ }
+
+ // a change in size requires a relink
+ if ( self->spawned )
+ trap_LinkEntity( self );
+}
+
+/*
+================
+ABarricade_Die
+
+Called when an alien spawn dies
+================
+*/
+void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+{
+ AGeneric_Die( self, inflictor, attacker, damage, mod );
+ ABarricade_Shrink( self, qtrue );
+}
+
+/*
+================
+ABarricade_Think
+
+Think function for Alien Barricade
+================
+*/
+void ABarricade_Think( gentity_t *self )
+{
+ AGeneric_Think( self );
+ ABarricade_Shrink( self, !G_FindOvermind( self ) );
+}
+
+/*
+================
+ABarricade_Touch
+
+Barricades shrink when they are come into contact with an Alien that can
+pass through
+================
+*/
+
+void ABarricade_Touch( gentity_t *self, gentity_t *other, trace_t *trace )
+{
+ gclient_t *client = other->client;
+ int client_z, min_z;
+
+ if( !client || client->pers.teamSelection != PTE_ALIENS )
+ return;
+
+ // Client must be high enough to pass over. Note that STEPSIZE (18) is
+ // hardcoded here because we don't include bg_local.h!
+ client_z = other->s.origin[ 2 ] + other->r.mins[ 2 ];
+ min_z = self->s.origin[ 2 ] - 18 +
+ (int)( self->r.maxs[ 2 ] * BARRICADE_SHRINKPROP );
+ if( client_z < min_z )
+ return;
+ ABarricade_Shrink( self, qtrue );
+}
+
+//==================================================================================
+
+
+
+
void AAcidTube_Think( gentity_t *self );
/*
@@ -1067,7 +1202,7 @@ Think function for Alien Hive
void AHive_Think( gentity_t *self )
{
int entityList[ MAX_GENTITIES ];
- vec3_t range = { ACIDTUBE_RANGE, ACIDTUBE_RANGE, ACIDTUBE_RANGE };
+ vec3_t range = { HIVE_SENSE_RANGE, HIVE_SENSE_RANGE, HIVE_SENSE_RANGE };
vec3_t mins, maxs;
int i, num;
gentity_t *enemy;
@@ -1125,6 +1260,61 @@ void AHive_Think( gentity_t *self )
G_CreepSlow( self );
}
+/*
+================
+AHive_Pain
+
+pain function for Alien Hive
+================
+*/
+void AHive_Pain( gentity_t *self, gentity_t *attacker, int damage )
+{
+ if( attacker && attacker->client && attacker->biteam == BIT_HUMANS &&
+ self->spawned && !self->active && G_FindOvermind( self ) )
+ {
+ vec3_t dirToTarget;
+
+ self->active = qtrue;
+ self->target_ent = attacker;
+ self->timestamp = level.time + HIVE_REPEAT;
+
+ VectorSubtract( attacker->s.pos.trBase, self->s.pos.trBase, dirToTarget );
+ VectorNormalize( dirToTarget );
+ vectoangles( dirToTarget, self->turretAim );
+
+ //fire at target
+ FireWeapon( self );
+ }
+ G_SetBuildableAnim( self, BANIM_PAIN1, qfalse );
+}
+
+/*
+================
+AHive_Die
+
+pain function for Alien Hive
+================
+*/
+void AHive_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
+{
+ if( attacker && attacker->client && attacker->biteam == BIT_HUMANS &&
+ self->spawned && !self->active && G_FindOvermind( self ) )
+ {
+ vec3_t dirToTarget;
+
+ self->active = qtrue;
+ self->target_ent = attacker;
+ self->timestamp = level.time + HIVE_REPEAT;
+
+ VectorSubtract( attacker->s.pos.trBase, self->s.pos.trBase, dirToTarget );
+ VectorNormalize( dirToTarget );
+ vectoangles( dirToTarget, self->turretAim );
+
+ //fire at target
+ FireWeapon( self );
+ }
+ AGeneric_Die( self, inflictor, attacker, damage, mod );
+}
@@ -1181,7 +1371,7 @@ qboolean AHovel_Blocked( gentity_t *hovel, gentity_t *player, qboolean provideEx
G_SetOrigin( player, origin );
VectorCopy( origin, player->client->ps.origin );
VectorCopy( vec3_origin, player->client->ps.velocity );
- SetClientViewAngle( player, angles );
+ G_SetClientViewAngle( player, angles );
}
if( tr.fraction < 1.0f )
@@ -1264,7 +1454,7 @@ void AHovel_Use( gentity_t *self, gentity_t *other, gentity_t *activator )
G_SetOrigin( activator, hovelOrigin );
VectorCopy( hovelOrigin, activator->client->ps.origin );
- SetClientViewAngle( activator, hovelAngles );
+ G_SetClientViewAngle( activator, hovelAngles );
}
}
}
@@ -1339,7 +1529,7 @@ void AHovel_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int
G_SetOrigin( builder, newOrigin );
VectorCopy( newOrigin, builder->client->ps.origin );
- SetClientViewAngle( builder, newAngles );
+ G_SetClientViewAngle( builder, newAngles );
//client leaves hovel
builder->client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING;
@@ -1397,15 +1587,8 @@ void ABooster_Touch( gentity_t *self, gentity_t *other, trace_t *trace )
if( client && client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
return;
- //only allow boostage once every 30 seconds
- if( client->lastBoostedTime + BOOSTER_INTERVAL > level.time )
- return;
-
- if( !( client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) )
- {
- client->ps.stats[ STAT_STATE ] |= SS_BOOSTED;
- client->lastBoostedTime = level.time;
- }
+ client->ps.stats[ STAT_STATE ] |= SS_BOOSTED;
+ client->ps.stats[ STAT_MISC2 ] = BOOST_TIME;
}
@@ -1648,9 +1831,6 @@ void HRepeater_Use( gentity_t *self, gentity_t *other, gentity_t *activator )
G_GiveClientMaxAmmo( other, qtrue );
}
-
-#define DCC_ATTACK_PERIOD 10000
-
/*
================
HReactor_Think
@@ -1661,13 +1841,26 @@ Think function for Human Reactor
void HReactor_Think( gentity_t *self )
{
int entityList[ MAX_GENTITIES ];
- vec3_t range = { REACTOR_ATTACK_RANGE, REACTOR_ATTACK_RANGE, REACTOR_ATTACK_RANGE };
+ vec3_t range = { REACTOR_ATTACK_RANGE,
+ REACTOR_ATTACK_RANGE,
+ REACTOR_ATTACK_RANGE };
+ vec3_t dccrange = { REACTOR_ATTACK_DCC_RANGE,
+ REACTOR_ATTACK_DCC_RANGE,
+ REACTOR_ATTACK_DCC_RANGE };
vec3_t mins, maxs;
int i, num;
gentity_t *enemy, *tent;
- VectorAdd( self->s.origin, range, maxs );
- VectorSubtract( self->s.origin, range, mins );
+ if( self->dcc )
+ {
+ VectorAdd( self->s.origin, dccrange, maxs );
+ VectorSubtract( self->s.origin, dccrange, mins );
+ }
+ else
+ {
+ VectorAdd( self->s.origin, range, maxs );
+ VectorSubtract( self->s.origin, range, mins );
+ }
if( self->spawned && ( self->health > 0 ) )
{
@@ -1680,8 +1873,18 @@ void HReactor_Think( gentity_t *self )
if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
{
self->timestamp = level.time;
- G_SelectiveRadiusDamage( self->s.pos.trBase, self, REACTOR_ATTACK_DAMAGE,
- REACTOR_ATTACK_RANGE, self, MOD_REACTOR, PTE_HUMANS );
+ if( self->dcc )
+ {
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self,
+ REACTOR_ATTACK_DCC_DAMAGE, REACTOR_ATTACK_DCC_RANGE, self,
+ MOD_REACTOR, PTE_HUMANS );
+ }
+ else
+ {
+ G_SelectiveRadiusDamage( self->s.pos.trBase, self,
+ REACTOR_ATTACK_DAMAGE, REACTOR_ATTACK_RANGE, self,
+ MOD_REACTOR, PTE_HUMANS );
+ }
tent = G_TempEntity( enemy->s.pos.trBase, EV_TESLATRAIL );
@@ -1691,19 +1894,12 @@ void HReactor_Think( gentity_t *self )
tent->s.clientNum = enemy->s.number; //dest
}
}
-
- //reactor under attack
- if( self->health < self->lastHealth &&
- level.time > level.humanBaseAttackTimer && G_IsDCCBuilt( ) )
- {
- level.humanBaseAttackTimer = level.time + DCC_ATTACK_PERIOD;
- G_BroadcastEvent( EV_DCC_ATTACK, 0 );
- }
-
- self->lastHealth = self->health;
}
- self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+ if( self->dcc )
+ self->nextthink = level.time + REACTOR_ATTACK_DCC_REPEAT;
+ else
+ self->nextthink = level.time + REACTOR_ATTACK_REPEAT;
}
//==================================================================================
@@ -1847,7 +2043,8 @@ void HMedistat_Think( gentity_t *self )
if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
{
- if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] &&
+ if( ( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] ||
+ player->client->ps.stats[ STAT_STAMINA ] < MAX_STAMINA ) &&
player->client->ps.pm_type != PM_DEAD )
{
self->enemy = player;
@@ -1873,17 +2070,26 @@ void HMedistat_Think( gentity_t *self )
self->active = qfalse;
}
- else if( self->enemy ) //heal!
+ else if( self->enemy && self->enemy->client ) //heal!
{
- if( self->enemy->client && self->enemy->client->ps.stats[ STAT_STATE ] & SS_POISONED )
+ if( self->enemy->client->ps.stats[ STAT_STATE ] & SS_POISONED )
self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED;
+ if( self->enemy->client->ps.stats[ STAT_STAMINA ] < MAX_STAMINA )
+ self->enemy->client->ps.stats[ STAT_STAMINA ] += STAMINA_MEDISTAT_RESTORE;
+
+ if( self->enemy->client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA )
+ self->enemy->client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA;
+
self->enemy->health++;
//if they're completely healed, give them a medkit
- if( self->enemy->health >= self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] &&
- !BG_InventoryContainsUpgrade( UP_MEDKIT, self->enemy->client->ps.stats ) )
- BG_AddUpgradeToInventory( UP_MEDKIT, self->enemy->client->ps.stats );
+ if( self->enemy->health >= self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] )
+ {
+ self->enemy->health = self->enemy->client->ps.stats[ STAT_MAX_HEALTH ];
+ if( !BG_InventoryContainsUpgrade( UP_MEDKIT, self->enemy->client->ps.stats ) )
+ BG_AddUpgradeToInventory( UP_MEDKIT, self->enemy->client->ps.stats );
+ }
}
}
}
@@ -1910,22 +2116,11 @@ qboolean HMGTurret_TrackEnemy( gentity_t *self )
float temp, rotAngle;
float accuracyTolerance, angularSpeed;
- if( self->lev1Grabbed )
- {
- //can't turn fast if grabbed
- accuracyTolerance = MGTURRET_GRAB_ACCURACYTOLERANCE;
- angularSpeed = MGTURRET_GRAB_ANGULARSPEED;
- }
- else if( self->dcced )
- {
- accuracyTolerance = MGTURRET_DCC_ACCURACYTOLERANCE;
- angularSpeed = MGTURRET_DCC_ANGULARSPEED;
- }
+ accuracyTolerance = MGTURRET_ACCURACYTOLERANCE;
+ if( self->locked )
+ angularSpeed = MGTURRET_ANGULARSPEED_LOCKED;
else
- {
- accuracyTolerance = MGTURRET_ACCURACYTOLERANCE;
angularSpeed = MGTURRET_ANGULARSPEED;
- }
VectorSubtract( self->enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget );
@@ -1942,9 +2137,9 @@ qboolean HMGTurret_TrackEnemy( gentity_t *self )
angularDiff[ YAW ] = AngleSubtract( self->s.angles2[ YAW ], angleToTarget[ YAW ] );
//if not pointing at our target then move accordingly
- if( angularDiff[ PITCH ] < (-accuracyTolerance) )
+ if( angularDiff[ PITCH ] < 0 && angularDiff[ PITCH ] < (-angularSpeed) )
self->s.angles2[ PITCH ] += angularSpeed;
- else if( angularDiff[ PITCH ] > accuracyTolerance )
+ else if( angularDiff[ PITCH ] > 0 && angularDiff[ PITCH ] > angularSpeed )
self->s.angles2[ PITCH ] -= angularSpeed;
else
self->s.angles2[ PITCH ] = angleToTarget[ PITCH ];
@@ -1958,9 +2153,9 @@ qboolean HMGTurret_TrackEnemy( gentity_t *self )
self->s.angles2[ PITCH ] = (-360) + MGTURRET_VERTICALCAP;
//if not pointing at our target then move accordingly
- if( angularDiff[ YAW ] < (-accuracyTolerance) )
+ if( angularDiff[ YAW ] < 0 && angularDiff[ YAW ] < ( -angularSpeed ) )
self->s.angles2[ YAW ] += angularSpeed;
- else if( angularDiff[ YAW ] > accuracyTolerance )
+ else if( angularDiff[ YAW ] > 0 && angularDiff[ YAW ] > angularSpeed )
self->s.angles2[ YAW ] -= angularSpeed;
else
self->s.angles2[ YAW ] = angleToTarget[ YAW ];
@@ -1985,7 +2180,7 @@ HMGTurret_CheckTarget
Used by HMGTurret_Think to check enemies for validity
================
*/
-qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target, qboolean ignorePainted )
+qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target )
{
trace_t trace;
gentity_t *traceEnt;
@@ -2005,8 +2200,14 @@ qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target, qboolean ign
if( Distance( self->s.origin, target->s.pos.trBase ) > MGTURRET_RANGE )
return qfalse;
- //some turret has already selected this target
- if( self->dcced && target->targeted && target->targeted->powered && !ignorePainted )
+ trap_Trace( &trace, self->s.pos.trBase, NULL, NULL, target->s.pos.trBase, self->s.number, MASK_SHOT );
+
+ traceEnt = &g_entities[ trace.entityNum ];
+
+ if( !traceEnt->client )
+ return qfalse;
+
+ if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS )
return qfalse;
trap_Trace( &trace, self->s.pos.trBase, NULL, NULL, target->s.pos.trBase, self->s.number, MASK_SHOT );
@@ -2051,7 +2252,7 @@ void HMGTurret_FindEnemy( gentity_t *self )
if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
{
//if target is not valid keep searching
- if( !HMGTurret_CheckTarget( self, target, qfalse ) )
+ if( !HMGTurret_CheckTarget( self, target ) )
continue;
//we found a target
@@ -2059,27 +2260,6 @@ void HMGTurret_FindEnemy( gentity_t *self )
return;
}
}
-
- if( self->dcced )
- {
- //check again, this time ignoring painted targets
- for( i = 0; i < num; i++ )
- {
- target = &g_entities[ entityList[ i ] ];
-
- if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
- {
- //if target is not valid keep searching
- if( !HMGTurret_CheckTarget( self, target, qtrue ) )
- continue;
-
- //we found a target
- self->enemy = target;
- return;
- }
- }
- }
-
//couldn't find a target
self->enemy = NULL;
}
@@ -2110,12 +2290,10 @@ void HMGTurret_Think( gentity_t *self )
if( self->spawned )
{
- //find a dcc for self
- self->dcced = G_FindDCC( self );
-
//if the current target is not valid find a new one
- if( !HMGTurret_CheckTarget( self, self->enemy, qfalse ) )
+ if( !HMGTurret_CheckTarget( self, self->enemy ) )
{
+ self->locked = qfalse;
if( self->enemy )
self->enemy->targeted = NULL;
@@ -2128,17 +2306,43 @@ void HMGTurret_Think( gentity_t *self )
self->enemy->targeted = self;
- //if we are pointing at our target and we can fire shoot it
- if( HMGTurret_TrackEnemy( self ) && ( self->count < level.time ) )
+ if( self->active )
{
- //fire at target
- FireWeapon( self );
+ qboolean canFire = HMGTurret_TrackEnemy( self );
- self->s.eFlags |= EF_FIRING;
- G_AddEvent( self, EV_FIRE_WEAPON, 0 );
- G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );
+ if( self->turretSpinupTime < level.time )
+ {
+ if( canFire )
+ {
+ if( !self->locked )
+ {
+ self->active = qfalse;
+ }
+ else if( self->count < level.time )
+ {
+ //fire at target
+ FireWeapon( self );
+ self->s.eFlags |= EF_FIRING;
+ G_AddEvent( self, EV_FIRE_WEAPON, 0 );
+ G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );
+ self->count = level.time + firespeed;
+ }
+ }
+ else
+ {
+ self->locked = qfalse;
+ }
+ }
+ return;
+ }
- self->count = level.time + firespeed;
+ //if we are pointing at our target, start spinning up
+ if( HMGTurret_TrackEnemy( self ) && self->count < level.time )
+ {
+ self->active = qtrue;
+ self->locked = qtrue;
+ self->turretSpinupTime = level.time + MGTURRET_SPINUP_TIME;
+ G_AddEvent( self, EV_MGTURRET_SPINUP, 0 );
}
}
}
@@ -2170,7 +2374,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 = G_FindPower( self ) ) || !( self->dcced = G_FindDCC( self ) ) )
+ if( !( self->powered = G_FindPower( self ) ) )
{
self->s.eFlags &= ~EF_FIRING;
self->nextthink = level.time + POWER_REFRESH_TIME;
@@ -2369,16 +2573,6 @@ void HSpawn_Think( gentity_t *self )
G_FreeEntity( ent ); //quietly remove
}
}
-
- //spawn under attack
- if( self->health < self->lastHealth &&
- level.time > level.humanBaseAttackTimer && G_IsDCCBuilt( ) )
- {
- level.humanBaseAttackTimer = level.time + DCC_ATTACK_PERIOD;
- G_BroadcastEvent( EV_DCC_ATTACK, 0 );
- }
-
- self->lastHealth = self->health;
}
self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
@@ -2471,6 +2665,8 @@ void G_BuildableThink( gentity_t *ent, int msec )
ent->spawned = qtrue;
}
+ ent->dcc = ( ent->biteam != BIT_HUMANS ) ? 0 : G_FindDCC( ent );
+
ent->s.generic1 = (int)( ( (float)ent->health / (float)bHealth ) * B_HEALTH_MASK );
if( ent->s.generic1 < 0 )
@@ -2479,7 +2675,7 @@ void G_BuildableThink( gentity_t *ent, int msec )
if( ent->powered )
ent->s.generic1 |= B_POWERED_TOGGLEBIT;
- if( ent->dcced )
+ if( ent->dcc )
ent->s.generic1 |= B_DCCED_TOGGLEBIT;
if( ent->spawned )
@@ -2496,9 +2692,19 @@ void G_BuildableThink( gentity_t *ent, int msec )
if( !ent->spawned && ent->health > 0 )
ent->health += (int)( ceil( (float)bHealth / (float)( bTime * 0.001 ) ) );
- else if( ent->biteam == BIT_ALIENS && ent->health > 0 && ent->health < bHealth &&
- bRegen && ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time )
+ else if( ent->health > 0 && ent->health < bHealth )
+ {
+ if( ent->biteam == BIT_ALIENS && bRegen &&
+ ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time )
+ {
ent->health += bRegen;
+ }
+ else if( ent->biteam == BIT_HUMANS && ent->dcc &&
+ ( ent->lastDamageTime + HUMAN_REGEN_DAMAGE_TIME ) < level.time )
+ {
+ ent->health += DC_HEALRATE * ent->dcc;
+ }
+ }
if( ent->health > bHealth )
ent->health = bHealth;
@@ -3002,7 +3208,7 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance
tempent = G_FindBuildable( BA_A_OVERMIND );
if( tempent == NULL || !tempent->spawned || tempent->health <= 0 )
- reason = IBE_OVERMIND;
+ reason = IBE_NOOVERMIND;
}
//check there is creep near by for building on
@@ -3196,29 +3402,32 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori
built->die = ABarricade_Die;
built->think = ABarricade_Think;
built->pain = ABarricade_Pain;
+ built->touch = ABarricade_Touch;
+ built->shrunkTime = 0;
+ ABarricade_Shrink( built, qtrue );
break;
case BA_A_BOOSTER:
- built->die = ABarricade_Die;
- built->think = ABarricade_Think;
- built->pain = ABarricade_Pain;
+ built->die = AGeneric_Die;
+ built->think = AGeneric_Think;
+ built->pain = AGeneric_Pain;
built->touch = ABooster_Touch;
break;
case BA_A_ACIDTUBE:
- built->die = ABarricade_Die;
+ built->die = AGeneric_Die;
built->think = AAcidTube_Think;
built->pain = ASpawn_Pain;
break;
case BA_A_HIVE:
- built->die = ABarricade_Die;
+ built->die = AHive_Die;
built->think = AHive_Think;
- built->pain = ASpawn_Pain;
+ built->pain = AHive_Pain;
break;
case BA_A_TRAPPER:
- built->die = ABarricade_Die;
+ built->die = AGeneric_Die;
built->think = ATrapper_Think;
built->pain = ASpawn_Pain;
break;
@@ -3327,9 +3536,6 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori
else if( ( built->powered = G_FindPower( built ) ) )
built->s.generic1 |= B_POWERED_TOGGLEBIT;
- if( ( built->dcced = G_FindDCC( built ) ) )
- built->s.generic1 |= B_DCCED_TOGGLEBIT;
-
built->s.generic1 &= ~B_SPAWNED_TOGGLEBIT;
VectorCopy( normal, built->s.origin2 );
diff --git a/src/game/g_client.c b/src/game/g_client.c
index 66ba3b6f..0e657f0a 100644
--- a/src/game/g_client.c
+++ b/src/game/g_client.c
@@ -86,51 +86,31 @@ G_AddCreditToClient
*/
void G_AddCreditToClient( gclient_t *client, short credit, qboolean cap )
{
+ int capAmount;
+
if( !client )
return;
- //if we're already at the max and trying to add credit then stop
- if( cap )
- {
- if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
- {
- if( client->ps.persistant[ PERS_CREDIT ] >= ALIEN_MAX_KILLS &&
- credit > 0 )
- return;
- }
- else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
- {
- if( client->ps.persistant[ PERS_CREDIT ] >= HUMAN_MAX_CREDITS &&
- credit > 0 )
- return;
- }
- }
+ client->pers.credit += credit;
+ capAmount = client->pers.teamSelection == PTE_ALIENS ?
+ ALIEN_MAX_KILLS : HUMAN_MAX_CREDITS;
- client->ps.persistant[ PERS_CREDIT ] += credit;
+ if( client->pers.credit > capAmount )
+ client->pers.credit = capAmount;
- if( cap )
- {
- if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
- {
- if( client->ps.persistant[ PERS_CREDIT ] > ALIEN_MAX_KILLS )
- client->ps.persistant[ PERS_CREDIT ] = ALIEN_MAX_KILLS;
- }
- else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
- {
- if( client->ps.persistant[ PERS_CREDIT ] > HUMAN_MAX_CREDITS )
- client->ps.persistant[ PERS_CREDIT ] = HUMAN_MAX_CREDITS;
- }
- }
+ if( client->pers.credit < 0 )
+ client->pers.credit = 0;
- if( client->ps.persistant[ PERS_CREDIT ] < 0 )
- client->ps.persistant[ PERS_CREDIT ] = 0;
+ // keep PERS_CREDIT in sync if not following
+ if( client->sess.spectatorState != SPECTATOR_FOLLOW )
+ client->ps.persistant[ PERS_CREDIT ] = client->pers.credit;
}
/*
=======================================================================
- SelectSpawnPoint
+ G_SelectSpawnPoint
=======================================================================
*/
@@ -165,13 +145,13 @@ qboolean SpotWouldTelefrag( gentity_t *spot )
/*
================
-SelectNearestDeathmatchSpawnPoint
+G_SelectNearestDeathmatchSpawnPoint
Find the spot that we DON'T want to use
================
*/
#define MAX_SPAWN_POINTS 128
-gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from )
+gentity_t *G_SelectNearestDeathmatchSpawnPoint( vec3_t from )
{
gentity_t *spot;
vec3_t delta;
@@ -200,13 +180,13 @@ gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from )
/*
================
-SelectRandomDeathmatchSpawnPoint
+G_SelectRandomDeathmatchSpawnPoint
go to a random point that doesn't telefrag
================
*/
#define MAX_SPAWN_POINTS 128
-gentity_t *SelectRandomDeathmatchSpawnPoint( void )
+gentity_t *G_SelectRandomDeathmatchSpawnPoint( void )
{
gentity_t *spot;
int count;
@@ -235,12 +215,12 @@ gentity_t *SelectRandomDeathmatchSpawnPoint( void )
/*
===========
-SelectRandomFurthestSpawnPoint
+G_SelectRandomFurthestSpawnPoint
Chooses a player start, deathmatch start, etc
============
*/
-gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles )
+gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles )
{
gentity_t *spot;
vec3_t delta;
@@ -318,12 +298,12 @@ gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, ve
/*
================
-SelectAlienSpawnPoint
+G_SelectAlienSpawnPoint
go to a random point that doesn't telefrag
================
*/
-gentity_t *SelectAlienSpawnPoint( vec3_t preference )
+gentity_t *G_SelectAlienSpawnPoint( vec3_t preference )
{
gentity_t *spot;
int count;
@@ -367,12 +347,12 @@ gentity_t *SelectAlienSpawnPoint( vec3_t preference )
/*
================
-SelectHumanSpawnPoint
+G_SelectHumanSpawnPoint
go to a random point that doesn't telefrag
================
*/
-gentity_t *SelectHumanSpawnPoint( vec3_t preference )
+gentity_t *G_SelectHumanSpawnPoint( vec3_t preference )
{
gentity_t *spot;
int count;
@@ -416,32 +396,32 @@ gentity_t *SelectHumanSpawnPoint( vec3_t preference )
/*
===========
-SelectSpawnPoint
+G_SelectSpawnPoint
Chooses a player start, deathmatch start, etc
============
*/
-gentity_t *SelectSpawnPoint( vec3_t avoidPoint, vec3_t origin, vec3_t angles )
+gentity_t *G_SelectSpawnPoint( vec3_t avoidPoint, vec3_t origin, vec3_t angles )
{
- return SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles );
+ return G_SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles );
}
/*
===========
-SelectTremulousSpawnPoint
+G_SelectTremulousSpawnPoint
Chooses a player start, deathmatch start, etc
============
*/
-gentity_t *SelectTremulousSpawnPoint( pTeam_t team, vec3_t preference, vec3_t origin, vec3_t angles )
+gentity_t *G_SelectTremulousSpawnPoint( pTeam_t team, vec3_t preference, vec3_t origin, vec3_t angles )
{
gentity_t *spot = NULL;
if( team == PTE_ALIENS )
- spot = SelectAlienSpawnPoint( preference );
+ spot = G_SelectAlienSpawnPoint( preference );
else if( team == PTE_HUMANS )
- spot = SelectHumanSpawnPoint( preference );
+ spot = G_SelectHumanSpawnPoint( preference );
//no available spots
if( !spot )
@@ -462,13 +442,13 @@ gentity_t *SelectTremulousSpawnPoint( pTeam_t team, vec3_t preference, vec3_t or
/*
===========
-SelectInitialSpawnPoint
+G_SelectInitialSpawnPoint
Try to find a spawn point marked 'initial', otherwise
use normal spawn selection.
============
*/
-gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles )
+gentity_t *G_SelectInitialSpawnPoint( vec3_t origin, vec3_t angles )
{
gentity_t *spot;
@@ -481,7 +461,7 @@ gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles )
if( !spot || SpotWouldTelefrag( spot ) )
{
- return SelectSpawnPoint( vec3_origin, origin, angles );
+ return G_SelectSpawnPoint( vec3_origin, origin, angles );
}
VectorCopy( spot->s.origin, origin );
@@ -493,11 +473,11 @@ gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles )
/*
===========
-SelectSpectatorSpawnPoint
+G_SelectSpectatorSpawnPoint
============
*/
-gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles )
+gentity_t *G_SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles )
{
FindIntermissionPoint( );
@@ -510,13 +490,13 @@ gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles )
/*
===========
-SelectAlienLockSpawnPoint
+G_SelectAlienLockSpawnPoint
Try to find a spawn point for alien intermission otherwise
use normal intermission spawn.
============
*/
-gentity_t *SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles )
+gentity_t *G_SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles )
{
gentity_t *spot;
@@ -524,7 +504,7 @@ gentity_t *SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles )
spot = G_Find( spot, FOFS( classname ), "info_alien_intermission" );
if( !spot )
- return SelectSpectatorSpawnPoint( origin, angles );
+ return G_SelectSpectatorSpawnPoint( origin, angles );
VectorCopy( spot->s.origin, origin );
VectorCopy( spot->s.angles, angles );
@@ -535,13 +515,13 @@ gentity_t *SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles )
/*
===========
-SelectHumanLockSpawnPoint
+G_SelectHumanLockSpawnPoint
Try to find a spawn point for human intermission otherwise
use normal intermission spawn.
============
*/
-gentity_t *SelectHumanLockSpawnPoint( vec3_t origin, vec3_t angles )
+gentity_t *G_SelectHumanLockSpawnPoint( vec3_t origin, vec3_t angles )
{
gentity_t *spot;
@@ -549,7 +529,7 @@ gentity_t *SelectHumanLockSpawnPoint( vec3_t origin, vec3_t angles )
spot = G_Find( spot, FOFS( classname ), "info_human_intermission" );
if( !spot )
- return SelectSpectatorSpawnPoint( origin, angles );
+ return G_SelectSpectatorSpawnPoint( origin, angles );
VectorCopy( spot->s.origin, origin );
VectorCopy( spot->s.angles, angles );
@@ -731,11 +711,11 @@ void SpawnCorpse( gentity_t *ent )
/*
==================
-SetClientViewAngle
+G_SetClientViewAngle
==================
*/
-void SetClientViewAngle( gentity_t *ent, vec3_t angle )
+void G_SetClientViewAngle( gentity_t *ent, vec3_t angle )
{
int i;
@@ -1119,10 +1099,7 @@ void ClientUserinfoChanged( int clientNum )
strcpy( c1, Info_ValueForKey( userinfo, "color1" ) );
strcpy( c2, Info_ValueForKey( userinfo, "color2" ) );
- if( client->ps.pm_flags & PMF_FOLLOW )
- team = PTE_NONE;
- else
- team = client->ps.stats[ STAT_PTEAM ];
+ team = client->pers.teamSelection;
// send over a subset of the userinfo keys so other clients can
// print scoreboards, display models, and play custom sounds
@@ -1280,6 +1257,7 @@ void ClientBegin( int clientNum )
client->pers.connected = CON_CONNECTED;
client->pers.enterTime = level.time;
client->pers.teamState.state = TEAM_BEGIN;
+ client->pers.classSelection = PCL_NONE;
// save eflags around this, because changing teams will
// cause this to happen with a valid entity, and we
@@ -1292,7 +1270,6 @@ void ClientBegin( int clientNum )
client->ps.eFlags = flags;
// locate ent at a spawn point
-
ClientSpawn( ent, NULL, NULL, NULL );
trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname ) );
@@ -1337,7 +1314,6 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles
int maxAmmo, maxClips;
weapon_t weapon;
-
index = ent - g_entities;
client = ent->client;
@@ -1367,11 +1343,11 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles
if( client->sess.sessionTeam == TEAM_SPECTATOR )
{
if( teamLocal == PTE_NONE )
- spawnPoint = SelectSpectatorSpawnPoint( spawn_origin, spawn_angles );
+ spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles );
else if( teamLocal == PTE_ALIENS )
- spawnPoint = SelectAlienLockSpawnPoint( spawn_origin, spawn_angles );
+ spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles );
else if( teamLocal == PTE_HUMANS )
- spawnPoint = SelectHumanLockSpawnPoint( spawn_origin, spawn_angles );
+ spawnPoint = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles );
}
else
{
@@ -1427,6 +1403,10 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles
client->ps.persistant[ PERS_SPAWN_COUNT ]++;
client->ps.persistant[ PERS_TEAM ] = client->sess.sessionTeam;
+ // restore really persistant things
+ client->ps.persistant[ PERS_SCORE ] = client->pers.score;
+ client->ps.persistant[ PERS_CREDIT ] = client->pers.credit;
+
client->airOutTime = level.time + 12000;
trap_GetUserinfo( index, userinfo, sizeof( userinfo ) );
@@ -1550,7 +1530,7 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles
client->ps.pm_flags |= PMF_RESPAWNED;
trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd );
- SetClientViewAngle( ent, spawn_angles );
+ G_SetClientViewAngle( ent, spawn_angles );
if( !( client->sess.sessionTeam == TEAM_SPECTATOR ) )
{
diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c
index 6364c8bb..0878957d 100644
--- a/src/game/g_cmds.c
+++ b/src/game/g_cmds.c
@@ -246,10 +246,12 @@ void ScoreboardMessage( gentity_t *ent )
if( cl->pers.connected == CON_CONNECTING )
ping = -1;
+ else if( cl->sess.spectatorState == SPECTATOR_FOLLOW )
+ ping = cl->pers.ping < 999 ? cl->pers.ping : 999;
else
ping = cl->ps.ping < 999 ? cl->ps.ping : 999;
- if( cl->ps.stats[ STAT_HEALTH ] > 0 )
+ if( cl->sess.sessionTeam != TEAM_SPECTATOR )
{
weapon = cl->ps.weapon;
@@ -273,8 +275,8 @@ void ScoreboardMessage( gentity_t *ent )
}
Com_sprintf( entry, sizeof( entry ),
- " %d %d %d %d %d %d", level.sortedClients[ i ], cl->ps.persistant[ PERS_SCORE ],
- ping, ( level.time - cl->pers.enterTime ) / 60000, weapon, upgrade );
+ " %d %d %d %d %d %d", level.sortedClients[ i ], cl->pers.score, ping,
+ ( level.time - cl->pers.enterTime ) / 60000, weapon, upgrade );
j = strlen( entry );
@@ -363,7 +365,7 @@ void Cmd_Give_f( gentity_t *ent )
if( Q_stricmp( name, "poison" ) == 0 )
{
ent->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED;
- ent->client->lastBoostedTime = level.time;
+ ent->client->ps.stats[ STAT_MISC2 ] = BOOST_TIME;
}
if( give_all || Q_stricmp( name, "ammo" ) == 0 )
@@ -528,6 +530,9 @@ void G_LeaveTeam( gentity_t *self )
else
return;
+ // stop any following clients
+ G_StopFromFollowing( self );
+
G_TeamVote( self, qfalse );
for( i = 0; i < level.num_entities; i++ )
@@ -541,14 +546,6 @@ void G_LeaveTeam( gentity_t *self )
G_FreeEntity( ent );
if( ent->client && ent->client->pers.connected == CON_CONNECTED )
{
- // stop following clients
- if( ent->client->sess.sessionTeam == TEAM_SPECTATOR &&
- ent->client->sess.spectatorState == SPECTATOR_FOLLOW &&
- ent->client->sess.spectatorClient == self->client->ps.clientNum )
- {
- if( !G_FollowNewClient( ent, 1 ) )
- G_StopFollowing( ent );
- }
// cure poison
if( ent->client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED &&
ent->client->lastPoisonCloudedClient == self )
@@ -581,39 +578,15 @@ void G_ChangeTeam( gentity_t *ent, pTeam_t newTeam )
( ( oldTeam == PTE_HUMANS || oldTeam == PTE_ALIENS )
&& ( level.time - ent->client->pers.teamChangeTime ) > 60000 ) )
{
- if( oldTeam == PTE_NONE )
- {
- // ps.persistant[] from a spectator cannot be trusted
- ent->client->ps.persistant[ PERS_SCORE ] = ent->client->pers.savedScore;
- ent->client->ps.persistant[ PERS_CREDIT ] = ent->client->pers.savedCredit;
- }
- else if( oldTeam == PTE_ALIENS )
- {
- // always save in human credtis
- ent->client->ps.persistant[ PERS_CREDIT ] *=
- (float)FREEKILL_HUMAN / FREEKILL_ALIEN;
- }
-
- if( newTeam == PTE_NONE )
- {
- // save values before the client enters the spectator team and their
- // ps.persistant[] values become trashed
- ent->client->pers.savedScore = ent->client->ps.persistant[ PERS_SCORE ];
- ent->client->pers.savedCredit = ent->client->ps.persistant[ PERS_CREDIT ];
- }
+ if( oldTeam == PTE_ALIENS )
+ ent->client->pers.credit *= (float)FREEKILL_HUMAN / FREEKILL_ALIEN;
else if( newTeam == PTE_ALIENS )
- {
- // convert to alien currency
- ent->client->ps.persistant[ PERS_CREDIT ] *=
- (float)FREEKILL_ALIEN / FREEKILL_HUMAN;
+ ent->client->pers.credit *= (float)FREEKILL_ALIEN / FREEKILL_HUMAN;
}
- }
else
{
- ent->client->ps.persistant[ PERS_CREDIT ] = 0;
- ent->client->ps.persistant[ PERS_SCORE ] = 0;
- ent->client->pers.savedScore = 0;
- ent->client->pers.savedCredit = 0;
+ ent->client->pers.credit = 0;
+ ent->client->pers.score = 0;
}
ent->client->pers.classSelection = PCL_NONE;
@@ -741,7 +714,7 @@ void Cmd_Team_f( gentity_t *ent )
return;
//guard against build timer exploit
- if( oldteam != PTE_NONE &&
+ if( oldteam != PTE_NONE && ent->client->sess.sessionTeam != TEAM_SPECTATOR &&
( ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 ||
ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ||
BG_InventoryContainsWeapon( WP_HBUILD, ent->client->ps.stats ) ||
@@ -800,10 +773,10 @@ static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, cons
if( BG_ClientListTest( &other->client->sess.ignoreList, ent-g_entities ) )
ignore = qtrue;
- trap_SendServerCommand( other-g_entities, va( "%s \"%s%s%c%c%s\"",
+ trap_SendServerCommand( other-g_entities, va( "%s \"%s%s%c%c%s%s\"",
mode == SAY_TEAM ? "tchat" : "chat",
( ignore ) ? "[skipnotify]" : "",
- name, Q_COLOR_ESCAPE, color, message ) );
+ name, Q_COLOR_ESCAPE, color, message, S_COLOR_WHITE ) );
}
#define EC "\x19"
@@ -880,10 +853,6 @@ void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText )
return;
}
- // echo the text to the console
- if( g_dedicated.integer )
- G_Printf( "%s%s\n", name, text);
-
// send it to all the apropriate clients
for( j = 0; j < level.maxclients; j++ )
{
@@ -1158,6 +1127,8 @@ void Cmd_CallVote_f( gentity_t *ent )
trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE
" called a vote\n\"", ent->client->pers.netname ) );
+ G_Printf( "'%s' called a vote for '%s'\n", ent->client->pers.netname,
+ level.voteString ) ;
ent->client->pers.voteCount++;
@@ -1388,6 +1359,9 @@ void Cmd_CallTeamVote_f( gentity_t *ent )
G_TeamCommand( team, va( "print \"%s " S_COLOR_WHITE "called a team vote\n\"",
ent->client->pers.netname ) );
+ G_Printf( "'%s' called a teamvote for '%s'\n", ent->client->pers.netname,
+ level.teamVoteString[ cs_offset ] ) ;
+
// start the voting, the caller autoamtically votes yes
level.teamVoteTime[ cs_offset ] = level.time;
level.teamVoteYes[ cs_offset ] = 1;
@@ -1552,9 +1526,7 @@ void Cmd_Class_f( gentity_t *ent )
int clientNum;
int i;
vec3_t infestOrigin;
- int allowedClasses[ PCL_NUM_CLASSES ];
- int numClasses = 0;
- pClass_t currentClass = ent->client->ps.stats[ STAT_PCLASS ];
+ pClass_t currentClass = ent->client->pers.classSelection;
pClass_t newClass;
int numLevels;
int entityList[ MAX_GENTITIES ];
@@ -1563,30 +1535,92 @@ void Cmd_Class_f( gentity_t *ent )
int num;
gentity_t *other;
- if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 )
- return;
-
clientNum = ent->client - level.clients;
trap_Argv( 1, s, sizeof( s ) );
+ newClass = BG_FindClassNumForName( s );
- if( BG_ClassIsAllowed( PCL_ALIEN_BUILDER0 ) )
- allowedClasses[ numClasses++ ] = PCL_ALIEN_BUILDER0;
+ if( ent->client->sess.sessionTeam == TEAM_SPECTATOR )
+ {
+ if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
+ G_StopFollowing( ent );
+ if( ent->client->pers.teamSelection == PTE_ALIENS )
+ {
+ if( newClass != PCL_ALIEN_BUILDER0 &&
+ newClass != PCL_ALIEN_BUILDER0_UPG &&
+ newClass != PCL_ALIEN_LEVEL0 )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"You cannot spawn with class %s\n\"", s ) );
+ return;
+ }
- if( BG_ClassIsAllowed( PCL_ALIEN_BUILDER0_UPG ) &&
- BG_FindStagesForClass( PCL_ALIEN_BUILDER0_UPG, g_alienStage.integer ) )
- allowedClasses[ numClasses++ ] = PCL_ALIEN_BUILDER0_UPG;
+ if( !BG_ClassIsAllowed( newClass ) )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"Class %s is not allowed\n\"", s ) );
+ return;
+ }
+
+ if( !BG_FindStagesForClass( newClass, g_alienStage.integer ) )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ va( "print \"Class %s not allowed at stage %d\n\"",
+ s, g_alienStage.integer ) );
+ return;
+ }
+
+ // spawn from an egg
+ if( G_PushSpawnQueue( &level.alienSpawnQueue, clientNum ) )
+ {
+ ent->client->pers.classSelection = newClass;
+ ent->client->ps.stats[ STAT_PCLASS ] = newClass;
+ }
+ }
+ else if( ent->client->pers.teamSelection == PTE_HUMANS )
+ {
+ //set the item to spawn with
+ if( !Q_stricmp( s, BG_FindNameForWeapon( WP_MACHINEGUN ) ) &&
+ BG_WeaponIsAllowed( WP_MACHINEGUN ) )
+ {
+ ent->client->pers.humanItemSelection = WP_MACHINEGUN;
+ }
+ else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD ) ) &&
+ BG_WeaponIsAllowed( WP_HBUILD ) )
+ {
+ ent->client->pers.humanItemSelection = WP_HBUILD;
+ }
+ else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD2 ) ) &&
+ BG_WeaponIsAllowed( WP_HBUILD2 ) &&
+ BG_FindStagesForWeapon( WP_HBUILD2, g_humanStage.integer ) )
+ {
+ ent->client->pers.humanItemSelection = WP_HBUILD2;
+ }
+ else
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"Unknown starting item\n\"" );
+ return;
+ }
+ // spawn from a telenode
+ if( G_PushSpawnQueue( &level.humanSpawnQueue, clientNum ) )
+ {
+ ent->client->pers.classSelection = PCL_HUMAN;
+ ent->client->ps.stats[ STAT_PCLASS ] = PCL_HUMAN;
+ }
+ }
+ return;
+ }
- if( BG_ClassIsAllowed( PCL_ALIEN_LEVEL0 ) )
- allowedClasses[ numClasses++ ] = PCL_ALIEN_LEVEL0;
+ if( ent->health <= 0 )
+ return;
if( ent->client->pers.teamSelection == PTE_ALIENS &&
!( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) &&
!( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) )
{
- newClass = BG_FindClassNumForName( s );
if( newClass == PCL_NONE )
{
- trap_SendServerCommand( ent-g_entities, va( "print \"Unknown class\n\"" ) );
+ trap_SendServerCommand( ent-g_entities, "print \"Unknown class\n\"" );
return;
}
@@ -1596,7 +1630,8 @@ void Cmd_Class_f( gentity_t *ent )
if( ( ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) ||
( ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) )
{
- trap_SendServerCommand( ent-g_entities, va( "print \"You cannot evolve while wallwalking\n\"" ) );
+ trap_SendServerCommand( ent-g_entities,
+ "print \"You cannot evolve while wallwalking\n\"" );
return;
}
@@ -1624,7 +1659,8 @@ void Cmd_Class_f( gentity_t *ent )
}
//guard against selling the HBUILD weapons exploit
- if( ( currentClass == PCL_ALIEN_BUILDER0 ||
+ if( ent->client->sess.sessionTeam != TEAM_SPECTATOR &&
+ ( currentClass == PCL_ALIEN_BUILDER0 ||
currentClass == PCL_ALIEN_BUILDER0_UPG ) &&
ent->client->ps.stats[ STAT_MISC ] > 0 )
{
@@ -1662,7 +1698,7 @@ void Cmd_Class_f( gentity_t *ent )
else
{
trap_SendServerCommand( ent-g_entities,
- va( "print \"You cannot evolve from your current class\n\"" ) );
+ "print \"You cannot evolve from your current class\n\"" );
return;
}
}
@@ -1672,53 +1708,13 @@ void Cmd_Class_f( gentity_t *ent )
return;
}
}
- else
- {
- //spawning from an egg
- for( i = 0; i < numClasses; i++ )
- {
- if( allowedClasses[ i ] == newClass &&
- BG_FindStagesForClass( newClass, g_alienStage.integer ) &&
- BG_ClassIsAllowed( newClass ) )
- {
- ent->client->pers.classSelection =
- ent->client->ps.stats[ STAT_PCLASS ] = newClass;
- G_PushSpawnQueue( &level.alienSpawnQueue, clientNum );
- return;
- }
- }
- trap_SendServerCommand( ent-g_entities, va( "print \"You cannot spawn as this class\n\"" ) );
- return;
- }
}
else if( ent->client->pers.teamSelection == PTE_HUMANS )
{
//humans cannot use this command whilst alive
- if( ent->client->pers.classSelection != PCL_NONE )
- {
- trap_SendServerCommand( ent-g_entities, va( "print \"You must be dead to use the class command\n\"" ) );
- return;
- }
-
- ent->client->pers.classSelection =
- ent->client->ps.stats[ STAT_PCLASS ] = PCL_HUMAN;
-
- //set the item to spawn with
- if( !Q_stricmp( s, BG_FindNameForWeapon( WP_MACHINEGUN ) ) && BG_WeaponIsAllowed( WP_MACHINEGUN ) )
- ent->client->pers.humanItemSelection = WP_MACHINEGUN;
- else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD ) ) && BG_WeaponIsAllowed( WP_HBUILD ) )
- ent->client->pers.humanItemSelection = WP_HBUILD;
- else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD2 ) ) && BG_WeaponIsAllowed( WP_HBUILD2 ) &&
- BG_FindStagesForWeapon( WP_HBUILD2, g_humanStage.integer ) )
- ent->client->pers.humanItemSelection = WP_HBUILD2;
- else
- {
- ent->client->pers.classSelection = PCL_NONE;
- trap_SendServerCommand( ent-g_entities, va( "print \"Unknown starting item\n\"" ) );
+ trap_SendServerCommand( ent-g_entities,
+ "print \"You must be dead to use the class command\n\"" );
return;
- }
-
- G_PushSpawnQueue( &level.humanSpawnQueue, clientNum );
}
}
@@ -1764,6 +1760,13 @@ void Cmd_Destroy_f( gentity_t *ent )
( ( ent->client->ps.weapon >= WP_ABUILD ) &&
( ent->client->ps.weapon <= WP_HBUILD ) ) )
{
+ // Always let the builder prevent the explosion
+ if( traceEnt->health <= 0 )
+ {
+ G_FreeEntity( traceEnt );
+ return;
+ }
+
// Cancel deconstruction
if( g_markDeconstruct.integer && traceEnt->deconstruct )
{
@@ -1793,19 +1796,18 @@ void Cmd_Destroy_f( gentity_t *ent )
return;
// Don't allow destruction of buildables that cannot be rebuilt
- if( G_TimeTilSuddenDeath( ) <= 0 &&
- BG_FindBuildPointsForBuildable( traceEnt->s.modelindex ) )
+ if( G_TimeTilSuddenDeath( ) <= 0 )
{
return;
}
- if( ent->client->ps.stats[ STAT_MISC ] > 0 )
+ if( !g_markDeconstruct.integer && ent->client->ps.stats[ STAT_MISC ] > 0 )
{
G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum );
return;
}
- if( traceEnt->health > 0 )
+ if( g_markDeconstruct.integer )
{
if( g_markDeconstruct.integer )
{
@@ -1832,14 +1834,13 @@ void Cmd_Destroy_f( gentity_t *ent )
if( !g_cheats.integer )
ent->client->ps.stats[ STAT_MISC ] +=
- BG_FindBuildDelayForWeapon( ent->s.weapon ) >> 2;
+ BG_FindBuildTimeForBuildable( traceEnt->s.modelindex );
}
}
}
}
}
-
/*
=================
Cmd_ActivateItem_f
@@ -2337,9 +2338,24 @@ void Cmd_Build_f( gentity_t *ent )
return;
}
+ if( ent->client->pers.teamSelection == level.surrenderTeam )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"Building has been denied to traitorous cowards\n\"" );
+ return;
+ }
+
trap_Argv( 1, s, sizeof( s ) );
buildable = BG_FindBuildNumForName( s );
+
+ if( G_TimeTilSuddenDeath( ) <= 0 )
+ {
+ trap_SendServerCommand( ent-g_entities,
+ "print \"Building is not allowed during Sudden Death\n\"" );
+ return;
+ }
+
team = ent->client->ps.stats[ STAT_PTEAM ];
if( buildable != BA_NONE &&
@@ -2363,6 +2379,8 @@ void Cmd_Build_f( gentity_t *ent )
case IBE_NOROOM:
case IBE_NORMAL:
case IBE_HOVELEXIT:
+ case IBE_REPEATER:
+ case IBE_NOCREEP:
ent->client->ps.stats[ STAT_BUILDABLE ] = ( buildable | SB_VALID_TOGGLEBIT );
break;
@@ -2382,18 +2400,10 @@ void Cmd_Build_f( gentity_t *ent )
G_TriggerMenu( ent->client->ps.clientNum, MN_H_REACTOR );
break;
- case IBE_REPEATER:
- G_TriggerMenu( ent->client->ps.clientNum, MN_H_REPEATER );
- break;
-
case IBE_NOPOWER:
G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOPOWER );
break;
- case IBE_NOCREEP:
- G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOCREEP );
- break;
-
case IBE_NODCC:
G_TriggerMenu( ent->client->ps.clientNum, MN_H_NODCC );
break;
@@ -2406,34 +2416,39 @@ void Cmd_Build_f( gentity_t *ent )
trap_SendServerCommand( ent-g_entities, va( "print \"Cannot build this item\n\"" ) );
}
-
/*
=================
-Cmd_Boost_f
+Cmd_Reload_f
=================
*/
-void Cmd_Boost_f( gentity_t *ent )
+void Cmd_Reload_f( gentity_t *ent )
{
- if( BG_InventoryContainsUpgrade( UP_JETPACK, ent->client->ps.stats ) &&
- BG_UpgradeIsActive( UP_JETPACK, ent->client->ps.stats ) )
- return;
-
- if( ent->client->pers.cmd.buttons & BUTTON_WALKING )
- return;
-
- if( ent->client->ps.stats[ STAT_STAMINA ] > 0 )
- ent->client->ps.stats[ STAT_STATE ] |= SS_SPEEDBOOST;
+ if( ent->client->ps.weaponstate != WEAPON_RELOADING )
+ ent->client->ps.pm_flags |= PMF_WEAPON_RELOAD;
}
/*
=================
-Cmd_Reload_f
+G_StopFromFollowing
+
+stops any other clients from following this one
+called when a player leaves a team or dies
=================
*/
-void Cmd_Reload_f( gentity_t *ent )
+void G_StopFromFollowing( gentity_t *ent )
{
- if( ent->client->ps.weaponstate != WEAPON_RELOADING )
- ent->client->ps.pm_flags |= PMF_WEAPON_RELOAD;
+ int i;
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ if( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR &&
+ level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW &&
+ level.clients[ i ].sess.spectatorClient == ent->client->ps.clientNum )
+ {
+ if( !G_FollowNewClient( &g_entities[ i ], 1 ) )
+ G_StopFollowing( &g_entities[ i ] );
+ }
+ }
}
/*
@@ -2448,10 +2463,29 @@ void G_StopFollowing( gentity_t *ent )
{
ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR;
ent->client->sess.sessionTeam = TEAM_SPECTATOR;
- ent->client->sess.spectatorState = SPECTATOR_FREE;
+ ent->client->ps.stats[ STAT_PTEAM ] = ent->client->pers.teamSelection;
+
+ if( ent->client->pers.teamSelection == PTE_NONE )
+ {
+ ent->client->sess.spectatorState = SPECTATOR_FREE;
+ }
+ else
+ {
+ vec3_t spawn_origin, spawn_angles;
+
+ ent->client->sess.spectatorState = SPECTATOR_LOCKED;
+
+ if( ent->client->pers.teamSelection == PTE_ALIENS )
+ G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles );
+ else if( ent->client->pers.teamSelection == PTE_HUMANS )
+ G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles );
+
+ G_SetOrigin( ent, spawn_origin );
+ VectorCopy( spawn_origin, ent->client->ps.origin );
+ G_SetClientViewAngle( ent, spawn_angles );
+ }
ent->client->sess.spectatorClient = -1;
ent->client->ps.pm_flags &= ~PMF_FOLLOW;
- ent->client->ps.stats[ STAT_PTEAM ] = PTE_NONE;
ent->client->ps.stats[ STAT_STATE ] &= ~SS_WALLCLIMBING;
ent->client->ps.stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING;
@@ -2519,6 +2553,14 @@ qboolean G_FollowNewClient( gentity_t *ent, int dir )
if( level.clients[ clientnum ].sess.sessionTeam == TEAM_SPECTATOR )
continue;
+ // can only follow teammates when dead and on a team
+ if( ent->client->pers.teamSelection != PTE_NONE &&
+ ( level.clients[ clientnum ].pers.teamSelection !=
+ ent->client->pers.teamSelection ) )
+ {
+ continue;
+ }
+
// this is good, we can use it
ent->client->sess.spectatorClient = clientnum;
ent->client->sess.spectatorState = SPECTATOR_FOLLOW;
@@ -2557,8 +2599,7 @@ void Cmd_Follow_f( gentity_t *ent )
{
G_ToggleFollow( ent );
}
- else if( ent->client->sess.spectatorState == SPECTATOR_FREE ||
- ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
+ else
{
trap_Argv( 1, arg, sizeof( arg ) );
if( G_ClientNumbersFromString( arg, pids, MAX_CLIENTS ) == 1 )
@@ -2582,9 +2623,17 @@ void Cmd_Follow_f( gentity_t *ent )
return;
// can't follow another spectator
- if( level.clients[ i ].pers.teamSelection == PTE_NONE )
+ if( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR )
return;
+ // can only follow teammates when dead and on a team
+ if( ent->client->pers.teamSelection != PTE_NONE &&
+ ( level.clients[ i ].pers.teamSelection !=
+ ent->client->pers.teamSelection ) )
+ {
+ return;
+ }
+
ent->client->sess.spectatorState = SPECTATOR_FOLLOW;
ent->client->sess.spectatorClient = i;
}
@@ -2605,6 +2654,8 @@ void Cmd_FollowCycle_f( gentity_t *ent )
dir = -1;
// won't work unless spectating
+ if( ent->client->sess.sessionTeam != TEAM_SPECTATOR )
+ return;
if( ent->client->sess.spectatorState == SPECTATOR_NOT )
return;
@@ -2807,9 +2858,9 @@ commands_t cmds[ ] = {
{ "ptrcverify", 0, Cmd_PTRCVerify_f },
{ "ptrcrestore", 0, Cmd_PTRCRestore_f },
- { "follow", CMD_NOTEAM, Cmd_Follow_f },
- { "follownext", CMD_NOTEAM, Cmd_FollowCycle_f },
- { "followprev", CMD_NOTEAM, Cmd_FollowCycle_f },
+ { "follow", CMD_SPEC, Cmd_Follow_f },
+ { "follownext", CMD_SPEC, Cmd_FollowCycle_f },
+ { "followprev", CMD_SPEC, Cmd_FollowCycle_f },
{ "where", CMD_TEAM, Cmd_Where_f },
{ "teamvote", CMD_TEAM, Cmd_TeamVote_f },
@@ -2824,7 +2875,8 @@ commands_t cmds[ ] = {
{ "itemdeact", CMD_HUMAN|CMD_LIVING, Cmd_DeActivateItem_f },
{ "itemtoggle", CMD_HUMAN|CMD_LIVING, Cmd_ToggleItem_f },
{ "reload", CMD_HUMAN|CMD_LIVING, Cmd_Reload_f },
- { "boost", CMD_HUMAN|CMD_LIVING, Cmd_Boost_f }
+
+ { "test", 0, Cmd_Test_f }
};
static int numCmds = sizeof( cmds ) / sizeof( cmds[ 0 ] );
@@ -2881,11 +2933,11 @@ void ClientCommand( int clientNum )
return;
}
- if( cmds[ i ].cmdFlags & CMD_NOTEAM &&
- ent->client->pers.teamSelection != PTE_NONE )
+ if( cmds[ i ].cmdFlags & CMD_SPEC &&
+ ent->client->sess.sessionTeam != TEAM_SPECTATOR )
{
trap_SendServerCommand( clientNum,
- "print \"Cannot use this command when on a team\n\"" );
+ "print \"You can only use this command when spectating\n\"" );
return;
}
@@ -3163,3 +3215,7 @@ void G_PrivateMessage( gentity_t *ent )
}
}
+void Cmd_Test_f( gentity_t *ent )
+{
+ G_Printf("%d\n", ent->client->pers.score);
+}
diff --git a/src/game/g_combat.c b/src/game/g_combat.c
index 6c8dcc2c..cf068fc0 100644
--- a/src/game/g_combat.c
+++ b/src/game/g_combat.c
@@ -108,7 +108,8 @@ char *modNames[ ] =
"MOD_LEVEL2_CLAW",
"MOD_LEVEL2_ZAP",
"MOD_LEVEL4_CLAW",
- "MOD_LEVEL4_CHARGE",
+ "MOD_LEVEL4_TRAMPLE",
+ "MOD_LEVEL4_CRUSH",
"MOD_SLOWBLOB",
"MOD_POISON",
@@ -139,7 +140,6 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int
float totalDamage = 0.0f;
gentity_t *player;
-
if( self->client->ps.pm_type == PM_DEAD )
return;
@@ -147,16 +147,7 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int
return;
// stop any following clients
- for( i = 0; i < level.maxclients; i++ )
- {
- if( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR &&
- level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW &&
- level.clients[ i ].sess.spectatorClient == self->client->ps.clientNum )
- {
- if( !G_FollowNewClient( &g_entities[ i ], 1 ) )
- G_StopFollowing( &g_entities[ i ] );
- }
- }
+ G_StopFromFollowing( self );
self->client->ps.pm_type = PM_DEAD;
self->suicideTime = 0;
@@ -1008,6 +999,14 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
}
}
+ // don't do friendly fire on movement attacks
+ if( ( mod == MOD_LEVEL4_TRAMPLE || mod == MOD_LEVEL3_POUNCE ||
+ mod == MOD_LEVEL4_CRUSH ) &&
+ targ->s.eType == ET_BUILDABLE && targ->biteam == BIT_ALIENS )
+ {
+ return;
+ }
+
// check for completely getting out of the damage
if( !( dflags & DAMAGE_NO_PROTECTION ) )
{
@@ -1016,7 +1015,24 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
// if the attacker was on the same team
if( targ != attacker && OnSameTeam( targ, attacker ) )
{
- if( !g_friendlyFire.integer )
+ // don't do friendly fire on movement attacks
+ if( mod == MOD_LEVEL4_TRAMPLE || mod == MOD_LEVEL3_POUNCE ||
+ mod == MOD_LEVEL4_CRUSH )
+ return;
+
+ if( g_dretchPunt.integer &&
+ targ->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL0 )
+ {
+ vec3_t dir, push;
+
+ VectorSubtract( targ->r.currentOrigin, attacker->r.currentOrigin, dir );
+ VectorNormalizeFast( dir );
+ VectorScale( dir, ( damage * 10.0f ), push );
+ push[2] = 64.0f;
+ VectorAdd( targ->client->ps.velocity, push, targ->client->ps.velocity );
+ return;
+ }
+ else if( !g_friendlyFire.integer )
{
if( !g_friendlyFireHumans.integer
&& targ->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
@@ -1031,13 +1047,22 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
}
}
- // If target is buildable on the same team as the attacking client
- if( targ->s.eType == ET_BUILDABLE && attacker->client &&
- targ->biteam == attacker->client->pers.teamSelection )
- {
- if( !g_friendlyBuildableFire.integer )
- return;
- }
+ if( targ->s.eType == ET_BUILDABLE && attacker->client )
+ {
+ if( targ->biteam == attacker->client->pers.teamSelection )
+ {
+ if( !g_friendlyBuildableFire.integer )
+ return;
+ }
+
+ // base is under attack warning if DCC'd
+ if( targ->biteam == BIT_HUMANS && G_FindDCC( targ ) &&
+ level.time > level.humanBaseAttackTimer )
+ {
+ level.humanBaseAttackTimer = level.time + DC_ATTACK_PERIOD;
+ G_BroadcastEvent( EV_DCC_ATTACK, 0 );
+ }
+ }
// check for godmode
if ( targ->flags & FL_GODMODE )
@@ -1092,9 +1117,9 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
//if boosted poison every attack
if( attacker->client && attacker->client->ps.stats[ STAT_STATE ] & SS_BOOSTED )
{
- if( !( targ->client->ps.stats[ STAT_STATE ] & SS_POISONED ) &&
- !BG_InventoryContainsUpgrade( UP_BATTLESUIT, targ->client->ps.stats ) &&
- mod != MOD_LEVEL2_ZAP &&
+ if( targ->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS &&
+ mod != MOD_LEVEL2_ZAP && mod != MOD_POISON &&
+ mod != MOD_LEVEL1_PCLOUD &&
targ->client->poisonImmunityTime < level.time )
{
targ->client->ps.stats[ STAT_STATE ] |= SS_POISONED;
diff --git a/src/game/g_local.h b/src/game/g_local.h
index c9538bbd..14673f2f 100644
--- a/src/game/g_local.h
+++ b/src/game/g_local.h
@@ -172,7 +172,6 @@ struct gentity_s
int splashRadius;
int methodOfDeath;
int splashMethodOfDeath;
- int chargeRepeat;
int count;
@@ -197,12 +196,13 @@ struct gentity_s
int biteam; // buildable item team
gentity_t *parentNode; // for creep and defence/spawn dependencies
qboolean active; // for power repeater, but could be useful elsewhere
+ qboolean locked; // used for turret tracking
qboolean powered; // for human buildables
int builtBy; // clientNum of person that built this
- gentity_t *dccNode; // controlling dcc
gentity_t *overmindNode; // controlling overmind
- qboolean dcced; // controlled by a dcc or not?
+ int dcc; // number of controlling dccs
qboolean spawned; // whether or not this buildable has finished spawning
+ int shrunkTime; // time when a barricade shrunk or zero
int buildTime; // when this buildable was built
int time1000; // timer evaluated every second
qboolean deconstruct; // deconstruct if no BP left
@@ -222,6 +222,8 @@ struct gentity_s
gentity_t *targeted; // true if the player is currently a valid target of a turret
vec3_t turretAim; // aim vector for turrets
+ vec3_t turretAimRate; // track turn speed for norfenturrets
+ int turretSpinupTime; // spinup delay for norfenturrets
vec4_t animation; // animated map objects
@@ -239,6 +241,14 @@ struct gentity_s
int suicideTime; // when the client will suicide
int lastDamageTime;
+ int lastRegenTime;
+
+ qboolean zapping; // adv maurader is zapping
+ qboolean wasZapping; // adv maurader was zapping
+ int zapTargets[ LEVEL2_AREAZAP_MAX_TARGETS ];
+ float zapDmg; // keep track of damage
+
+ qboolean ownerClear; // used for missle tracking
};
typedef enum
@@ -342,9 +352,10 @@ typedef struct
int nameChangeTime;
int nameChanges;
- // used to save persistant[] values while in SPECTATOR_FOLLOW mode
- int savedScore;
- int savedCredit;
+ // used to save playerState_t values while in SPECTATOR_FOLLOW mode
+ int score;
+ int credit;
+ int ping;
// votes
qboolean vote;
@@ -441,14 +452,11 @@ struct gclient_s
int grabExpiryTime;
int lastLockTime;
int lastSlowTime;
- int lastBoostedTime;
int lastMedKitTime;
int medKitHealthToRestore;
int medKitIncrementTime;
int lastCreepSlowTime; // time until creep can be removed
- qboolean allowedToPounce;
-
qboolean charging;
vec3_t hovelOrigin; // player origin before entering hovel
@@ -460,6 +468,11 @@ struct gclient_s
unlagged_t unlaggedCalc;
int unlaggedTime;
+ int lcannonStartTime;
+
+ // Tyrant crush
+ int forceCrouchTime;
+ int lastCrushTime;
};
@@ -477,7 +490,8 @@ void G_InitSpawnQueue( spawnQueue_t *sq );
int G_GetSpawnQueueLength( spawnQueue_t *sq );
int G_PopSpawnQueue( spawnQueue_t *sq );
int G_PeekSpawnQueue( spawnQueue_t *sq );
-void G_PushSpawnQueue( spawnQueue_t *sq, int clientNum );
+qboolean G_SearchSpawnQueue( spawnQueue_t *sq, int clientNum );
+qboolean G_PushSpawnQueue( spawnQueue_t *sq, int clientNum );
qboolean G_RemoveFromSpawnQueue( spawnQueue_t *sq, int clientNum );
int G_GetPosInSpawnQueue( spawnQueue_t *sq, int clientNum );
@@ -542,6 +556,7 @@ typedef struct
int framenum;
int time; // in msec
int previousTime; // so movers can back up when blocked
+ int frameMsec; // trap_Milliseconds() at end frame
int startTime; // level.time the map was started
@@ -661,7 +676,7 @@ typedef struct
#define CMD_CHEAT 0x01
#define CMD_MESSAGE 0x02 // sends message to others (skip when muted)
#define CMD_TEAM 0x04 // must be on a team
-#define CMD_NOTEAM 0x08 // must not be on a team
+#define CMD_SPEC 0x08 // must be in spectator mode
#define CMD_ALIEN 0x10
#define CMD_HUMAN 0x20
#define CMD_LIVING 0x40
@@ -689,6 +704,7 @@ char *G_NewString( const char *string );
// g_cmds.c
//
void Cmd_Score_f( gentity_t *ent );
+void G_StopFromFollowing( gentity_t *ent );
void G_StopFollowing( gentity_t *ent );
qboolean G_FollowNewClient( gentity_t *ent, int dir );
void G_ToggleFollow( gentity_t *ent );
@@ -702,6 +718,7 @@ void G_LeaveTeam( gentity_t *self );
void G_ChangeTeam( gentity_t *ent, pTeam_t newTeam );
void G_SanitiseName( char *in, char *out );
void G_PrivateMessage( gentity_t *ent );
+void Cmd_Test_f( gentity_t *ent );
//
// g_physics.c
@@ -747,7 +764,9 @@ gentity_t *G_CheckSpawnPoint( int spawnNum, vec3_t origin, vec3_t normal
buildable_t G_IsPowered( vec3_t origin );
qboolean G_IsDCCBuilt( void );
+int G_FindDCC( gentity_t *self );
qboolean G_IsOvermindBuilt( void );
+qboolean G_FindCreep( gentity_t *self );
void G_BuildableThink( gentity_t *ent, int msec );
qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable );
@@ -840,7 +859,7 @@ void G_RunMissile( gentity_t *ent );
gentity_t *fire_flamer( gentity_t *self, vec3_t start, vec3_t aimdir );
gentity_t *fire_blaster( gentity_t *self, vec3_t start, vec3_t dir );
gentity_t *fire_pulseRifle( gentity_t *self, vec3_t start, vec3_t dir );
-gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, int damage, int radius );
+gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, int damage, int radius, int speed );
gentity_t *fire_lockblob( gentity_t *self, vec3_t start, vec3_t dir );
gentity_t *fire_paraLockBlob( gentity_t *self, vec3_t start, vec3_t dir );
gentity_t *fire_slowBlob( gentity_t *self, vec3_t start, vec3_t dir );
@@ -896,8 +915,9 @@ void SnapVectorTowards( vec3_t v, vec3_t to );
qboolean CheckVenomAttack( gentity_t *ent );
void CheckGrabAttack( gentity_t *ent );
qboolean CheckPounceAttack( gentity_t *ent );
-void ChargeAttack( gentity_t *ent, gentity_t *victim );
-void G_UpdateZaps( int msec );
+void G_ChargeAttack( gentity_t *ent, gentity_t *victim );
+void G_CrushAttack( gentity_t *ent, gentity_t *victim, float sec );
+void G_UpdateZaps( gentity_t *ent );
//
@@ -905,9 +925,11 @@ void G_UpdateZaps( int msec );
//
void G_AddCreditToClient( gclient_t *client, short credit, qboolean cap );
team_t TeamCount( int ignoreClientNum, int team );
-void SetClientViewAngle( gentity_t *ent, vec3_t angle );
-gentity_t *SelectTremulousSpawnPoint( pTeam_t team, vec3_t preference, vec3_t origin, vec3_t angles );
-gentity_t *SelectSpawnPoint( vec3_t avoidPoint, vec3_t origin, vec3_t angles );
+void G_SetClientViewAngle( gentity_t *ent, vec3_t angle );
+gentity_t *G_SelectTremulousSpawnPoint( pTeam_t team, vec3_t preference, vec3_t origin, vec3_t angles );
+gentity_t *G_SelectSpawnPoint( vec3_t avoidPoint, vec3_t origin, vec3_t angles );
+gentity_t *G_SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles );
+gentity_t *G_SelectHumanLockSpawnPoint( vec3_t origin, vec3_t angles );
void SpawnCorpse( gentity_t *ent );
void respawn( gentity_t *ent );
void BeginIntermission( void );
@@ -1183,6 +1205,8 @@ extern vmCvar_t g_adminParseSay;
extern vmCvar_t g_adminNameProtect;
extern vmCvar_t g_adminTempBan;
+extern vmCvar_t g_dretchPunt;
+
extern vmCvar_t g_privateMessages;
void trap_Printf( const char *fmt );
diff --git a/src/game/g_main.c b/src/game/g_main.c
index dd258c13..82647557 100644
--- a/src/game/g_main.c
+++ b/src/game/g_main.c
@@ -128,6 +128,8 @@ vmCvar_t g_adminParseSay;
vmCvar_t g_adminNameProtect;
vmCvar_t g_adminTempBan;
+vmCvar_t g_dretchPunt;
+
vmCvar_t g_privateMessages;
vmCvar_t g_tag;
@@ -157,7 +159,7 @@ static cvarTable_t gameCvarTable[ ] =
{ &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse },
- { &g_friendlyFire, "g_friendlyFire", "0", CVAR_ARCHIVE, 0, qtrue },
+ { &g_friendlyFire, "g_friendlyFire", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue },
{ &g_friendlyFireAliens, "g_friendlyFireAliens", "0", CVAR_ARCHIVE, 0, qtrue },
{ &g_friendlyFireHumans, "g_friendlyFireHumans", "0", CVAR_ARCHIVE, 0, qtrue },
{ &g_friendlyBuildableFire, "g_friendlyBuildableFire", "0", CVAR_ARCHIVE, 0, qtrue },
@@ -234,6 +236,7 @@ static cvarTable_t gameCvarTable[ ] =
{ &g_currentMap, "g_currentMap", "0", 0, 0, qfalse },
{ &g_initialMapRotation, "g_initialMapRotation", "", CVAR_ARCHIVE, 0, qfalse },
{ &g_shove, "g_shove", "0.0", CVAR_ARCHIVE, 0, qfalse },
+
{ &g_mapConfigs, "g_mapConfigs", "", CVAR_ARCHIVE, 0, qfalse },
{ NULL, "g_mapConfigsLoaded", "0", CVAR_ROM, 0, qfalse },
@@ -246,6 +249,8 @@ static cvarTable_t gameCvarTable[ ] =
{ &g_adminNameProtect, "g_adminNameProtect", "1", CVAR_ARCHIVE, 0, qfalse },
{ &g_adminTempBan, "g_adminTempBan", "120", CVAR_ARCHIVE, 0, qfalse },
+ { &g_dretchPunt, "g_dretchPunt", "0", CVAR_ARCHIVE, 0, qfalse },
+
{ &g_privateMessages, "g_privateMessages", "1", CVAR_ARCHIVE, 0, qfalse },
{ &g_tag, "g_tag", "main", CVAR_INIT, 0, qfalse },
@@ -739,9 +744,9 @@ int QDECL SortRanks( const void *a, const void *b )
cb = &level.clients[ *(int *)b ];
// then sort by score
- if( ca->ps.persistant[ PERS_SCORE ] > cb->ps.persistant[ PERS_SCORE ] )
+ if( ca->pers.score > cb->pers.score )
return -1;
- else if( ca->ps.persistant[ PERS_SCORE ] < cb->ps.persistant[ PERS_SCORE ] )
+ else if( ca->pers.score < cb->pers.score )
return 1;
else
return 0;
@@ -823,17 +828,42 @@ int G_PeekSpawnQueue( spawnQueue_t *sq )
/*
============
+G_SearchSpawnQueue
+
+Look to see if clientNum is already in the spawnQueue
+============
+*/
+qboolean G_SearchSpawnQueue( spawnQueue_t *sq, int clientNum )
+{
+ int i;
+
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ if( sq->clients[ i ] == clientNum )
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+/*
+============
G_PushSpawnQueue
Add an element to the back of the spawn queue
============
*/
-void G_PushSpawnQueue( spawnQueue_t *sq, int clientNum )
+qboolean G_PushSpawnQueue( spawnQueue_t *sq, int clientNum )
{
+ // don't add the same client more than once
+ if( G_SearchSpawnQueue( sq, clientNum ) )
+ return qfalse;
+
sq->back = QUEUE_PLUS1( sq->back );
sq->clients[ sq->back ] = clientNum;
g_entities[ clientNum ].client->ps.pm_flags |= PMF_QUEUED;
+ return qtrue;
}
/*
@@ -967,7 +997,7 @@ void G_SpawnClients( pTeam_t team )
clientNum = G_PeekSpawnQueue( sq );
ent = &g_entities[ clientNum ];
- if( ( spawn = SelectTremulousSpawnPoint( team,
+ if( ( spawn = G_SelectTremulousSpawnPoint( team,
ent->client->pers.lastDeathLocation,
spawn_origin, spawn_angles ) ) )
{
@@ -1272,10 +1302,6 @@ and team change.
void CalculateRanks( void )
{
int i;
- int rank;
- int score;
- int newScore;
- gclient_t *cl;
char P[ MAX_CLIENTS + 1 ] = {""};
int ff = 0;
@@ -1340,30 +1366,6 @@ void CalculateRanks( void )
qsort( level.sortedClients, level.numConnectedClients,
sizeof( level.sortedClients[ 0 ] ), SortRanks );
- // set the rank value for all clients that are connected and not spectators
- rank = -1;
- score = 0;
- for( i = 0; i < level.numPlayingClients; i++ )
- {
- cl = &level.clients[ level.sortedClients[ i ] ];
- newScore = cl->ps.persistant[ PERS_SCORE ];
-
- if( i == 0 || newScore != score )
- {
- rank = i;
- // assume we aren't tied until the next client is checked
- level.clients[ level.sortedClients[ i ] ].ps.persistant[ PERS_RANK ] = rank;
- }
- else
- {
- // we are tied with the previous client
- level.clients[ level.sortedClients[ i - 1 ] ].ps.persistant[ PERS_RANK ] = rank;
- level.clients[ level.sortedClients[ i ] ].ps.persistant[ PERS_RANK ] = rank;
- }
-
- score = newScore;
- }
-
// see if it is time to end the level
CheckExitRules( );
@@ -1449,7 +1451,7 @@ void FindIntermissionPoint( void )
if( !ent )
{ // the map creator forgot to put in an intermission point...
- SelectSpawnPoint( vec3_origin, level.intermission_origin, level.intermission_angle );
+ G_SelectSpawnPoint( vec3_origin, level.intermission_origin, level.intermission_angle );
}
else
{
@@ -2358,7 +2360,6 @@ void G_RunFrame( int levelTime )
G_SpawnClients( PTE_ALIENS );
G_SpawnClients( PTE_HUMANS );
G_CalculateAvgPlayers( );
- G_UpdateZaps( msec );
// see if it is time to end the level
CheckExitRules( );
@@ -2383,5 +2384,7 @@ void G_RunFrame( int levelTime )
trap_Cvar_Set( "g_listEntity", "0" );
}
+
+ level.frameMsec = trap_Milliseconds();
}
diff --git a/src/game/g_misc.c b/src/game/g_misc.c
index 5c551104..a68eeb86 100644
--- a/src/game/g_misc.c
+++ b/src/game/g_misc.c
@@ -89,7 +89,7 @@ void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles )
G_UnlaggedClear( player );
// set angles
- SetClientViewAngle( player, angles );
+ G_SetClientViewAngle( player, angles );
// kill anything at the destination
if( player->client->sess.sessionTeam != TEAM_SPECTATOR )
diff --git a/src/game/g_missile.c b/src/game/g_missile.c
index 9602943b..9cdca6cf 100644
--- a/src/game/g_missile.c
+++ b/src/game/g_missile.c
@@ -408,8 +408,8 @@ gentity_t *fire_pulseRifle( gentity_t *self, vec3_t start, vec3_t dir )
bolt->r.ownerNum = self->s.number;
bolt->parent = self;
bolt->damage = PRIFLE_DMG;
- bolt->splashDamage = 0;
- bolt->splashRadius = 0;
+ bolt->splashDamage = PRIFLE_DMG;
+ bolt->splashRadius = PRIFLE_SPLASH_RADIUS;
bolt->methodOfDeath = MOD_PRIFLE;
bolt->splashMethodOfDeath = MOD_PRIFLE;
bolt->clipmask = MASK_SHOT;
@@ -434,7 +434,8 @@ fire_luciferCannon
=================
*/
-gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, int damage, int radius )
+gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir,
+ int damage, int radius, int speed )
{
gentity_t *bolt;
int localDamage = (int)( ceil( ( (float)damage /
@@ -468,7 +469,7 @@ gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, int da
bolt->s.pos.trType = TR_LINEAR;
bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
VectorCopy( start, bolt->s.pos.trBase );
- VectorScale( dir, LCANNON_SPEED, bolt->s.pos.trDelta );
+ VectorScale( dir, speed, bolt->s.pos.trDelta );
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
VectorCopy( start, bolt->r.currentOrigin );
@@ -589,7 +590,7 @@ void AHive_SearchAndDestroy( gentity_t *self )
//if there is no LOS or the parent hive is too far away or the target is dead, return
if( tr.entityNum == ENTITYNUM_WORLD ||
- Distance( self->r.currentOrigin, self->parent->r.currentOrigin ) > ( HIVE_RANGE * 5 ) ||
+ Distance( self->r.currentOrigin, self->parent->r.currentOrigin ) > HIVE_RANGE ||
self->target_ent->health <= 0 )
{
self->r.ownerNum = ENTITYNUM_WORLD;
@@ -789,8 +790,8 @@ gentity_t *fire_bounceBall( gentity_t *self, vec3_t start, vec3_t dir )
bolt->r.ownerNum = self->s.number;
bolt->parent = self;
bolt->damage = LEVEL3_BOUNCEBALL_DMG;
- bolt->splashDamage = 0;
- bolt->splashRadius = 0;
+ bolt->splashDamage = LEVEL3_BOUNCEBALL_DMG;
+ bolt->splashRadius = LEVEL3_BOUNCEBALL_RADIUS;
bolt->methodOfDeath = MOD_LEVEL3_BOUNCEBALL;
bolt->splashMethodOfDeath = MOD_LEVEL3_BOUNCEBALL;
bolt->clipmask = MASK_SHOT;
@@ -802,7 +803,6 @@ gentity_t *fire_bounceBall( gentity_t *self, vec3_t start, vec3_t dir )
VectorScale( dir, LEVEL3_BOUNCEBALL_SPEED, bolt->s.pos.trDelta );
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
VectorCopy( start, bolt->r.currentOrigin );
- /*bolt->s.eFlags |= EF_BOUNCE;*/
return bolt;
}
diff --git a/src/game/g_ptr.c b/src/game/g_ptr.c
index 5344aa1d..1b44f091 100644
--- a/src/game/g_ptr.c
+++ b/src/game/g_ptr.c
@@ -63,7 +63,7 @@ void G_UpdatePTRConnection( gclient_t *client )
{
client->pers.connection->clientTeam = client->pers.teamSelection;
if( client->pers.teamSelection == PTE_NONE )
- client->pers.connection->clientCredit = client->pers.savedCredit;
+ client->pers.connection->clientCredit = client->pers.credit;
else
client->pers.connection->clientCredit = client->ps.persistant[ PERS_CREDIT ];
}
diff --git a/src/game/g_weapon.c b/src/game/g_weapon.c
index ebfa230e..5bfb1fce 100644
--- a/src/game/g_weapon.c
+++ b/src/game/g_weapon.c
@@ -142,13 +142,15 @@ Trace a bounding box against entities, but not the world
Also check there is a line of sight between the start and end point
================
*/
-static void G_WideTrace( trace_t *tr, gentity_t *ent, float range, float width, gentity_t **target )
+static void G_WideTrace( trace_t *tr, gentity_t *ent, float range,
+ float width, float height, gentity_t **target )
{
vec3_t mins, maxs;
vec3_t end;
VectorSet( mins, -width, -width, -width );
VectorSet( maxs, width, width, width );
+ mins[ 2 ] -= height - width;
*target = NULL;
@@ -156,15 +158,25 @@ static void G_WideTrace( trace_t *tr, gentity_t *ent, float range, float width,
return;
// Set aiming directions
- AngleVectors( ent->client->ps.viewangles, forward, right, up );
- CalcMuzzlePoint( ent, forward, right, up, muzzle );
VectorMA( muzzle, range, forward, end );
G_UnlaggedOn( muzzle, range );
// Trace against entities
trap_Trace( tr, muzzle, mins, maxs, end, ent->s.number, CONTENTS_BODY );
- if( tr->entityNum != ENTITYNUM_NONE )
+
+ // If we started in a solid that means someone is within our muzzle box,
+ // the trace didn't give us the entity number though so do a trace
+ // from the entity origin to the muzzle
+ if( tr->startsolid )
+ {
+ trap_Trace( tr, ent->client->ps.origin, mins, maxs, muzzle,
+ ent->s.number, CONTENTS_BODY );
+ if( tr->entityNum != ENTITYNUM_NONE )
+ *target = &g_entities[ tr->entityNum ];
+ }
+
+ else if( tr->entityNum != ENTITYNUM_NONE )
{
*target = &g_entities[ tr->entityNum ];
@@ -207,32 +219,91 @@ void SnapVectorTowards( vec3_t v, vec3_t to )
/*
===============
-meleeAttack
+BloodSpurt
+
+Generates a blood spurt event for traces with accurate end points
===============
*/
-void meleeAttack( gentity_t *ent, float range, float width, int damage, meansOfDeath_t mod )
+static void BloodSpurt( gentity_t *attacker, gentity_t *victim, trace_t *tr )
{
- trace_t tr;
gentity_t *tent;
- gentity_t *traceEnt;
- G_WideTrace( &tr, ent, range, width, &traceEnt );
+ if( !attacker->client || !victim->client )
+ return;
+ tent = G_TempEntity( tr->endpos, EV_MISSILE_HIT );
+ tent->s.otherEntityNum = victim->s.number;
+ tent->s.eventParm = DirToByte( tr->plane.normal );
+ tent->s.weapon = attacker->s.weapon;
+ tent->s.generic1 = attacker->s.generic1; // weaponMode
+ }
+
+/*
+===============
+WideBloodSpurt
- if( traceEnt == NULL )
+Calculates the position of a blood spurt for wide traces and generates an event
+===============
+*/
+static void WideBloodSpurt( gentity_t *attacker, gentity_t *victim, trace_t *tr )
+{
+ gentity_t *tent;
+ vec3_t normal, origin;
+ float mag, radius;
+
+ if( !attacker->client || !victim->client )
return;
- // send blood impact
- if( traceEnt->takedamage && traceEnt->client )
+ if( tr )
+ VectorSubtract( tr->endpos, victim->client->ps.origin, normal );
+ else
+ VectorSubtract( attacker->client->ps.origin,
+ victim->client->ps.origin, normal );
+
+ // Normalize the horizontal components of the vector difference to the
+ // "radius" of the bounding box
+ mag = sqrt( normal[ 0 ] * normal[ 0 ] + normal[ 1 ] * normal[ 1 ] );
+ radius = victim->r.maxs[ 0 ] * 1.21f;
+ if( mag > radius )
{
- tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
- tent->s.otherEntityNum = traceEnt->s.number;
- tent->s.eventParm = DirToByte( tr.plane.normal );
- tent->s.weapon = ent->s.weapon;
- tent->s.generic1 = ent->s.generic1; //weaponMode
+ normal[ 0 ] = normal[ 0 ] / mag * radius;
+ normal[ 1 ] = normal[ 1 ] / mag * radius;
}
- if( traceEnt->takedamage )
- G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, mod );
+ // Clamp origin to be within bounding box vertically
+ if( normal[ 2 ] > victim->r.maxs[ 2 ] )
+ normal[ 2 ] = victim->r.maxs[ 2 ];
+ if( normal[ 2 ] < victim->r.mins[ 2 ] )
+ normal[ 2 ] = victim->r.mins[ 2 ];
+
+ VectorAdd( victim->client->ps.origin, normal, origin );
+ VectorNegate( normal, normal );
+ VectorNormalize( normal );
+
+ // Create the blood spurt effect entity
+ tent = G_TempEntity( origin, EV_MISSILE_HIT );
+ tent->s.eventParm = DirToByte( normal );
+ tent->s.otherEntityNum = victim->s.number;
+ tent->s.weapon = attacker->s.weapon;
+ tent->s.generic1 = attacker->s.generic1; // weaponMode
+}
+
+/*
+===============
+meleeAttack
+===============
+*/
+void meleeAttack( gentity_t *ent, float range, float width, float height,
+ int damage, meansOfDeath_t mod )
+{
+ trace_t tr;
+ gentity_t *traceEnt;
+
+ G_WideTrace( &tr, ent, range, width, height, &traceEnt );
+ if( traceEnt == NULL || !traceEnt->takedamage )
+ return;
+
+ WideBloodSpurt( ent, traceEnt, &tr );
+ G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, mod );
}
/*
@@ -326,7 +397,7 @@ void ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, gentity_t *ent )
{
r = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16;
u = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16;
- VectorMA( origin, 8192 * 16, forward, end );
+ VectorMA( origin, SHOTGUN_RANGE, forward, end );
VectorMA( end, r, right, end );
VectorMA( end, u, up, end );
@@ -353,7 +424,7 @@ void shotgunFire( gentity_t *ent )
SnapVector( tent->s.origin2 );
tent->s.eventParm = rand() & 255; // seed for spread pattern
tent->s.otherEntityNum = ent->s.number;
- G_UnlaggedOn( muzzle, 8192 * 16 );
+ G_UnlaggedOn( muzzle, SHOTGUN_RANGE );
ShotgunPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent );
G_UnlaggedOff();
}
@@ -390,11 +461,7 @@ void massDriverFire( gentity_t *ent )
// send impact
if( traceEnt->takedamage && traceEnt->client )
{
- tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
- tent->s.otherEntityNum = traceEnt->s.number;
- tent->s.eventParm = DirToByte( tr.plane.normal );
- tent->s.weapon = ent->s.weapon;
- tent->s.generic1 = ent->s.generic1; //weaponMode
+ BloodSpurt( ent, traceEnt, &tr );
}
else
{
@@ -546,11 +613,7 @@ void lasGunFire( gentity_t *ent )
// send impact
if( traceEnt->takedamage && traceEnt->client )
{
- tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
- tent->s.otherEntityNum = traceEnt->s.number;
- tent->s.eventParm = DirToByte( tr.plane.normal );
- tent->s.weapon = ent->s.weapon;
- tent->s.generic1 = ent->s.generic1; //weaponMode
+ BloodSpurt( ent, traceEnt, &tr );
}
else
{
@@ -575,49 +638,31 @@ PAIN SAW
void painSawFire( gentity_t *ent )
{
trace_t tr;
- vec3_t end;
- gentity_t *tent;
- gentity_t *traceEnt;
+ vec3_t temp;
+ gentity_t *tent, *traceEnt;
- // set aiming directions
- AngleVectors( ent->client->ps.viewangles, forward, right, up );
-
- CalcMuzzlePoint( ent, forward, right, up, muzzle );
-
- VectorMA( muzzle, PAINSAW_RANGE, forward, end );
-
- G_UnlaggedOn( muzzle, PAINSAW_RANGE );
- trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
- G_UnlaggedOff( );
-
- if( tr.surfaceFlags & SURF_NOIMPACT )
+ G_WideTrace( &tr, ent, PAINSAW_RANGE, PAINSAW_WIDTH, PAINSAW_HEIGHT,
+ &traceEnt );
+ if( !traceEnt || !traceEnt->takedamage )
return;
- traceEnt = &g_entities[ tr.entityNum ];
+ // hack to line up particle system with weapon model
+ tr.endpos[ 2 ] -= 5.0f;
// send blood impact
- if( traceEnt->takedamage )
- {
- vec3_t temp;
-
- //hack to get the particle system to line up with the weapon
- VectorCopy( tr.endpos, temp );
- temp[ 2 ] -= 10.0f;
-
if( traceEnt->client )
{
- tent = G_TempEntity( temp, EV_MISSILE_HIT );
- tent->s.otherEntityNum = traceEnt->s.number;
+ BloodSpurt( ent, traceEnt, &tr );
}
else
+ {
+ VectorCopy( tr.endpos, temp );
tent = G_TempEntity( temp, EV_MISSILE_MISS );
-
tent->s.eventParm = DirToByte( tr.plane.normal );
tent->s.weapon = ent->s.weapon;
tent->s.generic1 = ent->s.generic1; //weaponMode
}
- if( traceEnt->takedamage )
G_Damage( traceEnt, ent, ent, forward, tr.endpos, PAINSAW_DAMAGE, DAMAGE_NO_KNOCKBACK, MOD_PAINSAW );
}
@@ -638,16 +683,18 @@ void LCChargeFire( gentity_t *ent, qboolean secondary )
{
gentity_t *m;
- if( secondary )
+ if( secondary && ent->client->ps.stats[ STAT_MISC ] <= 0 )
{
m = fire_luciferCannon( ent, muzzle, forward, LCANNON_SECONDARY_DAMAGE,
- LCANNON_SECONDARY_RADIUS );
- ent->client->ps.weaponTime = LCANNON_REPEAT;
+ LCANNON_SECONDARY_RADIUS, LCANNON_SECONDARY_SPEED );
+ ent->client->ps.stats[ STAT_MISC2 ] = LCANNON_REPEAT;
}
else
{
- m = fire_luciferCannon( ent, muzzle, forward, ent->client->ps.stats[ STAT_MISC ], LCANNON_RADIUS );
- ent->client->ps.weaponTime = LCANNON_CHARGEREPEAT;
+ m = fire_luciferCannon( ent, muzzle, forward,
+ ent->client->ps.stats[ STAT_MISC ], LCANNON_RADIUS, LCANNON_SPEED );
+ ent->client->ps.stats[ STAT_MISC2 ] = LCANNON_CHARGEREPEAT -
+ ( level.time - ent->client->lcannonStartTime );
}
ent->client->ps.stats[ STAT_MISC ] = 0;
@@ -769,9 +816,10 @@ void cancelBuildFire( gentity_t *ent )
G_AddEvent( ent, EV_BUILD_REPAIR, 0 );
}
}
- else if( ent->client->ps.weapon == WP_ABUILD2 )
+ else if( ent->client->ps.weapon == WP_ABUILD ||
+ ent->client->ps.weapon == WP_ABUILD2 )
meleeAttack( ent, ABUILDER_CLAW_RANGE, ABUILDER_CLAW_WIDTH,
- ABUILDER_CLAW_DMG, MOD_ABUILDER_CLAW ); //melee attack for alien builder
+ ABUILDER_CLAW_WIDTH, ABUILDER_CLAW_DMG, MOD_ABUILDER_CLAW );
}
/*
@@ -781,7 +829,10 @@ buildFire
*/
void buildFire( gentity_t *ent, dynMenu_t menu )
{
- if( ( ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE )
+ buildable_t buildable = ( ent->client->ps.stats[ STAT_BUILDABLE ]
+ & ~SB_VALID_TOGGLEBIT );
+
+ if( buildable > BA_NONE )
{
if( ent->client->ps.stats[ STAT_MISC ] > 0 )
{
@@ -789,27 +840,17 @@ void buildFire( gentity_t *ent, dynMenu_t menu )
return;
}
- if( G_BuildIfValid( ent, ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) )
+ if( G_BuildIfValid( ent, buildable ) )
{
if( g_cheats.integer )
{
ent->client->ps.stats[ STAT_MISC ] = 0;
}
- else 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_IsPowered( muzzle ) == BA_NONE &&
- ( ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) != BA_H_REPEATER ) //hack
+ else
{
ent->client->ps.stats[ STAT_MISC ] +=
- BG_FindBuildDelayForWeapon( ent->s.weapon ) * 2;
+ BG_FindBuildTimeForBuildable( buildable );
}
- else
- ent->client->ps.stats[ STAT_MISC ] +=
- BG_FindBuildDelayForWeapon( ent->s.weapon );
ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE;
@@ -849,11 +890,18 @@ CheckVenomAttack
qboolean CheckVenomAttack( gentity_t *ent )
{
trace_t tr;
- gentity_t *tent;
gentity_t *traceEnt;
int damage = LEVEL0_BITE_DMG;
- G_WideTrace( &tr, ent, LEVEL0_BITE_RANGE, LEVEL0_BITE_WIDTH, &traceEnt );
+ if( ent->client->ps.weaponTime )
+ return qfalse;
+
+ // Calculate muzzle point
+ AngleVectors( ent->client->ps.viewangles, forward, right, up );
+ CalcMuzzlePoint( ent, forward, right, up, muzzle );
+
+ G_WideTrace( &tr, ent, LEVEL0_BITE_RANGE, LEVEL0_BITE_WIDTH,
+ LEVEL0_BITE_WIDTH, &traceEnt );
if( traceEnt == NULL )
return qfalse;
@@ -864,15 +912,14 @@ qboolean CheckVenomAttack( gentity_t *ent )
if( !traceEnt->client && !traceEnt->s.eType == ET_BUILDABLE )
return qfalse;
- //allow bites to work against defensive buildables only
+ // only allow bites to work against buildings as they are constructing
if( traceEnt->s.eType == ET_BUILDABLE )
{
- if( traceEnt->s.modelindex != BA_H_MGTURRET &&
- traceEnt->s.modelindex != BA_H_TESLAGEN )
+ if( traceEnt->spawned )
return qfalse;
- //hackery
- damage *= 0.5f;
+ if( traceEnt->biteam == BIT_ALIENS )
+ return qfalse;
}
if( traceEnt->client )
@@ -884,17 +931,10 @@ qboolean CheckVenomAttack( gentity_t *ent )
}
// send blood impact
- if( traceEnt->takedamage && traceEnt->client )
- {
- tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
- tent->s.otherEntityNum = traceEnt->s.number;
- tent->s.eventParm = DirToByte( tr.plane.normal );
- tent->s.weapon = ent->s.weapon;
- tent->s.generic1 = ent->s.generic1; //weaponMode
- }
+ WideBloodSpurt( ent, traceEnt, &tr );
G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_LEVEL0_BITE );
-
+ ent->client->ps.weaponTime += LEVEL0_BITE_REPEAT;
return qtrue;
}
@@ -993,12 +1033,6 @@ void poisonCloud( gentity_t *ent )
if( humanPlayer->client && humanPlayer->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
{
- if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, humanPlayer->client->ps.stats ) )
- continue;
-
- if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, humanPlayer->client->ps.stats ) )
- continue;
-
trap_Trace( &tr, muzzle, NULL, NULL, humanPlayer->s.origin, humanPlayer->s.number, MASK_SHOT );
//can't see target from here
@@ -1010,6 +1044,7 @@ void poisonCloud( gentity_t *ent )
humanPlayer->client->ps.stats[ STAT_STATE ] |= SS_POISONCLOUDED;
humanPlayer->client->lastPoisonCloudedTime = level.time;
humanPlayer->client->lastPoisonCloudedClient = ent;
+
trap_SendServerCommand( humanPlayer->client->ps.clientNum, "poisoncloud" );
}
}
@@ -1026,23 +1061,48 @@ LEVEL2
======================================================================
*/
-#define MAX_ZAPS 64
-
-static zap_t zaps[ MAX_CLIENTS ];
+static vec3_t sortReference;
+static int QDECL G_SortDistance( const void *a, const void *b )
+{
+ gentity_t *aent, *bent;
+ float adist, bdist;
+
+ aent = &g_entities[ *(int *)a ];
+ bent = &g_entities[ *(int *)b ];
+ adist = Distance( sortReference, aent->s.origin );
+ bdist = Distance( sortReference, bent->s.origin );
+ if( adist > bdist )
+ return -1;
+ else if( adist < bdist )
+ return 1;
+ else
+ return 0;
+}
/*
===============
-G_FindNewZapTarget
+G_UpdateZaps
===============
*/
-static gentity_t *G_FindNewZapTarget( gentity_t *ent )
+void G_UpdateZaps( gentity_t *ent )
{
int entityList[ MAX_GENTITIES ];
- vec3_t range = { LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE };
+ int hitList[ MAX_GENTITIES ];
+ vec3_t range = { LEVEL2_AREAZAP_CUTOFF,
+ LEVEL2_AREAZAP_CUTOFF,
+ LEVEL2_AREAZAP_CUTOFF };
vec3_t mins, maxs;
- int i, j, k, num;
+ int i, j;
+ int hit = 0;
+ int num;
gentity_t *enemy;
+ gentity_t *effect;
trace_t tr;
+ qboolean alreadyTargeted = qfalse;
+ int damage;
+
+ if( !ent->zapping || ent->health <= 0 )
+ return;
VectorScale( range, 1.0f / M_ROOT3, range );
VectorAdd( ent->s.origin, range, maxs );
@@ -1054,233 +1114,85 @@ static gentity_t *G_FindNewZapTarget( gentity_t *ent )
{
enemy = &g_entities[ entityList[ i ] ];
- if( ( ( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ||
+ if( ( ( enemy->client &&
+ enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ||
( enemy->s.eType == ET_BUILDABLE &&
- BG_FindTeamForBuildable( enemy->s.modelindex ) == BIT_HUMANS ) ) && enemy->health > 0 )
+ BG_FindTeamForBuildable( enemy->s.modelindex ) == BIT_HUMANS ) ) &&
+ enemy->health > 0 )
{
- qboolean foundOldTarget = qfalse;
-
- trap_Trace( &tr, muzzle, NULL, NULL, enemy->s.origin, ent->s.number, MASK_SHOT );
-
- //can't see target from here
- if( tr.entityNum == ENTITYNUM_WORLD )
- continue;
- for( j = 0; j < MAX_ZAPS; j++ )
+ alreadyTargeted = qfalse;
+ for( j = 0; j < LEVEL2_AREAZAP_MAX_TARGETS; j++ )
{
- zap_t *zap = &zaps[ j ];
-
- for( k = 0; k < zap->numTargets; k++ )
+ if( ent->zapTargets[ j ] == entityList[ i ] )
{
- if( zap->targets[ k ] == enemy )
- {
- foundOldTarget = qtrue;
+ alreadyTargeted = qtrue;
break;
- }
}
-
- if( foundOldTarget )
- break;
}
- // enemy is already targetted
- if( foundOldTarget )
+ if( !alreadyTargeted &&
+ Distance( ent->s.origin, enemy->s.origin ) > LEVEL2_AREAZAP_RANGE )
+ {
continue;
+ }
- return enemy;
+ trap_Trace( &tr, ent->s.origin, NULL, NULL, enemy->s.origin,
+ ent-g_entities, MASK_SHOT );
+ if( tr.entityNum == enemy-g_entities )
+ hitList[ hit++ ] = tr.entityNum;
}
}
- return NULL;
-}
-
-/*
-===============
-G_UpdateZapEffect
-===============
-*/
-static void G_UpdateZapEffect( zap_t *zap )
-{
- int j;
- gentity_t *effect = zap->effectChannel;
+ for( i = 0; i < LEVEL2_AREAZAP_MAX_TARGETS; i++ )
+ ent->zapTargets[ i ] = -1;
- effect->s.eType = ET_LEV2_ZAP_CHAIN;
- effect->classname = "lev2zapchain";
- G_SetOrigin( effect, zap->creator->s.origin );
- effect->s.misc = zap->creator->s.number;
-
- effect->s.time = effect->s.time2 = effect->s.constantLight = -1;
-
- for( j = 0; j < zap->numTargets; j++ )
- {
- int number = zap->targets[ j ]->s.number;
+ if( !hit )
+ return;
- switch( j )
- {
- case 0: effect->s.time = number; break;
- case 1: effect->s.time2 = number; break;
- case 2: effect->s.constantLight = number; break;
- default: break;
- }
- }
+ ent->zapDmg += ( (float)( level.time - level.previousTime ) / 1000.0f )
+ * LEVEL2_AREAZAP_DMG;
+ damage = (int)ent->zapDmg;
+ // wait until we've accumulated enough damage for bsuit to take at
+ // least 1 HP
+ if( damage < 5 )
+ damage = 0;
+ else
+ ent->zapDmg -= (int)damage;
- trap_LinkEntity( effect );
-}
-/*
-===============
-G_CreateNewZap
-===============
-*/
-static void G_CreateNewZap( gentity_t *creator, gentity_t *target )
-{
- int i, j;
- zap_t *zap;
+ VectorCopy( ent->s.origin, sortReference );
+ qsort( hitList, hit, sizeof( int ), G_SortDistance );
- for( i = 0; i < MAX_ZAPS; i++ )
+ effect = G_TempEntity( ent->s.origin, EV_LEV2_ZAP );
+ effect->s.misc = ent-g_entities;
+ effect->s.time = effect->s.time2 = effect->s.constantLight = -1;
+ for( i = 0; i < hit; i++ )
{
- zap = &zaps[ i ];
-
- if( !zap->used )
- {
- zap->used = qtrue;
-
- zap->timeToLive = LEVEL2_AREAZAP_TIME;
- zap->damageUsed = 0;
-
- zap->creator = creator;
-
- zap->targets[ 0 ] = target;
- zap->numTargets = 1;
-
- for( j = 1; j < MAX_ZAP_TARGETS && zap->targets[ j - 1 ]; j++ )
- {
- zap->targets[ j ] = G_FindNewZapTarget( zap->targets[ j - 1 ] );
-
- if( zap->targets[ j ] )
- zap->numTargets++;
- }
-
- zap->effectChannel = G_Spawn( );
- G_UpdateZapEffect( zap );
-
- return;
- }
- }
-}
+ if( i >= LEVEL2_AREAZAP_MAX_TARGETS )
+ break;
+ ent->zapTargets[ i ] = hitList[ i ];
-/*
-===============
-G_UpdateZaps
-===============
-*/
-void G_UpdateZaps( int msec )
-{
- int i, j;
- zap_t *zap;
- int damage;
+ enemy = &g_entities[ hitList[ i ] ];
- for( i = 0; i < MAX_ZAPS; i++ )
- {
- zap = &zaps[ i ];
- if( zap->used )
+ if( damage > 0 )
{
- //check each target is valid
- for( j = 0; j < zap->numTargets; j++ )
- {
- gentity_t *source;
- gentity_t *target = zap->targets[ j ];
-
- if( j == 0 )
- source = zap->creator;
- else
- source = zap->targets[ j - 1 ];
-
- if( target->health <= 0 || !target->inuse || //early out
- Distance( source->s.origin, target->s.origin ) > LEVEL2_AREAZAP_RANGE )
- {
- target = zap->targets[ j ] = G_FindNewZapTarget( source );
-
- //couldn't find a target, so forget about the rest of the chain
- if( !target )
- zap->numTargets = j;
- }
- }
-
- if( zap->numTargets )
- {
- for( j = 0; j < zap->numTargets; j++ )
- {
- gentity_t *source;
- gentity_t *target = zap->targets[ j ];
- float r = 1.0f / zap->numTargets;
- float damageFraction = 2 * r - 2 * j * r * r - r * r;
- vec3_t forward;
-
- if( j == 0 )
- source = zap->creator;
- else
- source = zap->targets[ j - 1 ];
-
- damage = ceil( ( (float)msec / LEVEL2_AREAZAP_TIME ) *
- LEVEL2_AREAZAP_DMG * damageFraction );
-
- // don't let a high msec value inflate the total damage
- if( damage + zap->damageUsed > LEVEL2_AREAZAP_DMG )
- damage = LEVEL2_AREAZAP_DMG - zap->damageUsed;
-
- VectorSubtract( target->s.origin, source->s.origin, forward );
- VectorNormalize( forward );
-
- //do the damage
- if( damage )
- {
- G_Damage( target, source, zap->creator, forward, target->s.origin,
+ G_Damage( enemy, ent, ent, NULL, enemy->s.origin,
damage, DAMAGE_NO_KNOCKBACK | DAMAGE_NO_LOCDAMAGE, MOD_LEVEL2_ZAP );
- zap->damageUsed += damage;
- }
- }
- }
-
- G_UpdateZapEffect( zap );
-
- zap->timeToLive -= msec;
-
- if( zap->timeToLive <= 0 || zap->numTargets == 0 || zap->creator->health <= 0 )
- {
- zap->used = qfalse;
- G_FreeEntity( zap->effectChannel );
- }
}
- }
-}
-
-/*
-===============
-areaZapFire
-===============
-*/
-void areaZapFire( gentity_t *ent )
-{
- trace_t tr;
- gentity_t *traceEnt;
-
- G_WideTrace( &tr, ent, LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_WIDTH, &traceEnt );
-
- if( traceEnt == NULL )
- return;
- if( ( ( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ||
- ( traceEnt->s.eType == ET_BUILDABLE &&
- BG_FindTeamForBuildable( traceEnt->s.modelindex ) == BIT_HUMANS ) ) && traceEnt->health > 0 )
- {
- G_CreateNewZap( ent, traceEnt );
+ switch( i )
+ {
+ case 0: effect->s.time = hitList[ i ]; break;
+ case 1: effect->s.time2 = hitList[ i ]; break;
+ case 2: effect->s.constantLight = hitList[ i ]; break;
+ default: break;
+ }
}
}
-
/*
======================================================================
@@ -1297,50 +1209,47 @@ CheckPounceAttack
qboolean CheckPounceAttack( gentity_t *ent )
{
trace_t tr;
- gentity_t *tent;
gentity_t *traceEnt;
int damage;
+ int payload;
- if( ent->client->ps.groundEntityNum != ENTITYNUM_NONE )
- {
- ent->client->allowedToPounce = qfalse;
+ if( ent->client->pmext.pouncePayload <= 0 )
+ return qfalse;
+
+ // in case the goon lands on his target, he get's one shot after landing
+ payload = ent->client->pmext.pouncePayload;
+ if( !( ent->client->ps.pm_flags & PMF_CHARGE ) )
ent->client->pmext.pouncePayload = 0;
- }
- if( !ent->client->allowedToPounce )
+ if( ent->client->ps.weaponTime > 0 )
return qfalse;
- if( ent->client->ps.weaponTime )
- return qfalse;
+ // Calculate muzzle point
+ AngleVectors( ent->client->ps.viewangles, forward, right, up );
+ CalcMuzzlePoint( ent, forward, right, up, muzzle );
- G_WideTrace( &tr, ent, LEVEL3_POUNCE_RANGE, LEVEL3_POUNCE_WIDTH, &traceEnt );
+ G_WideTrace( &tr, ent, LEVEL3_POUNCE_RANGE, LEVEL3_POUNCE_WIDTH,
+ LEVEL3_POUNCE_WIDTH, &traceEnt );
if( traceEnt == NULL )
return qfalse;
// send blood impact
- if( traceEnt->takedamage && traceEnt->client )
- {
- tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
- tent->s.otherEntityNum = traceEnt->s.number;
- tent->s.eventParm = DirToByte( tr.plane.normal );
- tent->s.weapon = ent->s.weapon;
- tent->s.generic1 = ent->s.generic1; //weaponMode
- }
+ if( traceEnt->takedamage )
+ WideBloodSpurt( ent, traceEnt, &tr );
if( !traceEnt->takedamage )
return qfalse;
- damage = (int)( ( (float)ent->client->pmext.pouncePayload
- / (float)LEVEL3_POUNCE_SPEED ) * LEVEL3_POUNCE_DMG );
+ damage = (int)( ( (float)payload / (float)LEVEL3_POUNCE_SPEED )
+ * LEVEL3_POUNCE_DMG );
ent->client->pmext.pouncePayload = 0;
G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage,
- DAMAGE_NO_KNOCKBACK|DAMAGE_NO_LOCDAMAGE, MOD_LEVEL3_POUNCE );
+ DAMAGE_NO_LOCDAMAGE, MOD_LEVEL3_POUNCE );
ent->client->ps.weaponTime += LEVEL3_POUNCE_TIME;
- ent->client->allowedToPounce = qfalse;
return qtrue;
}
@@ -1365,39 +1274,90 @@ LEVEL4
/*
===============
-ChargeAttack
+G_ChargeAttack
===============
*/
-void ChargeAttack( gentity_t *ent, gentity_t *victim )
+void G_ChargeAttack( gentity_t *ent, gentity_t *victim )
{
- gentity_t *tent;
int damage;
vec3_t forward, normal;
- if( level.time < victim->chargeRepeat )
+ if( ent->client->ps.stats[ STAT_MISC ] <= 0 || !ent->client->charging )
return;
- victim->chargeRepeat = level.time + LEVEL4_CHARGE_REPEAT;
-
VectorSubtract( victim->s.origin, ent->s.origin, forward );
VectorNormalize( forward );
VectorNegate( forward, normal );
- if( victim->client )
+ if( !victim->takedamage )
+ return;
+
+ WideBloodSpurt( ent, victim, NULL );
+
+ damage = (int)( ( (float)ent->client->ps.stats[ STAT_MISC ] /
+ (float)LEVEL4_TRAMPLE_CHARGE_MAX ) * LEVEL4_TRAMPLE_DMG );
+
+ G_Damage( victim, ent, ent, forward, victim->s.origin, damage,
+ 0, MOD_LEVEL4_TRAMPLE );
+
+ if( !victim->client )
+ ent->client->ps.stats[ STAT_MISC ] = 0;
+}
+
+/*
+===============
+G_CrushAttack
+
+Should only be called if there was an impact between a tyrant and another player
+===============
+*/
+void G_CrushAttack( gentity_t *ent, gentity_t *victim, float sec )
+{
+ vec3_t dir;
+ float jump;
+ int damage;
+
+ if( !victim->takedamage ||
+ ent->client->ps.origin[ 2 ] + ent->r.mins[ 2 ] <
+ victim->s.origin[ 2 ] + victim->r.maxs[ 2 ] ||
+ ( victim->client &&
+ victim->client->ps.groundEntityNum == ENTITYNUM_NONE ) )
+ return;
+
+ // Force target to crouch first if they can
+ if( victim->client &&
+ !BG_InventoryContainsUpgrade( UP_BATTLESUIT, victim->client->ps.stats ) &&
+ victim->client->ps.pm_type != PM_JETPACK &&
+ victim->client->pers.cmd.upmove >= 0 &&
+ !( victim->client->ps.pm_flags &
+ ( PMF_CROUCH_HELD | PMF_FORCE_CROUCH ) ) )
{
- tent = G_TempEntity( victim->s.origin, EV_MISSILE_HIT );
- tent->s.otherEntityNum = victim->s.number;
- tent->s.eventParm = DirToByte( normal );
- tent->s.weapon = ent->s.weapon;
- tent->s.generic1 = ent->s.generic1; //weaponMode
+ victim->client->forceCrouchTime = level.time;
+ return;
}
- if( !victim->takedamage )
- return;
+ // Deal velocity based damage to target
+ jump = BG_FindJumpMagnitudeForClass( ent->client->ps.stats[ STAT_PCLASS ] );
+ damage = ( ent->client->pmext.fallVelocity + jump ) *
+ -LEVEL4_CRUSH_DAMAGE_PER_V;
- damage = (int)( ( (float)ent->client->ps.stats[ STAT_MISC ] / (float)LEVEL4_CHARGE_TIME ) * LEVEL4_CHARGE_DMG );
+ if( damage < 0 )
+ damage = 0;
- G_Damage( victim, ent, ent, forward, victim->s.origin, damage, 0, MOD_LEVEL4_CHARGE );
+ if( victim->client &&
+ ent->client->lastCrushTime + LEVEL4_CRUSH_REPEAT < level.time )
+ {
+ ent->client->lastCrushTime = level.time;
+ damage += LEVEL4_CRUSH_DAMAGE;
+ }
+
+ if( damage < 1 )
+ return;
+
+ // Crush the victim over a period of time
+ VectorSubtract( victim->s.origin, ent->client->ps.origin, dir );
+ G_Damage( victim, ent, ent, dir, victim->s.origin, damage,
+ DAMAGE_NO_LOCDAMAGE, MOD_LEVEL4_CRUSH );
}
//======================================================================
@@ -1413,7 +1373,7 @@ void CalcMuzzlePoint( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, v
{
vec3_t normal;
- VectorCopy( ent->s.pos.trBase, muzzlePoint );
+ VectorCopy( ent->client->ps.origin, muzzlePoint );
BG_GetClientNormal( &ent->client->ps, normal );
VectorMA( muzzlePoint, ent->client->ps.viewheight, normal, muzzlePoint );
VectorMA( muzzlePoint, 1, forward, muzzlePoint );
@@ -1481,9 +1441,6 @@ void FireWeapon2( gentity_t *ent )
case WP_ALEVEL1_UPG:
poisonCloud( ent );
break;
- case WP_ALEVEL2_UPG:
- areaZapFire( ent );
- break;
case WP_LUCIFER_CANNON:
LCChargeFire( ent, qtrue );
@@ -1524,20 +1481,28 @@ void FireWeapon( gentity_t *ent )
{
case WP_ALEVEL1:
case WP_ALEVEL1_UPG:
- meleeAttack( ent, LEVEL1_CLAW_RANGE, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_DMG, MOD_LEVEL1_CLAW );
+ meleeAttack( ent, LEVEL1_CLAW_RANGE, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_WIDTH,
+ LEVEL1_CLAW_DMG, MOD_LEVEL1_CLAW );
break;
case WP_ALEVEL3:
+ meleeAttack( ent, LEVEL3_CLAW_RANGE, LEVEL3_CLAW_WIDTH, LEVEL3_CLAW_WIDTH,
+ LEVEL3_CLAW_DMG, MOD_LEVEL3_CLAW );
+ break;
case WP_ALEVEL3_UPG:
- meleeAttack( ent, LEVEL3_CLAW_RANGE, LEVEL3_CLAW_WIDTH, LEVEL3_CLAW_DMG, MOD_LEVEL3_CLAW );
+ meleeAttack( ent, LEVEL3_CLAW_UPG_RANGE, LEVEL3_CLAW_WIDTH,
+ LEVEL3_CLAW_WIDTH, LEVEL3_CLAW_DMG, MOD_LEVEL3_CLAW );
break;
case WP_ALEVEL2:
- meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW );
+ meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_WIDTH,
+ LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW );
break;
case WP_ALEVEL2_UPG:
- meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW );
+ meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_WIDTH,
+ LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW );
break;
case WP_ALEVEL4:
- meleeAttack( ent, LEVEL4_CLAW_RANGE, LEVEL4_CLAW_WIDTH, LEVEL4_CLAW_DMG, MOD_LEVEL4_CLAW );
+ meleeAttack( ent, LEVEL4_CLAW_RANGE, LEVEL4_CLAW_WIDTH,
+ LEVEL4_CLAW_HEIGHT, LEVEL4_CLAW_DMG, MOD_LEVEL4_CLAW );
break;
case WP_BLASTER:
diff --git a/src/game/tremulous.h b/src/game/tremulous.h
index f7ccc38d..47dbb438 100644
--- a/src/game/tremulous.h
+++ b/src/game/tremulous.h
@@ -56,38 +56,41 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define LEVEL0_BITE_K_SCALE 1.0f
#define LEVEL1_CLAW_DMG ADM(32)
-#define LEVEL1_CLAW_RANGE 96.0f
+#define LEVEL1_CLAW_RANGE 64.0f
#define LEVEL1_CLAW_WIDTH 10.0f
#define LEVEL1_CLAW_REPEAT 600
#define LEVEL1_CLAW_U_REPEAT 500
#define LEVEL1_CLAW_K_SCALE 1.0f
#define LEVEL1_CLAW_U_K_SCALE 1.0f
-#define LEVEL1_GRAB_RANGE 64.0f
-#define LEVEL1_GRAB_TIME 300
-#define LEVEL1_GRAB_U_TIME 450
+#define LEVEL1_GRAB_RANGE 96.0f
+#define LEVEL1_GRAB_TIME 400
+#define LEVEL1_GRAB_U_TIME 600
#define LEVEL1_PCLOUD_DMG ADM(4)
-#define LEVEL1_PCLOUD_RANGE 200.0f
-#define LEVEL1_PCLOUD_REPEAT 2000
+#define LEVEL1_PCLOUD_RANGE 150.0f
+#define LEVEL1_PCLOUD_REPEAT 2500
#define LEVEL1_PCLOUD_TIME 10000
+#define LEVEL1_REGEN_MOD 2.0f
+#define LEVEL1_UPG_REGEN_MOD 3.0f
#define LEVEL2_CLAW_DMG ADM(40)
#define LEVEL2_CLAW_RANGE 96.0f
-#define LEVEL2_CLAW_WIDTH 12.0f
+#define LEVEL2_CLAW_WIDTH 14.0f
#define LEVEL2_CLAW_REPEAT 500
#define LEVEL2_CLAW_K_SCALE 1.0f
#define LEVEL2_CLAW_U_REPEAT 400
#define LEVEL2_CLAW_U_K_SCALE 1.0f
-#define LEVEL2_AREAZAP_DMG ADM(80)
-#define LEVEL2_AREAZAP_RANGE 200.0f
-#define LEVEL2_AREAZAP_WIDTH 15.0f
-#define LEVEL2_AREAZAP_REPEAT 1500
-#define LEVEL2_AREAZAP_TIME 1000
+#define LEVEL2_AREAZAP_DMG ADM(50)
+#define LEVEL2_AREAZAP_RANGE 90.0f
+#define LEVEL2_AREAZAP_CUTOFF 350.0f
+#define LEVEL2_AREAZAP_REPEAT 500
#define LEVEL2_AREAZAP_MAX_TARGETS 3
#define LEVEL2_WALLJUMP_MAXSPEED 1000.0f
#define LEVEL3_CLAW_DMG ADM(80)
-#define LEVEL3_CLAW_RANGE 96.0f
-#define LEVEL3_CLAW_WIDTH 16.0f
+#define LEVEL3_CLAW_UPG_RANGE 96.0f
+#define LEVEL3_CLAW_RANGE 72.0f
+//#define LEVEL3_CLAW_WIDTH 16.0f
+#define LEVEL3_CLAW_WIDTH 12.0f
#define LEVEL3_CLAW_REPEAT 700
#define LEVEL3_CLAW_K_SCALE 1.0f
#define LEVEL3_CLAW_U_REPEAT 600
@@ -99,27 +102,41 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define LEVEL3_POUNCE_UPG_SPEED 800
#define LEVEL3_POUNCE_SPEED_MOD 0.75f
#define LEVEL3_POUNCE_CHARGE_TIME 700
+#define LEVEL3_POUNCE_CHARGE_MIN 400
#define LEVEL3_POUNCE_TIME 400
#define LEVEL3_BOUNCEBALL_DMG ADM(110)
#define LEVEL3_BOUNCEBALL_REPEAT 1000
#define LEVEL3_BOUNCEBALL_SPEED 1000.0f
+#define LEVEL3_BOUNCEBALL_RADIUS 30
#define LEVEL4_CLAW_DMG ADM(100)
-#define LEVEL4_CLAW_RANGE 128.0f
-#define LEVEL4_CLAW_WIDTH 20.0f
+#define LEVEL4_CLAW_RANGE 116.0f
+#define LEVEL4_CLAW_WIDTH 14.0f
+#define LEVEL4_CLAW_HEIGHT 20.0f
#define LEVEL4_CLAW_REPEAT 750
#define LEVEL4_CLAW_K_SCALE 1.0f
#define LEVEL4_REGEN_RANGE 200.0f
-#define LEVEL4_REGEN_MOD 2.0f
-#define LEVEL4_CHARGE_SPEED 2.0f
-#define LEVEL4_CHARGE_TIME 3000
-#define LEVEL4_CHARGE_CHARGE_TIME 1500
-#define LEVEL4_MIN_CHARGE_TIME 750
-#define LEVEL4_CHARGE_CHARGE_RATIO (LEVEL4_CHARGE_TIME/LEVEL4_CHARGE_CHARGE_TIME)
-#define LEVEL4_CHARGE_REPEAT 1000
-#define LEVEL4_CHARGE_DMG ADM(110)
-
+#define LEVEL4_TRAMPLE_SPEED 2.0f
+#define LEVEL4_TRAMPLE_TRIGGER_TIME 3000
+#define LEVEL4_TRAMPLE_CHARGE_MIN_TIME 375
+#define LEVEL4_TRAMPLE_CHARGE_MAX_TIME 1000
+#define LEVEL4_TRAMPLE_DURATION 3000
+#define LEVEL4_TRAMPLE_DMG ADM(110)
+
+#define LEVEL4_TRAMPLE_CHARGE_RATE 2.0f
+#define LEVEL4_TRAMPLE_CHARGE_TRIGGER ( LEVEL4_TRAMPLE_TRIGGER_TIME * \
+ LEVEL4_TRAMPLE_CHARGE_RATE )
+#define LEVEL4_TRAMPLE_CHARGE_MIN ( LEVEL4_TRAMPLE_CHARGE_MIN_TIME * \
+ LEVEL4_TRAMPLE_CHARGE_RATE )
+#define LEVEL4_TRAMPLE_CHARGE_MAX ( LEVEL4_TRAMPLE_CHARGE_MAX_TIME * \
+ LEVEL4_TRAMPLE_CHARGE_RATE )
+#define LEVEL4_TRAMPLE_DISCHARGE_RATE ( (float)LEVEL4_TRAMPLE_CHARGE_MAX / \
+ (float)LEVEL4_TRAMPLE_DURATION )
+
+#define LEVEL4_CRUSH_DAMAGE_PER_V 0.5f
+#define LEVEL4_CRUSH_DAMAGE 120 // to players only
+#define LEVEL4_CRUSH_REPEAT 500 // player damage repeat
/*
* ALIEN classes
@@ -137,13 +154,13 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define ALIEN_VALUE_MODIFIER 1.0f
#define AVM(h) ((int)((float)h*ALIEN_VALUE_MODIFIER))
-#define ABUILDER_SPEED 0.8f
+#define ABUILDER_SPEED 0.65f
#define ABUILDER_VALUE AVM(200)
#define ABUILDER_HEALTH AHM(50)
#define ABUILDER_REGEN 2
#define ABUILDER_COST 0
-#define ABUILDER_UPG_SPEED 1.0f
+#define ABUILDER_UPG_SPEED 0.65f
#define ABUILDER_UPG_VALUE AVM(250)
#define ABUILDER_UPG_HEALTH AHM(75)
#define ABUILDER_UPG_REGEN 3
@@ -193,8 +210,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define LEVEL4_SPEED 1.2f
#define LEVEL4_VALUE AVM(800)
-#define LEVEL4_HEALTH AHM(400)
-#define LEVEL4_REGEN 7
+#define LEVEL4_HEALTH AHM(350)
+#define LEVEL4_REGEN 9
#define LEVEL4_COST 2
@@ -222,6 +239,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define CREEP_ARMOUR_MODIFIER 0.75f
#define CREEP_SCALEDOWN_TIME 3000
+#define PCLOUD_MODIFIER 0.5f
+#define PCLOUD_ARMOUR_MODIFIER 0.75f
+
#define ASPAWN_BP 10
#define ASPAWN_BT 15000
#define ASPAWN_HEALTH ABHM(250)
@@ -231,13 +251,15 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define ASPAWN_CREEPSIZE 120
#define ASPAWN_VALUE 150
-#define BARRICADE_BP 10
+#define BARRICADE_BP 8
#define BARRICADE_BT 20000
-#define BARRICADE_HEALTH ABHM(200)
+#define BARRICADE_HEALTH ABHM(300)
#define BARRICADE_REGEN 14
#define BARRICADE_SPLASHDAMAGE 50
#define BARRICADE_SPLASHRADIUS 50
#define BARRICADE_CREEPSIZE 120
+#define BARRICADE_SHRINKPROP 0.25f
+#define BARRICADE_SHRINKTIMEOUT 500
#define BOOSTER_BP 12
#define BOOSTER_BT 15000
@@ -246,9 +268,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define BOOSTER_SPLASHDAMAGE 50
#define BOOSTER_SPLASHRADIUS 50
#define BOOSTER_CREEPSIZE 120
-#define BOOSTER_INTERVAL 30000 //time in msec between uses (per player)
-#define BOOSTER_REGEN_MOD 2.0f
-#define BOOST_TIME 30000
+#define BOOSTER_REGEN_MOD 3.0f
+#define BOOST_TIME 20000
#define ACIDTUBE_BP 8
#define ACIDTUBE_BT 15000
@@ -268,7 +289,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define HIVE_SPLASHDAMAGE 30
#define HIVE_SPLASHRADIUS 200
#define HIVE_CREEPSIZE 120
-#define HIVE_RANGE 400.0f
+#define HIVE_SENSE_RANGE 500.0f
+#define HIVE_RANGE 1500.0f
#define HIVE_REPEAT 5000
#define HIVE_K_SCALE 1.0f
#define HIVE_DMG 50
@@ -321,12 +343,13 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define ALIENSENSE_RANGE 1000.0f
#define ALIEN_POISON_TIME 10000
-#define ALIEN_POISON_DMG 30
+#define ALIEN_POISON_DMG 5
#define ALIEN_POISON_DIVIDER (1.0f/1.32f) //about 1.0/(time`th root of damage)
#define ALIEN_SPAWN_REPEAT_TIME 10000
#define ALIEN_REGEN_DAMAGE_TIME 2000 //msec since damage that regen starts again
+#define ALIEN_REGEN_NOCREEP_TIME 3000 //msec between regen off creep
/*
* HUMAN weapons
@@ -346,7 +369,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define BLASTER_K_SCALE 1.0f
#define BLASTER_SPREAD 200
#define BLASTER_SPEED 1400
-#define BLASTER_DMG HDM(9)
+#define BLASTER_DMG HDM(10)
#define RIFLE_CLIPSIZE 30
#define RIFLE_MAXCLIPS 6
@@ -360,8 +383,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define PAINSAW_PRICE 100
#define PAINSAW_REPEAT 75
#define PAINSAW_K_SCALE 1.0f
-#define PAINSAW_DAMAGE HDM(15)
-#define PAINSAW_RANGE 40.0f
+#define PAINSAW_DAMAGE HDM(11)
+#define PAINSAW_RANGE 64.0f
+#define PAINSAW_WIDTH 0.f
+#define PAINSAW_HEIGHT 8.f
#define GRENADE_PRICE 200
#define GRENADE_REPEAT 0
@@ -372,13 +397,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define SHOTGUN_PRICE 150
#define SHOTGUN_SHELLS 8
-#define SHOTGUN_PELLETS 8 //used to sync server and client side
+#define SHOTGUN_PELLETS 14 //used to sync server and client side
#define SHOTGUN_MAXCLIPS 3
#define SHOTGUN_REPEAT 1000
#define SHOTGUN_K_SCALE 1.0f
#define SHOTGUN_RELOAD 2000
#define SHOTGUN_SPREAD 900
-#define SHOTGUN_DMG HDM(7)
+#define SHOTGUN_DMG HDM(4)
+#define SHOTGUN_RANGE (8192 * 12)
#define LASGUN_PRICE 250
#define LASGUN_AMMO 200
@@ -400,7 +426,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define CHAINGUN_REPEAT 80
#define CHAINGUN_K_SCALE 1.0f
#define CHAINGUN_SPREAD 1000
-#define CHAINGUN_DMG HDM(6)
+#define CHAINGUN_DMG HDM(5)
#define PRIFLE_PRICE 400
#define PRIFLE_CLIPS 50
@@ -409,6 +435,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define PRIFLE_K_SCALE 1.0f
#define PRIFLE_RELOAD 2000
#define PRIFLE_DMG HDM(9)
+#define PRIFLE_SPLASH_RADIUS 16
#define PRIFLE_SPEED 1000
#define FLAMER_PRICE 450
@@ -418,23 +445,25 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define FLAMER_DMG HDM(20)
#define FLAMER_RADIUS 50
#define FLAMER_LIFETIME 800.0f
-#define FLAMER_SPEED 200.0f
+#define FLAMER_SPEED 300.0f
#define FLAMER_LAG 0.65f //the amount of player velocity that is added to the fireball
#define LCANNON_PRICE 600
-#define LCANNON_AMMO 90
-#define LCANNON_REPEAT 500
+#define LCANNON_AMMO 80
+#define LCANNON_REPEAT 1000
#define LCANNON_K_SCALE 1.0f
-#define LCANNON_CHARGEREPEAT 1000
-#define LCANNON_RELOAD 2000
+#define LCANNON_CHARGEREPEAT 500
+#define LCANNON_RELOAD 0
#define LCANNON_DAMAGE HDM(265)
#define LCANNON_RADIUS 150
-#define LCANNON_SECONDARY_DAMAGE HDM(27)
+#define LCANNON_SECONDARY_DAMAGE HDM(30)
#define LCANNON_SECONDARY_RADIUS 75
-#define LCANNON_SPEED 350
-#define LCANNON_CHARGE_TIME 2000
+#define LCANNON_SECONDARY_SPEED 1400
+#define LCANNON_SECONDARY_RELOAD 2000
+#define LCANNON_SPEED 700
+#define LCANNON_CHARGE_TIME 3000
#define LCANNON_TOTAL_CHARGE 255
-#define LCANNON_MIN_CHARGE 50
+#define LCANNON_MIN_CHARGE 1
#define HBUILD_PRICE 0
#define HBUILD_REPEAT 1000
@@ -452,9 +481,13 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#define LIGHTARMOUR_PRICE 70
+#define LIGHTARMOUR_POISON_PROTECTION 1
+#define LIGHTARMOUR_PCLOUD_PROTECTION 1000
#define HELMET_PRICE 90
#define HELMET_RANGE 1000.0f
+#define HELMET_POISON_PROTECTION 1
+#define HELMET_PCLOUD_PROTECTION 1000
#define MEDKIT_PRICE 0
@@ -468,6 +501,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define JETPACK_DISABLE_CHANCE 0.3f
#define BSUIT_PRICE 400
+#define BSUIT_POISON_PROTECTION 3
+#define BSUIT_PCLOUD_PROTECTION 3000
#define MGCLIP_PRICE 0
@@ -475,7 +510,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define GAS_PRICE 0
-#define MEDKIT_POISON_IMMUNITY_TIME 30000
+#define MEDKIT_POISON_IMMUNITY_TIME 0
#define MEDKIT_STARTUP_TIME 4000
#define MEDKIT_STARTUP_SPEED 5
@@ -519,18 +554,16 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define MGTURRET_HEALTH HBHM(190)
#define MGTURRET_SPLASHDAMAGE 100
#define MGTURRET_SPLASHRADIUS 100
-#define MGTURRET_ANGULARSPEED 8 //degrees/think ~= 200deg/sec
-#define MGTURRET_ACCURACYTOLERANCE MGTURRET_ANGULARSPEED / 1.5f //angular difference for turret to fire
+#define MGTURRET_ANGULARSPEED 12
+#define MGTURRET_ANGULARSPEED_LOCKED 8
+#define MGTURRET_ACCURACYTOLERANCE 0
#define MGTURRET_VERTICALCAP 30 // +/- maximum pitch
#define MGTURRET_REPEAT 100
#define MGTURRET_K_SCALE 1.0f
-#define MGTURRET_RANGE 300.0f
+#define MGTURRET_RANGE 400.0f
#define MGTURRET_SPREAD 200
-#define MGTURRET_DMG HDM(4)
-#define MGTURRET_DCC_ANGULARSPEED 10
-#define MGTURRET_DCC_ACCURACYTOLERANCE MGTURRET_DCC_ANGULARSPEED / 1.5f
-#define MGTURRET_GRAB_ANGULARSPEED 3
-#define MGTURRET_GRAB_ACCURACYTOLERANCE MGTURRET_GRAB_ANGULARSPEED / 1.5f
+#define MGTURRET_DMG HDM(8)
+#define MGTURRET_SPINUP_TIME 750 // time between target sighted and fire
#define TESLAGEN_BP 10
#define TESLAGEN_BT 15000
@@ -539,18 +572,21 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define TESLAGEN_SPLASHRADIUS 100
#define TESLAGEN_REPEAT 250
#define TESLAGEN_K_SCALE 4.0f
-#define TESLAGEN_RANGE 250
-#define TESLAGEN_DMG HDM(9)
+#define TESLAGEN_RANGE 150
+#define TESLAGEN_DMG HDM(10)
#define DC_BP 8
#define DC_BT 10000
#define DC_HEALTH HBHM(190)
#define DC_SPLASHDAMAGE 50
#define DC_SPLASHRADIUS 100
+#define DC_ATTACK_PERIOD 10000 // how often to spam "under attack"
+#define DC_HEALRATE 3
+#define DC_RANGE 10000
#define ARMOURY_BP 10
#define ARMOURY_BT 10000
-#define ARMOURY_HEALTH HBHM(280)
+#define ARMOURY_HEALTH HBHM(420)
#define ARMOURY_SPLASHDAMAGE 50
#define ARMOURY_SPLASHRADIUS 100
@@ -562,6 +598,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define REACTOR_ATTACK_RANGE 100.0f
#define REACTOR_ATTACK_REPEAT 1000
#define REACTOR_ATTACK_DAMAGE 40
+#define REACTOR_ATTACK_DCC_REPEAT 1000
+#define REACTOR_ATTACK_DCC_RANGE 150.0f
+#define REACTOR_ATTACK_DCC_DAMAGE 40
#define REACTOR_VALUE 2
#define REPEATER_BP 0
@@ -579,13 +618,21 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define HUMAN_JOG_MODIFIER 1.0f
#define HUMAN_BACK_MODIFIER 0.8f
#define HUMAN_SIDE_MODIFIER 0.9f
+#define HUMAN_DODGE_SIDE_MODIFIER 2.9f
+#define HUMAN_DODGE_UP_MODIFIER 0.5f
+#define HUMAN_DODGE_TIMEOUT 500
+#define HUMAN_LAND_FRICTION 3.f
#define STAMINA_STOP_RESTORE 25
#define STAMINA_WALK_RESTORE 15
+#define STAMINA_MEDISTAT_RESTORE 30 // stacked on STOP or WALK
#define STAMINA_SPRINT_TAKE 8
-#define STAMINA_LARMOUR_TAKE 4
+#define STAMINA_JUMP_TAKE 250
+#define STAMINA_DODGE_TAKE 250
+#define STAMINA_BREATHING_LEVEL 0
#define HUMAN_SPAWN_REPEAT_TIME 10000
+#define HUMAN_REGEN_DAMAGE_TIME 2000 //msec since damage before dcc repairs
/*
* Misc