From 1ca15f3b97aeaa80aa160e7317018834d70a7ffc Mon Sep 17 00:00:00 2001
From: D-AIRY <admin@ds-servers.com>
Date: Mon, 29 Jun 2020 19:34:11 +0300
Subject: [PATCH] TerraX Copypaste

---
 build/config_editor.cfg                   |   2 +-
 proj/terrax/vs2013/terrax.vcxproj         |   2 +
 proj/terrax/vs2013/terrax.vcxproj.filters |   6 +
 source/core/Config.cpp                    |  17 +-
 source/core/Config.h                      |   2 +
 source/game/EditorObject.cpp              |   1 +
 source/geom/plugin_main.cpp               | 138 ++++++++++-
 source/terrax/CommandDelete.cpp           |   2 +-
 source/terrax/CommandPaste.cpp            |  96 ++++++++
 source/terrax/CommandPaste.h              |  44 ++++
 source/terrax/CommandProperties.cpp       |   4 +-
 source/terrax/mainWindow.cpp              | 288 +++++++++++++++++++++-
 source/xcommon/IXConfig.h                 |   2 +
 source/xcommon/editor/IXEditorObject.h    |   1 +
 14 files changed, 589 insertions(+), 16 deletions(-)
 create mode 100644 source/terrax/CommandPaste.cpp
 create mode 100644 source/terrax/CommandPaste.h

diff --git a/build/config_editor.cfg b/build/config_editor.cfg
index 4fb54a4de..1f0c7fee8 100644
--- a/build/config_editor.cfg
+++ b/build/config_editor.cfg
@@ -2,7 +2,7 @@ echo "Executing editor config file"
 
 cl_mode_editor 1
 
-dbg_config_save 1
+// dbg_config_save 1
 
 r_stats 2
 
diff --git a/proj/terrax/vs2013/terrax.vcxproj b/proj/terrax/vs2013/terrax.vcxproj
index 9afc56dd3..667772490 100644
--- a/proj/terrax/vs2013/terrax.vcxproj
+++ b/proj/terrax/vs2013/terrax.vcxproj
@@ -206,6 +206,7 @@
     <ClCompile Include="..\..\..\source\terrax\CommandCreate.cpp" />
     <ClCompile Include="..\..\..\source\terrax\CommandDelete.cpp" />
     <ClCompile Include="..\..\..\source\terrax\CommandMove.cpp" />
+    <ClCompile Include="..\..\..\source\terrax\CommandPaste.cpp" />
     <ClCompile Include="..\..\..\source\terrax\CommandProperties.cpp" />
     <ClCompile Include="..\..\..\source\terrax\CommandRotate.cpp" />
     <ClCompile Include="..\..\..\source\terrax\CommandScale.cpp" />
@@ -232,6 +233,7 @@
     <ClInclude Include="..\..\..\source\terrax\CommandCreate.h" />
     <ClInclude Include="..\..\..\source\terrax\CommandDelete.h" />
     <ClInclude Include="..\..\..\source\terrax\CommandMove.h" />
+    <ClInclude Include="..\..\..\source\terrax\CommandPaste.h" />
     <ClInclude Include="..\..\..\source\terrax\CommandProperties.h" />
     <ClInclude Include="..\..\..\source\terrax\CommandRotate.h" />
     <ClInclude Include="..\..\..\source\terrax\CommandScale.h" />
diff --git a/proj/terrax/vs2013/terrax.vcxproj.filters b/proj/terrax/vs2013/terrax.vcxproj.filters
index 5a19fdacb..eaee64362 100644
--- a/proj/terrax/vs2013/terrax.vcxproj.filters
+++ b/proj/terrax/vs2013/terrax.vcxproj.filters
@@ -90,6 +90,9 @@
     <ClCompile Include="..\..\..\source\terrax\Tools.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\..\source\terrax\CommandPaste.cpp">
+      <Filter>Source Files\cmd</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\..\..\source\terrax\terrax.rc">
@@ -166,6 +169,9 @@
     <ClInclude Include="..\..\..\source\terrax\Tools.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\..\source\terrax\CommandPaste.h">
