Player request from the six-player night: a pod that dies at full throttle reappears under a hand still holding full throttle, and launches on it. The pod should respawn with the throttle at zero and stay there until the player has brought the control back to zero themselves. The arcade never met the problem in this form - its throttle was a physical lever that kept its position regardless - but the same idea protects a real lever too, which matters now that the pods want this code. The latch lives in VTVControlsMapper, the one place every control source - RIO lever, pad trigger, keyboard axis, input script - funnels through, per simulation step, so it is deterministic and local to the owning machine by construction (the mapper only runs on MasterInstance). VTV::Reset arms it through DeathReset, with one wrinkle: the controls mapper is subsystem ZERO, below BasicSubsystemCount, so Reset's subsystem loop has never reached it. It is called explicitly now. Only RegularReset latches - that is the death respawn and the first spawn (so a throttle held through the countdown no longer buys a flying start). Football's repositioning reset does not latch, and mission review has no controls. While latched the mapper computes as though the throttle were zero, but only for the pass: the true lever position is restored at the bottom of the function, so the cockpit gauge and the watchers keep showing the player the hand they need to bring down. The restore also keeps the release test honest - RIO analog events arrive on CHANGE, so a zeroed attribute would otherwise sit at zero, release the latch by itself, and hand back a live throttle the moment the hand twitched. Release is at 5% of travel: wide enough for a resting trigger or a real lever's potentiometer sitting a few counts off its stop, narrow enough that easing off does not satisfy it. Proven with the input-script harness, both directions. Throttle held at 1.0 from the green light: the pod moves 1.3cm in 30 seconds, all of it hover settle. Throttle at zero for two seconds then 1.0: the latch releases in the quiet window and the pod is 665m down the track twenty seconds later. The second run is also the normal-play case - a control already at zero releases the latch on the first step, invisibly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3353 lines
79 KiB
C++
3353 lines
79 KiB
C++
#include "rp.h"
|
|
#pragma hdrstop
|
|
|
|
#include "vtv.h"
|
|
#include "vtvmppr.h"
|
|
#include "vtvpwr.h"
|
|
#include "weapsys.h"
|
|
#include "thruster.h"
|
|
#include "booster.h"
|
|
#include "chute.h"
|
|
#include "rpplayer.h"
|
|
#include "..\munga\door.h"
|
|
#include "..\munga\line.h"
|
|
#include "..\munga\random.h"
|
|
#include "..\munga\fileutil.h"
|
|
#include "..\munga\boxsolid.h"
|
|
#include "..\munga\app.h"
|
|
#include "..\munga\hostmgr.h"
|
|
#include "..\munga\namelist.h"
|
|
|
|
//#############################################################################
|
|
// Shared Data Support
|
|
//
|
|
Derivation* VTV::GetClassDerivations()
|
|
{ static Derivation classDerivations(JointedMover::GetClassDerivations(), "VTV");
|
|
return &classDerivations;
|
|
}
|
|
|
|
|
|
VTV::SharedData
|
|
VTV::DefaultData(
|
|
VTV::GetClassDerivations(),
|
|
VTV::MessageHandlers,
|
|
VTV::GetAttributeIndex(),
|
|
VTV::StateCount,
|
|
(Entity::MakeHandler)VTV::Make
|
|
);
|
|
|
|
//#############################################################################
|
|
// Message Support
|
|
//
|
|
const VTV::HandlerEntry
|
|
VTV::MessageHandlerEntries[]=
|
|
{
|
|
MESSAGE_ENTRY(VTV, TakeDamage),
|
|
MESSAGE_ENTRY(VTV, Kavorkian),
|
|
MESSAGE_ENTRY(VTV, MarkSpot),
|
|
MESSAGE_ENTRY(VTV, RestoreToSpot),
|
|
MESSAGE_ENTRY(VTV, ZoomIn),
|
|
MESSAGE_ENTRY(VTV, ZoomOut),
|
|
MESSAGE_ENTRY(VTV, PlayerLink)
|
|
};
|
|
|
|
VTV::MessageHandlerSet
|
|
VTV::MessageHandlers(
|
|
ELEMENTS(VTV::MessageHandlerEntries),
|
|
VTV::MessageHandlerEntries,
|
|
JointedMover::GetMessageHandlers()
|
|
);
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
VTV::PlayerLinkMessageHandler(PlayerLinkMessage *message)
|
|
{
|
|
Entity::PlayerLinkMessageHandler(message);
|
|
RPPlayer
|
|
*rp_player = Cast_Object(RPPlayer*, playerLink);
|
|
rp_player->SetPlayerVehicle(this);
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// If our player is a runner, add two boosters to each booster subsystem
|
|
//----------------------------------------------------------------------
|
|
//
|
|
switch (rp_player->playerType)
|
|
{
|
|
case RPPlayer::RunnerPlayerType:
|
|
{
|
|
Check_Pointer(subsystemArray);
|
|
for (int i=0; i<subsystemCount; ++i)
|
|
{
|
|
if (subsystemArray[i])
|
|
{
|
|
Check(subsystemArray[i]);
|
|
if (subsystemArray[i]->GetClassID() == BoosterClassID)
|
|
{
|
|
Booster *booster = (Booster*)subsystemArray[i];
|
|
booster->maxBoosters += 2;
|
|
booster->boostersRemaining += 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
goto Is_A_Scorer;
|
|
|
|
case RPPlayer::GeneralPlayerType:
|
|
case RPPlayer::CrusherPlayerType:
|
|
case RPPlayer::BlockerPlayerType:
|
|
Is_A_Scorer:
|
|
rp_player->SetScoringPlayerFlag();
|
|
break;
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
VTV::TakeDamageMessageHandler(TakeDamageMessage *message)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
//
|
|
//-------------------------------------------------
|
|
// Check If the damage was caused by another Player
|
|
//-------------------------------------------------
|
|
//
|
|
Check(application);
|
|
HostManager
|
|
*host = application->GetHostManager();
|
|
Check(host);
|
|
Entity
|
|
*offensive_entity = Cast_Object(
|
|
Entity*,
|
|
host->GetEntityPointer(message->inflictingEntity)
|
|
);
|
|
|
|
//
|
|
//------------------------------------
|
|
// make sure we can enter something...
|
|
//------------------------------------
|
|
//
|
|
if (offensive_entity)
|
|
{
|
|
Check(offensive_entity);
|
|
RPPlayer
|
|
*offensive_player = (RPPlayer*)offensive_entity->GetPlayerLink();
|
|
|
|
if (offensive_player)
|
|
{
|
|
//
|
|
//---------------------------------------------------------------
|
|
// Notify this player that damaged was caused by offensive_player
|
|
//---------------------------------------------------------------
|
|
//
|
|
Check(offensive_player);
|
|
RPPlayer::OffensivePlayer
|
|
*shooter = new RPPlayer::OffensivePlayer(
|
|
offensive_player,
|
|
message->damageData.damageType,
|
|
Now()
|
|
);
|
|
Register_Object(shooter);
|
|
RPPlayer
|
|
*our_player = (RPPlayer*)GetPlayerLink();
|
|
Check(our_player);
|
|
our_player->AddOffensivePlayer(*shooter);
|
|
}
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// We we have received a collision packet, we should calculate our own
|
|
// damage loss
|
|
//--------------------------------------------------------------------
|
|
//
|
|
switch (message->damageData.damageType)
|
|
{
|
|
case Damage::CollisionDamageType:
|
|
{
|
|
//
|
|
//-----------------------------------------------------------
|
|
// Find out who hit us, and get our velocity relative to them
|
|
//-----------------------------------------------------------
|
|
//
|
|
Check(application);
|
|
HostManager
|
|
*host = application->GetHostManager();
|
|
Check(host);
|
|
Mover
|
|
*mover = (Mover*)host->GetEntityPointer(message->inflictingEntity);
|
|
if (mover == NULL)
|
|
{
|
|
//
|
|
// The inflicting entity is gone - its pod left the race
|
|
// mid-mission (consumer multiplayer; arcade pods never
|
|
// left). Nothing to bounce against.
|
|
//
|
|
Check_Fpu();
|
|
return;
|
|
}
|
|
Verify(mover->IsDerivedFrom(Mover::GetClassDerivations()));
|
|
Vector3D
|
|
v;
|
|
v.Subtract(worldLinearVelocity, mover->worldLinearVelocity);
|
|
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// The normal to bounce against is passed via the damage force, so see if
|
|
// it opposes our motion. If not, we can ignore this message
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
if (v*message->damageData.surfaceNormal <= 0.0f)
|
|
{
|
|
Check_Fpu();
|
|
return;
|
|
}
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Otherwise, do a dynamic bounce, and apply the damage to ourselves.
|
|
// We save the original positions of the vehicles because we do not know
|
|
// what time slice to use
|
|
//----------------------------------------------------------------------
|
|
//
|
|
Point3D
|
|
old_us = localOrigin.linearPosition,
|
|
old_them = mover->localOrigin.linearPosition;
|
|
Scalar
|
|
elasticity = elasticityCoefficient;
|
|
|
|
message->damageData.surfaceNormal.x =
|
|
-message->damageData.surfaceNormal.x;
|
|
message->damageData.surfaceNormal.y =
|
|
-message->damageData.surfaceNormal.y;
|
|
message->damageData.surfaceNormal.z =
|
|
-message->damageData.surfaceNormal.z;
|
|
|
|
message->damageData.damageAmount =
|
|
DynamicBounce(
|
|
mover,
|
|
0.1f,
|
|
1.0f,
|
|
message->damageData.surfaceNormal,
|
|
&elasticity
|
|
);
|
|
localOrigin.linearPosition = old_us;
|
|
mover->localOrigin.linearPosition = old_them;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
message->damageData.damageForce /= moverMass;
|
|
Check_Fpu();
|
|
worldLinearVelocity += message->damageData.damageForce;
|
|
break;
|
|
}
|
|
}
|
|
Entity::TakeDamageMessageHandler(message);
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
VTV::KavorkianMessageHandler(
|
|
ReceiverDataMessageOf<ControlsButton> *message
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
if (message->dataContents > 0)
|
|
{
|
|
Damage
|
|
damage;
|
|
|
|
Check_Pointer(damageZones);
|
|
Check(*damageZones);
|
|
damage.damageType = Damage::ExplosiveDamageType;
|
|
damage.damageAmount =
|
|
1.0f / (*damageZones)->damageScale[Damage::ExplosiveDamageType];
|
|
Check_Fpu();
|
|
damage.damageForce = Vector3D::Identity;
|
|
(*damageZones)->TakeDamage(damage);
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
VTV::MarkSpotMessageHandler(
|
|
ReceiverDataMessageOf<ControlsButton> *message
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
if (message->dataContents > 0)
|
|
{
|
|
savedOrigin = localOrigin;
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
VTV::RestoreToSpotMessageHandler(
|
|
ReceiverDataMessageOf<ControlsButton> *message
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
if (message->dataContents > 0)
|
|
{
|
|
Reset(savedOrigin, VTV::RegularReset);
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
VTV::ZoomInMessageHandler(
|
|
ReceiverDataMessageOf<ControlsButton> *message
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
if (message->dataContents > 0)
|
|
{
|
|
targetRangeExponent;
|
|
if (--targetRangeExponent < 0.0f)
|
|
{
|
|
targetRangeExponent = 0.0f;
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
VTV::ZoomOutMessageHandler(
|
|
ReceiverDataMessageOf<ControlsButton> *message
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
if (message->dataContents > 0)
|
|
{
|
|
if (++targetRangeExponent > 5.0f)
|
|
{
|
|
targetRangeExponent = 5.0f;
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//#############################################################################
|
|
// Attribute Support
|
|
//
|
|
const VTV::IndexEntry
|
|
VTV::AttributePointers[]=
|
|
{
|
|
ATTRIBUTE_ENTRY(VTV, HeightAboveTerrain, heightAboveTerrain),
|
|
ATTRIBUTE_ENTRY(VTV, ThrusterPitch, thrusterPitch),
|
|
ATTRIBUTE_ENTRY(VTV, CollisionState, collisionState),
|
|
ATTRIBUTE_ENTRY(VTV, CollisionMaterialType, collisionMaterialType),
|
|
ATTRIBUTE_ENTRY(VTV, CollisionNormal, collisionNormal),
|
|
ATTRIBUTE_ENTRY(VTV, CollisionSpeed, collisionSpeed),
|
|
ATTRIBUTE_ENTRY(VTV, EyepointRotation, eyepointRotation),
|
|
ATTRIBUTE_ENTRY(VTV, BoosterOn, boosterOn),
|
|
ATTRIBUTE_ENTRY(VTV, BoosterScale, boosterScale),
|
|
ATTRIBUTE_ENTRY(VTV, BoosterSmokeDensity, boosterSmokeDensity),
|
|
ATTRIBUTE_ENTRY(VTV, ForwardVelocity, forwardVelocity),
|
|
ATTRIBUTE_ENTRY(VTV, CompassHeading, compassHeading),
|
|
ATTRIBUTE_ENTRY(VTV, TargetReticle, targetReticle),
|
|
ATTRIBUTE_ENTRY(VTV, NavigationRange, navigationRange),
|
|
ATTRIBUTE_ENTRY(VTV, NavigationLinearPosition,navigationLinearPosition),
|
|
ATTRIBUTE_ENTRY(VTV, NavigationAngularPosition,navigationAngularPosition),
|
|
ATTRIBUTE_ENTRY(VTV, ScoreState, scoreState),
|
|
ATTRIBUTE_ENTRY(VTV, ScoreType, scoreType),
|
|
ATTRIBUTE_ENTRY(VTV, HornBlast, hornBlast) // HACK - ECH 7/31/95 - For replicants
|
|
};
|
|
|
|
VTV::AttributeIndexSet& VTV::GetAttributeIndex()
|
|
{
|
|
static VTV::AttributeIndexSet attributeIndex(ELEMENTS(VTV::AttributePointers),
|
|
VTV::AttributePointers,
|
|
JointedMover::GetAttributeIndex()
|
|
);
|
|
return attributeIndex;
|
|
}
|
|
|
|
//#############################################################################
|
|
// Model support
|
|
//
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
VTV::SetMappingSubsystem(VTVControlsMapper *mapper)
|
|
{
|
|
Check(this);
|
|
Check(mapper);
|
|
|
|
Check_Pointer(subsystemArray);
|
|
if (subsystemArray[ControlsMapperSubsystem])
|
|
{
|
|
Unregister_Object(subsystemArray[ControlsMapperSubsystem]);
|
|
delete subsystemArray[ControlsMapperSubsystem];
|
|
}
|
|
subsystemArray[ControlsMapperSubsystem] = mapper;
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Logical
|
|
VTV::BoosterOn()
|
|
{
|
|
Check(this);
|
|
for(int ii=BasicSubsystemCount;ii<subsystemCount;ii++)
|
|
{
|
|
Subsystem
|
|
*subsystem = GetSubsystem(ii);
|
|
if (subsystem)
|
|
{
|
|
Check(subsystem);
|
|
if (subsystem->GetClassID() == BoosterClassID)
|
|
{
|
|
Booster
|
|
*booster = (Booster*) subsystem;
|
|
Check(booster);
|
|
if (booster->GetSimulationState() == Booster::BoosterBurning)
|
|
{
|
|
Check_Fpu();
|
|
return True;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
return False;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
VTV::MoveAndCollide(Scalar time_slice)
|
|
{
|
|
Check(this);
|
|
JointSubsystem
|
|
*joint_subsystem = GetJointSubsystem();
|
|
Check(joint_subsystem);
|
|
Verify(joint_subsystem->GetJointCount());
|
|
|
|
VTVControlsMapper
|
|
*mapper = Cast_Object(
|
|
VTVControlsMapper*,
|
|
GetSubsystem(VTV::ControlsMapperSubsystem)
|
|
);
|
|
Check(mapper);
|
|
|
|
//
|
|
//-------------------------------------------------------------------------
|
|
// Figure out the average height and thruster rotation. While doing this,
|
|
// calculate the differences in the corner forces so that we can figure out
|
|
// the roll and pitch accelerations due to terrain
|
|
//-------------------------------------------------------------------------
|
|
//
|
|
heightAboveTerrain = 0.0f;
|
|
thrusterPitch.rotationAmount = 0.0f;
|
|
Vector3D difference = Vector3D::Identity;
|
|
int valid_thruster=0;
|
|
|
|
for (int i=0; i<MAX_THRUSTERS; ++i)
|
|
{
|
|
Thruster
|
|
*thruster = (Thruster*) subsystemArray[Thruster1Subsystem + i];
|
|
if (thruster)
|
|
{
|
|
heightAboveTerrain += thruster->currentHeight;
|
|
switch (joint_subsystem->GetJoint(valid_thruster)->GetJointType())
|
|
{
|
|
|
|
case Joint::BallJointType:
|
|
thrusterPitch.rotationAmount +=
|
|
joint_subsystem->GetJoint(valid_thruster)->
|
|
GetEulerAngles().pitch;
|
|
break;
|
|
case Joint::HingeXJointType:
|
|
thrusterPitch.rotationAmount +=
|
|
joint_subsystem->GetJoint(valid_thruster)->
|
|
GetHinge().rotationAmount;
|
|
break;
|
|
|
|
}
|
|
++valid_thruster;
|
|
|
|
//
|
|
//----------------------------------------------------------------
|
|
// Generate the pseudoforce. Ignore the center engines of the VTV
|
|
//----------------------------------------------------------------
|
|
//
|
|
if ((i == 2 || i == 3) && joint_subsystem->GetJointCount() > 4)
|
|
{
|
|
continue;
|
|
}
|
|
if (thruster->thrusterOffset.x > 0.0f)
|
|
{
|
|
difference.z -= thruster->currentHeight;
|
|
}
|
|
else
|
|
{
|
|
difference.z += thruster->currentHeight;
|
|
}
|
|
if (thruster->thrusterOffset.z > 0.0f)
|
|
{
|
|
difference.x += thruster->currentHeight;
|
|
}
|
|
else
|
|
{
|
|
difference.x -= thruster->currentHeight;
|
|
}
|
|
}
|
|
}
|
|
heightAboveTerrain /= joint_subsystem->GetJointCount();
|
|
thrusterPitch.rotationAmount /= joint_subsystem->GetJointCount();
|
|
Check_Fpu();
|
|
|
|
//
|
|
//---------------------------------------------------
|
|
// If we are not burning, follow the terrain and turn
|
|
//---------------------------------------------------
|
|
//
|
|
if (GetSimulationState() != BurningState)
|
|
{
|
|
difference *= angularSpringFactor / (15.0f + 2.0f*heightAboveTerrain);
|
|
Check_Fpu();
|
|
|
|
//
|
|
//-------------------------------------------------------------------
|
|
// If this is a two-engine VTV, try to straighten out the pitch value
|
|
//-------------------------------------------------------------------
|
|
//
|
|
if (joint_subsystem->GetJointCount() == 2)
|
|
{
|
|
Scalar
|
|
len =
|
|
localToWorld(2,1)*localToWorld(2,1)
|
|
+ localToWorld(1,1)*localToWorld(1,1);
|
|
if (!Small_Enough(len))
|
|
{
|
|
difference.x = 3.0f*localToWorld(2,1) / Sqrt(len);
|
|
Check_Fpu();
|
|
}
|
|
difference.z *= 0.5f;
|
|
}
|
|
else
|
|
{
|
|
difference *= 0.25f;
|
|
}
|
|
if (groundEffectDomainSquared)
|
|
{
|
|
difference.z -= bankFactor * mapper->powerDemand.x;
|
|
localAcceleration.angularMotion += difference;
|
|
|
|
//
|
|
//-------------------------------------
|
|
// Stabilize the craft at high altitude
|
|
//-------------------------------------
|
|
//
|
|
if (heightAboveTerrain > 10.0f)
|
|
{
|
|
Vector3D
|
|
world_g = Vector3D::Identity,
|
|
local_g,
|
|
torque;
|
|
|
|
world_g.y = -GetEnvironment()->gravityConstant;
|
|
local_g.MultiplyByInverse(world_g, localToWorld);
|
|
world_g.y = -1.0f;
|
|
torque.Cross(world_g, local_g);
|
|
torque *= momentOfInertia;
|
|
localAcceleration.angularMotion += torque;
|
|
}
|
|
}
|
|
|
|
//
|
|
//---------------------------------------
|
|
// Turn the VTV as required by the mapper
|
|
//---------------------------------------
|
|
//
|
|
Vector3D
|
|
vel;
|
|
vel.Multiply(localVelocity.angularMotion, localToWorld);
|
|
Scalar
|
|
step = mapper->angularVelocityDemand.y - vel.y;
|
|
difference = Vector3D::Identity;
|
|
if (step > 0.0f)
|
|
{
|
|
if (maxAngularAcceleration.y * time_slice > step)
|
|
{
|
|
difference.y += step / time_slice;
|
|
Check_Fpu();
|
|
}
|
|
else
|
|
{
|
|
difference.y += maxAngularAcceleration.y;
|
|
}
|
|
}
|
|
else if (step < 0.0f)
|
|
{
|
|
if (maxAngularAcceleration.y * time_slice > -step)
|
|
{
|
|
difference.y += step / time_slice;
|
|
Check_Fpu();
|
|
}
|
|
else
|
|
{
|
|
difference.y -= maxAngularAcceleration.y;
|
|
}
|
|
}
|
|
|
|
vel.MultiplyByInverse(difference,localToWorld);
|
|
localAcceleration.angularMotion += vel;
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Apply the standard air resistance and gravity, then project the VTV
|
|
// motion through the time slice
|
|
//--------------------------------------------------------------------
|
|
//
|
|
ApplyAirResistanceAndGravity();
|
|
|
|
Vector3D
|
|
old_acceleration = worldLinearAcceleration;
|
|
|
|
worldLinearAcceleration.x *= ZIPPY;
|
|
worldLinearAcceleration.z *= ZIPPY;
|
|
|
|
Point3D
|
|
old_position(localOrigin.linearPosition);
|
|
ApplyWorldAccelerations(time_slice);
|
|
|
|
//
|
|
//-------------------------------------------------------------------------
|
|
// We have now moved the VTV. We should now update our collision volume to
|
|
// the new location and check it against the tree
|
|
//-------------------------------------------------------------------------
|
|
//
|
|
Vector3D
|
|
zippy_accel = worldLinearAcceleration,
|
|
impact_velocity = localVelocity.linearMotion;
|
|
|
|
worldLinearAcceleration = old_acceleration;
|
|
MoveCollisionVolume();
|
|
|
|
//
|
|
//-------------------------------------------
|
|
// If we are going fast enough, do a ray-cast
|
|
//-------------------------------------------
|
|
//
|
|
BoxedSolidCollisionList* collision_list = AllocateCollisionList();
|
|
Line line;
|
|
line = old_position;
|
|
|
|
Vector3D step;
|
|
step.Subtract(localOrigin.linearPosition, old_position);
|
|
if (step.LengthSquared() > SMALL)
|
|
{
|
|
line = step;
|
|
Scalar diameter = collisionTemplate->maxX - collisionTemplate->minX;
|
|
if (diameter < line.length)
|
|
{
|
|
CollideCenterOfMotion(&line, collision_list);
|
|
}
|
|
}
|
|
if (!collision_list->GetCollisionCount())
|
|
{
|
|
GetCurrentCollisions(collision_list);
|
|
}
|
|
|
|
//
|
|
//-------------------------------
|
|
// Process each of the collisions
|
|
//-------------------------------
|
|
//
|
|
collisionTemporaryState = NoCollisionState;
|
|
|
|
Damage
|
|
collision_damage;
|
|
|
|
ProcessCollisionList(
|
|
collision_list,
|
|
time_slice,
|
|
old_position,
|
|
&collision_damage
|
|
);
|
|
|
|
//
|
|
//-------------------------
|
|
// Set Collision attributes
|
|
//-------------------------
|
|
//
|
|
if (collision_damage.damageAmount > 0.0f)
|
|
{
|
|
BoxedSolid
|
|
*boxed_solid;
|
|
|
|
Check(&(*lastCollisionList)[0]);
|
|
boxed_solid = (*lastCollisionList)[0].GetTreeVolume();
|
|
Check(boxed_solid);
|
|
collisionMaterialType = boxed_solid->materialType;
|
|
|
|
collisionNormal.MultiplyByInverse(
|
|
collision_damage.surfaceNormal,
|
|
localToWorld
|
|
);
|
|
collisionSpeed = -(collisionNormal * impact_velocity);
|
|
|
|
if (GetSimulationState() != BurningState)
|
|
{
|
|
collisionState.SetState(collisionTemporaryState);
|
|
}
|
|
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// Take the damage, allowing any slope less than 45 degrees relative to
|
|
// the keel to take one-tenth the damage
|
|
//---------------------------------------------------------------------
|
|
//
|
|
if (collisionNormal.y > 0.7f)
|
|
{
|
|
collision_damage.damageAmount *= bottomArmorScale;
|
|
}
|
|
|
|
Check_Pointer(damageZones);
|
|
Check(*damageZones);
|
|
|
|
//TODO: IMPORTANT COLLISION CODE HERE
|
|
|
|
(*damageZones)->TakeDamage(collision_damage);
|
|
|
|
//
|
|
//------------------------------------------------------------------
|
|
// If the VTV is burning, apply the world-space force vectors to the
|
|
// craft as torque. The spinning will start next frame
|
|
//------------------------------------------------------------------
|
|
//
|
|
if (GetSimulationState() == BurningState)
|
|
{
|
|
Vector3D
|
|
force,
|
|
torque;
|
|
Point3D
|
|
moment;
|
|
|
|
force.MultiplyByInverse(collision_damage.damageForce, localToWorld);
|
|
moment.MultiplyByInverse(
|
|
(Vector3D)collision_damage.impactPoint,
|
|
localToWorld
|
|
);
|
|
torque.Cross(moment, force);
|
|
torque *= momentOfInertia;
|
|
Clamp(torque.x, -20.0f, 20.0f);
|
|
Clamp(torque.y, -20.0f, 20.0f);
|
|
Clamp(torque.z, -20.0f, 20.0f);
|
|
localVelocity.angularMotion += torque;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
collisionState.SetState(NoCollisionState);
|
|
}
|
|
|
|
//
|
|
//------------------------------------------
|
|
// Check to see if we should blow up the VTV
|
|
//------------------------------------------
|
|
//
|
|
{
|
|
InterestManager
|
|
*interest_mgr = application->GetInterestManager();
|
|
Check(interest_mgr);
|
|
InterestZone
|
|
*zone = interest_mgr->GetInterestZone(interestZoneID);
|
|
Check(zone);
|
|
BoundingBoxTree
|
|
*tree = zone->GetExistanceRoot();
|
|
Check(tree);
|
|
Check_Pointer(damageZones);
|
|
Check(*damageZones);
|
|
insideWorld =
|
|
tree->FindBoundingBoxContaining(localOrigin.linearPosition) != NULL;
|
|
if (!insideWorld && (*damageZones)->damageLevel < 1.0f)
|
|
{
|
|
collision_damage.damageType = Damage::ExplosiveDamageType;
|
|
collision_damage.damageAmount =
|
|
1.0f / (*damageZones)->damageScale[Damage::ExplosiveDamageType];
|
|
Check_Fpu();
|
|
collision_damage.damageForce = Vector3D::Identity;
|
|
Check_Pointer(damageZones);
|
|
Check(*damageZones);
|
|
(*damageZones)->TakeDamage(collision_damage);
|
|
|
|
//
|
|
// The tumble draws from the vehicle's OWN stream, not the
|
|
// global Random - the global one is shared with the frame
|
|
// loop's consumers (particles, mostly), so its position here
|
|
// depended on how many frames had rendered. This kick goes
|
|
// straight into physics state; it was the last wall-clocked
|
|
// input left in the whole death cycle.
|
|
//
|
|
localVelocity.angularMotion.x += 8.0f * TumbleRandom() - 4.0f;
|
|
localVelocity.angularMotion.y += 8.0f * TumbleRandom() - 4.0f;
|
|
localVelocity.angularMotion.z += 8.0f * TumbleRandom() - 4.0f;
|
|
}
|
|
}
|
|
worldLinearAcceleration = zippy_accel;
|
|
if (collisionState.GetState() == RestState)
|
|
{
|
|
worldLinearAcceleration = Vector3D::Identity;
|
|
}
|
|
UpdateLocalMotion();
|
|
|
|
//
|
|
//-------------------------------------------------
|
|
// Update the stuff that the secondary screen wants
|
|
//-------------------------------------------------
|
|
//
|
|
compassHeading = localToWorld;
|
|
forwardVelocity = -3.6f * localVelocity.linearMotion.z;
|
|
if (targetRangeExponent > currentRangeExponent)
|
|
{
|
|
currentRangeExponent += time_slice;
|
|
if (currentRangeExponent > targetRangeExponent)
|
|
{
|
|
currentRangeExponent = targetRangeExponent;
|
|
}
|
|
navigationRange = 250.0f * pow(2.0f, currentRangeExponent);
|
|
}
|
|
else if (targetRangeExponent < currentRangeExponent)
|
|
{
|
|
currentRangeExponent -= time_slice;
|
|
if (currentRangeExponent < targetRangeExponent)
|
|
{
|
|
currentRangeExponent = targetRangeExponent;
|
|
}
|
|
navigationRange = 250.0f * pow(2.0f, currentRangeExponent);
|
|
}
|
|
Check_Fpu();
|
|
|
|
//
|
|
//------------------------------------------------------------------------
|
|
// Run the dead reckoner, and then see how far apart the two positions are
|
|
//------------------------------------------------------------------------
|
|
//
|
|
(this->*deadReckoner)();
|
|
|
|
Vector3D
|
|
error;
|
|
error.Subtract(
|
|
projectedOrigin.linearPosition,
|
|
localOrigin.linearPosition
|
|
);
|
|
Quaternion
|
|
angular_deviation;
|
|
angular_deviation.Subtract(
|
|
projectedOrigin.angularPosition,
|
|
localOrigin.angularPosition
|
|
);
|
|
if (
|
|
error.LengthSquared() > 0.04f
|
|
|| Abs(angular_deviation.w) < 0.997f
|
|
|| collisionState.GetState() != collisionState.GetOldState()
|
|
|| lastPerformance - lastUpdate > 2.0f
|
|
)
|
|
{
|
|
ForceUpdate();
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
VTV::PerformAndWatch(
|
|
const Time &till,
|
|
MemoryStream *update_stream
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(&till);
|
|
|
|
if (GetInstance() != ReplicantInstance)
|
|
{
|
|
boosterOn = False;
|
|
boosterScale = Vector3D::Identity;
|
|
boosterSmokeDensity = 0.0f;
|
|
}
|
|
JointedMover::PerformAndWatch(till, update_stream);
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
VTV::WriteUpdateRecord(Simulation::UpdateRecord *message, int update_model)
|
|
{
|
|
Check(this);
|
|
Check_Pointer(message);
|
|
|
|
JointedMover::WriteUpdateRecord(message, update_model);
|
|
|
|
UpdateRecord
|
|
*record = (UpdateRecord*)message;
|
|
|
|
record->recordLength = sizeof(*record);
|
|
|
|
record->collisionState = collisionState.GetState();
|
|
record->collisionMaterialType = collisionMaterialType;
|
|
record->collisionNormal = collisionNormal;
|
|
record->collisionSpeed = collisionSpeed;
|
|
|
|
record->boosterOn = boosterOn;
|
|
record->boosterScale = boosterScale;
|
|
record->boosterSmokeDensity = boosterSmokeDensity;
|
|
|
|
record->hornBlast = hornBlast; // HACK - ECH 7/31/95 - For replicants
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
VTV::ReadUpdateRecord(Simulation::UpdateRecord *message)
|
|
{
|
|
Check(this);
|
|
Check_Pointer(message);
|
|
|
|
JointedMover::ReadUpdateRecord(message);
|
|
|
|
UpdateRecord
|
|
*record = (UpdateRecord*)message;
|
|
|
|
collisionState.SetState(record->collisionState);
|
|
collisionMaterialType = record->collisionMaterialType;
|
|
collisionNormal = record->collisionNormal;
|
|
collisionSpeed = record->collisionSpeed;
|
|
|
|
boosterOn = record->boosterOn;
|
|
boosterScale = record->boosterScale;
|
|
boosterSmokeDensity = record->boosterSmokeDensity;
|
|
|
|
hornBlast = record->hornBlast; // HACK - ECH 7/31/95 - For replicants
|
|
|
|
if (
|
|
GetSimulationState() == DefaultState
|
|
&& simulationState.GetOldState() == BurningState
|
|
)
|
|
{
|
|
localOrigin = updateOrigin;
|
|
|
|
if (IsCollisionVolume())
|
|
{
|
|
MoveCollisionVolume();
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
VTV::ProcessCollision(
|
|
Scalar time_slice,
|
|
BoxedSolidCollision &collision,
|
|
const Point3D &old_position,
|
|
Damage *damage
|
|
)
|
|
{
|
|
Scalar
|
|
penetration,
|
|
r,
|
|
elasticity = elasticityCoefficient,
|
|
friction = frictionCoefficient,
|
|
added_damage = 0.0f;
|
|
Door
|
|
*door = NULL;
|
|
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// Find out if we have hit a mover or something static. Moving collision
|
|
// volumes must have an owning entity
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
BoxedSolid
|
|
*box = collision.GetTreeVolume();
|
|
Check(box);
|
|
|
|
Simulation
|
|
*sim = box->GetOwningSimulation();
|
|
if (!sim)
|
|
{
|
|
goto Static_Bounce;
|
|
}
|
|
if (sim->IsDerivedFrom(*Mover::GetClassDerivations()))
|
|
{
|
|
Mover *mover = (Mover*)sim;
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// If we hit a mover, make sure that the collision is valid, and if it
|
|
// is, calculate the energy_loss
|
|
//--------------------------------------------------------------------
|
|
//
|
|
if (
|
|
!collisionVolume->ProcessCollision(
|
|
collision,
|
|
worldLinearVelocity,
|
|
lastCollisionList,
|
|
&damage->surfaceNormal,
|
|
&penetration
|
|
)
|
|
)
|
|
{
|
|
Check_Fpu();
|
|
return;
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------
|
|
// Make sure that we are not rearending somebody going faster...
|
|
//--------------------------------------------------------------
|
|
//
|
|
Vector3D
|
|
rel_v;
|
|
rel_v.Subtract(worldLinearVelocity, mover->worldLinearVelocity);
|
|
if (rel_v * damage->surfaceNormal >= -SMALL)
|
|
{
|
|
Check_Fpu();
|
|
return;
|
|
}
|
|
|
|
//
|
|
//---------------------------------
|
|
// Calculate the moving energy loss
|
|
//---------------------------------
|
|
//
|
|
Max_Clamp(penetration, time_slice);
|
|
r = penetration / time_slice;
|
|
Check_Fpu();
|
|
|
|
damage->damageAmount =
|
|
DynamicBounce(
|
|
mover,
|
|
time_slice,
|
|
r,
|
|
damage->surfaceNormal,
|
|
&elasticity
|
|
);
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// If we hit another VTV, we will have to send it a damage message and
|
|
// enter it into the queue of guys what hit us in the last two seconds
|
|
//--------------------------------------------------------------------
|
|
//
|
|
if (mover->GetClassID() == VTVClassID)
|
|
{
|
|
TakeDamageMessage
|
|
message(
|
|
TakeDamageMessageID,
|
|
sizeof(TakeDamageMessage),
|
|
GetEntityID(),
|
|
0,
|
|
*damage
|
|
);
|
|
mover->Dispatch(&message);
|
|
|
|
RPPlayer
|
|
*offensive_player = (RPPlayer*)mover->GetPlayerLink();
|
|
if (offensive_player)
|
|
{
|
|
Check(offensive_player);
|
|
RPPlayer::OffensivePlayer
|
|
*shooter = new RPPlayer::OffensivePlayer(
|
|
offensive_player,
|
|
message.damageData.damageType,
|
|
lastPerformance
|
|
);
|
|
Register_Object(shooter);
|
|
RPPlayer
|
|
*our_player = (RPPlayer*)GetPlayerLink();
|
|
Check(our_player);
|
|
our_player->AddOffensivePlayer(*shooter);
|
|
}
|
|
}
|
|
|
|
//
|
|
//-------------------------------------
|
|
// Set up the collision state correctly
|
|
//-------------------------------------
|
|
//
|
|
goto Set_Collision_State;
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// If we hit a moving door, adjust our relative velocity to the door. If we
|
|
// get hit by both doors, CRUSH the vtv!!!
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
else if (sim->IsDerivedFrom(*Door::GetClassDerivations()))
|
|
{
|
|
door = (Door*)sim;
|
|
Check(door);
|
|
worldLinearVelocity += door->currentVelocity;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// If we really have a collision, do a static bounce off of the normal
|
|
// generated
|
|
//--------------------------------------------------------------------
|
|
//
|
|
Static_Bounce:
|
|
if (
|
|
collisionVolume->ProcessCollision(
|
|
collision,
|
|
worldLinearVelocity,
|
|
lastCollisionList,
|
|
&damage->surfaceNormal,
|
|
&penetration
|
|
)
|
|
)
|
|
{
|
|
Max_Clamp(penetration, time_slice);
|
|
r = penetration / time_slice;
|
|
Check_Fpu();
|
|
|
|
damage->damageAmount =
|
|
StaticBounce(
|
|
old_position,
|
|
time_slice,
|
|
r,
|
|
damage->surfaceNormal,
|
|
&elasticity,
|
|
minimumBounceSpeed,
|
|
&friction
|
|
) + added_damage;
|
|
|
|
//
|
|
//----------------------------------------
|
|
// If we have hit a door, look for a crush
|
|
//----------------------------------------
|
|
//
|
|
if (door)
|
|
{
|
|
if (
|
|
lastPerformance - lastDoorHit < 0.1f
|
|
&& doorHitNormal*damage->surfaceNormal < -0.7f
|
|
&& damageZones[0]->damageLevel < 1.0f
|
|
)
|
|
{
|
|
damageZones[0]->damageLevel = 1.0f;
|
|
}
|
|
doorHitNormal = damage->surfaceNormal;
|
|
lastDoorHit = lastPerformance;
|
|
}
|
|
|
|
//
|
|
//--------------------------
|
|
// Determine collision state
|
|
//--------------------------
|
|
//
|
|
Set_Collision_State:
|
|
if (!elasticity)
|
|
{
|
|
if (!friction)
|
|
{
|
|
if (collisionTemporaryState == NoCollisionState)
|
|
{
|
|
collisionTemporaryState = RestState;
|
|
}
|
|
}
|
|
else if (collisionTemporaryState != InitialHitState)
|
|
{
|
|
collisionTemporaryState = SlideState;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
collisionTemporaryState = InitialHitState;
|
|
}
|
|
}
|
|
|
|
if (door)
|
|
{
|
|
worldLinearVelocity -= door->currentVelocity;
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//#############################################################################
|
|
// Scoring support
|
|
//
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// HACK - ECH 7/6/95 - Allow the player vehicle to respond to score
|
|
// messages, allows attribute system to be used for scoring
|
|
//
|
|
void
|
|
VTV::RespondToScoreMessage(Message *message)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
RPPlayer::ScoreMessage
|
|
*score_message = Cast_Object(RPPlayer::ScoreMessage*, message);
|
|
|
|
//
|
|
// Set the score state and toggle the state to trigger the watchers
|
|
//
|
|
Check(score_message);
|
|
scoreType = score_message->scoreType;
|
|
Verify(
|
|
score_message->scoreType > RPPlayer::NullPointType &&
|
|
score_message->scoreType < RPPlayer::PointTypeCount
|
|
);
|
|
scoreState.SetState(score_message->scoreType);
|
|
scoreState.SetState(RPPlayer::NullPointType);
|
|
Check_Fpu();
|
|
}
|
|
|
|
//#############################################################################
|
|
// Construction and Destruction
|
|
//
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
VTV::VTV(
|
|
VTV::MakeMessage *creation_message,
|
|
VTV::SharedData &virtual_data
|
|
):
|
|
JointedMover(creation_message, virtual_data),
|
|
collisionState(CollisionStateCount),
|
|
collisionNormal(0.0f, 0.0f, 1.0f),
|
|
scoreState(RPPlayer::PointTypeCount)
|
|
{
|
|
SetValidFlag();
|
|
|
|
//
|
|
//------------------------
|
|
// Setup the initial state
|
|
//------------------------
|
|
//
|
|
Check(application);
|
|
Check(application->GetInterestManager());
|
|
|
|
InterestZone
|
|
*interest_zone =
|
|
application->GetInterestManager()->GetInterestZone(interestZoneID);
|
|
Check(interest_zone);
|
|
|
|
localEnvironment = interest_zone->GetEnvironment();
|
|
Check(localEnvironment);
|
|
|
|
//
|
|
//----------------
|
|
// Init attributes
|
|
//----------------
|
|
//
|
|
collisionMaterialType = BoxedSolid::SteelMaterial;
|
|
// collisionNormal = Normal::Identity; // Fails TestInstance
|
|
collisionSpeed = 0.0f;
|
|
collisionState.SetState(NoCollisionState);
|
|
scoreState.SetState(RPPlayer::NullPointType);
|
|
scoreType = -1;
|
|
boosterOn = False;
|
|
boosterScale = Vector3D::Identity;
|
|
boosterSmokeDensity = 0.0f;
|
|
doorHitNormal = Vector3D::Identity;
|
|
lastDoorHit = Time::Null;
|
|
respawnPending = False;
|
|
respawnHaveGoal = False;
|
|
deathClockValid = False;
|
|
|
|
//
|
|
// Creation order is deterministic, so each vehicle's tumble stream
|
|
// is too - see TumbleRandom in the header.
|
|
//
|
|
{
|
|
static unsigned long tumble_births = 0;
|
|
tumbleSeed = 0x52503431UL + 7919UL * ++tumble_births;
|
|
}
|
|
heightAboveTerrain = 0.0f;
|
|
forwardVelocity = 0.0f;
|
|
hornBlast = -1;
|
|
insideWorld = True;
|
|
thrusterAngle = 0.0f;
|
|
|
|
//
|
|
//-----------------------------
|
|
// Set the correct motion model
|
|
//-----------------------------
|
|
//
|
|
BoxedSolid::Material
|
|
material;
|
|
|
|
SetDeadReckoner(&VTV::AcceleratedDeadReckoner);
|
|
if (GetInstance() == ReplicantInstance)
|
|
{
|
|
SetPerformance(&VTV::DeadReckon);
|
|
material = BoxedSolid::OtherCraftMaterial;
|
|
}
|
|
else
|
|
{
|
|
SetPerformance(&VTV::MoveAndCollide);
|
|
material = BoxedSolid::OurCraftMaterial;
|
|
}
|
|
|
|
//
|
|
//-----------------------------
|
|
// Initialize the VTV variables
|
|
//-----------------------------
|
|
//
|
|
ResourceFile
|
|
*res_file = application->GetResourceFile();
|
|
ResourceDescription
|
|
*res = res_file->SearchList(
|
|
resourceID,
|
|
ResourceDescription::GameModelResourceType
|
|
);
|
|
Check(res);
|
|
res->Lock();
|
|
|
|
ModelResource
|
|
*model_resource = (ModelResource*)res->resourceAddress;
|
|
Check_Pointer(model_resource);
|
|
|
|
groundEffectDomainSquared = model_resource->groundEffectDomainSquared;
|
|
groundEffectRange = model_resource->groundEffectRange;
|
|
|
|
maxAngularAcceleration = model_resource->maxAngularAcceleration;
|
|
maxYawVelocity = model_resource->maxYawVelocity;
|
|
angularSpringFactor = model_resource->angularSpringFactor;
|
|
bankFactor = model_resource->bankFactor;
|
|
maxImpactSpeed = model_resource->maxImpactSpeed;
|
|
bottomArmorScale = model_resource->bottomArmorScale;
|
|
|
|
Scalar death_speed_km = model_resource->deathSpeed;
|
|
//
|
|
// Convert to Meters/Second
|
|
//
|
|
deathSpeed = death_speed_km/3.6f;
|
|
deathScoreLoss = model_resource->deathScoreLoss;
|
|
powerDive = model_resource->powerDive;
|
|
|
|
res->Unlock();
|
|
|
|
deathConstant = (2000 * deathScoreLoss) /
|
|
(
|
|
(1 - (elasticityCoefficient * elasticityCoefficient)) *
|
|
(deathSpeed * deathSpeed) * moverMass
|
|
);
|
|
Check_Fpu();
|
|
|
|
thrusterPitch.rotationAmount = 0.0f;
|
|
thrusterPitch.axisNumber = X_Axis;
|
|
compassHeading = YawPitchRoll::Identity;
|
|
eyepointRotation = EulerAngles::Identity;
|
|
|
|
navigationRange = 1000.0; // initial range = 1km
|
|
currentRangeExponent = 3.0f;
|
|
targetRangeExponent = 3.0f;
|
|
|
|
Str_Copy(vehicleBadge, creation_message->vehicleBadge, sizeof(vehicleBadge));
|
|
Str_Copy(vehicleColor, creation_message->vehicleColor, sizeof(vehicleColor));
|
|
|
|
//
|
|
//-------------------------------------------------
|
|
// Set up secondary display variables
|
|
// The indirection is required by the camera ship,
|
|
// but may be exploited for viewing the nav
|
|
// display at an arbitrary location, if desired.
|
|
//-------------------------------------------------
|
|
// If 'navigationLinearPosition' is NULL, it will
|
|
// extract the position from the entity directly.
|
|
// Not so for 'navigationAngularPosition': if it
|
|
// is NULL, the display will not 'turn' with the
|
|
// vehicle. It must be explicitly set if used.
|
|
//-------------------------------------------------
|
|
//
|
|
navigationLinearPosition = NULL;
|
|
navigationAngularPosition = &localOrigin.angularPosition;
|
|
//
|
|
//---------------------------------------------------
|
|
// Make sure to correctly set the material of the VTV
|
|
//---------------------------------------------------
|
|
//
|
|
collisionVolume->materialType = material;
|
|
|
|
//
|
|
//------------------------------------------------------------------------
|
|
// Setup the subsystems. The mapper subsystem will be left blank, so that
|
|
// the caller can hook in the appropriate mapper
|
|
//------------------------------------------------------------------------
|
|
//
|
|
res =
|
|
application->GetResourceFile()->SearchList(
|
|
resourceID,
|
|
ResourceDescription::SubsystemModelStreamResourceType
|
|
);
|
|
Check(res);
|
|
res->Lock();
|
|
|
|
Check_Pointer(res->resourceAddress);
|
|
MemoryStream
|
|
subsystems(res->resourceAddress, res->resourceSize);
|
|
|
|
subsystemCount = *(int*)subsystems.GetPointer() + BasicSubsystemCount;
|
|
subsystems.AdvancePointer(sizeof(int));
|
|
Verify(!subsystemArray);
|
|
subsystemArray = new (Subsystem (*[subsystemCount]));
|
|
Register_Pointer(subsystemArray);
|
|
|
|
if (GetInstance() == ReplicantInstance)
|
|
{
|
|
VTVControlsMapper::SubsystemResource
|
|
control_subsystem_resource;
|
|
|
|
Str_Copy(
|
|
control_subsystem_resource.subsystemName,
|
|
"ControlsMapper",
|
|
sizeof(control_subsystem_resource.subsystemName)
|
|
);
|
|
control_subsystem_resource.classID = TrivialSubsystemClassID;
|
|
control_subsystem_resource.subsystemModelSize =
|
|
sizeof(control_subsystem_resource);
|
|
control_subsystem_resource.segmentIndex = -1;
|
|
control_subsystem_resource.subsystemFlags = DontExecuteFlag;
|
|
|
|
subsystemArray[ControlsMapperSubsystem] =
|
|
new VTVControlsMapper(
|
|
this,
|
|
VTV::ControlsMapperSubsystem,
|
|
&control_subsystem_resource,
|
|
VTVControlsMapper::DefaultData
|
|
);
|
|
Register_Object(subsystemArray[ControlsMapperSubsystem]);
|
|
}
|
|
else
|
|
{
|
|
subsystemArray[ControlsMapperSubsystem] = NULL;
|
|
}
|
|
|
|
res->Unlock();
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Make the power subsystem. All VTVs have one in the designated slot
|
|
//--------------------------------------------------------------------
|
|
//
|
|
VTVPower::SubsystemResource
|
|
power_subsystem_resource;
|
|
|
|
Str_Copy(
|
|
power_subsystem_resource.subsystemName,
|
|
"Power",
|
|
sizeof(power_subsystem_resource.subsystemName)
|
|
);
|
|
power_subsystem_resource.classID = TrivialSubsystemClassID;
|
|
power_subsystem_resource.subsystemModelSize =
|
|
sizeof(power_subsystem_resource);
|
|
power_subsystem_resource.maxAcceleration = model_resource->maxAcceleration;
|
|
power_subsystem_resource.segmentIndex = -1;
|
|
power_subsystem_resource.subsystemFlags = 0;
|
|
|
|
|
|
subsystemArray[PowerSubsystem] =
|
|
new VTVPower(this, PowerSubsystem, &power_subsystem_resource);
|
|
Register_Object(subsystemArray[PowerSubsystem]);
|
|
|
|
//
|
|
//---------------------------------------------------------------
|
|
// Make the thruster subsystems. We can handle up to 6 thrusters
|
|
//---------------------------------------------------------------
|
|
//
|
|
Thruster::SubsystemResource
|
|
thruster_subsystem_resource;
|
|
|
|
Str_Copy(
|
|
thruster_subsystem_resource.subsystemName,
|
|
"Thruster?",
|
|
sizeof(thruster_subsystem_resource.subsystemName)
|
|
);
|
|
thruster_subsystem_resource.classID = TrivialSubsystemClassID;
|
|
thruster_subsystem_resource.subsystemModelSize =
|
|
sizeof(thruster_subsystem_resource);
|
|
thruster_subsystem_resource.subsystemFlags = 0;
|
|
thruster_subsystem_resource.primeThruster = True;
|
|
|
|
//
|
|
// Initialize the thruster subsystems from the jointSubystem
|
|
// Information
|
|
//
|
|
|
|
#if DEBUG_LEVEL>0
|
|
JointSubsystem
|
|
*joint_subsystem = GetJointSubsystem();
|
|
Check(joint_subsystem);
|
|
Verify(joint_subsystem->GetJointCount() <= MAX_THRUSTERS);
|
|
#endif
|
|
|
|
int i;
|
|
for (i=0; i<MAX_THRUSTERS; ++i)
|
|
{
|
|
if (
|
|
model_resource->jointIndex[i] == -1
|
|
|| model_resource->segmentIndex[i] == -1
|
|
)
|
|
{
|
|
subsystemArray[Thruster1Subsystem + i] = NULL;
|
|
continue;
|
|
}
|
|
//
|
|
// Append thruster number to subsystem name
|
|
//
|
|
thruster_subsystem_resource.subsystemName[8] = (char)(i + '0');
|
|
|
|
//
|
|
// assign joint and segment Indicies
|
|
//
|
|
thruster_subsystem_resource.jointIndex =
|
|
model_resource->jointIndex[i];
|
|
thruster_subsystem_resource.segmentIndex =
|
|
model_resource->segmentIndex[i];
|
|
|
|
//
|
|
// Get maxThrusterRotation Rate
|
|
//
|
|
thruster_subsystem_resource.maxThrusterRotationRate =
|
|
model_resource->maxThrusterRotationRate;
|
|
|
|
//
|
|
// Construct the thruster Subsystem
|
|
//
|
|
subsystemArray[Thruster1Subsystem + i] =
|
|
new Thruster(
|
|
this,
|
|
Thruster1Subsystem+i,
|
|
&thruster_subsystem_resource);
|
|
|
|
Register_Object(subsystemArray[Thruster1Subsystem + i]);
|
|
thruster_subsystem_resource.primeThruster = False;
|
|
}
|
|
|
|
//
|
|
// Add the JointSubsystem to the subsystemArray
|
|
//
|
|
subsystemArray[JointSubsystemID] = GetJointSubsystem();
|
|
|
|
//
|
|
//---------------------------------------------------
|
|
// Read and process the subsystem array specification
|
|
//---------------------------------------------------
|
|
//
|
|
for (i=BasicSubsystemCount; i<subsystemCount; ++i)
|
|
{
|
|
Subsystem::SubsystemResource
|
|
*subsystem_resource =
|
|
(Subsystem::SubsystemResource*)subsystems.GetPointer();
|
|
if (
|
|
!(subsystem_resource->subsystemFlags & Subsystem::DontReplicateFlag)
|
|
|| GetInstance() != ReplicantInstance
|
|
)
|
|
{
|
|
switch (subsystem_resource->classID)
|
|
{
|
|
case RegisteredClass::BoosterClassID:
|
|
subsystemArray[i] =
|
|
new Booster(
|
|
this,
|
|
i,
|
|
(Booster::SubsystemResource*)subsystem_resource
|
|
);
|
|
break;
|
|
case RegisteredClass::ChuteClassID:
|
|
subsystemArray[i] =
|
|
new Chute(this, i, (Chute::SubsystemResource*)subsystem_resource);
|
|
break;
|
|
case RegisteredClass::RivetGunClassID:
|
|
subsystemArray[i] =
|
|
new RivetGun(this, i, (RivetGun::SubsystemResource*)subsystem_resource);
|
|
break;
|
|
case RegisteredClass::LaserDrillClassID:
|
|
subsystemArray[i] =
|
|
new LaserGun(this, i, (LaserGun::SubsystemResource*)subsystem_resource);
|
|
break;
|
|
case RegisteredClass::DemolitionPackDropperClassID:
|
|
subsystemArray[i] =
|
|
new DemolitionPackDropper(
|
|
this,
|
|
i,
|
|
(DemolitionPackDropper::SubsystemResource*)subsystem_resource);
|
|
break;
|
|
}
|
|
Register_Object(subsystemArray[i]);
|
|
}
|
|
else
|
|
{
|
|
subsystemArray[i] = NULL;
|
|
}
|
|
subsystems.AdvancePointer(subsystem_resource->subsystemModelSize);
|
|
}
|
|
|
|
//
|
|
// Read In DamageZones and Create DamageZoneID's
|
|
//
|
|
|
|
ResourceDescription
|
|
*dmg_res = application->GetResourceFile()->SearchList(
|
|
resourceID,
|
|
ResourceDescription::DamageZoneStreamResourceType
|
|
);
|
|
Check(dmg_res);
|
|
dmg_res->Lock();
|
|
|
|
DynamicMemoryStream
|
|
damage_zone_stream(
|
|
dmg_res->resourceAddress,
|
|
dmg_res->resourceSize
|
|
);
|
|
//
|
|
//-------------------------------------
|
|
// skip over damageZoneCount in Stream
|
|
//-------------------------------------
|
|
//
|
|
damage_zone_stream.AdvancePointer(sizeof(damageZoneCount));
|
|
//
|
|
// Allocate damageZones Array
|
|
//
|
|
for(int ii=0;ii<damageZoneCount;++ii)
|
|
{
|
|
damageZones[ii] = new VTV::DamageZone(
|
|
this,
|
|
ii,
|
|
&damage_zone_stream
|
|
);
|
|
Register_Object(damageZones[ii]);
|
|
}
|
|
|
|
dmg_res->Unlock();
|
|
//
|
|
//--------------------
|
|
// Init the reticle
|
|
//--------------------
|
|
//
|
|
targetReticle.reticlePosition = model_resource->reticleResource.reticlePosition;
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
VTV::~VTV()
|
|
{
|
|
Check(this);
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
VTV::Reset(const Origin& new_origin, int reset_command)
|
|
{
|
|
Check(this);
|
|
Check(&new_origin);
|
|
|
|
localOrigin = new_origin;
|
|
localVelocity = Motion::Identity;
|
|
localAcceleration = Motion::Identity;
|
|
worldLinearVelocity = Vector3D::Identity;
|
|
worldLinearAcceleration = Vector3D::Identity;
|
|
localToWorld = localOrigin;
|
|
SetSimulationState(VTV::DefaultState);
|
|
damageZones[0]->damageLevel = 0.0f;
|
|
collisionState.SetState(NoCollisionState);
|
|
boosterOn = False;
|
|
boosterScale = Vector3D::Identity;
|
|
boosterSmokeDensity = 0.0f;
|
|
|
|
//
|
|
//-------------------------
|
|
// Reset subsystems
|
|
//-------------------------
|
|
//
|
|
for(int ii=BasicSubsystemCount;ii<subsystemCount;ii++)
|
|
{
|
|
Subsystem *subsystem = GetSubsystem(ii);
|
|
if (subsystem)
|
|
{
|
|
Check(subsystem);
|
|
subsystem->DeathReset(reset_command);
|
|
}
|
|
}
|
|
|
|
//
|
|
// The controls mapper too. It is subsystem ZERO, below
|
|
// BasicSubsystemCount, so the loop above has never reached it - which
|
|
// was fine while it had nothing to reset. It does now: the respawn
|
|
// throttle latch, which holds the pod at zero throttle until the
|
|
// player's control has come back to zero. See
|
|
// VTVControlsMapper::DeathReset for which resets latch and why.
|
|
//
|
|
{
|
|
Subsystem *mapper = GetSubsystem(ControlsMapperSubsystem);
|
|
|
|
if (mapper)
|
|
{
|
|
Check(mapper);
|
|
mapper->DeathReset(reset_command);
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
VTV::ScheduleRespawn(
|
|
const Origin &new_origin,
|
|
const Time &due,
|
|
const Point3D &face_toward,
|
|
Logical have_goal
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(&new_origin);
|
|
|
|
respawnOrigin = new_origin;
|
|
respawnDue = due;
|
|
respawnGoal = face_toward;
|
|
respawnHaveGoal = have_goal;
|
|
respawnPending = True;
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
// The pending respawn lands here, at the top of the first STEP whose clock
|
|
// has reached its due time - the same step count after death on every run
|
|
// and every frame rate. The old path applied the Reset from the event
|
|
// queue, which runs on wall clock: the crash was deterministic and the
|
|
// recovery was not, measured as two identical runs diverging on the first
|
|
// step after the pod stood back up.
|
|
//
|
|
// The turn-to-face-the-scorezone flip is the same arithmetic
|
|
// RPPlayer::PointVTVTowardGoal does, done here because it reads the
|
|
// POST-reset heading - it belongs to the same step as the teleport.
|
|
//
|
|
void
|
|
VTV::BeginStep()
|
|
{
|
|
Check(this);
|
|
|
|
if (respawnPending && !(GetLastPerformance() < respawnDue))
|
|
{
|
|
respawnPending = False;
|
|
|
|
{
|
|
static int diag = -1;
|
|
if (diag < 0)
|
|
{
|
|
const char *setting = getenv("RP412PHYSTRACE");
|
|
diag = (setting != NULL && atoi(setting) != 0) ? 1 : 0;
|
|
}
|
|
if (diag)
|
|
{
|
|
char buffer[120];
|
|
sprintf(buffer,
|
|
"PhysTrace: respawn fired, %.4f sim-s late, pad %.2f,%.2f\n",
|
|
(double)(Scalar)(GetLastPerformance() - respawnDue),
|
|
(double) respawnOrigin.linearPosition.x,
|
|
(double) respawnOrigin.linearPosition.z);
|
|
DEBUG_STREAM << buffer << std::flush;
|
|
}
|
|
}
|
|
|
|
Reset(respawnOrigin, RegularReset);
|
|
|
|
if (respawnHaveGoal)
|
|
{
|
|
Vector3D to_goal;
|
|
to_goal.Subtract(respawnGoal, localOrigin.linearPosition);
|
|
|
|
UnitVector current_heading;
|
|
localToWorld.GetFromAxis(Z_Axis, ¤t_heading);
|
|
|
|
Scalar length_to_goal = to_goal.LengthSquared();
|
|
if (length_to_goal > SMALL)
|
|
{
|
|
Scalar dot_prod =
|
|
(to_goal * current_heading) / Sqrt(length_to_goal);
|
|
if (dot_prod >= 0.0f)
|
|
{
|
|
Quaternion turn_around;
|
|
Quaternion y_roll(0.0f, 1.0f, 0.0f, 0.0);
|
|
|
|
turn_around.Multiply(
|
|
localOrigin.angularPosition, y_roll);
|
|
localOrigin.angularPosition = turn_around;
|
|
localToWorld = localOrigin;
|
|
}
|
|
}
|
|
ForceUpdate();
|
|
}
|
|
}
|
|
Mover::BeginStep();
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
VTV::DeathShutdown(int shutdown_command)
|
|
{
|
|
//
|
|
//-------------------------
|
|
// Shutdown subsystems
|
|
//-------------------------
|
|
//
|
|
for(int ii=BasicSubsystemCount;ii<subsystemCount;ii++)
|
|
{
|
|
Subsystem *subsystem = GetSubsystem(ii);
|
|
subsystem->DeathShutdown(shutdown_command);
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
ResourceDescription::ResourceID
|
|
VTV::CreateModelResource(
|
|
ResourceFile *resource_file,
|
|
const char *model_name,
|
|
NotationFile *model_file,
|
|
const ResourceDirectories *directories,
|
|
ModelResource *model
|
|
)
|
|
{
|
|
Check(resource_file);
|
|
Check_Pointer(model_name);
|
|
Check(model_file);
|
|
Check_Pointer(directories);
|
|
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// If we were not provided a buffer to write the model data into, we must
|
|
// create it ourselves. Then make sure that the model stuff is read in
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
ModelResource
|
|
*local_model = model;
|
|
if (!local_model)
|
|
{
|
|
local_model = new ModelResource;
|
|
Register_Pointer(local_model);
|
|
}
|
|
if (
|
|
JointedMover::CreateModelResource(
|
|
resource_file,
|
|
model_name,
|
|
model_file,
|
|
directories,
|
|
local_model
|
|
) == -1
|
|
)
|
|
{
|
|
Dump_And_Die:
|
|
if (!model)
|
|
{
|
|
Unregister_Pointer(local_model);
|
|
delete local_model;
|
|
}
|
|
Check_Fpu();
|
|
return -1;
|
|
}
|
|
if (
|
|
Reticle::CreateModelResource(
|
|
resource_file,
|
|
model_name,
|
|
model_file,
|
|
directories,
|
|
&local_model->reticleResource
|
|
) == -1
|
|
)
|
|
{
|
|
goto Dump_And_Die;
|
|
}
|
|
//
|
|
//-----------------------------
|
|
// Read in the max acceleration
|
|
//-----------------------------
|
|
//
|
|
if (
|
|
!model_file->GetEntry(
|
|
"gamedata",
|
|
"MaxAcceleration",
|
|
&local_model->maxAcceleration
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " missing MaxAcceleration!\n" << std::flush;
|
|
goto Dump_And_Die;
|
|
}
|
|
if (
|
|
!model_file->GetEntry(
|
|
"gamedata",
|
|
"MaxImpactSpeed",
|
|
&local_model->maxImpactSpeed
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " missing MaxImpactSpeed!\n" << std::flush;
|
|
goto Dump_And_Die;
|
|
}
|
|
|
|
//
|
|
//-------------------------
|
|
// Read in the bottom armor
|
|
//-------------------------
|
|
//
|
|
local_model->bottomArmorScale = 0.1f;
|
|
model_file->GetEntry(
|
|
"gamedata",
|
|
"BottomArmorScale",
|
|
&local_model->bottomArmorScale
|
|
);
|
|
|
|
//
|
|
//-------------------------
|
|
// Read in the bottom armor
|
|
//-------------------------
|
|
//
|
|
local_model->powerDive = 0.0f;
|
|
model_file->GetEntry(
|
|
"gamedata",
|
|
"PowerDive",
|
|
&local_model->powerDive
|
|
);
|
|
if (local_model->powerDive > 0.0 || local_model->powerDive < -1.0f)
|
|
{
|
|
DEBUG_STREAM << model_name << " has an invalid powerDive!\n" << std::flush;
|
|
goto Dump_And_Die;
|
|
}
|
|
|
|
//
|
|
//-----------------------------------
|
|
// Read in the thruster rotation rate
|
|
//-----------------------------------
|
|
//
|
|
if (
|
|
!model_file->GetEntry(
|
|
"gamedata",
|
|
"MaxThrusterRotationRate",
|
|
&local_model->maxThrusterRotationRate
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " missing MaxThrusterRotationRate!\n" << std::flush;
|
|
goto Dump_And_Die;
|
|
}
|
|
|
|
//
|
|
//--------------------------------
|
|
// Read in the ground effect stuff
|
|
//--------------------------------
|
|
//
|
|
if (
|
|
!model_file->GetEntry(
|
|
"gamedata",
|
|
"GroundEffectDomainSquared",
|
|
&local_model->groundEffectDomainSquared
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " missing GroundEffectDomainSquared!\n" << std::flush;
|
|
goto Dump_And_Die;
|
|
}
|
|
|
|
if (
|
|
!model_file->GetEntry(
|
|
"gamedata",
|
|
"GroundEffectRange",
|
|
&local_model->groundEffectRange
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " missing GroundEffectRange!\n" << std::flush;
|
|
goto Dump_And_Die;
|
|
}
|
|
|
|
|
|
//
|
|
//----------------------------------------
|
|
// Read in the angular acceleration limits
|
|
//----------------------------------------
|
|
//
|
|
const char *entry;
|
|
if (
|
|
!model_file->GetEntry(
|
|
"gamedata",
|
|
"MaxAngularAcceleration",
|
|
&entry
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " missing MaxAngularAcceleration!\n" << std::flush;
|
|
goto Dump_And_Die;
|
|
}
|
|
sscanf(
|
|
entry,
|
|
"%f %f %f",
|
|
&local_model->maxAngularAcceleration.x,
|
|
&local_model->maxAngularAcceleration.y,
|
|
&local_model->maxAngularAcceleration.z
|
|
);
|
|
|
|
if (
|
|
!model_file->GetEntry(
|
|
"gamedata",
|
|
"AngularSpringFactor",
|
|
&local_model->angularSpringFactor
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " missing AngularSpringFactor!\n" << std::flush;
|
|
goto Dump_And_Die;
|
|
}
|
|
if (
|
|
!model_file->GetEntry(
|
|
"gamedata",
|
|
"DeathSpeed",
|
|
&local_model->deathSpeed
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " missing DeathSpeed!\n" << std::flush;
|
|
goto Dump_And_Die;
|
|
}
|
|
if (
|
|
!model_file->GetEntry(
|
|
"gamedata",
|
|
"DeathScoreLoss",
|
|
&local_model->deathScoreLoss
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " missing DeathScoreLoss!\n" << std::flush;
|
|
goto Dump_And_Die;
|
|
}
|
|
if (
|
|
!model_file->GetEntry(
|
|
"gamedata",
|
|
"ElasticityCoefficient",
|
|
&local_model->elasticityCoefficient
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " missing ElasticityCoefficient!\n" << std::flush;
|
|
goto Dump_And_Die;
|
|
}
|
|
if (
|
|
!model_file->GetEntry(
|
|
"gamedata",
|
|
"MaxYawVelocity",
|
|
&local_model->maxYawVelocity
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " missing MaxYawVelocity!\n" << std::flush;
|
|
goto Dump_And_Die;
|
|
}
|
|
local_model->maxYawVelocity *= RAD_PER_DEG;
|
|
|
|
if (
|
|
!model_file->GetEntry(
|
|
"gamedata",
|
|
"BankFactor",
|
|
&local_model->bankFactor
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " missing BankFactor!\n" << std::flush;
|
|
goto Dump_And_Die;
|
|
}
|
|
|
|
//
|
|
// Read in Segment and Joint information from the .skl file
|
|
// for the thrusters
|
|
//
|
|
//-------------------
|
|
// Find the .skl file
|
|
//-------------------
|
|
//
|
|
const char
|
|
*skl_entry;
|
|
if (
|
|
!model_file->GetEntry(
|
|
"video",
|
|
"skeleton",
|
|
&skl_entry
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " is missing .skl file specification!\n" << std::flush;
|
|
return -1;
|
|
}
|
|
char
|
|
*skl_filename =
|
|
MakePathedFilename(directories->videoDirectory, skl_entry);
|
|
Register_Pointer(skl_filename);
|
|
|
|
NotationFile
|
|
*skl_file = new NotationFile(skl_filename);
|
|
Register_Object(skl_file);
|
|
|
|
if (!skl_file->PageCount())
|
|
{
|
|
DEBUG_STREAM << skl_filename << " is empty or missing!\n" << std::flush;
|
|
Dump_And_Die_1:
|
|
Unregister_Pointer(skl_filename);
|
|
delete[] skl_filename;
|
|
Unregister_Object(skl_file);
|
|
delete skl_file;
|
|
Check_Fpu();
|
|
return ResourceDescription::NullResourceID;
|
|
}
|
|
|
|
//
|
|
// Make a pagelist of all the segments
|
|
//
|
|
NameList
|
|
*segment_pages = skl_file->MakePageList();
|
|
Register_Object(segment_pages);
|
|
|
|
NameList::Entry
|
|
*segment_entry = segment_pages->GetFirstEntry();
|
|
int segment_count=0;
|
|
int joint_count=0;
|
|
//
|
|
// Initialize the thruster Model Resource info to No Thruster!
|
|
//
|
|
for(int ii=0;ii<MAX_THRUSTERS;ii++)
|
|
{
|
|
local_model->jointIndex[ii] = -1;
|
|
local_model->segmentIndex[ii] = -1;
|
|
}
|
|
|
|
while(segment_entry)
|
|
{
|
|
char entry[32];
|
|
Str_Copy(entry, segment_entry->GetName(), sizeof(entry));
|
|
//
|
|
//
|
|
// Skip LAB_ONLY and Damage_Zones
|
|
if(
|
|
(strcmp(entry, "LAB_ONLY") == 0) ||
|
|
(strcmp(entry,"DamageZones") ==0)
|
|
)
|
|
{
|
|
segment_entry = segment_entry->GetNextEntry();
|
|
continue;
|
|
}
|
|
if (strncmp(entry,"joint",5)==0)
|
|
{
|
|
if (!stricmp(entry,"jointrfeng"))
|
|
{
|
|
local_model->jointIndex[0] = joint_count;
|
|
local_model->segmentIndex[0] = segment_count;
|
|
}
|
|
else if (!stricmp(entry,"jointlfeng"))
|
|
{
|
|
local_model->jointIndex[1] = joint_count;
|
|
local_model->segmentIndex[1] = segment_count;
|
|
}
|
|
else if (!stricmp(entry,"jointreng") || !stricmp(entry,"jointrmeng"))
|
|
{
|
|
local_model->jointIndex[2] = joint_count;
|
|
local_model->segmentIndex[2] = segment_count;
|
|
}
|
|
else if (!stricmp(entry,"jointleng") || !stricmp(entry,"jointlmeng"))
|
|
{
|
|
local_model->jointIndex[3] = joint_count;
|
|
local_model->segmentIndex[3] = segment_count;
|
|
}
|
|
else if (!strcmp(entry,"jointrbeng"))
|
|
{
|
|
local_model->jointIndex[4] = joint_count;
|
|
local_model->segmentIndex[4] = segment_count;
|
|
}
|
|
else if (!strcmp(entry,"jointlbeng"))
|
|
{
|
|
local_model->jointIndex[5] = joint_count;
|
|
local_model->segmentIndex[5] = segment_count;
|
|
}
|
|
|
|
++joint_count;
|
|
}
|
|
++segment_count;
|
|
segment_entry = segment_entry->GetNextEntry();
|
|
}
|
|
|
|
//
|
|
//-------------------------------------------------------------------------
|
|
// If we created the model buffer, then we have the responsibility to write
|
|
// it out to the resource file
|
|
//-------------------------------------------------------------------------
|
|
//
|
|
if (!model)
|
|
{
|
|
ResourceDescription
|
|
*new_res = resource_file->AddResource(
|
|
model_name,
|
|
ResourceDescription::GameModelResourceType,
|
|
1,
|
|
ResourceDescription::Preload,
|
|
local_model,
|
|
sizeof(*local_model)
|
|
);
|
|
Unregister_Pointer(local_model);
|
|
delete local_model;
|
|
Unregister_Pointer(skl_filename);
|
|
delete skl_filename;
|
|
Unregister_Object(skl_file);
|
|
delete skl_file;
|
|
Unregister_Object(segment_pages);
|
|
delete segment_pages;
|
|
|
|
Check(new_res);
|
|
Check_Fpu();
|
|
return new_res->resourceID;
|
|
}
|
|
else
|
|
{
|
|
Unregister_Pointer(local_model);
|
|
delete local_model;
|
|
Unregister_Pointer(skl_filename);
|
|
delete skl_filename;
|
|
Unregister_Object(skl_file);
|
|
delete skl_file;
|
|
Unregister_Object(segment_pages);
|
|
delete segment_pages;
|
|
|
|
Check_Fpu();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
ResourceDescription::ResourceID
|
|
VTV::CreateSubsystemStream(
|
|
ResourceFile *resource_file,
|
|
const char *model_name,
|
|
NotationFile *model_file,
|
|
const ResourceDirectories *directories
|
|
)
|
|
{
|
|
Check(resource_file);
|
|
Check_Pointer(model_name);
|
|
Check(model_file);
|
|
Check_Pointer(directories);
|
|
|
|
//
|
|
//-------------------
|
|
// Find the .sub file
|
|
//-------------------
|
|
//
|
|
const char
|
|
*entry_data;
|
|
if (
|
|
!model_file->GetEntry(
|
|
"gamedata",
|
|
"Subsystems",
|
|
&entry_data
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " is missing .sub file specification!\n" << std::flush;
|
|
Check_Fpu();
|
|
return -1;
|
|
}
|
|
|
|
char
|
|
*filename = MakePathedFilename(directories->modelDirectory, entry_data);
|
|
Register_Pointer(filename);
|
|
|
|
NotationFile
|
|
*sys_file = new NotationFile(filename);
|
|
Register_Object(sys_file);
|
|
if (!sys_file->PageCount())
|
|
{
|
|
DEBUG_STREAM << filename << " cannot be found!\n" << std::flush;
|
|
Unregister_Pointer(filename);
|
|
delete filename;
|
|
Unregister_Object(sys_file);
|
|
delete sys_file;
|
|
Check_Fpu();
|
|
return -1;
|
|
}
|
|
|
|
//
|
|
//-------------------------------------------------------
|
|
// Figure out how many subsystems will have to be created
|
|
//-------------------------------------------------------
|
|
//
|
|
NameList
|
|
*namelist = sys_file->MakePageList();
|
|
Register_Object(namelist);
|
|
NameList::Entry
|
|
*entry = namelist->GetFirstEntry();
|
|
|
|
int
|
|
count = 0;
|
|
while (entry)
|
|
{
|
|
++count;
|
|
entry = entry->GetNextEntry();
|
|
}
|
|
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// Step through each of the pages, and create an entry in the subsystem
|
|
// stream
|
|
//---------------------------------------------------------------------
|
|
//
|
|
int
|
|
*subsystems = new int[sizeof(RivetGun::SubsystemResource)*count];
|
|
Register_Pointer(subsystems);
|
|
|
|
MemoryStream
|
|
stream(subsystems,sizeof(RivetGun::SubsystemResource)*count*sizeof(int));
|
|
|
|
*(int*)stream.GetPointer() = count;
|
|
stream.AdvancePointer(sizeof(int));
|
|
|
|
entry = namelist->GetFirstEntry();
|
|
while (entry)
|
|
{
|
|
const char
|
|
*subsystem_name = entry->GetName(),
|
|
*type;
|
|
if (
|
|
!sys_file->GetEntry(
|
|
subsystem_name,
|
|
"Type",
|
|
&type
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << ':' <<
|
|
subsystem_name << " missing Type!\n" << std::flush;
|
|
Dump_And_Die:
|
|
Unregister_Pointer(subsystems);
|
|
delete[] subsystems;
|
|
Unregister_Object(namelist);
|
|
delete namelist;
|
|
Unregister_Pointer(filename);
|
|
delete filename;
|
|
Unregister_Object(sys_file);
|
|
delete sys_file;
|
|
return -1;
|
|
}
|
|
|
|
Subsystem::SubsystemResource
|
|
*subsystem_resource =
|
|
(Subsystem::SubsystemResource*)stream.GetPointer();
|
|
if (!strcmp(type, "BoosterClassID"))
|
|
{
|
|
if (
|
|
Booster::CreateStreamedSubsystem(
|
|
model_file,
|
|
model_name,
|
|
subsystem_name,
|
|
(Booster::SubsystemResource*)subsystem_resource,
|
|
sys_file,
|
|
directories
|
|
) == -1
|
|
)
|
|
{
|
|
goto Dump_And_Die;
|
|
}
|
|
}
|
|
else
|
|
if (!strcmp(type, "ChuteClassID"))
|
|
{
|
|
if (
|
|
Chute::CreateStreamedSubsystem(
|
|
model_file,
|
|
model_name,
|
|
subsystem_name,
|
|
(Chute::SubsystemResource*)subsystem_resource,
|
|
sys_file,
|
|
directories
|
|
) == -1
|
|
)
|
|
{
|
|
goto Dump_And_Die;
|
|
}
|
|
}
|
|
else if (!strcmp(type, "RivetGunClassID"))
|
|
{
|
|
if (
|
|
RivetGun::CreateStreamedSubsystem(
|
|
model_file,
|
|
model_name,
|
|
subsystem_name,
|
|
(RivetGun::SubsystemResource*)subsystem_resource,
|
|
sys_file,
|
|
resource_file,
|
|
directories
|
|
) == -1
|
|
)
|
|
{
|
|
goto Dump_And_Die;
|
|
}
|
|
}
|
|
else if (!strcmp(type, "DemolitionPackDropperClassID"))
|
|
{
|
|
if (
|
|
DemolitionPackDropper::CreateStreamedSubsystem(
|
|
model_file,
|
|
model_name,
|
|
subsystem_name,
|
|
(DemolitionPackDropper::SubsystemResource*)subsystem_resource,
|
|
sys_file,
|
|
resource_file,
|
|
directories
|
|
) == -1
|
|
)
|
|
{
|
|
goto Dump_And_Die;
|
|
}
|
|
}
|
|
else if (!strcmp(type, "LaserDrillClassID"))
|
|
{
|
|
if (
|
|
LaserGun::CreateStreamedSubsystem(
|
|
model_file,
|
|
model_name,
|
|
subsystem_name,
|
|
(LaserGun::SubsystemResource*)subsystem_resource,
|
|
sys_file,
|
|
resource_file,
|
|
directories
|
|
) == -1
|
|
)
|
|
{
|
|
goto Dump_And_Die;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DEBUG_STREAM << model_name << ':' << subsystem_name
|
|
<< " has an unknown component type of " << type << std::endl << std::flush;
|
|
goto Dump_And_Die;
|
|
}
|
|
|
|
stream.AdvancePointer(subsystem_resource->subsystemModelSize);
|
|
entry = entry->GetNextEntry();
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Write the stream out to disk. Size is equal to the byte difference
|
|
// between stream and subsystems
|
|
//--------------------------------------------------------------------
|
|
//
|
|
ResourceDescription
|
|
*new_res = resource_file->AddResource(
|
|
model_name,
|
|
ResourceDescription::SubsystemModelStreamResourceType,
|
|
1,
|
|
ResourceDescription::Preload,
|
|
subsystems,
|
|
(char*)stream.GetPointer() - (char*)subsystems
|
|
);
|
|
Check(new_res);
|
|
|
|
//
|
|
//-------------------------
|
|
// Clean up after ourselves
|
|
//-------------------------
|
|
//
|
|
Unregister_Pointer(subsystems);
|
|
delete[] subsystems;
|
|
Unregister_Object(namelist);
|
|
delete namelist;
|
|
Unregister_Pointer(filename);
|
|
delete filename;
|
|
Unregister_Object(sys_file);
|
|
delete sys_file;
|
|
Check_Fpu();
|
|
return new_res->resourceID;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Simulation::SharedData*
|
|
VTV::GetSubsystemSharedData(const char *name)
|
|
{
|
|
Check_Pointer(name);
|
|
|
|
Check_Fpu();
|
|
if (!strcmp(name, ""))
|
|
{
|
|
return &VTV::DefaultData;
|
|
}
|
|
else if (!strcmp(name, "BoosterClassID"))
|
|
{
|
|
return &Booster::DefaultData;
|
|
}
|
|
else if (!strcmp(name, "ChuteClassID"))
|
|
{
|
|
return &Chute::DefaultData;
|
|
}
|
|
else if (!strcmp(name, "Power"))
|
|
{
|
|
return &VTVPower::DefaultData;
|
|
}
|
|
else if (!strcmp(name, "ControlsMapper"))
|
|
{
|
|
return &VTVControlsMapper::DefaultData;
|
|
}
|
|
else if (!strcmp(name, "RivetGunClassID"))
|
|
{
|
|
return &RivetGun::DefaultData;
|
|
}
|
|
else if (!strcmp(name, "LaserDrillClassID"))
|
|
{
|
|
return &LaserGun::DefaultData;
|
|
}
|
|
else if (!strcmp(name, "DemolitionPackDropperClassID"))
|
|
{
|
|
return &DemolitionPackDropper::DefaultData;
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
ResourceDescription::ResourceID
|
|
VTV::CreateControlMappingStream(
|
|
const char* mapping_name,
|
|
NotationFile *mapping_file,
|
|
FindNameFunction find_name,
|
|
ResourceFile *resource_file,
|
|
const char *model_name,
|
|
NotationFile *model_file,
|
|
const ResourceDirectories *directories,
|
|
PlatformTool *current_tool
|
|
)
|
|
{
|
|
Check(mapping_file);
|
|
Check(resource_file);
|
|
Check_Pointer(model_name);
|
|
Check(model_file);
|
|
Check_Pointer(directories);
|
|
|
|
NameList
|
|
*namelist = mapping_file->MakePageList();
|
|
Register_Object(namelist);
|
|
|
|
NameList::Entry
|
|
*entry = namelist->GetFirstEntry();
|
|
int
|
|
count=mapping_file->PageCount();
|
|
|
|
if (!count)
|
|
{
|
|
DEBUG_STREAM << model_name << " is missing control mappings!\n" << std::flush;
|
|
Unwind_1:
|
|
Unregister_Object(namelist);
|
|
delete namelist;
|
|
Check_Fpu();
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
*stream = (int*)new ControlsMapping[(count+1)*6];
|
|
Register_Pointer(stream);
|
|
int
|
|
*mappings = stream;
|
|
*mappings++ = count;
|
|
int
|
|
real_count = 0;
|
|
|
|
//
|
|
//----------------------------------
|
|
// For each page, create one mapping
|
|
//----------------------------------
|
|
//
|
|
for (int i=0; i<count; ++i)
|
|
{
|
|
ControlsMapping
|
|
*mapping = (ControlsMapping*)mappings;
|
|
const char
|
|
*control_name,
|
|
*page_name = entry->GetName();
|
|
if (
|
|
!mapping_file->GetEntry(
|
|
page_name,
|
|
"IOMapping",
|
|
&control_name
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << ':' << page_name
|
|
<< " has no IOMapping !\n" << std::flush;
|
|
Unwind_2:
|
|
Unregister_Pointer(stream);
|
|
delete stream;
|
|
goto Unwind_1;
|
|
}
|
|
if (!(*find_name)(control_name, mapping))
|
|
{
|
|
DEBUG_STREAM << model_name << ':' << control_name
|
|
<< " does not exist!\n" << std::flush;
|
|
goto Unwind_2;
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------
|
|
// Now, find the right subsystem to attach this control to
|
|
//--------------------------------------------------------
|
|
//
|
|
const char
|
|
*subsystem_name;
|
|
mapping->subsystemID = Entity::EntitySubsystemID;
|
|
char
|
|
subsystem_type[80];
|
|
*subsystem_type = '\0';
|
|
if (
|
|
mapping_file->GetEntry(
|
|
page_name,
|
|
"Subsystem",
|
|
&subsystem_name
|
|
)
|
|
)
|
|
{
|
|
//
|
|
//--------------------------------------------------
|
|
// See if this is one of the always there subsystems
|
|
//--------------------------------------------------
|
|
//
|
|
if (!strcmp(subsystem_name, "ControlsMapper"))
|
|
{
|
|
mapping->subsystemID = ControlsMapperSubsystem;
|
|
Str_Copy(subsystem_type, subsystem_name, sizeof(subsystem_type));
|
|
}
|
|
else if (!strcmp(subsystem_name, "Power"))
|
|
{
|
|
mapping->subsystemID = PowerSubsystem;
|
|
Str_Copy(subsystem_type, subsystem_name, sizeof(subsystem_type));
|
|
}
|
|
else if (!strncmp(subsystem_name, "Thruster", 8))
|
|
{
|
|
mapping->subsystemID =
|
|
Thruster1Subsystem + (subsystem_name[8] - '0');
|
|
Str_Copy(subsystem_type, subsystem_name, sizeof(subsystem_type));
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------
|
|
// Otherwise, we have to open up the subsystem file and find the
|
|
// matching page, keeping track of the count
|
|
//--------------------------------------------------------------
|
|
//
|
|
else
|
|
{
|
|
const char
|
|
*entry_data;
|
|
model_file->GetEntry("gamedata", "Subsystems", &entry_data);
|
|
|
|
char
|
|
*filename =
|
|
MakePathedFilename(directories->modelDirectory, entry_data);
|
|
Register_Pointer(filename);
|
|
|
|
NotationFile
|
|
*sys_file = new NotationFile(
|
|
filename,
|
|
NotationFile::CleanListMode|NotationFile::IgnoreCaseMode
|
|
);
|
|
Register_Object(sys_file);
|
|
|
|
NameList
|
|
*sublist = sys_file->MakePageList();
|
|
Register_Object(sublist);
|
|
|
|
NameList::Entry
|
|
*subsystem_entry = sublist->GetFirstEntry();
|
|
|
|
//
|
|
//-----------------------------------------------------------------
|
|
// Look through all the pages of the subsystem file, looking for
|
|
// our subsystem. When we find it, remember what number it was and
|
|
// what type it was
|
|
//-----------------------------------------------------------------
|
|
//
|
|
mapping->subsystemID = BasicSubsystemCount;
|
|
while (subsystem_entry)
|
|
{
|
|
if (!strcmp(subsystem_entry->GetName(), subsystem_name))
|
|
{
|
|
if (!sys_file->GetEntry(subsystem_name, "Type", &entry_data))
|
|
{
|
|
DEBUG_STREAM <<
|
|
model_name << ':' << subsystem_name <<
|
|
" does not have a 'Type' in file '" <<
|
|
filename << "'!\n" << std::flush;
|
|
}
|
|
Str_Copy(subsystem_type, entry_data, sizeof(subsystem_type));
|
|
subsystem_name = entry_data;
|
|
break;
|
|
}
|
|
subsystem_entry = subsystem_entry->GetNextEntry();
|
|
++mapping->subsystemID;
|
|
}
|
|
|
|
Unregister_Pointer(filename);
|
|
delete filename;
|
|
|
|
if (!subsystem_entry)
|
|
{
|
|
DEBUG_STREAM << model_name << ":subsystem '" << subsystem_name
|
|
<< "' does not exist!\n" << std::flush;
|
|
Unregister_Object(sys_file);
|
|
delete sys_file;
|
|
|
|
Unregister_Object(sublist);
|
|
delete sublist;
|
|
|
|
Unregister_Pointer(stream);
|
|
delete[] stream;
|
|
goto Unwind_1;
|
|
}
|
|
Unregister_Object(sys_file);
|
|
delete sys_file;
|
|
|
|
Unregister_Object(sublist);
|
|
delete sublist;
|
|
}
|
|
}
|
|
|
|
//
|
|
//--------------------------
|
|
// Find the Mode to use
|
|
//--------------------------
|
|
//
|
|
const char
|
|
*entry_string;
|
|
if (
|
|
!mapping_file->GetEntry(
|
|
page_name,
|
|
"Mode",
|
|
&entry_string
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM <<
|
|
model_name << ':' << page_name << " is missing Mode!\n" << std::flush;
|
|
Unwind_3:
|
|
Unregister_Pointer(stream);
|
|
delete[] stream;
|
|
goto Unwind_1;
|
|
}
|
|
|
|
CString
|
|
match_filter_string = entry_string,
|
|
mode_string,
|
|
null_string = NULL;
|
|
|
|
ModeMask
|
|
mode_mask;
|
|
|
|
mapping->modeMask = (ModeMask) 0;
|
|
{
|
|
int
|
|
i;
|
|
|
|
for(
|
|
i=0;
|
|
(mode_string=match_filter_string.GetNthToken(i)) != null_string;
|
|
++i
|
|
)
|
|
{
|
|
Logical
|
|
result = current_tool->ConvertStringToModeMask(
|
|
mode_string,
|
|
&mode_mask
|
|
);
|
|
|
|
if (result == False)
|
|
{
|
|
DEBUG_STREAM <<
|
|
model_name << ':' <<
|
|
page_name << " has an unsupported Mode type '" <<
|
|
mode_string << "'\n" << std::flush;
|
|
goto Unwind_3;
|
|
}
|
|
|
|
mapping->modeMask |= mode_mask;
|
|
}
|
|
|
|
if (i == 0)
|
|
{
|
|
DEBUG_STREAM <<
|
|
model_name << ':' <<
|
|
page_name << " has no Mode specified!\n" << std::flush;
|
|
goto Unwind_3;
|
|
}
|
|
}
|
|
//
|
|
//------------------------------------
|
|
// Find the type of connection to make
|
|
//------------------------------------
|
|
//
|
|
const char
|
|
*type;
|
|
if (
|
|
!mapping_file->GetEntry(
|
|
page_name,
|
|
"Type",
|
|
&type
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM <<
|
|
model_name << ':' <<
|
|
page_name << " is missing type!\n" << std::flush;
|
|
goto Unwind_3;
|
|
}
|
|
|
|
Simulation::SharedData
|
|
*shared_data = GetSubsystemSharedData(subsystem_type);
|
|
if (!shared_data)
|
|
{
|
|
DEBUG_STREAM <<
|
|
model_name << ':' <<
|
|
subsystem_name << " is an unsupported subsystem!\n" << std::flush;
|
|
goto Unwind_3;
|
|
}
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// If this is an event mapping, we have to have a message ID.
|
|
//----------------------------------------------------------------------
|
|
//
|
|
if (!strcmp(type, "EventMapping"))
|
|
{
|
|
mapping->mappingType = ControlsMapping::EventMapping;
|
|
const char* message;
|
|
if (
|
|
!mapping_file->GetEntry(
|
|
page_name,
|
|
"MessageID",
|
|
&message
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM <<
|
|
model_name << ':' <<
|
|
page_name << " is missing MessageID!\n" << std::flush;
|
|
goto Unwind_3;
|
|
}
|
|
const Receiver::HandlerEntry
|
|
*entry = shared_data->activeMessageHandlers->Find(message);
|
|
if (!entry)
|
|
{
|
|
DEBUG_STREAM <<
|
|
model_name << ':' <<
|
|
subsystem_name << ':' <<
|
|
message << " is an unsupported messageID!\n" << std::flush;
|
|
goto Unwind_3;
|
|
}
|
|
mapping->messageID = entry->entryID;
|
|
}
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// If this is a direct mapping, we have to have an attribute ID. How do
|
|
// we find an unknown attribute of an unknown subsystem?
|
|
//----------------------------------------------------------------------
|
|
//
|
|
else if (!strcmp(type, "DirectMapping"))
|
|
{
|
|
mapping->mappingType = ControlsMapping::DirectMapping;
|
|
const char
|
|
*attribute;
|
|
if (
|
|
!mapping_file->GetEntry(
|
|
page_name,
|
|
"AttributeID",
|
|
&attribute
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM <<
|
|
model_name << ':' <<
|
|
page_name << " is missing AttributeID!\n" << std::flush;
|
|
goto Unwind_3;
|
|
}
|
|
const Simulation::IndexEntry
|
|
*entry = shared_data->activeAttributeIndex->FindEntry(attribute);
|
|
if (!entry)
|
|
{
|
|
DEBUG_STREAM <<
|
|
model_name << ':' <<
|
|
subsystem_name << ':' <<
|
|
attribute << " is an unsupported AttributeID!\n" << std::flush;
|
|
goto Unwind_3;
|
|
}
|
|
mapping->attributeID = entry->entryID;
|
|
}
|
|
else
|
|
{
|
|
DEBUG_STREAM <<
|
|
model_name << ':' <<
|
|
page_name << " has unknown mapping type!\n" << std::flush;
|
|
goto Unwind_3;
|
|
}
|
|
|
|
mappings = (int*)((char*)mappings + sizeof(ControlsMapping));
|
|
++real_count;
|
|
|
|
//
|
|
//-------------------------
|
|
// Move to the next mapping
|
|
//-------------------------
|
|
//
|
|
entry = entry->GetNextEntry();
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Write the stream out to disk. Size is equal to the byte difference
|
|
// between stream and subsystems
|
|
//--------------------------------------------------------------------
|
|
//
|
|
*stream = real_count;
|
|
ResourceDescription
|
|
*new_res = resource_file->AddResource(
|
|
mapping_name,
|
|
ResourceDescription::ControlMappingStreamResourceType,
|
|
1,
|
|
ResourceDescription::Preload,
|
|
stream,
|
|
(char*)mappings - (char*)stream
|
|
);
|
|
Check(new_res);
|
|
|
|
//
|
|
//-------------------------
|
|
// Clean up after ourselves
|
|
//-------------------------
|
|
//
|
|
Unregister_Pointer(stream);
|
|
delete[] stream;
|
|
Unregister_Object(namelist);
|
|
delete namelist;
|
|
Check_Fpu();
|
|
return new_res->resourceID;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
VTV*
|
|
VTV::Make(VTV::MakeMessage *creation_message)
|
|
{
|
|
Check_Fpu();
|
|
return new VTV(creation_message);
|
|
}
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
|
|
ResourceDescription::ResourceID
|
|
VTV::CreateDamageZoneStream(
|
|
ResourceFile *resource_file,
|
|
const char *model_name,
|
|
NotationFile *model_file,
|
|
const ResourceDirectories *directories
|
|
)
|
|
{
|
|
Check(resource_file);
|
|
Check_Pointer(model_name);
|
|
Check(model_file);
|
|
Check_Pointer(directories);
|
|
|
|
//
|
|
//-------------------
|
|
// Find the .skl file
|
|
//-------------------
|
|
//
|
|
const char
|
|
*skl_entry;
|
|
if (
|
|
!model_file->GetEntry(
|
|
"video",
|
|
"skeleton",
|
|
&skl_entry
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " is missing .skl file specification!\n" << std::flush;
|
|
Check_Fpu();
|
|
return -1;
|
|
}
|
|
char
|
|
*skl_filename =
|
|
MakePathedFilename(directories->videoDirectory, skl_entry);
|
|
Register_Pointer(skl_filename);
|
|
|
|
NotationFile
|
|
*skl_file = new NotationFile(skl_filename);
|
|
Register_Object(skl_file);
|
|
|
|
if (!skl_file->PageCount())
|
|
{
|
|
DEBUG_STREAM << skl_filename << " is empty or missing!\n" << std::flush;
|
|
Dump_And_Die_1:
|
|
Unregister_Pointer(skl_filename);
|
|
delete[] skl_filename;
|
|
Unregister_Object(skl_file);
|
|
delete skl_file;
|
|
Check_Fpu();
|
|
return ResourceDescription::NullResourceID;
|
|
}
|
|
|
|
//
|
|
//-------------------
|
|
// Find the .dmg file
|
|
//-------------------
|
|
//
|
|
const char
|
|
*dmg_entry;
|
|
if (
|
|
!model_file->GetEntry(
|
|
"gamedata",
|
|
"DamageZones",
|
|
&dmg_entry
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " is missing .dmg file specification!\n" << std::flush;
|
|
goto Dump_And_Die_1;
|
|
}
|
|
|
|
char
|
|
*dmg_filename =
|
|
MakePathedFilename(directories->modelDirectory, dmg_entry);
|
|
Register_Pointer(dmg_filename);
|
|
|
|
NotationFile
|
|
*dmg_file = new NotationFile(dmg_filename);
|
|
Register_Object(dmg_file);
|
|
|
|
if (!dmg_file->PageCount())
|
|
{
|
|
DEBUG_STREAM << dmg_filename << " is empty or missing!\n" << std::flush;
|
|
Dump_And_Die_2:
|
|
Unregister_Pointer(dmg_filename);
|
|
delete[] dmg_filename;
|
|
Unregister_Object(dmg_file);
|
|
delete dmg_file;
|
|
goto Dump_And_Die_1;
|
|
}
|
|
|
|
//
|
|
// Get the number of Damage zones
|
|
//
|
|
int
|
|
dzone_count;
|
|
if (
|
|
!skl_file->GetEntry(
|
|
"ROOT",
|
|
"DZoneCount",
|
|
&dzone_count
|
|
)
|
|
)
|
|
{
|
|
DEBUG_STREAM << model_name << " is missing DZoneCount \n" << std::flush;
|
|
goto Dump_And_Die_2;
|
|
}
|
|
|
|
//
|
|
// Make a stream big enough for all the damage zones
|
|
//
|
|
DynamicMemoryStream
|
|
damage_zone_stream;
|
|
|
|
//
|
|
// write the Damage Zone Count Info from the
|
|
// .dmg file to the stream
|
|
//
|
|
damage_zone_stream << dzone_count;
|
|
|
|
//
|
|
// Make an entry list of all the entries in the damage zone page
|
|
//
|
|
NameList
|
|
*dzone_namelist = skl_file->MakeEntryList("DamageZones","dz_");
|
|
Register_Object(dzone_namelist);
|
|
|
|
if (dzone_namelist->EntryCount() == 0)
|
|
{
|
|
DEBUG_STREAM << "No dZones listed in DamageZones Page"<<std::endl << std::flush;
|
|
Dump_And_Die:
|
|
Unregister_Pointer(skl_filename);
|
|
delete skl_filename;
|
|
Unregister_Object(skl_file);
|
|
delete skl_file;
|
|
Unregister_Pointer(dmg_filename);
|
|
delete dmg_filename;
|
|
Unregister_Object(dmg_file);
|
|
delete dmg_file;
|
|
Check_Fpu();
|
|
return -1;
|
|
}
|
|
NameList::Entry
|
|
*dzone_entry;
|
|
char
|
|
current_dzone_name[32];
|
|
int
|
|
num_dzones_found = 0;
|
|
|
|
dzone_entry = dzone_namelist->GetFirstEntry();
|
|
|
|
while (dzone_entry)
|
|
{
|
|
++num_dzones_found;
|
|
//
|
|
// Get dzone name in .skl file
|
|
//
|
|
Str_Copy(
|
|
current_dzone_name,
|
|
dzone_entry->GetName(),
|
|
sizeof(current_dzone_name)
|
|
);
|
|
|
|
//
|
|
// Create the stream for this damage zone
|
|
// This Should Default to VTV::DamageZone::Create
|
|
//
|
|
VTV__DamageZone::CreateStreamedDamageZone(
|
|
model_file,
|
|
model_name,
|
|
skl_file,
|
|
current_dzone_name,
|
|
&damage_zone_stream,
|
|
dmg_file,
|
|
directories
|
|
);
|
|
//
|
|
// Get next dzone entry in this segment page
|
|
//
|
|
dzone_entry = dzone_entry->GetNextEntry();
|
|
} // End while more dzone_entries
|
|
|
|
if(dzone_count != num_dzones_found)
|
|
{
|
|
DEBUG_STREAM <<
|
|
"DZoneCount != damage zones found in Page DamageZones"<<std::endl << std::flush;
|
|
goto Dump_And_Die;
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Write the stream out to disk. Size is equal to the byte difference
|
|
// between stream and damage zone buffer
|
|
//--------------------------------------------------------------------
|
|
//
|
|
ResourceDescription
|
|
*new_res = resource_file->AddResourceMemoryStream(
|
|
model_name,
|
|
ResourceDescription::DamageZoneStreamResourceType,
|
|
1,
|
|
ResourceDescription::Preload,
|
|
&damage_zone_stream
|
|
);
|
|
Check(new_res);
|
|
//
|
|
// Free mem
|
|
//
|
|
Unregister_Pointer(skl_filename);
|
|
delete skl_filename;
|
|
Unregister_Object(skl_file);
|
|
delete skl_file;
|
|
Unregister_Pointer(dmg_filename);
|
|
delete dmg_filename;
|
|
Unregister_Object(dmg_file);
|
|
delete dmg_file;
|
|
Unregister_Object(dzone_namelist);
|
|
delete dzone_namelist;
|
|
Check_Fpu();
|
|
return new_res->resourceID;
|
|
}
|
|
|
|
//#############################################################################
|
|
//VTV::DamageZone
|
|
//
|
|
void
|
|
VTV__DamageZone::TakeDamage(Damage& damage)
|
|
{
|
|
Check(this);
|
|
Check_Pointer(&damage);
|
|
|
|
DamageZone::TakeDamage(damage);
|
|
|
|
//
|
|
//---------------------------------
|
|
// If the VTV is dead, make it burn
|
|
//---------------------------------
|
|
//
|
|
VTV
|
|
*vtv = (VTV*)GetOwningSimulation();
|
|
Check(vtv);
|
|
if (damageLevel >= 1.0f)
|
|
{
|
|
vtv->SetSimulationState(VTV::BurningState);
|
|
|
|
//
|
|
// Stamp the death on the vehicle's own step clock, here at the
|
|
// one site that declares it dead. The respawn schedule anchors
|
|
// to this instant - the last step-exact event in the death
|
|
// chain - so the recovery lands the same number of steps after
|
|
// the crash on every run. Marked once; repeated damage while
|
|
// already burning does not move it.
|
|
//
|
|
vtv->MarkDeathClock();
|
|
}
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// If this VTV is linked to a player, send the score loss to that player
|
|
//----------------------------------------------------------------------
|
|
//
|
|
RPPlayer
|
|
*rp_player = (RPPlayer*) vtv->GetPlayerLink();
|
|
if (rp_player)
|
|
{
|
|
Check(rp_player);
|
|
//
|
|
//--------------------------------------------------------
|
|
// If we died, send the I'm dead now message to the player
|
|
//--------------------------------------------------------
|
|
//
|
|
if (
|
|
vtv->GetSimulationState() == VTV::BurningState &&
|
|
vtv->simulationState.GetOldState() != VTV::BurningState
|
|
)
|
|
{
|
|
RPPlayer::VehicleDeadMessage
|
|
dead(
|
|
RPPlayer::VehicleDeadMessageID,
|
|
sizeof(RPPlayer::VehicleDeadMessage)
|
|
);
|
|
rp_player->Dispatch(&dead);
|
|
}
|
|
|
|
//
|
|
//-------------------------------------------
|
|
// Notify this player that damaged was caused
|
|
//-------------------------------------------
|
|
//
|
|
RPPlayer::TakeDamageMessage
|
|
damage_message(
|
|
RPPlayer::TakeDamageMessageID,
|
|
sizeof(RPPlayer::TakeDamageMessage),
|
|
rp_player->GetEntityID(),
|
|
0,
|
|
damage
|
|
);
|
|
rp_player->Dispatch(&damage_message);
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
VTV__DamageZone::VTV__DamageZone(VTV *vtv, int damage_zone_index, MemoryStream *damage_zone_stream) :
|
|
DamageZone(vtv, damage_zone_index, damage_zone_stream)
|
|
{
|
|
Scalar
|
|
dmg_data(vtv->maxImpactSpeed);
|
|
|
|
dmg_data /= 3.6f;
|
|
|
|
damageScale[Damage::CollisionDamageType] =
|
|
2000.0f
|
|
/ vtv->moverMass
|
|
/ (dmg_data*dmg_data)
|
|
/ (
|
|
1.0f
|
|
- vtv->elasticityCoefficient
|
|
* vtv->elasticityCoefficient
|
|
);
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Logical
|
|
VTV__DamageZone::CreateStreamedDamageZone(
|
|
NotationFile *model_file,
|
|
const char *model_name,
|
|
NotationFile *skl_file,
|
|
const char *damage_zone_name,
|
|
MemoryStream *damage_zone_stream,
|
|
NotationFile *dmg_file,
|
|
const ResourceDirectories *directories
|
|
)
|
|
{
|
|
if(!DamageZone::CreateStreamedDamageZone(
|
|
model_file,
|
|
model_name,
|
|
skl_file,
|
|
damage_zone_name,
|
|
damage_zone_stream,
|
|
dmg_file,
|
|
directories
|
|
)
|
|
)
|
|
{
|
|
Check_Fpu();
|
|
return -1;
|
|
}
|
|
Check_Fpu();
|
|
return True;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Logical
|
|
VTV::TestInstance() const
|
|
{
|
|
return IsDerivedFrom(*GetClassDerivations());
|
|
}
|
|
|