root/src/game/g_active.c @ 124:1950c7262178

Revision 124:1950c7262178, 54.3 kB (checked in by mdoison@…, 2 years ago)

merge head with tremulous r1060

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      doPmove = qtrue;
379
380  client = ent->client;
381
382  client->oldbuttons = client->buttons;
383  client->buttons = ucmd->buttons;
384
385  if( client->sess.spectatorState == SPECTATOR_LOCKED || client->sess.spectatorState == SPECTATOR_FOLLOW )
386    client->ps.pm_type = PM_FREEZE;
387  else
388    client->ps.pm_type = PM_SPECTATOR;
389
390  if ( client->sess.spectatorState == SPECTATOR_FOLLOW )
391  {
392    gclient_t *cl;
393    if ( client->sess.spectatorClient >= 0 )
394    {
395      cl = &level.clients[ client->sess.spectatorClient ];
396      if ( cl->sess.sessionTeam != TEAM_SPECTATOR )
397        doPmove = qfalse;
398    }
399  }
400
401  if (doPmove)
402  {
403    client->ps.speed = BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] );
404
405    client->ps.stats[ STAT_STAMINA ] = 0;
406    client->ps.stats[ STAT_MISC ] = 0;
407    client->ps.stats[ STAT_BUILDABLE ] = 0;
408    client->ps.stats[ STAT_PCLASS ] = PCL_NONE;
409    client->ps.weapon = WP_NONE;
410
411    // set up for pmove
412    memset( &pm, 0, sizeof( pm ) );
413    pm.ps = &client->ps;
414    pm.cmd = *ucmd;
415    pm.tracemask = MASK_DEADSOLID; // spectators can fly through bodies
416    pm.trace = trap_Trace;
417    pm.pointcontents = trap_PointContents;
418
419    // perform a pmove
420    Pmove( &pm );
421
422    // save results of pmove
423    VectorCopy( client->ps.origin, ent->s.origin );
424
425    G_TouchTriggers( ent );
426    trap_UnlinkEntity( ent );
427
428    if( ( client->buttons & BUTTON_ATTACK ) && !( client->oldbuttons & BUTTON_ATTACK ) )
429    {
430      //if waiting in a queue remove from the queue
431      if( client->ps.pm_flags & PMF_QUEUED )
432      {
433        if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
434          G_RemoveFromSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum );
435        else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
436          G_RemoveFromSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum );
437
438        client->pers.classSelection = PCL_NONE;
439        client->ps.stats[ STAT_PCLASS ] = PCL_NONE;
440      }
441      else if( client->pers.classSelection == PCL_NONE )
442      {
443        if( client->pers.teamSelection == PTE_NONE )
444          G_TriggerMenu( client->ps.clientNum, MN_TEAM );
445        else if( client->pers.teamSelection == PTE_ALIENS )
446          G_TriggerMenu( client->ps.clientNum, MN_A_CLASS );
447        else if( client->pers.teamSelection == PTE_HUMANS )
448          G_TriggerMenu( client->ps.clientNum, MN_H_SPAWN );
449      }
450    }
451
452    //set the queue position for the client side
453    if( client->ps.pm_flags & PMF_QUEUED )
454    {
455      if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
456      {
457        client->ps.persistant[ PERS_QUEUEPOS ] =
458          G_GetPosInSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum );
459      }
460      else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
461      {
462        client->ps.persistant[ PERS_QUEUEPOS ] =
463          G_GetPosInSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum );
464      }
465    }
466  }
467
468  if( ( client->buttons & BUTTON_USE_HOLDABLE ) && !( client->oldbuttons & BUTTON_USE_HOLDABLE ) )
469    G_ToggleFollow( ent );
470}
471
472
473
474/*
475=================
476ClientInactivityTimer
477
478Returns qfalse if the client is dropped
479=================
480*/
481qboolean ClientInactivityTimer( gclient_t *client )
482{
483  if( ! g_inactivity.integer )
484  {
485    // give everyone some time, so if the operator sets g_inactivity during
486    // gameplay, everyone isn't kicked
487    client->inactivityTime = level.time + 60 * 1000;
488    client->inactivityWarning = qfalse;
489  }
490  else if( client->pers.cmd.forwardmove ||
491           client->pers.cmd.rightmove ||
492           client->pers.cmd.upmove ||
493           ( client->pers.cmd.buttons & BUTTON_ATTACK ) )
494  {
495    client->inactivityTime = level.time + g_inactivity.integer * 1000;
496    client->inactivityWarning = qfalse;
497  }
498  else if( !client->pers.localClient )
499  {
500    if( level.time > client->inactivityTime )
501    {
502      trap_DropClient( client - level.clients, "Dropped due to inactivity" );
503      return qfalse;
504    }
505
506    if( level.time > client->inactivityTime - 10000 && !client->inactivityWarning )
507    {
508      client->inactivityWarning = qtrue;
509      trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" );
510    }
511  }
512
513  return qtrue;
514}
515
516/*
517==================
518ClientTimerActions
519
520Actions that happen once a second
521==================
522*/
523void ClientTimerActions( gentity_t *ent, int msec )
524{
525  gclient_t *client;
526  usercmd_t *ucmd;
527  int       aForward, aRight;
528  qboolean  walking = qfalse, stopped = qfalse,
529            crouched = qfalse, jumping = qfalse,
530            strafing = qfalse;
531  int       i;
532
533  ucmd = &ent->client->pers.cmd;
534
535  aForward  = abs( ucmd->forwardmove );
536  aRight    = abs( ucmd->rightmove );
537
538  if( aForward == 0 && aRight == 0 )
539    stopped = qtrue;
540  else if( aForward <= 64 && aRight <= 64 )
541    walking = qtrue;
542
543  if( aRight > 0 )
544    strafing = qtrue;
545
546  if( ucmd->upmove > 0 )
547    jumping = qtrue;
548  else if( ucmd->upmove < 0 )
549    crouched = qtrue;
550
551  client = ent->client;
552  client->time100 += msec;
553  client->time1000 += msec;
554  client->time10000 += msec;
555
556  while ( client->time100 >= 100 )
557  {
558    client->time100 -= 100;
559
560    //if not trying to run then not trying to sprint
561    if( walking || stopped )
562      client->ps.stats[ STAT_STATE ] &= ~SS_SPEEDBOOST;
563
564    if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) )
565    {
566      //don't run when jetpack on
567      client->ps.stats[ STAT_STATE ] &= ~SS_SPEEDBOOST;
568    }
569
570    if( ( client->ps.stats[ STAT_STATE ] & SS_SPEEDBOOST ) && !crouched )
571    {
572      //subtract stamina
573      if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) )
574        client->ps.stats[ STAT_STAMINA ] -= STAMINA_LARMOUR_TAKE;
575      else
576        client->ps.stats[ STAT_STAMINA ] -= STAMINA_SPRINT_TAKE;
577
578      if( client->ps.stats[ STAT_STAMINA ] < -MAX_STAMINA )
579        client->ps.stats[ STAT_STAMINA ] = -MAX_STAMINA;
580    }
581
582    if( walking || crouched )
583    {
584      //restore stamina
585      client->ps.stats[ STAT_STAMINA ] += STAMINA_WALK_RESTORE;
586
587      if( client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA )
588        client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA;
589    }
590    else if( stopped )
591    {
592      //restore stamina faster
593      client->ps.stats[ STAT_STAMINA ] += STAMINA_STOP_RESTORE;
594
595      if( client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA )
596        client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA;
597    }
598
599    //client is charging up for a pounce
600    if( client->ps.weapon == WP_ALEVEL3 || client->ps.weapon == WP_ALEVEL3_UPG )
601    {
602      int pounceSpeed = 0;
603
604      if( client->ps.weapon == WP_ALEVEL3 )
605        pounceSpeed = LEVEL3_POUNCE_SPEED;
606      else if( client->ps.weapon == WP_ALEVEL3_UPG )
607        pounceSpeed = LEVEL3_POUNCE_UPG_SPEED;
608
609      if( client->ps.stats[ STAT_MISC ] < pounceSpeed && ucmd->buttons & BUTTON_ATTACK2 )
610        client->ps.stats[ STAT_MISC ] += ( 100.0f / (float)LEVEL3_POUNCE_CHARGE_TIME ) * pounceSpeed;
611
612      if( !( ucmd->buttons & BUTTON_ATTACK2 ) )
613      {
614        if( client->pmext.pouncePayload > 0 )
615          client->allowedToPounce = qtrue;
616      }
617
618      if( client->ps.stats[ STAT_MISC ] > pounceSpeed )
619        client->ps.stats[ STAT_MISC ] = pounceSpeed;
620    }
621
622    //client is charging up for a... charge
623    if( client->ps.weapon == WP_ALEVEL4 )
624    {
625      if( client->ps.stats[ STAT_MISC ] < LEVEL4_CHARGE_TIME && ucmd->buttons & BUTTON_ATTACK2 &&
626          !client->charging )
627      {
628        client->charging = qfalse; //should already be off, just making sure
629        client->ps.stats[ STAT_STATE ] &= ~SS_CHARGING;
630
631        if( ucmd->forwardmove > 0 )
632        {
633          //trigger charge sound...is quite annoying
634          //if( client->ps.stats[ STAT_MISC ] <= 0 )
635          //  G_AddEvent( ent, EV_LEV4_CHARGE_PREPARE, 0 );
636
637          client->ps.stats[ STAT_MISC ] += (int)( 100 * (float)LEVEL4_CHARGE_CHARGE_RATIO );
638
639          if( client->ps.stats[ STAT_MISC ] > LEVEL4_CHARGE_TIME )
640            client->ps.stats[ STAT_MISC ] = LEVEL4_CHARGE_TIME;
641        }
642        else
643          client->ps.stats[ STAT_MISC ] = 0;
644      }
645
646      if( !( ucmd->buttons & BUTTON_ATTACK2 ) || client->charging ||
647          client->ps.stats[ STAT_MISC ] == LEVEL4_CHARGE_TIME )
648      {
649        if( client->ps.stats[ STAT_MISC ] > LEVEL4_MIN_CHARGE_TIME )
650        {
651          client->ps.stats[ STAT_MISC ] -= 100;
652
653          if( client->charging == qfalse )
654            G_AddEvent( ent, EV_LEV4_CHARGE_START, 0 );
655
656          client->charging = qtrue;
657          client->ps.stats[ STAT_STATE ] |= SS_CHARGING;
658
659          //if the charger has stopped moving take a chunk of charge away
660          if( VectorLength( client->ps.velocity ) < 64.0f || strafing )
661            client->ps.stats[ STAT_MISC ] = client->ps.stats[ STAT_MISC ] / 2;
662
663          //can't charge backwards
664          if( ucmd->forwardmove < 0 )
665            client->ps.stats[ STAT_MISC ] = 0;
666        }
667        else
668          client->ps.stats[ STAT_MISC ] = 0;
669
670
671        if( client->ps.stats[ STAT_MISC ] <= 0 )
672        {
673          client->ps.stats[ STAT_MISC ] = 0;
674          client->charging = qfalse;
675          client->ps.stats[ STAT_STATE ] &= ~SS_CHARGING;
676        }
677      }
678    }
679
680    //client is charging up an lcannon
681    if( client->ps.weapon == WP_LUCIFER_CANNON )
682    {
683      int ammo;
684
685      BG_UnpackAmmoArray( WP_LUCIFER_CANNON, client->ps.ammo, client->ps.powerups, &ammo, NULL );
686
687      if( client->ps.stats[ STAT_MISC ] < LCANNON_TOTAL_CHARGE && ucmd->buttons & BUTTON_ATTACK )
688        client->ps.stats[ STAT_MISC ] += ( 100.0f / LCANNON_CHARGE_TIME ) * LCANNON_TOTAL_CHARGE;
689
690      if( client->ps.stats[ STAT_MISC ] > LCANNON_TOTAL_CHARGE )
691        client->ps.stats[ STAT_MISC ] = LCANNON_TOTAL_CHARGE;
692
693      if( client->ps.stats[ STAT_MISC ] > ( ammo * LCANNON_TOTAL_CHARGE ) / 10 )
694        client->ps.stats[ STAT_MISC ] = ammo * LCANNON_TOTAL_CHARGE / 10;
695    }
696
697    switch( client->ps.weapon )
698    {
699      case WP_ABUILD:
700      case WP_ABUILD2:
701      case WP_HBUILD:
702      case WP_HBUILD2:
703        //set validity bit on buildable
704        if( ( client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE )
705        {
706          int     dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] );
707          vec3_t  dummy;
708
709          if( G_CanBuild( ent, client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT,
710                          dist, dummy ) == IBE_NONE )
711            client->ps.stats[ STAT_BUILDABLE ] |= SB_VALID_TOGGLEBIT;
712          else
713            client->ps.stats[ STAT_BUILDABLE ] &= ~SB_VALID_TOGGLEBIT;
714
715          // Let the client know which buildables will be removed by building
716          for( i = 0; i < MAX_POWERUPS; i++ )
717          {
718            if( i < level.numBuildablesForRemoval )
719              client->ps.powerups[ i ] = level.markedBuildables[ i ]->s.number;
720            else
721              client->ps.powerups[ i ] = 0;
722          }
723        }
724        else
725        {
726          for( i = 0; i < MAX_POWERUPS; i++ )
727            client->ps.powerups[ i ] = 0;
728        }
729
730      case WP_BLASTER:
731        //update build timer
732        if( client->ps.stats[ STAT_MISC ] > 0 )
733          client->ps.stats[ STAT_MISC ] -= 100;
734
735        if( client->ps.stats[ STAT_MISC ] < 0 )
736          client->ps.stats[ STAT_MISC ] = 0;
737        break;
738
739      default:
740        break;
741    }
742
743    if( client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE )
744    {
745      int remainingStartupTime = MEDKIT_STARTUP_TIME - ( level.time - client->lastMedKitTime );
746
747      if( remainingStartupTime < 0 )
748      {
749        if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] &&
750            ent->client->medKitHealthToRestore &&
751            ent->client->ps.pm_type != PM_DEAD )
752        {
753          ent->client->medKitHealthToRestore--;
754          do_health( ent,1 );
755        }
756        else
757          ent->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE;
758      }
759      else
760      {
761        if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] &&
762            ent->client->medKitHealthToRestore &&
763            ent->client->ps.pm_type != PM_DEAD )
764        {
765          //partial increase
766          if( level.time > client->medKitIncrementTime )
767          {
768            ent->client->medKitHealthToRestore--;
769            do_health( ent,1 );
770
771            client->medKitIncrementTime = level.time +
772              ( remainingStartupTime / MEDKIT_STARTUP_SPEED );
773          }
774        }
775        else
776          ent->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE;
777      }
778    }
779  }
780
781  while( client->time1000 >= 1000 )
782  {
783    client->time1000 -= 1000;
784
785    //jetpack power management
786    if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) )
787    {
788      if(BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) )
789      {
790        if(g_jetpackLimit.integer > 0)
791        {
792          //decrease jetpack power
793          if(client->jetpack_power > 0)
794          {
795            client->jetpack_power--;
796
797            //if no power, can't reactive jetpack for a moment
798            if(client->jetpack_power == 0)
799              client->jetpack_beat = g_jetpackLimit.integer / 10;
800          }
801        }
802      }
803      else
804      {
805        //recover jetpack
806        if(client->jetpack_beat > 0)
807          client->jetpack_beat--;
808        else if( level.reactorPresent && client->jetpack_power < g_jetpackLimit.integer)
809          client->jetpack_power++;
810      }
811    }
812
813    //client is poison clouded
814    if( client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED )
815      G_Damage( ent, client->lastPoisonCloudedClient, client->lastPoisonCloudedClient, NULL, NULL,
816                LEVEL1_PCLOUD_DMG, 0, MOD_LEVEL1_PCLOUD );
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 && boostEntity->health > 0)
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
1334/*
1335==============
1336ClientThink
1337
1338This will be called once for each client frame, which will
1339usually be a couple times for each server frame on fast clients.
1340
1341If "g_synchronousClients 1" is set, this will be called exactly
1342once for each server frame, which makes for smooth demo recording.
1343==============
1344*/
1345void ClientThink_real( gentity_t *ent )
1346{
1347  gclient_t *client;
1348  pmove_t   pm;
1349  int       oldEventSequence;
1350  int       msec;
1351  usercmd_t *ucmd;
1352
1353  client = ent->client;
1354
1355  // don't think if the client is not yet connected (and thus not yet spawned in)
1356  if( client->pers.connected != CON_CONNECTED )
1357    return;
1358
1359  // mark the time, so the connection sprite can be removed
1360  ucmd = &ent->client->pers.cmd;
1361
1362  // sanity check the command time to prevent speedup cheating
1363  if( ucmd->serverTime > level.time + 200 )
1364  {
1365    ucmd->serverTime = level.time + 200;
1366//    G_Printf("serverTime <<<<<\n" );
1367  }
1368
1369  if( ucmd->serverTime < level.time - 1000 )
1370  {
1371    ucmd->serverTime = level.time - 1000;
1372//    G_Printf("serverTime >>>>>\n" );
1373  }
1374
1375  msec = ucmd->serverTime - client->ps.commandTime;
1376  // following others may result in bad times, but we still want
1377  // to check for follow toggles
1378  if( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW )
1379    return;
1380
1381  if( msec > 200 )
1382    msec = 200;
1383
1384  if( ucmd->serverTime < level.time - g_unlagged.integer )
1385     client->unlaggedTime = level.time - g_unlagged.integer;
1386  else
1387     client->unlaggedTime = ucmd->serverTime;
1388
1389  if( pmove_msec.integer < 8 )
1390    trap_Cvar_Set( "pmove_msec", "8" );
1391  else if( pmove_msec.integer > 33 )
1392    trap_Cvar_Set( "pmove_msec", "33" );
1393
1394  if( pmove_fixed.integer || client->pers.pmoveFixed )
1395  {
1396    ucmd->serverTime = ( ( ucmd->serverTime + pmove_msec.integer - 1 ) / pmove_msec.integer ) * pmove_msec.integer;
1397    //if (ucmd->serverTime - client->ps.commandTime <= 0)
1398    //  return;
1399  }
1400
1401  //
1402  // check for exiting intermission
1403  //
1404  if( level.intermissiontime )
1405  {
1406    ClientIntermissionThink( client );
1407    return;
1408  }
1409
1410  // spectators don't do much
1411  if( client->sess.sessionTeam == TEAM_SPECTATOR )
1412  {
1413    if( client->sess.spectatorState == SPECTATOR_SCOREBOARD )
1414      return;
1415
1416    SpectatorThink( ent, ucmd );
1417    return;
1418  }
1419
1420  if (g_freeFunds.integer)
1421  {
1422     // give full evo/credits
1423     if (client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS)
1424     {
1425        client->ps.persistant[ PERS_CREDIT ] = ALIEN_MAX_KILLS;
1426     }
1427     else
1428     {
1429        client->ps.persistant[ PERS_CREDIT ] = HUMAN_MAX_CREDITS;
1430     }
1431  }
1432
1433  G_UpdatePTRConnection( client );
1434
1435  // check for inactivity timer, but never drop the local client of a non-dedicated server
1436  if( !ClientInactivityTimer( client ) )
1437    return;
1438
1439  // calculate where ent is currently seeing all the other active clients
1440  G_UnlaggedCalc( ent->client->unlaggedTime, ent );
1441
1442  if( client->noclip )
1443    client->ps.pm_type = PM_NOCLIP;
1444  else if( client->ps.stats[ STAT_HEALTH ] <= 0 )
1445    client->ps.pm_type = PM_DEAD;
1446  else if( client->ps.stats[ STAT_STATE ] & SS_INFESTING ||
1447           client->ps.stats[ STAT_STATE ] & SS_HOVELING )
1448    client->ps.pm_type = PM_FREEZE;
1449  else if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED ||
1450           client->ps.stats[ STAT_STATE ] & SS_GRABBED )
1451    client->ps.pm_type = PM_GRABBED;
1452  else if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) )
1453    client->ps.pm_type = PM_JETPACK;
1454  else
1455    client->ps.pm_type = PM_NORMAL;
1456
1457  if( client->ps.stats[ STAT_STATE ] & SS_GRABBED &&
1458      client->grabExpiryTime < level.time )
1459    client->ps.stats[ STAT_STATE ] &= ~SS_GRABBED;
1460
1461  if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED &&
1462      client->lastLockTime + LOCKBLOB_LOCKTIME < level.time )
1463    client->ps.stats[ STAT_STATE ] &= ~SS_BLOBLOCKED;
1464
1465  if( client->ps.stats[ STAT_STATE ] & SS_SLOWLOCKED &&
1466      client->lastSlowTime + ABUILDER_BLOB_TIME < level.time )
1467    client->ps.stats[ STAT_STATE ] &= ~SS_SLOWLOCKED;
1468
1469  if( client->ps.stats[ STAT_STATE ] & SS_BOOSTED &&
1470      client->lastBoostedTime + BOOST_TIME < level.time )
1471    client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTED;
1472
1473  if( client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED &&
1474      client->lastPoisonCloudedTime + LEVEL1_PCLOUD_TIME < level.time )
1475    client->ps.stats[ STAT_STATE ] &= ~SS_POISONCLOUDED;
1476
1477  if( client->ps.stats[ STAT_STATE ] & SS_POISONED &&
1478      client->lastPoisonTime + ALIEN_POISON_TIME < level.time )
1479    client->ps.stats[ STAT_STATE ] &= ~SS_POISONED;
1480
1481  client->ps.gravity = g_gravity.value;
1482
1483  if( BG_InventoryContainsUpgrade( UP_MEDKIT, client->ps.stats ) &&
1484      BG_UpgradeIsActive( UP_MEDKIT, client->ps.stats ) )
1485  {
1486    //if currently using a medkit or have no need for a medkit now
1487    if( client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE ||
1488        ( client->ps.stats[ STAT_HEALTH ] == client->ps.stats[ STAT_MAX_HEALTH ] &&
1489          !( client->ps.stats[ STAT_STATE ] & SS_POISONED ) ) )
1490    {
1491      BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats );
1492    }
1493    else if( client->ps.stats[ STAT_HEALTH ] > 0 )
1494    {
1495      //remove anti toxin
1496      BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats );
1497      BG_RemoveUpgradeFromInventory( UP_MEDKIT, client->ps.stats );
1498
1499      client->ps.stats[ STAT_STATE ] &= ~SS_POISONED;
1500      client->poisonImmunityTime = level.time + MEDKIT_POISON_IMMUNITY_TIME;
1501
1502      client->ps.stats[ STAT_STATE ] |= SS_MEDKIT_ACTIVE;
1503      client->lastMedKitTime = level.time;
1504      client->medKitHealthToRestore =
1505        client->ps.stats[ STAT_MAX_HEALTH ] - client->ps.stats[ STAT_HEALTH ];
1506      client->medKitIncrementTime = level.time +
1507        ( MEDKIT_STARTUP_TIME / MEDKIT_STARTUP_SPEED );
1508
1509      G_AddEvent( ent, EV_MEDKIT_USED, 0 );
1510    }
1511  }
1512
1513  if( BG_InventoryContainsUpgrade( UP_GRENADE, client->ps.stats ) &&
1514      BG_UpgradeIsActive( UP_GRENADE, client->ps.stats ) )
1515  {
1516    int lastWeapon = ent->s.weapon;
1517
1518    //remove grenade
1519    BG_DeactivateUpgrade( UP_GRENADE, client->ps.stats );
1520    BG_RemoveUpgradeFromInventory( UP_GRENADE, client->ps.stats );
1521
1522    //M-M-M-M-MONSTER HACK
1523    ent->s.weapon = WP_GRENADE;
1524    FireWeapon( ent );
1525    ent->s.weapon = lastWeapon;
1526  }
1527
1528  // set speed
1529  client->ps.speed = g_speed.value * BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] );
1530
1531  if( client->lastCreepSlowTime + CREEP_TIMEOUT < level.time )
1532    client->ps.stats[ STAT_STATE ] &= ~SS_CREEPSLOWED;
1533
1534  //randomly disable the jet pack if damaged
1535  if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) &&
1536      BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) )
1537  {
1538    if( ent->lastDamageTime + JETPACK_DISABLE_TIME > level.time )
1539    {
1540      if( random( ) > JETPACK_DISABLE_CHANCE )
1541        client->ps.pm_type = PM_NORMAL;
1542    }
1543
1544    //switch jetpack off if no power
1545    if( g_jetpackLimit.integer && (client->jetpack_beat || !client->jetpack_power))
1546    {
1547      BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats );
1548
1549      trap_SendServerCommand( ent - g_entities,
1550        "print \"Your jetpack is out of power\n\"" );
1551    }
1552  }
1553
1554  // set up for pmove
1555  oldEventSequence = client->ps.eventSequence;
1556
1557  memset( &pm, 0, sizeof( pm ) );
1558
1559  if( !( ucmd->buttons & BUTTON_TALK ) && !( client->ps.pm_flags & PMF_RESPAWNED ) )
1560  {
1561    switch( client->ps.weapon )
1562    {
1563      case WP_ALEVEL0:
1564        if( client->ps.weaponTime <= 0 )
1565          pm.autoWeaponHit[ client->ps.weapon ] = CheckVenomAttack( ent );
1566        break;
1567
1568      case WP_ALEVEL1:
1569      case WP_ALEVEL1_UPG:
1570        CheckGrabAttack( ent );
1571        break;
1572
1573      case WP_ALEVEL3:
1574      case WP_ALEVEL3_UPG:
1575        if( client->ps.weaponTime <= 0 )
1576          pm.autoWeaponHit[ client->ps.weapon ] = CheckPounceAttack( ent );
1577        break;
1578
1579      default:
1580        break;
1581    }
1582  }
1583
1584  if( ent->flags & FL_FORCE_GESTURE )
1585  {
1586    ent->flags &= ~FL_FORCE_GESTURE;
1587    ent->client->pers.cmd.buttons |= BUTTON_GESTURE;
1588  }
1589
1590  pm.ps = &client->ps;
1591  pm.pmext = &client->pmext;
1592  pm.cmd = *ucmd;
1593
1594  if( pm.ps->pm_type == PM_DEAD )
1595    pm.tracemask = MASK_DEADSOLID;
1596
1597  if( pm.ps->stats[ STAT_STATE ] & SS_INFESTING ||
1598      pm.ps->stats[ STAT_STATE ] & SS_HOVELING )
1599    pm.tracemask = MASK_DEADSOLID;
1600  else
1601    pm.tracemask = MASK_PLAYERSOLID;
1602
1603  pm.trace = trap_Trace;
1604  pm.pointcontents = trap_PointContents;
1605  pm.debugLevel = g_debugMove.integer;
1606  pm.noFootsteps = 0;
1607
1608  pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed;
1609  pm.pmove_msec = pmove_msec.integer;
1610
1611  VectorCopy( client->ps.origin, client->oldOrigin );
1612
1613  // moved from after Pmove -- potentially the cause of
1614  // future triggering bugs
1615  if( !ent->client->noclip )
1616    G_TouchTriggers( ent );
1617
1618  Pmove( &pm );
1619
1620  G_UnlaggedDetectCollisions( ent );
1621
1622  // save results of pmove
1623  if( ent->client->ps.eventSequence != oldEventSequence )
1624    ent->eventTime = level.time;
1625
1626  if( g_smoothClients.integer )
1627    BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue );
1628  else
1629    BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue );
1630
1631  SendPendingPredictableEvents( &ent->client->ps );
1632
1633  if( !( ent->client->ps.eFlags & EF_FIRING ) )
1634    client->fireHeld = qfalse;    // for grapple
1635  if( !( ent->client->ps.eFlags & EF_FIRING2 ) )
1636    client->fire2Held = qfalse;
1637
1638  // use the snapped origin for linking so it matches client predicted versions
1639  VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin );
1640
1641  VectorCopy( pm.mins, ent->r.mins );
1642  VectorCopy( pm.maxs, ent->r.maxs );
1643
1644  ent->waterlevel = pm.waterlevel;
1645  ent->watertype = pm.watertype;
1646
1647  // touch other objects
1648  ClientImpacts( ent, &pm );
1649
1650  // execute client events
1651  ClientEvents( ent, oldEventSequence );
1652
1653  // link entity now, after any personal teleporters have been used
1654  trap_LinkEntity( ent );
1655
1656  // NOTE: now copy the exact origin over otherwise clients can be snapped into solid
1657  VectorCopy( ent->client->ps.origin, ent->r.currentOrigin );
1658  VectorCopy( ent->client->ps.origin, ent->s.origin );
1659
1660  // save results of triggers and client events
1661  if( ent->client->ps.eventSequence != oldEventSequence )
1662    ent->eventTime = level.time;
1663
1664  // Don't think anymore if dead
1665  if( client->ps.stats[ STAT_HEALTH ] <= 0 )
1666    return;
1667
1668  // swap and latch button actions
1669  client->oldbuttons = client->buttons;
1670  client->buttons = ucmd->buttons;
1671  client->latched_buttons |= client->buttons & ~client->oldbuttons;
1672
1673  if( ( client->buttons & BUTTON_GETFLAG ) && !( client->oldbuttons & BUTTON_GETFLAG ) &&
1674       client->ps.stats[ STAT_HEALTH ] > 0 )
1675  {
1676    trace_t   trace;
1677    vec3_t    view, point;
1678    gentity_t *traceEnt;
1679
1680    if( client->ps.stats[ STAT_STATE ] & SS_HOVELING )
1681    {
1682      gentity_t *hovel = client->hovel;
1683
1684      //only let the player out if there is room
1685      if( !AHovel_Blocked( hovel, ent, qtrue ) )
1686      {
1687        //prevent lerping
1688        client->ps.eFlags ^= EF_TELEPORT_BIT;
1689        client->ps.eFlags &= ~EF_NODRAW;
1690        G_UnlaggedClear( ent );
1691
1692        //client leaves hovel
1693        client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING;
1694
1695        //hovel is empty
1696        G_SetBuildableAnim( hovel, BANIM_ATTACK2, qfalse );
1697        hovel->active = qfalse;
1698      }
1699      else
1700      {
1701        //exit is blocked
1702        G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL_BLOCKED );
1703      }
1704    }
1705    else
1706    {
1707#define USE_OBJECT_RANGE 64
1708
1709      int       entityList[ MAX_GENTITIES ];
1710      vec3_t    range = { USE_OBJECT_RANGE, USE_OBJECT_RANGE, USE_OBJECT_RANGE };
1711      vec3_t    mins, maxs;
1712      int       i, num;
1713
1714      // look for object infront of player
1715      AngleVectors( client->ps.viewangles, view, NULL, NULL );
1716      VectorMA( client->ps.origin, USE_OBJECT_RANGE, view, point );
1717      trap_Trace( &trace, client->ps.origin, NULL, NULL, point, ent->s.number, MASK_SHOT );
1718
1719      traceEnt = &g_entities[ trace.entityNum ];
1720
1721      if( traceEnt && traceEnt->biteam == client->ps.stats[ STAT_PTEAM ] && traceEnt->use )
1722        traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context
1723      else
1724      {
1725        //no entity in front of player - do a small area search
1726
1727        VectorAdd( client->ps.origin, range, maxs );
1728        VectorSubtract( client->ps.origin, range, mins );
1729
1730        num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
1731        for( i = 0; i < num; i++ )
1732        {
1733          traceEnt = &g_entities[ entityList[ i ] ];
1734
1735          if( traceEnt && traceEnt->biteam == client->ps.stats[ STAT_PTEAM ] && traceEnt->use )
1736          {
1737            traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context
1738            break;
1739          }
1740        }
1741
1742        if( i == num && client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
1743        {
1744          if( BG_UpgradeClassAvailable( &client->ps ) )
1745          {
1746            //no nearby objects and alien - show class menu
1747            G_TriggerMenu( ent->client->ps.clientNum, MN_A_INFEST );
1748          }
1749          else
1750          {
1751            //flash frags
1752            G_AddEvent( ent, EV_ALIEN_EVOLVE_FAILED, 0 );
1753          }
1754        }
1755      }
1756    }
1757  }
1758
1759  // Give clients some credit periodically
1760  if( ent->client->lastKillTime + FREEKILL_PERIOD < level.time )
1761  {
1762    if( G_TimeTilSuddenDeath( ) <= 0 )
1763    {
1764      //gotta love logic like this eh?
1765    }
1766    else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
1767      G_AddCreditToClient( ent->client, FREEKILL_ALIEN, qtrue );
1768    else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
1769      G_AddCreditToClient( ent->client, FREEKILL_HUMAN, qtrue );
1770
1771    ent->client->lastKillTime = level.time;
1772  }
1773
1774  // perform once-a-second actions
1775  ClientTimerActions( ent, msec );
1776
1777  if( ent->suicideTime > 0 && ent->suicideTime < level.time )
1778  {
1779    ent->flags &= ~FL_GODMODE;
1780    ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0;
1781    player_die( ent, ent, ent, 100000, MOD_SUICIDE );
1782
1783    ent->suicideTime = 0;
1784  }
1785}
1786
1787/*
1788==================
1789ClientThink
1790
1791A new command has arrived from the client
1792==================
1793*/
1794void ClientThink( int clientNum )
1795{
1796  gentity_t *ent;
1797
1798  ent = g_entities + clientNum;
1799  trap_GetUsercmd( clientNum, &ent->client->pers.cmd );
1800
1801  // mark the time we got info, so we can display the
1802  // phone jack if they don't get any for a while
1803  ent->client->lastCmdTime = level.time;
1804
1805  if( !g_synchronousClients.integer )
1806    ClientThink_real( ent );
1807}
1808
1809
1810void G_RunClient( gentity_t *ent )
1811{
1812  if( !g_synchronousClients.integer )
1813    return;
1814
1815  ent->client->pers.cmd.serverTime = level.time;
1816  ClientThink_real( ent );
1817}
1818
1819
1820/*
1821==================
1822SpectatorClientEndFrame
1823
1824==================
1825*/
1826void SpectatorClientEndFrame( gentity_t *ent )
1827{
1828  gclient_t *cl;
1829  int       clientNum, flags;
1830  int       score, ping;
1831
1832  // if we are doing a chase cam or a remote view, grab the latest info
1833  if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
1834  {
1835    clientNum = ent->client->sess.spectatorClient;
1836
1837    if( clientNum >= 0 )
1838    {
1839      cl = &level.clients[ clientNum ];
1840
1841      if( cl->pers.connected == CON_CONNECTED )
1842      {
1843        flags = ( cl->ps.eFlags & ~( EF_VOTED | EF_TEAMVOTED ) ) |
1844          ( ent->client->ps.eFlags & ( EF_VOTED | EF_TEAMVOTED ) );
1845        score = ent->client->ps.persistant[ PERS_SCORE ];
1846        ping = ent->client->ps.ping;
1847        ent->client->ps = cl->ps;
1848        ent->client->ps.persistant[ PERS_SCORE ] = score;
1849        ent->client->ps.ping = ping;
1850        ent->client->ps.pm_flags |= PMF_FOLLOW;
1851        ent->client->ps.pm_flags &= ~PMF_QUEUED;
1852        ent->client->ps.eFlags = flags;
1853      }
1854    }
1855  }
1856}
1857
1858/*
1859==============
1860ClientEndFrame
1861
1862Called at the end of each server frame for each connected client
1863A fast client will have multiple ClientThink for each ClientEdFrame,
1864while a slow client may have multiple ClientEndFrame between ClientThink.
1865==============
1866*/
1867void ClientEndFrame( gentity_t *ent )
1868{
1869  clientPersistant_t  *pers;
1870
1871   if (ent->client->pers.floodTimer)
1872   {
1873     ent->client->pers.floodTimer -= 5;
1874     if (ent->client->pers.floodTimer < 0)
1875     ent->client->pers.floodTimer = 0;
1876   }
1877
1878  if( ent->client->sess.sessionTeam == TEAM_SPECTATOR )
1879  {
1880    SpectatorClientEndFrame( ent );
1881    return;
1882  }
1883
1884  pers = &ent->client->pers;
1885
1886  //
1887  // If the end of unit layout is displayed, don't give
1888  // the player any normal movement attributes
1889  //
1890  if( level.intermissiontime )
1891    return;
1892
1893  // burn from lava, etc
1894  P_WorldEffects( ent );
1895
1896  // apply all the damage taken this frame
1897  P_DamageFeedback( ent );
1898
1899  // add the EF_CONNECTION flag if we haven't gotten commands recently
1900  if( level.time - ent->client->lastCmdTime > 1000 )
1901    ent->s.eFlags |= EF_CONNECTION;
1902  else
1903    ent->s.eFlags &= ~EF_CONNECTION;
1904
1905  ent->client->ps.stats[ STAT_HEALTH ] = ent->health; // FIXME: get rid of ent->health...
1906
1907  // respawn if dead
1908  if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 && level.time >= ent->client->respawnTime )
1909    respawn( ent );
1910
1911  G_SetClientSound( ent );
1912
1913  // set the latest infor
1914  if( g_smoothClients.integer )
1915    BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue );
1916  else
1917    BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue );
1918
1919  SendPendingPredictableEvents( &ent->client->ps );
1920}
1921
1922void do_health( gentity_t *ent, int quantity )
1923{
1924  int i = 0;
1925
1926  if( ent->health == ent->client->ps.stats[ STAT_MAX_HEALTH ] )
1927    return;
1928
1929  ent->health += quantity;
1930  if( ent->health >= ent->client->ps.stats[ STAT_MAX_HEALTH ] )
1931  {
1932    ent->health = ent->client->ps.stats[ STAT_MAX_HEALTH ];
1933    for( i = 0; i < MAX_CLIENTS; i++ )
1934      ent->credits[ i ] = 0;
1935  }
1936}
Note: See TracBrowser for help on using the browser.