diff --git a/CHANGELOG.md b/CHANGELOG.md index 33370b4c0..cec94b1c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,14 @@ 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 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 `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 7497ba440..8dcb6b7d7 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 { @@ -63,6 +65,9 @@ void AHuman::Clear() m_MoveState = STAND; m_ProneState = NOTPRONE; 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(); @@ -85,6 +90,7 @@ void AHuman::Clear() m_BGArmFlailScalar = 0.7F; m_EquipHUDTimer.Reset(); m_WalkAngle.fill(Matrix()); + m_WalkPathOffset.Reset(); m_ArmSwingRate = 1.0F; m_DeviceArmSwayRate = 0.5F; @@ -207,6 +213,9 @@ int AHuman::Create(const AHuman &reference) { m_BackupBGFootGroup->SetOwner(this); 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()); } m_ArmsState = reference.m_ArmsState; @@ -283,6 +292,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("MaxCrouchRotation", { reader >> m_MaxCrouchRotation; }); MatchProperty("StrideSound", { m_StrideSound = new SoundContainer; reader >> m_StrideSound; @@ -346,6 +357,10 @@ int AHuman::Save(Writer &writer) const writer << m_pFGFootGroup; writer.NewProperty("BGFootGroup"); writer << m_pBGFootGroup; + writer.NewProperty("MaxWalkPathCrouchShift"); + writer << m_MaxWalkPathCrouchShift; + writer.NewProperty("MaxCrouchRotation"); + writer << m_MaxCrouchRotation; writer.NewProperty("StrideSound"); writer << m_StrideSound; @@ -1710,6 +1725,54 @@ void AHuman::UpdateWalkAngle(AHuman::Layer whichLayer) { ////////////////////////////////////////////////////////////////////////////////////////// +void AHuman::UpdateCrouching() { + if (!m_Controller.IsState(BODY_JUMP) && m_pHead) { + 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); + 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; + } + + float headroom = m_pHead->GetPos().m_Y - std::max(hitPos.m_Y, hitPosPredicted.m_Y); + desiredWalkPathYOffset = desiredCrouchHeadRoom - headroom; + } else { + desiredWalkPathYOffset = m_CrouchAmountOverride * m_MaxWalkPathCrouchShift; + } + + 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; + 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 + 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(); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// + void AHuman::PreControllerUpdate() { ZoneScoped; @@ -2182,6 +2245,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. @@ -2194,6 +2259,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 +2280,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()); + 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; } @@ -2221,8 +2289,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()); + 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; @@ -2360,9 +2428,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); + 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) { 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); + 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); + } } } } @@ -2613,7 +2687,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); } } @@ -2722,10 +2802,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 8cfd0c529..22306412e 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. /// @@ -863,6 +868,48 @@ 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 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 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! /// @@ -935,6 +982,12 @@ 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; + // 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]; @@ -958,6 +1011,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. + 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/AtomGroup.cpp b/Entities/AtomGroup.cpp index f8c9e7f42..69990ae17 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()); @@ -1266,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; } } 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/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; } diff --git a/Entities/LimbPath.cpp b/Entities/LimbPath.cpp index 8cfb94eee..599185403 100644 --- a/Entities/LimbPath.cpp +++ b/Entities/LimbPath.cpp @@ -12,9 +12,12 @@ // Inclusions of header files #include "LimbPath.h" + #include "PresetMan.h" #include "SLTerrain.h" +#include "PrimitiveMan.h" + namespace RTE { ConcreteClassInfo(LimbPath, Entity, 20); @@ -34,14 +37,17 @@ 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(); m_JointVel.Reset(); m_Rotation.Reset(); m_RotationOffset.Reset(); + m_PositionOffset.Reset(); m_TimeLeft = 0.0; m_PathTimer.Reset(); m_SegTimer.Reset(); @@ -125,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; @@ -178,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; @@ -188,8 +197,8 @@ int LimbPath::ReadProperty(const std::string_view &propName, Reader &reader) Vector LimbPath::RotatePoint(const Vector &point) const { - Vector offset = m_RotationOffset.GetXFlipped(m_HFlipped); - return ((point - offset) * m_Rotation) + offset; + Vector offset = (m_RotationOffset).GetXFlipped(m_HFlipped); + return (((point - offset) * m_Rotation) + offset) + m_PositionOffset; } ////////////////////////////////////////////////////////////////////////////////////////// @@ -217,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; @@ -305,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()) { @@ -411,13 +422,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(); - } } } @@ -561,9 +565,6 @@ 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()) { @@ -590,14 +591,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), 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).GetMagnitude(); + m_SegProgress = g_SceneMan.ShortestDistance(GetProgressPos(), limbPos).GetMagnitude() / offsetSegment.GetMagnitude(); limbPos = GetProgressPos(); // m_SegProgress = 0; m_Ended = false; @@ -639,8 +641,6 @@ bool LimbPath::RestartFree(Vector &limbPos, MOID MOIDToIgnore, int ignoreTeam) found = true; } } - release_bitmap(g_SceneMan.GetMOIDBitmap()); - g_SceneMan.GetTerrain()->UnlockBitmaps(); if (found) { diff --git a/Entities/LimbPath.h b/Entities/LimbPath.h index 276ceb0af..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; } ////////////////////////////////////////////////////////////////////////////////////////// @@ -494,6 +506,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 @@ -651,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; @@ -666,6 +688,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 diff --git a/Lua/LuaBindingsEntities.cpp b/Lua/LuaBindingsEntities.cpp index adecf9a13..5311eaa6d 100644 --- a/Lua/LuaBindingsEntities.cpp +++ b/Lua/LuaBindingsEntities.cpp @@ -460,6 +460,10 @@ 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("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) @@ -801,6 +805,7 @@ namespace RTE { .property("StartOffset", &LimbPath::GetStartOffset, &LimbPath::SetStartOffset) .property("SegmentCount", &LimbPath::GetSegCount) + .property("TravelSpeedMultiplier", &LimbPath::GetTravelSpeedMultiplier, &LimbPath::SetTravelSpeedMultiplier) .def("GetSegment", &LimbPath::GetSegment); }