From 82e0a30100cf6008baf539127ff06ba4de4b8b1b Mon Sep 17 00:00:00 2001
From: EyeGuy <vanya619@list.ru>
Date: Wed, 18 May 2022 22:20:40 +0300
Subject: [PATCH 1/6] Ladder first part

---
 source/game/BaseCharacter.cpp                 |  23 +-
 source/game/BaseCharacter.h                   |   7 +
 source/game/FuncLadder.cpp                    | 223 +++++++++++++++++-
 source/game/FuncLadder.h                      |  36 ++-
 source/game/Player.cpp                        |  43 +++-
 source/physics/CharacterController.cpp        |  10 +-
 source/physics/CharacterController.h          |  10 +-
 .../xcommon/physics/IXCharacterController.h   |  10 +-
 source/xcommon/physics/IXCollisionObject.h    |   1 +
 9 files changed, 335 insertions(+), 28 deletions(-)

diff --git a/source/game/BaseCharacter.cpp b/source/game/BaseCharacter.cpp
index 7c080032c..6eb31fba5 100644
--- a/source/game/BaseCharacter.cpp
+++ b/source/game/BaseCharacter.cpp
@@ -8,6 +8,7 @@ See the license in LICENSE
 #include "GameData.h"
 #include "BaseTool.h"
 #include "BaseWeapon.h"
+#include "FuncLadder.h"
 
 #include <aigrid/sxaigrid.h>
 
@@ -128,6 +129,26 @@ CBaseCharacter::~CBaseCharacter()
 	}
 }
 
+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()
+{
+	m_pLadder = NULL;
+}
 
 void CBaseCharacter::attack(BOOL state)
 {
@@ -528,7 +549,7 @@ void CBaseCharacter::use(bool start)
 		float3 end = start + dir * 2.0f;
 
 		CClosestNotMeRayResultCallback cb(m_pGhostObject);
-		GetPhysWorld()->rayTest(start, end, &cb);
+		GetPhysWorld()->rayTest(start, end, &cb, CG_CHARACTER, CG_ALL ^ (CG_HITBOX | CG_STATIC | CG_TRIGGER | CG_WATER));
 
 		if(cb.hasHit() && cb.m_result.pCollisionObject->getUserPointer() && cb.m_result.pCollisionObject->getUserTypeId() == 1)
 		{
diff --git a/source/game/BaseCharacter.h b/source/game/BaseCharacter.h
index 1b93d64e6..a4cebf3e3 100644
--- a/source/game/BaseCharacter.h
+++ b/source/game/BaseCharacter.h
@@ -36,10 +36,12 @@ enum PLAYER_MOVE
 	PM_RUN = 0x40,       //!< бежать
 	PM_CRAWL = 0x80,     //!< лежать
 	PM_OBSERVER = 0x100, //!< наблюдатель
+	PM_LADDER = 0x200,   //!< лестница
 
 	PM_STOP = 0xFFFF
 };
 
+class CFuncLadder;
 class CHUDcontroller;
 
 class CBaseCharacter;
@@ -134,6 +136,10 @@ public:
 
 	void onPostLoad() override;
 
+	void mountToLadder(CFuncLadder *pLadder);
+
+	void dismountFromLadder();
+
 protected:
 	//! Фонарик
 	CLightDirectional* m_flashlight;
@@ -184,6 +190,7 @@ protected:
 
 	virtual float3 getHeadOffset();
 
+	CFuncLadder *m_pLadder = NULL;
 private:
 	static IEventChannel<XEventPhysicsStep> *m_pTickEventChannel;
 	CCharacterPhysicsTickEventListener m_physicsTicker;
diff --git a/source/game/FuncLadder.cpp b/source/game/FuncLadder.cpp
index c969b8b66..f6a9442eb 100644
--- a/source/game/FuncLadder.cpp
+++ b/source/game/FuncLadder.cpp
@@ -1,4 +1,5 @@
 #include "FuncLadder.h"
+#include "BaseCharacter.h"
 
 
 BEGIN_PROPTABLE(CInfoLadderDismount)
@@ -30,10 +31,51 @@ 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_isUpSet = true;
 	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)
