diff --git a/build/gamesource/config/entities/defaults.ent b/build/gamesource/config/entities/defaults.ent
index 7afee2706ba3b69ee7ad261ea2c2882be74164b4..90ac725b69c74c9beec04828028fbd8c0b7ea0e2 100644
--- a/build/gamesource/config/entities/defaults.ent
+++ b/build/gamesource/config/entities/defaults.ent
@@ -43,3 +43,7 @@ inv_equip_type = 1
 [func_narrow_passage]
 speed = 0.5
 up_point = 2 0 0
+
+[func_doghole]
+speed = 0.5
+up_point = 2 0 0
diff --git a/proj/sxgame/vs2013/sxgame.vcxproj b/proj/sxgame/vs2013/sxgame.vcxproj
index ed2254be7a058b4d6c1e7c30706b82c90624826a..8aa785a032493670f5738a00d3c180ec7ba75c9a 100644
--- a/proj/sxgame/vs2013/sxgame.vcxproj
+++ b/proj/sxgame/vs2013/sxgame.vcxproj
@@ -193,6 +193,7 @@
     <ClCompile Include="..\..\..\source\game\CraftSystem.cpp" />
     <ClCompile Include="..\..\..\source\game\crosshair.cpp" />
     <ClCompile Include="..\..\..\source\game\CrosshairManager.cpp" />
+    <ClCompile Include="..\..\..\source\game\DogholeMovementController.cpp" />
     <ClCompile Include="..\..\..\source\game\Editable.cpp" />
     <ClCompile Include="..\..\..\source\game\EditorExtension.cpp" />
     <ClCompile Include="..\..\..\source\game\EditorObject.cpp" />
@@ -201,6 +202,7 @@
     <ClCompile Include="..\..\..\source\game\EntityList.cpp" />
     <ClCompile Include="..\..\..\source\game\EntityManager.cpp" />
     <ClCompile Include="..\..\..\source\game\EnvSkybox.cpp" />
+    <ClCompile Include="..\..\..\source\game\FuncDoghole.cpp" />
     <ClCompile Include="..\..\..\source\game\FuncLadder.cpp" />
     <ClCompile Include="..\..\..\source\game\FuncNarrowPassage.cpp" />
     <ClCompile Include="..\..\..\source\game\FuncRotating.cpp" />
@@ -280,6 +282,7 @@
     <ClInclude Include="..\..\..\source\game\BaseTrigger.h" />
     <ClInclude Include="..\..\..\source\game\BaseCharacter.h" />
     <ClInclude Include="..\..\..\source\game\CraftSystem.h" />
+    <ClInclude Include="..\..\..\source\game\DogholeMovementController.h" />
     <ClInclude Include="..\..\..\source\game\Editable.h" />
     <ClInclude Include="..\..\..\source\game\EditorExtension.h" />
     <ClInclude Include="..\..\..\source\game\EditorObject.h" />
@@ -287,6 +290,7 @@
     <ClInclude Include="..\..\..\source\game\EntityList.h" />
     <ClInclude Include="..\..\..\source\game\EntityPointer.h" />
     <ClInclude Include="..\..\..\source\game\EnvSkybox.h" />
+    <ClInclude Include="..\..\..\source\game\FuncDoghole.h" />
     <ClInclude Include="..\..\..\source\game\FuncLadder.h" />
     <ClInclude Include="..\..\..\source\game\FuncNarrowPassage.h" />
     <ClInclude Include="..\..\..\source\game\FuncRotating.h" />
diff --git a/proj/sxgame/vs2013/sxgame.vcxproj.filters b/proj/sxgame/vs2013/sxgame.vcxproj.filters
index a55e5221581f52b1d4122d9d45a574bf1fd7af2e..9a6014e06dfe003fc72058e0290c008fab2d51ca 100644
--- a/proj/sxgame/vs2013/sxgame.vcxproj.filters
+++ b/proj/sxgame/vs2013/sxgame.vcxproj.filters
@@ -384,6 +384,12 @@
     <ClCompile Include="..\..\..\source\game\NarrowPassageMovementController.cpp">
       <Filter>Source Files\character_movement</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\..\source\game\FuncDoghole.cpp">
+      <Filter>Source Files\ents\func\mover</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\source\game\DogholeMovementController.cpp">
+      <Filter>Source Files\character_movement</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\source\game\sxgame.h">
@@ -668,6 +674,12 @@
     <ClInclude Include="..\..\..\source\game\NarrowPassageMovementController.h">
       <Filter>Header Files\character_movement</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\..\source\game\FuncDoghole.h">
+      <Filter>Header Files\ents\func\mover</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\source\game\DogholeMovementController.h">
+      <Filter>Header Files\character_movement</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\..\..\source\game\sxgame.rc">
diff --git a/sdks/bullet3 b/sdks/bullet3
index bdfeafa1e86c54d6a561fea9ad39d38e2d7a623f..0af01e52bc9053b53d344d52c7258d4e287d718d 160000
--- a/sdks/bullet3
+++ b/sdks/bullet3
@@ -1 +1 @@
-Subproject commit bdfeafa1e86c54d6a561fea9ad39d38e2d7a623f
+Subproject commit 0af01e52bc9053b53d344d52c7258d4e287d718d
diff --git a/source/core/ModelPhysbox.cpp b/source/core/ModelPhysbox.cpp
index 98104702b185f81164b32be48533fd2db5737f67..2674436f60295dde933039254ebf7eb12b0a8fad 100644
--- a/source/core/ModelPhysbox.cpp
+++ b/source/core/ModelPhysbox.cpp
@@ -85,6 +85,15 @@ float3_t * XMETHODCALLTYPE CModelPhysboxConvex::getData()
 	return(m_pData);
 }
 
+void XMETHODCALLTYPE CModelPhysboxConvex::setMargin(float fMargin)
+{
+	m_fMargin = fMargin;
+}
+float XMETHODCALLTYPE CModelPhysboxConvex::getMargin() const
+{
+	return(m_fMargin);
+}
+
 void XMETHODCALLTYPE CModelPhysboxConvex::initData(UINT uVertexCount, const float3_t *pData)
 {
 	mem_delete_a(m_pData);
diff --git a/source/core/ModelPhysbox.h b/source/core/ModelPhysbox.h
index 6cd956f36f25d789d0887930585f31bd86577dbb..4813755a0b42c022b13f1d4623e6a4605356fceb 100644
--- a/source/core/ModelPhysbox.h
+++ b/source/core/ModelPhysbox.h
@@ -118,9 +118,13 @@ public:
 	void XMETHODCALLTYPE initData(UINT uVertexCount, const float3_t *pData = NULL) override;
 	float3_t * XMETHODCALLTYPE getData() override;
 
+	void XMETHODCALLTYPE setMargin(float fMargin) override;
+	float XMETHODCALLTYPE getMargin() const override;
+
 protected:
 	UINT m_uVertexCount = 0;
 	float3_t *m_pData = NULL;
+	float m_fMargin = 0.04f;
 };
 
 class CModelPhysboxCylinder: public IModelPhysboxImplementation<IModelPhysboxCylinder>
diff --git a/source/dseplugin/ModelFile.h b/source/dseplugin/ModelFile.h
index 7f841898d4d7fae0fe55c35a0a2d5d3eb18c6bb0..56d041d35594771244261d29e09ab148e9f23d48 100644
--- a/source/dseplugin/ModelFile.h
+++ b/source/dseplugin/ModelFile.h
@@ -372,7 +372,8 @@ enum HITBOX_TYPE
 	HT_CYLINDER, /*!< Цилиндр */
 	HT_CAPSULE,  /*!< Капсула */
 	HT_ELIPSOID,  /*!< Эллипсойд */
-	HT_CONVEX
+	HT_CONVEX, /*!< Выпуклая оболочка */
+	HT_CONVEX2 /*!< Выпуклая оболочка с указанием маргина*/
 };
 
 /*! Типы частей тела для хитбоксов
@@ -414,8 +415,15 @@ struct ModelPhyspartDataConvex: public ModelPhyspartData
 	uint32_t iVertCount = 0;
 	float3_t *pVerts = NULL;
 };
+struct ModelPhyspartDataConvex2: public ModelPhyspartData
+{
+	uint32_t iVertCount = 0;
+	float fMargin = 0.04f;
+	float3_t *pVerts = NULL;
+};
 #pragma pack(pop)
 #define MODEL_PHYSPART_DATA_CONVEX_STRUCT_SIZE sizeof(uint32_t)
+#define MODEL_PHYSPART_DATA_CONVEX2_STRUCT_SIZE (sizeof(uint32_t) + sizeof(float))
 
 /*! Дескриптор хитбокса
 */
