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