diff --git a/proj/sxgame/vs2013/sxgame.vcxproj b/proj/sxgame/vs2013/sxgame.vcxproj
index 5f6b91c46b9adb2f15159afad05d05fe1692eb35..b70be725fa3ad8ec5a1892e3129ac3e8a9dfdfee 100644
--- a/proj/sxgame/vs2013/sxgame.vcxproj
+++ b/proj/sxgame/vs2013/sxgame.vcxproj
@@ -210,6 +210,7 @@
     <ClCompile Include="..\..\..\source\game\GUIInventoryController.cpp" />
     <ClCompile Include="..\..\..\source\game\HUDcontroller.cpp" />
     <ClCompile Include="..\..\..\source\game\InfoParticlePlayer.cpp" />
+    <ClCompile Include="..\..\..\source\game\LadderMovementController.cpp" />
     <ClCompile Include="..\..\..\source\game\LightDirectional.cpp" />
     <ClCompile Include="..\..\..\source\game\LightPoint.cpp" />
     <ClCompile Include="..\..\..\source\game\LightSun.cpp" />
@@ -291,7 +292,9 @@
     <ClInclude Include="..\..\..\source\game\GUIInventoryController.h" />
     <ClInclude Include="..\..\..\source\game\HUDcontroller.h" />
     <ClInclude Include="..\..\..\source\game\IGameState.h" />
+    <ClInclude Include="..\..\..\source\game\IMovementController.h" />
     <ClInclude Include="..\..\..\source\game\InfoParticlePlayer.h" />
+    <ClInclude Include="..\..\..\source\game\LadderMovementController.h" />
     <ClInclude Include="..\..\..\source\game\LightSun.h" />
     <ClInclude Include="..\..\..\source\game\LogicAuto.h" />
     <ClInclude Include="..\..\..\source\game\LogicConsole.h" />
diff --git a/proj/sxgame/vs2013/sxgame.vcxproj.filters b/proj/sxgame/vs2013/sxgame.vcxproj.filters
index e9732da19c4698d9cc891d33dfa33eb6877a8a2c..912083c6e42413dc9b11a3faf46109c7a44d1191 100644
--- a/proj/sxgame/vs2013/sxgame.vcxproj.filters
+++ b/proj/sxgame/vs2013/sxgame.vcxproj.filters
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
     <Filter Include="Source Files">
@@ -115,6 +115,12 @@
     <Filter Include="Header Files\ents\info">
       <UniqueIdentifier>{93ee5d69-dabd-4584-8e15-0acd713e614f}</UniqueIdentifier>
     </Filter>
+    <Filter Include="Header Files\character_movement">
+      <UniqueIdentifier>{3852670f-3617-4451-bfc1-c0aba9c194fa}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="Source Files\character_movement">
+      <UniqueIdentifier>{7f56c9eb-5a04-4d29-ab96-b7e3d8d368f1}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\..\..\source\game\sxgame_dll.cpp">
@@ -360,6 +366,9 @@
     <ClCompile Include="..\..\..\source\game\CraftSystem.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\..\source\game\LadderMovementController.cpp">
+      <Filter>Source Files\character_movement</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\source\game\sxgame.h">
@@ -629,6 +638,12 @@
     <ClInclude Include="..\..\..\source\game\CraftSystem.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\..\source\game\IMovementController.h">
+      <Filter>Header Files\character_movement</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\source\game\LadderMovementController.h">
+      <Filter>Header Files\character_movement</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\..\..\source\game\sxgame.rc">
diff --git a/source/game/BaseCharacter.cpp b/source/game/BaseCharacter.cpp
index 9fe1801979d2e5aca81632d8cc39bf251b6790ae..89dd1892c1856f74000bcce7c6f347d71c9ce70d 100644
--- a/source/game/BaseCharacter.cpp
+++ b/source/game/BaseCharacter.cpp
@@ -124,31 +124,20 @@ CBaseCharacter::~CBaseCharacter()
 
 	mem_release(m_pHandsModelResource);
 
+	mem_release(m_pMovementController);
+
 	if(m_idQuadCurr >= 0)
 	{
 		//SAIG_QuadSetState(m_idQuadCurr, AIQUAD_STATE_FREE);
 	}
 }
 
