1297 lines
36 KiB
C++
1297 lines
36 KiB
C++
#include "g_local.h"
|
|
#include "g_functions.h"
|
|
#include "b_local.h"
|
|
|
|
extern team_t TranslateTeamName( const char *name );
|
|
extern cvar_t *g_spskill;
|
|
|
|
//client side shortcut hacks from cg_local.h
|
|
extern void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke );
|
|
extern void CG_Chunks( int owner, vec3_t origin, const vec3_t normal, float speed, int numChunks, material_t chunkType, int customChunk, float baseScale );
|
|
extern void CG_FireLaser( vec3_t start, vec3_t end, vec3_t normal, vec4_t laserRGB, qboolean hit_ent );
|
|
extern void CG_AimLaser( vec3_t start, vec3_t end, vec3_t normal );
|
|
extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
|
|
|
|
|
|
#define ARM_ANGLE_RANGE 60
|
|
#define HEAD_ANGLE_RANGE 90
|
|
#define TURR_FOFS 18.0f
|
|
#define TURR_ROFS 0.0f
|
|
#define TURR_UOFS 12.0f
|
|
#define ARM_FOFS 0.0f
|
|
#define ARM_ROFS 0.0f
|
|
#define ARM_UOFS 0.0f
|
|
#define FARM_FOFS 14.0f
|
|
#define FARM_ROFS 0.0f
|
|
#define FARM_UOFS 4.0f
|
|
#define FTURR_FOFS 0.0f
|
|
#define FTURR_ROFS 0.0f
|
|
#define FTURR_UOFS 6.0f
|
|
#define LARM_FOFS 2.0f
|
|
#define LARM_ROFS 0.0f
|
|
#define LARM_UOFS -26.0f
|
|
|
|
void turret_die ( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
|
|
{
|
|
vec3_t dir;
|
|
|
|
//Turn off the thinking of the base & use it's targets
|
|
self->activator->e_ThinkFunc = thinkF_NULL;
|
|
self->activator->e_UseFunc = useF_NULL;
|
|
if ( self->activator->target )
|
|
{
|
|
G_UseTargets( self->activator, attacker );
|
|
}
|
|
|
|
//Remove the arm
|
|
G_FreeEntity( self->owner );
|
|
|
|
//clear my data
|
|
self->e_DieFunc = dieF_NULL;
|
|
self->e_ThinkFunc = thinkF_NULL;
|
|
self->takedamage = qfalse;
|
|
self->health = 0;
|
|
|
|
//Throw some chunks
|
|
AngleVectors( self->activator->currentAngles, dir, NULL, NULL );
|
|
VectorNormalize( dir );
|
|
|
|
CG_Chunks( self->s.number, self->currentOrigin, dir, Q_flrand(150, 300), Q_irand(3, 7), self->material, -1, 1.0 );
|
|
|
|
if ( self->splashDamage > 0 && self->splashRadius > 0 )
|
|
{//FIXME: specify type of explosion? (barrel, electrical, etc.)
|
|
G_RadiusDamage( self->currentOrigin, self->activator, self->splashDamage, self->splashRadius, self->activator, MOD_UNKNOWN );
|
|
|
|
CG_SurfaceExplosion(self->currentOrigin, dir, 20.0f, 12.0f, qtrue );
|
|
G_Sound(self->activator, G_SoundIndex("sound/weapons/explosions/explode11.wav"));
|
|
}
|
|
|
|
if ( self->noDamageTeam == TEAM_FORGE )
|
|
{
|
|
self->activator->s.modelindex = self->activator->s.modelindex2;
|
|
}
|
|
G_FreeEntity( self );
|
|
}
|
|
|
|
#define FORGE_TURRET_DAMAGE 2
|
|
#define FORGE_TURRET_SPLASH_RAD 64
|
|
#define FORGE_TURRET_SPLASH_DAM 4
|
|
#define FORGE_TURRET_VELOCITY 500
|
|
|
|
void fturret_fire ( gentity_t *ent, vec3_t start, vec3_t dir )
|
|
{
|
|
gentity_t *bolt;
|
|
|
|
bolt = G_Spawn();
|
|
bolt->classname = "forge_projectile";
|
|
bolt->nextthink = level.time + 10000;
|
|
bolt->e_ThinkFunc = thinkF_G_FreeEntity;
|
|
bolt->s.eType = ET_MISSILE;
|
|
bolt->svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
bolt->s.weapon = WP_FORGE_PROJ;
|
|
bolt->owner = ent;
|
|
bolt->damage = FORGE_TURRET_DAMAGE+(3*g_spskill->value);
|
|
bolt->dflags = 0;
|
|
bolt->splashDamage = FORGE_TURRET_SPLASH_DAM;
|
|
bolt->splashRadius = FORGE_TURRET_SPLASH_RAD;
|
|
bolt->methodOfDeath = MOD_ENERGY;
|
|
bolt->splashMethodOfDeath = MOD_ENERGY_SPLASH;
|
|
bolt->clipmask = MASK_SHOT;
|
|
|
|
// How 'bout we give this thing a size...
|
|
VectorSet( bolt->mins, -2.0f, -2.0f, -2.0f );
|
|
VectorSet( bolt->maxs, 2.0f, 2.0f, 2.0f );
|
|
|
|
bolt->s.pos.trType = TR_LINEAR;
|
|
bolt->s.pos.trTime = level.time; // move a bit on the very first frame
|
|
VectorCopy( start, bolt->s.pos.trBase );
|
|
VectorScale( dir, FORGE_TURRET_VELOCITY, bolt->s.pos.trDelta );
|
|
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
|
|
VectorCopy( start, bolt->currentOrigin );
|
|
}
|
|
|
|
void turret_fire ( gentity_t *ent, vec3_t start, vec3_t dir )
|
|
{
|
|
gentity_t *bolt;
|
|
|
|
bolt = G_Spawn();
|
|
|
|
bolt->classname = "turret_proj";
|
|
bolt->nextthink = level.time + 10000;
|
|
bolt->e_ThinkFunc = thinkF_G_FreeEntity;
|
|
bolt->s.eType = ET_MISSILE;
|
|
bolt->s.weapon = WP_DN_TURRET;
|
|
bolt->owner = ent;
|
|
bolt->damage = ent->damage;
|
|
bolt->dflags = DAMAGE_NO_KNOCKBACK; // Don't push them around, or else we are constantly re-aiming
|
|
bolt->splashDamage = 0;
|
|
bolt->splashRadius = 0;
|
|
bolt->methodOfDeath = MOD_SCAVENGER; // ?
|
|
bolt->clipmask = MASK_SHOT;
|
|
bolt->trigger_formation = qfalse; // don't draw tail on first frame
|
|
|
|
bolt->s.pos.trType = TR_LINEAR;
|
|
bolt->s.pos.trTime = level.time; // move a bit on the very first frame
|
|
VectorCopy( start, bolt->s.pos.trBase );
|
|
VectorScale( dir, 1100, bolt->s.pos.trDelta );
|
|
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
|
|
VectorCopy( start, bolt->currentOrigin);
|
|
}
|
|
|
|
void turret_head_think (gentity_t *self)
|
|
{
|
|
qboolean fire_now = qfalse;
|
|
|
|
if ( !(self->activator->spawnflags & 2) )
|
|
{//because forge turret heads have no anims... sigh...
|
|
//animate
|
|
if ( self->activator->enemy || self->painDebounceTime > level.time || self->s.frame )
|
|
{
|
|
self->s.frame++;
|
|
if ( self->s.frame > 10 )
|
|
{
|
|
self->s.frame = 0;
|
|
}
|
|
|
|
if ( self->s.frame == 0 || self->s.frame == 4 )
|
|
{
|
|
fire_now = qtrue;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( self->pushDebounceTime < level.time )
|
|
{
|
|
self->pushDebounceTime = level.time + self->wait * 10;
|
|
fire_now = qtrue;
|
|
}
|
|
}
|
|
|
|
//Fire
|
|
if ( fire_now && self->activator->enemy && self->attackDebounceTime < level.time )
|
|
{//Only fire if ready to
|
|
vec3_t forward, right, up, muzzleSpot;
|
|
float rOfs = 0;
|
|
|
|
AngleVectors(self->currentAngles, forward, right, up);
|
|
VectorMA( self->currentOrigin, 16, forward, muzzleSpot );
|
|
VectorMA( self->currentOrigin, 8, up, muzzleSpot );
|
|
if ( !(self->activator->spawnflags & 2) )
|
|
{//DN turrets have offsets
|
|
if ( self->s.frame == 0 )
|
|
{//Fire left barrel
|
|
rOfs = -6;
|
|
}
|
|
else if ( self->s.frame == 4 )
|
|
{//Fire right barrel
|
|
rOfs = 6;
|
|
}
|
|
}
|
|
|
|
VectorMA( self->currentOrigin, rOfs, right, muzzleSpot );
|
|
|
|
|
|
if ( self->noDamageTeam == TEAM_FORGE )
|
|
{//FIXME: do different attack than DN
|
|
G_Sound(self, G_SoundIndex("sound/enemies/turret/ffire.wav"));
|
|
fturret_fire( self, muzzleSpot, forward );
|
|
}
|
|
else
|
|
{
|
|
G_Sound(self, G_SoundIndex("sound/enemies/turret/fire.wav"));
|
|
turret_fire( self, muzzleSpot, forward );
|
|
}
|
|
}
|
|
|
|
//next think
|
|
self->nextthink = level.time + self->wait;
|
|
}
|
|
|
|
void bolt_head_to_arm( gentity_t *arm, gentity_t *head, float fwdOfs, float rtOfs, float upOfs )
|
|
{
|
|
vec3_t headOrg, forward, right, up;
|
|
|
|
AngleVectors( arm->currentAngles, forward, right, up );
|
|
VectorMA( arm->currentOrigin, fwdOfs, forward, headOrg );
|
|
VectorMA( headOrg, rtOfs, right, headOrg );
|
|
VectorMA( headOrg, upOfs, up, headOrg );
|
|
G_SetOrigin( head, headOrg );
|
|
head->currentAngles[1] = head->s.apos.trBase[1] = head->s.angles[1] = arm->currentAngles[1];
|
|
gi.linkentity( head );
|
|
}
|
|
|
|
void bolt_arm_to_base( gentity_t *base, gentity_t *arm, float fwdOfs, float rtOfs, float upOfs )
|
|
{
|
|
vec3_t headOrg, forward, right, up;
|
|
|
|
AngleVectors( base->currentAngles, forward, right, up );
|
|
VectorMA( base->currentOrigin, fwdOfs, forward, headOrg );
|
|
VectorMA( headOrg, rtOfs, right, headOrg );
|
|
VectorMA( headOrg, upOfs, up, headOrg );
|
|
G_SetOrigin( arm, headOrg );
|
|
gi.linkentity( arm );
|
|
G_SetAngles( arm, base->currentAngles );
|
|
}
|
|
|
|
void rebolt_turret( gentity_t *base )
|
|
{
|
|
vec3_t headOrg, forward, right, up;
|
|
|
|
if ( !base->lastEnemy )
|
|
{//no arm
|
|
return;
|
|
}
|
|
|
|
if ( !base->lastEnemy->lastEnemy )
|
|
{//no head
|
|
return;
|
|
}
|
|
|
|
if ( base->spawnflags&2 )
|
|
{
|
|
bolt_arm_to_base( base, base->lastEnemy, FARM_FOFS, FARM_ROFS, FARM_UOFS );
|
|
bolt_head_to_arm( base->lastEnemy, base->lastEnemy->lastEnemy, FTURR_FOFS, FTURR_ROFS, FTURR_UOFS );
|
|
}
|
|
else
|
|
{
|
|
//FIXME: maybe move these seperately so they interpolate?
|
|
G_SetOrigin( base->lastEnemy, base->s.pos.trBase );
|
|
gi.linkentity(base->lastEnemy);
|
|
//G_SetAngles( base->lastEnemy, base->currentAngles );
|
|
AngleVectors( base->lastEnemy->currentAngles, forward, right, up );
|
|
VectorMA( base->lastEnemy->currentOrigin, TURR_FOFS, forward, headOrg );
|
|
VectorMA( headOrg, TURR_ROFS, right, headOrg );
|
|
VectorMA( headOrg, TURR_UOFS, up, headOrg );
|
|
G_SetOrigin( base->lastEnemy->lastEnemy, headOrg );
|
|
//base->lastEnemy->lastEnemy->currentAngles[1] = base->lastEnemy->lastEnemy->s.apos.trBase[1] = base->lastEnemy->lastEnemy->s.angles[1] = base->lastEnemy->currentAngles[1];
|
|
gi.linkentity( base->lastEnemy->lastEnemy );
|
|
}
|
|
}
|
|
/*
|
|
void turret_aim( gentity_t *self )
|
|
|
|
aims arm and head at enemy or neutral position
|
|
*/
|
|
void turret_aim( gentity_t *self )
|
|
{
|
|
vec3_t enemyDir;
|
|
vec3_t desiredAngles;
|
|
float diffAngle, armAngleDiff, headAngleDiff;
|
|
//qboolean turned = qfalse;
|
|
int upTurn = 0;
|
|
int yawTurn = 0;
|
|
|
|
if ( self->enemy )
|
|
{//Aim at enemy
|
|
VectorSubtract( self->enemy->currentOrigin, self->currentOrigin, enemyDir );
|
|
vectoangles( enemyDir, desiredAngles );
|
|
}
|
|
else
|
|
{//Return to front
|
|
VectorCopy( self->currentAngles, desiredAngles );
|
|
}
|
|
|
|
//yaw-aim arm at enemy at speed
|
|
//FIXME: noise when turning?
|
|
diffAngle = AngleSubtract(desiredAngles[1], self->lastEnemy->currentAngles[1]);
|
|
if ( diffAngle )
|
|
{
|
|
if ( fabs(diffAngle) < self->speed )
|
|
{//Just set the angle
|
|
self->lastEnemy->currentAngles[1] = desiredAngles[1];
|
|
//turned = qtrue;
|
|
}
|
|
else
|
|
{//Add the increment
|
|
self->lastEnemy->currentAngles[1] += (diffAngle < 0) ? -self->speed : self->speed;
|
|
//turned = qtrue;
|
|
}
|
|
yawTurn = (diffAngle > 0) ? 1 : -1;
|
|
}
|
|
//Cap the range
|
|
armAngleDiff = AngleSubtract(self->currentAngles[1], self->lastEnemy->currentAngles[1]);
|
|
if ( armAngleDiff > ARM_ANGLE_RANGE )
|
|
{
|
|
self->lastEnemy->currentAngles[1] = AngleNormalize360(self->currentAngles[1] - ARM_ANGLE_RANGE);
|
|
//turned = qfalse;
|
|
}
|
|
else if ( armAngleDiff < -ARM_ANGLE_RANGE )
|
|
{
|
|
self->lastEnemy->currentAngles[1] = AngleNormalize360(self->currentAngles[1] + ARM_ANGLE_RANGE);
|
|
//turned = qfalse;
|
|
}
|
|
G_SetAngles( self->lastEnemy, self->lastEnemy->currentAngles );
|
|
|
|
//Now put the turret at the tip of the arm
|
|
if ( self->spawnflags&2 )
|
|
{
|
|
bolt_head_to_arm( self->lastEnemy, self->lastEnemy->lastEnemy, FTURR_FOFS, FTURR_ROFS, FTURR_UOFS );
|
|
}
|
|
else
|
|
{
|
|
bolt_head_to_arm( self->lastEnemy, self->lastEnemy->lastEnemy, TURR_FOFS, TURR_ROFS, TURR_UOFS );
|
|
}
|
|
|
|
//pitch-aim head at enemy at speed
|
|
//FIXME: noise when turning?
|
|
if ( self->enemy )
|
|
{
|
|
VectorSubtract( self->enemy->currentOrigin, self->lastEnemy->lastEnemy->currentOrigin, enemyDir );
|
|
vectoangles( enemyDir, desiredAngles );
|
|
}
|
|
/*//Not necc
|
|
else
|
|
{
|
|
VectorCopy(self->currentAngles, desiredAngles);
|
|
}
|
|
*/
|
|
diffAngle = AngleSubtract( desiredAngles[0], self->lastEnemy->lastEnemy->currentAngles[0] );
|
|
if ( diffAngle )
|
|
{
|
|
if ( fabs(diffAngle) < self->speed )
|
|
{//Just set the angle
|
|
self->lastEnemy->lastEnemy->currentAngles[0] = desiredAngles[0];
|
|
//turned = qtrue;
|
|
}
|
|
else
|
|
{//Add the increment
|
|
self->lastEnemy->lastEnemy->currentAngles[0] += (diffAngle < 0) ? -self->speed : self->speed;
|
|
//turned = qtrue;
|
|
}
|
|
upTurn = (diffAngle > 0) ? 1 : -1;
|
|
}
|
|
//Cap the range
|
|
headAngleDiff = AngleSubtract(self->currentAngles[0], self->lastEnemy->lastEnemy->currentAngles[0]);
|
|
if ( headAngleDiff > HEAD_ANGLE_RANGE )
|
|
{
|
|
self->lastEnemy->lastEnemy->currentAngles[0] = AngleNormalize360(self->currentAngles[0] - HEAD_ANGLE_RANGE);
|
|
//turned = qfalse;
|
|
}
|
|
else if ( headAngleDiff < -HEAD_ANGLE_RANGE )
|
|
{
|
|
self->lastEnemy->lastEnemy->currentAngles[0] = AngleNormalize360(self->lastEnemy->currentAngles[0] + HEAD_ANGLE_RANGE);
|
|
//turned = qfalse;
|
|
}
|
|
G_SetAngles( self->lastEnemy->lastEnemy, self->lastEnemy->lastEnemy->currentAngles );
|
|
|
|
//Play sound if turret changes direction
|
|
//Pitch:
|
|
/*
|
|
if ( upTurn && upTurn != self->count )
|
|
{//changed dir
|
|
G_Sound(self->lastEnemy->lastEnemy, G_SoundIndex("sound/enemies/turret/move.wav"));
|
|
}
|
|
else if ( !upTurn && self->count )
|
|
{//Just stopped
|
|
G_Sound(self->lastEnemy->lastEnemy, G_SoundIndex("sound/enemies/turret/stop.wav"));
|
|
}
|
|
self->count = upTurn;
|
|
*/
|
|
//Yaw:
|
|
if ( yawTurn && yawTurn != self->bounceCount )
|
|
{//changed dir
|
|
G_Sound(self->lastEnemy, G_SoundIndex("sound/enemies/turret/move.wav"));
|
|
}
|
|
else if ( !yawTurn && self->bounceCount )
|
|
{//Just stopped
|
|
G_Sound(self->lastEnemy, G_SoundIndex("sound/enemies/turret/stop.wav"));
|
|
}
|
|
self->bounceCount = yawTurn;
|
|
|
|
/*
|
|
if ( turned )
|
|
{
|
|
G_Sound(self->lastEnemy, G_SoundIndex("sound/enemies/turret/move.wav"));
|
|
}
|
|
*/
|
|
}
|
|
|
|
void turret_turnoff (gentity_t *self)
|
|
{
|
|
if ( self->enemy == NULL )
|
|
{
|
|
return;
|
|
}
|
|
//shut-down sound
|
|
G_Sound(self, G_SoundIndex("sound/enemies/turret/shutdown.wav"));
|
|
|
|
//make turret keep animating for 3 secs
|
|
self->lastEnemy->lastEnemy->painDebounceTime = level.time + 3000;
|
|
|
|
//Clear enemy
|
|
self->enemy = NULL;
|
|
}
|
|
|
|
void turret_base_think (gentity_t *self)
|
|
{
|
|
vec3_t enemyDir;
|
|
float enemyDist;
|
|
|
|
self->nextthink = level.time + FRAMETIME;
|
|
|
|
if ( self->spawnflags & 1 )
|
|
{//not turned on
|
|
turret_turnoff( self );
|
|
turret_aim( self );
|
|
//No target
|
|
if ( self->lastEnemy && self->lastEnemy->lastEnemy )
|
|
{
|
|
self->lastEnemy->lastEnemy->flags |= FL_NOTARGET;
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{//I'm all hot and bothered
|
|
if ( self->lastEnemy && self->lastEnemy->lastEnemy )
|
|
{
|
|
self->lastEnemy->lastEnemy->flags &= ~FL_NOTARGET;
|
|
}
|
|
}
|
|
|
|
if ( !self->enemy )
|
|
{//Find one
|
|
gentity_t *entity_list[MAX_GENTITIES], *target;
|
|
int count;
|
|
float bestDist = self->radius * self->radius;
|
|
|
|
if ( self->attackDebounceTime > level.time )
|
|
{//We're active and alert, had an enemy in the last 5 secs
|
|
if ( self->painDebounceTime < level.time )
|
|
{
|
|
G_Sound(self, G_SoundIndex("sound/enemies/turret/ping.wav"));
|
|
self->painDebounceTime = level.time + 1000;
|
|
}
|
|
}
|
|
|
|
count = G_RadiusList( self->currentOrigin, self->radius, self->lastEnemy->lastEnemy, qtrue, entity_list );
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
target = entity_list[i];
|
|
if ( target == self )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( target->takedamage && target->health > 0 && !(target->flags & FL_NOTARGET) )
|
|
{
|
|
if ( !target->client && target->noDamageTeam == self->noDamageTeam )
|
|
{//Something of ours we don't want to destroy
|
|
continue;
|
|
}
|
|
if ( target->client && target->client->playerTeam == self->noDamageTeam )
|
|
{//A bot we don't want to shoot
|
|
continue;
|
|
}
|
|
|
|
if ( gi.inPVS( self->lastEnemy->lastEnemy->currentOrigin, target->currentOrigin ) )
|
|
{
|
|
trace_t tr;
|
|
|
|
gi.trace( &tr, self->lastEnemy->lastEnemy->currentOrigin, NULL, NULL, target->currentOrigin, self->lastEnemy->lastEnemy->s.number, MASK_SHOT );
|
|
|
|
if ( !tr.allsolid && !tr.startsolid && (tr.fraction == 1.0 || tr.entityNum == target->s.number) )
|
|
{//Only acquire if have a clear shot
|
|
//Is it in range and closer than our best?
|
|
VectorSubtract( target->currentOrigin, self->currentOrigin, enemyDir );
|
|
enemyDist = VectorLengthSquared( enemyDir );
|
|
if ( enemyDist < bestDist )//all things equal, keep current
|
|
{
|
|
if ( self->attackDebounceTime < level.time )
|
|
{//We haven't fired or acquired an enemy in the last 5 seconds
|
|
//start-up sound
|
|
G_Sound(self, G_SoundIndex("sound/enemies/turret/startup.wav"));
|
|
//Wind up turrets for a second
|
|
self->lastEnemy->lastEnemy->attackDebounceTime = level.time + 1000;
|
|
}
|
|
G_SetEnemy( self, target );
|
|
bestDist = enemyDist;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( self->enemy )
|
|
{//Check if still in radius
|
|
if ( self->enemy->health <= 0 )
|
|
{
|
|
turret_turnoff( self );
|
|
return;
|
|
}
|
|
|
|
VectorSubtract( self->enemy->currentOrigin, self->currentOrigin, enemyDir );
|
|
enemyDist = VectorLengthSquared( enemyDir );
|
|
if ( enemyDist > self->radius*self->radius )
|
|
{
|
|
turret_turnoff( self );
|
|
return;
|
|
}
|
|
|
|
if ( !gi.inPVS( self->lastEnemy->lastEnemy->currentOrigin, self->enemy->currentOrigin ) )
|
|
{
|
|
turret_turnoff( self );
|
|
return;
|
|
}
|
|
|
|
// Every now and again, check to see if we can even trace to the enemy
|
|
if ( Q_irand( 0, 16 ) > 15 )
|
|
{
|
|
trace_t tr;
|
|
|
|
gi.trace( &tr, self->lastEnemy->lastEnemy->currentOrigin, NULL, NULL, self->enemy->currentOrigin, self->lastEnemy->lastEnemy->s.number, MASK_SHOT );
|
|
if ( tr.allsolid || tr.startsolid || tr.fraction != 1.0 )
|
|
{
|
|
// Couldn't see our enemy
|
|
turret_turnoff( self );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( self->enemy )
|
|
{//Aim
|
|
//Won't need to wind up turrets for a while
|
|
self->attackDebounceTime = level.time + 5000;
|
|
turret_aim( self );
|
|
}
|
|
else if ( self->attackDebounceTime < level.time )
|
|
{
|
|
//Move arm and head back to neutral angles
|
|
turret_aim( self );
|
|
}
|
|
}
|
|
|
|
void turret_base_use (gentity_t *self, gentity_t *other, gentity_t *activator)
|
|
{//Toggle on and off
|
|
self->spawnflags = (self->spawnflags ^ 1);
|
|
}
|
|
|
|
/*QUAKED misc_turret (1 0 0) (-8 -8 -8) (8 8 8) START_OFF FORGE
|
|
Will aim and shoot at enemies
|
|
|
|
START_OFF - Starts off
|
|
FORGE - Uses Forge turret models and projectiles
|
|
|
|
radius - How far away an enemy can be for it to pick it up (default 512)
|
|
speed - How fast it turns (degrees per second, default 30)
|
|
wait - How fast it shoots (shots per second, default 4, can't be less)
|
|
dmg - How much damage each shot does (default 5)
|
|
health - How much damage it can take before exploding (default 100)
|
|
|
|
splashDamage - How much damage the explosion does
|
|
splashRadius - The radius of the explosion
|
|
NOTE: If either of the above two are 0, it will not make an explosion
|
|
|
|
targetname - Toggles it on/off
|
|
target - What to use when destroyed
|
|
|
|
"team" - will not target members of this team (default "dreadnought"):
|
|
"starfleet"
|
|
"borg"
|
|
"parasite"
|
|
"scavengers"
|
|
"klingon"
|
|
"malon"
|
|
"hirogen"
|
|
"imperial"
|
|
"stasis"
|
|
"species8472"
|
|
"dreadnought"
|
|
"forge"
|
|
*/
|
|
void SP_misc_turret (gentity_t *base)
|
|
{
|
|
//We're the base, spawn the arm and head
|
|
gentity_t *arm = G_Spawn();
|
|
gentity_t *head = G_Spawn();
|
|
vec3_t fwd;
|
|
|
|
//Base
|
|
//Base does the looking for enemies and pointing the arm and head
|
|
G_SetAngles( base, base->s.angles );
|
|
AngleVectors( base->currentAngles, fwd, NULL, NULL );
|
|
VectorMA( base->s.origin, -8, fwd, base->s.origin );
|
|
G_SetOrigin(base, base->s.origin);
|
|
gi.linkentity(base);
|
|
if ( base->spawnflags & 2 )
|
|
{
|
|
base->s.modelindex = G_ModelIndex("models/mapobjects/forge/turret.md3");
|
|
base->s.modelindex2 = G_ModelIndex("models/mapobjects/forge/turret_d1.md3");
|
|
base->noDamageTeam = TEAM_FORGE;
|
|
}
|
|
else
|
|
{
|
|
base->s.modelindex = G_ModelIndex("models/mapobjects/dn/gunturret_base.md3");
|
|
base->noDamageTeam = TEAM_BOTS;
|
|
}
|
|
base->s.eType = ET_GENERAL;
|
|
if ( base->team && base->team[0] )
|
|
{
|
|
base->noDamageTeam = TranslateTeamName( base->team );
|
|
}
|
|
//anglespeed - how fast it can track the player, entered in degrees per second, so we divide by FRAMETIME/1000
|
|
if ( !base->speed )
|
|
{
|
|
base->speed = 3.0f;
|
|
}
|
|
else
|
|
{
|
|
base->speed /= FRAMETIME/1000.0f;
|
|
}
|
|
//range
|
|
if ( !base->radius )
|
|
{
|
|
base->radius = 512;
|
|
}
|
|
base->e_UseFunc = useF_turret_base_use;
|
|
base->e_ThinkFunc = thinkF_turret_base_think;
|
|
base->nextthink = level.time + FRAMETIME;
|
|
|
|
//Arm
|
|
//Does nothing, not solid, gets removed when head explodes
|
|
if ( base->spawnflags&2 )
|
|
{
|
|
bolt_arm_to_base( base, arm, FARM_FOFS, FARM_ROFS, FARM_UOFS );
|
|
bolt_head_to_arm( arm, head, FTURR_FOFS, FTURR_ROFS, FTURR_UOFS );
|
|
}
|
|
else
|
|
{
|
|
bolt_arm_to_base( base, arm, ARM_FOFS, ARM_ROFS, ARM_UOFS );
|
|
//G_SetOrigin( arm, base->s.origin );
|
|
//gi.linkentity(arm);
|
|
//G_SetAngles( arm, base->currentAngles );
|
|
bolt_head_to_arm( arm, head, TURR_FOFS, TURR_ROFS, TURR_UOFS );
|
|
}
|
|
if ( base->spawnflags & 2 )
|
|
{
|
|
arm->s.modelindex = G_ModelIndex("models/mapobjects/forge/turret_neck.md3");
|
|
arm->noDamageTeam = TEAM_FORGE;
|
|
}
|
|
else
|
|
{
|
|
arm->s.modelindex = G_ModelIndex("models/mapobjects/dn/gunturret_arm.md3");
|
|
arm->noDamageTeam = TEAM_BOTS;
|
|
}
|
|
|
|
//Head
|
|
//Fires when enemy detected, animates, can be blown up
|
|
G_SetAngles( head, base->currentAngles );
|
|
if ( base->spawnflags & 2 )
|
|
{
|
|
head->s.modelindex = G_ModelIndex("models/mapobjects/forge/turret_head.md3");
|
|
head->noDamageTeam = TEAM_FORGE;
|
|
}
|
|
else
|
|
{
|
|
head->s.modelindex = G_ModelIndex("models/mapobjects/dn/gunturret_head.md3");
|
|
head->noDamageTeam = TEAM_BOTS;
|
|
}
|
|
head->s.eType = ET_GENERAL;
|
|
VectorSet( head->mins, -8, -8, -16 );
|
|
VectorSet( head->maxs, 8, 8, 16 );
|
|
//FIXME: make an index into an external string table for localization
|
|
if (g_language && Q_stricmp("DEUTSCH",g_language->string)==0)
|
|
{
|
|
head->fullName = "Turm";
|
|
}
|
|
else
|
|
{
|
|
head->fullName = "Turret";
|
|
}
|
|
gi.linkentity(head);
|
|
|
|
//How much health head takes to explode
|
|
if ( !base->health )
|
|
{
|
|
head->health = 100;
|
|
}
|
|
else
|
|
{
|
|
head->health = base->health;
|
|
}
|
|
base->health = 0;
|
|
//How quickly to fire
|
|
if ( !base->wait )
|
|
{
|
|
head->wait = 50;
|
|
}
|
|
else
|
|
{
|
|
head->wait = 100/(base->wait/2);
|
|
}
|
|
base->wait = 0;
|
|
//splashDamage
|
|
if ( !base->splashDamage )
|
|
{
|
|
head->splashDamage = 10;
|
|
}
|
|
else
|
|
{
|
|
head->splashDamage = base->splashDamage;
|
|
}
|
|
base->splashDamage = 0;
|
|
//splashRadius
|
|
if ( !base->splashRadius )
|
|
{
|
|
head->splashRadius = 25;
|
|
}
|
|
else
|
|
{
|
|
head->splashRadius = base->splashRadius;
|
|
}
|
|
base->splashRadius = 0;
|
|
//dmg
|
|
if ( !base->damage )
|
|
{
|
|
head->damage = 5;
|
|
}
|
|
else
|
|
{
|
|
head->damage = base->damage;
|
|
}
|
|
base->damage = 0;
|
|
|
|
//Precache firing and explode sounds
|
|
G_SoundIndex("sound/weapons/explosions/explode11.wav");
|
|
G_SoundIndex("sound/enemies/turret/startup.wav");
|
|
G_SoundIndex("sound/enemies/turret/shutdown.wav");
|
|
G_SoundIndex("sound/enemies/turret/move.wav");
|
|
G_SoundIndex("sound/enemies/turret/stop.wav");
|
|
G_SoundIndex("sound/enemies/turret/ping.wav");
|
|
if ( base->spawnflags & 2 )
|
|
{
|
|
G_SoundIndex("sound/enemies/turret/ffire.wav");
|
|
}
|
|
else
|
|
{
|
|
G_SoundIndex("sound/enemies/turret/fire.wav");
|
|
}
|
|
|
|
head->contents = CONTENTS_BODY;
|
|
head->max_health = head->health;
|
|
head->takedamage = qtrue;
|
|
head->e_DieFunc = dieF_turret_die;
|
|
|
|
head->e_ThinkFunc = thinkF_turret_head_think;
|
|
head->nextthink = level.time + FRAMETIME;
|
|
|
|
head->material = MAT_METAL;
|
|
head->svFlags |= SVF_NO_TELEPORT|SVF_NONNPC_ENEMY|SVF_SELF_ANIMATING;
|
|
|
|
//Link them up
|
|
base->lastEnemy = arm;
|
|
arm->lastEnemy = head;
|
|
head->owner = arm;
|
|
arm->activator = head->activator = base;
|
|
|
|
if ( base->spawnflags & 2 )
|
|
{
|
|
//temp gfx and sounds
|
|
RegisterItem( FindItemForWeapon( WP_FORGE_PROJ ) ); //precache the weapon
|
|
}
|
|
else
|
|
{
|
|
//temp gfx and sounds
|
|
RegisterItem( FindItemForWeapon( WP_DN_TURRET ) ); //precache the weapon
|
|
}
|
|
}
|
|
|
|
void laser_arm_fire (gentity_t *ent)
|
|
{
|
|
vec3_t start, end, fwd, rt, up;
|
|
trace_t trace;
|
|
|
|
if ( ent->attackDebounceTime < level.time && ent->alt_fire )
|
|
{
|
|
// If I'm firing the laser and it's time to quit....then quit!
|
|
ent->alt_fire = qfalse;
|
|
// ent->e_ThinkFunc = thinkF_NULL;
|
|
// return;
|
|
}
|
|
|
|
ent->nextthink = level.time + FRAMETIME;
|
|
|
|
// If a fool gets in the laser path, fry 'em
|
|
AngleVectors( ent->currentAngles, fwd, rt, up );
|
|
|
|
VectorMA( ent->currentOrigin, 20, fwd, start );
|
|
//VectorMA( start, -6, rt, start );
|
|
//VectorMA( start, -3, up, start );
|
|
VectorMA( start, 4096, fwd, end );
|
|
|
|
gi.trace( &trace, start, NULL, NULL, end, -1, MASK_SHOT );//ignore
|
|
|
|
// Only deal damage when in alt-fire mode
|
|
if ( trace.fraction < 1.0 && ent->alt_fire )
|
|
{
|
|
if ( trace.entityNum < ENTITYNUM_WORLD )
|
|
{
|
|
gentity_t *hapless_victim = &g_entities[trace.entityNum];
|
|
if ( hapless_victim && hapless_victim->takedamage && ent->damage )
|
|
{
|
|
G_Damage( hapless_victim, ent, ent->nextTrain->activator, fwd, trace.endpos, ent->damage, DAMAGE_IGNORE_TEAM, MOD_SURGICAL_LASER );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ent->alt_fire )
|
|
{
|
|
CG_FireLaser( start, trace.endpos, trace.plane.normal, ent->nextTrain->startRGBA, qfalse );
|
|
}
|
|
else
|
|
{
|
|
CG_AimLaser( start, trace.endpos, trace.plane.normal );
|
|
}
|
|
}
|
|
|
|
void laser_arm_use (gentity_t *self, gentity_t *other, gentity_t *activator)
|
|
{
|
|
vec3_t newAngles;
|
|
|
|
self->activator = activator;
|
|
switch( self->count )
|
|
{
|
|
case 0:
|
|
default:
|
|
//Fire
|
|
//gi.Printf("FIRE!\n");
|
|
// self->lastEnemy->lastEnemy->e_ThinkFunc = thinkF_laser_arm_fire;
|
|
// self->lastEnemy->lastEnemy->nextthink = level.time + FRAMETIME;
|
|
//For 3 seconds
|
|
self->lastEnemy->lastEnemy->alt_fire = qtrue; // Let 'er rip!
|
|
self->lastEnemy->lastEnemy->attackDebounceTime = level.time + self->lastEnemy->lastEnemy->wait;
|
|
G_Sound(self->lastEnemy->lastEnemy, G_SoundIndex("sound/enemies/l_arm/fire.wav"));
|
|
break;
|
|
case 1:
|
|
//Yaw left
|
|
//gi.Printf("LEFT...\n");
|
|
VectorCopy( self->lastEnemy->currentAngles, newAngles );
|
|
newAngles[1] += self->speed;
|
|
G_SetAngles( self->lastEnemy, newAngles );
|
|
bolt_head_to_arm( self->lastEnemy, self->lastEnemy->lastEnemy, LARM_FOFS, LARM_ROFS, LARM_UOFS );
|
|
G_Sound( self->lastEnemy, G_SoundIndex( "sound/enemies/l_arm/move.wav" ) );
|
|
break;
|
|
case 2:
|
|
//Yaw right
|
|
//gi.Printf("RIGHT...\n");
|
|
VectorCopy( self->lastEnemy->currentAngles, newAngles );
|
|
newAngles[1] -= self->speed;
|
|
G_SetAngles( self->lastEnemy, newAngles );
|
|
bolt_head_to_arm( self->lastEnemy, self->lastEnemy->lastEnemy, LARM_FOFS, LARM_ROFS, LARM_UOFS );
|
|
G_Sound( self->lastEnemy, G_SoundIndex( "sound/enemies/l_arm/move.wav" ) );
|
|
break;
|
|
case 3:
|
|
//pitch up
|
|
//gi.Printf("UP...\n");
|
|
//FIXME: Clamp
|
|
VectorCopy( self->lastEnemy->lastEnemy->currentAngles, newAngles );
|
|
newAngles[0] -= self->speed;
|
|
if ( newAngles[0] < -45 )
|
|
{
|
|
newAngles[0] = -45;
|
|
}
|
|
G_SetAngles( self->lastEnemy->lastEnemy, newAngles );
|
|
G_Sound( self->lastEnemy->lastEnemy, G_SoundIndex( "sound/enemies/l_arm/move.wav" ) );
|
|
break;
|
|
case 4:
|
|
//pitch down
|
|
//gi.Printf("DOWN...\n");
|
|
//FIXME: Clamp
|
|
VectorCopy( self->lastEnemy->lastEnemy->currentAngles, newAngles );
|
|
newAngles[0] += self->speed;
|
|
if ( newAngles[0] > 90 )
|
|
{
|
|
newAngles[0] = 90;
|
|
}
|
|
G_SetAngles( self->lastEnemy->lastEnemy, newAngles );
|
|
G_Sound( self->lastEnemy->lastEnemy, G_SoundIndex( "sound/enemies/l_arm/move.wav" ) );
|
|
break;
|
|
}
|
|
}
|
|
/*QUAKED misc_laser_arm (1 0 0) (-8 -8 -8) (8 8 8)
|
|
|
|
What it does when used depends on it's "count" (can be set by a script)
|
|
count:
|
|
0 (default) - Fire in direction facing
|
|
1 turn left
|
|
2 turn right
|
|
3 aim up
|
|
4 aim down
|
|
|
|
speed - How fast it turns (degrees per second, default 30)
|
|
dmg - How much damage the laser does 10 times a second (default 5 = 50 points per second)
|
|
wait - How long the beam lasts, in seconds (default is 3)
|
|
|
|
targetname - to use it
|
|
target - What thing for it to be pointing at to start with
|
|
|
|
"startRGBA" - laser color, Red Green Blue Alpha, range 0 to 1 (default 1.0 0.85 0.15 0.75 = Yellow-Orange)
|
|
*/
|
|
void laser_arm_start (gentity_t *base)
|
|
{
|
|
vec3_t armAngles;
|
|
vec3_t headAngles;
|
|
|
|
base->e_ThinkFunc = thinkF_NULL;
|
|
//We're the base, spawn the arm and head
|
|
gentity_t *arm = G_Spawn();
|
|
gentity_t *head = G_Spawn();
|
|
|
|
VectorCopy( base->s.angles, armAngles );
|
|
VectorCopy( base->s.angles, headAngles );
|
|
if ( base->target && base->target[0] )
|
|
{//Start out pointing at something
|
|
gentity_t *targ = G_Find( NULL, FOFS(targetname), base->target );
|
|
if ( !targ )
|
|
{//couldn't find it!
|
|
Com_Printf(S_COLOR_RED "ERROR : laser_arm can't find target %s!\n", base->target);
|
|
}
|
|
else
|
|
{//point at it
|
|
vec3_t dir, angles;
|
|
|
|
VectorSubtract(targ->currentOrigin, base->s.origin, dir );
|
|
vectoangles( dir, angles );
|
|
armAngles[1] = angles[1];
|
|
headAngles[0] = angles[0];
|
|
headAngles[1] = angles[1];
|
|
}
|
|
}
|
|
|
|
//Base
|
|
//Base does the looking for enemies and pointing the arm and head
|
|
G_SetAngles( base, base->s.angles );
|
|
//base->s.origin[2] += 4;
|
|
G_SetOrigin(base, base->s.origin);
|
|
gi.linkentity(base);
|
|
//FIXME: need an actual model
|
|
base->s.modelindex = G_ModelIndex("models/mapobjects/dn/laser_base.md3");
|
|
base->s.eType = ET_GENERAL;
|
|
G_SpawnVector4( "startRGBA", "1.0 0.85 0.15 0.75", (float *)&base->startRGBA );
|
|
//anglespeed - how fast it can track the player, entered in degrees per second, so we divide by FRAMETIME/1000
|
|
if ( !base->speed )
|
|
{
|
|
base->speed = 3.0f;
|
|
}
|
|
else
|
|
{
|
|
base->speed *= FRAMETIME/1000.0f;
|
|
}
|
|
base->e_UseFunc = useF_laser_arm_use;
|
|
base->nextthink = level.time + FRAMETIME;
|
|
|
|
//Arm
|
|
//Does nothing, not solid, gets removed when head explodes
|
|
G_SetOrigin( arm, base->s.origin );
|
|
gi.linkentity(arm);
|
|
G_SetAngles( arm, armAngles );
|
|
bolt_head_to_arm( arm, head, LARM_FOFS, LARM_ROFS, LARM_UOFS );
|
|
arm->s.modelindex = G_ModelIndex("models/mapobjects/dn/laser_arm.md3");
|
|
|
|
//Head
|
|
//Fires when enemy detected, animates, can be blown up
|
|
//Need to normalize the headAngles pitch for the clamping later
|
|
if ( headAngles[0] < -180 )
|
|
{
|
|
headAngles[0] += 360;
|
|
}
|
|
else if ( headAngles[0] > 180 )
|
|
{
|
|
headAngles[0] -= 360;
|
|
}
|
|
G_SetAngles( head, headAngles );
|
|
head->s.modelindex = G_ModelIndex("models/mapobjects/dn/laser_head.md3");
|
|
head->s.eType = ET_GENERAL;
|
|
// head->svFlags |= SVF_BROADCAST;// Broadcast to all clients
|
|
VectorSet( head->mins, -8, -8, -8 );
|
|
VectorSet( head->maxs, 8, 8, 8 );
|
|
head->contents = CONTENTS_BODY;
|
|
//FIXME: make an index into an external string table for localization
|
|
if (g_language && Q_stricmp("DEUTSCH",g_language->string)==0)
|
|
{
|
|
head->fullName = "Medizinischer Laser";
|
|
}
|
|
else
|
|
{
|
|
head->fullName = "Surgical Laser";
|
|
}
|
|
gi.linkentity(head);
|
|
|
|
//dmg
|
|
if ( !base->damage )
|
|
{
|
|
head->damage = 5;
|
|
}
|
|
else
|
|
{
|
|
head->damage = base->damage;
|
|
}
|
|
base->damage = 0;
|
|
//lifespan of beam
|
|
if ( !base->wait )
|
|
{
|
|
head->wait = 3000;
|
|
}
|
|
else
|
|
{
|
|
head->wait = base->wait * 1000;
|
|
}
|
|
base->wait = 0;
|
|
|
|
//Precache firing and explode sounds
|
|
G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
|
|
G_SoundIndex("sound/enemies/l_arm/fire.wav");
|
|
G_SoundIndex("sound/enemies/l_arm/move.wav");
|
|
|
|
//Link them up
|
|
base->lastEnemy = arm;
|
|
arm->lastEnemy = head;
|
|
head->owner = arm;
|
|
arm->nextTrain = head->nextTrain = base;
|
|
|
|
// The head should always think, since it will be either firing a damage laser or just a target laser
|
|
head->e_ThinkFunc = thinkF_laser_arm_fire;
|
|
head->nextthink = level.time + FRAMETIME;
|
|
head->alt_fire = qfalse; // Don't do damage until told to
|
|
}
|
|
|
|
void SP_laser_arm (gentity_t *base)
|
|
{
|
|
base->e_ThinkFunc = thinkF_laser_arm_start;
|
|
base->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
//Stasis Multi-Headed CyberHydra head's firing effects
|
|
void stasis_shooter_active( gentity_t *self )
|
|
{
|
|
gentity_t *radiusEnts[ 1024 ];
|
|
vec3_t mins, maxs, org, end, diff, shotEnd;
|
|
int nearestDist = 99999999;
|
|
int distance;
|
|
int numEnts;
|
|
trace_t tr;
|
|
|
|
if ( self->owner )
|
|
{
|
|
if ( !self->owner->health )
|
|
{
|
|
// I'm dead, so don't think.
|
|
return;
|
|
}
|
|
}
|
|
self->enemy = NULL;
|
|
self->nextthink = level.time + FRAMETIME * 5;
|
|
|
|
if ( self->attackDebounceTime > level.time )
|
|
{
|
|
// don't fire if there is an attack debounce
|
|
return;
|
|
}
|
|
|
|
//Setup the bbox to search in
|
|
for ( int i = 0; i < 3; i++ )
|
|
{
|
|
mins[i] = self->currentOrigin[i] - 512;
|
|
maxs[i] = self->currentOrigin[i] + 512;
|
|
}
|
|
|
|
//Get a number of entities in a given space
|
|
numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, 1024 );
|
|
|
|
for ( i = 0; i < numEnts; i++ )
|
|
{
|
|
//Don't consider self
|
|
if ( radiusEnts[i] == self )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Must be valid...
|
|
//Must be a valid pointer
|
|
if ( radiusEnts[i] == NULL )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Must not be deleted
|
|
if ( radiusEnts[i]->inuse == qfalse )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Must be an NPC
|
|
if ( radiusEnts[i]->client == NULL )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Can't be on the same team
|
|
if ( radiusEnts[i]->client->playerTeam == self->noDamageTeam )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Can't be on the same team
|
|
if ( radiusEnts[i]->noDamageTeam == self->noDamageTeam )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Must be alive
|
|
if ( radiusEnts[i]->health <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//In case they're in notarget mode
|
|
if ( radiusEnts[i]->flags & FL_NOTARGET )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( radiusEnts[i]->s.number == 0 )
|
|
{//the player
|
|
//Must be visible
|
|
CalcEntitySpot( radiusEnts[i], SPOT_HEAD, end );
|
|
CalcEntitySpot( radiusEnts[i], SPOT_ORIGIN, org );
|
|
VectorSubtract(org, end, diff);
|
|
VectorSet( end, end[0]+(diff[0]*Q_flrand(0, 1)), end[1]+(diff[1]*Q_flrand(0, 1)), end[2]+(diff[2]*Q_flrand(0, 1)) );
|
|
if ( !gi.inPVS( self->currentOrigin, end ) )
|
|
{
|
|
continue;
|
|
}
|
|
gi.trace ( &tr, self->currentOrigin, NULL, NULL, end, self->s.number, CONTENTS_SOLID|CONTENTS_SHOTCLIP|CONTENTS_BODY );
|
|
if ( tr.fraction != 1.0 && tr.entityNum != radiusEnts[i]->s.number )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( Q_irand(0, 100) < 75 )
|
|
{
|
|
//First try the player
|
|
G_SetEnemy( self, radiusEnts[i] );
|
|
VectorCopy( end, shotEnd );
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
distance = DistanceSquared( self->currentOrigin, radiusEnts[i]->currentOrigin );
|
|
//Found one closer to us
|
|
if ( distance < nearestDist )
|
|
{
|
|
CalcEntitySpot( radiusEnts[i], SPOT_HEAD, end );
|
|
CalcEntitySpot( radiusEnts[i], SPOT_ORIGIN, org );
|
|
VectorSubtract(org, end, diff);
|
|
VectorSet( end, end[0]+(diff[0]*Q_flrand(0, 1)), end[1]+(diff[1]*Q_flrand(0, 1)), end[2]+(diff[2]*Q_flrand(0, 1)) );
|
|
if ( !gi.inPVS( self->currentOrigin, end ) )
|
|
{
|
|
continue;
|
|
}
|
|
gi.trace ( &tr, self->currentOrigin, NULL, NULL, end, self->s.number, CONTENTS_SOLID|CONTENTS_SHOTCLIP|CONTENTS_BODY );
|
|
if ( tr.fraction != 1.0 && tr.entityNum != radiusEnts[i]->s.number )
|
|
{
|
|
continue;
|
|
}
|
|
G_SetEnemy(self, radiusEnts[i]);
|
|
VectorCopy( end, shotEnd );
|
|
nearestDist = distance;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( self->enemy )
|
|
{
|
|
self->attackDebounceTime = level.time + Q_irand(2000, 4000);
|
|
//fire!
|
|
//NOTE: cut and pased from FireStasisGuyAttack
|
|
gentity_t *bolt;
|
|
vec3_t dir;
|
|
|
|
VectorSubtract( shotEnd, self->currentOrigin, dir );
|
|
VectorNormalize (dir);
|
|
|
|
bolt = G_Spawn();
|
|
bolt->classname = "stasis_alien_proj";
|
|
|
|
bolt->nextthink = level.time + 10000;
|
|
bolt->e_ThinkFunc = thinkF_G_FreeEntity;
|
|
|
|
bolt->s.eType = ET_MISSILE;
|
|
bolt->svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
bolt->s.weapon = WP_STASIS_ATTACK;
|
|
bolt->owner = self->owner;
|
|
bolt->damage = 3 + (g_spskill->integer * 2);//tweak?
|
|
bolt->dflags = 0;
|
|
bolt->splashDamage = 0;
|
|
bolt->splashRadius = 0;
|
|
bolt->methodOfDeath = MOD_STASIS;
|
|
bolt->clipmask = MASK_SHOT;
|
|
|
|
bolt->s.pos.trType = TR_LINEAR;
|
|
bolt->s.pos.trTime = level.time;
|
|
VectorCopy( self->currentOrigin, bolt->s.pos.trBase );
|
|
|
|
VectorScale( dir, 550, bolt->s.pos.trDelta );//tweak?
|
|
|
|
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
|
|
VectorCopy( self->currentOrigin, bolt->currentOrigin );
|
|
|
|
G_Sound( self, G_SoundIndex( "sound/weapons/stasis_alien/fire.wav" ) );
|
|
}
|
|
}
|
|
|
|
void stasis_shooter_toggle( gentity_t *self, gentity_t *other, gentity_t *activator )
|
|
{
|
|
self->count ^= 1;
|
|
if ( self->count )
|
|
{//active
|
|
self->e_ThinkFunc = thinkF_stasis_shooter_active;
|
|
self->nextthink = level.time + Q_irand( 0, 4000 );
|
|
|
|
if ( !self->owner )
|
|
{
|
|
gentity_t *owner = NULL;
|
|
while( NULL != (owner = G_Find( owner, FOFS(target), self->targetname )))
|
|
{
|
|
if ( Q_stricmp("misc_model_breakable", owner->classname) == 0 )
|
|
{
|
|
self->owner = owner;
|
|
self->owner->svFlags |= SVF_NONNPC_ENEMY;
|
|
self->owner->noDamageTeam = self->noDamageTeam;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ( !self->owner )
|
|
{
|
|
Com_Error( ERR_DROP, "misc_stasis_shooter %s at %s must be directly targetted by a misc_model_breakable!\n", self->targetname, vtos(self->currentOrigin) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self->e_ThinkFunc = thinkF_NULL;
|
|
}
|
|
}
|
|
|
|
/*QUAKED misc_stasis_shooter (1 0.5 0) (-4 -4 -4) (4 4 4)
|
|
Starts inactive.
|
|
|
|
When active, will fire at enemies
|
|
|
|
Using it toggles it active/inactive
|
|
|
|
targetname - must be directly targetted by a misc_model_breakable
|
|
*/
|
|
void SP_misc_stasis_shooter (gentity_t *self)
|
|
{
|
|
G_SetOrigin( self, self->s.origin );
|
|
// Now register the weapon
|
|
RegisterItem( FindItemForWeapon( WP_STASIS_ATTACK ) );
|
|
G_SoundIndex( "sound/weapons/stasis_alien/fire.wav" );
|
|
G_SoundIndex( "sound/weapons/stasis_alien/hit_wall.wav" );
|
|
self->e_UseFunc = useF_stasis_shooter_toggle;
|
|
self->noDamageTeam = TEAM_STASIS;
|
|
}
|