/* ** Command & Conquer Renegade(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ /*********************************************************************************************** *** Confidential - Westwood Studios *** *********************************************************************************************** * * * Project Name : Commando * * * * $Archive:: /Commando/Code/Combat/action.cpp $* * * * $Author:: Byon_g $* * * * $Modtime:: 3/19/02 11:31a $* * * * $Revision:: 266 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "action.h" #include "input.h" #include "persistfactory.h" #include "combatchunkid.h" #include "path.h" #include "pathsolve.h" #include "pathmgr.h" #include "vehicledriver.h" #include "pilot.h" #include "soldier.h" #include "vehicle.h" #include "weapons.h" #include "movephys.h" #include "pathfind.h" #include "pathfindportal.h" #include "debug.h" #include "animcontrol.h" #include "conversationmgr.h" #include "activeconversation.h" #include "orator.h" #include "soldierobserver.h" #include "physcon.h" #include "phys3.h" #include "humanphys.h" #include "pscene.h" #include "staticphys.h" #include "elevator.h" #include "doors.h" #include "pathaction.h" #include "crandom.h" #include "playertype.h" #include "wwprofile.h" #include "combat.h" #include "waypath.h" #include "waypoint.h" #include "gameobjmanager.h" #include "colmathaabox.h" #include "dinput.h" int _ActionActCalls = 0; int _ActionCodeChanges = 0; /* ** */ static bool Display_Findpaths = false; void Toggle_Display_Findpaths( void ) { Display_Findpaths = !Display_Findpaths; } /* ** ActionCodeClass */ class ActionCodeClass : public PersistClass { public: typedef enum { ACTION_DONE, ACTION_IN_PROGRESS, } ActResult; ActionCodeClass( void ) : Action( NULL ) {} virtual ~ActionCodeClass( void ) {} /* ** Save / Load */ enum { CHUNKID_PARENT = 1031001350, }; virtual bool Save( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_PARENT ); PersistClass::Save( csave ); csave.End_Chunk(); return true; } virtual bool Load( ChunkLoadClass & cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_PARENT: PersistClass::Load( cload ); break; default: Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Chunk(); } return true; } virtual void Init( ActionClass * action ) { WWASSERT( action ); WWASSERT( Action == NULL ); Action = action; // // Check to see if we need to change the soldier's AI state // if ( Action->Get_Parameters().AIState != NO_AI_STATE_CHANGE ) { SmartGameObj * obj = Action->Get_Action_Obj(); if ( obj != NULL ) { // // Check to ensure this is really a soldier // SoldierGameObj * soldier = obj->As_SoldierGameObj(); if ( soldier != NULL ) { soldier->Set_AI_State( Action->Get_Parameters().AIState ); } } } } virtual void Modify_Parameters( const SafeActionParamsStruct & parameters, bool modify_move, bool modify_attack ) {} virtual void Set_Action( ActionClass * action ) { WWASSERT( action ); WWASSERT( Action == NULL ); Action = action; } virtual void Shutdown( void ) { Action = NULL; } virtual ActResult Act( void ) { return ACTION_IN_PROGRESS; } virtual bool Is_Animating( void ) { return false; } virtual bool Is_Busy( void ) { return false; } virtual void Begin_Hibernation( void ) {} virtual void End_Hibernation( void ) {} protected: ActionClass * Action; }; /* ** */ SimpleDynVecClass ActionDeleteList; void Register_Action_Code_Deletion( ActionCodeClass * action ) { ActionDeleteList.Add( action ); } void Delete_Action_Code( void ) { for ( int i = 0; i < ActionDeleteList.Count(); i++ ) { delete ActionDeleteList[i]; } // Clear vector but don't allow shrink. ActionDeleteList.Delete_All(false); } /* ** Follow Input Action Code Class */ class FollowInputActionCodeClass; SimplePersistFactoryClass _FollowInputActionCodeClassFactory; class FollowInputActionCodeClass : public ActionCodeClass { public: virtual const PersistFactoryClass & Get_Factory( void ) const { return _FollowInputActionCodeClassFactory; } /* ** Save / Load */ enum { CHUNKID_PARENT = 1031001349, }; virtual bool Save( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_PARENT ); ActionCodeClass::Save( csave ); csave.End_Chunk(); return true; } virtual bool Load( ChunkLoadClass & cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_PARENT: ActionCodeClass::Load( cload ); break; default: Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Chunk(); } return true; } virtual ActResult Act( void ) { float amount; SmartGameObj * obj = Action->Get_Action_Obj(); // If we're in a vehicle, use the input function VEHICLE_TURN_LEFT... if ((obj->As_SoldierGameObj() != NULL) && (obj->As_SoldierGameObj()->Get_Vehicle() != NULL)) { amount = Input::Get_Amount( INPUT_FUNCTION_VEHICLE_TURN_LEFT ) - Input::Get_Amount( INPUT_FUNCTION_VEHICLE_TURN_RIGHT ); } else { amount = Input::Get_Amount( INPUT_FUNCTION_TURN_LEFT ) - Input::Get_Amount( INPUT_FUNCTION_TURN_RIGHT ); } obj->Set_Analog_Control( ControlClass::ANALOG_TURN_LEFT, amount ); float forward_amount = Input::Get_Amount( INPUT_FUNCTION_MOVE_FORWARD ) - Input::Get_Amount( INPUT_FUNCTION_MOVE_BACKWARD ); float left_amount = Input::Get_Amount( INPUT_FUNCTION_MOVE_LEFT ) - Input::Get_Amount( INPUT_FUNCTION_MOVE_RIGHT ); float up_amount = Input::Get_Amount( INPUT_FUNCTION_MOVE_UP ) - Input::Get_Amount( INPUT_FUNCTION_MOVE_DOWN ); if ( obj->As_SoldierGameObj() && obj->As_SoldierGameObj()->Get_Vehicle() ) { VehicleGameObj * vehicle = obj->As_SoldierGameObj()->Get_Vehicle(); // Convert turn to straffe if ( vehicle->Is_Aircraft() && (Input::Is_Button_Down (DIK_LCONTROL) || Input::Is_Button_Down (DIK_LMENU) ) ) { left_amount = amount; obj->Set_Analog_Control( ControlClass::ANALOG_TURN_LEFT, 0 ); } } Vector3 move( forward_amount, left_amount, up_amount ); float length = move.Length(); if ( length > 1 ) { forward_amount /= length; left_amount /= length; } #if 0 if ( Input::Get_State( INPUT_FUNCTION_WALK_MODE ) ) { forward_amount /= 4; left_amount /= 4; } #else obj->Set_Boolean_Control( ControlClass::BOOLEAN_WALK, Input::Get_State( INPUT_FUNCTION_WALK_MODE ) ); #endif obj->Set_Analog_Control( ControlClass::ANALOG_MOVE_FORWARD, forward_amount ); obj->Set_Analog_Control( ControlClass::ANALOG_MOVE_LEFT, left_amount ); obj->Set_Analog_Control( ControlClass::ANALOG_MOVE_UP, up_amount ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_JUMP, Input::Get_State( INPUT_FUNCTION_JUMP ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_VEHICLE_TOGGLE_GUNNER, Input::Get_State( INPUT_FUNCTION_VEHICLE_TOGGLE_GUNNER ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_NEXT, Input::Get_State( INPUT_FUNCTION_NEXT_WEAPON ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_PREV, Input::Get_State( INPUT_FUNCTION_PREV_WEAPON ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_FIRE_PRIMARY, Input::Get_State( INPUT_FUNCTION_FIRE_WEAPON_PRIMARY ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_FIRE_SECONDARY, Input::Get_State( INPUT_FUNCTION_FIRE_WEAPON_SECONDARY ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_USE, Input::Get_State( INPUT_FUNCTION_USE_WEAPON ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_RELOAD, Input::Get_State( INPUT_FUNCTION_RELOAD_WEAPON ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_DIVE_FORWARD, Input::Get_State( INPUT_FUNCTION_DIVE_FORWARD ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_DIVE_BACKWARD, Input::Get_State( INPUT_FUNCTION_DIVE_BACKWARD ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_DIVE_LEFT, Input::Get_State( INPUT_FUNCTION_DIVE_LEFT ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_DIVE_RIGHT, Input::Get_State( INPUT_FUNCTION_DIVE_RIGHT ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_CROUCH, Input::Get_State( INPUT_FUNCTION_CROUCH ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_ACTION, Input::Get_State( INPUT_FUNCTION_ACTION ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_DROP_FLAG, Input::Get_State( INPUT_FUNCTION_DROP_FLAG ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_NO_WEAPON, Input::Get_State( INPUT_FUNCTION_SELECT_NO_WEAPON ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_0, Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_0 ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_1, Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_1 ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_2, Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_2 ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_3, Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_3 ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_4, Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_4 ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_5, Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_5 ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_6, Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_6 ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_7, Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_7 ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_8, Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_8 ) ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_SELECT_WEAPON_9, Input::Get_State( INPUT_FUNCTION_SELECT_WEAPON_9 ) ); return ACTION_IN_PROGRESS; } }; /* ** Stand Action Code Class */ class StandActionCodeClass; SimplePersistFactoryClass _StandActionCodeClassFactory; class StandActionCodeClass : public ActionCodeClass { public: virtual const PersistFactoryClass & Get_Factory( void ) const { return _StandActionCodeClassFactory; } /* ** Save / Load */ enum { CHUNKID_PARENT = 1031001348, }; virtual bool Save( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_PARENT ); ActionCodeClass::Save( csave ); csave.End_Chunk(); return true; } virtual bool Load( ChunkLoadClass & cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_PARENT: ActionCodeClass::Load( cload ); break; default: Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Chunk(); } return true; } virtual ActResult Act( void ) { return ACTION_IN_PROGRESS; } }; /* ** Play Animation Action Code Class */ class PlayAnimationActionCodeClass; SimplePersistFactoryClass _PlayAnimationActionCodeClassFactory; class PlayAnimationActionCodeClass : public ActionCodeClass { public: virtual const PersistFactoryClass & Get_Factory( void ) const { return _PlayAnimationActionCodeClassFactory; } /* ** Save / Load */ enum { CHUNKID_PARENT = 1031001347, }; virtual bool Save( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_PARENT ); ActionCodeClass::Save( csave ); csave.End_Chunk(); return true; } virtual bool Load( ChunkLoadClass & cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_PARENT: ActionCodeClass::Load( cload ); break; default: Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Chunk(); } return true; } virtual bool Is_Animating( void ) { return true; } virtual void Init( ActionClass * action ) { ActionCodeClass::Init( action ); SmartGameObj * obj = Action->Get_Action_Obj(); WWASSERT( obj->Get_Anim_Control() != NULL ); obj->Set_Animation( action->Get_Parameters().SafeAnimationName, action->Get_Parameters().AnimationLooping ); } virtual void Shutdown( void ) { SmartGameObj * obj = Action->Get_Action_Obj(); WWASSERT( obj->Get_Anim_Control() != NULL ); // WWASSERT( obj->Get_Anim_Control()->Get_Animation_Name()[0] != 0 ); obj->Set_Animation( NULL, false ); ActionCodeClass::Shutdown(); } virtual ActResult Act( void ) { SmartGameObj * obj = Action->Get_Action_Obj(); WWASSERT( obj->Get_Anim_Control() != NULL ); if ( obj->Get_Anim_Control()->Get_Animation_Name()[0] == 0 ) { Debug_Say(( "Not playing an anim when we should be playing %s\n", Action->Get_Parameters().SafeAnimationName )); Action->Done( ACTION_COMPLETE_NORMAL ); return ACTION_DONE; } // WWASSERT( obj->Get_Anim_Control()->Get_Animation_Name()[0] != 0 ); if ( obj->Get_Anim_Control()->Is_Complete() ) { Action->Done( ACTION_COMPLETE_NORMAL ); return ACTION_DONE; } return ACTION_IN_PROGRESS; } }; /* ** Base Action Code Class ** ** This action will move and/or shoot context Object to the context Locations ** From the outside, you can set the moveto location, and you can set the ** shootAt location. The last one you set sets the moving flag, which ** is ignored if object can move and shoot independently. ** Need to know: Can object move and fire independantly( if turreted || moves laterally ) Is Object Turreted ( gun can face a different direction than object, just tanks ) Can object move laterally (tanks and cars cannot, humans can) Can object turn without moving ( cars cannot ) */ class BaseActionCodeClass : public ActionCodeClass { public: BaseActionCodeClass( void ) { IsMoving = false; IsAttacking = false; } /* ** Save / Load */ enum { CHUNKID_PARENT = 1031001345, CHUNKID_VARIABLES, CHUNKID_VEHICLE_DRIVER, CHUNKID_PILOT, MICROCHUNKID_IS_ATTACKING = 1, MICROCHUNKID_IS_MOVING = 2, }; virtual bool Save( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_PARENT ); ActionCodeClass::Save( csave ); csave.End_Chunk(); csave.Begin_Chunk( CHUNKID_VARIABLES ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_IS_ATTACKING, IsAttacking ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_IS_MOVING, IsMoving ); csave.End_Chunk(); csave.Begin_Chunk( CHUNKID_VEHICLE_DRIVER ); VehicleDriver.Save( csave ); csave.End_Chunk(); csave.Begin_Chunk( CHUNKID_PILOT ); Pilot.Save( csave ); csave.End_Chunk(); return true; } virtual bool Load( ChunkLoadClass & cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_PARENT: ActionCodeClass::Load( cload ); break; case CHUNKID_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { READ_MICRO_CHUNK( cload, MICROCHUNKID_IS_ATTACKING, IsAttacking ); READ_MICRO_CHUNK( cload, MICROCHUNKID_IS_MOVING, IsMoving ); default: Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Micro_Chunk(); } break; case CHUNKID_VEHICLE_DRIVER: VehicleDriver.Load( cload ); break; case CHUNKID_PILOT: Pilot.Load( cload ); break; default: Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Chunk(); } return true; } // Return a pointer to the object that will be doing the movement SmartGameObj *Peek_Transport_Object( SmartGameObj *game_obj ) { SmartGameObj *transport = game_obj; if ( game_obj->As_SoldierGameObj() != NULL && game_obj->As_SoldierGameObj()->Get_Profile_Vehicle() != NULL ) { transport = game_obj->As_SoldierGameObj()->Get_Profile_Vehicle(); } return transport; } void Clamped_Units( Vector3& value, float rate ) { // convert to "units", and clamp rate *= TimeManager::Get_Frame_Seconds(); if ( rate > 0.0001f ) { value /= rate; } if ( (rate <= 0.0001f) || value.Length() > 1.0 ) { value.Normalize(); } } // Move Human object to given relative position void Human_Move_To_Relative( Vector3 & orig_rel_pos ) { //Debug_Say(( "Mov To Rel %f %f %f\n", rel_pos.X, rel_pos.Y, rel_pos.Z )); SmartGameObj * obj = Action->Get_Action_Obj(); float speed = Action->Get_Parameters().MoveSpeed; Vector3 move = orig_rel_pos; // // Zero out the relative distance in the z axis (if necessary) // SoldierGameObj * soldier = obj->As_SoldierGameObj(); if ( soldier == NULL || soldier->Is_On_Ladder() == false) { move.Z = 0; } Clamped_Units( move, obj->Get_Max_Speed() * speed ); if ( move.Length2() > 1 ) { move.Normalize(); } obj->Set_Analog_Control( ControlClass::ANALOG_MOVE_FORWARD, move[0] * speed ); obj->Set_Analog_Control( ControlClass::ANALOG_MOVE_LEFT, move[1] * speed ); // Special case for soldier moving if ( soldier && soldier->Is_On_Ladder() ) { obj->Set_Analog_Control( ControlClass::ANALOG_MOVE_FORWARD, (move[2] >= 0) ? 1 : -1 ); // Could we make this move up? } } // Move object to given relative position, return if complete bool Move_To_Absolute( const Vector3 & abs_pos, bool ignore_arrived_dist = false ) { SmartGameObj * obj = Action->Get_Action_Obj(); // Should the vehicle AI code take over? SmartGameObj *transport_obj = Peek_Transport_Object( obj ); VehicleGameObj *vehicle = transport_obj->As_VehicleGameObj (); if ( vehicle != NULL ) { bool retval = false; if (vehicle->Is_Aircraft ()) { Pilot.Set_Speed_Factor( Action->Get_Parameters().MoveSpeed ); if ( Action->Get_Parameters().AttackObject != NULL ) { Vector3 target_pos; Action->Get_Parameters().AttackObject->Get_Position( &target_pos ); Pilot.Set_Target( &target_pos ); } Pilot.Set_Speed_Factor( Action->Get_Parameters().MoveSpeed ); Pilot.Set_Arrived_Dist( Action->Get_Parameters().MoveArrivedDistance ); retval = Pilot.Think(); } else { VehicleDriver.Set_Next_Position( abs_pos ); VehicleDriver.Set_Speed_Factor( Action->Get_Parameters().MoveSpeed ); VehicleDriver.Set_Arrived_Dist( Action->Get_Parameters().MoveArrivedDistance ); VehicleDriver.Force_Backup( Action->Get_Parameters().MoveBackup ); retval = VehicleDriver.Drive(); } IsMoving = true; return retval; } // If we want to be facing a particular position, set targeting to it. if ( Action->Get_Parameters().ForceFacing ) { obj->Set_Targeting( Action->Get_Parameters().FaceLocation, false ); // Don't tilt } Vector3 curr_pos; obj->Get_Position( &curr_pos ); curr_pos -= abs_pos; // I'm having soldiers get stuck because they are directly below // the goal point. Lets try ignoring Z curr_pos.Z = 0; float range = curr_pos.Length(); SoldierGameObj *soldier = obj->As_SoldierGameObj(); bool done_facing = true; // Assume we are done facing // // If we are not attacking, and not force facing, and are a soldier and // are not on a ladder and not very close // if ( !IsAttacking && !Action->Get_Parameters().ForceFacing && !Action->Get_Parameters().IgnoreFacing && soldier && !soldier->Is_On_Ladder() && (range > 0.1f ) ) { // Then try to face the point we are moving to Vector3 face_pt = abs_pos + Vector3( 0,0, obj->Get_Bullseye_Offset_Z() ); done_facing = obj->Set_Targeting( face_pt, false ); } // // Determine how close we want to get to the end-point before // we stop moving // float arrived_dist = Action->Get_Parameters().MoveArrivedDistance; if ( ignore_arrived_dist ) { arrived_dist = 0.05F; } // // If we are close enough // if ( (Get_Distance_From_Goal() + range) <= arrived_dist) { IsMoving = false; return done_facing; // complete; } IsMoving = true; // Find the relitive location Vector3 rel_pos; Matrix3D::Inverse_Transform_Vector( obj->Get_Transform(), abs_pos, &rel_pos ); // we done facing, or facing close enough, move float goal_bearing = WWMath::Atan2( rel_pos.Y, rel_pos.X ); if ( done_facing || WWMath::Fabs(goal_bearing) < DEG_TO_RAD( 40 ) ) { Human_Move_To_Relative( rel_pos ); } return false; } virtual float Get_Distance_From_Goal (void) { SmartGameObj * obj = Action->Get_Action_Obj(); // // Get the object's current position // Vector3 curr_pos; obj->Get_Position( &curr_pos ); // // Find the difference between the current position and // the destination // curr_pos -= Action->Get_Parameters().MoveLocation; curr_pos.Z = 0; // // Return the length // return curr_pos.Length (); } protected: bool IsAttacking; bool IsMoving; VehicleDriverClass VehicleDriver; PilotClass Pilot; }; /* ** Goto Action Code Class */ class GotoActionCodeClass; SimplePersistFactoryClass _GotoActionCodeClassFactory; class GotoActionCodeClass : public BaseActionCodeClass { public: virtual const PersistFactoryClass & Get_Factory( void ) const { return _GotoActionCodeClassFactory; } GotoActionCodeClass( void ) : UseMoveObject( false ), FirstCall( true ), PathSolver( NULL ), Path( NULL ) { } ~GotoActionCodeClass( void ) { return ; } /* ** Save / Load */ enum { CHUNKID_PARENT = 1031001308, CHUNKID_VARIABLES, CHUNKID_PATH, CHUNKID_PATH_SOLVER, CHUNKID_PATH_ACTION, MICROCHUNKID_FIRST_CALL = 1, MICROCHUNKID_USE_MOVE_OBJECT = 2, MICROCHUNKID_TARGET_PREV_POS = 3, MICROCHUNKID_PATH_SOLVE_PTR = 4, }; virtual bool Save( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_PARENT ); BaseActionCodeClass::Save( csave ); csave.End_Chunk(); if ( Path != NULL ) { csave.Begin_Chunk( CHUNKID_PATH ); Path->Save( csave ); csave.End_Chunk(); } csave.Begin_Chunk( CHUNKID_VARIABLES ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_FIRST_CALL, FirstCall ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_USE_MOVE_OBJECT, UseMoveObject ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TARGET_PREV_POS, TargetPrevPos ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_PATH_SOLVE_PTR, PathSolver ); csave.End_Chunk(); csave.Begin_Chunk( CHUNKID_PATH_ACTION ); PathAction.Save( csave ); csave.End_Chunk(); return true; } virtual bool Load( ChunkLoadClass & cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_PARENT: BaseActionCodeClass::Load( cload ); break; case CHUNKID_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { READ_MICRO_CHUNK( cload, MICROCHUNKID_FIRST_CALL, FirstCall ); READ_MICRO_CHUNK( cload, MICROCHUNKID_USE_MOVE_OBJECT, UseMoveObject ); READ_MICRO_CHUNK( cload, MICROCHUNKID_TARGET_PREV_POS, TargetPrevPos ); case MICROCHUNKID_PATH_SOLVE_PTR: LOAD_MICRO_CHUNK( cload, PathSolver ); if (PathSolver != NULL) { REQUEST_REF_COUNTED_POINTER_REMAP( (RefCountClass **)&PathSolver ); } break; default: Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Micro_Chunk(); } break; case CHUNKID_PATH: { Path = new PathClass; Path->Load( cload ); } break; case CHUNKID_PATH_ACTION: PathAction.Load( cload ); break; default: Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Chunk(); } return true; } virtual void Begin_Hibernation( void ) { BaseActionCodeClass::Begin_Hibernation(); FirstCall = true; // // Return the path we got from the manager // if ( PathSolver != NULL ) { PathMgrClass::Return_Path_Object( PathSolver ); PathSolver = NULL; } return ; } virtual void Init( ActionClass * action ) { BaseActionCodeClass::Init( action ); FirstCall = true; SoldierGameObj *soldier_obj = Action->Get_Action_Obj()->As_SoldierGameObj(); if ( soldier_obj && Action->Get_Parameters().LookDuration != 0 ) { soldier_obj->Look_At( Action->Get_Parameters().LookLocation, Action->Get_Parameters().LookDuration ); } UseMoveObject = (Action->Get_Parameters().MoveObjectRef.Get_Ptr() != NULL); } virtual void Shutdown( void ) { // // Return the path we got from the manager // if (PathSolver != NULL) { PathMgrClass::Return_Path_Object (PathSolver); PathSolver = NULL; } Release_Path(); BaseActionCodeClass::Shutdown(); return ; } virtual void Init( WaypathClass *waypath ) { // // Return the path we got from the manager // if (PathSolver != NULL) { PathMgrClass::Return_Path_Object (PathSolver); PathSolver = NULL; } Release_Path(); Prepare_Path (); Path->Set_Traversal_Type( PathClass::LINEAR ); Path->Initialize( waypath, -1, -1 ); Initialize_Vehicle_AI(); return ; } virtual bool Is_Busy( void ) { return ( PathAction.Get_State () != PathActionClass::STATE_FINISHED ); } virtual ActResult Arrived() { if ( Action->Get_Parameters().MoveFollow ) { return ACTION_IN_PROGRESS; // Do nothing special when we arrive } if ( Action->Get_Action_Obj() && Action->Get_Parameters().ShutdownEngineOnArrival ) { SmartGameObj *transport_obj = Peek_Transport_Object( Action->Get_Action_Obj() ); if ( transport_obj && transport_obj->As_VehicleGameObj() ) { transport_obj->As_VehicleGameObj()->Enable_Engine (false); } } Action->Done( ACTION_COMPLETE_NORMAL ); return ACTION_DONE; // We are done } virtual ActResult Beeline( void ) { // // Make sure we have a valid path object to follow // if ( Path == NULL ) { Prepare_Path(); Vector3 curr_pos; Action->Get_Action_Obj()->Get_Position( &curr_pos ); Path->Initialize( curr_pos, Action->Get_Parameters().MoveLocation ); Initialize_Vehicle_AI(); } ActResult result = ACTION_IN_PROGRESS; if ( Path != NULL ) { result = Traverse_Path (); } // Tell the unit to beeline w/o using pathfind /*ActResult result = ACTION_IN_PROGRESS; if ( Move_To_Absolute( Action->Get_Parameters().MoveLocation ) ) { result = Arrived(); }*/ return result; } void Prepare_Path( void ) { // // Allocate a path object (if necessary) // if ( Path == NULL ) { Path = new PathClass; } // // Setup the path traverser // PathObjectClass path_obj; Configure_Path_Object( path_obj ); // // Initialize the path // Path->Set_Path_Object( path_obj ); return ; } void Initialize_Vehicle_AI( void ) { // // Get the transport object // SmartGameObj *transport_obj = Peek_Transport_Object( Action->Get_Action_Obj() ); // // Initialize the different driving/flying AI's // VehicleDriver.Initialize( transport_obj, Path ); VehicleDriver.Force_Backup( Action->Get_Parameters().MoveBackup ); Pilot.Initialize( transport_obj ); Pilot.Set_Mode( PilotClass::MODE_FLY_TO_POINT ); Pilot.Set_Current_Path ( Path ); Pilot.Set_Arrived_Dist( Action->Get_Parameters().MoveArrivedDistance ); Pilot.Set_Is_Exact_Z_Important( bool(Action->Get_Parameters().WaypathID != 0) ); // // Check to make sure we aren't trying to drive a "simple" vehicle around // if ( transport_obj != NULL && transport_obj->As_VehicleGameObj () != NULL && transport_obj->Peek_Physical_Object () != NULL && transport_obj->Peek_Physical_Object ()->As_MoveablePhysClass () == NULL ) { // // Warn the user that they are trying to drive a non-driveable object // Debug_Say (("Warning! Attempting to drive a non-moveable vehicle. ID = %d.\n", transport_obj->Get_ID ())); } return ; } void Initialize_Waypath( void ) { Prepare_Path (); // // Lookup the waypath object // WaypathClass *waypath = PathfindClass::Get_Instance()->Find_Waypath( Action->Get_Parameters().WaypathID ); if ( waypath == NULL ) { Debug_Say (("Warning! Waypath %d does not exist.\n", Action->Get_Parameters().WaypathID)); } WWASSERT( waypath != NULL ); // // Initialize the path // Path->Set_Traversal_Type( Action->Get_Parameters().WaypathSplined ? PathClass::SPLINE : PathClass::LINEAR ); Path->Initialize( waypath, Action->Get_Parameters().WaypointStartID, Action->Get_Parameters().WaypointEndID ); return ; } void Configure_Path_Object( PathObjectClass &path_obj ) { // // Determine which game object will actually be doing the moving along the path // SmartGameObj *transport_obj = Peek_Transport_Object( Action->Get_Action_Obj() ); WWASSERT( transport_obj != NULL ); // // Determine the turn radius // float turn_radius = 0; VehicleGameObj *vehicle_obj = transport_obj->As_VehicleGameObj(); if ( vehicle_obj != NULL ) { turn_radius = vehicle_obj->Get_Turn_Radius(); } // // Dig out the physics object that will be moving along the path // MoveablePhysClass *phys_obj = transport_obj->Peek_Physical_Object()->As_MoveablePhysClass(); if (phys_obj != NULL) { // // Configure the path-traverser object // path_obj.Initialize( *phys_obj ); path_obj.Set_Max_Speed( transport_obj->Get_Max_Speed () ); path_obj.Set_Turn_Radius( turn_radius ); path_obj.Set_Flag( PathObjectClass::IS_VEHICLE, bool(vehicle_obj != NULL) ); } // // Pass the key information onto the path object // SoldierGameObj *soldier = Action->Get_Action_Obj()->As_SoldierGameObj(); if ( soldier != NULL ) { path_obj.Set_Key_Ring( soldier->Get_Key_Ring () ); } return ; } void Initialize_Path_Solver( void ) { // // Get a new path object from the manager (if necessary) // if (PathSolver == NULL) { PathSolver = PathMgrClass::Request_Path_Object(); } // // Get the destination position // Vector3 dest_pos = Action->Get_Parameters().MoveLocation; TargetPrevPos = Action->Get_Parameters().MoveLocation; // // Setup the path traverser // PathObjectClass path_obj; Configure_Path_Object( path_obj ); // // Initialize the path itself // Vector3 cur_pos; Peek_Transport_Object (Action->Get_Action_Obj())->Get_Position( &cur_pos ); PathSolver->Set_Path_Object( path_obj ); PathSolver->Reset( cur_pos, dest_pos, 2.0F ); return ; } void Initialize_Path( void ) { Release_Path(); // // Determine if we should use a waypath instead of trying to solve // the path dynamically // if ( Action->Get_Parameters().WaypathID != 0 ) { Initialize_Waypath(); Initialize_Vehicle_AI(); } else { Initialize_Path_Solver(); } return ; } void Release_Path( void ) { Pilot.Reset(); VehicleDriver.Reset(); REF_PTR_RELEASE( Path ); // Reset the PathAction class too - otherwise it keeps the pointer we game it to Path. ST - 9/6/2001 10:36AM PathAction.Reset(); return ; } bool Clip_Point (Vector3 *point, const AABoxClass &box) { Vector3 temp_point = *point; // // Clip the temporary point // temp_point.X = max (temp_point.X, box.Center.X - box.Extent.X); temp_point.Y = max (temp_point.Y, box.Center.Y - box.Extent.Y); temp_point.Z = max (temp_point.Z, box.Center.Z - box.Extent.Z); temp_point.X = min (temp_point.X, box.Center.X + box.Extent.X); temp_point.Y = min (temp_point.Y, box.Center.Y + box.Extent.Y); temp_point.Z = min (temp_point.Z, box.Center.Z + box.Extent.Z); // // Did the clip change the point? // bool retval = (point->X != temp_point.X); retval |= (point->Y != temp_point.Y); retval |= (point->Z != temp_point.Z); // // Pass the new point back to the caller // (*point) = temp_point; return retval; } virtual ActResult Traverse_Path ( void ) { ActResult act_result = ACTION_IN_PROGRESS; // // Let the pathfind system know which directions we can (and are) moving // Path->Set_Movement_Directions( PathClass::MOVE_X | PathClass::MOVE_Y ); // // Is the soldier only moving in Z? // SmartGameObj *obj = Action->Get_Action_Obj(); SoldierGameObj *soldier = obj->As_SoldierGameObj(); if( soldier != NULL ) { if ( soldier->Is_On_Ladder() ) { Path->Set_Movement_Directions( PathClass::MOVE_Z ); } } // // Get the object's current position // Vector3 curr_pos; obj->Get_Position( &curr_pos ); // // Determine where the object should go next // Vector3 next_pos; if( Path->Evaluate_Next_Point( curr_pos, next_pos ) ) { /*return act_result; }*/ // // Allow debug display of the path // Path->Display_Path( Display_Findpaths ); // // What should we be doing? // switch( Path->Get_State() ) { case PathClass::STATE_PATH_COMPLETE: case PathClass::STATE_TRAVERSING_PATH: // // Don't allow any movement if something is in our way // if ( soldier == NULL || soldier->Is_Soldier_Blocked( curr_pos ) == false ) { if ( Move_To_Absolute( next_pos ) ) { act_result = Arrived(); } } break; case PathClass::STATE_ACTION_REQUIRED: act_result = Perform_Action(); break; /*case PathClass::STATE_PATH_COMPLETE: act_result = Arrived(); break; */ } } else { // // Error, free the path object // Release_Path(); act_result = ACTION_DONE; Action->Done( ACTION_COMPLETE_MOVE_NO_PROGRESS_MADE ); } return act_result; } virtual ActResult Perform_Action ( void ) { SmartGameObj *obj = Action->Get_Action_Obj(); // // Determine what action we need to perform // PathClass::ACTION_ID action_id = Path->Get_Action_Type(); switch( action_id ) { case PathClass::ACTION_LADDER: { // // Initialize the action object that will be responsible for // controlling this action // PathAction.Initialize( PathActionClass::TYPE_LADDER, Path, obj, NULL ); PathAction.Set_Ladder_Index( Path->Get_Action_Mechanism() ); } break; case PathClass::ACTION_MECHANISM: { // // Lookup the physics object we will be interacting with // uint32 mechanism_id = Path->Get_Action_Mechanism(); StaticPhysClass *mechanism = PhysicsSceneClass::Get_Instance ()->Find_Static_Object( mechanism_id ); if (mechanism != NULL) { PathActionClass::TYPE type = PathActionClass::TYPE_UNKNOWN; // // Determine what type of mechanism this is // if( mechanism->As_ElevatorPhysClass () != NULL ) { type = PathActionClass::TYPE_ELEVATOR; } else if( mechanism->As_DoorPhysClass () != NULL ) { type = PathActionClass::TYPE_DOOR; } // // Initialize the action object that will be responsible for // controlling this action // PathAction.Initialize( type, Path, obj, mechanism ); } } break; case PathClass::ACTION_LEAP: case PathClass::ACTION_JUMP: { // // Get the point where we will be jumping to // Vector3 dest_pos; Path->Get_Action_Destination( dest_pos ); // // Instruct the physics object to jump to this point // HumanPhysClass *human_phys_obj = obj->Peek_Physical_Object()->As_HumanPhysClass (); if( human_phys_obj != NULL ) { human_phys_obj->Jump_To_Point( dest_pos ); } // // Initialize the action object that will be responsible for // controlling this action // PathAction.Initialize( PathActionClass::TYPE_JUMPING, Path, obj, NULL ); } break; } return ACTION_IN_PROGRESS; } virtual ActResult Process_Action ( void ) { // // Continue to let the action take place // PathAction.Process (); // // Does this action require us to move somewhere? // if( PathAction.Get_State () == PathActionClass::STATE_MOVING ) { Move_To_Absolute( PathAction.Get_Destination (), true ); } return ACTION_IN_PROGRESS; } virtual ActResult Solve_Path ( void ) { ActResult act_result = ACTION_IN_PROGRESS; // // Evaluate the path // PathSolveClass::STATE_DESC result = PathSolver->Get_State(); // // Has the path finished solving? // bool is_finished = true; switch( result ) { case PathSolveClass::THINKING: is_finished = false; break; case PathSolveClass::SOLVED_PATH: // // Allocate and initialize the path object // Prepare_Path(); Initialize_Vehicle_AI(); // // Configure the path object with information we learned // during the path solve // Path->Set_Traversal_Type( PathClass::SPLINE ); Path->Initialize( *PathSolver ); break; case PathSolveClass::ERROR_INVALID_START_POS: case PathSolveClass::ERROR_INVALID_DEST_POS: case PathSolveClass::ERROR_NO_PATH: break; } // // If we are finished solving the path, then release our // hold on the path solver. // if ( is_finished ) { PathMgrClass::Return_Path_Object( PathSolver ); PathSolver = NULL; } return act_result; } virtual ActResult Pathfind( void ) { ActResult act_result = ACTION_IN_PROGRESS; if( PathAction.Get_State () != PathActionClass::STATE_FINISHED ) { // // Keep performing the action until we've completed it // act_result = Process_Action(); } else { if( PathSolver != NULL ) { // // Try to find a path to the destination // act_result = Solve_Path(); } if( PathSolver == NULL) { if( Path != NULL ) { // // Move along the path we found // act_result = Traverse_Path(); } else if ( PathSolver == NULL ){ // // Unable to solve path... // // If this level doesn't have any pathfind data, then allow the unit // to beeline -- otherwise, kill the action... // if (PathfindClass::Get_Instance ()->Does_Pathfind_Data_Exist ()) { // // Kill the action // Action->Done( ACTION_COMPLETE_PATH_BAD_DEST ); act_result = ACTION_DONE; } else { act_result = Beeline (); } } } } return act_result; } virtual float Get_Distance_From_Goal (void) { float dist_to_goal = 0; // // Get the distance remaining from the path object (if possible) // if ( Path != NULL ) { dist_to_goal = Path->Get_Remaining_Path_Length(); } else { dist_to_goal = BaseActionCodeClass::Get_Distance_From_Goal(); } // // Return the length // return dist_to_goal; } void Update_Move_Location( void ) { ScriptableGameObj *target = Action->Get_Parameters().MoveObjectRef.Get_Ptr(); if ( target == NULL ) { // If the target is gone // Debug_Say(( "No Movement Target\n" )); // Move_Target_Gone(); // Action->Reset( Action->Get_Parameters().Priority ); // Stop current Command (this may get this deleted!) // Do we need to notify observers? // return ACTION_DONE; return; } // Action->Get_Parameters().MoveLocation = target->Get_Transform() * Action->Get_Parameters().MoveObjectOffset; target->Get_Position( &(Action->Get_Parameters().MoveLocation) ); if ( FirstCall == false ) { SmartGameObj * obj = Action->Get_Action_Obj(); SoldierGameObj * soldier = NULL; if ( target->As_PhysicalGameObj() != NULL ) { soldier = target->As_PhysicalGameObj()->As_SoldierGameObj(); } // Get the current positions of the action object and the target object Vector3 curr_pos; Vector3 target_pos; target->Get_Position( &target_pos ); obj->Get_Position( &curr_pos ); // Determine how far the target has moved since we last // did a pathfind. float target_move_dist = (target_pos - TargetPrevPos).Length (); float to_target_dist = (curr_pos - target_pos).Length (); if ( to_target_dist > 0 && (soldier == NULL || (soldier->Is_Airborne () == false && PathAction.Get_State () == PathActionClass::STATE_FINISHED))) { // Get the ratio of the distance the target has moved relative // to the distance we are from the target. float ratio = target_move_dist / to_target_dist; if ((target_move_dist > 2.0F && ratio > 0.1F)) { Initialize_Path(); } } } } virtual ActResult Act() { WWPROFILE( "Goto Act" ); SoldierGameObj * soldier = Action->Get_Action_Obj()->As_SoldierGameObj(); ScriptableGameObj * look_obj = Action->Get_Parameters().LookObjectRef.Get_Ptr(); if ( soldier != NULL && look_obj != NULL ) { Vector3 pos; look_obj->Get_Position( &pos ); pos.Z += 1; // Offset bullseye soldier->Update_Look_At( pos ); } if ( UseMoveObject ) { Update_Move_Location(); } if ( FirstCall ) { Initialize_Path(); FirstCall = false; } #if 0 // Make the soldier crouch (if necessary) if ( soldier && (soldier->Is_Crouched() != Action->Get_Parameters().MoveCrouched) ) { soldier->Set_Boolean_Control( ControlClass::BOOLEAN_CROUCH ); } #else // Make the soldier crouch (if necessary) if ( soldier ) { soldier->Set_Boolean_Control( ControlClass::BOOLEAN_CROUCH, Action->Get_Parameters().MoveCrouched ); } #endif ActResult result = ACTION_IN_PROGRESS; // Determine whether we should use pathfinding or // simply beeline to the destination if ( Action->Get_Parameters().MovePathfind ) { result = Pathfind(); } else { result = Beeline(); } return result; } private: PathClass * Path; PathSolveClass * PathSolver; PathActionClass PathAction; bool FirstCall; bool UseMoveObject; Vector3 TargetPrevPos; }; /* ** EnterExit Action Code Class */ class EnterExitActionCodeClass; SimplePersistFactoryClass _EnterExitActionCodeClassFactory; class EnterExitActionCodeClass : public BaseActionCodeClass { public: virtual const PersistFactoryClass & Get_Factory( void ) const { return _EnterExitActionCodeClassFactory; } /* ** Save / Load */ enum { CHUNKID_PARENT = 1031001312, }; virtual bool Save( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_PARENT ); BaseActionCodeClass::Save( csave ); csave.End_Chunk(); return true; } virtual bool Load( ChunkLoadClass & cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_PARENT: BaseActionCodeClass::Load( cload ); break; default: Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Chunk(); } return true; } virtual ActResult Act( void ) { SoldierGameObj * obj = Action->Get_Action_Obj()->As_SoldierGameObj(); WWASSERT( obj ); obj->Set_Boolean_Control( ControlClass::BOOLEAN_ACTION ); Action->Done( ACTION_COMPLETE_NORMAL ); return ACTION_DONE; } }; /* ** Dive Action Code Class */ class DiveActionCodeClass; SimplePersistFactoryClass _DiveActionCodeClassFactory; class DiveActionCodeClass : public BaseActionCodeClass { public: virtual const PersistFactoryClass & Get_Factory( void ) const { return _DiveActionCodeClassFactory; } /* ** Save / Load */ enum { CHUNKID_PARENT = 1031001310, }; virtual bool Save( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_PARENT ); BaseActionCodeClass::Save( csave ); csave.End_Chunk(); return true; } virtual bool Load( ChunkLoadClass & cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_PARENT: BaseActionCodeClass::Load( cload ); break; default: Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Chunk(); } return true; } virtual ActResult Act( void ) { SoldierGameObj * obj = Action->Get_Action_Obj()->As_SoldierGameObj(); WWASSERT( obj ); Vector3 rel_pos; Matrix3D::Inverse_Transform_Vector( obj->Get_Transform(), Action->Get_Parameters().MoveLocation, &rel_pos ); if ( WWMath::Fabs( rel_pos.X ) > WWMath::Fabs( rel_pos.Y ) ) { obj->Set_Boolean_Control( rel_pos.X > 0 ? ControlClass::BOOLEAN_DIVE_FORWARD : ControlClass::BOOLEAN_DIVE_BACKWARD ); } else { obj->Set_Boolean_Control( rel_pos.Y > 0 ? ControlClass::BOOLEAN_DIVE_LEFT : ControlClass::BOOLEAN_DIVE_RIGHT ); } #if 0 if ( !obj->Is_Crouched() ) { obj->Set_Boolean_Control( ControlClass::BOOLEAN_CROUCH ); } #else obj->Set_Boolean_Control( ControlClass::BOOLEAN_CROUCH ); #endif Action->Done( ACTION_COMPLETE_NORMAL ); return ACTION_DONE; } }; /* ** Attack - Location */ class AttackActionCodeClass; SimplePersistFactoryClass _AttackActionCodeClassFactory; class AttackActionCodeClass : public GotoActionCodeClass { public: virtual const PersistFactoryClass & Get_Factory( void ) const { return _AttackActionCodeClassFactory; } AttackActionCodeClass( void ) : UseAttackObject( false ), WanderPos(0,0,0), WanderTimer(0), HasArrived( false ), HoldPrimaryTriggerTimer( 0 ) { ErrorScale = FreeRandom.Get_Float( 0.75f, 1.25f ); } /* ** Save / Load */ enum { CHUNKID_PARENT = 1031001308, CHUNKID_VARIABLES, MICROCHUNKID_USE_ATTACK_OBJECT = 1, MICROCHUNKID_IS_BLOCKED = 2, MICROCHUNKID_CHECK_BLOCKED_TIMER = 3, }; virtual bool Save( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_PARENT ); GotoActionCodeClass::Save( csave ); csave.End_Chunk(); csave.Begin_Chunk( CHUNKID_VARIABLES ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_USE_ATTACK_OBJECT, UseAttackObject ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_IS_BLOCKED, IsBlocked ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_CHECK_BLOCKED_TIMER, CheckBlockedTimer ); csave.End_Chunk(); return true; } virtual bool Load( ChunkLoadClass & cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_PARENT: GotoActionCodeClass::Load( cload ); break; case CHUNKID_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { READ_MICRO_CHUNK( cload, MICROCHUNKID_USE_ATTACK_OBJECT, UseAttackObject ); READ_MICRO_CHUNK( cload, MICROCHUNKID_IS_BLOCKED, IsBlocked ); READ_MICRO_CHUNK( cload, MICROCHUNKID_CHECK_BLOCKED_TIMER, CheckBlockedTimer ); default: Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Micro_Chunk(); } break; default: Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Chunk(); } return true; } virtual void Init( ActionClass * action ) { GotoActionCodeClass::Init( action ); UseAttackObject = (Action->Get_Parameters().AttackObjectRef.Get_Ptr() != NULL); IsBlocked = action->Get_Parameters().AttackCheckBlocked; CheckBlockedTimer = 0; WanderPos = Vector3(0,0,0); } virtual ActResult Arrived() { if ( !HasArrived ) { // Debug_Say(( "Attack Arrived %d\n", HasArrived )); HasArrived = true; SoldierGameObj * obj = Action->Get_Action_Obj()->As_SoldierGameObj(); if ( obj != NULL ) { int observer_id = Action->Get_Parameters ().ObserverID; if ( observer_id != 0 ) { const GameObjObserverList & observer_list = obj->Get_Observers(); for( int index = 0; index < observer_list.Count(); index++ ) { if ( observer_list[ index ]->Get_ID() == observer_id ) { observer_list[ index ]->Custom( obj, CUSTOM_EVENT_ATTACK_ARRIVED, Action->Get_Parameters().ActionID, NULL ); } } } } } return ACTION_IN_PROGRESS; // Do nothing special when we arrive } // Attack the given absolute position, return nothing void Attack_Absolute( Vector3 & abs_pos ) { WWPROFILE( "Attack Absolute" ); IsAttacking = true; float range = Action->Get_Parameters().AttackRange; SmartGameObj * obj = Action->Get_Action_Obj(); Vector3 my_pos; obj->Get_Position( &my_pos ); // move the abs_pos around a little for AttackError Vector3 attack_pos = abs_pos; Matrix3D error; error.Obj_Look_At( my_pos, abs_pos, 0 ); float dist = (my_pos - abs_pos).Length(); float move = TimeManager::Get_Total_Seconds() * ErrorScale; float error_angle = WWMath::Fabs(Action->Get_Parameters().AttackError) + WWMath::Fabs(obj->Get_Weapon_Error()); #if 0 // Modify error angle based on difficulty switch ( CombatManager::Get_Difficulty_Level() ) { case 0: error_angle += 20; break; case 1: // no change break; case 2: error_angle -= 20; break; }; if ( error_angle < 0 ) { error_angle = 0; } #endif // If AttackErrorOverride, only use AttackError if ( Action->Get_Parameters().AttackErrorOverride ) { error_angle = WWMath::Fabs(Action->Get_Parameters().AttackError); } error.Rotate_Z( DEG_TO_RADF( WWMath::Sin( move * 1.7f ) * error_angle ) ); error.Rotate_Y( DEG_TO_RADF( WWMath::Cos( move ) * error_angle ) * 0.5f ); attack_pos = error * Vector3( dist,0,0 ); if ( obj->Set_Targeting( attack_pos ) == false && !Action->Get_Parameters().AttackForceFire ) { // we are not facing it yet, don't fire return; } WeaponClass * weapon = obj->Get_Weapon(); WWASSERT( weapon ); if ( Action->Get_Parameters().AttackCheckBlocked ) { WWPROFILE( "Check Blocked" ); CheckBlockedTimer -= TimeManager::Get_Frame_Seconds(); if ( CheckBlockedTimer <= 0 ) { CheckBlockedTimer = FreeRandom.Get_Float( 2, 3 ); // Re check every 2 - 3 sec // cast a ray from muzzle to target PhysicalGameObj * hit_obj = weapon->Cast_Weapon( abs_pos ); if ( hit_obj != NULL ) { if ( UseAttackObject ) { PhysicalGameObj * target = (PhysicalGameObj *)Action->Get_Parameters().AttackObjectRef.Get_Ptr(); IsBlocked = ( hit_obj != target ); if ( IsBlocked && target->As_SoldierGameObj() && target->As_SoldierGameObj()->Get_Vehicle() == hit_obj ) { IsBlocked = false; } } else { IsBlocked = !hit_obj->Is_Enemy( obj ); } } else { IsBlocked = true; } } } if ( IsBlocked && !Action->Get_Parameters().AttackForceFire ) { return; } // Update Weapons Dynamic Error if ( weapon ) { if ( Action->Get_Parameters().AttackCheckBlocked != false ) { range = MIN( range, weapon->Get_Range() ); // dont fire if not in range } } my_pos -= abs_pos; my_pos.Z = 0; if ( my_pos.Length() < range || Action->Get_Parameters().AttackForceFire ) { if ( Action->Get_Parameters().AttackPrimaryFire ) { obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_FIRE_PRIMARY ); // Keep the trigger down (helps obelisk) float rate = 0; if ( obj->Get_Weapon() ) { rate = obj->Get_Weapon()->Get_Primary_Fire_Rate(); } if ( rate != 0 ) { HoldPrimaryTriggerTimer = (1 / rate) * 0.75f; } } else { obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_FIRE_SECONDARY ); } } } virtual void Modify_Parameters( const SafeActionParamsStruct & parameters, bool modify_move, bool modify_attack ) { // // Copy the parameters that are safe to change in the middle of an action // if ( modify_attack ) { Action->Get_Parameters().AttackLocation = parameters.AttackLocation; Action->Get_Parameters().AttackObjectRef = parameters.AttackObjectRef; Action->Get_Parameters().AttackCrouched = parameters.AttackCrouched; Action->Get_Parameters().AttackRange = parameters.AttackRange; Action->Get_Parameters().AttackCheckBlocked = parameters.AttackCheckBlocked; Action->Get_Parameters().AttackError = parameters.AttackError; Action->Get_Parameters().AttackErrorOverride = parameters.AttackErrorOverride; Action->Get_Parameters().AttackPrimaryFire = parameters.AttackPrimaryFire; Action->Get_Parameters().AttackActive = parameters.AttackActive; Action->Get_Parameters().AttackWanderAllowed = parameters.AttackWanderAllowed; Action->Get_Parameters().AttackFaceTarget = parameters.AttackFaceTarget; Action->Get_Parameters().AttackForceFire = parameters.AttackForceFire; } if ( modify_move ) { Action->Get_Parameters().MoveObject = parameters.MoveObject; Action->Get_Parameters().MoveObjectOffset = parameters.MoveObjectOffset; Action->Get_Parameters().MoveSpeed = parameters.MoveSpeed; Action->Get_Parameters().MoveArrivedDistance = parameters.MoveArrivedDistance; Action->Get_Parameters().MoveBackup = parameters.MoveBackup; Action->Get_Parameters().MoveFollow = parameters.MoveFollow; Action->Get_Parameters().MoveCrouched = parameters.MoveCrouched; Action->Get_Parameters().ShutdownEngineOnArrival = parameters.ShutdownEngineOnArrival; Action->Get_Parameters().MovePathfind = parameters.MovePathfind; } // // Reset some of the internal flags // UseAttackObject = (parameters.AttackObjectRef.Get_Ptr() != NULL); IsBlocked = false; CheckBlockedTimer = 0; return ; } virtual ActResult Act( void ) { WWPROFILE( "Attack Act" ); // // Do the movement (if necessary) // if ( Action->Get_Parameters().MoveArrivedDistance < DONT_MOVE_ARRIVED_DIST ) { GotoActionCodeClass::Act(); } ActResult result = ACTION_IN_PROGRESS; // // Only "do" the attack if the attack state is currently active // if ( Action != NULL && Action->Get_Parameters().AttackActive ) { Vector3 set_target; result = Process_Attack(&set_target); if ( Action != NULL && Action->Get_Parameters().AttackFaceTarget) { Pilot.Set_Target (&set_target); } } else { Pilot.Set_Target (NULL); } return result; } ActResult Process_Attack( Vector3 * set_target ) { SmartGameObj * obj = Action->Get_Action_Obj(); if ( HoldPrimaryTriggerTimer > 0 ) { HoldPrimaryTriggerTimer -= TimeManager::Get_Frame_Seconds(); obj->Set_Boolean_Control( ControlClass::BOOLEAN_WEAPON_FIRE_PRIMARY ); } Vector3 target_pos = Action->Get_Parameters().AttackLocation; WeaponClass * weapon = obj->Get_Weapon(); if ( weapon == NULL ) { Action->Done( ACTION_COMPLETE_NORMAL ); // Stop current Command (this may get this deleted!) return ACTION_DONE; } if ( UseAttackObject ) { WWPROFILE( "Attack Object" ); // See if our target still exists PhysicalGameObj * target = (PhysicalGameObj *)Action->Get_Parameters().AttackObjectRef.Get_Ptr(); if ( target ) { if ( target->Get_Defense_Object()->Get_Health() <= 0 ) { // Debug_Say(( "Attack target dead\n" )); target = NULL; } } if ( target == NULL ) { // If the target is gone or dead static int count = 0; if (count++ < 20) { // Debug_Say(( "No Attack Target\n" )); } Action->Done( ACTION_COMPLETE_NORMAL ); // Stop current Command (this may get this deleted!) return ACTION_DONE; } // Goto our target's location target_pos = target->Get_Bullseye_Position(); if ( target->As_SoldierGameObj() != NULL && target->As_SoldierGameObj()->Peek_Human_Phys() != NULL ) { Vector3 vel; target->As_SoldierGameObj()->Peek_Human_Phys()->Get_Velocity( &vel ); vel *= TimeManager::Get_Frame_Seconds(); target_pos += vel; } } // If not moving, use attack crouch if ( !IsMoving ) { SoldierGameObj * soldier = Action->Get_Action_Obj()->As_SoldierGameObj(); if ( soldier != NULL ) { #if 0 if ( soldier->Is_Crouched() != Action->Get_Parameters().AttackCrouched ) { soldier->Set_Boolean_Control( ControlClass::BOOLEAN_CROUCH ); } #else soldier->Set_Boolean_Control( ControlClass::BOOLEAN_CROUCH, Action->Get_Parameters().AttackCrouched ); #endif } } Attack_Absolute( target_pos ); #if 0 // Elie Wander! // Always wander if attacking! WanderTimer -= TimeManager::Get_Frame_Seconds(); SoldierGameObj * soldier = Action->Get_Action_Obj()->As_SoldierGameObj(); if ( !IsMoving && WanderTimer <= 0 && soldier != NULL && Action->Get_Parameters().AttackWanderAllowed ) { WWPROFILE( "Wander" ); Vector3 rel_pos; Matrix3D::Inverse_Transform_Vector( obj->Get_Transform(), WanderPos, &rel_pos ); // Debug_Say(( "Wander Pos %f %f %f\n", WanderPos.X, WanderPos.Y, WanderPos.Z )); // Debug_Say(( "Rel Pos %f %f %f\n", rel_pos.X, rel_pos.Y, rel_pos.Z )); // If he is near the wander position, or very far from it, pick a new wander pos float dist = rel_pos.Length(); if ( dist < 0.5 || dist > 8 ) { WanderTimer = FreeRandom.Get_Float( 0.4f, 1.2f ); // random pause // If near the move loc, wander away, else wander back Vector3 dif = WanderPos - Action->Get_Parameters().MoveLocation; WanderPos = Action->Get_Parameters().MoveLocation; if ( dif.Length() < 2 ) { #if 1 PathfindClass *pathfind = PathfindClass::Get_Instance(); // Lookup a safe random position to walk to pathfind->Find_Random_Spot( Action->Get_Parameters().MoveLocation, 5, &WanderPos ); #else float angle = FreeRandom.Get_Float( 0, DEG_TO_RADF( 360.0f ) ); Vector3 move( ::sinf(angle), ::cosf(angle), 0 ); // Debug_Say(( " Move to %f %f %f\n", move.X, move.Y, move.Z )); WanderPos += move * FreeRandom.Get_Float( 3, 5 ); #endif soldier->Look_At( WanderPos + Vector3( 0,0,1 ), FreeRandom.Get_Float( 0.2f, 0.6f ) ); } } Human_Move_To_Relative( rel_pos ); } #endif *set_target = target_pos; return ACTION_IN_PROGRESS; } bool UseAttackObject; bool IsBlocked; float CheckBlockedTimer; bool HasArrived; float ErrorScale; float HoldPrimaryTriggerTimer; Vector3 WanderPos; float WanderTimer; }; /* ** Face - Location */ class FaceLocationActionCodeClass; SimplePersistFactoryClass _FaceLocationActionCodeClassFactory; class FaceLocationActionCodeClass : public BaseActionCodeClass { public: virtual const PersistFactoryClass & Get_Factory( void ) const { return _FaceLocationActionCodeClassFactory; } FaceLocationActionCodeClass( void ) : EndTime( 0 ) { } /* ** Save / Load */ enum { CHUNKID_PARENT = 1031001305, CHUNKID_VARIABLES, MICROCHUNKID_END_TIME = 1, }; virtual bool Save( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_PARENT ); BaseActionCodeClass::Save( csave ); csave.End_Chunk(); csave.Begin_Chunk( CHUNKID_VARIABLES ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_END_TIME, EndTime ); csave.End_Chunk(); return true; } virtual bool Load( ChunkLoadClass & cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_PARENT: BaseActionCodeClass::Load( cload ); break; case CHUNKID_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { READ_MICRO_CHUNK( cload, MICROCHUNKID_END_TIME, EndTime ); default: Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Micro_Chunk(); } break; default: Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Chunk(); } return true; } virtual void Init( ActionClass * action ) { BaseActionCodeClass::Init( action ); SoldierGameObj *soldier_obj = Action->Get_Action_Obj()->As_SoldierGameObj(); if ( soldier_obj && Action->Get_Parameters().LookDuration != 0 ) { soldier_obj->Look_At( Action->Get_Parameters().LookLocation, Action->Get_Parameters().LookDuration ); } EndTime = TimeManager::Get_Seconds() + MAX(action->Get_Parameters().FaceDuration, Action->Get_Parameters().LookDuration); } virtual ActResult Act( void ) { BaseActionCodeClass::Act(); // Look at the moving object SoldierGameObj * soldier = Action->Get_Action_Obj()->As_SoldierGameObj(); ScriptableGameObj * look_obj = Action->Get_Parameters().LookObjectRef.Get_Ptr(); if ( soldier != NULL && look_obj != NULL ) { Vector3 pos; look_obj->Get_Position( &pos ); pos.Z += 1; // Offset bullseye soldier->Update_Look_At( pos ); } if ( Action->Get_Parameters().FaceDuration != 0 ) { // // Make the object target (face for humans) the given location // SmartGameObj * obj = Action->Get_Action_Obj(); Vector3 face_location = Action->Get_Parameters().FaceLocation; obj->Set_Targeting( face_location, false ); } // // Have we timed out yet? // ActResult result = ACTION_IN_PROGRESS; if ( EndTime < TimeManager::Get_Seconds() ) { // // Stop this action. Note: The following call may cause the // object to be deleted... // Action->Done( ACTION_COMPLETE_NORMAL ); result = ACTION_DONE; } return result; } float EndTime; }; /* ** Have a conversation */ class ConversationActionCodeClass; SimplePersistFactoryClass _ConversationActionCodeClassFactory; class ConversationActionCodeClass : public GotoActionCodeClass { public: virtual const PersistFactoryClass & Get_Factory( void ) const { return _ConversationActionCodeClassFactory; } enum { STATE_WALKING_TO_START_POS = 0, STATE_HAVING_CONVERSATION, STATE_FINISHED }; ConversationActionCodeClass( void ) : Conversation( NULL ), State( STATE_WALKING_TO_START_POS ), OriginalPos( 0, 0, 0 ) { } ~ConversationActionCodeClass( void ) { return ; } /* ** Save / Load */ enum { CHUNKID_PARENT = 1031001258, CHUNKID_VARIABLES, MICROCHUNKID_STATE = 1, MICROCHUNKID_ORIGINAL_POS = 2, MICROCHUNKID_CONVERSATION_PTR = 3, }; virtual bool Save( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_PARENT ); GotoActionCodeClass::Save( csave ); csave.End_Chunk(); csave.Begin_Chunk( CHUNKID_VARIABLES ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_STATE, State ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ORIGINAL_POS, OriginalPos ); // // Only save the conversation ptr if it exists in the manager // if (Conversation != NULL) { ConversationClass *conv = ConversationMgrClass::Find_Conversation (Conversation->Get_ID ()); if (conv != NULL) { // // Save the conversation pointer // WRITE_MICRO_CHUNK( csave, MICROCHUNKID_CONVERSATION_PTR, Conversation ); REF_PTR_RELEASE( conv ); } } csave.End_Chunk(); return true; } virtual bool Load( ChunkLoadClass & cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_PARENT: GotoActionCodeClass::Load( cload ); break; case CHUNKID_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { READ_MICRO_CHUNK( cload, MICROCHUNKID_STATE, State ); READ_MICRO_CHUNK( cload, MICROCHUNKID_ORIGINAL_POS, OriginalPos ); case MICROCHUNKID_CONVERSATION_PTR: LOAD_MICRO_CHUNK( cload, Conversation ); // // Fixup the pointer // if (Conversation != NULL) { REQUEST_REF_COUNTED_POINTER_REMAP( (RefCountClass **)&Conversation ); } break; default: Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Micro_Chunk(); } break; default: Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Chunk(); } return true; } virtual void Begin_Hibernation( void ) { // // Stop this action. Note: The following call may cause the // object to be deleted... // if ( Action != NULL ) { Action->Done( ACTION_COMPLETE_NORMAL ); } return ; } virtual void Init( ActionClass * action ) { State = STATE_WALKING_TO_START_POS; REF_PTR_RELEASE( Conversation ); OriginalPos.Set( 0, 0, 0 ); SmartGameObj *obj = action->Get_Action_Obj(); if ( obj != NULL ) { SoldierGameObj *soldier = obj->As_SoldierGameObj (); if( soldier != NULL ) { // // Should we join an already active conversation or should // we start our own conversation? // if ( action->Get_Parameters().ActiveConversationID != 0 ) { Conversation = ConversationMgrClass::Find_Active_Conversation( action->Get_Parameters().ActiveConversationID ); } else { // // Start a new conversation // Conversation = ConversationMgrClass::Start_Conversation( soldier, action->Get_Parameters().SafeConversationName ); } // // By default our new position is our current position // soldier->Get_Position( &OriginalPos ); if ( Conversation != NULL ) { OratorClass *orator_info = Conversation->Get_Orator_Information( soldier ); // // Determine if the soldier needs to move to a new location // if (orator_info->Get_Flag (OratorClass::FLAG_DONT_MOVE) == false) { // // Get the soldier's expected position // Vector3 new_pos = OriginalPos; Conversation->Get_Orator_Location( soldier, &new_pos ); // // Set our movement location // action->Get_Parameters().MoveLocation = new_pos; action->Get_Parameters().MoveSpeed = 0.15F; action->Get_Parameters().MoveArrivedDistance = 0.25F; } else { State = STATE_HAVING_CONVERSATION; } } } } GotoActionCodeClass::Init( action ); return ; } virtual void Shutdown( void ) { // // Release our hold on the conversation (but don't terminate it) // if ( Conversation != NULL ) { //Conversation->Stop_Conversation (); REF_PTR_RELEASE( Conversation ); } GotoActionCodeClass::Shutdown(); return ; } virtual ActResult Act( void ) { SmartGameObj *obj = Action->Get_Action_Obj(); if ( obj != NULL ) { SoldierGameObj *soldier = obj->As_SoldierGameObj (); if( soldier != NULL ) { if( State == STATE_WALKING_TO_START_POS ) { GotoActionCodeClass::Act(); } else if ( State == STATE_HAVING_CONVERSATION ) { // // Check to see if the conversation has finished yet // if ( Conversation == NULL || Conversation->Is_Finished () ) { State ++; // // Tell this soldier to return to his original position // ActionParamsStruct parameters; parameters.Priority = Action->Get_Parameters().Priority; parameters.ObserverID = Action->Get_Parameters().ObserverID; parameters.MoveLocation = OriginalPos; parameters.MoveSpeed = 0.15F; parameters.MoveArrivedDistance = 1.0F; soldier->Get_Action()->Goto (parameters); } } } } // // Have we timed out yet? // ActResult result = ACTION_IN_PROGRESS; if ( Conversation == NULL || Conversation->Is_Finished () ) { // // Stop this action. Note: The following call may cause the // object to be deleted... // if ( Action != NULL ) { Action->Done( ACTION_COMPLETE_NORMAL ); } result = ACTION_DONE; } return result; } virtual ActResult Arrived (void) { if ( Conversation != NULL && State == STATE_WALKING_TO_START_POS ) { // // Let the conversation object know this soldier has arrived // SmartGameObj *obj = Action->Get_Action_Obj(); if ( obj != NULL ) { SoldierGameObj *soldier = obj->As_SoldierGameObj(); if( soldier != NULL ) { OratorClass *orator_info = Conversation->Get_Orator_Information( soldier ); if ( orator_info != NULL ) { orator_info->Set_Flag( OratorClass::FLAG_TEMP_DONT_FACE, false ); } Conversation->Set_Orator_Arrived( soldier, true ); } } State ++; } return ACTION_IN_PROGRESS; } private: ActiveConversationClass * Conversation; int State; Vector3 OriginalPos; }; /* ** Dock vehicle (backup) into a bay */ class DockActionCodeClass; SimplePersistFactoryClass _DockActionCodeClassFactory; class DockActionCodeClass : public GotoActionCodeClass { public: virtual const PersistFactoryClass & Get_Factory( void ) const { return _DockActionCodeClassFactory; } enum { STATE_DRIVING_TO_ENTRANCE = 0, STATE_DRIVING_TO_SIDE, STATE_BACKING_INTO_BAY }; DockActionCodeClass( void ) : State( STATE_DRIVING_TO_ENTRANCE ) { } ~DockActionCodeClass( void ) { return ; } /* ** Save / Load */ enum { CHUNKID_PARENT = 1031001258, CHUNKID_VARIABLES, MICROCHUNKID_STATE = 1, }; virtual bool Save( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_PARENT ); GotoActionCodeClass::Save( csave ); csave.End_Chunk(); csave.Begin_Chunk( CHUNKID_VARIABLES ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_STATE, State ); csave.End_Chunk(); return true; } virtual bool Load( ChunkLoadClass & cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_PARENT: GotoActionCodeClass::Load( cload ); break; case CHUNKID_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { READ_MICRO_CHUNK( cload, MICROCHUNKID_STATE, State ); default: Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Micro_Chunk(); } break; default: Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Chunk(); } return true; } virtual void Init( ActionClass * action ) { State = STATE_DRIVING_TO_ENTRANCE; // // Skip the "driving to side" state if we're a tracked vehicle which // can turn in place. // SmartGameObj *obj = action->Get_Action_Obj(); VehicleGameObj *vehicle = obj->As_VehicleGameObj(); if ( vehicle != NULL && vehicle->Get_Turn_Radius() == 0 ) { State = STATE_DRIVING_TO_SIDE; } // // Setup the action structure so the vehicle will drive // to the entrance of the docking bay // action->Get_Parameters().MoveLocation = action->Get_Parameters().DockEntrance; action->Get_Parameters().MovePathfind = true; action->Get_Parameters().MoveBackup = false; OriginalMoveSpeed = action->Get_Parameters().MoveSpeed; // // Ensure that the arrived distance is valid // if (action->Get_Parameters().MoveArrivedDistance == DONT_MOVE_ARRIVED_DIST) { action->Get_Parameters().MoveArrivedDistance = 1.0F; } GotoActionCodeClass::Init( action ); return ; } virtual ActResult Arrived (void) { SafeActionParamsStruct ¶ms = Action->Get_Parameters(); ActResult result = ACTION_IN_PROGRESS; // // Determine which action we just finished // if ( State == STATE_DRIVING_TO_ENTRANCE ) { // // Terminate the action, we have arrived successfully // SmartGameObj *obj = Action->Get_Action_Obj(); VehicleGameObj *vehicle = obj->As_VehicleGameObj(); // // Get the vector which points the direction the vehicle is // currently facing // const Matrix3D &tm = obj->Get_Transform(); Vector3 facing_vector; tm.Get_X_Vector( &facing_vector ); // // Build a vector which points the way into the docking bay // Vector3 entrance_vector = params.DockEntrance - params.DockLocation; entrance_vector.Normalize(); // // Lookup the vehicle's turn radius // float turn_radius = 2.0F; if ( vehicle != NULL ) { turn_radius = vehicle->Get_Turn_Radius(); } if (turn_radius == 0) { // // Instruct the object to drive to the side of the docking bay // ActionClass *action = Action; action->Get_Parameters().MoveLocation = params.DockEntrance; action->Get_Parameters().MovePathfind = false; action->Get_Parameters().MoveBackup = true; action->Get_Parameters().MoveSpeed = OriginalMoveSpeed * 0.25F; GotoActionCodeClass::Init( action ); } else { WaypathClass *test_waypath = new WaypathClass; Vector3 side_pos = params.DockLocation + (params.DockEntrance - params.DockLocation) * 0.8F; // // Determine which side we should drive to in order to be oriented // correctly for backing up // float dot_product = Vector3::Dot_Product (facing_vector, entrance_vector); if (dot_product > 0) { Vector3 vector1 = entrance_vector; Vector3 vector2 = Vector3 (0, 0, 1.0F); Vector3 side_vector = Vector3::Cross_Product (vector1, vector2); test_waypath->Add_Point (side_pos + (side_vector * (turn_radius * 0.5F))); side_pos += (side_vector * (turn_radius * 1.4F)); test_waypath->Add_Point (side_pos); } else { Vector3 vector1 = entrance_vector; Vector3 vector2 = Vector3 (0, 0, 1.0F); Vector3 side_vector = Vector3::Cross_Product (vector1, vector2); test_waypath->Add_Point (side_pos - (side_vector * (turn_radius * 0.5F))); side_pos -= (side_vector * (turn_radius * 1.4F)); test_waypath->Add_Point (side_pos); } // // Instruct the object to drive to the side of the docking bay // ActionClass *action = Action; action->Get_Parameters().MoveLocation = side_pos; action->Get_Parameters().MovePathfind = true; action->Get_Parameters().MoveBackup = false; action->Get_Parameters().MoveSpeed = OriginalMoveSpeed * 0.25F; GotoActionCodeClass::Init( test_waypath ); // // Free our temporary waypath // REF_PTR_RELEASE( test_waypath ); } // // Move onto the next state // State ++; } else if ( State == STATE_DRIVING_TO_SIDE ) { WaypathClass *test_waypath = new WaypathClass; SmartGameObj *obj = Action->Get_Action_Obj(); VehicleGameObj *vehicle = obj->As_VehicleGameObj(); float turn_radius = 2.0F; if ( vehicle != NULL ) { turn_radius = vehicle->Get_Turn_Radius(); } Vector3 entrance_vector = params.DockEntrance - params.DockLocation; entrance_vector.Normalize(); Vector3 vector1 = entrance_vector; Vector3 vector2 = Vector3 (0, 0, 1.0F); Vector3 side_vector = Vector3::Cross_Product (vector1, vector2); test_waypath->Add_Point (params.DockLocation + (params.DockEntrance - params.DockLocation) * 0.7F - (side_vector * turn_radius * 0.6F)); test_waypath->Add_Point (params.DockLocation + (params.DockEntrance - params.DockLocation) * 0.3F); test_waypath->Add_Point (params.DockLocation); // // Instruct the object to back into the docking bay // ActionClass *action = Action; action->Get_Parameters().MoveLocation = params.DockLocation; action->Get_Parameters().MovePathfind = true; action->Get_Parameters().MoveBackup = true; action->Get_Parameters().MoveSpeed = OriginalMoveSpeed * 0.1F; GotoActionCodeClass::Init( test_waypath ); // // Move onto the next state // State ++; // // Free our temporary waypath // REF_PTR_RELEASE( test_waypath ); // // Let the observer know that the vehicle will be // backing into the bay // int observer_id = Action->Get_Parameters ().ObserverID; if ( observer_id != 0 ) { const GameObjObserverList & observer_list = obj->Get_Observers(); for( int index = 0; index < observer_list.Count(); index++ ) { if ( observer_list[ index ]->Get_ID() == observer_id ) { observer_list[ index ]->Custom( obj, CUSTOM_EVENT_DOCK_BACKING_IN, 0, NULL ); } } } } else if ( State == STATE_BACKING_INTO_BAY ) { // // Terminate the action, we have arrived successfully // State ++; Action->Done( ACTION_COMPLETE_NORMAL ); result = ACTION_DONE; } return result; } private: int State; float OriginalMoveSpeed; }; /* ** */ SafeActionParamsStruct & SafeActionParamsStruct::operator = (const ActionParamsStruct & src) { Priority = src.Priority; ActionID = src.ActionID; ObserverID = src.ObserverID; LookLocation = src.LookLocation; LookObjectRef = src.LookObject; LookDuration = src.LookDuration; MoveLocation = src.MoveLocation; MoveObjectRef = src.MoveObject; MoveObjectOffset = src.MoveObjectOffset; MoveSpeed = src.MoveSpeed; MoveArrivedDistance = src.MoveArrivedDistance; MoveBackup = src.MoveBackup; MoveFollow = src.MoveFollow; MoveCrouched = src.MoveCrouched; ShutdownEngineOnArrival = src.ShutdownEngineOnArrival; MovePathfind = src.MovePathfind; AttackRange = src.AttackRange; AttackError = src.AttackError; AttackErrorOverride = src.AttackErrorOverride; AttackPrimaryFire = src.AttackPrimaryFire; AttackCrouched = src.AttackCrouched; AttackObjectRef = src.AttackObject; AttackLocation = src.AttackLocation; AttackCheckBlocked = src.AttackCheckBlocked; AttackActive = src.AttackActive; AttackWanderAllowed = src.AttackWanderAllowed; AttackFaceTarget = src.AttackFaceTarget; AttackForceFire = src.AttackForceFire; WaypathID = src.WaypathID; WaypointStartID = src.WaypointStartID; WaypointEndID = src.WaypointEndID; WaypathSplined = src.WaypathSplined; AnimationLooping = src.AnimationLooping; ForceFacing = src.ForceFacing; IgnoreFacing = src.IgnoreFacing; FaceLocation = src.FaceLocation; FaceDuration = src.FaceDuration; SafeConversationName = src.ConversationName; ActiveConversationID = src.ActiveConversationID; SafeAnimationName = src.AnimationName; AIState = src.AIState; DockLocation = src.DockLocation; DockEntrance = src.DockEntrance; return *this; } enum { CHUNKID_VARIABLES = 712001855, CHUNKID_MOVE_OBJECT, CHUNKID_ATTACK_OBJECT, CHUNKID_LOOK_OBJECT, MICROCHUNKID_PRIORITY = 1, MICROCHUNKID_ACTION_ID, MICROCHUNKID_OBSERVER_ID, MICROCHUNKID_MOVE_LOCATION, MICROCHUNKID_MOVE_OBJECT_OFFSET, MICROCHUNKID_MOVE_SPEED, MICROCHUNKID_MOVE_ARRIVED_DISTANCE, MICROCHUNKID_MOVE_BACKUP, MICROCHUNKID_MOVE_FOLLOW, MICROCHUNKID_MOVE_CROUCHED, MICROCHUNKID_MOVE_PATHFIND, MICROCHUNKID_ATTACK_RANGE, MICROCHUNKID_ATTACK_ERROR, MICROCHUNKID_ATTACK_PRIMARY_FIRE, MICROCHUNKID_ATTACK_CROUCHED, MICROCHUNKID_ATTACK_LOCATION, MICROCHUNKID_WAYPATH_ID, MICROCHUNKID_WAYPOINT_START_ID, MICROCHUNKID_WAYPOINT_END_ID, MICROCHUNKID_WAYPATH_SPLINED, MICROCHUNKID_ANIMATION_NAME, MICROCHUNKID_ANIMATION_LOOPING, MICROCHUNKID_FACE_LOCATION, MICROCHUNKID_FACE_DURATION, MICROCHUNKID_CONVERSATION_NAME, MICROCHUNKID_ACTIVE_CONVERSATION_ID, MICROCHUNKID_LOOK_LOCATION, MICROCHUNKID_LOOK_DURATION, MICROCHUNKID_DISABLE_HIBERNATION, MICROCHUNKID_ATTACK_CHECK_BLOCKED, MICROCHUNKID_ATTACK_ACTIVE, MICROCHUNKID_ATTACK_WANDER_ALLOWED, MICROCHUNKID_FORCE_FACING, MICROCHUNKID_ATTACK_ERROR_OVERRIDE, MICROCHUNKID_ATTACK_FACE_TARGET, MICROCHUNKID_IGNORE_FACING, MICROCHUNKID_ATTACK_FORCE_FIRE, MICROCHUNKID_SHUTDOWN_ENGINE_ON_ARRIVAL, }; bool SafeActionParamsStruct::Save( ChunkSaveClass & csave ) { csave.Begin_Chunk( CHUNKID_VARIABLES ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_PRIORITY, Priority ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ACTION_ID, ActionID ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_OBSERVER_ID, ObserverID ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_LOOK_LOCATION, LookLocation ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_LOOK_DURATION, LookDuration ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MOVE_LOCATION, MoveLocation ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MOVE_OBJECT_OFFSET, MoveObjectOffset ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MOVE_SPEED, MoveSpeed ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MOVE_ARRIVED_DISTANCE, MoveArrivedDistance ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MOVE_BACKUP, MoveBackup ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MOVE_FOLLOW, MoveFollow ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MOVE_CROUCHED, MoveCrouched ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_SHUTDOWN_ENGINE_ON_ARRIVAL, ShutdownEngineOnArrival ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MOVE_PATHFIND, MovePathfind ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_RANGE, AttackRange ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_ERROR, AttackError ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_ERROR_OVERRIDE, AttackErrorOverride ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_PRIMARY_FIRE, AttackPrimaryFire ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_CROUCHED, AttackCrouched ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_LOCATION, AttackLocation ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_ACTIVE, AttackActive ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_CHECK_BLOCKED, AttackCheckBlocked ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_WANDER_ALLOWED, AttackWanderAllowed ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_FACE_TARGET, AttackFaceTarget ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ATTACK_FORCE_FIRE, AttackForceFire ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_WAYPATH_ID, WaypathID ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_WAYPOINT_START_ID, WaypointStartID ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_WAYPOINT_END_ID, WaypointEndID ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_WAYPATH_SPLINED, WaypathSplined ); WRITE_MICRO_CHUNK_WWSTRING( csave, MICROCHUNKID_ANIMATION_NAME,SafeAnimationName ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ANIMATION_LOOPING, AnimationLooping ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_FORCE_FACING, ForceFacing ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_IGNORE_FACING, IgnoreFacing ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_FACE_LOCATION, FaceLocation ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_FACE_DURATION, FaceDuration ); WRITE_MICRO_CHUNK_WWSTRING( csave, MICROCHUNKID_CONVERSATION_NAME, SafeConversationName ); WRITE_MICRO_CHUNK( csave, MICROCHUNKID_ACTIVE_CONVERSATION_ID, ActiveConversationID ); csave.End_Chunk(); if ( MoveObjectRef != NULL ) { csave.Begin_Chunk( CHUNKID_MOVE_OBJECT ); MoveObjectRef.Save( csave ); csave.End_Chunk(); } if ( AttackObjectRef != NULL ) { csave.Begin_Chunk( CHUNKID_ATTACK_OBJECT ); AttackObjectRef.Save( csave ); csave.End_Chunk(); } if ( LookObjectRef != NULL ) { csave.Begin_Chunk( CHUNKID_LOOK_OBJECT ); LookObjectRef.Save( csave ); csave.End_Chunk(); } return true; } bool SafeActionParamsStruct::Load( ChunkLoadClass &cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { READ_MICRO_CHUNK( cload, MICROCHUNKID_PRIORITY, Priority ); READ_MICRO_CHUNK( cload, MICROCHUNKID_ACTION_ID, ActionID ); READ_MICRO_CHUNK( cload, MICROCHUNKID_OBSERVER_ID, ObserverID ); READ_MICRO_CHUNK( cload, MICROCHUNKID_LOOK_LOCATION, LookLocation ); READ_MICRO_CHUNK( cload, MICROCHUNKID_LOOK_DURATION, LookDuration ); READ_MICRO_CHUNK( cload, MICROCHUNKID_MOVE_LOCATION, MoveLocation ); READ_MICRO_CHUNK( cload, MICROCHUNKID_MOVE_OBJECT_OFFSET, MoveObjectOffset ); READ_MICRO_CHUNK( cload, MICROCHUNKID_MOVE_SPEED, MoveSpeed ); READ_MICRO_CHUNK( cload, MICROCHUNKID_MOVE_ARRIVED_DISTANCE,MoveArrivedDistance ); READ_MICRO_CHUNK( cload, MICROCHUNKID_MOVE_BACKUP, MoveBackup ); READ_MICRO_CHUNK( cload, MICROCHUNKID_MOVE_FOLLOW, MoveFollow ); READ_MICRO_CHUNK( cload, MICROCHUNKID_MOVE_CROUCHED, MoveCrouched ); READ_MICRO_CHUNK( cload, MICROCHUNKID_SHUTDOWN_ENGINE_ON_ARRIVAL, ShutdownEngineOnArrival ); READ_MICRO_CHUNK( cload, MICROCHUNKID_MOVE_PATHFIND, MovePathfind ); READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_RANGE, AttackRange ); READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_ERROR, AttackError ); READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_ERROR_OVERRIDE,AttackErrorOverride ); READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_PRIMARY_FIRE, AttackPrimaryFire ); READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_CROUCHED, AttackCrouched ); READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_LOCATION, AttackLocation ); READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_ACTIVE, AttackActive ); READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_CHECK_BLOCKED, AttackCheckBlocked ); READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_WANDER_ALLOWED, AttackWanderAllowed ); READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_FACE_TARGET, AttackFaceTarget ); READ_MICRO_CHUNK( cload, MICROCHUNKID_ATTACK_FORCE_FIRE, AttackForceFire ); READ_MICRO_CHUNK( cload, MICROCHUNKID_WAYPATH_ID, WaypathID ); READ_MICRO_CHUNK( cload, MICROCHUNKID_WAYPOINT_START_ID, WaypointStartID ); READ_MICRO_CHUNK( cload, MICROCHUNKID_WAYPOINT_END_ID, WaypointEndID ); READ_MICRO_CHUNK( cload, MICROCHUNKID_WAYPATH_SPLINED, WaypathSplined ); READ_MICRO_CHUNK_WWSTRING( cload, MICROCHUNKID_ANIMATION_NAME,SafeAnimationName ); READ_MICRO_CHUNK( cload, MICROCHUNKID_ANIMATION_LOOPING, AnimationLooping ); READ_MICRO_CHUNK( cload, MICROCHUNKID_FORCE_FACING, ForceFacing ); READ_MICRO_CHUNK( cload, MICROCHUNKID_IGNORE_FACING, IgnoreFacing ); READ_MICRO_CHUNK( cload, MICROCHUNKID_FACE_LOCATION, FaceLocation ); READ_MICRO_CHUNK( cload, MICROCHUNKID_FACE_DURATION, FaceDuration ); READ_MICRO_CHUNK_WWSTRING( cload, MICROCHUNKID_CONVERSATION_NAME, SafeConversationName ); READ_MICRO_CHUNK( cload, MICROCHUNKID_ACTIVE_CONVERSATION_ID, ActiveConversationID ); default: Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Micro_Chunk(); } break; case CHUNKID_MOVE_OBJECT: MoveObjectRef.Load( cload ); break; case CHUNKID_ATTACK_OBJECT: AttackObjectRef.Load( cload ); break; case CHUNKID_LOOK_OBJECT: LookObjectRef.Load( cload ); break; default: Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__)); break; } cload.Close_Chunk(); } return true; } /* ** */ ActionClass::ActionClass( SmartGameObj *obj ) : ActionCode( NULL ), ActCount( 0 ), IsPaused( false ) { WWASSERT( obj != NULL ); ActionObj = obj; } ActionClass::~ActionClass( void ) { Set_Action_Code( NULL ); Delete_Action_Code(); } void ActionClass::Set_Action_Code( ActionCodeClass * code ) { if ( ActionCode != NULL ) { ActionCode->Shutdown(); Register_Action_Code_Deletion( ActionCode ); } ActionCode = code; if ( ActionCode ) { _ActionCodeChanges++; ActionCode->Init( this ); } } void ActionClass::Done( int reason ) { Parameters.Priority = 0; // reset if ( ActionCode != NULL ) { Set_Action_Code( NULL ); Notify_Completed( Parameters.ObserverID, Parameters.ActionID, reason ); // Notify the observer } } bool ActionClass::Is_Acting( void ) { return ( ActionCode != NULL ); } bool ActionClass::Is_Animating( void ) { if ( ActionCode != NULL ) { return ActionCode->Is_Animating(); } return false; } bool ActionClass::Modify( const ActionParamsStruct & parameters, bool modify_move, bool modify_attack ) { bool retval = false; if ( ActionCode != NULL ) { SafeActionParamsStruct safe_params; safe_params = parameters; ActionCode->Modify_Parameters( safe_params, modify_move, modify_attack ); retval = true; } return retval; } bool ActionClass::Reset( float priority ) { if ( priority >= Parameters.Priority ) { Done( ACTION_COMPLETE_LOW_PRIORITY ); return true; } return false; } void ActionClass::Notify_Completed( int observer_id, int action_id, int reason ) { if ( observer_id != 0 ) { // Find the observer and notify SmartGameObj * obj = Get_Action_Obj(); const GameObjObserverList & observer_list = obj->Get_Observers(); for( int index = 0; index < observer_list.Count(); index++ ) { if ( observer_list[ index ]->Get_ID() == observer_id ) { observer_list[ index ]->Action_Complete( obj, action_id, (ActionCompleteReason)reason ); } } } } bool ActionClass::Request_Action( ActionCodeClass * action, const ActionParamsStruct & parameters ) { if ( parameters.Priority >= Parameters.Priority ) { bool old_action_completed = false; int old_observer_id = 0; int old_action_id = 0; if ( ActionCode != NULL ) { old_action_completed = true; old_observer_id = Parameters.ObserverID; old_action_id = Parameters.ActionID; } Parameters = parameters; Set_Action_Code( action ); if ( old_action_completed ) { Notify_Completed( old_observer_id, old_action_id, ACTION_COMPLETE_LOW_PRIORITY ); } return true; } else { Notify_Completed( parameters.ObserverID, parameters.ActionID, ACTION_COMPLETE_LOW_PRIORITY ); if ( action != NULL ) { delete action; } return false; } } bool ActionClass::Follow_Input( const ActionParamsStruct & parameters ) { return Request_Action( new FollowInputActionCodeClass(), parameters ); } bool ActionClass::Stand( const ActionParamsStruct & parameters ) { return Request_Action( new StandActionCodeClass(), parameters ); } bool ActionClass::Play_Animation( const ActionParamsStruct & parameters ) { #if 0 // This is difficult, because huamns play default anims // Try to confirm we are not playing a non-action anim if ( !Is_Animating() && ActionObj->Get_Anim_Control()->Get_Animation_Name()[0] != 0 ) { Debug_Say(( "Can't Action_Play_Animation when playing a non-action anim\n" )); WWASSERT( 0 ); } #endif return Request_Action( new PlayAnimationActionCodeClass(), parameters ); } bool ActionClass::Goto( const ActionParamsStruct & parameters ) { return Request_Action( new GotoActionCodeClass(), parameters ); } bool ActionClass::Attack( const ActionParamsStruct & parameters ) { return Request_Action( new AttackActionCodeClass(), parameters ); } bool ActionClass::Face_Location( const ActionParamsStruct & parameters ) { return Request_Action( new FaceLocationActionCodeClass(), parameters ); } bool ActionClass::Have_Conversation( const ActionParamsStruct & parameters ) { return Request_Action( new ConversationActionCodeClass(), parameters ); } bool ActionClass::Dock_Vehicle( const ActionParamsStruct & parameters ) { return Request_Action( new DockActionCodeClass(), parameters ); } bool ActionClass::Enter_Exit( const ActionParamsStruct & parameters ) { return Request_Action( new EnterExitActionCodeClass(), parameters ); } bool ActionClass::Dive( const ActionParamsStruct & parameters ) { return Request_Action( new DiveActionCodeClass(), parameters ); } void ActionClass::Act( void ) { if ( IsPaused == false ) { ActCount++; ActionObj->Clear_Control(); if ( ActionCode != NULL ) { WWPROFILE( "Action Code" ); _ActionActCalls++; ActionCode->Act(); } Delete_Action_Code(); } } bool ActionClass::Is_Active( void ) { return ( ActionCode != NULL ); } bool ActionClass::Is_Busy( void ) { bool retval = false; if ( ActionCode != NULL ) { retval = ActionCode->Is_Busy(); } return retval; } /* ** ActionClass Save and Load */ enum { CHUNKID_ACTION_CODE = 712001900, CHUNKID_PARAMETERS, }; bool ActionClass::Save( ChunkSaveClass & csave ) { if ( ActionCode != NULL ) { csave.Begin_Chunk( CHUNKID_ACTION_CODE ); csave.Begin_Chunk( ActionCode->Get_Factory().Chunk_ID() ); ActionCode->Get_Factory().Save( csave, ActionCode ); csave.End_Chunk(); csave.End_Chunk(); } csave.Begin_Chunk( CHUNKID_PARAMETERS ); Parameters.Save( csave ); csave.End_Chunk(); // Don't need to save ActionObj because it is set on ActionClass Constructor // Don't need to save ActCount because it is diagnostic return true; } bool ActionClass::Load( ChunkLoadClass &cload ) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case CHUNKID_ACTION_CODE: while (cload.Open_Chunk()) { PersistFactoryClass * factory = SaveLoadSystemClass::Find_Persist_Factory( cload.Cur_Chunk_ID() ); if ( factory ) { ActionCode = (ActionCodeClass *)factory->Load( cload ); WWASSERT( ActionCode != NULL ); ActionCode->Set_Action( this ); } cload.Close_Chunk(); } break; case CHUNKID_PARAMETERS: Parameters.Load( cload ); break; default: Debug_Say(( "Unrecognized Action chunkID\n" )); break; } cload.Close_Chunk(); } return true; } void ActionClass::Begin_Hibernation( void ) { // // Let the action code know we are entering hibernation // if ( ActionCode != NULL ) { ActionCode->Begin_Hibernation(); } return ; } void ActionClass::End_Hibernation( void ) { // // Let the action code know we are coming out of hibernation // if ( ActionCode != NULL ) { ActionCode->End_Hibernation(); } return ; }