mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 08:11:10 +02:00
390 lines
12 KiB
C++
390 lines
12 KiB
C++
#include "precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "prey_local.h"
|
|
|
|
const idEventDef MH_AlertFriends("alertFriends", NULL, NULL);
|
|
const idEventDef MH_DropBinds("dropBinds");
|
|
const idEventDef MH_DropProjectiles("<dropProjectiles>" );
|
|
|
|
CLASS_DECLARATION(hhMonsterAI, hhMutilatedHuman)
|
|
EVENT( MH_AlertFriends, hhMutilatedHuman::Event_AlertFriends )
|
|
EVENT( MH_DropBinds, hhMutilatedHuman::Event_DropBinds )
|
|
EVENT( MH_DropProjectiles, hhMutilatedHuman::Event_DropProjectiles )
|
|
END_CLASS
|
|
|
|
void hhMutilatedHuman::Spawn() {
|
|
damageFlag = 0;
|
|
}
|
|
|
|
void hhMutilatedHuman::Event_DropBinds( ) {
|
|
idEntity *ent;
|
|
idEntity *next;
|
|
for( ent = teamChain; ent != NULL; ent = next ) {
|
|
next = ent->GetTeamChain();
|
|
if ( ent && !ent->IsType( hhProjectile::Type ) && !ent->IsType( idEntityFx::Type ) && ent->GetBindMaster() == this && ent != head.GetEntity() ) {
|
|
ent->Unbind();
|
|
next = teamChain;
|
|
}
|
|
}
|
|
}
|
|
|
|
void hhMutilatedHuman::Event_DropProjectiles() {
|
|
idEntity *ent;
|
|
idEntity *next;
|
|
for( ent = teamChain; ent != NULL; ent = next ) {
|
|
next = ent->GetTeamChain();
|
|
if ( ent && ent->IsType( hhProjectile::Type ) ) {
|
|
ent->Unbind();
|
|
ent->Hide();
|
|
ent->PostEventSec( &EV_Remove, 5 );
|
|
next = teamChain;
|
|
}
|
|
}
|
|
}
|
|
|
|
void hhMutilatedHuman::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
|
|
if (!fl.takedamage) {
|
|
return;
|
|
}
|
|
hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
|
|
|
|
if ( AI_LIMB_FELL ) {
|
|
PostEventSec( &MH_DropProjectiles, 0.1f );
|
|
}
|
|
|
|
if ( AI_DEAD ) {
|
|
return;
|
|
}
|
|
|
|
idDict args;
|
|
idVec3 bonePos;
|
|
idMat3 boneAxis;
|
|
idStr debrisName;
|
|
idStr damageGroup = GetDamageGroup( location );
|
|
|
|
const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
|
|
if ( damageDef && damageDef->GetBool( "ice" ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( !( damageGroup == "head" && damageDef->GetInt( "damage" ) >= spawnArgs.GetFloat( "head_falloff_damage", "70" ) ) ) {
|
|
if ( gameLocal.random.RandomFloat() >= spawnArgs.GetFloat( "chanceLimbsWillFallOff", "0.1" ) ) { // CJR: Build in a chance that the limbs will fall off
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( damageGroup == "left_arm" && !(damageFlag & BIT(0)) ) {
|
|
damageFlag |= BIT(0);
|
|
debrisName = spawnArgs.GetString( "def_debris_arm" );
|
|
if ( !debrisName.IsEmpty() ) {
|
|
idEntity *debris = gameLocal.SpawnObject( debrisName, &args );
|
|
if ( debris ) {
|
|
AI_LIMB_FELL = true;
|
|
GetJointWorldTransform( spawnArgs.GetString( "bone_arm_left" ), bonePos, boneAxis );
|
|
debris->SetOrigin( bonePos );
|
|
debris->GetPhysics()->SetLinearVelocity( spawnArgs.GetVector( "debris_arm_velocity" ) * GetAxis() );
|
|
debris->GetPhysics()->SetAngularVelocity( 60 * hhUtils::RandomVector() );
|
|
BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_left_arm_blood"), spawnArgs.GetString("bone_arm_left") );
|
|
}
|
|
}
|
|
|
|
} else if ( damageGroup == "right_arm" && !(damageFlag & BIT(1)) ) {
|
|
damageFlag |= BIT(1);
|
|
debrisName = spawnArgs.GetString( "def_debris_arm" );
|
|
if ( !debrisName.IsEmpty() ) {
|
|
idEntity *debris = gameLocal.SpawnObject( debrisName, &args );
|
|
if ( debris ) {
|
|
AI_LIMB_FELL = true;
|
|
GetJointWorldTransform( spawnArgs.GetString( "bone_arm_right" ), bonePos, boneAxis );
|
|
debris->SetOrigin( bonePos );
|
|
debris->GetPhysics()->SetLinearVelocity( spawnArgs.GetVector( "debris_arm_velocity" ) * GetAxis() );
|
|
debris->GetPhysics()->SetAngularVelocity( 60 * hhUtils::RandomVector() );
|
|
BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_right_arm_blood"), spawnArgs.GetString("bone_arm_right") );
|
|
}
|
|
}
|
|
} else if ( damageGroup == "chest" && !(damageFlag & BIT(2)) ) {
|
|
damageFlag |= BIT(2);
|
|
damageFlag |= BIT(3);
|
|
debrisName = spawnArgs.GetString( "def_debris_chest" );
|
|
if ( !debrisName.IsEmpty() ) {
|
|
idEntity *debris = gameLocal.SpawnObject( debrisName, &args );
|
|
if ( debris ) {
|
|
AI_LIMB_FELL = true;
|
|
GetJointWorldTransform( spawnArgs.GetString( "bone_chest" ), bonePos, boneAxis );
|
|
debris->SetOrigin( bonePos );
|
|
debris->GetPhysics()->SetLinearVelocity( spawnArgs.GetVector( "debris_chest_velocity" ) * GetAxis() );
|
|
debris->GetPhysics()->SetAngularVelocity( 60 * hhUtils::RandomVector() );
|
|
BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_chest_blood"), spawnArgs.GetString("bone_chest") );
|
|
}
|
|
}
|
|
} else if ( damageGroup == "head" ) {
|
|
if ( head.IsValid() && !head->IsHidden() ) {
|
|
debrisName = spawnArgs.GetString( "def_debris_head" );
|
|
if ( !debrisName.IsEmpty() ) {
|
|
idEntity *debris = gameLocal.SpawnObject( debrisName, &args );
|
|
if ( debris ) {
|
|
AI_LIMB_FELL = true;
|
|
GetJointWorldTransform( spawnArgs.GetString( "bone_head" ), bonePos, boneAxis );
|
|
debris->SetOrigin( bonePos );
|
|
debris->GetPhysics()->SetLinearVelocity( spawnArgs.GetVector( "debris_head_velocity" ) * GetAxis() );
|
|
debris->GetPhysics()->SetAngularVelocity( 60 * hhUtils::RandomVector() );
|
|
BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_head_blood"), spawnArgs.GetString("bone_head") );
|
|
}
|
|
}
|
|
head->Hide();
|
|
}
|
|
}
|
|
|
|
idStr skinName = spawnArgs.GetString("damage_skin");
|
|
skinName += damageFlag;
|
|
if ( damageFlag > 0 ) {
|
|
SetSkin( declManager->FindSkin( skinName ) );
|
|
UpdateVisuals();
|
|
}
|
|
}
|
|
|
|
void hhMutilatedHuman::Event_AlertFriends() {
|
|
hhMonsterAI *check;
|
|
if ( !enemy.IsValid() ) {
|
|
return;
|
|
}
|
|
|
|
for( int i = 0; i < targets.Num(); i++ ) {
|
|
check = static_cast<hhMonsterAI *>(targets[ i ].GetEntity());
|
|
if ( !check || check->IsHidden() || !check->IsType( hhMutilatedHuman::Type ) ) {
|
|
continue;
|
|
}
|
|
check->SetEnemy( enemy.GetEntity() );
|
|
}
|
|
}
|
|
|
|
void hhMutilatedHuman::AnimMove( void ) {
|
|
//overridden to set enemy if blockedEnt can be an enemy
|
|
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
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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( idActor::Type ) && ReactionTo( blockEnt ) != ATTACK_IGNORE ) {
|
|
SetEnemy( static_cast<idActor*>(blockEnt) );
|
|
}
|
|
if ( blockEnt && blockEnt->IsType( hhPod::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
|
|
KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
|
|
blockEnt->Damage( this, this, vec3_zero, spawnArgs.GetString( "kick_damage" ), 1.0f, INVALID_JOINT );
|
|
}
|
|
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();
|
|
}
|
|
}
|
|
|
|
int hhMutilatedHuman::ReactionTo( const idEntity *ent ) {
|
|
const idActor *actor = static_cast<const idActor *>( ent );
|
|
if( actor && actor->IsType(hhDeathProxy::Type) ) {
|
|
return ATTACK_IGNORE;
|
|
}
|
|
|
|
if ( ent->health <= 0 ) {
|
|
return ATTACK_IGNORE;
|
|
}
|
|
|
|
if ( ent->fl.hidden ) {
|
|
// ignore hidden entities
|
|
return ATTACK_IGNORE;
|
|
}
|
|
|
|
if ( !ent->IsType( idActor::Type ) ) {
|
|
return ATTACK_IGNORE;
|
|
}
|
|
|
|
actor = static_cast<const idActor *>( ent );
|
|
if ( actor->IsType( idPlayer::Type ) && static_cast<const idPlayer *>(actor)->noclip ) {
|
|
// ignore players in noclip mode
|
|
return ATTACK_IGNORE;
|
|
}
|
|
|
|
//only attack spiritwalking players if they hurt me
|
|
if ( ent->IsType( hhPlayer::Type ) ) {
|
|
const hhPlayer *player = static_cast<const hhPlayer*>( ent );
|
|
if ( nextSpiritProxyCheck == 0 && player && player->IsSpiritWalking() ) {
|
|
nextSpiritProxyCheck = gameLocal.time + SEC2MS(2);
|
|
return ATTACK_ON_DAMAGE;
|
|
}
|
|
}
|
|
|
|
if ( ent->IsType( hhSpiritProxy::Type ) ) {
|
|
if ( gameLocal.time > nextSpiritProxyCheck ) {
|
|
nextSpiritProxyCheck = 0;
|
|
//attack spiritproxy on sight if we have no enemy or if its closer than our current enemy
|
|
if ( enemy.IsValid() && enemy->IsType( hhPlayer::Type ) ) {
|
|
float distToEnemy = (enemy->GetOrigin() - GetOrigin()).LengthSqr();
|
|
float distToProxy = (ent->GetOrigin() - GetOrigin()).LengthSqr();
|
|
if ( distToProxy < distToEnemy ) {
|
|
return ATTACK_ON_SIGHT;
|
|
}
|
|
} else {
|
|
return ATTACK_ON_SIGHT;
|
|
}
|
|
} else {
|
|
return ATTACK_IGNORE;
|
|
}
|
|
}
|
|
|
|
if ( ent && ent->spawnArgs.GetBool( "never_target" ) ) {
|
|
return ATTACK_IGNORE;
|
|
}
|
|
|
|
//if we're hurt, its probably because of the player, so keep him as an enemy
|
|
if ( actor->IsType( idPlayer::Type ) && health < spawnHealth ) {
|
|
return ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE | ATTACK_ON_SIGHT;
|
|
}
|
|
|
|
// actors on different teams will always fight each other
|
|
if ( actor->team != team ) {
|
|
if ( actor->fl.notarget ) {
|
|
// don't attack on sight when attacker is notargeted
|
|
if ( actor->IsType( idPlayer::Type ) ) {
|
|
return ATTACK_ON_DAMAGE;
|
|
} else {
|
|
return ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE;
|
|
}
|
|
}
|
|
//force players to only alert through damage
|
|
if ( actor->IsType( idPlayer::Type ) ) {
|
|
return ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE;
|
|
} else if ( actor->IsType( idAI::Type ) ) {
|
|
//only allow ai actors to otherwise alert them
|
|
return ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE;
|
|
}
|
|
}
|
|
|
|
// monsters will fight when attacked by lower ranked monsters. rank 0 never fights back.
|
|
if ( rank && ( actor->rank < rank ) ) {
|
|
return ATTACK_ON_DAMAGE;
|
|
}
|
|
|
|
// don't fight back
|
|
return ATTACK_IGNORE;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhMutilatedHuman::Save
|
|
=====================
|
|
*/
|
|
void hhMutilatedHuman::Save( idSaveGame *savefile ) const {
|
|
savefile->WriteInt( damageFlag );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhMutilatedHuman::Restore
|
|
=====================
|
|
*/
|
|
void hhMutilatedHuman::Restore( idRestoreGame *savefile ) {
|
|
savefile->ReadInt( damageFlag );
|
|
}
|
|
|
|
#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name )
|
|
void hhMutilatedHuman::LinkScriptVariables(void) {
|
|
hhMonsterAI::LinkScriptVariables();
|
|
LinkScriptVariable( AI_LIMB_FELL );
|
|
} |