stvoy-sp-sdk/game/g_misc.cpp

1683 lines
44 KiB
C++
Raw Normal View History

2002-11-22 00:00:00 +00:00
// g_misc.c
#include "g_local.h"
#include "g_functions.h"
#include "g_nav.h"
#include "g_items.h"
#include "g_infostrings.h"
extern ginfoitem_t bg_infoItemList[];
extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
extern void G_SoundOnEnt (gentity_t *ent, soundChannel_t channel, const char *soundPath);
/*QUAKED func_group (0 0 0) ?
Used to group brushes together just for editor convenience. They are turned into normal brushes by the utilities.
*/
/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
*/
void SP_info_null( gentity_t *self ) {
G_SetOrigin( self, self->s.origin );
self->e_ThinkFunc = thinkF_G_FreeEntity;
//Give other ents time to link
self->nextthink = level.time + 1000;
}
/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
Used as a positional target for in-game calculation, like jumppad targets.
target_position does the same thing
*/
void SP_info_notnull( gentity_t *self ){
G_SetOrigin( self, self->s.origin );
}
/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) linear noIncidence
Non-displayed light.
"light" overrides the default 300 intensity. - affects size
a negative "light" will subtract the light's color
'Linear' checkbox gives linear falloff instead of inverse square
'noIncidence' checkbox makes lighting smoother
Lights pointed at a target will be spotlights.
"radius" overrides the default 64 unit radius of a spotlight at the target point.
"scale" multiplier for the light intensity - does not affect size
"color" sets the light's color
*/
void SP_light( gentity_t *self ) {
G_FreeEntity( self );
}
/*
=================================================================================
TELEPORTERS
=================================================================================
*/
void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles, int teleport_fx ) {
gentity_t *tent;
// use temp events at source and destination to prevent the effect
// from getting dropped by a second player event
switch ( teleport_fx )
{
case STASIS_TELEPORT_FX:
tent = G_TempEntity( player->client->ps.origin, EV_STASIS_TELEPORT_OUT );
tent->s.clientNum = player->s.clientNum;
tent = G_TempEntity( origin, EV_STASIS_TELEPORT_IN );
tent->s.clientNum = player->s.clientNum;
break;
case STARFLEET_TELEPORT_FX:
default:
tent = G_TempEntity( player->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
tent->s.clientNum = player->s.clientNum;
tent = G_TempEntity( origin, EV_PLAYER_TELEPORT_IN );
tent->s.clientNum = player->s.clientNum;
}
if ( player->NPC )
{
player->NPC->sPCurSegPoint1 = player->NPC->sPCurSegPoint2 = -1;
}
if ( player->NPC && ( player->NPC->aiFlags&NPCAI_FORM_TELE_NAV ) )
{
//My leader teleported, I was trying to catch up, take this off
VectorClear( player->NPC->leaderTeleportSpot );
player->NPC->aiFlags &= ~NPCAI_FORM_TELE_NAV;
}
else
{ //Need to inform my followers I teleported
gentity_t *follower = player->client->follower;
while ( follower && follower->client && follower->NPC && follower->client->team_leader == player && follower->NPC->behaviorState == BS_FORMATION )
{
if ( !(follower->NPC->aiFlags&NPCAI_FORM_TELE_NAV) )
{
//Try to follow them for 10 seconds, then see if they're close, if not, try for another 10 seconds
follower->NPC->navTime = level.time + 10000;
follower->NPC->aiFlags |= NPCAI_FORM_TELE_NAV;
follower->NPC->tempGoal->lastWaypoint = follower->NPC->tempGoal->waypoint = follower->NPC->tempGoal->lastValidWaypoint = WAYPOINT_NONE;
VectorCopy( player->currentOrigin, follower->NPC->leaderTeleportSpot );
}
if ( follower->client )
{
follower = follower->client->follower;
}
else
{
follower = NULL;
}
}
}
// unlink to make sure it can't possibly interfere with G_KillBox
gi.unlinkentity (player);
VectorCopy ( origin, player->client->ps.origin );
player->client->ps.origin[2] += 1;
VectorCopy ( player->client->ps.origin, player->currentOrigin );
// spit the player out
AngleVectors( angles, player->client->ps.velocity, NULL, NULL );
VectorScale( player->client->ps.velocity, 0, player->client->ps.velocity );
//player->client->ps.pm_time = 160; // hold time
//player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
// toggle the teleport bit so the client knows to not lerp
player->client->ps.eFlags ^= EF_TELEPORT_BIT;
// set angles
SetClientViewAngle( player, angles );
// kill anything at the destination
G_KillBox (player);
// save results of pmove
PlayerStateToEntityState( &player->client->ps, &player->s );
gi.linkentity (player);
}
void TeleportMover( gentity_t *mover, vec3_t origin, vec3_t diffAngles, qboolean snapAngle )
{//FIXME: need an effect
vec3_t oldAngle, newAngle;
float speed;
// unlink to make sure it can't possibly interfere with G_KillBox
gi.unlinkentity (mover);
//reposition it
VectorCopy( origin, mover->s.pos.trBase );
VectorCopy( origin, mover->currentOrigin );
//Maintain their previous speed, but adjusted for new direction
if ( snapAngle )
{//not a diffAngle, actually an absolute angle
vec3_t dir;
VectorCopy( diffAngles, newAngle );
AngleVectors( newAngle, dir, NULL, NULL );
VectorNormalize( dir );//necessary?
speed = VectorLength( mover->s.pos.trDelta );
VectorScale( dir, speed, mover->s.pos.trDelta );
mover->s.pos.trTime = level.time;
VectorSubtract( newAngle, mover->s.apos.trBase, diffAngles );
VectorCopy( newAngle, mover->s.apos.trBase );
}
else
{
speed = VectorNormalize( mover->s.pos.trDelta );
vectoangles( mover->s.pos.trDelta, oldAngle );
VectorAdd( oldAngle, diffAngles, newAngle );
AngleVectors( newAngle, mover->s.pos.trDelta, NULL, NULL );
VectorNormalize( mover->s.pos.trDelta );
VectorScale( mover->s.pos.trDelta, speed, mover->s.pos.trDelta );
mover->s.pos.trTime = level.time;
//Maintain their previous angles, but adjusted to new orientation
VectorAdd( mover->s.apos.trBase, diffAngles, mover->s.apos.trBase );
}
//Maintain their previous anglespeed, but adjusted to new orientation
speed = VectorNormalize( mover->s.apos.trDelta );
VectorAdd( mover->s.apos.trDelta, diffAngles, mover->s.apos.trDelta );
VectorNormalize( mover->s.apos.trDelta );
VectorScale( mover->s.apos.trDelta, speed, mover->s.apos.trDelta );
mover->s.apos.trTime = level.time;
//Tell them it was teleported this move
mover->s.eFlags |= EF_TELEPORT_BIT;
// kill anything at the destination
//G_KillBox (mover);
//FIXME: call touch func instead of killbox?
gi.linkentity (mover);
}
void teleporter_touch (gentity_t *self, gentity_t *other, trace_t *trace)
{
gentity_t *dest;
if (!other->client)
return;
dest = G_PickTarget( self->target );
if (!dest) {
gi.Printf ("Couldn't find teleporter destination\n");
return;
}
TeleportPlayer( other, dest->s.origin, dest->s.angles, STARFLEET_TELEPORT_FX );
}
/*QUAK-D misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16)
Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object.
*/
void SP_misc_teleporter (gentity_t *ent)
{
gentity_t *trig;
if (!ent->target)
{
gi.Printf ("teleporter without a target.\n");
G_FreeEntity( ent );
return;
}
ent->s.modelindex = G_ModelIndex( "models/objects/dmspot.md3" );
ent->s.clientNum = 1;
ent->s.loopSound = G_SoundIndex("sound/world/amb10.wav");
ent->contents = CONTENTS_SOLID;
G_SetOrigin( ent, ent->s.origin );
VectorSet (ent->mins, -32, -32, -24);
VectorSet (ent->maxs, 32, 32, -16);
gi.linkentity (ent);
trig = G_Spawn ();
trig->e_TouchFunc = touchF_teleporter_touch;
trig->contents = CONTENTS_TRIGGER;
trig->target = ent->target;
trig->owner = ent;
G_SetOrigin( trig, ent->s.origin );
VectorSet (trig->mins, -8, -8, 8);
VectorSet (trig->maxs, 8, 8, 24);
gi.linkentity (trig);
}
/*QUAK-D misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) - - NODRAW
Point teleporters at these.
*/
void SP_misc_teleporter_dest( gentity_t *ent ) {
if ( ent->spawnflags & 4 ){
return;
}
G_SetOrigin( ent, ent->s.origin );
gi.linkentity (ent);
}
//===========================================================
/*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16)
"model" arbitrary .md3 or .ase file to display
turns into map triangles - not solid
*/
void SP_misc_model( gentity_t *ent ) {
#if 0
ent->s.modelindex = G_ModelIndex( ent->model );
VectorSet (ent->mins, -16, -16, -16);
VectorSet (ent->maxs, 16, 16, 16);
G_SetOrigin( ent, ent->s.origin );
VectorCopy( ent->s.angles, ent->s.apos.trBase );
gi.linkentity (ent);
#else
G_FreeEntity( ent );
#endif
}
//===========================================================
void setCamera ( gentity_t *ent )
{
vec3_t dir;
gentity_t *target = 0;
// frame holds the rotate speed
if ( ent->owner->spawnflags & 1 )
{
ent->s.frame = 25;
}
else if ( ent->owner->spawnflags & 2 )
{
ent->s.frame = 75;
}
// clientNum holds the rotate offset
ent->s.clientNum = ent->owner->s.clientNum;
VectorCopy( ent->owner->s.origin, ent->s.origin2 );
// see if the portal_camera has a target
if (ent->owner->target) {
target = G_PickTarget( ent->owner->target );
}
if ( target )
{
VectorSubtract( target->s.origin, ent->owner->s.origin, dir );
VectorNormalize( dir );
}
else
{
G_SetMovedir( ent->owner->s.angles, dir );
}
ent->s.eventParm = DirToByte( dir );
}
void cycleCamera( gentity_t *self )
{
self->owner = G_Find( self->owner, FOFS(targetname), self->target );
if ( self->owner == NULL )
{
//Uh oh! Not targeted at any ents! Or reached end of list? Which is it?
//for now assume reached end of list and are cycling
self->owner = G_Find( self->owner, FOFS(targetname), self->target );
if ( self->owner == NULL )
{//still didn't find one
gi.Printf( "Couldn't find target for misc_portal_surface\n" );
G_FreeEntity( self );
return;
}
}
setCamera( self );
if ( self->e_ThinkFunc == thinkF_cycleCamera )
{
if ( self->owner->wait > 0 )
{
self->nextthink = level.time + self->owner->wait;
}
else
{
self->nextthink = level.time + self->wait;
}
}
}
void misc_portal_use( gentity_t *self, gentity_t *other, gentity_t *activator )
{
cycleCamera( self );
}
void locateCamera( gentity_t *ent )
{//FIXME: make this fadeout with distance from misc_camera_portal
ent->owner = G_Find(NULL, FOFS(targetname), ent->target);
if ( !ent->owner )
{
gi.Printf( "Couldn't find target for misc_portal_surface\n" );
G_FreeEntity( ent );
return;
}
setCamera( ent );
if ( G_Find(ent->owner, FOFS(targetname), ent->target) != NULL )
{//targeted at more than one thing
ent->e_ThinkFunc = thinkF_cycleCamera;
if ( ent->owner->wait > 0 )
{
ent->nextthink = level.time + ent->owner->wait;
}
else
{
ent->nextthink = level.time + ent->wait;
}
}
}
/*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8)
The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted.
This must be within 64 world units of the surface!
targetname - When used, cycles to the next misc_portal_camera it's targeted
wait - makes it auto-cycle between all cameras it's pointed at at intevervals of specified number of seconds.
cameras will be cycled through in the order they were created on the map.
*/
void SP_misc_portal_surface(gentity_t *ent)
{
VectorClear( ent->mins );
VectorClear( ent->maxs );
gi.linkentity (ent);
ent->svFlags = SVF_PORTAL;
ent->s.eType = ET_PORTAL;
ent->wait *= 1000;
if ( !ent->target )
{//mirror?
VectorCopy( ent->s.origin, ent->s.origin2 );
}
else
{
ent->e_ThinkFunc = thinkF_locateCamera;
ent->nextthink = level.time + 100;
if ( ent->targetname )
{
ent->e_UseFunc = useF_misc_portal_use;
}
}
}
/*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate
The target for a misc_portal_surface. You can set either angles or target another entity (NOT an info_null) to determine the direction of view.
"roll" an angle modifier to orient the camera around the target vector;
*/
void SP_misc_portal_camera(gentity_t *ent) {
float roll;
VectorClear( ent->mins );
VectorClear( ent->maxs );
gi.linkentity (ent);
G_SpawnFloat( "roll", "0", &roll );
ent->s.clientNum = roll/360.0 * 256;
ent->wait *= 1000;
}
/*
======================================================================
SHOOTERS
======================================================================
*/
void Use_Shooter( gentity_t *ent, gentity_t *other, gentity_t *activator )
{
/* vec3_t dir;
float deg;
vec3_t up, right;
*/
if (ent->behaviorSet[BSET_USE])
{
G_ActivateBehavior(ent,BSET_USE);
}
/*
// see if we have a target
if ( ent->enemy ) {
VectorSubtract( ent->enemy->currentOrigin, ent->s.origin, dir );
VectorNormalize( dir );
} else {
VectorCopy( ent->movedir, dir );
}
// randomize a bit
PerpendicularVector( up, dir );
CrossProduct( up, dir, right );
deg = crandom() * ent->random;
VectorMA( dir, deg, up, dir );
deg = crandom() * ent->random;
VectorMA( dir, deg, right, dir );
VectorNormalize( dir );
switch ( ent->s.weapon )
{
case WP_GRENADE_LAUNCHER:
fire_grenade( ent, ent->s.origin, dir );
break;
case WP_ROCKET_LAUNCHER:
fire_rocket( ent, ent->s.origin, dir );
break;
case WP_PLASMAGUN:
fire_plasma( ent, ent->s.origin, dir );
break;
}
G_AddEvent( ent, EV_FIRE_WEAPON, 0 );
*/
}
void InitShooter( gentity_t *ent, int weapon ) {
ent->e_UseFunc = useF_Use_Shooter;
ent->s.weapon = weapon;
RegisterItem( FindItemForWeapon( (weapon_t) weapon ) );
G_SetMovedir( ent->s.angles, ent->movedir );
if ( !ent->random ) {
ent->random = 1.0;
}
ent->random = sin( M_PI * ent->random / 180 );
// target might be a moving object, so we can't set movedir for it
if ( ent->target ) {
G_SetEnemy(ent, G_PickTarget( ent->target ));
}
gi.linkentity( ent );
}
/*QUAKED shooter_rocket (1 0 0) (-16 -16 -16) (16 16 16)
Fires at either the target or the current direction.
"random" the number of degrees of deviance from the taget. (1.0 default)
*/
void SP_shooter_rocket( gentity_t *ent )
{
InitShooter( ent, WP_TETRION_DISRUPTOR );
}
/*QUAKED shooter_plasma (1 0 0) (-16 -16 -16) (16 16 16)
Fires at either the target or the current direction.
"random" is the number of degrees of deviance from the taget. (1.0 default)
*/
void SP_shooter_plasma( gentity_t *ent )
{
InitShooter( ent, WP_COMPRESSION_RIFLE);
}
/*QUAKED shooter_grenade (1 0 0) (-16 -16 -16) (16 16 16)
Fires at either the target or the current direction.
"random" is the number of degrees of deviance from the taget. (1.0 default)
*/
void SP_shooter_grenade( gentity_t *ent )
{
InitShooter( ent, WP_GRENADE_LAUNCHER);
}
/*QUAKED object_cargo_barrel1 (1 0 0) (-16 -16 -16) (16 16 29) SMALLER KLINGON NO_SMOKE POWDERKEG
Cargo Barrel
if given a targetname, using it makes it explode
SMALLER - (-8, -8, -16) (8, 8, 8)
KLINGON - klingon style barrel
NO_SMOKE - will not leave lingering smoke cloud when killed
POWDERKEG - wooden explosive barrel
health default = 20
splashDamage default = 100
splashRadius default = 200
*/
void SP_object_cargo_barrel1(gentity_t *ent)
{
if(ent->spawnflags & 8)
{
//FIXME: make an index into an external string table for localization
if (g_language && Q_stricmp("DEUTSCH",g_language->string)==0)
{
ent->fullName = "Pulver-Container";
}
else
{
ent->fullName = "Powderkeg Barrel";
}
ent->s.modelindex = G_ModelIndex( "/models/mapobjects/cargo/barrel_wood2.md3" );
ent->sounds = G_SoundIndex("sound/weapons/explosions/explode3.wav");
}
else if(ent->spawnflags & 2)
{
//FIXME: make an index into an external string table for localization
if (g_language && Q_stricmp("DEUTSCH",g_language->string)==0)
{
ent->fullName = "Klingonen-Frachtcontainer";
}
else
{
ent->fullName = "Klingon Cargo Barrel";
}
ent->s.modelindex = G_ModelIndex( "/models/mapobjects/scavenger/k_barrel.md3" );
ent->sounds = G_SoundIndex("sound/weapons/explosions/explode4.wav");
}
else
{
//FIXME: make an index into an external string table for localization
if (g_language && Q_stricmp("DEUTSCH",g_language->string)==0)
{
ent->fullName = "F<EFBFBD>derations-Frachtcontainer";
}
else
{
ent->fullName = "Federation Cargo Barrel";
}
ent->s.modelindex = G_ModelIndex( va("/models/mapobjects/cargo/barrel%i.md3", Q_irand( 0, 2 )) );
ent->sounds = G_SoundIndex("sound/weapons/explosions/explode1.wav");
}
ent->contents = CONTENTS_SOLID;
if ( ent->spawnflags & 1 )
{
VectorSet (ent->mins, -8, -8, -16);
VectorSet (ent->maxs, 8, 8, 8);
}
else
{
VectorSet (ent->mins, -16, -16, -16);
VectorSet (ent->maxs, 16, 16, 29);
}
G_SetOrigin( ent, ent->s.origin );
VectorCopy( ent->s.angles, ent->s.apos.trBase );
if(!ent->health)
ent->health = 20;
if(!ent->splashDamage)
ent->splashDamage = 100;
if(!ent->splashRadius)
ent->splashRadius = 200;
ent->takedamage = qtrue;
ent->e_DieFunc = dieF_ExplodeDeath_Wait;
if(ent->targetname)
ent->e_UseFunc = useF_GoExplodeDeath;
gi.linkentity (ent);
}
/*QUAKED misc_dlight (0.2 0.8 0.2) (-4 -4 -4) (4 4 4) STARTOFF FADEON FADEOFF PULSE
Dynamic light, toggles on and off when used
STARTOFF - Starts off
FADEON - Fades from 0 Radius to start Radius
FADEOFF - Fades from current Radius to 0 Radius before turning off
PULSE - This flag must be checked if you want it to fade/switch between start and final RGBA, otherwise it will just sit at startRGBA
ownername - Will display the light at the origin of the entity with this targetname
startRGBA - Red Green Blue Radius to start with - This MUST be set or your light won't do anything
These next values are used only if you want to fade/switch between 2 values (PULSE flag on)
finalRGBA - Red Green Blue Radius to end with
speed - how long to take to fade from start to final and final to start. Also how long to fade on and off if appropriate flags are checked (seconds)
finaltime - how long to hold at final (seconds)
starttime - how long to hold at start (seconds)
TODO: Add random to speed/radius?
*/
void SP_misc_dlight(gentity_t *ent)
{
G_SetOrigin( ent, ent->s.origin );
gi.linkentity( ent );
ent->speed *= 1000;
ent->wait *= 1000;
ent->radius *= 1000;
//FIXME: attach self to a train or something?
ent->e_UseFunc = useF_misc_dlight_use;
ent->misc_dlight_active = qfalse;
ent->e_clThinkFunc = clThinkF_NULL;
ent->s.eType = ET_GENERAL;
//Delay first think so we can find owner
if ( ent->ownername )
{
ent->e_ThinkFunc = thinkF_misc_dlight_think;
ent->nextthink = level.time + FRAMETIME;
}
if ( !(ent->spawnflags & 1) )
{//Turn myself on now
GEntity_UseFunc( ent, ent, ent );
}
}
void misc_dlight_use ( gentity_t *ent, gentity_t *other, gentity_t *activator )
{
if (ent->behaviorSet[BSET_USE])
{
G_ActivateBehavior(ent,BSET_USE);
}
if ( ent->misc_dlight_active )
{//We're on, turn off
if ( ent->spawnflags & 4 )
{//fade off
ent->pushDebounceTime = 3;
}
else
{
ent->misc_dlight_active = qfalse;
ent->e_clThinkFunc = clThinkF_NULL;
ent->s.eType = ET_GENERAL;
ent->svFlags &= ~SVF_BROADCAST;
}
}
else
{
//Start at start regardless of when we were turned off
if ( ent->spawnflags & 4 )
{//fade on
ent->pushDebounceTime = 2;
}
else
{//Just start on
ent->pushDebounceTime = 0;
}
ent->painDebounceTime = level.time;
ent->misc_dlight_active = qtrue;
ent->e_ThinkFunc = thinkF_misc_dlight_think;
ent->nextthink = level.time + FRAMETIME;
ent->e_clThinkFunc = clThinkF_CG_DLightThink;
ent->s.eType = ET_THINKER;
ent->svFlags |= SVF_BROADCAST;// Broadcast to all clients
}
}
void misc_dlight_think ( gentity_t *ent )
{
//Stay Attached to owner
if ( ent->owner )
{
G_SetOrigin( ent, ent->owner->currentOrigin );
gi.linkentity( ent );
}
else if ( ent->ownername )
{
ent->owner = G_Find( NULL, FOFS(targetname), ent->ownername );
ent->ownername = NULL;
}
ent->nextthink = level.time + FRAMETIME;
}
void station_pain( gentity_t *self, gentity_t *other, int damage )
{
// self->s.modelindex = G_ModelIndex("/models/mapobjects/stasis/plugin2_in.md3");
// self->s.eFlags &= ~ EF_ANIM_ALLFAST;
// self->s.eFlags |= EF_ANIM_ONCE;
// gi.linkentity (self);
self->s.modelindex = self->s.modelindex2;
gi.linkentity (self);
}
// --------------------------------------------------------------------
//
// HEALTH/ARMOR plugin functions
//
// --------------------------------------------------------------------
void health_use( gentity_t *self, gentity_t *other, gentity_t *activator);
int ITM_AddArmor (gentity_t *ent, int count);
int ITM_AddHealth (gentity_t *ent, int count);
void health_shutdown( gentity_t *self )
{
if (!(self->s.eFlags & EF_ANIM_ONCE))
{
self->s.eFlags &= ~ EF_ANIM_ALLFAST;
self->s.eFlags |= EF_ANIM_ONCE;
G_SpawnString( "infostring", NULL, &self->infoString );
// Switch to and animate its used up model.
if (!Q_stricmp(self->model,"models/mapobjects/stasis/plugin2.md3"))
{
self->s.modelindex = self->s.modelindex2;
}
else if (!Q_stricmp(self->model,"models/mapobjects/borg/plugin2.md3"))
{
self->s.modelindex = self->s.modelindex2;
}
else if (!Q_stricmp(self->model,"models/mapobjects/stasis/plugin2_floor.md3"))
{
self->s.modelindex = self->s.modelindex2;
G_Sound(self, G_SoundIndex("sound/ambience/stasis/shrinkage1.wav") );
}
else if (!Q_stricmp(self->model,"models/mapobjects/forge/panels.md3"))
{
self->s.modelindex = self->s.modelindex2;
}
gi.linkentity (self);
}
}
void health_think( gentity_t *ent )
{
int dif;
// He's dead, Jim. Don't give him health
if (ent->enemy->health<1)
{
ent->count = 0;
ent->e_ThinkFunc = thinkF_NULL;
}
// Still has power to give
if (ent->count > 0)
{
// For every 3 points of health, you get 1 point of armor
// BUT!!! after health is filled up, you get the full energy going to armor
dif = ent->enemy->client->ps.stats[STAT_MAX_HEALTH] - ent->enemy->health;
if (dif > 3 )
{
dif= 3;
}
else if (dif < 0)
{
dif= 0;
}
if (dif > ent->count) // Can't give more than count
{
dif = ent->count;
}
if ((ITM_AddHealth (ent->enemy,dif)) && (dif>0))
{
ITM_AddArmor (ent->enemy,1); // 1 armor for every 3 health
ent->count-=dif;
ent->nextthink = level.time + 10;
}
else // User has taken all health he can hold, see about giving it all to armor
{
dif = ent->enemy->client->ps.stats[STAT_MAX_HEALTH] -
ent->enemy->client->ps.stats[STAT_ARMOR];
if (dif > 3)
{
dif = 3;
}
else if (dif < 0)
{
dif= 0;
}
if (ent->count < dif) // Can't give more than count
{
dif = ent->count;
}
if ((!ITM_AddArmor(ent->enemy,dif)) || (dif<=0))
{
ent->e_UseFunc = useF_health_use;
ent->e_ThinkFunc = thinkF_NULL;
}
else
{
ent->count-=dif;
ent->nextthink = level.time + 10;
}
}
}
if (ent->count < 1)
{
health_shutdown(ent);
}
}
void misc_model_useup( gentity_t *self, gentity_t *other, gentity_t *activator)
{
if (self->behaviorSet[BSET_USE])
{
G_ActivateBehavior(self,BSET_USE);
}
self->s.eFlags &= ~ EF_ANIM_ALLFAST;
self->s.eFlags |= EF_ANIM_ONCE;
if ( VALIDSTRING( self->infoString ))
self->infoString = NULL;
// Switch to and animate its used up model.
self->s.modelindex = self->s.modelindex2;
gi.linkentity (self);
// Use target when used
if (self->spawnflags & 8)
{
G_UseTargets( self, activator );
}
self->e_UseFunc = useF_NULL;
self->e_ThinkFunc = thinkF_NULL;
self->nextthink = -1;
}
void health_use( gentity_t *self, gentity_t *other, gentity_t *activator)
{//FIXME: Heal entire team? Or only those that are undying...?
int dif;
int dif2;
int hold;
if (self->behaviorSet[BSET_USE])
{
G_ActivateBehavior(self,BSET_USE);
}
if (self->e_ThinkFunc != thinkF_NULL)
{
self->e_ThinkFunc = thinkF_NULL;
}
else
{
if (other->client)
{
// He's dead, Jim. Don't give him health
if (other->client->ps.stats[STAT_HEALTH]<1)
{
dif = 1;
self->count = 0;
}
// Health
dif = other->client->ps.stats[STAT_MAX_HEALTH] - other->client->ps.stats[STAT_HEALTH];
// Armor
dif2 = other->client->ps.stats[STAT_MAX_HEALTH] - other->client->ps.stats[STAT_ARMOR];
hold = (dif2 - dif);
// For every 3 points of health, you get 1 point of armor
// BUT!!! after health is filled up, you get the full energy going to armor
if (hold>0) // Need more armor than health
{
// Calculate total amount of station energy needed.
hold = dif / 3; // For every 3 points of health, you get 1 point of armor
dif2 -= hold;
dif2 += dif;
dif = dif2;
}
}
else
{ // Being triggered to be used up
dif = 1;
self->count = 0;
}
// Does player already have full health and full armor?
if (dif > 0)
{
G_Sound(self, G_SoundIndex("sound/player/suithealth.wav") );
if ((dif >= self->count) || (self->count<1)) // use it all up?
{
health_shutdown(self);
}
// Use target when used
if (self->spawnflags & 8)
{
G_UseTargets( self, activator );
}
self->e_UseFunc = useF_NULL;
self->enemy = other;
self->e_ThinkFunc = thinkF_health_think;
self->nextthink = level.time + 50;
}
else
{
G_Sound(self, G_SoundIndex("sound/weapons/noammo.wav") );
}
}
}
// --------------------------------------------------------------------
//
// AMMO plugin functions
//
// --------------------------------------------------------------------
void ammo_use( gentity_t *self, gentity_t *other, gentity_t *activator);
int Add_Ammo2 (gentity_t *ent, int ammoType, int count);
void ammo_shutdown( gentity_t *self )
{
if (!(self->s.eFlags & EF_ANIM_ONCE))
{
self->s.eFlags &= ~ EF_ANIM_ALLFAST;
self->s.eFlags |= EF_ANIM_ONCE;
G_SpawnString( "infostring", NULL, &self->infoString );
if (!Q_stricmp(self->model,"models/mapobjects/stasis/plugin.md3"))
{
self->s.modelindex = self->s.modelindex2;
}
else if (!Q_stricmp(self->model,"models/mapobjects/borg/plugin.md3"))
{
self->s.modelindex = self->s.modelindex2;
}
else if (!Q_stricmp(self->model,"models/mapobjects/stasis/plugin_floor.md3"))
{
G_Sound(self, G_SoundIndex("sound/ambience/stasis/shrinkage.wav") );
self->s.modelindex = self->s.modelindex2;
}
gi.linkentity (self);
}
}
void ammo_think( gentity_t *ent )
{
int dif;
// Still has ammo to give
if (ent->count > 0 && ent->enemy )
{
dif = ammoData[AMMO_STARFLEET].max - ent->enemy->client->ps.ammo[AMMO_STARFLEET];
if (dif > 2 )
{
dif= 2;
}
else if (dif < 0)
{
dif= 0;
}
if (ent->count < dif) // Can't give more than count
{
dif = ent->count;
}
// Give player ammo
if (Add_Ammo2(ent->enemy,AMMO_STARFLEET,dif) && (dif!=0))
{
ent->count-=dif;
ent->nextthink = level.time + 10;
}
else // User has taken all ammo he can hold
{
ent->e_UseFunc = useF_ammo_use;
ent->e_ThinkFunc = thinkF_NULL;
}
}
if (ent->count < 1)
{
ammo_shutdown(ent);
}
}
//------------------------------------------------------------
void ammo_use( gentity_t *self, gentity_t *other, gentity_t *activator)
{
int dif;
if (self->behaviorSet[BSET_USE])
{
G_ActivateBehavior(self,BSET_USE);
}
if (self->e_ThinkFunc != thinkF_NULL)
{
if (self->e_UseFunc != useF_NULL)
{
self->e_ThinkFunc = thinkF_NULL;
}
}
else
{
if (other->client)
{
dif = ammoData[AMMO_STARFLEET].max - other->client->ps.ammo[AMMO_STARFLEET];
}
else
{ // Being triggered to be used up
dif = 1;
self->count = 0;
}
// Does player already have full ammo?
if (dif > 0)
{
G_Sound(self, G_SoundIndex("sound/player/suitenergy.wav") );
if ((dif >= self->count) || (self->count<1)) // use it all up?
{
ammo_shutdown(self);
}
}
else
{
G_Sound(self, G_SoundIndex("sound/weapons/noammo.wav") );
}
// Use target when used
if (self->spawnflags & 8)
{
G_UseTargets( self, activator );
}
self->e_UseFunc = useF_NULL;
G_SetEnemy( self, other );
self->e_ThinkFunc = thinkF_ammo_think;
self->nextthink = level.time + 50;
}
}
//------------------------------------------------------------
void mega_ammo_use( gentity_t *self, gentity_t *other, gentity_t *activator )
{
if ( self->behaviorSet[BSET_USE] )
G_ActivateBehavior( self, BSET_USE );
// Use target when used
G_UseTargets( self, activator );
// first use, adjust the max ammo a person can hold for each type of ammo
ammoData[AMMO_STARFLEET].max = 999;
ammoData[AMMO_ALIEN].max = 999;
// Set up our count with whatever the max difference will be
if ( other->client->ps.ammo[AMMO_ALIEN] > other->client->ps.ammo[AMMO_STARFLEET] )
self->count = ammoData[AMMO_STARFLEET].max - other->client->ps.ammo[AMMO_STARFLEET];
else
self->count = ammoData[AMMO_ALIEN].max - other->client->ps.ammo[AMMO_ALIEN];
G_Sound( self, G_SoundIndex("sound/player/superenergy.wav") );
G_SpawnString( "infostring", NULL, &self->infoString );
// Clear our usefunc, then think until our ammo is full
self->e_UseFunc = useF_NULL;
G_SetEnemy( self, other );
self->e_ThinkFunc = thinkF_mega_ammo_think;
self->nextthink = level.time + 50;
self->s.frame = 0;
self->s.eFlags |= EF_ANIM_ONCE;
}
//------------------------------------------------------------
void mega_ammo_think( gentity_t *self )
{
int ammo_add = 5;
// If the middle model is done animating, and we haven't switched to the last model yet...
// chuck up the last model.
if (!Q_stricmp(self->model,"models/mapobjects/forge/power_up_boss.md3")) // Because the normal forge_ammo model can use this too
{
if ( self->s.frame > 16 && self->s.modelindex != self->s.modelindex2 )
self->s.modelindex = self->s.modelindex2;
}
if ( self->enemy && self->count > 0 )
{
// Add an equal ammount of ammo to each type
self->enemy->client->ps.ammo[AMMO_STARFLEET] += ammo_add;
self->enemy->client->ps.ammo[AMMO_ALIEN] += ammo_add;
// Now cap to prevent overflows
if ( self->enemy->client->ps.ammo[AMMO_STARFLEET] > ammoData[AMMO_STARFLEET].max )
self->enemy->client->ps.ammo[AMMO_STARFLEET] = ammoData[AMMO_STARFLEET].max;
if ( self->enemy->client->ps.ammo[AMMO_ALIEN] > ammoData[AMMO_ALIEN].max )
self->enemy->client->ps.ammo[AMMO_ALIEN] = ammoData[AMMO_ALIEN].max;
// Decrement the count given counter
self->count -= ammo_add;
// If we've given all we should, prevent giving any more, even if they player is no longer full
if ( self->count <= 0 )
{
self->count = 0;
self->e_ThinkFunc = thinkF_NULL;
self->nextthink = -1;
}
else
self->nextthink = 20;
}
}
//------------------------------------------------------------
void spawn_stasis_control_trigger( gentity_t *ent )
{
gentity_t *other;
vec3_t mins, maxs;
// Set the base bounds
VectorCopy( ent->s.origin, mins);
VectorCopy( ent->s.origin, maxs);
// Now add an area of influence around the thing
for ( int i = 0; i < 3; i++ )
{
maxs[i] += 64;
mins[i] -= 64;
}
// create a trigger with this size
other = G_Spawn();
VectorCopy (mins, other->mins);
VectorCopy (maxs, other->maxs);
// set up the other bits that the engine needs to know
other->owner = ent;
other->contents = CONTENTS_TRIGGER;
other->e_TouchFunc = touchF_touch_stasis_control_trigger;
gi.linkentity (other);
}
//------------------------------------------------------------
void touch_stasis_control_trigger( gentity_t *self, gentity_t *other, trace_t *trace )
{
if ( self->owner )
{
self->owner->s.eFlags |= EF_ANIM_ONCE;
G_Sound( self->owner, G_SoundIndex("sound/movers/switches/stasisopen.wav") );
}
// Trigger once only
self->e_TouchFunc = touchF_NULL;
}
//------------------------------------------------------------
void stasis_control_switch_use( gentity_t *self, gentity_t *other, gentity_t *activator)
{
if ( self->spawnflags & 1 ) // NO_PLAYER
{
if (other)
{
if (other->client)
{
return;
}
}
}
if (self->behaviorSet[BSET_USE])
{
G_ActivateBehavior(self,BSET_USE);
}
if (self->e_ThinkFunc != thinkF_NULL)
{
self->e_ThinkFunc = thinkF_NULL;
}
if ( self->spawnflags & 2 ) // NO_TRIGGER
{
// There wasn't a trigger, so start the animation and the sound when the NPC uses us in a script
self->s.eFlags |= EF_ANIM_ONCE;
G_Sound(self, G_SoundIndex("sound/movers/switches/stasisopen.wav") );
}
else
{
G_Sound(self, G_SoundIndex("sound/movers/switches/stasispos.wav") );
}
G_UseTargets( self, activator );
self->e_UseFunc = useF_NULL;
G_SetEnemy( self, other );
}
//------------------------------------------------------------
void switch_models( gentity_t *self, gentity_t *other, gentity_t *activator )
{
// FIXME: need a sound here!!
if ( self->s.modelindex2 )
self->s.modelindex = self->s.modelindex2;
}
//------------------------------------------------------------
void touch_ammo_crystal_tigger( gentity_t *self, gentity_t *other, trace_t *trace )
{
if ( !other->client )
return;
// dead people can't pick things up
if ( other->health < 1 )
return;
// Only player can pick it up
if ( !other->s.number == 0 )
{
return;
}
if ( other->client->ps.ammo[ AMMO_ALIEN ] >= ammoData[AMMO_ALIEN].max )
{
return; // can't hold any more
}
// Add the ammo
other->client->ps.ammo[AMMO_ALIEN] += self->owner->count;
if ( other->client->ps.ammo[AMMO_ALIEN] > ammoData[AMMO_ALIEN].max )
{
other->client->ps.ammo[AMMO_ALIEN] = ammoData[AMMO_ALIEN].max;
}
// Trigger once only
self->e_TouchFunc = touchF_NULL;
// swap the model to the version without the crystal and ditch the infostring
self->owner->s.modelindex = self->owner->s.modelindex2;
self->owner->infoString = NULL;
// play the normal pickup sound
G_AddEvent( other, EV_ITEM_PICKUP, ITM_AMMO_CRYSTAL_BORG );
// fire item targets
G_UseTargets( self->owner, other );
}
//------------------------------------------------------------
void spawn_ammo_crystal_trigger( gentity_t *ent )
{
gentity_t *other;
vec3_t mins, maxs;
// Set the base bounds
VectorCopy( ent->s.origin, mins );
VectorCopy( ent->s.origin, maxs );
// Now add an area of influence around the thing
for ( int i = 0; i < 3; i++ )
{
maxs[i] += 48;
mins[i] -= 48;
}
// create a trigger with this size
other = G_Spawn( );
VectorCopy( mins, other->mins );
VectorCopy( maxs, other->maxs );
// set up the other bits that the engine needs to know
other->owner = ent;
other->contents = CONTENTS_TRIGGER;
other->e_TouchFunc = touchF_touch_ammo_crystal_tigger;
gi.linkentity( other );
}
//REPLICATOR ITEMS
infoStringItem_t G_InfoStringForModel( const char *model )
{
infoStringItem_t infoString = II_NONE;
//NOTE THIS ASSUMES COMMON INFOSTRINGS ARE IN CONSECUTIVE ORDER
if ( Q_stricmp( "models/mapobjects/tour_mode/roastbeef.md3", model ) == 0 )
{
infoString = (infoStringItem_t)Q_irand( II_ROASTBEEF, II_STEAK );
}
else if ( Q_stricmp( "models/mapobjects/cargo/square_cup.md3", model ) == 0 )
{
infoString = (infoStringItem_t)Q_irand( II_WINE, II_BERGUNDY );
}
else if ( Q_stricmp( "models/mapobjects/tour_mode/cup.md3", model ) == 0 )
{
infoString = (infoStringItem_t)Q_irand( II_COFFEE, II_LANDRASCOFFEE );
}
else if ( Q_stricmp( "models/mapobjects/cargo/tray1.md3", model ) == 0 )
{
infoString = (infoStringItem_t)Q_irand( II_PASTA, II_CRISPS );
}
else if ( Q_stricmp( "models/mapobjects/cargo/tray2.md3", model ) == 0 )
{
infoString = (infoStringItem_t)Q_irand( II_STEAK_SALAD, II_SOUP_FRITTERS );
}
else
{//nothing!
}
return infoString;
}
char *G_SoundForInfoString( infoStringItem_t infoString )
{
char *playSound = NULL;
if ( infoString <= II_NONE || infoString >= II_NUM_ITEMS )
{
return NULL;
}
switch ( infoString )
{
case II_ROASTBEEF:
playSound = "sound/voice/computer/tour/beef1.wav";
break;
case II_PORKCHOPS:
playSound = "sound/voice/computer/tour/beef2.wav";
break;
case II_RUMPROAST:
playSound = "sound/voice/computer/tour/beef3.wav";
break;
case II_STEAK:
playSound = "sound/voice/computer/tour/beef4.wav";
break;
case II_WINE:
playSound = "sound/voice/computer/tour/cup1.wav";
break;
case II_BRANDY:
playSound = "sound/voice/computer/tour/cup2.wav";
break;
case II_LATOUR:
playSound = "sound/voice/computer/tour/cup3.wav";
break;
case II_BERGUNDY:
playSound = "sound/voice/computer/tour/cup4.wav";
break;
case II_COFFEE:
playSound = "sound/voice/computer/tour/cup5.wav";
break;
case II_JUICE:
playSound = "sound/voice/computer/tour/cup6.wav";
break;
case II_TEA:
playSound = "sound/voice/computer/tour/cup62.wav";
break;
case II_LANDRASCOFFEE:
playSound = "sound/voice/computer/tour/cup7.wav";
break;
case II_PASTA:
playSound = "sound/voice/computer/tour/plate.wav";
break;
case II_BANTAN:
playSound = "sound/voice/computer/tour/plate1.wav";
break;
case II_CARDAWAY:
playSound = "sound/voice/computer/tour/plate2.wav";
break;
case II_CRISPS:
playSound = "sound/voice/computer/tour/plate3.wav";
break;
case II_STEAK_SALAD:
playSound = "sound/voice/computer/tour/bowl.wav";
break;
case II_STEW_VEGG:
playSound = "sound/voice/computer/tour/bowl2.wav";
break;
case II_SOUP_BISCUITS:
playSound = "sound/voice/computer/tour/bowl3.wav";
break;
case II_SOUP_FRITTERS:
playSound = "sound/voice/computer/tour/bowl4.wav";
break;
//NOTE: No Romulan Ale
}
return playSound;
}
void G_PrecacheModelSounds( const char *model )
{
if ( Q_stricmp( "models/mapobjects/tour_mode/roastbeef.md3", model ) == 0 )
{
G_SoundIndex( G_SoundForInfoString( II_ROASTBEEF ) );
G_SoundIndex( G_SoundForInfoString( II_PORKCHOPS ) );
G_SoundIndex( G_SoundForInfoString( II_RUMPROAST ) );
G_SoundIndex( G_SoundForInfoString( II_STEAK ) );
}
else if ( Q_stricmp( "models/mapobjects/cargo/square_cup.md3", model ) == 0 )
{
G_SoundIndex( G_SoundForInfoString( II_WINE ) );
G_SoundIndex( G_SoundForInfoString( II_BRANDY ) );
G_SoundIndex( G_SoundForInfoString( II_LATOUR ) );
G_SoundIndex( G_SoundForInfoString( II_BERGUNDY ) );
}
else if ( Q_stricmp( "models/mapobjects/tour_mode/cup.md3", model ) == 0 )
{
G_SoundIndex( G_SoundForInfoString( II_COFFEE ) );
G_SoundIndex( G_SoundForInfoString( II_JUICE ) );
G_SoundIndex( G_SoundForInfoString( II_TEA ) );
G_SoundIndex( G_SoundForInfoString( II_LANDRASCOFFEE ) );
}
else if ( Q_stricmp( "models/mapobjects/cargo/tray1.md3", model ) == 0 )
{
G_SoundIndex( G_SoundForInfoString( II_PASTA ) );
G_SoundIndex( G_SoundForInfoString( II_BANTAN ) );
G_SoundIndex( G_SoundForInfoString( II_CARDAWAY ) );
G_SoundIndex( G_SoundForInfoString( II_CRISPS ) );
}
else if ( Q_stricmp( "models/mapobjects/cargo/tray2.md3", model ) == 0 )
{
G_SoundIndex( G_SoundForInfoString( II_STEAK_SALAD ) );
G_SoundIndex( G_SoundForInfoString( II_STEW_VEGG ) );
G_SoundIndex( G_SoundForInfoString( II_SOUP_BISCUITS ) );
G_SoundIndex( G_SoundForInfoString( II_SOUP_FRITTERS ) );
}
else
{//nothing!
}
}
void PlaySoundAndSetInfostring( gentity_t *self, gentity_t *activator, const char *model )
{
infoStringItem_t infoString = II_NONE;
char *playSound = NULL;
if ( !self || !model )
{
return;
}
infoString = G_InfoStringForModel( model );
if ( infoString > II_NONE && infoString < II_NUM_ITEMS )
{
self->infoString = bg_infoItemList[infoString].infoString;
self->contents |= CONTENTS_CORPSE;
playSound = G_SoundForInfoString( infoString );
if ( playSound != NULL )
{
G_SoundOnEnt( activator, CHAN_ANNOUNCER, playSound );
}
}
}
void misc_replicator_item_remove ( gentity_t *self, gentity_t *other, gentity_t *activator )
{
self->s.eFlags |= EF_NODRAW;
self->contents = 0;
self->s.modelindex = 0;
self->e_UseFunc = useF_misc_replicator_item_spawn;
//FIXME: pickup sound?
if ( activator->client )
{
activator->health += 5;
if ( activator->health > activator->client->ps.stats[STAT_MAX_HEALTH] ) // Past max health
{
activator->health = activator->client->ps.stats[STAT_MAX_HEALTH];
}
}
}
void misc_replicator_item_finish_spawn( gentity_t *self )
{
//self->contents = CONTENTS_ITEM;
//FIXME: blinks out for a couple frames when transporter effect is done?
self->e_UseFunc = useF_misc_replicator_item_remove;
}
void misc_replicator_item_spawn ( gentity_t *self, gentity_t *other, gentity_t *activator )
{
char *model = NULL;
switch ( Q_irand( 1, self->count ) )
{
case 1:
self->s.modelindex = self->bounceCount;
model = self->model;
break;
case 2:
self->s.modelindex = self->fly_sound_debounce_time;
model = self->model2;
break;
case 3:
self->s.modelindex = self->painDebounceTime;
model = self->target;
break;
case 4:
self->s.modelindex = self->disconnectDebounceTime;
model = self->target2;
break;
case 5:
self->s.modelindex = self->attackDebounceTime;
model = self->target3;
break;
case 6://max
self->s.modelindex = self->pushDebounceTime;
model = self->target3;
break;
}
self->s.eFlags &= ~EF_NODRAW;
self->e_ThinkFunc = thinkF_misc_replicator_item_finish_spawn;
self->nextthink = level.time + 4000;//shorter?
self->e_UseFunc = useF_NULL;
PlaySoundAndSetInfostring( self, activator, model );
gentity_t *tent = G_TempEntity( self->currentOrigin, EV_REPLICATOR );
tent->owner = self;
}
/*QUAKED misc_replicator_item (0.2 0.8 0.2) (-4 -4 0) (4 4 8)
When used. this will "spawn" an entity with a random model from the ones provided below...
Using it again removes the item as if it were picked up.
model - first random model key
model2 - second random model key
model3 - third random model key
model4 - fourth random model key
model5 - fifth random model key
model6 - sixth random model key
NOTE: do not skip one of these model names, start with the lowest and fill in each next highest one with a value. A gap will cause the item to not work correctly.
NOTE: if you use an invalid model, it will still try to use it and show the NULL axis model (or nothing at all)
targetname - how you refer to it for using it
TODO: Some way to assign an infoString to each model?
*/
void SP_misc_replicator_item ( gentity_t *self )
{
//precache
if ( self->model )
{
self->bounceCount = G_ModelIndex( self->model );
G_PrecacheModelSounds( self->model );
self->count++;
if ( self->model2 )
{
self->fly_sound_debounce_time = G_ModelIndex( self->model2 );
G_PrecacheModelSounds( self->model2 );
self->count++;
if ( self->target )
{
self->painDebounceTime = G_ModelIndex( self->target );
G_PrecacheModelSounds( self->target );
self->count++;
if ( self->target2 )
{
self->disconnectDebounceTime = G_ModelIndex( self->target2 );
G_PrecacheModelSounds( self->target2 );
self->count++;
if ( self->target3 )
{
self->attackDebounceTime = G_ModelIndex( self->target3 );
G_PrecacheModelSounds( self->target3 );
self->count++;
if ( self->target4 )
{
self->pushDebounceTime = G_ModelIndex( self->target4 );
G_PrecacheModelSounds( self->target4 );
self->count++;
}
}
}
}
}
}
G_SoundIndex( "sound/movers/switches/replicator.wav" );
self->e_UseFunc = useF_misc_replicator_item_spawn;
self->s.eFlags |= EF_NODRAW;
//self->contents = 0;
VectorSet( self->mins, -4, -4, 0 );
VectorSet( self->maxs, 4, 4, 8 );
G_SetOrigin( self, self->s.origin );
G_SetAngles( self, self->s.angles );
gi.linkentity( self );
}