From 763b9bee2a38fd85453f3acffc44109019d7115c Mon Sep 17 00:00:00 2001
From: D-AIRY <admin@ds-servers.com>
Date: Wed, 25 Dec 2024 02:10:23 +0300
Subject: [PATCH] Split func_ladder to base_mover and func_ladder

---
 proj/sxgame/vs2013/sxgame.vcxproj         |   2 +
 proj/sxgame/vs2013/sxgame.vcxproj.filters |  24 +-
 source/game/BaseMover.cpp                 | 467 ++++++++++++++++++++++
 source/game/BaseMover.h                   |  78 ++++
 source/game/FuncLadder.cpp                | 443 +-------------------
 source/game/FuncLadder.h                  |  68 +---
 6 files changed, 574 insertions(+), 508 deletions(-)
 create mode 100644 source/game/BaseMover.cpp
 create mode 100644 source/game/BaseMover.h

diff --git a/proj/sxgame/vs2013/sxgame.vcxproj b/proj/sxgame/vs2013/sxgame.vcxproj
index b70be725f..352079e14 100644
--- a/proj/sxgame/vs2013/sxgame.vcxproj
+++ b/proj/sxgame/vs2013/sxgame.vcxproj
@@ -182,6 +182,7 @@
     <ClCompile Include="..\..\..\source\game\BaseHandle.cpp" />
     <ClCompile Include="..\..\..\source\game\BaseLight.cpp" />
     <ClCompile Include="..\..\..\source\game\BaseMag.cpp" />
+    <ClCompile Include="..\..\..\source\game\BaseMover.cpp" />
     <ClCompile Include="..\..\..\source\game\BaseRecipe.cpp" />
     <ClCompile Include="..\..\..\source\game\BaseScope.cpp" />
     <ClCompile Include="..\..\..\source\game\BaseSilencer.cpp" />
@@ -269,6 +270,7 @@
     <ClInclude Include="..\..\..\source\game\BaseLight.h" />
     <ClInclude Include="..\..\..\source\game\Baseline.h" />
     <ClInclude Include="..\..\..\source\game\BaseMag.h" />
+    <ClInclude Include="..\..\..\source\game\BaseMover.h" />
     <ClInclude Include="..\..\..\source\game\BaseRecipe.h" />
     <ClInclude Include="..\..\..\source\game\BaseScope.h" />
     <ClInclude Include="..\..\..\source\game\BaseSilencer.h" />
diff --git a/proj/sxgame/vs2013/sxgame.vcxproj.filters b/proj/sxgame/vs2013/sxgame.vcxproj.filters
index 912083c6e..1c43d9d0f 100644
--- a/proj/sxgame/vs2013/sxgame.vcxproj.filters
+++ b/proj/sxgame/vs2013/sxgame.vcxproj.filters
@@ -121,6 +121,12 @@
     <Filter Include="Source Files\character_movement">
       <UniqueIdentifier>{7f56c9eb-5a04-4d29-ab96-b7e3d8d368f1}</UniqueIdentifier>
     </Filter>
+    <Filter Include="Source Files\ents\func\mover">
+      <UniqueIdentifier>{c06faca7-33f4-4cd1-999d-b47c933d2b66}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="Header Files\ents\func\mover">
+      <UniqueIdentifier>{457c7c3e-8b6b-4173-ba02-c16e1d376e54}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\..\..\source\game\sxgame_dll.cpp">
@@ -324,9 +330,6 @@
     <ClCompile Include="..\..\..\source\game\EntityList.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\..\source\game\FuncLadder.cpp">
-      <Filter>Source Files\ents\func</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\..\source\game\PointCamera.cpp">
       <Filter>Source Files\ents\point</Filter>
     </ClCompile>
@@ -369,6 +372,12 @@
     <ClCompile Include="..\..\..\source\game\LadderMovementController.cpp">
       <Filter>Source Files\character_movement</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\..\source\game\BaseMover.cpp">
+      <Filter>Source Files\ents\func\mover</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\source\game\FuncLadder.cpp">
+      <Filter>Source Files\ents\func\mover</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\source\game\sxgame.h">
@@ -596,9 +605,6 @@
     <ClInclude Include="..\..\..\source\game\physics_util.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\..\..\source\game\FuncLadder.h">
-      <Filter>Header Files\ents\func</Filter>
-    </ClInclude>
     <ClInclude Include="..\..\..\source\game\PlayerSpawn.h">
       <Filter>Header Files\ents\info</Filter>
     </ClInclude>
@@ -644,6 +650,12 @@
     <ClInclude Include="..\..\..\source\game\LadderMovementController.h">
       <Filter>Header Files\character_movement</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\..\source\game\BaseMover.h">
