jkxr/Projects/Android/jni/OpenJK/codeJK2/game/g_turret.cpp
Simon 4597b03873 Initial Commit
Opens in Android Studio but haven't even tried to build it yet (it won't.. I know that much!)
2022-09-18 16:37:21 +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->forwardmove || ucmd->rightmove || ucmd->upmove) )
{
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;
}