diff --git a/source/dseplugin/ModelLoader.cpp b/source/dseplugin/ModelLoader.cpp
index 3f3c865663df75f66b6d3bcee69273897bc33ea4..09a80276e9cef9da71a9bdb96f4521147f27e611 100644
--- a/source/dseplugin/ModelLoader.cpp
+++ b/source/dseplugin/ModelLoader.cpp
@@ -662,7 +662,7 @@ bool XMETHODCALLTYPE CModelLoader::loadAsAnimated(IXResourceModelAnimated *pReso
 									IModelPhysboxConvex *pConvex = pResource->newPhysboxConvex();
 
 									m_pCurrentFile->setPos((size_t)pHitboxes[i].hitbox.iDataOffset);
-									ModelPhyspartDataConvex *pData = (ModelPhyspartDataConvex *)alloca(sizeof(ModelPhyspartDataConvex));
+									ModelPhyspartDataConvex *pData = (ModelPhyspartDataConvex*)alloca(sizeof(ModelPhyspartDataConvex));
 									m_pCurrentFile->readBin(pData, MODEL_PHYSPART_DATA_CONVEX_STRUCT_SIZE);
 
 									pConvex->initData(pData->iVertCount);
@@ -671,6 +671,21 @@ bool XMETHODCALLTYPE CModelLoader::loadAsAnimated(IXResourceModelAnimated *pReso
 									pPhysbox = pConvex;
 								}
 								break;
+							case HT_CONVEX2:
+								{
+									IModelPhysboxConvex *pConvex = pResource->newPhysboxConvex();
+
+									m_pCurrentFile->setPos((size_t)pHitboxes[i].hitbox.iDataOffset);
+									ModelPhyspartDataConvex2 *pData = (ModelPhyspartDataConvex2*)alloca(sizeof(ModelPhyspartDataConvex2));
+									m_pCurrentFile->readBin(pData, MODEL_PHYSPART_DATA_CONVEX2_STRUCT_SIZE);
+
+									pConvex->initData(pData->iVertCount);
+									pConvex->setMargin(pData->fMargin);
+									m_pCurrentFile->readBin(pConvex->getData(), sizeof(float3_t) * pData->iVertCount);
+
+									pPhysbox = pConvex;
+								}
+								break;
 								//default:
 									//LibReport(REPORT_MSG_LEVEL_WARNING, "Unknown physbox type");
 							}
@@ -750,6 +765,22 @@ bool CModelLoader::loadGeneric(IXResourceModel *pResource)
 					pPhysbox = pConvex;
 				}
 				break;
+			case HT_CONVEX2:
+				{
+					IModelPhysboxConvex *pConvex = pResource->newPhysboxConvex();
+
+					m_pCurrentFile->setPos((size_t)pPhysparts[i].iDataOffset);
+					ModelPhyspartDataConvex2 *pData = (ModelPhyspartDataConvex2*)alloca(sizeof(ModelPhyspartDataConvex2));
+					m_pCurrentFile->readBin(pData, MODEL_PHYSPART_DATA_CONVEX2_STRUCT_SIZE);
+
+					pConvex->setMargin(pData->fMargin);
+
+					pConvex->initData(pData->iVertCount);
+					m_pCurrentFile->readBin(pConvex->getData(), sizeof(float3_t) * pData->iVertCount);
+
+					pPhysbox = pConvex;
+				}
+				break;
 			//default:
 				//LibReport(REPORT_MSG_LEVEL_WARNING, "Unknown physbox type");
 			}
diff --git a/source/dseplugin/ModelWriter.cpp b/source/dseplugin/ModelWriter.cpp
index 1feec1914331ac36815093d23c9b74abdce54cf7..715b1e40045c560e80f8147f689ee7de6e0520b6 100644
--- a/source/dseplugin/ModelWriter.cpp
+++ b/source/dseplugin/ModelWriter.cpp
@@ -138,17 +138,17 @@ bool XMETHODCALLTYPE CModelWriter::writeModel(IXResourceModel *pResource, IFile
 					part.lwh.y = pPhysbox->asCapsule()->getHeight();
 					break;
 				case XPBT_CONVEX:
-					part.type = HT_CONVEX;
+					part.type = HT_CONVEX2;
 					part.iDataOffset = pFile->getPos();
 
 					IModelPhysboxConvex *pConvex = pPhysbox->asConvex();
 
-					ModelPhyspartDataConvex data = {};
+					ModelPhyspartDataConvex2 data = {};
 					data.iVertCount = pConvex->getVertexCount();
-					pFile->writeBin(&data, MODEL_PHYSPART_DATA_CONVEX_STRUCT_SIZE);
+					data.fMargin = pConvex->getMargin();
+					pFile->writeBin(&data, MODEL_PHYSPART_DATA_CONVEX2_STRUCT_SIZE);
 					pFile->writeBin(pConvex->getData(), sizeof(float3_t) * data.iVertCount);
-
-
+					
 					break;
 				}
 			}
diff --git a/source/exporter_base/Exporter.cpp b/source/exporter_base/Exporter.cpp
index 7fc5521010001bd7aaaafe49ea8b10456db4b853..ed121eb081db3293ef16ae00ab7aa2455a117520 100644
--- a/source/exporter_base/Exporter.cpp
+++ b/source/exporter_base/Exporter.cpp
@@ -422,6 +422,18 @@ void CExporter::loadPhysdata(FILE *pf)
 					fread(pData->pVerts, sizeof(float3_t), pData->iVertCount, pf);
 				}
 				break;
+			case HT_CONVEX:
+				{
+					_fseeki64(pf, m_aPhysParts[i].iDataOffset, SEEK_SET);
+
+					ModelPhyspartDataConvex2 *pData = new ModelPhyspartDataConvex2();
+					m_aPhysParts[i].pData = pData;
+					fread(m_aPhysParts[i].pData, MODEL_PHYSPART_DATA_CONVEX2_STRUCT_SIZE, 1, pf);
+
+					pData->pVerts = new float3_t[pData->iVertCount];
+					fread(pData->pVerts, sizeof(float3_t), pData->iVertCount, pf);
+				}
+				break;
 			}
 		}
 		
@@ -674,6 +686,17 @@ void CExporter::loadChunks(FILE *pf)
 							m_aHitboxesEx[j].hitbox.pData = pData;
 							fread(m_aHitboxesEx[j].hitbox.pData, MODEL_PHYSPART_DATA_CONVEX_STRUCT_SIZE, 1, pf);
 
+							pData->pVerts = new float3_t[pData->iVertCount];
+							fread(pData->pVerts, sizeof(float3_t), pData->iVertCount, pf);
+						}
+						else if(m_aHitboxesEx[j].hitbox.type == HT_CONVEX2)
+						{
+							_fseeki64(pf, m_aHitboxesEx[j].hitbox.iDataOffset, SEEK_SET);
+
+							ModelPhyspartDataConvex2 *pData = new ModelPhyspartDataConvex2();
+							m_aHitboxesEx[j].hitbox.pData = pData;
+							fread(m_aHitboxesEx[j].hitbox.pData, MODEL_PHYSPART_DATA_CONVEX2_STRUCT_SIZE, 1, pf);
+
 							pData->pVerts = new float3_t[pData->iVertCount];
 							fread(pData->pVerts, sizeof(float3_t), pData->iVertCount, pf);
 						}
@@ -1008,6 +1031,15 @@ void CExporter::writePhysdata(FILE *pf)
 				uBaseOffset += sizeof(float3_t) * pData->iVertCount;
 			}
 			break;
+
+		case HT_CONVEX2:
+			{
+				m_aPhysParts[i].iDataOffset = uBaseOffset;
+				uBaseOffset += MODEL_PHYSPART_DATA_CONVEX2_STRUCT_SIZE;
+				ModelPhyspartDataConvex2 *pData = (ModelPhyspartDataConvex2*)m_aPhysParts[i].pData;
+				uBaseOffset += sizeof(float3_t) * pData->iVertCount;
+			}
+			break;
 		}
 	}
 
