# 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:
Ykkrosh
2006-05-31 05:27:02 +00:00
parent e0dfbe719d
commit d605cb39ec
20 changed files with 619 additions and 33 deletions
@@ -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>
+70
View File
@@ -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;
}
+11
View File
@@ -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
+1
View File
@@ -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
+3 -1
View File
@@ -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++)
{
+6
View File
@@ -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;
+4
View File
@@ -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);
+13 -1
View File
@@ -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);
}