+      <Filter>Header Files\ents\func\mover</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\source\game\FuncLadder.h">
+      <Filter>Header Files\ents\func\mover</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\..\..\source\game\sxgame.rc">
diff --git a/source/game/BaseMover.cpp b/source/game/BaseMover.cpp
new file mode 100644
index 000000000..773eefe87
--- /dev/null
+++ b/source/game/BaseMover.cpp
@@ -0,0 +1,467 @@
+#include "BaseMover.h"
+#include "BaseCharacter.h"
+
+TODO("Trigger OnPlayerGetOn/OnPlayerGetOff events");
+TODO("Handle MOVER_NO_AUTOMOUNT flag");
+
+BEGIN_PROPTABLE(CBaseMover)
+	DEFINE_FIELD_VECTORFN(m_vUpPoint, PDFF_USE_GIZMO, "up_point", "Up point", setUpPoint, EDITOR_POINTCOORD)
+
+	//! Игрок присоединился
+	DEFINE_OUTPUT(m_onPlayerGetOn, "OnPlayerGetOn", "On player get on")
+	//! Игрок отсоединился
+	DEFINE_OUTPUT(m_onPlayerGetOff, "OnPlayerGetOff", "On player get off")
+
+	//! Включает
+	DEFINE_INPUT(turnOn, "turnOn", "Turn on", PDF_NONE)
+	//! Выключает
+	DEFINE_INPUT(turnOff, "turnOff", "Turn off", PDF_NONE)
+	//! Переключает состояние
+	DEFINE_INPUT(toggle, "toggle", "Toggle", PDF_NONE)
+
+	//! Изначально выключена
+	DEFINE_FLAG(MOVER_INITIALLY_DISABLED, "Start disabled")
+	DEFINE_FLAG(MOVER_NO_AUTOMOUNT, "Disable automount")
+END_PROPTABLE()
+
+REGISTER_ENTITY(CBaseMover, base_mover);
+
+IEventChannel<XEventPhysicsStep> *CBaseMover::m_pTickEventChannel = NULL;
+
+CBaseMover::CBaseMover():
+	m_physicsTicker(this)
+{
+	if(!m_pTickEventChannel)
+	{
+		m_pTickEventChannel = Core_GetIXCore()->getEventChannel<XEventPhysicsStep>(EVENT_PHYSICS_STEP_GUID);
+	}
+}
+
+CBaseMover::~CBaseMover()
+{
+	disable();
+	mem_release(m_pCollideShape);
+	mem_release(m_pGhostObject);
+}
+
+void CBaseMover::setUpPoint(const float3 &vUp)
+{
+	m_vUpPoint = vUp;
+
+	initPhysics();
+}
+
+void CBaseMover::createPhysBody()
+{
+	if(!m_pGhostObject)
+	{
+		GetPhysics()->newGhostObject(&m_pGhostObject);
+		m_pGhostObject->setPosition(getPos());
+		m_pGhostObject->setUserPointer(this);
+		m_pGhostObject->setUserTypeId(1);
+		m_pGhostObject->setCollisionFlags(m_pGhostObject->getCollisionFlags() ^ XCF_NO_CONTACT_RESPONSE);
+		m_pGhostObject->setCollisionShape(m_pCollideShape);
+		if(m_isEnabled)
+		{
+			GetPhysWorld()->addCollisionObject(m_pGhostObject, CG_LADDER, CG_CHARACTER);
+		}
+	}
+	else
+	{
+		m_pGhostObject->setCollisionShape(m_pCollideShape);
+	}
+}
+
+void CBaseMover::setPos(const float3 &pos)
+{
+	BaseClass::setPos(pos);
+	initPhysics();
+	SAFE_CALL(m_pGhostObject, setPosition, pos);
+}
+
+void CBaseMover::updateFlags()
+{
+	BaseClass::updateFlags();
+
+	if(getFlags() & MOVER_INITIALLY_DISABLED)
+	{
+		disable();
+	}
+	else
+	{
+		enable();
+	}
+}
+
+void CBaseMover::turnOn(inputdata_t *pInputdata)
+{
+	enable();
+}
+
+void CBaseMover::turnOff(inputdata_t *pInputdata)
+{
+	disable();
+}
+
+void CBaseMover::toggle(inputdata_t *pInputdata)
+{
+	if(m_isEnabled)
+	{
+		turnOff(pInputdata);
+	}
+	else
+	{
+		turnOn(pInputdata);
+	}
+}
+
+void CBaseMover::initPhysics()
+{
+	//TODO: сделать обработку ситуации когда m_vUpPoint ниже getPos
+	mem_release(m_pCollideShape);
+
+	float3 vDelta = getOrient() * m_vUpPoint;
+	SMAABB aabb = getBound();
+	float3 vMinDelta, vMaxDelta;
+
+	float3_t aPointsBot[] = { 
+		{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 = aabb.vMin + vDelta;
+	vMaxDelta = aabb.vMax + vDelta;
+
+	float3_t aPointsTop[] = {
+		{ vMinDelta.x, vMinDelta.y, vMinDelta.z },
+		{ vMaxDelta.x, vMinDelta.y, vMinDelta.z },
+		{ vMinDelta.x, vMinDelta.y, vMaxDelta.z },
+		{ vMaxDelta.x, vMinDelta.y, vMaxDelta.z }
+	};
+
+	const UINT c_uSize = 14;
+	UINT uIndex = 0;
+	float3_t aShapePoints[c_uSize] = {};
+
+	if(vDelta.x > 0.0f || vDelta.z > 0.0f)
+	{
+		aShapePoints[uIndex++] = aPointsBot[0];
+		aShapePoints[uIndex++] = aPointsTop[3];
+	}
+	if(vDelta.x < 0.0f || vDelta.z < 0.0f)
+	{
+		aShapePoints[uIndex++] = aPointsBot[3];
+		aShapePoints[uIndex++] = aPointsTop[0];
+	}
+	if(vDelta.x < 0.0f || vDelta.z > 0.0f)
+	{
+		aShapePoints[uIndex++] = aPointsBot[1];
+		aShapePoints[uIndex++] = aPointsTop[2];
+	}
+	if(vDelta.x > 0.0f || vDelta.z < 0.0f)
+	{
+		aShapePoints[uIndex++] = aPointsBot[2];
+		aShapePoints[uIndex++] = aPointsTop[1];
+	}
+
+	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};
+	aShapePoints[uIndex++] = {vMinDelta.x, vMaxDelta.y, vMinDelta.z};
+	aShapePoints[uIndex++] = {vMinDelta.x, vMaxDelta.y, vMaxDelta.z};
+
+	GetPhysics()->newConvexHullShape(uIndex, aShapePoints, &m_pCollideShape, sizeof(float3_t), false);
+	createPhysBody();
+}
+
+void CBaseMover::renderEditor(bool is3D, bool bRenderSelection, IXGizmoRenderer *pRenderer)
+{
+	if(pRenderer)
+	{
+		const float4 c_vLineColor = bRenderSelection ? float4(1.0f, 0.0f, 0.0f, 1.0f) : float4(1.0f, 0.0f, 1.0f, 1.0f);
+		pRenderer->setColor(c_vLineColor);
+		pRenderer->setLineWidth(is3D ? 0.02f : 1.0f);
+
+		SMAABB aabb = getBound();
+		pRenderer->drawAABB(aabb + getPos());
+		pRenderer->drawAABB(aabb + getUpPos());
+		pRenderer->jumpTo(getPos());
+		pRenderer->lineTo(getUpPos());
+	}
+}
+
+void CBaseMover::getMinMax(float3 *min, float3 *max)
+{
+	SMAABB aabb = getBound();
+	aabb = SMAABBConvex(aabb, aabb + getUpPos() - getPos());
+
+	if(min)
+	{
+		*min = aabb.vMin;
+	}
+
+	if(max)
+	{
+		*max = aabb.vMax;
+	}
+}
+
+SMAABB CBaseMover::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)
+{
+	float3 vPoint;
+	SMPLANE plane;
+
+	plane = SMPLANE(float3(1.0f, 0.0f, 0.0f), -aabb.vMax.x);
+	if(plane.intersectLine(&vPoint, vStart, vEnd) && SMIsAABBInsideAABB(SMAABB(vPoint, vPoint), aabb))
+	{
+		*pvOut = vPoint;
+		if(pvNormal)
+		{
+			*pvNormal = float3(1.0f, 0.0f, 0.0f);
+		}
+		return(true);
+	}
+	
+	plane = SMPLANE(float3(-1.0f, 0.0f, 0.0f), aabb.vMin.x);
+	if(plane.intersectLine(&vPoint, vStart, vEnd) && SMIsAABBInsideAABB(SMAABB(vPoint, vPoint), aabb))
+	{
+		*pvOut = vPoint;
+		if(pvNormal)
+		{
+			*pvNormal = float3(-1.0f, 0.0f, 0.0f);
+		}
+		return(true);
+	}
+
+	plane = SMPLANE(float3(0.0f, 1.0f, 0.0f), -aabb.vMax.y);
+	if(plane.intersectLine(&vPoint, vStart, vEnd) && SMIsAABBInsideAABB(SMAABB(vPoint, vPoint), aabb))
+	{
+		*pvOut = vPoint;
+		if(pvNormal)
+		{
+			*pvNormal = float3(0.0f, 1.0f, 0.0f);
+		}
+		return(true);
+	}
+
+	plane = SMPLANE(float3(0.0f, -1.0f, 0.0f), aabb.vMin.y);
+	if(plane.intersectLine(&vPoint, vStart, vEnd) && SMIsAABBInsideAABB(SMAABB(vPoint, vPoint), aabb))
+	{
+		*pvOut = vPoint;
+		if(pvNormal)
+		{
+			*pvNormal = float3(0.0f, -1.0f, 0.0f);
+		}
+		return(true);
+	}
+
+	plane = SMPLANE(float3(0.0f, 0.0f, 1.0f), -aabb.vMax.z);
+	if(plane.intersectLine(&vPoint, vStart, vEnd) && SMIsAABBInsideAABB(SMAABB(vPoint, vPoint), aabb))
+	{
+		*pvOut = vPoint;
+		if(pvNormal)
+		{
+			*pvNormal = float3(0.0f, 0.0f, 1.0f);
+		}
+		return(true);
+	}
+
+	plane = SMPLANE(float3(0.0f, 0.0f, -1.0f), aabb.vMin.z);
+	if(plane.intersectLine(&vPoint, vStart, vEnd) && SMIsAABBInsideAABB(SMAABB(vPoint, vPoint), aabb))
+	{
+		*pvOut = vPoint;
+		if(pvNormal)
+		{
+			*pvNormal = float3(0.0f, 0.0f, -1.0f);
+		}
+		return(true);
+	}
+
+	return(false);
+}
+
+bool CBaseMover::rayTest(const float3 &vStart, const float3 &vEnd, float3 *pvOut, float3 *pvNormal, bool isRayInWorldSpace, bool bReturnNearestPoint)
+{
+	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);
+}
+
+void CBaseMover::onUse(CBaseEntity *pUser)
+{
+	handleCharacterMount(pUser);
+	BaseClass::onUse(pUser);
+}
+
+float3 CBaseMover::getUpPos()
+{
+	return(getOrient() * m_vUpPoint + getPos());
+}
+
+void CBaseMover::handleCharacterMount(CBaseEntity *pEntity)
+{
+	if(fstrcmp(pEntity->getClassName(), "player"))
+	{
+		return;
+	}
+	CBaseCharacter *pCharacter = (CBaseCharacter*)pEntity;
+
+	IMovementController *pController = NULL;
+	newMovementController(&pController);
+
+	if(pController)
+	{
+		pCharacter->setMovementController(pController);
+		mem_release(pController);
+	}
+}
+
+void CBaseMover::onPhysicsStep()
+{
+	if(!m_pGhostObject || !m_isEnabled)
+	{
+		return;
+	}
+
+	Array<CBaseEntity*> aCurrentTouchingEntities(m_aTouchedEntities.size());
+
+	for(UINT i = 0, l = m_pGhostObject->getOverlappingPairCount(); i < l; ++i)
+	{
+		IXCollisionPair *pair = m_pGhostObject->getOverlappingPair(i);
+
+		for(UINT j = 0, jl = pair->getContactManifoldCount(); j < jl; ++j)
+		{
+			IXContactManifold *pManifold = pair->getContactManifold(j);
+			if(pManifold->getContactCount() > 0)
+			{
+				IXCollisionObject *p0 = pair->getObject0();
+				IXCollisionObject *p1 = pair->getObject1();
+
+				const IXCollisionObject *pObject = p0 == m_pGhostObject ? p1 : p0;
+
+				if(pObject->getUserPointer() && pObject->getUserTypeId() == 1)
+				{
+					CBaseEntity *pEnt = (CBaseEntity*)pObject->getUserPointer();
+					if(pEnt)
+					{
+						aCurrentTouchingEntities.push_back(pEnt);
+						if(m_aTouchedEntities.indexOf(pEnt) < 0)
+						{
+							handleCharacterMount(pEnt);
+							//printf("touched %s\n", pEnt->getClassName());
+						}
+					}
+				}
+				break;
+			}
+		}
+	}
+
+	m_aTouchedEntities = aCurrentTouchingEntities;
+}
+
+
+void CBaseMover::enable()
+{
+	if(!m_isEnabled)
+	{
+		if(m_pGhostObject)
+		{
+			GetPhysWorld()->addCollisionObject(m_pGhostObject, CG_LADDER, CG_CHARACTER);
+		}
+		m_pTickEventChannel->addListener(&m_physicsTicker);
+		m_isEnabled = true;
+	}
+}
+
+void CBaseMover::disable()
+{
+	if(m_isEnabled)
+	{
+		if(m_pGhostObject)
+		{
+			GetPhysWorld()->removeCollisionObject(m_pGhostObject);
+		}
+		m_pTickEventChannel->removeListener(&m_physicsTicker);
+		m_isEnabled = false;
+	}
+}
+
+void CBaseMover::newMovementController(IMovementController **ppOut)
+{
+	*ppOut = NULL;
+}
+
+//##########################################################################
+
+void CPhysicsLadderTickEventListener::onEvent(const XEventPhysicsStep *pData)
+{
+	m_pMover->onPhysicsStep();
+}
diff --git a/source/game/BaseMover.h b/source/game/BaseMover.h
new file mode 100644
index 000000000..6b9776be3
--- /dev/null
+++ b/source/game/BaseMover.h
@@ -0,0 +1,78 @@
+#ifndef __BASE_MOVER_H
+#define __BASE_MOVER_H
+
+#include "PointEntity.h"
+
+#define MOVER_INITIALLY_DISABLED ENT_FLAG_0
+#define MOVER_NO_AUTOMOUNT ENT_FLAG_1
+
+class CBaseMover;
+class CPhysicsLadderTickEventListener final: public IEventListener<XEventPhysicsStep>
+{
+public:
+	CPhysicsLadderTickEventListener(CBaseMover *pMover):
+		m_pMover(pMover)
+	{
+	}
+	void onEvent(const XEventPhysicsStep *pData) override;
+
+private:
+	CBaseMover *m_pMover;
+};
+
+class IMovementController;
+class CBaseMover: public CPointEntity
+{
+	DECLARE_CLASS(CBaseMover, CPointEntity);
+	DECLARE_PROPTABLE();
+	friend class CPhysicsLadderTickEventListener;
+public:
+	DECLARE_CONSTRUCTOR();
+	~CBaseMover();
+
+	void setPos(const float3 &pos) override;
+
+	void renderEditor(bool is3D, bool bRenderSelection, IXGizmoRenderer *pRenderer) override;
+
+	void getMinMax(float3 *min, float3 *max) override;
+
+	bool rayTest(const float3 &vStart, const float3 &vEnd, float3 *pvOut = NULL, float3 *pvNormal = NULL, bool isRayInWorldSpace = true, bool bReturnNearestPoint = false) override;
+
+	void onUse(CBaseEntity *pUser) override;
+
+	float3 getUpPos();
+
+private:
+	void handleCharacterMount(CBaseEntity *pEntity);
+	void createPhysBody();
+	void setUpPoint(const float3 &vUp);
+	void updateFlags() override;
+
+	void turnOn(inputdata_t *pInputdata);
+	void turnOff(inputdata_t *pInputdata);
+	void toggle(inputdata_t *pInputdata);
+	void initPhysics();
+	void enable();
+	void disable();
+	void onPhysicsStep();
+
+	SMAABB getBound();
+
+	virtual void newMovementController(IMovementController **ppOut);
+
+private:
+	float3_t m_vUpPoint = float3_t(0.0f, 2.0f, 0.0f);
+	bool m_isEnabled = false;
+
+	output_t m_onPlayerGetOn;
+	output_t m_onPlayerGetOff;
+
+	IXGhostObject *m_pGhostObject = NULL;
+	IXConvexHullShape *m_pCollideShape = NULL;
+	static IEventChannel<XEventPhysicsStep> *m_pTickEventChannel;
+	CPhysicsLadderTickEventListener m_physicsTicker;
+
+	Array<CBaseEntity*> m_aTouchedEntities;
+};
+
+#endif
diff --git a/source/game/FuncLadder.cpp b/source/game/FuncLadder.cpp
index d738af4db..dc0bea4ab 100644
--- a/source/game/FuncLadder.cpp
+++ b/source/game/FuncLadder.cpp
@@ -12,449 +12,12 @@ REGISTER_ENTITY(CInfoLadderDismount, info_ladder_dismount);
 //##########################################################################
 
 BEGIN_PROPTABLE(CFuncLadder)