@@ -1029,6 +1061,16 @@ void CExporter::writePhysdata(FILE *pf)
 				fwrite(pData->pVerts, sizeof(float3_t), pData->iVertCount, pf);
 			}
 			break;
+
+		case HT_CONVEX2:
+			{
+				fwrite(m_aPhysParts[i].pData, MODEL_PHYSPART_DATA_CONVEX2_STRUCT_SIZE, 1, pf);
+
+				ModelPhyspartDataConvex2 *pData = (ModelPhyspartDataConvex2*)m_aPhysParts[i].pData;
+
+				fwrite(pData->pVerts, sizeof(float3_t), pData->iVertCount, pf);
+			}
+			break;
 		}
 	}
 
@@ -1204,6 +1246,13 @@ void CExporter::writeChunks(FILE *pf)
 					ModelPhyspartDataConvex *pData = (ModelPhyspartDataConvex*)m_aHitboxesEx[j].hitbox.pData;
 					uBaseOffset += sizeof(float3_t) * pData->iVertCount;
 				}
+				else if(m_aHitboxesEx[j].hitbox.type == HT_CONVEX2)
+				{
+					m_aHitboxesEx[j].hitbox.iDataOffset = uBaseOffset;
+					uBaseOffset += MODEL_PHYSPART_DATA_CONVEX2_STRUCT_SIZE;
+					ModelPhyspartDataConvex2 *pData = (ModelPhyspartDataConvex2*)m_aHitboxesEx[j].hitbox.pData;
+					uBaseOffset += sizeof(float3_t) * pData->iVertCount;
+				}
 			}
 
 			fora(j, m_aHitboxesEx)
@@ -1219,6 +1268,14 @@ void CExporter::writeChunks(FILE *pf)
 
 					ModelPhyspartDataConvex *pData = (ModelPhyspartDataConvex*)m_aHitboxesEx[j].hitbox.pData;
 
+					fwrite(pData->pVerts, sizeof(float3_t), pData->iVertCount, pf);
+				}
+				else if(m_aHitboxesEx[j].hitbox.type == HT_CONVEX2)
+				{
+					fwrite(m_aHitboxesEx[j].hitbox.pData, MODEL_PHYSPART_DATA_CONVEX2_STRUCT_SIZE, 1, pf);
+
+					ModelPhyspartDataConvex2 *pData = (ModelPhyspartDataConvex2*)m_aHitboxesEx[j].hitbox.pData;
+
 					fwrite(pData->pVerts, sizeof(float3_t), pData->iVertCount, pf);
 				}
 			}
@@ -1449,6 +1506,13 @@ void CExporter::clearPhysparts()
 				mem_delete(pData);
 			}
 			break;
+		case HT_CONVEX:
+			{
+				ModelPhyspartDataConvex2 *pData = (ModelPhyspartDataConvex2*)m_aPhysParts[i].pData;
+				mem_delete_a(pData->pVerts);
+				mem_delete(pData);
+			}
+			break;
 		}
 	}
 	m_aPhysParts.clearFast();
@@ -1461,12 +1525,19 @@ void CExporter::clearHitboxes()
 		switch(m_aHitboxesEx[i].hitbox.type)
 		{
 		case HT_CONVEX:
-		{
-			ModelPhyspartDataConvex *pData = (ModelPhyspartDataConvex*)m_aHitboxesEx[i].hitbox.pData;
-			mem_delete_a(pData->pVerts);
-			mem_delete(pData);
-		}
-		break;
+			{
+				ModelPhyspartDataConvex *pData = (ModelPhyspartDataConvex*)m_aHitboxesEx[i].hitbox.pData;
+				mem_delete_a(pData->pVerts);
+				mem_delete(pData);
+			}
+			break;
+		case HT_CONVEX2:
+			{
+				ModelPhyspartDataConvex2 *pData = (ModelPhyspartDataConvex2*)m_aHitboxesEx[i].hitbox.pData;
+				mem_delete_a(pData->pVerts);
+				mem_delete(pData);
+			}
+			break;
 		}
 	}
 	m_aHitboxesEx.clearFast();
@@ -1786,6 +1857,7 @@ void CExporter::preparePhysMesh(bool bIgnoreLayers, bool bHitbox)
 					}
 					else // HT_CONVEX
 					{
+						TODO("Adjust with collision margin!");
 						physPart.type = HT_CONVEX;
 						ModelPhyspartDataConvex *pDataConvex = new ModelPhyspartDataConvex();
 						pDataConvex->iVertCount = aVertices.size();
diff --git a/source/game/BaseAnimating.cpp b/source/game/BaseAnimating.cpp
index be8265c5c24a651bf04a010430efa3acec465855..5b37882ff0d5cecd2e175a261753e0af96906e28 100644
--- a/source/game/BaseAnimating.cpp
+++ b/source/game/BaseAnimating.cpp
@@ -284,7 +284,8 @@ void CBaseAnimating::initPhysics()
 			break;
 		case XPBT_CONVEX:
 			IXConvexHullShape *pConvexHull;
-			GetPhysics()->newConvexHullShape(pPhysbox->asConvex()->getVertexCount(), pPhysbox->asConvex()->getData(), &pConvexHull);
+			GetPhysics()->newConvexHullShape(pPhysbox->asConvex()->getVertexCount(), pPhysbox->asConvex()->getData(), &pConvexHull); 
+			pConvexHull->setMargin(pPhysbox->asConvex()->getMargin());
 			pLocalShape = pConvexHull;
 			break;
 		}
diff --git a/source/game/BaseCharacter.cpp b/source/game/BaseCharacter.cpp
index a8d01e43f0a1ff740233739206fedeaab34c8338..65d093d77880a92eabb8491252a38b3fee6d2114 100644
--- a/source/game/BaseCharacter.cpp
+++ b/source/game/BaseCharacter.cpp
@@ -232,7 +232,7 @@ void CBaseCharacter::playFootstepsSound()
 void CBaseCharacter::setPos(const float3 & pos)
 {
 	BaseClass::setPos(pos);
-	m_pGhostObject->setPosition(pos + float3(0.0f, (m_fCurrentHeight * m_fCapsHeight) * 0.5f, 0.0f));
+	m_pGhostObject->setPosition(pos + float3(0.0f, getCurrentHeight() * 0.5f, 0.0f));
 }
 
 float CBaseCharacter::getAimRange()
@@ -363,6 +363,7 @@ void CBaseCharacter::initHitboxes()
 		case XPBT_CONVEX:
 			IXConvexHullShape *pConvexHull;
 			GetPhysics()->newConvexHullShape(pPhysbox->asConvex()->getVertexCount(), pPhysbox->asConvex()->getData(), &pConvexHull);
+			pConvexHull->setMargin(pPhysbox->asConvex()->getMargin());
 			pLocalShape = pConvexHull;
 			break;
 		}
@@ -464,7 +465,7 @@ void CBaseCharacter::onPhysicsStep(float fDT)
 	if(!m_pMovementController)
 	{
 		float3 vPos = m_pGhostObject->getPosition();
-		setPos(vPos - float3(0.0f, m_fCapsHeight * m_fCurrentHeight * 0.5f, 0.0f));
+		setPos(vPos - float3(0.0f, m_pCollideShape->getHeight() * 0.5f + m_pCollideShape->getRadius(), 0.0f));
 	}
 
 	m_pHeadEnt->setOffsetPos(getHeadOffset());
diff --git a/source/game/BaseCharacter.h b/source/game/BaseCharacter.h
index 532861d1b3bbf96895b97938699d6c429c427245..a070f4c37560ff57187092fbe988fbff91a7600b 100644
--- a/source/game/BaseCharacter.h
+++ b/source/game/BaseCharacter.h
@@ -185,8 +185,9 @@ protected:
 	ID m_idQuadLast = -1;	//!< Последний валидный квад аи сетки на котором стоял игрок
 
 	float m_fCapsHeight = 1.8f;
