ef2-sdk/dlls/game/actorstrategies.cpp

581 lines
16 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 );
}