mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-17 11:00:38 +02:00
Initial Prey Game integration support.
This commit is contained in:
@@ -0,0 +1,838 @@
|
||||
#include "precompiled.h"
|
||||
#pragma hdrstop
|
||||
|
||||
#include "prey_local.h"
|
||||
|
||||
const idEventDef MA_AntiProjectileAttack("<antiProjectileAttack>", "e");
|
||||
const idEventDef MA_StartPreDeath("<startPreDeath>" );
|
||||
const idEventDef MA_HandlePassageway("<handlePassageway>", NULL);
|
||||
const idEventDef MA_EnterPassageway("<enterPassageway>", "e",NULL);
|
||||
const idEventDef MA_ExitPassageway("<exitPassageway>", "e",NULL);
|
||||
const idEventDef MA_UseThisPassageway("useThisPassageway", "ed",NULL);
|
||||
const idEventDef MA_GibOnDeath("gibOnDeath", "d");
|
||||
|
||||
CLASS_DECLARATION( hhMonsterAI, hhHarvesterSimple )
|
||||
EVENT( MA_OnProjectileLaunch, hhHarvesterSimple::Event_OnProjectileLaunch )
|
||||
EVENT( MA_AntiProjectileAttack, hhHarvesterSimple::Event_AntiProjectileAttack )
|
||||
EVENT( MA_StartPreDeath, hhHarvesterSimple::Event_StartPreDeath )
|
||||
EVENT( MA_HandlePassageway, hhHarvesterSimple::Event_HandlePassageway )
|
||||
EVENT( MA_EnterPassageway, hhHarvesterSimple::Event_EnterPassageway )
|
||||
EVENT( MA_ExitPassageway, hhHarvesterSimple::Event_ExitPassageway )
|
||||
EVENT( MA_UseThisPassageway, hhHarvesterSimple::Event_UseThisPassageway )
|
||||
EVENT( EV_Broadcast_AppendFxToList, hhHarvesterSimple::Event_AppendFxToList )
|
||||
EVENT( MA_GibOnDeath, hhHarvesterSimple::Event_GibOnDeath )
|
||||
END_CLASS
|
||||
|
||||
#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
|
||||
//
|
||||
// Spawn()
|
||||
//
|
||||
void hhHarvesterSimple::Spawn(void) {
|
||||
allowPreDeath = true;
|
||||
lastAntiProjectileAttack = 0;
|
||||
lastPassagewayTime = 0;
|
||||
passageCount = spawnArgs.GetInt("passage_count", "0"); // Used for passing to and from torso
|
||||
bSmokes = spawnArgs.GetBool("smokes", "0");
|
||||
bGibOnDeath = false;
|
||||
}
|
||||
|
||||
//
|
||||
// Event_OnProjectileLaunch()
|
||||
//
|
||||
void hhHarvesterSimple::Event_OnProjectileLaunch(hhProjectile *proj) {
|
||||
|
||||
// Can't launch again yet
|
||||
float min = spawnArgs.GetFloat( "dda_delay_min" );
|
||||
float max = spawnArgs.GetFloat( "dda_delay_max" );
|
||||
float delay = min + (max - min) * (1.0f - gameLocal.GetDDAValue());
|
||||
|
||||
if(gameLocal.GetTime() - lastAntiProjectileAttack < delay)
|
||||
return;
|
||||
|
||||
// The person who launched this projectile wasn't someone to worry about
|
||||
if(proj->GetOwner() && !(ReactionTo(proj->GetOwner()) & (ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE)))
|
||||
return;
|
||||
|
||||
// TODO: more intelligent checks for if we should launch the anti-projectile chaff
|
||||
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;
|
||||
|
||||
ProcessEvent(&MA_AntiProjectileAttack, proj);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Event_AntiProjectileAttack()
|
||||
//
|
||||
void hhHarvesterSimple::Event_AntiProjectileAttack(hhProjectile *proj) {
|
||||
|
||||
if(!spawnArgs.GetBool("block_projectiles", "1")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const idDict *projectileDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_chaff") );
|
||||
if ( !projectileDef ) {
|
||||
gameLocal.Error( "Unknown def_chaff: %s\n", spawnArgs.GetString("def_chaff") );
|
||||
}
|
||||
hhProjectile* projectile = NULL;
|
||||
|
||||
//bjk: new parms
|
||||
int numProj = spawnArgs.GetInt("shieldNumProj", "10");
|
||||
float spread = DEG2RAD( spawnArgs.GetFloat("shieldSpread", "10") );
|
||||
float yaw = DEG2RAD( spawnArgs.GetFloat("shieldYawSpread", "10") );
|
||||
|
||||
//StartSound("snd_fire_chaff", SND_CHANNEL_ANY, 0, true, NULL);
|
||||
torsoAnim.PlayAnim( GetAnimator()->GetAnim( "antiprojectile_attack" ) );
|
||||
|
||||
idVec3 dir = proj->GetOrigin() - GetOrigin();
|
||||
dir.Normalize();
|
||||
dir = dir+idVec3(0.f, 0.f, -.3f); // bjk: bias towards ground
|
||||
dir.Normalize();
|
||||
idMat3 muzzleAxis = dir.ToMat3();
|
||||
// todo: read this in from a bone
|
||||
idVec3 launchPos = GetOrigin() + idVec3(0.0f, 0.0f, 64.0f);
|
||||
|
||||
for(int i=0; i<numProj; i++) {
|
||||
projectile = hhProjectile::SpawnProjectile( projectileDef );
|
||||
HH_ASSERT( projectile );
|
||||
|
||||
//bjk: uses random now
|
||||
float ang = spread * hhMath::Sqrt( gameLocal.random.RandomFloat() );
|
||||
float spin = hhMath::TWO_PI * gameLocal.random.RandomFloat();
|
||||
dir = muzzleAxis[ 0 ] + muzzleAxis[ 2 ] * ( hhMath::Sin(ang) * hhMath::Sin(spin) ) - muzzleAxis[ 1 ] * ( hhMath::Sin(ang+yaw) * hhMath::Cos(spin) );
|
||||
dir.Normalize();
|
||||
|
||||
projectile->Create(this, launchPos, dir);
|
||||
projectile->Launch(launchPos, dir, idVec3(0.0f, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
lastAntiProjectileAttack = gameLocal.GetTime();
|
||||
}
|
||||
|
||||
bool hhHarvesterSimple::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
|
||||
int preDeathThresh = spawnArgs.GetInt("predeath_thresh", "-1");
|
||||
if(allowPreDeath && !bGibOnDeath && preDeathThresh > 0 && health < preDeathThresh) {
|
||||
allowPreDeath = false; // We've tried, cannot try anymore
|
||||
float preDeathFreq = spawnArgs.GetFloat("predeath_freq", "1");
|
||||
if(gameLocal.random.RandomFloat() < preDeathFreq) {
|
||||
ProcessEvent(&MA_StartPreDeath);
|
||||
}
|
||||
}
|
||||
|
||||
if ( bSmokes ) {
|
||||
if ( !fxSmoke[0].IsValid() && health <= AI_PASSAGEWAY_HEALTH ) {
|
||||
SpawnSmoke();
|
||||
} else if (fxSmoke[0].IsValid() && health > AI_PASSAGEWAY_HEALTH) {
|
||||
ClearSmoke();
|
||||
}
|
||||
}
|
||||
|
||||
return( idAI::Pain(inflictor, attacker, damage, dir, location) );
|
||||
}
|
||||
|
||||
void hhHarvesterSimple::Event_StartPreDeath(void) {
|
||||
int i;
|
||||
|
||||
// No torso? Just kill myself .....
|
||||
if(!spawnArgs.GetString("def_torso")) {
|
||||
ProcessEvent(&AI_Kill);
|
||||
return;
|
||||
}
|
||||
|
||||
// Spawn death fx, if any
|
||||
const char *fx = spawnArgs.GetString("fx_death");
|
||||
if (fx && fx[0]) {
|
||||
hhFxInfo fxInfo;
|
||||
|
||||
fxInfo.RemoveWhenDone(true);
|
||||
BroadcastFxInfo(fx, GetOrigin(), GetAxis(), &fxInfo);
|
||||
}
|
||||
|
||||
// do blood splats
|
||||
float size = spawnArgs.GetFloat("decal_torso_pop_size","96");
|
||||
|
||||
// copy my target keys to the keys that will spawn the torso
|
||||
idDict dict;
|
||||
const idDict *torsoDict = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_torso"));
|
||||
if ( !torsoDict ) {
|
||||
gameLocal.Error("Unknown def_torso: %s\n", spawnArgs.GetString("def_torso"));
|
||||
}
|
||||
dict.Copy(*torsoDict);
|
||||
for(i=0;i<targets.Num();i++)
|
||||
{
|
||||
if(!targets[i].GetEntity()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
idStr key("target");
|
||||
if(i > 0) {
|
||||
sprintf(key, "target%i", i);
|
||||
}
|
||||
|
||||
dict.Set((const char*)key, (const char*)targets[i].GetEntity()->name);
|
||||
}
|
||||
|
||||
// Passageway variables
|
||||
dict.SetInt("passage_health", AI_PASSAGEWAY_HEALTH);
|
||||
dict.SetInt("passage_count", passageCount);
|
||||
dict.SetVector("origin", GetOrigin());
|
||||
dict.SetMatrix("rotation", GetAxis());
|
||||
|
||||
// remove my target keys (so they don't get fired)
|
||||
targets.Clear();
|
||||
|
||||
// Spawn the torso
|
||||
idEntity *e;
|
||||
hhMonsterAI *aiTorso;
|
||||
if(!gameLocal.SpawnEntityDef(dict, &e))
|
||||
gameLocal.Error("Failed to spawn ai torso.");
|
||||
HH_ASSERT(e && e->IsType(hhMonsterAI::Type));
|
||||
aiTorso = static_cast<hhMonsterAI*>(e);
|
||||
HH_ASSERT(aiTorso != NULL);
|
||||
|
||||
// Throw gibs
|
||||
idStr debrisDef = spawnArgs.GetString("def_gibDebrisSpawnerPreDeath");
|
||||
if(debrisDef.Length() > 0) {
|
||||
hhUtils::SpawnDebrisMass(debrisDef.c_str(), this, 1.0f);
|
||||
}
|
||||
|
||||
health = 0;
|
||||
|
||||
// remove myself
|
||||
Hide();
|
||||
PostEventMS(&EV_Remove, 3000);
|
||||
}
|
||||
|
||||
void hhHarvesterSimple::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
|
||||
if ( attacker == this ) {
|
||||
return;
|
||||
}
|
||||
const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
|
||||
if ( !AI_DEAD ) {
|
||||
if ( damageDef && damageDef->GetBool( "ice" ) && spawnArgs.GetBool( "can_freeze", "0" ) ) {
|
||||
spawnArgs.Set( "bAlwaysGib", "0" );
|
||||
allowPreDeath = false;
|
||||
} else {
|
||||
allowPreDeath = true;
|
||||
}
|
||||
if ( damageDef && damageDef->GetBool( "no_special_death" ) ) {
|
||||
allowPreDeath = false;
|
||||
}
|
||||
}
|
||||
hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
|
||||
}
|
||||
|
||||
int hhHarvesterSimple::EvaluateReaction( const hhReaction *react ) {
|
||||
// Let the script limit the distance of Climb reactions we consider
|
||||
if ( react->desc->effect == hhReactionDesc::Effect_Climb && AI_CLIMB_RANGE >= 0.0f && ( react->causeEntity->GetOrigin() - GetOrigin()).LengthSqr() > AI_CLIMB_RANGE ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If we're supposed to use a specific passageway, only accept it and reject the others
|
||||
if ( nextPassageway.IsValid() && react->desc->effect == hhReactionDesc::Effect_Passageway ) {
|
||||
if (react->causeEntity.GetEntity() == nextPassageway.GetEntity()) {
|
||||
return 100;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int rank = hhMonsterAI::EvaluateReaction( react );
|
||||
if ( react->desc->effect == hhReactionDesc::Effect_Passageway &&
|
||||
react->causeEntity.GetEntity() == lastPassageway.GetEntity() ) {
|
||||
if ( lastPassagewayTime + 10000 > gameLocal.time ) {
|
||||
if (rank > 10) {
|
||||
// Discourage entering the same passageway we just left
|
||||
return 10;
|
||||
}
|
||||
} else {
|
||||
// Ignore previous passageway for 10 seconds
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return rank;
|
||||
}
|
||||
|
||||
#define CheckPassageFreqInitial 6000
|
||||
void hhHarvesterSimple::Event_EnterPassageway(hhAIPassageway *pn) {
|
||||
CancelEvents( &MA_OnProjectileLaunch ); // Clear any anti-projectile attacks
|
||||
CancelEvents( &MA_AntiProjectileAttack ); // Clear any anti-projectile attacks
|
||||
if (nextPassageway.IsValid()) {
|
||||
if (nextPassageway.GetEntity() != pn) {
|
||||
gameLocal.Warning("hhAIPassageway entered was not the specified one!\n");
|
||||
}
|
||||
nextPassageway.Clear();
|
||||
}
|
||||
|
||||
if(currPassageway != NULL) {
|
||||
gameLocal.Error("%s tried to ENTER a passageway but already had a passageway!", (const char*)name);
|
||||
}
|
||||
|
||||
HH_ASSERT(currPassageway == NULL);
|
||||
Hide(); // Should already be hidden but just to make sure
|
||||
|
||||
// Only consider passageCount if damaged (ignores scripted passage sequences) or if it's the torso
|
||||
if (health < spawnHealth || !idStr::Icmp(spawnArgs.GetString("classname"), "monster_harvester_torso") ) {
|
||||
passageCount++;
|
||||
if (passageCount > 1) {
|
||||
//TODO make this affected by the DDA system
|
||||
AI_PASSAGEWAY_HEALTH = AI_PASSAGEWAY_HEALTH * 0.75f; // Reduce passageway seeking health level to 3/4 of it's original value
|
||||
}
|
||||
}
|
||||
|
||||
GetAnimator()->ClearAllAnims(gameLocal.GetTime(), 0);
|
||||
Event_AnimState(ANIMCHANNEL_LEGS, "Legs_Idle", (int)0);
|
||||
Event_AnimState(ANIMCHANNEL_TORSO, "Torso_Idle", (int)0);
|
||||
torsoAnim.UpdateState();
|
||||
legsAnim.UpdateState();
|
||||
|
||||
StartSound("snd_inside_passage", SND_CHANNEL_VOICE, SSF_LOOPING, true, NULL);
|
||||
|
||||
currPassageway = pn;
|
||||
if(GetPhysics())
|
||||
GetPhysics()->SetContents(0);
|
||||
|
||||
// Does this passage way give us health?
|
||||
int h = 0;
|
||||
pn->spawnArgs.GetInt("give_health", "0", h);
|
||||
health += h;
|
||||
health = idMath::ClampInt(0, spawnArgs.GetInt("health"), health);
|
||||
|
||||
|
||||
// Should we transform to another monster when entering?
|
||||
idStr transTo;
|
||||
if(spawnArgs.GetString("passage_transform_to", "", transTo)) {
|
||||
const idDict *def = gameLocal.FindEntityDefDict( transTo );
|
||||
if(!def) {
|
||||
gameLocal.Error("Unknown def : %s", (const char*)transTo);
|
||||
}
|
||||
|
||||
// copy my target keys to the keys of the transformed-to entity
|
||||
idDict dict;
|
||||
dict.Copy(*def);
|
||||
for(int i=0;i<targets.Num();i++)
|
||||
{
|
||||
if(!targets[i].GetEntity())
|
||||
continue;
|
||||
|
||||
idStr key("target");
|
||||
if(i > 0)
|
||||
sprintf(key, "target%i", i);
|
||||
|
||||
dict.Set((const char*)key, (const char*)targets[i].GetEntity()->name);
|
||||
}
|
||||
|
||||
// Passageway variables
|
||||
dict.SetInt("passage_health", AI_PASSAGEWAY_HEALTH);
|
||||
dict.SetInt("passage_count", passageCount);
|
||||
|
||||
idEntity *e = NULL;
|
||||
gameLocal.SpawnEntityDef(dict, &e);
|
||||
HH_ASSERT( e->IsType( hhHarvesterSimple::Type ) );
|
||||
hhHarvesterSimple *ai = static_cast<hhHarvesterSimple*>(e);
|
||||
ai->SetOrigin(GetOrigin());
|
||||
ai->SetAxis(GetAxis());
|
||||
ai->Event_EnterPassageway(pn);
|
||||
targets.Clear();
|
||||
PostEventSec(&EV_Remove, 0.1f);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
PostEventMS(&MA_HandlePassageway, CheckPassageFreqInitial);
|
||||
}
|
||||
|
||||
void hhHarvesterSimple::Event_ExitPassageway(hhAIPassageway *pn) {
|
||||
if(currPassageway == NULL) {
|
||||
gameLocal.Error("%s tried to EXIT a passageway but did NOT have a curr passageway!", (const char*)name);
|
||||
}
|
||||
|
||||
HH_ASSERT(currPassageway != NULL);
|
||||
|
||||
// Store this passageway so we don't jump right back into it.
|
||||
lastPassageway = idEntityPtr<idEntity> (pn);
|
||||
lastPassagewayTime = gameLocal.time;
|
||||
|
||||
if(GetPhysics())
|
||||
GetPhysics()->SetContents(CONTENTS_BODY);
|
||||
currPassageway = NULL;
|
||||
AI_ACTIVATED = true; // Sometimes this is NOT true?!?!
|
||||
StopSound(SND_CHANNEL_VOICE, true);
|
||||
|
||||
// Teleport to new pos
|
||||
idAngles angs = pn->GetAxis().ToAngles();
|
||||
idVec3 pos = pn->GetExitPos();
|
||||
Teleport( pos, angs, pn );
|
||||
Show();
|
||||
|
||||
current_yaw = angs.yaw;
|
||||
ideal_yaw = angs.yaw;
|
||||
turnVel = 0.0f;
|
||||
HH_ASSERT(FacingIdeal());
|
||||
|
||||
// Exit anim to play?
|
||||
idStr exitAnim = pn->spawnArgs.GetString("exit_anim");
|
||||
if(exitAnim.Length() && GetAnimator()->HasAnim(exitAnim)) {
|
||||
GetAnimator()->ClearAllAnims(gameLocal.GetTime(), 0);
|
||||
torsoAnim.UpdateState();
|
||||
legsAnim.UpdateState();
|
||||
torsoAnim.PlayAnim( GetAnimator()->GetAnim( exitAnim ) );
|
||||
legsAnim.PlayAnim( GetAnimator()->GetAnim( exitAnim ) );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Event_HandlePassageway()
|
||||
//
|
||||
#define MaxExits 32
|
||||
#define CheckPassageFreq 3000
|
||||
void hhHarvesterSimple::Event_HandlePassageway(void) {
|
||||
|
||||
if(currPassageway == NULL) {
|
||||
gameLocal.Error("%s tried to handle a passageway but did NOT have a passageway!", (const char*)name);
|
||||
}
|
||||
|
||||
HH_ASSERT(currPassageway != NULL);
|
||||
hhAIPassageway *exitNode = NULL;
|
||||
|
||||
// CATCH 'went inside hole - never came out' bug
|
||||
if(currPassageway.GetEntity()->lastEntered == this && gameLocal.GetTime() - currPassageway.GetEntity()->timeLastEntered > 30000) {
|
||||
gameLocal.Warning("%s has been stuck in a passageway for >30 secs, killing it off.", (const char*)name);
|
||||
fl.takedamage = true;
|
||||
// Use hhMonsterAI::Damage because our Damage returns if we're the attacker
|
||||
hhMonsterAI::Damage(this, this, vec3_origin, "damage_gib", 1.0f, INVALID_JOINT);
|
||||
return;
|
||||
}
|
||||
|
||||
// No other way out - gotta go out the way we came in
|
||||
if(currPassageway->targets.Num() <= 0) {
|
||||
exitNode = currPassageway.GetEntity();
|
||||
}
|
||||
// We can go out another way
|
||||
else {
|
||||
hhAIPassageway *possibleExits[MaxExits];
|
||||
hhAIPassageway *tmp;
|
||||
int count = 0;
|
||||
|
||||
bool isRandom;
|
||||
if (gameLocal.GetTime() - currPassageway.GetEntity()->timeLastEntered > SEC2MS(10)) {
|
||||
// Try to prevent us from being stuck after 10 seconds
|
||||
isRandom = true;
|
||||
} else {
|
||||
// Choose possibleExits[] randomly if we have no enemy or based on chance. Otherwise choose based on line-of-sight to the player
|
||||
isRandom = !enemy.IsValid() || gameLocal.random.RandomInt(100) > 50;
|
||||
}
|
||||
trace_t tr;
|
||||
idVec3 toPos;
|
||||
if (!isRandom) {
|
||||
toPos = enemy->GetOrigin();
|
||||
}
|
||||
|
||||
// We've tried once, now we can use our entrance as an exit
|
||||
if(gameLocal.GetTime() - currPassageway.GetEntity()->timeLastEntered > CheckPassageFreqInitial + 1000) {
|
||||
count = 1;
|
||||
possibleExits[0] = currPassageway.GetEntity(); // We can always go out the same way we came in
|
||||
}
|
||||
for(int i=0;i<currPassageway->targets.Num();i++) {
|
||||
if(currPassageway->targets[i] != NULL && currPassageway->targets[i].GetEntity()->IsType(hhAIPassageway::Type)) {
|
||||
if (isRandom) {
|
||||
possibleExits[count++] = static_cast<hhAIPassageway*>(currPassageway->targets[i].GetEntity());
|
||||
} else {
|
||||
tmp = static_cast<hhAIPassageway*>(currPassageway->targets[i].GetEntity());
|
||||
// Choose based on visibility to our enemy
|
||||
gameLocal.clip.TracePoint( tr, tmp->GetOrigin(), toPos, MASK_SHOT_BOUNDINGBOX, tmp );
|
||||
if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemy.GetEntity() ) ) {
|
||||
lastVisibleEnemyPos = toPos; // Can now see enemy
|
||||
possibleExits[count++] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We've got enough
|
||||
if(count >= MaxExits)
|
||||
break;
|
||||
}
|
||||
|
||||
if(count == 0) {
|
||||
exitNode = currPassageway.GetEntity();
|
||||
} else if ( enemy.IsValid() && gameLocal.random.RandomInt(100) > 70 ) {
|
||||
// Find the one closest to the last known enemy pos
|
||||
if (!exitNode) {
|
||||
float bestDistance = -1.0f, distSq;
|
||||
for (int i = 0; i < count; i++) {
|
||||
distSq = ( possibleExits[i]->GetOrigin() - lastVisibleEnemyPos ).LengthSqr();
|
||||
if ( bestDistance == -1.0f || distSq < bestDistance ) {
|
||||
exitNode = possibleExits[i];
|
||||
bestDistance = distSq;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Pick a random one
|
||||
int randExit = gameLocal.random.RandomInt(count); // TODO: Randomness isn't very random! Fix!
|
||||
HH_ASSERT(randExit >= 0 && randExit < count);
|
||||
exitNode = possibleExits[randExit];
|
||||
}
|
||||
}
|
||||
|
||||
if(!exitNode)
|
||||
exitNode = currPassageway.GetEntity();
|
||||
|
||||
// The one we picked is disabled, so go out where we came in
|
||||
// TODO: make this time based, try a few more times before going back out
|
||||
if(!exitNode->IsPassagewayEnabled()) {
|
||||
exitNode = currPassageway.GetEntity();
|
||||
}
|
||||
|
||||
// Special case: The passageway is never meant to be exited from, use it's specified exit point instead
|
||||
const char *altPassage = exitNode->spawnArgs.GetString("force_passageway", "");
|
||||
if (altPassage && altPassage[0]) {
|
||||
idEntity *node = gameLocal.FindEntity(altPassage);
|
||||
if (node && node->IsType(hhAIPassageway::Type)) {
|
||||
exitNode = reinterpret_cast<hhAIPassageway *> (node);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure no one is blocking where we want to go out
|
||||
idBounds myBnds = GetPhysics()->GetBounds();
|
||||
myBnds.Expand(12.0f);
|
||||
myBnds.TranslateSelf(exitNode->GetExitPos());
|
||||
idEntity *ents[MAX_GENTITIES];
|
||||
int count = gameLocal.clip.EntitiesTouchingBounds(myBnds, -1, ents, MAX_GENTITIES);
|
||||
for(int i=0;i<count;i++) {
|
||||
// We'll have to try again later when the coast is clear
|
||||
if(ents[i] && ents[i] != this && !ents[i]->IsHidden() && (ents[i]->IsType(idActor::Type) || ents[i]->IsType(hhMoveable::Type))) {
|
||||
// Chunk moveables
|
||||
if ( ents[i]->IsType(idMoveable::Type) ) {
|
||||
ents[i]->Damage( this, this, vec3_origin, "damage_gib", 1.0f, INVALID_JOINT );
|
||||
ents[i]->SquishedByDoor(this);
|
||||
continue;
|
||||
}
|
||||
gameLocal.Warning("Passageway exit blocked.\n");
|
||||
PostEventMS(&MA_HandlePassageway, CheckPassageFreq);
|
||||
if(ai_debugBrain.GetInteger() != 0) {
|
||||
gameRenderWorld->DebugBounds(colorYellow, myBnds, vec3_origin, 2000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Else we can just exit now - its safe
|
||||
exitNode->ProcessEvent(&EV_Activate, this);
|
||||
}
|
||||
|
||||
#define LinkScriptVariable( name ) name.LinkTo(scriptObject, #name)
|
||||
void hhHarvesterSimple::LinkScriptVariables() {
|
||||
hhMonsterAI::LinkScriptVariables();
|
||||
LinkScriptVariable(AI_CLIMB_RANGE);
|
||||
LinkScriptVariable(AI_PASSAGEWAY_HEALTH);
|
||||
}
|
||||
|
||||
void hhHarvesterSimple::Event_UseThisPassageway(hhAIPassageway *pn, bool force) {
|
||||
nextPassageway.Assign(pn);
|
||||
|
||||
if (force) { // Snap to position
|
||||
Event_FindReaction("passageway");
|
||||
hhReaction *reaction = targetReaction.GetReaction();
|
||||
if (!reaction) {
|
||||
gameLocal.Warning("useThisPassageway could not find a valid reaction!\n");
|
||||
} else {
|
||||
idVec3 tp = GetTouchPos( reaction->causeEntity.GetEntity(), reaction->desc );
|
||||
SetOrigin(tp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=====================
|
||||
hhHarvesterSimple::Save
|
||||
=====================
|
||||
*/
|
||||
void hhHarvesterSimple::Save( idSaveGame *savefile ) const {
|
||||
savefile->WriteBool( allowPreDeath );
|
||||
savefile->WriteInt( lastAntiProjectileAttack );
|
||||
savefile->WriteInt( passageCount );
|
||||
savefile->WriteBool( bGibOnDeath );
|
||||
savefile->WriteInt( lastPassagewayTime );
|
||||
|
||||
lastPassageway.Save( savefile );
|
||||
nextPassageway.Save( savefile );
|
||||
}
|
||||
|
||||
/*
|
||||
=====================
|
||||
hhHarvesterSimple::Restore
|
||||
=====================
|
||||
*/
|
||||
void hhHarvesterSimple::Restore( idRestoreGame *savefile ) {
|
||||
savefile->ReadBool( allowPreDeath );
|
||||
savefile->ReadInt( lastAntiProjectileAttack );
|
||||
savefile->ReadInt( passageCount );
|
||||
savefile->ReadBool( bGibOnDeath );
|
||||
savefile->ReadInt( lastPassagewayTime );
|
||||
|
||||
lastPassageway.Restore( savefile );
|
||||
nextPassageway.Restore( savefile );
|
||||
|
||||
bSmokes = spawnArgs.GetBool("smokes", "0");
|
||||
}
|
||||
|
||||
void hhHarvesterSimple::SpawnSmoke(void) {
|
||||
const char *bones[MAX_HARVESTER_LEGS];
|
||||
bones[0] = spawnArgs.GetString( "bone_smoke_front_left" );
|
||||
bones[1] = spawnArgs.GetString( "bone_smoke_front_right" );
|
||||
bones[2] = spawnArgs.GetString( "bone_smoke_rear_left" );
|
||||
bones[3] = spawnArgs.GetString( "bone_smoke_rear_right" );
|
||||
|
||||
const char *psystem = spawnArgs.GetString( "fx_smoke" );
|
||||
if ( psystem && *psystem ) {
|
||||
idVec3 bonePos;
|
||||
idMat3 boneAxis;
|
||||
|
||||
for(int i = 0; i < MAX_HARVESTER_LEGS; i++) {
|
||||
if( fxSmoke[i] != NULL ) {
|
||||
gameLocal.Warning("Harvester already had smoke particles for leg %i.\n", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
hhFxInfo fxInfo;
|
||||
|
||||
this->GetJointWorldTransform( bones[i], bonePos, boneAxis );
|
||||
fxInfo.SetEntity( this );
|
||||
fxInfo.SetBindBone( bones[i] );
|
||||
fxInfo.Toggle();
|
||||
fxInfo.RemoveWhenDone( false );
|
||||
|
||||
BroadcastFxInfo( psystem, bonePos, boneAxis, &fxInfo, &EV_Broadcast_AppendFxToList );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void hhHarvesterSimple::ClearSmoke(void) {
|
||||
for(int i=0; i < MAX_HARVESTER_LEGS; i++) {
|
||||
if(fxSmoke[i].IsValid()) {
|
||||
fxSmoke[i]->Nozzle(false);
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = 0; i < MAX_HARVESTER_LEGS; i++) {
|
||||
SAFE_REMOVE(fxSmoke[MAX_HARVESTER_LEGS]);
|
||||
}
|
||||
}
|
||||
|
||||
void hhHarvesterSimple::Event_AppendFxToList(hhEntityFx *fx) {
|
||||
for(int i = 0; i < MAX_HARVESTER_LEGS; ++i) {
|
||||
if(fxSmoke[i] != NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fxSmoke[i] = fx;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void hhHarvesterSimple::Event_GibOnDeath(const idList<idStr>* parmList) {
|
||||
bGibOnDeath = (atoi((*parmList)[0].c_str()) != 0);
|
||||
}
|
||||
|
||||
void hhHarvesterSimple::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) {
|
||||
ClearSmoke();
|
||||
hhMonsterAI::Killed(inflictor, attacker, damage, dir, location);
|
||||
|
||||
if (bGibOnDeath) {
|
||||
Gib(vec3_origin, "damage_gib");
|
||||
}
|
||||
}
|
||||
|
||||
void hhHarvesterSimple::Event_Activate(idEntity *activator) {
|
||||
if (fl.hidden) {
|
||||
// If we're hidden, we're probably birthing out of passageway. Figure out which one.
|
||||
hhAIPassageway *nearest = NULL;
|
||||
float dist, nearDist = idMath::INFINITY;
|
||||
hhAIPassageway *passage = reinterpret_cast<hhAIPassageway *> (gameLocal.FindEntityOfType( hhAIPassageway::Type, NULL ));
|
||||
while (passage) {
|
||||
dist = (passage->GetOrigin() - GetOrigin()).Length();
|
||||
if (dist < nearDist) {
|
||||
nearDist = dist;
|
||||
nearest = passage;
|
||||
}
|
||||
passage = reinterpret_cast<hhAIPassageway *> (gameLocal.FindEntityOfType( hhAIPassageway::Type, passage ));
|
||||
}
|
||||
|
||||
// If we found one near us and it's near enough, mark it as our last passageway
|
||||
if (nearest && nearDist < 250.0f) {
|
||||
lastPassageway = nearest;
|
||||
lastPassagewayTime = gameLocal.time;
|
||||
}
|
||||
}
|
||||
hhMonsterAI::Event_Activate(activator);
|
||||
}
|
||||
|
||||
void hhHarvesterSimple::AnimMove( void ) {
|
||||
idVec3 goalPos;
|
||||
idVec3 delta;
|
||||
idVec3 goalDelta;
|
||||
float goalDist;
|
||||
monsterMoveResult_t moveResult;
|
||||
idVec3 newDest;
|
||||
|
||||
// Turn on physics to prevent flying harvesters
|
||||
BecomeActive(TH_PHYSICS);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HUMANHEAD //shrink functionality
|
||||
float scale = renderEntity.shaderParms[SHADERPARM_ANY_DEFORM_PARM1];
|
||||
if ( scale > 0.0f && scale < 2.0f ) {
|
||||
delta *= scale;
|
||||
}
|
||||
#endif
|
||||
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( hhHarvesterSimple::Type ) ) {
|
||||
StopMove( MOVE_STATUS_BLOCKED_BY_MONSTER );
|
||||
return;
|
||||
}
|
||||
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 hhHarvesterSimple::Event_CanBecomeSolid( void ) {
|
||||
int i;
|
||||
int num;
|
||||
idEntity * hit;
|
||||
idClipModel *cm;
|
||||
idClipModel *clipModels[ MAX_GENTITIES ];
|
||||
|
||||
num = gameLocal.clip.ClipModelsTouchingBounds( physicsObj.GetAbsBounds(), MASK_MONSTERSOLID, clipModels, MAX_GENTITIES );
|
||||
for ( i = 0; i < num; i++ ) {
|
||||
cm = clipModels[ i ];
|
||||
|
||||
// don't check render entities
|
||||
if ( cm->IsRenderModel() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hit = cm->GetEntity();
|
||||
if ( hit == this ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Special case, crush any moveables blocking our start point
|
||||
if ( hit->IsType( idMoveable::Type ) ) {
|
||||
hit->Damage( this, this, vec3_origin, "damage_gib", 1.0f, INVALID_JOINT );
|
||||
hit->SquishedByDoor(this);
|
||||
}
|
||||
|
||||
if ( !hit->fl.takedamage ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( physicsObj.ClipContents( cm ) ) {
|
||||
idThread::ReturnFloat( false );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
idThread::ReturnFloat( true );
|
||||
}
|
||||
|
||||
#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
|
||||
Reference in New Issue
Block a user