q3rally/engine/code/game/g_active.c
zturtleman 0081965385 Sync Engine and Game VM code to ioquake3
Mostly whitespace changes. No bug fixes.
Q3Rally doesn't change any botlib code so revert copyright in botlib headers that use to be in the Q3 SDK game source directory.
MD3_MAX_* defines (besides MD3_MAX_LOD) are not used so revert to ioq3 values.
2017-11-17 03:49:41 +00:00

1945 lines
58 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2002-2015 Q3Rally Team (Per Thormann - q3rally@gmail.com)
This file is part of q3rally source code.
q3rally source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
q3rally source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with q3rally; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
//
#include "g_local.h"
// STONELANCE
/*
=================
Com_LogPrintf
Print to the logfile
=================
*/
static __attribute__ ((format (printf, 1, 2))) void QDECL Com_LogPrintf( const char *fmt, ... ) {
va_list argptr;
char string[1024];
fileHandle_t logFile;
trap_FS_FOpenFile( "g_physics.log", &logFile, FS_APPEND );
va_start( argptr, fmt );
Q_vsnprintf (string, sizeof(string), fmt, argptr);
va_end( argptr );
trap_FS_Write( string, strlen( string ), logFile );
trap_FS_FCloseFile( logFile );
}
/*
================================================================================
G_DebugDynamics
================================================================================
*/
void G_DebugDynamics( carBody_t *body, carPoint_t *points, int i ){
Com_LogPrintf("\n");
Com_LogPrintf("PM_DebugDynamics: point ORIGIN - %.3f, %.3f, %.3f\n", points[i].r[0], points[i].r[1], points[i].r[2]);
Com_LogPrintf("PM_DebugDynamics: point VELOCITY - %.3f, %.3f, %.3f\n", points[i].v[0], points[i].v[1], points[i].v[2]);
Com_LogPrintf("PM_DebugDynamics: point ONGROUND - %i\n", points[i].onGround);
Com_LogPrintf("PM_DebugDynamics: point NORMAL 1- %.3f, %.3f, %.3f\n", points[i].normals[0][0], points[i].normals[0][1], points[i].normals[0][2]);
Com_LogPrintf("PM_DebugDynamics: point NORMAL 2- %.3f, %.3f, %.3f\n", points[i].normals[1][0], points[i].normals[1][1], points[i].normals[1][2]);
Com_LogPrintf("PM_DebugDynamics: point NORMAL 3- %.3f, %.3f, %.3f\n", points[i].normals[2][0], points[i].normals[2][1], points[i].normals[2][2]);
// Com_LogPrintf("PM_DebugDynamics: point MASS - %.3f\n", points[pm->pDebug-1].mass);
Com_LogPrintf("PM_DebugDynamics: body ORIGIN - %.3f, %.3f, %.3f\n", body->r[0], body->r[1], body->r[2]);
Com_LogPrintf("PM_DebugDynamics: body VELOCITY - %.3f, %.3f, %.3f\n", body->v[0], body->v[1], body->v[2]);
Com_LogPrintf("PM_DebugDynamics: body ANG VELOCITY - %.3f, %.3f, %.3f\n", body->w[0], body->w[1], body->w[2]);
// Com_LogPrintf("PM_DebugDynamics: body COM - %.3f, %.3f, %.3f\n", body->CoM[0], body->CoM[1], body->CoM[2]);
// Com_LogPrintf("PM_DebugDynamics: body MASS - %.3f\n", body->mass);
Com_LogPrintf("Direction vectors ----------------------------------------\n");
Com_LogPrintf("PM_DebugDynamics: body FORWARD - %.3f, %.3f, %.3f\n", body->forward[0], body->forward[1], body->forward[2]);
Com_LogPrintf("PM_DebugDynamics: body RIGHT - %.3f, %.3f, %.3f\n", body->right[0], body->right[1], body->right[2]);
Com_LogPrintf("PM_DebugDynamics: body UP - %.3f, %.3f, %.3f\n", body->up[0], body->up[1], body->up[2]);
Com_LogPrintf("\n");
}
/*
================================================================================
G_DebugForces
================================================================================
*/
void G_DebugForces( carBody_t *body, carPoint_t *points, int i ){
Com_LogPrintf("\n");
Com_LogPrintf("PM_DebugForces: point GRAVITY - %.3f, %.3f, %.3f\n", points[i].forces[GRAVITY][0], points[i].forces[GRAVITY][1], points[i].forces[GRAVITY][2]);
Com_LogPrintf("PM_DebugForces: point NORMAL - %.3f, %.3f, %.3f\n", points[i].forces[NORMAL][0], points[i].forces[NORMAL][1], points[i].forces[NORMAL][2]);
Com_LogPrintf("PM_DebugForces: point SHOCK - %.3f, %.3f, %.3f\n", points[i].forces[SHOCK][0], points[i].forces[SHOCK][1], points[i].forces[SHOCK][2]);
Com_LogPrintf("PM_DebugForces: point SPRING - %.3f, %.3f, %.3f\n", points[i].forces[SPRING][0], points[i].forces[SPRING][1], points[i].forces[SPRING][2]);
Com_LogPrintf("PM_DebugForces: point SWAY_BAR - %.3f, %.3f, %.3f\n", points[i].forces[SWAY_BAR][0], points[i].forces[SWAY_BAR][1], points[i].forces[SWAY_BAR][2]);
Com_LogPrintf("PM_DebugForces: point ROAD - %.3f, %.3f, %.3f\n", points[i].forces[ROAD][0], points[i].forces[ROAD][1], points[i].forces[ROAD][2]);
Com_LogPrintf("PM_DebugForces: point INTERNAL - %.3f, %.3f, %.3f\n", points[i].forces[INTERNAL][0], points[i].forces[INTERNAL][1], points[i].forces[INTERNAL][2]);
Com_LogPrintf("PM_DebugForces: point AIR_FRICTION - %.3f, %.3f, %.3f\n", points[i].forces[AIR_FRICTION][0], points[i].forces[AIR_FRICTION][1], points[i].forces[AIR_FRICTION][2]);
Com_LogPrintf("PM_DebugForces: point NETFORCE - %.3f, %.3f, %.3f\n", points[i].netForce[0], points[i].netForce[1], points[i].netForce[2]);
Com_LogPrintf("PM_DebugForces: point fluidDensity - %.6f\n", points[i].fluidDensity);
Com_LogPrintf("PM_DebugForces: body netForce - %.3f, %.3f, %.3f\n", body->netForce[0], body->netForce[1], body->netForce[2]);
Com_LogPrintf("PM_DebugForces: body netMoment - %.3f, %.3f, %.3f\n", body->netMoment[0], body->netMoment[1], body->netMoment[2]);
Com_LogPrintf("\n");
}
// END
/*
===============
G_DamageFeedback
Called just before a snapshot is sent to the given player.
Totals up all damage and generates both the player_state_t
damage values to that client for pain blends and kicks, and
global pain sound events for all clients.
===============
*/
void P_DamageFeedback( gentity_t *player ) {
gclient_t *client;
float count;
// STONELANCE
// vec3_t angles;
// END
client = player->client;
if ( client->ps.pm_type == PM_DEAD ) {
return;
}
// total points of damage shot at the player this frame
count = client->damage_blood + client->damage_armor;
if ( count == 0 ) {
return; // didn't take any damage
}
if ( count > 255 ) {
count = 255;
}
// send the information to the client
// world damage (falling, slime, etc) uses a special code
// to make the blend blob centered instead of positional
// STONELANCE
/*
if ( client->damage_fromWorld ) {
client->ps.damagePitch = 255;
client->ps.damageYaw = 255;
client->damage_fromWorld = qfalse;
} else {
vectoangles( client->damage_from, angles );
client->ps.damagePitch = angles[PITCH]/360.0 * 256;
client->ps.damageYaw = angles[YAW]/360.0 * 256;
}
*/
// END
// play an apropriate pain sound
if ( (level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) ) {
player->pain_debounce_time = level.time + 700;
G_AddEvent( player, EV_PAIN, player->health );
// STONELANCE
// client->ps.damageEvent++;
// END
}
// STONELANCE
// client->ps.damageCount = count;
// END
//
// clear totals
//
client->damage_blood = 0;
client->damage_armor = 0;
client->damage_knockback = 0;
}
/*
=============
P_WorldEffects
Check for lava / slime contents and drowning
=============
*/
void P_WorldEffects( gentity_t *ent ) {
qboolean envirosuit;
int waterlevel;
if ( ent->client->noclip ) {
ent->client->airOutTime = level.time + 12000; // don't need air
return;
}
waterlevel = ent->waterlevel;
envirosuit = ent->client->ps.powerups[PW_BATTLESUIT] > level.time;
//
// check for drowning
//
if ( waterlevel == 3 ) {
// envirosuit give air
// STONELANCE envirosuit doesnt help a car
/*
if ( envirosuit ) {
ent->client->airOutTime = level.time + 10000;
}
*/
// END
// if out of air, start drowning
if ( ent->client->airOutTime < level.time) {
// drown!
// STONELANCE - damage a lot fast for a car
// ent->client->airOutTime += 1000;
ent->client->airOutTime += 200;
// END
if ( ent->health > 0 ) {
// take more damage the longer underwater
ent->damage += 2;
if (ent->damage > 15)
ent->damage = 15;
// don't play a normal pain sound
ent->pain_debounce_time = level.time + 200;
G_Damage (ent, NULL, NULL, NULL, NULL,
ent->damage, DAMAGE_NO_ARMOR, MOD_WATER);
}
}
} else {
// STONELANCE
// ent->client->airOutTime = level.time + 12000;
ent->client->airOutTime = level.time + 200;
ent->damage = 2;
// END
}
//
// check for sizzle damage (move to pmove?)
//
if (waterlevel &&
(ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) {
if (ent->health > 0
&& ent->pain_debounce_time <= level.time ) {
if ( envirosuit ) {
G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 );
} else {
if (ent->watertype & CONTENTS_LAVA) {
G_Damage (ent, NULL, NULL, NULL, NULL,
30*waterlevel, 0, MOD_LAVA);
}
if (ent->watertype & CONTENTS_SLIME) {
G_Damage (ent, NULL, NULL, NULL, NULL,
10*waterlevel, 0, MOD_SLIME);
}
}
}
}
}
/*
===============
G_SetClientSound
===============
*/
void G_SetClientSound( gentity_t *ent ) {
#ifdef MISSIONPACK
if( ent->s.eFlags & EF_TICKING ) {
ent->client->ps.loopSound = G_SoundIndex( "sound/weapons/proxmine/wstbtick.wav");
}
else
#endif
if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) {
ent->client->ps.loopSound = level.snd_fry;
} else {
ent->client->ps.loopSound = 0;
}
}
//==============================================================
/*
==============
ClientImpacts
==============
*/
void ClientImpacts( gentity_t *ent, pmove_t *pm ) {
int i, j;
trace_t trace;
gentity_t *other;
memset( &trace, 0, sizeof( trace ) );
for (i=0 ; i<pm->numtouch ; i++) {
for (j=0 ; j<i ; j++) {
if (pm->touchents[j] == pm->touchents[i] ) {
break;
}
}
if (j != i) {
continue; // duplicated
}
other = &g_entities[ pm->touchents[i] ];
// STONELANCE
VectorSubtract( other->s.origin, pm->touchPos[i], trace.plane.normal );
VectorNormalize( trace.plane.normal );
VectorCopy(pm->touchPos[i], trace.endpos);
// END
if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) {
ent->touch( ent, other, &trace );
}
if ( !other->touch ) {
continue;
}
other->touch( other, ent, &trace );
}
}
/*
int entitiesInTracedBox( const vec3_t mins, const vec3_t maxs, const vec3_t start, const vec3_t end, int *list, int maxcount )
{
int num;
int ents[MAX_GENTITIES];
int numInMovingBox;
vec3_t curMins;
vec3_t curMaxs;
vecCopy( mins, curMins );
vecCopy( maxs, curMaxs );
num = trap_EntitiesInBox( curMins, curMaxs, ents, MAX_GENTITIES );
}
*/
/*
============
G_TouchTriggers
Find all trigger entities that ent's current position touches.
Spectators will only interact with teleporters.
============
*/
void G_TouchTriggers( gentity_t *ent ) {
int i, num;
int touch[MAX_GENTITIES];
gentity_t *hit;
trace_t trace;
vec3_t mins, maxs;
static vec3_t range = { 40, 40, 52 };
if ( !ent->client ) {
return;
}
// dead clients don't activate triggers!
if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) {
return;
}
VectorSubtract( ent->client->ps.origin, range, mins );
VectorAdd( ent->client->ps.origin, range, maxs );
// STONELANCE
AddPointToBounds( ent->client->car.tBody.r, mins, maxs );
// END
num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
// can't use ent->absmin, because that has a one unit pad
VectorAdd( ent->client->ps.origin, ent->r.mins, mins );
VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs );
for ( i=0 ; i<num ; i++ ) {
hit = &g_entities[touch[i]];
if ( !hit->touch && !ent->touch ) {
continue;
}
if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) {
continue;
}
// ignore most entities if a spectator
// STONELANCE
// if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR
|| isRaceObserver( ent->s.number ) ) {
// END
if ( hit->s.eType != ET_TELEPORT_TRIGGER &&
// this is ugly but adding a new ET_? type will
// most likely cause network incompatibilities
hit->touch != Touch_DoorTrigger) {
continue;
}
}
// use seperate code for determining if an item is picked up
// so you don't have to actually contact its bounding box
if ( hit->s.eType == ET_ITEM ) {
if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) {
continue;
}
} else {
if ( !trap_EntityContact( mins, maxs, hit ) ) {
continue;
}
}
memset( &trace, 0, sizeof(trace) );
if ( hit->touch ) {
hit->touch (hit, ent, &trace);
}
if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) {
ent->touch( ent, hit, &trace );
}
}
// if we didn't touch a jump pad this pmove frame
if ( ent->client->ps.jumppad_frame != ent->client->ps.pmove_framecount ) {
ent->client->ps.jumppad_frame = 0;
ent->client->ps.jumppad_ent = 0;
}
}
/*
=================
SpectatorThink
=================
*/
void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) {
pmove_t pm;
gclient_t *client;
client = ent->client;
// STONELANCE
// if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) {
if ( client->sess.spectatorState == SPECTATOR_FREE ) {
// END
if ( client->noclip ) {
client->ps.pm_type = PM_NOCLIP;
} else {
client->ps.pm_type = PM_SPECTATOR;
}
// STONELANCE
// client->ps.speed = 400; // faster than normal
client->ps.speed = 800; // faster than normal
// END
// set up for pmove
memset (&pm, 0, sizeof(pm));
pm.ps = &client->ps;
pm.cmd = *ucmd;
pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies
pm.trace = trap_Trace;
pm.pointcontents = trap_PointContents;
// perform a pmove
Pmove (&pm);
// save results of pmove
VectorCopy( client->ps.origin, ent->s.origin );
G_TouchTriggers( ent );
trap_UnlinkEntity( ent );
}
// STONELANCE
if (client->sess.spectatorState == SPECTATOR_OBSERVE)
{
client->ps.commandTime = ucmd->serverTime;
UpdateObserverSpot( ent, qfalse );
}
// END
client->oldbuttons = client->buttons;
client->buttons = ucmd->buttons;
// attack button cycles through spectators
// STONELANCE
// if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) {
if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK )
&& !(ent->r.svFlags & SVF_BOT) ) {
// END
Cmd_FollowCycle_f( ent, 1 );
}
// STONELANCE
if ( ( client->buttons & BUTTON_REARATTACK ) && ! ( client->oldbuttons & BUTTON_REARATTACK ) ) {
int newState;
vec3_t origin, angles;
int clientNum;
if( client->sess.spectatorState == SPECTATOR_FREE )
newState = SPECTATOR_OBSERVE;
else
newState = client->sess.spectatorState + 1;
switch (newState){
default:
case SPECTATOR_FOLLOW:
trap_SendServerCommand( ent - g_entities, "print \"Spectator Mode: Follow\n\"" );
client->sess.spectatorState = SPECTATOR_FOLLOW;
break;
case SPECTATOR_OBSERVE:
trap_SendServerCommand( ent - g_entities, "print \"Spectator Mode: Observe\n\"" );
StopFollowing( ent );
clientNum = ent->client->sess.spectatorClient;
// team follow1 and team follow2 go to whatever clients are playing
if ( clientNum == -1 ) {
clientNum = level.follow1;
} else if ( clientNum == -2 ) {
clientNum = level.follow2;
}
if ( clientNum < 0 )
Cmd_FollowCycle_f( ent, 1 );
if ( clientNum < 0 )
break;
client->sess.spectatorState = SPECTATOR_OBSERVE;
// ent->s.eType = ET_INVISIBLE;
if ( FindBestObserverSpot(ent, &g_entities[client->sess.spectatorClient], origin, angles) )
{
G_SetOrigin(ent, origin);
VectorCopy(origin, ent->client->ps.origin);
VectorCopy(angles, ent->client->ps.viewangles);
}
else
client->sess.spectatorState = SPECTATOR_FOLLOW;
break;
}
}
// END
}
/*
=================
ClientInactivityTimer
Returns qfalse if the client is dropped
=================
*/
qboolean ClientInactivityTimer( gclient_t *client ) {
if ( ! g_inactivity.integer ) {
// give everyone some time, so if the operator sets g_inactivity during
// gameplay, everyone isn't kicked
client->inactivityTime = level.time + 60 * 1000;
client->inactivityWarning = qfalse;
} else if ( client->pers.cmd.forwardmove ||
client->pers.cmd.rightmove ||
client->pers.cmd.upmove ||
(client->pers.cmd.buttons & BUTTON_ATTACK) ) {
client->inactivityTime = level.time + g_inactivity.integer * 1000;
client->inactivityWarning = qfalse;
} else if ( !client->pers.localClient ) {
if ( level.time > client->inactivityTime ) {
trap_DropClient( client - level.clients, "Dropped due to inactivity" );
return qfalse;
}
if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) {
client->inactivityWarning = qtrue;
trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" );
}
}
return qtrue;
}
/*
==================
ClientTimerActions
Actions that happen once a second
==================
*/
void ClientTimerActions( gentity_t *ent, int msec ) {
gclient_t *client;
#ifdef MISSIONPACK
int maxHealth;
#endif
client = ent->client;
client->timeResidual += msec;
while ( client->timeResidual >= 1000 ) {
client->timeResidual -= 1000;
// regenerate
#ifdef MISSIONPACK
if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) {
maxHealth = client->ps.stats[STAT_MAX_HEALTH] / 2;
}
else if ( client->ps.powerups[PW_REGEN] ) {
maxHealth = client->ps.stats[STAT_MAX_HEALTH];
}
else {
maxHealth = 0;
}
if( maxHealth ) {
if ( ent->health < maxHealth ) {
ent->health += 15;
if ( ent->health > maxHealth * 1.1 ) {
ent->health = maxHealth * 1.1;
}
G_AddEvent( ent, EV_POWERUP_REGEN, 0 );
} else if ( ent->health < maxHealth * 2) {
ent->health += 5;
if ( ent->health > maxHealth * 2 ) {
ent->health = maxHealth * 2;
}
G_AddEvent( ent, EV_POWERUP_REGEN, 0 );
}
#else
if ( client->ps.powerups[PW_REGEN] ) {
if ( ent->health < client->ps.stats[STAT_MAX_HEALTH]) {
ent->health += 15;
if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 1.1 ) {
ent->health = client->ps.stats[STAT_MAX_HEALTH] * 1.1;
}
G_AddEvent( ent, EV_POWERUP_REGEN, 0 );
} else if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] * 2) {
ent->health += 5;
if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 2 ) {
ent->health = client->ps.stats[STAT_MAX_HEALTH] * 2;
}
G_AddEvent( ent, EV_POWERUP_REGEN, 0 );
}
#endif
} else {
// count down health when over max
// STONELANCE
// if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) {
if (((!isRallyRace() && !g_gametype.integer == GT_DERBY)
|| level.startRaceTime) && ent->health > client->ps.stats[STAT_MAX_HEALTH]){
// END
ent->health--;
}
}
// count down armor when over max
// STONELANCE
// if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH] ) {
if (((!isRallyRace() && !g_gametype.integer == GT_DERBY) || level.startRaceTime)
&& client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH]){
// END
client->ps.stats[STAT_ARMOR]--;
}
}
#ifdef MISSIONPACK
if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) {
int w, max, inc, t, i;
int weapList[]={WP_MACHINEGUN,WP_SHOTGUN,WP_GRENADE_LAUNCHER,WP_ROCKET_LAUNCHER,WP_LIGHTNING,WP_RAILGUN,WP_PLASMAGUN,WP_BFG,WP_NAILGUN,WP_PROX_LAUNCHER,WP_CHAINGUN,WP_FLAME_THROWER};
int weapCount = ARRAY_LEN( weapList );
//
for (i = 0; i < weapCount; i++) {
w = weapList[i];
switch(w) {
case WP_MACHINEGUN: max = 50; inc = 4; t = 1000; break;
case WP_SHOTGUN: max = 10; inc = 1; t = 1500; break;
case WP_GRENADE_LAUNCHER: max = 10; inc = 1; t = 2000; break;
case WP_ROCKET_LAUNCHER: max = 10; inc = 1; t = 1750; break;
case WP_LIGHTNING: max = 50; inc = 5; t = 1500; break;
case WP_RAILGUN: max = 10; inc = 1; t = 1750; break;
case WP_PLASMAGUN: max = 50; inc = 5; t = 1500; break;
case WP_BFG: max = 10; inc = 1; t = 4000; break;
case WP_NAILGUN: max = 10; inc = 1; t = 1250; break;
case WP_PROX_LAUNCHER: max = 5; inc = 1; t = 2000; break;
case WP_CHAINGUN: max = 100; inc = 5; t = 1000; break;
case WP_FLAME_THROWER: max = 100; inc = 5; t = 1000; break;
default: max = 0; inc = 0; t = 1000; break;
}
client->ammoTimes[w] += msec;
if ( client->ps.ammo[w] >= max ) {
client->ammoTimes[w] = 0;
}
if ( client->ammoTimes[w] >= t ) {
while ( client->ammoTimes[w] >= t )
client->ammoTimes[w] -= t;
client->ps.ammo[w] += inc;
if ( client->ps.ammo[w] > max ) {
client->ps.ammo[w] = max;
}
}
}
}
#endif
}
/*
====================
ClientIntermissionThink
====================
*/
void ClientIntermissionThink( gclient_t *client ) {
client->ps.eFlags &= ~EF_TALK;
client->ps.eFlags &= ~EF_FIRING;
// the level will exit when everyone wants to or after timeouts
// swap and latch button actions
client->oldbuttons = client->buttons;
client->buttons = client->pers.cmd.buttons;
if ( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) {
// this used to be an ^1 but once a player says ready, it should stick
client->readyToExit = 1;
}
}
/*
================
ClientEvents
Events will be passed on to the clients for presentation,
but any server game effects are handled here
================
*/
void ClientEvents( gentity_t *ent, int oldEventSequence ) {
int i, j;
int event;
gclient_t *client;
int damage;
vec3_t origin, angles;
// qboolean fired;
gitem_t *item;
gentity_t *drop;
client = ent->client;
if ( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) {
oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS;
}
for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) {
event = client->ps.events[ i & (MAX_PS_EVENTS-1) ];
switch ( event ) {
case EV_FALL_MEDIUM:
case EV_FALL_FAR:
if ( ent->s.eType != ET_PLAYER ) {
break; // not in the player model
}
if ( g_dmflags.integer & DF_NO_FALLING ) {
break;
}
if ( event == EV_FALL_FAR ) {
damage = 10;
} else {
damage = 5;
}
ent->pain_debounce_time = level.time + 200; // no normal pain sound
G_Damage (ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING);
break;
case EV_FIRE_WEAPON:
FireWeapon( ent );
break;
case EV_ALTFIRE_WEAPON:
FireAltWeapon( ent );
break;
// STONELANCE
case EV_FIRE_REARWEAPON:
FireRearWeapon( ent );
break;
// END
case EV_USE_ITEM1: // teleporter
// drop flags in CTF
item = NULL;
j = 0;
if ( ent->client->ps.powerups[ PW_REDFLAG ] ) {
item = BG_FindItemForPowerup( PW_REDFLAG );
j = PW_REDFLAG;
} else if ( ent->client->ps.powerups[ PW_BLUEFLAG ] ) {
item = BG_FindItemForPowerup( PW_BLUEFLAG );
j = PW_BLUEFLAG;
} else if ( ent->client->ps.powerups[ PW_NEUTRALFLAG ] ) {
item = BG_FindItemForPowerup( PW_NEUTRALFLAG );
j = PW_NEUTRALFLAG;
}
if ( item ) {
drop = Drop_Item( ent, item, 0 );
// decide how many seconds it has left
drop->count = ( ent->client->ps.powerups[ j ] - level.time ) / 1000;
if ( drop->count < 1 ) {
drop->count = 1;
}
ent->client->ps.powerups[ j ] = 0;
}
#ifdef MISSIONPACK
if ( g_gametype.integer == GT_HARVESTER ) {
if ( ent->client->ps.generic1 > 0 ) {
if ( ent->client->sess.sessionTeam == TEAM_RED ) {
item = BG_FindItem( "Blue Cube" );
} else {
item = BG_FindItem( "Red Cube" );
}
if ( item ) {
for ( j = 0; j < ent->client->ps.generic1; j++ ) {
drop = Drop_Item( ent, item, 0 );
if ( ent->client->sess.sessionTeam == TEAM_RED ) {
drop->spawnflags = TEAM_BLUE;
} else {
drop->spawnflags = TEAM_RED;
}
}
}
ent->client->ps.generic1 = 0;
}
}
#endif
SelectSpawnPoint( ent->client->ps.origin, origin, angles, qfalse );
TeleportPlayer( ent, origin, angles );
break;
case EV_USE_ITEM2: // medkit
ent->health = ent->client->ps.stats[STAT_MAX_HEALTH] + 25;
break;
#ifdef MISSIONPACK
case EV_USE_ITEM3: // kamikaze
// make sure the invulnerability is off
ent->client->invulnerabilityTime = 0;
// start the kamikze
G_StartKamikaze( ent );
break;
case EV_USE_ITEM4: // portal
if( ent->client->portalID ) {
DropPortalSource( ent );
}
else {
DropPortalDestination( ent );
}
break;
case EV_USE_ITEM5: // invulnerability
ent->client->invulnerabilityTime = level.time + 10000;
break;
#endif
default:
break;
}
}
}
#ifdef MISSIONPACK
/*
==============
StuckInOtherClient
==============
*/
static int StuckInOtherClient(gentity_t *ent) {
int i;
gentity_t *ent2;
ent2 = &g_entities[0];
for ( i = 0; i < MAX_CLIENTS; i++, ent2++ ) {
if ( ent2 == ent ) {
continue;
}
if ( !ent2->inuse ) {
continue;
}
if ( !ent2->client ) {
continue;
}
if ( ent2->health <= 0 ) {
continue;
}
//
if (ent2->r.absmin[0] > ent->r.absmax[0])
continue;
if (ent2->r.absmin[1] > ent->r.absmax[1])
continue;
if (ent2->r.absmin[2] > ent->r.absmax[2])
continue;
if (ent2->r.absmax[0] < ent->r.absmin[0])
continue;
if (ent2->r.absmax[1] < ent->r.absmin[1])
continue;
if (ent2->r.absmax[2] < ent->r.absmin[2])
continue;
return qtrue;
}
return qfalse;
}
#endif
void BotTestSolid(vec3_t origin);
/*
==============
SendPendingPredictableEvents
==============
*/
void SendPendingPredictableEvents( playerState_t *ps ) {
gentity_t *t;
int event, seq;
int extEvent, number;
// if there are still events pending
if ( ps->entityEventSequence < ps->eventSequence ) {
// create a temporary entity for this event which is sent to everyone
// except the client who generated the event
seq = ps->entityEventSequence & (MAX_PS_EVENTS-1);
event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 );
// set external event to zero before calling BG_PlayerStateToEntityState
extEvent = ps->externalEvent;
ps->externalEvent = 0;
// create temporary entity for event
t = G_TempEntity( ps->origin, event );
number = t->s.number;
BG_PlayerStateToEntityState( ps, &t->s, qtrue );
t->s.number = number;
t->s.eType = ET_EVENTS + event;
t->s.eFlags |= EF_PLAYER_EVENT;
t->s.otherEntityNum = ps->clientNum;
// send to everyone except the client who generated the event
t->r.svFlags |= SVF_NOTSINGLECLIENT;
t->r.singleClient = ps->clientNum;
// set back external event
ps->externalEvent = extEvent;
}
}
/*
==============
ClientThink
This will be called once for each client frame, which will
usually be a couple times for each server frame on fast clients.
If "g_synchronousClients 1" is set, this will be called exactly
once for each server frame, which makes for smooth demo recording.
==============
*/
void ClientThink_real( gentity_t *ent ) {
gclient_t *client;
pmove_t pm;
int oldEventSequence;
int msec;
usercmd_t *ucmd;
// STONELANCE
vec3_t origin, forward;
int i;
int start;
vec3_t oldAngles;
int oldTime;
// END
client = ent->client;
// don't think if the client is not yet connected (and thus not yet spawned in)
if (client->pers.connected != CON_CONNECTED) {
return;
}
// mark the time, so the connection sprite can be removed
ucmd = &ent->client->pers.cmd;
// sanity check the command time to prevent speedup cheating
if ( ucmd->serverTime > level.time + 200 ) {
ucmd->serverTime = level.time + 200;
// G_Printf("serverTime <<<<<\n" );
}
if ( ucmd->serverTime < level.time - 1000 ) {
ucmd->serverTime = level.time - 1000;
// G_Printf("serverTime >>>>>\n" );
}
msec = ucmd->serverTime - client->ps.commandTime;
// following others may result in bad times, but we still want
// to check for follow toggles
if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) {
return;
}
if ( msec > 200 ) {
msec = 200;
}
if ( pmove_msec.integer < 8 ) {
trap_Cvar_Set("pmove_msec", "8");
trap_Cvar_Update(&pmove_msec);
}
else if (pmove_msec.integer > 33) {
trap_Cvar_Set("pmove_msec", "33");
trap_Cvar_Update(&pmove_msec);
}
if ( pmove_fixed.integer || client->pers.pmoveFixed ) {
ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer;
//if (ucmd->serverTime - client->ps.commandTime <= 0)
// return;
}
//
// check for exiting intermission
//
if ( level.intermissiontime ) {
ClientIntermissionThink( client );
return;
}
// spectators don't do much
if ( client->sess.sessionTeam == TEAM_SPECTATOR ) {
if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) {
return;
}
SpectatorThink( ent, ucmd );
return;
}
// STONELANCE
else if ( isRaceObserver( ent->s.number ) ){
if ( client->sess.spectatorState == SPECTATOR_NOT ) {
client->sess.spectatorState = SPECTATOR_OBSERVE;
UpdateObserverSpot( ent, qtrue );
}
SpectatorThink( ent, ucmd );
return;
}
// END
// check for inactivity timer, but never drop the local client of a non-dedicated server
if ( !ClientInactivityTimer( client ) ) {
return;
}
// clear the rewards if time
if ( level.time > client->rewardTime ) {
client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP );
}
if ( client->noclip ) {
client->ps.pm_type = PM_NOCLIP;
} else if ( client->ps.stats[STAT_HEALTH] <= 0 ) {
client->ps.pm_type = PM_DEAD;
} else {
client->ps.pm_type = PM_NORMAL;
}
client->ps.gravity = g_gravity.value;
// set speed
client->ps.speed = g_speed.value;
#ifdef MISSIONPACK
if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) {
client->ps.speed *= 1.5;
}
else
#endif
// STONELANCE - haste changes fire rate now
/*
if ( client->ps.powerups[PW_HASTE] ) {
client->ps.speed *= 1.3;
}
*/
// END
// Let go of the hook if we aren't firing
// STONELANCE - no more hook
/*
if ( client->ps.weapon == WP_GRAPPLING_HOOK &&
client->hook && !( ucmd->buttons & BUTTON_ATTACK ) ) {
Weapon_HookFree(client->hook);
}
*/
// STONELANCE
// check for car reset
G_ResetCar( ent );
if (!level.startRaceTime && (isRallyRace() || g_gametype.integer == GT_DERBY)){
if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) && !ent->ready ) {
trap_SendServerCommand( ent->s.clientNum, "cp \"Waiting for other players...\n\"");
ent->ready = qtrue;
}
else if (!ent->ready && level.time < level.startTime + (g_forceEngineStart.integer * 1000) - 10000){
if (ent->updateTime < level.time){
trap_SendServerCommand( ent->s.clientNum, "cp \"Press FIRE or USE when ready to race.\n\"");
ent->updateTime = level.time + 2000;
}
}
ucmd->buttons = BUTTON_HANDBRAKE;
ucmd->forwardmove = 0;
// ucmd->rightmove = 0;
ucmd->upmove = 0;
}
if ( ent->client->finishRaceTime && ent->client->finishRaceTime + 500 < level.time
&& !level.intermissiontime ){
ent->s.weapon = WP_NONE;
ent->s.powerups = 0;
ent->r.contents = CONTENTS_CORPSE;
ucmd->weapon = ent->s.weapon;
ucmd->buttons = BUTTON_HANDBRAKE;
ucmd->forwardmove = 0;
// ucmd->rightmove = 0;
ucmd->upmove = 0;
}
if ( ent->client->finishRaceTime && ent->client->finishRaceTime + RACE_OBSERVER_DELAY < level.time ){
gentity_t *tent;
tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
tent->s.clientNum = ent->s.clientNum;
SelectSpectatorSpawnPoint ( ent->client->ps.origin, ent->client->ps.viewangles );
VectorCopy( ent->client->ps.origin, ent->r.currentOrigin );
VectorCopy( ent->client->ps.origin, ent->s.pos.trBase );
VectorCopy( ent->client->ps.viewangles, ent->r.currentAngles );
VectorCopy( ent->client->ps.viewangles, ent->s.apos.trBase );
ent->raceObserver = qtrue;
if( ent->frontBounds )
trap_UnlinkEntity( ent->frontBounds );
if( ent->rearBounds )
trap_UnlinkEntity( ent->rearBounds );
trap_UnlinkEntity( client->carPoints[0] );
trap_UnlinkEntity( client->carPoints[1] );
trap_UnlinkEntity( client->carPoints[2] );
trap_UnlinkEntity( client->carPoints[3] );
return;
}
/*
if( client->sess.sessionTeam != TEAM_SPECTATOR && ent->finishRaceTime &&
ent->finishRaceTime + 10000 < level.time ){
SetTeam( ent, "racerSpectator" );
}
*/
if (isRallyNonDMRace()/* TEMP DERBY || g_gametype.integer == GT_DERBY*/){
ent->s.weapon = WP_NONE;
ucmd->weapon = ent->s.weapon;
}
// UPDATE - enable this
// sound horn
if ( client->sess.sessionTeam != TEAM_SPECTATOR && !level.intermissiontime &&
ucmd->buttons & BUTTON_HORN && client->horn_sound_time < level.time){
G_Sound(ent, CHAN_AUTO, G_SoundIndex("*horn.wav"));
client->horn_sound_time = level.time + 100;
}
// use turbo
if ((ucmd->buttons & BUTTON_TURBO) && !(client->oldbuttons & BUTTON_TURBO)
&& client->ps.powerups[ PW_TURBO ] < 0){
client->ps.powerups[ PW_TURBO ] = level.time + (-client->ps.powerups[ PW_TURBO ]);
}
else if (!(ucmd->buttons & BUTTON_TURBO) && (client->oldbuttons & BUTTON_TURBO)
&& ent->client->ps.powerups[ PW_TURBO ] > level.time){
client->ps.powerups[ PW_TURBO ] = -(client->ps.powerups[ PW_TURBO ] - level.time);
}
// END
// set up for pmove
oldEventSequence = client->ps.eventSequence;
memset (&pm, 0, sizeof(pm));
// check for the hit-scan gauntlet, don't let the action
// go through as an attack unless it actually hits something
if ( client->ps.weapon == WP_GAUNTLET && !( ucmd->buttons & BUTTON_TALK ) &&
// STONELANCE
// ( ucmd->buttons & BUTTON_ATTACK ) && client->ps.weaponTime <= 0 ) {
( ucmd->buttons & BUTTON_ATTACK ) && (client->ps.weaponTime & NORMAL_WEAPON_TIME_MASK) <= 0 ) {
// END
pm.gauntletHit = CheckGauntletAttack( ent );
}
// STONELANCE
/*
if ( ent->flags & FL_FORCE_GESTURE ) {
ent->flags &= ~FL_FORCE_GESTURE;
ent->client->pers.cmd.buttons |= BUTTON_GESTURE;
}
*/
// END
#ifdef MISSIONPACK
// check for invulnerability expansion before doing the Pmove
if (client->ps.powerups[PW_INVULNERABILITY] ) {
if ( !(client->ps.pm_flags & PMF_INVULEXPAND) ) {
vec3_t mins = { -42, -42, -42 };
vec3_t maxs = { 42, 42, 42 };
vec3_t oldmins, oldmaxs;
VectorCopy (ent->r.mins, oldmins);
VectorCopy (ent->r.maxs, oldmaxs);
// expand
VectorCopy (mins, ent->r.mins);
VectorCopy (maxs, ent->r.maxs);
trap_LinkEntity(ent);
// check if this would get anyone stuck in this player
if ( !StuckInOtherClient(ent) ) {
// set flag so the expanded size will be set in PM_CheckDuck
client->ps.pm_flags |= PMF_INVULEXPAND;
}
// set back
VectorCopy (oldmins, ent->r.mins);
VectorCopy (oldmaxs, ent->r.maxs);
trap_LinkEntity(ent);
}
}
#endif
pm.ps = &client->ps;
pm.cmd = *ucmd;
// STONELANCE - dead cars can still hit things
/*
if ( pm.ps->pm_type == PM_DEAD ) {
pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY;
}
else
*/
// END
if ( ent->r.svFlags & SVF_BOT ) {
pm.tracemask = MASK_PLAYERSOLID | CONTENTS_BOTCLIP;
}
else {
pm.tracemask = MASK_PLAYERSOLID;
}
pm.trace = trap_Trace;
pm.pointcontents = trap_PointContents;
pm.frictionFunc = G_FrictionCalc;
pm.debugLevel = g_debugMove.integer;
pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0;
pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed;
pm.pmove_msec = pmove_msec.integer;
// STONELANCE
// TEMP used to move car around
/*
client->car.sBody.r[2] += 3.0f * (pm.cmd.upmove * msec / 1000.0f);
client->car.sBody.CoM[2] += 3.0f * (pm.cmd.upmove * msec / 1000.0f);
for (i = 0; i < 8; i++){
client->car.sPoints[i].r[2] += 3.0f * (pm.cmd.upmove * msec / 1000.0f);
}
*/
// END TEMP
// load car position etc into pmove
level.cars[ent->s.clientNum] = &client->car;
pm.car = &client->car;
pm.cars = level.cars;
pm.pDebug = ent->pDebug;
pm.controlMode = client->pers.controlMode;
pm.manualShift = client->pers.manualShift;
pm.client = qfalse;
if (ent->pDebug > 0){
Com_LogPrintf("Server Time: %i\n", pm.cmd.serverTime);
Com_LogPrintf("Source: Debug %d\n", ent->pDebug);
G_DebugForces(&pm.car->sBody, pm.car->sPoints, ent->pDebug-1);
G_DebugDynamics(&pm.car->sBody, pm.car->sPoints, ent->pDebug-1);
}
start = trap_Milliseconds();
oldTime = client->ps.commandTime;
VectorCopy( client->ps.viewangles, oldAngles );
pm.car_spring = car_spring.value;
pm.car_shock_up = car_shock_up.value;
pm.car_shock_down = car_shock_down.value;
pm.car_swaybar = car_swaybar.value;
pm.car_wheel = car_wheel.value;
pm.car_wheel_damp = car_wheel_damp.value;
pm.car_frontweight_dist = car_frontweight_dist.value;
pm.car_IT_xScale = car_IT_xScale.value;
pm.car_IT_yScale = car_IT_yScale.value;
pm.car_IT_zScale = car_IT_zScale.value;
pm.car_body_elasticity = car_body_elasticity.value;
pm.car_air_cof = car_air_cof.value;
pm.car_air_frac_to_df = car_air_frac_to_df.value;
pm.car_friction_scale = car_friction_scale.value;
// END
VectorCopy( client->ps.origin, client->oldOrigin );
#ifdef MISSIONPACK
if (level.intermissionQueued != 0 && g_singlePlayer.integer) {
if ( level.time - level.intermissionQueued >= 1000 ) {
pm.cmd.buttons = 0;
pm.cmd.forwardmove = 0;
pm.cmd.rightmove = 0;
pm.cmd.upmove = 0;
if ( level.time - level.intermissionQueued >= 2000 && level.time - level.intermissionQueued <= 2500 ) {
trap_SendConsoleCommand( EXEC_APPEND, "centerview\n");
}
ent->client->ps.pm_type = PM_SPINTERMISSION;
}
}
Pmove (&pm);
#else
Pmove (&pm);
#endif
// STONELANCE
AnglesSubtract( client->ps.viewangles, oldAngles, ent->s.apos.trDelta );
VectorScale( ent->s.apos.trDelta, 1000.0f / ( client->ps.commandTime - oldTime ), ent->s.apos.trDelta );
if (level.time > 2000){
client->frameNum++;
client->pmoveTime = ((client->frameNum - 1) / (float)client->frameNum) * client->pmoveTime + (trap_Milliseconds() - start) / (float)client->frameNum;
}
// Com_Printf("Average pmoveTime %f, Instantanious pmoveTime %d, n=%d\n", client->pmoveTime, (trap_Milliseconds() - start), client->frameNum);
AngleVectors(client->ps.viewangles, forward, NULL, NULL);
if ( ent->frontBounds ){
VectorMA( client->ps.origin, (CAR_LENGTH - CAR_WIDTH) / 2, forward, origin );
G_SetOrigin( ent->frontBounds, origin );
trap_LinkEntity ( ent->frontBounds );
}
if ( ent->rearBounds ){
VectorMA( client->ps.origin, -(CAR_LENGTH - CAR_WIDTH) / 2, forward, origin );
G_SetOrigin( ent->rearBounds, origin );
trap_LinkEntity ( ent->rearBounds );
}
if (ent->pDebug > 0){
Com_LogPrintf("Target\n");
G_DebugDynamics(&pm.car->tBody, pm.car->tPoints, ent->pDebug-1);
}
for (i = 0; i < FIRST_FRAME_POINT; i++){
if ( client->carPoints[i] ){
// client->carPoints[i]->s.modelindex = G_ModelIndex( "models/test/sphere01.md3" );
// G_SetOrigin(client->carPoints[i], client->car.sPoints[i].r);
ent->s.pos.trType = TR_LINEAR;
ent->s.pos.trTime = level.time;
client->carPoints[i]->s.otherEntityNum = ent->s.clientNum;
client->carPoints[i]->s.otherEntityNum2 = i;
client->carPoints[i]->s.apos.trDelta[0] = client->car.sPoints[i].w;
client->carPoints[i]->s.apos.trDelta[1] = client->car.wheelAngle;
client->carPoints[i]->s.frame = client->car.sPoints[i].slipping;
VectorCopy(client->car.sPoints[i].r, client->carPoints[i]->s.pos.trBase);
VectorCopy(client->car.sPoints[i].r, ent->r.currentOrigin);
VectorCopy(client->car.sPoints[i].v, client->carPoints[i]->s.pos.trDelta);
VectorCopy(client->car.sPoints[i].normals[0], client->carPoints[i]->s.origin2);
client->carPoints[i]->s.groundEntityNum = client->car.sPoints[i].onGround;
// FIXME: remove this for bandwidth
// client->carPoints[i]->r.svFlags |= SVF_BROADCAST;
// send to owner only and ignore PVS ( SVF_BROADCAST )
client->carPoints[i]->r.svFlags |= SVF_SINGLECLIENT | SVF_BROADCAST;
client->carPoints[i]->r.singleClient = ent->s.number;
trap_LinkEntity(client->carPoints[i]);
}
else
Com_Printf("Warning: no carpoint entities\n");
}
/*
if (ent->pDebug == -1){
Com_LogPrintf("server time %d\n", pm.ps->commandTime);
Com_LogPrintf("wheelAngle %f\n", client->car.wheelAngle);
Com_LogPrintf("throttle %f\n", client->car.throttle);
Com_LogPrintf("gear %d\n", client->car.gear);
Com_LogPrintf("rpm %f\n", client->car.rpm);
// Body
Com_LogPrintf("r %f, %f, %f\n", client->car.sBody.r[0], client->car.sBody.r[1], client->car.sBody.r[2]);
Com_LogPrintf("v %f, %f, %f\n", client->car.sBody.v[0], client->car.sBody.v[1], client->car.sBody.v[2]);
Com_LogPrintf("L %f, %f, %f\n", client->car.sBody.L[0], client->car.sBody.L[1], client->car.sBody.L[2]);
Com_LogPrintf("viewangles: %f, %f, %f\n", pm.ps->viewangles[0], pm.ps->viewangles[1], pm.ps->viewangles[2]);
// Point
for (i = 0; i < 4; i++){
Com_LogPrintf("wheel %d\n", i);
Com_LogPrintf("r %f, %f, %f\n", client->car.sPoints[i].r[0], client->car.sPoints[i].r[1], client->car.sPoints[i].r[2]);
Com_LogPrintf("v %f, %f, %f\n", client->car.sPoints[i].v[0], client->car.sPoints[i].v[1], client->car.sPoints[i].v[2]);
Com_LogPrintf("w %f\n", client->car.sPoints[i].w);
Com_LogPrintf("normals %f, %f, %f\n", client->car.sPoints[i].normals[0][0], client->car.sPoints[i].normals[0][1], client->car.sPoints[i].normals[0][2]);
Com_LogPrintf("fluidDensity %f\n", client->car.sPoints[i].fluidDensity);
Com_LogPrintf("onGround %d\n", client->car.sPoints[i].onGround);
Com_LogPrintf("slipping %d\n", client->car.sPoints[i].slipping);
}
ent->pDebug = 0;
}
*/
/* Car
Com_Printf("springStrength %f\n", client->car.springStrength);
Com_Printf("springMaxLength %f\n", client->car.springMaxLength);
Com_Printf("springMinLength %f\n", client->car.springMinLength);
Com_Printf("shockStrength %f\n", client->car.shockStrength);
Com_Printf("wheelAngle %f\n", client->car.wheelAngle);
Com_Printf("throttle %f\n", client->car.throttle);
Com_Printf("gear %d\n", client->car.gear);
Com_Printf("rpm %f\n", client->car.rpm);
Com_Printf("aCOF %f\n", client->car.aCOF);
Com_Printf("sCOF %f\n", client->car.sCOF);
Com_Printf("kCOF %f\n", client->car.kCOF);
Com_Printf("dfCOF %f\n", client->car.dfCOF);
Com_Printf("ewCOF %f\n", client->car.ewCOF);
Com_Printf("inverseBodyInertiaTensor:\n");
Com_Printf("%f, %f, %f\n", client->car.inverseBodyInertiaTensor[0][0], client->car.inverseBodyInertiaTensor[0][1], client->car.inverseBodyInertiaTensor[0][2]);
Com_Printf("%f, %f, %f\n", client->car.inverseBodyInertiaTensor[1][0], client->car.inverseBodyInertiaTensor[1][1], client->car.inverseBodyInertiaTensor[1][2]);
Com_Printf("%f, %f, %f\n", client->car.inverseBodyInertiaTensor[2][0], client->car.inverseBodyInertiaTensor[2][1], client->car.inverseBodyInertiaTensor[2][2]);
*/
/* Body
Com_Printf("r %f, %f, %f\n", client->car.sBody.r[0], client->car.sBody.r[1], client->car.sBody.r[2]);
Com_Printf("v %f, %f, %f\n", client->car.sBody.v[0], client->car.sBody.v[1], client->car.sBody.v[2]);
Com_Printf("w %f, %f, %f\n", client->car.sBody.w[0], client->car.sBody.w[1], client->car.sBody.w[2]);
Com_Printf("L %f, %f, %f\n", client->car.sBody.L[0], client->car.sBody.L[1], client->car.sBody.L[2]);
Com_Printf("CoM %f, %f, %f\n", client->car.sBody.CoM[0], client->car.sBody.CoM[1], client->car.sBody.CoM[2]);
Com_Printf("t:\n");
Com_Printf("%f, %f, %f\n", client->car.sBody.t[0][0], client->car.sBody.t[0][1], client->car.sBody.t[0][2]);
Com_Printf("%f, %f, %f\n", client->car.sBody.t[1][0], client->car.sBody.t[1][1], client->car.sBody.t[1][2]);
Com_Printf("%f, %f, %f\n", client->car.sBody.t[2][0], client->car.sBody.t[2][1], client->car.sBody.t[2][2]);
*/
/* Point
i = 4;
Com_Printf("r %f, %f, %f\n", client->car.sPoints[i].r[0], client->car.sPoints[i].r[1], client->car.sPoints[i].r[2]);
Com_Printf("v %f, %f, %f\n", client->car.sPoints[i].v[0], client->car.sPoints[i].v[1], client->car.sPoints[i].v[2]);
Com_Printf("w %f\n", client->car.sPoints[i].w);
Com_Printf("netForce %f, %f, %f\n", client->car.sPoints[i].netForce[0], client->car.sPoints[i].netForce[1], client->car.sPoints[i].netForce[2]);
Com_Printf("netMoment %f\n", client->car.sPoints[i].netMoment);
Com_Printf("normals %f, %f, %f\n", client->car.sPoints[i].normals[0][0], client->car.sPoints[i].normals[0][1], client->car.sPoints[i].normals[0][2]);
Com_Printf("mass %f\n", client->car.sPoints[i].mass);
Com_Printf("elasticity %f\n", client->car.sPoints[i].elasticity);
Com_Printf("kcof %f\n", client->car.sPoints[i].kcof);
Com_Printf("scof %f\n", client->car.sPoints[i].scof);
Com_Printf("fluidDensity %f\n", client->car.sPoints[i].fluidDensity);
Com_Printf("onGround %d\n", client->car.sPoints[i].onGround);
Com_Printf("slipping %d\n", client->car.sPoints[i].slipping);
*/
// END
// save results of pmove
if ( ent->client->ps.eventSequence != oldEventSequence ) {
ent->eventTime = level.time;
}
if (g_smoothClients.integer) {
// STONELANCE
// BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue );
BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, trap_Cvar_VariableIntegerValue("sv_fps"), qtrue );
// BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, 2000, qtrue );
// END
}
else {
BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue );
}
SendPendingPredictableEvents( &ent->client->ps );
if ( !( ent->client->ps.eFlags & EF_FIRING ) ) {
client->fireHeld = qfalse; // for grapple
}
// use the snapped origin for linking so it matches client predicted versions
VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin );
VectorCopy (pm.mins, ent->r.mins);
VectorCopy (pm.maxs, ent->r.maxs);
ent->waterlevel = pm.waterlevel;
ent->watertype = pm.watertype;
// execute client events
ClientEvents( ent, oldEventSequence );
// STONELANCE - do damage from pmove
if (pm.damage.damage)
{
if( !(pm.damage.dflags & DAMAGE_NO_PROTECTION) )
pm.damage.damage *= g_damageScale.value;
if( pm.damage.damage > 0 )
{
if (pm.damage.otherEnt >= 0){
G_Damage(ent, NULL, &g_entities[pm.damage.otherEnt], pm.damage.dir, pm.damage.origin, pm.damage.damage, pm.damage.dflags, pm.damage.mod);
VectorInverse(pm.damage.dir);
G_Damage(&g_entities[pm.damage.otherEnt], NULL, ent, pm.damage.dir, pm.damage.origin, pm.damage.damage, pm.damage.dflags, pm.damage.mod);
}
else
G_Damage(ent, NULL, NULL, pm.damage.dir, pm.damage.origin, pm.damage.damage, pm.damage.dflags, pm.damage.mod);
}
}
// END
// link entity now, after any personal teleporters have been used
trap_LinkEntity (ent);
if ( !ent->client->noclip ) {
G_TouchTriggers( ent );
}
// NOTE: now copy the exact origin over otherwise clients can be snapped into solid
VectorCopy( ent->client->ps.origin, ent->r.currentOrigin );
//test for solid areas in the AAS file
BotTestAAS(ent->r.currentOrigin);
// touch other objects
ClientImpacts( ent, &pm );
// save results of triggers and client events
if (ent->client->ps.eventSequence != oldEventSequence) {
ent->eventTime = level.time;
}
// swap and latch button actions
client->oldbuttons = client->buttons;
client->buttons = ucmd->buttons;
client->latched_buttons |= client->buttons & ~client->oldbuttons;
// check for respawning
if ( client->ps.stats[STAT_HEALTH] <= 0 ) {
// wait for the attack button to be pressed
if ( level.time > client->respawnTime ) {
// forcerespawn is to prevent users from waiting out powerups
if ( g_forcerespawn.integer > 0 &&
( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) {
// STONELANCE
if (g_gametype.integer != GT_DERBY || !ent->client->finishRaceTime)
// END
ClientRespawn( ent );
return;
}
// pressing attack or use is the normal respawn method
if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) {
// STONELANCE
if (g_gametype.integer != GT_DERBY || !ent->client->finishRaceTime)
// END
ClientRespawn( ent );
}
}
return;
}
// perform once-a-second actions
ClientTimerActions( ent, msec );
// STONELANCE - UPDATE: enable this (use flags instead?)
/*
if ((client->ps.stats[STAT_HEALTH] < 25 && ent->prevHealth >= 25) ||
(client->ps.stats[STAT_HEALTH] < 50 && ent->prevHealth >= 50) ||
(client->ps.stats[STAT_HEALTH] >= 50 && ent->prevHealth < 50)){
BG_AddPredictableEventToPlayerstate( EV_ENGINE_SMOKE, client->ps.stats[STAT_HEALTH], &ent->client->ps );
}
ent->prevHealth = client->ps.stats[STAT_HEALTH];
*/
// END
}
/*
==================
ClientThink
A new command has arrived from the client
==================
*/
void ClientThink( int clientNum ) {
gentity_t *ent;
ent = g_entities + clientNum;
trap_GetUsercmd( clientNum, &ent->client->pers.cmd );
// mark the time we got info, so we can display the
// phone jack if they don't get any for a while
ent->client->lastCmdTime = level.time;
if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) {
ClientThink_real( ent );
}
}
void G_RunClient( gentity_t *ent ) {
if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) {
return;
}
ent->client->pers.cmd.serverTime = level.time;
ClientThink_real( ent );
}
/*
==================
SpectatorClientEndFrame
==================
*/
void SpectatorClientEndFrame( gentity_t *ent ) {
gclient_t *cl;
// STONELANCE
int i;
int savedDmgDealt;
int savedDmgTaken;
// Com_Printf( "Spectator Mode: %i\n", ent->client->sess.spectatorState );
// END
// if we are doing a chase cam or a remote view, grab the latest info
if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) {
int clientNum, flags;
clientNum = ent->client->sess.spectatorClient;
// team follow1 and team follow2 go to whatever clients are playing
if ( clientNum == -1 ) {
clientNum = level.follow1;
} else if ( clientNum == -2 ) {
clientNum = level.follow2;
}
if ( clientNum >= 0 ) {
cl = &level.clients[ clientNum ];
// STONELANCE
// if ( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR ) {
if ( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR
&& !isRaceObserver( clientNum )) {
// END
flags = (cl->ps.eFlags & ~(EF_VOTED | EF_TEAMVOTED)) | (ent->client->ps.eFlags & (EF_VOTED | EF_TEAMVOTED));
// STONELANCE - FIXME: dont overwrite stats if the player was in the race or derby
// ent->client->ps = cl->ps;
savedDmgDealt = ent->client->ps.stats[STAT_DAMAGE_DEALT];
savedDmgTaken = ent->client->ps.stats[STAT_DAMAGE_TAKEN];
memcpy( &ent->client->ps, &cl->ps, sizeof(playerState_t) );
ent->client->ps.stats[STAT_DAMAGE_DEALT] = savedDmgDealt;
ent->client->ps.stats[STAT_DAMAGE_TAKEN] = savedDmgTaken;
/*
VectorCopy( cl->ps.origin, ent->client->ps.origin );
VectorCopy( cl->ps.velocity, ent->client->ps.velocity );
VectorCopy( cl->ps.angularMomentum, ent->client->ps.angularMomentum );
VectorCopy( cl->ps.viewangles, ent->client->ps.viewangles );
ent->client->ps.commandTime = cl->ps.commandTime;
ent->client->ps.clientNum = cl->ps.clientNum;
ent->client->ps.pm_flags = cl->ps.pm_flags;
ent->client->ps.pm_type = cl->ps.pm_type;
ent->client->ps.damagePitch = cl->ps.damagePitch;
ent->client->ps.damageYaw = cl->ps.damageYaw;
ent->client->ps.delta_angles[0] = cl->ps.delta_angles[0];
ent->client->ps.delta_angles[1] = cl->ps.delta_angles[1];
ent->client->ps.delta_angles[2] = cl->ps.delta_angles[2];
ent->client->ps.legsAnim = cl->ps.legsAnim;
ent->client->ps.legsTimer = cl->ps.legsTimer;
ent->client->ps.torsoAnim = cl->ps.torsoAnim;
ent->client->ps.torsoTimer = cl->ps.torsoTimer;
*/
// END
ent->client->ps.pm_flags |= PMF_FOLLOW;
ent->client->ps.eFlags = flags;
// STONELANCE
// FIXME: could this fuck some shit up with entity numbers or anything like that?
// TODO: optimize this so it only copies the fields that change and not everything
for( i = 0; i < 4; i++ )
{
memcpy( ent->client->carPoints[i], cl->carPoints[i], sizeof(gentity_t) );
ent->client->carPoints[i]->r.singleClient = ent->s.number;
}
// END
return;
} else {
// drop them to free spectators unless they are dedicated camera followers
if ( ent->client->sess.spectatorClient >= 0 ) {
// STONELANCE
// try cycling to a new client
clientNum = ent->client->sess.spectatorClient;
Cmd_FollowCycle_f( ent, 1 );
// Com_Printf( "Cycle target\n" );
if ( clientNum == ent->client->sess.spectatorClient )
{
// Com_Printf( "Follow Cam: drop back to free\n" );
// G_DebugLogPrintf( "Follow Cam: drop back to free\n" );
// ent->client->sess.spectatorState = SPECTATOR_FREE;
// ClientBegin( ent->client - level.clients );
// ent->client->ps.clientNum = ent->client - level.clients;
StopFollowing( ent );
}
// END
}
}
}
}
// STONELANCE
else if ( ent->client->sess.spectatorState == SPECTATOR_OBSERVE )
{
int clientNum;
vec3_t angles, delta;
float dist;
clientNum = ent->client->sess.spectatorClient;
if ( clientNum == -1 ) {
clientNum = level.follow1;
} else if ( clientNum == -2 ) {
clientNum = level.follow2;
}
if ( clientNum >= 0 ) {
// ent->client->ps.clientNum = clientNum;
cl = &level.clients[ clientNum ];
if ( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR
&& !isRaceObserver( clientNum ) ) {
VectorClear(delta);
if ( !(ent->spotflags & OBSERVERCAM_FIXED) ){
VectorSubtract(cl->ps.origin, ent->client->ps.origin, delta);
vectoangles(delta, angles);
VectorCopy(angles, ent->client->ps.viewangles);
}
VectorClear(ent->client->ps.velocity);
if ( ent->spotflags & OBSERVERCAM_ZOOM ){
dist = VectorLength(delta);
// zoom in closer to target if too far away
if (dist > 300)
{
dist -= 300;
VectorSet(ent->client->ps.velocity, dist, 0, 0);
}
}
ent->client->ps.damagePitch = ANGLE2SHORT(ent->client->ps.viewangles[PITCH]);
ent->client->ps.damageYaw = ANGLE2SHORT(ent->client->ps.viewangles[YAW]);
ent->client->ps.pm_flags |= PMF_OBSERVE;
}
else {
// drop them to free spectators unless they are dedicated camera followers
if ( ent->client->sess.spectatorClient >= 0 ) {
// try cycling to a new client
clientNum = ent->client->sess.spectatorClient;
Cmd_FollowCycle_f( ent, 1 );
// Com_Printf( "Cycle target\n" );
if ( clientNum == ent->client->sess.spectatorClient )
{
// Com_Printf( "Observer Cam: drop back to free\n" );
// G_DebugLogPrintf( "Observer Cam: drop back to free\n" );
// ent->client->sess.spectatorState = SPECTATOR_FREE;
// ClientBegin( ent->client - level.clients );
// ent->client->ps.clientNum = ent->client - level.clients;
StopFollowing( ent );
}
}
}
}
// else
// G_DebugLogPrintf( "Observer Cam: Target client %i\n", clientNum );
}
// END
if ( ent->client->sess.spectatorState == SPECTATOR_SCOREBOARD ) {
ent->client->ps.pm_flags |= PMF_SCOREBOARD;
} else {
ent->client->ps.pm_flags &= ~PMF_SCOREBOARD;
}
}
/*
==============
ClientEndFrame
Called at the end of each server frame for each connected client
A fast client will have multiple ClientThink for each ClientEdFrame,
while a slow client may have multiple ClientEndFrame between ClientThink.
==============
*/
void ClientEndFrame( gentity_t *ent ) {
int i;
// STONELANCE
// if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR || isRaceObserver( ent->s.number ) ){
// END
SpectatorClientEndFrame( ent );
return;
}
// turn off any expired powerups
for ( i = 0 ; i < MAX_POWERUPS ; i++ ) {
// STONELANCE
if ( i == PW_TURBO && ent->client->ps.powerups[ i ] < level.time &&
ent->client->ps.powerups[ i ] > 0) {
ent->client->ps.powerups[ i ] = 0;
}
else if ( i != PW_TURBO && ent->client->ps.powerups[ i ] < level.time ) {
// if ( ent->client->ps.powerups[ i ] < level.time ) {
// END
ent->client->ps.powerups[ i ] = 0;
}
}
#ifdef MISSIONPACK
// set powerup for player animation
if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) {
ent->client->ps.powerups[PW_GUARD] = level.time;
}
if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) {
ent->client->ps.powerups[PW_SCOUT] = level.time;
}
if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_DOUBLER ) {
ent->client->ps.powerups[PW_DOUBLER] = level.time;
}
if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) {
ent->client->ps.powerups[PW_AMMOREGEN] = level.time;
}
if ( ent->client->invulnerabilityTime > level.time ) {
ent->client->ps.powerups[PW_INVULNERABILITY] = level.time;
}
#endif
// save network bandwidth
#if 0
if ( !g_synchronousClients->integer && ent->client->ps.pm_type == PM_NORMAL ) {
// FIXME: this must change eventually for non-sync demo recording
VectorClear( ent->client->ps.viewangles );
}
#endif
//
// If the end of unit layout is displayed, don't give
// the player any normal movement attributes
//
if ( level.intermissiontime ) {
return;
}
// burn from lava, etc
P_WorldEffects (ent);
// apply all the damage taken this frame
P_DamageFeedback (ent);
// add the EF_CONNECTION flag if we haven't gotten commands recently
if ( level.time - ent->client->lastCmdTime > 1000 ) {
ent->client->ps.eFlags |= EF_CONNECTION;
} else {
ent->client->ps.eFlags &= ~EF_CONNECTION;
}
ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health...
G_SetClientSound (ent);
// set the latest infor
if (g_smoothClients.integer) {
// STONELANCE
// BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue );
BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, trap_Cvar_VariableIntegerValue("sv_fps"), qtrue );
// BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, 2000, qtrue );
// Com_Printf( "Extrapolating playerstate, trType %i\n", ent->s.pos.trType );
// END
}
else {
BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue );
}
SendPendingPredictableEvents( &ent->client->ps );
/*
if( g_entities[0].client )
{
int rearTime;
g_entities[0].client->ps.weaponTime |= 0x7fff;
rearTime = ( g_entities[0].client->ps.weaponTime & REAR_WEAPON_TIME_MASK ) >> 16;
Com_Printf( "g forward: cur weapon time %i\n", g_entities[0].client->ps.weaponTime & NORMAL_WEAPON_TIME_MASK );
Com_Printf( "g rear: cur weapon time %i\n", rearTime );
Com_Printf( "g: cur weapon time %i\n", g_entities[0].client->ps.weaponTime );
}
*/
// set the bit for the reachability area the client is currently in
// i = trap_AAS_PointReachabilityAreaIndex( ent->client->ps.origin );
// ent->client->areabits[i >> 3] |= 1 << (i & 7);
}