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.
This commit is contained in:
parent
063408c252
commit
40cbde1925
@ -5107,7 +5107,7 @@ UnitAI.prototype.SetFormationController = function(ent)
|
|||||||
|
|
||||||
// Set obstruction group, so we can walk through members
|
// Set obstruction group, so we can walk through members
|
||||||
// of our own formation (or ourself if not in formation)
|
// 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 (cmpObstruction)
|
||||||
{
|
{
|
||||||
if (ent == INVALID_ENTITY)
|
if (ent == INVALID_ENTITY)
|
||||||
@ -5116,6 +5116,10 @@ UnitAI.prototype.SetFormationController = function(ent)
|
|||||||
cmpObstruction.SetControlGroup(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 we were removed from a formation, let the FSM switch back to INDIVIDUAL
|
||||||
if (ent == INVALID_ENTITY)
|
if (ent == INVALID_ENTITY)
|
||||||
this.UnitFsm.ProcessMessage(this, { "type": "FormationLeave" });
|
this.UnitFsm.ProcessMessage(this, { "type": "FormationLeave" });
|
||||||
|
@ -296,6 +296,11 @@ UnitMotionFlying.prototype.MoveToTargetRange = function(target, minRange, maxRan
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
UnitMotionFlying.prototype.SetMemberOfFormation = function()
|
||||||
|
{
|
||||||
|
// Ignored.
|
||||||
|
};
|
||||||
|
|
||||||
UnitMotionFlying.prototype.GetWalkSpeed = function()
|
UnitMotionFlying.prototype.GetWalkSpeed = function()
|
||||||
{
|
{
|
||||||
return +this.template.MaxSpeed;
|
return +this.template.MaxSpeed;
|
||||||
|
@ -170,6 +170,7 @@ function TestFormationExiting(mode)
|
|||||||
"GetWalkSpeed": () => 1,
|
"GetWalkSpeed": () => 1,
|
||||||
"MoveToFormationOffset": (target, x, z) => {},
|
"MoveToFormationOffset": (target, x, z) => {},
|
||||||
"MoveToTargetRange": (target, min, max) => true,
|
"MoveToTargetRange": (target, min, max) => true,
|
||||||
|
"SetMemberOfFormation": () => {},
|
||||||
"StopMoving": () => {},
|
"StopMoving": () => {},
|
||||||
"SetFacePointAfterMove": () => {},
|
"SetFacePointAfterMove": () => {},
|
||||||
"GetFacePointAfterMove": () => true,
|
"GetFacePointAfterMove": () => true,
|
||||||
@ -349,6 +350,7 @@ function TestMoveIntoFormationWhileAttacking()
|
|||||||
"GetWalkSpeed": () => 1,
|
"GetWalkSpeed": () => 1,
|
||||||
"MoveToFormationOffset": (target, x, z) => {},
|
"MoveToFormationOffset": (target, x, z) => {},
|
||||||
"MoveToTargetRange": (target, min, max) => true,
|
"MoveToTargetRange": (target, min, max) => true,
|
||||||
|
"SetMemberOfFormation": () => {},
|
||||||
"StopMoving": () => {},
|
"StopMoving": () => {},
|
||||||
"SetFacePointAfterMove": () => {},
|
"SetFacePointAfterMove": () => {},
|
||||||
"GetFacePointAfterMove": () => true,
|
"GetFacePointAfterMove": () => true,
|
||||||
|
@ -4,9 +4,22 @@
|
|||||||
<element name="MaxSameTurnMoves">
|
<element name="MaxSameTurnMoves">
|
||||||
<data type="nonNegativeInteger"/>
|
<data type="nonNegativeInteger"/>
|
||||||
</element>
|
</element>
|
||||||
<element name="PushingRadius">
|
<element name="Pushing">
|
||||||
|
<interleave>
|
||||||
|
<element name="Radius">
|
||||||
<data type="decimal"/>
|
<data type="decimal"/>
|
||||||
</element>
|
</element>
|
||||||
|
<element name="StaticExtension">
|
||||||
|
<data type="decimal"/>
|
||||||
|
</element>
|
||||||
|
<element name="MovingExtension">
|
||||||
|
<data type="decimal"/>
|
||||||
|
</element>
|
||||||
|
<element name="MinimalForce">
|
||||||
|
<data type="decimal"/>
|
||||||
|
</element>
|
||||||
|
</interleave>
|
||||||
|
</element>
|
||||||
<element name="PassabilityClasses">
|
<element name="PassabilityClasses">
|
||||||
<oneOrMore>
|
<oneOrMore>
|
||||||
<element>
|
<element>
|
||||||
|
@ -4,11 +4,30 @@
|
|||||||
<!-- Setting the value to 0 disable this functionality -->
|
<!-- Setting the value to 0 disable this functionality -->
|
||||||
<MaxSameTurnMoves>20</MaxSameTurnMoves>
|
<MaxSameTurnMoves>20</MaxSameTurnMoves>
|
||||||
|
|
||||||
<!-- Multiplier for the distance at which units push each other. -->
|
<Pushing>
|
||||||
|
<!-- Units push each other if they are within 'clearance * radius' meters. -->
|
||||||
<!-- Setting the value to 0 disables unit pushing entirely. -->
|
<!-- Setting the value to 0 disables unit pushing entirely. -->
|
||||||
<!-- Note that values above 2-3 are likely to start behaving weirdly -->
|
<!-- Note that values above 2-3 are likely to start behaving weirdly. -->
|
||||||
<!-- (in particular, formations offsets may need adjusting). -->
|
<!-- You can also tweaks extensions below. -->
|
||||||
<PushingRadius>1.6</PushingRadius>
|
<Radius>1.6</Radius>
|
||||||
|
|
||||||
|
<!-- Actual pushing radius for non-moving units is: -->
|
||||||
|
<!-- Clearance * PushingRadius + StaticPushExtension -->
|
||||||
|
<!-- NB: Once idle units start being pushed, they become moving units, -->
|
||||||
|
<!-- so this should be understood as the maximum unit density. -->
|
||||||
|
<StaticExtension>2</StaticExtension>
|
||||||
|
|
||||||
|
<!-- Actual pushing radius for moving units is: -->
|
||||||
|
<!-- Clearance * PushingRadius + MovingPushExtension -->
|
||||||
|
<!-- This is the factor that has the largest influence on actual sparsity. -->
|
||||||
|
<MovingExtension>2.5</MovingExtension>
|
||||||
|
|
||||||
|
<!-- After the combined pushing force of all neighboring units is calculated, -->
|
||||||
|
<!-- if the value is below this number, treat it as effectively zero. -->
|
||||||
|
<!-- This nullifies very small pushes, and makes things look and behave nicer. -->
|
||||||
|
<!-- NB: This impacts the pushing max distance. -->
|
||||||
|
<MinimalForce>0.2</MinimalForce>
|
||||||
|
</Pushing>
|
||||||
|
|
||||||
<PassabilityClasses>
|
<PassabilityClasses>
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<SortingClasses>Hero Champion Elite Advanced Basic</SortingClasses>
|
<SortingClasses>Hero Champion Elite Advanced Basic</SortingClasses>
|
||||||
<FormationName>Testudo</FormationName>
|
<FormationName>Testudo</FormationName>
|
||||||
<FormationShape>square</FormationShape>
|
<FormationShape>square</FormationShape>
|
||||||
<UnitSeparationWidthMultiplier>0.50</UnitSeparationWidthMultiplier>
|
<UnitSeparationWidthMultiplier>0.5</UnitSeparationWidthMultiplier>
|
||||||
<UnitSeparationDepthMultiplier>0.4</UnitSeparationDepthMultiplier>
|
<UnitSeparationDepthMultiplier>0.4</UnitSeparationDepthMultiplier>
|
||||||
<SortingOrder>fillFromTheSides</SortingOrder>
|
<SortingOrder>fillFromTheSides</SortingOrder>
|
||||||
<WidthDepthRatio>0.8</WidthDepthRatio>
|
<WidthDepthRatio>0.8</WidthDepthRatio>
|
||||||
|
@ -143,7 +143,7 @@ public:
|
|||||||
|
|
||||||
// Template state:
|
// Template state:
|
||||||
|
|
||||||
bool m_FormationController;
|
bool m_IsFormationController;
|
||||||
|
|
||||||
fixed m_TemplateWalkSpeed, m_TemplateRunMultiplier;
|
fixed m_TemplateWalkSpeed, m_TemplateRunMultiplier;
|
||||||
pass_class_t m_PassClass;
|
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) {};
|
MoveRequest(entity_id_t target, CFixedVector2D offset) : m_Type(OFFSET), m_Entity(target), m_Position(offset) {};
|
||||||
} m_MoveRequest;
|
} 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.
|
// If the entity moves, it will do so at m_WalkSpeed * m_SpeedMultiplier.
|
||||||
fixed m_SpeedMultiplier;
|
fixed m_SpeedMultiplier;
|
||||||
// This caches the resulting speed from m_WalkSpeed * m_SpeedMultiplier for convenience.
|
// This caches the resulting speed from m_WalkSpeed * m_SpeedMultiplier for convenience.
|
||||||
@ -253,7 +256,7 @@ public:
|
|||||||
|
|
||||||
virtual void Init(const CParamNode& paramNode)
|
virtual void Init(const CParamNode& paramNode)
|
||||||
{
|
{
|
||||||
m_FormationController = paramNode.GetChild("FormationController").ToBool();
|
m_IsFormationController = paramNode.GetChild("FormationController").ToBool();
|
||||||
|
|
||||||
m_FacePointAfterMove = true;
|
m_FacePointAfterMove = true;
|
||||||
|
|
||||||
@ -307,6 +310,8 @@ public:
|
|||||||
serialize.NumberFixed_Unbounded("target min range", m_MoveRequest.m_MinRange);
|
serialize.NumberFixed_Unbounded("target min range", m_MoveRequest.m_MinRange);
|
||||||
serialize.NumberFixed_Unbounded("target max range", m_MoveRequest.m_MaxRange);
|
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("speed multiplier", m_SpeedMultiplier);
|
||||||
|
|
||||||
serialize.NumberFixed_Unbounded("current speed", m_CurSpeed);
|
serialize.NumberFixed_Unbounded("current speed", m_CurSpeed);
|
||||||
@ -358,7 +363,7 @@ public:
|
|||||||
case MT_Create:
|
case MT_Create:
|
||||||
{
|
{
|
||||||
if (!ENTITY_IS_LOCAL(GetEntityId()))
|
if (!ENTITY_IS_LOCAL(GetEntityId()))
|
||||||
CmpPtr<ICmpUnitMotionManager>(GetSystemEntity())->Register(this, GetEntityId(), m_FormationController);
|
CmpPtr<ICmpUnitMotionManager>(GetSystemEntity())->Register(this, GetEntityId(), m_IsFormationController);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MT_Destroy:
|
case MT_Destroy:
|
||||||
@ -390,7 +395,7 @@ public:
|
|||||||
{
|
{
|
||||||
OnValueModification();
|
OnValueModification();
|
||||||
if (!ENTITY_IS_LOCAL(GetEntityId()))
|
if (!ENTITY_IS_LOCAL(GetEntityId()))
|
||||||
CmpPtr<ICmpUnitMotionManager>(GetSystemEntity())->Register(this, GetEntityId(), m_FormationController);
|
CmpPtr<ICmpUnitMotionManager>(GetSystemEntity())->Register(this, GetEntityId(), m_IsFormationController);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -501,9 +506,16 @@ public:
|
|||||||
return MoveTo(MoveRequest(target, minRange, maxRange));
|
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);
|
virtual bool IsTargetRangeReachable(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
|
||||||
@ -542,19 +554,18 @@ public:
|
|||||||
private:
|
private:
|
||||||
bool IsFormationMember() const
|
bool IsFormationMember() const
|
||||||
{
|
{
|
||||||
// TODO: this really shouldn't be what we are checking for.
|
return m_FormationController != INVALID_ENTITY;
|
||||||
return m_MoveRequest.m_Type == MoveRequest::OFFSET;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsFormationControllerMoving() const
|
bool IsFormationControllerMoving() const
|
||||||
{
|
{
|
||||||
CmpPtr<ICmpUnitMotion> cmpControllerMotion(GetSimContext(), m_MoveRequest.m_Entity);
|
CmpPtr<ICmpUnitMotion> cmpControllerMotion(GetSimContext(), m_FormationController);
|
||||||
return cmpControllerMotion && cmpControllerMotion->IsMoveRequested();
|
return cmpControllerMotion && cmpControllerMotion->IsMoveRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
entity_id_t GetGroup() const
|
entity_id_t GetGroup() const
|
||||||
{
|
{
|
||||||
return IsFormationMember() ? m_MoveRequest.m_Entity : GetEntityId();
|
return IsFormationMember() ? m_FormationController : GetEntityId();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetParticipateInPushing(bool pushing)
|
void SetParticipateInPushing(bool pushing)
|
||||||
@ -975,7 +986,7 @@ void CCmpUnitMotion::PreMove(CCmpUnitMotionManager::MotionState& state)
|
|||||||
if (!m_BlockMovement)
|
if (!m_BlockMovement)
|
||||||
return;
|
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,
|
// 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.
|
// so it does not really reflect whether the unit is actually moving or not.
|
||||||
|
@ -81,9 +81,15 @@ public:
|
|||||||
bool isMoving = false;
|
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).
|
// "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;
|
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.
|
// These vectors are reconstructed on deserialization.
|
||||||
|
|
||||||
|
@ -38,11 +38,6 @@ namespace {
|
|||||||
*/
|
*/
|
||||||
static const int PUSHING_GRID_SIZE = 20;
|
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,
|
* For pushing, treat the clearances as a circle - they're defined as squares,
|
||||||
* so we'll take the circumscribing square (approximately).
|
* 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;
|
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.
|
* Arbitrary constant used to reduce pushing to levels that won't break physics for our turn length.
|
||||||
*/
|
*/
|
||||||
static const int PUSHING_REDUCTION_FACTOR = 2;
|
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<ICmpPosition> cmpPos, CCmpUnitMotion* cmpMotion)
|
CCmpUnitMotionManager::MotionState::MotionState(CmpPtr<ICmpPosition> 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.
|
// TODO: there seems to be no real reason why we could not register a 'system' entity somewhere instead.
|
||||||
CParamNode externalParamNode;
|
CParamNode externalParamNode;
|
||||||
CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder");
|
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())
|
if (radius.IsOk())
|
||||||
{
|
{
|
||||||
m_PushingRadius = radius.ToFixed();
|
m_PushingRadius = radius.ToFixed();
|
||||||
@ -86,7 +86,25 @@ void CCmpUnitMotionManager::Init(const CParamNode&)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
m_PushingRadius = entity_pos_t::FromInt(8) / 5;
|
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)
|
void CCmpUnitMotionManager::Register(CCmpUnitMotion* component, entity_id_t ent, bool formationController)
|
||||||
@ -212,7 +230,7 @@ void CCmpUnitMotionManager::Move(EntityMap<MotionState>& ents, fixed dt)
|
|||||||
it->second.push = CFixedVector2D();
|
it->second.push = CFixedVector2D();
|
||||||
}
|
}
|
||||||
// Only apply pushing if the effect is significant enough.
|
// 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
|
// If there was an attempt at movement, and the pushed movement is in a sufficiently different direction
|
||||||
// (measured by an extremely arbitrary dot product)
|
// (measured by an extremely arbitrary dot product)
|
||||||
@ -255,16 +273,17 @@ void CCmpUnitMotionManager::Push(EntityMap<MotionState>::value_type& a, EntityMa
|
|||||||
// Exception: units in the same control group (i.e. the same formation) never push farther than themselves
|
// 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,
|
// 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).
|
// 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;
|
movingPush = 0;
|
||||||
|
|
||||||
if (movingPush == 1)
|
if (movingPush == 1)
|
||||||
return;
|
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;
|
entity_pos_t maxDist = combinedClearance;
|
||||||
if (movingPush)
|
if (!sameControlGroup)
|
||||||
maxDist += PUSHING_MOVING_INFLUENCE_EXTENSION;
|
maxDist = combinedClearance.Multiply(m_PushingRadius) + (movingPush ? m_MovingPushExtension : m_StaticPushExtension);
|
||||||
|
|
||||||
CFixedVector2D offset = a.second.pos - b.second.pos;
|
CFixedVector2D offset = a.second.pos - b.second.pos;
|
||||||
if (offset.CompareLength(maxDist) > 0)
|
if (offset.CompareLength(maxDist) > 0)
|
||||||
@ -301,11 +320,12 @@ void CCmpUnitMotionManager::Push(EntityMap<MotionState>::value_type& a, EntityMa
|
|||||||
offsetLength = fixed::Zero();
|
offsetLength = fixed::Zero();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// The formula expects 'normal' pushing if the two entities edges are touching.
|
// 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);
|
entity_pos_t distanceFactor = maxDist - combinedClearance;
|
||||||
distanceFactor = Clamp(distanceFactor, entity_pos_t::Zero(), entity_pos_t::FromInt(2));
|
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.
|
// Mark both as needing an update so they actually get moved.
|
||||||
a.second.needUpdate = true;
|
a.second.needUpdate = true;
|
||||||
@ -314,6 +334,6 @@ void CCmpUnitMotionManager::Push(EntityMap<MotionState>::value_type& a, EntityMa
|
|||||||
CFixedVector2D pushingDir = offset.Multiply(distanceFactor);
|
CFixedVector2D pushingDir = offset.Multiply(distanceFactor);
|
||||||
|
|
||||||
// Divide by an arbitrary constant to avoid pushing too much.
|
// Divide by an arbitrary constant to avoid pushing too much.
|
||||||
a.second.push += pushingDir.Multiply(movingPush ? dt : dt / PUSHING_REDUCTION_FACTOR);
|
a.second.push += pushingDir.Multiply(dt / PUSHING_REDUCTION_FACTOR);
|
||||||
b.second.push -= pushingDir.Multiply(movingPush ? dt : dt / PUSHING_REDUCTION_FACTOR);
|
b.second.push -= pushingDir.Multiply(dt / PUSHING_REDUCTION_FACTOR);
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ BEGIN_INTERFACE_WRAPPER(UnitMotion)
|
|||||||
DEFINE_INTERFACE_METHOD("MoveToPointRange", ICmpUnitMotion, MoveToPointRange)
|
DEFINE_INTERFACE_METHOD("MoveToPointRange", ICmpUnitMotion, MoveToPointRange)
|
||||||
DEFINE_INTERFACE_METHOD("MoveToTargetRange", ICmpUnitMotion, MoveToTargetRange)
|
DEFINE_INTERFACE_METHOD("MoveToTargetRange", ICmpUnitMotion, MoveToTargetRange)
|
||||||
DEFINE_INTERFACE_METHOD("MoveToFormationOffset", ICmpUnitMotion, MoveToFormationOffset)
|
DEFINE_INTERFACE_METHOD("MoveToFormationOffset", ICmpUnitMotion, MoveToFormationOffset)
|
||||||
|
DEFINE_INTERFACE_METHOD("SetMemberOfFormation", ICmpUnitMotion, SetMemberOfFormation)
|
||||||
DEFINE_INTERFACE_METHOD("IsTargetRangeReachable", ICmpUnitMotion, IsTargetRangeReachable)
|
DEFINE_INTERFACE_METHOD("IsTargetRangeReachable", ICmpUnitMotion, IsTargetRangeReachable)
|
||||||
DEFINE_INTERFACE_METHOD("FaceTowardsPoint", ICmpUnitMotion, FaceTowardsPoint)
|
DEFINE_INTERFACE_METHOD("FaceTowardsPoint", ICmpUnitMotion, FaceTowardsPoint)
|
||||||
DEFINE_INTERFACE_METHOD("StopMoving", ICmpUnitMotion, StopMoving)
|
DEFINE_INTERFACE_METHOD("StopMoving", ICmpUnitMotion, StopMoving)
|
||||||
@ -63,6 +64,11 @@ public:
|
|||||||
m_Script.CallVoid("MoveToFormationOffset", target, x, z);
|
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)
|
virtual bool IsTargetRangeReachable(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
|
||||||
{
|
{
|
||||||
return m_Script.Call<bool>("IsTargetRangeReachable", target, minRange, maxRange);
|
return m_Script.Call<bool>("IsTargetRangeReachable", target, minRange, maxRange);
|
||||||
|
@ -56,9 +56,16 @@ public:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Join a formation, and move towards a given offset relative to the formation controller entity.
|
* 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.
|
* Check if the target is reachable.
|
||||||
|
Loading…
Reference in New Issue
Block a user