977 lines
24 KiB
C
977 lines
24 KiB
C
// Copyright (C) 1999-2000 Id Software, Inc.
|
|
//
|
|
#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 20
|
|
#define RESPAWN_TEAM_WEAPON 30
|
|
#define RESPAWN_HEALTH 30
|
|
#define RESPAWN_AMMO 40
|
|
#define RESPAWN_HOLDABLE 60
|
|
#define RESPAWN_MEGAHEALTH 120
|
|
#define RESPAWN_POWERUP 120
|
|
|
|
|
|
// For more than four players, adjust the respawn times, up to 1/4.
|
|
int adjustRespawnTime(float respawnTime)
|
|
{
|
|
if (!g_adaptRespawn.integer)
|
|
{
|
|
return((int)respawnTime);
|
|
}
|
|
|
|
if (level.numPlayingClients > 4)
|
|
{ // Start scaling the respawn times.
|
|
if (level.numPlayingClients > 32)
|
|
{ // 1/4 time minimum.
|
|
respawnTime *= 0.25;
|
|
}
|
|
else if (level.numPlayingClients > 12)
|
|
{ // From 12-32, scale from 0.5 to 0.25;
|
|
respawnTime *= 20.0 / (float)(level.numPlayingClients + 8);
|
|
}
|
|
else
|
|
{ // From 4-12, scale from 1.0 to 0.5;
|
|
respawnTime *= 8.0 / (float)(level.numPlayingClients + 4);
|
|
}
|
|
}
|
|
|
|
if (respawnTime < 1.0)
|
|
{ // No matter what, don't go lower than 1 second, or the pickups become very noisy!
|
|
respawnTime = 1.0;
|
|
}
|
|
|
|
return ((int)respawnTime);
|
|
}
|
|
|
|
|
|
//======================================================================
|
|
|
|
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 );
|
|
|
|
// kef -- log the fact that we picked up this powerup
|
|
G_LogWeaponPowerup(other->s.number, ent->item->giTag);
|
|
}
|
|
|
|
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_REWARD_COUNT]++;
|
|
client->ps.persistant[PERS_REWARD] = REWARD_DENIED;
|
|
}
|
|
|
|
return RESPAWN_POWERUP;
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
int Pickup_Holdable( gentity_t *ent, gentity_t *other )
|
|
{
|
|
int nItem = ent->item - bg_itemlist;
|
|
|
|
other->client->ps.stats[STAT_HOLDABLE_ITEM] = nItem;
|
|
|
|
// if we just picked up the detpack, indicate that it has not been placed yet
|
|
if (HI_DETPACK == bg_itemlist[nItem].giTag)
|
|
{
|
|
other->client->ps.stats[STAT_USEABLE_PLACED] = 0;
|
|
}
|
|
// kef -- log the fact that we picked up this item
|
|
G_LogWeaponItem(other->s.number, bg_itemlist[nItem].giTag);
|
|
|
|
return adjustRespawnTime(RESPAWN_HOLDABLE);
|
|
}
|
|
|
|
|
|
//======================================================================
|
|
|
|
void Add_Ammo (gentity_t *ent, int weapon, int count)
|
|
{
|
|
ent->client->ps.ammo[weapon] += count;
|
|
if ( ent->client->ps.ammo[weapon] > Max_Ammo[weapon] ) {
|
|
ent->client->ps.ammo[weapon] = Max_Ammo[weapon];
|
|
}
|
|
}
|
|
|
|
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 adjustRespawnTime(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
|
|
|
|
// New method: If the player has less than half the minimum, give them the minimum, else add 1/2 the min.
|
|
|
|
// drop the quantity if the already have over the minimum
|
|
if ( other->client->ps.ammo[ ent->item->giTag ] < quantity*0.5 ) {
|
|
quantity = quantity - other->client->ps.ammo[ ent->item->giTag ];
|
|
} else {
|
|
quantity = quantity*0.5; // only add half the value.
|
|
}
|
|
|
|
// Old method: If the player has less than the minimum, give them the minimum, else just add 1.
|
|
/*
|
|
// 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 );
|
|
|
|
G_LogWeaponPickup(other->s.number, ent->item->giTag);
|
|
|
|
// team deathmatch has slow weapon respawns
|
|
if ( g_gametype.integer == GT_TEAM )
|
|
{
|
|
return adjustRespawnTime(RESPAWN_TEAM_WEAPON);
|
|
}
|
|
|
|
return adjustRespawnTime(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
|
|
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->giTag == 100 ) { // mega health respawns slow
|
|
return RESPAWN_MEGAHEALTH; // It also does not adapt like other health pickups.
|
|
}
|
|
|
|
return adjustRespawnTime(RESPAWN_HEALTH);
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
int Pickup_Armor( gentity_t *ent, gentity_t *other ) {
|
|
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;
|
|
}
|
|
|
|
return adjustRespawnTime(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 | EF_ITEMPLACEHOLDER);
|
|
ent->r.svFlags &= ~SVF_NOCLIENT;
|
|
trap_LinkEntity (ent);
|
|
|
|
if ( ent->item->giType == IT_POWERUP ) {
|
|
// play powerup spawn sound to all clients
|
|
gentity_t *te;
|
|
|
|
te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND );
|
|
te->s.eventParm = G_SoundIndex( "sound/items/poweruprespawn.wav" );//cgs.media.poweruprespawn
|
|
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;
|
|
|
|
if (!other->client)
|
|
return;
|
|
if (other->health < 1)
|
|
return; // dead people can't pickup
|
|
|
|
// If ghosted, then end the ghost-ness in favor of the pickup.
|
|
if (other->client->ps.powerups[PW_GHOST] >= level.time)
|
|
{
|
|
other->client->ps.powerups[PW_GHOST] = 0; // Unghost the player. This
|
|
}
|
|
|
|
// the same pickup rules are used for client side and server side
|
|
if ( !BG_CanItemBeGrabbed( &ent->s, &other->client->ps ) ) {
|
|
return;
|
|
}
|
|
|
|
G_LogPrintf( "Item: %i %s\n", other->s.number, ent->item->classname );
|
|
|
|
// call the item-specific pickup function
|
|
switch( ent->item->giType ) {
|
|
case IT_WEAPON:
|
|
respawn = Pickup_Weapon(ent, other);
|
|
break;
|
|
case IT_AMMO:
|
|
respawn = Pickup_Ammo(ent, other);
|
|
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);
|
|
break;
|
|
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 ( other->client->pers.predictItemPickup ) {
|
|
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) {
|
|
gentity_t *te;
|
|
|
|
te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP );
|
|
te->s.eventParm = ent->s.modelindex;
|
|
// tell us which client fired off this global sound
|
|
te->s.otherEntityNum = other->s.number;
|
|
te->r.svFlags |= SVF_BROADCAST;
|
|
}
|
|
|
|
// 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.
|
|
if (ent->item->giType==IT_WEAPON || ent->item->giType==IT_POWERUP)
|
|
{
|
|
ent->s.eFlags |= EF_ITEMPLACEHOLDER;
|
|
}
|
|
else
|
|
{
|
|
ent->s.eFlags |= EF_NODRAW;
|
|
}
|
|
|
|
|
|
|
|
// this line used to prevent items that were picked up from being drawn, but we now want to draw the techy grid thing instead
|
|
// ent->r.svFlags |= SVF_NOCLIENT;
|
|
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;
|
|
dropped->s.eFlags |= EF_DEAD; // Yes, this is totally lame, but we use it bg_misc to check
|
|
// if the item has been droped, and if so, make it pick-up-able
|
|
// cdr
|
|
|
|
if (item->giType == IT_TEAM) { // Special case for CTF flags
|
|
gentity_t *te;
|
|
|
|
dropped->think = Team_DroppedFlagThink;
|
|
dropped->nextthink = level.time + 30000;
|
|
Team_CheckDroppedItem( dropped );
|
|
|
|
// make the sound call for a dropped flag
|
|
te = G_TempEntity( dropped->s.pos.trBase, EV_TEAM_SOUND );
|
|
te->s.eventParm = DROPPED_FLAG_SOUND;
|
|
if (dropped->item->giTag == PW_REDFLAG)
|
|
{
|
|
te->s.otherEntityNum = TEAM_RED;
|
|
}
|
|
else
|
|
{
|
|
te->s.otherEntityNum = TEAM_BLUE;
|
|
}
|
|
|
|
te->r.svFlags |= SVF_BROADCAST;
|
|
|
|
} 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->item->giType == IT_TEAM )
|
|
{
|
|
VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, 0 );
|
|
}
|
|
|
|
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: removing %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
|
|
#ifndef FINAL_BUILD
|
|
G_Error("FinishSpawningItem: removing %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
|
|
#endif
|
|
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;
|
|
|
|
#ifdef _DEBUG
|
|
respawn = 1; // This makes powerups spawn immediately in debug.
|
|
#else // _DEBUG
|
|
respawn = 45 + crandom() * 15;
|
|
#endif // _DEBUG
|
|
|
|
ent->s.eFlags |= EF_NODRAW;
|
|
ent->r.contents = 0;
|
|
ent->nextthink = level.time + respawn * 1000;
|
|
ent->think = RespawnItem;
|
|
return;
|
|
}
|
|
|
|
|
|
trap_LinkEntity (ent);
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
FinishSpawningDetpack
|
|
|
|
Traces down to find where an item should rest, instead of letting them
|
|
free fall from their spawn points
|
|
================
|
|
*/
|
|
extern void detpack_shot( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath );
|
|
|
|
void FinishSpawningDetpack( gentity_t *ent, int itemIndex )
|
|
{
|
|
trace_t tr;
|
|
vec3_t dest;
|
|
|
|
VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, 0 );
|
|
VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );
|
|
|
|
ent->s.eType = ET_USEABLE;
|
|
ent->s.modelindex = bg_itemlist[itemIndex].giTag; // this'll be used in CG_Useable()
|
|
ent->s.modelindex2 = itemIndex; // store item number in modelindex
|
|
|
|
ent->classname = bg_itemlist[itemIndex].classname;
|
|
ent->r.contents = CONTENTS_CORPSE;//CONTENTS_TRIGGER;
|
|
ent->takedamage = 1;
|
|
ent->health = 5;
|
|
ent->touch = 0;
|
|
ent->die = detpack_shot;
|
|
|
|
// useing an item causes it to respawn
|
|
ent->use = Use_Item;
|
|
|
|
// 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_FreeEntity( ent );
|
|
return;
|
|
}
|
|
|
|
// allow to ride movers
|
|
ent->s.groundEntityNum = tr.entityNum;
|
|
|
|
G_SetOrigin( ent, tr.endpos );
|
|
|
|
ent->s.eFlags &= ~EF_NODRAW;
|
|
ent->r.svFlags &= ~SVF_NOCLIENT;
|
|
|
|
trap_LinkEntity (ent);
|
|
|
|
ent->noise_index = G_SoundIndex("sound/weapons/detpacklatch.wav");
|
|
G_AddEvent( ent, EV_GENERAL_SOUND, ent->noise_index );
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
FinishSpawningDecoy
|
|
|
|
Traces down to find where an item should rest, instead of letting them
|
|
free fall from their spawn points
|
|
================
|
|
*/
|
|
void FinishSpawningDecoy( gentity_t *ent, int itemIndex )
|
|
{
|
|
trace_t tr;
|
|
vec3_t dest;
|
|
|
|
// OLD RADIUS SETTINGS
|
|
// VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS );
|
|
// VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );
|
|
// ent->s.eType = ET_USEABLE;
|
|
// ent->s.modelindex = bg_itemlist[itemIndex].giTag; // this'll be used in CG_Useable()
|
|
|
|
ent->classname = bg_itemlist[itemIndex].classname;
|
|
// ent->r.contents = CONTENTS_CORPSE;
|
|
ent->touch = 0; // null touch function pointer
|
|
// useing an item causes it to respawn
|
|
ent->use = Use_Item;
|
|
|
|
// 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 )
|
|
{ // If stuck in a solid, give up and go home
|
|
G_FreeEntity( ent );
|
|
return;
|
|
}
|
|
|
|
// allow to ride movers
|
|
ent->s.groundEntityNum = tr.entityNum;
|
|
|
|
G_SetOrigin( ent, tr.endpos );
|
|
|
|
// Turn off the NODRAW and NOCLIENT flags
|
|
ent->s.eFlags &= ~EF_NODRAW;
|
|
ent->r.svFlags &= ~SVF_NOCLIENT;
|
|
|
|
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;
|
|
|
|
// make sure we actually have two flags...
|
|
item = BG_FindItem( "Red Flag" );
|
|
if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
|
|
G_Printf( "^1WARNING: No team_CTF_redflag in map" );
|
|
}
|
|
item = BG_FindItem( "Blue Flag" );
|
|
if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
|
|
G_Printf( "^1WARNING: No team_CTF_blueflag in map" );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
ClearRegisteredItems
|
|
==============
|
|
*/
|
|
void ClearRegisteredItems( void ) {
|
|
memset( itemRegistered, 0, sizeof( itemRegistered ) );
|
|
|
|
// players always start with the base weapon
|
|
RegisterItem( BG_FindItemForWeapon( WP_PHASER ) );
|
|
RegisterItem( BG_FindItemForWeapon( WP_COMPRESSION_RIFLE ) ); //this is for the podium at the end, make sure we have the model
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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_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 );
|
|
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" );//cgs.media.poweruprespawn
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
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 );
|
|
}
|
|
|