mirror of
https://github.com/ReactionQuake3/reaction.git
synced 2024-11-22 20:31:11 +00:00
1558 lines
45 KiB
C
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);
|
|
}
|
|
}
|