// leave this line at the top for all g_xxxx.cpp files... #include "g_headers.h" #include "g_local.h" #include "g_functions.h" #include "..\cgame\cg_local.h" #include "Q3_Interface.h" #include "wp_saber.h" #include "g_vehicles.h" #ifdef _DEBUG #include #endif //_DEBUG #define SLOWDOWN_DIST 128.0f #define MIN_NPC_SPEED 16.0f #ifdef _XBOX int g_lastFireTime = 0; #endif extern void VehicleExplosionDelay( gentity_t *self ); extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType ); extern void G_MaintainFormations(gentity_t *self); extern void BG_CalculateOffsetAngles( gentity_t *ent, usercmd_t *ucmd );//in bg_pangles.cpp extern void TryUse( gentity_t *ent ); extern void ChangeWeapon( gentity_t *ent, int newWeapon ); extern void ScoreBoardReset(void); extern void WP_SaberReflectCheck( gentity_t *self, usercmd_t *ucmd ); extern void WP_SaberUpdate( gentity_t *self, usercmd_t *ucmd ); extern void WP_SaberStartMissileBlockCheck( gentity_t *self, usercmd_t *ucmd ); extern void WP_ForcePowersUpdate( gentity_t *self, usercmd_t *ucmd ); extern gentity_t *SeekerAcquiresTarget ( gentity_t *ent, vec3_t pos ); extern void FireSeeker( gentity_t *owner, gentity_t *target, vec3_t origin, vec3_t dir ); extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f ); extern float DotToSpot( vec3_t spot, vec3_t from, vec3_t fromAngles ); extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ); extern qboolean PM_LockAngles( gentity_t *ent, usercmd_t *ucmd ); extern qboolean PM_AdjustAnglesToGripper( gentity_t *gent, usercmd_t *cmd ); extern qboolean PM_AdjustAnglesToPuller( gentity_t *ent, gentity_t *puller, usercmd_t *ucmd, qboolean faceAway ); extern qboolean PM_AdjustAngleForWallRun( gentity_t *ent, usercmd_t *ucmd, qboolean doMove ); extern qboolean PM_AdjustAngleForWallRunUp( gentity_t *ent, usercmd_t *ucmd, qboolean doMove ); extern qboolean PM_AdjustAnglesForSpinningFlip( gentity_t *ent, usercmd_t *ucmd, qboolean anglesOnly ); extern qboolean PM_AdjustAnglesForBackAttack( gentity_t *ent, usercmd_t *ucmd ); extern qboolean PM_AdjustAnglesForSaberLock( gentity_t *ent, usercmd_t *ucmd ); extern qboolean PM_AdjustAnglesForKnockdown( gentity_t *ent, usercmd_t *ucmd, qboolean angleClampOnly ); extern qboolean PM_AdjustAnglesForDualJumpAttack( gentity_t *ent, usercmd_t *ucmd ); extern qboolean PM_AdjustAnglesForLongJump( gentity_t *ent, usercmd_t *ucmd ); extern qboolean PM_AdjustAnglesForGrapple( gentity_t *ent, usercmd_t *ucmd ); extern qboolean PM_AdjustAngleForWallJump( gentity_t *ent, usercmd_t *ucmd, qboolean doMove ); extern qboolean PM_AdjustAnglesForBFKick( gentity_t *ent, usercmd_t *ucmd, vec3_t fwdAngs, qboolean aimFront ); extern qboolean PM_AdjustAnglesForStabDown( gentity_t *ent, usercmd_t *ucmd ); extern qboolean PM_AdjustAnglesForSpinProtect( gentity_t *ent, usercmd_t *ucmd ); extern qboolean PM_AdjustAnglesForWallRunUpFlipAlt( gentity_t *ent, usercmd_t *ucmd ); extern qboolean PM_AdjustAnglesForHeldByMonster( gentity_t *ent, gentity_t *monster, usercmd_t *ucmd ); extern qboolean PM_HasAnimation( gentity_t *ent, int animation ); extern qboolean PM_LeapingSaberAnim( int anim ); extern qboolean PM_SpinningSaberAnim( int anim ); extern qboolean PM_SaberInAttack( int move ); extern qboolean PM_KickingAnim( int anim ); extern int PM_AnimLength( int index, animNumber_t anim ); extern qboolean PM_InKnockDown( playerState_t *ps ); extern qboolean PM_InGetUp( playerState_t *ps ); extern qboolean PM_InRoll( playerState_t *ps ); extern void PM_CmdForRoll( playerState_t *ps, usercmd_t *pCmd ); extern qboolean PM_InAttackRoll( int anim ); extern qboolean PM_CrouchAnim( int anim ); extern qboolean PM_FlippingAnim( int anim ); extern qboolean PM_InCartwheel( int anim ); extern qboolean PM_StandingAnim( int anim ); extern qboolean PM_InForceGetUp( playerState_t *ps ); extern qboolean PM_GetupAnimNoMove( int legsAnim ); extern qboolean PM_SuperBreakLoseAnim( int anim ); extern qboolean PM_SuperBreakWinAnim( int anim ); extern qboolean PM_CanRollFromSoulCal( playerState_t *ps ); extern qboolean BG_FullBodyTauntAnim( int anim ); extern qboolean FlyingCreature( gentity_t *ent ); extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent ); extern void G_AttachToVehicle( gentity_t *ent, usercmd_t **ucmd ); extern void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex = 0 ); extern void G_UpdateEmplacedWeaponData( gentity_t *ent ); extern void RunEmplacedWeapon( gentity_t *ent, usercmd_t **ucmd ); extern qboolean G_PointInBounds( const vec3_t point, const vec3_t mins, const vec3_t maxs ); extern void NPC_SetPainEvent( gentity_t *self ); extern qboolean G_HasKnockdownAnims( gentity_t *ent ); extern int G_GetEntsNearBolt( gentity_t *self, gentity_t **radiusEnts, float radius, int boltIndex, vec3_t boltOrg ); extern qboolean PM_InOnGroundAnim ( playerState_t *ps ); extern qboolean PM_LockedAnim( int anim ); extern qboolean WP_SabersCheckLock2( gentity_t *attacker, gentity_t *defender, sabersLockMode_t lockMode ); extern qboolean G_JediInNormalAI( gentity_t *ent ); extern bool in_camera; extern qboolean player_locked; extern qboolean stop_icarus; extern qboolean MatrixMode; extern cvar_t *g_spskill; extern cvar_t *g_timescale; extern cvar_t *g_saberMoveSpeed; extern cvar_t *g_saberAutoBlocking; extern cvar_t *g_speederControlScheme; extern cvar_t *d_slowmodeath; extern cvar_t *g_debugMelee; extern vmCvar_t cg_thirdPersonAlpha; extern vmCvar_t cg_thirdPersonAutoAlpha; void ClientEndPowerUps( gentity_t *ent ); int G_FindLookItem( gentity_t *self ) { //FIXME: should be a more intelligent way of doing this, like auto aim? //closest, most in front... did damage to... took damage from? How do we know who the player is focusing on? gentity_t *ent; int bestEntNum = ENTITYNUM_NONE; gentity_t *entityList[MAX_GENTITIES]; int numListedEntities; vec3_t center, mins, maxs, fwdangles, forward, dir; int i, e; float radius = 256; float rating, bestRating = 0.0f; //FIXME: no need to do this in 1st person? fwdangles[1] = self->client->ps.viewangles[1]; AngleVectors( fwdangles, forward, NULL, NULL ); VectorCopy( self->currentOrigin, center ); for ( i = 0 ; i < 3 ; i++ ) { mins[i] = center[i] - radius; maxs[i] = center[i] + radius; } numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); if ( !numListedEntities ) { return ENTITYNUM_NONE; } for ( e = 0 ; e < numListedEntities ; e++ ) { ent = entityList[ e ]; if ( !ent->item ) { continue; } if ( ent->s.eFlags&EF_NODRAW ) { continue; } if ( (ent->spawnflags&4/*ITMSF_MONSTER*/) ) {//NPCs only continue; } if ( !BG_CanItemBeGrabbed( &ent->s, &self->client->ps ) ) {//don't need it continue; } if ( !gi.inPVS( self->currentOrigin, ent->currentOrigin ) ) {//not even potentially visible continue; } if ( !G_ClearLOS( self, self->client->renderInfo.eyePoint, ent ) ) {//can't see him continue; } //rate him based on how close & how in front he is VectorSubtract( ent->currentOrigin, center, dir ); rating = (1.0f-(VectorNormalize( dir )/radius)); rating *= DotProduct( forward, dir ); if ( ent->item->giType == IT_HOLDABLE && ent->item->giTag == INV_SECURITY_KEY ) {//security keys are of the highest importance rating *= 2.0f; } if ( rating > bestRating ) { bestEntNum = ent->s.number; bestRating = rating; } } return bestEntNum; } extern void CG_SetClientViewAngles( vec3_t angles, qboolean overrideViewEnt ); qboolean G_ClearViewEntity( gentity_t *ent ) { if ( !ent->client->ps.viewEntity ) return qfalse; if ( ent->client->ps.viewEntity > 0 && ent->client->ps.viewEntity < ENTITYNUM_NONE ) { if ( &g_entities[ent->client->ps.viewEntity] ) { g_entities[ent->client->ps.viewEntity].svFlags &= ~SVF_BROADCAST; if ( g_entities[ent->client->ps.viewEntity].NPC ) { g_entities[ent->client->ps.viewEntity].NPC->controlledTime = 0; SetClientViewAngle( &g_entities[ent->client->ps.viewEntity], g_entities[ent->client->ps.viewEntity].currentAngles ); G_SetAngles( &g_entities[ent->client->ps.viewEntity], g_entities[ent->client->ps.viewEntity].currentAngles ); VectorCopy( g_entities[ent->client->ps.viewEntity].currentAngles, g_entities[ent->client->ps.viewEntity].NPC->lastPathAngles ); g_entities[ent->client->ps.viewEntity].NPC->desiredYaw = g_entities[ent->client->ps.viewEntity].currentAngles[YAW]; } } CG_SetClientViewAngles( ent->pos4, qtrue ); SetClientViewAngle( ent, ent->pos4 ); } ent->client->ps.viewEntity = 0; return qtrue; } void G_SetViewEntity( gentity_t *self, gentity_t *viewEntity ) { if ( !self || !self->client || !viewEntity ) { return; } if ( self->s.number == 0 && cg.zoomMode ) { // yeah, it should really toggle them so it plays the end sound.... cg.zoomMode = 0; } if ( viewEntity->s.number == self->client->ps.viewEntity ) { return; } //clear old one first G_ClearViewEntity( self ); //set new one self->client->ps.viewEntity = viewEntity->s.number; viewEntity->svFlags |= SVF_BROADCAST; //remember current angles VectorCopy( self->client->ps.viewangles, self->pos4 ); if ( viewEntity->client ) { //vec3_t clear = {0,0,0}; CG_SetClientViewAngles( viewEntity->client->ps.viewangles, qtrue ); //SetClientViewAngle( self, viewEntity->client->ps.viewangles ); //SetClientViewAngle( viewEntity, clear ); /* VectorCopy( viewEntity->client->ps.viewangles, self->client->ps.viewangles ); for ( int i = 0; i < 3; i++ ) { self->client->ps.delta_angles[i] = viewEntity->client->ps.delta_angles[i]; } */ } if ( !self->s.number ) { CG_CenterPrint( "@SP_INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.95 ); } } qboolean G_ControlledByPlayer( gentity_t *self ) { if ( self && self->NPC && self->NPC->controlledTime > level.time ) {//being controlled gentity_t *controller = &g_entities[0]; if ( controller->client && controller->client->ps.viewEntity == self->s.number ) {//we're the player's viewEntity return qtrue; } } return qfalse; } qboolean G_ValidateLookEnemy( gentity_t *self, gentity_t *enemy ) { if ( !enemy ) { return qfalse; } if ( enemy->flags&FL_NOTARGET ) { return qfalse; } if ( (enemy->s.eFlags&EF_NODRAW) ) { return qfalse; } if ( !enemy || enemy == self || !enemy->inuse ) { return qfalse; } if ( !enemy->client || !enemy->NPC ) {//not valid if ( (enemy->svFlags&SVF_NONNPC_ENEMY) && enemy->s.weapon == WP_TURRET && enemy->noDamageTeam != self->client->playerTeam && enemy->health > 0 ) {//a turret //return qtrue; } else { return qfalse; } } else { if ( self->client->playerTeam != TEAM_FREE//evil player hates everybody && enemy->client->playerTeam == self->client->playerTeam ) {//on same team return qfalse; } Vehicle_t *pVeh = G_IsRidingVehicle( self ); if ( pVeh && pVeh == enemy->m_pVehicle ) { return qfalse; } if ( enemy->health <= 0 && ((level.time-enemy->s.time) > 3000||!InFront(enemy->currentOrigin,self->currentOrigin,self->client->ps.viewangles,0.2f)||DistanceHorizontal(enemy->currentOrigin,self->currentOrigin)>16384))//>128 {//corpse, been dead too long or too out of sight to be interesting if ( !enemy->message ) { return qfalse; } } } if ( (!InFront( enemy->currentOrigin, self->currentOrigin, self->client->ps.viewangles, 0.0f) || !G_ClearLOS( self, self->client->renderInfo.eyePoint, enemy ) ) && ( DistanceHorizontalSquared( enemy->currentOrigin, self->currentOrigin ) > 65536 || fabs(enemy->currentOrigin[2]-self->currentOrigin[2]) > 384 ) ) {//(not in front or not clear LOS) & greater than 256 away return qfalse; } //LOS? return qtrue; } void G_ChooseLookEnemy( gentity_t *self, usercmd_t *ucmd ) { //FIXME: should be a more intelligent way of doing this, like auto aim? //closest, most in front... did damage to... took damage from? How do we know who the player is focusing on? gentity_t *ent, *bestEnt = NULL; gentity_t *entityList[MAX_GENTITIES]; int numListedEntities; vec3_t center, mins, maxs, fwdangles, forward, dir; int i, e; float radius = 256; float rating, bestRating = 0.0f; //FIXME: no need to do this in 1st person? fwdangles[0] = 0; //Must initialize data! fwdangles[1] = self->client->ps.viewangles[1]; fwdangles[2] = 0; AngleVectors( fwdangles, forward, NULL, NULL ); VectorCopy( self->currentOrigin, center ); for ( i = 0 ; i < 3 ; i++ ) { mins[i] = center[i] - radius; maxs[i] = center[i] + radius; } numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); if ( !numListedEntities ) {//should we clear the enemy? return; } for ( e = 0 ; e < numListedEntities ; e++ ) { ent = entityList[ e ]; if ( !gi.inPVS( self->currentOrigin, ent->currentOrigin ) ) {//not even potentially visible continue; } if ( !G_ValidateLookEnemy( self, ent ) ) {//doesn't meet criteria of valid look enemy (don't check current since we would have done that before this func's call continue; } if ( !G_ClearLOS( self, self->client->renderInfo.eyePoint, ent ) ) {//can't see him continue; } //rate him based on how close & how in front he is VectorSubtract( ent->currentOrigin, center, dir ); rating = (1.0f-(VectorNormalize( dir )/radius)); rating *= DotProduct( forward, dir )+1.0f; if ( ent->health <= 0 ) { if ( (ucmd->buttons&BUTTON_ATTACK) || (ucmd->buttons&BUTTON_ALT_ATTACK) || (ucmd->buttons&BUTTON_FORCE_FOCUS) ) {//if attacking, don't consider dead enemies continue; } if ( ent->message ) {//keyholder rating *= 0.5f; } else { rating *= 0.1f; } } if ( ent->s.weapon == WP_SABER ) { rating *= 2.0f; } if ( ent->enemy == self ) {//he's mad at me, he's more important rating *= 2.0f; } else if ( ent->NPC && ent->NPC->blockedSpeechDebounceTime > level.time - 6000 ) {//he's detected me, he's more important if ( ent->NPC->blockedSpeechDebounceTime > level.time + 4000 ) { rating *= 1.5f; } else {//from 1.0f to 1.5f rating += rating * ((float)(ent->NPC->blockedSpeechDebounceTime-level.time) + 6000.0f)/20000.0f; } } /* if ( g_crosshairEntNum == ent && !self->enemy ) {//we don't have an enemy and we are aiming at this guy rading *= 2.0f; } */ if ( rating > bestRating ) { bestEnt = ent; bestRating = rating; } } if ( bestEnt ) { self->enemy = bestEnt; } } /* =============== G_DamageFeedback Called just before a snapshot is sent to the given player. Totals up all damage and generates both the player_state_t damage values to that client for pain blends and kicks, and global pain sound events for all clients. =============== */ void P_DamageFeedback( gentity_t *player ) { gclient_t *client; float count; vec3_t angles; client = player->client; if ( client->ps.pm_type == PM_DEAD ) { return; } // total points of damage shot at the player this frame count = client->damage_blood + client->damage_armor; if ( count == 0 ) { return; // didn't take any damage } if ( count > 255 ) { count = 255; } // send the information to the client // world damage (falling, slime, etc) uses a special code // to make the blend blob centered instead of positional if ( client->damage_fromWorld ) { client->ps.damagePitch = 255; client->ps.damageYaw = 255; client->damage_fromWorld = false; } else { vectoangles( client->damage_from, angles ); client->ps.damagePitch = angles[PITCH]/360.0 * 256; client->ps.damageYaw = angles[YAW]/360.0 * 256; } client->ps.damageCount = count; // // clear totals // client->damage_blood = 0; client->damage_armor = 0; } /* ============= P_WorldEffects Check for lava / slime contents and drowning ============= */ extern void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt ); void P_WorldEffects( gentity_t *ent ) { int mouthContents = 0; if ( ent->client->noclip ) { ent->client->airOutTime = level.time + 12000; // don't need air return; } if ( !in_camera ) { #ifndef _XBOX if (gi.totalMapContents() & (CONTENTS_WATER|CONTENTS_SLIME)) { mouthContents = gi.pointcontents( ent->client->renderInfo.eyePoint, ent->s.number ); } #endif // _XBOX } // // check for drowning // #ifdef _XBOX // using waterlevel 3 should be good enough // this saves us from doing an expensive trace if ( ent->waterlevel == 3 ) #else if ( (mouthContents&(CONTENTS_WATER|CONTENTS_SLIME)) ) #endif // _XBOX { if ( ent->client->NPC_class == CLASS_SWAMPTROOPER ) {//they have air tanks ent->client->airOutTime = level.time + 12000; // don't need air ent->damage = 2; } else if ( ent->client->airOutTime < level.time) {// if out of air, start drowning // drown! ent->client->airOutTime += 1000; if ( ent->health > 0 ) { // take more damage the longer underwater ent->damage += 2; if (ent->damage > 15) ent->damage = 15; // play a gurp sound instead of a normal pain sound if (ent->health <= ent->damage) { G_AddEvent( ent, EV_WATER_DROWN, 0 ); } else { G_AddEvent( ent, Q_irand(EV_WATER_GURP1, EV_WATER_GURP2), 0 ); } // don't play a normal pain sound ent->painDebounceTime = level.time + 200; G_Damage (ent, NULL, NULL, NULL, NULL, ent->damage, DAMAGE_NO_ARMOR, MOD_WATER); } } } else { ent->client->airOutTime = level.time + 12000; ent->damage = 2; } // // check for sizzle damage (move to pmove?) // if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { if (ent->health > 0 && ent->painDebounceTime < level.time ) { if (ent->watertype & CONTENTS_LAVA) { G_Damage (ent, NULL, NULL, NULL, NULL, 15*ent->waterlevel, 0, MOD_LAVA); } if (ent->watertype & CONTENTS_SLIME) { G_Damage (ent, NULL, NULL, NULL, NULL, 1, 0, MOD_SLIME); } } } if ((ent->health > 0) && (ent->painDebounceTime < level.time) && gi.WE_IsOutsideCausingPain(ent->currentOrigin) && TIMER_Done(ent, "AcidPainDebounce")) { if (ent->NPC && ent->client && (ent->client->ps.forcePowersKnown&(1<< FP_PROTECT))) { if (!(ent->client->ps.forcePowersActive & (1<client->poisonDamage) && (ent->client->poisonTime < level.time)) { ent->client->poisonDamage -= 2; ent->client->poisonTime = level.time + 1000; G_Damage( ent, NULL, NULL, 0, 0, 2, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_ARMOR, MOD_UNKNOWN );//FIXME: MOD_POISON? if (ent->client->poisonDamage<0) { ent->client->poisonDamage = 0; } } //in space? if (ent->client->inSpaceIndex && ent->client->inSpaceIndex != ENTITYNUM_NONE) { //we're in space, check for suffocating and for exiting gentity_t *spacetrigger = &g_entities[ent->client->inSpaceIndex]; if (!spacetrigger->inuse || !G_PointInBounds(ent->client->ps.origin, spacetrigger->absmin, spacetrigger->absmax)) { //no longer in space then I suppose ent->client->inSpaceIndex = 0; } else { //check for suffocation if (ent->client->inSpaceSuffocation < level.time) { //suffocate! if (ent->health > 0) { //if they're still alive.. G_Damage(ent, spacetrigger, spacetrigger, NULL, ent->client->ps.origin, Q_irand(20, 40), DAMAGE_NO_ARMOR, MOD_SUICIDE); if (ent->health > 0) { //did that last one kill them? //play the choking sound G_SoundOnEnt( ent, CHAN_VOICE, va( "*choke%d.wav", Q_irand( 1, 3 ) ) ); int anim = BOTH_CHOKE3; //left-handed choke if ( ent->client->ps.weapon == WP_NONE || ent->client->ps.weapon == WP_MELEE ) { anim = BOTH_CHOKE1; //two-handed choke } //make them grasp their throat NPC_SetAnim( ent, SETANIM_BOTH, BOTH_CHOKE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } } ent->client->inSpaceSuffocation = level.time + Q_irand(1000, 2000); } } } } /* =============== G_SetClientSound =============== */ void G_SetClientSound( gentity_t *ent ) { // if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) // ent->s.loopSound = G_SoundIndex("sound/weapons/stasis/electricloop.wav"); // else // ent->s.loopSound = 0; } //============================================================== extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); extern void G_StartMatrixEffect( gentity_t *ent, int meFlags = 0, int length = 1000, float timeScale = 0.0f, int spinTime = 0 ); void G_GetMassAndVelocityForEnt( gentity_t *ent, float *mass, vec3_t velocity ) { if( ent->client ) { VectorCopy( ent->client->ps.velocity, velocity ); *mass = ent->mass; } else { VectorCopy( ent->s.pos.trDelta, velocity ); if ( ent->s.pos.trType == TR_GRAVITY ) { velocity[2] -= 0.25f * g_gravity->value; } if( !ent->mass ) { *mass = 1; } else if ( ent->mass <= 10 ) { *mass = 10; } else { *mass = ent->mass;///10; } } } void DoImpact( gentity_t *self, gentity_t *other, qboolean damageSelf, trace_t *trace ) { float magnitude, my_mass; bool thrown = false; vec3_t velocity; Vehicle_t *pSelfVeh = NULL; Vehicle_t *pOtherVeh = NULL; // See if either of these guys are vehicles, if so, keep a pointer to the vehicle npc. if ( self->client && self->client->NPC_class == CLASS_VEHICLE ) { pSelfVeh = self->m_pVehicle; } if ( other->client && other->client->NPC_class == CLASS_VEHICLE ) { pOtherVeh = other->m_pVehicle; } G_GetMassAndVelocityForEnt( self, &my_mass, velocity ); if ( pSelfVeh ) { magnitude = VectorLength( velocity ) * pSelfVeh->m_pVehicleInfo->mass / 50.0f; } else { magnitude = VectorLength( velocity ) * my_mass / 50; } //if vehicle hit another vehicle, factor in their data, too // TODO: Bring this back in later on, it's not critical right now... /* if ( self->client && self->client->NPC_class == CLASS_VEHICLE ) {//we're in a vehicle if ( other->client && other->client->ps.vehicleIndex != VEHICLE_NONE ) {//they're in a vehicle float o_mass; vec3_t o_velocity; G_GetMassAndVelocityForEnt( other, &o_mass, o_velocity ); //now combine if ( DotProduct( o_velocity, velocity ) < 0 ) {//were heading towards each other, this is going to be a STRONG impact... vec3_t velocityMod; //incorportate mass into each velocity to get directional force VectorScale( velocity, my_mass/50, velocityMod ); VectorScale( o_velocity, o_mass/50, o_velocity ); //figure out the overall magnitude of those 2 directed forces impacting magnitude = (DotProduct( o_velocity, velocityMod ) * -1.0f)/500.0f; } } }*/ // Check For Vehicle On Vehicle Impact (Ramming) //----------------------------------------------- if ( pSelfVeh && pSelfVeh->m_pVehicleInfo->type!=VH_ANIMAL && pOtherVeh && pSelfVeh->m_pVehicleInfo==pOtherVeh->m_pVehicleInfo ) { gentity_t* attacker = self; Vehicle_t* attackerVeh = pSelfVeh; gentity_t* victim = other; Vehicle_t* victimVeh = pOtherVeh; // Is The Attacker Actually Not Attacking? //----------------------------------------- if (!(attackerVeh->m_ulFlags&VEH_STRAFERAM)) { // Ok, So Is The Victim Actually Attacking? //------------------------------------------ if (victimVeh->m_ulFlags&VEH_STRAFERAM) { // Ah, Ok. Swap Who Is The Attacker Then //---------------------------------------- attacker = other; attackerVeh = pOtherVeh; victim = self; victimVeh = pSelfVeh; } else { // No Attackers, So Stop //----------------------- attacker = victim = 0; } } if (attacker && victim) { // float maxMoveSpeed = pSelfVeh->m_pVehicleInfo->speedMax; // float minLockingSpeed = maxMoveSpeed * 0.75; vec3_t attackerMoveDir; float attackerMoveSpeed; vec3_t victimMoveDir; float victimMoveSpeed; vec3_t victimTowardAttacker; float victimTowardAttackerDistance; vec3_t victimRight; float victimRightAccuracy; VectorCopy(attacker->client->ps.velocity, attackerMoveDir); VectorCopy(victim->client->ps.velocity, victimMoveDir); attackerMoveSpeed = VectorNormalize(attackerMoveDir); victimMoveSpeed = VectorNormalize(victimMoveDir); AngleVectors(victim->currentAngles, 0, victimRight, 0); VectorSubtract(victim->currentOrigin, attacker->currentOrigin, victimTowardAttacker); victimTowardAttackerDistance = VectorNormalize(victimTowardAttacker); victimRightAccuracy = DotProduct(victimTowardAttacker, victimRight); if ( fabsf(victimRightAccuracy)>0.25 // Must Be Exactly Right Or Left // && victimTowardAttackerDistance<100.0f // Must Be Close Enough // && attackerMoveSpeed>minLockingSpeed // Must be moving fast enough // && fabsf(attackerMoveSpeed - victimMoveSpeed)<100 // Both must be going about the same speed ) { thrown = true; vec3_t victimRight; vec3_t victimAngles; VectorCopy(victim->currentAngles, victimAngles); victimAngles[2] = 0; AngleVectors(victimAngles, 0, victimRight, 0); if (attackerVeh->m_fStrafeTime<0) { VectorScale(victimRight, -1.0f, victimRight); } if ( !(victim->flags&FL_NO_KNOCKBACK) ) { G_Throw(victim, victimRight, 250); } // if (false) // { // VectorMA(victim->currentOrigin, 250.0f, victimRight, victimRight); // CG_DrawEdge(victim->currentOrigin, victimRight, EDGE_IMPACT_POSSIBLE); // } if (victimVeh->m_pVehicleInfo->iImpactFX) { G_PlayEffect(victimVeh->m_pVehicleInfo->iImpactFX, victim->currentOrigin, trace->plane.normal ); } } } } if ( !self->client || self->client->ps.lastOnGround+300client->ps.lastOnGround+100 < level.time ) ) { vec3_t dir1, dir2; float force = 0, dot; qboolean vehicleHitOwner = qfalse; if ( other->material == MAT_GLASS || other->material == MAT_GLASS_METAL || other->material == MAT_GRATE1 || ((other->svFlags&SVF_BBRUSH)&&(other->spawnflags&8/*THIN*/)) )//(other->absmax[0]-other->absmin[0]<=32||other->absmax[1]-other->absmin[1]<=32||other->absmax[2]-other->absmin[2]<=32)) ) {//glass and thin breakable brushes (axially aligned only, unfortunately) take more impact damage magnitude *= 2; } // See if the vehicle has crashed into the ground. if ( pSelfVeh && pSelfVeh->m_pVehicleInfo->type!=VH_ANIMAL) { if ((magnitude >= 80) && (self->painDebounceTime < level.time)) { // Setup Some Variables //---------------------- vec3_t vehFwd; VectorCopy(velocity, vehFwd); float vehSpeed = VectorNormalize(vehFwd); float vehToughnessAgainstOther = pSelfVeh->m_pVehicleInfo->toughness; float vehHitPercent = fabsf(DotProduct(vehFwd, trace->plane.normal)); int vehDFlags = DAMAGE_NO_ARMOR; bool vehPilotedByPlayer = (pSelfVeh->m_pPilot && pSelfVeh->m_pPilot->s.numberm_iTurboTime>level.time); self->painDebounceTime = level.time + 200; // Modify Magnitude By Hit Percent And Toughness Against Other //------------------------------------------------------------- if (pSelfVeh->m_ulFlags & VEH_OUTOFCONTROL) { vehToughnessAgainstOther *= 0.01f; // If Out Of Control, No Damage Resistance } else { if (vehPilotedByPlayer) { vehToughnessAgainstOther *= 1.5f; } if (other && other->client) { vehToughnessAgainstOther *= 15.0f; // Very Tough against other clients (NPCS, Player, etc) } } if (vehToughnessAgainstOther>0.0f) { magnitude *= (vehHitPercent / vehToughnessAgainstOther); } else { magnitude *= vehHitPercent; } // If We Hit Architecture //------------------------ if (!other || !other->client) { // Turbo Hurts //------------- if (vehInTurbo) { magnitude *= 5.0f; } else if (trace->plane.normal[2]>0.75f && vehHitPercent<0.2f) { magnitude /= 10.0f; } // If No Pilot, Blow This Thing Now //---------------------------------- if (vehHitPercent>0.9f && !pSelfVeh->m_pPilot && vehSpeed>1000.0f) { vehDFlags |= DAMAGE_IMPACT_DIE; } // If Out Of Control, And We Hit A Wall Or Landed Or Head On //------------------------------------------------------------ if ((pSelfVeh->m_ulFlags&VEH_OUTOFCONTROL) && (vehHitPercent>0.5f || trace->plane.normal[2]<0.5f || velocity[2]<-50.0f)) { vehDFlags |= DAMAGE_IMPACT_DIE; } // If This Is A Direct Impact (Debounced By 4 Seconds) //----------------------------------------------------- if (vehHitPercent>0.9f && (level.time - self->lastImpact)>2000 && vehSpeed>300.0f) { self->lastImpact = level.time; // The Player Has Harder Requirements to Explode //----------------------------------------------- if (vehPilotedByPlayer) { if ((vehHitPercent>0.99f && vehSpeed>1000.0f && !Q_irand(0,30)) || (vehHitPercent>0.999f && vehInTurbo)) { vehDFlags |= DAMAGE_IMPACT_DIE; } } else if (player && G_IsRidingVehicle(player) && (Distance(self->currentOrigin, player->currentOrigin)<800.0f) && (vehInTurbo || !Q_irand(0,1) || vehHitPercent>0.999f)) { vehDFlags |= DAMAGE_IMPACT_DIE; } } // Make Sure He Dies This Time. I will accept no excuses. //--------------------------------------------------------- if (vehDFlags&DAMAGE_IMPACT_DIE) { // If close enough To The PLayer if (player && G_IsRidingVehicle(player) && self->owner && Distance(self->currentOrigin, player->currentOrigin)<500.0f) { player->lastEnemy = self->owner; G_StartMatrixEffect(player, MEF_LOOK_AT_ENEMY|MEF_NO_RANGEVAR|MEF_NO_VERTBOB|MEF_NO_SPIN, 1000); } magnitude = 100000.0f; } } if (magnitude>10.0f) { // Play The Impact Effect //------------------------ if (pSelfVeh->m_pVehicleInfo->iImpactFX && vehSpeed>100.0f) { G_PlayEffect( pSelfVeh->m_pVehicleInfo->iImpactFX, self->currentOrigin, trace->plane.normal ); } // Set The Crashing Flag And Pain Debounce Time //---------------------------------------------- pSelfVeh->m_ulFlags |= VEH_CRASHING; } G_Damage( self, player, player, NULL, self->currentOrigin, magnitude, vehDFlags, MOD_FALLING );//FIXME: MOD_IMPACT } if ( self->owner == other || self->activator == other ) {//hit owner/activator if ( self->m_pVehicle && !self->m_pVehicle->m_pVehicleInfo->Inhabited( self->m_pVehicle ) ) {//empty swoop if ( self->client->respawnTime - level.time < 1000 ) {//just spawned in a second ago //don't actually damage or throw him... vehicleHitOwner = qtrue; } } } //if 2 vehicles on same side hit each other, tone it down //NOTE: we do this here because we still want the impact effect if ( pOtherVeh ) { if ( self->client->playerTeam == other->client->playerTeam ) { magnitude /= 25; } } } else if ( self->client && (PM_InKnockDown( &self->client->ps )||(self->client->ps.eFlags&EF_FORCE_GRIPPED)) && magnitude >= 120 ) {//FORCE-SMACKED into something if ( TIMER_Done( self, "impactEffect" ) ) { G_PlayEffect( G_EffectIndex( "env/impact_dustonly" ), trace->endpos, trace->plane.normal ); G_Sound( self, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) ); TIMER_Set( self, "impactEffect", 1000 ); } } //damage them if ( magnitude >= 100 && other->s.number < ENTITYNUM_WORLD ) { VectorCopy( velocity, dir1 ); VectorNormalize( dir1 ); if( VectorCompare( other->currentOrigin, vec3_origin ) ) {//a brush with no origin VectorCopy ( dir1, dir2 ); } else { VectorSubtract( other->currentOrigin, self->currentOrigin, dir2 ); VectorNormalize( dir2 ); } dot = DotProduct( dir1, dir2 ); if ( dot >= 0.2 ) { force = dot; } else { force = 0; } force *= (magnitude/50); int cont = gi.pointcontents( other->absmax, other->s.number ); if( (cont&CONTENTS_WATER) ) {//water absorbs 2/3 velocity force *= 0.33333f; } if ( self->NPC && other->s.number == ENTITYNUM_WORLD ) {//NPCs take less damage force *= 0.5f; } if ( self->s.number >= MAX_CLIENTS && self->client && (PM_InKnockDown( &self->client->ps )||self->client->ps.eFlags&EF_FORCE_GRIPPED) ) {//NPC: I was knocked down or being gripped, impact should be harder //FIXME: what if I was just thrown - force pushed/pulled or thrown from a grip? force *= 10; } //FIXME: certain NPCs/entities should be TOUGH - like Ion Cannons, AT-STs, Mark1 droids, etc. if ( pOtherVeh ) {//if hit another vehicle, take their toughness into account, too force /= pOtherVeh->m_pVehicleInfo->toughness; } if( ( (force >= 1 || pSelfVeh) && other->s.number>=MAX_CLIENTS ) || force >= 10) { /* dprint("Damage other ("); dprint(loser.classname); dprint("): "); dprint(ftos(force)); dprint("\n"); */ if ( other->svFlags & SVF_GLASS_BRUSH ) { other->splashRadius = (float)(self->maxs[0] - self->mins[0])/4.0f; } if ( pSelfVeh ) {//if in a vehicle when land on someone, always knockdown, throw, damage if ( !vehicleHitOwner ) {//didn't hit owner // If the player was hit don't make the damage so bad... if ( other && other->s.numberflags&FL_NO_KNOCKBACK) ) { G_Throw( other, dir2, force ); } G_Knockdown( other, self, dir2, force, qtrue ); G_Damage( other, self, self, velocity, self->currentOrigin, force, DAMAGE_NO_ARMOR|DAMAGE_EXTRA_KNOCKBACK, MOD_IMPACT ); } } else if ( self->forcePushTime > level.time - 1000//was force pushed/pulled in the last 1600 milliseconds && self->forcePuller == other->s.number>=MAX_CLIENTS )//hit the person who pushed/pulled me {//ignore the impact } else if ( other->takedamage ) { if ( !self->client || other->s.numberclient ) {//aw, fuck it, clients no longer take impact damage from other clients, unless you're the player if ( other->client //he's a client && self->client //I'm a client && other->client->ps.forceGripEntityNum == self->s.number )//he's force-gripping me {//don't damage the other guy if he's gripping me } else { G_Damage( other, self, self, velocity, self->currentOrigin, floor(force), DAMAGE_NO_ARMOR, MOD_IMPACT ); } } else { GEntity_PainFunc( other, self, self, self->currentOrigin, force, MOD_IMPACT ); //Hmm, maybe knockdown? if (!thrown) { if ( !(other->flags&FL_NO_KNOCKBACK) ) { G_Throw( other, dir2, force ); } } } if ( other->health > 0 ) {//still alive? //TODO: if someone was thrown through the air (in a knockdown or being gripped) // and they hit me hard enough, knock me down if ( other->client ) { if ( self->client ) { if ( PM_InKnockDown( &self->client->ps ) || (self->client->ps.eFlags&EF_FORCE_GRIPPED) ) { G_Knockdown( other, self, dir2, Q_irand( 200, 400 ), qtrue ); } } else if ( self->forcePuller != ENTITYNUM_NONE && g_entities[self->forcePuller].client && self->mass > Q_irand( 50, 100 ) ) { G_Knockdown( other, &g_entities[self->forcePuller], dir2, Q_irand( 200, 400 ), qtrue ); } } } } else { //Hmm, maybe knockdown? if (!thrown) { if ( !(other->flags&FL_NO_KNOCKBACK) ) { G_Throw( other, dir2, force ); } } } } } if ( damageSelf && self->takedamage && !(self->flags&FL_NO_IMPACT_DMG)) { //Now damage me //FIXME: more lenient falling damage, especially for when driving a vehicle if ( pSelfVeh && self->client->ps.forceJumpZStart ) {//we were force-jumping if ( self->currentOrigin[2] >= self->client->ps.forceJumpZStart ) {//we landed at same height or higher than we landed magnitude = 0; } else {//FIXME: take off some of it, at least? magnitude = (self->client->ps.forceJumpZStart-self->currentOrigin[2])/3; } } if( ( magnitude >= 100 + self->health && self->s.number >= MAX_CLIENTS && self->s.weapon != WP_SABER ) || self->client->NPC_class == CLASS_VEHICLE || ( magnitude >= 700 ) )//health here is used to simulate structural integrity { if ( (self->s.weapon == WP_SABER || self->s.numberclient&&(self->client->NPC_class==CLASS_BOBAFETT||self->client->NPC_class==CLASS_ROCKETTROOPER))) && self->client && self->client->ps.groundEntityNum < ENTITYNUM_NONE && magnitude < 1000 ) {//players and jedi take less impact damage //allow for some lenience on high falls magnitude /= 2; } //drop it some (magic number... sigh) magnitude /= 40; //If damage other, subtract half of that damage off of own injury if ( other->bmodel && other->material != MAT_GLASS ) {//take off only a little because we broke architecture (not including glass), that should hurt magnitude = magnitude - force/8; } else {//take off half damage we did to it magnitude = magnitude - force/2; } if ( pSelfVeh ) { //FIXME: if hit another vehicle, take their toughness into // account, too? Or should their toughness only matter // when they hit me? magnitude /= pSelfVeh->m_pVehicleInfo->toughness * 1000.0f; if ( other->bmodel && other->material != MAT_GLASS ) {//broke through some architecture, take a good amount of damage } else if ( pOtherVeh ) {//they're tougher //magnitude /= 4.0f;//FIXME: get the toughness of other from vehicles.cfg } else {//take some off because of give //NOTE: this covers all other entities and impact with world... //FIXME: certain NPCs/entities should be TOUGH - like Ion Cannons, AT-STs, Mark1 droids, etc. magnitude /= 10.0f; } if ( magnitude < 1.0f ) { magnitude = 0; } } if ( magnitude >= 1 ) { //FIXME: Put in a thingtype impact sound function /* dprint("Damage self ("); dprint(self.classname); dprint("): "); dprint(ftos(magnitude)); dprint("\n"); */ if ( self->NPC && self->s.weapon == WP_SABER ) {//FIXME: for now Jedi take no falling damage, but really they should if pushed off? magnitude = 0; } G_Damage( self, NULL, NULL, NULL, self->currentOrigin, magnitude/2, DAMAGE_NO_ARMOR, MOD_FALLING );//FIXME: MOD_IMPACT } } } //FIXME: slow my velocity some? /* if(self.flags&FL_ONGROUND) self.last_onground=time; */ } } /* ============== ClientImpacts ============== */ void ClientImpacts( gentity_t *ent, pmove_t *pm ) { int i, j; trace_t trace; gentity_t *other; memset( &trace, 0, sizeof( trace ) ); for (i=0 ; inumtouch ; i++) { for (j=0 ; jtouchents[j] == pm->touchents[i] ) { break; } } if (j != i) { continue; // duplicated } other = &g_entities[ pm->touchents[i] ]; if ( ( ent->NPC != NULL ) && ( ent->e_TouchFunc != touchF_NULL ) ) { // last check unneccessary GEntity_TouchFunc( ent, other, &trace ); } if ( other->e_TouchFunc == touchF_NULL ) { // not needed, but I'll leave it I guess (cache-hit issues) continue; } GEntity_TouchFunc( other, ent, &trace ); } } /* ============ G_TouchTriggersLerped Find all trigger entities that ent's current position touches. Spectators will only interact with teleporters. This version checks at 6 unit steps between last and current origins ============ */ void G_TouchTriggersLerped( gentity_t *ent ) { int i, num; float dist, curDist = 0; gentity_t *touch[MAX_GENTITIES], *hit; trace_t trace; vec3_t end, mins, maxs, diff; const vec3_t range = { 40, 40, 52 }; qboolean touched[MAX_GENTITIES]; qboolean done = qfalse; if ( !ent->client ) { return; } // dead NPCs don't activate triggers! if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { if ( ent->s.number>=MAX_CLIENTS ) { return; } } #ifdef _DEBUG for ( int j = 0; j < 3; j++ ) { assert( !_isnan(ent->currentOrigin[j])); assert( !_isnan(ent->lastOrigin[j])); } #endif// _DEBUG VectorSubtract( ent->currentOrigin, ent->lastOrigin, diff ); dist = VectorNormalize( diff ); #ifdef _DEBUG assert( (dist<1024) && "insane distance in G_TouchTriggersLerped!" ); #endif// _DEBUG if ( dist > 1024 ) { return; } memset (touched, qfalse, sizeof(touched) ); for ( curDist = 0; !done && ent->maxs[1]>0; curDist += (float)ent->maxs[1]/2.0f ) { if ( curDist >= dist ) { VectorCopy( ent->currentOrigin, end ); done = qtrue; } else { VectorMA( ent->lastOrigin, curDist, diff, end ); } VectorSubtract( end, range, mins ); VectorAdd( end, range, maxs ); num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); // can't use ent->absmin, because that has a one unit pad VectorAdd( end, ent->mins, mins ); VectorAdd( end, ent->maxs, maxs ); for ( i=0 ; ie_TouchFunc == touchF_NULL) && (ent->e_TouchFunc == touchF_NULL) ) { continue; } if ( !( hit->contents & CONTENTS_TRIGGER ) ) { continue; } if ( touched[i] == qtrue ) { continue;//already touched this move } if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { if ( Q_stricmp( "trigger_teleport", hit->classname ) || !(hit->spawnflags&16/*TTSF_DEAD_OK*/) ) {//dead clients can only touch tiogger_teleports that are marked as touchable continue; } } // use seperate code for determining if an item is picked up // so you don't have to actually contact its bounding box /* if ( hit->s.eType == ET_ITEM ) { if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) { continue; } } else */ { if ( !gi.EntityContact( mins, maxs, hit ) ) { continue; } } touched[i] = qtrue; memset( &trace, 0, sizeof(trace) ); if ( hit->e_TouchFunc != touchF_NULL ) { GEntity_TouchFunc(hit, ent, &trace); } //WTF? Why would a trigger ever fire off the NPC's touch func??!!! /* if ( ( ent->NPC != NULL ) && ( ent->e_TouchFunc != touchF_NULL ) ) { GEntity_TouchFunc( ent, hit, &trace ); } */ } } } /* ============ G_TouchTriggers Find all trigger entities that ent's current position touches. Spectators will only interact with teleporters. ============ */ void G_TouchTriggers( gentity_t *ent ) { int i, num; gentity_t *touch[MAX_GENTITIES], *hit; trace_t trace; vec3_t mins, maxs; const vec3_t range = { 40, 40, 52 }; if ( !ent->client ) { return; } // dead clients don't activate triggers! if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { return; } VectorSubtract( ent->client->ps.origin, range, mins ); VectorAdd( ent->client->ps.origin, range, maxs ); num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); // can't use ent->absmin, because that has a one unit pad VectorAdd( ent->client->ps.origin, ent->mins, mins ); VectorAdd( ent->client->ps.origin, ent->maxs, maxs ); for ( i=0 ; ie_TouchFunc == touchF_NULL) && (ent->e_TouchFunc == touchF_NULL) ) { continue; } if ( !( hit->contents & CONTENTS_TRIGGER ) ) { continue; } // use seperate code for determining if an item is picked up // so you don't have to actually contact its bounding box /* if ( hit->s.eType == ET_ITEM ) { if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) { continue; } } else */ { if ( !gi.EntityContact( mins, maxs, hit ) ) { continue; } } memset( &trace, 0, sizeof(trace) ); if ( hit->e_TouchFunc != touchF_NULL ) { GEntity_TouchFunc(hit, ent, &trace); } if ( ( ent->NPC != NULL ) && ( ent->e_TouchFunc != touchF_NULL ) ) { GEntity_TouchFunc( ent, hit, &trace ); } } } /* ============ G_MoverTouchTriggers Find all trigger entities that ent's current position touches. Spectators will only interact with teleporters. ============ */ void G_MoverTouchPushTriggers( gentity_t *ent, vec3_t oldOrg ) { int i, num; float step, stepSize, dist; gentity_t *touch[MAX_GENTITIES], *hit; trace_t trace; vec3_t mins, maxs, dir, size, checkSpot; const vec3_t range = { 40, 40, 52 }; // non-moving movers don't hit triggers! if ( !VectorLengthSquared( ent->s.pos.trDelta ) ) { return; } VectorSubtract( ent->mins, ent->maxs, size ); stepSize = VectorLength( size ); if ( stepSize < 1 ) { stepSize = 1; } VectorSubtract( ent->currentOrigin, oldOrg, dir ); dist = VectorNormalize( dir ); for ( step = 0; step <= dist; step += stepSize ) { VectorMA( ent->currentOrigin, step, dir, checkSpot ); VectorSubtract( checkSpot, range, mins ); VectorAdd( checkSpot, range, maxs ); num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); // can't use ent->absmin, because that has a one unit pad VectorAdd( checkSpot, ent->mins, mins ); VectorAdd( checkSpot, ent->maxs, maxs ); for ( i=0 ; is.eType != ET_PUSH_TRIGGER ) { continue; } if ( hit->e_TouchFunc == touchF_NULL ) { continue; } if ( !( hit->contents & CONTENTS_TRIGGER ) ) { continue; } if ( !gi.EntityContact( mins, maxs, hit ) ) { continue; } memset( &trace, 0, sizeof(trace) ); if ( hit->e_TouchFunc != touchF_NULL ) { GEntity_TouchFunc(hit, ent, &trace); } } } } void G_MatchPlayerWeapon( gentity_t *ent ) { if ( g_entities[0].inuse && g_entities[0].client ) {//player is around int newWeap; if ( g_entities[0].client->ps.weapon > WP_CONCUSSION ) { newWeap = WP_BLASTER_PISTOL; } else { newWeap = g_entities[0].client->ps.weapon; } if ( newWeap != WP_NONE && ent->client->ps.weapon != newWeap ) { G_RemoveWeaponModels( ent ); ent->client->ps.stats[STAT_WEAPONS] = ( 1 << newWeap ); ent->client->ps.ammo[weaponData[newWeap].ammoIndex] = 999; ChangeWeapon( ent, newWeap ); ent->client->ps.weapon = newWeap; ent->client->ps.weaponstate = WEAPON_READY; if ( newWeap == WP_SABER ) { //FIXME: AddSound/Sight Event int numSabers = WP_SaberInitBladeData( ent ); WP_SaberAddG2SaberModels( ent ); for ( int saberNum = 0; saberNum < numSabers; saberNum++ ) { //G_CreateG2AttachedWeaponModel( ent, ent->client->ps.saber[saberNum].model, ent->handRBolt, 0 ); ent->client->ps.saber[saberNum].type = g_entities[0].client->ps.saber[saberNum].type; for ( int bladeNum = 0; bladeNum < ent->client->ps.saber[saberNum].numBlades; bladeNum++ ) { ent->client->ps.saber[saberNum].blade[0].active = g_entities[0].client->ps.saber[saberNum].blade[bladeNum].active; ent->client->ps.saber[saberNum].blade[0].length = g_entities[0].client->ps.saber[saberNum].blade[bladeNum].length; } } ent->client->ps.saberAnimLevel = g_entities[0].client->ps.saberAnimLevel; ent->client->ps.saberStylesKnown = g_entities[0].client->ps.saberStylesKnown; } else { G_CreateG2AttachedWeaponModel( ent, weaponData[newWeap].weaponMdl, ent->handRBolt, 0 ); } } } } void G_NPCMunroMatchPlayerWeapon( gentity_t *ent ) { //special uber hack for cinematic players to match player's weapon if ( !in_camera ) { if ( ent && ent->client && ent->NPC && (ent->NPC->aiFlags&NPCAI_MATCHPLAYERWEAPON) ) {//we're a Player NPC G_MatchPlayerWeapon( ent ); } } } /* ================== ClientTimerActions Actions that happen once a second ================== */ void ClientTimerActions( gentity_t *ent, int msec ) { gclient_t *client; client = ent->client; client->timeResidual += msec; while ( client->timeResidual >= 1000 ) { client->timeResidual -= 1000; if ( ent->s.weapon != WP_NONE ) { ent->client->sess.missionStats.weaponUsed[ent->s.weapon]++; } // if we've got the seeker powerup, see if we can shoot it at someone /* if ( ent->client->ps.powerups[PW_SEEKER] > level.time ) { vec3_t seekerPos, dir; gentity_t *enemy = SeekerAcquiresTarget( ent, seekerPos ); if ( enemy != NULL ) // set the client's enemy to a valid target { FireSeeker( ent, enemy, seekerPos, dir ); gentity_t *tent; tent = G_TempEntity( seekerPos, EV_POWERUP_SEEKER_FIRE ); VectorCopy( dir, tent->pos1 ); tent->s.eventParm = ent->s.number; } }*/ if ( (ent->flags&FL_OVERCHARGED_HEALTH) ) {//need to gradually reduce health back to max if ( ent->health > ent->client->ps.stats[STAT_MAX_HEALTH] ) {//decrement it ent->health--; ent->client->ps.stats[STAT_HEALTH] = ent->health; } else {//done ent->flags &= ~FL_OVERCHARGED_HEALTH; } } } } /* ==================== ClientIntermissionThink ==================== */ static qboolean ClientCinematicThink( gclient_t *client ) { client->ps.eFlags &= ~EF_FIRING; // swap button actions client->oldbuttons = client->buttons; client->buttons = client->usercmd.buttons; if ( client->buttons & ( BUTTON_USE ) & ( client->oldbuttons ^ client->buttons ) ) { return( qtrue ); } return( qfalse ); } /* ================ ClientEvents Events will be passed on to the clients for presentation, but any server game effects are handled here ================ */ extern void WP_SabersDamageTrace( gentity_t *ent, qboolean noEffects = qfalse ); extern void WP_SaberUpdateOldBladeData( gentity_t *ent ); void ClientEvents( gentity_t *ent, int oldEventSequence ) { int i; int event; gclient_t *client; //int damage; qboolean fired; client = ent->client; fired = qfalse; for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) { event = client->ps.events[ i & (MAX_PS_EVENTS-1) ]; switch ( event ) { case EV_FALL_MEDIUM: case EV_FALL_FAR://these come from bg_pmove, PM_CrashLand if ( ent->s.eType != ET_PLAYER ) { break; // not in the player model } /* //FIXME: isn't there a more accurate way to calculate damage from falls? if ( event == EV_FALL_FAR ) { damage = 50; } else { damage = 25; } ent->painDebounceTime = level.time + 200; // no normal pain sound G_Damage (ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING); */ break; case EV_FIRE_WEAPON: #ifndef FINAL_BUILD if ( fired ) { gi.Printf( "DOUBLE EV_FIRE_WEAPON AND-OR EV_ALT_FIRE!!\n" ); } #endif fired = qtrue; FireWeapon( ent, qfalse ); #ifdef _XBOX extern int Sys_Milliseconds(); if (ent->s.clientNum == 0) g_lastFireTime = Sys_Milliseconds(); #endif break; case EV_ALT_FIRE: #ifndef FINAL_BUILD if ( fired ) { gi.Printf( "DOUBLE EV_FIRE_WEAPON AND-OR EV_ALT_FIRE!!\n" ); } #endif fired = qtrue; FireWeapon( ent, qtrue ); #ifdef _XBOX if (ent->s.clientNum == 0) g_lastFireTime = Sys_Milliseconds(); #endif break; default: break; } } //by the way, if you have your saber in hand and it's on, do the damage trace if ( client->ps.weapon == WP_SABER ) { if ( g_timescale->value >= 1.0f || !(client->ps.forcePowersActive&(1<ps.saberDamageDebounceTime - level.time > wait ) {//when you unpause the game with force speed on, the time gets *really* wiggy... client->ps.saberDamageDebounceTime = level.time + wait; } if ( client->ps.saberDamageDebounceTime <= level.time ) { WP_SabersDamageTrace( ent ); WP_SaberUpdateOldBladeData( ent ); /* if ( g_timescale->value&&client->ps.clientNum==0&&!player_locked&&!MatrixMode&&client->ps.forcePowersActive&(1<value ); } */ client->ps.saberDamageDebounceTime = level.time + wait; } } } } void G_ThrownDeathAnimForDeathAnim( gentity_t *hitEnt, vec3_t impactPoint ) { int anim = -1; if ( !hitEnt || !hitEnt->client ) { return; } switch ( hitEnt->client->ps.legsAnim ) { case BOTH_DEATH9://fall to knees, fall over case BOTH_DEATH10://fall to knees, fall over case BOTH_DEATH11://fall to knees, fall over case BOTH_DEATH13://stumble back, fall over case BOTH_DEATH17://jerky fall to knees, fall over case BOTH_DEATH18://grab gut, fall to knees, fall over case BOTH_DEATH19://grab gut, fall to knees, fall over case BOTH_DEATH20://grab shoulder, fall forward case BOTH_DEATH21://grab shoulder, fall forward case BOTH_DEATH3://knee collapse, twist & fall forward case BOTH_DEATH7://knee collapse, twist & fall forward { vec3_t dir2Impact, fwdAngles, facing; VectorSubtract( impactPoint, hitEnt->currentOrigin, dir2Impact ); dir2Impact[2] = 0; VectorNormalize( dir2Impact ); VectorSet( fwdAngles, 0, hitEnt->client->ps.viewangles[YAW], 0 ); AngleVectors( fwdAngles, facing, NULL, NULL ); float dot = DotProduct( facing, dir2Impact );//-1 = hit in front, 0 = hit on side, 1 = hit in back if ( dot > 0.5f ) {//kicked in chest, fly backward switch ( Q_irand( 0, 4 ) ) {//FIXME: don't start at beginning of anim? case 0: anim = BOTH_DEATH1;//thrown backwards break; case 1: anim = BOTH_DEATH2;//fall backwards break; case 2: anim = BOTH_DEATH15;//roll over backwards break; case 3: anim = BOTH_DEATH22;//fast fall back break; case 4: anim = BOTH_DEATH23;//fast fall back break; } } else if ( dot < -0.5f ) {//kicked in back, fly forward switch ( Q_irand( 0, 5 ) ) {//FIXME: don't start at beginning of anim? case 0: anim = BOTH_DEATH14; break; case 1: anim = BOTH_DEATH24; break; case 2: anim = BOTH_DEATH25; break; case 3: anim = BOTH_DEATH4;//thrown forwards break; case 4: anim = BOTH_DEATH5;//thrown forwards break; case 5: anim = BOTH_DEATH16;//thrown forwards break; } } else {//hit on side, spin switch ( Q_irand( 0, 2 ) ) {//FIXME: don't start at beginning of anim? case 0: anim = BOTH_DEATH12; break; case 1: anim = BOTH_DEATH14; break; case 2: anim = BOTH_DEATH15; break; case 3: anim = BOTH_DEATH6; break; case 4: anim = BOTH_DEATH8; break; } } } break; } if ( anim != -1 ) { NPC_SetAnim( hitEnt, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } } gentity_t *G_KickTrace( gentity_t *ent, vec3_t kickDir, float kickDist, vec3_t kickEnd, int kickDamage, float kickPush, qboolean doSoundOnWalls ) { vec3_t traceOrg, traceEnd, kickMins={-2,-2,-2}, kickMaxs={2,2,2}; trace_t trace; gentity_t *hitEnt = NULL; //FIXME: variable kick height? if ( kickEnd && !VectorCompare( kickEnd, vec3_origin ) ) {//they passed us the end point of the trace, just use that //this makes the trace flat VectorSet( traceOrg, ent->currentOrigin[0], ent->currentOrigin[1], kickEnd[2] ); VectorCopy( kickEnd, traceEnd ); } else {//extrude VectorSet( traceOrg, ent->currentOrigin[0], ent->currentOrigin[1], ent->currentOrigin[2]+ent->maxs[2]*0.5f ); VectorMA( traceOrg, kickDist, kickDir, traceEnd ); } gi.trace( &trace, traceOrg, kickMins, kickMaxs, traceEnd, ent->s.number, MASK_SHOT );//clipmask ok? if ( trace.fraction < 1.0f && !trace.startsolid && !trace.allsolid && trace.entityNum < ENTITYNUM_NONE ) { hitEnt = &g_entities[trace.entityNum]; if ( ent->client->ps.lastKickedEntNum != trace.entityNum ) { TIMER_Remove( ent, "kickSoundDebounce" ); ent->client->ps.lastKickedEntNum = trace.entityNum; } if ( hitEnt ) {//we hit an entity if ( hitEnt->client ) { if ( !(hitEnt->client->ps.pm_flags&PMF_TIME_KNOCKBACK) && TIMER_Done( hitEnt, "kickedDebounce" ) )//not already flying through air? Intended to stop multiple hits, but... {//FIXME: this should not always work if ( PM_InKnockDown( &hitEnt->client->ps ) && !PM_InGetUp( &hitEnt->client->ps ) ) {//don't hit people who are knocked down or being knocked down (okay to hit people getting up, though) return NULL; } if ( PM_InRoll( &hitEnt->client->ps ) ) {//can't hit people who are rolling return NULL; } //don't hit same ent more than once per kick if ( hitEnt->takedamage ) {//hurt it G_Damage( hitEnt, ent, ent, kickDir, trace.endpos, kickDamage, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_KILL, MOD_MELEE ); } //do kick hit sound and impact effect if ( TIMER_Done( ent, "kickSoundDebounce" ) ) { if ( ent->client->ps.torsoAnim == BOTH_A7_HILT ) { G_Sound( ent, G_SoundIndex( "sound/movers/objects/saber_slam" ) ); } else { vec3_t fxOrg, fxDir; VectorCopy( kickDir, fxDir ); VectorMA( trace.endpos, Q_flrand( 5.0f, 10.0f ), fxDir, fxOrg ); VectorScale( fxDir, -1, fxDir ); G_PlayEffect( G_EffectIndex( "melee/kick_impact" ), fxOrg, fxDir ); //G_Sound( ent, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) ); } TIMER_Set( ent, "kickSoundDebounce", 2000 ); } TIMER_Set( hitEnt, "kickedDebounce", 1000 ); if ( ent->client->ps.torsoAnim == BOTH_A7_HILT ) {//hit in head if ( hitEnt->health > 0 ) {//knock down if ( kickPush >= 150.0f/*75.0f*/ && !Q_irand( 0, 1 ) ) {//knock them down if ( !(hitEnt->flags&FL_NO_KNOCKBACK) ) { G_Throw( hitEnt, kickDir, kickPush/3.0f ); } G_Knockdown( hitEnt, ent, kickDir, 300, qtrue ); } else {//force them to play a pain anim if ( hitEnt->s.number < MAX_CLIENTS ) { NPC_SetPainEvent( hitEnt ); } else { GEntity_PainFunc( hitEnt, ent, ent, hitEnt->currentOrigin, 0, MOD_MELEE ); } } //just so we don't hit him again... hitEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; hitEnt->client->ps.pm_time = 100; } else { if ( !(hitEnt->flags&FL_NO_KNOCKBACK) ) { G_Throw( hitEnt, kickDir, kickPush ); } //see if we should play a better looking death on them G_ThrownDeathAnimForDeathAnim( hitEnt, trace.endpos ); } } else if ( ent->client->ps.legsAnim == BOTH_GETUP_BROLL_B || ent->client->ps.legsAnim == BOTH_GETUP_BROLL_F || ent->client->ps.legsAnim == BOTH_GETUP_FROLL_B || ent->client->ps.legsAnim == BOTH_GETUP_FROLL_F ) { if ( hitEnt->health > 0 ) {//knock down if ( hitEnt->client->ps.groundEntityNum == ENTITYNUM_NONE ) {//he's in the air? Send him flying back if ( !(hitEnt->flags&FL_NO_KNOCKBACK) ) { G_Throw( hitEnt, kickDir, kickPush ); } } else { //just so we don't hit him again... hitEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; hitEnt->client->ps.pm_time = 100; } //knock them down G_Knockdown( hitEnt, ent, kickDir, 300, qtrue ); } else { if ( !(hitEnt->flags&FL_NO_KNOCKBACK) ) { G_Throw( hitEnt, kickDir, kickPush ); } //see if we should play a better looking death on them G_ThrownDeathAnimForDeathAnim( hitEnt, trace.endpos ); } } else if ( hitEnt->health <= 0 ) {//we kicked a dead guy //throw harder - FIXME: no matter how hard I push them, they don't go anywhere... corpses use less physics??? if ( !(hitEnt->flags&FL_NO_KNOCKBACK) ) { G_Throw( hitEnt, kickDir, kickPush*4 ); } //see if we should play a better looking death on them G_ThrownDeathAnimForDeathAnim( hitEnt, trace.endpos ); } else { if ( !(hitEnt->flags&FL_NO_KNOCKBACK) ) { G_Throw( hitEnt, kickDir, kickPush ); } if ( kickPush >= 150.0f/*75.0f*/ && !Q_irand( 0, 2 ) ) { G_Knockdown( hitEnt, ent, kickDir, 300, qtrue ); } else { G_Knockdown( hitEnt, ent, kickDir, kickPush, qtrue ); } } } } else {//FIXME: don't do this in repeated frames... only allow 1 frame in kick to hit wall? The most extended one? Pass in a bool on that frame. if ( doSoundOnWalls ) {//do kick hit sound and impact effect if ( TIMER_Done( ent, "kickSoundDebounce" ) ) { if ( ent->client->ps.torsoAnim == BOTH_A7_HILT ) { G_Sound( ent, G_SoundIndex( "sound/movers/objects/saber_slam" ) ); } else { G_PlayEffect( G_EffectIndex( "melee/kick_impact" ), trace.endpos, trace.plane.normal ); //G_Sound( ent, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) ); } TIMER_Set( ent, "kickSoundDebounce", 2000 ); } } } } } return (hitEnt); } qboolean G_CheckRollSafety( gentity_t *self, int anim, float testDist ) { vec3_t forward, right, testPos, angles; trace_t trace; int contents = (CONTENTS_SOLID|CONTENTS_BOTCLIP); if ( !self || !self->client ) { return qfalse; } if ( self->s.number < MAX_CLIENTS ) {//player contents |= CONTENTS_PLAYERCLIP; } else {//NPC contents |= CONTENTS_MONSTERCLIP; } if ( PM_InAttackRoll( self->client->ps.legsAnim ) ) {//we don't care if people are in the way, we'll knock them down! contents &= ~CONTENTS_BODY; } angles[PITCH] = angles[ROLL] = 0; angles[YAW] = self->client->ps.viewangles[YAW];//Add ucmd.angles[YAW]? AngleVectors( angles, forward, right, NULL ); switch ( anim ) { case BOTH_GETUP_BROLL_R: case BOTH_GETUP_FROLL_R: VectorMA( self->currentOrigin, testDist, right, testPos ); break; case BOTH_GETUP_BROLL_L: case BOTH_GETUP_FROLL_L: VectorMA( self->currentOrigin, -testDist, right, testPos ); break; case BOTH_GETUP_BROLL_F: case BOTH_GETUP_FROLL_F: VectorMA( self->currentOrigin, testDist, forward, testPos ); break; case BOTH_GETUP_BROLL_B: case BOTH_GETUP_FROLL_B: VectorMA( self->currentOrigin, -testDist, forward, testPos ); break; default://FIXME: add normal rolls? Make generic for any forced-movement anim? return qtrue; break; } gi.trace( &trace, self->currentOrigin, self->mins, self->maxs, testPos, self->s.number, contents ); if ( trace.fraction < 1.0f || trace.allsolid || trace.startsolid ) {//inside something or will hit something return qfalse; } return qtrue; } void G_CamPullBackForLegsAnim( gentity_t *ent, qboolean useTorso = qfalse ) { if ( (ent->s.number < MAX_CLIENTS||G_ControlledByPlayer(ent)) ) { float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (useTorso?(animNumber_t)ent->client->ps.torsoAnim:(animNumber_t)ent->client->ps.legsAnim) ); float elapsedTime = (float)(animLength-(useTorso?ent->client->ps.torsoAnimTimer:ent->client->ps.legsAnimTimer)); float backDist = 0; if ( elapsedTime < animLength/2.0f ) {//starting anim backDist = (elapsedTime/animLength)*120.0f; } else {//ending anim backDist = ((animLength-elapsedTime)/animLength)*120.0f; } cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_RNG; cg.overrides.thirdPersonRange = cg_thirdPersonRange.value+backDist; } } void G_CamCircleForLegsAnim( gentity_t *ent ) { if ( (ent->s.number < MAX_CLIENTS||G_ControlledByPlayer(ent)) ) { float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.legsAnim ); float elapsedTime = (float)(animLength-ent->client->ps.legsAnimTimer); float angle = 0; angle = (elapsedTime/animLength)*360.0f; cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_ANG; cg.overrides.thirdPersonAngle = cg_thirdPersonAngle.value+angle; } } qboolean G_GrabClient( gentity_t *ent, usercmd_t *ucmd ) { gentity_t *bestEnt = NULL, *radiusEnts[ 128 ]; int numEnts; const float radius = 100.0f; const float radiusSquared = (radius*radius); float bestDistSq = (radiusSquared+1.0f), distSq; int i; vec3_t boltOrg; numEnts = G_GetEntsNearBolt( ent, radiusEnts, radius, ent->handRBolt, boltOrg ); for ( i = 0; i < numEnts; i++ ) { if ( !radiusEnts[i]->inuse ) { continue; } if ( radiusEnts[i] == ent ) {//Skip the rancor ent continue; } if ( !radiusEnts[i]->inuse || radiusEnts[i]->health <= 0 ) {//must be alive continue; } if ( radiusEnts[i]->client == NULL ) {//must be a client continue; } if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR) ||(radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_WAMPA) ||(radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_SAND_CREATURE) ) {//can't be one being held continue; } if ( PM_LockedAnim( radiusEnts[i]->client->ps.torsoAnim ) || PM_LockedAnim( radiusEnts[i]->client->ps.legsAnim ) ) {//don't interrupt continue; } if ( radiusEnts[i]->client->ps.groundEntityNum == ENTITYNUM_NONE ) {//must be on ground continue; } if ( PM_InOnGroundAnim( &radiusEnts[i]->client->ps ) ) {//must be standing up continue; } if ( fabs(radiusEnts[i]->currentOrigin[2]-ent->currentOrigin[2])>8.0f ) {//have to be close in Z continue; } if ( !PM_HasAnimation( radiusEnts[i], BOTH_PLAYER_PA_1 ) ) {//doesn't have matching anims continue; } distSq = DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg ); if ( distSq < bestDistSq ) { bestDistSq = distSq; bestEnt = radiusEnts[i]; } } if ( bestEnt != NULL ) { int lockType = LOCK_KYLE_GRAB1; if ( ucmd->forwardmove > 0 ) { lockType = LOCK_KYLE_GRAB3; } else if ( ucmd->forwardmove < 0 ) { lockType = LOCK_KYLE_GRAB2; } WP_SabersCheckLock2( ent, bestEnt, (sabersLockMode_t)lockType ); return qtrue; } return qfalse; } qboolean G_PullAttack( gentity_t *ent, usercmd_t *ucmd ) { qboolean overridAngles = qfalse; if ( ent->client->ps.torsoAnim == BOTH_PULL_IMPALE_STAB || ent->client->ps.torsoAnim == BOTH_PULL_IMPALE_SWING ) {//pulling if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); } overridAngles = (PM_LockAngles( ent, ucmd )?qtrue:overridAngles); ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; } else if ( ent->client->ps.torsoAnim == BOTH_PULLED_INAIR_B || ent->client->ps.torsoAnim == BOTH_PULLED_INAIR_F ) {//being pulled gentity_t *puller = &g_entities[ent->client->ps.pullAttackEntNum]; if ( puller && puller->inuse && puller->client && ( puller->client->ps.torsoAnim == BOTH_PULL_IMPALE_STAB || puller->client->ps.torsoAnim == BOTH_PULL_IMPALE_SWING ) ) { vec3_t pullDir; vec3_t pullPos; //calc where to pull me to /* VectorCopy( puller->client->ps.saber[0].blade[0].muzzlePoint, pullPos ); VectorMA( pullPos, puller->client->ps.saber[0].blade[0].length*0.5f, puller->client->ps.saber[0].blade[0].muzzleDir, pullPos ); */ vec3_t pullerFwd; AngleVectors( puller->client->ps.viewangles, pullerFwd, NULL, NULL ); VectorMA( puller->currentOrigin, (puller->maxs[0]*1.5f)+16.0f, pullerFwd, pullPos ); //pull me towards that pos VectorSubtract( pullPos, ent->currentOrigin, pullDir ); float pullDist = VectorNormalize( pullDir ); int sweetSpotTime = (puller->client->ps.torsoAnim == BOTH_PULL_IMPALE_STAB)?1250:1350; float pullLength = PM_AnimLength( puller->client->clientInfo.animFileIndex, (animNumber_t)puller->client->ps.torsoAnim )-sweetSpotTime; if ( pullLength <= 0.25f ) { pullLength = 0.25f; } //float pullTimeRemaining = ent->client->ps.pullAttackTime - level.time; //float pullSpeed = pullDist * (pullTimeRemaining/pullLength); float pullSpeed = (pullDist*1000.0f)/pullLength; VectorScale( pullDir, pullSpeed, ent->client->ps.velocity ); //slide, if necessary ent->client->ps.pm_flags |= PMF_TIME_NOFRICTION; ent->client->ps.pm_time = 100; //make it so I don't actually hurt them when pulled at them... ent->forcePuller = puller->s.number; ent->forcePushTime = level.time + 100; // let the push effect last for 100 more ms //look at him PM_AdjustAnglesToPuller( ent, puller, ucmd, qboolean(ent->client->ps.legsAnim==BOTH_PULLED_INAIR_B) ); overridAngles = qtrue; //don't move if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); } ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; } } return overridAngles; } void G_FixMins( gentity_t *ent ) { //do a trace to make sure it's okay float downdist = (DEFAULT_MINS_2-ent->mins[2]); vec3_t end={ent->currentOrigin[0],ent->currentOrigin[1],ent->currentOrigin[2]+downdist}; trace_t trace; gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, end, ent->s.number, ent->clipmask ); if ( !trace.allsolid && !trace.startsolid ) { if ( trace.fraction >= 1.0f ) {//all clear //drop the bottom of my bbox back down ent->mins[2] = DEFAULT_MINS_2; if ( ent->client ) { ent->client->ps.pm_flags &= ~PMF_FIX_MINS; } } else {//move me up so the bottom of my bbox will be where the trace ended, at least //need to trace up, too float updist = ((1.0f-trace.fraction) * -downdist); end[2] = ent->currentOrigin[2]+updist; gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, end, ent->s.number, ent->clipmask ); if ( !trace.allsolid && !trace.startsolid ) { if ( trace.fraction >= 1.0f ) {//all clear //move me up ent->currentOrigin[2] += updist; //drop the bottom of my bbox back down ent->mins[2] = DEFAULT_MINS_2; G_SetOrigin( ent, ent->currentOrigin ); gi.linkentity( ent ); if ( ent->client ) { ent->client->ps.pm_flags &= ~PMF_FIX_MINS; } } else {//crap, no room to expand! if ( ent->client->ps.legsAnimTimer <= 200 ) {//at the end of the anim, and we can't leave ourselves like this //so drop the maxs, put the mins back and move us up ent->maxs[2] += downdist; ent->currentOrigin[2] -= downdist; ent->mins[2] = DEFAULT_MINS_2; G_SetOrigin( ent, ent->currentOrigin ); gi.linkentity( ent ); //this way we'll be in a crouch when we're done ent->client->ps.legsAnimTimer = ent->client->ps.torsoAnimTimer = 0; ent->client->ps.pm_flags |= PMF_DUCKED; //FIXME: do we need to set a crouch anim here? if ( ent->client ) { ent->client->ps.pm_flags &= ~PMF_FIX_MINS; } } } }//crap, stuck } }//crap, stuck! } qboolean G_CheckClampUcmd( gentity_t *ent, usercmd_t *ucmd ) { qboolean overridAngles = qfalse; if ( ent->client->NPC_class == CLASS_PROTOCOL || ent->client->NPC_class == CLASS_R2D2 || ent->client->NPC_class == CLASS_R5D2 || ent->client->NPC_class == CLASS_GONK || ent->client->NPC_class == CLASS_MOUSE ) {//these droids *cannot* strafe (looks bad) if ( ucmd->rightmove ) { //clear the strafe ucmd->rightmove = 0; //movedir is invalid now, but PM_WalkMove will rebuild it from the ucmds, with the appropriate speed VectorClear( ent->client->ps.moveDir ); } } if ( ent->client->ps.pullAttackEntNum < ENTITYNUM_WORLD && ent->client->ps.pullAttackTime > level.time ) { return G_PullAttack( ent, ucmd ); } if ( (ent->s.number < MAX_CLIENTS||G_ControlledByPlayer(ent)) && ent->s.weapon == WP_MELEE && ent->client->ps.torsoAnim == BOTH_KYLE_GRAB ) {//see if we grabbed enemy if ( ent->client->ps.torsoAnimTimer <= 200 ) {//close to end of anim if ( G_GrabClient( ent, ucmd ) ) {//grabbed someone! } else {//missed NPC_SetAnim( ent, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); ent->client->ps.weaponTime = ent->client->ps.torsoAnimTimer; } } ucmd->rightmove = ucmd->forwardmove = ucmd->upmove = 0; } if ( (!ent->s.number&&ent->aimDebounceTime>level.time) || (ent->client->ps.pm_time && (ent->client->ps.pm_flags&PMF_TIME_KNOCKBACK)) || ent->forcePushTime > level.time ) {//being knocked back, can't do anything! ucmd->buttons = 0; ucmd->forwardmove = 0; ucmd->rightmove = 0; ucmd->upmove = 0; if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); } } overridAngles = (PM_AdjustAnglesForKnockdown( ent, ucmd, qfalse )?qtrue:overridAngles); if ( PM_GetupAnimNoMove( ent->client->ps.legsAnim ) ) { ucmd->forwardmove = ucmd->rightmove = 0;//ucmd->upmove = ? } //check saberlock if ( ent->client->ps.saberLockTime > level.time ) {//in saberlock ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; if ( ent->client->ps.saberLockTime - level.time > SABER_LOCK_DELAYED_TIME ) {//2 second delay before either can push //FIXME: base on difficulty ucmd->buttons = 0; } else { ucmd->buttons &= ~(ucmd->buttons&~BUTTON_ATTACK); } overridAngles = (PM_AdjustAnglesForSaberLock( ent, ucmd )?qtrue:overridAngles); if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); } } //check force drain if ( (ent->client->ps.forcePowersActive&(1<forwardmove = ucmd->rightmove = ucmd->upmove = 0; ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS); if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); } } if ( ent->client->ps.saberMove == LS_A_LUNGE ) {//can't move during lunge ucmd->rightmove = ucmd->upmove = 0; if ( ent->client->ps.legsAnimTimer > 500 && (ent->s.number || !player_locked) ) { ucmd->forwardmove = 127; } else { ucmd->forwardmove = 0; } if ( ent->NPC ) {//invalid now VectorClear( ent->client->ps.moveDir ); } } if ( ent->client->ps.legsAnim == BOTH_FORCEWALLRUNFLIP_ALT && ent->client->ps.legsAnimTimer ) { vec3_t vFwd, fwdAng = {0,ent->currentAngles[YAW],0}; AngleVectors( fwdAng, vFwd, NULL, NULL ); if ( ent->client->ps.groundEntityNum == ENTITYNUM_NONE ) { float savZ = ent->client->ps.velocity[2]; VectorScale( vFwd, 100, ent->client->ps.velocity ); ent->client->ps.velocity[2] = savZ; } ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; overridAngles = (PM_AdjustAnglesForWallRunUpFlipAlt( ent, ucmd )?qtrue:overridAngles); } if ( ent->client->ps.saberMove == LS_A_JUMP_T__B_ ) {//can't move during leap if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE || (!ent->s.number && player_locked) ) {//hit the ground ucmd->forwardmove = 0; } ucmd->rightmove = ucmd->upmove = 0; if ( ent->NPC ) {//invalid now VectorClear( ent->client->ps.moveDir ); } } if ( ent->client->ps.saberMove == LS_A_BACKFLIP_ATK && ent->client->ps.legsAnim == BOTH_JUMPATTACK7 ) {//backflip attack if ( ent->client->ps.legsAnimTimer > 800 //not at end && PM_AnimLength( ent->client->clientInfo.animFileIndex, BOTH_JUMPATTACK7 ) - ent->client->ps.legsAnimTimer >= 400 )//not in beginning {//middle of anim if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) {//still on ground? vec3_t yawAngles, backDir; //push backwards some? VectorSet( yawAngles, 0, ent->client->ps.viewangles[YAW]+180, 0 ); AngleVectors( yawAngles, backDir, 0, 0 ); VectorScale( backDir, 100, ent->client->ps.velocity ); //jump! ent->client->ps.velocity[2] = 180; ent->client->ps.forceJumpZStart = ent->client->ps.origin[2];//so we don't take damage if we land at same height ent->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; //FIXME: NPCs yell? G_AddEvent( ent, EV_JUMP, 0 ); G_SoundOnEnt( ent, CHAN_BODY, "sound/weapons/force/jump.wav" ); ucmd->upmove = 0;//clear any actual jump command } } ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; if ( ent->NPC ) {//invalid now VectorClear( ent->client->ps.moveDir ); } } if ( ent->client->ps.legsAnim == BOTH_JUMPATTACK6 && ent->client->ps.legsAnimTimer > 0 ) { ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; //FIXME: don't slide off people/obstacles? if ( ent->client->ps.legsAnimTimer >= 100 //not at end && PM_AnimLength( ent->client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - ent->client->ps.legsAnimTimer >= 250 )//not in beginning {//middle of anim //push forward ucmd->forwardmove = 127; } if ( (ent->client->ps.legsAnimTimer >= 900 //not at end && PM_AnimLength( ent->client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - ent->client->ps.legsAnimTimer >= 950 ) //not in beginning || ( ent->client->ps.legsAnimTimer >= 1600 && PM_AnimLength( ent->client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - ent->client->ps.legsAnimTimer >= 400 ) )//not in beginning {//one of the two jumps if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) {//still on ground? //jump! ent->client->ps.velocity[2] = 250; ent->client->ps.forceJumpZStart = ent->client->ps.origin[2];//so we don't take damage if we land at same height ent->client->ps.pm_flags |= PMF_JUMPING;//|PMF_SLOW_MO_FALL; //FIXME: NPCs yell? G_AddEvent( ent, EV_JUMP, 0 ); G_SoundOnEnt( ent, CHAN_BODY, "sound/weapons/force/jump.wav" ); } else {//FIXME: if this is the second jump, maybe we should just stop the anim? } } //else {//disallow turning unless in the middle frame when you're on the ground //overridAngles = (PM_AdjustAnglesForDualJumpAttack( ent, ucmd )?qtrue:overridAngles); } //dynamically reduce bounding box to let character sail over heads of enemies if ( ( ent->client->ps.legsAnimTimer >= 1450 && PM_AnimLength( ent->client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - ent->client->ps.legsAnimTimer >= 400 ) ||(ent->client->ps.legsAnimTimer >= 400 && PM_AnimLength( ent->client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - ent->client->ps.legsAnimTimer >= 1100 ) ) {//in a part of the anim that we're pretty much sideways in, raise up the mins ent->mins[2] = 0; ent->client->ps.pm_flags |= PMF_FIX_MINS; } else if ( (ent->client->ps.pm_flags&PMF_FIX_MINS) ) {//drop the mins back down G_FixMins( ent ); } if ( ent->NPC ) {//invalid now VectorClear( ent->client->ps.moveDir ); } } else if ( (ent->client->ps.pm_flags&PMF_FIX_MINS) ) { G_FixMins( ent ); } if ( ( ent->client->ps.legsAnim == BOTH_BUTTERFLY_FL1 && ent->client->ps.saberMove == LS_JUMPATTACK_STAFF_LEFT ) || ( ent->client->ps.legsAnim == BOTH_BUTTERFLY_FR1 && ent->client->ps.saberMove == LS_JUMPATTACK_STAFF_RIGHT ) || ( ent->client->ps.legsAnim == BOTH_BUTTERFLY_RIGHT && ent->client->ps.saberMove == LS_BUTTERFLY_RIGHT ) || ( ent->client->ps.legsAnim == BOTH_BUTTERFLY_LEFT && ent->client->ps.saberMove == LS_BUTTERFLY_LEFT ) ) {//forward flip/spin attack ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; /*if ( ent->client->ps.legsAnim == BOTH_BUTTERFLY_FL1 || ent->client->ps.legsAnim == BOTH_BUTTERFLY_FR1 || ent->client->ps.legsAnim == BOTH_BUTTERFLY_LEFT || ent->client->ps.legsAnim == BOTH_BUTTERFLY RIGHT )*/ { //FIXME: don't slide off people/obstacles? if ( ent->client->ps.legsAnim != BOTH_BUTTERFLY_LEFT && ent->client->ps.legsAnim != BOTH_BUTTERFLY_RIGHT ) { if ( ent->client->ps.legsAnimTimer >= 100 //not at end && PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.legsAnim ) - ent->client->ps.legsAnimTimer >= 250 )//not in beginning {//middle of anim //push forward ucmd->forwardmove = 127; } } if ( ent->client->ps.legsAnimTimer >= 1700 && ent->client->ps.legsAnimTimer < 1800 ) {//one of the two jumps if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) {//still on ground? //jump! ent->client->ps.velocity[2] = 250; ent->client->ps.forceJumpZStart = ent->client->ps.origin[2];//so we don't take damage if we land at same height ent->client->ps.pm_flags |= PMF_JUMPING;//|PMF_SLOW_MO_FALL; //FIXME: NPCs yell? G_AddEvent( ent, EV_JUMP, 0 ); G_SoundOnEnt( ent, CHAN_BODY, "sound/weapons/force/jump.wav" ); } else {//FIXME: if this is the second jump, maybe we should just stop the anim? } } //disallow turning unless in the middle frame when you're on the ground //overridAngles = (PM_AdjustAnglesForDualJumpAttack( ent, ucmd )?qtrue:overridAngles); } if ( ent->NPC ) {//invalid now VectorClear( ent->client->ps.moveDir ); } } if ( ent->client->ps.legsAnim == BOTH_A7_SOULCAL && ent->client->ps.saberMove == LS_STAFF_SOULCAL ) {//forward spinning staff attack ucmd->upmove = 0; if ( PM_CanRollFromSoulCal( &ent->client->ps ) ) { ucmd->upmove = -127; } else { ucmd->rightmove = 0; //FIXME: don't slide off people/obstacles? if ( ent->client->ps.legsAnimTimer >= 2750 ) {//not at end //push forward ucmd->forwardmove = 64; } else { ucmd->forwardmove = 0; } } if ( ent->client->ps.legsAnimTimer >= 2650 && ent->client->ps.legsAnimTimer < 2850 ) {//the jump if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) {//still on ground? //jump! ent->client->ps.velocity[2] = 250; ent->client->ps.forceJumpZStart = ent->client->ps.origin[2];//so we don't take damage if we land at same height ent->client->ps.pm_flags |= PMF_JUMPING;//|PMF_SLOW_MO_FALL; //FIXME: NPCs yell? G_AddEvent( ent, EV_JUMP, 0 ); G_SoundOnEnt( ent, CHAN_BODY, "sound/weapons/force/jump.wav" ); } } if ( ent->NPC ) {//invalid now VectorClear( ent->client->ps.moveDir ); } } if ( ent->client->ps.torsoAnim == BOTH_LK_DL_S_T_SB_1_W ) { G_CamPullBackForLegsAnim( ent, qtrue ); } if ( ent->client->ps.torsoAnim == BOTH_A6_FB || ent->client->ps.torsoAnim == BOTH_A6_LR ) {//can't turn or move during dual attack if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); } overridAngles = (PM_LockAngles( ent, ucmd )?qtrue:overridAngles); ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; } else if ( ent->client->ps.legsAnim == BOTH_ROLL_STAB ) { if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); } overridAngles = (PM_LockAngles( ent, ucmd )?qtrue:overridAngles); ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; if ( ent->client->ps.legsAnimTimer ) { ucmd->upmove = -127; } if ( ent->client->ps.dualSabers && ent->client->ps.saber[1].Active() ) { G_CamPullBackForLegsAnim( ent ); } } else if ( PM_SuperBreakLoseAnim( ent->client->ps.torsoAnim ) ) {//can't turn during Kyle's grappling if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); } overridAngles = (PM_LockAngles( ent, ucmd )?qtrue:overridAngles); ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; if ( ent->client->ps.legsAnim == BOTH_LK_DL_ST_T_SB_1_L ) { PM_CmdForRoll( &ent->client->ps, ucmd ); if ( ent->NPC ) {//invalid now VectorClear( ent->client->ps.moveDir ); } ent->client->ps.speed = 400; } } else if ( ent->client->ps.torsoAnim==BOTH_FORCE_DRAIN_GRAB_START || ent->client->ps.torsoAnim==BOTH_FORCE_DRAIN_GRAB_HOLD || ent->client->ps.torsoAnim==BOTH_FORCE_DRAIN_GRAB_END || ent->client->ps.legsAnim==BOTH_FORCE_DRAIN_GRABBED ) {//can't turn or move if ( ent->s.number < MAX_CLIENTS || G_ControlledByPlayer(ent) ) {//player float forceDrainAngle = 90.0f; if ( ent->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_START ) {//starting drain float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.torsoAnim ); float elapsedTime = (float)(animLength-ent->client->ps.legsAnimTimer); float angle = (elapsedTime/animLength)*forceDrainAngle; cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_ANG; cg.overrides.thirdPersonAngle = cg_thirdPersonAngle.value+angle; } else if ( ent->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_HOLD ) {//draining cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_ANG; cg.overrides.thirdPersonAngle = cg_thirdPersonAngle.value+forceDrainAngle; } else if ( ent->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_END ) {//ending drain float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.torsoAnim ); float elapsedTime = (float)(animLength-ent->client->ps.legsAnimTimer); float angle = forceDrainAngle-((elapsedTime/animLength)*forceDrainAngle); cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_ANG; cg.overrides.thirdPersonAngle = cg_thirdPersonAngle.value+angle; } } if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); } ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; overridAngles = PM_LockAngles( ent, ucmd )?qtrue:overridAngles; } else if ( ent->client->ps.legsAnim==BOTH_PLAYER_PA_1 || ent->client->ps.legsAnim==BOTH_PLAYER_PA_2 || ent->client->ps.legsAnim==BOTH_PLAYER_PA_3 || ent->client->ps.legsAnim==BOTH_PLAYER_PA_3_FLY || ent->client->ps.legsAnim==BOTH_KYLE_PA_1 || ent->client->ps.legsAnim==BOTH_KYLE_PA_2 || ent->client->ps.legsAnim==BOTH_KYLE_PA_3 ) {//can't turn during Kyle's grappling if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); } overridAngles = PM_AdjustAnglesForGrapple( ent, ucmd )?qtrue:overridAngles; //if ( g_debugMelee->integer ) {//actually do some damage during sequence int damage = 0; int dflags = (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_ARMOR|DAMAGE_NO_KILL); if ( TIMER_Done( ent, "grappleDamageDebounce" ) ) { switch ( ent->client->ps.legsAnim ) { case BOTH_PLAYER_PA_1: if ( ent->client->ps.legsAnimTimer <= 3150 && ent->client->ps.legsAnimTimer > 3050 ) { TIMER_Set( ent, "grappleDamageDebounce", 150 ); if ( ent->s.number < MAX_CLIENTS ) {//special case damage = Q_irand( 1, 3 ); } else { damage = Q_irand( 3, 5 ); } } if ( ent->client->ps.legsAnimTimer <= 1150 && ent->client->ps.legsAnimTimer > 10500 ) { TIMER_Set( ent, "grappleDamageDebounce", 150 ); if ( ent->s.number < MAX_CLIENTS ) {//special case damage = Q_irand( 3, 5 ); } else { damage = Q_irand( 5, 8 ); } } if ( ent->client->ps.legsAnimTimer <= 100 ) { TIMER_Set( ent, "grappleDamageDebounce", 150 ); if ( ent->s.number < MAX_CLIENTS ) {//special case damage = Q_irand( 5, 8 ); } else { damage = Q_irand( 10, 20 ); } dflags &= ~DAMAGE_NO_KILL; } break; case BOTH_PLAYER_PA_2: if ( ent->client->ps.legsAnimTimer <= 5700 && ent->client->ps.legsAnimTimer > 5600 ) { TIMER_Set( ent, "grappleDamageDebounce", 150 ); if ( ent->s.number < MAX_CLIENTS ) {//special case damage = Q_irand( 3, 6 ); } else { damage = Q_irand( 7, 10 ); } } if ( ent->client->ps.legsAnimTimer <= 5200 && ent->client->ps.legsAnimTimer > 5100 ) { TIMER_Set( ent, "grappleDamageDebounce", 150 ); if ( ent->s.number < MAX_CLIENTS ) {//special case damage = Q_irand( 1, 3 ); } else { damage = Q_irand( 3, 5 ); } } if ( ent->client->ps.legsAnimTimer <= 4550 && ent->client->ps.legsAnimTimer > 4450 ) { TIMER_Set( ent, "grappleDamageDebounce", 150 ); if ( ent->s.number < MAX_CLIENTS ) {//special case damage = Q_irand( 3, 6 ); } else { damage = Q_irand( 10, 15 ); } } if ( ent->client->ps.legsAnimTimer <= 3550 && ent->client->ps.legsAnimTimer > 3450 ) { TIMER_Set( ent, "grappleDamageDebounce", 150 ); if ( ent->s.number < MAX_CLIENTS ) {//special case damage = Q_irand( 3, 6 ); } else { damage = Q_irand( 10, 20 ); } dflags &= ~DAMAGE_NO_KILL; } break; case BOTH_PLAYER_PA_3: if ( ent->client->ps.legsAnimTimer <= 3800 && ent->client->ps.legsAnimTimer > 3700 ) { TIMER_Set( ent, "grappleDamageDebounce", 150 ); if ( ent->s.number < MAX_CLIENTS ) {//special case damage = Q_irand( 2, 5 ); } else { damage = Q_irand( 5, 8 ); } } if ( ent->client->ps.legsAnimTimer <= 3100 && ent->client->ps.legsAnimTimer > 3000 ) { TIMER_Set( ent, "grappleDamageDebounce", 150 ); if ( ent->s.number < MAX_CLIENTS ) {//special case damage = Q_irand( 2, 5 ); } else { damage = Q_irand( 5, 8 ); } } if ( ent->client->ps.legsAnimTimer <= 1650 && ent->client->ps.legsAnimTimer > 1550 ) { TIMER_Set( ent, "grappleDamageDebounce", 150 ); if ( ent->s.number < MAX_CLIENTS ) {//special case damage = Q_irand( 3, 6 ); } else { damage = Q_irand( 7, 12 ); } } break; case BOTH_PLAYER_PA_3_FLY: /* if ( ent->s.number < MAX_CLIENTS ) {//special case if ( ent->client->ps.legsAnimTimer > PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME && ent->client->ps.legsAnimTimer <= PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME + 100 ) { TIMER_Set( ent, "grappleDamageDebounce", 150 ); damage = Q_irand( 4, 8 ); dflags &= ~DAMAGE_NO_KILL; } } else*/ if ( ent->client->ps.legsAnimTimer <= 100 ) { TIMER_Set( ent, "grappleDamageDebounce", 150 ); damage = Q_irand( 10, 20 ); dflags &= ~DAMAGE_NO_KILL; } break; } if ( damage ) { G_Damage( ent, ent->enemy, ent->enemy, NULL, ent->currentOrigin, damage, dflags, MOD_MELEE );//MOD_IMPACT? } } } qboolean clearMove = qtrue; int endOfAnimTime = 100; if ( ent->s.number < MAX_CLIENTS && ent->client->ps.legsAnim == BOTH_PLAYER_PA_3_FLY ) {//player holds extra long so you have more time to decide to do the quick getup //endOfAnimTime += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME; if ( ent->client->ps.legsAnimTimer <= endOfAnimTime )//PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME ) { clearMove = qfalse; } } if ( clearMove ) { ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; } if ( ent->client->ps.legsAnimTimer <= endOfAnimTime ) {//pretty much done with the anim, so get up now if ( ent->client->ps.legsAnim==BOTH_PLAYER_PA_3 ) { vec3_t ang = {10,ent->currentAngles[YAW],0}; vec3_t throwDir; AngleVectors( ang, throwDir, NULL, NULL ); if ( !(ent->flags&FL_NO_KNOCKBACK) ) { G_Throw( ent, throwDir, -100 ); } ent->client->ps.legsAnimTimer = ent->client->ps.torsoAnimTimer = 0; NPC_SetAnim( ent, SETANIM_BOTH, BOTH_PLAYER_PA_3_FLY, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); ent->client->ps.weaponTime = ent->client->ps.legsAnimTimer; /* if ( ent->s.number < MAX_CLIENTS ) {//player holds extra long so you have more time to decide to do the quick getup ent->client->ps.legsAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME; ent->client->ps.torsoAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME; } */ //force-thrown - FIXME: start earlier? ent->forcePushTime = level.time + 500; } else if ( ent->client->ps.legsAnim==BOTH_PLAYER_PA_1 ) { vec3_t ang = {10,ent->currentAngles[YAW],0}; vec3_t throwDir; AngleVectors( ang, throwDir, NULL, NULL ); if ( !(ent->flags&FL_NO_KNOCKBACK) ) { G_Throw( ent, throwDir, -100 ); } ent->client->ps.legsAnimTimer = ent->client->ps.torsoAnimTimer = 0; NPC_SetAnim( ent, SETANIM_BOTH, BOTH_KNOCKDOWN2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); ent->client->ps.weaponTime = ent->client->ps.legsAnimTimer; if ( ent->s.number < MAX_CLIENTS ) {//player holds extra long so you have more time to decide to do the quick getup ent->client->ps.legsAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME; ent->client->ps.torsoAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME; } } //FIXME: should end so you can do a getup /* else if ( ent->client->ps.legsAnim==BOTH_PLAYER_PA_2 ) { ent->client->ps.legsAnimTimer = ent->client->ps.torsoAnimTimer = 0; NPC_SetAnim( ent, SETANIM_BOTH, BOTH_GETUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); ent->client->ps.weaponTime = ent->client->ps.legsAnimTimer; } */ } if ( d_slowmodeath->integer <= 3 && ent->s.number < MAX_CLIENTS ) {//no matrix effect, so slide the camera back and to the side G_CamPullBackForLegsAnim( ent ); G_CamCircleForLegsAnim( ent ); } } else if ( ent->client->ps.legsAnim == BOTH_FORCELONGLEAP_START || ent->client->ps.legsAnim == BOTH_FORCELONGLEAP_ATTACK || ent->client->ps.legsAnim == BOTH_FORCELONGLEAP_LAND ) {//can't turn during force leap if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); } overridAngles = PM_AdjustAnglesForLongJump( ent, ucmd )?qtrue:overridAngles; } else if ( PM_KickingAnim( ent->client->ps.legsAnim ) || ent->client->ps.legsAnim == BOTH_ARIAL_F1 || ent->client->ps.legsAnim == BOTH_ARIAL_LEFT || ent->client->ps.legsAnim == BOTH_ARIAL_RIGHT || ent->client->ps.legsAnim == BOTH_CARTWHEEL_LEFT || ent->client->ps.legsAnim == BOTH_CARTWHEEL_RIGHT || ent->client->ps.legsAnim == BOTH_JUMPATTACK7 || ent->client->ps.legsAnim == BOTH_BUTTERFLY_FL1 || ent->client->ps.legsAnim == BOTH_BUTTERFLY_FR1 || ent->client->ps.legsAnim == BOTH_BUTTERFLY_RIGHT || ent->client->ps.legsAnim == BOTH_BUTTERFLY_LEFT || ent->client->ps.legsAnim == BOTH_A7_SOULCAL ) {//can't move, check for damage frame if ( PM_KickingAnim( ent->client->ps.legsAnim ) ) { ucmd->forwardmove = ucmd->rightmove = 0; if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); } } vec3_t kickDir={0,0,0}, kickDir2={0,0,0}, kickEnd={0,0,0}, kickEnd2={0,0,0}, fwdAngs = {0,ent->client->ps.viewangles[YAW],0}; float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.legsAnim ); float elapsedTime = (float)(animLength-ent->client->ps.legsAnimTimer); float remainingTime = (animLength-elapsedTime); float kickDist = (ent->maxs[0]*1.5f)+STAFF_KICK_RANGE+8.0f;//fudge factor of 8 float kickDist2 = kickDist; int kickDamage = Q_irand( 3, 8 ); int kickDamage2 = Q_irand( 3, 8 ); int kickPush = Q_flrand( 100.0f, 200.0f );//Q_flrand( 50.0f, 100.0f ); int kickPush2 = Q_flrand( 100.0f, 200.0f );//Q_flrand( 50.0f, 100.0f ); qboolean doKick = qfalse; qboolean doKick2 = qfalse; qboolean kickSoundOnWalls = qfalse; //HMM... or maybe trace from origin to footRBolt/footLBolt? Which one? G2 trace? Will do hitLoc, if so... if ( ent->client->ps.torsoAnim == BOTH_A7_HILT ) { if ( elapsedTime >= 250 && remainingTime >= 250 ) {//front doKick = qtrue; if ( ent->handRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ent->handRBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); } } } else { switch ( ent->client->ps.legsAnim ) { case BOTH_A7_SOULCAL: kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); if ( elapsedTime >= 1400 && elapsedTime <= 1500 ) {//right leg doKick = qtrue; if ( ent->footRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); } } break; case BOTH_ARIAL_F1: if ( elapsedTime >= 550 && elapsedTime <= 1000 ) {//right leg doKick = qtrue; if ( ent->footRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); } } if ( elapsedTime >= 800 && elapsedTime <= 1200 ) {//left leg doKick2 = qtrue; if ( ent->footLBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ent->footLBolt, kickEnd2 ); VectorSubtract( kickEnd2, ent->currentOrigin, kickDir2 ); kickDir2[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir2 ); } else {//guess AngleVectors( fwdAngs, kickDir2, NULL, NULL ); } } break; case BOTH_ARIAL_LEFT: case BOTH_ARIAL_RIGHT: if ( elapsedTime >= 200 && elapsedTime <= 600 ) {//lead leg int footBolt = (ent->client->ps.legsAnim==BOTH_ARIAL_LEFT)?ent->footRBolt:ent->footLBolt;//mirrored anims doKick = qtrue; if ( footBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, footBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); } } if ( elapsedTime >= 400 && elapsedTime <= 850 ) {//trailing leg int footBolt = (ent->client->ps.legsAnim==BOTH_ARIAL_LEFT)?ent->footLBolt:ent->footRBolt;//mirrored anims doKick2 = qtrue; if ( footBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, footBolt, kickEnd2 ); VectorSubtract( kickEnd2, ent->currentOrigin, kickDir2 ); kickDir2[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir2 ); } else {//guess AngleVectors( fwdAngs, kickDir2, NULL, NULL ); } } break; case BOTH_CARTWHEEL_LEFT: case BOTH_CARTWHEEL_RIGHT: if ( elapsedTime >= 200 && elapsedTime <= 600 ) {//lead leg int footBolt = (ent->client->ps.legsAnim==BOTH_CARTWHEEL_LEFT)?ent->footRBolt:ent->footLBolt;//mirrored anims doKick = qtrue; if ( footBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, footBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); } } if ( elapsedTime >= 350 && elapsedTime <= 650 ) {//trailing leg int footBolt = (ent->client->ps.legsAnim==BOTH_CARTWHEEL_LEFT)?ent->footLBolt:ent->footRBolt;//mirrored anims doKick2 = qtrue; if ( footBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, footBolt, kickEnd2 ); VectorSubtract( kickEnd2, ent->currentOrigin, kickDir2 ); kickDir2[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir2 ); } else {//guess AngleVectors( fwdAngs, kickDir2, NULL, NULL ); } } break; case BOTH_JUMPATTACK7: if ( elapsedTime >= 300 && elapsedTime <= 900 ) {//right knee doKick = qtrue; if ( ent->kneeRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ent->kneeRBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); } } if ( elapsedTime >= 600 && elapsedTime <= 900 ) {//left leg doKick2 = qtrue; if ( ent->footLBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ent->footLBolt, kickEnd2 ); VectorSubtract( kickEnd2, ent->currentOrigin, kickDir2 ); kickDir2[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir2 ); } else {//guess AngleVectors( fwdAngs, kickDir2, NULL, NULL ); } } break; case BOTH_BUTTERFLY_FL1: case BOTH_BUTTERFLY_FR1: if ( elapsedTime >= 950 && elapsedTime <= 1300 ) {//lead leg int footBolt = (ent->client->ps.legsAnim==BOTH_BUTTERFLY_FL1)?ent->footRBolt:ent->footLBolt;//mirrored anims doKick = qtrue; if ( footBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, footBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); } } if ( elapsedTime >= 1150 && elapsedTime <= 1600 ) {//trailing leg int footBolt = (ent->client->ps.legsAnim==BOTH_BUTTERFLY_FL1)?ent->footLBolt:ent->footRBolt;//mirrored anims doKick2 = qtrue; if ( footBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, footBolt, kickEnd2 ); VectorSubtract( kickEnd2, ent->currentOrigin, kickDir2 ); kickDir2[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir2 ); } else {//guess AngleVectors( fwdAngs, kickDir2, NULL, NULL ); } } break; case BOTH_BUTTERFLY_LEFT: case BOTH_BUTTERFLY_RIGHT: if ( (elapsedTime >= 100 && elapsedTime <= 450) || (elapsedTime >= 1100 && elapsedTime <= 1350) ) {//lead leg int footBolt = (ent->client->ps.legsAnim==BOTH_BUTTERFLY_LEFT)?ent->footLBolt:ent->footRBolt;//mirrored anims doKick = qtrue; if ( footBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, footBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); } } if ( elapsedTime >= 900 && elapsedTime <= 1600 ) {//trailing leg int footBolt = (ent->client->ps.legsAnim==BOTH_BUTTERFLY_LEFT)?ent->footRBolt:ent->footLBolt;//mirrored anims doKick2 = qtrue; if ( footBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, footBolt, kickEnd2 ); VectorSubtract( kickEnd2, ent->currentOrigin, kickDir2 ); kickDir2[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir2 ); } else {//guess AngleVectors( fwdAngs, kickDir2, NULL, NULL ); } } break; case BOTH_GETUP_BROLL_B: case BOTH_GETUP_BROLL_F: case BOTH_GETUP_FROLL_B: case BOTH_GETUP_FROLL_F: kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); if ( elapsedTime >= 250 && remainingTime >= 250 ) {//front doKick = qtrue; if ( ent->footRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); } } if ( ent->client->ps.legsAnim == BOTH_GETUP_BROLL_B || ent->client->ps.legsAnim == BOTH_GETUP_FROLL_B ) {//rolling back, pull back the view G_CamPullBackForLegsAnim( ent ); } break; case BOTH_A7_KICK_F_AIR: kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); kickSoundOnWalls = qtrue; if ( elapsedTime >= 100 && remainingTime >= 500 ) {//front doKick = qtrue; if ( ent->footRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); } } break; case BOTH_A7_KICK_F: kickSoundOnWalls = qtrue; //FIXME: push forward? if ( elapsedTime >= 250 && remainingTime >= 250 ) {//front doKick = qtrue; if ( ent->footRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); } } break; case BOTH_A7_KICK_B_AIR: kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); kickSoundOnWalls = qtrue; if ( elapsedTime >= 100 && remainingTime >= 400 ) {//back doKick = qtrue; if ( ent->footRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); VectorScale( kickDir, -1, kickDir ); } } break; case BOTH_A7_KICK_B: kickSoundOnWalls = qtrue; if ( elapsedTime >= 250 && remainingTime >= 250 ) {//back doKick = qtrue; if ( ent->footRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, kickDir, NULL, NULL ); VectorScale( kickDir, -1, kickDir ); } } break; case BOTH_A7_KICK_R_AIR: kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); kickSoundOnWalls = qtrue; if ( elapsedTime >= 150 && remainingTime >= 300 ) {//left doKick = qtrue; if ( ent->footLBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ent->footLBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, NULL, kickDir, NULL ); VectorScale( kickDir, -1, kickDir ); } } break; case BOTH_A7_KICK_R: kickSoundOnWalls = qtrue; //FIXME: push right? if ( elapsedTime >= 250 && remainingTime >= 250 ) {//right doKick = qtrue; if ( ent->footRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, NULL, kickDir, NULL ); } } break; case BOTH_A7_KICK_L_AIR: kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); kickSoundOnWalls = qtrue; if ( elapsedTime >= 150 && remainingTime >= 300 ) {//left doKick = qtrue; if ( ent->footLBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ent->footLBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, NULL, kickDir, NULL ); VectorScale( kickDir, -1, kickDir ); } } break; case BOTH_A7_KICK_L: kickSoundOnWalls = qtrue; //FIXME: push left? if ( elapsedTime >= 250 && remainingTime >= 250 ) {//left doKick = qtrue; if ( ent->footLBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ent->footLBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); } else {//guess AngleVectors( fwdAngs, NULL, kickDir, NULL ); VectorScale( kickDir, -1, kickDir ); } } break; case BOTH_A7_KICK_S: kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); if ( ent->footRBolt != -1 ) {//actually trace to a bolt if ( elapsedTime >= 550 && elapsedTime <= 1050 ) { doKick = qtrue; G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); //NOTE: have to fudge this a little because it's not getting enough range with the anim as-is VectorMA( kickEnd, 8.0f, kickDir, kickEnd ); } } else {//guess if ( elapsedTime >= 400 && elapsedTime < 500 ) {//front doKick = qtrue; AngleVectors( fwdAngs, kickDir, NULL, NULL ); } else if ( elapsedTime >= 500 && elapsedTime < 600 ) {//front-right? doKick = qtrue; fwdAngs[YAW] += 45; AngleVectors( fwdAngs, kickDir, NULL, NULL ); } else if ( elapsedTime >= 600 && elapsedTime < 700 ) {//right doKick = qtrue; AngleVectors( fwdAngs, NULL, kickDir, NULL ); } else if ( elapsedTime >= 700 && elapsedTime < 800 ) {//back-right? doKick = qtrue; fwdAngs[YAW] += 45; AngleVectors( fwdAngs, NULL, kickDir, NULL ); } else if ( elapsedTime >= 800 && elapsedTime < 900 ) {//back doKick = qtrue; AngleVectors( fwdAngs, kickDir, NULL, NULL ); VectorScale( kickDir, -1, kickDir ); } else if ( elapsedTime >= 900 && elapsedTime < 1000 ) {//back-left? doKick = qtrue; fwdAngs[YAW] += 45; AngleVectors( fwdAngs, kickDir, NULL, NULL ); } else if ( elapsedTime >= 1000 && elapsedTime < 1100 ) {//left doKick = qtrue; AngleVectors( fwdAngs, NULL, kickDir, NULL ); VectorScale( kickDir, -1, kickDir ); } else if ( elapsedTime >= 1100 && elapsedTime < 1200 ) {//front-left? doKick = qtrue; fwdAngs[YAW] += 45; AngleVectors( fwdAngs, NULL, kickDir, NULL ); VectorScale( kickDir, -1, kickDir ); } } break; case BOTH_A7_KICK_BF: kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); if ( elapsedTime < 1500 ) {//auto-aim! overridAngles = PM_AdjustAnglesForBFKick( ent, ucmd, fwdAngs, qboolean(elapsedTime<850) )?qtrue:overridAngles; //FIXME: if we haven't done the back kick yet and there's no-one there to // kick anymore, go into some anim that returns us to our base stance } if ( ent->footRBolt != -1 ) {//actually trace to a bolt if ( ( elapsedTime >= 750 && elapsedTime < 850 ) || ( elapsedTime >= 1400 && elapsedTime < 1500 ) ) {//right, though either would do doKick = qtrue; G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); //NOTE: have to fudge this a little because it's not getting enough range with the anim as-is VectorMA( kickEnd, 8, kickDir, kickEnd ); } } else {//guess if ( elapsedTime >= 250 && elapsedTime < 350 ) {//front doKick = qtrue; AngleVectors( fwdAngs, kickDir, NULL, NULL ); } else if ( elapsedTime >= 350 && elapsedTime < 450 ) {//back doKick = qtrue; AngleVectors( fwdAngs, kickDir, NULL, NULL ); VectorScale( kickDir, -1, kickDir ); } } break; case BOTH_A7_KICK_RL: kickSoundOnWalls = qtrue; kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); //FIXME: auto aim at enemies on the side of us? //overridAngles = PM_AdjustAnglesForRLKick( ent, ucmd, fwdAngs, qboolean(elapsedTime<850) )?qtrue:overridAngles; if ( elapsedTime >= 250 && elapsedTime < 350 ) {//right doKick = qtrue; if ( ent->footRBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); //NOTE: have to fudge this a little because it's not getting enough range with the anim as-is VectorMA( kickEnd, 8, kickDir, kickEnd ); } else {//guess AngleVectors( fwdAngs, NULL, kickDir, NULL ); } } else if ( elapsedTime >= 350 && elapsedTime < 450 ) {//left doKick = qtrue; if ( ent->footLBolt != -1 ) {//actually trace to a bolt G_GetBoltPosition( ent, ent->footLBolt, kickEnd ); VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); kickDir[2] = 0;//ah, flatten it, I guess... VectorNormalize( kickDir ); //NOTE: have to fudge this a little because it's not getting enough range with the anim as-is VectorMA( kickEnd, 8, kickDir, kickEnd ); } else {//guess AngleVectors( fwdAngs, NULL, kickDir, NULL ); VectorScale( kickDir, -1, kickDir ); } } break; } } if ( doKick ) { G_KickTrace( ent, kickDir, kickDist, kickEnd, kickDamage, kickPush, kickSoundOnWalls ); } if ( doKick2 ) { G_KickTrace( ent, kickDir2, kickDist2, kickEnd2, kickDamage2, kickPush2, kickSoundOnWalls ); } } else if ( ent->client->ps.saberMove == LS_DUAL_FB ) { //pull back the view G_CamPullBackForLegsAnim( ent ); } else if ( ent->client->ps.saberMove == LS_A_BACK || ent->client->ps.saberMove == LS_A_BACK_CR || ent->client->ps.saberMove == LS_A_BACKSTAB ) {//can't move or turn during back attacks ucmd->forwardmove = ucmd->rightmove = 0; if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); } if ( (overridAngles = (PM_AdjustAnglesForBackAttack( ent, ucmd )?qtrue:overridAngles)) == qtrue ) { //pull back the view G_CamPullBackForLegsAnim( ent ); } } else if ( ent->client->ps.torsoAnim == BOTH_WALL_FLIP_BACK1 || ent->client->ps.torsoAnim == BOTH_WALL_FLIP_BACK2 || ent->client->ps.legsAnim == BOTH_FORCEWALLRUNFLIP_END || ent->client->ps.legsAnim == BOTH_FORCEWALLREBOUND_BACK ) { //pull back the view G_CamPullBackForLegsAnim( ent ); } else if ( ent->client->ps.torsoAnim == BOTH_A6_SABERPROTECT ) { ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); ent->client->ps.forceJumpCharge = 0; } if ( !ent->s.number ) { float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.torsoAnim ); float elapsedTime = (float)(animLength-ent->client->ps.torsoAnimTimer); float backDist = 0; if ( elapsedTime <= 300.0f ) {//starting anim backDist = (elapsedTime/300.0f)*90.0f; } else if ( ent->client->ps.torsoAnimTimer <= 300.0f ) {//ending anim backDist = (ent->client->ps.torsoAnimTimer/300.0f)*90.0f; } else {//in middle of anim backDist = 90.0f; } //back off and look down cg.overrides.active |= (CG_OVERRIDE_3RD_PERSON_RNG|CG_OVERRIDE_3RD_PERSON_POF); cg.overrides.thirdPersonRange = cg_thirdPersonRange.value+backDist; cg.overrides.thirdPersonPitchOffset = cg_thirdPersonPitchOffset.value+(backDist/2.0f); } overridAngles = (PM_AdjustAnglesForSpinProtect( ent, ucmd )?qtrue:overridAngles); } else if ( ent->client->ps.legsAnim == BOTH_A3_SPECIAL ) {//push forward float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.torsoAnim ); float elapsedTime = (float)(animLength-ent->client->ps.torsoAnimTimer); ucmd->upmove = ucmd->rightmove = 0; ucmd->forwardmove = 0; if ( elapsedTime >= 350 && elapsedTime < 1500 ) {//push forward ucmd->forwardmove = 64; ent->client->ps.speed = 200.0f; } //FIXME: pull back camera? } else if ( ent->client->ps.legsAnim == BOTH_A2_SPECIAL ) {//push forward ucmd->upmove = ucmd->rightmove = 0; ucmd->forwardmove = 0; if ( ent->client->ps.legsAnimTimer > 200.0f ) { float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.torsoAnim ); float elapsedTime = (float)(animLength-ent->client->ps.torsoAnimTimer); if ( elapsedTime < 750 || (elapsedTime >= 1650 && elapsedTime < 2400) ) {//push forward ucmd->forwardmove = 64; ent->client->ps.speed = 200.0f; } } //FIXME: pull back camera? }//FIXME: fast special? else if ( ent->client->ps.legsAnim == BOTH_A1_SPECIAL && (ucmd->forwardmove || ucmd->rightmove || (VectorCompare( ent->client->ps.moveDir, vec3_origin )&&ent->client->ps.speed>0)) ) {//moving during full-body fast special ent->client->ps.legsAnimTimer = 0;//don't hold this legsAnim, allow them to run //FIXME: just add this to the list of overridable special moves in PM_Footsteps? } else if ( ent->client->ps.legsAnim == BOTH_FLIP_LAND ) {//moving during full-body fast special float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.torsoAnim ); float elapsedTime = (float)(animLength-ent->client->ps.torsoAnimTimer); ucmd->upmove = ucmd->rightmove = ucmd->forwardmove = 0; if ( elapsedTime > 600 && elapsedTime < 800 && ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) {//jump - FIXME: how do we stop double-jumps? ent->client->ps.pm_flags |= PMF_JUMP_HELD; ent->client->ps.groundEntityNum = ENTITYNUM_NONE; ent->client->ps.jumpZStart = ent->currentOrigin[2]; ent->client->ps.velocity[2] = JUMP_VELOCITY; G_AddEvent( ent, EV_JUMP, 0 ); } } else if ( ( PM_SuperBreakWinAnim( ent->client->ps.torsoAnim ) || PM_SuperBreakLoseAnim( ent->client->ps.torsoAnim ) ) && ent->client->ps.torsoAnimTimer ) {//can't move or turn ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); ent->client->ps.forceJumpCharge = 0; } overridAngles = (PM_LockAngles( ent, ucmd )?qtrue:overridAngles); } else if ( BG_FullBodyTauntAnim( ent->client->ps.legsAnim ) && BG_FullBodyTauntAnim( ent->client->ps.torsoAnim ) ) { if ( (ucmd->buttons&BUTTON_ATTACK) || (ucmd->buttons&BUTTON_ALT_ATTACK) || (ucmd->buttons&BUTTON_USE_FORCE) || (ucmd->buttons&BUTTON_FORCEGRIP) || (ucmd->buttons&BUTTON_FORCE_LIGHTNING) || (ucmd->buttons&BUTTON_FORCE_DRAIN) || ucmd->upmove ) {//stop the anim if ( ent->client->ps.legsAnim == BOTH_MEDITATE && ent->client->ps.torsoAnim == BOTH_MEDITATE ) { NPC_SetAnim( ent, SETANIM_BOTH, BOTH_MEDITATE_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); } else { ent->client->ps.legsAnimTimer = ent->client->ps.torsoAnimTimer = 0; } } else { if ( ent->client->ps.legsAnim == BOTH_MEDITATE ) { if ( ent->client->ps.legsAnimTimer < 100 ) { ent->client->ps.legsAnimTimer = 100; } } if ( ent->client->ps.torsoAnim == BOTH_MEDITATE ) { if ( ent->client->ps.torsoAnimTimer < 100 ) { ent->client->ps.legsAnimTimer = 100; } } if ( ent->client->ps.legsAnimTimer > 0 || ent->client->ps.torsoAnimTimer > 0 ) { ucmd->rightmove = 0; ucmd->upmove = 0; ucmd->forwardmove = 0; ucmd->buttons = 0; if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); ent->client->ps.forceJumpCharge = 0; } overridAngles = (PM_LockAngles( ent, ucmd )?qtrue:overridAngles); } } } else if ( ent->client->ps.legsAnim == BOTH_MEDITATE_END && ent->client->ps.legsAnimTimer > 0 ) { ucmd->rightmove = 0; ucmd->upmove = 0; ucmd->forwardmove = 0; ucmd->buttons = 0; if ( ent->NPC ) { VectorClear( ent->client->ps.moveDir ); ent->client->ps.forceJumpCharge = 0; } overridAngles = (PM_LockAngles( ent, ucmd )?qtrue:overridAngles); } else if ( !ent->s.number ) { if ( ent->client->NPC_class != CLASS_ATST ) { // Not in a vehicle. if ( ent->s.m_iVehicleNum == 0 ) { if ( !MatrixMode ) { cg.overrides.active &= ~(CG_OVERRIDE_3RD_PERSON_RNG|CG_OVERRIDE_3RD_PERSON_POF|CG_OVERRIDE_3RD_PERSON_ANG); cg.overrides.thirdPersonRange = 0; } } } } if ( PM_InRoll( &ent->client->ps ) ) { if ( ent->s.number >= MAX_CLIENTS || !player_locked ) { //FIXME: NPCs should try to face us during this roll, so they roll around us...? PM_CmdForRoll( &ent->client->ps, ucmd ); if ( ent->s.number >= MAX_CLIENTS ) {//make sure it doesn't roll me off a ledge if ( !G_CheckRollSafety( ent, ent->client->ps.legsAnim, 24 ) ) {//crap! I guess all we can do is stop... UGH ucmd->rightmove = ucmd->forwardmove = 0; } } } if ( ent->NPC ) {//invalid now VectorClear( ent->client->ps.moveDir ); } ent->client->ps.speed = 400; } if ( PM_InCartwheel( ent->client->ps.legsAnim ) ) {//can't keep moving in cartwheel if ( ent->client->ps.legsAnimTimer > 100 ) {//still have time left in the anim ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; if ( ent->NPC ) {//invalid now VectorClear( ent->client->ps.moveDir ); } if ( ent->s.number || !player_locked ) { switch ( ent->client->ps.legsAnim ) { case BOTH_ARIAL_LEFT: case BOTH_CARTWHEEL_LEFT: ucmd->rightmove = -127; break; case BOTH_ARIAL_RIGHT: case BOTH_CARTWHEEL_RIGHT: ucmd->rightmove = 127; break; case BOTH_ARIAL_F1: ucmd->forwardmove = 127; break; default: break; } } } } if ( ent->client->ps.legsAnim == BOTH_BUTTERFLY_LEFT || ent->client->ps.legsAnim == BOTH_BUTTERFLY_RIGHT ) { if ( ent->client->ps.legsAnimTimer > 100 ) {//still have time left in the anim ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; if ( ent->NPC ) {//invalid now VectorClear( ent->client->ps.moveDir ); } if ( ent->s.number || !player_locked ) { if ( ent->client->ps.legsAnimTimer > 450 ) { switch ( ent->client->ps.legsAnim ) { case BOTH_BUTTERFLY_LEFT: ucmd->rightmove = -127; break; case BOTH_BUTTERFLY_RIGHT: ucmd->rightmove = 127; break; default: break; } } } } } overridAngles = (PM_AdjustAnglesForStabDown( ent, ucmd )?qtrue:overridAngles); overridAngles = (PM_AdjustAngleForWallJump( ent, ucmd, qtrue )?qtrue:overridAngles); overridAngles = (PM_AdjustAngleForWallRunUp( ent, ucmd, qtrue )?qtrue:overridAngles); overridAngles = (PM_AdjustAngleForWallRun( ent, ucmd, qtrue )?qtrue:overridAngles); return overridAngles; } void BG_AddPushVecToUcmd( gentity_t *self, usercmd_t *ucmd ) { vec3_t forward, right, moveDir; float pushSpeed, fMove, rMove; if ( !self->client ) { return; } pushSpeed = VectorLengthSquared(self->client->pushVec); if(!pushSpeed) {//not being pushed return; } AngleVectors(self->client->ps.viewangles, forward, right, NULL); VectorScale(forward, ucmd->forwardmove/127.0f * self->client->ps.speed, moveDir); VectorMA(moveDir, ucmd->rightmove/127.0f * self->client->ps.speed, right, moveDir); //moveDir is now our intended move velocity VectorAdd(moveDir, self->client->pushVec, moveDir); self->client->ps.speed = VectorNormalize(moveDir); //moveDir is now our intended move velocity plus our push Vector fMove = 127.0 * DotProduct(forward, moveDir); rMove = 127.0 * DotProduct(right, moveDir); ucmd->forwardmove = floor(fMove);//If in the same dir , will be positive ucmd->rightmove = floor(rMove);//If in the same dir , will be positive if ( self->client->pushVecTime < level.time ) { VectorClear( self->client->pushVec ); } } void NPC_Accelerate( gentity_t *ent, qboolean fullWalkAcc, qboolean fullRunAcc ) { if ( !ent->client || !ent->NPC ) { return; } if ( !ent->NPC->stats.acceleration ) {//No acceleration means just start and stop ent->NPC->currentSpeed = ent->NPC->desiredSpeed; } //FIXME: in cinematics always accel/decel? else if ( ent->NPC->desiredSpeed <= ent->NPC->stats.walkSpeed ) {//Only accelerate if at walkSpeeds if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed + ent->NPC->stats.acceleration ) { //ent->client->ps.friction = 0; ent->NPC->currentSpeed += ent->NPC->stats.acceleration; } else if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed ) { //ent->client->ps.friction = 0; ent->NPC->currentSpeed = ent->NPC->desiredSpeed; } else if ( fullWalkAcc && ent->NPC->desiredSpeed < ent->NPC->currentSpeed - ent->NPC->stats.acceleration ) {//decelerate even when walking ent->NPC->currentSpeed -= ent->NPC->stats.acceleration; } else if ( ent->NPC->desiredSpeed < ent->NPC->currentSpeed ) {//stop on a dime ent->NPC->currentSpeed = ent->NPC->desiredSpeed; } } else// if ( ent->NPC->desiredSpeed > ent->NPC->stats.walkSpeed ) {//Only decelerate if at runSpeeds if ( fullRunAcc && ent->NPC->desiredSpeed > ent->NPC->currentSpeed + ent->NPC->stats.acceleration ) {//Accelerate to runspeed //ent->client->ps.friction = 0; ent->NPC->currentSpeed += ent->NPC->stats.acceleration; } else if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed ) {//accelerate instantly //ent->client->ps.friction = 0; ent->NPC->currentSpeed = ent->NPC->desiredSpeed; } else if ( fullRunAcc && ent->NPC->desiredSpeed < ent->NPC->currentSpeed - ent->NPC->stats.acceleration ) { ent->NPC->currentSpeed -= ent->NPC->stats.acceleration; } else if ( ent->NPC->desiredSpeed < ent->NPC->currentSpeed ) { ent->NPC->currentSpeed = ent->NPC->desiredSpeed; } } } /* ------------------------- NPC_GetWalkSpeed ------------------------- */ static int NPC_GetWalkSpeed( gentity_t *ent ) { int walkSpeed = 0; if ( ( ent->client == NULL ) || ( ent->NPC == NULL ) ) return 0; switch ( ent->client->playerTeam ) { case TEAM_PLAYER: //To shutup compiler, will add entries later (this is stub code) default: walkSpeed = ent->NPC->stats.walkSpeed; break; } return walkSpeed; } /* ------------------------- NPC_GetRunSpeed ------------------------- */ #define BORG_RUN_INCR 25 #define SPECIES_RUN_INCR 25 #define STASIS_RUN_INCR 20 #define WARBOT_RUN_INCR 20 static int NPC_GetRunSpeed( gentity_t *ent ) { int runSpeed = 0; if ( ( ent->client == NULL ) || ( ent->NPC == NULL ) ) return 0; /* switch ( ent->client->playerTeam ) { case TEAM_BORG: runSpeed = ent->NPC->stats.runSpeed; runSpeed += BORG_RUN_INCR * (g_spskill->integer%3); break; case TEAM_8472: runSpeed = ent->NPC->stats.runSpeed; runSpeed += SPECIES_RUN_INCR * (g_spskill->integer%3); break; case TEAM_STASIS: runSpeed = ent->NPC->stats.runSpeed; runSpeed += STASIS_RUN_INCR * (g_spskill->integer%3); break; case TEAM_BOTS: runSpeed = ent->NPC->stats.runSpeed; break; default: runSpeed = ent->NPC->stats.runSpeed; break; } */ // team no longer indicates species/race. Use NPC_class to adjust speed for specific npc types switch( ent->client->NPC_class) { case CLASS_PROBE: // droid cases here to shut-up compiler case CLASS_GONK: case CLASS_R2D2: case CLASS_R5D2: case CLASS_MARK1: case CLASS_MARK2: case CLASS_PROTOCOL: case CLASS_ATST: // hmm, not really your average droid case CLASS_MOUSE: case CLASS_SEEKER: case CLASS_REMOTE: runSpeed = ent->NPC->stats.runSpeed; break; default: runSpeed = ent->NPC->stats.runSpeed; break; } return runSpeed; } void G_HeldByMonster( gentity_t *ent, usercmd_t **ucmd ) { if ( ent && ent->activator && ent->activator->inuse && ent->activator->health > 0 ) { gentity_t *monster = ent->activator; //take the monster's waypoint as your own ent->waypoint = monster->waypoint; //update the actual origin of the victim mdxaBone_t boltMatrix; // Getting the bolt here int boltIndex = monster->gutBolt;//default to being held in his mouth if ( monster->count == 1 ) {//being held in hand rather than the mouth, so use *that* bolt boltIndex = monster->handRBolt; } vec3_t monAngles = {0}; monAngles[YAW] = monster->currentAngles[YAW];//only use YAW when passing angles to G2 gi.G2API_GetBoltMatrix( monster->ghoul2, monster->playerModel, boltIndex, &boltMatrix, monAngles, monster->currentOrigin, (cg.time?cg.time:level.time), NULL, monster->s.modelScale ); // Storing ent position, bolt position, and bolt axis gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->client->ps.origin ); gi.linkentity( ent ); //lock view angles PM_AdjustAnglesForHeldByMonster( ent, monster, *ucmd ); if ( monster->client && monster->client->NPC_class == CLASS_WAMPA ) {//can only hit attack button (*ucmd)->buttons &= ~((*ucmd)->buttons&~BUTTON_ATTACK); } } else if (ent) {//doh, my captor died! ent->activator = NULL; if (ent->client) { ent->client->ps.eFlags &= ~(EF_HELD_BY_WAMPA|EF_HELD_BY_RANCOR); } } // don't allow movement, weapon switching, and most kinds of button presses (*ucmd)->forwardmove = 0; (*ucmd)->rightmove = 0; (*ucmd)->upmove = 0; } // yes... so stop skipping... void G_StopCinematicSkip( void ) { gi.cvar_set("skippingCinematic", "0"); gi.cvar_set("timescale", "1"); } void G_StartCinematicSkip( void ) { if (cinematicSkipScript[0]) { Quake3Game()->RunScript( &g_entities[0], cinematicSkipScript ); cinematicSkipScript[0] = 0; gi.cvar_set("skippingCinematic", "1"); gi.cvar_set("timescale", "100"); } else { // no... so start skipping... gi.cvar_set("skippingCinematic", "1"); gi.cvar_set("timescale", "100"); } } void G_CheckClientIdle( gentity_t *ent, usercmd_t *ucmd ) { if ( !ent || !ent->client || ent->health <= 0 ) { return; } if ( !ent->s.number && ( !cg.renderingThirdPerson || cg.zoomMode ) ) { if ( ent->client->idleTime < level.time ) { ent->client->idleTime = level.time; } return; } if ( !VectorCompare( vec3_origin, ent->client->ps.velocity ) || ucmd->buttons || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove || !PM_StandingAnim( ent->client->ps.legsAnim ) || ent->enemy || ent->client->ps.legsAnimTimer || ent->client->ps.torsoAnimTimer ) {//FIXME: also check for turning? if ( !VectorCompare( vec3_origin, ent->client->ps.velocity ) || ucmd->buttons || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove || ent->enemy ) { //if in an idle, break out switch ( ent->client->ps.legsAnim ) { case BOTH_STAND1IDLE1: case BOTH_STAND2IDLE1: case BOTH_STAND2IDLE2: case BOTH_STAND3IDLE1: case BOTH_STAND5IDLE1: ent->client->ps.legsAnimTimer = 0; break; } switch ( ent->client->ps.torsoAnim ) { case BOTH_STAND1IDLE1: case BOTH_STAND2IDLE1: case BOTH_STAND2IDLE2: case BOTH_STAND3IDLE1: case BOTH_STAND5IDLE1: ent->client->ps.torsoAnimTimer = 0; break; } } // if ( ent->client->idleTime < level.time ) { ent->client->idleTime = level.time; } } else if ( level.time - ent->client->idleTime > 5000 ) {//been idle for 5 seconds int idleAnim = -1; switch ( ent->client->ps.legsAnim ) { case BOTH_STAND1: idleAnim = BOTH_STAND1IDLE1; break; case BOTH_STAND2: idleAnim = Q_irand(BOTH_STAND2IDLE1,BOTH_STAND2IDLE2); break; case BOTH_STAND3: idleAnim = BOTH_STAND3IDLE1; break; case BOTH_STAND5: idleAnim = BOTH_STAND5IDLE1; break; } if ( idleAnim != -1 && PM_HasAnimation( ent, idleAnim ) ) { NPC_SetAnim( ent, SETANIM_BOTH, idleAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); //don't idle again after this anim for a while ent->client->idleTime = level.time + PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)idleAnim ) + Q_irand( 0, 2000 ); } } } void G_CheckMovingLoopingSounds( gentity_t *ent, usercmd_t *ucmd ) { if ( ent->client ) { if ( (ent->NPC&&!VectorCompare( vec3_origin, ent->client->ps.moveDir ))//moving using moveDir || ucmd->forwardmove || ucmd->rightmove//moving using ucmds || (ucmd->upmove&&FlyingCreature( ent ))//flier using ucmds to move || (FlyingCreature( ent )&&!VectorCompare( vec3_origin, ent->client->ps.velocity )&&ent->health>0))//flier using velocity to move { switch( ent->client->NPC_class ) { case CLASS_R2D2: ent->s.loopSound = G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp.wav" ); break; case CLASS_R5D2: ent->s.loopSound = G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp2.wav" ); break; case CLASS_MARK2: ent->s.loopSound = G_SoundIndex( "sound/chars/mark2/misc/mark2_move_lp" ); break; case CLASS_MOUSE: ent->s.loopSound = G_SoundIndex( "sound/chars/mouse/misc/mouse_lp" ); break; case CLASS_PROBE: ent->s.loopSound = G_SoundIndex( "sound/chars/probe/misc/probedroidloop" ); } } else {//not moving under your own control, stop loopSound if ( ent->client->NPC_class == CLASS_R2D2 || ent->client->NPC_class == CLASS_R5D2 || ent->client->NPC_class == CLASS_MARK2 || ent->client->NPC_class == CLASS_MOUSE || ent->client->NPC_class == CLASS_PROBE ) { ent->s.loopSound = 0; } } } } /* ============== ClientAlterSpeed This function is called ONLY from ClientThinkReal, and is responsible for setting client ps.speed ============== */ void ClientAlterSpeed(gentity_t *ent, usercmd_t *ucmd, qboolean controlledByPlayer, float vehicleFrameTimeModifier) { gclient_t *client = ent->client; Vehicle_t *pVeh = NULL; if ( ent->client && ent->client->NPC_class == CLASS_VEHICLE ) { pVeh = ent->m_pVehicle; } // set speed // This may be wrong: If we're an npc and we are in a vehicle??? if ( ent->NPC != NULL && ent->client && ( ent->s.m_iVehicleNum != 0 )/*&& ent->client->NPC_class == CLASS_VEHICLE*/ ) {//we don't actually scale the ucmd, we use actual speeds //FIXME: swoop should keep turning (and moving forward?) for a little bit? if ( ent->NPC->combatMove == qfalse ) { if ( !(ucmd->buttons & BUTTON_USE) ) {//Not leaning qboolean Flying = (ucmd->upmove && ent->client->moveType == MT_FLYSWIM); qboolean Climbing = (ucmd->upmove && ent->watertype&CONTENTS_LADDER ); client->ps.friction = 6; if ( ucmd->forwardmove || ucmd->rightmove || Flying ) { //if ( ent->NPC->behaviorState != BS_FORMATION ) {//In - Formation NPCs set thier desiredSpeed themselves if ( ucmd->buttons & BUTTON_WALKING ) { ent->NPC->desiredSpeed = NPC_GetWalkSpeed( ent );//ent->NPC->stats.walkSpeed; } else//running { ent->NPC->desiredSpeed = NPC_GetRunSpeed( ent );//ent->NPC->stats.runSpeed; } if ( ent->NPC->currentSpeed >= 80 && !controlledByPlayer ) {//At higher speeds, need to slow down close to stuff //Slow down as you approach your goal // if ( ent->NPC->distToGoal < SLOWDOWN_DIST && client->race != RACE_BORG && !(ent->NPC->aiFlags&NPCAI_NO_SLOWDOWN) )//128 if ( ent->NPC->distToGoal < SLOWDOWN_DIST && !(ent->NPC->aiFlags&NPCAI_NO_SLOWDOWN) )//128 { if ( ent->NPC->desiredSpeed > MIN_NPC_SPEED ) { float slowdownSpeed = ((float)ent->NPC->desiredSpeed) * ent->NPC->distToGoal / SLOWDOWN_DIST; ent->NPC->desiredSpeed = ceil(slowdownSpeed); if ( ent->NPC->desiredSpeed < MIN_NPC_SPEED ) {//don't slow down too much ent->NPC->desiredSpeed = MIN_NPC_SPEED; } } } } } } else if ( Climbing ) { ent->NPC->desiredSpeed = ent->NPC->stats.walkSpeed; } else {//We want to stop ent->NPC->desiredSpeed = 0; } NPC_Accelerate( ent, qfalse, qfalse ); if ( ent->NPC->currentSpeed <= 24 && ent->NPC->desiredSpeed < ent->NPC->currentSpeed ) {//No-one walks this slow client->ps.speed = ent->NPC->currentSpeed = 0;//Full stop ucmd->forwardmove = 0; ucmd->rightmove = 0; } else { if ( ent->NPC->currentSpeed <= ent->NPC->stats.walkSpeed ) {//Play the walkanim ucmd->buttons |= BUTTON_WALKING; } else { ucmd->buttons &= ~BUTTON_WALKING; } if ( ent->NPC->currentSpeed > 0 ) {//We should be moving if ( Climbing || Flying ) { if ( !ucmd->upmove ) {//We need to force them to take a couple more steps until stopped ucmd->upmove = ent->NPC->last_ucmd.upmove;//was last_upmove; } } else if ( !ucmd->forwardmove && !ucmd->rightmove ) {//We need to force them to take a couple more steps until stopped ucmd->forwardmove = ent->NPC->last_ucmd.forwardmove;//was last_forwardmove; ucmd->rightmove = ent->NPC->last_ucmd.rightmove;//was last_rightmove; } } client->ps.speed = ent->NPC->currentSpeed; if ( player && player->client && player->client->ps.viewEntity == ent->s.number ) { } else { //Slow down on turns - don't orbit!!! float turndelta = 0; // if the NPC is locked into a Yaw, we want to check the lockedDesiredYaw...otherwise the NPC can't walk backwards, because it always thinks it trying to turn according to desiredYaw if( client->renderInfo.renderFlags & RF_LOCKEDANGLE ) // yeah I know the RF_ flag is a pretty ugly hack... { turndelta = (180 - fabs( AngleDelta( ent->currentAngles[YAW], ent->NPC->lockedDesiredYaw ) ))/180; } else { turndelta = (180 - fabs( AngleDelta( ent->currentAngles[YAW], ent->NPC->desiredYaw ) ))/180; } if ( turndelta < 0.75f ) { client->ps.speed = 0; } else if ( ent->NPC->distToGoal < 100 && turndelta < 1.0 ) {//Turn is greater than 45 degrees or closer than 100 to goal client->ps.speed = floor(((float)(client->ps.speed))*turndelta); } } } } } else { ent->NPC->desiredSpeed = ( ucmd->buttons & BUTTON_WALKING ) ? NPC_GetWalkSpeed( ent ) : NPC_GetRunSpeed( ent ); client->ps.speed = ent->NPC->desiredSpeed; } } else {//Client sets ucmds and such for speed alterations { client->ps.speed = g_speed->value;//default is 320 /*if ( !ent->s.number && ent->painDebounceTime>level.time ) { client->ps.speed *= 0.25f; } else */if (ent->client->ps.heldClient < ENTITYNUM_WORLD) { client->ps.speed *= 0.3f; } else if ( PM_SaberInAttack( ent->client->ps.saberMove ) && ucmd->forwardmove < 0 ) {//if running backwards while attacking, don't run as fast. switch( client->ps.saberAnimLevel ) { case SS_FAST: client->ps.speed *= 0.75f; break; case SS_MEDIUM: case SS_DUAL: case SS_STAFF: client->ps.speed *= 0.60f; break; case SS_STRONG: client->ps.speed *= 0.45f; break; } if ( g_saberMoveSpeed->value != 1.0f ) { client->ps.speed *= g_saberMoveSpeed->value; } } else if ( PM_LeapingSaberAnim( client->ps.legsAnim ) ) {//no mod on speed when leaping //FIXME: maybe jump? } else if ( PM_SpinningSaberAnim( client->ps.legsAnim ) ) { client->ps.speed *= 0.5f; if ( g_saberMoveSpeed->value != 1.0f ) { client->ps.speed *= g_saberMoveSpeed->value; } } else if ( client->ps.weapon == WP_SABER && ( ucmd->buttons & BUTTON_ATTACK ) ) {//if attacking with saber while running, drop your speed //FIXME: should be weaponTime? Or in certain anims? switch( client->ps.saberAnimLevel ) { case SS_MEDIUM: case SS_DUAL: case SS_STAFF: client->ps.speed *= 0.85f; break; case SS_STRONG: client->ps.speed *= 0.70f; break; } if ( g_saberMoveSpeed->value != 1.0f ) { client->ps.speed *= g_saberMoveSpeed->value; } } } } if ( client->NPC_class == CLASS_ATST && client->ps.legsAnim == BOTH_RUN1START ) {//HACK: when starting to move as atst, ramp up speed //float animLength = PM_AnimLength( client->clientInfo.animFileIndex, (animNumber_t)client->ps.legsAnim); //client->ps.speed *= ( animLength - client->ps.legsAnimTimer)/animLength; if ( client->ps.legsAnimTimer > 100 ) { client->ps.speed = 0; } } //Apply forced movement if ( client->forced_forwardmove ) { ucmd->forwardmove = client->forced_forwardmove; if ( !client->ps.speed ) { if ( ent->NPC != NULL ) { client->ps.speed = ent->NPC->stats.runSpeed; } else { client->ps.speed = g_speed->value;//default is 320 } } } if ( client->forced_rightmove ) { ucmd->rightmove = client->forced_rightmove; if ( !client->ps.speed ) { if ( ent->NPC != NULL ) { client->ps.speed = ent->NPC->stats.runSpeed; } else { client->ps.speed = g_speed->value;//default is 320 } } } if ( !pVeh ) { if ( ucmd->forwardmove < 0 && !(ucmd->buttons&BUTTON_WALKING) && client->ps.groundEntityNum != ENTITYNUM_NONE ) {//running backwards is slower than running forwards client->ps.speed *= 0.75; } if (client->ps.forceRageRecoveryTime > level.time ) { client->ps.speed *= 0.75; } } } extern qboolean ForceDrain2(gentity_t *ent); extern void ForceGrip(gentity_t *ent); extern void ForceLightning(gentity_t *ent); extern void ForceProtect(gentity_t *ent); extern void ForceRage(gentity_t *ent); extern void ForceSeeing(gentity_t *ent); extern void ForceTelepathy(gentity_t *ent); extern void ForceAbsorb(gentity_t *ent); extern void ForceHeal(gentity_t *ent); static void ProcessGenericCmd(gentity_t *ent, byte cmd) { switch(cmd) { case 0: break; case GENCMD_FORCE_DRAIN: ForceDrain2(ent); break; case GENCMD_FORCE_THROW: ForceThrow(ent, qfalse); break; case GENCMD_FORCE_SPEED: ForceSpeed(ent); break; case GENCMD_FORCE_PULL: ForceThrow(ent, qtrue); break; case GENCMD_FORCE_DISTRACT: ForceTelepathy(ent); break; case GENCMD_FORCE_GRIP: ForceGrip(ent); break; case GENCMD_FORCE_LIGHTNING: ForceLightning(ent); break; case GENCMD_FORCE_RAGE: ForceRage(ent); break; case GENCMD_FORCE_PROTECT: ForceProtect(ent); break; case GENCMD_FORCE_ABSORB: ForceAbsorb(ent); break; case GENCMD_FORCE_SEEING: ForceSeeing(ent); break; case GENCMD_FORCE_HEAL: ForceHeal(ent); break; } } /* ============== ClientThink This will be called once for each client frame, which will usually be a couple times for each server frame on fast clients. ============== */ extern int G_FindLocalInterestPoint( gentity_t *self ); extern float G_CanJumpToEnemyVeh(Vehicle_t *pVeh, const usercmd_t *pUmcd ); void ClientThink_real( gentity_t *ent, usercmd_t *ucmd ) { gclient_t *client; pmove_t pm; vec3_t oldOrigin; int oldEventSequence; int msec; qboolean inSpinFlipAttack = PM_AdjustAnglesForSpinningFlip( ent, ucmd, qfalse ); qboolean controlledByPlayer = qfalse; Vehicle_t *pVeh = NULL; if ( ent->client && ent->client->NPC_class == CLASS_VEHICLE ) { pVeh = ent->m_pVehicle; } //Don't let the player do anything if in a camera if ( (ent->s.eFlags&EF_HELD_BY_RANCOR) || (ent->s.eFlags&EF_HELD_BY_WAMPA) ) { G_HeldByMonster( ent, &ucmd ); } if ( ent->s.number == 0 ) { extern cvar_t *g_skippingcin; if ( ent->s.eFlags & EF_LOCKED_TO_WEAPON ) { G_UpdateEmplacedWeaponData( ent ); RunEmplacedWeapon( ent, &ucmd ); } if ( ent->client->ps.saberLockTime > level.time && ent->client->ps.saberLockEnemy != ENTITYNUM_NONE ) { NPC_SetLookTarget( ent, ent->client->ps.saberLockEnemy, level.time+1000 ); } if ( ent->client->renderInfo.lookTargetClearTime < level.time //NOTE: here this is used as a debounce, not an actual timer && ent->health > 0 //must be alive && (!ent->enemy || ent->client->ps.saberMove != LS_A_BACKSTAB) )//don't update if in backstab unless don't currently have an enemy {//NOTE: doesn't keep updating to nearest enemy once you're dead int newLookTarget; if ( !G_ValidateLookEnemy( ent, ent->enemy ) ) { ent->enemy = NULL; } //FIXME: make this a little prescient? G_ChooseLookEnemy( ent, ucmd ); if ( ent->enemy ) {//target newLookTarget = ent->enemy->s.number; //so we don't change our minds in the next 1 second ent->client->renderInfo.lookTargetClearTime = level.time+1000; ent->client->renderInfo.lookMode = LM_ENT; } else {//no target //FIXME: what about sightalerts and missiles? newLookTarget = ENTITYNUM_NONE; newLookTarget = G_FindLocalInterestPoint( ent ); if ( newLookTarget != ENTITYNUM_NONE ) {//found something of interest ent->client->renderInfo.lookMode = LM_INTEREST; } else {//okay, no interesting things and no enemies, so look for items newLookTarget = G_FindLookItem( ent ); ent->client->renderInfo.lookMode = LM_ENT; } } if ( ent->client->renderInfo.lookTarget != newLookTarget ) {//transitioning NPC_SetLookTarget( ent, newLookTarget, level.time+1000 ); } } if ( in_camera ) { // watch the code here, you MUST "return" within this IF(), *unless* you're stopping the cinematic skip. // if ( ClientCinematicThink(ent->client) ) { if (g_skippingcin->integer) // already doing cinematic skip? {// yes... so stop skipping... G_StopCinematicSkip(); } else {// no... so start skipping... G_StartCinematicSkip(); return; } } else { return; } } // If he's riding the vehicle... else if ( ent->s.m_iVehicleNum != 0 && ent->health > 0 ) { } else { if ( g_skippingcin->integer ) {//We're skipping the cinematic and it's over now gi.cvar_set("timescale", "1"); gi.cvar_set("skippingCinematic", "0"); } if ( ent->client->ps.pm_type == PM_DEAD && cg.missionStatusDeadTime < level.time ) {//mission status screen is up because player is dead, stop all scripts if (Q_stricmpn(level.mapname,"_holo",5)) { stop_icarus = qtrue; } } } // // Don't allow the player to adjust the pitch when they are in third person overhead cam. //extern vmCvar_t cg_thirdPerson; // if ( cg_thirdPerson.integer == 2 ) // { // ucmd->angles[PITCH] = 0; // } if ( cg.zoomMode == 2 ) { // Any kind of movement when the player is NOT ducked when the disruptor gun is zoomed will cause us to auto-magically un-zoom if ( ( (ucmd->forwardmove||ucmd->rightmove) && ucmd->upmove >= 0 //crouching-moving is ok && !(ucmd->buttons&BUTTON_USE)/*leaning is ok*/ ) || ucmd->upmove > 0 //jumping not allowed ) { // already zooming, so must be wanting to turn it off G_Sound( ent, G_SoundIndex( "sound/weapons/disruptor/zoomend.wav" )); #ifdef _IMMERSION G_Force( ent, G_ForceIndex( "fffx/weapons/disruptor/zoomend", FF_CHANNEL_WEAPON ) ); #endif // _IMMERSION cg.zoomMode = 0; cg.zoomTime = cg.time; cg.zoomLocked = qfalse; } } if ( (player_locked || (ent->client->ps.eFlags&EF_FORCE_GRIPPED) || (ent->client->ps.eFlags&EF_FORCE_DRAINED) || (ent->client->ps.legsAnim==BOTH_PLAYER_PA_1) || (ent->client->ps.legsAnim==BOTH_PLAYER_PA_2) || (ent->client->ps.legsAnim==BOTH_PLAYER_PA_3)) && ent->client->ps.pm_type < PM_DEAD ) // unless dead {//lock out player control if ( !player_locked ) { VectorClear( ucmd->angles ); } ucmd->forwardmove = 0; ucmd->rightmove = 0; ucmd->buttons = 0; ucmd->upmove = 0; PM_AdjustAnglesToGripper( ent, ucmd ); } if ( ent->client->ps.leanofs ) {//no shooting while leaning ucmd->buttons &= ~BUTTON_ATTACK; if ( ent->client->ps.weapon != WP_DISRUPTOR ) {//can still zoom around corners ucmd->buttons &= ~BUTTON_ALT_ATTACK; } } } else { if ( ent->s.eFlags & EF_LOCKED_TO_WEAPON ) { G_UpdateEmplacedWeaponData( ent ); } if ( player && player->client && player->client->ps.viewEntity == ent->s.number ) { controlledByPlayer = qtrue; int sav_weapon = ucmd->weapon; memcpy( ucmd, &player->client->usercmd, sizeof( usercmd_t ) ); ucmd->weapon = sav_weapon; ent->client->usercmd = *ucmd; } // Transfer over our driver's commands to us (the vehicle). if ( ent->owner && ent->client && ent->client->NPC_class == CLASS_VEHICLE ) { memcpy( ucmd, &ent->owner->client->usercmd, sizeof( usercmd_t ) ); ucmd->buttons &= ~BUTTON_USE;//Vehicles NEVER try to use ANYTHING!!! //ucmd->weapon = ent->client->ps.weapon; // but keep our weapon. ent->client->usercmd = *ucmd; } G_NPCMunroMatchPlayerWeapon( ent ); } // If we are a vehicle, update ourself. if ( pVeh && (pVeh->m_pVehicleInfo->Inhabited(pVeh) || pVeh->m_iBoarding!=0 || pVeh->m_pVehicleInfo->type!=VH_ANIMAL) ) { pVeh->m_pVehicleInfo->Update( pVeh, ucmd ); } else if ( ent->client ) {//this is any client that is not a vehicle (OR: is a vehicle and it not being ridden, is not being boarded, or is a TaunTaun...! if ( ent->client->NPC_class == CLASS_GONK || ent->client->NPC_class == CLASS_MOUSE || ent->client->NPC_class == CLASS_R2D2 || ent->client->NPC_class == CLASS_R5D2 ) {//no jumping or strafing in these guys ucmd->upmove = ucmd->rightmove = 0; } else if ( ent->client->NPC_class == CLASS_ATST || ent->client->NPC_class == CLASS_RANCOR ) {//no jumping in atst if (ent->client->ps.pm_type != PM_NOCLIP) { ucmd->upmove = 0; } if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) {//ATST crushes anything underneath it gentity_t *under = &g_entities[ent->client->ps.groundEntityNum]; if ( under && under->health && under->takedamage ) { vec3_t down = {0,0,-1}; //FIXME: we'll be doing traces down from each foot, so we'll have a real impact origin G_Damage( under, ent, ent, down, under->currentOrigin, 100, 0, MOD_CRUSH ); } //so they know to run like hell when I get close //FIXME: project this in the direction I'm moving? if ( ent->client->NPC_class != CLASS_RANCOR && !Q_irand( 0, 10 ) ) {//not so often... AddSoundEvent( ent, ent->currentOrigin, ent->maxs[1]*5, AEL_DANGER, qfalse, qtrue ); AddSightEvent( ent, ent->currentOrigin, ent->maxs[1]*5, AEL_DANGER, 100 ); } } } else if ( ent->client->ps.groundEntityNum < ENTITYNUM_WORLD && !ent->client->ps.forceJumpCharge ) {//standing on an entity and not currently force jumping gentity_t *groundEnt = &g_entities[ent->client->ps.groundEntityNum]; if ( groundEnt && groundEnt->client ) { // If you landed on a speeder or animal vehicle... if ( groundEnt->client && groundEnt->client->NPC_class == CLASS_VEHICLE ) { if ( ent->client->NPC_class != CLASS_VEHICLE ) {//um... vehicles shouldn't ride other vehicles, mmkay? if ( (groundEnt->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL && PM_HasAnimation( ent, BOTH_VT_IDLE )) || (groundEnt->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER && PM_HasAnimation( ent, BOTH_VS_IDLE )) ) { //groundEnt->m_pVehicle->m_iBoarding = -3; // Land From Behind groundEnt->m_pVehicle->m_pVehicleInfo->Board( groundEnt->m_pVehicle, ent ); } } } else if ( groundEnt->client && groundEnt->client->NPC_class == CLASS_SAND_CREATURE && G_HasKnockdownAnims( ent ) ) {//step on a sand creature = *you* fall down G_Knockdown( ent, groundEnt, vec3_origin, 300, qtrue ); } else if ( groundEnt->client && groundEnt->client->NPC_class == CLASS_RANCOR ) {//step on a Rancor, it bucks & throws you off if ( groundEnt->client->ps.legsAnim != BOTH_ATTACK3 && groundEnt->client->ps.legsAnim != BOTH_ATTACK4 && groundEnt->client->ps.legsAnim != BOTH_BUCK_RIDER ) {//don't interrupt special anims vec3_t throwDir, right; AngleVectors( groundEnt->currentAngles, throwDir, right, NULL ); VectorScale(throwDir,-1,throwDir); VectorMA( throwDir, Q_flrand( -0.5f, 0.5f), right, throwDir ); throwDir[2] = 0.2f; VectorNormalize( throwDir ); if ( !(ent->flags&FL_NO_KNOCKBACK) ) { G_Throw( ent, throwDir, Q_flrand( 50, 200 ) ); } if ( G_HasKnockdownAnims( ent ) ) { G_Knockdown( ent, groundEnt, vec3_origin, 300, qtrue ); } NPC_SetAnim( groundEnt, SETANIM_BOTH, BOTH_BUCK_RIDER, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); } } else if ( groundEnt->client->ps.groundEntityNum != ENTITYNUM_NONE && groundEnt->health > 0 && !PM_InRoll( &groundEnt->client->ps ) && !(groundEnt->client->ps.eFlags&EF_LOCKED_TO_WEAPON) && !(groundEnt->client->ps.eFlags&EF_HELD_BY_RANCOR) && !(groundEnt->client->ps.eFlags&EF_HELD_BY_WAMPA) && !(groundEnt->client->ps.eFlags&EF_HELD_BY_SAND_CREATURE) && !inSpinFlipAttack ) {//landed on a live client who is on the ground, jump off them and knock them down qboolean forceKnockdown = qfalse; // If in a vehicle when land on someone, always knockdown. if ( pVeh ) { forceKnockdown = qtrue; } else if ( ent->s.number && ent->NPC && ent->client->ps.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 )//ent->s.weapon == WP_SABER ) {//force-jumper landed on someone //don't jump off, too many ledges, plus looks weird if ( groundEnt->client->playerTeam != ent->client->playerTeam ) {//don't knock down own guys forceKnockdown = (qboolean)(Q_irand( 0, RANK_CAPTAIN+4 )NPC->rank); } //now what... push the groundEnt out of the way? if ( !ent->client->ps.velocity[0] && !ent->client->ps.velocity[1] ) {//not moving, shove us a little vec3_t slideFwd; AngleVectors( ent->client->ps.viewangles, slideFwd, NULL, NULL ); slideFwd[2] = 0.0f; VectorNormalize( slideFwd ); ent->client->ps.velocity[0] = slideFwd[0]*10.0f; ent->client->ps.velocity[1] = slideFwd[1]*10.0f; } //slide for a little ent->client->ps.pm_flags |= PMF_TIME_NOFRICTION; ent->client->ps.pm_time = 100; } else if ( ent->health > 0 ) { if ( !PM_InRoll( &ent->client->ps ) && !PM_FlippingAnim( ent->client->ps.legsAnim ) ) { if ( ent->s.number && ent->s.weapon == WP_SABER ) { ent->client->ps.forceJumpCharge = 320;//FIXME: calc this intelligently? } else if ( !ucmd->upmove ) {//if not ducking (which should cause a roll), then jump ucmd->upmove = 127; } if ( !ucmd->forwardmove && !ucmd->rightmove ) {// If not moving, don't want to jump straight up //FIXME: trace for clear di? if ( !Q_irand( 0, 3 ) ) { ucmd->forwardmove = 127; } else if ( !Q_irand( 0, 3 ) ) { ucmd->forwardmove = -127; } else if ( !Q_irand( 0, 1 ) ) { ucmd->rightmove = 127; } else { ucmd->rightmove = -127; } } if ( !ent->s.number && ucmd->upmove < 0 ) {//player who should roll- force it int rollAnim = BOTH_ROLL_F; if ( ucmd->forwardmove >= 0 ) { rollAnim = BOTH_ROLL_F; } else if ( ucmd->forwardmove < 0 ) { rollAnim = BOTH_ROLL_B; } else if ( ucmd->rightmove > 0 ) { rollAnim = BOTH_ROLL_R; } else if ( ucmd->rightmove < 0 ) { rollAnim = BOTH_ROLL_L; } NPC_SetAnim(ent,SETANIM_BOTH,rollAnim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); G_AddEvent( ent, EV_ROLL, 0 ); ent->client->ps.saberMove = LS_NONE; } } } else {//a corpse? Shit //Hmm, corpses should probably *always* knockdown... forceKnockdown = qtrue; ent->clipmask &= ~CONTENTS_BODY; } //FIXME: need impact sound event GEntity_PainFunc( groundEnt, ent, ent, groundEnt->currentOrigin, 0, MOD_CRUSH ); if ( !forceKnockdown && groundEnt->client->NPC_class == CLASS_DESANN && ent->client->NPC_class != CLASS_LUKE ) {//can't knock down desann unless you're luke //FIXME: should he smack you away like Galak Mech? } else if ( forceKnockdown //forced || ent->client->NPC_class == CLASS_DESANN //desann always knocks people down || ( ( (groundEnt->s.number&&(groundEnt->s.weapon!=WP_SABER||!groundEnt->NPC||groundEnt->NPC->ranks.number||G_ControlledByPlayer(groundEnt)) && !Q_irand( 0, 3 )&&cg.renderingThirdPerson&&!cg.zoomMode) )//or a player in third person, 25% of the time && groundEnt->client->playerTeam != ent->client->playerTeam//and not on the same team && ent->client->ps.legsAnim != BOTH_JUMPATTACK6 ) )//not in the sideways-spinning jump attack { int knockAnim = BOTH_KNOCKDOWN1; if ( PM_CrouchAnim( groundEnt->client->ps.legsAnim ) ) {//knockdown from crouch knockAnim = BOTH_KNOCKDOWN4; } else { vec3_t gEFwd, gEAngles = {0,groundEnt->client->ps.viewangles[YAW],0}; AngleVectors( gEAngles, gEFwd, NULL, NULL ); if ( DotProduct( ent->client->ps.velocity, gEFwd ) > 50 ) {//pushing him forward knockAnim = BOTH_KNOCKDOWN3; } } NPC_SetAnim( groundEnt, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } } } } } client = ent->client; // mark the time, so the connection sprite can be removed client->lastCmdTime = level.time; client->pers.lastCommand = *ucmd; // sanity check the command time to prevent speedup cheating if ( ucmd->serverTime > level.time + 200 ) { ucmd->serverTime = level.time + 200; } if ( ucmd->serverTime < level.time - 1000 ) { ucmd->serverTime = level.time - 1000; } msec = ucmd->serverTime - client->ps.commandTime; if ( msec < 1 ) { msec = 1; } if ( msec > 200 ) { msec = 200; } if ( client->noclip ) { client->ps.pm_type = PM_NOCLIP; } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { client->ps.pm_type = PM_DEAD; } else { client->ps.pm_type = PM_NORMAL; } //FIXME: if global gravity changes this should update everyone's personal gravity... if ( !(ent->svFlags & SVF_CUSTOM_GRAVITY) ) { if (ent->client->inSpaceIndex) { //in space, so no gravity... client->ps.gravity = 0.0f; } else { client->ps.gravity = g_gravity->value; } } if (!USENEWNAVSYSTEM || ent->s.number==0) { ClientAlterSpeed(ent, ucmd, controlledByPlayer, 0); } //FIXME: need to do this before check to avoid walls and cliffs (or just cliffs?) BG_AddPushVecToUcmd( ent, ucmd ); G_CheckClampUcmd( ent, ucmd ); WP_ForcePowersUpdate( ent, ucmd ); //if we have the saber in hand, check for starting a block to reflect shots if ( ent->s.number < MAX_CLIENTS//player || ( ent->NPC && G_JediInNormalAI( ent ) ) )//NPC jedi not in a special AI mode { WP_SaberStartMissileBlockCheck( ent, ucmd ); } // Update the position of the saber, and check to see if we're throwing it if ( client->ps.saberEntityNum != ENTITYNUM_NONE ) { int updates = 1; if ( ent->NPC ) { updates = 3;//simulate player update rate? } for ( int update = 0; update < updates; update++ ) { WP_SaberUpdate( ent, ucmd ); } } //NEED to do this every frame, since these overrides do not go into the save/load data if ( ent->client && ent->s.m_iVehicleNum != 0 && !ent->s.number && !MatrixMode) {//FIXME: extern and read from g_vehicleInfo? Vehicle_t *pPlayerVeh = ent->owner->m_pVehicle; if ( pPlayerVeh && pPlayerVeh->m_pVehicleInfo->cameraOverride ) { // Vehicle Camera Overrides //-------------------------- cg.overrides.active |= ( CG_OVERRIDE_3RD_PERSON_RNG | CG_OVERRIDE_FOV | CG_OVERRIDE_3RD_PERSON_VOF | CG_OVERRIDE_3RD_PERSON_POF ); cg.overrides.thirdPersonRange = pPlayerVeh->m_pVehicleInfo->cameraRange; cg.overrides.fov = pPlayerVeh->m_pVehicleInfo->cameraFOV; cg.overrides.thirdPersonVertOffset = pPlayerVeh->m_pVehicleInfo->cameraVertOffset; cg.overrides.thirdPersonPitchOffset = pPlayerVeh->m_pVehicleInfo->cameraPitchOffset; if ( pPlayerVeh->m_pVehicleInfo->cameraAlpha ) { cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_APH; } // If In A Speeder (NOT DURING TURBO) //------------------------------------ if ((level.time>pPlayerVeh->m_iTurboTime) && pPlayerVeh->m_pVehicleInfo->type==VH_SPEEDER) { // If Using Strafe And Use Keys //------------------------------ if ((pPlayerVeh->m_ucmd.rightmove!=0) && // (pPlayerVeh->m_pParentEntity->client->ps.speed>=0) && (pPlayerVeh->m_ucmd.buttons&BUTTON_USE) ) { cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_ANG; // Turn On Angle Offset cg.overrides.thirdPersonRange *= -2; // Camera In Front Of Player cg.overrides.thirdPersonAngle = (pPlayerVeh->m_ucmd.rightmove>0)?(20):(-20); } // Auto Pullback Of Camera To Show Enemy //--------------------------------------- else { cg.overrides.active &= ~CG_OVERRIDE_3RD_PERSON_ANG; // Turn Off Angle Offset if (ent->enemy) { vec3_t actorDirection; vec3_t enemyDirection; AngleVectors(ent->currentAngles, actorDirection, 0, 0); VectorSubtract(ent->enemy->currentOrigin, ent->currentOrigin, enemyDirection); float enemyDistance = VectorNormalize(enemyDirection); if (enemyDistance>cg.overrides.thirdPersonRange && enemyDistance<400 && DotProduct(actorDirection, enemyDirection)<-0.5f) { cg.overrides.thirdPersonRange = enemyDistance; } } } } } } else if ( client->ps.eFlags&EF_IN_ATST ) { cg.overrides.active |= (CG_OVERRIDE_3RD_PERSON_RNG|CG_OVERRIDE_3RD_PERSON_POF|CG_OVERRIDE_3RD_PERSON_VOF); cg.overrides.thirdPersonRange = 240; if ( cg_thirdPersonAutoAlpha.integer ) { if ( ent->health > 0 && ent->client->ps.viewangles[PITCH] < 15 && ent->client->ps.viewangles[PITCH] > 0 ) { cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_APH; if ( cg.overrides.thirdPersonAlpha > 0.525f ) { cg.overrides.thirdPersonAlpha -= 0.025f; } else if ( cg.overrides.thirdPersonAlpha > 0.5f ) { cg.overrides.thirdPersonAlpha = 0.5f; } } else if ( cg.overrides.active&CG_OVERRIDE_3RD_PERSON_APH ) { if ( cg.overrides.thirdPersonAlpha > cg_thirdPersonAlpha.value ) { cg.overrides.active &= ~CG_OVERRIDE_3RD_PERSON_APH; } else if ( cg.overrides.thirdPersonAlpha < cg_thirdPersonAlpha.value-0.1f ) { cg.overrides.thirdPersonAlpha += 0.1f; } else if ( cg.overrides.thirdPersonAlpha < cg_thirdPersonAlpha.value ) { cg.overrides.thirdPersonAlpha = cg_thirdPersonAlpha.value; cg.overrides.active &= ~CG_OVERRIDE_3RD_PERSON_APH; } } } if ( ent->client->ps.viewangles[PITCH] > 0 ) { cg.overrides.thirdPersonPitchOffset = ent->client->ps.viewangles[PITCH]*-0.75; cg.overrides.thirdPersonVertOffset = 300+ent->client->ps.viewangles[PITCH]*-10; if ( cg.overrides.thirdPersonVertOffset < 0 ) { cg.overrides.thirdPersonVertOffset = 0; } } else if ( ent->client->ps.viewangles[PITCH] < 0 ) { cg.overrides.thirdPersonPitchOffset = ent->client->ps.viewangles[PITCH]*-0.75; cg.overrides.thirdPersonVertOffset = 300+ent->client->ps.viewangles[PITCH]*-5; if ( cg.overrides.thirdPersonVertOffset > 300 ) { cg.overrides.thirdPersonVertOffset = 300; } } else { cg.overrides.thirdPersonPitchOffset = 0; cg.overrides.thirdPersonVertOffset = 200; } } //play/stop any looping sounds tied to controlled movement G_CheckMovingLoopingSounds( ent, ucmd ); //remember your last angles VectorCopy ( ent->client->ps.viewangles, ent->lastAngles ); // set up for pmove oldEventSequence = client->ps.eventSequence; memset( &pm, 0, sizeof(pm) ); pm.gent = ent; pm.ps = &client->ps; pm.cmd = *ucmd; // pm.tracemask = MASK_PLAYERSOLID; // used differently for navgen pm.tracemask = ent->clipmask; pm.trace = gi.trace; pm.pointcontents = gi.pointcontents; pm.debugLevel = g_debugMove->integer; pm.noFootsteps = 0;//( g_dmflags->integer & DF_NO_FOOTSTEPS ) > 0; if ( ent->client && ent->NPC ) { pm.cmd.weapon = ent->client->ps.weapon; } VectorCopy( client->ps.origin, oldOrigin ); #ifdef _XBOX // if we're an npc then set the waterlevel // based on the entity structure // otherwise, zero it if(ent->s.number != 0) { pm.waterlevel = ent->waterlevel; pm.watertype = ent->watertype; } else { pm.waterlevel = 0; pm.watertype = 0; } #endif // perform a pmove Pmove( &pm ); pm.gent = 0; ProcessGenericCmd(ent, pm.cmd.generic_cmd); // save results of pmove if ( ent->client->ps.eventSequence != oldEventSequence ) { ent->eventTime = level.time; { int seq; seq = (ent->client->ps.eventSequence-1) & (MAX_PS_EVENTS-1); ent->s.event = ent->client->ps.events[ seq ] | ( ( ent->client->ps.eventSequence & 3 ) << 8 ); ent->s.eventParm = ent->client->ps.eventParms[ seq ]; } } PlayerStateToEntityState( &ent->client->ps, &ent->s ); VectorCopy ( ent->currentOrigin, ent->lastOrigin ); #if 1 // use the precise origin for linking VectorCopy( ent->client->ps.origin, ent->currentOrigin ); #else //We don't use prediction anymore, so screw this // use the snapped origin for linking so it matches client predicted versions VectorCopy( ent->s.pos.trBase, ent->currentOrigin ); #endif //Had to leave this in, some legacy code must still be using s.angles //Shouldn't interfere with interpolation of angles, should it? VectorCopy(ent->client->ps.viewangles , ent->currentAngles ); // if (pVeh) // { // gi.Printf("%d\n", ucmd->angles[2]); // } VectorCopy( pm.mins, ent->mins ); VectorCopy( pm.maxs, ent->maxs ); #ifdef _XBOX // if this is the player then set the ent water level // npcs are updated elsewhere if(ent->s.number == 0) { ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; } #else ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; #endif _VectorCopy( ucmd->angles, client->pers.cmd_angles ); // execute client events ClientEvents( ent, oldEventSequence ); if ( pm.useEvent ) { //TODO: Use TryUse( ent ); } // link entity now, after any personal teleporters have been used gi.linkentity( ent ); ent->client->hiddenDist = 0; if ( !ent->client->noclip ) { G_TouchTriggersLerped( ent ); } // touch other objects ClientImpacts( ent, &pm ); // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons |= client->buttons & ~client->oldbuttons; // check for respawning if ( client->ps.stats[STAT_HEALTH] <= 0 ) { // wait for the attack button to be pressed if ( ent->NPC == NULL && level.time > client->respawnTime ) { // don't allow respawn if they are still flying through the // air, unless 10 extra seconds have passed, meaning something // strange is going on, like the corpse is caught in a wind tunnel /* if ( level.time < client->respawnTime + 10000 ) { if ( client->ps.groundEntityNum == ENTITYNUM_NONE ) { return; } } */ // pressing attack or use is the normal respawn method if ( ucmd->buttons & ( BUTTON_ATTACK ) ) { respawn( ent ); } } if ( ent && !ent->s.number && ent->enemy && ent->enemy != ent && ent->enemy->s.number < ENTITYNUM_WORLD && ent->enemy->inuse && !(cg.overrides.active&CG_OVERRIDE_3RD_PERSON_ANG) ) {//keep facing enemy vec3_t deadDir; float deadYaw; VectorSubtract( ent->enemy->currentOrigin, ent->currentOrigin, deadDir ); deadYaw = AngleNormalize180( vectoyaw ( deadDir ) ); if ( deadYaw > ent->client->ps.stats[STAT_DEAD_YAW] + 1 ) { ent->client->ps.stats[STAT_DEAD_YAW]++; } else if ( deadYaw < ent->client->ps.stats[STAT_DEAD_YAW] - 1 ) { ent->client->ps.stats[STAT_DEAD_YAW]--; } else { ent->client->ps.stats[STAT_DEAD_YAW] = deadYaw; } } return; } // perform once-a-second actions ClientTimerActions( ent, msec ); ClientEndPowerUps( ent ); //try some idle anims on ent if getting no input and not moving for some time G_CheckClientIdle( ent, ucmd ); } /* ================== ClientThink A new command has arrived from the client ================== */ extern void PM_CheckForceUseButton( gentity_t *ent, usercmd_t *ucmd ); extern qboolean PM_GentCantJump( gentity_t *gent ); extern qboolean PM_WeaponOkOnVehicle( int weapon ); void ClientThink( int clientNum, usercmd_t *ucmd ) { gentity_t *ent; qboolean restore_ucmd = qfalse; usercmd_t sav_ucmd = {0}; ent = g_entities + clientNum; if ( ent->s.numberclient->ps.viewEntity > 0 && ent->client->ps.viewEntity < ENTITYNUM_WORLD ) {//you're controlling another NPC gentity_t *controlled = &g_entities[ent->client->ps.viewEntity]; qboolean freed = qfalse; if ( controlled->NPC && controlled->NPC->controlledTime && ent->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 ) {//An NPC I'm controlling with mind trick if ( controlled->NPC->controlledTime < level.time ) {//time's up! G_ClearViewEntity( ent ); freed = qtrue; } } else if ( controlled->client //an NPC && PM_GentCantJump( controlled ) //that cannot jump && controlled->client->moveType != MT_FLYSWIM ) //and does not use upmove to fly {//these types use jump to get out if ( ucmd->upmove > 0 ) {//jumping gets you out of it FIXME: check some other button instead... like ESCAPE... so you could even have total control over an NPC? G_ClearViewEntity( ent ); ucmd->upmove = 0;//ucmd->buttons = 0; //stop player from doing anything for a half second after ent->aimDebounceTime = level.time + 500; freed = qtrue; } } if ( !freed ) {//still controlling, save off my ucmd and clear it for my actual run through pmove restore_ucmd = qtrue; memcpy( &sav_ucmd, ucmd, sizeof( usercmd_t ) ); memset( ucmd, 0, sizeof( usercmd_t ) ); //to keep pointing in same dir, need to set ucmd->angles ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; ucmd->angles[ROLL] = 0; if ( controlled->NPC ) { VectorClear( controlled->client->ps.moveDir ); controlled->client->ps.speed = (sav_ucmd.buttons&BUTTON_WALKING)?controlled->NPC->stats.walkSpeed:controlled->NPC->stats.runSpeed; } } else { ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; ucmd->angles[ROLL] = 0; } } else if ( ent->client->NPC_class == CLASS_ATST ) { if ( ucmd->upmove > 0 ) {//get out of ATST GEntity_UseFunc( ent->activator, ent, ent ); ucmd->upmove = 0;//ucmd->buttons = 0; } } PM_CheckForceUseButton( ent, ucmd ); } Vehicle_t *pVeh = NULL; // Rider logic. // NOTE: Maybe this should be extracted into a RiderUpdate() within the vehicle. if ( ( pVeh = G_IsRidingVehicle( ent ) ) != 0 ) { // If we're still in the vehicle... if ( pVeh->m_pVehicleInfo->UpdateRider( pVeh, ent, ucmd ) ) { restore_ucmd = qtrue; memcpy( &sav_ucmd, ucmd, sizeof( usercmd_t ) ); memset( ucmd, 0, sizeof( usercmd_t ) ); //to keep pointing in same dir, need to set ucmd->angles //ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; //ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; //ucmd->angles[ROLL] = 0; ucmd->angles[PITCH] = sav_ucmd.angles[PITCH]; ucmd->angles[YAW] = sav_ucmd.angles[YAW]; ucmd->angles[ROLL] = sav_ucmd.angles[ROLL]; //if ( sav_ucmd.weapon != ent->client->ps.weapon && PM_WeaponOkOnVehicle( sav_ucmd.weapon ) ) {//trying to change weapons to a valid weapon for this vehicle, to preserve this weapon change command ucmd->weapon = sav_ucmd.weapon; } //else {//keep our current weapon // ucmd->weapon = ent->client->ps.weapon; // if ( ent->client->ps.weapon != WP_NONE ) {//not changing weapons and we are using one of our weapons, not using vehicle weapon //so we actually want to do our fire weapon on us, not the vehicle ucmd->buttons = (sav_ucmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)); // sav_ucmd.buttons &= ~ucmd->buttons; } } } } ent->client->usercmd = *ucmd; // if ( !g_syncronousClients->integer ) { ClientThink_real( ent, ucmd ); } // If a vehicle, make sure to attach our driver and passengers here (after we pmove, which is done in Think_Real)) if ( ent->client && ent->client->NPC_class == CLASS_VEHICLE ) { pVeh = ent->m_pVehicle; pVeh->m_pVehicleInfo->AttachRiders( pVeh ); } // ClientThink_real can end up freeing this ent, need to check if ( restore_ucmd && ent->client ) {//restore ucmd for later so NPC you're controlling can refer to them memcpy( &ent->client->usercmd, &sav_ucmd, sizeof( usercmd_t ) ); } if ( ent->s.number ) {//NPCs drown, burn from lava, etc, also P_WorldEffects( ent ); } } void ClientEndPowerUps( gentity_t *ent ) { int i; if ( ent == NULL || ent->client == NULL ) { return; } // turn off any expired powerups for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { if ( ent->client->ps.powerups[ i ] < level.time ) { ent->client->ps.powerups[ i ] = 0; } } } /* ============== ClientEndFrame Called at the end of each server frame for each connected client A fast client will have multiple ClientThink for each ClientEdFrame, while a slow client may have multiple ClientEndFrame between ClientThink. ============== */ void ClientEndFrame( gentity_t *ent ) { // // If the end of unit layout is displayed, don't give // the player any normal movement attributes // // burn from lava, etc P_WorldEffects (ent); // apply all the damage taken this frame P_DamageFeedback (ent); // add the EF_CONNECTION flag if we haven't gotten commands recently /* if ( level.time - ent->client->lastCmdTime > 1000 ) { ent->s.eFlags |= EF_CONNECTION; } else { ent->s.eFlags &= ~EF_CONNECTION; } */ ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health... // G_SetClientSound (ent); }