Fixes macOS warnings after adding final keyword to simulation classes in rP26537.
[0ad.git] / source / simulation2 / components / CCmpObstruction.cpp
blobd665d9957fdcc68acd08b19e39f43af5fdc99f85
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"
32 template<>
33 struct SerializeHelper<ICmpObstructionManager::tag_t>
35 template<typename S>
36 void operator()(S& serialize, const char* UNUSED(name), Serialize::qualify<S, ICmpObstructionManager::tag_t> value)
38 serialize.NumberU32_Unbounded("tag", value.n);
42 /**
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
48 public:
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;
60 // Template state:
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;
69 typedef struct {
70 entity_pos_t dx, dz;
71 entity_angle_t da;
72 entity_pos_t size0, size1;
73 flags_t flags;
74 } Shape;
76 std::vector<Shape> m_Shapes;
78 // Dynamic state:
80 /// Whether the obstruction is actively obstructing or just an inactive placeholder.
81 bool m_Active;
82 /// Whether the entity associated with this obstruction is currently moving. Only applicable for
83 /// UNIT-type obstructions.
84 bool m_Moving;
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;
94 /**
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.
113 tag_t m_Tag;
114 std::vector<tag_t> m_ClusterTags;
116 /// Set of flags affecting the behaviour of this entity's obstruction shape.
117 flags_t m_Flags;
119 static std::string GetSchema()
121 return
122 "<a:example/>"
123 "<a:help>Causes this entity to obstruct the motion of other units.</a:help>"
124 "<choice>"
125 "<element name='Static'>"
126 "<attribute name='width'>"
127 "<data type='decimal'>"
128 "<param name='minInclusive'>1.5</param>"
129 "</data>"
130 "</attribute>"
131 "<attribute name='depth'>"
132 "<data type='decimal'>"
133 "<param name='minInclusive'>1.5</param>"
134 "</data>"
135 "</attribute>"
136 "</element>"
137 "<element name='Unit'>"
138 "<empty/>"
139 "</element>"
140 "<element name='Obstructions'>"
141 "<zeroOrMore>"
142 "<element>"
143 "<anyName/>"
144 "<optional>"
145 "<attribute name='x'>"
146 "<data type='decimal'/>"
147 "</attribute>"
148 "</optional>"
149 "<optional>"
150 "<attribute name='z'>"
151 "<data type='decimal'/>"
152 "</attribute>"
153 "</optional>"
154 "<attribute name='width'>"
155 "<data type='decimal'>"
156 "<param name='minInclusive'>1.5</param>"
157 "</data>"
158 "</attribute>"
159 "<attribute name='depth'>"
160 "<data type='decimal'>"
161 "<param name='minInclusive'>1.5</param>"
162 "</data>"
163 "</attribute>"
164 "</element>"
165 "</zeroOrMore>"
166 "</element>"
167 "</choice>"
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'/>"
170 "</element>"
171 "<element name='BlockMovement' a:help='Whether units should be allowed to walk through this entity'>"
172 "<data type='boolean'/>"
173 "</element>"
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'/>"
176 "</element>"
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'/>"
179 "</element>"
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'/>"
182 "</element>"
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'/>"
185 "</element>"
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'/>"
188 "</element>"
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'/>"
191 "</element>"
192 "<optional>"
193 "<element name='ControlPersist' a:help='If present, the control group of this entity will be given to entities that are colliding with it.'>"
194 "<empty/>"
195 "</element>"
196 "</optional>";
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();
204 m_TemplateFlags = 0;
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())
224 m_Type = UNIT;
226 CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetEntityHandle());
227 if (cmpUnitMotion)
228 m_Clearance = cmpUnitMotion->GetUnitClearance();
230 else if (paramNode.GetChild("Static").IsOk())
232 m_Type = STATIC;
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);
238 else
240 m_Type = CLUSTER;
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)
246 Shape b;
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);
254 b.flags = m_Flags;
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();
268 m_Tag = tag_t();
269 if (m_Type == CLUSTER)
270 m_ClusterTags.clear();
271 m_Moving = false;
272 m_ControlGroup = GetEntityId();
273 m_ControlGroup2 = INVALID_ENTITY;
276 void Deinit() override
280 template<typename S>
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);
291 if (m_Type == UNIT)
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
302 Init(paramNode);
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)
314 break;
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)
323 break; // error
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];
334 fixed s, c;
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);
349 else
350 AddClusterShapes(data.x, data.x, data.a);
352 else if (!data.inWorld && m_Tag.valid())
354 cmpObstructionManager->RemoveShape(m_Tag);
355 m_Tag = tag_t();
356 if(m_Type == CLUSTER)
357 RemoveClusterShapes();
359 break;
361 case MT_Destroy:
363 // Mark the obstruction as destroyed to prevent reactivating it after this point
364 m_IsDestroyed = true;
365 m_Active = false;
367 if (m_Tag.valid())
369 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
370 if (!cmpObstructionManager)
371 break; // error
373 cmpObstructionManager->RemoveShape(m_Tag);
374 m_Tag = tag_t();
375 if(m_Type == CLUSTER)
376 RemoveClusterShapes();
378 break;
383 void SetActive(bool active) override
385 if (active && !m_Active && !m_IsDestroyed)
387 m_Active = true;
389 // Construct the obstruction shape
391 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
392 if (!cmpObstructionManager)
393 return; // error
395 CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
396 if (!cmpPosition)
397 return; // error
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);
410 else
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)
422 m_Active = false;
424 // Delete the obstruction shape
426 // TODO: code duplication from message handlers
427 if (m_Tag.valid())
429 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
430 if (!cmpObstructionManager)
431 return; // error
433 cmpObstructionManager->RemoveShape(m_Tag);
434 m_Tag = tag_t();
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;
452 if (shape == -1)
453 flags = &m_Flags;
454 else if (m_Type == CLUSTER && shape < (int32_t)m_Shapes.size())
455 flags = &m_Shapes[shape].flags;
456 else
457 return; // error
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);
463 else
464 *flags |= (flags_t)(m_TemplateFlags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT);
465 if (pathfindingDisabled)
466 *flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_PATHFINDING);
467 else
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)
472 if (m_Active)
474 SetActive(false);
475 SetActive(true);
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
486 return m_Type;
489 ICmpObstructionManager::tag_t GetObstruction() const override
491 return m_Tag;
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());
507 if (!cmpPosition)
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
517 CFixedVector2D pos;
518 if (previousPosition)
519 pos = cmpPosition->GetPreviousPosition2D();
520 else
521 pos = cmpPosition->GetPosition2D();
522 if (m_Type == UNIT)
523 out = cmpObstructionManager->GetUnitShapeObstruction(pos.X, pos.Y, m_Clearance);
524 else
525 out = cmpObstructionManager->GetStaticShapeObstruction(pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1);
526 return true;
529 entity_pos_t GetSize() const override
531 if (m_Type == UNIT)
532 return m_Clearance;
533 else
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.
546 if (m_Type == UNIT)
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))
559 return false;
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)
567 return false;
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());
582 if (!cmpPosition)
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());
591 if (!cmpPathfinder)
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);
610 if (m_Type == UNIT)
611 return cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, m_Clearance, passClass, onlyCenterPoint);
612 else
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());
619 if (!cmpPosition)
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");
635 return false;
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);
642 if (m_Type == UNIT)
643 return !cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Clearance, NULL);
644 else
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)
654 return ret; // error
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))
663 return ret; // error
665 cmpObstructionManager->GetUnitsOnObstruction(square, ret, filter, false);
666 cmpObstructionManager->GetStaticObstructionsOnObstruction(square, ret, filter);
668 return ret;
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
688 m_Moving = enabled;
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()
722 if (m_Tag.valid())
724 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
725 if (cmpObstructionManager)
727 if (m_Type == UNIT)
729 cmpObstructionManager->SetUnitControlGroup(m_Tag, m_ControlGroup);
731 else if (m_Type == STATIC)
733 cmpObstructionManager->SetStaticControlGroup(m_Tag, m_ControlGroup, m_ControlGroup2);
735 else
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
749 if (m_Type == UNIT)
750 return;
752 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
753 if (!cmpObstructionManager)
754 return;
756 CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
757 if (!cmpPosition)
758 return; // error
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);
778 else
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)
785 continue;
787 CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), ent);
788 if (!cmpObstruction->IsControlPersistent())
789 normalEnts.push_back(ent);
790 else
791 persistentEnts.push_back(cmpObstruction->GetControlGroup());
794 // The collision can't be resolved without usable persistent control groups.
795 if (persistentEnts.empty())
796 return;
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);
815 protected:
817 inline void AddClusterShapes(entity_pos_t x, entity_pos_t z, entity_angle_t a)
819 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
820 if (!cmpObstructionManager)
821 return; // error
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);
831 fixed s, c;
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)
847 return; // error
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)