@@ -42,27 +84,111 @@ void CFuncLadder::setPos(const float3 &pos)
 	if(!m_isUpSet)
 	{
 		m_vUpPoint = (float3)(pos + float3(0.0f, 2.0f, 0.0f));
+		initPhysics();
 	}
+	SAFE_CALL(m_pGhostObject, setPosition, pos);
 }
 
 void CFuncLadder::updateFlags()
 {
 	BaseClass::updateFlags();
 
-	m_isEnabled = !(getFlags() & LADDER_INITIALLY_DISABLED);
+	if(getFlags() & LADDER_INITIALLY_DISABLED)
+	{
+		disable();
+	}
+	else
+	{
+		enable();
+	}
 }
 
 void CFuncLadder::turnOn(inputdata_t *pInputdata)
 {
-	m_isEnabled = true;
+	enable();
 }
+
 void CFuncLadder::turnOff(inputdata_t *pInputdata)
 {
-	m_isEnabled = false;
+	disable();
 }
+
 void CFuncLadder::toggle(inputdata_t *pInputdata)
 {
-	m_isEnabled = !m_isEnabled;
+	if(m_isEnabled)
+	{
+		turnOff(pInputdata);
+	}
+	else
+	{
+		turnOn(pInputdata);
+	}
+}
+
+void CFuncLadder::initPhysics()
+{
+	//TODO: сделать обработку ситуации когда m_vUpPoint ниже getPos
+	mem_release(m_pCollideShape);
+
+	float3 vDelta = m_vUpPoint - getPos();
+	float3 vMin, vMax;
+	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 }
+	};
+
+	vMinDelta = vMin + vDelta;
+	vMaxDelta = 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++] = 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++] = 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)
@@ -105,3 +231,92 @@ bool CFuncLadder::rayTest(const float3 &vStart, const float3 &vEnd, float3 *pvOu
 	
 	return(false);
 }
+
+void CFuncLadder::onUse(CBaseEntity *pUser)
+{
+	connectToLadder(pUser);
+	BaseClass::onUse(pUser);
+}
+
+float3 CFuncLadder::getUpPos()
+{
+	return m_vUpPoint;
+}
+
+void CFuncLadder::connectToLadder(CBaseEntity *pEntity)
+{
+	if(fstrcmp(pEntity->getClassName(), "player"))
+	{
+		return;
+	}
+	CBaseCharacter *pCharacter = (CBaseCharacter*)pEntity;
+	pCharacter->mountToLadder(this);
+}
+
+void CFuncLadder::onPhysicsStep()
+{
+	if(!m_pGhostObject || !m_isEnabled)
+	{
+		return;
+	}
+
+	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)
+					{
+						connectToLadder(pEnt);
+						//printf("touched %s\n", pEnt->getClassName());
+					}
+				}
+				break;
+			}
+		}
+	}
+}
+
+
+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)
+{
+	m_pLadder->onPhysicsStep();
+}
diff --git a/source/game/FuncLadder.h b/source/game/FuncLadder.h
index ad349f999..78530c7ac 100644
--- a/source/game/FuncLadder.h
+++ b/source/game/FuncLadder.h
@@ -15,12 +15,28 @@ public:
 
 #define LADDER_INITIALLY_DISABLED ENT_FLAG_0
 
+class CFuncLadder;
+class CPhysicsLadderTickEventListener final : public IEventListener<XEventPhysicsStep>
+{
+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_PROPTABLE();
+	friend class CPhysicsLadderTickEventListener;
 public:
-	DECLARE_TRIVIAL_CONSTRUCTOR();
+	DECLARE_CONSTRUCTOR();
+	~CFuncLadder();
 
 	void setPos(const float3 &pos) override;
 
@@ -30,21 +46,37 @@ public:
 
 	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();
 
 private:
 	float3_t m_vUpPoint;
 	bool m_isUpSet = false;
-	bool m_isEnabled = true;
+	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;
+
 };
 
 #endif
diff --git a/source/game/Player.cpp b/source/game/Player.cpp
index 17e2832e5..a63d6cfca 100644
--- a/source/game/Player.cpp
+++ b/source/game/Player.cpp
@@ -8,6 +8,7 @@ See the license in LICENSE
 #include "Player.h"
 #include "LightDirectional.h"
 #include "BaseAmmo.h"
