/* 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();