Line data Source code
1 : /* Copyright (C) 2022 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
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.
8 : *
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.
13 : *
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/>.
16 : */
17 :
18 : #include "precompiled.h"
19 :
20 : #include "simulation2/system/Component.h"
21 : #include "ICmpObstruction.h"
22 :
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"
29 :
30 : #include "ps/CLogger.h"
31 :
32 : template<>
33 : struct SerializeHelper<ICmpObstructionManager::tag_t>
34 : {
35 : template<typename S>
36 0 : void operator()(S& serialize, const char* UNUSED(name), Serialize::qualify<S, ICmpObstructionManager::tag_t> value)
37 : {
38 0 : serialize.NumberU32_Unbounded("tag", value.n);
39 0 : }
40 : };
41 :
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.
45 : */
46 0 : class CCmpObstruction final : public ICmpObstruction
47 : {
48 : public:
49 116 : static void ClassInit(CComponentManager& componentManager)
50 : {
51 116 : componentManager.SubscribeToMessageType(MT_PositionChanged);
52 116 : componentManager.SubscribeToMessageType(MT_Destroy);
53 116 : }
54 :
55 0 : DEFAULT_COMPONENT_ALLOCATOR(Obstruction)
56 :
57 : typedef ICmpObstructionManager::tag_t tag_t;
58 : typedef ICmpObstructionManager::flags_t flags_t;
59 :
60 : // Template state:
61 :
62 : EObstructionType m_Type;
63 :
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;
68 :
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 0 : } Shape;
75 :
76 : std::vector<Shape> m_Shapes;
77 :
78 : // Dynamic state:
79 :
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;
88 :
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;
93 :
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.
98 : */
99 : entity_id_t m_ControlGroup;
100 :
101 : /**
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.
105 : *
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.
108 : */
109 : entity_id_t m_ControlGroup2;
110 :
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;
115 :
116 : /// Set of flags affecting the behaviour of this entity's obstruction shape.
117 : flags_t m_Flags;
118 :
119 116 : static std::string GetSchema()
120 : {
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 116 : "</optional>";
197 : }
198 :
199 0 : void Init(const CParamNode& paramNode) override
200 : {
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 0 : fixed minObstruction = (Pathfinding::NAVCELL_SIZE.Square() * 2).Sqrt();
204 0 : m_TemplateFlags = 0;
205 0 : if (paramNode.GetChild("BlockMovement").ToBool())
206 0 : m_TemplateFlags |= ICmpObstructionManager::FLAG_BLOCK_MOVEMENT;
207 0 : if (paramNode.GetChild("BlockPathfinding").ToBool())
208 0 : m_TemplateFlags |= ICmpObstructionManager::FLAG_BLOCK_PATHFINDING;
209 0 : if (paramNode.GetChild("BlockFoundation").ToBool())
210 0 : m_TemplateFlags |= ICmpObstructionManager::FLAG_BLOCK_FOUNDATION;
211 0 : if (paramNode.GetChild("BlockConstruction").ToBool())
212 0 : m_TemplateFlags |= ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION;
213 0 : if (paramNode.GetChild("DeleteUponConstruction").ToBool())
214 0 : m_TemplateFlags |= ICmpObstructionManager::FLAG_DELETE_UPON_CONSTRUCTION;
215 :
216 0 : m_Flags = m_TemplateFlags;
217 0 : if (paramNode.GetChild("DisableBlockMovement").ToBool())
218 0 : m_Flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_MOVEMENT);
219 0 : if (paramNode.GetChild("DisableBlockPathfinding").ToBool())
220 0 : m_Flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_PATHFINDING);
221 :
222 0 : if (paramNode.GetChild("Unit").IsOk())
223 : {
224 0 : m_Type = UNIT;
225 :
226 0 : CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetEntityHandle());
227 0 : if (cmpUnitMotion)
228 0 : m_Clearance = cmpUnitMotion->GetUnitClearance();
229 : }
230 0 : else if (paramNode.GetChild("Static").IsOk())
231 : {
232 0 : m_Type = STATIC;
233 0 : m_Size0 = paramNode.GetChild("Static").GetChild("@width").ToFixed();
234 0 : m_Size1 = paramNode.GetChild("Static").GetChild("@depth").ToFixed();
235 0 : ENSURE(m_Size0 > minObstruction);
236 0 : ENSURE(m_Size1 > minObstruction);
237 : }
238 : else
239 : {
240 0 : m_Type = CLUSTER;
241 0 : CFixedVector2D max = CFixedVector2D(fixed::FromInt(0), fixed::FromInt(0));
242 0 : CFixedVector2D min = CFixedVector2D(fixed::FromInt(0), fixed::FromInt(0));
243 0 : const CParamNode::ChildrenMap& clusterMap = paramNode.GetChild("Obstructions").GetChildren();
244 0 : for(CParamNode::ChildrenMap::const_iterator it = clusterMap.begin(); it != clusterMap.end(); ++it)
245 : {
246 0 : Shape b;
247 0 : b.size0 = it->second.GetChild("@width").ToFixed();
248 0 : b.size1 = it->second.GetChild("@depth").ToFixed();
249 0 : ENSURE(b.size0 > minObstruction);
250 0 : ENSURE(b.size1 > minObstruction);
251 0 : b.dx = it->second.GetChild("@x").ToFixed();
252 0 : b.dz = it->second.GetChild("@z").ToFixed();
253 0 : b.da = entity_angle_t::FromInt(0);
254 0 : b.flags = m_Flags;
255 0 : m_Shapes.push_back(b);
256 0 : max.X = std::max(max.X, b.dx + b.size0/2);
257 0 : max.Y = std::max(max.Y, b.dz + b.size1/2);
258 0 : min.X = std::min(min.X, b.dx - b.size0/2);
259 0 : min.Y = std::min(min.Y, b.dz - b.size1/2);
260 : }
261 0 : m_Size0 = fixed::FromInt(2).Multiply(std::max(max.X, -min.X));
262 0 : m_Size1 = fixed::FromInt(2).Multiply(std::max(max.Y, -min.Y));
263 : }
264 :
265 0 : m_Active = paramNode.GetChild("Active").ToBool();
266 0 : m_ControlPersist = paramNode.GetChild("ControlPersist").IsOk();
267 :
268 0 : m_Tag = tag_t();
269 0 : if (m_Type == CLUSTER)
270 0 : m_ClusterTags.clear();
271 0 : m_Moving = false;
272 0 : m_ControlGroup = GetEntityId();
273 0 : m_ControlGroup2 = INVALID_ENTITY;
274 0 : }
275 :
276 0 : void Deinit() override
277 : {
278 0 : }
279 :
280 : template<typename S>
281 0 : void SerializeCommon(S& serialize)
282 : {
283 0 : serialize.Bool("active", m_Active);
284 0 : serialize.Bool("moving", m_Moving);
285 0 : serialize.NumberU32_Unbounded("control group", m_ControlGroup);
286 0 : serialize.NumberU32_Unbounded("control group 2", m_ControlGroup2);
287 0 : serialize.NumberU32_Unbounded("tag", m_Tag.n);
288 0 : serialize.NumberU8_Unbounded("flags", m_Flags);
289 0 : if (m_Type == CLUSTER)
290 0 : Serializer(serialize, "cluster tags", m_ClusterTags);
291 0 : if (m_Type == UNIT)
292 0 : serialize.NumberFixed_Unbounded("clearance", m_Clearance);
293 0 : }
294 :
295 0 : void Serialize(ISerializer& serialize) override
296 : {
297 0 : SerializeCommon(serialize);
298 0 : }
299 :
300 0 : void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) override
301 : {
302 0 : Init(paramNode);
303 :
304 0 : SerializeCommon(deserialize);
305 0 : }
306 :
307 0 : void HandleMessage(const CMessage& msg, bool UNUSED(global)) override
308 : {
309 0 : switch (msg.GetType())
310 : {
311 0 : case MT_PositionChanged:
312 : {
313 0 : if (!m_Active || m_IsDestroyed)
314 : break;
315 :
316 0 : const CMessagePositionChanged& data = static_cast<const CMessagePositionChanged&> (msg);
317 :
318 0 : if (!data.inWorld && !m_Tag.valid())
319 0 : break; // nothing needs to change
320 :
321 0 : CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
322 0 : if (!cmpObstructionManager)
323 0 : break; // error
324 :
325 0 : if (data.inWorld && m_Tag.valid())
326 : {
327 0 : cmpObstructionManager->MoveShape(m_Tag, data.x, data.z, data.a);
328 :
329 0 : if (m_Type == CLUSTER)
330 : {
331 0 : for (size_t i = 0; i < m_Shapes.size(); ++i)
332 : {
333 0 : Shape& b = m_Shapes[i];
334 0 : fixed s, c;
335 0 : sincos_approx(data.a, s, c);
336 0 : 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);
337 : }
338 : }
339 : }
340 0 : else if (data.inWorld && !m_Tag.valid())
341 : {
342 : // Need to create a new pathfinder shape:
343 0 : if (m_Type == STATIC)
344 0 : m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(),
345 0 : data.x, data.z, data.a, m_Size0, m_Size1, m_Flags, m_ControlGroup, m_ControlGroup2);
346 0 : else if (m_Type == UNIT)
347 0 : m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(),
348 0 : data.x, data.z, m_Clearance, (flags_t)(m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0)), m_ControlGroup);
349 : else
350 0 : AddClusterShapes(data.x, data.x, data.a);
351 : }
352 0 : else if (!data.inWorld && m_Tag.valid())
353 : {
354 0 : cmpObstructionManager->RemoveShape(m_Tag);
355 0 : m_Tag = tag_t();
356 0 : if(m_Type == CLUSTER)
357 0 : RemoveClusterShapes();
358 : }
359 0 : break;
360 : }
361 0 : case MT_Destroy:
362 : {
363 : // Mark the obstruction as destroyed to prevent reactivating it after this point
364 0 : m_IsDestroyed = true;
365 0 : m_Active = false;
366 :
367 0 : if (m_Tag.valid())
368 : {
369 0 : CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
370 0 : if (!cmpObstructionManager)
371 0 : break; // error
372 :
373 0 : cmpObstructionManager->RemoveShape(m_Tag);
374 0 : m_Tag = tag_t();
375 0 : if(m_Type == CLUSTER)
376 0 : RemoveClusterShapes();
377 : }
378 0 : break;
379 : }
380 : }
381 0 : }
382 :
383 0 : void SetActive(bool active) override
384 : {
385 0 : if (active && !m_Active && !m_IsDestroyed)
386 : {
387 0 : m_Active = true;
388 :
389 : // Construct the obstruction shape
390 :
391 0 : CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
392 0 : if (!cmpObstructionManager)
393 0 : return; // error
394 :
395 0 : CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
396 0 : if (!cmpPosition)
397 0 : return; // error
398 :
399 0 : if (!cmpPosition->IsInWorld())
400 0 : return; // don't need an obstruction
401 :
402 : // TODO: code duplication from message handlers
403 0 : CFixedVector2D pos = cmpPosition->GetPosition2D();
404 0 : if (m_Type == STATIC)
405 0 : m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(),
406 0 : pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, m_Flags, m_ControlGroup, m_ControlGroup2);
407 0 : else if (m_Type == UNIT)
408 0 : m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(),
409 0 : pos.X, pos.Y, m_Clearance, (flags_t)(m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0)), m_ControlGroup);
410 : else
411 0 : AddClusterShapes(pos.X, pos.Y, cmpPosition->GetRotation().Y);
412 :
413 : // Used by UnitMotion to activate/deactivate pushing
414 0 : if (m_TemplateFlags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT)
415 : {
416 0 : CMessageMovementObstructionChanged msg;
417 0 : GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
418 0 : }
419 : }
420 0 : else if (!active && m_Active)
421 : {
422 0 : m_Active = false;
423 :
424 : // Delete the obstruction shape
425 :
426 : // TODO: code duplication from message handlers
427 0 : if (m_Tag.valid())
428 : {
429 0 : CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
430 0 : if (!cmpObstructionManager)
431 0 : return; // error
432 :
433 0 : cmpObstructionManager->RemoveShape(m_Tag);
434 0 : m_Tag = tag_t();
435 0 : if (m_Type == CLUSTER)
436 0 : RemoveClusterShapes();
437 : }
438 :
439 : // Used by UnitMotion to activate/deactivate pushing
440 0 : if (m_TemplateFlags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT)
441 : {
442 0 : CMessageMovementObstructionChanged msg;
443 0 : GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
444 : }
445 : }
446 : // else we didn't change the active status
447 : }
448 :
449 0 : void SetDisableBlockMovementPathfinding(bool movementDisabled, bool pathfindingDisabled, int32_t shape) override
450 : {
451 0 : flags_t *flags = NULL;
452 0 : if (shape == -1)
453 0 : flags = &m_Flags;
454 0 : else if (m_Type == CLUSTER && shape < (int32_t)m_Shapes.size())
455 0 : flags = &m_Shapes[shape].flags;
456 : else
457 0 : return; // error
458 :
459 : // Remove the blocking / pathfinding flags or
460 : // Add the blocking / pathfinding flags if the template had enabled them
461 0 : if (movementDisabled)
462 0 : *flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_MOVEMENT);
463 : else
464 0 : *flags |= (flags_t)(m_TemplateFlags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT);
465 0 : if (pathfindingDisabled)
466 0 : *flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_PATHFINDING);
467 : else
468 0 : *flags |= (flags_t)(m_TemplateFlags & ICmpObstructionManager::FLAG_BLOCK_PATHFINDING);
469 :
470 : // Reset the shape with the new flags (kind of inefficiently - we
471 : // should have a ICmpObstructionManager::SetFlags function or something)
472 0 : if (m_Active)
473 : {
474 0 : SetActive(false);
475 0 : SetActive(true);
476 : }
477 : }
478 :
479 0 : bool GetBlockMovementFlag(bool templateOnly) const override
480 : {
481 0 : return m_Active && ((templateOnly ? m_TemplateFlags : m_Flags) & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT) != 0;
482 : }
483 :
484 0 : EObstructionType GetObstructionType() const override
485 : {
486 0 : return m_Type;
487 : }
488 :
489 0 : ICmpObstructionManager::tag_t GetObstruction() const override
490 : {
491 0 : return m_Tag;
492 : }
493 :
494 0 : bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare& out) const override
495 : {
496 0 : return GetObstructionSquare(out, true);
497 : }
498 :
499 0 : bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare& out) const override
500 : {
501 0 : return GetObstructionSquare(out, false);
502 : }
503 :
504 0 : virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare& out, bool previousPosition) const
505 : {
506 0 : CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
507 0 : if (!cmpPosition)
508 0 : return false; // error
509 :
510 0 : CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
511 0 : if (!cmpObstructionManager)
512 0 : return false; // error
513 :
514 0 : if (!cmpPosition->IsInWorld())
515 0 : return false; // no obstruction square
516 :
517 0 : CFixedVector2D pos;
518 0 : if (previousPosition)
519 0 : pos = cmpPosition->GetPreviousPosition2D();
520 : else
521 0 : pos = cmpPosition->GetPosition2D();
522 0 : if (m_Type == UNIT)
523 0 : out = cmpObstructionManager->GetUnitShapeObstruction(pos.X, pos.Y, m_Clearance);
524 : else
525 0 : out = cmpObstructionManager->GetStaticShapeObstruction(pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1);
526 0 : return true;
527 : }
528 :
529 0 : entity_pos_t GetSize() const override
530 : {
531 0 : if (m_Type == UNIT)
532 0 : return m_Clearance;
533 : else
534 0 : return CFixedVector2D(m_Size0 / 2, m_Size1 / 2).Length();
535 : }
536 :
537 0 : CFixedVector2D GetStaticSize() const override
538 : {
539 0 : return m_Type == STATIC ? CFixedVector2D(m_Size0, m_Size1) : CFixedVector2D();
540 : }
541 :
542 0 : void SetUnitClearance(const entity_pos_t& clearance) override
543 : {
544 : // This doesn't send a MovementObstructionChanged message
545 : // because it's a just a workaround init order, and used in UnitMotion directly.
546 0 : if (m_Type == UNIT)
547 0 : m_Clearance = clearance;
548 0 : }
549 :
550 0 : bool IsControlPersistent() const override
551 : {
552 0 : return m_ControlPersist;
553 : }
554 :
555 0 : bool CheckShorePlacement() const override
556 : {
557 0 : ICmpObstructionManager::ObstructionSquare s;
558 0 : if (!GetObstructionSquare(s))
559 0 : return false;
560 :
561 0 : CFixedVector2D front = CFixedVector2D(s.x, s.z) + s.v.Multiply(s.hh);
562 0 : CFixedVector2D back = CFixedVector2D(s.x, s.z) - s.v.Multiply(s.hh);
563 :
564 0 : CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
565 0 : CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity());
566 0 : if (!cmpTerrain || !cmpWaterManager)
567 0 : return false;
568 :
569 : // Keep these constants in agreement with the pathfinder.
570 0 : return cmpWaterManager->GetWaterLevel(front.X, front.Y) - cmpTerrain->GetGroundLevel(front.X, front.Y) > fixed::FromInt(1) &&
571 0 : cmpWaterManager->GetWaterLevel( back.X, back.Y) - cmpTerrain->GetGroundLevel( back.X, back.Y) < fixed::FromInt(2);
572 : }
573 :
574 0 : EFoundationCheck CheckFoundation(const std::string& className) const override
575 : {
576 0 : return CheckFoundation(className, false);
577 : }
578 :
579 0 : EFoundationCheck CheckFoundation(const std::string& className, bool onlyCenterPoint) const override
580 : {
581 0 : CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
582 0 : if (!cmpPosition)
583 0 : return FOUNDATION_CHECK_FAIL_ERROR; // error
584 :
585 0 : if (!cmpPosition->IsInWorld())
586 0 : return FOUNDATION_CHECK_FAIL_NO_OBSTRUCTION; // no obstruction
587 :
588 0 : CFixedVector2D pos = cmpPosition->GetPosition2D();
589 :
590 0 : CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
591 0 : if (!cmpPathfinder)
592 0 : return FOUNDATION_CHECK_FAIL_ERROR; // error
593 :
594 : // required precondition to use SkipControlGroupsRequireFlagObstructionFilter
595 0 : if (m_ControlGroup == INVALID_ENTITY)
596 : {
597 0 : LOGERROR("[CmpObstruction] Cannot test for foundation obstructions; primary control group must be valid");
598 0 : return FOUNDATION_CHECK_FAIL_ERROR;
599 : }
600 :
601 : // Get passability class
602 0 : pass_class_t passClass = cmpPathfinder->GetPassabilityClass(className);
603 :
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 0 : SkipControlGroupsRequireFlagObstructionFilter filter(m_ControlGroup, m_ControlGroup2,
608 0 : ICmpObstructionManager::FLAG_BLOCK_FOUNDATION);
609 :
610 0 : if (m_Type == UNIT)
611 0 : return cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, m_Clearance, passClass, onlyCenterPoint);
612 : else
613 0 : return cmpPathfinder->CheckBuildingPlacement(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, GetEntityId(), passClass, onlyCenterPoint);
614 : }
615 :
616 0 : bool CheckDuplicateFoundation() const override
617 : {
618 0 : CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
619 0 : if (!cmpPosition)
620 0 : return false; // error
621 :
622 0 : if (!cmpPosition->IsInWorld())
623 0 : return false; // no obstruction
624 :
625 0 : CFixedVector2D pos = cmpPosition->GetPosition2D();
626 :
627 0 : CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
628 0 : if (!cmpObstructionManager)
629 0 : return false; // error
630 :
631 : // required precondition to use SkipControlGroupsRequireFlagObstructionFilter
632 0 : if (m_ControlGroup == INVALID_ENTITY)
633 : {
634 0 : LOGERROR("[CmpObstruction] Cannot test for foundation obstructions; primary control group must be valid");
635 0 : return false;
636 : }
637 :
638 : // Ignore collisions with entities unless they block foundations and match both control groups.
639 0 : SkipTagRequireControlGroupsAndFlagObstructionFilter filter(m_Tag, m_ControlGroup, m_ControlGroup2,
640 0 : ICmpObstructionManager::FLAG_BLOCK_FOUNDATION);
641 :
642 0 : if (m_Type == UNIT)
643 0 : return !cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Clearance, NULL);
644 : else
645 0 : return !cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, NULL );
646 : }
647 :
648 0 : std::vector<entity_id_t> GetEntitiesByFlags(flags_t flags) const override
649 : {
650 0 : std::vector<entity_id_t> ret;
651 :
652 0 : CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
653 0 : if (!cmpObstructionManager)
654 0 : return ret; // error
655 :
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 0 : SkipControlGroupsRequireFlagObstructionFilter filter(false, m_ControlGroup, m_ControlGroup2, flags);
660 :
661 0 : ICmpObstructionManager::ObstructionSquare square;
662 0 : if (!GetObstructionSquare(square))
663 0 : return ret; // error
664 :
665 0 : cmpObstructionManager->GetUnitsOnObstruction(square, ret, filter, false);
666 0 : cmpObstructionManager->GetStaticObstructionsOnObstruction(square, ret, filter);
667 :
668 0 : return ret;
669 : }
670 :
671 0 : std::vector<entity_id_t> GetEntitiesBlockingMovement() const override
672 : {
673 0 : return GetEntitiesByFlags(ICmpObstructionManager::FLAG_BLOCK_MOVEMENT);
674 : }
675 :
676 0 : std::vector<entity_id_t> GetEntitiesBlockingConstruction() const override
677 : {
678 0 : return GetEntitiesByFlags(ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION);
679 : }
680 :
681 0 : std::vector<entity_id_t> GetEntitiesDeletedUponConstruction() const override
682 : {
683 0 : return GetEntitiesByFlags(ICmpObstructionManager::FLAG_DELETE_UPON_CONSTRUCTION);
684 : }
685 :
686 0 : void SetMovingFlag(bool enabled) override
687 : {
688 0 : m_Moving = enabled;
689 :
690 0 : if (m_Tag.valid() && m_Type == UNIT)
691 : {
692 0 : CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
693 0 : if (cmpObstructionManager)
694 0 : cmpObstructionManager->SetUnitMovingFlag(m_Tag, m_Moving);
695 : }
696 0 : }
697 :
698 0 : void SetControlGroup(entity_id_t group) override
699 : {
700 0 : m_ControlGroup = group;
701 0 : UpdateControlGroups();
702 0 : }
703 :
704 0 : void SetControlGroup2(entity_id_t group2) override
705 : {
706 0 : m_ControlGroup2 = group2;
707 0 : UpdateControlGroups();
708 0 : }
709 :
710 0 : entity_id_t GetControlGroup() const override
711 : {
712 0 : return m_ControlGroup;
713 : }
714 :
715 0 : entity_id_t GetControlGroup2() const override
716 : {
717 0 : return m_ControlGroup2;
718 : }
719 :
720 0 : void UpdateControlGroups()
721 : {
722 0 : if (m_Tag.valid())
723 : {
724 0 : CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
725 0 : if (cmpObstructionManager)
726 : {
727 0 : if (m_Type == UNIT)
728 : {
729 0 : cmpObstructionManager->SetUnitControlGroup(m_Tag, m_ControlGroup);
730 : }
731 0 : else if (m_Type == STATIC)
732 : {
733 0 : cmpObstructionManager->SetStaticControlGroup(m_Tag, m_ControlGroup, m_ControlGroup2);
734 : }
735 : else
736 : {
737 0 : cmpObstructionManager->SetStaticControlGroup(m_Tag, m_ControlGroup, m_ControlGroup2);
738 0 : for (size_t i = 0; i < m_ClusterTags.size(); ++i)
739 : {
740 0 : cmpObstructionManager->SetStaticControlGroup(m_ClusterTags[i], m_ControlGroup, m_ControlGroup2);
741 : }
742 : }
743 : }
744 : }
745 0 : }
746 :
747 0 : void ResolveFoundationCollisions() const override
748 : {
749 0 : if (m_Type == UNIT)
750 0 : return;
751 :
752 0 : CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
753 0 : if (!cmpObstructionManager)
754 0 : return;
755 :
756 0 : CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
757 0 : if (!cmpPosition)
758 0 : return; // error
759 :
760 0 : if (!cmpPosition->IsInWorld())
761 0 : return; // no obstruction
762 :
763 0 : CFixedVector2D pos = cmpPosition->GetPosition2D();
764 :
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 0 : SkipControlGroupsRequireFlagObstructionFilter filter(m_ControlGroup, m_ControlGroup2,
769 0 : ICmpObstructionManager::FLAG_BLOCK_FOUNDATION);
770 :
771 0 : std::vector<entity_id_t> collisions;
772 0 : if (cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &collisions))
773 : {
774 0 : std::vector<entity_id_t> persistentEnts, normalEnts;
775 :
776 0 : if (m_ControlPersist)
777 0 : persistentEnts.push_back(m_ControlGroup);
778 : else
779 0 : normalEnts.push_back(GetEntityId());
780 :
781 0 : for (std::vector<entity_id_t>::iterator it = collisions.begin(); it != collisions.end(); ++it)
782 : {
783 0 : entity_id_t ent = *it;
784 0 : if (ent == INVALID_ENTITY)
785 0 : continue;
786 :
787 0 : CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), ent);
788 0 : if (!cmpObstruction->IsControlPersistent())
789 0 : normalEnts.push_back(ent);
790 : else
791 0 : persistentEnts.push_back(cmpObstruction->GetControlGroup());
792 : }
793 :
794 : // The collision can't be resolved without usable persistent control groups.
795 0 : if (persistentEnts.empty())
796 0 : return;
797 :
798 : // Attempt to replace colliding entities' control groups with a persistent one.
799 0 : for (const entity_id_t normalEnt : normalEnts)
800 : {
801 0 : CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), normalEnt);
802 0 : for (const entity_id_t persistent : normalEnts)
803 : {
804 0 : entity_id_t group = cmpObstruction->GetControlGroup();
805 :
806 : // Only clobber 'default' control groups.
807 0 : if (group == normalEnt)
808 0 : cmpObstruction->SetControlGroup(persistent);
809 0 : else if (cmpObstruction->GetControlGroup2() == INVALID_ENTITY && group != persistent)
810 0 : cmpObstruction->SetControlGroup2(persistent);
811 : }
812 : }
813 : }
814 : }
815 : protected:
816 :
817 0 : inline void AddClusterShapes(entity_pos_t x, entity_pos_t z, entity_angle_t a)
818 : {
819 0 : CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
820 0 : if (!cmpObstructionManager)
821 0 : return; // error
822 :
823 0 : flags_t flags = m_Flags;
824 : // Disable block movement and block pathfinding for the obstruction shape
825 0 : flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_MOVEMENT);
826 0 : flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_PATHFINDING);
827 :
828 0 : m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(),
829 0 : x, z, a, m_Size0, m_Size1, flags, m_ControlGroup, m_ControlGroup2);
830 :
831 0 : fixed s, c;
832 0 : sincos_approx(a, s, c);
833 :
834 0 : for (size_t i = 0; i < m_Shapes.size(); ++i)
835 : {
836 0 : Shape& b = m_Shapes[i];
837 0 : tag_t tag = cmpObstructionManager->AddStaticShape(GetEntityId(),
838 0 : 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 0 : m_ClusterTags.push_back(tag);
840 : }
841 : }
842 :
843 0 : inline void RemoveClusterShapes()
844 : {
845 0 : CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
846 0 : if (!cmpObstructionManager)
847 0 : return; // error
848 :
849 0 : for (size_t i = 0; i < m_ClusterTags.size(); ++i)
850 : {
851 0 : if (m_ClusterTags[i].valid())
852 : {
853 0 : cmpObstructionManager->RemoveShape(m_ClusterTags[i]);
854 : }
855 : }
856 0 : m_ClusterTags.clear();
857 : }
858 :
859 : };
860 :
861 119 : REGISTER_COMPONENT_TYPE(Obstruction)
|