-void CBaseCharacter::mountToLadder(CFuncLadder *pLadder)
-{
-	if(m_pLadder == pLadder)
-	{
-		return;
-	}
-	m_pLadder = pLadder;
-	float3 vStart = m_pLadder->getPos(), vEnd = m_pLadder->getUpPos();
-
-	float3 vPointOnLadder = SMProjectPointOnLine(getPos(), vEnd, vEnd - vStart);
-	setPos(vPointOnLadder);
-	m_uMoveDir |= PM_LADDER;
-	m_pCharacter->setGravity({0, 0, 0});
-	m_pCharacter->setVelocityForTimeInterval({0, 0, 0}, 0);
-}
-
-void CBaseCharacter::dismountFromLadder()
+void CBaseCharacter::setMovementController(IMovementController *pController)
 {
-	m_pLadder = NULL;
+	mem_release(m_pMovementController);
+	m_pMovementController = pController;
+	add_ref(m_pMovementController);
+	SAFE_CALL(m_pMovementController, setCharacter, this);
 }
 
 void CBaseCharacter::attack(BOOL state)
@@ -471,8 +460,12 @@ void CBaseCharacter::onPhysicsStep()
 	{
 		return;
 	}
-	float3 vPos = m_pGhostObject->getPosition();
-	setPos(vPos - float3(0.0f, m_fCapsHeight * m_fCurrentHeight * 0.5f, 0.0f));
+
+	if(!m_pMovementController)
+	{
+		float3 vPos = m_pGhostObject->getPosition();
+		setPos(vPos - float3(0.0f, m_fCapsHeight * m_fCurrentHeight * 0.5f, 0.0f));
+	}
 
 	m_pHeadEnt->setOffsetPos(getHeadOffset());
 
@@ -558,6 +551,11 @@ void CBaseCharacter::use(bool start)
 
 	if(start)
 	{
+		if(m_pMovementController && m_pMovementController->handleUse())
+		{
+			return;
+		}
+
 		float3 start = getHead()->getPos();
 		float3 dir = getHead()->getOrient() * float3(0.0f, 0.0f, 1.0f);
 		float3 end = start + dir * 2.0f;
diff --git a/source/game/BaseCharacter.h b/source/game/BaseCharacter.h
index bdd0670bc3a26a9a2ac81ab08736084dc77be28e..ad1cbcb3deb5d8c108b57232050f6dbd064683c3 100644
--- a/source/game/BaseCharacter.h
+++ b/source/game/BaseCharacter.h
@@ -20,6 +20,7 @@ See the license in LICENSE
 #include "LightDirectional.h"
 #include "CharacterInventory.h"
 #include "PointEntity.h"
+#include "IMovementController.h"
 
 class CBaseTool;
 
@@ -36,12 +37,10 @@ enum PLAYER_MOVE
 	PM_RUN = 0x40,       //!< бежать
 	PM_CRAWL = 0x80,     //!< лежать
 	PM_OBSERVER = 0x100, //!< наблюдатель
-	PM_LADDER = 0x200,   //!< лестница
 
 	PM_STOP = 0xFFFF
 };
 
-class CFuncLadder;
 class CHUDcontroller;
 
 class CBaseCharacter;
@@ -138,17 +137,14 @@ public:
 
 	void onPostLoad() override;
 
-	void mountToLadder(CFuncLadder *pLadder);
-
-	void dismountFromLadder();
+	void setMovementController(IMovementController *pController);
 
 	void renderEditor(bool is3D, bool bRenderSelection, IXGizmoRenderer *pRenderer) override;
 
-	void mountToLadder(CFuncLadder *pLadder);
-
-	void dismountFromLadder();
-
-	void renderEditor(bool is3D, bool bRenderSelection, IXGizmoRenderer *pRenderer) override;
+	IXCharacterController* getCharacterController()
+	{
+		return(m_pCharacter);
+	}
 
 protected:
 	//! Фонарик
@@ -200,7 +196,8 @@ protected:
 
 	virtual float3 getHeadOffset();
 
-	CFuncLadder *m_pLadder = NULL;
+	IMovementController *m_pMovementController = NULL;
+
 private:
 	static IEventChannel<XEventPhysicsStep> *m_pTickEventChannel;
 	CCharacterPhysicsTickEventListener m_physicsTicker;
