Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
CCmpRangeManager.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2013 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 
21 #include "ICmpRangeManager.h"
22 
23 #include "ICmpTerrain.h"
32 
33 #include "graphics/Overlay.h"
34 #include "graphics/Terrain.h"
35 #include "lib/timer.h"
36 #include "maths/FixedVector2D.h"
37 #include "ps/CLogger.h"
38 #include "ps/Overlay.h"
39 #include "ps/Profile.h"
40 #include "renderer/Scene.h"
41 #include "lib/ps_stl.h"
42 
43 
44 #define DEBUG_RANGE_MANAGER_BOUNDS 0
45 
46 /**
47  * Representation of a range query.
48  */
49 struct Query
50 {
51  bool enabled;
52  bool parabolic;
53  CEntityHandle source; // TODO: this could crash if an entity is destroyed while a Query is still referencing it
59  std::vector<entity_id_t> lastMatch;
61 };
62 
63 /**
64  * Convert an owner ID (-1 = unowned, 0 = gaia, 1..30 = players)
65  * into a 32-bit mask for quick set-membership tests.
66  */
67 static inline u32 CalcOwnerMask(player_id_t owner)
68 {
69  if (owner >= -1 && owner < 31)
70  return 1 << (1+owner);
71  else
72  return 0; // owner was invalid
73 }
74 
75 /**
76  * Returns LOS mask for given player.
77  */
78 static inline u32 CalcPlayerLosMask(player_id_t player)
79 {
80  if (player > 0 && player <= 16)
81  return ICmpRangeManager::LOS_MASK << (2*(player-1));
82  return 0;
83 }
84 
85 /**
86  * Returns shared LOS mask for given list of players.
87  */
88 static u32 CalcSharedLosMask(std::vector<player_id_t> players)
89 {
90  u32 playerMask = 0;
91  for (size_t i = 0; i < players.size(); i++)
92  playerMask |= CalcPlayerLosMask(players[i]);
93 
94  return playerMask;
95 }
96 
97 /**
98  * Checks whether v is in a parabolic range of (0,0,0)
99  * The highest point of the paraboloid is (0,range/2,0)
100  * and the circle of distance 'range' around (0,0,0) on height y=0 is part of the paraboloid
101  *
102  * Avoids sqrting and overflowing.
103  */
104 static bool InParabolicRange(CFixedVector3D v, fixed range)
105 {
106  i32 x = v.X.GetInternalValue(); // abs(x) <= 2^31
107  i32 z = v.Z.GetInternalValue();
108  u64 xx = (u64)FIXED_MUL_I64_I32_I32(x, x); // xx <= 2^62
109  u64 zz = (u64)FIXED_MUL_I64_I32_I32(z, z);
110  i64 d2 = (xx + zz) >> 1; // d2 <= 2^62 (no overflow)
111 
112  i32 y = v.Y.GetInternalValue();
113  i32 c = range.GetInternalValue();
114  i32 c_2 = c >> 1;
115 
116  i64 c2 = FIXED_MUL_I64_I32_I32(c_2 - y, c);
117 
118  if (d2 <= c2)
119  return true;
120 
121  return false;
122 }
123 
125 {
129  std::vector<entity_pos_t> outline;
130 };
131 
132 static std::map<entity_id_t, EntityParabolicRangeOutline> ParabolicRangesOutlines;
133 
134 /**
135  * Representation of an entity, with the data needed for queries.
136  */
138 {
139  EntityData() : retainInFog(0), owner(-1), inWorld(0), flags(1) { }
142  u8 retainInFog; // boolean
144  u8 inWorld; // boolean
145  u8 flags; // See GetEntityFlagMask
146 };
147 
148 cassert(sizeof(EntityData) == 16);
149 
150 /**
151  * Serialization helper template for Query
152  */
154 {
155  template<typename S>
156  void Common(S& serialize, const char* UNUSED(name), Query& value)
157  {
158  serialize.Bool("enabled", value.enabled);
159  serialize.Bool("parabolic",value.parabolic);
160  serialize.NumberFixed_Unbounded("min range", value.minRange);
161  serialize.NumberFixed_Unbounded("max range", value.maxRange);
162  serialize.NumberFixed_Unbounded("elevation bonus", value.elevationBonus);
163  serialize.NumberU32_Unbounded("owners mask", value.ownersMask);
164  serialize.NumberI32_Unbounded("interface", value.interface);
165  SerializeVector<SerializeU32_Unbounded>()(serialize, "last match", value.lastMatch);
166  serialize.NumberU8_Unbounded("flagsMask", value.flagsMask);
167  }
168 
169  void operator()(ISerializer& serialize, const char* name, Query& value, const CSimContext& UNUSED(context))
170  {
171  Common(serialize, name, value);
172 
173  uint32_t id = value.source.GetId();
174  serialize.NumberU32_Unbounded("source", id);
175  }
176 
177  void operator()(IDeserializer& deserialize, const char* name, Query& value, const CSimContext& context)
178  {
179  Common(deserialize, name, value);
180 
181  uint32_t id;
182  deserialize.NumberU32_Unbounded("source", id);
183  value.source = context.GetComponentManager().LookupEntityHandle(id, true);
184  // the referenced entity might not have been deserialized yet,
185  // so tell LookupEntityHandle to allocate the handle if necessary
186  }
187 };
188 
189 /**
190  * Serialization helper template for EntityData
191  */
193 {
194  template<typename S>
195  void operator()(S& serialize, const char* UNUSED(name), EntityData& value)
196  {
197  serialize.NumberFixed_Unbounded("x", value.x);
198  serialize.NumberFixed_Unbounded("z", value.z);
199  serialize.NumberFixed_Unbounded("vision", value.visionRange);
200  serialize.NumberU8("retain in fog", value.retainInFog, 0, 1);
201  serialize.NumberI8_Unbounded("owner", value.owner);
202  serialize.NumberU8("in world", value.inWorld, 0, 1);
203  serialize.NumberU8_Unbounded("flags", value.flags);
204  }
205 };
206 
207 
208 /**
209  * Functor for sorting entities by distance from a source point.
210  * It must only be passed entities that are in 'entities'
211  * and are currently in the world.
212  */
214 {
216  m_EntityData(entities), m_Source(source)
217  {
218  }
219 
221  {
222  const EntityData& da = m_EntityData.find(a)->second;
223  const EntityData& db = m_EntityData.find(b)->second;
224  CFixedVector2D vecA = CFixedVector2D(da.x, da.z) - m_Source;
225  CFixedVector2D vecB = CFixedVector2D(db.x, db.z) - m_Source;
226  return (vecA.CompareLength(vecB) < 0);
227  }
228 
231 
232 private:
234 };
235 
236 /**
237  * Range manager implementation.
238  * Maintains a list of all entities (and their positions and owners), which is used for
239  * queries.
240  *
241  * LOS implementation is based on the model described in GPG2.
242  * (TODO: would be nice to make it cleverer, so e.g. mountains and walls
243  * can block vision)
244  */
246 {
247 public:
248  static void ClassInit(CComponentManager& componentManager)
249  {
250  componentManager.SubscribeGloballyToMessageType(MT_Create);
253  componentManager.SubscribeGloballyToMessageType(MT_Destroy);
255 
256  componentManager.SubscribeToMessageType(MT_Update);
257 
258  componentManager.SubscribeToMessageType(MT_RenderSubmit); // for debug overlays
259  }
260 
262 
266 
267  // World bounds (entities are expected to be within this range)
272 
273  // Range query state:
274  tag_t m_QueryNext; // next allocated id
275  std::map<tag_t, Query> m_Queries;
277 
278  SpatialSubdivision m_Subdivision; // spatial index of m_EntityData
279 
280  // LOS state:
281 
282  std::map<player_id_t, bool> m_LosRevealAll;
286 
287  // Counts of units seeing vertex, per vertex, per player (starting with player 0).
288  // Use u16 to avoid overflows when we have very large (but not infeasibly large) numbers
289  // of units in a very small area.
290  // (Note we use vertexes, not tiles, to better match the renderer.)
291  // Lazily constructed when it's needed, to save memory in smaller games.
292  std::vector<std::vector<u16> > m_LosPlayerCounts;
293 
294  // 2-bit ELosState per player, starting with player 1 (not 0!) up to player MAX_LOS_PLAYER_ID (inclusive)
295  std::vector<u32> m_LosState;
296  static const player_id_t MAX_LOS_PLAYER_ID = 16;
297 
298  // Special static visibility data for the "reveal whole map" mode
299  // (TODO: this is usually a waste of memory)
300  std::vector<u32> m_LosStateRevealed;
301 
302  // Shared LOS masks, one per player.
303  std::map<player_id_t, u32> m_SharedLosMasks;
304 
305  // Cache explored vertices per player (not serialized)
307  std::vector<u32> m_ExploredVertices;
308 
309  static std::string GetSchema()
310  {
311  return "<a:component type='system'/><empty/>";
312  }
313 
314  virtual void Init(const CParamNode& UNUSED(paramNode))
315  {
316  m_QueryNext = 1;
317 
318  m_DebugOverlayEnabled = false;
319  m_DebugOverlayDirty = true;
320 
322 
323  // Initialise with bogus values (these will get replaced when
324  // SetBounds is called)
326 
327  // The whole map should be visible to Gaia by default, else e.g. animals
328  // will get confused when trying to run from enemies
329  m_LosRevealAll[0] = true;
330 
331  // This is not really an error condition, an entity recently created or destroyed
332  // might have an owner of INVALID_PLAYER
334 
335  m_LosCircular = false;
337 
339  }
340 
341  virtual void Deinit()
342  {
343  }
344 
345  template<typename S>
346  void SerializeCommon(S& serialize)
347  {
348  serialize.NumberFixed_Unbounded("world x0", m_WorldX0);
349  serialize.NumberFixed_Unbounded("world z0", m_WorldZ0);
350  serialize.NumberFixed_Unbounded("world x1", m_WorldX1);
351  serialize.NumberFixed_Unbounded("world z1", m_WorldZ1);
352 
353  serialize.NumberU32_Unbounded("query next", m_QueryNext);
355  SerializeEntityMap<SerializeEntityData>()(serialize, "entity data", m_EntityData);
356 
358  serialize.Bool("los circular", m_LosCircular);
359  serialize.NumberI32_Unbounded("terrain verts per side", m_TerrainVerticesPerSide);
360 
361  // We don't serialize m_Subdivision or m_LosPlayerCounts
362  // since they can be recomputed from the entity data when deserializing;
363  // m_LosState must be serialized since it depends on the history of exploration
364 
365  SerializeVector<SerializeU32_Unbounded>()(serialize, "los state", m_LosState);
366 
368  }
369 
370  virtual void Serialize(ISerializer& serialize)
371  {
372  SerializeCommon(serialize);
373  }
374 
375  virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
376  {
377  Init(paramNode);
378 
379  SerializeCommon(deserialize);
380 
381  // Reinitialise subdivisions and LOS data
382  ResetDerivedData(true);
383  }
384 
385  virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
386  {
387  switch (msg.GetType())
388  {
389  case MT_Create:
390  {
391  const CMessageCreate& msgData = static_cast<const CMessageCreate&> (msg);
392  entity_id_t ent = msgData.entity;
393 
394  // Ignore local entities - we shouldn't let them influence anything
395  if (ENTITY_IS_LOCAL(ent))
396  break;
397 
398  // Ignore non-positional entities
399  CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), ent);
400  if (!cmpPosition)
401  break;
402 
403  // The newly-created entity will have owner -1 and position out-of-world
404  // (any initialisation of those values will happen later), so we can just
405  // use the default-constructed EntityData here
406  EntityData entdata;
407 
408  // Store the LOS data, if any
409  CmpPtr<ICmpVision> cmpVision(GetSimContext(), ent);
410  if (cmpVision)
411  {
412  entdata.visionRange = cmpVision->GetRange();
413  entdata.retainInFog = (cmpVision->GetRetainInFog() ? 1 : 0);
414  }
415 
416  // Remember this entity
417  m_EntityData.insert(ent, entdata);
418  break;
419  }
420  case MT_PositionChanged:
421  {
422  const CMessagePositionChanged& msgData = static_cast<const CMessagePositionChanged&> (msg);
423  entity_id_t ent = msgData.entity;
424 
426 
427  // Ignore if we're not already tracking this entity
428  if (it == m_EntityData.end())
429  break;
430 
431  if (msgData.inWorld)
432  {
433  if (it->second.inWorld)
434  {
435  CFixedVector2D from(it->second.x, it->second.z);
436  CFixedVector2D to(msgData.x, msgData.z);
437  m_Subdivision.Move(ent, from, to);
438  LosMove(it->second.owner, it->second.visionRange, from, to);
439  }
440  else
441  {
442  CFixedVector2D to(msgData.x, msgData.z);
443  m_Subdivision.Add(ent, to);
444  LosAdd(it->second.owner, it->second.visionRange, to);
445  }
446 
447  it->second.inWorld = 1;
448  it->second.x = msgData.x;
449  it->second.z = msgData.z;
450  }
451  else
452  {
453  if (it->second.inWorld)
454  {
455  CFixedVector2D from(it->second.x, it->second.z);
456  m_Subdivision.Remove(ent, from);
457  LosRemove(it->second.owner, it->second.visionRange, from);
458  }
459 
460  it->second.inWorld = 0;
461  it->second.x = entity_pos_t::Zero();
462  it->second.z = entity_pos_t::Zero();
463  }
464 
465  break;
466  }
467  case MT_OwnershipChanged:
468  {
469  const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg);
470  entity_id_t ent = msgData.entity;
471 
473 
474  // Ignore if we're not already tracking this entity
475  if (it == m_EntityData.end())
476  break;
477 
478  if (it->second.inWorld)
479  {
480  CFixedVector2D pos(it->second.x, it->second.z);
481  LosRemove(it->second.owner, it->second.visionRange, pos);
482  LosAdd(msgData.to, it->second.visionRange, pos);
483  }
484 
485  ENSURE(-128 <= msgData.to && msgData.to <= 127);
486  it->second.owner = (i8)msgData.to;
487 
488  break;
489  }
490  case MT_Destroy:
491  {
492  const CMessageDestroy& msgData = static_cast<const CMessageDestroy&> (msg);
493  entity_id_t ent = msgData.entity;
494 
496 
497  // Ignore if we're not already tracking this entity
498  if (it == m_EntityData.end())
499  break;
500 
501  if (it->second.inWorld)
502  m_Subdivision.Remove(ent, CFixedVector2D(it->second.x, it->second.z));
503 
504  // This will be called after Ownership's OnDestroy, so ownership will be set
505  // to -1 already and we don't have to do a LosRemove here
506  ENSURE(it->second.owner == -1);
507 
508  m_EntityData.erase(it);
509 
510  break;
511  }
513  {
514  const CMessageVisionRangeChanged& msgData = static_cast<const CMessageVisionRangeChanged&> (msg);
515  entity_id_t ent = msgData.entity;
516 
518 
519  // Ignore if we're not already tracking this entity
520  if (it == m_EntityData.end())
521  break;
522 
523  CmpPtr<ICmpVision> cmpVision(GetSimContext(), ent);
524  if (!cmpVision)
525  break;
526 
527  entity_pos_t oldRange = it->second.visionRange;
528  entity_pos_t newRange = msgData.newRange;
529 
530  // If the range changed and the entity's in-world, we need to manually adjust it
531  // but if it's not in-world, we only need to set the new vision range
532  CFixedVector2D pos(it->second.x, it->second.z);
533  if (it->second.inWorld)
534  LosRemove(it->second.owner, oldRange, pos);
535 
536  it->second.visionRange = newRange;
537 
538  if (it->second.inWorld)
539  LosAdd(it->second.owner, newRange, pos);
540 
541  break;
542  }
543  case MT_Update:
544  {
545  m_DebugOverlayDirty = true;
548  break;
549  }
550  case MT_RenderSubmit:
551  {
552  const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
553  RenderSubmit(msgData.collector);
554  break;
555  }
556  }
557  }
558 
559  virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, ssize_t vertices)
560  {
561  m_WorldX0 = x0;
562  m_WorldZ0 = z0;
563  m_WorldX1 = x1;
564  m_WorldZ1 = z1;
565  m_TerrainVerticesPerSide = (i32)vertices;
566 
567  ResetDerivedData(false);
568  }
569 
570  virtual void Verify()
571  {
572  // Ignore if map not initialised yet
573  if (m_WorldX1.IsZero())
574  return;
575 
576  // Check that calling ResetDerivedData (i.e. recomputing all the state from scratch)
577  // does not affect the incrementally-computed state
578 
579  std::vector<std::vector<u16> > oldPlayerCounts = m_LosPlayerCounts;
580  std::vector<u32> oldStateRevealed = m_LosStateRevealed;
581  SpatialSubdivision oldSubdivision = m_Subdivision;
582 
583  ResetDerivedData(true);
584 
585  if (oldPlayerCounts != m_LosPlayerCounts)
586  {
587  for (size_t i = 0; i < oldPlayerCounts.size(); ++i)
588  {
589  debug_printf(L"%d: ", (int)i);
590  for (size_t j = 0; j < oldPlayerCounts[i].size(); ++j)
591  debug_printf(L"%d ", oldPlayerCounts[i][j]);
592  debug_printf(L"\n");
593  }
594  for (size_t i = 0; i < m_LosPlayerCounts.size(); ++i)
595  {
596  debug_printf(L"%d: ", (int)i);
597  for (size_t j = 0; j < m_LosPlayerCounts[i].size(); ++j)
598  debug_printf(L"%d ", m_LosPlayerCounts[i][j]);
599  debug_printf(L"\n");
600  }
601  debug_warn(L"inconsistent player counts");
602  }
603  if (oldStateRevealed != m_LosStateRevealed)
604  debug_warn(L"inconsistent revealed");
605  if (oldSubdivision != m_Subdivision)
606  debug_warn(L"inconsistent subdivs");
607  }
608 
609  // Reinitialise subdivisions and LOS data, based on entity data
610  void ResetDerivedData(bool skipLosState)
611  {
612  ENSURE(m_WorldX0.IsZero() && m_WorldZ0.IsZero()); // don't bother implementing non-zero offsets yet
614 
615  m_LosPlayerCounts.clear();
617  m_ExploredVertices.clear();
619  if (skipLosState)
620  {
621  // recalc current exploration stats.
622  for (i32 j = 0; j < m_TerrainVerticesPerSide; j++)
623  {
624  for (i32 i = 0; i < m_TerrainVerticesPerSide; i++)
625  {
626  if (!LosIsOffWorld(i, j))
627  {
628  for (u8 k = 1; k < MAX_LOS_PLAYER_ID+1; ++k)
629  m_ExploredVertices.at(k) += ((m_LosState[j*m_TerrainVerticesPerSide + i] & (LOS_EXPLORED << (2*(k-1)))) > 0);
630  }
631  }
632  }
633  }
634  else
635  {
636  m_LosState.clear();
638  }
639  m_LosStateRevealed.clear();
641 
643  {
644  if (it->second.inWorld)
645  LosAdd(it->second.owner, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z));
646  }
647 
649  for (ssize_t j = 0; j < m_TerrainVerticesPerSide; ++j)
650  for (ssize_t i = 0; i < m_TerrainVerticesPerSide; ++i)
651  {
652  if (LosIsOffWorld(i,j))
654  else
655  {
656  m_LosStateRevealed[i + j*m_TerrainVerticesPerSide] = 0xFFFFFFFFu;
658  }
659  }
660  }
661 
663  {
664  // Use 8x8 tile subdivisions
665  // (TODO: find the optimal number instead of blindly guessing)
667 
669  {
670  if (it->second.inWorld)
671  m_Subdivision.Add(it->first, CFixedVector2D(it->second.x, it->second.z));
672  }
673  }
674 
676  entity_pos_t minRange, entity_pos_t maxRange,
677  std::vector<int> owners, int requiredInterface, u8 flags)
678  {
679  tag_t id = m_QueryNext++;
680  m_Queries[id] = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flags);
681 
682  return id;
683  }
684 
686  entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus,
687  std::vector<int> owners, int requiredInterface, u8 flags)
688  {
689  tag_t id = m_QueryNext++;
690  m_Queries[id] = ConstructParabolicQuery(source, minRange, maxRange, elevationBonus, owners, requiredInterface, flags);
691 
692  return id;
693  }
694 
695  virtual void DestroyActiveQuery(tag_t tag)
696  {
697  if (m_Queries.find(tag) == m_Queries.end())
698  {
699  LOGERROR(L"CCmpRangeManager: DestroyActiveQuery called with invalid tag %u", tag);
700  return;
701  }
702 
703  m_Queries.erase(tag);
704  }
705 
706  virtual void EnableActiveQuery(tag_t tag)
707  {
708  std::map<tag_t, Query>::iterator it = m_Queries.find(tag);
709  if (it == m_Queries.end())
710  {
711  LOGERROR(L"CCmpRangeManager: EnableActiveQuery called with invalid tag %u", tag);
712  return;
713  }
714 
715  Query& q = it->second;
716  q.enabled = true;
717  }
718 
719  virtual void DisableActiveQuery(tag_t tag)
720  {
721  std::map<tag_t, Query>::iterator it = m_Queries.find(tag);
722  if (it == m_Queries.end())
723  {
724  LOGERROR(L"CCmpRangeManager: DisableActiveQuery called with invalid tag %u", tag);
725  return;
726  }
727 
728  Query& q = it->second;
729  q.enabled = false;
730  }
731 
732  virtual std::vector<entity_id_t> ExecuteQuery(entity_id_t source,
733  entity_pos_t minRange, entity_pos_t maxRange,
734  std::vector<int> owners, int requiredInterface)
735  {
736  PROFILE("ExecuteQuery");
737 
738  Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"));
739 
740  std::vector<entity_id_t> r;
741 
742  CmpPtr<ICmpPosition> cmpSourcePosition(q.source);
743  if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
744  {
745  // If the source doesn't have a position, then the result is just the empty list
746  return r;
747  }
748 
749  PerformQuery(q, r);
750 
751  // Return the list sorted by distance from the entity
752  CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
753  std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
754 
755  return r;
756  }
757 
758  virtual std::vector<entity_id_t> ResetActiveQuery(tag_t tag)
759  {
760  PROFILE("ResetActiveQuery");
761 
762  std::vector<entity_id_t> r;
763 
764  std::map<tag_t, Query>::iterator it = m_Queries.find(tag);
765  if (it == m_Queries.end())
766  {
767  LOGERROR(L"CCmpRangeManager: ResetActiveQuery called with invalid tag %u", tag);
768  return r;
769  }
770 
771  Query& q = it->second;
772  q.enabled = true;
773 
774  CmpPtr<ICmpPosition> cmpSourcePosition(q.source);
775  if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
776  {
777  // If the source doesn't have a position, then the result is just the empty list
778  q.lastMatch = r;
779  return r;
780  }
781 
782  PerformQuery(q, r);
783 
784  q.lastMatch = r;
785 
786  // Return the list sorted by distance from the entity
787  CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
788  std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
789 
790  return r;
791  }
792 
793  virtual std::vector<entity_id_t> GetEntitiesByPlayer(player_id_t player)
794  {
795  std::vector<entity_id_t> entities;
796 
797  u32 ownerMask = CalcOwnerMask(player);
798 
800  {
801  // Check owner and add to list if it matches
802  if (CalcOwnerMask(it->second.owner) & ownerMask)
803  entities.push_back(it->first);
804  }
805 
806  return entities;
807  }
808 
809  virtual void SetDebugOverlay(bool enabled)
810  {
811  m_DebugOverlayEnabled = enabled;
812  m_DebugOverlayDirty = true;
813  if (!enabled)
814  m_DebugOverlayLines.clear();
815  }
816 
817  /**
818  * Update all currently-enabled active queries.
819  */
821  {
822  PROFILE3("ExecuteActiveQueries");
823 
824  // Store a queue of all messages before sending any, so we can assume
825  // no entities will move until we've finished checking all the ranges
826  std::vector<std::pair<entity_id_t, CMessageRangeUpdate> > messages;
827  std::vector<entity_id_t> results;
828  std::vector<entity_id_t> added;
829  std::vector<entity_id_t> removed;
830 
831  for (std::map<tag_t, Query>::iterator it = m_Queries.begin(); it != m_Queries.end(); ++it)
832  {
833  Query& query = it->second;
834 
835  if (!query.enabled)
836  continue;
837 
838  CmpPtr<ICmpPosition> cmpSourcePosition(query.source);
839  if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
840  continue;
841 
842  results.clear();
843  results.reserve(query.lastMatch.size());
844  PerformQuery(query, results);
845 
846  // Compute the changes vs the last match
847  added.clear();
848  removed.clear();
849  // Return the 'added' list sorted by distance from the entity
850  // (Don't bother sorting 'removed' because they might not even have positions or exist any more)
851  std::set_difference(results.begin(), results.end(), query.lastMatch.begin(), query.lastMatch.end(),
852  std::back_inserter(added));
853  std::set_difference(query.lastMatch.begin(), query.lastMatch.end(), results.begin(), results.end(),
854  std::back_inserter(removed));
855  if (added.empty() && removed.empty())
856  continue;
857 
858  std::stable_sort(added.begin(), added.end(), EntityDistanceOrdering(m_EntityData, cmpSourcePosition->GetPosition2D()));
859 
860  messages.resize(messages.size() + 1);
861  std::pair<entity_id_t, CMessageRangeUpdate>& back = messages.back();
862  back.first = query.source.GetId();
863  back.second.tag = it->first;
864  back.second.added.swap(added);
865  back.second.removed.swap(removed);
866  it->second.lastMatch.swap(results);
867  }
868 
870  for (size_t i = 0; i < messages.size(); ++i)
871  cmpMgr.PostMessage(messages[i].first, messages[i].second);
872  }
873 
874  /**
875  * Returns whether the given entity matches the given query (ignoring maxRange)
876  */
877  bool TestEntityQuery(const Query& q, entity_id_t id, const EntityData& entity)
878  {
879  // Quick filter to ignore entities with the wrong owner
880  if (!(CalcOwnerMask(entity.owner) & q.ownersMask))
881  return false;
882 
883  // Ignore entities not present in the world
884  if (!entity.inWorld)
885  return false;
886 
887  // Ignore entities that don't match the current flags
888  if (!(entity.flags & q.flagsMask))
889  return false;
890 
891  // Ignore self
892  if (id == q.source.GetId())
893  return false;
894 
895  // Ignore if it's missing the required interface
897  return false;
898 
899  return true;
900  }
901 
902  /**
903  * Returns a list of distinct entity IDs that match the given query, sorted by ID.
904  */
905  void PerformQuery(const Query& q, std::vector<entity_id_t>& r)
906  {
907  CmpPtr<ICmpPosition> cmpSourcePosition(q.source);
908  if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
909  return;
910  CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
911 
912  // Special case: range -1.0 means check all entities ignoring distance
913  if (q.maxRange == entity_pos_t::FromInt(-1))
914  {
916  {
917  if (!TestEntityQuery(q, it->first, it->second))
918  continue;
919 
920  r.push_back(it->first);
921  }
922  }
923  // Not the entire world, so check a parabolic range, or a regular range
924  else if (q.parabolic)
925  {
926  // elevationBonus is part of the 3D position, as the source is really that much heigher
927  CFixedVector3D pos3d = cmpSourcePosition->GetPosition()+
929  // Get a quick list of entities that are potentially in range, with a cutoff of 2*maxRange
930  SpatialQueryArray ents;
931  m_Subdivision.GetNear(ents, pos, q.maxRange*2);
932 
933  for (int i = 0; i < ents.size(); ++i)
934  {
936  ENSURE(it != m_EntityData.end());
937 
938  if (!TestEntityQuery(q, it->first, it->second))
939  continue;
940 
941  CmpPtr<ICmpPosition> cmpSecondPosition(GetSimContext(), ents[i]);
942  if (!cmpSecondPosition || !cmpSecondPosition->IsInWorld())
943  continue;
944  CFixedVector3D secondPosition = cmpSecondPosition->GetPosition();
945 
946  // Restrict based on precise distance
947  if (!InParabolicRange(
948  CFixedVector3D(it->second.x, secondPosition.Y, it->second.z)
949  - pos3d,
950  q.maxRange))
951  continue;
952 
953  if (!q.minRange.IsZero())
954  {
955  int distVsMin = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange);
956  if (distVsMin < 0)
957  continue;
958  }
959 
960  r.push_back(it->first);
961  }
962  }
963  // check a regular range (i.e. not the entire world, and not parabolic)
964  else
965  {
966  // Get a quick list of entities that are potentially in range
967  SpatialQueryArray ents;
968  m_Subdivision.GetNear(ents, pos, q.maxRange);
969 
970  for (int i = 0; i < ents.size(); ++i)
971  {
973  ENSURE(it != m_EntityData.end());
974 
975  if (!TestEntityQuery(q, it->first, it->second))
976  continue;
977 
978  // Restrict based on precise distance
979  int distVsMax = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.maxRange);
980  if (distVsMax > 0)
981  continue;
982 
983  if (!q.minRange.IsZero())
984  {
985  int distVsMin = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange);
986  if (distVsMin < 0)
987  continue;
988  }
989 
990  r.push_back(it->first);
991  }
992  }
993  }
994 
995 
997  {
999 
1000  pos.Y += elevationBonus;
1001  entity_pos_t orientation = rot.Y;
1002 
1003  entity_pos_t maxAngle = orientation + angle/2;
1004  entity_pos_t minAngle = orientation - angle/2;
1005 
1006  int numberOfSteps = 16;
1007 
1008  if (angle == entity_pos_t::Zero())
1009  numberOfSteps = 1;
1010 
1011  std::vector<entity_pos_t> coords = getParabolicRangeForm(pos, range, range*2, minAngle, maxAngle, numberOfSteps);
1012 
1013  entity_pos_t part = entity_pos_t::FromInt(numberOfSteps);
1014 
1015  for (int i = 0; i < numberOfSteps; i++)
1016  {
1017  r = r + CFixedVector2D(coords[2*i],coords[2*i+1]).Length() / part;
1018  }
1019 
1020  return r;
1021 
1022  }
1023 
1024  virtual std::vector<entity_pos_t> getParabolicRangeForm(CFixedVector3D pos, entity_pos_t maxRange, entity_pos_t cutoff, entity_pos_t minAngle, entity_pos_t maxAngle, int numberOfSteps)
1025  {
1026 
1027  // angle = 0 goes in the positive Z direction
1029 
1030  std::vector<entity_pos_t> r;
1031 
1032 
1033  CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
1034  CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity());
1035  entity_pos_t waterLevel = cmpWaterManager->GetWaterLevel(pos.X,pos.Z);
1036  entity_pos_t thisHeight = pos.Y > waterLevel ? pos.Y : waterLevel;
1037 
1038  if (cmpTerrain)
1039  {
1040  for (int i = 0; i < numberOfSteps; i++)
1041  {
1042  entity_pos_t angle = minAngle + (maxAngle - minAngle) / numberOfSteps * i;
1043  entity_pos_t sin;
1044  entity_pos_t cos;
1045  entity_pos_t minDistance = entity_pos_t::Zero();
1046  entity_pos_t maxDistance = cutoff;
1047  sincos_approx(angle,sin,cos);
1048 
1050  CFixedVector2D maxVector = CFixedVector2D(sin,cos).Multiply(cutoff);
1051  entity_pos_t targetHeight = cmpTerrain->GetGroundLevel(pos.X+maxVector.X,pos.Z+maxVector.Y);
1052  // use water level to display range on water
1053  targetHeight = targetHeight > waterLevel ? targetHeight : waterLevel;
1054 
1055  if (InParabolicRange(CFixedVector3D(maxVector.X,targetHeight-thisHeight,maxVector.Y),maxRange))
1056  {
1057  r.push_back(maxVector.X);
1058  r.push_back(maxVector.Y);
1059  continue;
1060  }
1061 
1062  // Loop until vectors come close enough
1063  while ((maxVector - minVector).CompareLength(precision) > 0)
1064  {
1065  // difference still bigger than precision, bisect to get smaller difference
1066  entity_pos_t newDistance = (minDistance+maxDistance)/entity_pos_t::FromInt(2);
1067 
1068  CFixedVector2D newVector = CFixedVector2D(sin,cos).Multiply(newDistance);
1069 
1070  // get the height of the ground
1071  targetHeight = cmpTerrain->GetGroundLevel(pos.X+newVector.X,pos.Z+newVector.Y);
1072  targetHeight = targetHeight > waterLevel ? targetHeight : waterLevel;
1073 
1074  if (InParabolicRange(CFixedVector3D(newVector.X,targetHeight-thisHeight,newVector.Y),maxRange))
1075  {
1076  // new vector is in parabolic range, so this is a new minVector
1077  minVector = newVector;
1078  minDistance = newDistance;
1079  }
1080  else
1081  {
1082  // new vector is out parabolic range, so this is a new maxVector
1083  maxVector = newVector;
1084  maxDistance = newDistance;
1085  }
1086 
1087  }
1088  r.push_back(maxVector.X);
1089  r.push_back(maxVector.Y);
1090 
1091  }
1092  r.push_back(r[0]);
1093  r.push_back(r[1]);
1094 
1095  }
1096  return r;
1097 
1098  }
1099 
1101  entity_pos_t minRange, entity_pos_t maxRange,
1102  const std::vector<int>& owners, int requiredInterface, u8 flagsMask)
1103  {
1104  // Min range must be non-negative
1105  if (minRange < entity_pos_t::Zero())
1106  LOGWARNING(L"CCmpRangeManager: Invalid min range %f in query for entity %u", minRange.ToDouble(), source);
1107 
1108  // Max range must be non-negative, or else -1
1109  if (maxRange < entity_pos_t::Zero() && maxRange != entity_pos_t::FromInt(-1))
1110  LOGWARNING(L"CCmpRangeManager: Invalid max range %f in query for entity %u", maxRange.ToDouble(), source);
1111 
1112  Query q;
1113  q.enabled = false;
1114  q.parabolic = false;
1116  q.minRange = minRange;
1117  q.maxRange = maxRange;
1119 
1120  q.ownersMask = 0;
1121  for (size_t i = 0; i < owners.size(); ++i)
1122  q.ownersMask |= CalcOwnerMask(owners[i]);
1123 
1124  q.interface = requiredInterface;
1125  q.flagsMask = flagsMask;
1126 
1127  return q;
1128  }
1129 
1131  entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus,
1132  const std::vector<int>& owners, int requiredInterface, u8 flagsMask)
1133  {
1134  Query q = ConstructQuery(source,minRange,maxRange,owners,requiredInterface,flagsMask);
1135  q.parabolic = true;
1136  q.elevationBonus = elevationBonus;
1137  return q;
1138  }
1139 
1140 
1141  void RenderSubmit(SceneCollector& collector)
1142  {
1143  if (!m_DebugOverlayEnabled)
1144  return;
1145  static CColor disabledRingColour(1, 0, 0, 1); // red
1146  static CColor enabledRingColour(0, 1, 0, 1); // green
1147  static CColor subdivColour(0, 0, 1, 1); // blue
1148  static CColor rayColour(1, 1, 0, 0.2f);
1149 
1150  if (m_DebugOverlayDirty)
1151  {
1152  m_DebugOverlayLines.clear();
1153 
1154  for (std::map<tag_t, Query>::iterator it = m_Queries.begin(); it != m_Queries.end(); ++it)
1155  {
1156  Query& q = it->second;
1157 
1158  CmpPtr<ICmpPosition> cmpSourcePosition(q.source);
1159  if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
1160  continue;
1161  CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
1162 
1163  // Draw the max range circle
1164  if (!q.parabolic)
1165  {
1166  m_DebugOverlayLines.push_back(SOverlayLine());
1167  m_DebugOverlayLines.back().m_Color = (q.enabled ? enabledRingColour : disabledRingColour);
1169  }
1170  else
1171  {
1172  // elevation bonus is part of the 3D position. As if the unit is really that much higher
1173  CFixedVector3D pos = cmpSourcePosition->GetPosition();
1174  pos.Y += q.elevationBonus;
1175 
1176  std::vector<entity_pos_t> coords;
1177 
1178  // Get the outline from cache if possible
1180  {
1182  if (e.position == pos && e.range == q.maxRange)
1183  {
1184  // outline is cached correctly, use it
1185  coords = e.outline;
1186  }
1187  else
1188  {
1189  // outline was cached, but important parameters changed
1190  // (position, elevation, range)
1191  // update it
1192  coords = getParabolicRangeForm(pos,q.maxRange,q.maxRange*2, entity_pos_t::Zero(), entity_pos_t::FromFloat(2.0f*3.14f),70);
1193  e.outline = coords;
1194  e.range = q.maxRange;
1195  e.position = pos;
1197  }
1198  }
1199  else
1200  {
1201  // outline wasn't cached (first time you enable the range overlay
1202  // or you created a new entiy)
1203  // cache a new outline
1204  coords = getParabolicRangeForm(pos,q.maxRange,q.maxRange*2, entity_pos_t::Zero(), entity_pos_t::FromFloat(2.0f*3.14f),70);
1206  e.source = q.source.GetId();
1207  e.range = q.maxRange;
1208  e.position = pos;
1209  e.outline = coords;
1211  }
1212 
1213  CColor thiscolor = q.enabled ? enabledRingColour : disabledRingColour;
1214 
1215  // draw the outline (piece by piece)
1216  for (size_t i = 3; i < coords.size(); i += 2)
1217  {
1218  std::vector<float> c;
1219  c.push_back((coords[i-3]+pos.X).ToFloat());
1220  c.push_back((coords[i-2]+pos.Z).ToFloat());
1221  c.push_back((coords[i-1]+pos.X).ToFloat());
1222  c.push_back((coords[i]+pos.Z).ToFloat());
1223  m_DebugOverlayLines.push_back(SOverlayLine());
1224  m_DebugOverlayLines.back().m_Color = thiscolor;
1226  }
1227  }
1228 
1229  // Draw the min range circle
1230  if (!q.minRange.IsZero())
1231  {
1233  }
1234 
1235  // Draw a ray from the source to each matched entity
1236  for (size_t i = 0; i < q.lastMatch.size(); ++i)
1237  {
1238  CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), q.lastMatch[i]);
1239  if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
1240  continue;
1241  CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
1242 
1243  std::vector<float> coords;
1244  coords.push_back(pos.X.ToFloat());
1245  coords.push_back(pos.Y.ToFloat());
1246  coords.push_back(targetPos.X.ToFloat());
1247  coords.push_back(targetPos.Y.ToFloat());
1248 
1249  m_DebugOverlayLines.push_back(SOverlayLine());
1250  m_DebugOverlayLines.back().m_Color = rayColour;
1252  }
1253  }
1254 
1255  // render subdivision grid
1256  float divSize = m_Subdivision.GetDivisionSize().ToFloat();
1257  int width = m_Subdivision.GetWidth();
1258  int height = m_Subdivision.GetHeight();
1259  for (int x = 0; x < width; ++x)
1260  {
1261  for (int y = 0; y < height; ++y)
1262  {
1263  m_DebugOverlayLines.push_back(SOverlayLine());
1264  m_DebugOverlayLines.back().m_Color = subdivColour;
1265 
1266  float xpos = x*divSize + divSize/2;
1267  float zpos = y*divSize + divSize/2;
1268  SimRender::ConstructSquareOnGround(GetSimContext(), xpos, zpos, divSize, divSize, 0.0f,
1269  m_DebugOverlayLines.back(), false, 1.0f);
1270  }
1271  }
1272 
1273  m_DebugOverlayDirty = false;
1274  }
1275 
1276  for (size_t i = 0; i < m_DebugOverlayLines.size(); ++i)
1277  collector.Submit(&m_DebugOverlayLines[i]);
1278  }
1279 
1280  virtual u8 GetEntityFlagMask(std::string identifier)
1281  {
1282  if (identifier == "normal")
1283  return 1;
1284  if (identifier == "injured")
1285  return 2;
1286 
1287  LOGWARNING(L"CCmpRangeManager: Invalid flag identifier %hs", identifier.c_str());
1288  return 0;
1289  }
1290 
1291  virtual void SetEntityFlag(entity_id_t ent, std::string identifier, bool value)
1292  {
1294 
1295  // We don't have this entity
1296  if (it == m_EntityData.end())
1297  return;
1298 
1299  u8 flag = GetEntityFlagMask(identifier);
1300 
1301  // We don't have a flag set
1302  if (flag == 0)
1303  {
1304  LOGWARNING(L"CCmpRangeManager: Invalid flag identifier %hs for entity %u", identifier.c_str(), ent);
1305  return;
1306  }
1307 
1308  if (value)
1309  it->second.flags |= flag;
1310  else
1311  it->second.flags &= ~flag;
1312  }
1313 
1314  // ****************************************************************
1315 
1316  // LOS implementation:
1317 
1319  {
1320  if (GetLosRevealAll(player))
1322  else
1324  }
1325 
1326  virtual ELosVisibility GetLosVisibility(CEntityHandle ent, player_id_t player, bool forceRetainInFog)
1327  {
1328  // (We can't use m_EntityData since this needs to handle LOCAL entities too)
1329 
1330  // Entities not with positions in the world are never visible
1331  if (ent.GetId() == INVALID_ENTITY)
1332  return VIS_HIDDEN;
1333  CmpPtr<ICmpPosition> cmpPosition(ent);
1334  if (!cmpPosition || !cmpPosition->IsInWorld())
1335  return VIS_HIDDEN;
1336 
1337  CFixedVector2D pos = cmpPosition->GetPosition2D();
1338 
1339  int i = (pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
1340  int j = (pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
1341 
1342  // Reveal flag makes all positioned entities visible
1343  if (GetLosRevealAll(player))
1344  {
1345  if (LosIsOffWorld(i, j))
1346  return VIS_HIDDEN;
1347  else
1348  return VIS_VISIBLE;
1349  }
1350 
1351  // Visible if within a visible region
1353 
1354  if (los.IsVisible(i, j))
1355  return VIS_VISIBLE;
1356 
1357  // Fogged if the 'retain in fog' flag is set, and in a non-visible explored region
1358  if (los.IsExplored(i, j))
1359  {
1360  CmpPtr<ICmpVision> cmpVision(ent);
1361  if (forceRetainInFog || (cmpVision && cmpVision->GetRetainInFog()))
1362  return VIS_FOGGED;
1363  }
1364 
1365  // Otherwise not visible
1366  return VIS_HIDDEN;
1367  }
1368 
1369  virtual ELosVisibility GetLosVisibility(entity_id_t ent, player_id_t player, bool forceRetainInFog)
1370  {
1372  return GetLosVisibility(handle, player, forceRetainInFog);
1373  }
1374 
1375 
1376  virtual void SetLosRevealAll(player_id_t player, bool enabled)
1377  {
1378  m_LosRevealAll[player] = enabled;
1379  }
1380 
1381  virtual bool GetLosRevealAll(player_id_t player)
1382  {
1383  std::map<player_id_t, bool>::const_iterator it;
1384 
1385  // Special player value can force reveal-all for every player
1386  it = m_LosRevealAll.find(-1);
1387  if (it != m_LosRevealAll.end() && it->second)
1388  return true;
1389 
1390  // Otherwise check the player-specific flag
1391  it = m_LosRevealAll.find(player);
1392  if (it != m_LosRevealAll.end() && it->second)
1393  return true;
1394 
1395  return false;
1396  }
1397 
1398  virtual void SetLosCircular(bool enabled)
1399  {
1400  m_LosCircular = enabled;
1401 
1402  ResetDerivedData(false);
1403  }
1404 
1405  virtual bool GetLosCircular()
1406  {
1407  return m_LosCircular;
1408  }
1409 
1410  virtual void SetSharedLos(player_id_t player, std::vector<player_id_t> players)
1411  {
1412  m_SharedLosMasks[player] = CalcSharedLosMask(players);
1413  }
1414 
1416  {
1417  std::map<player_id_t, u32>::const_iterator it = m_SharedLosMasks.find(player);
1418  ENSURE(it != m_SharedLosMasks.end());
1419 
1420  return m_SharedLosMasks[player];
1421  }
1422 
1424  {
1425  CmpPtr<ICmpTerritoryManager> cmpTerritoryManager(GetSystemEntity());
1426  if (!cmpTerritoryManager || !cmpTerritoryManager->NeedUpdate(&m_TerritoriesDirtyID))
1427  return;
1428 
1429  const Grid<u8>& grid = cmpTerritoryManager->GetTerritoryGrid();
1431 
1432  // For each tile, if it is owned by a valid player then update the LOS
1433  // for every vertex around that tile, to mark them as explored
1434 
1435  for (u16 j = 0; j < grid.m_H; ++j)
1436  {
1437  for (u16 i = 0; i < grid.m_W; ++i)
1438  {
1440  if (p > 0 && p <= MAX_LOS_PLAYER_ID)
1441  {
1442  u32 &explored = m_ExploredVertices.at(p);
1443  explored += !(m_LosState[i + j*m_TerrainVerticesPerSide] & (LOS_EXPLORED << (2*(p-1))));
1444  m_LosState[i + j*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
1445  explored += !(m_LosState[i+1 + j*m_TerrainVerticesPerSide] & (LOS_EXPLORED << (2*(p-1))));
1446  m_LosState[i+1 + j*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
1447  explored += !(m_LosState[i + (j+1)*m_TerrainVerticesPerSide] & (LOS_EXPLORED << (2*(p-1))));
1448  m_LosState[i + (j+1)*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
1449  explored += !(m_LosState[i+1 + (j+1)*m_TerrainVerticesPerSide] & (LOS_EXPLORED << (2*(p-1))));
1450  m_LosState[i+1 + (j+1)*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
1451  }
1452  }
1453  }
1454  }
1455 
1456  /**
1457  * Returns whether the given vertex is outside the normal bounds of the world
1458  * (i.e. outside the range of a circular map)
1459  */
1460  inline bool LosIsOffWorld(ssize_t i, ssize_t j)
1461  {
1462  // WARNING: CCmpObstructionManager::Rasterise needs to be kept in sync with this
1463  const ssize_t edgeSize = 3; // number of vertexes around the edge that will be off-world
1464 
1465  if (m_LosCircular)
1466  {
1467  // With a circular map, vertex is off-world if hypot(i - size/2, j - size/2) >= size/2:
1468 
1471 
1472  ssize_t r = m_TerrainVerticesPerSide/2 - edgeSize + 1;
1473  // subtract a bit from the radius to ensure nice
1474  // SoD blurring around the edges of the map
1475 
1476  return (dist2 >= r*r);
1477  }
1478  else
1479  {
1480  // With a square map, the outermost edge of the map should be off-world,
1481  // so the SoD texture blends out nicely
1482 
1483  return (i < edgeSize || j < edgeSize || i >= m_TerrainVerticesPerSide-edgeSize || j >= m_TerrainVerticesPerSide-edgeSize);
1484  }
1485  }
1486 
1487  /**
1488  * Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
1489  */
1490  inline void LosAddStripHelper(u8 owner, i32 i0, i32 i1, i32 j, u16* counts)
1491  {
1492  if (i1 < i0)
1493  return;
1494 
1495  i32 idx0 = j*m_TerrainVerticesPerSide + i0;
1496  i32 idx1 = j*m_TerrainVerticesPerSide + i1;
1497  u32 &explored = m_ExploredVertices.at(owner);
1498  for (i32 idx = idx0; idx <= idx1; ++idx)
1499  {
1500  // Increasing from zero to non-zero - move from unexplored/explored to visible+explored
1501  if (counts[idx] == 0)
1502  {
1503  i32 i = i0 + idx - idx0;
1504  if (!LosIsOffWorld(i, j))
1505  {
1506  explored += !(m_LosState[idx] & (LOS_EXPLORED << (2*(owner-1))));
1507  m_LosState[idx] |= ((LOS_VISIBLE | LOS_EXPLORED) << (2*(owner-1)));
1508  }
1509  }
1510 
1511  ASSERT(counts[idx] < 65535);
1512  counts[idx] = (u16)(counts[idx] + 1); // ignore overflow; the player should never have 64K units
1513  }
1514  }
1515 
1516  /**
1517  * Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
1518  */
1519  inline void LosRemoveStripHelper(u8 owner, i32 i0, i32 i1, i32 j, u16* counts)
1520  {
1521  if (i1 < i0)
1522  return;
1523 
1524  i32 idx0 = j*m_TerrainVerticesPerSide + i0;
1525  i32 idx1 = j*m_TerrainVerticesPerSide + i1;
1526  for (i32 idx = idx0; idx <= idx1; ++idx)
1527  {
1528  ASSERT(counts[idx] > 0);
1529  counts[idx] = (u16)(counts[idx] - 1);
1530 
1531  // Decreasing from non-zero to zero - move from visible+explored to explored
1532  if (counts[idx] == 0)
1533  {
1534  // (If LosIsOffWorld then this is a no-op, so don't bother doing the check)
1535  m_LosState[idx] &= ~(LOS_VISIBLE << (2*(owner-1)));
1536  }
1537  }
1538  }
1539 
1540  /**
1541  * Update the LOS state of tiles within a given circular range,
1542  * either adding or removing visibility depending on the template parameter.
1543  * Assumes owner is in the valid range.
1544  */
1545  template<bool adding>
1546  void LosUpdateHelper(u8 owner, entity_pos_t visionRange, CFixedVector2D pos)
1547  {
1548  if (m_TerrainVerticesPerSide == 0) // do nothing if not initialised yet
1549  return;
1550 
1551  PROFILE("LosUpdateHelper");
1552 
1553  std::vector<u16>& counts = m_LosPlayerCounts.at(owner);
1554 
1555  // Lazy initialisation of counts:
1556  if (counts.empty())
1558 
1559  u16* countsData = &counts[0];
1560 
1561  // Compute the circular region as a series of strips.
1562  // Rather than quantise pos to vertexes, we do more precise sub-tile computations
1563  // to get smoother behaviour as a unit moves rather than jumping a whole tile
1564  // at once.
1565  // To avoid the cost of sqrt when computing the outline of the circle,
1566  // we loop from the bottom to the top and estimate the width of the current
1567  // strip based on the previous strip, then adjust each end of the strip
1568  // inwards or outwards until it's the widest that still falls within the circle.
1569 
1570  // Compute top/bottom coordinates, and clamp to exclude the 1-tile border around the map
1571  // (so that we never render the sharp edge of the map)
1572  i32 j0 = ((pos.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity();
1573  i32 j1 = ((pos.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity();
1574  i32 j0clamp = std::max(j0, 1);
1575  i32 j1clamp = std::min(j1, m_TerrainVerticesPerSide-2);
1576 
1577  // Translate world coordinates into fractional tile-space coordinates
1578  entity_pos_t x = pos.X / (int)TERRAIN_TILE_SIZE;
1579  entity_pos_t y = pos.Y / (int)TERRAIN_TILE_SIZE;
1580  entity_pos_t r = visionRange / (int)TERRAIN_TILE_SIZE;
1581  entity_pos_t r2 = r.Square();
1582 
1583  // Compute the integers on either side of x
1584  i32 xfloor = (x - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
1585  i32 xceil = (x + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
1586 
1587  // Initialise the strip (i0, i1) to a rough guess
1588  i32 i0 = xfloor;
1589  i32 i1 = xceil;
1590 
1591  for (i32 j = j0clamp; j <= j1clamp; ++j)
1592  {
1593  // Adjust i0 and i1 to be the outermost values that don't exceed
1594  // the circle's radius (i.e. require dy^2 + dx^2 <= r^2).
1595  // When moving the points inwards, clamp them to xceil+1 or xfloor-1
1596  // so they don't accidentally shoot off in the wrong direction forever.
1597 
1598  entity_pos_t dy = entity_pos_t::FromInt(j) - y;
1599  entity_pos_t dy2 = dy.Square();
1600  while (dy2 + (entity_pos_t::FromInt(i0-1) - x).Square() <= r2)
1601  --i0;
1602  while (i0 < xceil && dy2 + (entity_pos_t::FromInt(i0) - x).Square() > r2)
1603  ++i0;
1604  while (dy2 + (entity_pos_t::FromInt(i1+1) - x).Square() <= r2)
1605  ++i1;
1606  while (i1 > xfloor && dy2 + (entity_pos_t::FromInt(i1) - x).Square() > r2)
1607  --i1;
1608 
1609 #if DEBUG_RANGE_MANAGER_BOUNDS
1610  if (i0 <= i1)
1611  {
1612  ENSURE(dy2 + (entity_pos_t::FromInt(i0) - x).Square() <= r2);
1613  ENSURE(dy2 + (entity_pos_t::FromInt(i1) - x).Square() <= r2);
1614  }
1615  ENSURE(dy2 + (entity_pos_t::FromInt(i0 - 1) - x).Square() > r2);
1616  ENSURE(dy2 + (entity_pos_t::FromInt(i1 + 1) - x).Square() > r2);
1617 #endif
1618 
1619  // Clamp the strip to exclude the 1-tile border,
1620  // then add or remove the strip as requested
1621  i32 i0clamp = std::max(i0, 1);
1622  i32 i1clamp = std::min(i1, m_TerrainVerticesPerSide-2);
1623  if (adding)
1624  LosAddStripHelper(owner, i0clamp, i1clamp, j, countsData);
1625  else
1626  LosRemoveStripHelper(owner, i0clamp, i1clamp, j, countsData);
1627  }
1628  }
1629 
1630  /**
1631  * Update the LOS state of tiles within a given circular range,
1632  * by removing visibility around the 'from' position
1633  * and then adding visibility around the 'to' position.
1634  */
1636  {
1637  if (m_TerrainVerticesPerSide == 0) // do nothing if not initialised yet
1638  return;
1639 
1640  PROFILE("LosUpdateHelperIncremental");
1641 
1642  std::vector<u16>& counts = m_LosPlayerCounts.at(owner);
1643 
1644  // Lazy initialisation of counts:
1645  if (counts.empty())
1647 
1648  u16* countsData = &counts[0];
1649 
1650  // See comments in LosUpdateHelper.
1651  // This does exactly the same, except computing the strips for
1652  // both circles simultaneously.
1653  // (The idea is that the circles will be heavily overlapping,
1654  // so we can compute the difference between the removed/added strips
1655  // and only have to touch tiles that have a net change.)
1656 
1657  i32 j0_from = ((from.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity();
1658  i32 j1_from = ((from.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity();
1659  i32 j0_to = ((to.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity();
1660  i32 j1_to = ((to.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity();
1661  i32 j0clamp = std::max(std::min(j0_from, j0_to), 1);
1662  i32 j1clamp = std::min(std::max(j1_from, j1_to), m_TerrainVerticesPerSide-2);
1663 
1664  entity_pos_t x_from = from.X / (int)TERRAIN_TILE_SIZE;
1665  entity_pos_t y_from = from.Y / (int)TERRAIN_TILE_SIZE;
1666  entity_pos_t x_to = to.X / (int)TERRAIN_TILE_SIZE;
1667  entity_pos_t y_to = to.Y / (int)TERRAIN_TILE_SIZE;
1668  entity_pos_t r = visionRange / (int)TERRAIN_TILE_SIZE;
1669  entity_pos_t r2 = r.Square();
1670 
1671  i32 xfloor_from = (x_from - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
1672  i32 xceil_from = (x_from + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
1673  i32 xfloor_to = (x_to - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
1674  i32 xceil_to = (x_to + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
1675 
1676  i32 i0_from = xfloor_from;
1677  i32 i1_from = xceil_from;
1678  i32 i0_to = xfloor_to;
1679  i32 i1_to = xceil_to;
1680 
1681  for (i32 j = j0clamp; j <= j1clamp; ++j)
1682  {
1683  entity_pos_t dy_from = entity_pos_t::FromInt(j) - y_from;
1684  entity_pos_t dy2_from = dy_from.Square();
1685  while (dy2_from + (entity_pos_t::FromInt(i0_from-1) - x_from).Square() <= r2)
1686  --i0_from;
1687  while (i0_from < xceil_from && dy2_from + (entity_pos_t::FromInt(i0_from) - x_from).Square() > r2)
1688  ++i0_from;
1689  while (dy2_from + (entity_pos_t::FromInt(i1_from+1) - x_from).Square() <= r2)
1690  ++i1_from;
1691  while (i1_from > xfloor_from && dy2_from + (entity_pos_t::FromInt(i1_from) - x_from).Square() > r2)
1692  --i1_from;
1693 
1694  entity_pos_t dy_to = entity_pos_t::FromInt(j) - y_to;
1695  entity_pos_t dy2_to = dy_to.Square();
1696  while (dy2_to + (entity_pos_t::FromInt(i0_to-1) - x_to).Square() <= r2)
1697  --i0_to;
1698  while (i0_to < xceil_to && dy2_to + (entity_pos_t::FromInt(i0_to) - x_to).Square() > r2)
1699  ++i0_to;
1700  while (dy2_to + (entity_pos_t::FromInt(i1_to+1) - x_to).Square() <= r2)
1701  ++i1_to;
1702  while (i1_to > xfloor_to && dy2_to + (entity_pos_t::FromInt(i1_to) - x_to).Square() > r2)
1703  --i1_to;
1704 
1705 #if DEBUG_RANGE_MANAGER_BOUNDS
1706  if (i0_from <= i1_from)
1707  {
1708  ENSURE(dy2_from + (entity_pos_t::FromInt(i0_from) - x_from).Square() <= r2);
1709  ENSURE(dy2_from + (entity_pos_t::FromInt(i1_from) - x_from).Square() <= r2);
1710  }
1711  ENSURE(dy2_from + (entity_pos_t::FromInt(i0_from - 1) - x_from).Square() > r2);
1712  ENSURE(dy2_from + (entity_pos_t::FromInt(i1_from + 1) - x_from).Square() > r2);
1713  if (i0_to <= i1_to)
1714  {
1715  ENSURE(dy2_to + (entity_pos_t::FromInt(i0_to) - x_to).Square() <= r2);
1716  ENSURE(dy2_to + (entity_pos_t::FromInt(i1_to) - x_to).Square() <= r2);
1717  }
1718  ENSURE(dy2_to + (entity_pos_t::FromInt(i0_to - 1) - x_to).Square() > r2);
1719  ENSURE(dy2_to + (entity_pos_t::FromInt(i1_to + 1) - x_to).Square() > r2);
1720 #endif
1721 
1722  // Check whether this strip moved at all
1723  if (!(i0_to == i0_from && i1_to == i1_from))
1724  {
1725  i32 i0clamp_from = std::max(i0_from, 1);
1726  i32 i1clamp_from = std::min(i1_from, m_TerrainVerticesPerSide-2);
1727  i32 i0clamp_to = std::max(i0_to, 1);
1728  i32 i1clamp_to = std::min(i1_to, m_TerrainVerticesPerSide-2);
1729 
1730  // Check whether one strip is negative width,
1731  // and we can just add/remove the entire other strip
1732  if (i1clamp_from < i0clamp_from)
1733  {
1734  LosAddStripHelper(owner, i0clamp_to, i1clamp_to, j, countsData);
1735  }
1736  else if (i1clamp_to < i0clamp_to)
1737  {
1738  LosRemoveStripHelper(owner, i0clamp_from, i1clamp_from, j, countsData);
1739  }
1740  else
1741  {
1742  // There are four possible regions of overlap between the two strips
1743  // (remove before add, remove after add, add before remove, add after remove).
1744  // Process each of the regions as its own strip.
1745  // (If this produces negative-width strips then they'll just get ignored
1746  // which is fine.)
1747  // (If the strips don't actually overlap (which is very rare with normal unit
1748  // movement speeds), the region between them will be both added and removed,
1749  // so we have to do the add first to avoid overflowing to -1 and triggering
1750  // assertion failures.)
1751  LosAddStripHelper(owner, i0clamp_to, i0clamp_from-1, j, countsData);
1752  LosAddStripHelper(owner, i1clamp_from+1, i1clamp_to, j, countsData);
1753  LosRemoveStripHelper(owner, i0clamp_from, i0clamp_to-1, j, countsData);
1754  LosRemoveStripHelper(owner, i1clamp_to+1, i1clamp_from, j, countsData);
1755  }
1756  }
1757  }
1758  }
1759 
1760  void LosAdd(player_id_t owner, entity_pos_t visionRange, CFixedVector2D pos)
1761  {
1762  if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
1763  return;
1764 
1765  LosUpdateHelper<true>((u8)owner, visionRange, pos);
1766  }
1767 
1768  void LosRemove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D pos)
1769  {
1770  if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
1771  return;
1772 
1773  LosUpdateHelper<false>((u8)owner, visionRange, pos);
1774  }
1775 
1776  void LosMove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
1777  {
1778  if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
1779  return;
1780 
1781  if ((from - to).CompareLength(visionRange) > 0)
1782  {
1783  // If it's a very large move, then simply remove and add to the new position
1784 
1785  LosUpdateHelper<false>((u8)owner, visionRange, from);
1786  LosUpdateHelper<true>((u8)owner, visionRange, to);
1787  }
1788  else
1789  {
1790  // Otherwise use the version optimised for mostly-overlapping circles
1791 
1792  LosUpdateHelperIncremental((u8)owner, visionRange, from, to);
1793  }
1794  }
1795 
1797  {
1798  return m_ExploredVertices.at((u8)player) * 100 / m_TotalInworldVertices;
1799  }
1800 };
1801 
1802 REGISTER_COMPONENT_TYPE(RangeManager)
int CompareLength(fixed cmp) const
Returns -1, 0, +1 depending on whether length is less/equal/greater than the argument.
An entity initialisation parameter node.
Definition: ParamNode.h:112
void SubscribeToMessageType(MessageTypeId mtid)
Subscribe the current component type to the given message type.
iterator begin()
Definition: EntityMap.h:99
void RenderSubmit(SceneCollector &collector)
#define u8
Definition: types.h:39
A simple fixed-point number class.
Definition: Fixed.h:115
int size() const
Definition: Spatial.h:37
virtual entity_pos_t GetGroundLevel(entity_pos_t x, entity_pos_t z)=0
std::vector< u32 > m_LosState
uint32_t GetWidth() const
Definition: Spatial.h:147
entity_pos_t z
void operator()(ISerializer &serialize, const char *name, Query &value, const CSimContext &context)
virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, ssize_t vertices)
Set the bounds of the world.
virtual entity_pos_t GetWaterLevel(entity_pos_t x, entity_pos_t z)=0
Get the current water level at the given point.
#define REGISTER_COMPONENT_TYPE(cname)
Definition: Component.h:30
virtual void Deserialize(const CParamNode &paramNode, IDeserializer &deserialize)
This is sent immediately after a new entity&#39;s components have all been created and initialised...
Definition: MessageTypes.h:191
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
#define FIXED_MUL_I64_I32_I32(a, b)
Definition: Fixed.h:36
Line-based overlay, with world-space coordinates, rendered in the world potentially behind other obje...
Definition: Overlay.h:36
entity_id_t entity
Definition: MessageTypes.h:221
void erase(iterator it)
Definition: EntityMap.h:171
static CFixed Zero()
Definition: Fixed.h:127
Sent by CCmpVision when an entity&#39;s vision range changes.
Definition: MessageTypes.h:388
void LosUpdateHelperIncremental(u8 owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
Update the LOS state of tiles within a given circular range, by removing visibility around the &#39;from&#39;...
const ssize_t TERRAIN_TILE_SIZE
metres [world space units] per tile in x and z
Definition: Terrain.h:40
void insert(const key_type key, const mapped_type &value)
Definition: EntityMap.h:125
void GetNear(SpatialQueryArray &out, CFixedVector2D pos, entity_pos_t range)
Returns a sorted list of unique items that includes all items within the given circular distance of t...
Definition: Spatial.h:314
void Remove(uint32_t item, CFixedVector2D fromMin, CFixedVector2D fromMax)
Remove an item with the given &#39;from&#39; size.
Definition: Spatial.h:222
#define LOGERROR
Definition: CLogger.h:35
virtual void SetSharedLos(player_id_t player, std::vector< player_id_t > players)
Sets shared LOS data for player to the given list of players.
void LosRemoveStripHelper(u8 owner, i32 i0, i32 i1, i32 j, u16 *counts)
Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
void Reset(entity_pos_t maxX, entity_pos_t maxZ, entity_pos_t divisionSize)
Definition: Spatial.h:186
#define ENTITY_IS_LOCAL(id)
Definition: Entity.h:60
void ResetDerivedData(bool skipLosState)
void operator()(IDeserializer &deserialize, const char *name, Query &value, const CSimContext &context)
entity_pos_t m_WorldZ0
Definition: Overlay.h:34
#define i32
Definition: types.h:36
EntityDistanceOrdering(const EntityMap< EntityData > &entities, const CFixedVector2D &source)
Serialization interface; see serialization overview.
Definition: ISerializer.h:120
IComponent * QueryInterface(entity_id_t ent, InterfaceId iid) const
u16 m_H
Definition: Grid.h:101
Add renderable objects to the scene collector.
Definition: MessageTypes.h:145
uint32_t GetHeight() const
Definition: Spatial.h:148
Object wrapping an entity_id_t, with a SEntityComponentCache to support fast QueryInterface() / CmpPt...
Definition: Entity.h:80
entity_pos_t minRange
iterator find(const entity_id_t key)
Definition: EntityMap.h:211
virtual std::vector< entity_id_t > ResetActiveQuery(tag_t tag)
Immediately execute a query, and re-enable it if disabled.
virtual void NumberU32_Unbounded(const char *name, uint32_t &out)
virtual tag_t CreateActiveQuery(entity_id_t source, entity_pos_t minRange, entity_pos_t maxRange, std::vector< int > owners, int requiredInterface, u8 flags)
Construct an active query.
Provides efficient range-based queries of the game world, and also LOS-based effects (fog of war)...
virtual const Grid< u8 > & GetTerritoryGrid()=0
For each tile, the TERRITORY_PLAYER_MASK bits are player ID; TERRITORY_CONNECTED_MASK is set if the t...
iterator end()
Definition: EntityMap.h:105
Representation of an entity, with the data needed for queries.
#define ASSERT(expr)
same as ENSURE in debug mode, does nothing in release mode.
Definition: debug.h:310
T GetInternalValue() const
Definition: Fixed.h:131
virtual void Init(const CParamNode &paramNode)
virtual bool NeedUpdate(size_t *dirtyID)=0
Serialization helper template for Query.
Contains pointers to various &#39;global&#39; objects that are needed by the simulation code, to allow easy access without using real (evil) global variables.
Definition: SimContext.h:32
virtual void SetEntityFlag(entity_id_t ent, std::string identifier, bool value)
Set the flag specified by the identifier to the supplied value for the entity.
std::vector< u32 > m_LosStateRevealed
virtual bool IsInWorld()=0
Returns true if the entity currently exists at a defined position in the world.
const EntityMap< EntityData > & m_EntityData
entity_pos_t elevationBonus
void PerformQuery(const Query &q, std::vector< entity_id_t > &r)
Returns a list of distinct entity IDs that match the given query, sorted by ID.
static const player_id_t MAX_LOS_PLAYER_ID
std::vector< entity_pos_t > outline
std::vector< u32 > m_ExploredVertices
virtual std::vector< entity_id_t > GetEntitiesByPlayer(player_id_t player)
Returns list of all entities for specific player.
entity_pos_t maxRange
int32_t player_id_t
valid player IDs are non-negative (see ICmpOwnership)
Definition: Player.h:24
This interface accepts renderable objects.
Definition: Scene.h:82
CEntityHandle LookupEntityHandle(entity_id_t ent, bool allowCreate=false)
Returns a CEntityHandle with id ent.
#define LOGWARNING
Definition: CLogger.h:34
void LosUpdateHelper(u8 owner, entity_pos_t visionRange, CFixedVector2D pos)
Update the LOS state of tiles within a given circular range, either adding or removing visibility dep...
static bool InParabolicRange(CFixedVector3D v, fixed range)
Checks whether v is in a parabolic range of (0,0,0) The highest point of the paraboloid is (0...
Range manager implementation.
virtual ELosVisibility GetLosVisibility(entity_id_t ent, player_id_t player, bool forceRetainInFog)
Query ConstructParabolicQuery(entity_id_t source, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus, const std::vector< int > &owners, int requiredInterface, u8 flagsMask)
virtual bool GetRetainInFog()=0
virtual bool GetLosRevealAll(player_id_t player)
Returns whether the whole map has been made visible to the given player.
std::map< player_id_t, u32 > m_SharedLosMasks
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
void ConstructCircleOnGround(const CSimContext &context, float x, float z, float radius, SOverlayLine &overlay, bool floating, float heightOffset=0.25f)
Constructs overlay line as a circle with given center and radius, conforming to terrain.
Definition: Render.cpp:70
std::map< tag_t, Query > m_Queries
virtual int GetType() const =0
EntityDistanceOrdering & operator=(const EntityDistanceOrdering &)
void LosAddStripHelper(u8 owner, i32 i0, i32 i1, i32 j, u16 *counts)
Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
entity_pos_t x
virtual ELosVisibility GetLosVisibility(CEntityHandle ent, player_id_t player, bool forceRetainInFog)
Returns the visibility status of the given entity, with respect to the given player.
void ConstructLineOnGround(const CSimContext &context, const std::vector< float > &xz, SOverlayLine &overlay, bool floating, float heightOffset=0.25f)
Constructs overlay line from given points, conforming to terrain.
Definition: Render.cpp:35
static std::map< entity_id_t, EntityParabolicRangeOutline > ParabolicRangesOutlines
bool operator()(entity_id_t a, entity_id_t b)
virtual void Verify()
Perform some internal consistency checks for testing/debugging.
virtual std::vector< entity_id_t > ExecuteQuery(entity_id_t source, entity_pos_t minRange, entity_pos_t maxRange, std::vector< int > owners, int requiredInterface)
Execute a passive query.
This is sent immediately before a destroyed entity is flushed and really destroyed.
Definition: MessageTypes.h:211
static u32 CalcOwnerMask(player_id_t owner)
Convert an owner ID (-1 = unowned, 0 = gaia, 1..30 = players) into a 32-bit mask for quick set-member...
virtual CLosQuerier GetLosQuerier(player_id_t player)
Returns a CLosQuerier for checking whether vertex positions are visible to the given player (or other...
static u32 CalcPlayerLosMask(player_id_t player)
Returns LOS mask for given player.
float ToFloat() const
Convert to float. May be lossy - float can&#39;t represent all values.
Definition: Fixed.h:161
entity_pos_t visionRange
virtual u32 GetSharedLosMask(player_id_t player)
Returns shared LOS mask for player.
static CFixed Epsilon()
Definition: Fixed.h:128
virtual CFixedVector2D GetPosition2D()=0
Returns the current x,z position (no interpolation).
std::vector< SOverlayLine > m_DebugOverlayLines
void Common(S &serialize, const char *name, Query &value)
virtual entity_pos_t GetElevationAdaptedRange(CFixedVector3D pos, CFixedVector3D rot, entity_pos_t range, entity_pos_t elevationBonus, entity_pos_t angle)
Get the average elevation over 8 points on distance range around the entity.
void ResetSubdivisions(entity_pos_t x1, entity_pos_t z1)
entity_id_t GetId() const
Definition: Entity.h:89
static u32 CalcSharedLosMask(std::vector< player_id_t > players)
Returns shared LOS mask for given list of players.
#define PROFILE(name)
Definition: Profile.h:195
#define DEFAULT_COMPONENT_ALLOCATOR(cname)
Definition: Component.h:44
const CSimContext & GetSimContext() const
Definition: IComponent.h:52
void LosMove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
Sent during TurnStart.
Definition: MessageTypes.h:245
void SubscribeGloballyToMessageType(MessageTypeId mtid)
Subscribe the current component type to all messages of the given message type.
entity_id_t entity
Definition: MessageTypes.h:201
virtual void EnableActiveQuery(tag_t tag)
Re-enable the processing of a query.
void SerializeCommon(S &serialize)
void NumberU32_Unbounded(const char *name, uint32_t value)
Serialize a number.
Definition: ISerializer.h:171
virtual u8 GetEntityFlagMask(std::string identifier)
Returns the mask for the specified identifier.
double ToDouble() const
Convert to double. Won&#39;t be lossy - double can precisely represent all values.
Definition: Fixed.h:167
bool parabolic
A simplified syntax for accessing entity components.
Definition: CmpPtr.h:55
virtual entity_pos_t GetRange()=0
#define i8
Definition: types.h:34
virtual void Deinit()
virtual void SetLosCircular(bool enabled)
Set the LOS to be restricted to a circular map.
intptr_t ssize_t
Definition: wposix_types.h:82
CEntityHandle GetSystemEntity() const
Definition: IComponent.h:50
static CFixed FromInt(int n)
Definition: Fixed.h:136
bool LosIsOffWorld(ssize_t i, ssize_t j)
Returns whether the given vertex is outside the normal bounds of the world (i.e.
static std::string GetSchema()
Representation of a range query.
Helper functions related to rendering.
std::vector< std::vector< u16 > > m_LosPlayerCounts
void PostMessage(entity_id_t ent, const CMessage &msg) const
Send a message, targeted at a particular entity.
CFixedVector2D Multiply(fixed n) const
Multiply by a CFixed.
Definition: FixedVector2D.h:86
#define u16
Definition: types.h:40
A very basic subdivision scheme for finding items in ranges.
Definition: Spatial.h:63
static void ClassInit(CComponentManager &componentManager)
fixed Length() const
Returns the length of the vector.
Definition: FixedVector2D.h:95
#define u64
Definition: types.h:42
bool IsVisible(ssize_t i, ssize_t j)
Returns whether the given vertex is visible (i.e.
std::map< player_id_t, bool > m_LosRevealAll
bool IsExplored(ssize_t i, ssize_t j)
Returns whether the given vertex is explored (i.e.
#define i64
Definition: types.h:37
virtual void DestroyActiveQuery(tag_t tag)
Destroy a query and clean up resources.
#define PROFILE3(name)
Definition: Profile.h:201
#define u32
Definition: types.h:41
virtual CFixedVector3D GetPosition()=0
Returns the current x,y,z position (no interpolation).
static const int TERRITORY_PLAYER_MASK
CEntityHandle source
bool IsZero() const
Returns true if the number is precisely 0.
Definition: Fixed.h:199
SceneCollector & collector
Definition: MessageTypes.h:155
virtual void HandleMessage(const CMessage &msg, bool global)
unsigned int uint32_t
Definition: wposix_types.h:53
virtual u8 GetPercentMapExplored(player_id_t player)
Get percent map explored statistics for specified player.
Object providing efficient abstracted access to the LOS state.
T & get(int i, int j) const
Definition: Grid.h:93
bool TestEntityQuery(const Query &q, entity_id_t id, const EntityData &entity)
Returns whether the given entity matches the given query (ignoring maxRange)
void ConstructSquareOnGround(const CSimContext &context, float x, float z, float w, float h, float a, SOverlayLine &overlay, bool floating, float heightOffset=0.25f)
Constructs overlay line as rectangle with given center and dimensions, conforming to terrain...
Definition: Render.cpp:121
entity_pos_t m_WorldX0
entity_pos_t m_WorldX1
static CFixed FromFloat(float n)
Definition: Fixed.h:141
Functor for sorting entities by distance from a source point.
std::vector< entity_id_t > lastMatch
static Handle handle(size_t idx, u64 tag)
Definition: h_mgr.cpp:121
#define debug_warn(expr)
display the error dialog with the given text.
Definition: debug.h:324
void sincos_approx(CFixed_15_16 a, CFixed_15_16 &sin_out, CFixed_15_16 &cos_out)
Compute sin(a) and cos(a).
Definition: Fixed.cpp:187
A simple fixed-size array that works similar to an std::vector but is obviously limited in its max it...
Definition: Spatial.h:28
CFixed Square() const
Multiply the value by itself.
Definition: Fixed.h:302
void Move(uint32_t item, CFixedVector2D fromMin, CFixedVector2D fromMax, CFixedVector2D toMin, CFixedVector2D toMax)
Equivalent to Remove() then Add(), but potentially faster.
Definition: Spatial.h:251
u16 m_W
Definition: Grid.h:101
Query ConstructQuery(entity_id_t source, entity_pos_t minRange, entity_pos_t maxRange, const std::vector< int > &owners, int requiredInterface, u8 flagsMask)
void Add(uint32_t item, CFixedVector2D toMin, CFixedVector2D toMax)
Add an item with the given &#39;to&#39; size.
Definition: Spatial.h:200
virtual void SetDebugOverlay(bool enabled)
Toggle the rendering of debug info.
void LosRemove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D pos)
#define cassert(expr)
Compile-time assertion.
EntityMap< EntityData > m_EntityData
const entity_id_t INVALID_ENTITY
Invalid entity ID.
Definition: Entity.h:36
virtual void Serialize(ISerializer &serialize)
virtual void SetLosRevealAll(player_id_t player, bool enabled)
Set whether the whole map should be made visible to the given player.
virtual void DisableActiveQuery(tag_t tag)
Disable the processing of a query (no RangeUpdate messages will be sent).
u32 entity_id_t
Entity ID type.
Definition: Entity.h:24
virtual void Submit(CPatch *patch)=0
Submit a terrain patch that is part of the scene.
void operator()(S &serialize, const char *name, EntityData &value)
entity_pos_t GetDivisionSize() const
Definition: Spatial.h:146
static const player_id_t INVALID_PLAYER
Definition: Player.h:26
void ExecuteActiveQueries()
Update all currently-enabled active queries.
virtual tag_t CreateActiveParabolicQuery(entity_id_t source, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus, std::vector< int > owners, int requiredInterface, u8 flags)
Construct an active query of a paraboloic form around the unit.
Serialization helper template for EntityData.
virtual bool GetLosCircular()
Returns whether the LOS is restricted to a circular map.
SpatialSubdivision m_Subdivision
u32 tag_t
External identifiers for active queries.
void LosAdd(player_id_t owner, entity_pos_t visionRange, CFixedVector2D pos)
CComponentManager & GetComponentManager() const
Definition: SimContext.cpp:35
virtual std::vector< entity_pos_t > getParabolicRangeForm(CFixedVector3D pos, entity_pos_t maxRange, entity_pos_t cutoff, entity_pos_t minAngle, entity_pos_t maxAngle, int numberOfSteps)
void debug_printf(const wchar_t *fmt,...)
write a formatted string to the debug channel, subject to filtering (see below).
Definition: debug.cpp:142
Deserialization interface; see serialization overview.
Definition: IDeserializer.h:34
entity_pos_t m_WorldZ1