+      <Filter>Header Files\cmd</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <Image Include="..\..\..\source\terrax\resource\new.bmp">
diff --git a/source/core/Config.cpp b/source/core/Config.cpp
index 711eea85f..a89165180 100644
--- a/source/core/Config.cpp
+++ b/source/core/Config.cpp
@@ -816,7 +816,18 @@ void CConfig::clear()
 	m_vIncludes.clear();
 	m_mFinalValues.clear();
 	m_mSections.clear();
-	BaseFile = "\0";
+	BaseFile = "";
+}
+void CConfig::clear2()
+{
+	auto tmp = BaseFile;
+	clear();
+	BaseFile = tmp;
+	FILE *pF = fopen(BaseFile.c_str(), "wb");
+	if(pF)
+	{
+		fclose(pF);
+	}
 }
 
 AssotiativeArray<CConfigString, CConfig::CSection> * CConfig::getSections()
@@ -852,6 +863,10 @@ bool XMETHODCALLTYPE CXConfig::save()
 {
 	return(m_pConfig->save() == 0);
 }
+void XMETHODCALLTYPE CXConfig::clear()
+{
+	m_pConfig->clear2();
+}
 
 const char* XMETHODCALLTYPE CXConfig::getKey(const char *szSection, const char *szKey)
 {
diff --git a/source/core/Config.h b/source/core/Config.h
index 340005725..637230bf8 100644
--- a/source/core/Config.h
+++ b/source/core/Config.h
@@ -110,6 +110,7 @@ public:
 
 	void Release();
 	void clear();
+	void clear2();
 
 	AssotiativeArray<CConfigString, CSection> * getSections();
 
@@ -160,6 +161,7 @@ public:
 
 	bool XMETHODCALLTYPE open(const char *szPath) override;
 	bool XMETHODCALLTYPE save() override;
+	void XMETHODCALLTYPE clear() override;
 
 	const char* XMETHODCALLTYPE getKey(const char *szSection, const char *szKey) override;
 	const char* XMETHODCALLTYPE getKeyName(const char *szSection, UINT uIndex) override;
diff --git a/source/game/EditorObject.cpp b/source/game/EditorObject.cpp
index cd636ff5b..11199d637 100644
--- a/source/game/EditorObject.cpp
+++ b/source/game/EditorObject.cpp
@@ -94,6 +94,7 @@ void CEditorObject::_iniFieldList()
 				xField.szHelp = "";
 				xField.szKey = pField->szKey;
 				xField.szName = pField->szEdName;
+				xField.isGeneric = !fstrcmp(pField->szKey, "origin") || !fstrcmp(pField->szKey, "rotation") || !fstrcmp(pField->szKey, "scale");
 
 				m_aFields.push_back(xField);
 			}
diff --git a/source/geom/plugin_main.cpp b/source/geom/plugin_main.cpp
index 1245f35a9..cb9fc1bf6 100644
--- a/source/geom/plugin_main.cpp
+++ b/source/geom/plugin_main.cpp
@@ -14,6 +14,14 @@
 //! версия формата файла статики
 #define SX_GEOM_FILE_FORMAT_VERSION 1
 
+struct vertex_static
+{
+	float3_t Pos;  /*!< Позиция */
+	float2_t Tex;  /*!< Текстурные координаты */
+	float3_t Norm; /*!< Нормаль */
+};
+
+
 class CLevelLoadListener: public IEventListener<XEventLevel>
 {
 public:
@@ -61,6 +69,130 @@ public:
 		}
 	}
 
