mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 16:21:04 +02:00
694 lines
22 KiB
C++
694 lines
22 KiB
C++
|
|
#include "precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "prey_local.h"
|
|
|
|
const idEventDef MA_KeeperStartBlast("keeper_StartBlast", "e");
|
|
const idEventDef MA_KeeperEndBlast("keeper_EndBlast", NULL);
|
|
const idEventDef MA_KeeperUpdateTelepathicThrow("keeper_UpdateTelepathicThrow", NULL, NULL);
|
|
const idEventDef MA_KeeperTelepathicThrow("keeper_TelepathicThrow", NULL, NULL);
|
|
const idEventDef MA_KeeperStartTeleport("keeper_StartTeleport", NULL);
|
|
const idEventDef MA_KeeperEndTeleport("keeper_EndTeleport", NULL);
|
|
const idEventDef MA_KeeperTeleportExit("keeper_TeleportExit", NULL);
|
|
const idEventDef MA_KeeperCreatePortal("keeper_createPortal", NULL);
|
|
const idEventDef MA_KeeperEnableShield("keeper_enableShield", NULL);
|
|
const idEventDef MA_KeeperDisableShield("keeper_disableShield", NULL);
|
|
const idEventDef MA_KeeperAssignShieldFx( "<assignShieldFx>", "e" );
|
|
const idEventDef MA_KeeperTrigger("keeper_Trigger", NULL, NULL);
|
|
const idEventDef MA_KeeperTeleportEnter("keeper_TeleportEnter", NULL);
|
|
const idEventDef MA_KeeperStartHeadFx("keeper_StartHeadFx");
|
|
const idEventDef MA_KeeperEndHeadFx("keeper_EndHeadFx");
|
|
const idEventDef MA_KeeperAssignHeadFx( "<assignHeadFx>", "e" );
|
|
const idEventDef MA_KeeperGetThrowEntity( "keeper_GetThrowEntity", NULL, 'e' );
|
|
const idEventDef MA_KeeperGetTriggerEntity( "keeper_GetTriggerEntity", NULL, 'e' );
|
|
const idEventDef MA_KeeperTriggerEntity( "keeper_TriggerEntity", "e" );
|
|
const idEventDef MA_KeeperThrowEntity( "keeper_ThrowEntity", "e" );
|
|
const idEventDef MA_KeeperForceDisableShield("keeper_forceDisableShield", NULL);
|
|
|
|
CLASS_DECLARATION( hhMonsterAI, hhKeeperSimple )
|
|
EVENT( MA_KeeperStartBlast, hhKeeperSimple::Event_StartBlast )
|
|
EVENT( MA_KeeperEndBlast, hhKeeperSimple::Event_EndBlast )
|
|
EVENT( MA_KeeperUpdateTelepathicThrow, hhKeeperSimple::Event_UpdateTelepathicThrow )
|
|
EVENT( MA_KeeperTelepathicThrow, hhKeeperSimple::Event_TelepathicThrow )
|
|
EVENT( MA_KeeperStartTeleport, hhKeeperSimple::Event_StartTeleport )
|
|
EVENT( MA_KeeperEndTeleport, hhKeeperSimple::Event_EndTeleport )
|
|
EVENT( MA_KeeperTeleportExit, hhKeeperSimple::Event_TeleportExit )
|
|
EVENT( MA_KeeperCreatePortal, hhKeeperSimple::Event_CreatePortal )
|
|
EVENT( MA_KeeperEnableShield, hhKeeperSimple::Event_EnableShield )
|
|
EVENT( MA_KeeperDisableShield, hhKeeperSimple::Event_DisableShield )
|
|
EVENT( MA_KeeperAssignShieldFx, hhKeeperSimple::Event_AssignShieldFx )
|
|
EVENT( MA_KeeperTrigger, hhKeeperSimple::Event_KeeperTrigger )
|
|
EVENT( MA_KeeperTeleportEnter, hhKeeperSimple::Event_TeleportEnter )
|
|
EVENT( MA_KeeperStartHeadFx, hhKeeperSimple::Event_StartHeadFx )
|
|
EVENT( MA_KeeperEndHeadFx, hhKeeperSimple::Event_EndHeadFx )
|
|
EVENT( MA_KeeperAssignHeadFx, hhKeeperSimple::Event_AssignHeadFx )
|
|
EVENT( MA_KeeperTriggerEntity, hhKeeperSimple::Event_TriggerEntity )
|
|
EVENT( MA_KeeperThrowEntity, hhKeeperSimple::Event_ThrowEntity )
|
|
EVENT( MA_KeeperGetTriggerEntity, hhKeeperSimple::Event_GetTriggerEntity )
|
|
EVENT( MA_KeeperGetThrowEntity, hhKeeperSimple::Event_GetThrowEntity )
|
|
EVENT( MA_KeeperForceDisableShield, hhKeeperSimple::Event_ForceDisableShield )
|
|
END_CLASS
|
|
|
|
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
|
|
|
|
hhKeeperSimple::hhKeeperSimple() {
|
|
shieldAlpha = 0.0f;
|
|
}
|
|
|
|
hhKeeperSimple::~hhKeeperSimple() {
|
|
idEntity *ent = NULL;
|
|
if ( throwEntity.IsValid() ) {
|
|
ent = throwEntity.GetEntity();
|
|
} else if ( targetReaction.entity.IsValid() ) {
|
|
ent = targetReaction.entity.GetEntity();
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
if ( ent && ent->IsType(hhMoveable::Type)) {
|
|
hhMoveable *throwMoveable = static_cast<hhMoveable*>(ent);
|
|
if ( throwMoveable ) {
|
|
throwMoveable->Event_Unhover();
|
|
}
|
|
}
|
|
}
|
|
|
|
void hhKeeperSimple::Spawn( void ) {
|
|
beamAttack = idEntityPtr<hhBeamSystem> ( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "beamAttack" ) ) );
|
|
if(beamAttack.IsValid()) {
|
|
beamAttack->MoveToJoint( this, spawnArgs.GetString("bone_beamAttack"));
|
|
beamAttack->BindToJoint( this, spawnArgs.GetString("bone_beamAttack"), false );
|
|
beamAttack->Activate(FALSE);
|
|
}
|
|
|
|
beamTelepathic = idEntityPtr<hhBeamSystem> (hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "beamTelepathic" ) ) );
|
|
if(beamTelepathic.IsValid()) {
|
|
beamTelepathic->MoveToJoint( this, spawnArgs.GetString("bone_beamTelepathic"));
|
|
beamTelepathic->BindToJoint( this, spawnArgs.GetString("bone_beamTelepathic"), false );
|
|
beamTelepathic->Activate(FALSE);
|
|
beamTelepathic->Hide();
|
|
}
|
|
if ( spawnArgs.GetInt( "always_shield", "0" ) && !spawnArgs.GetBool( "hide", "0" ) ) {
|
|
Event_EnableShield();
|
|
}
|
|
nextShieldImpact = gameLocal.time;
|
|
bThrowing = false;
|
|
}
|
|
|
|
void hhKeeperSimple::Think() {
|
|
PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI);
|
|
if (ai_skipThink.GetBool()) {
|
|
return;
|
|
}
|
|
|
|
idEntity *ent = NULL;
|
|
if ( throwEntity.IsValid() ) {
|
|
ent = throwEntity.GetEntity();
|
|
} else if ( targetReaction.entity.IsValid() ) {
|
|
ent = targetReaction.entity.GetEntity();
|
|
}
|
|
|
|
// Draw beam from our brain to the object we are controlling
|
|
if ( beamTelepathic.IsValid() ) {
|
|
if( bThrowing && ent && !beamTelepathic->IsHidden() && beamTelepathic->IsActivated() ) {
|
|
beamTelepathic->SetTargetLocation(ent->GetOrigin());
|
|
} else {
|
|
beamTelepathic->Activate( false );
|
|
beamTelepathic->Hide();
|
|
}
|
|
}
|
|
|
|
if ( shieldFx.IsValid() ) {
|
|
if ( AI_SHIELD ) {
|
|
//fade on shield
|
|
shieldAlpha += spawnArgs.GetFloat( "shield_delta", "0.05" );
|
|
if ( shieldAlpha > 1.0 ) {
|
|
shieldAlpha = 1.0f;
|
|
}
|
|
} else {
|
|
//fade off shield
|
|
shieldAlpha -= spawnArgs.GetFloat( "shield_delta", "0.05" );
|
|
if ( shieldAlpha < 0.0 ) {
|
|
shieldAlpha = 0.0f;
|
|
}
|
|
}
|
|
shieldFx->SetParticleShaderParm( SHADERPARM_TIME_OF_DEATH, shieldAlpha );
|
|
}
|
|
|
|
hhMonsterAI::Think();
|
|
|
|
if ( physicsObj.HasGroundContacts() && !physicsObj.HadGroundContacts() ) {
|
|
AI_LANDED = true;
|
|
}
|
|
}
|
|
|
|
void hhKeeperSimple::Event_StartBlast( idEntity *ent) {
|
|
if( beamAttack.IsValid() ) {
|
|
hhFxInfo fxInfo;
|
|
fxInfo.SetEntity( ent );
|
|
fxInfo.RemoveWhenDone( true );
|
|
BroadcastFxInfo( spawnArgs.GetString("fx_portalstart"), ent->GetOrigin() + (spawnArgs.GetVector( "portal_start_offset" ) * ent->GetAxis()), ent->GetAxis(), &fxInfo );
|
|
beamAttack->SetTargetLocation( ent->GetOrigin() + ent->GetAxis() * spawnArgs.GetVector( "telepathic_beam_offset" ) );
|
|
beamAttack->Activate(TRUE);
|
|
}
|
|
}
|
|
|
|
void hhKeeperSimple::Event_EndBlast() {
|
|
if( beamAttack.IsValid() ) {
|
|
beamAttack->Activate(FALSE);
|
|
}
|
|
}
|
|
|
|
void hhKeeperSimple::Event_UpdateTelepathicThrow() {
|
|
idEntity *ent = NULL;
|
|
if ( throwEntity.IsValid() ) {
|
|
ent = throwEntity.GetEntity();
|
|
} else if ( targetReaction.entity.IsValid() ) {
|
|
ent = targetReaction.entity.GetEntity();
|
|
} else {
|
|
return;
|
|
}
|
|
if ( !ent || !ent->IsType(hhMoveable::Type) ) {
|
|
return;
|
|
}
|
|
bThrowing = true;
|
|
if ( beamTelepathic.IsValid() ) {
|
|
if ( beamTelepathic->IsHidden() ) {
|
|
beamTelepathic->Show();
|
|
}
|
|
beamTelepathic->Activate( true );
|
|
}
|
|
|
|
hhMoveable *throwMoveable = static_cast<hhMoveable*>(ent);
|
|
idVec3 hoverToPos;
|
|
|
|
if(GetEnemy()) {
|
|
UpdateEnemyPosition();
|
|
idVec3 toEnemy = GetLastEnemyPos() - GetOrigin();
|
|
float dist = toEnemy.Normalize();
|
|
hoverToPos = GetOrigin() + toEnemy * dist * 0.25f;
|
|
hoverToPos.z = GetOrigin().z + EyeHeight() + 200.0f;
|
|
}
|
|
else {
|
|
hoverToPos = throwMoveable->GetOrigin() + idVec3(0.0f, 0.0f, 128.0f);
|
|
}
|
|
throwMoveable->AllowImpact( true );
|
|
throwMoveable->Event_HoverTo(hoverToPos);
|
|
throwMoveable->GetPhysics()->SetAngularVelocity(idVec3(10.0f, 10.0f, 10.0f));
|
|
// HUMANHEAD mdl: Beef up the damage on movables thrown by the keeper
|
|
throwMoveable->SetDamageDef( spawnArgs.GetString( "moveableDamageDef", "damage_movable20" ) );
|
|
PostEventMS(&MA_KeeperUpdateTelepathicThrow, 100);
|
|
}
|
|
|
|
void hhKeeperSimple::Event_TelepathicThrow() {
|
|
idEntity *ent = NULL;
|
|
bThrowing = false;
|
|
if ( beamTelepathic.IsValid() ) {
|
|
beamTelepathic->Hide();
|
|
}
|
|
if ( throwEntity.IsValid() ) {
|
|
ent = throwEntity.GetEntity();
|
|
} else if ( targetReaction.entity.IsValid() ) {
|
|
ent = targetReaction.entity.GetEntity();
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
// Start the object hovering
|
|
if ( !ent || !ent->IsType(hhMoveable::Type) ) {
|
|
return;
|
|
}
|
|
|
|
targetReaction.entity.Clear();
|
|
CancelEvents( &MA_KeeperUpdateTelepathicThrow );
|
|
hhMoveable *throwMoveable = static_cast<hhMoveable*>(ent);
|
|
if ( throwMoveable && throwMoveable->IsType( hhMoveable::Type ) ) {
|
|
throwMoveable->Event_Unhover();
|
|
}
|
|
|
|
// Throw it at an enemy if we have one
|
|
if(GetEnemy()) {
|
|
idVec3 toEnemy = GetEnemy()->GetAimPosition() - throwMoveable->GetOrigin();
|
|
toEnemy.Normalize();
|
|
throwMoveable->GetPhysics()->SetLinearVelocity(toEnemy * spawnArgs.GetFloat("telepathic_throw_velocity", "2000"));
|
|
}
|
|
}
|
|
|
|
void hhKeeperSimple::Event_TeleportExit() {
|
|
const function_t *newstate = NULL;
|
|
newstate = GetScriptFunction( "state_TeleportExit" );
|
|
if ( newstate ) {
|
|
SetState( newstate );
|
|
UpdateScript();
|
|
}
|
|
}
|
|
|
|
void hhKeeperSimple::Event_TeleportEnter() {
|
|
const function_t *newstate = NULL;
|
|
newstate = GetScriptFunction( "state_TeleportEnter" );
|
|
if ( newstate ) {
|
|
SetState( newstate );
|
|
UpdateScript();
|
|
}
|
|
}
|
|
|
|
void hhKeeperSimple::Event_StartTeleport() {
|
|
Hide();
|
|
}
|
|
|
|
void hhKeeperSimple::Event_EndTeleport() {
|
|
idVec3 newOrig;
|
|
idEntity *ent;
|
|
idList<idEntity*> nodeList;
|
|
float bestScore = -1;
|
|
float bestNodeIndex = -1;
|
|
|
|
// Find the best position to teleport to
|
|
// Find where we want to teleport to
|
|
idStr randomNode = spawnArgs.RandomPrefix( "teleport_info", gameLocal.random );
|
|
ent = gameLocal.FindEntity( randomNode );
|
|
if ( ent ) {
|
|
newOrig = ent->GetOrigin();
|
|
} else {
|
|
newOrig = GetOrigin();
|
|
gameLocal.Warning( "%s doesn't have any teleport infos", name.c_str() );
|
|
}
|
|
|
|
//teleport and face enemy
|
|
SetOrigin( newOrig );
|
|
if( GetEnemy() ) {
|
|
TurnToward(GetEnemy()->GetOrigin());
|
|
current_yaw = ideal_yaw;
|
|
Turn();
|
|
}
|
|
}
|
|
|
|
float hhKeeperSimple::GetTeleportDestRating(const idVec3 &pos) {
|
|
// Make sure this spot doesn't contain something else
|
|
idEntity *entList[MAX_GENTITIES];
|
|
idBounds myNewBnds = this->GetPhysics()->GetBounds();
|
|
myNewBnds = myNewBnds.Translate(pos);
|
|
myNewBnds.ExpandSelf(24.0f);
|
|
int numHit = gameLocal.clip.EntitiesTouchingBounds(myNewBnds, -1, entList, MAX_GENTITIES);
|
|
if(numHit > 0) {
|
|
for(int i=0;i<numHit;i++) {
|
|
if(entList[i] && !entList[i]->IsHidden() && (entList[i]->IsType(hhMoveable::Type) || entList[i]->IsType(idActor::Type))) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
float w = gameLocal.random.RandomFloat();
|
|
|
|
return idMath::ClampFloat(-1.0f, 1.0f, w);
|
|
}
|
|
|
|
#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name )
|
|
void hhKeeperSimple::LinkScriptVariables() {
|
|
hhMonsterAI::LinkScriptVariables();
|
|
LinkScriptVariable( AI_LANDED );
|
|
LinkScriptVariable( AI_SHIELD );
|
|
}
|
|
|
|
void hhKeeperSimple::Event_CreatePortal (void) {
|
|
static const char * passPrefix = "portal_";
|
|
const char * portalDef;
|
|
idEntity * portal;
|
|
idDict portalArgs;
|
|
idList<idStr> xferKeys;
|
|
idList<idStr> xferValues;
|
|
|
|
const idKeyValue *buddyKV = spawnArgs.FindKey( "portal_buddy" );
|
|
if ( buddyKV && buddyKV->GetValue().Length() && gameLocal.FindEntity(buddyKV->GetValue().c_str()) ) {
|
|
// Case of a valid portal_buddy key, make a real portal
|
|
portalDef = spawnArgs.GetString( "def_portal" );
|
|
} else if ( spawnArgs.FindKey( "portal_cameraTarget" ) ) { // The monster portal has a camera target, so use a real portal instead of the fake portal
|
|
portalDef = spawnArgs.GetString( "def_portal" );
|
|
} else {
|
|
portalDef = spawnArgs.GetString( "def_fakeportal" );
|
|
}
|
|
|
|
if ( !portalDef || !portalDef[0] ) {
|
|
return;
|
|
}
|
|
|
|
// Set the origin of the portal to us.
|
|
portalArgs.SetVector( "origin", GetOrigin() );
|
|
portalArgs.Set( "rotation", spawnArgs.GetString( "rotation" ) );
|
|
portalArgs.Set( "remove_on_close", "1" );
|
|
|
|
// Pass along all 'portal_' keys to the portal's spawnArgs;
|
|
hhUtils::GetKeysAndValues( spawnArgs, passPrefix, xferKeys, xferValues );
|
|
for ( int i = 0; i < xferValues.Num(); ++i ) {
|
|
xferKeys[ i ].StripLeadingOnce( passPrefix );
|
|
portalArgs.Set( xferKeys[ i ].c_str(), xferValues[ i ].c_str() );
|
|
}
|
|
|
|
// Spawn the portal
|
|
portal = gameLocal.SpawnObject( portalDef, &portalArgs );
|
|
if ( !portal ) {
|
|
return;
|
|
}
|
|
|
|
// Move the portal up some pre determinted amt, since its origin is in the middle of it
|
|
trace_t tr;
|
|
gameLocal.clip.TracePoint(tr, GetOrigin(), GetOrigin() + idVec3(0,0,-200), MASK_MONSTERSOLID, this);
|
|
if ( tr.fraction < 1.0f ) {
|
|
portal->GetPhysics()->SetOrigin( tr.c.point + idVec3( 0,0,2 ) );
|
|
} else {
|
|
float offset = spawnArgs.GetFloat( "offset_portal", 0 );
|
|
portal->GetPhysics()->SetOrigin( portal->GetPhysics()->GetOrigin() + (portal->GetAxis()[2] * offset) );
|
|
}
|
|
|
|
// Update the camera stuff
|
|
portal->ProcessEvent( &EV_UpdateCameraTarget );
|
|
|
|
// Open the portal - Need to delay this, so that PostSpawn gets called/sets up the partner portal
|
|
//? Should we always pass in the player?
|
|
portal->PostEventSec( &EV_Activate, 0, gameLocal.GetLocalPlayer() );
|
|
}
|
|
|
|
void hhKeeperSimple::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
|
|
hhMonsterAI::Killed( inflictor, attacker, damage, dir, location );
|
|
if ( beamAttack.IsValid() ) {
|
|
SAFE_REMOVE( beamAttack );
|
|
}
|
|
if( beamTelepathic.IsValid() ) {
|
|
SAFE_REMOVE( beamTelepathic );
|
|
}
|
|
hhMoveable *throwMoveable = static_cast<hhMoveable*>(targetReaction.entity.GetEntity());
|
|
if ( throwMoveable && throwMoveable->IsType( hhMoveable::Type ) ) {
|
|
targetReaction.entity.Clear();
|
|
throwMoveable->Event_Unhover();
|
|
}
|
|
if ( renderEntity.customSkin == NULL ) {
|
|
SetSkinByName( "skins/monsters/keeper_nolegs" );
|
|
}
|
|
}
|
|
|
|
void hhKeeperSimple::Event_EnableShield(void) {
|
|
if ( spawnArgs.GetInt( "never_shield", "0" ) ) {
|
|
return;
|
|
}
|
|
fl.applyDamageEffects = false;
|
|
AI_SHIELD = true;
|
|
if ( shieldFx.IsValid() ) {
|
|
SAFE_REMOVE( shieldFx );
|
|
}
|
|
hhFxInfo fxInfo;
|
|
fxInfo.SetEntity( this );
|
|
fxInfo.SetBindBone( "head" );
|
|
fxInfo.RemoveWhenDone( true );
|
|
BroadcastFxInfo( spawnArgs.GetString("fx_shield"), GetOrigin(), mat3_identity, &fxInfo, &MA_KeeperAssignShieldFx );
|
|
}
|
|
|
|
void hhKeeperSimple::Event_ForceDisableShield(void) {
|
|
fl.applyDamageEffects = true;
|
|
AI_SHIELD = false;
|
|
}
|
|
|
|
void hhKeeperSimple::Event_DisableShield(void) {
|
|
if ( spawnArgs.GetInt( "always_shield", "0" ) ) {
|
|
return;
|
|
}
|
|
fl.applyDamageEffects = true;
|
|
AI_SHIELD = false;
|
|
}
|
|
|
|
void hhKeeperSimple::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int loc ) {
|
|
if ( AI_SHIELD ) {
|
|
const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
|
|
if ( damageDef && damageDef->GetBool( "spirit_damage" ) ) {
|
|
hhMonsterAI::Damage(inflictor, attacker, dir, damageDefName, damageScale, loc);
|
|
}
|
|
if ( inflictor ) {
|
|
hhFxInfo fxInfo;
|
|
fxInfo.RemoveWhenDone( true );
|
|
if ( gameLocal.time >= nextShieldImpact ) {
|
|
nextShieldImpact = gameLocal.time + int(spawnArgs.GetFloat( "shield_impact_freq", "0.4" ) * 1000);
|
|
BroadcastFxInfoPrefixed( "fx_shield_impact", inflictor->GetOrigin() + GetAxis()*idVec3(50,0,0), mat3_identity, &fxInfo );
|
|
}
|
|
StartSound( "snd_shield_impact", SND_CHANNEL_BODY );
|
|
if ( inflictor->IsType( idProjectile::Type ) ) {
|
|
inflictor->PostEventMS( &EV_Remove, 0 );
|
|
}
|
|
}
|
|
} else {
|
|
hhMonsterAI::Damage(inflictor, attacker, dir, damageDefName, damageScale, loc);
|
|
}
|
|
}
|
|
|
|
void hhKeeperSimple::Event_AssignShieldFx( hhEntityFx* fx ) {
|
|
shieldFx = fx;
|
|
}
|
|
|
|
void hhKeeperSimple::Event_StartHeadFx() {
|
|
hhFxInfo fxInfo;
|
|
fxInfo.SetEntity( this );
|
|
fxInfo.SetBindBone( spawnArgs.GetString( "bone_telepathic_fx", "head" ) );
|
|
fxInfo.RemoveWhenDone( true );
|
|
BroadcastFxInfo( spawnArgs.GetString("fx_telepathic"), GetOrigin(), mat3_identity, &fxInfo, &MA_KeeperAssignHeadFx );
|
|
}
|
|
|
|
void hhKeeperSimple::Event_EndHeadFx() {
|
|
SAFE_REMOVE( headFx );
|
|
}
|
|
|
|
void hhKeeperSimple::Event_AssignHeadFx( hhEntityFx* fx ) {
|
|
headFx = fx;
|
|
}
|
|
|
|
void hhKeeperSimple::Event_KeeperTrigger() {
|
|
if ( targetReaction.entity.IsValid() ) {
|
|
targetReaction.entity->ProcessEvent( &EV_Activate, this );
|
|
hhReaction *reaction = targetReaction.GetReaction();
|
|
if (reaction) {
|
|
reaction->active = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
idProjectile *hhKeeperSimple::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 = spawnArgs.GetFloat( "projectile_spread", "0" );
|
|
num_projectiles = spawnArgs.GetInt( "num_projectiles", "1" );
|
|
|
|
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.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy;
|
|
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, vec3_origin );
|
|
projectile = NULL;
|
|
}
|
|
|
|
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 hhKeeperSimple::Event_GetTriggerEntity() {
|
|
idThread::ReturnEntity( triggerEntity.GetEntity() );
|
|
}
|
|
|
|
void hhKeeperSimple::Event_TriggerEntity( idEntity *ent ) {
|
|
if ( !ent ) {
|
|
return;
|
|
}
|
|
triggerEntity = ent;
|
|
const function_t *newstate = NULL;
|
|
newstate = GetScriptFunction( "state_TriggerEntity" );
|
|
if ( newstate ) {
|
|
SetState( newstate );
|
|
UpdateScript();
|
|
}
|
|
}
|
|
|
|
void hhKeeperSimple::Event_GetThrowEntity() {
|
|
idThread::ReturnEntity( throwEntity.GetEntity() );
|
|
}
|
|
|
|
void hhKeeperSimple::Event_ThrowEntity( idEntity *ent ) {
|
|
if ( !ent || !ent->IsType( idMoveable::Type ) ) {
|
|
return;
|
|
}
|
|
throwEntity = ent;
|
|
const function_t *newstate = NULL;
|
|
newstate = GetScriptFunction( "state_ThrowEntity" );
|
|
if ( newstate ) {
|
|
SetState( newstate );
|
|
UpdateScript();
|
|
}
|
|
}
|
|
|
|
void hhKeeperSimple::Hide() {
|
|
Event_DisableShield();
|
|
hhMonsterAI::Hide();
|
|
}
|
|
|
|
void hhKeeperSimple::Show() {
|
|
hhMonsterAI::Show();
|
|
if ( spawnArgs.GetInt( "always_shield", "0" ) ) {
|
|
Event_EnableShield();
|
|
}
|
|
}
|
|
|
|
void hhKeeperSimple::HideNoDormant() {
|
|
//overridden to hide shield fx
|
|
if ( shieldFx.IsValid() ) {
|
|
shieldFx->Hide();
|
|
}
|
|
idAI::Hide();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhKeeperSimple::Save
|
|
=====================
|
|
*/
|
|
void hhKeeperSimple::Save( idSaveGame *savefile ) const {
|
|
beamTelepathic.Save( savefile );
|
|
beamAttack.Save( savefile );
|
|
shieldFx.Save( savefile );
|
|
headFx.Save( savefile );
|
|
triggerEntity.Save( savefile );
|
|
throwEntity.Save( savefile );
|
|
savefile->WriteInt( nextShieldImpact );
|
|
savefile->WriteBool( bThrowing );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhKeeperSimple::Restore
|
|
=====================
|
|
*/
|
|
void hhKeeperSimple::Restore( idRestoreGame *savefile ) {
|
|
beamTelepathic.Restore( savefile );
|
|
beamAttack.Restore( savefile );
|
|
shieldFx.Restore( savefile );
|
|
headFx.Restore( savefile );
|
|
triggerEntity.Restore( savefile );
|
|
throwEntity.Restore( savefile );
|
|
savefile->ReadInt( nextShieldImpact );
|
|
savefile->ReadBool( bThrowing );
|
|
}
|
|
|
|
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
|