973 lines
21 KiB
C++
973 lines
21 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Logfile:: /Quake 2 Engine/Sin/code/game/thrall.cpp $
|
|
// $Revision:: 12 $
|
|
// $Author:: Aldie $
|
|
// $Date:: 11/09/98 12:58a $
|
|
//
|
|
// Copyright (C) 1998 by Ritual Entertainment, Inc.
|
|
// All rights reserved.
|
|
//
|
|
// This source is may not be distributed and/or modified without
|
|
// expressly written permission by Ritual Entertainment, Inc.
|
|
//
|
|
// $Log:: /Quake 2 Engine/Sin/code/game/thrall.cpp $
|
|
//
|
|
// 12 11/09/98 12:58a Aldie
|
|
// Parentmode for death sequence
|
|
//
|
|
// 11 11/08/98 8:30p Aldie
|
|
// Added gibfest for thrall
|
|
//
|
|
// 10 10/27/98 10:17p Jimdose
|
|
// better aiming by walls
|
|
// more damage from pulse
|
|
// precached models
|
|
//
|
|
// 9 10/27/98 5:22p Jimdose
|
|
// tweaked him
|
|
//
|
|
// 8 10/27/98 5:55a Jimdose
|
|
// added Chatter
|
|
//
|
|
// 7 10/27/98 4:55a Jimdose
|
|
// NULLed out EV_FadeOut response so that Thrall sticks around
|
|
//
|
|
// 6 10/27/98 3:52a Jimdose
|
|
// got pulse weapon working
|
|
//
|
|
// 5 10/26/98 2:50p Aldie
|
|
// Fixed a bug with checking of NULL owners
|
|
//
|
|
// 4 10/24/98 3:33a Jimdose
|
|
// changed refrences to origin to worldorigin
|
|
//
|
|
// 3 10/24/98 12:51a Aldie
|
|
// Fixed init bug
|
|
//
|
|
// 2 10/23/98 5:37a Jimdose
|
|
// Created file
|
|
//
|
|
// 1 10/22/98 10:19p Jimdose
|
|
//
|
|
// DESCRIPTION:
|
|
// ThrallMaster
|
|
//
|
|
|
|
#include "g_local.h"
|
|
#include "actor.h"
|
|
#include "thrall.h"
|
|
#include "explosion.h"
|
|
#include "vehicle.h"
|
|
#include "object.h"
|
|
#include "gibs.h"
|
|
|
|
#define DRUNKMISSILE_SPEED 1000.0f
|
|
|
|
Event EV_ThrallMaster_FirePulse( "firepulse" );
|
|
Event EV_ThrallMaster_FireRockets( "firerockets" );
|
|
Event EV_ThrallMaster_GibFest( "gibfest" );
|
|
|
|
CLASS_DECLARATION( Actor, ThrallMaster, "boss_thrallmaster" );
|
|
|
|
ResponseDef ThrallMaster::Responses[] =
|
|
{
|
|
{ &EV_ThrallMaster_FirePulse, ( Response )ThrallMaster::FirePulse },
|
|
{ &EV_ThrallMaster_FireRockets, ( Response )ThrallMaster::FireRockets },
|
|
{ &EV_Sentient_WeaponUse, ( Response )ThrallMaster::WeaponUse },
|
|
{ &EV_ThrallMaster_GibFest, ( Response )ThrallMaster::GibFest },
|
|
{ &EV_FadeOut, NULL },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
ThrallMaster::ThrallMaster()
|
|
{
|
|
modelIndex( "sprites/thrallpulse.spr" );
|
|
modelIndex( "view_genbullet.def" );
|
|
modelIndex( "trocket.def" );
|
|
modelIndex( "thrallfire.def" );
|
|
|
|
setModel( "thrall.def" );
|
|
weaponmode = PRIMARY;
|
|
gunbone = "gun";
|
|
flags |= FL_NOION;
|
|
}
|
|
|
|
Vector ThrallMaster::GunPosition
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
vec3_t trans[ 3 ];
|
|
vec3_t orient;
|
|
int groupindex;
|
|
int tri_num;
|
|
Vector offset = vec_zero;
|
|
Vector result;
|
|
|
|
// get the gun position of the actor
|
|
if ( !gi.GetBoneInfo( edict->s.modelindex, gunbone.c_str(), &groupindex, &tri_num, orient ) )
|
|
{
|
|
// Gun doesn't have a barrel, just return the default
|
|
return worldorigin + gunoffset;
|
|
}
|
|
|
|
gi.GetBoneTransform( edict->s.modelindex, groupindex, tri_num, orient, edict->s.anim,
|
|
edict->s.frame, edict->s.scale, trans, offset.vec3() );
|
|
|
|
MatrixTransformVector( offset.vec3(), orientation, result.vec3() );
|
|
result += worldorigin;
|
|
|
|
return result;
|
|
}
|
|
|
|
qboolean ThrallMaster::CanShootFrom
|
|
(
|
|
Vector pos,
|
|
Entity *ent,
|
|
qboolean usecurrentangles
|
|
)
|
|
|
|
{
|
|
int mask;
|
|
Vector delta;
|
|
Vector start;
|
|
Vector end;
|
|
float len;
|
|
trace_t trace;
|
|
Vehicle *v;
|
|
Entity *t;
|
|
Vector ang;
|
|
|
|
if ( !currentWeapon || !WithinDistance( ent, vision_distance ) )
|
|
{
|
|
if (!currentWeapon && !has_melee )
|
|
return false;
|
|
}
|
|
|
|
if ( usecurrentangles )
|
|
{
|
|
Vector dir;
|
|
|
|
start = pos + GunPosition() - worldorigin;
|
|
end = ent->centroid;
|
|
end.z += ( ent->absmax.z - ent->centroid.z ) * 0.75f;
|
|
delta = end - start;
|
|
ang = delta.toAngles();
|
|
ang.x = -ang.x;
|
|
ang.y = angles.y;
|
|
len = delta.length();
|
|
ang.AngleVectors( &dir, NULL, NULL );
|
|
dir *= len;
|
|
end = start + dir;
|
|
}
|
|
else
|
|
{
|
|
vec3_t trans[ 3 ];
|
|
vec3_t transtemp[ 3 ];
|
|
vec3_t orient;
|
|
int groupindex;
|
|
int tri_num;
|
|
Vector offset = vec_zero;
|
|
Vector result;
|
|
|
|
// endpos
|
|
end = ent->centroid;
|
|
end.z += ( ent->absmax.z - ent->centroid.z ) * 0.75f;
|
|
|
|
// get the gun position of the actor
|
|
if ( !gi.GetBoneInfo( edict->s.modelindex, gunbone.c_str(), &groupindex, &tri_num, orient ) )
|
|
{
|
|
// Gun doesn't have a barrel, just return the default
|
|
result = gunoffset;
|
|
}
|
|
else
|
|
{
|
|
Vector forward, right, up;
|
|
|
|
// get new forward vec
|
|
forward = end - worldorigin;
|
|
forward.z = 0;
|
|
forward.normalize();
|
|
forward.copyTo( trans[ 0 ] );
|
|
|
|
// new right vec
|
|
right.x = -forward.y;
|
|
right.y = forward.x;
|
|
right.z = 0;
|
|
right.copyTo( trans[ 1 ] );
|
|
|
|
// new up vec
|
|
up.x = 0;
|
|
up.y = 0;
|
|
up.z = 1;
|
|
up.copyTo( trans[ 2 ] );
|
|
|
|
gi.GetBoneTransform( edict->s.modelindex, groupindex, tri_num, orient, edict->s.anim,
|
|
edict->s.frame, edict->s.scale, transtemp, offset.vec3() );
|
|
|
|
MatrixTransformVector( offset.vec3(), trans, result.vec3() );
|
|
}
|
|
|
|
start = pos + result;
|
|
delta = end - start;
|
|
len = delta.length();
|
|
}
|
|
|
|
// check if we're too far away, or too close
|
|
if ( currentWeapon )
|
|
{
|
|
if ( ( len > attack_range ) || ( len > currentWeapon->GetMaxRange() ) || ( len < currentWeapon->GetMinRange() ) )
|
|
{
|
|
return false;
|
|
}
|
|
mask = MASK_SHOT;
|
|
}
|
|
else
|
|
{
|
|
if ( ( len > attack_range ) || ( len > melee_range ) )
|
|
{
|
|
return false;
|
|
}
|
|
mask = MASK_PROJECTILE;
|
|
}
|
|
|
|
// shoot past the guy we're shooting at
|
|
end += delta * 4;
|
|
|
|
// Check if he's visible
|
|
trace = G_Trace( start, vec_zero, vec_zero, end, this, mask, "Actor::CanShootFrom" );
|
|
if ( trace.startsolid )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If we hit the guy we wanted, then shoot
|
|
if ( trace.ent == ent->edict )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// if we hit a vehicle, check if the driver is someone we want to hit
|
|
t = trace.ent->entity;
|
|
if ( t && t->isSubclassOf( Vehicle ) )
|
|
{
|
|
v = ( Vehicle * )t;
|
|
if ( ( v->Driver() == ent ) || IsEnemy( v->Driver() ) )
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// If we hit someone else we don't like, then shoot
|
|
if ( IsEnemy( t ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// if we hit something breakable, check if shooting it will
|
|
// let us shoot someone.
|
|
if ( t->isSubclassOf( Shatter ) ||
|
|
t->isSubclassOf( Object ) ||
|
|
t->isSubclassOf( DamageThreshold ) ||
|
|
t->isSubclassOf( ScriptModel ) )
|
|
{
|
|
trace = G_Trace( Vector( trace.endpos ), vec_zero, vec_zero, end, t, mask, "Actor::CanShootFrom 2" );
|
|
if ( trace.startsolid )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If we hit the guy we wanted, then shoot
|
|
if ( trace.ent == ent->edict )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// If we hit someone else we don't like, then shoot
|
|
if ( IsEnemy( trace.ent->entity ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Forget it then
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ThrallMaster::Chatter
|
|
(
|
|
const char *snd,
|
|
float chance,
|
|
float volume,
|
|
int channel
|
|
)
|
|
|
|
{
|
|
if ( chattime > level.time )
|
|
{
|
|
return;
|
|
}
|
|
|
|
RandomSound( snd, volume, channel, ATTN_NONE );
|
|
|
|
chattime = level.time + 7 + G_Random( 5 );
|
|
}
|
|
|
|
void ThrallMaster::WeaponUse
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
Sentient::WeaponUse( ev );
|
|
if ( weaponmode == PRIMARY )
|
|
{
|
|
weaponmode = SECONDARY;
|
|
gunbone = "chest";
|
|
}
|
|
else
|
|
{
|
|
weaponmode = PRIMARY;
|
|
gunbone = "gun";
|
|
}
|
|
}
|
|
|
|
void ThrallMaster::GibFest
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
Vector pos;
|
|
Gib *gib1, *gib2;
|
|
|
|
if ( sv_gibs->value && !parentmode->value )
|
|
{
|
|
GetBone( "chest", &pos, NULL, NULL, NULL );
|
|
|
|
pos += worldorigin;
|
|
|
|
gib1 = new Gib( "gib1.def" );
|
|
gib1->setOrigin(pos);
|
|
gib1->worldorigin.copyTo(gib1->edict->s.old_origin);
|
|
gib1->SetVelocity( 1000 );
|
|
gib1->velocity *= 3;
|
|
gib1->edict->s.scale = 3.0;
|
|
gib1->fadesplat = false;
|
|
|
|
gib2 = new Gib( "gib2.def" );
|
|
gib2->setOrigin(pos);
|
|
gib2->worldorigin.copyTo(gib2->edict->s.old_origin);
|
|
gib2->SetVelocity( 1000 );
|
|
gib2->velocity *= 3;
|
|
gib2->edict->s.scale = 3.0;
|
|
gib2->fadesplat = false;
|
|
}
|
|
}
|
|
|
|
void ThrallMaster::FirePulse
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
if ( ( currentWeapon ) && currentWeapon->ReadyToFire() && currentWeapon->HasAmmo() )
|
|
{
|
|
weaponmode = SECONDARY;
|
|
currentWeapon->Fire();
|
|
}
|
|
}
|
|
|
|
void ThrallMaster::FireRockets
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
if ( ( currentWeapon ) && currentWeapon->ReadyToFire() && currentWeapon->HasAmmo() )
|
|
{
|
|
weaponmode = PRIMARY;
|
|
currentWeapon->Fire();
|
|
}
|
|
}
|
|
|
|
CLASS_DECLARATION( Weapon, ThrallGun, NULL );
|
|
|
|
ResponseDef ThrallGun::Responses[] =
|
|
{
|
|
{ &EV_Weapon_Shoot, ( Response )ThrallGun::Shoot },
|
|
{ &EV_Weapon_SecondaryUse, ( Response )ThrallGun::SecondaryUse },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
ThrallGun::ThrallGun()
|
|
{
|
|
SetModels( NULL, "view_genbullet.def" );
|
|
SetAmmo( "Rockets", 1, 10 );
|
|
}
|
|
|
|
void ThrallGun::Shoot
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
DrunkMissile *missile;
|
|
ThrallPulse *pulse;
|
|
Vector pos;
|
|
Vector dir;
|
|
|
|
assert( owner );
|
|
if ( !owner )
|
|
{
|
|
return;
|
|
}
|
|
|
|
GetMuzzlePosition( &pos, &dir );
|
|
|
|
if ( weaponmode == PRIMARY )
|
|
{
|
|
missile = new DrunkMissile;
|
|
missile->Setup( owner, pos, dir );
|
|
NextAttack( 0 );
|
|
}
|
|
else
|
|
{
|
|
pulse = new ThrallPulse;
|
|
pulse->Setup( owner, pos, dir );
|
|
NextAttack( 0 );
|
|
}
|
|
}
|
|
|
|
void ThrallGun::SecondaryUse
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
if ( weaponmode == PRIMARY )
|
|
{
|
|
weaponmode = SECONDARY;
|
|
}
|
|
else
|
|
{
|
|
weaponmode = PRIMARY;
|
|
}
|
|
}
|
|
|
|
|
|
Event EV_DrunkMissile_HeatSeek( "heatseek" );
|
|
|
|
CLASS_DECLARATION( Projectile, DrunkMissile, NULL );
|
|
|
|
ResponseDef DrunkMissile::Responses[] =
|
|
{
|
|
{ &EV_Touch, ( Response )DrunkMissile::Explode },
|
|
{ &EV_DrunkMissile_HeatSeek, ( Response )DrunkMissile::HeatSeek },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
EXPORT_FROM_DLL void DrunkMissile::Explode
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
int damg;
|
|
Vector v;
|
|
Entity *other;
|
|
Entity *owner;
|
|
|
|
other = ev->GetEntity( 1 );
|
|
|
|
owner = G_GetEntity( this->owner );
|
|
|
|
if ( !owner )
|
|
{
|
|
owner = world;
|
|
}
|
|
|
|
if ( !other || ( ( other == owner ) && ( owner != world ) ) || ( other->isSubclassOf( DrunkMissile ) ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
flags &= ~FL_PRETHINK;
|
|
stopsound( CHAN_VOICE );
|
|
setSolidType( SOLID_NOT );
|
|
hideModel();
|
|
|
|
if ( HitSky() )
|
|
{
|
|
PostEvent( EV_Remove, 0 );
|
|
return;
|
|
}
|
|
|
|
damg = 40 + ( int )G_Random( 20 );
|
|
|
|
other->Damage( this, owner, damg, worldorigin, velocity, level.impact_trace.plane.normal, 200, 0, MOD_ROCKET, -1, -1, 1.0f );
|
|
|
|
SpawnBlastDamage( &level.impact_trace, damg, owner );
|
|
|
|
v = velocity;
|
|
v.normalize();
|
|
|
|
// don't do radius damage to the other, because all the damage
|
|
// was done in the impact
|
|
v = worldorigin - v * 36;
|
|
CreateExplosion( v, damg, 0.7f, true, this, owner, other );
|
|
PostEvent( EV_Remove, 0.1 );
|
|
}
|
|
|
|
EXPORT_FROM_DLL float DrunkMissile::ResolveMinimumDistance
|
|
(
|
|
Entity *potential_target,
|
|
float currmin
|
|
)
|
|
|
|
{
|
|
float currdist;
|
|
float dot;
|
|
Vector angle;
|
|
Vector delta;
|
|
Vector norm;
|
|
float sine = 0.4f;
|
|
|
|
delta = potential_target->centroid - worldorigin;
|
|
|
|
norm = delta;
|
|
norm.normalize();
|
|
|
|
// Test if the target is in front of the missile
|
|
dot = norm * orientation[ 0 ];
|
|
if ( dot < 0 )
|
|
{
|
|
return currmin;
|
|
}
|
|
|
|
// Test if we're within the rocket's viewcone (45 degree cone)
|
|
dot = norm * orientation[ 1 ];
|
|
if ( fabs( dot ) > sine )
|
|
{
|
|
return currmin;
|
|
}
|
|
|
|
dot = norm * orientation[ 2 ];
|
|
if ( fabs( dot ) > sine )
|
|
{
|
|
return currmin;
|
|
}
|
|
|
|
currdist = delta.length();
|
|
if ( currdist < currmin )
|
|
{
|
|
currmin = currdist;
|
|
target = potential_target;
|
|
}
|
|
|
|
return currmin;
|
|
}
|
|
|
|
EXPORT_FROM_DLL float DrunkMissile::AdjustAngle
|
|
(
|
|
float maxadjust,
|
|
float currangle,
|
|
float targetangle
|
|
)
|
|
|
|
{
|
|
float dangle;
|
|
float magangle;
|
|
|
|
dangle = currangle - targetangle;
|
|
|
|
if ( dangle )
|
|
{
|
|
magangle = ( float )fabs( dangle );
|
|
if ( magangle < maxadjust )
|
|
{
|
|
currangle = targetangle;
|
|
}
|
|
else
|
|
{
|
|
if ( magangle > 180.0f )
|
|
{
|
|
maxadjust = -maxadjust;
|
|
}
|
|
if ( dangle > 0 )
|
|
{
|
|
maxadjust = -maxadjust;
|
|
}
|
|
currangle += maxadjust;
|
|
}
|
|
}
|
|
|
|
while( currangle >= 360.0f )
|
|
{
|
|
currangle -= 360.0f;
|
|
}
|
|
|
|
while( currangle < 0.0f )
|
|
{
|
|
currangle += 360.0f;
|
|
}
|
|
|
|
return currangle;
|
|
}
|
|
|
|
EXPORT_FROM_DLL void DrunkMissile::HeatSeek
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
float mindist;
|
|
Entity *ent;
|
|
trace_t trace;
|
|
Vector delta;
|
|
Vector v;
|
|
int n;
|
|
int i;
|
|
|
|
if ( ( !target ) || ( target == world ) )
|
|
{
|
|
mindist = 8192.0f;
|
|
|
|
n = SentientList.NumObjects();
|
|
for( i = 1; i <= n; i++ )
|
|
{
|
|
ent = SentientList.ObjectAt( i );
|
|
if ( ent->entnum == owner )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( ( ( ent->takedamage != DAMAGE_AIM ) || ( ent->health <= 0 ) ) && !( edict->svflags & SVF_MONSTER ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
trace = G_Trace( worldorigin, vec_zero, vec_zero, ent->centroid, this, MASK_SHOT, "DrunkMissile::HeatSeek" );
|
|
if ( ( trace.fraction != 1.0 ) && ( trace.ent != ent->edict ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
mindist = ResolveMinimumDistance( ent, mindist );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float predict;
|
|
float dist;
|
|
float time;
|
|
float angspeed;
|
|
|
|
delta = target->centroid - worldorigin;
|
|
dist = delta.length();
|
|
time = dist * ( 1 / DRUNKMISSILE_SPEED );
|
|
predict = ( time * ( 0.5 + ( skill->value + 1 ) * 0.125 ) );
|
|
delta += target->velocity * predict;
|
|
delta.z = -delta.z;
|
|
v = delta.toAngles();
|
|
|
|
angspeed = 5.0f + skill->value;
|
|
angles.x = AdjustAngle( angspeed, angles.x, v.x );
|
|
angles.y = AdjustAngle( angspeed, angles.y, v.y );
|
|
angles.z = AdjustAngle( angspeed, angles.z, v.z );
|
|
}
|
|
|
|
if ( !target )
|
|
{
|
|
PostEvent( EV_DrunkMissile_HeatSeek, 0.2 );
|
|
}
|
|
else
|
|
{
|
|
PostEvent( EV_DrunkMissile_HeatSeek, 0.1 );
|
|
}
|
|
}
|
|
|
|
EXPORT_FROM_DLL void DrunkMissile::Prethink
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
trace_t trace;
|
|
Vector end;
|
|
|
|
angles += Vector( G_CRandom( 3 ), G_CRandom( 5 ), 0 );
|
|
|
|
// Check if we're about to hit the ground at a shallow angle
|
|
if ( ( velocity.z < 0 ) && ( angles.x < 15.0f ) )
|
|
{
|
|
end = worldorigin + velocity * 0.1f;
|
|
trace = G_Trace( worldorigin, vec_zero, vec_zero, end, this, MASK_SHOT, "DrunkMissile::Prethink" );
|
|
if ( trace.fraction != 1 )
|
|
{
|
|
if ( trace.plane.normal[ 2 ] > 0.6f )
|
|
{
|
|
angles.x = -3;
|
|
}
|
|
}
|
|
}
|
|
|
|
setAngles( angles );
|
|
velocity = Vector( orientation[ 0 ] ) * DRUNKMISSILE_SPEED;
|
|
}
|
|
|
|
EXPORT_FROM_DLL void DrunkMissile::Setup
|
|
(
|
|
Entity *owner,
|
|
Vector pos,
|
|
Vector dir
|
|
)
|
|
|
|
{
|
|
Event *ev;
|
|
|
|
this->owner = owner->entnum;
|
|
edict->owner = owner->edict;
|
|
|
|
flags |= FL_PRETHINK;
|
|
|
|
setMoveType( MOVETYPE_FLYMISSILE );
|
|
setSolidType( SOLID_BBOX );
|
|
edict->clipmask = MASK_PROJECTILE;
|
|
|
|
// set missile duration
|
|
ev = new Event( EV_Touch );
|
|
ev->AddEntity( world );
|
|
PostEvent( ev, 10 );
|
|
|
|
PostEvent( EV_DrunkMissile_HeatSeek, 0.1 + G_Random( 0.2f ) );
|
|
|
|
// set missile direction
|
|
angles = dir.toAngles();
|
|
angles[ PITCH ] = - angles[ PITCH ];
|
|
setAngles( angles );
|
|
velocity = Vector( orientation[ 0 ] ) * 800.0f;
|
|
|
|
target = NULL;
|
|
|
|
setModel( "trocket.def" );
|
|
setSize( "-1 -1 -1", "1 1 1" );
|
|
takedamage = DAMAGE_YES;
|
|
health = 10;
|
|
setOrigin( pos );
|
|
worldorigin.copyTo(edict->s.old_origin);
|
|
|
|
edict->s.renderfx |= RF_DLIGHT;
|
|
edict->s.effects |= EF_ROCKET;
|
|
edict->s.effects |= EF_EVERYFRAME;
|
|
edict->s.angles[ROLL] = rand() % 360;
|
|
avelocity = "0 0 90";
|
|
gravity = 0;
|
|
edict->s.color_r = 0.8;
|
|
edict->s.color_g = 0.4;
|
|
edict->s.color_b = 0;
|
|
edict->s.radius = 200;
|
|
|
|
// setup ambient thrust
|
|
ev = new Event( EV_RandomEntitySound );
|
|
//ev->AddString( "thrust" );
|
|
ev->AddString( "fire" );
|
|
ProcessEvent( ev );
|
|
}
|
|
|
|
CLASS_DECLARATION( Projectile, ThrallPulse, NULL );
|
|
|
|
ResponseDef ThrallPulse::Responses[] =
|
|
{
|
|
{ &EV_Touch, ( Response )ThrallPulse::Explode },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
EXPORT_FROM_DLL void ThrallPulse::Explode
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
int damg;
|
|
Vector v;
|
|
Entity *other;
|
|
Entity *owner;
|
|
ThrallPulseDebris *debris;
|
|
int i;
|
|
|
|
other = ev->GetEntity( 1 );
|
|
|
|
owner = G_GetEntity( this->owner );
|
|
|
|
if ( !owner )
|
|
owner = world;
|
|
|
|
if ( !other || ( other == owner ) || ( other->isSubclassOf( ThrallPulse ) ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
flags &= ~FL_PRETHINK;
|
|
stopsound( CHAN_VOICE );
|
|
setSolidType( SOLID_NOT );
|
|
hideModel();
|
|
|
|
if ( HitSky() )
|
|
{
|
|
PostEvent( EV_Remove, 0 );
|
|
return;
|
|
}
|
|
|
|
damg = 160 + ( int )G_Random( 50 );
|
|
|
|
other->Damage( this, owner, damg, worldorigin, velocity, level.impact_trace.plane.normal, 320, 0, MOD_ROCKET, -1, -1, 1.0f );
|
|
|
|
SpawnBlastDamage( &level.impact_trace, damg, owner );
|
|
|
|
v = velocity;
|
|
v.normalize();
|
|
|
|
// don't do radius damage to the other, because all the damage
|
|
// was done in the impact
|
|
v = worldorigin - v * 36;
|
|
CreateExplosion( v, damg, 0.7f, true, this, owner, other );
|
|
PostEvent( EV_Remove, 0.1 );
|
|
FlashPlayers( v, 1, 1, 1, 0.5, 768 );
|
|
|
|
for( i = 1; i < 4; i++ )
|
|
{
|
|
debris = new ThrallPulseDebris;
|
|
debris->Setup( owner, v, i );
|
|
}
|
|
}
|
|
|
|
EXPORT_FROM_DLL void ThrallPulse::Setup
|
|
(
|
|
Entity *owner,
|
|
Vector pos,
|
|
Vector dir
|
|
)
|
|
|
|
{
|
|
Event *ev;
|
|
|
|
this->owner = owner->entnum;
|
|
edict->owner = owner->edict;
|
|
|
|
setMoveType( MOVETYPE_FLYMISSILE );
|
|
setSolidType( SOLID_BBOX );
|
|
edict->clipmask = MASK_PROJECTILE;
|
|
|
|
// set missile duration
|
|
ev = new Event( EV_Touch );
|
|
ev->AddEntity( world );
|
|
PostEvent( ev, 10 );
|
|
|
|
// set missile direction
|
|
angles = dir.toAngles();
|
|
angles[ PITCH ] = - angles[ PITCH ];
|
|
setAngles( angles );
|
|
velocity = Vector( orientation[ 0 ] ) * 1400.0f;
|
|
|
|
setModel( "sprites/thrallpulse.spr" );
|
|
setSize( "-8 -8 -8", "8 8 8" );
|
|
takedamage = DAMAGE_NO;
|
|
setOrigin( pos );
|
|
worldorigin.copyTo(edict->s.old_origin);
|
|
showModel();
|
|
|
|
edict->s.renderfx |= RF_DLIGHT;
|
|
edict->s.angles[ROLL] = rand() % 360;
|
|
avelocity = "0 0 90";
|
|
gravity = 0;
|
|
edict->s.color_r = 0.8;
|
|
edict->s.color_g = 0;
|
|
edict->s.color_b = 0;
|
|
edict->s.radius = 200;
|
|
}
|
|
|
|
CLASS_DECLARATION( Projectile, ThrallPulseDebris, NULL );
|
|
|
|
ResponseDef ThrallPulseDebris::Responses[] =
|
|
{
|
|
{ &EV_Touch, ( Response )ThrallPulseDebris::Touch },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
EXPORT_FROM_DLL void ThrallPulseDebris::Touch
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
Entity *other;
|
|
Entity *owner;
|
|
|
|
if ( level.time < nexttouch )
|
|
{
|
|
return;
|
|
}
|
|
|
|
nexttouch = level.time + 1;
|
|
|
|
other = ev->GetEntity( 1 );
|
|
owner = G_GetEntity( this->owner );
|
|
if ( !owner )
|
|
{
|
|
owner = world;
|
|
}
|
|
|
|
if ( !other )
|
|
{
|
|
return;
|
|
}
|
|
|
|
other->Damage( this, owner, 10 * edict->s.scale, worldorigin, velocity,
|
|
level.impact_trace.plane.normal, velocity.length(), 0, MOD_DEBRIS, -1, -1, 1.0f );
|
|
}
|
|
|
|
EXPORT_FROM_DLL void ThrallPulseDebris::Prethink
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
if ( ( level.time - spawntime ) > 4 )
|
|
{
|
|
edict->s.scale *= 0.9;
|
|
setSize( Vector( -4, -4, -4 ) * edict->s.scale, Vector( 4, 4, 4 ) * edict->s.scale );
|
|
}
|
|
}
|
|
|
|
EXPORT_FROM_DLL void ThrallPulseDebris::Setup
|
|
(
|
|
Entity *owner,
|
|
Vector pos,
|
|
float size
|
|
)
|
|
|
|
{
|
|
this->owner = owner->entnum;
|
|
edict->owner = owner->edict;
|
|
|
|
nexttouch = 0;
|
|
spawntime = level.time;
|
|
flags |= FL_PRETHINK;
|
|
setModel( "thrallfire.def" );
|
|
setMoveType( MOVETYPE_BOUNCE );
|
|
setSolidType( SOLID_TRIGGER );
|
|
edict->s.effects |= EF_ROCKET;
|
|
showModel();
|
|
setOrigin( pos );
|
|
worldorigin.copyTo( edict->s.old_origin );
|
|
velocity = Vector( G_CRandom( 200 ), G_CRandom( 200 ), G_Random( 100 ) + 100 );
|
|
PostEvent( EV_Remove, 4 + 2 * size );
|
|
edict->s.scale *= size;
|
|
setSize( Vector( -4, -4, -4 ) * edict->s.scale, Vector( 4, 4, 4 ) * edict->s.scale );
|
|
}
|