reaction/code/game/g_active.c
2013-01-04 13:27:22 +00:00

1558 lines
45 KiB
C

//-----------------------------------------------------------------------------
//
// $Id$
//
//-----------------------------------------------------------------------------
//
// $Log$
// Revision 1.99 2003/04/19 15:27:30 jbravo
// Backing out of most of unlagged. Only optimized prediction and smooth clients
// remains.
//
// Revision 1.98 2003/03/22 20:29:26 jbravo
// wrapping linkent and unlinkent calls
//
// Revision 1.97 2003/03/10 07:07:58 jbravo
// Small unlagged fixes and voting delay added.
//
// Revision 1.96 2003/03/09 21:30:38 jbravo
// Adding unlagged. Still needs work.
//
// Revision 1.95 2002/11/13 00:50:38 jbravo
// Fixed item dropping, specmode selection on death and helmet probs.
//
// Revision 1.94 2002/10/30 21:24:47 jbravo
// Minor helmet tweaking
//
// Revision 1.93 2002/10/30 20:04:34 jbravo
// Adding helmet
//
// Revision 1.92 2002/10/26 22:03:43 jbravo
// Made TeamDM work RQ3 style.
//
// Revision 1.91 2002/10/26 00:37:18 jbravo
// New multiple item code and added PB support to the UI
//
// Revision 1.90 2002/09/29 16:06:44 jbravo
// Work done at the HPWorld expo
//
// Revision 1.89 2002/09/24 05:06:16 blaze
// fixed spectating so ref\'s can now use all the chasecam modes.
//
// Revision 1.88 2002/08/30 01:09:06 jbravo
// Semi fixed the bodies thing in CTB
//
// Revision 1.87 2002/08/21 07:00:07 jbravo
// Added CTB respawn queue and fixed game <-> cgame synch problem in CTB
//
// Revision 1.86 2002/07/24 02:17:38 jbravo
// Added a respawn delay for CTB
//
// Revision 1.85 2002/07/22 06:33:04 niceass
// cleaned up the powerup code
//
// Revision 1.84 2002/07/21 18:48:06 niceass
// weapon prediction stuff
//
// Revision 1.83 2002/07/07 18:36:13 jbravo
// Added an AntiIdle system. Can play insane sounds for idle players, drop them
// from teams or kick them. Upped version to Beta 2.1
//
// Revision 1.82 2002/07/02 19:15:17 jbravo
// Drop weapon with akimbos now behaves like AQ, plus no suicides during LCA
//
// Revision 1.81 2002/06/23 19:27:52 niceass
// bandage bug fix
//
// Revision 1.80 2002/06/19 18:13:57 jbravo
// New TNG spawning system :)
//
// Revision 1.79 2002/06/18 06:15:30 niceass
// m4 kick now smooth
//
// Revision 1.78 2002/06/17 00:27:29 jbravo
// Cleanup
//
// Revision 1.77 2002/06/16 20:06:14 jbravo
// Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap"
//
// Revision 1.76 2002/06/16 17:38:00 jbravo
// Removed the MISSIONPACK ifdefs and missionpack only code.
//
// Revision 1.75 2002/06/05 15:17:51 jbravo
// Gibbed players now vanish (gibs with them tho :() and suicide is no
// longer -2 frags. Added Obit handling for telefrags and better handling
// for unhandled mod's
//
// Revision 1.74 2002/06/03 05:25:37 niceass
// spectator changes
//
// Revision 1.73 2002/05/21 04:58:27 blaze
// kicked the reload bugs ass!
//
// Revision 1.72 2002/05/06 21:41:01 slicer
// Added rq3_cmd
//
// Revision 1.71 2002/04/13 15:37:53 jbravo
// limchasecam has been redone with new spec system
//
// Revision 1.70 2002/04/03 15:29:24 jbravo
// Those __ZCAM__ ifdefs keep creaping back in :)
//
// Revision 1.69 2002/04/03 03:13:16 blaze
// NEW BREAKABLE CODE - will break all old breakables(wont appear in maps)
//
// Revision 1.68 2002/04/02 20:23:12 jbravo
// Bots dont get to use any specmode other than FREE and the recive radio cmds
// as text and not sounds.
//
// Revision 1.67 2002/04/01 22:23:14 slicer
// Added "weapon" command buffering | Solved Gren Mode Bug
//
// Revision 1.66 2002/03/30 21:51:42 jbravo
// Removed all those ifdefs for zcam.
//
// Revision 1.65 2002/03/30 17:37:48 jbravo
// Added damage tracking to the server. Added zcam flic mode. cleaned up g_damage.
//
// Revision 1.64 2002/03/30 02:54:24 jbravo
// MOre spec tweaks and a scoreboard fix
//
// Revision 1.63 2002/03/30 02:29:43 jbravo
// Lots of spectator code updates. Removed debugshit, added some color.
//
// Revision 1.62 2002/03/26 11:32:04 jbravo
// Remember specstate between rounds.
//
// Revision 1.61 2002/03/23 21:29:42 jbravo
// I finally fixed snipers spawning with pistol up. g_RQ3_sniperup has been
// reinstated.
//
// Revision 1.60 2002/03/23 05:17:42 jbravo
// Major cleanup of game -> cgame communication with LCA vars.
//
// Revision 1.59 2002/03/18 19:18:39 slicer
// Fixed bandage bugs ( i hope )
//
// Revision 1.58 2002/03/02 12:24:30 jbravo
// Removed some debugging messages
//
// Revision 1.57 2002/02/27 01:54:29 jbravo
// More spectatorfixes and finally stopped all fallingdamage anims and
// sounds during LCA.
//
// Revision 1.56 2002/02/26 02:58:47 jbravo
// Fixing the spectator_free mode not being predicted in the client.
//
// Revision 1.55 2002/02/25 19:41:53 jbravo
// Fixed the use ESC and join menu to join teams when dead players are
// spectating in TP mode.
// Tuned the autorespawn system a bit. Now dead ppl. are dead for a very
// small time before they are made into spectators.
//
// Revision 1.54 2002/02/23 16:55:09 jbravo
// Added debugging to help find what was going with can't find item for weapon
// error that crash the server.
//
// Revision 1.53 2002/02/22 02:13:13 jbravo
// Fixed a few bugs and did some cleanups
//
// Revision 1.52 2002/02/10 21:21:22 slicer
// Saving persistant and other data on some events..
//
// Revision 1.51 2002/02/10 18:38:42 jbravo
// Added new SPECTATOR_ZCAM spec mode.
//
// Revision 1.50 2002/02/10 17:20:45 jbravo
// Attempting to fix the camera freeze in the begining of the game
//
// Revision 1.49 2002/02/09 18:27:44 slicer
// Spectator code: +attack button to change view, and jump to switch player
//
// Revision 1.48 2002/02/09 00:10:12 jbravo
// Fixed spectator follow and free and updated zcam to 1.04 and added the
// missing zcam files.
//
// Revision 1.47 2002/02/08 18:59:01 slicer
// Spec code changes
//
// Revision 1.46 2002/02/06 12:06:48 slicer
// TP Scores bug fix
//
// Revision 1.45 2002/02/06 03:10:43 jbravo
// Fix the instant spectate on death and an attempt to fix the scores
//
// Revision 1.44 2002/01/29 03:13:45 jbravo
// Further fixes to antistick
//
// Revision 1.43 2002/01/27 13:33:28 jbravo
// Teamplay antistick system.
//
// Revision 1.42 2002/01/11 20:20:58 jbravo
// Adding TP to main branch
//
// Revision 1.41 2002/01/11 19:48:30 jbravo
// Formatted the source in non DOS format.
//
// Revision 1.40 2001/12/31 16:28:42 jbravo
// I made a Booboo with the Log tag.
//
//
//-----------------------------------------------------------------------------
// Copyright (C) 1999-2000 Id Software, Inc.
//
#include "g_local.h"
#include "zcam.h"
//Elder: moved kick to g_weapon.c where it belongs
/*
===============
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, side;
vec3_t angles, v;
vec3_t forward, right, up;
client = player->client;
if (client->ps.pm_type == PM_DEAD) {
return;
}
// total points of damage shot at the player this frame
count = client->damage_blood + client->damage_armor;
if (count == 0) {
return; // didn't take any damage
}
if (count > 255) {
count = 255;
}
// send the information to the client
// world damage (falling, slime, etc) uses a special code
// to make the blend blob centered instead of positional
if (client->damage_fromWorld) {
client->ps.damagePitch = 255;
client->ps.damageYaw = 255;
client->damage_fromWorld = qfalse;
} else {
vectoangles(client->damage_from, angles);
// new RQ3 view-kick code, needs more tweaking (the 50 needs to be replaces with that below calcuation
// from the AQ2 code.
// set aiming directions
AngleVectors(client->ps.viewangles, forward, right, up);
VectorSubtract(client->damage_from, player->s.origin, v);
VectorNormalize(v);
side = -DotProduct(v, forward);
client->ps.damagePitch = 50 * side * 0.3;
side = -DotProduct(v, right);
client->ps.damageYaw = 50 * side * 0.3;
}
// play an appropriate pain sound
if ((level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE)) {
//Elder: reduced pain debounce time so we can have a few more sounds :)
player->pain_debounce_time = level.time + 250;
switch (client->lasthurt_location & ~(LOCATION_BACK | LOCATION_LEFT | LOCATION_RIGHT | LOCATION_FRONT)) {
//Elder: headshot sound
case LOCATION_HEAD:
case LOCATION_FACE:
if (client->lasthurt_mod == MOD_KNIFE || client->lasthurt_mod == MOD_KNIFE_THROWN)
G_AddEvent(player, EV_RQ3_SOUND, RQ3_SOUND_KNIFEDEATH);
break;
default:
G_AddEvent(player, EV_PAIN, player->health);
break;
}
client->ps.damageEvent++;
}
client->ps.damageCount = count;
// Elder: HC Smoke
if (client->lasthurt_mod == MOD_HANDCANNON &&
client->damage_blood >= 120 && client->damage_knockback >= RQ3_HANDCANNON_KICK * 6) {
client->ps.eFlags |= EF_HANDCANNON_SMOKED;
}
//
// 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)
{
int waterlevel;
if (ent->client->noclip) {
ent->client->airOutTime = level.time + 12000; // don't need air
return;
}
waterlevel = ent->waterlevel;
//
// check for drowning
//
if (waterlevel == 3) {
// if out of air, start drowning
if (ent->client->airOutTime < level.time) {
// drown!
ent->client->airOutTime += 1000;
if (ent->health > 0) {
// take more damage the longer underwater
ent->damage += 2;
if (ent->damage > 15)
ent->damage = 15;
// play a gurp sound instead of a normal pain sound
if (ent->health <= ent->damage) {
G_Sound(ent, CHAN_VOICE, G_SoundIndex("*drown.wav"));
} else if (rand() & 1) {
G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp1.wav"));
} else {
G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp2.wav"));
}
// 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 {
ent->client->airOutTime = level.time + 12000;
ent->damage = 2;
}
//
// check for sizzle damage (move to pmove?)
//
if (waterlevel && (ent->watertype & (CONTENTS_LAVA | CONTENTS_SLIME))) {
// Elder: changed around a bit -- using timestamp variable
if (ent->health > 0 && level.time > ent->timestamp) {
if (ent->watertype & CONTENTS_LAVA) {
G_Damage(ent, NULL, NULL, NULL, NULL, 3 * waterlevel, 0, MOD_LAVA);
}
if (ent->watertype & CONTENTS_SLIME) {
G_Damage(ent, NULL, NULL, NULL, NULL, waterlevel, 0, MOD_SLIME);
}
// Elder: added
ent->timestamp = level.time + FRAMETIME;
}
}
}
/*
===============
G_SetClientSound
===============
*/
void G_SetClientSound(gentity_t * ent)
{
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
}
//Blaze: Print out some debug info
if (&g_entities[pm->touchents[i]] == NULL) G_Printf("Ln 0377\n");
other = &g_entities[pm->touchents[i]];
if ((ent->r.svFlags & SVF_BOT) && (ent->touch)) {
ent->touch(ent, other, &trace);
}
if (!other->touch) {
continue;
}
other->touch(other, ent, &trace);
}
}
/*
============
G_TouchTriggers
Find all trigger entities that ent's current position touches.
Spectators will only interact with teleporters (and door triggers for spectators)
============
*/
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);
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++) {
//Blaze: Print out some debug info
if (&g_entities[touch[i]] == NULL) G_Printf("Ln 0429\n");
hit = &g_entities[touch[i]];
if (!hit->touch && !ent->touch) {
continue;
}
if (!(hit->r.contents & CONTENTS_TRIGGER)) {
continue;
}
// ignore most entities if a spectator
if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) {
if (hit->s.eType != ET_TELEPORT_TRIGGER) { //&&
// this is ugly but adding a new ET_? type will
// most likely cause network incompatibilities
// NiceAss: changed Touch_DoorTrigger to Touch_DoorTriggerSpectator
// hit->touch != Touch_DoorTriggerSpectator) {
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;
}
if (ent->client->openDoor == 2) {
ent->client->openDoor = qfalse;
ent->client->openDoorTime = 0;
}
}
/*
=================
SpectatorThink
NiceAss: Heavy modifications will be here for AQ2-like spectator mode and zcam!?
=================
*/
void SpectatorThink(gentity_t * ent, usercmd_t * ucmd)
{
pmove_t pm;
gclient_t *client;
int clientNum;
client = ent->client;
clientNum = client - level.clients;
if (client->sess.spectatorState == SPECTATOR_ZCAM) {
client->ps.commandTime = ucmd->serverTime;
camera_think(ent);
}
if (client->sess.spectatorState == SPECTATOR_FREE) {
if (g_gametype.integer == GT_CTF && level.team_round_going &&
(client->sess.savedTeam == TEAM_RED || client->sess.savedTeam == TEAM_BLUE))
client->ps.pm_type = PM_FREEZE;
else
client->ps.pm_type = PM_SPECTATOR;
client->ps.speed = 400; // faster than normal
// set up for pmove
memset(&pm, 0, sizeof(pm));
pm.ps = &client->ps;
pm.cmd = *ucmd;
pm.tracemask = 0; // spectators can fly through bodies
pm.trace = trap_Trace;
pm.pointcontents = trap_PointContents;
pm.predict = qtrue;
// perform a pmove
Pmove(&pm);
// save results of pmove
VectorCopy(client->ps.origin, ent->s.origin);
G_TouchTriggers(ent);
trap_UnlinkEntity(ent);
}
// JBravo: Lets not allow bots to use any specmode other than FREE
if (ent->r.svFlags & SVF_BOT)
return;
//Slicer - Changing this for aq2 way
// Jump button cycles throught spectators
if (client->sess.spectatorState == SPECTATOR_FOLLOW || client->sess.spectatorState == SPECTATOR_ZCAM) {
if (ucmd->upmove >= 10) {
if (!(client->ps.pm_flags & PMF_JUMP_HELD)) {
client->ps.pm_flags |= PMF_JUMP_HELD;
if (client->sess.spectatorState == SPECTATOR_ZCAM) {
if (client->camera->mode == CAMERA_MODE_SWING)
CameraSwingCycle(ent, 1);
} else
Cmd_FollowCycle_f(ent, 1);
}
} else
client->ps.pm_flags &= ~PMF_JUMP_HELD;
}
client->oldbuttons = client->buttons;
client->buttons = ucmd->buttons;
if (g_gametype.integer == GT_CTF && client->sess.spectatorState == SPECTATOR_FREE &&
(client->sess.savedTeam == TEAM_RED || client->sess.savedTeam == TEAM_BLUE))
return;
// Attack Button cycles throught free view, follow or zcam
if ((ucmd->buttons & BUTTON_ATTACK) && !(client->oldbuttons & BUTTON_ATTACK)) {
if (g_gametype.integer == GT_TEAMPLAY && g_RQ3_limchasecam.integer != 0 && client->sess.referee == 0 ) {
if (!OKtoFollow(clientNum))
return;
if (client->sess.spectatorState != SPECTATOR_FOLLOW) {
client->sess.spectatorState = SPECTATOR_FOLLOW;
client->specMode = SPECTATOR_FOLLOW;
client->ps.pm_flags |= PMF_FOLLOW;
client->ps.stats[STAT_RQ3] &= ~RQ3_ZCAM;
}
return;
} else if (client->sess.spectatorState == SPECTATOR_FREE && OKtoFollow(clientNum)) {
client->sess.spectatorState = SPECTATOR_ZCAM;
client->specMode = SPECTATOR_ZCAM;
client->camera->mode = CAMERA_MODE_SWING;
client->ps.stats[STAT_RQ3] |= RQ3_ZCAM;
client->ps.pm_flags &= ~PMF_FOLLOW;
CameraSwingCycle(ent, 1);
RQ3_SpectatorMode(ent);
} else if (client->sess.spectatorState == SPECTATOR_ZCAM && client->camera->mode == CAMERA_MODE_SWING
&& OKtoFollow(clientNum)) {
client->sess.spectatorState = SPECTATOR_ZCAM;
client->specMode = SPECTATOR_ZCAM;
client->camera->mode = CAMERA_MODE_FLIC;
client->ps.stats[STAT_RQ3] |= RQ3_ZCAM;
client->ps.pm_flags &= ~PMF_FOLLOW;
CameraFlicBegin(ent);
RQ3_SpectatorMode(ent);
} else if (client->sess.spectatorState == SPECTATOR_ZCAM && OKtoFollow(clientNum)) {
client->sess.spectatorState = SPECTATOR_FOLLOW;
client->specMode = SPECTATOR_FOLLOW;
client->ps.pm_flags |= PMF_FOLLOW;
client->ps.stats[STAT_RQ3] &= ~RQ3_ZCAM;
Cmd_FollowCycle_f(ent, 1);
RQ3_SpectatorMode(ent);
} else if (client->sess.spectatorState == SPECTATOR_FOLLOW) {
client->sess.spectatorState = SPECTATOR_FREE;
client->specMode = SPECTATOR_FREE;
client->ps.pm_flags &= ~PMF_FOLLOW;
client->ps.stats[STAT_RQ3] &= ~RQ3_ZCAM;
StopFollowing(ent);
RQ3_SpectatorMode(ent);
}
}
}
/*
=================
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;
client = ent->client;
client->timeResidual += msec;
while (client->timeResidual >= 1000) {
client->timeResidual -= 1000;
// count down health when over max
if (ent->health > 100) { //max is 100 client->ps.stats[STAT_MAX_HEALTH] ) {
ent->health--;
}
// count down armor when over max
if (client->ps.stats[STAT_ARMOR] > 100) { // max is 100 client->ps.stats[STAT_MAX_HEALTH] ) {
client->ps.stats[STAT_ARMOR]--;
}
//Blaze: Do bandaging stuff
if (ent->client->bleedtick > 1) {
//G_Printf("Bleedtick (%d) getting lowered by one (%d)\n", ent->client->bleedtick, client->timeResidual);
ent->client->bleedtick--;
} else if (ent->client->bleedtick == 1) {
ent->client->bleed_remain = 0;
ent->client->bleeding = 0;
ent->client->bleedtick = 0;
//Elder: moved from somewhere - err, g_cmds.c I think
ent->client->ps.stats[STAT_RQ3] &= ~RQ3_LEGDAMAGE;
// NiceAss: clear last player to hit you.
ent->client->lasthurt_mod = 0;
}
}
}
/*
====================
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 dir;
vec3_t origin, angles;
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;
}
// JBravo: fix falling pain during lca
if (g_gametype.integer >= GT_TEAM && level.lights_camera_action) {
break;
}
damage = ent->client->ps.stats[STAT_FALLDAMAGE];
// VectorSet(dir, 0, 0, 1);
ent->pain_debounce_time = level.time + 200; // no normal pain sound
//Elder: added so we can trigger AQ2 pain blends
ent->client->ps.damageEvent++;
ent->client->ps.damageCount += damage;
if (ent->client->lasthurt_mod != 0) {
//Blaze: Print out some debug info
if (&g_entities[ent->client->lasthurt_client] == NULL) G_Printf("Ln 0748\n");
G_Damage(ent, &g_entities[ent->client->lasthurt_client],
&g_entities[ent->client->lasthurt_client], NULL, NULL, damage, 0, MOD_FALLING);
} else {
G_Damage(ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING);
}
break;
case EV_FALL_FAR_NOSOUND:
if (ent->s.eType != ET_PLAYER) {
break; // not in the player model
}
if (g_dmflags.integer & DF_NO_FALLING) {
break;
}
// JBravo: fix falling pain during lca again
if (g_gametype.integer >= GT_TEAM && level.lights_camera_action) {
break;
}
damage = ent->client->ps.stats[STAT_FALLDAMAGE];
// VectorSet(dir, 0, 0, 1);
ent->pain_debounce_time = level.time + 200; // no normal pain sound
//Elder: added so we can trigger AQ2 pain blends
ent->client->ps.damageEvent++;
ent->client->ps.damageCount += damage;
if (ent->client->lasthurt_mod != 0) {
//Blaze: Print out some debug info
if (&g_entities[ent->client->lasthurt_client] == NULL) G_Printf("Ln 0779\n");
G_Damage(ent, &g_entities[ent->client->lasthurt_client],
&g_entities[ent->client->lasthurt_client], NULL, NULL, damage, 0, MOD_FALLING);
} else {
G_Damage(ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING);
}
break;
case EV_FIRE_WEAPON:
FireWeapon(ent);
break;
case EV_RELOAD_WEAPON1:
ReloadWeapon(ent, 1);
break;
case EV_RELOAD_WEAPON2:
ReloadWeapon(ent, 2);
break;
case EV_CHANGE_WEAPON:
//Elder: not a good place to put stuff
break;
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;
}
SelectSpawnPoint(ent->client->ps.origin, origin, angles);
TeleportPlayer(ent, origin, angles);
break;
case EV_USE_ITEM2: // medkit
ent->health = 125; //ent->client->ps.stats[STAT_MAX_HEALTH] + 25;
break;
default:
break;
}
}
}
void BotTestSolid(vec3_t origin);
/*
=============
ThrowWeapon
XRAY FMJ
Returns the number of the weapon thrown
=============
*/
int ThrowWeapon(gentity_t * ent, qboolean forceThrow)
{
gclient_t *client;
usercmd_t *ucmd;
gitem_t *xr_item;
gentity_t *xr_drop;
int weap;
client = ent->client;
ucmd = &ent->client->pers.cmd;
if (!forceThrow)
if ((ucmd->buttons & BUTTON_ATTACK) || client->ps.weaponTime > 0)
return 0;
//Elder: remove zoom bits
Cmd_Unzoom(ent);
// JBravo: simulate AQ drop weapon for akimbo with no special weap
if (client->ps.weapon == WP_AKIMBO && client->uniqueWeapons == 0) {
trap_SendServerCommand(ent - g_entities, va("stuff weapon %i\n", WP_PISTOL));
return 0;
}
weap = 0;
if (client->uniqueWeapons > 0) {
if (client->ps.weapon == WP_AKIMBO || client->ps.weapon == WP_PISTOL || client->ps.weapon == WP_GRENADE || client->ps.weapon == WP_KNIFE || client->ps.weapon == WP_NONE) // shouldn't have to worry about NONE, but just in case
{
weap = client->ps.stats[STAT_WEAPONS];
if ((client->ps.stats[STAT_WEAPONS] & (1 << WP_M4)) == (1 << WP_M4))
weap = WP_M4;
if ((client->ps.stats[STAT_WEAPONS] & (1 << WP_M3)) == (1 << WP_M3))
weap = WP_M3;
if ((client->ps.stats[STAT_WEAPONS] & (1 << WP_MP5)) == (1 << WP_MP5))
weap = WP_MP5;
if ((client->ps.stats[STAT_WEAPONS] & (1 << WP_HANDCANNON)) == (1 << WP_HANDCANNON))
weap = WP_HANDCANNON;
if ((client->ps.stats[STAT_WEAPONS] & (1 << WP_SSG3000)) == (1 << WP_SSG3000))
weap = WP_SSG3000;
if (weap == 0)
return 0;
} else {
weap = client->ps.weapon;
}
xr_item = BG_FindItemForWeapon(weap);
client->pers.hadUniqueWeapon[weap] = qtrue;
//Elder: for immediate weapon drops
if (client->ps.weapon == weap) {
client->ps.stats[STAT_RQ3] |= RQ3_THROWWEAPON;
trap_SendServerCommand(ent - g_entities, va("rq3_cmd %i", SELECTPISTOL));
}
client->weaponCount[weap]--;
if (client->weaponCount[weap] == 0)
client->ps.stats[STAT_WEAPONS] &= ~(1 << weap);
xr_drop = dropWeapon(ent, xr_item, 0, FL_DROPPED_ITEM | FL_THROWN_ITEM);
xr_drop->count = -1; // XRAY FMJ 0 is already taken, -1 means no ammo
// remember who dropped you, child!
xr_drop->s.otherEntityNum = client->ps.clientNum;
client->uniqueWeapons--;
}
return weap;
}
/*
==============
ThrowItem
Used to toss an item much like weapons except a bit leaner
You can throw items ANY time (firing, bandaging, etc.)
=============
*/
void ThrowItem(gentity_t * ent)
{
gclient_t *client;
gitem_t *xr_item;
gentity_t *xr_drop;
// int item;
client = ent->client;
//itemonTime > 0 or itemonState == itemon_dropping? Or both?
//item = 0;
// JBravo: New drop item code for multiple items.
if (client->uniqueItems > 0) {
if (g_gametype.integer >= GT_TEAM) {
if (client->ps.stats[STAT_HOLDABLE_ITEM] & (1 << client->teamplayItem)) {
xr_item = BG_FindItemForHoldable(client->teamplayItem);
xr_drop = dropWeapon(ent, xr_item, 0, FL_DROPPED_ITEM | FL_THROWN_ITEM);
xr_drop->count = -1;
client->ps.stats[STAT_HOLDABLE_ITEM] &= ~(1 << client->teamplayItem);
client->uniqueItems--;
return;
}
}
if (client->ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_BANDOLIER)) {
xr_item = BG_FindItemForHoldable(HI_BANDOLIER);
xr_drop = dropWeapon(ent, xr_item, 0, FL_DROPPED_ITEM | FL_THROWN_ITEM);
xr_drop->count = -1;
client->ps.stats[STAT_HOLDABLE_ITEM] &= ~(1 << HI_BANDOLIER);
client->uniqueItems--;
return;
} else if (client->ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_SLIPPERS)) {
xr_item = BG_FindItemForHoldable(HI_SLIPPERS);
xr_drop = dropWeapon(ent, xr_item, 0, FL_DROPPED_ITEM | FL_THROWN_ITEM);
xr_drop->count = -1;
client->ps.stats[STAT_HOLDABLE_ITEM] &= ~(1 << HI_SLIPPERS);
client->uniqueItems--;
return;
} else if (client->ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_SILENCER)) {
xr_item = BG_FindItemForHoldable(HI_SILENCER);
xr_drop = dropWeapon(ent, xr_item, 0, FL_DROPPED_ITEM | FL_THROWN_ITEM);
xr_drop->count = -1;
client->ps.stats[STAT_HOLDABLE_ITEM] &= ~(1 << HI_SILENCER);
client->uniqueItems--;
return;
// JBravo: adding the helmet :)
} else if (client->ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_HELMET)) {
xr_item = BG_FindItemForHoldable(HI_HELMET);
xr_drop = dropWeapon(ent, xr_item, 0, FL_DROPPED_ITEM | FL_THROWN_ITEM);
xr_drop->count = -1;
client->ps.stats[STAT_HOLDABLE_ITEM] &= ~(1 << HI_HELMET);
client->uniqueItems--;
return;
} else if (client->ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_LASER)) {
xr_item = BG_FindItemForHoldable(HI_LASER);
xr_drop = dropWeapon(ent, xr_item, 0, FL_DROPPED_ITEM | FL_THROWN_ITEM);
xr_drop->count = -1;
client->ps.stats[STAT_HOLDABLE_ITEM] &= ~(1 << HI_LASER);
client->uniqueItems--;
return;
} else if (client->ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_KEVLAR)) {
xr_item = BG_FindItemForHoldable(HI_KEVLAR);
xr_drop = dropWeapon(ent, xr_item, 0, FL_DROPPED_ITEM | FL_THROWN_ITEM);
xr_drop->count = -1;
client->ps.stats[STAT_HOLDABLE_ITEM] &= ~(1 << HI_KEVLAR);
client->uniqueItems--;
return;
}
}
}
/*
==============
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;
int bJumping = 0;
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;
}
if (ucmd->serverTime < level.time - 1000) {
ucmd->serverTime = level.time - 1000;
}
client->lastUpdateFrame = level.framenum;
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");
} else if (pmove_msec.integer > 33) {
trap_Cvar_Set("pmove_msec", "33");
}
if (pmove_fixed.integer || client->pers.pmoveFixed) {
ucmd->serverTime =
((ucmd->serverTime + pmove_msec.integer - 1) / pmove_msec.integer) * pmove_msec.integer;
}
//
// 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;
}
// 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;
// set up for pmove
oldEventSequence = client->ps.eventSequence;
memset(&pm, 0, sizeof(pm));
if (ent->flags & FL_FORCE_GESTURE) {
ent->flags &= ~FL_FORCE_GESTURE;
ent->client->pers.cmd.buttons |= BUTTON_GESTURE;
}
//Elder: 3rb Code moved to bg_pmove.c (resides in PM_Weapon)
pm.ps = &client->ps;
pm.cmd = *ucmd;
if (pm.ps->pm_type == PM_DEAD) {
pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY;
} else if (ent->r.svFlags & SVF_BOT) {
pm.tracemask = MASK_PLAYERSOLID | CONTENTS_BOTCLIP;
} else {
pm.tracemask = MASK_PLAYERSOLID;
}
// JBravo: fixing telefragging and shit during spawnig. (Thanks NiceAss! :)
if ((g_gametype.integer == GT_TEAMPLAY || g_gametype.integer == GT_TEAM) &&
((ent->client->ps.stats[STAT_RQ3] & RQ3_PLAYERSOLID) != RQ3_PLAYERSOLID) && !level.lights_camera_action) {
UnstickPlayer(ent);
}
if ((g_gametype.integer == GT_TEAMPLAY || g_gametype.integer == GT_TEAM) &&
((ent->client->ps.stats[STAT_RQ3] & RQ3_PLAYERSOLID) != RQ3_PLAYERSOLID)) {
pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY;
}
pm.trace = trap_Trace;
pm.pointcontents = trap_PointContents;
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;
VectorCopy(client->ps.origin, client->oldOrigin);
// JBravo: setting lca in pm if appropriate
if (g_RQ3_lca.integer == 1)
pm.lca = qtrue;
else
pm.lca = qfalse;
pm.predict = qtrue;
Pmove(&pm);
if ((pm.cmd.upmove > 10) &&
(pm.waterlevel == 0) &&
ent->s.groundEntityNum != ENTITYNUM_NONE && pm.ps->groundEntityNum == ENTITYNUM_NONE)
bJumping = 1;
// save results of pmove
if (ent->client->ps.eventSequence != oldEventSequence) {
ent->eventTime = level.time;
}
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);
// link entity now, after any personal teleporters have been used
// JBravo: this call reactivates gibbed players.
if (!ent->client->gibbed)
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);
//Elder: someone added
if (bJumping)
JumpKick(ent);
// 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
// JBravo: Lets make dead players into spectators.
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 &&
g_gametype.integer != GT_TEAMPLAY && g_gametype.integer != GT_CTF) {
respawn(ent);
return;
}
if ((g_gametype.integer == GT_TEAMPLAY || g_gametype.integer == GT_CTF) && level.time > client->respawnTime) {
MakeSpectator(ent);
}
// pressing attack or use is the normal respawn method
// JBravo: make'em spactate
if (ucmd->buttons & (BUTTON_ATTACK | BUTTON_USE_HOLDABLE)) {
if (g_gametype.integer == GT_TEAMPLAY || g_gametype.integer == GT_CTF) {
MakeSpectator(ent);
} else {
respawn(ent);
}
}
}
return;
}
// JBravo: Idle sounds
if (g_RQ3_ppl_idletime.integer) {
if (ucmd->forwardmove == 0 && ucmd->rightmove == 0) {
if (client->idletime) {
if (level.time >= client->idletime + (g_RQ3_ppl_idletime.integer * 1000)) {
if (g_gametype.integer >= GT_TEAM && g_RQ3_idleaction.integer == 1 &&
(ent->client->sess.sessionTeam == TEAM_RED || ent->client->sess.sessionTeam == TEAM_BLUE)) {
trap_SendServerCommand( -1, va("print \"Removing %s^7 from his team for excessive Idling\n\"",
ent->client->pers.netname));
trap_SendServerCommand(ent - g_entities, "stuff team none\n");
} else if (g_gametype.integer >= GT_TEAM && g_RQ3_idleaction.integer == 2 &&
(ent->client->sess.sessionTeam == TEAM_RED || ent->client->sess.sessionTeam == TEAM_BLUE)) {
trap_SendServerCommand( -1, va("print \"Kicking %s^7 for excessive Idling\n\"",
ent->client->pers.netname));
trap_DropClient(ent - g_entities, "Dropped due to excessive Idling");
} else
G_TempEntity(ent->r.currentOrigin, EV_INSANESOUND);
client->idletime = 0;
}
} else {
client->idletime = level.time;
}
} else {
client->idletime = 0;
}
}
// perform once-a-second actions
ClientTimerActions(ent, msec);
}
/*
==================
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;
/* camera jitter fix (server side) */
// JBravo: Take SPECTATOR_ZCAM into account
if (!(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer &&
(ent->client->sess.sessionTeam != TEAM_SPECTATOR ||
(ent->client->sess.sessionTeam == TEAM_SPECTATOR && ent->client->sess.spectatorState != SPECTATOR_ZCAM))) {
ClientThink_real(ent);
}
}
void G_RunClient(gentity_t * ent)
{
/* camera jitter fix (server side) */
// JBravo: Take SPECTATOR_ZCAM into account
if (!(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer &&
(ent->client->sess.sessionTeam != TEAM_SPECTATOR ||
(ent->client->sess.sessionTeam == TEAM_SPECTATOR && ent->client->sess.spectatorState != SPECTATOR_ZCAM))) {
return;
}
ent->client->pers.cmd.serverTime = level.time;
ClientThink_real(ent);
}
/*
==================
SpectatorClientEndFrame
==================
*/
void SpectatorClientEndFrame(gentity_t * ent)
{
gclient_t *cl;
int savedPing, savedFlags, i;
int savedPers[MAX_PERSISTANT];
// 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];
if (cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR) {
flags =
(cl->ps.eFlags & ~(EF_VOTED | EF_TEAMVOTED)) | (ent->client->ps.
eFlags & (EF_VOTED | EF_TEAMVOTED));
// JBravo: saving score and ping to fix the scoreboard
savedPing = ent->client->ps.ping;
//Slicer saving pm_flags & pers
savedFlags = ent->client->ps.pm_flags;
for (i = 0; i < MAX_PERSISTANT; i++)
savedPers[i] = ent->client->ps.persistant[i];
//This will make the spectator get the client's stuff
ent->client->ps = cl->ps;
//Reposting score and ping..
if (g_gametype.integer >= GT_TEAM) {
for (i = 0; i < MAX_PERSISTANT; i++)
ent->client->ps.persistant[i] = savedPers[i];
ent->client->ps.ping = savedPing;
//Slicer reposting pmflags
ent->client->ps.pm_flags = savedFlags;
}
ent->client->ps.pm_flags |= PMF_FOLLOW;
ent->client->ps.eFlags = flags;
return;
} else {
// drop them to free spectators unless they are dedicated camera followers
if (ent->client->sess.spectatorClient >= 0) {
ent->client->sess.spectatorState = SPECTATOR_FREE;
// JBravo: saving spectatorState
ent->client->specMode = SPECTATOR_FREE;
ClientBegin(ent->client - level.clients);
}
}
}
}
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, frames;
// clientPersistant_t *pers;
if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) {
SpectatorClientEndFrame(ent);
return;
}
// pers = &ent->client->pers;
// turn off any expired powerups
for (i = 0; i < MAX_POWERUPS; i++) {
if (ent->client->ps.powerups[i] < level.time) {
ent->client->ps.powerups[i] = 0;
}
}
// 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;
}
if (ent->client->bleeding)
CheckBleeding(ent); // perform once-a-second actions
// burn from lava, etc
P_WorldEffects(ent);
// apply all the damage taken this frame
P_DamageFeedback(ent);
// Update the clips Amount in weapon for the client
ent->client->ps.stats[STAT_CLIPS] = ent->client->numClips[ent->client->ps.weapon];
ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health...
//Elder: bleeding notification
if (ent->client->bleeding || (ent->client->ps.stats[STAT_RQ3] & RQ3_LEGDAMAGE) == RQ3_LEGDAMAGE) {
ent->client->ps.stats[STAT_RQ3] |= RQ3_BANDAGE_NEED;
} else {
ent->client->ps.stats[STAT_RQ3] &= ~RQ3_BANDAGE_NEED;
}
//Moved to pmove.c
//Elder: M4 ride-up/kick -- condition for non-burst and ammo only
if (ent->client->consecutiveShots &&
(ent->client->ps.ammo[WP_M4] <= 0 || ent->client->ps.weaponstate != WEAPON_FIRING)) {
//Restore view after shots if not firing
ent->client->ps.delta_angles[0] =
ANGLE2SHORT(SHORT2ANGLE(ent->client->ps.delta_angles[0]) - ent->client->consecutiveShots * -0.8);//-0.7f);
ent->client->consecutiveShots = 0;
}
// Check to reset our openDoor boolean
if (ent->client->openDoorTime && level.time - ent->client->openDoorTime > MAXDOORTIME) {
ent->client->openDoor = qfalse;
ent->client->openDoorTime = 0;
}
// JBravo: multiple items
if (ent->client->ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_LASER)) {
//Try to turn the laser on if it's off
if (ent->client->lasersight == NULL)
Laser_Gen(ent, qtrue);
}
//Slicer
if (ent->client->weapon_attempts > 0)
Cmd_Weapon(ent);
G_SetClientSound(ent);
BG_PlayerStateToEntityState(&ent->client->ps, &ent->s, qtrue);
SendPendingPredictableEvents(&ent->client->ps);
// JBravo: unlagged
ent->client->ps.eFlags &= ~EF_CONNECTION;
frames = level.framenum - ent->client->lastUpdateFrame - 1;
if (frames > 2) {
frames = 2;
ent->client->ps.eFlags |= EF_CONNECTION;
ent->s.eFlags |= EF_CONNECTION;
}
if (frames > 0 && g_smoothClients.integer) {
G_PredictPlayerMove(ent, (float)frames / sv_fps.integer);
SnapVector(ent->s.pos.trBase);
}
}