jkxr/Projects/Android/jni/OpenJK/codeJK2/game/g_turret.cpp
Simon 12d117f679 Prevent cameras and turrets exiting if player moves their head too much
also.. for the secrity cameras, it will show the actual angles of the camera, but in mono.
2022-10-25 23:12:20 +01:00

2139 lines
No EOL
60 KiB
C++

/*
===========================================================================
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2013 - 2015, OpenJK contributors
This file is part of the OpenJK source code.
OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/
#include "g_headers.h"
#include "g_local.h"
#include "g_functions.h"
#include "b_local.h"
extern team_t TranslateTeamName( const char *name );
extern cvar_t *g_spskill;
void G_SetEnemy( gentity_t *self, gentity_t *enemy );
void finish_spawning_turret( gentity_t *base );
void ObjectDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath );
extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
#define ARM_ANGLE_RANGE 60
#define HEAD_ANGLE_RANGE 90
//------------------------------------------------------------------------------------------------------------
void TurretPain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, vec3_t point, int damage, int mod, int hitLoc )
//------------------------------------------------------------------------------------------------------------
{
vec3_t dir;
VectorSubtract( point, self->currentOrigin, dir );
VectorNormalize( dir );
if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT )
{
// DEMP2 makes the turret stop shooting for a bit..and does extra feedback
self->attackDebounceTime = level.time + 800 + Q_flrand(0.0f, 1.0f) * 500;
G_PlayEffect( "spark_exp_nosnd", point, dir );
}
G_PlayEffect( "spark_exp_nosnd", point, dir );
}
//------------------------------------------------------------------------------------------------------------
void turret_die ( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc )
//------------------------------------------------------------------------------------------------------------
{
vec3_t forward = { 0,0,-1 }, pos;
// Turn off the thinking of the base & use it's targets
self->e_ThinkFunc = thinkF_NULL;
self->e_UseFunc = useF_NULL;
// clear my data
self->e_DieFunc = dieF_NULL;
self->takedamage = qfalse;
self->health = 0;
self->s.loopSound = 0;
// hack the effect angle so that explode death can orient the effect properly
if ( self->spawnflags & 2 )
{
VectorSet( forward, 0, 0, 1 );
}
// VectorCopy( self->currentOrigin, self->s.pos.trBase );
if ( self->fxID > 0 )
{
VectorMA( self->currentOrigin, 12, forward, pos );
G_PlayEffect( self->fxID, pos, forward );
}
if ( self->splashDamage > 0 && self->splashRadius > 0 )
{
G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius, attacker, MOD_UNKNOWN );
}
if ( self->s.eFlags & EF_SHADER_ANIM )
{
self->s.frame = 1; // black
}
self->s.weapon = 0; // crosshair code uses this to mark crosshair red
if ( self->s.modelindex2 )
{
// switch to damage model if we should
self->s.modelindex = self->s.modelindex2;
VectorCopy( self->currentAngles, self->s.apos.trBase );
VectorClear( self->s.apos.trDelta );
if ( self->target )
{
G_UseTargets( self, attacker );
}
}
else
{
ObjectDie( self, inflictor, attacker, damage, meansOfDeath );
}
}
#define START_DIS 15
//----------------------------------------------------------------
static void turret_fire ( gentity_t *ent, vec3_t start, vec3_t dir )
//----------------------------------------------------------------
{
vec3_t org;
gentity_t *bolt;
if ( gi.pointcontents( start, MASK_SHOT ))
{
return;
}
VectorMA( start, -START_DIS, dir, org ); // dumb....
G_PlayEffect( "blaster/muzzle_flash", org, dir );
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_BLASTER;
bolt->owner = ent;
bolt->damage = ent->damage;
bolt->dflags = DAMAGE_NO_KNOCKBACK | DAMAGE_HEAVY_WEAP_CLASS; // Don't push them around, or else we are constantly re-aiming
bolt->splashDamage = 0;
bolt->splashRadius = 0;
bolt->methodOfDeath = MOD_ENERGY;
bolt->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
bolt->trigger_formation = qfalse; // don't draw tail on first frame
VectorSet( bolt->maxs, 1.5, 1.5, 1.5 );
VectorScale( bolt->maxs, -1, bolt->mins );
bolt->s.pos.trType = TR_LINEAR;
bolt->s.pos.trTime = level.time;
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 )
//-----------------------------------------------------
{
// if it's time to fire and we have an enemy, then gun 'em down! pushDebounce time controls next fire time
if ( self->enemy && self->pushDebounceTime < level.time && self->attackDebounceTime < level.time )
{
// set up our next fire time
self->pushDebounceTime = level.time + self->wait;
vec3_t fwd, org;
mdxaBone_t boltMatrix;
// Getting the flash bolt here
gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel,
self->torsoBolt,
&boltMatrix, self->currentAngles, self->currentOrigin, (cg.time?cg.time:level.time),
NULL, self->s.modelScale );
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, fwd );
VectorMA( org, START_DIS, fwd, org );
turret_fire( self, org, fwd );
self->fly_sound_debounce_time = level.time;//used as lastShotTime
}
}
//-----------------------------------------------------
static void turret_aim( gentity_t *self )
//-----------------------------------------------------
{
vec3_t enemyDir, org, org2;
vec3_t desiredAngles, setAngle;
float diffYaw = 0.0f, diffPitch = 0.0f;
// move our gun base yaw to where we should be at this time....
EvaluateTrajectory( &self->s.apos, level.time, self->currentAngles );
self->currentAngles[YAW] = AngleNormalize360( self->currentAngles[YAW] );
self->speed = AngleNormalize360( self->speed );
if ( self->enemy )
{
// ...then we'll calculate what new aim adjustments we should attempt to make this frame
// Aim at enemy
if ( self->enemy->client )
{
VectorCopy( self->enemy->client->renderInfo.eyePoint, org );
}
else
{
VectorCopy( self->enemy->currentOrigin, org );
}
if ( self->spawnflags & 2 )
{
org[2] -= 15;
}
else
{
org[2] -= 5;
}
mdxaBone_t boltMatrix;
// Getting the "eye" here
gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel,
self->torsoBolt,
&boltMatrix, self->currentAngles, self->s.origin, (cg.time?cg.time:level.time),
NULL, self->s.modelScale );
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org2 );
VectorSubtract( org, org2, enemyDir );
vectoangles( enemyDir, desiredAngles );
diffYaw = AngleSubtract( self->currentAngles[YAW], desiredAngles[YAW] );
diffPitch = AngleSubtract( self->speed, desiredAngles[PITCH] );
}
else
{
// no enemy, so make us slowly sweep back and forth as if searching for a new one
// diffYaw = sin( level.time * 0.0001f + self->count ) * 5.0f; // don't do this for now since it can make it go into walls.
}
if ( diffYaw )
{
// cap max speed....
if ( fabs(diffYaw) > 14.0f )
{
diffYaw = ( diffYaw >= 0 ? 14.0f : -14.0f );
}
// ...then set up our desired yaw
VectorSet( setAngle, 0.0f, diffYaw, 0.0f );
VectorCopy( self->currentAngles, self->s.apos.trBase );
VectorScale( setAngle,- 5, self->s.apos.trDelta );
self->s.apos.trTime = level.time;
self->s.apos.trType = TR_LINEAR;
}
if ( diffPitch )
{
if ( fabs(diffPitch) > 3.0f )
{
// cap max speed
self->speed += (diffPitch > 0.0f) ? -3.0f : 3.0f;
}
else
{
// small enough, so just add half the diff so we smooth out the stopping
self->speed -= ( diffPitch );//desiredAngles[PITCH];
}
// Note that this is NOT interpolated, so it will be less smooth...On the other hand, it does use Ghoul2 to blend, so it may smooth it out a bit?
if ( self->spawnflags & 2 )
{
VectorSet( desiredAngles, self->speed, 0.0f, 0.0f );
}
else
{
VectorSet( desiredAngles, -self->speed, 0.0f, 0.0f );
}
gi.G2API_SetBoneAngles( &self->ghoul2[0], "Bone_body", desiredAngles,
BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 100, cg.time );
}
if ( diffYaw || diffPitch )
{
self->s.loopSound = G_SoundIndex( "sound/chars/turret/move.wav" );
}
else
{
self->s.loopSound = 0;
}
}
//-----------------------------------------------------
static void turret_turnoff( gentity_t *self )
//-----------------------------------------------------
{
if ( self->enemy == NULL )
{
// we don't need to turnoff
return;
}
// shut-down sound
G_Sound( self, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
// make turret play ping sound for 5 seconds
self->aimDebounceTime = level.time + 5000;
// Clear enemy
self->enemy = NULL;
}
//-----------------------------------------------------
static qboolean turret_find_enemies( gentity_t *self )
//-----------------------------------------------------
{
qboolean found = qfalse;
int count;
float bestDist = self->radius * self->radius;
float enemyDist;
vec3_t enemyDir, org, org2;
gentity_t *entity_list[MAX_GENTITIES], *target, *bestTarget = NULL;
if ( self->aimDebounceTime > level.time ) // time since we've been shut off
{
// We were active and alert, i.e. had an enemy in the last 3 secs
if ( self->painDebounceTime < level.time )
{
G_Sound(self, G_SoundIndex( "sound/chars/turret/ping.wav" ));
self->painDebounceTime = level.time + 1000;
}
}
VectorCopy( self->currentOrigin, org2 );
if ( self->spawnflags & 2 )
{
org2[2] += 20;
}
else
{
org2[2] -= 20;
}
count = G_RadiusList( org2, self->radius, self, qtrue, entity_list );
for ( int i = 0; i < count; i++ )
{
target = entity_list[i];
if ( !target->client )
{
// only attack clients
continue;
}
if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET ))
{
continue;
}
if ( target->client->playerTeam == self->noDamageTeam )
{
// A bot we don't want to shoot
continue;
}
if ( !gi.inPVS( org2, target->currentOrigin ))
{
continue;
}
VectorCopy( target->client->renderInfo.eyePoint, org );
if ( self->spawnflags & 2 )
{
org[2] -= 15;
}
else
{
org[2] += 5;
}
trace_t tr;
gi.trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
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 2 seconds-start-up sound
G_Sound( self, G_SoundIndex( "sound/chars/turret/startup.wav" ));
// Wind up turrets for a bit
self->attackDebounceTime = level.time + 1400;
}
bestTarget = target;
bestDist = enemyDist;
found = qtrue;
}
}
}
if ( found )
{
if ( !self->enemy )
{//just aquired one
AddSoundEvent( bestTarget, self->currentOrigin, 256, AEL_DISCOVERED );
AddSightEvent( bestTarget, self->currentOrigin, 512, AEL_DISCOVERED, 20 );
}
G_SetEnemy( self, bestTarget );
if ( VALIDSTRING( self->target2 ))
{
G_UseTargets2( self, self, self->target2 );
}
}
return found;
}
//-----------------------------------------------------
void turret_base_think( gentity_t *self )
//-----------------------------------------------------
{
qboolean turnOff = qtrue;
float enemyDist;
vec3_t enemyDir, org, org2;
self->nextthink = level.time + FRAMETIME;
if ( self->spawnflags & 1 )
{
// not turned on
turret_turnoff( self );
turret_aim( self );
// No target
self->flags |= FL_NOTARGET;
return;
}
else
{
// I'm all hot and bothered
self->flags &= ~FL_NOTARGET;
}
if ( !self->enemy )
{
if ( turret_find_enemies( self ))
{
turnOff = qfalse;
}
}
else
{
if ( self->enemy->health > 0 )
{
// enemy is alive
VectorSubtract( self->enemy->currentOrigin, self->currentOrigin, enemyDir );
enemyDist = VectorLengthSquared( enemyDir );
if ( enemyDist < self->radius * self->radius )
{
// was in valid radius
if ( gi.inPVS( self->currentOrigin, self->enemy->currentOrigin ) )
{
// Every now and again, check to see if we can even trace to the enemy
trace_t tr;
if ( self->enemy->client )
{
VectorCopy( self->enemy->client->renderInfo.eyePoint, org );
}
else
{
VectorCopy( self->enemy->currentOrigin, org );
}
VectorCopy( self->currentOrigin, org2 );
if ( self->spawnflags & 2 )
{
org2[2] += 10;
}
else
{
org2[2] -= 10;
}
gi.trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
if ( !tr.allsolid && !tr.startsolid && tr.entityNum == self->enemy->s.number )
{
turnOff = qfalse; // Can see our enemy
}
}
}
}
turret_head_think( self );
}
if ( turnOff )
{
if ( self->bounceCount < level.time ) // bounceCount is used to keep the thing from ping-ponging from on to off
{
turret_turnoff( self );
}
}
else
{
// keep our enemy for a minimum of 2 seconds from now
self->bounceCount = level.time + 2000 + Q_flrand(0.0f, 1.0f) * 150;
}
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);
if (( self->s.eFlags & EF_SHADER_ANIM ) && ( self->spawnflags & 1 )) // Start_Off
{
self->s.frame = 1; // black
}
else
{
self->s.frame = 0; // glow
}
}
/*QUAKED misc_turret (1 0 0) (-8 -8 -22) (8 8 0) START_OFF UPSIDE_DOWN
Turret that hangs from the ceiling, will aim and shoot at enemies
START_OFF - Starts off
UPSIDE_DOWN - make it rest on a surface/floor instead of hanging from the ceiling
radius - How far away an enemy can be for it to pick it up (default 512)
wait - Time between shots (default 150 ms)
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
target2 - What to use when it decides to start shooting at an enemy
team - team that is not targeted by and does not take damage from this turret
"player",
"enemy", (default)
"neutral"
*/
//-----------------------------------------------------
void SP_misc_turret( gentity_t *base )
//-----------------------------------------------------
{
base->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/turret_canon.glm" );
base->s.modelindex2 = G_ModelIndex( "models/map_objects/imp_mine/turret_damage.md3" );
base->playerModel = gi.G2API_InitGhoul2Model( base->ghoul2, "models/map_objects/imp_mine/turret_canon.glm", base->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
base->s.radius = 80.0f;
gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "Bone_body", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 );
base->torsoBolt = gi.G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash03" );
finish_spawning_turret( base );
if (( base->spawnflags & 1 )) // Start_Off
{
base->s.frame = 1; // black
}
else
{
base->s.frame = 0; // glow
}
base->s.eFlags |= EF_SHADER_ANIM;
}
//-----------------------------------------------------
void finish_spawning_turret( gentity_t *base )
{
vec3_t fwd;
if ( base->spawnflags & 2 )
{
base->s.angles[ROLL] += 180;
base->s.origin[2] -= 22.0f;
}
G_SetAngles( base, base->s.angles );
AngleVectors( base->currentAngles, fwd, NULL, NULL );
G_SetOrigin(base, base->s.origin);
base->noDamageTeam = TEAM_ENEMY;
base->s.eType = ET_GENERAL;
if ( base->team && base->team[0] )
{
base->noDamageTeam = TranslateTeamName( base->team );
base->team = NULL;
}
// Set up our explosion effect for the ExplodeDeath code....
base->fxID = G_EffectIndex( "turret/explode" );
G_EffectIndex( "spark_exp_nosnd" );
base->e_UseFunc = useF_turret_base_use;
base->e_PainFunc = painF_TurretPain;
// don't start working right away
base->e_ThinkFunc = thinkF_turret_base_think;
base->nextthink = level.time + FRAMETIME * 5;
// this is really the pitch angle.....
base->speed = 0;
// this is a random time offset for the no-enemy-search-around-mode
base->count = Q_flrand(0.0f, 1.0f) * 9000;
if ( !base->health )
{
base->health = 100;
}
// search radius
if ( !base->radius )
{
base->radius = 512;
}
// How quickly to fire
if ( !base->wait )
{
base->wait = 150 + Q_flrand(0.0f, 1.0f) * 55;
}
if ( !base->splashDamage )
{
base->splashDamage = 10;
}
if ( !base->splashRadius )
{
base->splashRadius = 25;
}
// how much damage each shot does
if ( !base->damage )
{
base->damage = 5;
}
if ( base->spawnflags & 2 )
{//upside-down, invert mins and maxe
VectorSet( base->maxs, 10.0f, 10.0f, 30.0f );
VectorSet( base->mins, -10.0f, -10.0f, 0.0f );
}
else
{
VectorSet( base->maxs, 10.0f, 10.0f, 0.0f );
VectorSet( base->mins, -10.0f, -10.0f, -30.0f );
}
// Precache moving sounds
G_SoundIndex( "sound/chars/turret/startup.wav" );
G_SoundIndex( "sound/chars/turret/shutdown.wav" );
G_SoundIndex( "sound/chars/turret/ping.wav" );
G_SoundIndex( "sound/chars/turret/move.wav" );
base->contents = CONTENTS_BODY|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_SHOTCLIP;
base->max_health = base->health;
base->takedamage = qtrue;
base->e_DieFunc = dieF_turret_die;
base->material = MAT_METAL;
base->svFlags |= SVF_NO_TELEPORT|SVF_NONNPC_ENEMY|SVF_SELF_ANIMATING;
// Register this so that we can use it for the missile effect
RegisterItem( FindItemForWeapon( WP_BLASTER ));
// But set us as a turret so that we can be identified as a turret
base->s.weapon = WP_TURRET;
gi.linkentity( base );
}
/*QUAKED misc_ns_turret (1 0 0) (-8 -8 -32) (8 8 29) START_OFF
NS turret that only hangs from the ceiling, will aim and shoot at enemies
START_OFF - Starts off
radius - How far away an enemy can be for it to pick it up (default 512)
wait - Time between shots (default 150 ms)
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 - team that is not targeted by and does not take damage from this turret
"player",
"enemy", (default)
"neutral"
*/
//-----------------------------------------------------
void SP_misc_ns_turret( gentity_t *base )
//-----------------------------------------------------
{
base->s.modelindex = G_ModelIndex( "models/map_objects/nar_shaddar/turret/turret.glm" );
base->s.modelindex2 = G_ModelIndex( "models/map_objects/imp_mine/turret_damage.md3" ); // FIXME!
base->playerModel = gi.G2API_InitGhoul2Model( base->ghoul2, "models/map_objects/nar_shaddar/turret/turret.glm", base->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
base->s.radius = 80.0f;
gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "Bone_body", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 );
base->torsoBolt = gi.G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash02" );
finish_spawning_turret( base );
}
//--------------------------------------
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, ENTITYNUM_NONE, MASK_SHOT, G2_NOCOLLIDE, 0 );//ignore
ent->fly_sound_debounce_time = level.time;//used as lastShotTime
// 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_UNKNOWN );
}
}
}
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/chars/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/chars/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/chars/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/chars/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/chars/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
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/chars/l_arm/fire.wav");
G_SoundIndex("sound/chars/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 + START_TIME_LINK_ENTS;
}
//--------------------------
// PERSONAL ASSAULT SENTRY
//--------------------------
#define PAS_DAMAGE 2
//-----------------------------------------------------------------------------
void pas_use( gentity_t *self, gentity_t *other, gentity_t *activator )
//-----------------------------------------------------------------------------
{
// Toggle on and off
self->spawnflags = (self->spawnflags ^ 1);
if ( self->spawnflags & 1 )
{
self->nextthink = 0; // turn off and do nothing
self->e_ThinkFunc = thinkF_NULL;
}
else
{
self->nextthink = level.time + 50;
self->e_ThinkFunc = thinkF_pas_think;
}
}
//----------------------------------------------------------------
void pas_fire( gentity_t *ent )
//----------------------------------------------------------------
{
vec3_t fwd, org;
mdxaBone_t boltMatrix;
// Getting the flash bolt here
gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel,
ent->torsoBolt,
&boltMatrix, ent->currentAngles, ent->s.origin, (cg.time?cg.time:level.time),
NULL, ent->s.modelScale );
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, fwd );
G_PlayEffect( "turret/muzzle_flash", org, fwd );
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_TURRET;
bolt->owner = ent;
bolt->damage = PAS_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_ENERGY;
bolt->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
VectorSet( bolt->maxs, 1, 1, 1 );
VectorScale( bolt->maxs, -1, bolt->mins );
bolt->s.pos.trType = TR_LINEAR;
bolt->s.pos.trTime = level.time; // move a bit on the very first frame
VectorCopy( org, bolt->s.pos.trBase );
VectorScale( fwd, 900, bolt->s.pos.trDelta );
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
VectorCopy( org, bolt->currentOrigin);
}
//-----------------------------------------------------
static qboolean pas_find_enemies( gentity_t *self )
//-----------------------------------------------------
{
qboolean found = qfalse;
int count;
float bestDist = self->radius * self->radius;
float enemyDist;
vec3_t enemyDir, org, org2;
gentity_t *entity_list[MAX_GENTITIES], *target;
if ( self->aimDebounceTime > level.time ) // time since we've been shut off
{
// We were active and alert, i.e. had an enemy in the last 3 secs
if ( self->painDebounceTime < level.time )
{
G_Sound(self, G_SoundIndex( "sound/chars/turret/ping.wav" ));
self->painDebounceTime = level.time + 1000;
}
}
mdxaBone_t boltMatrix;
// Getting the "eye" here
gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel,
self->torsoBolt,
&boltMatrix, self->currentAngles, self->s.origin, (cg.time?cg.time:level.time),
NULL, self->s.modelScale );
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org2 );
count = G_RadiusList( org2, self->radius, self, qtrue, entity_list );
for ( int i = 0; i < count; i++ )
{
target = entity_list[i];
if ( !target->client )
{
continue;
}
if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET ))
{
continue;
}
if ( target->client->playerTeam == self->noDamageTeam )
{
// A bot we don't want to shoot
continue;
}
if ( !gi.inPVS( org2, target->currentOrigin ))
{
continue;
}
if ( target->client )
{
VectorCopy( target->client->renderInfo.eyePoint, org );
org[2] -= 15;
}
else
{
VectorCopy( target->currentOrigin, org );
}
trace_t tr;
gi.trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
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 ( target->s.number ) // don't do this for the player
{
G_StartFlee( target, self, self->currentOrigin, AEL_DANGER, 3000, 5000 );
}
if ( enemyDist < bestDist )// all things equal, keep current
{
if ( self->attackDebounceTime + 2000 < level.time )
{
// We haven't fired or acquired an enemy in the last 2 seconds-start-up sound
G_Sound( self, G_SoundIndex( "sound/chars/turret/startup.wav" ));
// Wind up turrets for a bit
self->attackDebounceTime = level.time + 900 + Q_flrand(0.0f, 1.0f) * 200;
}
G_SetEnemy( self, target );
bestDist = enemyDist;
found = qtrue;
}
}
}
if ( found && VALIDSTRING( self->target2 ))
{
G_UseTargets2( self, self, self->target2 );
}
return found;
}
//---------------------------------
void pas_adjust_enemy( gentity_t *ent )
//---------------------------------
{
qboolean keep = qtrue;
if ( ent->enemy->health <= 0 )
{
keep = qfalse;
}
else// if ( Q_flrand(0.0f, 1.0f) > 0.5f )
{
// do a trace every now and then.
mdxaBone_t boltMatrix;
vec3_t org, org2;
// Getting the "eye" here
gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel,
ent->torsoBolt,
&boltMatrix, ent->currentAngles, ent->s.origin, (cg.time?cg.time:level.time),
NULL, ent->s.modelScale );
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org2 );
if ( ent->enemy->client )
{
VectorCopy( ent->enemy->client->renderInfo.eyePoint, org );
org[2] -= 15;
}
else
{
VectorCopy( ent->enemy->currentOrigin, org );
}
trace_t tr;
gi.trace( &tr, org2, NULL, NULL, org, ent->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
if ( tr.allsolid || tr.startsolid || tr.entityNum != ent->enemy->s.number )
{
// trace failed
keep = qfalse;
}
}
if ( keep )
{
ent->bounceCount = level.time + 500 + Q_flrand(0.0f, 1.0f) * 150;
}
else if ( ent->bounceCount < level.time ) // don't ping pong on and off
{
ent->enemy = NULL;
// shut-down sound
G_Sound( ent, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
// make turret play ping sound for 5 seconds
ent->aimDebounceTime = level.time + 5000;
}
}
//---------------------------------
void pas_think( gentity_t *ent )
//---------------------------------
{
if ( !ent->damage )
{
// let us do our animation, then we are good to go in terms of pounding the crap out of enemies.
ent->damage = 1;
gi.G2API_SetBoneAnimIndex( &ent->ghoul2[ent->playerModel], ent->rootBone, 0, 11, BONE_ANIM_OVERRIDE_FREEZE, 0.8f, cg.time, -1, -1 );
ent->nextthink = level.time + 1200;
return;
}
if ( !ent->count )
{
// turrets that have no ammo may as well do nothing
return;
}
ent->nextthink = level.time + FRAMETIME;
if ( ent->enemy )
{
// make sure that the enemy is still valid
pas_adjust_enemy( ent );
}
if ( !ent->enemy )
{
pas_find_enemies( ent );
}
qboolean moved = qfalse;
float diffYaw = 0.0f, diffPitch = 0.0f;
vec3_t enemyDir, org;
vec3_t frontAngles, backAngles;
vec3_t desiredAngles;
ent->speed = AngleNormalize360( ent->speed );
ent->random = AngleNormalize360( ent->random );
if ( ent->enemy )
{
// ...then we'll calculate what new aim adjustments we should attempt to make this frame
// Aim at enemy
if ( ent->enemy->client )
{
VectorCopy( ent->enemy->client->renderInfo.eyePoint, org );
org[2] -= 40;
}
else
{
VectorCopy( ent->enemy->currentOrigin, org );
}
VectorSubtract( org, ent->currentOrigin, enemyDir );
vectoangles( enemyDir, desiredAngles );
diffYaw = AngleSubtract( ent->speed, desiredAngles[YAW] );
diffPitch = AngleSubtract( ent->random, desiredAngles[PITCH] );
}
else
{
// no enemy, so make us slowly sweep back and forth as if searching for a new one
diffYaw = sin( level.time * 0.0001f + ent->count ) * 2.0f;
}
if ( fabs(diffYaw) > 0.25f )
{
moved = qtrue;
if ( fabs(diffYaw) > 10.0f )
{
// cap max speed
ent->speed += (diffYaw > 0.0f) ? -10.0f : 10.0f;
}
else
{
// small enough
ent->speed -= diffYaw;
}
}
if ( fabs(diffPitch) > 0.25f )
{
moved = qtrue;
if ( fabs(diffPitch) > 4.0f )
{
// cap max speed
ent->random += (diffPitch > 0.0f) ? -4.0f : 4.0f;
}
else
{
// small enough
ent->random -= diffPitch;
}
}
// the bone axes are messed up, so hence some dumbness here
VectorSet( frontAngles, -ent->random, 0.0f, 0.0f );
VectorSet( backAngles, 0.0f, 0.0f, ent->speed - ent->s.angles[YAW] );
gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "bone_barrel", frontAngles,
BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, NEGATIVE_X, NULL,100,cg.time);
gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "bone_gback", frontAngles,
BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, NEGATIVE_X, NULL,100,cg.time);
gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "bone_hinge", backAngles,
BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL,100,cg.time);
if ( moved )
{
//ent->s.loopSound = G_SoundIndex( "sound/chars/turret/move.wav" );
}
else
{
ent->s.loopSound = 0;
}
if ( ent->enemy && ent->attackDebounceTime < level.time && Q_flrand(0.0f, 1.0f) > 0.3f )
{
ent->count--;
if ( ent->count )
{
pas_fire( ent );
ent->fly_sound_debounce_time = level.time;//used as lastShotTime
}
else
{
ent->nextthink = 0;
G_Sound( ent, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
}
}
}
/*QUAKED misc_sentry_turret (1 0 0) (-16 -16 0) (16 16 24) START_OFF RESERVED
personal assault sentry, like the ones you can carry in your inventory
RESERVED - do no use this flag for anything, does nothing..etc.
radius - How far away an enemy can be for it to pick it up (default 512)
count - number of shots before thing deactivates. -1 = infinite, default 150
health - How much damage it can take before exploding (default 50)
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
target - What to use when destroyed
target2 - What to use when it decides to fire at an enemy
team - team that does not take damage from this item
"player",
"enemy",
"neutral"
*/
//---------------------------------
void SP_PAS( gentity_t *base )
//---------------------------------
{
base->classname = "PAS";
G_SetOrigin( base, base->s.origin );
G_SetAngles( base, base->s.angles );
base->speed = base->s.angles[YAW];
base->s.modelindex = G_ModelIndex( "models/items/psgun.glm" );
base->playerModel = gi.G2API_InitGhoul2Model( base->ghoul2, "models/items/psgun.glm", base->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
base->s.radius = 30.0f;
VectorSet( base->s.modelScale, 1.0f, 1.0f, 1.0f );
base->rootBone = gi.G2API_GetBoneIndex( &base->ghoul2[base->playerModel], "model_root", qtrue );
gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "bone_hinge", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 );
gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "bone_gback", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 );
gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "bone_barrel", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 );
base->torsoBolt = gi.G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash02" );
base->s.eType = ET_GENERAL;
if ( !base->radius )
{
base->radius = 512;
}
if ( base->count == 0 )
{
// give ammo
base->count = 150;
}
base->e_UseFunc = useF_pas_use;
base->damage = 0; // start animation flag
base->contents = CONTENTS_SHOTCLIP|CONTENTS_CORPSE;//for certain traces
VectorSet( base->mins, -8, -8, 0 );
VectorSet( base->maxs, 8, 8, 18 );
if ( !(base->spawnflags & 1 )) // START_OFF
{
base->nextthink = level.time + 1000; // we aren't starting off, so start working right away
base->e_ThinkFunc = thinkF_pas_think;
}
// Set up our explosion effect for the ExplodeDeath code....
base->fxID = G_EffectIndex( "turret/explode" );
G_EffectIndex( "spark_exp_nosnd" );
if ( !base->health )
{
base->health = 50;
}
base->max_health = base->health;
base->takedamage = qtrue;
base->e_PainFunc = painF_TurretPain;
base->e_DieFunc = dieF_turret_die;
// hack this flag on so that when it calls the turret die code, it will orient the effect up
// HACK
//--------------------------------------
base->spawnflags |= 2;
// Use this for our missile effect
RegisterItem( FindItemForWeapon( WP_TURRET ));
base->s.weapon = WP_TURRET;
base->svFlags |= SVF_NONNPC_ENEMY;
base->noDamageTeam = TEAM_NEUTRAL;
if ( base->team && base->team[0] )
{
base->noDamageTeam = TranslateTeamName( base->team );
base->team = NULL;
}
gi.linkentity( base );
}
//------------------------------------------------------------------------
qboolean place_portable_assault_sentry( gentity_t *self, vec3_t origin, vec3_t angs )
//------------------------------------------------------------------------
{
vec3_t fwd, pos;
vec3_t mins, maxs;
trace_t tr;
gentity_t *pas;
VectorSet( maxs, 9, 9, 0 );
VectorScale( maxs, -1, mins );
angs[PITCH] = 0;
angs[ROLL] = 0;
AngleVectors( angs, fwd, NULL, NULL );
// and move a consistent distance away from us so we don't have the dumb thing spawning inside of us.
VectorMA( origin, 30, fwd, pos );
gi.trace( &tr, origin, NULL, NULL, pos, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
// find the ground
tr.endpos[2] += 20;
VectorCopy( tr.endpos, pos );
pos[2] -= 64;
gi.trace( &tr, tr.endpos, mins, maxs, pos, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
// check for a decent surface, meaning mostly flat...should probably also check surface parms so we don't set us down on lava or something.
if ( !tr.startsolid && !tr.allsolid && tr.fraction < 1.0f && tr.plane.normal[2] > 0.9f && tr.entityNum >= ENTITYNUM_WORLD )
{
// Then spawn us if it seems cool.
pas = G_Spawn();
if ( pas )
{
VectorCopy( tr.endpos, pas->s.origin );
SP_PAS( pas );
pas->contents |= CONTENTS_PLAYERCLIP; // player placed ones can block players but not npcs
pas->e_UseFunc = useF_NULL; // placeable ones never need to be used
// we don't hurt us or anyone who belongs to the same team as us.
if ( self->client )
{
pas->noDamageTeam = self->client->playerTeam;
}
G_Sound( self, G_SoundIndex( "sound/player/use_sentry" ));
pas->activator = self;
return qtrue;
}
}
return qfalse;
}
//-------------
// ION CANNON
//-------------
//----------------------------------------
void ion_cannon_think( gentity_t *self )
//----------------------------------------
{
if ( self->spawnflags & 2 )
{
if ( self->count )
{
// still have bursts left, so keep going
self->count--;
}
else
{
// done with burst, so wait delay amount, plus a random bit
self->nextthink = level.time + ( self->delay + Q_flrand(-1.0f, 1.0f) * self->random );
self->count = Q_irand(0,5); // 0-5 bursts
// Not firing this time
return;
}
}
if ( self->fxID )
{
vec3_t fwd, org;
mdxaBone_t boltMatrix;
// Getting the flash bolt here
gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel,
self->torsoBolt,
&boltMatrix, self->s.angles, self->s.origin, (cg.time?cg.time:level.time),
NULL, self->s.modelScale );
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, fwd );
G_PlayEffect( self->fxID, org, fwd );
}
if ( self->target2 )
{
// If we have a target2 fire it off in sync with our gun firing
G_UseTargets2( self, self, self->target2 );
}
gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, 0, 8, BONE_ANIM_OVERRIDE_FREEZE, 0.6f, cg.time, -1, -1 );
self->nextthink = level.time + self->wait + Q_flrand(-1.0f, 1.0f) * self->random;
}
//----------------------------------------------------------------------------------------------
void ion_cannon_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc )
//----------------------------------------------------------------------------------------------
{
vec3_t org;
// dead, so nuke the ghoul model and put in the damage md3 version
if ( self->playerModel >= 0 )
{
gi.G2API_RemoveGhoul2Model( self->ghoul2, self->playerModel );
}
self->s.modelindex = self->s.modelindex2;
self->s.modelindex2 = 0;
// Turn off the thinking of the base & use it's targets
self->e_ThinkFunc = thinkF_NULL;
self->e_UseFunc = useF_NULL;
if ( self->target )
{
G_UseTargets( self, attacker );
}
// clear my data
self->e_DieFunc = dieF_NULL;
self->takedamage = qfalse;
self->health = 0;
self->takedamage = qfalse;//stop chain reaction runaway loops
self->s.loopSound = 0;
// not solid anymore
self->contents = 0;
VectorCopy( self->currentOrigin, self->s.pos.trBase );
VectorCopy( self->currentOrigin, org );
org[2] += 20;
G_PlayEffect( "env/ion_cannon_explosion", org );
if ( self->splashDamage > 0 && self->splashRadius > 0 )
{
G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius,
attacker, MOD_UNKNOWN );
}
gi.linkentity( self );
}
//----------------------------------------------------------------------------
void ion_cannon_use( gentity_t *self, gentity_t *other, gentity_t *activator )
//----------------------------------------------------------------------------
{
// toggle
if ( self->e_ThinkFunc == thinkF_NULL )
{
// start thinking now
self->e_ThinkFunc = thinkF_ion_cannon_think;
self->nextthink = level.time + FRAMETIME; // fires right on being used
}
else
{
self->e_ThinkFunc = thinkF_NULL;
}
}
/*QUAKED misc_ion_cannon (1 0 0) (-140 -140 0) (140 140 320) START_OFF BURSTS SHIELDED
Huge ion cannon, like the ones at the rebel base on Hoth.
START_OFF - Starts off
BURSTS - adds more variation, shots come out in bursts
SHIELDED - cannon is shielded, any kind of shot bounces off.
wait - How fast it shoots (default 1500 ms between shots, can't be less than 500 ms)
random - milliseconds wait variation (default 400 ms...up to plus or minus .4 seconds)
delay - Number of milliseconds between bursts (default 6000 ms, can't be less than 1000 ms, only works when BURSTS checked)
health - default 2000
splashDamage - how much damage to do when it dies, must be greater than 0 to actually work
splashRadius - damage radius, must be greater than 0 to actually work
targetname - Toggles it on/off
target - What to use when destroyed
target2 - What to use when it fires a shot.
*/
//-----------------------------------------------------
void SP_misc_ion_cannon( gentity_t *base )
//-----------------------------------------------------
{
G_SetAngles( base, base->s.angles );
G_SetOrigin(base, base->s.origin);
base->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/ion_cannon.glm" );
base->playerModel = gi.G2API_InitGhoul2Model( base->ghoul2, "models/map_objects/imp_mine/ion_cannon.glm", base->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
base->s.radius = 320.0f;
VectorSet( base->s.modelScale, 1.0f, 1.0f, 1.0f );
base->rootBone = gi.G2API_GetBoneIndex( &base->ghoul2[base->playerModel], "model_root", qtrue );
base->torsoBolt = gi.G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash02" );
// register damage model
base->s.modelindex2 = G_ModelIndex( "models/map_objects/imp_mine/ion_cannon_damage.md3" );
base->e_UseFunc = useF_ion_cannon_use;
// How quickly to fire
if ( base->wait == 0.0f )
{
base->wait = 1500.0f;
}
else if ( base->wait < 500.0f )
{
base->wait = 500.0f;
}
if ( base->random == 0.0f )
{
base->random = 400.0f;
}
if ( base->delay == 0 )
{
base->delay = 6000;
}
else if ( base->delay < 1000 )
{
base->delay = 1000;
}
// we only take damage from a heavy weapon class missile
base->flags |= FL_DMG_BY_HEAVY_WEAP_ONLY;
if ( base->spawnflags & 4 )//shielded
{
base->flags |= FL_SHIELDED; //technically, this would only take damage from a lightsaber, but the other flag just above would cancel that out too.
}
G_SpawnInt( "health", "2000", &base->health );
base->e_DieFunc = dieF_ion_cannon_die;
base->takedamage = qtrue;
// Start Off?
if ( base->spawnflags & 1 )
{
base->e_ThinkFunc = thinkF_NULL;
}
else
{
// start thinking now, otherwise, we'll wait until we are used
base->e_ThinkFunc = thinkF_ion_cannon_think;
base->nextthink = level.time + base->wait + Q_flrand(-1.0f, 1.0f) * base->random;
}
// Bursts?
if ( base->spawnflags & 2 )
{
base->count = Q_irand(0,5); // 0-5 bursts
}
// precache
base->fxID = G_EffectIndex( "env/ion_cannon" );
// Set up our explosion effect for the ExplodeDeath code....
G_EffectIndex( "env/ion_cannon_explosion" );
base->contents = CONTENTS_BODY;
VectorSet( base->mins, -141.0f, -148.0f, 0.0f );
VectorSet( base->maxs, 142.0f, 135.0f, 245.0f );
gi.linkentity( base );
}
//-----------------------------------------------------
void spotlight_use( gentity_t *self, gentity_t *other, gentity_t *activator )
{
if ( self->e_ThinkFunc == thinkF_NULL )
{
// start thinking now, otherwise, we'll wait until we are used
self->e_ThinkFunc = thinkF_spotlight_think;
self->nextthink = level.time + FRAMETIME;
}
else
{
self->e_ThinkFunc = thinkF_NULL;
self->s.eFlags &= ~EF_ALT_FIRING;
}
}
//-----------------------------------------------------
void spotlight_think( gentity_t *ent )
{
vec3_t dir, end;
trace_t tr;
// dumb hack flag so that we can draw an interpolated light cone cgame side.
ent->s.eFlags |= EF_ALT_FIRING;
VectorSubtract( ent->enemy->currentOrigin, ent->currentOrigin, dir );
VectorNormalize( dir );
vectoangles( dir, ent->s.apos.trBase );
ent->s.apos.trType = TR_INTERPOLATE;
VectorMA( ent->currentOrigin, 2048, dir, end ); // just pick some max trace distance
gi.trace( &tr, ent->currentOrigin, vec3_origin, vec3_origin, end, ent->s.number, CONTENTS_SOLID, G2_NOCOLLIDE, 0 );
ent->radius = tr.fraction * 2048.0f;
if ( tr.fraction < 1 )
{
if ( DistanceSquared( tr.endpos, g_entities[0].currentOrigin ) < 140 * 140 )
{
// hit player--use target2
G_UseTargets2( ent, &g_entities[0], ent->target2 );
#ifndef FINAL_BUILD
if ( g_developer->integer == PRINT_DEVELOPER )
{
Com_Printf( S_COLOR_MAGENTA "Spotlight hit player at time: %d!!!\n", level.time );
}
#endif
}
}
ent->nextthink = level.time + 50;
}
//-----------------------------------------------------
void spotlight_link( gentity_t *ent )
{
gentity_t *target = 0;
target = G_Find( target, FOFS(targetname), ent->target );
if ( !target )
{
Com_Printf( S_COLOR_RED "ERROR: spotlight_link: bogus target %s\n", ent->target );
G_FreeEntity( ent );
return;
}
ent->enemy = target;
// Start Off?
if ( ent->spawnflags & 1 )
{
ent->e_ThinkFunc = thinkF_NULL;
ent->s.eFlags &= ~EF_ALT_FIRING;
}
else
{
// start thinking now, otherwise, we'll wait until we are used
ent->e_ThinkFunc = thinkF_spotlight_think;
ent->nextthink = level.time + FRAMETIME;
}
}
/*QUAKED misc_spotlight (1 0 0) (-10 -10 0) (10 10 10) START_OFF
#MODELNAME="models/map_objects/imp_mine/spotlight.md3"
Search spotlight that must be targeted at a func_train or other entity
Uses its target2 when it detects the player
START_OFF - Starts off
targetname - Toggles it on/off
target - What to point at
target2 - What to use when detects player
*/
//-----------------------------------------------------
void SP_misc_spotlight( gentity_t *base )
//-----------------------------------------------------
{
if ( !base->target )
{
Com_Printf( S_COLOR_RED "ERROR: misc_spotlight must have a target\n" );
G_FreeEntity( base );
return;
}
G_SetAngles( base, base->s.angles );
G_SetOrigin( base, base->s.origin );
base->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/spotlight.md3" );
G_SpawnInt( "health", "300", &base->health );
// Set up our lightcone effect, though we will have it draw cgame side so it looks better
G_EffectIndex( "env/light_cone" );
base->contents = CONTENTS_BODY;
base->e_UseFunc = useF_spotlight_use;
// the thing we need to target may not have spawned yet, so try back in a bit
base->e_ThinkFunc = thinkF_spotlight_link;
base->nextthink = level.time + 100;
gi.linkentity( base );
}
/*QUAKED misc_panel_turret (0 0 1) (-8 -8 -12) (8 8 16) HEALTH
Creates a turret that, when the player uses a panel, takes control of this turret and adopts the turret view
HEALTH - gun turret has health and displays a hud with its current health
"target" - thing to use when player enters the turret view
"target2" - thing to use when player leaves the turret view
"target3" - thing to use when it dies.
radius - the max yaw range in degrees, (default 90) which means you can move 90 degrees on either side of the start angles.
random - the max pitch range in degrees, (default 60) which means you can move 60 degrees above or below the start angles.
delay - time between shots, in milliseconds (default 200).
damage - amount of damage shots do, (default 50).
speed - missile speed, (default 3000)
heatlh - how much heatlh the thing has, (default 200) only works if HEALTH is checked, otherwise it can't be destroyed.
*/
extern gentity_t *player;
extern qboolean G_ClearViewEntity( gentity_t *ent );
extern void G_SetViewEntity( gentity_t *self, gentity_t *viewEntity );
extern gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire = qfalse );
void panel_turret_shoot( gentity_t *self, vec3_t org, vec3_t dir)
{
gentity_t *missile = CreateMissile( org, dir, self->speed, 10000, self );
missile->classname = "b_proj";
missile->s.weapon = WP_EMPLACED_GUN;
VectorSet( missile->maxs, 7, 7, 7 );
VectorScale( missile->maxs, -1, missile->mins );
missile->bounceCount = 0;
missile->damage = self->damage;
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
missile->methodOfDeath = MOD_ENERGY;
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
G_SoundOnEnt( self, CHAN_AUTO, "sound/movers/objects/ladygun_fire" );
VectorMA( org, 32, dir, org );
org[2] -= 5;
G_PlayEffect( "emplaced/muzzle_flash", org, dir );
}
//-----------------------------------------
void misc_panel_turret_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod, int dFlags, int hitLoc )
{
if ( self->target3 )
{
G_UseTargets2( self, player, self->target3 );
}
// FIXME: might need some other kind of logic or functionality in here??
G_UseTargets2( self, player, self->target2 );
G_ClearViewEntity( player );
cg.overrides.active &= ~CG_OVERRIDE_FOV;
cg.overrides.fov = 0;
}
//-----------------------------------------
void panel_turret_think( gentity_t *self )
{
// Ensure that I am the viewEntity
if ( player && player->client && player->client->ps.viewEntity == self->s.number )
{
usercmd_t *ucmd = &player->client->usercmd;
// We are the viewEnt so update our new viewangles based on the sum of our start angles and the ucmd angles
for ( int i = 0; i < 3; i++ )
{
// convert our base angle to a short, add with the usercmd.angle ( a short ), then switch use back to a real angle
self->s.apos.trBase[i] = AngleNormalize180( SHORT2ANGLE( ucmd->angles[i] + ANGLE2SHORT( self->s.angles[i] ) + self->pos3[i] ));
}
// Only clamp if we have a PITCH clamp
if ( self->random != 0.0f )
// Angle clamping -- PITCH
{
if ( self->s.apos.trBase[PITCH] > self->random ) // random is PITCH
{
self->pos3[PITCH] += ANGLE2SHORT( AngleNormalize180( self->random - self->s.apos.trBase[PITCH]));
self->s.apos.trBase[PITCH] = self->random;
}
else if ( self->s.apos.trBase[PITCH] < -self->random )
{
self->pos3[PITCH] -= ANGLE2SHORT( AngleNormalize180( self->random + self->s.apos.trBase[PITCH]));
self->s.apos.trBase[PITCH] = -self->random;
}
}
// Only clamp if we have a YAW clamp
if ( self->radius != 0.0f )
{
float yawDif = AngleSubtract( self->s.apos.trBase[YAW], self->s.angles[YAW] );
// Angle clamping -- YAW
if ( yawDif > self->radius ) // radius is YAW
{
self->pos3[YAW] += ANGLE2SHORT( self->radius - yawDif );
self->s.apos.trBase[YAW] = AngleNormalize180( self->s.angles[YAW] + self->radius );
}
else if ( yawDif < -self->radius ) // radius is YAW
{
self->pos3[YAW] -= ANGLE2SHORT( self->radius + yawDif );
self->s.apos.trBase[YAW] = AngleNormalize180( self->s.angles[YAW] - self->radius );
}
}
// Let cgame interpolation smooth out the angle changes
self->s.apos.trType = TR_INTERPOLATE;
self->s.pos.trType = TR_INTERPOLATE; // not really moving, but this fixes an interpolation bug in cg_ents.
// Check for backing out of turret
if ( ( self->useDebounceTime < level.time ) && ((ucmd->buttons & BUTTON_BLOCKING) || (ucmd->buttons & BUTTON_USE) || ucmd->upmove) ) // || ucmd->forwardmove || ucmd->rightmove) )
{
self->useDebounceTime = level.time + 200;
G_UseTargets2( self, player, self->target2 );
G_ClearViewEntity( player );
G_Sound( player, self->soundPos2 );
cg.overrides.active &= ~CG_OVERRIDE_FOV;
cg.overrides.fov = 0;
if ( ucmd->upmove > 0 )
{//stop player from doing anything for a half second after
player->aimDebounceTime = level.time + 500;
}
// can be drawn
// self->s.eFlags &= ~EF_NODRAW;
}
else
{
// don't draw me when being looked through
// self->s.eFlags |= EF_NODRAW;
// self->s.modelindex = 0;
// we only need to think when we are being used
self->nextthink = level.time + 50;
cg.overrides.active |= CG_OVERRIDE_FOV;
cg.overrides.fov = 50;
}
if ( ucmd->buttons & BUTTON_ATTACK || ucmd->buttons & BUTTON_ALT_ATTACK )
{
if ( self->attackDebounceTime < level.time )
{
vec3_t dir, pt;
AngleVectors( self->s.apos.trBase, dir, NULL, NULL );
VectorCopy( self->currentOrigin, pt );
pt[2] -= 4;
panel_turret_shoot( self, pt, dir );
self->attackDebounceTime = level.time + self->delay;
}
}
}
}
//-----------------------------------------
void panel_turret_use( gentity_t *self, gentity_t *other, gentity_t *activator )
{
// really only usable by the player
if ( !activator || !activator->client || activator->s.number )
{
return;
}
if ( self->useDebounceTime > level.time )
{
// can't use it again right away.
return;
}
if ( self->spawnflags & 1 ) // health...presumably the lady luck gun
{
G_Sound( self, G_SoundIndex( "sound/movers/objects/ladygun_on" ));
}
self->useDebounceTime = level.time + 200;
// Compensating for the difference between the players view at the time of use and the start angles that the gun object has
self->pos3[PITCH] = -activator->client->usercmd.angles[PITCH];
self->pos3[YAW] = -activator->client->usercmd.angles[YAW];
self->pos3[ROLL] = 0;
// set me as view entity
G_UseTargets2( self, activator, self->target );
G_SetViewEntity( activator, self );
G_Sound( activator, self->soundPos1 );
self->e_ThinkFunc = thinkF_panel_turret_think;
// panel_turret_think( self );
self->nextthink = level.time + 150;
}
//-----------------------------------------
void SP_misc_panel_turret( gentity_t *self )
{
G_SpawnFloat( "radius", "90", &self->radius ); // yaw
G_SpawnFloat( "random", "60", &self->random ); // pitch
G_SpawnFloat( "speed" , "3000", &self->speed );
G_SpawnInt( "delay", "200", &self->delay );
G_SpawnInt( "damage", "50", &self->damage );
VectorSet( self->pos3, 0.0f, 0.0f, 0.0f );
if ( self->spawnflags & 1 ) // heatlh
{
self->takedamage = qtrue;
self->contents = CONTENTS_SHOTCLIP;
G_SpawnInt( "health", "200", &self->health );
self->max_health = self->health;
self->dflags |= DAMAGE_CUSTOM_HUD; // dumb, but we draw a custom hud
G_SoundIndex( "sound/movers/objects/ladygun_on" );
}
self->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/ladyluck_gun.md3" );
self->soundPos1 = G_SoundIndex( "sound/movers/camera_on.mp3" );
self->soundPos2 = G_SoundIndex( "sound/movers/camera_off.mp3" );
G_SoundIndex( "sound/movers/objects/ladygun_fire" );
G_SetOrigin( self, self->s.origin );
G_SetAngles( self, self->s.angles );
VectorSet( self->mins, -8, -8, -12 );
VectorSet( self->maxs, 8, 8, 0 );
self->contents = CONTENTS_SOLID;
self->s.weapon = WP_TURRET;
RegisterItem( FindItemForWeapon( WP_EMPLACED_GUN ));
gi.linkentity( self );
self->e_UseFunc = useF_panel_turret_use;
self->e_DieFunc = dieF_misc_panel_turret_die;
}