From 6bf20c78f5b69d40bcc4931df93d29198435ab67 Mon Sep 17 00:00:00 2001 From: zakk Date: Fri, 26 Aug 2005 17:39:27 +0000 Subject: newlines fixed git-svn-id: svn://svn.icculus.org/quake3/trunk@6 edf5b092-35ff-0310-97b2-ce42778d08ea --- code/game/g_items.c | 2020 +++++++++++++++++++++++++-------------------------- 1 file changed, 1010 insertions(+), 1010 deletions(-) (limited to 'code/game/g_items.c') diff --git a/code/game/g_items.c b/code/game/g_items.c index 48d7b9f..1222fbd 100755 --- a/code/game/g_items.c +++ b/code/game/g_items.c @@ -1,1010 +1,1010 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Quake III Arena source code is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -#include "g_local.h" - -/* - - Items are any object that a player can touch to gain some effect. - - Pickup will return the number of seconds until they should respawn. - - all items should pop when dropped in lava or slime - - Respawnable items don't actually go away when picked up, they are - just made invisible and untouchable. This allows them to ride - movers and respawn apropriately. -*/ - - -#define RESPAWN_ARMOR 25 -#define RESPAWN_HEALTH 35 -#define RESPAWN_AMMO 40 -#define RESPAWN_HOLDABLE 60 -#define RESPAWN_MEGAHEALTH 35//120 -#define RESPAWN_POWERUP 120 - - -//====================================================================== - -int Pickup_Powerup( gentity_t *ent, gentity_t *other ) { - int quantity; - int i; - gclient_t *client; - - if ( !other->client->ps.powerups[ent->item->giTag] ) { - // round timing to seconds to make multiple powerup timers - // count in sync - other->client->ps.powerups[ent->item->giTag] = - level.time - ( level.time % 1000 ); - } - - if ( ent->count ) { - quantity = ent->count; - } else { - quantity = ent->item->quantity; - } - - other->client->ps.powerups[ent->item->giTag] += quantity * 1000; - - // give any nearby players a "denied" anti-reward - for ( i = 0 ; i < level.maxclients ; i++ ) { - vec3_t delta; - float len; - vec3_t forward; - trace_t tr; - - client = &level.clients[i]; - if ( client == other->client ) { - continue; - } - if ( client->pers.connected == CON_DISCONNECTED ) { - continue; - } - if ( client->ps.stats[STAT_HEALTH] <= 0 ) { - continue; - } - - // if same team in team game, no sound - // cannot use OnSameTeam as it expects to g_entities, not clients - if ( g_gametype.integer >= GT_TEAM && other->client->sess.sessionTeam == client->sess.sessionTeam ) { - continue; - } - - // if too far away, no sound - VectorSubtract( ent->s.pos.trBase, client->ps.origin, delta ); - len = VectorNormalize( delta ); - if ( len > 192 ) { - continue; - } - - // if not facing, no sound - AngleVectors( client->ps.viewangles, forward, NULL, NULL ); - if ( DotProduct( delta, forward ) < 0.4 ) { - continue; - } - - // if not line of sight, no sound - trap_Trace( &tr, client->ps.origin, NULL, NULL, ent->s.pos.trBase, ENTITYNUM_NONE, CONTENTS_SOLID ); - if ( tr.fraction != 1.0 ) { - continue; - } - - // anti-reward - client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_DENIEDREWARD; - } - return RESPAWN_POWERUP; -} - -//====================================================================== - -#ifdef MISSIONPACK -int Pickup_PersistantPowerup( gentity_t *ent, gentity_t *other ) { - int clientNum; - char userinfo[MAX_INFO_STRING]; - float handicap; - int max; - - other->client->ps.stats[STAT_PERSISTANT_POWERUP] = ent->item - bg_itemlist; - other->client->persistantPowerup = ent; - - switch( ent->item->giTag ) { - case PW_GUARD: - clientNum = other->client->ps.clientNum; - trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); - handicap = atof( Info_ValueForKey( userinfo, "handicap" ) ); - if( handicap<=0.0f || handicap>100.0f) { - handicap = 100.0f; - } - max = (int)(2 * handicap); - - other->health = max; - other->client->ps.stats[STAT_HEALTH] = max; - other->client->ps.stats[STAT_MAX_HEALTH] = max; - other->client->ps.stats[STAT_ARMOR] = max; - other->client->pers.maxHealth = max; - - break; - - case PW_SCOUT: - clientNum = other->client->ps.clientNum; - trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); - handicap = atof( Info_ValueForKey( userinfo, "handicap" ) ); - if( handicap<=0.0f || handicap>100.0f) { - handicap = 100.0f; - } - other->client->pers.maxHealth = handicap; - other->client->ps.stats[STAT_ARMOR] = 0; - break; - - case PW_DOUBLER: - clientNum = other->client->ps.clientNum; - trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); - handicap = atof( Info_ValueForKey( userinfo, "handicap" ) ); - if( handicap<=0.0f || handicap>100.0f) { - handicap = 100.0f; - } - other->client->pers.maxHealth = handicap; - break; - case PW_AMMOREGEN: - clientNum = other->client->ps.clientNum; - trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); - handicap = atof( Info_ValueForKey( userinfo, "handicap" ) ); - if( handicap<=0.0f || handicap>100.0f) { - handicap = 100.0f; - } - other->client->pers.maxHealth = handicap; - memset(other->client->ammoTimes, 0, sizeof(other->client->ammoTimes)); - break; - default: - clientNum = other->client->ps.clientNum; - trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); - handicap = atof( Info_ValueForKey( userinfo, "handicap" ) ); - if( handicap<=0.0f || handicap>100.0f) { - handicap = 100.0f; - } - other->client->pers.maxHealth = handicap; - break; - } - - return -1; -} - -//====================================================================== -#endif - -int Pickup_Holdable( gentity_t *ent, gentity_t *other ) { - - other->client->ps.stats[STAT_HOLDABLE_ITEM] = ent->item - bg_itemlist; - - if( ent->item->giTag == HI_KAMIKAZE ) { - other->client->ps.eFlags |= EF_KAMIKAZE; - } - - return RESPAWN_HOLDABLE; -} - - -//====================================================================== - -void Add_Ammo (gentity_t *ent, int weapon, int count) -{ - ent->client->ps.ammo[weapon] += count; - if ( ent->client->ps.ammo[weapon] > 200 ) { - ent->client->ps.ammo[weapon] = 200; - } -} - -int Pickup_Ammo (gentity_t *ent, gentity_t *other) -{ - int quantity; - - if ( ent->count ) { - quantity = ent->count; - } else { - quantity = ent->item->quantity; - } - - Add_Ammo (other, ent->item->giTag, quantity); - - return RESPAWN_AMMO; -} - -//====================================================================== - - -int Pickup_Weapon (gentity_t *ent, gentity_t *other) { - int quantity; - - if ( ent->count < 0 ) { - quantity = 0; // None for you, sir! - } else { - if ( ent->count ) { - quantity = ent->count; - } else { - quantity = ent->item->quantity; - } - - // dropped items and teamplay weapons always have full ammo - if ( ! (ent->flags & FL_DROPPED_ITEM) && g_gametype.integer != GT_TEAM ) { - // respawning rules - // drop the quantity if the already have over the minimum - if ( other->client->ps.ammo[ ent->item->giTag ] < quantity ) { - quantity = quantity - other->client->ps.ammo[ ent->item->giTag ]; - } else { - quantity = 1; // only add a single shot - } - } - } - - // add the weapon - other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag ); - - Add_Ammo( other, ent->item->giTag, quantity ); - - if (ent->item->giTag == WP_GRAPPLING_HOOK) - other->client->ps.ammo[ent->item->giTag] = -1; // unlimited ammo - - // team deathmatch has slow weapon respawns - if ( g_gametype.integer == GT_TEAM ) { - return g_weaponTeamRespawn.integer; - } - - return g_weaponRespawn.integer; -} - - -//====================================================================== - -int Pickup_Health (gentity_t *ent, gentity_t *other) { - int max; - int quantity; - - // small and mega healths will go over the max -#ifdef MISSIONPACK - if( other->client && bg_itemlist[other->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { - max = other->client->ps.stats[STAT_MAX_HEALTH]; - } - else -#endif - if ( ent->item->quantity != 5 && ent->item->quantity != 100 ) { - max = other->client->ps.stats[STAT_MAX_HEALTH]; - } else { - max = other->client->ps.stats[STAT_MAX_HEALTH] * 2; - } - - if ( ent->count ) { - quantity = ent->count; - } else { - quantity = ent->item->quantity; - } - - other->health += quantity; - - if (other->health > max ) { - other->health = max; - } - other->client->ps.stats[STAT_HEALTH] = other->health; - - if ( ent->item->quantity == 100 ) { // mega health respawns slow - return RESPAWN_MEGAHEALTH; - } - - return RESPAWN_HEALTH; -} - -//====================================================================== - -int Pickup_Armor( gentity_t *ent, gentity_t *other ) { -#ifdef MISSIONPACK - int upperBound; - - other->client->ps.stats[STAT_ARMOR] += ent->item->quantity; - - if( other->client && bg_itemlist[other->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { - upperBound = other->client->ps.stats[STAT_MAX_HEALTH]; - } - else { - upperBound = other->client->ps.stats[STAT_MAX_HEALTH] * 2; - } - - if ( other->client->ps.stats[STAT_ARMOR] > upperBound ) { - other->client->ps.stats[STAT_ARMOR] = upperBound; - } -#else - other->client->ps.stats[STAT_ARMOR] += ent->item->quantity; - if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] * 2 ) { - other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH] * 2; - } -#endif - - return RESPAWN_ARMOR; -} - -//====================================================================== - -/* -=============== -RespawnItem -=============== -*/ -void RespawnItem( gentity_t *ent ) { - // randomly select from teamed entities - if (ent->team) { - gentity_t *master; - int count; - int choice; - - if ( !ent->teammaster ) { - G_Error( "RespawnItem: bad teammaster"); - } - master = ent->teammaster; - - for (count = 0, ent = master; ent; ent = ent->teamchain, count++) - ; - - choice = rand() % count; - - for (count = 0, ent = master; count < choice; ent = ent->teamchain, count++) - ; - } - - ent->r.contents = CONTENTS_TRIGGER; - ent->s.eFlags &= ~EF_NODRAW; - ent->r.svFlags &= ~SVF_NOCLIENT; - trap_LinkEntity (ent); - - if ( ent->item->giType == IT_POWERUP ) { - // play powerup spawn sound to all clients - gentity_t *te; - - // if the powerup respawn sound should Not be global - if (ent->speed) { - te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND ); - } - else { - te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND ); - } - te->s.eventParm = G_SoundIndex( "sound/items/poweruprespawn.wav" ); - te->r.svFlags |= SVF_BROADCAST; - } - - if ( ent->item->giType == IT_HOLDABLE && ent->item->giTag == HI_KAMIKAZE ) { - // play powerup spawn sound to all clients - gentity_t *te; - - // if the powerup respawn sound should Not be global - if (ent->speed) { - te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND ); - } - else { - te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND ); - } - te->s.eventParm = G_SoundIndex( "sound/items/kamikazerespawn.wav" ); - te->r.svFlags |= SVF_BROADCAST; - } - - // play the normal respawn sound only to nearby clients - G_AddEvent( ent, EV_ITEM_RESPAWN, 0 ); - - ent->nextthink = 0; -} - - -/* -=============== -Touch_Item -=============== -*/ -void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) { - int respawn; - qboolean predict; - - if (!other->client) - return; - if (other->health < 1) - return; // dead people can't pickup - - // the same pickup rules are used for client side and server side - if ( !BG_CanItemBeGrabbed( g_gametype.integer, &ent->s, &other->client->ps ) ) { - return; - } - - G_LogPrintf( "Item: %i %s\n", other->s.number, ent->item->classname ); - - predict = other->client->pers.predictItemPickup; - - // call the item-specific pickup function - switch( ent->item->giType ) { - case IT_WEAPON: - respawn = Pickup_Weapon(ent, other); -// predict = qfalse; - break; - case IT_AMMO: - respawn = Pickup_Ammo(ent, other); -// predict = qfalse; - break; - case IT_ARMOR: - respawn = Pickup_Armor(ent, other); - break; - case IT_HEALTH: - respawn = Pickup_Health(ent, other); - break; - case IT_POWERUP: - respawn = Pickup_Powerup(ent, other); - predict = qfalse; - break; -#ifdef MISSIONPACK - case IT_PERSISTANT_POWERUP: - respawn = Pickup_PersistantPowerup(ent, other); - break; -#endif - case IT_TEAM: - respawn = Pickup_Team(ent, other); - break; - case IT_HOLDABLE: - respawn = Pickup_Holdable(ent, other); - break; - default: - return; - } - - if ( !respawn ) { - return; - } - - // play the normal pickup sound - if (predict) { - G_AddPredictableEvent( other, EV_ITEM_PICKUP, ent->s.modelindex ); - } else { - G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex ); - } - - // powerup pickups are global broadcasts - if ( ent->item->giType == IT_POWERUP || ent->item->giType == IT_TEAM) { - // if we want the global sound to play - if (!ent->speed) { - gentity_t *te; - - te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP ); - te->s.eventParm = ent->s.modelindex; - te->r.svFlags |= SVF_BROADCAST; - } else { - gentity_t *te; - - te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP ); - te->s.eventParm = ent->s.modelindex; - // only send this temp entity to a single client - te->r.svFlags |= SVF_SINGLECLIENT; - te->r.singleClient = other->s.number; - } - } - - // fire item targets - G_UseTargets (ent, other); - - // wait of -1 will not respawn - if ( ent->wait == -1 ) { - ent->r.svFlags |= SVF_NOCLIENT; - ent->s.eFlags |= EF_NODRAW; - ent->r.contents = 0; - ent->unlinkAfterEvent = qtrue; - return; - } - - // non zero wait overrides respawn time - if ( ent->wait ) { - respawn = ent->wait; - } - - // random can be used to vary the respawn time - if ( ent->random ) { - respawn += crandom() * ent->random; - if ( respawn < 1 ) { - respawn = 1; - } - } - - // dropped items will not respawn - if ( ent->flags & FL_DROPPED_ITEM ) { - ent->freeAfterEvent = qtrue; - } - - // picked up items still stay around, they just don't - // draw anything. This allows respawnable items - // to be placed on movers. - ent->r.svFlags |= SVF_NOCLIENT; - ent->s.eFlags |= EF_NODRAW; - ent->r.contents = 0; - - // ZOID - // A negative respawn times means to never respawn this item (but don't - // delete it). This is used by items that are respawned by third party - // events such as ctf flags - if ( respawn <= 0 ) { - ent->nextthink = 0; - ent->think = 0; - } else { - ent->nextthink = level.time + respawn * 1000; - ent->think = RespawnItem; - } - trap_LinkEntity( ent ); -} - - -//====================================================================== - -/* -================ -LaunchItem - -Spawns an item and tosses it forward -================ -*/ -gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity ) { - gentity_t *dropped; - - dropped = G_Spawn(); - - dropped->s.eType = ET_ITEM; - dropped->s.modelindex = item - bg_itemlist; // store item number in modelindex - dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item - - dropped->classname = item->classname; - dropped->item = item; - VectorSet (dropped->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS); - VectorSet (dropped->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS); - dropped->r.contents = CONTENTS_TRIGGER; - - dropped->touch = Touch_Item; - - G_SetOrigin( dropped, origin ); - dropped->s.pos.trType = TR_GRAVITY; - dropped->s.pos.trTime = level.time; - VectorCopy( velocity, dropped->s.pos.trDelta ); - - dropped->s.eFlags |= EF_BOUNCE_HALF; -#ifdef MISSIONPACK - if ((g_gametype.integer == GT_CTF || g_gametype.integer == GT_1FCTF) && item->giType == IT_TEAM) { // Special case for CTF flags -#else - if (g_gametype.integer == GT_CTF && item->giType == IT_TEAM) { // Special case for CTF flags -#endif - dropped->think = Team_DroppedFlagThink; - dropped->nextthink = level.time + 30000; - Team_CheckDroppedItem( dropped ); - } else { // auto-remove after 30 seconds - dropped->think = G_FreeEntity; - dropped->nextthink = level.time + 30000; - } - - dropped->flags = FL_DROPPED_ITEM; - - trap_LinkEntity (dropped); - - return dropped; -} - -/* -================ -Drop_Item - -Spawns an item and tosses it forward -================ -*/ -gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle ) { - vec3_t velocity; - vec3_t angles; - - VectorCopy( ent->s.apos.trBase, angles ); - angles[YAW] += angle; - angles[PITCH] = 0; // always forward - - AngleVectors( angles, velocity, NULL, NULL ); - VectorScale( velocity, 150, velocity ); - velocity[2] += 200 + crandom() * 50; - - return LaunchItem( item, ent->s.pos.trBase, velocity ); -} - - -/* -================ -Use_Item - -Respawn the item -================ -*/ -void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator ) { - RespawnItem( ent ); -} - -//====================================================================== - -/* -================ -FinishSpawningItem - -Traces down to find where an item should rest, instead of letting them -free fall from their spawn points -================ -*/ -void FinishSpawningItem( gentity_t *ent ) { - trace_t tr; - vec3_t dest; - - VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS ); - VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS ); - - ent->s.eType = ET_ITEM; - ent->s.modelindex = ent->item - bg_itemlist; // store item number in modelindex - ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item - - ent->r.contents = CONTENTS_TRIGGER; - ent->touch = Touch_Item; - // useing an item causes it to respawn - ent->use = Use_Item; - - if ( ent->spawnflags & 1 ) { - // suspended - G_SetOrigin( ent, ent->s.origin ); - } else { - // drop to floor - VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 ); - trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID ); - if ( tr.startsolid ) { - G_Printf ("FinishSpawningItem: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); - G_FreeEntity( ent ); - return; - } - - // allow to ride movers - ent->s.groundEntityNum = tr.entityNum; - - G_SetOrigin( ent, tr.endpos ); - } - - // team slaves and targeted items aren't present at start - if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) { - ent->s.eFlags |= EF_NODRAW; - ent->r.contents = 0; - return; - } - - // powerups don't spawn in for a while - if ( ent->item->giType == IT_POWERUP ) { - float respawn; - - respawn = 45 + crandom() * 15; - ent->s.eFlags |= EF_NODRAW; - ent->r.contents = 0; - ent->nextthink = level.time + respawn * 1000; - ent->think = RespawnItem; - return; - } - - - trap_LinkEntity (ent); -} - - -qboolean itemRegistered[MAX_ITEMS]; - -/* -================== -G_CheckTeamItems -================== -*/ -void G_CheckTeamItems( void ) { - - // Set up team stuff - Team_InitGame(); - - if( g_gametype.integer == GT_CTF ) { - gitem_t *item; - - // check for the two flags - item = BG_FindItem( "Red Flag" ); - if ( !item || !itemRegistered[ item - bg_itemlist ] ) { - G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_redflag in map" ); - } - item = BG_FindItem( "Blue Flag" ); - if ( !item || !itemRegistered[ item - bg_itemlist ] ) { - G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_blueflag in map" ); - } - } -#ifdef MISSIONPACK - if( g_gametype.integer == GT_1FCTF ) { - gitem_t *item; - - // check for all three flags - item = BG_FindItem( "Red Flag" ); - if ( !item || !itemRegistered[ item - bg_itemlist ] ) { - G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_redflag in map" ); - } - item = BG_FindItem( "Blue Flag" ); - if ( !item || !itemRegistered[ item - bg_itemlist ] ) { - G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_blueflag in map" ); - } - item = BG_FindItem( "Neutral Flag" ); - if ( !item || !itemRegistered[ item - bg_itemlist ] ) { - G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_neutralflag in map" ); - } - } - - if( g_gametype.integer == GT_OBELISK ) { - gentity_t *ent; - - // check for the two obelisks - ent = NULL; - ent = G_Find( ent, FOFS(classname), "team_redobelisk" ); - if( !ent ) { - G_Printf( S_COLOR_YELLOW "WARNING: No team_redobelisk in map" ); - } - - ent = NULL; - ent = G_Find( ent, FOFS(classname), "team_blueobelisk" ); - if( !ent ) { - G_Printf( S_COLOR_YELLOW "WARNING: No team_blueobelisk in map" ); - } - } - - if( g_gametype.integer == GT_HARVESTER ) { - gentity_t *ent; - - // check for all three obelisks - ent = NULL; - ent = G_Find( ent, FOFS(classname), "team_redobelisk" ); - if( !ent ) { - G_Printf( S_COLOR_YELLOW "WARNING: No team_redobelisk in map" ); - } - - ent = NULL; - ent = G_Find( ent, FOFS(classname), "team_blueobelisk" ); - if( !ent ) { - G_Printf( S_COLOR_YELLOW "WARNING: No team_blueobelisk in map" ); - } - - ent = NULL; - ent = G_Find( ent, FOFS(classname), "team_neutralobelisk" ); - if( !ent ) { - G_Printf( S_COLOR_YELLOW "WARNING: No team_neutralobelisk in map" ); - } - } -#endif -} - -/* -============== -ClearRegisteredItems -============== -*/ -void ClearRegisteredItems( void ) { - memset( itemRegistered, 0, sizeof( itemRegistered ) ); - - // players always start with the base weapon - RegisterItem( BG_FindItemForWeapon( WP_MACHINEGUN ) ); - RegisterItem( BG_FindItemForWeapon( WP_GAUNTLET ) ); -#ifdef MISSIONPACK - if( g_gametype.integer == GT_HARVESTER ) { - RegisterItem( BG_FindItem( "Red Cube" ) ); - RegisterItem( BG_FindItem( "Blue Cube" ) ); - } -#endif -} - -/* -=============== -RegisterItem - -The item will be added to the precache list -=============== -*/ -void RegisterItem( gitem_t *item ) { - if ( !item ) { - G_Error( "RegisterItem: NULL" ); - } - itemRegistered[ item - bg_itemlist ] = qtrue; -} - - -/* -=============== -SaveRegisteredItems - -Write the needed items to a config string -so the client will know which ones to precache -=============== -*/ -void SaveRegisteredItems( void ) { - char string[MAX_ITEMS+1]; - int i; - int count; - - count = 0; - for ( i = 0 ; i < bg_numItems ; i++ ) { - if ( itemRegistered[i] ) { - count++; - string[i] = '1'; - } else { - string[i] = '0'; - } - } - string[ bg_numItems ] = 0; - - G_Printf( "%i items registered\n", count ); - trap_SetConfigstring(CS_ITEMS, string); -} - -/* -============ -G_ItemDisabled -============ -*/ -int G_ItemDisabled( gitem_t *item ) { - - char name[128]; - - Com_sprintf(name, sizeof(name), "disable_%s", item->classname); - return trap_Cvar_VariableIntegerValue( name ); -} - -/* -============ -G_SpawnItem - -Sets the clipping size and plants the object on the floor. - -Items can't be immediately dropped to floor, because they might -be on an entity that hasn't spawned yet. -============ -*/ -void G_SpawnItem (gentity_t *ent, gitem_t *item) { - G_SpawnFloat( "random", "0", &ent->random ); - G_SpawnFloat( "wait", "0", &ent->wait ); - - RegisterItem( item ); - if ( G_ItemDisabled(item) ) - return; - - ent->item = item; - // some movers spawn on the second frame, so delay item - // spawns until the third frame so they can ride trains - ent->nextthink = level.time + FRAMETIME * 2; - ent->think = FinishSpawningItem; - - ent->physicsBounce = 0.50; // items are bouncy - - if ( item->giType == IT_POWERUP ) { - G_SoundIndex( "sound/items/poweruprespawn.wav" ); - G_SpawnFloat( "noglobalsound", "0", &ent->speed); - } - -#ifdef MISSIONPACK - if ( item->giType == IT_PERSISTANT_POWERUP ) { - ent->s.generic1 = ent->spawnflags; - } -#endif -} - - -/* -================ -G_BounceItem - -================ -*/ -void G_BounceItem( gentity_t *ent, trace_t *trace ) { - vec3_t velocity; - float dot; - int hitTime; - - // reflect the velocity on the trace plane - hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction; - BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity ); - dot = DotProduct( velocity, trace->plane.normal ); - VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta ); - - // cut the velocity to keep from bouncing forever - VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta ); - - // check for stop - if ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 ) { - trace->endpos[2] += 1.0; // make sure it is off ground - SnapVector( trace->endpos ); - G_SetOrigin( ent, trace->endpos ); - ent->s.groundEntityNum = trace->entityNum; - return; - } - - VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin); - VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); - ent->s.pos.trTime = level.time; -} - - -/* -================ -G_RunItem - -================ -*/ -void G_RunItem( gentity_t *ent ) { - vec3_t origin; - trace_t tr; - int contents; - int mask; - - // if groundentity has been set to -1, it may have been pushed off an edge - if ( ent->s.groundEntityNum == -1 ) { - if ( ent->s.pos.trType != TR_GRAVITY ) { - ent->s.pos.trType = TR_GRAVITY; - ent->s.pos.trTime = level.time; - } - } - - if ( ent->s.pos.trType == TR_STATIONARY ) { - // check think function - G_RunThink( ent ); - return; - } - - // get current position - BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); - - // trace a line from the previous position to the current position - if ( ent->clipmask ) { - mask = ent->clipmask; - } else { - mask = MASK_PLAYERSOLID & ~CONTENTS_BODY;//MASK_SOLID; - } - trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, - ent->r.ownerNum, mask ); - - VectorCopy( tr.endpos, ent->r.currentOrigin ); - - if ( tr.startsolid ) { - tr.fraction = 0; - } - - trap_LinkEntity( ent ); // FIXME: avoid this for stationary? - - // check think function - G_RunThink( ent ); - - if ( tr.fraction == 1 ) { - return; - } - - // if it is in a nodrop volume, remove it - contents = trap_PointContents( ent->r.currentOrigin, -1 ); - if ( contents & CONTENTS_NODROP ) { - if (ent->item && ent->item->giType == IT_TEAM) { - Team_FreeEntity(ent); - } else { - G_FreeEntity( ent ); - } - return; - } - - G_BounceItem( ent, &tr ); -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +#include "g_local.h" + +/* + + Items are any object that a player can touch to gain some effect. + + Pickup will return the number of seconds until they should respawn. + + all items should pop when dropped in lava or slime + + Respawnable items don't actually go away when picked up, they are + just made invisible and untouchable. This allows them to ride + movers and respawn apropriately. +*/ + + +#define RESPAWN_ARMOR 25 +#define RESPAWN_HEALTH 35 +#define RESPAWN_AMMO 40 +#define RESPAWN_HOLDABLE 60 +#define RESPAWN_MEGAHEALTH 35//120 +#define RESPAWN_POWERUP 120 + + +//====================================================================== + +int Pickup_Powerup( gentity_t *ent, gentity_t *other ) { + int quantity; + int i; + gclient_t *client; + + if ( !other->client->ps.powerups[ent->item->giTag] ) { + // round timing to seconds to make multiple powerup timers + // count in sync + other->client->ps.powerups[ent->item->giTag] = + level.time - ( level.time % 1000 ); + } + + if ( ent->count ) { + quantity = ent->count; + } else { + quantity = ent->item->quantity; + } + + other->client->ps.powerups[ent->item->giTag] += quantity * 1000; + + // give any nearby players a "denied" anti-reward + for ( i = 0 ; i < level.maxclients ; i++ ) { + vec3_t delta; + float len; + vec3_t forward; + trace_t tr; + + client = &level.clients[i]; + if ( client == other->client ) { + continue; + } + if ( client->pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( client->ps.stats[STAT_HEALTH] <= 0 ) { + continue; + } + + // if same team in team game, no sound + // cannot use OnSameTeam as it expects to g_entities, not clients + if ( g_gametype.integer >= GT_TEAM && other->client->sess.sessionTeam == client->sess.sessionTeam ) { + continue; + } + + // if too far away, no sound + VectorSubtract( ent->s.pos.trBase, client->ps.origin, delta ); + len = VectorNormalize( delta ); + if ( len > 192 ) { + continue; + } + + // if not facing, no sound + AngleVectors( client->ps.viewangles, forward, NULL, NULL ); + if ( DotProduct( delta, forward ) < 0.4 ) { + continue; + } + + // if not line of sight, no sound + trap_Trace( &tr, client->ps.origin, NULL, NULL, ent->s.pos.trBase, ENTITYNUM_NONE, CONTENTS_SOLID ); + if ( tr.fraction != 1.0 ) { + continue; + } + + // anti-reward + client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_DENIEDREWARD; + } + return RESPAWN_POWERUP; +} + +//====================================================================== + +#ifdef MISSIONPACK +int Pickup_PersistantPowerup( gentity_t *ent, gentity_t *other ) { + int clientNum; + char userinfo[MAX_INFO_STRING]; + float handicap; + int max; + + other->client->ps.stats[STAT_PERSISTANT_POWERUP] = ent->item - bg_itemlist; + other->client->persistantPowerup = ent; + + switch( ent->item->giTag ) { + case PW_GUARD: + clientNum = other->client->ps.clientNum; + trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); + handicap = atof( Info_ValueForKey( userinfo, "handicap" ) ); + if( handicap<=0.0f || handicap>100.0f) { + handicap = 100.0f; + } + max = (int)(2 * handicap); + + other->health = max; + other->client->ps.stats[STAT_HEALTH] = max; + other->client->ps.stats[STAT_MAX_HEALTH] = max; + other->client->ps.stats[STAT_ARMOR] = max; + other->client->pers.maxHealth = max; + + break; + + case PW_SCOUT: + clientNum = other->client->ps.clientNum; + trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); + handicap = atof( Info_ValueForKey( userinfo, "handicap" ) ); + if( handicap<=0.0f || handicap>100.0f) { + handicap = 100.0f; + } + other->client->pers.maxHealth = handicap; + other->client->ps.stats[STAT_ARMOR] = 0; + break; + + case PW_DOUBLER: + clientNum = other->client->ps.clientNum; + trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); + handicap = atof( Info_ValueForKey( userinfo, "handicap" ) ); + if( handicap<=0.0f || handicap>100.0f) { + handicap = 100.0f; + } + other->client->pers.maxHealth = handicap; + break; + case PW_AMMOREGEN: + clientNum = other->client->ps.clientNum; + trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); + handicap = atof( Info_ValueForKey( userinfo, "handicap" ) ); + if( handicap<=0.0f || handicap>100.0f) { + handicap = 100.0f; + } + other->client->pers.maxHealth = handicap; + memset(other->client->ammoTimes, 0, sizeof(other->client->ammoTimes)); + break; + default: + clientNum = other->client->ps.clientNum; + trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); + handicap = atof( Info_ValueForKey( userinfo, "handicap" ) ); + if( handicap<=0.0f || handicap>100.0f) { + handicap = 100.0f; + } + other->client->pers.maxHealth = handicap; + break; + } + + return -1; +} + +//====================================================================== +#endif + +int Pickup_Holdable( gentity_t *ent, gentity_t *other ) { + + other->client->ps.stats[STAT_HOLDABLE_ITEM] = ent->item - bg_itemlist; + + if( ent->item->giTag == HI_KAMIKAZE ) { + other->client->ps.eFlags |= EF_KAMIKAZE; + } + + return RESPAWN_HOLDABLE; +} + + +//====================================================================== + +void Add_Ammo (gentity_t *ent, int weapon, int count) +{ + ent->client->ps.ammo[weapon] += count; + if ( ent->client->ps.ammo[weapon] > 200 ) { + ent->client->ps.ammo[weapon] = 200; + } +} + +int Pickup_Ammo (gentity_t *ent, gentity_t *other) +{ + int quantity; + + if ( ent->count ) { + quantity = ent->count; + } else { + quantity = ent->item->quantity; + } + + Add_Ammo (other, ent->item->giTag, quantity); + + return RESPAWN_AMMO; +} + +//====================================================================== + + +int Pickup_Weapon (gentity_t *ent, gentity_t *other) { + int quantity; + + if ( ent->count < 0 ) { + quantity = 0; // None for you, sir! + } else { + if ( ent->count ) { + quantity = ent->count; + } else { + quantity = ent->item->quantity; + } + + // dropped items and teamplay weapons always have full ammo + if ( ! (ent->flags & FL_DROPPED_ITEM) && g_gametype.integer != GT_TEAM ) { + // respawning rules + // drop the quantity if the already have over the minimum + if ( other->client->ps.ammo[ ent->item->giTag ] < quantity ) { + quantity = quantity - other->client->ps.ammo[ ent->item->giTag ]; + } else { + quantity = 1; // only add a single shot + } + } + } + + // add the weapon + other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag ); + + Add_Ammo( other, ent->item->giTag, quantity ); + + if (ent->item->giTag == WP_GRAPPLING_HOOK) + other->client->ps.ammo[ent->item->giTag] = -1; // unlimited ammo + + // team deathmatch has slow weapon respawns + if ( g_gametype.integer == GT_TEAM ) { + return g_weaponTeamRespawn.integer; + } + + return g_weaponRespawn.integer; +} + + +//====================================================================== + +int Pickup_Health (gentity_t *ent, gentity_t *other) { + int max; + int quantity; + + // small and mega healths will go over the max +#ifdef MISSIONPACK + if( other->client && bg_itemlist[other->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { + max = other->client->ps.stats[STAT_MAX_HEALTH]; + } + else +#endif + if ( ent->item->quantity != 5 && ent->item->quantity != 100 ) { + max = other->client->ps.stats[STAT_MAX_HEALTH]; + } else { + max = other->client->ps.stats[STAT_MAX_HEALTH] * 2; + } + + if ( ent->count ) { + quantity = ent->count; + } else { + quantity = ent->item->quantity; + } + + other->health += quantity; + + if (other->health > max ) { + other->health = max; + } + other->client->ps.stats[STAT_HEALTH] = other->health; + + if ( ent->item->quantity == 100 ) { // mega health respawns slow + return RESPAWN_MEGAHEALTH; + } + + return RESPAWN_HEALTH; +} + +//====================================================================== + +int Pickup_Armor( gentity_t *ent, gentity_t *other ) { +#ifdef MISSIONPACK + int upperBound; + + other->client->ps.stats[STAT_ARMOR] += ent->item->quantity; + + if( other->client && bg_itemlist[other->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { + upperBound = other->client->ps.stats[STAT_MAX_HEALTH]; + } + else { + upperBound = other->client->ps.stats[STAT_MAX_HEALTH] * 2; + } + + if ( other->client->ps.stats[STAT_ARMOR] > upperBound ) { + other->client->ps.stats[STAT_ARMOR] = upperBound; + } +#else + other->client->ps.stats[STAT_ARMOR] += ent->item->quantity; + if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] * 2 ) { + other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH] * 2; + } +#endif + + return RESPAWN_ARMOR; +} + +//====================================================================== + +/* +=============== +RespawnItem +=============== +*/ +void RespawnItem( gentity_t *ent ) { + // randomly select from teamed entities + if (ent->team) { + gentity_t *master; + int count; + int choice; + + if ( !ent->teammaster ) { + G_Error( "RespawnItem: bad teammaster"); + } + master = ent->teammaster; + + for (count = 0, ent = master; ent; ent = ent->teamchain, count++) + ; + + choice = rand() % count; + + for (count = 0, ent = master; count < choice; ent = ent->teamchain, count++) + ; + } + + ent->r.contents = CONTENTS_TRIGGER; + ent->s.eFlags &= ~EF_NODRAW; + ent->r.svFlags &= ~SVF_NOCLIENT; + trap_LinkEntity (ent); + + if ( ent->item->giType == IT_POWERUP ) { + // play powerup spawn sound to all clients + gentity_t *te; + + // if the powerup respawn sound should Not be global + if (ent->speed) { + te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND ); + } + else { + te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND ); + } + te->s.eventParm = G_SoundIndex( "sound/items/poweruprespawn.wav" ); + te->r.svFlags |= SVF_BROADCAST; + } + + if ( ent->item->giType == IT_HOLDABLE && ent->item->giTag == HI_KAMIKAZE ) { + // play powerup spawn sound to all clients + gentity_t *te; + + // if the powerup respawn sound should Not be global + if (ent->speed) { + te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND ); + } + else { + te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND ); + } + te->s.eventParm = G_SoundIndex( "sound/items/kamikazerespawn.wav" ); + te->r.svFlags |= SVF_BROADCAST; + } + + // play the normal respawn sound only to nearby clients + G_AddEvent( ent, EV_ITEM_RESPAWN, 0 ); + + ent->nextthink = 0; +} + + +/* +=============== +Touch_Item +=============== +*/ +void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) { + int respawn; + qboolean predict; + + if (!other->client) + return; + if (other->health < 1) + return; // dead people can't pickup + + // the same pickup rules are used for client side and server side + if ( !BG_CanItemBeGrabbed( g_gametype.integer, &ent->s, &other->client->ps ) ) { + return; + } + + G_LogPrintf( "Item: %i %s\n", other->s.number, ent->item->classname ); + + predict = other->client->pers.predictItemPickup; + + // call the item-specific pickup function + switch( ent->item->giType ) { + case IT_WEAPON: + respawn = Pickup_Weapon(ent, other); +// predict = qfalse; + break; + case IT_AMMO: + respawn = Pickup_Ammo(ent, other); +// predict = qfalse; + break; + case IT_ARMOR: + respawn = Pickup_Armor(ent, other); + break; + case IT_HEALTH: + respawn = Pickup_Health(ent, other); + break; + case IT_POWERUP: + respawn = Pickup_Powerup(ent, other); + predict = qfalse; + break; +#ifdef MISSIONPACK + case IT_PERSISTANT_POWERUP: + respawn = Pickup_PersistantPowerup(ent, other); + break; +#endif + case IT_TEAM: + respawn = Pickup_Team(ent, other); + break; + case IT_HOLDABLE: + respawn = Pickup_Holdable(ent, other); + break; + default: + return; + } + + if ( !respawn ) { + return; + } + + // play the normal pickup sound + if (predict) { + G_AddPredictableEvent( other, EV_ITEM_PICKUP, ent->s.modelindex ); + } else { + G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex ); + } + + // powerup pickups are global broadcasts + if ( ent->item->giType == IT_POWERUP || ent->item->giType == IT_TEAM) { + // if we want the global sound to play + if (!ent->speed) { + gentity_t *te; + + te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP ); + te->s.eventParm = ent->s.modelindex; + te->r.svFlags |= SVF_BROADCAST; + } else { + gentity_t *te; + + te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP ); + te->s.eventParm = ent->s.modelindex; + // only send this temp entity to a single client + te->r.svFlags |= SVF_SINGLECLIENT; + te->r.singleClient = other->s.number; + } + } + + // fire item targets + G_UseTargets (ent, other); + + // wait of -1 will not respawn + if ( ent->wait == -1 ) { + ent->r.svFlags |= SVF_NOCLIENT; + ent->s.eFlags |= EF_NODRAW; + ent->r.contents = 0; + ent->unlinkAfterEvent = qtrue; + return; + } + + // non zero wait overrides respawn time + if ( ent->wait ) { + respawn = ent->wait; + } + + // random can be used to vary the respawn time + if ( ent->random ) { + respawn += crandom() * ent->random; + if ( respawn < 1 ) { + respawn = 1; + } + } + + // dropped items will not respawn + if ( ent->flags & FL_DROPPED_ITEM ) { + ent->freeAfterEvent = qtrue; + } + + // picked up items still stay around, they just don't + // draw anything. This allows respawnable items + // to be placed on movers. + ent->r.svFlags |= SVF_NOCLIENT; + ent->s.eFlags |= EF_NODRAW; + ent->r.contents = 0; + + // ZOID + // A negative respawn times means to never respawn this item (but don't + // delete it). This is used by items that are respawned by third party + // events such as ctf flags + if ( respawn <= 0 ) { + ent->nextthink = 0; + ent->think = 0; + } else { + ent->nextthink = level.time + respawn * 1000; + ent->think = RespawnItem; + } + trap_LinkEntity( ent ); +} + + +//====================================================================== + +/* +================ +LaunchItem + +Spawns an item and tosses it forward +================ +*/ +gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity ) { + gentity_t *dropped; + + dropped = G_Spawn(); + + dropped->s.eType = ET_ITEM; + dropped->s.modelindex = item - bg_itemlist; // store item number in modelindex + dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item + + dropped->classname = item->classname; + dropped->item = item; + VectorSet (dropped->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS); + VectorSet (dropped->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS); + dropped->r.contents = CONTENTS_TRIGGER; + + dropped->touch = Touch_Item; + + G_SetOrigin( dropped, origin ); + dropped->s.pos.trType = TR_GRAVITY; + dropped->s.pos.trTime = level.time; + VectorCopy( velocity, dropped->s.pos.trDelta ); + + dropped->s.eFlags |= EF_BOUNCE_HALF; +#ifdef MISSIONPACK + if ((g_gametype.integer == GT_CTF || g_gametype.integer == GT_1FCTF) && item->giType == IT_TEAM) { // Special case for CTF flags +#else + if (g_gametype.integer == GT_CTF && item->giType == IT_TEAM) { // Special case for CTF flags +#endif + dropped->think = Team_DroppedFlagThink; + dropped->nextthink = level.time + 30000; + Team_CheckDroppedItem( dropped ); + } else { // auto-remove after 30 seconds + dropped->think = G_FreeEntity; + dropped->nextthink = level.time + 30000; + } + + dropped->flags = FL_DROPPED_ITEM; + + trap_LinkEntity (dropped); + + return dropped; +} + +/* +================ +Drop_Item + +Spawns an item and tosses it forward +================ +*/ +gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle ) { + vec3_t velocity; + vec3_t angles; + + VectorCopy( ent->s.apos.trBase, angles ); + angles[YAW] += angle; + angles[PITCH] = 0; // always forward + + AngleVectors( angles, velocity, NULL, NULL ); + VectorScale( velocity, 150, velocity ); + velocity[2] += 200 + crandom() * 50; + + return LaunchItem( item, ent->s.pos.trBase, velocity ); +} + + +/* +================ +Use_Item + +Respawn the item +================ +*/ +void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + RespawnItem( ent ); +} + +//====================================================================== + +/* +================ +FinishSpawningItem + +Traces down to find where an item should rest, instead of letting them +free fall from their spawn points +================ +*/ +void FinishSpawningItem( gentity_t *ent ) { + trace_t tr; + vec3_t dest; + + VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS ); + VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS ); + + ent->s.eType = ET_ITEM; + ent->s.modelindex = ent->item - bg_itemlist; // store item number in modelindex + ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item + + ent->r.contents = CONTENTS_TRIGGER; + ent->touch = Touch_Item; + // useing an item causes it to respawn + ent->use = Use_Item; + + if ( ent->spawnflags & 1 ) { + // suspended + G_SetOrigin( ent, ent->s.origin ); + } else { + // drop to floor + VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 ); + trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID ); + if ( tr.startsolid ) { + G_Printf ("FinishSpawningItem: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEntity( ent ); + return; + } + + // allow to ride movers + ent->s.groundEntityNum = tr.entityNum; + + G_SetOrigin( ent, tr.endpos ); + } + + // team slaves and targeted items aren't present at start + if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) { + ent->s.eFlags |= EF_NODRAW; + ent->r.contents = 0; + return; + } + + // powerups don't spawn in for a while + if ( ent->item->giType == IT_POWERUP ) { + float respawn; + + respawn = 45 + crandom() * 15; + ent->s.eFlags |= EF_NODRAW; + ent->r.contents = 0; + ent->nextthink = level.time + respawn * 1000; + ent->think = RespawnItem; + return; + } + + + trap_LinkEntity (ent); +} + + +qboolean itemRegistered[MAX_ITEMS]; + +/* +================== +G_CheckTeamItems +================== +*/ +void G_CheckTeamItems( void ) { + + // Set up team stuff + Team_InitGame(); + + if( g_gametype.integer == GT_CTF ) { + gitem_t *item; + + // check for the two flags + item = BG_FindItem( "Red Flag" ); + if ( !item || !itemRegistered[ item - bg_itemlist ] ) { + G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_redflag in map" ); + } + item = BG_FindItem( "Blue Flag" ); + if ( !item || !itemRegistered[ item - bg_itemlist ] ) { + G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_blueflag in map" ); + } + } +#ifdef MISSIONPACK + if( g_gametype.integer == GT_1FCTF ) { + gitem_t *item; + + // check for all three flags + item = BG_FindItem( "Red Flag" ); + if ( !item || !itemRegistered[ item - bg_itemlist ] ) { + G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_redflag in map" ); + } + item = BG_FindItem( "Blue Flag" ); + if ( !item || !itemRegistered[ item - bg_itemlist ] ) { + G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_blueflag in map" ); + } + item = BG_FindItem( "Neutral Flag" ); + if ( !item || !itemRegistered[ item - bg_itemlist ] ) { + G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_neutralflag in map" ); + } + } + + if( g_gametype.integer == GT_OBELISK ) { + gentity_t *ent; + + // check for the two obelisks + ent = NULL; + ent = G_Find( ent, FOFS(classname), "team_redobelisk" ); + if( !ent ) { + G_Printf( S_COLOR_YELLOW "WARNING: No team_redobelisk in map" ); + } + + ent = NULL; + ent = G_Find( ent, FOFS(classname), "team_blueobelisk" ); + if( !ent ) { + G_Printf( S_COLOR_YELLOW "WARNING: No team_blueobelisk in map" ); + } + } + + if( g_gametype.integer == GT_HARVESTER ) { + gentity_t *ent; + + // check for all three obelisks + ent = NULL; + ent = G_Find( ent, FOFS(classname), "team_redobelisk" ); + if( !ent ) { + G_Printf( S_COLOR_YELLOW "WARNING: No team_redobelisk in map" ); + } + + ent = NULL; + ent = G_Find( ent, FOFS(classname), "team_blueobelisk" ); + if( !ent ) { + G_Printf( S_COLOR_YELLOW "WARNING: No team_blueobelisk in map" ); + } + + ent = NULL; + ent = G_Find( ent, FOFS(classname), "team_neutralobelisk" ); + if( !ent ) { + G_Printf( S_COLOR_YELLOW "WARNING: No team_neutralobelisk in map" ); + } + } +#endif +} + +/* +============== +ClearRegisteredItems +============== +*/ +void ClearRegisteredItems( void ) { + memset( itemRegistered, 0, sizeof( itemRegistered ) ); + + // players always start with the base weapon + RegisterItem( BG_FindItemForWeapon( WP_MACHINEGUN ) ); + RegisterItem( BG_FindItemForWeapon( WP_GAUNTLET ) ); +#ifdef MISSIONPACK + if( g_gametype.integer == GT_HARVESTER ) { + RegisterItem( BG_FindItem( "Red Cube" ) ); + RegisterItem( BG_FindItem( "Blue Cube" ) ); + } +#endif +} + +/* +=============== +RegisterItem + +The item will be added to the precache list +=============== +*/ +void RegisterItem( gitem_t *item ) { + if ( !item ) { + G_Error( "RegisterItem: NULL" ); + } + itemRegistered[ item - bg_itemlist ] = qtrue; +} + + +/* +=============== +SaveRegisteredItems + +Write the needed items to a config string +so the client will know which ones to precache +=============== +*/ +void SaveRegisteredItems( void ) { + char string[MAX_ITEMS+1]; + int i; + int count; + + count = 0; + for ( i = 0 ; i < bg_numItems ; i++ ) { + if ( itemRegistered[i] ) { + count++; + string[i] = '1'; + } else { + string[i] = '0'; + } + } + string[ bg_numItems ] = 0; + + G_Printf( "%i items registered\n", count ); + trap_SetConfigstring(CS_ITEMS, string); +} + +/* +============ +G_ItemDisabled +============ +*/ +int G_ItemDisabled( gitem_t *item ) { + + char name[128]; + + Com_sprintf(name, sizeof(name), "disable_%s", item->classname); + return trap_Cvar_VariableIntegerValue( name ); +} + +/* +============ +G_SpawnItem + +Sets the clipping size and plants the object on the floor. + +Items can't be immediately dropped to floor, because they might +be on an entity that hasn't spawned yet. +============ +*/ +void G_SpawnItem (gentity_t *ent, gitem_t *item) { + G_SpawnFloat( "random", "0", &ent->random ); + G_SpawnFloat( "wait", "0", &ent->wait ); + + RegisterItem( item ); + if ( G_ItemDisabled(item) ) + return; + + ent->item = item; + // some movers spawn on the second frame, so delay item + // spawns until the third frame so they can ride trains + ent->nextthink = level.time + FRAMETIME * 2; + ent->think = FinishSpawningItem; + + ent->physicsBounce = 0.50; // items are bouncy + + if ( item->giType == IT_POWERUP ) { + G_SoundIndex( "sound/items/poweruprespawn.wav" ); + G_SpawnFloat( "noglobalsound", "0", &ent->speed); + } + +#ifdef MISSIONPACK + if ( item->giType == IT_PERSISTANT_POWERUP ) { + ent->s.generic1 = ent->spawnflags; + } +#endif +} + + +/* +================ +G_BounceItem + +================ +*/ +void G_BounceItem( gentity_t *ent, trace_t *trace ) { + vec3_t velocity; + float dot; + int hitTime; + + // reflect the velocity on the trace plane + hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction; + BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity ); + dot = DotProduct( velocity, trace->plane.normal ); + VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta ); + + // cut the velocity to keep from bouncing forever + VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta ); + + // check for stop + if ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 ) { + trace->endpos[2] += 1.0; // make sure it is off ground + SnapVector( trace->endpos ); + G_SetOrigin( ent, trace->endpos ); + ent->s.groundEntityNum = trace->entityNum; + return; + } + + VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin); + VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); + ent->s.pos.trTime = level.time; +} + + +/* +================ +G_RunItem + +================ +*/ +void G_RunItem( gentity_t *ent ) { + vec3_t origin; + trace_t tr; + int contents; + int mask; + + // if groundentity has been set to -1, it may have been pushed off an edge + if ( ent->s.groundEntityNum == -1 ) { + if ( ent->s.pos.trType != TR_GRAVITY ) { + ent->s.pos.trType = TR_GRAVITY; + ent->s.pos.trTime = level.time; + } + } + + if ( ent->s.pos.trType == TR_STATIONARY ) { + // check think function + G_RunThink( ent ); + return; + } + + // get current position + BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); + + // trace a line from the previous position to the current position + if ( ent->clipmask ) { + mask = ent->clipmask; + } else { + mask = MASK_PLAYERSOLID & ~CONTENTS_BODY;//MASK_SOLID; + } + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, + ent->r.ownerNum, mask ); + + VectorCopy( tr.endpos, ent->r.currentOrigin ); + + if ( tr.startsolid ) { + tr.fraction = 0; + } + + trap_LinkEntity( ent ); // FIXME: avoid this for stationary? + + // check think function + G_RunThink( ent ); + + if ( tr.fraction == 1 ) { + return; + } + + // if it is in a nodrop volume, remove it + contents = trap_PointContents( ent->r.currentOrigin, -1 ); + if ( contents & CONTENTS_NODROP ) { + if (ent->item && ent->item->giType == IT_TEAM) { + Team_FreeEntity(ent); + } else { + G_FreeEntity( ent ); + } + return; + } + + G_BounceItem( ent, &tr ); +} + -- cgit v1.2.3