+#include "FuncLadder.h"
 
 #include "BaseWeapon.h"
 
@@ -195,7 +196,7 @@ void CPlayer::updateInput(float dt)
 		{
 			//dt *= 5.0f;
 		}
-		dt *= 10.0f;
+		//dt *= 10.0f;
 		float3 dir;
 		bool mov = false;
 		if(m_uMoveDir & PM_FORWARD)
@@ -224,7 +225,7 @@ void CPlayer::updateInput(float dt)
 
 		if(m_uMoveDir & PM_CROUCH || (m_fCurrentHeight < 0.99f && !m_pCharacter->canStandUp((m_fCapsHeight - m_fCapsRadius * 2.0f) * (1.0f - m_fCurrentHeight))))
 		{
-			m_fCurrentHeight -= dt;
+			m_fCurrentHeight -= dt * 10.0f;
 			float fMinHeight = (m_fCapsHeightCrouch - m_fCapsRadius * 2.0f) / (m_fCapsHeight - m_fCapsRadius * 2.0f);
 			if(m_fCurrentHeight < fMinHeight)
 			{
@@ -233,7 +234,7 @@ void CPlayer::updateInput(float dt)
 		}
 		else
 		{
-			m_fCurrentHeight += dt;
+			m_fCurrentHeight += dt* 10.0f;
 			if(m_fCurrentHeight > 1.0f)
 			{
 				m_fCurrentHeight = 1.0f;
@@ -243,7 +244,37 @@ void CPlayer::updateInput(float dt)
 
 		if(m_uMoveDir & PM_OBSERVER)
 		{
-			setPos(getPos() + m_pHeadEnt->getOrient() * (SMVector3Normalize(dir) * dt));
+			setPos(getPos() + m_pHeadEnt->getOrient() * (SMVector3Normalize(dir) * dt * 10.0f));
+		}
+		else if(m_uMoveDir & PM_LADDER)
+		{
+			if(m_uMoveDir & PM_FORWARD)
+			{
+				float3 vSpeed(0.0f, 3.0f, 0.0f);
+				float3 fNewPos = getPos() + vSpeed * dt;
+				if(m_pLadder->getUpPos().y >= fNewPos.y)
+				{
+					setPos(fNewPos);
+				}
+				else
+				{
+					setPos(m_pLadder->getUpPos());
+				}
+			}
+			else if(m_uMoveDir & PM_BACKWARD)
+			{
+				float3 vSpeed(0.0f, -3.0f, 0.0f);
+				float3 fNewPos = getPos() + vSpeed * dt;
+				if(m_pLadder->getPos().y <= fNewPos.y)
+				{
+					setPos(fNewPos);
+				}
+				else
+				{
+					setPos(m_pLadder->getPos());
+				}
+			}
+			m_vCurrentSpeed = {0.0f, 0.0f, 0.0f};
 		}
 		else
 		{
@@ -302,7 +333,7 @@ void CPlayer::updateInput(float dt)
 				}
 				else
 				{
-					m_vCurrentSpeed = (float3)(m_vCurrentSpeed + SMVector3Normalize(fAccelDir) * fAccel * dt);
+					m_vCurrentSpeed = (float3)(m_vCurrentSpeed + SMVector3Normalize(fAccelDir) * fAccel * dt * 10.0f);
 
 					if(SMVector3Dot(m_vCurrentSpeed, m_vTargetSpeed) > SM_PIDIV2 && SMVector3Length2(m_vCurrentSpeed) > SMVector3Length2(m_vTargetSpeed))
 					{
@@ -328,7 +359,7 @@ void CPlayer::updateInput(float dt)
 
 					
 
-					m_fViewbobStep += dt * *cl_bob_period * fBobCoeff;
+					m_fViewbobStep += dt * 10.0f * *cl_bob_period * fBobCoeff;
 					
 
 					/*if(m_uMoveDir & PM_RUN)
diff --git a/source/physics/CharacterController.cpp b/source/physics/CharacterController.cpp
index 6af897f2e..b7dee7a3a 100644
--- a/source/physics/CharacterController.cpp
+++ b/source/physics/CharacterController.cpp
@@ -65,23 +65,23 @@ void XMETHODCALLTYPE CCharacterController::unregisterInWorld()
 	}
 }
 
-void XMETHODCALLTYPE CCharacterController::setMaxJumpHeight(float fHeight) const
+void XMETHODCALLTYPE CCharacterController::setMaxJumpHeight(float fHeight)
 {
 	m_pController->setMaxJumpHeight(fHeight);
 }
-void XMETHODCALLTYPE CCharacterController::setJumpSpeed(float fSpeed) const
+void XMETHODCALLTYPE CCharacterController::setJumpSpeed(float fSpeed)
 {
 	m_pController->setJumpSpeed(fSpeed);
 }
-void XMETHODCALLTYPE CCharacterController::setGravity(const float3_t &vSpeed) const
+void XMETHODCALLTYPE CCharacterController::setGravity(const float3_t &vSpeed)
 {
 	m_pController->setGravity(F3_BTVEC(vSpeed));
 }
-void XMETHODCALLTYPE CCharacterController::setFallSpeed(float fSpeed) const
+void XMETHODCALLTYPE CCharacterController::setFallSpeed(float fSpeed)
 {
 	m_pController->setFallSpeed(fSpeed);
 }
-void XMETHODCALLTYPE CCharacterController::setMaxPenetrationDepth(float fMaxDepth) const
+void XMETHODCALLTYPE CCharacterController::setMaxPenetrationDepth(float fMaxDepth)
 {
 	m_pController->setMaxPenetrationDepth(fMaxDepth);
 }
diff --git a/source/physics/CharacterController.h b/source/physics/CharacterController.h
index 24cbe4061..f3af6e1cb 100644
--- a/source/physics/CharacterController.h
+++ b/source/physics/CharacterController.h
@@ -24,11 +24,11 @@ public:
 	void XMETHODCALLTYPE registerInWorld(IXPhysicsWorld *pWorld) override;
 	void XMETHODCALLTYPE unregisterInWorld() override;
 
-	void XMETHODCALLTYPE setMaxJumpHeight(float fHeight) const override;
-	void XMETHODCALLTYPE setJumpSpeed(float fSpeed) const override;
-	void XMETHODCALLTYPE setGravity(const float3_t &vSpeed) const override;
-	void XMETHODCALLTYPE setFallSpeed(float fSpeed) const override;
-	void XMETHODCALLTYPE setMaxPenetrationDepth(float fMaxDepth) const override;
+	void XMETHODCALLTYPE setMaxJumpHeight(float fHeight) override;
+	void XMETHODCALLTYPE setJumpSpeed(float fSpeed) override;
+	void XMETHODCALLTYPE setGravity(const float3_t &vSpeed) override;
+	void XMETHODCALLTYPE setFallSpeed(float fSpeed) override;
+	void XMETHODCALLTYPE setMaxPenetrationDepth(float fMaxDepth) override;
 
 private:
 	btKinematicCharacterController *m_pController;
diff --git a/source/xcommon/physics/IXCharacterController.h b/source/xcommon/physics/IXCharacterController.h
index ac56270ab..4f35e0c5a 100644
--- a/source/xcommon/physics/IXCharacterController.h
+++ b/source/xcommon/physics/IXCharacterController.h
@@ -20,11 +20,11 @@ public:
 	virtual void XMETHODCALLTYPE registerInWorld(IXPhysicsWorld *pWorld) = 0;
 	virtual void XMETHODCALLTYPE unregisterInWorld() = 0;
 	
-	virtual void XMETHODCALLTYPE setMaxJumpHeight(float fHeight) const = 0;
-	virtual void XMETHODCALLTYPE setJumpSpeed(float fSpeed) const = 0;
-	virtual void XMETHODCALLTYPE setGravity(const float3_t &vSpeed) const = 0;
-	virtual void XMETHODCALLTYPE setFallSpeed(float fSpeed) const = 0;
-	virtual void XMETHODCALLTYPE setMaxPenetrationDepth(float fMaxDepth) const = 0;
+	virtual void XMETHODCALLTYPE setMaxJumpHeight(float fHeight) = 0;
+	virtual void XMETHODCALLTYPE setJumpSpeed(float fSpeed) = 0;
+	virtual void XMETHODCALLTYPE setGravity(const float3_t &vSpeed) = 0;
+	virtual void XMETHODCALLTYPE setFallSpeed(float fSpeed) = 0;
+	virtual void XMETHODCALLTYPE setMaxPenetrationDepth(float fMaxDepth) = 0;
 
 
 	/*
diff --git a/source/xcommon/physics/IXCollisionObject.h b/source/xcommon/physics/IXCollisionObject.h
index 7b0747039..2592ff18b 100644
--- a/source/xcommon/physics/IXCollisionObject.h
+++ b/source/xcommon/physics/IXCollisionObject.h
@@ -21,6 +21,7 @@ enum COLLISION_GROUP
 	CG_BULLETFIRE = BIT(8),
 	CG_NPCVIEW = BIT(9),
 	CG_DOOR = BIT(10),
+	CG_LADDER = BIT(11),
 
 	CG_ALL = 0xFFFFFFFF
 };
-- 
GitLab


From 79611009ef392882b91e3b7de8c83ca9d1ae23b6 Mon Sep 17 00:00:00 2001
From: D-AIRY <admin@ds-servers.com>
Date: Fri, 20 Dec 2024 20:39:33 +0300
Subject: [PATCH 2/6] Added movement controllers

---
 proj/sxgame/vs2013/sxgame.vcxproj         |   3 +
 proj/sxgame/vs2013/sxgame.vcxproj.filters |  17 +-
 source/game/BaseCharacter.cpp             |  38 +++--
 source/game/BaseCharacter.h               |  19 +--
 source/game/FuncLadder.cpp                | 187 +++++++++++++++++++---
 source/game/FuncLadder.h                  |   3 +
 source/game/IMovementController.h         |  19 +++
 source/game/LadderMovementController.cpp  | 119 ++++++++++++++
 source/game/LadderMovementController.h    |  32 ++++
 source/game/Player.cpp                    |  67 +++-----
 source/game/Player.h                      |   1 +
 11 files changed, 407 insertions(+), 98 deletions(-)
 create mode 100644 source/game/IMovementController.h
 create mode 100644 source/game/LadderMovementController.cpp
 create mode 100644 source/game/LadderMovementController.h

diff --git a/proj/sxgame/vs2013/sxgame.vcxproj b/proj/sxgame/vs2013/sxgame.vcxproj
index 5f6b91c46..b70be725f 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 e9732da19..912083c6e 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 9fe180197..89dd1892c 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 bdd0670bc..ad1cbcb3d 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 f6a9442eb..d8e859d99 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 78530c7ac..520bae843 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 000000000..cf98817c7
--- /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 000000000..6152853f6
--- /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 000000000..a41cc4ac2
--- /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 acf1e85c2..3268ecebb 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 797497644..34ab93672 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:
-- 
GitLab


From 1b67a09bb90ab7ab9fe0cf88e444943d37eeaeff Mon Sep 17 00:00:00 2001
From: D-AIRY <admin@ds-servers.com>
Date: Sun, 22 Dec 2024 00:45:23 +0300
Subject: [PATCH 3/6] make XPET_POINTCOORD relative

---
 build/demos/levels/demo_ladder/demo_ladder.ent |  6 +++---
 source/game/FuncLadder.cpp                     | 17 ++++++-----------
 source/game/FuncLadder.h                       |  3 +--
 source/terrax/mainWindow.cpp                   |  5 +++--
 4 files changed, 13 insertions(+), 18 deletions(-)

diff --git a/build/demos/levels/demo_ladder/demo_ladder.ent b/build/demos/levels/demo_ladder/demo_ladder.ent
index 25b90276a..f762571a8 100644
--- a/build/demos/levels/demo_ladder/demo_ladder.ent
+++ b/build/demos/levels/demo_ladder/demo_ladder.ent
@@ -68,7 +68,7 @@ name =
 model = 
 is_static = 1
 glow_color_ref = 
-glow_color = 0.000000 0.000000 0.000000
+glow_color = 0.000000 0.273974 0.827866
 flags = 0
 classname = prop_static
 auto_physbox = 1
@@ -130,10 +130,10 @@ OnTurnOn =
 OnTurnOff = 
 
 [{F7FAD41B-4168-41D2-9E82-51DA455F22B6}]
-up_point = 12.860000 5.000000 10.004517
+up_point = 0.000000 4.000000 0.000001
 rotation = 0.000000 0.000000 0.000000 1.000000
 parent = 
-origin = 12.858377 1.000000 10.004519
+origin = 12.849999 1.000000 9.999999
 name = 
 flags = 0
 classname = func_ladder
diff --git a/source/game/FuncLadder.cpp b/source/game/FuncLadder.cpp
index d8e859d99..e6dd9e1ba 100644
--- a/source/game/FuncLadder.cpp
+++ b/source/game/FuncLadder.cpp
@@ -52,7 +52,6 @@ CFuncLadder::~CFuncLadder()
 
 void CFuncLadder::setUpPoint(const float3 &vUp)
 {
-	m_isUpSet = true;
 	m_vUpPoint = vUp;
 
 	initPhysics();
@@ -82,11 +81,7 @@ void CFuncLadder::createPhysBody()
 void CFuncLadder::setPos(const float3 &pos)
 {
 	BaseClass::setPos(pos);
-	if(!m_isUpSet)
-	{
-		m_vUpPoint = (float3)(pos + float3(0.0f, 2.0f, 0.0f));
-		initPhysics();
-	}
+	initPhysics();
 	SAFE_CALL(m_pGhostObject, setPosition, pos);
 }
 
@@ -131,7 +126,7 @@ void CFuncLadder::initPhysics()
 	//TODO: сделать обработку ситуации когда m_vUpPoint ниже getPos
 	mem_release(m_pCollideShape);
 
-	float3 vDelta = m_vUpPoint - getPos();
+	float3 vDelta = getOrient() * m_vUpPoint;
 	SMAABB aabb = getBound();
 	float3 vMinDelta, vMaxDelta;
 
@@ -201,16 +196,16 @@ void CFuncLadder::renderEditor(bool is3D, bool bRenderSelection, IXGizmoRenderer
 
 		SMAABB aabb = getBound();
 		pRenderer->drawAABB(aabb + getPos());
-		pRenderer->drawAABB(aabb + m_vUpPoint);
+		pRenderer->drawAABB(aabb + getUpPos());
 		pRenderer->jumpTo(getPos());
-		pRenderer->lineTo(m_vUpPoint);
+		pRenderer->lineTo(getUpPos());
 	}
 }
 
 void CFuncLadder::getMinMax(float3 *min, float3 *max)
 {
 	SMAABB aabb = getBound();
-	aabb = SMAABBConvex(aabb, aabb + (getUpPos() - getPos()));
+	aabb = SMAABBConvex(aabb, aabb + getUpPos() - getPos());
 
 	if(min)
 	{
@@ -367,7 +362,7 @@ void CFuncLadder::onUse(CBaseEntity *pUser)
 
 float3 CFuncLadder::getUpPos()
 {
-	return(m_vUpPoint);
+	return(getOrient() * m_vUpPoint + getPos());
 }
 
 void CFuncLadder::connectToLadder(CBaseEntity *pEntity)
diff --git a/source/game/FuncLadder.h b/source/game/FuncLadder.h
index 520bae843..5ea6f958b 100644
--- a/source/game/FuncLadder.h
+++ b/source/game/FuncLadder.h
@@ -67,8 +67,7 @@ private:
 	SMAABB getBound();
 
 private:
-	float3_t m_vUpPoint;
-	bool m_isUpSet = false;
+	float3_t m_vUpPoint = float3_t(0.0f, 2.0f, 0.0f);
 	bool m_isEnabled = false;
 
 	output_t m_onPlayerGetOn;
diff --git a/source/terrax/mainWindow.cpp b/source/terrax/mainWindow.cpp
index 6996e46fb..2b9b8270d 100644
--- a/source/terrax/mainWindow.cpp
+++ b/source/terrax/mainWindow.cpp
@@ -4532,15 +4532,16 @@ XDECLARE_PROP_GIZMO(Radius, void XMETHODCALLTYPE onChange(float fNewRadius, IXEd
 XDECLARE_PROP_GIZMO(Handle, void XMETHODCALLTYPE moveTo(const float3 &vNewPos, IXEditorGizmoHandle *pGizmo) override
 {
 	pGizmo->setPos(vNewPos);
+	float3_t vTmp = m_pObj->getOrient().Conjugate() * (vNewPos - m_pObj->getPos());
 	char tmp[64];
-	sprintf(tmp, "%f %f %f", vNewPos.x, vNewPos.y, vNewPos.z);
+	sprintf(tmp, "%f %f %f", vTmp.x, vTmp.y, vTmp.z);
 	m_pCommand->setKV(m_field.szKey, tmp);
 }, void init()
 {
 	float3_t vec;
 	if(sscanf(m_pObj->getKV(m_field.szKey), "%f %f %f", &vec.x, &vec.y, &vec.z))
 	{
-		m_pGizmo->setPos(vec);
+		m_pGizmo->setPos(m_pObj->getOrient() * vec + m_pObj->getPos());
 	}
 });
 
-- 
GitLab


From 4238a7238684f49f7ce627d888b5713b3183d241 Mon Sep 17 00:00:00 2001
From: D-AIRY <admin@ds-servers.com>
Date: Mon, 23 Dec 2024 15:36:57 +0300
Subject: [PATCH 4/6] Animate ladder mounting

---
 source/game/LadderMovementController.cpp | 19 +++++++++++++++----
 source/game/LadderMovementController.h   |  9 +++++++++
 2 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/source/game/LadderMovementController.cpp b/source/game/LadderMovementController.cpp
index 6152853f6..ef824bf04 100644
--- a/source/game/LadderMovementController.cpp
+++ b/source/game/LadderMovementController.cpp
@@ -45,9 +45,10 @@ void CLadderMovementController::setCharacter(CBaseCharacter *pCharacter)
 	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);
+	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]);
 }
 
 void CLadderMovementController::handleMove(const float3 &vDir)
@@ -68,7 +69,17 @@ bool CLadderMovementController::handleUse()
 
 void CLadderMovementController::update(float fDt)
 {
-	if(m_bWillDismount)
+	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);
diff --git a/source/game/LadderMovementController.h b/source/game/LadderMovementController.h
index a41cc4ac2..0b34f2256 100644
--- a/source/game/LadderMovementController.h
+++ b/source/game/LadderMovementController.h
@@ -26,6 +26,15 @@ private:
 
 	float3_t m_vMoveDir;
 
+	struct
+	{
+		bool is = false;
+		float fFrac = 0.0f;
+		float3_t vStartPos;
+		float3_t vTargetPos;
+	}
+	m_mounting;
+
 	bool m_bWillDismount = false;
 };
 
-- 
GitLab


From 384709661283526a34e4833706b45a865f1a2a85 Mon Sep 17 00:00:00 2001
From: D-AIRY <admin@ds-servers.com>
Date: Tue, 24 Dec 2024 14:05:45 +0300
Subject: [PATCH 5/6] Prevent spawning proxy object when target is not found

---
 source/terrax/terrax.cpp | 37 +++++++++++++++++++++----------------
 1 file changed, 21 insertions(+), 16 deletions(-)

diff --git a/source/terrax/terrax.cpp b/source/terrax/terrax.cpp
index 56df65475..3c256fea7 100644
--- a/source/terrax/terrax.cpp
+++ b/source/terrax/terrax.cpp
@@ -1278,28 +1278,33 @@ int main(int argc, char **argv)
 								}
 
 								CProxyObject *pProxy = new CProxyObject(guid);
-								pProxy->setDstObject(guidTarget);
-
-								for(UINT k = 0, kl = pSArr->size(); k < kl; ++k)
+								if(pProxy->setDstObject(guidTarget))
 								{
-									szGUID = pSArr->at(k)->getString();
-									if(!szGUID || !XGUIDFromString(&guid, szGUID))
+									for(UINT k = 0, kl = pSArr->size(); k < kl; ++k)
 									{
-										LibReport(REPORT_MSG_LEVEL_ERROR, "Invalid model '%u' guid in proxy '%u' in '%s'. '%s'\n", k, j, szFile, szGUID ? szGUID : "");
-										continue;
+										szGUID = pSArr->at(k)->getString();
+										if(!szGUID || !XGUIDFromString(&guid, szGUID))
+										{
+											LibReport(REPORT_MSG_LEVEL_ERROR, "Invalid model '%u' guid in proxy '%u' in '%s'. '%s'\n", k, j, szFile, szGUID ? szGUID : "");
+											continue;
+										}
+
+										pProxy->addSrcModel(guid);
 									}
 
-									pProxy->addSrcModel(guid);
-								}
-
-								g_apProxies.push_back(pProxy);
+									g_apProxies.push_back(pProxy);
 
-								//pProxy->build();
+									//pProxy->build();
 
-								add_ref(pProxy);
-								g_pLevelObjects.push_back(pProxy);
+									add_ref(pProxy);
+									g_pLevelObjects.push_back(pProxy);
 
-								isLoaded = true;
+									isLoaded = true;
+								}
+								else
+								{
+									mem_release(pProxy);
+								}
 							}
 						}
 					}
@@ -1849,7 +1854,7 @@ void XRender3D()
 			pvData = NULL;
 
 			XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){
-				float3_t vPos = pObj->getPos();
+				//float3_t vPos = pObj->getPos();
 				TODO("Add visibility check");
 				/*if(fViewportBorders.x > vPos.x || fViewportBorders.z < vPos.x || fViewportBorders.y < vPos.z) // not visible
 				{
-- 
GitLab


From b07c08018491c15be1f859a1b39630553cf23f82 Mon Sep 17 00:00:00 2001
From: D-AIRY <admin@ds-servers.com>
Date: Tue, 24 Dec 2024 15:32:09 +0300
Subject: [PATCH 6/6] Updated SMAABBIntersectLine

---
 source/game/FuncLadder.cpp | 98 ++++++++++++++++++++------------------
 1 file changed, 52 insertions(+), 46 deletions(-)

diff --git a/source/game/FuncLadder.cpp b/source/game/FuncLadder.cpp
index e6dd9e1ba..d738af4db 100644
--- a/source/game/FuncLadder.cpp
+++ b/source/game/FuncLadder.cpp
@@ -225,77 +225,83 @@ SMAABB CFuncLadder::getBound()
 
 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 vPoint;
+	SMPLANE plane;
 
-	float3 vDir = vEnd - vStart;
-
-	for(int i = 0; i < 3; ++i)
+	plane = SMPLANE(float3(1.0f, 0.0f, 0.0f), -aabb.vMax.x);
+	if(plane.intersectLine(&vPoint, vStart, vEnd) && SMIsAABBInsideAABB(SMAABB(vPoint, vPoint), aabb))
 	{
-		if(SMIsZero(vDir[i]))
+		*pvOut = vPoint;
+		if(pvNormal)
 		{
-			if(vStart[i] < aabb.vMin[i] || vStart[i] > aabb.vMax[i])
-			{
-				return(false);
-			}
+			*pvNormal = float3(1.0f, 0.0f, 0.0f);
 		}
-
-		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(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)
 		{
-			return(false);
+			*pvNormal = float3(-1.0f, 0.0f, 0.0f);
 		}
+		return(true);
 	}
 
-	if(pvOut)
+	plane = SMPLANE(float3(0.0f, 1.0f, 0.0f), -aabb.vMax.y);
+	if(plane.intersectLine(&vPoint, vStart, vEnd) && SMIsAABBInsideAABB(SMAABB(vPoint, vPoint), aabb))
 	{
-		*pvOut = vStart + min_t * vDir;
+		*pvOut = vPoint;
+		if(pvNormal)
+		{
+			*pvNormal = float3(0.0f, 1.0f, 0.0f);
+		}
+		return(true);
 	}
 
-	if(pvNormal)
+	plane = SMPLANE(float3(0.0f, -1.0f, 0.0f), aabb.vMin.y);
+	if(plane.intersectLine(&vPoint, vStart, vEnd) && SMIsAABBInsideAABB(SMAABB(vPoint, vPoint), aabb))
 	{
-		*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))
+		*pvOut = vPoint;
+		if(pvNormal)
 		{
-			pvNormal->y = 1.0f;
+			*pvNormal = float3(0.0f, -1.0f, 0.0f);
 		}
-		else if(SMIsZero(aabb.vMin.z - pvOut->z))
+		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->z = -1.0f;
+			*pvNormal = float3(0.0f, 0.0f, 1.0f);
 		}
-		else
+		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->z = 1.0f;
+			*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();
+ 	SMAABB aabb = getBound();
 
 	if(bReturnNearestPoint)
 	{
-- 
GitLab