Index: binaries/data/mods/public/simulation/components/Formation.js =================================================================== --- binaries/data/mods/public/simulation/components/Formation.js +++ binaries/data/mods/public/simulation/components/Formation.js @@ -899,17 +899,22 @@ { let maxRadius = 0; let minSpeed = Infinity; + let minAcceleration = Infinity; for (let ent of this.members) { let cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion); if (cmpUnitMotion) + { minSpeed = Math.min(minSpeed, cmpUnitMotion.GetWalkSpeed()); + minAcceleration = Math.min(minAcceleration, cmpUnitMotion.GetAcceleration()); + } } minSpeed *= this.GetSpeedMultiplier(); let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); cmpUnitMotion.SetSpeedMultiplier(minSpeed / cmpUnitMotion.GetWalkSpeed()); + cmpUnitMotion.SetAcceleration(minAcceleration); }; Formation.prototype.ShapeUpdate = function() Index: binaries/data/mods/public/simulation/components/tests/test_UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_UnitAI.js +++ binaries/data/mods/public/simulation/components/tests/test_UnitAI.js @@ -92,6 +92,7 @@ "MoveToTargetRange": () => true, "GetRunMultiplier": () => 1, "SetSpeedMultiplier": () => {}, + "GetAcceleration": () => 1, "StopMoving": () => {} }); @@ -171,6 +172,7 @@ AddMock(unit, IID_UnitMotion, { "GetWalkSpeed": () => 1, + "GetAcceleration": () => 1, "MoveToFormationOffset": (target, x, z) => {}, "MoveToTargetRange": (target, min, max) => true, "StopMoving": () => {}, @@ -242,6 +244,7 @@ "GetWalkSpeed": () => 1, "StopMoving": () => {}, "SetSpeedMultiplier": () => {}, + "SetAcceleration": (accel) => {}, "MoveToPointRange": () => true, "SetFacePointAfterMove": () => {}, "GetFacePointAfterMove": () => true, @@ -346,6 +349,7 @@ AddMock(unit + i, IID_UnitMotion, { "GetWalkSpeed": () => 1, + "GetAcceleration": () => 1, "MoveToFormationOffset": (target, x, z) => {}, "MoveToTargetRange": (target, min, max) => true, "StopMoving": () => {}, @@ -408,6 +412,7 @@ AddMock(controller, IID_UnitMotion, { "GetWalkSpeed": () => 1, "SetSpeedMultiplier": (speed) => {}, + "SetAcceleration": (accel) => {}, "MoveToPointRange": (x, z, minRange, maxRange) => {}, "StopMoving": () => {}, "SetFacePointAfterMove": () => {}, Index: binaries/data/mods/public/simulation/templates/template_formation.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_formation.xml +++ binaries/data/mods/public/simulation/templates/template_formation.xml @@ -71,5 +71,6 @@ 1.0 100.0 large + 6 Index: binaries/data/mods/public/simulation/templates/template_unit.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_unit.xml +++ binaries/data/mods/public/simulation/templates/template_unit.xml @@ -132,6 +132,7 @@ default 9.0 1.67 + 4 false Index: source/simulation2/components/CCmpUnitMotion.cpp =================================================================== --- source/simulation2/components/CCmpUnitMotion.cpp +++ source/simulation2/components/CCmpUnitMotion.cpp @@ -128,7 +128,7 @@ bool m_FormationController; - fixed m_TemplateWalkSpeed, m_TemplateRunMultiplier; + fixed m_TemplateWalkSpeed, m_TemplateRunMultiplier, m_TemplateAcceleration; pass_class_t m_PassClass; std::string m_PassClassName; @@ -190,8 +190,12 @@ // This caches the resulting speed from m_WalkSpeed * m_SpeedMultiplier for convenience. fixed m_Speed; - // Current mean speed (over the last turn). - fixed m_CurSpeed; + // Current mean speed over the last turn. + fixed m_CurMeanSpeed; + // The highest speed achieved on the last turn. Usually at the end of the last turn. Only zero if did't move last and this turn. + fixed m_CurTopSpeed; + + fixed m_Acceleration; // Currently active paths (storing waypoints in reverse order). // The last item in each path is the point we're currently heading towards. @@ -217,6 +221,9 @@ "" "" "" + "" + "" + "" "" "" ""; @@ -230,12 +237,15 @@ m_WalkSpeed = m_TemplateWalkSpeed = m_Speed = paramNode.GetChild("WalkSpeed").ToFixed(); m_SpeedMultiplier = fixed::FromInt(1); - m_CurSpeed = fixed::Zero(); + m_CurMeanSpeed = fixed::Zero(); + m_CurTopSpeed = fixed::Zero(); m_RunMultiplier = m_TemplateRunMultiplier = fixed::FromInt(1); if (paramNode.GetChild("RunMultiplier").IsOk()) m_RunMultiplier = m_TemplateRunMultiplier = paramNode.GetChild("RunMultiplier").ToFixed(); + m_Acceleration = m_TemplateAcceleration = paramNode.GetChild("Acceleration").ToFixed(); + CmpPtr cmpPathfinder(GetSystemEntity()); if (cmpPathfinder) { @@ -275,7 +285,10 @@ serialize.NumberFixed_Unbounded("speed multiplier", m_SpeedMultiplier); - serialize.NumberFixed_Unbounded("current speed", m_CurSpeed); + serialize.NumberFixed_Unbounded("current mean speed", m_CurMeanSpeed); + serialize.NumberFixed_Unbounded("current top speed", m_CurTopSpeed); + + serialize.NumberFixed_Unbounded("acceleration", m_Acceleration); serialize.Bool("facePointAfterMove", m_FacePointAfterMove); @@ -339,6 +352,7 @@ const CMessageValueModification& msgData = static_cast (msg); if (msgData.component != L"UnitMotion") break; + FALLTHROUGH; } case MT_OwnershipChanged: @@ -398,6 +412,16 @@ return m_RunMultiplier; } + virtual fixed GetAcceleration() const + { + return m_Acceleration; + } + + virtual void SetAcceleration(fixed acceleration) + { + m_Acceleration = acceleration; + } + virtual pass_class_t GetPassabilityClass() const { return m_PassClass; @@ -418,7 +442,7 @@ virtual fixed GetCurrentSpeed() const { - return m_CurSpeed; + return m_CurMeanSpeed; } virtual void SetFacePointAfterMove(bool facePointAfterMove) @@ -616,7 +640,7 @@ * This does not send actually change the position. * @returns true if the move was obstructed. */ - bool PerformMove(fixed dt, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos) const; + bool PerformMove(fixed dt, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos, fixed& speed,entity_angle_t angle) const; /** * Update other components on our speed. @@ -849,7 +873,7 @@ // If we were idle and will still be, we can return. // TODO: this will need to be removed if pushing is implemented. - if (m_CurSpeed == fixed::Zero() && m_MoveRequest.m_Type == MoveRequest::NONE) + if (m_CurTopSpeed == fixed::Zero() && m_CurMeanSpeed == fixed::Zero() && m_MoveRequest.m_Type == MoveRequest::NONE) return; if (PossiblyAtDestination()) @@ -881,7 +905,7 @@ // to it, then throw away our current path and go straight to it bool wentStraight = TryGoingStraightToTarget(initialPos); - bool wasObstructed = PerformMove(dt, m_ShortPath, m_LongPath, pos); + bool wasObstructed = PerformMove(dt, m_ShortPath, m_LongPath, pos, m_CurTopSpeed, cmpPosition->GetRotation().Y); // Update our speed over this turn so that the visual actor shows the correct animation. if (pos == initialPos) @@ -942,7 +966,7 @@ return false; } -bool CCmpUnitMotion::PerformMove(fixed dt, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos) const +bool CCmpUnitMotion::PerformMove(fixed dt, WaypointPath& shortPath, WaypointPath& longPath, CFixedVector2D& pos, fixed& speed, entity_angle_t angle) const { // If there are no waypoint, behave as though we were obstructed and let HandleObstructedMove handle it. if (shortPath.m_Waypoints.empty() && longPath.m_Waypoints.empty()) @@ -969,11 +993,16 @@ fixed maxSpeed = basicSpeed.Multiply(terrainSpeed); - // We want to move (at most) maxSpeed*dt units from pos towards the next waypoint + // We want to move (at most) std::min(maxSpeed * dt, nowSpeed * dt + 1/2 * acceleration * dt^2) units from pos towards the next waypoint fixed timeLeft = dt; fixed zero = fixed::Zero(); + // Take the current rotation as the previous direction, slight TODO, might need to be the previous angle. + fixed sin, cos; + sincos_approx(angle, sin, cos); + CFixedVector2D prevOffset(sin, cos); + while (timeLeft > zero) { // If we ran out of path, we have to stop @@ -988,20 +1017,27 @@ CFixedVector2D offset = target - pos; - // Work out how far we can travel in timeLeft - fixed maxdist = maxSpeed.Multiply(timeLeft); + // Modify the speed depending on the angle difference between previous and next offset. + sincos_approx(atan2_approx(offset.X, offset.Y) / 2 - atan2_approx(prevOffset.X, prevOffset.Y) / 2, sin, cos); + speed = speed.Multiply(cos); + + // Work out how far we can travel in timeLeft. + fixed maxdist = std::min(maxSpeed, speed + m_Acceleration.Multiply(dt) / 2).Multiply(timeLeft); - // If the target is close, we can move there directly + // If the target is close, we can move there directly. fixed offsetLength = offset.Length(); if (offsetLength <= maxdist) { if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass)) { + prevOffset = offset; pos = target; // Spend the rest of the time heading towards the next waypoint timeLeft = (maxdist - offsetLength) / maxSpeed; + speed = std::min(maxSpeed, speed + m_Acceleration.Multiply(dt - timeLeft)); + if (shortPath.m_Waypoints.empty()) longPath.m_Waypoints.pop_back(); else @@ -1021,6 +1057,8 @@ offset.Normalize(maxdist); target = pos + offset; + speed = std::min(maxSpeed, speed + m_Acceleration.Multiply(dt)); + if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass)) pos = target; else @@ -1036,8 +1074,11 @@ { CmpPtr cmpObstruction(GetEntityHandle()); CmpPtr cmpVisual(GetEntityHandle()); + // Not moving this and last turn. + if (speed == fixed::Zero() && m_CurMeanSpeed == fixed::Zero()) + m_CurTopSpeed = fixed::Zero(); // Moved last turn, didn't this turn. - if (speed == fixed::Zero() && m_CurSpeed > fixed::Zero()) + else if (speed == fixed::Zero() && m_CurMeanSpeed > fixed::Zero()) { if (cmpObstruction) cmpObstruction->SetMovingFlag(false); @@ -1045,18 +1086,18 @@ cmpVisual->SelectMovementAnimation("idle", fixed::FromInt(1)); } // Moved this turn, didn't last turn - else if (speed > fixed::Zero() && m_CurSpeed == fixed::Zero()) + else if (speed > fixed::Zero() && m_CurMeanSpeed == fixed::Zero()) { if (cmpObstruction) cmpObstruction->SetMovingFlag(true); if (cmpVisual) - cmpVisual->SelectMovementAnimation(m_Speed > m_WalkSpeed ? "run" : "walk", m_Speed); + cmpVisual->SelectMovementAnimation(m_Speed > m_WalkSpeed ? "run" : "walk", speed); } // Speed change, update the visual actor if necessary. - else if (speed != m_CurSpeed && cmpVisual) - cmpVisual->SelectMovementAnimation(m_Speed > m_WalkSpeed ? "run" : "walk", m_Speed); + else if (speed != m_CurMeanSpeed && cmpVisual) + cmpVisual->SelectMovementAnimation(m_Speed > m_WalkSpeed ? "run" : "walk", speed); - m_CurSpeed = speed; + m_CurMeanSpeed = speed; } bool CCmpUnitMotion::HandleObstructedMove() Index: source/simulation2/components/ICmpUnitMotion.h =================================================================== --- source/simulation2/components/ICmpUnitMotion.h +++ source/simulation2/components/ICmpUnitMotion.h @@ -117,6 +117,17 @@ virtual fixed GetSpeed() const = 0; /** + * Get the current acceleration. + */ + virtual fixed GetAcceleration() const = 0; + + /** + * Set the current acceleration. + * @param acceleration The acceleration. + */ + virtual void SetAcceleration(fixed acceleration) = 0; + + /** * Set whether the unit will turn to face the target point after finishing moving. */ virtual void SetFacePointAfterMove(bool facePointAfterMove) = 0; Index: source/simulation2/components/ICmpUnitMotion.cpp =================================================================== --- source/simulation2/components/ICmpUnitMotion.cpp +++ source/simulation2/components/ICmpUnitMotion.cpp @@ -35,6 +35,8 @@ DEFINE_INTERFACE_METHOD_CONST_0("GetWalkSpeed", fixed, ICmpUnitMotion, GetWalkSpeed) DEFINE_INTERFACE_METHOD_CONST_0("GetRunMultiplier", fixed, ICmpUnitMotion, GetRunMultiplier) DEFINE_INTERFACE_METHOD_1("SetSpeedMultiplier", void, ICmpUnitMotion, SetSpeedMultiplier, fixed) +DEFINE_INTERFACE_METHOD_CONST_0("GetAcceleration", fixed, ICmpUnitMotion, GetAcceleration) +DEFINE_INTERFACE_METHOD_1("SetAcceleration", void, ICmpUnitMotion, SetAcceleration, fixed) DEFINE_INTERFACE_METHOD_CONST_0("GetPassabilityClassName", std::string, ICmpUnitMotion, GetPassabilityClassName) DEFINE_INTERFACE_METHOD_CONST_0("GetUnitClearance", entity_pos_t, ICmpUnitMotion, GetUnitClearance) DEFINE_INTERFACE_METHOD_1("SetFacePointAfterMove", void, ICmpUnitMotion, SetFacePointAfterMove, bool) @@ -112,6 +114,16 @@ return m_Script.Call("GetSpeedMultiplier"); } + virtual fixed GetAcceleration() const + { + return m_Script.Call("GetAcceleration"); + } + + virtual void SetAcceleration(fixed acceleration) + { + m_Script.CallVoid("SetAcceleration", acceleration); + } + virtual void SetFacePointAfterMove(bool facePointAfterMove) { m_Script.CallVoid("SetFacePointAfterMove", facePointAfterMove);