1683 lines
44 KiB
C++
1683 lines
44 KiB
C++
// 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 );
|
||
} |