mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
There is a workaround for an old bug with the use of wxCollapsiblePane. As the widget itself is no longer in use, just remove the now dead code. Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
1017 lines
32 KiB
C++
1017 lines
32 KiB
C++
/* Copyright (C) 2026 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
#include "Player.h"
|
|
|
|
#include "tools/atlas/AtlasObject/AtlasObject.h"
|
|
#include "tools/atlas/AtlasUI/CustomControls/ColorDialog/ColorDialog.h"
|
|
#include "tools/atlas/AtlasUI/General/Observable.h"
|
|
#include "tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h"
|
|
#include "tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.h"
|
|
#include "tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Tools.h"
|
|
#include "tools/atlas/GameInterface/MessagePasser.h"
|
|
#include "tools/atlas/GameInterface/Messages.h"
|
|
#include "tools/atlas/GameInterface/Shareable.h"
|
|
#include "tools/atlas/GameInterface/SharedTypes.h"
|
|
|
|
#include <climits>
|
|
#include <cstddef>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <wx/arrstr.h>
|
|
#include <wx/bookctrl.h>
|
|
#include <wx/button.h>
|
|
#include <wx/chartype.h>
|
|
#include <wx/checkbox.h>
|
|
#include <wx/choice.h>
|
|
#include <wx/choicebk.h>
|
|
#include <wx/clntdata.h>
|
|
#include <wx/colour.h>
|
|
#include <wx/colourdata.h>
|
|
#include <wx/debug.h>
|
|
#include <wx/gdicmn.h>
|
|
#include <wx/msgdlg.h>
|
|
#include <wx/object.h>
|
|
#include <wx/panel.h>
|
|
#include <wx/scrolwin.h>
|
|
#include <wx/sizer.h>
|
|
#include <wx/spinbutt.h>
|
|
#include <wx/spinctrl.h>
|
|
#include <wx/statbox.h>
|
|
#include <wx/stattext.h>
|
|
#include <wx/textctrl.h>
|
|
#include <wx/toolbar.h>
|
|
#include <wx/translation.h>
|
|
#include <wx/window.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(playerInfoSizer->GetStaticBox(), wxID_ANY);
|
|
gridSizer->Add(new DefaultCheckbox(playerInfoSizer->GetStaticBox(), ID_DefaultName, nameCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
|
|
gridSizer->Add(new wxStaticText(playerInfoSizer->GetStaticBox(), 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(playerInfoSizer->GetStaticBox(), wxID_ANY);
|
|
gridSizer->Add(new DefaultCheckbox(playerInfoSizer->GetStaticBox(), ID_DefaultCiv, civChoice), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
|
|
gridSizer->Add(new wxStaticText(playerInfoSizer->GetStaticBox(), 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(playerInfoSizer->GetStaticBox(), ID_PlayerColor);
|
|
gridSizer->Add(new DefaultCheckbox(playerInfoSizer->GetStaticBox(), ID_DefaultColor, colorButton), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
|
|
gridSizer->Add(new wxStaticText(playerInfoSizer->GetStaticBox(), 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(playerInfoSizer->GetStaticBox(), wxID_ANY);
|
|
gridSizer->Add(new DefaultCheckbox(playerInfoSizer->GetStaticBox(), ID_DefaultAI, aiChoice), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
|
|
gridSizer->Add(new wxStaticText(playerInfoSizer->GetStaticBox(), 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().Border(wxALL, 5));
|
|
sizer->Add(playerInfoSizer, wxSizerFlags().Expand().Border(wxTOP, 5));
|
|
}
|
|
|
|
{
|
|
/////////////////////////////////////////////////////////////////////////
|
|
// Resources
|
|
wxStaticBoxSizer* resourceSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Resources"));
|
|
wxFlexGridSizer* gridSizer = new wxFlexGridSizer(3, 5, 5);
|
|
gridSizer->AddGrowableCol(2);
|
|
|
|
wxSpinCtrl* foodCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerFood, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX);
|
|
gridSizer->Add(new DefaultCheckbox(resourceSizer->GetStaticBox(), ID_DefaultFood, foodCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
|
|
gridSizer->Add(new wxStaticText(resourceSizer->GetStaticBox(), 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(resourceSizer->GetStaticBox(), ID_PlayerWood, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX);
|
|
gridSizer->Add(new DefaultCheckbox(resourceSizer->GetStaticBox(), ID_DefaultWood, woodCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
|
|
gridSizer->Add(new wxStaticText(resourceSizer->GetStaticBox(), 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(resourceSizer->GetStaticBox(), ID_PlayerMetal, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX);
|
|
gridSizer->Add(new DefaultCheckbox(resourceSizer->GetStaticBox(), ID_DefaultMetal, metalCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
|
|
gridSizer->Add(new wxStaticText(resourceSizer->GetStaticBox(), 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(resourceSizer->GetStaticBox(), ID_PlayerStone, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX);
|
|
gridSizer->Add(new DefaultCheckbox(resourceSizer->GetStaticBox(), ID_DefaultStone, stoneCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
|
|
gridSizer->Add(new wxStaticText(resourceSizer->GetStaticBox(), 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(resourceSizer->GetStaticBox(), ID_PlayerPop, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX);
|
|
gridSizer->Add(new DefaultCheckbox(resourceSizer->GetStaticBox(), ID_DefaultPop, popCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
|
|
gridSizer->Add(new wxStaticText(resourceSizer->GetStaticBox(), 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().Border(wxALL, 5));
|
|
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(diplomacySizer->GetStaticBox(), wxID_ANY);
|
|
boxSizer->Add(new DefaultCheckbox(diplomacySizer->GetStaticBox(), ID_DefaultTeam, teamCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
|
|
boxSizer->AddSpacer(5);
|
|
boxSizer->Add(new wxStaticText(diplomacySizer->GetStaticBox(), 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().Expand().Border(wxALL, 5));
|
|
|
|
// TODO: possibly have advanced panel where each player's diplomacy can be set?
|
|
// Advanced panel
|
|
|
|
sizer->Add(diplomacySizer, wxSizerFlags().Expand().Border(wxTOP, 10));
|
|
}
|
|
|
|
{
|
|
/////////////////////////////////////////////////////////////////////////
|
|
// Camera
|
|
wxStaticBoxSizer* cameraSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Starting Camera"));
|
|
wxGridSizer* gridSizer = new wxGridSizer(3, 5, 5);
|
|
wxButton* cameraSet = new wxButton(cameraSizer->GetStaticBox(), ID_CameraSet, _("Set"), wxDefaultPosition, wxSize(48, -1));
|
|
gridSizer->Add(Tooltipped(cameraSet,
|
|
_("Set player camera to cameraSizer->GetStaticBox() view")), wxSizerFlags().Expand());
|
|
wxButton* cameraView = new wxButton(cameraSizer->GetStaticBox(), ID_CameraView, _("View"), wxDefaultPosition, wxSize(48, -1));
|
|
cameraView->Enable(false);
|
|
gridSizer->Add(Tooltipped(cameraView,
|
|
_("View the player camera")), wxSizerFlags().Expand());
|
|
wxButton* cameraClear = new wxButton(cameraSizer->GetStaticBox(), ID_CameraClear, _("Clear"), wxDefaultPosition, wxSize(48, -1));
|
|
cameraClear->Enable(false);
|
|
gridSizer->Add(Tooltipped(cameraClear,
|
|
_("Clear player camera")), wxSizerFlags().Expand());
|
|
cameraSizer->Add(gridSizer, wxSizerFlags().Expand().Border(wxALL, 5));
|
|
|
|
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<PlayerNotebookPage*>(GetPage(evt.GetSelection()))->OnDisplay();
|
|
}
|
|
evt.Skip();
|
|
}
|
|
|
|
private:
|
|
std::vector<PlayerNotebookPage*> 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<AtlasMessage::ObjectID> 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<PlayerPageControls> m_PlayerControls;
|
|
Observable<AtObj>& 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* topSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Player settings"));
|
|
wxStaticBox* topBox = topSizer->GetStaticBox();
|
|
SetSizer(topSizer);
|
|
|
|
wxBoxSizer* boxSizer = new wxBoxSizer(wxHORIZONTAL);
|
|
boxSizer->Add(new wxStaticText(topBox, wxID_ANY, _("Num players")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
|
|
boxSizer->AddSpacer(10);
|
|
wxSpinCtrl* numPlayersSpin = new wxSpinCtrl(topBox, ID_NumPlayers, wxEmptyString, wxDefaultPosition, wxSize(40, -1));
|
|
numPlayersSpin->SetValue(MAX_NUM_PLAYERS);
|
|
numPlayersSpin->SetRange(1, MAX_NUM_PLAYERS);
|
|
boxSizer->Add(numPlayersSpin);
|
|
topSizer->Add(boxSizer, wxSizerFlags().Expand().Proportion(0).Border(wxALL, 5));
|
|
m_Players = new PlayerNotebook(topBox);
|
|
topSizer->Add(m_Players, wxSizerFlags().Expand().Proportion(1).Border(wxALL, 5));
|
|
|
|
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<std::vector<std::wstring>> civData = *qryCiv.data;
|
|
for (const std::vector<std::wstring>& 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(_("<None>"), 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<wxStringClientData*>(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<wxStringClientData*>(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<wxStringClientData*>(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<wxStringClientData*>(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::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();
|
|
}
|
|
}
|