-	float m_fCapsHeightCrouch = 1.2f;
-	float m_fCapsRadius = 0.4f;
+	float m_fCapsHeightCrouch = 0.66f;
+	float m_fCapsHeightCrawl = 0.4f;
+	float m_fCapsRadius = 0.33f;
 
 	CPointEntity *m_pHeadEnt = NULL;
 
@@ -200,6 +201,16 @@ protected:
 
 	float m_fPrevVerticalSpeed = 0.0f;
 
+	float getCurrentHeight()
+	{
+		float fHeight = m_fCapsHeight * m_fCurrentHeight;
+		if(fHeight < m_fCapsRadius * 2.0f)
+		{
+			fHeight = m_fCapsRadius * 2.0f;
+		}
+		return(fHeight);
+	}
+
 private:
 	static IEventChannel<XEventPhysicsStep> *m_pTickEventChannel;
 	CCharacterPhysicsTickEventListener m_physicsTicker;
diff --git a/source/game/BaseMover.h b/source/game/BaseMover.h
index 6323dd550a296dbe2ee587d8e6763d71bf660e23..f5259703f60daa33f17662aa14a4af649fc90f88 100644
--- a/source/game/BaseMover.h
+++ b/source/game/BaseMover.h
@@ -44,6 +44,9 @@ public:
 
 	float getSpeed();
 
+protected:
+	virtual SMAABB getBound();
+
 private:
 	void handleCharacterMount(CBaseEntity *pEntity);
 	void createPhysBody();
@@ -57,9 +60,7 @@ private:
 	void enable();
 	void disable();
 	void onPhysicsStep();
-
-	SMAABB getBound();
-
+	
 	virtual void newMovementController(IMovementController **ppOut);
 
 private:
diff --git a/source/game/DogholeMovementController.cpp b/source/game/DogholeMovementController.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a4292c0a801c985c10ad23738d4d897f28cb07c7
--- /dev/null
+++ b/source/game/DogholeMovementController.cpp
@@ -0,0 +1,135 @@
+#include "DogholeMovementController.h"
+#include "BaseCharacter.h"
+#include "FuncDoghole.h"
+#include "Player.h"
+
+CDogholeMovementController::CDogholeMovementController(CFuncDoghole *pPassage)
+{
+	m_vLadderPoint[0] = pPassage->getPos();
+	m_vLadderPoint[1] = pPassage->getUpPos();
+
+	m_vLadderDir = SMVector3Normalize(m_vLadderPoint[1] - m_vLadderPoint[0]);
+
+	m_fSpeed = pPassage->getSpeed();
+}
+CDogholeMovementController::~CDogholeMovementController()
+{
+	if(m_pCharacter)
+	{
+		m_pCharacter->getCharacterController()->setGravity(float3(0.0f, -10.0f, 0.0f));
+		((CPlayer*)m_pCharacter)->move(PM_CRAWL, false);
+	}
+}
+
+static float3 SMProjectPointOnLine(const float3 &vPos, const float3 &vStart, const float3 &vEnd)
+{
+	float3 vN = SMVector3Normalize(vEnd - vStart);
+	float fDot0 = SMVector3Dot(vN, vPos - vStart);
+	if(fDot0 <= 0.0f)
+	{
+		return(vStart);
+	}
+
+	float fDot1 = SMVector3Dot(vN, vPos - vEnd);
+	if(fDot1 >= 0.0f)
+	{
+		return(vEnd);
+	}
+
+	return(vStart + vN * fDot0);
+}
+
+void CDogholeMovementController::setCharacter(CBaseCharacter *pCharacter)
+{
+	m_pCharacter = pCharacter;
+
+	IXCharacterController *pCharacterController = pCharacter->getCharacterController();
+
+	pCharacterController->setGravity(float3(0.0f, 0.0f, 0.0f));
+	pCharacterController->setVelocityForTimeInterval(float3(0.0f, 0.0f, 0.0f), 0.0f);
+
+	m_mounting.is = true;
+	m_mounting.fFrac = 0.0f;
+	m_mounting.vStartPos = m_pCharacter->getPos();
+	m_mounting.vTargetPos = SMProjectPointOnLine(m_pCharacter->getPos(), m_vLadderPoint[0], m_vLadderPoint[1]);
+
+	((CPlayer*)pCharacter)->move(PM_CRAWL, true);
+}
+
+void CDogholeMovementController::handleMove(const float3 &vDir)
+{
+	m_vMoveDir = vDir;
+}
+
+void CDogholeMovementController::handleJump()
+{
+	m_bWillDismount = true;
+}
+
+bool CDogholeMovementController::handleUse()
+{
+	m_bWillDismount = true;
+	return(true);
+}
+
+void CDogholeMovementController::update(float fDt)
+{
+	if(m_mounting.is)
+	{
+		m_mounting.fFrac += 7.0f * fDt;
+		if(m_mounting.fFrac > 1.0f)
+		{
+			m_mounting.fFrac = 1.0f;
+			m_mounting.is = false;
+		}
+		m_pCharacter->setPos(vlerp(m_mounting.vStartPos, m_mounting.vTargetPos, m_mounting.fFrac));
+	}
+	else if(m_bWillDismount)
+	{
+		((CPlayer*)m_pCharacter)->m_vCurrentSpeed = m_vMoveDir;
+		m_pCharacter->setMovementController(NULL);
+	}
+	else if(!SMIsZero(SMVector3Length2(m_vMoveDir)))
+	{
+		float fDot = SMVector3Dot(m_vLadderDir, m_vMoveDir);
+
+		float3 vSpeed = m_vLadderDir * m_fSpeed;
+		float3 vNewPos;
+
+		if(fDot > /*-SM_PIDIV4*/ -SMToRadian(10.0f))
+		{
+			if(SMIsZero(SMVector3Length2(m_vLadderPoint[1] - m_pCharacter->getPos())))
+			{
+				m_bWillDismount = true;
+			}
+			else
+			{
+				vNewPos = m_pCharacter->getPos() + vSpeed * fDt;
+				if(SMVector3Length2(vNewPos - m_vLadderPoint[0]) > SMVector3Length2(m_vLadderPoint[1] - m_vLadderPoint[0]))
+				{
+					vNewPos = m_vLadderPoint[1];
+				}
+			}
+		}
+		else
+		{
+			if(SMIsZero(SMVector3Length2(m_vLadderPoint[0] - m_pCharacter->getPos())))
+			{
+				m_bWillDismount = true;
+			}
+			else
+			{
+				vNewPos = m_pCharacter->getPos() - vSpeed * fDt;
+				if(SMVector3Length2(vNewPos - m_vLadderPoint[1]) > SMVector3Length2(m_vLadderPoint[1] - m_vLadderPoint[0]))
+				{
+					vNewPos = m_vLadderPoint[0];
+				}
+			}
+		}
+
+		if(!m_bWillDismount)
+		{
+			m_pCharacter->setPos(vNewPos);
+		}
+	}
+}
diff --git a/source/game/DogholeMovementController.h b/source/game/DogholeMovementController.h
new file mode 100644
index 0000000000000000000000000000000000000000..c43514db5fe7f3ec2093238e6ce6af47a590bdec
--- /dev/null
+++ b/source/game/DogholeMovementController.h
@@ -0,0 +1,48 @@
+#ifndef __DOGHOLEMOVEMENTCONTROLLER_H
+#define __DOGHOLEMOVEMENTCONTROLLER_H
+
+#include "IMovementController.h"
+
+class CFuncDoghole;
+class CDogholeMovementController: public IXUnknownImplementation<IMovementController>
+{
+public:
+	CDogholeMovementController(CFuncDoghole *pPassage);
+	~CDogholeMovementController();
+
+	void setCharacter(CBaseCharacter *pCharacter) override;
+
+	void handleMove(const float3 &vDir) override;
+	void handleJump() override;
+	bool handleUse() override;
+
+	void update(float fDt) override;
+
+	bool isCrawlOrCrouchAllowed() override
+	{
+		return(true);
+	}
+
+private:
+	CBaseCharacter *m_pCharacter;
+
+	float3_t m_vLadderPoint[2];
+	float3_t m_vLadderDir;
+
+	float3_t m_vMoveDir;
+
+	float m_fSpeed = 0.5f;
+
+	struct
+	{
+		bool is = false;
+		float fFrac = 0.0f;
+		float3_t vStartPos;
+		float3_t vTargetPos;
+	}
+	m_mounting;
+
+	bool m_bWillDismount = false;
+};
+
+#endif
diff --git a/source/game/FuncDoghole.cpp b/source/game/FuncDoghole.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..281bc81530eeabfe86beddc6249c8279e6136d06
--- /dev/null
+++ b/source/game/FuncDoghole.cpp
@@ -0,0 +1,22 @@
+#include "FuncDoghole.h"
+#include "BaseCharacter.h"
+#include "DogholeMovementController.h"
+
+
+BEGIN_PROPTABLE(CFuncDoghole)
+	// empty
+END_PROPTABLE()
+
+REGISTER_ENTITY(CFuncDoghole, func_doghole);
+
+void CFuncDoghole::newMovementController(IMovementController **ppOut)
+{
+	*ppOut = new CDogholeMovementController(this);
+}
+
+SMAABB CFuncDoghole::getBound()
+{
+	SMAABB aabb = BaseClass::getBound();
+	aabb.vMax.y = 0.4f;
+	return(aabb);
+}
diff --git a/source/game/FuncDoghole.h b/source/game/FuncDoghole.h
new file mode 100644
index 0000000000000000000000000000000000000000..d45184975781d33ee9c286dab13693ef3bda9fba
--- /dev/null
+++ b/source/game/FuncDoghole.h
@@ -0,0 +1,20 @@
+#ifndef __FUNC_DOGHOLE_H
+#define __FUNC_DOGHOLE_H
+
+#include "BaseMover.h"
+
+class CFuncDoghole: public CBaseMover
+{
+	DECLARE_CLASS(CFuncDoghole, CBaseMover);
+	DECLARE_PROPTABLE();
+public:
+	DECLARE_TRIVIAL_CONSTRUCTOR();
+	
+protected:
+	SMAABB getBound() override;
+
+private:
+	void newMovementController(IMovementController **ppOut) override;
+};
+
+#endif
diff --git a/source/game/IMovementController.h b/source/game/IMovementController.h
index cf98817c7be270ed835ee1b96a9d304c2d471df6..7e2eec1e7110f33dce6eb7d8d3b5cc5c54fb88cb 100644
--- a/source/game/IMovementController.h
+++ b/source/game/IMovementController.h
@@ -14,6 +14,8 @@ public:
 	virtual bool handleUse() = 0;
 
 	virtual void update(float fDt) = 0;
