1 /*
2 ===========================================================================
3
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
6
7 This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
8
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
23
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25
26 ===========================================================================
27 */
28
29 #include "sys/platform.h"
30 #include "idlib/LangDict.h"
31 #include "framework/async/NetworkSystem.h"
32 #include "framework/DeclEntityDef.h"
33 #include "renderer/RenderSystem.h"
34
35 #include "gamesys/SysCvar.h"
36 #include "script/Script_Thread.h"
37 #include "ai/AI.h"
38 #include "WorldSpawn.h"
39 #include "Player.h"
40 #include "Camera.h"
41 #include "Fx.h"
42 #include "Misc.h"
43
44 const int ASYNC_PLAYER_INV_AMMO_BITS = idMath::BitsForInteger( 999 ); // 9 bits to cover the range [0, 999]
45 const int ASYNC_PLAYER_INV_CLIP_BITS = -7; // -7 bits to cover the range [-1, 60]
46
47 /*
48 ===============================================================================
49
50 Player control of the Doom Marine.
51 This object handles all player movement and world interaction.
52
53 ===============================================================================
54 */
55
56 // distance between ladder rungs (actually is half that distance, but this sounds better)
57 const int LADDER_RUNG_DISTANCE = 32;
58
59 // amount of health per dose from the health station
60 const int HEALTH_PER_DOSE = 10;
61
62 // time before a weapon dropped to the floor disappears
63 const int WEAPON_DROP_TIME = 20 * 1000;
64
65 // time before a next or prev weapon switch happens
66 const int WEAPON_SWITCH_DELAY = 150;
67
68 // how many units to raise spectator above default view height so it's in the head of someone
69 const int SPECTATE_RAISE = 25;
70
71 const int HEALTHPULSE_TIME = 333;
72
73 // minimum speed to bob and play run/walk animations at
74 const float MIN_BOB_SPEED = 5.0f;
75
76 const idEventDef EV_Player_GetButtons( "getButtons", NULL, 'd' );
77 const idEventDef EV_Player_GetMove( "getMove", NULL, 'v' );
78 const idEventDef EV_Player_GetViewAngles( "getViewAngles", NULL, 'v' );
79 const idEventDef EV_Player_StopFxFov( "stopFxFov" );
80 const idEventDef EV_Player_EnableWeapon( "enableWeapon" );
81 const idEventDef EV_Player_DisableWeapon( "disableWeapon" );
82 const idEventDef EV_Player_GetCurrentWeapon( "getCurrentWeapon", NULL, 's' );
83 const idEventDef EV_Player_GetPreviousWeapon( "getPreviousWeapon", NULL, 's' );
84 const idEventDef EV_Player_SelectWeapon( "selectWeapon", "s" );
85 const idEventDef EV_Player_GetWeaponEntity( "getWeaponEntity", NULL, 'e' );
86 const idEventDef EV_Player_OpenPDA( "openPDA" );
87 const idEventDef EV_Player_InPDA( "inPDA", NULL, 'd' );
88 const idEventDef EV_Player_ExitTeleporter( "exitTeleporter" );
89 const idEventDef EV_Player_StopAudioLog( "stopAudioLog" );
90 const idEventDef EV_Player_HideTip( "hideTip" );
91 const idEventDef EV_Player_LevelTrigger( "levelTrigger" );
92 const idEventDef EV_SpectatorTouch( "spectatorTouch", "et" );
93 const idEventDef EV_Player_GetIdealWeapon( "getIdealWeapon", NULL, 's' );
94
95 CLASS_DECLARATION( idActor, idPlayer )
96 EVENT( EV_Player_GetButtons, idPlayer::Event_GetButtons )
97 EVENT( EV_Player_GetMove, idPlayer::Event_GetMove )
98 EVENT( EV_Player_GetViewAngles, idPlayer::Event_GetViewAngles )
99 EVENT( EV_Player_StopFxFov, idPlayer::Event_StopFxFov )
100 EVENT( EV_Player_EnableWeapon, idPlayer::Event_EnableWeapon )
101 EVENT( EV_Player_DisableWeapon, idPlayer::Event_DisableWeapon )
102 EVENT( EV_Player_GetCurrentWeapon, idPlayer::Event_GetCurrentWeapon )
103 EVENT( EV_Player_GetPreviousWeapon, idPlayer::Event_GetPreviousWeapon )
104 EVENT( EV_Player_SelectWeapon, idPlayer::Event_SelectWeapon )
105 EVENT( EV_Player_GetWeaponEntity, idPlayer::Event_GetWeaponEntity )
106 EVENT( EV_Player_OpenPDA, idPlayer::Event_OpenPDA )
107 EVENT( EV_Player_InPDA, idPlayer::Event_InPDA )
108 EVENT( EV_Player_ExitTeleporter, idPlayer::Event_ExitTeleporter )
109 EVENT( EV_Player_StopAudioLog, idPlayer::Event_StopAudioLog )
110 EVENT( EV_Player_HideTip, idPlayer::Event_HideTip )
111 EVENT( EV_Player_LevelTrigger, idPlayer::Event_LevelTrigger )
112 EVENT( EV_Gibbed, idPlayer::Event_Gibbed )
113 EVENT( EV_Player_GetIdealWeapon, idPlayer::Event_GetIdealWeapon )
114 END_CLASS
115
116 const int MAX_RESPAWN_TIME = 10000;
117 const int RAGDOLL_DEATH_TIME = 3000;
118 const int MAX_PDAS = 64;
119 const int MAX_PDA_ITEMS = 128;
120 const int STEPUP_TIME = 200;
121 const int MAX_INVENTORY_ITEMS = 20;
122
123 idVec3 idPlayer::colorBarTable[ 5 ] = {
124 idVec3( 0.25f, 0.25f, 0.25f ),
125 idVec3( 1.00f, 0.00f, 0.00f ),
126 idVec3( 0.00f, 0.80f, 0.10f ),
127 idVec3( 0.20f, 0.50f, 0.80f ),
128 idVec3( 1.00f, 0.80f, 0.10f )
129 };
130
131 /*
132 ==============
133 idInventory::Clear
134 ==============
135 */
Clear(void)136 void idInventory::Clear( void ) {
137 maxHealth = 0;
138 weapons = 0;
139 powerups = 0;
140 armor = 0;
141 maxarmor = 0;
142 deplete_armor = 0;
143 deplete_rate = 0.0f;
144 deplete_ammount = 0;
145 nextArmorDepleteTime = 0;
146
147 memset( ammo, 0, sizeof( ammo ) );
148
149 ClearPowerUps();
150
151 // set to -1 so that the gun knows to have a full clip the first time we get it and at the start of the level
152 memset( clip, -1, sizeof( clip ) );
153
154 items.DeleteContents( true );
155 memset(pdasViewed, 0, 4 * sizeof( pdasViewed[0] ) );
156 pdas.Clear();
157 videos.Clear();
158 emails.Clear();
159 selVideo = 0;
160 selEMail = 0;
161 selPDA = 0;
162 selAudio = 0;
163 pdaOpened = false;
164 turkeyScore = false;
165
166 levelTriggers.Clear();
167
168 nextItemPickup = 0;
169 nextItemNum = 1;
170 onePickupTime = 0;
171 pickupItemNames.Clear();
172 objectiveNames.Clear();
173
174 ammoPredictTime = 0;
175
176 lastGiveTime = 0;
177
178 ammoPulse = false;
179 weaponPulse = false;
180 armorPulse = false;
181 }
182
183 /*
184 ==============
185 idInventory::GivePowerUp
186 ==============
187 */
GivePowerUp(idPlayer * player,int powerup,int msec)188 void idInventory::GivePowerUp( idPlayer *player, int powerup, int msec ) {
189 if ( !msec ) {
190 // get the duration from the .def files
191 const idDeclEntityDef *def = NULL;
192 switch ( powerup ) {
193 case BERSERK:
194 def = gameLocal.FindEntityDef( "powerup_berserk", false );
195 break;
196 case INVISIBILITY:
197 def = gameLocal.FindEntityDef( "powerup_invisibility", false );
198 break;
199 case MEGAHEALTH:
200 def = gameLocal.FindEntityDef( "powerup_megahealth", false );
201 break;
202 case ADRENALINE:
203 def = gameLocal.FindEntityDef( "powerup_adrenaline", false );
204 break;
205 }
206 assert( def );
207 msec = def->dict.GetInt( "time" ) * 1000;
208 }
209 powerups |= 1 << powerup;
210 powerupEndTime[ powerup ] = gameLocal.time + msec;
211 }
212
213 /*
214 ==============
215 idInventory::ClearPowerUps
216 ==============
217 */
ClearPowerUps(void)218 void idInventory::ClearPowerUps( void ) {
219 int i;
220 for ( i = 0; i < MAX_POWERUPS; i++ ) {
221 powerupEndTime[ i ] = 0;
222 }
223 powerups = 0;
224 }
225
226 /*
227 ==============
228 idInventory::GetPersistantData
229 ==============
230 */
GetPersistantData(idDict & dict)231 void idInventory::GetPersistantData( idDict &dict ) {
232 int i;
233 int num;
234 idDict *item;
235 idStr key;
236 const idKeyValue *kv;
237 const char *name;
238
239 // armor
240 dict.SetInt( "armor", armor );
241
242 // don't bother with powerups, maxhealth, maxarmor, or the clip
243
244 // ammo
245 for( i = 0; i < AMMO_NUMTYPES; i++ ) {
246 name = idWeapon::GetAmmoNameForNum( ( ammo_t )i );
247 if ( name ) {
248 dict.SetInt( name, ammo[ i ] );
249 }
250 }
251
252 // items
253 num = 0;
254 for( i = 0; i < items.Num(); i++ ) {
255 item = items[ i ];
256
257 // copy all keys with "inv_"
258 kv = item->MatchPrefix( "inv_" );
259 if ( kv ) {
260 while( kv ) {
261 sprintf( key, "item_%i %s", num, kv->GetKey().c_str() );
262 dict.Set( key, kv->GetValue() );
263 kv = item->MatchPrefix( "inv_", kv );
264 }
265 num++;
266 }
267 }
268 dict.SetInt( "items", num );
269
270 // pdas viewed
271 for ( i = 0; i < 4; i++ ) {
272 dict.SetInt( va("pdasViewed_%i", i), pdasViewed[i] );
273 }
274
275 dict.SetInt( "selPDA", selPDA );
276 dict.SetInt( "selVideo", selVideo );
277 dict.SetInt( "selEmail", selEMail );
278 dict.SetInt( "selAudio", selAudio );
279 dict.SetInt( "pdaOpened", pdaOpened );
280 dict.SetInt( "turkeyScore", turkeyScore );
281
282 // pdas
283 for ( i = 0; i < pdas.Num(); i++ ) {
284 sprintf( key, "pda_%i", i );
285 dict.Set( key, pdas[ i ] );
286 }
287 dict.SetInt( "pdas", pdas.Num() );
288
289 // video cds
290 for ( i = 0; i < videos.Num(); i++ ) {
291 sprintf( key, "video_%i", i );
292 dict.Set( key, videos[ i ].c_str() );
293 }
294 dict.SetInt( "videos", videos.Num() );
295
296 // emails
297 for ( i = 0; i < emails.Num(); i++ ) {
298 sprintf( key, "email_%i", i );
299 dict.Set( key, emails[ i ].c_str() );
300 }
301 dict.SetInt( "emails", emails.Num() );
302
303 // weapons
304 dict.SetInt( "weapon_bits", weapons );
305
306 dict.SetInt( "levelTriggers", levelTriggers.Num() );
307 for ( i = 0; i < levelTriggers.Num(); i++ ) {
308 sprintf( key, "levelTrigger_Level_%i", i );
309 dict.Set( key, levelTriggers[i].levelName );
310 sprintf( key, "levelTrigger_Trigger_%i", i );
311 dict.Set( key, levelTriggers[i].triggerName );
312 }
313 }
314
315 /*
316 ==============
317 idInventory::RestoreInventory
318 ==============
319 */
RestoreInventory(idPlayer * owner,const idDict & dict)320 void idInventory::RestoreInventory( idPlayer *owner, const idDict &dict ) {
321 int i;
322 int num;
323 idDict *item;
324 idStr key;
325 idStr itemname;
326 const idKeyValue *kv;
327 const char *name;
328
329 Clear();
330
331 // health/armor
332 maxHealth = dict.GetInt( "maxhealth", "100" );
333 armor = dict.GetInt( "armor", "50" );
334 maxarmor = dict.GetInt( "maxarmor", "100" );
335 deplete_armor = dict.GetInt( "deplete_armor", "0" );
336 deplete_rate = dict.GetFloat( "deplete_rate", "2.0" );
337 deplete_ammount = dict.GetInt( "deplete_ammount", "1" );
338
339 // the clip and powerups aren't restored
340
341 // ammo
342 for( i = 0; i < AMMO_NUMTYPES; i++ ) {
343 name = idWeapon::GetAmmoNameForNum( ( ammo_t )i );
344 if ( name ) {
345 ammo[ i ] = dict.GetInt( name );
346 }
347 }
348
349 // items
350 num = dict.GetInt( "items" );
351 items.SetNum( num );
352 for( i = 0; i < num; i++ ) {
353 item = new idDict();
354 items[ i ] = item;
355 sprintf( itemname, "item_%i ", i );
356 kv = dict.MatchPrefix( itemname );
357 while( kv ) {
358 key = kv->GetKey();
359 key.Strip( itemname );
360 item->Set( key, kv->GetValue() );
361 kv = dict.MatchPrefix( itemname, kv );
362 }
363 }
364
365 // pdas viewed
366 for ( i = 0; i < 4; i++ ) {
367 pdasViewed[i] = dict.GetInt(va("pdasViewed_%i", i));
368 }
369
370 selPDA = dict.GetInt( "selPDA" );
371 selEMail = dict.GetInt( "selEmail" );
372 selVideo = dict.GetInt( "selVideo" );
373 selAudio = dict.GetInt( "selAudio" );
374 pdaOpened = dict.GetBool( "pdaOpened" );
375 turkeyScore = dict.GetBool( "turkeyScore" );
376
377 // pdas
378 num = dict.GetInt( "pdas" );
379 pdas.SetNum( num );
380 for ( i = 0; i < num; i++ ) {
381 sprintf( itemname, "pda_%i", i );
382 pdas[i] = dict.GetString( itemname, "default" );
383 }
384
385 // videos
386 num = dict.GetInt( "videos" );
387 videos.SetNum( num );
388 for ( i = 0; i < num; i++ ) {
389 sprintf( itemname, "video_%i", i );
390 videos[i] = dict.GetString( itemname, "default" );
391 }
392
393 // emails
394 num = dict.GetInt( "emails" );
395 emails.SetNum( num );
396 for ( i = 0; i < num; i++ ) {
397 sprintf( itemname, "email_%i", i );
398 emails[i] = dict.GetString( itemname, "default" );
399 }
400
401 // weapons are stored as a number for persistant data, but as strings in the entityDef
402 weapons = dict.GetInt( "weapon_bits", "0" );
403
404 if ( g_skill.GetInteger() >= 3 ) {
405 Give( owner, dict, "weapon", dict.GetString( "weapon_nightmare" ), NULL, false );
406 } else {
407 Give( owner, dict, "weapon", dict.GetString( "weapon" ), NULL, false );
408 }
409
410 num = dict.GetInt( "levelTriggers" );
411 for ( i = 0; i < num; i++ ) {
412 sprintf( itemname, "levelTrigger_Level_%i", i );
413 idLevelTriggerInfo lti;
414 lti.levelName = dict.GetString( itemname );
415 sprintf( itemname, "levelTrigger_Trigger_%i", i );
416 lti.triggerName = dict.GetString( itemname );
417 levelTriggers.Append( lti );
418 }
419
420 }
421
422 /*
423 ==============
424 idInventory::Save
425 ==============
426 */
Save(idSaveGame * savefile) const427 void idInventory::Save( idSaveGame *savefile ) const {
428 int i;
429
430 savefile->WriteInt( maxHealth );
431 savefile->WriteInt( weapons );
432 savefile->WriteInt( powerups );
433 savefile->WriteInt( armor );
434 savefile->WriteInt( maxarmor );
435 savefile->WriteInt( ammoPredictTime );
436 savefile->WriteInt( deplete_armor );
437 savefile->WriteFloat( deplete_rate );
438 savefile->WriteInt( deplete_ammount );
439 savefile->WriteInt( nextArmorDepleteTime );
440
441 for( i = 0; i < AMMO_NUMTYPES; i++ ) {
442 savefile->WriteInt( ammo[ i ] );
443 }
444 for( i = 0; i < MAX_WEAPONS; i++ ) {
445 savefile->WriteInt( clip[ i ] );
446 }
447 for( i = 0; i < MAX_POWERUPS; i++ ) {
448 savefile->WriteInt( powerupEndTime[ i ] );
449 }
450
451 savefile->WriteInt( items.Num() );
452 for( i = 0; i < items.Num(); i++ ) {
453 savefile->WriteDict( items[ i ] );
454 }
455
456 savefile->WriteInt( pdasViewed[0] );
457 savefile->WriteInt( pdasViewed[1] );
458 savefile->WriteInt( pdasViewed[2] );
459 savefile->WriteInt( pdasViewed[3] );
460
461 savefile->WriteInt( selPDA );
462 savefile->WriteInt( selVideo );
463 savefile->WriteInt( selEMail );
464 savefile->WriteInt( selAudio );
465 savefile->WriteBool( pdaOpened );
466 savefile->WriteBool( turkeyScore );
467
468 savefile->WriteInt( pdas.Num() );
469 for( i = 0; i < pdas.Num(); i++ ) {
470 savefile->WriteString( pdas[ i ] );
471 }
472
473 savefile->WriteInt( pdaSecurity.Num() );
474 for( i=0; i < pdaSecurity.Num(); i++ ) {
475 savefile->WriteString( pdaSecurity[ i ] );
476 }
477
478 savefile->WriteInt( videos.Num() );
479 for( i = 0; i < videos.Num(); i++ ) {
480 savefile->WriteString( videos[ i ] );
481 }
482
483 savefile->WriteInt( emails.Num() );
484 for ( i = 0; i < emails.Num(); i++ ) {
485 savefile->WriteString( emails[ i ] );
486 }
487
488 savefile->WriteInt( nextItemPickup );
489 savefile->WriteInt( nextItemNum );
490 savefile->WriteInt( onePickupTime );
491
492 savefile->WriteInt( pickupItemNames.Num() );
493 for( i = 0; i < pickupItemNames.Num(); i++ ) {
494 savefile->WriteString( pickupItemNames[i].icon );
495 savefile->WriteString( pickupItemNames[i].name );
496 }
497
498 savefile->WriteInt( objectiveNames.Num() );
499 for( i = 0; i < objectiveNames.Num(); i++ ) {
500 savefile->WriteString( objectiveNames[i].screenshot );
501 savefile->WriteString( objectiveNames[i].text );
502 savefile->WriteString( objectiveNames[i].title );
503 }
504
505 savefile->WriteInt( levelTriggers.Num() );
506 for ( i = 0; i < levelTriggers.Num(); i++ ) {
507 savefile->WriteString( levelTriggers[i].levelName );
508 savefile->WriteString( levelTriggers[i].triggerName );
509 }
510
511 savefile->WriteBool( ammoPulse );
512 savefile->WriteBool( weaponPulse );
513 savefile->WriteBool( armorPulse );
514
515 savefile->WriteInt( lastGiveTime );
516 }
517
518 /*
519 ==============
520 idInventory::Restore
521 ==============
522 */
Restore(idRestoreGame * savefile)523 void idInventory::Restore( idRestoreGame *savefile ) {
524 int i, num;
525
526 savefile->ReadInt( maxHealth );
527 savefile->ReadInt( weapons );
528 savefile->ReadInt( powerups );
529 savefile->ReadInt( armor );
530 savefile->ReadInt( maxarmor );
531 savefile->ReadInt( ammoPredictTime );
532 savefile->ReadInt( deplete_armor );
533 savefile->ReadFloat( deplete_rate );
534 savefile->ReadInt( deplete_ammount );
535 savefile->ReadInt( nextArmorDepleteTime );
536
537 for( i = 0; i < AMMO_NUMTYPES; i++ ) {
538 savefile->ReadInt( ammo[ i ] );
539 }
540 for( i = 0; i < MAX_WEAPONS; i++ ) {
541 savefile->ReadInt( clip[ i ] );
542 }
543 for( i = 0; i < MAX_POWERUPS; i++ ) {
544 savefile->ReadInt( powerupEndTime[ i ] );
545 }
546
547 savefile->ReadInt( num );
548 for( i = 0; i < num; i++ ) {
549 idDict *itemdict = new idDict;
550
551 savefile->ReadDict( itemdict );
552 items.Append( itemdict );
553 }
554
555 // pdas
556 savefile->ReadInt( pdasViewed[0] );
557 savefile->ReadInt( pdasViewed[1] );
558 savefile->ReadInt( pdasViewed[2] );
559 savefile->ReadInt( pdasViewed[3] );
560
561 savefile->ReadInt( selPDA );
562 savefile->ReadInt( selVideo );
563 savefile->ReadInt( selEMail );
564 savefile->ReadInt( selAudio );
565 savefile->ReadBool( pdaOpened );
566 savefile->ReadBool( turkeyScore );
567
568 savefile->ReadInt( num );
569 for( i = 0; i < num; i++ ) {
570 idStr strPda;
571 savefile->ReadString( strPda );
572 pdas.Append( strPda );
573 }
574
575 // pda security clearances
576 savefile->ReadInt( num );
577 for ( i = 0; i < num; i++ ) {
578 idStr invName;
579 savefile->ReadString( invName );
580 pdaSecurity.Append( invName );
581 }
582
583 // videos
584 savefile->ReadInt( num );
585 for( i = 0; i < num; i++ ) {
586 idStr strVideo;
587 savefile->ReadString( strVideo );
588 videos.Append( strVideo );
589 }
590
591 // email
592 savefile->ReadInt( num );
593 for( i = 0; i < num; i++ ) {
594 idStr strEmail;
595 savefile->ReadString( strEmail );
596 emails.Append( strEmail );
597 }
598
599 savefile->ReadInt( nextItemPickup );
600 savefile->ReadInt( nextItemNum );
601 savefile->ReadInt( onePickupTime );
602 savefile->ReadInt( num );
603 for( i = 0; i < num; i++ ) {
604 idItemInfo info;
605
606 savefile->ReadString( info.icon );
607 savefile->ReadString( info.name );
608
609 pickupItemNames.Append( info );
610 }
611
612 savefile->ReadInt( num );
613 for( i = 0; i < num; i++ ) {
614 idObjectiveInfo obj;
615
616 savefile->ReadString( obj.screenshot );
617 savefile->ReadString( obj.text );
618 savefile->ReadString( obj.title );
619
620 objectiveNames.Append( obj );
621 }
622
623 savefile->ReadInt( num );
624 for ( i = 0; i < num; i++ ) {
625 idLevelTriggerInfo lti;
626 savefile->ReadString( lti.levelName );
627 savefile->ReadString( lti.triggerName );
628 levelTriggers.Append( lti );
629 }
630
631 savefile->ReadBool( ammoPulse );
632 savefile->ReadBool( weaponPulse );
633 savefile->ReadBool( armorPulse );
634
635 savefile->ReadInt( lastGiveTime );
636 }
637
638 /*
639 ==============
640 idInventory::AmmoIndexForAmmoClass
641 ==============
642 */
AmmoIndexForAmmoClass(const char * ammo_classname) const643 ammo_t idInventory::AmmoIndexForAmmoClass( const char *ammo_classname ) const {
644 return idWeapon::GetAmmoNumForName( ammo_classname );
645 }
646
647 /*
648 ==============
649 idInventory::AmmoIndexForAmmoClass
650 ==============
651 */
MaxAmmoForAmmoClass(idPlayer * owner,const char * ammo_classname) const652 int idInventory::MaxAmmoForAmmoClass( idPlayer *owner, const char *ammo_classname ) const {
653 return owner->spawnArgs.GetInt( va( "max_%s", ammo_classname ), "0" );
654 }
655
656 /*
657 ==============
658 idInventory::AmmoPickupNameForIndex
659 ==============
660 */
AmmoPickupNameForIndex(ammo_t ammonum) const661 const char *idInventory::AmmoPickupNameForIndex( ammo_t ammonum ) const {
662 return idWeapon::GetAmmoPickupNameForNum( ammonum );
663 }
664
665 /*
666 ==============
667 idInventory::WeaponIndexForAmmoClass
668 mapping could be prepared in the constructor
669 ==============
670 */
WeaponIndexForAmmoClass(const idDict & spawnArgs,const char * ammo_classname) const671 int idInventory::WeaponIndexForAmmoClass( const idDict & spawnArgs, const char *ammo_classname ) const {
672 int i;
673 const char *weapon_classname;
674 for( i = 0; i < MAX_WEAPONS; i++ ) {
675 weapon_classname = spawnArgs.GetString( va( "def_weapon%d", i ) );
676 if ( !weapon_classname ) {
677 continue;
678 }
679 const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false );
680 if ( !decl ) {
681 continue;
682 }
683 if ( !idStr::Icmp( ammo_classname, decl->dict.GetString( "ammoType" ) ) ) {
684 return i;
685 }
686 }
687 return -1;
688 }
689
690 /*
691 ==============
692 idInventory::AmmoIndexForWeaponClass
693 ==============
694 */
AmmoIndexForWeaponClass(const char * weapon_classname,int * ammoRequired)695 ammo_t idInventory::AmmoIndexForWeaponClass( const char *weapon_classname, int *ammoRequired ) {
696 const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false );
697 if ( !decl ) {
698 gameLocal.Error( "Unknown weapon in decl '%s'", weapon_classname );
699 }
700 if ( ammoRequired ) {
701 *ammoRequired = decl->dict.GetInt( "ammoRequired" );
702 }
703 ammo_t ammo_i = AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) );
704 return ammo_i;
705 }
706
707 /*
708 ==============
709 idInventory::AddPickupName
710 ==============
711 */
AddPickupName(const char * name,const char * icon)712 void idInventory::AddPickupName( const char *name, const char *icon ) {
713 int num;
714
715 num = pickupItemNames.Num();
716 if ( ( num == 0 ) || ( pickupItemNames[ num - 1 ].name.Icmp( name ) != 0 ) ) {
717 idItemInfo &info = pickupItemNames.Alloc();
718
719 if ( idStr::Cmpn( name, STRTABLE_ID, STRTABLE_ID_LENGTH ) == 0 ) {
720 info.name = common->GetLanguageDict()->GetString( name );
721 } else {
722 info.name = name;
723 }
724 info.icon = icon;
725 }
726 }
727
728 /*
729 ==============
730 idInventory::Give
731 ==============
732 */
Give(idPlayer * owner,const idDict & spawnArgs,const char * statname,const char * value,int * idealWeapon,bool updateHud)733 bool idInventory::Give( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value, int *idealWeapon, bool updateHud ) {
734 int i;
735 const char *pos;
736 const char *end;
737 int len;
738 idStr weaponString;
739 int max;
740 const idDeclEntityDef *weaponDecl;
741 bool tookWeapon;
742 int amount;
743 idItemInfo info;
744 const char *name;
745
746 if ( !idStr::Icmpn( statname, "ammo_", 5 ) ) {
747 i = AmmoIndexForAmmoClass( statname );
748 max = MaxAmmoForAmmoClass( owner, statname );
749 if ( ammo[ i ] >= max ) {
750 return false;
751 }
752 amount = atoi( value );
753 if ( amount ) {
754 ammo[ i ] += amount;
755 if ( ( max > 0 ) && ( ammo[ i ] > max ) ) {
756 ammo[ i ] = max;
757 }
758 ammoPulse = true;
759
760 name = AmmoPickupNameForIndex( i );
761 if ( idStr::Length( name ) ) {
762 AddPickupName( name, "" );
763 }
764 }
765 } else if ( !idStr::Icmp( statname, "armor" ) ) {
766 if ( armor >= maxarmor ) {
767 return false; // can't hold any more, so leave the item
768 }
769 amount = atoi( value );
770 if ( amount ) {
771 armor += amount;
772 if ( armor > maxarmor ) {
773 armor = maxarmor;
774 }
775 nextArmorDepleteTime = 0;
776 armorPulse = true;
777 }
778 } else if ( idStr::FindText( statname, "inclip_" ) == 0 ) {
779 i = WeaponIndexForAmmoClass( spawnArgs, statname + 7 );
780 if ( i != -1 ) {
781 // set, don't add. not going over the clip size limit.
782 clip[ i ] = atoi( value );
783 }
784 } else if ( !idStr::Icmp( statname, "berserk" ) ) {
785 GivePowerUp( owner, BERSERK, SEC2MS( atof( value ) ) );
786 } else if ( !idStr::Icmp( statname, "mega" ) ) {
787 GivePowerUp( owner, MEGAHEALTH, SEC2MS( atof( value ) ) );
788 } else if ( !idStr::Icmp( statname, "weapon" ) ) {
789 tookWeapon = false;
790 for( pos = value; pos != NULL; pos = end ) {
791 end = strchr( pos, ',' );
792 if ( end ) {
793 len = end - pos;
794 end++;
795 } else {
796 len = strlen( pos );
797 }
798
799 idStr weaponName( pos, 0, len );
800
801 // find the number of the matching weapon name
802 for( i = 0; i < MAX_WEAPONS; i++ ) {
803 if ( weaponName == spawnArgs.GetString( va( "def_weapon%d", i ) ) ) {
804 break;
805 }
806 }
807
808 if ( i >= MAX_WEAPONS ) {
809 gameLocal.Error( "Unknown weapon '%s'", weaponName.c_str() );
810 }
811
812 // cache the media for this weapon
813 weaponDecl = gameLocal.FindEntityDef( weaponName, false );
814
815 // don't pickup "no ammo" weapon types twice
816 // not for D3 SP .. there is only one case in the game where you can get a no ammo
817 // weapon when you might already have it, in that case it is more conistent to pick it up
818 if ( gameLocal.isMultiplayer && weaponDecl && ( weapons & ( 1 << i ) ) && !weaponDecl->dict.GetInt( "ammoRequired" ) ) {
819 continue;
820 }
821
822 if ( !gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || ( weaponName == "weapon_fists" ) || ( weaponName == "weapon_soulcube" ) ) {
823 if ( ( weapons & ( 1 << i ) ) == 0 || gameLocal.isMultiplayer ) {
824 if ( owner->GetUserInfo()->GetBool( "ui_autoSwitch" ) && idealWeapon ) {
825 assert( !gameLocal.isClient );
826 *idealWeapon = i;
827 }
828 if ( owner->hud && updateHud && lastGiveTime + 1000 < gameLocal.time ) {
829 owner->hud->SetStateInt( "newWeapon", i );
830 owner->hud->HandleNamedEvent( "newWeapon" );
831 lastGiveTime = gameLocal.time;
832 }
833 weaponPulse = true;
834 weapons |= ( 1 << i );
835 tookWeapon = true;
836 }
837 }
838 }
839 return tookWeapon;
840 } else if ( !idStr::Icmp( statname, "item" ) || !idStr::Icmp( statname, "icon" ) || !idStr::Icmp( statname, "name" ) ) {
841 // ignore these as they're handled elsewhere
842 return false;
843 } else {
844 // unknown item
845 gameLocal.Warning( "Unknown stat '%s' added to player's inventory", statname );
846 return false;
847 }
848
849 return true;
850 }
851
852 /*
853 ===============
854 idInventoy::Drop
855 ===============
856 */
Drop(const idDict & spawnArgs,const char * weapon_classname,int weapon_index)857 void idInventory::Drop( const idDict &spawnArgs, const char *weapon_classname, int weapon_index ) {
858 // remove the weapon bit
859 // also remove the ammo associated with the weapon as we pushed it in the item
860 assert( weapon_index != -1 || weapon_classname );
861 if ( weapon_index == -1 ) {
862 for( weapon_index = 0; weapon_index < MAX_WEAPONS; weapon_index++ ) {
863 if ( !idStr::Icmp( weapon_classname, spawnArgs.GetString( va( "def_weapon%d", weapon_index ) ) ) ) {
864 break;
865 }
866 }
867 if ( weapon_index >= MAX_WEAPONS ) {
868 gameLocal.Error( "Unknown weapon '%s'", weapon_classname );
869 }
870 } else if ( !weapon_classname ) {
871 weapon_classname = spawnArgs.GetString( va( "def_weapon%d", weapon_index ) );
872 }
873 weapons &= ( 0xffffffff ^ ( 1 << weapon_index ) );
874 ammo_t ammo_i = AmmoIndexForWeaponClass( weapon_classname, NULL );
875 if ( ammo_i ) {
876 clip[ weapon_index ] = -1;
877 ammo[ ammo_i ] = 0;
878 }
879 }
880
881 /*
882 ===============
883 idInventory::HasAmmo
884 ===============
885 */
HasAmmo(ammo_t type,int amount)886 int idInventory::HasAmmo( ammo_t type, int amount ) {
887 if ( ( type == 0 ) || !amount ) {
888 // always allow weapons that don't use ammo to fire
889 return -1;
890 }
891
892 // check if we have infinite ammo
893 if ( ammo[ type ] < 0 ) {
894 return -1;
895 }
896
897 // return how many shots we can fire
898 return ammo[ type ] / amount;
899 }
900
901 /*
902 ===============
903 idInventory::HasAmmo
904 ===============
905 */
HasAmmo(const char * weapon_classname)906 int idInventory::HasAmmo( const char *weapon_classname ) {
907 int ammoRequired;
908 ammo_t ammo_i = AmmoIndexForWeaponClass( weapon_classname, &ammoRequired );
909 return HasAmmo( ammo_i, ammoRequired );
910 }
911
912 /*
913 ===============
914 idInventory::UseAmmo
915 ===============
916 */
UseAmmo(ammo_t type,int amount)917 bool idInventory::UseAmmo( ammo_t type, int amount ) {
918 if ( !HasAmmo( type, amount ) ) {
919 return false;
920 }
921
922 // take an ammo away if not infinite
923 if ( ammo[ type ] >= 0 ) {
924 ammo[ type ] -= amount;
925 ammoPredictTime = gameLocal.time; // mp client: we predict this. mark time so we're not confused by snapshots
926 }
927
928 return true;
929 }
930
931 /*
932 ===============
933 idInventory::UpdateArmor
934 ===============
935 */
UpdateArmor(void)936 void idInventory::UpdateArmor( void ) {
937 if ( deplete_armor != 0.0f && deplete_armor < armor ) {
938 if ( !nextArmorDepleteTime ) {
939 nextArmorDepleteTime = gameLocal.time + deplete_rate * 1000;
940 } else if ( gameLocal.time > nextArmorDepleteTime ) {
941 armor -= deplete_ammount;
942 if ( armor < deplete_armor ) {
943 armor = deplete_armor;
944 }
945 nextArmorDepleteTime = gameLocal.time + deplete_rate * 1000;
946 }
947 }
948 }
949
950 /*
951 ==============
952 idPlayer::idPlayer
953 ==============
954 */
idPlayer()955 idPlayer::idPlayer() {
956 memset( &usercmd, 0, sizeof( usercmd ) );
957
958 noclip = false;
959 godmode = false;
960
961 spawnAnglesSet = false;
962 spawnAngles = ang_zero;
963 viewAngles = ang_zero;
964 cmdAngles = ang_zero;
965
966 oldButtons = 0;
967 buttonMask = 0;
968 oldFlags = 0;
969
970 lastHitTime = 0;
971 lastSndHitTime = 0;
972 lastSavingThrowTime = 0;
973
974 weapon = NULL;
975
976 hud = NULL;
977 objectiveSystem = NULL;
978 objectiveSystemOpen = false;
979
980 heartRate = BASE_HEARTRATE;
981 heartInfo.Init( 0, 0, 0, 0 );
982 lastHeartAdjust = 0;
983 lastHeartBeat = 0;
984 lastDmgTime = 0;
985 deathClearContentsTime = 0;
986 lastArmorPulse = -10000;
987 stamina = 0.0f;
988 healthPool = 0.0f;
989 nextHealthPulse = 0;
990 healthPulse = false;
991 nextHealthTake = 0;
992 healthTake = false;
993
994 scoreBoardOpen = false;
995 forceScoreBoard = false;
996 forceRespawn = false;
997 spectating = false;
998 spectator = 0;
999 colorBar = vec3_zero;
1000 colorBarIndex = 0;
1001 forcedReady = false;
1002 wantSpectate = false;
1003
1004 lastHitToggle = false;
1005
1006 minRespawnTime = 0;
1007 maxRespawnTime = 0;
1008
1009 firstPersonViewOrigin = vec3_zero;
1010 firstPersonViewAxis = mat3_identity;
1011
1012 hipJoint = INVALID_JOINT;
1013 chestJoint = INVALID_JOINT;
1014 headJoint = INVALID_JOINT;
1015
1016 bobFoot = 0;
1017 bobFrac = 0.0f;
1018 bobfracsin = 0.0f;
1019 bobCycle = 0;
1020 xyspeed = 0.0f;
1021 stepUpTime = 0;
1022 stepUpDelta = 0.0f;
1023 idealLegsYaw = 0.0f;
1024 legsYaw = 0.0f;
1025 legsForward = true;
1026 oldViewYaw = 0.0f;
1027 viewBobAngles = ang_zero;
1028 viewBob = vec3_zero;
1029 landChange = 0;
1030 landTime = 0;
1031
1032 currentWeapon = -1;
1033 idealWeapon = -1;
1034 previousWeapon = -1;
1035 weaponSwitchTime = 0;
1036 weaponEnabled = true;
1037 weapon_soulcube = -1;
1038 weapon_pda = -1;
1039 weapon_fists = -1;
1040 showWeaponViewModel = true;
1041
1042 skin = NULL;
1043 powerUpSkin = NULL;
1044 baseSkinName = "";
1045
1046 numProjectilesFired = 0;
1047 numProjectileHits = 0;
1048
1049 airless = false;
1050 airTics = 0;
1051 lastAirDamage = 0;
1052
1053 gibDeath = false;
1054 gibsLaunched = false;
1055 gibsDir = vec3_zero;
1056
1057 zoomFov.Init( 0, 0, 0, 0 );
1058 centerView.Init( 0, 0, 0, 0 );
1059 fxFov = false;
1060
1061 influenceFov = 0;
1062 influenceActive = 0;
1063 influenceRadius = 0.0f;
1064 influenceEntity = NULL;
1065 influenceMaterial = NULL;
1066 influenceSkin = NULL;
1067
1068 privateCameraView = NULL;
1069
1070 memset( loggedViewAngles, 0, sizeof( loggedViewAngles ) );
1071 memset( loggedAccel, 0, sizeof( loggedAccel ) );
1072 currentLoggedAccel = 0;
1073
1074 focusTime = 0;
1075 focusGUIent = NULL;
1076 focusUI = NULL;
1077 focusCharacter = NULL;
1078 talkCursor = 0;
1079 focusVehicle = NULL;
1080 cursor = NULL;
1081
1082 oldMouseX = 0;
1083 oldMouseY = 0;
1084
1085 pdaAudio = "";
1086 pdaVideo = "";
1087 pdaVideoWave = "";
1088
1089 lastDamageDef = 0;
1090 lastDamageDir = vec3_zero;
1091 lastDamageLocation = 0;
1092 smoothedFrame = 0;
1093 smoothedOriginUpdated = false;
1094 smoothedOrigin = vec3_zero;
1095 smoothedAngles = ang_zero;
1096
1097 fl.networkSync = true;
1098
1099 latchedTeam = -1;
1100 doingDeathSkin = false;
1101 weaponGone = false;
1102 useInitialSpawns = false;
1103 tourneyRank = 0;
1104 lastSpectateTeleport = 0;
1105 tourneyLine = 0;
1106 hiddenWeapon = false;
1107 tipUp = false;
1108 objectiveUp = false;
1109 teleportEntity = NULL;
1110 teleportKiller = -1;
1111 respawning = false;
1112 ready = false;
1113 leader = false;
1114 lastSpectateChange = 0;
1115 lastTeleFX = -9999;
1116 weaponCatchup = false;
1117 lastSnapshotSequence = 0;
1118
1119 MPAim = -1;
1120 lastMPAim = -1;
1121 lastMPAimTime = 0;
1122 MPAimFadeTime = 0;
1123 MPAimHighlight = false;
1124
1125 spawnedTime = 0;
1126 lastManOver = false;
1127 lastManPlayAgain = false;
1128 lastManPresent = false;
1129
1130 isTelefragged = false;
1131
1132 isLagged = false;
1133 isChatting = false;
1134
1135 selfSmooth = false;
1136 }
1137
1138 /*
1139 ==============
1140 idPlayer::LinkScriptVariables
1141
1142 set up conditions for animation
1143 ==============
1144 */
LinkScriptVariables(void)1145 void idPlayer::LinkScriptVariables( void ) {
1146 AI_FORWARD.LinkTo( scriptObject, "AI_FORWARD" );
1147 AI_BACKWARD.LinkTo( scriptObject, "AI_BACKWARD" );
1148 AI_STRAFE_LEFT.LinkTo( scriptObject, "AI_STRAFE_LEFT" );
1149 AI_STRAFE_RIGHT.LinkTo( scriptObject, "AI_STRAFE_RIGHT" );
1150 AI_ATTACK_HELD.LinkTo( scriptObject, "AI_ATTACK_HELD" );
1151 AI_WEAPON_FIRED.LinkTo( scriptObject, "AI_WEAPON_FIRED" );
1152 AI_JUMP.LinkTo( scriptObject, "AI_JUMP" );
1153 AI_DEAD.LinkTo( scriptObject, "AI_DEAD" );
1154 AI_CROUCH.LinkTo( scriptObject, "AI_CROUCH" );
1155 AI_ONGROUND.LinkTo( scriptObject, "AI_ONGROUND" );
1156 AI_ONLADDER.LinkTo( scriptObject, "AI_ONLADDER" );
1157 AI_HARDLANDING.LinkTo( scriptObject, "AI_HARDLANDING" );
1158 AI_SOFTLANDING.LinkTo( scriptObject, "AI_SOFTLANDING" );
1159 AI_RUN.LinkTo( scriptObject, "AI_RUN" );
1160 AI_PAIN.LinkTo( scriptObject, "AI_PAIN" );
1161 AI_RELOAD.LinkTo( scriptObject, "AI_RELOAD" );
1162 AI_TELEPORT.LinkTo( scriptObject, "AI_TELEPORT" );
1163 AI_TURN_LEFT.LinkTo( scriptObject, "AI_TURN_LEFT" );
1164 AI_TURN_RIGHT.LinkTo( scriptObject, "AI_TURN_RIGHT" );
1165 }
1166
1167 /*
1168 ==============
1169 idPlayer::SetupWeaponEntity
1170 ==============
1171 */
SetupWeaponEntity(void)1172 void idPlayer::SetupWeaponEntity( void ) {
1173 int w;
1174 const char *weap;
1175
1176 if ( weapon.GetEntity() ) {
1177 // get rid of old weapon
1178 weapon.GetEntity()->Clear();
1179 currentWeapon = -1;
1180 } else if ( !gameLocal.isClient ) {
1181 weapon = static_cast<idWeapon *>( gameLocal.SpawnEntityType( idWeapon::Type, NULL ) );
1182 weapon.GetEntity()->SetOwner( this );
1183 currentWeapon = -1;
1184 }
1185
1186 for( w = 0; w < MAX_WEAPONS; w++ ) {
1187 weap = spawnArgs.GetString( va( "def_weapon%d", w ) );
1188 if ( weap && *weap ) {
1189 idWeapon::CacheWeapon( weap );
1190 }
1191 }
1192 }
1193
1194 /*
1195 ==============
1196 idPlayer::Init
1197 ==============
1198 */
Init(void)1199 void idPlayer::Init( void ) {
1200 const char *value;
1201 const idKeyValue *kv;
1202
1203 noclip = false;
1204 godmode = false;
1205
1206 oldButtons = 0;
1207 oldFlags = 0;
1208
1209 currentWeapon = -1;
1210 idealWeapon = -1;
1211 previousWeapon = -1;
1212 weaponSwitchTime = 0;
1213 weaponEnabled = true;
1214 weapon_soulcube = SlotForWeapon( "weapon_soulcube" );
1215 weapon_pda = SlotForWeapon( "weapon_pda" );
1216 weapon_fists = SlotForWeapon( "weapon_fists" );
1217 showWeaponViewModel = GetUserInfo()->GetBool( "ui_showGun" );
1218
1219
1220 lastDmgTime = 0;
1221 lastArmorPulse = -10000;
1222 lastHeartAdjust = 0;
1223 lastHeartBeat = 0;
1224 heartInfo.Init( 0, 0, 0, 0 );
1225
1226 bobCycle = 0;
1227 bobFrac = 0.0f;
1228 landChange = 0;
1229 landTime = 0;
1230 zoomFov.Init( 0, 0, 0, 0 );
1231 centerView.Init( 0, 0, 0, 0 );
1232 fxFov = false;
1233
1234 influenceFov = 0;
1235 influenceActive = 0;
1236 influenceRadius = 0.0f;
1237 influenceEntity = NULL;
1238 influenceMaterial = NULL;
1239 influenceSkin = NULL;
1240
1241 currentLoggedAccel = 0;
1242
1243 focusTime = 0;
1244 focusGUIent = NULL;
1245 focusUI = NULL;
1246 focusCharacter = NULL;
1247 talkCursor = 0;
1248 focusVehicle = NULL;
1249
1250 // remove any damage effects
1251 playerView.ClearEffects();
1252
1253 // damage values
1254 fl.takedamage = true;
1255 ClearPain();
1256
1257 // restore persistent data
1258 RestorePersistantInfo();
1259
1260 bobCycle = 0;
1261 stamina = 0.0f;
1262 healthPool = 0.0f;
1263 nextHealthPulse = 0;
1264 healthPulse = false;
1265 nextHealthTake = 0;
1266 healthTake = false;
1267
1268 SetupWeaponEntity();
1269 currentWeapon = -1;
1270 previousWeapon = -1;
1271
1272 heartRate = BASE_HEARTRATE;
1273 AdjustHeartRate( BASE_HEARTRATE, 0.0f, 0.0f, true );
1274
1275 idealLegsYaw = 0.0f;
1276 legsYaw = 0.0f;
1277 legsForward = true;
1278 oldViewYaw = 0.0f;
1279
1280 // set the pm_ cvars
1281 if ( !gameLocal.isMultiplayer || gameLocal.isServer ) {
1282 kv = spawnArgs.MatchPrefix( "pm_", NULL );
1283 while( kv ) {
1284 cvarSystem->SetCVarString( kv->GetKey(), kv->GetValue() );
1285 kv = spawnArgs.MatchPrefix( "pm_", kv );
1286 }
1287 }
1288
1289 // disable stamina on hell levels
1290 if ( gameLocal.world && gameLocal.world->spawnArgs.GetBool( "no_stamina" ) ) {
1291 pm_stamina.SetFloat( 0.0f );
1292 }
1293
1294 // stamina always initialized to maximum
1295 stamina = pm_stamina.GetFloat();
1296
1297 // air always initialized to maximum too
1298 airTics = pm_airTics.GetFloat();
1299 airless = false;
1300
1301 gibDeath = false;
1302 gibsLaunched = false;
1303 gibsDir.Zero();
1304
1305 // set the gravity
1306 physicsObj.SetGravity( gameLocal.GetGravity() );
1307
1308 // start out standing
1309 SetEyeHeight( pm_normalviewheight.GetFloat() );
1310
1311 stepUpTime = 0;
1312 stepUpDelta = 0.0f;
1313 viewBobAngles.Zero();
1314 viewBob.Zero();
1315
1316 value = spawnArgs.GetString( "model" );
1317 if ( value && ( *value != 0 ) ) {
1318 SetModel( value );
1319 }
1320
1321 if ( cursor ) {
1322 cursor->SetStateInt( "talkcursor", 0 );
1323 cursor->SetStateString( "combatcursor", "1" );
1324 cursor->SetStateString( "itemcursor", "0" );
1325 cursor->SetStateString( "guicursor", "0" );
1326 }
1327
1328 if ( ( gameLocal.isMultiplayer || g_testDeath.GetBool() ) && skin ) {
1329 SetSkin( skin );
1330 renderEntity.shaderParms[6] = 0.0f;
1331 } else if ( spawnArgs.GetString( "spawn_skin", NULL, &value ) ) {
1332 skin = declManager->FindSkin( value );
1333 SetSkin( skin );
1334 renderEntity.shaderParms[6] = 0.0f;
1335 }
1336
1337 value = spawnArgs.GetString( "bone_hips", "" );
1338 hipJoint = animator.GetJointHandle( value );
1339 if ( hipJoint == INVALID_JOINT ) {
1340 gameLocal.Error( "Joint '%s' not found for 'bone_hips' on '%s'", value, name.c_str() );
1341 }
1342
1343 value = spawnArgs.GetString( "bone_chest", "" );
1344 chestJoint = animator.GetJointHandle( value );
1345 if ( chestJoint == INVALID_JOINT ) {
1346 gameLocal.Error( "Joint '%s' not found for 'bone_chest' on '%s'", value, name.c_str() );
1347 }
1348
1349 value = spawnArgs.GetString( "bone_head", "" );
1350 headJoint = animator.GetJointHandle( value );
1351 if ( headJoint == INVALID_JOINT ) {
1352 gameLocal.Error( "Joint '%s' not found for 'bone_head' on '%s'", value, name.c_str() );
1353 }
1354
1355 // initialize the script variables
1356 AI_FORWARD = false;
1357 AI_BACKWARD = false;
1358 AI_STRAFE_LEFT = false;
1359 AI_STRAFE_RIGHT = false;
1360 AI_ATTACK_HELD = false;
1361 AI_WEAPON_FIRED = false;
1362 AI_JUMP = false;
1363 AI_DEAD = false;
1364 AI_CROUCH = false;
1365 AI_ONGROUND = true;
1366 AI_ONLADDER = false;
1367 AI_HARDLANDING = false;
1368 AI_SOFTLANDING = false;
1369 AI_RUN = false;
1370 AI_PAIN = false;
1371 AI_RELOAD = false;
1372 AI_TELEPORT = false;
1373 AI_TURN_LEFT = false;
1374 AI_TURN_RIGHT = false;
1375
1376 // reset the script object
1377 ConstructScriptObject();
1378
1379 // execute the script so the script object's constructor takes effect immediately
1380 scriptThread->Execute();
1381
1382 forceScoreBoard = false;
1383 forcedReady = false;
1384
1385 privateCameraView = NULL;
1386
1387 lastSpectateChange = 0;
1388 lastTeleFX = -9999;
1389
1390 hiddenWeapon = false;
1391 tipUp = false;
1392 objectiveUp = false;
1393 teleportEntity = NULL;
1394 teleportKiller = -1;
1395 leader = false;
1396
1397 SetPrivateCameraView( NULL );
1398
1399 lastSnapshotSequence = 0;
1400
1401 MPAim = -1;
1402 lastMPAim = -1;
1403 lastMPAimTime = 0;
1404 MPAimFadeTime = 0;
1405 MPAimHighlight = false;
1406
1407 if ( hud ) {
1408 hud->HandleNamedEvent( "aim_clear" );
1409 }
1410
1411 cvarSystem->SetCVarBool( "ui_chat", false );
1412 }
1413
1414 /*
1415 ==============
1416 idPlayer::Spawn
1417
1418 Prepare any resources used by the player.
1419 ==============
1420 */
Spawn(void)1421 void idPlayer::Spawn( void ) {
1422 idStr temp;
1423 idBounds bounds;
1424
1425 if ( entityNumber >= MAX_CLIENTS ) {
1426 gameLocal.Error( "entityNum > MAX_CLIENTS for player. Player may only be spawned with a client." );
1427 }
1428
1429 // allow thinking during cinematics
1430 cinematic = true;
1431
1432 if ( gameLocal.isMultiplayer ) {
1433 // always start in spectating state waiting to be spawned in
1434 // do this before SetClipModel to get the right bounding box
1435 spectating = true;
1436 }
1437
1438 // set our collision model
1439 physicsObj.SetSelf( this );
1440 SetClipModel();
1441 physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) );
1442 physicsObj.SetContents( CONTENTS_BODY );
1443 physicsObj.SetClipMask( MASK_PLAYERSOLID );
1444 SetPhysics( &physicsObj );
1445 InitAASLocation();
1446
1447 skin = renderEntity.customSkin;
1448
1449 // only the local player needs guis
1450 if ( !gameLocal.isMultiplayer || entityNumber == gameLocal.localClientNum ) {
1451
1452 // load HUD
1453 if ( gameLocal.isMultiplayer ) {
1454 hud = uiManager->FindGui( "guis/mphud.gui", true, false, true );
1455 } else if ( spawnArgs.GetString( "hud", "", temp ) ) {
1456 hud = uiManager->FindGui( temp, true, false, true );
1457 }
1458 if ( hud ) {
1459 hud->Activate( true, gameLocal.time );
1460 }
1461
1462 // load cursor
1463 if ( spawnArgs.GetString( "cursor", "", temp ) ) {
1464 cursor = uiManager->FindGui( temp, true, gameLocal.isMultiplayer, gameLocal.isMultiplayer );
1465 }
1466 if ( cursor ) {
1467 // DG: make it scale to 4:3 so crosshair looks properly round
1468 // yes, like so many scaling-related things this is a bit hacky
1469 // and note that this is special cased in StateChanged and you
1470 // can *not* generally set windowDef properties like this.
1471 cursor->SetStateBool("scaleto43", true);
1472 cursor->StateChanged(gameLocal.time); // DG end
1473
1474 cursor->Activate( true, gameLocal.time );
1475 }
1476
1477 objectiveSystem = uiManager->FindGui( "guis/pda.gui", true, false, true );
1478 objectiveSystemOpen = false;
1479 }
1480
1481 SetLastHitTime( 0 );
1482
1483 // load the armor sound feedback
1484 declManager->FindSound( "player_sounds_hitArmor" );
1485
1486 // set up conditions for animation
1487 LinkScriptVariables();
1488
1489 animator.RemoveOriginOffset( true );
1490
1491 // initialize user info related settings
1492 // on server, we wait for the userinfo broadcast, as this controls when the player is initially spawned in game
1493 if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
1494 UserInfoChanged( false );
1495 }
1496
1497 // create combat collision hull for exact collision detection
1498 SetCombatModel();
1499
1500 // init the damage effects
1501 playerView.SetPlayerEntity( this );
1502
1503 // supress model in non-player views, but allow it in mirrors and remote views
1504 renderEntity.suppressSurfaceInViewID = entityNumber+1;
1505
1506 // don't project shadow on self or weapon
1507 renderEntity.noSelfShadow = true;
1508
1509 idAFAttachment *headEnt = head.GetEntity();
1510 if ( headEnt ) {
1511 headEnt->GetRenderEntity()->suppressSurfaceInViewID = entityNumber+1;
1512 headEnt->GetRenderEntity()->noSelfShadow = true;
1513 }
1514
1515 if ( gameLocal.isMultiplayer ) {
1516 Init();
1517 Hide(); // properly hidden if starting as a spectator
1518 if ( !gameLocal.isClient ) {
1519 // set yourself ready to spawn. idMultiplayerGame will decide when/if appropriate and call SpawnFromSpawnSpot
1520 SetupWeaponEntity();
1521 SpawnFromSpawnSpot();
1522 forceRespawn = true;
1523 assert( spectating );
1524 }
1525 } else {
1526 SetupWeaponEntity();
1527 SpawnFromSpawnSpot();
1528 }
1529
1530 // trigger playtesting item gives, if we didn't get here from a previous level
1531 // the devmap key will be set on the first devmap, but cleared on any level
1532 // transitions
1533 if ( !gameLocal.isMultiplayer && gameLocal.serverInfo.FindKey( "devmap" ) ) {
1534 // fire a trigger with the name "devmap"
1535 idEntity *ent = gameLocal.FindEntity( "devmap" );
1536 if ( ent ) {
1537 ent->ActivateTargets( this );
1538 }
1539 }
1540 if ( hud ) {
1541 // We can spawn with a full soul cube, so we need to make sure the hud knows this
1542 if ( weapon_soulcube > 0 && ( inventory.weapons & ( 1 << weapon_soulcube ) ) ) {
1543 int max_souls = inventory.MaxAmmoForAmmoClass( this, "ammo_souls" );
1544 if ( inventory.ammo[ idWeapon::GetAmmoNumForName( "ammo_souls" ) ] >= max_souls ) {
1545 hud->HandleNamedEvent( "soulCubeReady" );
1546 }
1547 }
1548 hud->HandleNamedEvent( "itemPickup" );
1549 }
1550
1551 if ( GetPDA() ) {
1552 // Add any emails from the inventory
1553 for ( int i = 0; i < inventory.emails.Num(); i++ ) {
1554 GetPDA()->AddEmail( inventory.emails[i] );
1555 }
1556 GetPDA()->SetSecurity( common->GetLanguageDict()->GetString( "#str_00066" ) );
1557 }
1558
1559 if ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) {
1560 hiddenWeapon = true;
1561 if ( weapon.GetEntity() ) {
1562 weapon.GetEntity()->LowerWeapon();
1563 }
1564 idealWeapon = 0;
1565 } else {
1566 hiddenWeapon = false;
1567 }
1568
1569 if ( hud ) {
1570 UpdateHudWeapon();
1571 hud->StateChanged( gameLocal.time );
1572 }
1573
1574 tipUp = false;
1575 objectiveUp = false;
1576
1577 if ( inventory.levelTriggers.Num() ) {
1578 PostEventMS( &EV_Player_LevelTrigger, 0 );
1579 }
1580
1581 inventory.pdaOpened = false;
1582 inventory.selPDA = 0;
1583
1584 if ( !gameLocal.isMultiplayer ) {
1585 if ( g_skill.GetInteger() < 2 ) {
1586 if ( health < 25 ) {
1587 health = 25;
1588 }
1589 if ( g_useDynamicProtection.GetBool() ) {
1590 g_damageScale.SetFloat( 1.0f );
1591 }
1592 } else {
1593 g_damageScale.SetFloat( 1.0f );
1594 g_armorProtection.SetFloat( ( g_skill.GetInteger() < 2 ) ? 0.4f : 0.2f );
1595
1596 if ( g_skill.GetInteger() == 3 ) {
1597 healthTake = true;
1598 nextHealthTake = gameLocal.time + g_healthTakeTime.GetInteger() * 1000;
1599 }
1600 }
1601 }
1602 }
1603
1604 /*
1605 ==============
1606 idPlayer::~idPlayer()
1607
1608 Release any resources used by the player.
1609 ==============
1610 */
~idPlayer()1611 idPlayer::~idPlayer() {
1612 delete weapon.GetEntity();
1613 weapon = NULL;
1614 }
1615
1616 /*
1617 ===========
1618 idPlayer::Save
1619 ===========
1620 */
Save(idSaveGame * savefile) const1621 void idPlayer::Save( idSaveGame *savefile ) const {
1622 int i;
1623
1624 savefile->WriteUsercmd( usercmd );
1625 playerView.Save( savefile );
1626
1627 savefile->WriteBool( noclip );
1628 savefile->WriteBool( godmode );
1629
1630 // don't save spawnAnglesSet, since we'll have to reset them after loading the savegame
1631 savefile->WriteAngles( spawnAngles );
1632 savefile->WriteAngles( viewAngles );
1633 savefile->WriteAngles( cmdAngles );
1634
1635 savefile->WriteInt( buttonMask );
1636 savefile->WriteInt( oldButtons );
1637 savefile->WriteInt( oldFlags );
1638
1639 savefile->WriteInt( lastHitTime );
1640 savefile->WriteInt( lastSndHitTime );
1641 savefile->WriteInt( lastSavingThrowTime );
1642
1643 // idBoolFields don't need to be saved, just re-linked in Restore
1644
1645 inventory.Save( savefile );
1646 weapon.Save( savefile );
1647
1648 savefile->WriteUserInterface( hud, false );
1649 savefile->WriteUserInterface( objectiveSystem, false );
1650 savefile->WriteBool( objectiveSystemOpen );
1651
1652 savefile->WriteInt( weapon_soulcube );
1653 savefile->WriteInt( weapon_pda );
1654 savefile->WriteInt( weapon_fists );
1655
1656 savefile->WriteInt( heartRate );
1657
1658 savefile->WriteFloat( heartInfo.GetStartTime() );
1659 savefile->WriteFloat( heartInfo.GetDuration() );
1660 savefile->WriteFloat( heartInfo.GetStartValue() );
1661 savefile->WriteFloat( heartInfo.GetEndValue() );
1662
1663 savefile->WriteInt( lastHeartAdjust );
1664 savefile->WriteInt( lastHeartBeat );
1665 savefile->WriteInt( lastDmgTime );
1666 savefile->WriteInt( deathClearContentsTime );
1667 savefile->WriteBool( doingDeathSkin );
1668 savefile->WriteInt( lastArmorPulse );
1669 savefile->WriteFloat( stamina );
1670 savefile->WriteFloat( healthPool );
1671 savefile->WriteInt( nextHealthPulse );
1672 savefile->WriteBool( healthPulse );
1673 savefile->WriteInt( nextHealthTake );
1674 savefile->WriteBool( healthTake );
1675
1676 savefile->WriteBool( hiddenWeapon );
1677 soulCubeProjectile.Save( savefile );
1678
1679 savefile->WriteInt( spectator );
1680 savefile->WriteVec3( colorBar );
1681 savefile->WriteInt( colorBarIndex );
1682 savefile->WriteBool( scoreBoardOpen );
1683 savefile->WriteBool( forceScoreBoard );
1684 savefile->WriteBool( forceRespawn );
1685 savefile->WriteBool( spectating );
1686 savefile->WriteInt( lastSpectateTeleport );
1687 savefile->WriteBool( lastHitToggle );
1688 savefile->WriteBool( forcedReady );
1689 savefile->WriteBool( wantSpectate );
1690 savefile->WriteBool( weaponGone );
1691 savefile->WriteBool( useInitialSpawns );
1692 savefile->WriteInt( latchedTeam );
1693 savefile->WriteInt( tourneyRank );
1694 savefile->WriteInt( tourneyLine );
1695
1696 teleportEntity.Save( savefile );
1697 savefile->WriteInt( teleportKiller );
1698
1699 savefile->WriteInt( minRespawnTime );
1700 savefile->WriteInt( maxRespawnTime );
1701
1702 savefile->WriteVec3( firstPersonViewOrigin );
1703 savefile->WriteMat3( firstPersonViewAxis );
1704
1705 // don't bother saving dragEntity since it's a dev tool
1706
1707 savefile->WriteJoint( hipJoint );
1708 savefile->WriteJoint( chestJoint );
1709 savefile->WriteJoint( headJoint );
1710
1711 savefile->WriteStaticObject( physicsObj );
1712
1713 savefile->WriteInt( aasLocation.Num() );
1714 for( i = 0; i < aasLocation.Num(); i++ ) {
1715 savefile->WriteInt( aasLocation[ i ].areaNum );
1716 savefile->WriteVec3( aasLocation[ i ].pos );
1717 }
1718
1719 savefile->WriteInt( bobFoot );
1720 savefile->WriteFloat( bobFrac );
1721 savefile->WriteFloat( bobfracsin );
1722 savefile->WriteInt( bobCycle );
1723 savefile->WriteFloat( xyspeed );
1724 savefile->WriteInt( stepUpTime );
1725 savefile->WriteFloat( stepUpDelta );
1726 savefile->WriteFloat( idealLegsYaw );
1727 savefile->WriteFloat( legsYaw );
1728 savefile->WriteBool( legsForward );
1729 savefile->WriteFloat( oldViewYaw );
1730 savefile->WriteAngles( viewBobAngles );
1731 savefile->WriteVec3( viewBob );
1732 savefile->WriteInt( landChange );
1733 savefile->WriteInt( landTime );
1734
1735 savefile->WriteInt( currentWeapon );
1736 savefile->WriteInt( idealWeapon );
1737 savefile->WriteInt( previousWeapon );
1738 savefile->WriteInt( weaponSwitchTime );
1739 savefile->WriteBool( weaponEnabled );
1740 savefile->WriteBool( showWeaponViewModel );
1741
1742 savefile->WriteSkin( skin );
1743 savefile->WriteSkin( powerUpSkin );
1744 savefile->WriteString( baseSkinName );
1745
1746 savefile->WriteInt( numProjectilesFired );
1747 savefile->WriteInt( numProjectileHits );
1748
1749 savefile->WriteBool( airless );
1750 savefile->WriteInt( airTics );
1751 savefile->WriteInt( lastAirDamage );
1752
1753 savefile->WriteBool( gibDeath );
1754 savefile->WriteBool( gibsLaunched );
1755 savefile->WriteVec3( gibsDir );
1756
1757 savefile->WriteFloat( zoomFov.GetStartTime() );
1758 savefile->WriteFloat( zoomFov.GetDuration() );
1759 savefile->WriteFloat( zoomFov.GetStartValue() );
1760 savefile->WriteFloat( zoomFov.GetEndValue() );
1761
1762 savefile->WriteFloat( centerView.GetStartTime() );
1763 savefile->WriteFloat( centerView.GetDuration() );
1764 savefile->WriteFloat( centerView.GetStartValue() );
1765 savefile->WriteFloat( centerView.GetEndValue() );
1766
1767 savefile->WriteBool( fxFov );
1768
1769 savefile->WriteFloat( influenceFov );
1770 savefile->WriteInt( influenceActive );
1771 savefile->WriteFloat( influenceRadius );
1772 savefile->WriteObject( influenceEntity );
1773 savefile->WriteMaterial( influenceMaterial );
1774 savefile->WriteSkin( influenceSkin );
1775
1776 savefile->WriteObject( privateCameraView );
1777
1778 for( i = 0; i < NUM_LOGGED_VIEW_ANGLES; i++ ) {
1779 savefile->WriteAngles( loggedViewAngles[ i ] );
1780 }
1781 for( i = 0; i < NUM_LOGGED_ACCELS; i++ ) {
1782 savefile->WriteInt( loggedAccel[ i ].time );
1783 savefile->WriteVec3( loggedAccel[ i ].dir );
1784 }
1785 savefile->WriteInt( currentLoggedAccel );
1786
1787 savefile->WriteObject( focusGUIent );
1788 // can't save focusUI
1789 savefile->WriteObject( focusCharacter );
1790 savefile->WriteInt( talkCursor );
1791 savefile->WriteInt( focusTime );
1792 savefile->WriteObject( focusVehicle );
1793 savefile->WriteUserInterface( cursor, false );
1794
1795 savefile->WriteInt( oldMouseX );
1796 savefile->WriteInt( oldMouseY );
1797
1798 savefile->WriteString( pdaAudio );
1799 savefile->WriteString( pdaVideo );
1800 savefile->WriteString( pdaVideoWave );
1801
1802 savefile->WriteBool( tipUp );
1803 savefile->WriteBool( objectiveUp );
1804
1805 savefile->WriteInt( lastDamageDef );
1806 savefile->WriteVec3( lastDamageDir );
1807 savefile->WriteInt( lastDamageLocation );
1808 savefile->WriteInt( smoothedFrame );
1809 savefile->WriteBool( smoothedOriginUpdated );
1810 savefile->WriteVec3( smoothedOrigin );
1811 savefile->WriteAngles( smoothedAngles );
1812
1813 savefile->WriteBool( ready );
1814 savefile->WriteBool( respawning );
1815 savefile->WriteBool( leader );
1816 savefile->WriteInt( lastSpectateChange );
1817 savefile->WriteInt( lastTeleFX );
1818
1819 savefile->WriteFloat( pm_stamina.GetFloat() );
1820
1821 if ( hud ) {
1822 hud->SetStateString( "message", common->GetLanguageDict()->GetString( "#str_02916" ) );
1823 hud->HandleNamedEvent( "Message" );
1824 }
1825 }
1826
1827 /*
1828 ===========
1829 idPlayer::Restore
1830 ===========
1831 */
Restore(idRestoreGame * savefile)1832 void idPlayer::Restore( idRestoreGame *savefile ) {
1833 int i;
1834 int num;
1835 float set;
1836
1837 savefile->ReadUsercmd( usercmd );
1838 playerView.Restore( savefile );
1839
1840 savefile->ReadBool( noclip );
1841 savefile->ReadBool( godmode );
1842
1843 savefile->ReadAngles( spawnAngles );
1844 savefile->ReadAngles( viewAngles );
1845 savefile->ReadAngles( cmdAngles );
1846
1847 memset( usercmd.angles, 0, sizeof( usercmd.angles ) );
1848 SetViewAngles( viewAngles );
1849 spawnAnglesSet = true;
1850
1851 savefile->ReadInt( buttonMask );
1852 savefile->ReadInt( oldButtons );
1853 savefile->ReadInt( oldFlags );
1854
1855 usercmd.flags = 0;
1856 oldFlags = 0;
1857
1858 savefile->ReadInt( lastHitTime );
1859 savefile->ReadInt( lastSndHitTime );
1860 savefile->ReadInt( lastSavingThrowTime );
1861
1862 // Re-link idBoolFields to the scriptObject, values will be restored in scriptObject's restore
1863 LinkScriptVariables();
1864
1865 inventory.Restore( savefile );
1866 weapon.Restore( savefile );
1867
1868 for ( i = 0; i < inventory.emails.Num(); i++ ) {
1869 GetPDA()->AddEmail( inventory.emails[i] );
1870 }
1871
1872 savefile->ReadUserInterface( hud );
1873 savefile->ReadUserInterface( objectiveSystem );
1874 savefile->ReadBool( objectiveSystemOpen );
1875
1876 savefile->ReadInt( weapon_soulcube );
1877 savefile->ReadInt( weapon_pda );
1878 savefile->ReadInt( weapon_fists );
1879
1880 savefile->ReadInt( heartRate );
1881
1882 savefile->ReadFloat( set );
1883 heartInfo.SetStartTime( set );
1884 savefile->ReadFloat( set );
1885 heartInfo.SetDuration( set );
1886 savefile->ReadFloat( set );
1887 heartInfo.SetStartValue( set );
1888 savefile->ReadFloat( set );
1889 heartInfo.SetEndValue( set );
1890
1891 savefile->ReadInt( lastHeartAdjust );
1892 savefile->ReadInt( lastHeartBeat );
1893 savefile->ReadInt( lastDmgTime );
1894 savefile->ReadInt( deathClearContentsTime );
1895 savefile->ReadBool( doingDeathSkin );
1896 savefile->ReadInt( lastArmorPulse );
1897 savefile->ReadFloat( stamina );
1898 savefile->ReadFloat( healthPool );
1899 savefile->ReadInt( nextHealthPulse );
1900 savefile->ReadBool( healthPulse );
1901 savefile->ReadInt( nextHealthTake );
1902 savefile->ReadBool( healthTake );
1903
1904 savefile->ReadBool( hiddenWeapon );
1905 soulCubeProjectile.Restore( savefile );
1906
1907 savefile->ReadInt( spectator );
1908 savefile->ReadVec3( colorBar );
1909 savefile->ReadInt( colorBarIndex );
1910 savefile->ReadBool( scoreBoardOpen );
1911 savefile->ReadBool( forceScoreBoard );
1912 savefile->ReadBool( forceRespawn );
1913 savefile->ReadBool( spectating );
1914 savefile->ReadInt( lastSpectateTeleport );
1915 savefile->ReadBool( lastHitToggle );
1916 savefile->ReadBool( forcedReady );
1917 savefile->ReadBool( wantSpectate );
1918 savefile->ReadBool( weaponGone );
1919 savefile->ReadBool( useInitialSpawns );
1920 savefile->ReadInt( latchedTeam );
1921 savefile->ReadInt( tourneyRank );
1922 savefile->ReadInt( tourneyLine );
1923
1924 teleportEntity.Restore( savefile );
1925 savefile->ReadInt( teleportKiller );
1926
1927 savefile->ReadInt( minRespawnTime );
1928 savefile->ReadInt( maxRespawnTime );
1929
1930 savefile->ReadVec3( firstPersonViewOrigin );
1931 savefile->ReadMat3( firstPersonViewAxis );
1932
1933 // don't bother saving dragEntity since it's a dev tool
1934 dragEntity.Clear();
1935
1936 savefile->ReadJoint( hipJoint );
1937 savefile->ReadJoint( chestJoint );
1938 savefile->ReadJoint( headJoint );
1939
1940 savefile->ReadStaticObject( physicsObj );
1941 RestorePhysics( &physicsObj );
1942
1943 savefile->ReadInt( num );
1944 aasLocation.SetGranularity( 1 );
1945 aasLocation.SetNum( num );
1946 for( i = 0; i < num; i++ ) {
1947 savefile->ReadInt( aasLocation[ i ].areaNum );
1948 savefile->ReadVec3( aasLocation[ i ].pos );
1949 }
1950
1951 savefile->ReadInt( bobFoot );
1952 savefile->ReadFloat( bobFrac );
1953 savefile->ReadFloat( bobfracsin );
1954 savefile->ReadInt( bobCycle );
1955 savefile->ReadFloat( xyspeed );
1956 savefile->ReadInt( stepUpTime );
1957 savefile->ReadFloat( stepUpDelta );
1958 savefile->ReadFloat( idealLegsYaw );
1959 savefile->ReadFloat( legsYaw );
1960 savefile->ReadBool( legsForward );
1961 savefile->ReadFloat( oldViewYaw );
1962 savefile->ReadAngles( viewBobAngles );
1963 savefile->ReadVec3( viewBob );
1964 savefile->ReadInt( landChange );
1965 savefile->ReadInt( landTime );
1966
1967 savefile->ReadInt( currentWeapon );
1968 savefile->ReadInt( idealWeapon );
1969 savefile->ReadInt( previousWeapon );
1970 savefile->ReadInt( weaponSwitchTime );
1971 savefile->ReadBool( weaponEnabled );
1972 savefile->ReadBool( showWeaponViewModel );
1973
1974 savefile->ReadSkin( skin );
1975 savefile->ReadSkin( powerUpSkin );
1976 savefile->ReadString( baseSkinName );
1977
1978 savefile->ReadInt( numProjectilesFired );
1979 savefile->ReadInt( numProjectileHits );
1980
1981 savefile->ReadBool( airless );
1982 savefile->ReadInt( airTics );
1983 savefile->ReadInt( lastAirDamage );
1984
1985 savefile->ReadBool( gibDeath );
1986 savefile->ReadBool( gibsLaunched );
1987 savefile->ReadVec3( gibsDir );
1988
1989 savefile->ReadFloat( set );
1990 zoomFov.SetStartTime( set );
1991 savefile->ReadFloat( set );
1992 zoomFov.SetDuration( set );
1993 savefile->ReadFloat( set );
1994 zoomFov.SetStartValue( set );
1995 savefile->ReadFloat( set );
1996 zoomFov.SetEndValue( set );
1997
1998 savefile->ReadFloat( set );
1999 centerView.SetStartTime( set );
2000 savefile->ReadFloat( set );
2001 centerView.SetDuration( set );
2002 savefile->ReadFloat( set );
2003 centerView.SetStartValue( set );
2004 savefile->ReadFloat( set );
2005 centerView.SetEndValue( set );
2006
2007 savefile->ReadBool( fxFov );
2008
2009 savefile->ReadFloat( influenceFov );
2010 savefile->ReadInt( influenceActive );
2011 savefile->ReadFloat( influenceRadius );
2012 savefile->ReadObject( reinterpret_cast<idClass *&>( influenceEntity ) );
2013 savefile->ReadMaterial( influenceMaterial );
2014 savefile->ReadSkin( influenceSkin );
2015
2016 savefile->ReadObject( reinterpret_cast<idClass *&>( privateCameraView ) );
2017
2018 for( i = 0; i < NUM_LOGGED_VIEW_ANGLES; i++ ) {
2019 savefile->ReadAngles( loggedViewAngles[ i ] );
2020 }
2021 for( i = 0; i < NUM_LOGGED_ACCELS; i++ ) {
2022 savefile->ReadInt( loggedAccel[ i ].time );
2023 savefile->ReadVec3( loggedAccel[ i ].dir );
2024 }
2025 savefile->ReadInt( currentLoggedAccel );
2026
2027 savefile->ReadObject( reinterpret_cast<idClass *&>( focusGUIent ) );
2028 // can't save focusUI
2029 focusUI = NULL;
2030 savefile->ReadObject( reinterpret_cast<idClass *&>( focusCharacter ) );
2031 savefile->ReadInt( talkCursor );
2032 savefile->ReadInt( focusTime );
2033 savefile->ReadObject( reinterpret_cast<idClass *&>( focusVehicle ) );
2034 savefile->ReadUserInterface( cursor );
2035
2036 // DG: make it scale to 4:3 so crosshair looks properly round
2037 // yes, like so many scaling-related things this is a bit hacky
2038 // and note that this is special cased in StateChanged and you
2039 // can *not* generally set windowDef properties like this.
2040 cursor->SetStateBool("scaleto43", true);
2041 cursor->StateChanged(gameLocal.time); // DG end
2042
2043 savefile->ReadInt( oldMouseX );
2044 savefile->ReadInt( oldMouseY );
2045
2046 savefile->ReadString( pdaAudio );
2047 savefile->ReadString( pdaVideo );
2048 savefile->ReadString( pdaVideoWave );
2049
2050 savefile->ReadBool( tipUp );
2051 savefile->ReadBool( objectiveUp );
2052
2053 savefile->ReadInt( lastDamageDef );
2054 savefile->ReadVec3( lastDamageDir );
2055 savefile->ReadInt( lastDamageLocation );
2056 savefile->ReadInt( smoothedFrame );
2057 savefile->ReadBool( smoothedOriginUpdated );
2058 savefile->ReadVec3( smoothedOrigin );
2059 savefile->ReadAngles( smoothedAngles );
2060
2061 savefile->ReadBool( ready );
2062 savefile->ReadBool( respawning );
2063 savefile->ReadBool( leader );
2064 savefile->ReadInt( lastSpectateChange );
2065 savefile->ReadInt( lastTeleFX );
2066
2067 // set the pm_ cvars
2068 const idKeyValue *kv;
2069 kv = spawnArgs.MatchPrefix( "pm_", NULL );
2070 while( kv ) {
2071 cvarSystem->SetCVarString( kv->GetKey(), kv->GetValue() );
2072 kv = spawnArgs.MatchPrefix( "pm_", kv );
2073 }
2074
2075 savefile->ReadFloat( set );
2076 pm_stamina.SetFloat( set );
2077
2078 // create combat collision hull for exact collision detection
2079 SetCombatModel();
2080
2081 // DG: workaround for lingering messages that are shown forever after loading a savegame
2082 // (one way to get them is saving again, while the message from first save is still
2083 // shown, and then load)
2084 if ( hud ) {
2085 hud->SetStateString( "message", "" );
2086 }
2087 }
2088
2089 /*
2090 ===============
2091 idPlayer::PrepareForRestart
2092 ================
2093 */
PrepareForRestart(void)2094 void idPlayer::PrepareForRestart( void ) {
2095 ClearPowerUps();
2096 Spectate( true );
2097 forceRespawn = true;
2098
2099 // we will be restarting program, clear the client entities from program-related things first
2100 ShutdownThreads();
2101
2102 // the sound world is going to be cleared, don't keep references to emitters
2103 FreeSoundEmitter( false );
2104 }
2105
2106 /*
2107 ===============
2108 idPlayer::Restart
2109 ================
2110 */
Restart(void)2111 void idPlayer::Restart( void ) {
2112 idActor::Restart();
2113
2114 // client needs to setup the animation script object again
2115 if ( gameLocal.isClient ) {
2116 Init();
2117 } else {
2118 // choose a random spot and prepare the point of view in case player is left spectating
2119 assert( spectating );
2120 SpawnFromSpawnSpot();
2121 }
2122
2123 useInitialSpawns = true;
2124 UpdateSkinSetup( true );
2125 }
2126
2127 /*
2128 ===============
2129 idPlayer::ServerSpectate
2130 ================
2131 */
ServerSpectate(bool spectate)2132 void idPlayer::ServerSpectate( bool spectate ) {
2133 assert( !gameLocal.isClient );
2134
2135 if ( spectating != spectate ) {
2136 Spectate( spectate );
2137 if ( spectate ) {
2138 SetSpectateOrigin();
2139 } else {
2140 if ( gameLocal.gameType == GAME_DM ) {
2141 // make sure the scores are reset so you can't exploit by spectating and entering the game back
2142 // other game types don't matter, as you either can't join back, or it's team scores
2143 gameLocal.mpGame.ClearFrags( entityNumber );
2144 }
2145 }
2146 }
2147 if ( !spectate ) {
2148 SpawnFromSpawnSpot();
2149 }
2150 }
2151
2152 /*
2153 ===========
2154 idPlayer::SelectInitialSpawnPoint
2155
2156 Try to find a spawn point marked 'initial', otherwise
2157 use normal spawn selection.
2158 ============
2159 */
SelectInitialSpawnPoint(idVec3 & origin,idAngles & angles)2160 void idPlayer::SelectInitialSpawnPoint( idVec3 &origin, idAngles &angles ) {
2161 idEntity *spot;
2162 idStr skin;
2163
2164 spot = gameLocal.SelectInitialSpawnPoint( this );
2165
2166 // set the player skin from the spawn location
2167 if ( spot->spawnArgs.GetString( "skin", NULL, skin ) ) {
2168 spawnArgs.Set( "spawn_skin", skin );
2169 }
2170
2171 // activate the spawn locations targets
2172 spot->PostEventMS( &EV_ActivateTargets, 0, this );
2173
2174 origin = spot->GetPhysics()->GetOrigin();
2175 origin[2] += 4.0f + CM_BOX_EPSILON; // move up to make sure the player is at least an epsilon above the floor
2176 angles = spot->GetPhysics()->GetAxis().ToAngles();
2177 }
2178
2179 /*
2180 ===========
2181 idPlayer::SpawnFromSpawnSpot
2182
2183 Chooses a spawn location and spawns the player
2184 ============
2185 */
SpawnFromSpawnSpot(void)2186 void idPlayer::SpawnFromSpawnSpot( void ) {
2187 idVec3 spawn_origin;
2188 idAngles spawn_angles;
2189
2190 SelectInitialSpawnPoint( spawn_origin, spawn_angles );
2191 SpawnToPoint( spawn_origin, spawn_angles );
2192 }
2193
2194 /*
2195 ===========
2196 idPlayer::SpawnToPoint
2197
2198 Called every time a client is placed fresh in the world:
2199 after the first ClientBegin, and after each respawn
2200 Initializes all non-persistant parts of playerState
2201
2202 when called here with spectating set to true, just place yourself and init
2203 ============
2204 */
SpawnToPoint(const idVec3 & spawn_origin,const idAngles & spawn_angles)2205 void idPlayer::SpawnToPoint( const idVec3 &spawn_origin, const idAngles &spawn_angles ) {
2206 idVec3 spec_origin;
2207
2208 assert( !gameLocal.isClient );
2209
2210 respawning = true;
2211
2212 Init();
2213
2214 fl.noknockback = false;
2215
2216 // stop any ragdolls being used
2217 StopRagdoll();
2218
2219 // set back the player physics
2220 SetPhysics( &physicsObj );
2221
2222 physicsObj.SetClipModelAxis();
2223 physicsObj.EnableClip();
2224
2225 if ( !spectating ) {
2226 SetCombatContents( true );
2227 }
2228
2229 physicsObj.SetLinearVelocity( vec3_origin );
2230
2231 // setup our initial view
2232 if ( !spectating ) {
2233 SetOrigin( spawn_origin );
2234 } else {
2235 spec_origin = spawn_origin;
2236 spec_origin[ 2 ] += pm_normalheight.GetFloat();
2237 spec_origin[ 2 ] += SPECTATE_RAISE;
2238 SetOrigin( spec_origin );
2239 }
2240
2241 // if this is the first spawn of the map, we don't have a usercmd yet,
2242 // so the delta angles won't be correct. This will be fixed on the first think.
2243 viewAngles = ang_zero;
2244 SetDeltaViewAngles( ang_zero );
2245 SetViewAngles( spawn_angles );
2246 spawnAngles = spawn_angles;
2247 spawnAnglesSet = false;
2248
2249 legsForward = true;
2250 legsYaw = 0.0f;
2251 idealLegsYaw = 0.0f;
2252 oldViewYaw = viewAngles.yaw;
2253
2254 if ( spectating ) {
2255 Hide();
2256 } else {
2257 Show();
2258 }
2259
2260 if ( gameLocal.isMultiplayer ) {
2261 if ( !spectating ) {
2262 // we may be called twice in a row in some situations. avoid a double fx and 'fly to the roof'
2263 if ( lastTeleFX < gameLocal.time - 1000 ) {
2264 idEntityFx::StartFx( spawnArgs.GetString( "fx_spawn" ), &spawn_origin, NULL, this, true );
2265 lastTeleFX = gameLocal.time;
2266 }
2267 }
2268 AI_TELEPORT = true;
2269 } else {
2270 AI_TELEPORT = false;
2271 }
2272
2273 // kill anything at the new position
2274 if ( !spectating ) {
2275 physicsObj.SetClipMask( MASK_PLAYERSOLID ); // the clip mask is usually maintained in Move(), but KillBox requires it
2276 gameLocal.KillBox( this );
2277 }
2278
2279 // don't allow full run speed for a bit
2280 physicsObj.SetKnockBack( 100 );
2281
2282 // set our respawn time and buttons so that if we're killed we don't respawn immediately
2283 minRespawnTime = gameLocal.time;
2284 maxRespawnTime = gameLocal.time;
2285 if ( !spectating ) {
2286 forceRespawn = false;
2287 }
2288
2289 privateCameraView = NULL;
2290
2291 BecomeActive( TH_THINK );
2292
2293 // run a client frame to drop exactly to the floor,
2294 // initialize animations and other things
2295 Think();
2296
2297 respawning = false;
2298 lastManOver = false;
2299 lastManPlayAgain = false;
2300 isTelefragged = false;
2301 }
2302
2303 /*
2304 ===============
2305 idPlayer::SavePersistantInfo
2306
2307 Saves any inventory and player stats when changing levels.
2308 ===============
2309 */
SavePersistantInfo(void)2310 void idPlayer::SavePersistantInfo( void ) {
2311 idDict &playerInfo = gameLocal.persistentPlayerInfo[entityNumber];
2312
2313 playerInfo.Clear();
2314 inventory.GetPersistantData( playerInfo );
2315 playerInfo.SetInt( "health", health );
2316 playerInfo.SetInt( "current_weapon", currentWeapon );
2317 }
2318
2319 /*
2320 ===============
2321 idPlayer::RestorePersistantInfo
2322
2323 Restores any inventory and player stats when changing levels.
2324 ===============
2325 */
RestorePersistantInfo(void)2326 void idPlayer::RestorePersistantInfo( void ) {
2327 if ( gameLocal.isMultiplayer ) {
2328 gameLocal.persistentPlayerInfo[entityNumber].Clear();
2329 }
2330
2331 spawnArgs.Copy( gameLocal.persistentPlayerInfo[entityNumber] );
2332
2333 inventory.RestoreInventory( this, spawnArgs );
2334 health = spawnArgs.GetInt( "health", "100" );
2335 if ( !gameLocal.isClient ) {
2336 idealWeapon = spawnArgs.GetInt( "current_weapon", "1" );
2337 }
2338 }
2339
2340 /*
2341 ================
2342 idPlayer::GetUserInfo
2343 ================
2344 */
GetUserInfo(void)2345 idDict *idPlayer::GetUserInfo( void ) {
2346 return &gameLocal.userInfo[ entityNumber ];
2347 }
2348
2349 /*
2350 ==============
2351 idPlayer::UpdateSkinSetup
2352 ==============
2353 */
UpdateSkinSetup(bool restart)2354 void idPlayer::UpdateSkinSetup( bool restart ) {
2355 if ( restart ) {
2356 team = ( idStr::Icmp( GetUserInfo()->GetString( "ui_team" ), "Blue" ) == 0 );
2357 }
2358 if ( gameLocal.gameType == GAME_TDM ) {
2359 if ( team ) {
2360 baseSkinName = "skins/characters/player/marine_mp_blue";
2361 } else {
2362 baseSkinName = "skins/characters/player/marine_mp_red";
2363 }
2364 if ( !gameLocal.isClient && team != latchedTeam ) {
2365 gameLocal.mpGame.SwitchToTeam( entityNumber, latchedTeam, team );
2366 }
2367 latchedTeam = team;
2368 } else {
2369 baseSkinName = GetUserInfo()->GetString( "ui_skin" );
2370 }
2371 if ( !baseSkinName.Length() ) {
2372 baseSkinName = "skins/characters/player/marine_mp";
2373 }
2374 skin = declManager->FindSkin( baseSkinName, false );
2375 assert( skin );
2376 // match the skin to a color band for scoreboard
2377 if ( baseSkinName.Find( "red" ) != -1 ) {
2378 colorBarIndex = 1;
2379 } else if ( baseSkinName.Find( "green" ) != -1 ) {
2380 colorBarIndex = 2;
2381 } else if ( baseSkinName.Find( "blue" ) != -1 ) {
2382 colorBarIndex = 3;
2383 } else if ( baseSkinName.Find( "yellow" ) != -1 ) {
2384 colorBarIndex = 4;
2385 } else {
2386 colorBarIndex = 0;
2387 }
2388 colorBar = colorBarTable[ colorBarIndex ];
2389 if ( PowerUpActive( BERSERK ) ) {
2390 powerUpSkin = declManager->FindSkin( baseSkinName + "_berserk" );
2391 }
2392 }
2393
2394 /*
2395 ==============
2396 idPlayer::BalanceTDM
2397 ==============
2398 */
BalanceTDM(void)2399 bool idPlayer::BalanceTDM( void ) {
2400 int i, balanceTeam, teamCount[2];
2401 idEntity *ent;
2402
2403 teamCount[ 0 ] = teamCount[ 1 ] = 0;
2404 for( i = 0; i < gameLocal.numClients; i++ ) {
2405 ent = gameLocal.entities[ i ];
2406 if ( ent && ent->IsType( idPlayer::Type ) ) {
2407 teamCount[ static_cast< idPlayer * >( ent )->team ]++;
2408 }
2409 }
2410 balanceTeam = -1;
2411 if ( teamCount[ 0 ] < teamCount[ 1 ] ) {
2412 balanceTeam = 0;
2413 } else if ( teamCount[ 0 ] > teamCount[ 1 ] ) {
2414 balanceTeam = 1;
2415 }
2416 if ( balanceTeam != -1 && team != balanceTeam ) {
2417 common->DPrintf( "team balance: forcing player %d to %s team\n", entityNumber, balanceTeam ? "blue" : "red" );
2418 team = balanceTeam;
2419 GetUserInfo()->Set( "ui_team", team ? "Blue" : "Red" );
2420 return true;
2421 }
2422 return false;
2423 }
2424
2425 /*
2426 ==============
2427 idPlayer::UserInfoChanged
2428 ==============
2429 */
UserInfoChanged(bool canModify)2430 bool idPlayer::UserInfoChanged( bool canModify ) {
2431 idDict *userInfo;
2432 bool modifiedInfo;
2433 bool spec;
2434 bool newready;
2435
2436 userInfo = GetUserInfo();
2437 showWeaponViewModel = userInfo->GetBool( "ui_showGun" );
2438
2439 if ( !gameLocal.isMultiplayer ) {
2440 return false;
2441 }
2442
2443 modifiedInfo = false;
2444
2445 spec = ( idStr::Icmp( userInfo->GetString( "ui_spectate" ), "Spectate" ) == 0 );
2446 if ( gameLocal.serverInfo.GetBool( "si_spectators" ) ) {
2447 // never let spectators go back to game while sudden death is on
2448 if ( canModify && gameLocal.mpGame.GetGameState() == idMultiplayerGame::SUDDENDEATH && !spec && wantSpectate == true ) {
2449 userInfo->Set( "ui_spectate", "Spectate" );
2450 modifiedInfo |= true;
2451 } else {
2452 if ( spec != wantSpectate && !spec ) {
2453 // returning from spectate, set forceRespawn so we don't get stuck in spectate forever
2454 forceRespawn = true;
2455 }
2456 wantSpectate = spec;
2457 }
2458 } else {
2459 if ( canModify && spec ) {
2460 userInfo->Set( "ui_spectate", "Play" );
2461 modifiedInfo |= true;
2462 } else if ( spectating ) {
2463 // allow player to leaving spectator mode if they were in it when si_spectators got turned off
2464 forceRespawn = true;
2465 }
2466 wantSpectate = false;
2467 }
2468
2469 newready = ( idStr::Icmp( userInfo->GetString( "ui_ready" ), "Ready" ) == 0 );
2470 if ( ready != newready && gameLocal.mpGame.GetGameState() == idMultiplayerGame::WARMUP && !wantSpectate ) {
2471 gameLocal.mpGame.AddChatLine( common->GetLanguageDict()->GetString( "#str_07180" ), userInfo->GetString( "ui_name" ), newready ? common->GetLanguageDict()->GetString( "#str_04300" ) : common->GetLanguageDict()->GetString( "#str_04301" ) );
2472 }
2473 ready = newready;
2474 team = ( idStr::Icmp( userInfo->GetString( "ui_team" ), "Blue" ) == 0 );
2475 // server maintains TDM balance
2476 if ( canModify && gameLocal.gameType == GAME_TDM && !gameLocal.mpGame.IsInGame( entityNumber ) && g_balanceTDM.GetBool() ) {
2477 modifiedInfo |= BalanceTDM( );
2478 }
2479 UpdateSkinSetup( false );
2480
2481 isChatting = userInfo->GetBool( "ui_chat", "0" );
2482 if ( canModify && isChatting && AI_DEAD ) {
2483 // if dead, always force chat icon off.
2484 isChatting = false;
2485 userInfo->SetBool( "ui_chat", false );
2486 modifiedInfo |= true;
2487 }
2488
2489 return modifiedInfo;
2490 }
2491
2492 /*
2493 ===============
2494 idPlayer::UpdateHudAmmo
2495 ===============
2496 */
UpdateHudAmmo(idUserInterface * _hud)2497 void idPlayer::UpdateHudAmmo( idUserInterface *_hud ) {
2498 int inclip;
2499 int ammoamount;
2500
2501 assert( weapon.GetEntity() );
2502 assert( _hud );
2503
2504 inclip = weapon.GetEntity()->AmmoInClip();
2505 ammoamount = weapon.GetEntity()->AmmoAvailable();
2506 if ( ammoamount < 0 || !weapon.GetEntity()->IsReady() ) {
2507 // show infinite ammo
2508 _hud->SetStateString( "player_ammo", "" );
2509 _hud->SetStateString( "player_totalammo", "" );
2510 } else {
2511 // show remaining ammo
2512 _hud->SetStateString( "player_totalammo", va( "%i", ammoamount - inclip ) );
2513 _hud->SetStateString( "player_ammo", weapon.GetEntity()->ClipSize() ? va( "%i", inclip ) : "--" ); // how much in the current clip
2514 _hud->SetStateString( "player_clips", weapon.GetEntity()->ClipSize() ? va( "%i", ammoamount / weapon.GetEntity()->ClipSize() ) : "--" );
2515 _hud->SetStateString( "player_allammo", va( "%i/%i", inclip, ammoamount - inclip ) );
2516 }
2517
2518 _hud->SetStateBool( "player_ammo_empty", ( ammoamount == 0 ) );
2519 _hud->SetStateBool( "player_clip_empty", ( weapon.GetEntity()->ClipSize() ? inclip == 0 : false ) );
2520 _hud->SetStateBool( "player_clip_low", ( weapon.GetEntity()->ClipSize() ? inclip <= weapon.GetEntity()->LowAmmo() : false ) );
2521
2522 _hud->HandleNamedEvent( "updateAmmo" );
2523 }
2524
2525 /*
2526 ===============
2527 idPlayer::UpdateHudStats
2528 ===============
2529 */
UpdateHudStats(idUserInterface * _hud)2530 void idPlayer::UpdateHudStats( idUserInterface *_hud ) {
2531 int staminapercentage;
2532 float max_stamina;
2533
2534 assert( _hud );
2535
2536 max_stamina = pm_stamina.GetFloat();
2537 if ( !max_stamina ) {
2538 // stamina disabled, so show full stamina bar
2539 staminapercentage = 100.0f;
2540 } else {
2541 staminapercentage = idMath::FtoiFast( 100.0f * stamina / max_stamina );
2542 }
2543
2544 _hud->SetStateInt( "player_health", health );
2545 _hud->SetStateInt( "player_stamina", staminapercentage );
2546 _hud->SetStateInt( "player_armor", inventory.armor );
2547 _hud->SetStateInt( "player_hr", heartRate );
2548 _hud->SetStateInt( "player_nostamina", ( max_stamina == 0 ) ? 1 : 0 );
2549
2550 _hud->HandleNamedEvent( "updateArmorHealthAir" );
2551
2552 if ( healthPulse ) {
2553 _hud->HandleNamedEvent( "healthPulse" );
2554 StartSound( "snd_healthpulse", SND_CHANNEL_ITEM, 0, false, NULL );
2555 healthPulse = false;
2556 }
2557
2558 if ( healthTake ) {
2559 _hud->HandleNamedEvent( "healthPulse" );
2560 StartSound( "snd_healthtake", SND_CHANNEL_ITEM, 0, false, NULL );
2561 healthTake = false;
2562 }
2563
2564 if ( inventory.ammoPulse ) {
2565 _hud->HandleNamedEvent( "ammoPulse" );
2566 inventory.ammoPulse = false;
2567 }
2568 if ( inventory.weaponPulse ) {
2569 // We need to update the weapon hud manually, but not
2570 // the armor/ammo/health because they are updated every
2571 // frame no matter what
2572 UpdateHudWeapon();
2573 _hud->HandleNamedEvent( "weaponPulse" );
2574 inventory.weaponPulse = false;
2575 }
2576 if ( inventory.armorPulse ) {
2577 _hud->HandleNamedEvent( "armorPulse" );
2578 inventory.armorPulse = false;
2579 }
2580
2581 UpdateHudAmmo( _hud );
2582 }
2583
2584 /*
2585 ===============
2586 idPlayer::UpdateHudWeapon
2587 ===============
2588 */
UpdateHudWeapon(bool flashWeapon)2589 void idPlayer::UpdateHudWeapon( bool flashWeapon ) {
2590 idUserInterface *hud = idPlayer::hud;
2591
2592 // if updating the hud of a followed client
2593 if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) {
2594 idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] );
2595 if ( p->spectating && p->spectator == entityNumber ) {
2596 assert( p->hud );
2597 hud = p->hud;
2598 }
2599 }
2600
2601 if ( !hud ) {
2602 return;
2603 }
2604
2605 for ( int i = 0; i < MAX_WEAPONS; i++ ) {
2606 const char *weapnum = va( "def_weapon%d", i );
2607 const char *hudWeap = va( "weapon%d", i );
2608 int weapstate = 0;
2609 if ( inventory.weapons & ( 1 << i ) ) {
2610 const char *weap = spawnArgs.GetString( weapnum );
2611 if ( weap && *weap ) {
2612 weapstate++;
2613 }
2614 if ( idealWeapon == i ) {
2615 weapstate++;
2616 }
2617 }
2618 hud->SetStateInt( hudWeap, weapstate );
2619 }
2620 if ( flashWeapon ) {
2621 hud->HandleNamedEvent( "weaponChange" );
2622 }
2623 }
2624
2625 /*
2626 ===============
2627 idPlayer::DrawHUD
2628 ===============
2629 */
DrawHUD(idUserInterface * _hud)2630 void idPlayer::DrawHUD( idUserInterface *_hud ) {
2631
2632 if ( !weapon.GetEntity() || influenceActive != INFLUENCE_NONE || privateCameraView || gameLocal.GetCamera() || !_hud || !g_showHud.GetBool() ) {
2633 return;
2634 }
2635
2636 UpdateHudStats( _hud );
2637
2638 _hud->SetStateString( "weapicon", weapon.GetEntity()->Icon() );
2639
2640 // FIXME: this is temp to allow the sound meter to show up in the hud
2641 // it should be commented out before shipping but the code can remain
2642 // for mod developers to enable for the same functionality
2643 _hud->SetStateInt( "s_debug", cvarSystem->GetCVarInteger( "s_showLevelMeter" ) );
2644
2645 weapon.GetEntity()->UpdateGUI();
2646
2647 _hud->Redraw( gameLocal.realClientTime );
2648
2649 // weapon targeting crosshair
2650 if ( !GuiActive() ) {
2651 if ( cursor && weapon.GetEntity()->ShowCrosshair() ) {
2652 cursor->Redraw( gameLocal.realClientTime );
2653 }
2654 }
2655 }
2656
2657 /*
2658 ===============
2659 idPlayer::EnterCinematic
2660 ===============
2661 */
EnterCinematic(void)2662 void idPlayer::EnterCinematic( void ) {
2663 Hide();
2664 StopAudioLog();
2665 StopSound( SND_CHANNEL_PDA, false );
2666 if ( hud ) {
2667 hud->HandleNamedEvent( "radioChatterDown" );
2668 }
2669
2670 physicsObj.SetLinearVelocity( vec3_origin );
2671
2672 SetState( "EnterCinematic" );
2673 UpdateScript();
2674
2675 if ( weaponEnabled && weapon.GetEntity() ) {
2676 weapon.GetEntity()->EnterCinematic();
2677 }
2678
2679 AI_FORWARD = false;
2680 AI_BACKWARD = false;
2681 AI_STRAFE_LEFT = false;
2682 AI_STRAFE_RIGHT = false;
2683 AI_RUN = false;
2684 AI_ATTACK_HELD = false;
2685 AI_WEAPON_FIRED = false;
2686 AI_JUMP = false;
2687 AI_CROUCH = false;
2688 AI_ONGROUND = true;
2689 AI_ONLADDER = false;
2690 AI_DEAD = ( health <= 0 );
2691 AI_RUN = false;
2692 AI_PAIN = false;
2693 AI_HARDLANDING = false;
2694 AI_SOFTLANDING = false;
2695 AI_RELOAD = false;
2696 AI_TELEPORT = false;
2697 AI_TURN_LEFT = false;
2698 AI_TURN_RIGHT = false;
2699 }
2700
2701 /*
2702 ===============
2703 idPlayer::ExitCinematic
2704 ===============
2705 */
ExitCinematic(void)2706 void idPlayer::ExitCinematic( void ) {
2707 Show();
2708
2709 if ( weaponEnabled && weapon.GetEntity() ) {
2710 weapon.GetEntity()->ExitCinematic();
2711 }
2712
2713 SetState( "ExitCinematic" );
2714 UpdateScript();
2715 }
2716
2717 /*
2718 =====================
2719 idPlayer::UpdateConditions
2720 =====================
2721 */
UpdateConditions(void)2722 void idPlayer::UpdateConditions( void ) {
2723 idVec3 velocity;
2724 float forwardspeed;
2725 float sidespeed;
2726
2727 // minus the push velocity to avoid playing the walking animation and sounds when riding a mover
2728 velocity = physicsObj.GetLinearVelocity() - physicsObj.GetPushedLinearVelocity();
2729
2730 if ( influenceActive ) {
2731 AI_FORWARD = false;
2732 AI_BACKWARD = false;
2733 AI_STRAFE_LEFT = false;
2734 AI_STRAFE_RIGHT = false;
2735 } else if ( gameLocal.time - lastDmgTime < 500 ) {
2736 forwardspeed = velocity * viewAxis[ 0 ];
2737 sidespeed = velocity * viewAxis[ 1 ];
2738 AI_FORWARD = AI_ONGROUND && ( forwardspeed > 20.01f );
2739 AI_BACKWARD = AI_ONGROUND && ( forwardspeed < -20.01f );
2740 AI_STRAFE_LEFT = AI_ONGROUND && ( sidespeed > 20.01f );
2741 AI_STRAFE_RIGHT = AI_ONGROUND && ( sidespeed < -20.01f );
2742 } else if ( xyspeed > MIN_BOB_SPEED ) {
2743 AI_FORWARD = AI_ONGROUND && ( usercmd.forwardmove > 0 );
2744 AI_BACKWARD = AI_ONGROUND && ( usercmd.forwardmove < 0 );
2745 AI_STRAFE_LEFT = AI_ONGROUND && ( usercmd.rightmove < 0 );
2746 AI_STRAFE_RIGHT = AI_ONGROUND && ( usercmd.rightmove > 0 );
2747 } else {
2748 AI_FORWARD = false;
2749 AI_BACKWARD = false;
2750 AI_STRAFE_LEFT = false;
2751 AI_STRAFE_RIGHT = false;
2752 }
2753
2754 AI_RUN = ( usercmd.buttons & BUTTON_RUN ) && ( ( !pm_stamina.GetFloat() ) || ( stamina > pm_staminathreshold.GetFloat() ) );
2755 AI_DEAD = ( health <= 0 );
2756 }
2757
2758 /*
2759 ==================
2760 WeaponFireFeedback
2761
2762 Called when a weapon fires, generates head twitches, etc
2763 ==================
2764 */
WeaponFireFeedback(const idDict * weaponDef)2765 void idPlayer::WeaponFireFeedback( const idDict *weaponDef ) {
2766 // force a blink
2767 blink_time = 0;
2768
2769 // play the fire animation
2770 AI_WEAPON_FIRED = true;
2771
2772 // update view feedback
2773 playerView.WeaponFireFeedback( weaponDef );
2774 }
2775
2776 /*
2777 ===============
2778 idPlayer::StopFiring
2779 ===============
2780 */
StopFiring(void)2781 void idPlayer::StopFiring( void ) {
2782 AI_ATTACK_HELD = false;
2783 AI_WEAPON_FIRED = false;
2784 AI_RELOAD = false;
2785 if ( weapon.GetEntity() ) {
2786 weapon.GetEntity()->EndAttack();
2787 }
2788 }
2789
2790 /*
2791 ===============
2792 idPlayer::FireWeapon
2793 ===============
2794 */
FireWeapon(void)2795 void idPlayer::FireWeapon( void ) {
2796 idMat3 axis;
2797 idVec3 muzzle;
2798
2799 if ( privateCameraView ) {
2800 return;
2801 }
2802
2803 if ( g_editEntityMode.GetInteger() ) {
2804 GetViewPos( muzzle, axis );
2805 if ( gameLocal.editEntities->SelectEntity( muzzle, axis[0], this ) ) {
2806 return;
2807 }
2808 }
2809
2810 if ( !hiddenWeapon && weapon.GetEntity()->IsReady() ) {
2811 if ( weapon.GetEntity()->AmmoInClip() || weapon.GetEntity()->AmmoAvailable() ) {
2812 AI_ATTACK_HELD = true;
2813 weapon.GetEntity()->BeginAttack();
2814 if ( ( weapon_soulcube >= 0 ) && ( currentWeapon == weapon_soulcube ) ) {
2815 if ( hud ) {
2816 hud->HandleNamedEvent( "soulCubeNotReady" );
2817 }
2818 SelectWeapon( previousWeapon, false );
2819 }
2820 } else {
2821 NextBestWeapon();
2822 }
2823 }
2824
2825 if ( hud ) {
2826 if ( tipUp ) {
2827 HideTip();
2828 }
2829 // may want to track with with a bool as well
2830 // keep from looking up named events so often
2831 if ( objectiveUp ) {
2832 HideObjective();
2833 }
2834 }
2835 }
2836
2837 /*
2838 ===============
2839 idPlayer::CacheWeapons
2840 ===============
2841 */
CacheWeapons(void)2842 void idPlayer::CacheWeapons( void ) {
2843 idStr weap;
2844 int w;
2845
2846 // check if we have any weapons
2847 if ( !inventory.weapons ) {
2848 return;
2849 }
2850
2851 for( w = 0; w < MAX_WEAPONS; w++ ) {
2852 if ( inventory.weapons & ( 1 << w ) ) {
2853 weap = spawnArgs.GetString( va( "def_weapon%d", w ) );
2854 if ( weap != "" ) {
2855 idWeapon::CacheWeapon( weap );
2856 } else {
2857 inventory.weapons &= ~( 1 << w );
2858 }
2859 }
2860 }
2861 }
2862
2863 /*
2864 ===============
2865 idPlayer::Give
2866 ===============
2867 */
Give(const char * statname,const char * value)2868 bool idPlayer::Give( const char *statname, const char *value ) {
2869 int amount;
2870
2871 if ( AI_DEAD ) {
2872 return false;
2873 }
2874
2875 if ( !idStr::Icmp( statname, "health" ) ) {
2876 if ( health >= inventory.maxHealth ) {
2877 return false;
2878 }
2879 amount = atoi( value );
2880 if ( amount ) {
2881 health += amount;
2882 if ( health > inventory.maxHealth ) {
2883 health = inventory.maxHealth;
2884 }
2885 if ( hud ) {
2886 hud->HandleNamedEvent( "healthPulse" );
2887 }
2888 }
2889
2890 } else if ( !idStr::Icmp( statname, "stamina" ) ) {
2891 if ( stamina >= 100 ) {
2892 return false;
2893 }
2894 stamina += atof( value );
2895 if ( stamina > 100 ) {
2896 stamina = 100;
2897 }
2898
2899 } else if ( !idStr::Icmp( statname, "heartRate" ) ) {
2900 heartRate += atoi( value );
2901 if ( heartRate > MAX_HEARTRATE ) {
2902 heartRate = MAX_HEARTRATE;
2903 }
2904
2905 } else if ( !idStr::Icmp( statname, "air" ) ) {
2906 if ( airTics >= pm_airTics.GetInteger() ) {
2907 return false;
2908 }
2909 airTics += atoi( value ) / 100.0 * pm_airTics.GetInteger();
2910 if ( airTics > pm_airTics.GetInteger() ) {
2911 airTics = pm_airTics.GetInteger();
2912 }
2913 } else {
2914 return inventory.Give( this, spawnArgs, statname, value, &idealWeapon, true );
2915 }
2916 return true;
2917 }
2918
2919
2920 /*
2921 ===============
2922 idPlayer::GiveHealthPool
2923
2924 adds health to the player health pool
2925 ===============
2926 */
GiveHealthPool(float amt)2927 void idPlayer::GiveHealthPool( float amt ) {
2928
2929 if ( AI_DEAD ) {
2930 return;
2931 }
2932
2933 if ( health > 0 ) {
2934 healthPool += amt;
2935 if ( healthPool > inventory.maxHealth - health ) {
2936 healthPool = inventory.maxHealth - health;
2937 }
2938 nextHealthPulse = gameLocal.time;
2939 }
2940 }
2941
2942 /*
2943 ===============
2944 idPlayer::GiveItem
2945
2946 Returns false if the item shouldn't be picked up
2947 ===============
2948 */
GiveItem(idItem * item)2949 bool idPlayer::GiveItem( idItem *item ) {
2950 int i;
2951 const idKeyValue *arg;
2952 idDict attr;
2953 bool gave;
2954 int numPickup;
2955
2956 if ( gameLocal.isMultiplayer && spectating ) {
2957 return false;
2958 }
2959
2960 item->GetAttributes( attr );
2961
2962 gave = false;
2963 numPickup = inventory.pickupItemNames.Num();
2964 for( i = 0; i < attr.GetNumKeyVals(); i++ ) {
2965 arg = attr.GetKeyVal( i );
2966 if ( Give( arg->GetKey(), arg->GetValue() ) ) {
2967 gave = true;
2968 }
2969 }
2970
2971 arg = item->spawnArgs.MatchPrefix( "inv_weapon", NULL );
2972 if ( arg && hud ) {
2973 // We need to update the weapon hud manually, but not
2974 // the armor/ammo/health because they are updated every
2975 // frame no matter what
2976 UpdateHudWeapon( false );
2977 hud->HandleNamedEvent( "weaponPulse" );
2978 }
2979
2980 // display the pickup feedback on the hud
2981 if ( gave && ( numPickup == inventory.pickupItemNames.Num() ) ) {
2982 inventory.AddPickupName( item->spawnArgs.GetString( "inv_name" ), item->spawnArgs.GetString( "inv_icon" ) );
2983 }
2984
2985 return gave;
2986 }
2987
2988 /*
2989 ===============
2990 idPlayer::PowerUpModifier
2991 ===============
2992 */
PowerUpModifier(int type)2993 float idPlayer::PowerUpModifier( int type ) {
2994 float mod = 1.0f;
2995
2996 if ( PowerUpActive( BERSERK ) ) {
2997 switch( type ) {
2998 case SPEED: {
2999 mod *= 1.7f;
3000 break;
3001 }
3002 case PROJECTILE_DAMAGE: {
3003 mod *= 2.0f;
3004 break;
3005 }
3006 case MELEE_DAMAGE: {
3007 mod *= 30.0f;
3008 break;
3009 }
3010 case MELEE_DISTANCE: {
3011 mod *= 2.0f;
3012 break;
3013 }
3014 }
3015 }
3016
3017 if ( gameLocal.isMultiplayer && !gameLocal.isClient ) {
3018 if ( PowerUpActive( MEGAHEALTH ) ) {
3019 if ( healthPool <= 0 ) {
3020 GiveHealthPool( 100 );
3021 }
3022 } else {
3023 healthPool = 0;
3024 }
3025 }
3026
3027 return mod;
3028 }
3029
3030 /*
3031 ===============
3032 idPlayer::PowerUpActive
3033 ===============
3034 */
PowerUpActive(int powerup) const3035 bool idPlayer::PowerUpActive( int powerup ) const {
3036 return ( inventory.powerups & ( 1 << powerup ) ) != 0;
3037 }
3038
3039 /*
3040 ===============
3041 idPlayer::GivePowerUp
3042 ===============
3043 */
GivePowerUp(int powerup,int time)3044 bool idPlayer::GivePowerUp( int powerup, int time ) {
3045 const char *sound;
3046 const char *skin;
3047
3048 if ( powerup >= 0 && powerup < MAX_POWERUPS ) {
3049
3050 if ( gameLocal.isServer ) {
3051 idBitMsg msg;
3052 byte msgBuf[MAX_EVENT_PARAM_SIZE];
3053
3054 msg.Init( msgBuf, sizeof( msgBuf ) );
3055 msg.WriteShort( powerup );
3056 msg.WriteBits( 1, 1 );
3057 ServerSendEvent( EVENT_POWERUP, &msg, false, -1 );
3058 }
3059
3060 if ( powerup != MEGAHEALTH ) {
3061 inventory.GivePowerUp( this, powerup, time );
3062 }
3063
3064 const idDeclEntityDef *def = NULL;
3065
3066 switch( powerup ) {
3067 case BERSERK: {
3068 if ( spawnArgs.GetString( "snd_berserk_third", "", &sound ) ) {
3069 StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_DEMONIC, 0, false, NULL );
3070 }
3071 if ( baseSkinName.Length() ) {
3072 powerUpSkin = declManager->FindSkin( baseSkinName + "_berserk" );
3073 }
3074 if ( !gameLocal.isClient ) {
3075 idealWeapon = 0;
3076 }
3077 break;
3078 }
3079 case INVISIBILITY: {
3080 spawnArgs.GetString( "skin_invisibility", "", &skin );
3081 powerUpSkin = declManager->FindSkin( skin );
3082 // remove any decals from the model
3083 if ( modelDefHandle != -1 ) {
3084 gameRenderWorld->RemoveDecals( modelDefHandle );
3085 }
3086 if ( weapon.GetEntity() ) {
3087 weapon.GetEntity()->UpdateSkin();
3088 }
3089 if ( spawnArgs.GetString( "snd_invisibility", "", &sound ) ) {
3090 StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_ANY, 0, false, NULL );
3091 }
3092 break;
3093 }
3094 case ADRENALINE: {
3095 stamina = 100.0f;
3096 break;
3097 }
3098 case MEGAHEALTH: {
3099 if ( spawnArgs.GetString( "snd_megahealth", "", &sound ) ) {
3100 StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_ANY, 0, false, NULL );
3101 }
3102 def = gameLocal.FindEntityDef( "powerup_megahealth", false );
3103 if ( def ) {
3104 health = def->dict.GetInt( "inv_health" );
3105 }
3106 break;
3107 }
3108 }
3109
3110 if ( hud ) {
3111 hud->HandleNamedEvent( "itemPickup" );
3112 }
3113
3114 return true;
3115 } else {
3116 gameLocal.Warning( "Player given power up %i\n which is out of range", powerup );
3117 }
3118 return false;
3119 }
3120
3121 /*
3122 ==============
3123 idPlayer::ClearPowerup
3124 ==============
3125 */
ClearPowerup(int i)3126 void idPlayer::ClearPowerup( int i ) {
3127
3128 if ( gameLocal.isServer ) {
3129 idBitMsg msg;
3130 byte msgBuf[MAX_EVENT_PARAM_SIZE];
3131
3132 msg.Init( msgBuf, sizeof( msgBuf ) );
3133 msg.WriteShort( i );
3134 msg.WriteBits( 0, 1 );
3135 ServerSendEvent( EVENT_POWERUP, &msg, false, -1 );
3136 }
3137
3138 powerUpSkin = NULL;
3139 inventory.powerups &= ~( 1 << i );
3140 inventory.powerupEndTime[ i ] = 0;
3141 switch( i ) {
3142 case BERSERK: {
3143 StopSound( SND_CHANNEL_DEMONIC, false );
3144 break;
3145 }
3146 case INVISIBILITY: {
3147 if ( weapon.GetEntity() ) {
3148 weapon.GetEntity()->UpdateSkin();
3149 }
3150 break;
3151 }
3152 }
3153 }
3154
3155 /*
3156 ==============
3157 idPlayer::UpdatePowerUps
3158 ==============
3159 */
UpdatePowerUps(void)3160 void idPlayer::UpdatePowerUps( void ) {
3161 int i;
3162
3163 if ( !gameLocal.isClient ) {
3164 for ( i = 0; i < MAX_POWERUPS; i++ ) {
3165 if ( PowerUpActive( i ) && inventory.powerupEndTime[i] <= gameLocal.time ) {
3166 ClearPowerup( i );
3167 }
3168 }
3169 }
3170
3171 if ( health > 0 ) {
3172 if ( powerUpSkin ) {
3173 renderEntity.customSkin = powerUpSkin;
3174 } else {
3175 renderEntity.customSkin = skin;
3176 }
3177 }
3178
3179 if ( healthPool && gameLocal.time > nextHealthPulse && !AI_DEAD && health > 0 ) {
3180 assert( !gameLocal.isClient ); // healthPool never be set on client
3181 int amt = ( healthPool > 5 ) ? 5 : healthPool;
3182 health += amt;
3183 if ( health > inventory.maxHealth ) {
3184 health = inventory.maxHealth;
3185 healthPool = 0;
3186 } else {
3187 healthPool -= amt;
3188 }
3189 nextHealthPulse = gameLocal.time + HEALTHPULSE_TIME;
3190 healthPulse = true;
3191 }
3192
3193 if ( !gameLocal.inCinematic && influenceActive == 0 && g_skill.GetInteger() == 3 && gameLocal.time > nextHealthTake && !AI_DEAD && health > g_healthTakeLimit.GetInteger() ) {
3194 assert( !gameLocal.isClient ); // healthPool never be set on client
3195 health -= g_healthTakeAmt.GetInteger();
3196 if ( health < g_healthTakeLimit.GetInteger() ) {
3197 health = g_healthTakeLimit.GetInteger();
3198 }
3199 nextHealthTake = gameLocal.time + g_healthTakeTime.GetInteger() * 1000;
3200 healthTake = true;
3201 }
3202 }
3203
3204 /*
3205 ===============
3206 idPlayer::ClearPowerUps
3207 ===============
3208 */
ClearPowerUps(void)3209 void idPlayer::ClearPowerUps( void ) {
3210 int i;
3211 for ( i = 0; i < MAX_POWERUPS; i++ ) {
3212 if ( PowerUpActive( i ) ) {
3213 ClearPowerup( i );
3214 }
3215 }
3216 inventory.ClearPowerUps();
3217 }
3218
3219 /*
3220 ===============
3221 idPlayer::GiveInventoryItem
3222 ===============
3223 */
GiveInventoryItem(idDict * item)3224 bool idPlayer::GiveInventoryItem( idDict *item ) {
3225 if ( gameLocal.isMultiplayer && spectating ) {
3226 return false;
3227 }
3228 inventory.items.Append( new idDict( *item ) );
3229 idItemInfo info;
3230 const char* itemName = item->GetString( "inv_name" );
3231 if ( idStr::Cmpn( itemName, STRTABLE_ID, STRTABLE_ID_LENGTH ) == 0 ) {
3232 info.name = common->GetLanguageDict()->GetString( itemName );
3233 } else {
3234 info.name = itemName;
3235 }
3236 info.icon = item->GetString( "inv_icon" );
3237 inventory.pickupItemNames.Append( info );
3238 if ( hud ) {
3239 hud->SetStateString( "itemicon", info.icon );
3240 hud->HandleNamedEvent( "invPickup" );
3241 }
3242 return true;
3243 }
3244
3245 /*
3246 ==============
3247 idPlayer::UpdateObjectiveInfo
3248 ==============
3249 */
UpdateObjectiveInfo(void)3250 void idPlayer::UpdateObjectiveInfo( void ) {
3251 if ( objectiveSystem == NULL ) {
3252 return;
3253 }
3254 objectiveSystem->SetStateString( "objective1", "" );
3255 objectiveSystem->SetStateString( "objective2", "" );
3256 objectiveSystem->SetStateString( "objective3", "" );
3257 for ( int i = 0; i < inventory.objectiveNames.Num(); i++ ) {
3258 objectiveSystem->SetStateString( va( "objective%i", i+1 ), "1" );
3259 objectiveSystem->SetStateString( va( "objectivetitle%i", i+1 ), inventory.objectiveNames[i].title.c_str() );
3260 objectiveSystem->SetStateString( va( "objectivetext%i", i+1 ), inventory.objectiveNames[i].text.c_str() );
3261 objectiveSystem->SetStateString( va( "objectiveshot%i", i+1 ), inventory.objectiveNames[i].screenshot.c_str() );
3262 }
3263 objectiveSystem->StateChanged( gameLocal.time );
3264 }
3265
3266 /*
3267 ===============
3268 idPlayer::GiveObjective
3269 ===============
3270 */
GiveObjective(const char * title,const char * text,const char * screenshot)3271 void idPlayer::GiveObjective( const char *title, const char *text, const char *screenshot ) {
3272 idObjectiveInfo info;
3273 info.title = title;
3274 info.text = text;
3275 info.screenshot = screenshot;
3276 inventory.objectiveNames.Append( info );
3277 ShowObjective( "newObjective" );
3278 if ( hud ) {
3279 hud->HandleNamedEvent( "newObjective" );
3280 }
3281 }
3282
3283 /*
3284 ===============
3285 idPlayer::CompleteObjective
3286 ===============
3287 */
CompleteObjective(const char * title)3288 void idPlayer::CompleteObjective( const char *title ) {
3289 int c = inventory.objectiveNames.Num();
3290 for ( int i = 0; i < c; i++ ) {
3291 if ( idStr::Icmp(inventory.objectiveNames[i].title, title) == 0 ) {
3292 inventory.objectiveNames.RemoveIndex( i );
3293 break;
3294 }
3295 }
3296 ShowObjective( "newObjectiveComplete" );
3297
3298 if ( hud ) {
3299 hud->HandleNamedEvent( "newObjectiveComplete" );
3300 }
3301 }
3302
3303 /*
3304 ===============
3305 idPlayer::GiveVideo
3306 ===============
3307 */
GiveVideo(const char * videoName,idDict * item)3308 void idPlayer::GiveVideo( const char *videoName, idDict *item ) {
3309
3310 if ( videoName == NULL || *videoName == 0 ) {
3311 return;
3312 }
3313
3314 inventory.videos.AddUnique( videoName );
3315
3316 if ( item ) {
3317 idItemInfo info;
3318 info.name = item->GetString( "inv_name" );
3319 info.icon = item->GetString( "inv_icon" );
3320 inventory.pickupItemNames.Append( info );
3321 }
3322 if ( hud ) {
3323 hud->HandleNamedEvent( "videoPickup" );
3324 }
3325 }
3326
3327 /*
3328 ===============
3329 idPlayer::GiveSecurity
3330 ===============
3331 */
GiveSecurity(const char * security)3332 void idPlayer::GiveSecurity( const char *security ) {
3333 GetPDA()->SetSecurity( security );
3334 if ( hud ) {
3335 hud->SetStateString( "pda_security", "1" );
3336 hud->HandleNamedEvent( "securityPickup" );
3337 }
3338 }
3339
3340 /*
3341 ===============
3342 idPlayer::GiveEmail
3343 ===============
3344 */
GiveEmail(const char * emailName)3345 void idPlayer::GiveEmail( const char *emailName ) {
3346
3347 if ( emailName == NULL || *emailName == 0 ) {
3348 return;
3349 }
3350
3351 inventory.emails.AddUnique( emailName );
3352 GetPDA()->AddEmail( emailName );
3353
3354 if ( hud ) {
3355 hud->HandleNamedEvent( "emailPickup" );
3356 }
3357 }
3358
3359 /*
3360 ===============
3361 idPlayer::GivePDA
3362 ===============
3363 */
GivePDA(const char * pdaName,idDict * item)3364 void idPlayer::GivePDA( const char *pdaName, idDict *item )
3365 {
3366 if ( gameLocal.isMultiplayer && spectating ) {
3367 return;
3368 }
3369
3370 if ( item ) {
3371 inventory.pdaSecurity.AddUnique( item->GetString( "inv_name" ) );
3372 }
3373
3374 if ( pdaName == NULL || *pdaName == 0 ) {
3375 pdaName = "personal";
3376 }
3377
3378 const idDeclPDA *pda = static_cast< const idDeclPDA* >( declManager->FindType( DECL_PDA, pdaName ) );
3379
3380 inventory.pdas.AddUnique( pdaName );
3381
3382 // Copy any videos over
3383 for ( int i = 0; i < pda->GetNumVideos(); i++ ) {
3384 const idDeclVideo *video = pda->GetVideoByIndex( i );
3385 if ( video ) {
3386 inventory.videos.AddUnique( video->GetName() );
3387 }
3388 }
3389
3390 // This is kind of a hack, but it works nicely
3391 // We don't want to display the 'you got a new pda' message during a map load
3392 if ( gameLocal.GetFrameNum() > 10 ) {
3393 if ( pda && hud ) {
3394 idStr pdaName = pda->GetPdaName();
3395 pdaName.RemoveColors();
3396 hud->SetStateString( "pda", "1" );
3397 hud->SetStateString( "pda_text", pdaName );
3398 const char *sec = pda->GetSecurity();
3399 hud->SetStateString( "pda_security", ( sec && *sec ) ? "1" : "0" );
3400 hud->HandleNamedEvent( "pdaPickup" );
3401 }
3402
3403 if ( inventory.pdas.Num() == 1 ) {
3404 GetPDA()->RemoveAddedEmailsAndVideos();
3405 if ( !objectiveSystemOpen ) {
3406 TogglePDA();
3407 }
3408 objectiveSystem->HandleNamedEvent( "showPDATip" );
3409 //ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_firstPDA" ), true );
3410 }
3411
3412 if ( inventory.pdas.Num() > 1 && pda->GetNumVideos() > 0 && hud ) {
3413 hud->HandleNamedEvent( "videoPickup" );
3414 }
3415 }
3416 }
3417
3418 /*
3419 ===============
3420 idPlayer::FindInventoryItem
3421 ===============
3422 */
FindInventoryItem(const char * name)3423 idDict *idPlayer::FindInventoryItem( const char *name ) {
3424 for ( int i = 0; i < inventory.items.Num(); i++ ) {
3425 const char *iname = inventory.items[i]->GetString( "inv_name" );
3426 if ( iname && *iname ) {
3427 if ( idStr::Icmp( name, iname ) == 0 ) {
3428 return inventory.items[i];
3429 }
3430 }
3431 }
3432 return NULL;
3433 }
3434
3435 /*
3436 ===============
3437 idPlayer::RemoveInventoryItem
3438 ===============
3439 */
RemoveInventoryItem(const char * name)3440 void idPlayer::RemoveInventoryItem( const char *name ) {
3441 idDict *item = FindInventoryItem(name);
3442 if ( item ) {
3443 RemoveInventoryItem( item );
3444 }
3445 }
3446
3447 /*
3448 ===============
3449 idPlayer::RemoveInventoryItem
3450 ===============
3451 */
RemoveInventoryItem(idDict * item)3452 void idPlayer::RemoveInventoryItem( idDict *item ) {
3453 inventory.items.Remove( item );
3454 delete item;
3455 }
3456
3457 /*
3458 ===============
3459 idPlayer::GiveItem
3460 ===============
3461 */
GiveItem(const char * itemname)3462 void idPlayer::GiveItem( const char *itemname ) {
3463 idDict args;
3464
3465 args.Set( "classname", itemname );
3466 args.Set( "owner", name.c_str() );
3467 gameLocal.SpawnEntityDef( args );
3468 if ( hud ) {
3469 hud->HandleNamedEvent( "itemPickup" );
3470 }
3471 }
3472
3473 /*
3474 ==================
3475 idPlayer::SlotForWeapon
3476 ==================
3477 */
SlotForWeapon(const char * weaponName)3478 int idPlayer::SlotForWeapon( const char *weaponName ) {
3479 int i;
3480
3481 for( i = 0; i < MAX_WEAPONS; i++ ) {
3482 const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) );
3483 if ( !idStr::Cmp( weap, weaponName ) ) {
3484 return i;
3485 }
3486 }
3487
3488 // not found
3489 return -1;
3490 }
3491
3492 /*
3493 ===============
3494 idPlayer::Reload
3495 ===============
3496 */
Reload(void)3497 void idPlayer::Reload( void ) {
3498 if ( gameLocal.isClient ) {
3499 return;
3500 }
3501
3502 if ( spectating || gameLocal.inCinematic || influenceActive ) {
3503 return;
3504 }
3505
3506 if ( weapon.GetEntity() && weapon.GetEntity()->IsLinked() ) {
3507 weapon.GetEntity()->Reload();
3508 }
3509 }
3510
3511 /*
3512 ===============
3513 idPlayer::NextBestWeapon
3514 ===============
3515 */
NextBestWeapon(void)3516 void idPlayer::NextBestWeapon( void ) {
3517 const char *weap;
3518 int w = MAX_WEAPONS;
3519
3520 if ( gameLocal.isClient || !weaponEnabled ) {
3521 return;
3522 }
3523
3524 while ( w > 0 ) {
3525 w--;
3526 weap = spawnArgs.GetString( va( "def_weapon%d", w ) );
3527 if ( !weap[ 0 ] || ( ( inventory.weapons & ( 1 << w ) ) == 0 ) || ( !inventory.HasAmmo( weap ) ) ) {
3528 continue;
3529 }
3530 if ( !spawnArgs.GetBool( va( "weapon%d_best", w ) ) ) {
3531 continue;
3532 }
3533 break;
3534 }
3535 idealWeapon = w;
3536 weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY;
3537 UpdateHudWeapon();
3538 }
3539
3540 /*
3541 ===============
3542 idPlayer::NextWeapon
3543 ===============
3544 */
NextWeapon(void)3545 void idPlayer::NextWeapon( void ) {
3546 const char *weap;
3547 int w;
3548
3549 if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) {
3550 return;
3551 }
3552
3553 if ( gameLocal.isClient ) {
3554 return;
3555 }
3556
3557 // check if we have any weapons
3558 if ( !inventory.weapons ) {
3559 return;
3560 }
3561
3562 w = idealWeapon;
3563 while( 1 ) {
3564 w++;
3565 if ( w >= MAX_WEAPONS ) {
3566 w = 0;
3567 }
3568 weap = spawnArgs.GetString( va( "def_weapon%d", w ) );
3569 if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) {
3570 continue;
3571 }
3572 if ( !weap[ 0 ] ) {
3573 continue;
3574 }
3575 if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) {
3576 continue;
3577 }
3578 if ( inventory.HasAmmo( weap ) ) {
3579 break;
3580 }
3581 }
3582
3583 if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) {
3584 idealWeapon = w;
3585 weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY;
3586 UpdateHudWeapon();
3587 }
3588 }
3589
3590 /*
3591 ===============
3592 idPlayer::PrevWeapon
3593 ===============
3594 */
PrevWeapon(void)3595 void idPlayer::PrevWeapon( void ) {
3596 const char *weap;
3597 int w;
3598
3599 if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) {
3600 return;
3601 }
3602
3603 if ( gameLocal.isClient ) {
3604 return;
3605 }
3606
3607 // check if we have any weapons
3608 if ( !inventory.weapons ) {
3609 return;
3610 }
3611
3612 w = idealWeapon;
3613 while( 1 ) {
3614 w--;
3615 if ( w < 0 ) {
3616 w = MAX_WEAPONS - 1;
3617 }
3618 weap = spawnArgs.GetString( va( "def_weapon%d", w ) );
3619 if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) {
3620 continue;
3621 }
3622 if ( !weap[ 0 ] ) {
3623 continue;
3624 }
3625 if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) {
3626 continue;
3627 }
3628 if ( inventory.HasAmmo( weap ) ) {
3629 break;
3630 }
3631 }
3632
3633 if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) {
3634 idealWeapon = w;
3635 weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY;
3636 UpdateHudWeapon();
3637 }
3638 }
3639
3640 /*
3641 ===============
3642 idPlayer::SelectWeapon
3643 ===============
3644 */
SelectWeapon(int num,bool force)3645 void idPlayer::SelectWeapon( int num, bool force ) {
3646 const char *weap;
3647
3648 if ( !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 ) {
3649 return;
3650 }
3651
3652 if ( ( num < 0 ) || ( num >= MAX_WEAPONS ) ) {
3653 return;
3654 }
3655
3656 if ( gameLocal.isClient ) {
3657 return;
3658 }
3659
3660 if ( ( num != weapon_pda ) && gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) {
3661 num = weapon_fists;
3662 hiddenWeapon ^= 1;
3663 if ( hiddenWeapon && weapon.GetEntity() ) {
3664 weapon.GetEntity()->LowerWeapon();
3665 } else {
3666 weapon.GetEntity()->RaiseWeapon();
3667 }
3668 }
3669
3670 weap = spawnArgs.GetString( va( "def_weapon%d", num ) );
3671 if ( !weap[ 0 ] ) {
3672 gameLocal.Printf( "Invalid weapon\n" );
3673 return;
3674 }
3675
3676 if ( force || ( inventory.weapons & ( 1 << num ) ) ) {
3677 if ( !inventory.HasAmmo( weap ) && !spawnArgs.GetBool( va( "weapon%d_allowempty", num ) ) ) {
3678 return;
3679 }
3680 if ( ( previousWeapon >= 0 ) && ( idealWeapon == num ) && ( spawnArgs.GetBool( va( "weapon%d_toggle", num ) ) ) ) {
3681 weap = spawnArgs.GetString( va( "def_weapon%d", previousWeapon ) );
3682 if ( !inventory.HasAmmo( weap ) && !spawnArgs.GetBool( va( "weapon%d_allowempty", previousWeapon ) ) ) {
3683 return;
3684 }
3685 idealWeapon = previousWeapon;
3686 } else if ( ( weapon_pda >= 0 ) && ( num == weapon_pda ) && ( inventory.pdas.Num() == 0 ) ) {
3687 ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_noPDA" ), true );
3688 return;
3689 } else {
3690 idealWeapon = num;
3691 }
3692 UpdateHudWeapon();
3693 }
3694 }
3695
3696 /*
3697 =================
3698 idPlayer::DropWeapon
3699 =================
3700 */
DropWeapon(bool died)3701 void idPlayer::DropWeapon( bool died ) {
3702 idVec3 forward, up;
3703 int inclip, ammoavailable;
3704
3705 assert( !gameLocal.isClient );
3706
3707 if ( spectating || weaponGone || weapon.GetEntity() == NULL ) {
3708 return;
3709 }
3710
3711 if ( ( !died && !weapon.GetEntity()->IsReady() ) || weapon.GetEntity()->IsReloading() ) {
3712 return;
3713 }
3714 // ammoavailable is how many shots we can fire
3715 // inclip is which amount is in clip right now
3716 ammoavailable = weapon.GetEntity()->AmmoAvailable();
3717 inclip = weapon.GetEntity()->AmmoInClip();
3718
3719 // don't drop a grenade if we have none left
3720 if ( !idStr::Icmp( idWeapon::GetAmmoNameForNum( weapon.GetEntity()->GetAmmoType() ), "ammo_grenades" ) && ( ammoavailable - inclip <= 0 ) ) {
3721 return;
3722 }
3723
3724 // expect an ammo setup that makes sense before doing any dropping
3725 // ammoavailable is -1 for infinite ammo, and weapons like chainsaw
3726 // a bad ammo config usually indicates a bad weapon state, so we should not drop
3727 // used to be an assertion check, but it still happens in edge cases
3728 if ( ( ammoavailable != -1 ) && ( ammoavailable - inclip < 0 ) ) {
3729 common->DPrintf( "idPlayer::DropWeapon: bad ammo setup\n" );
3730 return;
3731 }
3732 idEntity *item = NULL;
3733 if ( died ) {
3734 // ain't gonna throw you no weapon if I'm dead
3735 item = weapon.GetEntity()->DropItem( vec3_origin, 0, WEAPON_DROP_TIME, died );
3736 } else {
3737 viewAngles.ToVectors( &forward, NULL, &up );
3738 item = weapon.GetEntity()->DropItem( 250.0f * forward + 150.0f * up, 500, WEAPON_DROP_TIME, died );
3739 }
3740 if ( !item ) {
3741 return;
3742 }
3743 // set the appropriate ammo in the dropped object
3744 const idKeyValue * keyval = item->spawnArgs.MatchPrefix( "inv_ammo_" );
3745 if ( keyval ) {
3746 item->spawnArgs.SetInt( keyval->GetKey(), ammoavailable );
3747 idStr inclipKey = keyval->GetKey();
3748 inclipKey.Insert( "inclip_", 4 );
3749 item->spawnArgs.SetInt( inclipKey, inclip );
3750 }
3751 if ( !died ) {
3752 // remove from our local inventory completely
3753 inventory.Drop( spawnArgs, item->spawnArgs.GetString( "inv_weapon" ), -1 );
3754 weapon.GetEntity()->ResetAmmoClip();
3755 NextWeapon();
3756 weapon.GetEntity()->WeaponStolen();
3757 weaponGone = true;
3758 }
3759 }
3760
3761 /*
3762 =================
3763 idPlayer::StealWeapon
3764 steal the target player's current weapon
3765 =================
3766 */
StealWeapon(idPlayer * player)3767 void idPlayer::StealWeapon( idPlayer *player ) {
3768 assert( !gameLocal.isClient );
3769
3770 // make sure there's something to steal
3771 idWeapon *player_weapon = static_cast< idWeapon * >( player->weapon.GetEntity() );
3772 if ( !player_weapon || !player_weapon->CanDrop() || weaponGone ) {
3773 return;
3774 }
3775 // steal - we need to effectively force the other player to abandon his weapon
3776 int newweap = player->currentWeapon;
3777 if ( newweap == -1 ) {
3778 return;
3779 }
3780 // might be just dropped - check inventory
3781 if ( ! ( player->inventory.weapons & ( 1 << newweap ) ) ) {
3782 return;
3783 }
3784 const char *weapon_classname = spawnArgs.GetString( va( "def_weapon%d", newweap ) );
3785 assert( weapon_classname );
3786 int ammoavailable = player->weapon.GetEntity()->AmmoAvailable();
3787 int inclip = player->weapon.GetEntity()->AmmoInClip();
3788 if ( ( ammoavailable != -1 ) && ( ammoavailable - inclip < 0 ) ) {
3789 // see DropWeapon
3790 common->DPrintf( "idPlayer::StealWeapon: bad ammo setup\n" );
3791 // we still steal the weapon, so let's use the default ammo levels
3792 inclip = -1;
3793 const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname );
3794 assert( decl );
3795 const idKeyValue *keypair = decl->dict.MatchPrefix( "inv_ammo_" );
3796 assert( keypair );
3797 ammoavailable = atoi( keypair->GetValue() );
3798 }
3799
3800 player->weapon.GetEntity()->WeaponStolen();
3801 player->inventory.Drop( player->spawnArgs, NULL, newweap );
3802 player->SelectWeapon( weapon_fists, false );
3803 // in case the robbed player is firing rounds with a continuous fire weapon like the chaingun/plasma etc.
3804 // this will ensure the firing actually stops
3805 player->weaponGone = true;
3806
3807 // give weapon, setup the ammo count
3808 Give( "weapon", weapon_classname );
3809 ammo_t ammo_i = player->inventory.AmmoIndexForWeaponClass( weapon_classname, NULL );
3810 idealWeapon = newweap;
3811 inventory.ammo[ ammo_i ] += ammoavailable;
3812 inventory.clip[ newweap ] = inclip;
3813 }
3814
3815 /*
3816 ===============
3817 idPlayer::ActiveGui
3818 ===============
3819 */
ActiveGui(void)3820 idUserInterface *idPlayer::ActiveGui( void ) {
3821 if ( objectiveSystemOpen ) {
3822 return objectiveSystem;
3823 }
3824
3825 return focusUI;
3826 }
3827
3828 /*
3829 ===============
3830 idPlayer::Weapon_Combat
3831 ===============
3832 */
Weapon_Combat(void)3833 void idPlayer::Weapon_Combat( void ) {
3834 if ( influenceActive || !weaponEnabled || gameLocal.inCinematic || privateCameraView ) {
3835 return;
3836 }
3837
3838 weapon.GetEntity()->RaiseWeapon();
3839 if ( weapon.GetEntity()->IsReloading() ) {
3840 if ( !AI_RELOAD ) {
3841 AI_RELOAD = true;
3842 SetState( "ReloadWeapon" );
3843 UpdateScript();
3844 }
3845 } else {
3846 AI_RELOAD = false;
3847 }
3848
3849 if ( idealWeapon == weapon_soulcube && soulCubeProjectile.GetEntity() != NULL ) {
3850 idealWeapon = currentWeapon;
3851 }
3852
3853 if ( idealWeapon != currentWeapon ) {
3854 if ( weaponCatchup ) {
3855 assert( gameLocal.isClient );
3856
3857 currentWeapon = idealWeapon;
3858 weaponGone = false;
3859 animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) );
3860 weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ currentWeapon ] );
3861 animPrefix.Strip( "weapon_" );
3862
3863 weapon.GetEntity()->NetCatchup();
3864 const function_t *newstate = GetScriptFunction( "NetCatchup" );
3865 if ( newstate ) {
3866 SetState( newstate );
3867 UpdateScript();
3868 }
3869 weaponCatchup = false;
3870 } else {
3871 if ( weapon.GetEntity()->IsReady() ) {
3872 weapon.GetEntity()->PutAway();
3873 }
3874
3875 if ( weapon.GetEntity()->IsHolstered() ) {
3876 assert( idealWeapon >= 0 );
3877 assert( idealWeapon < MAX_WEAPONS );
3878
3879 if ( currentWeapon != weapon_pda && !spawnArgs.GetBool( va( "weapon%d_toggle", currentWeapon ) ) ) {
3880 previousWeapon = currentWeapon;
3881 }
3882 currentWeapon = idealWeapon;
3883 weaponGone = false;
3884 animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) );
3885 weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ currentWeapon ] );
3886 animPrefix.Strip( "weapon_" );
3887
3888 weapon.GetEntity()->Raise();
3889 }
3890 }
3891 } else {
3892 weaponGone = false; // if you drop and re-get weap, you may miss the = false above
3893 if ( weapon.GetEntity()->IsHolstered() ) {
3894 if ( !weapon.GetEntity()->AmmoAvailable() ) {
3895 // weapons can switch automatically if they have no more ammo
3896 NextBestWeapon();
3897 } else {
3898 weapon.GetEntity()->Raise();
3899 state = GetScriptFunction( "RaiseWeapon" );
3900 if ( state ) {
3901 SetState( state );
3902 }
3903 }
3904 }
3905 }
3906
3907 // check for attack
3908 AI_WEAPON_FIRED = false;
3909 if ( !influenceActive ) {
3910 if ( ( usercmd.buttons & BUTTON_ATTACK ) && !weaponGone ) {
3911 FireWeapon();
3912 } else if ( oldButtons & BUTTON_ATTACK ) {
3913 AI_ATTACK_HELD = false;
3914 weapon.GetEntity()->EndAttack();
3915 }
3916 }
3917
3918 // update our ammo clip in our inventory
3919 if ( ( currentWeapon >= 0 ) && ( currentWeapon < MAX_WEAPONS ) ) {
3920 inventory.clip[ currentWeapon ] = weapon.GetEntity()->AmmoInClip();
3921 if ( hud && ( currentWeapon == idealWeapon ) ) {
3922 UpdateHudAmmo( hud );
3923 }
3924 }
3925 }
3926
3927 /*
3928 ===============
3929 idPlayer::Weapon_NPC
3930 ===============
3931 */
Weapon_NPC(void)3932 void idPlayer::Weapon_NPC( void ) {
3933 if ( idealWeapon != currentWeapon ) {
3934 Weapon_Combat();
3935 }
3936 StopFiring();
3937 weapon.GetEntity()->LowerWeapon();
3938
3939 if ( ( usercmd.buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) {
3940 buttonMask |= BUTTON_ATTACK;
3941 focusCharacter->TalkTo( this );
3942 }
3943 }
3944
3945 /*
3946 ===============
3947 idPlayer::LowerWeapon
3948 ===============
3949 */
LowerWeapon(void)3950 void idPlayer::LowerWeapon( void ) {
3951 if ( weapon.GetEntity() && !weapon.GetEntity()->IsHidden() ) {
3952 weapon.GetEntity()->LowerWeapon();
3953 }
3954 }
3955
3956 /*
3957 ===============
3958 idPlayer::RaiseWeapon
3959 ===============
3960 */
RaiseWeapon(void)3961 void idPlayer::RaiseWeapon( void ) {
3962 if ( weapon.GetEntity() && weapon.GetEntity()->IsHidden() ) {
3963 weapon.GetEntity()->RaiseWeapon();
3964 }
3965 }
3966
3967 /*
3968 ===============
3969 idPlayer::WeaponLoweringCallback
3970 ===============
3971 */
WeaponLoweringCallback(void)3972 void idPlayer::WeaponLoweringCallback( void ) {
3973 SetState( "LowerWeapon" );
3974 UpdateScript();
3975 }
3976
3977 /*
3978 ===============
3979 idPlayer::WeaponRisingCallback
3980 ===============
3981 */
WeaponRisingCallback(void)3982 void idPlayer::WeaponRisingCallback( void ) {
3983 SetState( "RaiseWeapon" );
3984 UpdateScript();
3985 }
3986
3987 /*
3988 ===============
3989 idPlayer::Weapon_GUI
3990 ===============
3991 */
Weapon_GUI(void)3992 void idPlayer::Weapon_GUI( void ) {
3993
3994 if ( !objectiveSystemOpen ) {
3995 if ( idealWeapon != currentWeapon ) {
3996 Weapon_Combat();
3997 }
3998 StopFiring();
3999 weapon.GetEntity()->LowerWeapon();
4000 }
4001
4002 // disable click prediction for the GUIs. handy to check the state sync does the right thing
4003 if ( gameLocal.isClient && !net_clientPredictGUI.GetBool() ) {
4004 return;
4005 }
4006
4007 if ( ( oldButtons ^ usercmd.buttons ) & BUTTON_ATTACK ) {
4008 sysEvent_t ev;
4009 const char *command = NULL;
4010 bool updateVisuals = false;
4011
4012 idUserInterface *ui = ActiveGui();
4013 if ( ui ) {
4014 ev = sys->GenerateMouseButtonEvent( 1, ( usercmd.buttons & BUTTON_ATTACK ) != 0 );
4015 command = ui->HandleEvent( &ev, gameLocal.time, &updateVisuals );
4016 if ( updateVisuals && focusGUIent && ui == focusUI ) {
4017 focusGUIent->UpdateVisuals();
4018 }
4019 }
4020 if ( gameLocal.isClient ) {
4021 // we predict enough, but don't want to execute commands
4022 return;
4023 }
4024 if ( focusGUIent ) {
4025 HandleGuiCommands( focusGUIent, command );
4026 } else {
4027 HandleGuiCommands( this, command );
4028 }
4029 }
4030 }
4031
4032 /*
4033 ===============
4034 idPlayer::UpdateWeapon
4035 ===============
4036 */
UpdateWeapon(void)4037 void idPlayer::UpdateWeapon( void ) {
4038 if ( health <= 0 ) {
4039 return;
4040 }
4041
4042 assert( !spectating );
4043
4044 if ( gameLocal.isClient ) {
4045 // clients need to wait till the weapon and it's world model entity
4046 // are present and synchronized ( weapon.worldModel idEntityPtr to idAnimatedEntity )
4047 if ( !weapon.GetEntity()->IsWorldModelReady() ) {
4048 return;
4049 }
4050 }
4051
4052 // always make sure the weapon is correctly setup before accessing it
4053 if ( !weapon.GetEntity()->IsLinked() ) {
4054 if ( idealWeapon != -1 ) {
4055 animPrefix = spawnArgs.GetString( va( "def_weapon%d", idealWeapon ) );
4056 weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ idealWeapon ] );
4057 assert( weapon.GetEntity()->IsLinked() );
4058 } else {
4059 return;
4060 }
4061 }
4062
4063 if ( hiddenWeapon && tipUp && usercmd.buttons & BUTTON_ATTACK ) {
4064 HideTip();
4065 }
4066
4067 if ( g_dragEntity.GetBool() ) {
4068 StopFiring();
4069 weapon.GetEntity()->LowerWeapon();
4070 dragEntity.Update( this );
4071 } else if ( ActiveGui() ) {
4072 // gui handling overrides weapon use
4073 Weapon_GUI();
4074 } else if ( focusCharacter && ( focusCharacter->health > 0 ) ) {
4075 Weapon_NPC();
4076 } else {
4077 Weapon_Combat();
4078 }
4079
4080 if ( hiddenWeapon ) {
4081 weapon.GetEntity()->LowerWeapon();
4082 }
4083
4084 // update weapon state, particles, dlights, etc
4085 weapon.GetEntity()->PresentWeapon( showWeaponViewModel );
4086 }
4087
4088 /*
4089 ===============
4090 idPlayer::SpectateFreeFly
4091 ===============
4092 */
SpectateFreeFly(bool force)4093 void idPlayer::SpectateFreeFly( bool force ) {
4094 idPlayer *player;
4095 idVec3 newOrig;
4096 idVec3 spawn_origin;
4097 idAngles spawn_angles;
4098
4099 player = gameLocal.GetClientByNum( spectator );
4100 if ( force || gameLocal.time > lastSpectateChange ) {
4101 spectator = entityNumber;
4102 if ( player && player != this && !player->spectating && !player->IsInTeleport() ) {
4103 newOrig = player->GetPhysics()->GetOrigin();
4104 if ( player->physicsObj.IsCrouching() ) {
4105 newOrig[ 2 ] += pm_crouchviewheight.GetFloat();
4106 } else {
4107 newOrig[ 2 ] += pm_normalviewheight.GetFloat();
4108 }
4109 newOrig[ 2 ] += SPECTATE_RAISE;
4110 idBounds b = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f );
4111 idVec3 start = player->GetPhysics()->GetOrigin();
4112 start[2] += pm_spectatebbox.GetFloat() * 0.5f;
4113 trace_t t;
4114 // assuming spectate bbox is inside stand or crouch box
4115 gameLocal.clip.TraceBounds( t, start, newOrig, b, MASK_PLAYERSOLID, player );
4116 newOrig.Lerp( start, newOrig, t.fraction );
4117 SetOrigin( newOrig );
4118 idAngles angle = player->viewAngles;
4119 angle[ 2 ] = 0;
4120 SetViewAngles( angle );
4121 } else {
4122 SelectInitialSpawnPoint( spawn_origin, spawn_angles );
4123 spawn_origin[ 2 ] += pm_normalviewheight.GetFloat();
4124 spawn_origin[ 2 ] += SPECTATE_RAISE;
4125 SetOrigin( spawn_origin );
4126 SetViewAngles( spawn_angles );
4127 }
4128 lastSpectateChange = gameLocal.time + 500;
4129 }
4130 }
4131
4132 /*
4133 ===============
4134 idPlayer::SpectateCycle
4135 ===============
4136 */
SpectateCycle(void)4137 void idPlayer::SpectateCycle( void ) {
4138 idPlayer *player;
4139
4140 if ( gameLocal.time > lastSpectateChange ) {
4141 int latchedSpectator = spectator;
4142 spectator = gameLocal.GetNextClientNum( spectator );
4143 player = gameLocal.GetClientByNum( spectator );
4144 assert( player ); // never call here when the current spectator is wrong
4145 // ignore other spectators
4146 while ( latchedSpectator != spectator && player->spectating ) {
4147 spectator = gameLocal.GetNextClientNum( spectator );
4148 player = gameLocal.GetClientByNum( spectator );
4149 }
4150 lastSpectateChange = gameLocal.time + 500;
4151 }
4152 }
4153
4154 /*
4155 ===============
4156 idPlayer::UpdateSpectating
4157 ===============
4158 */
UpdateSpectating(void)4159 void idPlayer::UpdateSpectating( void ) {
4160 assert( spectating );
4161 assert( !gameLocal.isClient );
4162 assert( IsHidden() );
4163 idPlayer *player;
4164 if ( !gameLocal.isMultiplayer ) {
4165 return;
4166 }
4167 player = gameLocal.GetClientByNum( spectator );
4168 if ( !player || ( player->spectating && player != this ) ) {
4169 SpectateFreeFly( true );
4170 } else if ( usercmd.upmove > 0 ) {
4171 SpectateFreeFly( false );
4172 } else if ( usercmd.buttons & BUTTON_ATTACK ) {
4173 SpectateCycle();
4174 }
4175 }
4176
4177 /*
4178 ===============
4179 idPlayer::HandleSingleGuiCommand
4180 ===============
4181 */
HandleSingleGuiCommand(idEntity * entityGui,idLexer * src)4182 bool idPlayer::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) {
4183 idToken token;
4184
4185 if ( !src->ReadToken( &token ) ) {
4186 return false;
4187 }
4188
4189 if ( token == ";" ) {
4190 return false;
4191 }
4192
4193 if ( token.Icmp( "addhealth" ) == 0 ) {
4194 if ( entityGui && health < 100 ) {
4195 int _health = entityGui->spawnArgs.GetInt( "gui_parm1" );
4196 int amt = ( _health >= HEALTH_PER_DOSE ) ? HEALTH_PER_DOSE : _health;
4197 _health -= amt;
4198 entityGui->spawnArgs.SetInt( "gui_parm1", _health );
4199 if ( entityGui->GetRenderEntity() && entityGui->GetRenderEntity()->gui[ 0 ] ) {
4200 entityGui->GetRenderEntity()->gui[ 0 ]->SetStateInt( "gui_parm1", _health );
4201 }
4202 health += amt;
4203 if ( health > 100 ) {
4204 health = 100;
4205 }
4206 }
4207 return true;
4208 }
4209
4210 if ( token.Icmp( "ready" ) == 0 ) {
4211 PerformImpulse( IMPULSE_17 );
4212 return true;
4213 }
4214
4215 if ( token.Icmp( "updatepda" ) == 0 ) {
4216 UpdatePDAInfo( true );
4217 return true;
4218 }
4219
4220 if ( token.Icmp( "updatepda2" ) == 0 ) {
4221 UpdatePDAInfo( false );
4222 return true;
4223 }
4224
4225 if ( token.Icmp( "stoppdavideo" ) == 0 ) {
4226 if ( objectiveSystem && objectiveSystemOpen && pdaVideoWave.Length() > 0 ) {
4227 StopSound( SND_CHANNEL_PDA, false );
4228 }
4229 return true;
4230 }
4231
4232 if ( token.Icmp( "close" ) == 0 ) {
4233 if ( objectiveSystem && objectiveSystemOpen ) {
4234 TogglePDA();
4235 }
4236 }
4237
4238 if ( token.Icmp( "playpdavideo" ) == 0 ) {
4239 if ( objectiveSystem && objectiveSystemOpen && pdaVideo.Length() > 0 ) {
4240 const idMaterial *mat = declManager->FindMaterial( pdaVideo );
4241 if ( mat ) {
4242 int c = mat->GetNumStages();
4243 for ( int i = 0; i < c; i++ ) {
4244 const shaderStage_t *stage = mat->GetStage(i);
4245 if ( stage && stage->texture.cinematic ) {
4246 stage->texture.cinematic->ResetTime( gameLocal.time );
4247 }
4248 }
4249 if ( pdaVideoWave.Length() ) {
4250 const idSoundShader *shader = declManager->FindSound( pdaVideoWave );
4251 StartSoundShader( shader, SND_CHANNEL_PDA, 0, false, NULL );
4252 }
4253 }
4254 }
4255 }
4256
4257 if ( token.Icmp( "playpdaaudio" ) == 0 ) {
4258 if ( objectiveSystem && objectiveSystemOpen && pdaAudio.Length() > 0 ) {
4259 const idSoundShader *shader = declManager->FindSound( pdaAudio );
4260 int ms;
4261 StartSoundShader( shader, SND_CHANNEL_PDA, 0, false, &ms );
4262 StartAudioLog();
4263 CancelEvents( &EV_Player_StopAudioLog );
4264 PostEventMS( &EV_Player_StopAudioLog, ms + 150 );
4265 }
4266 return true;
4267 }
4268
4269 if ( token.Icmp( "stoppdaaudio" ) == 0 ) {
4270 if ( objectiveSystem && objectiveSystemOpen && pdaAudio.Length() > 0 ) {
4271 // idSoundShader *shader = declManager->FindSound( pdaAudio );
4272 StopAudioLog();
4273 StopSound( SND_CHANNEL_PDA, false );
4274 }
4275 return true;
4276 }
4277
4278 src->UnreadToken( &token );
4279 return false;
4280 }
4281
4282 /*
4283 ==============
4284 idPlayer::Collide
4285 ==============
4286 */
Collide(const trace_t & collision,const idVec3 & velocity)4287 bool idPlayer::Collide( const trace_t &collision, const idVec3 &velocity ) {
4288 idEntity *other;
4289
4290 if ( gameLocal.isClient ) {
4291 return false;
4292 }
4293
4294 other = gameLocal.entities[ collision.c.entityNum ];
4295 if ( other ) {
4296 other->Signal( SIG_TOUCH );
4297 if ( !spectating ) {
4298 if ( other->RespondsTo( EV_Touch ) ) {
4299 other->ProcessEvent( &EV_Touch, this, &collision );
4300 }
4301 } else {
4302 if ( other->RespondsTo( EV_SpectatorTouch ) ) {
4303 other->ProcessEvent( &EV_SpectatorTouch, this, &collision );
4304 }
4305 }
4306 }
4307 return false;
4308 }
4309
4310
4311 /*
4312 ================
4313 idPlayer::UpdateLocation
4314
4315 Searches nearby locations
4316 ================
4317 */
UpdateLocation(void)4318 void idPlayer::UpdateLocation( void ) {
4319 if ( hud ) {
4320 idLocationEntity *locationEntity = gameLocal.LocationForPoint( GetEyePosition() );
4321 if ( locationEntity ) {
4322 hud->SetStateString( "location", locationEntity->GetLocation() );
4323 } else {
4324 hud->SetStateString( "location", common->GetLanguageDict()->GetString( "#str_02911" ) );
4325 }
4326 }
4327 }
4328
4329 /*
4330 ================
4331 idPlayer::ClearFocus
4332
4333 Clears the focus cursor
4334 ================
4335 */
ClearFocus(void)4336 void idPlayer::ClearFocus( void ) {
4337 focusCharacter = NULL;
4338 focusGUIent = NULL;
4339 focusUI = NULL;
4340 focusVehicle = NULL;
4341 talkCursor = 0;
4342 }
4343
4344 /*
4345 ================
4346 idPlayer::UpdateFocus
4347
4348 Searches nearby entities for interactive guis, possibly making one of them
4349 the focus and sending it a mouse move event
4350 ================
4351 */
UpdateFocus(void)4352 void idPlayer::UpdateFocus( void ) {
4353 idClipModel *clipModelList[ MAX_GENTITIES ];
4354 idClipModel *clip;
4355 int listedClipModels;
4356 idEntity *oldFocus;
4357 idEntity *ent;
4358 idUserInterface *oldUI;
4359 idAI *oldChar;
4360 int oldTalkCursor;
4361 int i, j;
4362 idVec3 start, end;
4363 bool allowFocus;
4364 const char *command;
4365 trace_t trace;
4366 guiPoint_t pt;
4367 const idKeyValue *kv;
4368 sysEvent_t ev;
4369 idUserInterface *ui;
4370
4371 if ( gameLocal.inCinematic ) {
4372 return;
4373 }
4374
4375 // only update the focus character when attack button isn't pressed so players
4376 // can still chainsaw NPC's
4377 if ( gameLocal.isMultiplayer || ( !focusCharacter && ( usercmd.buttons & BUTTON_ATTACK ) ) ) {
4378 allowFocus = false;
4379 } else {
4380 allowFocus = true;
4381 }
4382
4383 oldFocus = focusGUIent;
4384 oldUI = focusUI;
4385 oldChar = focusCharacter;
4386 oldTalkCursor = talkCursor;
4387
4388 if ( focusTime <= gameLocal.time ) {
4389 ClearFocus();
4390 }
4391
4392 // don't let spectators interact with GUIs
4393 if ( spectating ) {
4394 return;
4395 }
4396
4397 start = GetEyePosition();
4398 end = start + viewAngles.ToForward() * 80.0f;
4399
4400 // player identification -> names to the hud
4401 if ( gameLocal.isMultiplayer && entityNumber == gameLocal.localClientNum ) {
4402 idVec3 end = start + viewAngles.ToForward() * 768.0f;
4403 gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this );
4404 int iclient = -1;
4405 if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum < MAX_CLIENTS ) ) {
4406 iclient = trace.c.entityNum;
4407 }
4408 if ( MPAim != iclient ) {
4409 lastMPAim = MPAim;
4410 MPAim = iclient;
4411 lastMPAimTime = gameLocal.realClientTime;
4412 }
4413 }
4414
4415 idBounds bounds( start );
4416 bounds.AddPoint( end );
4417
4418 listedClipModels = gameLocal.clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES );
4419
4420 // no pretense at sorting here, just assume that there will only be one active
4421 // gui within range along the trace
4422 for ( i = 0; i < listedClipModels; i++ ) {
4423 clip = clipModelList[ i ];
4424 ent = clip->GetEntity();
4425
4426 if ( ent->IsHidden() ) {
4427 continue;
4428 }
4429
4430 if ( allowFocus ) {
4431 if ( ent->IsType( idAFAttachment::Type ) ) {
4432 idEntity *body = static_cast<idAFAttachment *>( ent )->GetBody();
4433 if ( body && body->IsType( idAI::Type ) && ( static_cast<idAI *>( body )->GetTalkState() >= TALK_OK ) ) {
4434 gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_RENDERMODEL, this );
4435 if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == ent->entityNumber ) ) {
4436 ClearFocus();
4437 focusCharacter = static_cast<idAI *>( body );
4438 talkCursor = 1;
4439 focusTime = gameLocal.time + FOCUS_TIME;
4440 break;
4441 }
4442 }
4443 continue;
4444 }
4445
4446 if ( ent->IsType( idAI::Type ) ) {
4447 if ( static_cast<idAI *>( ent )->GetTalkState() >= TALK_OK ) {
4448 gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_RENDERMODEL, this );
4449 if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == ent->entityNumber ) ) {
4450 ClearFocus();
4451 focusCharacter = static_cast<idAI *>( ent );
4452 talkCursor = 1;
4453 focusTime = gameLocal.time + FOCUS_TIME;
4454 break;
4455 }
4456 }
4457 continue;
4458 }
4459
4460 if ( ent->IsType( idAFEntity_Vehicle::Type ) ) {
4461 gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_RENDERMODEL, this );
4462 if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == ent->entityNumber ) ) {
4463 ClearFocus();
4464 focusVehicle = static_cast<idAFEntity_Vehicle *>( ent );
4465 focusTime = gameLocal.time + FOCUS_TIME;
4466 break;
4467 }
4468 continue;
4469 }
4470 }
4471
4472 if ( !ent->GetRenderEntity() || !ent->GetRenderEntity()->gui[ 0 ] || !ent->GetRenderEntity()->gui[ 0 ]->IsInteractive() ) {
4473 continue;
4474 }
4475
4476 if ( ent->spawnArgs.GetBool( "inv_item" ) ) {
4477 // don't allow guis on pickup items focus
4478 continue;
4479 }
4480
4481 pt = gameRenderWorld->GuiTrace( ent->GetModelDefHandle(), start, end );
4482 if ( pt.x != -1 ) {
4483 // we have a hit
4484 renderEntity_t *focusGUIrenderEntity = ent->GetRenderEntity();
4485 if ( !focusGUIrenderEntity ) {
4486 continue;
4487 }
4488
4489 if ( pt.guiId == 1 ) {
4490 ui = focusGUIrenderEntity->gui[ 0 ];
4491 } else if ( pt.guiId == 2 ) {
4492 ui = focusGUIrenderEntity->gui[ 1 ];
4493 } else {
4494 ui = focusGUIrenderEntity->gui[ 2 ];
4495 }
4496
4497 if ( ui == NULL ) {
4498 continue;
4499 }
4500
4501 ClearFocus();
4502 focusGUIent = ent;
4503 focusUI = ui;
4504
4505 if ( oldFocus != ent ) {
4506 // new activation
4507 // going to see if we have anything in inventory a gui might be interested in
4508 // need to enumerate inventory items
4509 focusUI->SetStateInt( "inv_count", inventory.items.Num() );
4510 for ( j = 0; j < inventory.items.Num(); j++ ) {
4511 idDict *item = inventory.items[ j ];
4512 const char *iname = item->GetString( "inv_name" );
4513 const char *iicon = item->GetString( "inv_icon" );
4514 const char *itext = item->GetString( "inv_text" );
4515
4516 focusUI->SetStateString( va( "inv_name_%i", j), iname );
4517 focusUI->SetStateString( va( "inv_icon_%i", j), iicon );
4518 focusUI->SetStateString( va( "inv_text_%i", j), itext );
4519 kv = item->MatchPrefix("inv_id", NULL);
4520 if ( kv ) {
4521 focusUI->SetStateString( va( "inv_id_%i", j ), kv->GetValue() );
4522 }
4523 focusUI->SetStateInt( iname, 1 );
4524 }
4525
4526
4527 for( j = 0; j < inventory.pdaSecurity.Num(); j++ ) {
4528 const char *p = inventory.pdaSecurity[ j ];
4529 if ( p && *p ) {
4530 focusUI->SetStateInt( p, 1 );
4531 }
4532 }
4533
4534 int staminapercentage = ( int )( 100.0f * stamina / pm_stamina.GetFloat() );
4535 focusUI->SetStateString( "player_health", va("%i", health ) );
4536 focusUI->SetStateString( "player_stamina", va( "%i%%", staminapercentage ) );
4537 focusUI->SetStateString( "player_armor", va( "%i%%", inventory.armor ) );
4538
4539 kv = focusGUIent->spawnArgs.MatchPrefix( "gui_parm", NULL );
4540 while ( kv ) {
4541 focusUI->SetStateString( kv->GetKey(), kv->GetValue() );
4542 kv = focusGUIent->spawnArgs.MatchPrefix( "gui_parm", kv );
4543 }
4544 }
4545
4546 // clamp the mouse to the corner
4547 ev = sys->GenerateMouseMoveEvent( -2000, -2000 );
4548 command = focusUI->HandleEvent( &ev, gameLocal.time );
4549 HandleGuiCommands( focusGUIent, command );
4550
4551 // move to an absolute position
4552 ev = sys->GenerateMouseMoveEvent( pt.x * SCREEN_WIDTH, pt.y * SCREEN_HEIGHT );
4553 command = focusUI->HandleEvent( &ev, gameLocal.time );
4554 HandleGuiCommands( focusGUIent, command );
4555 focusTime = gameLocal.time + FOCUS_GUI_TIME;
4556 break;
4557 }
4558 }
4559
4560 if ( focusGUIent && focusUI ) {
4561 if ( !oldFocus || oldFocus != focusGUIent ) {
4562 command = focusUI->Activate( true, gameLocal.time );
4563 HandleGuiCommands( focusGUIent, command );
4564 StartSound( "snd_guienter", SND_CHANNEL_ANY, 0, false, NULL );
4565 // HideTip();
4566 // HideObjective();
4567 }
4568 } else if ( oldFocus && oldUI ) {
4569 command = oldUI->Activate( false, gameLocal.time );
4570 HandleGuiCommands( oldFocus, command );
4571 StartSound( "snd_guiexit", SND_CHANNEL_ANY, 0, false, NULL );
4572 }
4573
4574 if ( cursor && ( oldTalkCursor != talkCursor ) ) {
4575 cursor->SetStateInt( "talkcursor", talkCursor );
4576 }
4577
4578 if ( oldChar != focusCharacter && hud ) {
4579 if ( focusCharacter ) {
4580 hud->SetStateString( "npc", focusCharacter->spawnArgs.GetString( "npc_name", "Joe" ) );
4581 hud->HandleNamedEvent( "showNPC" );
4582 // HideTip();
4583 // HideObjective();
4584 } else {
4585 hud->SetStateString( "npc", "" );
4586 hud->HandleNamedEvent( "hideNPC" );
4587 }
4588 }
4589 }
4590
4591 /*
4592 =================
4593 idPlayer::CrashLand
4594
4595 Check for hard landings that generate sound events
4596 =================
4597 */
CrashLand(const idVec3 & oldOrigin,const idVec3 & oldVelocity)4598 void idPlayer::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) {
4599 idVec3 origin, velocity;
4600 idVec3 gravityVector, gravityNormal;
4601 float delta;
4602 float hardDelta, fatalDelta;
4603 float dist;
4604 float vel, acc;
4605 float t;
4606 float a, b, c, den;
4607 waterLevel_t waterLevel;
4608 bool noDamage;
4609
4610 AI_SOFTLANDING = false;
4611 AI_HARDLANDING = false;
4612
4613 // if the player is not on the ground
4614 if ( !physicsObj.HasGroundContacts() ) {
4615 return;
4616 }
4617
4618 gravityNormal = physicsObj.GetGravityNormal();
4619
4620 // if the player wasn't going down
4621 if ( ( oldVelocity * -gravityNormal ) >= 0.0f ) {
4622 return;
4623 }
4624
4625 waterLevel = physicsObj.GetWaterLevel();
4626
4627 // never take falling damage if completely underwater
4628 if ( waterLevel == WATERLEVEL_HEAD ) {
4629 return;
4630 }
4631
4632 // no falling damage if touching a nodamage surface
4633 noDamage = false;
4634 for ( int i = 0; i < physicsObj.GetNumContacts(); i++ ) {
4635 const contactInfo_t &contact = physicsObj.GetContact( i );
4636 if ( contact.material->GetSurfaceFlags() & SURF_NODAMAGE ) {
4637 noDamage = true;
4638 StartSound( "snd_land_hard", SND_CHANNEL_ANY, 0, false, NULL );
4639 break;
4640 }
4641 }
4642
4643 origin = GetPhysics()->GetOrigin();
4644 gravityVector = physicsObj.GetGravity();
4645
4646 // calculate the exact velocity on landing
4647 dist = ( origin - oldOrigin ) * -gravityNormal;
4648 vel = oldVelocity * -gravityNormal;
4649 acc = -gravityVector.Length();
4650
4651 a = acc / 2.0f;
4652 b = vel;
4653 c = -dist;
4654
4655 den = b * b - 4.0f * a * c;
4656 if ( den < 0 ) {
4657 return;
4658 }
4659 t = ( -b - idMath::Sqrt( den ) ) / ( 2.0f * a );
4660
4661 delta = vel + t * acc;
4662 delta = delta * delta * 0.0001;
4663
4664 // reduce falling damage if there is standing water
4665 if ( waterLevel == WATERLEVEL_WAIST ) {
4666 delta *= 0.25f;
4667 }
4668 if ( waterLevel == WATERLEVEL_FEET ) {
4669 delta *= 0.5f;
4670 }
4671
4672 if ( delta < 1.0f ) {
4673 return;
4674 }
4675
4676 // allow falling a bit further for multiplayer
4677 if ( gameLocal.isMultiplayer ) {
4678 fatalDelta = 75.0f;
4679 hardDelta = 50.0f;
4680 } else {
4681 fatalDelta = 65.0f;
4682 hardDelta = 45.0f;
4683 }
4684
4685 if ( delta > fatalDelta ) {
4686 AI_HARDLANDING = true;
4687 landChange = -32;
4688 landTime = gameLocal.time;
4689 if ( !noDamage ) {
4690 pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim
4691 Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_fatalfall", 1.0f, 0 );
4692 }
4693 } else if ( delta > hardDelta ) {
4694 AI_HARDLANDING = true;
4695 landChange = -24;
4696 landTime = gameLocal.time;
4697 if ( !noDamage ) {
4698 pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim
4699 Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_hardfall", 1.0f, 0 );
4700 }
4701 } else if ( delta > 30 ) {
4702 AI_HARDLANDING = true;
4703 landChange = -16;
4704 landTime = gameLocal.time;
4705 if ( !noDamage ) {
4706 pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim
4707 Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_softfall", 1.0f, 0 );
4708 }
4709 } else if ( delta > 7 ) {
4710 AI_SOFTLANDING = true;
4711 landChange = -8;
4712 landTime = gameLocal.time;
4713 } else if ( delta > 3 ) {
4714 // just walk on
4715 }
4716 }
4717
4718 /*
4719 ===============
4720 idPlayer::BobCycle
4721 ===============
4722 */
BobCycle(const idVec3 & pushVelocity)4723 void idPlayer::BobCycle( const idVec3 &pushVelocity ) {
4724 float bobmove;
4725 int old, deltaTime;
4726 idVec3 vel, gravityDir, velocity;
4727 idMat3 viewaxis;
4728 float bob;
4729 float delta;
4730 float speed;
4731 float f;
4732
4733 //
4734 // calculate speed and cycle to be used for
4735 // all cyclic walking effects
4736 //
4737 velocity = physicsObj.GetLinearVelocity() - pushVelocity;
4738
4739 gravityDir = physicsObj.GetGravityNormal();
4740 vel = velocity - ( velocity * gravityDir ) * gravityDir;
4741 xyspeed = vel.LengthFast();
4742
4743 // do not evaluate the bob for other clients
4744 // when doing a spectate follow, don't do any weapon bobbing
4745 if ( gameLocal.isClient && entityNumber != gameLocal.localClientNum ) {
4746 viewBobAngles.Zero();
4747 viewBob.Zero();
4748 return;
4749 }
4750
4751 if ( !physicsObj.HasGroundContacts() || influenceActive == INFLUENCE_LEVEL2 || ( gameLocal.isMultiplayer && spectating ) ) {
4752 // airborne
4753 bobCycle = 0;
4754 bobFoot = 0;
4755 bobfracsin = 0;
4756 } else if ( ( !usercmd.forwardmove && !usercmd.rightmove ) || ( xyspeed <= MIN_BOB_SPEED ) ) {
4757 // start at beginning of cycle again
4758 bobCycle = 0;
4759 bobFoot = 0;
4760 bobfracsin = 0;
4761 } else {
4762 if ( physicsObj.IsCrouching() ) {
4763 bobmove = pm_crouchbob.GetFloat();
4764 // ducked characters never play footsteps
4765 } else {
4766 // vary the bobbing based on the speed of the player
4767 bobmove = pm_walkbob.GetFloat() * ( 1.0f - bobFrac ) + pm_runbob.GetFloat() * bobFrac;
4768 }
4769
4770 // check for footstep / splash sounds
4771 old = bobCycle;
4772 bobCycle = (int)( old + bobmove * gameLocal.msec ) & 255;
4773 bobFoot = ( bobCycle & 128 ) >> 7;
4774 bobfracsin = idMath::Fabs( sin( ( bobCycle & 127 ) / 127.0 * idMath::PI ) );
4775 }
4776
4777 // calculate angles for view bobbing
4778 viewBobAngles.Zero();
4779
4780 viewaxis = viewAngles.ToMat3() * physicsObj.GetGravityAxis();
4781
4782 // add angles based on velocity
4783 delta = velocity * viewaxis[0];
4784 viewBobAngles.pitch += delta * pm_runpitch.GetFloat();
4785
4786 delta = velocity * viewaxis[1];
4787 viewBobAngles.roll -= delta * pm_runroll.GetFloat();
4788
4789 // add angles based on bob
4790 // make sure the bob is visible even at low speeds
4791 speed = xyspeed > 200 ? xyspeed : 200;
4792
4793 delta = bobfracsin * pm_bobpitch.GetFloat() * speed;
4794 if ( physicsObj.IsCrouching() ) {
4795 delta *= 3; // crouching
4796 }
4797 viewBobAngles.pitch += delta;
4798 delta = bobfracsin * pm_bobroll.GetFloat() * speed;
4799 if ( physicsObj.IsCrouching() ) {
4800 delta *= 3; // crouching accentuates roll
4801 }
4802 if ( bobFoot & 1 ) {
4803 delta = -delta;
4804 }
4805 viewBobAngles.roll += delta;
4806
4807 // calculate position for view bobbing
4808 viewBob.Zero();
4809
4810 if ( physicsObj.HasSteppedUp() ) {
4811
4812 // check for stepping up before a previous step is completed
4813 deltaTime = gameLocal.time - stepUpTime;
4814 if ( deltaTime < STEPUP_TIME ) {
4815 stepUpDelta = stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME + physicsObj.GetStepUp();
4816 } else {
4817 stepUpDelta = physicsObj.GetStepUp();
4818 }
4819 if ( stepUpDelta > 2.0f * pm_stepsize.GetFloat() ) {
4820 stepUpDelta = 2.0f * pm_stepsize.GetFloat();
4821 }
4822 stepUpTime = gameLocal.time;
4823 }
4824
4825 idVec3 gravity = physicsObj.GetGravityNormal();
4826
4827 // if the player stepped up recently
4828 deltaTime = gameLocal.time - stepUpTime;
4829 if ( deltaTime < STEPUP_TIME ) {
4830 viewBob += gravity * ( stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME );
4831 }
4832
4833 // add bob height after any movement smoothing
4834 bob = bobfracsin * xyspeed * pm_bobup.GetFloat();
4835 if ( bob > 6 ) {
4836 bob = 6;
4837 }
4838 viewBob[2] += bob;
4839
4840 // add fall height
4841 delta = gameLocal.time - landTime;
4842 if ( delta < LAND_DEFLECT_TIME ) {
4843 f = delta / LAND_DEFLECT_TIME;
4844 viewBob -= gravity * ( landChange * f );
4845 } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) {
4846 delta -= LAND_DEFLECT_TIME;
4847 f = 1.0 - ( delta / LAND_RETURN_TIME );
4848 viewBob -= gravity * ( landChange * f );
4849 }
4850 }
4851
4852 /*
4853 ================
4854 idPlayer::UpdateDeltaViewAngles
4855 ================
4856 */
UpdateDeltaViewAngles(const idAngles & angles)4857 void idPlayer::UpdateDeltaViewAngles( const idAngles &angles ) {
4858 // set the delta angle
4859 idAngles delta;
4860 for( int i = 0; i < 3; i++ ) {
4861 delta[ i ] = angles[ i ] - SHORT2ANGLE( usercmd.angles[ i ] );
4862 }
4863 SetDeltaViewAngles( delta );
4864 }
4865
4866 /*
4867 ================
4868 idPlayer::SetViewAngles
4869 ================
4870 */
SetViewAngles(const idAngles & angles)4871 void idPlayer::SetViewAngles( const idAngles &angles ) {
4872 UpdateDeltaViewAngles( angles );
4873 viewAngles = angles;
4874 }
4875
4876 /*
4877 ================
4878 idPlayer::UpdateViewAngles
4879 ================
4880 */
UpdateViewAngles(void)4881 void idPlayer::UpdateViewAngles( void ) {
4882 int i;
4883 idAngles delta;
4884
4885 if ( !noclip && ( gameLocal.inCinematic || privateCameraView || gameLocal.GetCamera() || influenceActive == INFLUENCE_LEVEL2 || objectiveSystemOpen ) ) {
4886 // no view changes at all, but we still want to update the deltas or else when
4887 // we get out of this mode, our view will snap to a kind of random angle
4888 UpdateDeltaViewAngles( viewAngles );
4889 return;
4890 }
4891
4892 // if dead
4893 if ( health <= 0 ) {
4894 if ( pm_thirdPersonDeath.GetBool() ) {
4895 viewAngles.roll = 0.0f;
4896 viewAngles.pitch = 30.0f;
4897 } else {
4898 viewAngles.roll = 40.0f;
4899 viewAngles.pitch = -15.0f;
4900 }
4901 return;
4902 }
4903
4904 // circularly clamp the angles with deltas
4905 for ( i = 0; i < 3; i++ ) {
4906 cmdAngles[i] = SHORT2ANGLE( usercmd.angles[i] );
4907 if ( influenceActive == INFLUENCE_LEVEL3 ) {
4908 viewAngles[i] += idMath::ClampFloat( -1.0f, 1.0f, idMath::AngleDelta( idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i]) + deltaViewAngles[i] ) , viewAngles[i] ) );
4909 } else {
4910 viewAngles[i] = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i]) + deltaViewAngles[i] );
4911 }
4912 }
4913 if ( !centerView.IsDone( gameLocal.time ) ) {
4914 viewAngles.pitch = centerView.GetCurrentValue(gameLocal.time);
4915 }
4916
4917 // clamp the pitch
4918 if ( noclip ) {
4919 if ( viewAngles.pitch > 89.0f ) {
4920 // don't let the player look down more than 89 degrees while noclipping
4921 viewAngles.pitch = 89.0f;
4922 } else if ( viewAngles.pitch < -89.0f ) {
4923 // don't let the player look up more than 89 degrees while noclipping
4924 viewAngles.pitch = -89.0f;
4925 }
4926 } else {
4927 if ( viewAngles.pitch > pm_maxviewpitch.GetFloat() ) {
4928 // don't let the player look down enough to see the shadow of his (non-existant) feet
4929 viewAngles.pitch = pm_maxviewpitch.GetFloat();
4930 } else if ( viewAngles.pitch < pm_minviewpitch.GetFloat() ) {
4931 // don't let the player look up more than 89 degrees
4932 viewAngles.pitch = pm_minviewpitch.GetFloat();
4933 }
4934 }
4935
4936 UpdateDeltaViewAngles( viewAngles );
4937
4938 // orient the model towards the direction we're looking
4939 SetAngles( idAngles( 0, viewAngles.yaw, 0 ) );
4940
4941 // save in the log for analyzing weapon angle offsets
4942 loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ] = viewAngles;
4943 }
4944
4945 /*
4946 ==============
4947 idPlayer::AdjustHeartRate
4948
4949 Player heartrate works as follows
4950
4951 DEF_HEARTRATE is resting heartrate
4952
4953 Taking damage when health is above 75 adjusts heart rate by 1 beat per second
4954 Taking damage when health is below 75 adjusts heart rate by 5 beats per second
4955 Maximum heartrate from damage is MAX_HEARTRATE
4956
4957 Firing a weapon adds 1 beat per second up to a maximum of COMBAT_HEARTRATE
4958
4959 Being at less than 25% stamina adds 5 beats per second up to ZEROSTAMINA_HEARTRATE
4960
4961 All heartrates are target rates.. the heart rate will start falling as soon as there have been no adjustments for 5 seconds
4962 Once it starts falling it always tries to get to DEF_HEARTRATE
4963
4964 The exception to the above rule is upon death at which point the rate is set to DYING_HEARTRATE and starts falling
4965 immediately to zero
4966
4967 Heart rate volumes go from zero ( -40 db for DEF_HEARTRATE to 5 db for MAX_HEARTRATE ) the volume is
4968 scaled linearly based on the actual rate
4969
4970 Exception to the above rule is once the player is dead, the dying heart rate starts at either the current volume if
4971 it is audible or -10db and scales to 8db on the last few beats
4972 ==============
4973 */
AdjustHeartRate(int target,float timeInSecs,float delay,bool force)4974 void idPlayer::AdjustHeartRate( int target, float timeInSecs, float delay, bool force ) {
4975
4976 if ( heartInfo.GetEndValue() == target ) {
4977 return;
4978 }
4979
4980 if ( AI_DEAD && !force ) {
4981 return;
4982 }
4983
4984 lastHeartAdjust = gameLocal.time;
4985
4986 heartInfo.Init( gameLocal.time + delay * 1000, timeInSecs * 1000, heartRate, target );
4987 }
4988
4989 /*
4990 ==============
4991 idPlayer::GetBaseHeartRate
4992 ==============
4993 */
GetBaseHeartRate(void)4994 int idPlayer::GetBaseHeartRate( void ) {
4995 int base = idMath::FtoiFast( ( BASE_HEARTRATE + LOWHEALTH_HEARTRATE_ADJ ) - ( (float)health / 100.0f ) * LOWHEALTH_HEARTRATE_ADJ );
4996 int rate = idMath::FtoiFast( base + ( ZEROSTAMINA_HEARTRATE - base ) * ( 1.0f - stamina / pm_stamina.GetFloat() ) );
4997 int diff = ( lastDmgTime ) ? gameLocal.time - lastDmgTime : 99999;
4998 rate += ( diff < 5000 ) ? ( diff < 2500 ) ? ( diff < 1000 ) ? 15 : 10 : 5 : 0;
4999 return rate;
5000 }
5001
5002 /*
5003 ==============
5004 idPlayer::SetCurrentHeartRate
5005 ==============
5006 */
SetCurrentHeartRate(void)5007 void idPlayer::SetCurrentHeartRate( void ) {
5008
5009 int base = idMath::FtoiFast( ( BASE_HEARTRATE + LOWHEALTH_HEARTRATE_ADJ ) - ( (float) health / 100.0f ) * LOWHEALTH_HEARTRATE_ADJ );
5010
5011 if ( PowerUpActive( ADRENALINE )) {
5012 heartRate = 135;
5013 } else {
5014 heartRate = idMath::FtoiFast( heartInfo.GetCurrentValue( gameLocal.time ) );
5015 int currentRate = GetBaseHeartRate();
5016 if ( health >= 0 && gameLocal.time > lastHeartAdjust + 2500 ) {
5017 AdjustHeartRate( currentRate, 2.5f, 0.0f, false );
5018 }
5019 }
5020
5021 int bps = idMath::FtoiFast( 60.0f / heartRate * 1000.0f );
5022 if ( gameLocal.time - lastHeartBeat > bps ) {
5023 int dmgVol = DMG_VOLUME;
5024 int deathVol = DEATH_VOLUME;
5025 int zeroVol = ZERO_VOLUME;
5026 float pct = 0.0;
5027 if ( heartRate > BASE_HEARTRATE && health > 0 ) {
5028 pct = (float)(heartRate - base) / (MAX_HEARTRATE - base);
5029 pct *= ((float)dmgVol - (float)zeroVol);
5030 } else if ( health <= 0 ) {
5031 pct = (float)(heartRate - DYING_HEARTRATE) / (BASE_HEARTRATE - DYING_HEARTRATE);
5032 if ( pct > 1.0f ) {
5033 pct = 1.0f;
5034 } else if (pct < 0.0f) {
5035 pct = 0.0f;
5036 }
5037 pct *= ((float)deathVol - (float)zeroVol);
5038 }
5039
5040 pct += (float)zeroVol;
5041
5042 if ( pct != zeroVol ) {
5043 StartSound( "snd_heartbeat", SND_CHANNEL_HEART, SSF_PRIVATE_SOUND, false, NULL );
5044 // modify just this channel to a custom volume
5045 soundShaderParms_t parms;
5046 memset( &parms, 0, sizeof( parms ) );
5047 parms.volume = pct;
5048 refSound.referenceSound->ModifySound( SND_CHANNEL_HEART, &parms );
5049 }
5050
5051 lastHeartBeat = gameLocal.time;
5052 }
5053 }
5054
5055 /*
5056 ==============
5057 idPlayer::UpdateAir
5058 ==============
5059 */
UpdateAir(void)5060 void idPlayer::UpdateAir( void ) {
5061 if ( health <= 0 ) {
5062 return;
5063 }
5064
5065 // see if the player is connected to the info_vacuum
5066 bool newAirless = false;
5067
5068 if ( gameLocal.vacuumAreaNum != -1 ) {
5069 int num = GetNumPVSAreas();
5070 if ( num > 0 ) {
5071 int areaNum;
5072
5073 // if the player box spans multiple areas, get the area from the origin point instead,
5074 // otherwise a rotating player box may poke into an outside area
5075 if ( num == 1 ) {
5076 const int *pvsAreas = GetPVSAreas();
5077 areaNum = pvsAreas[0];
5078 } else {
5079 areaNum = gameRenderWorld->PointInArea( this->GetPhysics()->GetOrigin() );
5080 }
5081 newAirless = gameRenderWorld->AreasAreConnected( gameLocal.vacuumAreaNum, areaNum, PS_BLOCK_AIR );
5082 }
5083 }
5084
5085 if ( newAirless ) {
5086 if ( !airless ) {
5087 StartSound( "snd_decompress", SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL );
5088 StartSound( "snd_noAir", SND_CHANNEL_BODY2, 0, false, NULL );
5089 if ( hud ) {
5090 hud->HandleNamedEvent( "noAir" );
5091 }
5092 }
5093 airTics--;
5094 if ( airTics < 0 ) {
5095 airTics = 0;
5096 // check for damage
5097 const idDict *damageDef = gameLocal.FindEntityDefDict( "damage_noair", false );
5098 int dmgTiming = 1000 * ((damageDef) ? damageDef->GetFloat( "delay", "3.0" ) : 3.0f );
5099 if ( gameLocal.time > lastAirDamage + dmgTiming ) {
5100 Damage( NULL, NULL, vec3_origin, "damage_noair", 1.0f, 0 );
5101 lastAirDamage = gameLocal.time;
5102 }
5103 }
5104
5105 } else {
5106 if ( airless ) {
5107 StartSound( "snd_recompress", SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL );
5108 StopSound( SND_CHANNEL_BODY2, false );
5109 if ( hud ) {
5110 hud->HandleNamedEvent( "Air" );
5111 }
5112 }
5113 airTics+=2; // regain twice as fast as lose
5114 if ( airTics > pm_airTics.GetInteger() ) {
5115 airTics = pm_airTics.GetInteger();
5116 }
5117 }
5118
5119 airless = newAirless;
5120
5121 if ( hud ) {
5122 hud->SetStateInt( "player_air", 100 * airTics / pm_airTics.GetInteger() );
5123 }
5124 }
5125
5126 /*
5127 ==============
5128 idPlayer::AddGuiPDAData
5129 ==============
5130 */
AddGuiPDAData(const declType_t dataType,const char * listName,const idDeclPDA * src,idUserInterface * gui)5131 int idPlayer::AddGuiPDAData( const declType_t dataType, const char *listName, const idDeclPDA *src, idUserInterface *gui ) {
5132 int c, i;
5133 idStr work;
5134 if ( dataType == DECL_EMAIL ) {
5135 c = src->GetNumEmails();
5136 for ( i = 0; i < c; i++ ) {
5137 const idDeclEmail *email = src->GetEmailByIndex( i );
5138 if ( email == NULL ) {
5139 work = va( "-\tEmail %d not found\t-", i );
5140 } else {
5141 work = email->GetFrom();
5142 work += "\t";
5143 work += email->GetSubject();
5144 work += "\t";
5145 work += email->GetDate();
5146 }
5147 gui->SetStateString( va( "%s_item_%i", listName, i ), work );
5148 }
5149 return c;
5150 } else if ( dataType == DECL_AUDIO ) {
5151 c = src->GetNumAudios();
5152 for ( i = 0; i < c; i++ ) {
5153 const idDeclAudio *audio = src->GetAudioByIndex( i );
5154 if ( audio == NULL ) {
5155 work = va( "Audio Log %d not found", i );
5156 } else {
5157 work = audio->GetAudioName();
5158 }
5159 gui->SetStateString( va( "%s_item_%i", listName, i ), work );
5160 }
5161 return c;
5162 } else if ( dataType == DECL_VIDEO ) {
5163 c = inventory.videos.Num();
5164 for ( i = 0; i < c; i++ ) {
5165 const idDeclVideo *video = GetVideo( i );
5166 if ( video == NULL ) {
5167 work = va( "Video CD %s not found", inventory.videos[i].c_str() );
5168 } else {
5169 work = video->GetVideoName();
5170 }
5171 gui->SetStateString( va( "%s_item_%i", listName, i ), work );
5172 }
5173 return c;
5174 }
5175 return 0;
5176 }
5177
5178 /*
5179 ==============
5180 idPlayer::GetPDA
5181 ==============
5182 */
GetPDA(void) const5183 const idDeclPDA *idPlayer::GetPDA( void ) const {
5184 if ( inventory.pdas.Num() ) {
5185 return static_cast< const idDeclPDA* >( declManager->FindType( DECL_PDA, inventory.pdas[ 0 ] ) );
5186 } else {
5187 return NULL;
5188 }
5189 }
5190
5191
5192 /*
5193 ==============
5194 idPlayer::GetVideo
5195 ==============
5196 */
GetVideo(int index)5197 const idDeclVideo *idPlayer::GetVideo( int index ) {
5198 if ( index >= 0 && index < inventory.videos.Num() ) {
5199 return static_cast< const idDeclVideo* >( declManager->FindType( DECL_VIDEO, inventory.videos[index], false ) );
5200 }
5201 return NULL;
5202 }
5203
5204
5205 /*
5206 ==============
5207 idPlayer::UpdatePDAInfo
5208 ==============
5209 */
UpdatePDAInfo(bool updatePDASel)5210 void idPlayer::UpdatePDAInfo( bool updatePDASel ) {
5211 int j, sel;
5212
5213 if ( objectiveSystem == NULL ) {
5214 return;
5215 }
5216
5217 assert( hud );
5218
5219 int currentPDA = objectiveSystem->State().GetInt( "listPDA_sel_0", "0" );
5220 if ( currentPDA == -1 ) {
5221 currentPDA = 0;
5222 }
5223
5224 if ( updatePDASel ) {
5225 objectiveSystem->SetStateInt( "listPDAVideo_sel_0", 0 );
5226 objectiveSystem->SetStateInt( "listPDAEmail_sel_0", 0 );
5227 objectiveSystem->SetStateInt( "listPDAAudio_sel_0", 0 );
5228 }
5229
5230 if ( currentPDA > 0 ) {
5231 currentPDA = inventory.pdas.Num() - currentPDA;
5232 }
5233
5234 // Mark in the bit array that this pda has been read
5235 if ( currentPDA < 128 ) {
5236 inventory.pdasViewed[currentPDA >> 5] |= 1 << (currentPDA & 31);
5237 }
5238
5239 pdaAudio = "";
5240 pdaVideo = "";
5241 pdaVideoWave = "";
5242 idStr name, data, preview, info, wave;
5243 for ( j = 0; j < MAX_PDAS; j++ ) {
5244 objectiveSystem->SetStateString( va( "listPDA_item_%i", j ), "" );
5245 }
5246 for ( j = 0; j < MAX_PDA_ITEMS; j++ ) {
5247 objectiveSystem->SetStateString( va( "listPDAVideo_item_%i", j ), "" );
5248 objectiveSystem->SetStateString( va( "listPDAAudio_item_%i", j ), "" );
5249 objectiveSystem->SetStateString( va( "listPDAEmail_item_%i", j ), "" );
5250 objectiveSystem->SetStateString( va( "listPDASecurity_item_%i", j ), "" );
5251 }
5252 for ( j = 0; j < inventory.pdas.Num(); j++ ) {
5253
5254 const idDeclPDA *pda = static_cast< const idDeclPDA* >( declManager->FindType( DECL_PDA, inventory.pdas[j], false ) );
5255
5256 if ( pda == NULL ) {
5257 continue;
5258 }
5259
5260 int index = inventory.pdas.Num() - j;
5261 if ( j == 0 ) {
5262 // Special case for the first PDA
5263 index = 0;
5264 }
5265
5266 if ( j != currentPDA && j < 128 && inventory.pdasViewed[j >> 5] & (1 << (j & 31)) ) {
5267 // This pda has been read already, mark in gray
5268 objectiveSystem->SetStateString( va( "listPDA_item_%i", index), va(S_COLOR_GRAY "%s", pda->GetPdaName()) );
5269 } else {
5270 // This pda has not been read yet
5271 objectiveSystem->SetStateString( va( "listPDA_item_%i", index), pda->GetPdaName() );
5272 }
5273
5274 const char *security = pda->GetSecurity();
5275 if ( j == currentPDA || (currentPDA == 0 && security && *security ) ) {
5276 if ( *security == 0 ) {
5277 security = common->GetLanguageDict()->GetString( "#str_00066" );
5278 }
5279 objectiveSystem->SetStateString( "PDASecurityClearance", security );
5280 }
5281
5282 if ( j == currentPDA ) {
5283
5284 objectiveSystem->SetStateString( "pda_icon", pda->GetIcon() );
5285 objectiveSystem->SetStateString( "pda_id", pda->GetID() );
5286 objectiveSystem->SetStateString( "pda_title", pda->GetTitle() );
5287
5288 if ( j == 0 ) {
5289 // Selected, personal pda
5290 // Add videos
5291 if ( updatePDASel || !inventory.pdaOpened ) {
5292 objectiveSystem->HandleNamedEvent( "playerPDAActive" );
5293 objectiveSystem->SetStateString( "pda_personal", "1" );
5294 inventory.pdaOpened = true;
5295 }
5296 objectiveSystem->SetStateString( "pda_location", hud->State().GetString("location") );
5297 objectiveSystem->SetStateString( "pda_name", cvarSystem->GetCVarString( "ui_name") );
5298 AddGuiPDAData( DECL_VIDEO, "listPDAVideo", pda, objectiveSystem );
5299 sel = objectiveSystem->State().GetInt( "listPDAVideo_sel_0", "0" );
5300 const idDeclVideo *vid = NULL;
5301 if ( sel >= 0 && sel < inventory.videos.Num() ) {
5302 vid = static_cast< const idDeclVideo * >( declManager->FindType( DECL_VIDEO, inventory.videos[ sel ], false ) );
5303 }
5304 if ( vid ) {
5305 pdaVideo = vid->GetRoq();
5306 pdaVideoWave = vid->GetWave();
5307 objectiveSystem->SetStateString( "PDAVideoTitle", vid->GetVideoName() );
5308 objectiveSystem->SetStateString( "PDAVideoVid", vid->GetRoq() );
5309 objectiveSystem->SetStateString( "PDAVideoIcon", vid->GetPreview() );
5310 objectiveSystem->SetStateString( "PDAVideoInfo", vid->GetInfo() );
5311 } else {
5312 //FIXME: need to precache these in the player def
5313 objectiveSystem->SetStateString( "PDAVideoVid", "sound/vo/video/welcome.tga" );
5314 objectiveSystem->SetStateString( "PDAVideoIcon", "sound/vo/video/welcome.tga" );
5315 objectiveSystem->SetStateString( "PDAVideoTitle", "" );
5316 objectiveSystem->SetStateString( "PDAVideoInfo", "" );
5317 }
5318 } else {
5319 // Selected, non-personal pda
5320 // Add audio logs
5321 if ( updatePDASel ) {
5322 objectiveSystem->HandleNamedEvent( "playerPDANotActive" );
5323 objectiveSystem->SetStateString( "pda_personal", "0" );
5324 inventory.pdaOpened = true;
5325 }
5326 objectiveSystem->SetStateString( "pda_location", pda->GetPost() );
5327 objectiveSystem->SetStateString( "pda_name", pda->GetFullName() );
5328 int audioCount = AddGuiPDAData( DECL_AUDIO, "listPDAAudio", pda, objectiveSystem );
5329 objectiveSystem->SetStateInt( "audioLogCount", audioCount );
5330 sel = objectiveSystem->State().GetInt( "listPDAAudio_sel_0", "0" );
5331 const idDeclAudio *aud = NULL;
5332 if ( sel >= 0 ) {
5333 aud = pda->GetAudioByIndex( sel );
5334 }
5335 if ( aud ) {
5336 pdaAudio = aud->GetWave();
5337 objectiveSystem->SetStateString( "PDAAudioTitle", aud->GetAudioName() );
5338 objectiveSystem->SetStateString( "PDAAudioIcon", aud->GetPreview() );
5339 objectiveSystem->SetStateString( "PDAAudioInfo", aud->GetInfo() );
5340 } else {
5341 objectiveSystem->SetStateString( "PDAAudioIcon", "sound/vo/video/welcome.tga" );
5342 objectiveSystem->SetStateString( "PDAAutioTitle", "" );
5343 objectiveSystem->SetStateString( "PDAAudioInfo", "" );
5344 }
5345 }
5346 // add emails
5347 name = "";
5348 data = "";
5349 int numEmails = pda->GetNumEmails();
5350 if ( numEmails > 0 ) {
5351 AddGuiPDAData( DECL_EMAIL, "listPDAEmail", pda, objectiveSystem );
5352 sel = objectiveSystem->State().GetInt( "listPDAEmail_sel_0", "-1" );
5353 if ( sel >= 0 && sel < numEmails ) {
5354 const idDeclEmail *email = pda->GetEmailByIndex( sel );
5355 name = email->GetSubject();
5356 data = email->GetBody();
5357 }
5358 }
5359 objectiveSystem->SetStateString( "PDAEmailTitle", name );
5360 objectiveSystem->SetStateString( "PDAEmailText", data );
5361 }
5362 }
5363 if ( objectiveSystem->State().GetInt( "listPDA_sel_0", "-1" ) == -1 ) {
5364 objectiveSystem->SetStateInt( "listPDA_sel_0", 0 );
5365 }
5366 objectiveSystem->StateChanged( gameLocal.time );
5367 }
5368
5369 /*
5370 ==============
5371 idPlayer::TogglePDA
5372 ==============
5373 */
TogglePDA(void)5374 void idPlayer::TogglePDA( void ) {
5375 if ( objectiveSystem == NULL ) {
5376 return;
5377 }
5378
5379 if ( inventory.pdas.Num() == 0 ) {
5380 ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_noPDA" ), true );
5381 return;
5382 }
5383
5384 assert( hud );
5385
5386 if ( !objectiveSystemOpen ) {
5387 int j, c = inventory.items.Num();
5388 objectiveSystem->SetStateInt( "inv_count", c );
5389 for ( j = 0; j < MAX_INVENTORY_ITEMS; j++ ) {
5390 objectiveSystem->SetStateString( va( "inv_name_%i", j ), "" );
5391 objectiveSystem->SetStateString( va( "inv_icon_%i", j ), "" );
5392 objectiveSystem->SetStateString( va( "inv_text_%i", j ), "" );
5393 }
5394 for ( j = 0; j < c; j++ ) {
5395 idDict *item = inventory.items[j];
5396 if ( !item->GetBool( "inv_pda" ) ) {
5397 const char *iname = item->GetString( "inv_name" );
5398 const char *iicon = item->GetString( "inv_icon" );
5399 const char *itext = item->GetString( "inv_text" );
5400 objectiveSystem->SetStateString( va( "inv_name_%i", j ), iname );
5401 objectiveSystem->SetStateString( va( "inv_icon_%i", j ), iicon );
5402 objectiveSystem->SetStateString( va( "inv_text_%i", j ), itext );
5403 const idKeyValue *kv = item->MatchPrefix( "inv_id", NULL );
5404 if ( kv ) {
5405 objectiveSystem->SetStateString( va( "inv_id_%i", j ), kv->GetValue() );
5406 }
5407 }
5408 }
5409
5410 for ( j = 0; j < MAX_WEAPONS; j++ ) {
5411 const char *weapnum = va( "def_weapon%d", j );
5412 const char *hudWeap = va( "weapon%d", j );
5413 int weapstate = 0;
5414 if ( inventory.weapons & ( 1 << j ) ) {
5415 const char *weap = spawnArgs.GetString( weapnum );
5416 if ( weap && *weap ) {
5417 weapstate++;
5418 }
5419 }
5420 objectiveSystem->SetStateInt( hudWeap, weapstate );
5421 }
5422
5423 objectiveSystem->SetStateInt( "listPDA_sel_0", inventory.selPDA );
5424 objectiveSystem->SetStateInt( "listPDAVideo_sel_0", inventory.selVideo );
5425 objectiveSystem->SetStateInt( "listPDAAudio_sel_0", inventory.selAudio );
5426 objectiveSystem->SetStateInt( "listPDAEmail_sel_0", inventory.selEMail );
5427 UpdatePDAInfo( false );
5428 UpdateObjectiveInfo();
5429 objectiveSystem->Activate( true, gameLocal.time );
5430 hud->HandleNamedEvent( "pdaPickupHide" );
5431 hud->HandleNamedEvent( "videoPickupHide" );
5432 } else {
5433 inventory.selPDA = objectiveSystem->State().GetInt( "listPDA_sel_0" );
5434 inventory.selVideo = objectiveSystem->State().GetInt( "listPDAVideo_sel_0" );
5435 inventory.selAudio = objectiveSystem->State().GetInt( "listPDAAudio_sel_0" );
5436 inventory.selEMail = objectiveSystem->State().GetInt( "listPDAEmail_sel_0" );
5437 objectiveSystem->Activate( false, gameLocal.time );
5438 }
5439 objectiveSystemOpen ^= 1;
5440 }
5441
5442 /*
5443 ==============
5444 idPlayer::ToggleScoreboard
5445 ==============
5446 */
ToggleScoreboard(void)5447 void idPlayer::ToggleScoreboard( void ) {
5448 scoreBoardOpen ^= 1;
5449 }
5450
5451 /*
5452 ==============
5453 idPlayer::Spectate
5454 ==============
5455 */
Spectate(bool spectate)5456 void idPlayer::Spectate( bool spectate ) {
5457 idBitMsg msg;
5458 byte msgBuf[MAX_EVENT_PARAM_SIZE];
5459
5460 // track invisible player bug
5461 // all hiding and showing should be performed through Spectate calls
5462 // except for the private camera view, which is used for teleports
5463 assert( ( teleportEntity.GetEntity() != NULL ) || ( IsHidden() == spectating ) );
5464
5465 if ( spectating == spectate ) {
5466 return;
5467 }
5468
5469 spectating = spectate;
5470
5471 if ( gameLocal.isServer ) {
5472 msg.Init( msgBuf, sizeof( msgBuf ) );
5473 msg.WriteBits( spectating, 1 );
5474 ServerSendEvent( EVENT_SPECTATE, &msg, false, -1 );
5475 }
5476
5477 if ( spectating ) {
5478 // join the spectators
5479 ClearPowerUps();
5480 spectator = this->entityNumber;
5481 Init();
5482 StopRagdoll();
5483 SetPhysics( &physicsObj );
5484 physicsObj.DisableClip();
5485 Hide();
5486 Event_DisableWeapon();
5487 if ( hud ) {
5488 hud->HandleNamedEvent( "aim_clear" );
5489 MPAimFadeTime = 0;
5490 }
5491 } else {
5492 // put everything back together again
5493 currentWeapon = -1; // to make sure the def will be loaded if necessary
5494 Show();
5495 Event_EnableWeapon();
5496 }
5497 SetClipModel();
5498 }
5499
5500 /*
5501 ==============
5502 idPlayer::SetClipModel
5503 ==============
5504 */
SetClipModel(void)5505 void idPlayer::SetClipModel( void ) {
5506 idBounds bounds;
5507
5508 if ( spectating ) {
5509 bounds = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f );
5510 } else {
5511 bounds[0].Set( -pm_bboxwidth.GetFloat() * 0.5f, -pm_bboxwidth.GetFloat() * 0.5f, 0 );
5512 bounds[1].Set( pm_bboxwidth.GetFloat() * 0.5f, pm_bboxwidth.GetFloat() * 0.5f, pm_normalheight.GetFloat() );
5513 }
5514 // the origin of the clip model needs to be set before calling SetClipModel
5515 // otherwise our physics object's current origin value gets reset to 0
5516 idClipModel *newClip;
5517 if ( pm_usecylinder.GetBool() ) {
5518 newClip = new idClipModel( idTraceModel( bounds, 8 ) );
5519 newClip->Translate( physicsObj.PlayerGetOrigin() );
5520 physicsObj.SetClipModel( newClip, 1.0f );
5521 } else {
5522 newClip = new idClipModel( idTraceModel( bounds ) );
5523 newClip->Translate( physicsObj.PlayerGetOrigin() );
5524 physicsObj.SetClipModel( newClip, 1.0f );
5525 }
5526 }
5527
5528 /*
5529 ==============
5530 idPlayer::UseVehicle
5531 ==============
5532 */
UseVehicle(void)5533 void idPlayer::UseVehicle( void ) {
5534 trace_t trace;
5535 idVec3 start, end;
5536 idEntity *ent;
5537
5538 if ( GetBindMaster() && GetBindMaster()->IsType( idAFEntity_Vehicle::Type ) ) {
5539 Show();
5540 static_cast<idAFEntity_Vehicle*>(GetBindMaster())->Use( this );
5541 } else {
5542 start = GetEyePosition();
5543 end = start + viewAngles.ToForward() * 80.0f;
5544 gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_RENDERMODEL, this );
5545 if ( trace.fraction < 1.0f ) {
5546 ent = gameLocal.entities[ trace.c.entityNum ];
5547 if ( ent && ent->IsType( idAFEntity_Vehicle::Type ) ) {
5548 Hide();
5549 static_cast<idAFEntity_Vehicle*>(ent)->Use( this );
5550 }
5551 }
5552 }
5553 }
5554
5555 /*
5556 ==============
5557 idPlayer::PerformImpulse
5558 ==============
5559 */
PerformImpulse(int impulse)5560 void idPlayer::PerformImpulse( int impulse ) {
5561
5562 if ( gameLocal.isClient ) {
5563 idBitMsg msg;
5564 byte msgBuf[MAX_EVENT_PARAM_SIZE];
5565
5566 assert( entityNumber == gameLocal.localClientNum );
5567 msg.Init( msgBuf, sizeof( msgBuf ) );
5568 msg.BeginWriting();
5569 msg.WriteBits( impulse, 6 );
5570 ClientSendEvent( EVENT_IMPULSE, &msg );
5571 }
5572
5573 if ( impulse >= IMPULSE_0 && impulse <= IMPULSE_12 ) {
5574 SelectWeapon( impulse, false );
5575 return;
5576 }
5577
5578 switch( impulse ) {
5579 case IMPULSE_13: {
5580 Reload();
5581 break;
5582 }
5583 case IMPULSE_14: {
5584 NextWeapon();
5585 break;
5586 }
5587 case IMPULSE_15: {
5588 PrevWeapon();
5589 break;
5590 }
5591 case IMPULSE_17: {
5592 if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
5593 gameLocal.mpGame.ToggleReady();
5594 }
5595 break;
5596 }
5597 case IMPULSE_18: {
5598 centerView.Init(gameLocal.time, 200, viewAngles.pitch, 0);
5599 break;
5600 }
5601 case IMPULSE_19: {
5602 // when we're not in single player, IMPULSE_19 is used for showScores
5603 // otherwise it opens the pda
5604 if ( !gameLocal.isMultiplayer ) {
5605 if ( objectiveSystemOpen ) {
5606 TogglePDA();
5607 } else if ( weapon_pda >= 0 ) {
5608 SelectWeapon( weapon_pda, true );
5609 }
5610 }
5611 break;
5612 }
5613 case IMPULSE_20: {
5614 if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
5615 gameLocal.mpGame.ToggleTeam();
5616 }
5617 break;
5618 }
5619 case IMPULSE_22: {
5620 if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
5621 gameLocal.mpGame.ToggleSpectate();
5622 }
5623 break;
5624 }
5625 case IMPULSE_28: {
5626 if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
5627 gameLocal.mpGame.CastVote( gameLocal.localClientNum, true );
5628 }
5629 break;
5630 }
5631 case IMPULSE_29: {
5632 if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
5633 gameLocal.mpGame.CastVote( gameLocal.localClientNum, false );
5634 }
5635 break;
5636 }
5637 case IMPULSE_40: {
5638 UseVehicle();
5639 break;
5640 }
5641 }
5642 }
5643
HandleESC(void)5644 bool idPlayer::HandleESC( void ) {
5645 if ( gameLocal.inCinematic ) {
5646 return SkipCinematic();
5647 }
5648
5649 if ( objectiveSystemOpen ) {
5650 TogglePDA();
5651 return true;
5652 }
5653
5654 return false;
5655 }
5656
SkipCinematic(void)5657 bool idPlayer::SkipCinematic( void ) {
5658 StartSound( "snd_skipcinematic", SND_CHANNEL_ANY, 0, false, NULL );
5659 return gameLocal.SkipCinematic();
5660 }
5661
5662 /*
5663 ==============
5664 idPlayer::EvaluateControls
5665 ==============
5666 */
EvaluateControls(void)5667 void idPlayer::EvaluateControls( void ) {
5668 // check for respawning
5669 if ( health <= 0 ) {
5670 if ( ( gameLocal.time > minRespawnTime ) && ( usercmd.buttons & BUTTON_ATTACK ) ) {
5671 forceRespawn = true;
5672 } else if ( gameLocal.time > maxRespawnTime ) {
5673 forceRespawn = true;
5674 }
5675 }
5676
5677 // in MP, idMultiplayerGame decides spawns
5678 if ( forceRespawn && !gameLocal.isMultiplayer && !g_testDeath.GetBool() ) {
5679 // in single player, we let the session handle restarting the level or loading a game
5680 gameLocal.sessionCommand = "died";
5681 }
5682
5683 if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) {
5684 PerformImpulse( usercmd.impulse );
5685 }
5686
5687 scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard );
5688
5689 oldFlags = usercmd.flags;
5690
5691 AdjustSpeed();
5692
5693 // update the viewangles
5694 UpdateViewAngles();
5695 }
5696
5697 /*
5698 ==============
5699 idPlayer::AdjustSpeed
5700 ==============
5701 */
AdjustSpeed(void)5702 void idPlayer::AdjustSpeed( void ) {
5703 float speed;
5704 float rate;
5705
5706 if ( spectating ) {
5707 speed = pm_spectatespeed.GetFloat();
5708 bobFrac = 0.0f;
5709 } else if ( noclip ) {
5710 speed = pm_noclipspeed.GetFloat();
5711 bobFrac = 0.0f;
5712 } else if ( !physicsObj.OnLadder() && ( usercmd.buttons & BUTTON_RUN ) && ( usercmd.forwardmove || usercmd.rightmove ) && ( usercmd.upmove >= 0 ) ) {
5713 if ( !gameLocal.isMultiplayer && !physicsObj.IsCrouching() && !PowerUpActive( ADRENALINE ) ) {
5714 stamina -= MS2SEC( gameLocal.msec );
5715 }
5716 if ( stamina < 0 ) {
5717 stamina = 0;
5718 }
5719 if ( ( !pm_stamina.GetFloat() ) || ( stamina > pm_staminathreshold.GetFloat() ) ) {
5720 bobFrac = 1.0f;
5721 } else if ( pm_staminathreshold.GetFloat() <= 0.0001f ) {
5722 bobFrac = 0.0f;
5723 } else {
5724 bobFrac = stamina / pm_staminathreshold.GetFloat();
5725 }
5726 speed = pm_walkspeed.GetFloat() * ( 1.0f - bobFrac ) + pm_runspeed.GetFloat() * bobFrac;
5727 } else {
5728 rate = pm_staminarate.GetFloat();
5729
5730 // increase 25% faster when not moving
5731 if ( ( usercmd.forwardmove == 0 ) && ( usercmd.rightmove == 0 ) && ( !physicsObj.OnLadder() || ( usercmd.upmove == 0 ) ) ) {
5732 rate *= 1.25f;
5733 }
5734
5735 stamina += rate * MS2SEC( gameLocal.msec );
5736 if ( stamina > pm_stamina.GetFloat() ) {
5737 stamina = pm_stamina.GetFloat();
5738 }
5739 speed = pm_walkspeed.GetFloat();
5740 bobFrac = 0.0f;
5741 }
5742
5743 speed *= PowerUpModifier(SPEED);
5744
5745 if ( influenceActive == INFLUENCE_LEVEL3 ) {
5746 speed *= 0.33f;
5747 }
5748
5749 physicsObj.SetSpeed( speed, pm_crouchspeed.GetFloat() );
5750 }
5751
5752 /*
5753 ==============
5754 idPlayer::AdjustBodyAngles
5755 ==============
5756 */
AdjustBodyAngles(void)5757 void idPlayer::AdjustBodyAngles( void ) {
5758 idMat3 lookAxis;
5759 idMat3 legsAxis;
5760 bool blend;
5761 float diff;
5762 float frac;
5763 float upBlend;
5764 float forwardBlend;
5765 float downBlend;
5766
5767 if ( health < 0 ) {
5768 return;
5769 }
5770
5771 blend = true;
5772
5773 if ( !physicsObj.HasGroundContacts() ) {
5774 idealLegsYaw = 0.0f;
5775 legsForward = true;
5776 } else if ( usercmd.forwardmove < 0 ) {
5777 idealLegsYaw = idMath::AngleNormalize180( idVec3( -usercmd.forwardmove, usercmd.rightmove, 0.0f ).ToYaw() );
5778 legsForward = false;
5779 } else if ( usercmd.forwardmove > 0 ) {
5780 idealLegsYaw = idMath::AngleNormalize180( idVec3( usercmd.forwardmove, -usercmd.rightmove, 0.0f ).ToYaw() );
5781 legsForward = true;
5782 } else if ( ( usercmd.rightmove != 0 ) && physicsObj.IsCrouching() ) {
5783 if ( !legsForward ) {
5784 idealLegsYaw = idMath::AngleNormalize180( idVec3( idMath::Abs( usercmd.rightmove ), usercmd.rightmove, 0.0f ).ToYaw() );
5785 } else {
5786 idealLegsYaw = idMath::AngleNormalize180( idVec3( idMath::Abs( usercmd.rightmove ), -usercmd.rightmove, 0.0f ).ToYaw() );
5787 }
5788 } else if ( usercmd.rightmove != 0 ) {
5789 idealLegsYaw = 0.0f;
5790 legsForward = true;
5791 } else {
5792 legsForward = true;
5793 diff = idMath::Fabs( idealLegsYaw - legsYaw );
5794 idealLegsYaw = idealLegsYaw - idMath::AngleNormalize180( viewAngles.yaw - oldViewYaw );
5795 if ( diff < 0.1f ) {
5796 legsYaw = idealLegsYaw;
5797 blend = false;
5798 }
5799 }
5800
5801 if ( !physicsObj.IsCrouching() ) {
5802 legsForward = true;
5803 }
5804
5805 oldViewYaw = viewAngles.yaw;
5806
5807 AI_TURN_LEFT = false;
5808 AI_TURN_RIGHT = false;
5809 if ( idealLegsYaw < -45.0f ) {
5810 idealLegsYaw = 0;
5811 AI_TURN_RIGHT = true;
5812 blend = true;
5813 } else if ( idealLegsYaw > 45.0f ) {
5814 idealLegsYaw = 0;
5815 AI_TURN_LEFT = true;
5816 blend = true;
5817 }
5818
5819 if ( blend ) {
5820 legsYaw = legsYaw * 0.9f + idealLegsYaw * 0.1f;
5821 }
5822 legsAxis = idAngles( 0.0f, legsYaw, 0.0f ).ToMat3();
5823 animator.SetJointAxis( hipJoint, JOINTMOD_WORLD, legsAxis );
5824
5825 // calculate the blending between down, straight, and up
5826 frac = viewAngles.pitch / 90.0f;
5827 if ( frac > 0.0f ) {
5828 downBlend = frac;
5829 forwardBlend = 1.0f - frac;
5830 upBlend = 0.0f;
5831 } else {
5832 downBlend = 0.0f;
5833 forwardBlend = 1.0f + frac;
5834 upBlend = -frac;
5835 }
5836
5837 animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, downBlend );
5838 animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, forwardBlend );
5839 animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 2, upBlend );
5840
5841 animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, downBlend );
5842 animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, forwardBlend );
5843 animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 2, upBlend );
5844 }
5845
5846 /*
5847 ==============
5848 idPlayer::InitAASLocation
5849 ==============
5850 */
InitAASLocation(void)5851 void idPlayer::InitAASLocation( void ) {
5852 int i;
5853 int num;
5854 idVec3 size;
5855 idBounds bounds;
5856 idAAS *aas;
5857 idVec3 origin;
5858
5859 GetFloorPos( 64.0f, origin );
5860
5861 num = gameLocal.NumAAS();
5862 aasLocation.SetGranularity( 1 );
5863 aasLocation.SetNum( num );
5864 for( i = 0; i < aasLocation.Num(); i++ ) {
5865 aasLocation[ i ].areaNum = 0;
5866 aasLocation[ i ].pos = origin;
5867 aas = gameLocal.GetAAS( i );
5868 if ( aas && aas->GetSettings() ) {
5869 size = aas->GetSettings()->boundingBoxes[0][1];
5870 bounds[0] = -size;
5871 size.z = 32.0f;
5872 bounds[1] = size;
5873
5874 aasLocation[ i ].areaNum = aas->PointReachableAreaNum( origin, bounds, AREA_REACHABLE_WALK );
5875 }
5876 }
5877 }
5878
5879 /*
5880 ==============
5881 idPlayer::SetAASLocation
5882 ==============
5883 */
SetAASLocation(void)5884 void idPlayer::SetAASLocation( void ) {
5885 int i;
5886 int areaNum;
5887 idVec3 size;
5888 idBounds bounds;
5889 idAAS *aas;
5890 idVec3 origin;
5891
5892 if ( !GetFloorPos( 64.0f, origin ) ) {
5893 return;
5894 }
5895
5896 for( i = 0; i < aasLocation.Num(); i++ ) {
5897 aas = gameLocal.GetAAS( i );
5898 if ( !aas ) {
5899 continue;
5900 }
5901
5902 size = aas->GetSettings()->boundingBoxes[0][1];
5903 bounds[0] = -size;
5904 size.z = 32.0f;
5905 bounds[1] = size;
5906
5907 areaNum = aas->PointReachableAreaNum( origin, bounds, AREA_REACHABLE_WALK );
5908 if ( areaNum ) {
5909 aasLocation[ i ].pos = origin;
5910 aasLocation[ i ].areaNum = areaNum;
5911 }
5912 }
5913 }
5914
5915 /*
5916 ==============
5917 idPlayer::GetAASLocation
5918 ==============
5919 */
GetAASLocation(idAAS * aas,idVec3 & pos,int & areaNum) const5920 void idPlayer::GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const {
5921 int i;
5922
5923 if ( aas != NULL ) {
5924 for( i = 0; i < aasLocation.Num(); i++ ) {
5925 if ( aas == gameLocal.GetAAS( i ) ) {
5926 areaNum = aasLocation[ i ].areaNum;
5927 pos = aasLocation[ i ].pos;
5928 return;
5929 }
5930 }
5931 }
5932
5933 areaNum = 0;
5934 pos = physicsObj.GetOrigin();
5935 }
5936
5937 /*
5938 ==============
5939 idPlayer::Move
5940 ==============
5941 */
Move(void)5942 void idPlayer::Move( void ) {
5943 float newEyeOffset;
5944 idVec3 oldOrigin;
5945 idVec3 oldVelocity;
5946 idVec3 pushVelocity;
5947
5948 // save old origin and velocity for crashlanding
5949 oldOrigin = physicsObj.GetOrigin();
5950 oldVelocity = physicsObj.GetLinearVelocity();
5951 pushVelocity = physicsObj.GetPushedLinearVelocity();
5952
5953 // set physics variables
5954 physicsObj.SetMaxStepHeight( pm_stepsize.GetFloat() );
5955 physicsObj.SetMaxJumpHeight( pm_jumpheight.GetFloat() );
5956
5957 if ( noclip ) {
5958 physicsObj.SetContents( 0 );
5959 physicsObj.SetMovementType( PM_NOCLIP );
5960 } else if ( spectating ) {
5961 physicsObj.SetContents( 0 );
5962 physicsObj.SetMovementType( PM_SPECTATOR );
5963 } else if ( health <= 0 ) {
5964 physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP );
5965 physicsObj.SetMovementType( PM_DEAD );
5966 } else if ( gameLocal.inCinematic || gameLocal.GetCamera() || privateCameraView || ( influenceActive == INFLUENCE_LEVEL2 ) ) {
5967 physicsObj.SetContents( CONTENTS_BODY );
5968 physicsObj.SetMovementType( PM_FREEZE );
5969 } else {
5970 physicsObj.SetContents( CONTENTS_BODY );
5971 physicsObj.SetMovementType( PM_NORMAL );
5972 }
5973
5974 if ( spectating ) {
5975 physicsObj.SetClipMask( MASK_DEADSOLID );
5976 } else if ( health <= 0 ) {
5977 physicsObj.SetClipMask( MASK_DEADSOLID );
5978 } else {
5979 physicsObj.SetClipMask( MASK_PLAYERSOLID );
5980 }
5981
5982 physicsObj.SetDebugLevel( g_debugMove.GetBool() );
5983 physicsObj.SetPlayerInput( usercmd, viewAngles );
5984
5985 // FIXME: physics gets disabled somehow
5986 BecomeActive( TH_PHYSICS );
5987 RunPhysics();
5988
5989 // update our last valid AAS location for the AI
5990 SetAASLocation();
5991
5992 if ( spectating ) {
5993 newEyeOffset = 0.0f;
5994 } else if ( health <= 0 ) {
5995 newEyeOffset = pm_deadviewheight.GetFloat();
5996 } else if ( physicsObj.IsCrouching() ) {
5997 newEyeOffset = pm_crouchviewheight.GetFloat();
5998 } else if ( GetBindMaster() && GetBindMaster()->IsType( idAFEntity_Vehicle::Type ) ) {
5999 newEyeOffset = 0.0f;
6000 } else {
6001 newEyeOffset = pm_normalviewheight.GetFloat();
6002 }
6003
6004 if ( EyeHeight() != newEyeOffset ) {
6005 if ( spectating ) {
6006 SetEyeHeight( newEyeOffset );
6007 } else {
6008 // smooth out duck height changes
6009 SetEyeHeight( EyeHeight() * pm_crouchrate.GetFloat() + newEyeOffset * ( 1.0f - pm_crouchrate.GetFloat() ) );
6010 }
6011 }
6012
6013 if ( noclip || gameLocal.inCinematic || ( influenceActive == INFLUENCE_LEVEL2 ) ) {
6014 AI_CROUCH = false;
6015 AI_ONGROUND = ( influenceActive == INFLUENCE_LEVEL2 );
6016 AI_ONLADDER = false;
6017 AI_JUMP = false;
6018 } else {
6019 AI_CROUCH = physicsObj.IsCrouching();
6020 AI_ONGROUND = physicsObj.HasGroundContacts();
6021 AI_ONLADDER = physicsObj.OnLadder();
6022 AI_JUMP = physicsObj.HasJumped();
6023
6024 // check if we're standing on top of a monster and give a push if we are
6025 idEntity *groundEnt = physicsObj.GetGroundEntity();
6026 if ( groundEnt && groundEnt->IsType( idAI::Type ) ) {
6027 idVec3 vel = physicsObj.GetLinearVelocity();
6028 if ( vel.ToVec2().LengthSqr() < 0.1f ) {
6029 vel.ToVec2() = physicsObj.GetOrigin().ToVec2() - groundEnt->GetPhysics()->GetAbsBounds().GetCenter().ToVec2();
6030 vel.ToVec2().NormalizeFast();
6031 vel.ToVec2() *= pm_walkspeed.GetFloat();
6032 } else {
6033 // give em a push in the direction they're going
6034 vel *= 1.1f;
6035 }
6036 physicsObj.SetLinearVelocity( vel );
6037 }
6038 }
6039
6040 if ( AI_JUMP ) {
6041 // bounce the view weapon
6042 loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)];
6043 currentLoggedAccel++;
6044 acc->time = gameLocal.time;
6045 acc->dir[2] = 200;
6046 acc->dir[0] = acc->dir[1] = 0;
6047 }
6048
6049 if ( AI_ONLADDER ) {
6050 int old_rung = oldOrigin.z / LADDER_RUNG_DISTANCE;
6051 int new_rung = physicsObj.GetOrigin().z / LADDER_RUNG_DISTANCE;
6052
6053 if ( old_rung != new_rung ) {
6054 StartSound( "snd_stepladder", SND_CHANNEL_ANY, 0, false, NULL );
6055 }
6056 }
6057
6058 BobCycle( pushVelocity );
6059 CrashLand( oldOrigin, oldVelocity );
6060 }
6061
6062 /*
6063 ==============
6064 idPlayer::UpdateHud
6065 ==============
6066 */
UpdateHud(void)6067 void idPlayer::UpdateHud( void ) {
6068 idPlayer *aimed;
6069
6070 if ( !hud ) {
6071 return;
6072 }
6073
6074 if ( entityNumber != gameLocal.localClientNum ) {
6075 return;
6076 }
6077
6078 int c = inventory.pickupItemNames.Num();
6079 if ( c > 0 ) {
6080 if ( gameLocal.time > inventory.nextItemPickup ) {
6081 if ( inventory.nextItemPickup && gameLocal.time - inventory.nextItemPickup > 2000 ) {
6082 inventory.nextItemNum = 1;
6083 }
6084 int i;
6085 for ( i = 0; i < 5 && i < c; i++ ) {
6086 hud->SetStateString( va( "itemtext%i", inventory.nextItemNum ), inventory.pickupItemNames[0].name );
6087 hud->SetStateString( va( "itemicon%i", inventory.nextItemNum ), inventory.pickupItemNames[0].icon );
6088 hud->HandleNamedEvent( va( "itemPickup%i", inventory.nextItemNum++ ) );
6089 inventory.pickupItemNames.RemoveIndex( 0 );
6090 if (inventory.nextItemNum == 1 ) {
6091 inventory.onePickupTime = gameLocal.time;
6092 } else if ( inventory.nextItemNum > 5 ) {
6093 inventory.nextItemNum = 1;
6094 inventory.nextItemPickup = inventory.onePickupTime + 2000;
6095 } else {
6096 inventory.nextItemPickup = gameLocal.time + 400;
6097 }
6098 }
6099 }
6100 }
6101
6102 if ( gameLocal.realClientTime == lastMPAimTime ) {
6103 if ( MPAim != -1 && gameLocal.gameType == GAME_TDM
6104 && gameLocal.entities[ MPAim ] && gameLocal.entities[ MPAim ]->IsType( idPlayer::Type )
6105 && static_cast< idPlayer * >( gameLocal.entities[ MPAim ] )->team == team ) {
6106 aimed = static_cast< idPlayer * >( gameLocal.entities[ MPAim ] );
6107 hud->SetStateString( "aim_text", gameLocal.userInfo[ MPAim ].GetString( "ui_name" ) );
6108 hud->SetStateFloat( "aim_color", aimed->colorBarIndex );
6109 hud->HandleNamedEvent( "aim_flash" );
6110 MPAimHighlight = true;
6111 MPAimFadeTime = 0; // no fade till loosing focus
6112 } else if ( MPAimHighlight ) {
6113 hud->HandleNamedEvent( "aim_fade" );
6114 MPAimFadeTime = gameLocal.realClientTime;
6115 MPAimHighlight = false;
6116 }
6117 }
6118 if ( MPAimFadeTime ) {
6119 assert( !MPAimHighlight );
6120 if ( gameLocal.realClientTime - MPAimFadeTime > 2000 ) {
6121 MPAimFadeTime = 0;
6122 }
6123 }
6124
6125 hud->SetStateInt( "g_showProjectilePct", g_showProjectilePct.GetInteger() );
6126 if ( numProjectilesFired ) {
6127 hud->SetStateString( "projectilepct", va( "Hit %% %.1f", ( (float) numProjectileHits / numProjectilesFired ) * 100 ) );
6128 } else {
6129 hud->SetStateString( "projectilepct", "Hit % 0.0" );
6130 }
6131
6132 if ( isLagged && gameLocal.isMultiplayer && gameLocal.localClientNum == entityNumber ) {
6133 hud->SetStateString( "hudLag", "1" );
6134 } else {
6135 hud->SetStateString( "hudLag", "0" );
6136 }
6137 }
6138
6139 /*
6140 ==============
6141 idPlayer::UpdateDeathSkin
6142 ==============
6143 */
UpdateDeathSkin(bool state_hitch)6144 void idPlayer::UpdateDeathSkin( bool state_hitch ) {
6145 if ( !( gameLocal.isMultiplayer || g_testDeath.GetBool() ) ) {
6146 return;
6147 }
6148 if ( health <= 0 ) {
6149 if ( !doingDeathSkin ) {
6150 deathClearContentsTime = spawnArgs.GetInt( "deathSkinTime" );
6151 doingDeathSkin = true;
6152 renderEntity.noShadow = true;
6153 if ( state_hitch ) {
6154 renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f - 2.0f;
6155 } else {
6156 renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f;
6157 }
6158 UpdateVisuals();
6159 }
6160
6161 // wait a bit before switching off the content
6162 if ( deathClearContentsTime && gameLocal.time > deathClearContentsTime ) {
6163 SetCombatContents( false );
6164 deathClearContentsTime = 0;
6165 }
6166 } else {
6167 renderEntity.noShadow = false;
6168 renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f;
6169 UpdateVisuals();
6170 doingDeathSkin = false;
6171 }
6172 }
6173
6174 /*
6175 ==============
6176 idPlayer::StartFxOnBone
6177 ==============
6178 */
StartFxOnBone(const char * fx,const char * bone)6179 void idPlayer::StartFxOnBone( const char *fx, const char *bone ) {
6180 idVec3 offset;
6181 idMat3 axis;
6182 jointHandle_t jointHandle = GetAnimator()->GetJointHandle( bone );
6183
6184 if ( jointHandle == INVALID_JOINT ) {
6185 gameLocal.Printf( "Cannot find bone %s\n", bone );
6186 return;
6187 }
6188
6189 if ( GetAnimator()->GetJointTransform( jointHandle, gameLocal.time, offset, axis ) ) {
6190 offset = GetPhysics()->GetOrigin() + offset * GetPhysics()->GetAxis();
6191 axis = axis * GetPhysics()->GetAxis();
6192 }
6193
6194 idEntityFx::StartFx( fx, &offset, &axis, this, true );
6195 }
6196
6197 /*
6198 ==============
6199 idPlayer::Think
6200
6201 Called every tic for each player
6202 ==============
6203 */
Think(void)6204 void idPlayer::Think( void ) {
6205 renderEntity_t *headRenderEnt;
6206
6207 UpdatePlayerIcons();
6208
6209 // latch button actions
6210 oldButtons = usercmd.buttons;
6211
6212 // grab out usercmd
6213 usercmd_t oldCmd = usercmd;
6214 usercmd = gameLocal.usercmds[ entityNumber ];
6215 buttonMask &= usercmd.buttons;
6216 usercmd.buttons &= ~buttonMask;
6217
6218 if ( gameLocal.inCinematic && gameLocal.skipCinematic ) {
6219 return;
6220 }
6221
6222 // clear the ik before we do anything else so the skeleton doesn't get updated twice
6223 walkIK.ClearJointMods();
6224
6225 // if this is the very first frame of the map, set the delta view angles
6226 // based on the usercmd angles
6227 if ( !spawnAnglesSet && ( gameLocal.GameState() != GAMESTATE_STARTUP ) ) {
6228 spawnAnglesSet = true;
6229 SetViewAngles( spawnAngles );
6230 oldFlags = usercmd.flags;
6231 }
6232
6233 if ( objectiveSystemOpen || gameLocal.inCinematic || influenceActive ) {
6234 if ( objectiveSystemOpen && AI_PAIN ) {
6235 TogglePDA();
6236 }
6237 usercmd.forwardmove = 0;
6238 usercmd.rightmove = 0;
6239 usercmd.upmove = 0;
6240 }
6241
6242 // log movement changes for weapon bobbing effects
6243 if ( usercmd.forwardmove != oldCmd.forwardmove ) {
6244 loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)];
6245 currentLoggedAccel++;
6246 acc->time = gameLocal.time;
6247 acc->dir[0] = usercmd.forwardmove - oldCmd.forwardmove;
6248 acc->dir[1] = acc->dir[2] = 0;
6249 }
6250
6251 if ( usercmd.rightmove != oldCmd.rightmove ) {
6252 loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)];
6253 currentLoggedAccel++;
6254 acc->time = gameLocal.time;
6255 acc->dir[1] = usercmd.rightmove - oldCmd.rightmove;
6256 acc->dir[0] = acc->dir[2] = 0;
6257 }
6258
6259 // freelook centering
6260 if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_MLOOK ) {
6261 centerView.Init( gameLocal.time, 200, viewAngles.pitch, 0 );
6262 }
6263
6264 // zooming
6265 if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_ZOOM ) {
6266 if ( ( usercmd.buttons & BUTTON_ZOOM ) && weapon.GetEntity() ) {
6267 zoomFov.Init( gameLocal.time, 200.0f, CalcFov( false ), weapon.GetEntity()->GetZoomFov() );
6268 } else {
6269 zoomFov.Init( gameLocal.time, 200.0f, zoomFov.GetCurrentValue( gameLocal.time ), DefaultFov() );
6270 }
6271 }
6272
6273 // if we have an active gui, we will unrotate the view angles as
6274 // we turn the mouse movements into gui events
6275 idUserInterface *gui = ActiveGui();
6276 if ( gui && gui != focusUI ) {
6277 RouteGuiMouse( gui );
6278 }
6279
6280 // set the push velocity on the weapon before running the physics
6281 if ( weapon.GetEntity() ) {
6282 weapon.GetEntity()->SetPushVelocity( physicsObj.GetPushedLinearVelocity() );
6283 }
6284
6285 EvaluateControls();
6286
6287 if ( !af.IsActive() ) {
6288 AdjustBodyAngles();
6289 CopyJointsFromBodyToHead();
6290 }
6291
6292 Move();
6293
6294 if ( !g_stopTime.GetBool() ) {
6295
6296 if ( !noclip && !spectating && ( health > 0 ) && !IsHidden() ) {
6297 TouchTriggers();
6298 }
6299
6300 // not done on clients for various reasons. don't do it on server and save the sound channel for other things
6301 if ( !gameLocal.isMultiplayer ) {
6302 SetCurrentHeartRate();
6303 float scale = g_damageScale.GetFloat();
6304 if ( g_useDynamicProtection.GetBool() && scale < 1.0f && gameLocal.time - lastDmgTime > 500 ) {
6305 if ( scale < 1.0f ) {
6306 scale += 0.05f;
6307 }
6308 if ( scale > 1.0f ) {
6309 scale = 1.0f;
6310 }
6311 g_damageScale.SetFloat( scale );
6312 }
6313 }
6314
6315 // update GUIs, Items, and character interactions
6316 UpdateFocus();
6317
6318 UpdateLocation();
6319
6320 // update player script
6321 UpdateScript();
6322
6323 // service animations
6324 if ( !spectating && !af.IsActive() && !gameLocal.inCinematic ) {
6325 UpdateConditions();
6326 UpdateAnimState();
6327 CheckBlink();
6328 }
6329
6330 // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think
6331 AI_PAIN = false;
6332 }
6333
6334 // calculate the exact bobbed view position, which is used to
6335 // position the view weapon, among other things
6336 CalculateFirstPersonView();
6337
6338 // this may use firstPersonView, or a thirdPeroson / camera view
6339 CalculateRenderView();
6340
6341 inventory.UpdateArmor();
6342
6343 if ( spectating ) {
6344 UpdateSpectating();
6345 } else if ( health > 0 ) {
6346 UpdateWeapon();
6347 }
6348
6349 UpdateAir();
6350
6351 UpdateHud();
6352
6353 UpdatePowerUps();
6354
6355 UpdateDeathSkin( false );
6356
6357 if ( gameLocal.isMultiplayer ) {
6358 DrawPlayerIcons();
6359 }
6360
6361 if ( head.GetEntity() ) {
6362 headRenderEnt = head.GetEntity()->GetRenderEntity();
6363 } else {
6364 headRenderEnt = NULL;
6365 }
6366
6367 if ( headRenderEnt ) {
6368 if ( influenceSkin ) {
6369 headRenderEnt->customSkin = influenceSkin;
6370 } else {
6371 headRenderEnt->customSkin = NULL;
6372 }
6373 }
6374
6375 if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() ) {
6376 renderEntity.suppressShadowInViewID = 0;
6377 if ( headRenderEnt ) {
6378 headRenderEnt->suppressShadowInViewID = 0;
6379 }
6380 } else {
6381 renderEntity.suppressShadowInViewID = entityNumber+1;
6382 if ( headRenderEnt ) {
6383 headRenderEnt->suppressShadowInViewID = entityNumber+1;
6384 }
6385 }
6386 // never cast shadows from our first-person muzzle flashes
6387 renderEntity.suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber;
6388 if ( headRenderEnt ) {
6389 headRenderEnt->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber;
6390 }
6391
6392 if ( !g_stopTime.GetBool() ) {
6393 UpdateAnimation();
6394
6395 Present();
6396
6397 UpdateDamageEffects();
6398
6399 LinkCombat();
6400
6401 playerView.CalculateShake();
6402 }
6403
6404 if ( !( thinkFlags & TH_THINK ) ) {
6405 gameLocal.Printf( "player %d not thinking?\n", entityNumber );
6406 }
6407
6408 if ( g_showEnemies.GetBool() ) {
6409 idActor *ent;
6410 int num = 0;
6411 for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) {
6412 gameLocal.Printf( "enemy (%d)'%s'\n", ent->entityNumber, ent->name.c_str() );
6413 gameRenderWorld->DebugBounds( colorRed, ent->GetPhysics()->GetBounds().Expand( 2 ), ent->GetPhysics()->GetOrigin() );
6414 num++;
6415 }
6416 gameLocal.Printf( "%d: enemies\n", num );
6417 }
6418 }
6419
6420 /*
6421 =================
6422 idPlayer::RouteGuiMouse
6423 =================
6424 */
RouteGuiMouse(idUserInterface * gui)6425 void idPlayer::RouteGuiMouse( idUserInterface *gui ) {
6426 sysEvent_t ev;
6427
6428 if ( usercmd.mx != oldMouseX || usercmd.my != oldMouseY ) {
6429 ev = sys->GenerateMouseMoveEvent( usercmd.mx - oldMouseX, usercmd.my - oldMouseY );
6430 gui->HandleEvent( &ev, gameLocal.time );
6431 oldMouseX = usercmd.mx;
6432 oldMouseY = usercmd.my;
6433 }
6434 }
6435
6436 /*
6437 ==================
6438 idPlayer::LookAtKiller
6439 ==================
6440 */
LookAtKiller(idEntity * inflictor,idEntity * attacker)6441 void idPlayer::LookAtKiller( idEntity *inflictor, idEntity *attacker ) {
6442 idVec3 dir;
6443
6444 if ( attacker && attacker != this ) {
6445 dir = attacker->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
6446 } else if ( inflictor && inflictor != this ) {
6447 dir = inflictor->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
6448 } else {
6449 dir = viewAxis[ 0 ];
6450 }
6451
6452 idAngles ang( 0, dir.ToYaw(), 0 );
6453 SetViewAngles( ang );
6454 }
6455
6456 /*
6457 ==============
6458 idPlayer::Kill
6459 ==============
6460 */
Kill(bool delayRespawn,bool nodamage)6461 void idPlayer::Kill( bool delayRespawn, bool nodamage ) {
6462 if ( spectating ) {
6463 SpectateFreeFly( false );
6464 } else if ( health > 0 ) {
6465 godmode = false;
6466 if ( nodamage ) {
6467 ServerSpectate( true );
6468 forceRespawn = true;
6469 } else {
6470 Damage( this, this, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT );
6471 if ( delayRespawn ) {
6472 forceRespawn = false;
6473 int delay = spawnArgs.GetFloat( "respawn_delay" );
6474 minRespawnTime = gameLocal.time + SEC2MS( delay );
6475 maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME;
6476 }
6477 }
6478 }
6479 }
6480
6481 /*
6482 ==================
6483 idPlayer::Killed
6484 ==================
6485 */
Killed(idEntity * inflictor,idEntity * attacker,int damage,const idVec3 & dir,int location)6486 void idPlayer::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
6487 float delay;
6488
6489 assert( !gameLocal.isClient );
6490
6491 // stop taking knockback once dead
6492 fl.noknockback = true;
6493 if ( health < -999 ) {
6494 health = -999;
6495 }
6496
6497 if ( AI_DEAD ) {
6498 AI_PAIN = true;
6499 return;
6500 }
6501
6502 heartInfo.Init( 0, 0, 0, BASE_HEARTRATE );
6503 AdjustHeartRate( DEAD_HEARTRATE, 10.0f, 0.0f, true );
6504
6505 if ( !g_testDeath.GetBool() ) {
6506 playerView.Fade( colorBlack, 12000 );
6507 }
6508
6509 AI_DEAD = true;
6510 SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 4 );
6511 SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 4 );
6512 SetWaitState( "" );
6513
6514 animator.ClearAllJoints();
6515
6516 if ( StartRagdoll() ) {
6517 pm_modelView.SetInteger( 0 );
6518 minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME;
6519 maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME;
6520 } else {
6521 // don't allow respawn until the death anim is done
6522 // g_forcerespawn may force spawning at some later time
6523 delay = spawnArgs.GetFloat( "respawn_delay" );
6524 minRespawnTime = gameLocal.time + SEC2MS( delay );
6525 maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME;
6526 }
6527
6528 physicsObj.SetMovementType( PM_DEAD );
6529 StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
6530 StopSound( SND_CHANNEL_BODY2, false );
6531
6532 fl.takedamage = true; // can still be gibbed
6533
6534 // get rid of weapon
6535 weapon.GetEntity()->OwnerDied();
6536
6537 // drop the weapon as an item
6538 DropWeapon( true );
6539
6540 if ( !g_testDeath.GetBool() ) {
6541 LookAtKiller( inflictor, attacker );
6542 }
6543
6544 if ( gameLocal.isMultiplayer || g_testDeath.GetBool() ) {
6545 idPlayer *killer = NULL;
6546 // no gibbing in MP. Event_Gib will early out in MP
6547 if ( attacker->IsType( idPlayer::Type ) ) {
6548 killer = static_cast<idPlayer*>(attacker);
6549 if ( health < -20 || killer->PowerUpActive( BERSERK ) ) {
6550 gibDeath = true;
6551 gibsDir = dir;
6552 gibsLaunched = false;
6553 }
6554 }
6555 gameLocal.mpGame.PlayerDeath( this, killer, isTelefragged );
6556 } else {
6557 physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP );
6558 }
6559
6560 ClearPowerUps();
6561
6562 UpdateVisuals();
6563
6564 isChatting = false;
6565 }
6566
6567 /*
6568 =====================
6569 idPlayer::GetAIAimTargets
6570
6571 Returns positions for the AI to aim at.
6572 =====================
6573 */
GetAIAimTargets(const idVec3 & lastSightPos,idVec3 & headPos,idVec3 & chestPos)6574 void idPlayer::GetAIAimTargets( const idVec3 &lastSightPos, idVec3 &headPos, idVec3 &chestPos ) {
6575 idVec3 offset;
6576 idMat3 axis;
6577 idVec3 origin;
6578
6579 origin = lastSightPos - physicsObj.GetOrigin();
6580
6581 GetJointWorldTransform( chestJoint, gameLocal.time, offset, axis );
6582 headPos = offset + origin;
6583
6584 GetJointWorldTransform( headJoint, gameLocal.time, offset, axis );
6585 chestPos = offset + origin;
6586 }
6587
6588 /*
6589 ================
6590 idPlayer::DamageFeedback
6591
6592 callback function for when another entity received damage from this entity. damage can be adjusted and returned to the caller.
6593 ================
6594 */
DamageFeedback(idEntity * victim,idEntity * inflictor,int & damage)6595 void idPlayer::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) {
6596 assert( !gameLocal.isClient );
6597 damage *= PowerUpModifier( BERSERK );
6598 if ( damage && ( victim != this ) && victim->IsType( idActor::Type ) ) {
6599 SetLastHitTime( gameLocal.time );
6600 }
6601 }
6602
6603 /*
6604 =================
6605 idPlayer::CalcDamagePoints
6606
6607 Calculates how many health and armor points will be inflicted, but
6608 doesn't actually do anything with them. This is used to tell when an attack
6609 would have killed the player, possibly allowing a "saving throw"
6610 =================
6611 */
CalcDamagePoints(idEntity * inflictor,idEntity * attacker,const idDict * damageDef,const float damageScale,const int location,int * health,int * armor)6612 void idPlayer::CalcDamagePoints( idEntity *inflictor, idEntity *attacker, const idDict *damageDef,
6613 const float damageScale, const int location, int *health, int *armor ) {
6614 int damage;
6615 int armorSave;
6616
6617 damageDef->GetInt( "damage", "20", damage );
6618 damage = GetDamageForLocation( damage, location );
6619
6620 idPlayer *player = attacker->IsType( idPlayer::Type ) ? static_cast<idPlayer*>(attacker) : NULL;
6621 if ( !gameLocal.isMultiplayer ) {
6622 if ( inflictor != gameLocal.world ) {
6623 switch ( g_skill.GetInteger() ) {
6624 case 0:
6625 damage *= 0.80f;
6626 if ( damage < 1 ) {
6627 damage = 1;
6628 }
6629 break;
6630 case 2:
6631 damage *= 1.70f;
6632 break;
6633 case 3:
6634 damage *= 3.5f;
6635 break;
6636 default:
6637 break;
6638 }
6639 }
6640 }
6641
6642 damage *= damageScale;
6643
6644 // always give half damage if hurting self
6645 if ( attacker == this ) {
6646 if ( gameLocal.isMultiplayer ) {
6647 // only do this in mp so single player plasma and rocket splash is very dangerous in close quarters
6648 damage *= damageDef->GetFloat( "selfDamageScale", "0.5" );
6649 } else {
6650 damage *= damageDef->GetFloat( "selfDamageScale", "1" );
6651 }
6652 }
6653
6654 // check for completely getting out of the damage
6655 if ( !damageDef->GetBool( "noGod" ) ) {
6656 // check for godmode
6657 if ( godmode ) {
6658 damage = 0;
6659 }
6660 }
6661
6662 // inform the attacker that they hit someone
6663 attacker->DamageFeedback( this, inflictor, damage );
6664
6665 // save some from armor
6666 if ( !damageDef->GetBool( "noArmor" ) ) {
6667 float armor_protection;
6668
6669 armor_protection = ( gameLocal.isMultiplayer ) ? g_armorProtectionMP.GetFloat() : g_armorProtection.GetFloat();
6670
6671 armorSave = ceil( damage * armor_protection );
6672 if ( armorSave >= inventory.armor ) {
6673 armorSave = inventory.armor;
6674 }
6675
6676 if ( !damage ) {
6677 armorSave = 0;
6678 } else if ( armorSave >= damage ) {
6679 armorSave = damage - 1;
6680 damage = 1;
6681 } else {
6682 damage -= armorSave;
6683 }
6684 } else {
6685 armorSave = 0;
6686 }
6687
6688 // check for team damage
6689 if ( gameLocal.gameType == GAME_TDM
6690 && !gameLocal.serverInfo.GetBool( "si_teamDamage" )
6691 && !damageDef->GetBool( "noTeam" )
6692 && player
6693 && player != this // you get self damage no matter what
6694 && player->team == team ) {
6695 damage = 0;
6696 }
6697
6698 *health = damage;
6699 *armor = armorSave;
6700 }
6701
6702 /*
6703 ============
6704 Damage
6705
6706 this entity that is being damaged
6707 inflictor entity that is causing the damage
6708 attacker entity that caused the inflictor to damage targ
6709 example: this=monster, inflictor=rocket, attacker=player
6710
6711 dir direction of the attack for knockback in global space
6712
6713 damageDef an idDict with all the options for damage effects
6714
6715 inflictor, attacker, dir, and point can be NULL for environmental effects
6716 ============
6717 */
Damage(idEntity * inflictor,idEntity * attacker,const idVec3 & dir,const char * damageDefName,const float damageScale,const int location)6718 void idPlayer::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir,
6719 const char *damageDefName, const float damageScale, const int location ) {
6720 idVec3 kick;
6721 int damage;
6722 int armorSave;
6723 int knockback;
6724 idVec3 damage_from;
6725 idVec3 localDamageVector;
6726 float attackerPushScale;
6727
6728 // damage is only processed on server
6729 if ( gameLocal.isClient ) {
6730 return;
6731 }
6732
6733 if ( !fl.takedamage || noclip || spectating || gameLocal.inCinematic ) {
6734 return;
6735 }
6736
6737 if ( !inflictor ) {
6738 inflictor = gameLocal.world;
6739 }
6740 if ( !attacker ) {
6741 attacker = gameLocal.world;
6742 }
6743
6744 if ( attacker->IsType( idAI::Type ) ) {
6745 if ( PowerUpActive( BERSERK ) ) {
6746 return;
6747 }
6748 // don't take damage from monsters during influences
6749 if ( influenceActive != 0 ) {
6750 return;
6751 }
6752 }
6753
6754 const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false );
6755 if ( !damageDef ) {
6756 gameLocal.Warning( "Unknown damageDef '%s'", damageDefName );
6757 return;
6758 }
6759
6760 if ( damageDef->dict.GetBool( "ignore_player" ) ) {
6761 return;
6762 }
6763
6764 CalcDamagePoints( inflictor, attacker, &damageDef->dict, damageScale, location, &damage, &armorSave );
6765
6766 // determine knockback
6767 damageDef->dict.GetInt( "knockback", "20", knockback );
6768
6769 if ( knockback != 0 && !fl.noknockback ) {
6770 if ( attacker == this ) {
6771 damageDef->dict.GetFloat( "attackerPushScale", "0", attackerPushScale );
6772 } else {
6773 attackerPushScale = 1.0f;
6774 }
6775
6776 kick = dir;
6777 kick.Normalize();
6778 kick *= g_knockback.GetFloat() * knockback * attackerPushScale / 200.0f;
6779 physicsObj.SetLinearVelocity( physicsObj.GetLinearVelocity() + kick );
6780
6781 // set the timer so that the player can't cancel out the movement immediately
6782 physicsObj.SetKnockBack( idMath::ClampInt( 50, 200, knockback * 2 ) );
6783 }
6784
6785 // give feedback on the player view and audibly when armor is helping
6786 if ( armorSave ) {
6787 inventory.armor -= armorSave;
6788
6789 if ( gameLocal.time > lastArmorPulse + 200 ) {
6790 StartSound( "snd_hitArmor", SND_CHANNEL_ITEM, 0, false, NULL );
6791 }
6792 lastArmorPulse = gameLocal.time;
6793 }
6794
6795 if ( damageDef->dict.GetBool( "burn" ) ) {
6796 StartSound( "snd_burn", SND_CHANNEL_BODY3, 0, false, NULL );
6797 } else if ( damageDef->dict.GetBool( "no_air" ) ) {
6798 if ( !armorSave && health > 0 ) {
6799 StartSound( "snd_airGasp", SND_CHANNEL_ITEM, 0, false, NULL );
6800 }
6801 }
6802
6803 if ( g_debugDamage.GetInteger() ) {
6804 gameLocal.Printf( "client:%i health:%i damage:%i armor:%i\n",
6805 entityNumber, health, damage, armorSave );
6806 }
6807
6808 // move the world direction vector to local coordinates
6809 damage_from = dir;
6810 damage_from.Normalize();
6811
6812 viewAxis.ProjectVector( damage_from, localDamageVector );
6813
6814 // add to the damage inflicted on a player this frame
6815 // the total will be turned into screen blends and view angle kicks
6816 // at the end of the frame
6817 if ( health > 0 ) {
6818 playerView.DamageImpulse( localDamageVector, &damageDef->dict );
6819 }
6820
6821 // do the damage
6822 if ( damage > 0 ) {
6823
6824 if ( !gameLocal.isMultiplayer ) {
6825 float scale = g_damageScale.GetFloat();
6826 if ( g_useDynamicProtection.GetBool() && g_skill.GetInteger() < 2 ) {
6827 if ( gameLocal.time > lastDmgTime + 500 && scale > 0.25f ) {
6828 scale -= 0.05f;
6829 g_damageScale.SetFloat( scale );
6830 }
6831 }
6832
6833 if ( scale > 0.0f ) {
6834 damage *= scale;
6835 }
6836 }
6837
6838 if ( damage < 1 ) {
6839 damage = 1;
6840 }
6841
6842 health -= damage;
6843
6844 if ( health <= 0 ) {
6845
6846 if ( health < -999 ) {
6847 health = -999;
6848 }
6849
6850 isTelefragged = damageDef->dict.GetBool( "telefrag" );
6851
6852 lastDmgTime = gameLocal.time;
6853 Killed( inflictor, attacker, damage, dir, location );
6854
6855 } else {
6856 // force a blink
6857 blink_time = 0;
6858
6859 // let the anim script know we took damage
6860 AI_PAIN = Pain( inflictor, attacker, damage, dir, location );
6861 if ( !g_testDeath.GetBool() ) {
6862 lastDmgTime = gameLocal.time;
6863 }
6864 }
6865 } else {
6866 // don't accumulate impulses
6867 if ( af.IsLoaded() ) {
6868 // clear impacts
6869 af.Rest();
6870
6871 // physics is turned off by calling af.Rest()
6872 BecomeActive( TH_PHYSICS );
6873 }
6874 }
6875
6876 lastDamageDef = damageDef->Index();
6877 lastDamageDir = damage_from;
6878 lastDamageLocation = location;
6879 }
6880
6881 /*
6882 ===========
6883 idPlayer::Teleport
6884 ============
6885 */
Teleport(const idVec3 & origin,const idAngles & angles,idEntity * destination)6886 void idPlayer::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) {
6887 idVec3 org;
6888
6889 if ( weapon.GetEntity() ) {
6890 weapon.GetEntity()->LowerWeapon();
6891 }
6892
6893 SetOrigin( origin + idVec3( 0, 0, CM_CLIP_EPSILON ) );
6894 if ( !gameLocal.isMultiplayer && GetFloorPos( 16.0f, org ) ) {
6895 SetOrigin( org );
6896 }
6897
6898 // clear the ik heights so model doesn't appear in the wrong place
6899 walkIK.EnableAll();
6900
6901 GetPhysics()->SetLinearVelocity( vec3_origin );
6902
6903 SetViewAngles( angles );
6904
6905 legsYaw = 0.0f;
6906 idealLegsYaw = 0.0f;
6907 oldViewYaw = viewAngles.yaw;
6908
6909 if ( gameLocal.isMultiplayer ) {
6910 playerView.Flash( colorWhite, 140 );
6911 }
6912
6913 UpdateVisuals();
6914
6915 teleportEntity = destination;
6916
6917 if ( !gameLocal.isClient && !noclip ) {
6918 if ( gameLocal.isMultiplayer ) {
6919 // kill anything at the new position or mark for kill depending on immediate or delayed teleport
6920 gameLocal.KillBox( this, destination != NULL );
6921 } else {
6922 // kill anything at the new position
6923 gameLocal.KillBox( this, true );
6924 }
6925 }
6926 }
6927
6928 /*
6929 ====================
6930 idPlayer::SetPrivateCameraView
6931 ====================
6932 */
SetPrivateCameraView(idCamera * camView)6933 void idPlayer::SetPrivateCameraView( idCamera *camView ) {
6934 privateCameraView = camView;
6935 if ( camView ) {
6936 StopFiring();
6937 Hide();
6938 } else {
6939 if ( !spectating ) {
6940 Show();
6941 }
6942 }
6943 }
6944
6945 /*
6946 ====================
6947 idPlayer::DefaultFov
6948
6949 Returns the base FOV
6950 ====================
6951 */
DefaultFov(void) const6952 float idPlayer::DefaultFov( void ) const {
6953 float fov;
6954
6955 fov = g_fov.GetFloat();
6956 if ( gameLocal.isMultiplayer ) {
6957 if ( fov < 90.0f ) {
6958 return 90.0f;
6959 } else if ( fov > 110.0f ) {
6960 return 110.0f;
6961 }
6962 }
6963
6964 return fov;
6965 }
6966
6967 /*
6968 ====================
6969 idPlayer::CalcFov
6970
6971 Fixed fov at intermissions, otherwise account for fov variable and zooms.
6972 ====================
6973 */
CalcFov(bool honorZoom)6974 float idPlayer::CalcFov( bool honorZoom ) {
6975 float fov;
6976
6977 if ( fxFov ) {
6978 return DefaultFov() + 10.0f + cos( ( gameLocal.time + 2000 ) * 0.01 ) * 10.0f;
6979 }
6980
6981 if ( influenceFov ) {
6982 return influenceFov;
6983 }
6984
6985 if ( zoomFov.IsDone( gameLocal.time ) ) {
6986 fov = ( honorZoom && usercmd.buttons & BUTTON_ZOOM ) && weapon.GetEntity() ? weapon.GetEntity()->GetZoomFov() : DefaultFov();
6987 } else {
6988 fov = zoomFov.GetCurrentValue( gameLocal.time );
6989 }
6990
6991 // bound normal viewsize
6992 if ( fov < 1 ) {
6993 fov = 1;
6994 } else if ( fov > 179 ) {
6995 fov = 179;
6996 }
6997
6998 return fov;
6999 }
7000
7001 /*
7002 ==============
7003 idPlayer::GunTurningOffset
7004
7005 generate a rotational offset for the gun based on the view angle
7006 history in loggedViewAngles
7007 ==============
7008 */
GunTurningOffset(void)7009 idAngles idPlayer::GunTurningOffset( void ) {
7010 idAngles a;
7011
7012 a.Zero();
7013
7014 if ( gameLocal.framenum < NUM_LOGGED_VIEW_ANGLES ) {
7015 return a;
7016 }
7017
7018 idAngles current = loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ];
7019
7020 idAngles av, base;
7021 int weaponAngleOffsetAverages;
7022 float weaponAngleOffsetScale, weaponAngleOffsetMax;
7023
7024 weapon.GetEntity()->GetWeaponAngleOffsets( &weaponAngleOffsetAverages, &weaponAngleOffsetScale, &weaponAngleOffsetMax );
7025
7026 av = current;
7027
7028 // calcualte this so the wrap arounds work properly
7029 for ( int j = 1 ; j < weaponAngleOffsetAverages ; j++ ) {
7030 idAngles a2 = loggedViewAngles[ ( gameLocal.framenum - j ) & (NUM_LOGGED_VIEW_ANGLES-1) ];
7031
7032 idAngles delta = a2 - current;
7033
7034 if ( delta[1] > 180 ) {
7035 delta[1] -= 360;
7036 } else if ( delta[1] < -180 ) {
7037 delta[1] += 360;
7038 }
7039
7040 av += delta * ( 1.0f / weaponAngleOffsetAverages );
7041 }
7042
7043 a = ( av - current ) * weaponAngleOffsetScale;
7044
7045 for ( int i = 0 ; i < 3 ; i++ ) {
7046 if ( a[i] < -weaponAngleOffsetMax ) {
7047 a[i] = -weaponAngleOffsetMax;
7048 } else if ( a[i] > weaponAngleOffsetMax ) {
7049 a[i] = weaponAngleOffsetMax;
7050 }
7051 }
7052
7053 return a;
7054 }
7055
7056 /*
7057 ==============
7058 idPlayer::GunAcceleratingOffset
7059
7060 generate a positional offset for the gun based on the movement
7061 history in loggedAccelerations
7062 ==============
7063 */
GunAcceleratingOffset(void)7064 idVec3 idPlayer::GunAcceleratingOffset( void ) {
7065 idVec3 ofs;
7066
7067 float weaponOffsetTime, weaponOffsetScale;
7068
7069 ofs.Zero();
7070
7071 weapon.GetEntity()->GetWeaponTimeOffsets( &weaponOffsetTime, &weaponOffsetScale );
7072
7073 int stop = currentLoggedAccel - NUM_LOGGED_ACCELS;
7074 if ( stop < 0 ) {
7075 stop = 0;
7076 }
7077 for ( int i = currentLoggedAccel-1 ; i > stop ; i-- ) {
7078 loggedAccel_t *acc = &loggedAccel[i&(NUM_LOGGED_ACCELS-1)];
7079
7080 float f;
7081 float t = gameLocal.time - acc->time;
7082 if ( t >= weaponOffsetTime ) {
7083 break; // remainder are too old to care about
7084 }
7085
7086 f = t / weaponOffsetTime;
7087 f = ( cos( f * 2.0f * idMath::PI ) - 1.0f ) * 0.5f;
7088 ofs += f * weaponOffsetScale * acc->dir;
7089 }
7090
7091 return ofs;
7092 }
7093
7094 /*
7095 ==============
7096 idPlayer::CalculateViewWeaponPos
7097
7098 Calculate the bobbing position of the view weapon
7099 ==============
7100 */
CalculateViewWeaponPos(idVec3 & origin,idMat3 & axis)7101 void idPlayer::CalculateViewWeaponPos( idVec3 &origin, idMat3 &axis ) {
7102 float scale;
7103 float fracsin;
7104 idAngles angles;
7105 int delta;
7106
7107 // CalculateRenderView must have been called first
7108 const idVec3 &viewOrigin = firstPersonViewOrigin;
7109 const idMat3 &viewAxis = firstPersonViewAxis;
7110
7111 // these cvars are just for hand tweaking before moving a value to the weapon def
7112 idVec3 gunpos( g_gun_x.GetFloat(), g_gun_y.GetFloat(), g_gun_z.GetFloat() );
7113
7114 // as the player changes direction, the gun will take a small lag
7115 idVec3 gunOfs = GunAcceleratingOffset();
7116 origin = viewOrigin + ( gunpos + gunOfs ) * viewAxis;
7117
7118 // on odd legs, invert some angles
7119 if ( bobCycle & 128 ) {
7120 scale = -xyspeed;
7121 } else {
7122 scale = xyspeed;
7123 }
7124
7125 // gun angles from bobbing
7126 angles.roll = scale * bobfracsin * 0.005f;
7127 angles.yaw = scale * bobfracsin * 0.01f;
7128 angles.pitch = xyspeed * bobfracsin * 0.005f;
7129
7130 // gun angles from turning
7131 if ( gameLocal.isMultiplayer ) {
7132 idAngles offset = GunTurningOffset();
7133 offset *= g_mpWeaponAngleScale.GetFloat();
7134 angles += offset;
7135 } else {
7136 angles += GunTurningOffset();
7137 }
7138
7139 idVec3 gravity = physicsObj.GetGravityNormal();
7140
7141 // drop the weapon when landing after a jump / fall
7142 delta = gameLocal.time - landTime;
7143 if ( delta < LAND_DEFLECT_TIME ) {
7144 origin -= gravity * ( landChange*0.25f * delta / LAND_DEFLECT_TIME );
7145 } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) {
7146 origin -= gravity * ( landChange*0.25f * (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME );
7147 }
7148
7149 // speed sensitive idle drift
7150 scale = xyspeed + 40.0f;
7151 fracsin = scale * sin( MS2SEC( gameLocal.time ) ) * 0.01f;
7152 angles.roll += fracsin;
7153 angles.yaw += fracsin;
7154 angles.pitch += fracsin;
7155
7156 axis = angles.ToMat3() * viewAxis;
7157 }
7158
7159 /*
7160 ===============
7161 idPlayer::OffsetThirdPersonView
7162 ===============
7163 */
OffsetThirdPersonView(float angle,float range,float height,bool clip)7164 void idPlayer::OffsetThirdPersonView( float angle, float range, float height, bool clip ) {
7165 idVec3 view;
7166 idVec3 focusAngles;
7167 trace_t trace;
7168 idVec3 focusPoint;
7169 float focusDist;
7170 float forwardScale, sideScale;
7171 idVec3 origin;
7172 idAngles angles;
7173 idMat3 axis;
7174 idBounds bounds;
7175
7176 angles = viewAngles;
7177 GetViewPos( origin, axis );
7178
7179 if ( angle ) {
7180 angles.pitch = 0.0f;
7181 }
7182
7183 if ( angles.pitch > 45.0f ) {
7184 angles.pitch = 45.0f; // don't go too far overhead
7185 }
7186
7187 focusPoint = origin + angles.ToForward() * THIRD_PERSON_FOCUS_DISTANCE;
7188 focusPoint.z += height;
7189 view = origin;
7190 view.z += 8 + height;
7191
7192 angles.pitch *= 0.5f;
7193 renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis();
7194
7195 idMath::SinCos( DEG2RAD( angle ), sideScale, forwardScale );
7196 view -= range * forwardScale * renderView->viewaxis[ 0 ];
7197 view += range * sideScale * renderView->viewaxis[ 1 ];
7198
7199 if ( clip ) {
7200 // trace a ray from the origin to the viewpoint to make sure the view isn't
7201 // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything
7202 bounds = idBounds( idVec3( -4, -4, -4 ), idVec3( 4, 4, 4 ) );
7203 gameLocal.clip.TraceBounds( trace, origin, view, bounds, MASK_SOLID, this );
7204 if ( trace.fraction != 1.0f ) {
7205 view = trace.endpos;
7206 view.z += ( 1.0f - trace.fraction ) * 32.0f;
7207
7208 // try another trace to this position, because a tunnel may have the ceiling
7209 // close enough that this is poking out
7210 gameLocal.clip.TraceBounds( trace, origin, view, bounds, MASK_SOLID, this );
7211 view = trace.endpos;
7212 }
7213 }
7214
7215 // select pitch to look at focus point from vieword
7216 focusPoint -= view;
7217 focusDist = idMath::Sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] );
7218 if ( focusDist < 1.0f ) {
7219 focusDist = 1.0f; // should never happen
7220 }
7221
7222 angles.pitch = - RAD2DEG( atan2( focusPoint.z, focusDist ) );
7223 angles.yaw -= angle;
7224
7225 renderView->vieworg = view;
7226 renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis();
7227 renderView->viewID = 0;
7228 }
7229
7230 /*
7231 ===============
7232 idPlayer::GetEyePosition
7233 ===============
7234 */
GetEyePosition(void) const7235 idVec3 idPlayer::GetEyePosition( void ) const {
7236 idVec3 org;
7237
7238 // use the smoothed origin if spectating another player in multiplayer
7239 if ( gameLocal.isClient && entityNumber != gameLocal.localClientNum ) {
7240 org = smoothedOrigin;
7241 } else {
7242 org = GetPhysics()->GetOrigin();
7243 }
7244 return org + ( GetPhysics()->GetGravityNormal() * -eyeOffset.z );
7245 }
7246
7247 /*
7248 ===============
7249 idPlayer::GetViewPos
7250 ===============
7251 */
GetViewPos(idVec3 & origin,idMat3 & axis) const7252 void idPlayer::GetViewPos( idVec3 &origin, idMat3 &axis ) const {
7253 idAngles angles;
7254
7255 // if dead, fix the angle and don't add any kick
7256 if ( health <= 0 ) {
7257 angles.yaw = viewAngles.yaw;
7258 angles.roll = 40;
7259 angles.pitch = -15;
7260 axis = angles.ToMat3();
7261 origin = GetEyePosition();
7262 } else {
7263 origin = GetEyePosition() + viewBob;
7264 angles = viewAngles + viewBobAngles + playerView.AngleOffset();
7265
7266 axis = angles.ToMat3() * physicsObj.GetGravityAxis();
7267
7268 // adjust the origin based on the camera nodal distance (eye distance from neck)
7269 origin += physicsObj.GetGravityNormal() * g_viewNodalZ.GetFloat();
7270 origin += axis[0] * g_viewNodalX.GetFloat() + axis[2] * g_viewNodalZ.GetFloat();
7271 }
7272 }
7273
7274 /*
7275 ===============
7276 idPlayer::CalculateFirstPersonView
7277 ===============
7278 */
CalculateFirstPersonView(void)7279 void idPlayer::CalculateFirstPersonView( void ) {
7280 if ( ( pm_modelView.GetInteger() == 1 ) || ( ( pm_modelView.GetInteger() == 2 ) && ( health <= 0 ) ) ) {
7281 // Displays the view from the point of view of the "camera" joint in the player model
7282
7283 idMat3 axis;
7284 idVec3 origin;
7285 idAngles ang;
7286
7287 ang = viewBobAngles + playerView.AngleOffset();
7288 ang.yaw += viewAxis[ 0 ].ToYaw();
7289
7290 jointHandle_t joint = animator.GetJointHandle( "camera" );
7291 animator.GetJointTransform( joint, gameLocal.time, origin, axis );
7292 firstPersonViewOrigin = ( origin + modelOffset ) * ( viewAxis * physicsObj.GetGravityAxis() ) + physicsObj.GetOrigin() + viewBob;
7293 firstPersonViewAxis = axis * ang.ToMat3() * physicsObj.GetGravityAxis();
7294 } else {
7295 // offset for local bobbing and kicks
7296 GetViewPos( firstPersonViewOrigin, firstPersonViewAxis );
7297 #if 0
7298 // shakefrom sound stuff only happens in first person
7299 firstPersonViewAxis = firstPersonViewAxis * playerView.ShakeAxis();
7300 #endif
7301 }
7302 }
7303
7304 /*
7305 ==================
7306 idPlayer::GetRenderView
7307
7308 Returns the renderView that was calculated for this tic
7309 ==================
7310 */
GetRenderView(void)7311 renderView_t *idPlayer::GetRenderView( void ) {
7312 return renderView;
7313 }
7314
7315 /*
7316 ==================
7317 idPlayer::CalculateRenderView
7318
7319 create the renderView for the current tic
7320 ==================
7321 */
CalculateRenderView(void)7322 void idPlayer::CalculateRenderView( void ) {
7323 int i;
7324 float range;
7325
7326 if ( !renderView ) {
7327 renderView = new renderView_t;
7328 }
7329 memset( renderView, 0, sizeof( *renderView ) );
7330
7331 // copy global shader parms
7332 for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) {
7333 renderView->shaderParms[ i ] = gameLocal.globalShaderParms[ i ];
7334 }
7335 renderView->globalMaterial = gameLocal.GetGlobalMaterial();
7336 renderView->time = gameLocal.time;
7337
7338 // calculate size of 3D view
7339 renderView->x = 0;
7340 renderView->y = 0;
7341 renderView->width = SCREEN_WIDTH;
7342 renderView->height = SCREEN_HEIGHT;
7343 renderView->viewID = 0;
7344
7345 // check if we should be drawing from a camera's POV
7346 if ( !noclip && (gameLocal.GetCamera() || privateCameraView) ) {
7347 // get origin, axis, and fov
7348 if ( privateCameraView ) {
7349 privateCameraView->GetViewParms( renderView );
7350 } else {
7351 gameLocal.GetCamera()->GetViewParms( renderView );
7352 }
7353 } else {
7354 if ( g_stopTime.GetBool() ) {
7355 renderView->vieworg = firstPersonViewOrigin;
7356 renderView->viewaxis = firstPersonViewAxis;
7357
7358 if ( !pm_thirdPerson.GetBool() ) {
7359 // set the viewID to the clientNum + 1, so we can suppress the right player bodies and
7360 // allow the right player view weapons
7361 renderView->viewID = entityNumber + 1;
7362 }
7363 } else if ( pm_thirdPerson.GetBool() ) {
7364 OffsetThirdPersonView( pm_thirdPersonAngle.GetFloat(), pm_thirdPersonRange.GetFloat(), pm_thirdPersonHeight.GetFloat(), pm_thirdPersonClip.GetBool() );
7365 } else if ( pm_thirdPersonDeath.GetBool() ) {
7366 range = gameLocal.time < minRespawnTime ? ( gameLocal.time + RAGDOLL_DEATH_TIME - minRespawnTime ) * ( 120.0f / RAGDOLL_DEATH_TIME ) : 120.0f;
7367 OffsetThirdPersonView( 0.0f, 20.0f + range, 0.0f, false );
7368 } else {
7369 renderView->vieworg = firstPersonViewOrigin;
7370 renderView->viewaxis = firstPersonViewAxis;
7371
7372 // set the viewID to the clientNum + 1, so we can suppress the right player bodies and
7373 // allow the right player view weapons
7374 renderView->viewID = entityNumber + 1;
7375 }
7376
7377 // field of view
7378 gameLocal.CalcFov( CalcFov( true ), renderView->fov_x, renderView->fov_y );
7379 }
7380
7381 if ( renderView->fov_y == 0 ) {
7382 common->Error( "renderView->fov_y == 0" );
7383 }
7384
7385 if ( g_showviewpos.GetBool() ) {
7386 gameLocal.Printf( "%s : %s\n", renderView->vieworg.ToString(), renderView->viewaxis.ToAngles().ToString() );
7387 }
7388 }
7389
7390 /*
7391 =============
7392 idPlayer::AddAIKill
7393 =============
7394 */
AddAIKill(void)7395 void idPlayer::AddAIKill( void ) {
7396 int max_souls;
7397 int ammo_souls;
7398
7399 if ( ( weapon_soulcube < 0 ) || ( inventory.weapons & ( 1 << weapon_soulcube ) ) == 0 ) {
7400 return;
7401 }
7402
7403 assert( hud );
7404
7405 ammo_souls = idWeapon::GetAmmoNumForName( "ammo_souls" );
7406 max_souls = inventory.MaxAmmoForAmmoClass( this, "ammo_souls" );
7407 if ( inventory.ammo[ ammo_souls ] < max_souls ) {
7408 inventory.ammo[ ammo_souls ]++;
7409 if ( inventory.ammo[ ammo_souls ] >= max_souls ) {
7410 hud->HandleNamedEvent( "soulCubeReady" );
7411 StartSound( "snd_soulcube_ready", SND_CHANNEL_ANY, 0, false, NULL );
7412 }
7413 }
7414 }
7415
7416 /*
7417 =============
7418 idPlayer::SetSoulCubeProjectile
7419 =============
7420 */
SetSoulCubeProjectile(idProjectile * projectile)7421 void idPlayer::SetSoulCubeProjectile( idProjectile *projectile ) {
7422 soulCubeProjectile = projectile;
7423 }
7424
7425 /*
7426 =============
7427 idPlayer::AddProjectilesFired
7428 =============
7429 */
AddProjectilesFired(int count)7430 void idPlayer::AddProjectilesFired( int count ) {
7431 numProjectilesFired += count;
7432 }
7433
7434 /*
7435 =============
7436 idPlayer::AddProjectileHites
7437 =============
7438 */
AddProjectileHits(int count)7439 void idPlayer::AddProjectileHits( int count ) {
7440 numProjectileHits += count;
7441 }
7442
7443 /*
7444 =============
7445 idPlayer::SetLastHitTime
7446 =============
7447 */
SetLastHitTime(int time)7448 void idPlayer::SetLastHitTime( int time ) {
7449 idPlayer *aimed = NULL;
7450
7451 if ( time && lastHitTime != time ) {
7452 lastHitToggle ^= 1;
7453 }
7454 lastHitTime = time;
7455 if ( !time ) {
7456 // level start and inits
7457 return;
7458 }
7459 if ( gameLocal.isMultiplayer && ( time - lastSndHitTime ) > 10 ) {
7460 lastSndHitTime = time;
7461 StartSound( "snd_hit_feedback", SND_CHANNEL_ANY, SSF_PRIVATE_SOUND, false, NULL );
7462 }
7463 if ( cursor ) {
7464 cursor->HandleNamedEvent( "hitTime" );
7465 }
7466 if ( hud ) {
7467 if ( MPAim != -1 ) {
7468 if ( gameLocal.entities[ MPAim ] && gameLocal.entities[ MPAim ]->IsType( idPlayer::Type ) ) {
7469 aimed = static_cast< idPlayer * >( gameLocal.entities[ MPAim ] );
7470 }
7471 assert( aimed );
7472 // full highlight, no fade till loosing aim
7473 hud->SetStateString( "aim_text", gameLocal.userInfo[ MPAim ].GetString( "ui_name" ) );
7474 if ( aimed ) {
7475 hud->SetStateFloat( "aim_color", aimed->colorBarIndex );
7476 }
7477 hud->HandleNamedEvent( "aim_flash" );
7478 MPAimHighlight = true;
7479 MPAimFadeTime = 0;
7480 } else if ( lastMPAim != -1 ) {
7481 if ( gameLocal.entities[ lastMPAim ] && gameLocal.entities[ lastMPAim ]->IsType( idPlayer::Type ) ) {
7482 aimed = static_cast< idPlayer * >( gameLocal.entities[ lastMPAim ] );
7483 }
7484 assert( aimed );
7485 // start fading right away
7486 hud->SetStateString( "aim_text", gameLocal.userInfo[ lastMPAim ].GetString( "ui_name" ) );
7487 if ( aimed ) {
7488 hud->SetStateFloat( "aim_color", aimed->colorBarIndex );
7489 }
7490 hud->HandleNamedEvent( "aim_flash" );
7491 hud->HandleNamedEvent( "aim_fade" );
7492 MPAimHighlight = false;
7493 MPAimFadeTime = gameLocal.realClientTime;
7494 }
7495 }
7496 }
7497
7498 /*
7499 =============
7500 idPlayer::SetInfluenceLevel
7501 =============
7502 */
SetInfluenceLevel(int level)7503 void idPlayer::SetInfluenceLevel( int level ) {
7504 if ( level != influenceActive ) {
7505 if ( level ) {
7506 for ( idEntity *ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
7507 if ( ent->IsType( idProjectile::Type ) ) {
7508 // remove all projectiles
7509 ent->PostEventMS( &EV_Remove, 0 );
7510 }
7511 }
7512 if ( weaponEnabled && weapon.GetEntity() ) {
7513 weapon.GetEntity()->EnterCinematic();
7514 }
7515 } else {
7516 physicsObj.SetLinearVelocity( vec3_origin );
7517 if ( weaponEnabled && weapon.GetEntity() ) {
7518 weapon.GetEntity()->ExitCinematic();
7519 }
7520 }
7521 influenceActive = level;
7522 }
7523 }
7524
7525 /*
7526 =============
7527 idPlayer::SetInfluenceView
7528 =============
7529 */
SetInfluenceView(const char * mtr,const char * skinname,float radius,idEntity * ent)7530 void idPlayer::SetInfluenceView( const char *mtr, const char *skinname, float radius, idEntity *ent ) {
7531 influenceMaterial = NULL;
7532 influenceEntity = NULL;
7533 influenceSkin = NULL;
7534 if ( mtr && *mtr ) {
7535 influenceMaterial = declManager->FindMaterial( mtr );
7536 }
7537 if ( skinname && *skinname ) {
7538 influenceSkin = declManager->FindSkin( skinname );
7539 if ( head.GetEntity() ) {
7540 head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
7541 }
7542 UpdateVisuals();
7543 }
7544 influenceRadius = radius;
7545 if ( radius > 0.0f ) {
7546 influenceEntity = ent;
7547 }
7548 }
7549
7550 /*
7551 =============
7552 idPlayer::SetInfluenceFov
7553 =============
7554 */
SetInfluenceFov(float fov)7555 void idPlayer::SetInfluenceFov( float fov ) {
7556 influenceFov = fov;
7557 }
7558
7559 /*
7560 ================
7561 idPlayer::OnLadder
7562 ================
7563 */
OnLadder(void) const7564 bool idPlayer::OnLadder( void ) const {
7565 return physicsObj.OnLadder();
7566 }
7567
7568 /*
7569 ==================
7570 idPlayer::Event_GetButtons
7571 ==================
7572 */
Event_GetButtons(void)7573 void idPlayer::Event_GetButtons( void ) {
7574 idThread::ReturnInt( usercmd.buttons );
7575 }
7576
7577 /*
7578 ==================
7579 idPlayer::Event_GetMove
7580 ==================
7581 */
Event_GetMove(void)7582 void idPlayer::Event_GetMove( void ) {
7583 idVec3 move( usercmd.forwardmove, usercmd.rightmove, usercmd.upmove );
7584 idThread::ReturnVector( move );
7585 }
7586
7587 /*
7588 ================
7589 idPlayer::Event_GetViewAngles
7590 ================
7591 */
Event_GetViewAngles(void)7592 void idPlayer::Event_GetViewAngles( void ) {
7593 idThread::ReturnVector( idVec3( viewAngles[0], viewAngles[1], viewAngles[2] ) );
7594 }
7595
7596 /*
7597 ==================
7598 idPlayer::Event_StopFxFov
7599 ==================
7600 */
Event_StopFxFov(void)7601 void idPlayer::Event_StopFxFov( void ) {
7602 fxFov = false;
7603 }
7604
7605 /*
7606 ==================
7607 idPlayer::StartFxFov
7608 ==================
7609 */
StartFxFov(float duration)7610 void idPlayer::StartFxFov( float duration ) {
7611 fxFov = true;
7612 PostEventSec( &EV_Player_StopFxFov, duration );
7613 }
7614
7615 /*
7616 ==================
7617 idPlayer::Event_EnableWeapon
7618 ==================
7619 */
Event_EnableWeapon(void)7620 void idPlayer::Event_EnableWeapon( void ) {
7621 hiddenWeapon = gameLocal.world->spawnArgs.GetBool( "no_Weapons" );
7622 weaponEnabled = true;
7623 if ( weapon.GetEntity() ) {
7624 weapon.GetEntity()->ExitCinematic();
7625 }
7626 }
7627
7628 /*
7629 ==================
7630 idPlayer::Event_DisableWeapon
7631 ==================
7632 */
Event_DisableWeapon(void)7633 void idPlayer::Event_DisableWeapon( void ) {
7634 hiddenWeapon = gameLocal.world->spawnArgs.GetBool( "no_Weapons" );
7635 weaponEnabled = false;
7636 if ( weapon.GetEntity() ) {
7637 weapon.GetEntity()->EnterCinematic();
7638 }
7639 }
7640
7641 /*
7642 ==================
7643 idPlayer::Event_GetCurrentWeapon
7644 ==================
7645 */
Event_GetCurrentWeapon(void)7646 void idPlayer::Event_GetCurrentWeapon( void ) {
7647 const char *weapon;
7648
7649 if ( currentWeapon >= 0 ) {
7650 weapon = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) );
7651 idThread::ReturnString( weapon );
7652 } else {
7653 idThread::ReturnString( "" );
7654 }
7655 }
7656
7657 /*
7658 ==================
7659 idPlayer::Event_GetPreviousWeapon
7660 ==================
7661 */
Event_GetPreviousWeapon(void)7662 void idPlayer::Event_GetPreviousWeapon( void ) {
7663 const char *weapon;
7664
7665 if ( previousWeapon >= 0 ) {
7666 int pw = ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) ? 0 : previousWeapon;
7667 weapon = spawnArgs.GetString( va( "def_weapon%d", pw) );
7668 idThread::ReturnString( weapon );
7669 } else {
7670 idThread::ReturnString( spawnArgs.GetString( "def_weapon0" ) );
7671 }
7672 }
7673
7674 /*
7675 ==================
7676 idPlayer::Event_SelectWeapon
7677 ==================
7678 */
Event_SelectWeapon(const char * weaponName)7679 void idPlayer::Event_SelectWeapon( const char *weaponName ) {
7680 int i;
7681 int weaponNum;
7682
7683 if ( gameLocal.isClient ) {
7684 gameLocal.Warning( "Cannot switch weapons from script in multiplayer" );
7685 return;
7686 }
7687
7688 if ( hiddenWeapon && gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) {
7689 idealWeapon = weapon_fists;
7690 weapon.GetEntity()->HideWeapon();
7691 return;
7692 }
7693
7694 weaponNum = -1;
7695 for( i = 0; i < MAX_WEAPONS; i++ ) {
7696 if ( inventory.weapons & ( 1 << i ) ) {
7697 const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) );
7698 if ( !idStr::Cmp( weap, weaponName ) ) {
7699 weaponNum = i;
7700 break;
7701 }
7702 }
7703 }
7704
7705 if ( weaponNum < 0 ) {
7706 gameLocal.Warning( "%s is not carrying weapon '%s'", name.c_str(), weaponName );
7707 return;
7708 }
7709
7710 hiddenWeapon = false;
7711 idealWeapon = weaponNum;
7712
7713 UpdateHudWeapon();
7714 }
7715
7716 /*
7717 ==================
7718 idPlayer::Event_GetWeaponEntity
7719 ==================
7720 */
Event_GetWeaponEntity(void)7721 void idPlayer::Event_GetWeaponEntity( void ) {
7722 idThread::ReturnEntity( weapon.GetEntity() );
7723 }
7724
7725 /*
7726 ==================
7727 idPlayer::Event_OpenPDA
7728 ==================
7729 */
Event_OpenPDA(void)7730 void idPlayer::Event_OpenPDA( void ) {
7731 if ( !gameLocal.isMultiplayer ) {
7732 TogglePDA();
7733 }
7734 }
7735
7736 /*
7737 ==================
7738 idPlayer::Event_InPDA
7739 ==================
7740 */
Event_InPDA(void)7741 void idPlayer::Event_InPDA( void ) {
7742 idThread::ReturnInt( objectiveSystemOpen );
7743 }
7744
7745 /*
7746 ==================
7747 idPlayer::TeleportDeath
7748 ==================
7749 */
TeleportDeath(int killer)7750 void idPlayer::TeleportDeath( int killer ) {
7751 teleportKiller = killer;
7752 }
7753
7754 /*
7755 ==================
7756 idPlayer::Event_ExitTeleporter
7757 ==================
7758 */
Event_ExitTeleporter(void)7759 void idPlayer::Event_ExitTeleporter( void ) {
7760 idEntity *exitEnt;
7761 float pushVel;
7762
7763 // verify and setup
7764 exitEnt = teleportEntity.GetEntity();
7765 if ( !exitEnt ) {
7766 common->DPrintf( "Event_ExitTeleporter player %d while not being teleported\n", entityNumber );
7767 return;
7768 }
7769
7770 pushVel = exitEnt->spawnArgs.GetFloat( "push", "300" );
7771
7772 if ( gameLocal.isServer ) {
7773 ServerSendEvent( EVENT_EXIT_TELEPORTER, NULL, false, -1 );
7774 }
7775
7776 SetPrivateCameraView( NULL );
7777 // setup origin and push according to the exit target
7778 SetOrigin( exitEnt->GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) );
7779 SetViewAngles( exitEnt->GetPhysics()->GetAxis().ToAngles() );
7780 physicsObj.SetLinearVelocity( exitEnt->GetPhysics()->GetAxis()[ 0 ] * pushVel );
7781 physicsObj.ClearPushedVelocity();
7782 // teleport fx
7783 playerView.Flash( colorWhite, 120 );
7784
7785 // clear the ik heights so model doesn't appear in the wrong place
7786 walkIK.EnableAll();
7787
7788 UpdateVisuals();
7789
7790 StartSound( "snd_teleport_exit", SND_CHANNEL_ANY, 0, false, NULL );
7791
7792 if ( teleportKiller != -1 ) {
7793 // we got killed while being teleported
7794 Damage( gameLocal.entities[ teleportKiller ], gameLocal.entities[ teleportKiller ], vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT );
7795 teleportKiller = -1;
7796 } else {
7797 // kill anything that would have waited at teleport exit
7798 gameLocal.KillBox( this );
7799 }
7800 teleportEntity = NULL;
7801 }
7802
7803 /*
7804 ================
7805 idPlayer::ClientPredictionThink
7806 ================
7807 */
ClientPredictionThink(void)7808 void idPlayer::ClientPredictionThink( void ) {
7809 renderEntity_t *headRenderEnt;
7810
7811 oldFlags = usercmd.flags;
7812 oldButtons = usercmd.buttons;
7813
7814 usercmd = gameLocal.usercmds[ entityNumber ];
7815
7816 if ( entityNumber != gameLocal.localClientNum ) {
7817 // ignore attack button of other clients. that's no good for predictions
7818 usercmd.buttons &= ~BUTTON_ATTACK;
7819 }
7820
7821 buttonMask &= usercmd.buttons;
7822 usercmd.buttons &= ~buttonMask;
7823
7824 if ( objectiveSystemOpen ) {
7825 usercmd.forwardmove = 0;
7826 usercmd.rightmove = 0;
7827 usercmd.upmove = 0;
7828 }
7829
7830 // clear the ik before we do anything else so the skeleton doesn't get updated twice
7831 walkIK.ClearJointMods();
7832
7833 if ( gameLocal.isNewFrame ) {
7834 if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) {
7835 PerformImpulse( usercmd.impulse );
7836 }
7837 }
7838
7839 scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard );
7840
7841 AdjustSpeed();
7842
7843 UpdateViewAngles();
7844
7845 // update the smoothed view angles
7846 if ( gameLocal.framenum >= smoothedFrame && entityNumber != gameLocal.localClientNum ) {
7847 idAngles anglesDiff = viewAngles - smoothedAngles;
7848 anglesDiff.Normalize180();
7849 if ( idMath::Fabs( anglesDiff.yaw ) < 90.0f && idMath::Fabs( anglesDiff.pitch ) < 90.0f ) {
7850 // smoothen by pushing back to the previous angles
7851 viewAngles -= gameLocal.clientSmoothing * anglesDiff;
7852 viewAngles.Normalize180();
7853 }
7854 smoothedAngles = viewAngles;
7855 }
7856 smoothedOriginUpdated = false;
7857
7858 if ( !af.IsActive() ) {
7859 AdjustBodyAngles();
7860 }
7861
7862 if ( !isLagged ) {
7863 // don't allow client to move when lagged
7864 Move();
7865 }
7866
7867 // update GUIs, Items, and character interactions
7868 UpdateFocus();
7869
7870 // service animations
7871 if ( !spectating && !af.IsActive() ) {
7872 UpdateConditions();
7873 UpdateAnimState();
7874 CheckBlink();
7875 }
7876
7877 // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think
7878 AI_PAIN = false;
7879
7880 // calculate the exact bobbed view position, which is used to
7881 // position the view weapon, among other things
7882 CalculateFirstPersonView();
7883
7884 // this may use firstPersonView, or a thirdPerson / camera view
7885 CalculateRenderView();
7886
7887 if ( !gameLocal.inCinematic && weapon.GetEntity() && ( health > 0 ) && !( gameLocal.isMultiplayer && spectating ) ) {
7888 UpdateWeapon();
7889 }
7890
7891 UpdateHud();
7892
7893 if ( gameLocal.isNewFrame ) {
7894 UpdatePowerUps();
7895 }
7896
7897 UpdateDeathSkin( false );
7898
7899 if ( head.GetEntity() ) {
7900 headRenderEnt = head.GetEntity()->GetRenderEntity();
7901 } else {
7902 headRenderEnt = NULL;
7903 }
7904
7905 if ( headRenderEnt ) {
7906 if ( influenceSkin ) {
7907 headRenderEnt->customSkin = influenceSkin;
7908 } else {
7909 headRenderEnt->customSkin = NULL;
7910 }
7911 }
7912
7913 if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() ) {
7914 renderEntity.suppressShadowInViewID = 0;
7915 if ( headRenderEnt ) {
7916 headRenderEnt->suppressShadowInViewID = 0;
7917 }
7918 } else {
7919 renderEntity.suppressShadowInViewID = entityNumber+1;
7920 if ( headRenderEnt ) {
7921 headRenderEnt->suppressShadowInViewID = entityNumber+1;
7922 }
7923 }
7924 // never cast shadows from our first-person muzzle flashes
7925 renderEntity.suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber;
7926 if ( headRenderEnt ) {
7927 headRenderEnt->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber;
7928 }
7929
7930 if ( !gameLocal.inCinematic ) {
7931 UpdateAnimation();
7932 }
7933
7934 if ( gameLocal.isMultiplayer ) {
7935 DrawPlayerIcons();
7936 }
7937
7938 Present();
7939
7940 UpdateDamageEffects();
7941
7942 LinkCombat();
7943
7944 if ( gameLocal.isNewFrame && entityNumber == gameLocal.localClientNum ) {
7945 playerView.CalculateShake();
7946 }
7947 }
7948
7949 /*
7950 ================
7951 idPlayer::GetPhysicsToVisualTransform
7952 ================
7953 */
GetPhysicsToVisualTransform(idVec3 & origin,idMat3 & axis)7954 bool idPlayer::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) {
7955 if ( af.IsActive() ) {
7956 af.GetPhysicsToVisualTransform( origin, axis );
7957 return true;
7958 }
7959
7960 // smoothen the rendered origin and angles of other clients
7961 // smooth self origin if snapshots are telling us prediction is off
7962 if ( gameLocal.isClient && gameLocal.framenum >= smoothedFrame && ( entityNumber != gameLocal.localClientNum || selfSmooth ) ) {
7963 // render origin and axis
7964 idMat3 renderAxis = viewAxis * GetPhysics()->GetAxis();
7965 idVec3 renderOrigin = GetPhysics()->GetOrigin() + modelOffset * renderAxis;
7966
7967 // update the smoothed origin
7968 if ( !smoothedOriginUpdated ) {
7969 idVec2 originDiff = renderOrigin.ToVec2() - smoothedOrigin.ToVec2();
7970 if ( originDiff.LengthSqr() < Square( 100.0f ) ) {
7971 // smoothen by pushing back to the previous position
7972 if ( selfSmooth ) {
7973 assert( entityNumber == gameLocal.localClientNum );
7974 renderOrigin.ToVec2() -= net_clientSelfSmoothing.GetFloat() * originDiff;
7975 } else {
7976 renderOrigin.ToVec2() -= gameLocal.clientSmoothing * originDiff;
7977 }
7978 }
7979 smoothedOrigin = renderOrigin;
7980
7981 smoothedFrame = gameLocal.framenum;
7982 smoothedOriginUpdated = true;
7983 }
7984
7985 axis = idAngles( 0.0f, smoothedAngles.yaw, 0.0f ).ToMat3();
7986 origin = ( smoothedOrigin - GetPhysics()->GetOrigin() ) * axis.Transpose();
7987
7988 } else {
7989
7990 axis = viewAxis;
7991 origin = modelOffset;
7992 }
7993 return true;
7994 }
7995
7996 /*
7997 ================
7998 idPlayer::GetPhysicsToSoundTransform
7999 ================
8000 */
GetPhysicsToSoundTransform(idVec3 & origin,idMat3 & axis)8001 bool idPlayer::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) {
8002 idCamera *camera;
8003
8004 if ( privateCameraView ) {
8005 camera = privateCameraView;
8006 } else {
8007 camera = gameLocal.GetCamera();
8008 }
8009
8010 if ( camera ) {
8011 renderView_t view;
8012
8013 memset( &view, 0, sizeof( view ) );
8014 camera->GetViewParms( &view );
8015 origin = view.vieworg;
8016 axis = view.viewaxis;
8017 return true;
8018 } else {
8019 return idActor::GetPhysicsToSoundTransform( origin, axis );
8020 }
8021 }
8022
8023 /*
8024 ================
8025 idPlayer::WriteToSnapshot
8026 ================
8027 */
WriteToSnapshot(idBitMsgDelta & msg) const8028 void idPlayer::WriteToSnapshot( idBitMsgDelta &msg ) const {
8029 physicsObj.WriteToSnapshot( msg );
8030 WriteBindToSnapshot( msg );
8031 msg.WriteDeltaFloat( 0.0f, deltaViewAngles[0] );
8032 msg.WriteDeltaFloat( 0.0f, deltaViewAngles[1] );
8033 msg.WriteDeltaFloat( 0.0f, deltaViewAngles[2] );
8034 msg.WriteShort( health );
8035 msg.WriteBits( gameLocal.ServerRemapDecl( -1, DECL_ENTITYDEF, lastDamageDef ), gameLocal.entityDefBits );
8036 msg.WriteDir( lastDamageDir, 9 );
8037 msg.WriteShort( lastDamageLocation );
8038 msg.WriteBits( idealWeapon, idMath::BitsForInteger( MAX_WEAPONS ) );
8039 msg.WriteBits( inventory.weapons, MAX_WEAPONS );
8040 msg.WriteBits( weapon.GetSpawnId(), 32 );
8041 msg.WriteBits( spectator, idMath::BitsForInteger( MAX_CLIENTS ) );
8042 msg.WriteBits( lastHitToggle, 1 );
8043 msg.WriteBits( weaponGone, 1 );
8044 msg.WriteBits( isLagged, 1 );
8045 msg.WriteBits( isChatting, 1 );
8046 }
8047
8048 /*
8049 ================
8050 idPlayer::ReadFromSnapshot
8051 ================
8052 */
ReadFromSnapshot(const idBitMsgDelta & msg)8053 void idPlayer::ReadFromSnapshot( const idBitMsgDelta &msg ) {
8054 int i, oldHealth, newIdealWeapon, weaponSpawnId;
8055 bool newHitToggle, stateHitch;
8056
8057 if ( snapshotSequence - lastSnapshotSequence > 1 ) {
8058 stateHitch = true;
8059 } else {
8060 stateHitch = false;
8061 }
8062 lastSnapshotSequence = snapshotSequence;
8063
8064 oldHealth = health;
8065
8066 physicsObj.ReadFromSnapshot( msg );
8067 ReadBindFromSnapshot( msg );
8068 deltaViewAngles[0] = msg.ReadDeltaFloat( 0.0f );
8069 deltaViewAngles[1] = msg.ReadDeltaFloat( 0.0f );
8070 deltaViewAngles[2] = msg.ReadDeltaFloat( 0.0f );
8071 health = msg.ReadShort();
8072 lastDamageDef = gameLocal.ClientRemapDecl( DECL_ENTITYDEF, msg.ReadBits( gameLocal.entityDefBits ) );
8073 lastDamageDir = msg.ReadDir( 9 );
8074 lastDamageLocation = msg.ReadShort();
8075 newIdealWeapon = msg.ReadBits( idMath::BitsForInteger( MAX_WEAPONS ) );
8076 inventory.weapons = msg.ReadBits( MAX_WEAPONS );
8077 weaponSpawnId = msg.ReadBits( 32 );
8078 spectator = msg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) );
8079 newHitToggle = msg.ReadBits( 1 ) != 0;
8080 weaponGone = msg.ReadBits( 1 ) != 0;
8081 isLagged = msg.ReadBits( 1 ) != 0;
8082 isChatting = msg.ReadBits( 1 ) != 0;
8083
8084 // no msg reading below this
8085
8086 if ( weapon.SetSpawnId( weaponSpawnId ) ) {
8087 if ( weapon.GetEntity() ) {
8088 // maintain ownership locally
8089 weapon.GetEntity()->SetOwner( this );
8090 }
8091 currentWeapon = -1;
8092 }
8093 // if not a local client assume the client has all ammo types
8094 if ( entityNumber != gameLocal.localClientNum ) {
8095 for( i = 0; i < AMMO_NUMTYPES; i++ ) {
8096 inventory.ammo[ i ] = 999;
8097 }
8098 }
8099
8100 if ( oldHealth > 0 && health <= 0 ) {
8101 if ( stateHitch ) {
8102 // so we just hide and don't show a death skin
8103 UpdateDeathSkin( true );
8104 }
8105 // die
8106 AI_DEAD = true;
8107 ClearPowerUps();
8108 SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 4 );
8109 SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 4 );
8110 SetWaitState( "" );
8111 animator.ClearAllJoints();
8112 if ( entityNumber == gameLocal.localClientNum ) {
8113 playerView.Fade( colorBlack, 12000 );
8114 }
8115 StartRagdoll();
8116 physicsObj.SetMovementType( PM_DEAD );
8117 if ( !stateHitch ) {
8118 StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
8119 }
8120 if ( weapon.GetEntity() ) {
8121 weapon.GetEntity()->OwnerDied();
8122 }
8123 } else if ( oldHealth <= 0 && health > 0 ) {
8124 // respawn
8125 Init();
8126 StopRagdoll();
8127 SetPhysics( &physicsObj );
8128 physicsObj.EnableClip();
8129 SetCombatContents( true );
8130 } else if ( health < oldHealth && health > 0 ) {
8131 if ( stateHitch ) {
8132 lastDmgTime = gameLocal.time;
8133 } else {
8134 // damage feedback
8135 const idDeclEntityDef *def = static_cast<const idDeclEntityDef *>( declManager->DeclByIndex( DECL_ENTITYDEF, lastDamageDef, false ) );
8136 if ( def ) {
8137 playerView.DamageImpulse( lastDamageDir * viewAxis.Transpose(), &def->dict );
8138 AI_PAIN = Pain( NULL, NULL, oldHealth - health, lastDamageDir, lastDamageLocation );
8139 lastDmgTime = gameLocal.time;
8140 } else {
8141 common->Warning( "NET: no damage def for damage feedback '%d'\n", lastDamageDef );
8142 }
8143 }
8144 } else if ( health > oldHealth && PowerUpActive( MEGAHEALTH ) && !stateHitch ) {
8145 // just pulse, for any health raise
8146 healthPulse = true;
8147 }
8148
8149 // If the player is alive, restore proper physics object
8150 if ( health > 0 && IsActiveAF() ) {
8151 StopRagdoll();
8152 SetPhysics( &physicsObj );
8153 physicsObj.EnableClip();
8154 SetCombatContents( true );
8155 }
8156
8157 if ( idealWeapon != newIdealWeapon ) {
8158 if ( stateHitch ) {
8159 weaponCatchup = true;
8160 }
8161 idealWeapon = newIdealWeapon;
8162 UpdateHudWeapon();
8163 }
8164
8165 if ( lastHitToggle != newHitToggle ) {
8166 SetLastHitTime( gameLocal.realClientTime );
8167 }
8168
8169 if ( msg.HasChanged() ) {
8170 UpdateVisuals();
8171 }
8172 }
8173
8174 /*
8175 ================
8176 idPlayer::WritePlayerStateToSnapshot
8177 ================
8178 */
WritePlayerStateToSnapshot(idBitMsgDelta & msg) const8179 void idPlayer::WritePlayerStateToSnapshot( idBitMsgDelta &msg ) const {
8180 int i;
8181
8182 msg.WriteByte( bobCycle );
8183 msg.WriteInt( stepUpTime );
8184 msg.WriteFloat( stepUpDelta );
8185 msg.WriteShort( inventory.weapons );
8186 msg.WriteByte( inventory.armor );
8187
8188 for( i = 0; i < AMMO_NUMTYPES; i++ ) {
8189 msg.WriteBits( inventory.ammo[i], ASYNC_PLAYER_INV_AMMO_BITS );
8190 }
8191 for( i = 0; i < MAX_WEAPONS; i++ ) {
8192 msg.WriteBits( inventory.clip[i], ASYNC_PLAYER_INV_CLIP_BITS );
8193 }
8194 }
8195
8196 /*
8197 ================
8198 idPlayer::ReadPlayerStateFromSnapshot
8199 ================
8200 */
ReadPlayerStateFromSnapshot(const idBitMsgDelta & msg)8201 void idPlayer::ReadPlayerStateFromSnapshot( const idBitMsgDelta &msg ) {
8202 int i, ammo;
8203
8204 bobCycle = msg.ReadByte();
8205 stepUpTime = msg.ReadInt();
8206 stepUpDelta = msg.ReadFloat();
8207 inventory.weapons = msg.ReadShort();
8208 inventory.armor = msg.ReadByte();
8209
8210 for( i = 0; i < AMMO_NUMTYPES; i++ ) {
8211 ammo = msg.ReadBits( ASYNC_PLAYER_INV_AMMO_BITS );
8212 if ( gameLocal.time >= inventory.ammoPredictTime ) {
8213 inventory.ammo[ i ] = ammo;
8214 }
8215 }
8216 for( i = 0; i < MAX_WEAPONS; i++ ) {
8217 inventory.clip[i] = msg.ReadBits( ASYNC_PLAYER_INV_CLIP_BITS );
8218 }
8219 }
8220
8221 /*
8222 ================
8223 idPlayer::ServerReceiveEvent
8224 ================
8225 */
ServerReceiveEvent(int event,int time,const idBitMsg & msg)8226 bool idPlayer::ServerReceiveEvent( int event, int time, const idBitMsg &msg ) {
8227
8228 if ( idEntity::ServerReceiveEvent( event, time, msg ) ) {
8229 return true;
8230 }
8231
8232 // client->server events
8233 switch( event ) {
8234 case EVENT_IMPULSE: {
8235 PerformImpulse( msg.ReadBits( 6 ) );
8236 return true;
8237 }
8238 default: {
8239 return false;
8240 }
8241 }
8242 }
8243
8244 /*
8245 ================
8246 idPlayer::ClientReceiveEvent
8247 ================
8248 */
ClientReceiveEvent(int event,int time,const idBitMsg & msg)8249 bool idPlayer::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
8250 int powerup;
8251 bool start;
8252
8253 switch ( event ) {
8254 case EVENT_EXIT_TELEPORTER:
8255 Event_ExitTeleporter();
8256 return true;
8257 case EVENT_ABORT_TELEPORTER:
8258 SetPrivateCameraView( NULL );
8259 return true;
8260 case EVENT_POWERUP: {
8261 powerup = msg.ReadShort();
8262 start = msg.ReadBits( 1 ) != 0;
8263 if ( start ) {
8264 GivePowerUp( powerup, 0 );
8265 } else {
8266 ClearPowerup( powerup );
8267 }
8268 return true;
8269 }
8270 case EVENT_SPECTATE: {
8271 bool spectate = ( msg.ReadBits( 1 ) != 0 );
8272 Spectate( spectate );
8273 return true;
8274 }
8275 case EVENT_ADD_DAMAGE_EFFECT: {
8276 if ( spectating ) {
8277 // if we're spectating, ignore
8278 // happens if the event and the spectate change are written on the server during the same frame (fraglimit)
8279 return true;
8280 }
8281 break;
8282 }
8283 default:
8284 break;
8285 }
8286
8287 return idActor::ClientReceiveEvent( event, time, msg );
8288 }
8289
8290 /*
8291 ================
8292 idPlayer::Hide
8293 ================
8294 */
Hide(void)8295 void idPlayer::Hide( void ) {
8296 idWeapon *weap;
8297
8298 idActor::Hide();
8299 weap = weapon.GetEntity();
8300 if ( weap ) {
8301 weap->HideWorldModel();
8302 }
8303 }
8304
8305 /*
8306 ================
8307 idPlayer::Show
8308 ================
8309 */
Show(void)8310 void idPlayer::Show( void ) {
8311 idWeapon *weap;
8312
8313 idActor::Show();
8314 weap = weapon.GetEntity();
8315 if ( weap ) {
8316 weap->ShowWorldModel();
8317 }
8318 }
8319
8320 /*
8321 ===============
8322 idPlayer::StartAudioLog
8323 ===============
8324 */
StartAudioLog(void)8325 void idPlayer::StartAudioLog( void ) {
8326 if ( hud ) {
8327 hud->HandleNamedEvent( "audioLogUp" );
8328 }
8329 }
8330
8331 /*
8332 ===============
8333 idPlayer::StopAudioLog
8334 ===============
8335 */
StopAudioLog(void)8336 void idPlayer::StopAudioLog( void ) {
8337 if ( hud ) {
8338 hud->HandleNamedEvent( "audioLogDown" );
8339 }
8340 }
8341
8342 /*
8343 ===============
8344 idPlayer::ShowTip
8345 ===============
8346 */
ShowTip(const char * title,const char * tip,bool autoHide)8347 void idPlayer::ShowTip( const char *title, const char *tip, bool autoHide ) {
8348 if ( tipUp ) {
8349 return;
8350 }
8351 hud->SetStateString( "tip", tip );
8352 hud->SetStateString( "tiptitle", title );
8353 hud->HandleNamedEvent( "tipWindowUp" );
8354 if ( autoHide ) {
8355 PostEventSec( &EV_Player_HideTip, 5.0f );
8356 }
8357 tipUp = true;
8358 }
8359
8360 /*
8361 ===============
8362 idPlayer::HideTip
8363 ===============
8364 */
HideTip(void)8365 void idPlayer::HideTip( void ) {
8366 hud->HandleNamedEvent( "tipWindowDown" );
8367 tipUp = false;
8368 }
8369
8370 /*
8371 ===============
8372 idPlayer::Event_HideTip
8373 ===============
8374 */
Event_HideTip(void)8375 void idPlayer::Event_HideTip( void ) {
8376 HideTip();
8377 }
8378
8379 /*
8380 ===============
8381 idPlayer::ShowObjective
8382 ===============
8383 */
ShowObjective(const char * obj)8384 void idPlayer::ShowObjective( const char *obj ) {
8385 hud->HandleNamedEvent( obj );
8386 objectiveUp = true;
8387 }
8388
8389 /*
8390 ===============
8391 idPlayer::HideObjective
8392 ===============
8393 */
HideObjective(void)8394 void idPlayer::HideObjective( void ) {
8395 hud->HandleNamedEvent( "closeObjective" );
8396 objectiveUp = false;
8397 }
8398
8399 /*
8400 ===============
8401 idPlayer::Event_StopAudioLog
8402 ===============
8403 */
Event_StopAudioLog(void)8404 void idPlayer::Event_StopAudioLog( void ) {
8405 StopAudioLog();
8406 }
8407
8408 /*
8409 ===============
8410 idPlayer::SetSpectateOrigin
8411 ===============
8412 */
SetSpectateOrigin(void)8413 void idPlayer::SetSpectateOrigin( void ) {
8414 idVec3 neworig;
8415
8416 neworig = GetPhysics()->GetOrigin();
8417 neworig[ 2 ] += EyeHeight();
8418 neworig[ 2 ] += 25;
8419 SetOrigin( neworig );
8420 }
8421
8422 /*
8423 ===============
8424 idPlayer::RemoveWeapon
8425 ===============
8426 */
RemoveWeapon(const char * weap)8427 void idPlayer::RemoveWeapon( const char *weap ) {
8428 if ( weap && *weap ) {
8429 inventory.Drop( spawnArgs, spawnArgs.GetString( weap ), -1 );
8430 }
8431 }
8432
8433 /*
8434 ===============
8435 idPlayer::CanShowWeaponViewmodel
8436 ===============
8437 */
CanShowWeaponViewmodel(void) const8438 bool idPlayer::CanShowWeaponViewmodel( void ) const {
8439 return showWeaponViewModel;
8440 }
8441
8442 /*
8443 ===============
8444 idPlayer::SetLevelTrigger
8445 ===============
8446 */
SetLevelTrigger(const char * levelName,const char * triggerName)8447 void idPlayer::SetLevelTrigger( const char *levelName, const char *triggerName ) {
8448 if ( levelName && *levelName && triggerName && *triggerName ) {
8449 idLevelTriggerInfo lti;
8450 lti.levelName = levelName;
8451 lti.triggerName = triggerName;
8452 inventory.levelTriggers.Append( lti );
8453 }
8454 }
8455
8456 /*
8457 ===============
8458 idPlayer::Event_LevelTrigger
8459 ===============
8460 */
Event_LevelTrigger(void)8461 void idPlayer::Event_LevelTrigger( void ) {
8462 idStr mapName = gameLocal.GetMapName();
8463 mapName.StripPath();
8464 mapName.StripFileExtension();
8465 for ( int i = inventory.levelTriggers.Num() - 1; i >= 0; i-- ) {
8466 if ( idStr::Icmp( mapName, inventory.levelTriggers[i].levelName) == 0 ){
8467 idEntity *ent = gameLocal.FindEntity( inventory.levelTriggers[i].triggerName );
8468 if ( ent ) {
8469 ent->PostEventMS( &EV_Activate, 1, this );
8470 }
8471 }
8472 }
8473 }
8474
8475 /*
8476 ===============
8477 idPlayer::Event_Gibbed
8478 ===============
8479 */
Event_Gibbed(void)8480 void idPlayer::Event_Gibbed( void ) {
8481 }
8482
8483 /*
8484 ==================
8485 idPlayer::Event_GetIdealWeapon
8486 ==================
8487 */
Event_GetIdealWeapon(void)8488 void idPlayer::Event_GetIdealWeapon( void ) {
8489 const char *weapon;
8490
8491 if ( idealWeapon >= 0 ) {
8492 weapon = spawnArgs.GetString( va( "def_weapon%d", idealWeapon ) );
8493 idThread::ReturnString( weapon );
8494 } else {
8495 idThread::ReturnString( "" );
8496 }
8497 }
8498
8499 /*
8500 ===============
8501 idPlayer::UpdatePlayerIcons
8502 ===============
8503 */
UpdatePlayerIcons(void)8504 void idPlayer::UpdatePlayerIcons( void ) {
8505 int time = networkSystem->ServerGetClientTimeSinceLastPacket( entityNumber );
8506 if ( time > cvarSystem->GetCVarInteger( "net_clientMaxPrediction" ) ) {
8507 isLagged = true;
8508 } else {
8509 isLagged = false;
8510 }
8511 }
8512
8513 /*
8514 ===============
8515 idPlayer::DrawPlayerIcons
8516 ===============
8517 */
DrawPlayerIcons(void)8518 void idPlayer::DrawPlayerIcons( void ) {
8519 if ( !NeedsIcon() ) {
8520 playerIcon.FreeIcon();
8521 return;
8522 }
8523 playerIcon.Draw( this, headJoint );
8524 }
8525
8526 /*
8527 ===============
8528 idPlayer::HidePlayerIcons
8529 ===============
8530 */
HidePlayerIcons(void)8531 void idPlayer::HidePlayerIcons( void ) {
8532 playerIcon.FreeIcon();
8533 }
8534
8535 /*
8536 ===============
8537 idPlayer::NeedsIcon
8538 ==============
8539 */
NeedsIcon(void)8540 bool idPlayer::NeedsIcon( void ) {
8541 // local clients don't render their own icons... they're only info for other clients
8542 return entityNumber != gameLocal.localClientNum && ( isLagged || isChatting );
8543 }
8544