1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7
8 This file is part of the OpenJK source code.
9
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13
14 This program 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 this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23
24 // leave this line at the top for all g_xxxx.cpp files...
25 #include "g_headers.h"
26
27 #include "g_local.h"
28 #include "g_functions.h"
29 #include "g_items.h"
30 #include "wp_saber.h"
31
32 extern qboolean missionInfo_Updated;
33
34 extern void CrystalAmmoSettings(gentity_t *ent);
35 extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel );
36 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
37 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
38 extern qboolean PM_InKnockDown( playerState_t *ps );
39 extern qboolean PM_InGetUp( playerState_t *ps );
40
41 extern cvar_t *g_spskill;
42
43 #define MAX_BACTA_HEAL_AMOUNT 25
44
45 /*
46
47 Items are any object that a player can touch to gain some effect.
48
49 Pickup will return the number of seconds until they should respawn.
50
51 all items should pop when dropped in lava or slime
52
53 Respawnable items don't actually go away when picked up, they are
54 just made invisible and untouchable. This allows them to ride
55 movers and respawn apropriately.
56 */
57
58 // Item Spawn flags
59 #define ITMSF_SUSPEND 1
60 #define ITMSF_TEAM 2
61 #define ITMSF_MONSTER 4
62 #define ITMSF_NOTSOLID 8
63 #define ITMSF_VERTICAL 16
64 #define ITMSF_INVISIBLE 32
65
66 //======================================================================
67
68 /*
69 ===============
70 G_InventorySelectable
71 ===============
72 */
G_InventorySelectable(int index,gentity_t * other)73 qboolean G_InventorySelectable( int index,gentity_t *other)
74 {
75 if (other->client->ps.inventory[index])
76 {
77 return qtrue;
78 }
79
80 return qfalse;
81 }
82
83 extern qboolean INV_GoodieKeyGive( gentity_t *target );
84 extern qboolean INV_SecurityKeyGive( gentity_t *target, const char *keyname );
Pickup_Holdable(gentity_t * ent,gentity_t * other)85 int Pickup_Holdable( gentity_t *ent, gentity_t *other )
86 {
87 int i,original;
88
89 other->client->ps.stats[STAT_ITEMS] |= (1<<ent->item->giTag);
90
91 if ( ent->item->giTag == INV_SECURITY_KEY )
92 {//give the key
93 //FIXME: temp message
94 gi.SendServerCommand( 0, "cp @INGAME_YOU_TOOK_SECURITY_KEY" );
95 INV_SecurityKeyGive( other, ent->message );
96 }
97 else if ( ent->item->giTag == INV_GOODIE_KEY )
98 {//give the key
99 //FIXME: temp message
100 gi.SendServerCommand( 0, "cp @INGAME_YOU_TOOK_SUPPLY_KEY" );
101 INV_GoodieKeyGive( other );
102 }
103 else
104 {// Picking up a normal item?
105 other->client->ps.inventory[ent->item->giTag]++;
106 }
107 // Got a security key
108
109 // Set the inventory select, just in case it hasn't
110 original = cg.inventorySelect;
111 for ( i = 0 ; i < INV_MAX ; i++ )
112 {
113 if ((cg.inventorySelect < INV_ELECTROBINOCULARS) || (cg.inventorySelect >= INV_MAX))
114 {
115 cg.inventorySelect = (INV_MAX - 1);
116 }
117
118 if ( G_InventorySelectable( cg.inventorySelect,other ) )
119 {
120 return 60;
121 }
122 cg.inventorySelect++;
123 }
124
125 cg.inventorySelect = original;
126
127 return 60;
128 }
129
130
131 //======================================================================
Add_Ammo2(gentity_t * ent,int ammoType,int count)132 int Add_Ammo2 (gentity_t *ent, int ammoType, int count)
133 {
134
135 if (ammoType != AMMO_FORCE)
136 {
137 ent->client->ps.ammo[ammoType] += count;
138
139 // since the ammo is the weapon in this case, picking up ammo should actually give you the weapon
140 switch( ammoType )
141 {
142 case AMMO_THERMAL:
143 ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_THERMAL );
144 break;
145 case AMMO_DETPACK:
146 ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_DET_PACK );
147 break;
148 case AMMO_TRIPMINE:
149 ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_TRIP_MINE );
150 break;
151 }
152
153 if ( ent->client->ps.ammo[ammoType] > ammoData[ammoType].max )
154 {
155 ent->client->ps.ammo[ammoType] = ammoData[ammoType].max;
156 return qfalse;
157 }
158 }
159 else
160 {
161 if ( ent->client->ps.forcePower >= ammoData[ammoType].max )
162 {//if have full force, just get 25 extra per crystal
163 ent->client->ps.forcePower += 25;
164 }
165 else
166 {//else if don't have full charge, give full amount, up to max + 25
167 ent->client->ps.forcePower += count;
168 if ( ent->client->ps.forcePower >= ammoData[ammoType].max + 25 )
169 {//cap at max + 25
170 ent->client->ps.forcePower = ammoData[ammoType].max + 25;
171 }
172 }
173
174 if ( ent->client->ps.forcePower >= ammoData[ammoType].max*2 )
175 {//always cap at twice a full charge
176 ent->client->ps.forcePower = ammoData[ammoType].max*2;
177 return qfalse; // can't hold any more
178 }
179 }
180 return qtrue;
181 }
182
183 //-------------------------------------------------------
Add_Ammo(gentity_t * ent,int weapon,int count)184 void Add_Ammo (gentity_t *ent, int weapon, int count)
185 {
186 Add_Ammo2(ent,weaponData[weapon].ammoIndex,count);
187 }
188
189 //-------------------------------------------------------
Pickup_Ammo(gentity_t * ent,gentity_t * other)190 int Pickup_Ammo (gentity_t *ent, gentity_t *other)
191 {
192 int quantity;
193
194 if ( ent->count ) {
195 quantity = ent->count;
196 } else {
197 quantity = ent->item->quantity;
198 }
199
200 Add_Ammo2 (other, ent->item->giTag, quantity);
201
202 return 30;
203 }
204
205 //======================================================================
Add_Batteries(gentity_t * ent,int * count)206 void Add_Batteries( gentity_t *ent, int *count )
207 {
208 if ( ent->client && ent->client->ps.batteryCharge < MAX_BATTERIES && *count )
209 {
210 if ( *count + ent->client->ps.batteryCharge > MAX_BATTERIES )
211 {
212 // steal what we need, then leave the rest for later
213 *count -= ( MAX_BATTERIES - ent->client->ps.batteryCharge );
214 ent->client->ps.batteryCharge = MAX_BATTERIES;
215 }
216 else
217 {
218 // just drain all of the batteries
219 ent->client->ps.batteryCharge += *count;
220 *count = 0;
221 }
222
223 G_AddEvent( ent, EV_BATTERIES_CHARGED, 0 );
224 }
225 }
226
227 //-------------------------------------------------------
Pickup_Battery(gentity_t * ent,gentity_t * other)228 int Pickup_Battery( gentity_t *ent, gentity_t *other )
229 {
230 int quantity;
231
232 if ( ent->count )
233 {
234 quantity = ent->count;
235 }
236 else
237 {
238 quantity = ent->item->quantity;
239 }
240
241 // There may be some left over in quantity if the player is close to full, but with pickup items, this amount will just be lost
242 Add_Batteries( other, &quantity );
243
244 return 30;
245 }
246
247 //======================================================================
248
249
250 extern void WP_SaberInitBladeData( gentity_t *ent );
251 extern void CG_ChangeWeapon( int num );
Pickup_Weapon(gentity_t * ent,gentity_t * other)252 int Pickup_Weapon (gentity_t *ent, gentity_t *other)
253 {
254 int quantity;
255 qboolean hadWeapon = qfalse;
256
257 /*
258 if ( ent->count || (ent->activator && !ent->activator->s.number) )
259 {
260 quantity = ent->count;
261 }
262 else
263 {
264 quantity = ent->item->quantity;
265 }
266 */
267
268 // dropped items are always picked up
269 if ( ent->flags & FL_DROPPED_ITEM )
270 {
271 quantity = ent->count;
272 }
273 else
274 {//wasn't dropped
275 quantity = ent->item->quantity?ent->item->quantity:50;
276 }
277
278 // add the weapon
279 if ( other->client->ps.stats[STAT_WEAPONS] & ( 1 << ent->item->giTag ) )
280 {
281 hadWeapon = qtrue;
282 }
283 other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag );
284
285 if ( ent->item->giTag == WP_SABER && !hadWeapon )
286 {
287 WP_SaberInitBladeData( other );
288 }
289
290 if ( other->s.number )
291 {//NPC
292 if ( other->s.weapon == WP_NONE )
293 {//NPC with no weapon picked up a weapon, change to this weapon
294 //FIXME: clear/set the alt-fire flag based on the picked up weapon and my class?
295 other->client->ps.weapon = ent->item->giTag;
296 other->client->ps.weaponstate = WEAPON_RAISING;
297 ChangeWeapon( other, ent->item->giTag );
298 if ( ent->item->giTag == WP_SABER )
299 {
300 other->client->ps.saberActive = qtrue;
301 G_CreateG2AttachedWeaponModel( other, other->client->ps.saberModel );
302 }
303 else
304 {
305 G_CreateG2AttachedWeaponModel( other, weaponData[ent->item->giTag].weaponMdl );
306 }
307 }
308 }
309
310 if ( quantity )
311 {
312 // Give ammo
313 Add_Ammo( other, ent->item->giTag, quantity );
314 }
315 return 5;
316 }
317
318
319 //======================================================================
320
ITM_AddHealth(gentity_t * ent,int count)321 int ITM_AddHealth (gentity_t *ent, int count)
322 {
323
324 ent->health += count;
325
326 if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH]) // Past max health
327 {
328 ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
329
330 return qfalse;
331 }
332
333 return qtrue;
334
335 }
336
Pickup_Health(gentity_t * ent,gentity_t * other)337 int Pickup_Health (gentity_t *ent, gentity_t *other) {
338 int max;
339 int quantity;
340
341 max = other->client->ps.stats[STAT_MAX_HEALTH];
342
343 if ( ent->count ) {
344 quantity = ent->count;
345 } else {
346 quantity = ent->item->quantity;
347 }
348
349 other->health += quantity;
350
351 if (other->health > max ) {
352 other->health = max;
353 }
354
355 if ( ent->item->giTag == 100 ) { // mega health respawns slow
356 return 120;
357 }
358
359 return 30;
360 }
361
362 //======================================================================
363
ITM_AddArmor(gentity_t * ent,int count)364 int ITM_AddArmor (gentity_t *ent, int count)
365 {
366
367 ent->client->ps.stats[STAT_ARMOR] += count;
368
369 if (ent->client->ps.stats[STAT_ARMOR] > ent->client->ps.stats[STAT_MAX_HEALTH])
370 {
371 ent->client->ps.stats[STAT_ARMOR] = ent->client->ps.stats[STAT_MAX_HEALTH];
372 return qfalse;
373 }
374
375 return qtrue;
376 }
377
378
Pickup_Armor(gentity_t * ent,gentity_t * other)379 int Pickup_Armor( gentity_t *ent, gentity_t *other ) {
380
381 // make sure that the shield effect is on
382 other->client->ps.powerups[PW_BATTLESUIT] = Q3_INFINITE;
383
384 other->client->ps.stats[STAT_ARMOR] += ent->item->quantity;
385 if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] ) {
386 other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH];
387 }
388
389 return 30;
390 }
391
392
393
394 //======================================================================
395
Pickup_Holocron(gentity_t * ent,gentity_t * other)396 int Pickup_Holocron( gentity_t *ent, gentity_t *other )
397 {
398 int forcePower = ent->item->giTag;
399 int forceLevel = ent->count;
400 // check if out of range
401 if( forceLevel < 0 || forceLevel >= NUM_FORCE_POWER_LEVELS )
402 {
403 gi.Printf(" Pickup_Holocron : count %d not in valid range\n", forceLevel );
404 return 1;
405 }
406
407 // don't pick up if already known AND your level is higher than pickup level
408 if ( ( other->client->ps.forcePowersKnown & ( 1 << forcePower )) )
409 {
410 //don't pickup if item is lower than current level
411 if( other->client->ps.forcePowerLevel[forcePower] >= forceLevel )
412 {
413 return 1;
414 }
415 }
416
417 other->client->ps.forcePowerLevel[forcePower] = forceLevel;
418 other->client->ps.forcePowersKnown |= ( 1 << forcePower );
419
420 missionInfo_Updated = qtrue; // Activate flashing text
421 gi.cvar_set("cg_updatedDataPadForcePower1", va("%d",forcePower+1)); // The +1 is offset in the print routine.
422 cg_updatedDataPadForcePower1.integer = forcePower+1;
423 gi.cvar_set("cg_updatedDataPadForcePower2", "0"); // The +1 is offset in the print routine.
424 cg_updatedDataPadForcePower2.integer = 0;
425 gi.cvar_set("cg_updatedDataPadForcePower3", "0"); // The +1 is offset in the print routine.
426 cg_updatedDataPadForcePower3.integer = 0;
427
428 return 1;
429 }
430
431
432 //======================================================================
433
434 /*
435 ===============
436 RespawnItem
437 ===============
438 */
RespawnItem(gentity_t * ent)439 void RespawnItem( gentity_t *ent ) {
440 }
441
442
CheckItemCanBePickedUpByNPC(gentity_t * item,gentity_t * pickerupper)443 qboolean CheckItemCanBePickedUpByNPC( gentity_t *item, gentity_t *pickerupper )
444 {
445 if ( !item->item ) {
446 return qfalse;
447 }
448 if ( item->item->giType == IT_HOLDABLE &&
449 item->item->giTag == INV_SECURITY_KEY ) {
450 return qfalse;
451 }
452 if ( (item->flags&FL_DROPPED_ITEM)
453 && item->activator != &g_entities[0]
454 && pickerupper->s.number
455 && pickerupper->s.weapon == WP_NONE
456 && pickerupper->enemy
457 && pickerupper->painDebounceTime < level.time
458 && pickerupper->NPC && pickerupper->NPC->surrenderTime < level.time //not surrendering
459 && !(pickerupper->NPC->scriptFlags&SCF_FORCED_MARCH) ) // not being forced to march
460 {//non-player, in combat, picking up a dropped item that does NOT belong to the player and it *not* a security key
461 if ( level.time - item->s.time < 3000 )//was 5000
462 {
463 return qfalse;
464 }
465 return qtrue;
466 }
467 return qfalse;
468 }
469 /*
470 ===============
471 Touch_Item
472 ===============
473 */
474 extern cvar_t *g_timescale;
Touch_Item(gentity_t * ent,gentity_t * other,trace_t * trace)475 void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) {
476 int respawn = 0;
477
478 if (!other->client)
479 return;
480 if (other->health < 1)
481 return; // dead people can't pickup
482
483 if ( other->client->ps.pm_time > 0 )
484 {//cant pick up when out of control
485 return;
486 }
487
488 // Only monsters can pick it up
489 if ((ent->spawnflags & ITMSF_MONSTER) && (other->client->playerTeam == TEAM_PLAYER))
490 {
491 return;
492 }
493
494 // Only starfleet can pick it up
495 if ((ent->spawnflags & ITMSF_TEAM) && (other->client->playerTeam != TEAM_PLAYER))
496 {
497 return;
498 }
499
500
501 if ( other->client->NPC_class == CLASS_ATST ||
502 other->client->NPC_class == CLASS_GONK ||
503 other->client->NPC_class == CLASS_MARK1 ||
504 other->client->NPC_class == CLASS_MARK2 ||
505 other->client->NPC_class == CLASS_MOUSE ||
506 other->client->NPC_class == CLASS_PROBE ||
507 other->client->NPC_class == CLASS_PROTOCOL ||
508 other->client->NPC_class == CLASS_R2D2 ||
509 other->client->NPC_class == CLASS_R5D2 ||
510 other->client->NPC_class == CLASS_SEEKER ||
511 other->client->NPC_class == CLASS_REMOTE ||
512 other->client->NPC_class == CLASS_SENTRY )
513 {//FIXME: some flag would be better
514 //droids can't pick up items/weapons!
515 return;
516 }
517
518 //FIXME: need to make them run toward a dropped weapon when fleeing without one?
519 //FIXME: need to make them come out of flee mode when pick up their old weapon?
520 if ( CheckItemCanBePickedUpByNPC( ent, other ) )
521 {
522 if ( other->NPC && other->NPC->goalEntity && other->NPC->goalEntity->enemy == ent )
523 {//they were running to pick me up, they did, so clear goal
524 other->NPC->goalEntity = NULL;
525 other->NPC->squadState = SQUAD_STAND_AND_SHOOT;
526 }
527 }
528 else if (!(ent->spawnflags & ITMSF_TEAM) && !(ent->spawnflags & ITMSF_MONSTER))
529 {// Only player can pick it up
530 if ( other->s.number != 0 ) // Not the player?
531 {
532 return;
533 }
534 }
535
536 // the same pickup rules are used for client side and server side
537 if ( !BG_CanItemBeGrabbed( &ent->s, &other->client->ps ) ) {
538 return;
539 }
540
541 if ( other->client )
542 {
543 if ( other->client->ps.eFlags&EF_FORCE_GRIPPED )
544 {//can't pick up anything while being gripped
545 return;
546 }
547 if ( PM_InKnockDown( &other->client->ps ) && !PM_InGetUp( &other->client->ps ) )
548 {//can't pick up while in a knockdown
549 return;
550 }
551 }
552 if (!ent->item) { //not an item!
553 gi.Printf( "Touch_Item: %s is not an item!\n", ent->classname);
554 return;
555 }
556 qboolean bHadWeapon = qfalse;
557 // call the item-specific pickup function
558 switch( ent->item->giType )
559 {
560 case IT_WEAPON:
561 if ( other->NPC && other->s.weapon == WP_NONE )
562 {//Make them duck and sit here for a few seconds
563 int pickUpTime = Q_irand( 1000, 3000 );
564 TIMER_Set( other, "duck", pickUpTime );
565 TIMER_Set( other, "roamTime", pickUpTime );
566 TIMER_Set( other, "stick", pickUpTime );
567 TIMER_Set( other, "verifyCP", pickUpTime );
568 TIMER_Set( other, "attackDelay", 600 );
569 respawn = 0;
570 }
571 if ( other->client->ps.stats[STAT_WEAPONS] & ( 1 << ent->item->giTag ) )
572 {
573 bHadWeapon = qtrue;
574 }
575 respawn = Pickup_Weapon(ent, other);
576 break;
577 case IT_AMMO:
578 respawn = Pickup_Ammo(ent, other);
579 break;
580 case IT_ARMOR:
581 respawn = Pickup_Armor(ent, other);
582 break;
583 case IT_HEALTH:
584 respawn = Pickup_Health(ent, other);
585 break;
586 case IT_HOLDABLE:
587 respawn = Pickup_Holdable(ent, other);
588 break;
589 case IT_BATTERY:
590 respawn = Pickup_Battery( ent, other );
591 break;
592 case IT_HOLOCRON:
593 respawn = Pickup_Holocron( ent, other );
594 break;
595 default:
596 return;
597 }
598
599 if ( !respawn )
600 {
601 return;
602 }
603
604 // play the normal pickup sound
605 if ( !other->s.number && g_timescale->value < 1.0f )
606 {//SIGH... with timescale on, you lose events left and right
607 extern void CG_ItemPickup( int itemNum, qboolean bHadItem );
608 // but we're SP so we'll cheat
609 cgi_S_StartSound( NULL, other->s.number, CHAN_AUTO, cgi_S_RegisterSound( ent->item->pickup_sound ) );
610 // show icon and name on status bar
611 CG_ItemPickup( ent->s.modelindex, bHadWeapon );
612 }
613 else
614 {
615 if ( bHadWeapon )
616 {
617 G_AddEvent( other, EV_ITEM_PICKUP, -ent->s.modelindex );
618 }
619 else
620 {
621 G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex );
622 }
623 }
624
625 // fire item targets
626 G_UseTargets (ent, other);
627
628 // wait of -1 will not respawn
629 // if ( ent->wait == -1 )
630 {
631 //why not just remove me?
632 G_FreeEntity( ent );
633 /*
634 //NOTE: used to do this: (for respawning?)
635 ent->svFlags |= SVF_NOCLIENT;
636 ent->s.eFlags |= EF_NODRAW;
637 ent->contents = 0;
638 ent->unlinkAfterEvent = qtrue;
639 */
640 return;
641 }
642 }
643
644
645 //======================================================================
646
647 /*
648 ================
649 LaunchItem
650
651 Spawns an item and tosses it forward
652 ================
653 */
LaunchItem(gitem_t * item,vec3_t origin,vec3_t velocity,char * target)654 gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity, char *target ) {
655 gentity_t *dropped;
656
657 dropped = G_Spawn();
658
659 dropped->s.eType = ET_ITEM;
660 dropped->s.modelindex = item - bg_itemlist; // store item number in modelindex
661 dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item
662
663 dropped->classname = item->classname;
664 dropped->item = item;
665
666 // try using the "correct" mins/maxs first
667 VectorSet( dropped->mins, item->mins[0], item->mins[1], item->mins[2] );
668 VectorSet( dropped->maxs, item->maxs[0], item->maxs[1], item->maxs[2] );
669
670 if ((!dropped->mins[0] && !dropped->mins[1] && !dropped->mins[2]) &&
671 (!dropped->maxs[0] && !dropped->maxs[1] && !dropped->maxs[2]))
672 {
673 VectorSet( dropped->maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );
674 VectorScale( dropped->maxs, -1, dropped->mins );
675 }
676
677 dropped->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;//CONTENTS_TRIGGER;//not CONTENTS_BODY for dropped items, don't need to ID them
678
679 if ( target && target[0] )
680 {
681 dropped->target = G_NewString( target );
682 }
683 else
684 {
685 // if not targeting something, auto-remove after 30 seconds
686 // only if it's NOT a security or goodie key
687 if (dropped->item->giTag != INV_SECURITY_KEY )
688 {
689 dropped->e_ThinkFunc = thinkF_G_FreeEntity;
690 dropped->nextthink = level.time + 30000;
691 }
692
693 if ( dropped->item->giType == IT_AMMO && dropped->item->giTag == AMMO_FORCE )
694 {
695 dropped->nextthink = -1;
696 dropped->e_ThinkFunc = thinkF_NULL;
697 }
698 }
699
700 dropped->e_TouchFunc = touchF_Touch_Item;
701
702 if ( item->giType == IT_WEAPON )
703 {
704 // give weapon items zero pitch, a random yaw, and rolled onto their sides...but would be bad to do this for a bowcaster
705 if ( item->giTag != WP_BOWCASTER
706 && item->giTag != WP_THERMAL
707 && item->giTag != WP_TRIP_MINE
708 && item->giTag != WP_DET_PACK )
709 {
710 VectorSet( dropped->s.angles, 0, Q_flrand(-1.0f, 1.0f) * 180, 90.0f );
711 G_SetAngles( dropped, dropped->s.angles );
712 }
713 }
714
715 G_SetOrigin( dropped, origin );
716 dropped->s.pos.trType = TR_GRAVITY;
717 dropped->s.pos.trTime = level.time;
718 VectorCopy( velocity, dropped->s.pos.trDelta );
719
720 dropped->s.eFlags |= EF_BOUNCE_HALF;
721
722 dropped->flags = FL_DROPPED_ITEM;
723
724 gi.linkentity (dropped);
725
726 return dropped;
727 }
728
729 /*
730 ================
731 Drop_Item
732
733 Spawns an item and tosses it forward
734 ================
735 */
Drop_Item(gentity_t * ent,gitem_t * item,float angle,qboolean copytarget)736 gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle, qboolean copytarget ) {
737 gentity_t *dropped = NULL;
738 vec3_t velocity;
739 vec3_t angles;
740
741 VectorCopy( ent->s.apos.trBase, angles );
742 angles[YAW] += angle;
743 angles[PITCH] = 0; // always forward
744
745 AngleVectors( angles, velocity, NULL, NULL );
746 VectorScale( velocity, 150, velocity );
747 velocity[2] += 200 + Q_flrand(-1.0f, 1.0f) * 50;
748
749 if ( copytarget )
750 {
751 dropped = LaunchItem( item, ent->s.pos.trBase, velocity, ent->opentarget );
752 }
753 else
754 {
755 dropped = LaunchItem( item, ent->s.pos.trBase, velocity, NULL );
756 }
757
758 dropped->activator = ent;//so we know who we belonged to so they can pick it back up later
759 dropped->s.time = level.time;//mark this time so we aren't picked up instantly by the guy who dropped us
760 return dropped;
761 }
762
763
764 /*
765 ================
766 Use_Item
767
768 Respawn the item
769 ================
770 */
Use_Item(gentity_t * ent,gentity_t * other,gentity_t * activator)771 void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator )
772 {
773 if ( (ent->svFlags&SVF_PLAYER_USABLE) && other && !other->s.number )
774 {//used directly by the player, pick me up
775 GEntity_TouchFunc( ent, other, NULL );
776 }
777 else
778 {//use me
779 if ( ent->spawnflags & 32 ) // invisible
780 {
781 // If it was invisible, first use makes it visible....
782 ent->s.eFlags &= ~EF_NODRAW;
783 ent->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;
784
785 ent->spawnflags &= ~32;
786 return;
787 }
788
789 G_ActivateBehavior( ent, BSET_USE );
790 RespawnItem( ent );
791 }
792 }
793
794 //======================================================================
795
796 /*
797 ================
798 FinishSpawningItem
799
800 Traces down to find where an item should rest, instead of letting them
801 free fall from their spawn points
802 ================
803 */
804 #ifndef FINAL_BUILD
805 extern int delayedShutDown;
806 #endif
FinishSpawningItem(gentity_t * ent)807 void FinishSpawningItem( gentity_t *ent ) {
808 trace_t tr;
809 vec3_t dest;
810 gitem_t *item;
811 int itemNum;
812
813 itemNum=1;
814 for ( item = bg_itemlist + 1 ; item->classname ; item++,itemNum++)
815 {
816 if (!strcmp(item->classname,ent->classname))
817 {
818 break;
819 }
820 }
821
822 // Set bounding box for item
823 VectorSet( ent->mins, item->mins[0],item->mins[1] ,item->mins[2]);
824 VectorSet( ent->maxs, item->maxs[0],item->maxs[1] ,item->maxs[2]);
825
826 if ((!ent->mins[0] && !ent->mins[1] && !ent->mins[2]) &&
827 (!ent->maxs[0] && !ent->maxs[1] && !ent->maxs[2]))
828 {
829 VectorSet (ent->mins, -ITEM_RADIUS, -ITEM_RADIUS, -2);//to match the comments in the items.dat file!
830 VectorSet (ent->maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS);
831 }
832
833 if ((item->quantity) && (item->giType == IT_AMMO))
834 {
835 ent->count = item->quantity;
836 }
837
838 if ((item->quantity) && (item->giType == IT_BATTERY))
839 {
840 ent->count = item->quantity;
841 }
842
843
844 // if ( item->giType == IT_WEAPON ) // NOTE: james thought it was ok to just always do this?
845 {
846 ent->s.radius = 20;
847 VectorSet( ent->s.modelScale, 1.0f, 1.0f, 1.0f );
848 gi.G2API_InitGhoul2Model( ent->ghoul2, ent->item->world_model, G_ModelIndex( ent->item->world_model ), NULL_HANDLE, NULL_HANDLE, 0, 0);
849 }
850
851 // Set crystal ammo amount based on skill level
852 /* if ((itemNum == ITM_AMMO_CRYSTAL_BORG) ||
853 (itemNum == ITM_AMMO_CRYSTAL_DN) ||
854 (itemNum == ITM_AMMO_CRYSTAL_FORGE) ||
855 (itemNum == ITM_AMMO_CRYSTAL_SCAVENGER) ||
856 (itemNum == ITM_AMMO_CRYSTAL_STASIS))
857 {
858 CrystalAmmoSettings(ent);
859 }
860 */
861 ent->s.eType = ET_ITEM;
862 ent->s.modelindex = ent->item - bg_itemlist; // store item number in modelindex
863 ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item
864
865 ent->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;//CONTENTS_BODY;//CONTENTS_TRIGGER|
866 ent->e_TouchFunc = touchF_Touch_Item;
867 // useing an item causes it to respawn
868 ent->e_UseFunc = useF_Use_Item;
869 ent->svFlags |= SVF_PLAYER_USABLE;//so player can pick it up
870
871 // Hang in air?
872 ent->s.origin[2] += 1;//just to get it off the damn ground because coplanar = insolid
873 if ( ent->spawnflags & ITMSF_SUSPEND)
874 {
875 // suspended
876 G_SetOrigin( ent, ent->s.origin );
877 }
878 else
879 {
880 // drop to floor
881 VectorSet( dest, ent->s.origin[0], ent->s.origin[1], MIN_WORLD_COORD );
882 gi.trace( &tr, ent->s.origin, ent->mins, ent->maxs, dest, ent->s.number, MASK_SOLID|CONTENTS_PLAYERCLIP, G2_NOCOLLIDE, 0 );
883 if ( tr.startsolid )
884 {
885 if ( &g_entities[tr.entityNum] != NULL )
886 {
887 gi.Printf (S_COLOR_RED"FinishSpawningItem: removing %s startsolid at %s (in a %s)\n", ent->classname, vtos(ent->s.origin), g_entities[tr.entityNum].classname );
888 }
889 else
890 {
891 gi.Printf (S_COLOR_RED"FinishSpawningItem: removing %s startsolid at %s (in a %s)\n", ent->classname, vtos(ent->s.origin) );
892 }
893 assert( 0 && "item starting in solid");
894 #ifndef FINAL_BUILD
895 if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region
896 delayedShutDown = level.time + 100;
897 }
898 #endif
899 G_FreeEntity( ent );
900 return;
901 }
902
903 // allow to ride movers
904 ent->s.groundEntityNum = tr.entityNum;
905
906 G_SetOrigin( ent, tr.endpos );
907 }
908
909 /* ? don't need this
910 // team slaves and targeted items aren't present at start
911 if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) {
912 ent->s.eFlags |= EF_NODRAW;
913 ent->contents = 0;
914 return;
915 }
916 */
917 if ( ent->spawnflags & ITMSF_INVISIBLE ) // invisible
918 {
919 ent->s.eFlags |= EF_NODRAW;
920 ent->contents = 0;
921 }
922
923 if ( ent->spawnflags & ITMSF_NOTSOLID ) // not solid
924 {
925 ent->contents = 0;
926 }
927
928 gi.linkentity (ent);
929 }
930
931
932 char itemRegistered[MAX_ITEMS+1];
933
934
935 /*
936 ==============
937 ClearRegisteredItems
938 ==============
939 */
ClearRegisteredItems(void)940 void ClearRegisteredItems( void ) {
941 for ( int i = 0; i < bg_numItems; i++ )
942 {
943 itemRegistered[i] = '0';
944 }
945 itemRegistered[ bg_numItems ] = 0;
946
947 RegisterItem( FindItemForWeapon( WP_BRYAR_PISTOL ) ); //these are given in g_client, ClientSpawn(), but MUST be registered HERE, BEFORE cgame starts.
948 RegisterItem( FindItemForWeapon( WP_STUN_BATON ) ); //these are given in g_client, ClientSpawn(), but MUST be registered HERE, BEFORE cgame starts.
949 RegisterItem( FindItemForInventory( INV_ELECTROBINOCULARS ));
950 // saber or baton is cached in SP_info_player_deathmatch now.
951
952 extern void Player_CacheFromPrevLevel(void);//g_client.cpp
953 Player_CacheFromPrevLevel(); //reads from transition carry-over;
954 }
955
956 /*
957 ===============
958 RegisterItem
959
960 The item will be added to the precache list
961 ===============
962 */
RegisterItem(gitem_t * item)963 void RegisterItem( gitem_t *item ) {
964 if ( !item ) {
965 G_Error( "RegisterItem: NULL" );
966 }
967 itemRegistered[ item - bg_itemlist ] = '1';
968 gi.SetConfigstring(CS_ITEMS, itemRegistered); //Write the needed items to a config string
969 }
970
971
972 /*
973 ===============
974 SaveRegisteredItems
975
976 Write the needed items to a config string
977 so the client will know which ones to precache
978 ===============
979 */
SaveRegisteredItems(void)980 void SaveRegisteredItems( void ) {
981 /* char string[MAX_ITEMS+1];
982 int i;
983 int count;
984
985 count = 0;
986 for ( i = 0 ; i < bg_numItems ; i++ ) {
987 if ( itemRegistered[i] ) {
988 count++;
989 string[i] = '1';
990 } else {
991 string[i] = '0';
992 }
993 }
994 string[ bg_numItems ] = 0;
995
996 gi.Printf( "%i items registered\n", count );
997 gi.SetConfigstring(CS_ITEMS, string);
998 */
999 gi.SetConfigstring(CS_ITEMS, itemRegistered);
1000 }
1001
1002 /*
1003 ============
1004 item_spawn_use
1005
1006 if an item is given a targetname, it will be spawned in when used
1007 ============
1008 */
item_spawn_use(gentity_t * self,gentity_t * other,gentity_t * activator)1009 void item_spawn_use( gentity_t *self, gentity_t *other, gentity_t *activator )
1010 //-----------------------------------------------------------------------------
1011 {
1012 self->nextthink = level.time + 50;
1013 self->e_ThinkFunc = thinkF_FinishSpawningItem;
1014 // I could be fancy and add a count or something like that to be able to spawn the item numerous times...
1015 self->e_UseFunc = useF_NULL;
1016 }
1017
1018 /*
1019 ============
1020 G_SpawnItem
1021
1022 Sets the clipping size and plants the object on the floor.
1023
1024 Items can't be immediately dropped to floor, because they might
1025 be on an entity that hasn't spawned yet.
1026 ============
1027 */
G_SpawnItem(gentity_t * ent,gitem_t * item)1028 void G_SpawnItem (gentity_t *ent, gitem_t *item) {
1029 G_SpawnFloat( "random", "0", &ent->random );
1030 G_SpawnFloat( "wait", "0", &ent->wait );
1031
1032 RegisterItem( item );
1033 ent->item = item;
1034
1035 // targetname indicates they want to spawn it later
1036 if( ent->targetname )
1037 {
1038 ent->e_UseFunc = useF_item_spawn_use;
1039 }
1040 else
1041 { // some movers spawn on the second frame, so delay item
1042 // spawns until the third frame so they can ride trains
1043 ent->nextthink = level.time + START_TIME_MOVERS_SPAWNED + 50;
1044 ent->e_ThinkFunc = thinkF_FinishSpawningItem;
1045 }
1046
1047 ent->physicsBounce = 0.50; // items are bouncy
1048
1049 // Set a default infoString text color
1050 // NOTE: if we want to do cool cross-hair colors for items, we can just modify this, but for now, don't do it
1051 VectorSet( ent->startRGBA, 1.0f, 1.0f, 1.0f );
1052 }
1053
1054
1055 /*
1056 ================
1057 G_BounceItem
1058
1059 ================
1060 */
G_BounceItem(gentity_t * ent,trace_t * trace)1061 void G_BounceItem( gentity_t *ent, trace_t *trace ) {
1062 vec3_t velocity;
1063 float dot;
1064 int hitTime;
1065
1066 // reflect the velocity on the trace plane
1067 hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
1068 EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
1069 dot = DotProduct( velocity, trace->plane.normal );
1070 VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta );
1071
1072 // cut the velocity to keep from bouncing forever
1073 VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta );
1074
1075 // check for stop
1076 if ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 ) {
1077 G_SetOrigin( ent, trace->endpos );
1078 ent->s.groundEntityNum = trace->entityNum;
1079 return;
1080 }
1081
1082 VectorAdd( ent->currentOrigin, trace->plane.normal, ent->currentOrigin);
1083 VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
1084 ent->s.pos.trTime = level.time;
1085 }
1086
1087
1088 /*
1089 ================
1090 G_RunItem
1091
1092 ================
1093 */
G_RunItem(gentity_t * ent)1094 void G_RunItem( gentity_t *ent ) {
1095 vec3_t origin;
1096 trace_t tr;
1097 int contents;
1098 int mask;
1099
1100 // if groundentity has been set to -1, it may have been pushed off an edge
1101 if ( ent->s.groundEntityNum == ENTITYNUM_NONE )
1102 {
1103 if ( ent->s.pos.trType != TR_GRAVITY )
1104 {
1105 ent->s.pos.trType = TR_GRAVITY;
1106 ent->s.pos.trTime = level.time;
1107 }
1108 }
1109
1110 if ( ent->s.pos.trType == TR_STATIONARY )
1111 {
1112 // check think function
1113 G_RunThink( ent );
1114 if ( !g_gravity->value )
1115 {
1116 ent->s.pos.trType = TR_GRAVITY;
1117 ent->s.pos.trTime = level.time;
1118 ent->s.pos.trDelta[0] += Q_flrand(-1.0f, 1.0f) * 40.0f; // I dunno, just do this??
1119 ent->s.pos.trDelta[1] += Q_flrand(-1.0f, 1.0f) * 40.0f;
1120 ent->s.pos.trDelta[2] += Q_flrand(0.0f, 1.0f) * 20.0f;
1121 }
1122 return;
1123 }
1124
1125 // get current position
1126 EvaluateTrajectory( &ent->s.pos, level.time, origin );
1127
1128 // trace a line from the previous position to the current position
1129 if ( ent->clipmask )
1130 {
1131 mask = ent->clipmask;
1132 }
1133 else
1134 {
1135 mask = MASK_SOLID|CONTENTS_PLAYERCLIP;//shouldn't be able to get anywhere player can't
1136 }
1137
1138 int ignore = ENTITYNUM_NONE;
1139 if ( ent->owner )
1140 {
1141 ignore = ent->owner->s.number;
1142 }
1143 else if ( ent->activator )
1144 {
1145 ignore = ent->activator->s.number;
1146 }
1147 gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, ignore, mask, G2_NOCOLLIDE, 0 );
1148
1149 VectorCopy( tr.endpos, ent->currentOrigin );
1150
1151 if ( tr.startsolid )
1152 {
1153 tr.fraction = 0;
1154 }
1155
1156 gi.linkentity( ent ); // FIXME: avoid this for stationary?
1157
1158 // check think function
1159 G_RunThink( ent );
1160
1161 if ( tr.fraction == 1 )
1162 {
1163 if ( g_gravity->value <= 0 )
1164 {
1165 if ( ent->s.apos.trType != TR_LINEAR )
1166 {
1167 VectorCopy( ent->currentAngles, ent->s.apos.trBase );
1168 ent->s.apos.trType = TR_LINEAR;
1169 ent->s.apos.trDelta[1] = Q_flrand( -300, 300 );
1170 ent->s.apos.trDelta[0] = Q_flrand( -10, 10 );
1171 ent->s.apos.trDelta[2] = Q_flrand( -10, 10 );
1172 ent->s.apos.trTime = level.time;
1173 }
1174 }
1175 //friction in zero-G
1176 if ( !g_gravity->value )
1177 {
1178 float friction = 0.975f;
1179 /*friction -= ent->mass/1000.0f;
1180 if ( friction < 0.1 )
1181 {
1182 friction = 0.1f;
1183 }
1184 */
1185 VectorScale( ent->s.pos.trDelta, friction, ent->s.pos.trDelta );
1186 VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
1187 ent->s.pos.trTime = level.time;
1188 }
1189 return;
1190 }
1191
1192 // if it is in a nodrop volume, remove it
1193 contents = gi.pointcontents( ent->currentOrigin, -1 );
1194 if ( contents & CONTENTS_NODROP )
1195 {
1196 G_FreeEntity( ent );
1197 return;
1198 }
1199
1200 if ( !tr.startsolid )
1201 {
1202 G_BounceItem( ent, &tr );
1203 }
1204 }
1205
1206 /*
1207 ================
1208 ItemUse_Bacta
1209
1210 ================
1211 */
ItemUse_Bacta(gentity_t * ent)1212 void ItemUse_Bacta(gentity_t *ent)
1213 {
1214 if (!ent || !ent->client)
1215 {
1216 return;
1217 }
1218
1219 if (ent->health >= ent->client->ps.stats[STAT_MAX_HEALTH] || !ent->client->ps.inventory[INV_BACTA_CANISTER] )
1220 {
1221 return;
1222 }
1223
1224 ent->health += MAX_BACTA_HEAL_AMOUNT;
1225
1226 if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH])
1227 {
1228 ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
1229 }
1230
1231 ent->client->ps.inventory[INV_BACTA_CANISTER]--;
1232
1233 G_SoundOnEnt( ent, CHAN_VOICE, va( "sound/weapons/force/heal%d.mp3", Q_irand( 1, 4 ) ) );
1234 }
1235