+
+	virtual bool isCrawlOrCrouchAllowed() = 0;
 };
 
 #endif
diff --git a/source/game/LadderMovementController.h b/source/game/LadderMovementController.h
index 9cdf95cc03810af5b58d17a077d704c26e7fb98c..8bd1f52691fbaa7d16701dca4c338c94f2c63974 100644
--- a/source/game/LadderMovementController.h
+++ b/source/game/LadderMovementController.h
@@ -18,6 +18,11 @@ public:
 
 	void update(float fDt) override;
 
+	bool isCrawlOrCrouchAllowed() override
+	{
+		return(false);
+	}
+
 private:
 	CBaseCharacter *m_pCharacter;
 
diff --git a/source/game/NarrowPassageMovementController.h b/source/game/NarrowPassageMovementController.h
index 86ea5df9803cf059e6a70fac518ab6eee8bfbbf1..8dd8949d2ae16fab734c57721334e2bc4d29a107 100644
--- a/source/game/NarrowPassageMovementController.h
+++ b/source/game/NarrowPassageMovementController.h
@@ -18,6 +18,11 @@ public:
 
 	void update(float fDt) override;
 
+	bool isCrawlOrCrouchAllowed() override
+	{
+		return(false);
+	}
+
 private:
 	CBaseCharacter *m_pCharacter;
 
diff --git a/source/game/Player.cpp b/source/game/Player.cpp
index 7e5795e78755e42906ba76d724f4a7d5a9475f52..e35c9268dafcd85dc26d4c19ccd903f2addfb92b 100644
--- a/source/game/Player.cpp
+++ b/source/game/Player.cpp
@@ -247,166 +247,197 @@ void CPlayer::updateInput(float dt)
 		{
 			setPos(getPos() + m_pHeadEnt->getOrient() * (SMVector3Normalize(dir) * dt * 10.0f));
 		}