-	DEFINE_FIELD_VECTORFN(m_vUpPoint, PDFF_USE_GIZMO, "up_point", "Up point", setUpPoint, EDITOR_POINTCOORD)
-
-	//! Плеер залез на лестницу
-	DEFINE_OUTPUT(m_onPlayerGetOn, "OnPlayerGetOn", "On player get on")
-	//! Плеер слез с лестницы
-	DEFINE_OUTPUT(m_onPlayerGetOff, "OnPlayerGetOff", "On player get off")
-
-	//! Включает лестницу
-	DEFINE_INPUT(turnOn, "turnOn", "Turn on", PDF_NONE)
-	//! Выключает лестницу
-	DEFINE_INPUT(turnOff, "turnOff", "Turn off", PDF_NONE)
-	//! Переключает состояние лестницы
-	DEFINE_INPUT(toggle, "toggle", "Toggle", PDF_NONE)
-
-	//! Изначально выключена
-	DEFINE_FLAG(LADDER_INITIALLY_DISABLED, "Start disabled")
+	// empty
 END_PROPTABLE()
 
 REGISTER_ENTITY(CFuncLadder, func_ladder);
 
-IEventChannel<XEventPhysicsStep> *CFuncLadder::m_pTickEventChannel = NULL;
-
-CFuncLadder::CFuncLadder():
-	m_physicsTicker(this)
-{
-	if(!m_pTickEventChannel)
-	{
-		m_pTickEventChannel = Core_GetIXCore()->getEventChannel<XEventPhysicsStep>(EVENT_PHYSICS_STEP_GUID);
-	}
-}
-
-CFuncLadder::~CFuncLadder()
-{
-	disable();
-	mem_release(m_pCollideShape);
-	mem_release(m_pGhostObject);
-}
-
-void CFuncLadder::setUpPoint(const float3 &vUp)
-{
-	m_vUpPoint = vUp;
-
-	initPhysics();
-}
-
-void CFuncLadder::createPhysBody()
-{
-	if(!m_pGhostObject)
-	{
-		GetPhysics()->newGhostObject(&m_pGhostObject);
-		m_pGhostObject->setPosition(getPos());
-		m_pGhostObject->setUserPointer(this);
-		m_pGhostObject->setUserTypeId(1);
-		m_pGhostObject->setCollisionFlags(m_pGhostObject->getCollisionFlags() ^ XCF_NO_CONTACT_RESPONSE);
-		m_pGhostObject->setCollisionShape(m_pCollideShape);
-		if(m_isEnabled)
-		{
-			GetPhysWorld()->addCollisionObject(m_pGhostObject, CG_LADDER, CG_CHARACTER);
-		}
-	}
-	else
-	{
-		m_pGhostObject->setCollisionShape(m_pCollideShape);
-	}
-}
-
-void CFuncLadder::setPos(const float3 &pos)
-{
-	BaseClass::setPos(pos);
-	initPhysics();
-	SAFE_CALL(m_pGhostObject, setPosition, pos);
-}
-
-void CFuncLadder::updateFlags()
-{
-	BaseClass::updateFlags();
-
-	if(getFlags() & LADDER_INITIALLY_DISABLED)
-	{
-		disable();
-	}
-	else
-	{
-		enable();
-	}
-}
-
-void CFuncLadder::turnOn(inputdata_t *pInputdata)
-{
-	enable();
-}
-
-void CFuncLadder::turnOff(inputdata_t *pInputdata)
-{
-	disable();
-}
-
-void CFuncLadder::toggle(inputdata_t *pInputdata)
-{
-	if(m_isEnabled)
-	{
-		turnOff(pInputdata);
-	}
-	else
-	{
-		turnOn(pInputdata);
-	}
-}
-
-void CFuncLadder::initPhysics()
-{
-	//TODO: сделать обработку ситуации когда m_vUpPoint ниже getPos
-	mem_release(m_pCollideShape);
-
-	float3 vDelta = getOrient() * m_vUpPoint;
-	SMAABB aabb = getBound();
-	float3 vMinDelta, vMaxDelta;
-
-	float3_t aPointsBot[] = { 
-		{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 = aabb.vMin + vDelta;
-	vMaxDelta = aabb.vMax + vDelta;
-
-	float3_t aPointsTop[] = {
-		{ vMinDelta.x, vMinDelta.y, vMinDelta.z },
-		{ vMaxDelta.x, vMinDelta.y, vMinDelta.z },
-		{ vMinDelta.x, vMinDelta.y, vMaxDelta.z },
-		{ vMaxDelta.x, vMinDelta.y, vMaxDelta.z }
-	};
-
-	const UINT c_uSize = 14;
-	UINT uIndex = 0;
-	float3_t aShapePoints[c_uSize] = {};
-
-	if(vDelta.x > 0.0f || vDelta.z > 0.0f)
-	{
-		aShapePoints[uIndex++] = aPointsBot[0];
-		aShapePoints[uIndex++] = aPointsTop[3];
-	}
-	if(vDelta.x < 0.0f || vDelta.z < 0.0f)
-	{
-		aShapePoints[uIndex++] = aPointsBot[3];
-		aShapePoints[uIndex++] = aPointsTop[0];
-	}
-	if(vDelta.x < 0.0f || vDelta.z > 0.0f)
-	{
-		aShapePoints[uIndex++] = aPointsBot[1];
-		aShapePoints[uIndex++] = aPointsTop[2];
-	}
-	if(vDelta.x > 0.0f || vDelta.z < 0.0f)
-	{
-		aShapePoints[uIndex++] = aPointsBot[2];
-		aShapePoints[uIndex++] = aPointsTop[1];
-	}
-
-	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};
-	aShapePoints[uIndex++] = {vMinDelta.x, vMaxDelta.y, vMinDelta.z};
-	aShapePoints[uIndex++] = {vMinDelta.x, vMaxDelta.y, vMaxDelta.z};
-
-	GetPhysics()->newConvexHullShape(uIndex, aShapePoints, &m_pCollideShape, sizeof(float3_t), false);
-	createPhysBody();
-}
-
-void CFuncLadder::renderEditor(bool is3D, bool bRenderSelection, IXGizmoRenderer *pRenderer)
-{
-	if(pRenderer)
-	{
-		const float4 c_vLineColor = bRenderSelection ? float4(1.0f, 0.0f, 0.0f, 1.0f) : float4(1.0f, 0.0f, 1.0f, 1.0f);
-		pRenderer->setColor(c_vLineColor);
-		pRenderer->setLineWidth(is3D ? 0.02f : 1.0f);
-
-		SMAABB aabb = getBound();
-		pRenderer->drawAABB(aabb + getPos());
-		pRenderer->drawAABB(aabb + getUpPos());
-		pRenderer->jumpTo(getPos());
-		pRenderer->lineTo(getUpPos());
-	}
-}
-
-void CFuncLadder::getMinMax(float3 *min, float3 *max)
-{
-	SMAABB aabb = getBound();
-	aabb = SMAABBConvex(aabb, aabb + getUpPos() - getPos());
-
-	if(min)
-	{
-		*min = aabb.vMin;
-	}
-
-	if(max)
-	{
-		*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)
-{
-	float3 vPoint;
-	SMPLANE plane;
-
-	plane = SMPLANE(float3(1.0f, 0.0f, 0.0f), -aabb.vMax.x);
-	if(plane.intersectLine(&vPoint, vStart, vEnd) && SMIsAABBInsideAABB(SMAABB(vPoint, vPoint), aabb))
-	{
-		*pvOut = vPoint;
-		if(pvNormal)
-		{
-			*pvNormal = float3(1.0f, 0.0f, 0.0f);
-		}
-		return(true);
-	}
-	
-	plane = SMPLANE(float3(-1.0f, 0.0f, 0.0f), aabb.vMin.x);
-	if(plane.intersectLine(&vPoint, vStart, vEnd) && SMIsAABBInsideAABB(SMAABB(vPoint, vPoint), aabb))
-	{
-		*pvOut = vPoint;
-		if(pvNormal)
-		{
-			*pvNormal = float3(-1.0f, 0.0f, 0.0f);
-		}
-		return(true);
-	}
-
-	plane = SMPLANE(float3(0.0f, 1.0f, 0.0f), -aabb.vMax.y);
-	if(plane.intersectLine(&vPoint, vStart, vEnd) && SMIsAABBInsideAABB(SMAABB(vPoint, vPoint), aabb))
-	{
-		*pvOut = vPoint;
-		if(pvNormal)
-		{
-			*pvNormal = float3(0.0f, 1.0f, 0.0f);
-		}
-		return(true);
-	}
-
-	plane = SMPLANE(float3(0.0f, -1.0f, 0.0f), aabb.vMin.y);
-	if(plane.intersectLine(&vPoint, vStart, vEnd) && SMIsAABBInsideAABB(SMAABB(vPoint, vPoint), aabb))
-	{
-		*pvOut = vPoint;
-		if(pvNormal)
-		{
-			*pvNormal = float3(0.0f, -1.0f, 0.0f);
-		}
-		return(true);
-	}
-
-	plane = SMPLANE(float3(0.0f, 0.0f, 1.0f), -aabb.vMax.z);
-	if(plane.intersectLine(&vPoint, vStart, vEnd) && SMIsAABBInsideAABB(SMAABB(vPoint, vPoint), aabb))
-	{
-		*pvOut = vPoint;
-		if(pvNormal)
-		{
-			*pvNormal = float3(0.0f, 0.0f, 1.0f);
-		}
-		return(true);
-	}
-
-	plane = SMPLANE(float3(0.0f, 0.0f, -1.0f), aabb.vMin.z);
-	if(plane.intersectLine(&vPoint, vStart, vEnd) && SMIsAABBInsideAABB(SMAABB(vPoint, vPoint), aabb))
-	{
-		*pvOut = vPoint;
-		if(pvNormal)
-		{
-			*pvNormal = float3(0.0f, 0.0f, -1.0f);
-		}
-		return(true);
-	}
-
-	return(false);
-}
-
-bool CFuncLadder::rayTest(const float3 &vStart, const float3 &vEnd, float3 *pvOut, float3 *pvNormal, bool isRayInWorldSpace, bool bReturnNearestPoint)
-{
-	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);
-}
-
-void CFuncLadder::onUse(CBaseEntity *pUser)
-{
-	connectToLadder(pUser);
-	BaseClass::onUse(pUser);
-}
-
-float3 CFuncLadder::getUpPos()
-{
-	return(getOrient() * m_vUpPoint + getPos());
-}
-
-void CFuncLadder::connectToLadder(CBaseEntity *pEntity)
-{
-	if(fstrcmp(pEntity->getClassName(), "player"))
-	{
-		return;
-	}
-	CBaseCharacter *pCharacter = (CBaseCharacter*)pEntity;
-	CLadderMovementController *pController = new CLadderMovementController(this);
-	pCharacter->setMovementController(pController);
-	mem_release(pController);
-}
-
-void CFuncLadder::onPhysicsStep()
-{
-	if(!m_pGhostObject || !m_isEnabled)
-	{
-		return;
-	}
-
-	Array<CBaseEntity*> aCurrentTouchingEntities(m_aTouchedEntities.size());
-
-	for(UINT i = 0, l = m_pGhostObject->getOverlappingPairCount(); i < l; ++i)
-	{
-		IXCollisionPair *pair = m_pGhostObject->getOverlappingPair(i);
-
-		for(UINT j = 0, jl = pair->getContactManifoldCount(); j < jl; ++j)
-		{
-			IXContactManifold *pManifold = pair->getContactManifold(j);
-			if(pManifold->getContactCount() > 0)
-			{
-				IXCollisionObject *p0 = pair->getObject0();
-				IXCollisionObject *p1 = pair->getObject1();
-
-				const IXCollisionObject *pObject = p0 == m_pGhostObject ? p1 : p0;
-
-				if(pObject->getUserPointer() && pObject->getUserTypeId() == 1)
-				{
-					CBaseEntity *pEnt = (CBaseEntity*)pObject->getUserPointer();
-					if(pEnt)
-					{
-						aCurrentTouchingEntities.push_back(pEnt);
-						if(m_aTouchedEntities.indexOf(pEnt) < 0)
-						{
-							connectToLadder(pEnt);
-							//printf("touched %s\n", pEnt->getClassName());
-						}
-					}
-				}
-				break;
-			}
-		}
-	}
-
-	m_aTouchedEntities = aCurrentTouchingEntities;
-}
-
-
-void CFuncLadder::enable()
-{
-	if(!m_isEnabled)
-	{
-		if(m_pGhostObject)
-		{
-			GetPhysWorld()->addCollisionObject(m_pGhostObject, CG_LADDER, CG_CHARACTER);
-		}
-		m_pTickEventChannel->addListener(&m_physicsTicker);
-		m_isEnabled = true;
-	}
-}
-
-void CFuncLadder::disable()
-{
-	if(m_isEnabled)
-	{
-		if(m_pGhostObject)
-		{
-			GetPhysWorld()->removeCollisionObject(m_pGhostObject);
-		}
-		m_pTickEventChannel->removeListener(&m_physicsTicker);
-		m_isEnabled = false;
-	}
-}
-
-void CPhysicsLadderTickEventListener::onEvent(const XEventPhysicsStep *pData)
+void CFuncLadder::newMovementController(IMovementController **ppOut)
 {
-	m_pLadder->onPhysicsStep();
+	*ppOut = new CLadderMovementController(this);
 }
diff --git a/source/game/FuncLadder.h b/source/game/FuncLadder.h
index 5ea6f958b..9fd556a77 100644
--- a/source/game/FuncLadder.h
+++ b/source/game/FuncLadder.h
@@ -1,7 +1,7 @@
 #ifndef __FUNC_LADDER_H
 #define __FUNC_LADDER_H
 
-#include "PointEntity.h"
+#include "BaseMover.h"
 
 class CInfoLadderDismount: public CPointEntity
 {
@@ -13,72 +13,16 @@ public:
 
 //##########################################################################
 
-#define LADDER_INITIALLY_DISABLED ENT_FLAG_0
-
-class CFuncLadder;
-class CPhysicsLadderTickEventListener final : public IEventListener<XEventPhysicsStep>
+class CFuncLadder: public CBaseMover
 {
-public:
-	CPhysicsLadderTickEventListener(CFuncLadder *pLadder) :
-		m_pLadder(pLadder)
-	{
-	}
-	void onEvent(const XEventPhysicsStep *pData) override;
-
-private:
-	CFuncLadder *m_pLadder;
-};
-
-class CFuncLadder: public CPointEntity
-{
-	DECLARE_CLASS(CFuncLadder, CPointEntity);
+	DECLARE_CLASS(CFuncLadder, CBaseMover);
 	DECLARE_PROPTABLE();
 	friend class CPhysicsLadderTickEventListener;
 public:
-	DECLARE_CONSTRUCTOR();
-	~CFuncLadder();
-
-	void setPos(const float3 &pos) override;
-
-	void renderEditor(bool is3D, bool bRenderSelection, IXGizmoRenderer *pRenderer) override;
-
-	void getMinMax(float3 *min, float3 *max) override;
-
-	bool rayTest(const float3 &vStart, const float3 &vEnd, float3 *pvOut = NULL, float3 *pvNormal = NULL, bool isRayInWorldSpace = true, bool bReturnNearestPoint = false) override;
-
-	void onUse(CBaseEntity *pUser) override;
-
-	float3 getUpPos();
-
-private:
-	void connectToLadder(CBaseEntity *pEntity);
-	void createPhysBody();
-	void setUpPoint(const float3 &vUp);
-	void updateFlags() override;
-
-	void turnOn(inputdata_t *pInputdata);
-	void turnOff(inputdata_t *pInputdata);
-	void toggle(inputdata_t *pInputdata);
-	void initPhysics();
-	void enable();
-	void disable();
-	void onPhysicsStep();
-
-	SMAABB getBound();
-
+	DECLARE_TRIVIAL_CONSTRUCTOR();
+	
 private:
-	float3_t m_vUpPoint = float3_t(0.0f, 2.0f, 0.0f);
-	bool m_isEnabled = false;
-
-	output_t m_onPlayerGetOn;
-	output_t m_onPlayerGetOff;
-
-	IXGhostObject *m_pGhostObject = NULL;
-	IXConvexHullShape *m_pCollideShape = NULL;
-	static IEventChannel<XEventPhysicsStep> *m_pTickEventChannel;
-	CPhysicsLadderTickEventListener m_physicsTicker;
-
-	Array<CBaseEntity*> m_aTouchedEntities;
+	void newMovementController(IMovementController **ppOut) override;
 };
 
 #endif
-- 
GitLab