From f088716cf27a4e4682b33d6fbc2d7938877dbbc6 Mon Sep 17 00:00:00 2001 From: Causeless Date: Tue, 26 Dec 2023 01:46:57 +0000 Subject: [PATCH 01/19] First implementation of auto walkpath crouching --- Entities/AHuman.cpp | 39 +++++++++++++++++++++++++++++++++------ Entities/AHuman.h | 3 +++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 7497ba440..f07802359 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -63,6 +63,7 @@ void AHuman::Clear() m_MoveState = STAND; m_ProneState = NOTPRONE; m_ProneTimer.Reset(); + m_MaxWalkPathCrouchShift = 5.0F; for (int i = 0; i < MOVEMENTSTATECOUNT; ++i) { m_Paths[FGROUND][i].Reset(); m_Paths[BGROUND][i].Reset(); @@ -85,6 +86,7 @@ void AHuman::Clear() m_BGArmFlailScalar = 0.7F; m_EquipHUDTimer.Reset(); m_WalkAngle.fill(Matrix()); + m_WalkPathYOffset = 0.0F; m_ArmSwingRate = 1.0F; m_DeviceArmSwayRate = 0.5F; @@ -1705,6 +1707,22 @@ void AHuman::UpdateWalkAngle(AHuman::Layer whichLayer) { Matrix walkAngle; walkAngle.SetDegAngle(terrainRotationDegs); m_WalkAngle[whichLayer] = walkAngle; + + if (m_pHead) { + // Cast a ray above our head to either side to determine whether we need to crouch + float desiredCrouchHeadRoom = std::floor(m_pHead->GetRadius() + 2.0f); + float toSide = std::floor(m_pHead->GetRadius() + 3.0f); + Vector hitPosLeft = (m_pHead->GetPos() + Vector(-toSide, 0.0F)).Floor(); + Vector hitPosRight = (m_pHead->GetPos() + Vector(toSide, 0.0F)).Floor(); + bool leftHit = g_SceneMan.CastStrengthRay(hitPosLeft, Vector(0.0F, -desiredCrouchHeadRoom), 10.0F, hitPosLeft, 0, g_MaterialGrass); + bool rightHit = g_SceneMan.CastStrengthRay(hitPosRight, Vector(0.0F, -desiredCrouchHeadRoom), 10.0F, hitPosRight, 0, g_MaterialGrass); + float lowestY = std::max(hitPosLeft.m_Y, hitPosRight.m_Y); + float headroom = std::floor(m_pHead->GetPos().m_Y - lowestY); + float adjust = desiredCrouchHeadRoom - headroom; + m_WalkPathYOffset = std::clamp(LERP(0.0F, 1.0F, m_WalkPathYOffset, adjust, 0.3F), 0.0F, m_MaxWalkPathCrouchShift); + } else { + m_WalkPathYOffset = 0.0f; + } } } @@ -2194,6 +2212,9 @@ void AHuman::PreControllerUpdate() std::swap(m_pBGFootGroup, m_BackupBGFootGroup); } + if (m_pFGLeg) { UpdateWalkAngle(FGROUND); } + if (m_pBGLeg) { UpdateWalkAngle(BGROUND); } + // WALKING, OR WE ARE JETPACKING AND STUCK if (m_MoveState == WALK || (m_MoveState == JUMP && isStill)) { m_Paths[FGROUND][STAND].Terminate(); @@ -2212,8 +2233,8 @@ void AHuman::PreControllerUpdate() if (m_pFGLeg && (!m_pBGLeg || !(m_Paths[FGROUND][WALK].PathEnded() && BGLegProg < 0.5F) || m_StrideStart)) { // Reset the stride timer if the path is about to restart. if (m_Paths[FGROUND][WALK].PathEnded() || m_Paths[FGROUND][WALK].PathIsAtStart()) { m_StrideTimer.Reset(); } - m_ArmClimbing[BGROUND] = !m_pFGFootGroup->PushAsLimb(m_Pos + RotateOffset(m_pFGLeg->GetParentOffset()), m_Vel, m_WalkAngle[FGROUND], m_Paths[FGROUND][WALK], deltaTime, &restarted, false, Vector(0.0F, m_Paths[FGROUND][WALK].GetLowestY())); - if (restarted) { UpdateWalkAngle(FGROUND); } + Vector jointPos = m_Pos + RotateOffset(m_pFGLeg->GetParentOffset()) + Vector(0.0F, -m_WalkPathYOffset); + m_ArmClimbing[BGROUND] = !m_pFGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[FGROUND], m_Paths[FGROUND][WALK], deltaTime, &restarted, false, Vector(0.0F, m_Paths[FGROUND][WALK].GetLowestY())); } else { m_ArmClimbing[BGROUND] = false; } @@ -2221,8 +2242,8 @@ void AHuman::PreControllerUpdate() m_StrideStart = false; // Reset the stride timer if the path is about to restart. if (m_Paths[BGROUND][WALK].PathEnded() || m_Paths[BGROUND][WALK].PathIsAtStart()) { m_StrideTimer.Reset(); } - m_ArmClimbing[FGROUND] = !m_pBGFootGroup->PushAsLimb(m_Pos + RotateOffset(m_pBGLeg->GetParentOffset()), m_Vel, m_WalkAngle[BGROUND], m_Paths[BGROUND][WALK], deltaTime, &restarted, false, Vector(0.0F, m_Paths[BGROUND][WALK].GetLowestY())); - if (restarted) { UpdateWalkAngle(BGROUND); } + Vector jointPos = m_Pos + RotateOffset(m_pBGLeg->GetParentOffset()) + Vector(0.0F, -m_WalkPathYOffset); + m_ArmClimbing[FGROUND] = !m_pBGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[BGROUND], m_Paths[BGROUND][WALK], deltaTime, &restarted, false, Vector(0.0F, m_Paths[BGROUND][WALK].GetLowestY())); } else { if (m_pBGLeg) { m_pBGFootGroup->FlailAsLimb(m_Pos, RotateOffset(m_pBGLeg->GetParentOffset()), m_pBGLeg->GetMaxLength(), m_PrevVel, m_AngularVel, m_pBGLeg->GetMass(), deltaTime); } m_ArmClimbing[FGROUND] = false; @@ -2360,9 +2381,15 @@ void AHuman::PreControllerUpdate() m_Paths[FGROUND][ARMCRAWL].Terminate(); m_Paths[BGROUND][ARMCRAWL].Terminate(); - if (m_pFGLeg) { m_pFGFootGroup->PushAsLimb(m_Pos.GetFloored() + m_pFGLeg->GetParentOffset().GetXFlipped(m_HFlipped), m_Vel, m_WalkAngle[FGROUND], m_Paths[FGROUND][STAND], deltaTime, nullptr, !m_pBGLeg, Vector(0.0F, m_Paths[FGROUND][STAND].GetLowestY())); } + if (m_pFGLeg) { + Vector jointPos = m_Pos.GetFloored() + m_pFGLeg->GetParentOffset().GetXFlipped(m_HFlipped) + Vector(0.0F, -m_WalkPathYOffset); + m_pFGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[FGROUND], m_Paths[FGROUND][STAND], deltaTime, nullptr, !m_pBGLeg, Vector(0.0F, m_Paths[FGROUND][STAND].GetLowestY())); + } - if (m_pBGLeg) { m_pBGFootGroup->PushAsLimb(m_Pos.GetFloored() + m_pBGLeg->GetParentOffset().GetXFlipped(m_HFlipped), m_Vel, m_WalkAngle[BGROUND], m_Paths[BGROUND][STAND], deltaTime, nullptr, !m_pFGLeg, Vector(0.0F, m_Paths[FGROUND][STAND].GetLowestY())); } + if (m_pBGLeg) { + Vector jointPos = m_Pos.GetFloored() + m_pBGLeg->GetParentOffset().GetXFlipped(m_HFlipped) + Vector(0.0F, -m_WalkPathYOffset); + m_pBGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[BGROUND], m_Paths[BGROUND][STAND], deltaTime, nullptr, !m_pFGLeg, Vector(0.0F, m_Paths[FGROUND][STAND].GetLowestY())); + } } } } diff --git a/Entities/AHuman.h b/Entities/AHuman.h index 8cfd0c529..2fe061254 100644 --- a/Entities/AHuman.h +++ b/Entities/AHuman.h @@ -935,6 +935,8 @@ DefaultPieMenuNameGetter("Default Human Pie Menu"); ProneState m_ProneState; // Timer for the going prone procedural animation Timer m_ProneTimer; + // The maximum amount our walkpath can be shifted upwards to crouch and avoid ceilings above us + float m_MaxWalkPathCrouchShift; // Limb paths for different movement states. // [0] is for the foreground limbs, and [1] is for BG. LimbPath m_Paths[2][MOVEMENTSTATECOUNT]; @@ -958,6 +960,7 @@ DefaultPieMenuNameGetter("Default Human Pie Menu"); float m_BGArmFlailScalar; //!< The rate at which this AHuman's BG Arm follows the the bodily rotation. Set to a negative value for a "counterweight" effect. Timer m_EquipHUDTimer; //!< Timer for showing the name of any newly equipped Device. std::array m_WalkAngle; //!< An array of rot angle targets for different movement states. + float m_WalkPathYOffset; float m_ArmSwingRate; //!< Controls the rate at which this AHuman's Arms follow the movement of its Legs while they're not holding device(s). float m_DeviceArmSwayRate; //!< Controls the rate at which this AHuman's Arms follow the movement of its Legs while they're holding device(s). One-handed devices sway half as much as two-handed ones. Defaults to three quarters of Arm swing rate. From 1982920a1cad6d98dd8294a11ab7a7b530ccd29f Mon Sep 17 00:00:00 2001 From: Causeless Date: Tue, 26 Dec 2023 12:54:01 +0000 Subject: [PATCH 02/19] Improved walkpath auto crouch --- Entities/AHuman.cpp | 16 ++++++++-------- Entities/AtomGroup.cpp | 3 ++- Entities/AtomGroup.h | 3 ++- Entities/LimbPath.cpp | 20 ++++++++------------ Entities/LimbPath.h | 8 ++++++++ 5 files changed, 28 insertions(+), 22 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index f07802359..5086df803 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -2233,8 +2233,8 @@ void AHuman::PreControllerUpdate() if (m_pFGLeg && (!m_pBGLeg || !(m_Paths[FGROUND][WALK].PathEnded() && BGLegProg < 0.5F) || m_StrideStart)) { // Reset the stride timer if the path is about to restart. if (m_Paths[FGROUND][WALK].PathEnded() || m_Paths[FGROUND][WALK].PathIsAtStart()) { m_StrideTimer.Reset(); } - Vector jointPos = m_Pos + RotateOffset(m_pFGLeg->GetParentOffset()) + Vector(0.0F, -m_WalkPathYOffset); - m_ArmClimbing[BGROUND] = !m_pFGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[FGROUND], m_Paths[FGROUND][WALK], deltaTime, &restarted, false, Vector(0.0F, m_Paths[FGROUND][WALK].GetLowestY())); + Vector jointPos = m_Pos + RotateOffset(m_pFGLeg->GetParentOffset()); + m_ArmClimbing[BGROUND] = !m_pFGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[FGROUND], m_Paths[FGROUND][WALK], deltaTime, &restarted, false, Vector(0.0F, m_Paths[FGROUND][WALK].GetLowestY()), Vector(0.0F, -m_WalkPathYOffset)); } else { m_ArmClimbing[BGROUND] = false; } @@ -2242,8 +2242,8 @@ void AHuman::PreControllerUpdate() m_StrideStart = false; // Reset the stride timer if the path is about to restart. if (m_Paths[BGROUND][WALK].PathEnded() || m_Paths[BGROUND][WALK].PathIsAtStart()) { m_StrideTimer.Reset(); } - Vector jointPos = m_Pos + RotateOffset(m_pBGLeg->GetParentOffset()) + Vector(0.0F, -m_WalkPathYOffset); - m_ArmClimbing[FGROUND] = !m_pBGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[BGROUND], m_Paths[BGROUND][WALK], deltaTime, &restarted, false, Vector(0.0F, m_Paths[BGROUND][WALK].GetLowestY())); + Vector jointPos = m_Pos + RotateOffset(m_pBGLeg->GetParentOffset()); + m_ArmClimbing[FGROUND] = !m_pBGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[BGROUND], m_Paths[BGROUND][WALK], deltaTime, &restarted, false, Vector(0.0F, m_Paths[BGROUND][WALK].GetLowestY()), Vector(0.0F, -m_WalkPathYOffset)); } else { if (m_pBGLeg) { m_pBGFootGroup->FlailAsLimb(m_Pos, RotateOffset(m_pBGLeg->GetParentOffset()), m_pBGLeg->GetMaxLength(), m_PrevVel, m_AngularVel, m_pBGLeg->GetMass(), deltaTime); } m_ArmClimbing[FGROUND] = false; @@ -2382,13 +2382,13 @@ void AHuman::PreControllerUpdate() m_Paths[BGROUND][ARMCRAWL].Terminate(); if (m_pFGLeg) { - Vector jointPos = m_Pos.GetFloored() + m_pFGLeg->GetParentOffset().GetXFlipped(m_HFlipped) + Vector(0.0F, -m_WalkPathYOffset); - m_pFGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[FGROUND], m_Paths[FGROUND][STAND], deltaTime, nullptr, !m_pBGLeg, Vector(0.0F, m_Paths[FGROUND][STAND].GetLowestY())); + Vector jointPos = m_Pos.GetFloored() + m_pFGLeg->GetParentOffset().GetXFlipped(m_HFlipped); + m_pFGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[FGROUND], m_Paths[FGROUND][STAND], deltaTime, nullptr, !m_pBGLeg, Vector(0.0F, m_Paths[FGROUND][STAND].GetLowestY()), Vector(0.0F, -m_WalkPathYOffset)); } if (m_pBGLeg) { - Vector jointPos = m_Pos.GetFloored() + m_pBGLeg->GetParentOffset().GetXFlipped(m_HFlipped) + Vector(0.0F, -m_WalkPathYOffset); - m_pBGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[BGROUND], m_Paths[BGROUND][STAND], deltaTime, nullptr, !m_pFGLeg, Vector(0.0F, m_Paths[FGROUND][STAND].GetLowestY())); + Vector jointPos = m_Pos.GetFloored() + m_pBGLeg->GetParentOffset().GetXFlipped(m_HFlipped); + m_pBGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[BGROUND], m_Paths[BGROUND][STAND], deltaTime, nullptr, !m_pFGLeg, Vector(0.0F, m_Paths[FGROUND][STAND].GetLowestY()), Vector(0.0F, -m_WalkPathYOffset)); } } } diff --git a/Entities/AtomGroup.cpp b/Entities/AtomGroup.cpp index f8c9e7f42..104e535a7 100644 --- a/Entities/AtomGroup.cpp +++ b/Entities/AtomGroup.cpp @@ -1214,7 +1214,7 @@ namespace RTE { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - bool AtomGroup::PushAsLimb(const Vector &jointPos, const Vector &velocity, const Matrix &rotation, LimbPath &limbPath, const float travelTime, bool *restarted, bool affectRotation, Vector rotationOffset) { + bool AtomGroup::PushAsLimb(const Vector &jointPos, const Vector &velocity, const Matrix &rotation, LimbPath &limbPath, const float travelTime, bool *restarted, bool affectRotation, Vector rotationOffset, Vector positionOffset) { RTEAssert(m_OwnerMOSR, "Tried to push-as-limb an AtomGroup that has no parent!"); bool didWrap = false; @@ -1234,6 +1234,7 @@ namespace RTE { limbPath.SetJointVel(velocity); limbPath.SetRotation(rotation); limbPath.SetRotationOffset(rotationOffset); + limbPath.SetPositionOffset(positionOffset); limbPath.SetFrameTime(travelTime); Vector limbDist = g_SceneMan.ShortestDistance(adjustedJointPos, m_LimbPos, g_SceneMan.SceneWrapsX()); diff --git a/Entities/AtomGroup.h b/Entities/AtomGroup.h index a33c86532..403ed2901 100644 --- a/Entities/AtomGroup.h +++ b/Entities/AtomGroup.h @@ -289,8 +289,9 @@ namespace RTE { /// Pointer to a bool which gets set to true if the LimbPath got restarted during this push. It does NOT get initialized to false! /// Whether the forces created by this should have rotational leverage on the owner or only have translational effect. /// The position, relative to the owning actor's position, that we should rotate around. + /// The positional offset to apply to our limb path. /// Whether the LimbPath passed in could start free of terrain or not. - bool PushAsLimb(const Vector &jointPos, const Vector &velocity, const Matrix &rotation, LimbPath &limbPath, const float travelTime, bool *restarted = nullptr, bool affectRotation = true, Vector rotationOffset = Vector()); + bool PushAsLimb(const Vector &jointPos, const Vector &velocity, const Matrix &rotation, LimbPath &limbPath, const float travelTime, bool *restarted = nullptr, bool affectRotation = true, Vector rotationOffset = Vector(), Vector positionOffset = Vector()); /// /// Makes this AtomGroup travel as a lifeless limb, constrained to a radius around the joint pin in the center. diff --git a/Entities/LimbPath.cpp b/Entities/LimbPath.cpp index 8cfb94eee..bdeac6a2a 100644 --- a/Entities/LimbPath.cpp +++ b/Entities/LimbPath.cpp @@ -42,6 +42,7 @@ void LimbPath::Clear() m_JointVel.Reset(); m_Rotation.Reset(); m_RotationOffset.Reset(); + m_PositionOffset.Reset(); m_TimeLeft = 0.0; m_PathTimer.Reset(); m_SegTimer.Reset(); @@ -188,7 +189,7 @@ int LimbPath::ReadProperty(const std::string_view &propName, Reader &reader) Vector LimbPath::RotatePoint(const Vector &point) const { - Vector offset = m_RotationOffset.GetXFlipped(m_HFlipped); + Vector offset = (m_RotationOffset + m_PositionOffset).GetXFlipped(m_HFlipped); return ((point - offset) * m_Rotation) + offset; } @@ -246,7 +247,7 @@ void LimbPath::Destroy(bool notInherited) Vector LimbPath::GetProgressPos() { - Vector returnVec(m_Start); + Vector returnVec(m_Start + m_PositionOffset); if (IsStaticPoint()) { return m_JointPos + RotatePoint(returnVec); } @@ -273,7 +274,7 @@ Vector LimbPath::GetProgressPos() Vector LimbPath::GetCurrentSegTarget() { - Vector returnVec(m_Start); + Vector returnVec(m_Start + m_PositionOffset); if (IsStaticPoint()) { return m_JointPos + RotatePoint(returnVec); } @@ -561,14 +562,11 @@ bool LimbPath::RestartFree(Vector &limbPos, MOID MOIDToIgnore, int ignoreTeam) m_SegProgress = 0; bool found = false; float result = 0; - - g_SceneMan.GetTerrain()->LockBitmaps(); - acquire_bitmap(g_SceneMan.GetMOIDBitmap()); if (IsStaticPoint()) { Vector notUsed; - Vector targetPos = m_JointPos + RotatePoint(m_Start); + Vector targetPos = m_JointPos + RotatePoint(m_Start + m_PositionOffset); Vector beginPos = targetPos; // TODO: don't hardcode the beginpos beginPos.m_Y -= 24; @@ -590,14 +588,14 @@ bool LimbPath::RestartFree(Vector &limbPos, MOID MOIDToIgnore, int ignoreTeam) int i = 0; for (; i < m_StartSegCount; ++i) { - result = g_SceneMan.CastObstacleRay(GetProgressPos(), RotatePoint(*m_CurrentSegment), notUsed, limbPos, MOIDToIgnore, ignoreTeam, g_MaterialGrass); + result = g_SceneMan.CastObstacleRay(GetProgressPos(), RotatePoint(*m_CurrentSegment + m_PositionOffset), notUsed, limbPos, MOIDToIgnore, ignoreTeam, g_MaterialGrass); // If we found an obstacle after the first pixel, report the current segment as the starting one and that there is free space here if (result > 0) { // Set accurate segment progress // TODO: See if this is a good idea, or if we should just set it to 0 and set limbPos to the start of current segment - m_SegProgress = g_SceneMan.ShortestDistance(GetProgressPos(), limbPos).GetMagnitude() / (*m_CurrentSegment).GetMagnitude(); + m_SegProgress = g_SceneMan.ShortestDistance(GetProgressPos(), limbPos).GetMagnitude() / (*m_CurrentSegment + m_PositionOffset).GetMagnitude(); limbPos = GetProgressPos(); // m_SegProgress = 0; m_Ended = false; @@ -639,8 +637,6 @@ bool LimbPath::RestartFree(Vector &limbPos, MOID MOIDToIgnore, int ignoreTeam) found = true; } } - release_bitmap(g_SceneMan.GetMOIDBitmap()); - g_SceneMan.GetTerrain()->UnlockBitmaps(); if (found) { @@ -689,7 +685,7 @@ void LimbPath::Draw(BITMAP *pTargetBitmap, const Vector &targetPos, unsigned char color) const { - Vector prevPoint = m_Start; + Vector prevPoint = m_Start + m_PositionOffset; Vector nextPoint = prevPoint; for (std::deque::const_iterator itr = m_Segments.begin(); itr != m_Segments.end(); ++itr) { diff --git a/Entities/LimbPath.h b/Entities/LimbPath.h index 276ceb0af..235b2253f 100644 --- a/Entities/LimbPath.h +++ b/Entities/LimbPath.h @@ -494,6 +494,12 @@ ClassInfoGetters; /// The new rotation offset, in local space. void SetRotationOffset(const Vector& rotationOffset) { m_RotationOffset = rotationOffset; } + /// + /// Sets the new position offset. + /// + /// The new position offset, in local space. + void SetPositionOffset(const Vector& positionOffset) { m_PositionOffset = positionOffset; } + ////////////////////////////////////////////////////////////////////////////////////////// // Method: FrameDone @@ -666,6 +672,8 @@ ClassInfoGetters; Matrix m_Rotation; // The point we should be rotated around, in local space. Vector m_RotationOffset; + // The offset to apply to our walkpath position, in local space. + Vector m_PositionOffset; // If GetNextTimeSeg() couldn't use up all frame time because the current segment // ended,this var stores the remainder of time that should be used to progress From 6df4484ede68118df9e2230a9dbe9f9d358b5d7a Mon Sep 17 00:00:00 2001 From: Causeless Date: Wed, 27 Dec 2023 13:54:41 +0000 Subject: [PATCH 03/19] Lua, INI bindings and changelog --- CHANGELOG.md | 2 ++ Entities/AHuman.cpp | 7 +++++++ Entities/AHuman.h | 12 ++++++++++++ Lua/LuaBindingsEntities.cpp | 1 + 4 files changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 238627c1c..3bf7e6845 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - New `Actor` INI and Lua (R/W) property `PainThreshold`, which determines how much damage this actor must take in a frame to play their `PainSound`. This can be set to 0 to never manually play the sound. Defaults to 15. +- New `AHuman` INI and Lua (R/W) property `MaxWalkPathCrouchShift`, which determines how much the actor will automatically duck down to avoid low ceilings above them. This can be set to 0 to never duck. Defaults to 5. + - New `MOPixel` INI and Lua (R/W) property `Staininess`, which defines how likely a pixel is to stain a surface when it collides with it. Staining a surface changes that surface's `Color` to that of this `MOPixel`, without changing the underlying material. Value can be between 0 and 1. Defaults to 0 (never stain). - New `Activity` INI and Lua (R/W) property `AllowsUserSaving`, which can be used to enable/disable manual user saving/loading. This defaults to true for all `GAScripted` with an `OnSave()` function, but false otherwise. Lua `ActivityMan::SaveGame()` function now forces a save even if `AllowsUserSaving` is disabled. This allows mods and scripted gamemodes to handle saving in their own way (for example, only allowing saving at set points). diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 5086df803..32e9b35fc 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -209,6 +209,8 @@ int AHuman::Create(const AHuman &reference) { m_BackupBGFootGroup->SetOwner(this); m_BackupBGFootGroup->SetLimbPos(atomGroupToUseAsFootGroupBG->GetLimbPos()); + m_MaxWalkPathCrouchShift = reference.m_MaxWalkPathCrouchShift; + if (reference.m_StrideSound) { m_StrideSound = dynamic_cast(reference.m_StrideSound->Clone()); } m_ArmsState = reference.m_ArmsState; @@ -285,6 +287,9 @@ int AHuman::ReadProperty(const std::string_view &propName, Reader &reader) { m_BackupBGFootGroup = new AtomGroup(*m_pBGFootGroup); m_BackupBGFootGroup->RemoveAllAtoms(); }); + MatchProperty("MaxWalkPathCrouchShift", { + reader >> m_MaxWalkPathCrouchShift; + }); MatchProperty("StrideSound", { m_StrideSound = new SoundContainer; reader >> m_StrideSound; @@ -348,6 +353,8 @@ int AHuman::Save(Writer &writer) const writer << m_pFGFootGroup; writer.NewProperty("BGFootGroup"); writer << m_pBGFootGroup; + writer.NewProperty("MaxWalkPathCrouchShift"); + writer << m_MaxWalkPathCrouchShift; writer.NewProperty("StrideSound"); writer << m_StrideSound; diff --git a/Entities/AHuman.h b/Entities/AHuman.h index 2fe061254..04a70b725 100644 --- a/Entities/AHuman.h +++ b/Entities/AHuman.h @@ -863,6 +863,18 @@ DefaultPieMenuNameGetter("Default Human Pie Menu"); /// The new device arm sway rate for this AHuman. void SetDeviceArmSwayRate(float newValue) { m_DeviceArmSwayRate = newValue; } + /// + /// Gets this AHuman's max walkpath adjustment upwards to crouch below low ceilings. + /// + /// This AHuman's max walkpath adjustment. + float GetMaxWalkPathCrouchShift() const { return m_MaxWalkPathCrouchShift; } + + /// + /// Sets this AHuman's max walkpath adjustment upwards to crouch below low ceilings. + /// + /// The new value for this AHuman's max walkpath adjustment. + void SetMaxWalkPathCrouchShift(float newValue) { m_MaxWalkPathCrouchShift = newValue; } + /// /// Gets this AHuman's stride sound. Ownership is NOT transferred! /// diff --git a/Lua/LuaBindingsEntities.cpp b/Lua/LuaBindingsEntities.cpp index adecf9a13..2815e350f 100644 --- a/Lua/LuaBindingsEntities.cpp +++ b/Lua/LuaBindingsEntities.cpp @@ -460,6 +460,7 @@ namespace RTE { .property("BGLeg", &AHuman::GetBGLeg, &LuaAdaptersPropertyOwnershipSafetyFaker::AHumanSetBGLeg) .property("FGFoot", &AHuman::GetFGFoot, &LuaAdaptersPropertyOwnershipSafetyFaker::AHumanSetFGFoot) .property("BGFoot", &AHuman::GetBGFoot, &LuaAdaptersPropertyOwnershipSafetyFaker::AHumanSetBGFoot) + .property("MaxWalkPathCrouchShift", &AHuman::GetMaxWalkPathCrouchShift, &AHuman::SetMaxWalkPathCrouchShift) .property("StrideSound", &AHuman::GetStrideSound, &LuaAdaptersPropertyOwnershipSafetyFaker::AHumanSetStrideSound) .property("UpperBodyState", &AHuman::GetUpperBodyState, &AHuman::SetUpperBodyState) .property("MovementState", &AHuman::GetMovementState, &AHuman::SetMovementState) From 04829b5ef82bfbf87825aee6a015c1952651c490 Mon Sep 17 00:00:00 2001 From: Causeless Date: Wed, 27 Dec 2023 14:21:04 +0000 Subject: [PATCH 04/19] Little cleanup, tweaking and adjustment --- CHANGELOG.md | 2 +- Entities/AHuman.cpp | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bf7e6845..2b71481f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,7 +84,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - New `Actor` INI and Lua (R/W) property `PainThreshold`, which determines how much damage this actor must take in a frame to play their `PainSound`. This can be set to 0 to never manually play the sound. Defaults to 15. -- New `AHuman` INI and Lua (R/W) property `MaxWalkPathCrouchShift`, which determines how much the actor will automatically duck down to avoid low ceilings above them. This can be set to 0 to never duck. Defaults to 5. +- New `AHuman` INI and Lua (R/W) property `MaxWalkPathCrouchShift`, which determines how much the actor will automatically duck down to avoid low ceilings above them. This can be set to 0 to never duck. Defaults to 6. - New `MOPixel` INI and Lua (R/W) property `Staininess`, which defines how likely a pixel is to stain a surface when it collides with it. Staining a surface changes that surface's `Color` to that of this `MOPixel`, without changing the underlying material. Value can be between 0 and 1. Defaults to 0 (never stain). diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 32e9b35fc..f509e3ced 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -63,7 +63,7 @@ void AHuman::Clear() m_MoveState = STAND; m_ProneState = NOTPRONE; m_ProneTimer.Reset(); - m_MaxWalkPathCrouchShift = 5.0F; + m_MaxWalkPathCrouchShift = 6.0F; for (int i = 0; i < MOVEMENTSTATECOUNT; ++i) { m_Paths[FGROUND][i].Reset(); m_Paths[BGROUND][i].Reset(); @@ -1717,14 +1717,13 @@ void AHuman::UpdateWalkAngle(AHuman::Layer whichLayer) { if (m_pHead) { // Cast a ray above our head to either side to determine whether we need to crouch - float desiredCrouchHeadRoom = std::floor(m_pHead->GetRadius() + 2.0f); + float desiredCrouchHeadRoom = std::floor(m_pHead->GetRadius() + 1.5f); float toSide = std::floor(m_pHead->GetRadius() + 3.0f); Vector hitPosLeft = (m_pHead->GetPos() + Vector(-toSide, 0.0F)).Floor(); Vector hitPosRight = (m_pHead->GetPos() + Vector(toSide, 0.0F)).Floor(); - bool leftHit = g_SceneMan.CastStrengthRay(hitPosLeft, Vector(0.0F, -desiredCrouchHeadRoom), 10.0F, hitPosLeft, 0, g_MaterialGrass); - bool rightHit = g_SceneMan.CastStrengthRay(hitPosRight, Vector(0.0F, -desiredCrouchHeadRoom), 10.0F, hitPosRight, 0, g_MaterialGrass); - float lowestY = std::max(hitPosLeft.m_Y, hitPosRight.m_Y); - float headroom = std::floor(m_pHead->GetPos().m_Y - lowestY); + g_SceneMan.CastStrengthRay(hitPosLeft, Vector(0.0F, -desiredCrouchHeadRoom), 10.0F, hitPosLeft, 0, g_MaterialGrass); + g_SceneMan.CastStrengthRay(hitPosRight, Vector(0.0F, -desiredCrouchHeadRoom), 10.0F, hitPosRight, 0, g_MaterialGrass); + float headroom = m_pHead->GetPos().m_Y - std::max(hitPosLeft.m_Y, hitPosRight.m_Y); float adjust = desiredCrouchHeadRoom - headroom; m_WalkPathYOffset = std::clamp(LERP(0.0F, 1.0F, m_WalkPathYOffset, adjust, 0.3F), 0.0F, m_MaxWalkPathCrouchShift); } else { From 7bfdfb4ff2e1dda9c8f04c09690f2b1e26db02af Mon Sep 17 00:00:00 2001 From: Causeless Date: Wed, 27 Dec 2023 17:25:46 +0000 Subject: [PATCH 05/19] Stop bouncing when prone --- Entities/AtomGroup.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Entities/AtomGroup.cpp b/Entities/AtomGroup.cpp index 104e535a7..69990ae17 100644 --- a/Entities/AtomGroup.cpp +++ b/Entities/AtomGroup.cpp @@ -1267,9 +1267,13 @@ namespace RTE { owner->GetController()->IsState(MOVE_RIGHT) && pushImpulse.m_X < 0.0F; if (againstTravelDirection) { // Filter some of our impulse out. We're pushing against an obstacle, but we don't want to kick backwards! - // Translate it into to upwards motion to step over what we're walking into instead ;) const float againstIntendedDirectionMultiplier = 0.5F; - pushImpulse.m_Y -= std::abs(pushImpulse.m_X * (1.0F - againstIntendedDirectionMultiplier)); + + if (!owner->GetController()->IsState(BODY_CROUCH) && !owner->GetController()->IsState(MOVE_DOWN)) { + // Translate it into to upwards motion to step over what we're walking into instead ;) + pushImpulse.m_Y -= std::abs(pushImpulse.m_X * (1.0F - againstIntendedDirectionMultiplier)); + } + pushImpulse.m_X *= againstIntendedDirectionMultiplier; } } From cc0e18200b0189ab7bca07b1e991478f89791cae Mon Sep 17 00:00:00 2001 From: Causeless Date: Wed, 27 Dec 2023 19:00:45 +0000 Subject: [PATCH 06/19] Added crouch rotation --- CHANGELOG.md | 2 ++ Entities/AHuman.cpp | 44 ++++++++++++++++++++++++------------- Entities/AHuman.h | 16 +++++++++++++- Entities/LimbPath.cpp | 18 ++++++++------- Lua/LuaBindingsEntities.cpp | 1 + 5 files changed, 57 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b71481f5..c2253019c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - New `AHuman` INI and Lua (R/W) property `MaxWalkPathCrouchShift`, which determines how much the actor will automatically duck down to avoid low ceilings above them. This can be set to 0 to never duck. Defaults to 6. +- New `AHuman` INI and Lua (R/W) property `MaxCrouchRotation`, which determines how much the actor will rotate when ducking to avoid low ceilings above them. This can be set to 0 to never duck. Defaults to a quarter of Pi * 1.25 (roughly 56 degrees). + - New `MOPixel` INI and Lua (R/W) property `Staininess`, which defines how likely a pixel is to stain a surface when it collides with it. Staining a surface changes that surface's `Color` to that of this `MOPixel`, without changing the underlying material. Value can be between 0 and 1. Defaults to 0 (never stain). - New `Activity` INI and Lua (R/W) property `AllowsUserSaving`, which can be used to enable/disable manual user saving/loading. This defaults to true for all `GAScripted` with an `OnSave()` function, but false otherwise. Lua `ActivityMan::SaveGame()` function now forces a save even if `AllowsUserSaving` is disabled. This allows mods and scripted gamemodes to handle saving in their own way (for example, only allowing saving at set points). diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index f509e3ced..02339c8de 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -64,6 +64,7 @@ void AHuman::Clear() m_ProneState = NOTPRONE; m_ProneTimer.Reset(); m_MaxWalkPathCrouchShift = 6.0F; + m_MaxCrouchRotation = c_QuarterPI * 1.25F for (int i = 0; i < MOVEMENTSTATECOUNT; ++i) { m_Paths[FGROUND][i].Reset(); m_Paths[BGROUND][i].Reset(); @@ -86,7 +87,7 @@ void AHuman::Clear() m_BGArmFlailScalar = 0.7F; m_EquipHUDTimer.Reset(); m_WalkAngle.fill(Matrix()); - m_WalkPathYOffset = 0.0F; + m_WalkPathOffset.Reset(); m_ArmSwingRate = 1.0F; m_DeviceArmSwayRate = 0.5F; @@ -210,6 +211,7 @@ int AHuman::Create(const AHuman &reference) { m_BackupBGFootGroup->SetLimbPos(atomGroupToUseAsFootGroupBG->GetLimbPos()); m_MaxWalkPathCrouchShift = reference.m_MaxWalkPathCrouchShift; + m_MaxCrouchRotation = reference.m_MaxCrouchRotation; if (reference.m_StrideSound) { m_StrideSound = dynamic_cast(reference.m_StrideSound->Clone()); } @@ -287,9 +289,8 @@ int AHuman::ReadProperty(const std::string_view &propName, Reader &reader) { m_BackupBGFootGroup = new AtomGroup(*m_pBGFootGroup); m_BackupBGFootGroup->RemoveAllAtoms(); }); - MatchProperty("MaxWalkPathCrouchShift", { - reader >> m_MaxWalkPathCrouchShift; - }); + MatchProperty("MaxWalkPathCrouchShift", { reader >> m_MaxWalkPathCrouchShift; }); + MatchProperty("MaxCrouchRotation", { reader >> m_MaxCrouchRotation; }); MatchProperty("StrideSound", { m_StrideSound = new SoundContainer; reader >> m_StrideSound; @@ -355,6 +356,8 @@ int AHuman::Save(Writer &writer) const writer << m_pBGFootGroup; writer.NewProperty("MaxWalkPathCrouchShift"); writer << m_MaxWalkPathCrouchShift; + writer.NewProperty("MaxCrouchRotation"); + writer << m_MaxCrouchRotation; writer.NewProperty("StrideSound"); writer << m_StrideSound; @@ -1725,9 +1728,14 @@ void AHuman::UpdateWalkAngle(AHuman::Layer whichLayer) { g_SceneMan.CastStrengthRay(hitPosRight, Vector(0.0F, -desiredCrouchHeadRoom), 10.0F, hitPosRight, 0, g_MaterialGrass); float headroom = m_pHead->GetPos().m_Y - std::max(hitPosLeft.m_Y, hitPosRight.m_Y); float adjust = desiredCrouchHeadRoom - headroom; - m_WalkPathYOffset = std::clamp(LERP(0.0F, 1.0F, m_WalkPathYOffset, adjust, 0.3F), 0.0F, m_MaxWalkPathCrouchShift); + float walkPathYOffset = std::clamp(LERP(0.0F, 1.0F, -m_WalkPathOffset.m_Y, adjust, 0.3F), 0.0F, m_MaxWalkPathCrouchShift); + m_WalkPathOffset.m_Y = -walkPathYOffset; + + // Adjust our X offset to try to keep our legs under our centre-of-mass + float predictedPosition = ((m_pHead->GetPos().m_X - m_Pos.m_X) * 0.15F) + m_Vel.m_X; + m_WalkPathOffset.m_X = predictedPosition; } else { - m_WalkPathYOffset = 0.0f; + m_WalkPathOffset.Reset(); } } } @@ -2240,7 +2248,7 @@ void AHuman::PreControllerUpdate() // Reset the stride timer if the path is about to restart. if (m_Paths[FGROUND][WALK].PathEnded() || m_Paths[FGROUND][WALK].PathIsAtStart()) { m_StrideTimer.Reset(); } Vector jointPos = m_Pos + RotateOffset(m_pFGLeg->GetParentOffset()); - m_ArmClimbing[BGROUND] = !m_pFGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[FGROUND], m_Paths[FGROUND][WALK], deltaTime, &restarted, false, Vector(0.0F, m_Paths[FGROUND][WALK].GetLowestY()), Vector(0.0F, -m_WalkPathYOffset)); + m_ArmClimbing[BGROUND] = !m_pFGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[FGROUND], m_Paths[FGROUND][WALK], deltaTime, &restarted, false, Vector(0.0F, m_Paths[FGROUND][WALK].GetLowestY()), m_WalkPathOffset); } else { m_ArmClimbing[BGROUND] = false; } @@ -2249,7 +2257,7 @@ void AHuman::PreControllerUpdate() // Reset the stride timer if the path is about to restart. if (m_Paths[BGROUND][WALK].PathEnded() || m_Paths[BGROUND][WALK].PathIsAtStart()) { m_StrideTimer.Reset(); } Vector jointPos = m_Pos + RotateOffset(m_pBGLeg->GetParentOffset()); - m_ArmClimbing[FGROUND] = !m_pBGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[BGROUND], m_Paths[BGROUND][WALK], deltaTime, &restarted, false, Vector(0.0F, m_Paths[BGROUND][WALK].GetLowestY()), Vector(0.0F, -m_WalkPathYOffset)); + m_ArmClimbing[FGROUND] = !m_pBGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[BGROUND], m_Paths[BGROUND][WALK], deltaTime, &restarted, false, Vector(0.0F, m_Paths[BGROUND][WALK].GetLowestY()), m_WalkPathOffset); } else { if (m_pBGLeg) { m_pBGFootGroup->FlailAsLimb(m_Pos, RotateOffset(m_pBGLeg->GetParentOffset()), m_pBGLeg->GetMaxLength(), m_PrevVel, m_AngularVel, m_pBGLeg->GetMass(), deltaTime); } m_ArmClimbing[FGROUND] = false; @@ -2389,12 +2397,12 @@ void AHuman::PreControllerUpdate() if (m_pFGLeg) { Vector jointPos = m_Pos.GetFloored() + m_pFGLeg->GetParentOffset().GetXFlipped(m_HFlipped); - m_pFGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[FGROUND], m_Paths[FGROUND][STAND], deltaTime, nullptr, !m_pBGLeg, Vector(0.0F, m_Paths[FGROUND][STAND].GetLowestY()), Vector(0.0F, -m_WalkPathYOffset)); + m_pFGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[FGROUND], m_Paths[FGROUND][STAND], deltaTime, nullptr, !m_pBGLeg, Vector(0.0F, m_Paths[FGROUND][STAND].GetLowestY()), m_WalkPathOffset); } if (m_pBGLeg) { Vector jointPos = m_Pos.GetFloored() + m_pBGLeg->GetParentOffset().GetXFlipped(m_HFlipped); - m_pBGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[BGROUND], m_Paths[BGROUND][STAND], deltaTime, nullptr, !m_pFGLeg, Vector(0.0F, m_Paths[FGROUND][STAND].GetLowestY()), Vector(0.0F, -m_WalkPathYOffset)); + m_pBGFootGroup->PushAsLimb(jointPos, m_Vel, m_WalkAngle[BGROUND], m_Paths[BGROUND][STAND], deltaTime, nullptr, !m_pFGLeg, Vector(0.0F, m_Paths[FGROUND][STAND].GetLowestY()), m_WalkPathOffset); } } } @@ -2646,7 +2654,13 @@ void AHuman::Update() } } else { // Upright body posture - float rotDiff = rot - (GetRotAngleTarget(m_MoveState) * (m_AimAngle > 0 ? 1.0F - (m_AimAngle / c_HalfPI) : 1.0F) * GetFlipFactor()); + float rotTarget = (GetRotAngleTarget(m_MoveState) * (m_AimAngle > 0 ? 1.0F - (m_AimAngle / c_HalfPI) : 1.0F) * GetFlipFactor()); + + // Lean forwards when crouching + float crouchAngleAdjust = m_HFlipped ? m_MaxCrouchRotation : -m_MaxCrouchRotation; + rotTarget += LERP(0.0F, m_MaxWalkPathCrouchShift, 0.0F, crouchAngleAdjust, m_WalkPathOffset.m_Y * -1.0F); + + float rotDiff = rot - rotTarget; m_AngularVel = m_AngularVel * (0.98F - 0.06F * (m_Health / m_MaxHealth)) - (rotDiff * 0.5F); } } @@ -2755,10 +2769,10 @@ void AHuman::Draw(BITMAP *pTargetBitmap, const Vector &targetPos, DrawMode mode, } if (mode == g_DrawColor && !onlyPhysical && g_SettingsMan.DrawLimbPathVisualizations()) { - m_Paths[m_HFlipped][WALK].Draw(pTargetBitmap, targetPos, 122); - m_Paths[m_HFlipped][CRAWL].Draw(pTargetBitmap, targetPos, 122); - m_Paths[m_HFlipped][ARMCRAWL].Draw(pTargetBitmap, targetPos, 13); - m_Paths[m_HFlipped][CLIMB].Draw(pTargetBitmap, targetPos, 165); + m_Paths[m_HFlipped][WALK].Draw(pTargetBitmap, targetPos, 122); + m_Paths[m_HFlipped][CRAWL].Draw(pTargetBitmap, targetPos, 122); + m_Paths[m_HFlipped][ARMCRAWL].Draw(pTargetBitmap, targetPos, 13); + m_Paths[m_HFlipped][CLIMB].Draw(pTargetBitmap, targetPos, 165); } } diff --git a/Entities/AHuman.h b/Entities/AHuman.h index 04a70b725..88462f63c 100644 --- a/Entities/AHuman.h +++ b/Entities/AHuman.h @@ -875,6 +875,18 @@ DefaultPieMenuNameGetter("Default Human Pie Menu"); /// The new value for this AHuman's max walkpath adjustment. void SetMaxWalkPathCrouchShift(float newValue) { m_MaxWalkPathCrouchShift = newValue; } + /// + /// Gets this AHuman's max crouch rotation to duck below low ceilings. + /// + /// This AHuman's max crouch rotation adjustment. + float GetMaxCrouchRotation() const { return m_MaxCrouchRotation; } + + /// + /// Sets this AHuman's max crouch rotation to duck below low ceilings. + /// + /// The new value for this AHuman's max crouch rotation adjustment. + void SetMaxCrouchRotation(float newValue) { m_MaxCrouchRotation = newValue; } + /// /// Gets this AHuman's stride sound. Ownership is NOT transferred! /// @@ -949,6 +961,8 @@ DefaultPieMenuNameGetter("Default Human Pie Menu"); Timer m_ProneTimer; // The maximum amount our walkpath can be shifted upwards to crouch and avoid ceilings above us float m_MaxWalkPathCrouchShift; + // The maximum amount we will duck our head down to avoid obstacles above us. + float m_MaxCrouchRotation; // Limb paths for different movement states. // [0] is for the foreground limbs, and [1] is for BG. LimbPath m_Paths[2][MOVEMENTSTATECOUNT]; @@ -972,7 +986,7 @@ DefaultPieMenuNameGetter("Default Human Pie Menu"); float m_BGArmFlailScalar; //!< The rate at which this AHuman's BG Arm follows the the bodily rotation. Set to a negative value for a "counterweight" effect. Timer m_EquipHUDTimer; //!< Timer for showing the name of any newly equipped Device. std::array m_WalkAngle; //!< An array of rot angle targets for different movement states. - float m_WalkPathYOffset; + Vector m_WalkPathOffset; float m_ArmSwingRate; //!< Controls the rate at which this AHuman's Arms follow the movement of its Legs while they're not holding device(s). float m_DeviceArmSwayRate; //!< Controls the rate at which this AHuman's Arms follow the movement of its Legs while they're holding device(s). One-handed devices sway half as much as two-handed ones. Defaults to three quarters of Arm swing rate. diff --git a/Entities/LimbPath.cpp b/Entities/LimbPath.cpp index bdeac6a2a..ca98fddef 100644 --- a/Entities/LimbPath.cpp +++ b/Entities/LimbPath.cpp @@ -12,6 +12,7 @@ // Inclusions of header files #include "LimbPath.h" + #include "PresetMan.h" #include "SLTerrain.h" @@ -189,8 +190,8 @@ int LimbPath::ReadProperty(const std::string_view &propName, Reader &reader) Vector LimbPath::RotatePoint(const Vector &point) const { - Vector offset = (m_RotationOffset + m_PositionOffset).GetXFlipped(m_HFlipped); - return ((point - offset) * m_Rotation) + offset; + Vector offset = (m_RotationOffset).GetXFlipped(m_HFlipped); + return (((point - offset) * m_Rotation) + offset) + m_PositionOffset; } ////////////////////////////////////////////////////////////////////////////////////////// @@ -247,7 +248,7 @@ void LimbPath::Destroy(bool notInherited) Vector LimbPath::GetProgressPos() { - Vector returnVec(m_Start + m_PositionOffset); + Vector returnVec(m_Start); if (IsStaticPoint()) { return m_JointPos + RotatePoint(returnVec); } @@ -274,7 +275,7 @@ Vector LimbPath::GetProgressPos() Vector LimbPath::GetCurrentSegTarget() { - Vector returnVec(m_Start + m_PositionOffset); + Vector returnVec(m_Start); if (IsStaticPoint()) { return m_JointPos + RotatePoint(returnVec); } @@ -566,7 +567,7 @@ bool LimbPath::RestartFree(Vector &limbPos, MOID MOIDToIgnore, int ignoreTeam) if (IsStaticPoint()) { Vector notUsed; - Vector targetPos = m_JointPos + RotatePoint(m_Start + m_PositionOffset); + Vector targetPos = m_JointPos + RotatePoint(m_Start); Vector beginPos = targetPos; // TODO: don't hardcode the beginpos beginPos.m_Y -= 24; @@ -588,14 +589,15 @@ bool LimbPath::RestartFree(Vector &limbPos, MOID MOIDToIgnore, int ignoreTeam) int i = 0; for (; i < m_StartSegCount; ++i) { - result = g_SceneMan.CastObstacleRay(GetProgressPos(), RotatePoint(*m_CurrentSegment + m_PositionOffset), notUsed, limbPos, MOIDToIgnore, ignoreTeam, g_MaterialGrass); + Vector offsetSegment = (*m_CurrentSegment); + result = g_SceneMan.CastObstacleRay(GetProgressPos(), RotatePoint(offsetSegment), notUsed, limbPos, MOIDToIgnore, ignoreTeam, g_MaterialGrass); // If we found an obstacle after the first pixel, report the current segment as the starting one and that there is free space here if (result > 0) { // Set accurate segment progress // TODO: See if this is a good idea, or if we should just set it to 0 and set limbPos to the start of current segment - m_SegProgress = g_SceneMan.ShortestDistance(GetProgressPos(), limbPos).GetMagnitude() / (*m_CurrentSegment + m_PositionOffset).GetMagnitude(); + m_SegProgress = g_SceneMan.ShortestDistance(GetProgressPos(), limbPos).GetMagnitude() / offsetSegment.GetMagnitude(); limbPos = GetProgressPos(); // m_SegProgress = 0; m_Ended = false; @@ -685,7 +687,7 @@ void LimbPath::Draw(BITMAP *pTargetBitmap, const Vector &targetPos, unsigned char color) const { - Vector prevPoint = m_Start + m_PositionOffset; + Vector prevPoint = m_Start; Vector nextPoint = prevPoint; for (std::deque::const_iterator itr = m_Segments.begin(); itr != m_Segments.end(); ++itr) { diff --git a/Lua/LuaBindingsEntities.cpp b/Lua/LuaBindingsEntities.cpp index 2815e350f..7fbeb4028 100644 --- a/Lua/LuaBindingsEntities.cpp +++ b/Lua/LuaBindingsEntities.cpp @@ -461,6 +461,7 @@ namespace RTE { .property("FGFoot", &AHuman::GetFGFoot, &LuaAdaptersPropertyOwnershipSafetyFaker::AHumanSetFGFoot) .property("BGFoot", &AHuman::GetBGFoot, &LuaAdaptersPropertyOwnershipSafetyFaker::AHumanSetBGFoot) .property("MaxWalkPathCrouchShift", &AHuman::GetMaxWalkPathCrouchShift, &AHuman::SetMaxWalkPathCrouchShift) + .property("MaxCrouchRotation", &AHuman::GetMaxCrouchRotation, &AHuman::SetMaxCrouchRotation) .property("StrideSound", &AHuman::GetStrideSound, &LuaAdaptersPropertyOwnershipSafetyFaker::AHumanSetStrideSound) .property("UpperBodyState", &AHuman::GetUpperBodyState, &AHuman::SetUpperBodyState) .property("MovementState", &AHuman::GetMovementState, &AHuman::SetMovementState) From 7014cadeb5ebc0f007d6f04904c452867b2e40dd Mon Sep 17 00:00:00 2001 From: Causeless Date: Wed, 27 Dec 2023 19:01:56 +0000 Subject: [PATCH 07/19] Reset walk crouching when jumping --- Entities/AHuman.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 02339c8de..ca0a858cd 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -1690,6 +1690,8 @@ void AHuman::OnNewMovePath() ////////////////////////////////////////////////////////////////////////////////////////// void AHuman::UpdateWalkAngle(AHuman::Layer whichLayer) { + m_WalkPathOffset.Reset(); + if (m_Controller.IsState(BODY_JUMP)) { m_WalkAngle[whichLayer] = Matrix(c_QuarterPI * GetFlipFactor()); } else { @@ -1734,8 +1736,6 @@ void AHuman::UpdateWalkAngle(AHuman::Layer whichLayer) { // Adjust our X offset to try to keep our legs under our centre-of-mass float predictedPosition = ((m_pHead->GetPos().m_X - m_Pos.m_X) * 0.15F) + m_Vel.m_X; m_WalkPathOffset.m_X = predictedPosition; - } else { - m_WalkPathOffset.Reset(); } } } From ae3a2a379030a3e0c0571672490d505c4ea253c5 Mon Sep 17 00:00:00 2001 From: Causeless Date: Wed, 27 Dec 2023 19:02:41 +0000 Subject: [PATCH 08/19] Whoops, missing ; --- Entities/AHuman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index ca0a858cd..dfdac9d8f 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -64,7 +64,7 @@ void AHuman::Clear() m_ProneState = NOTPRONE; m_ProneTimer.Reset(); m_MaxWalkPathCrouchShift = 6.0F; - m_MaxCrouchRotation = c_QuarterPI * 1.25F + m_MaxCrouchRotation = c_QuarterPI * 1.25F; for (int i = 0; i < MOVEMENTSTATECOUNT; ++i) { m_Paths[FGROUND][i].Reset(); m_Paths[BGROUND][i].Reset(); From c7037ed966901da1c748f9074eb84c3d7c16346e Mon Sep 17 00:00:00 2001 From: Causeless Date: Wed, 27 Dec 2023 19:08:08 +0000 Subject: [PATCH 09/19] Whoops, unfucked crouching --- Entities/AHuman.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index dfdac9d8f..97d4675a9 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -1690,10 +1690,9 @@ void AHuman::OnNewMovePath() ////////////////////////////////////////////////////////////////////////////////////////// void AHuman::UpdateWalkAngle(AHuman::Layer whichLayer) { - m_WalkPathOffset.Reset(); - if (m_Controller.IsState(BODY_JUMP)) { m_WalkAngle[whichLayer] = Matrix(c_QuarterPI * GetFlipFactor()); + m_WalkPathOffset.Reset(); } else { float rayLength = 15.0F; Vector hipPos = m_Pos; @@ -1736,6 +1735,8 @@ void AHuman::UpdateWalkAngle(AHuman::Layer whichLayer) { // Adjust our X offset to try to keep our legs under our centre-of-mass float predictedPosition = ((m_pHead->GetPos().m_X - m_Pos.m_X) * 0.15F) + m_Vel.m_X; m_WalkPathOffset.m_X = predictedPosition; + } else { + m_WalkPathOffset.Reset(); } } } From a7cd3f0fc66e6ca049be8712e6a57d964812a9f3 Mon Sep 17 00:00:00 2001 From: Causeless Date: Wed, 27 Dec 2023 19:34:53 +0000 Subject: [PATCH 10/19] Avoid unnecessary ducking --- Entities/AHuman.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 97d4675a9..2ea859ef5 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -1721,12 +1721,23 @@ void AHuman::UpdateWalkAngle(AHuman::Layer whichLayer) { if (m_pHead) { // Cast a ray above our head to either side to determine whether we need to crouch - float desiredCrouchHeadRoom = std::floor(m_pHead->GetRadius() + 1.5f); + float desiredCrouchHeadRoom = std::floor(m_pHead->GetRadius() + 2.0f); float toSide = std::floor(m_pHead->GetRadius() + 3.0f); - Vector hitPosLeft = (m_pHead->GetPos() + Vector(-toSide, 0.0F)).Floor(); - Vector hitPosRight = (m_pHead->GetPos() + Vector(toSide, 0.0F)).Floor(); - g_SceneMan.CastStrengthRay(hitPosLeft, Vector(0.0F, -desiredCrouchHeadRoom), 10.0F, hitPosLeft, 0, g_MaterialGrass); - g_SceneMan.CastStrengthRay(hitPosRight, Vector(0.0F, -desiredCrouchHeadRoom), 10.0F, hitPosRight, 0, g_MaterialGrass); + Vector hitPosLeftStart = (m_pHead->GetPos() + Vector(-toSide, m_SpriteRadius * 0.5F)).Floor(); + Vector hitPosRightStart = (m_pHead->GetPos() + Vector(toSide, m_SpriteRadius * 0.5F)).Floor(); + Vector hitPosLeft, hitPosRight; + g_SceneMan.CastStrengthRay(hitPosLeftStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 10.0F, hitPosLeft, 0, g_MaterialGrass); + g_SceneMan.CastStrengthRay(hitPosRightStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 10.0F, hitPosRight, 0, g_MaterialGrass); + + // Don't do it if we're already hitting, we're probably standing next to a wall + if (hitPosLeftStart == hitPosLeft) { + hitPosLeft.m_X = 0.0F; + } + + if (hitPosRightStart == hitPosRight) { + hitPosRight.m_X = 0.0F; + } + float headroom = m_pHead->GetPos().m_Y - std::max(hitPosLeft.m_Y, hitPosRight.m_Y); float adjust = desiredCrouchHeadRoom - headroom; float walkPathYOffset = std::clamp(LERP(0.0F, 1.0F, -m_WalkPathOffset.m_Y, adjust, 0.3F), 0.0F, m_MaxWalkPathCrouchShift); From fe59de9330e5f165cb7b9f1a64372e4cea9ef16e Mon Sep 17 00:00:00 2001 From: Causeless Date: Wed, 27 Dec 2023 19:49:10 +0000 Subject: [PATCH 11/19] Reduced stumbling dramatically --- Entities/LimbPath.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Entities/LimbPath.cpp b/Entities/LimbPath.cpp index ca98fddef..59a0ced1b 100644 --- a/Entities/LimbPath.cpp +++ b/Entities/LimbPath.cpp @@ -16,6 +16,8 @@ #include "PresetMan.h" #include "SLTerrain.h" +#include "PrimitiveMan.h" + namespace RTE { ConcreteClassInfo(LimbPath, Entity, 20); @@ -413,13 +415,6 @@ void LimbPath::ReportProgress(const Vector &limbPos) m_SegProgress = distance > segMag ? 0.0F : (1.0F - (distance / segMag)); m_Ended = false; } - - // Make sure we're not stuck on one segment, time that it isn't taking unreasonably long, and restart the path if it seems stuck - if (!m_Ended && m_SegTimer.IsPastSimMS(((segMag * c_MPP) / GetSpeed()) * 1000 * 2)) -// if (!m_Ended && m_SegTimer.IsPastSimMS(333)) - { - Terminate(); - } } } From c031177e28c8a0ae2b0247fad5879e16b25965ca Mon Sep 17 00:00:00 2001 From: Causeless Date: Wed, 27 Dec 2023 23:21:28 +0000 Subject: [PATCH 12/19] Made crouch detection a bit more accurate and nicer --- Entities/AHuman.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 2ea859ef5..9b0964d07 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -1722,23 +1722,23 @@ void AHuman::UpdateWalkAngle(AHuman::Layer whichLayer) { if (m_pHead) { // Cast a ray above our head to either side to determine whether we need to crouch float desiredCrouchHeadRoom = std::floor(m_pHead->GetRadius() + 2.0f); - float toSide = std::floor(m_pHead->GetRadius() + 3.0f); - Vector hitPosLeftStart = (m_pHead->GetPos() + Vector(-toSide, m_SpriteRadius * 0.5F)).Floor(); - Vector hitPosRightStart = (m_pHead->GetPos() + Vector(toSide, m_SpriteRadius * 0.5F)).Floor(); - Vector hitPosLeft, hitPosRight; - g_SceneMan.CastStrengthRay(hitPosLeftStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 10.0F, hitPosLeft, 0, g_MaterialGrass); - g_SceneMan.CastStrengthRay(hitPosRightStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 10.0F, hitPosRight, 0, g_MaterialGrass); + float toPredicted = std::floor(m_Vel.m_X * m_pHead->GetRadius()); // Check where we'll be a second from now + Vector hitPosStart = (m_pHead->GetPos() + Vector(0.0F, m_SpriteRadius * 0.5F)).Floor(); + Vector hitPosPredictedStart = (m_pHead->GetPos() + Vector(toPredicted, m_SpriteRadius * 0.5F)).Floor(); + Vector hitPos, hitPosPredicted; + g_SceneMan.CastStrengthRay(hitPosStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 10.0F, hitPos, 0, g_MaterialGrass); + g_SceneMan.CastStrengthRay(hitPosPredictedStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 10.0F, hitPosPredicted, 0, g_MaterialGrass); - // Don't do it if we're already hitting, we're probably standing next to a wall - if (hitPosLeftStart == hitPosLeft) { - hitPosLeft.m_X = 0.0F; + // Don't do it if we're already hitting, we're probably in a weird spot + if (hitPosStart == hitPos) { + hitPos.m_X = 0.0F; } - if (hitPosRightStart == hitPosRight) { - hitPosRight.m_X = 0.0F; + if (hitPosPredictedStart == hitPosPredicted) { + hitPosPredicted.m_X = 0.0F; } - float headroom = m_pHead->GetPos().m_Y - std::max(hitPosLeft.m_Y, hitPosRight.m_Y); + float headroom = m_pHead->GetPos().m_Y - std::max(hitPos.m_Y, hitPosPredicted.m_Y); float adjust = desiredCrouchHeadRoom - headroom; float walkPathYOffset = std::clamp(LERP(0.0F, 1.0F, -m_WalkPathOffset.m_Y, adjust, 0.3F), 0.0F, m_MaxWalkPathCrouchShift); m_WalkPathOffset.m_Y = -walkPathYOffset; From 6fe74213c82c8229153f0e19fabdac7977f3ab69 Mon Sep 17 00:00:00 2001 From: Causeless Date: Wed, 27 Dec 2023 23:44:53 +0000 Subject: [PATCH 13/19] Update crouching in our own function, and reduce speed when crouching --- Entities/AHuman.cpp | 64 +++++++++++++++++++++---------------- Entities/AHuman.h | 5 +++ Entities/LimbPath.cpp | 13 ++++++-- Entities/LimbPath.h | 22 +++++++++++-- Lua/LuaBindingsEntities.cpp | 1 + 5 files changed, 72 insertions(+), 33 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 9b0964d07..504f9c2b0 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -1692,7 +1692,6 @@ void AHuman::OnNewMovePath() void AHuman::UpdateWalkAngle(AHuman::Layer whichLayer) { if (m_Controller.IsState(BODY_JUMP)) { m_WalkAngle[whichLayer] = Matrix(c_QuarterPI * GetFlipFactor()); - m_WalkPathOffset.Reset(); } else { float rayLength = 15.0F; Vector hipPos = m_Pos; @@ -1718,37 +1717,46 @@ void AHuman::UpdateWalkAngle(AHuman::Layer whichLayer) { Matrix walkAngle; walkAngle.SetDegAngle(terrainRotationDegs); m_WalkAngle[whichLayer] = walkAngle; + } +} - if (m_pHead) { - // Cast a ray above our head to either side to determine whether we need to crouch - float desiredCrouchHeadRoom = std::floor(m_pHead->GetRadius() + 2.0f); - float toPredicted = std::floor(m_Vel.m_X * m_pHead->GetRadius()); // Check where we'll be a second from now - Vector hitPosStart = (m_pHead->GetPos() + Vector(0.0F, m_SpriteRadius * 0.5F)).Floor(); - Vector hitPosPredictedStart = (m_pHead->GetPos() + Vector(toPredicted, m_SpriteRadius * 0.5F)).Floor(); - Vector hitPos, hitPosPredicted; - g_SceneMan.CastStrengthRay(hitPosStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 10.0F, hitPos, 0, g_MaterialGrass); - g_SceneMan.CastStrengthRay(hitPosPredictedStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 10.0F, hitPosPredicted, 0, g_MaterialGrass); - - // Don't do it if we're already hitting, we're probably in a weird spot - if (hitPosStart == hitPos) { - hitPos.m_X = 0.0F; - } +////////////////////////////////////////////////////////////////////////////////////////// - if (hitPosPredictedStart == hitPosPredicted) { - hitPosPredicted.m_X = 0.0F; - } +void AHuman::UpdateCrouching() { + if (!m_Controller.IsState(BODY_JUMP) && m_pHead) { + // Cast a ray above our head to either side to determine whether we need to crouch + float desiredCrouchHeadRoom = std::floor(m_pHead->GetRadius() + 2.0f); + float toPredicted = std::floor(m_Vel.m_X * m_pHead->GetRadius()); // Check where we'll be a second from now + Vector hitPosStart = (m_pHead->GetPos() + Vector(0.0F, m_SpriteRadius * 0.5F)).Floor(); + Vector hitPosPredictedStart = (m_pHead->GetPos() + Vector(toPredicted, m_SpriteRadius * 0.5F)).Floor(); + Vector hitPos, hitPosPredicted; + g_SceneMan.CastStrengthRay(hitPosStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 10.0F, hitPos, 0, g_MaterialGrass); + g_SceneMan.CastStrengthRay(hitPosPredictedStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 10.0F, hitPosPredicted, 0, g_MaterialGrass); - float headroom = m_pHead->GetPos().m_Y - std::max(hitPos.m_Y, hitPosPredicted.m_Y); - float adjust = desiredCrouchHeadRoom - headroom; - float walkPathYOffset = std::clamp(LERP(0.0F, 1.0F, -m_WalkPathOffset.m_Y, adjust, 0.3F), 0.0F, m_MaxWalkPathCrouchShift); - m_WalkPathOffset.m_Y = -walkPathYOffset; + // Don't do it if we're already hitting, we're probably in a weird spot + if (hitPosStart == hitPos) { + hitPos.m_X = 0.0F; + } - // Adjust our X offset to try to keep our legs under our centre-of-mass - float predictedPosition = ((m_pHead->GetPos().m_X - m_Pos.m_X) * 0.15F) + m_Vel.m_X; - m_WalkPathOffset.m_X = predictedPosition; - } else { - m_WalkPathOffset.Reset(); + if (hitPosPredictedStart == hitPosPredicted) { + hitPosPredicted.m_X = 0.0F; } + + float headroom = m_pHead->GetPos().m_Y - std::max(hitPos.m_Y, hitPosPredicted.m_Y); + float adjust = desiredCrouchHeadRoom - headroom; + float walkPathYOffset = std::clamp(LERP(0.0F, 1.0F, -m_WalkPathOffset.m_Y, adjust, 0.3F), 0.0F, m_MaxWalkPathCrouchShift); + m_WalkPathOffset.m_Y = -walkPathYOffset; + + // If crouching, move at third speed + float travelSpeedMultiplier = LERP(0.0F, m_MaxWalkPathCrouchShift, 1.0F, 0.5F, -m_WalkPathOffset.m_Y); + m_Paths[FGROUND][WALK].SetTravelSpeedMultiplier(travelSpeedMultiplier); + m_Paths[BGROUND][WALK].SetTravelSpeedMultiplier(travelSpeedMultiplier); + + // Adjust our X offset to try to keep our legs under our centre-of-mass + float predictedPosition = ((m_pHead->GetPos().m_X - m_Pos.m_X) * 0.15F) + m_Vel.m_X; + m_WalkPathOffset.m_X = predictedPosition; + } else { + m_WalkPathOffset.Reset(); } } @@ -2226,6 +2234,8 @@ void AHuman::PreControllerUpdate() m_StrideFrame = false; + UpdateCrouching(); + if (m_Status == STABLE && !m_LimbPushForcesAndCollisionsDisabled && m_MoveState != NOMOVE) { // This exists to support disabling foot collisions if the limbpath has that flag set. diff --git a/Entities/AHuman.h b/Entities/AHuman.h index 88462f63c..948029cb7 100644 --- a/Entities/AHuman.h +++ b/Entities/AHuman.h @@ -688,6 +688,11 @@ DefaultPieMenuNameGetter("Default Human Pie Menu"); /// The Layer in question. void UpdateWalkAngle(AHuman::Layer whichLayer); + /// + /// Detects overhead ceilings and crouches for them. + /// + void UpdateCrouching(); + /// /// Gets the walk path rotation for the specified Layer. /// diff --git a/Entities/LimbPath.cpp b/Entities/LimbPath.cpp index 59a0ced1b..599185403 100644 --- a/Entities/LimbPath.cpp +++ b/Entities/LimbPath.cpp @@ -37,8 +37,10 @@ void LimbPath::Clear() // m_CurrentSegment = 0; m_FootCollisionsDisabledSegment = -1; m_SegProgress = 0.0; - for (int i = 0; i < SPEEDCOUNT; ++i) + for (int i = 0; i < SPEEDCOUNT; ++i) { m_TravelSpeed[i] = 0.0; + } + m_TravelSpeedMultiplier = 1.0F; m_WhichSpeed = NORMAL; m_PushForce = 0.0; m_JointPos.Reset(); @@ -129,8 +131,10 @@ int LimbPath::Create(const LimbPath &reference) m_FootCollisionsDisabledSegment = reference.m_FootCollisionsDisabledSegment; m_SegProgress = reference.m_SegProgress; - for (int i = 0; i < SPEEDCOUNT; ++i) + for (int i = 0; i < SPEEDCOUNT; ++i) { m_TravelSpeed[i] = reference.m_TravelSpeed[i]; + } + m_TravelSpeedMultiplier = reference.m_TravelSpeedMultiplier; m_PushForce = reference.m_PushForce; m_TimeLeft = reference.m_TimeLeft; m_TotalLength = reference.m_TotalLength; @@ -182,6 +186,7 @@ int LimbPath::ReadProperty(const std::string_view &propName, Reader &reader) reader >> m_TravelSpeed[FAST]; //m_TravelSpeed[FAST] = m_TravelSpeed[FAST] * 2; }); + MatchProperty("TravelSpeedMultiplier", { reader >> m_TravelSpeedMultiplier; }); MatchProperty("PushForce", { reader >> m_PushForce; //m_PushForce = m_PushForce / 1.5; @@ -221,6 +226,8 @@ int LimbPath::Save(Writer &writer) const writer << m_TravelSpeed[NORMAL]; writer.NewProperty("FastTravelSpeed"); writer << m_TravelSpeed[FAST]; + writer.NewProperty("TravelSpeedMultiplier"); + writer << m_TravelSpeedMultiplier; writer.NewProperty("PushForce"); writer << m_PushForce; @@ -309,7 +316,7 @@ Vector LimbPath::GetCurrentVel(const Vector &limbPos) { Vector returnVel; Vector distVect = g_SceneMan.ShortestDistance(limbPos, GetCurrentSegTarget()); - float adjustedTravelSpeed = m_TravelSpeed[m_WhichSpeed] / (1.0F + std::abs(m_JointVel.GetY()) * 0.1F); + float adjustedTravelSpeed = (m_TravelSpeed[m_WhichSpeed] / (1.0F + std::abs(m_JointVel.GetY()) * 0.1F)) * m_TravelSpeedMultiplier; if (IsStaticPoint()) { diff --git a/Entities/LimbPath.h b/Entities/LimbPath.h index 235b2253f..c84e409ab 100644 --- a/Entities/LimbPath.h +++ b/Entities/LimbPath.h @@ -228,7 +228,7 @@ ClassInfoGetters; // Arguments: None. // Return value: A float describing the speed in m/s. - float GetSpeed() const { return m_TravelSpeed[m_WhichSpeed]; } + float GetSpeed() const { return m_TravelSpeed[m_WhichSpeed] * m_TravelSpeedMultiplier; } ////////////////////////////////////////////////////////////////////////////////////////// @@ -240,6 +240,18 @@ ClassInfoGetters; float GetSpeed(int speedPreset) const { if (speedPreset == SLOW || speedPreset == NORMAL || speedPreset == FAST) return m_TravelSpeed[speedPreset]; else return 0; } + /// + /// Sets the current travel speed multiplier. + /// + /// The new travel speed multiplier. + void SetTravelSpeedMultiplier(float newValue) { m_TravelSpeedMultiplier = newValue; } + + /// + /// Gets the current travel speed multiplier. + /// + /// The current travel speed multiplier. + float GetTravelSpeedMultiplier() const { return m_TravelSpeedMultiplier; } + ////////////////////////////////////////////////////////////////////////////////////////// // Method: GetPushForce @@ -286,7 +298,7 @@ ClassInfoGetters; // Arguments: None. // Return value: The total time (ms) this should take to travel along, if unobstructed. - float GetTotalPathTime() const { return ((m_TotalLength * c_MPP) / m_TravelSpeed[m_WhichSpeed]) * 1000; } + float GetTotalPathTime() const { return ((m_TotalLength * c_MPP) / (m_TravelSpeed[m_WhichSpeed] * m_TravelSpeedMultiplier)) * 1000; } ////////////////////////////////////////////////////////////////////////////////////////// @@ -297,7 +309,7 @@ ClassInfoGetters; // Arguments: None. // Return value: The total time (ms) this should take to travel along, if unobstructed. - float GetRegularPathTime() const { return ((m_RegularLength * c_MPP) / m_TravelSpeed[m_WhichSpeed]) * 1000; } + float GetRegularPathTime() const { return ((m_RegularLength * c_MPP) / (m_TravelSpeed[m_WhichSpeed] * m_TravelSpeedMultiplier)) * 1000; } ////////////////////////////////////////////////////////////////////////////////////////// @@ -657,6 +669,10 @@ ClassInfoGetters; // The constant speed that the limb traveling this path has in m/s. float m_TravelSpeed[SPEEDCOUNT]; + + // The current travel speed multiplier + float m_TravelSpeedMultiplier; + // The current speed setting. int m_WhichSpeed; diff --git a/Lua/LuaBindingsEntities.cpp b/Lua/LuaBindingsEntities.cpp index 7fbeb4028..cd9609ff2 100644 --- a/Lua/LuaBindingsEntities.cpp +++ b/Lua/LuaBindingsEntities.cpp @@ -803,6 +803,7 @@ namespace RTE { .property("StartOffset", &LimbPath::GetStartOffset, &LimbPath::SetStartOffset) .property("SegmentCount", &LimbPath::GetSegCount) + .property("TravelSpeedMultiplier", &LimbPath::GetTravelSpeedMultiplier, &LimbPath::SetTravelSpeedMultiplier) .def("GetSegment", &LimbPath::GetSegment); } From 060fe74469f5eab50dda0f8fc85fbc3aab201c8f Mon Sep 17 00:00:00 2001 From: Causeless Date: Thu, 28 Dec 2023 00:08:41 +0000 Subject: [PATCH 14/19] Slightly cleaner code --- Entities/AHuman.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 504f9c2b0..a02f1b463 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -1747,13 +1747,15 @@ void AHuman::UpdateCrouching() { float walkPathYOffset = std::clamp(LERP(0.0F, 1.0F, -m_WalkPathOffset.m_Y, adjust, 0.3F), 0.0F, m_MaxWalkPathCrouchShift); m_WalkPathOffset.m_Y = -walkPathYOffset; - // If crouching, move at third speed - float travelSpeedMultiplier = LERP(0.0F, m_MaxWalkPathCrouchShift, 1.0F, 0.5F, -m_WalkPathOffset.m_Y); + // If crouching, move at reduced speed + const float crouchSpeedMultiplier = 0.5F; + float travelSpeedMultiplier = LERP(0.0F, m_MaxWalkPathCrouchShift, 1.0F, crouchSpeedMultiplier, -m_WalkPathOffset.m_Y); m_Paths[FGROUND][WALK].SetTravelSpeedMultiplier(travelSpeedMultiplier); m_Paths[BGROUND][WALK].SetTravelSpeedMultiplier(travelSpeedMultiplier); // Adjust our X offset to try to keep our legs under our centre-of-mass - float predictedPosition = ((m_pHead->GetPos().m_X - m_Pos.m_X) * 0.15F) + m_Vel.m_X; + const float ratioBetweenBodyAndHeadToAimFor = 0.15F; + float predictedPosition = ((m_pHead->GetPos().m_X - m_Pos.m_X) * ratioBetweenBodyAndHeadToAimFor) + m_Vel.m_X; m_WalkPathOffset.m_X = predictedPosition; } else { m_WalkPathOffset.Reset(); From bf25eca486ad499c74eb2b3ad338383994a94c2f Mon Sep 17 00:00:00 2001 From: Causeless Date: Thu, 28 Dec 2023 00:29:04 +0000 Subject: [PATCH 15/19] Stopped crouching when walking up hillside --- Entities/AHuman.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index a02f1b463..ddedc3bf9 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -30,6 +30,8 @@ #include "GUI.h" #include "AllegroBitmap.h" +#include "PrimitiveMan.h" + #include "tracy/Tracy.hpp" namespace RTE { @@ -1730,15 +1732,15 @@ void AHuman::UpdateCrouching() { Vector hitPosStart = (m_pHead->GetPos() + Vector(0.0F, m_SpriteRadius * 0.5F)).Floor(); Vector hitPosPredictedStart = (m_pHead->GetPos() + Vector(toPredicted, m_SpriteRadius * 0.5F)).Floor(); Vector hitPos, hitPosPredicted; - g_SceneMan.CastStrengthRay(hitPosStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 10.0F, hitPos, 0, g_MaterialGrass); - g_SceneMan.CastStrengthRay(hitPosPredictedStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 10.0F, hitPosPredicted, 0, g_MaterialGrass); + g_SceneMan.CastStrengthRay(hitPosStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 1.0F, hitPos, 0, g_MaterialGrass); + g_SceneMan.CastStrengthRay(hitPosPredictedStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 1.0F, hitPosPredicted, 0, g_MaterialGrass); // Don't do it if we're already hitting, we're probably in a weird spot - if (hitPosStart == hitPos) { + if (hitPosStart.m_Y - hitPos.m_Y <= 2.0F) { hitPos.m_X = 0.0F; } - if (hitPosPredictedStart == hitPosPredicted) { + if (hitPosPredictedStart.m_Y - hitPosPredicted.m_Y <= 2.0F) { hitPosPredicted.m_X = 0.0F; } From ce42a5d1837ad9a0e33b780f58392961b05052b8 Mon Sep 17 00:00:00 2001 From: Causeless Date: Thu, 28 Dec 2023 00:31:49 +0000 Subject: [PATCH 16/19] Actually fixed it this time... --- Entities/AHuman.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index ddedc3bf9..2ad365462 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -1737,11 +1737,11 @@ void AHuman::UpdateCrouching() { // Don't do it if we're already hitting, we're probably in a weird spot if (hitPosStart.m_Y - hitPos.m_Y <= 2.0F) { - hitPos.m_X = 0.0F; + hitPos.m_Y = 0.0F; } if (hitPosPredictedStart.m_Y - hitPosPredicted.m_Y <= 2.0F) { - hitPosPredicted.m_X = 0.0F; + hitPosPredicted.m_Y = 0.0F; } float headroom = m_pHead->GetPos().m_Y - std::max(hitPos.m_Y, hitPosPredicted.m_Y); From 37f9f0a6b409e091308c2f749c0618d5fabe953c Mon Sep 17 00:00:00 2001 From: Causeless Date: Thu, 28 Dec 2023 00:55:15 +0000 Subject: [PATCH 17/19] Duh. --- Entities/GlobalScript.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entities/GlobalScript.cpp b/Entities/GlobalScript.cpp index fe5ed02a1..ce18158f3 100644 --- a/Entities/GlobalScript.cpp +++ b/Entities/GlobalScript.cpp @@ -75,7 +75,7 @@ namespace RTE { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const std::vector>& GlobalScript::GetPieSlicesToAdd() const { - const std::vector> emptyVector; + static const std::vector> emptyVector; if (!m_HasStarted || !m_IsActive || !g_SettingsMan.IsGlobalScriptEnabled(GetModuleAndPresetName())) { return emptyVector; } From b3d1f2bf8cc0702e7a605f27c47b0dcd9c8ec27a Mon Sep 17 00:00:00 2001 From: Causeless Date: Thu, 28 Dec 2023 18:17:17 +0000 Subject: [PATCH 18/19] Lua bindings to get current crouch amount or set an override --- CHANGELOG.md | 4 ++++ Entities/AHuman.cpp | 43 +++++++++++++++++++++---------------- Entities/AHuman.h | 20 +++++++++++++++++ Lua/LuaBindingsEntities.cpp | 2 ++ 4 files changed, 51 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a871c98b6..a72147d37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - New `AHuman` INI and Lua (R/W) property `MaxCrouchRotation`, which determines how much the actor will rotate when ducking to avoid low ceilings above them. This can be set to 0 to never duck. Defaults to a quarter of Pi * 1.25 (roughly 56 degrees). +- New `AHuman` Lua (R/W) property `CrouchAmountOverride`, which enforces that the actor crouch a certain amount, where 0 means fully standing and 1 is fully crouching. This override can be disabled by setting it to -1.0. + +- New `AHuman` Lua (R) property `CrouchAmount`, which returns how much the actor is crouching, where 0 means fully standing and 1 is fully crouching. + - New `MOPixel` INI and Lua (R/W) property `Staininess`, which defines how likely a pixel is to stain a surface when it collides with it. Staining a surface changes that surface's `Color` to that of this `MOPixel`, without changing the underlying material. Value can be between 0 and 1. Defaults to 0 (never stain). - New `Activity` INI and Lua (R/W) property `AllowsUserSaving`, which can be used to enable/disable manual user saving/loading. This defaults to true for all `GAScripted` with an `OnSave()` function, but false otherwise. Lua `ActivityMan::SaveGame()` function now forces a save even if `AllowsUserSaving` is disabled. This allows mods and scripted gamemodes to handle saving in their own way (for example, only allowing saving at set points). diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 2ad365462..0cb47c1ff 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -67,6 +67,7 @@ void AHuman::Clear() m_ProneTimer.Reset(); m_MaxWalkPathCrouchShift = 6.0F; m_MaxCrouchRotation = c_QuarterPI * 1.25F; + m_CrouchAmountOverride = -1.0F; for (int i = 0; i < MOVEMENTSTATECOUNT; ++i) { m_Paths[FGROUND][i].Reset(); m_Paths[BGROUND][i].Reset(); @@ -1726,27 +1727,33 @@ void AHuman::UpdateWalkAngle(AHuman::Layer whichLayer) { void AHuman::UpdateCrouching() { if (!m_Controller.IsState(BODY_JUMP) && m_pHead) { - // Cast a ray above our head to either side to determine whether we need to crouch - float desiredCrouchHeadRoom = std::floor(m_pHead->GetRadius() + 2.0f); - float toPredicted = std::floor(m_Vel.m_X * m_pHead->GetRadius()); // Check where we'll be a second from now - Vector hitPosStart = (m_pHead->GetPos() + Vector(0.0F, m_SpriteRadius * 0.5F)).Floor(); - Vector hitPosPredictedStart = (m_pHead->GetPos() + Vector(toPredicted, m_SpriteRadius * 0.5F)).Floor(); - Vector hitPos, hitPosPredicted; - g_SceneMan.CastStrengthRay(hitPosStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 1.0F, hitPos, 0, g_MaterialGrass); - g_SceneMan.CastStrengthRay(hitPosPredictedStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 1.0F, hitPosPredicted, 0, g_MaterialGrass); - - // Don't do it if we're already hitting, we're probably in a weird spot - if (hitPosStart.m_Y - hitPos.m_Y <= 2.0F) { - hitPos.m_Y = 0.0F; - } + float walkPathYOffset = 0.0F; + if (m_CrouchAmountOverride == -1.0F) { + // Cast a ray above our head to either side to determine whether we need to crouch + float desiredCrouchHeadRoom = std::floor(m_pHead->GetRadius() + 2.0f); + float toPredicted = std::floor(m_Vel.m_X * m_pHead->GetRadius()); // Check where we'll be a second from now + Vector hitPosStart = (m_pHead->GetPos() + Vector(0.0F, m_SpriteRadius * 0.5F)).Floor(); + Vector hitPosPredictedStart = (m_pHead->GetPos() + Vector(toPredicted, m_SpriteRadius * 0.5F)).Floor(); + Vector hitPos, hitPosPredicted; + g_SceneMan.CastStrengthRay(hitPosStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 1.0F, hitPos, 0, g_MaterialGrass); + g_SceneMan.CastStrengthRay(hitPosPredictedStart, Vector(0.0F, -desiredCrouchHeadRoom + m_SpriteRadius * -0.5F), 1.0F, hitPosPredicted, 0, g_MaterialGrass); + + // Don't do it if we're already hitting, we're probably in a weird spot + if (hitPosStart.m_Y - hitPos.m_Y <= 2.0F) { + hitPos.m_Y = 0.0F; + } - if (hitPosPredictedStart.m_Y - hitPosPredicted.m_Y <= 2.0F) { - hitPosPredicted.m_Y = 0.0F; + if (hitPosPredictedStart.m_Y - hitPosPredicted.m_Y <= 2.0F) { + hitPosPredicted.m_Y = 0.0F; + } + + float headroom = m_pHead->GetPos().m_Y - std::max(hitPos.m_Y, hitPosPredicted.m_Y); + float adjust = desiredCrouchHeadRoom - headroom; + walkPathYOffset = std::clamp(LERP(0.0F, 1.0F, -m_WalkPathOffset.m_Y, adjust, 0.3F), 0.0F, m_MaxWalkPathCrouchShift); + } else { + walkPathYOffset = m_CrouchAmountOverride * m_MaxWalkPathCrouchShift; } - float headroom = m_pHead->GetPos().m_Y - std::max(hitPos.m_Y, hitPosPredicted.m_Y); - float adjust = desiredCrouchHeadRoom - headroom; - float walkPathYOffset = std::clamp(LERP(0.0F, 1.0F, -m_WalkPathOffset.m_Y, adjust, 0.3F), 0.0F, m_MaxWalkPathCrouchShift); m_WalkPathOffset.m_Y = -walkPathYOffset; // If crouching, move at reduced speed diff --git a/Entities/AHuman.h b/Entities/AHuman.h index 948029cb7..22306412e 100644 --- a/Entities/AHuman.h +++ b/Entities/AHuman.h @@ -892,6 +892,24 @@ DefaultPieMenuNameGetter("Default Human Pie Menu"); /// The new value for this AHuman's max crouch rotation adjustment. void SetMaxCrouchRotation(float newValue) { m_MaxCrouchRotation = newValue; } + /// + /// Gets this AHuman's current crouch amount. 0.0 == fully standing, 1.0 == fully crouched. + /// + /// This AHuman's current crouch amount. + float GetCrouchAmount() const { return (m_WalkPathOffset.m_Y * -1.0F) / m_MaxWalkPathCrouchShift; } + + /// + /// Gets this AHuman's current crouch amount override. 0.0 == fully standing, 1.0 == fully crouched, -1 == no override. + /// + /// This AHuman's current crouch amount override. + float GetCrouchAmountOverride() const { return m_CrouchAmountOverride; } + + /// + /// Sets this AHuman's current crouch amount override. + /// + /// The new value for this AHuman's current crouch amount override. + void SetCrouchAmountOverride(float newValue) { m_CrouchAmountOverride = newValue; } + /// /// Gets this AHuman's stride sound. Ownership is NOT transferred! /// @@ -968,6 +986,8 @@ DefaultPieMenuNameGetter("Default Human Pie Menu"); float m_MaxWalkPathCrouchShift; // The maximum amount we will duck our head down to avoid obstacles above us. float m_MaxCrouchRotation; + // The script-set forced crouching amount. 0.0 == fully standing, 1.0 == fully crouched, -1 == no override. + float m_CrouchAmountOverride; // Limb paths for different movement states. // [0] is for the foreground limbs, and [1] is for BG. LimbPath m_Paths[2][MOVEMENTSTATECOUNT]; diff --git a/Lua/LuaBindingsEntities.cpp b/Lua/LuaBindingsEntities.cpp index cd9609ff2..5311eaa6d 100644 --- a/Lua/LuaBindingsEntities.cpp +++ b/Lua/LuaBindingsEntities.cpp @@ -462,6 +462,8 @@ namespace RTE { .property("BGFoot", &AHuman::GetBGFoot, &LuaAdaptersPropertyOwnershipSafetyFaker::AHumanSetBGFoot) .property("MaxWalkPathCrouchShift", &AHuman::GetMaxWalkPathCrouchShift, &AHuman::SetMaxWalkPathCrouchShift) .property("MaxCrouchRotation", &AHuman::GetMaxCrouchRotation, &AHuman::SetMaxCrouchRotation) + .property("CrouchAmount", &AHuman::GetCrouchAmount) + .property("CrouchAmountOverride", &AHuman::GetCrouchAmountOverride, &AHuman::SetCrouchAmountOverride) .property("StrideSound", &AHuman::GetStrideSound, &LuaAdaptersPropertyOwnershipSafetyFaker::AHumanSetStrideSound) .property("UpperBodyState", &AHuman::GetUpperBodyState, &AHuman::SetUpperBodyState) .property("MovementState", &AHuman::GetMovementState, &AHuman::SetMovementState) From 8182cf287609ccef5150a5a8d126b2a9b449a181 Mon Sep 17 00:00:00 2001 From: Causeless Date: Fri, 29 Dec 2023 17:27:59 +0000 Subject: [PATCH 19/19] Made Lua set crouch amount lerp instead of snap --- Entities/AHuman.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 0cb47c1ff..8dcb6b7d7 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -1727,7 +1727,7 @@ void AHuman::UpdateWalkAngle(AHuman::Layer whichLayer) { void AHuman::UpdateCrouching() { if (!m_Controller.IsState(BODY_JUMP) && m_pHead) { - float walkPathYOffset = 0.0F; + float desiredWalkPathYOffset = 0.0F; if (m_CrouchAmountOverride == -1.0F) { // Cast a ray above our head to either side to determine whether we need to crouch float desiredCrouchHeadRoom = std::floor(m_pHead->GetRadius() + 2.0f); @@ -1748,13 +1748,13 @@ void AHuman::UpdateCrouching() { } float headroom = m_pHead->GetPos().m_Y - std::max(hitPos.m_Y, hitPosPredicted.m_Y); - float adjust = desiredCrouchHeadRoom - headroom; - walkPathYOffset = std::clamp(LERP(0.0F, 1.0F, -m_WalkPathOffset.m_Y, adjust, 0.3F), 0.0F, m_MaxWalkPathCrouchShift); + desiredWalkPathYOffset = desiredCrouchHeadRoom - headroom; } else { - walkPathYOffset = m_CrouchAmountOverride * m_MaxWalkPathCrouchShift; + desiredWalkPathYOffset = m_CrouchAmountOverride * m_MaxWalkPathCrouchShift; } - m_WalkPathOffset.m_Y = -walkPathYOffset; + float finalWalkPathYOffset = std::clamp(LERP(0.0F, 1.0F, -m_WalkPathOffset.m_Y, desiredWalkPathYOffset, 0.3F), 0.0F, m_MaxWalkPathCrouchShift); + m_WalkPathOffset.m_Y = -finalWalkPathYOffset; // If crouching, move at reduced speed const float crouchSpeedMultiplier = 0.5F;