-		else if(m_pMovementController)
-		{
-			m_vCurrentSpeed = {0.0f, 0.0f, 0.0f};
-
-			if(m_uMoveDir & PM_JUMP)
-			{
-				m_pMovementController->handleJump();
-			}
-			m_pMovementController->handleMove(m_pHeadEnt->getOrient() * SMVector3Normalize(dir));
-			m_pMovementController->update(dt);
-		}
 		else
 		{
-			if(m_uMoveDir & PM_CROUCH || (m_fCurrentHeight < 0.99f && !m_pCharacter->canStandUp((m_fCapsHeight - m_fCapsRadius * 2.0f) * (1.0f - m_fCurrentHeight))))
+			bool bAllowCrouchCrawl = true;
+			if(m_pMovementController)
 			{
-				m_fCurrentHeight -= dt * 10.0f;
-				float fMinHeight = (m_fCapsHeightCrouch - m_fCapsRadius * 2.0f) / (m_fCapsHeight - m_fCapsRadius * 2.0f);
-				if(m_fCurrentHeight < fMinHeight)
+				m_vCurrentSpeed = {0.0f, 0.0f, 0.0f};
+
+				bAllowCrouchCrawl = m_pMovementController->isCrawlOrCrouchAllowed();
+
+				if(m_uMoveDir & PM_JUMP)
 				{
-					m_fCurrentHeight = fMinHeight;
+					m_pMovementController->handleJump();
 				}
+				m_pMovementController->handleMove(m_pHeadEnt->getOrient() * SMVector3Normalize(dir));
+				m_pMovementController->update(dt);
+
 			}
 			else
 			{
-				m_fCurrentHeight += dt * 6.0f;
-				if(m_fCurrentHeight > 1.0f)
+				dir = SMQuaternion(m_vPitchYawRoll.y, 'y') * SMVector3Normalize(dir);
+				if(m_uMoveDir & PM_CROUCH)
 				{
-					m_fCurrentHeight = 1.0f;
+					dir *= *cl_speed_crouch;
+				}
+				else if(m_uMoveDir & PM_CRAWL)
+				{
+					dir *= *cl_speed_crawl;
+				}
+				else if(m_uMoveDir & PM_RUN)
+				{
+					dir *= *cl_speed_run;
+				}
+				else
+				{
+					dir *= *cl_speed_walk;
 				}
-			}
-			m_pCollideShape->setLocalScaling(float3(1.0f, m_fCurrentHeight, 1.0f));
-
-
-			dir = SMQuaternion(m_vPitchYawRoll.y, 'y') * SMVector3Normalize(dir);
-			if(m_uMoveDir & PM_CROUCH)
-			{
-				dir *= *cl_speed_crouch;
-			}
-			else if(m_uMoveDir & PM_CRAWL)
-			{
-				dir *= *cl_speed_crawl;
-			}
-			else if(m_uMoveDir & PM_RUN)
-			{
-				dir *= *cl_speed_run;
-			}
-			else
-			{
-				dir *= *cl_speed_walk;
-			}
 
-			if((m_uMoveDir & PM_JUMP))
-			{
-				if(m_pCharacter->canJump())
+				if((m_uMoveDir & PM_JUMP))
 				{
-					if(m_canJump)
+					if(m_pCharacter->canJump())
+					{
+						if(m_canJump)
+						{
+							playFootstepsSound();
+							m_pCharacter->jump();
+							m_canJump = false;
+						}
+					}
+					else
 					{
-						playFootstepsSound();
-						m_pCharacter->jump();
 						m_canJump = false;
 					}
 				}
 				else
 				{
-					m_canJump = false;
+					m_canJump = true;
 				}
-			}
-			else
-			{
-				m_canJump = true;
-			}
 
-			m_vTargetSpeed = dir;
+				m_vTargetSpeed = dir;
 
-			if(onGround())
-			{
-				float fAccel = *cl_acceleration * dt;
-				float3 fAccelDir = m_vTargetSpeed - m_vCurrentSpeed;
-				float fMaxAccel = SMVector3Length(fAccelDir);
+				if(onGround())
+				{
+					float fAccel = *cl_acceleration * dt;
+					float3 fAccelDir = m_vTargetSpeed - m_vCurrentSpeed;
+					float fMaxAccel = SMVector3Length(fAccelDir);
 
-				m_vCurrentSpeed = (float3)(m_vCurrentSpeed + SMVector3Normalize(fAccelDir) * min(fAccel, fMaxAccel));
-			}
+					m_vCurrentSpeed = (float3)(m_vCurrentSpeed + SMVector3Normalize(fAccelDir) * min(fAccel, fMaxAccel));
+				}
 
-			m_pCharacter->setVelocityForTimeInterval(m_vCurrentSpeed, dt);
-			
+				m_pCharacter->setVelocityForTimeInterval(m_vCurrentSpeed, dt);
 
-			
 
-			if(*cl_bob)
-			{
-				float fBobCoeff = SMVector3Length(m_vCurrentSpeed) / *cl_speed_walk;
-				if(mov && m_pCharacter->onGround())
+
+
+				if(*cl_bob)
 				{
-					const float fFS1 = SM_PIDIV2;
-					const float fFS2 = SM_PI + SM_PIDIV2;
-					bool bFS1 = m_fViewbobStep < fFS1;
-					bool bFS2 = m_fViewbobStep < fFS2;
+					float fBobCoeff = SMVector3Length(m_vCurrentSpeed) / *cl_speed_walk;
+					if(mov && m_pCharacter->onGround())
+					{
+						const float fFS1 = SM_PIDIV2;
+						const float fFS2 = SM_PI + SM_PIDIV2;
+						bool bFS1 = m_fViewbobStep < fFS1;
+						bool bFS2 = m_fViewbobStep < fFS2;
 
-					
 
-					m_fViewbobStep += dt * *cl_bob_period * fBobCoeff;
-					
 
-					/*if(m_uMoveDir & PM_RUN)
-					{
+						m_fViewbobStep += dt * *cl_bob_period * fBobCoeff;
+
+
+						/*if(m_uMoveDir & PM_RUN)
+						{
 						m_fViewbobStep += dt * *cl_bob_run * 0.2f;
-					}
-					else
-					{
+						}
+						else
+						{
 						m_fViewbobStep += dt * *cl_bob_walk;
-					}*/
-					if((bFS1 && m_fViewbobStep > fFS1) || (bFS2 && m_fViewbobStep > fFS2))
-					{
-						playFootstepsSound();
-					}
-					//printf("%f\n", m_fViewbobStep);
-					while(m_fViewbobStep > SM_2PI)
-					{
-						m_fViewbobStep -= SM_2PI;
+						}*/
+						if((bFS1 && m_fViewbobStep > fFS1) || (bFS2 && m_fViewbobStep > fFS2))
+						{
+							playFootstepsSound();
+						}
+						//printf("%f\n", m_fViewbobStep);
+						while(m_fViewbobStep > SM_2PI)
+						{
+							m_fViewbobStep -= SM_2PI;
+						}
+						while(m_fViewbobStep < -SM_2PI)
+						{
+							m_fViewbobStep += SM_2PI;
+						}
 					}
-					while(m_fViewbobStep < -SM_2PI)
+					else
 					{
-						m_fViewbobStep += SM_2PI;
+						if(m_fViewbobStep > SM_PI + SM_PIDIV2)
+						{
+							m_fViewbobStep = m_fViewbobStep - SM_2PI;
+						}
+						if(m_fViewbobStep > SM_PIDIV2 || m_fViewbobStep < -SM_PIDIV2)
+						{
+							m_fViewbobStep = SM_PI - m_fViewbobStep;
+						}
+						m_fViewbobStep *= 0.7f;
 					}
+					float sin = cosf(m_fViewbobStep * 2.0f);
+					float sin2 = sinf(m_fViewbobStep);
+					m_fViewbobY = (sin * (*cl_bob_y * fBobCoeff));
+					m_fViewbobStrafe = sin2 * (*cl_bob_x * fBobCoeff);
 				}
-				else
+
+
+				//m_vWpnShakeAngles
+				float3 vel = m_pCharacter->getLinearVelocity();
+				//printf("%f, %f, %f\n", vel.x, vel.y, vel.z);
+				//vel = getDiscreteLinearVelocity();
+				//printf("%f, %f, %f\n", vel.x, vel.y, vel.z);
+
+				m_vWpnShakeAngles.x += -vel.y * 0.01f;
+				const float fMaxAng = SM_PI * 0.1f;
+				m_vWpnShakeAngles.x = clampf(m_vWpnShakeAngles.x, -fMaxAng, fMaxAng);
+			}
+
+			//LogInfo("%f; m_pCharacter->canStandUp(%f) = %d\n", m_fCurrentHeight, m_fCapsHeight - getCurrentHeight(), m_pCharacter->canStandUp(m_fCapsHeight - getCurrentHeight()));
+			float fPrevHeight = m_fCurrentHeight;
+			if(bAllowCrouchCrawl && (m_uMoveDir & (PM_CROUCH | PM_CRAWL) || (m_fCurrentHeight < 0.99f && !m_pCharacter->canStandUp(m_fCapsHeight - getCurrentHeight()))))
+			{
+				m_fCurrentHeight -= dt * 5.0f;
+				float fTargetHeight = (m_uMoveDir & PM_CRAWL) ? m_fCapsHeightCrawl : m_fCapsHeightCrouch;
+				float fMinHeight = fTargetHeight / m_fCapsHeight;
+				if(fMinHeight < 0.0f)
 				{
-					if(m_fViewbobStep > SM_PI + SM_PIDIV2)
-					{
-						m_fViewbobStep = m_fViewbobStep - SM_2PI;
-					}
-					if(m_fViewbobStep > SM_PIDIV2 || m_fViewbobStep < -SM_PIDIV2)
-					{
-						m_fViewbobStep = SM_PI - m_fViewbobStep;
-					}
-					m_fViewbobStep *= 0.7f;
+					fMinHeight = 0.0f;
+				}
+				if(m_fCurrentHeight < fMinHeight)
+				{
+					m_fCurrentHeight = fMinHeight;
+				}
+			}
+			else
+			{
+				m_fCurrentHeight += dt * 3.0f;
+				if(m_fCurrentHeight > 1.0f)
+				{
+					m_fCurrentHeight = 1.0f;
 				}
-				float sin = cosf(m_fViewbobStep * 2.0f);
-				float sin2 = sinf(m_fViewbobStep);
-				m_fViewbobY = (sin * (*cl_bob_y * fBobCoeff));
-				m_fViewbobStrafe = sin2 * (*cl_bob_x * fBobCoeff);
 			}
 
+			if(!SMIsZero(m_fCurrentHeight - fPrevHeight))
+			{
+				//LogInfo("%f\n", getPos().y);
+				m_pGhostObject->setPosition(getPos() + float3(0.0f, getCurrentHeight() * 0.5f, 0.0f));
+				m_pCollideShape->setHeight(m_fCurrentHeight * m_fCapsHeight - m_fCapsRadius * 2.0f);
+				GetPhysWorld()->removeCollisionObject(m_pGhostObject);
+				GetPhysWorld()->addCollisionObject(m_pGhostObject, CG_CHARACTER, CG_ALL & ~(CG_DEBRIS | CG_HITBOX | CG_WATER));
+
+				//LogInfo("421: %f\n", getPos().y + getCurrentHeight() * 0.5f);
+				//m_pGhostObject->setPosition(getPos() + float3(0.0f, m_pCollideShape->getHeight() * 0.5f + m_pCollideShape->getRadius(), 0.0f));
+			}
 
-			//m_vWpnShakeAngles
-			float3 vel = m_pCharacter->getLinearVelocity();
-			//printf("%f, %f, %f\n", vel.x, vel.y, vel.z);
-			//vel = getDiscreteLinearVelocity();
-			//printf("%f, %f, %f\n", vel.x, vel.y, vel.z);
+			//LogInfo("%f\n", m_fCurrentHeight * m_fCapsHeight - m_fCapsRadius * 2.0f);
+			//m_pGhostObject->setPosition(getPos() + float3(0.0f, getCurrentHeight() * 0.5f, 0.0f));
+			//m_pCollideShape->setLocalScaling(float3(1.0f, m_fCurrentHeight, 1.0f));
 
-			m_vWpnShakeAngles.x += -vel.y * 0.01f;
-			const float fMaxAng = SM_PI * 0.1f;
-			m_vWpnShakeAngles.x = clampf(m_vWpnShakeAngles.x, -fMaxAng, fMaxAng);
 		}
 
 		GameData::m_pHUDcontroller->setPlayerPos(getPos());
 
