Files
2026-05-05 15:01:01 -07:00

975 lines
31 KiB
C++

//*****************************************************************************
//**
//** GAME_WRAITH.CPP
//**
//** Game code for the Wraith creature
//**
//*****************************************************************************
// HEADER FILES ---------------------------------------------------------------
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
// MACROS ---------------------------------------------------------------------
#define TWEEN_BANK 1000
// TYPES ----------------------------------------------------------------------
// CLASS DECLARATIONS ---------------------------------------------------------
const idEventDef EV_Flee( "<flee>", NULL );
const idEventDef EV_PlayAnimMoveEnd( "<playanimmoveend>", NULL );
extern const idEventDef EV_SpawnDeathWraith;
extern const idEventDef AI_FindEnemy;
CLASS_DECLARATION( hhMonsterAI, hhWraith )
EVENT( EV_Flee, hhWraith::Event_Flee )
EVENT( EV_PlayAnimMoveEnd, hhWraith::Event_PlayAnimMoveEnd )
EVENT( AI_FindEnemy, hhWraith::Event_FindEnemy )
EVENT( EV_Activate, hhWraith::Event_Activate )
END_CLASS
//=============================================================================
//
// hhWraith::Spawn
//
//=============================================================================
void hhWraith::Spawn(void) {
// List animations
flyAnim = GetAnimator()->GetAnim( "fly" );
possessAnim = GetAnimator()->GetAnim( "alert" );
leftAnim = GetAnimator()->GetAnim( "bankLeft" );
rightAnim = GetAnimator()->GetAnim( "bankRight" );
fleeAnim = GetAnimator()->GetAnim( "flee" );
fleeInAnim = GetAnimator()->GetAnim( "fleeIn" );
lastAnim = NULL;
canPossess = spawnArgs.GetBool( "possess", "1" );
// Physics and collision information
fl.takedamage = true;
minDamageDist = spawnArgs.GetFloat( "minDamageDist", "50" );
// Initialize flight logic
straightTicks = 0;
damageTicks = 0;
turnTicks = 0;
isScaling = false;
// Initially launch the wraith forward
velocity = GetPhysics()->GetAxis()[0] * 100;
GetAnimator()->ClearAllAnims( gameLocal.time, 0 );
GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, flyAnim, gameLocal.time, 0 );
BecomeActive( TH_THINK );
fl.neverDormant = true;
nextCheckTime = gameLocal.time + spawnArgs.GetInt("trace_check_time", "200");
lastCheckOrigin = GetPhysics()->GetOrigin();
lastDamageTime = spawnArgs.GetFloat( "damageDelay", "0.5" );
if ( spawnArgs.GetBool( "spawnFlyUp", "0" ) ) {
state = WS_SPAWN;
// Orient the wraith pointing upwards
desiredAxis[0] = idVec3( 0, 0, 1 );
desiredAxis[1] = idVec3( 1, 0, 0 );
desiredAxis[2] = idVec3( 0, -1, 0 );
// Set the speed of this movement
desiredVelocity = spawnArgs.GetFloat( "initialVelocity", "10" ); // TODO: Make this random
// Calculate how far the wraith should fly and how long that should take
// NOTE: If we make the velocity a random range, then we can make this spawn time constant
countDownTimer = spawnArgs.GetFloat( "spawnTime", "0.5" ) * 60; // Half a second to raise up
} else {
state = WS_FLY;
}
// Initialize .def values
velocity_xy = spawnArgs.GetFloat( "velocity_xy", "7" ) * (60.0f * USERCMD_ONE_OVER_HZ);
velocity_z = spawnArgs.GetFloat( "velocity_z", "1" ) * (60.0f * USERCMD_ONE_OVER_HZ);
velocity_z_fast = spawnArgs.GetFloat( "velocity_z_fast", "5" ) * (60.0f * USERCMD_ONE_OVER_HZ);
dist_z_close = spawnArgs.GetFloat( "dist_z_close", "5" );
dist_z_far = spawnArgs.GetFloat( "dist_z_far", "100" );
turn_threshold = DEG2RAD( spawnArgs.GetFloat( "turn_threshold", "5" ) ); // CJR PCF 5/17/06: Removed unnecessary 30Hz compensation
turn_radius_max = DEG2RAD( spawnArgs.GetFloat( "turn_radius_max", "130" ) ); // CJR PCF 5/17/06: Remove unnecessary 30Hz compensation
straight_ticks = spawnArgs.GetInt( "straight_ticks", "30" ) * (USERCMD_HZ / 60.0f);
damage_ticks = spawnArgs.GetFloat( "damage_ticks", "8" ) * (USERCMD_HZ / 60.0f);
turn_ticks = spawnArgs.GetFloat( "turn_ticks", "200" ) * (USERCMD_HZ / 60.0f);
flee_speed_z = spawnArgs.GetFloat( "flee_speed_z", "10" ) * (60.0f * USERCMD_ONE_OVER_HZ);
target_z_threshold = spawnArgs.GetFloat( "target_z_threshold", "20" );
// JRM: are we waiting for a trigger? Then no sound.
if(!spawnArgs.GetBool("trigger","0")) {
StartSound( "snd_flyloop", SND_CHANNEL_BODY );
StartSound( "snd_sight", SND_CHANNEL_VOICE );
// If this wraith should flee when spawned, then kill it immediately and get it fleeing
// This is used by the possessed kids to spawn a wraith that flees immediately
if ( spawnArgs.GetBool("flee_at_spawn", "0" ) ) {
// Fleeing
Killed( this, this, 0, vec3_origin, 0 );
isScaling = true;
scaleStart = spawnArgs.GetFloat( "scaleStart", "1" );
scaleEnd = spawnArgs.GetFloat( "scaleEnd", "1" );
scaleTime = spawnArgs.GetFloat( "scaleTime", "1" );
// Set data for dynamically scaling the wraith
SetShaderParm( SHADERPARM_ANY_DEFORM, DEFORMTYPE_SCALE ); // Scale deform
SetShaderParm( SHADERPARM_ANY_DEFORM_PARM1, scaleStart ); // Scale deform
lastScaleTime = MS2SEC( gameLocal.time ) + scaleTime;
}
}
Event_SetMoveType(MOVETYPE_FLY); // JRM to get the wraiths to move again
GetPhysics()->SetContents( CONTENTS_SHOOTABLE|CONTENTS_SHOOTABLEBYARROW );
GetPhysics()->SetClipMask( 0 );
bFaceEnemy = false;
nextDrop = 0;
nextChatter = 0;
nextPossessTime = 0;
minChatter = SEC2MS(spawnArgs.GetFloat( "min_chatter_time", "3" ));
maxChatter = SEC2MS(spawnArgs.GetFloat( "max_chatter_time", "6" ));
if ( !IsHidden() ) {
Hide();
PostEventMS( &EV_Activate, 0, this );
}
}
//=============================================================================
//
// hhWraith::~hhWraith
//
//=============================================================================
hhWraith::~hhWraith() {
StopSound( SND_CHANNEL_BODY );
}
//=============================================================================
//
// hhWraith::Save
//
//=============================================================================
void hhWraith::Save( idSaveGame *savefile ) const {
savefile->WriteInt( lastAnim );
savefile->WriteBool( canPossess );
savefile->WriteVec3( velocity );
savefile->WriteInt( damageTicks );
savefile->WriteInt( straightTicks );
savefile->WriteInt( turnTicks );
savefile->WriteVec3( lastCheckOrigin );
savefile->WriteFloat( lastDamageTime );
savefile->WriteInt( state );
savefile->WriteFloat( velocity_xy );
savefile->WriteFloat( velocity_z );
savefile->WriteFloat( velocity_z_fast );
savefile->WriteFloat( dist_z_close );
savefile->WriteFloat( dist_z_far );
savefile->WriteFloat( turn_threshold );
savefile->WriteFloat( turn_radius_max );
savefile->WriteInt( straight_ticks );
savefile->WriteInt( damage_ticks );
savefile->WriteInt( turn_ticks );
savefile->WriteFloat( flee_speed_z );
savefile->WriteFloat( target_z_threshold );
savefile->WriteFloat( minDamageDist );
savefile->WriteBool( isScaling );
savefile->WriteFloat( scaleStart );
savefile->WriteFloat( scaleEnd );
savefile->WriteFloat( lastScaleTime );
savefile->WriteInt( countDownTimer );
savefile->WriteMat3( desiredAxis );
savefile->WriteFloat( desiredVelocity );
savefile->WriteBool( bFaceEnemy );
fxFly.Save( savefile );
}
//=============================================================================
//
// hhWraith::Restore
//
//=============================================================================
void hhWraith::Restore( idRestoreGame *savefile ) {
savefile->ReadInt( lastAnim );
savefile->ReadBool( canPossess );
savefile->ReadVec3( velocity );
savefile->ReadInt( damageTicks );
savefile->ReadInt( straightTicks );
savefile->ReadInt( turnTicks );
savefile->ReadVec3( lastCheckOrigin );
savefile->ReadFloat( lastDamageTime );
savefile->ReadInt( (int &)state );
savefile->ReadFloat( velocity_xy );
savefile->ReadFloat( velocity_z );
savefile->ReadFloat( velocity_z_fast );
savefile->ReadFloat( dist_z_close );
savefile->ReadFloat( dist_z_far );
savefile->ReadFloat( turn_threshold );
savefile->ReadFloat( turn_radius_max );
savefile->ReadInt( straight_ticks );
savefile->ReadInt( damage_ticks );
savefile->ReadInt( turn_ticks );
savefile->ReadFloat( flee_speed_z );
savefile->ReadFloat( target_z_threshold );
savefile->ReadFloat( minDamageDist );
savefile->ReadBool( isScaling );
savefile->ReadFloat( scaleStart );
savefile->ReadFloat( scaleEnd );
savefile->ReadFloat( lastScaleTime );
savefile->ReadInt( countDownTimer );
savefile->ReadMat3( desiredAxis );
savefile->ReadFloat( desiredVelocity );
savefile->ReadBool( bFaceEnemy );
// List animations
flyAnim = GetAnimator()->GetAnim( "fly" );
possessAnim = GetAnimator()->GetAnim( "alert" );
leftAnim = GetAnimator()->GetAnim( "bankLeft" );
rightAnim = GetAnimator()->GetAnim( "bankRight" );
fleeAnim = GetAnimator()->GetAnim( "flee" );
fleeInAnim = GetAnimator()->GetAnim( "fleeIn" );
canPossess = spawnArgs.GetBool( "possess", "1" );
minDamageDist = spawnArgs.GetFloat( "minDamageDist", "50" );
// Initialize .def values
velocity_xy = spawnArgs.GetFloat( "velocity_xy", "7" );
velocity_z = spawnArgs.GetFloat( "velocity_z", "1" );
velocity_z_fast = spawnArgs.GetFloat( "velocity_z_fast", "5" );
dist_z_close = spawnArgs.GetFloat( "dist_z_close", "5" );
dist_z_far = spawnArgs.GetFloat( "dist_z_far", "100" );
turn_threshold = DEG2RAD( spawnArgs.GetFloat( "turn_threshold", "5" ) );
turn_radius_max = DEG2RAD( spawnArgs.GetFloat( "turn_radius_max", "130" ) );
straight_ticks = spawnArgs.GetInt( "straight_ticks", "30" ) * (60.0f * USERCMD_ONE_OVER_HZ);
damage_ticks = spawnArgs.GetFloat( "damage_ticks", "8" ) * (60.0f * USERCMD_ONE_OVER_HZ);
turn_ticks = spawnArgs.GetFloat( "turn_ticks", "200" ) * (60.0f * USERCMD_ONE_OVER_HZ);
flee_speed_z = spawnArgs.GetFloat( "flee_speed_z", "10" );
target_z_threshold = spawnArgs.GetFloat( "target_z_threshold", "20" );
nextDrop = 0;
nextChatter = 0;
nextPossessTime = gameLocal.time + 500; // Don't possess for half a second after loading
minChatter = SEC2MS(spawnArgs.GetFloat( "min_chatter_time", "3" ));
maxChatter = SEC2MS(spawnArgs.GetFloat( "max_chatter_time", "6" ));
fxFly.Restore( savefile );
nextCheckTime = gameLocal.time + spawnArgs.GetInt("trace_check_time", "200");
scaleTime = spawnArgs.GetFloat( "scaleTime", "1" );
}
//=============================================================================
//
// hhWraith::Think
//
//=============================================================================
void hhWraith::Think(void) {
if(!IsHidden()) { // JRM - NO TRACES WHEN HIDDEN!
CheckCollisions();
if (gameLocal.time > nextDrop) {
hhMonsterAI::Damage(this, this, vec3_origin, "damage_wraithminion", 0.05f, INVALID_JOINT);
nextDrop = gameLocal.time + 1000;
}
if (gameLocal.time > nextChatter) {
StartSound( "snd_chatter", SND_CHANNEL_VOICE );
nextChatter = gameLocal.time + (minChatter + gameLocal.random.RandomFloat() * (maxChatter - minChatter));
}
}
// Scale the wraith
if ( isScaling ) {
float delta = lastScaleTime - MS2SEC( gameLocal.time );
if ( delta > 0 ) {
SetShaderParm( SHADERPARM_ANY_DEFORM_PARM1, hhMath::Lerp( scaleEnd, scaleStart, delta / scaleTime ) );
}
}
hhMonsterAI::Think(); // JRM: Bypassing hhAI::Think()
}
//=============================================================================
//
// hhWraith::Event_Activate
//
//=============================================================================
void hhWraith::Event_Activate(idEntity *activator) {
if ( IsHidden() ) {
TeleportIn(activator);
return;
}
if ( spawnArgs.GetBool("flee_at_spawn", "0" ) ) {
// Fleeing
Killed( this, this, 0, vec3_origin, 0 );
isScaling = true;
scaleStart = spawnArgs.GetFloat( "scaleStart", "1" );
scaleEnd = spawnArgs.GetFloat( "scaleEnd", "1" );
scaleTime = spawnArgs.GetFloat( "scaleTime", "1" );
// Set data for dynamically scaling the wraith
SetShaderParm( SHADERPARM_ANY_DEFORM, DEFORMTYPE_SCALE ); // Scale deform
SetShaderParm( SHADERPARM_ANY_DEFORM_PARM1, scaleStart ); // Scale deform
lastScaleTime = MS2SEC( gameLocal.time ) + scaleTime;
}
hhMonsterAI::Event_Activate(activator);
// we've been waiting for a trigger
if (spawnArgs.GetBool("trigger")) {
StartSound( "snd_flyloop", SND_CHANNEL_BODY );
StartSound( "snd_sight", SND_CHANNEL_VOICE );
}
const char *defName = spawnArgs.GetString("fx_fly");
if (defName && defName[0]) {
hhFxInfo fxInfo;
fxInfo.SetNormal( -GetAxis()[0] );
fxInfo.SetEntity( this );
fxInfo.RemoveWhenDone( false );
fxFly = SpawnFxLocal( defName, GetOrigin(), GetAxis(), &fxInfo, gameLocal.isClient );
if (fxFly.IsValid()) {
fxFly->fl.neverDormant = true;
fxFly->fl.networkSync = false;
fxFly->fl.clientEvents = true;
}
}
}
//=============================================================================
//
// hhWraith::FindEnemy
//
//=============================================================================
void hhWraith::Event_FindEnemy( int useFOV ) {
int i;
idEntity *ent;
idActor *closest;
idActor *actor;
float closestDist;
float distSquared;
closest = NULL;
closestDist = 99999999.0f;
if (team != 0) { // Search players
for ( i = 0; i < gameLocal.numClients ; i++ ) {
ent = gameLocal.entities[ i ];
if ( !ent || !ent->IsType( idActor::Type ) ) {
continue;
}
if ( ent->IsType( hhPlayer::Type ) ) { // Check if the player is really dead or deathwalking
hhPlayer *player = static_cast< hhPlayer * >( ent );
if ( player->IsDead() || player->IsDeathWalking() ) {
continue;
}
}
// Find the closest enemy
distSquared = ( GetOrigin() - ent->GetOrigin() ).LengthSqr();
if ( distSquared < closestDist ) {
closestDist = distSquared;
closest = static_cast<idActor *>( ent );
}
}
}
else { // Search monsters
int numMonsters = hhMonsterAI::allSimpleMonsters.Num();
for ( i = 0; i < numMonsters ; i++ ) {
actor = hhMonsterAI::allSimpleMonsters[ i ];
if ( !actor || actor==this ) {
continue;
}
// Ignore dormant entities!
if (actor->IsHidden() || actor->fl.isDormant) {
continue;
}
if ( (actor->health <= 0) || !(ReactionTo(actor) & ATTACK_ON_SIGHT) ) {
continue;
}
distSquared = (actor->GetOrigin() - GetOrigin()).LengthSqr();
if ( distSquared < closestDist ) {
closestDist = distSquared;
closest = actor;
}
}
}
if (closest) {
SetEnemy( closest );
}
else {
// FIXME: No enemies found, should we just go away or circle for a while?
Damage(this, this, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT);
}
idThread::ReturnEntity( closest );
}
//=============================================================================
//
// hhWraith::UpdateEnemyPosition
//
// This is here to override the default UpdateEnemyPosition, which
// runs code that is unnecessary for the wraiths
//=============================================================================
void hhWraith::UpdateEnemyPosition( void ) {
// Do nothing. Wraith's don't need to check if the enemy is no longer visible, since they can go through walls
}
//=============================================================================
//
// hhWraith::EnemyDead
//
//=============================================================================
void hhWraith::EnemyDead() {
if ( !enemy.IsValid() || !enemy->IsType( hhPlayer::Type ) ) { // Only consider the enemy dead if the enemy is not a player
hhMonsterAI::EnemyDead();
} else if ( enemy.IsValid() && enemy->IsType( hhPlayer::Type ) ) { // If the player is deathwalking, then the enemy is dead
hhPlayer *player = static_cast< hhPlayer * >( enemy.GetEntity() );
if ( player->IsDead() || player->IsDeathWalking() ) {
hhMonsterAI::EnemyDead();
}
}
}
//=============================================================================
//
// hhWraith::FlyMove
//
//=============================================================================
void hhWraith::FlyMove( void ) {
// The state of the creature determines how it will move
switch ( state ) {
case WS_SPAWN:
FlyUp(); // Flying up right after spawn
break;
case WS_FLY:
FlyToEnemy();
break;
case WS_FLEE: // Flying away
FlyAway();
break;
// case WS_POSSESS_CHARGE:
// WraithDamageEnemy();
// break;
case WS_STILL: // Wraith is not moving at all (playing a specific anim, etc)
return;
}
// run the physics for this frame
physicsObj.UseFlyMove( true );
physicsObj.UseVelocityMove( false );
physicsObj.SetDelta( vec3_zero );
physicsObj.ForceDeltaMove( disableGravity );
RunPhysics();
}
//=============================================================================
//
// hhWraith::FlyToEnemy
//
//=============================================================================
void hhWraith::FlyToEnemy( void ) {
float delta = 0;
bool dir = false;
int anim;
if ( !enemy.IsValid() ) {
PostEventMS( &AI_FindEnemy, 1, 0 );
return;
}
if (canPossess) {
// Determine which direction the wraith needs to turn to the enemy (if the wraith should turn)
if ( --straightTicks <= 0 ) {
dir = GetFacePosAngle( enemy->GetOrigin(), delta );
turnTicks++; // Add in the number of ticks that the wraith has been turning
// If the wraith has been turning for a very long period of time (more than 4 seconds), then force the wraith to go straight
// Vary turn_ticks by +/- 25 depending on the DDA value
if ( turnTicks > this->turn_ticks - (25 * ((gameLocal.GetDDAValue() - 0.5f) * 2))) {
dir = 0;
delta = 0;
turnTicks = 0;
deltaViewAngles.yaw = 0;
}
} else {
dir = 0;
delta = 0;
deltaViewAngles.yaw = 0;
turnTicks = 0;
}
}
// Turn the wraith, and set the correct bank animation
anim = flyAnim;
if ( delta > this->turn_threshold ) { // Wraith should turn
if ( dir ) { // Turn to the left
deltaViewAngles.yaw = this->turn_radius_max;
anim = leftAnim;
} else {
deltaViewAngles.yaw = -this->turn_radius_max;
anim = rightAnim;
}
} else if ( straightTicks <= 0 ) { // Straight at the player
deltaViewAngles.yaw = 0;
// Vary straight_ticks by +/- 25 depending on the DDA value
straightTicks = this->straight_ticks - (25 * ((gameLocal.GetDDAValue() - 0.5f) * 2)); // Stay on this path for a short period of time
}
// Actually set the animation
if ( anim != lastAnim ) {
GetAnimator()->ClearAllAnims( gameLocal.time, TWEEN_BANK );
GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, TWEEN_BANK );
lastAnim = anim;
}
velocity = viewAxis[0] * this->velocity_xy;
if ( damageTicks > 0 ) { // Fly slower if damaged
velocity *= 0.85f;
velocity += 10 * idVec3( gameLocal.random.CRandomFloat(), gameLocal.random.CRandomFloat(), gameLocal.random.CRandomFloat() );
damageTicks--;
}
// Equalize the z-height of the wraith relative to its target
idVec3 enemyOrigin = enemy->GetEyePosition();
idVec3 origin = GetPhysics()->GetOrigin();
// Factor in fly_offset
enemyOrigin.z += fly_offset;
// Adjust z-velocity based upon the delta Z to target
velocity.z = 0;
if( ( origin.z > enemyOrigin.z + this->target_z_threshold ) || ( origin.z < enemyOrigin.z - this->target_z_threshold ) ) {
float newZ = enemyOrigin.z + target_z_threshold * 0.5f;
float deltaZ = newZ - origin.z;
if ( abs( deltaZ ) > this->dist_z_far ) {
velocity.z = this->velocity_z_fast * ( ( deltaZ > 0 ) ? 1 : -1 );
} else if ( abs( deltaZ ) > this->dist_z_close ) {
velocity.z = this->velocity_z * ( ( deltaZ > 0 ) ? 1 : -1 );
}
}
// 1.5x the velocity if DDA is 1.0, half it for DDA = 0.0, leave it the same for normal (DDA = 0.5)
GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() + (velocity * (gameLocal.GetDDAValue() + 0.5f)));
UpdateVisuals();
}
//=============================================================================
//
// hhWraith::FlyAway
//
// Wraiths will automatically fly away from their enemy and up
// The Wraith will eventually be removed from the world in the CheckFleeRemove function
//=============================================================================
void hhWraith::FlyAway( void ) {
velocity.x = 0;
velocity.y = 0;
velocity.z = this->flee_speed_z;
GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() + velocity );
UpdateVisuals();
// Check to see if the wraith has flown out of the world
CheckFleeRemove();
}
//=============================================================================
//
// hhWraith::FlyUp
//
// mdl: Wraiths will automatically fly up after being spawned if spawnFlyUp is set to 1
// NOT related to flying away during death, that's FlyAway()
//=============================================================================
void hhWraith::FlyUp( void ) {
idVec3 velocity;
// Turn towards desired angle
// TODO: Make this smooth
SetAxis( desiredAxis );
// Apply velocity
velocity = desiredAxis[0] * desiredVelocity;
SetOrigin( GetOrigin() + velocity );
UpdateVisuals();
// If the wraith is near the end of the spawn, then smoothly rotate the wraith to the horizontal
if ( --countDownTimer <= 0 ) {
state = WS_FLY;
}
}
//=============================================================================
//
// hhWraith::TurnTowardEnemy
//
//=============================================================================
void hhWraith::TurnTowardEnemy() {
float delta;
bool dir;
if ( !enemy.IsValid() ) {
return;
}
// Face torward the enemy
dir = GetFacePosAngle( enemy->GetOrigin(), delta );
if ( delta > turn_threshold ) {
// Turn based upon delta angles
if ( dir ) { // Turn to the right
deltaViewAngles.yaw = turn_radius_max;
} else {
deltaViewAngles.yaw = -turn_radius_max;
}
}
UpdateVisuals();
}
//=============================================================================
//
// hhWraith::CheckFleeRemove
//
//=============================================================================
void hhWraith::CheckFleeRemove( void ) {
// Check if the Wraith has flown out of the world
if( gameLocal.clip.Contents( GetPhysics()->GetOrigin() + idVec3(0, 0, 48), GetPhysics()->GetClipModel(), viewAxis, CONTENTS_SOLID, this ) ) {
Hide();
PostEventSec( &EV_Remove, 2.0f ); // Keep the wraith around for a few seconds so the sounds can finish
state = WS_STILL; // No reason to keep moving the wraith
}
}
//=============================================================================
//
// hhWraith::Damage
//
//=============================================================================
void hhWraith::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
damageTicks = this->damage_ticks;
}
//=============================================================================
//
// hhWraith::Killed
//
//=============================================================================
void hhWraith::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
hhFxInfo fx;
//fx.SetEntity( this );
fx.RemoveWhenDone( true );
const char *deathFX;
// Stop flyloop and chatter sounds, if any
StopSound( SND_CHANNEL_BODY );
StopSound( SND_CHANNEL_VOICE );
if ( inflictor == this ) {
// If we timed out, don't drop a soul
spawnArgs.Delete( "def_dropSoul" );
deathFX = spawnArgs.GetString( "fx_death" );
} else {
// Use alternate death fx if we were killed by the player
deathFX = spawnArgs.GetString( "fx_death2" );
}
SpawnFxLocal( deathFX, GetOrigin(), GetAxis(), &fx );
SAFE_REMOVE(fxFly);
hhMonsterAI::Killed( inflictor, attacker, damage, dir, location );
StartSound( "snd_death", SND_CHANNEL_VOICE );
Hide();
// activate targets
//ActivateTargets( this );
PostEventSec( &EV_Remove, 4 );
}
//=============================================================================
//
// hhWraith::CheckCollisions
//
// Check if the wraith is entering or exiting the world
//=============================================================================
void hhWraith::CheckCollisions( void ) {
if (gameLocal.time < nextCheckTime) {
return;
}
hhFxInfo fxInfo;
trace_t tr1;
trace_t tr2;
idVec3 jointPos;
idMat3 jointAxis;
if (!GetJointWorldTransform( spawnArgs.GetString("joint_collision"), jointPos, jointAxis )) {
jointPos = GetOrigin();
jointAxis = GetAxis();
}
// Trace to determine if the wraith just flew through a wall, or just emerged from a wall
// For speed, this only checks against the world
idVec3 dir, point;
gameLocal.clip.TracePoint( tr1, jointPos, lastCheckOrigin, MASK_SHOT_BOUNDINGBOX, this ); // Check if we entered something solid
gameLocal.clip.TracePoint( tr2, lastCheckOrigin, jointPos, MASK_SHOT_BOUNDINGBOX, this ); // Check if we exited something solid
idVec3 playerOrigin = gameLocal.GetLocalPlayer()->GetOrigin();
if ( tr1.fraction < 1.0f ) {
point = tr1.c.point;
dir = tr1.c.normal;
}
else if ( tr2.fraction < 1.0f) {
point = tr2.c.point;
dir = tr2.c.normal;
}
if ( tr1.fraction < 1.0f && tr2.fraction < 1.0f ) {
if ( (tr1.c.point - playerOrigin).LengthSqr() <= (tr2.c.point - playerOrigin).LengthSqr() ) {
point = tr1.c.point;
dir = tr1.c.normal;
}
else {
point = tr2.c.point;
dir = tr2.c.normal;
}
}
if ( tr1.fraction < 1.0f || tr2.fraction < 1.0f ) {
// Spawn an FX at wall collision
//TODO: IMPORTANT: This currently spawns a system every check while passing through something. This creates enormous
// amounts of particles. Fix so there is a single system spawned on the way in and a single system spawned on the way out.
//TODO: This currently hits players so you get a big blob of particles right in your face and right behind you.
const char *hitWallFxName = spawnArgs.GetString("fx_hitwall");
if (hitWallFxName && *hitWallFxName) {
fxInfo.RemoveWhenDone( true );
BroadcastFxInfo(hitWallFxName, point, mat3_identity, &fxInfo);
}
// Project a spooge decal on the wall here
const char *decalName = spawnArgs.GetString("mtr_decal", NULL);
if (decalName && *decalName) {
float depth = spawnArgs.GetFloat("decal_trace");
float size = spawnArgs.GetFloat( "decal_size" );
// gameRenderWorld->DebugArrow(colorRed, point, point + dir*128, 5, 10000);
gameLocal.ProjectDecal(tr1.c.point, -tr1.c.normal, depth, true, size, decalName);
gameLocal.ProjectDecal(tr2.c.point, -tr2.c.normal, depth, true, size, decalName);
}
}
// Possession check
if ( canPossess && gameLocal.time > nextPossessTime ) {
gameLocal.clip.TraceBounds( tr1, lastCheckOrigin, jointPos, GetPhysics()->GetBounds(), MASK_SHOT_BOUNDINGBOX, this );
if ( gameLocal.entities[ tr1.c.entityNum ] && gameLocal.entities[ tr1.c.entityNum ]->IsType( idActor::Type ) ) {
// If we hit a possessable actor, possess them
idActor *actor = reinterpret_cast<idActor *> ( gameLocal.entities[ tr1.c.entityNum ] );
if ( actor != lastActor.GetEntity() ) {
// Play the attack sound
StartSound( "snd_attack", SND_CHANNEL_VOICE );
if ( actor->IsType( hhPlayer::Type ) ) {
hhPlayer *player = reinterpret_cast<hhPlayer *> ( gameLocal.entities[ tr1.c.entityNum ] );
int power = player->GetSpiritPower();
power -= 25;
if ( power <= 0 ) { // Possess the player
power = 0;
// mdl: Wraiths no longer possess
//if ( player->CanBePossessed() ) {
// Possessable
//WraithPossess( player );
//} else
if ( enemy.GetEntity() == player && player->IsSpiritWalking() && !player->IsPossessed() ) {
// Send the player back to his body
reinterpret_cast<hhPlayer *> (enemy.GetEntity())->DisableSpiritWalk(5);
// Don't immediately possess if the players body happens to be right there.
nextPossessTime = gameLocal.time + 1000;
}
} else { // Gain health from the player's spirit power
health += 5;
if ( health > spawnHealth ) {
health = spawnHealth;
}
}
player->SetSpiritPower( power );
}// else if ( actor->CanBePossessed() ) {
// // Possess the actor
// WraithPossess( actor );
//}
}
lastActor = actor;
} else {
lastActor = NULL;
}
}
nextCheckTime = gameLocal.time + spawnArgs.GetInt("trace_check_time", "200");
lastCheckOrigin = jointPos;
}
//=============================================================================
//
// hhWraith::Event_Flee
//
// The wraith is about to flee and has already played the flee intro anim
//=============================================================================
void hhWraith::Event_Flee( ) {
GetAnimator()->ClearAllAnims( gameLocal.time, 0 );
GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, fleeAnim, gameLocal.time, 0 );
state = WS_FLEE;
}
//=============================================================================
//
// hhWraith::PlayAnimMove
//
//=============================================================================
void hhWraith::PlayAnimMove( int anim, int blendTime ) {
GetAnimator()->ClearAllAnims( gameLocal.time, blendTime );
GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, blendTime );
state = WS_STILL;
PostEventMS( &EV_PlayAnimMoveEnd, GetAnimator()->GetAnim( possessAnim )->Length() - blendTime );
}
//=============================================================================
//
// hhWraith::PlayAnimMoveEnd
//
//=============================================================================
void hhWraith::PlayAnimMoveEnd() {
idVec3 boneOrigin;
idMat3 boneAxis;
GetJointWorldTransform( "Head", boneOrigin, boneAxis );
GetPhysics()->SetOrigin( boneOrigin ); // Move the wraith to the end of the animation
GetAnimator()->ClearAllAnims( gameLocal.time, 0 );
GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, flyAnim, gameLocal.time, 0 );
state = WS_FLY;
damageTicks = 0;
}
//=============================================================================
//
// hhWraith::WraithPossess
//
// Actually possess an actor
//=============================================================================
void hhWraith::WraithPossess( idActor *actor ) {
hhFxInfo fxInfo;
actor->Possess( this );
// Spawn in a flash
fxInfo.RemoveWhenDone( true );
BroadcastFxInfoPrefixed( "fx_possessionFlash", GetOrigin(), GetAxis(), &fxInfo );
// activate targets
ActivateTargets( actor );
PostEventMS( &EV_Remove, 0 );
}
//=============================================================================
//
// hhWraith::Event_PlayAnimMoveEnd
//
//=============================================================================
void hhWraith::Event_PlayAnimMoveEnd( ) {
PlayAnimMoveEnd();
}
void hhWraith::Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ) {
// Only switch to the proxy if the player has no spirit power
//if ( player->GetSpiritPower() == 0 ) {
// hhMonsterAI::Event_EnemyIsSpirit( player, proxy );
//}
}
void hhWraith::Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ) {
// We don't care whether we can see the enemy or not
enemy = player;
}
void hhWraith::TeleportIn( idEntity *activator ) {
if ( spawnArgs.GetBool( "quickSpawn" ) ) {
PostEventMS( &EV_Show, 0 );
PostEventMS( &EV_Activate, 50, activator );
}
else {
hhFxInfo fx;
fx.RemoveWhenDone( true );
fx.SetEntity( this );
SpawnFxLocal( spawnArgs.GetString( "fx_spawn" ), GetOrigin(), GetAxis(), &fx );
PostEventMS( &EV_Show, 1000 );
PostEventMS( &EV_Activate, 1050, activator );
}
}
void hhWraith::Portalled(idEntity *portal) {
hhMonsterAI::Portalled( portal );
if ( fxFly.IsValid() ) {
hhFxInfo fxInfo;
fxInfo.SetNormal( -GetAxis()[0] );
fxInfo.SetEntity( this );
fxInfo.RemoveWhenDone( false );
fxFly->SetFxInfo( fxInfo );
// Reset the fx system
fxFly->Stop();
fxFly->Start( gameLocal.time );
}
}