1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2005, Sven2
6 * Copyright (c) 2017-2021, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18// Custom game options and configuration dialog
19
20#include "C4GuiComboBox.h"
21#include "C4GuiResource.h"
22#include "C4Include.h"
23#include "C4GameOptions.h"
24#include "C4Game.h"
25
26// C4GameOptionsList::Option
27
28C4GameOptionsList::Option::Option(C4GameOptionsList *pForDlg)
29 : BaseClass(C4Rect(0, 0, 0, 0)), pPrimarySubcomponent(nullptr) {}
30
31void C4GameOptionsList::Option::InitOption(C4GameOptionsList *pForDlg)
32{
33 // post-call after initialization: Adds to list box and does initial update
34 // add to listbox (will eventually get moved)
35 pForDlg->AddElement(pChild: this);
36 // first-time update
37 Update();
38}
39
40// C4GameOptionsList::OptionDropdown
41
42C4GameOptionsList::OptionDropdown::OptionDropdown(C4GameOptionsList *pForDlg, const char *szCaption, bool fReadOnly)
43 : Option(pForDlg)
44{
45 CStdFont &rUseFont = C4GUI::GetRes()->TextFont;
46 // get size of caption label
47 bool fTabular = pForDlg->IsTabular();
48 int32_t iCaptWidth, iCaptHeight;
49 if (fTabular)
50 {
51 // tabular layout: Caption label width by largest caption
52 rUseFont.GetTextExtent(szText: LoadResStr(id: C4ResStrTableKey::IDS_NET_RUNTIMEJOIN), rsx&: iCaptWidth, rsy&: iCaptHeight, fCheckMarkup: true);
53 iCaptWidth = iCaptWidth * 5 / 4;
54 }
55 else
56 {
57 rUseFont.GetTextExtent(szText: szCaption, rsx&: iCaptWidth, rsy&: iCaptHeight, fCheckMarkup: true);
58 }
59 // calc total height for component
60 int iHorizontalMargin = 1;
61 int iVerticalMargin = 1;
62 int iComboMargin = 5;
63 int iSelComboHgt = C4GUI::ComboBox::GetDefaultHeight();
64 SetBounds(C4Rect(0, 0, pForDlg->GetItemWidth(), (!fTabular) * (iCaptHeight + iVerticalMargin * 2) + iVerticalMargin * 2 + iSelComboHgt));
65 C4GUI::ComponentAligner ca(GetContainedClientRect(), iHorizontalMargin, iVerticalMargin);
66 // create subcomponents
67 AddElement(pChild: pCaption = new C4GUI::Label(std::format(fmt: "{}:", args&: szCaption).c_str(), fTabular ? ca.GetFromLeft(iWdt: iCaptWidth, iHgt: iCaptHeight) : ca.GetFromTop(iHgt: iCaptHeight), ALeft));
68 ca.ExpandLeft(iByWdt: -iComboMargin);
69 AddElement(pChild: pPrimarySubcomponent = pDropdownList = new C4GUI::ComboBox(ca.GetAll()));
70 pDropdownList->SetReadOnly(fReadOnly);
71 pDropdownList->SetComboCB(new C4GUI::ComboBox_FillCallback<C4GameOptionsList::OptionDropdown>(this, &C4GameOptionsList::OptionDropdown::OnDropdownFill, &C4GameOptionsList::OptionDropdown::OnDropdownSelChange));
72 // final init
73 InitOption(pForDlg);
74}
75
76// C4GameOptionsList::OptionControlMode
77
78// Unfortunately, the control mode cannot be changed in the lobby
79C4GameOptionsList::OptionControlMode::OptionControlMode(C4GameOptionsList *pForDlg)
80 : C4GameOptionsList::OptionDropdown(pForDlg, LoadResStr(id: C4ResStrTableKey::IDS_TEXT_CONTROLMODE), !Game.Control.isCtrlHost() || !Game.Control.isNetwork() || !Game.Control.Network.IsEnabled() || !pForDlg->IsRuntime())
81{
82 SetToolTip(LoadResStr(id: C4ResStrTableKey::IDS_DESC_CHANGESTHEWAYCONTROLDATAI));
83}
84
85void C4GameOptionsList::OptionControlMode::DoDropdownFill(C4GUI::ComboBox_FillCB *pFiller)
86{
87 // change possible?
88 if (!Game.Control.isNetwork() || !Game.Control.Network.IsEnabled() || !Game.Control.isCtrlHost()) return;
89 // add possible modes
90 pFiller->AddEntry(szText: LoadResStr(id: C4ResStrTableKey::IDS_NET_CTRLMODE_CENTRAL), id: CNM_Central);
91 pFiller->AddEntry(szText: LoadResStr(id: C4ResStrTableKey::IDS_NET_CTRLMODE_DECENTRAL), id: CNM_Decentral);
92 if (!Game.Parameters.isLeague())
93 pFiller->AddEntry(szText: "[!]Asynchroner Netzwerkmodus (experimentell!)", id: CNM_Async);
94}
95
96void C4GameOptionsList::OptionControlMode::DoDropdownSelChange(int32_t idNewSelection)
97{
98 // change possible?
99 if (!Game.Control.isNetwork() || !Game.Control.Network.IsEnabled() || !Game.Control.isCtrlHost()) return;
100 // perform it
101 Game.Network.SetCtrlMode(idNewSelection);
102 // update done in parent call
103}
104
105void C4GameOptionsList::OptionControlMode::Update()
106{
107 const char *szControlMode;
108 if (!Game.Control.isNetwork() || !Game.Control.Network.IsEnabled())
109 szControlMode = LoadResStr(id: C4ResStrTableKey::IDS_NET_NONET);
110 else
111 {
112 switch (Game.Control.Network.GetCtrlMode())
113 {
114 case CNM_Central: szControlMode = LoadResStr(id: C4ResStrTableKey::IDS_NET_CTRLMODE_CENTRAL); break;
115 case CNM_Decentral: szControlMode = LoadResStr(id: C4ResStrTableKey::IDS_NET_CTRLMODE_DECENTRAL); break;
116 case CNM_Async: szControlMode = "[!]Asynchroner Netzwerkmodus (experimentell!)"; break;
117 default: szControlMode = LoadResStr(id: C4ResStrTableKey::IDS_NET_CTRLMODE_NONE); break;
118 }
119 }
120 pDropdownList->SetText(szControlMode);
121}
122
123// C4GameOptionsList::OptionControlRate
124
125C4GameOptionsList::OptionControlRate::OptionControlRate(C4GameOptionsList *pForDlg)
126 : C4GameOptionsList::OptionDropdown(pForDlg, LoadResStr(id: C4ResStrTableKey::IDS_CTL_CONTROLRATE), !Game.Control.isCtrlHost())
127{
128 SetToolTip(LoadResStr(id: C4ResStrTableKey::IDS_CTL_CONTROLRATE_DESC));
129}
130
131void C4GameOptionsList::OptionControlRate::DoDropdownFill(C4GUI::ComboBox_FillCB *pFiller)
132{
133 for (int i = 1; i < (std::min)(a: C4MaxControlRate, b: 10); ++i)
134 pFiller->AddEntry(szText: std::format(fmt: "{}", args&: i).c_str(), id: i);
135}
136
137void C4GameOptionsList::OptionControlRate::DoDropdownSelChange(int32_t idNewSelection)
138{
139 // adjust rate
140 int32_t iNewRate = idNewSelection;
141 if (!iNewRate || iNewRate == Game.Control.ControlRate) return;
142 Game.Control.AdjustControlRate(iBy: iNewRate - Game.Control.ControlRate);
143}
144
145void C4GameOptionsList::OptionControlRate::Update()
146{
147 if (atoi(nptr: pDropdownList->GetText().getData()) == Game.Control.ControlRate) return;
148 pDropdownList->SetText(std::format(fmt: "{}", args&: Game.Control.ControlRate).c_str());
149}
150
151// C4GameOptionsList::OptionRuntimeJoin
152
153C4GameOptionsList::OptionRuntimeJoin::OptionRuntimeJoin(C4GameOptionsList *pForDlg)
154 : C4GameOptionsList::OptionDropdown(pForDlg, LoadResStr(id: C4ResStrTableKey::IDS_NET_RUNTIMEJOIN), !Game.Network.isHost())
155{
156 SetToolTip(LoadResStr(id: C4ResStrTableKey::IDS_NET_RUNTIMEJOIN_DESC));
157}
158
159void C4GameOptionsList::OptionRuntimeJoin::DoDropdownFill(C4GUI::ComboBox_FillCB *pFiller)
160{
161 pFiller->AddEntry(szText: LoadResStr(id: C4ResStrTableKey::IDS_NET_RUNTIMEJOINBARRED), id: 0);
162 pFiller->AddEntry(szText: LoadResStr(id: C4ResStrTableKey::IDS_NET_RUNTIMEJOINFREE), id: 1);
163}
164
165void C4GameOptionsList::OptionRuntimeJoin::DoDropdownSelChange(int32_t idNewSelection)
166{
167 // adjust mode
168 bool fAllowed = !!idNewSelection;
169 Config.Network.NoRuntimeJoin = !fAllowed;
170 if (Game.IsRunning) Game.Network.AllowJoin(fAllow: fAllowed);
171}
172
173void C4GameOptionsList::OptionRuntimeJoin::Update()
174{
175 const char *szText;
176 if (Config.Network.NoRuntimeJoin)
177 szText = LoadResStr(id: C4ResStrTableKey::IDS_NET_RUNTIMEJOINBARRED);
178 else
179 szText = LoadResStr(id: C4ResStrTableKey::IDS_NET_RUNTIMEJOINFREE);
180 pDropdownList->SetText(szText);
181}
182
183// C4GameOptionsList::OptionTeamDist
184
185C4GameOptionsList::OptionTeamDist::OptionTeamDist(C4GameOptionsList *pForDlg)
186 : C4GameOptionsList::OptionDropdown(pForDlg, LoadResStr(id: C4ResStrTableKey::IDS_MSG_TEAMDIST), !Game.Control.isCtrlHost()), optionsList{pForDlg}
187{
188 SetToolTip(LoadResStr(id: C4ResStrTableKey::IDS_MSG_TEAMDIST_DESC));
189}
190
191void C4GameOptionsList::OptionTeamDist::DoDropdownFill(C4GUI::ComboBox_FillCB *pFiller)
192{
193 Game.Teams.FillTeamDistOptions(pFiller);
194}
195
196void C4GameOptionsList::OptionTeamDist::DoDropdownSelChange(int32_t idNewSelection)
197{
198 // adjust team distribution
199 Game.Teams.SendSetTeamDist(eNewDist: C4TeamList::TeamDist(idNewSelection));
200 optionsList->Update();
201}
202
203void C4GameOptionsList::OptionTeamDist::Update()
204{
205 pDropdownList->SetText(Game.Teams.GetTeamDistString().c_str());
206}
207
208// C4GameOptionsList::OptionTeamColors
209
210C4GameOptionsList::OptionTeamColors::OptionTeamColors(C4GameOptionsList *pForDlg)
211 : C4GameOptionsList::OptionDropdown(pForDlg, LoadResStr(id: C4ResStrTableKey::IDS_MSG_TEAMCOLORS), !Game.Control.isCtrlHost())
212{
213 SetToolTip(LoadResStr(id: C4ResStrTableKey::IDS_MSG_TEAMCOLORS_DESC));
214}
215
216void C4GameOptionsList::OptionTeamColors::DoDropdownFill(C4GUI::ComboBox_FillCB *pFiller)
217{
218 pFiller->AddEntry(szText: LoadResStr(id: C4ResStrTableKey::IDS_MSG_ENABLED), id: 1);
219 pFiller->AddEntry(szText: LoadResStr(id: C4ResStrTableKey::IDS_MSG_DISABLED), id: 0);
220}
221
222void C4GameOptionsList::OptionTeamColors::DoDropdownSelChange(int32_t idNewSelection)
223{
224 bool fEnabled = !!idNewSelection;
225 Game.Teams.SendSetTeamColors(fEnabled);
226}
227
228void C4GameOptionsList::OptionTeamColors::Update()
229{
230 pDropdownList->SetText(Game.Teams.IsTeamColors() ? LoadResStr(id: C4ResStrTableKey::IDS_MSG_ENABLED) : LoadResStr(id: C4ResStrTableKey::IDS_MSG_DISABLED));
231}
232
233C4GameOptionsList::OptionRandomTeamCount::OptionRandomTeamCount(C4GameOptionsList *forDlg)
234 : OptionDropdown(forDlg, LoadResStr(id: C4ResStrTableKey::IDS_MSG_RANDOMTEAMCOUNT), !Game.Network.isHost())
235{
236 SetToolTip(LoadResStr(id: C4ResStrTableKey::IDS_MSG_RANDOMTEAMCOUNT_DESC));
237}
238
239void C4GameOptionsList::OptionRandomTeamCount::DoDropdownFill(C4GUI::ComboBox_FillCB *filler)
240{
241 filler->AddEntry(szText: LoadResStr(id: C4ResStrTableKey::IDS_MSG_TEAMCOUNT_AUTO), id: 0, desc: LoadResStr(id: C4ResStrTableKey::IDS_MSG_TEAMCOUNT_AUTO_DESC));
242 for (int32_t i = 2, max = Game.Teams.IsAutoGenerateTeams() ? Game.PlayerInfos.GetActivePlayerCount(fCountInvisible: true) : Game.Teams.GetTeamCount(); i <= max; ++i)
243 {
244 filler->AddEntry(szText: std::to_string(val: i).c_str(), id: i);
245 }
246}
247
248void C4GameOptionsList::OptionRandomTeamCount::DoDropdownSelChange(int32_t newSelection)
249{
250 Game.Teams.SetRandomTeamCount(newSelection);
251}
252
253void C4GameOptionsList::OptionRandomTeamCount::Update()
254{
255 const auto count = Game.Teams.GetRandomTeamCount();
256 pDropdownList->SetText(count > 1 ? std::to_string(val: count).c_str() : LoadResStr(id: C4ResStrTableKey::IDS_MSG_TEAMCOUNT_AUTO));
257}
258
259// C4GameOptionsList
260
261C4GameOptionsList::C4GameOptionsList(const C4Rect &rcBounds, bool fActive, bool fRuntime)
262 : C4GUI::ListBox(rcBounds), pSec1Timer(nullptr), fRuntime(fRuntime)
263{
264 // initial option fill
265 InitOptions();
266 if (fActive) Activate();
267}
268
269void C4GameOptionsList::InitOptions()
270{
271 // creates option selection components
272 new OptionControlMode(this);
273 new OptionControlRate(this);
274 if (Game.Network.isHost()) new OptionRuntimeJoin(this);
275 if (!IsRuntime())
276 {
277 if (Game.Teams.HasTeamDistOptions()) new OptionTeamDist(this);
278 if (Game.Teams.IsMultiTeams()) new OptionTeamColors(this);
279 }
280}
281
282void C4GameOptionsList::Update()
283{
284 const auto haveRandomTeamCount = !IsRuntime() && Game.Teams.HasTeamDistOptions() && Game.Network.isHost() && Game.Teams.IsRandomTeam();
285 if (haveRandomTeamCount != (randomTeamCount != nullptr))
286 {
287 if (!randomTeamCount)
288 {
289 randomTeamCount = new OptionRandomTeamCount{this};
290 }
291 else
292 {
293 delete randomTeamCount;
294 randomTeamCount = nullptr;
295 }
296 }
297 // update all option items
298 for (Option *pItem = static_cast<Option *>(pClientWindow->GetFirst()); pItem; pItem = pItem->GetNext())
299 pItem->Update();
300}
301
302void C4GameOptionsList::Activate()
303{
304 // create timer if necessary
305 if (!pSec1Timer) pSec1Timer = new C4Sec1TimerCallback<C4GameOptionsList>(this);
306 // force an update
307 Update();
308}
309
310void C4GameOptionsList::Deactivate()
311{
312 // release timer if set
313 if (pSec1Timer)
314 {
315 pSec1Timer->Release();
316 pSec1Timer = nullptr;
317 }
318}
319