diff --git a/MUNGA_L4/L4VIDEO.cpp b/MUNGA_L4/L4VIDEO.cpp index de2eaeb..085972e 100644 --- a/MUNGA_L4/L4VIDEO.cpp +++ b/MUNGA_L4/L4VIDEO.cpp @@ -1797,6 +1797,12 @@ DPLRenderer::DPLRenderer( backgroundGreen = 0.0f; backgroundBlue = 0.0f; viewAngle = 30.0f; + // the Winners Circle camera is off until the podium asks for it. No + // restore path is needed: a fresh DPLRenderer is built per mission, so + // the next race re-reads viewangle from RPDPL.INI. + mPresentationCamera = False; + mPresentationFog = False; + D3DXMatrixIdentity(&mPresentationView); dplMainView = NULL; dplDeathZone = NULL; dplMainZone = NULL; @@ -2343,10 +2349,39 @@ void } break; case winnersCircleFogStyle: + // + // The presentation shot, restored from the DPL body below. The + // stand sits far off the track in open ground, so the track's own + // fog leaves it in the dark - this is the lighter blue-violet the + // original used to lift it, with the fog pushed back to 100/1050. // // HACK!! This really shouldn't reset the clip planes, but since // it only happens at the end of the review, it should be safe for now. // + fogRed = 0.32f; + fogGreen = 0.30f; + fogBlue = 0.65f; + fogNear = 100.0f; + fogFar = 1050.0f; + + // the per-frame FOGSTART/FOGEND come from these + currentFogNear = fogNear; + currentFogFar = fogFar; + + clipNear = 0.25f; + clipFar = 1100.0f; + + // tells the end-of-mission fade to stand down + mPresentationFog = True; + + // unconditional: fogUpdating is off while a vehicle drives its own + // headlight fog, and the podium overrides all of that + if (mDevice != NULL) + { + mDevice->SetRenderState(D3DRS_FOGCOLOR, + D3DCOLOR_XRGB((int)(255 * fogRed), (int)(255 * fogGreen), + (int)(255 * fogBlue))); + } // dpl_SetViewClipPlanes ( dplMainView, 0.25f, 1100.0f ); // dpl_SetViewFog( // dplMainView, @@ -5608,6 +5643,24 @@ void intersect_mask = INTERSECT_ALL; } + // + // RP412RENDERDIAG=1: name what actually gets built, so a missing + // Winners Circle can be told from one that is simply out of shot. + // + { + static const char *diag = getenv("RP412RENDERDIAG"); + if (diag != NULL && atoi(diag) != 0) + { + DEBUG_STREAM << "RenderDiag: building class" + << entity->GetClassID() << " res " + << entity->GetResourceID() << " at " + << entity->localOrigin.linearPosition.x << "," + << entity->localOrigin.linearPosition.y << "," + << entity->localOrigin.linearPosition.z + << "\n" << std::flush; + } + } + Logical first_object = True; video_iterator.First(); @@ -5997,6 +6050,44 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte if (mCamShipHUD) mCamShipHUD->Execute(); + // + // The eye renderable has just written D3DTS_VIEW from the viewpoint + // entity. For the Winners Circle the shot comes from off the stand + // instead, so stomp it here - before the read-back below that feeds + // every draw call. + // + if (mPresentationCamera) + { + mDevice->SetTransform(D3DTS_VIEW, &mPresentationView); + } + + // + // RP412RENDERDIAG=1: report what the frame is actually made of once the + // mission is ending, which is the only way to tell an empty scene from a + // misaimed camera. + // + { + static const char *diag = getenv("RP412RENDERDIAG"); + static int reported = 0; + if (diag != NULL && atoi(diag) != 0 && + currentAppState == Application::EndingMission && reported < 4) + { + ++reported; + int dynamic_count = 0; + SChainIteratorOf diag_iter(&mRenderables); + while (diag_iter.ReadAndNext() != NULL) + { + ++dynamic_count; + } + DEBUG_STREAM << "RenderDiag: ending frame " << reported + << " dead=" << (l4_application->IsDead() ? 1 : 0) + << " statics=" << (int) mConsolidatedStaticObjects.size() + << " renderables=" << dynamic_count + << " camera=" << (mPresentationCamera ? 1 : 0) + << "\n" << std::flush; + } + } + gNumBatches = 0; static Time lastFrameTime = mTargetRenderTime; Scalar dT = mTargetRenderTime - lastFrameTime; @@ -6161,6 +6252,43 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte hr = mDevice->EndScene(); hr = mDevice->Present(NULL, NULL, gMainPresentWindow, NULL); + + // + // RP412RENDERDIAG=1: did the ending frame actually reach the window, and + // what colour did it clear to? A black clear with a blue-violet fog set + // means something is overwriting the fog behind us. + // + { + static const char *diag = getenv("RP412RENDERDIAG"); + static int presented = 0; + if (diag != NULL && atoi(diag) != 0 && + application->GetApplicationState() == Application::EndingMission && + presented < 4) + { + ++presented; + DWORD fog_now = 0; + mDevice->GetRenderState(D3DRS_FOGCOLOR, &fog_now); + HWND present_window = (HWND) gMainPresentWindow; + RECT present_rect; + present_rect.left = present_rect.top = 0; + present_rect.right = present_rect.bottom = 0; + if (present_window != NULL) + { + GetClientRect(present_window, &present_rect); + } + DEBUG_STREAM << "RenderDiag: present " << presented + << " hr=0x" << std::hex << (unsigned int) hr << std::dec + << " fogcolor=0x" << std::hex << (unsigned int) fog_now << std::dec + << " window=" << (void*) present_window + << " visible=" << (present_window != NULL && + IsWindowVisible(present_window) ? 1 : 0) + << " rect=" << present_rect.right << "x" << present_rect.bottom + << " backbuf=" << mPresentParams.BackBufferWidth << "x" + << mPresentParams.BackBufferHeight + << "\n" << std::flush; + } + } + if (hr == D3DERR_DEVICELOST) { int bbCount = mPresentParams.BackBufferCount; @@ -6736,27 +6864,86 @@ void } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +// +// Un-stubbed for the Winners Circle, which asks for 45 degrees to get all +// eight racers in frame. The DPL body below it built a dpl_View; the D3D9 +// path builds the same two matrices DPLReadINIPage does and pushes them, +// because mProjectionMatrix only reaches the device on the LaunchingMission +// edge and on the sky-pass restore - rebuilding alone would not show until +// the next frame. +// +// Pass degrees, as the INI does. RPDPL.INI ships viewangle=40, which is what +// to hand back afterwards - not the 30 in the constructor, which the INI +// overwrites at load. +// void DPLRenderer::SetViewAngle(Degree new_angle) { - //STUBBED: DPL RB 1/14/07 - //Check(this); - //// - ////~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - //// Convert From Degree To Radian - ////~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - //// - //Radian view_angle; - //view_angle = new_angle; - //viewAngle = view_angle; - //viewRatio = tan(viewAngle/2.0f); - //// - ////~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - //// Calc Aspect Ratio and Set View Projection - ////~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - //// - //aspectRatio = (float) y_size / (float) x_size; - //dpl_SetViewProjection ( dplMainView, -1.0f, -aspectRatio, 1.0f, aspectRatio, 1.0f/viewRatio); - //dpl_FlushView(dplMainView); + Check(this); + + viewAngle = (float) new_angle.angle; + + Radian view_angle; + view_angle = new_angle; + // tan(half angle): the culling helper GetViewRatio() reads this, and + // nothing has written it since the DPL body was stubbed out + viewRatio = (float) tan(view_angle / 2.0f); + aspectRatio = (float) y_size / (float) x_size; + + D3DXMatrixIdentity(&mProjectionMatrix); + D3DXMatrixPerspectiveFovLH( + &mProjectionMatrix, + viewAngle * (PI / 180.0f), + (float) x_size / (float) y_size, + clipNear, + clipFar); + mProjectionMatrix(0, 0) *= -1; // handedness flip - the view is RH + + mDecalProjectionMatrix = mProjectionMatrix; + mDecalProjectionMatrix._33 -= mDecalEpsilon; + + if (mDevice != NULL) + { + mDevice->SetTransform(D3DTS_PROJECTION, &mProjectionMatrix); + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// The presentation camera. DPLEyeRenderable writes D3DTS_VIEW from the +// viewpoint entity every frame; ExecuteImplementation reads it straight back +// out of the device and hands it to every draw call. Overriding it between +// those two points is enough to move the shot without touching the eye +// renderable or building a CameraShip. +// +void + DPLRenderer::SetPresentationCamera(const Point3D &eye, const Point3D &look_at) +{ + Check(this); + + D3DXVECTOR3 from((float) eye.x, (float) eye.y, (float) eye.z); + D3DXVECTOR3 at((float) look_at.x, (float) look_at.y, (float) look_at.z); + + // + // LookAt*LH*, to match the LH projection. The two differ by exactly the + // sign of the view direction, and RH here points the camera the opposite + // way: ask to look down at the stand and you get sky behind you. + // + // The engine's own eye renderable does use RH, and is right to - its + // forward and up come out of the entity matrix already in that + // convention. A camera aimed with plain world coordinates does not, so + // it wants the handedness the projection was built with. + // + D3DXVECTOR3 up(0.0f, 1.0f, 0.0f); + + D3DXMatrixLookAtLH(&mPresentationView, &from, &at, &up); + mPresentationCamera = True; +} + +void + DPLRenderer::ClearPresentationCamera() +{ + Check(this); + mPresentationCamera = False; } // //############################################################################# diff --git a/MUNGA_L4/L4VIDEO.h b/MUNGA_L4/L4VIDEO.h index 897f850..7ea6f04 100644 --- a/MUNGA_L4/L4VIDEO.h +++ b/MUNGA_L4/L4VIDEO.h @@ -310,6 +310,31 @@ public: void SetViewAngle(Degree new_angle); + //------------------------------------------------------------------ + // Winners Circle presentation camera. + // + // The eye normally rides the viewpoint entity - your own vehicle - + // and DPLEyeRenderable writes D3DTS_VIEW from it each frame. For the + // podium the shot has to come from off the stand looking back, with + // every racer including you in frame, so this overrides that view + // for as long as it is set. Culling still runs from the viewpoint + // entity, which is standing on the podium, so the stand and everyone + // on it stay resident. + // + // Eye and target are world space. ClearPresentationCamera() hands + // the view back to the entity. + //------------------------------------------------------------------ + void SetPresentationCamera(const Point3D &eye, const Point3D &look_at); + void ClearPresentationCamera(); + + //------------------------------------------------------------------ + // True once the Winners Circle has taken the screen. The end-of- + // mission fade-to-black checks this and stands down: it multiplies + // the fog to black over FADE_OUT_TIME, which would otherwise black + // out the podium no matter what is drawn. + //------------------------------------------------------------------ + Logical InPresentation() const { return mPresentationFog; } + unsigned int* MakeBitSliceStorage(); void SortAndReloadNameBitmaps(); @@ -438,6 +463,12 @@ private: D3DXMATRIX mDecalProjectionMatrix; float mDecalEpsilon; + // Winners Circle: overrides D3DTS_VIEW while set (see + // SetPresentationCamera) + D3DXMATRIX mPresentationView; + Logical mPresentationCamera; + Logical mPresentationFog; + void FindBestAdapterIndices(bool isWindowed); float mCloudRed, mCloudGreen, mCloudBlue; diff --git a/MUNGA_L4/L4VIDRND.cpp b/MUNGA_L4/L4VIDRND.cpp index 0a47997..ffc8350 100644 --- a/MUNGA_L4/L4VIDRND.cpp +++ b/MUNGA_L4/L4VIDRND.cpp @@ -2283,6 +2283,20 @@ void // case FadeOutState: { + // + // The Winners Circle has taken the screen, so leave it alone. + // This fade multiplies the fog colour and both fog distances + // toward zero every frame, which blacks out the whole scene - + // correct when the race just ends, fatal to a podium shown + // afterwards. Stand down and stop running. + // + if (myRenderer->InPresentation()) + { + myState = WaitForStartState; + myRenderer->RemoveDynamicRenderable(this); + break; + } + percent_time_left = (myStateTimer - current_time)/FADE_OUT_TIME; if(percent_time_left <= 0.0f) { diff --git a/RP/RPPLAYER.cpp b/RP/RPPLAYER.cpp index 56cdbaa..40f13f4 100644 --- a/RP/RPPLAYER.cpp +++ b/RP/RPPLAYER.cpp @@ -11,6 +11,16 @@ #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 ###################### //############################################################################# @@ -188,6 +198,24 @@ void 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(); } diff --git a/RP_L4/RPL4APP.cpp b/RP_L4/RPL4APP.cpp index 661c47a..c771da6 100644 --- a/RP_L4/RPL4APP.cpp +++ b/RP_L4/RPL4APP.cpp @@ -15,12 +15,44 @@ #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" // //############################################################################# // RPL4Application //############################################################################# // +const Receiver::HandlerEntry + RPL4Application::MessageHandlerEntries[]= +{ + MESSAGE_ENTRY(RPL4Application, StopMission) +}; + +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() + ); + RPL4Application::RPL4Application( HINSTANCE hInstance, HWND hWnd, @@ -43,6 +75,258 @@ 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. + //--------------------------------------------------------------------- + // + char winners_spot[] = "win?"; + char *place = winners_spot + 3; + int placed = 0; + Point3D standFront(0.0f, 0.0f, 0.0f); + Point3D standCentre(0.0f, 0.0f, 0.0f); + + Player *p; + for (int rank = 0; (p = CameraDirector::FindPlayerByRank(rank)) != NULL; ++rank) + { + if (rank > 7) + { + break; // only eight spots exist on the stand + } + *place = (char) ('1' + rank); + + ChainIteratorOf iterator(dropzones->groupMembers); + DropZone *dropzone; + while ((dropzone = (DropZone*) iterator.ReadAndNext()) != NULL) + { + if (!strcmp(dropzone->GetDropZoneName(), winners_spot)) + { + break; + } + } + if (dropzone == NULL) + { + continue; + } + + Entity *vehicle = p->GetPlayerVehicle(); + if (vehicle == NULL || vehicle->GetClassID() != VTVClassID) + { + continue; + } + VTV *vtv = (VTV*) vehicle; + vtv->Reset(dropzone->localOrigin, VTV::MissionReviewReset); + vtv->SetPerformance(&VTV::DoNothing); + vtv->FlushEvents(); + + Point3D spot = dropzone->localOrigin.linearPosition; + if (placed == 0) + { + // remember where the front of the stand is - the shot is framed + // off it rather than off hardcoded map coordinates + standFront = spot; + } + standCentre += spot; + DEBUG_STREAM << "WinnersCircle: " << winners_spot << " at " + << spot.x << "," << spot.y << "," << spot.z << "\n" << std::flush; + ++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; + } + + // + //--------------------------------------------------------------------- + // 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. + //--------------------------------------------------------------------- + // + dpl_renderer->SortAndReloadNameBitmaps(); + + // + //--------------------------------------------------------------------- + // 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 middle of the group. + //--------------------------------------------------------------------- + // + standCentre.x /= (Scalar) placed; + standCentre.y /= (Scalar) placed; + standCentre.z /= (Scalar) placed; + + // + // Stand off along the line from the middle of the group out through the + // front spot, so the shot faces the stand however the map has it turned, + // and lift the eye above the top tier to look down on the line-up. + // + Vector3D facing; + facing.x = standFront.x - standCentre.x; + facing.y = 0.0f; + facing.z = standFront.z - standCentre.z; + Scalar reach = (Scalar) sqrt(facing.x * facing.x + facing.z * facing.z); + if (reach < 0.01f) + { + 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: far enough back for all eight bays and + // the numbers above them, high enough to look down the tiers + Scalar standoff = 45.0f; + Scalar height = 18.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 = standCentre.x + (facing.x / reach) * standoff; + eye.y = standCentre.y + height; + eye.z = standCentre.z + (facing.z / reach) * standoff; + standCentre.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. + // + // + // 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, standCentre); + } + 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; + } + + DEBUG_STREAM << "WinnersCircle: " << placed << " placed; centre " + << standCentre.x << "," << standCentre.y << "," << standCentre.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, so only the first stands anybody up. + // + if (GetApplicationState() != Application::EndingMission) + { + DEBUG_STREAM << "WinnersCircle: mission stopped, standing the finishers up\n" + << std::flush; + ShowWinnersCircle(); + } + + L4Application::StopMissionMessageHandler(message); + Check_Fpu(); +} + // //############################################################################# // MakeRegistry diff --git a/RP_L4/RPL4APP.h b/RP_L4/RPL4APP.h index a46e5f0..1615ea6 100644 --- a/RP_L4/RPL4APP.h +++ b/RP_L4/RPL4APP.h @@ -26,4 +26,27 @@ private: Mission* MakeMission(NotationFile *notation_file, ResourceFile *resources); Entity* MakeViewpointEntity(Entity__MakeMessage *); + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// The Winners Circle. +// +// The podium is engine content that only the mission-review build ever +// ran: geometry, the eight ranked dropzones and the presentation code all +// ship, but the sequence lived on RPL4PlaybackApplication behind a spool +// file. This is the same sequence on the path the pods actually race. +// +public: + static const HandlerEntry + MessageHandlerEntries[]; + static MessageHandlerSet& GetMessageHandlers(); + static Derivation *GetClassDerivations(); + static SharedData DefaultData; + + void + StopMissionMessageHandler(StopMissionMessage *message); + +private: + // stand the finishers on their ranked spots and frame the shot + void + ShowWinnersCircle(); }; diff --git a/RP_L4/RPL4CONSOLE.cpp b/RP_L4/RPL4CONSOLE.cpp index 3de0906..e57f3d4 100644 --- a/RP_L4/RPL4CONSOLE.cpp +++ b/RP_L4/RPL4CONSOLE.cpp @@ -498,6 +498,19 @@ namespace << mission_seconds << "s\n" << std::flush; } + // + // RP412MISSIONSECONDS overrides the menu's game length. The shortest + // the menu offers is 3:00, which is a long wait when what you are + // testing is what happens at the buzzer. + // + const char *seconds_override = getenv("RP412MISSIONSECONDS"); + if (seconds_override != NULL && atoi(seconds_override) > 0) + { + mission_seconds = atoi(seconds_override); + DEBUG_STREAM << "LocalConsole: length overridden to " + << mission_seconds << "s by RP412MISSIONSECONDS\n" << std::flush; + } + gMissionSeconds = mission_seconds; InterlockedExchange(&gLengthMs, (LONG) mission_seconds * 1000); gPhase = PhaseWaiting;