# Atlas updates: better unit ownership control; fixed bottom-bar display; fixed iterator problem in undo; renamed 'd' to 'msg' for consistency

This was SVN commit r3836.
This commit is contained in:
Ykkrosh 2006-05-04 02:44:03 +00:00
parent 37663d86fb
commit 9e74e3a077
25 changed files with 406 additions and 129 deletions

View File

@ -51,6 +51,9 @@ public:
// Set player ID of this unit // Set player ID of this unit
void SetPlayerID(int id); void SetPlayerID(int id);
// Get player ID of this unit
int GetPlayerID() { return m_PlayerID; }
int GetID() const { return m_ID; } int GetID() const { return m_ID; }
void SetID(int id) { m_ID = id; } void SetID(int id) { m_ID = id; }

View File

@ -0,0 +1,28 @@
#ifndef OBSERVABLE_H__
#define OBSERVABLE_H__
#include <boost/signals.hpp>
#include <boost/bind.hpp>
typedef boost::signals::connection ObservableConnection;
template <typename T> class Observable : public T
{
public:
template<typename C> ObservableConnection RegisterObserver(int order, void (C::*callback) (const T&), C* obj)
{
return m_Signal.connect(order, boost::bind(std::mem_fun(callback), obj, _1));
}
void RemoveObserver(ObservableConnection handle)
{
handle.disconnect();
}
void NotifyObservers()
{
m_Signal(*this);
}
private:
boost::signal<void (const T&)> m_Signal;
};
#endif // OBSERVABLE_H__

View File

@ -34,6 +34,8 @@
#include <boost/preprocessor/seq/for_each_i.hpp> #include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/tuple/elem.hpp> #include <boost/preprocessor/tuple/elem.hpp>
#include <boost/preprocessor/punctuation/comma_if.hpp> #include <boost/preprocessor/punctuation/comma_if.hpp>
#include <boost/signals.hpp>
#include <boost/bind.hpp>
// Nicer memory-leak detection: // Nicer memory-leak detection:
#ifdef _DEBUG #ifdef _DEBUG

View File

