/* ** 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 . */ /*********************************************************************************************** *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** *********************************************************************************************** * * * Project Name : LevelEdit * * * * $Archive:: /Commando/Code/Combat/pilot.cpp $* * * * Author:: Patrick Smith * * * * $Modtime:: 11/26/01 5:07p $* * * * $Revision:: 18 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "pilot.h" #include "smartgameobj.h" #include "matrix3d.h" #include "movephys.h" #include "vehiclephys.h" #include "path.h" #include "pscene.h" #include "vehicle.h" #include "soldier.h" #include "heightdb.h" #include "debug.h" //////////////////////////////////////////////////////////////// // Save/Load constants //////////////////////////////////////////////////////////////// enum { CHUNKID_VARIABLES = 0x11040504, }; enum { VARID_FINAL_DEST = 1, VARID_NEXT_POINT, VARID_CURRENT_TM, VARID_OBJ_SPACE_DEST, VARID_OBJ_SPACE_WPOINT, VARID_GAMEOBJ_PTR, VARID_MAX_SPEED, VARID_SPEED_FACTOR, VARID_AGGRESSIVENESS, VARID_ARRIVED_DIST, VARID_HOVERDIST, VARID_FACE_TARGET, VARID_TARGET_LOCATION, VARID_MODE, VARID_FORWARD_SPEED, VARID_STRAFE_SPEED, VARID_LIFT_SPEED, VARID_TURN_SHARPNESS, VARID_CIRCLE_ANGLE, VARID_CIRCLE_DIST, VARID_MIN_CIRCLE_ANGLE, VARID_MAX_CIRCLE_ANGLE, VARID_ISEXACT_Z_IMPORT, VARID_PATH_PTR }; //////////////////////////////////////////////////////////////// // Constants //////////////////////////////////////////////////////////////// const float MAX_HEIGHT_DELTA = 2.5F; const float MAX_STRAFE_DELTA = 10.0F; const float MAX_FORWARD_DELTA = 10.0F; //////////////////////////////////////////////////////////////// // Clamp_Angle //////////////////////////////////////////////////////////////// inline float Clamp_Angle (float angle, float min_angle, float max_angle) { // // Make sure all the parameters are in the same range // angle = WWMath::Wrap (angle, 0, DEG_TO_RADF (360)); min_angle = WWMath::Wrap (min_angle, 0, DEG_TO_RADF (360)); max_angle = WWMath::Wrap (max_angle, 0, DEG_TO_RADF (360)); float result = angle; if (min_angle <= max_angle) { // // Handle the 'typical' case where there is no 360-mark wrap-around // if (angle < min_angle) { result = min_angle; } else if (angle > max_angle) { result = max_angle; } } else { // // Handle the 360-mark wrap-around case // if (angle < min_angle && angle > max_angle) { float min_delta = min_angle - angle; float max_delta = angle - max_angle; // // Which edge is the angle closer to? // if (min_delta < max_delta) { result = min_angle; } else { result = max_angle; } } } return result; } //////////////////////////////////////////////////////////////// // // PilotClass // //////////////////////////////////////////////////////////////// PilotClass::PilotClass (void) : m_FinalDest (0, 0, 0), m_NextPoint (0, 0, 0), m_GameObj (NULL), m_MaxSpeed (20.0F), m_SpeedFactor (1.0F), m_Aggressiveness (0.25F), m_ArrivedDist (0), m_HoverDist (15.0F), m_FaceTarget (false), m_TargetLocation (0, 0, 0), m_Mode (MODE_HOVER), m_ForwardSpeed (0), m_StrafeSpeed (0), m_LiftSpeed (0), m_TurnSharpness (0), m_CurrentPath (NULL), m_CircleAngle (0), m_CircleDist (0), m_MinCircleAngle (-DEG_TO_RADF(180)), m_MaxCircleAngle (DEG_TO_RADF(180)), m_IsExactZImportant (false) { return ; } //////////////////////////////////////////////////////////////// // // Get_Object_Space_Velocity // // Returns the object-space velocity vector // //////////////////////////////////////////////////////////////// void PilotClass::Get_Object_Space_Velocity (Vector3 &vel_vector) const { // // Get a pointer to the physics object for this vehicle // MoveablePhysClass *phys_obj = m_GameObj->Peek_Physical_Object()->As_MoveablePhysClass (); WWASSERT (phys_obj != NULL); // // Get the world-space velocity vector for this vehicle and transform // it into object space. // Vector3 vel_vector_world; phys_obj->Get_Velocity (&vel_vector_world); Matrix3D::Inverse_Rotate_Vector (m_GameObj->Get_Transform (), vel_vector_world, &vel_vector); return ; } //////////////////////////////////////////////////////////////// // // Set_Target // //////////////////////////////////////////////////////////////// void PilotClass::Set_Target (const Vector3 *target) { if (target != NULL) { m_TargetLocation = *target; if (m_Mode != MODE_CIRCLE_POINT) { m_FaceTarget = true; } } else { m_FaceTarget = false; } return ; } //////////////////////////////////////////////////////////////// // // Initialize // //////////////////////////////////////////////////////////////// void PilotClass::Initialize (SmartGameObj *game_obj) { m_GameObj = game_obj; m_CurrentPath = NULL; // // If the game object is flying a vehicle, then get the vehicle instead // SoldierGameObj *soldier_game_obj = game_obj->As_SoldierGameObj (); if (soldier_game_obj != NULL) { VehicleGameObj *vehicle_game_obj = soldier_game_obj->Get_Profile_Vehicle (); if (vehicle_game_obj != NULL) { m_GameObj = vehicle_game_obj; } } // // By default assume our final destination is our current location // game_obj->Get_Position (&m_FinalDest); game_obj->Get_Position (&m_NextPoint); // // Determine if this game object is a vehicle or not (it better be...) // VehicleGameObj *vehicle = m_GameObj->As_VehicleGameObj (); if (vehicle != NULL) { // // Start this vehicle's engine's running // if (vehicle->Is_Engine_Enabled () == false) { vehicle->Enable_Engine (true); } } return ; } //////////////////////////////////////////////////////////////// // // Calculate_Desired_Relative_Facing // //////////////////////////////////////////////////////////////// float PilotClass::Calculate_Desired_Relative_Facing (void) { float base_angle = 0; // // Should we be facing the destination or the current target? // if (m_FaceTarget) { // // Convert the target location from world to object space // Vector3 target_pos; Matrix3D::Inverse_Transform_Vector (m_CurrentTM, m_TargetLocation, &target_pos); // // Calculate the absolute turn angle // base_angle = WWMath::Atan2 (target_pos.Y, target_pos.X); base_angle = WWMath::Wrap (base_angle, 0, WWMATH_PI * 2); } else if (m_Mode != MODE_HOVER) { // // Calculate the absolute turn angle // base_angle = WWMath::Atan2 (m_ObjSpaceWaypoint.Y, m_ObjSpaceWaypoint.X); base_angle = WWMath::Wrap (base_angle, 0, WWMATH_PI * 2); } // // Convert the angle from [0, 360] to [-180, 180] // return WWMath::Wrap (base_angle, -WWMATH_PI, WWMATH_PI); } //////////////////////////////////////////////////////////////// // // Think // //////////////////////////////////////////////////////////////// bool PilotClass::Think (void) { // // Update the current // if (m_CurrentPath != NULL) { m_FinalDest = m_CurrentPath->Get_Dest_Pos (); m_NextPoint = m_CurrentPath->Get_Next_Pos (); } // // Let each mode think independently // switch (m_Mode) { case MODE_HOVER: Process_Hover (); break; case MODE_FLY_TO_POINT: Process_Fly_To_Point (); break; case MODE_CIRCLE_POINT: Process_Circle_Point (); break; } // // Pass the current controls onto the game object // Apply_Controls (); // // Return true when we've arrived at the destination // return Has_Arrived (); } //////////////////////////////////////////////////////////////// // // Calculate_Strafe_Speed // //////////////////////////////////////////////////////////////// void PilotClass::Calculate_Strafe_Speed (void) { m_StrafeSpeed = 0; bool should_strafe = m_FaceTarget; if (should_strafe) { m_StrafeSpeed = m_ObjSpaceWaypoint.Y / MAX_STRAFE_DELTA; m_StrafeSpeed = WWMath::Clamp (m_StrafeSpeed, -1.0F, 1.0F); } return ; } //////////////////////////////////////////////////////////////// // // Calculate_Forward_Speed // //////////////////////////////////////////////////////////////// void PilotClass::Calculate_Forward_Speed (float distance) { m_ForwardSpeed = 0; // // Pick a speed based on our distance from the destination // m_ForwardSpeed = distance / MAX_FORWARD_DELTA; m_ForwardSpeed = WWMath::Clamp (m_ForwardSpeed, -1.0F, 1.0F); return ; } //////////////////////////////////////////////////////////////// // // Calculate_Lift_Speed // //////////////////////////////////////////////////////////////// void PilotClass::Calculate_Lift_Speed (void) { //bool is_exact_height_important = (m_NextPoint != m_FinalDest); // // Determine what z position we should aim for // float preferred_height = m_NextPoint.Z; float flight_floor = -5000.0F; if (m_IsExactZImportant == false) { flight_floor = Determine_Preferred_Height (); preferred_height = flight_floor; if (preferred_height < m_NextPoint.Z) { preferred_height = m_NextPoint.Z; } } else { preferred_height = m_NextPoint.Z; } // // Get the vehicle's current position // Vector3 curr_pos; m_GameObj->Get_Position (&curr_pos); // // Calculate the lift speed based on the change in altitude // Once the height delta is within MAX_HIEGHT_DELTA, we set the desired velocity to zero // and try to let the hovering code maintain the desired altitude. // float delta_z = preferred_height - curr_pos.Z; float abs_delta_z = WWMath::Fabs(delta_z); if (m_Mode != MODE_HOVER || abs_delta_z > MAX_HEIGHT_DELTA) { m_LiftSpeed = delta_z / MAX_HEIGHT_DELTA; m_LiftSpeed = WWMath::Clamp (m_LiftSpeed, -1.0F, 1.0F); } else { m_LiftSpeed = 0.0f; } // // Keep the vehicle from 'sinking' sharply // if ( m_IsExactZImportant == false && m_Mode == MODE_FLY_TO_POINT && delta_z < 0) { m_LiftSpeed = m_LiftSpeed * 0.25f; } // // Slow down our forward speed to give us time to change altitude (if necessary) // if (curr_pos.Z < flight_floor) { //float new_speed = WWMath::Clamp (1.0F - m_LiftSpeed, 0.5F, 1.0F); //m_ForwardSpeed = min (m_ForwardSpeed, new_speed); } return ; } //////////////////////////////////////////////////////////////// // // Calculate_Turn_Sharpness // //////////////////////////////////////////////////////////////// void PilotClass::Calculate_Turn_Sharpness (void) { float facing_angle = Calculate_Desired_Relative_Facing (); // // Calculate how sharp we should turn to make this facing // m_TurnSharpness = facing_angle / DEG_TO_RADF (45); m_TurnSharpness = WWMath::Clamp (m_TurnSharpness, -1.0F, 1.0F); // // If the vehicle needs to face its target, then make it turn faster then normal // if (m_FaceTarget || m_Mode == MODE_CIRCLE_POINT) { m_TurnSharpness *= 2.0F; m_TurnSharpness = min (m_TurnSharpness, 1.0F); } return ; } //////////////////////////////////////////////////////////////// // // Process_Hover // //////////////////////////////////////////////////////////////// void PilotClass::Process_Hover (void) { // // Update the "simplified" transform // Update_Transform (); // // When hovering, we want to maintain a zero horizontal velocity // while moving towards our desired altitude // m_ForwardSpeed = 0.0f; m_StrafeSpeed = 0.0f; // // Update the "simplified" transform // Update_Transform (); // // When hovering, we want to maintain a zero horizontal velocity // while moving towards our desired altitude // m_ForwardSpeed = 0.0f; m_StrafeSpeed = 0.0f; // // When hovering its valid to face a target, so update // the vehicle's facing // if (m_FaceTarget) { Calculate_Turn_Sharpness (); } return ; } //////////////////////////////////////////////////////////////// // // Process_Circle_Point // //////////////////////////////////////////////////////////////// void PilotClass::Process_Circle_Point (void) { // // Reset all control inputs // m_ForwardSpeed = 0; m_StrafeSpeed = 0; m_LiftSpeed = 0; // // When hovering its valid to face a target, so update // the vehicle's facing // Update_Transform (); // // Make sure we are facing the destination // Calculate_Turn_Sharpness (); // // If we are facing the destination, then we can // start our 'circling' movement // if (::fabs (m_TurnSharpness) < 0.1F) { // // Make sure the vehicle is the appropriate distance from the circle point // Calculate_Forward_Speed (m_ObjSpaceDest.X - m_CircleDist); Calculate_Lift_Speed (); // // Calculate what our current circling angle is // Vector3 curr_pos; m_GameObj->Get_Position (&curr_pos); Vector3 delta = curr_pos - m_FinalDest; float curr_angle = WWMath::Atan2 (delta.Y, delta.X); float angle_delta = WWMath::Wrap (curr_angle - m_CircleAngle, -WWMATH_PI, WWMATH_PI); // // Calculate how fast we should strafe in order to circle the target // m_StrafeSpeed = angle_delta / DEG_TO_RADF (5); m_StrafeSpeed = WWMath::Clamp (m_StrafeSpeed, -1.0F, 1.0F); } return ; } //////////////////////////////////////////////////////////////// // // Process_Fly_To_Point // //////////////////////////////////////////////////////////////// void PilotClass::Process_Fly_To_Point (void) { // // Get the vehicle's current transform and check to see if // the vehicle has arrived at its destination yet // Update_Transform (); Check_Completion (); // // Calculate how sharp the vehicle needs to turn // Calculate_Turn_Sharpness (); // // Calculate how fast the vehicle needs to move in each direction // Calculate_Strafe_Speed (); // // If we are on a path then we need to calculate the forward speed // as if we are going full speed the whole way there // bool is_on_path = (m_NextPoint != m_FinalDest); if (is_on_path) { // // Clamp the speed to -1, 0, or 1. // m_ForwardSpeed = m_ObjSpaceWaypoint.X; if (m_ForwardSpeed > 0) { m_ForwardSpeed = 1.0F; } else if (m_ForwardSpeed < 0) { m_ForwardSpeed = -1.0F; } else { m_ForwardSpeed = 0.0F; } // // Reduce forward/back speed if we are mostly strafing. // if (WWMath::Fabs (m_StrafeSpeed) > 0.25f) { m_ForwardSpeed = m_ForwardSpeed * (1.25F - WWMath::Fabs (m_StrafeSpeed)); } // // Let the pilot slow down to handle sharp corners // if (WWMath::Fabs (m_TurnSharpness) > 0.65F) { m_ForwardSpeed = m_ForwardSpeed * (1.25F - WWMath::Fabs (m_TurnSharpness)); } } else { Calculate_Forward_Speed (m_ObjSpaceDest.X); } Calculate_Lift_Speed (); return ; } //////////////////////////////////////////////////////////////// // // Check_Completion // //////////////////////////////////////////////////////////////// void PilotClass::Check_Completion (void) { // // Get the vehicle's bounding box // MoveablePhysClass *phys_obj = m_GameObj->Peek_Physical_Object ()->As_MoveablePhysClass (); RenderObjClass *render_obj = phys_obj->Peek_Model (); AABoxClass bounding_box; render_obj->Get_Obj_Space_Bounding_Box (bounding_box); // // Calculate the distance remaining on the path // float dist_to_goal = 0; if (m_CurrentPath != NULL) { float dist_on_path = m_CurrentPath->Get_Remaining_Path_Length (); dist_to_goal = (m_ObjSpaceWaypoint.Length () + dist_on_path); } else { dist_to_goal = m_ObjSpaceDest.Length (); } // // Take into account the 'arrived distance' setting. We basically fly towards the // closest point on a sphere around the destination point. m_ObjSpaceDest will // be initialized to that point transformed into the vehicle's coordinate system. // if (dist_to_goal > m_ArrivedDist * m_ArrivedDist) { Vector3 arrived_len = m_ObjSpaceDest; arrived_len.Normalize (); arrived_len *= m_ArrivedDist; m_ObjSpaceDest -= arrived_len; m_ObjSpaceDest += bounding_box.Center; } // // Once we come within m_HoverDist of the desired position we switch into "hover" mode. // if (dist_to_goal < m_HoverDist && m_NextPoint == m_FinalDest) { Vector2 xy_delta(m_ObjSpaceWaypoint.X, m_ObjSpaceWaypoint.Y); if (xy_delta.Length2() < m_HoverDist * m_HoverDist) { Set_Mode(MODE_HOVER); // // If we don't have a target, we need to create a fake target so that // the orientation of the vehicle doesn't drift. // if (m_FaceTarget == false) { Vector3 fake_target; Matrix3D::Transform_Vector(m_CurrentTM,Vector3(1000.0f,0.0f,0.0f),&fake_target); Set_Target(&fake_target); } } } return ; } //////////////////////////////////////////////////////////////// // // Update_Transform // //////////////////////////////////////////////////////////////// void PilotClass::Update_Transform (void) { Matrix3D obj_tm = m_GameObj->Get_Transform (); // // Build a transform that only uses the facing and position // of the vehicle. // m_CurrentTM.Make_Identity (); m_CurrentTM.Rotate_Z (obj_tm.Get_Z_Rotation ()); m_CurrentTM.Set_Translation (obj_tm.Get_Translation ()); // // Now convert the destination from world space to object space // Matrix3D::Inverse_Transform_Vector (m_CurrentTM, m_FinalDest, &m_ObjSpaceDest); Matrix3D::Inverse_Transform_Vector (m_CurrentTM, m_NextPoint, &m_ObjSpaceWaypoint); // // If its not important for the vehicle to tightly follow the Z value // of its points, then simply zero out the Z component // if (m_IsExactZImportant == false) { m_ObjSpaceWaypoint.Z = 0; } return ; } //////////////////////////////////////////////////////////////// // // Determine_Preferred_Height // //////////////////////////////////////////////////////////////// float PilotClass::Determine_Preferred_Height (void) { float height = m_NextPoint.Z; if (m_Mode != MODE_FLY_TO_POINT) { return height; } // // Get a pointer to the physics object for this vehicle // MoveablePhysClass *phys_obj = m_GameObj->Peek_Physical_Object()->As_MoveablePhysClass (); WWASSERT (phys_obj != NULL); // // Get the world-space velocity vector for this vehicle and transform // it into object space. // Vector3 vel_vector_world; phys_obj->Get_Velocity (&vel_vector_world); // // Try to determine where the aircraft will be in 1 and 2 seconds // from now. // Vector3 curr_pos; m_GameObj->Get_Position (&curr_pos); RenderObjClass *render_obj = phys_obj->Peek_Model (); const AABoxClass &bounding_box = render_obj->Get_Bounding_Box (); Vector3 curr_pos1 = curr_pos; Vector3 curr_pos2 = curr_pos + Vector3 (bounding_box.Extent.X, bounding_box.Extent.Y, 0); Vector3 curr_pos3 = curr_pos + Vector3 (-bounding_box.Extent.X, -bounding_box.Extent.Y, 0); Vector3 curr_pos4 = curr_pos + Vector3 (bounding_box.Extent.X, -bounding_box.Extent.Y, 0); Vector3 curr_pos5 = curr_pos + Vector3 (-bounding_box.Extent.X, bounding_box.Extent.Y, 0); Vector3 future_pos1 = curr_pos1 + (vel_vector_world * 1.25F); Vector3 future_pos2 = curr_pos2 + (vel_vector_world * 1.25F); Vector3 future_pos3 = curr_pos3 + (vel_vector_world * 1.25F); Vector3 future_pos4 = curr_pos4 + (vel_vector_world * 1.25F); Vector3 future_pos5 = curr_pos5 + (vel_vector_world * 1.25F); Vector3 future_pos6 = curr_pos1 + (vel_vector_world * 2.25F); Vector3 future_pos7 = curr_pos2 + (vel_vector_world * 2.25F); Vector3 future_pos8 = curr_pos3 + (vel_vector_world * 2.25F); Vector3 future_pos9 = curr_pos4 + (vel_vector_world * 2.25F); Vector3 future_pos10 = curr_pos5 + (vel_vector_world * 2.25F); future_pos1.Z = curr_pos.Z; future_pos2.Z = curr_pos.Z; future_pos3.Z = curr_pos.Z; future_pos4.Z = curr_pos.Z; future_pos5.Z = curr_pos.Z; future_pos6.Z = curr_pos.Z; future_pos7.Z = curr_pos.Z; future_pos8.Z = curr_pos.Z; future_pos9.Z = curr_pos.Z; future_pos10.Z = curr_pos.Z; // // Now, lookup the preferred height for these future positions // float height1 = HeightDBClass::Get_Height (future_pos1); float height2 = HeightDBClass::Get_Height (future_pos2); float height3 = HeightDBClass::Get_Height (future_pos3); float height4 = HeightDBClass::Get_Height (future_pos4); float height5 = HeightDBClass::Get_Height (future_pos5); float height6 = HeightDBClass::Get_Height (future_pos6); float height7 = HeightDBClass::Get_Height (future_pos7); float height8 = HeightDBClass::Get_Height (future_pos8); float height9 = HeightDBClass::Get_Height (future_pos9); float height10 = HeightDBClass::Get_Height (future_pos10); // // Return the largest height to the caller // height = max (height1, height2); height = max (height, height3); height = max (height, height4); height = max (height, height5); height = max (height, height6); height = max (height, height7); height = max (height, height8); height = max (height, height9); height = max (height, height10); return height; } //////////////////////////////////////////////////////////////// // // Apply_Controls // //////////////////////////////////////////////////////////////// void PilotClass::Apply_Controls (void) { // // If the vehicle has a driver, then pass the controls onto the driver // instead... // SmartGameObj *game_obj = m_GameObj; VehicleGameObj *vehicle = m_GameObj->As_VehicleGameObj (); if (vehicle != NULL) { if (vehicle->Get_Driver () != NULL) { //game_obj = vehicle->Get_Driver (); } } // // Calculate our (current) normalized speeds // Vector3 vel_vector; Get_Object_Space_Velocity (vel_vector); float curr_forward_speed = WWMath::Clamp ((vel_vector.X / m_MaxSpeed), -1.0F, 1.0F); float curr_strafe_speed = WWMath::Clamp ((vel_vector.Y / m_MaxSpeed), -1.0F, 1.0F); float curr_lift_speed = WWMath::Clamp ((vel_vector.Z / m_MaxSpeed), -1.0F, 1.0F); // // Calculate how much we need to accelerate or decelerate in the three directions // in order to meet our target speeds // float forward_accel = 0.0f; float strafe_accel = 0.0f; float lift_accel = 0.0f; if (m_Mode == MODE_HOVER) { forward_accel = 5.5f * (m_ForwardSpeed - curr_forward_speed) + 0.25f * m_ObjSpaceDest.X; strafe_accel = 3.0f * (m_StrafeSpeed - curr_strafe_speed) + 0.5f * m_ObjSpaceDest.Y; lift_accel = 1.0f * (m_LiftSpeed - curr_lift_speed) + 0.5f * m_ObjSpaceDest.Z; forward_accel = WWMath::Clamp(forward_accel,-1.0f,1.0f); strafe_accel = WWMath::Clamp(strafe_accel,-1.0f,1.0f); lift_accel = WWMath::Clamp(lift_accel,-1.0f,1.0f); } else { forward_accel = WWMath::Clamp (5.0f * (m_ForwardSpeed - curr_forward_speed), -1.0F, 1.0F); strafe_accel = WWMath::Clamp (1.0f * (m_StrafeSpeed - curr_strafe_speed), -1.0F, 1.0F); lift_accel = WWMath::Clamp (3.0f * (m_LiftSpeed - curr_lift_speed), -1.0F, 1.0F); } // // Make the vehicle go forward/backward // game_obj->Set_Analog_Control (ControlClass::ANALOG_MOVE_FORWARD, forward_accel); // // Make the vehicle strafe left/right // game_obj->Set_Analog_Control (ControlClass::ANALOG_MOVE_LEFT, strafe_accel); // // Make the vehicle go up/down // game_obj->Set_Analog_Control (ControlClass::ANALOG_MOVE_UP, lift_accel); // // Make the vehicle turn // game_obj->Set_Analog_Control (ControlClass::ANALOG_TURN_LEFT, m_TurnSharpness); return ; } //////////////////////////////////////////////////////////////// // // Set_Mode // //////////////////////////////////////////////////////////////// void PilotClass::Set_Mode (PilotClass::MODE mode) { if (m_Mode != mode) { switch (mode) { case MODE_HOVER: break; case MODE_FLY_TO_POINT: break; case MODE_CIRCLE_POINT: { m_FaceTarget = false; // // Initialize the circling distance // Vector3 curr_pos; m_GameObj->Get_Position (&curr_pos); m_CircleDist = (curr_pos - m_FinalDest).Length (); } break; } // // When we change modes, we reset the target in case we had // a fake target from hover mode // Set_Target(NULL); m_Mode = mode; } return ; } //////////////////////////////////////////////////////////////// // // Set_Circle_Pos // //////////////////////////////////////////////////////////////// void PilotClass::Set_Circle_Pos (const Vector3 &pos) { Vector3 delta = pos - m_FinalDest; m_CircleAngle = WWMath::Atan2 (delta.Y, delta.X); m_CircleAngle = WWMath::Wrap (m_CircleAngle, 0, DEG_TO_RADF (360)); m_CircleAngle = ::Clamp_Angle (m_CircleAngle, m_MinCircleAngle, m_MaxCircleAngle); return ; } //////////////////////////////////////////////////////////////// // // Set_Circle_Angle // //////////////////////////////////////////////////////////////// void PilotClass::Set_Circle_Angle (float angle) { m_CircleAngle = WWMath::Wrap (angle, 0, DEG_TO_RADF (360)); m_CircleAngle = ::Clamp_Angle (m_CircleAngle, m_MinCircleAngle, m_MaxCircleAngle); return ; } //////////////////////////////////////////////////////////////// // // Set_Circle_Bounds // //////////////////////////////////////////////////////////////// void PilotClass::Set_Circle_Bounds (float min_angle, float max_angle) { m_MinCircleAngle = min_angle; m_MaxCircleAngle = max_angle; m_CircleAngle = ::Clamp_Angle (m_CircleAngle, m_MinCircleAngle, m_MaxCircleAngle); return ; } //////////////////////////////////////////////////////////////// // // Has_Arrived // //////////////////////////////////////////////////////////////// bool PilotClass::Has_Arrived (void) const { bool has_arrived = false; if (m_Mode == MODE_HOVER) { /*bool if (m_CurrentPath != NULL) { m_ObjSpaceDest.Length () }*/ // // In hovering mode, we have 'arrived' if we have mostly finished moving // Vector3 vel_vector; Get_Object_Space_Velocity (vel_vector); has_arrived = (WWMath::Fabs (vel_vector.X) < 0.5F && WWMath::Fabs (vel_vector.Y) < 0.5F && WWMath::Fabs (vel_vector.Z) < 0.5F); } return has_arrived; } //////////////////////////////////////////////////////////////// // // Get_Current_Circle_Angle // //////////////////////////////////////////////////////////////// float PilotClass::Get_Current_Circle_Angle (void) const { Vector3 curr_pos; m_GameObj->Get_Position (&curr_pos); Vector3 delta = curr_pos - m_FinalDest; return WWMath::Atan2 (delta.Y, delta.X); } //////////////////////////////////////////////////////////////////////////////////////////// // // Save // //////////////////////////////////////////////////////////////////////////////////////////// void PilotClass::Save (ChunkSaveClass &csave) { csave.Begin_Chunk (CHUNKID_VARIABLES); // // Save each variable to its own microchunk // WRITE_MICRO_CHUNK (csave, VARID_FINAL_DEST, m_FinalDest); WRITE_MICRO_CHUNK (csave, VARID_NEXT_POINT, m_NextPoint); WRITE_MICRO_CHUNK (csave, VARID_CURRENT_TM, m_CurrentTM); WRITE_MICRO_CHUNK (csave, VARID_OBJ_SPACE_DEST, m_ObjSpaceDest); WRITE_MICRO_CHUNK (csave, VARID_OBJ_SPACE_WPOINT, m_ObjSpaceWaypoint); WRITE_MICRO_CHUNK (csave, VARID_PATH_PTR, m_CurrentPath); WRITE_MICRO_CHUNK (csave, VARID_GAMEOBJ_PTR, m_GameObj); WRITE_MICRO_CHUNK (csave, VARID_MAX_SPEED, m_MaxSpeed); WRITE_MICRO_CHUNK (csave, VARID_SPEED_FACTOR, m_SpeedFactor); WRITE_MICRO_CHUNK (csave, VARID_AGGRESSIVENESS, m_Aggressiveness); WRITE_MICRO_CHUNK (csave, VARID_ARRIVED_DIST, m_ArrivedDist); WRITE_MICRO_CHUNK (csave, VARID_HOVERDIST, m_HoverDist); WRITE_MICRO_CHUNK (csave, VARID_ISEXACT_Z_IMPORT, m_IsExactZImportant); WRITE_MICRO_CHUNK (csave, VARID_FACE_TARGET, m_FaceTarget); WRITE_MICRO_CHUNK (csave, VARID_TARGET_LOCATION, m_TargetLocation); WRITE_MICRO_CHUNK (csave, VARID_MODE, m_Mode); WRITE_MICRO_CHUNK (csave, VARID_FORWARD_SPEED, m_ForwardSpeed); WRITE_MICRO_CHUNK (csave, VARID_STRAFE_SPEED, m_StrafeSpeed); WRITE_MICRO_CHUNK (csave, VARID_LIFT_SPEED, m_LiftSpeed); WRITE_MICRO_CHUNK (csave, VARID_TURN_SHARPNESS, m_TurnSharpness); WRITE_MICRO_CHUNK (csave, VARID_CIRCLE_ANGLE, m_CircleAngle); WRITE_MICRO_CHUNK (csave, VARID_CIRCLE_DIST, m_CircleDist); WRITE_MICRO_CHUNK (csave, VARID_MIN_CIRCLE_ANGLE, m_MinCircleAngle); WRITE_MICRO_CHUNK (csave, VARID_MAX_CIRCLE_ANGLE, m_MaxCircleAngle); csave.End_Chunk (); return ; } //////////////////////////////////////////////////////////////////////////////////////////// // // Load // //////////////////////////////////////////////////////////////////////////////////////////// void PilotClass::Load (ChunkLoadClass &cload) { while (cload.Open_Chunk ()) { switch (cload.Cur_Chunk_ID ()) { case CHUNKID_VARIABLES: Load_Variables (cload); break; } cload.Close_Chunk (); } return ; } /////////////////////////////////////////////////////////////////////// // // Load_Variables // /////////////////////////////////////////////////////////////////////// void PilotClass::Load_Variables (ChunkLoadClass &cload) { // // Loop through all the microchunks that define the variables // while (cload.Open_Micro_Chunk ()) { switch (cload.Cur_Micro_Chunk_ID ()) { READ_MICRO_CHUNK (cload, VARID_FINAL_DEST, m_FinalDest); READ_MICRO_CHUNK (cload, VARID_NEXT_POINT, m_NextPoint); READ_MICRO_CHUNK (cload, VARID_CURRENT_TM, m_CurrentTM); READ_MICRO_CHUNK (cload, VARID_OBJ_SPACE_DEST, m_ObjSpaceDest); READ_MICRO_CHUNK (cload, VARID_OBJ_SPACE_WPOINT, m_ObjSpaceWaypoint); READ_MICRO_CHUNK (cload, VARID_PATH_PTR, m_CurrentPath); READ_MICRO_CHUNK (cload, VARID_GAMEOBJ_PTR, m_GameObj); READ_MICRO_CHUNK (cload, VARID_MAX_SPEED, m_MaxSpeed); READ_MICRO_CHUNK (cload, VARID_SPEED_FACTOR, m_SpeedFactor); READ_MICRO_CHUNK (cload, VARID_AGGRESSIVENESS, m_Aggressiveness); READ_MICRO_CHUNK (cload, VARID_ARRIVED_DIST, m_ArrivedDist); READ_MICRO_CHUNK (cload, VARID_HOVERDIST, m_HoverDist); READ_MICRO_CHUNK (cload, VARID_ISEXACT_Z_IMPORT, m_IsExactZImportant); READ_MICRO_CHUNK (cload, VARID_FACE_TARGET, m_FaceTarget); READ_MICRO_CHUNK (cload, VARID_TARGET_LOCATION, m_TargetLocation); READ_MICRO_CHUNK (cload, VARID_MODE, m_Mode); READ_MICRO_CHUNK (cload, VARID_FORWARD_SPEED, m_ForwardSpeed); READ_MICRO_CHUNK (cload, VARID_STRAFE_SPEED, m_StrafeSpeed); READ_MICRO_CHUNK (cload, VARID_LIFT_SPEED, m_LiftSpeed); READ_MICRO_CHUNK (cload, VARID_TURN_SHARPNESS, m_TurnSharpness); READ_MICRO_CHUNK (cload, VARID_CIRCLE_ANGLE, m_CircleAngle); READ_MICRO_CHUNK (cload, VARID_CIRCLE_DIST, m_CircleDist); READ_MICRO_CHUNK (cload, VARID_MIN_CIRCLE_ANGLE, m_MinCircleAngle); READ_MICRO_CHUNK (cload, VARID_MAX_CIRCLE_ANGLE, m_MaxCircleAngle); } cload.Close_Micro_Chunk (); } // // Request that the game object ptr gets remapped // if (m_GameObj != NULL) { REQUEST_POINTER_REMAP ((void **)&m_GameObj); } // // Request that the path object ptr gets remapped // if (m_CurrentPath != NULL) { REQUEST_POINTER_REMAP ((void **)&m_CurrentPath); } return ; } /////////////////////////////////////////////////////////////////////// // // Reset // /////////////////////////////////////////////////////////////////////// void PilotClass::Reset (void) { m_CurrentPath = NULL; m_GameObj = NULL; m_Mode = MODE_HOVER, m_IsExactZImportant = false; return ; }