Files
RP412/RP/RPPLAYER.cpp
T
CydandClaude Fable 5 67d57452ba The podium hold asks whether there is a podium
RP412PODIUM=0 promises "straight to the results" and delivered eleven
seconds of black screen first: the winners' circle hold was applied
unconditionally at the buzzer, and the timer never asked whether there
was a stand to hold the mission open FOR. Found by the -egg harness,
which could reach the end of a race unattended and noticed the promise
not being kept.

With the podium off, the hold now stands aside and the base 3-second
race fade runs the show. With it on, RP412PODIUMHOLD tunes the length
(1-60 seconds, default the same 11 as always) - eleven seconds of one
parked pod is a long look in single player, and that is now a choice
rather than a constant. The decision point logs which path it took and
the value it applied, verified all three ways:

  podium off - the race fade stands (3s) and the results come straight up
  holding the mission open 5s for the stand
  holding the mission open 11s for the stand

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 22:04:18 -05:00

1575 lines
40 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.
//
// RP412PODIUMHOLD tunes it, and RP412PODIUM=0 declines it entirely: that
// option promises "straight to the results", and it used to get the full
// eleven seconds against a black screen anyway, because this timer never
// asked whether there was a podium to hold the mission open FOR. Zero
// here means "do not override the base fade" - the stock 3 seconds.
//
static Scalar
WinnersCircleHoldTime()
{
static Scalar
hold = (Scalar) -1;
if (hold < (Scalar) 0)
{
const char *podium = getenv("RP412PODIUM");
if (podium != NULL && atoi(podium) == 0)
{
hold = (Scalar) 0;
}
else
{
const char *setting = getenv("RP412PODIUMHOLD");
hold = (setting != NULL) ? (Scalar) atof(setting) : (Scalar) 11;
//
// Under a second cuts into the race's own fade-out, and a
// minute is a stuck-looking screen; both read as bugs, not
// choices.
//
if (hold < (Scalar) 1) hold = (Scalar) 1;
if (hold > (Scalar) 60) hold = (Scalar) 60;
}
}
return hold;
}
//#############################################################################
//######################## 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)
{
Scalar hold = WinnersCircleHoldTime();
if (hold > (Scalar) 0)
{
fadeTimeRemaining = hold;
DEBUG_STREAM << "WinnersCircle: holding the mission open "
<< hold << "s for the stand\n" << std::flush;
}
else
{
DEBUG_STREAM << "WinnersCircle: podium off - the race fade "
<< "stands (" << fadeTimeRemaining << "s) and the results "
<< "come straight up\n" << std::flush;
}
}
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, &current_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());
}