+		//LogInfo("%f\n", getPos().y);
+		//LogInfo("%f\n", m_pCollideShape->getHeight() + m_pCollideShape->getRadius() * 2.0f);
 	}
 
-	m_pActiveTool->setShakeRotation(SMQuaternion(m_vWpnShakeAngles.x, 'x') * SMQuaternion(m_vWpnShakeAngles.y, 'y') * SMQuaternion(m_vWpnShakeAngles.z, 'z'));
+	SAFE_CALL(m_pActiveTool, setShakeRotation, SMQuaternion(m_vWpnShakeAngles.x, 'x') * SMQuaternion(m_vWpnShakeAngles.y, 'y') * SMQuaternion(m_vWpnShakeAngles.z, 'z'));
 
 #ifndef _SERVER
 	if(*grab_cursor && (!*editor_mode || SSInput_GetKeyState(SIM_LBUTTON)))
diff --git a/source/game/Player.h b/source/game/Player.h
index e8416d717ab8d854ebc55ff1b85f74d24ebb1714..adea1a09b776541a20ad4c68cda0cfa6ecb200b6 100644
--- a/source/game/Player.h
+++ b/source/game/Player.h
@@ -28,6 +28,7 @@ class CPlayer: public CBaseCharacter
 	TODO("Fix that");
 	friend class CLadderMovementController;
 	friend class CNarrowPassageMovementController;
+	friend class CDogholeMovementController;
 	DECLARE_CLASS(CPlayer, CBaseCharacter);
 	DECLARE_PROPTABLE();
 public:
diff --git a/source/physics/CharacterController.cpp b/source/physics/CharacterController.cpp
index b7dee7a3abf8f8004a33b0b516219c47fef6aef5..6ca6ab4127cc5a1437ee917b02106af408badec8 100644
--- a/source/physics/CharacterController.cpp
+++ b/source/physics/CharacterController.cpp
@@ -33,7 +33,7 @@ bool XMETHODCALLTYPE CCharacterController::onGround() const
 }
 bool XMETHODCALLTYPE CCharacterController::canStandUp(float fDelta) const
 {
-	return(m_pController->canStandUp(fDelta));
+	return(m_pController->canStandUp(fDelta, m_pWorld));
 }
 bool XMETHODCALLTYPE CCharacterController::canJump() const
 {
diff --git a/source/physics/CollisionShape.cpp b/source/physics/CollisionShape.cpp
index b6d65876b36927ae04757d1336cbf06705eb77c6..5f36ec1f5145e43d99fb25f1dfe38aa1e2e81807 100644
--- a/source/physics/CollisionShape.cpp
+++ b/source/physics/CollisionShape.cpp
@@ -169,6 +169,15 @@ float XMETHODCALLTYPE CCapsuleShape::getHeight() const
 	return(m_pShape->getHalfHeight() * 2.0f);
 }
 
+void XMETHODCALLTYPE CCapsuleShape::setRadius(float fValue)
+{
+	m_pShape->setImplicitShapeDimensions(btVector3(fValue, 0.5f * getHeight(), fValue));
+}
+void XMETHODCALLTYPE CCapsuleShape::setHeight(float fValue)
+{
+	m_pShape->setImplicitShapeDimensions(btVector3(getRadius(), 0.5f * fValue, getRadius()));
+}
+
 //#############################################################################
 
 CCylinderShape::CCylinderShape(float fRadius, float fHeight):
diff --git a/source/physics/CollisionShape.h b/source/physics/CollisionShape.h
index 86b63e02f9d35084a186d192677ec9bc55669f47..ef06af00568bcc974a5bcf11654a461e799e0ab9 100644
--- a/source/physics/CollisionShape.h
+++ b/source/physics/CollisionShape.h
@@ -211,6 +211,9 @@ public:
 	float XMETHODCALLTYPE getRadius() const override;
 	float XMETHODCALLTYPE getHeight() const override;
 
+	void XMETHODCALLTYPE setRadius(float fValue) override;
+	void XMETHODCALLTYPE setHeight(float fValue) override;
+
 	IXCapsuleShape* XMETHODCALLTYPE asCapsule() override
 	{
 		return(this);
diff --git a/source/physics/Physics.cpp b/source/physics/Physics.cpp
index 2271e27f459ec3203bb5c564560d146b83da109f..34a9850cb29fc0c3916ccf4d319ebdadbfc365c2 100644
--- a/source/physics/Physics.cpp
+++ b/source/physics/Physics.cpp
@@ -6,6 +6,7 @@
 #include "CharacterController.h"
 #include "MutationObserver.h"
 #include <core/sxcore.h>
+#include <LinearMath/btGeometryUtil.h>
 
 CPhysics::CPhysics(CPhyWorld *pDefaultWorld):
 	m_pDefaultWorld(pDefaultWorld)
@@ -70,3 +71,125 @@ IXPhysicsWorld* XMETHODCALLTYPE CPhysics::getWorld(void *pReserved)
 
 	return(m_pDefaultWorld);
 }
+
+ATTRIBUTE_ALIGNED16(class) CShapeHull: public btShapeHull
+{
+public:
+	CShapeHull():
+		btShapeHull(NULL)
+	{
+
+	}
+	void setShape(const btConvexShape* shape)
+	{
+		m_shape = shape;
+	}
+
+	btAlignedObjectArray<btVector3>& getVertexArray()
+	{
+		return(m_vertices);
+	}
+};
+
+
+#define F4_BTVEC(xmf) (btVector3((xmf).x, (xmf).y, (xmf).z, (xmf).w))
+#define BTVEC_F4(btv) (float4((btv).x(), (btv).y(), (btv).z(), (btv).w()))
+
+UINT XMETHODCALLTYPE CPhysics::adjustConvexHullForMargin(UINT uPoints, const float3_t *pInPoints, IXBuffer **ppOutBuffer, float *pfMargin, byte u8Stride, bool bOptimize)
+{
+	CShapeHull tmpHull;
+	
+	if(bOptimize)
+	{
+		btConvexHullShape tmpShape((float*)pInPoints, uPoints, u8Stride);
+		tmpShape.setMargin(0);
+		tmpHull.setShape(&tmpShape);
+		tmpHull.buildHull(0);
+
+		if(!tmpHull.numVertices())
+		{
+			return(0);
+		}
+	}
+
+	btAlignedObjectArray<btVector3> planes;
+	if(tmpHull.numVertices())
+	{
+		btGeometryUtil::getPlaneEquationsFromVertices(tmpHull.getVertexArray(), planes);
+	}
+	else
+	{
+		btAlignedObjectArray<btVector3> vertices;
+		vertices.reserve(uPoints);
+		const float3_t *pIter = pInPoints;
+		for(UINT i = 0; i < uPoints; ++i)
+		{
+			vertices.push_back(F3_BTVEC(*pIter));
+			MOVE_PTR(pIter, u8Stride);
+		}
+		btGeometryUtil::getPlaneEquationsFromVertices(vertices, planes);
+	}
+
+	float fMinDist = FLT_MAX;
+	//float3 vBestNormal;
+	for(int i = 0, l = planes.size(); i < l; ++i)
+	{
+		float4 vPlane = BTVEC_F4(planes[i]);
+		
+		float fCurMin = FLT_MAX;
+		const float3_t *pIter = pInPoints;
+		for(UINT i = 0; i < uPoints; ++i)
+		{
+			float fDist = fabsf(SMVector4Dot(vPlane, float4(*pIter, 1.0f)));
+			if(fDist > 0.001 && fDist < fCurMin)
+			{
+				fCurMin = fDist;
+			}
+			MOVE_PTR(pIter, u8Stride);
+		}
+
+		if(fCurMin < fMinDist)
+		{
+			fMinDist = fCurMin;
+			//vBestNormal = vPlane;
+		}
+	}
+
+	float fMargin = fMinDist * 0.1f; // 10% margin
+	if(fMargin > CONVEX_DISTANCE_MARGIN)
+	{
+		fMargin = CONVEX_DISTANCE_MARGIN;
+	}
+
+	if(pfMargin)
+	{
+		*pfMargin = fMargin;
+	}
+
+	int sz = planes.size();
+	for(int i = 0; i<sz; ++i)
+	{
+		planes[i][3] += fMargin;
+	}
+	tmpHull.getVertexArray().clear();
+	btGeometryUtil::getVerticesFromPlaneEquations(planes, tmpHull.getVertexArray());
+
+	btAlignedObjectArray<btVector3>& aOut = tmpHull.getVertexArray();
+	UINT uResultVertexCount = aOut.size();
+	if(uResultVertexCount < 4)
+	{
+		return(0);
+	}
+
+	IXBuffer *pBuffer = Core_GetIXCore()->newBuffer();
+	pBuffer->alloc(sizeof(float3_t) * uResultVertexCount);
+	float3_t *pOut = (float3_t*)pBuffer->get();
+	for(UINT i = 0; i < uResultVertexCount; ++i)
+	{
+		const btVector3 &v = aOut[i];
+		pOut[i] = BTVEC_F3(v);
+	}
+	*ppOutBuffer = pBuffer;
+
+	return(uResultVertexCount);
+}
diff --git a/source/physics/Physics.h b/source/physics/Physics.h
index 1a5f7ed773807a3d46ef1e5a5fc3f59ca89a58ee..b03133fcae5efe4bf10c95b3de39d346948305f6 100644
--- a/source/physics/Physics.h
+++ b/source/physics/Physics.h
@@ -28,6 +28,8 @@ public:
 
 	IXPhysicsWorld* XMETHODCALLTYPE getWorld(void *pReserved = NULL) override;
 
