/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 1998-2000, Matthes Bender * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ /* Operates viewports, message board and draws the game */ #include "C4Include.h" #include "game/C4GraphicsSystem.h" #include "game/C4Viewport.h" #include "game/C4Application.h" #include "editor/C4Console.h" #include "game/C4FullScreen.h" #include "gui/C4Gui.h" #include "gui/C4LoaderScreen.h" #include "graphics/C4GraphicsResource.h" #include "landscape/C4Landscape.h" #include "landscape/C4Sky.h" #include "network/C4Network2.h" #include "game/C4Game.h" #include "object/C4GameObjects.h" #include "graphics/StdPNG.h" static const int MAX_BACKGROUND_FPS = 5; C4GraphicsSystem::C4GraphicsSystem() { Default(); } C4GraphicsSystem::~C4GraphicsSystem() { Clear(); } bool C4GraphicsSystem::Init() { // Success return true; } void C4GraphicsSystem::Clear() { // Clear message board MessageBoard.reset(); // clear loader if (pLoaderScreen) { delete pLoaderScreen; pLoaderScreen=NULL; } // Close viewports ::Viewports.Clear(); // No debug stuff DeactivateDebugOutput(); } bool C4GraphicsSystem::StartDrawing() { // only if ddraw is ready if (!pDraw) return false; if (!pDraw->Active) return false; // if the window is not focused, draw no more than MAX_BACKGROUND_FPS frames per second if (!Application.Active && (C4TimeMilliseconds::Now() - lastFrame) < 1000 / MAX_BACKGROUND_FPS) return false; // drawing OK return true; } void C4GraphicsSystem::FinishDrawing() { if (!Application.isEditor) FullScreen.pSurface->PageFlip(); lastFrame = C4TimeMilliseconds::Now(); } void C4GraphicsSystem::Execute() { // activity check if (!StartDrawing()) return; bool fBGDrawn = false; // If lobby running, message board only (page flip done by startup message board) if (!::pGUI->HasFullscreenDialog(true)) // allow for message board behind GUI if (::Network.isLobbyActive() || !Game.IsRunning) if (!Application.isEditor) { // Message board if (iRedrawBackground) ClearFullscreenBackground(); MessageBoard->Execute(); if (!C4GUI::IsActive()) { FinishDrawing(); return; } fBGDrawn = true; } // fullscreen GUI? if (!Application.isEditor && C4GUI::IsActive() && (::pGUI->HasFullscreenDialog(false) || !Game.IsRunning)) { if (!fBGDrawn && iRedrawBackground) ClearFullscreenBackground(); ::pGUI->Render(!fBGDrawn); FinishDrawing(); return; } // Reset object audibility ::Objects.ResetAudibility(); // some hack to ensure the mouse is drawn after a dialog close and before any // movement messages if (!C4GUI::IsActive()) ::pGUI->SetMouseInGUI(false, false); // Viewports ::Viewports.Execute(!Application.isEditor && iRedrawBackground); if (iRedrawBackground) --iRedrawBackground; if (!Application.isEditor) { // Upper board UpperBoard.Execute(); // Message board MessageBoard->Execute(); // Help & Messages DrawHelp(); DrawHoldMessages(); DrawFlashMessage(); } // InGame-GUI if (C4GUI::IsActive()) { ::pGUI->Render(false); } // done FinishDrawing(); } void C4GraphicsSystem::Default() { MessageBoard.reset(new C4MessageBoard); InvalidateBg(); ShowVertices=false; ShowAction=false; ShowCommand=false; ShowEntrance=false; ShowPathfinder=false; ShowNetstatus=false; Show8BitSurface=0; ShowLights=false; ShowMenuInfo=false; ShowHelp=false; FlashMessageText[0]=0; FlashMessageTime=0; FlashMessageX=FlashMessageY=0; pLoaderScreen=NULL; } void C4GraphicsSystem::ClearFullscreenBackground() { pDraw->FillBG(0); --iRedrawBackground; } bool C4GraphicsSystem::InitLoaderScreen(const char *szLoaderSpec) { // create new loader; overwrite current only if successful C4LoaderScreen *pNewLoader = new C4LoaderScreen(); pNewLoader->SetBlackScreen(false); if (!pNewLoader->Init(szLoaderSpec)) { delete pNewLoader; return false; } if (pLoaderScreen) delete pLoaderScreen; pLoaderScreen = pNewLoader; // done, success return true; } void C4GraphicsSystem::EnableLoaderDrawing() { // reset black screen loader flag if (pLoaderScreen) pLoaderScreen->SetBlackScreen(false); } bool C4GraphicsSystem::SaveScreenshot(bool fSaveAll, float fSaveAllZoom) { // Find a unique screenshot filename by iterating over all possible names // Keep static counter so multiple screenshots in succession do not use same filename even if the background thread hasn't started writing the file yet char szFilename[_MAX_PATH+1]; static int32_t iScreenshotIndex=1; const char *strFilePath = NULL; do sprintf(szFilename,"Screenshot%03i.png",iScreenshotIndex++); while (FileExists(strFilePath = Config.AtScreenshotPath(szFilename))); bool fSuccess=DoSaveScreenshot(fSaveAll, strFilePath, fSaveAllZoom); // log if successful/where it has been stored if (!fSuccess) LogF(LoadResStr("IDS_PRC_SCREENSHOTERROR"), Config.AtUserDataRelativePath(Config.AtScreenshotPath(szFilename))); else LogF(LoadResStr("IDS_PRC_SCREENSHOT"), Config.AtUserDataRelativePath(Config.AtScreenshotPath(szFilename))); // return success return !!fSuccess; } bool C4GraphicsSystem::DoSaveScreenshot(bool fSaveAll, const char *szFilename, float fSaveAllZoom) { // Fullscreen only if (Application.isEditor) return false; // back surface must be present if (!FullScreen.pSurface) return false; // save landscape if (fSaveAll) { // Create full map screenshots at zoom 2x. Fractional zooms (like 1.7x) should work but might cause some trouble at screen borders. float zoom = fSaveAllZoom; // get viewport to draw in C4Viewport *pVP=::Viewports.GetFirstViewport(); if (!pVP) return false; // create image large enough to hold the landscape std::unique_ptr png(new CPNGFile()); int32_t lWdt = ::Landscape.GetWidth() * zoom, lHgt = ::Landscape.GetHeight() * zoom; if (!png->Create(lWdt, lHgt, false)) return false; // get backbuffer size int32_t bkWdt=C4GUI::GetScreenWdt(), bkHgt=C4GUI::GetScreenHgt(); if (!bkWdt || !bkHgt) return false; // facet for blitting C4TargetFacet bkFct; // mark background to be redrawn InvalidateBg(); // draw on one big viewport pVP->SetOutputSize(0,0,0,0, bkWdt, bkHgt); // backup and clear sky parallaxity int32_t iParX=::Landscape.GetSky().ParX; ::Landscape.GetSky().ParX=10; int32_t iParY=::Landscape.GetSky().ParY; ::Landscape.GetSky().ParY=10; // backup and clear viewport borders FLOAT_RECT vp_borders = { pVP->BorderLeft, pVP->BorderRight, pVP->BorderTop, pVP->BorderBottom }; pVP->BorderLeft = pVP->BorderRight = pVP->BorderTop = pVP->BorderBottom = 0.0f; // temporarily change viewport player int32_t iVpPlr=pVP->Player; pVP->Player=NO_OWNER; // blit all tiles needed for (int32_t iY=0; iYlWdt) bkWdt2-=iX+bkWdt2-lWdt; if (iY+bkHgt2>lHgt) bkHgt2-=iY+bkHgt2-lHgt; // update facet bkFct.Set(FullScreen.pSurface, 0, 0, ceil(float(bkWdt2)/zoom), ceil(float(bkHgt2)/zoom), iX/zoom, iY/zoom, zoom, 0, 0); // draw there pVP->Draw(bkFct, true, false); // render FullScreen.pSurface->PageFlip(); FullScreen.pSurface->PageFlip(); // get output (locking primary!) if (FullScreen.pSurface->Lock()) { // transfer each pixel - slooow... for (int32_t iY2 = 0; iY2 < bkHgt2; ++iY2) #ifndef USE_CONSOLE glReadPixels(0, FullScreen.pSurface->Hgt - iY2 - 1, bkWdt2, 1, GL_BGR, GL_UNSIGNED_BYTE, reinterpret_cast(png->GetRow(iY + iY2)) + iX * 3); #else for (int32_t iX2=0; iX2SetPix(iX+iX2, iY+iY2, FullScreen.pSurface->GetPixDw(iX2, iY2, false)); #endif // done; unlock FullScreen.pSurface->Unlock(); // This can take a long time and we would like to pump messages // However, we're currently hogging the primary surface and horrible things might happen if we do that, including initiation of another screenshot // The only thing that can be safely run is music (sound could play but that would just make them out of sync of the game) ::Application.MusicSystem.Execute(true); } } // restore viewport player pVP->Player=iVpPlr; // restore viewport borders pVP->BorderLeft = vp_borders.left; pVP->BorderTop = vp_borders.top; pVP->BorderRight = vp_borders.right; pVP->BorderBottom = vp_borders.bottom; // restore parallaxity ::Landscape.GetSky().ParX=iParX; ::Landscape.GetSky().ParY=iParY; // restore viewport size ::Viewports.RecalculateViewports(); // save! CPNGFile::ScheduleSaving(png.release(), szFilename); return true; } // Save primary surface in background thread return FullScreen.pSurface->SavePNG(szFilename, false, false, true); } void C4GraphicsSystem::DeactivateDebugOutput() { ShowVertices=false; ShowAction=false; ShowCommand=false; ShowEntrance=false; ShowPathfinder=false; // allow pathfinder! - why this?? ShowLights=false; Show8BitSurface=0; ShowNetstatus=false; ShowMenuInfo=false; } void C4GraphicsSystem::DrawHoldMessages() { if (!Application.isEditor && Game.HaltCount) { pDraw->TextOut("Pause", ::GraphicsResource.FontRegular,1.0, FullScreen.pSurface, C4GUI::GetScreenWdt()/2, C4GUI::GetScreenHgt()/2 - ::GraphicsResource.FontRegular.GetLineHeight()*2, C4Draw::DEFAULT_MESSAGE_COLOR, ACenter); ::GraphicsSystem.OverwriteBg(); } } void C4GraphicsSystem::FlashMessage(const char *szMessage) { // Store message SCopy(szMessage, FlashMessageText, C4MaxTitle); // Calculate message time FlashMessageTime = SLen(FlashMessageText) * 2; // Initial position FlashMessageX = -1; FlashMessageY = 10; // Upper board active: stay below upper board if (Config.Graphics.UpperBoard) FlashMessageY += C4UpperBoardHeight; // More than one viewport: try to stay below portraits etc. if (::Viewports.GetViewportCount() > 1) FlashMessageY += 64; // New flash message: redraw background (might be drawing one message on top of another) InvalidateBg(); } void C4GraphicsSystem::FlashMessageOnOff(const char *strWhat, bool fOn) { StdStrBuf strMessage; strMessage.Format("%s: %s", strWhat, LoadResStr(fOn ? "IDS_CTL_ON" : "IDS_CTL_OFF")); FlashMessage(strMessage.getData()); } void C4GraphicsSystem::DrawFlashMessage() { if (!FlashMessageTime) return; if (Application.isEditor) return; pDraw->TextOut(FlashMessageText, ::GraphicsResource.FontRegular, 1.0, FullScreen.pSurface, (FlashMessageX==-1) ? C4GUI::GetScreenWdt()/2 : FlashMessageX, (FlashMessageY==-1) ? C4GUI::GetScreenHgt()/2 : FlashMessageY, C4Draw::DEFAULT_MESSAGE_COLOR, (FlashMessageX==-1) ? ACenter : ALeft); FlashMessageTime--; // Flash message timed out: redraw background if (!FlashMessageTime) InvalidateBg(); } void C4GraphicsSystem::DrawHelp() { if (!ShowHelp) return; if (Application.isEditor) return; int32_t iX = ::Viewports.ViewportArea.X, iY = ::Viewports.ViewportArea.Y; int32_t iWdt = ::Viewports.ViewportArea.Wdt; StdStrBuf strText; // left coloumn strText.AppendFormat("[%s]\n\n", LoadResStr("IDS_CTL_GAMEFUNCTIONS")); // main functions strText.AppendFormat("%s - %s\n", GetKeyboardInputName("ToggleShowHelp").getData(), LoadResStr("IDS_CON_HELP")); strText.AppendFormat("%s - %s\n", GetKeyboardInputName("MusicToggle").getData(), LoadResStr("IDS_CTL_MUSIC")); strText.AppendFormat("%s - %s\n", GetKeyboardInputName("NetClientListDlgToggle").getData(), LoadResStr("IDS_DLG_NETWORK")); // messages StdCopyStrBuf strAltChatKey(GetKeyboardInputName("ChatOpen", false, 0)); strText.AppendFormat("\n%s/%s - %s\n", GetKeyboardInputName("ChatOpen", false, 1).getData(), strAltChatKey.getData(), LoadResStr("IDS_CTL_SENDMESSAGE")); strText.AppendFormat("%s - %s\n", GetKeyboardInputName("MsgBoardScrollUp").getData(), LoadResStr("IDS_CTL_MESSAGEBOARDBACK")); strText.AppendFormat("%s - %s\n", GetKeyboardInputName("MsgBoardScrollDown").getData(), LoadResStr("IDS_CTL_MESSAGEBOARDFORWARD")); // irc chat strText.AppendFormat("\n%s - %s\n", GetKeyboardInputName("ToggleChat").getData(), LoadResStr("IDS_CTL_IRCCHAT")); // scoreboard strText.AppendFormat("\n%s - %s\n", GetKeyboardInputName("ScoreboardToggle").getData(), LoadResStr("IDS_CTL_SCOREBOARD")); // screenshots strText.AppendFormat("\n%s - %s\n", GetKeyboardInputName("Screenshot").getData(), LoadResStr("IDS_CTL_SCREENSHOT")); strText.AppendFormat("%s - %s\n", GetKeyboardInputName("ScreenshotEx").getData(), LoadResStr("IDS_CTL_SCREENSHOTEX")); pDraw->TextOut(strText.getData(), ::GraphicsResource.FontRegular, 1.0, FullScreen.pSurface, iX + 128, iY + 64, C4Draw::DEFAULT_MESSAGE_COLOR, ALeft); // right coloumn strText.Clear(); // game speed strText.AppendFormat("\n\n%s - %s\n", GetKeyboardInputName("GameSpeedUp").getData(), LoadResStr("IDS_CTL_GAMESPEEDUP")); strText.AppendFormat("%s - %s\n", GetKeyboardInputName("GameSlowDown").getData(), LoadResStr("IDS_CTL_GAMESPEEDDOWN")); // debug strText.AppendFormat("\n\n[%s]\n\n", "Debug"); strText.AppendFormat("%s - %s\n", GetKeyboardInputName("DbgModeToggle").getData(), LoadResStr("IDS_CTL_DEBUGMODE")); strText.AppendFormat("%s - %s\n", GetKeyboardInputName("DbgShowVtxToggle").getData(), "Entrance+Vertices"); strText.AppendFormat("%s - %s\n", GetKeyboardInputName("DbgShowActionToggle").getData(), "Actions/Commands/Pathfinder/Lights/Menus"); strText.AppendFormat("%s - %s\n", GetKeyboardInputName("DbgShow8BitSurface").getData(), "8-bit surfaces"); pDraw->TextOut(strText.getData(), ::GraphicsResource.FontRegular, 1.0, FullScreen.pSurface, iX + iWdt/2 + 64, iY + 64, C4Draw::DEFAULT_MESSAGE_COLOR, ALeft); } bool C4GraphicsSystem::ToggleShowNetStatus() { ShowNetstatus = !ShowNetstatus; return true; } bool C4GraphicsSystem::ToggleShowVertices() { if (!Game.DebugMode && !Console.Active) { FlashMessage(LoadResStr("IDS_MSG_NODEBUGMODE")); return false; } ShowVertices = !ShowVertices; ShowEntrance = !ShowEntrance; // vertices and entrance now toggled together FlashMessageOnOff("Entrance+Vertices", ShowVertices || ShowEntrance); return true; } bool C4GraphicsSystem::ToggleShowAction() { if (!Game.DebugMode && !Console.Active) { FlashMessage(LoadResStr("IDS_MSG_NODEBUGMODE")); return false; } if (!(ShowAction || ShowCommand || ShowPathfinder || ShowLights || ShowMenuInfo)) { ShowAction = true; FlashMessage("Actions"); } else if (ShowAction) { ShowAction = false; ShowCommand = true; FlashMessage("Commands"); } else if (ShowCommand) { ShowCommand = false; ShowPathfinder = true; FlashMessage("Pathfinder"); } else if (ShowPathfinder) { ShowPathfinder = false; ShowLights = true; FlashMessage("Lights"); } else if (ShowLights) { ShowLights = false; ShowMenuInfo = true; FlashMessage("Menu Info"); } else if (ShowMenuInfo) { ShowMenuInfo = false; FlashMessageOnOff("Actions/Commands/Pathfinder/Lights/Menus", false); } return true; } bool C4GraphicsSystem::ToggleShow8BitSurface() { if (!Game.DebugMode && !Console.Active) { FlashMessage(LoadResStr("IDS_MSG_NODEBUGMODE")); return false; } Show8BitSurface = (Show8BitSurface + 1) % 3; if (Show8BitSurface == 0) FlashMessage("Default view"); else if (Show8BitSurface == 1) FlashMessage("Foreground 8-bit landscape"); else if (Show8BitSurface == 2) FlashMessage("Background 8-bit landscape"); return true; } bool C4GraphicsSystem::ToggleShowHelp() { ShowHelp = !ShowHelp; // Turned off? Invalidate background. if (!ShowHelp) InvalidateBg(); return true; }