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 #include "g_local.h"
25 #include "g_functions.h"
26 #include "g_items.h"
27 #include "wp_saber.h"
28 #include "../cgame/cg_local.h"
29 #include "b_local.h"
30
31 extern qboolean missionInfo_Updated;
32
33 extern void CrystalAmmoSettings(gentity_t *ent);
34 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
35 extern qboolean PM_InKnockDown( playerState_t *ps );
36 extern qboolean PM_InGetUp( playerState_t *ps );
37 extern void WP_SetSaber( gentity_t *ent, int saberNum, const char *saberName );
38 extern void WP_RemoveSaber( gentity_t *ent, int saberNum );
39 extern void WP_SaberFallSound( gentity_t *owner, gentity_t *saber );
40 extern saber_colors_t TranslateSaberColor( const char *name );
41
42 extern cvar_t *g_spskill;
43 extern cvar_t *g_sex;
44 extern cvar_t *g_saberPickuppableDroppedSabers;
45
46 #define MAX_BACTA_HEAL_AMOUNT 25
47
48 /*
49
50 Items are any object that a player can touch to gain some effect.
51
52 Pickup will return the number of seconds until they should respawn.
53
54 all items should pop when dropped in lava or slime
55
56 Respawnable items don't actually go away when picked up, they are
57 just made invisible and untouchable. This allows them to ride
58 movers and respawn apropriately.
59 */
60
61 // Item Spawn flags
62 #define ITMSF_SUSPEND 1
63 #define ITMSF_NOPLAYER 2
64 #define ITMSF_ALLOWNPC 4
65 #define ITMSF_NOTSOLID 8
66 #define ITMSF_VERTICAL 16
67 #define ITMSF_INVISIBLE 32
68 #define ITMSF_NOGLOW 64
69 #define ITMSF_USEPICKUP 128
70 #define ITMSF_STATIONARY 2048
71
72 //======================================================================
73
74 /*
75 ===============
76 G_InventorySelectable
77 ===============
78 */
G_InventorySelectable(int index,gentity_t * other)79 qboolean G_InventorySelectable( int index,gentity_t *other)
80 {
81 if (other->client->ps.inventory[index])
82 {
83 return qtrue;
84 }
85
86 return qfalse;
87 }
88
89 extern qboolean INV_GoodieKeyGive( gentity_t *target );
90 extern qboolean INV_SecurityKeyGive( gentity_t *target, const char *keyname );
Pickup_Holdable(gentity_t * ent,gentity_t * other)91 int Pickup_Holdable( gentity_t *ent, gentity_t *other )
92 {
93 int i,original;
94
95 other->client->ps.stats[STAT_ITEMS] |= (1<<ent->item->giTag);
96
97 if ( ent->item->giTag == INV_SECURITY_KEY )
98 {//give the key
99 //FIXME: temp message
100 gi.SendServerCommand( 0, "cp @SP_INGAME_YOU_TOOK_SECURITY_KEY" );
101 INV_SecurityKeyGive( other, ent->message );
102 }
103 else if ( ent->item->giTag == INV_GOODIE_KEY )
104 {//give the key
105 //FIXME: temp message
106 gi.SendServerCommand( 0, "cp @SP_INGAME_YOU_TOOK_SUPPLY_KEY" );
107 INV_GoodieKeyGive( other );
108 }
109 else
110 {// Picking up a normal item?
111 other->client->ps.inventory[ent->item->giTag]++;
112 }
113 // Got a security key
114
115 // Set the inventory select, just in case it hasn't
116 original = cg.inventorySelect;
117 for ( i = 0 ; i < INV_MAX ; i++ )
118 {
119 if ((cg.inventorySelect < INV_ELECTROBINOCULARS) || (cg.inventorySelect >= INV_MAX))
120 {
121 cg.inventorySelect = (INV_MAX - 1);
122 }
123
124 if ( G_InventorySelectable( cg.inventorySelect,other ) )
125 {
126 return 60;
127 }
128 cg.inventorySelect++;
129 }
130
131 cg.inventorySelect = original;
132
133 return 60;
134 }
135
136
137 //======================================================================
Add_Ammo2(gentity_t * ent,int ammoType,int count)138 int Add_Ammo2 (gentity_t *ent, int ammoType, int count)
139 {
140
141 if (ammoType != AMMO_FORCE)
142 {
143 ent->client->ps.ammo[ammoType] += count;
144
145 // since the ammo is the weapon in this case, picking up ammo should actually give you the weapon
146 switch( ammoType )
147 {
148 case AMMO_THERMAL:
149 ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_THERMAL );
150 break;
151 case AMMO_DETPACK:
152 ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_DET_PACK );
153 break;
154 case AMMO_TRIPMINE:
155 ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_TRIP_MINE );
156 break;
157 }
158
159 if ( ent->client->ps.ammo[ammoType] > ammoData[ammoType].max )
160 {
161 ent->client->ps.ammo[ammoType] = ammoData[ammoType].max;
162 return qfalse;
163 }
164 }
165 else
166 {
167 if ( ent->client->ps.forcePower >= ammoData[ammoType].max )
168 {//if have full force, just get 25 extra per crystal
169 ent->client->ps.forcePower += 25;
170 }
171 else
172 {//else if don't have full charge, give full amount, up to max + 25
173 ent->client->ps.forcePower += count;
174 if ( ent->client->ps.forcePower >= ammoData[ammoType].max + 25 )
175 {//cap at max + 25
176 ent->client->ps.forcePower = ammoData[ammoType].max + 25;
177 }
178 }
179
180 if ( ent->client->ps.forcePower >= ammoData[ammoType].max*2 )
181 {//always cap at twice a full charge
182 ent->client->ps.forcePower = ammoData[ammoType].max*2;
183 return qfalse; // can't hold any more
184 }
185 }
186 return qtrue;
187 }
188
189 //-------------------------------------------------------
Add_Ammo(gentity_t * ent,int weapon,int count)190 void Add_Ammo (gentity_t *ent, int weapon, int count)
191 {
192 Add_Ammo2(ent,weaponData[weapon].ammoIndex,count);
193 }
194
195 //-------------------------------------------------------
Pickup_Ammo(gentity_t * ent,gentity_t * other)196 int Pickup_Ammo (gentity_t *ent, gentity_t *other)
197 {
198 int quantity;
199
200 if ( ent->count ) {
201 quantity = ent->count;
202 } else {
203 quantity = ent->item->quantity;
204 }
205
206 Add_Ammo2 (other, ent->item->giTag, quantity);
207
208 return 30;
209 }
210
211 //======================================================================
Add_Batteries(gentity_t * ent,int * count)212 void Add_Batteries( gentity_t *ent, int *count )
213 {
214 if ( ent->client && ent->client->ps.batteryCharge < MAX_BATTERIES && *count )
215 {
216 if ( *count + ent->client->ps.batteryCharge > MAX_BATTERIES )
217 {
218 // steal what we need, then leave the rest for later
219 *count -= ( MAX_BATTERIES - ent->client->ps.batteryCharge );
220 ent->client->ps.batteryCharge = MAX_BATTERIES;
221 }
222 else
223 {
224 // just drain all of the batteries
225 ent->client->ps.batteryCharge += *count;
226 *count = 0;
227 }
228
229 G_AddEvent( ent, EV_BATTERIES_CHARGED, 0 );
230 }
231 }
232
233 //-------------------------------------------------------
Pickup_Battery(gentity_t * ent,gentity_t * other)234 int Pickup_Battery( gentity_t *ent, gentity_t *other )
235 {
236 int quantity;
237
238 if ( ent->count )
239 {
240 quantity = ent->count;
241 }
242 else
243 {
244 quantity = ent->item->quantity;
245 }
246
247 // 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
248 Add_Batteries( other, &quantity );
249
250 return 30;
251 }
252
253 //======================================================================
254
G_CopySaberItemValues(gentity_t * pickUpSaber,gentity_t * oldSaber)255 void G_CopySaberItemValues( gentity_t *pickUpSaber, gentity_t *oldSaber )
256 {
257 if ( oldSaber && pickUpSaber )
258 {
259 oldSaber->spawnflags = pickUpSaber->spawnflags;
260 oldSaber->random = pickUpSaber->random;
261 oldSaber->flags = pickUpSaber->flags;
262 }
263 }
264
G_DropSaberItem(const char * saberType,saber_colors_t saberColor,vec3_t saberPos,vec3_t saberVel,vec3_t saberAngles,gentity_t * copySaber)265 gentity_t *G_DropSaberItem( const char *saberType, saber_colors_t saberColor, vec3_t saberPos, vec3_t saberVel, vec3_t saberAngles, gentity_t *copySaber )
266 {//turn it into a pick-uppable item!
267 gentity_t *newItem = NULL;
268 if ( saberType
269 && saberType[0] )
270 {//have a valid string to use for saberType
271 newItem = G_Spawn();
272 if ( newItem )
273 {
274 newItem->classname = G_NewString( "weapon_saber" );
275 VectorCopy( saberPos, newItem->s.origin );
276 G_SetOrigin( newItem, newItem->s.origin );
277 VectorCopy( saberAngles, newItem->s.angles );
278 G_SetAngles( newItem, newItem->s.angles );
279 newItem->spawnflags = 128;/*ITMSF_USEPICKUP*/
280 newItem->spawnflags |= 64;/*ITMSF_NOGLOW*/
281 newItem->NPC_type = G_NewString( saberType );//saberType
282 //FIXME: transfer per-blade color somehow?
283 newItem->NPC_targetname = (char *)saberColorStringForColor[saberColor];
284 newItem->count = 1;
285 newItem->flags = FL_DROPPED_ITEM;
286 G_SpawnItem( newItem, FindItemForWeapon( WP_SABER ) );
287 newItem->s.pos.trType = TR_GRAVITY;
288 newItem->s.pos.trTime = level.time;
289 VectorCopy( saberVel, newItem->s.pos.trDelta );
290 //newItem->s.eFlags |= EF_BOUNCE_HALF;
291 //copy some values from another saber, if provided:
292 G_CopySaberItemValues( copySaber, newItem );
293 //don't *think* about calling FinishSpawningItem, just do it!
294 newItem->e_ThinkFunc = thinkF_NULL;
295 newItem->nextthink = -1;
296 FinishSpawningItem( newItem );
297 newItem->delay = level.time + 500;//so you can't pick it back up right away
298 }
299 }
300 return newItem;
301 }
302
303 extern void G_SetSabersFromCVars( gentity_t *ent );
Pickup_Saber(gentity_t * self,qboolean hadSaber,gentity_t * pickUpSaber)304 qboolean Pickup_Saber( gentity_t *self, qboolean hadSaber, gentity_t *pickUpSaber )
305 {
306 //NOTE: loopAnim = saberSolo, alt_fire = saberLeftHand, NPC_type = saberType, NPC_targetname = saberColor
307 qboolean foundIt = qfalse;
308
309 if ( !pickUpSaber || !self || !self->client )
310 {
311 return qfalse;
312 }
313
314 //G_RemoveWeaponModels( ent );//???
315 if ( Q_stricmp( "player", pickUpSaber->NPC_type ) == 0 )
316 {//"player" means use cvar info
317 G_SetSabersFromCVars( self );
318 foundIt = qtrue;
319 }
320 else
321 {
322 saberInfo_t newSaber={0};
323 qboolean swapSabers = qfalse;
324
325 if ( self->client->ps.weapon == WP_SABER
326 && self->client->ps.weaponTime > 0 )
327 {//can't pick up a new saber while the old one is busy (also helps to work as a debouncer so you don't swap out sabers rapidly when touching more than one at a time)
328 return qfalse;
329 }
330
331 if ( pickUpSaber->count == 1
332 && g_saberPickuppableDroppedSabers->integer )
333 {
334 swapSabers = qtrue;
335 }
336
337 if ( WP_SaberParseParms( pickUpSaber->NPC_type, &newSaber ) )
338 {//successfully found a saber .sab entry to use
339 int saberNum = 0;
340 qboolean removeLeftSaber = qfalse;
341 if ( pickUpSaber->alt_fire )
342 {//always go in the left hand
343 if ( !hadSaber )
344 {//can't have a saber only in your left hand!
345 return qfalse;
346 }
347 saberNum = 1;
348 //just in case...
349 removeLeftSaber = qtrue;
350 }
351 else if ( !hadSaber )
352 {//don't have a saber at all yet, put it in our right hand
353 saberNum = 0;
354 //just in case...
355 removeLeftSaber = qtrue;
356 }
357 else if ( pickUpSaber->loopAnim//only supposed to use this one saber when grab this pickup
358 || (newSaber.saberFlags&SFL_TWO_HANDED) //new saber is two-handed
359 || (hadSaber && (self->client->ps.saber[0].saberFlags&SFL_TWO_HANDED)) )//old saber is two-handed
360 {//replace the old right-hand saber and remove the left hand one
361 saberNum = 0;
362 removeLeftSaber = qtrue;
363 }
364 else
365 {//have, at least, a saber in our right hand and the new one could go in either left or right hand
366 if ( self->client->ps.dualSabers )
367 {//I already have 2 sabers
368 vec3_t dir2Saber, rightDir;
369 //to determine which one to replace, see which side of me it's on
370 VectorSubtract( pickUpSaber->currentOrigin, self->currentOrigin, dir2Saber );
371 dir2Saber[2] = 0;
372 AngleVectors( self->currentAngles, NULL, rightDir, NULL );
373 rightDir[2] = 0;
374 if ( DotProduct( rightDir, dir2Saber ) > 0 )
375 {
376 saberNum = 0;
377 }
378 else
379 {
380 saberNum = 1;
381 //just in case...
382 removeLeftSaber = qtrue;
383 }
384 }
385 else
386 {//just add it as a second saber
387 saberNum = 1;
388 //just in case...
389 removeLeftSaber = qtrue;
390 }
391 }
392 if ( saberNum == 0 )
393 {//want to reach out with right hand
394 if ( self->client->ps.torsoAnim == BOTH_BUTTON_HOLD )
395 {//but only if already playing the pickup with left hand anim...
396 NPC_SetAnim( self, SETANIM_TORSO, BOTH_SABERPULL, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
397 }
398 if ( swapSabers )
399 {//drop first one where the one we're picking up is
400 G_DropSaberItem( self->client->ps.saber[saberNum].name, self->client->ps.saber[saberNum].blade[0].color, pickUpSaber->currentOrigin, (float *)vec3_origin, pickUpSaber->currentAngles, pickUpSaber );
401 if ( removeLeftSaber )
402 {//drop other one at my origin
403 G_DropSaberItem( self->client->ps.saber[1].name, self->client->ps.saber[1].blade[0].color, self->currentOrigin, (float *)vec3_origin, self->currentAngles, pickUpSaber );
404 }
405 }
406 }
407 else
408 {
409 if ( swapSabers )
410 {
411 G_DropSaberItem( self->client->ps.saber[saberNum].name, self->client->ps.saber[saberNum].blade[0].color, pickUpSaber->currentOrigin, (float *)vec3_origin, pickUpSaber->currentAngles, pickUpSaber );
412 }
413 }
414 if ( removeLeftSaber )
415 {
416 WP_RemoveSaber( self, 1 );
417 }
418 WP_SetSaber( self, saberNum, pickUpSaber->NPC_type );
419 WP_SaberInitBladeData( self );
420 if ( self->client->ps.saber[saberNum].stylesLearned )
421 {
422 self->client->ps.saberStylesKnown |= self->client->ps.saber[saberNum].stylesLearned;
423 }
424 if ( self->client->ps.saber[saberNum].singleBladeStyle )
425 {
426 self->client->ps.saberStylesKnown |= self->client->ps.saber[saberNum].singleBladeStyle;
427 }
428 if ( pickUpSaber->NPC_targetname != NULL )
429 {//NPC_targetname = saberColor
430 saber_colors_t saber_color = TranslateSaberColor( pickUpSaber->NPC_targetname );
431 for ( int bladeNum = 0; bladeNum < MAX_BLADES; bladeNum++ )
432 {
433 self->client->ps.saber[saberNum].blade[bladeNum].color = saber_color;
434 }
435 }
436 if ( self->client->ps.torsoAnim == BOTH_BUTTON_HOLD
437 || self->client->ps.torsoAnim == BOTH_SABERPULL )
438 {//don't let them attack right away, force them to finish the anim
439 self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
440 }
441 foundIt = qtrue;
442 }
443 WP_SaberFreeStrings(newSaber);
444 }
445 return foundIt;
446 }
447
448 extern void CG_ChangeWeapon( int num );
Pickup_Weapon(gentity_t * ent,gentity_t * other)449 int Pickup_Weapon (gentity_t *ent, gentity_t *other)
450 {
451 int quantity;
452 qboolean hadWeapon = qfalse;
453
454 /*
455 if ( ent->count || (ent->activator && !ent->activator->s.number) )
456 {
457 quantity = ent->count;
458 }
459 else
460 {
461 quantity = ent->item->quantity;
462 }
463 */
464
465 // dropped items are always picked up
466 if ( ent->flags & FL_DROPPED_ITEM )
467 {
468 quantity = ent->count;
469 }
470 else
471 {//wasn't dropped
472 quantity = ent->item->quantity?ent->item->quantity:50;
473 }
474
475 // add the weapon
476 if ( other->client->ps.stats[STAT_WEAPONS] & ( 1 << ent->item->giTag ) )
477 {
478 hadWeapon = qtrue;
479 }
480 other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag );
481
482 if ( ent->item->giTag == WP_SABER && (!hadWeapon || ent->NPC_type != NULL) )
483 {//didn't have a saber or it is specifying a certain kind of saber to use
484 if ( !Pickup_Saber( other, hadWeapon, ent ) )
485 {
486 return 0;
487 }
488 }
489
490 if ( other->s.number )
491 {//NPC
492 if ( other->s.weapon == WP_NONE
493 || ent->item->giTag == WP_SABER )
494 {//NPC with no weapon picked up a weapon, change to this weapon
495 //FIXME: clear/set the alt-fire flag based on the picked up weapon and my class?
496 other->client->ps.weapon = ent->item->giTag;
497 other->client->ps.weaponstate = WEAPON_RAISING;
498 ChangeWeapon( other, ent->item->giTag );
499 if ( ent->item->giTag == WP_SABER )
500 {
501 other->client->ps.SaberActivate();
502 WP_SaberAddG2SaberModels( other );
503 }
504 else
505 {
506 G_CreateG2AttachedWeaponModel( other, weaponData[ent->item->giTag].weaponMdl, other->handRBolt, 0 );
507 }
508 }
509 }
510 if ( ent->item->giTag == WP_SABER )
511 {//picked up a saber
512 if ( other->s.weapon != WP_SABER )
513 {//player picking up saber
514 other->client->ps.weapon = WP_SABER;
515 other->client->ps.weaponstate = WEAPON_RAISING;
516 if ( other->s.number < MAX_CLIENTS )
517 {//make sure the cgame-side knows this
518 CG_ChangeWeapon( WP_SABER );
519 }
520 else
521 {//make sure the cgame-side knows this
522 ChangeWeapon( other, WP_SABER );
523 }
524 }
525 if ( !other->client->ps.SaberActive() )
526 {//turn it/them on!
527 other->client->ps.SaberActivate();
528 }
529 }
530
531 if ( quantity )
532 {
533 // Give ammo
534 Add_Ammo( other, ent->item->giTag, quantity );
535 }
536 return 5;
537 }
538
539
540 //======================================================================
541
ITM_AddHealth(gentity_t * ent,int count)542 int ITM_AddHealth (gentity_t *ent, int count)
543 {
544
545 ent->health += count;
546
547 if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH]) // Past max health
548 {
549 ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
550
551 return qfalse;
552 }
553
554 return qtrue;
555
556 }
557
Pickup_Health(gentity_t * ent,gentity_t * other)558 int Pickup_Health (gentity_t *ent, gentity_t *other) {
559 int max;
560 int quantity;
561
562 max = other->client->ps.stats[STAT_MAX_HEALTH];
563
564 if ( ent->count ) {
565 quantity = ent->count;
566 } else {
567 quantity = ent->item->quantity;
568 }
569
570 other->health += quantity;
571
572 if (other->health > max ) {
573 other->health = max;
574 }
575
576 if ( ent->item->giTag == 100 ) { // mega health respawns slow
577 return 120;
578 }
579
580 return 30;
581 }
582
583 //======================================================================
584
ITM_AddArmor(gentity_t * ent,int count)585 int ITM_AddArmor (gentity_t *ent, int count)
586 {
587
588 ent->client->ps.stats[STAT_ARMOR] += count;
589
590 if (ent->client->ps.stats[STAT_ARMOR] > ent->client->ps.stats[STAT_MAX_HEALTH])
591 {
592 ent->client->ps.stats[STAT_ARMOR] = ent->client->ps.stats[STAT_MAX_HEALTH];
593 return qfalse;
594 }
595
596 return qtrue;
597 }
598
599
Pickup_Armor(gentity_t * ent,gentity_t * other)600 int Pickup_Armor( gentity_t *ent, gentity_t *other ) {
601
602 // make sure that the shield effect is on
603 other->client->ps.powerups[PW_BATTLESUIT] = Q3_INFINITE;
604
605 other->client->ps.stats[STAT_ARMOR] += ent->item->quantity;
606 if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] ) {
607 other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH];
608 }
609
610 return 30;
611 }
612
613
614
615 //======================================================================
616
Pickup_Holocron(gentity_t * ent,gentity_t * other)617 int Pickup_Holocron( gentity_t *ent, gentity_t *other )
618 {
619 int forcePower = ent->item->giTag;
620 int forceLevel = ent->count;
621 // check if out of range
622 if( forceLevel < 0 || forceLevel >= NUM_FORCE_POWER_LEVELS )
623 {
624 gi.Printf(" Pickup_Holocron : count %d not in valid range\n", forceLevel );
625 return 1;
626 }
627
628 // don't pick up if already known AND your level is higher than pickup level
629 if ( ( other->client->ps.forcePowersKnown & ( 1 << forcePower )) )
630 {
631 //don't pickup if item is lower than current level
632 if( other->client->ps.forcePowerLevel[forcePower] >= forceLevel )
633 {
634 return 1;
635 }
636 }
637
638 other->client->ps.forcePowerLevel[forcePower] = forceLevel;
639 other->client->ps.forcePowersKnown |= ( 1 << forcePower );
640
641 missionInfo_Updated = qtrue; // Activate flashing text
642 gi.cvar_set("cg_updatedDataPadForcePower1", va("%d",forcePower+1)); // The +1 is offset in the print routine.
643 cg_updatedDataPadForcePower1.integer = forcePower+1;
644 gi.cvar_set("cg_updatedDataPadForcePower2", "0"); // The +1 is offset in the print routine.
645 cg_updatedDataPadForcePower2.integer = 0;
646 gi.cvar_set("cg_updatedDataPadForcePower3", "0"); // The +1 is offset in the print routine.
647 cg_updatedDataPadForcePower3.integer = 0;
648
649 return 1;
650 }
651
652
653 //======================================================================
654
655 /*
656 ===============
657 RespawnItem
658 ===============
659 */
RespawnItem(gentity_t * ent)660 void RespawnItem( gentity_t *ent ) {
661 }
662
663
CheckItemCanBePickedUpByNPC(gentity_t * item,gentity_t * pickerupper)664 qboolean CheckItemCanBePickedUpByNPC( gentity_t *item, gentity_t *pickerupper )
665 {
666 if ( !item->item ) {
667 return qfalse;
668 }
669 if ( item->item->giType == IT_HOLDABLE &&
670 item->item->giTag == INV_SECURITY_KEY ) {
671 return qfalse;
672 }
673 if ( (item->flags&FL_DROPPED_ITEM)
674 && item->activator != &g_entities[0]
675 && pickerupper->s.number
676 && pickerupper->s.weapon == WP_NONE
677 && pickerupper->enemy
678 && pickerupper->painDebounceTime < level.time
679 && pickerupper->NPC && pickerupper->NPC->surrenderTime < level.time //not surrendering
680 && !(pickerupper->NPC->scriptFlags&SCF_FORCED_MARCH) ) // not being forced to march
681 {//non-player, in combat, picking up a dropped item that does NOT belong to the player and it *not* a security key
682 if ( level.time - item->s.time < 3000 )//was 5000
683 {
684 return qfalse;
685 }
686 return qtrue;
687 }
688 return qfalse;
689 }
690
G_CanPickUpWeapons(gentity_t * other)691 qboolean G_CanPickUpWeapons( gentity_t *other )
692 {
693 if ( !other || !other->client )
694 {
695 return qfalse;
696 }
697 switch ( other->client->NPC_class )
698 {
699 case CLASS_ATST:
700 case CLASS_GONK:
701 case CLASS_MARK1:
702 case CLASS_MARK2:
703 case CLASS_MOUSE:
704 case CLASS_PROBE:
705 case CLASS_PROTOCOL:
706 case CLASS_R2D2:
707 case CLASS_R5D2:
708 case CLASS_SEEKER:
709 case CLASS_REMOTE:
710 case CLASS_RANCOR:
711 case CLASS_WAMPA:
712 case CLASS_JAWA: //FIXME: in some cases it's okay?
713 case CLASS_UGNAUGHT: //FIXME: in some cases it's okay?
714 case CLASS_SENTRY:
715 return qfalse;
716 break;
717 default:
718 break;
719 }
720 return qtrue;
721 }
722 /*
723 ===============
724 Touch_Item
725 ===============
726 */
727 extern cvar_t *g_timescale;
Touch_Item(gentity_t * ent,gentity_t * other,trace_t * trace)728 void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) {
729 int respawn = 0;
730
731 if (!other->client)
732 return;
733 if (other->health < 1)
734 return; // dead people can't pickup
735
736 if ( other->client->ps.pm_time > 0 )
737 {//cant pick up when out of control
738 return;
739 }
740
741 // NPCs can pick it up
742 if ((ent->spawnflags & ITMSF_ALLOWNPC) && (!other->s.number))
743 {
744 return;
745 }
746
747 // Players cannot pick it up
748 if ( (ent->spawnflags & ITMSF_NOPLAYER) && (other->s.number) )
749 {
750 return;
751 }
752
753 if ( ent->noDamageTeam != TEAM_FREE && other->client->playerTeam != ent->noDamageTeam )
754 {//only one team can pick it up
755 return;
756 }
757
758 if ( !G_CanPickUpWeapons( other ) )
759 {//FIXME: some flag would be better
760 //droids can't pick up items/weapons!
761 return;
762 }
763
764 //FIXME: need to make them run toward a dropped weapon when fleeing without one?
765 //FIXME: need to make them come out of flee mode when pick up their old weapon?
766 if ( CheckItemCanBePickedUpByNPC( ent, other ) )
767 {
768 if ( other->NPC && other->NPC->goalEntity && other->NPC->goalEntity == ent )
769 {//they were running to pick me up, they did, so clear goal
770 other->NPC->goalEntity = NULL;
771 other->NPC->squadState = SQUAD_STAND_AND_SHOOT;
772 NPCInfo->tempBehavior = BS_DEFAULT;
773 TIMER_Set(other, "flee", -1);
774 }
775 else
776 {
777 return;
778 }
779 }
780 else if ( !(ent->spawnflags & ITMSF_ALLOWNPC) )
781 {// NPCs cannot pick it up
782 if ( other->s.number != 0 )
783 {// Not the player?
784 return;
785 }
786 }
787
788 // the same pickup rules are used for client side and server side
789 if ( !BG_CanItemBeGrabbed( &ent->s, &other->client->ps ) ) {
790 return;
791 }
792
793 if ( other->client )
794 {
795 if ( (other->client->ps.eFlags&EF_FORCE_GRIPPED) || (other->client->ps.eFlags&EF_FORCE_DRAINED) )
796 {//can't pick up anything while being gripped
797 return;
798 }
799 if ( PM_InKnockDown( &other->client->ps ) && !PM_InGetUp( &other->client->ps ) )
800 {//can't pick up while in a knockdown
801 return;
802 }
803 }
804 if (!ent->item) { //not an item!
805 gi.Printf( "Touch_Item: %s is not an item!\n", ent->classname);
806 return;
807 }
808
809 if ( ent->item->giType == IT_WEAPON
810 && ent->item->giTag == WP_SABER )
811 {//a saber
812 if ( ent->delay > level.time )
813 {//just picked it up, don't pick up again right away
814 return;
815 }
816 }
817
818 if ( other->s.number < MAX_CLIENTS
819 && (ent->spawnflags&ITMSF_USEPICKUP) )
820 {//only if player is holing use button
821 if ( !(other->client->usercmd.buttons&BUTTON_USE) )
822 {//not holding use?
823 return;
824 }
825 }
826
827 qboolean bHadWeapon = qfalse;
828 // call the item-specific pickup function
829 switch( ent->item->giType )
830 {
831 case IT_WEAPON:
832 if ( other->NPC && other->s.weapon == WP_NONE )
833 {//Make them duck and sit here for a few seconds
834 int pickUpTime = Q_irand( 1000, 3000 );
835 TIMER_Set( other, "duck", pickUpTime );
836 TIMER_Set( other, "roamTime", pickUpTime );
837 TIMER_Set( other, "stick", pickUpTime );
838 TIMER_Set( other, "verifyCP", pickUpTime );
839 TIMER_Set( other, "attackDelay", 600 );
840 respawn = 0;
841 }
842 if ( other->client->ps.stats[STAT_WEAPONS] & ( 1 << ent->item->giTag ) )
843 {
844 bHadWeapon = qtrue;
845 }
846 respawn = Pickup_Weapon(ent, other);
847 break;
848 case IT_AMMO:
849 respawn = Pickup_Ammo(ent, other);
850 break;
851 case IT_ARMOR:
852 respawn = Pickup_Armor(ent, other);
853 break;
854 case IT_HEALTH:
855 respawn = Pickup_Health(ent, other);
856 break;
857 case IT_HOLDABLE:
858 respawn = Pickup_Holdable(ent, other);
859 break;
860 case IT_BATTERY:
861 respawn = Pickup_Battery( ent, other );
862 break;
863 case IT_HOLOCRON:
864 respawn = Pickup_Holocron( ent, other );
865 break;
866 default:
867 return;
868 }
869
870 if ( !respawn )
871 {
872 return;
873 }
874
875 // play the normal pickup sound
876 if ( !other->s.number && g_timescale->value < 1.0f )
877 {//SIGH... with timescale on, you lose events left and right
878 extern void CG_ItemPickup( int itemNum, qboolean bHadItem );
879 // but we're SP so we'll cheat
880 cgi_S_StartSound( NULL, other->s.number, CHAN_AUTO, cgi_S_RegisterSound( ent->item->pickup_sound ) );
881 // show icon and name on status bar
882 CG_ItemPickup( ent->s.modelindex, bHadWeapon );
883 }
884 else
885 {
886 if ( bHadWeapon )
887 {
888 G_AddEvent( other, EV_ITEM_PICKUP, -ent->s.modelindex );
889 }
890 else
891 {
892 G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex );
893 }
894 }
895
896 // fire item targets
897 G_UseTargets (ent, other);
898
899 if ( ent->item->giType == IT_WEAPON
900 && ent->item->giTag == WP_SABER )
901 {//a saber that was picked up
902 if ( ent->count < 0 )
903 {//infinite supply
904 ent->delay = level.time + 500;
905 return;
906 }
907 ent->count--;
908 if ( ent->count > 0 )
909 {//still have more to pick up
910 ent->delay = level.time + 500;
911 return;
912 }
913 }
914 // wait of -1 will not respawn
915 // if ( ent->wait == -1 )
916 {
917 //why not just remove me?
918 G_FreeEntity( ent );
919 /*
920 //NOTE: used to do this: (for respawning?)
921 ent->svFlags |= SVF_NOCLIENT;
922 ent->s.eFlags |= EF_NODRAW;
923 ent->contents = 0;
924 ent->unlinkAfterEvent = qtrue;
925 */
926 return;
927 }
928 }
929
930
931 //======================================================================
932
933 /*
934 ================
935 LaunchItem
936
937 Spawns an item and tosses it forward
938 ================
939 */
LaunchItem(gitem_t * item,const vec3_t origin,const vec3_t velocity,char * target)940 gentity_t *LaunchItem( gitem_t *item, const vec3_t origin, const vec3_t velocity, char *target ) {
941 gentity_t *dropped;
942
943 dropped = G_Spawn();
944
945 dropped->s.eType = ET_ITEM;
946 dropped->s.modelindex = item - bg_itemlist; // store item number in modelindex
947 dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item
948
949 dropped->classname = G_NewString(item->classname); //copy it so it can be freed safely
950 dropped->item = item;
951
952 // try using the "correct" mins/maxs first
953 VectorSet( dropped->mins, item->mins[0], item->mins[1], item->mins[2] );
954 VectorSet( dropped->maxs, item->maxs[0], item->maxs[1], item->maxs[2] );
955
956 if ((!dropped->mins[0] && !dropped->mins[1] && !dropped->mins[2]) &&
957 (!dropped->maxs[0] && !dropped->maxs[1] && !dropped->maxs[2]))
958 {
959 VectorSet( dropped->maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );
960 VectorScale( dropped->maxs, -1, dropped->mins );
961 }
962
963 dropped->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;//CONTENTS_TRIGGER;//not CONTENTS_BODY for dropped items, don't need to ID them
964
965 if ( target && target[0] )
966 {
967 dropped->target = G_NewString( target );
968 }
969 else
970 {
971 // if not targeting something, auto-remove after 30 seconds
972 // only if it's NOT a security or goodie key
973 if (dropped->item->giTag != INV_SECURITY_KEY )
974 {
975 dropped->e_ThinkFunc = thinkF_G_FreeEntity;
976 dropped->nextthink = level.time + 30000;
977 }
978
979 if ( dropped->item->giType == IT_AMMO && dropped->item->giTag == AMMO_FORCE )
980 {
981 dropped->nextthink = -1;
982 dropped->e_ThinkFunc = thinkF_NULL;
983 }
984 }
985
986 dropped->e_TouchFunc = touchF_Touch_Item;
987
988 if ( item->giType == IT_WEAPON )
989 {
990 // give weapon items zero pitch, a random yaw, and rolled onto their sides...but would be bad to do this for a bowcaster
991 if ( item->giTag != WP_BOWCASTER
992 && item->giTag != WP_THERMAL
993 && item->giTag != WP_TRIP_MINE
994 && item->giTag != WP_DET_PACK )
995 {
996 VectorSet( dropped->s.angles, 0, Q_flrand(-1.0f, 1.0f) * 180, 90.0f );
997 G_SetAngles( dropped, dropped->s.angles );
998 }
999 }
1000
1001 G_SetOrigin( dropped, origin );
1002 dropped->s.pos.trType = TR_GRAVITY;
1003 dropped->s.pos.trTime = level.time;
1004 VectorCopy( velocity, dropped->s.pos.trDelta );
1005
1006 dropped->s.eFlags |= EF_BOUNCE_HALF;
1007
1008 dropped->flags = FL_DROPPED_ITEM;
1009
1010 gi.linkentity (dropped);
1011
1012 return dropped;
1013 }
1014
1015 /*
1016 ================
1017 Drop_Item
1018
1019 Spawns an item and tosses it forward
1020 ================
1021 */
Drop_Item(gentity_t * ent,gitem_t * item,float angle,qboolean copytarget)1022 gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle, qboolean copytarget ) {
1023 gentity_t *dropped = NULL;
1024 vec3_t velocity;
1025 vec3_t angles;
1026
1027 VectorCopy( ent->s.apos.trBase, angles );
1028 angles[YAW] += angle;
1029 angles[PITCH] = 0; // always forward
1030
1031 AngleVectors( angles, velocity, NULL, NULL );
1032 VectorScale( velocity, 150, velocity );
1033 velocity[2] += 200 + Q_flrand(-1.0f, 1.0f) * 50;
1034
1035 if ( copytarget )
1036 {
1037 dropped = LaunchItem( item, ent->s.pos.trBase, velocity, ent->opentarget );
1038 }
1039 else
1040 {
1041 dropped = LaunchItem( item, ent->s.pos.trBase, velocity, NULL );
1042 }
1043
1044 dropped->activator = ent;//so we know who we belonged to so they can pick it back up later
1045 dropped->s.time = level.time;//mark this time so we aren't picked up instantly by the guy who dropped us
1046 return dropped;
1047 }
1048
1049
1050 /*
1051 ================
1052 Use_Item
1053
1054 Respawn the item
1055 ================
1056 */
Use_Item(gentity_t * ent,gentity_t * other,gentity_t * activator)1057 void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator )
1058 {
1059 if ( (ent->svFlags&SVF_PLAYER_USABLE) && other && !other->s.number )
1060 {//used directly by the player, pick me up
1061 if ( (ent->spawnflags&ITMSF_USEPICKUP) )
1062 {//player has to be touching me and hit use to pick it up, so don't allow this
1063 if ( !G_BoundsOverlap( ent->absmin, ent->absmax, other->absmin, other->absmax ) )
1064 {//not touching
1065 return;
1066 }
1067 }
1068 GEntity_TouchFunc( ent, other, NULL );
1069 }
1070 else
1071 {//use me
1072 if ( ent->spawnflags & 32 ) // invisible
1073 {
1074 // If it was invisible, first use makes it visible....
1075 ent->s.eFlags &= ~EF_NODRAW;
1076 ent->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;
1077
1078 ent->spawnflags &= ~32;
1079 return;
1080 }
1081
1082 G_ActivateBehavior( ent, BSET_USE );
1083 RespawnItem( ent );
1084 }
1085 }
1086
1087 //======================================================================
1088
1089 /*
1090 ================
1091 FinishSpawningItem
1092
1093 Traces down to find where an item should rest, instead of letting them
1094 free fall from their spawn points
1095 ================
1096 */
1097 extern int delayedShutDown;
1098 extern cvar_t *g_saber;
FinishSpawningItem(gentity_t * ent)1099 void FinishSpawningItem( gentity_t *ent ) {
1100 trace_t tr;
1101 vec3_t dest;
1102 gitem_t *item;
1103 int itemNum;
1104
1105 itemNum=1;
1106 for ( item = bg_itemlist + 1 ; item->classname ; item++,itemNum++)
1107 {
1108 if (!strcmp(item->classname,ent->classname))
1109 {
1110 break;
1111 }
1112 }
1113
1114 // Set bounding box for item
1115 VectorSet( ent->mins, item->mins[0],item->mins[1] ,item->mins[2]);
1116 VectorSet( ent->maxs, item->maxs[0],item->maxs[1] ,item->maxs[2]);
1117
1118 if ((!ent->mins[0] && !ent->mins[1] && !ent->mins[2]) &&
1119 (!ent->maxs[0] && !ent->maxs[1] && !ent->maxs[2]))
1120 {
1121 VectorSet (ent->mins, -ITEM_RADIUS, -ITEM_RADIUS, -2);//to match the comments in the items.dat file!
1122 VectorSet (ent->maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS);
1123 }
1124
1125 if ((item->quantity) && (item->giType == IT_AMMO))
1126 {
1127 ent->count = item->quantity;
1128 }
1129
1130 if ((item->quantity) && (item->giType == IT_BATTERY))
1131 {
1132 ent->count = item->quantity;
1133 }
1134
1135 ent->s.radius = 20;
1136 VectorSet( ent->s.modelScale, 1.0f, 1.0f, 1.0f );
1137
1138 if ( ent->item->giType == IT_WEAPON
1139 && ent->item->giTag == WP_SABER
1140 && ent->NPC_type
1141 && ent->NPC_type[0] )
1142 {
1143 saberInfo_t itemSaber;
1144 if ( Q_stricmp( "player", ent->NPC_type ) == 0
1145 && g_saber->string
1146 && g_saber->string[0]
1147 && Q_stricmp( "none", g_saber->string )
1148 && Q_stricmp( "NULL", g_saber->string ) )
1149 {//player's saber
1150 WP_SaberParseParms( g_saber->string, &itemSaber );
1151 }
1152 else
1153 {//specific saber
1154 WP_SaberParseParms( ent->NPC_type, &itemSaber );
1155 }
1156 //NOTE: should I keep this string around for any reason? Will I ever need it later?
1157 //ent->??? = G_NewString( itemSaber.model );
1158 gi.G2API_InitGhoul2Model( ent->ghoul2, itemSaber.model, G_ModelIndex( itemSaber.model ), NULL_HANDLE, NULL_HANDLE, 0, 0);
1159 WP_SaberFreeStrings(itemSaber);
1160 }
1161 else
1162 {
1163 gi.G2API_InitGhoul2Model( ent->ghoul2, ent->item->world_model, G_ModelIndex( ent->item->world_model ), NULL_HANDLE, NULL_HANDLE, 0, 0);
1164 }
1165
1166 // Set crystal ammo amount based on skill level
1167 /* if ((itemNum == ITM_AMMO_CRYSTAL_BORG) ||
1168 (itemNum == ITM_AMMO_CRYSTAL_DN) ||
1169 (itemNum == ITM_AMMO_CRYSTAL_FORGE) ||
1170 (itemNum == ITM_AMMO_CRYSTAL_SCAVENGER) ||
1171 (itemNum == ITM_AMMO_CRYSTAL_STASIS))
1172 {
1173 CrystalAmmoSettings(ent);
1174 }
1175 */
1176 ent->s.eType = ET_ITEM;
1177 ent->s.modelindex = ent->item - bg_itemlist; // store item number in modelindex
1178 ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item
1179
1180 ent->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;//CONTENTS_BODY;//CONTENTS_TRIGGER|
1181 ent->e_TouchFunc = touchF_Touch_Item;
1182 // useing an item causes it to respawn
1183 ent->e_UseFunc = useF_Use_Item;
1184 ent->svFlags |= SVF_PLAYER_USABLE;//so player can pick it up
1185
1186 // Hang in air?
1187 ent->s.origin[2] += 1;//just to get it off the damn ground because coplanar = insolid
1188 if ( (ent->spawnflags&ITMSF_SUSPEND)
1189 || (ent->flags&FL_DROPPED_ITEM) )
1190 {
1191 // suspended
1192 G_SetOrigin( ent, ent->s.origin );
1193 }
1194 else
1195 {
1196 // drop to floor
1197 VectorSet( dest, ent->s.origin[0], ent->s.origin[1], MIN_WORLD_COORD );
1198 gi.trace( &tr, ent->s.origin, ent->mins, ent->maxs, dest, ent->s.number, MASK_SOLID|CONTENTS_PLAYERCLIP, (EG2_Collision)0, 0 );
1199 if ( tr.startsolid )
1200 {
1201 if ( &g_entities[tr.entityNum] != NULL )
1202 {
1203 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 );
1204 }
1205 else
1206 {
1207 gi.Printf (S_COLOR_RED"FinishSpawningItem: removing %s startsolid at %s (in a %s)\n", ent->classname, vtos(ent->s.origin) );
1208 }
1209 assert( 0 && "item starting in solid");
1210 if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region
1211 delayedShutDown = level.time + 100;
1212 }
1213 G_FreeEntity( ent );
1214 return;
1215 }
1216
1217 // allow to ride movers
1218 ent->s.groundEntityNum = tr.entityNum;
1219
1220 G_SetOrigin( ent, tr.endpos );
1221 }
1222
1223 /* ? don't need this
1224 // team slaves and targeted items aren't present at start
1225 if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) {
1226 ent->s.eFlags |= EF_NODRAW;
1227 ent->contents = 0;
1228 return;
1229 }
1230 */
1231 if ( ent->spawnflags & ITMSF_INVISIBLE ) // invisible
1232 {
1233 ent->s.eFlags |= EF_NODRAW;
1234 ent->contents = 0;
1235 }
1236
1237 if ( ent->spawnflags & ITMSF_NOTSOLID ) // not solid
1238 {
1239 ent->contents = 0;
1240 }
1241
1242 if ( (ent->spawnflags&ITMSF_STATIONARY) )
1243 {//can't be pushed around
1244 ent->flags |= FL_NO_KNOCKBACK;
1245 }
1246
1247 if ( (ent->flags&FL_DROPPED_ITEM) )
1248 {//go away after 30 seconds
1249 ent->e_ThinkFunc = thinkF_G_FreeEntity;
1250 ent->nextthink = level.time + 30000;
1251 }
1252
1253 gi.linkentity (ent);
1254 }
1255
1256
1257 char itemRegistered[MAX_ITEMS+1];
1258
1259
1260 /*
1261 ==============
1262 ClearRegisteredItems
1263 ==============
1264 */
ClearRegisteredItems(void)1265 void ClearRegisteredItems( void ) {
1266 for ( int i = 0; i < bg_numItems; i++ )
1267 {
1268 itemRegistered[i] = '0';
1269 }
1270 itemRegistered[ bg_numItems ] = 0;
1271
1272 //these are given in g_client, ClientSpawn(), but MUST be registered HERE, BEFORE cgame starts.
1273 //RegisterItem( FindItemForWeapon( WP_NONE ) ); //has no item
1274 RegisterItem( FindItemForInventory( INV_ELECTROBINOCULARS ));
1275 //RegisterItem( FindItemForInventory( INV_BACTA_CANISTER ));
1276 // saber or baton is cached in SP_info_player_deathmatch now.
1277
1278 extern void Player_CacheFromPrevLevel(void);//g_client.cpp
1279 Player_CacheFromPrevLevel(); //reads from transition carry-over;
1280 }
1281
1282 /*
1283 ===============
1284 RegisterItem
1285
1286 The item will be added to the precache list
1287 ===============
1288 */
RegisterItem(gitem_t * item)1289 void RegisterItem( gitem_t *item ) {
1290 if ( !item ) {
1291 G_Error( "RegisterItem: NULL" );
1292 }
1293 itemRegistered[ item - bg_itemlist ] = '1';
1294 gi.SetConfigstring(CS_ITEMS, itemRegistered); //Write the needed items to a config string
1295 }
1296
1297
1298 /*
1299 ===============
1300 SaveRegisteredItems
1301
1302 Write the needed items to a config string
1303 so the client will know which ones to precache
1304 ===============
1305 */
SaveRegisteredItems(void)1306 void SaveRegisteredItems( void ) {
1307 /* char string[MAX_ITEMS+1];
1308 int i;
1309 int count;
1310
1311 count = 0;
1312 for ( i = 0 ; i < bg_numItems ; i++ ) {
1313 if ( itemRegistered[i] ) {
1314 count++;
1315 string[i] = '1';
1316 } else {
1317 string[i] = '0';
1318 }
1319 }
1320 string[ bg_numItems ] = 0;
1321
1322 gi.Printf( "%i items registered\n", count );
1323 gi.SetConfigstring(CS_ITEMS, string);
1324 */
1325 gi.SetConfigstring(CS_ITEMS, itemRegistered);
1326 }
1327
1328 /*
1329 ============
1330 item_spawn_use
1331
1332 if an item is given a targetname, it will be spawned in when used
1333 ============
1334 */
item_spawn_use(gentity_t * self,gentity_t * other,gentity_t * activator)1335 void item_spawn_use( gentity_t *self, gentity_t *other, gentity_t *activator )
1336 //-----------------------------------------------------------------------------
1337 {
1338 self->nextthink = level.time + 50;
1339 self->e_ThinkFunc = thinkF_FinishSpawningItem;
1340 // I could be fancy and add a count or something like that to be able to spawn the item numerous times...
1341 self->e_UseFunc = useF_NULL;
1342 }
1343
1344 /*
1345 ============
1346 G_SpawnItem
1347
1348 Sets the clipping size and plants the object on the floor.
1349
1350 Items can't be immediately dropped to floor, because they might
1351 be on an entity that hasn't spawned yet.
1352 ============
1353 */
G_SpawnItem(gentity_t * ent,gitem_t * item)1354 void G_SpawnItem (gentity_t *ent, gitem_t *item) {
1355 G_SpawnFloat( "random", "0", &ent->random );
1356 G_SpawnFloat( "wait", "0", &ent->wait );
1357
1358 RegisterItem( item );
1359 ent->item = item;
1360
1361 // targetname indicates they want to spawn it later
1362 if( ent->targetname )
1363 {
1364 ent->e_UseFunc = useF_item_spawn_use;
1365 }
1366 else
1367 { // some movers spawn on the second frame, so delay item
1368 // spawns until the third frame so they can ride trains
1369 ent->nextthink = level.time + START_TIME_MOVERS_SPAWNED + 50;
1370 ent->e_ThinkFunc = thinkF_FinishSpawningItem;
1371 }
1372
1373 ent->physicsBounce = 0.50; // items are bouncy
1374
1375 // Set a default infoString text color
1376 // NOTE: if we want to do cool cross-hair colors for items, we can just modify this, but for now, don't do it
1377 VectorSet( ent->startRGBA, 1.0f, 1.0f, 1.0f );
1378
1379 if ( ent->team && ent->team[0] )
1380 {
1381 ent->noDamageTeam = (team_t)GetIDForString( TeamTable, ent->team );
1382 if ( ent->noDamageTeam == TEAM_FREE )
1383 {
1384 G_Error("team name %s not recognized\n", ent->team);
1385 }
1386 }
1387
1388 if ( ent->item
1389 && ent->item->giType == IT_WEAPON
1390 && ent->item->giTag == WP_SABER )
1391 {//weapon_saber item
1392 if ( !ent->count )
1393 {//can only pick up once
1394 ent->count = 1;
1395 }
1396 }
1397 ent->team = NULL;
1398 }
1399
1400
1401 /*
1402 ================
1403 G_BounceItem
1404
1405 ================
1406 */
G_BounceItem(gentity_t * ent,trace_t * trace)1407 void G_BounceItem( gentity_t *ent, trace_t *trace ) {
1408 vec3_t velocity;
1409 float dot;
1410 int hitTime;
1411 qboolean droppedSaber = qtrue;
1412
1413 if ( ent->item
1414 && ent->item->giType == IT_WEAPON
1415 && ent->item->giTag == WP_SABER
1416 && (ent->flags&FL_DROPPED_ITEM) )
1417 {
1418 droppedSaber = qtrue;
1419 }
1420
1421 // reflect the velocity on the trace plane
1422 hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
1423 EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
1424 dot = DotProduct( velocity, trace->plane.normal );
1425 VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta );
1426
1427 // cut the velocity to keep from bouncing forever
1428 VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta );
1429
1430 if ( droppedSaber )
1431 {//a dropped saber item
1432 //FIXME: use NPC_type (as saberType) to get proper bounce sound?
1433 WP_SaberFallSound( NULL, ent );
1434 }
1435
1436 // check for stop
1437 if ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 )
1438 {//stop
1439 G_SetOrigin( ent, trace->endpos );
1440 ent->s.groundEntityNum = trace->entityNum;
1441 if ( droppedSaber )
1442 {//a dropped saber item
1443 //stop rotation
1444 VectorClear( ent->s.apos.trDelta );
1445 ent->currentAngles[PITCH] = SABER_PITCH_HACK;
1446 ent->currentAngles[ROLL] = 0;
1447 if ( ent->NPC_type
1448 && ent->NPC_type[0] )
1449 {//we have a valid saber for this
1450 saberInfo_t saber;
1451 if ( WP_SaberParseParms( ent->NPC_type, &saber ) )
1452 {
1453 if ( (saber.saberFlags&SFL_BOLT_TO_WRIST) )
1454 {
1455 ent->currentAngles[PITCH] = 0;
1456 }
1457 }
1458 }
1459 pitch_roll_for_slope( ent, trace->plane.normal, ent->currentAngles, qtrue );
1460 G_SetAngles( ent, ent->currentAngles );
1461 }
1462 return;
1463 }
1464 //bounce
1465 if ( droppedSaber )
1466 {//a dropped saber item
1467 //change rotation
1468 VectorCopy( ent->currentAngles, ent->s.apos.trBase );
1469 ent->s.apos.trType = TR_LINEAR;
1470 ent->s.apos.trTime = level.time;
1471 VectorSet( ent->s.apos.trDelta, Q_irand( -300, 300 ), Q_irand( -300, 300 ), Q_irand( -300, 300 ) );
1472 }
1473
1474 VectorAdd( ent->currentOrigin, trace->plane.normal, ent->currentOrigin);
1475 VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
1476 ent->s.pos.trTime = level.time;
1477 }
1478
1479
1480 /*
1481 ================
1482 G_RunItem
1483
1484 ================
1485 */
G_RunItem(gentity_t * ent)1486 void G_RunItem( gentity_t *ent ) {
1487 vec3_t origin;
1488 trace_t tr;
1489 int contents;
1490 int mask;
1491
1492 // if groundentity has been set to -1, it may have been pushed off an edge
1493 if ( ent->s.groundEntityNum == ENTITYNUM_NONE )
1494 {
1495 if ( ent->s.pos.trType != TR_GRAVITY )
1496 {
1497 ent->s.pos.trType = TR_GRAVITY;
1498 ent->s.pos.trTime = level.time;
1499 }
1500 }
1501
1502 if ( ent->s.pos.trType == TR_STATIONARY )
1503 {
1504 // check think function
1505 G_RunThink( ent );
1506 if ( !g_gravity->value )
1507 {
1508 ent->s.pos.trType = TR_GRAVITY;
1509 ent->s.pos.trTime = level.time;
1510 ent->s.pos.trDelta[0] += Q_flrand(-1.0f, 1.0f) * 40.0f; // I dunno, just do this??
1511 ent->s.pos.trDelta[1] += Q_flrand(-1.0f, 1.0f) * 40.0f;
1512 ent->s.pos.trDelta[2] += Q_flrand(0.0f, 1.0f) * 20.0f;
1513 }
1514 else if ( (ent->flags&FL_DROPPED_ITEM)
1515 && ent->item
1516 && ent->item->giType == IT_WEAPON
1517 && ent->item->giTag == WP_SABER )
1518 {//a dropped saber item, check below, just in case
1519 int ignore = ENTITYNUM_NONE;
1520 if ( ent->clipmask )
1521 {
1522 mask = ent->clipmask;
1523 }
1524 else
1525 {
1526 mask = MASK_SOLID|CONTENTS_PLAYERCLIP;//shouldn't be able to get anywhere player can't
1527 }
1528 if ( ent->owner )
1529 {
1530 ignore = ent->owner->s.number;
1531 }
1532 else if ( ent->activator )
1533 {
1534 ignore = ent->activator->s.number;
1535 }
1536 VectorSet( origin, ent->currentOrigin[0], ent->currentOrigin[1], ent->currentOrigin[2]-1 );
1537 gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, ignore, mask, (EG2_Collision)0, 0 );
1538 if ( !tr.allsolid
1539 && !tr.startsolid
1540 && tr.fraction > 0.001f )
1541 {//wha? fall....
1542 ent->s.pos.trType = TR_GRAVITY;
1543 ent->s.pos.trTime = level.time;
1544 }
1545 }
1546 return;
1547 }
1548
1549 // get current position
1550 EvaluateTrajectory( &ent->s.pos, level.time, origin );
1551 if ( ent->s.apos.trType != TR_STATIONARY )
1552 {
1553 EvaluateTrajectory( &ent->s.apos, level.time, ent->currentAngles );
1554 G_SetAngles( ent, ent->currentAngles );
1555 }
1556
1557 // trace a line from the previous position to the current position
1558 if ( ent->clipmask )
1559 {
1560 mask = ent->clipmask;
1561 }
1562 else
1563 {
1564 mask = MASK_SOLID|CONTENTS_PLAYERCLIP;//shouldn't be able to get anywhere player can't
1565 }
1566
1567 int ignore = ENTITYNUM_NONE;
1568 if ( ent->owner )
1569 {
1570 ignore = ent->owner->s.number;
1571 }
1572 else if ( ent->activator )
1573 {
1574 ignore = ent->activator->s.number;
1575 }
1576 gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, ignore, mask, (EG2_Collision)0, 0 );
1577
1578 VectorCopy( tr.endpos, ent->currentOrigin );
1579
1580 if ( tr.startsolid )
1581 {
1582 tr.fraction = 0;
1583 }
1584
1585 gi.linkentity( ent ); // FIXME: avoid this for stationary?
1586
1587 // check think function
1588 G_RunThink( ent );
1589
1590 if ( tr.fraction == 1 )
1591 {
1592 if ( g_gravity->value <= 0 )
1593 {
1594 if ( ent->s.apos.trType != TR_LINEAR )
1595 {
1596 VectorCopy( ent->currentAngles, ent->s.apos.trBase );
1597 ent->s.apos.trType = TR_LINEAR;
1598 ent->s.apos.trDelta[1] = Q_flrand( -300, 300 );
1599 ent->s.apos.trDelta[0] = Q_flrand( -10, 10 );
1600 ent->s.apos.trDelta[2] = Q_flrand( -10, 10 );
1601 ent->s.apos.trTime = level.time;
1602 }
1603 }
1604 //friction in zero-G
1605 if ( !g_gravity->value )
1606 {
1607 float friction = 0.975f;
1608 /*friction -= ent->mass/1000.0f;
1609 if ( friction < 0.1 )
1610 {
1611 friction = 0.1f;
1612 }
1613 */
1614 VectorScale( ent->s.pos.trDelta, friction, ent->s.pos.trDelta );
1615 VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
1616 ent->s.pos.trTime = level.time;
1617 }
1618 return;
1619 }
1620
1621 // if it is in a nodrop volume, remove it
1622 contents = gi.pointcontents( ent->currentOrigin, -1 );
1623 if ( contents & CONTENTS_NODROP )
1624 {
1625 G_FreeEntity( ent );
1626 return;
1627 }
1628
1629 if ( !tr.startsolid )
1630 {
1631 G_BounceItem( ent, &tr );
1632 }
1633 }
1634
1635 /*
1636 ================
1637 ItemUse_Bacta
1638
1639 ================
1640 */
ItemUse_Bacta(gentity_t * ent)1641 void ItemUse_Bacta(gentity_t *ent)
1642 {
1643 if (!ent || !ent->client)
1644 {
1645 return;
1646 }
1647
1648 if (ent->health >= ent->client->ps.stats[STAT_MAX_HEALTH] || !ent->client->ps.inventory[INV_BACTA_CANISTER] )
1649 {
1650 return;
1651 }
1652
1653 ent->health += MAX_BACTA_HEAL_AMOUNT;
1654
1655 if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH])
1656 {
1657 ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
1658 }
1659
1660 ent->client->ps.inventory[INV_BACTA_CANISTER]--;
1661
1662 G_SoundOnEnt( ent, CHAN_VOICE, va( "sound/weapons/force/heal%d_%c.mp3", Q_irand( 1, 4 ), g_sex->string[0] ) );
1663 }
1664
1665