root/src/game/g_active.c @ 78:dcde131dddae

Revision 78:dcde131dddae, 56.5 kB (checked in by mdoison, 3 years ago)

add goon.patch (tjw 1.2)

  • goon+ is now at s2
  • improve goons capacities
  • pounce give always a knockback
  • improve pounce effect
  • give splash damage to pounceball
Line 
1/*
2===========================================================================
3Copyright (C) 1999-2005 Id Software, Inc.
4Copyright (C) 2000-2006 Tim Angus
5
6This file is part of Tremulous.
7
8Tremulous is free software; you can redistribute it
9and/or modify it under the terms of the GNU General Public License as
10published by the Free Software Foundation; either version 2 of the License,
11or (at your option) any later version.
12
13Tremulous is distributed in the hope that it will be
14useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with Tremulous; if not, write to the Free Software
20Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21===========================================================================
22*/
23
24#include "g_local.h"
25
26/*
27===============
28G_DamageFeedback
29
30Called just before a snapshot is sent to the given player.
31Totals up all damage and generates both the player_state_t
32damage values to that client for pain blends and kicks, and
33global pain sound events for all clients.
34===============
35*/
36void P_DamageFeedback( gentity_t *player )
37{
38  gclient_t *client;
39  float     count;
40  vec3_t    angles;
41
42  client = player->client;
43  if( client->ps.pm_type == PM_DEAD )
44    return;
45
46  // total points of damage shot at the player this frame
47  count = client->damage_blood + client->damage_armor;
48  if( count == 0 )
49    return;   // didn't take any damage
50
51  if( count > 255 )
52    count = 255;
53
54  // send the information to the client
55
56  // world damage (falling, slime, etc) uses a special code
57  // to make the blend blob centered instead of positional
58  if( client->damage_fromWorld )
59  {
60    client->ps.damagePitch = 255;
61    client->ps.damageYaw = 255;
62
63    client->damage_fromWorld = qfalse;
64  }
65  else
66  {
67    vectoangles( client->damage_from, angles );
68    client->ps.damagePitch = angles[ PITCH ] / 360.0 * 256;
69    client->ps.damageYaw = angles[ YAW ] / 360.0 * 256;
70  }
71
72  // play an apropriate pain sound
73  if( ( level.time > player->pain_debounce_time ) && !( player->flags & FL_GODMODE ) )
74  {
75    player->pain_debounce_time = level.time + 700;
76    G_AddEvent( player, EV_PAIN, player->health > 255 ? 255 : player->health );
77    client->ps.damageEvent++;
78  }
79
80
81  client->ps.damageCount = count;
82
83  //
84  // clear totals
85  //
86  client->damage_blood = 0;
87  client->damage_armor = 0;
88  client->damage_knockback = 0;
89}
90
91
92
93/*
94=============
95P_WorldEffects
96
97Check for lava / slime contents and drowning
98=============
99*/
100void P_WorldEffects( gentity_t *ent )
101{
102  int       waterlevel;
103
104  if( ent->client->noclip )
105  {
106    ent->client->airOutTime = level.time + 12000; // don't need air
107    return;
108  }
109
110  waterlevel = ent->waterlevel;
111
112  //
113  // check for drowning
114  //
115  if( waterlevel == 3 )
116  {
117    // if out of air, start drowning
118    if( ent->client->airOutTime < level.time)
119    {
120      // drown!
121      ent->client->airOutTime += 1000;
122      if( ent->health > 0 )
123      {
124        // take more damage the longer underwater
125        ent->damage += 2;
126        if( ent->damage > 15 )
127          ent->damage = 15;
128
129        // play a gurp sound instead of a normal pain sound
130        if( ent->health <= ent->damage )
131          G_Sound( ent, CHAN_VOICE, G_SoundIndex( "*drown.wav" ) );
132        else if( rand( ) & 1 )
133          G_Sound( ent, CHAN_VOICE, G_SoundIndex( "sound/player/gurp1.wav" ) );
134        else
135          G_Sound( ent, CHAN_VOICE, G_SoundIndex( "sound/player/gurp2.wav" ) );
136
137        // don't play a normal pain sound
138        ent->pain_debounce_time = level.time + 200;
139
140        G_Damage( ent, NULL, NULL, NULL, NULL,
141          ent->damage, DAMAGE_NO_ARMOR, MOD_WATER );
142      }
143    }
144  }
145  else
146  {
147    ent->client->airOutTime = level.time + 12000;
148    ent->damage = 2;
149  }
150
151  //
152  // check for sizzle damage (move to pmove?)
153  //
154  if( waterlevel &&
155      ( ent->watertype & ( CONTENTS_LAVA | CONTENTS_SLIME ) ) )
156  {
157    if( ent->health > 0 &&
158        ent->pain_debounce_time <= level.time  )
159    {
160      if( ent->watertype & CONTENTS_LAVA )
161      {
162        G_Damage( ent, NULL, NULL, NULL, NULL,
163          30 * waterlevel, 0, MOD_LAVA );
164      }
165
166      if( ent->watertype & CONTENTS_SLIME )
167      {
168        G_Damage( ent, NULL, NULL, NULL, NULL,
169          10 * waterlevel, 0, MOD_SLIME );
170      }
171    }
172  }
173}
174
175
176
177/*
178===============
179G_SetClientSound
180===============
181*/
182void G_SetClientSound( gentity_t *ent )
183{
184  if( ent->waterlevel && ( ent->watertype & ( CONTENTS_LAVA | CONTENTS_SLIME ) ) )
185    ent->client->ps.loopSound = level.snd_fry;
186  else
187    ent->client->ps.loopSound = 0;
188}
189
190
191
192//==============================================================
193
194static void G_ClientShove( gentity_t *ent, gentity_t *victim )
195{
196  vec3_t  dir, push;
197  int entMass = 200, vicMass = 200;
198
199  // shoving enemies changes gameplay too much
200  if( !OnSameTeam( ent, victim ) )
201    return;
202
203  // alien mass is directly related to their health points
204  // human mass is 200, double for bsuit 
205  if( ent->client->pers.teamSelection == PTE_ALIENS )
206  {
207    entMass = BG_FindHealthForClass( ent->client->pers.classSelection );
208  }
209  else if( ent->client->pers.teamSelection == PTE_HUMANS )
210  {
211    if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, ent->client->ps.stats ) )
212      entMass *= 2;
213  }
214  else
215    return;
216
217  if( victim->client->pers.teamSelection == PTE_ALIENS )
218  {
219    vicMass = BG_FindHealthForClass( victim->client->pers.classSelection );
220  }
221  else if( BG_InventoryContainsUpgrade( UP_BATTLESUIT,
222    victim->client->ps.stats ) )
223  {
224    vicMass *= 2;
225  }
226
227  if( vicMass <= 0 || entMass <= 0 )
228    return;
229
230  VectorSubtract( victim->r.currentOrigin, ent->r.currentOrigin, dir );
231  VectorNormalizeFast( dir );
232
233  // don't break the dretch elevator
234  if( abs( dir[ 2 ] ) > abs( dir[ 0 ] ) && abs( dir[ 2 ] ) > abs( dir[ 1 ] ) )
235    return;
236
237  VectorScale( dir,
238    ( g_shove.value * ( ( float )entMass / ( float )vicMass ) ), push );
239  VectorAdd( victim->client->ps.velocity, push,
240                victim->client->ps.velocity );
241
242}
243
244/*
245==============
246ClientImpacts
247==============
248*/
249void ClientImpacts( gentity_t *ent, pmove_t *pm )
250{
251  int       i, j;
252  trace_t   trace;
253  gentity_t *other;
254
255  memset( &trace, 0, sizeof( trace ) );
256
257  for( i = 0; i < pm->numtouch; i++ )
258  {
259    for( j = 0; j < i; j++ )
260    {
261      if( pm->touchents[ j ] == pm->touchents[ i ] )
262        break;
263    }
264
265    if( j != i )
266      continue; // duplicated
267
268    other = &g_entities[ pm->touchents[ i ] ];
269
270    // see G_UnlaggedDetectCollisions(), this is the inverse of that.
271    // if our movement is blocked by another player's real position,
272    // don't use the unlagged position for them because they are
273    // blocking or server-side Pmove() from reaching it
274    if( other->client && other->client->unlaggedCalc.used )
275      other->client->unlaggedCalc.used = qfalse;
276
277    //charge attack
278    if( ent->client->ps.weapon == WP_ALEVEL4 &&
279        ent->client->ps.stats[ STAT_MISC ] > 0 &&
280        ent->client->charging )
281      ChargeAttack( ent, other );
282
283    if( ent->client && other->client )
284      G_ClientShove( ent, other );
285
286    if( !other->touch )
287      continue;
288
289    other->touch( other, ent, &trace );
290  }
291}
292
293/*
294============
295G_TouchTriggers
296
297Find all trigger entities that ent's current position touches.
298Spectators will only interact with teleporters.
299============
300*/
301void  G_TouchTriggers( gentity_t *ent )
302{
303  int       i, num;
304  int       touch[MAX_GENTITIES];
305  gentity_t *hit;
306  trace_t   trace;
307  vec3_t    mins, maxs;
308  vec3_t    pmins, pmaxs;
309  static    vec3_t range = { 10, 10, 10 };
310
311  if( !ent->client )
312    return;
313
314  // dead clients don't activate triggers!
315  if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 )
316    return;
317
318  BG_FindBBoxForClass( ent->client->ps.stats[ STAT_PCLASS ],
319                       pmins, pmaxs, NULL, NULL, NULL );
320
321  VectorAdd( ent->client->ps.origin, pmins, mins );
322  VectorAdd( ent->client->ps.origin, pmaxs, maxs );
323
324  VectorSubtract( mins, range, mins );
325  VectorAdd( maxs, range, maxs );
326
327  num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
328
329  // can't use ent->absmin, because that has a one unit pad
330  VectorAdd( ent->client->ps.origin, ent->r.mins, mins );
331  VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs );
332
333  for( i = 0; i < num; i++ )
334  {
335    hit = &g_entities[ touch[ i ] ];
336
337    if( !hit->touch && !ent->touch )
338      continue;
339
340    if( !( hit->r.contents & CONTENTS_TRIGGER ) )
341      continue;
342
343    // ignore most entities if a spectator
344    if( ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) ||
345        ( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) ||
346        ( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) )
347    {
348      if( hit->s.eType != ET_TELEPORT_TRIGGER &&
349          // this is ugly but adding a new ET_? type will
350          // most likely cause network incompatibilities
351          hit->touch != Touch_DoorTrigger )
352      {
353        //check for manually triggered doors
354        manualTriggerSpectator( hit, ent );
355        continue;
356      }
357    }
358
359    if( !trap_EntityContact( mins, maxs, hit ) )
360      continue;
361
362    memset( &trace, 0, sizeof( trace ) );
363
364    if( hit->touch )
365      hit->touch( hit, ent, &trace );
366  }
367}
368
369/*
370=================
371SpectatorThink
372=================
373*/
374void SpectatorThink( gentity_t *ent, usercmd_t *ucmd )
375{
376  pmove_t pm;
377  gclient_t *client;
378  qboolean attack1, attack3;
379  qboolean      doPmove = qtrue;
380
381  client = ent->client;
382
383  client->oldbuttons = client->buttons;
384  client->buttons = ucmd->buttons;
385
386  attack1 = ( ( client->buttons & BUTTON_ATTACK ) &&
387              !( client->oldbuttons & BUTTON_ATTACK ) );
388  attack3 = ( ( client->buttons & BUTTON_USE_HOLDABLE ) &&
389              !( client->oldbuttons & BUTTON_USE_HOLDABLE ) );
390
391  if( client->sess.spectatorState == SPECTATOR_LOCKED || client->sess.spectatorState == SPECTATOR_FOLLOW )
392    client->ps.pm_type = PM_FREEZE;
393  else
394    client->ps.pm_type = PM_SPECTATOR;
395
396  if ( client->sess.spectatorState == SPECTATOR_FOLLOW )
397  {
398    gclient_t *cl;
399    if ( client->sess.spectatorClient >= 0 )
400    {
401      cl = &level.clients[ client->sess.spectatorClient ];
402      if ( cl->sess.sessionTeam != TEAM_SPECTATOR )
403        doPmove = qfalse;
404    }
405  }
406
407  if (doPmove)
408  {
409    // in case the client entered the queue while following a teammate
410    if( ( client->pers.teamSelection == PTE_ALIENS &&
411          G_SearchSpawnQueue( &level.alienSpawnQueue, ent-g_entities ) ) ||
412        ( client->pers.teamSelection == PTE_HUMANS &&
413          G_SearchSpawnQueue( &level.humanSpawnQueue, ent-g_entities ) ) )
414    {
415      client->ps.pm_flags |= PMF_QUEUED;
416    }
417
418    client->ps.speed = BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] );
419
420    client->ps.stats[ STAT_STAMINA ] = 0;
421    client->ps.stats[ STAT_MISC ] = 0;
422    client->ps.stats[ STAT_BUILDABLE ] = 0;
423    client->ps.stats[ STAT_PCLASS ] = PCL_NONE;
424    client->ps.weapon = WP_NONE;
425
426    // set up for pmove
427    memset( &pm, 0, sizeof( pm ) );
428    pm.ps = &client->ps;
429    pm.cmd = *ucmd;
430    pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies
431    pm.trace = trap_Trace;
432    pm.pointcontents = trap_PointContents;
433
434    // perform a pmove
435    Pmove( &pm );
436
437    // save results of pmove
438    VectorCopy( client->ps.origin, ent->s.origin );
439
440    G_TouchTriggers( ent );
441    trap_UnlinkEntity( ent );
442
443    if( ( attack1 || attack3 ) && ( client->ps.pm_flags & PMF_QUEUED ) )
444    {
445      if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
446        G_RemoveFromSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum );
447      else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
448        G_RemoveFromSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum );
449
450      client->pers.classSelection = PCL_NONE;
451      client->ps.stats[ STAT_PCLASS ] = PCL_NONE;
452    }
453   
454    if( attack1 && client->pers.classSelection == PCL_NONE )
455    {
456      if( client->pers.teamSelection == PTE_NONE )
457        G_TriggerMenu( client->ps.clientNum, MN_TEAM );
458      else if( client->pers.teamSelection == PTE_ALIENS )
459        G_TriggerMenu( client->ps.clientNum, MN_A_CLASS );
460      else if( client->pers.teamSelection == PTE_HUMANS )
461        G_TriggerMenu( client->ps.clientNum, MN_H_SPAWN );
462    }
463
464    //set the queue position for the client side
465    if( client->ps.pm_flags & PMF_QUEUED )
466    {
467      if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
468      {
469        client->ps.persistant[ PERS_QUEUEPOS ] =
470          G_GetPosInSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum );
471      }
472      else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
473      {
474        client->ps.persistant[ PERS_QUEUEPOS ] =
475          G_GetPosInSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum );
476      }
477    }
478  }
479
480  else if( attack1 && ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
481  {
482    G_StopFollowing( ent );
483    client->pers.classSelection = PCL_NONE;
484    if( client->pers.teamSelection == PTE_NONE )
485      G_TriggerMenu( ent-g_entities, MN_TEAM );
486    else if( client->pers.teamSelection == PTE_ALIENS )
487      G_TriggerMenu( ent-g_entities, MN_A_CLASS );
488    else if( client->pers.teamSelection == PTE_HUMANS )
489      G_TriggerMenu( ent-g_entities, MN_H_SPAWN );
490  }
491 
492  if( attack3 )
493  {
494    G_ToggleFollow( ent );
495  }
496}
497
498
499
500/*
501=================
502ClientInactivityTimer
503
504Returns qfalse if the client is dropped
505=================
506*/
507qboolean ClientInactivityTimer( gclient_t *client )
508{
509  if( ! g_inactivity.integer )
510  {
511    // give everyone some time, so if the operator sets g_inactivity during
512    // gameplay, everyone isn't kicked
513    client->inactivityTime = level.time + 60 * 1000;
514    client->inactivityWarning = qfalse;
515  }
516  else if( client->pers.cmd.forwardmove ||
517           client->pers.cmd.rightmove ||
518           client->pers.cmd.upmove ||
519           ( client->pers.cmd.buttons & BUTTON_ATTACK ) )
520  {
521    client->inactivityTime = level.time + g_inactivity.integer * 1000;
522    client->inactivityWarning = qfalse;
523  }
524  else if( !client->pers.localClient )
525  {
526    if( level.time > client->inactivityTime )
527    {
528      trap_DropClient( client - level.clients, "Dropped due to inactivity" );
529      return qfalse;
530    }
531
532    if( level.time > client->inactivityTime - 10000 && !client->inactivityWarning )
533    {
534      client->inactivityWarning = qtrue;
535      trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" );
536    }
537  }
538
539  return qtrue;
540}
541
542/*
543==================
544ClientTimerActions
545
546Actions that happen once a second
547==================
548*/
549void ClientTimerActions( gentity_t *ent, int msec )
550{
551  gclient_t *client;
552  usercmd_t *ucmd;
553  int       aForward, aRight;
554  qboolean  walking = qfalse, stopped = qfalse,
555            crouched = qfalse, jumping = qfalse,
556            strafing = qfalse;
557
558  ucmd = &ent->client->pers.cmd;
559
560  aForward  = abs( ucmd->forwardmove );
561  aRight    = abs( ucmd->rightmove );
562
563  if( aForward == 0 && aRight == 0 )
564    stopped = qtrue;
565  else if( aForward <= 64 && aRight <= 64 )
566    walking = qtrue;
567
568  if( aRight > 0 )
569    strafing = qtrue;
570
571  if( ucmd->upmove > 0 )
572    jumping = qtrue;
573  else if( ucmd->upmove < 0 )
574    crouched = qtrue;
575
576  client = ent->client;
577  client->time100 += msec;
578  client->time1000 += msec;
579  client->time10000 += msec;
580
581  while ( client->time100 >= 100 )
582  {
583    client->time100 -= 100;
584
585    //if not trying to run then not trying to sprint
586    if( walking || stopped )
587      client->ps.stats[ STAT_STATE ] &= ~SS_SPEEDBOOST;
588
589    if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) )
590    {
591      //don't run when jetpack on
592      client->ps.stats[ STAT_STATE ] &= ~SS_SPEEDBOOST;
593    }
594
595    if( ( client->ps.stats[ STAT_STATE ] & SS_SPEEDBOOST ) && !crouched )
596    {
597      //subtract stamina
598      if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) )
599        client->ps.stats[ STAT_STAMINA ] -= STAMINA_LARMOUR_TAKE;
600      else
601        client->ps.stats[ STAT_STAMINA ] -= STAMINA_SPRINT_TAKE;
602
603      if( client->ps.stats[ STAT_STAMINA ] < -MAX_STAMINA )
604        client->ps.stats[ STAT_STAMINA ] = -MAX_STAMINA;
605    }
606
607    if( walking || crouched )
608    {
609      //restore stamina
610      client->ps.stats[ STAT_STAMINA ] += STAMINA_WALK_RESTORE;
611
612      if( client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA )
613        client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA;
614    }
615    else if( stopped )
616    {
617      //restore stamina faster
618      client->ps.stats[ STAT_STAMINA ] += STAMINA_STOP_RESTORE;
619
620      if( client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA )
621        client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA;
622    }
623
624    //client is charging up for a pounce
625    if( client->ps.weapon == WP_ALEVEL3 || client->ps.weapon == WP_ALEVEL3_UPG )
626    {
627      int pounceSpeed = 0;
628
629      if( client->ps.weapon == WP_ALEVEL3 )
630        pounceSpeed = LEVEL3_POUNCE_SPEED;
631      else if( client->ps.weapon == WP_ALEVEL3_UPG )
632        pounceSpeed = LEVEL3_POUNCE_UPG_SPEED;
633
634      if( client->ps.stats[ STAT_MISC ] < pounceSpeed && ucmd->buttons & BUTTON_ATTACK2 )
635        client->ps.stats[ STAT_MISC ] += ( 100.0f / (float)LEVEL3_POUNCE_CHARGE_TIME ) * pounceSpeed;
636
637      if( client->ps.stats[ STAT_MISC ] > pounceSpeed )
638        client->ps.stats[ STAT_MISC ] = pounceSpeed;
639    }
640
641    //client is charging up for a... charge
642    if( client->ps.weapon == WP_ALEVEL4 )
643    {
644      if( client->ps.stats[ STAT_MISC ] < LEVEL4_CHARGE_TIME && ucmd->buttons & BUTTON_ATTACK2 &&
645          !client->charging )
646      {
647        client->charging = qfalse; //should already be off, just making sure
648        client->ps.stats[ STAT_STATE ] &= ~SS_CHARGING;
649
650        if( ucmd->forwardmove > 0 )
651        {
652          //trigger charge sound...is quite annoying
653          //if( client->ps.stats[ STAT_MISC ] <= 0 )
654          //  G_AddEvent( ent, EV_LEV4_CHARGE_PREPARE, 0 );
655
656          client->ps.stats[ STAT_MISC ] += (int)( 100 * (float)LEVEL4_CHARGE_CHARGE_RATIO );
657
658          if( client->ps.stats[ STAT_MISC ] > LEVEL4_CHARGE_TIME )
659            client->ps.stats[ STAT_MISC ] = LEVEL4_CHARGE_TIME;
660        }
661        else
662          client->ps.stats[ STAT_MISC ] = 0;
663      }
664
665      if( !( ucmd->buttons & BUTTON_ATTACK2 ) || client->charging ||
666          client->ps.stats[ STAT_MISC ] == LEVEL4_CHARGE_TIME )
667      {
668        if( client->ps.stats[ STAT_MISC ] > LEVEL4_MIN_CHARGE_TIME )
669        {
670          client->ps.stats[ STAT_MISC ] -= 100;
671
672          if( client->charging == qfalse )
673            G_AddEvent( ent, EV_LEV4_CHARGE_START, 0 );
674
675          client->charging = qtrue;
676          client->ps.stats[ STAT_STATE ] |= SS_CHARGING;
677
678          //if the charger has stopped moving take a chunk of charge away
679          if( VectorLength( client->ps.velocity ) < 64.0f || strafing )
680            client->ps.stats[ STAT_MISC ] = client->ps.stats[ STAT_MISC ] / 2;
681
682          //can't charge backwards
683          if( ucmd->forwardmove < 0 )
684            client->ps.stats[ STAT_MISC ] = 0;
685        }
686        else
687          client->ps.stats[ STAT_MISC ] = 0;
688
689
690        if( client->ps.stats[ STAT_MISC ] <= 0 )
691        {
692          client->ps.stats[ STAT_MISC ] = 0;
693          client->charging = qfalse;
694          client->ps.stats[ STAT_STATE ] &= ~SS_CHARGING;
695        }
696      }
697    }
698
699    //client is charging up an lcannon
700    if( client->ps.weapon == WP_LUCIFER_CANNON )
701    {
702      int ammo;
703
704      BG_UnpackAmmoArray( WP_LUCIFER_CANNON, client->ps.ammo, client->ps.powerups, &ammo, NULL );
705
706      if( client->ps.stats[ STAT_MISC ] < LCANNON_TOTAL_CHARGE && ucmd->buttons & BUTTON_ATTACK )
707        client->ps.stats[ STAT_MISC ] += ( 100.0f / LCANNON_CHARGE_TIME ) * LCANNON_TOTAL_CHARGE;
708
709      if( client->ps.stats[ STAT_MISC ] > LCANNON_TOTAL_CHARGE )
710        client->ps.stats[ STAT_MISC ] = LCANNON_TOTAL_CHARGE;
711
712      if( client->ps.stats[ STAT_MISC ] > ( ammo * LCANNON_TOTAL_CHARGE ) / 10 )
713        client->ps.stats[ STAT_MISC ] = ammo * LCANNON_TOTAL_CHARGE / 10;
714    }
715
716    switch( client->ps.weapon )
717    {
718      case WP_ABUILD:
719      case WP_ABUILD2:
720      case WP_HBUILD:
721      case WP_HBUILD2:
722        //set validity bit on buildable
723        if( ( client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE )
724        {
725          int     dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] );
726          vec3_t  dummy;
727
728          if( G_CanBuild( ent, client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT,
729                          dist, dummy ) == IBE_NONE )
730            client->ps.stats[ STAT_BUILDABLE ] |= SB_VALID_TOGGLEBIT;
731          else
732            client->ps.stats[ STAT_BUILDABLE ] &= ~SB_VALID_TOGGLEBIT;
733        }
734
735      case WP_BLASTER:
736        //update build timer
737        if( client->ps.stats[ STAT_MISC ] > 0 )
738          client->ps.stats[ STAT_MISC ] -= 100;
739
740        if( client->ps.stats[ STAT_MISC ] < 0 )
741          client->ps.stats[ STAT_MISC ] = 0;
742        break;
743
744      default:
745        break;
746    }
747
748    if( client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE )
749    {
750      int remainingStartupTime = MEDKIT_STARTUP_TIME - ( level.time - client->lastMedKitTime );
751
752      if( remainingStartupTime < 0 )
753      {
754        if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] &&
755            ent->client->medKitHealthToRestore &&
756            ent->client->ps.pm_type != PM_DEAD )
757        {
758          ent->client->medKitHealthToRestore--;
759          do_health( ent,1 );
760        }
761        else
762          ent->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE;
763      }
764      else
765      {
766        if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] &&
767            ent->client->medKitHealthToRestore &&
768            ent->client->ps.pm_type != PM_DEAD )
769        {
770          //partial increase
771          if( level.time > client->medKitIncrementTime )
772          {
773            ent->client->medKitHealthToRestore--;
774            do_health( ent,1 );
775
776            client->medKitIncrementTime = level.time +
777              ( remainingStartupTime / MEDKIT_STARTUP_SPEED );
778          }
779        }
780        else
781          ent->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE;
782      }
783    }
784  }
785
786  while( client->time1000 >= 1000 )
787  {
788    client->time1000 -= 1000;
789
790    //jetpack power management
791    if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) )
792    {
793      if(BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) )
794      {
795        if(g_jetpackLimit.integer > 0)
796        {
797          //decrease jetpack power
798          if(client->jetpack_power > 0)
799          {
800            client->jetpack_power--;
801
802            //if no power, can't reactive jetpack for a moment
803            if(client->jetpack_power == 0)
804              client->jetpack_beat = g_jetpackLimit.integer / 10;
805          }
806        }
807      }
808      else
809      {
810        //recover jetpack
811        if(client->jetpack_beat > 0)
812          client->jetpack_beat--;
813        else if( level.reactorPresent && client->jetpack_power < g_jetpackLimit.integer)
814          client->jetpack_power++;
815      }
816    }
817
818    //client is poisoned
819    if( client->ps.stats[ STAT_STATE ] & SS_POISONED )
820    {
821      int i;
822      int seconds = ( ( level.time - client->lastPoisonTime ) / 1000 ) + 1;
823      int damage = ALIEN_POISON_DMG, damage2 = 0;
824
825      for( i = 0; i < seconds; i++ )
826      {
827        if( i == seconds - 1 )
828          damage2 = damage;
829
830        damage *= ALIEN_POISON_DIVIDER;
831      }
832
833      damage = damage2 - damage;
834
835      G_Damage( ent, client->lastPoisonClient, client->lastPoisonClient, NULL, NULL,
836                damage, 0, MOD_POISON );
837    }
838
839    //replenish alien health
840    if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS &&
841      level.surrenderTeam != PTE_ALIENS )
842    {
843      int       entityList[ MAX_GENTITIES ];
844      vec3_t    range = { LEVEL4_REGEN_RANGE, LEVEL4_REGEN_RANGE, LEVEL4_REGEN_RANGE };
845      vec3_t    mins, maxs;
846      int       i, num;
847      gentity_t *boostEntity;
848      float     modifier = 1.0f;
849
850      VectorAdd( client->ps.origin, range, maxs );
851      VectorSubtract( client->ps.origin, range, mins );
852
853      num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
854      for( i = 0; i < num; i++ )
855      {
856        boostEntity = &g_entities[ entityList[ i ] ];
857
858        if( boostEntity->client && boostEntity->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS &&
859            boostEntity->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL4 )
860        {
861          modifier = LEVEL4_REGEN_MOD;
862          break;
863        }
864        else if( boostEntity->s.eType == ET_BUILDABLE &&
865            boostEntity->s.modelindex == BA_A_BOOSTER &&
866            boostEntity->spawned )
867        {
868          modifier = BOOSTER_REGEN_MOD;
869          break;
870        }
871      }
872
873      if( ent->health > 0 && ent->health < client->ps.stats[ STAT_MAX_HEALTH ] &&
874          ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time )
875        do_health( ent,BG_FindRegenRateForClass( client->ps.stats[ STAT_PCLASS ] ) * modifier );
876
877      if( ent->health > client->ps.stats[ STAT_MAX_HEALTH ] )
878        ent->health = client->ps.stats[ STAT_MAX_HEALTH ];
879    }
880   
881    // turn off life support when a team admits defeat
882    if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS &&
883      level.surrenderTeam == PTE_ALIENS )
884    {
885      G_Damage( ent, NULL, NULL, NULL, NULL,
886        BG_FindRegenRateForClass( client->ps.stats[ STAT_PCLASS ] ),
887        DAMAGE_NO_ARMOR, MOD_SUICIDE );
888    }
889    else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS &&
890      level.surrenderTeam == PTE_HUMANS )
891    {
892      G_Damage( ent, NULL, NULL, NULL, NULL, 5, DAMAGE_NO_ARMOR, MOD_SUICIDE );
893    }
894  }
895
896  while( client->time10000 >= 10000 )
897  {
898    client->time10000 -= 10000;
899
900    if( client->ps.weapon == WP_ALEVEL3_UPG )
901    {
902      int ammo, maxAmmo;
903
904      BG_FindAmmoForWeapon( WP_ALEVEL3_UPG, &maxAmmo, NULL );
905      BG_UnpackAmmoArray( WP_ALEVEL3_UPG, client->ps.ammo, client->ps.powerups, &ammo, NULL );
906
907      if( ammo < maxAmmo )
908      {
909        ammo++;
910        BG_PackAmmoArray( WP_ALEVEL3_UPG, client->ps.ammo, client->ps.powerups, ammo, 0 );
911      }
912    }
913  }
914}
915
916/*
917====================
918ClientIntermissionThink
919====================
920*/
921void ClientIntermissionThink( gclient_t *client )
922{
923  client->ps.eFlags &= ~EF_TALK;
924  client->ps.eFlags &= ~EF_FIRING;
925  client->ps.eFlags &= ~EF_FIRING2;
926
927  // the level will exit when everyone wants to or after timeouts
928
929  // swap and latch button actions
930  client->oldbuttons = client->buttons;
931  client->buttons = client->pers.cmd.buttons;
932  if( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) )
933    client->readyToExit = 1;
934}
935
936
937/*
938================
939ClientEvents
940
941Events will be passed on to the clients for presentation,
942but any server game effects are handled here
943================
944*/
945void ClientEvents( gentity_t *ent, int oldEventSequence )
946{
947  int       i;
948  int       event;
949  gclient_t *client;
950  int       damage;
951  vec3_t    dir;
952  vec3_t    point, mins;
953  float     fallDistance;
954  pClass_t  class;
955
956  client = ent->client;
957  class = client->ps.stats[ STAT_PCLASS ];
958
959  if( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS )
960    oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS;
961
962  for( i = oldEventSequence; i < client->ps.eventSequence; i++ )
963  {
964    event = client->ps.events[ i & ( MAX_PS_EVENTS - 1 ) ];
965
966    switch( event )
967    {
968      case EV_FALL_MEDIUM:
969      case EV_FALL_FAR:
970        if( ent->s.eType != ET_PLAYER )
971          break;    // not in the player model
972
973        fallDistance = ( (float)client->ps.stats[ STAT_FALLDIST ] - MIN_FALL_DISTANCE ) /
974                         ( MAX_FALL_DISTANCE - MIN_FALL_DISTANCE );
975
976        if( fallDistance < 0.0f )
977          fallDistance = 0.0f;
978        else if( fallDistance > 1.0f )
979          fallDistance = 1.0f;
980
981        damage = (int)( (float)BG_FindHealthForClass( class ) *
982                 BG_FindFallDamageForClass( class ) * fallDistance );
983
984        VectorSet( dir, 0, 0, 1 );
985        BG_FindBBoxForClass( class, mins, NULL, NULL, NULL, NULL );
986        mins[ 0 ] = mins[ 1 ] = 0.0f;
987        VectorAdd( client->ps.origin, mins, point );
988
989        ent->pain_debounce_time = level.time + 200; // no normal pain sound
990        G_Damage( ent, NULL, NULL, dir, point, damage, DAMAGE_NO_LOCDAMAGE, MOD_FALLING );
991        break;
992
993      case EV_FIRE_WEAPON:
994        FireWeapon( ent );
995        break;
996
997      case EV_FIRE_WEAPON2:
998        FireWeapon2( ent );
999        break;
1000
1001      case EV_FIRE_WEAPON3:
1002        FireWeapon3( ent );
1003        break;
1004
1005      case EV_NOAMMO:
1006        break;
1007
1008      default:
1009        break;
1010    }
1011  }
1012}
1013
1014
1015/*
1016==============
1017SendPendingPredictableEvents
1018==============
1019*/
1020void SendPendingPredictableEvents( playerState_t *ps )
1021{
1022  gentity_t *t;
1023  int       event, seq;
1024  int       extEvent, number;
1025
1026  // if there are still events pending
1027  if( ps->entityEventSequence < ps->eventSequence )
1028  {
1029    // create a temporary entity for this event which is sent to everyone
1030    // except the client who generated the event
1031    seq = ps->entityEventSequence & ( MAX_PS_EVENTS - 1 );
1032    event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 );
1033    // set external event to zero before calling BG_PlayerStateToEntityState
1034    extEvent = ps->externalEvent;
1035    ps->externalEvent = 0;
1036    // create temporary entity for event
1037    t = G_TempEntity( ps->origin, event );
1038    number = t->s.number;
1039    BG_PlayerStateToEntityState( ps, &t->s, qtrue );
1040    t->s.number = number;
1041    t->s.eType = ET_EVENTS + event;
1042    t->s.eFlags |= EF_PLAYER_EVENT;
1043    t->s.otherEntityNum = ps->clientNum;
1044    // send to everyone except the client who generated the event
1045    t->r.svFlags |= SVF_NOTSINGLECLIENT;
1046    t->r.singleClient = ps->clientNum;
1047    // set back external event
1048    ps->externalEvent = extEvent;
1049  }
1050}
1051
1052/*
1053==============
1054 G_UnlaggedStore
1055
1056 Called on every server frame.  Stores position data for the client at that
1057 into client->unlaggedHist[] and the time into level.unlaggedTimes[]. 
1058 This data is used by G_UnlaggedCalc()
1059==============
1060*/
1061void G_UnlaggedStore( void )
1062{
1063  int i = 0;
1064  gentity_t *ent;
1065  unlagged_t *save;
1066 
1067  if( !g_unlagged.integer )
1068    return;
1069  level.unlaggedIndex++; 
1070  if( level.unlaggedIndex >= MAX_UNLAGGED_MARKERS )
1071    level.unlaggedIndex = 0;
1072
1073  level.unlaggedTimes[ level.unlaggedIndex ] = level.time;
1074 
1075  for( i = 0; i < level.maxclients; i++ )
1076  {
1077    ent = &g_entities[ i ];
1078    save = &ent->client->unlaggedHist[ level.unlaggedIndex ];
1079    save->used = qfalse; 
1080    if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) )
1081      continue;
1082    if( ent->client->pers.connected != CON_CONNECTED )
1083      continue; 
1084    VectorCopy( ent->r.mins, save->mins );
1085    VectorCopy( ent->r.maxs, save->maxs );
1086    VectorCopy( ent->s.pos.trBase, save->origin );
1087    save->used = qtrue;
1088  }
1089}
1090
1091/*
1092==============
1093 G_UnlaggedClear
1094 
1095 Mark all unlaggedHist[] markers for this client invalid.  Useful for
1096 preventing teleporting and death.
1097==============
1098*/
1099void G_UnlaggedClear( gentity_t *ent )
1100{
1101  int i;
1102
1103  for( i = 0; i < MAX_UNLAGGED_MARKERS; i++ )
1104    ent->client->unlaggedHist[ i ].used = qfalse;
1105}
1106
1107/*
1108==============
1109 G_UnlaggedCalc
1110
1111 Loops through all active clients and calculates their predicted position
1112 for time then stores it in client->unlaggedCalc
1113==============
1114*/
1115void G_UnlaggedCalc( int time, gentity_t *rewindEnt )
1116{
1117  int i = 0;
1118  gentity_t *ent;
1119  int startIndex = level.unlaggedIndex;
1120  int stopIndex = -1;
1121  int frameMsec = 0;
1122  float lerp = 0.5f;
1123
1124  if( !g_unlagged.integer )
1125    return;
1126 
1127  // clear any calculated values from a previous run
1128  for( i = 0; i < level.maxclients; i++ )
1129  {
1130    ent = &g_entities[ i ];
1131    ent->client->unlaggedCalc.used = qfalse;
1132  }
1133
1134  for( i = 0; i < MAX_UNLAGGED_MARKERS; i++ )
1135  {
1136    if( level.unlaggedTimes[ startIndex ] <= time )
1137      break;
1138    stopIndex = startIndex;
1139    if( --startIndex < 0 )
1140      startIndex = MAX_UNLAGGED_MARKERS - 1;
1141  }
1142  if( i == MAX_UNLAGGED_MARKERS )
1143  {
1144    // if we searched all markers and the oldest one still isn't old enough
1145    // just use the oldest marker with no lerping
1146    lerp = 0.0f;
1147  }
1148
1149  // client is on the current frame, no need for unlagged
1150  if( stopIndex == -1 )
1151    return;
1152
1153  // lerp between two markers
1154  frameMsec = level.unlaggedTimes[ stopIndex ] -
1155    level.unlaggedTimes[ startIndex ];
1156  if( frameMsec > 0 )
1157  {
1158    lerp = ( float )( time - level.unlaggedTimes[ startIndex ] )
1159      / ( float )frameMsec; 
1160  }
1161 
1162  for( i = 0; i < level.maxclients; i++ )
1163  {
1164    ent = &g_entities[ i ];
1165    if( ent == rewindEnt )
1166      continue;
1167    if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) )
1168      continue;
1169    if( ent->client->pers.connected != CON_CONNECTED )
1170      continue;
1171    if( !ent->client->unlaggedHist[ startIndex ].used )
1172      continue;
1173    if( !ent->client->unlaggedHist[ stopIndex ].used )
1174      continue;
1175
1176    // between two unlagged markers
1177    VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].mins,
1178      ent->client->unlaggedHist[ stopIndex ].mins,
1179      ent->client->unlaggedCalc.mins );
1180    VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].maxs,
1181      ent->client->unlaggedHist[ stopIndex ].maxs,
1182      ent->client->unlaggedCalc.maxs );
1183    VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].origin,
1184      ent->client->unlaggedHist[ stopIndex ].origin,
1185      ent->client->unlaggedCalc.origin );
1186
1187    ent->client->unlaggedCalc.used = qtrue;
1188  }
1189}
1190
1191/*
1192==============
1193 G_UnlaggedOff
1194
1195 Reverses the changes made to all active clients by G_UnlaggedOn()
1196==============
1197*/
1198void G_UnlaggedOff( void )
1199{
1200  int i = 0;
1201  gentity_t *ent;
1202 
1203  if( !g_unlagged.integer )
1204    return;
1205 
1206  for( i = 0; i < level.maxclients; i++ )
1207  {
1208    ent = &g_entities[ i ];
1209    if( !ent->client->unlaggedBackup.used )
1210      continue;
1211    VectorCopy( ent->client->unlaggedBackup.mins, ent->r.mins );
1212    VectorCopy( ent->client->unlaggedBackup.maxs, ent->r.maxs );
1213    VectorCopy( ent->client->unlaggedBackup.origin, ent->r.currentOrigin );
1214    ent->client->unlaggedBackup.used = qfalse;
1215    trap_LinkEntity( ent );
1216  }
1217}
1218
1219/*
1220==============
1221 G_UnlaggedOn
1222
1223 Called after G_UnlaggedCalc() to apply the calculated values to all active
1224 clients.  Once finished tracing, G_UnlaggedOff() must be called to restore
1225 the clients' position data
1226
1227 As an optimization, all clients that have an unlagged position that is
1228 not touchable at "range" from "muzzle" will be ignored.  This is required
1229 to prevent a huge amount of trap_LinkEntity() calls per user cmd.
1230==============
1231*/
1232
1233void G_UnlaggedOn( vec3_t muzzle, float range )
1234{
1235  int i = 0;
1236  gentity_t *ent;
1237  unlagged_t *calc;
1238 
1239  if( !g_unlagged.integer )
1240    return;
1241 
1242  for( i = 0; i < level.maxclients; i++ )
1243  {
1244    ent = &g_entities[ i ];
1245    calc = &ent->client->unlaggedCalc;
1246
1247    if( !calc->used )
1248      continue;
1249    if( ent->client->unlaggedBackup.used )
1250      continue;
1251    if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) )
1252      continue;
1253    if( VectorCompare( ent->r.currentOrigin, calc->origin ) )
1254      continue;
1255    if( muzzle )
1256    {
1257      float r1 = Distance( calc->origin, calc->maxs );
1258      float r2 = Distance( calc->origin, calc->mins );
1259      float maxRadius = ( r1 > r2 ) ? r1 : r2;
1260
1261      if( Distance( muzzle, calc->origin ) > range + maxRadius )
1262        continue; 
1263    }
1264
1265    // create a backup of the real positions
1266    VectorCopy( ent->r.mins, ent->client->unlaggedBackup.mins );
1267    VectorCopy( ent->r.maxs, ent->client->unlaggedBackup.maxs );
1268    VectorCopy( ent->r.currentOrigin, ent->client->unlaggedBackup.origin );
1269    ent->client->unlaggedBackup.used = qtrue;
1270
1271    // move the client to the calculated unlagged position
1272    VectorCopy( calc->mins, ent->r.mins );
1273    VectorCopy( calc->maxs, ent->r.maxs );
1274    VectorCopy( calc->origin, ent->r.currentOrigin );
1275    trap_LinkEntity( ent );
1276  }
1277}
1278/*
1279==============
1280 G_UnlaggedDetectCollisions
1281
1282 cgame prediction will predict a client's own position all the way up to
1283 the current time, but only updates other player's positions up to the
1284 postition sent in the most recent snapshot.
1285
1286 This allows player X to essentially "move through" the position of player Y
1287 when player X's cmd is processed with Pmove() on the server.  This is because
1288 player Y was clipping player X's Pmove() on his client, but when the same
1289 cmd is processed with Pmove on the server it is not clipped.
1290
1291 Long story short (too late): don't use unlagged positions for players who
1292 were blocking this player X's client-side Pmove().  This makes the assumption
1293 that if player X's movement was blocked in the client he's going to still
1294 be up against player Y when the Pmove() is run on the server with the
1295 same cmd.
1296
1297 NOTE: this must be called after Pmove() and G_UnlaggedCalc()
1298==============
1299*/
1300static void G_UnlaggedDetectCollisions( gentity_t *ent )
1301{
1302  unlagged_t *calc;
1303  trace_t tr;
1304  float r1, r2;
1305  float range;
1306
1307  if( !g_unlagged.integer )
1308    return;
1309
1310  calc = &ent->client->unlaggedCalc;
1311
1312  // if the client isn't moving, this is not necessary
1313  if( VectorCompare( ent->client->oldOrigin, ent->client->ps.origin ) )
1314    return;
1315
1316  range = Distance( ent->client->oldOrigin, ent->client->ps.origin );
1317
1318  // increase the range by the player's largest possible radius since it's
1319  // the players bounding box that collides, not their origin
1320  r1 = Distance( calc->origin, calc->mins );
1321  r2 = Distance( calc->origin, calc->maxs );
1322  range += ( r1 > r2 ) ? r1 : r2;
1323
1324  G_UnlaggedOn( ent->client->oldOrigin, range );
1325
1326  trap_Trace(&tr, ent->client->oldOrigin, ent->r.mins, ent->r.maxs,
1327    ent->client->ps.origin, ent->s.number,  MASK_PLAYERSOLID );
1328  if( tr.entityNum >= 0 && tr.entityNum < MAX_CLIENTS )
1329    g_entities[ tr.entityNum ].client->unlaggedCalc.used = qfalse; 
1330
1331  G_UnlaggedOff( );
1332}
1333
1334static void G_CheckZap( gentity_t *ent )
1335{
1336  int i;
1337
1338  if( !ent->zapping )
1339  {
1340    // clear out established targets
1341    for( i = 0; i < LEVEL2_AREAZAP_MAX_TARGETS; i++ )
1342    {
1343      ent->zapTargets[ i ] = -1;
1344    }
1345    ent->zapDmg = 0.0f;
1346  }
1347  ent->wasZapping = ent->zapping;
1348  ent->zapping = qfalse;
1349
1350  if( ent->client->ps.weapon == WP_ALEVEL2_UPG &&
1351    ( ent->client->pers.cmd.buttons & BUTTON_ATTACK2 ) )
1352  {
1353    ent->zapping = qtrue;
1354  }
1355 
1356  if( ent->wasZapping && !ent->zapping )
1357    ent->client->ps.weaponTime = LEVEL2_AREAZAP_REPEAT;
1358}
1359
1360/*
1361==============
1362ClientThink
1363
1364This will be called once for each client frame, which will
1365usually be a couple times for each server frame on fast clients.
1366
1367If "g_synchronousClients 1" is set, this will be called exactly
1368once for each server frame, which makes for smooth demo recording.
1369==============
1370*/
1371void ClientThink_real( gentity_t *ent )
1372{
1373  gclient_t *client;
1374  pmove_t   pm;
1375  int       oldEventSequence;
1376  int       msec;
1377  usercmd_t *ucmd;
1378
1379  client = ent->client;
1380
1381  // don't think if the client is not yet connected (and thus not yet spawned in)
1382  if( client->pers.connected != CON_CONNECTED )
1383    return;
1384
1385  // mark the time, so the connection sprite can be removed
1386  ucmd = &ent->client->pers.cmd;
1387
1388  // sanity check the command time to prevent speedup cheating
1389  if( ucmd->serverTime > level.time + 200 )
1390  {
1391    ucmd->serverTime = level.time + 200;
1392//    G_Printf("serverTime <<<<<\n" );
1393  }
1394
1395  if( ucmd->serverTime < level.time - 1000 )
1396  {
1397    ucmd->serverTime = level.time - 1000;
1398//    G_Printf("serverTime >>>>>\n" );
1399  }
1400
1401  // ucmd->serverTime is a client predicted value, but it works for making a
1402  // replacement for client->ps.ping when in SPECTATOR_FOLLOW
1403  client->pers.ping = level.time - ucmd->serverTime;
1404
1405  // account for the one frame of delay on client side
1406  client->pers.ping -= level.time - level.previousTime;
1407
1408  // account for the time that's elapsed since the last ClientEndFrame()
1409  client->pers.ping += trap_Milliseconds() - level.frameMsec;
1410
1411  if( client->pers.ping < 0 )
1412    client->pers.ping = 0;
1413
1414  msec = ucmd->serverTime - client->ps.commandTime;
1415  // following others may result in bad times, but we still want
1416  // to check for follow toggles
1417  if( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW )
1418    return;
1419
1420  if( msec > 200 )
1421    msec = 200;
1422
1423  if( ucmd->serverTime < level.time - g_unlagged.integer )
1424     client->unlaggedTime = level.time - g_unlagged.integer;
1425  else
1426     client->unlaggedTime = ucmd->serverTime;
1427
1428  if( pmove_msec.integer < 8 )
1429    trap_Cvar_Set( "pmove_msec", "8" );
1430  else if( pmove_msec.integer > 33 )
1431    trap_Cvar_Set( "pmove_msec", "33" );
1432
1433  if( pmove_fixed.integer || client->pers.pmoveFixed )
1434  {
1435    ucmd->serverTime = ( ( ucmd->serverTime + pmove_msec.integer - 1 ) / pmove_msec.integer ) * pmove_msec.integer;
1436    //if (ucmd->serverTime - client->ps.commandTime <= 0)
1437    //  return;
1438  }
1439
1440  //
1441  // check for exiting intermission
1442  //
1443  if( level.intermissiontime )
1444  {
1445    ClientIntermissionThink( client );
1446    return;
1447  }
1448
1449  // spectators don't do much
1450  if( client->sess.sessionTeam == TEAM_SPECTATOR )
1451  {
1452    if( client->sess.spectatorState == SPECTATOR_SCOREBOARD )
1453      return;
1454
1455    SpectatorThink( ent, ucmd );
1456    return;
1457  }
1458
1459  if (g_freeFunds.integer)
1460  {
1461     // give full evo/credits
1462     if (client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS)
1463     {
1464        client->ps.persistant[ PERS_CREDIT ] = ALIEN_MAX_KILLS;
1465     }
1466     else
1467     {
1468        client->ps.persistant[ PERS_CREDIT ] = HUMAN_MAX_CREDITS;
1469     }
1470  }
1471
1472  G_UpdatePTRConnection( client );
1473
1474  // check for inactivity timer, but never drop the local client of a non-dedicated server
1475  if( !ClientInactivityTimer( client ) )
1476    return;
1477
1478  // calculate where ent is currently seeing all the other active clients
1479  G_UnlaggedCalc( ent->client->unlaggedTime, ent );
1480
1481  if( client->noclip )
1482    client->ps.pm_type = PM_NOCLIP;
1483  else if( client->ps.stats[ STAT_HEALTH ] <= 0 )
1484    client->ps.pm_type = PM_DEAD;
1485  else if( client->ps.stats[ STAT_STATE ] & SS_INFESTING ||
1486           client->ps.stats[ STAT_STATE ] & SS_HOVELING )
1487    client->ps.pm_type = PM_FREEZE;
1488  else if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED ||
1489           client->ps.stats[ STAT_STATE ] & SS_GRABBED )
1490    client->ps.pm_type = PM_GRABBED;
1491  else if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) )
1492    client->ps.pm_type = PM_JETPACK;
1493  else
1494    client->ps.pm_type = PM_NORMAL;
1495
1496  if( client->ps.stats[ STAT_STATE ] & SS_GRABBED &&
1497      client->grabExpiryTime < level.time )
1498    client->ps.stats[ STAT_STATE ] &= ~SS_GRABBED;
1499
1500  if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED &&
1501      client->lastLockTime + LOCKBLOB_LOCKTIME < level.time )
1502    client->ps.stats[ STAT_STATE ] &= ~SS_BLOBLOCKED;
1503
1504  if( client->ps.stats[ STAT_STATE ] & SS_SLOWLOCKED &&
1505      client->lastSlowTime + ABUILDER_BLOB_TIME < level.time )
1506    client->ps.stats[ STAT_STATE ] &= ~SS_SLOWLOCKED;
1507
1508  client->ps.stats[ STAT_BOOSTTIME ] = level.time - client->lastBoostedTime;
1509
1510  if( client->ps.stats[ STAT_STATE ] & SS_BOOSTED &&
1511      client->lastBoostedTime + BOOST_TIME < level.time )
1512    client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTED;
1513
1514  if( client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED )
1515  {
1516    int timeLeft = LEVEL1_PCLOUD_TIME -
1517      ( level.time - client->lastPoisonCloudedTime );
1518 
1519    if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, client->ps.stats ) )
1520      timeLeft -= BSUIT_PCLOUD_PROTECTION;
1521    if( BG_InventoryContainsUpgrade( UP_HELMET, client->ps.stats ) )
1522      timeLeft -= HELMET_PCLOUD_PROTECTION;
1523    if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) )
1524      timeLeft -= LIGHTARMOUR_PCLOUD_PROTECTION;
1525 
1526    if( timeLeft <= 0 )
1527      client->ps.stats[ STAT_STATE ] &= ~SS_POISONCLOUDED;
1528  }
1529
1530  if( client->ps.stats[ STAT_STATE ] & SS_POISONED &&
1531      client->lastPoisonTime + ALIEN_POISON_TIME < level.time )
1532    client->ps.stats[ STAT_STATE ] &= ~SS_POISONED;
1533
1534  client->ps.gravity = g_gravity.value;
1535
1536  if( BG_InventoryContainsUpgrade( UP_MEDKIT, client->ps.stats ) &&
1537      BG_UpgradeIsActive( UP_MEDKIT, client->ps.stats ) )
1538  {
1539    //if currently using a medkit or have no need for a medkit now
1540    if( client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE ||
1541        ( client->ps.stats[ STAT_HEALTH ] == client->ps.stats[ STAT_MAX_HEALTH ] &&
1542          !( client->ps.stats[ STAT_STATE ] & SS_POISONED ) ) )
1543    {
1544      BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats );
1545    }
1546    else if( client->ps.stats[ STAT_HEALTH ] > 0 )
1547    {
1548      //remove anti toxin
1549      BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats );
1550      BG_RemoveUpgradeFromInventory( UP_MEDKIT, client->ps.stats );
1551
1552      client->ps.stats[ STAT_STATE ] &= ~SS_POISONED;
1553      client->poisonImmunityTime = level.time + MEDKIT_POISON_IMMUNITY_TIME;
1554
1555      client->ps.stats[ STAT_STATE ] |= SS_MEDKIT_ACTIVE;
1556      client->lastMedKitTime = level.time;
1557      client->medKitHealthToRestore =
1558        client->ps.stats[ STAT_MAX_HEALTH ] - client->ps.stats[ STAT_HEALTH ];
1559      client->medKitIncrementTime = level.time +
1560        ( MEDKIT_STARTUP_TIME / MEDKIT_STARTUP_SPEED );
1561
1562      G_AddEvent( ent, EV_MEDKIT_USED, 0 );
1563    }
1564  }
1565
1566  if( BG_InventoryContainsUpgrade( UP_GRENADE, client->ps.stats ) &&
1567      BG_UpgradeIsActive( UP_GRENADE, client->ps.stats ) )
1568  {
1569    int lastWeapon = ent->s.weapon;
1570
1571    //remove grenade
1572    BG_DeactivateUpgrade( UP_GRENADE, client->ps.stats );
1573    BG_RemoveUpgradeFromInventory( UP_GRENADE, client->ps.stats );
1574
1575    //M-M-M-M-MONSTER HACK
1576    ent->s.weapon = WP_GRENADE;
1577    FireWeapon( ent );
1578    ent->s.weapon = lastWeapon;
1579  }
1580
1581  // set speed
1582  client->ps.speed = g_speed.value * BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] );
1583
1584  if( client->lastCreepSlowTime + CREEP_TIMEOUT < level.time )
1585    client->ps.stats[ STAT_STATE ] &= ~SS_CREEPSLOWED;
1586
1587  //randomly disable the jet pack if damaged
1588  if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) &&
1589      BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) )
1590  {
1591    if( ent->lastDamageTime + JETPACK_DISABLE_TIME > level.time )
1592    {
1593      if( random( ) > JETPACK_DISABLE_CHANCE )
1594        client->ps.pm_type = PM_NORMAL;
1595    }
1596
1597    //switch jetpack off if no power
1598    if( g_jetpackLimit.integer && (client->jetpack_beat || !client->jetpack_power))
1599    {
1600      BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats );
1601
1602      trap_SendServerCommand( ent - g_entities,
1603        "print \"Your jetpack is out of power\n\"" );
1604    }
1605  }
1606
1607  // set up for pmove
1608  oldEventSequence = client->ps.eventSequence;
1609
1610  memset( &pm, 0, sizeof( pm ) );
1611
1612  if( ent->flags & FL_FORCE_GESTURE )
1613  {
1614    ent->flags &= ~FL_FORCE_GESTURE;
1615    ent->client->pers.cmd.buttons |= BUTTON_GESTURE;
1616  }
1617
1618  pm.ps = &client->ps;
1619  pm.pmext = &client->pmext;
1620  pm.cmd = *ucmd;
1621
1622  if( pm.ps->pm_type == PM_DEAD )
1623    pm.tracemask = MASK_PLAYERSOLID; // & ~CONTENTS_BODY;
1624
1625  if( pm.ps->stats[ STAT_STATE ] & SS_INFESTING ||
1626      pm.ps->stats[ STAT_STATE ] & SS_HOVELING )
1627    pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY;
1628  else
1629    pm.tracemask = MASK_PLAYERSOLID;
1630
1631  pm.trace = trap_Trace;
1632  pm.pointcontents = trap_PointContents;
1633  pm.debugLevel = g_debugMove.integer;
1634  pm.noFootsteps = 0;
1635
1636  pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed;
1637  pm.pmove_msec = pmove_msec.integer;
1638
1639  VectorCopy( client->ps.origin, client->oldOrigin );
1640
1641  // moved from after Pmove -- potentially the cause of
1642  // future triggering bugs
1643  if( !ent->client->noclip )
1644    G_TouchTriggers( ent );
1645
1646  Pmove( &pm );
1647
1648  G_UnlaggedDetectCollisions( ent );
1649
1650  // save results of pmove
1651  if( ent->client->ps.eventSequence != oldEventSequence )
1652    ent->eventTime = level.time;
1653
1654  if( g_smoothClients.integer )
1655    BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue );
1656  else
1657    BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue );
1658
1659  switch( client->ps.weapon )
1660  {
1661    case WP_ALEVEL0:
1662      if( !CheckVenomAttack( ent ) )
1663      {
1664        client->ps.weaponstate = WEAPON_READY;
1665      }
1666      else
1667      {
1668        client->ps.generic1 = WPM_PRIMARY;
1669        G_AddEvent( ent, EV_FIRE_WEAPON, 0 );
1670      }
1671      break;
1672
1673    case WP_ALEVEL1:
1674    case WP_ALEVEL1_UPG:
1675      CheckGrabAttack( ent );
1676      break;
1677
1678    case WP_ALEVEL3:
1679    case WP_ALEVEL3_UPG:
1680      if( !CheckPounceAttack( ent ) )
1681      {
1682        client->ps.weaponstate = WEAPON_READY;
1683      }
1684      else
1685      {
1686        client->ps.generic1 = WPM_SECONDARY;
1687        G_AddEvent( ent, EV_FIRE_WEAPON2, 0 );
1688      }
1689      break;
1690
1691    default:
1692      break;
1693  }
1694
1695  SendPendingPredictableEvents( &ent->client->ps );
1696
1697  if( !( ent->client->ps.eFlags & EF_FIRING ) )
1698    client->fireHeld = qfalse;    // for grapple
1699  if( !( ent->client->ps.eFlags & EF_FIRING2 ) )
1700    client->fire2Held = qfalse;
1701
1702  // use the snapped origin for linking so it matches client predicted versions
1703  VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin );
1704
1705  VectorCopy( pm.mins, ent->r.mins );
1706  VectorCopy( pm.maxs, ent->r.maxs );
1707
1708  ent->waterlevel = pm.waterlevel;
1709  ent->watertype = pm.watertype;
1710
1711  // touch other objects
1712  ClientImpacts( ent, &pm );
1713
1714  G_CheckZap( ent );
1715 
1716  // execute client events
1717  ClientEvents( ent, oldEventSequence );
1718
1719  // link entity now, after any personal teleporters have been used
1720  trap_LinkEntity( ent );
1721
1722  // NOTE: now copy the exact origin over otherwise clients can be snapped into solid
1723  VectorCopy( ent->client->ps.origin, ent->r.currentOrigin );
1724  VectorCopy( ent->client->ps.origin, ent->s.origin );
1725
1726  // save results of triggers and client events
1727  if( ent->client->ps.eventSequence != oldEventSequence )
1728    ent->eventTime = level.time;
1729
1730  // swap and latch button actions
1731  client->oldbuttons = client->buttons;
1732  client->buttons = ucmd->buttons;
1733  client->latched_buttons |= client->buttons & ~client->oldbuttons;
1734
1735  if( ( client->buttons & BUTTON_GETFLAG ) && !( client->oldbuttons & BUTTON_GETFLAG ) &&
1736       client->ps.stats[ STAT_HEALTH ] > 0 )
1737  {
1738    trace_t   trace;
1739    vec3_t    view, point;
1740    gentity_t *traceEnt;
1741
1742    if( client->ps.stats[ STAT_STATE ] & SS_HOVELING )
1743    {
1744      gentity_t *hovel = client->hovel;
1745
1746      //only let the player out if there is room
1747      if( !AHovel_Blocked( hovel, ent, qtrue ) )
1748      {
1749        //prevent lerping
1750        client->ps.eFlags ^= EF_TELEPORT_BIT;
1751        client->ps.eFlags &= ~EF_NODRAW;
1752        G_UnlaggedClear( ent );
1753
1754        //client leaves hovel
1755        client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING;
1756
1757        //hovel is empty
1758        G_SetBuildableAnim( hovel, BANIM_ATTACK2, qfalse );
1759        hovel->active = qfalse;
1760      }
1761      else
1762      {
1763        //exit is blocked
1764        G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL_BLOCKED );
1765      }
1766    }
1767    else
1768    {
1769#define USE_OBJECT_RANGE 64
1770
1771      int       entityList[ MAX_GENTITIES ];
1772      vec3_t    range = { USE_OBJECT_RANGE, USE_OBJECT_RANGE, USE_OBJECT_RANGE };
1773      vec3_t    mins, maxs;
1774      int       i, num;
1775
1776      //TA: look for object infront of player
1777      AngleVectors( client->ps.viewangles, view, NULL, NULL );
1778      VectorMA( client->ps.origin, USE_OBJECT_RANGE, view, point );
1779      trap_Trace( &trace, client->ps.origin, NULL, NULL, point, ent->s.number, MASK_SHOT );
1780
1781      traceEnt = &g_entities[ trace.entityNum ];
1782
1783      if( traceEnt && traceEnt->biteam == client->ps.stats[ STAT_PTEAM ] && traceEnt->use )
1784        traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context
1785      else
1786      {
1787        //no entity in front of player - do a small area search
1788
1789        VectorAdd( client->ps.origin, range, maxs );
1790        VectorSubtract( client->ps.origin, range, mins );
1791
1792        num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
1793        for( i = 0; i < num; i++ )
1794        {
1795          traceEnt = &g_entities[ entityList[ i ] ];
1796
1797          if( traceEnt && traceEnt->biteam == client->ps.stats[ STAT_PTEAM ] && traceEnt->use )
1798          {
1799            traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context
1800            break;
1801          }
1802        }
1803
1804        if( i == num && client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
1805        {
1806          if( BG_UpgradeClassAvailable( &client->ps ) )
1807          {
1808            //no nearby objects and alien - show class menu
1809            G_TriggerMenu( ent->client->ps.clientNum, MN_A_INFEST );
1810          }
1811          else
1812          {
1813            //flash frags
1814            G_AddEvent( ent, EV_ALIEN_EVOLVE_FAILED, 0 );
1815          }
1816        }
1817      }
1818    }
1819  }
1820
1821  // check for respawning
1822  if( client->ps.stats[ STAT_HEALTH ] <= 0 )
1823  {
1824    // wait for the attack button to be pressed
1825    if( level.time > client->respawnTime )
1826    {
1827      // forcerespawn is to prevent users from waiting out powerups
1828      if( g_forcerespawn.integer > 0 &&
1829        ( level.time - client->respawnTime ) > 0 )
1830      {
1831        respawn( ent );
1832        return;
1833      }
1834
1835      // pressing attack or use is the normal respawn method
1836      if( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) )
1837      {
1838        respawn( ent );
1839      }
1840    }
1841    return;
1842  }
1843
1844  if( level.framenum > client->retriggerArmouryMenu && client->retriggerArmouryMenu )
1845  {
1846    G_TriggerMenu( client->ps.clientNum, MN_H_ARMOURY );
1847
1848    client->retriggerArmouryMenu = 0;
1849  }
1850
1851  // Give clients some credit periodically
1852  if( ent->client->lastKillTime + FREEKILL_PERIOD < level.time )
1853  {
1854    if( G_TimeTilSuddenDeath( ) <= 0 )
1855    {
1856      //gotta love logic like this eh?
1857    }
1858    else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
1859      G_AddCreditToClient( ent->client, FREEKILL_ALIEN, qtrue );
1860    else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
1861      G_AddCreditToClient( ent->client, FREEKILL_HUMAN, qtrue );
1862
1863    ent->client->lastKillTime = level.time;
1864  }
1865
1866  // perform once-a-second actions
1867  ClientTimerActions( ent, msec );
1868 
1869  if( ent->suicideTime > 0 && ent->suicideTime < level.time )
1870  {
1871    ent->flags &= ~FL_GODMODE;
1872    ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0;
1873    player_die( ent, ent, ent, 100000, MOD_SUICIDE );
1874
1875    ent->suicideTime = 0;
1876  }
1877}
1878
1879/*
1880==================
1881ClientThink
1882
1883A new command has arrived from the client
1884==================
1885*/
1886void ClientThink( int clientNum )
1887{
1888  gentity_t *ent;
1889
1890  ent = g_entities + clientNum;
1891  trap_GetUsercmd( clientNum, &ent->client->pers.cmd );
1892
1893  // mark the time we got info, so we can display the
1894  // phone jack if they don't get any for a while
1895  ent->client->lastCmdTime = level.time;
1896
1897  if( !g_synchronousClients.integer )
1898    ClientThink_real( ent );
1899}
1900
1901
1902void G_RunClient( gentity_t *ent )
1903{
1904  if( !g_synchronousClients.integer )
1905    return;
1906
1907  ent->client->pers.cmd.serverTime = level.time;
1908  ClientThink_real( ent );
1909}
1910
1911
1912/*
1913==================
1914SpectatorClientEndFrame
1915
1916==================
1917*/
1918void SpectatorClientEndFrame( gentity_t *ent )
1919{
1920  gclient_t *cl;
1921  int       clientNum, flags;
1922
1923  // if we are doing a chase cam or a remote view, grab the latest info
1924  if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
1925  {
1926    clientNum = ent->client->sess.spectatorClient;
1927
1928    if( clientNum >= 0 )
1929    {
1930      cl = &level.clients[ clientNum ];
1931
1932      if( cl->pers.connected == CON_CONNECTED )
1933      {
1934        flags = ( cl->ps.eFlags & ~( EF_VOTED | EF_TEAMVOTED ) ) |
1935          ( ent->client->ps.eFlags & ( EF_VOTED | EF_TEAMVOTED ) );
1936        ent->client->ps = cl->ps;
1937        ent->client->ps.eFlags = flags;
1938        ent->client->ps.pm_flags |= PMF_FOLLOW;
1939        ent->client->ps.pm_flags &= ~PMF_QUEUED;
1940      }
1941    }
1942  }
1943}
1944
1945/*
1946==============
1947ClientEndFrame
1948
1949Called at the end of each server frame for each connected client
1950A fast client will have multiple ClientThink for each ClientEdFrame,
1951while a slow client may have multiple ClientEndFrame between ClientThink.
1952==============
1953*/
1954void ClientEndFrame( gentity_t *ent )
1955{
1956  clientPersistant_t  *pers;
1957
1958   if (ent->client->pers.floodTimer)
1959   {
1960     ent->client->pers.floodTimer -= 5;
1961     if (ent->client->pers.floodTimer < 0)
1962     ent->client->pers.floodTimer = 0;
1963   }
1964
1965  if( ent->client->sess.sessionTeam == TEAM_SPECTATOR )
1966  {
1967    SpectatorClientEndFrame( ent );
1968    return;
1969  }
1970
1971  pers = &ent->client->pers;
1972
1973  // save a copy of things from playerState in case of SPECTATOR_FOLLOW
1974  pers->score = ent->client->ps.persistant[ PERS_SCORE ];
1975  pers->credit = ent->client->ps.persistant[ PERS_CREDIT ];
1976
1977  //
1978  // If the end of unit layout is displayed, don't give
1979  // the player any normal movement attributes
1980  //
1981  if( level.intermissiontime )
1982    return;
1983
1984  // burn from lava, etc
1985  P_WorldEffects( ent );
1986
1987  // apply all the damage taken this frame
1988  P_DamageFeedback( ent );
1989
1990  // add the EF_CONNECTION flag if we haven't gotten commands recently
1991  if( level.time - ent->client->lastCmdTime > 1000 )
1992    ent->s.eFlags |= EF_CONNECTION;
1993  else
1994    ent->s.eFlags &= ~EF_CONNECTION;
1995
1996  ent->client->ps.stats[ STAT_HEALTH ] = ent->health; // FIXME: get rid of ent->health...
1997
1998  G_SetClientSound( ent );
1999
2000  G_UpdateZaps( ent );
2001
2002  // set the latest infor
2003  if( g_smoothClients.integer )
2004    BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue );
2005  else
2006    BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue );
2007
2008  SendPendingPredictableEvents( &ent->client->ps );
2009}
2010
2011void do_health( gentity_t *ent, int quantity )
2012{
2013  int i = 0;
2014
2015  if( ent->health == ent->client->ps.stats[ STAT_MAX_HEALTH ] )
2016    return;
2017
2018  ent->health += quantity;
2019  if( ent->health >= ent->client->ps.stats[ STAT_MAX_HEALTH ] )
2020  {
2021    ent->health = ent->client->ps.stats[ STAT_MAX_HEALTH ];
2022    for( i = 0; i < MAX_CLIENTS; i++ )
2023      ent->credits[ i ] = 0;
2024  }
2025}
Note: See TracBrowser for help on using the browser.