stvoy-sp-sdk/game/g_items.cpp

701 lines
16 KiB
C++

#include "g_local.h"
#include "g_functions.h"
#include "g_infostrings.h"
#include "g_items.h"
extern void CrystalAmmoSettings(gentity_t *ent);
extern cvar_t *g_spskill;
/*
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.
*/
// Item Spawn flags
#define ITMSF_SUSPEND 1
#define ITMSF_TEAM 2
#define ITMSF_MONSTER 4
#define ITMSF_NOTSOLID 8
#define ITMSF_VERTICAL 16
#define ITMSF_INVISIBLE 32
//======================================================================
int Pickup_Holdable( gentity_t *ent, gentity_t *other ) {
other->client->ps.stats[STAT_HOLDABLE_ITEM] = ent->item - bg_itemlist;
return 60;
}
//======================================================================
int Add_Ammo2 (gentity_t *ent, int ammoType, int count)
{
ent->client->ps.ammo[ammoType] += count;
if ( ent->client->ps.ammo[ammoType] > ammoData[ammoType].max )
{
ent->client->ps.ammo[ammoType] = ammoData[ammoType].max;
return qfalse;
}
return qtrue;
}
void Add_Ammo (gentity_t *ent, int weapon, int count)
{
Add_Ammo2(ent,weaponData[weapon].ammoIndex,count);
}
int Pickup_Ammo (gentity_t *ent, gentity_t *other)
{
int quantity;
if ( ent->count ) {
quantity = ent->count;
} else {
quantity = ent->item->quantity;
}
Add_Ammo2 (other, ent->item->giTag, quantity);
return 30;
}
//======================================================================
int Pickup_Weapon (gentity_t *ent, gentity_t *other) {
int quantity;
if (ent->count)
{
quantity = ent->count;
}
else
{
quantity = ent->item->quantity;
}
// The Scav rifle is a special case
if ( ent->item->giTag == WP_SCAVENGER_RIFLE )
{
switch (g_spskill->integer)
{
case 0: //easy
quantity = 40;
break;
case 1: //medium
quantity = 30;
break;
default:
case 2: //hard
quantity = 20;
break;
}
}
// dropped items are always picked up
if (!quantity)
{
if ( ! (ent->flags & FL_DROPPED_ITEM ) ) {
quantity = 50;
}
}
// add the weapon
other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag );
// Give ammo
Add_Ammo( other, ent->item->giTag, quantity );
return 5;
}
//======================================================================
int ITM_AddHealth (gentity_t *ent, int count)
{
ent->health += count;
if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH]) // Past max health
{
ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
return qfalse;
}
return qtrue;
}
int Pickup_Health (gentity_t *ent, gentity_t *other) {
int max;
int quantity;
max = other->client->ps.stats[STAT_MAX_HEALTH];
if ( ent->count ) {
quantity = ent->count;
} else {
quantity = ent->item->quantity;
}
other->health += quantity;
if (other->health > max ) {
other->health = max;
}
if ( ent->item->giTag == 100 ) { // mega health respawns slow
return 120;
}
return 30;
}
//======================================================================
int ITM_AddArmor (gentity_t *ent, int count)
{
ent->client->ps.stats[STAT_ARMOR] += count;
if (ent->client->ps.stats[STAT_ARMOR] > ent->client->ps.stats[STAT_MAX_HEALTH])
{
ent->client->ps.stats[STAT_ARMOR] = ent->client->ps.stats[STAT_MAX_HEALTH];
return qfalse;
}
return qtrue;
}
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] ) {
other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH];
}
return 30;
}
//======================================================================
/*
===============
RespawnItem
===============
*/
void RespawnItem( gentity_t *ent ) {
}
/*
===============
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
// Only monsters can pick it up
if ((ent->spawnflags & ITMSF_MONSTER) && (other->client->playerTeam == TEAM_STARFLEET))
{
return;
}
// Only star fleet can pick it up
if ((ent->spawnflags & ITMSF_TEAM) && (other->client->playerTeam != TEAM_STARFLEET))
{
return;
}
// Only player can pick it up
if (!(ent->spawnflags & ITMSF_TEAM) && !(ent->spawnflags & ITMSF_MONSTER))
{
if (!other->s.number == 0) // Not the player?
{
return;
}
}
// the same pickup rules are used for client side and server side
if ( !BG_CanItemBeGrabbed( &ent->s, &other->client->ps ) ) {
return;
}
if (!ent->item) { //not an item!
gi.Printf( "Touch_Item: %s is not an item!\n", ent->classname);
return;
}
// 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_HOLDABLE:
respawn = Pickup_Holdable(ent, other);
break;
default:
return;
}
if ( !respawn )
{
return;
}
// play the normal pickup sound
G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex );
// fire item targets
G_UseTargets (ent, other);
// wait of -1 will not respawn
// if ( ent->wait == -1 )
{
ent->svFlags |= SVF_NOCLIENT;
ent->s.eFlags |= EF_NODRAW;
ent->contents = 0;
ent->unlinkAfterEvent = qtrue;
return;
}
}
//======================================================================
/*
================
LaunchItem
Spawns an item and tosses it forward
================
*/
gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity, char *target ) {
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->mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS);
VectorSet (dropped->maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS);
dropped->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;//CONTENTS_TRIGGER;//not CONTENTS_BODY for dropped items, don't need to ID them
if ( target && target[0] )
{
dropped->target = G_NewString( target );
}
else
{
// if not targeting something, auto-remove after 30 seconds
dropped->e_ThinkFunc = thinkF_G_FreeEntity;
dropped->nextthink = level.time + 30000;
}
dropped->e_TouchFunc = touchF_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->flags = FL_DROPPED_ITEM;
gi.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, qboolean copytarget ) {
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;
if ( copytarget )
{
return LaunchItem( item, ent->s.pos.trBase, velocity, ent->opentarget );
}
else
{
return LaunchItem( item, ent->s.pos.trBase, velocity, NULL );
}
}
/*
================
Use_Item
Respawn the item
================
*/
void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator )
{
if ( ent->spawnflags & 32 ) // invisible
{
// If it was invisible, first use makes it visible....
ent->s.eFlags &= ~EF_NODRAW;
ent->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;
ent->spawnflags &= ~32;
return;
}
if (ent->behaviorSet[BSET_USE])
{
G_ActivateBehavior(ent,BSET_USE);
}
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;
gitem_t *item;
int itemNum;
itemNum=1;
for ( item = bg_itemlist + 1 ; item->classname ; item++,itemNum++)
{
if (!strcmp(item->classname,ent->classname))
{
break;
}
}
// Set bounding box for item
VectorSet( ent->mins, item->mins[0],item->mins[1] ,item->mins[2]);
VectorSet( ent->maxs, item->maxs[0],item->maxs[1] ,item->maxs[2]);
if ((!ent->mins[0] && !ent->mins[1] && !ent->mins[2]) &&
(!ent->maxs[0] && !ent->maxs[1] && !ent->maxs[2]))
{
VectorSet (ent->mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS);
VectorSet (ent->maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS);
}
if ((item->quantity) && (item->giType == IT_AMMO))
{
ent->count = item->quantity;
}
// Set crystal ammo amount based on skill level
if ((itemNum == ITM_AMMO_CRYSTAL_BORG) ||
(itemNum == ITM_AMMO_CRYSTAL_DN) ||
(itemNum == ITM_AMMO_CRYSTAL_FORGE) ||
(itemNum == ITM_AMMO_CRYSTAL_SCAVENGER) ||
(itemNum == ITM_AMMO_CRYSTAL_STASIS))
{
CrystalAmmoSettings(ent);
}
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->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;//CONTENTS_BODY;//CONTENTS_TRIGGER|
ent->e_TouchFunc = touchF_Touch_Item;
// useing an item causes it to respawn
ent->e_UseFunc = useF_Use_Item;
// Hang in air?
if ( ent->spawnflags & ITMSF_SUSPEND)
{
// 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 );
gi.trace( &tr, ent->s.origin, ent->mins, ent->maxs, dest, ent->s.number, MASK_SOLID );
if ( tr.startsolid )
{
gi.Printf (S_COLOR_RED"FinishSpawningItem: removing %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
assert( 0 && "item starting in solid");
#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 );
}
/* ? don't need this
// team slaves and targeted items aren't present at start
if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) {
ent->s.eFlags |= EF_NODRAW;
ent->contents = 0;
return;
}
*/
if ( ent->spawnflags & ITMSF_INVISIBLE ) // invisible
{
ent->s.eFlags |= EF_NODRAW;
ent->contents = 0;
}
if ( ent->spawnflags & ITMSF_NOTSOLID ) // not solid
{
ent->contents = 0;
}
gi.linkentity (ent);
}
qboolean itemRegistered[MAX_ITEMS];
/*
==============
ClearRegisteredItems
==============
*/
void ClearRegisteredItems( void ) {
memset( itemRegistered, 0, sizeof( itemRegistered ) );
// players always start with the base weapon
RegisterItem( FindItemForWeapon( WP_PHASER ) ); //these are given in g_client, ClientSpawn()
RegisterItem( FindItemForWeapon( WP_COMPRESSION_RIFLE ) );
extern void Player_CacheFromPrevLevel(void);//g_client.cpp
Player_CacheFromPrevLevel(); //reads from transition carry-over;
}
/*
===============
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;
gi.Printf( "%i items registered\n", count );
gi.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->e_ThinkFunc = thinkF_FinishSpawningItem;
ent->physicsBounce = 0.50; // items are bouncy
// Set a default infoString text color
VectorSet( ent->startRGBA, 0.9f, 0.7f, 0.0f ); // yellow
ent->infoString = item->pickup_name;
}
/*
================
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;
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 ) {
G_SetOrigin( ent, trace->endpos );
ent->s.groundEntityNum = trace->entityNum;
return;
}
VectorAdd( ent->currentOrigin, trace->plane.normal, ent->currentOrigin);
VectorCopy( ent->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
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_SOLID;
}
gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin,
ent->owner ? ent->owner->s.number : ENTITYNUM_NONE, mask );
VectorCopy( tr.endpos, ent->currentOrigin );
if ( tr.startsolid ) {
tr.fraction = 0;
}
gi.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 = gi.pointcontents( ent->currentOrigin, -1 );
if ( contents & CONTENTS_NODROP )
{
G_FreeEntity( ent );
return;
}
G_BounceItem( ent, &tr );
}