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