+	// https://dev.ds-servers.com/sip/engine/-/blob/8306303dda397cb047048559a29ea8051822fa04/source/geom/static_geom.cpp
+	bool loadOld(FILE *pFile)
+	{
+		int32_t countgroup = -1;
+		fread(&countgroup, sizeof(int32_t), 1, pFile);
+
+		for(int i = 0; i < countgroup; ++i)
+		{
+			int32_t tmpstrlen;
+			fread(&tmpstrlen, sizeof(int32_t), 1, pFile);
+			fseek(pFile, sizeof(char) * tmpstrlen, SEEK_CUR);
+
+			//записываем количество буферов в подгруппе
+			int32_t countbuffingroup = -1;
+			fread(&countbuffingroup, sizeof(int32_t), 1, pFile);
+
+			for(int k = 0; k < countbuffingroup; ++k)
+			{
+				//записываем количество моделей, которые используют данную подгруппу
+				int32_t countusingmodels;
+				fread(&countusingmodels, sizeof(int32_t), 1, pFile);
+				for(int j = 0; j < countusingmodels; ++j)
+				{
+					fseek(pFile, sizeof(int32_t), SEEK_CUR);
+				}
+
+				int32_t iCountVertex, iCountIndex;
+
+				fread(&iCountVertex, sizeof(int32_t), 1, pFile);
+				fread(&iCountIndex, sizeof(int32_t), 1, pFile);
+
+				fseek(pFile, sizeof(vertex_static)* iCountVertex, SEEK_CUR);
+			}
+		}
+
+		int32_t countmodels;
+		fread(&countmodels, sizeof(int32_t), 1, pFile);
+
+		for(int i = 0; i < countmodels; ++i)
+		{
+			int32_t countsubset;
+			fread(&countsubset, sizeof(int32_t), 1, pFile);
+
+			CBaseEntity *pEntity = SGame_CreateEntity("prop_static");
+
+			int32_t iStrLen = 0;
+			char szStr[1024];
+
+			fread(&iStrLen, sizeof(int32_t), 1, pFile);
+			fread(szStr, sizeof(char), iStrLen, pFile);
+			szStr[iStrLen] = 0;
+			pEntity->setKV("name", szStr);
+
+			fread(&iStrLen, sizeof(int32_t), 1, pFile);
+			strcpy(szStr, "meshes/");
+			fread(szStr + 7, sizeof(char), iStrLen, pFile);
+			szStr[iStrLen + 7] = 0;
+			pEntity->setKV("model", szStr);
+
+			int32_t iCountPoly;
+			fread(&iCountPoly, sizeof(int32_t), 1, pFile);
+
+			//считываем трансформации
+			//{
+			float3 vPosition, vRotation, vScale;
+			fread(&(vPosition.x), sizeof(float), 1, pFile);
+			fread(&(vPosition.y), sizeof(float), 1, pFile);
+			fread(&(vPosition.z), sizeof(float), 1, pFile);
+			pEntity->setPos(vPosition);
+
+			fread(&(vRotation.x), sizeof(float), 1, pFile);
+			fread(&(vRotation.y), sizeof(float), 1, pFile);
+			fread(&(vRotation.z), sizeof(float), 1, pFile);
+			pEntity->setOrient(SMQuaternion(-vRotation.x, 'x') * SMQuaternion(-vRotation.y, 'y') * SMQuaternion(-vRotation.z, 'z'));
+
+			fread(&(vScale.x), sizeof(float), 1, pFile);
+			fread(&(vScale.y), sizeof(float), 1, pFile);
+			fread(&(vScale.z), sizeof(float), 1, pFile);
+			sprintf_s(szStr, "%f", vScale.y);
+			pEntity->setKV("scale", szStr);
+			//}
+
+			pEntity->setKV("use_trimesh", "1");
+
+			fread(&iStrLen, sizeof(int32_t), 1, pFile);
+			if(iStrLen > 0)
+			{
+				fseek(pFile, sizeof(char) * iStrLen, SEEK_SET);
+			}
+
+
+			pEntity->setFlags(pEntity->getFlags() | EF_LEVEL | EF_EXPORT);
+#if 0
+			for(int k = 0; k < countsubset; ++k)
+			{
+				CModel::CSubSet gdb;
+				fread(&gdb, sizeof(CModel::CSubSet), 1, file);
+				m_aAllModels[i]->m_aSubSets.push_back(gdb);
+
+				char tmptex[1024];
+				sprintf(tmptex, "%s.dds", m_aAllGroups[gdb.m_idGroup]->m_sName.c_str());
+				m_aAllModels[i]->m_aIDsTexures[k] = SGCore_MtlLoad(tmptex, MTL_TYPE_GEOM);
+			}
+
+			Array<CSegment**> queue;
+			int tmpcount = 0;
+			queue.push_back(&(m_aAllModels[i]->m_pArrSplits));
+
+			while(queue.size())
+			{
+				loadSplit(queue[0], file, &queue);
+
+				queue.erase(0);
+				++tmpcount;
+			}
+#endif
+
+			break;
+		}
+
+
+		return(false);
+	}
+
 	void loadLevel(const char *szPath)
 	{
 		FILE *pFile = fopen(szPath, "rb");
@@ -79,7 +211,11 @@ public:
 
 		if(ui64Magic != SX_GEOM_MAGIC_NUM)
 		{
-			LibReport(REPORT_MSG_LEVEL_ERROR, "file static geometry [%s] is not static geometry\n", szPath);
+			fseek(pFile, 0, SEEK_SET);
+			if(!loadOld(pFile))
+			{
+				LibReport(REPORT_MSG_LEVEL_ERROR, "file static geometry [%s] is not static geometry\n", szPath);
+			}
 			fclose(pFile);
 			return;
 		}
diff --git a/source/terrax/CommandDelete.cpp b/source/terrax/CommandDelete.cpp
index f557a0e01..3da0d717c 100644
--- a/source/terrax/CommandDelete.cpp
+++ b/source/terrax/CommandDelete.cpp
@@ -28,7 +28,7 @@ bool CCommandDelete::undo()
 		pObj->pObject->setOrient(pObj->qRotate);
 
 		pObj->pObject->preSetup();
-		for(auto i = pObj->mKeyValues.begin(); i; i++)
+		for(auto i = pObj->mKeyValues.begin(); i; ++i)
 		{
 			pObj->pObject->setKV(i.first->c_str(), i.second->c_str());
 		}
diff --git a/source/terrax/CommandPaste.cpp b/source/terrax/CommandPaste.cpp
new file mode 100644
index 000000000..badb08bc2
--- /dev/null
+++ b/source/terrax/CommandPaste.cpp
@@ -0,0 +1,96 @@
+#include "CommandPaste.h"
+#include <common/aastring.h>
+
+extern AssotiativeArray<AAString, IXEditable*> g_mEditableSystems;
+
+bool CCommandPaste::exec()
+{
+	if(!m_pCommandSelect)
+	{
+		m_pCommandSelect = new CCommandSelect();
+		
+		for(UINT i = 0, l = g_pLevelObjects.size(); i < l; ++i)
+		{
+			if(g_pLevelObjects[i]->isSelected())
+			{
+				m_pCommandSelect->addDeselected(i);
+			}
+		}
+	}
+
+	m_pCommandSelect->exec();
+
+	_paste_obj *pObj;
+	for(UINT i = 0, l = m_aObjects.size(); i < l; ++i)
+	{
+		pObj = &m_aObjects[i];
+
+		pObj->pObject->setSelected(true);
+		pObj->pObject->create();
+
+		pObj->pObject->setPos(pObj->vPos);
+		pObj->pObject->setScale(pObj->vScale);
+		pObj->pObject->setOrient(pObj->qRotate);
+		pObj->pObject->preSetup();
+		for(auto i = pObj->mKeyValues.begin(); i; ++i)
+		{
+			pObj->pObject->setKV(i.first->c_str(), i.second->c_str());
+		}
+		pObj->pObject->postSetup();
+
+		g_pLevelObjects.push_back(pObj->pObject);
+	}
+	XUpdatePropWindow();
+	return(m_aObjects.size());
+}
+bool CCommandPaste::undo()
+{
+	_paste_obj *pObj;
+	for(int i = m_aObjects.size() - 1; i >= 0; --i)
+	{
+		pObj = &m_aObjects[i];
+
+		pObj->pObject->remove();
+		g_pLevelObjects.erase(g_pLevelObjects.size() - 1);
+	}
+
+	m_pCommandSelect->undo();
+
+	XUpdatePropWindow();
+	return(true);
+}
+
+CCommandPaste::~CCommandPaste()
+{
+	for(UINT i = 0, l = m_aObjects.size(); i < l; ++i)
+	{
+		mem_release(m_aObjects[i].pObject);
+	}
+	mem_delete(m_pCommandSelect);
+}
+
+UINT CCommandPaste::addObject(const char *szTypeName, const char *szClassName, const float3 &vPos, const float3 &vScale, const SMQuaternion &qRotate)
+{
+	const AssotiativeArray<AAString, IXEditable*>::Node *pNode;
+	if(!g_mEditableSystems.KeyExists(AAString(szTypeName), &pNode))
+	{
+		LibReport(REPORT_MSG_LEVEL_ERROR, "Unknown object type %s, skipping!", szTypeName);
+		return(~0u);
+	}
+
+	_paste_obj obj;
+	obj.pObject = (*(pNode->Val))->newObject(szClassName);
+	obj.vPos = vPos;
+	obj.vScale = vScale;
+	obj.qRotate = qRotate;
+
+	m_aObjects.push_back(obj);
+
+	return(m_aObjects.size() - 1);
+}
+void CCommandPaste::setKV(UINT uId, const char *szKey, const char *szValue)
+{
+	assert(uId < m_aObjects.size());
+
+	m_aObjects[uId].mKeyValues[szKey] = szValue;
+}
diff --git a/source/terrax/CommandPaste.h b/source/terrax/CommandPaste.h
new file mode 100644
index 000000000..0f898fb20
--- /dev/null
+++ b/source/terrax/CommandPaste.h
@@ -0,0 +1,44 @@
+#ifndef _COMMAND_PASTE_H_
+#define _COMMAND_PASTE_H_
+
+#include "Command.h"
+#include "terrax.h"
+
+#include <common/assotiativearray.h>
+#include <common/string.h>
+#include <xcommon/editor/IXEditable.h>
+#include "CommandSelect.h"
+
+class CCommandPaste: public CCommand
+{
+public:
+	~CCommandPaste();
+
+	bool exec();
+	bool undo();
+
+	const char *getText()
+	{
+		return("paste");
+	}
+
+	UINT addObject(const char *szTypeName, const char *szClassName, const float3 &vPos, const float3 &vScale, const SMQuaternion &qRotate);
+	void setKV(UINT uId, const char *szKey, const char *szValue);
+
+protected:
+	struct _paste_obj
+	{
+	//	ID idObject;
+		IXEditorObject *pObject;
+		AssotiativeArray<String, String> mKeyValues;
+
+		float3_t vPos;
+		float3_t vScale;
+		SMQuaternion qRotate;
+	};
+	Array<_paste_obj> m_aObjects;
+
+	CCommandSelect *m_pCommandSelect = NULL;
+};
+
+#endif
diff --git a/source/terrax/CommandProperties.cpp b/source/terrax/CommandProperties.cpp
index 2949037de..12cd978aa 100644
--- a/source/terrax/CommandProperties.cpp
+++ b/source/terrax/CommandProperties.cpp
@@ -8,7 +8,7 @@ bool CCommandProperties::exec()
 		pObj = &m_aObjects[j];
 
 		pObj->pObject->preSetup();
-		for(auto i = pObj->mKeyValues.begin(); i; i++)
+		for(auto i = pObj->mKeyValues.begin(); i; ++i)
 		{
 			if(i.second->isChanged)
 			{
@@ -28,7 +28,7 @@ bool CCommandProperties::undo()
 		pObj = &m_aObjects[j];
 
 		pObj->pObject->preSetup();
-		for(auto i = pObj->mKeyValues.begin(); i; i++)
+		for(auto i = pObj->mKeyValues.begin(); i; ++i)
 		{
 			if(i.second->isChanged)
 			{
diff --git a/source/terrax/mainWindow.cpp b/source/terrax/mainWindow.cpp
index 2c15065db..4107d9ed2 100644
--- a/source/terrax/mainWindow.cpp
+++ b/source/terrax/mainWindow.cpp
@@ -36,9 +36,13 @@
 #include "CommandDelete.h"
 #include "CommandCreate.h"
 #include "CommandProperties.h"
+#include "CommandPaste.h"
 
 #include "PropertyWindow.h"
 
+#define CLIPBOARD_FILE "TerraX.clipboard"
+char g_szClipboardFile[MAX_PATH + sizeof(CLIPBOARD_FILE)];
+
 #include <gui/guimain.h>
 
 extern Array<IXEditorObject*> g_pLevelObjects;
@@ -297,7 +301,10 @@ BOOL XInitInstance(HINSTANCE hInstance, int nCmdShow)
 
 	XCheckMenuItem(g_hMenu, ID_VIEW_GRID, g_xConfig.m_bShowGrid);
 
-	return TRUE;
+	GetTempPathA(sizeof(g_szClipboardFile), g_szClipboardFile);
+	strcat(g_szClipboardFile, CLIPBOARD_FILE);
+
+	return(TRUE);
 }
 
 bool IsEditMessage()
@@ -387,6 +394,256 @@ LRESULT CALLBACK StatusBarWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
 	return(CallWindowProc(g_pfnStatusBarOldWndproc, hWnd, message, wParam, lParam));
 }
 
+static void DeleteSelection()
+{
+	CCommandDelete *pDelCmd = new CCommandDelete();
+	for(UINT i = 0, l = g_pLevelObjects.size(); i < l; ++i)
+	{
+		if(g_pLevelObjects[i]->isSelected())
+		{
+			pDelCmd->addObject(i);
+		}
+	}
+	XExecCommand(pDelCmd);
+}
+
+static void ToClipboard(bool isCut = false)
+{
+	IXConfig *pConfig = g_pEngine->getCore()->newConfig();
+	pConfig->open(g_szClipboardFile);
+	pConfig->clear();
+
+	UINT uCount = 0;
+	char szSection[64], szTmp[64];
+
+	for(UINT i = 0, l = g_pLevelObjects.size(); i < l; ++i)
+	{
+		IXEditorObject *pObj = g_pLevelObjects[i];
+		if(pObj->isSelected())
+		{
+			sprintf(szSection, "obj_%u_type", uCount);
+			pConfig->set("meta", szSection, pObj->getTypeName());
+
+			sprintf(szSection, "obj_%u_class", uCount);
+			pConfig->set("meta", szSection, pObj->getClassName());
+
+			float3_t vTmp = pObj->getPos();
+			sprintf(szSection, "obj_%u_pos", uCount);
+			sprintf(szTmp, "%f %f %f", vTmp.x, vTmp.y, vTmp.z);
+			pConfig->set("meta", szSection, szTmp);
+
+			vTmp = pObj->getScale();
+			sprintf(szSection, "obj_%u_scale", uCount);
+			sprintf(szTmp, "%f %f %f", vTmp.x, vTmp.y, vTmp.z);
+			pConfig->set("meta", szSection, szTmp);
+
+			SMQuaternion qTmp = pObj->getOrient();
+			sprintf(szSection, "obj_%u_orient", uCount);
+			sprintf(szTmp, "%f %f %f %f", qTmp.x, qTmp.y, qTmp.z, qTmp.w);
+			pConfig->set("meta", szSection, szTmp);
+
+			sprintf(szSection, "obj_%u", uCount);
+
+
+			for(UINT i = 0, l = pObj->getProperyCount(); i < l; ++i)
+			{
+				const X_PROP_FIELD *pField = pObj->getPropertyMeta(i);
+
+				if(!pField->isGeneric)
+				{
+					pConfig->set(szSection, pField->szKey, pObj->getKV(pField->szKey));
+				}
+			}
+
+			++uCount;
+		}
+	}
+
+	sprintf(szSection, "%u", uCount);
+	pConfig->set("meta", "count", szSection);
+
+	sprintf(szSection, "%f %f %f", g_xState.vSelectionBoundMin.x, g_xState.vSelectionBoundMin.y, g_xState.vSelectionBoundMin.z);
+	pConfig->set("meta", "aabb_min", szSection);
+	sprintf(szSection, "%f %f %f", g_xState.vSelectionBoundMax.x, g_xState.vSelectionBoundMax.y, g_xState.vSelectionBoundMax.z);
+	pConfig->set("meta", "aabb_max", szSection);
+
+	pConfig->save();
+	mem_release(pConfig);
+
+	if(isCut)
+	{
+		DeleteSelection();
+	}
+}
+
+static float GetPasteCenter(char axis, X_WINDOW_POS skipPos)
+{
+	for(UINT i = 0; i < 4; ++i)
+	{
+		if(i != skipPos)
+		{
+			X_2D_VIEW x2dView = g_xConfig.m_x2DView[i];
+			float3 vCamPos;
+			g_xConfig.m_pViewportCamera[i]->getPosition(&vCamPos);
+			switch(axis)
+			{
+			case 'x':
+				if(x2dView == X2D_TOP || x2dView == X2D_FRONT)
+				{
+					return(vCamPos.x);
+				}
+				break;
+
+			case 'y':
+				if(x2dView == X2D_FRONT || x2dView == X2D_SIDE)
+				{
+					return(vCamPos.y);
+				}
+				break;
+
+			case 'z':
+				if(x2dView == X2D_TOP || x2dView == X2D_SIDE)
+				{
+					return(vCamPos.z);
+				}
+				break;
+
+			default:
+				assert(!"Invalid axis");
+			}
+		}
+	}
+
+	return(0.0f);
+}
+
+static float3 GetPasteCenter()
+{
+	X_2D_VIEW x2dView = g_xConfig.m_x2DView[g_xState.activeWindow];
+
+	float3 vPos;
+
+	switch(x2dView)
+	{
+	case X2D_TOP:
+		vPos.x = g_xState.vWorldMousePos.x;
+		vPos.y = GetPasteCenter('y', g_xState.activeWindow);
+		vPos.z = g_xState.vWorldMousePos.y;
+		break;
+
+	case X2D_FRONT:
+		vPos.x = g_xState.vWorldMousePos.x;
+		vPos.y = g_xState.vWorldMousePos.y;
+		vPos.z = GetPasteCenter('z', g_xState.activeWindow);
+		break;
+
+	case X2D_SIDE:
+		vPos.x = GetPasteCenter('x', g_xState.activeWindow);
+		vPos.y = g_xState.vWorldMousePos.y;
+		vPos.z = g_xState.vWorldMousePos.x;
+		break;
+
+	default:
+		vPos.x = GetPasteCenter('x', XWP_TOP_LEFT);
+		vPos.y = GetPasteCenter('y', XWP_TOP_LEFT);
+		vPos.z = GetPasteCenter('z', XWP_TOP_LEFT);
+		break;
+	}
+
+	return(vPos);
+}
+
+static void FromClipboard()
+{
+	IXConfig *pConfig = g_pEngine->getCore()->newConfig();
+	if(pConfig->open(g_szClipboardFile))
+	{
+		const char *szVal = pConfig->getKey("meta", "count");
+		if(szVal)
+		{
+			UINT uCount = 0;
+			sscanf(szVal, "%u", &uCount);
+
+			SMAABB aabb;
+
+			szVal = pConfig->getKey("meta", "aabb_min");
+			if(szVal)
+			{
+				sscanf(szVal, "%f %f %f", &aabb.vMin.x, &aabb.vMin.y, &aabb.vMin.z);
+			}
+			szVal = pConfig->getKey("meta", "aabb_max");
+			if(szVal)
+			{
+				sscanf(szVal, "%f %f %f", &aabb.vMax.x, &aabb.vMax.y, &aabb.vMax.z);
+			}
+
+			aabb.vMax = SMVectorMax(aabb.vMin, aabb.vMax);
+			float3 vClipboardCenter = (aabb.vMin + aabb.vMax) * 0.5f;
+			float3 vPasteCenter = GetPasteCenter();
+			float3 vDelta = vPasteCenter - vClipboardCenter;
+
+			float3 vPos, vScale;
+			SMQuaternion qRot;
+
+			CCommandPaste *pCmd = new CCommandPaste();
+
+			char szSection[64];
+			for(UINT i = 0; i < uCount; ++i)
+			{
+				sprintf(szSection, "obj_%u_type", i);
+				const char *szTypeName = pConfig->getKey("meta", szSection);
+				sprintf(szSection, "obj_%u_class", i);
+				const char *szClassName = pConfig->getKey("meta", szSection);
+
+				if(!szTypeName || !szClassName)
+				{
+					continue;
+				}
+
+				sprintf(szSection, "obj_%u_pos", i);
+				const char *szTmp = pConfig->getKey("meta", szSection);
+				if(szTmp)
+				{
+					sscanf(szTmp, "%f %f %f", &vPos.x, &vPos.y, &vPos.z);
+				}
+
+				sprintf(szSection, "obj_%u_scale", i);
+				szTmp = pConfig->getKey("meta", szSection);
+				if(szTmp)
+				{
+					sscanf(szTmp, "%f %f %f", &vScale.x, &vScale.y, &vScale.z);
+				}
+
+				sprintf(szSection, "obj_%u_orient", i);
+				szTmp = pConfig->getKey("meta", szSection);
+				if(szTmp)
+				{
+					sscanf(szTmp, "%f %f %f %f", &qRot.x, &qRot.y, &qRot.z, &qRot.w);
+				}
+
+				sprintf(szSection, "obj_%u", i);
+
+
+				UINT uObj = pCmd->addObject(szTypeName, szClassName, vPos + vDelta, vScale, qRot);
+
+				for(UINT j = 0, jl = pConfig->getKeyCount(szSection); j < jl; ++j)
+				{
+					const char *szKey = pConfig->getKeyName(szSection, j);
+					const char *szValue = pConfig->getKey(szSection, szKey);
+
+					pCmd->setKV(uObj, szKey, szValue);
+				}
+			}
+
+			XExecCommand(pCmd);
+		}
+	}
+
+	// create objects
+
+	mem_release(pConfig);
+}
+
 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
 {
 	RECT rect;
@@ -654,6 +911,16 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
 			EnableMenuItem(hMenu, ID_EDIT_UNDO, MF_ENABLED);
 			EnableMenuItem(hMenu, ID_EDIT_REDO, MF_ENABLED);
 		}
+		else
+		{
+			bool hasSelection = g_xState.bHasSelection;
+
+			HMENU hMenu = (HMENU)wParam;
+			EnableMenuItem(hMenu, ID_EDIT_CUT, hasSelection ? MF_ENABLED : MF_DISABLED);
+			EnableMenuItem(hMenu, ID_EDIT_COPY, hasSelection ? MF_ENABLED : MF_DISABLED);
+			EnableMenuItem(hMenu, ID_EDIT_DELETE, hasSelection ? MF_ENABLED : MF_DISABLED);
+			EnableMenuItem(hMenu, ID_EDIT_PASTE, GetFileAttributesA(g_szClipboardFile) != ~0 ? MF_ENABLED : MF_DISABLED);
+		}
 		XUpdateUndoRedo();
 
 		break;
@@ -886,6 +1153,8 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
 				SendMessage(GetFocus(), WM_COPY, 0, 0);
 				break;
 			}
+			
+			ToClipboard();
 			break;
 
 		case ID_EDIT_CUT:
@@ -894,6 +1163,8 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
 				SendMessage(GetFocus(), WM_CUT, 0, 0);
 				break;
 			}
+			
+			ToClipboard(true);
 			break;
 
 		case ID_EDIT_PASTE:
@@ -902,6 +1173,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
 				SendMessage(GetFocus(), WM_PASTE, 0, 0);
 				break;
 			}
