g_items.c (25393B)
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 Foobar; 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 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 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 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 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 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 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 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 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 */ 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 */ 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 */ 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