diff options
Diffstat (limited to 'src/game')
-rw-r--r-- | src/game/bg_misc.c | 23 | ||||
-rw-r--r-- | src/game/bg_pmove.c | 215 | ||||
-rw-r--r-- | src/game/bg_public.h | 33 | ||||
-rw-r--r-- | src/game/g_active.c | 390 | ||||
-rw-r--r-- | src/game/g_buildable.c | 550 | ||||
-rw-r--r-- | src/game/g_client.c | 126 | ||||
-rw-r--r-- | src/game/g_cmds.c | 356 | ||||
-rw-r--r-- | src/game/g_combat.c | 71 | ||||
-rw-r--r-- | src/game/g_local.h | 58 | ||||
-rw-r--r-- | src/game/g_main.c | 73 | ||||
-rw-r--r-- | src/game/g_misc.c | 2 | ||||
-rw-r--r-- | src/game/g_missile.c | 16 | ||||
-rw-r--r-- | src/game/g_ptr.c | 2 | ||||
-rw-r--r-- | src/game/g_weapon.c | 667 | ||||
-rw-r--r-- | src/game/tremulous.h | 175 |
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 |