mirror of
https://github.com/UberGames/EF2GameSource.git
synced 2024-11-12 23:44:08 +00:00
580 lines
15 KiB
C++
580 lines
15 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Logfile:: /EF2/Code/DLLs/game/actorstrategies.cpp $
|
|
// $Revision:: 46 $
|
|
// $Author:: Singlis $
|
|
// $Date:: 9/26/03 2:35p $
|
|
//
|
|
// Copyright (C) 2001 by Ritual Entertainment, Inc.
|
|
// All rights reserved.
|
|
//
|
|
// This source may not be distributed and/or modified without
|
|
// expressly written permission by Ritual Entertainment, Inc.
|
|
//
|
|
//
|
|
//
|
|
// DESCRIPTION:
|
|
// I am replacing the old way that Actor::Think was done by implementing a group of strategies for it
|
|
// instead. This will form the foundation for new flexiblility within the actor class.
|
|
//
|
|
// What I am trying to achieve is a specialized "think" for different types of actors. A Boss, for instance,
|
|
// would use BossThink, an Non-Attacking-NPC could use NPCThink. Using the event system we already have inplace
|
|
// it will be easy to change thinking modalities on the fly, allowing us to make a cowardly NPC turn into a
|
|
// roaring death machine if he gets shot.
|
|
//
|
|
|
|
#include "_pch_cpp.h"
|
|
#include "actorstrategies.h"
|
|
#include "actor.h"
|
|
#include "entity.h"
|
|
|
|
extern cvar_t *ai_showfailure;
|
|
//------------------------- CLASS ------------------------------
|
|
//
|
|
// Name: ActorThink
|
|
// Base Class: None
|
|
//
|
|
// Description: Base class from which all Actor Think Strategies
|
|
// are derived.
|
|
//
|
|
// Method of Use:
|
|
// Deriving a new class from ActorThink is a good
|
|
// to allow Actor to exhibit new behavior.
|
|
//
|
|
//--------------------------------------------------------------
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: DoArchive
|
|
// Class: ActorThink
|
|
//
|
|
// Description: Archives this instance of ActorThink
|
|
//
|
|
// Parameters:
|
|
// Archiver the class that receives/supplies
|
|
// archival info
|
|
//
|
|
// Returns: None
|
|
//----------------------------------------------------------------
|
|
void ActorThink::DoArchive( Archiver &arc )
|
|
{
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: ProcessBehaviors
|
|
// Class: ActorThink
|
|
//
|
|
// Description: Processes all the Actors Behaviors
|
|
//
|
|
// Parameters:
|
|
// Actor in question
|
|
//
|
|
// Returns: None
|
|
//----------------------------------------------------------------
|
|
void ActorThink::ProcessBehaviors( Actor &actor )
|
|
{
|
|
bool showFailure;
|
|
showFailure = ( ai_showfailure->integer != 0 );
|
|
|
|
// Do the state machine for this creature
|
|
actor.ProcessMasterStateMachine();
|
|
actor.ProcessActorStateMachine();
|
|
|
|
// Process the current behavior
|
|
|
|
if ( actor.behavior )
|
|
{
|
|
actor.behaviorCode = actor.behavior->Evaluate( actor );
|
|
|
|
if ( actor.behaviorCode == BEHAVIOR_FAILED )
|
|
{
|
|
actor.behaviorFailureReason = actor.behavior->GetFailureReason();
|
|
}
|
|
|
|
if ( actor.behaviorCode != BEHAVIOR_EVALUATING )
|
|
{
|
|
if ( stricmp( actor.behavior->getClassname(), "Talk" ) == 0 )
|
|
{
|
|
actor.EndBehavior();
|
|
actor.EndMode();
|
|
}
|
|
else
|
|
{
|
|
actor.EndBehavior();
|
|
}
|
|
}
|
|
|
|
// Process state machine again because the behavior finished
|
|
actor.ProcessActorStateMachine();
|
|
}
|
|
|
|
// Process the current head behavior
|
|
if ( actor.headBehavior )
|
|
{
|
|
actor.headBehaviorCode = actor.headBehavior->Evaluate( actor );
|
|
|
|
if ( actor.headBehaviorCode != BEHAVIOR_EVALUATING )
|
|
{
|
|
actor.EndHeadBehavior();
|
|
}
|
|
|
|
// Process state machine again because the behavior finished
|
|
actor.ProcessActorStateMachine();
|
|
}
|
|
|
|
// Process the current eye behavior
|
|
if ( actor.eyeBehavior )
|
|
{
|
|
actor.eyeBehaviorCode = actor.eyeBehavior->Evaluate( actor );
|
|
|
|
if ( actor.eyeBehaviorCode != BEHAVIOR_EVALUATING )
|
|
{
|
|
actor.EndEyeBehavior();
|
|
}
|
|
|
|
// Process state machine again because the behavior finished
|
|
actor.ProcessActorStateMachine();
|
|
}
|
|
|
|
// Process the current torso behavior
|
|
if ( actor.torsoBehavior )
|
|
{
|
|
actor.torsoBehaviorCode = actor.torsoBehavior->Evaluate( actor );
|
|
|
|
if ( actor.torsoBehaviorCode != BEHAVIOR_EVALUATING )
|
|
{
|
|
actor.EndTorsoBehavior();
|
|
}
|
|
|
|
// Process state machine again because the behavior finished
|
|
actor.ProcessActorStateMachine();
|
|
}
|
|
|
|
// Reset the animation is done flag
|
|
actor.SetActorFlag( ACTOR_FLAG_ANIM_DONE, false );
|
|
actor.SetActorFlag( ACTOR_FLAG_TORSO_ANIM_DONE, false );
|
|
|
|
// Change the animation if necessary
|
|
if ( ( actor.newanimnum != -1 ) || ( actor.newTorsoAnimNum != -1 ) )
|
|
actor.ChangeAnim();
|
|
|
|
if ( !showFailure )
|
|
return;
|
|
|
|
if ( actor.behaviorCode == BEHAVIOR_FAILED && !actor.GetActorFlag(ACTOR_FLAG_DISPLAYING_FAILURE_FX) )
|
|
{
|
|
Event* event;
|
|
event = new Event( EV_DisplayEffect );
|
|
event->AddString( "electric" );
|
|
actor.ProcessEvent( event );
|
|
actor.SetActorFlag( ACTOR_FLAG_DISPLAYING_FAILURE_FX , true );
|
|
return;
|
|
}
|
|
else if ( actor.behaviorCode != BEHAVIOR_FAILED && actor.GetActorFlag(ACTOR_FLAG_DISPLAYING_FAILURE_FX) )
|
|
{
|
|
Event* event;
|
|
event = new Event( EV_DisplayEffect );
|
|
event->AddString( "noelectric" );
|
|
actor.ProcessEvent( event );
|
|
actor.SetActorFlag( ACTOR_FLAG_DISPLAYING_FAILURE_FX , false );
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: DoMove
|
|
// Class: ActorThink
|
|
//
|
|
// Description: Does Movement Stuff for Actor
|
|
//
|
|
// Parameters:
|
|
// Actor in question
|
|
//
|
|
// Returns: None
|
|
//----------------------------------------------------------------
|
|
void ActorThink::DoMove( Actor &actor )
|
|
{
|
|
if ( ( actor.flags & FL_IMMOBILE ) || ( actor.flags & FL_PARTIAL_IMMOBILE ) )
|
|
{
|
|
actor.animate->StopAnimating();
|
|
return;
|
|
}
|
|
|
|
actor.movementSubsystem->CalcMove();
|
|
actor.movementSubsystem->setLastMove( STEPMOVE_STUCK );
|
|
|
|
stepmoveresult_t lastMove;
|
|
|
|
if ( actor.flags & FL_SWIM )
|
|
lastMove = actor.movementSubsystem->WaterMove();
|
|
else if ( actor.flags & FL_FLY )
|
|
lastMove = actor.movementSubsystem->AirMove();
|
|
else
|
|
lastMove = actor.movementSubsystem->TryMove();
|
|
|
|
actor.movementSubsystem->setLastMove( lastMove );
|
|
|
|
if (
|
|
( actor.movetype != MOVETYPE_NONE ) &&
|
|
( actor.movetype != MOVETYPE_STATIONARY ) &&
|
|
actor.GetActorFlag( ACTOR_FLAG_TOUCH_TRIGGERS ) &&
|
|
actor.GetActorFlag( ACTOR_FLAG_HAVE_MOVED )
|
|
)
|
|
G_TouchTriggers( &actor );
|
|
|
|
if ( actor.groundentity && ( actor.groundentity->entity != world ) && !M_CheckBottom( &actor ) )
|
|
actor.flags |= FL_PARTIALGROUND;
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: UpdateBossHealth
|
|
// Class: ActorThink
|
|
//
|
|
// Description: Handles Boss Specific behavior
|
|
//
|
|
// Parameters:
|
|
// Actor that is the boss in question
|
|
//
|
|
// Returns: None
|
|
//----------------------------------------------------------------
|
|
void ActorThink::UpdateBossHealth( Actor &actor )
|
|
{
|
|
char bosshealth_string[20];
|
|
|
|
sprintf( bosshealth_string, "%.5f", actor.health / actor.max_boss_health );
|
|
gi.cvar_set( "bosshealth", bosshealth_string );
|
|
|
|
gi.cvar_set( "bossname", actor.getName() );
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: CheckGround
|
|
// Class: ActorThink
|
|
//
|
|
// Description: Checks that the actor is on the ground, and does
|
|
// Falling Damage if necessary
|
|
//
|
|
// Parameters:
|
|
// Actor in question
|
|
//
|
|
// Returns: None
|
|
//----------------------------------------------------------------
|
|
void ActorThink::CheckGround( Actor &actor )
|
|
{
|
|
|
|
if ( actor.GetActorFlag( ACTOR_FLAG_HAVE_MOVED ) ||
|
|
( actor.groundentity && actor.groundentity->entity && ( actor.groundentity->entity->entnum != ENTITYNUM_WORLD ) ) )
|
|
actor.CheckGround();
|
|
|
|
// Add Fall Damage if necessary
|
|
if ( actor.groundentity )
|
|
{
|
|
if ( !actor.Immune( MOD_FALLING ) && !( actor.flags & FL_FLY ) && ( actor.origin.z + 1000.0f < actor.last_ground_z ) )
|
|
actor.Damage( world, world, 1000.0f, actor.origin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_FALLING );
|
|
|
|
actor.last_ground_z = actor.origin.z;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: InanimateObject
|
|
// Class: ActorThink
|
|
//
|
|
// Description: Handles behavior ending, if the Actor is an
|
|
// InanimateObject
|
|
//
|
|
// Parameters:
|
|
// Actor in question
|
|
//
|
|
// Returns: None
|
|
//----------------------------------------------------------------
|
|
void ActorThink::InanimateObject( Actor &actor )
|
|
{
|
|
if ( actor.behavior && actor.behavior->Evaluate( actor ) != BEHAVIOR_EVALUATING )
|
|
{
|
|
actor.EndBehavior();
|
|
|
|
// stop thinking
|
|
actor.turnThinkOff();
|
|
ActiveList.RemoveObject( &actor );
|
|
}
|
|
|
|
if ( actor.headBehavior && actor.headBehavior->Evaluate( actor ) != BEHAVIOR_EVALUATING )
|
|
{
|
|
actor.EndHeadBehavior();
|
|
|
|
// stop thinking
|
|
actor.turnThinkOff();
|
|
ActiveList.RemoveObject( &actor );
|
|
}
|
|
|
|
if ( actor.eyeBehavior && actor.eyeBehavior->Evaluate( actor ) != BEHAVIOR_EVALUATING )
|
|
{
|
|
actor.EndEyeBehavior();
|
|
|
|
// stop thinking
|
|
actor.turnThinkOff();
|
|
ActiveList.RemoveObject( &actor );
|
|
}
|
|
|
|
if ( actor.torsoBehavior && actor.torsoBehavior->Evaluate( actor ) != BEHAVIOR_EVALUATING )
|
|
{
|
|
actor.EndTorsoBehavior();
|
|
|
|
// stop thinking
|
|
actor.turnThinkOff();
|
|
ActiveList.RemoveObject( &actor );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: TryDrown
|
|
// Class: ActorThink
|
|
//
|
|
// Description: Damages the Actor if they are drowning
|
|
//
|
|
// Parameters:
|
|
// Actor in question
|
|
//
|
|
// Returns: None
|
|
//----------------------------------------------------------------
|
|
void ActorThink::TryDrown( Actor &actor )
|
|
{
|
|
if ( actor.waterlevel == 3 && !( actor.flags & FL_SWIM ) )
|
|
{
|
|
// if out of air, start drowning
|
|
if ( actor.air_finished < level.time )
|
|
{
|
|
// we may have been in a water brush when we spawned, so check our water level again to be sure
|
|
actor.movementSubsystem->CheckWater();
|
|
if ( actor.waterlevel < 3 )
|
|
{
|
|
// we're ok, so reset our air
|
|
actor.air_finished = level.time + 5.0f;
|
|
}
|
|
else if ( ( actor.next_drown_time < level.time ) && ( actor.health > 0 ) )
|
|
{
|
|
// drown!
|
|
actor.next_drown_time = level.time + 1.0f;
|
|
|
|
//Sound( "snd_uwchoke", CHAN_VOICE );
|
|
actor.BroadcastSound();
|
|
|
|
actor.Damage( world, world, 15.0f, actor.origin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_DROWN );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
actor.air_finished = level.time + 5.0f;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: ActorStateUpdate
|
|
// Class: ActorThink
|
|
//
|
|
// Description: Updates miscellaneous actor state variables
|
|
//
|
|
// Parameters:
|
|
// Actor in question
|
|
//
|
|
// Returns: None
|
|
//----------------------------------------------------------------
|
|
void ActorThink::ActorStateUpdate( Actor &actor )
|
|
{
|
|
// Update move status
|
|
if ( actor.last_origin != actor.origin )
|
|
actor.SetActorFlag( ACTOR_FLAG_HAVE_MOVED, true );
|
|
else
|
|
actor.SetActorFlag( ACTOR_FLAG_HAVE_MOVED, false );
|
|
|
|
// Set Origins;
|
|
actor.last_origin = actor.origin;
|
|
|
|
// Check for the ground
|
|
if ( !( actor.flags & FL_SWIM ) && !( actor.flags & FL_FLY ) && actor.GetStickToGround() )
|
|
CheckGround( actor );
|
|
|
|
if ( !actor.deadflag )
|
|
{
|
|
// Check to see if stunned
|
|
actor.CheckStun();
|
|
|
|
// Handle Game Specific Stuff
|
|
actor.gameComponent->HandleThink();
|
|
|
|
// See if can talk to the player
|
|
if(actor.DialogMode == DIALOG_MODE_ANXIOUS)
|
|
actor.TryTalkToPlayer();
|
|
|
|
// Blink -- Emotions
|
|
if ( actor.GetActorFlag( ACTOR_FLAG_SHOULD_BLINK ) )
|
|
actor.TryBlink();
|
|
|
|
if ( actor.getHeadWatchAllowed() )
|
|
actor.headWatcher->HeadWatchTarget();
|
|
|
|
//Update Eyeposition
|
|
actor.eyeposition[ 2 ] = actor.maxs[ 2 ] + actor.eyeoffset[ 2 ];
|
|
|
|
// See if we should damage the actor because of waterlevel
|
|
TryDrown( actor );
|
|
|
|
if ( actor.groundentity && ( actor.groundentity->entity != world ) && !M_CheckBottom( &actor ) )
|
|
actor.flags |= FL_PARTIALGROUND;
|
|
}
|
|
}
|
|
|
|
|
|
//------------------------- CLASS ------------------------------
|
|
//
|
|
// Name: DefaultThink
|
|
// Base Class: ActorThink
|
|
//
|
|
// Description:
|
|
// Think class instantiated by most actors -- and
|
|
// instantiated in ALL actors by default
|
|
//
|
|
// Method of Use:
|
|
// This is the default behavior for Actor, nothing
|
|
// special need be done to use this class
|
|
//
|
|
//--------------------------------------------------------------
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: Think
|
|
// Class: DefaultThink
|
|
//
|
|
// Description: Main Think Function for Actor
|
|
//
|
|
// Parameters:
|
|
// Actor in question
|
|
//
|
|
// Returns: None
|
|
//----------------------------------------------------------------
|
|
void DefaultThink::Think( Actor &actor )
|
|
{
|
|
if ( actor.flags & FL_IMMOBILE )
|
|
{
|
|
// Update boss health if necessary
|
|
if ( (actor.GetActorFlag( ACTOR_FLAG_UPDATE_BOSS_HEALTH ) && actor.max_boss_health && ( actor.mode == ACTOR_MODE_AI )) || actor.GetActorFlag(ACTOR_FLAG_FORCE_LIFEBAR) )
|
|
UpdateBossHealth( actor );
|
|
|
|
if ( actor.statemap )
|
|
{
|
|
actor.last_time_active = level.time;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Update boss health if necessary
|
|
if ( (actor.GetActorFlag( ACTOR_FLAG_UPDATE_BOSS_HEALTH ) && actor.max_boss_health && ( actor.mode == ACTOR_MODE_AI )) || actor.GetActorFlag(ACTOR_FLAG_FORCE_LIFEBAR) )
|
|
UpdateBossHealth( actor );
|
|
|
|
if ( actor.postureController )
|
|
actor.postureController->evaluate();
|
|
|
|
ActorStateUpdate( actor );
|
|
|
|
if ( !actor.deadflag )
|
|
{
|
|
// Update the hate list
|
|
actor.enemyManager->Update();
|
|
|
|
// Do the movement
|
|
DoMove( actor );
|
|
|
|
if ( actor.actortype == IS_INANIMATE )
|
|
{
|
|
InanimateObject( actor );
|
|
return;
|
|
}
|
|
|
|
//Process our behaviors
|
|
ProcessBehaviors( actor );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//------------------------- CLASS ------------------------------
|
|
//
|
|
// Name: SimplifiedThink
|
|
// Base Class: ActorThink
|
|
//
|
|
// Description:
|
|
// This is a light weight version of Default Think
|
|
// The purpose of this class is to allow Actors that
|
|
// move, have behaviors and accept messages without
|
|
// the CPU overhead of doing DefaultThink
|
|
//
|
|
// Method of Use:
|
|
// Actors can be given this method of updating via
|
|
// the script command "setsimplifiedthink"
|
|
//
|
|
//--------------------------------------------------------------
|
|
SimplifiedThink::SimplifiedThink( Actor *actor ) :
|
|
_actor( actor ),
|
|
_previousContents( actor->getContents() )
|
|
{
|
|
actor->setContents( 0 );
|
|
}
|
|
|
|
SimplifiedThink::~SimplifiedThink( void )
|
|
{
|
|
_actor->setContents( _previousContents );
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: DoMove
|
|
// Class: SimplifiedThink
|
|
//
|
|
// Description: Does Movement Stuff for Actor
|
|
//
|
|
// Parameters:
|
|
// Actor in question
|
|
//
|
|
// Returns: None
|
|
//----------------------------------------------------------------
|
|
void SimplifiedThink::DoMove( Actor &actor )
|
|
{
|
|
actor.last_origin = actor.origin;
|
|
actor.movementSubsystem->CalcMove();
|
|
actor.movementSubsystem->setLastMove( actor.movementSubsystem->SimpleMove( actor.GetStickToGround() ) );
|
|
}
|
|
|
|
void SimplifiedThink::DoArchive( Archiver &arc )
|
|
{
|
|
ActorThink::DoArchive( arc );
|
|
arc.ArchiveInteger ( &_previousContents );
|
|
arc.ArchiveSafePointer( &_actor );
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
// Name: Think
|
|
// Class: SimplifiedThink
|
|
//
|
|
// Description: Main Think Function for Actor
|
|
//
|
|
// Parameters:
|
|
// Actor in question
|
|
//
|
|
// Returns: None
|
|
//----------------------------------------------------------------
|
|
void SimplifiedThink::Think( Actor &actor )
|
|
{
|
|
// Update the hate list
|
|
actor.enemyManager->TrivialUpdate();
|
|
|
|
if ( actor.GetActorFlag( ACTOR_FLAG_SHOULD_BLINK ) )
|
|
actor.TryBlink();
|
|
|
|
//Process our behaviors
|
|
ProcessBehaviors( actor );
|
|
|
|
// Do the movement
|
|
DoMove( actor );
|
|
}
|