/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2008-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. */ // game over dialog showing winners and losers #include "C4Include.h" #include "gui/C4GameOverDlg.h" #include "game/C4Application.h" #include "object/C4Def.h" #include "object/C4DefList.h" #include "game/C4Game.h" #include "game/C4FullScreen.h" #include "player/C4Player.h" #include "gui/C4PlayerInfoListBox.h" #include "player/C4PlayerList.h" #include "object/C4GameObjects.h" #include "control/C4GameControl.h" #include "graphics/C4GraphicsResource.h" // --------------------------------------------------- // C4GoalDisplay C4GoalDisplay::GoalPicture::GoalPicture(const C4Rect &rcBounds, C4ID idGoal, bool fFulfilled) : C4GUI::Window(), idGoal(idGoal), fFulfilled(fFulfilled) { // bounds SetBounds(rcBounds); // can't get specialized desc from object at the moment because of potential script callbacks! StdStrBuf strGoalName; // just get desc from def C4Def *pGoalDef = ::Definitions.ID2Def(idGoal); if (pGoalDef) { strGoalName.Copy(pGoalDef->GetName()); // strGoalDesc.Copy(pGoalDef->GetDesc()); } // get tooltip StdStrBuf sToolTip; if (fFulfilled) sToolTip.Format(LoadResStr("IDS_DESC_GOALFULFILLED"), strGoalName.getData()); else sToolTip.Format(LoadResStr("IDS_DESC_GOALNOTFULFILLED"), strGoalName.getData()); SetToolTip(sToolTip.getData()); // create buffered picture of goal definition C4Def *pDrawDef = ::Definitions.ID2Def(idGoal); if (pDrawDef) { Picture.Create(C4PictureSize, C4PictureSize); // get an object instance to draw (optional; may be zero) C4Object *pGoalObj = ::Objects.Find(pDrawDef); // draw goal def! pDrawDef->Draw(Picture, false, 0, pGoalObj); } // unfulfilled goal: grey out picture if (!fFulfilled) Picture.Grayscale(30); } void C4GoalDisplay::GoalPicture::DrawElement(C4TargetFacet &cgo) { // draw area C4Facet cgoDraw; cgoDraw.Set(cgo.Surface, cgo.X+rcBounds.x+cgo.TargetX, cgo.Y+rcBounds.y+cgo.TargetY, rcBounds.Wdt, rcBounds.Hgt); // draw buffered picture Picture.Draw(cgoDraw); // draw star symbol if fulfilled if (fFulfilled) { cgoDraw.Set(cgoDraw.Surface, cgoDraw.X+cgoDraw.Wdt*1/2, cgoDraw.Y+cgoDraw.Hgt*1/2, cgoDraw.Wdt/2, cgoDraw.Hgt/2); C4GUI::Icon::GetIconFacet(C4GUI::Ico_Star).Draw(cgoDraw); } } void C4GoalDisplay::SetGoals(const C4IDList &rAllGoals, const C4IDList &rFulfilledGoals, int32_t iGoalSymbolHeight) { // clear previous ClearChildren(); // determine goal display area by goal count int32_t iGoalSymbolMargin = C4GUI_DefDlgSmallIndent; int32_t iGoalSymbolAreaHeight = 2*iGoalSymbolMargin + iGoalSymbolHeight; int32_t iGoalAreaWdt = GetClientRect().Wdt; int32_t iGoalsPerRow = std::max(1, iGoalAreaWdt / iGoalSymbolAreaHeight); int32_t iGoalCount = rAllGoals.GetNumberOfIDs(); int32_t iRowCount = (iGoalCount-1) / iGoalsPerRow + 1; C4Rect rcNewBounds = GetBounds(); rcNewBounds.Hgt = GetMarginTop()+GetMarginBottom()+iRowCount*iGoalSymbolAreaHeight; SetBounds(rcNewBounds); C4GUI::ComponentAligner caAll(GetClientRect(), 0,0, true); // place goal symbols in this area int32_t iGoal = 0; for (int32_t iRow=0; iRow(iGoalCount - iGoal, iGoalsPerRow); C4GUI::ComponentAligner caGoalArea(caAll.GetFromTop(iGoalSymbolAreaHeight, iColCount*iGoalSymbolAreaHeight), iGoalSymbolMargin,iGoalSymbolMargin, false); for (int32_t iCol=0; iCol(C4GUI::GetScreenWdt()-150, 800), (C4GUI::GetScreenHgt() < 600) ? (C4GUI::GetScreenHgt()-10) : std::min(C4GUI::GetScreenHgt()-150, 600), LoadResStr("IDS_TEXT_EVALUATION"), false), pNetResultLabel(nullptr), fIsNetDone(false), fHasNextMissionButton(false) { is_shown = true; // assume dlg will be shown, soon UpdateOwnPos(); // indents / sizes int32_t iDefBtnHeight = 32; int32_t iIndentX1=10; int32_t iIndentY1=6, iIndentY2=0; // main screen components C4GUI::ComponentAligner caMain(GetClientRect(), 0,iIndentY1,true); int32_t iMainTextWidth = caMain.GetWidth() - 6*iIndentX1; caMain.GetFromBottom(iIndentY2); // lower button-area C4GUI::ComponentAligner caBottom(caMain.GetFromBottom(iDefBtnHeight+iIndentY1*2), iIndentX1,0); int32_t iBottomButtonSize = caBottom.GetInnerWidth(); iBottomButtonSize = std::min(iBottomButtonSize/2-2*iIndentX1, ::GraphicsResource.CaptionFont.GetTextWidth("Quit it, baby! And some.")*2); // goal display const C4IDList &rGoals = Game.RoundResults.GetGoals(); const C4IDList &rFulfilledGoals = Game.RoundResults.GetFulfilledGoals(); if (rGoals.GetNumberOfIDs()) { C4GoalDisplay *pGoalDisplay = new C4GoalDisplay(caMain.GetFromTop(C4GUI_IconExHgt)); pGoalDisplay->SetGoals(rGoals, rFulfilledGoals, C4GUI_IconExHgt); AddElement(pGoalDisplay); // goal display may have resized itself; adjust component aligner caMain.ExpandTop(C4GUI_IconExHgt - pGoalDisplay->GetBounds().Hgt); } // league/network result, present or pending fIsNetDone = false; bool fHasNetResult = Game.RoundResults.HasNetResult(); const char *szNetResult = nullptr; if (Game.Parameters.isLeague() || fHasNetResult) { if (fHasNetResult) szNetResult = Game.RoundResults.GetNetResultString(); else szNetResult = LoadResStr("IDS_TEXT_LEAGUEWAITINGFOREVALUATIO"); pNetResultLabel = new C4GUI::Label(szNetResult, caMain.GetFromTop(::GraphicsResource.TextFont.GetLineHeight()*2, iMainTextWidth), ACenter, C4GUI_Caption2FontClr, nullptr, false, false, true); AddElement(pNetResultLabel); // only add label - contents and fIsNetDone will be set in next update } else { // otherwise, network is always done fIsNetDone = true; } // extra evaluation string area const char *szCustomEvaluationStrings = Game.RoundResults.GetCustomEvaluationStrings(); if (szCustomEvaluationStrings && *szCustomEvaluationStrings) { int32_t iMaxHgt = caMain.GetInnerHeight() / 3; // max 1/3rd of height for extra data C4GUI::MultilineLabel *pCustomStrings = new C4GUI::MultilineLabel(caMain.GetFromTop(0 /* resized later*/, iMainTextWidth), 0,0, " ", true, true); pCustomStrings->AddLine(szCustomEvaluationStrings, &::GraphicsResource.TextFont, C4GUI_MessageFontClr, true, false, nullptr); C4Rect rcCustomStringBounds = pCustomStrings->GetBounds(); if (rcCustomStringBounds.Hgt > iMaxHgt) { // Buffer too large: Use a scrollbox instead delete pCustomStrings; rcCustomStringBounds.Hgt = iMaxHgt; C4GUI::TextWindow *pCustomStringsWin = new C4GUI::TextWindow(rcCustomStringBounds, 0,0,0, 0,0," ",true, nullptr,0, true); pCustomStringsWin->SetDecoration(false, false, nullptr, false); pCustomStringsWin->AddTextLine(szCustomEvaluationStrings, &::GraphicsResource.TextFont, C4GUI_MessageFontClr, true, false, nullptr); caMain.ExpandTop(-iMaxHgt); AddElement(pCustomStringsWin); } else { // buffer size OK: Reserve required space caMain.ExpandTop(-rcCustomStringBounds.Hgt); AddElement(pCustomStrings); } } // player list area C4GUI::ComponentAligner caPlayerArea(caMain.GetAll(), iIndentX1,0); iPlrListCount = 1; bool fSepTeamLists = false; if (Game.Teams.GetTeamCount() == 2 && !Game.Teams.IsAutoGenerateTeams()) { // exactly two predefined teams: Use two player list boxes; one for each team iPlrListCount = 2; fSepTeamLists = true; } ppPlayerLists = new C4PlayerInfoListBox *[iPlrListCount]; for (int32_t i=0; iGetID() : 0); ppPlayerLists[i]->SetSelectionDiabled(true); ppPlayerLists[i]->SetDecoration(false, nullptr, true, false); AddElement(ppPlayerLists[i]); } // add buttons C4GUI::CallbackButton *btnExit; pBtnExit = btnExit = new C4GUI::CallbackButton(LoadResStr("IDS_BTN_ENDROUND"), caBottom.GetGridCell(0,2, 0,1, iBottomButtonSize, -1, true), &C4GameOverDlg::OnExitBtn); btnExit->SetToolTip(LoadResStr("IDS_DESC_ENDTHEROUND")); AddElement(btnExit); C4GUI::CallbackButton *btnContinue; pBtnContinue = btnContinue = new C4GUI::CallbackButton(LoadResStr("IDS_BTN_CONTINUEGAME"), caBottom.GetGridCell(1,2, 0,1, iBottomButtonSize, -1, true), &C4GameOverDlg::OnContinueBtn); btnContinue->SetToolTip(LoadResStr("IDS_DESC_CONTINUETHEROUNDWITHNOFUR")); AddElement(btnContinue); // convert continue button to "next mission" button if available if (Game.NextMission) { // not available for regular replay and network clients, obviously // it is available for films though, so you can create cinematics for adventures if (::Control.isCtrlHost() || (Game.C4S.Head.Film == 2)) { fHasNextMissionButton = true; btnContinue->SetText(Game.NextMissionText.getData()); btnContinue->SetToolTip(Game.NextMissionDesc.getData()); } } fIsQuitBtnVisible = fIsNetDone || !::Network.isHost(); // updates Application.Add(this); Update(); // initial focus on quit button if visible, so space/enter/low gamepad buttons quit if (fIsQuitBtnVisible) SetFocus(btnExit, false); } C4GameOverDlg::~C4GameOverDlg() { Application.Remove(this); delete ppPlayerLists; is_shown = false; } void C4GameOverDlg::Update() { for (int32_t i=0; iUpdate(); if (pNetResultLabel) { SetNetResult(Game.RoundResults.GetNetResultString(), Game.RoundResults.GetNetResult(), ::Network.getPendingStreamData(), ::Network.isStreaming()); } // exit/continue button only visible for host if league streaming finished bool fBtnsVisible = fIsNetDone || !::Network.isHost(); if (fBtnsVisible != fIsQuitBtnVisible) { fIsQuitBtnVisible = fBtnsVisible; pBtnExit->SetVisibility(fBtnsVisible); pBtnContinue->SetVisibility(fBtnsVisible); if (fIsQuitBtnVisible) SetFocus(pBtnExit, false); } } void C4GameOverDlg::SetNetResult(const char *szResultString, C4RoundResults::NetResult eResultType, size_t iPendingStreamingData, bool fIsStreaming) { // add info about pending streaming data StdStrBuf sResult(szResultString); if (fIsStreaming) { sResult.AppendChar('|'); sResult.AppendFormat("[!]Transmitting record to league server... (%d kb remaining)", int(iPendingStreamingData/1024)); } // message linebreak into box StdStrBuf sBrokenResult; ::GraphicsResource.TextFont.BreakMessage(sResult.getData(), pNetResultLabel->GetBounds().Wdt, &sBrokenResult, true); pNetResultLabel->SetText(sBrokenResult.getData(), false); // all done? if (eResultType != C4RoundResults::NR_None && !fIsStreaming) { // a final result is determined and all streaming data has been transmitted fIsNetDone = true; } // network error? if (eResultType == C4RoundResults::NR_NetError) { // disconnected. Do not show winners/losers for (int32_t i=0; iSetMode(C4PlayerInfoListBox::PILBM_EvaluationNoWinners); } } void C4GameOverDlg::OnExitBtn(C4GUI::Control *btn) { // callback: exit button pressed. Application.QuitGame(); Close(false); } void C4GameOverDlg::OnContinueBtn(C4GUI::Control *btn) { // callback: continue button pressed if (fHasNextMissionButton) { // switch to next mission if next mission button is pressed Application.SetNextMission(Game.NextMission.getData()); Application.QuitGame(); } Close(true); // unpauses and deletes this object } void C4GameOverDlg::OnShown() { // close some other dialogs Game.Scoreboard.HideDlg(); FullScreen.CloseMenu(); for (C4Player *plr = ::Players.First; plr; plr = plr->Next) plr->CloseMenu(); // pause game when round results dlg is shown Game.Pause(); } void C4GameOverDlg::OnClosed(bool fOK) { Game.Unpause(); typedef C4GUI::Dialog BaseClass; BaseClass::OnClosed(fOK); // deletes this object! }