The recording tee, which is what the Live Cam was for. L4SpoolingApplication turned out not to be the obstacle it looked like. The review build already records by teeing - it spools each packet and then hands it on - and what tied that to a review build was never the recording but where the buffer came from. MissionReviewApplicationManager is only a pool allocator, and SpoolFile takes whatever buffer it is handed, so SpoolRecorder owns one buffer and needs none of it. One hook covers what the review build taps in two places. Both InterestManager and NetworkManager derive from NetworkClient, so NetworkClient::ReceiveNetworkPacket sees entity updates and mission control alike, and it sits before Dispatch so a packet is kept whether or not anything downstream wants it. The recorder arms on the first packet rather than at the green light, because playback rebuilds the world from the LoadMission and RunMission packets and a spool that starts at the flag cannot be replayed. Two things a live recorder must do that the review one did not. It must not touch the packet. The spooler restamps in place with local arrival time, which is right in itself - playback paces off those stamps and packets from different senders carry different clock origins - but the sender's timestamp is what Simulation::ReadUpdateRecord hands to RP412NETCLOCK and from there to the projection. Overwriting it live would feed arrival jitter into where remote pods are drawn, which is the tick just fixed. So the write position is taken first and the COPY is stamped, in the spool, afterwards. And it must not take the race down. SpoolFile::SpoolPacket answers a full buffer with PostQuitMessage - a fair end to a replay, and killing the race being recorded on a live host. The recorder checks the room first and stops, and says so. RP412RECORDSIZE defaults to 100MB rather than the review build's 6, on Cyd's call: a full grid sends around 17KB a second, so six megabytes is six minutes and a hundred is an hour and a half, which costs nothing on any machine that can run this. Saved at the buzzer - the first of the two StopMissions, the end of the race rather than the fade timer - into SPOOLS\<timestamp>.spl and copied to last.spl, matching where the review build looks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
844 lines
25 KiB
C++
844 lines
25 KiB
C++
#include "rpl4.h"
|
|
#pragma hdrstop
|
|
|
|
#include "rpl4app.h"
|
|
#include "rpl4vid.h"
|
|
#include "rpl4arnd.h"
|
|
#include "rpl4grnd.h"
|
|
#include "rpl4mssn.h"
|
|
#include "..\munga_l4\l4gauge.h"
|
|
#include "..\rp\rpreg.h"
|
|
#include "..\munga_l4\l4ctrl.h"
|
|
#include "..\munga_l4\l4mppr.h"
|
|
#include "..\munga\appmgr.h"
|
|
#include "..\munga\spooler.h"
|
|
#include "rpl4mode.h"
|
|
#include "..\rp\vtv.h"
|
|
#include "rpl4mppr.h"
|
|
#include "..\rp\rpplayer.h"
|
|
#include "..\munga_l4\l4video.h"
|
|
#include "..\munga\dropzone.h"
|
|
#include "..\munga\director.h"
|
|
#include "..\munga\nttmgr.h"
|
|
#include "..\munga_l4\l4vb16.h"
|
|
|
|
//
|
|
//#############################################################################
|
|
// RPL4Application
|
|
//#############################################################################
|
|
//
|
|
const Receiver::HandlerEntry
|
|
RPL4Application::MessageHandlerEntries[]=
|
|
{
|
|
MESSAGE_ENTRY(RPL4Application, StopMission),
|
|
MESSAGE_ENTRY(RPL4Application, WinnersCircle)
|
|
};
|
|
|
|
Receiver::MessageHandlerSet& RPL4Application::GetMessageHandlers()
|
|
{
|
|
static Receiver::MessageHandlerSet messageHandlers(
|
|
ELEMENTS(RPL4Application::MessageHandlerEntries),
|
|
RPL4Application::MessageHandlerEntries,
|
|
L4Application::GetMessageHandlers());
|
|
return messageHandlers;
|
|
}
|
|
|
|
Derivation* RPL4Application::GetClassDerivations()
|
|
{
|
|
static Derivation classDerivations(
|
|
L4Application::GetClassDerivations(), "RPL4Application");
|
|
return &classDerivations;
|
|
}
|
|
|
|
RPL4Application::SharedData
|
|
RPL4Application::DefaultData(
|
|
RPL4Application::GetClassDerivations(),
|
|
RPL4Application::GetMessageHandlers()
|
|
);
|
|
|
|
//
|
|
// How long to sit on black between the race fading out and the podium
|
|
// fading in. The race fade is FADE_OUT_TIME (half a second), so this is
|
|
// that plus a beat, to land on black rather than on the tail of it.
|
|
//
|
|
const Scalar winnersCircleFadeOutTime = 0.7f;
|
|
|
|
// and how quickly the stand comes up out of the black afterwards
|
|
const Scalar winnersCircleFadeInTime = 0.45f;
|
|
|
|
RPL4Application::RPL4Application(
|
|
HINSTANCE hInstance,
|
|
HWND hWnd,
|
|
ResourceFile *resource_file,
|
|
ClassID class_ID,
|
|
SharedData &shared_data
|
|
):
|
|
L4Application(hInstance, hWnd, resource_file, RPL4, class_ID, shared_data)
|
|
{
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ~RPL4Application
|
|
//#############################################################################
|
|
//
|
|
RPL4Application::~RPL4Application()
|
|
{
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// ShowWinnersCircle
|
|
//#############################################################################
|
|
//
|
|
// The pod hall's award platform. Every map carries a "wcircle" stand at
|
|
// (1200,0,0) with eight ranked dropzones named win1..win8 on it - rank 1 at
|
|
// the front on the low tier, ranks 4-8 across the back on the high one. All
|
|
// this shipped; only the mission-review build ever drove it.
|
|
//
|
|
// Stand the finishers on their spots in finishing order, freeze them, and
|
|
// look back at the stand from in front of it.
|
|
//
|
|
void
|
|
RPL4Application::ShowWinnersCircle()
|
|
{
|
|
Check(this);
|
|
|
|
// RP412PODIUM=0 skips the whole thing
|
|
const char *podium_mode = getenv("RP412PODIUM");
|
|
if (podium_mode != NULL && atoi(podium_mode) == 0)
|
|
{
|
|
DEBUG_STREAM << "WinnersCircle: disabled\n" << std::flush;
|
|
return;
|
|
}
|
|
|
|
EntityManager *entity_manager = GetEntityManager();
|
|
if (entity_manager == NULL)
|
|
{
|
|
return;
|
|
}
|
|
EntityGroup *dropzones = entity_manager->FindGroup("DropZones");
|
|
if (dropzones == NULL)
|
|
{
|
|
DEBUG_STREAM << "WinnersCircle: no DropZones group\n" << std::flush;
|
|
return;
|
|
}
|
|
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// Walk the finishing order. Player::CalcRanking has been ranking every
|
|
// scoring player by score each frame, in football as much as in a race
|
|
// (CalcFootballRanking exists but is never called), so rank order is
|
|
// meaningful in both.
|
|
//---------------------------------------------------------------------
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// The stand's own geometry first, before anybody stands on it.
|
|
//
|
|
// The eight spots are fixed furniture on every map, so reading them up
|
|
// front gives one set of numbers to both jobs below: where to stand
|
|
// the finishers, and where to put the camera. Only the first is
|
|
// allowed to care who finished.
|
|
//---------------------------------------------------------------------
|
|
//
|
|
enum { winnersSpotCount = 8 };
|
|
DropZone *spot[winnersSpotCount];
|
|
int spots_found = 0;
|
|
|
|
char winners_spot[] = "win?";
|
|
char *place = winners_spot + 3;
|
|
|
|
for (int i = 0; i < winnersSpotCount; ++i)
|
|
{
|
|
*place = (char) ('1' + i);
|
|
spot[i] = NULL;
|
|
|
|
ChainIteratorOf<Node*> iterator(dropzones->groupMembers);
|
|
DropZone *dropzone;
|
|
while ((dropzone = (DropZone*) iterator.ReadAndNext()) != NULL)
|
|
{
|
|
if (!strcmp(dropzone->GetDropZoneName(), winners_spot))
|
|
{
|
|
spot[i] = dropzone;
|
|
++spots_found;
|
|
Point3D at = dropzone->localOrigin.linearPosition;
|
|
DEBUG_STREAM << "WinnersCircle: " << winners_spot << " at "
|
|
<< at.x << "," << at.y << "," << at.z << "\n" << std::flush;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (spot[0] == NULL)
|
|
{
|
|
DEBUG_STREAM << "WinnersCircle: no win1 spot on this map\n" << std::flush;
|
|
return;
|
|
}
|
|
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// Stand the finishers up, in finishing order.
|
|
//---------------------------------------------------------------------
|
|
//
|
|
int placed = 0;
|
|
Player *p;
|
|
for (int rank = 0; (p = CameraDirector::FindPlayerByRank(rank)) != NULL; ++rank)
|
|
{
|
|
if (rank >= winnersSpotCount)
|
|
{
|
|
break; // only eight spots exist on the stand
|
|
}
|
|
if (spot[rank] == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Entity *vehicle = p->GetPlayerVehicle();
|
|
if (vehicle == NULL || vehicle->GetClassID() != VTVClassID)
|
|
{
|
|
continue;
|
|
}
|
|
VTV *vtv = (VTV*) vehicle;
|
|
vtv->Reset(spot[rank]->localOrigin, VTV::MissionReviewReset);
|
|
vtv->SetPerformance(&VTV::DoNothing);
|
|
vtv->FlushEvents();
|
|
++placed;
|
|
}
|
|
|
|
if (placed == 0)
|
|
{
|
|
DEBUG_STREAM << "WinnersCircle: nobody to place\n" << std::flush;
|
|
return;
|
|
}
|
|
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// Switch the world back on.
|
|
//
|
|
// Dying collapses the view and sets the application "dead" until the
|
|
// pilot reincarnates - while that flag is up the renderer skips every
|
|
// static object, which is the whole map. A pilot killed near the buzzer
|
|
// is still waiting for a respawn that will never come, so the flag is
|
|
// still up and the podium would play out against a black screen.
|
|
//
|
|
// The mission-review build never hit this: it watches from a CameraShip,
|
|
// which cannot die, so nothing ever set the flag there.
|
|
//
|
|
// Standing the finishers up IS the resurrection, so clear it.
|
|
//---------------------------------------------------------------------
|
|
//
|
|
SetIsDead(false);
|
|
|
|
DPLRenderer *dpl_renderer = GetVideoRenderer();
|
|
if (dpl_renderer == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// Your own vehicle is built as a cockpit with no hull, so from any
|
|
// camera outside it there is nothing where you should be - on the stand
|
|
// you would be the one empty spot. Turn it inside out before the shot.
|
|
//---------------------------------------------------------------------
|
|
//
|
|
//
|
|
// Timed, because the black screen between the race fading out and the
|
|
// stand fading in is several seconds long and the fade only accounts
|
|
// for 0.7 of them. Nothing else runs while this does - the whole game
|
|
// is one thread - so whatever these two cost IS that gap.
|
|
//
|
|
Time podiumOutsideStart = Now();
|
|
|
|
dpl_renderer->ShowViewpointFromOutside();
|
|
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// The name plates are drawn per rank slot, so they have to be re-sorted
|
|
// now that the finishing order is final - otherwise the signs read in
|
|
// whatever order the players were created.
|
|
//---------------------------------------------------------------------
|
|
//
|
|
Time podiumNamesStart = Now();
|
|
|
|
dpl_renderer->SortAndReloadNameBitmaps();
|
|
|
|
{
|
|
Time podiumNamesEnd = Now();
|
|
char timing[160];
|
|
|
|
sprintf(timing,
|
|
"WinnersCircle: exterior %.0f ms, name plates %.0f ms\n",
|
|
(double)((Scalar)(podiumNamesStart - podiumOutsideStart) * 1000.0f),
|
|
(double)((Scalar)(podiumNamesEnd - podiumNamesStart) * 1000.0f));
|
|
DEBUG_STREAM << timing << std::flush;
|
|
}
|
|
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// Widen to 45 degrees and pull back in front of the stand so the whole
|
|
// line-up frames up. The stand runs from z~3 (rank 1, low) to z~39
|
|
// (ranks 4-8, high) and x~1180..1219; at 45 degrees that needs roughly
|
|
// 30 units of standoff. Eye is above the top tier looking slightly
|
|
// down at the winner's spot.
|
|
//
|
|
// Framed off the stand, NOT off who is standing on it. This used to
|
|
// average the filled spots and anchor on the first one filled, which
|
|
// meant the camera moved with the head count: eight finishers put the
|
|
// anchor in the middle of the tiers and the eye about 58 units out,
|
|
// one finisher collapsed it onto the winner's spot and brought the eye
|
|
// in to 36. Same stand, different shot per race - and per machine, if
|
|
// a remote player's vehicle was not there to be placed. The furniture
|
|
// does not move, so the camera does not either.
|
|
//---------------------------------------------------------------------
|
|
//
|
|
Point3D standFront = spot[0]->localOrigin.linearPosition;
|
|
|
|
//
|
|
// The stand's own axis: back rows to the winner's spot, which points
|
|
// out of the front however the map has the stand turned. Taken from
|
|
// the geometry rather than assumed, so a map that mounts its stand at
|
|
// another angle still gets photographed from the front.
|
|
//
|
|
Point3D standBack(0.0f, 0.0f, 0.0f);
|
|
int back_count = 0;
|
|
for (int i = 1; i < winnersSpotCount; ++i)
|
|
{
|
|
if (spot[i] != NULL)
|
|
{
|
|
standBack += spot[i]->localOrigin.linearPosition;
|
|
++back_count;
|
|
}
|
|
}
|
|
if (back_count > 0)
|
|
{
|
|
standBack.x /= (Scalar) back_count;
|
|
standBack.y /= (Scalar) back_count;
|
|
standBack.z /= (Scalar) back_count;
|
|
}
|
|
|
|
Vector3D facing;
|
|
facing.x = standFront.x - standBack.x;
|
|
facing.y = 0.0f;
|
|
facing.z = standFront.z - standBack.z;
|
|
Scalar reach = (Scalar) sqrt(facing.x * facing.x + facing.z * facing.z);
|
|
if (reach < 0.01f)
|
|
{
|
|
// a one-spot stand, or all eight stacked: nothing to take a
|
|
// direction from, so fall back to the way the shipped stands face
|
|
facing.x = 0.0f; facing.z = -1.0f; reach = 1.0f;
|
|
}
|
|
//
|
|
// Framing is tunable while the shot is being dialled in:
|
|
// RP412PODIUMSTANDOFF distance out in front of the stand
|
|
// RP412PODIUMHEIGHT eye height above the group
|
|
// RP412PODIUMAIM height of the aim point above the group
|
|
//
|
|
//
|
|
// Framed off the stand itself: down low and tilted up across the tiers,
|
|
// which is how you photograph a podium. It is a balance in both
|
|
// directions - drop the camera further or tilt harder and the sky takes
|
|
// the top half while the winner's spot slides off the bottom; tilt down
|
|
// instead and it becomes a floor plan. Pillarboxing to 4:3 is what lets
|
|
// it sit this close without the platform trailing off at the sides.
|
|
//
|
|
Scalar standoff = 36.0f;
|
|
Scalar height = 12.0f;
|
|
Scalar aim_lift = 2.0f;
|
|
const char *tune = getenv("RP412PODIUMSTANDOFF");
|
|
if (tune != NULL && atof(tune) != 0.0) standoff = (Scalar) atof(tune);
|
|
tune = getenv("RP412PODIUMHEIGHT");
|
|
if (tune != NULL && atof(tune) != 0.0) height = (Scalar) atof(tune);
|
|
tune = getenv("RP412PODIUMAIM");
|
|
if (tune != NULL) aim_lift = (Scalar) atof(tune);
|
|
|
|
Point3D eye;
|
|
eye.x = standFront.x + (facing.x / reach) * standoff;
|
|
eye.y = standFront.y + height;
|
|
eye.z = standFront.z + (facing.z / reach) * standoff;
|
|
|
|
Point3D aim = standFront;
|
|
aim.y += aim_lift;
|
|
|
|
//
|
|
// RP412PODIUMCAM=0 leaves the view in the cockpit, which is also the
|
|
// way to tell a bad camera from a scene that is not drawing at all.
|
|
//
|
|
//
|
|
// Clear the cockpit glass away. The MFDs and the radar sit over the
|
|
// viewscreen like the pod's bezels and have nothing to say once the
|
|
// race is over; the podium gets the whole canvas.
|
|
//
|
|
SVGA16 *cockpit = SVGA16::GetCockpit();
|
|
if (cockpit != NULL)
|
|
{
|
|
cockpit->HideSecondaryDisplays();
|
|
}
|
|
|
|
//
|
|
// Fog first: it pulls the clip plane in to 1100, and SetViewAngle is what
|
|
// rebuilds the projection that reads it.
|
|
//
|
|
dpl_renderer->SetFogStyle(DPLRenderer::winnersCircleFogStyle);
|
|
|
|
const char *camera_mode = getenv("RP412PODIUMCAM");
|
|
if (camera_mode == NULL || atoi(camera_mode) != 0)
|
|
{
|
|
dpl_renderer->SetViewAngle(Degree(45.0f));
|
|
dpl_renderer->SetPresentationCamera(eye, aim);
|
|
}
|
|
else
|
|
{
|
|
// still rebuild the projection so the new clip plane takes effect
|
|
dpl_renderer->SetViewAngle(Degree(40.0f));
|
|
}
|
|
if (camera_mode != NULL && atoi(camera_mode) == 0)
|
|
{
|
|
DEBUG_STREAM << "WinnersCircle: presentation camera disabled\n"
|
|
<< std::flush;
|
|
}
|
|
|
|
//
|
|
// Everything is in place behind the black - bring it up.
|
|
// RP412PODIUMFADEIN sets the ramp in seconds.
|
|
//
|
|
Scalar fade_in = winnersCircleFadeInTime;
|
|
const char *fade_tune = getenv("RP412PODIUMFADEIN");
|
|
if (fade_tune != NULL && atof(fade_tune) > 0.0)
|
|
{
|
|
fade_in = (Scalar) atof(fade_tune);
|
|
}
|
|
dpl_renderer->StartPresentationFadeIn(fade_in);
|
|
|
|
//
|
|
// placed is reported for diagnosis only - the camera numbers beside it
|
|
// must not move when it does.
|
|
//
|
|
DEBUG_STREAM << "WinnersCircle: " << placed << " placed on "
|
|
<< spots_found << " spots; aim "
|
|
<< aim.x << "," << aim.y << "," << aim.z
|
|
<< " eye " << eye.x << "," << eye.y << "," << eye.z
|
|
<< "\n" << std::flush;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// StopMissionMessageHandler
|
|
//#############################################################################
|
|
//
|
|
// The mission is over. Show the podium, then let the base handler run - it
|
|
// puts the player into MissionEndingState, whose fade timer is what actually
|
|
// keeps the sim and the renderer alive until teardown.
|
|
//
|
|
void
|
|
RPL4Application::StopMissionMessageHandler(StopMissionMessage *message)
|
|
{
|
|
Check(this);
|
|
|
|
//
|
|
// StopMission arrives twice: once from the console at the buzzer, and
|
|
// again from the player when the ending fade runs out - that second one
|
|
// is what actually retires the application. Only the first is the end of
|
|
// the race.
|
|
//
|
|
if (GetApplicationState() != Application::EndingMission)
|
|
{
|
|
//
|
|
//-------------------------------------------------------------
|
|
// Don't cut straight to the podium. The race gets its own
|
|
// fade-to-black first - that fade is already running by the time
|
|
// this returns - and the Winners Circle comes up out of the
|
|
// black afterwards. Standing everyone up now would just fade out
|
|
// the podium instead of the race.
|
|
//
|
|
// FADE_OUT_TIME is half a second; a beat more than that lands on
|
|
// black rather than on the tail of the fade.
|
|
//-------------------------------------------------------------
|
|
//
|
|
Receiver::Message podium_message(
|
|
WinnersCircleMessageID, sizeof(Receiver::Message));
|
|
|
|
Time event_time;
|
|
event_time = Now();
|
|
event_time += winnersCircleFadeOutTime;
|
|
|
|
Post(LowEventPriority, this, &podium_message, event_time);
|
|
|
|
DEBUG_STREAM << "WinnersCircle: race over, fading out\n" << std::flush;
|
|
|
|
//
|
|
// Write the recording here rather than at teardown. This is the
|
|
// first of the two StopMissions - the buzzer, not the fade timer -
|
|
// so it is the end of the RACE, and everything worth keeping has
|
|
// arrived. Waiting for teardown would risk the process going away
|
|
// first and taking the spool with it.
|
|
//
|
|
if (Application::IsRecording())
|
|
{
|
|
SpoolRecorder_Get()->Save();
|
|
}
|
|
}
|
|
|
|
L4Application::StopMissionMessageHandler(message);
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// WinnersCircleMessageHandler
|
|
//#############################################################################
|
|
//
|
|
// The race has faded to black. Set the stand up behind the black and fade
|
|
// back in to it.
|
|
//
|
|
void
|
|
RPL4Application::WinnersCircleMessageHandler(Receiver::Message *)
|
|
{
|
|
Check(this);
|
|
|
|
DEBUG_STREAM << "WinnersCircle: standing the finishers up\n" << std::flush;
|
|
ShowWinnersCircle();
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeRegistry
|
|
//#############################################################################
|
|
//
|
|
Registry*
|
|
RPL4Application::MakeRegistry()
|
|
{
|
|
Check_Fpu();
|
|
return new RPRegistry(GetResourceFile());
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeVideoRenderer
|
|
//#############################################################################
|
|
//
|
|
VideoRenderer*
|
|
RPL4Application::MakeVideoRenderer()
|
|
{
|
|
if (divisionParameters)
|
|
{
|
|
return
|
|
new RPL4VideoRenderer(
|
|
ghWnd,
|
|
L4Application::mScreenWidth,
|
|
L4Application::mScreenHeight,
|
|
L4Application::mFullscreen,
|
|
VisualInterestType,
|
|
DefaultInterestDepth
|
|
);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeAudioRenderer
|
|
//#############################################################################
|
|
//
|
|
AudioRenderer*
|
|
RPL4Application::MakeAudioRenderer()
|
|
{
|
|
Check_Fpu();
|
|
// char *blaster1, *blaster2;
|
|
|
|
// blaster1 = getenv(FRONT_CARD_ENV_VAR);
|
|
// blaster2 = getenv(REAR_CARD_ENV_VAR);
|
|
|
|
//if (blaster1 != NULL && blaster2 != NULL)
|
|
//{
|
|
return new RPL4AudioRenderer(
|
|
1000.0f,
|
|
GetMissionReviewMode()
|
|
);
|
|
//}
|
|
// return NULL;
|
|
}
|
|
|
|
//#############################################################################
|
|
// MakeModeManager
|
|
//#############################################################################
|
|
ModeManager*
|
|
RPL4Application::MakeModeManager()
|
|
{
|
|
RPL4ModeManager
|
|
*manager = new RPL4ModeManager(RPL4ModeManager::ModeInitial);
|
|
Check(manager);
|
|
return manager;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeGaugeRenderer
|
|
//#############################################################################
|
|
//
|
|
GaugeRenderer*
|
|
RPL4Application::MakeGaugeRenderer(int *secondaryIndex, int *aux1Index, int *aux2Index)
|
|
{
|
|
char *mode_string = getenv("L4GAUGE");
|
|
if (mode_string != NULL && (secondaryIndex != NULL || aux1Index != NULL || aux2Index != NULL))
|
|
{
|
|
return new RPL4GaugeRenderer(!L4Application::mFullscreen, secondaryIndex, aux1Index, aux2Index);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeMission
|
|
//#############################################################################
|
|
//
|
|
Mission*
|
|
RPL4Application::MakeMission(
|
|
NotationFile *notation_file,
|
|
ResourceFile *resources
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(notation_file);
|
|
Check(resources);
|
|
|
|
Check_Fpu();
|
|
return new RPL4Mission(notation_file, resources);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeViewpointEntity
|
|
//#############################################################################
|
|
//
|
|
Entity*
|
|
RPL4Application::MakeViewpointEntity(Entity::MakeMessage *message)
|
|
{
|
|
Check(this);
|
|
|
|
// Entity
|
|
// *viewing_entity = L4Application::MakeViewpointEntity(message);
|
|
//
|
|
// if(viewing_entity)
|
|
// {
|
|
// Check_Fpu();
|
|
// return viewing_entity;
|
|
// }
|
|
|
|
LBE4ControlsManager* controls =
|
|
(LBE4ControlsManager *)GetControlsManager();
|
|
Check(controls);
|
|
|
|
Entity
|
|
*viewing_entity = NULL;
|
|
char
|
|
*primary_mapping_name = NULL;
|
|
|
|
switch(message->classToCreate)
|
|
{
|
|
case CameraShipClassID:
|
|
//=======================================================================
|
|
// Create Camera ship
|
|
//=======================================================================
|
|
{
|
|
CameraShip
|
|
*viewing_camera =
|
|
CameraShip::Make((CameraShip::MakeMessage*)message);
|
|
Check(viewing_camera);
|
|
|
|
viewing_entity = viewing_camera;
|
|
//--------------------------------------------------
|
|
// Create controls mapping object
|
|
//--------------------------------------------------
|
|
CameraControlsMapper::SubsystemResource
|
|
control_subsystem_resource;
|
|
|
|
Str_Copy(
|
|
control_subsystem_resource.subsystemName,
|
|
"ControlsMapper",
|
|
sizeof(control_subsystem_resource.subsystemName)
|
|
);
|
|
control_subsystem_resource.classID = TrivialSubsystemClassID;
|
|
control_subsystem_resource.subsystemModelSize =
|
|
sizeof(control_subsystem_resource);
|
|
|
|
CameraControlsMapper
|
|
*camera_mapper;
|
|
|
|
switch (controls->primaryControlType)
|
|
{
|
|
case LBE4ControlsManager::PrimaryThrustMaster:
|
|
Tell("RPL4Application, CameraShip using ThrustMaster\n");
|
|
camera_mapper = new CameraThrustmasterMapper(
|
|
viewing_camera,
|
|
CameraShip::ControlsMapperSubsystem,
|
|
&control_subsystem_resource
|
|
);
|
|
primary_mapping_name = "Thrustmaster";
|
|
break;
|
|
|
|
case LBE4ControlsManager::PrimaryRIO:
|
|
Tell("RPL4Application, CameraShip using RIO\n");
|
|
camera_mapper = new CameraRIOMapper(
|
|
viewing_camera,
|
|
CameraShip::ControlsMapperSubsystem,
|
|
&control_subsystem_resource
|
|
);
|
|
primary_mapping_name = "L4";
|
|
break;
|
|
|
|
default:
|
|
Tell("RPL4Application, CameraShip using default mapper\n");
|
|
camera_mapper = new CameraL4Mapper(
|
|
viewing_camera,
|
|
CameraShip::ControlsMapperSubsystem,
|
|
&control_subsystem_resource
|
|
);
|
|
// primary mapping name is NULL
|
|
break;
|
|
}
|
|
|
|
//-------------------------------------------
|
|
// Set mapping subsystem for this entity
|
|
//-------------------------------------------
|
|
Register_Object(camera_mapper);
|
|
viewing_camera->SetMappingSubsystem(camera_mapper);
|
|
}
|
|
break;
|
|
|
|
case VTVClassID:
|
|
//=======================================================================
|
|
// Create VTV
|
|
//=======================================================================
|
|
{
|
|
VTV
|
|
*viewing_VTV = VTV::Make((VTV::MakeMessage*)message);
|
|
Check(viewing_VTV);
|
|
|
|
viewing_entity = viewing_VTV;
|
|
//--------------------------------------------------
|
|
// Set control deadbanding
|
|
//--------------------------------------------------
|
|
controls->SetJoystickDeadBand(.1);
|
|
controls->SetThrottleDeadBand(.1);
|
|
controls->SetPedalsDeadBand(.1);
|
|
//--------------------------------------------------
|
|
// Create controls mapping object
|
|
//--------------------------------------------------
|
|
VTVControlsMapper::SubsystemResource
|
|
control_subsystem_resource;
|
|
Str_Copy(
|
|
control_subsystem_resource.subsystemName,
|
|
"ControlsMapper",
|
|
sizeof(control_subsystem_resource.subsystemName)
|
|
);
|
|
control_subsystem_resource.classID = TrivialSubsystemClassID;
|
|
control_subsystem_resource.subsystemModelSize =
|
|
sizeof(control_subsystem_resource);
|
|
control_subsystem_resource.segmentIndex = -1;
|
|
control_subsystem_resource.subsystemFlags = 0;
|
|
|
|
VTVControlsMapper
|
|
*vtv_mapper;
|
|
|
|
switch (controls->primaryControlType)
|
|
{
|
|
case LBE4ControlsManager::PrimaryThrustMaster:
|
|
Tell("RPL4Application, VTV using ThrustMaster\n");
|
|
vtv_mapper = new VTVThrustmasterMapper(
|
|
viewing_VTV,
|
|
VTV::ControlsMapperSubsystem,
|
|
&control_subsystem_resource
|
|
);
|
|
primary_mapping_name = "Thrustmaster";
|
|
break;
|
|
|
|
case LBE4ControlsManager::PrimaryRIO:
|
|
Tell("RPL4Application, VTV using RIO\n");
|
|
vtv_mapper = new VTVRIOMapper(
|
|
viewing_VTV,
|
|
VTV::ControlsMapperSubsystem,
|
|
&control_subsystem_resource
|
|
);
|
|
primary_mapping_name = "L4";
|
|
break;
|
|
|
|
default:
|
|
DEBUG_STREAM << "*****VTV has no controls mapping!*****" << std::flush;
|
|
Fail("RPL4Application::MakeViewpointEntity");
|
|
vtv_mapper = NULL;
|
|
// primary mapping name is NULL
|
|
break;
|
|
}
|
|
|
|
//-------------------------------------------
|
|
// Set mapping subsystem for this entity
|
|
//-------------------------------------------
|
|
Register_Object(vtv_mapper);
|
|
viewing_VTV->SetMappingSubsystem(vtv_mapper);
|
|
//-------------------------------------------
|
|
// Enable collision detection for this entity
|
|
//-------------------------------------------
|
|
viewing_VTV->StartCollisionAssistant();
|
|
}
|
|
break;
|
|
}
|
|
Check(viewing_entity);
|
|
|
|
//------------------------------------------------------------------------
|
|
// Load up the control mapping stream for the entity
|
|
//------------------------------------------------------------------------
|
|
if (primary_mapping_name != NULL)
|
|
{
|
|
ResourceDescription
|
|
*resource_description =
|
|
application->GetResourceFile()->SearchList(
|
|
viewing_entity->GetResourceID(),
|
|
ResourceDescription::ControlMappingsListResourceType
|
|
);
|
|
|
|
if (resource_description != NULL)
|
|
{
|
|
Check(resource_description);
|
|
|
|
Check(GetResourceFile());
|
|
resource_description =
|
|
GetResourceFile()->SearchList(
|
|
resource_description->resourceID,
|
|
primary_mapping_name
|
|
);
|
|
|
|
if (resource_description != NULL)
|
|
{
|
|
Check(resource_description);
|
|
|
|
resource_description->Lock();
|
|
controls->CreateStreamedMappings(
|
|
viewing_entity,
|
|
(int*)resource_description->resourceAddress
|
|
);
|
|
resource_description->Unlock();
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Create gauges for this entity
|
|
//------------------------------------------------------------------------
|
|
RPL4GaugeRenderer
|
|
*gauge_renderer = (RPL4GaugeRenderer *) GetGaugeRenderer();
|
|
|
|
if (gauge_renderer != NULL)
|
|
{
|
|
Check(gauge_renderer);
|
|
gauge_renderer->ConfigureForModel("Init", viewing_entity);
|
|
}
|
|
|
|
Check_Fpu();
|
|
return viewing_entity;
|
|
}
|