diff --git a/source/game/FuncLadder.cpp b/source/game/FuncLadder.cpp
index f6a9442eb496c11319c51c861bd5aed10f76ddcf..d8e859d99fd6e38941c083f54c24f08c1c476ff6 100644
--- a/source/game/FuncLadder.cpp
+++ b/source/game/FuncLadder.cpp
@@ -1,5 +1,6 @@
 #include "FuncLadder.h"
 #include "BaseCharacter.h"
+#include "LadderMovementController.h"
 
 
 BEGIN_PROPTABLE(CInfoLadderDismount)
@@ -131,19 +132,18 @@ void CFuncLadder::initPhysics()
 	mem_release(m_pCollideShape);
 
 	float3 vDelta = m_vUpPoint - getPos();
-	float3 vMin, vMax;
+	SMAABB aabb = getBound();
 	float3 vMinDelta, vMaxDelta;
-	getMinMax(&vMin, &vMax);
 
 	float3_t aPointsBot[] = { 
-		{ vMin.x, vMax.y, vMin.z }, 
-		{ vMax.x, vMax.y, vMin.z },
-		{ vMin.x, vMax.y, vMax.z },
-		{ vMax.x, vMax.y, vMax.z }
+		{aabb.vMin.x, aabb.vMax.y, aabb.vMin.z }, 
+		{aabb.vMax.x, aabb.vMax.y, aabb.vMin.z },
+		{aabb.vMin.x, aabb.vMax.y, aabb.vMax.z },
+		{aabb.vMax.x, aabb.vMax.y, aabb.vMax.z }
 	};
 
-	vMinDelta = vMin + vDelta;
-	vMaxDelta = vMax + vDelta;
+	vMinDelta = aabb.vMin + vDelta;
+	vMaxDelta = aabb.vMax + vDelta;
 
 	float3_t aPointsTop[] = {
 		{ vMinDelta.x, vMinDelta.y, vMinDelta.z },
@@ -177,10 +177,10 @@ void CFuncLadder::initPhysics()
 		aShapePoints[uIndex++] = aPointsTop[1];
 	}
 
-	aShapePoints[uIndex++] = vMin;
-	aShapePoints[uIndex++] = {vMin.x, vMin.y ,vMax.z};
-	aShapePoints[uIndex++] = {vMax.x, vMin.y, vMax.z};
-	aShapePoints[uIndex++] = {vMax.x, vMin.y, vMin.z};
+	aShapePoints[uIndex++] = aabb.vMin;
+	aShapePoints[uIndex++] = {aabb.vMin.x, aabb.vMin.y, aabb.vMax.z};
+	aShapePoints[uIndex++] = {aabb.vMax.x, aabb.vMin.y, aabb.vMax.z};
+	aShapePoints[uIndex++] = {aabb.vMax.x, aabb.vMin.y, aabb.vMin.z};
 
 	aShapePoints[uIndex++] = vMaxDelta;
 	aShapePoints[uIndex++] = {vMaxDelta.x, vMaxDelta.y, vMinDelta.z};
