0ad/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Player/Player.cpp
Ralph Sennhauser fa9584fdc0
Fix some Atlas header includes
iwyu 0.26 now want's full declaration of TYPE in case of
std::vector<TYPE>, which is good as it allows the header to be
self standing.

There are some other changes suggested in part of the headers not being
updated when they should have been and some due to changes in iwyu
itself.

Ref: #8086
Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
2026-06-12 21:22:36 +02:00

1038 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/collpane.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
/*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, 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::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();