float G_GetNonLocDamageMod( class_t pcl ) { int regionNum; damageRegion_t *region; for ( regionNum = 0; regionNum < g_numDamageRegions[ pcl ]; regionNum++ ) { region = &g_damageRegions[ pcl ][ regionNum ]; if ( !region->nonlocational ) { continue; } if ( g_debugDamage.integer > 1 ) { Com_Printf( "GetNonLocDamageModifier( pcl = %s ): " S_COLOR_GREEN "FOUND:" S_COLOR_WHITE " %.2f\n", BG_Class( pcl )->name, region->modifier ); } return region->modifier; } if ( g_debugDamage.integer > 1 ) { Com_Printf( "GetNonLocDamageModifier( pcl = %s ): " S_COLOR_YELLOW "NOT FOUND:" S_COLOR_WHITE " %.2f.\n", BG_Class( pcl )->name, 1.0f ); } return 1.0f; }
// FIXME: use nav handle instead of classes void G_BotNavInit() { int i; Log::Notice( "==== Bot Navigation Initialization ==== \n" ); for ( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) { classModelConfig_t *model; botClass_t bot; bot.polyFlagsInclude = POLYFLAGS_WALK; bot.polyFlagsExclude = POLYFLAGS_DISABLED; model = BG_ClassModelConfig( i ); if ( model->navMeshClass ) { if ( BG_ClassModelConfig( model->navMeshClass )->navMeshClass ) { Log::Warn( "class '%s': navmesh reference target class '%s' must have its own navmesh", BG_Class( i )->name, BG_Class( model->navMeshClass )->name ); return; } continue; } Q_strncpyz( bot.name, BG_Class( i )->name, sizeof( bot.name ) ); if ( !trap_BotSetupNav( &bot, &model->navHandle ) ) { return; } } navMeshLoaded = true; }
/* ================= CG_GetColorCharForHealth ================= */ char CG_GetColorCharForHealth( int clientnum ) { char health_char = '2'; int healthPercent; int maxHealth; int curWeaponClass = cgs.clientinfo[ clientnum ].curWeaponClass; if ( cgs.clientinfo[ clientnum ].team == TEAM_ALIENS ) { maxHealth = BG_Class( curWeaponClass )->health; } else { maxHealth = BG_Class( PCL_HUMAN )->health; } healthPercent = ( int )( 100.0f * ( float ) cgs.clientinfo[ clientnum ].health / ( float ) maxHealth ); if ( healthPercent < 33 ) { health_char = '1'; } else if ( healthPercent < 67 ) { health_char = '3'; } return health_char; }
void G_WeightAttack( gentity_t *self, gentity_t *victim ) { float weightDPS; int attackerMass, victimMass, weightDamage; // weigth damage is only dealt between clients if ( !self->client || !victim->client ) { return; } // don't do friendly fire if ( G_OnSameTeam( self, victim ) ) { return; } // ignore invincible targets if ( !victim->takedamage ) { return; } // attacker must be above victim if ( self->client->ps.origin[ 2 ] + self->r.mins[ 2 ] < victim->s.origin[ 2 ] + victim->r.maxs[ 2 ] ) { return; } // victim must be on the ground if ( victim->client->ps.groundEntityNum == ENTITYNUM_NONE ) { return; } // check timer if ( victim->client->nextCrushTime > level.time ) { return; } attackerMass = BG_Class( self->client->pers.classSelection )->mass; victimMass = BG_Class( victim->client->pers.classSelection )->mass; weightDPS = WEIGHTDMG_DMG_MODIFIER * MAX( attackerMass - victimMass, 0 ); if ( weightDPS > WEIGHTDMG_DPS_THRESHOLD ) { weightDamage = ( int )( weightDPS * ( WEIGHTDMG_REPEAT / 1000.0f ) ); if ( weightDamage > 0 ) { G_Damage( victim, self, self, NULL, victim->s.origin, weightDamage, DAMAGE_NO_LOCDAMAGE, ModWeight( self ) ); } } victim->client->nextCrushTime = level.time + WEIGHTDMG_REPEAT; }
void G_WeightAttack( gentity_t *self, gentity_t *victim ) { float weightDPS, weightDamage; int attackerMass, victimMass; // weigth damage is only dealt between clients if ( !self->client || !victim->client ) { return; } // don't do friendly fire if ( G_OnSameTeam( self, victim ) ) { return; } // attacker must be above victim if ( self->client->ps.origin[ 2 ] + self->r.mins[ 2 ] < victim->s.origin[ 2 ] + victim->r.maxs[ 2 ] ) { return; } // victim must be on the ground if ( victim->client->ps.groundEntityNum == ENTITYNUM_NONE ) { return; } // check timer if ( victim->client->nextCrushTime > level.time ) { return; } attackerMass = BG_Class( self->client->pers.classSelection )->mass; victimMass = BG_Class( victim->client->pers.classSelection )->mass; weightDPS = WEIGHTDMG_DMG_MODIFIER * std::max( attackerMass - victimMass, 0 ); if ( weightDPS > WEIGHTDMG_DPS_THRESHOLD ) { weightDamage = weightDPS * ( WEIGHTDMG_REPEAT / 1000.0f ); victim->entity->Damage(weightDamage, self, Vec3::Load(victim->s.origin), Util::nullopt, DAMAGE_NO_LOCDAMAGE, ModWeight(self)); } victim->client->nextCrushTime = level.time + WEIGHTDMG_REPEAT; }
void BotMoveToGoal( gentity_t *self ) { int staminaJumpCost; vec3_t dir; VectorCopy( self->botMind->nav.dir, dir ); if ( dir[ 2 ] < 0 ) { dir[ 2 ] = 0; VectorNormalize( dir ); } BotAvoidObstacles( self, dir ); BotSeek( self, dir ); staminaJumpCost = BG_Class( self->client->ps.stats[ STAT_CLASS ] )->staminaJumpCost; //dont sprint or dodge if we dont have enough stamina and are about to slow if ( self->client->pers.team == TEAM_HUMANS && self->client->ps.stats[ STAT_STAMINA ] < staminaJumpCost ) { usercmd_t *botCmdBuffer = &self->botMind->cmdBuffer; usercmdReleaseButton( botCmdBuffer->buttons, BUTTON_SPRINT ); usercmdReleaseButton( botCmdBuffer->buttons, BUTTON_DODGE ); // walk to regain stamina BotWalk( self, true ); } }
bool BotSprint( gentity_t *self, bool enable ) { usercmd_t *botCmdBuffer = &self->botMind->cmdBuffer; int staminaJumpCost; if ( !enable ) { usercmdReleaseButton( botCmdBuffer->buttons, BUTTON_SPRINT ); return false; } staminaJumpCost = BG_Class( self->client->ps.stats[ STAT_CLASS ] )->staminaJumpCost; if ( self->client->pers.team == TEAM_HUMANS && self->client->ps.stats[ STAT_STAMINA ] > staminaJumpCost && self->botMind->botSkill.level >= 5 ) { usercmdPressButton( botCmdBuffer->buttons, BUTTON_SPRINT ); BotWalk( self, false ); return true; } else { usercmdReleaseButton( botCmdBuffer->buttons, BUTTON_SPRINT ); return false; } }
void G_ImpactAttack( gentity_t *self, gentity_t *victim ) { float impactVelocity, impactEnergy; vec3_t knockbackDir; int attackerMass, impactDamage; // self must be a client if ( !self->client ) { return; } // ignore invincible targets if ( !victim->takedamage ) { return; } // don't do friendly fire if ( G_OnSameTeam( self, victim ) ) { return; } // attacker must be above victim if ( self->client->ps.origin[ 2 ] + self->r.mins[ 2 ] < victim->s.origin[ 2 ] + victim->r.maxs[ 2 ] ) { return; } // allow the granger airlifting ritual if ( victim->client && victim->client->ps.stats[ STAT_STATE2 ] & SS2_JETPACK_ACTIVE && ( self->client->pers.classSelection == PCL_ALIEN_BUILDER0 || self->client->pers.classSelection == PCL_ALIEN_BUILDER0_UPG ) ) { return; } // calculate impact damage attackerMass = BG_Class( self->client->pers.classSelection )->mass; impactVelocity = fabs( self->client->pmext.fallImpactVelocity[ 2 ] ) * IMPACTDMG_QU_TO_METER; // in m/s impactEnergy = attackerMass * impactVelocity * impactVelocity; // in J impactDamage = ( int )( impactEnergy * IMPACTDMG_JOULE_TO_DAMAGE ); // deal impact damage to both clients and structures, use a threshold for friendly fire if ( impactDamage > 0 ) { // calculate knockback direction VectorSubtract( victim->s.origin, self->client->ps.origin, knockbackDir ); VectorNormalize( knockbackDir ); G_Damage( victim, self, self, knockbackDir, victim->s.origin, impactDamage, DAMAGE_NO_LOCDAMAGE, ModWeight( self ) ); } }
bool BotShouldJump( gentity_t *self, gentity_t *blocker, const vec3_t dir ) { vec3_t playerMins; vec3_t playerMaxs; float jumpMagnitude; trace_t trace; const int TRACE_LENGTH = BOT_OBSTACLE_AVOID_RANGE; vec3_t end; //blocker is not on our team, so ignore if ( BotGetEntityTeam( self ) != BotGetEntityTeam( blocker ) ) { return false; } //already normalized BG_ClassBoundingBox( ( class_t ) self->client->ps.stats[STAT_CLASS], playerMins, playerMaxs, nullptr, nullptr, nullptr ); playerMins[2] += STEPSIZE; playerMaxs[2] += STEPSIZE; //Log::Debug(vtos(self->movedir)); VectorMA( self->s.origin, TRACE_LENGTH, dir, end ); //make sure we are moving into a block trap_Trace( &trace, self->s.origin, playerMins, playerMaxs, end, self->s.number, MASK_SHOT, 0 ); if ( trace.fraction >= 1.0f || blocker != &g_entities[trace.entityNum] ) { return false; } jumpMagnitude = BG_Class( ( class_t )self->client->ps.stats[STAT_CLASS] )->jumpMagnitude; //find the actual height of our jump jumpMagnitude = Square( jumpMagnitude ) / ( self->client->ps.gravity * 2 ); //prepare for trace playerMins[2] += jumpMagnitude; playerMaxs[2] += jumpMagnitude; //check if jumping will clear us of entity trap_Trace( &trace, self->s.origin, playerMins, playerMaxs, end, self->s.number, MASK_SHOT, 0 ); //if we can jump over it, then jump //note that we also test for a blocking barricade because barricades will collapse to let us through if ( blocker->s.modelindex == BA_A_BARRICADE || trace.fraction == 1.0f ) { return true; } else { return false; } }
void G_ImpactAttack( gentity_t *self, gentity_t *victim ) { float impactVelocity, impactEnergy, impactDamage; vec3_t knockbackDir; int attackerMass; // self must be a client if ( !self->client ) { return; } // don't do friendly fire if ( G_OnSameTeam( self, victim ) ) { return; } // attacker must be above victim if ( self->client->ps.origin[ 2 ] + self->r.mins[ 2 ] < victim->s.origin[ 2 ] + victim->r.maxs[ 2 ] ) { return; } // allow the granger airlifting ritual if ( victim->client && victim->client->ps.stats[ STAT_STATE2 ] & SS2_JETPACK_ACTIVE && ( self->client->pers.classSelection == PCL_ALIEN_BUILDER0 || self->client->pers.classSelection == PCL_ALIEN_BUILDER0_UPG ) ) { return; } // calculate impact damage impactVelocity = fabs( self->client->pmext.fallImpactVelocity[ 2 ] ) * QU_TO_METER; // in m/s if (!impactVelocity) return; attackerMass = BG_Class( self->client->pers.classSelection )->mass; impactEnergy = attackerMass * impactVelocity * impactVelocity; // in J impactDamage = impactEnergy * IMPACTDMG_JOULE_TO_DAMAGE; // calculate knockback direction VectorSubtract( victim->s.origin, self->client->ps.origin, knockbackDir ); VectorNormalize( knockbackDir ); victim->entity->Damage((float)impactDamage, self, Vec3::Load(victim->s.origin), Vec3::Load(knockbackDir), DAMAGE_NO_LOCDAMAGE, ModWeight(self)); }
/* ============== CG_CalculateWeaponPosition ============== */ static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) { float scale; int delta; float fracsin; float bob; weaponInfo_t *weapon; weapon = &cg_weapons[ cg.predictedPlayerState.weapon ]; VectorCopy( cg.refdef.vieworg, origin ); VectorCopy( cg.refdefViewAngles, angles ); // on odd legs, invert some angles if( cg.bobcycle & 1 ) scale = -cg.xyspeed; else scale = cg.xyspeed; // gun angles from bobbing // bob amount is class dependant bob = BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->bob; if( bob != 0 ) { angles[ ROLL ] += scale * cg.bobfracsin * 0.005; angles[ YAW ] += scale * cg.bobfracsin * 0.01; angles[ PITCH ] += cg.xyspeed * cg.bobfracsin * 0.005; } // drop the weapon when landing if( !weapon->noDrift ) { delta = cg.time - cg.landTime; if( delta < LAND_DEFLECT_TIME ) origin[ 2 ] += cg.landChange*0.25 * delta / LAND_DEFLECT_TIME; else if( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) origin[ 2 ] += cg.landChange*0.25 * ( LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta ) / LAND_RETURN_TIME; // idle drift scale = cg.xyspeed + 40; fracsin = sin( cg.time * 0.001 ); angles[ ROLL ] += scale * fracsin * 0.01; angles[ YAW ] += scale * fracsin * 0.01; angles[ PITCH ] += scale * fracsin * 0.01; } }
// TODO: Consider location as well as direction when both given. void KnockbackComponent::HandleDamage(float amount, gentity_t* source, Util::optional<Vec3> location, Util::optional<Vec3> direction, int flags, meansOfDeath_t meansOfDeath) { if (!(flags & DAMAGE_KNOCKBACK)) return; if (amount <= 0.0f) return; if (!direction) { knockbackLogger.Warn("Received damage message with knockback flag set but no direction."); return; } if (Math::Length(direction.value()) == 0.0f) { knockbackLogger.Warn("Attempt to do knockback with null vector direction."); return; } // TODO: Remove dependency on client. gclient_t *client = entity.oldEnt->client; assert(client); // Check for immunity. if (client->noclip) return; if (client->sess.spectatorState != SPECTATOR_NOT) return; float mass = (float)BG_Class(client->ps.stats[ STAT_CLASS ])->mass; if (mass <= 0.0f) { knockbackLogger.Warn("Attempt to do knockback against target with no mass, assuming normal mass."); mass = KNOCKBACK_NORMAL_MASS; } float massMod = Math::Clamp(KNOCKBACK_NORMAL_MASS / mass, KNOCKBACK_MIN_MASSMOD, KNOCKBACK_MAX_MASSMOD); float strength = amount * DAMAGE_TO_KNOCKBACK * massMod; // Change client velocity. Vec3 clientVelocity = Vec3::Load(client->ps.velocity); clientVelocity += Math::Normalize(direction.value()) * strength; clientVelocity.Store(client->ps.velocity); // Set pmove timer so that the client can't cancel out the movement immediately. if (!client->ps.pm_time) { client->ps.pm_time = KNOCKBACK_PMOVE_TIME; client->ps.pm_flags |= PMF_TIME_KNOCKBACK; } knockbackLogger.Debug("Knockback: client: %i, strength: %.1f (massMod: %.1f).", entity.oldEnt->s.number, strength, massMod); }
bool BotJump( gentity_t *self ) { int staminaJumpCost; if ( self->client->pers.team == TEAM_HUMANS ) { staminaJumpCost = BG_Class( self->client->ps.stats[ STAT_CLASS ] )->staminaJumpCost; if ( self->client->ps.stats[STAT_STAMINA] < staminaJumpCost ) { return false; } } self->botMind->cmdBuffer.upmove = 127; return true; }
/** * Return common rank * @param self * @param target * @return */ int BotTargetRank( gentity_t *self, gentity_t *target ) { float distance; float rank = 0; float damage; float damage_pct; distance = botGetDistanceBetweenPlayer(self, target); rank += 3000 / distance; //--- Add some rand chance (not so high) rank += G_Rand_Range(0, 10); //If we are attacking this target, increase the chance to stick to it (unless we haven't hit it within 5 secs): if(self->bot->Enemy == target) { if(!botHitTarget( self, 5000 )) { rank -= 30; } else { rank += 30; } } //If its attacking you if(self->client->lasthurt_client == target->s.number) { rank += 10; } if(target->client) { damage = self->credits[ target->client->ps.clientNum ]; //How much it has damaged you damage_pct = (damage / (float)BG_Class( self->client->ps.stats[ STAT_CLASS ] )->health) * 100; if(damage_pct > 50) { rank += 20; } else if(damage_pct > 25) { rank += 10; } } //If target health is critical, increase its chances if(target->health < 50) { //First Sound warning rank += 25; } if(target->health < 25) { //Second Sound warning rank += 50; } //The enemies or my friends are my enemies! if(self->bot->Friend) { if(self->bot->Friend->bot && self->bot->Friend->bot->Enemy == target) { rank += 30; } } return rank; }
static void CG_CompleteClass( void ) { int i = 0; if ( cgs.clientinfo[ cg.clientNum ].team == TEAM_ALIENS ) { for ( i = PCL_ALIEN_BUILDER0; i < PCL_HUMAN; i++ ) { trap_CompleteCallback( BG_Class( i )->name ); } } else if ( cgs.clientinfo[ cg.clientNum ].team == TEAM_HUMANS ) { trap_CompleteCallback( BG_Weapon( WP_HBUILD )->name ); trap_CompleteCallback( BG_Weapon( WP_MACHINEGUN )->name ); } }
static void CG_CompleteClass() { int i = 0; if ( cgs.clientinfo[ cg.clientNum ].team == TEAM_ALIENS ) { // TODO: Add iterator for alien/human classes for ( i = PCL_ALIEN_BUILDER0; i < PCL_HUMAN_NAKED; i++ ) { trap_CompleteCallback( BG_Class( i )->name ); } } else if ( cgs.clientinfo[ cg.clientNum ].team == TEAM_HUMANS ) { trap_CompleteCallback( BG_Weapon( WP_HBUILD )->name ); trap_CompleteCallback( BG_Weapon( WP_MACHINEGUN )->name ); } }
/* =============== G_CrushAttack Should only be called if there was an impact between a tyrant and another player =============== */ void G_CrushAttack( gentity_t *ent, gentity_t *victim ) { vec3_t dir; float jump; int damage; if ( !victim->takedamage || ent->client->ps.origin[ 2 ] + ent->r.mins[ 2 ] < victim->s.origin[ 2 ] + victim->r.maxs[ 2 ] || ( victim->client && victim->client->ps.groundEntityNum == ENTITYNUM_NONE ) ) { return; } // Deal velocity based damage to target jump = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->jumpMagnitude; damage = ( ent->client->pmext.fallVelocity + jump ) * -LEVEL4_CRUSH_DAMAGE_PER_V; if ( damage < 0 ) { damage = 0; } // Players also get damaged periodically if ( victim->client && ent->client->lastCrushTime + LEVEL4_CRUSH_REPEAT < level.time ) { ent->client->lastCrushTime = level.time; damage += LEVEL4_CRUSH_DAMAGE; } if ( damage < 1 ) { return; } // Crush the victim over a period of time VectorSubtract( victim->s.origin, ent->client->ps.origin, dir ); VectorNormalize( dir ); G_Damage( victim, ent, ent, dir, victim->s.origin, damage, DAMAGE_NO_LOCDAMAGE, MOD_LEVEL4_CRUSH ); }
// this causes a compiler bug on mac MrC compiler static void CG_StepOffset( void ) { float steptime; int timeDelta; vec3_t normal; playerState_t *ps = &cg.predictedPlayerState; BG_GetClientNormal( ps, normal ); steptime = BG_Class( ps->stats[ STAT_CLASS ] )->steptime; // smooth out stair climbing timeDelta = cg.time - cg.stepTime; if( timeDelta < steptime ) { float stepChange = cg.stepChange * (steptime - timeDelta) / steptime; VectorMA( cg.refdef.vieworg, -stepChange, normal, cg.refdef.vieworg ); } }
/* =============== CG_OffsetFirstPersonView =============== */ void CG_OffsetFirstPersonView( void ) { float *origin; float *angles; float bob; float ratio; float delta; float speed; float f; vec3_t predictedVelocity; int timeDelta; float bob2; vec3_t normal, baseOrigin; playerState_t *ps = &cg.predictedPlayerState; BG_GetClientNormal( ps, normal ); if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { return; } origin = cg.refdef.vieworg; angles = cg.refdefViewAngles; VectorCopy( origin, baseOrigin ); // if dead, fix the angle and don't add any kick if ( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 ) { angles[ ROLL ] = 40; angles[ PITCH ] = -15; angles[ YAW ] = cg.snap->ps.stats[ STAT_VIEWLOCK ]; origin[ 2 ] += cg.predictedPlayerState.viewheight; return; } // add angles based on damage kick if ( cg.damageTime ) { ratio = cg.time - cg.damageTime; if ( ratio < DAMAGE_DEFLECT_TIME ) { ratio /= DAMAGE_DEFLECT_TIME; angles[ PITCH ] += ratio * cg.v_dmg_pitch; angles[ ROLL ] += ratio * cg.v_dmg_roll; } else { ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME; if ( ratio > 0 ) { angles[ PITCH ] += ratio * cg.v_dmg_pitch; angles[ ROLL ] += ratio * cg.v_dmg_roll; } } } // add pitch based on fall kick #if 0 ratio = ( cg.time - cg.landTime ) / FALL_TIME; if ( ratio < 0 ) { ratio = 0; } angles[ PITCH ] += ratio * cg.fall_value; #endif // add angles based on velocity VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity ); delta = DotProduct( predictedVelocity, cg.refdef.viewaxis[ 0 ] ); angles[ PITCH ] += delta * cg_runpitch.value; delta = DotProduct( predictedVelocity, cg.refdef.viewaxis[ 1 ] ); angles[ ROLL ] -= delta * cg_runroll.value; // add angles based on bob // bob amount is class-dependent if ( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) { bob2 = 0.0f; } else { bob2 = BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->bob; } #define LEVEL4_FEEDBACK 10.0f //give a charging player some feedback if ( ps->weapon == WP_ALEVEL4 ) { if ( ps->stats[ STAT_MISC ] > 0 ) { float fraction = ( float ) ps->stats[ STAT_MISC ] / LEVEL4_TRAMPLE_CHARGE_MAX; if ( fraction > 1.0f ) { fraction = 1.0f; } bob2 *= ( 1.0f + fraction * LEVEL4_FEEDBACK ); } } if ( bob2 != 0.0f ) { // make sure the bob is visible even at low speeds speed = cg.xyspeed > 200 ? cg.xyspeed : 200; delta = cg.bobfracsin * ( bob2 ) * speed; if ( cg.predictedPlayerState.pm_flags & PMF_DUCKED ) { delta *= 3; // crouching } angles[ PITCH ] += delta; delta = cg.bobfracsin * ( bob2 ) * speed; if ( cg.predictedPlayerState.pm_flags & PMF_DUCKED ) { delta *= 3; // crouching accentuates roll } if ( cg.bobcycle & 1 ) { delta = -delta; } angles[ ROLL ] += delta; } #define LEVEL3_FEEDBACK 20.0f //provide some feedback for pouncing if ( ( cg.predictedPlayerState.weapon == WP_ALEVEL3 || cg.predictedPlayerState.weapon == WP_ALEVEL3_UPG ) && cg.predictedPlayerState.stats[ STAT_MISC ] > 0 ) { float fraction1, fraction2; vec3_t forward; AngleVectors( angles, forward, NULL, NULL ); VectorNormalize( forward ); fraction1 = ( float ) cg.predictedPlayerState.stats[ STAT_MISC ] / LEVEL3_POUNCE_TIME_UPG; if ( fraction1 > 1.0f ) { fraction1 = 1.0f; } fraction2 = -sin( fraction1 * M_PI / 2 ); VectorMA( origin, LEVEL3_FEEDBACK * fraction2, forward, origin ); } #define STRUGGLE_DIST 5.0f #define STRUGGLE_TIME 250 //allow the player to struggle a little whilst grabbed if ( cg.predictedPlayerState.pm_type == PM_GRABBED ) { vec3_t forward, right, up; usercmd_t cmd; int cmdNum; float fFraction, rFraction, uFraction; cmdNum = trap_GetCurrentCmdNumber(); trap_GetUserCmd( cmdNum, &cmd ); AngleVectors( angles, forward, right, up ); fFraction = ( float )( cg.time - cg.forwardMoveTime ) / STRUGGLE_TIME; rFraction = ( float )( cg.time - cg.rightMoveTime ) / STRUGGLE_TIME; uFraction = ( float )( cg.time - cg.upMoveTime ) / STRUGGLE_TIME; if ( fFraction > 1.0f ) { fFraction = 1.0f; } if ( rFraction > 1.0f ) { rFraction = 1.0f; } if ( uFraction > 1.0f ) { uFraction = 1.0f; } if ( cmd.forwardmove > 0 ) { VectorMA( origin, STRUGGLE_DIST * fFraction, forward, origin ); } else if ( cmd.forwardmove < 0 ) { VectorMA( origin, -STRUGGLE_DIST * fFraction, forward, origin ); } else { cg.forwardMoveTime = cg.time; } if ( cmd.rightmove > 0 ) { VectorMA( origin, STRUGGLE_DIST * rFraction, right, origin ); } else if ( cmd.rightmove < 0 ) { VectorMA( origin, -STRUGGLE_DIST * rFraction, right, origin ); } else { cg.rightMoveTime = cg.time; } if ( cmd.upmove > 0 ) { VectorMA( origin, STRUGGLE_DIST * uFraction, up, origin ); } else if ( cmd.upmove < 0 ) { VectorMA( origin, -STRUGGLE_DIST * uFraction, up, origin ); } else { cg.upMoveTime = cg.time; } } // this *feels* more realisitic for humans <- this comment feels very descriptive if ( cg.predictedPlayerState.persistant[ PERS_TEAM ] == TEAM_HUMANS && cg.predictedPlayerState.pm_type == PM_NORMAL ) { angles[ PITCH ] += cg.bobfracsin * bob2 * 0.5; } // add view height VectorMA( origin, ps->viewheight, normal, origin ); // smooth out duck height changes timeDelta = cg.time - cg.duckTime; if ( timeDelta < DUCK_TIME ) { cg.refdef.vieworg[ 2 ] -= cg.duckChange * ( DUCK_TIME - timeDelta ) / DUCK_TIME; } // add bob height bob = cg.bobfracsin * cg.xyspeed * bob2; if ( bob > 6 ) { bob = 6; } VectorMA( origin, bob, normal, origin ); // add fall height delta = cg.time - cg.landTime; if ( delta < LAND_DEFLECT_TIME ) { f = delta / LAND_DEFLECT_TIME; cg.refdef.vieworg[ 2 ] += cg.landChange * f; } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { delta -= LAND_DEFLECT_TIME; f = 1.0 - ( delta / LAND_RETURN_TIME ); cg.refdef.vieworg[ 2 ] += cg.landChange * f; } // add step offset CG_StepOffset(); }
static int CG_CalcFov( void ) { float y; float phase; float v; int contents; float fov_x, fov_y; float zoomFov; float f; int inwater; int attribFov; usercmd_t cmd; usercmd_t oldcmd; int cmdNum; cmdNum = trap_GetCurrentCmdNumber( ); trap_GetUserCmd( cmdNum, &cmd ); trap_GetUserCmd( cmdNum - 1, &oldcmd ); // switch follow modes if necessary: cycle between free -> follow -> third-person follow if( cmd.buttons & BUTTON_USE_HOLDABLE && !( oldcmd.buttons & BUTTON_USE_HOLDABLE ) ) { if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { if( !cg.chaseFollow ) cg.chaseFollow = qtrue; else { cg.chaseFollow = qfalse; trap_SendClientCommand( "follow\n" ); } } else if ( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) trap_SendClientCommand( "follow\n" ); } if( cg.predictedPlayerState.pm_type == PM_INTERMISSION || ( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) || ( cg.renderingThirdPerson ) ) { // if in intermission or third person, use a fixed value fov_y = BASE_FOV_Y; } else { // don't lock the fov globally - we need to be able to change it attribFov = BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->fov * 0.75f; fov_y = attribFov; if ( fov_y < 1.0f ) fov_y = 1.0f; else if ( fov_y > MAX_FOV_Y ) fov_y = MAX_FOV_Y; if( cg.spawnTime > ( cg.time - FOVWARPTIME ) && BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_CLASS ], SCA_FOVWARPS ) ) { float fraction = (float)( cg.time - cg.spawnTime ) / FOVWARPTIME; fov_y = MAX_FOV_WARP_Y - ( ( MAX_FOV_WARP_Y - fov_y ) * fraction ); } // account for zooms zoomFov = BG_Weapon( cg.predictedPlayerState.weapon )->zoomFov * 0.75f; if ( zoomFov < 1.0f ) zoomFov = 1.0f; else if ( zoomFov > attribFov ) zoomFov = attribFov; // only do all the zoom stuff if the client CAN zoom // FIXME: zoom control is currently hard coded to BUTTON_ATTACK2 if( BG_Weapon( cg.predictedPlayerState.weapon )->canZoom ) { if ( cg.zoomed ) { f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; if ( f > 1.0f ) fov_y = zoomFov; else fov_y = fov_y + f * ( zoomFov - fov_y ); // BUTTON_ATTACK2 isn't held so unzoom next time if( !( cmd.buttons & BUTTON_ATTACK2 ) ) { cg.zoomed = qfalse; cg.zoomTime = MIN( cg.time, cg.time + cg.time - cg.zoomTime - ZOOM_TIME ); } } else { f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; if ( f <= 1.0f ) fov_y = zoomFov + f * ( fov_y - zoomFov ); // BUTTON_ATTACK2 is held so zoom next time if( cmd.buttons & BUTTON_ATTACK2 ) { cg.zoomed = qtrue; cg.zoomTime = MIN( cg.time, cg.time + cg.time - cg.zoomTime - ZOOM_TIME ); } } } } y = cg.refdef.height / tan( 0.5f * DEG2RAD( fov_y ) ); fov_x = atan2( cg.refdef.width, y ); fov_x = 2.0f * RAD2DEG( fov_x ); // warp if underwater contents = CG_PointContents( cg.refdef.vieworg, -1 ); if( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { phase = cg.time / 1000.0f * WAVE_FREQUENCY * M_PI * 2.0f; v = WAVE_AMPLITUDE * sin( phase ); fov_x += v; fov_y -= v; inwater = qtrue; } else inwater = qfalse; if( ( cg.predictedPlayerEntity.currentState.eFlags & EF_POISONCLOUDED ) && ( cg.time - cg.poisonedTime < PCLOUD_DISORIENT_DURATION) && cg.predictedPlayerState.stats[ STAT_HEALTH ] > 0 && !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { float scale = 1.0f - (float)( cg.time - cg.poisonedTime ) / BG_PlayerPoisonCloudTime( &cg.predictedPlayerState ); phase = ( cg.time - cg.poisonedTime ) / 1000.0f * PCLOUD_ZOOM_FREQUENCY * M_PI * 2.0f; v = PCLOUD_ZOOM_AMPLITUDE * sin( phase ) * scale; fov_x += v; fov_y += v; } // set it cg.refdef.fov_x = fov_x; cg.refdef.fov_y = fov_y; if( !cg.zoomed ) cg.zoomSensitivity = 1.0f; else cg.zoomSensitivity = cg.refdef.fov_y / 75.0f; return inwater; }
/* =============== CG_OffsetFirstPersonView =============== */ void CG_OffsetFirstPersonView( void ) { float *origin; float *angles; float bob; float ratio; float delta; float speed; float f; vec3_t predictedVelocity; int timeDelta; float bob2; vec3_t normal, baseOrigin; playerState_t *ps = &cg.predictedPlayerState; BG_GetClientNormal( ps, normal ); if( cg.snap->ps.pm_type == PM_INTERMISSION ) return; origin = cg.refdef.vieworg; angles = cg.refdefViewAngles; VectorCopy( origin, baseOrigin ); // if dead, fix the angle and don't add any kick if( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 ) { angles[ ROLL ] = 40; angles[ PITCH ] = -15; angles[ YAW ] = cg.snap->ps.stats[ STAT_VIEWLOCK ]; origin[ 2 ] += cg.predictedPlayerState.viewheight; return; } // camera shake effect else if( cg.snap->ps.stats[ STAT_SHAKE ] > 0 ) { float fac; fac = (float) cg.snap->ps.stats[ STAT_SHAKE ] * cg_cameraShakeMagnitude.value * 0.15f; angles[ 0 ] += crandom() * fac; angles[ 1 ] += crandom() * fac; angles[ 2 ] += crandom() * fac; } // add angles based on damage kick if( cg.damageTime ) { ratio = cg.time - cg.damageTime; if( ratio < DAMAGE_DEFLECT_TIME ) { ratio /= DAMAGE_DEFLECT_TIME; angles[ PITCH ] += ratio * cg.v_dmg_pitch; angles[ ROLL ] += ratio * cg.v_dmg_roll; } else { ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME; if( ratio > 0 ) { angles[ PITCH ] += ratio * cg.v_dmg_pitch; angles[ ROLL ] += ratio * cg.v_dmg_roll; } } } // add pitch based on fall kick #if 0 ratio = ( cg.time - cg.landTime) / FALL_TIME; if (ratio < 0) ratio = 0; angles[PITCH] += ratio * cg.fall_value; #endif // add angles based on velocity VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity ); delta = DotProduct( predictedVelocity, cg.refdef.viewaxis[ 0 ] ); angles[ PITCH ] += delta * cg_runpitch.value; delta = DotProduct( predictedVelocity, cg.refdef.viewaxis[ 1 ] ); angles[ ROLL ] -= delta * cg_runroll.value; // add angles based on bob // bob amount is class dependant if( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) bob2 = 0.0f; else bob2 = BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->bob; #define LEVEL4_FEEDBACK 10.0f //give a charging player some feedback if( ps->weapon == WP_ALEVEL4 ) { if( ps->stats[ STAT_MISC ] > 0 ) { float fraction = (float)ps->stats[ STAT_MISC ] / LEVEL4_TRAMPLE_CHARGE_MAX; if( fraction > 1.0f ) fraction = 1.0f; bob2 *= ( 1.0f + fraction * LEVEL4_FEEDBACK ); } } if( bob2 != 0.0f ) { // make sure the bob is visible even at low speeds speed = cg.xyspeed > 200 ? cg.xyspeed : 200; delta = cg.bobfracsin * ( bob2 ) * speed; if( cg.predictedPlayerState.pm_flags & PMF_DUCKED ) delta *= 3; // crouching angles[ PITCH ] += delta; delta = cg.bobfracsin * ( bob2 ) * speed; if( cg.predictedPlayerState.pm_flags & PMF_DUCKED ) delta *= 3; // crouching accentuates roll if( cg.bobcycle & 1 ) delta = -delta; angles[ ROLL ] += delta; } #define LEVEL3_FEEDBACK 20.0f //provide some feedback for pouncing if( ( cg.predictedPlayerState.weapon == WP_ALEVEL3 || cg.predictedPlayerState.weapon == WP_ALEVEL3_UPG ) && cg.predictedPlayerState.stats[ STAT_MISC ] > 0 ) { float fraction1, fraction2; vec3_t forward; AngleVectors( angles, forward, NULL, NULL ); VectorNormalize( forward ); fraction1 = (float)cg.predictedPlayerState.stats[ STAT_MISC ] / LEVEL3_POUNCE_TIME_UPG; if( fraction1 > 1.0f ) fraction1 = 1.0f; fraction2 = -sin( fraction1 * M_PI / 2 ); VectorMA( origin, LEVEL3_FEEDBACK * fraction2, forward, origin ); } #define STRUGGLE_DIST 5.0f #define STRUGGLE_TIME 250 //allow the player to struggle a little whilst grabbed if( cg.predictedPlayerState.pm_type == PM_GRABBED ) { vec3_t forward, right, up; usercmd_t cmd; int cmdNum; float fFraction, rFraction, uFraction; float fFraction2, rFraction2, uFraction2; cmdNum = trap_GetCurrentCmdNumber(); trap_GetUserCmd( cmdNum, &cmd ); AngleVectors( angles, forward, right, up ); fFraction = (float)( cg.time - cg.forwardMoveTime ) / STRUGGLE_TIME; rFraction = (float)( cg.time - cg.rightMoveTime ) / STRUGGLE_TIME; uFraction = (float)( cg.time - cg.upMoveTime ) / STRUGGLE_TIME; if( fFraction > 1.0f ) fFraction = 1.0f; if( rFraction > 1.0f ) rFraction = 1.0f; if( uFraction > 1.0f ) uFraction = 1.0f; fFraction2 = -sin( fFraction * M_PI / 2 ); rFraction2 = -sin( rFraction * M_PI / 2 ); uFraction2 = -sin( uFraction * M_PI / 2 ); if( cmd.forwardmove > 0 ) VectorMA( origin, STRUGGLE_DIST * fFraction, forward, origin ); else if( cmd.forwardmove < 0 ) VectorMA( origin, -STRUGGLE_DIST * fFraction, forward, origin ); else cg.forwardMoveTime = cg.time; if( cmd.rightmove > 0 ) VectorMA( origin, STRUGGLE_DIST * rFraction, right, origin ); else if( cmd.rightmove < 0 ) VectorMA( origin, -STRUGGLE_DIST * rFraction, right, origin ); else cg.rightMoveTime = cg.time; if( cmd.upmove > 0 ) VectorMA( origin, STRUGGLE_DIST * uFraction, up, origin ); else if( cmd.upmove < 0 ) VectorMA( origin, -STRUGGLE_DIST * uFraction, up, origin ); else cg.upMoveTime = cg.time; } if( ( cg.predictedPlayerEntity.currentState.eFlags & EF_POISONCLOUDED ) && ( cg.time - cg.poisonedTime < PCLOUD_DISORIENT_DURATION) && !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { float scale, fraction, pitchFraction; scale = 1.0f - (float)( cg.time - cg.poisonedTime ) / BG_PlayerPoisonCloudTime( &cg.predictedPlayerState ); if( scale < 0.0f ) scale = 0.0f; fraction = sin( ( cg.time - cg.poisonedTime ) / 500.0f * M_PI * PCLOUD_ROLL_FREQUENCY ) * scale; pitchFraction = sin( ( cg.time - cg.poisonedTime ) / 200.0f * M_PI * PCLOUD_ROLL_FREQUENCY ) * scale; angles[ ROLL ] += fraction * PCLOUD_ROLL_AMPLITUDE; angles[ YAW ] += fraction * PCLOUD_ROLL_AMPLITUDE; angles[ PITCH ] += pitchFraction * PCLOUD_ROLL_AMPLITUDE / 2.0f; } // this *feels* more realisitic for humans if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS && ( cg.predictedPlayerState.pm_type == PM_NORMAL || cg.predictedPlayerState.pm_type == PM_JETPACK ) ) { angles[PITCH] += cg.bobfracsin * bob2 * 0.5; // heavy breathing effects //FIXME: sound if( cg.predictedPlayerState.stats[ STAT_STAMINA ] < STAMINA_BREATHING_LEVEL ) { float deltaBreath = ( cg.predictedPlayerState.stats[ STAT_STAMINA ] - STAMINA_BREATHING_LEVEL ) / -250.0; float deltaAngle = cos( (float)cg.time/150.0 ) * deltaBreath; deltaAngle += ( deltaAngle < 0 ? -deltaAngle : deltaAngle ) * 0.5; angles[ PITCH ] -= deltaAngle; } } //=================================== // add view height VectorMA( origin, ps->viewheight, normal, origin ); // smooth out duck height changes timeDelta = cg.time - cg.duckTime; if( timeDelta < DUCK_TIME) { cg.refdef.vieworg[ 2 ] -= cg.duckChange * ( DUCK_TIME - timeDelta ) / DUCK_TIME; } // add bob height bob = cg.bobfracsin * cg.xyspeed * bob2; if( bob > 6 ) bob = 6; VectorMA( origin, bob, normal, origin ); // add fall height delta = cg.time - cg.landTime; if( delta < LAND_DEFLECT_TIME ) { f = delta / LAND_DEFLECT_TIME; cg.refdef.vieworg[ 2 ] += cg.landChange * f; } else if( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { delta -= LAND_DEFLECT_TIME; f = 1.0 - ( delta / LAND_RETURN_TIME ); cg.refdef.vieworg[ 2 ] += cg.landChange * f; } // add step offset CG_StepOffset( ); }
/* ================== G_RewardAttackers Function to distribute rewards to entities that killed this one. ================== */ void G_RewardAttackers( gentity_t *self ) { float value, reward; int playerNum, enemyDamage, maxHealth, damageShare; gentity_t *player; team_t ownTeam, playerTeam; confidence_reason_t reason; confidence_qualifier_t qualifier; // Only reward killing players and buildables if ( self->client ) { ownTeam = self->client->pers.teamSelection; maxHealth = self->client->ps.stats[ STAT_MAX_HEALTH ]; value = ( float )BG_GetValueOfPlayer( &self->client->ps ); } else if ( self->s.eType == ET_BUILDABLE ) { ownTeam = self->buildableTeam; maxHealth = BG_Buildable( self->s.modelindex )->health; value = ( float )BG_Buildable( self->s.modelindex )->value; // Give partial credits for buildables in construction if ( !self->spawned ) { value *= ( float )( level.time - self->creationTime ) / BG_Buildable( self->s.modelindex )->buildTime; } } else { return; } enemyDamage = 0; // Sum up damage dealt by enemies for ( playerNum = 0; playerNum < level.maxclients; playerNum++ ) { player = &g_entities[ playerNum ]; playerTeam = player->client->pers.teamSelection; // Player must be on the other team if ( playerTeam == ownTeam || playerTeam <= TEAM_NONE || playerTeam >= NUM_TEAMS ) { continue; } enemyDamage += self->credits[ playerNum ]; } if ( enemyDamage <= 0 ) { return; } // Give individual rewards for ( playerNum = 0; playerNum < level.maxclients; playerNum++ ) { player = &g_entities[ playerNum ]; playerTeam = player->client->pers.teamSelection; damageShare = self->credits[ playerNum ]; // Clear reward array self->credits[ playerNum ] = 0; // Player must be on the other team if ( playerTeam == ownTeam || playerTeam <= TEAM_NONE || playerTeam >= NUM_TEAMS ) { continue; } // Player must have dealt damage if ( damageShare <= 0 ) { continue; } reward = value * ( damageShare / ( float )maxHealth ); if ( reward <= 0.0f ) { continue; } if ( self->s.eType == ET_BUILDABLE ) { G_AddConfidenceToScore( player, reward ); switch ( self->s.modelindex ) { case BA_A_OVERMIND: case BA_H_REACTOR: reason = CONF_REAS_DESTR_CRUCIAL; break; case BA_A_ACIDTUBE: case BA_A_TRAPPER: case BA_A_HIVE: case BA_H_MGTURRET: case BA_H_TESLAGEN: reason = CONF_REAS_DESTR_AGGRESSIVE; break; default: reason = CONF_REAS_DESTR_SUPPORT; } qualifier = CONF_QUAL_NONE; G_AddConfidence( playerTeam, CONFIDENCE_DESTRUCTION, reason, qualifier, reward, player ); } else { G_AddCreditsToScore( player, ( int )reward ); G_AddCreditToClient( player->client, ( short )reward, qtrue ); // Give confidence for killing non-naked players outside the friendly base switch ( self->client->ps.stats[ STAT_CLASS ] ) { case PCL_ALIEN_LEVEL0: case PCL_ALIEN_BUILDER0: case PCL_ALIEN_BUILDER0_UPG: break; case PCL_HUMAN: // Treat a human just wearing light armor as naked if ( ( int )value <= BG_Class( PCL_HUMAN )->value + ( BG_Upgrade( UP_LIGHTARMOUR )->price / 2 ) ) { break; } default: if ( G_InsideBase( player, qtrue ) || G_InsideBase( self, qfalse ) ) { break; } qualifier = CONF_QUAL_OUTSIDE_OWN_BASE; G_AddConfidence( playerTeam, CONFIDENCE_KILLING, CONF_REAS_KILLING, qualifier, reward * CONFIDENCE_PER_CREDIT, player ); } } } }
/* ============== CG_EntityEvent An entity has an event value also called by CG_CheckPlayerstateEvents ============== */ void CG_EntityEvent( centity_t *cent, vec3_t position ) { entityState_t *es; int event; vec3_t dir; const char *s; int clientNum; clientInfo_t *ci; int steptime; if ( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) { steptime = 200; } else { steptime = BG_Class( cg.snap->ps.stats[ STAT_CLASS ] )->steptime; } es = ¢->currentState; event = es->event & ~EV_EVENT_BITS; if ( cg_debugEvents.integer ) { CG_Printf( "ent:%3i event:%3i %s\n", es->number, event, BG_EventName( event ) ); } if ( !event ) { return; } clientNum = es->clientNum; if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { clientNum = 0; } ci = &cgs.clientinfo[ clientNum ]; switch ( event ) { case EV_FOOTSTEP: if ( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { if ( ci->footsteps == FOOTSTEP_CUSTOM ) { trap_S_StartSound( NULL, es->number, CHAN_BODY, ci->customFootsteps[ rand() & 3 ] ); } else { trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.footsteps[ ci->footsteps ][ rand() & 3 ] ); } } break; case EV_FOOTSTEP_METAL: if ( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { if ( ci->footsteps == FOOTSTEP_CUSTOM ) { trap_S_StartSound( NULL, es->number, CHAN_BODY, ci->customMetalFootsteps[ rand() & 3 ] ); } else { trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.footsteps[ FOOTSTEP_METAL ][ rand() & 3 ] ); } } break; case EV_FOOTSTEP_SQUELCH: if ( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.footsteps[ FOOTSTEP_FLESH ][ rand() & 3 ] ); } break; case EV_FOOTSPLASH: if ( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.footsteps[ FOOTSTEP_SPLASH ][ rand() & 3 ] ); } break; case EV_FOOTWADE: if ( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.footsteps[ FOOTSTEP_SPLASH ][ rand() & 3 ] ); } break; case EV_SWIM: if ( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE ) { trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.footsteps[ FOOTSTEP_SPLASH ][ rand() & 3 ] ); } break; case EV_FALL_SHORT: trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound ); if ( clientNum == cg.predictedPlayerState.clientNum ) { // smooth landing z changes cg.landChange = -8; cg.landTime = cg.time; } break; case EV_FALL_MEDIUM: // use a general pain sound trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100_1.wav" ) ); if ( clientNum == cg.predictedPlayerState.clientNum ) { // smooth landing z changes cg.landChange = -16; cg.landTime = cg.time; } break; case EV_FALL_FAR: trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); cent->pe.painTime = cg.time; // don't play a pain sound right after this if ( clientNum == cg.predictedPlayerState.clientNum ) { // smooth landing z changes cg.landChange = -24; cg.landTime = cg.time; } break; case EV_FALLING: trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*falling1.wav" ) ); break; case EV_STEP_4: case EV_STEP_8: case EV_STEP_12: case EV_STEP_16: // smooth out step up transitions case EV_STEPDN_4: case EV_STEPDN_8: case EV_STEPDN_12: case EV_STEPDN_16: // smooth out step down transitions { float oldStep; int delta; int step; if ( clientNum != cg.predictedPlayerState.clientNum ) { break; } // if we are interpolating, we don't need to smooth steps if ( cg.demoPlayback || ( cg.snap->ps.pm_flags & PMF_FOLLOW ) || cg_nopredict.integer || cg_synchronousClients.integer ) { break; } // check for stepping up before a previous step is completed delta = cg.time - cg.stepTime; if ( delta < steptime ) { oldStep = cg.stepChange * ( steptime - delta ) / steptime; } else { oldStep = 0; } // add this amount if ( event >= EV_STEPDN_4 ) { step = 4 * ( event - EV_STEPDN_4 + 1 ); cg.stepChange = oldStep - step; } else { step = 4 * ( event - EV_STEP_4 + 1 ); cg.stepChange = oldStep + step; } if ( cg.stepChange > MAX_STEP_CHANGE ) { cg.stepChange = MAX_STEP_CHANGE; } else if ( cg.stepChange < -MAX_STEP_CHANGE ) { cg.stepChange = -MAX_STEP_CHANGE; } cg.stepTime = cg.time; break; } case EV_JUMP: trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); if ( BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_CLASS ], SCA_WALLJUMPER ) ) { vec3_t surfNormal, refNormal = { 0.0f, 0.0f, 1.0f }; vec3_t rotAxis; if ( clientNum != cg.predictedPlayerState.clientNum ) { break; } //set surfNormal VectorCopy( cg.predictedPlayerState.grapplePoint, surfNormal ); //if we are moving from one surface to another smooth the transition if ( !VectorCompare( surfNormal, cg.lastNormal ) && surfNormal[ 2 ] != 1.0f ) { CrossProduct( refNormal, surfNormal, rotAxis ); VectorNormalize( rotAxis ); //add the op CG_addSmoothOp( rotAxis, 15.0f, 1.0f ); } //copy the current normal to the lastNormal VectorCopy( surfNormal, cg.lastNormal ); } break; case EV_LEV1_GRAB: trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL1Grab ); break; case EV_LEV4_TRAMPLE_PREPARE: trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL4ChargePrepare ); break; case EV_LEV4_TRAMPLE_START: //FIXME: stop cgs.media.alienL4ChargePrepare playing here trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL4ChargeStart ); break; case EV_TAUNT: if ( !cg_noTaunt.integer ) { trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*taunt.wav" ) ); } break; case EV_WATER_TOUCH: trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrInSound ); break; case EV_WATER_LEAVE: trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound ); break; case EV_WATER_UNDER: trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound ); break; case EV_WATER_CLEAR: trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) ); break; case EV_JETPACK_ENABLE: // TODO: Trigger jetpack enable animation break; case EV_JETPACK_DISABLE: // TODO: Trigger jetpack disable animation break; case EV_JETPACK_START: // TODO: Start jetpack gfx/sfx break; case EV_JETPACK_STOP: // TODO: Stop jetpack gfx/sfx break; case EV_NOAMMO: trap_S_StartSound( NULL, es->number, CHAN_WEAPON, cgs.media.weaponEmptyClick ); break; case EV_CHANGE_WEAPON: trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.selectSound ); break; case EV_FIRE_WEAPON: CG_HandleFireWeapon( cent, WPM_PRIMARY ); break; case EV_FIRE_WEAPON2: CG_HandleFireWeapon( cent, WPM_SECONDARY ); break; case EV_FIRE_WEAPON3: CG_HandleFireWeapon( cent, WPM_TERTIARY ); break; case EV_WEAPON_RELOAD: if ( cg_weapons[ es->eventParm ].wim[ WPM_PRIMARY ].reloadSound ) { trap_S_StartSound( NULL, es->number, CHAN_WEAPON, cg_weapons[ es->eventParm ].wim[ WPM_PRIMARY ].reloadSound ); } break; case EV_PLAYER_TELEPORT_IN: //deprecated break; case EV_PLAYER_TELEPORT_OUT: CG_PlayerDisconnect( position ); break; case EV_BUILD_CONSTRUCT: break; case EV_BUILD_DESTROY: break; case EV_AMMO_REFILL: case EV_CLIPS_REFILL: case EV_FUEL_REFILL: // TODO: Add different sounds for EV_AMMO_REFILL, EV_CLIPS_REFILL, EV_FUEL_REFILL trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.repeaterUseSound ); break; case EV_GRENADE_BOUNCE: if ( rand() & 1 ) { trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.hardBounceSound1 ); } else { trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.hardBounceSound2 ); } break; case EV_WEAPON_HIT_ENTITY: CG_HandleWeaponHitEntity( es, position ); break; case EV_WEAPON_HIT_ENVIRONMENT: CG_HandleWeaponHitWall( es, position ); break; case EV_MISSILE_HIT_ENTITY: CG_HandleMissileHitEntity( es, position ); break; // currently there is no support for metal sounds case EV_MISSILE_HIT_ENVIRONMENT: case EV_MISSILE_HIT_METAL: CG_HandleMissileHitWall( es, position ); break; case EV_SHOTGUN: CG_HandleFireShotgun( es ); break; case EV_HUMAN_BUILDABLE_DYING: CG_HumanBuildableDying( (buildable_t) es->modelindex, position ); break; case EV_HUMAN_BUILDABLE_EXPLOSION: ByteToDir( es->eventParm, dir ); CG_HumanBuildableExplosion( (buildable_t) es->modelindex, position, dir ); break; case EV_ALIEN_BUILDABLE_EXPLOSION: ByteToDir( es->eventParm, dir ); CG_AlienBuildableExplosion( position, dir ); break; case EV_TESLATRAIL: cent->currentState.weapon = WP_TESLAGEN; { centity_t *source = &cg_entities[ es->generic1 ]; centity_t *target = &cg_entities[ es->clientNum ]; vec3_t sourceOffset = { 0.0f, 0.0f, 28.0f }; if ( !CG_IsTrailSystemValid( &source->muzzleTS ) ) { source->muzzleTS = CG_SpawnNewTrailSystem( cgs.media.teslaZapTS ); if ( CG_IsTrailSystemValid( &source->muzzleTS ) ) { CG_SetAttachmentCent( &source->muzzleTS->frontAttachment, source ); CG_SetAttachmentCent( &source->muzzleTS->backAttachment, target ); CG_AttachToCent( &source->muzzleTS->frontAttachment ); CG_AttachToCent( &source->muzzleTS->backAttachment ); CG_SetAttachmentOffset( &source->muzzleTS->frontAttachment, sourceOffset ); source->muzzleTSDeathTime = cg.time + cg_teslaTrailTime.integer; } } } break; case EV_GENERAL_SOUND: if ( cgs.gameSounds[ es->eventParm ] ) { trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] ); } else { s = CG_ConfigString( CS_SOUNDS + es->eventParm ); trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ) ); } break; case EV_GLOBAL_SOUND: // play from the player's head so it never diminishes if ( cgs.gameSounds[ es->eventParm ] ) { trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] ); } else { s = CG_ConfigString( CS_SOUNDS + es->eventParm ); trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, s ) ); } break; case EV_PAIN: // local player sounds are triggered in CG_CheckLocalSounds, // so ignore events on the player if ( cent->currentState.number != cg.snap->ps.clientNum ) { CG_PainEvent( cent, es->eventParm ); } break; case EV_DEATH1: case EV_DEATH2: case EV_DEATH3: trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, va( "*death%i.wav", event - EV_DEATH1 + 1 ) ) ); break; case EV_OBITUARY: CG_Obituary( es ); break; case EV_GIB_PLAYER: // no gibbing break; case EV_STOPLOOPINGSOUND: trap_S_StopLoopingSound( es->number ); es->loopSound = 0; break; case EV_DEBUG_LINE: CG_Beam( cent ); break; case EV_BUILD_DELAY: if ( clientNum == cg.predictedPlayerState.clientNum ) { trap_S_StartLocalSound( cgs.media.buildableRepairedSound, CHAN_LOCAL_SOUND ); cg.lastBuildAttempt = cg.time; } break; case EV_BUILD_REPAIR: trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.buildableRepairSound ); break; case EV_BUILD_REPAIRED: trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.buildableRepairedSound ); break; case EV_OVERMIND_ATTACK_1: case EV_OVERMIND_ATTACK_2: if ( cg.predictedPlayerState.persistant[ PERS_TEAM ] == TEAM_ALIENS ) { trap_S_StartLocalSound( cgs.media.alienOvermindAttack, CHAN_ANNOUNCER ); CG_CenterPrint( va( "^%c%s", "31"[ event - EV_OVERMIND_ATTACK_1 ], _( "The Overmind is under attack!" ) ), 200, GIANTCHAR_WIDTH * 4 ); } break; case EV_OVERMIND_DYING: if ( cg.predictedPlayerState.persistant[ PERS_TEAM ] == TEAM_ALIENS ) { trap_S_StartLocalSound( cgs.media.alienOvermindDying, CHAN_ANNOUNCER ); CG_CenterPrint( _( "^1The Overmind is dying!" ), 200, GIANTCHAR_WIDTH * 4 ); } break; case EV_REACTOR_ATTACK_1: case EV_REACTOR_ATTACK_2: if ( cg.predictedPlayerState.persistant[ PERS_TEAM ] == TEAM_HUMANS ) { CG_CenterPrint( va( "^%c%s", "31"[ event - EV_REACTOR_ATTACK_1 ], _( "The reactor is under attack!" ) ), 200, GIANTCHAR_WIDTH * 4 ); } break; case EV_REACTOR_DYING: if ( cg.predictedPlayerState.persistant[ PERS_TEAM ] == TEAM_HUMANS ) { CG_CenterPrint( _( "^1The reactor is about to explode!" ), 200, GIANTCHAR_WIDTH * 4 ); } break; case EV_WARN_ATTACK: // if eventParm is non-zero, this is for humans and there's a nearby reactor or repeater, otherwise it's for aliens if ( es->eventParm >= MAX_CLIENTS && es->eventParm < MAX_GENTITIES ) { const char *location; qboolean base = cg_entities[ es->eventParm ].currentState.modelindex == BA_H_REACTOR; centity_t *locent = CG_GetLocation( cg_entities[ es->eventParm ].currentState.origin ); CG_CenterPrint( base ? _( "Our base is under attack!" ) : _( "A forward base is under attack!" ), 200, GIANTCHAR_WIDTH * 4 ); if ( locent ) { location = CG_ConfigString( CS_LOCATIONS + locent->currentState.generic1 ); } else { location = CG_ConfigString( CS_LOCATIONS ); } if ( location && *location ) { Com_Printf( _( "%s Under attack – %s\n" ), base ? "[reactor]" : "[repeater]", location ); } else { Com_Printf( _( "%s Under attack\n" ), base ? "[reactor]" : "[repeater]" ); } } else // this is for aliens, and the overmind is in range { CG_CenterPrint( _( "Our base is under attack!" ), 200, GIANTCHAR_WIDTH * 4 ); } break; case EV_MGTURRET_SPINUP: trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.turretSpinupSound ); break; case EV_OVERMIND_SPAWNS: if ( cg.predictedPlayerState.persistant[ PERS_TEAM ] == TEAM_ALIENS ) { trap_S_StartLocalSound( cgs.media.alienOvermindSpawns, CHAN_ANNOUNCER ); CG_CenterPrint( "The Overmind needs spawns!", 200, GIANTCHAR_WIDTH * 4 ); } break; case EV_ALIEN_EVOLVE: trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.alienEvolveSound ); { particleSystem_t *ps = CG_SpawnNewParticleSystem( cgs.media.alienEvolvePS ); if ( CG_IsParticleSystemValid( &ps ) ) { CG_SetAttachmentCent( &ps->attachment, cent ); CG_AttachToCent( &ps->attachment ); } } if ( es->number == cg.clientNum ) { CG_ResetPainBlend(); cg.spawnTime = cg.time; } break; case EV_ALIEN_EVOLVE_FAILED: if ( clientNum == cg.predictedPlayerState.clientNum ) { //FIXME: change to "negative" sound trap_S_StartLocalSound( cgs.media.buildableRepairedSound, CHAN_LOCAL_SOUND ); cg.lastEvolveAttempt = cg.time; } break; case EV_ALIEN_ACIDTUBE: { particleSystem_t *ps = CG_SpawnNewParticleSystem( cgs.media.alienAcidTubePS ); if ( CG_IsParticleSystemValid( &ps ) ) { CG_SetAttachmentCent( &ps->attachment, cent ); ByteToDir( es->eventParm, dir ); CG_SetParticleSystemNormal( ps, dir ); CG_AttachToCent( &ps->attachment ); } } break; case EV_MEDKIT_USED: trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.medkitUseSound ); break; case EV_PLAYER_RESPAWN: if ( es->number == cg.clientNum ) { cg.spawnTime = cg.time; } break; case EV_LEV2_ZAP: CG_Level2Zap( es ); break; case EV_HIT: cg.hitTime = cg.time; break; case EV_MOMENTUM: CG_Momentum( es ); break; default: CG_Error( "Unknown event: %i", event ); } }
/* =========== ClientUserInfoChanged Called from ClientConnect when the player first connects and directly by the server system when the player updates a userinfo variable. The game can override any of the settings and call trap_SetUserinfo if desired. ============ */ char *ClientUserinfoChanged( int clientNum, qboolean forceName ) { gentity_t *ent; char *s; char model[ MAX_QPATH ]; char buffer[ MAX_QPATH ]; char filename[ MAX_QPATH ]; char oldname[ MAX_NAME_LENGTH ]; char newname[ MAX_NAME_LENGTH ]; char err[ MAX_STRING_CHARS ]; qboolean revertName = qfalse; gclient_t *client; char userinfo[ MAX_INFO_STRING ]; ent = g_entities + clientNum; client = ent->client; trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); // check for malformed or illegal info strings if( !Info_Validate(userinfo) ) { trap_SendServerCommand( ent - g_entities, "disconnect \"illegal or malformed userinfo\n\"" ); trap_DropClient( ent - g_entities, "dropped: illegal or malformed userinfo"); return "Illegal or malformed userinfo"; } // If their userinfo overflowed, tremded is in the process of disconnecting them. // If we send our own disconnect, it won't work, so just return to prevent crashes later // in this function. This check must come after the Info_Validate call. else if( !userinfo[ 0 ] ) return "Empty (overflowed) userinfo"; // stickyspec toggle s = Info_ValueForKey( userinfo, "cg_stickySpec" ); client->pers.stickySpec = atoi( s ) != 0; // set name Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) ); s = Info_ValueForKey( userinfo, "name" ); G_ClientCleanName( s, newname, sizeof( newname ) ); if( strcmp( oldname, newname ) ) { if( !forceName && client->pers.namelog->nameChangeTime && level.time - client->pers.namelog->nameChangeTime <= g_minNameChangePeriod.value * 1000 ) { trap_SendServerCommand( ent - g_entities, va( "print \"Name change spam protection (g_minNameChangePeriod = %d)\n\"", g_minNameChangePeriod.integer ) ); revertName = qtrue; } else if( !forceName && g_maxNameChanges.integer > 0 && client->pers.namelog->nameChanges >= g_maxNameChanges.integer ) { trap_SendServerCommand( ent - g_entities, va( "print \"Maximum name changes reached (g_maxNameChanges = %d)\n\"", g_maxNameChanges.integer ) ); revertName = qtrue; } else if( !forceName && client->pers.namelog->muted ) { trap_SendServerCommand( ent - g_entities, "print \"You cannot change your name while you are muted\n\"" ); revertName = qtrue; } else if( !G_admin_name_check( ent, newname, err, sizeof( err ) ) ) { trap_SendServerCommand( ent - g_entities, va( "print \"%s\n\"", err ) ); revertName = qtrue; } if( revertName ) { Q_strncpyz( client->pers.netname, *oldname ? oldname : "UnnamedPlayer", sizeof( client->pers.netname ) ); Info_SetValueForKey( userinfo, "name", oldname ); trap_SetUserinfo( clientNum, userinfo ); } else { G_CensorString( client->pers.netname, newname, sizeof( client->pers.netname ), ent ); if( !forceName && client->pers.connected == CON_CONNECTED ) { client->pers.namelog->nameChangeTime = level.time; client->pers.namelog->nameChanges++; } if( *oldname ) { G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\" \"%c%s%c^7\"\n", clientNum, client->pers.ip.str, client->pers.guid, oldname, client->pers.netname, DECOLOR_OFF, client->pers.netname, DECOLOR_ON ); } } G_namelog_update_name( client ); } if( client->pers.classSelection == PCL_NONE ) { //This looks hacky and frankly it is. The clientInfo string needs to hold different //model details to that of the spawning class or the info change will not be //registered and an axis appears instead of the player model. There is zero chance //the player can spawn with the battlesuit, hence this choice. Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_ClassConfig( PCL_HUMAN_BSUIT )->modelName, BG_ClassConfig( PCL_HUMAN_BSUIT )->skinName ); } else { Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_ClassConfig( client->pers.classSelection )->modelName, BG_ClassConfig( client->pers.classSelection )->skinName ); //model segmentation Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", BG_ClassConfig( client->pers.classSelection )->modelName ); if( G_NonSegModel( filename ) ) client->ps.persistant[ PERS_STATE ] |= PS_NONSEGMODEL; else client->ps.persistant[ PERS_STATE ] &= ~PS_NONSEGMODEL; } Q_strncpyz( model, buffer, sizeof( model ) ); // wallwalk follow s = Info_ValueForKey( userinfo, "cg_wwFollow" ); if( atoi( s ) ) client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGFOLLOW; else client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGFOLLOW; // wallwalk toggle s = Info_ValueForKey( userinfo, "cg_wwToggle" ); if( atoi( s ) ) client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGTOGGLE; else client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGTOGGLE; // always sprint s = Info_ValueForKey( userinfo, "cg_sprintToggle" ); if( atoi( s ) ) client->ps.persistant[ PERS_STATE ] |= PS_SPRINTTOGGLE; else client->ps.persistant[ PERS_STATE ] &= ~PS_SPRINTTOGGLE; // fly speed s = Info_ValueForKey( userinfo, "cg_flySpeed" ); if( *s ) client->pers.flySpeed = atoi( s ); else client->pers.flySpeed = BG_Class( PCL_NONE )->speed; // disable blueprint errors s = Info_ValueForKey( userinfo, "cg_disableBlueprintErrors" ); if( atoi( s ) ) client->pers.disableBlueprintErrors = qtrue; else client->pers.disableBlueprintErrors = qfalse; // teamInfo s = Info_ValueForKey( userinfo, "teamoverlay" ); if( atoi( s ) != 0 ) { // teamoverlay was enabled so we need an update if( client->pers.teamInfo == 0 ) client->pers.teamInfo = 1; } else client->pers.teamInfo = 0; s = Info_ValueForKey( userinfo, "cg_unlagged" ); if( !s[0] || atoi( s ) != 0 ) client->pers.useUnlagged = qtrue; else client->pers.useUnlagged = qfalse; Q_strncpyz( client->pers.voice, Info_ValueForKey( userinfo, "voice" ), sizeof( client->pers.voice ) ); // send over a subset of the userinfo keys so other clients can // print scoreboards, display models, and play custom sounds Com_sprintf( userinfo, sizeof( userinfo ), "n\\%s\\t\\%i\\model\\%s\\ig\\%16s\\v\\%s", client->pers.netname, client->pers.teamSelection, model, Com_ClientListString( &client->sess.ignoreList ), client->pers.voice ); trap_SetConfigstring( CS_PLAYERS + clientNum, userinfo ); /*G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, userinfo );*/ return NULL; }
/* =========== ClientSpawn Called every time a client is placed fresh in the world: after the first ClientBegin, and after each respawn Initializes all non-persistant parts of playerState ============ */ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ) { int index; vec3_t spawn_origin, spawn_angles; gclient_t *client; int i; clientPersistant_t saved; clientSession_t savedSess; int persistant[ MAX_PERSISTANT ]; gentity_t *spawnPoint = NULL; int flags; int savedPing; int teamLocal; int eventSequence; char userinfo[ MAX_INFO_STRING ]; vec3_t up = { 0.0f, 0.0f, 1.0f }; int maxAmmo, maxClips; weapon_t weapon; index = ent - g_entities; client = ent->client; teamLocal = client->pers.teamSelection; //if client is dead and following teammate, stop following before spawning if( client->sess.spectatorClient != -1 ) { client->sess.spectatorClient = -1; client->sess.spectatorState = SPECTATOR_FREE; } // only start client if chosen a class and joined a team if( client->pers.classSelection == PCL_NONE && teamLocal == TEAM_NONE ) client->sess.spectatorState = SPECTATOR_FREE; else if( client->pers.classSelection == PCL_NONE ) client->sess.spectatorState = SPECTATOR_LOCKED; // if client is dead and following teammate, stop following before spawning if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) G_StopFollowing( ent ); if( origin != NULL ) VectorCopy( origin, spawn_origin ); if( angles != NULL ) VectorCopy( angles, spawn_angles ); // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client if( client->sess.spectatorState != SPECTATOR_NOT ) { if( teamLocal == TEAM_NONE ) spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); else if( teamLocal == TEAM_ALIENS ) spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); else if( teamLocal == TEAM_HUMANS ) spawnPoint = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); } else { if( spawn == NULL ) { G_Error( "ClientSpawn: spawn is NULL\n" ); return; } spawnPoint = spawn; if( ent != spawn ) { //start spawn animation on spawnPoint G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, qtrue ); if( spawnPoint->buildableTeam == TEAM_ALIENS ) spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; else if( spawnPoint->buildableTeam == TEAM_HUMANS ) spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; } } // toggle the teleport bit so the client knows to not lerp flags = ( ent->client->ps.eFlags & EF_TELEPORT_BIT ) ^ EF_TELEPORT_BIT; G_UnlaggedClear( ent ); // clear everything but the persistant data saved = client->pers; savedSess = client->sess; savedPing = client->ps.ping; for( i = 0; i < MAX_PERSISTANT; i++ ) persistant[ i ] = client->ps.persistant[ i ]; eventSequence = client->ps.eventSequence; memset( client, 0, sizeof( *client ) ); client->pers = saved; client->sess = savedSess; client->ps.ping = savedPing; client->lastkilled_client = -1; for( i = 0; i < MAX_PERSISTANT; i++ ) client->ps.persistant[ i ] = persistant[ i ]; client->ps.eventSequence = eventSequence; // increment the spawncount so the client will detect the respawn client->ps.persistant[ PERS_SPAWN_COUNT ]++; client->ps.persistant[ PERS_SPECSTATE ] = client->sess.spectatorState; client->airOutTime = level.time + 12000; trap_GetUserinfo( index, userinfo, sizeof( userinfo ) ); client->ps.eFlags = flags; //Com_Printf( "ent->client->pers->pclass = %i\n", ent->client->pers.classSelection ); ent->s.groundEntityNum = ENTITYNUM_NONE; ent->client = &level.clients[ index ]; ent->takedamage = qtrue; ent->inuse = qtrue; ent->classname = "player"; ent->r.contents = CONTENTS_BODY; ent->clipmask = MASK_PLAYERSOLID; ent->die = player_die; ent->waterlevel = 0; ent->watertype = 0; ent->flags = 0; // calculate each client's acceleration ent->evaluateAcceleration = qtrue; client->ps.stats[ STAT_MISC ] = 0; client->ps.eFlags = flags; client->ps.clientNum = index; BG_ClassBoundingBox( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL ); if( client->sess.spectatorState == SPECTATOR_NOT ) client->ps.stats[ STAT_MAX_HEALTH ] = BG_Class( ent->client->pers.classSelection )->health; else client->ps.stats[ STAT_MAX_HEALTH ] = 100; // clear entity values if( ent->client->pers.classSelection == PCL_HUMAN ) { BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats ); weapon = client->pers.humanItemSelection; } else if( client->sess.spectatorState == SPECTATOR_NOT ) weapon = BG_Class( ent->client->pers.classSelection )->startWeapon; else weapon = WP_NONE; maxAmmo = BG_Weapon( weapon )->maxAmmo; maxClips = BG_Weapon( weapon )->maxClips; client->ps.stats[ STAT_WEAPON ] = weapon; client->ps.ammo = maxAmmo; client->ps.clips = maxClips; // We just spawned, not changing weapons client->ps.persistant[ PERS_NEWWEAPON ] = 0; ent->client->ps.stats[ STAT_CLASS ] = ent->client->pers.classSelection; ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; ent->client->ps.stats[ STAT_STATE ] = 0; VectorSet( ent->client->ps.grapplePoint, 0.0f, 0.0f, 1.0f ); // health will count down towards max_health ent->health = client->ps.stats[ STAT_HEALTH ] = client->ps.stats[ STAT_MAX_HEALTH ]; //* 1.25; //if evolving scale health if( ent == spawn ) { ent->health *= ent->client->pers.evolveHealthFraction; client->ps.stats[ STAT_HEALTH ] *= ent->client->pers.evolveHealthFraction; } //clear the credits array for( i = 0; i < MAX_CLIENTS; i++ ) ent->credits[ i ] = 0; client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); #define UP_VEL 150.0f #define F_VEL 50.0f //give aliens some spawn velocity if( client->sess.spectatorState == SPECTATOR_NOT && client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) { if( ent == spawn ) { //evolution particle system G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) ); } else { spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); if( spawnPoint->s.origin2[ 2 ] > 0.0f ) { vec3_t forward, dir; AngleVectors( spawn_angles, forward, NULL, NULL ); VectorScale( forward, F_VEL, forward ); VectorAdd( spawnPoint->s.origin2, forward, dir ); VectorNormalize( dir ); VectorScale( dir, UP_VEL, client->ps.velocity ); } G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); } } else if( client->sess.spectatorState == SPECTATOR_NOT && client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); } // the respawned flag will be cleared after the attack and jump keys come up client->ps.pm_flags |= PMF_RESPAWNED; trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); G_SetClientViewAngle( ent, spawn_angles ); if( client->sess.spectatorState == SPECTATOR_NOT ) { trap_LinkEntity( ent ); // force the base weapon up if( client->pers.teamSelection == TEAM_HUMANS ) G_ForceWeaponChange( ent, weapon ); client->ps.weaponstate = WEAPON_READY; } // don't allow full run speed for a bit client->ps.pm_flags |= PMF_TIME_KNOCKBACK; client->ps.pm_time = 100; client->respawnTime = level.time; ent->nextRegenTime = level.time; client->inactivityTime = level.time + g_inactivity.integer * 1000; client->latched_buttons = 0; // set default animations client->ps.torsoAnim = TORSO_STAND; client->ps.legsAnim = LEGS_IDLE; if( level.intermissiontime ) MoveClientToIntermission( ent ); else { // fire the targets of the spawn point if( !spawn ) G_UseTargets( spawnPoint, ent ); client->ps.weapon = client->ps.stats[ STAT_WEAPON ]; } // run a client frame to drop exactly to the floor, // initialize animations and other things client->ps.commandTime = level.time - 100; ent->client->pers.cmd.serverTime = level.time; ClientThink( ent-g_entities ); // positively link the client, even if the command times are weird if( client->sess.spectatorState == SPECTATOR_NOT ) { BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); } // must do this here so the number of active clients is calculated CalculateRanks( ); // run the presend to set anything else ClientEndFrame( ent ); // clear entity state values BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); client->pers.infoChangeTime = level.time; }
void G_KnockbackByDir( gentity_t *target, const vec3_t direction, float strength, qboolean ignoreMass ) { vec3_t dir, vel; int mass; float massMod; const classAttributes_t *ca; // sanity check parameters if ( !target || !target->client || VectorLength( direction ) == 0.0f || strength == 0 ) { return; } // check target flags if ( target->flags & FL_NO_KNOCKBACK ) { return; } ca = BG_Class( target->client->ps.stats[ STAT_CLASS ] ); // normalize direction VectorCopy( direction, dir ); VectorNormalize( dir ); // adjust strength according to client mass if ( !ignoreMass ) { if ( ca->mass <= 0 ) { mass = KNOCKBACK_NORMAL_MASS; } else { mass = ca->mass; } massMod = ( float )KNOCKBACK_NORMAL_MASS / ( float )mass; if ( massMod < 0.5f ) { massMod = 0.5f; } else if ( massMod > 2.0f ) { massMod = 2.0f; } } else { // for debug print massMod = 1.0f; } strength *= massMod; // adjust client velocity VectorScale( dir, strength, vel ); VectorAdd( target->client->ps.velocity, vel, target->client->ps.velocity ); // set pmove timer so that the client can't cancel out the movement immediately if ( !target->client->ps.pm_time ) { target->client->ps.pm_time = KNOCKBACK_PMOVE_TIME; target->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; } // print debug info if ( g_debugKnockback.integer ) { G_Printf( "%i: Knockback: client: %i, strength: %.1f (massMod: %.1f)\n", level.time, target->s.number, strength, massMod ); } }
float G_GetPointDamageMod( gentity_t *target, class_t pcl, float angle, float height ) { int regionNum; damageRegion_t *region; qboolean crouching; if ( !target || !target->client ) { return 1.0f; } crouching = ( target->client->ps.pm_flags & PMF_DUCKED ); for ( regionNum = 0; regionNum < g_numDamageRegions[ pcl ]; regionNum++ ) { region = &g_damageRegions[ pcl ][ regionNum ]; // ignore nonlocational if ( region->nonlocational ) { continue; } // crouch state must match if ( region->crouch != crouching ) { continue; } // height must be within range if ( height < region->minHeight || height > region->maxHeight ) { continue; } // angle must be within range if ( ( region->minAngle <= region->maxAngle && ( angle < region->minAngle || angle > region->maxAngle ) ) || ( region->minAngle > region->maxAngle && ( angle > region->maxAngle && angle < region->minAngle ) ) ) { continue; } if ( g_debugDamage.integer > 1 ) { G_Printf( "GetPointDamageModifier( pcl = %s, angle = %.2f, height = %.2f ): " S_COLOR_GREEN "FOUND:" S_COLOR_WHITE " %.2f (%s)\n", BG_Class( pcl )->name, angle, height, region->modifier, region->name ); } return region->modifier; } if ( g_debugDamage.integer > 1 ) { G_Printf( "GetPointDamageModifier( pcl = %s, angle = %.2f, height = %.2f ): " S_COLOR_YELLOW "NOT FOUND:" S_COLOR_WHITE " %.2f\n", BG_Class( pcl )->name, angle, height, 1.0f ); } return 1.0f; }
void G_UpdateUnlockables() { int itemNum = 0, unlockableNum, unlockThreshold; float momentum; unlockable_t *unlockable; int unlockableType = 0; team_t team; for ( unlockableNum = 0; unlockableNum < NUM_UNLOCKABLES; unlockableNum++ ) { unlockable = &unlockables[ unlockableNum ]; // also iterate over item types, itemNum is a per-type counter while ( unlockableType < UNLT_NUM_UNLOCKABLETYPES - 1 && unlockableNum == unlockablesTypeOffset[ unlockableType + 1 ] ) { unlockableType++; itemNum = 0; } switch ( unlockableType ) { case UNLT_WEAPON: team = BG_Weapon( itemNum )->team; unlockThreshold = BG_Weapon( itemNum )->unlockThreshold; break; case UNLT_UPGRADE: team = TEAM_HUMANS; unlockThreshold = BG_Upgrade( itemNum )->unlockThreshold; break; case UNLT_BUILDABLE: team = BG_Buildable( itemNum )->team; unlockThreshold = BG_Buildable( itemNum )->unlockThreshold; break; case UNLT_CLASS: team = TEAM_ALIENS; unlockThreshold = BG_Class( itemNum )->unlockThreshold; break; default: Com_Error( ERR_FATAL, "G_UpdateUnlockables: Unknown unlockable type" ); } unlockThreshold = MAX( unlockThreshold, 0 ); momentum = level.team[ team ].momentum; unlockable->type = unlockableType; unlockable->num = itemNum; unlockable->team = team; unlockable->statusKnown = true; unlockable->unlockThreshold = unlockThreshold; unlockable->lockThreshold = UnlockToLockThreshold( unlockThreshold ); // calculate the item's locking state unlockable->unlocked = ( !unlockThreshold || momentum >= unlockThreshold || ( unlockable->unlocked && momentum >= unlockable->lockThreshold ) ); itemNum++; /*Com_Printf( "G_UpdateUnlockables: Team %s, Type %s, Item %s, Momentum %d, Threshold %d, " "Unlocked %d, Synchronize %d\n", BG_TeamName( team ), UnlockableTypeName( unlockable ), UnlockableName( unlockable ), momentum, unlockThreshold, unlockable->unlocked, unlockable->synchronize );*/ } // GAME knows about all teams unlockablesDataAvailable = true; unlockablesTeamKnowledge = TEAM_ALL; // generate masks for network transmission UpdateUnlockablesMask(); }
static int CG_CalcFov( void ) { float y; float phase; float v; int contents; float fov_x, fov_y; float zoomFov; float f; int inwater; int attribFov; usercmd_t cmd; usercmd_t oldcmd; int cmdNum; cmdNum = trap_GetCurrentCmdNumber(); trap_GetUserCmd( cmdNum, &cmd ); trap_GetUserCmd( cmdNum - 1, &oldcmd ); // switch follow modes if necessary: cycle between free -> follow -> third-person follow if ( usercmdButtonPressed( cmd.buttons, BUTTON_USE_HOLDABLE ) && !usercmdButtonPressed( oldcmd.buttons, BUTTON_USE_HOLDABLE ) ) { if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { if ( !cg.chaseFollow ) { cg.chaseFollow = qtrue; } else { cg.chaseFollow = qfalse; trap_SendClientCommand( "follow\n" ); } } else if ( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) { trap_SendClientCommand( "follow\n" ); } } if ( cg.predictedPlayerState.pm_type == PM_INTERMISSION || ( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) || ( cg.renderingThirdPerson ) ) { // if in intermission or third person, use a fixed value fov_y = BASE_FOV_Y; } else { // don't lock the fov globally - we need to be able to change it if ( ( attribFov = trap_Cvar_VariableIntegerValue( BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->fovCvar ) ) ) { if ( attribFov < 80 ) { attribFov = 80; } else if ( attribFov >= 140 ) { attribFov = 140; } } else { attribFov = BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->fov; } attribFov *= 0.75; fov_y = attribFov; if ( fov_y < 1.0f ) { fov_y = 1.0f; } else if ( fov_y > MAX_FOV_Y ) { fov_y = MAX_FOV_Y; } if ( cg.spawnTime > ( cg.time - FOVWARPTIME ) && BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_CLASS ], SCA_FOVWARPS ) ) { float fraction = ( float )( cg.time - cg.spawnTime ) / FOVWARPTIME; fov_y = MAX_FOV_WARP_Y - ( ( MAX_FOV_WARP_Y - fov_y ) * fraction ); } // account for zooms zoomFov = BG_Weapon( cg.predictedPlayerState.weapon )->zoomFov * 0.75f; if ( zoomFov < 1.0f ) { zoomFov = 1.0f; } else if ( zoomFov > attribFov ) { zoomFov = attribFov; } // only do all the zoom stuff if the client CAN zoom // FIXME: zoom control is currently hard coded to WBUTTON_ATTACK2 if ( BG_Weapon( cg.predictedPlayerState.weapon )->canZoom ) { if ( cg.zoomed ) { f = ( cg.time - cg.zoomTime ) / ( float ) ZOOM_TIME; if ( f > 1.0f ) { fov_y = zoomFov; } else { fov_y = fov_y + f * ( zoomFov - fov_y ); } // WBUTTON_ATTACK2 isn't held so unzoom next time if ( !usercmdButtonPressed( cmd.buttons, BUTTON_ATTACK2 ) || cg.snap->ps.weaponstate == WEAPON_RELOADING ) { cg.zoomed = qfalse; cg.zoomTime = MIN( cg.time, cg.time + cg.time - cg.zoomTime - ZOOM_TIME ); } } else { f = ( cg.time - cg.zoomTime ) / ( float ) ZOOM_TIME; if ( f < 1.0f ) { fov_y = zoomFov + f * ( fov_y - zoomFov ); } // WBUTTON_ATTACK2 is held so zoom next time if ( usercmdButtonPressed( cmd.buttons, BUTTON_ATTACK2 ) && cg.snap->ps.weaponstate != WEAPON_RELOADING ) { cg.zoomed = qtrue; cg.zoomTime = MIN( cg.time, cg.time + cg.time - cg.zoomTime - ZOOM_TIME ); } } } else if ( cg.zoomed ) { cg.zoomed = qfalse; } } y = cg.refdef.height / tan( 0.5f * DEG2RAD( fov_y ) ); fov_x = atan2( cg.refdef.width, y ); fov_x = 2.0f * RAD2DEG( fov_x ); // warp if underwater contents = CG_PointContents( cg.refdef.vieworg, -1 ); if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { phase = cg.time / 1000.0f * WAVE_FREQUENCY * M_PI * 2.0f; v = WAVE_AMPLITUDE * sin( phase ); fov_x += v; fov_y -= v; inwater = qtrue; } else { inwater = qfalse; } // set it cg.refdef.fov_x = fov_x; cg.refdef.fov_y = fov_y; if ( !cg.zoomed ) { cg.zoomSensitivity = 1.0f; } else { cg.zoomSensitivity = cg.refdef.fov_y / 75.0f; } return inwater; }
/* =========== ClientSpawn Called every time a client is placed fresh in the world: after the first ClientBegin, and after each respawn and evolve Initializes all non-persistent parts of playerState ============ */ void ClientSpawn( gentity_t *ent, gentity_t *spawn, const vec3_t origin, const vec3_t angles ) { int index; vec3_t spawn_origin, spawn_angles; gclient_t *client; int i; clientPersistant_t saved; clientSession_t savedSess; bool savedNoclip, savedCliprcontents; int persistant[ MAX_PERSISTANT ]; gentity_t *spawnPoint = nullptr; int flags; int savedPing; int teamLocal; int eventSequence; char userinfo[ MAX_INFO_STRING ]; vec3_t up = { 0.0f, 0.0f, 1.0f }; int maxAmmo, maxClips; weapon_t weapon; ClientSpawnCBSE(ent, ent == spawn); index = ent - g_entities; client = ent->client; teamLocal = client->pers.team; //if client is dead and following teammate, stop following before spawning if ( client->sess.spectatorClient != -1 ) { client->sess.spectatorClient = -1; client->sess.spectatorState = SPECTATOR_FREE; } // only start client if chosen a class and joined a team if ( client->pers.classSelection == PCL_NONE && teamLocal == TEAM_NONE ) { client->sess.spectatorState = SPECTATOR_FREE; } else if ( client->pers.classSelection == PCL_NONE ) { client->sess.spectatorState = SPECTATOR_LOCKED; } // if client is dead and following teammate, stop following before spawning if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { G_StopFollowing( ent ); } if ( origin != nullptr ) { VectorCopy( origin, spawn_origin ); } if ( angles != nullptr ) { VectorCopy( angles, spawn_angles ); } // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client if ( client->sess.spectatorState != SPECTATOR_NOT ) { if ( teamLocal == TEAM_NONE ) { spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); } else if ( teamLocal == TEAM_ALIENS ) { spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); } else if ( teamLocal == TEAM_HUMANS ) { spawnPoint = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); } } else { if ( spawn == nullptr ) { Com_Error(errorParm_t::ERR_DROP, "ClientSpawn: spawn is NULL" ); } spawnPoint = spawn; if ( spawnPoint->s.eType == entityType_t::ET_BUILDABLE ) { G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, true ); if ( spawnPoint->buildableTeam == TEAM_ALIENS ) { spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; } else if ( spawnPoint->buildableTeam == TEAM_HUMANS ) { spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; } } } // toggle the teleport bit so the client knows to not lerp flags = ( ent->client->ps.eFlags & EF_TELEPORT_BIT ) ^ EF_TELEPORT_BIT; G_UnlaggedClear( ent ); // clear everything but the persistent data saved = client->pers; savedSess = client->sess; savedPing = client->ps.ping; savedNoclip = client->noclip; savedCliprcontents = client->cliprcontents; for ( i = 0; i < MAX_PERSISTANT; i++ ) { persistant[ i ] = client->ps.persistant[ i ]; } eventSequence = client->ps.eventSequence; memset( client, 0, sizeof( *client ) ); client->pers = saved; client->sess = savedSess; client->ps.ping = savedPing; client->noclip = savedNoclip; client->cliprcontents = savedCliprcontents; for ( i = 0; i < MAX_PERSISTANT; i++ ) { client->ps.persistant[ i ] = persistant[ i ]; } client->ps.eventSequence = eventSequence; // increment the spawncount so the client will detect the respawn client->ps.persistant[ PERS_SPAWN_COUNT ]++; client->ps.persistant[ PERS_SPECSTATE ] = client->sess.spectatorState; client->airOutTime = level.time + 12000; trap_GetUserinfo( index, userinfo, sizeof( userinfo ) ); client->ps.eFlags = flags; //Log::Notice( "ent->client->pers->pclass = %i\n", ent->client->pers.classSelection ); ent->s.groundEntityNum = ENTITYNUM_NONE; ent->client = &level.clients[ index ]; ent->classname = S_PLAYER_CLASSNAME; if ( client->noclip ) { client->cliprcontents = CONTENTS_BODY; } else { ent->r.contents = CONTENTS_BODY; } ent->clipmask = MASK_PLAYERSOLID; ent->die = G_PlayerDie; ent->waterlevel = 0; ent->watertype = 0; ent->flags &= FL_GODMODE | FL_NOTARGET; // calculate each client's acceleration ent->evaluateAcceleration = true; client->ps.stats[ STAT_MISC ] = 0; client->ps.eFlags = flags; client->ps.clientNum = index; BG_ClassBoundingBox( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, nullptr, nullptr, nullptr ); // clear entity values if ( ent->client->pers.classSelection == PCL_HUMAN_NAKED ) { BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats ); weapon = client->pers.humanItemSelection; } else if ( client->sess.spectatorState == SPECTATOR_NOT ) { weapon = BG_Class( ent->client->pers.classSelection )->startWeapon; } else { weapon = WP_NONE; } maxAmmo = BG_Weapon( weapon )->maxAmmo; maxClips = BG_Weapon( weapon )->maxClips; client->ps.stats[ STAT_WEAPON ] = weapon; client->ps.ammo = maxAmmo; client->ps.clips = maxClips; // We just spawned, not changing weapons client->ps.persistant[ PERS_NEWWEAPON ] = 0; client->ps.persistant[ PERS_TEAM ] = client->pers.team; // TODO: Check whether stats can be cleared at once instead of per field client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; client->ps.stats[ STAT_FUEL ] = JETPACK_FUEL_MAX; client->ps.stats[ STAT_CLASS ] = ent->client->pers.classSelection; client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; client->ps.stats[ STAT_PREDICTION ] = 0; client->ps.stats[ STAT_STATE ] = 0; VectorSet( client->ps.grapplePoint, 0.0f, 0.0f, 1.0f ); //clear the credits array // TODO: Handle in HealthComponent or ClientComponent. for ( i = 0; i < MAX_CLIENTS; i++ ) { ent->credits[ i ].value = 0.0f; ent->credits[ i ].time = 0; ent->credits[ i ].team = TEAM_NONE; } G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); //give aliens some spawn velocity if ( client->sess.spectatorState == SPECTATOR_NOT && client->pers.team == TEAM_ALIENS ) { if ( ent == spawn ) { //evolution particle system G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) ); } else { spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); if ( spawnPoint->s.origin2[ 2 ] > 0.0f ) { vec3_t forward, dir; AngleVectors( spawn_angles, forward, nullptr, nullptr ); VectorAdd( spawnPoint->s.origin2, forward, dir ); VectorNormalize( dir ); VectorScale( dir, BG_Class( ent->client->pers.classSelection )->jumpMagnitude, client->ps.velocity ); } G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); } } else if ( client->sess.spectatorState == SPECTATOR_NOT && client->pers.team == TEAM_HUMANS ) { spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); } // the respawned flag will be cleared after the attack and jump keys come up client->ps.pm_flags |= PMF_RESPAWNED; trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); G_SetClientViewAngle( ent, spawn_angles ); if ( client->sess.spectatorState == SPECTATOR_NOT ) { trap_LinkEntity( ent ); // force the base weapon up if ( client->pers.team == TEAM_HUMANS ) { G_ForceWeaponChange( ent, weapon ); } client->ps.weaponstate = WEAPON_READY; } // don't allow full run speed for a bit client->ps.pm_flags |= PMF_TIME_KNOCKBACK; client->ps.pm_time = 100; client->respawnTime = level.time; ent->nextRegenTime = level.time; client->inactivityTime = level.time + g_inactivity.integer * 1000; usercmdClearButtons( client->latched_buttons ); // set default animations client->ps.torsoAnim = TORSO_STAND; client->ps.legsAnim = LEGS_IDLE; if ( level.intermissiontime ) { MoveClientToIntermission( ent ); } else { // fire the targets of the spawn point if ( !spawn && spawnPoint ) { G_EventFireEntity( spawnPoint, ent, ON_SPAWN ); } // select the highest weapon number available, after any // spawn given items have fired client->ps.weapon = 1; for ( i = WP_NUM_WEAPONS - 1; i > 0; i-- ) { if ( BG_InventoryContainsWeapon( i, client->ps.stats ) ) { client->ps.weapon = i; break; } } } // run a client frame to drop exactly to the floor, // initialize animations and other things client->ps.commandTime = level.time - 100; ent->client->pers.cmd.serverTime = level.time; ClientThink( ent - g_entities ); // positively link the client, even if the command times are weird if ( client->sess.spectatorState == SPECTATOR_NOT ) { BG_PlayerStateToEntityState( &client->ps, &ent->s, true ); VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); } // must do this here so the number of active clients is calculated CalculateRanks(); // run the presend to set anything else ClientEndFrame( ent ); // clear entity state values BG_PlayerStateToEntityState( &client->ps, &ent->s, true ); client->pers.infoChangeTime = level.time; // (re)tag the client for its team Beacon::DeleteTags( ent ); Beacon::Tag( ent, (team_t)ent->client->ps.persistant[ PERS_TEAM ], true ); }