@@ -199,8 +199,7 @@ void CFuncLadder::renderEditor(bool is3D, bool bRenderSelection, IXGizmoRenderer
 		pRenderer->setColor(c_vLineColor);
 		pRenderer->setLineWidth(is3D ? 0.02f : 1.0f);
 
-		SMAABB aabb;
-		getMinMax(&aabb.vMin, &aabb.vMax);
+		SMAABB aabb = getBound();
 		pRenderer->drawAABB(aabb + getPos());
 		pRenderer->drawAABB(aabb + m_vUpPoint);
 		pRenderer->jumpTo(getPos());
@@ -210,24 +209,152 @@ void CFuncLadder::renderEditor(bool is3D, bool bRenderSelection, IXGizmoRenderer
 
 void CFuncLadder::getMinMax(float3 *min, float3 *max)
 {
+	SMAABB aabb = getBound();
+	aabb = SMAABBConvex(aabb, aabb + (getUpPos() - getPos()));
+
 	if(min)
 	{
-		min->x = -0.25f;
-		min->y = 0.0f;
-		min->z = -0.25f;
+		*min = aabb.vMin;
 	}
 
 	if(max)
 	{
-		max->x = 0.25f;
-		max->y = 1.8f;
-		max->z = 0.25f;
+		*max = aabb.vMax;
+	}
+}
+
+SMAABB CFuncLadder::getBound()
+{
+	return(SMAABB(float3(-0.25f, 0.0f, -0.25f), float3(0.25f, 1.8f, 0.25f)));
+}
+
+bool SMAABBIntersectLine(const SMAABB &aabb, const float3 &vStart, const float3 &vEnd, float3 *pvOut, float3 *pvNormal)
+{
+	float min_t = 0.0f;
+    float max_t = 1.0f;
+
+	float3 vDir = vEnd - vStart;
+
+	for(int i = 0; i < 3; ++i)
+	{
+		if(SMIsZero(vDir[i]))
+		{
+			if(vStart[i] < aabb.vMin[i] || vStart[i] > aabb.vMax[i])
+			{
+				return(false);
+			}
+		}
+
+		float t1 = (aabb.vMin[i] - vStart[i]) / vDir[i];
+		float t2 = (aabb.vMax[i] - vStart[i]) / vDir[i];
+
+		float tmin = min(t1, t2);
+		float tmax = max(t1, t2);
+
+		min_t = max(min_t, tmin);
+		max_t = min(max_t, tmax);
+
+		if(min_t > max_t)
+		{
+			return(false);
+		}
+	}
+
+	if(pvOut)
+	{
+		*pvOut = vStart + min_t * vDir;
+	}
+
+	if(pvNormal)
+	{
+		*pvNormal = 0.0f;
+
+		if(SMIsZero(aabb.vMin.x - pvOut->x))
+		{
+			pvNormal->x = -1.0f;
+		}
+		else if(SMIsZero(aabb.vMax.x - pvOut->x))
+		{
+			pvNormal->x = 1.0f;
+		}
+		else if(SMIsZero(aabb.vMin.y - pvOut->y))
+		{
+			pvNormal->y = -1.0f;
+		}
+		else if(SMIsZero(aabb.vMax.y - pvOut->y))
+		{
+			pvNormal->y = 1.0f;
+		}
+		else if(SMIsZero(aabb.vMin.z - pvOut->z))
+		{
+			pvNormal->z = -1.0f;
+		}
+		else
+		{
+			pvNormal->z = 1.0f;
+		}
 	}
 }
 
 bool CFuncLadder::rayTest(const float3 &vStart, const float3 &vEnd, float3 *pvOut, float3 *pvNormal, bool isRayInWorldSpace, bool bReturnNearestPoint)
 {
-	// TODO Implement me!
+	assert(isRayInWorldSpace);
+
+	SMAABB aabb = getBound();
+
+	if(bReturnNearestPoint)
+	{
+		bool b0, b1;
+		float3 vPos0, vPos1;
+		float3 vNormal0, vNormal1;
+		float3 *pvNormal0 = NULL, *pvNormal1 = NULL;
+		if(pvNormal)
+		{
+			pvNormal0 = &vNormal0;
+			pvNormal1 = &vNormal1;
+		}
+
+		b0 = SMAABBIntersectLine(aabb + getPos(), vStart, vEnd, &vPos0, pvNormal0);
+		b1 = SMAABBIntersectLine(aabb + getUpPos(), vStart, vEnd, &vPos1, pvNormal1);
+
+		if(b0 && b1)
+		{
+			if(SMVector3Length2(vPos0 - vStart) > SMVector3Length2(vPos1 - vStart))
+			{
+				b0 = false;
+			}
+			else
+			{
+				b1 = false;
+			}
+		}
+
+		if(b0)
+		{
+			*pvOut = vPos0;
+			if(pvNormal)
+			{
+				*pvNormal = vNormal0;
+			}
+		}
+		else if(b1)
+		{
+			*pvOut = vPos1;
+			if(pvNormal)
+			{
+				*pvNormal = vNormal1;
+			}
+		}
+
+		return(b0 || b1);
+	}
+	else
+	{
+		if(SMAABBIntersectLine(aabb + getPos(), vStart, vEnd, pvOut, pvNormal) || SMAABBIntersectLine(aabb + getUpPos(), vStart, vEnd, pvOut, pvNormal))
+		{
+			return(true);
+		}
+	}
 	
 	return(false);
 }
@@ -240,7 +367,7 @@ void CFuncLadder::onUse(CBaseEntity *pUser)
 
 float3 CFuncLadder::getUpPos()
 {
-	return m_vUpPoint;
+	return(m_vUpPoint);
 }
 
 void CFuncLadder::connectToLadder(CBaseEntity *pEntity)
@@ -250,7 +377,9 @@ void CFuncLadder::connectToLadder(CBaseEntity *pEntity)
 		return;
 	}
 	CBaseCharacter *pCharacter = (CBaseCharacter*)pEntity;
-	pCharacter->mountToLadder(this);
+	CLadderMovementController *pController = new CLadderMovementController(this);
+	pCharacter->setMovementController(pController);
+	mem_release(pController);
 }
 
 void CFuncLadder::onPhysicsStep()
