2
===========================================================================
3
Copyright (C) 1999-2005 Id Software, Inc.
5
This file is part of Quake III Arena source code.
7
Quake III Arena source code is free software; you can redistribute it
8
and/or modify it under the terms of the GNU General Public License as
9
published by the Free Software Foundation; either version 2 of the License,
10
or (at your option) any later version.
12
Quake III Arena source code is distributed in the hope that it will be
13
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
GNU General Public License for more details.
17
You should have received a copy of the GNU General Public License
18
along with Quake III Arena source code; if not, write to the Free Software
19
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20
===========================================================================
31
Called just before a snapshot is sent to the given player.
32
Totals up all damage and generates both the player_state_t
33
damage values to that client for pain blends and kicks, and
34
global pain sound events for all clients.
37
void P_DamageFeedback( gentity_t *player ) {
42
client = player->client;
43
if ( client->ps.pm_type == PM_DEAD ) {
47
// total points of damage shot at the player this frame
48
count = client->damage_blood + client->damage_armor;
50
return; // didn't take any damage
57
// send the information to the client
59
// world damage (falling, slime, etc) uses a special code
60
// to make the blend blob centered instead of positional
61
if ( client->damage_fromWorld ) {
62
client->ps.damagePitch = 255;
63
client->ps.damageYaw = 255;
65
client->damage_fromWorld = qfalse;
67
vectoangles( client->damage_from, angles );
68
client->ps.damagePitch = angles[PITCH]/360.0 * 256;
69
client->ps.damageYaw = angles[YAW]/360.0 * 256;
72
// play an apropriate pain sound
73
if ( (level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) ) {
74
player->pain_debounce_time = level.time + 700;
75
G_AddEvent( player, EV_PAIN, player->health );
76
client->ps.damageEvent++;
80
client->ps.damageCount = count;
85
client->damage_blood = 0;
86
client->damage_armor = 0;
87
client->damage_knockback = 0;
96
Check for lava / slime contents and drowning
99
void P_WorldEffects( gentity_t *ent ) {
103
if ( ent->client->noclip ) {
104
ent->client->airOutTime = level.time + 12000; // don't need air
108
waterlevel = ent->waterlevel;
110
envirosuit = ent->client->ps.powerups[PW_BATTLESUIT] > level.time;
113
// check for drowning
115
if ( waterlevel == 3 ) {
116
// envirosuit give air
118
ent->client->airOutTime = level.time + 10000;
121
// if out of air, start drowning
122
if ( ent->client->airOutTime < level.time) {
124
ent->client->airOutTime += 1000;
125
if ( ent->health > 0 ) {
126
// take more damage the longer underwater
128
if (ent->damage > 15)
131
// play a gurp sound instead of a normal pain sound
132
if (ent->health <= ent->damage) {
133
G_Sound(ent, CHAN_VOICE, G_SoundIndex("*drown.wav"));
134
} else if (rand()&1) {
135
G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp1.wav"));
137
G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp2.wav"));
140
// don't play a normal pain sound
141
ent->pain_debounce_time = level.time + 200;
143
G_Damage (ent, NULL, NULL, NULL, NULL,
144
ent->damage, DAMAGE_NO_ARMOR, MOD_WATER);
148
ent->client->airOutTime = level.time + 12000;
153
// check for sizzle damage (move to pmove?)
156
(ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) {
158
&& ent->pain_debounce_time <= level.time ) {
161
G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 );
163
if (ent->watertype & CONTENTS_LAVA) {
164
G_Damage (ent, NULL, NULL, NULL, NULL,
165
30*waterlevel, 0, MOD_LAVA);
168
if (ent->watertype & CONTENTS_SLIME) {
169
G_Damage (ent, NULL, NULL, NULL, NULL,
170
10*waterlevel, 0, MOD_SLIME);
184
void G_SetClientSound( gentity_t *ent ) {
186
if( ent->s.eFlags & EF_TICKING ) {
187
ent->client->ps.loopSound = G_SoundIndex( "sound/weapons/proxmine/wstbtick.wav");
191
if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) {
192
ent->client->ps.loopSound = level.snd_fry;
194
ent->client->ps.loopSound = 0;
200
//==============================================================
207
void ClientImpacts( gentity_t *ent, pmove_t *pm ) {
212
memset( &trace, 0, sizeof( trace ) );
213
for (i=0 ; i<pm->numtouch ; i++) {
214
for (j=0 ; j<i ; j++) {
215
if (pm->touchents[j] == pm->touchents[i] ) {
220
continue; // duplicated
222
other = &g_entities[ pm->touchents[i] ];
224
if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) {
225
ent->touch( ent, other, &trace );
228
if ( !other->touch ) {
232
other->touch( other, ent, &trace );
241
Find all trigger entities that ent's current position touches.
242
Spectators will only interact with teleporters.
245
void G_TouchTriggers( gentity_t *ent ) {
247
int touch[MAX_GENTITIES];
251
static vec3_t range = { 40, 40, 52 };
253
if ( !ent->client ) {
257
// dead clients don't activate triggers!
258
if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) {
262
VectorSubtract( ent->client->ps.origin, range, mins );
263
VectorAdd( ent->client->ps.origin, range, maxs );
265
num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
267
// can't use ent->absmin, because that has a one unit pad
268
VectorAdd( ent->client->ps.origin, ent->r.mins, mins );
269
VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs );
271
for ( i=0 ; i<num ; i++ ) {
272
hit = &g_entities[touch[i]];
274
if ( !hit->touch && !ent->touch ) {
277
if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) {
281
// ignore most entities if a spectator
282
if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
283
if ( hit->s.eType != ET_TELEPORT_TRIGGER &&
284
// this is ugly but adding a new ET_? type will
285
// most likely cause network incompatibilities
286
hit->touch != Touch_DoorTrigger) {
291
// use seperate code for determining if an item is picked up
292
// so you don't have to actually contact its bounding box
293
if ( hit->s.eType == ET_ITEM ) {
294
if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) {
298
if ( !trap_EntityContact( mins, maxs, hit ) ) {
303
memset( &trace, 0, sizeof(trace) );
306
hit->touch (hit, ent, &trace);
309
if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) {
310
ent->touch( ent, hit, &trace );
314
// if we didn't touch a jump pad this pmove frame
315
if ( ent->client->ps.jumppad_frame != ent->client->ps.pmove_framecount ) {
316
ent->client->ps.jumppad_frame = 0;
317
ent->client->ps.jumppad_ent = 0;
326
void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) {
330
client = ent->client;
332
if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) {
333
client->ps.pm_type = PM_SPECTATOR;
334
client->ps.speed = 400; // faster than normal
337
memset (&pm, 0, sizeof(pm));
340
pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies
341
pm.trace = trap_Trace;
342
pm.pointcontents = trap_PointContents;
346
// save results of pmove
347
VectorCopy( client->ps.origin, ent->s.origin );
349
G_TouchTriggers( ent );
350
trap_UnlinkEntity( ent );
353
client->oldbuttons = client->buttons;
354
client->buttons = ucmd->buttons;
356
// attack button cycles through spectators
357
if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) {
358
Cmd_FollowCycle_f( ent, 1 );
366
ClientInactivityTimer
368
Returns qfalse if the client is dropped
371
qboolean ClientInactivityTimer( gclient_t *client ) {
372
if ( ! g_inactivity.integer ) {
373
// give everyone some time, so if the operator sets g_inactivity during
374
// gameplay, everyone isn't kicked
375
client->inactivityTime = level.time + 60 * 1000;
376
client->inactivityWarning = qfalse;
377
} else if ( client->pers.cmd.forwardmove ||
378
client->pers.cmd.rightmove ||
379
client->pers.cmd.upmove ||
380
(client->pers.cmd.buttons & BUTTON_ATTACK) ) {
381
client->inactivityTime = level.time + g_inactivity.integer * 1000;
382
client->inactivityWarning = qfalse;
383
} else if ( !client->pers.localClient ) {
384
if ( level.time > client->inactivityTime ) {
385
trap_DropClient( client - level.clients, "Dropped due to inactivity" );
388
if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) {
389
client->inactivityWarning = qtrue;
390
trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" );
400
Actions that happen once a second
403
void ClientTimerActions( gentity_t *ent, int msec ) {
409
client = ent->client;
410
client->timeResidual += msec;
412
while ( client->timeResidual >= 1000 ) {
413
client->timeResidual -= 1000;
417
if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) {
418
maxHealth = client->ps.stats[STAT_MAX_HEALTH] / 2;
420
else if ( client->ps.powerups[PW_REGEN] ) {
421
maxHealth = client->ps.stats[STAT_MAX_HEALTH];
427
if ( ent->health < maxHealth ) {
429
if ( ent->health > maxHealth * 1.1 ) {
430
ent->health = maxHealth * 1.1;
432
G_AddEvent( ent, EV_POWERUP_REGEN, 0 );
433
} else if ( ent->health < maxHealth * 2) {
435
if ( ent->health > maxHealth * 2 ) {
436
ent->health = maxHealth * 2;
438
G_AddEvent( ent, EV_POWERUP_REGEN, 0 );
441
if ( client->ps.powerups[PW_REGEN] ) {
442
if ( ent->health < client->ps.stats[STAT_MAX_HEALTH]) {
444
if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 1.1 ) {
445
ent->health = client->ps.stats[STAT_MAX_HEALTH] * 1.1;
447
G_AddEvent( ent, EV_POWERUP_REGEN, 0 );
448
} else if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] * 2) {
450
if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 2 ) {
451
ent->health = client->ps.stats[STAT_MAX_HEALTH] * 2;
453
G_AddEvent( ent, EV_POWERUP_REGEN, 0 );
457
// count down health when over max
458
if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) {
463
// count down armor when over max
464
if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH] ) {
465
client->ps.stats[STAT_ARMOR]--;
469
if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) {
470
int w, max, inc, t, i;
471
int weapList[]={WP_MACHINEGUN,WP_SHOTGUN,WP_GRENADE_LAUNCHER,WP_ROCKET_LAUNCHER,WP_LIGHTNING,WP_RAILGUN,WP_PLASMAGUN,WP_BFG,WP_NAILGUN,WP_PROX_LAUNCHER,WP_CHAINGUN};
472
int weapCount = sizeof(weapList) / sizeof(int);
474
for (i = 0; i < weapCount; i++) {
478
case WP_MACHINEGUN: max = 50; inc = 4; t = 1000; break;
479
case WP_SHOTGUN: max = 10; inc = 1; t = 1500; break;
480
case WP_GRENADE_LAUNCHER: max = 10; inc = 1; t = 2000; break;
481
case WP_ROCKET_LAUNCHER: max = 10; inc = 1; t = 1750; break;
482
case WP_LIGHTNING: max = 50; inc = 5; t = 1500; break;
483
case WP_RAILGUN: max = 10; inc = 1; t = 1750; break;
484
case WP_PLASMAGUN: max = 50; inc = 5; t = 1500; break;
485
case WP_BFG: max = 10; inc = 1; t = 4000; break;
486
case WP_NAILGUN: max = 10; inc = 1; t = 1250; break;
487
case WP_PROX_LAUNCHER: max = 5; inc = 1; t = 2000; break;
488
case WP_CHAINGUN: max = 100; inc = 5; t = 1000; break;
489
default: max = 0; inc = 0; t = 1000; break;
491
client->ammoTimes[w] += msec;
492
if ( client->ps.ammo[w] >= max ) {
493
client->ammoTimes[w] = 0;
495
if ( client->ammoTimes[w] >= t ) {
496
while ( client->ammoTimes[w] >= t )
497
client->ammoTimes[w] -= t;
498
client->ps.ammo[w] += inc;
499
if ( client->ps.ammo[w] > max ) {
500
client->ps.ammo[w] = max;
510
ClientIntermissionThink
513
void ClientIntermissionThink( gclient_t *client ) {
514
client->ps.eFlags &= ~EF_TALK;
515
client->ps.eFlags &= ~EF_FIRING;
517
// the level will exit when everyone wants to or after timeouts
519
// swap and latch button actions
520
client->oldbuttons = client->buttons;
521
client->buttons = client->pers.cmd.buttons;
522
if ( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) {
523
// this used to be an ^1 but once a player says ready, it should stick
524
client->readyToExit = 1;
533
Events will be passed on to the clients for presentation,
534
but any server game effects are handled here
537
void ClientEvents( gentity_t *ent, int oldEventSequence ) {
543
vec3_t origin, angles;
548
client = ent->client;
550
if ( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) {
551
oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS;
553
for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) {
554
event = client->ps.events[ i & (MAX_PS_EVENTS-1) ];
559
if ( ent->s.eType != ET_PLAYER ) {
560
break; // not in the player model
562
if ( g_dmflags.integer & DF_NO_FALLING ) {
565
if ( event == EV_FALL_FAR ) {
570
VectorSet (dir, 0, 0, 1);
571
ent->pain_debounce_time = level.time + 200; // no normal pain sound
572
G_Damage (ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING);
579
case EV_USE_ITEM1: // teleporter
584
if ( ent->client->ps.powerups[ PW_REDFLAG ] ) {
585
item = BG_FindItemForPowerup( PW_REDFLAG );
587
} else if ( ent->client->ps.powerups[ PW_BLUEFLAG ] ) {
588
item = BG_FindItemForPowerup( PW_BLUEFLAG );
590
} else if ( ent->client->ps.powerups[ PW_NEUTRALFLAG ] ) {
591
item = BG_FindItemForPowerup( PW_NEUTRALFLAG );
596
drop = Drop_Item( ent, item, 0 );
597
// decide how many seconds it has left
598
drop->count = ( ent->client->ps.powerups[ j ] - level.time ) / 1000;
599
if ( drop->count < 1 ) {
603
ent->client->ps.powerups[ j ] = 0;
607
if ( g_gametype.integer == GT_HARVESTER ) {
608
if ( ent->client->ps.generic1 > 0 ) {
609
if ( ent->client->sess.sessionTeam == TEAM_RED ) {
610
item = BG_FindItem( "Blue Cube" );
612
item = BG_FindItem( "Red Cube" );
615
for ( j = 0; j < ent->client->ps.generic1; j++ ) {
616
drop = Drop_Item( ent, item, 0 );
617
if ( ent->client->sess.sessionTeam == TEAM_RED ) {
618
drop->spawnflags = TEAM_BLUE;
620
drop->spawnflags = TEAM_RED;
624
ent->client->ps.generic1 = 0;
628
SelectSpawnPoint( ent->client->ps.origin, origin, angles );
629
TeleportPlayer( ent, origin, angles );
632
case EV_USE_ITEM2: // medkit
633
ent->health = ent->client->ps.stats[STAT_MAX_HEALTH] + 25;
638
case EV_USE_ITEM3: // kamikaze
639
// make sure the invulnerability is off
640
ent->client->invulnerabilityTime = 0;
642
G_StartKamikaze( ent );
645
case EV_USE_ITEM4: // portal
646
if( ent->client->portalID ) {
647
DropPortalSource( ent );
650
DropPortalDestination( ent );
653
case EV_USE_ITEM5: // invulnerability
654
ent->client->invulnerabilityTime = level.time + 10000;
671
static int StuckInOtherClient(gentity_t *ent) {
675
ent2 = &g_entities[0];
676
for ( i = 0; i < MAX_CLIENTS; i++, ent2++ ) {
680
if ( !ent2->inuse ) {
683
if ( !ent2->client ) {
686
if ( ent2->health <= 0 ) {
690
if (ent2->r.absmin[0] > ent->r.absmax[0])
692
if (ent2->r.absmin[1] > ent->r.absmax[1])
694
if (ent2->r.absmin[2] > ent->r.absmax[2])
696
if (ent2->r.absmax[0] < ent->r.absmin[0])
698
if (ent2->r.absmax[1] < ent->r.absmin[1])
700
if (ent2->r.absmax[2] < ent->r.absmin[2])
708
void BotTestSolid(vec3_t origin);
712
SendPendingPredictableEvents
715
void SendPendingPredictableEvents( playerState_t *ps ) {
718
int extEvent, number;
720
// if there are still events pending
721
if ( ps->entityEventSequence < ps->eventSequence ) {
722
// create a temporary entity for this event which is sent to everyone
723
// except the client who generated the event
724
seq = ps->entityEventSequence & (MAX_PS_EVENTS-1);
725
event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 );
726
// set external event to zero before calling BG_PlayerStateToEntityState
727
extEvent = ps->externalEvent;
728
ps->externalEvent = 0;
729
// create temporary entity for event
730
t = G_TempEntity( ps->origin, event );
731
number = t->s.number;
732
BG_PlayerStateToEntityState( ps, &t->s, qtrue );
733
t->s.number = number;
734
t->s.eType = ET_EVENTS + event;
735
t->s.eFlags |= EF_PLAYER_EVENT;
736
t->s.otherEntityNum = ps->clientNum;
737
// send to everyone except the client who generated the event
738
t->r.svFlags |= SVF_NOTSINGLECLIENT;
739
t->r.singleClient = ps->clientNum;
740
// set back external event
741
ps->externalEvent = extEvent;
749
This will be called once for each client frame, which will
750
usually be a couple times for each server frame on fast clients.
752
If "g_synchronousClients 1" is set, this will be called exactly
753
once for each server frame, which makes for smooth demo recording.
756
void ClientThink_real( gentity_t *ent ) {
759
int oldEventSequence;
763
client = ent->client;
765
// don't think if the client is not yet connected (and thus not yet spawned in)
766
if (client->pers.connected != CON_CONNECTED) {
769
// mark the time, so the connection sprite can be removed
770
ucmd = &ent->client->pers.cmd;
772
// sanity check the command time to prevent speedup cheating
773
if ( ucmd->serverTime > level.time + 200 ) {
774
ucmd->serverTime = level.time + 200;
775
// G_Printf("serverTime <<<<<\n" );
777
if ( ucmd->serverTime < level.time - 1000 ) {
778
ucmd->serverTime = level.time - 1000;
779
// G_Printf("serverTime >>>>>\n" );
782
msec = ucmd->serverTime - client->ps.commandTime;
783
// following others may result in bad times, but we still want
784
// to check for follow toggles
785
if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) {
792
if ( pmove_msec.integer < 8 ) {
793
trap_Cvar_Set("pmove_msec", "8");
795
else if (pmove_msec.integer > 33) {
796
trap_Cvar_Set("pmove_msec", "33");
799
if ( pmove_fixed.integer || client->pers.pmoveFixed ) {
800
ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer;
801
//if (ucmd->serverTime - client->ps.commandTime <= 0)
806
// check for exiting intermission
808
if ( level.intermissiontime ) {
809
ClientIntermissionThink( client );
813
// spectators don't do much
814
if ( client->sess.sessionTeam == TEAM_SPECTATOR ) {
815
if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) {
818
SpectatorThink( ent, ucmd );
822
// check for inactivity timer, but never drop the local client of a non-dedicated server
823
if ( !ClientInactivityTimer( client ) ) {
827
// clear the rewards if time
828
if ( level.time > client->rewardTime ) {
829
client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP );
832
if ( client->noclip ) {
833
client->ps.pm_type = PM_NOCLIP;
834
} else if ( client->ps.stats[STAT_HEALTH] <= 0 ) {
835
client->ps.pm_type = PM_DEAD;
837
client->ps.pm_type = PM_NORMAL;
840
client->ps.gravity = g_gravity.value;
843
client->ps.speed = g_speed.value;
846
if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) {
847
client->ps.speed *= 1.5;
851
if ( client->ps.powerups[PW_HASTE] ) {
852
client->ps.speed *= 1.3;
855
// Let go of the hook if we aren't firing
856
if ( client->ps.weapon == WP_GRAPPLING_HOOK &&
857
client->hook && !( ucmd->buttons & BUTTON_ATTACK ) ) {
858
Weapon_HookFree(client->hook);
862
oldEventSequence = client->ps.eventSequence;
864
memset (&pm, 0, sizeof(pm));
866
// check for the hit-scan gauntlet, don't let the action
867
// go through as an attack unless it actually hits something
868
if ( client->ps.weapon == WP_GAUNTLET && !( ucmd->buttons & BUTTON_TALK ) &&
869
( ucmd->buttons & BUTTON_ATTACK ) && client->ps.weaponTime <= 0 ) {
870
pm.gauntletHit = CheckGauntletAttack( ent );
873
if ( ent->flags & FL_FORCE_GESTURE ) {
874
ent->flags &= ~FL_FORCE_GESTURE;
875
ent->client->pers.cmd.buttons |= BUTTON_GESTURE;
879
// check for invulnerability expansion before doing the Pmove
880
if (client->ps.powerups[PW_INVULNERABILITY] ) {
881
if ( !(client->ps.pm_flags & PMF_INVULEXPAND) ) {
882
vec3_t mins = { -42, -42, -42 };
883
vec3_t maxs = { 42, 42, 42 };
884
vec3_t oldmins, oldmaxs;
886
VectorCopy (ent->r.mins, oldmins);
887
VectorCopy (ent->r.maxs, oldmaxs);
889
VectorCopy (mins, ent->r.mins);
890
VectorCopy (maxs, ent->r.maxs);
891
trap_LinkEntity(ent);
892
// check if this would get anyone stuck in this player
893
if ( !StuckInOtherClient(ent) ) {
894
// set flag so the expanded size will be set in PM_CheckDuck
895
client->ps.pm_flags |= PMF_INVULEXPAND;
898
VectorCopy (oldmins, ent->r.mins);
899
VectorCopy (oldmaxs, ent->r.maxs);
900
trap_LinkEntity(ent);
907
if ( pm.ps->pm_type == PM_DEAD ) {
908
pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY;
910
else if ( ent->r.svFlags & SVF_BOT ) {
911
pm.tracemask = MASK_PLAYERSOLID | CONTENTS_BOTCLIP;
914
pm.tracemask = MASK_PLAYERSOLID;
916
pm.trace = trap_Trace;
917
pm.pointcontents = trap_PointContents;
918
pm.debugLevel = g_debugMove.integer;
919
pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0;
921
pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed;
922
pm.pmove_msec = pmove_msec.integer;
924
VectorCopy( client->ps.origin, client->oldOrigin );
927
if (level.intermissionQueued != 0 && g_singlePlayer.integer) {
928
if ( level.time - level.intermissionQueued >= 1000 ) {
930
pm.cmd.forwardmove = 0;
931
pm.cmd.rightmove = 0;
933
if ( level.time - level.intermissionQueued >= 2000 && level.time - level.intermissionQueued <= 2500 ) {
934
trap_SendConsoleCommand( EXEC_APPEND, "centerview\n");
936
ent->client->ps.pm_type = PM_SPINTERMISSION;
944
// save results of pmove
945
if ( ent->client->ps.eventSequence != oldEventSequence ) {
946
ent->eventTime = level.time;
948
if (g_smoothClients.integer) {
949
BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue );
952
BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue );
954
SendPendingPredictableEvents( &ent->client->ps );
956
if ( !( ent->client->ps.eFlags & EF_FIRING ) ) {
957
client->fireHeld = qfalse; // for grapple
960
// use the snapped origin for linking so it matches client predicted versions
961
VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin );
963
VectorCopy (pm.mins, ent->r.mins);
964
VectorCopy (pm.maxs, ent->r.maxs);
966
ent->waterlevel = pm.waterlevel;
967
ent->watertype = pm.watertype;
969
// execute client events
970
ClientEvents( ent, oldEventSequence );
972
// link entity now, after any personal teleporters have been used
973
trap_LinkEntity (ent);
974
if ( !ent->client->noclip ) {
975
G_TouchTriggers( ent );
978
// NOTE: now copy the exact origin over otherwise clients can be snapped into solid
979
VectorCopy( ent->client->ps.origin, ent->r.currentOrigin );
981
//test for solid areas in the AAS file
982
BotTestAAS(ent->r.currentOrigin);
984
// touch other objects
985
ClientImpacts( ent, &pm );
987
// save results of triggers and client events
988
if (ent->client->ps.eventSequence != oldEventSequence) {
989
ent->eventTime = level.time;
992
// swap and latch button actions
993
client->oldbuttons = client->buttons;
994
client->buttons = ucmd->buttons;
995
client->latched_buttons |= client->buttons & ~client->oldbuttons;
997
// check for respawning
998
if ( client->ps.stats[STAT_HEALTH] <= 0 ) {
999
// wait for the attack button to be pressed
1000
if ( level.time > client->respawnTime ) {
1001
// forcerespawn is to prevent users from waiting out powerups
1002
if ( g_forcerespawn.integer > 0 &&
1003
( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) {
1008
// pressing attack or use is the normal respawn method
1009
if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) {
1016
// perform once-a-second actions
1017
ClientTimerActions( ent, msec );
1024
A new command has arrived from the client
1027
void ClientThink( int clientNum ) {
1030
ent = g_entities + clientNum;
1031
trap_GetUsercmd( clientNum, &ent->client->pers.cmd );
1033
// mark the time we got info, so we can display the
1034
// phone jack if they don't get any for a while
1035
ent->client->lastCmdTime = level.time;
1037
if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) {
1038
ClientThink_real( ent );
1043
void G_RunClient( gentity_t *ent ) {
1044
if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) {
1047
ent->client->pers.cmd.serverTime = level.time;
1048
ClientThink_real( ent );
1054
SpectatorClientEndFrame
1058
void SpectatorClientEndFrame( gentity_t *ent ) {
1061
// if we are doing a chase cam or a remote view, grab the latest info
1062
if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) {
1063
int clientNum, flags;
1065
clientNum = ent->client->sess.spectatorClient;
1067
// team follow1 and team follow2 go to whatever clients are playing
1068
if ( clientNum == -1 ) {
1069
clientNum = level.follow1;
1070
} else if ( clientNum == -2 ) {
1071
clientNum = level.follow2;
1073
if ( clientNum >= 0 ) {
1074
cl = &level.clients[ clientNum ];
1075
if ( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR ) {
1076
flags = (cl->ps.eFlags & ~(EF_VOTED | EF_TEAMVOTED)) | (ent->client->ps.eFlags & (EF_VOTED | EF_TEAMVOTED));
1077
ent->client->ps = cl->ps;
1078
ent->client->ps.pm_flags |= PMF_FOLLOW;
1079
ent->client->ps.eFlags = flags;
1082
// drop them to free spectators unless they are dedicated camera followers
1083
if ( ent->client->sess.spectatorClient >= 0 ) {
1084
ent->client->sess.spectatorState = SPECTATOR_FREE;
1085
ClientBegin( ent->client - level.clients );
1091
if ( ent->client->sess.spectatorState == SPECTATOR_SCOREBOARD ) {
1092
ent->client->ps.pm_flags |= PMF_SCOREBOARD;
1094
ent->client->ps.pm_flags &= ~PMF_SCOREBOARD;
1102
Called at the end of each server frame for each connected client
1103
A fast client will have multiple ClientThink for each ClientEdFrame,
1104
while a slow client may have multiple ClientEndFrame between ClientThink.
1107
void ClientEndFrame( gentity_t *ent ) {
1109
clientPersistant_t *pers;
1111
if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
1112
SpectatorClientEndFrame( ent );
1116
pers = &ent->client->pers;
1118
// turn off any expired powerups
1119
for ( i = 0 ; i < MAX_POWERUPS ; i++ ) {
1120
if ( ent->client->ps.powerups[ i ] < level.time ) {
1121
ent->client->ps.powerups[ i ] = 0;
1126
// set powerup for player animation
1127
if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) {
1128
ent->client->ps.powerups[PW_GUARD] = level.time;
1130
if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) {
1131
ent->client->ps.powerups[PW_SCOUT] = level.time;
1133
if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_DOUBLER ) {
1134
ent->client->ps.powerups[PW_DOUBLER] = level.time;
1136
if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) {
1137
ent->client->ps.powerups[PW_AMMOREGEN] = level.time;
1139
if ( ent->client->invulnerabilityTime > level.time ) {
1140
ent->client->ps.powerups[PW_INVULNERABILITY] = level.time;
1144
// save network bandwidth
1146
if ( !g_synchronousClients->integer && ent->client->ps.pm_type == PM_NORMAL ) {
1147
// FIXME: this must change eventually for non-sync demo recording
1148
VectorClear( ent->client->ps.viewangles );
1153
// If the end of unit layout is displayed, don't give
1154
// the player any normal movement attributes
1156
if ( level.intermissiontime ) {
1160
// burn from lava, etc
1161
P_WorldEffects (ent);
1163
// apply all the damage taken this frame
1164
P_DamageFeedback (ent);
1166
// add the EF_CONNECTION flag if we haven't gotten commands recently
1167
if ( level.time - ent->client->lastCmdTime > 1000 ) {
1168
ent->s.eFlags |= EF_CONNECTION;
1170
ent->s.eFlags &= ~EF_CONNECTION;
1173
ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health...
1175
G_SetClientSound (ent);
1177
// set the latest infor
1178
if (g_smoothClients.integer) {
1179
BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue );
1182
BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue );
1184
SendPendingPredictableEvents( &ent->client->ps );
1186
// set the bit for the reachability area the client is currently in
1187
// i = trap_AAS_PointReachabilityAreaIndex( ent->client->ps.origin );
1188
// ent->client->areabits[i >> 3] |= 1 << (i & 7);