A scripted lap - full throttle, a steer, a crash at speed, the burn, the tumble, death, respawn, a second crash, a second respawn - now plays out bit-identical between identical runs and across 30 and 144 fps under RP412PHYSICSHZ. Ninety of ninety samples exact in the repro pair, sixty of sixty across frame rates, max difference 0.000000. The crash was already deterministic; this makes the RECOVERY deterministic, and it took five pieces, every one found by measurement: - The respawn teleport moves onto the vehicle's own step grid. VTV::ScheduleRespawn stores it and BeginStep applies it at the first step whose clock reaches the due time, teleport and turn-toward-goal together, because the goal flip reads the POST-reset heading. The old path applied the Reset from the event queue, which runs on wall clock, and identical runs diverged on the first step after the pod stood back up. - The handler keeps its Reset for the FIRST spawn of a mission, gated by a flag rather than by mode. A Mover is born in StasisState and the first Reset is what wakes it; gating on "is fixed stepping on" - the first attempt - skipped that wake-up and parked the pod frozen at its spawn point for an entire race. The scripted-lap harness caught it in one run. - The vehicle stamps its own death clock, at the single site that sets BurningState - inside the step machinery, which is why the crash measured exact. The schedule anchors to the death, the last step-exact event in the chain. - The due time is quantized to a half-second grid ANCHORED AT THE DEATH. The instrument showed the naive anchor was four seconds stale by scheduling time: the fry chain reposts itself at wall-clock Now()+2.0 and the drop-zone reply lands about five sim-seconds after death, jittered by a few steps of queue timing. Firing "next step" inherited that jitter whole. Rounding up to the next half-second after the death puts hundredths of jitter against tenths of headroom, so every run lands in the same cell - and the felt delay stays the six-ish seconds it has always been. - The out-of-world tumble draws from a per-vehicle random stream seeded by creation order. The global Random is shared with the frame loop's consumers - particles, mostly - so its position at the moment a burning pod drew from it depended on how many frames had rendered, and the kick went straight into angular velocity. Last wall-clocked input in the whole death cycle. The respawn scheduling and firing log under RP412PHYSTRACE in run-comparable terms - pad identity, due offset, lateness - because those lines are what cracked this: "due in -4.06 sim-s" said more in one glance than three rounds of hypothesis. Still outside the claim: multi-vehicle contact (DynamicBounce writes the victim's state from the striker's step) and network play. That is the lockstep frontier, and it now has a harness waiting for it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3335 lines
78 KiB
C++
3335 lines
78 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);
|
|
}
|
|
}
|
|
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());
|
|
}
|
|
|