1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2005-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 // Custom game options and configuration dialog
17 
18 #include "C4Include.h"
19 #include "gui/C4GameOptions.h"
20 
21 #include "control/C4GameControl.h"
22 #include "game/C4Application.h"
23 #include "graphics/C4GraphicsResource.h"
24 #include "gui/C4GameLobby.h"
25 #include "gui/C4Startup.h"
26 
27 // ----------- C4GameOptionsList::Option ----------------------------------------------------------------
28 
Option(C4GameOptionsList * pForDlg)29 C4GameOptionsList::Option::Option(C4GameOptionsList *pForDlg) :
30 	BaseClass(C4Rect(0, 0, 0, 0)), pForDlg(pForDlg), pPrimarySubcomponent(nullptr)
31 {}
32 
InitOption(C4GameOptionsList * pForDlg)33 void C4GameOptionsList::Option::InitOption(C4GameOptionsList *pForDlg)
34 {
35 	// post-call after initialization: Adds to list box and does initial update
36 	// add to listbox (will eventually get moved)
37 	pForDlg->AddElement(this);
38 	// first-time update
39 	Update();
40 }
41 
42 
43 
44 // ----------- C4GameOptionsList::OptionDropdown ----------------------------------------------------------------
45 
OptionDropdown(class C4GameOptionsList * pForDlg,const char * szCaption,bool fReadOnly)46 C4GameOptionsList::OptionDropdown::OptionDropdown(class C4GameOptionsList *pForDlg, const char *szCaption, bool fReadOnly)
47 		: Option(pForDlg), fReadOnly(fReadOnly)
48 {
49 	bool fIsPreGame = pForDlg->IsPreGame();
50 	CStdFont &rUseFont = fIsPreGame ? C4Startup::Get()->Graphics.BookFont : ::GraphicsResource.TextFont;
51 	uint32_t dwFontClr = fIsPreGame ? C4StartupFontClr : 0xffffffff;
52 	// get size of caption label
53 	bool fTabular = pForDlg->IsTabular();
54 	int32_t iCaptWidth, iCaptHeight;
55 	if (fTabular)
56 	{
57 		// tabular layout: Caption label width by largest caption on runtime; fixed 1/3rd on startup
58 		rUseFont.GetTextExtent(LoadResStr("IDS_NET_RUNTIMEJOIN"), iCaptWidth, iCaptHeight, true);
59 		if (pForDlg->IsPreGame())
60 		{
61 			iCaptWidth = pForDlg->GetItemWidth() * 1 / 3;
62 		}
63 		else
64 		{
65 			iCaptWidth = iCaptWidth * 5 / 4;
66 		}
67 	}
68 	else
69 	{
70 		rUseFont.GetTextExtent(szCaption, iCaptWidth, iCaptHeight, true);
71 	}
72 	// calc total height for component
73 	int iHorizontalMargin = 1;
74 	int iVerticalMargin = 1;
75 	int iComboMargin = 5;
76 	int iSelComboHgt = C4GUI::ComboBox::GetDefaultHeight();
77 	SetBounds(C4Rect(0, 0, pForDlg->GetItemWidth(), (!fTabular) * (iCaptHeight + iVerticalMargin*2) + iVerticalMargin*2 + iSelComboHgt));
78 	C4GUI::ComponentAligner ca(GetContainedClientRect(), iHorizontalMargin, iVerticalMargin);
79 	// create subcomponents
80 	AddElement(pCaption = new C4GUI::Label(FormatString("%s:", szCaption).getData(), fTabular ? ca.GetFromLeft(iCaptWidth, iCaptHeight) : ca.GetFromTop(iCaptHeight), ALeft, dwFontClr, &rUseFont, false, false));
81 	ca.ExpandLeft(-iComboMargin);
82 	AddElement(pPrimarySubcomponent = pDropdownList = new C4GUI::ComboBox(ca.GetAll()));
83 	pDropdownList->SetReadOnly(fReadOnly);
84 	pDropdownList->SetComboCB(new C4GUI::ComboBox_FillCallback<C4GameOptionsList::OptionDropdown>(this, &C4GameOptionsList::OptionDropdown::OnDropdownFill, &C4GameOptionsList::OptionDropdown::OnDropdownSelChange));
85 	if (fIsPreGame)
86 	{
87 		pDropdownList->SetColors(C4StartupFontClr, C4StartupEditBGColor, C4StartupEditBorderColor);
88 		pDropdownList->SetFont(&rUseFont);
89 		pDropdownList->SetDecoration(&(C4Startup::Get()->Graphics.fctContext));
90 	}
91 	// final init
92 	InitOption(pForDlg);
93 }
94 
95 
96 
97 // ----------- C4GameOptionsList::OptionScenarioParameter----------------------------------------------------------------
98 
OptionScenarioParameter(class C4GameOptionsList * pForDlg,const class C4ScenarioParameterDef * parameter_def)99 C4GameOptionsList::OptionScenarioParameter::OptionScenarioParameter(class C4GameOptionsList *pForDlg, const class C4ScenarioParameterDef *parameter_def)
100 	: C4GameOptionsList::OptionDropdown(pForDlg, parameter_def->GetName(), !pForDlg->IsPreGame() && (!::Control.isCtrlHost() || ::Game.C4S.Head.SaveGame)), ParameterDef(parameter_def), LastValue(0), LastValueValid(false)
101 {
102 	SetToolTip(parameter_def->GetDescription());
103 }
104 
DoDropdownFill(C4GUI::ComboBox_FillCB * pFiller)105 void C4GameOptionsList::OptionScenarioParameter::DoDropdownFill(C4GUI::ComboBox_FillCB *pFiller)
106 {
107 	// Fill dropdown menuy with known possible options for this parameter
108 	size_t idx=0; const C4ScenarioParameterDef::Option *option;
109 	while ((option = ParameterDef->GetOptionByIndex(idx++)))
110 	{
111 		pFiller->AddEntry(option->Name.getData(), option->Value);
112 	}
113 }
114 
DoDropdownSelChange(int32_t idNewSelection)115 void C4GameOptionsList::OptionScenarioParameter::DoDropdownSelChange(int32_t idNewSelection)
116 {
117 	// runtime change needs to be synchronized
118 	if (!pForDlg->IsPreGame())
119 	{
120 		// change possible?
121 		if (!::Control.isCtrlHost()) return;
122 		// Then initiate an update of the parameters on all clients
123 		C4GameLobby::C4PacketSetScenarioParameter pck(ParameterDef->GetID(), idNewSelection);
124 		::Network.Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_SetScenarioParameter, pck));
125 	}
126 	// also process on host (and standalone pre-game)
127 	pForDlg->GetParameters()->SetValue(ParameterDef->GetID(), idNewSelection, false);
128 	if (pForDlg->IsPreGame()) Update();
129 }
130 
Update()131 void C4GameOptionsList::OptionScenarioParameter::Update()
132 {
133 	int32_t val=0;
134 	// display forced league value?
135 	bool fLeagueReadOnly = false;
136 	if (::Config.Network.LeagueServerSignUp && !fReadOnly && !pForDlg->IsPreGameSingle()) val = ParameterDef->GetLeagueValue();
137 	if (val)
138 		fLeagueReadOnly = true;
139 	else
140 		val = pForDlg->GetParameters()->GetValueByID(ParameterDef->GetID(), ParameterDef->GetDefault());
141 	if (!fReadOnly) pDropdownList->SetReadOnly(fLeagueReadOnly);
142 	// update data to currently set option
143 	if (LastValueValid && val == LastValue) return;
144 	const C4ScenarioParameterDef::Option *option = ParameterDef->GetOptionByValue(val);
145 	if (option)
146 		pDropdownList->SetText(option->Name.getData());
147 	else
148 		pDropdownList->SetText(FormatString("%d", (int)val).getData());
149 	LastValueValid = true;
150 	LastValue = val;
151 }
152 
153 
154 
155 // ----------- C4GameOptionsList::OptionControlMode ----------------------------------------------------------------
156 
157 // Unfortunately, the control mode cannot be changed in the lobby
OptionControlMode(class C4GameOptionsList * pForDlg)158 C4GameOptionsList::OptionControlMode::OptionControlMode(class C4GameOptionsList *pForDlg)
159 		: C4GameOptionsList::OptionDropdown(pForDlg, LoadResStr("IDS_TEXT_CONTROLMODE"), !::Control.isCtrlHost() || !::Control.isNetwork() || !::Control.Network.IsEnabled())
160 {
161 	SetToolTip(LoadResStr("IDS_DESC_CHANGESTHEWAYCONTROLDATAI"));
162 }
163 
DoDropdownFill(C4GUI::ComboBox_FillCB * pFiller)164 void C4GameOptionsList::OptionControlMode::DoDropdownFill(C4GUI::ComboBox_FillCB *pFiller)
165 {
166 	// change possible?
167 	if (!::Control.isNetwork() || !::Control.Network.IsEnabled() || !::Control.isCtrlHost()) return;
168 	// add possible modes
169 	pFiller->AddEntry(LoadResStr("IDS_NET_CTRLMODE_CENTRAL"), CNM_Central);
170 	pFiller->AddEntry(LoadResStr("IDS_NET_CTRLMODE_DECENTRAL"), CNM_Decentral);
171 }
172 
DoDropdownSelChange(int32_t idNewSelection)173 void C4GameOptionsList::OptionControlMode::DoDropdownSelChange(int32_t idNewSelection)
174 {
175 	// change possible?
176 	if (!::Control.isNetwork() || !::Control.Network.IsEnabled() || !::Control.isCtrlHost()) return;
177 	// perform it
178 	::Network.SetCtrlMode(idNewSelection);
179 	// update for clients done by packet; host needs to set it manually
180 	Update();
181 }
182 
Update()183 void C4GameOptionsList::OptionControlMode::Update()
184 {
185 	const char *szControlMode;
186 	if (!::Control.isNetwork() || !::Control.Network.IsEnabled())
187 		szControlMode = LoadResStr("IDS_NET_NONET");
188 	else
189 	{
190 		switch (::Network.Status.getCtrlMode())
191 		{
192 		case CNM_Central: szControlMode = LoadResStr("IDS_NET_CTRLMODE_CENTRAL"); break;
193 		case CNM_Decentral: szControlMode = LoadResStr("IDS_NET_CTRLMODE_DECENTRAL"); break;
194 		default: szControlMode = LoadResStr("IDS_NET_CTRLMODE_NONE"); break;
195 		}
196 	}
197 	pDropdownList->SetText(szControlMode);
198 }
199 
200 
201 // ----------- C4GameOptionsList::OptionControlRate ----------------------------------------------------------------
202 
OptionControlRate(class C4GameOptionsList * pForDlg)203 C4GameOptionsList::OptionControlRate::OptionControlRate(class C4GameOptionsList *pForDlg)
204 		: C4GameOptionsList::OptionDropdown(pForDlg, LoadResStr("IDS_CTL_CONTROLRATE"), !::Control.isCtrlHost())
205 {
206 	SetToolTip(LoadResStr("IDS_CTL_CONTROLRATE_DESC"));
207 }
208 
DoDropdownFill(C4GUI::ComboBox_FillCB * pFiller)209 void C4GameOptionsList::OptionControlRate::DoDropdownFill(C4GUI::ComboBox_FillCB *pFiller)
210 {
211 	for (int i = 1; i < std::min(C4MaxControlRate, 10); ++i)
212 		pFiller->AddEntry(FormatString("%d", i).getData(), i);
213 }
214 
DoDropdownSelChange(int32_t idNewSelection)215 void C4GameOptionsList::OptionControlRate::DoDropdownSelChange(int32_t idNewSelection)
216 {
217 	// adjust rate
218 	int32_t iNewRate = idNewSelection;
219 	if (!iNewRate || iNewRate == ::Control.ControlRate) return;
220 	::Control.AdjustControlRate(iNewRate - ::Control.ControlRate);
221 }
222 
Update()223 void C4GameOptionsList::OptionControlRate::Update()
224 {
225 	if (atoi(pDropdownList->GetText().getData()) == ::Control.ControlRate) return;
226 	pDropdownList->SetText(FormatString("%d", ::Control.ControlRate).getData());
227 }
228 
229 
230 // ----------- C4GameOptionsList::OptionRuntimeJoin ----------------------------------------------------------------
231 
OptionRuntimeJoin(class C4GameOptionsList * pForDlg)232 C4GameOptionsList::OptionRuntimeJoin::OptionRuntimeJoin(class C4GameOptionsList *pForDlg)
233 		: C4GameOptionsList::OptionDropdown(pForDlg, LoadResStr("IDS_NET_RUNTIMEJOIN"), !::Network.isHost())
234 {
235 	SetToolTip(LoadResStr("IDS_NET_RUNTIMEJOIN_DESC"));
236 }
237 
DoDropdownFill(C4GUI::ComboBox_FillCB * pFiller)238 void C4GameOptionsList::OptionRuntimeJoin::DoDropdownFill(C4GUI::ComboBox_FillCB *pFiller)
239 {
240 	pFiller->AddEntry(LoadResStr("IDS_NET_RUNTIMEJOINBARRED"), 0);
241 	pFiller->AddEntry(LoadResStr("IDS_NET_RUNTIMEJOINFREE"), 1);
242 }
243 
DoDropdownSelChange(int32_t idNewSelection)244 void C4GameOptionsList::OptionRuntimeJoin::DoDropdownSelChange(int32_t idNewSelection)
245 {
246 	// adjust mode
247 	bool fAllowed = !!idNewSelection;
248 	Config.Network.NoRuntimeJoin = !fAllowed;
249 	if (Game.IsRunning) ::Network.AllowJoin(fAllowed);
250 }
251 
Update()252 void C4GameOptionsList::OptionRuntimeJoin::Update()
253 {
254 	const char *szText;
255 	if (Config.Network.NoRuntimeJoin)
256 		szText = LoadResStr("IDS_NET_RUNTIMEJOINBARRED");
257 	else
258 		szText = LoadResStr("IDS_NET_RUNTIMEJOINFREE");
259 	pDropdownList->SetText(szText);
260 }
261 
262 
263 // ----------- C4GameOptionsList::OptionTeamDist ----------------------------------------------------------------
264 
OptionTeamDist(class C4GameOptionsList * pForDlg)265 C4GameOptionsList::OptionTeamDist::OptionTeamDist(class C4GameOptionsList *pForDlg)
266 		: C4GameOptionsList::OptionDropdown(pForDlg, LoadResStr("IDS_MSG_TEAMDIST"), !::Control.isCtrlHost())
267 {
268 	SetToolTip(LoadResStr("IDS_MSG_TEAMDIST_DESC"));
269 }
270 
DoDropdownFill(C4GUI::ComboBox_FillCB * pFiller)271 void C4GameOptionsList::OptionTeamDist::DoDropdownFill(C4GUI::ComboBox_FillCB *pFiller)
272 {
273 	Game.Teams.FillTeamDistOptions(pFiller);
274 }
275 
DoDropdownSelChange(int32_t idNewSelection)276 void C4GameOptionsList::OptionTeamDist::DoDropdownSelChange(int32_t idNewSelection)
277 {
278 	// adjust team distribution
279 	Game.Teams.SendSetTeamDist(C4TeamList::TeamDist(idNewSelection));
280 }
281 
Update()282 void C4GameOptionsList::OptionTeamDist::Update()
283 {
284 	StdStrBuf sOption; sOption.Take(Game.Teams.GetTeamDistString());
285 	pDropdownList->SetText(sOption.getData());
286 }
287 
288 
289 // ----------- C4GameOptionsList::OptionTeamColors ----------------------------------------------------------------
290 
OptionTeamColors(class C4GameOptionsList * pForDlg)291 C4GameOptionsList::OptionTeamColors::OptionTeamColors(class C4GameOptionsList *pForDlg)
292 		: C4GameOptionsList::OptionDropdown(pForDlg, LoadResStr("IDS_MSG_TEAMCOLORS"), !::Control.isCtrlHost())
293 {
294 	SetToolTip(LoadResStr("IDS_MSG_TEAMCOLORS_DESC"));
295 }
296 
DoDropdownFill(C4GUI::ComboBox_FillCB * pFiller)297 void C4GameOptionsList::OptionTeamColors::DoDropdownFill(C4GUI::ComboBox_FillCB *pFiller)
298 {
299 	pFiller->AddEntry(LoadResStr("IDS_MSG_ENABLED"), 1);
300 	pFiller->AddEntry(LoadResStr("IDS_MSG_DISABLED"), 0);
301 }
302 
DoDropdownSelChange(int32_t idNewSelection)303 void C4GameOptionsList::OptionTeamColors::DoDropdownSelChange(int32_t idNewSelection)
304 {
305 	bool fEnabled = !!idNewSelection;
306 	Game.Teams.SendSetTeamColors(fEnabled);
307 }
308 
Update()309 void C4GameOptionsList::OptionTeamColors::Update()
310 {
311 	pDropdownList->SetText(Game.Teams.IsTeamColors() ? LoadResStr("IDS_MSG_ENABLED") : LoadResStr("IDS_MSG_DISABLED"));
312 }
313 
314 // ----------- C4GameOptionsList -----------------------------------------------------------------------
315 
C4GameOptionsList(const C4Rect & rcBounds,bool fActive,C4GameOptionsListSource source,C4ScenarioParameterDefs * param_defs,C4ScenarioParameters * params)316 C4GameOptionsList::C4GameOptionsList(const C4Rect &rcBounds, bool fActive, C4GameOptionsListSource source, C4ScenarioParameterDefs *param_defs, C4ScenarioParameters *params)
317 	: C4GUI::ListBox(rcBounds), source(source), param_defs(param_defs), params(params)
318 {
319 	// default parameter defs
320 	if (!IsPreGame())
321 	{
322 		if (!this->param_defs) this->param_defs = &::Game.ScenarioParameterDefs;
323 		if (!this->params) this->params = &::Game.Parameters.ScenarioParameters;
324 	}
325 	// initial option fill
326 	InitOptions();
327 	if (fActive) Activate();
328 }
329 
InitOptions()330 void C4GameOptionsList::InitOptions()
331 {
332 	// create options for custom scenario parameters
333 	if (param_defs)
334 	{
335 		size_t idx = 0; const C4ScenarioParameterDef *def;
336 		while ((def = param_defs->GetParameterDefByIndex(idx++)))
337 			if (!def->IsAchievement()) // achievements are displayed in scenario selection. no need to repeat them here
338 				new OptionScenarioParameter(this, def);
339 	}
340 	// create lobby and runtime option selection components
341 	if (!IsPreGame())
342 	{
343 		new OptionControlMode(this);
344 		new OptionControlRate(this);
345 		if (::Network.isHost()) new OptionRuntimeJoin(this);
346 		if (!IsRuntime())
347 		{
348 			if (Game.Teams.HasTeamDistOptions()) new OptionTeamDist(this);
349 			if (Game.Teams.IsMultiTeams()) new OptionTeamColors(this);
350 		}
351 	}
352 }
353 
ClearOptions()354 void C4GameOptionsList::ClearOptions()
355 {
356 	C4GUI::Element *pFirst;
357 	while ((pFirst = GetFirst())) delete pFirst;
358 }
359 
Update()360 void C4GameOptionsList::Update()
361 {
362 	// update all option items
363 	for (Option *pItem = static_cast<Option *>(pClientWindow->GetFirst()); pItem; pItem = pItem->GetNext())
364 		pItem->Update();
365 }
366 
Activate()367 void C4GameOptionsList::Activate()
368 {
369 	// register timer if necessary
370 	Application.Add(this);
371 	// force an update
372 	Update();
373 }
374 
Deactivate()375 void C4GameOptionsList::Deactivate()
376 {
377 	// release timer if set
378 	Application.Remove(this);
379 }
380 
SetParameters(C4ScenarioParameterDefs * param_defs,C4ScenarioParameters * params)381 void C4GameOptionsList::SetParameters(C4ScenarioParameterDefs *param_defs, C4ScenarioParameters *params)
382 {
383 	// update to new parameter set
384 	ClearOptions();
385 	this->param_defs = param_defs;
386 	this->params = params;
387 	InitOptions();
388 }
389 
390