Files
RP412/RP/VTV.cpp
T
CydandClaude Fable 5 c479cd48e9 The death cycle is deterministic
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>
2026-08-09 21:42:04 -05:00

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