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