Files
RP412/MUNGA/PLAYER.cpp
T
CydandClaude Opus 4.8 4abbf8879f Initial import of Red Planet v4.10 Win32 source
Imports the current Win32 source for the pod-racing game 'Red Planet',
built on the MUNGA engine and its L4 (Win32/DirectX) platform layer:

- MUNGA / MUNGA_L4: cross-platform engine core and Win32 backend
- RP / RP_L4: Red Planet game logic and Win32 application
- DivLoader, Setup1: asset loader and installer project
- lib, MUNGA_L4/openal, MUNGA_L4/sos: third-party audio dependencies

Removed stale Subversion metadata and added .gitignore/.gitattributes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 07:59:51 -05:00

877 lines
22 KiB
C++

#include "munga.h"
#pragma hdrstop
#include "player.h"
#include "icom.h"
#include "camship.h"
#include "dropzone.h"
#include "mission.h"
#include "audio.h"
#include "director.h"
#include "app.h"
#include "hostmgr.h"
#include "nttmgr.h"
#include "console.h"
#include "appmsg.h"
//#############################################################################
//####################### Player::StatusMessage #########################
//#############################################################################
Player__StatusMessage::Player__StatusMessage(Player *player_involved, int message_type, Scalar display_time)
{
playerInvolved = player_involved;
messageType = message_type;
displayTime = display_time;
}
//#############################################################################
//############################### Player ################################
//#############################################################################
//#############################################################################
// Shared Data Support
//
Derivation* Player::GetClassDerivations()
{
static Derivation classDerivations(Entity::GetClassDerivations(), "Player");
return &classDerivations;
}
Player::SharedData
Player::DefaultData(
Player::GetClassDerivations(),
Player::GetMessageHandlers(),
Player::GetAttributeIndex(),
Player::StateCount,
(Entity::MakeHandler)Player::Make
);
//#############################################################################
// Attribute Support
//
const Player::IndexEntry
Player::AttributePointers[]=
{
ATTRIBUTE_ENTRY(Player, CurrentScore, currentScore),
ATTRIBUTE_ENTRY(Player, PlayerRanking, playerRanking),
ATTRIBUTE_ENTRY(Player, DropZoneLocation, dropZoneLocation),
ATTRIBUTE_ENTRY(Player, StatusMessagePointer,statusMessagePointer),
ATTRIBUTE_ENTRY(Player, PlayerBitmapIndex, playerBitmapIndex),
ATTRIBUTE_ENTRY(Player, PlayerHighlighted, playerHighlighted)
};
Player::AttributeIndexSet& Player::GetAttributeIndex()
{
static Player::AttributeIndexSet attributeIndex(ELEMENTS(Player::AttributePointers),
Player::AttributePointers,
Entity::GetAttributeIndex()
);
return attributeIndex;
}
//#############################################################################
// Message Support
//
const Receiver::HandlerEntry
Player::MessageHandlerEntries[]=
{
MESSAGE_ENTRY(Player, DropZoneReply),
MESSAGE_ENTRY(Player, Score),
MESSAGE_ENTRY(Player, VehicleDead),
MESSAGE_ENTRY(Player, MissionStarting),
MESSAGE_ENTRY(Player, MissionEnding)
};
Receiver::MessageHandlerSet& Player::GetMessageHandlers()
{
static Receiver::MessageHandlerSet messageHandlers(ELEMENTS(Player::MessageHandlerEntries), Player::MessageHandlerEntries, Entity::GetMessageHandlers());
return messageHandlers;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Player::MissionStartingMessageHandler(Message *)
{
Check(this);
Tell("Player::MissionStartingMessageHandler - starting fade in\n");
fadeTimeRemaining = 3.0f;
SetSimulationState(MissionStartingState);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Player::MissionEndingMessageHandler(Message *)
{
Check(this);
fadeTimeRemaining = 3.0f;
ForceUpdate();
SetSimulationState(MissionEndingState);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Enumeration
Player::GetAudioRepresentation(
#if DEBUG_LEVEL>0
Entity *linked_entity
#else
Entity *
#endif
)
{
Check(this);
Check(linked_entity);
Check_Fpu();
return
(GetInstance() == MasterInstance) ?
InternalAudioRepresentation : ExternalAudioRepresentation;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Player::ScoreMessageHandler(ScoreMessage *message)
{
Check(this);
Check(message);
currentScore += message->scoreAward;
//
// HACK - ECH 7/6/95 - Allow the player vehicle to respond to score
// messages, allows attribute system to be used for scoring
//
Check(playerVehicle);
playerVehicle->RespondToScoreMessage(message);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Player::DropZoneReplyMessageHandler(
DropZone::ReplyMessage * //message
)
{
Check(this);
Fail("Drop zone reply should not be handled by base player class!\n");
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Player::CreatePlayerVehicle(Origin camera_origin)
{
Check(this);
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Create any MUNGA level vehicles
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
if (IsCameraShipPlayer())
{
Check(application);
ResourceFile *resources = application->GetResourceFile();
Check(resources);
Check_Pointer(playerMission->GetGameModel());
HostManager *host_manager = application->GetHostManager();
Check(host_manager);
ResourceDescription *camera_res =
resources->FindResourceDescription(
playerMission->GetGameModel(),
ResourceDescription::ModelListResourceType
);
Check(camera_res);
CameraShip::MakeMessage
create_camera(
CameraShip::MakeMessageID,
sizeof(CameraShip::MakeMessage),
EntityID(host_manager->GetLocalHostID()),
CameraShipClassID,
EntityID::Null,
camera_res->resourceID,
CameraShip::DefaultFlags,
camera_origin,
Motion::Identity,
Motion::Identity
);
CameraShip *camera =
(CameraShip*)application->MakeAndLinkViewpointEntity(&create_camera);
Register_Object(camera);
playerVehicle = camera;
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Player::VehicleDeadMessageHandler(VehicleDeadMessage *message)
{
Check(this);
Check(message);
Check(application);
//DEBUG_STREAM << "VEHICLEDEAD : " << endl << std::flush;
//
//-------------------------------------------------------------------------
// If we have let the player fry, send a message to the dropzone to give us
// a new location to return to
//-------------------------------------------------------------------------
//
if (
message->deathCount == deathCount
&& GetSimulationState() != DropZoneAcquiredState)
{
DropZone *dropzone;
//
//---------------------------------------------------------------------
// If no dropzone has been assigned to this death, find the closest one
//---------------------------------------------------------------------
//
if (message->dropZoneID == EntityID::Null)
{
//DEBUG_STREAM << " Find Closest" << endl << std::flush;
EntityManager *entity_manager = application->GetEntityManager();
Check(entity_manager);
EntityGroup *dropzones = entity_manager->FindGroup("DropZones");
Check(dropzones);
ChainIteratorOf<Node*> iterator(dropzones->groupMembers);
DropZone *closest_dropzone;
Vector3D range;
Scalar closest_range;
while ((dropzone = (DropZone*)iterator.ReadAndNext()) != NULL)
{
Check(dropzone);
if (!strnicmp(dropzone->GetDropZoneName(), "win", 3))
{
continue;
}
closest_dropzone = dropzone;
range.Subtract(
localOrigin.linearPosition,
dropzone->localOrigin.linearPosition
);
closest_range = range.LengthSquared();
break;
}
while ((dropzone = (DropZone*)iterator.ReadAndNext()) != NULL)
{
Check(dropzone);
if (!strnicmp(dropzone->GetDropZoneName(), "win", 3))
{
continue;
}
range.Subtract(
localOrigin.linearPosition,
dropzone->localOrigin.linearPosition
);
Scalar len = range.LengthSquared();
if (len < closest_range)
{
closest_dropzone = dropzone;
closest_range = len;
}
}
message->dropZoneID = closest_dropzone->GetEntityID();
dropzone = closest_dropzone;
}
//
//----------------------------------------------
// Otherwise, get this one from the host manager
//----------------------------------------------
//
else
{
//DEBUG_STREAM << " Use Assigned" << endl << std::flush;
HostManager *host = application->GetHostManager();
Check(host);
dropzone =
Cast_Object(
DropZone*,
host->GetEntityPointer(message->dropZoneID)
);
}
Check(dropzone);
DropZone::AssignDropZoneMessage
request(
DropZone::AssignDropZoneMessageID,
sizeof(DropZone::AssignDropZoneMessage),
GetEntityID(),
DropZoneReplyMessageID,
deathCount
);
dropzone->Dispatch(&request);
Time when = Now();
when += 2.0f;
application->Post(HighEventPriority, this, message, when);
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Player::HuntForDropZone(Scalar)
{
Check(this);
Check(playerMission);
//
//----------------------------------------------------------------------
// See if our designated drop zone is around yet. If the dropzone group
// does not exist, then no dropzones at all are available
//----------------------------------------------------------------------
//
Check(application);
EntityManager *entity_manager = application->GetEntityManager();
Check(entity_manager);
EntityGroup *dropzones = entity_manager->FindGroup("DropZones");
if (!dropzones)
{
Check_Fpu();
return;
}
Check(dropzones);
ChainIteratorOf<Node*> iterator(dropzones->groupMembers);
DropZone *dropzone;
const char* zone_name = playerMission->GetDropZoneName();
while ((dropzone = (DropZone*)iterator.ReadAndNext()) != NULL)
{
if (!strcmp(dropzone->GetDropZoneName(), zone_name))
{
break;
}
}
//
//------------------------------------------------------------------------
// If it is, reset our performance, and send a message to the drop zone to
// give us a location
//------------------------------------------------------------------------
//
if (dropzone)
{
//DEBUG_STREAM << "FOUND DROPZONE" << endl << std::flush;
Check(dropzone);
SetPerformance(&Player::DoNothingOnce);
VehicleDeadMessage
dead(
VehicleDeadMessageID,
sizeof(VehicleDeadMessage),
dropzone->GetEntityID()
);
dead.deathCount = -2;
DropZone::AssignDropZoneMessage
request(
DropZone::AssignDropZoneMessageID,
sizeof(DropZone::AssignDropZoneMessage),
GetEntityID(),
DropZoneReplyMessageID,
deathCount
);
dropzone->Dispatch(&request);
Time when = Now();
when += 2.0f;
application->Post(HighEventPriority, this, &dead, when);
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Player::ManageApplicationStatus(Scalar time_slice)
{
//
//--------------------------------------------------------------------------
// If we are in the game start/stop phase, count the timer down and send the
// appropriate message to the application when done
//--------------------------------------------------------------------------
//
Check(this);
Check(application);
switch (GetSimulationState())
{
case MissionStartingState:
Verify(fadeTimeRemaining <= 3.0f);
fadeTimeRemaining -= time_slice;
if (fadeTimeRemaining <= 0.0f)
{
SetSimulationState(VehicleTranslocatedState);
Check(application);
switch (application->GetApplicationState())
{
case Application::LaunchingMission:
{
Application::RunMissionMessage message;
application->Dispatch(&message);
}
break;
case Application::ResumingMission:
{
Application::ResumeMissionMessage message;
application->Dispatch(&message);
}
break;
}
}
break;
case MissionEndingState:
fadeTimeRemaining -= time_slice;
if (fadeTimeRemaining <= 0.0f)
{
Check(application);
switch (application->GetApplicationState())
{
case Application::EndingMission:
{
// Fill in appropriate exit code here. GAH
Application::StopMissionMessage message(NullExitCodeID);
application->Dispatch(&message);
}
break;
case Application::AbortingMission:
{
// Fill in appropriate exit code here. GAH
Application::AbortMissionMessage message(NullExitCodeID);
application->Dispatch(&message);
}
break;
case Application::SuspendingMission:
{
Application::SuspendMissionMessage message;
application->Dispatch(&message);
}
break;
}
}
break;
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Player::CameraShipSimulation(Scalar time_slice)
{
Check(this);
CalcRanking();
ManageApplicationStatus(time_slice);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Player::PlayerSimulation(Scalar time_slice)
{
Check(this);
//-------------------------------
// Calculate the player's ranking
//-------------------------------
CalcRanking();
//----------------------------------
// ...manage the application status?
//----------------------------------
ManageApplicationStatus(time_slice);
//-------------------------------
// Copy vehicle position
//-------------------------------
if (playerVehicle)
{
Check(playerVehicle);
localOrigin = playerVehicle->localOrigin;
localToWorld = playerVehicle->localToWorld;
}
//---------------------------------
// Service the status message queue
//---------------------------------
StatusMessageUpdate(time_slice);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Player::GoToVehicle(Scalar)
{
Check(this);
if (playerVehicle)
{
Check(playerVehicle);
localOrigin = playerVehicle->localOrigin;
localToWorld = playerVehicle->localToWorld;
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
Player::CalcRanking()
{
//
//---------------------------------------
// Get all players from the entity group
//---------------------------------------
//
EntityGroup *player_group =
application->GetEntityManager()->FindGroup("Players");
if(player_group)
{
int num_players = 0;
Player *active_player;
//
//-----------------------------------------------------
// Iterate through the players add to the sorted chain
//-----------------------------------------------------
//
ChainIteratorOf<Node*> iterator(player_group->groupMembers);
VChainOf<Player*, Scalar> player_rank(NULL, False);
while ((active_player = (Player*) iterator.ReadAndNext()) != NULL)
{
if(active_player->IsScoringPlayer())
{
player_rank.AddValue(active_player, active_player->currentScore);
++num_players;
//
//~~~~~~~~~~~~~~~~~~~~~~~~~
// Reset Player Highlighted
//~~~~~~~~~~~~~~~~~~~~~~~~~
//
active_player->playerHighlighted = False;
}
else
{
active_player->playerRanking = -1;
}
}
--num_players;
//
//------------------------------------------------
// Iterate through the chain assigning the rank
//------------------------------------------------
//
VChainIteratorOf<Player*, Scalar> rank_iterator(player_rank);
while ((active_player = rank_iterator.ReadAndNext()) != NULL)
{
active_player->playerRanking = num_players;
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// If player in first place turn on player highlighted
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
if (!active_player->playerRanking)
{
active_player->playerHighlighted = True;
}
--num_players;
}
Check_Fpu();
}
return 1;
}
//#############################################################################
// Update Support
//
void
Player::ReadUpdateRecord(Simulation::UpdateRecord *message)
{
Check(this);
Check_Pointer(message);
Entity::ReadUpdateRecord(message);
UpdateRecord* record = (UpdateRecord*) message;
currentScore = record->currentScore;
dropZoneLocation = record->dropZoneLocation;
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Player::WriteUpdateRecord(
Simulation::UpdateRecord *record,
int update_model
)
{
Check(this);
Check_Pointer(record);
Entity::WriteUpdateRecord(record, update_model);
UpdateRecord* update = (UpdateRecord*) record;
update->recordLength = sizeof(*update);
update->currentScore = currentScore;
update->dropZoneLocation = dropZoneLocation;
Check_Fpu();
}
//#############################################################################
// Construction and Destruction
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Player::Player(
Player::MakeMessage *creation_message,
Player::SharedData &virtual_data
):
Entity(creation_message, virtual_data),
statusMessageQueue(NULL)
{
Check_Pointer(this);
Check_Pointer(creation_message);
scenarioRole = NULL;
//---------------------------------
// Initialize data
//---------------------------------
statusMessagePointer = NULL;
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Initialize the playerMission
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
playerMission = application->GetCurrentMission();
Check(playerMission);
//
//---------------------------------
// Set the resource ID for a player
//---------------------------------
//
Check(application);
Check(application->GetResourceFile());
ResourceDescription *resource_description =
application->GetResourceFile()->FindResourceDescription(
"player",
ResourceDescription::ModelListResourceType
);
if (resource_description != NULL)
{
Verify(resourceID == ResourceDescription::NullResourceID);
resourceID = resource_description->resourceID;
}
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Get the host type associated with the entity ID
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
HostType host_type;
HostID host_ID = entityID.GetHostID();
Check(application);
HostManager *host_mgr = application->GetHostManager();
Check(host_mgr);
Host *local_host = host_mgr->GetLocalHost();
Check(local_host);
if (host_ID == local_host->GetHostID())
{
host_type = local_host->GetHostType();
}
else
{
HostManager::RemoteHostIterator iterator(host_mgr);
local_host = iterator.Find(host_ID);
Check(local_host);
host_type = local_host->GetHostType();
}
if (IsCameraShipPlayer())
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Make A CameraPlayers Entity Group
// since cameras are not involved in
// standard game play they need a
// separate Entity Group!
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
playerBitmapIndex = -1;
}
else
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Make A Regular Players Entity Group
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Check(application);
EntityManager *entity_manager = application->GetEntityManager();
Check(entity_manager);
EntityGroup *players = entity_manager->UseGroup("Players");
Check(players);
players->Add(this);
playerBitmapIndex = creation_message->playerBitmapIndex;
}
SetValidFlag();
playerVehicle = NULL;
if (GetInstance() != ReplicantInstance)
{
Check(application->GetCurrentMission());
playerMission = application->GetCurrentMission();
SetPerformance(&Player::HuntForDropZone);
}
else
{
SetPerformance(&Player::GoToVehicle);
playerMission = NULL;
}
playerRanking = 0;
deathCount = -2;
//-------------------------------------
// Clear score
//-------------------------------------
currentScore = 0.0f;
//-------------------------------------
// Create intercom object
//-------------------------------------
Check(application);
IcomManager
*intercom_manager = application->GetIntercomManager();
Check(intercom_manager);
intercomPointer = intercom_manager->MakeIcom(GetOwnerID());
Check(intercomPointer);
// registered in MakeIcom()
playerHighlighted = False;
fadeTimeRemaining = 0.0f;
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Player* Player::Make(Player::MakeMessage *creation_message)
{
Check_Fpu();
return new Player(creation_message, DefaultData);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Player::~Player()
{
Check(this);
//--------------------------------------------
// Delete intercom object
//--------------------------------------------
Check(intercomPointer);
Unregister_Object(intercomPointer);
delete intercomPointer;
intercomPointer = NULL;
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Player::AddStatusMessage(StatusMessage *status_message)
{
Check(this);
Check_Pointer(status_message);
statusMessageQueue.Add(status_message);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Player::StatusMessageUpdate(Scalar time_slice)
{
Check(this);
//-------------------------------------
// We handle this queue as a STACK.
// The most recent message is presented
// first, causing older messages to
// wait "below" them on the stack.
// This allows a default message to
// sit at the "bottom" of the stack
// and allow others to override it
// temporarily.
//-------------------------------------
//
ChainIteratorOf<StatusMessage*>
iterator(statusMessageQueue);
//----------------------------------------
// ...we really want iterator.GetLast(),
// but since it doesn't exist we'll
// do it directly.
//----------------------------------------
statusMessagePointer = NULL;
{
StatusMessage
*the_message;
while ((the_message = iterator.ReadAndNext()) != NULL)
{
statusMessagePointer = the_message;
}
}
//----------------------------------------
// If a message exists, decrement the
// timer: discard it if timed out.
//----------------------------------------
if (statusMessagePointer != NULL)
{
Check_Pointer(statusMessagePointer);
statusMessagePointer->displayTime -= time_slice;
if (statusMessagePointer->displayTime <= (Scalar) 0.0f)
{
Unregister_Object(statusMessagePointer);
delete statusMessagePointer;
statusMessagePointer = NULL;
}
}
Check_Fpu();
}
Logical
Player::TestInstance() const
{
return IsDerivedFrom(*GetClassDerivations());
}