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>
1530 lines
39 KiB
C++
1530 lines
39 KiB
C++
#include "rp.h"
|
|
#pragma hdrstop
|
|
|
|
#include "rpplayer.h"
|
|
#include "..\munga\dropzone.h"
|
|
#include "..\munga\mission.h"
|
|
#include "scorzone.h"
|
|
#include "rpcnsl.h"
|
|
#include "..\munga\app.h"
|
|
#include "vtv.h"
|
|
#include "..\munga\hostmgr.h"
|
|
#include "..\munga\nttmgr.h"
|
|
|
|
//
|
|
// How long the mission stays up after the buzzer, for the Winners Circle.
|
|
// This timer is the only thing holding the simulation and the renderer
|
|
// open once the race is over - when it runs out the player dispatches the
|
|
// StopMission that retires the application. The stock 3 seconds is the
|
|
// fade; the rest is the podium. Kept under the +30s LightsOut post so that
|
|
// never fires while the stand is up.
|
|
//
|
|
const Scalar winnersCircleHoldTime = 11.0f;
|
|
|
|
//#############################################################################
|
|
//######################## RPPlayer__StatusMessage ######################
|
|
//#############################################################################
|
|
MemoryBlock *RPPlayer__StatusMessage::GetAllocatedMemory()
|
|
{
|
|
static MemoryBlock allocatedMemory(sizeof(RPPlayer__StatusMessage), RPPlayer__StatusMessage::numberOfAllocatedBlocks, RPPlayer__StatusMessage::numberOfAllocatedBlocks);
|
|
return &allocatedMemory;
|
|
}
|
|
|
|
//#############################################################################
|
|
//############################### RPPlayer ##############################
|
|
//#############################################################################
|
|
|
|
//#############################################################################
|
|
// Message Support
|
|
//
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// General PlayerType Message Handlers
|
|
//
|
|
const Receiver::HandlerEntry
|
|
RPPlayer::MessageHandlerEntries[]=
|
|
{
|
|
MESSAGE_ENTRY(RPPlayer, TakeDamage),
|
|
MESSAGE_ENTRY(RPPlayer, VehicleDead),
|
|
MESSAGE_ENTRY(RPPlayer, Score),
|
|
MESSAGE_ENTRY(RPPlayer, DropZoneReply),
|
|
MESSAGE_ENTRY(RPPlayer, ScoreZoneReply),
|
|
MESSAGE_ENTRY(RPPlayer, MissionStarting),
|
|
MESSAGE_ENTRY(RPPlayer, MissionEnding)
|
|
};
|
|
|
|
Receiver::MessageHandlerSet& RPPlayer::GetMessageHandlers()
|
|
{
|
|
static Receiver::MessageHandlerSet messageHandlers(ELEMENTS(RPPlayer::MessageHandlerEntries), RPPlayer::MessageHandlerEntries, Player::GetMessageHandlers());
|
|
return messageHandlers;
|
|
}
|
|
|
|
//#############################################################################
|
|
// Shared Data Support
|
|
//
|
|
Derivation* RPPlayer::GetClassDerivations()
|
|
{ static Derivation classDerivations(Player::GetClassDerivations(), "RPPlayer");
|
|
return &classDerivations;
|
|
}
|
|
|
|
|
|
RPPlayer::SharedData
|
|
RPPlayer::DefaultData(
|
|
RPPlayer::GetClassDerivations(),
|
|
RPPlayer::GetMessageHandlers(),
|
|
RPPlayer::GetAttributeIndex(),
|
|
RPPlayer::StateCount,
|
|
(RPPlayer::MakeHandler)RPPlayer::Make
|
|
);
|
|
|
|
|
|
//#############################################################################
|
|
// Attribute Support
|
|
//
|
|
const RPPlayer::IndexEntry
|
|
RPPlayer::AttributePointers[]=
|
|
{
|
|
ATTRIBUTE_ENTRY(RPPlayer, GoalEntity, goalEntity)
|
|
};
|
|
|
|
RPPlayer::AttributeIndexSet& RPPlayer::GetAttributeIndex()
|
|
{
|
|
static RPPlayer::AttributeIndexSet attributeIndex(ELEMENTS(RPPlayer::AttributePointers),
|
|
RPPlayer::AttributePointers,
|
|
Player::GetAttributeIndex()
|
|
);
|
|
return attributeIndex;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
static Origin first_drop;
|
|
Entity *first_goal;
|
|
|
|
void
|
|
RPPlayer::MissionStartingMessageHandler(Message *message)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
Player::MissionStartingMessageHandler(message);
|
|
Check(application);
|
|
if (application->GetApplicationState() == Application::ResumingMission)
|
|
{
|
|
Check(playerVehicle);
|
|
if (playerVehicle->IsDerivedFrom(*VTV::GetClassDerivations()))
|
|
{
|
|
currentScore = 1000.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
first_goal = goalEntity;
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
RPPlayer::MissionEndingMessageHandler(Message *message)
|
|
{
|
|
Player::MissionEndingMessageHandler(message);
|
|
|
|
Check(application);
|
|
|
|
// feed the in-process console the same final score a real console
|
|
// gets (no-op when none is registered)
|
|
if (gConsoleScoreSink != NULL &&
|
|
application->GetApplicationState() == Application::EndingMission)
|
|
{
|
|
(*gConsoleScoreSink)((int) ownerID, (int) currentScore);
|
|
}
|
|
|
|
HostManager *host_manager = application->GetHostManager();
|
|
Check(host_manager);
|
|
Host *console_host = host_manager->GetConsoleHost();
|
|
if (console_host)
|
|
{
|
|
Check(console_host);
|
|
|
|
if (application->GetApplicationState() == Application::EndingMission)
|
|
{
|
|
ConsoleApplicationEndMissionMessage
|
|
score_message(ownerID, (int)currentScore);
|
|
|
|
application->SendMessage(
|
|
console_host->GetHostID(),
|
|
NetworkClient::ConsoleClientID,
|
|
&score_message
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ConsoleApplicationAbortMissionMessage
|
|
score_message(ownerID);
|
|
|
|
application->SendMessage(
|
|
console_host->GetHostID(),
|
|
NetworkClient::ConsoleClientID,
|
|
&score_message
|
|
);
|
|
}
|
|
}
|
|
|
|
lastConsoleUpdate -= 15.0f;
|
|
Check(application);
|
|
if (application->GetApplicationState() == Application::SuspendingMission)
|
|
{
|
|
Check(playerVehicle);
|
|
if (playerVehicle->IsDerivedFrom(*VTV::GetClassDerivations()))
|
|
{
|
|
if (first_goal != goalEntity)
|
|
{
|
|
ScoreZone::ProcessScoreZoneRequestMessage
|
|
message(
|
|
ScoreZone::ProcessScoreZoneRequestMessageID,
|
|
sizeof(ScoreZone::ProcessScoreZoneRequestMessage),
|
|
GetEntityID(),
|
|
ScoreZoneReplyMessageID,
|
|
goalEntity->localOrigin.linearPosition
|
|
);
|
|
goalEntity->Dispatch(&message);
|
|
}
|
|
|
|
VTV *vtv = (VTV*) playerVehicle;
|
|
Check(vtv);
|
|
vtv->Reset(first_drop, True);
|
|
ForceUpdate();
|
|
}
|
|
}
|
|
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// Hold the mission open for the Winners Circle.
|
|
//
|
|
// The base handler sets a 3 second fade, and when it runs out the
|
|
// player dispatches the StopMission that retires the application and
|
|
// takes the renderer with it. That is the only thing keeping the sim
|
|
// and the renderer alive after the race, so the podium gets exactly as
|
|
// long as this timer says. Sim and render both keep running throughout
|
|
// EndingMission - neither has a case for it that bails out.
|
|
//
|
|
// Only on a clean finish: an abort should still leave promptly.
|
|
//
|
|
if (application->GetApplicationState() == Application::EndingMission)
|
|
{
|
|
fadeTimeRemaining = winnersCircleHoldTime;
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void RPPlayer::CreatePlayerVehicle(Origin vtv_location)
|
|
{
|
|
Check(this);
|
|
Check(&vtv_location);
|
|
Check(application);
|
|
ResourceFile *resources = application->GetResourceFile();
|
|
Check(resources);
|
|
Check_Pointer(playerMission->GetGameModel());
|
|
|
|
HostManager *host_manager = application->GetHostManager();
|
|
Check(host_manager);
|
|
//
|
|
// Create MUNGA level vehicles
|
|
//
|
|
Player::CreatePlayerVehicle(vtv_location);
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// If the player has made no vehicle yet, make him a new one and link the
|
|
// player to it
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
if (!playerVehicle)
|
|
{
|
|
ResourceDescription *vtv_res =
|
|
resources->FindResourceDescription(
|
|
playerMission->GetGameModel(),
|
|
ResourceDescription::ModelListResourceType
|
|
);
|
|
Check(vtv_res);
|
|
vtv_res->Lock();
|
|
|
|
VTV::MakeMessage
|
|
create_player(
|
|
VTV::MakeMessageID,
|
|
sizeof(VTV::MakeMessage),
|
|
EntityID(host_manager->GetLocalHostID()),
|
|
VTV::VTVClassID,
|
|
EntityID::Null,
|
|
vtv_res->resourceID,
|
|
VTV::DefaultFlags|VTV::InitialStasisFlag,
|
|
vtv_location,
|
|
Motion::Identity,
|
|
Motion::Identity,
|
|
playerMission->GetBadgeName(),
|
|
playerMission->GetColorName()
|
|
);
|
|
|
|
vtv_res->Unlock();
|
|
|
|
playerVehicle = application->MakeAndLinkViewpointEntity(&create_player);
|
|
Register_Object(playerVehicle);
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void RPPlayer::InitializePlayerLink()
|
|
{
|
|
Check(this);
|
|
|
|
PlayerLinkMessage player_link_message (
|
|
PlayerLinkMessageID,
|
|
sizeof(PlayerLinkMessage),
|
|
this->GetEntityID()
|
|
);
|
|
Check(playerVehicle);
|
|
playerVehicle->Dispatch(&player_link_message);
|
|
playerVehicle->DispatchToReplicants(&player_link_message);
|
|
Check_Fpu();
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void RPPlayer::SetGoalToFirstScoreZone()
|
|
{
|
|
//
|
|
//----------------------------------------------------------
|
|
// Change the goalEntity to the first sequential scorezone
|
|
//----------------------------------------------------------
|
|
//
|
|
EntityGroup *score_zone_group =
|
|
application->GetEntityManager()->FindGroup("ActiveScoreZones");
|
|
if(score_zone_group)
|
|
{
|
|
ChainIteratorOf<Node*> iterator(score_zone_group->groupMembers);
|
|
ScoreZone *score_zone;
|
|
while ((score_zone = (ScoreZone*)iterator.ReadAndNext()) != NULL)
|
|
{
|
|
if (
|
|
(
|
|
(score_zone->scoreZoneType == LocalSequential) ||
|
|
(score_zone->scoreZoneType == GlobalSequential)
|
|
) &&
|
|
score_zone->sequenceNumber == 1
|
|
)
|
|
{
|
|
goalEntity = score_zone;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void RPPlayer::PointVTVTowardGoal()
|
|
{
|
|
Check(this);
|
|
|
|
if (!goalEntity)
|
|
{
|
|
return;
|
|
}
|
|
Vector3D to_goal;
|
|
|
|
to_goal.Subtract(
|
|
goalEntity->localOrigin.linearPosition,
|
|
playerVehicle->localOrigin.linearPosition
|
|
);
|
|
|
|
UnitVector current_heading;
|
|
playerVehicle->localToWorld.GetFromAxis(Z_Axis, ¤t_heading);
|
|
|
|
Scalar length_to_goal;
|
|
|
|
length_to_goal = to_goal.LengthSquared();
|
|
if (length_to_goal > SMALL)
|
|
{
|
|
Scalar dot_prod;
|
|
|
|
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);
|
|
|
|
Check(playerVehicle);
|
|
turn_around.Multiply(
|
|
playerVehicle->localOrigin.angularPosition,
|
|
y_roll
|
|
);
|
|
playerVehicle->localOrigin.angularPosition = turn_around;
|
|
playerVehicle->localToWorld = playerVehicle->localOrigin;
|
|
playerVehicle->ForceUpdate();
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void RPPlayer::ResetAfterDeath(DropZone::ReplyMessage *message)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
Check(application);
|
|
message->deathCount = ++deathCount;
|
|
ForceUpdate();
|
|
SetSimulationState(DropZoneAcquiredState);
|
|
dropZoneLocation = message->dropZoneLocation;
|
|
|
|
//
|
|
//------------------------------------------------------------------
|
|
// Fixed-step: the RECOVERY goes on the vehicle's own step grid.
|
|
//
|
|
// The event queue runs on wall clock, so a Reset fired from it
|
|
// lands between different sim steps on every run - the crash was
|
|
// measured bit-identical between runs and the first divergence was
|
|
// the step after the pod stood back up. The vehicle applies the
|
|
// teleport itself at the first step one SIM second after now; the
|
|
// message still makes its round trip below, but only for the
|
|
// player-state bookkeeping - the handler leaves the physics to the
|
|
// schedule it can see is pending.
|
|
//------------------------------------------------------------------
|
|
//
|
|
if (Simulation::FixedStep() > (Scalar) 0 &&
|
|
playerVehicle != NULL && playerVehicle->GetClassID() == VTVClassID)
|
|
{
|
|
VTV *vtv = (VTV *) playerVehicle;
|
|
|
|
//
|
|
// Anchored to the DEATH and quantized to a half-second grid.
|
|
//
|
|
// This handler runs when the drop-zone reply finally comes off
|
|
// the event queue, and the whole death-to-here chain is wall
|
|
// clock - the fry retries repost at Now()+2.0, and the measured
|
|
// arrival is about five sim-seconds after death, give or take a
|
|
// few STEPS of queue jitter. An anchor of death+1.0 is long past
|
|
// by then, so "fire at the next step" inherited the jitter
|
|
// whole.
|
|
//
|
|
// The death stamp is the last step-exact event in the chain, so:
|
|
// take the measured gap, add the second the old code waited, and
|
|
// round UP to the next half-second AFTER THE DEATH. The jitter
|
|
// is hundredths; the nearest grid boundary is tenths away; every
|
|
// run lands in the same cell and fires on the same step. The
|
|
// felt delay is the same six-ish seconds it has always been.
|
|
//
|
|
Time due;
|
|
Time death_mark;
|
|
if (vtv->ConsumeDeathClock(&death_mark))
|
|
{
|
|
Scalar gap = vtv->GetLastPerformance() - death_mark;
|
|
const Scalar quantum = (Scalar) 0.5;
|
|
int cells = (int)((gap + (Scalar) 1.0) / quantum) + 1;
|
|
|
|
due = death_mark;
|
|
due += quantum * (Scalar) cells;
|
|
}
|
|
else
|
|
{
|
|
due = vtv->GetLastPerformance();
|
|
due += 1.0f;
|
|
}
|
|
vtv->ScheduleRespawn(
|
|
message->dropZoneLocation, due,
|
|
(goalEntity != NULL)
|
|
? goalEntity->localOrigin.linearPosition
|
|
: Point3D(0.0f, 0.0f, 0.0f),
|
|
(goalEntity != NULL) ? True : False);
|
|
respawnScheduled = True;
|
|
|
|
//
|
|
// Under the trace, say what was scheduled in run-comparable
|
|
// terms: the pad (identity by position), and how far ahead of
|
|
// the vehicle's clock the due time sits. Two runs that disagree
|
|
// here diverge before the physics gets a vote.
|
|
//
|
|
{
|
|
static int diag = -1;
|
|
if (diag < 0)
|
|
{
|
|
const char *setting = getenv("RP412PHYSTRACE");
|
|
diag = (setting != NULL && atoi(setting) != 0) ? 1 : 0;
|
|
}
|
|
if (diag)
|
|
{
|
|
char buffer[160];
|
|
sprintf(buffer,
|
|
"PhysTrace: respawn #%d scheduled, pad %.2f,%.2f "
|
|
"due in %.4f sim-s\n",
|
|
deathCount,
|
|
(double) message->dropZoneLocation.linearPosition.x,
|
|
(double) message->dropZoneLocation.linearPosition.z,
|
|
(double)(Scalar)(due - vtv->GetLastPerformance()));
|
|
DEBUG_STREAM << buffer << std::flush;
|
|
}
|
|
}
|
|
}
|
|
|
|
Time when = Now();
|
|
when += 1.0f;
|
|
application->Post(HighEventPriority, this, message, when);
|
|
Check_Fpu();
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
RPPlayer::DropZoneReplyMessageHandler(
|
|
DropZone::ReplyMessage *message
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// If the player has made no vehicle yet, make him a new one and link the
|
|
// player to it
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
if (!playerVehicle)
|
|
{
|
|
first_drop = message->dropZoneLocation;
|
|
CreatePlayerVehicle(message->dropZoneLocation);
|
|
InitializePlayerLink();
|
|
SetGoalToFirstScoreZone();
|
|
switch(playerVehicle->GetClassID())
|
|
{
|
|
case VTVClassID:
|
|
SetPerformance(&RPPlayer::PlayerSimulation);
|
|
break;
|
|
case CameraShipClassID:
|
|
SetPerformance(&RPPlayer::CameraShipSimulation);
|
|
playerRanking = -1;
|
|
break;
|
|
}
|
|
AlwaysExecute();
|
|
deathCount = 0;
|
|
respawnScheduled = False;
|
|
}
|
|
|
|
//
|
|
//-------------------------------------------------------------------------
|
|
// Otherwise, just set the new position of the VTV and turn off the burning
|
|
// stuff.
|
|
//-------------------------------------------------------------------------
|
|
//
|
|
else if (deathCount == message->deathCount)
|
|
{
|
|
if (GetSimulationState() == MissionEndingState)
|
|
{
|
|
Check_Fpu();
|
|
return;
|
|
}
|
|
if (GetSimulationState() != DropZoneAcquiredState)
|
|
{
|
|
ResetAfterDeath(message);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
//---------------------------------
|
|
// Make sure we ignore old messages
|
|
//---------------------------------
|
|
//
|
|
else
|
|
{
|
|
Check_Fpu();
|
|
return;
|
|
}
|
|
|
|
//
|
|
//-----------------------------------
|
|
// Delete All offensivePlayers
|
|
//-----------------------------------
|
|
//
|
|
DeleteAllOffensivePlayers();
|
|
ForceUpdate();
|
|
SetSimulationState(VehicleTranslocatedState);
|
|
if (playerVehicle->GetClassID() == VTVClassID)
|
|
{
|
|
VTV *vtv = (VTV*)playerVehicle;
|
|
Check(vtv);
|
|
|
|
//
|
|
// A SCHEDULED respawn means the vehicle already holds - or has
|
|
// already applied - its teleport and goal-flip, on its own step
|
|
// grid. Resetting it AGAIN here, at whatever wall instant this
|
|
// message came off the queue, would re-teleport it mid-step and
|
|
// put the nondeterminism straight back.
|
|
//
|
|
// The flag, not "is fixed stepping on": this same leg also runs
|
|
// for the FIRST spawn of the mission, where the Reset below is
|
|
// what wakes a Mover out of its initial stasis. Gating on the
|
|
// mode alone skipped that wake-up and parked the pod, frozen at
|
|
// exactly its spawn point, for an entire race.
|
|
//
|
|
if (respawnScheduled)
|
|
{
|
|
respawnScheduled = False;
|
|
Check_Fpu();
|
|
return;
|
|
}
|
|
vtv->Reset(message->dropZoneLocation, VTV::RegularReset);
|
|
}
|
|
|
|
//
|
|
//---------------------------------------------------
|
|
// Always Point vtv in the direction of the scorezone
|
|
//---------------------------------------------------
|
|
//
|
|
PointVTVTowardGoal();
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// GetPlayer
|
|
//
|
|
RPPlayer*
|
|
RPPlayer::GetPlayer(EntityID playerID)
|
|
{
|
|
Check(this);
|
|
Check(&playerID);
|
|
|
|
//
|
|
//---------------------------------------------------
|
|
// Get the pointer to the RPPlayer from the playerID
|
|
//---------------------------------------------------
|
|
//
|
|
Check(application);
|
|
HostManager *host = application->GetHostManager();
|
|
Check(host);
|
|
RPPlayer *rp_player = Cast_Object(
|
|
RPPlayer*,
|
|
host->GetEntityPointer(playerID)
|
|
);
|
|
Check(rp_player);
|
|
Check_Fpu();
|
|
return rp_player;
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
RPPlayer::DeleteAllOffensivePlayers()
|
|
{
|
|
VChainIteratorOf<OffensivePlayer*, Enumeration>
|
|
iterator(offensivePlayerList);
|
|
OffensivePlayer
|
|
*current_player;
|
|
|
|
while ((current_player = iterator.GetCurrent()) != NULL)
|
|
{
|
|
Unregister_Object(current_player);
|
|
delete current_player;
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
RPPlayer::CleanUpOffensivePlayers()
|
|
{
|
|
VChainIteratorOf<OffensivePlayer*, Enumeration>
|
|
iterator(offensivePlayerList);
|
|
OffensivePlayer
|
|
*current_player;
|
|
|
|
while ((current_player = iterator.GetCurrent()) != NULL)
|
|
{
|
|
Check(current_player);
|
|
if(Now() - current_player->timeStamp >= 2.0f)
|
|
{
|
|
OffensivePlayer *stale_player = iterator.GetCurrent();
|
|
Unregister_Object(stale_player);
|
|
delete stale_player;
|
|
}
|
|
else
|
|
{
|
|
iterator.Next();
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
RPPlayer::TakeDamageMessageHandler (TakeDamageMessage *message)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
//
|
|
//-------------------------------------------------------
|
|
// Scale the damage amount to point loss on this vehicle
|
|
//-------------------------------------------------------
|
|
//
|
|
VTV*
|
|
our_vtv = (VTV*)playerVehicle;
|
|
Check(our_vtv);
|
|
|
|
if (GetSimulationState() != MissionEndingState)
|
|
{
|
|
Scalar score_loss = message->damageData.damageAmount;
|
|
if (message->damageData.damageType == Damage::CollisionDamageType)
|
|
{
|
|
score_loss *= our_vtv->deathConstant;
|
|
}
|
|
|
|
ScoreMessage score_message(
|
|
RPPlayer::ScoreMessageID,
|
|
sizeof(RPPlayer::ScoreMessageID),
|
|
DamageScoreLossPointType,
|
|
-score_loss,
|
|
GetEntityID()
|
|
);
|
|
Dispatch(&score_message);
|
|
|
|
//
|
|
//-----------------------------------------------------------
|
|
// Clean up the list, and look for a player to give points to
|
|
//-----------------------------------------------------------
|
|
//
|
|
CleanUpOffensivePlayers();
|
|
|
|
VChainIteratorOf<OffensivePlayer*, Enumeration>
|
|
iterator(offensivePlayerList);
|
|
if(iterator.GetCurrent())
|
|
{
|
|
//
|
|
//--------------------------------------------------------
|
|
// Send a score message to the player at the top
|
|
// of the offensivePlayer list the points for this damage
|
|
//--------------------------------------------------------
|
|
//
|
|
OffensivePlayer *top_player = iterator.GetCurrent();
|
|
RPPlayer *player_points = top_player->offensivePlayer;
|
|
if (
|
|
top_player->deathBonus
|
|
&& top_player->timeStamp < ((RPPlayer*)our_vtv)->lastPerformance
|
|
&& top_player->offensivePlayer != this
|
|
)
|
|
{
|
|
if (top_player->deathBonus < 0.0f)
|
|
{
|
|
score_loss = -score_loss;
|
|
}
|
|
|
|
ScoreMessage score_message(
|
|
RPPlayer::ScoreMessageID,
|
|
sizeof(RPPlayer::ScoreMessage),
|
|
DamageBonusPointType,
|
|
score_loss,
|
|
GetEntityID()
|
|
);
|
|
if (score_loss < 0.0f)
|
|
{
|
|
score_message.scoreType = DropZoneHitType;
|
|
}
|
|
player_points->Dispatch(&score_message);
|
|
|
|
Check(application);
|
|
HostManager *host_manager = application->GetHostManager();
|
|
Check(host_manager);
|
|
Host *console_host = host_manager->GetConsoleHost();
|
|
if (console_host)
|
|
{
|
|
Check(console_host);
|
|
|
|
ConsolePlayerVTVDamagedMessage
|
|
scored_message(
|
|
ownerID,
|
|
player_points->ownerID,
|
|
score_loss,
|
|
score_loss
|
|
);
|
|
|
|
application->SendMessage(
|
|
console_host->GetHostID(),
|
|
NetworkClient::ConsoleClientID,
|
|
&scored_message
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
RPPlayer::AddOffensivePlayer(OffensivePlayer &offensive_player)
|
|
{
|
|
Check(this);
|
|
Check(&offensive_player);
|
|
|
|
//
|
|
//----------------------------------------------------------------
|
|
// Calculate the deathBonus based on playerType and damageType
|
|
//----------------------------------------------------------------
|
|
//
|
|
if(offensive_player.damageType == Damage::CollisionDamageType)
|
|
{
|
|
offensive_player.deathBonus = 500.0f;
|
|
}
|
|
else
|
|
{
|
|
offensive_player.deathBonus = 0.0f;
|
|
}
|
|
|
|
offensivePlayerList.AddValue(
|
|
&offensive_player,
|
|
offensive_player.damageType
|
|
);
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
RPPlayer::ScoreMessageHandler(ScoreMessage *message)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
Check(playerVehicle);
|
|
|
|
if (GetSimulationState() == MissionEndingState)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
//-------------------------------------------------------------------------
|
|
// If this is a score zone message, tell the console (if there is one) that
|
|
// we scored
|
|
//-------------------------------------------------------------------------
|
|
//
|
|
Check(application);
|
|
HostManager *host_manager = application->GetHostManager();
|
|
Check(host_manager);
|
|
|
|
switch (message->scoreType)
|
|
{
|
|
case ScoreZonePointType:
|
|
{
|
|
Host
|
|
*console_host = host_manager->GetConsoleHost();
|
|
if (console_host)
|
|
{
|
|
Check(console_host);
|
|
|
|
ConsolePlayerVTVScoredMessage
|
|
scored_message(ownerID);
|
|
|
|
application->SendMessage(
|
|
console_host->GetHostID(),
|
|
NetworkClient::ConsoleClientID,
|
|
&scored_message
|
|
);
|
|
}
|
|
//-------------------------------------
|
|
// Inform pilot of score zone entry
|
|
//-------------------------------------
|
|
RPPlayer
|
|
*player =
|
|
(RPPlayer*)host_manager->GetEntityPointer(message->pointSender);
|
|
Check(player);
|
|
|
|
if (player->playerType != RunnerPlayerType)
|
|
{
|
|
RPStatusMessage *message = new RPStatusMessage(NULL, RPStatusMessage::ScoreZoneMessage, 6.0f);
|
|
Register_Object(message);
|
|
AddStatusMessage((StatusMessage *) message);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DeathBonusPointType:
|
|
case DropZoneKillType:
|
|
{
|
|
//-------------------------------------
|
|
// Inform pilot of killer's identity
|
|
//-------------------------------------
|
|
RPPlayer
|
|
*killer =
|
|
(RPPlayer*)host_manager->GetEntityPointer(message->pointSender);
|
|
Check(killer);
|
|
|
|
RPStatusMessage *message = new RPStatusMessage(killer, RPStatusMessage::DestroyedMessage, 6.0f);
|
|
Register_Object(message);
|
|
AddStatusMessage((StatusMessage *) message);
|
|
|
|
}
|
|
break;
|
|
}
|
|
|
|
Player::ScoreMessageHandler(message);
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
RPPlayer::VehicleDeadMessageHandler(VehicleDeadMessage *message)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
if (GetSimulationState() == MissionEndingState)
|
|
{
|
|
Check_Fpu();
|
|
return;
|
|
}
|
|
|
|
//
|
|
//-------------------------------------------------------------------------
|
|
// If this is not the initial death notice, or we are still looking for our
|
|
// initial dropzone, let the player handler do its thing
|
|
//-------------------------------------------------------------------------
|
|
//
|
|
if (message->deathCount != -1)
|
|
{
|
|
Player::VehicleDeadMessageHandler(message);
|
|
Check_Fpu();
|
|
return;
|
|
}
|
|
|
|
//
|
|
//-------------------------
|
|
// Reset the VTV subsystems
|
|
//-------------------------
|
|
//
|
|
Check(playerVehicle);
|
|
VTV
|
|
*vtv = (VTV*) playerVehicle;
|
|
Check(vtv);
|
|
if ((playerType == CrusherPlayerType) ||
|
|
(playerType == BlockerPlayerType)
|
|
)
|
|
{
|
|
vtv->DeathShutdown(VTV::FootballReset);
|
|
}
|
|
else
|
|
{
|
|
vtv->DeathShutdown(VTV::RegularReset);
|
|
}
|
|
|
|
//
|
|
//----------------------------------------------------
|
|
// See if any other players get points for this death
|
|
//----------------------------------------------------
|
|
//
|
|
CleanUpOffensivePlayers();
|
|
|
|
HostID
|
|
killer_id = ownerID;
|
|
VChainIteratorOf<OffensivePlayer*, Enumeration>
|
|
iterator(offensivePlayerList);
|
|
if (iterator.GetCurrent())
|
|
{
|
|
//
|
|
//----------------------------------------------------
|
|
// Yes, send score messages...
|
|
//----------------------------------------------------
|
|
//
|
|
OffensivePlayer
|
|
*offensive_player = iterator.GetCurrent();
|
|
Check(offensive_player);
|
|
|
|
ScoreMessage
|
|
score_message(
|
|
RPPlayer::ScoreMessageID,
|
|
sizeof(RPPlayer::ScoreMessage),
|
|
DeathBonusPointType,
|
|
offensive_player->deathBonus,
|
|
GetEntityID()
|
|
);
|
|
if (offensive_player->deathBonus < 0.0f)
|
|
{
|
|
score_message.scoreType = DropZoneKillType;
|
|
}
|
|
|
|
RPPlayer
|
|
*other_player = offensive_player->offensivePlayer;
|
|
Check(other_player);
|
|
other_player->Dispatch(&score_message);
|
|
|
|
Check(application);
|
|
HostManager *host_manager = application->GetHostManager();
|
|
Check(host_manager);
|
|
Host *console_host = host_manager->GetConsoleHost();
|
|
if (console_host)
|
|
{
|
|
Check(console_host);
|
|
|
|
ConsolePlayerVTVDamagedMessage
|
|
scored_message(
|
|
ownerID,
|
|
other_player->ownerID,
|
|
offensive_player->deathBonus,
|
|
offensive_player->deathBonus
|
|
);
|
|
|
|
application->SendMessage(
|
|
console_host->GetHostID(),
|
|
NetworkClient::ConsoleClientID,
|
|
&scored_message
|
|
);
|
|
}
|
|
|
|
//
|
|
//----------------------------------------------------
|
|
// ...keep track of the killer...
|
|
//----------------------------------------------------
|
|
//
|
|
killer_id = other_player->ownerID;
|
|
//
|
|
//----------------------------------------------------
|
|
// ...and send a notice to the pilot.
|
|
//----------------------------------------------------
|
|
//
|
|
RPStatusMessage *message = new RPStatusMessage(other_player, RPStatusMessage::DestroyedByMessage, 6.0f);
|
|
Register_Object(message);
|
|
AddStatusMessage((StatusMessage *)message);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
//----------------------------------------------------
|
|
// No, tell the pilot that it was self-induced.
|
|
//----------------------------------------------------
|
|
//
|
|
RPStatusMessage *message = new RPStatusMessage(NULL, RPStatusMessage::PilotErrorMessage, 6.0f);
|
|
Register_Object(message);
|
|
AddStatusMessage((StatusMessage *) message);
|
|
}
|
|
|
|
//
|
|
//------------------------------------------------
|
|
// Tell the console (if there is one) that we died
|
|
//------------------------------------------------
|
|
//
|
|
Check(application);
|
|
HostManager
|
|
*host_manager = application->GetHostManager();
|
|
Check(host_manager);
|
|
|
|
Host
|
|
*console_host = host_manager->GetConsoleHost();
|
|
if (console_host)
|
|
{
|
|
Check(console_host);
|
|
|
|
ConsolePlayerVTVKilledMessage killed_message(ownerID, killer_id);
|
|
|
|
application->SendMessage(
|
|
console_host->GetHostID(),
|
|
NetworkClient::ConsoleClientID,
|
|
&killed_message
|
|
);
|
|
}
|
|
|
|
//
|
|
//------------------------------------------------------------------------
|
|
// Send a score message of deathpointloss, and notify ourselves again in 5
|
|
// seconds so we can begin looking for the drop zone
|
|
//------------------------------------------------------------------------
|
|
//
|
|
ScoreMessage
|
|
score_message(
|
|
RPPlayer::ScoreMessageID,
|
|
sizeof(RPPlayer::ScoreMessage),
|
|
DeathScoreLossPointType,
|
|
-vtv->deathScoreLoss,
|
|
GetEntityID()
|
|
);
|
|
Dispatch(&score_message);
|
|
|
|
Time when = Now();
|
|
when += 5.0f;
|
|
message->deathCount = deathCount;
|
|
application->Post(HighEventPriority, this, message, when);
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
RPPlayer::ScoreZoneReplyMessageHandler(
|
|
ScoreZone::ReplyMessage *message
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
if (GetSimulationState() == MissionEndingState)
|
|
{
|
|
Check_Fpu();
|
|
return;
|
|
}
|
|
|
|
//
|
|
//-------------------------------------------------
|
|
// Send Score message if score zone was covered
|
|
//-------------------------------------------------
|
|
//
|
|
ScoreMessage
|
|
score_message(
|
|
RPPlayer::ScoreMessageID,
|
|
sizeof(RPPlayer::ScoreMessageID),
|
|
ScoreZonePointType,
|
|
message->pointValue,
|
|
GetEntityID()
|
|
);
|
|
Dispatch(&score_message);
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Change the goalEntity to the new sequential scorezone if necessary
|
|
//--------------------------------------------------------------------
|
|
//
|
|
|
|
EntityGroup
|
|
*score_zone_group =
|
|
application->GetEntityManager()->FindGroup("ActiveScoreZones");
|
|
if(score_zone_group)
|
|
{
|
|
ChainIteratorOf<Node*>
|
|
iterator(score_zone_group->groupMembers);
|
|
ScoreZone
|
|
*score_zone;
|
|
|
|
while ((score_zone = (ScoreZone*) iterator.ReadAndNext()) != NULL)
|
|
{
|
|
Check(score_zone);
|
|
if (
|
|
(score_zone->scoreZoneType == LocalSequential) ||
|
|
(score_zone->scoreZoneType == GlobalSequential)
|
|
)
|
|
{
|
|
goalEntity = score_zone;
|
|
}
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
RPPlayer::PlayerSimulation(Scalar time_slice)
|
|
{
|
|
Check(this);
|
|
Check(application);
|
|
Check(application->GetEntityManager());
|
|
Check(playerVehicle);
|
|
|
|
//
|
|
//------------------------------------------------------------------------
|
|
// Check for our synchronization signal
|
|
//------------------------------------------------------------------------
|
|
//
|
|
if (
|
|
(GetSimulationState() == MissionStartingState) &&
|
|
((fadeTimeRemaining - time_slice) <= 0.0f)
|
|
)
|
|
{
|
|
lastConsoleUpdate = Now();
|
|
}
|
|
|
|
if (!goalEntity)
|
|
{
|
|
SetGoalToFirstScoreZone();
|
|
PointVTVTowardGoal();
|
|
}
|
|
|
|
//
|
|
//------------------------------------------------------------------------
|
|
// Call PlayerSimulation
|
|
//------------------------------------------------------------------------
|
|
//
|
|
Player::PlayerSimulation(time_slice);
|
|
if (
|
|
application->GetApplicationState() != Application::RunningMission
|
|
&& application->GetApplicationState() != Application::EndingMission
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
//------------------------------------------------------------------------
|
|
// Update everybody once per second
|
|
//------------------------------------------------------------------------
|
|
//
|
|
if (lastPerformance - lastUpdate >= 1.0f)
|
|
{
|
|
ForceUpdate();
|
|
}
|
|
|
|
//
|
|
//------------------------------------------------------------------------
|
|
// Update the console every 15 seconds
|
|
//------------------------------------------------------------------------
|
|
//
|
|
if (lastPerformance - lastConsoleUpdate >= 15.0f)
|
|
{
|
|
lastConsoleUpdate = lastPerformance;
|
|
|
|
//
|
|
//---------------------------------------------
|
|
// Tell the console (if there is one) our score
|
|
//---------------------------------------------
|
|
//
|
|
Check(application);
|
|
HostManager *host_manager = application->GetHostManager();
|
|
Check(host_manager);
|
|
Host *console_host = host_manager->GetConsoleHost();
|
|
if (console_host)
|
|
{
|
|
Check(console_host);
|
|
|
|
ConsolePlayerVTVScoreUpdateMessage
|
|
score_message(ownerID, (int)currentScore);
|
|
|
|
application->SendMessage(
|
|
console_host->GetHostID(),
|
|
NetworkClient::ConsoleClientID,
|
|
&score_message
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
//------------------------------------------------------------------------
|
|
// See if there are any zones to check
|
|
//------------------------------------------------------------------------
|
|
//
|
|
if (
|
|
GetSimulationState() != MissionEndingState
|
|
&& application->GetApplicationState() == Application::RunningMission
|
|
)
|
|
{
|
|
EntityGroup *score_zone_group =
|
|
application->GetEntityManager()->FindGroup("ActiveScoreZones");
|
|
if(!score_zone_group)
|
|
{
|
|
Check_Fpu();
|
|
return;
|
|
}
|
|
//
|
|
//----------------------------------
|
|
// If VTV is in a BurningState ie. dead
|
|
// Exit from loop and don't check
|
|
// score zones
|
|
//----------------------------------
|
|
//
|
|
VTV *vtv = (VTV*)playerVehicle;
|
|
Check(vtv);
|
|
if (vtv->GetSimulationState() == VTV::BurningState)
|
|
{
|
|
Check_Fpu();
|
|
return;
|
|
}
|
|
|
|
//
|
|
//----------------------------------
|
|
// Make an iterator and send the
|
|
// "do I get points" message to
|
|
// all the active score zones
|
|
//----------------------------------
|
|
//
|
|
Check(score_zone_group);
|
|
ChainIteratorOf<Node*>
|
|
iterator(score_zone_group->groupMembers);
|
|
ScoreZone::ProcessScoreZoneRequestMessage
|
|
message(
|
|
ScoreZone::ProcessScoreZoneRequestMessageID,
|
|
sizeof(ScoreZone::ProcessScoreZoneRequestMessage),
|
|
GetEntityID(),
|
|
ScoreZoneReplyMessageID,
|
|
playerVehicle->localOrigin.linearPosition
|
|
);
|
|
ScoreZone
|
|
*score_zone;
|
|
while ((score_zone = (ScoreZone*)iterator.ReadAndNext()) != NULL)
|
|
{
|
|
score_zone->Dispatch(&message);
|
|
}
|
|
|
|
//
|
|
//---------------------------------
|
|
// Get Points for speed if
|
|
// going in the right direction
|
|
//---------------------------------
|
|
//
|
|
AccumulateSpeedPoints(time_slice);
|
|
Check_Fpu();
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
int
|
|
RPPlayer::CalcRanking()
|
|
{
|
|
Check(this);
|
|
Player::CalcRanking();
|
|
Check_Fpu();
|
|
return 1;
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
RPPlayer::CalcFootballRanking()
|
|
{
|
|
//
|
|
//---------------------------------------
|
|
// Get all players from the Runner group
|
|
//---------------------------------------
|
|
//
|
|
EntityGroup
|
|
*runner_group =
|
|
application->GetEntityManager()->FindGroup("RunnerPlayers");
|
|
Check(runner_group);
|
|
Player *active_runner;
|
|
//
|
|
//-----------------------------------------------------
|
|
// Iterate through the players add to the sorted chain
|
|
//-----------------------------------------------------
|
|
//
|
|
ChainIteratorOf<Node*>
|
|
iterator(runner_group->groupMembers);
|
|
VChainOf<Player*, Scalar>
|
|
player_rank(NULL, False);
|
|
|
|
while ((active_runner = (Player*) iterator.ReadAndNext()) != NULL)
|
|
{
|
|
player_rank.AddValue(active_runner, active_runner->currentScore);
|
|
}
|
|
|
|
int num_runners = iterator.GetSize() - 1;
|
|
//
|
|
//------------------------------------------------
|
|
// Iterate through the chain assigning the rank
|
|
//------------------------------------------------
|
|
//
|
|
VChainIteratorOf<Player*, Scalar>
|
|
rank_iterator(player_rank);
|
|
|
|
while ((active_runner = rank_iterator.ReadAndNext()) != NULL)
|
|
{
|
|
active_runner->playerRanking = num_runners;
|
|
--num_runners;
|
|
}
|
|
|
|
Check_Fpu();
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
RPPlayer::AccumulateSpeedPoints(Scalar time_slice)
|
|
{
|
|
Check(this);
|
|
Scalar
|
|
compression=1.0f;
|
|
|
|
if(scoreCompression)
|
|
{
|
|
Scalar
|
|
top_score=-1.0f;
|
|
EntityGroup
|
|
*player_group =
|
|
application->GetEntityManager()->FindGroup("Players");
|
|
Check(player_group);
|
|
|
|
Player
|
|
*active_player;
|
|
//
|
|
//-----------------------------------------------------
|
|
// Iterate through the players find top ranked player
|
|
//-----------------------------------------------------
|
|
//
|
|
ChainIteratorOf<Node*>
|
|
iterator(player_group->groupMembers);
|
|
while ((active_player = (Player*) iterator.ReadAndNext()) != NULL)
|
|
{
|
|
if (active_player->playerRanking == 0)
|
|
{
|
|
top_score = active_player->currentScore;
|
|
break;
|
|
}
|
|
}
|
|
if (currentScore < (top_score - 400.0f))
|
|
{
|
|
compression *= ( (top_score - currentScore) + 400.0f)/800;
|
|
}
|
|
}
|
|
|
|
//
|
|
//------------------------------------------------------
|
|
// Get Points for speed if going in the right direction
|
|
//------------------------------------------------------
|
|
//
|
|
Check(playerVehicle);
|
|
Check(goalEntity);
|
|
Vector3D
|
|
to_goal,
|
|
current_heading;
|
|
|
|
VTV
|
|
*vtv = Cast_Object(VTV*, playerVehicle);
|
|
|
|
to_goal.Subtract(
|
|
vtv->localOrigin.linearPosition,
|
|
goalEntity->localOrigin.linearPosition);
|
|
|
|
current_heading = vtv->worldLinearVelocity;
|
|
Scalar
|
|
length_to_goal,
|
|
length_current_heading;
|
|
|
|
length_to_goal = to_goal.LengthSquared();
|
|
length_current_heading = current_heading.LengthSquared();
|
|
if (length_to_goal > SMALL && length_current_heading > SMALL)
|
|
{
|
|
Scalar
|
|
dot_prod = (to_goal * current_heading) /
|
|
(Sqrt(length_to_goal) * Sqrt(length_current_heading));
|
|
|
|
if (dot_prod <= 0.985f)
|
|
{
|
|
Scalar
|
|
speed_bonus = length_current_heading;
|
|
speed_bonus /= POINTS_SPEED_RATIO;
|
|
speed_bonus *= time_slice * compression;
|
|
|
|
//
|
|
//-------------------------------------------
|
|
// Send a score message with the speed bonus
|
|
//-------------------------------------------
|
|
//
|
|
ScoreMessage
|
|
score_message(
|
|
RPPlayer::ScoreMessageID,
|
|
sizeof(RPPlayer::ScoreMessageID),
|
|
SpeedBonusPointType,
|
|
speed_bonus,
|
|
GetEntityID()
|
|
);
|
|
if (!vtv->insideWorld)
|
|
{
|
|
score_message.scoreAward = -speed_bonus;
|
|
score_message.scoreType = OutOfBoundsType;
|
|
}
|
|
Dispatch(&score_message);
|
|
}
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//#############################################################################
|
|
// Construction and Destruction
|
|
//
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
RPPlayer::RPPlayer(
|
|
RPPlayer::MakeMessage *creation_message,
|
|
SharedData &shared_data
|
|
):
|
|
Player(creation_message, shared_data),
|
|
offensivePlayerList(NULL, 0)
|
|
{
|
|
currentScore = 1000.0f;
|
|
lastConsoleUpdate = Now();
|
|
goalEntity = NULL;
|
|
|
|
//
|
|
//--------------------------------
|
|
// Get the info from the mission
|
|
//--------------------------------
|
|
//
|
|
teamID = creation_message->teamID;
|
|
scoreCompression = creation_message->scoreCompression;
|
|
playerType = (PlayerType) creation_message->playerType;
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
RPPlayer*
|
|
RPPlayer::Make(RPPlayer::MakeMessage *creation_message)
|
|
{
|
|
return new RPPlayer(creation_message);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
RPPlayer::~RPPlayer()
|
|
{
|
|
Check(this);
|
|
DeleteAllOffensivePlayers();
|
|
Check_Fpu();
|
|
}
|
|
|
|
//#############################################################################
|
|
// Offensive Player
|
|
//
|
|
|
|
RPPlayer__OffensivePlayer::RPPlayer__OffensivePlayer(RPPlayer *offensive_player, int damage_type, Time time_stamp)
|
|
{
|
|
//
|
|
//-----------------------------------------
|
|
// Initialize the offensivePlayer fields
|
|
//-----------------------------------------
|
|
//
|
|
offensivePlayer = offensive_player;
|
|
damageType = damage_type;
|
|
timeStamp = time_stamp;
|
|
deathBonus = 0.0f;
|
|
Check_Fpu();
|
|
}
|
|
|
|
//#############################################################################
|
|
// RP Status message
|
|
//
|
|
RPPlayer__StatusMessage::RPPlayer__StatusMessage(
|
|
RPPlayer *player_involved,
|
|
int message_type,
|
|
Scalar display_time,
|
|
Scalar damage_amount
|
|
) :
|
|
Player__StatusMessage(
|
|
(Player *) player_involved,
|
|
message_type,
|
|
display_time
|
|
)
|
|
{
|
|
damageAmount = damage_amount;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Logical
|
|
RPPlayer::TestInstance() const
|
|
{
|
|
return IsDerivedFrom(*GetClassDerivations());
|
|
}
|