1 /* Copyright (C) 2022 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
18 #include "precompiled.h"
20 #include "simulation2/system/Component.h"
21 #include "ICmpObstruction.h"
23 #include "simulation2/MessageTypes.h"
24 #include "simulation2/components/ICmpObstructionManager.h"
25 #include "simulation2/components/ICmpTerrain.h"
26 #include "simulation2/components/ICmpUnitMotion.h"
27 #include "simulation2/components/ICmpWaterManager.h"
28 #include "simulation2/serialization/SerializedTypes.h"
30 #include "ps/CLogger.h"
33 struct SerializeHelper
<ICmpObstructionManager::tag_t
>
36 void operator()(S
& serialize
, const char* UNUSED(name
), Serialize::qualify
<S
, ICmpObstructionManager::tag_t
> value
)
38 serialize
.NumberU32_Unbounded("tag", value
.n
);
43 * Obstruction implementation. This keeps the ICmpPathfinder's model of the world updated when the
44 * entities move and die, with shapes derived from ICmpFootprint.
46 class CCmpObstruction final
: public ICmpObstruction
49 static void ClassInit(CComponentManager
& componentManager
)
51 componentManager
.SubscribeToMessageType(MT_PositionChanged
);
52 componentManager
.SubscribeToMessageType(MT_Destroy
);
55 DEFAULT_COMPONENT_ALLOCATOR(Obstruction
)
57 typedef ICmpObstructionManager::tag_t tag_t
;
58 typedef ICmpObstructionManager::flags_t flags_t
;
62 EObstructionType m_Type
;
64 entity_pos_t m_Size0
; // radius or width
65 entity_pos_t m_Size1
; // radius or depth
66 flags_t m_TemplateFlags
;
67 entity_pos_t m_Clearance
;
72 entity_pos_t size0
, size1
;
76 std::vector
<Shape
> m_Shapes
;
80 /// Whether the obstruction is actively obstructing or just an inactive placeholder.
82 /// Whether the entity associated with this obstruction is currently moving. Only applicable for
83 /// UNIT-type obstructions.
85 /// Whether an obstruction's control group should be kept consistent and
86 /// used to set control groups for entities that collide with it.
87 bool m_ControlPersist
;
89 // WORKAROUND: While processing Destroy messages, the obstruction component may receive messages
90 // that make it re-enable the obstruction, thus leaving behind dangling obstructions.
91 // To avoid that, if this is true, _never_ reactivate the obstruction.
92 bool m_IsDestroyed
= false;
95 * Primary control group identifier. Indicates to which control group this entity's shape belongs.
96 * Typically used in combination with obstruction test filters to have member shapes ignore each
97 * other during obstruction tests. Defaults to the entity's ID. Must never be set to INVALID_ENTITY.
99 entity_id_t m_ControlGroup
;
102 * Optional secondary control group identifier. Similar to m_ControlGroup; if set to a valid value,
103 * then this field identifies an additional, secondary control group to which this entity's shape
104 * belongs. Set to INVALID_ENTITY to not assign any secondary group. Defaults to INVALID_ENTITY.
106 * These are only necessary in case it is not sufficient for an entity to belong to only one control
107 * group. Otherwise, they can be ignored.
109 entity_id_t m_ControlGroup2
;
111 /// Identifier of this entity's obstruction shape, as registered in the obstruction manager. Contains
112 /// structure, but should be treated as opaque here.
114 std::vector
<tag_t
> m_ClusterTags
;
116 /// Set of flags affecting the behaviour of this entity's obstruction shape.
119 static std::string
GetSchema()
123 "<a:help>Causes this entity to obstruct the motion of other units.</a:help>"
125 "<element name='Static'>"
126 "<attribute name='width'>"
127 "<data type='decimal'>"
128 "<param name='minInclusive'>1.5</param>"
131 "<attribute name='depth'>"
132 "<data type='decimal'>"
133 "<param name='minInclusive'>1.5</param>"
137 "<element name='Unit'>"
140 "<element name='Obstructions'>"
145 "<attribute name='x'>"
146 "<data type='decimal'/>"
150 "<attribute name='z'>"
151 "<data type='decimal'/>"
154 "<attribute name='width'>"
155 "<data type='decimal'>"
156 "<param name='minInclusive'>1.5</param>"
159 "<attribute name='depth'>"
160 "<data type='decimal'>"
161 "<param name='minInclusive'>1.5</param>"
168 "<element name='Active' a:help='If false, this entity will be ignored in collision tests by other units but can still perform its own collision tests'>"
169 "<data type='boolean'/>"
171 "<element name='BlockMovement' a:help='Whether units should be allowed to walk through this entity'>"
172 "<data type='boolean'/>"
174 "<element name='BlockPathfinding' a:help='Whether the long-distance pathfinder should avoid paths through this entity. This should only be set for large stationary obstructions'>"
175 "<data type='boolean'/>"
177 "<element name='BlockFoundation' a:help='Whether players should be unable to place building foundations on top of this entity. If true, BlockConstruction should be true too'>"
178 "<data type='boolean'/>"
180 "<element name='BlockConstruction' a:help='Whether players should be unable to begin constructing buildings placed on top of this entity'>"
181 "<data type='boolean'/>"
183 "<element name='DeleteUponConstruction' a:help='Whether this entity should be deleted when construction on a buildings placed on top of this entity is started.'>"
184 "<data type='boolean'/>"
186 "<element name='DisableBlockMovement' a:help='If true, BlockMovement will be overridden and treated as false. (This is a special case to handle foundations)'>"
187 "<data type='boolean'/>"
189 "<element name='DisableBlockPathfinding' a:help='If true, BlockPathfinding will be overridden and treated as false. (This is a special case to handle foundations)'>"
190 "<data type='boolean'/>"
193 "<element name='ControlPersist' a:help='If present, the control group of this entity will be given to entities that are colliding with it.'>"
199 void Init(const CParamNode
& paramNode
) override
201 // The minimum obstruction size is the navcell size * sqrt(2)
202 // This is enforced in the schema as a minimum of 1.5
203 fixed minObstruction
= (Pathfinding::NAVCELL_SIZE
.Square() * 2).Sqrt();
205 if (paramNode
.GetChild("BlockMovement").ToBool())
206 m_TemplateFlags
|= ICmpObstructionManager::FLAG_BLOCK_MOVEMENT
;
207 if (paramNode
.GetChild("BlockPathfinding").ToBool())
208 m_TemplateFlags
|= ICmpObstructionManager::FLAG_BLOCK_PATHFINDING
;
209 if (paramNode
.GetChild("BlockFoundation").ToBool())
210 m_TemplateFlags
|= ICmpObstructionManager::FLAG_BLOCK_FOUNDATION
;
211 if (paramNode
.GetChild("BlockConstruction").ToBool())
212 m_TemplateFlags
|= ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION
;
213 if (paramNode
.GetChild("DeleteUponConstruction").ToBool())
214 m_TemplateFlags
|= ICmpObstructionManager::FLAG_DELETE_UPON_CONSTRUCTION
;
216 m_Flags
= m_TemplateFlags
;
217 if (paramNode
.GetChild("DisableBlockMovement").ToBool())
218 m_Flags
&= (flags_t
)(~ICmpObstructionManager::FLAG_BLOCK_MOVEMENT
);
219 if (paramNode
.GetChild("DisableBlockPathfinding").ToBool())
220 m_Flags
&= (flags_t
)(~ICmpObstructionManager::FLAG_BLOCK_PATHFINDING
);
222 if (paramNode
.GetChild("Unit").IsOk())
226 CmpPtr
<ICmpUnitMotion
> cmpUnitMotion(GetEntityHandle());
228 m_Clearance
= cmpUnitMotion
->GetUnitClearance();
230 else if (paramNode
.GetChild("Static").IsOk())
233 m_Size0
= paramNode
.GetChild("Static").GetChild("@width").ToFixed();
234 m_Size1
= paramNode
.GetChild("Static").GetChild("@depth").ToFixed();
235 ENSURE(m_Size0
> minObstruction
);
236 ENSURE(m_Size1
> minObstruction
);
241 CFixedVector2D max
= CFixedVector2D(fixed::FromInt(0), fixed::FromInt(0));
242 CFixedVector2D min
= CFixedVector2D(fixed::FromInt(0), fixed::FromInt(0));
243 const CParamNode::ChildrenMap
& clusterMap
= paramNode
.GetChild("Obstructions").GetChildren();
244 for(CParamNode::ChildrenMap::const_iterator it
= clusterMap
.begin(); it
!= clusterMap
.end(); ++it
)
247 b
.size0
= it
->second
.GetChild("@width").ToFixed();
248 b
.size1
= it
->second
.GetChild("@depth").ToFixed();
249 ENSURE(b
.size0
> minObstruction
);
250 ENSURE(b
.size1
> minObstruction
);
251 b
.dx
= it
->second
.GetChild("@x").ToFixed();
252 b
.dz
= it
->second
.GetChild("@z").ToFixed();
253 b
.da
= entity_angle_t::FromInt(0);
255 m_Shapes
.push_back(b
);
256 max
.X
= std::max(max
.X
, b
.dx
+ b
.size0
/2);
257 max
.Y
= std::max(max
.Y
, b
.dz
+ b
.size1
/2);
258 min
.X
= std::min(min
.X
, b
.dx
- b
.size0
/2);
259 min
.Y
= std::min(min
.Y
, b
.dz
- b
.size1
/2);
261 m_Size0
= fixed::FromInt(2).Multiply(std::max(max
.X
, -min
.X
));
262 m_Size1
= fixed::FromInt(2).Multiply(std::max(max
.Y
, -min
.Y
));
265 m_Active
= paramNode
.GetChild("Active").ToBool();
266 m_ControlPersist
= paramNode
.GetChild("ControlPersist").IsOk();
269 if (m_Type
== CLUSTER
)
270 m_ClusterTags
.clear();
272 m_ControlGroup
= GetEntityId();
273 m_ControlGroup2
= INVALID_ENTITY
;
276 void Deinit() override
281 void SerializeCommon(S
& serialize
)
283 serialize
.Bool("active", m_Active
);
284 serialize
.Bool("moving", m_Moving
);
285 serialize
.NumberU32_Unbounded("control group", m_ControlGroup
);
286 serialize
.NumberU32_Unbounded("control group 2", m_ControlGroup2
);
287 serialize
.NumberU32_Unbounded("tag", m_Tag
.n
);
288 serialize
.NumberU8_Unbounded("flags", m_Flags
);
289 if (m_Type
== CLUSTER
)
290 Serializer(serialize
, "cluster tags", m_ClusterTags
);
292 serialize
.NumberFixed_Unbounded("clearance", m_Clearance
);
295 void Serialize(ISerializer
& serialize
) override
297 SerializeCommon(serialize
);
300 void Deserialize(const CParamNode
& paramNode
, IDeserializer
& deserialize
) override
304 SerializeCommon(deserialize
);
307 void HandleMessage(const CMessage
& msg
, bool UNUSED(global
)) override
309 switch (msg
.GetType())
311 case MT_PositionChanged
:
313 if (!m_Active
|| m_IsDestroyed
)
316 const CMessagePositionChanged
& data
= static_cast<const CMessagePositionChanged
&> (msg
);
318 if (!data
.inWorld
&& !m_Tag
.valid())
319 break; // nothing needs to change
321 CmpPtr
<ICmpObstructionManager
> cmpObstructionManager(GetSystemEntity());
322 if (!cmpObstructionManager
)
325 if (data
.inWorld
&& m_Tag
.valid())
327 cmpObstructionManager
->MoveShape(m_Tag
, data
.x
, data
.z
, data
.a
);
329 if (m_Type
== CLUSTER
)
331 for (size_t i
= 0; i
< m_Shapes
.size(); ++i
)
333 Shape
& b
= m_Shapes
[i
];
335 sincos_approx(data
.a
, s
, c
);
336 cmpObstructionManager
->MoveShape(m_ClusterTags
[i
], data
.x
+ b
.dx
.Multiply(c
) + b
.dz
.Multiply(s
), data
.z
+ b
.dz
.Multiply(c
) - b
.dx
.Multiply(s
), data
.a
+ b
.da
);
340 else if (data
.inWorld
&& !m_Tag
.valid())
342 // Need to create a new pathfinder shape:
343 if (m_Type
== STATIC
)
344 m_Tag
= cmpObstructionManager
->AddStaticShape(GetEntityId(),
345 data
.x
, data
.z
, data
.a
, m_Size0
, m_Size1
, m_Flags
, m_ControlGroup
, m_ControlGroup2
);
346 else if (m_Type
== UNIT
)
347 m_Tag
= cmpObstructionManager
->AddUnitShape(GetEntityId(),
348 data
.x
, data
.z
, m_Clearance
, (flags_t
)(m_Flags
| (m_Moving
? ICmpObstructionManager::FLAG_MOVING
: 0)), m_ControlGroup
);
350 AddClusterShapes(data
.x
, data
.x
, data
.a
);
352 else if (!data
.inWorld
&& m_Tag
.valid())
354 cmpObstructionManager
->RemoveShape(m_Tag
);
356 if(m_Type
== CLUSTER
)
357 RemoveClusterShapes();
363 // Mark the obstruction as destroyed to prevent reactivating it after this point
364 m_IsDestroyed
= true;
369 CmpPtr
<ICmpObstructionManager
> cmpObstructionManager(GetSystemEntity());
370 if (!cmpObstructionManager
)
373 cmpObstructionManager
->RemoveShape(m_Tag
);
375 if(m_Type
== CLUSTER
)
376 RemoveClusterShapes();
383 void SetActive(bool active
) override
385 if (active
&& !m_Active
&& !m_IsDestroyed
)
389 // Construct the obstruction shape
391 CmpPtr
<ICmpObstructionManager
> cmpObstructionManager(GetSystemEntity());
392 if (!cmpObstructionManager
)
395 CmpPtr
<ICmpPosition
> cmpPosition(GetEntityHandle());
399 if (!cmpPosition
->IsInWorld())
400 return; // don't need an obstruction
402 // TODO: code duplication from message handlers
403 CFixedVector2D pos
= cmpPosition
->GetPosition2D();
404 if (m_Type
== STATIC
)
405 m_Tag
= cmpObstructionManager
->AddStaticShape(GetEntityId(),
406 pos
.X
, pos
.Y
, cmpPosition
->GetRotation().Y
, m_Size0
, m_Size1
, m_Flags
, m_ControlGroup
, m_ControlGroup2
);
407 else if (m_Type
== UNIT
)
408 m_Tag
= cmpObstructionManager
->AddUnitShape(GetEntityId(),
409 pos
.X
, pos
.Y
, m_Clearance
, (flags_t
)(m_Flags
| (m_Moving
? ICmpObstructionManager::FLAG_MOVING
: 0)), m_ControlGroup
);
411 AddClusterShapes(pos
.X
, pos
.Y
, cmpPosition
->GetRotation().Y
);
413 // Used by UnitMotion to activate/deactivate pushing
414 if (m_TemplateFlags
& ICmpObstructionManager::FLAG_BLOCK_MOVEMENT
)
416 CMessageMovementObstructionChanged msg
;
417 GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg
);
420 else if (!active
&& m_Active
)
424 // Delete the obstruction shape
426 // TODO: code duplication from message handlers
429 CmpPtr
<ICmpObstructionManager
> cmpObstructionManager(GetSystemEntity());
430 if (!cmpObstructionManager
)
433 cmpObstructionManager
->RemoveShape(m_Tag
);
435 if (m_Type
== CLUSTER
)
436 RemoveClusterShapes();
439 // Used by UnitMotion to activate/deactivate pushing
440 if (m_TemplateFlags
& ICmpObstructionManager::FLAG_BLOCK_MOVEMENT
)
442 CMessageMovementObstructionChanged msg
;
443 GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg
);
446 // else we didn't change the active status
449 void SetDisableBlockMovementPathfinding(bool movementDisabled
, bool pathfindingDisabled
, int32_t shape
) override
451 flags_t
*flags
= NULL
;
454 else if (m_Type
== CLUSTER
&& shape
< (int32_t)m_Shapes
.size())
455 flags
= &m_Shapes
[shape
].flags
;
459 // Remove the blocking / pathfinding flags or
460 // Add the blocking / pathfinding flags if the template had enabled them
461 if (movementDisabled
)
462 *flags
&= (flags_t
)(~ICmpObstructionManager::FLAG_BLOCK_MOVEMENT
);
464 *flags
|= (flags_t
)(m_TemplateFlags
& ICmpObstructionManager::FLAG_BLOCK_MOVEMENT
);
465 if (pathfindingDisabled
)
466 *flags
&= (flags_t
)(~ICmpObstructionManager::FLAG_BLOCK_PATHFINDING
);
468 *flags
|= (flags_t
)(m_TemplateFlags
& ICmpObstructionManager::FLAG_BLOCK_PATHFINDING
);
470 // Reset the shape with the new flags (kind of inefficiently - we
471 // should have a ICmpObstructionManager::SetFlags function or something)
479 bool GetBlockMovementFlag(bool templateOnly
) const override
481 return m_Active
&& ((templateOnly
? m_TemplateFlags
: m_Flags
) & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT
) != 0;
484 EObstructionType
GetObstructionType() const override
489 ICmpObstructionManager::tag_t
GetObstruction() const override
494 bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare
& out
) const override
496 return GetObstructionSquare(out
, true);
499 bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare
& out
) const override
501 return GetObstructionSquare(out
, false);
504 virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare
& out
, bool previousPosition
) const
506 CmpPtr
<ICmpPosition
> cmpPosition(GetEntityHandle());
508 return false; // error
510 CmpPtr
<ICmpObstructionManager
> cmpObstructionManager(GetSystemEntity());
511 if (!cmpObstructionManager
)
512 return false; // error
514 if (!cmpPosition
->IsInWorld())
515 return false; // no obstruction square
518 if (previousPosition
)
519 pos
= cmpPosition
->GetPreviousPosition2D();
521 pos
= cmpPosition
->GetPosition2D();
523 out
= cmpObstructionManager
->GetUnitShapeObstruction(pos
.X
, pos
.Y
, m_Clearance
);
525 out
= cmpObstructionManager
->GetStaticShapeObstruction(pos
.X
, pos
.Y
, cmpPosition
->GetRotation().Y
, m_Size0
, m_Size1
);
529 entity_pos_t
GetSize() const override
534 return CFixedVector2D(m_Size0
/ 2, m_Size1
/ 2).Length();
537 CFixedVector2D
GetStaticSize() const override
539 return m_Type
== STATIC
? CFixedVector2D(m_Size0
, m_Size1
) : CFixedVector2D();
542 void SetUnitClearance(const entity_pos_t
& clearance
) override
544 // This doesn't send a MovementObstructionChanged message
545 // because it's a just a workaround init order, and used in UnitMotion directly.
547 m_Clearance
= clearance
;
550 bool IsControlPersistent() const override
552 return m_ControlPersist
;
555 bool CheckShorePlacement() const override
557 ICmpObstructionManager::ObstructionSquare s
;
558 if (!GetObstructionSquare(s
))
561 CFixedVector2D front
= CFixedVector2D(s
.x
, s
.z
) + s
.v
.Multiply(s
.hh
);
562 CFixedVector2D back
= CFixedVector2D(s
.x
, s
.z
) - s
.v
.Multiply(s
.hh
);
564 CmpPtr
<ICmpTerrain
> cmpTerrain(GetSystemEntity());
565 CmpPtr
<ICmpWaterManager
> cmpWaterManager(GetSystemEntity());
566 if (!cmpTerrain
|| !cmpWaterManager
)
569 // Keep these constants in agreement with the pathfinder.
570 return cmpWaterManager
->GetWaterLevel(front
.X
, front
.Y
) - cmpTerrain
->GetGroundLevel(front
.X
, front
.Y
) > fixed::FromInt(1) &&
571 cmpWaterManager
->GetWaterLevel( back
.X
, back
.Y
) - cmpTerrain
->GetGroundLevel( back
.X
, back
.Y
) < fixed::FromInt(2);
574 EFoundationCheck
CheckFoundation(const std::string
& className
) const override
576 return CheckFoundation(className
, false);
579 EFoundationCheck
CheckFoundation(const std::string
& className
, bool onlyCenterPoint
) const override
581 CmpPtr
<ICmpPosition
> cmpPosition(GetEntityHandle());
583 return FOUNDATION_CHECK_FAIL_ERROR
; // error
585 if (!cmpPosition
->IsInWorld())
586 return FOUNDATION_CHECK_FAIL_NO_OBSTRUCTION
; // no obstruction
588 CFixedVector2D pos
= cmpPosition
->GetPosition2D();
590 CmpPtr
<ICmpPathfinder
> cmpPathfinder(GetSystemEntity());
592 return FOUNDATION_CHECK_FAIL_ERROR
; // error
594 // required precondition to use SkipControlGroupsRequireFlagObstructionFilter
595 if (m_ControlGroup
== INVALID_ENTITY
)
597 LOGERROR("[CmpObstruction] Cannot test for foundation obstructions; primary control group must be valid");
598 return FOUNDATION_CHECK_FAIL_ERROR
;
601 // Get passability class
602 pass_class_t passClass
= cmpPathfinder
->GetPassabilityClass(className
);
604 // Ignore collisions within the same control group, or with other non-foundation-blocking shapes.
605 // Note that, since the control group for each entity defaults to the entity's ID, this is typically
606 // equivalent to only ignoring the entity's own shape and other non-foundation-blocking shapes.
607 SkipControlGroupsRequireFlagObstructionFilter
filter(m_ControlGroup
, m_ControlGroup2
,
608 ICmpObstructionManager::FLAG_BLOCK_FOUNDATION
);
611 return cmpPathfinder
->CheckUnitPlacement(filter
, pos
.X
, pos
.Y
, m_Clearance
, passClass
, onlyCenterPoint
);
613 return cmpPathfinder
->CheckBuildingPlacement(filter
, pos
.X
, pos
.Y
, cmpPosition
->GetRotation().Y
, m_Size0
, m_Size1
, GetEntityId(), passClass
, onlyCenterPoint
);
616 bool CheckDuplicateFoundation() const override
618 CmpPtr
<ICmpPosition
> cmpPosition(GetEntityHandle());
620 return false; // error
622 if (!cmpPosition
->IsInWorld())
623 return false; // no obstruction
625 CFixedVector2D pos
= cmpPosition
->GetPosition2D();
627 CmpPtr
<ICmpObstructionManager
> cmpObstructionManager(GetSystemEntity());
628 if (!cmpObstructionManager
)
629 return false; // error
631 // required precondition to use SkipControlGroupsRequireFlagObstructionFilter
632 if (m_ControlGroup
== INVALID_ENTITY
)
634 LOGERROR("[CmpObstruction] Cannot test for foundation obstructions; primary control group must be valid");
638 // Ignore collisions with entities unless they block foundations and match both control groups.
639 SkipTagRequireControlGroupsAndFlagObstructionFilter
filter(m_Tag
, m_ControlGroup
, m_ControlGroup2
,
640 ICmpObstructionManager::FLAG_BLOCK_FOUNDATION
);
643 return !cmpObstructionManager
->TestUnitShape(filter
, pos
.X
, pos
.Y
, m_Clearance
, NULL
);
645 return !cmpObstructionManager
->TestStaticShape(filter
, pos
.X
, pos
.Y
, cmpPosition
->GetRotation().Y
, m_Size0
, m_Size1
, NULL
);
648 std::vector
<entity_id_t
> GetEntitiesByFlags(flags_t flags
) const override
650 std::vector
<entity_id_t
> ret
;
652 CmpPtr
<ICmpObstructionManager
> cmpObstructionManager(GetSystemEntity());
653 if (!cmpObstructionManager
)
656 // Ignore collisions within the same control group, or with other shapes that don't match the filter.
657 // Note that, since the control group for each entity defaults to the entity's ID, this is typically
658 // equivalent to only ignoring the entity's own shape and other shapes that don't match the filter.
659 SkipControlGroupsRequireFlagObstructionFilter
filter(false, m_ControlGroup
, m_ControlGroup2
, flags
);
661 ICmpObstructionManager::ObstructionSquare square
;
662 if (!GetObstructionSquare(square
))
665 cmpObstructionManager
->GetUnitsOnObstruction(square
, ret
, filter
, false);
666 cmpObstructionManager
->GetStaticObstructionsOnObstruction(square
, ret
, filter
);
671 std::vector
<entity_id_t
> GetEntitiesBlockingMovement() const override
673 return GetEntitiesByFlags(ICmpObstructionManager::FLAG_BLOCK_MOVEMENT
);
676 std::vector
<entity_id_t
> GetEntitiesBlockingConstruction() const override
678 return GetEntitiesByFlags(ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION
);
681 std::vector
<entity_id_t
> GetEntitiesDeletedUponConstruction() const override
683 return GetEntitiesByFlags(ICmpObstructionManager::FLAG_DELETE_UPON_CONSTRUCTION
);
686 void SetMovingFlag(bool enabled
) override
690 if (m_Tag
.valid() && m_Type
== UNIT
)
692 CmpPtr
<ICmpObstructionManager
> cmpObstructionManager(GetSystemEntity());
693 if (cmpObstructionManager
)
694 cmpObstructionManager
->SetUnitMovingFlag(m_Tag
, m_Moving
);
698 void SetControlGroup(entity_id_t group
) override
700 m_ControlGroup
= group
;
701 UpdateControlGroups();
704 void SetControlGroup2(entity_id_t group2
) override
706 m_ControlGroup2
= group2
;
707 UpdateControlGroups();
710 entity_id_t
GetControlGroup() const override
712 return m_ControlGroup
;
715 entity_id_t
GetControlGroup2() const override
717 return m_ControlGroup2
;
720 void UpdateControlGroups()
724 CmpPtr
<ICmpObstructionManager
> cmpObstructionManager(GetSystemEntity());
725 if (cmpObstructionManager
)
729 cmpObstructionManager
->SetUnitControlGroup(m_Tag
, m_ControlGroup
);
731 else if (m_Type
== STATIC
)
733 cmpObstructionManager
->SetStaticControlGroup(m_Tag
, m_ControlGroup
, m_ControlGroup2
);
737 cmpObstructionManager
->SetStaticControlGroup(m_Tag
, m_ControlGroup
, m_ControlGroup2
);
738 for (size_t i
= 0; i
< m_ClusterTags
.size(); ++i
)
740 cmpObstructionManager
->SetStaticControlGroup(m_ClusterTags
[i
], m_ControlGroup
, m_ControlGroup2
);
747 void ResolveFoundationCollisions() const override
752 CmpPtr
<ICmpObstructionManager
> cmpObstructionManager(GetSystemEntity());
753 if (!cmpObstructionManager
)
756 CmpPtr
<ICmpPosition
> cmpPosition(GetEntityHandle());
760 if (!cmpPosition
->IsInWorld())
761 return; // no obstruction
763 CFixedVector2D pos
= cmpPosition
->GetPosition2D();
765 // Ignore collisions within the same control group, or with other non-foundation-blocking shapes.
766 // Note that, since the control group for each entity defaults to the entity's ID, this is typically
767 // equivalent to only ignoring the entity's own shape and other non-foundation-blocking shapes.
768 SkipControlGroupsRequireFlagObstructionFilter
filter(m_ControlGroup
, m_ControlGroup2
,
769 ICmpObstructionManager::FLAG_BLOCK_FOUNDATION
);
771 std::vector
<entity_id_t
> collisions
;
772 if (cmpObstructionManager
->TestStaticShape(filter
, pos
.X
, pos
.Y
, cmpPosition
->GetRotation().Y
, m_Size0
, m_Size1
, &collisions
))
774 std::vector
<entity_id_t
> persistentEnts
, normalEnts
;
776 if (m_ControlPersist
)
777 persistentEnts
.push_back(m_ControlGroup
);
779 normalEnts
.push_back(GetEntityId());
781 for (std::vector
<entity_id_t
>::iterator it
= collisions
.begin(); it
!= collisions
.end(); ++it
)
783 entity_id_t ent
= *it
;
784 if (ent
== INVALID_ENTITY
)
787 CmpPtr
<ICmpObstruction
> cmpObstruction(GetSimContext(), ent
);
788 if (!cmpObstruction
->IsControlPersistent())
789 normalEnts
.push_back(ent
);
791 persistentEnts
.push_back(cmpObstruction
->GetControlGroup());
794 // The collision can't be resolved without usable persistent control groups.
795 if (persistentEnts
.empty())
798 // Attempt to replace colliding entities' control groups with a persistent one.
799 for (const entity_id_t normalEnt
: normalEnts
)
801 CmpPtr
<ICmpObstruction
> cmpObstruction(GetSimContext(), normalEnt
);
802 for (const entity_id_t persistent
: normalEnts
)
804 entity_id_t group
= cmpObstruction
->GetControlGroup();
806 // Only clobber 'default' control groups.
807 if (group
== normalEnt
)
808 cmpObstruction
->SetControlGroup(persistent
);
809 else if (cmpObstruction
->GetControlGroup2() == INVALID_ENTITY
&& group
!= persistent
)
810 cmpObstruction
->SetControlGroup2(persistent
);
817 inline void AddClusterShapes(entity_pos_t x
, entity_pos_t z
, entity_angle_t a
)
819 CmpPtr
<ICmpObstructionManager
> cmpObstructionManager(GetSystemEntity());
820 if (!cmpObstructionManager
)
823 flags_t flags
= m_Flags
;
824 // Disable block movement and block pathfinding for the obstruction shape
825 flags
&= (flags_t
)(~ICmpObstructionManager::FLAG_BLOCK_MOVEMENT
);
826 flags
&= (flags_t
)(~ICmpObstructionManager::FLAG_BLOCK_PATHFINDING
);
828 m_Tag
= cmpObstructionManager
->AddStaticShape(GetEntityId(),
829 x
, z
, a
, m_Size0
, m_Size1
, flags
, m_ControlGroup
, m_ControlGroup2
);
832 sincos_approx(a
, s
, c
);
834 for (size_t i
= 0; i
< m_Shapes
.size(); ++i
)
836 Shape
& b
= m_Shapes
[i
];
837 tag_t tag
= cmpObstructionManager
->AddStaticShape(GetEntityId(),
838 x
+ b
.dx
.Multiply(c
) + b
.dz
.Multiply(s
), z
+ b
.dz
.Multiply(c
) - b
.dx
.Multiply(s
), a
+ b
.da
, b
.size0
, b
.size1
, b
.flags
, m_ControlGroup
, m_ControlGroup2
);
839 m_ClusterTags
.push_back(tag
);
843 inline void RemoveClusterShapes()
845 CmpPtr
<ICmpObstructionManager
> cmpObstructionManager(GetSystemEntity());
846 if (!cmpObstructionManager
)
849 for (size_t i
= 0; i
< m_ClusterTags
.size(); ++i
)
851 if (m_ClusterTags
[i
].valid())
853 cmpObstructionManager
->RemoveShape(m_ClusterTags
[i
]);
856 m_ClusterTags
.clear();
861 REGISTER_COMPONENT_TYPE(Obstruction
)