/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Player.h" #include "AtlasObject/AtlasObject.h" #include "CustomControls/ColorDialog/ColorDialog.h" #include "ScenarioEditor/ScenarioEditor.h" #include "wx/choicebk.h" enum { ID_NumPlayers, ID_PlayerFood, ID_PlayerWood, ID_PlayerMetal, ID_PlayerStone, ID_PlayerPop, ID_PlayerColor, ID_DefaultName, ID_DefaultCiv, ID_DefaultColor, ID_DefaultAI, ID_DefaultFood, ID_DefaultWood, ID_DefaultMetal, ID_DefaultStone, ID_DefaultPop, ID_DefaultTeam, ID_CameraSet, ID_CameraView, ID_CameraClear }; // TODO: Some of these helper things should be moved out of this file // and into shared locations // Helper function for adding tooltips static wxWindow* Tooltipped(wxWindow* window, const wxString& tip) { window->SetToolTip(tip); return window; } ////////////////////////////////////////////////////////////////////////// class DefaultCheckbox : public wxCheckBox { public: DefaultCheckbox(wxWindow* parent, wxWindowID id, wxWindow* control, bool initialValue = false) : wxCheckBox(parent, id, wxEmptyString), m_Control(control) { SetValue(initialValue); } virtual void SetValue(bool value) { m_Control->Enable(value); wxCheckBox::SetValue(value); } void OnChecked(wxCommandEvent& evt) { m_Control->Enable(evt.IsChecked()); evt.Skip(); } private: wxWindow* m_Control; DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(DefaultCheckbox, wxCheckBox) EVT_CHECKBOX(wxID_ANY, DefaultCheckbox::OnChecked) END_EVENT_TABLE(); class PlayerNotebookPage : public wxPanel { public: PlayerNotebookPage(wxWindow* parent, const wxString& name, size_t playerID) : wxPanel(parent, wxID_ANY), m_Name(name), m_PlayerID(playerID) { m_Controls.page = this; Freeze(); wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); SetSizer(sizer); { ///////////////////////////////////////////////////////////////////////// // Player Info wxStaticBoxSizer* playerInfoSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Player info")); wxFlexGridSizer* gridSizer = new wxFlexGridSizer(3, 5, 5); gridSizer->AddGrowableCol(2); wxTextCtrl* nameCtrl = new wxTextCtrl(this, wxID_ANY); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultName, nameCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Name")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(nameCtrl, wxSizerFlags(1).Expand().Align(wxALIGN_RIGHT)); m_Controls.name = nameCtrl; wxChoice* civChoice = new wxChoice(this, wxID_ANY); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultCiv, civChoice), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Civilisation")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(civChoice, wxSizerFlags(1).Expand().Align(wxALIGN_RIGHT)); m_Controls.civ = civChoice; wxButton* colorButton = new wxButton(this, ID_PlayerColor); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultColor, colorButton), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Color")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(colorButton, _("Set player color")), wxSizerFlags(1).Expand().Align(wxALIGN_RIGHT)); m_Controls.color = colorButton; wxChoice* aiChoice = new wxChoice(this, wxID_ANY); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultAI, aiChoice), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("AI")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(aiChoice, _("Select AI")), wxSizerFlags(1).Expand().Align(wxALIGN_RIGHT)); m_Controls.ai = aiChoice; playerInfoSizer->Add(gridSizer, wxSizerFlags(1).Expand()); sizer->Add(playerInfoSizer, wxSizerFlags().Expand().Border(wxTOP, 10)); } { ///////////////////////////////////////////////////////////////////////// // Resources wxStaticBoxSizer* resourceSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Resources")); wxFlexGridSizer* gridSizer = new wxFlexGridSizer(3, 5, 5); gridSizer->AddGrowableCol(2); wxSpinCtrl* foodCtrl = new wxSpinCtrl(this, ID_PlayerFood, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultFood, foodCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Food")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(foodCtrl, _("Initial value of food resource")), wxSizerFlags().Expand()); m_Controls.food = foodCtrl; wxSpinCtrl* woodCtrl = new wxSpinCtrl(this, ID_PlayerWood, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultWood, woodCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Wood")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(woodCtrl, _("Initial value of wood resource")), wxSizerFlags().Expand()); m_Controls.wood = woodCtrl; wxSpinCtrl* metalCtrl = new wxSpinCtrl(this, ID_PlayerMetal, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultMetal, metalCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Metal")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(metalCtrl, _("Initial value of metal resource")), wxSizerFlags().Expand()); m_Controls.metal = metalCtrl; wxSpinCtrl* stoneCtrl = new wxSpinCtrl(this, ID_PlayerStone, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultStone, stoneCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Stone")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(stoneCtrl, _("Initial value of stone resource")), wxSizerFlags().Expand()); m_Controls.stone = stoneCtrl; wxSpinCtrl* popCtrl = new wxSpinCtrl(this, ID_PlayerPop, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultPop, popCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Pop limit")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(popCtrl, _("Population limit for this player")), wxSizerFlags().Expand()); m_Controls.pop = popCtrl; resourceSizer->Add(gridSizer, wxSizerFlags(1).Expand()); sizer->Add(resourceSizer, wxSizerFlags().Expand().Border(wxTOP, 10)); } { ///////////////////////////////////////////////////////////////////////// // Diplomacy wxStaticBoxSizer* diplomacySizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Diplomacy")); wxBoxSizer* boxSizer = new wxBoxSizer(wxHORIZONTAL); wxChoice* teamCtrl = new wxChoice(this, wxID_ANY); boxSizer->Add(new DefaultCheckbox(this, ID_DefaultTeam, teamCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); boxSizer->AddSpacer(5); boxSizer->Add(new wxStaticText(this, wxID_ANY, _("Team")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); boxSizer->AddSpacer(5); teamCtrl->Append(_("None")); teamCtrl->Append(_T("1")); teamCtrl->Append(_T("2")); teamCtrl->Append(_T("3")); teamCtrl->Append(_T("4")); boxSizer->Add(teamCtrl); m_Controls.team = teamCtrl; diplomacySizer->Add(boxSizer, wxSizerFlags(1).Expand()); // TODO: possibly have advanced panel where each player's diplomacy can be set? // Advanced panel /*wxCollapsiblePane* advPane = new wxCollapsiblePane(this, wxID_ANY, _("Advanced")); wxWindow* pane = advPane->GetPane(); diplomacySizer->Add(advPane, 0, wxGROW | wxALL, 2);*/ sizer->Add(diplomacySizer, wxSizerFlags().Expand().Border(wxTOP, 10)); } { ///////////////////////////////////////////////////////////////////////// // Camera wxStaticBoxSizer* cameraSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Starting Camera")); wxGridSizer* gridSizer = new wxGridSizer(3); wxButton* cameraSet = new wxButton(this, ID_CameraSet, _("Set"), wxDefaultPosition, wxSize(48, -1)); gridSizer->Add(Tooltipped(cameraSet, _("Set player camera to this view")), wxSizerFlags().Expand()); wxButton* cameraView = new wxButton(this, ID_CameraView, _("View"), wxDefaultPosition, wxSize(48, -1)); cameraView->Enable(false); gridSizer->Add(Tooltipped(cameraView, _("View the player camera")), wxSizerFlags().Expand()); wxButton* cameraClear = new wxButton(this, ID_CameraClear, _("Clear"), wxDefaultPosition, wxSize(48, -1)); cameraClear->Enable(false); gridSizer->Add(Tooltipped(cameraClear, _("Clear player camera")), wxSizerFlags().Expand()); cameraSizer->Add(gridSizer, wxSizerFlags().Expand()); sizer->Add(cameraSizer, wxSizerFlags().Expand().Border(wxTOP, 10)); } Layout(); Thaw(); } void OnDisplay() { } PlayerPageControls GetControls() { return m_Controls; } wxString GetPlayerName() { return m_Name; } size_t GetPlayerID() { return m_PlayerID; } bool IsCameraDefined() { return m_CameraDefined; } sCameraInfo GetCamera() { return m_Camera; } void SetCamera(sCameraInfo info, bool isDefined = true) { m_Camera = info; m_CameraDefined = isDefined; // Enable/disable controls wxDynamicCast(FindWindow(ID_CameraView), wxButton)->Enable(isDefined); wxDynamicCast(FindWindow(ID_CameraClear), wxButton)->Enable(isDefined); } private: void OnColor(wxCommandEvent& evt) { // Show color dialog ColorDialog colorDlg(this, _T("Scenario Editor/PlayerColor"), m_Controls.color->GetBackgroundColour()); if (colorDlg.ShowModal() == wxID_OK) { m_Controls.color->SetBackgroundColour(colorDlg.GetColourData().GetColour()); // Pass event on to next handler evt.Skip(); } } void OnCameraSet(wxCommandEvent& evt) { AtlasMessage::qGetView qryView; qryView.Post(); SetCamera(qryView.info, true); // Pass event on to next handler evt.Skip(); } void OnCameraView(wxCommandEvent& WXUNUSED(evt)) { POST_MESSAGE(SetView, (m_Camera)); } void OnCameraClear(wxCommandEvent& evt) { SetCamera(sCameraInfo(), false); // Pass event on to next handler evt.Skip(); } sCameraInfo m_Camera; bool m_CameraDefined; wxString m_Name; size_t m_PlayerID; PlayerPageControls m_Controls; DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(PlayerNotebookPage, wxPanel) EVT_BUTTON(ID_PlayerColor, PlayerNotebookPage::OnColor) EVT_BUTTON(ID_CameraSet, PlayerNotebookPage::OnCameraSet) EVT_BUTTON(ID_CameraView, PlayerNotebookPage::OnCameraView) EVT_BUTTON(ID_CameraClear, PlayerNotebookPage::OnCameraClear) END_EVENT_TABLE(); ////////////////////////////////////////////////////////////////////////// class PlayerNotebook : public wxChoicebook { public: PlayerNotebook(wxWindow *parent) : wxChoicebook(parent, wxID_ANY/*, wxDefaultPosition, wxDefaultSize, wxNB_FIXEDWIDTH*/) { } PlayerPageControls AddPlayer(wxString name, size_t player) { PlayerNotebookPage* playerPage = new PlayerNotebookPage(this, name, player); AddPage(playerPage, name); m_Pages.push_back(playerPage); return playerPage->GetControls(); } void ResizePlayers(size_t numPlayers) { wxASSERT(numPlayers <= m_Pages.size()); // We don't really want to destroy the windows corresponding // to the tabs, so we've kept them in a vector and will // only remove and add them to the notebook as needed int selection = GetSelection(); size_t pageCount = GetPageCount(); if (numPlayers > pageCount) { // Add previously removed pages for (size_t i = pageCount; i < numPlayers; ++i) { AddPage(m_Pages[i], m_Pages[i]->GetPlayerName()); } } else { // Remove previously added pages // we have to manually hide them or they remain visible for (size_t i = pageCount - 1; i >= numPlayers; --i) { m_Pages[i]->Hide(); RemovePage(i); } } // Workaround for bug on wxGTK 2.8: wxChoice selection doesn't update // (in fact it loses its selection when adding/removing pages) GetChoiceCtrl()->SetSelection(selection); } protected: void OnPageChanged(wxChoicebookEvent& evt) { if (evt.GetSelection() >= 0 && evt.GetSelection() < (int)GetPageCount()) { static_cast(GetPage(evt.GetSelection()))->OnDisplay(); } evt.Skip(); } private: std::vector m_Pages; DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(PlayerNotebook, wxChoicebook) EVT_CHOICEBOOK_PAGE_CHANGED(wxID_ANY, PlayerNotebook::OnPageChanged) END_EVENT_TABLE(); ////////////////////////////////////////////////////////////////////////// class PlayerSettingsControl : public wxPanel { public: PlayerSettingsControl(wxWindow* parent, ScenarioEditor& scenarioEditor); void CreateWidgets(); void LoadDefaults(); void ReadFromEngine(); AtObj UpdateSettingsObject(); private: void SendToEngine(); void OnEdit(wxCommandEvent& WXUNUSED(evt)) { if (!m_InGUIUpdate) { SendToEngine(); } } void OnEditSpin(wxSpinEvent& WXUNUSED(evt)) { if (!m_InGUIUpdate) { SendToEngine(); } } void OnPlayerColor(wxCommandEvent& WXUNUSED(evt)) { if (!m_InGUIUpdate) { SendToEngine(); // Update player settings, to show new color POST_MESSAGE(LoadPlayerSettings, (false)); } } void OnNumPlayersText(wxCommandEvent& WXUNUSED(evt)) { // Ignore because it will also trigger EVT_SPINCTRL // and we don't want to handle the same event twice } void OnNumPlayersSpin(wxSpinEvent& evt) { if (!m_InGUIUpdate) { wxASSERT(evt.GetInt() > 0); // When wxMessageBox pops up, wxSpinCtrl loses focus, which // forces another EVT_SPINCTRL event, which we don't want // to handle, so we check here for a change if (evt.GetInt() == (int)m_NumPlayers) { return; // No change } size_t oldNumPlayers = m_NumPlayers; m_NumPlayers = evt.GetInt(); if (m_NumPlayers < oldNumPlayers) { // Remove players, but check if they own any entities bool notified = false; for (size_t i = oldNumPlayers; i > m_NumPlayers; --i) { qGetPlayerObjects objectsQry(i); objectsQry.Post(); std::vector ids = *objectsQry.ids; if (ids.size() > 0) { if (!notified) { // TODO: Add option to reassign objects? if (wxMessageBox(_("WARNING: All objects belonging to the removed players will be deleted. Continue anyway?"), _("Remove player confirmation"), wxICON_EXCLAMATION | wxYES_NO) != wxYES) { // Restore previous player count m_NumPlayers = oldNumPlayers; wxDynamicCast(FindWindow(ID_NumPlayers), wxSpinCtrl)->SetValue(m_NumPlayers); return; } notified = true; } // Delete objects // TODO: Merge multiple commands? POST_COMMAND(DeleteObjects, (ids)); } } } m_Players->ResizePlayers(m_NumPlayers); SendToEngine(); // Reload players, notify observers POST_MESSAGE(LoadPlayerSettings, (true)); m_MapSettings.NotifyObservers(); } } // TODO: we shouldn't hardcode this, but instead dynamically create // new player notebook pages on demand; of course the default data // will be limited by the entries in player_defaults.json static const size_t MAX_NUM_PLAYERS = 8; bool m_InGUIUpdate; AtObj m_PlayerDefaults; PlayerNotebook* m_Players; std::vector m_PlayerControls; Observable& m_MapSettings; size_t m_NumPlayers; DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(PlayerSettingsControl, wxPanel) EVT_BUTTON(ID_PlayerColor, PlayerSettingsControl::OnPlayerColor) EVT_BUTTON(ID_CameraSet, PlayerSettingsControl::OnEdit) EVT_BUTTON(ID_CameraClear, PlayerSettingsControl::OnEdit) EVT_CHECKBOX(wxID_ANY, PlayerSettingsControl::OnEdit) EVT_CHOICE(wxID_ANY, PlayerSettingsControl::OnEdit) EVT_TEXT(ID_NumPlayers, PlayerSettingsControl::OnNumPlayersText) EVT_TEXT(wxID_ANY, PlayerSettingsControl::OnEdit) EVT_SPINCTRL(ID_NumPlayers, PlayerSettingsControl::OnNumPlayersSpin) EVT_SPINCTRL(ID_PlayerFood, PlayerSettingsControl::OnEditSpin) EVT_SPINCTRL(ID_PlayerWood, PlayerSettingsControl::OnEditSpin) EVT_SPINCTRL(ID_PlayerMetal, PlayerSettingsControl::OnEditSpin) EVT_SPINCTRL(ID_PlayerStone, PlayerSettingsControl::OnEditSpin) EVT_SPINCTRL(ID_PlayerPop, PlayerSettingsControl::OnEditSpin) END_EVENT_TABLE(); PlayerSettingsControl::PlayerSettingsControl(wxWindow* parent, ScenarioEditor& scenarioEditor) : wxPanel(parent, wxID_ANY), m_InGUIUpdate(false), m_MapSettings(scenarioEditor.GetMapSettings()), m_NumPlayers(0) { // To prevent recursion, don't handle GUI events right now m_InGUIUpdate = true; wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Player settings")); SetSizer(sizer); wxBoxSizer* boxSizer = new wxBoxSizer(wxHORIZONTAL); boxSizer->Add(new wxStaticText(this, wxID_ANY, _("Num players")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); wxSpinCtrl* numPlayersSpin = new wxSpinCtrl(this, ID_NumPlayers, wxEmptyString, wxDefaultPosition, wxSize(40, -1)); numPlayersSpin->SetValue(MAX_NUM_PLAYERS); numPlayersSpin->SetRange(1, MAX_NUM_PLAYERS); boxSizer->Add(numPlayersSpin); sizer->Add(boxSizer, wxSizerFlags().Expand().Proportion(0)); sizer->AddSpacer(5); m_Players = new PlayerNotebook(this); sizer->Add(m_Players, wxSizerFlags().Expand().Proportion(1)); m_InGUIUpdate = false; } void PlayerSettingsControl::CreateWidgets() { // To prevent recursion, don't handle GUI events right now m_InGUIUpdate = true; // Load default civ and player data wxArrayString civNames; wxArrayString civCodes; AtlasMessage::qGetCivData qryCiv; qryCiv.Post(); std::vector> civData = *qryCiv.data; for (const std::vector& civ : civData) { civCodes.Add(civ[0]); civNames.Add(civ[1]); } // Load AI data ArrayOfAIData ais(AIData::CompareAIData); AtlasMessage::qGetAIData qryAI; qryAI.Post(); AtObj aiData = AtlasObject::LoadFromJSON(*qryAI.data); for (AtIter a = aiData["AIData"]["item"]; a.defined(); ++a) { ais.Add(new AIData(wxString::FromUTF8(a["id"]), wxString::FromUTF8(a["data"]["name"]))); } // Create player pages AtIter playerDefs = m_PlayerDefaults["item"]; if (playerDefs.defined()) ++playerDefs; // Skip gaia for (size_t i = 0; i < MAX_NUM_PLAYERS; ++i) { // Create new player tab and get controls wxString name(_("Unknown")); if (playerDefs["Name"].defined()) name = playerDefs["Name"]; PlayerPageControls controls = m_Players->AddPlayer(name, i); m_PlayerControls.push_back(controls); // Populate civ choice box wxChoice* civChoice = controls.civ; for (size_t j = 0; j < civNames.Count(); ++j) civChoice->Append(civNames[j], new wxStringClientData(civCodes[j])); civChoice->SetSelection(0); // Populate ai choice box wxChoice* aiChoice = controls.ai; aiChoice->Append(_(""), new wxStringClientData()); for (size_t j = 0; j < ais.Count(); ++j) aiChoice->Append(ais[j]->GetName(), new wxStringClientData(ais[j]->GetID())); aiChoice->SetSelection(0); if (playerDefs.defined()) ++playerDefs; } m_InGUIUpdate = false; } void PlayerSettingsControl::LoadDefaults() { AtlasMessage::qGetPlayerDefaults qryPlayers; qryPlayers.Post(); AtObj playerData = AtlasObject::LoadFromJSON(*qryPlayers.defaults); m_PlayerDefaults = *playerData["PlayerData"]; } void PlayerSettingsControl::ReadFromEngine() { AtlasMessage::qGetMapSettings qry; qry.Post(); if (!(*qry.settings).empty()) { // Prevent error if there's no map settings to parse m_MapSettings = AtlasObject::LoadFromJSON(*qry.settings); } else { // Use blank object, it will be created next m_MapSettings = AtObj(); } AtIter player = m_MapSettings["PlayerData"]["item"]; if (!m_MapSettings.defined() || !player.defined() || player.count() == 0) { // Player data missing - set number of players to max m_NumPlayers = MAX_NUM_PLAYERS; } else { ++player; // skip gaia m_NumPlayers = player.count(); } wxASSERT(m_NumPlayers <= MAX_NUM_PLAYERS && m_NumPlayers != 0); // To prevent recursion, don't handle GUI events right now m_InGUIUpdate = true; wxDynamicCast(FindWindow(ID_NumPlayers), wxSpinCtrl)->SetValue(m_NumPlayers); // Remove / add extra player pages as needed m_Players->ResizePlayers(m_NumPlayers); // Update player controls with player data AtIter playerDefs = m_PlayerDefaults["item"]; if (playerDefs.defined()) ++playerDefs; // skip gaia for (size_t i = 0; i < MAX_NUM_PLAYERS; ++i) { const PlayerPageControls& controls = m_PlayerControls[i]; // name wxString name(_("Unknown")); bool defined = player["Name"].defined(); if (defined) name = wxString::FromUTF8(player["Name"]); else if (playerDefs["Name"].defined()) name = wxString::FromUTF8(playerDefs["Name"]); controls.name->SetValue(name); wxDynamicCast(FindWindowById(ID_DefaultName, controls.page), DefaultCheckbox)->SetValue(defined); // civ wxChoice* choice = controls.civ; defined = player["Civ"].defined(); wxString civCode = wxString::FromUTF8(defined ? player["Civ"] : playerDefs["Civ"]); for (size_t j = 0; j < choice->GetCount(); ++j) { wxStringClientData* str = dynamic_cast(choice->GetClientObject(j)); if (str->GetData() == civCode) { choice->SetSelection(j); break; } } wxDynamicCast(FindWindowById(ID_DefaultCiv, controls.page), DefaultCheckbox)->SetValue(defined); // color wxColor color; AtObj clrObj = *player["Color"]; defined = clrObj.defined(); if (!defined) clrObj = *playerDefs["Color"]; color = wxColor((*clrObj["r"]).getInt(), (*clrObj["g"]).getInt(), (*clrObj["b"]).getInt()); controls.color->SetBackgroundColour(color); wxDynamicCast(FindWindowById(ID_DefaultColor, controls.page), DefaultCheckbox)->SetValue(defined); // player type defined = player["AI"].defined(); wxString aiID = wxString::FromUTF8(defined ? player["AI"] : playerDefs["AI"]); choice = controls.ai; if (!aiID.empty()) { // AI for (size_t j = 0; j < choice->GetCount(); ++j) { wxStringClientData* str = dynamic_cast(choice->GetClientObject(j)); if (str->GetData() == aiID) { choice->SetSelection(j); break; } } } else // Human choice->SetSelection(0); wxDynamicCast(FindWindowById(ID_DefaultAI, controls.page), DefaultCheckbox)->SetValue(defined); // resources AtObj resObj = *player["Resources"]; defined = resObj.defined() && resObj["food"].defined(); if (defined) controls.food->SetValue((*resObj["food"]).getInt()); else controls.food->SetValue(0); wxDynamicCast(FindWindowById(ID_DefaultFood, controls.page), DefaultCheckbox)->SetValue(defined); defined = resObj.defined() && resObj["wood"].defined(); if (defined) controls.wood->SetValue((*resObj["wood"]).getInt()); else controls.wood->SetValue(0); wxDynamicCast(FindWindowById(ID_DefaultWood, controls.page), DefaultCheckbox)->SetValue(defined); defined = resObj.defined() && resObj["metal"].defined(); if (defined) controls.metal->SetValue((*resObj["metal"]).getInt()); else controls.metal->SetValue(0); wxDynamicCast(FindWindowById(ID_DefaultMetal, controls.page), DefaultCheckbox)->SetValue(defined); defined = resObj.defined() && resObj["stone"].defined(); if (defined) controls.stone->SetValue((*resObj["stone"]).getInt()); else controls.stone->SetValue(0); wxDynamicCast(FindWindowById(ID_DefaultStone, controls.page), DefaultCheckbox)->SetValue(defined); // population limit defined = player["PopulationLimit"].defined(); if (defined) controls.pop->SetValue((*player["PopulationLimit"]).getInt()); else controls.pop->SetValue(0); wxDynamicCast(FindWindowById(ID_DefaultPop, controls.page), DefaultCheckbox)->SetValue(defined); // team defined = player["Team"].defined(); if (defined) controls.team->SetSelection((*player["Team"]).getInt() + 1); else controls.team->SetSelection(0); wxDynamicCast(FindWindowById(ID_DefaultTeam, controls.page), DefaultCheckbox)->SetValue(defined); // camera if (player["StartingCamera"].defined()) { sCameraInfo info; // Don't use wxAtof because it depends on locales which // may cause problems with decimal points // see: http://www.wxwidgets.org/docs/faqgtk.htm#locale AtObj camPos = *player["StartingCamera"]["Position"]; info.pX = (float)(*camPos["x"]).getDouble(); info.pY = (float)(*camPos["y"]).getDouble(); info.pZ = (float)(*camPos["z"]).getDouble(); AtObj camRot = *player["StartingCamera"]["Rotation"]; info.rX = (float)(*camRot["x"]).getDouble(); info.rY = (float)(*camRot["y"]).getDouble(); info.rZ = (float)(*camRot["z"]).getDouble(); controls.page->SetCamera(info, true); } else controls.page->SetCamera(sCameraInfo(), false); if (player.defined()) ++player; if (playerDefs.defined()) ++playerDefs; } // Send default properties to engine, since they might not be set SendToEngine(); m_InGUIUpdate = false; } AtObj PlayerSettingsControl::UpdateSettingsObject() { // Update player data in the map settings AtObj players; players.set("@array", ""); wxASSERT(m_NumPlayers <= MAX_NUM_PLAYERS); AtIter playerDefs = m_PlayerDefaults["item"]; if (playerDefs.defined()) ++playerDefs; // Skip gaia for (size_t i = 0; i < m_NumPlayers; ++i) { PlayerPageControls controls = m_PlayerControls[i]; AtObj player; // name wxTextCtrl* text = controls.name; if (text->IsEnabled()) player.set("Name", text->GetValue().utf8_str()); // civ wxChoice* choice = controls.civ; if (choice->IsEnabled() && choice->GetSelection() >= 0) { wxStringClientData* str = dynamic_cast(choice->GetClientObject(choice->GetSelection())); player.set("Civ", str->GetData().utf8_str()); } else player.unset("Civ"); // color if (controls.color->IsEnabled()) { wxColor color = controls.color->GetBackgroundColour(); AtObj clrObj; clrObj.setInt("r", (int)color.Red()); clrObj.setInt("g", (int)color.Green()); clrObj.setInt("b", (int)color.Blue()); player.set("Color", clrObj); } // player type choice = controls.ai; if (choice->IsEnabled() && choice->GetSelection() > 0) { // ai - get id wxStringClientData* str = dynamic_cast(choice->GetClientObject(choice->GetSelection())); player.set("AI", str->GetData().utf8_str()); } else // human player.unset("AI"); // resources AtObj resObj; if (controls.food->IsEnabled()) resObj.setInt("food", controls.food->GetValue()); if (controls.wood->IsEnabled()) resObj.setInt("wood", controls.wood->GetValue()); if (controls.metal->IsEnabled()) resObj.setInt("metal", controls.metal->GetValue()); if (controls.stone->IsEnabled()) resObj.setInt("stone", controls.stone->GetValue()); if (resObj.defined()) player.set("Resources", resObj); // population limit if (controls.pop->IsEnabled()) player.setInt("PopulationLimit", controls.pop->GetValue()); // team choice = controls.team; if (choice->IsEnabled() && choice->GetSelection() >= 0) player.setInt("Team", choice->GetSelection() - 1); // camera AtObj camObj; if (controls.page->IsCameraDefined()) { sCameraInfo cam = controls.page->GetCamera(); AtObj camPos; camPos.setDouble("x", cam.pX); camPos.setDouble("y", cam.pY); camPos.setDouble("z", cam.pZ); camObj.set("Position", camPos); AtObj camRot; camRot.setDouble("x", cam.rX); camRot.setDouble("y", cam.rY); camRot.setDouble("z", cam.rZ); camObj.set("Rotation", camRot); } player.set("StartingCamera", camObj); players.add("item", player); if (playerDefs.defined()) ++playerDefs; } m_MapSettings.set("PlayerData", players); return m_MapSettings; } void PlayerSettingsControl::SendToEngine() { UpdateSettingsObject(); std::string json = AtlasObject::SaveToJSON(m_MapSettings); // TODO: would be nice if we supported undo for settings changes POST_COMMAND(SetMapSettings, (json)); } ////////////////////////////////////////////////////////////////////////// PlayerSidebar::PlayerSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer) : Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer), m_Loaded(false) { wxSizer* scrollSizer = new wxBoxSizer(wxVERTICAL); wxScrolledWindow* scrolledWindow = new wxScrolledWindow(this); scrolledWindow->SetScrollRate(10, 10); scrolledWindow->SetSizer(scrollSizer); m_MainSizer->Add(scrolledWindow, wxSizerFlags().Proportion(1).Expand()); m_PlayerSettingsCtrl = new PlayerSettingsControl(scrolledWindow, m_ScenarioEditor); scrollSizer->Add(m_PlayerSettingsCtrl, wxSizerFlags().Expand()); } void PlayerSidebar::OnCollapse(wxCollapsiblePaneEvent& WXUNUSED(evt)) { Freeze(); // Toggling the collapsing doesn't seem to update the sidebar layout // automatically, so do it explicitly here Layout(); Refresh(); // fixes repaint glitch on Windows Thaw(); } void PlayerSidebar::OnFirstDisplay() { // We do this here becase messages are used which requires simulation to be init'd m_PlayerSettingsCtrl->LoadDefaults(); m_PlayerSettingsCtrl->CreateWidgets(); m_PlayerSettingsCtrl->ReadFromEngine(); m_Loaded = true; Layout(); } void PlayerSidebar::OnMapReload() { // Make sure we've loaded the controls if (m_Loaded) { m_PlayerSettingsCtrl->ReadFromEngine(); } } BEGIN_EVENT_TABLE(PlayerSidebar, Sidebar) EVT_COLLAPSIBLEPANE_CHANGED(wxID_ANY, PlayerSidebar::OnCollapse) END_EVENT_TABLE();