Files
Cyd 2b8ca921cb Initial full mirror of c:\VWE (source + assets + toolchain + outputs) via Git LFS
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.
2026-06-24 21:28:16 -05:00

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(&current_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;
}