@ -36,13 +36,16 @@ protected:
if (event.GetSelection() != -1) if (event.GetSelection() != -1)
newPage = wxDynamicCast(GetPage(event.GetSelection()), Sidebar); newPage = wxDynamicCast(GetPage(event.GetSelection()), Sidebar);
if (oldPage)
oldPage->OnSwitchAway();
if (newPage) if (newPage)
newPage->OnSwitchTo(); newPage->OnSwitchTo();
if (m_Splitter->IsSplit()) if (m_Splitter->IsSplit())
{ {
wxWindow* bottom; wxWindow* bottom;
if (newPage && NULL != (bottom = newPage->GetBottomBar(m_Splitter))) if (newPage && NULL != (bottom = newPage->GetBottomBar()))
{ {
m_Splitter->ReplaceWindow(m_Splitter->GetWindow2(), bottom); m_Splitter->ReplaceWindow(m_Splitter->GetWindow2(), bottom);
} }
@ -54,7 +57,7 @@ protected:
else else
{ {
wxWindow* bottom; wxWindow* bottom;
if (newPage && NULL != (bottom = newPage->GetBottomBar(m_Splitter))) if (newPage && NULL != (bottom = newPage->GetBottomBar()))
{ {
m_Splitter->SplitHorizontally(m_Splitter->GetWindow1(), bottom); m_Splitter->SplitHorizontally(m_Splitter->GetWindow1(), bottom);
} }
@ -104,14 +107,24 @@ void SectionLayout::Build()
{ {
// TODO: wxWidgets bug (http://sourceforge.net/tracker/index.php?func=detail&aid=1298803&group_id=9863&atid=109863) // TODO: wxWidgets bug (http://sourceforge.net/tracker/index.php?func=detail&aid=1298803&group_id=9863&atid=109863)
// - pressing menu keys (e.g. alt+f) with notebook tab focussed causes application to freeze // - pressing menu keys (e.g. alt+f) with notebook tab focussed causes application to freeze
wxNotebook* sidebar = new SidebarNotebook(m_HorizSplitter, m_VertSplitter); SidebarNotebook* sidebarBook = new SidebarNotebook(m_HorizSplitter, m_VertSplitter);
sidebar->AddPage(new MapSidebar(sidebar), _("Map"), false); Sidebar* sidebar;
sidebar->AddPage(new TerrainSidebar(sidebar), _("Terrain"), false);
sidebar->AddPage(new ObjectSidebar(sidebar), _("Object"), false); #define ADD_SIDEBAR(classname, label) \
sidebar = new classname(sidebarBook, m_VertSplitter); \
if (sidebar->GetBottomBar()) \
sidebar->GetBottomBar()->Show(false); \
sidebarBook->AddPage(sidebar, _(label));
ADD_SIDEBAR(MapSidebar, "Map");
ADD_SIDEBAR(TerrainSidebar, "Terrain");
ADD_SIDEBAR(ObjectSidebar, "Object");
#undef ADD_SIDEBAR
m_VertSplitter->SetDefaultSashPosition(-165); m_VertSplitter->SetDefaultSashPosition(-165);
m_VertSplitter->Initialize(m_Canvas); m_VertSplitter->Initialize(m_Canvas);
m_HorizSplitter->SetDefaultSashPosition(200); m_HorizSplitter->SetDefaultSashPosition(200);
m_HorizSplitter->SplitVertically(sidebar, m_VertSplitter); m_HorizSplitter->SplitVertically(sidebarBook, m_VertSplitter);
} }

View File

@ -4,23 +4,27 @@
IMPLEMENT_DYNAMIC_CLASS(Sidebar, wxPanel) IMPLEMENT_DYNAMIC_CLASS(Sidebar, wxPanel)
Sidebar::Sidebar(wxWindow* parent) Sidebar::Sidebar(wxWindow* sidebarContainer, wxWindow* bottomBarContainer)
: wxPanel(parent), m_AlreadyDisplayed(false) : wxPanel(sidebarContainer), m_BottomBar(NULL), m_AlreadyDisplayed(false)
{ {
m_MainSizer = new wxBoxSizer(wxVERTICAL); m_MainSizer = new wxBoxSizer(wxVERTICAL);
SetSizer(m_MainSizer); SetSizer(m_MainSizer);
} }
wxWindow* Sidebar::GetBottomBar(wxWindow* WXUNUSED(parent)) void Sidebar::OnSwitchAway()
{ {
return NULL; if (m_BottomBar)
m_BottomBar->Show(false);
} }
void Sidebar::OnSwitchTo() void Sidebar::OnSwitchTo()
{ {
if (m_AlreadyDisplayed) if (! m_AlreadyDisplayed)
return; {
m_AlreadyDisplayed = true; m_AlreadyDisplayed = true;
OnFirstDisplay(); OnFirstDisplay();
} }
if (m_BottomBar)
m_BottomBar->Show(true);
}

View File

@ -7,20 +7,21 @@ class Sidebar : public wxPanel
public: public:
Sidebar() {} Sidebar() {}
Sidebar(wxWindow* parent); Sidebar(wxWindow* sidebarContainer, wxWindow* bottomBarContainer);
void OnSwitchAway();
void OnSwitchTo(); void OnSwitchTo();
virtual wxWindow* GetBottomBar(wxWindow* parent); wxWindow* GetBottomBar() { return m_BottomBar; }
// called whenever the bottom bar is made visible; should usually be
// lazily constructed, then cached forever (to maximise responsiveness)
protected: protected:
wxSizer* m_MainSizer; // vertical box sizer, used by most sidebars wxSizer* m_MainSizer; // vertical box sizer, used by most sidebars
wxWindow* m_BottomBar; // window that goes at the bottom of the screen; may be NULL
virtual void OnFirstDisplay() {} virtual void OnFirstDisplay() {}
// should be overridden when sidebars need to do expensive construction, // should be overridden when sidebars need to do expensive construction,
// so it can be delayed until it is required // so it can be delayed until it is required;
private: private:
bool m_AlreadyDisplayed; bool m_AlreadyDisplayed;

View File

@ -69,8 +69,8 @@ static void GenerateRMS(void* data)
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
MapSidebar::MapSidebar(wxWindow* parent) MapSidebar::MapSidebar(wxWindow* sidebarContainer, wxWindow* bottomBarContainer)
: Sidebar(parent) : Sidebar(sidebarContainer, bottomBarContainer)
{ {
// TODO: Less ugliness // TODO: Less ugliness
// TODO: Intercept arrow keys and send them to the GL window // TODO: Intercept arrow keys and send them to the GL window

View File

@ -3,5 +3,5 @@
class MapSidebar : public Sidebar class MapSidebar : public Sidebar
{ {
public: public:
MapSidebar(wxWindow* parent); MapSidebar(wxWindow* sidebarContainer, wxWindow* bottomBarContainer);
}; };

View File

@ -5,7 +5,8 @@
#include "Buttons/ActionButton.h" #include "Buttons/ActionButton.h"
#include "Buttons/ToolButton.h" #include "Buttons/ToolButton.h"
#include "ScenarioEditor/Tools/Common/Tools.h" #include "ScenarioEditor/Tools/Common/Tools.h"
#include "ScenarioEditor/Tools/Common/UnitSettings.h" #include "ScenarioEditor/Tools/Common/ObjectSettings.h"
#include "ScenarioEditor/Tools/Common/MiscState.h"
#include "GameInterface/Messages.h" #include "GameInterface/Messages.h"
@ -62,8 +63,8 @@ struct ObjectSidebarImpl
std::vector<AtlasMessage::sObjectsListItem> m_Objects; std::vector<AtlasMessage::sObjectsListItem> m_Objects;
}; };
ObjectSidebar::ObjectSidebar(wxWindow* parent) ObjectSidebar::ObjectSidebar(wxWindow* sidebarContainer, wxWindow* bottomBarContainer)
: Sidebar(parent), p(new ObjectSidebarImpl()) : Sidebar(sidebarContainer, bottomBarContainer), p(new ObjectSidebarImpl())
{ {
wxArrayString strings; wxArrayString strings;
strings.Add(_("Entities")); strings.Add(_("Entities"));
@ -72,6 +73,8 @@ ObjectSidebar::ObjectSidebar(wxWindow* parent)
p->m_ObjectListBox = new ObjectSelectListBox(this); p->m_ObjectListBox = new ObjectSelectListBox(this);
m_MainSizer->Add(p->m_ObjectListBox, wxSizerFlags().Proportion(1).Expand()); m_MainSizer->Add(p->m_ObjectListBox, wxSizerFlags().Proportion(1).Expand());
m_BottomBar = new ObjectBottomBar(bottomBarContainer);
} }
ObjectSidebar::~ObjectSidebar() ObjectSidebar::~ObjectSidebar()
@ -79,15 +82,6 @@ ObjectSidebar::~ObjectSidebar()
delete p; delete p;
} }
wxWindow* ObjectSidebar::GetBottomBar(wxWindow* parent)
{
if (p->m_BottomBar)
return p->m_BottomBar;
p->m_BottomBar = new ObjectBottomBar(parent);
return p->m_BottomBar;
}
void ObjectSidebar::OnFirstDisplay() void ObjectSidebar::OnFirstDisplay()
{ {
AtlasMessage::qGetObjectsList qry; AtlasMessage::qGetObjectsList qry;
@ -118,15 +112,27 @@ class PlayerComboBox : public wxComboBox
{ {
public: public:
PlayerComboBox(wxWindow* parent, wxArrayString& choices) PlayerComboBox(wxWindow* parent, wxArrayString& choices)
: wxComboBox(parent, -1, choices[g_UnitSettings.GetPlayerID()], wxDefaultPosition, wxDefaultSize, choices, wxCB_READONLY) : wxComboBox(parent, -1, choices[g_ObjectSettings.GetPlayerID()], wxDefaultPosition, wxDefaultSize, choices, wxCB_READONLY)
{ {
m_Conn = g_SelectedObjects.RegisterObserver(1, &PlayerComboBox::OnSelectionChange, this);
}
~PlayerComboBox()
{
g_SelectedObjects.RemoveObserver(m_Conn);
} }
private: private:
ObservableConnection m_Conn;
void OnSelectionChange(const std::vector<AtlasMessage::ObjectID>& selection)
{
SetSelection(g_ObjectSettings.GetPlayerID());
}
void OnSelect(wxCommandEvent& evt) void OnSelect(wxCommandEvent& evt)
{ {
g_UnitSettings.SetPlayerID(evt.GetInt()); g_ObjectSettings.SetPlayerID(evt.GetInt());
} }
DECLARE_EVENT_TABLE(); DECLARE_EVENT_TABLE();
@ -135,6 +141,7 @@ BEGIN_EVENT_TABLE(PlayerComboBox, wxComboBox)
EVT_COMBOBOX(wxID_ANY, OnSelect) EVT_COMBOBOX(wxID_ANY, OnSelect)
END_EVENT_TABLE(); END_EVENT_TABLE();
ObjectBottomBar::ObjectBottomBar(wxWindow* parent) ObjectBottomBar::ObjectBottomBar(wxWindow* parent)
: wxPanel(parent, wxID_ANY) : wxPanel(parent, wxID_ANY)
{ {
@ -153,5 +160,6 @@ ObjectBottomBar::ObjectBottomBar(wxWindow* parent)
players.Add(_("Player 8")); players.Add(_("Player 8"));
wxComboBox* playerSelect = new PlayerComboBox(this, players); wxComboBox* playerSelect = new PlayerComboBox(this, players);
sizer->Add(playerSelect);
SetSizer(sizer); SetSizer(sizer);
} }

View File

@ -4,9 +4,8 @@ struct ObjectSidebarImpl;
class ObjectSidebar : public Sidebar class ObjectSidebar : public Sidebar
{ {
public: public:
ObjectSidebar(wxWindow* parent); ObjectSidebar(wxWindow* sidebarContainer, wxWindow* bottomBarContainer);
~ObjectSidebar(); ~ObjectSidebar();
wxWindow* GetBottomBar(wxWindow* parent);
void SetObjectFilter(int type); void SetObjectFilter(int type);
protected: protected:

View File

@ -13,8 +13,20 @@
#include "wx/spinctrl.h" #include "wx/spinctrl.h"
TerrainSidebar::TerrainSidebar(wxWindow* parent) class TextureNotebook;
: Sidebar(parent), m_BottomBar(NULL)
class TerrainBottomBar : public wxPanel
{
public:
TerrainBottomBar(wxWindow* parent);
void LoadTerrain();
private:
TextureNotebook* m_Textures;
};
TerrainSidebar::TerrainSidebar(wxWindow* sidebarContainer, wxWindow* bottomBarContainer)
: Sidebar(sidebarContainer, bottomBarContainer)
{ {
// TODO: Less ugliness // TODO: Less ugliness
@ -36,15 +48,12 @@ TerrainSidebar::TerrainSidebar(wxWindow* parent)
m_MainSizer->Add(sizer); m_MainSizer->Add(sizer);
} }
m_BottomBar = new TerrainBottomBar(bottomBarContainer);
} }
wxWindow* TerrainSidebar::GetBottomBar(wxWindow* parent) void TerrainSidebar::OnFirstDisplay()
{ {
if (m_BottomBar) static_cast<TerrainBottomBar*>(m_BottomBar)->LoadTerrain();
return m_BottomBar;
m_BottomBar = new TerrainBottomBar(parent);
return m_BottomBar;
} }
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@ -145,6 +154,13 @@ public:
TextureNotebook(wxWindow *parent) TextureNotebook(wxWindow *parent)
: wxNotebook(parent, wxID_ANY/*, wxDefaultPosition, wxDefaultSize, wxNB_FIXEDWIDTH*/) : wxNotebook(parent, wxID_ANY/*, wxDefaultPosition, wxDefaultSize, wxNB_FIXEDWIDTH*/)
{ {
}
void LoadTerrain()
{
DeleteAllPages();
m_TerrainGroups.Clear();
// Get the list of terrain groups from the engine // Get the list of terrain groups from the engine
AtlasMessage::qGetTerrainGroups qry; AtlasMessage::qGetTerrainGroups qry;
qry.Post(); qry.Post();
@ -164,7 +180,7 @@ public:
protected: protected:
void OnPageChanged(wxNotebookEvent& event) void OnPageChanged(wxNotebookEvent& event)
{ {
if (event.GetSelection() != -1) if (event.GetSelection() >= 0 && event.GetSelection() < (int)GetPageCount())
{ {
static_cast<TextureNotebookPage*>(GetPage(event.GetSelection()))->OnDisplay(); static_cast<TextureNotebookPage*>(GetPage(event.GetSelection()))->OnDisplay();
} }
@ -186,7 +202,12 @@ TerrainBottomBar::TerrainBottomBar(wxWindow* parent)
: wxPanel(parent, wxID_ANY) : wxPanel(parent, wxID_ANY)
{ {
wxSizer* sizer = new wxBoxSizer(wxVERTICAL); wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
wxNotebook* notebook = new TextureNotebook(this); m_Textures = new TextureNotebook(this);
sizer->Add(notebook, wxSizerFlags().Expand().Proportion(1)); sizer->Add(m_Textures, wxSizerFlags().Expand().Proportion(1));
SetSizer(sizer); SetSizer(sizer);
} }
void TerrainBottomBar::LoadTerrain()
{
m_Textures->LoadTerrain();
}

View File

@ -3,16 +3,8 @@
class TerrainSidebar : public Sidebar class TerrainSidebar : public Sidebar
{ {
public: public:
TerrainSidebar(wxWindow* parent); TerrainSidebar(wxWindow* sidebarContainer, wxWindow* bottomBarContainer);
wxWindow* GetBottomBar(wxWindow* parent);
private: protected:
wxWindow* m_BottomBar; virtual void OnFirstDisplay();
};
class TerrainBottomBar : public wxPanel
{
public:
TerrainBottomBar(wxWindow* parent);
private:
}; };

View File

@ -3,3 +3,5 @@
#include "MiscState.h" #include "MiscState.h"
wxString g_SelectedTexture = _T("grass1_spring"); wxString g_SelectedTexture = _T("grass1_spring");
Observable<std::vector<AtlasMessage::ObjectID> > g_SelectedObjects;

View File

@ -1,6 +1,18 @@
#ifndef MISCSTATE_H__ #ifndef MISCSTATE_H__
#define MISCSTATE_H__ #define MISCSTATE_H__
#include "General/Observable.h"
namespace AtlasMessage
{
typedef int ObjectID;
}
extern wxString g_SelectedTexture; extern wxString g_SelectedTexture;
// Observer order:
// 0 = g_UnitSettings
// 1 = things that want to access g_UnitSettings
extern Observable<std::vector<AtlasMessage::ObjectID> > g_SelectedObjects;
#endif // MISCSTATE_H__ #endif // MISCSTATE_H__

View File

@ -0,0 +1,77 @@
#include "stdafx.h"
#include "ObjectSettings.h"
#include "GameInterface/Messages.h"
#include "ScenarioEditor/Tools/Common/Tools.h"
ObjectSettings g_ObjectSettings;
ObjectSettings::ObjectSettings()
: m_PlayerID(0)
{
m_Conn = g_SelectedObjects.RegisterObserver(0, &ObjectSettings::OnSelectionChange, this);
}
ObjectSettings::~ObjectSettings()
{
m_Conn.disconnect();
}
int ObjectSettings::GetPlayerID()
{
return m_PlayerID;
}
void ObjectSettings::SetPlayerID(int playerID)
{
m_PlayerID = playerID;
PostToGame();
}
void ObjectSettings::SetActorSelections(const std::set<wxString>& selections)
{
m_ActorSelections = selections;
PostToGame();
}
AtlasMessage::sObjectSettings ObjectSettings::GetSettings() const
{
AtlasMessage::sObjectSettings settings;
settings.player = m_PlayerID;
std::vector<std::wstring> selections;
for (std::set<wxString>::const_iterator it = m_ActorSelections.begin(); it != m_ActorSelections.end(); ++it)
selections.push_back(it->c_str());
settings.selections = selections;
return settings;
}
void ObjectSettings::OnSelectionChange(const std::vector<AtlasMessage::ObjectID>& selection)
{
// TODO: what would be the sensible action if nothing's selected?
// and if multiple objects are selected?
if (selection.empty())
return;
AtlasMessage::qGetObjectSettings qry (selection[0]);
qry.Post();
m_PlayerID = qry.settings->player;
std::vector<std::wstring> selections = *qry.settings->selections;
m_ActorSelections.clear();
for (std::vector<std::wstring>::iterator it = selections.begin(); it != selections.end(); ++it)
m_ActorSelections.insert(it->c_str());
}
void ObjectSettings::PostToGame()
{
if (g_SelectedObjects.empty())
return;
POST_COMMAND(SetObjectSettings, (g_SelectedObjects[0], GetSettings()));
}

View File

@ -0,0 +1,47 @@
#ifndef ObjectSettings_H__
#define ObjectSettings_H__
#include <set>
#include "ScenarioEditor/Tools/Common/MiscState.h"
namespace AtlasMessage
{
struct sObjectSettings;
}
// Various settings to be applied to newly created units, or to the currently
// selected unit. If a unit is selected or being previewed, it should match
// these settings.
class ObjectSettings
{
public:
ObjectSettings();
~ObjectSettings();
int GetPlayerID();
void SetPlayerID(int playerID);
void SetActorSelections(const std::set<wxString>& selections);
AtlasMessage::sObjectSettings GetSettings() const;
private:
// 0 = gaia, 1..inf = normal players
int m_PlayerID;
// Set of user-chosen actor selections, potentially a superset of any single
// actor's possible variants (since it doesn't get reset if you select
// a new actor, and will accumulate variant names)
std::set<wxString> m_ActorSelections;
// Observe changes to unit selection
ObservableConnection m_Conn;
void OnSelectionChange(const std::vector<AtlasMessage::ObjectID>& selection);
// Transfer current settings to the currently selected unit (if any)
void PostToGame();
};
extern ObjectSettings g_ObjectSettings;
#endif // ObjectSettings_H__

View File

@ -3,7 +3,7 @@
#include "Common/Tools.h" #include "Common/Tools.h"
#include "Common/Brushes.h" #include "Common/Brushes.h"
#include "Common/MiscState.h" #include "Common/MiscState.h"
#include "Common/UnitSettings.h" #include "Common/ObjectSettings.h"
#include "GameInterface/Messages.h" #include "GameInterface/Messages.h"
using AtlasMessage::Position; using AtlasMessage::Position;
@ -30,9 +30,9 @@ public:
+ (m_ScreenPos.type1.y-m_Target.type1.y)*(m_ScreenPos.type1.y-m_Target.type1.y); + (m_ScreenPos.type1.y-m_Target.type1.y)*(m_ScreenPos.type1.y-m_Target.type1.y);
bool useTarget = (dragDistSq >= 16*16); bool useTarget = (dragDistSq >= 16*16);
if (preview) if (preview)
POST_MESSAGE(ObjectPreview, (m_ObjectID.c_str(), g_UnitSettings.GetSettings(), m_ObjPos, useTarget, m_Target, g_DefaultAngle)); POST_MESSAGE(ObjectPreview, (m_ObjectID.c_str(), g_ObjectSettings.GetSettings(), m_ObjPos, useTarget, m_Target, g_DefaultAngle));
else else
POST_COMMAND(CreateObject, (m_ObjectID.c_str(), g_UnitSettings.GetSettings(), m_ObjPos, useTarget, m_Target, g_DefaultAngle)); POST_COMMAND(CreateObject, (m_ObjectID.c_str(), g_ObjectSettings.GetSettings(), m_ObjPos, useTarget, m_Target, g_DefaultAngle));
} }
virtual void Init(void* initData) virtual void Init(void* initData)

View File

@ -11,7 +11,6 @@ class TransformObject : public StateDrivenTool<TransformObject>
{ {
DECLARE_DYNAMIC_CLASS(TransformObject); DECLARE_DYNAMIC_CLASS(TransformObject);
std::vector<AtlasMessage::ObjectID> m_Selection;
int m_dx, m_dy; int m_dx, m_dy;
public: public:
@ -22,8 +21,9 @@ public:
void OnDisable() void OnDisable()
{ {
m_Selection.clear(); g_SelectedObjects.clear();
POST_MESSAGE(SetSelectionPreview, (m_Selection)); g_SelectedObjects.NotifyObservers();
POST_MESSAGE(SetSelectionPreview, (g_SelectedObjects));
} }
@ -36,25 +36,26 @@ public:
if (evt.LeftDown()) if (evt.LeftDown())
{ {
// TODO: multiple selection // TODO: multiple selection
AtlasMessage::qSelectObject qry(Position(evt.GetPosition())); AtlasMessage::qPickObject qry(Position(evt.GetPosition()));
qry.Post(); qry.Post();
obj->m_Selection.clear(); g_SelectedObjects.clear();
if (AtlasMessage::ObjectIDIsValid(qry.id)) if (AtlasMessage::ObjectIDIsValid(qry.id))
{ {
obj->m_Selection.push_back(qry.id); g_SelectedObjects.push_back(qry.id);
obj->m_dx = qry.offsetx; obj->m_dx = qry.offsetx;
obj->m_dy = qry.offsety; obj->m_dy = qry.offsety;
SET_STATE(Dragging); SET_STATE(Dragging);
} }
POST_MESSAGE(SetSelectionPreview, (obj->m_Selection)); g_SelectedObjects.NotifyObservers();
POST_MESSAGE(SetSelectionPreview, (g_SelectedObjects));
ScenarioEditor::GetCommandProc().FinaliseLastCommand(); ScenarioEditor::GetCommandProc().FinaliseLastCommand();
return true; return true;
} }
else if (evt.Dragging() && evt.RightIsDown() || evt.RightDown()) else if (evt.Dragging() && evt.RightIsDown() || evt.RightDown())
{ {
Position pos (evt.GetPosition()); Position pos (evt.GetPosition());
for (size_t i = 0; i < obj->m_Selection.size(); ++i) for (size_t i = 0; i < g_SelectedObjects.size(); ++i)
POST_COMMAND(RotateObject, (obj->m_Selection[i], true, pos, 0.f)); POST_COMMAND(RotateObject, (g_SelectedObjects[i], true, pos, 0.f));
return true; return true;
} }
else else
@ -65,10 +66,11 @@ public:
{ {
if (type == KEY_CHAR && evt.GetKeyCode() == WXK_DELETE) if (type == KEY_CHAR && evt.GetKeyCode() == WXK_DELETE)
{ {
for (size_t i = 0; i < obj->m_Selection.size(); ++i) for (size_t i = 0; i < g_SelectedObjects.size(); ++i)
POST_COMMAND(DeleteObject, (obj->m_Selection[i])); POST_COMMAND(DeleteObject, (g_SelectedObjects[i]));
obj->m_Selection.clear(); g_SelectedObjects.clear();
POST_MESSAGE(SetSelectionPreview, (obj->m_Selection)); g_SelectedObjects.NotifyObservers();
POST_MESSAGE(SetSelectionPreview, (g_SelectedObjects));
return true; return true;
} }
else else
@ -89,8 +91,8 @@ public:
else if (evt.Dragging()) else if (evt.Dragging())
{ {
Position pos (evt.GetPosition() + wxPoint(obj->m_dx, obj->m_dy)); Position pos (evt.GetPosition() + wxPoint(obj->m_dx, obj->m_dy));
for (size_t i = 0; i < obj->m_Selection.size(); ++i) for (size_t i = 0; i < g_SelectedObjects.size(); ++i)
POST_COMMAND(MoveObject, (obj->m_Selection[i], pos)); POST_COMMAND(MoveObject, (g_SelectedObjects[i], pos));
return true; return true;
} }
else else

View File

@ -8,15 +8,6 @@
template<typename T> T next(T x) { T t = x; return ++t; } template<typename T> T next(T x) { T t = x; return ++t; }
template<typename T, typename I> void delete_erase(T list, I first, I last)
{
while (first != last)
{
delete *first;
first = list.erase(first);
}
}
template<typename T> void delete_fn(T* v) { delete v; } template<typename T> void delete_fn(T* v) { delete v; }
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@ -63,7 +54,12 @@ void CommandProc::Destroy()
void CommandProc::Submit(Command* cmd) void CommandProc::Submit(Command* cmd)
{ {
delete_erase(m_Commands, next(m_CurrentCommand), m_Commands.end()); // If some commands have been undone at the time we insert this new one,
// delete and remove them all.
std::for_each(next(m_CurrentCommand), m_Commands.end(), delete_fn<Command>);
m_Commands.erase(next(m_CurrentCommand), m_Commands.end());
assert(next(m_CurrentCommand) == m_Commands.end());
m_CurrentCommand = m_Commands.insert(next(m_CurrentCommand), cmd); m_CurrentCommand = m_Commands.insert(next(m_CurrentCommand), cmd);
(*m_CurrentCommand)->Do(); (*m_CurrentCommand)->Do();

View File

@ -36,6 +36,8 @@ public:
private: private:
std::list<Command*> m_Commands; std::list<Command*> m_Commands;
typedef std::list<Command*>::iterator cmdIt; typedef std::list<Command*>::iterator cmdIt;
// The 'current' command is the latest one which has been executed
// (ignoring any that have been undone)
cmdIt m_CurrentCommand; cmdIt m_CurrentCommand;
}; };
@ -49,7 +51,7 @@ struct DataCommand : public Command // so commands can optionally override (De|C
{ {
void Destruct() {}; void Destruct() {};
void Construct() {}; void Construct() {};
// MergeWithSelf should be overriden by commands, and implemented // MergeWithSelf should be overridden by commands, and implemented
// to update 'prev' to include the effects of 'this' // to update 'prev' to include the effects of 'this'
void MergeWithSelf(void*) { debug_warn("MergeWithSelf unimplemented in some command"); } void MergeWithSelf(void*) { debug_warn("MergeWithSelf unimplemented in some command"); }
}; };
@ -57,10 +59,10 @@ struct DataCommand : public Command // so commands can optionally override (De|C
#define BEGIN_COMMAND(t) \ #define BEGIN_COMMAND(t) \
class c##t : public DataCommand \ class c##t : public DataCommand \
{ \ { \
d##t* d; \ d##t* msg; \
public: \ public: \
c##t(d##t* data) : d(data) { Construct(); } \ c##t(d##t* data) : msg(data) { Construct(); } \
~c##t() { Destruct(); AtlasMessage::ShareableDelete(d); /* d was allocated by mDoCommand() */ } \ ~c##t() { Destruct(); AtlasMessage::ShareableDelete(msg); /* msg was allocated by mDoCommand() */ } \
static Command* Create(const void* data) { return new c##t ((d##t*)data); } \ static Command* Create(const void* data) { return new c##t ((d##t*)data); } \
virtual void Merge(Command* prev) { MergeWithSelf((c##t*)prev); } \ virtual void Merge(Command* prev) { MergeWithSelf((c##t*)prev); } \
virtual const char* GetType() const { return #t; } virtual const char* GetType() const { return #t; }

View File

@ -91,12 +91,12 @@ BEGIN_COMMAND(AlterElevation)
void Do() void Do()
{ {
int amount = (int)d->amount; int amount = (int)msg->amount;
// If the framerate is very high, 'amount' is often very // If the framerate is very high, 'amount' is often very
// small (even zero) so the integer truncation is significant // small (even zero) so the integer truncation is significant
static float roundingError = 0.0; static float roundingError = 0.0;
roundingError += d->amount - (float)amount; roundingError += msg->amount - (float)amount;
if (roundingError >= 1.f) if (roundingError >= 1.f)
{ {
amount += (int)roundingError; amount += (int)roundingError;
@ -104,7 +104,7 @@ BEGIN_COMMAND(AlterElevation)
} }
static CVector3D previousPosition; static CVector3D previousPosition;
d->pos->GetWorldSpace(g_CurrentBrush.m_Centre, previousPosition); msg->pos->GetWorldSpace(g_CurrentBrush.m_Centre, previousPosition);
previousPosition = g_CurrentBrush.m_Centre; previousPosition = g_CurrentBrush.m_Centre;
int x0, y0; int x0, y0;
@ -160,10 +160,10 @@ BEGIN_COMMAND(FlattenElevation)
void Do() void Do()
{ {
int amount = (int)d->amount; int amount = (int)msg->amount;
static CVector3D previousPosition; static CVector3D previousPosition;
d->pos->GetWorldSpace(g_CurrentBrush.m_Centre, previousPosition); msg->pos->GetWorldSpace(g_CurrentBrush.m_Centre, previousPosition);
previousPosition = g_CurrentBrush.m_Centre; previousPosition = g_CurrentBrush.m_Centre;
int xc, yc; int xc, yc;

View File

@ -103,6 +103,54 @@ MESSAGEHANDLER(SetSelectionPreview)
g_Selection = *msg->ids; g_Selection = *msg->ids;
} }
QUERYHANDLER(GetObjectSettings)
{
CUnit* unit = g_UnitMan.FindByID(msg->id);
if (! unit) return;
sObjectSettings settings;
settings.player = unit->GetPlayerID();
// TODO: actor variation
msg->settings = settings;
}
BEGIN_COMMAND(SetObjectSettings)
int m_PlayerOld, m_PlayerNew;
void Do()
{
CUnit* unit = g_UnitMan.FindByID(msg->id);
if (! unit) return;
sObjectSettings settings = msg->settings;
m_PlayerOld = unit->GetPlayerID();
m_PlayerNew = settings.player;
// TODO: actor variations
Redo();
}
void Redo()
{
CUnit* unit = g_UnitMan.FindByID(msg->id);
if (! unit) return;
unit->SetPlayerID(m_PlayerNew);
}
void Undo()
{
CUnit* unit = g_UnitMan.FindByID(msg->id);
if (! unit) return;
unit->SetPlayerID(m_PlayerOld);
}
END_COMMAND(SetObjectSettings);
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@ -234,21 +282,27 @@ BEGIN_COMMAND(CreateObject)
void Do() void Do()
{ {
m_Pos = GetUnitPos(d->pos); // Calculate the position/orientation to create this unit with
m_Player = d->settings->player;
if (d->usetarget) m_Pos = GetUnitPos(msg->pos);
if (msg->usetarget)
{ {
// Aim from m_Pos towards msg->target
CVector3D target; CVector3D target;
d->target->GetWorldSpace(target, m_Pos.Y); msg->target->GetWorldSpace(target, m_Pos.Y);
CVector2D dir(target.X-m_Pos.X, target.Z-m_Pos.Z); CVector2D dir(target.X-m_Pos.X, target.Z-m_Pos.Z);
m_Angle = atan2(dir.x, dir.y); m_Angle = atan2(dir.x, dir.y);
} }
else else
{ {
m_Angle = d->angle; m_Angle = msg->angle;
} }
// TODO: variations too
m_Player = msg->settings->player;
// Get a new ID, for future reference to this unit
m_ID = g_UnitMan.GetNewID(); m_ID = g_UnitMan.GetNewID();
Redo(); Redo();
@ -258,7 +312,7 @@ BEGIN_COMMAND(CreateObject)
{ {
bool isEntity; bool isEntity;
CStrW name; CStrW name;
if (ParseObjectName(*d->id, isEntity, name)) if (ParseObjectName(*msg->id, isEntity, name))
{ {
std::set<CStrW> selections; std::set<CStrW> selections;
@ -309,6 +363,8 @@ BEGIN_COMMAND(CreateObject)
m._31 = s; m._32 = 0.0f; m._33 = -c; m._34 = m_Pos.Z; m._31 = s; m._32 = 0.0f; m._33 = -c; m._34 = m_Pos.Z;
m._41 = 0.0f; m._42 = 0.0f; m._43 = 0.0f; m._44 = 1.0f; m._41 = 0.0f; m._42 = 0.0f; m._43 = 0.0f; m._44 = 1.0f;
unit->GetModel()->SetTransform(m); unit->GetModel()->SetTransform(m);
unit->SetPlayerID(m_Player);
} }
} }
} }
@ -332,7 +388,7 @@ BEGIN_COMMAND(CreateObject)
END_COMMAND(CreateObject) END_COMMAND(CreateObject)
QUERYHANDLER(SelectObject) QUERYHANDLER(PickObject)
{ {
float x, y; float x, y;
msg->pos->GetScreenSpace(x, y); msg->pos->GetScreenSpace(x, y);
@ -369,7 +425,7 @@ BEGIN_COMMAND(MoveObject)
void Do() void Do()
{ {
CUnit* unit = g_UnitMan.FindByID(d->id); CUnit* unit = g_UnitMan.FindByID(msg->id);
if (! unit) return; if (! unit) return;
if (unit->GetEntity()) if (unit->GetEntity())
@ -382,14 +438,14 @@ BEGIN_COMMAND(MoveObject)
m_PosOld = m.GetTranslation(); m_PosOld = m.GetTranslation();
} }
m_PosNew = GetUnitPos(d->pos); m_PosNew = GetUnitPos(msg->pos);
SetPos(m_PosNew); SetPos(m_PosNew);
} }
void SetPos(CVector3D& pos) void SetPos(CVector3D& pos)
{ {
CUnit* unit = g_UnitMan.FindByID(d->id); CUnit* unit = g_UnitMan.FindByID(msg->id);
if (! unit) return; if (! unit) return;
if (unit->GetEntity()) if (unit->GetEntity())
@ -430,23 +486,23 @@ BEGIN_COMMAND(RotateObject)
void Do() void Do()
{ {
CUnit* unit = g_UnitMan.FindByID(d->id); CUnit* unit = g_UnitMan.FindByID(msg->id);
if (! unit) return; if (! unit) return;
if (unit->GetEntity()) if (unit->GetEntity())
{ {
m_AngleOld = unit->GetEntity()->m_orientation; m_AngleOld = unit->GetEntity()->m_orientation;
if (d->usetarget) if (msg->usetarget)
{ {
CVector3D& pos = unit->GetEntity()->m_position; CVector3D& pos = unit->GetEntity()->m_position;
CVector3D target; CVector3D target;
d->target->GetWorldSpace(target, pos.Y); msg->target->GetWorldSpace(target, pos.Y);
CVector2D dir(target.X-pos.X, target.Z-pos.Z); CVector2D dir(target.X-pos.X, target.Z-pos.Z);
m_AngleNew = atan2(dir.x, dir.y); m_AngleNew = atan2(dir.x, dir.y);
} }
else else
{ {
m_AngleNew = d->angle; m_AngleNew = msg->angle;
} }
} }
else else
@ -456,10 +512,10 @@ BEGIN_COMMAND(RotateObject)
CVector3D pos = unit->GetModel()->GetTransform().GetTranslation(); CVector3D pos = unit->GetModel()->GetTransform().GetTranslation();
float s, c; float s, c;
if (d->usetarget) if (msg->usetarget)
{ {
CVector3D target; CVector3D target;
d->target->GetWorldSpace(target, pos.Y); msg->target->GetWorldSpace(target, pos.Y);
CVector2D dir(target.X-pos.X, target.Z-pos.Z); CVector2D dir(target.X-pos.X, target.Z-pos.Z);
dir = dir.normalize(); dir = dir.normalize();
s = dir.x; s = dir.x;
@ -467,8 +523,8 @@ BEGIN_COMMAND(RotateObject)
} }
else else
{ {
s = sinf(d->angle); s = sinf(msg->angle);
c = cosf(d->angle); c = cosf(msg->angle);
} }
CMatrix3D& m = m_TransformNew; CMatrix3D& m = m_TransformNew;
m._11 = -c; m._12 = 0.0f; m._13 = -s; m._14 = pos.X; m._11 = -c; m._12 = 0.0f; m._13 = -s; m._14 = pos.X;
@ -482,7 +538,7 @@ BEGIN_COMMAND(RotateObject)
void SetAngle(float angle, CMatrix3D& transform) void SetAngle(float angle, CMatrix3D& transform)
{ {
CUnit* unit = g_UnitMan.FindByID(d->id); CUnit* unit = g_UnitMan.FindByID(msg->id);
if (! unit) return; if (! unit) return;
if (unit->GetEntity()) if (unit->GetEntity())
@ -542,7 +598,7 @@ BEGIN_COMMAND(DeleteObject)
void Redo() void Redo()
{ {
CUnit* unit = g_UnitMan.FindByID(d->id); CUnit* unit = g_UnitMan.FindByID(msg->id);
if (! unit) return; if (! unit) return;
if (unit->GetEntity()) if (unit->GetEntity())

View File

@ -165,12 +165,12 @@ BEGIN_COMMAND(PaintTerrain)
void Do() void Do()
{ {
d->pos->GetWorldSpace(g_CurrentBrush.m_Centre); msg->pos->GetWorldSpace(g_CurrentBrush.m_Centre);
int x0, y0; int x0, y0;
g_CurrentBrush.GetBottomLeft(x0, y0); g_CurrentBrush.GetBottomLeft(x0, y0);
CTextureEntry* texentry = g_TexMan.FindTexture(CStrW(*d->texture)); CTextureEntry* texentry = g_TexMan.FindTexture(CStrW(*msg->texture));
if (! texentry) if (! texentry)
{ {
debug_warn("Can't find texentry"); // TODO: nicer error handling debug_warn("Can't find texentry"); // TODO: nicer error handling
@ -182,7 +182,7 @@ BEGIN_COMMAND(PaintTerrain)
for (int dx = 0; dx < g_CurrentBrush.m_W; ++dx) for (int dx = 0; dx < g_CurrentBrush.m_W; ++dx)
{ {
if (g_CurrentBrush.Get(dx, dy) > 0.5f) // TODO: proper solid brushes if (g_CurrentBrush.Get(dx, dy) > 0.5f) // TODO: proper solid brushes
m_TerrainDelta.PaintTile(x0+dx, y0+dy, texture, d->priority); m_TerrainDelta.PaintTile(x0+dx, y0+dy, texture, msg->priority);
} }
g_Game->GetWorld()->GetTerrain()->MakeDirty(x0, y0, x0+g_CurrentBrush.m_W, y0+g_CurrentBrush.m_H, RENDERDATA_UPDATE_INDICES); g_Game->GetWorld()->GetTerrain()->MakeDirty(x0, y0, x0+g_CurrentBrush.m_W, y0+g_CurrentBrush.m_H, RENDERDATA_UPDATE_INDICES);

View File

@ -102,16 +102,16 @@ QUERY(GetObjectsList,
((std::vector<sObjectsListItem>, objects)) ((std::vector<sObjectsListItem>, objects))
); );
struct sUnitSettings struct sObjectSettings
{ {
Shareable<int> player; Shareable<int> player;
Shareable<std::vector<std::wstring> > selections; Shareable<std::vector<std::wstring> > selections;
}; };
SHAREABLE_STRUCT(sUnitSettings); SHAREABLE_STRUCT(sObjectSettings);
MESSAGE(ObjectPreview, MESSAGE(ObjectPreview,
((std::wstring, id)) // or empty string => disable ((std::wstring, id)) // or empty string => disable
((sUnitSettings, settings)) ((sObjectSettings, settings))
((Position, pos)) ((Position, pos))
((bool, usetarget)) // true => use 'target' for orientation; false => use 'angle' ((bool, usetarget)) // true => use 'target' for orientation; false => use 'angle'
((Position, target)) ((Position, target))
@ -120,7 +120,7 @@ MESSAGE(ObjectPreview,
COMMAND(CreateObject, NOMERGE, COMMAND(CreateObject, NOMERGE,
((std::wstring, id)) ((std::wstring, id))
((sUnitSettings, settings)) ((sObjectSettings, settings))
((Position, pos)) ((Position, pos))
((bool, usetarget)) // true => use 'target' for orientation; false => use 'angle' ((bool, usetarget)) // true => use 'target' for orientation; false => use 'angle'
((Position, target)) ((Position, target))
@ -183,7 +183,7 @@ COMMAND(PaintTerrain, MERGE,
typedef int ObjectID; typedef int ObjectID;
inline bool ObjectIDIsValid(ObjectID id) { return (id >= 0); } inline bool ObjectIDIsValid(ObjectID id) { return (id >= 0); }
QUERY(SelectObject, QUERY(PickObject,
((Position, pos)) ((Position, pos))
, ,
((ObjectID, id)) ((ObjectID, id))
@ -211,6 +211,18 @@ MESSAGE(SetSelectionPreview,
((std::vector<ObjectID>, ids)) ((std::vector<ObjectID>, ids))
); );
QUERY(GetObjectSettings,
((ObjectID, id))
,
((sObjectSettings, settings))
);
COMMAND(SetObjectSettings, NOMERGE,
((ObjectID, id))
((sObjectSettings, settings))
);
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
#include "MessagesSetup.h" #include "MessagesSetup.h"