1 /* Copyright (C) 2017 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "precompiled.h"
19 
20 #include "Player.h"
21 
22 #include "AtlasObject/AtlasObject.h"
23 #include "CustomControls/ColorDialog/ColorDialog.h"
24 #include "ScenarioEditor/ScenarioEditor.h"
25 
26 #include "wx/choicebk.h"
27 
28 enum
29 {
30 	ID_NumPlayers,
31 	ID_PlayerFood,
32 	ID_PlayerWood,
33 	ID_PlayerMetal,
34 	ID_PlayerStone,
35 	ID_PlayerPop,
36 	ID_PlayerColor,
37 
38 	ID_DefaultName,
39 	ID_DefaultCiv,
40 	ID_DefaultColor,
41 	ID_DefaultAI,
42 	ID_DefaultFood,
43 	ID_DefaultWood,
44 	ID_DefaultMetal,
45 	ID_DefaultStone,
46 	ID_DefaultPop,
47 	ID_DefaultTeam,
48 
49 	ID_CameraSet,
50 	ID_CameraView,
51 	ID_CameraClear
52 };
53 
54 // TODO: Some of these helper things should be moved out of this file
55 // and into shared locations
56 
57 // Helper function for adding tooltips
Tooltipped(wxWindow * window,const wxString & tip)58 static wxWindow* Tooltipped(wxWindow* window, const wxString& tip)
59 {
60 	window->SetToolTip(tip);
61 	return window;
62 }
63 
64 //////////////////////////////////////////////////////////////////////////
65 
66 class DefaultCheckbox : public wxCheckBox
67 {
68 public:
DefaultCheckbox(wxWindow * parent,wxWindowID id,wxWindow * control,bool initialValue=false)69 	DefaultCheckbox(wxWindow* parent, wxWindowID id, wxWindow* control, bool initialValue = false)
70 		: wxCheckBox(parent, id, wxEmptyString), m_Control(control)
71 	{
72 		SetValue(initialValue);
73 	}
74 
SetValue(bool value)75 	virtual void SetValue(bool value)
76 	{
77 		m_Control->Enable(value);
78 		wxCheckBox::SetValue(value);
79 	}
80 
OnChecked(wxCommandEvent & evt)81 	void OnChecked(wxCommandEvent& evt)
82 	{
83 		m_Control->Enable(evt.IsChecked());
84 
85 		evt.Skip();
86 	}
87 
88 private:
89 	wxWindow* m_Control;
90 
91 	DECLARE_EVENT_TABLE();
92 };
93 
94 BEGIN_EVENT_TABLE(DefaultCheckbox, wxCheckBox)
95 	EVT_CHECKBOX(wxID_ANY, DefaultCheckbox::OnChecked)
96 END_EVENT_TABLE();
97 
98 
99 class PlayerNotebookPage : public wxPanel
100 {
101 
102 public:
PlayerNotebookPage(wxWindow * parent,const wxString & name,size_t playerID)103 	PlayerNotebookPage(wxWindow* parent, const wxString& name, size_t playerID)
104 		: wxPanel(parent, wxID_ANY), m_Name(name), m_PlayerID(playerID)
105 	{
106 
107 		m_Controls.page = this;
108 
109 		Freeze();
110 
111 		wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
112 		SetSizer(sizer);
113 
114 		{
115 			/////////////////////////////////////////////////////////////////////////
116 			// Player Info
117 			wxStaticBoxSizer* playerInfoSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Player info"));
118 			wxFlexGridSizer* gridSizer = new wxFlexGridSizer(3, 5, 5);
119 			gridSizer->AddGrowableCol(2);
120 
121 			wxTextCtrl* nameCtrl = new wxTextCtrl(this, wxID_ANY);
122 			gridSizer->Add(new DefaultCheckbox(this, ID_DefaultName, nameCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
123 			gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Name")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
124 			gridSizer->Add(nameCtrl, wxSizerFlags(1).Expand().Align(wxALIGN_RIGHT));
125 			m_Controls.name = nameCtrl;
126 
127 			wxChoice* civChoice = new wxChoice(this, wxID_ANY);
128 			gridSizer->Add(new DefaultCheckbox(this, ID_DefaultCiv, civChoice), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
129 			gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Civilisation")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
130 			gridSizer->Add(civChoice, wxSizerFlags(1).Expand().Align(wxALIGN_RIGHT));
131 			m_Controls.civ = civChoice;
132 
133 			wxButton* colorButton = new wxButton(this, ID_PlayerColor);
134 			gridSizer->Add(new DefaultCheckbox(this, ID_DefaultColor, colorButton), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
135 			gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Color")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
136 			gridSizer->Add(Tooltipped(colorButton,
137 				_("Set player color")), wxSizerFlags(1).Expand().Align(wxALIGN_RIGHT));
138 			m_Controls.color = colorButton;
139 
140 			wxChoice* aiChoice = new wxChoice(this, wxID_ANY);
141 			gridSizer->Add(new DefaultCheckbox(this, ID_DefaultAI, aiChoice), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
142 			gridSizer->Add(new wxStaticText(this, wxID_ANY, _("AI")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
143 			gridSizer->Add(Tooltipped(aiChoice,
144 				_("Select AI")), wxSizerFlags(1).Expand().Align(wxALIGN_RIGHT));
145 			m_Controls.ai = aiChoice;
146 
147 			playerInfoSizer->Add(gridSizer, wxSizerFlags(1).Expand());
148 			sizer->Add(playerInfoSizer, wxSizerFlags().Expand().Border(wxTOP, 10));
149 		}
150 
151 		{
152 			/////////////////////////////////////////////////////////////////////////
153 			// Resources
154 			wxStaticBoxSizer* resourceSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Resources"));
155 			wxFlexGridSizer* gridSizer = new wxFlexGridSizer(3, 5, 5);
156 			gridSizer->AddGrowableCol(2);
157 
158 			wxSpinCtrl* foodCtrl = new wxSpinCtrl(this, ID_PlayerFood, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX);
159 			gridSizer->Add(new DefaultCheckbox(this, ID_DefaultFood, foodCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
160 			gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Food")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
161 			gridSizer->Add(Tooltipped(foodCtrl,
162 				_("Initial value of food resource")), wxSizerFlags().Expand());
163 			m_Controls.food = foodCtrl;
164 
165 			wxSpinCtrl* woodCtrl = new wxSpinCtrl(this, ID_PlayerWood, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX);
166 			gridSizer->Add(new DefaultCheckbox(this, ID_DefaultWood, woodCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
167 			gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Wood")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
168 			gridSizer->Add(Tooltipped(woodCtrl,
169 				_("Initial value of wood resource")), wxSizerFlags().Expand());
170 			m_Controls.wood = woodCtrl;
171 
172 			wxSpinCtrl* metalCtrl = new wxSpinCtrl(this, ID_PlayerMetal, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX);
173 			gridSizer->Add(new DefaultCheckbox(this, ID_DefaultMetal, metalCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
174 			gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Metal")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
175 			gridSizer->Add(Tooltipped(metalCtrl,
176 				_("Initial value of metal resource")), wxSizerFlags().Expand());
177 			m_Controls.metal = metalCtrl;
178 
179 			wxSpinCtrl* stoneCtrl = new wxSpinCtrl(this, ID_PlayerStone, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX);
180 			gridSizer->Add(new DefaultCheckbox(this, ID_DefaultStone, stoneCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
181 			gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Stone")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
182 			gridSizer->Add(Tooltipped(stoneCtrl,
183 				_("Initial value of stone resource")), wxSizerFlags().Expand());
184 			m_Controls.stone = stoneCtrl;
185 
186 			wxSpinCtrl* popCtrl = new wxSpinCtrl(this, ID_PlayerPop, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX);
187 			gridSizer->Add(new DefaultCheckbox(this, ID_DefaultPop, popCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
188 			gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Pop limit")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
189 			gridSizer->Add(Tooltipped(popCtrl,
190 				_("Population limit for this player")), wxSizerFlags().Expand());
191 			m_Controls.pop = popCtrl;
192 
193 			resourceSizer->Add(gridSizer, wxSizerFlags(1).Expand());
194 			sizer->Add(resourceSizer, wxSizerFlags().Expand().Border(wxTOP, 10));
195 		}
196 		{
197 			/////////////////////////////////////////////////////////////////////////
198 			// Diplomacy
199 			wxStaticBoxSizer* diplomacySizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Diplomacy"));
200 			wxBoxSizer* boxSizer = new wxBoxSizer(wxHORIZONTAL);
201 			wxChoice* teamCtrl = new wxChoice(this, wxID_ANY);
202 			boxSizer->Add(new DefaultCheckbox(this, ID_DefaultTeam, teamCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
203 			boxSizer->AddSpacer(5);
204 			boxSizer->Add(new wxStaticText(this, wxID_ANY, _("Team")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
205 			boxSizer->AddSpacer(5);
206 			teamCtrl->Append(_("None"));
207 			teamCtrl->Append(_T("1"));
208 			teamCtrl->Append(_T("2"));
209 			teamCtrl->Append(_T("3"));
210 			teamCtrl->Append(_T("4"));
211 			boxSizer->Add(teamCtrl);
212 			m_Controls.team = teamCtrl;
213 			diplomacySizer->Add(boxSizer, wxSizerFlags(1).Expand());
214 
215 			// TODO: possibly have advanced panel where each player's diplomacy can be set?
216 			// Advanced panel
217 			/*wxCollapsiblePane* advPane = new wxCollapsiblePane(this, wxID_ANY, _("Advanced"));
218 			wxWindow* pane = advPane->GetPane();
219 			diplomacySizer->Add(advPane, 0, wxGROW | wxALL, 2);*/
220 
221 			sizer->Add(diplomacySizer, wxSizerFlags().Expand().Border(wxTOP, 10));
222 		}
223 
224 		{
225 			/////////////////////////////////////////////////////////////////////////
226 			// Camera
227 			wxStaticBoxSizer* cameraSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Starting Camera"));
228 			wxGridSizer* gridSizer = new wxGridSizer(3);
229 			wxButton* cameraSet = new wxButton(this, ID_CameraSet, _("Set"), wxDefaultPosition, wxSize(48, -1));
230 			gridSizer->Add(Tooltipped(cameraSet,
231 				_("Set player camera to this view")), wxSizerFlags().Expand());
232 			wxButton* cameraView = new wxButton(this, ID_CameraView, _("View"), wxDefaultPosition, wxSize(48, -1));
233 			cameraView->Enable(false);
234 			gridSizer->Add(Tooltipped(cameraView,
235 				_("View the player camera")), wxSizerFlags().Expand());
236 			wxButton* cameraClear = new wxButton(this, ID_CameraClear, _("Clear"), wxDefaultPosition, wxSize(48, -1));
237 			cameraClear->Enable(false);
238 			gridSizer->Add(Tooltipped(cameraClear,
239 				_("Clear player camera")), wxSizerFlags().Expand());
240 			cameraSizer->Add(gridSizer, wxSizerFlags().Expand());
241 
242 			sizer->Add(cameraSizer, wxSizerFlags().Expand().Border(wxTOP, 10));
243 		}
244 
245 		Layout();
246 		Thaw();
247 
248 	}
249 
OnDisplay()250 	void OnDisplay()
251 	{
252 	}
253 
GetControls()254 	PlayerPageControls GetControls()
255 	{
256 		return m_Controls;
257 	}
258 
GetPlayerName()259 	wxString GetPlayerName()
260 	{
261 		return m_Name;
262 	}
263 
GetPlayerID()264 	size_t GetPlayerID()
265 	{
266 		return m_PlayerID;
267 	}
268 
IsCameraDefined()269 	bool IsCameraDefined()
270 	{
271 		return m_CameraDefined;
272 	}
273 
GetCamera()274 	sCameraInfo GetCamera()
275 	{
276 		return m_Camera;
277 	}
278 
SetCamera(sCameraInfo info,bool isDefined=true)279 	void SetCamera(sCameraInfo info, bool isDefined = true)
280 	{
281 		m_Camera = info;
282 		m_CameraDefined = isDefined;
283 
284 		// Enable/disable controls
285 		wxDynamicCast(FindWindow(ID_CameraView), wxButton)->Enable(isDefined);
286 		wxDynamicCast(FindWindow(ID_CameraClear), wxButton)->Enable(isDefined);
287 	}
288 
289 private:
OnColor(wxCommandEvent & evt)290 	void OnColor(wxCommandEvent& evt)
291 	{
292 		// Show color dialog
293 		ColorDialog colorDlg(this, _T("Scenario Editor/PlayerColor"), m_Controls.color->GetBackgroundColour());
294 
295 		if (colorDlg.ShowModal() == wxID_OK)
296 		{
297 			m_Controls.color->SetBackgroundColour(colorDlg.GetColourData().GetColour());
298 
299 			// Pass event on to next handler
300 			evt.Skip();
301 		}
302 	}
303 
OnCameraSet(wxCommandEvent & evt)304 	void OnCameraSet(wxCommandEvent& evt)
305 	{
306 		AtlasMessage::qGetView qryView;
307 		qryView.Post();
308 		SetCamera(qryView.info, true);
309 
310 		// Pass event on to next handler
311 		evt.Skip();
312 	}
313 
OnCameraView(wxCommandEvent & WXUNUSED (evt))314 	void OnCameraView(wxCommandEvent& WXUNUSED(evt))
315 	{
316 		POST_MESSAGE(SetView, (m_Camera));
317 	}
318 
OnCameraClear(wxCommandEvent & evt)319 	void OnCameraClear(wxCommandEvent& evt)
320 	{
321 		SetCamera(sCameraInfo(), false);
322 
323 		// Pass event on to next handler
324 		evt.Skip();
325 	}
326 
327 	sCameraInfo m_Camera;
328 	bool m_CameraDefined;
329 	wxString m_Name;
330 	size_t m_PlayerID;
331 
332 	PlayerPageControls m_Controls;
333 
334 	DECLARE_EVENT_TABLE();
335 };
336 
337 BEGIN_EVENT_TABLE(PlayerNotebookPage, wxPanel)
338 	EVT_BUTTON(ID_PlayerColor, PlayerNotebookPage::OnColor)
339 	EVT_BUTTON(ID_CameraSet, PlayerNotebookPage::OnCameraSet)
340 	EVT_BUTTON(ID_CameraView, PlayerNotebookPage::OnCameraView)
341 	EVT_BUTTON(ID_CameraClear, PlayerNotebookPage::OnCameraClear)
342 END_EVENT_TABLE();
343 
344 //////////////////////////////////////////////////////////////////////////
345 
346 class PlayerNotebook : public wxChoicebook
347 {
348 public:
PlayerNotebook(wxWindow * parent)349 	PlayerNotebook(wxWindow *parent)
350 		: wxChoicebook(parent, wxID_ANY/*, wxDefaultPosition, wxDefaultSize, wxNB_FIXEDWIDTH*/)
351 	{
352 	}
353 
AddPlayer(wxString name,size_t player)354 	PlayerPageControls AddPlayer(wxString name, size_t player)
355 	{
356 		PlayerNotebookPage* playerPage = new PlayerNotebookPage(this, name, player);
357 		AddPage(playerPage, name);
358 		m_Pages.push_back(playerPage);
359 		return playerPage->GetControls();
360 	}
361 
ResizePlayers(size_t numPlayers)362 	void ResizePlayers(size_t numPlayers)
363 	{
364 		wxASSERT(numPlayers <= m_Pages.size());
365 
366 		// We don't really want to destroy the windows corresponding
367 		//	to the tabs, so we've kept them in a vector and will
368 		//	only remove and add them to the notebook as needed
369 		int selection = GetSelection();
370 		size_t pageCount = GetPageCount();
371 
372 		if (numPlayers > pageCount)
373 		{
374 			// Add previously removed pages
375 			for (size_t i = pageCount; i < numPlayers; ++i)
376 			{
377 				AddPage(m_Pages[i], m_Pages[i]->GetPlayerName());
378 			}
379 		}
380 		else
381 		{
382 			// Remove previously added pages
383 			// we have to manually hide them or they remain visible
384 			for (size_t i = pageCount - 1; i >= numPlayers; --i)
385 			{
386 				m_Pages[i]->Hide();
387 				RemovePage(i);
388 			}
389 		}
390 
391 		// Workaround for bug on wxGTK 2.8: wxChoice selection doesn't update
392 		//	(in fact it loses its selection when adding/removing pages)
393 		GetChoiceCtrl()->SetSelection(selection);
394 	}
395 
396 protected:
OnPageChanged(wxChoicebookEvent & evt)397 	void OnPageChanged(wxChoicebookEvent& evt)
398 	{
399 		if (evt.GetSelection() >= 0 && evt.GetSelection() < (int)GetPageCount())
400 		{
401 			static_cast<PlayerNotebookPage*>(GetPage(evt.GetSelection()))->OnDisplay();
402 		}
403 		evt.Skip();
404 	}
405 
406 private:
407 	std::vector<PlayerNotebookPage*> m_Pages;
408 
409 	DECLARE_EVENT_TABLE();
410 };
411 
412 BEGIN_EVENT_TABLE(PlayerNotebook, wxChoicebook)
413 	EVT_CHOICEBOOK_PAGE_CHANGED(wxID_ANY, PlayerNotebook::OnPageChanged)
414 END_EVENT_TABLE();
415 
416 //////////////////////////////////////////////////////////////////////////
417 
418 class PlayerSettingsControl : public wxPanel
419 {
420 public:
421 	PlayerSettingsControl(wxWindow* parent, ScenarioEditor& scenarioEditor);
422 	void CreateWidgets();
423 	void LoadDefaults();
424 	void ReadFromEngine();
425 	AtObj UpdateSettingsObject();
426 
427 private:
428 	void SendToEngine();
429 
OnEdit(wxCommandEvent & WXUNUSED (evt))430 	void OnEdit(wxCommandEvent& WXUNUSED(evt))
431 	{
432 		if (!m_InGUIUpdate)
433 		{
434 			SendToEngine();
435 		}
436 	}
437 
OnEditSpin(wxSpinEvent & WXUNUSED (evt))438 	void OnEditSpin(wxSpinEvent& WXUNUSED(evt))
439 	{
440 		if (!m_InGUIUpdate)
441 		{
442 			SendToEngine();
443 		}
444 	}
445 
OnPlayerColor(wxCommandEvent & WXUNUSED (evt))446 	void OnPlayerColor(wxCommandEvent& WXUNUSED(evt))
447 	{
448 		if (!m_InGUIUpdate)
449 		{
450 			SendToEngine();
451 
452 			// Update player settings, to show new color
453 			POST_MESSAGE(LoadPlayerSettings, (false));
454 		}
455 	}
456 
OnNumPlayersText(wxCommandEvent & WXUNUSED (evt))457 	void OnNumPlayersText(wxCommandEvent& WXUNUSED(evt))
458 	{	// Ignore because it will also trigger EVT_SPINCTRL
459 		//	and we don't want to handle the same event twice
460 	}
461 
OnNumPlayersSpin(wxSpinEvent & evt)462 	void OnNumPlayersSpin(wxSpinEvent& evt)
463 	{
464 		if (!m_InGUIUpdate)
465 		{
466 			wxASSERT(evt.GetInt() > 0);
467 
468 			// When wxMessageBox pops up, wxSpinCtrl loses focus, which
469 			//	forces another EVT_SPINCTRL event, which we don't want
470 			//	to handle, so we check here for a change
471 			if (evt.GetInt() == (int)m_NumPlayers)
472 			{
473 				return;	// No change
474 			}
475 
476 			size_t oldNumPlayers = m_NumPlayers;
477 			m_NumPlayers = evt.GetInt();
478 
479 			if (m_NumPlayers < oldNumPlayers)
480 			{
481 				// Remove players, but check if they own any entities
482 				bool notified = false;
483 				for (size_t i = oldNumPlayers; i > m_NumPlayers; --i)
484 				{
485 					qGetPlayerObjects objectsQry(i);
486 					objectsQry.Post();
487 
488 					std::vector<AtlasMessage::ObjectID> ids = *objectsQry.ids;
489 
490 					if (ids.size() > 0)
491 					{
492 						if (!notified)
493 						{
494 							// TODO: Add option to reassign objects?
495 							if (wxMessageBox(_("WARNING: All objects belonging to the removed players will be deleted. Continue anyway?"), _("Remove player confirmation"), wxICON_EXCLAMATION | wxYES_NO) != wxYES)
496 							{
497 								// Restore previous player count
498 								m_NumPlayers = oldNumPlayers;
499 								wxDynamicCast(FindWindow(ID_NumPlayers), wxSpinCtrl)->SetValue(m_NumPlayers);
500 								return;
501 							}
502 
503 							notified = true;
504 						}
505 
506 						// Delete objects
507 						// TODO: Merge multiple commands?
508 						POST_COMMAND(DeleteObjects, (ids));
509 					}
510 				}
511 			}
512 
513 			m_Players->ResizePlayers(m_NumPlayers);
514 			SendToEngine();
515 
516 			// Reload players, notify observers
517 			POST_MESSAGE(LoadPlayerSettings, (true));
518 			m_MapSettings.NotifyObservers();
519 		}
520 	}
521 
522 	// TODO: we shouldn't hardcode this, but instead dynamically create
523 	//	new player notebook pages on demand; of course the default data
524 	//	will be limited by the entries in player_defaults.json
525 	static const size_t MAX_NUM_PLAYERS = 8;
526 
527 	bool m_InGUIUpdate;
528 	AtObj m_PlayerDefaults;
529 	PlayerNotebook* m_Players;
530 	std::vector<PlayerPageControls> m_PlayerControls;
531 	Observable<AtObj>& m_MapSettings;
532 	size_t m_NumPlayers;
533 
534 	DECLARE_EVENT_TABLE();
535 };
536 
537 BEGIN_EVENT_TABLE(PlayerSettingsControl, wxPanel)
538 	EVT_BUTTON(ID_PlayerColor, PlayerSettingsControl::OnPlayerColor)
539 	EVT_BUTTON(ID_CameraSet, PlayerSettingsControl::OnEdit)
540 	EVT_BUTTON(ID_CameraClear, PlayerSettingsControl::OnEdit)
541 	EVT_CHECKBOX(wxID_ANY, PlayerSettingsControl::OnEdit)
542 	EVT_CHOICE(wxID_ANY, PlayerSettingsControl::OnEdit)
543 	EVT_TEXT(ID_NumPlayers, PlayerSettingsControl::OnNumPlayersText)
544 	EVT_TEXT(wxID_ANY, PlayerSettingsControl::OnEdit)
545 	EVT_SPINCTRL(ID_NumPlayers, PlayerSettingsControl::OnNumPlayersSpin)
546 	EVT_SPINCTRL(ID_PlayerFood, PlayerSettingsControl::OnEditSpin)
547 	EVT_SPINCTRL(ID_PlayerWood, PlayerSettingsControl::OnEditSpin)
548 	EVT_SPINCTRL(ID_PlayerMetal, PlayerSettingsControl::OnEditSpin)
549 	EVT_SPINCTRL(ID_PlayerStone, PlayerSettingsControl::OnEditSpin)
550 	EVT_SPINCTRL(ID_PlayerPop, PlayerSettingsControl::OnEditSpin)
551 END_EVENT_TABLE();
552 
PlayerSettingsControl(wxWindow * parent,ScenarioEditor & scenarioEditor)553 PlayerSettingsControl::PlayerSettingsControl(wxWindow* parent, ScenarioEditor& scenarioEditor)
554 	: wxPanel(parent, wxID_ANY), m_InGUIUpdate(false), m_MapSettings(scenarioEditor.GetMapSettings()), m_NumPlayers(0)
555 {
556 	// To prevent recursion, don't handle GUI events right now
557 	m_InGUIUpdate = true;
558 
559 	wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Player settings"));
560 	SetSizer(sizer);
561 
562 	wxBoxSizer* boxSizer = new wxBoxSizer(wxHORIZONTAL);
563 	boxSizer->Add(new wxStaticText(this, wxID_ANY, _("Num players")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
564 	wxSpinCtrl* numPlayersSpin = new wxSpinCtrl(this, ID_NumPlayers, wxEmptyString, wxDefaultPosition, wxSize(40, -1));
565 	numPlayersSpin->SetValue(MAX_NUM_PLAYERS);
566 	numPlayersSpin->SetRange(1, MAX_NUM_PLAYERS);
567 	boxSizer->Add(numPlayersSpin);
568 	sizer->Add(boxSizer, wxSizerFlags().Expand().Proportion(0));
569 	sizer->AddSpacer(5);
570 	m_Players = new PlayerNotebook(this);
571 	sizer->Add(m_Players, wxSizerFlags().Expand().Proportion(1));
572 
573 	m_InGUIUpdate = false;
574 }
575 
CreateWidgets()576 void PlayerSettingsControl::CreateWidgets()
577 {
578 	// To prevent recursion, don't handle GUI events right now
579 	m_InGUIUpdate = true;
580 
581 	// Load default civ and player data
582 	wxArrayString civNames;
583 	wxArrayString civCodes;
584 	AtlasMessage::qGetCivData qryCiv;
585 	qryCiv.Post();
586 	std::vector<std::string> civData = *qryCiv.data;
587 	for (size_t i = 0; i < civData.size(); ++i)
588 	{
589 		AtObj civ = AtlasObject::LoadFromJSON(civData[i]);
590 		civNames.Add(wxString(civ["Name"]));
591 		civCodes.Add(wxString(civ["Code"]));
592 	}
593 
594 	// Load AI data
595 	ArrayOfAIData ais(AIData::CompareAIData);
596 	AtlasMessage::qGetAIData qryAI;
597 	qryAI.Post();
598 	AtObj aiData = AtlasObject::LoadFromJSON(*qryAI.data);
599 	for (AtIter a = aiData["AIData"]["item"]; a.defined(); ++a)
600 	{
601 		ais.Add(new AIData(wxString(a["id"]), wxString(a["data"]["name"])));
602 	}
603 
604 	// Create player pages
605 	AtIter playerDefs = m_PlayerDefaults["item"];
606 	if (playerDefs.defined())
607 		++playerDefs;	// Skip gaia
608 
609 	for (size_t i = 0; i < MAX_NUM_PLAYERS; ++i)
610 	{
611 		// Create new player tab and get controls
612 		wxString name(_("Unknown"));
613 		if (playerDefs["Name"].defined())
614 			name = playerDefs["Name"];
615 
616 		PlayerPageControls controls = m_Players->AddPlayer(name, i);
617 		m_PlayerControls.push_back(controls);
618 
619 		// Populate civ choice box
620 		wxChoice* civChoice = controls.civ;
621 		for (size_t j = 0; j < civNames.Count(); ++j)
622 			civChoice->Append(civNames[j], new wxStringClientData(civCodes[j]));
623 		civChoice->SetSelection(0);
624 
625 		// Populate ai choice box
626 		wxChoice* aiChoice = controls.ai;
627 		aiChoice->Append(_("<None>"), new wxStringClientData());
628 		for (size_t j = 0; j < ais.Count(); ++j)
629 			aiChoice->Append(ais[j]->GetName(), new wxStringClientData(ais[j]->GetID()));
630 		aiChoice->SetSelection(0);
631 
632 		if (playerDefs.defined())
633 			++playerDefs;
634 	}
635 
636 	m_InGUIUpdate = false;
637 }
638 
LoadDefaults()639 void PlayerSettingsControl::LoadDefaults()
640 {
641 	AtlasMessage::qGetPlayerDefaults qryPlayers;
642 	qryPlayers.Post();
643 	AtObj playerData = AtlasObject::LoadFromJSON(*qryPlayers.defaults);
644 	m_PlayerDefaults = *playerData["PlayerData"];
645 }
646 
ReadFromEngine()647 void PlayerSettingsControl::ReadFromEngine()
648 {
649 	AtlasMessage::qGetMapSettings qry;
650 	qry.Post();
651 
652 	if (!(*qry.settings).empty())
653 	{
654 		// Prevent error if there's no map settings to parse
655 		m_MapSettings = AtlasObject::LoadFromJSON(*qry.settings);
656 	}
657 	else
658 	{
659 		// Use blank object, it will be created next
660 		m_MapSettings = AtObj();
661 	}
662 
663 	AtIter player = m_MapSettings["PlayerData"]["item"];
664 	if (!m_MapSettings.defined() || !player.defined() || player.count() == 0)
665 	{
666 		// Player data missing - set number of players to max
667 		m_NumPlayers = MAX_NUM_PLAYERS;
668 	}
669 	else
670 	{
671 		++player; // skip gaia
672 		m_NumPlayers = player.count();
673 	}
674 
675 	wxASSERT(m_NumPlayers <= MAX_NUM_PLAYERS && m_NumPlayers != 0);
676 
677 	// To prevent recursion, don't handle GUI events right now
678 	m_InGUIUpdate = true;
679 
680 	wxDynamicCast(FindWindow(ID_NumPlayers), wxSpinCtrl)->SetValue(m_NumPlayers);
681 
682 	// Remove / add extra player pages as needed
683 	m_Players->ResizePlayers(m_NumPlayers);
684 
685 	// Update player controls with player data
686 	AtIter playerDefs = m_PlayerDefaults["item"];
687 	if (playerDefs.defined())
688 		++playerDefs;	// skip gaia
689 
690 	for (size_t i = 0; i < MAX_NUM_PLAYERS; ++i)
691 	{
692 		const PlayerPageControls& controls = m_PlayerControls[i];
693 
694 		// name
695 		wxString name(_("Unknown"));
696 		bool defined = player["Name"].defined();
697 		if (defined)
698 			name = wxString(player["Name"]);
699 		else if (playerDefs["Name"].defined())
700 			name = wxString(playerDefs["Name"]);
701 
702 		controls.name->SetValue(name);
703 		wxDynamicCast(FindWindowById(ID_DefaultName, controls.page), DefaultCheckbox)->SetValue(defined);
704 
705 		// civ
706 		wxChoice* choice = controls.civ;
707 		wxString civCode;
708 		defined = player["Civ"].defined();
709 		if (defined)
710 			civCode = wxString(player["Civ"]);
711 		else
712 			civCode = wxString(playerDefs["Civ"]);
713 
714 		for (size_t j = 0; j < choice->GetCount(); ++j)
715 		{
716 			wxStringClientData* str = dynamic_cast<wxStringClientData*>(choice->GetClientObject(j));
717 			if (str->GetData() == civCode)
718 			{
719 				choice->SetSelection(j);
720 				break;
721 			}
722 		}
723 		wxDynamicCast(FindWindowById(ID_DefaultCiv, controls.page), DefaultCheckbox)->SetValue(defined);
724 
725 		// color
726 		wxColor color;
727 		AtObj clrObj = *player["Color"];
728 		defined = clrObj.defined();
729 		if (!defined)
730 			clrObj = *playerDefs["Color"];
731 		color = wxColor((*clrObj["r"]).getInt(), (*clrObj["g"]).getInt(), (*clrObj["b"]).getInt());
732 		controls.color->SetBackgroundColour(color);
733 		wxDynamicCast(FindWindowById(ID_DefaultColor, controls.page), DefaultCheckbox)->SetValue(defined);
734 
735 		// player type
736 		wxString aiID;
737 		defined = player["AI"].defined();
738 		if (defined)
739 			aiID = wxString(player["AI"]);
740 		else
741 			aiID = wxString(playerDefs["AI"]);
742 
743 		choice = controls.ai;
744 		if (!aiID.empty())
745 		{
746 			// AI
747 			for (size_t j = 0; j < choice->GetCount(); ++j)
748 			{
749 				wxStringClientData* str = dynamic_cast<wxStringClientData*>(choice->GetClientObject(j));
750 				if (str->GetData() == aiID)
751 				{
752 					choice->SetSelection(j);
753 					break;
754 				}
755 			}
756 		}
757 		else // Human
758 			choice->SetSelection(0);
759 		wxDynamicCast(FindWindowById(ID_DefaultAI, controls.page), DefaultCheckbox)->SetValue(defined);
760 
761 		// resources
762 		AtObj resObj = *player["Resources"];
763 		defined = resObj.defined() && resObj["food"].defined();
764 		if (defined)
765 			controls.food->SetValue(wxString(resObj["food"]));
766 		else
767 			controls.food->SetValue(0);
768 		wxDynamicCast(FindWindowById(ID_DefaultFood, controls.page), DefaultCheckbox)->SetValue(defined);
769 
770 		defined = resObj.defined() && resObj["wood"].defined();
771 		if (defined)
772 			controls.wood->SetValue(wxString(resObj["wood"]));
773 		else
774 			controls.wood->SetValue(0);
775 		wxDynamicCast(FindWindowById(ID_DefaultWood, controls.page), DefaultCheckbox)->SetValue(defined);
776 
777 		defined = resObj.defined() && resObj["metal"].defined();
778 		if (defined)
779 			controls.metal->SetValue(wxString(resObj["metal"]));
780 		else
781 			controls.metal->SetValue(0);
782 		wxDynamicCast(FindWindowById(ID_DefaultMetal, controls.page), DefaultCheckbox)->SetValue(defined);
783 
784 		defined = resObj.defined() && resObj["stone"].defined();
785 		if (defined)
786 			controls.stone->SetValue(wxString(resObj["stone"]));
787 		else
788 			controls.stone->SetValue(0);
789 		wxDynamicCast(FindWindowById(ID_DefaultStone, controls.page), DefaultCheckbox)->SetValue(defined);
790 
791 		// population limit
792 		defined = player["PopulationLimit"].defined();
793 		if (defined)
794 			controls.pop->SetValue(wxString(player["PopulationLimit"]));
795 		else
796 			controls.pop->SetValue(0);
797 		wxDynamicCast(FindWindowById(ID_DefaultPop, controls.page), DefaultCheckbox)->SetValue(defined);
798 
799 		// team
800 		defined = player["Team"].defined();
801 		if (defined)
802 			controls.team->SetSelection((*player["Team"]).getInt() + 1);
803 		else
804 			controls.team->SetSelection(0);
805 		wxDynamicCast(FindWindowById(ID_DefaultTeam, controls.page), DefaultCheckbox)->SetValue(defined);
806 
807 		// camera
808 		if (player["StartingCamera"].defined())
809 		{
810 			sCameraInfo info;
811 			// Don't use wxAtof because it depends on locales which
812 			//	may cause problems with decimal points
813 			//	see: http://www.wxwidgets.org/docs/faqgtk.htm#locale
814 			AtObj camPos = *player["StartingCamera"]["Position"];
815 			info.pX = (float)(*camPos["x"]).getDouble();
816 			info.pY = (float)(*camPos["y"]).getDouble();
817 			info.pZ = (float)(*camPos["z"]).getDouble();
818 			AtObj camRot = *player["StartingCamera"]["Rotation"];
819 			info.rX = (float)(*camRot["x"]).getDouble();
820 			info.rY = (float)(*camRot["y"]).getDouble();
821 			info.rZ = (float)(*camRot["z"]).getDouble();
822 
823 			controls.page->SetCamera(info, true);
824 		}
825 		else
826 			controls.page->SetCamera(sCameraInfo(), false);
827 
828 		if (player.defined())
829 			++player;
830 
831 		if (playerDefs.defined())
832 			++playerDefs;
833 	}
834 
835 	// Send default properties to engine, since they might not be set
836 	SendToEngine();
837 
838 	m_InGUIUpdate = false;
839 }
840 
UpdateSettingsObject()841 AtObj PlayerSettingsControl::UpdateSettingsObject()
842 {
843 	// Update player data in the map settings
844 	AtObj players;
845 	players.set("@array", L"");
846 
847 	wxASSERT(m_NumPlayers <= MAX_NUM_PLAYERS);
848 
849 	AtIter playerDefs = m_PlayerDefaults["item"];
850 	if (playerDefs.defined())
851 		++playerDefs;	// Skip gaia
852 
853 	for (size_t i = 0; i < m_NumPlayers; ++i)
854 	{
855 		PlayerPageControls controls = m_PlayerControls[i];
856 
857 		AtObj player;
858 
859 		// name
860 		wxTextCtrl* text = controls.name;
861 		if (text->IsEnabled())
862 			player.set("Name", text->GetValue());
863 
864 		// civ
865 		wxChoice* choice = controls.civ;
866 		if (choice->IsEnabled() && choice->GetSelection() >= 0)
867 		{
868 			wxStringClientData* str = dynamic_cast<wxStringClientData*>(choice->GetClientObject(choice->GetSelection()));
869 			player.set("Civ", str->GetData());
870 		}
871 		else
872 			player.set("Civ", playerDefs["Civ"]);
873 
874 		// color
875 		if (controls.color->IsEnabled())
876 		{
877 			wxColor color = controls.color->GetBackgroundColour();
878 			AtObj clrObj;
879 			clrObj.setInt("r", (int)color.Red());
880 			clrObj.setInt("g", (int)color.Green());
881 			clrObj.setInt("b", (int)color.Blue());
882 			player.set("Color", clrObj);
883 		}
884 
885 		// player type
886 		choice = controls.ai;
887 		if (choice->IsEnabled())
888 		{
889 			if (choice->GetSelection() > 0)
890 			{
891 				// ai - get id
892 				wxStringClientData* str = dynamic_cast<wxStringClientData*>(choice->GetClientObject(choice->GetSelection()));
893 				player.set("AI", str->GetData());
894 			}
895 			else // human
896 				player.set("AI", _T(""));
897 		}
898 
899 		// resources
900 		AtObj resObj;
901 		if (controls.food->IsEnabled())
902 			resObj.setInt("food", controls.food->GetValue());
903 		if (controls.wood->IsEnabled())
904 			resObj.setInt("wood", controls.wood->GetValue());
905 		if (controls.metal->IsEnabled())
906 			resObj.setInt("metal", controls.metal->GetValue());
907 		if (controls.stone->IsEnabled())
908 			resObj.setInt("stone", controls.stone->GetValue());
909 		if (resObj.defined())
910 			player.set("Resources", resObj);
911 
912 		// population limit
913 		if (controls.pop->IsEnabled())
914 			player.setInt("PopulationLimit", controls.pop->GetValue());
915 
916 		// team
917 		choice = controls.team;
918 		if (choice->IsEnabled() && choice->GetSelection() >= 0)
919 			player.setInt("Team", choice->GetSelection() - 1);
920 
921 		// camera
922 		AtObj camObj;
923 		if (controls.page->IsCameraDefined())
924 		{
925 			sCameraInfo cam = controls.page->GetCamera();
926 			AtObj camPos;
927 			camPos.setDouble("x", cam.pX);
928 			camPos.setDouble("y", cam.pY);
929 			camPos.setDouble("z", cam.pZ);
930 			camObj.set("Position", camPos);
931 
932 			AtObj camRot;
933 			camRot.setDouble("x", cam.rX);
934 			camRot.setDouble("y", cam.rY);
935 			camRot.setDouble("z", cam.rZ);
936 			camObj.set("Rotation", camRot);
937 		}
938 		player.set("StartingCamera", camObj);
939 
940 		players.add("item", player);
941 
942 		if (playerDefs.defined())
943 			++playerDefs;
944 	}
945 
946 	m_MapSettings.set("PlayerData", players);
947 
948 	return m_MapSettings;
949 }
950 
SendToEngine()951 void PlayerSettingsControl::SendToEngine()
952 {
953 	UpdateSettingsObject();
954 
955 	std::string json = AtlasObject::SaveToJSON(m_MapSettings);
956 
957 	// TODO: would be nice if we supported undo for settings changes
958 
959 	POST_COMMAND(SetMapSettings, (json));
960 }
961 
962 //////////////////////////////////////////////////////////////////////////
963 
PlayerSidebar(ScenarioEditor & scenarioEditor,wxWindow * sidebarContainer,wxWindow * bottomBarContainer)964 PlayerSidebar::PlayerSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer)
965 	: Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer), m_Loaded(false)
966 {
967 	wxSizer* scrollSizer = new wxBoxSizer(wxVERTICAL);
968 	wxScrolledWindow* scrolledWindow = new wxScrolledWindow(this);
969 	scrolledWindow->SetScrollRate(10, 10);
970 	scrolledWindow->SetSizer(scrollSizer);
971 	m_MainSizer->Add(scrolledWindow, wxSizerFlags().Proportion(1).Expand());
972 
973 	m_PlayerSettingsCtrl = new PlayerSettingsControl(scrolledWindow, m_ScenarioEditor);
974 	scrollSizer->Add(m_PlayerSettingsCtrl, wxSizerFlags().Expand());
975 }
976 
OnCollapse(wxCollapsiblePaneEvent & WXUNUSED (evt))977 void PlayerSidebar::OnCollapse(wxCollapsiblePaneEvent& WXUNUSED(evt))
978 {
979 	Freeze();
980 
981 	// Toggling the collapsing doesn't seem to update the sidebar layout
982 	// automatically, so do it explicitly here
983 	Layout();
984 
985 	Refresh(); // fixes repaint glitch on Windows
986 
987 	Thaw();
988 }
989 
OnFirstDisplay()990 void PlayerSidebar::OnFirstDisplay()
991 {
992 	// We do this here becase messages are used which requires simulation to be init'd
993 	m_PlayerSettingsCtrl->LoadDefaults();
994 	m_PlayerSettingsCtrl->CreateWidgets();
995 	m_PlayerSettingsCtrl->ReadFromEngine();
996 
997 	m_Loaded = true;
998 
999 	Layout();
1000 }
1001 
OnMapReload()1002 void PlayerSidebar::OnMapReload()
1003 {
1004 	// Make sure we've loaded the controls
1005 	if (m_Loaded)
1006 	{
1007 		m_PlayerSettingsCtrl->ReadFromEngine();
1008 	}
1009 }
1010 
1011 BEGIN_EVENT_TABLE(PlayerSidebar, Sidebar)
1012 	EVT_COLLAPSIBLEPANE_CHANGED(wxID_ANY, PlayerSidebar::OnCollapse)
1013 END_EVENT_TABLE();
1014