diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp
index 57b4734..8f1d507 100644
--- a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp
+++ b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp
@@ -19,6 +19,8 @@
 
 #include "ScenarioEditor.h"
 
+#include <vector>
+
 #include "wx/busyinfo.h"
 #include "wx/config.h"
 #include "wx/dir.h"
@@ -28,6 +30,7 @@
 #include "wx/image.h"
 #include "wx/sysopt.h"
 #include "wx/tooltip.h"
+#include <sstream>
 
 #include "General/AtlasEventLoop.h"
 #include "General/Datafile.h"
@@ -44,9 +47,10 @@
 
 #include "Misc/KeyMap.h"
 
-#include "Tools/Common/Tools.h"
 #include "Tools/Common/Brushes.h"
 #include "Tools/Common/MiscState.h"
+#include "Tools/Common/PasteObjectSettings.h"
+#include "Tools/Common/Tools.h"
 
 static HighResTimer g_Timer;
 
@@ -340,6 +344,9 @@ BEGIN_EVENT_TABLE(ScenarioEditor, wxFrame)
 	EVT_MENU(ID_Quit, ScenarioEditor::OnQuit)
 	EVT_MENU(wxID_UNDO, ScenarioEditor::OnUndo)
 	EVT_MENU(wxID_REDO, ScenarioEditor::OnRedo)
+    EVT_MENU(wxID_COPY, ScenarioEditor::OnCopy)
+    EVT_MENU(wxID_CUT, ScenarioEditor::OnCut)
+    EVT_MENU(wxID_PASTE, ScenarioEditor::OnPaste)
 
 	EVT_MENU(ID_Wireframe, ScenarioEditor::OnWireframe)
 	EVT_MENU(ID_MessageTrace, ScenarioEditor::OnMessageTrace)
@@ -436,6 +443,10 @@ ScenarioEditor::ScenarioEditor(wxWindow* parent, ScriptInterface& scriptInterfac
 	{
 		menuEdit->Append(wxID_UNDO, _("&Undo"));
 		menuEdit->Append(wxID_REDO, _("&Redo"));
+        menuEdit->AppendSeparator();
+        menuEdit->Append(wxID_COPY, _("&Copy"));
+        menuEdit->Append(wxID_CUT, _("Cut"));
+        menuEdit->Append(wxID_PASTE, _("&Paste"));
 	}
 
 	GetCommandProc().SetEditMenu(menuEdit);
@@ -622,6 +633,46 @@ void ScenarioEditor::OnRedo(wxCommandEvent&)
 	GetCommandProc().Redo();
 }
 
+
+void ScenarioEditor::OnCopy(wxCommandEvent& event)
+{
+    if (g_SelectedObjects.empty())
+    {
+        wxLogWarning(_("Nothing to copy!"));
+        return;
+    }
+
+    AtlasMessage::qGetXmlForObjects qry(g_SelectedObjects);
+    qry.Post();
+
+    wxLogWarning(wxString(qry.xml.c_str()));
+}
+
+void ScenarioEditor::OnCut(wxCommandEvent& event)
+{
+}
+
+void ScenarioEditor::OnPaste(wxCommandEvent& event)
+{
+    std::vector<PasteObjectSettings> objects;
+
+    PasteObjectSettings object1;
+    object1.templateName = L"gaia/fauna_bear";
+    object1.atlasSettings.player = 1;
+    object1.pasteRelativePosition.type1.x = 100;
+    object1.pasteRelativePosition.type1.y = 0;
+    objects.push_back(object1);
+
+    PasteObjectSettings object2;
+    object2.templateName = L"gaia/fauna_bear";
+    object2.atlasSettings.player = 1;
+    object2.pasteRelativePosition.type1.x = 0;
+    object2.pasteRelativePosition.type1.y = 0;
+    objects.push_back(object2);
+
+    GetToolManager().SetCurrentTool(_T("PasteObjects"), &objects);
+}
+
 //////////////////////////////////////////////////////////////////////////
 
 void ScenarioEditor::OnNew(wxCommandEvent& WXUNUSED(event))
diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h
index c3a6b38..ae054e4 100644
--- a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h
+++ b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h
@@ -48,6 +48,9 @@ public:
 	void OnQuit(wxCommandEvent& event);
 	void OnUndo(wxCommandEvent& event);
 	void OnRedo(wxCommandEvent& event);
+    void OnCopy(wxCommandEvent& event);
+    void OnCut(wxCommandEvent& event);
+    void OnPaste(wxCommandEvent& event);
 
 	void OnWireframe(wxCommandEvent& event);
 	void OnMessageTrace(wxCommandEvent& event);
diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/PasteObjectSettings.h b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/PasteObjectSettings.h
new file mode 100644
index 0000000..1a4cabc
--- /dev/null
+++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/PasteObjectSettings.h
@@ -0,0 +1,41 @@
+/* Copyright (C) 2012 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef INCLUDED_PASTEOBJECTSETTINGS
+#define INCLUDED_PASTEOBJECTSETTINGS
+
+#include <string>
+
+#include "GameInterface/Messages.h"
+
+namespace AtlasMessage
+{
+    struct sObjectSettings;
+}
+
+struct PasteObjectSettings
+{
+    AtlasMessage::Position pasteRelativePosition;
+    AtlasMessage::Position position;
+    AtlasMessage::Position target;
+    unsigned int actorSeed;
+    std::wstring templateName;
+    AtlasMessage::sObjectSettings atlasSettings;
+};
+
+#endif // INCLUDED_PASTEOBJECTSETTINGS
+
diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/PasteObjects.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/PasteObjects.cpp
new file mode 100644
index 0000000..dbc5919
--- /dev/null
+++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/PasteObjects.cpp
@@ -0,0 +1,239 @@
+/* Copyright (C) 2013 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "precompiled.h"
+
+#include <vector>
+
+#include "ScenarioEditor/ScenarioEditor.h"
+#include "Common/Tools.h"
+#include "Common/Brushes.h"
+#include "Common/MiscState.h"
+#include "Common/ObjectSettings.h"
+#include "Common/PasteObjectSettings.h"
+#include "GameInterface/Messages.h"
+
+using AtlasMessage::Position;
+
+static float g_DefaultAngle = (float)(M_PI*3.0/4.0);
+
+
+class PasteObjects : public StateDrivenTool<PasteObjects>
+{
+    DECLARE_DYNAMIC_CLASS(PasteObjects);
+
+    Position m_ScreenPos, m_ObjPos, m_Target;
+    wxString m_ObjectID;
+    unsigned int m_ActorSeed;
+
+    std::vector<PasteObjectSettings> m_objectsToPaste;
+
+public:
+    PasteObjects()
+    {
+        SetState(&Waiting);
+    }
+
+    void SendObjectMsg(bool preview)
+    {
+        int dragDistSq =
+              (m_ScreenPos.type1.x-m_Target.type1.x)*(m_ScreenPos.type1.x-m_Target.type1.x)
+            + (m_ScreenPos.type1.y-m_Target.type1.y)*(m_ScreenPos.type1.y-m_Target.type1.y);
+        bool useTarget = (dragDistSq >= 16*16);
+
+        for (std::vector<PasteObjectSettings>::iterator it = m_objectsToPaste.begin(); it != m_objectsToPaste.end(); ++it)
+        {
+            PasteObjectSettings & object = *it;
+            if (preview)
+            {
+                POST_MESSAGE(ObjectPreview, (object.templateName, object.atlasSettings, object.position,
+                    useTarget, object.target, g_DefaultAngle, object.actorSeed));
+            }
+            else
+            {
+                POST_COMMAND(CreateObject, (object.templateName, object.atlasSettings, object.position,
+                    useTarget, object.target, g_DefaultAngle, object.actorSeed));
+                RandomizeActorSeed();
+            }
+        }
+    }
+
+    virtual void Init(void* initData, ScenarioEditor* scenarioEditor)
+    {
+        StateDrivenTool<PasteObjects>::Init(initData, scenarioEditor);
+
+        wxASSERT(initData);
+        m_objectsToPaste = *static_cast<std::vector<PasteObjectSettings>*>(initData);
+        SendObjectMsg(true);
+    }
+
+    void OnEnable()
+    {
+        RandomizeActorSeed();
+    }
+
+    void OnDisable()
+    {
+        m_ObjectID = _T("");
+        SendObjectMsg(true);
+    }
+
+    /*
+        Object placement:
+        * Select unit from list
+        * Move mouse around screen; preview of unit follows mouse
+        * Left mouse down -> remember position, fix preview to point
+        * Mouse move -> if moved > [limit], rotate unit to face mouse; else default orientation
+        * Left mouse release -> finalise placement of object on map
+
+        * Page up/down -> rotate default orientation
+
+        * Escape -> cancel placement tool
+
+        TOOD: what happens if somebody saves while the preview is active?
+    */
+
+    bool OnMouseOverride(wxMouseEvent& WXUNUSED(evt))
+    {
+        // This used to let the scroll-wheel rotate units, but that overrides
+        // the camera zoom and makes navigation very awkward, so it doesn't
+        // any more.
+        return false;
+    }
+
+    bool OnKeyOverride(wxKeyEvent& evt, KeyEventType type)
+    {
+        if (type == KEY_CHAR && evt.GetKeyCode() == WXK_ESCAPE)
+        {
+            SetState(&Disabled);
+            return true;
+        }
+        else
+            return false;
+    }
+
+    void RotateTick(float dt)
+    {
+        int dir = 0;
+        if (wxGetKeyState(WXK_PAGEDOWN))  ++dir; // page-down key
+        if (wxGetKeyState(WXK_PAGEUP)) --dir; // page-up key
+        if (dir)
+        {
+            float speed = M_PI/2.f * ScenarioEditor::GetSpeedModifier(); // radians per second
+            g_DefaultAngle += (dir * dt * speed);
+            SendObjectMsg(true);
+        }
+    }
+
+    void RandomizeActorSeed()
+    {
+        m_ActorSeed = (unsigned int)floor((rand() / (float)RAND_MAX) * 65535.f);
+    }
+
+    struct sWaiting : public State
+    {
+        bool OnMouse(PasteObjects* obj, wxMouseEvent& evt)
+        {
+            if (obj->OnMouseOverride(evt))
+            {
+                return true;
+            }
+            else if (evt.LeftDown())
+            {
+                obj->m_ObjPos = obj->m_ScreenPos = obj->m_Target = Position(evt.GetPosition());
+                obj->SendObjectMsg(true);
+                obj->m_ObjPos = Position::Unchanged(); // make sure object is stationary even if the camera moves
+                SET_STATE(Placing);
+                return true;
+            }
+            else if (evt.Moving())
+            {
+                for (std::vector<PasteObjectSettings>::iterator it = obj->m_objectsToPaste.begin(); it != obj->m_objectsToPaste.end(); ++it)
+                {
+                    (*it).position = Position(evt.GetPosition());
+                    (*it).position.type1.x += (*it).pasteRelativePosition.type1.x;
+                    (*it).position.type1.y += (*it).pasteRelativePosition.type1.y;
+                    (*it).target = (*it).position;
+                    obj->SendObjectMsg(true);
+                }
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        bool OnKey(PasteObjects* obj, wxKeyEvent& evt, KeyEventType type)
+        {
+            if (type == KEY_CHAR && (evt.GetKeyCode() >= '0' && evt.GetKeyCode() <= '9'))
+            {
+                int playerID = evt.GetKeyCode() - '0';
+                obj->GetScenarioEditor().GetObjectSettings().SetPlayerID(playerID);
+                obj->GetScenarioEditor().GetObjectSettings().NotifyObservers();
+                obj->SendObjectMsg(true);
+                return true;
+            }
+            else
+                return obj->OnKeyOverride(evt, type);
+        }
+        void OnTick(PasteObjects* obj, float dt)
+        {
+            obj->RotateTick(dt);
+        }
+    }
+    Waiting;
+
+    struct sPlacing : public State
+    {
+        bool OnMouse(PasteObjects* obj, wxMouseEvent& evt)
+        {
+            if (obj->OnMouseOverride(evt))
+                return true;
+            else if (evt.LeftUp())
+            {
+                obj->m_Target = Position(evt.GetPosition());
+                // Create the actual object
+                obj->SendObjectMsg(false);
+                // Go back to preview mode
+                SET_STATE(Waiting);
+                obj->m_ObjPos = obj->m_ScreenPos = obj->m_Target;
+                obj->SendObjectMsg(true);
+                return true;
+            }
+            else if (evt.Dragging())
+            {
+                obj->m_Target = Position(evt.GetPosition());
+                obj->SendObjectMsg(true);
+                return true;
+            }
+            else
+                return false;
+        }
+        bool OnKey(PasteObjects* obj, wxKeyEvent& evt, KeyEventType type)
+        {
+            return obj->OnKeyOverride(evt, type);
+        }
+        void OnTick(PasteObjects* obj, float dt)
+        {
+            obj->RotateTick(dt);
+        }
+    }
+    Placing;
+};
+
+IMPLEMENT_DYNAMIC_CLASS(PasteObjects, StateDrivenTool<PasteObjects>);
diff --git a/source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp b/source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp
index ea36267..12eba27 100644
--- a/source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp
+++ b/source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp
@@ -38,6 +38,7 @@
 #include "ps/CLogger.h"
 #include "ps/Game.h"
 #include "ps/World.h"
+#include "ps/XML/XMLWriter.h"
 #include "renderer/Renderer.h"
 #include "renderer/WaterManager.h"
 #include "simulation2/Simulation2.h"
@@ -95,6 +96,37 @@ QUERYHANDLER(GetObjectsList)
 }
 
 
