sin-sdk/thrall.cpp
1998-12-20 00:00:00 +00:00

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 );
}