+			else
+			{
+				FromClipboard();
+			}
 			break;
 
 		case ID_EDIT_DELETE:
@@ -914,15 +1189,8 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
 				break;
 			}
 
-			CCommandDelete *pDelCmd = new CCommandDelete();
-			for(UINT i = 0, l = g_pLevelObjects.size(); i < l; ++i)
-			{
-				if(g_pLevelObjects[i]->isSelected())
-				{
-					pDelCmd->addObject(i);
-				}
-			}
-			XExecCommand(pDelCmd);
+			DeleteSelection();
+
 			break;
 		}
 
diff --git a/source/xcommon/IXConfig.h b/source/xcommon/IXConfig.h
index 589b89f89..fe3dc7c9e 100644
--- a/source/xcommon/IXConfig.h
+++ b/source/xcommon/IXConfig.h
@@ -22,6 +22,8 @@ public:
 	virtual UINT XMETHODCALLTYPE getKeyCount(const char *szSection) = 0; //!< общее количество ключей в секции
 	virtual bool XMETHODCALLTYPE sectionExists(const char *szSection) = 0; //!< существует ли секция section
 	virtual bool XMETHODCALLTYPE keyExists(const char *szSection, const char *szKey) = 0; //!< существует ли ключ key в секции section
+
+	virtual void XMETHODCALLTYPE clear() = 0; //!< очистить
 };
 
 #endif
diff --git a/source/xcommon/editor/IXEditorObject.h b/source/xcommon/editor/IXEditorObject.h
index ca22814e9..3f237d8f3 100644
--- a/source/xcommon/editor/IXEditorObject.h
+++ b/source/xcommon/editor/IXEditorObject.h
@@ -22,6 +22,7 @@ struct X_PROP_FIELD
 	X_PROP_EDITOR_TYPE editorType;
 	const void *pEditorData;
 	const char *szHelp;
+	bool isGeneric;
 };
 
 class IXEditorObject: public IXUnknown
-- 
GitLab