+QUERYHANDLER(GetXmlForObjects)
+{
+    std::string xml;
+
+    std::vector<AtlasMessage::ObjectID> objects = *msg->objects;
+
+    CSimulation2 & simulation = *g_Game->GetSimulation2();
+
+    XML_Start();
+    XML_Element("Entities");
+
+    CSimulation2::InterfaceList ents = simulation.GetEntitiesWithInterface(IID_Position);
+    for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
+    {
+        entity_id_t uid = it->first;
+        if (objects.end() != std::find(objects.begin(), objects.end(), uid))
+        {
+            XML_Element("Entity");
+            XML_Attribute("uid", uid);
+
+            objects.erase(std::remove(objects.begin(), objects.end(), uid), objects.end());
+        }
+    }
+
+    xml = XML_GetOutput().EscapeToPrintableASCII();
+
+    std::wstring wxml(xml.begin(), xml.end());
+
+    msg->xml = wxml;
+}
+
 static std::vector<entity_id_t> g_Selection;
 typedef std::map<player_id_t, CColor> PlayerColorMap;
 
@@ -366,6 +398,45 @@ MESSAGEHANDLER(ObjectPreview)
 	}
 }
 
+MESSAGEHANDLER(PasteObjectPreview)
+{
+    if (g_PreviewEntityID != INVALID_ENTITY)
+    {
+        // Update the unit's position and orientation:
+
+        CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), g_PreviewEntityID);
+        if (cmpPosition)
+        {
+            CVector3D pos = GetUnitPos(msg->pos, cmpPosition->IsFloating());
+            cmpPosition->JumpTo(entity_pos_t::FromFloat(pos.X), entity_pos_t::FromFloat(pos.Z));
+
+            float angle;
+            if (msg->usetarget)
+            {
+                // Aim from pos towards msg->target
+                CVector3D target = msg->target->GetWorldSpace(pos.Y);
+                angle = atan2(target.X-pos.X, target.Z-pos.Z);
+            }
+            else
+            {
+                angle = msg->angle;
+            }
+
+            cmpPosition->SetYRotation(entity_angle_t::FromFloat(angle));
+        }
+
+        // TODO: handle random variations somehow
+
+        CmpPtr<ICmpVisual> cmpVisual(*g_Game->GetSimulation2(), g_PreviewEntityID);
+        if (cmpVisual)
+            cmpVisual->SetActorSeed(msg->actorseed);
+
+        CmpPtr<ICmpOwnership> cmpOwnership(*g_Game->GetSimulation2(), g_PreviewEntityID);
+        if (cmpOwnership)
+            cmpOwnership->SetOwner((player_id_t)msg->settings->player);
+    }
+}
+
 BEGIN_COMMAND(CreateObject)
 {
 	CVector3D m_Pos;
diff --git a/source/tools/atlas/GameInterface/Messages.h b/source/tools/atlas/GameInterface/Messages.h
index 48ce280..2a1431b 100644
--- a/source/tools/atlas/GameInterface/Messages.h
+++ b/source/tools/atlas/GameInterface/Messages.h
@@ -320,6 +320,12 @@ QUERY(GetObjectsList,
 	  ((std::vector<sObjectsListItem>, objects)) // sorted by .name
 	  );
 
+QUERY(GetXmlForObjects,
+        ((std::vector<AtlasMessage::ObjectID>, objects))
+        ,
+        ((std::wstring, xml))
+        );
+
 #ifndef MESSAGES_SKIP_STRUCTS
 struct sObjectSettings
 {
@@ -346,6 +352,16 @@ MESSAGE(ObjectPreview,
 		((unsigned int, actorseed))
 		);
 
+MESSAGE(PasteObjectPreview,
+        ((std::wstring, id)) // or empty string => disable
+        ((sObjectSettings, settings))
+        ((Position, pos))
+        ((bool, usetarget)) // true => use 'target' for orientation; false => use 'angle'
+        ((Position, target))
+        ((float, angle))
+        ((unsigned int, actorseed))
+        );
+
 COMMAND(CreateObject, NOMERGE,
 		((std::wstring, id))
 		((sObjectSettings, settings))
@@ -366,6 +382,7 @@ MESSAGE(SetActorViewer,
 		((bool, flushcache)) // true => unload all actor files before starting the preview (because we don't have proper hotloading yet)
 		);
 
+
 //////////////////////////////////////////////////////////////////////////
 
 QUERY(Exit,,); // no inputs nor outputs
