/* * SV_Impact * * Two entities have touched, so run their touch functions */ void SV_Impact( edict_t *e1, trace_t *trace ) { edict_t *e2; if( trace->ent != -1 ) { e2 = &game.edicts[trace->ent]; if( e1->r.solid != SOLID_NOT ) { G_CallTouch( e1, e2, &trace->plane, trace->surfFlags ); } if( e2->r.solid != SOLID_NOT ) { G_CallTouch( e2, e1, NULL, 0 ); } } }
/* * GClip_TouchTriggers */ void GClip_TouchTriggers( edict_t *ent ) { int i, num; edict_t *hit; int touch[MAX_EDICTS]; vec3_t mins, maxs; // dead things don't activate triggers! if( ent->r.client && G_IsDead( ent ) ) return; VectorAdd( ent->s.origin, ent->r.mins, mins ); VectorAdd( ent->s.origin, ent->r.maxs, maxs ); // FIXME: should be s.origin + mins and s.origin + maxs because of absmin and absmax padding? num = GClip_AreaEdicts( ent->r.absmin, ent->r.absmax, touch, MAX_EDICTS, AREA_TRIGGERS, 0 ); // be careful, it is possible to have an entity in this // list removed before we get to it (killtriggered) for( i = 0; i < num; i++ ) { if( !ent->r.inuse ) break; hit = &game.edicts[touch[i]]; if( !hit->r.inuse ) continue; if( !hit->touch && !hit->asTouchFunc ) continue; if( !hit->item && !GClip_EntityContact( mins, maxs, hit ) ) continue; G_CallTouch( hit, ent, NULL, 0 ); } }
void G_PMoveTouchTriggers( pmove_t *pm ) { int i, num; edict_t *hit; int touch[MAX_EDICTS]; vec3_t mins, maxs; edict_t *ent; if( pm->playerState->POVnum <= 0 || (int)pm->playerState->POVnum > gs.maxclients ) return; ent = game.edicts + pm->playerState->POVnum; if( !ent->r.client || G_IsDead( ent ) ) // dead things don't activate triggers! return; // update the entity with the new position VectorCopy( pm->playerState->pmove.origin, ent->s.origin ); VectorCopy( pm->playerState->pmove.velocity, ent->velocity ); VectorCopy( pm->playerState->viewangles, ent->s.angles ); ent->viewheight = pm->playerState->viewheight; VectorCopy( pm->mins, ent->r.mins ); VectorCopy( pm->maxs, ent->r.maxs ); ent->waterlevel = pm->waterlevel; ent->watertype = pm->watertype; if( pm->groundentity == -1 ) { ent->groundentity = NULL; } else { ent->groundentity = &game.edicts[pm->groundentity]; ent->groundentity_linkcount = ent->groundentity->r.linkcount; } GClip_LinkEntity( ent ); VectorAdd( pm->playerState->pmove.origin, pm->mins, mins ); VectorAdd( pm->playerState->pmove.origin, pm->maxs, maxs ); num = GClip_AreaEdicts( mins, maxs, touch, MAX_EDICTS, AREA_TRIGGERS, 0 ); // be careful, it is possible to have an entity in this // list removed before we get to it (killtriggered) for( i = 0; i < num; i++ ) { if( !ent->r.inuse ) break; hit = &game.edicts[touch[i]]; if( !hit->r.inuse ) continue; if( !hit->touch && !hit->asTouchFunc ) continue; if( !hit->item && !GClip_EntityContact( mins, maxs, hit ) ) continue; G_CallTouch( hit, ent, NULL, 0 ); } }
/* * G_BoxSlideMove * calls GS_SlideMove for edict_t and triggers touch functions of touched objects */ int G_BoxSlideMove( edict_t *ent, int contentmask, float slideBounce, float friction ) { int i; move_t entMove; int blockedmask = 0; float oldVelocity; memset( &entMove, 0, sizeof( move_t ) ); oldVelocity = VectorLength( ent->velocity ); if( !ent->groundentity ) { SV_AddGravity( ent ); } else { // horizontal friction G_AddGroundFriction( ent, friction ); } entMove.numClipPlanes = 0; entMove.numtouch = 0; if( oldVelocity > 0 ) { VectorCopy( ent->s.origin, entMove.origin ); VectorCopy( ent->velocity, entMove.velocity ); VectorCopy( ent->r.mins, entMove.mins ); VectorCopy( ent->r.maxs, entMove.maxs ); entMove.remainingTime = FRAMETIME; VectorSet( entMove.gravityDir, 0, 0, -1 ); entMove.slideBounce = slideBounce; entMove.groundEntity = ( ent->groundentity == NULL ) ? -1 : ENTNUM( ent->groundentity ); entMove.passent = ENTNUM( ent ); entMove.contentmask = contentmask; blockedmask = GS_SlideMove( &entMove ); // update with the new values VectorCopy( entMove.origin, ent->s.origin ); VectorCopy( entMove.velocity, ent->velocity ); ent->groundentity = ( entMove.groundEntity == -1 ) ? NULL : &game.edicts[entMove.groundEntity]; GClip_LinkEntity( ent ); } // call touches if( contentmask != 0 ) { edict_t *other; GClip_TouchTriggers( ent ); // touch other objects for( i = 0; i < entMove.numtouch; i++ ) { other = &game.edicts[entMove.touchents[i]]; if( other->r.svflags & SVF_PROJECTILE ) { continue; } G_CallTouch( other, ent, NULL, 0 ); // if self touch function, fire up touch and if freed stop G_CallTouch( ent, other, NULL, 0 ); if( !ent->r.inuse ) { // it may have been freed by the touch function break; } } } if( ent->r.inuse ) { G_CheckGround( ent ); if( ent->groundentity && VectorLength( ent->velocity ) <= 1 && oldVelocity > 1 ) { VectorClear( ent->velocity ); VectorClear( ent->avelocity ); G_CallStop( ent ); } } return blockedmask; }
/* * ClientThink */ void ClientThink( edict_t *ent, usercmd_t *ucmd, int timeDelta ) { gclient_t *client; int i, j; static pmove_t pm; int delta, count; client = ent->r.client; client->ps.POVnum = ENTNUM( ent ); client->ps.playerNum = PLAYERNUM( ent ); // anti-lag if( ent->r.svflags & SVF_FAKECLIENT ) { client->timeDelta = 0; } else { int nudge; int fixedNudge = ( game.snapFrameTime ) * 0.5; // fixme: find where this nudge comes from. // add smoothing to timeDelta between the last few ucmds and a small fine-tuning nudge. nudge = fixedNudge + g_antilag_timenudge->integer; timeDelta += nudge; clamp( timeDelta, -g_antilag_maxtimedelta->integer, 0 ); // smooth using last valid deltas i = client->timeDeltasHead - 6; if( i < 0 ) i = 0; for( count = 0, delta = 0; i < client->timeDeltasHead; i++ ) { if( client->timeDeltas[i & G_MAX_TIME_DELTAS_MASK] < 0 ) { delta += client->timeDeltas[i & G_MAX_TIME_DELTAS_MASK]; count++; } } if( !count ) client->timeDelta = timeDelta; else { delta /= count; client->timeDelta = ( delta + timeDelta ) * 0.5; } client->timeDeltas[client->timeDeltasHead & G_MAX_TIME_DELTAS_MASK] = timeDelta; client->timeDeltasHead++; #ifdef UCMDTIMENUDGE client->timeDelta += client->pers.ucmdTimeNudge; #endif } clamp( client->timeDelta, -g_antilag_maxtimedelta->integer, 0 ); // update activity if he touched any controls if( ucmd->forwardmove != 0 || ucmd->sidemove != 0 || ucmd->upmove != 0 || ( ucmd->buttons & ~BUTTON_BUSYICON ) != 0 || client->ucmd.angles[PITCH] != ucmd->angles[PITCH] || client->ucmd.angles[YAW] != ucmd->angles[YAW] ) G_Client_UpdateActivity( client ); client->ucmd = *ucmd; // can exit intermission after two seconds, not counting postmatch if( GS_MatchState() == MATCH_STATE_WAITEXIT && ( ucmd->buttons & BUTTON_ATTACK ) && game.serverTime > GS_MatchStartTime() + 2000 ) level.exitNow = true; // (is this really needed?:only if not cared enough about ps in the rest of the code) // refresh player state position from the entity VectorCopy( ent->s.origin, client->ps.pmove.origin ); VectorCopy( ent->velocity, client->ps.pmove.velocity ); VectorCopy( ent->s.angles, client->ps.viewangles ); client->ps.pmove.gravity = level.gravity; if( GS_MatchState() >= MATCH_STATE_POSTMATCH || GS_MatchPaused() || ( ent->movetype != MOVETYPE_PLAYER && ent->movetype != MOVETYPE_NOCLIP ) ) client->ps.pmove.pm_type = PM_FREEZE; else if( ent->s.type == ET_GIB ) client->ps.pmove.pm_type = PM_GIB; else if( ent->movetype == MOVETYPE_NOCLIP || client->isTV ) client->ps.pmove.pm_type = PM_SPECTATOR; else client->ps.pmove.pm_type = PM_NORMAL; // set up for pmove memset( &pm, 0, sizeof( pmove_t ) ); pm.playerState = &client->ps; if( !client->isTV ) pm.cmd = *ucmd; if( memcmp( &client->old_pmove, &client->ps.pmove, sizeof( pmove_state_t ) ) ) pm.snapinitial = true; // perform a pmove Pmove( &pm ); // save results of pmove client->old_pmove = client->ps.pmove; // update the entity with the new position VectorCopy( client->ps.pmove.origin, ent->s.origin ); VectorCopy( client->ps.pmove.velocity, ent->velocity ); VectorCopy( client->ps.viewangles, ent->s.angles ); ent->viewheight = client->ps.viewheight; VectorCopy( pm.mins, ent->r.mins ); VectorCopy( pm.maxs, ent->r.maxs ); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; if( pm.groundentity == -1 ) { ent->groundentity = NULL; } else { G_AwardResetPlayerComboStats( ent ); ent->groundentity = &game.edicts[pm.groundentity]; ent->groundentity_linkcount = ent->groundentity->linkcount; } GClip_LinkEntity( ent ); GS_AddLaserbeamPoint( &ent->r.client->resp.trail, &ent->r.client->ps, ucmd->serverTimeStamp ); // Regeneration if( ent->r.client->ps.inventory[POWERUP_REGEN] > 0 && ent->health < 200) { ent->health += ( game.frametime * 0.001f ) * 10.0f; // Regen expires if health reaches 200 if ( ent->health >= 199.0f ) ent->r.client->ps.inventory[POWERUP_REGEN]--; } // fire touch functions if( ent->movetype != MOVETYPE_NOCLIP ) { edict_t *other; // touch other objects for( i = 0; i < pm.numtouch; i++ ) { other = &game.edicts[pm.touchents[i]]; for( j = 0; j < i; j++ ) { if( &game.edicts[pm.touchents[j]] == other ) break; } if( j != i ) continue; // duplicated // player can't touch projectiles, only projectiles can touch the player G_CallTouch( other, ent, NULL, 0 ); } } ent->s.weapon = GS_ThinkPlayerWeapon( &client->ps, ucmd->buttons, ucmd->msec, client->timeDelta ); if( G_IsDead( ent ) ) { if( ent->deathTimeStamp + g_respawn_delay_min->integer <= level.time ) client->resp.snap.buttons |= ucmd->buttons; } else if( client->ps.pmove.stats[PM_STAT_NOUSERCONTROL] <= 0 ) client->resp.snap.buttons |= ucmd->buttons; // trigger the instashield if( GS_Instagib() && g_instashield->integer ) { if( client->ps.pmove.pm_type == PM_NORMAL && pm.cmd.upmove < 0 && client->resp.instashieldCharge == INSTA_SHIELD_MAX && client->ps.inventory[POWERUP_SHELL] == 0 ) { client->ps.inventory[POWERUP_SHELL] = client->resp.instashieldCharge; G_Sound( ent, CHAN_AUTO, trap_SoundIndex( GS_FindItemByTag( POWERUP_SHELL )->pickup_sound ), ATTN_NORM ); } } // if( client->ps.pmove.pm_type == PM_NORMAL ) client->level.stats.had_playtime = true; // generating plrkeys (optimized for net communication) ClientMakePlrkeys( client, ucmd ); }