mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-09-21 20:06:40 +00:00
# Atlas editor: Control over 'random' actor variations.
Actor variation selection (though not saved to maps, so not very useful). Added more levels of complexity to the waiting-for-game-to-respond message pump, to fix reentrancy problems. Use number keys to assign player to selected unit. This was SVN commit r3913.
This commit is contained in:
@@ -4,22 +4,22 @@
|
||||
<castshadow/>
|
||||
|
||||
<group>
|
||||
<variant frequency="10" name="1">
|
||||
<variant frequency="10" name="Base">
|
||||
<mesh>props/head_lime.pmd</mesh>
|
||||
</variant>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<variant>
|
||||
<variant name="head e">
|
||||
<texture>props/head/celt_e.dds</texture>
|
||||
</variant>
|
||||
<variant>
|
||||
<variant name="head f">
|
||||
<texture>props/head/celt_f.dds</texture>
|
||||
</variant>
|
||||
<variant>
|
||||
<variant name="head g">
|
||||
<texture>props/head/celt_g.dds</texture>
|
||||
</variant>
|
||||
<variant>
|
||||
<variant name="head h">
|
||||
<texture>props/head/celt_h.dds</texture>
|
||||
</variant>
|
||||
</group>
|
||||
|
||||
@@ -486,3 +486,73 @@ std::set<CStrW> CObjectBase::CalculateRandomVariation(const std::set<CStrW>& ini
|
||||
|
||||
return selections;
|
||||
}
|
||||
|
||||
std::vector<std::vector<CStrW> > CObjectBase::GetVariantGroups() const
|
||||
{
|
||||
std::vector<std::vector<CStrW> > groups;
|
||||
|
||||
// Queue of objects (main actor plus props (recursively)) to be processed
|
||||
std::queue<const CObjectBase*> objectsQueue;
|
||||
objectsQueue.push(this);
|
||||
|
||||
// Set of objects already processed, so we don't do them more than once
|
||||
std::set<const CObjectBase*> objectsProcessed;
|
||||
|
||||
while (objectsQueue.size())
|
||||
{
|
||||
const CObjectBase* obj = objectsQueue.front();
|
||||
objectsQueue.pop();
|
||||
// Ignore repeated objects (likely to be props)
|
||||
if (objectsProcessed.find(obj) != objectsProcessed.end())
|
||||
continue;
|
||||
|
||||
objectsProcessed.insert(obj);
|
||||
|
||||
// Iterate through the list of groups
|
||||
for (size_t i = 0; i < obj->m_VariantGroups.size(); ++i)
|
||||
{
|
||||
// Copy the group's variant names into a new vector
|
||||
std::vector<CStrW> group;
|
||||
group.reserve(obj->m_VariantGroups[i].size());
|
||||
for (size_t j = 0; j < obj->m_VariantGroups[i].size(); ++j)
|
||||
group.push_back(obj->m_VariantGroups[i][j].m_VariantName);
|
||||
|
||||
// If this group is identical to one elsewhere, don't bother listing
|
||||
// it twice.
|
||||
// Linear search is theoretically not very efficient, but hopefully
|
||||
// we don't have enough props for that to matter...
|
||||
bool dupe = false;
|
||||
for (size_t j = 0; j < groups.size(); ++j)
|
||||
{
|
||||
if (groups[j] == group)
|
||||
{
|
||||
dupe = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dupe)
|
||||
continue;
|
||||
|
||||
// Add non-trivial groups (i.e. not just one entry) to the returned list
|
||||
if (obj->m_VariantGroups[i].size() > 1)
|
||||
groups.push_back(group);
|
||||
|
||||
// Add all props onto the queue to be considered
|
||||
for (size_t j = 0; j < obj->m_VariantGroups[i].size(); ++j)
|
||||
{
|
||||
const std::vector<Prop>& props = obj->m_VariantGroups[i][j].m_Props;
|
||||
for (size_t k = 0; k < props.size(); ++k)
|
||||
{
|
||||
if (props[k].m_ModelName.Length())
|
||||
{
|
||||
CObjectBase* prop = g_ObjMan.FindObjectBase(props[k].m_ModelName);
|
||||
if (prop)
|
||||
objectsQueue.push(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
@@ -60,12 +60,23 @@ public:
|
||||
|
||||
CObjectBase();
|
||||
|
||||
// Get the variation key (indices of chosen variants from each group)
|
||||
// based on the selection strings
|
||||
std::vector<u8> CalculateVariationKey(const std::vector<std::set<CStrW> >& selections);
|
||||
|
||||
// Get the final actor data, combining all selected variants
|
||||
const Variation BuildVariation(const std::vector<u8>& variationKey);
|
||||
|
||||
// Get a set of selection strings that are complete enough to specify an
|
||||
// exact variation of the actor, using the initial selections wherever possible
|
||||
// and choosing randomly where a choice is necessary.
|
||||
std::set<CStrW> CalculateRandomVariation(const std::set<CStrW>& initialSelections);
|
||||
|
||||
// Get a list of variant groups for this object, plus for all possible
|
||||
// props. Duplicated groups are removed, if several props share the same
|
||||
// variant names.
|
||||
std::vector<std::vector<CStrW> > GetVariantGroups() const;
|
||||
|
||||
bool Load(const char* filename);
|
||||
|
||||
// object name
|
||||
|
||||
@@ -18,6 +18,7 @@ public:
|
||||
CObjectEntry(int type, CObjectBase* base);
|
||||
~CObjectEntry();
|
||||
|
||||
// Construct this actor, using the specified variation selections
|
||||
bool BuildVariation(const std::vector<std::set<CStrW> >& selections, const std::vector<u8>& variationKey);
|
||||
|
||||
// Base actor. Contains all the things that don't change between
|
||||
|
||||
@@ -47,7 +47,9 @@ CObjectManager::~CObjectManager()
|
||||
|
||||
CObjectBase* CObjectManager::FindObjectBase(const char* objectname)
|
||||
{
|
||||
// See if the base type has been loaded yet
|
||||
debug_assert(strcmp(objectname, "") != 0);
|
||||
|
||||
// See if the base type has been loaded yet:
|
||||
|
||||
for (uint k = 0; k < m_ObjectTypes.size(); k++)
|
||||
{
|
||||
|
||||
@@ -130,6 +130,12 @@ void CUnit::SetEntitySelection(const CStrW& selection)
|
||||
ReloadObject();
|
||||
}
|
||||
|
||||
void CUnit::SetActorSelections(const std::set<CStrW>& selections)
|
||||
{
|
||||
m_ActorSelections = selections;
|
||||
ReloadObject();
|
||||
}
|
||||
|
||||
void CUnit::ReloadObject()
|
||||
{
|
||||
std::vector<std::set<CStrW> > selections;
|
||||
|
||||
@@ -57,6 +57,10 @@ public:
|
||||
int GetID() const { return m_ID; }
|
||||
void SetID(int id) { m_ID = id; }
|
||||
|
||||
const std::set<CStrW>& GetActorSelections() const { return m_ActorSelections; }
|
||||
|
||||
void SetActorSelections(const std::set<CStrW>& selections);
|
||||
|
||||
private:
|
||||
// object from which unit was created
|
||||
CObjectEntry* m_Object;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "AtlasEventLoop.h"
|
||||
|
||||
AtlasEventLoop::AtlasEventLoop()
|
||||
: m_NeedsPaint(false)
|
||||
{
|
||||
}
|
||||
|
||||
void AtlasEventLoop::AddMessage(MSG* msg)
|
||||
{
|
||||
m_Messages.push_back(msg);
|
||||
}
|
||||
|
||||
void AtlasEventLoop::NeedsPaint()
|
||||
{
|
||||
m_NeedsPaint = true;
|
||||
}
|
||||
|
||||
bool AtlasEventLoop::Dispatch()
|
||||
{
|
||||
// Process the messages that QueryCallback collected
|
||||
for (size_t i = 0; i < m_Messages.size(); ++i)
|
||||
{
|
||||
MSG* pMsg = m_Messages[i];
|
||||
wxEventLoop::GetActive()->ProcessMessage(pMsg);
|
||||
delete pMsg;
|
||||
}
|
||||
m_Messages.clear();
|
||||
|
||||
if (m_NeedsPaint && wxTheApp && wxTheApp->GetTopWindow())
|
||||
{
|
||||
wxTheApp->GetTopWindow()->Refresh();
|
||||
m_NeedsPaint = false;
|
||||
}
|
||||
|
||||
return wxEventLoop::Dispatch();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef ATLASEVENTLOOP_H__
|
||||
#define ATLASEVENTLOOP_H__
|
||||
|
||||
#include "wx/evtloop.h"
|
||||
|
||||
struct tagMSG;
|
||||
|
||||
// See ScenarioEditor.cpp's QueryCallback for explanation
|
||||
|
||||
class AtlasEventLoop : public wxEventLoop
|
||||
{
|
||||
std::vector<tagMSG*> m_Messages;
|
||||
bool m_NeedsPaint;
|
||||
|
||||
public:
|
||||
AtlasEventLoop();
|
||||
|
||||
void AddMessage(tagMSG* msg);
|
||||
void NeedsPaint();
|
||||
|
||||
virtual bool Dispatch();
|
||||
};
|
||||
|
||||
#endif // ATLASEVENTLOOP_H__
|
||||
@@ -13,14 +13,36 @@ public:
|
||||
{
|
||||
return m_Signal.connect(order, boost::bind(std::mem_fun(callback), obj, _1));
|
||||
}
|
||||
void RemoveObserver(ObservableConnection handle)
|
||||
|
||||
void RemoveObserver(const ObservableConnection& conn)
|
||||
{
|
||||
handle.disconnect();
|
||||
conn.disconnect();
|
||||
}
|
||||
|
||||
void NotifyObservers()
|
||||
{
|
||||
m_Signal(*this);
|
||||
}
|
||||
|
||||
// Use when an object is changing something that it's also observing,
|
||||
// because it already knows about the change and doesn't need to be notified
|
||||
// again (particularly since that may cause infinite loops).
|
||||
void NotifyObserversExcept(ObservableConnection& conn)
|
||||
{
|
||||
if (conn.blocked())
|
||||
{
|
||||
// conn is already blocked and won't see anything
|
||||
NotifyObservers();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Temporarily disable conn
|
||||
conn.block();
|
||||
NotifyObservers();
|
||||
conn.unblock();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
boost::signal<void (const T&)> m_Signal;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "DLLInterface.h"
|
||||
|
||||
#include "General/AtlasEventLoop.h"
|
||||
|
||||
#include "General/Datafile.h"
|
||||
#include "ActorEditor/ActorEditor.h"
|
||||
#include "ColourTester/ColourTester.h"
|
||||
@@ -184,6 +186,20 @@ public:
|
||||
OpenDirectory(dir);
|
||||
}
|
||||
}
|
||||
|
||||
virtual int MainLoop()
|
||||
{
|
||||
// Override the default MainLoop so that we can provide our own event loop
|
||||
|
||||
wxEventLoop* old = m_mainLoop;
|
||||
m_mainLoop = new AtlasEventLoop;
|
||||
|
||||
int ret = m_mainLoop->Run();
|
||||
|
||||
delete m_mainLoop;
|
||||
m_mainLoop = old;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_APP_NO_MAIN(wxDLLApp)
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "wx/evtloop.h"
|
||||
#include "wx/tooltip.h"
|
||||
|
||||
#include "General/AtlasEventLoop.h"
|
||||
|
||||
#include "SnapSplitterWindow/SnapSplitterWindow.h"
|
||||
#include "HighResTimer/HighResTimer.h"
|
||||
#include "Buttons/ToolButton.h"
|
||||
@@ -532,6 +534,9 @@ AtlasMessage::Position::Position(const wxPoint& pt)
|
||||
type1.y = pt.y;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
static void QueryCallback()
|
||||
{
|
||||
// If this thread completely blocked on the semaphore inside Query, it would
|
||||
@@ -544,9 +549,66 @@ static void QueryCallback()
|
||||
// This is kind of like wxYield, but without the ProcessPendingEvents -
|
||||
// it's enough to make Windows happy and stop deadlocking, without actually
|
||||
// calling the event handlers (which could lead to nasty recursion)
|
||||
while (wxEventLoop::GetActive()->Pending())
|
||||
wxEventLoop::GetActive()->Dispatch();
|
||||
|
||||
// while (wxEventLoop::GetActive()->Pending())
|
||||
// wxEventLoop::GetActive()->Dispatch();
|
||||
|
||||
// Oh dear, we can't use that either - it (at least in wx 2.6.3) still
|
||||
// processes messages, which causes reentry into various things that we
|
||||
// don't want to be reentrant. So do it all manually, accepting Windows
|
||||
// messages and sticking them on a list for later processing (in a custom
|
||||
// event loop class):
|
||||
|
||||
// (TODO: Rethink this entire process on Linux)
|
||||
// (Alt TODO: Could we make the game never pop up windows (or use the Win32
|
||||
// GUI in any other way) when it's running under Atlas, so we wouldn't need
|
||||
// to do any message processing here at all?)
|
||||
|
||||
AtlasEventLoop* evtLoop = (AtlasEventLoop*)wxEventLoop::GetActive();
|
||||
|
||||
while (evtLoop->Pending())
|
||||
{
|
||||
// Based on src/msw/evtloop.cpp's wxEventLoop::Dispatch()
|
||||
|
||||
MSG msg;
|
||||
BOOL rc = ::GetMessage(&msg, (HWND) NULL, 0, 0);
|
||||
|
||||
if (rc == 0)
|
||||
{
|
||||
// got WM_QUIT
|
||||
return;
|
||||
}
|
||||
|
||||
if (rc == -1)
|
||||
{
|
||||
wxLogLastError(wxT("GetMessage"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Our special bits:
|
||||
|
||||
if (msg.message == WM_PAINT)
|
||||
{
|
||||
// "GetMessage does not remove WM_PAINT messages from the queue.
|
||||
// The messages remain in the queue until processed."
|
||||
// So let's process them, to avoid infinite loops...
|
||||
PAINTSTRUCT paint;
|
||||
::BeginPaint(msg.hwnd, &paint);
|
||||
::EndPaint(msg.hwnd, &paint);
|
||||
// Remember that some painting was needed - we'll just repaint
|
||||
// the whole screen when this is finished.
|
||||
evtLoop->NeedsPaint();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add this message to a queue for later processing. (That's
|
||||
// probably kind of valid, at least in most cases.)
|
||||
MSG* pMsg = new MSG(msg);
|
||||
evtLoop->AddMessage(pMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AtlasMessage::QueryMessage::Post()
|
||||
{
|
||||
g_MessagePasser->Query(this, &QueryCallback);
|
||||
|
||||
@@ -20,9 +20,11 @@ public:
|
||||
|
||||
void OnSelect(wxCommandEvent& evt)
|
||||
{
|
||||
// On selecting an object, enable the PlaceObject tool with this object
|
||||
wxString id = static_cast<wxStringClientData*>(evt.GetClientObject())->GetData();
|
||||
SetCurrentTool(_T("PlaceObject"), &id);
|
||||
}
|
||||
|
||||
private:
|
||||
DECLARE_EVENT_TABLE();
|
||||
};
|
||||
@@ -43,10 +45,13 @@ public:
|
||||
|
||||
void OnSelect(wxCommandEvent& evt)
|
||||
{
|
||||
// Switch between displayed lists of objects (e.g. entities vs actors)
|
||||
m_Sidebar.SetObjectFilter(evt.GetSelection());
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectSidebar& m_Sidebar;
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
};
|
||||
BEGIN_EVENT_TABLE(ObjectChoiceCtrl, wxChoice)
|
||||
@@ -84,9 +89,11 @@ ObjectSidebar::~ObjectSidebar()
|
||||
|
||||
void ObjectSidebar::OnFirstDisplay()
|
||||
{
|
||||
// Get the list of objects from the game
|
||||
AtlasMessage::qGetObjectsList qry;
|
||||
qry.Post();
|
||||
p->m_Objects = *qry.objects;
|
||||
// Display first group of objects
|
||||
SetObjectFilter(0);
|
||||
}
|
||||
|
||||
@@ -114,25 +121,27 @@ public:
|
||||
PlayerComboBox(wxWindow* parent, wxArrayString& choices)
|
||||
: wxComboBox(parent, -1, choices[g_ObjectSettings.GetPlayerID()], wxDefaultPosition, wxDefaultSize, choices, wxCB_READONLY)
|
||||
{
|
||||
m_Conn = g_SelectedObjects.RegisterObserver(1, &PlayerComboBox::OnSelectionChange, this);
|
||||
m_Conn = g_ObjectSettings.RegisterObserver(1, &PlayerComboBox::OnObjectSettingsChange, this);
|
||||
}
|
||||
|
||||
~PlayerComboBox()
|
||||
{
|
||||
g_SelectedObjects.RemoveObserver(m_Conn);
|
||||
g_ObjectSettings.RemoveObserver(m_Conn);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
ObservableConnection m_Conn;
|
||||
|
||||
void OnSelectionChange(const std::vector<AtlasMessage::ObjectID>& selection)
|
||||
void OnObjectSettingsChange(const ObjectSettings& settings)
|
||||
{
|
||||
SetSelection(g_ObjectSettings.GetPlayerID());
|
||||
SetSelection(settings.GetPlayerID());
|
||||
}
|
||||
|
||||
void OnSelect(wxCommandEvent& evt)
|
||||
{
|
||||
g_ObjectSettings.SetPlayerID(evt.GetInt());
|
||||
g_ObjectSettings.NotifyObserversExcept(m_Conn);
|
||||
}
|
||||
|
||||
DECLARE_EVENT_TABLE();
|
||||
@@ -142,6 +151,153 @@ BEGIN_EVENT_TABLE(PlayerComboBox, wxComboBox)
|
||||
END_EVENT_TABLE();
|
||||
|
||||
|
||||
class VariationDisplay : public wxScrolledWindow
|
||||
{
|
||||
public:
|
||||
VariationDisplay(wxWindow* parent)
|
||||
: wxScrolledWindow(parent, -1)
|
||||
{
|
||||
m_Conn = g_ObjectSettings.RegisterObserver(1, &VariationDisplay::OnObjectSettingsChange, this);
|
||||
|
||||
SetMinSize(wxSize(160, wxDefaultCoord));
|
||||
|
||||
SetScrollRate(0, 5);
|
||||
|
||||
m_Sizer = new wxBoxSizer(wxVERTICAL);
|
||||
SetSizer(m_Sizer);
|
||||
}
|
||||
|
||||
~VariationDisplay()
|
||||
{
|
||||
g_ObjectSettings.RemoveObserver(m_Conn);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
ObservableConnection m_Conn;
|
||||
|
||||
std::vector<wxWindow*> m_ComboBoxes;
|
||||
wxSizer* m_Sizer;
|
||||
|
||||
// Event handler shared by all the combo boxes created by this window
|
||||
void OnSelect(wxCommandEvent& evt)
|
||||
{
|
||||
std::set<wxString> selections;
|
||||
|
||||
// It's possible for a variant name to appear in multiple groups.
|
||||
// If so, assume that all the names in each group are the same, so
|
||||
// we don't have to worry about some impossible combinations (e.g.
|
||||
// one group "a,b", a second "b,c", and a third "c,a", where's there's
|
||||
// no set of selections that matches one (and only one) of each group).
|
||||
//
|
||||
// So... When a combo box is changed from 'a' to 'b', add 'b' to the new
|
||||
// selections and make sure any other combo boxes containing both 'a' and
|
||||
// 'b' no longer contain 'a'.
|
||||
|
||||
wxComboBox* thisComboBox = wxDynamicCast(evt.GetEventObject(), wxComboBox);
|
||||
wxString newValue = thisComboBox->GetValue();
|
||||
|
||||
selections.insert(newValue);
|
||||
|
||||
for (size_t i = 0; i < m_ComboBoxes.size(); ++i)
|
||||
{
|
||||
wxComboBox* comboBox = wxDynamicCast(m_ComboBoxes[i], wxComboBox);
|
||||
wxCHECK(comboBox != NULL, );
|
||||
// If our newly selected value is used in another combobox, we want
|
||||
// that combobox to use the new value, so don't add its old value
|
||||
// to the list of selections
|
||||
if (comboBox->FindString(newValue) == wxNOT_FOUND)
|
||||
selections.insert(comboBox->GetValue());
|
||||
}
|
||||
|
||||
g_ObjectSettings.SetActorSelections(selections);
|
||||
g_ObjectSettings.NotifyObserversExcept(m_Conn);
|
||||
RefreshObjectSettings();
|
||||
}
|
||||
|
||||
void OnObjectSettingsChange(const ObjectSettings& settings)
|
||||
{
|
||||
Freeze();
|
||||
|
||||
const std::vector<ObjectSettings::Group>& variation = settings.GetActorVariation();
|
||||
|
||||
// Creating combo boxes seems to be pretty expensive - so we create as
|
||||
// few as possible, by never deleting any.
|
||||
|
||||
size_t oldCount = m_ComboBoxes.size();
|
||||
size_t newCount = variation.size();
|
||||
|
||||
// If we have too many combo boxes, hide the excess ones
|
||||
for (size_t i = newCount; i < oldCount; ++i)
|
||||
{
|
||||
m_ComboBoxes[i]->Show(false);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < variation.size(); ++i)
|
||||
{
|
||||
const ObjectSettings::Group& group = variation[i];
|
||||
|
||||
if (i < oldCount)
|
||||
{
|
||||
// Already got enough boxes available, so use an old one
|
||||
wxComboBox* comboBox = wxDynamicCast(m_ComboBoxes[i], wxComboBox);
|
||||
wxCHECK(comboBox != NULL, );
|
||||
// Replace the contents of the old combobox with the new data
|
||||
comboBox->Freeze();
|
||||
comboBox->Clear();
|
||||
comboBox->Append(group.variants);
|
||||
comboBox->SetValue(group.chosen);
|
||||
comboBox->Show(true);
|
||||
comboBox->Thaw();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create an initially empty combobox, because we can fill it
|
||||
// quicker than the default constructor can
|
||||
wxComboBox* combo = new wxComboBox(this, -1, wxEmptyString, wxDefaultPosition,
|
||||
wxSize(130, wxDefaultCoord), wxArrayString(), wxCB_READONLY);
|
||||
// Freeze it before adding all the values
|
||||
combo->Freeze();
|
||||
combo->Append(group.variants);
|
||||
combo->SetValue(group.chosen);
|
||||
combo->Thaw();
|
||||
// Add the on-select event handler
|
||||
combo->Connect(wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED,
|
||||
wxCommandEventHandler(VariationDisplay::OnSelect), NULL, this);
|
||||
// Add box to sizer and list
|
||||
m_Sizer->Add(combo);
|
||||
m_ComboBoxes.push_back(combo);
|
||||
}
|
||||
}
|
||||
|
||||
Layout();
|
||||
|
||||
// Make the scrollbars appear when appropriate
|
||||
FitInside();
|
||||
|
||||
Thaw();
|
||||
}
|
||||
|
||||
void RefreshObjectSettings()
|
||||
{
|
||||
const std::vector<ObjectSettings::Group>& variation = g_ObjectSettings.GetActorVariation();
|
||||
|
||||
// For each group, set the corresponding combobox's value to the chosen one
|
||||
size_t i = 0;
|
||||
for (std::vector<ObjectSettings::Group>::const_iterator group = variation.begin();
|
||||
group != variation.end() && i < m_ComboBoxes.size();
|
||||
++group, ++i)
|
||||
{
|
||||
wxComboBox* comboBox = wxDynamicCast(m_ComboBoxes[i], wxComboBox);
|
||||
wxCHECK(comboBox != NULL, );
|
||||
|
||||
comboBox->SetValue(group->chosen);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ObjectBottomBar::ObjectBottomBar(wxWindow* parent)
|
||||
: wxPanel(parent, wxID_ANY)
|
||||
{
|
||||
@@ -159,7 +315,12 @@ ObjectBottomBar::ObjectBottomBar(wxWindow* parent)
|
||||
players.Add(_("Player 7"));
|
||||
players.Add(_("Player 8"));
|
||||
wxComboBox* playerSelect = new PlayerComboBox(this, players);
|
||||
|
||||
sizer->Add(playerSelect);
|
||||
|
||||
wxWindow* variationSelect = new VariationDisplay(this);
|
||||
wxSizer* variationSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Variation"));
|
||||
variationSizer->Add(variationSelect, wxSizerFlags().Proportion(1).Expand());
|
||||
sizer->Add(variationSizer, wxSizerFlags().Proportion(1));
|
||||
|
||||
SetSizer(sizer);
|
||||
}
|
||||
|
||||
@@ -10,9 +10,6 @@ namespace AtlasMessage
|
||||
|
||||
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__
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "GameInterface/Messages.h"
|
||||
#include "ScenarioEditor/Tools/Common/Tools.h"
|
||||
|
||||
ObjectSettings g_ObjectSettings;
|
||||
Observable<ObjectSettings> g_ObjectSettings;
|
||||
|
||||
ObjectSettings::ObjectSettings()
|
||||
: m_PlayerID(0)
|
||||
@@ -18,7 +18,7 @@ ObjectSettings::~ObjectSettings()
|
||||
m_Conn.disconnect();
|
||||
}
|
||||
|
||||
int ObjectSettings::GetPlayerID()
|
||||
int ObjectSettings::GetPlayerID() const
|
||||
{
|
||||
return m_PlayerID;
|
||||
}
|
||||
@@ -29,21 +29,62 @@ void ObjectSettings::SetPlayerID(int playerID)
|
||||
PostToGame();
|
||||
}
|
||||
|
||||
const std::set<wxString>& ObjectSettings::GetActorSelections() const
|
||||
{
|
||||
return m_ActorSelections;
|
||||
}
|
||||
|
||||
void ObjectSettings::SetActorSelections(const std::set<wxString>& selections)
|
||||
{
|
||||
m_ActorSelections = selections;
|
||||
PostToGame();
|
||||
}
|
||||
|
||||
const std::vector<ObjectSettings::Group> ObjectSettings::GetActorVariation() const
|
||||
{
|
||||
std::vector<Group> variation;
|
||||
|
||||
for (std::vector<wxArrayString>::const_iterator grp = m_VariantGroups.begin();
|
||||
grp != m_VariantGroups.end();
|
||||
++grp)
|
||||
{
|
||||
Group group;
|
||||
group.variants = *grp;
|
||||
|
||||
// Variant choice method, as used by the game: Choose the first variant
|
||||
// which matches any of the selections
|
||||
|
||||
size_t chosen = 0; // default to first
|
||||
for (size_t i = 0; i < grp->GetCount(); ++i)
|
||||
{
|
||||
if (m_ActorSelections.find(grp->Item(i)) != m_ActorSelections.end())
|
||||
{
|
||||
chosen = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
group.chosen = grp->Item(chosen);
|
||||
|
||||
variation.push_back(group);
|
||||
}
|
||||
|
||||
return variation;
|
||||
}
|
||||
|
||||
AtlasMessage::sObjectSettings ObjectSettings::GetSettings() const
|
||||
{
|
||||
AtlasMessage::sObjectSettings settings;
|
||||
|
||||
settings.player = m_PlayerID;
|
||||
|
||||
// Copy selections from set into vector
|
||||
std::vector<std::wstring> selections;
|
||||
for (std::set<wxString>::const_iterator it = m_ActorSelections.begin(); it != m_ActorSelections.end(); ++it)
|
||||
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;
|
||||
@@ -61,11 +102,36 @@ void ObjectSettings::OnSelectionChange(const std::vector<AtlasMessage::ObjectID>
|
||||
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());
|
||||
m_VariantGroups.clear();
|
||||
|
||||
std::vector<std::vector<std::wstring> > variation = *qry.settings->variantgroups;
|
||||
for (std::vector<std::vector<std::wstring> >::iterator grp = variation.begin();
|
||||
grp != variation.end();
|
||||
++grp)
|
||||
{
|
||||
wxArrayString variants;
|
||||
|
||||
for (std::vector<std::wstring>::iterator it = grp->begin();
|
||||
it != grp->end();
|
||||
++it)
|
||||
{
|
||||
variants.Add(it->c_str());
|
||||
}
|
||||
|
||||
m_VariantGroups.push_back(variants);
|
||||
}
|
||||
|
||||
std::vector<std::wstring> selections = *qry.settings->selections;
|
||||
for (std::vector<std::wstring>::iterator sel = selections.begin();
|
||||
sel != selections.end();
|
||||
++sel)
|
||||
{
|
||||
m_ActorSelections.insert(sel->c_str());
|
||||
}
|
||||
|
||||
static_cast<Observable<ObjectSettings>*>(this)->NotifyObservers();
|
||||
}
|
||||
|
||||
void ObjectSettings::PostToGame()
|
||||
|
||||
@@ -19,10 +19,21 @@ public:
|
||||
ObjectSettings();
|
||||
~ObjectSettings();
|
||||
|
||||
int GetPlayerID();
|
||||
int GetPlayerID() const;
|
||||
void SetPlayerID(int playerID);
|
||||
|
||||
struct Group
|
||||
{
|
||||
wxArrayString variants;
|
||||
wxString chosen;
|
||||
};
|
||||
|
||||
const std::vector<Group> GetActorVariation() const;
|
||||
|
||||
const std::set<wxString>& GetActorSelections() const;
|
||||
void SetActorSelections(const std::set<wxString>& selections);
|
||||
|
||||
// Constructs new sObjectSettings object from settings
|
||||
AtlasMessage::sObjectSettings GetSettings() const;
|
||||
|
||||
private:
|
||||
@@ -34,6 +45,9 @@ private:
|
||||
// a new actor, and will accumulate variant names)
|
||||
std::set<wxString> m_ActorSelections;
|
||||
|
||||
// List of actor variant groups (each a list of variant names)
|
||||
std::vector<wxArrayString> m_VariantGroups;
|
||||
|
||||
// Observe changes to unit selection
|
||||
ObservableConnection m_Conn;
|
||||
void OnSelectionChange(const std::vector<AtlasMessage::ObjectID>& selection);
|
||||
@@ -42,6 +56,6 @@ private:
|
||||
void PostToGame();
|
||||
};
|
||||
|
||||
extern ObjectSettings g_ObjectSettings;
|
||||
extern Observable<ObjectSettings> g_ObjectSettings;
|
||||
|
||||
#endif // ObjectSettings_H__
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "Common/Tools.h"
|
||||
#include "Common/Brushes.h"
|
||||
#include "Common/MiscState.h"
|
||||
#include "Common/ObjectSettings.h"
|
||||
#include "GameInterface/Messages.h"
|
||||
|
||||
using AtlasMessage::Position;
|
||||
@@ -35,27 +36,41 @@ public:
|
||||
{
|
||||
if (evt.LeftDown())
|
||||
{
|
||||
// TODO: multiple selection
|
||||
// New selection - never merge with movements of other objects
|
||||
ScenarioEditor::GetCommandProc().FinaliseLastCommand();
|
||||
|
||||
// Select the object clicked on:
|
||||
|
||||
AtlasMessage::qPickObject qry(Position(evt.GetPosition()));
|
||||
qry.Post();
|
||||
|
||||
// TODO: handle multiple selections
|
||||
g_SelectedObjects.clear();
|
||||
|
||||
// Check they actually clicked on a valid object
|
||||
if (AtlasMessage::ObjectIDIsValid(qry.id))
|
||||
{
|
||||
g_SelectedObjects.push_back(qry.id);
|
||||
// Remember the screen-space offset of the mouse from the
|
||||
// object's centre, so we can add that back when moving it
|
||||
// (instead of just moving the object's centre to directly
|
||||
// beneath the mouse)
|
||||
obj->m_dx = qry.offsetx;
|
||||
obj->m_dy = qry.offsety;
|
||||
SET_STATE(Dragging);
|
||||
}
|
||||
g_SelectedObjects.NotifyObservers();
|
||||
POST_MESSAGE(SetSelectionPreview, (g_SelectedObjects));
|
||||
ScenarioEditor::GetCommandProc().FinaliseLastCommand();
|
||||
return true;
|
||||
}
|
||||
else if (evt.Dragging() && evt.RightIsDown() || evt.RightDown())
|
||||
{
|
||||
// Dragging with right mouse button -> rotate objects to look
|
||||
// at mouse
|
||||
Position pos (evt.GetPosition());
|
||||
for (size_t i = 0; i < g_SelectedObjects.size(); ++i)
|
||||
POST_COMMAND(RotateObject, (g_SelectedObjects[i], true, pos, 0.f));
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -68,11 +83,20 @@ public:
|
||||
{
|
||||
for (size_t i = 0; i < g_SelectedObjects.size(); ++i)
|
||||
POST_COMMAND(DeleteObject, (g_SelectedObjects[i]));
|
||||
|
||||
g_SelectedObjects.clear();
|
||||
g_SelectedObjects.NotifyObservers();
|
||||
|
||||
POST_MESSAGE(SetSelectionPreview, (g_SelectedObjects));
|
||||
return true;
|
||||
}
|
||||
else if (type == KEY_CHAR && (evt.GetKeyCode() >= '0' && evt.GetKeyCode() <= '9'))
|
||||
{
|
||||
int playerID = evt.GetKeyCode() - '0';
|
||||
g_ObjectSettings.SetPlayerID(playerID);
|
||||
g_ObjectSettings.NotifyObservers();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -110,14 +110,51 @@ QUERYHANDLER(GetObjectSettings)
|
||||
|
||||
sObjectSettings settings;
|
||||
settings.player = unit->GetPlayerID();
|
||||
// TODO: actor variation
|
||||
|
||||
// Get the unit's possible variants and selected variants
|
||||
std::vector<std::vector<CStrW> > groups = unit->GetObject()->m_Base->GetVariantGroups();
|
||||
const std::set<CStrW>& selections = unit->GetActorSelections();
|
||||
|
||||
// Iterate over variant groups
|
||||
std::vector<std::vector<std::wstring> > variantgroups;
|
||||
std::set<std::wstring> selections_set;
|
||||
variantgroups.reserve(groups.size());
|
||||
for (size_t i = 0; i < groups.size(); ++i)
|
||||
{
|
||||
// Copy variants into output structure
|
||||
|
||||
std::vector<std::wstring> group;
|
||||
group.reserve(groups[i].size());
|
||||
int choice = -1;
|
||||
|
||||
for (size_t j = 0; j < groups[i].size(); ++j)
|
||||
{
|
||||
group.push_back(groups[i][j]);
|
||||
|
||||
// Find the first string in 'selections' that matches one of this
|
||||
// group's variants
|
||||
if (choice == -1)
|
||||
if (selections.find(groups[i][j]) != selections.end())
|
||||
choice = (int)j;
|
||||
}
|
||||
|
||||
// Assuming one of the variants was selected (which it really ought
|
||||
// to be), remember that one's name
|
||||
if (choice != -1)
|
||||
selections_set.insert(groups[i][choice]);
|
||||
|
||||
variantgroups.push_back(group);
|
||||
}
|
||||
|
||||
settings.variantgroups = variantgroups;
|
||||
settings.selections = std::vector<std::wstring> (selections_set.begin(), selections_set.end()); // convert set->vector
|
||||
msg->settings = settings;
|
||||
}
|
||||
|
||||
BEGIN_COMMAND(SetObjectSettings)
|
||||
|
||||
int m_PlayerOld, m_PlayerNew;
|
||||
std::set<CStrW> m_SelectionsOld, m_SelectionsNew;
|
||||
|
||||
void Do()
|
||||
{
|
||||
@@ -128,7 +165,12 @@ BEGIN_COMMAND(SetObjectSettings)
|
||||
|
||||
m_PlayerOld = unit->GetPlayerID();
|
||||
m_PlayerNew = settings.player;
|
||||
// TODO: actor variations
|
||||
|
||||
m_SelectionsOld = unit->GetActorSelections();
|
||||
|
||||
std::vector<std::wstring> selections = *settings.selections;
|
||||
copy(selections.begin(), selections.end(),
|
||||
std::insert_iterator<std::set<CStrW> >(m_SelectionsNew, m_SelectionsNew.begin()));
|
||||
|
||||
Redo();
|
||||
}
|
||||
@@ -139,6 +181,7 @@ BEGIN_COMMAND(SetObjectSettings)
|
||||
if (! unit) return;
|
||||
|
||||
unit->SetPlayerID(m_PlayerNew);
|
||||
unit->SetActorSelections(m_SelectionsNew);
|
||||
}
|
||||
|
||||
void Undo()
|
||||
@@ -147,6 +190,7 @@ BEGIN_COMMAND(SetObjectSettings)
|
||||
if (! unit) return;
|
||||
|
||||
unit->SetPlayerID(m_PlayerOld);
|
||||
unit->SetActorSelections(m_SelectionsOld);
|
||||
}
|
||||
|
||||
END_COMMAND(SetObjectSettings);
|
||||
@@ -405,10 +449,15 @@ QUERYHANDLER(PickObject)
|
||||
|
||||
if (target)
|
||||
{
|
||||
// Get screen coordinates of the point on the ground underneath the
|
||||
// object's model-centre, so that callers know the offset to use when
|
||||
// working out the screen coordinates to move the object to.
|
||||
// (TODO: http://trac.0ad.homeip.net/ticket/99)
|
||||
CVector3D centre = target->GetModel()->GetTransform().GetTranslation();
|
||||
centre.Y = g_Game->GetWorld()->GetTerrain()->getExactGroundLevel(centre.X, centre.Z);
|
||||
float cx, cy;
|
||||
g_Game->GetView()->GetCamera()->GetScreenCoordinates(centre, cx, cy);
|
||||
|
||||
msg->offsetx = (int)(cx - x);
|
||||
msg->offsety = (int)(cy - y);
|
||||
}
|
||||
@@ -472,7 +521,8 @@ BEGIN_COMMAND(MoveObject)
|
||||
|
||||
void MergeWithSelf(cMoveObject* prev)
|
||||
{
|
||||
// TODO: merge correctly when prev unit != this unit
|
||||
// TODO: do something valid if prev unit != this unit
|
||||
debug_assert(prev->msg->id == msg->id);
|
||||
prev->m_PosNew = m_PosNew;
|
||||
}
|
||||
|
||||
@@ -563,7 +613,8 @@ BEGIN_COMMAND(RotateObject)
|
||||
|
||||
void MergeWithSelf(cRotateObject* prev)
|
||||
{
|
||||
// TODO: merge correctly when prev unit != this unit
|
||||
// TODO: do something valid if prev unit != this unit
|
||||
debug_assert(prev->msg->id == msg->id);
|
||||
prev->m_AngleNew = m_AngleNew;
|
||||
prev->m_TransformNew = m_TransformNew;
|
||||
}
|
||||
|
||||
@@ -106,6 +106,11 @@ struct sObjectSettings
|
||||
{
|
||||
Shareable<int> player;
|
||||
Shareable<std::vector<std::wstring> > selections;
|
||||
|
||||
// Some settings are immutable and therefore are ignored (and should be left
|
||||
// empty) when passed from the editor to the game:
|
||||
|
||||
Shareable<std::vector<std::vector<std::wstring> > > variantgroups;
|
||||
};
|
||||
SHAREABLE_STRUCT(sObjectSettings);
|
||||
|
||||
|
||||
@@ -9,12 +9,23 @@ class CVector3D;
|
||||
namespace AtlasMessage
|
||||
{
|
||||
|
||||
// Represents a position in the game world, with an interface usable from the
|
||||
// UI (which usually knows only about screen space). Typically constructed
|
||||
// by the UI, then passed to the game which uses GetWorldSpace.
|
||||
|
||||
struct Position
|
||||
{
|
||||
Position() : type(0) { type0.x = type0.y = type0.z = 0.f; }
|
||||
|
||||
// Constructs a position with specified world-space coordinates
|
||||
Position(float x_, float y_, float z_) : type(0) { type0.x = x_; type0.y = y_; type0.z = z_; }
|
||||
|
||||
// Constructs a position on the terrain underneath the screen-space coordinates
|
||||
Position(const wxPoint& pt); // (implementation in ScenarioEditor.cpp)
|
||||
|
||||
// Store all possible position representations in a union, instead of something
|
||||
// like inheritance and virtual functions, so we don't have to care about
|
||||
// dynamic memory allocation across DLLs.
|
||||
int type;
|
||||
union {
|
||||
struct { float x, y, z; } type0; // world-space coordinates
|
||||
@@ -23,7 +34,7 @@ struct Position
|
||||
};
|
||||
|
||||
// Constructs a position with the meaning "same as previous", which is handled
|
||||
// in an unspecified way by various message handlers.
|
||||
// in unspecified (but usually obvious) ways by different message handlers.
|
||||
static Position Unchanged() { Position p; p.type = 2; return p; }
|
||||
|
||||
// Only for use in the game, not the UI.
|
||||
@@ -33,6 +44,7 @@ struct Position
|
||||
void GetWorldSpace(CVector3D& vec, const CVector3D& prev) const;
|
||||
void GetScreenSpace(float& x, float& y) const;
|
||||
};
|
||||
|
||||
SHAREABLE_STRUCT(Position);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user