Complete disaster-recovery snapshot: engine/game source, game data assets, VC6 toolchain + DX SDKs, build outputs, deployed game, and _UNUSED archive. Large binaries in Git LFS; text preserved byte-for-byte (core.autocrlf=false, no eol attributes). See RECOVERY.md for the one-clone rebuild procedure.
2031 lines
59 KiB
C++
2031 lines
59 KiB
C++
//===========================================================================//
|
|
// File: Missile.cpp
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// 09/09/98 JSE Inital base class based off of Shadowrun/Adept //
|
|
// 09/25/98 BDB WeaponMover based on Vehicle //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1998, Fasa Interactive //
|
|
// All Rights reserved worldwide //
|
|
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
|
//===========================================================================//
|
|
|
|
#include "MW4Headers.hpp"
|
|
|
|
#include "Missile.hpp"
|
|
#include "Vehicle.hpp"
|
|
#include "Decal.hpp"
|
|
#include "VehicleInterface.hpp"
|
|
#include "MWMission.hpp"
|
|
#include "MWDamageObject.hpp"
|
|
|
|
#include <Adept\Map.hpp>
|
|
#include <Adept\Effect.hpp>
|
|
#include <Adept\NameTable.hpp>
|
|
#include <Adept\EntityManager.hpp>
|
|
#include <Adept\CollisionGrid.hpp>
|
|
#include <Adept\CollisionVolume.hpp>
|
|
|
|
//#define MISSILE_BUG "johnyo"
|
|
//#define MISSILE_BUG "daberger"
|
|
//#define TARGET_BUG "daberger"
|
|
|
|
//#############################################################################
|
|
//########################## ExecutionStateEngine #######################
|
|
//#############################################################################
|
|
|
|
Missile::ExecutionStateEngine::ClassData*
|
|
Missile::ExecutionStateEngine::DefaultData = NULL;
|
|
|
|
DWORD MechWarrior4::Executed_Missile_Count = 0;
|
|
|
|
const StateEngine::StateEntry
|
|
Missile::ExecutionStateEngine::StateEntries[]=
|
|
{
|
|
STATE_ENTRY(Missile__ExecutionStateEngine, LRMMotion),
|
|
STATE_ENTRY(Missile__ExecutionStateEngine, MRMMotion),
|
|
STATE_ENTRY(Missile__ExecutionStateEngine, SRMMotion),
|
|
STATE_ENTRY(Missile__ExecutionStateEngine, SSRMMotion),
|
|
STATE_ENTRY(Missile__ExecutionStateEngine, BombMotion),
|
|
STATE_ENTRY(Missile__ExecutionStateEngine, LongTomMotion)
|
|
|
|
};
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::ExecutionStateEngine::InitializeClass()
|
|
{
|
|
Check_Object(StateEngine::DefaultData);
|
|
Verify(!DefaultData);
|
|
DefaultData =
|
|
new ClassData(
|
|
Missile__ExecutionStateEngineClassID,
|
|
"Missile::ExecutionStateEngine",
|
|
BaseClass::DefaultData,
|
|
ELEMENTS(StateEntries), StateEntries,
|
|
(Entity::ExecutionStateEngine::Factory)Make,
|
|
(Entity::ExecutionStateEngine::FactoryRequest::Factory)
|
|
&FactoryRequest::ConstructFactoryRequest
|
|
);
|
|
Register_Object(DefaultData);
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::ExecutionStateEngine::TerminateClass()
|
|
{
|
|
Unregister_Object(DefaultData);
|
|
delete DefaultData;
|
|
DefaultData = NULL;
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Missile::ExecutionStateEngine*
|
|
Missile::ExecutionStateEngine::Make(
|
|
Missile *weapon,
|
|
FactoryRequest *request
|
|
)
|
|
{
|
|
Check_Object(weapon);
|
|
Check_Object(request);
|
|
gos_PushCurrentHeap(Heap);
|
|
Missile::ExecutionStateEngine *engine =
|
|
new Missile::ExecutionStateEngine(DefaultData, weapon, request);
|
|
gos_PopCurrentHeap();
|
|
return engine;
|
|
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::ExecutionStateEngine::TestInstance() const
|
|
{
|
|
Verify(IsDerivedFrom(DefaultData));
|
|
}
|
|
|
|
//#############################################################################
|
|
//############################### Missile ###############################
|
|
//#############################################################################
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Missile::ClassData*
|
|
Missile::DefaultData = NULL;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::InitializeClass()
|
|
{
|
|
Check_Object(Missile::ExecutionStateEngine::DefaultData);
|
|
Verify(!DefaultData);
|
|
DefaultData =
|
|
new ClassData(
|
|
MissileClassID,
|
|
"MechWarrior4::Missile",
|
|
BaseClass::DefaultData,
|
|
0, NULL,
|
|
(Entity::Factory)Make,
|
|
(Entity::CreateMessage::Factory)CreateMessage::ConstructCreateMessage,
|
|
ExecutionStateEngine::DefaultData,
|
|
(Entity::GameModel::Factory)GameModel::ConstructGameModel,
|
|
NULL,
|
|
(Entity::GameModel::ReadAndVerifier)GameModel::ReadAndVerify,
|
|
(Entity::GameModel::ModelWrite)GameModel::WriteToText,
|
|
(Entity::GameModel::ModelSave)GameModel::SaveGameModel
|
|
);
|
|
Register_Object(DefaultData);
|
|
|
|
DIRECT_GAME_MODEL_ATTRIBUTE(
|
|
Missile__GameModel,
|
|
MaxTurnAngle,
|
|
maxTurnAngle,
|
|
Scalar
|
|
);
|
|
|
|
DIRECT_GAME_MODEL_ATTRIBUTE(
|
|
Missile__GameModel,
|
|
ThrusterAcceleration,
|
|
thrusterAcceleration,
|
|
Scalar
|
|
);
|
|
|
|
DIRECT_GAME_MODEL_ATTRIBUTE(
|
|
Missile__GameModel,
|
|
ProximityFuseDistance,
|
|
proximityFuseDistance,
|
|
Scalar
|
|
);
|
|
|
|
DIRECT_GAME_MODEL_ATTRIBUTE(
|
|
Missile__GameModel,
|
|
MaxLiveTime,
|
|
maxLiveTime,
|
|
Scalar
|
|
);
|
|
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::TerminateClass()
|
|
{
|
|
Unregister_Object(DefaultData);
|
|
delete DefaultData;
|
|
DefaultData = NULL;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Missile*
|
|
Missile::Make(
|
|
CreateMessage *message,
|
|
ReplicatorID *base_id
|
|
)
|
|
{
|
|
Check_Object(message);
|
|
gos_PushCurrentHeap(Heap);
|
|
Missile *new_entity;
|
|
Entity *entity = EntityManager::GetInstance()->RequestFromArmory(message->dataListID);
|
|
if (entity)
|
|
{
|
|
new_entity = Cast_Object(Missile*, entity);
|
|
new_entity->Reuse(message, base_id);
|
|
}
|
|
else
|
|
new_entity = new Missile(DefaultData, message, base_id, NULL);
|
|
gos_PopCurrentHeap();
|
|
Check_Object(new_entity);
|
|
return new_entity;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Missile::Missile(
|
|
ClassData *class_data,
|
|
CreateMessage *message,
|
|
ReplicatorID *base_id,
|
|
ElementRenderer::Element *element
|
|
):
|
|
WeaponMover(class_data, message, base_id, element),
|
|
targetEntity(NULL),
|
|
targetMWObject(NULL)
|
|
{
|
|
Check_Pointer(this);
|
|
Check_Object(message);
|
|
targetEntity.Remove();
|
|
|
|
targetOffsetFromEntity.x = 0.0f;
|
|
targetOffsetFromEntity.y = 0.0f;
|
|
targetOffsetFromEntity.z = 0.0f;
|
|
|
|
targetPoint = message->targetPoint;
|
|
burnTime = message->burnTime;
|
|
|
|
if(message->targetID != ReplicatorID::Null)
|
|
{
|
|
Check_Object(Network::GetInstance());
|
|
Connection *connection = Network::GetInstance()->GetConnection(message->targetID.connectionID);
|
|
if (connection)
|
|
{
|
|
Check_Object(connection);
|
|
|
|
Replicator *replicator;
|
|
replicator = connection->FindReplicator(message->targetID);
|
|
if (replicator)
|
|
{
|
|
Check_Object(replicator);
|
|
targetEntity.Remove();
|
|
targetEntity.Add(Cast_Object(Entity*, replicator));
|
|
|
|
//
|
|
// This won't account for rotation. I'm not sure how well this will work on a component that can rotate, since I
|
|
// don't have a full transform to get to this point.
|
|
//
|
|
// only in the case of a locked entity, is targetPoint relative to the entity
|
|
targetOffsetFromEntity = targetPoint;
|
|
|
|
|
|
Stuff::Point3D tempPoint3D = targetPoint;
|
|
targetPoint.Multiply(tempPoint3D, targetEntity.GetCurrent()->GetLocalToWorld());
|
|
|
|
|
|
|
|
Entity * parentEntity = TraceToParent(targetEntity.GetCurrent());
|
|
|
|
targetMWObject.Remove();
|
|
if(parentEntity && parentEntity->IsDerivedFrom(MWObject::DefaultData))
|
|
targetMWObject.Add(Cast_Object(MWObject*, parentEntity));
|
|
|
|
if(targetMWObject.GetCurrent())
|
|
{
|
|
targetMWObject.GetCurrent()->NotifyOfMissileLock();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
targetPosition = targetPoint;
|
|
|
|
timeInAir = 0.0f;
|
|
|
|
|
|
m_fAllowArcMissile = true;
|
|
m_fTurnOffSeek = false;
|
|
m_nMissCount = 0;
|
|
|
|
srmPitchVector.x = 0.0f;
|
|
srmPitchVector.y = -0.6f;
|
|
srmPitchVector.z = 0.8f;
|
|
|
|
srmYTargetOffset = 0.0f;
|
|
|
|
doesAMSDestroyThisFrame = false;
|
|
amsExplosionResource = ResourceID::Null;
|
|
|
|
//
|
|
// We are going to store this to fix the odd angles when frame rate dips
|
|
//
|
|
lastWorldSpaceVelocity = initialWorldSpaceVelocity;
|
|
|
|
m_nDoubleTeleportPreventor = 0;
|
|
GetTargetCollisionVolume();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Entity* Missile::GetFirstAcceptableParentEntity(Entity* entity)
|
|
{
|
|
//
|
|
// Walk up the chain looking for the next acceptable entity
|
|
//
|
|
Entity * curEntity = entity;
|
|
while (curEntity)
|
|
{
|
|
|
|
if (!curEntity->IsDestroyed())
|
|
{
|
|
CollisionVolume *volume = curEntity->GetHierarchicalVolume();
|
|
if (!volume)
|
|
{
|
|
volume = curEntity->GetSolidVolume();
|
|
}
|
|
if (volume)
|
|
{
|
|
//
|
|
// This breaks if the volume above doesn't have anything at it's center since we can't guarantee this is
|
|
// a valid local as we can with the original target passed in.
|
|
//
|
|
targetOffsetFromEntity = volume->m_localSpaceBounds.localToParent;
|
|
return curEntity;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
curEntity = curEntity->GetParentEntity();
|
|
}
|
|
return NULL;
|
|
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void Missile::GetTargetCollisionVolume()
|
|
{
|
|
//
|
|
// Walk up the chain looking for something with a collision volume
|
|
//
|
|
Entity * curEntity = targetEntity.GetCurrent();
|
|
CollisionVolume *volume = NULL;
|
|
while (curEntity)
|
|
{
|
|
volume = curEntity->GetHierarchicalVolume();
|
|
if (!volume)
|
|
{
|
|
volume = curEntity->GetSolidVolume();
|
|
}
|
|
if (volume && !volume->IsDestroyed())
|
|
{
|
|
//
|
|
// If we have a solid volume we should check for the first child.
|
|
//
|
|
CollisionVolume * childvolume = volume;
|
|
//
|
|
// Recurse down to the last node
|
|
//
|
|
while (childvolume)
|
|
{
|
|
ChainIteratorOf<CollisionVolume*> children(&childvolume->m_children);
|
|
while ((childvolume = children.ReadAndNext()) != NULL)
|
|
{
|
|
if (childvolume && !childvolume->IsDestroyed())
|
|
{
|
|
volume = childvolume;
|
|
childvolume = NULL; // to kick us out of here
|
|
break;
|
|
}
|
|
}
|
|
} // while childvolume
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
curEntity = curEntity->GetParentEntity();
|
|
}
|
|
target_collision_volume = volume;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Missile::~Missile()
|
|
{
|
|
DESTRUCTOR("Missile");
|
|
Check_Object(this);
|
|
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::Reuse(
|
|
const CreateMessage *message,
|
|
ReplicatorID *base_id
|
|
)
|
|
{
|
|
Check_Pointer(this);
|
|
Check_Object(message);
|
|
|
|
BaseClass::Reuse(message, base_id);
|
|
|
|
targetEntity.Remove();
|
|
|
|
targetOffsetFromEntity.x = 0.0f;
|
|
targetOffsetFromEntity.y = 0.0f;
|
|
targetOffsetFromEntity.z = 0.0f;
|
|
|
|
targetPoint = message->targetPoint;
|
|
if(message->targetID != ReplicatorID::Null)
|
|
{
|
|
Check_Object(Network::GetInstance());
|
|
Connection *connection = Network::GetInstance()->GetConnection(message->targetID.connectionID);
|
|
if (connection)
|
|
{
|
|
Check_Object(connection);
|
|
|
|
Replicator *replicator;
|
|
replicator = connection->FindReplicator(message->targetID);
|
|
|
|
if (replicator)
|
|
{
|
|
Check_Object(replicator);
|
|
targetEntity.Remove();
|
|
targetEntity.Add(Cast_Object(Entity*, replicator));
|
|
|
|
targetMWObject.Remove();
|
|
|
|
//
|
|
// This won't account for rotation. I'm not sure how well this will work on a component that can rotate, since I
|
|
// don't have a full transform to get to this point.
|
|
//
|
|
// only in the case of a locked entity, is targetPoint relative to the entity
|
|
targetOffsetFromEntity = targetPoint;
|
|
|
|
|
|
Stuff::Point3D tempPoint3D = targetPoint;
|
|
targetPoint.Multiply(tempPoint3D, targetEntity.GetCurrent()->GetLocalToWorld());
|
|
|
|
Entity * parentEntity = TraceToParent(targetEntity.GetCurrent());
|
|
|
|
targetMWObject.Remove();
|
|
if(parentEntity && parentEntity->IsDerivedFrom(MWObject::DefaultData))
|
|
targetMWObject.Add(Cast_Object(MWObject*, parentEntity));
|
|
}
|
|
}
|
|
}
|
|
|
|
targetPosition = targetPoint;
|
|
timeInAir = 0.0f;
|
|
|
|
m_fAllowArcMissile = true;
|
|
m_fTurnOffSeek = false;
|
|
m_nMissCount = 0;
|
|
|
|
srmPitchVector.x = 0.0f;
|
|
srmPitchVector.y = -0.6f;
|
|
srmPitchVector.z = 0.8f;
|
|
|
|
srmYTargetOffset = 0.0f;
|
|
|
|
//
|
|
// We are going to store this to fix the odd angles when frame rate dips
|
|
//
|
|
lastWorldSpaceVelocity = initialWorldSpaceVelocity;
|
|
m_nDoubleTeleportPreventor = 0;
|
|
GetTargetCollisionVolume();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::PreCollisionExecute(Stuff::Time till)
|
|
{
|
|
Check_Object(this);
|
|
PRECOLLISION_LOGIC("Missile");
|
|
|
|
timeInAir += GetTimeSlice(till);
|
|
const GameModel *model = GetGameModel();
|
|
Check_Object(model);
|
|
|
|
//
|
|
//----------------------------------------------------
|
|
// Make sure we use post collision and call our parent
|
|
//----------------------------------------------------
|
|
//
|
|
Set_Statistic(Executed_Missile_Count, Executed_Missile_Count+1);
|
|
BaseClass::PreCollisionExecute(till);
|
|
|
|
//
|
|
//-------------------------------
|
|
// Apply the right type of forces
|
|
//-------------------------------
|
|
//
|
|
Check_Object(executionState);
|
|
int execution_state = executionState->GetState();
|
|
switch(execution_state)
|
|
{
|
|
case ExecutionStateEngine::LRMMotionState:
|
|
{
|
|
LRMSeekerModel(till);
|
|
SeekingThrusterSimulation(till);
|
|
LinearDragMotionSimulation(till);
|
|
ProximityCheck(till);
|
|
break;
|
|
}
|
|
case ExecutionStateEngine::MRMMotionState:
|
|
{
|
|
MRMSeekerModel(till);
|
|
SeekingThrusterSimulation(till);
|
|
LinearDragMotionSimulation(till);
|
|
break;
|
|
}
|
|
case ExecutionStateEngine::SRMMotionState:
|
|
{
|
|
SRMSeekerModel(till);
|
|
//
|
|
// SRM's used to work like bullets with a fixed speed out of the gun. To make them behave more consistenly
|
|
// I am using the same code path as the others even though it is a little bit of overkill for their seek model.
|
|
//
|
|
SeekingThrusterSimulation(till);
|
|
LinearDragMotionSimulation(till);
|
|
break;
|
|
}
|
|
case ExecutionStateEngine::SSRMMotionState:
|
|
{
|
|
SSRMSeekerModel(till);
|
|
SeekingThrusterSimulation(till);
|
|
LinearDragMotionSimulation(till);
|
|
break;
|
|
}
|
|
case ExecutionStateEngine::LongTomMotionState:
|
|
{
|
|
LongTomSeekerModel();
|
|
SeekingThrusterSimulation(till);
|
|
LinearDragMotionSimulation(till);
|
|
break;
|
|
}
|
|
case ExecutionStateEngine::BombMotionState:
|
|
{
|
|
if(timeInAir >= model->maxLiveTime)
|
|
{
|
|
SentenceToDeathRow();
|
|
return;
|
|
}
|
|
SRMSeekerModel(till);
|
|
//
|
|
// SRM's used to work like bullets with a fixed speed out of the gun. To make them behave more consistenly
|
|
// I am using the same code path as the others even though it is a little bit of overkill for their seek model.
|
|
//
|
|
SeekingThrusterSimulation(till);
|
|
LinearDragMotionSimulation(till);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
// STOP(("This is not a valid state for Missile"));
|
|
}
|
|
}
|
|
//
|
|
// If the missile is not a seeking missile or not locked, let it seek one frame to get direction
|
|
// then stop seeking from the next frame on. Otherwise when it gets where it was
|
|
// going, even non-locked, it will turn around and try to come back
|
|
//
|
|
if (!m_fTurnOffSeek && !targetEntity.GetCurrent() && Small_Enough(targetPoint.y - targetPosition.y))
|
|
{
|
|
Point3D local_direction;
|
|
Point3D local_to_world_position;
|
|
local_to_world_position = GetLocalToWorld();
|
|
local_direction.Subtract(
|
|
//targetPosition,
|
|
targetPoint,
|
|
local_to_world_position
|
|
);
|
|
|
|
UnitVector3D direction_to_target;
|
|
Stuff::Scalar direction_to_target_length = local_direction.GetLengthSquared();
|
|
if (Small_Enough(direction_to_target_length))
|
|
{
|
|
goto ExitFunction;
|
|
}
|
|
|
|
//
|
|
// Normalize
|
|
//
|
|
direction_to_target_length = 1.0f / Sqrt(direction_to_target_length);
|
|
direction_to_target.x = local_direction.x * direction_to_target_length;
|
|
direction_to_target.y = local_direction.y * direction_to_target_length;
|
|
direction_to_target.z = local_direction.z * direction_to_target_length;
|
|
|
|
Stuff::Scalar differenceTargetAngleToPreviousVelocity;
|
|
|
|
UnitVector3D current_direction;
|
|
Stuff::Scalar current_direction_length = worldSpaceVelocity.linearMotion.GetLengthSquared();
|
|
if (Small_Enough(current_direction_length))
|
|
{
|
|
goto ExitFunction;
|
|
}
|
|
|
|
//
|
|
// Normalize
|
|
//
|
|
current_direction_length = 1.0f / Sqrt(current_direction_length);
|
|
current_direction.x = worldSpaceVelocity.linearMotion.x * current_direction_length;
|
|
current_direction.y = worldSpaceVelocity.linearMotion.y * current_direction_length;
|
|
current_direction.z = worldSpaceVelocity.linearMotion.z * current_direction_length;
|
|
|
|
|
|
differenceTargetAngleToPreviousVelocity = direction_to_target * current_direction;
|
|
|
|
|
|
// If we are aimed close enough the target, and we are physically close enough, then just assume we are at the target
|
|
|
|
if (differenceTargetAngleToPreviousVelocity >= 0.999f)
|
|
{
|
|
m_fTurnOffSeek = true;
|
|
//
|
|
// Aim right at the spot now
|
|
//
|
|
Point3D local_direction;
|
|
Point3D local_to_world_position;
|
|
local_to_world_position = GetLocalToWorld();
|
|
local_direction.Subtract(
|
|
targetPosition,
|
|
local_to_world_position
|
|
);
|
|
LinearMatrix4D aligned_matrix = entityElement->GetNewLocalToParent();
|
|
aligned_matrix.AlignLocalAxisToWorldVector(local_direction, Z_Axis, X_Axis, Y_Axis);
|
|
SetNewLocalToParent(aligned_matrix);
|
|
|
|
|
|
Stuff::Scalar linearMotionSpeed = localSpaceVelocity.linearMotion.GetApproximateLength();
|
|
localSpaceVelocity.angularMotion.Zero();
|
|
localSpaceVelocity.linearMotion.z = linearMotionSpeed;
|
|
localSpaceVelocity.linearMotion.y = 0.0f;
|
|
localSpaceVelocity.linearMotion.x = 0.0f;
|
|
// reuse for acceleration
|
|
linearMotionSpeed = localSpaceAcceleration.linearMotion.GetApproximateLength();
|
|
localSpaceAcceleration.linearMotion.z = linearMotionSpeed;
|
|
localSpaceAcceleration.linearMotion.y = 0.0f;
|
|
localSpaceAcceleration.linearMotion.x = 0.0f;
|
|
|
|
|
|
|
|
|
|
// localSpaceVelocity.angularMotion.Zero(); // make sure we don't end up in a spin when we stop orienting.
|
|
/*
|
|
//
|
|
// In order to preserve it's current acceleration, but make sure it hits the target we need to
|
|
// find the normalized direction that we want to go (
|
|
//
|
|
Stuff::Scalar current_acceleration_length = worldSpaceAcceleration.linearMotion.GetApproximateLength();
|
|
worldSpaceAcceleration.linearMotion.Multiply(current_direction, current_acceleration_length);
|
|
|
|
current_acceleration_length = localSpaceAcceleration.linearMotion.GetApproximateLength();
|
|
|
|
|
|
localSpaceAcceleration.linearMotion.Multiply(current_direction, current_acceleration_length);
|
|
|
|
*/
|
|
//
|
|
// We need to change the target point to the extreme distance of the current direction, then we can
|
|
// still seek as far as the thruster model is concerned. If we don't do this, then the missiles
|
|
// will be affected by torque and have problems hitting the target.
|
|
//
|
|
Stuff::Point3D current_location;
|
|
current_location = GetLocalToWorld();
|
|
Stuff::Line3D target_line(current_location, direction_to_target, 1000.0f);
|
|
target_line.FindEnd(&targetPoint);
|
|
targetPosition = targetPoint;
|
|
|
|
// localSpaceAcceleration.angularMotion.Zero();
|
|
// worldSpaceVelocity.angularMotion.Zero(); // make sure we don't end up in a spin when we stop orienting.
|
|
// worldSpaceAcceleration.angularMotion.Zero();
|
|
}
|
|
}
|
|
ExitFunction:
|
|
;
|
|
}
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::LinearDragMotionSimulation(Stuff::Time till)
|
|
{
|
|
//
|
|
// Before doing the real math, see if we have any easy outs...
|
|
//
|
|
if (m_fTurnOffSeek || timeInAir >= burnTime)
|
|
{
|
|
BaseClass::LinearDragMotionSimulation(till);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Has the last slice of time been greater than the time it would take to turn to that angle?
|
|
// If so, just aim it at the target;
|
|
|
|
Point3D local_direction;
|
|
Point3D local_to_world_position;
|
|
local_to_world_position = GetLocalToWorld();
|
|
local_direction.Subtract(
|
|
targetPosition,
|
|
local_to_world_position
|
|
);
|
|
|
|
UnitVector3D new_direction;
|
|
Stuff::Scalar new_direction_length = local_direction.GetLengthSquared();
|
|
if (Small_Enough(new_direction_length))
|
|
{
|
|
BaseClass::LinearDragMotionSimulation(till);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Normalize
|
|
//
|
|
new_direction_length = 1.0f / Sqrt(new_direction_length);
|
|
new_direction.x = local_direction.x * new_direction_length;
|
|
new_direction.y = local_direction.y * new_direction_length;
|
|
new_direction.z = local_direction.z * new_direction_length;
|
|
|
|
Stuff::Scalar differenceTargetAngleToPreviousVelocity;
|
|
|
|
UnitVector3D previousDirection;
|
|
Stuff::Scalar previousDirection_length = worldSpaceVelocity.linearMotion.GetLengthSquared();
|
|
if (Small_Enough(previousDirection_length))
|
|
{
|
|
BaseClass::LinearDragMotionSimulation(till);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Normalize
|
|
//
|
|
previousDirection_length = 1.0f / Sqrt(previousDirection_length);
|
|
previousDirection.x = worldSpaceVelocity.linearMotion.x * previousDirection_length;
|
|
previousDirection.y = worldSpaceVelocity.linearMotion.y * previousDirection_length;
|
|
previousDirection.z = worldSpaceVelocity.linearMotion.z * previousDirection_length;
|
|
|
|
|
|
differenceTargetAngleToPreviousVelocity = new_direction * previousDirection;
|
|
|
|
|
|
// If we are aimed close enough the target, and we are physically close enough, then just assume we are at the target
|
|
|
|
lastWorldSpaceVelocity = worldSpaceVelocity;
|
|
BaseClass::LinearDragMotionSimulation(till);
|
|
|
|
UnitVector3D currentDirection;
|
|
Stuff::Scalar currentDirection_length = worldSpaceVelocity.linearMotion.GetLengthSquared();
|
|
if (Small_Enough(currentDirection_length))
|
|
return;
|
|
|
|
//
|
|
// Normalize
|
|
//
|
|
currentDirection_length = 1.0f / Sqrt(currentDirection_length);
|
|
currentDirection.x = worldSpaceVelocity.linearMotion.x * currentDirection_length;
|
|
currentDirection.y = worldSpaceVelocity.linearMotion.y * currentDirection_length;
|
|
currentDirection.z = worldSpaceVelocity.linearMotion.z * currentDirection_length;
|
|
|
|
Stuff::Scalar differencePreviousVelocityToCurrentVelocity;
|
|
differencePreviousVelocityToCurrentVelocity = previousDirection * currentDirection;
|
|
|
|
|
|
if (differencePreviousVelocityToCurrentVelocity < differenceTargetAngleToPreviousVelocity)
|
|
{
|
|
|
|
//
|
|
// If this is true then the missile is oversteering. What this translates to is that the missile
|
|
// turned more than was necessary last frame to end up pointing at the target. This is easy to do
|
|
// on slower frame rates since the momentum carries them past their target during the long break between
|
|
// frames. In order to compensate for this we use the following logic:
|
|
// if (missile turned passed target last frame)
|
|
// point missile at the target
|
|
// kill the angular motion and acceleration, so it stays pointing at it for the next frame.
|
|
//
|
|
//
|
|
// Some variables already defined at this point:
|
|
// new_direction - this is the normalized world space direction to the target prior to
|
|
// simulating the linear drag/propulsion calculations
|
|
// currentDirection - this is the direction the missile is moving in after the simulation
|
|
|
|
// cheating since the missiles parent should be the world, we aren't multiplying by it's parent
|
|
local_to_world_position = GetLocalToWorld();
|
|
local_direction.Subtract(
|
|
targetPosition,
|
|
local_to_world_position
|
|
);
|
|
LinearMatrix4D aligned_matrix = entityElement->GetNewLocalToParent();
|
|
aligned_matrix.AlignLocalAxisToWorldVector(local_direction, Z_Axis, X_Axis, Y_Axis);
|
|
SetNewLocalToParent(aligned_matrix);
|
|
|
|
|
|
Stuff::Scalar linearMotionSpeed = localSpaceVelocity.linearMotion.GetApproximateLength();
|
|
localSpaceVelocity.angularMotion.Zero();
|
|
localSpaceVelocity.linearMotion.z = linearMotionSpeed;
|
|
localSpaceVelocity.linearMotion.y = 0.0f;
|
|
localSpaceVelocity.linearMotion.x = 0.0f;
|
|
// reuse for acceleration
|
|
linearMotionSpeed = localSpaceAcceleration.linearMotion.GetApproximateLength();
|
|
localSpaceAcceleration.linearMotion.z = linearMotionSpeed;
|
|
localSpaceAcceleration.linearMotion.y = 0.0f;
|
|
localSpaceAcceleration.linearMotion.x = 0.0f;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// new_direction is in world space
|
|
//
|
|
|
|
|
|
/* Stuff::Point3D p1, p2;
|
|
p1 = new_direction;
|
|
p2.MultiplyByInverse(p1, entityElement->GetNewLocalToParent());
|
|
// Stuff::Point3D p2;
|
|
// p2 = currentDirection;
|
|
// Stuff::Point3D p3;
|
|
// p3.Subtract(p1, p2);
|
|
// LinearMatrix4D d;
|
|
// d.BuildRotation(p3);
|
|
// d.BuildTranslation(Point3D::Identity);
|
|
Stuff::LinearMatrix4D new_local_to_parent;
|
|
|
|
// new_local_to_parent.Multiply(entityElement->GetNewLocalToParent(), d);
|
|
// p1 = entityElement->GetNewLocalToParent();
|
|
// new_local_to_parent.BuildTranslation(p1);
|
|
// new_local_to_parent.Normalize();
|
|
|
|
new_local_to_parent.BuildRotation(p2);
|
|
p1 = entityElement->GetNewLocalToParent();
|
|
new_local_to_parent.BuildTranslation(p1);
|
|
SetNewLocalToParent(new_local_to_parent);
|
|
// new_local_to_parent.BuildTranslation(new_translation);
|
|
|
|
|
|
/*
|
|
localSpaceAcceleration.angularMotion.Zero();
|
|
worldSpaceAcceleration.angularMotion.Zero();
|
|
localSpaceAcceleration.linearMotion.x = 0.0f;
|
|
localSpaceAcceleration.linearMotion.y = 0.0f;
|
|
*/
|
|
//
|
|
// In order to preserve it's current acceleration, but make sure it hits the target we need to
|
|
// find the normalized direction that we want to go (
|
|
//
|
|
/*
|
|
Stuff::Scalar current_acceleration_length = worldSpaceAcceleration.linearMotion.GetApproximateLength();
|
|
worldSpaceAcceleration.linearMotion.Multiply(new_direction, current_acceleration_length);
|
|
|
|
current_acceleration_length = localSpaceAcceleration.linearMotion.GetApproximateLength();
|
|
|
|
|
|
localSpaceAcceleration.linearMotion.Multiply(new_direction, current_acceleration_length);
|
|
*/
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::ProximityCheck(Stuff::Time till)
|
|
{
|
|
const GameModel *model = GetGameModel();
|
|
Point3D targetDistance;
|
|
Point3D local_to_parent_position;
|
|
|
|
Entity *target_entity = targetEntity.GetCurrent();
|
|
|
|
|
|
Point3D targetLocalToOurParent;
|
|
if (!GetParentEntity())
|
|
return;
|
|
//
|
|
// To avoid missiles chasing dead things we need to make sure we cut the gas when this is detected
|
|
//
|
|
// make sure we burn a little at least (.5)
|
|
|
|
if (target_entity && target_entity->IsDestroyed() && timeInAir < burnTime/* && timeInAir > 0.5f*/)
|
|
{
|
|
m_nMissCount++; // this makes sure we run for at least a couple of frames to orient the missile correctly
|
|
|
|
|
|
// This should no longer be needed since we'll just turn off seeking
|
|
// timeInAir = model->burnTime;
|
|
}
|
|
|
|
if(timeInAir >= burnTime)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Don't do
|
|
//
|
|
|
|
if (!target_entity || (target_entity->IsDerivedFrom(Map::DefaultData)) || m_fTurnOffSeek)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
LinearMatrix4D world_to_entity = LinearMatrix4D::Identity;
|
|
world_to_entity.Invert(target_entity->GetLocalToWorld());
|
|
ElementRenderer::Element *element = target_entity->GetElement();
|
|
Check_Object(element);
|
|
LinearMatrix4D world_to_new_world = LinearMatrix4D::Identity;
|
|
world_to_new_world.Multiply(world_to_entity, element->GetNewLocalToParent());
|
|
LinearMatrix4D targetPositionMatrix = LinearMatrix4D::Identity;
|
|
// world_to_entity being reused as a temp.
|
|
targetPositionMatrix.BuildTranslation(targetPosition);
|
|
world_to_entity.Multiply(targetPositionMatrix, world_to_new_world);
|
|
targetPosition = world_to_entity;
|
|
|
|
|
|
|
|
|
|
targetLocalToOurParent.MultiplyByInverse(targetPosition, GetParentEntity()->GetLocalToWorld());
|
|
|
|
|
|
|
|
local_to_parent_position = GetLocalToParent();
|
|
targetDistance.Subtract(
|
|
targetLocalToOurParent,
|
|
local_to_parent_position
|
|
);
|
|
|
|
|
|
|
|
Stuff::Scalar distanceSquared = targetDistance.GetLengthSquared();
|
|
if ((m_nDoubleTeleportPreventor < 5) && target_entity && (target_entity->IsDerivedFrom(MWObject::DefaultData)) &&
|
|
(distanceSquared < model->proximityFuseDistance))
|
|
{
|
|
/*
|
|
|
|
// Since it's going to die, we don't need to know about the orientation
|
|
//
|
|
// Can I call the CollisionHandler directly from here?
|
|
Entity::CollisionData collision_data;
|
|
|
|
UnitVector3D normal;
|
|
local_to_world_position.Negate(targetDistance); // reusing local_to_world_position as temp
|
|
normal.Normalize(local_to_world_position);
|
|
collision_data.m_normal = normal;
|
|
collision_data.m_otherEntity = target_entity;
|
|
collision_data.m_otherVolume = NULL;
|
|
collision_data.m_volume = NULL;
|
|
collision_data.m_material = collision_query.m_material;
|
|
collision_data.m_timeSlice =
|
|
(line.m_length * t) * collider->GetTimeSlice(till);
|
|
line.FindEnd(&collision_data.m_worldIntersectionPoint);
|
|
DynamicArrayOf<Entity::CollisionData> *array = new DynamicArrayOf<Entity::CollisionData>(1);
|
|
(*array)[0] = collision_data;
|
|
|
|
|
|
|
|
PostCollision();
|
|
SentenceToDeathRow();
|
|
|
|
*/
|
|
|
|
//
|
|
// If due to a data error (collision volume that doesn't have geometry in center) or some other mishap, we need to prevent
|
|
// the missile from continually teleporting.
|
|
//
|
|
//
|
|
m_nDoubleTeleportPreventor++;
|
|
|
|
LinearMatrix4D targetDirectionLocalToWorld = LinearMatrix4D::Identity;
|
|
|
|
//
|
|
// Try going just a little bit through the point we are trying to hit to maximize collision chances
|
|
//
|
|
targetDirectionLocalToWorld.BuildTranslation(targetLocalToOurParent);
|
|
targetDirectionLocalToWorld.AlignLocalAxisToWorldVector(targetDistance, Z_Axis, Y_Axis, X_Axis);
|
|
SetNewLocalToParent(targetDirectionLocalToWorld);
|
|
//
|
|
// We have to immediately sync, or we have problems with splash damage location.
|
|
// ** Seeing if I can do this as part of the damage handler instead
|
|
// SyncMatrices(true);
|
|
}
|
|
else if (m_nDoubleTeleportPreventor >= 5)
|
|
{
|
|
//
|
|
// Let it get out of range before it tries to teleport again.
|
|
//
|
|
if (distanceSquared > (model->proximityFuseDistance + 1.0f))
|
|
m_nDoubleTeleportPreventor = 0;
|
|
m_nMissCount++;
|
|
|
|
}
|
|
|
|
|
|
}
|
|
/*
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::ProximityCheck(Stuff::Time till)
|
|
{
|
|
const GameModel *model = GetGameModel();
|
|
Point3D targetDistance;
|
|
Point3D local_to_parent_position;
|
|
|
|
Point3D targetLocalToOurParent;
|
|
if (!GetParentEntity())
|
|
return;
|
|
targetLocalToOurParent.MultiplyByInverse(targetPosition, GetParentEntity()->GetLocalToWorld());
|
|
|
|
|
|
|
|
|
|
local_to_parent_position = GetLocalToParent();
|
|
targetDistance.Subtract(
|
|
targetLocalToOurParent,
|
|
local_to_parent_position
|
|
);
|
|
|
|
|
|
|
|
Entity *target_entity = targetEntity.GetCurrent();
|
|
Stuff::Scalar distanceSquared = targetDistance.GetLengthSquared();
|
|
if (target_entity && (target_entity->IsDerivedFrom(MWObject::DefaultData)) &&
|
|
(distanceSquared < model->proximityFuseDistance))
|
|
{
|
|
|
|
LinearMatrix4D targetDirectionLocalToWorld = LinearMatrix4D::Identity;
|
|
targetDirectionLocalToWorld.BuildTranslation(targetLocalToOurParent);
|
|
|
|
// Since it's going to die, we don't need to know about the orientation
|
|
//
|
|
// Can I call the CollisionHandler directly from here?
|
|
Entity::CollisionData collision_data;
|
|
|
|
UnitVector3D normal;
|
|
|
|
|
|
|
|
Stuff::Scalar new_direction_length_inverse_sqrt = targetDistance.GetLengthSquared();
|
|
Stuff::Scalar new_direction_length = Sqrt(new_direction_length_inverse_sqrt);
|
|
if (Small_Enough(new_direction_length))
|
|
{
|
|
normal = UnitVector3D::Identity;
|
|
//
|
|
// Keep our current direction in this case
|
|
//
|
|
targetDirectionLocalToWorld.BuildRotation(worldSpaceVelocity.linearMotion);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Normalize
|
|
//
|
|
new_direction_length_inverse_sqrt = 1.0f / Sqrt(new_direction_length);
|
|
normal.x = -targetDistance.x * new_direction_length_inverse_sqrt;
|
|
normal.y = -targetDistance.y * new_direction_length_inverse_sqrt;
|
|
normal.z = -targetDistance.z * new_direction_length_inverse_sqrt;
|
|
//
|
|
// If we have a true line, align our direction to it
|
|
//
|
|
targetDirectionLocalToWorld.AlignLocalAxisToWorldVector(targetDistance, Z_Axis, Y_Axis, X_Axis);
|
|
}
|
|
//
|
|
// We now have our position, and possibly our rotation so commit it.
|
|
//
|
|
SetNewLocalToParent(targetDirectionLocalToWorld);
|
|
|
|
collision_data.m_normal = normal;
|
|
if (target_collision_volume && !target_collision_volume->IsDestroyed())
|
|
{
|
|
collision_data.m_otherVolume = target_collision_volume;
|
|
collision_data.m_material = target_collision_volume->m_material;
|
|
}
|
|
else
|
|
{
|
|
collision_data.m_otherVolume = NULL;
|
|
collision_data.m_material = MechWarrior4::MaterialCount;
|
|
}
|
|
collision_data.m_otherEntity = target_entity;
|
|
collision_data.m_volume = NULL;
|
|
|
|
collision_data.m_timeSlice = GetTimeSlice(till);
|
|
|
|
collision_data.m_worldIntersectionPoint = targetPosition;
|
|
|
|
DynamicArrayOf<Entity::CollisionData> *array = new DynamicArrayOf<Entity::CollisionData>(1);
|
|
(*array)[0] = collision_data;
|
|
|
|
|
|
|
|
PostCollision(array);
|
|
SentenceToDeathRow();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
*/
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
bool
|
|
Missile::CollisionHandler(
|
|
Stuff::LinearMatrix4D *new_position,
|
|
Stuff::DynamicArrayOf<CollisionData> *collisions
|
|
)
|
|
{
|
|
Check_Object(this);
|
|
Check_Object(collisions);
|
|
Verify(collisions->GetLength() == 1);
|
|
Verify(EntityManager::GetInstance()->IsInPostCollisionExecution(this));
|
|
|
|
return BaseClass::CollisionHandler(new_position, collisions);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::SeekingThrusterSimulation(Stuff::Time till)
|
|
{
|
|
Check_Object(this);
|
|
const GameModel *model = GetGameModel();
|
|
Check_Object(model);
|
|
|
|
#if defined(MISSILE_BUG)
|
|
SPEW((MISSILE_BUG, "Position: %f %f %f",
|
|
GetLocalToWorld()(3,0), GetLocalToWorld()(3,1), GetLocalToWorld()(3,2)));
|
|
|
|
SPEW((MISSILE_BUG, "LocalSpaceVelocityLinearMotion: %f %f %f",
|
|
localSpaceVelocity.linearMotion.x, localSpaceVelocity.linearMotion.y,
|
|
localSpaceVelocity.linearMotion.z));
|
|
|
|
SPEW((MISSILE_BUG, "LocalSpaceVelocityAngularMotion: %f %f %f",
|
|
localSpaceVelocity.angularMotion.x, localSpaceVelocity.angularMotion.y,
|
|
localSpaceVelocity.angularMotion.z));
|
|
#endif
|
|
|
|
#if defined(MISSILE_BUG)
|
|
SPEW((MISSILE_BUG, "TargetPosition: %f %f %f",
|
|
targetPosition.x, targetPosition.y, targetPosition.z));
|
|
#endif
|
|
|
|
#if defined(TARGET_BUG)
|
|
SPEW((TARGET_BUG, "TargetPosition: %f %f %f",
|
|
targetPosition.x, targetPosition.y, targetPosition.z));
|
|
#endif
|
|
|
|
if(timeInAir <= burnTime)
|
|
{
|
|
UnitVector3D new_direction;
|
|
//
|
|
//-----------------------------
|
|
//Find direction in local space
|
|
//-----------------------------
|
|
//
|
|
Stuff::Point3D local_direction;
|
|
Stuff::Point3D temp_target_location;
|
|
temp_target_location = targetPosition;
|
|
// Stuff::Scalar timeSlice = GetTimeParameter(till);
|
|
|
|
// local_direction.MultiplyByInverse(
|
|
// temp_target_location,
|
|
// GetLocalToWorld()
|
|
// );
|
|
//new_direction = UnitVector3D::Identity;
|
|
// local_direction.y = 0;
|
|
// Stuff::Scalar new_direction_length = local_direction.GetLengthSquared();
|
|
//
|
|
// Gravity isn't supposedly acting on these objects but they seem to draw up short.
|
|
//
|
|
// Stuff::Scalar gravity_compensation_factor = new_direction_length;
|
|
// Max_Clamp(gravity_compensation_factor, 2.0f);
|
|
// temp_target_location.y += (g_Gravity * timeSlice * timeSlice) * gravity_compensation_factor;
|
|
|
|
//
|
|
// Re-get the direction since we are shifting the target position
|
|
//
|
|
local_direction.MultiplyByInverse(
|
|
temp_target_location,
|
|
GetLocalToWorld()
|
|
);
|
|
|
|
#if defined(MISSILE_BUG)
|
|
SPEW((MISSILE_BUG, "TargetPositionInLocalSpace: %f %f %f",
|
|
local_direction.x, local_direction.y, local_direction.z));
|
|
#endif
|
|
|
|
Stuff::Scalar new_direction_length = local_direction.GetLengthSquared();
|
|
Vector3D local_steer_direction;
|
|
|
|
// if (!m_fTurnOffSeek)
|
|
// {
|
|
if (Small_Enough(new_direction_length))
|
|
{
|
|
new_direction = Vector3D::Forward;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Normalize
|
|
//
|
|
new_direction_length = 1.0f / Sqrt(new_direction_length);
|
|
new_direction.x = local_direction.x * new_direction_length;
|
|
new_direction.y = local_direction.y * new_direction_length;
|
|
new_direction.z = local_direction.z * new_direction_length;
|
|
}
|
|
//
|
|
//-------------------------------------------
|
|
//Find the direction that we want to steer to
|
|
//-------------------------------------------
|
|
//
|
|
local_steer_direction.Cross(
|
|
Vector3D::Forward,
|
|
new_direction
|
|
);
|
|
|
|
//
|
|
//----------------------------------
|
|
//We need to find the degree to turn
|
|
//----------------------------------
|
|
//
|
|
Scalar degree_to_turn;
|
|
Scalar sine = 0.0f;
|
|
|
|
if (!Small_Enough(local_steer_direction))
|
|
{
|
|
sine = local_steer_direction.GetLength();
|
|
}
|
|
|
|
if(Small_Enough(sine))
|
|
{
|
|
degree_to_turn = 0.0f;
|
|
local_steer_direction = Vector3D::Identity;
|
|
}
|
|
else
|
|
{
|
|
if(local_steer_direction.y < 0.0f)
|
|
{
|
|
sine = -sine;
|
|
}
|
|
degree_to_turn =
|
|
Arctan(sine, new_direction.z);
|
|
|
|
#if defined(MISSILE_BUG)
|
|
SPEW((MISSILE_BUG, "RadianToTurn: %f", degree_to_turn));
|
|
#endif
|
|
|
|
//
|
|
// We need to clamp the turn, but it has to be clamped based on the time slice, it can't just be clamped a fixed amount per frame
|
|
// or the slower frame rates won't be able to turn well.
|
|
//
|
|
|
|
Stuff::Scalar timeSlice = GetTimeParameter(till);
|
|
Scalar max_angle = model->maxTurnAngle * timeSlice ;
|
|
|
|
Clamp(degree_to_turn, -max_angle, max_angle);
|
|
#if defined(MISSILE_BUG)
|
|
SPEW((MISSILE_BUG, "ClampedRadianToTurn: %f", degree_to_turn));
|
|
#endif
|
|
degree_to_turn /= sine;
|
|
local_steer_direction *= degree_to_turn;
|
|
}
|
|
|
|
#if defined(MISSILE_BUG)
|
|
SPEW((MISSILE_BUG, "AxisOfRotation: %f %f %f",
|
|
local_steer_direction.x, local_steer_direction.y, local_steer_direction.z));
|
|
#endif
|
|
|
|
//
|
|
//-------------------------------------------
|
|
//Negate the z value of the turn direction to
|
|
//-------------------------------------------
|
|
//
|
|
LinearMatrix4D force_to_parent = LinearMatrix4D::Identity;
|
|
force_to_parent.BuildRotation(local_steer_direction);
|
|
UnitVector3D force_forward;
|
|
force_to_parent.GetLocalForwardInWorld(&force_forward);
|
|
|
|
force_forward.x = -force_forward.x;
|
|
force_forward.y = -force_forward.y;
|
|
|
|
|
|
// force_forward.z = -force_forward.z;
|
|
local_steer_direction.Multiply(
|
|
force_forward,
|
|
model->thrusterAcceleration
|
|
);
|
|
|
|
#if defined(MISSILE_BUG)
|
|
SPEW((MISSILE_BUG, "LocalForce: %f %f %f\n",
|
|
local_steer_direction.x, local_steer_direction.y, local_steer_direction.z));
|
|
#endif
|
|
|
|
ApplyLocalAcceleration(
|
|
local_steer_direction,
|
|
Vector3D::Backward
|
|
);
|
|
|
|
//
|
|
// If Dot Product of the angles between current direction and the direction to target
|
|
// are small enough, then kill the angular motion. It's a cheat to prevent over
|
|
// correction.
|
|
//
|
|
|
|
Stuff::Scalar differenceTargetAngleToActualAngle;
|
|
|
|
local_direction.MultiplyByInverse(
|
|
temp_target_location,
|
|
GetLocalToWorld());
|
|
|
|
new_direction_length = local_direction.GetLengthSquared();
|
|
if (Small_Enough(new_direction_length))
|
|
return;
|
|
|
|
//
|
|
// Normalize
|
|
//
|
|
new_direction_length = 1.0f / Sqrt(new_direction_length);
|
|
new_direction.x = local_direction.x * new_direction_length;
|
|
new_direction.y = local_direction.y * new_direction_length;
|
|
new_direction.z = local_direction.z * new_direction_length;
|
|
|
|
|
|
differenceTargetAngleToActualAngle = new_direction * UnitVector3D::Forward;
|
|
if ( differenceTargetAngleToActualAngle >= 0.985f)
|
|
{
|
|
|
|
localSpaceVelocity.angularMotion.Zero();
|
|
localSpaceAcceleration.angularMotion.Zero();
|
|
Stuff::Scalar speed = localSpaceVelocity.linearMotion.GetApproximateLength();
|
|
localSpaceVelocity.linearMotion.Multiply(new_direction, speed);
|
|
|
|
/*
|
|
LinearMatrix4D targetDirectionLocalToWorld;
|
|
targetDirectionLocalToWorld.BuildRotation(new_direction);
|
|
LinearMatrix4D localToParent;
|
|
LinearMatrix4D oldLocalToWorld;
|
|
oldLocalToWorld = parentEntity->GetLocalToWorld();
|
|
localToParent.Multiply(targetDirectionLocalToWorld, oldLocalToWorld.Invert());
|
|
SetNewLocalToParent(localToParent);
|
|
*/
|
|
}
|
|
// }
|
|
/* else
|
|
{
|
|
Stuff::UnitVector3D current_direction;
|
|
GetLocalToWorld().GetLocalForwardInWorld(¤t_direction);
|
|
|
|
/*
|
|
Stuff::Scalar new_direction_length = current_direction.GetLengthSquared();
|
|
if (Small_Enough(new_direction_length))
|
|
return;
|
|
|
|
//
|
|
// Normalize
|
|
//
|
|
new_direction_length = 1.0f / Sqrt(new_direction_length);
|
|
new_direction.x = current_direction.x * new_direction_length;
|
|
new_direction.y = current_direction.y * new_direction_length;
|
|
new_direction.z = current_direction.z * new_direction_length;
|
|
// * /
|
|
current_direction.x = -current_direction.x;
|
|
current_direction.y = -current_direction.y;
|
|
|
|
local_steer_direction.Multiply(
|
|
current_direction,
|
|
model->thrusterAcceleration
|
|
);
|
|
|
|
localSpaceAcceleration.linearMotion += local_steer_direction;
|
|
|
|
ApplyLocalAcceleration(
|
|
local_steer_direction,
|
|
Vector3D::Backward
|
|
);
|
|
// Stuff::Scalar speed = localSpaceVelocity.linearMotion.GetApproximateLength();
|
|
// localSpaceVelocity.linearMotion.Multiply(new_direction, speed);
|
|
|
|
}
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
//
|
|
//------------------
|
|
//We are out of fuel
|
|
//------------------
|
|
//
|
|
#if defined(MISSILE_BUG)
|
|
SPEW((MISSILE_BUG, "We are out of fuel"));
|
|
#endif
|
|
|
|
localSpaceDrag = Motion3D::Identity;
|
|
|
|
UnitVector3D local_down;
|
|
GetLocalToWorld().GetWorldDownInLocal(&local_down);
|
|
localSpaceAcceleration.linearMotion.Multiply(
|
|
local_down,
|
|
g_Gravity
|
|
);
|
|
|
|
if(trailEffect.GetCurrent())
|
|
trailEffect.GetCurrent()->executionState->RequestState(Effect::ExecutionStateEngine::StoppingState);
|
|
|
|
#if defined(MISSILE_BUG)
|
|
|
|
UnitVector3D local_forward,local_up;
|
|
GetLocalToWorld().GetLocalForwardInWorld(&local_forward);
|
|
GetLocalToWorld().GetLocalUpInWorld(&local_up);
|
|
|
|
SPEW((MISSILE_BUG, "Local Down: %f %f %f", local_down.x, local_down.y, local_down.z));
|
|
|
|
SPEW((MISSILE_BUG, "Local Forward in Local: %f %f %f", local_forward.x,
|
|
local_forward.y, local_forward.z));
|
|
SPEW((MISSILE_BUG, "Local Up in Local: %f %f %f", local_up.x,
|
|
local_up.y, local_up.z));
|
|
SPEW((MISSILE_BUG, "WorldSpaceVelocityLinearMotion: %f %f %f\n",
|
|
worldSpaceVelocity.linearMotion.x, worldSpaceVelocity.linearMotion.y,
|
|
worldSpaceVelocity.linearMotion.z));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::PostCollisionExecute(Stuff::Time till)
|
|
{
|
|
Check_Object(this);
|
|
Verify(Close_Enough(GetLocalToParent(), GetLocalToWorld()));
|
|
POSTCOLLISION_LOGIC("Missile");
|
|
|
|
lastParameterization = till;
|
|
initialLocalToParent = GetLocalToParent();
|
|
initialWorldSpaceVelocity = worldSpaceVelocity,
|
|
initialWorldSpaceAcceleration = worldSpaceAcceleration;
|
|
|
|
if(doesAMSDestroyThisFrame)
|
|
{
|
|
CreateEffect(amsExplosionResource);
|
|
SentenceToDeathRow();
|
|
}
|
|
|
|
BaseClass::PostCollisionExecute(till);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::LRMSeekerModel(Stuff::Time till)
|
|
{
|
|
Check_Object(this);
|
|
|
|
if (m_fTurnOffSeek)
|
|
{
|
|
localSpaceVelocity.angularMotion.Zero();
|
|
localSpaceAcceleration.angularMotion.Zero();
|
|
return; // leave the current direction alone since we're done turning
|
|
}
|
|
|
|
//
|
|
// Don't continually circle, but just burn off fuel in the current direction instead.
|
|
//
|
|
if (m_nMissCount > 1)
|
|
{
|
|
m_fTurnOffSeek = true;
|
|
localSpaceVelocity.angularMotion.Zero();
|
|
localSpaceAcceleration.angularMotion.Zero();
|
|
return;
|
|
}
|
|
|
|
|
|
Point3D local_to_world;
|
|
local_to_world = GetLocalToWorld();
|
|
|
|
CalculateTargetPosition(&targetPosition, till);
|
|
targetPoint = targetPosition;
|
|
//
|
|
// Decide whether or not to make this missile arc in it's path.
|
|
//
|
|
Stuff::Scalar diffY = (targetPosition.y - local_to_world.y);
|
|
|
|
if (diffY <= 50.0f)
|
|
{
|
|
Vector3D local_direction;
|
|
local_direction.Subtract(
|
|
targetPosition,
|
|
local_to_world
|
|
);
|
|
|
|
Scalar true_distance = local_direction.GetApproximateLength();
|
|
local_direction.y = 0;
|
|
Scalar arc_value;
|
|
arc_value = local_direction.GetLength();
|
|
|
|
//
|
|
//------------------------------------------------------------
|
|
//Right here we need to tell the target how far we are from it
|
|
//------------------------------------------------------------
|
|
//
|
|
if((true_distance < 50.0f) && (DoesAMSDestroy()))
|
|
{
|
|
ConveyDistanceToTarget(true_distance);
|
|
}
|
|
|
|
|
|
arc_value -= 200.0f;
|
|
if(m_fAllowArcMissile && arc_value > 0.0f)
|
|
{
|
|
arc_value /= 2.0f; //reduced the angle since our curve code might get in the way.
|
|
Max_Clamp(arc_value, 150.0f);
|
|
targetPosition.y += ((1.0f / (timeInAir + 0.5f)) * arc_value);
|
|
// targetPosition.y += arc_value;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::MRMSeekerModel(Stuff::Time till)
|
|
{
|
|
Check_Object(this);
|
|
|
|
if (m_fTurnOffSeek)
|
|
{
|
|
localSpaceVelocity.angularMotion.Zero();
|
|
localSpaceAcceleration.angularMotion.Zero();
|
|
return; // leave the current direction alone since we're done turning
|
|
}
|
|
|
|
Point3D local_to_world;
|
|
local_to_world = GetLocalToWorld();
|
|
|
|
CalculateTargetPosition(&targetPosition, till);
|
|
|
|
if((targetPosition.y - local_to_world.y) <= 50.0)
|
|
{
|
|
Vector3D local_direction;
|
|
local_direction.Subtract(
|
|
targetPosition,
|
|
local_to_world
|
|
);
|
|
|
|
Scalar true_distance = local_direction.GetApproximateLength();
|
|
/*
|
|
local_direction.y = 0;
|
|
Scalar arc_value;
|
|
arc_value = local_direction.GetLength();
|
|
arc_value -= 140.0f;
|
|
*/
|
|
//
|
|
//------------------------------------------------------------
|
|
//Right here we need to tell the target how far we are from it
|
|
//------------------------------------------------------------
|
|
//
|
|
if((true_distance < 50.0f) && (DoesAMSDestroy()))
|
|
{
|
|
ConveyDistanceToTarget(true_distance);
|
|
}
|
|
|
|
/*
|
|
if(arc_value > 0.0f)
|
|
{
|
|
arc_value /= 3;
|
|
Max_Clamp(arc_value, 120.0f);
|
|
targetPosition.y += arc_value;
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::LongTomSeekerModel()
|
|
{
|
|
Check_Object(this);
|
|
|
|
Point3D local_to_world;
|
|
local_to_world = GetLocalToWorld();
|
|
|
|
if((targetPosition.y - local_to_world.y) <= 50.0)
|
|
{
|
|
Vector3D local_direction;
|
|
local_direction.Subtract(
|
|
targetPosition,
|
|
local_to_world
|
|
);
|
|
|
|
local_direction.y = 0;
|
|
Scalar arc_value;
|
|
arc_value = local_direction.GetLength();
|
|
arc_value -= 140.0f;
|
|
if(arc_value > 0.0f)
|
|
{
|
|
arc_value /= 3;
|
|
Max_Clamp(arc_value, 120.0f);
|
|
targetPosition.y += arc_value;
|
|
}
|
|
}
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::SSRMSeekerModel(Stuff::Time till)
|
|
{
|
|
Check_Object(this);
|
|
|
|
if (m_fTurnOffSeek)
|
|
{
|
|
localSpaceVelocity.angularMotion.Zero();
|
|
localSpaceAcceleration.angularMotion.Zero();
|
|
return; // leave the current direction alone since we're done turning
|
|
}
|
|
|
|
Point3D local_to_world;
|
|
local_to_world = GetLocalToWorld();
|
|
|
|
CalculateTargetPosition(&targetPosition, till);
|
|
|
|
Point3D origin;
|
|
origin = GetLocalToWorld();
|
|
Vector3D local_direction;
|
|
local_direction.Subtract(
|
|
targetPosition,
|
|
origin
|
|
);
|
|
|
|
Scalar true_distance = local_direction.GetApproximateLength();
|
|
//
|
|
//------------------------------------------------------------
|
|
//Right here we need to tell the target how far we are from it
|
|
//------------------------------------------------------------
|
|
//
|
|
if((true_distance < 50.0f) && (DoesAMSDestroy()))
|
|
{
|
|
ConveyDistanceToTarget(true_distance);
|
|
}
|
|
|
|
#if 0
|
|
if(local_direction.GetLength() > 80.0f)
|
|
{
|
|
//
|
|
//--------------------------------------------
|
|
//Now we want to cast a ray towards the ground
|
|
//--------------------------------------------
|
|
//
|
|
Vector3D ray_direction;
|
|
ray_direction.Multiply(srmPitchVector, GetLocalToWorld());
|
|
|
|
Stuff::Line3D line;
|
|
line.length = 20.0f;
|
|
line.direction = ray_direction;
|
|
line.origin = origin;
|
|
|
|
//
|
|
// Prepare query
|
|
//
|
|
Stuff::Normal3D normal;
|
|
CollisionQuery query(&line, &normal, CanBeShotFlag, this);
|
|
|
|
//
|
|
// Cast ray
|
|
//
|
|
Check_Object(CollisionGrid::Instance);
|
|
Entity *entity_hit = CollisionGrid::Instance->ProjectLine(&query);
|
|
if(entity_hit)
|
|
{
|
|
if(entity_hit != Map::GetInstance())
|
|
{
|
|
SPEW(("daberger", "I did not hit the map, and I hit %s", (const char *)entity_hit->instanceName));
|
|
srmYTargetOffset += (20 - line.length);
|
|
SPEW(("daberger", "We are adding to the offset %f", srmYTargetOffset));
|
|
}
|
|
else
|
|
{
|
|
SPEW(("daberger", "I hit the fucking Map"));
|
|
srmYTargetOffset = (20 - line.length);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
srmYTargetOffset = 0.0f;
|
|
SPEW(("daberger", "We have cleared the offset"));
|
|
}
|
|
|
|
targetPoint.y += srmYTargetOffset;
|
|
SPEW(("daberger", "The Y target is %f\n", targetPoint.y));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::SRMSeekerModel(Stuff::Time till)
|
|
{
|
|
Check_Object(this);
|
|
|
|
if (m_fTurnOffSeek)
|
|
{
|
|
localSpaceVelocity.angularMotion.Zero();
|
|
localSpaceAcceleration.angularMotion.Zero();
|
|
return; // leave the current direction alone since we're done turning
|
|
}
|
|
/*
|
|
if(timeInAir <= burnTime)
|
|
{
|
|
Mover::StraightLineMotionSimulation(till);
|
|
}
|
|
else
|
|
{
|
|
localSpaceDrag = Motion3D::Identity;
|
|
|
|
UnitVector3D local_down;
|
|
GetLocalToWorld().GetWorldDownInLocal(&local_down);
|
|
localSpaceAcceleration.linearMotion.Multiply(
|
|
local_down,
|
|
g_Gravity
|
|
);
|
|
|
|
LinearDragMotionSimulation(till);
|
|
|
|
if(trailEffect.GetCurrent())
|
|
trailEffect.GetCurrent()->executionState->RequestState(Effect::ExecutionStateEngine::StoppingState);
|
|
}
|
|
*/
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::ConveyDistanceToTarget(Stuff::Scalar distance)
|
|
{
|
|
Check_Object(this);
|
|
|
|
if(targetMWObject.GetCurrent() == NULL)
|
|
return;
|
|
|
|
Point3D local_to_world;
|
|
local_to_world = GetLocalToWorld();
|
|
amsExplosionResource = targetMWObject.GetCurrent()->ReactToMissileApproach(distance, local_to_world);
|
|
if(amsExplosionResource != ResourceID::Null)
|
|
doesAMSDestroyThisFrame = true;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::CreateEffect(const Adept::ResourceID& effect_id)
|
|
{
|
|
|
|
if (effect_id == ResourceID::Null)
|
|
return;
|
|
|
|
Resource resource(effect_id);
|
|
Verify(resource.DoesResourceExist());
|
|
|
|
ClassID class_ID;
|
|
class_ID = Entity::GetClassIDFromDataListID(effect_id);
|
|
Verify(class_ID != NullClassID);
|
|
|
|
LinearMatrix4D effect_location = GetLocalToWorld();
|
|
|
|
Effect::CreateMessage effect_create_message(
|
|
sizeof(Effect::CreateMessage),
|
|
DefaultEventPriority,
|
|
Entity::CreateMessage::DefaultFlags,
|
|
class_ID,
|
|
Effect::DefaultFlags,
|
|
effect_id,
|
|
effect_location,
|
|
0.0f,
|
|
Entity::ExecutionStateEngine::AlwaysExecuteState,
|
|
NameTable::NullObjectID,
|
|
Entity::DefaultAlignment
|
|
);
|
|
MemoryStream stream(&effect_create_message, effect_create_message.messageLength);
|
|
|
|
Effect *entity;
|
|
ReplicatorID effect_rep_id = Connection::Hermit->GetNextReplicatorID();
|
|
entity = Cast_Object(Effect *,Entity::CreateEntity(&stream, &effect_rep_id, false));
|
|
Check_Object(entity);
|
|
Check_Object(Map::GetInstance());
|
|
Map::GetInstance()->AddChild(entity);
|
|
|
|
entity->SyncMatrices(true);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
Missile::TestInstance() const
|
|
{
|
|
Verify(IsDerivedFrom(DefaultData));
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Stuff::Point3D&
|
|
Missile::CalculateTargetPosition(Stuff::Point3D *new_position, Time till)
|
|
{
|
|
Check_Object(this);
|
|
Check_Pointer(new_position);
|
|
|
|
//
|
|
//-------------------------------------------------------------------
|
|
// If we are targeted on something that isn't the map, figure out its
|
|
// current position
|
|
//-------------------------------------------------------------------
|
|
//
|
|
Entity *target_entity = targetEntity.GetCurrent();
|
|
MWObject * target_mwobject = targetMWObject.GetCurrent();
|
|
if((target_entity) && (!target_entity->IsDerivedFrom(Map::DefaultData)))
|
|
{
|
|
if (target_entity->IsDestroyed())
|
|
{
|
|
target_collision_volume = NULL;
|
|
target_entity = GetFirstAcceptableParentEntity(target_entity);
|
|
if (target_entity)
|
|
{
|
|
Check_Object(target_entity);
|
|
targetEntity.Remove();
|
|
targetEntity.Add(target_entity);
|
|
}
|
|
//
|
|
// If we don't have anything valid to aim at, then just find a spot on the ground below the object we were
|
|
// previously aiming at
|
|
//
|
|
else
|
|
{
|
|
targetEntity.Remove();
|
|
//
|
|
// Cast a ray down from the last known position.
|
|
//
|
|
Vector3D ray_direction = Vector3D::Down;
|
|
Stuff::Line3D line;
|
|
line.m_length = 1000.0f;
|
|
line.m_direction = ray_direction;
|
|
line.m_origin = *new_position;
|
|
line.m_origin.y += 5.0f;
|
|
//
|
|
// Prepare query
|
|
//
|
|
Stuff::Normal3D normal;
|
|
CollisionQuery query(&line, &normal, CanBeShotFlag, this);
|
|
CollisionGrid::Instance->ProjectLine(&query);
|
|
line.FindEnd(new_position);
|
|
}
|
|
}
|
|
if (target_entity && !target_entity->IsDestroyed())
|
|
{
|
|
|
|
//
|
|
// Rather than indirectly getting the collision volume, I'll just get where the original raycast hit
|
|
// relative to the entity that we are tracking.
|
|
//
|
|
|
|
Stuff::Point3D target_local_to_world;
|
|
(*new_position).Multiply(targetOffsetFromEntity, target_entity->GetLocalToWorld());
|
|
|
|
//
|
|
//------------------------------------------------------------------
|
|
// If it is an MWObject, look for the center torso damage object and
|
|
// seek its collision volume
|
|
//------------------------------------------------------------------
|
|
//
|
|
//
|
|
// Make sure our current target_collision_volume is valid first...
|
|
//
|
|
// if (!target_collision_volume || target_collision_volume->IsDestroyed())
|
|
// {
|
|
// GetTargetCollisionVolume();
|
|
// }
|
|
|
|
//
|
|
// Aim at the targetOffset in this case.
|
|
//
|
|
// if (target_collision_volume) // we didn't find one previously
|
|
// {
|
|
// *new_position = target_collision_volume->m_worldSpaceBounds.localToParent;
|
|
// }
|
|
// else
|
|
// {
|
|
// *new_position = target_entity->GetLocalToWorld();
|
|
// }
|
|
//
|
|
if (target_mwobject )
|
|
{
|
|
Check_Object(target_mwobject);
|
|
|
|
//
|
|
//---------------------------------------------------------------
|
|
// If this is a vehicle, we use dead reckoning to figure out
|
|
// the best point along its potential path for us to hit it,
|
|
// given our two relative speeds
|
|
//---------------------------------------------------------------
|
|
//
|
|
if (target_mwobject->IsDerivedFrom(Vehicle::DefaultData))
|
|
{
|
|
// Stuff::Point3D hit_point_offset;
|
|
Stuff::LinearMatrix4D mw_object_ltw;
|
|
Stuff::LinearMatrix4D mw_object_ltw_inverse;
|
|
Stuff::LinearMatrix4D target_ltp_mwobject;
|
|
mw_object_ltw = target_mwobject->GetLocalToWorld();
|
|
mw_object_ltw_inverse.Invert(mw_object_ltw);
|
|
target_ltp_mwobject.Multiply(target_entity->GetLocalToWorld(), mw_object_ltw_inverse);
|
|
//
|
|
// If we use a fixed offset like this is it true when we rotate in the prediction?
|
|
// Probably since we are only guessing at linear motion...
|
|
|
|
// hit_point_offset.MultiplyByInverse(*new_position,(Stuff::Point3D)mw_object_ltw);
|
|
|
|
Stuff::Scalar my_velocity(worldSpaceVelocity.linearMotion.GetApproximateLength());
|
|
|
|
Vehicle* v = Cast_Object(Vehicle*,target_mwobject);
|
|
Check_Object(v);
|
|
|
|
Stuff::Point3D distanceToTarget;
|
|
distanceToTarget.Subtract((Stuff::Point3D)v->GetLocalToWorld(), (Stuff::Point3D)GetLocalToWorld());
|
|
if (!Small_Enough(distanceToTarget.GetLengthSquared()))
|
|
{
|
|
Stuff::Scalar time = TimeToCollide(v->GetLocalToWorld(),
|
|
v->currentSpeedMPS,
|
|
(Stuff::Point3D)GetLocalToWorld(),
|
|
my_velocity);
|
|
|
|
Stuff::Scalar timeSlice = GetTimeParameter(till);
|
|
if (time < 0)
|
|
{
|
|
time = 0.0f; // negative time is bad; it means we will never hit -- so we clamp it at 0 minimum
|
|
}
|
|
else if (time > 2)
|
|
{
|
|
time = 2.0f; // no point predicting more than, say, 2 seconds in advance
|
|
}
|
|
|
|
time += timeSlice; // compensate for the last frame.
|
|
|
|
v->EstimateFuturePosition(new_position,time);
|
|
|
|
//
|
|
// Right now the missiles won't alter their target based on any animation information. Jerry needs to work with me
|
|
// to find a cheap method for finding out this info. We may not have a cheaper way than storing the index and querying
|
|
// the array every frame
|
|
//
|
|
/* ElementRenderer::Element *element = target_mwobject->animationArray[vehiclerootJointIndex]->GetElement();
|
|
Check_Object(element);
|
|
//
|
|
// Multiply by the root's localtoparent
|
|
//
|
|
camera_local_to_torso.Multiply(tempJumpMatrix, element->GetLocalToParent());
|
|
*/
|
|
mw_object_ltw.BuildTranslation(*new_position);
|
|
mw_object_ltw_inverse.Multiply(target_ltp_mwobject, mw_object_ltw);
|
|
(*new_position).Multiply(targetOffsetFromEntity, mw_object_ltw_inverse);
|
|
|
|
} // we were able to do a prediction (!smallenough)
|
|
} // it's a vehicle
|
|
} // if target_mwobject has anything in it
|
|
//
|
|
//----------------------------------------------------------
|
|
// The object is something else, so just seek it where it is
|
|
//----------------------------------------------------------
|
|
//
|
|
// else
|
|
// {
|
|
// *new_position = target_entity->GetLocalToWorld();
|
|
// }
|
|
} // is the entity we currently have !destroyed
|
|
} // if we have an enitity and it's not the map
|
|
|
|
//
|
|
//---------------------------------------------------
|
|
// If we hit the map, just fly to where we where told
|
|
//---------------------------------------------------
|
|
//
|
|
else
|
|
{
|
|
*new_position = targetPoint;
|
|
}
|
|
#if defined(MISSILE_BUG)
|
|
SPEW((MISSILE_BUG, "EstimatedTargetPosition: %f %f %f",
|
|
new_position->x, new_position->y, new_position->z));
|
|
#endif
|
|
return *new_position;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Entity*
|
|
Missile::TraceToParent(Entity *entity)
|
|
{
|
|
if(!entity)
|
|
return NULL;
|
|
|
|
if(entity->IsDerivedFrom(MWMover::DefaultData))
|
|
{
|
|
MWMover *mover;
|
|
mover = Cast_Object(MWMover *, entity);
|
|
if(mover->myParentVehicle)
|
|
return mover->myParentVehicle;
|
|
}
|
|
return entity;
|
|
}
|