+	UINT XMETHODCALLTYPE adjustConvexHullForMargin(UINT uPoints, const float3_t *pInPoints, IXBuffer **ppOutBuffer, float *pfMargin = NULL, byte u8Stride = sizeof(float3_t), bool bOptimize = true) override;
+	
 private:
 	CPhyWorld *m_pDefaultWorld;
 };
diff --git a/source/xcommon/physics/IXCollisionShape.h b/source/xcommon/physics/IXCollisionShape.h
index 439f083333ac4a822b5e0d789966fea10913dcf7..b6b221039a6b1dbf88ab02d4b4bf75f8ccff1433 100644
--- a/source/xcommon/physics/IXCollisionShape.h
+++ b/source/xcommon/physics/IXCollisionShape.h
@@ -117,6 +117,9 @@ class IXCapsuleShape: public IXConvexShape
 public:
 	virtual float XMETHODCALLTYPE getRadius() const = 0;
 	virtual float XMETHODCALLTYPE getHeight() const = 0;
+
+	virtual void XMETHODCALLTYPE setRadius(float fValue) = 0;
+	virtual void XMETHODCALLTYPE setHeight(float fValue) = 0;
 };
 
 //#############################################################################
diff --git a/source/xcommon/physics/IXPhysics.h b/source/xcommon/physics/IXPhysics.h
index 6291f6cc3b822ca8e6dc0ebccc06079d892ceb5f..5597e53269635091916030d330d6f5c65d879fed 100644
--- a/source/xcommon/physics/IXPhysics.h
+++ b/source/xcommon/physics/IXPhysics.h
@@ -1,6 +1,7 @@
 #ifndef __IXPHYSICS_H
 #define __IXPHYSICS_H
 
+#include <xcommon/IXBuffer.h>
 #include "IXCollisionShape.h"
 #include "IXCollisionObject.h"
 #include "IXCharacterController.h"
@@ -101,6 +102,8 @@ public:
 	virtual void XMETHODCALLTYPE newMutationObserver(IXMutationObserver **ppOut) = 0;
 
 	virtual IXPhysicsWorld* XMETHODCALLTYPE getWorld(void *pReserved = NULL) = 0;
+
+	virtual UINT XMETHODCALLTYPE adjustConvexHullForMargin(UINT uPoints, const float3_t *pInPoints, IXBuffer **ppOutBuffer, float *pfMargin = NULL, byte u8Stride = sizeof(float3_t), bool bOptimize = true) = 0;
 };
 
 #endif
diff --git a/source/xcommon/resource/IXResourceModel.h b/source/xcommon/resource/IXResourceModel.h
index 0100966644477764983b88098ef6119bfca10ab0..4d886eee1f56efcb8f08a7c4306c04fe25ab92f2 100644
--- a/source/xcommon/resource/IXResourceModel.h
+++ b/source/xcommon/resource/IXResourceModel.h
@@ -239,6 +239,9 @@ public:
 
 	virtual void XMETHODCALLTYPE initData(UINT uVertexCount, const float3_t *pData = NULL) = 0;
 	virtual float3_t * XMETHODCALLTYPE getData() = 0;
+
+	virtual void XMETHODCALLTYPE setMargin(float fMargin) = 0;
+	virtual float XMETHODCALLTYPE getMargin() const = 0;
 };
 
 class IModelPhysboxCylinder: public IModelPhysbox
diff --git a/source/xcsg/BrushMesh.cpp b/source/xcsg/BrushMesh.cpp
index b92d8af810e46a212bc6ca60db34d416fb8fbab5..79a1ee1f1d0f7bf6a4ca164cd4e4205f8e86a4a5 100644
--- a/source/xcsg/BrushMesh.cpp
+++ b/source/xcsg/BrushMesh.cpp
@@ -183,16 +183,26 @@ void CBrushMesh::buildModel(bool bBuildPhysbox)
 		}
 		mem_release(m_pRigidBody);
 
-		IXConvexHullShape *pShape;
-		m_pPhysics->newConvexHullShape(m_aVertices.size(), m_aVertices, &pShape, sizeof(float3_t), false);
+		float fMargin;
+		IXBuffer *pBuffer;
+		UINT uNewCount = m_pPhysics->adjustConvexHullForMargin(m_aVertices.size(), m_aVertices, &pBuffer, &fMargin);
 
-		XRIDIGBODY_DESC desc;
-		desc.pCollisionShape = pShape;
-		m_pPhysics->newRigidBody(desc, &m_pRigidBody);
-		mem_release(pShape);
+		if(uNewCount)
+		{
+			IXConvexHullShape *pShape;
+			m_pPhysics->newConvexHullShape(uNewCount, (float3_t*)pBuffer->get(), &pShape, sizeof(float3_t), false);
+			mem_release(pBuffer);
+
+			pShape->setMargin(fMargin);
+
+			XRIDIGBODY_DESC desc;
+			desc.pCollisionShape = pShape;
+			m_pPhysics->newRigidBody(desc, &m_pRigidBody);
+			mem_release(pShape);
 
-		m_pPhysics->getWorld()->addCollisionObject(m_pRigidBody, CG_STATIC, CG_STATIC_MASK);
-		m_isPhysicsLoaded = true;
+			m_pPhysics->getWorld()->addCollisionObject(m_pRigidBody, CG_STATIC, CG_STATIC_MASK);
+			m_isPhysicsLoaded = true;
+		}
 	}
 }
 
@@ -1793,11 +1803,19 @@ void CBrushMesh::setFinalized(bool set)
 
 void CBrushMesh::buildPhysbox(IXResourceModel *pResource)
 {
-	IModelPhysboxConvex *pConvex = pResource->newPhysboxConvex();
+	float fMargin;
+	IXBuffer *pBuffer;
+	UINT uNewCount = m_pPhysics->adjustConvexHullForMargin(m_aVertices.size(), m_aVertices, &pBuffer, &fMargin);
 
-	pConvex->initData(m_aVertices.size(), m_aVertices);
+	if(uNewCount)
+	{
+		IModelPhysboxConvex *pConvex = pResource->newPhysboxConvex();
+		pConvex->initData(uNewCount, (float3_t*)pBuffer->get());
+		mem_release(pBuffer);
+		pConvex->setMargin(fMargin);
 
-	pResource->addPhysbox(pConvex);
+		pResource->addPhysbox(pConvex);
+	}
 }
 
 bool CBrushMesh::couldMoveVertices(const UINT *puAffectedVertices, UINT uVertexCount, UINT uVertexOffset, const float3 &vDeltaPos)