From 40cbde1925dc6b48e4100da1ce32aaa4eb8d0129 Mon Sep 17 00:00:00 2001 From: wraitii Date: Sun, 6 Jun 2021 15:25:52 +0000 Subject: [PATCH] Further Pushing tweaks: more customisable, longer ranges. This overall decreases the deathball effect from units walking to each other a bit. - Fix formations - this cleans up a UnitMotion hack for formations, making it possible to increase pushing ranges without breaking closely knit formations like testudo. - Make MINIMAL_PUSHING and the MOVE_EXTENSION configurable, and add a STATIC_EXTENSION as well. - Increase the pushing range significantly, making units sparser. Differential Revision: https://code.wildfiregames.com/D4098 This was SVN commit r25708. --- .../public/simulation/components/UnitAI.js | 6 +- .../simulation/components/UnitMotionFlying.js | 5 ++ .../components/tests/test_UnitAI.js | 2 + .../public/simulation/data/pathfinder.rng | 17 ++++- .../public/simulation/data/pathfinder.xml | 29 ++++++-- .../templates/special/formations/testudo.xml | 2 +- .../simulation2/components/CCmpUnitMotion.h | 33 ++++++---- .../components/CCmpUnitMotionManager.h | 8 ++- .../components/CCmpUnitMotion_System.cpp | 66 ++++++++++++------- .../simulation2/components/ICmpUnitMotion.cpp | 6 ++ .../simulation2/components/ICmpUnitMotion.h | 11 +++- 11 files changed, 139 insertions(+), 46 deletions(-) diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js index e6e6d65c57..a31bf88ee2 100644 --- a/binaries/data/mods/public/simulation/components/UnitAI.js +++ b/binaries/data/mods/public/simulation/components/UnitAI.js @@ -5107,7 +5107,7 @@ UnitAI.prototype.SetFormationController = function(ent) // Set obstruction group, so we can walk through members // of our own formation (or ourself if not in formation) - var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); + const cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); if (cmpObstruction) { if (ent == INVALID_ENTITY) @@ -5116,6 +5116,10 @@ UnitAI.prototype.SetFormationController = function(ent) cmpObstruction.SetControlGroup(ent); } + const cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); + if (cmpUnitMotion) + cmpUnitMotion.SetMemberOfFormation(ent); + // If we were removed from a formation, let the FSM switch back to INDIVIDUAL if (ent == INVALID_ENTITY) this.UnitFsm.ProcessMessage(this, { "type": "FormationLeave" }); diff --git a/binaries/data/mods/public/simulation/components/UnitMotionFlying.js b/binaries/data/mods/public/simulation/components/UnitMotionFlying.js index fa2eb10fcd..277a445f84 100644 --- a/binaries/data/mods/public/simulation/components/UnitMotionFlying.js +++ b/binaries/data/mods/public/simulation/components/UnitMotionFlying.js @@ -296,6 +296,11 @@ UnitMotionFlying.prototype.MoveToTargetRange = function(target, minRange, maxRan return true; }; +UnitMotionFlying.prototype.SetMemberOfFormation = function() +{ + // Ignored. +}; + UnitMotionFlying.prototype.GetWalkSpeed = function() { return +this.template.MaxSpeed; diff --git a/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js b/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js index b587a9d8d5..38480a92ea 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js +++ b/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js @@ -170,6 +170,7 @@ function TestFormationExiting(mode) "GetWalkSpeed": () => 1, "MoveToFormationOffset": (target, x, z) => {}, "MoveToTargetRange": (target, min, max) => true, + "SetMemberOfFormation": () => {}, "StopMoving": () => {}, "SetFacePointAfterMove": () => {}, "GetFacePointAfterMove": () => true, @@ -349,6 +350,7 @@ function TestMoveIntoFormationWhileAttacking() "GetWalkSpeed": () => 1, "MoveToFormationOffset": (target, x, z) => {}, "MoveToTargetRange": (target, min, max) => true, + "SetMemberOfFormation": () => {}, "StopMoving": () => {}, "SetFacePointAfterMove": () => {}, "GetFacePointAfterMove": () => true, diff --git a/binaries/data/mods/public/simulation/data/pathfinder.rng b/binaries/data/mods/public/simulation/data/pathfinder.rng index e3bdd2d457..c732022b0a 100644 --- a/binaries/data/mods/public/simulation/data/pathfinder.rng +++ b/binaries/data/mods/public/simulation/data/pathfinder.rng @@ -4,8 +4,21 @@ - - + + + + + + + + + + + + + + + diff --git a/binaries/data/mods/public/simulation/data/pathfinder.xml b/binaries/data/mods/public/simulation/data/pathfinder.xml index c8c1a4c3d9..bb5c3d1146 100644 --- a/binaries/data/mods/public/simulation/data/pathfinder.xml +++ b/binaries/data/mods/public/simulation/data/pathfinder.xml @@ -4,11 +4,30 @@ 20 - - - - - 1.6 + + + + + + 1.6 + + + + + + 2 + + + + + 2.5 + + + + + + 0.2 + diff --git a/binaries/data/mods/public/simulation/templates/special/formations/testudo.xml b/binaries/data/mods/public/simulation/templates/special/formations/testudo.xml index 9f606c007a..4078bb0c6d 100644 --- a/binaries/data/mods/public/simulation/templates/special/formations/testudo.xml +++ b/binaries/data/mods/public/simulation/templates/special/formations/testudo.xml @@ -7,7 +7,7 @@ Hero Champion Elite Advanced Basic Testudo square - 0.50 + 0.5 0.4 fillFromTheSides 0.8 diff --git a/source/simulation2/components/CCmpUnitMotion.h b/source/simulation2/components/CCmpUnitMotion.h index 072064fd1d..6828004d92 100644 --- a/source/simulation2/components/CCmpUnitMotion.h +++ b/source/simulation2/components/CCmpUnitMotion.h @@ -143,7 +143,7 @@ public: // Template state: - bool m_FormationController; + bool m_IsFormationController; fixed m_TemplateWalkSpeed, m_TemplateRunMultiplier; pass_class_t m_PassClass; @@ -209,6 +209,9 @@ public: MoveRequest(entity_id_t target, CFixedVector2D offset) : m_Type(OFFSET), m_Entity(target), m_Position(offset) {}; } m_MoveRequest; + // If this is not INVALID_ENTITY, the unit is a formation member. + entity_id_t m_FormationController = INVALID_ENTITY; + // If the entity moves, it will do so at m_WalkSpeed * m_SpeedMultiplier. fixed m_SpeedMultiplier; // This caches the resulting speed from m_WalkSpeed * m_SpeedMultiplier for convenience. @@ -253,7 +256,7 @@ public: virtual void Init(const CParamNode& paramNode) { - m_FormationController = paramNode.GetChild("FormationController").ToBool(); + m_IsFormationController = paramNode.GetChild("FormationController").ToBool(); m_FacePointAfterMove = true; @@ -307,6 +310,8 @@ public: serialize.NumberFixed_Unbounded("target min range", m_MoveRequest.m_MinRange); serialize.NumberFixed_Unbounded("target max range", m_MoveRequest.m_MaxRange); + serialize.NumberU32_Unbounded("formation controller", m_FormationController); + serialize.NumberFixed_Unbounded("speed multiplier", m_SpeedMultiplier); serialize.NumberFixed_Unbounded("current speed", m_CurSpeed); @@ -358,7 +363,7 @@ public: case MT_Create: { if (!ENTITY_IS_LOCAL(GetEntityId())) - CmpPtr(GetSystemEntity())->Register(this, GetEntityId(), m_FormationController); + CmpPtr(GetSystemEntity())->Register(this, GetEntityId(), m_IsFormationController); break; } case MT_Destroy: @@ -390,7 +395,7 @@ public: { OnValueModification(); if (!ENTITY_IS_LOCAL(GetEntityId())) - CmpPtr(GetSystemEntity())->Register(this, GetEntityId(), m_FormationController); + CmpPtr(GetSystemEntity())->Register(this, GetEntityId(), m_IsFormationController); break; } } @@ -501,9 +506,16 @@ public: return MoveTo(MoveRequest(target, minRange, maxRange)); } - virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z) + virtual void MoveToFormationOffset(entity_id_t controller, entity_pos_t x, entity_pos_t z) { - MoveTo(MoveRequest(target, CFixedVector2D(x, z))); + SetMemberOfFormation(controller); + // Pass the controller to the move request anyways. + MoveTo(MoveRequest(controller, CFixedVector2D(x, z))); + } + + virtual void SetMemberOfFormation(entity_id_t controller) + { + m_FormationController = controller; } virtual bool IsTargetRangeReachable(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange); @@ -542,19 +554,18 @@ public: private: bool IsFormationMember() const { - // TODO: this really shouldn't be what we are checking for. - return m_MoveRequest.m_Type == MoveRequest::OFFSET; + return m_FormationController != INVALID_ENTITY; } bool IsFormationControllerMoving() const { - CmpPtr cmpControllerMotion(GetSimContext(), m_MoveRequest.m_Entity); + CmpPtr cmpControllerMotion(GetSimContext(), m_FormationController); return cmpControllerMotion && cmpControllerMotion->IsMoveRequested(); } entity_id_t GetGroup() const { - return IsFormationMember() ? m_MoveRequest.m_Entity : GetEntityId(); + return IsFormationMember() ? m_FormationController : GetEntityId(); } void SetParticipateInPushing(bool pushing) @@ -975,7 +986,7 @@ void CCmpUnitMotion::PreMove(CCmpUnitMotionManager::MotionState& state) if (!m_BlockMovement) return; - state.controlGroup = IsFormationMember() ? m_MoveRequest.m_Entity : INVALID_ENTITY; + state.controlGroup = IsFormationMember() ? m_FormationController : INVALID_ENTITY; // Update moving flag, this is an internal construct used for pushing, // so it does not really reflect whether the unit is actually moving or not. diff --git a/source/simulation2/components/CCmpUnitMotionManager.h b/source/simulation2/components/CCmpUnitMotionManager.h index 98fb51f65a..8641e66cd6 100644 --- a/source/simulation2/components/CCmpUnitMotionManager.h +++ b/source/simulation2/components/CCmpUnitMotionManager.h @@ -81,9 +81,15 @@ public: bool isMoving = false; }; - // Multiplier for the pushing radius. Pre-multiplied by the circle-square correction factor. // "Template" state, not serialized (cannot be changed mid-game). + + // Multiplier for the pushing radius. Pre-multiplied by the circle-square correction factor. entity_pos_t m_PushingRadius; + // Additive modifiers to the pushing radius for moving units and idle units respectively. + entity_pos_t m_MovingPushExtension; + entity_pos_t m_StaticPushExtension; + // Pushing forces below this value are ignored - this prevents units moving forever by very small increments. + entity_pos_t m_MinimalPushing; // These vectors are reconstructed on deserialization. diff --git a/source/simulation2/components/CCmpUnitMotion_System.cpp b/source/simulation2/components/CCmpUnitMotion_System.cpp index f0fe0fac8a..e12698c402 100644 --- a/source/simulation2/components/CCmpUnitMotion_System.cpp +++ b/source/simulation2/components/CCmpUnitMotion_System.cpp @@ -38,11 +38,6 @@ namespace { */ static const int PUSHING_GRID_SIZE = 20; - /** - * Pushing is ignored if the combined push force has lower magnitude than this. - */ - static const entity_pos_t MINIMAL_PUSHING = entity_pos_t::FromInt(3) / 10; - /** * For pushing, treat the clearances as a circle - they're defined as squares, * so we'll take the circumscribing square (approximately). @@ -50,15 +45,15 @@ namespace { */ static const entity_pos_t PUSHING_CORRECTION = entity_pos_t::FromInt(5) / 7; - /** - * When moving, units exert a pushing influence at a greater distance. - */ - static const entity_pos_t PUSHING_MOVING_INFLUENCE_EXTENSION = entity_pos_t::FromInt(1); - /** * Arbitrary constant used to reduce pushing to levels that won't break physics for our turn length. */ static const int PUSHING_REDUCTION_FACTOR = 2; + + /** + * Maximum distance multiplier. + */ + static const entity_pos_t MAX_DISTANCE_FACTOR = entity_pos_t::FromInt(2); } CCmpUnitMotionManager::MotionState::MotionState(CmpPtr cmpPos, CCmpUnitMotion* cmpMotion) @@ -73,7 +68,12 @@ void CCmpUnitMotionManager::Init(const CParamNode&) // TODO: there seems to be no real reason why we could not register a 'system' entity somewhere instead. CParamNode externalParamNode; CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); - const CParamNode radius = externalParamNode.GetChild("Pathfinder").GetChild("PushingRadius"); + CParamNode pushingNode = externalParamNode.GetChild("Pathfinder").GetChild("Pushing"); + + // NB: all values are given sane default, but they are not treated as optional in the schema, + // so the XML file is the reference. + + const CParamNode radius = pushingNode.GetChild("Radius"); if (radius.IsOk()) { m_PushingRadius = radius.ToFixed(); @@ -86,7 +86,25 @@ void CCmpUnitMotionManager::Init(const CParamNode&) } else m_PushingRadius = entity_pos_t::FromInt(8) / 5; - m_PushingRadius = m_PushingRadius.Multiply(PUSHING_CORRECTION); + + const CParamNode minForce = pushingNode.GetChild("MinimalForce"); + if (minForce.IsOk()) + m_MinimalPushing = minForce.ToFixed(); + else + m_MinimalPushing = entity_pos_t::FromInt(2) / 10; + + const CParamNode movingExt = pushingNode.GetChild("MovingExtension"); + const CParamNode staticExt = pushingNode.GetChild("StaticExtension"); + if (movingExt.IsOk() && staticExt.IsOk()) + { + m_MovingPushExtension = movingExt.ToFixed(); + m_StaticPushExtension = staticExt.ToFixed(); + } + else + { + m_MovingPushExtension = entity_pos_t::FromInt(5) / 2; + m_StaticPushExtension = entity_pos_t::FromInt(2); + } } void CCmpUnitMotionManager::Register(CCmpUnitMotion* component, entity_id_t ent, bool formationController) @@ -212,7 +230,7 @@ void CCmpUnitMotionManager::Move(EntityMap& ents, fixed dt) it->second.push = CFixedVector2D(); } // Only apply pushing if the effect is significant enough. - if (it->second.push.CompareLength(MINIMAL_PUSHING) > 0) + if (it->second.push.CompareLength(m_MinimalPushing) > 0) { // If there was an attempt at movement, and the pushed movement is in a sufficiently different direction // (measured by an extremely arbitrary dot product) @@ -255,16 +273,17 @@ void CCmpUnitMotionManager::Push(EntityMap::value_type& a, EntityMa // Exception: units in the same control group (i.e. the same formation) never push farther than themselves // and are also allowed to push idle units (obstructions are ignored within formations, // so pushing idle units makes one member crossing the formation look better). - if (a.second.controlGroup != INVALID_ENTITY && a.second.controlGroup == b.second.controlGroup) + bool sameControlGroup = a.second.controlGroup != INVALID_ENTITY && a.second.controlGroup == b.second.controlGroup; + if (sameControlGroup) movingPush = 0; if (movingPush == 1) return; - entity_pos_t combinedClearance = (a.second.cmpUnitMotion->m_Clearance + b.second.cmpUnitMotion->m_Clearance).Multiply(m_PushingRadius); + entity_pos_t combinedClearance = (a.second.cmpUnitMotion->m_Clearance + b.second.cmpUnitMotion->m_Clearance).Multiply(PUSHING_CORRECTION); entity_pos_t maxDist = combinedClearance; - if (movingPush) - maxDist += PUSHING_MOVING_INFLUENCE_EXTENSION; + if (!sameControlGroup) + maxDist = combinedClearance.Multiply(m_PushingRadius) + (movingPush ? m_MovingPushExtension : m_StaticPushExtension); CFixedVector2D offset = a.second.pos - b.second.pos; if (offset.CompareLength(maxDist) > 0) @@ -301,11 +320,12 @@ void CCmpUnitMotionManager::Push(EntityMap::value_type& a, EntityMa offsetLength = fixed::Zero(); } - - // The formula expects 'normal' pushing if the two entities edges are touching. - entity_pos_t distanceFactor = movingPush ? (maxDist - offsetLength) / (maxDist - combinedClearance) : combinedClearance - offsetLength + entity_pos_t::FromInt(1); - distanceFactor = Clamp(distanceFactor, entity_pos_t::Zero(), entity_pos_t::FromInt(2)); + entity_pos_t distanceFactor = maxDist - combinedClearance; + if (distanceFactor <= entity_pos_t::Zero()) + distanceFactor = MAX_DISTANCE_FACTOR; + else + distanceFactor = Clamp((maxDist - offsetLength) / distanceFactor, entity_pos_t::Zero(), MAX_DISTANCE_FACTOR); // Mark both as needing an update so they actually get moved. a.second.needUpdate = true; @@ -314,6 +334,6 @@ void CCmpUnitMotionManager::Push(EntityMap::value_type& a, EntityMa CFixedVector2D pushingDir = offset.Multiply(distanceFactor); // Divide by an arbitrary constant to avoid pushing too much. - a.second.push += pushingDir.Multiply(movingPush ? dt : dt / PUSHING_REDUCTION_FACTOR); - b.second.push -= pushingDir.Multiply(movingPush ? dt : dt / PUSHING_REDUCTION_FACTOR); + a.second.push += pushingDir.Multiply(dt / PUSHING_REDUCTION_FACTOR); + b.second.push -= pushingDir.Multiply(dt / PUSHING_REDUCTION_FACTOR); } diff --git a/source/simulation2/components/ICmpUnitMotion.cpp b/source/simulation2/components/ICmpUnitMotion.cpp index 09068e7107..589d678808 100644 --- a/source/simulation2/components/ICmpUnitMotion.cpp +++ b/source/simulation2/components/ICmpUnitMotion.cpp @@ -26,6 +26,7 @@ BEGIN_INTERFACE_WRAPPER(UnitMotion) DEFINE_INTERFACE_METHOD("MoveToPointRange", ICmpUnitMotion, MoveToPointRange) DEFINE_INTERFACE_METHOD("MoveToTargetRange", ICmpUnitMotion, MoveToTargetRange) DEFINE_INTERFACE_METHOD("MoveToFormationOffset", ICmpUnitMotion, MoveToFormationOffset) +DEFINE_INTERFACE_METHOD("SetMemberOfFormation", ICmpUnitMotion, SetMemberOfFormation) DEFINE_INTERFACE_METHOD("IsTargetRangeReachable", ICmpUnitMotion, IsTargetRangeReachable) DEFINE_INTERFACE_METHOD("FaceTowardsPoint", ICmpUnitMotion, FaceTowardsPoint) DEFINE_INTERFACE_METHOD("StopMoving", ICmpUnitMotion, StopMoving) @@ -63,6 +64,11 @@ public: m_Script.CallVoid("MoveToFormationOffset", target, x, z); } + virtual void SetMemberOfFormation(entity_id_t controller) + { + m_Script.CallVoid("SetMemberOfFormation", controller); + } + virtual bool IsTargetRangeReachable(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) { return m_Script.Call("IsTargetRangeReachable", target, minRange, maxRange); diff --git a/source/simulation2/components/ICmpUnitMotion.h b/source/simulation2/components/ICmpUnitMotion.h index caf8c5a78a..e85f0969a8 100644 --- a/source/simulation2/components/ICmpUnitMotion.h +++ b/source/simulation2/components/ICmpUnitMotion.h @@ -56,9 +56,16 @@ public: /** * Join a formation, and move towards a given offset relative to the formation controller entity. - * Continues following the formation until given a different command. + * The unit will remain 'in formation' fromthe perspective of UnitMotion + * until SetMemberOfFormation(INVALID_ENTITY) is passed. */ - virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z) = 0; + virtual void MoveToFormationOffset(entity_id_t controller, entity_pos_t x, entity_pos_t z) = 0; + + /** + * Set/unset the unit as a formation member. + * @param controller - if INVALID_ENTITY, the unit is no longer a formation member. Otherwise it is and this is the controller. + */ + virtual void SetMemberOfFormation(entity_id_t controller) = 0; /** * Check if the target is reachable.