@@ -260,6 +389,8 @@ void CFuncLadder::onPhysicsStep()
 		return;
 	}
 
+	Array<CBaseEntity*> aCurrentTouchingEntities(m_aTouchedEntities.size());
+
 	for(UINT i = 0, l = m_pGhostObject->getOverlappingPairCount(); i < l; ++i)
 	{
 		IXCollisionPair *pair = m_pGhostObject->getOverlappingPair(i);
@@ -279,14 +410,20 @@ void CFuncLadder::onPhysicsStep()
 					CBaseEntity *pEnt = (CBaseEntity*)pObject->getUserPointer();
 					if(pEnt)
 					{
-						connectToLadder(pEnt);
-						//printf("touched %s\n", pEnt->getClassName());
+						aCurrentTouchingEntities.push_back(pEnt);
+						if(m_aTouchedEntities.indexOf(pEnt) < 0)
+						{
+							connectToLadder(pEnt);
+							//printf("touched %s\n", pEnt->getClassName());
+						}
 					}
 				}
 				break;
 			}
 		}
 	}
+
+	m_aTouchedEntities = aCurrentTouchingEntities;
 }
 
 
diff --git a/source/game/FuncLadder.h b/source/game/FuncLadder.h
index 78530c7acda78bf3ebe5511d5c1b3eb84f48ecc9..520bae843399c68464c81ce786ed8b6976c09c3c 100644
--- a/source/game/FuncLadder.h
+++ b/source/game/FuncLadder.h
@@ -64,6 +64,8 @@ private:
 	void disable();
 	void onPhysicsStep();
 
+	SMAABB getBound();
+
 private:
 	float3_t m_vUpPoint;
 	bool m_isUpSet = false;
@@ -77,6 +79,7 @@ private:
 	static IEventChannel<XEventPhysicsStep> *m_pTickEventChannel;
 	CPhysicsLadderTickEventListener m_physicsTicker;
 
+	Array<CBaseEntity*> m_aTouchedEntities;
 };
 
 #endif
