/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2005-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. */ // Custom game options and configuration dialog #include "C4Include.h" #include "gui/C4GameOptions.h" #include "control/C4GameControl.h" #include "game/C4Application.h" #include "graphics/C4GraphicsResource.h" #include "gui/C4GameLobby.h" #include "gui/C4Startup.h" // ----------- C4GameOptionsList::Option ---------------------------------------------------------------- C4GameOptionsList::Option::Option(C4GameOptionsList *pForDlg) : BaseClass(C4Rect(0, 0, 0, 0)), pForDlg(pForDlg), pPrimarySubcomponent(nullptr) {} void C4GameOptionsList::Option::InitOption(C4GameOptionsList *pForDlg) { // post-call after initialization: Adds to list box and does initial update // add to listbox (will eventually get moved) pForDlg->AddElement(this); // first-time update Update(); } // ----------- C4GameOptionsList::OptionDropdown ---------------------------------------------------------------- C4GameOptionsList::OptionDropdown::OptionDropdown(class C4GameOptionsList *pForDlg, const char *szCaption, bool fReadOnly) : Option(pForDlg), fReadOnly(fReadOnly) { bool fIsPreGame = pForDlg->IsPreGame(); CStdFont &rUseFont = fIsPreGame ? C4Startup::Get()->Graphics.BookFont : ::GraphicsResource.TextFont; uint32_t dwFontClr = fIsPreGame ? C4StartupFontClr : 0xffffffff; // get size of caption label bool fTabular = pForDlg->IsTabular(); int32_t iCaptWidth, iCaptHeight; if (fTabular) { // tabular layout: Caption label width by largest caption on runtime; fixed 1/3rd on startup rUseFont.GetTextExtent(LoadResStr("IDS_NET_RUNTIMEJOIN"), iCaptWidth, iCaptHeight, true); if (pForDlg->IsPreGame()) { iCaptWidth = pForDlg->GetItemWidth() * 1 / 3; } else { iCaptWidth = iCaptWidth * 5 / 4; } } else { rUseFont.GetTextExtent(szCaption, iCaptWidth, iCaptHeight, true); } // calc total height for component int iHorizontalMargin = 1; int iVerticalMargin = 1; int iComboMargin = 5; int iSelComboHgt = C4GUI::ComboBox::GetDefaultHeight(); SetBounds(C4Rect(0, 0, pForDlg->GetItemWidth(), (!fTabular) * (iCaptHeight + iVerticalMargin*2) + iVerticalMargin*2 + iSelComboHgt)); C4GUI::ComponentAligner ca(GetContainedClientRect(), iHorizontalMargin, iVerticalMargin); // create subcomponents AddElement(pCaption = new C4GUI::Label(FormatString("%s:", szCaption).getData(), fTabular ? ca.GetFromLeft(iCaptWidth, iCaptHeight) : ca.GetFromTop(iCaptHeight), ALeft, dwFontClr, &rUseFont, false, false)); ca.ExpandLeft(-iComboMargin); AddElement(pPrimarySubcomponent = pDropdownList = new C4GUI::ComboBox(ca.GetAll())); pDropdownList->SetReadOnly(fReadOnly); pDropdownList->SetComboCB(new C4GUI::ComboBox_FillCallback(this, &C4GameOptionsList::OptionDropdown::OnDropdownFill, &C4GameOptionsList::OptionDropdown::OnDropdownSelChange)); if (fIsPreGame) { pDropdownList->SetColors(C4StartupFontClr, C4StartupEditBGColor, C4StartupEditBorderColor); pDropdownList->SetFont(&rUseFont); pDropdownList->SetDecoration(&(C4Startup::Get()->Graphics.fctContext)); } // final init InitOption(pForDlg); } // ----------- C4GameOptionsList::OptionScenarioParameter---------------------------------------------------------------- C4GameOptionsList::OptionScenarioParameter::OptionScenarioParameter(class C4GameOptionsList *pForDlg, const class C4ScenarioParameterDef *parameter_def) : C4GameOptionsList::OptionDropdown(pForDlg, parameter_def->GetName(), !pForDlg->IsPreGame() && (!::Control.isCtrlHost() || ::Game.C4S.Head.SaveGame)), ParameterDef(parameter_def), LastValue(0), LastValueValid(false) { SetToolTip(parameter_def->GetDescription()); } void C4GameOptionsList::OptionScenarioParameter::DoDropdownFill(C4GUI::ComboBox_FillCB *pFiller) { // Fill dropdown menuy with known possible options for this parameter size_t idx=0; const C4ScenarioParameterDef::Option *option; while ((option = ParameterDef->GetOptionByIndex(idx++))) { pFiller->AddEntry(option->Name.getData(), option->Value); } } void C4GameOptionsList::OptionScenarioParameter::DoDropdownSelChange(int32_t idNewSelection) { // runtime change needs to be synchronized if (!pForDlg->IsPreGame()) { // change possible? if (!::Control.isCtrlHost()) return; // Then initiate an update of the parameters on all clients C4GameLobby::C4PacketSetScenarioParameter pck(ParameterDef->GetID(), idNewSelection); ::Network.Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_SetScenarioParameter, pck)); } // also process on host (and standalone pre-game) pForDlg->GetParameters()->SetValue(ParameterDef->GetID(), idNewSelection, false); if (pForDlg->IsPreGame()) Update(); } void C4GameOptionsList::OptionScenarioParameter::Update() { int32_t val=0; // display forced league value? bool fLeagueReadOnly = false; if (::Config.Network.LeagueServerSignUp && !fReadOnly && !pForDlg->IsPreGameSingle()) val = ParameterDef->GetLeagueValue(); if (val) fLeagueReadOnly = true; else val = pForDlg->GetParameters()->GetValueByID(ParameterDef->GetID(), ParameterDef->GetDefault()); if (!fReadOnly) pDropdownList->SetReadOnly(fLeagueReadOnly); // update data to currently set option if (LastValueValid && val == LastValue) return; const C4ScenarioParameterDef::Option *option = ParameterDef->GetOptionByValue(val); if (option) pDropdownList->SetText(option->Name.getData()); else pDropdownList->SetText(FormatString("%d", (int)val).getData()); LastValueValid = true; LastValue = val; } // ----------- C4GameOptionsList::OptionControlMode ---------------------------------------------------------------- // Unfortunately, the control mode cannot be changed in the lobby C4GameOptionsList::OptionControlMode::OptionControlMode(class C4GameOptionsList *pForDlg) : C4GameOptionsList::OptionDropdown(pForDlg, LoadResStr("IDS_TEXT_CONTROLMODE"), !::Control.isCtrlHost() || !::Control.isNetwork() || !::Control.Network.IsEnabled()) { SetToolTip(LoadResStr("IDS_DESC_CHANGESTHEWAYCONTROLDATAI")); } void C4GameOptionsList::OptionControlMode::DoDropdownFill(C4GUI::ComboBox_FillCB *pFiller) { // change possible? if (!::Control.isNetwork() || !::Control.Network.IsEnabled() || !::Control.isCtrlHost()) return; // add possible modes pFiller->AddEntry(LoadResStr("IDS_NET_CTRLMODE_CENTRAL"), CNM_Central); pFiller->AddEntry(LoadResStr("IDS_NET_CTRLMODE_DECENTRAL"), CNM_Decentral); } void C4GameOptionsList::OptionControlMode::DoDropdownSelChange(int32_t idNewSelection) { // change possible? if (!::Control.isNetwork() || !::Control.Network.IsEnabled() || !::Control.isCtrlHost()) return; // perform it ::Network.SetCtrlMode(idNewSelection); // update for clients done by packet; host needs to set it manually Update(); } void C4GameOptionsList::OptionControlMode::Update() { const char *szControlMode; if (!::Control.isNetwork() || !::Control.Network.IsEnabled()) szControlMode = LoadResStr("IDS_NET_NONET"); else { switch (::Network.Status.getCtrlMode()) { case CNM_Central: szControlMode = LoadResStr("IDS_NET_CTRLMODE_CENTRAL"); break; case CNM_Decentral: szControlMode = LoadResStr("IDS_NET_CTRLMODE_DECENTRAL"); break; default: szControlMode = LoadResStr("IDS_NET_CTRLMODE_NONE"); break; } } pDropdownList->SetText(szControlMode); } // ----------- C4GameOptionsList::OptionControlRate ---------------------------------------------------------------- C4GameOptionsList::OptionControlRate::OptionControlRate(class C4GameOptionsList *pForDlg) : C4GameOptionsList::OptionDropdown(pForDlg, LoadResStr("IDS_CTL_CONTROLRATE"), !::Control.isCtrlHost()) { SetToolTip(LoadResStr("IDS_CTL_CONTROLRATE_DESC")); } void C4GameOptionsList::OptionControlRate::DoDropdownFill(C4GUI::ComboBox_FillCB *pFiller) { for (int i = 1; i < std::min(C4MaxControlRate, 10); ++i) pFiller->AddEntry(FormatString("%d", i).getData(), i); } void C4GameOptionsList::OptionControlRate::DoDropdownSelChange(int32_t idNewSelection) { // adjust rate int32_t iNewRate = idNewSelection; if (!iNewRate || iNewRate == ::Control.ControlRate) return; ::Control.AdjustControlRate(iNewRate - ::Control.ControlRate); } void C4GameOptionsList::OptionControlRate::Update() { if (atoi(pDropdownList->GetText().getData()) == ::Control.ControlRate) return; pDropdownList->SetText(FormatString("%d", ::Control.ControlRate).getData()); } // ----------- C4GameOptionsList::OptionRuntimeJoin ---------------------------------------------------------------- C4GameOptionsList::OptionRuntimeJoin::OptionRuntimeJoin(class C4GameOptionsList *pForDlg) : C4GameOptionsList::OptionDropdown(pForDlg, LoadResStr("IDS_NET_RUNTIMEJOIN"), !::Network.isHost()) { SetToolTip(LoadResStr("IDS_NET_RUNTIMEJOIN_DESC")); } void C4GameOptionsList::OptionRuntimeJoin::DoDropdownFill(C4GUI::ComboBox_FillCB *pFiller) { pFiller->AddEntry(LoadResStr("IDS_NET_RUNTIMEJOINBARRED"), 0); pFiller->AddEntry(LoadResStr("IDS_NET_RUNTIMEJOINFREE"), 1); } void C4GameOptionsList::OptionRuntimeJoin::DoDropdownSelChange(int32_t idNewSelection) { // adjust mode bool fAllowed = !!idNewSelection; Config.Network.NoRuntimeJoin = !fAllowed; if (Game.IsRunning) ::Network.AllowJoin(fAllowed); } void C4GameOptionsList::OptionRuntimeJoin::Update() { const char *szText; if (Config.Network.NoRuntimeJoin) szText = LoadResStr("IDS_NET_RUNTIMEJOINBARRED"); else szText = LoadResStr("IDS_NET_RUNTIMEJOINFREE"); pDropdownList->SetText(szText); } // ----------- C4GameOptionsList::OptionTeamDist ---------------------------------------------------------------- C4GameOptionsList::OptionTeamDist::OptionTeamDist(class C4GameOptionsList *pForDlg) : C4GameOptionsList::OptionDropdown(pForDlg, LoadResStr("IDS_MSG_TEAMDIST"), !::Control.isCtrlHost()) { SetToolTip(LoadResStr("IDS_MSG_TEAMDIST_DESC")); } void C4GameOptionsList::OptionTeamDist::DoDropdownFill(C4GUI::ComboBox_FillCB *pFiller) { Game.Teams.FillTeamDistOptions(pFiller); } void C4GameOptionsList::OptionTeamDist::DoDropdownSelChange(int32_t idNewSelection) { // adjust team distribution Game.Teams.SendSetTeamDist(C4TeamList::TeamDist(idNewSelection)); } void C4GameOptionsList::OptionTeamDist::Update() { StdStrBuf sOption; sOption.Take(Game.Teams.GetTeamDistString()); pDropdownList->SetText(sOption.getData()); } // ----------- C4GameOptionsList::OptionTeamColors ---------------------------------------------------------------- C4GameOptionsList::OptionTeamColors::OptionTeamColors(class C4GameOptionsList *pForDlg) : C4GameOptionsList::OptionDropdown(pForDlg, LoadResStr("IDS_MSG_TEAMCOLORS"), !::Control.isCtrlHost()) { SetToolTip(LoadResStr("IDS_MSG_TEAMCOLORS_DESC")); } void C4GameOptionsList::OptionTeamColors::DoDropdownFill(C4GUI::ComboBox_FillCB *pFiller) { pFiller->AddEntry(LoadResStr("IDS_MSG_ENABLED"), 1); pFiller->AddEntry(LoadResStr("IDS_MSG_DISABLED"), 0); } void C4GameOptionsList::OptionTeamColors::DoDropdownSelChange(int32_t idNewSelection) { bool fEnabled = !!idNewSelection; Game.Teams.SendSetTeamColors(fEnabled); } void C4GameOptionsList::OptionTeamColors::Update() { pDropdownList->SetText(Game.Teams.IsTeamColors() ? LoadResStr("IDS_MSG_ENABLED") : LoadResStr("IDS_MSG_DISABLED")); } // ----------- C4GameOptionsList ----------------------------------------------------------------------- C4GameOptionsList::C4GameOptionsList(const C4Rect &rcBounds, bool fActive, C4GameOptionsListSource source, C4ScenarioParameterDefs *param_defs, C4ScenarioParameters *params) : C4GUI::ListBox(rcBounds), source(source), param_defs(param_defs), params(params) { // default parameter defs if (!IsPreGame()) { if (!this->param_defs) this->param_defs = &::Game.ScenarioParameterDefs; if (!this->params) this->params = &::Game.Parameters.ScenarioParameters; } // initial option fill InitOptions(); if (fActive) Activate(); } void C4GameOptionsList::InitOptions() { // create options for custom scenario parameters if (param_defs) { size_t idx = 0; const C4ScenarioParameterDef *def; while ((def = param_defs->GetParameterDefByIndex(idx++))) if (!def->IsAchievement()) // achievements are displayed in scenario selection. no need to repeat them here new OptionScenarioParameter(this, def); } // create lobby and runtime option selection components if (!IsPreGame()) { new OptionControlMode(this); new OptionControlRate(this); if (::Network.isHost()) new OptionRuntimeJoin(this); if (!IsRuntime()) { if (Game.Teams.HasTeamDistOptions()) new OptionTeamDist(this); if (Game.Teams.IsMultiTeams()) new OptionTeamColors(this); } } } void C4GameOptionsList::ClearOptions() { C4GUI::Element *pFirst; while ((pFirst = GetFirst())) delete pFirst; } void C4GameOptionsList::Update() { // update all option items for (Option *pItem = static_cast