//----------------------------------------------------------------------------- // // $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 ); }