diff --git a/source/game/IMovementController.h b/source/game/IMovementController.h
new file mode 100644
index 0000000000000000000000000000000000000000..cf98817c7be270ed835ee1b96a9d304c2d471df6
--- /dev/null
+++ b/source/game/IMovementController.h
@@ -0,0 +1,19 @@
+#ifndef __IMOVEMENTCONTROLLER_H
+#define __IMOVEMENTCONTROLLER_H
+
+#include <gdefines.h>
+
+class CBaseCharacter;
+class IMovementController: public IXUnknown
+{
+public:
+	virtual void setCharacter(CBaseCharacter *pCharacter) = 0;
+
+	virtual void handleMove(const float3 &vDir) = 0;
+	virtual void handleJump() = 0;
+	virtual bool handleUse() = 0;
+
+	virtual void update(float fDt) = 0;
+};
+
+#endif
diff --git a/source/game/LadderMovementController.cpp b/source/game/LadderMovementController.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6152853f6bdaecc805dc1ce60e52c9693c7e9f76
--- /dev/null
+++ b/source/game/LadderMovementController.cpp
@@ -0,0 +1,119 @@
+#include "LadderMovementController.h"
+#include "BaseCharacter.h"
+#include "FuncLadder.h"
+#include "Player.h"
+
+CLadderMovementController::CLadderMovementController(CFuncLadder *pLadder)
+{
+	m_vLadderPoint[0] = pLadder->getPos();
+	m_vLadderPoint[1] = pLadder->getUpPos();
+
+	m_vLadderDir = SMVector3Normalize(m_vLadderPoint[1] - m_vLadderPoint[0]);
+}
+CLadderMovementController::~CLadderMovementController()
+{
+	if(m_pCharacter)
+	{
+		m_pCharacter->getCharacterController()->setGravity(float3(0.0f, -10.0f, 0.0f));
+	}
+}
+
+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 CLadderMovementController::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);
+
+	TODO("Make move smoother");
+	float3 vPointOnLadder = SMProjectPointOnLine(m_pCharacter->getPos(), m_vLadderPoint[0], m_vLadderPoint[1]);
+	m_pCharacter->setPos(vPointOnLadder);
+}
+
+void CLadderMovementController::handleMove(const float3 &vDir)
+{
+	m_vMoveDir = vDir;
+}
+
+void CLadderMovementController::handleJump()
+{
+	m_bWillDismount = true;
+}
+
+bool CLadderMovementController::handleUse()
+{
+	m_bWillDismount = true;
+	return(true);
+}
+
+void CLadderMovementController::update(float fDt)
+{
+	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 * 3.0f;
+		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/LadderMovementController.h b/source/game/LadderMovementController.h
new file mode 100644
index 0000000000000000000000000000000000000000..a41cc4ac208975e67aba2a2005d6e2157cc878aa
--- /dev/null
+++ b/source/game/LadderMovementController.h
@@ -0,0 +1,32 @@
+#ifndef __LADDERMOVEMENTCONTROLLER_H
+#define __LADDERMOVEMENTCONTROLLER_H
+
+#include "IMovementController.h"
+
+class CFuncLadder;
+class CLadderMovementController: public IXUnknownImplementation<IMovementController>
+{
+public:
+	CLadderMovementController(CFuncLadder *pLadder);
+	~CLadderMovementController();
+
+	void setCharacter(CBaseCharacter *pCharacter) override;
+
+	void handleMove(const float3 &vDir) override;
+	void handleJump() override;
+	bool handleUse() override;
+
+	void update(float fDt) override;
+
+private:
+	CBaseCharacter *m_pCharacter;
+
+	float3_t m_vLadderPoint[2];
+	float3_t m_vLadderDir;
+
+	float3_t m_vMoveDir;
+
+	bool m_bWillDismount = false;
+};
+
+#endif
diff --git a/source/game/Player.cpp b/source/game/Player.cpp
index acf1e85c24042e13727f48364cd3a8d75e12955f..3268ecebb50c9c7d4afd863eda2d5b270151064f 100644
--- a/source/game/Player.cpp
+++ b/source/game/Player.cpp
@@ -150,6 +150,7 @@ void CPlayer::updateInput(float dt)
 	// m_vWpnShakeAngles = (float3)(m_vWpnShakeAngles * 0.4f);
 	m_vWpnShakeAngles = (float3)(m_vWpnShakeAngles / (1.05f + dt));
 
+	// Handle look
 	if(!*editor_mode || SSInput_GetKeyState(SIM_LBUTTON))
 	{
 		SSInput_GetMouseDelta(&x, &y);
@@ -236,61 +237,43 @@ void CPlayer::updateInput(float dt)
 			mov = true;
 		}
 
-		if(m_uMoveDir & PM_CROUCH || (m_fCurrentHeight < 0.99f && !m_pCharacter->canStandUp((m_fCapsHeight - m_fCapsRadius * 2.0f) * (1.0f - m_fCurrentHeight))))
+		if(m_uMoveDir & PM_OBSERVER)
 		{
-			m_fCurrentHeight -= dt * 10.0f;
-			float fMinHeight = (m_fCapsHeightCrouch - m_fCapsRadius * 2.0f) / (m_fCapsHeight - m_fCapsRadius * 2.0f);
-			if(m_fCurrentHeight < fMinHeight)
-			{
-				m_fCurrentHeight = fMinHeight;
-			}
+			setPos(getPos() + m_pHeadEnt->getOrient() * (SMVector3Normalize(dir) * dt * 10.0f));
 		}
-		else
+		else if(m_pMovementController)
 		{
-			m_fCurrentHeight += dt * 10.0f;
-			if(m_fCurrentHeight > 1.0f)
+			m_vCurrentSpeed = {0.0f, 0.0f, 0.0f};
+
+			if(m_uMoveDir & PM_JUMP)
 			{
-				m_fCurrentHeight = 1.0f;
+				m_pMovementController->handleJump();
 			}
+			m_pMovementController->handleMove(m_pHeadEnt->getOrient() * SMVector3Normalize(dir));
+			m_pMovementController->update(dt);
 		}
-		m_pCollideShape->setLocalScaling(float3(1.0f, m_fCurrentHeight, 1.0f));
-
-		if(m_uMoveDir & PM_OBSERVER)
-		{
-			setPos(getPos() + m_pHeadEnt->getOrient() * (SMVector3Normalize(dir) * dt * 10.0f));
-		}
-		else if(m_uMoveDir & PM_LADDER)
+		else
 		{
-			if(m_uMoveDir & PM_FORWARD)
+			if(m_uMoveDir & PM_CROUCH || (m_fCurrentHeight < 0.99f && !m_pCharacter->canStandUp((m_fCapsHeight - m_fCapsRadius * 2.0f) * (1.0f - m_fCurrentHeight))))
 			{
-				float3 vSpeed(0.0f, 3.0f, 0.0f);
-				float3 fNewPos = getPos() + vSpeed * dt;
-				if(m_pLadder->getUpPos().y >= fNewPos.y)
+				m_fCurrentHeight -= dt * 10.0f;
+				float fMinHeight = (m_fCapsHeightCrouch - m_fCapsRadius * 2.0f) / (m_fCapsHeight - m_fCapsRadius * 2.0f);
+				if(m_fCurrentHeight < fMinHeight)
 				{
-					setPos(fNewPos);
-				}
-				else
-				{
-					setPos(m_pLadder->getUpPos());
+					m_fCurrentHeight = fMinHeight;
 				}
 			}
-			else if(m_uMoveDir & PM_BACKWARD)
+			else
 			{
-				float3 vSpeed(0.0f, -3.0f, 0.0f);
-				float3 fNewPos = getPos() + vSpeed * dt;
-				if(m_pLadder->getPos().y <= fNewPos.y)
+				m_fCurrentHeight += dt * 10.0f;
+				if(m_fCurrentHeight > 1.0f)
 				{
-					setPos(fNewPos);
-				}
-				else
-				{
-					setPos(m_pLadder->getPos());
+					m_fCurrentHeight = 1.0f;
 				}
 			}
-			m_vCurrentSpeed = {0.0f, 0.0f, 0.0f};
-		}
-		else
-		{
+			m_pCollideShape->setLocalScaling(float3(1.0f, m_fCurrentHeight, 1.0f));
+
+
 			dir = SMQuaternion(m_vPitchYawRoll.y, 'y') * (SMVector3Normalize(dir)/* * dt*/);
 			dir *= 3.5f;
 			if(m_uMoveDir & PM_CROUCH)
@@ -411,7 +394,7 @@ void CPlayer::updateInput(float dt)
 			//vel = getDiscreteLinearVelocity();
 			//printf("%f, %f, %f\n", vel.x, vel.y, vel.z);
 
-			m_vWpnShakeAngles.x += -vel.y * 0.05f;
+			m_vWpnShakeAngles.x += -vel.y * 0.01f;
 			const float fMaxAng = SM_PI * 0.1f;
 			m_vWpnShakeAngles.x = clampf(m_vWpnShakeAngles.x, -fMaxAng, fMaxAng);
 		}
@@ -492,6 +475,8 @@ void CPlayer::spawn()
 			m_uMoveDir &= ~PM_OBSERVER;
 			m_pCrosshair->enable();
 
+			setMovementController(NULL);
+
 			GameData::m_pHUDcontroller->setPlayerRot(m_vPitchYawRoll);
 			GameData::m_pHUDcontroller->setPlayerPos(getPos());
 			GameData::m_pHUDcontroller->setPlayerHealth(m_fHealth);
diff --git a/source/game/Player.h b/source/game/Player.h
index 797497644def32e57199073f33e0faed5ca8d53b..34ab9367236655984586e182949ce97c0ea35329 100644
--- a/source/game/Player.h
+++ b/source/game/Player.h
@@ -25,6 +25,7 @@ See the license in LICENSE
 //! Класс игрока  \ingroup cbaseanimating
 class CPlayer: public CBaseCharacter
 {
+	friend class CLadderMovementController;
 	DECLARE_CLASS(CPlayer, CBaseCharacter);
 	DECLARE_PROPTABLE();
 public: