1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 ===========================================================================
21 */
22 //
23 #include "g_local.h"
24
25 /*
26
27 Items are any object that a player can touch to gain some effect.
28
29 Pickup will return the number of seconds until they should respawn.
30
31 all items should pop when dropped in lava or slime
32
33 Respawnable items don't actually go away when picked up, they are
34 just made invisible and untouchable. This allows them to ride
35 movers and respawn apropriately.
36 */
37
38
39 #define RESPAWN_ARMOR 25
40 #define RESPAWN_HEALTH 35
41 #define RESPAWN_AMMO 40
42 #define RESPAWN_HOLDABLE 60
43 #define RESPAWN_MEGAHEALTH 35//120
44 #define RESPAWN_POWERUP 120
45
46
47 //======================================================================
48
Pickup_Powerup(gentity_t * ent,gentity_t * other)49 int Pickup_Powerup( gentity_t *ent, gentity_t *other ) {
50 int quantity;
51 int i;
52 gclient_t *client;
53
54 if ( !other->client->ps.powerups[ent->item->giTag] ) {
55 // round timing to seconds to make multiple powerup timers
56 // count in sync
57 other->client->ps.powerups[ent->item->giTag] =
58 level.time - ( level.time % 1000 );
59 }
60
61 if ( ent->count ) {
62 quantity = ent->count;
63 } else {
64 quantity = ent->item->quantity;
65 }
66
67 other->client->ps.powerups[ent->item->giTag] += quantity * 1000;
68
69 // give any nearby players a "denied" anti-reward
70 for ( i = 0 ; i < level.maxclients ; i++ ) {
71 vec3_t delta;
72 float len;
73 vec3_t forward;
74 trace_t tr;
75
76 client = &level.clients[i];
77 if ( client == other->client ) {
78 continue;
79 }
80 if ( client->pers.connected == CON_DISCONNECTED ) {
81 continue;
82 }
83 if ( client->ps.stats[STAT_HEALTH] <= 0 ) {
84 continue;
85 }
86
87 // if same team in team game, no sound
88 // cannot use OnSameTeam as it expects to g_entities, not clients
89 if ( g_gametype.integer >= GT_TEAM && other->client->sess.sessionTeam == client->sess.sessionTeam ) {
90 continue;
91 }
92
93 // if too far away, no sound
94 VectorSubtract( ent->s.pos.trBase, client->ps.origin, delta );
95 len = VectorNormalize( delta );
96 if ( len > 192 ) {
97 continue;
98 }
99
100 // if not facing, no sound
101 AngleVectors( client->ps.viewangles, forward, NULL, NULL );
102 if ( DotProduct( delta, forward ) < 0.4 ) {
103 continue;
104 }
105
106 // if not line of sight, no sound
107 trap_Trace( &tr, client->ps.origin, NULL, NULL, ent->s.pos.trBase, ENTITYNUM_NONE, CONTENTS_SOLID );
108 if ( tr.fraction != 1.0 ) {
109 continue;
110 }
111
112 // anti-reward
113 client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_DENIEDREWARD;
114 }
115 return RESPAWN_POWERUP;
116 }
117
118 //======================================================================
119
120 #ifdef MISSIONPACK
Pickup_PersistantPowerup(gentity_t * ent,gentity_t * other)121 int Pickup_PersistantPowerup( gentity_t *ent, gentity_t *other ) {
122 int clientNum;
123 char userinfo[MAX_INFO_STRING];
124 float handicap;
125 int max;
126
127 other->client->ps.stats[STAT_PERSISTANT_POWERUP] = ent->item - bg_itemlist;
128 other->client->persistantPowerup = ent;
129
130 switch( ent->item->giTag ) {
131 case PW_GUARD:
132 clientNum = other->client->ps.clientNum;
133 trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
134 handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
135 if( handicap<=0.0f || handicap>100.0f) {
136 handicap = 100.0f;
137 }
138 max = (int)(2 * handicap);
139
140 other->health = max;
141 other->client->ps.stats[STAT_HEALTH] = max;
142 other->client->ps.stats[STAT_MAX_HEALTH] = max;
143 other->client->ps.stats[STAT_ARMOR] = max;
144 other->client->pers.maxHealth = max;
145
146 break;
147
148 case PW_SCOUT:
149 clientNum = other->client->ps.clientNum;
150 trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
151 handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
152 if( handicap<=0.0f || handicap>100.0f) {
153 handicap = 100.0f;
154 }
155 other->client->pers.maxHealth = handicap;
156 other->client->ps.stats[STAT_ARMOR] = 0;
157 break;
158
159 case PW_DOUBLER:
160 clientNum = other->client->ps.clientNum;
161 trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
162 handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
163 if( handicap<=0.0f || handicap>100.0f) {
164 handicap = 100.0f;
165 }
166 other->client->pers.maxHealth = handicap;
167 break;
168 case PW_AMMOREGEN:
169 clientNum = other->client->ps.clientNum;
170 trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
171 handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
172 if( handicap<=0.0f || handicap>100.0f) {
173 handicap = 100.0f;
174 }
175 other->client->pers.maxHealth = handicap;
176 memset(other->client->ammoTimes, 0, sizeof(other->client->ammoTimes));
177 break;
178 default:
179 clientNum = other->client->ps.clientNum;
180 trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
181 handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
182 if( handicap<=0.0f || handicap>100.0f) {
183 handicap = 100.0f;
184 }
185 other->client->pers.maxHealth = handicap;
186 break;
187 }
188
189 return -1;
190 }
191
192 //======================================================================
193 #endif
194
Pickup_Holdable(gentity_t * ent,gentity_t * other)195 int Pickup_Holdable( gentity_t *ent, gentity_t *other ) {
196
197 other->client->ps.stats[STAT_HOLDABLE_ITEM] = ent->item - bg_itemlist;
198
199 if( ent->item->giTag == HI_KAMIKAZE ) {
200 other->client->ps.eFlags |= EF_KAMIKAZE;
201 }
202
203 return RESPAWN_HOLDABLE;
204 }
205
206
207 //======================================================================
208
Add_Ammo(gentity_t * ent,int weapon,int count)209 void Add_Ammo (gentity_t *ent, int weapon, int count)
210 {
211 ent->client->ps.ammo[weapon] += count;
212 if ( ent->client->ps.ammo[weapon] > 200 ) {
213 ent->client->ps.ammo[weapon] = 200;
214 }
215 }
216
Pickup_Ammo(gentity_t * ent,gentity_t * other)217 int Pickup_Ammo (gentity_t *ent, gentity_t *other)
218 {
219 int quantity;
220
221 if ( ent->count ) {
222 quantity = ent->count;
223 } else {
224 quantity = ent->item->quantity;
225 }
226
227 Add_Ammo (other, ent->item->giTag, quantity);
228
229 return RESPAWN_AMMO;
230 }
231
232 //======================================================================
233
234
Pickup_Weapon(gentity_t * ent,gentity_t * other)235 int Pickup_Weapon (gentity_t *ent, gentity_t *other) {
236 int quantity;
237
238 if ( ent->count < 0 ) {
239 quantity = 0; // None for you, sir!
240 } else {
241 if ( ent->count ) {
242 quantity = ent->count;
243 } else {
244 quantity = ent->item->quantity;
245 }
246
247 // dropped items and teamplay weapons always have full ammo
248 if ( ! (ent->flags & FL_DROPPED_ITEM) && g_gametype.integer != GT_TEAM ) {
249 // respawning rules
250 // drop the quantity if the already have over the minimum
251 if ( other->client->ps.ammo[ ent->item->giTag ] < quantity ) {
252 quantity = quantity - other->client->ps.ammo[ ent->item->giTag ];
253 } else {
254 quantity = 1; // only add a single shot
255 }
256 }
257 }
258
259 // add the weapon
260 other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag );
261
262 Add_Ammo( other, ent->item->giTag, quantity );
263
264 if (ent->item->giTag == WP_GRAPPLING_HOOK)
265 other->client->ps.ammo[ent->item->giTag] = -1; // unlimited ammo
266
267 // team deathmatch has slow weapon respawns
268 if ( g_gametype.integer == GT_TEAM ) {
269 return g_weaponTeamRespawn.integer;
270 }
271
272 return g_weaponRespawn.integer;
273 }
274
275
276 //======================================================================
277
Pickup_Health(gentity_t * ent,gentity_t * other)278 int Pickup_Health (gentity_t *ent, gentity_t *other) {
279 int max;
280 int quantity;
281
282 // small and mega healths will go over the max
283 #ifdef MISSIONPACK
284 if( other->client && bg_itemlist[other->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) {
285 max = other->client->ps.stats[STAT_MAX_HEALTH];
286 }
287 else
288 #endif
289 if ( ent->item->quantity != 5 && ent->item->quantity != 100 ) {
290 max = other->client->ps.stats[STAT_MAX_HEALTH];
291 } else {
292 max = other->client->ps.stats[STAT_MAX_HEALTH] * 2;
293 }
294
295 if ( ent->count ) {
296 quantity = ent->count;
297 } else {
298 quantity = ent->item->quantity;
299 }
300
301 other->health += quantity;
302
303 if (other->health > max ) {
304 other->health = max;
305 }
306 other->client->ps.stats[STAT_HEALTH] = other->health;
307
308 if ( ent->item->quantity == 100 ) { // mega health respawns slow
309 return RESPAWN_MEGAHEALTH;
310 }
311
312 return RESPAWN_HEALTH;
313 }
314
315 //======================================================================
316
Pickup_Armor(gentity_t * ent,gentity_t * other)317 int Pickup_Armor( gentity_t *ent, gentity_t *other ) {
318 #ifdef MISSIONPACK
319 int upperBound;
320
321 other->client->ps.stats[STAT_ARMOR] += ent->item->quantity;
322
323 if( other->client && bg_itemlist[other->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) {
324 upperBound = other->client->ps.stats[STAT_MAX_HEALTH];
325 }
326 else {
327 upperBound = other->client->ps.stats[STAT_MAX_HEALTH] * 2;
328 }
329
330 if ( other->client->ps.stats[STAT_ARMOR] > upperBound ) {
331 other->client->ps.stats[STAT_ARMOR] = upperBound;
332 }
333 #else
334 other->client->ps.stats[STAT_ARMOR] += ent->item->quantity;
335 if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] * 2 ) {
336 other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH] * 2;
337 }
338 #endif
339
340 return RESPAWN_ARMOR;
341 }
342
343 //======================================================================
344
345 /*
346 ===============
347 RespawnItem
348 ===============
349 */
RespawnItem(gentity_t * ent)350 void RespawnItem( gentity_t *ent ) {
351 // randomly select from teamed entities
352 if (ent->team) {
353 gentity_t *master;
354 int count;
355 int choice;
356
357 if ( !ent->teammaster ) {
358 G_Error( "RespawnItem: bad teammaster");
359 }
360 master = ent->teammaster;
361
362 for (count = 0, ent = master; ent; ent = ent->teamchain, count++)
363 ;
364
365 choice = rand() % count;
366
367 for (count = 0, ent = master; count < choice; ent = ent->teamchain, count++)
368 ;
369 }
370
371 ent->r.contents = CONTENTS_TRIGGER;
372 ent->s.eFlags &= ~EF_NODRAW;
373 ent->r.svFlags &= ~SVF_NOCLIENT;
374 trap_LinkEntity (ent);
375
376 if ( ent->item->giType == IT_POWERUP ) {
377 // play powerup spawn sound to all clients
378 gentity_t *te;
379
380 // if the powerup respawn sound should Not be global
381 if (ent->speed) {
382 te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND );
383 }
384 else {
385 te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND );
386 }
387 te->s.eventParm = G_SoundIndex( "sound/items/poweruprespawn.wav" );
388 te->r.svFlags |= SVF_BROADCAST;
389 }
390
391 if ( ent->item->giType == IT_HOLDABLE && ent->item->giTag == HI_KAMIKAZE ) {
392 // play powerup spawn sound to all clients
393 gentity_t *te;
394
395 // if the powerup respawn sound should Not be global
396 if (ent->speed) {
397 te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND );
398 }
399 else {
400 te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND );
401 }
402 te->s.eventParm = G_SoundIndex( "sound/items/kamikazerespawn.wav" );
403 te->r.svFlags |= SVF_BROADCAST;
404 }
405
406 // play the normal respawn sound only to nearby clients
407 G_AddEvent( ent, EV_ITEM_RESPAWN, 0 );
408
409 ent->nextthink = 0;
410 }
411
412
413 /*
414 ===============
415 Touch_Item
416 ===============
417 */
Touch_Item(gentity_t * ent,gentity_t * other,trace_t * trace)418 void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) {
419 int respawn;
420 qboolean predict;
421
422 if (!other->client)
423 return;
424 if (other->health < 1)
425 return; // dead people can't pickup
426
427 // the same pickup rules are used for client side and server side
428 if ( !BG_CanItemBeGrabbed( g_gametype.integer, &ent->s, &other->client->ps ) ) {
429 return;
430 }
431
432 G_LogPrintf( "Item: %i %s\n", other->s.number, ent->item->classname );
433
434 predict = other->client->pers.predictItemPickup;
435
436 // call the item-specific pickup function
437 switch( ent->item->giType ) {
438 case IT_WEAPON:
439 respawn = Pickup_Weapon(ent, other);
440 // predict = qfalse;
441 break;
442 case IT_AMMO:
443 respawn = Pickup_Ammo(ent, other);
444 // predict = qfalse;
445 break;
446 case IT_ARMOR:
447 respawn = Pickup_Armor(ent, other);
448 break;
449 case IT_HEALTH:
450 respawn = Pickup_Health(ent, other);
451 break;
452 case IT_POWERUP:
453 respawn = Pickup_Powerup(ent, other);
454 predict = qfalse;
455 break;
456 #ifdef MISSIONPACK
457 case IT_PERSISTANT_POWERUP:
458 respawn = Pickup_PersistantPowerup(ent, other);
459 break;
460 #endif
461 case IT_TEAM:
462 respawn = Pickup_Team(ent, other);
463 break;
464 case IT_HOLDABLE:
465 respawn = Pickup_Holdable(ent, other);
466 break;
467 default:
468 return;
469 }
470
471 if ( !respawn ) {
472 return;
473 }
474
475 // play the normal pickup sound
476 if (predict) {
477 G_AddPredictableEvent( other, EV_ITEM_PICKUP, ent->s.modelindex );
478 } else {
479 G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex );
480 }
481
482 // powerup pickups are global broadcasts
483 if ( ent->item->giType == IT_POWERUP || ent->item->giType == IT_TEAM) {
484 // if we want the global sound to play
485 if (!ent->speed) {
486 gentity_t *te;
487
488 te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP );
489 te->s.eventParm = ent->s.modelindex;
490 te->r.svFlags |= SVF_BROADCAST;
491 } else {
492 gentity_t *te;
493
494 te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP );
495 te->s.eventParm = ent->s.modelindex;
496 // only send this temp entity to a single client
497 te->r.svFlags |= SVF_SINGLECLIENT;
498 te->r.singleClient = other->s.number;
499 }
500 }
501
502 // fire item targets
503 G_UseTargets (ent, other);
504
505 // wait of -1 will not respawn
506 if ( ent->wait == -1 ) {
507 ent->r.svFlags |= SVF_NOCLIENT;
508 ent->s.eFlags |= EF_NODRAW;
509 ent->r.contents = 0;
510 ent->unlinkAfterEvent = qtrue;
511 return;
512 }
513
514 // non zero wait overrides respawn time
515 if ( ent->wait ) {
516 respawn = ent->wait;
517 }
518
519 // random can be used to vary the respawn time
520 if ( ent->random ) {
521 respawn += crandom() * ent->random;
522 if ( respawn < 1 ) {
523 respawn = 1;
524 }
525 }
526
527 // dropped items will not respawn
528 if ( ent->flags & FL_DROPPED_ITEM ) {
529 ent->freeAfterEvent = qtrue;
530 }
531
532 // picked up items still stay around, they just don't
533 // draw anything. This allows respawnable items
534 // to be placed on movers.
535 ent->r.svFlags |= SVF_NOCLIENT;
536 ent->s.eFlags |= EF_NODRAW;
537 ent->r.contents = 0;
538
539 // ZOID
540 // A negative respawn times means to never respawn this item (but don't
541 // delete it). This is used by items that are respawned by third party
542 // events such as ctf flags
543 if ( respawn <= 0 ) {
544 ent->nextthink = 0;
545 ent->think = 0;
546 } else {
547 ent->nextthink = level.time + respawn * 1000;
548 ent->think = RespawnItem;
549 }
550 trap_LinkEntity( ent );
551 }
552
553
554 //======================================================================
555
556 /*
557 ================
558 LaunchItem
559
560 Spawns an item and tosses it forward
561 ================
562 */
LaunchItem(gitem_t * item,vec3_t origin,vec3_t velocity)563 gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity ) {
564 gentity_t *dropped;
565
566 dropped = G_Spawn();
567
568 dropped->s.eType = ET_ITEM;
569 dropped->s.modelindex = item - bg_itemlist; // store item number in modelindex
570 dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item
571
572 dropped->classname = item->classname;
573 dropped->item = item;
574 VectorSet (dropped->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS);
575 VectorSet (dropped->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS);
576 dropped->r.contents = CONTENTS_TRIGGER;
577
578 dropped->touch = Touch_Item;
579
580 G_SetOrigin( dropped, origin );
581 dropped->s.pos.trType = TR_GRAVITY;
582 dropped->s.pos.trTime = level.time;
583 VectorCopy( velocity, dropped->s.pos.trDelta );
584
585 dropped->s.eFlags |= EF_BOUNCE_HALF;
586 #ifdef MISSIONPACK
587 if ((g_gametype.integer == GT_CTF || g_gametype.integer == GT_1FCTF) && item->giType == IT_TEAM) { // Special case for CTF flags
588 #else
589 if (g_gametype.integer == GT_CTF && item->giType == IT_TEAM) { // Special case for CTF flags
590 #endif
591 dropped->think = Team_DroppedFlagThink;
592 dropped->nextthink = level.time + 30000;
593 Team_CheckDroppedItem( dropped );
594 } else { // auto-remove after 30 seconds
595 dropped->think = G_FreeEntity;
596 dropped->nextthink = level.time + 30000;
597 }
598
599 dropped->flags = FL_DROPPED_ITEM;
600
601 trap_LinkEntity (dropped);
602
603 return dropped;
604 }
605
606 /*
607 ================
608 Drop_Item
609
610 Spawns an item and tosses it forward
611 ================
612 */
613 gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle ) {
614 vec3_t velocity;
615 vec3_t angles;
616
617 VectorCopy( ent->s.apos.trBase, angles );
618 angles[YAW] += angle;
619 angles[PITCH] = 0; // always forward
620
621 AngleVectors( angles, velocity, NULL, NULL );
622 VectorScale( velocity, 150, velocity );
623 velocity[2] += 200 + crandom() * 50;
624
625 return LaunchItem( item, ent->s.pos.trBase, velocity );
626 }
627
628
629 /*
630 ================
631 Use_Item
632
633 Respawn the item
634 ================
635 */
636 void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
637 RespawnItem( ent );
638 }
639
640 //======================================================================
641
642 /*
643 ================
644 FinishSpawningItem
645
646 Traces down to find where an item should rest, instead of letting them
647 free fall from their spawn points
648 ================
649 */
650 void FinishSpawningItem( gentity_t *ent ) {
651 trace_t tr;
652 vec3_t dest;
653
654 VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS );
655 VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );
656
657 ent->s.eType = ET_ITEM;
658 ent->s.modelindex = ent->item - bg_itemlist; // store item number in modelindex
659 ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item
660
661 ent->r.contents = CONTENTS_TRIGGER;
662 ent->touch = Touch_Item;
663 // useing an item causes it to respawn
664 ent->use = Use_Item;
665
666 if ( ent->spawnflags & 1 ) {
667 // suspended
668 G_SetOrigin( ent, ent->s.origin );
669 } else {
670 // drop to floor
671 VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
672 trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID );
673 if ( tr.startsolid ) {
674 G_Printf ("FinishSpawningItem: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
675 G_FreeEntity( ent );
676 return;
677 }
678
679 // allow to ride movers
680 ent->s.groundEntityNum = tr.entityNum;
681
682 G_SetOrigin( ent, tr.endpos );
683 }
684
685 // team slaves and targeted items aren't present at start
686 if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) {
687 ent->s.eFlags |= EF_NODRAW;
688 ent->r.contents = 0;
689 return;
690 }
691
692 // powerups don't spawn in for a while
693 if ( ent->item->giType == IT_POWERUP ) {
694 float respawn;
695
696 respawn = 45 + crandom() * 15;
697 ent->s.eFlags |= EF_NODRAW;
698 ent->r.contents = 0;
699 ent->nextthink = level.time + respawn * 1000;
700 ent->think = RespawnItem;
701 return;
702 }
703
704
705 trap_LinkEntity (ent);
706 }
707
708
709 qboolean itemRegistered[MAX_ITEMS];
710
711 /*
712 ==================
713 G_CheckTeamItems
714 ==================
715 */
716 void G_CheckTeamItems( void ) {
717
718 // Set up team stuff
719 Team_InitGame();
720
721 if( g_gametype.integer == GT_CTF ) {
722 gitem_t *item;
723
724 // check for the two flags
725 item = BG_FindItem( "Red Flag" );
726 if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
727 G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_redflag in map" );
728 }
729 item = BG_FindItem( "Blue Flag" );
730 if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
731 G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_blueflag in map" );
732 }
733 }
734 #ifdef MISSIONPACK
735 if( g_gametype.integer == GT_1FCTF ) {
736 gitem_t *item;
737
738 // check for all three flags
739 item = BG_FindItem( "Red Flag" );
740 if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
741 G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_redflag in map" );
742 }
743 item = BG_FindItem( "Blue Flag" );
744 if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
745 G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_blueflag in map" );
746 }
747 item = BG_FindItem( "Neutral Flag" );
748 if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
749 G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_neutralflag in map" );
750 }
751 }
752
753 if( g_gametype.integer == GT_OBELISK ) {
754 gentity_t *ent;
755
756 // check for the two obelisks
757 ent = NULL;
758 ent = G_Find( ent, FOFS(classname), "team_redobelisk" );
759 if( !ent ) {
760 G_Printf( S_COLOR_YELLOW "WARNING: No team_redobelisk in map" );
761 }
762
763 ent = NULL;
764 ent = G_Find( ent, FOFS(classname), "team_blueobelisk" );
765 if( !ent ) {
766 G_Printf( S_COLOR_YELLOW "WARNING: No team_blueobelisk in map" );
767 }
768 }
769
770 if( g_gametype.integer == GT_HARVESTER ) {
771 gentity_t *ent;
772
773 // check for all three obelisks
774 ent = NULL;
775 ent = G_Find( ent, FOFS(classname), "team_redobelisk" );
776 if( !ent ) {
777 G_Printf( S_COLOR_YELLOW "WARNING: No team_redobelisk in map" );
778 }
779
780 ent = NULL;
781 ent = G_Find( ent, FOFS(classname), "team_blueobelisk" );
782 if( !ent ) {
783 G_Printf( S_COLOR_YELLOW "WARNING: No team_blueobelisk in map" );
784 }
785
786 ent = NULL;
787 ent = G_Find( ent, FOFS(classname), "team_neutralobelisk" );
788 if( !ent ) {
789 G_Printf( S_COLOR_YELLOW "WARNING: No team_neutralobelisk in map" );
790 }
791 }
792 #endif
793 }
794
795 /*
796 ==============
797 ClearRegisteredItems
798 ==============
799 */
800 void ClearRegisteredItems( void ) {
801 memset( itemRegistered, 0, sizeof( itemRegistered ) );
802
803 // players always start with the base weapon
804 RegisterItem( BG_FindItemForWeapon( WP_MACHINEGUN ) );
805 RegisterItem( BG_FindItemForWeapon( WP_GAUNTLET ) );
806 #ifdef MISSIONPACK
807 if( g_gametype.integer == GT_HARVESTER ) {
808 RegisterItem( BG_FindItem( "Red Cube" ) );
809 RegisterItem( BG_FindItem( "Blue Cube" ) );
810 }
811 #endif
812 }
813
814 /*
815 ===============
816 RegisterItem
817
818 The item will be added to the precache list
819 ===============
820 */
821 void RegisterItem( gitem_t *item ) {
822 if ( !item ) {
823 G_Error( "RegisterItem: NULL" );
824 }
825 itemRegistered[ item - bg_itemlist ] = qtrue;
826 }
827
828
829 /*
830 ===============
831 SaveRegisteredItems
832
833 Write the needed items to a config string
834 so the client will know which ones to precache
835 ===============
836 */
837 void SaveRegisteredItems( void ) {
838 char string[MAX_ITEMS+1];
839 int i;
840 int count;
841
842 count = 0;
843 for ( i = 0 ; i < bg_numItems ; i++ ) {
844 if ( itemRegistered[i] ) {
845 count++;
846 string[i] = '1';
847 } else {
848 string[i] = '0';
849 }
850 }
851 string[ bg_numItems ] = 0;
852
853 G_Printf( "%i items registered\n", count );
854 trap_SetConfigstring(CS_ITEMS, string);
855 }
856
857 /*
858 ============
859 G_ItemDisabled
860 ============
861 */
862 int G_ItemDisabled( gitem_t *item ) {
863
864 char name[128];
865
866 Com_sprintf(name, sizeof(name), "disable_%s", item->classname);
867 return trap_Cvar_VariableIntegerValue( name );
868 }
869
870 /*
871 ============
872 G_SpawnItem
873
874 Sets the clipping size and plants the object on the floor.
875
876 Items can't be immediately dropped to floor, because they might
877 be on an entity that hasn't spawned yet.
878 ============
879 */
880 void G_SpawnItem (gentity_t *ent, gitem_t *item) {
881 G_SpawnFloat( "random", "0", &ent->random );
882 G_SpawnFloat( "wait", "0", &ent->wait );
883
884 RegisterItem( item );
885 if ( G_ItemDisabled(item) )
886 return;
887
888 ent->item = item;
889 // some movers spawn on the second frame, so delay item
890 // spawns until the third frame so they can ride trains
891 ent->nextthink = level.time + FRAMETIME * 2;
892 ent->think = FinishSpawningItem;
893
894 ent->physicsBounce = 0.50; // items are bouncy
895
896 if ( item->giType == IT_POWERUP ) {
897 G_SoundIndex( "sound/items/poweruprespawn.wav" );
898 G_SpawnFloat( "noglobalsound", "0", &ent->speed);
899 }
900
901 #ifdef MISSIONPACK
902 if ( item->giType == IT_PERSISTANT_POWERUP ) {
903 ent->s.generic1 = ent->spawnflags;
904 }
905 #endif
906 }
907
908
909 /*
910 ================
911 G_BounceItem
912
913 ================
914 */
915 void G_BounceItem( gentity_t *ent, trace_t *trace ) {
916 vec3_t velocity;
917 float dot;
918 int hitTime;
919
920 // reflect the velocity on the trace plane
921 hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
922 BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
923 dot = DotProduct( velocity, trace->plane.normal );
924 VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta );
925
926 // cut the velocity to keep from bouncing forever
927 VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta );
928
929 // check for stop
930 if ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 ) {
931 trace->endpos[2] += 1.0; // make sure it is off ground
932 SnapVector( trace->endpos );
933 G_SetOrigin( ent, trace->endpos );
934 ent->s.groundEntityNum = trace->entityNum;
935 return;
936 }
937
938 VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin);
939 VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
940 ent->s.pos.trTime = level.time;
941 }
942
943
944 /*
945 ================
946 G_RunItem
947
948 ================
949 */
950 void G_RunItem( gentity_t *ent ) {
951 vec3_t origin;
952 trace_t tr;
953 int contents;
954 int mask;
955
956 // if groundentity has been set to -1, it may have been pushed off an edge
957 if ( ent->s.groundEntityNum == -1 ) {
958 if ( ent->s.pos.trType != TR_GRAVITY ) {
959 ent->s.pos.trType = TR_GRAVITY;
960 ent->s.pos.trTime = level.time;
961 }
962 }
963
964 if ( ent->s.pos.trType == TR_STATIONARY ) {
965 // check think function
966 G_RunThink( ent );
967 return;
968 }
969
970 // get current position
971 BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
972
973 // trace a line from the previous position to the current position
974 if ( ent->clipmask ) {
975 mask = ent->clipmask;
976 } else {
977 mask = MASK_PLAYERSOLID & ~CONTENTS_BODY;//MASK_SOLID;
978 }
979 trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin,
980 ent->r.ownerNum, mask );
981
982 VectorCopy( tr.endpos, ent->r.currentOrigin );
983
984 if ( tr.startsolid ) {
985 tr.fraction = 0;
986 }
987
988 trap_LinkEntity( ent ); // FIXME: avoid this for stationary?
989
990 // check think function
991 G_RunThink( ent );
992
993 if ( tr.fraction == 1 ) {
994 return;
995 }
996
997 // if it is in a nodrop volume, remove it
998 contents = trap_PointContents( ent->r.currentOrigin, -1 );
999 if ( contents & CONTENTS_NODROP ) {
1000 if (ent->item && ent->item->giType == IT_TEAM) {
1001 Team_FreeEntity(ent);
1002 } else {
1003 G_FreeEntity( ent );
1004 }
1005 return;
1006 }
1007
1008 G_BounceItem( ent, &tr );
1009 }
1010
1011