1 /*
2 ===========================================================================
3
4 Return to Castle Wolfenstein single player GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6
7 This file is part of the Return to Castle Wolfenstein single player GPL Source Code (RTCW SP Source Code).
8
9 RTCW SP 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 RTCW SP 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 RTCW SP Source Code. If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the RTCW SP 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 RTCW SP 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 /*
30 * name: g_items.c
31 *
32 * desc: Items are any object that a player can touch to gain some effect.
33 * Pickup will return the number of seconds until they should respawn.
34 * all items should pop when dropped in lava or slime.
35 * Respawnable items don't actually go away when picked up, they are
36 * just made invisible and untouchable. This allows them to ride
37 * movers and respawn apropriately.
38 *
39 */
40
41 #include "g_local.h"
42
43
44
45 #define RESPAWN_SP -1
46 #define RESPAWN_KEY 4
47 #define RESPAWN_ARMOR 25
48 #define RESPAWN_TEAM_WEAPON 30
49 #define RESPAWN_HEALTH 35
50 #define RESPAWN_AMMO 40
51 #define RESPAWN_HOLDABLE 60
52 #define RESPAWN_MEGAHEALTH 120
53 #define RESPAWN_POWERUP 120
54 #define RESPAWN_PARTIAL 998 // for multi-stage ammo/health
55 #define RESPAWN_PARTIAL_DONE 999 // for multi-stage ammo/health
56
57
58 //======================================================================
59
Pickup_Powerup(gentity_t * ent,gentity_t * other)60 int Pickup_Powerup( gentity_t *ent, gentity_t *other ) {
61 int quantity;
62 int i;
63 gclient_t *client;
64
65 if ( !other->client->ps.powerups[ent->item->giTag] ) {
66
67 // some powerups are time based on how long the powerup is /used/
68 // rather than timed from when the player picks it up.
69 if ( ent->item->giTag == PW_NOFATIGUE ) {
70 } else {
71 // round timing to seconds to make multiple powerup timers
72 // count in sync
73 other->client->ps.powerups[ent->item->giTag] = level.time - ( level.time % 1000 );
74 }
75 }
76
77 // if an amount was specified in the ent, use it
78 if ( ent->count ) {
79 quantity = ent->count;
80 } else {
81 quantity = ent->item->quantity;
82 }
83
84 other->client->ps.powerups[ent->item->giTag] += quantity * 1000;
85
86
87 // brandy also gives a little health (10)
88 if ( ent->item->giTag == PW_NOFATIGUE ) {
89 if ( Q_stricmp( ent->item->classname, "item_stamina_brandy" ) == 0 ) {
90 other->health += 10;
91 if ( other->health > other->client->ps.stats[STAT_MAX_HEALTH] ) {
92 other->health = other->client->ps.stats[STAT_MAX_HEALTH];
93 }
94 other->client->ps.stats[STAT_HEALTH] = other->health;
95 }
96
97 // cap stamina
98 if ( other->client->ps.powerups[PW_NOFATIGUE] > 60000 ) {
99 other->client->ps.powerups[PW_NOFATIGUE] = 60000;
100 }
101 }
102
103
104 // Ridah, not in single player
105 if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
106 // done.
107 // give any nearby players a "denied" anti-reward
108 for ( i = 0 ; i < level.maxclients ; i++ ) {
109 vec3_t delta;
110 float len;
111 vec3_t forward;
112 trace_t tr;
113
114 client = &level.clients[i];
115 if ( client == other->client ) {
116 continue;
117 }
118 if ( client->pers.connected == CON_DISCONNECTED ) {
119 continue;
120 }
121 if ( client->ps.stats[STAT_HEALTH] <= 0 ) {
122 continue;
123 }
124
125 // if too far away, no sound
126 VectorSubtract( ent->s.pos.trBase, client->ps.origin, delta );
127 len = VectorNormalize( delta );
128 if ( len > 192 ) {
129 continue;
130 }
131
132 // if not facing, no sound
133 AngleVectors( client->ps.viewangles, forward, NULL, NULL );
134 if ( DotProduct( delta, forward ) < 0.4 ) {
135 continue;
136 }
137
138 // if not line of sight, no sound
139 trap_Trace( &tr, client->ps.origin, NULL, NULL, ent->s.pos.trBase, ENTITYNUM_NONE, CONTENTS_SOLID );
140 if ( tr.fraction != 1.0 ) {
141 continue;
142 }
143
144 // anti-reward
145 client->ps.persistant[PERS_REWARD_COUNT]++;
146 client->ps.persistant[PERS_REWARD] = REWARD_DENIED;
147 }
148 // Ridah
149 }
150 // done.
151
152 if ( ent->s.density == 2 ) { // multi-stage health first stage
153 return RESPAWN_PARTIAL;
154 } else if ( ent->s.density == 1 ) { // last stage, leave the plate
155 return RESPAWN_PARTIAL_DONE;
156 }
157
158 // single player has no respawns (SA)
159 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
160 if ( !( ent->spawnflags & 8 ) ) {
161 return RESPAWN_SP;
162 }
163 }
164
165 return RESPAWN_POWERUP;
166 }
167
168 //----(SA) Wolf keys
169 //======================================================================
Pickup_Key(gentity_t * ent,gentity_t * other)170 int Pickup_Key( gentity_t *ent, gentity_t *other ) {
171 other->client->ps.stats[STAT_KEYS] |= ( 1 << ent->item->giTag );
172 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
173 if ( !( ent->spawnflags & 8 ) ) {
174 return RESPAWN_SP;
175 }
176 }
177
178 return RESPAWN_KEY;
179 }
180
181
182
183 /*
184 ==============
185 Pickup_Clipboard
186 ==============
187 */
Pickup_Clipboard(gentity_t * ent,gentity_t * other)188 int Pickup_Clipboard( gentity_t *ent, gentity_t *other ) {
189
190 if ( ent->spawnflags & 4 ) {
191 return 0; // leave in world
192
193 }
194 return -1;
195 }
196
197
198 /*
199 ==============
200 Pickup_Treasure
201 ==============
202 */
Pickup_Treasure(gentity_t * ent,gentity_t * other)203 int Pickup_Treasure( gentity_t *ent, gentity_t *other ) {
204 gentity_t *player = AICast_FindEntityForName( "player" );
205 player->numTreasureFound++;
206 G_SendMissionStats();
207 return RESPAWN_SP; // no respawn
208 }
209
210
211 /*
212 ==============
213 UseHoldableItem
214 server side handling of holdable item use
215 ==============
216 */
UseHoldableItem(gentity_t * ent,int item)217 void UseHoldableItem( gentity_t *ent, int item ) {
218 switch ( item ) {
219 case HI_WINE: // 1921 Chateu Lafite - gives 25 pts health up to max health
220 ent->health += 25;
221 if ( ent->health > ent->client->ps.stats[STAT_MAX_HEALTH] ) {
222 ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
223 }
224 break;
225
226 case HI_STAMINA: // restores fatigue bar and sets "nofatigue" for a time period (currently forced to 60 sec)
227 //----(SA) NOTE: currently only gives free nofatigue time, doesn't reset fatigue bar.
228 // (this is because I'd like the restore to be visually gradual (on the HUD item representing
229 // current status of your fatigue) rather than snapping back to 'full')
230 ent->client->ps.powerups[PW_NOFATIGUE] = 60000;
231 break;
232
233 case HI_BOOK1:
234 case HI_BOOK2:
235 case HI_BOOK3:
236 G_AddEvent( ent, EV_POPUPBOOK, ( item - HI_BOOK1 ) + 1 );
237 break;
238 }
239 }
240
241
242 //======================================================================
243
Pickup_Holdable(gentity_t * ent,gentity_t * other)244 int Pickup_Holdable( gentity_t *ent, gentity_t *other ) {
245 gitem_t *item;
246
247 // item = BG_FindItemForHoldable(ent->item);
248 item = ent->item;
249
250 if ( item->gameskillnumber[0] ) { // if the item specifies an amount, give it
251 other->client->ps.holdable[item->giTag] += item->gameskillnumber[0];
252 } else {
253 other->client->ps.holdable[item->giTag] += 1; // add default of 1
254
255 }
256 other->client->ps.holding = item->giTag;
257
258 other->client->ps.stats[STAT_HOLDABLE_ITEM] |= ( 1 << ent->item->giTag ); //----(SA) added
259
260 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
261 if ( !( ent->spawnflags & 8 ) ) {
262 return RESPAWN_SP;
263 }
264 }
265
266 return RESPAWN_HOLDABLE;
267 }
268
269
270 //======================================================================
271
272 /*
273 ==============
274 Fill_Clip
275 push reserve ammo into available space in the clip
276 ==============
277 */
Fill_Clip(playerState_t * ps,int weapon)278 void Fill_Clip( playerState_t *ps, int weapon ) {
279 int inclip, maxclip, ammomove;
280 int ammoweap = BG_FindAmmoForWeapon( weapon );
281
282 if ( weapon < WP_LUGER || weapon >= WP_NUM_WEAPONS ) {
283 return;
284 }
285
286 if ( g_dmflags.integer & DF_NO_WEAPRELOAD ) {
287 return;
288 }
289
290 inclip = ps->ammoclip[BG_FindClipForWeapon( weapon )];
291 maxclip = ammoTable[weapon].maxclip;
292
293 ammomove = maxclip - inclip; // max amount that can be moved into the clip
294
295 // cap move amount if it's more than you've got in reserve
296 if ( ammomove > ps->ammo[ammoweap] ) {
297 ammomove = ps->ammo[ammoweap];
298 }
299
300 if ( ammomove ) {
301 if ( !ps->aiChar || ps->ammo[ammoweap] < 999 ) { // RF, dont take ammo away if they need unlimited supplies
302 ps->ammo[ammoweap] -= ammomove;
303 }
304 ps->ammoclip[BG_FindClipForWeapon( weapon )] += ammomove;
305 }
306 }
307
308 /*
309 ==============
310 Add_Ammo
311 Try to always add ammo here unless you have specific needs
312 (like the AI "infinite ammo" where they get below 900 and force back up to 999)
313
314 fillClip will push the ammo straight through into the clip and leave the rest in reserve
315 ==============
316 */
Add_Ammo(gentity_t * ent,int weapon,int count,qboolean fillClip)317 void Add_Ammo( gentity_t *ent, int weapon, int count, qboolean fillClip ) {
318 int ammoweap = BG_FindAmmoForWeapon( weapon );
319 qboolean noPack = qfalse; // no extra ammo in your 'pack'
320
321 ent->client->ps.ammo[ammoweap] += count;
322
323 switch ( ammoweap ) {
324 // some weaps load straight into the 'clip' since they have no storage outside the clip
325
326 case WP_GRENADE_LAUNCHER: // make sure if he picks up a grenade that he get's the "launcher" too
327 case WP_GRENADE_PINEAPPLE:
328 case WP_DYNAMITE:
329 COM_BitSet( ent->client->ps.weapons, ammoweap );
330
331 case WP_TESLA:
332 case WP_FLAMETHROWER:
333 noPack = qtrue;
334 break;
335 default:
336 break;
337 }
338
339 if ( fillClip || noPack ) {
340 Fill_Clip( &ent->client->ps, weapon );
341 }
342
343 if ( ent->aiCharacter ) {
344 noPack = qfalse; // let AI's deal with their own clip/ammo handling
345
346 }
347 // cap to max ammo
348 if ( noPack ) {
349 ent->client->ps.ammo[ammoweap] = 0;
350 } else {
351 if ( ent->client->ps.ammo[ammoweap] > ammoTable[ammoweap].maxammo ) {
352 ent->client->ps.ammo[ammoweap] = ammoTable[ammoweap].maxammo;
353 }
354
355 if ( count >= 999 ) { // 'really, give /all/'
356 ent->client->ps.ammo[ammoweap] = count;
357 }
358 }
359
360 if ( ent->client->ps.ammoclip[ammoweap] > ammoTable[ammoweap].maxclip ) {
361 ent->client->ps.ammoclip[ammoweap] = ammoTable[ammoweap].maxclip;
362 }
363
364 }
365
366
367 /*
368 ==============
369 Pickup_Ammo
370 ==============
371 */
Pickup_Ammo(gentity_t * ent,gentity_t * other)372 int Pickup_Ammo( gentity_t *ent, gentity_t *other ) {
373 int quantity;
374
375 if ( ent->count ) {
376 quantity = ent->count;
377 } else {
378 // quantity = ent->item->quantity;
379
380 quantity = ent->item->gameskillnumber[g_gameskill.integer];
381
382 // FIXME just for now
383 if ( !quantity ) {
384 quantity = ent->item->quantity;
385 }
386 }
387
388 Add_Ammo( other, ent->item->giTag, quantity, qfalse ); //----(SA) modified
389
390 // single player has no respawns (SA)
391 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
392 if ( !( ent->spawnflags & 8 ) ) {
393 return RESPAWN_SP;
394 }
395 }
396
397 return RESPAWN_AMMO;
398 }
399
400 //======================================================================
401
402
Pickup_Weapon(gentity_t * ent,gentity_t * other)403 int Pickup_Weapon( gentity_t *ent, gentity_t *other ) {
404 int quantity;
405 qboolean alreadyHave = qfalse;
406 int weapon;
407
408 weapon = ent->item->giTag;
409
410 if ( ent->count < 0 ) {
411 quantity = 0; // None for you, sir!
412 } else {
413 if ( ent->count ) {
414 quantity = ent->count;
415 } else {
416 //----(SA) modified
417 // quantity = ent->item->quantity;
418 // quantity = (random() * (ent->item->quantity - 1)) + 1; // giving 1-<item default count>
419 quantity = ( random() * ( ammoTable[weapon].maxclip - 4 ) ) + 4; // giving 4-<item default count>
420
421 }
422
423 // dropped items and teamplay weapons always have full ammo
424 // if ( ! (ent->flags & FL_DROPPED_ITEM) && g_gametype.integer != GT_TEAM ) {
425 // // respawning rules
426 // // drop the quantity if the already have over the minimum
427 // if ( other->client->ps.ammo[ BG_FindAmmoForWeapon(weapon)] < quantity ) {
428 // quantity = quantity - other->client->ps.ammo[ BG_FindAmmoForWeapon(weapon)];
429 // } else {
430 // quantity = 1; // only add a single shot
431 // }
432 // }
433 //----(SA) end
434 }
435
436
437 //----(SA) added
438 // check for special colt->akimbo add (if you've got a colt already, add the second now)
439 if ( weapon == WP_COLT ) {
440 if ( COM_BitCheck( other->client->ps.weapons, WP_COLT ) ) {
441 weapon = WP_AKIMBO;
442 }
443 }
444 //----(SA) end
445
446 // check if player already had the weapon
447 alreadyHave = COM_BitCheck( other->client->ps.weapons, weapon );
448
449 // add the weapon
450 COM_BitSet( other->client->ps.weapons, weapon );
451
452 //----(SA) added
453 // snooper/garand
454 if ( weapon == WP_SNOOPERSCOPE ) {
455 COM_BitSet( other->client->ps.weapons, WP_GARAND );
456 } else if ( weapon == WP_GARAND ) {
457 COM_BitSet( other->client->ps.weapons, WP_SNOOPERSCOPE );
458 }
459 // fg42/scope
460 else if ( weapon == WP_FG42SCOPE ) {
461 COM_BitSet( other->client->ps.weapons, WP_FG42 );
462 } else if ( weapon == WP_FG42 ) {
463 COM_BitSet( other->client->ps.weapons, WP_FG42SCOPE );
464 } else if ( weapon == WP_SNIPERRIFLE ) {
465 COM_BitSet( other->client->ps.weapons, WP_MAUSER );
466 }
467
468 //----(SA) end
469
470 Add_Ammo( other, weapon, quantity, !alreadyHave );
471
472 //----(SA) no hook
473 // if (weapon == WP_GRAPPLING_HOOK)
474 // other->client->ps.ammo[BG_FindAmmoForWeapon(weapon)] = -1; // unlimited ammo
475
476 // single player has no respawns (SA)
477 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
478 if ( !( ent->spawnflags & 8 ) ) {
479 return RESPAWN_SP;
480 }
481 }
482
483 // // team deathmatch has slow weapon respawns
484 // if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
485 // return RESPAWN_TEAM_WEAPON;
486 // }
487
488 if ( g_gametype.integer == GT_TEAM ) {
489 return g_weaponTeamRespawn.integer;
490 }
491
492 return g_weaponRespawn.integer;
493 }
494
495
496 //======================================================================
497
Pickup_Health(gentity_t * ent,gentity_t * other)498 int Pickup_Health( gentity_t *ent, gentity_t *other ) {
499 int max;
500 int quantity = 0;
501
502 // small and mega healths will go over the max
503 if ( ent->item->quantity != 5 && ent->item->quantity != 100 ) {
504 max = other->client->ps.stats[STAT_MAX_HEALTH];
505 } else {
506 max = other->client->ps.stats[STAT_MAX_HEALTH] * 2;
507 }
508
509 if ( ent->count ) {
510 quantity = ent->count;
511 } else {
512 if ( ent->s.density ) { // multi-stage health
513 if ( ent->s.density == 2 ) { // first stage (it counts down)
514 quantity = ent->item->gameskillnumber[g_gameskill.integer];
515 } else if ( ent->s.density == 1 ) { // second stage
516 quantity = ent->item->quantity;
517 }
518 } else {
519 quantity = ent->item->gameskillnumber[g_gameskill.integer];
520 }
521 }
522
523 other->health += quantity;
524
525 if ( other->health > max ) {
526 other->health = max;
527 }
528 other->client->ps.stats[STAT_HEALTH] = other->health;
529
530 if ( ent->s.density == 2 ) { // multi-stage health first stage
531 return RESPAWN_PARTIAL;
532 } else if ( ent->s.density == 1 ) { // last stage, leave the plate
533 return RESPAWN_PARTIAL_DONE;
534 }
535
536 // single player has no respawns (SA)
537 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
538 return RESPAWN_SP;
539 }
540
541 if ( ent->item->giTag == 100 ) { // mega health respawns slow
542 return RESPAWN_MEGAHEALTH;
543 }
544
545 return RESPAWN_HEALTH;
546 }
547
548 //======================================================================
549
Pickup_Armor(gentity_t * ent,gentity_t * other)550 int Pickup_Armor( gentity_t *ent, gentity_t *other ) {
551 other->client->ps.stats[STAT_ARMOR] += ent->item->quantity;
552 // if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] * 2 ) {
553 // other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH] * 2;
554 if ( other->client->ps.stats[STAT_ARMOR] > 100 ) {
555 other->client->ps.stats[STAT_ARMOR] = 100;
556 }
557
558 // single player has no respawns (SA)
559 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
560 return RESPAWN_SP;
561 }
562
563 return RESPAWN_ARMOR;
564 }
565
566 //======================================================================
567
568 /*
569 ===============
570 RespawnItem
571 ===============
572 */
RespawnItem(gentity_t * ent)573 void RespawnItem( gentity_t *ent ) {
574 if ( !ent ) {
575 return;
576 }
577
578 // randomly select from teamed entities
579 if ( ent->team ) {
580 gentity_t *master;
581 int count;
582 int choice;
583
584 if ( !ent->teammaster ) {
585 G_Error( "RespawnItem: bad teammaster" );
586 }
587 master = ent->teammaster;
588
589 for ( count = 0, ent = master; ent; ent = ent->teamchain, count++ )
590 ;
591
592 choice = rand() % count;
593
594 for ( count = 0, ent = master; ent && count < choice; ent = ent->teamchain, count++ )
595 ;
596 }
597
598 if ( !ent ) {
599 return;
600 }
601
602 ent->r.contents = CONTENTS_TRIGGER;
603 //ent->s.eFlags &= ~EF_NODRAW;
604 ent->flags &= ~FL_NODRAW;
605 ent->r.svFlags &= ~SVF_NOCLIENT;
606 trap_LinkEntity( ent );
607
608 /*
609 if ( ent->item->giType == IT_POWERUP && g_gametype.integer != GT_SINGLE_PLAYER) {
610 // play powerup spawn sound to all clients
611 gentity_t *te;
612
613 te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND );
614 te->s.eventParm = G_SoundIndex( "sound/items/poweruprespawn.wav" );
615 te->r.svFlags |= SVF_BROADCAST;
616 }
617 */
618
619 // play the normal respawn sound only to nearby clients
620 G_AddEvent( ent, EV_ITEM_RESPAWN, 0 );
621
622 ent->nextthink = 0;
623 }
624
625
626 /*
627 ==============
628 Touch_Item
629 if other->client->pers.autoActivate == PICKUP_ACTIVATE (0), he will pick up items only when using +activate
630 if other->client->pers.autoActivate == PICKUP_TOUCH (1), he will pickup items when touched
631 if other->client->pers.autoActivate == PICKUP_FORCE (2), he will pickup the next item when touched (and reset to PICKUP_ACTIVATE when done)
632 ==============
633 */
Touch_Item_Auto(gentity_t * ent,gentity_t * other,trace_t * trace)634 void Touch_Item_Auto( gentity_t *ent, gentity_t *other, trace_t *trace ) {
635 if ( other->client->pers.autoActivate == PICKUP_ACTIVATE ) {
636 return;
637 }
638
639 ent->active = qtrue;
640 Touch_Item( ent, other, trace );
641
642 if ( other->client->pers.autoActivate == PICKUP_FORCE ) { // autoactivate probably forced by the "Cmd_Activate_f()" function
643 other->client->pers.autoActivate = PICKUP_ACTIVATE; // so reset it.
644 }
645 }
646
647
648 /*
649 ===============
650 Touch_Item
651 ===============
652 */
Touch_Item(gentity_t * ent,gentity_t * other,trace_t * trace)653 void Touch_Item( gentity_t *ent, gentity_t *other, trace_t *trace ) {
654 int respawn;
655 int makenoise = EV_ITEM_PICKUP;
656
657 // only activated items can be picked up
658 if ( !ent->active ) {
659 return;
660 } else {
661 // need to set active to false if player is maxed out
662 ent->active = qfalse;
663 }
664
665 if ( !other->client ) {
666 return;
667 }
668 if ( other->health < 1 ) {
669 return; // dead people can't pickup
670
671 }
672 // the same pickup rules are used for client side and server side
673 if ( !BG_CanItemBeGrabbed( &ent->s, &other->client->ps ) ) {
674 return;
675 }
676
677 G_LogPrintf( "Item: %i %s\n", other->s.number, ent->item->classname );
678
679 // call the item-specific pickup function
680 switch ( ent->item->giType ) {
681 case IT_WEAPON:
682 respawn = Pickup_Weapon( ent, other );
683 break;
684 case IT_AMMO:
685 respawn = Pickup_Ammo( ent, other );
686 break;
687 case IT_ARMOR:
688 respawn = Pickup_Armor( ent, other );
689 break;
690 case IT_HEALTH:
691 respawn = Pickup_Health( ent, other );
692 break;
693 case IT_POWERUP:
694 respawn = Pickup_Powerup( ent, other );
695 break;
696 case IT_TEAM:
697 respawn = Pickup_Team( ent, other );
698 break;
699 case IT_HOLDABLE:
700 respawn = Pickup_Holdable( ent, other );
701 break;
702 case IT_KEY:
703 respawn = Pickup_Key( ent, other );
704 break;
705 case IT_TREASURE:
706 respawn = Pickup_Treasure( ent, other );
707 break;
708 case IT_CLIPBOARD:
709 respawn = Pickup_Clipboard( ent, other );
710 // send the event to the client to request that the UI draw a popup
711 // (specified by the configstring in ent->s.density).
712 G_AddEvent( other, EV_POPUP, ent->s.density );
713 if ( ent->key ) {
714 G_AddEvent( other, EV_GIVEPAGE, ent->key );
715 }
716 break;
717 default:
718 return;
719 }
720
721 if ( !respawn ) {
722 return;
723 }
724
725 // play sounds
726 if ( ent->noise_index ) {
727 // (SA) a sound was specified in the entity, so play that sound
728 // (this G_AddEvent) and send the pickup as "EV_ITEM_PICKUP_QUIET"
729 // so it doesn't make the default pickup sound when the pickup event is recieved
730 makenoise = EV_ITEM_PICKUP_QUIET;
731 G_AddEvent( other, EV_GENERAL_SOUND, ent->noise_index );
732 }
733
734
735 // send the pickup event
736 if ( other->client->pers.predictItemPickup ) {
737 G_AddPredictableEvent( other, makenoise, ent->s.modelindex );
738 } else {
739 G_AddEvent( other, makenoise, ent->s.modelindex );
740 }
741
742 // powerup pickups are global broadcasts
743 if ( ent->item->giType == IT_POWERUP || ent->item->giType == IT_TEAM ) {
744 // (SA) probably need to check for IT_KEY here too... (coop?)
745 gentity_t *te;
746
747 te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP );
748 te->s.eventParm = ent->s.modelindex;
749 te->r.svFlags |= SVF_BROADCAST;
750
751 // (SA) set if we want this to only go to the pickup client
752 // te->r.svFlags |= SVF_SINGLECLIENT;
753 // te->r.singleClient = other->s.number;
754
755 }
756
757 // fire item targets
758 G_UseTargets( ent, other );
759
760 // wait of -1 will not respawn
761 if ( ent->wait == -1 ) {
762 ent->flags |= FL_NODRAW;
763 ent->r.svFlags |= SVF_NOCLIENT; // (SA) commented back in.
764 ent->s.eFlags |= EF_NODRAW;
765 ent->r.contents = 0;
766 ent->unlinkAfterEvent = qtrue;
767 return;
768 }
769
770 // wait of -2 will respawn but not be available for pickup anymore
771 // (partial use things that leave a spent modle (ex. plate for turkey)
772 if ( respawn == RESPAWN_PARTIAL_DONE ) {
773 ent->s.density = ( 1 << 9 ); // (10 bits of data transmission for density)
774 ent->active = qtrue; // re-activate
775 trap_LinkEntity( ent );
776 return;
777 }
778
779 if ( respawn == RESPAWN_PARTIAL ) { // multi-stage health
780 ent->s.density--;
781 if ( ent->s.density ) { // still not completely used up ( (SA) this will change to == 0 and stage 1 will be a destroyable item (plate/etc.) )
782 ent->active = qtrue; // re-activate
783 trap_LinkEntity( ent );
784 return;
785 }
786 }
787
788
789 // non zero wait overrides respawn time
790 if ( ent->wait ) {
791 respawn = ent->wait;
792 }
793
794 // random can be used to vary the respawn time
795 if ( ent->random ) {
796 respawn += crandom() * ent->random;
797 if ( respawn < 1 ) {
798 respawn = 1;
799 }
800 }
801
802 // dropped items will not respawn
803 if ( ent->flags & FL_DROPPED_ITEM ) {
804 ent->freeAfterEvent = qtrue;
805 }
806
807 // picked up items still stay around, they just don't
808 // draw anything. This allows respawnable items
809 // to be placed on movers.
810 ent->r.svFlags |= SVF_NOCLIENT;
811 ent->flags |= FL_NODRAW;
812 //ent->s.eFlags |= EF_NODRAW;
813 ent->r.contents = 0;
814
815 // ZOID
816 // A negative respawn times means to never respawn this item (but don't
817 // delete it). This is used by items that are respawned by third party
818 // events such as ctf flags
819 if ( respawn <= 0 ) {
820 ent->nextthink = 0;
821 ent->think = 0;
822 } else {
823 ent->nextthink = level.time + respawn * 1000;
824 ent->think = RespawnItem;
825 }
826 trap_LinkEntity( ent );
827 }
828
829
830 //======================================================================
831
832 /*
833 ================
834 LaunchItem
835
836 Spawns an item and tosses it forward
837 ================
838 */
LaunchItem(gitem_t * item,vec3_t origin,vec3_t velocity)839 gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity ) {
840 gentity_t *dropped;
841
842 dropped = G_Spawn();
843
844 dropped->s.eType = ET_ITEM;
845 dropped->s.modelindex = item - bg_itemlist; // store item number in modelindex
846 // dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item //----(SA) commented out since I'm using modelindex2 for model indexing now
847 dropped->s.otherEntityNum2 = 1; // DHM - Nerve :: this is taking modelindex2's place for signaling a dropped item
848
849 dropped->classname = item->classname;
850 dropped->item = item;
851 // VectorSet (dropped->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS);
852 // VectorSet (dropped->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS);
853 VectorSet( dropped->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, 0 ); //----(SA) so items sit on the ground
854 VectorSet( dropped->r.maxs, ITEM_RADIUS, ITEM_RADIUS, 2 * ITEM_RADIUS ); //----(SA) so items sit on the ground
855 dropped->r.contents = CONTENTS_TRIGGER | CONTENTS_ITEM;
856
857 dropped->touch = Touch_Item_Auto;
858
859 G_SetOrigin( dropped, origin );
860 dropped->s.pos.trType = TR_GRAVITY;
861 dropped->s.pos.trTime = level.time;
862 VectorCopy( velocity, dropped->s.pos.trDelta );
863
864 dropped->s.eFlags |= EF_BOUNCE_HALF;
865
866 // (SA) TODO: FIXME: don't do this right now. bug needs to be found.
867 // if(item->giType == IT_WEAPON)
868 // dropped->s.eFlags |= EF_SPINNING; // spin the weapon as it flies from the dead player. it will stop when it hits the ground
869
870
871 if ( item->giType == IT_TEAM ) { // Special case for CTF flags
872 dropped->think = Team_DroppedFlagThink;
873 dropped->nextthink = level.time + 30000;
874 } else { // auto-remove after 30 seconds
875 dropped->think = G_FreeEntity;
876 dropped->nextthink = level.time + 30000;
877 }
878
879 dropped->flags = FL_DROPPED_ITEM;
880
881 trap_LinkEntity( dropped );
882
883 return dropped;
884 }
885
886 /*
887 ================
888 Drop_Item
889
890 Spawns an item and tosses it forward
891 ================
892 */
Drop_Item(gentity_t * ent,gitem_t * item,float angle,qboolean novelocity)893 gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle, qboolean novelocity ) {
894 vec3_t velocity;
895 vec3_t angles;
896
897 VectorCopy( ent->s.apos.trBase, angles );
898 angles[YAW] += angle;
899 angles[PITCH] = 0; // always forward
900
901 if ( novelocity ) {
902 VectorClear( velocity );
903 } else
904 {
905 AngleVectors( angles, velocity, NULL, NULL );
906 VectorScale( velocity, 150, velocity );
907 velocity[2] += 200 + crandom() * 50;
908 }
909
910 return LaunchItem( item, ent->s.pos.trBase, velocity );
911 }
912
913
914 /*
915 ================
916 Use_Item
917
918 Respawn the item
919 ================
920 */
Use_Item(gentity_t * ent,gentity_t * other,gentity_t * activator)921 void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
922 RespawnItem( ent );
923 }
924
925 //======================================================================
926
927 /*
928 ================
929 FinishSpawningItem
930
931 Traces down to find where an item should rest, instead of letting them
932 free fall from their spawn points
933 ================
934 */
FinishSpawningItem(gentity_t * ent)935 void FinishSpawningItem( gentity_t *ent ) {
936 trace_t tr;
937 vec3_t dest;
938 vec3_t maxs;
939
940 if ( ent->spawnflags & 1 ) { // suspended
941 VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS );
942 VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );
943 VectorCopy( ent->r.maxs, maxs );
944 } else
945 {
946 // Rafael
947 // had to modify this so that items would spawn in shelves
948 VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, 0 );
949 VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );
950 VectorCopy( ent->r.maxs, maxs );
951 maxs[2] /= 2;
952 }
953
954 ent->r.contents = CONTENTS_TRIGGER | CONTENTS_ITEM;
955 ent->touch = Touch_Item_Auto;
956 ent->s.eType = ET_ITEM;
957 ent->s.modelindex = ent->item - bg_itemlist; // store item number in modelindex
958
959 ent->s.otherEntityNum2 = 0; // DHM - Nerve :: takes modelindex2's place in signaling a dropped item
960 //----(SA) we don't use this (yet, anyway) so I'm taking it so you can specify a model for treasure items and clipboards
961 // ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item
962 if ( ent->model ) {
963 ent->s.modelindex2 = G_ModelIndex( ent->model );
964 }
965
966
967 // if clipboard, add the menu name string to the client's configstrings
968 if ( ent->item->giType == IT_CLIPBOARD ) {
969 if ( !ent->message ) {
970 ent->s.density = G_FindConfigstringIndex( "clip_test", CS_CLIPBOARDS, MAX_CLIPBOARD_CONFIGSTRINGS, qtrue );
971 } else {
972 ent->s.density = G_FindConfigstringIndex( ent->message, CS_CLIPBOARDS, MAX_CLIPBOARD_CONFIGSTRINGS, qtrue );
973 }
974
975 ent->touch = Touch_Item; // no auto-pickup, only activate
976 } else if ( ent->item->giType == IT_HOLDABLE ) {
977 if ( ent->item->giTag >= HI_BOOK1 && ent->item->giTag <= HI_BOOK3 ) {
978 G_FindConfigstringIndex( va( "hbook%d", ent->item->giTag - HI_BOOK1 ), CS_CLIPBOARDS, MAX_CLIPBOARD_CONFIGSTRINGS, qtrue );
979 }
980 // ent->touch = Touch_Item; // no auto-pickup, only activate
981 }
982
983
984 // using an item causes it to respawn
985 ent->use = Use_Item;
986
987 //----(SA) moved this up so it happens for suspended items too (and made it a function)
988 G_SetAngle( ent, ent->s.angles );
989
990 if ( ent->spawnflags & 1 ) { // suspended
991 G_SetOrigin( ent, ent->s.origin );
992 } else {
993
994 VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
995 trap_Trace( &tr, ent->s.origin, ent->r.mins, maxs, dest, ent->s.number, MASK_SOLID );
996
997 if ( tr.startsolid ) {
998 vec3_t temp;
999
1000 VectorCopy( ent->s.origin, temp );
1001 temp[2] -= ITEM_RADIUS;
1002
1003 VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
1004 trap_Trace( &tr, temp, ent->r.mins, maxs, dest, ent->s.number, MASK_SOLID );
1005 }
1006
1007 #if 0
1008 // drop to floor
1009 VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
1010 trap_Trace( &tr, ent->s.origin, ent->r.mins, maxs, dest, ent->s.number, MASK_SOLID );
1011 #endif
1012 if ( tr.startsolid ) {
1013 G_Printf( "FinishSpawningItem: %s startsolid at %s\n", ent->classname, vtos( ent->s.origin ) );
1014 G_FreeEntity( ent );
1015 return;
1016 }
1017
1018 // allow to ride movers
1019 ent->s.groundEntityNum = tr.entityNum;
1020
1021 G_SetOrigin( ent, tr.endpos );
1022 }
1023
1024 if ( ent->spawnflags & 2 ) { // spin
1025 ent->s.eFlags |= EF_SPINNING;
1026 }
1027
1028
1029 // team slaves and targeted items aren't present at start
1030 if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) {
1031 ent->flags |= FL_NODRAW;
1032 //ent->s.eFlags |= EF_NODRAW;
1033 ent->r.contents = 0;
1034 return;
1035 }
1036
1037 // health/ammo can potentially be multi-stage (multiple use)
1038 if ( ent->item->giType == IT_HEALTH || ent->item->giType == IT_AMMO || ent->item->giType == IT_POWERUP ) {
1039 int i;
1040
1041 // having alternate models defined in bg_misc.c for a health or ammo item specify it as "multi-stage"
1042 // TTimo: left-hand operand of comma expression has no effect
1043 // was:
1044 // for(i=0;i<4,ent->item->world_model[i];i++) {}
1045 i = 0;
1046 while ( i < 4 && ent->item->world_model[i] )
1047 i++;
1048
1049 ent->s.density = i - 1; // store number of stages in 'density' for client (most will have '1')
1050 }
1051
1052 // powerups don't spawn in for a while
1053 if ( ent->item->giType == IT_POWERUP && g_gametype.integer != GT_SINGLE_PLAYER ) {
1054 float respawn;
1055
1056 respawn = 45 + crandom() * 15;
1057 ent->flags |= FL_NODRAW;
1058 //ent->s.eFlags |= EF_NODRAW;
1059 ent->r.contents = 0;
1060 ent->nextthink = level.time + respawn * 1000;
1061 ent->think = RespawnItem;
1062 return;
1063 }
1064
1065 trap_LinkEntity( ent );
1066 }
1067
1068
1069 qboolean itemRegistered[MAX_ITEMS];
1070
1071 /*
1072 ==================
1073 G_CheckTeamItems
1074 ==================
1075 */
G_CheckTeamItems(void)1076 void G_CheckTeamItems( void ) {
1077 if ( g_gametype.integer == GT_CTF ) {
1078 gitem_t *item;
1079
1080 // make sure we actually have two flags...
1081 item = BG_FindItem( "Red Flag" );
1082 if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
1083 G_Error( "No team_CTF_redflag in map\n" );
1084 }
1085 item = BG_FindItem( "Blue Flag" );
1086 if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
1087 G_Error( "No team_CTF_blueflag in map\n" );
1088 }
1089 }
1090 }
1091
1092 /*
1093 ==============
1094 ClearRegisteredItems
1095 ==============
1096 */
ClearRegisteredItems(void)1097 void ClearRegisteredItems( void ) {
1098 memset( itemRegistered, 0, sizeof( itemRegistered ) );
1099
1100 // players always start with the base weapon
1101 // (SA) Nope, not any more...
1102
1103 //----(SA) this will be determined by the level or starting position, or the savegame
1104 // but for now, re-register the MP40 automatically
1105 // RegisterItem( BG_FindItemForWeapon( WP_MP40 ) );
1106 RegisterItem( BG_FindItem( "Med Health" ) ); // NERVE - SMF - this is so med packs properly display
1107 }
1108
1109 /*
1110 ===============
1111 RegisterItem
1112
1113 The item will be added to the precache list
1114 ===============
1115 */
RegisterItem(gitem_t * item)1116 void RegisterItem( gitem_t *item ) {
1117 if ( !item ) {
1118 G_Error( "RegisterItem: NULL" );
1119 }
1120 itemRegistered[ item - bg_itemlist ] = qtrue;
1121 }
1122
1123
1124 /*
1125 ===============
1126 SaveRegisteredItems
1127
1128 Write the needed items to a config string
1129 so the client will know which ones to precache
1130 ===============
1131 */
SaveRegisteredItems(void)1132 void SaveRegisteredItems( void ) {
1133 char string[MAX_ITEMS + 1];
1134 int i;
1135 int count;
1136
1137 count = 0;
1138 for ( i = 0 ; i < bg_numItems ; i++ ) {
1139 if ( itemRegistered[i] ) {
1140 count++;
1141 string[i] = '1';
1142 } else {
1143 string[i] = '0';
1144 }
1145 }
1146 string[ bg_numItems ] = 0;
1147
1148 if ( trap_Cvar_VariableIntegerValue( "g_gametype" ) != GT_SINGLE_PLAYER ) {
1149 G_Printf( "%i items registered\n", count );
1150 }
1151 trap_SetConfigstring( CS_ITEMS, string );
1152 }
1153
1154
1155 /*
1156 ============
1157 G_SpawnItem
1158
1159 Sets the clipping size and plants the object on the floor.
1160
1161 Items can't be immediately dropped to floor, because they might
1162 be on an entity that hasn't spawned yet.
1163 ============
1164 */
G_SpawnItem(gentity_t * ent,gitem_t * item)1165 void G_SpawnItem( gentity_t *ent, gitem_t *item ) {
1166 char *noise;
1167 int page;
1168
1169 G_SpawnFloat( "random", "0", &ent->random );
1170 G_SpawnFloat( "wait", "0", &ent->wait );
1171
1172 RegisterItem( item );
1173 ent->item = item;
1174 // some movers spawn on the second frame, so delay item
1175 // spawns until the third frame so they can ride trains
1176 ent->nextthink = level.time + FRAMETIME * 2;
1177 ent->think = FinishSpawningItem;
1178
1179 if ( G_SpawnString( "noise", 0, &noise ) ) {
1180 ent->noise_index = G_SoundIndex( noise );
1181 }
1182
1183 ent->physicsBounce = 0.50; // items are bouncy
1184
1185 if ( ent->model ) {
1186 ent->s.modelindex2 = G_ModelIndex( ent->model );
1187 }
1188
1189 if ( item->giType == IT_CLIPBOARD ) {
1190 if ( G_SpawnInt( "notebookpage", "1", &page ) ) {
1191 ent->key = page;
1192 }
1193 }
1194
1195 if ( item->giType == IT_POWERUP ) {
1196 G_SoundIndex( "sound/items/poweruprespawn.wav" );
1197 }
1198
1199 if ( item->giType == IT_TREASURE ) {
1200 level.numTreasure++;
1201 G_SendMissionStats();
1202 }
1203 }
1204
1205
1206 /*
1207 ================
1208 G_BounceItem
1209
1210 ================
1211 */
G_BounceItem(gentity_t * ent,trace_t * trace)1212 void G_BounceItem( gentity_t *ent, trace_t *trace ) {
1213 vec3_t velocity;
1214 float dot;
1215 int hitTime;
1216
1217 // reflect the velocity on the trace plane
1218 hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
1219 BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
1220 dot = DotProduct( velocity, trace->plane.normal );
1221 VectorMA( velocity, -2 * dot, trace->plane.normal, ent->s.pos.trDelta );
1222
1223 // cut the velocity to keep from bouncing forever
1224 VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta );
1225
1226 // check for stop
1227 if ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 ) {
1228 trace->endpos[2] += 1.0; // make sure it is off ground
1229 SnapVector( trace->endpos );
1230 G_SetOrigin( ent, trace->endpos );
1231 ent->s.groundEntityNum = trace->entityNum;
1232 return;
1233 }
1234
1235 VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin );
1236 VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
1237 ent->s.pos.trTime = level.time;
1238 }
1239
1240 /*
1241 =================
1242 G_RunItemProp
1243 =================
1244 */
1245
G_RunItemProp(gentity_t * ent,vec3_t origin)1246 void G_RunItemProp( gentity_t *ent, vec3_t origin ) {
1247 gentity_t *traceEnt;
1248 trace_t trace;
1249 gentity_t *owner;
1250 vec3_t start;
1251 vec3_t end;
1252
1253 owner = &g_entities[ent->r.ownerNum];
1254
1255 VectorCopy( ent->r.currentOrigin, start );
1256 start[2] += 1;
1257
1258 VectorCopy( origin, end );
1259 end[2] += 1;
1260
1261 trap_Trace( &trace, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, end,
1262 ent->r.ownerNum, MASK_SHOT );
1263
1264 traceEnt = &g_entities[ trace.entityNum ];
1265
1266 if ( traceEnt && traceEnt->takedamage && traceEnt != ent ) {
1267 ent->enemy = traceEnt;
1268 }
1269
1270 if ( owner->client && trace.startsolid && traceEnt != owner && traceEnt != ent /* && !traceEnt->active*/ ) {
1271
1272 ent->takedamage = qfalse;
1273 ent->die( ent, ent, NULL, 10, 0 );
1274 Prop_Break_Sound( ent );
1275
1276 return;
1277 } else if ( trace.surfaceFlags & SURF_NOIMPACT ) {
1278 ent->takedamage = qfalse;
1279
1280 Props_Chair_Skyboxtouch( ent );
1281
1282 return;
1283 }
1284 }
1285
1286 /*
1287 ================
1288 G_RunItem
1289
1290 ================
1291 */
G_RunItem(gentity_t * ent)1292 void G_RunItem( gentity_t *ent ) {
1293 vec3_t origin;
1294 trace_t tr;
1295 int contents;
1296 int mask;
1297
1298 // if its groundentity has been set to none, it may have been pushed off an edge
1299 if ( ent->s.groundEntityNum == ENTITYNUM_NONE ) {
1300 if ( ent->s.pos.trType != TR_GRAVITY ) {
1301 ent->s.pos.trType = TR_GRAVITY;
1302 ent->s.pos.trTime = level.time;
1303 }
1304 }
1305
1306 if ( ent->s.pos.trType == TR_STATIONARY || ent->s.pos.trType == TR_GRAVITY_PAUSED ) { //----(SA)
1307 // check think function
1308 G_RunThink( ent );
1309 return;
1310 }
1311
1312 // get current position
1313 BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
1314
1315 // trace a line from the previous position to the current position
1316 if ( ent->clipmask ) {
1317 mask = ent->clipmask;
1318 } else {
1319 mask = MASK_SOLID | CONTENTS_MISSILECLIP;
1320 }
1321 trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin,
1322 ent->r.ownerNum, mask );
1323
1324 if ( ent->isProp && ent->takedamage ) {
1325 G_RunItemProp( ent, origin );
1326 }
1327
1328 VectorCopy( tr.endpos, ent->r.currentOrigin );
1329
1330 if ( tr.startsolid ) {
1331 tr.fraction = 0;
1332 }
1333
1334 trap_LinkEntity( ent ); // FIXME: avoid this for stationary?
1335
1336 // check think function
1337 G_RunThink( ent );
1338
1339 if ( tr.fraction == 1 ) {
1340 return;
1341 }
1342
1343 // if it is in a nodrop volume, remove it
1344 contents = trap_PointContents( ent->r.currentOrigin, -1 );
1345 if ( contents & CONTENTS_NODROP ) {
1346 if ( ent->item && ent->item->giType == IT_TEAM ) {
1347 Team_FreeEntity( ent );
1348 } else {
1349 G_FreeEntity( ent );
1350 }
1351 return;
1352 }
1353
1354 G_BounceItem( ent, &tr );
1355 }
1356
1357