mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 08:11:10 +02:00
662 lines
20 KiB
C++
662 lines
20 KiB
C++
#include "precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "prey_local.h"
|
|
|
|
const idEventDef AI_RemoveJetFx("removeJetFx");
|
|
const idEventDef AI_CreateJetFx("createJetFx");
|
|
const idEventDef AI_LaunchMine("launchMine", "d");
|
|
const idEventDef MA_IsEnemySniping("isEnemySniping", NULL, 'd');
|
|
const idEventDef MA_NumMines("numMines", NULL, 'f');
|
|
const idEventDef MA_DodgeProjectile("<dodgeProjectile>", "e");
|
|
const idEventDef MA_AppendHoverFx( "<appendHoverFx>", "e" );
|
|
|
|
CLASS_DECLARATION( hhMonsterAI, hhJetpackHarvesterSimple )
|
|
EVENT( AI_RemoveJetFx, hhJetpackHarvesterSimple::Event_RemoveJetFx )
|
|
EVENT( EV_Activate, hhJetpackHarvesterSimple::Event_Activate)
|
|
EVENT( AI_CreateJetFx, hhJetpackHarvesterSimple::Event_CreateJetFx )
|
|
EVENT( AI_LaunchMine, hhJetpackHarvesterSimple::Event_LaunchMine )
|
|
EVENT( EV_Broadcast_AppendFxToList, hhJetpackHarvesterSimple::Event_AppendToThrusterList )
|
|
EVENT( MA_OnProjectileLaunch, hhJetpackHarvesterSimple::Event_OnProjectileLaunch )
|
|
EVENT( MA_DodgeProjectile, hhJetpackHarvesterSimple::Event_DodgeProjectile )
|
|
EVENT( MA_IsEnemySniping, hhJetpackHarvesterSimple::Event_IsEnemySniping )
|
|
EVENT( MA_NumMines, hhJetpackHarvesterSimple::Event_NumMines )
|
|
EVENT( MA_AppendHoverFx, hhJetpackHarvesterSimple::Event_AppendHoverFx )
|
|
END_CLASS
|
|
|
|
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
|
|
|
|
hhJetpackHarvesterSimple::~hhJetpackHarvesterSimple() {
|
|
for ( int i=0;i<mineList.Num();i++ ) {
|
|
if ( mineList[i].IsValid() ) {
|
|
trace_t collision;
|
|
memset( &collision, 0, sizeof( collision ) );
|
|
collision.endAxis = mineList[i]->GetPhysics()->GetAxis();
|
|
collision.endpos = mineList[i]->GetPhysics()->GetOrigin();
|
|
collision.c.point = mineList[i]->GetPhysics()->GetOrigin();
|
|
collision.c.normal.Set( 0, 0, 1 );
|
|
mineList[i]->Explode( &collision, idVec3(0,0,0), 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
void hhJetpackHarvesterSimple::Spawn(void) {
|
|
lastAntiProjectileAttack = gameLocal.GetTime();
|
|
for(int i=0;i<ThrustSide_Total;i++) {
|
|
fxThrusters[ThrustType_Idle][i] = NULL;
|
|
fxThrusters[ThrustType_Hover][i] = NULL;
|
|
}
|
|
if ( !spawnArgs.GetBool( "bAlwaysGib" ) && !spawnArgs.GetBool( "no_pre_death" ) ) {
|
|
allowPreDeath = true;
|
|
} else {
|
|
allowPreDeath = false;
|
|
}
|
|
freezeDamage = false;
|
|
specialDamage = false;
|
|
Event_CreateJetFx();
|
|
|
|
//give random model offset
|
|
float offset_min = spawnArgs.GetFloat( "offset_z_min", "0" );
|
|
float offset_max = spawnArgs.GetFloat( "offset_z_max", "0" );
|
|
modelOffset.z = gameLocal.random.RandomFloat() * (offset_max-offset_min) + offset_min;
|
|
walkIK.Init( this, IK_ANIM, modelOffset );
|
|
}
|
|
|
|
void hhJetpackHarvesterSimple::Event_RemoveJetFx(void) {
|
|
for(int i=0;i<ThrustSide_Total;i++) {
|
|
SAFE_REMOVE(fxThrusters[ThrustType_Idle][i]);
|
|
SAFE_REMOVE(fxThrusters[ThrustType_Hover][i]);
|
|
}
|
|
|
|
}
|
|
|
|
void hhJetpackHarvesterSimple::Event_CreateJetFx(void) {
|
|
const char *bones[ThrustSide_Total];
|
|
bones[ThrustSide_Left] = spawnArgs.GetString( "bone_thrust_left" );
|
|
bones[ThrustSide_Right] = spawnArgs.GetString( "bone_thrust_right" );
|
|
|
|
const char *psystem = spawnArgs.GetString( "fx_thruster_idle" );
|
|
if ( psystem && *psystem ) {
|
|
for(int i=0;i<ThrustSide_Total;i++) {
|
|
hhFxInfo fxInfo;
|
|
|
|
idVec3 bonePos;
|
|
idMat3 boneAxis;
|
|
this->GetJointWorldTransform( bones[i], bonePos, boneAxis );
|
|
fxInfo.SetEntity( this );
|
|
fxInfo.SetBindBone( bones[i] );
|
|
fxInfo.Toggle();
|
|
fxInfo.RemoveWhenDone( false );
|
|
|
|
if( fxThrusters[ThrustType_Idle][i] != NULL ) {
|
|
gameLocal.Warning("Jetpack harvester already had flamejet made.");
|
|
}
|
|
|
|
BroadcastFxInfo( psystem, bonePos, boneAxis, &fxInfo, &EV_Broadcast_AppendFxToList );
|
|
}
|
|
}
|
|
|
|
psystem = spawnArgs.GetString( "fx_thruster_hover" );
|
|
if ( psystem && *psystem ) {
|
|
for(int i=0;i<ThrustSide_Total;i++) {
|
|
hhFxInfo fxInfo;
|
|
idVec3 bonePos;
|
|
idMat3 boneAxis;
|
|
GetJointWorldTransform( bones[i], bonePos, boneAxis );
|
|
fxInfo.SetEntity( this );
|
|
fxInfo.SetBindBone( bones[i] );
|
|
fxInfo.Toggle();
|
|
fxInfo.RemoveWhenDone( false );
|
|
|
|
if( fxThrusters[ThrustType_Hover][i] != NULL ) {
|
|
gameLocal.Warning("Jetpack harvester already had flamejet made.");
|
|
}
|
|
|
|
BroadcastFxInfo( psystem, bonePos, boneAxis, &fxInfo, &MA_AppendHoverFx );
|
|
}
|
|
}
|
|
}
|
|
|
|
void hhJetpackHarvesterSimple::Event_AppendToThrusterList( hhEntityFx* fx ) {
|
|
for( int ix = 0; ix < ThrustSide_Total; ++ix ) {
|
|
if( fxThrusters[ThrustType_Idle][ix] != NULL ) {
|
|
continue;
|
|
}
|
|
|
|
fxThrusters[ThrustType_Idle][ix] = fx;
|
|
return;
|
|
}
|
|
}
|
|
|
|
void hhJetpackHarvesterSimple::Event_AppendHoverFx( hhEntityFx* fx ) {
|
|
for( int ix = 0; ix < ThrustSide_Total; ++ix ) {
|
|
if( fxThrusters[ThrustType_Hover][ix] != NULL ) {
|
|
continue;
|
|
}
|
|
fxThrusters[ThrustType_Hover][ix] = fx;
|
|
return;
|
|
}
|
|
}
|
|
|
|
void hhJetpackHarvesterSimple::Event_LaunchMine( const idList<idStr>* parmList ) {
|
|
if( !parmList || parmList->Num() != 2 ) {
|
|
return;
|
|
}
|
|
|
|
idVec3 jointPos;
|
|
idMat3 jointAxis;
|
|
const char* projectileDefName = (*parmList)[0].c_str();
|
|
const char* jointName = (*parmList)[1].c_str();
|
|
|
|
if( !projectileDefName || !projectileDefName[0] ) {
|
|
return;
|
|
}
|
|
|
|
if( !GetJointWorldTransform(jointName, jointPos, jointAxis) ) {
|
|
return;
|
|
}
|
|
|
|
hhHarvesterMine* mine = static_cast<hhHarvesterMine*>( gameLocal.SpawnObject(projectileDefName) );
|
|
mine->Create( this, jointPos, jointAxis );
|
|
mine->Launch( jointPos, jointAxis, physicsObj.GetPushedLinearVelocity() );
|
|
}
|
|
|
|
void hhJetpackHarvesterSimple::Hide( void ) {
|
|
hhMonsterAI::Hide();
|
|
|
|
for(int i=0;i<ThrustSide_Total;i++) {
|
|
if(fxThrusters[ThrustType_Idle][i].GetEntity()) {
|
|
fxThrusters[ThrustType_Idle][i]->Nozzle(FALSE);
|
|
}
|
|
if(fxThrusters[ThrustType_Hover][i].GetEntity()) {
|
|
fxThrusters[ThrustType_Hover][i]->Nozzle(FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void hhJetpackHarvesterSimple::Show( void ) {
|
|
hhMonsterAI::Show();
|
|
|
|
for(int i=0;i<ThrustSide_Total;i++) {
|
|
if(fxThrusters[ThrustType_Idle][i].GetEntity()) {
|
|
fxThrusters[ThrustType_Idle][i]->Nozzle(TRUE);
|
|
}
|
|
if(fxThrusters[ThrustType_Hover][i].GetEntity()) {
|
|
fxThrusters[ThrustType_Hover][i]->Nozzle(FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
idProjectile *hhJetpackHarvesterSimple::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs.
|
|
idVec3 muzzle;
|
|
idVec3 dir;
|
|
idVec3 start;
|
|
trace_t tr;
|
|
idBounds projBounds;
|
|
float distance;
|
|
const idClipModel *projClip;
|
|
float attack_accuracy;
|
|
float attack_cone;
|
|
float projectile_spread;
|
|
float diff;
|
|
float angle;
|
|
float spin;
|
|
idAngles ang;
|
|
int num_projectiles;
|
|
int i;
|
|
idMat3 axis;
|
|
idVec3 tmp;
|
|
idProjectile *lastProjectile;
|
|
|
|
//HUMANHEAD mdc - added to support multiple projectiles
|
|
if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile
|
|
int projIndex = FindProjectileInfo( desiredProjectileDef );
|
|
if( projIndex >= 0 ) {
|
|
SetCurrentProjectile( projIndex );
|
|
}
|
|
}
|
|
//HUMANHEAD END
|
|
|
|
|
|
if ( !projectileDef ) {
|
|
gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
|
|
return NULL;
|
|
}
|
|
|
|
attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" );
|
|
attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
|
|
projectile_spread = desiredProjectileDef->GetFloat( "projectile_spread", "0" );
|
|
num_projectiles = desiredProjectileDef->GetInt( "num_projectiles", "1" );
|
|
idVec3 launch_velocity = vec3_zero;
|
|
if ( desiredProjectileDef->GetFloat( "launch_y", "0" ) > 0.0f ) {
|
|
if ( gameLocal.random.RandomFloat() < 0.5 ) {
|
|
launch_velocity.y = -desiredProjectileDef->GetFloat( "launch_y", "0" );
|
|
} else {
|
|
launch_velocity.y = desiredProjectileDef->GetFloat( "launch_y", "0" );
|
|
}
|
|
launch_velocity *= viewAxis;
|
|
}
|
|
|
|
GetMuzzle( jointname, muzzle, axis );
|
|
|
|
if ( !projectile.GetEntity() ) {
|
|
CreateProjectile( muzzle, axis[ 0 ] );
|
|
}
|
|
|
|
lastProjectile = projectile.GetEntity();
|
|
|
|
if ( target != NULL ) {
|
|
tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle;
|
|
tmp.Normalize();
|
|
axis = tmp.ToMat3();
|
|
} else {
|
|
axis = viewAxis;
|
|
}
|
|
|
|
// rotate it because the cone points up by default
|
|
tmp = axis[2];
|
|
axis[2] = axis[0];
|
|
axis[0] = -tmp;
|
|
|
|
// make sure the projectile starts inside the monster bounding box
|
|
const idBounds &ownerBounds = physicsObj.GetAbsBounds();
|
|
projClip = lastProjectile->GetPhysics()->GetClipModel();
|
|
projBounds = projClip->GetBounds().Rotate( axis );
|
|
|
|
// check if the owner bounds is bigger than the projectile bounds
|
|
if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
|
|
( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
|
|
( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
|
|
if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) {
|
|
start = muzzle + distance * viewAxis[ 0 ];
|
|
} else {
|
|
start = ownerBounds.GetCenter();
|
|
}
|
|
} else {
|
|
// projectile bounds bigger than the owner bounds, so just start it from the center
|
|
start = ownerBounds.GetCenter();
|
|
}
|
|
|
|
gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this );
|
|
muzzle = tr.endpos;
|
|
|
|
// set aiming direction
|
|
GetAimDir( muzzle, target, this, dir );
|
|
ang = dir.ToAngles();
|
|
|
|
// adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread.
|
|
float t = MS2SEC( gameLocal.time + entityNumber * 497 );
|
|
ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy;
|
|
|
|
if ( clampToAttackCone ) {
|
|
// clamp the attack direction to be within monster's attack cone so he doesn't do
|
|
// things like throw the missile backwards if you're behind him
|
|
diff = idMath::AngleDelta( ang.yaw, current_yaw );
|
|
if ( diff > attack_cone ) {
|
|
ang.yaw = current_yaw + attack_cone;
|
|
} else if ( diff < -attack_cone ) {
|
|
ang.yaw = current_yaw - attack_cone;
|
|
}
|
|
}
|
|
|
|
axis = ang.ToMat3();
|
|
|
|
float spreadRad = DEG2RAD( projectile_spread );
|
|
for( i = 0; i < num_projectiles; i++ ) {
|
|
// spread the projectiles out
|
|
angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
|
|
spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
|
|
//dir = axis[ 0 ] + axis[ 2 ] * ( 0 * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
|
|
dir = axis[ 0 ] - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
|
|
dir.Normalize();
|
|
|
|
// launch the projectile
|
|
if ( !projectile.GetEntity() ) {
|
|
CreateProjectile( muzzle, dir );
|
|
}
|
|
lastProjectile = projectile.GetEntity();
|
|
lastProjectile->Launch( muzzle, dir, launch_velocity );
|
|
projectile = NULL;
|
|
if ( projectileDef->GetBool( "mine", "0" ) ) {
|
|
idEntityPtr<hhProjectile> &entityPtr = mineList.Alloc();
|
|
entityPtr = lastProjectile;
|
|
}
|
|
}
|
|
|
|
TriggerWeaponEffects( muzzle, axis );
|
|
|
|
lastAttackTime = gameLocal.time;
|
|
|
|
//HUMANHEAD mdc - added to support multiple projectiles
|
|
projectile = NULL;
|
|
SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side
|
|
//HUMANHEAD END
|
|
|
|
return lastProjectile;
|
|
}
|
|
|
|
void hhJetpackHarvesterSimple::AnimMove( void ) {
|
|
idVec3 goalPos;
|
|
idVec3 delta;
|
|
idVec3 goalDelta;
|
|
float goalDist;
|
|
monsterMoveResult_t moveResult;
|
|
idVec3 newDest;
|
|
|
|
idVec3 oldorigin = physicsObj.GetOrigin();
|
|
#ifdef HUMANHEAD //jsh wallwalk
|
|
idMat3 oldaxis = GetGravViewAxis();
|
|
#else
|
|
idMat3 oldaxis = viewAxis;
|
|
#endif
|
|
|
|
physicsObj.UseFlyMove( false );
|
|
|
|
AI_BLOCKED = false;
|
|
|
|
if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){
|
|
move.lastMoveOrigin.Zero();
|
|
move.lastMoveTime = gameLocal.time;
|
|
}
|
|
|
|
move.obstacle = NULL;
|
|
if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
|
|
TurnToward( lastVisibleEnemyPos );
|
|
goalPos = oldorigin;
|
|
} else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
|
|
TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
|
|
goalPos = oldorigin;
|
|
} else if ( GetMovePos( goalPos ) ) {
|
|
if ( move.moveCommand != MOVE_WANDER ) {
|
|
CheckObstacleAvoidance( goalPos, newDest );
|
|
TurnToward( newDest );
|
|
} else {
|
|
TurnToward( goalPos );
|
|
}
|
|
}
|
|
|
|
Turn();
|
|
|
|
if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) {
|
|
if ( gameLocal.time < move.startTime + move.duration ) {
|
|
goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time );
|
|
delta = goalPos - oldorigin;
|
|
delta.z = 0.0f;
|
|
} else {
|
|
delta = move.moveDest - oldorigin;
|
|
delta.z = 0.0f;
|
|
StopMove( MOVE_STATUS_DONE );
|
|
}
|
|
} else if ( allowMove ) {
|
|
#ifdef HUMANHEAD //jsh wallwalk
|
|
GetMoveDelta( oldaxis, GetGravViewAxis(), delta );
|
|
#else
|
|
GetMoveDelta( oldaxis, viewAxis, delta );
|
|
#endif
|
|
} else {
|
|
delta.Zero();
|
|
}
|
|
|
|
if ( move.moveCommand == MOVE_TO_POSITION ) {
|
|
goalDelta = move.moveDest - oldorigin;
|
|
goalDist = goalDelta.LengthFast();
|
|
if ( goalDist < delta.LengthFast() ) {
|
|
delta = goalDelta;
|
|
}
|
|
}
|
|
|
|
delta *= spawnArgs.GetFloat( "fly_scale", "1.0" );
|
|
|
|
physicsObj.SetDelta( delta );
|
|
physicsObj.ForceDeltaMove( disableGravity );
|
|
|
|
RunPhysics();
|
|
|
|
if ( ai_debugMove.GetBool() ) {
|
|
// HUMANHEAD JRM - so we can see if grav is on or off
|
|
if(disableGravity) {
|
|
gameRenderWorld->DebugLine( colorRed, oldorigin, physicsObj.GetOrigin(), 5000 );
|
|
} else {
|
|
gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 );
|
|
}
|
|
}
|
|
|
|
moveResult = physicsObj.GetMoveResult();
|
|
if ( !af_push_moveables && attack.Length() && TestMelee() ) {
|
|
DirectDamage( attack, enemy.GetEntity() );
|
|
} else {
|
|
idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
|
|
if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
|
|
KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
|
|
}
|
|
}
|
|
|
|
BlockedFailSafe();
|
|
|
|
AI_ONGROUND = physicsObj.OnGround();
|
|
|
|
idVec3 org = physicsObj.GetOrigin();
|
|
if ( oldorigin != org ) {
|
|
TouchTriggers();
|
|
}
|
|
|
|
if ( ai_debugMove.GetBool() ) {
|
|
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
|
|
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
|
|
gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
|
|
DrawRoute();
|
|
}
|
|
}
|
|
|
|
void hhJetpackHarvesterSimple::Event_NumMines() {
|
|
//we only want valid mines, so remove dead ones
|
|
for ( int i=0;i<mineList.Num();i++ ) {
|
|
if ( !mineList[i].IsValid() || mineList[i].GetEntity()->health <= 0 ) {
|
|
mineList.RemoveIndex(i);
|
|
}
|
|
}
|
|
idThread::ReturnFloat( float(mineList.Num()) );
|
|
}
|
|
|
|
void hhJetpackHarvesterSimple::Event_DodgeProjectile( hhProjectile *proj ) {
|
|
if ( !proj || !enemy.IsValid() ) {
|
|
return;
|
|
}
|
|
float min = spawnArgs.GetFloat( "dda_dodge_min", "0.3" );
|
|
float max = spawnArgs.GetFloat( "dda_dodge_max", "0.8" );
|
|
|
|
float dodgeChance = (min + (max-min)*gameLocal.GetDDAValue() );
|
|
|
|
if ( ai_debugBrain.GetBool() ) {
|
|
gameLocal.Printf( "%s dodge chance %f\n", GetName(), dodgeChance );
|
|
}
|
|
if ( gameLocal.random.RandomFloat() > dodgeChance ) {
|
|
return;
|
|
}
|
|
|
|
idVec3 fw = viewAxis[0];
|
|
idVec3 projFw = proj->GetAxis()[0];
|
|
if(proj->GetOwner())
|
|
projFw = proj->GetOwner()->GetAxis()[0];
|
|
float dot = fw * projFw;
|
|
if(dot > -.7f)
|
|
return;
|
|
const function_t *newstate = NULL;
|
|
|
|
//determine which side to dodge to
|
|
idVec3 povPos, targetPos;
|
|
povPos = enemy->GetPhysics()->GetOrigin();
|
|
targetPos = GetPhysics()->GetOrigin();
|
|
idVec3 povToTarget = targetPos - povPos;
|
|
povToTarget.z = 0.f;
|
|
povToTarget.Normalize();
|
|
idVec3 povLeft, povUp;
|
|
povToTarget.OrthogonalBasis(povLeft, povUp);
|
|
povLeft.Normalize();
|
|
idVec3 projVel = proj->GetPhysics()->GetLinearVelocity();
|
|
projVel.Normalize();
|
|
dot = povLeft * projVel;
|
|
if ( dot < 0 ) {
|
|
newstate = GetScriptFunction( "state_DodgeRight" );
|
|
} else {
|
|
newstate = GetScriptFunction( "state_DodgeLeft" );
|
|
}
|
|
if ( newstate ) {
|
|
lastAntiProjectileAttack = gameLocal.GetTime();
|
|
SetState( newstate );
|
|
UpdateScript();
|
|
}
|
|
}
|
|
|
|
void hhJetpackHarvesterSimple::Event_OnProjectileLaunch( hhProjectile *proj ) {
|
|
if ( !proj || !spawnArgs.GetBool( "can_dodge", "1" ) || health <= 0 ) {
|
|
return;
|
|
}
|
|
if ( gameLocal.GetTime() - lastAntiProjectileAttack < 1000 * int(spawnArgs.GetFloat( "dodge_freq", "2.0" )) ) {
|
|
return;
|
|
}
|
|
if( proj->GetOwner() && !(ReactionTo(proj->GetOwner()) & (ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE)) ) {
|
|
return; // The person who launched this projectile wasn't someone to worry about
|
|
}
|
|
if ( !enemy.IsValid() ) {
|
|
return;
|
|
}
|
|
PostEventSec( &MA_DodgeProjectile, spawnArgs.GetFloat( "dodge_delay", "0.5" ), proj );
|
|
}
|
|
|
|
bool hhJetpackHarvesterSimple::Collide( const trace_t &collision, const idVec3 &velocity ) {
|
|
if ( af.IsActive() && !IsHidden() && !freezeDamage && !specialDamage ) {
|
|
const char *fx = spawnArgs.GetString("fx_death");
|
|
if ( fx && *fx ) {
|
|
hhFxInfo fxInfo;
|
|
fxInfo.RemoveWhenDone(true);
|
|
BroadcastFxInfo(fx, GetOrigin(), GetAxis(), &fxInfo);
|
|
Hide();
|
|
PostEventMS(&EV_Remove, 1000);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void hhJetpackHarvesterSimple::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
|
|
if (!frozen) {
|
|
// Being tractored can mess up predeath
|
|
fl.canBeTractored = false;
|
|
}
|
|
HandleNoGore();
|
|
if ( AI_DEAD ) {
|
|
AI_PAIN = true;
|
|
AI_DAMAGE = true;
|
|
return;
|
|
}
|
|
|
|
//stop rocket fx and sounds
|
|
StopSound( SND_CHANNEL_BODY3, false );
|
|
for(int i=0;i<ThrustSide_Total;i++) {
|
|
SAFE_REMOVE(fxThrusters[ThrustType_Idle][i]);
|
|
SAFE_REMOVE(fxThrusters[ThrustType_Hover][i]);
|
|
}
|
|
|
|
if ( !frozen && allowPreDeath && !AI_BOUND ) {
|
|
allowPreDeath = false;
|
|
trace_t TraceInfo;
|
|
gameLocal.clip.TracePoint(TraceInfo, GetOrigin(), GetOrigin() + GetPhysics()->GetGravityNormal() * 800, MASK_MONSTERSOLID, this);
|
|
if( TraceInfo.fraction < 1.0f ) {
|
|
if ( HasPathTo( TraceInfo.endpos ) ) {
|
|
Event_SetMoveType( MOVETYPE_ANIM );
|
|
health += 30;
|
|
state = GetScriptFunction( "state_StartPreDeath" );
|
|
SetState( state );
|
|
SetWaitState( "" );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
hhMonsterAI::Killed( inflictor, attacker, damage, dir, location );
|
|
|
|
//explode creature's mines upon death
|
|
for ( int i=0;i<mineList.Num();i++ ) {
|
|
if ( mineList[i].IsValid() ) {
|
|
trace_t collision;
|
|
memset( &collision, 0, sizeof( collision ) );
|
|
collision.endAxis = mineList[i]->GetPhysics()->GetAxis();
|
|
collision.endpos = mineList[i]->GetPhysics()->GetOrigin();
|
|
collision.c.point = mineList[i]->GetPhysics()->GetOrigin();
|
|
collision.c.normal.Set( 0, 0, 1 );
|
|
mineList[i]->Explode( &collision, idVec3(0,0,0), 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
void hhJetpackHarvesterSimple::Event_IsEnemySniping(void) {
|
|
idThread::ReturnInt( false );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhJetpackHarvesterSimple::Save
|
|
=====================
|
|
*/
|
|
void hhJetpackHarvesterSimple::Save( idSaveGame *savefile ) const {
|
|
int i;
|
|
for ( i = 0; i < ThrustSide_Total; i++ ) {
|
|
for ( int j = 0; j < ThrustType_Total; j++ ) {
|
|
fxThrusters[i][j].Save( savefile );
|
|
}
|
|
}
|
|
|
|
savefile->WriteInt( lastAntiProjectileAttack );
|
|
savefile->WriteBool( allowPreDeath );
|
|
|
|
int num = mineList.Num();
|
|
savefile->WriteInt( num );
|
|
savefile->WriteBool( specialDamage );
|
|
for ( i = 0; i < num; i++ ) {
|
|
mineList[i].Save( savefile );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhJetpackHarvesterSimple::Restore
|
|
=====================
|
|
*/
|
|
void hhJetpackHarvesterSimple::Restore( idRestoreGame *savefile ) {
|
|
int i;
|
|
for ( i = 0; i < ThrustSide_Total; i++ ) {
|
|
for ( int j = 0; j < ThrustType_Total; j++ ) {
|
|
fxThrusters[i][j].Restore( savefile );
|
|
}
|
|
}
|
|
|
|
savefile->ReadInt( lastAntiProjectileAttack );
|
|
savefile->ReadBool( allowPreDeath );
|
|
|
|
int num;
|
|
savefile->ReadInt( num );
|
|
savefile->ReadBool( specialDamage );
|
|
mineList.SetNum( num );
|
|
for ( i = 0; i < num; i++ ) {
|
|
mineList[i].Restore( savefile );
|
|
}
|
|
}
|
|
|
|
void hhJetpackHarvesterSimple::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
|
|
const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
|
|
if ( !AI_DEAD ) {
|
|
if ( damageDef && damageDef->GetBool( "ice" ) && spawnArgs.GetBool( "can_freeze", "0" ) ) {
|
|
freezeDamage = true;
|
|
allowPreDeath = false;
|
|
} else {
|
|
freezeDamage = false;
|
|
}
|
|
if ( damageDef && damageDef->GetBool( "no_special_death" ) ) {
|
|
specialDamage = true;
|
|
allowPreDeath = false;
|
|
}
|
|
}
|
|
hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
|
|
}
|
|
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
|