LCOV - code coverage report
Current view: top level - source/simulation2/components - CCmpRangeManager.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 440 1206 36.5 %
Date: 2023-01-19 00:18:29 Functions: 51 128 39.8 %

          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 "ICmpRangeManager.h"
      22             : 
      23             : #include "ICmpTerrain.h"
      24             : #include "simulation2/system/EntityMap.h"
      25             : #include "simulation2/MessageTypes.h"
      26             : #include "simulation2/components/ICmpFogging.h"
      27             : #include "simulation2/components/ICmpMirage.h"
      28             : #include "simulation2/components/ICmpOwnership.h"
      29             : #include "simulation2/components/ICmpPosition.h"
      30             : #include "simulation2/components/ICmpObstructionManager.h"
      31             : #include "simulation2/components/ICmpTerritoryManager.h"
      32             : #include "simulation2/components/ICmpVisibility.h"
      33             : #include "simulation2/components/ICmpVision.h"
      34             : #include "simulation2/components/ICmpWaterManager.h"
      35             : #include "simulation2/helpers/Los.h"
      36             : #include "simulation2/helpers/MapEdgeTiles.h"
      37             : #include "simulation2/helpers/Render.h"
      38             : #include "simulation2/helpers/Spatial.h"
      39             : #include "simulation2/serialization/SerializedTypes.h"
      40             : 
      41             : #include "graphics/Overlay.h"
      42             : #include "lib/timer.h"
      43             : #include "ps/CLogger.h"
      44             : #include "ps/Profile.h"
      45             : #include "renderer/Scene.h"
      46             : 
      47             : #define DEBUG_RANGE_MANAGER_BOUNDS 0
      48             : 
      49             : namespace
      50             : {
      51             : /**
      52             :  * How many LOS vertices to have per region.
      53             :  * LOS regions are used to keep track of units.
      54             :  */
      55             : constexpr int LOS_REGION_RATIO = 8;
      56             : 
      57             : /**
      58             :  * Tolerance for parabolic range calculations.
      59             :  * TODO C++20: change this to constexpr by fixing CFixed with std::is_constant_evaluated
      60             :  */
      61           1 : const fixed PARABOLIC_RANGE_TOLERANCE = fixed::FromInt(1)/2;
      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         175 : u32 CalcOwnerMask(player_id_t owner)
      68             : {
      69         175 :     if (owner >= -1 && owner < 31)
      70         175 :         return 1 << (1+owner);
      71             :     else
      72           0 :         return 0; // owner was invalid
      73             : }
      74             : 
      75             : /**
      76             :  * Returns LOS mask for given player.
      77             :  */
      78           0 : u32 CalcPlayerLosMask(player_id_t player)
      79             : {
      80           0 :     if (player > 0 && player <= 16)
      81           0 :         return (u32)LosState::MASK << (2*(player-1));
      82           0 :     return 0;
      83             : }
      84             : 
      85             : /**
      86             :  * Returns shared LOS mask for given list of players.
      87             :  */
      88           0 : u32 CalcSharedLosMask(std::vector<player_id_t> players)
      89             : {
      90           0 :     u32 playerMask = 0;
      91           0 :     for (size_t i = 0; i < players.size(); i++)
      92           0 :         playerMask |= CalcPlayerLosMask(players[i]);
      93             : 
      94           0 :     return playerMask;
      95             : }
      96             : 
      97             : /**
      98             :  * Add/remove a player to/from mask, which is a 1-bit mask representing a list of players.
      99             :  * Returns true if the mask is modified.
     100             :  */
     101           0 : bool SetPlayerSharedDirtyVisibilityBit(u16& mask, player_id_t player, bool enable)
     102             : {
     103           0 :     if (player <= 0 || player > 16)
     104           0 :         return false;
     105             : 
     106           0 :     u16 oldMask = mask;
     107             : 
     108           0 :     if (enable)
     109           0 :         mask |= (0x1 << (player - 1));
     110             :     else
     111           0 :         mask &= ~(0x1 << (player - 1));
     112             : 
     113           0 :     return oldMask != mask;
     114             : }
     115             : 
     116             : /**
     117             :  * Computes the 2-bit visibility for one player, given the total 32-bit visibilities
     118             :  */
     119           0 : LosVisibility GetPlayerVisibility(u32 visibilities, player_id_t player)
     120             : {
     121           0 :     if (player > 0 && player <= 16)
     122           0 :         return static_cast<LosVisibility>( (visibilities >> (2 *(player-1))) & 0x3 );
     123           0 :     return LosVisibility::HIDDEN;
     124             : }
     125             : 
     126             : /**
     127             :  * Test whether the visibility is dirty for a given LoS region and a given player
     128             :  */
     129           0 : bool IsVisibilityDirty(u16 dirty, player_id_t player)
     130             : {
     131           0 :     if (player > 0 && player <= 16)
     132           0 :         return (dirty >> (player - 1)) & 0x1;
     133           0 :     return false;
     134             : }
     135             : 
     136             : /**
     137             :  * Test whether a player share this vision
     138             :  */
     139           0 : bool HasVisionSharing(u16 visionSharing, player_id_t player)
     140             : {
     141           0 :     return (visionSharing & (1 << (player - 1))) != 0;
     142             : }
     143             : 
     144             : /**
     145             :  * Computes the shared vision mask for the player
     146             :  */
     147           0 : u16 CalcVisionSharingMask(player_id_t player)
     148             : {
     149           0 :     return 1 << (player-1);
     150             : }
     151             : 
     152             : /**
     153             :  * Representation of a range query.
     154             :  */
     155          26 : struct Query
     156             : {
     157             :     std::vector<entity_id_t> lastMatch;
     158             :     CEntityHandle source; // TODO: this could crash if an entity is destroyed while a Query is still referencing it
     159             :     entity_pos_t minRange;
     160             :     entity_pos_t maxRange;
     161             :     entity_pos_t yOrigin; // Used for parabolas only.
     162             :     u32 ownersMask;
     163             :     i32 interface;
     164             :     u8 flagsMask;
     165             :     bool enabled;
     166             :     bool parabolic;
     167             :     bool accountForSize; // If true, the query accounts for unit sizes, otherwise it treats all entities as points.
     168             : };
     169             : 
     170             : /**
     171             :  * Checks whether v is in a parabolic range of (0,0,0)
     172             :  * The highest point of the paraboloid is (0,range/2,0)
     173             :  * and the circle of distance 'range' around (0,0,0) on height y=0 is part of the paraboloid
     174             :  * This equates to computing f(x, z) = y = -(xx + zz)/(2*range) + range/2 > 0,
     175             :  * or alternatively sqrt(xx+zz) <= sqrt(range^2 - 2range*y).
     176             :  *
     177             :  * Avoids sqrting and overflowing.
     178             :  */
     179           0 : static bool InParabolicRange(CFixedVector3D v, fixed range)
     180             : {
     181           0 :     u64 xx = SQUARE_U64_FIXED(v.X); // xx <= 2^62
     182           0 :     u64 zz = SQUARE_U64_FIXED(v.Z);
     183           0 :     i64 d2 = (xx + zz) >> 1; // d2 <= 2^62 (no overflow)
     184             : 
     185           0 :     i32 y = v.Y.GetInternalValue();
     186           0 :     i32 c = range.GetInternalValue();
     187           0 :     i32 c_2 = c >> 1;
     188             : 
     189           0 :     i64 c2 = MUL_I64_I32_I32(c_2 - y, c);
     190             : 
     191           0 :     return d2 <= c2;
     192             : }
     193             : 
     194           0 : struct EntityParabolicRangeOutline
     195             : {
     196             :     entity_id_t source;
     197             :     CFixedVector3D position;
     198             :     entity_pos_t range;
     199             :     std::vector<entity_pos_t> outline;
     200             : };
     201             : 
     202           1 : static std::map<entity_id_t, EntityParabolicRangeOutline> ParabolicRangesOutlines;
     203             : 
     204             : /**
     205             :  * Representation of an entity, with the data needed for queries.
     206             :  */
     207             : enum FlagMasks
     208             : {
     209             :     // flags used for queries
     210             :     None = 0x00,
     211             :     Normal = 0x01,
     212             :     Injured = 0x02,
     213             :     AllQuery = Normal | Injured,
     214             : 
     215             :     // 0x04 reserved for future use
     216             : 
     217             :     // general flags
     218             :     InWorld = 0x08,
     219             :     RetainInFog = 0x10,
     220             :     RevealShore = 0x20,
     221             :     ScriptedVisibility = 0x40,
     222             :     SharedVision = 0x80
     223             : };
     224             : 
     225             : struct EntityData
     226             : {
     227           3 :     EntityData() :
     228             :         visibilities(0), size(0), visionSharing(0),
     229           3 :         owner(-1), flags(FlagMasks::Normal)
     230           3 :         { }
     231             :     entity_pos_t x, z;
     232             :     entity_pos_t visionRange;
     233             :     u32 visibilities; // 2-bit visibility, per player
     234             :     u32 size;
     235             :     u16 visionSharing; // 1-bit per player
     236             :     i8 owner;
     237             :     u8 flags; // See the FlagMasks enum
     238             : 
     239             :     template<int mask>
     240        6265 :     inline bool HasFlag() const { return (flags & mask) != 0; }
     241             : 
     242             :     template<int mask>
     243        1041 :     inline void SetFlag(bool val) { flags = val ? (flags | mask) : (flags & ~mask); }
     244             : 
     245           0 :     inline void SetFlag(u8 mask, bool val) { flags = val ? (flags | mask) : (flags & ~mask); }
     246             : };
     247             : 
     248             : static_assert(sizeof(EntityData) == 24);
     249             : 
     250             : /**
     251             :  * Functor for sorting entities by distance from a source point.
     252             :  * It must only be passed entities that are in 'entities'
     253             :  * and are currently in the world.
     254             :  */
     255             : class EntityDistanceOrdering
     256             : {
     257             : public:
     258          13 :     EntityDistanceOrdering(const EntityMap<EntityData>& entities, const CFixedVector2D& source) :
     259          13 :         m_EntityData(entities), m_Source(source)
     260             :     {
     261          13 :     }
     262             : 
     263             :     EntityDistanceOrdering(const EntityDistanceOrdering& entity) = default;
     264             : 
     265           0 :     bool operator()(entity_id_t a, entity_id_t b) const
     266             :     {
     267           0 :         const EntityData& da = m_EntityData.find(a)->second;
     268           0 :         const EntityData& db = m_EntityData.find(b)->second;
     269           0 :         CFixedVector2D vecA = CFixedVector2D(da.x, da.z) - m_Source;
     270           0 :         CFixedVector2D vecB = CFixedVector2D(db.x, db.z) - m_Source;
     271           0 :         return (vecA.CompareLength(vecB) < 0);
     272             :     }
     273             : 
     274             :     const EntityMap<EntityData>& m_EntityData;
     275             :     CFixedVector2D m_Source;
     276             : 
     277             : private:
     278             :     EntityDistanceOrdering& operator=(const EntityDistanceOrdering&);
     279             : };
     280             : } // anonymous namespace
     281             : 
     282             : /**
     283             :  * Serialization helper template for Query
     284             :  */
     285             : template<>
     286             : struct SerializeHelper<Query>
     287             : {
     288             :     template<typename S>
     289           0 :     void Common(S& serialize, const char* UNUSED(name), Serialize::qualify<S, Query> value)
     290             :     {
     291           0 :         serialize.NumberFixed_Unbounded("min range", value.minRange);
     292           0 :         serialize.NumberFixed_Unbounded("max range", value.maxRange);
     293           0 :         serialize.NumberFixed_Unbounded("yOrigin", value.yOrigin);
     294           0 :         serialize.NumberU32_Unbounded("owners mask", value.ownersMask);
     295           0 :         serialize.NumberI32_Unbounded("interface", value.interface);
     296           0 :         Serializer(serialize, "last match", value.lastMatch);
     297           0 :         serialize.NumberU8_Unbounded("flagsMask", value.flagsMask);
     298           0 :         serialize.Bool("enabled", value.enabled);
     299           0 :         serialize.Bool("parabolic",value.parabolic);
     300           0 :         serialize.Bool("account for size",value.accountForSize);
     301           0 :     }
     302             : 
     303           0 :     void operator()(ISerializer& serialize, const char* name, Query& value, const CSimContext& UNUSED(context))
     304             :     {
     305           0 :         Common(serialize, name, value);
     306             : 
     307           0 :         uint32_t id = value.source.GetId();
     308           0 :         serialize.NumberU32_Unbounded("source", id);
     309           0 :     }
     310             : 
     311           0 :     void operator()(IDeserializer& deserialize, const char* name, Query& value, const CSimContext& context)
     312             :     {
     313           0 :         Common(deserialize, name, value);
     314             : 
     315             :         uint32_t id;
     316           0 :         deserialize.NumberU32_Unbounded("source", id);
     317           0 :         value.source = context.GetComponentManager().LookupEntityHandle(id, true);
     318             :         // the referenced entity might not have been deserialized yet,
     319             :         // so tell LookupEntityHandle to allocate the handle if necessary
     320           0 :     }
     321             : };
     322             : 
     323             : /**
     324             :  * Serialization helper template for EntityData
     325             :  */
     326             : template<>
     327             : struct SerializeHelper<EntityData>
     328             : {
     329             :     template<typename S>
     330           0 :     void operator()(S& serialize, const char* UNUSED(name), Serialize::qualify<S, EntityData> value)
     331             :     {
     332           0 :         serialize.NumberFixed_Unbounded("x", value.x);
     333           0 :         serialize.NumberFixed_Unbounded("z", value.z);
     334           0 :         serialize.NumberFixed_Unbounded("vision", value.visionRange);
     335           0 :         serialize.NumberU32_Unbounded("visibilities", value.visibilities);
     336           0 :         serialize.NumberU32_Unbounded("size", value.size);
     337           0 :         serialize.NumberU16_Unbounded("vision sharing", value.visionSharing);
     338           0 :         serialize.NumberI8_Unbounded("owner", value.owner);
     339           0 :         serialize.NumberU8_Unbounded("flags", value.flags);
     340           0 :     }
     341             : };
     342             : 
     343             : /**
     344             :  * Range manager implementation.
     345             :  * Maintains a list of all entities (and their positions and owners), which is used for
     346             :  * queries.
     347             :  *
     348             :  * LOS implementation is based on the model described in GPG2.
     349             :  * (TODO: would be nice to make it cleverer, so e.g. mountains and walls
     350             :  * can block vision)
     351             :  */
     352          18 : class CCmpRangeManager final : public ICmpRangeManager
     353             : {
     354             : public:
     355         116 :     static void ClassInit(CComponentManager& componentManager)
     356             :     {
     357         116 :         componentManager.SubscribeGloballyToMessageType(MT_Create);
     358         116 :         componentManager.SubscribeGloballyToMessageType(MT_PositionChanged);
     359         116 :         componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged);
     360         116 :         componentManager.SubscribeGloballyToMessageType(MT_Destroy);
     361         116 :         componentManager.SubscribeGloballyToMessageType(MT_VisionRangeChanged);
     362         116 :         componentManager.SubscribeGloballyToMessageType(MT_VisionSharingChanged);
     363             : 
     364         116 :         componentManager.SubscribeToMessageType(MT_Deserialized);
     365         116 :         componentManager.SubscribeToMessageType(MT_Update);
     366         116 :         componentManager.SubscribeToMessageType(MT_RenderSubmit); // for debug overlays
     367         116 :     }
     368             : 
     369          12 :     DEFAULT_COMPONENT_ALLOCATOR(RangeManager)
     370             : 
     371             :     bool m_DebugOverlayEnabled;
     372             :     bool m_DebugOverlayDirty;
     373             :     std::vector<SOverlayLine> m_DebugOverlayLines;
     374             : 
     375             :     // Deserialization flag. A lot of different functions are called by Deserialize()
     376             :     // and we don't want to pass isDeserializing bool arguments to all of them...
     377             :     bool m_Deserializing;
     378             : 
     379             :     // World bounds (entities are expected to be within this range)
     380             :     entity_pos_t m_WorldX0;
     381             :     entity_pos_t m_WorldZ0;
     382             :     entity_pos_t m_WorldX1;
     383             :     entity_pos_t m_WorldZ1;
     384             : 
     385             :     // Range query state:
     386             :     tag_t m_QueryNext; // next allocated id
     387             :     std::map<tag_t, Query> m_Queries;
     388             :     EntityMap<EntityData> m_EntityData;
     389             : 
     390             :     FastSpatialSubdivision m_Subdivision; // spatial index of m_EntityData
     391             :     std::vector<entity_id_t> m_SubdivisionResults;
     392             : 
     393             :     // LOS state:
     394             :     static const player_id_t MAX_LOS_PLAYER_ID = 16;
     395             : 
     396             :     using LosRegion = std::pair<u16, u16>;
     397             : 
     398             :     std::array<bool, MAX_LOS_PLAYER_ID+2> m_LosRevealAll;
     399             :     bool m_LosCircular;
     400             :     i32 m_LosVerticesPerSide;
     401             : 
     402             :     // Cache for visibility tracking
     403             :     i32 m_LosRegionsPerSide;
     404             :     bool m_GlobalVisibilityUpdate;
     405             :     std::array<bool, MAX_LOS_PLAYER_ID> m_GlobalPlayerVisibilityUpdate;
     406             :     Grid<u16> m_DirtyVisibility;
     407             :     Grid<std::set<entity_id_t>> m_LosRegions;
     408             :     // List of entities that must be updated, regardless of the status of their tile
     409             :     std::vector<entity_id_t> m_ModifiedEntities;
     410             : 
     411             :     // Counts of units seeing vertex, per vertex, per player (starting with player 0).
     412             :     // Use u16 to avoid overflows when we have very large (but not infeasibly large) numbers
     413             :     // of units in a very small area.
     414             :     // (Note we use vertexes, not tiles, to better match the renderer.)
     415             :     // Lazily constructed when it's needed, to save memory in smaller games.
     416             :     std::array<Grid<u16>, MAX_LOS_PLAYER_ID> m_LosPlayerCounts;
     417             : 
     418             :     // 2-bit LosState per player, starting with player 1 (not 0!) up to player MAX_LOS_PLAYER_ID (inclusive)
     419             :     Grid<u32> m_LosState;
     420             : 
     421             :     // Special static visibility data for the "reveal whole map" mode
     422             :     // (TODO: this is usually a waste of memory)
     423             :     Grid<u32> m_LosStateRevealed;
     424             : 
     425             :     // Shared LOS masks, one per player.
     426             :     std::array<u32, MAX_LOS_PLAYER_ID+2> m_SharedLosMasks;
     427             :     // Shared dirty visibility masks, one per player.
     428             :     std::array<u16, MAX_LOS_PLAYER_ID+2> m_SharedDirtyVisibilityMasks;
     429             : 
     430             :     // Cache explored vertices per player (not serialized)
     431             :     u32 m_TotalInworldVertices;
     432             :     std::vector<u32> m_ExploredVertices;
     433             : 
     434         116 :     static std::string GetSchema()
     435             :     {
     436         116 :         return "<a:component type='system'/><empty/>";
     437             :     }
     438             : 
     439           6 :     void Init(const CParamNode& UNUSED(paramNode)) override
     440             :     {
     441           6 :         m_QueryNext = 1;
     442             : 
     443           6 :         m_DebugOverlayEnabled = false;
     444           6 :         m_DebugOverlayDirty = true;
     445             : 
     446           6 :         m_Deserializing = false;
     447           6 :         m_WorldX0 = m_WorldZ0 = m_WorldX1 = m_WorldZ1 = entity_pos_t::Zero();
     448             : 
     449             :         // Initialise with bogus values (these will get replaced when
     450             :         // SetBounds is called)
     451           6 :         ResetSubdivisions(entity_pos_t::FromInt(1024), entity_pos_t::FromInt(1024));
     452             : 
     453           6 :         m_SubdivisionResults.reserve(4096);
     454             : 
     455             :         // The whole map should be visible to Gaia by default, else e.g. animals
     456             :         // will get confused when trying to run from enemies
     457           6 :         m_LosRevealAll[0] = true;
     458             : 
     459           6 :         m_GlobalVisibilityUpdate = true;
     460             : 
     461           6 :         m_LosCircular = false;
     462           6 :         m_LosVerticesPerSide = 0;
     463           6 :     }
     464             : 
     465           6 :     void Deinit() override
     466             :     {
     467           6 :     }
     468             : 
     469             :     template<typename S>
     470           0 :     void SerializeCommon(S& serialize)
     471             :     {
     472           0 :         serialize.NumberFixed_Unbounded("world x0", m_WorldX0);
     473           0 :         serialize.NumberFixed_Unbounded("world z0", m_WorldZ0);
     474           0 :         serialize.NumberFixed_Unbounded("world x1", m_WorldX1);
     475           0 :         serialize.NumberFixed_Unbounded("world z1", m_WorldZ1);
     476             : 
     477           0 :         serialize.NumberU32_Unbounded("query next", m_QueryNext);
     478           0 :         Serializer(serialize, "queries", m_Queries, GetSimContext());
     479           0 :         Serializer(serialize, "entity data", m_EntityData);
     480             : 
     481           0 :         Serializer(serialize, "los reveal all", m_LosRevealAll);
     482           0 :         serialize.Bool("los circular", m_LosCircular);
     483           0 :         serialize.NumberI32_Unbounded("los verts per side", m_LosVerticesPerSide);
     484             : 
     485           0 :         serialize.Bool("global visibility update", m_GlobalVisibilityUpdate);
     486           0 :         Serializer(serialize, "global player visibility update", m_GlobalPlayerVisibilityUpdate);
     487           0 :         Serializer(serialize, "dirty visibility", m_DirtyVisibility);
     488           0 :         Serializer(serialize, "modified entities", m_ModifiedEntities);
     489             : 
     490             :         // We don't serialize m_Subdivision, m_LosPlayerCounts or m_LosRegions
     491             :         // since they can be recomputed from the entity data when deserializing;
     492             :         // m_LosState must be serialized since it depends on the history of exploration
     493             : 
     494           0 :         Serializer(serialize, "los state", m_LosState);
     495           0 :         Serializer(serialize, "shared los masks", m_SharedLosMasks);
     496           0 :         Serializer(serialize, "shared dirty visibility masks", m_SharedDirtyVisibilityMasks);
     497           0 :     }
     498             : 
     499           0 :     void Serialize(ISerializer& serialize) override
     500             :     {
     501           0 :         SerializeCommon(serialize);
     502           0 :     }
     503             : 
     504           0 :     void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) override
     505             :     {
     506           0 :         Init(paramNode);
     507             : 
     508           0 :         SerializeCommon(deserialize);
     509           0 :     }
     510             : 
     511        1060 :     void HandleMessage(const CMessage& msg, bool UNUSED(global)) override
     512             :     {
     513        1060 :         switch (msg.GetType())
     514             :         {
     515           0 :         case MT_Deserialized:
     516             :         {
     517             :             // Reinitialize subdivisions and LOS data after all
     518             :             // other components have been deserialized.
     519           0 :             m_Deserializing = true;
     520           0 :             ResetDerivedData();
     521           0 :             m_Deserializing = false;
     522           0 :             break;
     523             :         }
     524           9 :         case MT_Create:
     525             :         {
     526           9 :             const CMessageCreate& msgData = static_cast<const CMessageCreate&> (msg);
     527           9 :             entity_id_t ent = msgData.entity;
     528             : 
     529             :             // Ignore local entities - we shouldn't let them influence anything
     530           9 :             if (ENTITY_IS_LOCAL(ent))
     531           0 :                 break;
     532             : 
     533             :             // Ignore non-positional entities
     534           9 :             CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), ent);
     535           9 :             if (!cmpPosition)
     536           6 :                 break;
     537             : 
     538             :             // The newly-created entity will have owner -1 and position out-of-world
     539             :             // (any initialisation of those values will happen later), so we can just
     540             :             // use the default-constructed EntityData here
     541           3 :             EntityData entdata;
     542             : 
     543             :             // Store the LOS data, if any
     544           3 :             CmpPtr<ICmpVision> cmpVision(GetSimContext(), ent);
     545           3 :             if (cmpVision)
     546             :             {
     547           3 :                 entdata.visionRange = cmpVision->GetRange();
     548           3 :                 entdata.SetFlag<FlagMasks::RevealShore>(cmpVision->GetRevealShore());
     549             :             }
     550           3 :             CmpPtr<ICmpVisibility> cmpVisibility(GetSimContext(), ent);
     551           3 :             if (cmpVisibility)
     552           0 :                 entdata.SetFlag<FlagMasks::RetainInFog>(cmpVisibility->GetRetainInFog());
     553             : 
     554             :             // Store the size
     555           3 :             CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), ent);
     556           3 :             if (cmpObstruction)
     557           2 :                 entdata.size = cmpObstruction->GetSize().ToInt_RoundToInfinity();
     558             : 
     559             :             // Remember this entity
     560           3 :             m_EntityData.insert(ent, entdata);
     561           3 :             break;
     562             :         }
     563        1038 :         case MT_PositionChanged:
     564             :         {
     565        1038 :             const CMessagePositionChanged& msgData = static_cast<const CMessagePositionChanged&> (msg);
     566        1038 :             entity_id_t ent = msgData.entity;
     567             : 
     568        1038 :             EntityMap<EntityData>::iterator it = m_EntityData.find(ent);
     569             : 
     570             :             // Ignore if we're not already tracking this entity
     571        1038 :             if (it == m_EntityData.end())
     572           0 :                 break;
     573             : 
     574        1038 :             if (msgData.inWorld)
     575             :             {
     576        1038 :                 if (it->second.HasFlag<FlagMasks::InWorld>())
     577             :                 {
     578        1035 :                     CFixedVector2D from(it->second.x, it->second.z);
     579        1035 :                     CFixedVector2D to(msgData.x, msgData.z);
     580        1035 :                     m_Subdivision.Move(ent, from, to, it->second.size);
     581        1035 :                     if (it->second.HasFlag<FlagMasks::SharedVision>())
     582           0 :                         SharingLosMove(it->second.visionSharing, it->second.visionRange, from, to);
     583             :                     else
     584        1035 :                         LosMove(it->second.owner, it->second.visionRange, from, to);
     585        1035 :                     LosRegion oldLosRegion = PosToLosRegionsHelper(it->second.x, it->second.z);
     586        1035 :                     LosRegion newLosRegion = PosToLosRegionsHelper(msgData.x, msgData.z);
     587        1035 :                     if (oldLosRegion != newLosRegion)
     588             :                     {
     589        1025 :                         RemoveFromRegion(oldLosRegion, ent);
     590        1025 :                         AddToRegion(newLosRegion, ent);
     591             :                     }
     592             :                 }
     593             :                 else
     594             :                 {
     595           3 :                     CFixedVector2D to(msgData.x, msgData.z);
     596           3 :                     m_Subdivision.Add(ent, to, it->second.size);
     597           3 :                     if (it->second.HasFlag<FlagMasks::SharedVision>())
     598           0 :                         SharingLosAdd(it->second.visionSharing, it->second.visionRange, to);
     599             :                     else
     600           3 :                         LosAdd(it->second.owner, it->second.visionRange, to);
     601           3 :                     AddToRegion(PosToLosRegionsHelper(msgData.x, msgData.z), ent);
     602             :                 }
     603             : 
     604        1038 :                 it->second.SetFlag<FlagMasks::InWorld>(true);
     605        1038 :                 it->second.x = msgData.x;
     606        1038 :                 it->second.z = msgData.z;
     607             :             }
     608             :             else
     609             :             {
     610           0 :                 if (it->second.HasFlag<FlagMasks::InWorld>())
     611             :                 {
     612           0 :                     CFixedVector2D from(it->second.x, it->second.z);
     613           0 :                     m_Subdivision.Remove(ent, from, it->second.size);
     614           0 :                     if (it->second.HasFlag<FlagMasks::SharedVision>())
     615           0 :                         SharingLosRemove(it->second.visionSharing, it->second.visionRange, from);
     616             :                     else
     617           0 :                         LosRemove(it->second.owner, it->second.visionRange, from);
     618           0 :                     RemoveFromRegion(PosToLosRegionsHelper(it->second.x, it->second.z), ent);
     619             :                 }
     620             : 
     621           0 :                 it->second.SetFlag<FlagMasks::InWorld>(false);
     622           0 :                 it->second.x = entity_pos_t::Zero();
     623           0 :                 it->second.z = entity_pos_t::Zero();
     624             :             }
     625             : 
     626        1038 :             RequestVisibilityUpdate(ent);
     627             : 
     628        1038 :             break;
     629             :         }
     630          11 :         case MT_OwnershipChanged:
     631             :         {
     632          11 :             const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg);
     633          11 :             entity_id_t ent = msgData.entity;
     634             : 
     635          11 :             EntityMap<EntityData>::iterator it = m_EntityData.find(ent);
     636             : 
     637             :             // Ignore if we're not already tracking this entity
     638          11 :             if (it == m_EntityData.end())
     639           0 :                 break;
     640             : 
     641          11 :             if (it->second.HasFlag<FlagMasks::InWorld>())
     642             :             {
     643             :                 // Entity vision is taken into account in VisionSharingChanged
     644             :                 // when sharing component activated
     645           8 :                 if (!it->second.HasFlag<FlagMasks::SharedVision>())
     646             :                 {
     647           8 :                     CFixedVector2D pos(it->second.x, it->second.z);
     648           8 :                     LosRemove(it->second.owner, it->second.visionRange, pos);
     649           8 :                     LosAdd(msgData.to, it->second.visionRange, pos);
     650             :                 }
     651             : 
     652           8 :                 if (it->second.HasFlag<FlagMasks::RevealShore>())
     653             :                 {
     654           0 :                     RevealShore(it->second.owner, false);
     655           0 :                     RevealShore(msgData.to, true);
     656             :                 }
     657             :             }
     658             : 
     659          11 :             ENSURE(-128 <= msgData.to && msgData.to <= 127);
     660          11 :             it->second.owner = (i8)msgData.to;
     661             : 
     662          11 :             break;
     663             :         }
     664           2 :         case MT_Destroy:
     665             :         {
     666           2 :             const CMessageDestroy& msgData = static_cast<const CMessageDestroy&> (msg);
     667           2 :             entity_id_t ent = msgData.entity;
     668             : 
     669           2 :             EntityMap<EntityData>::iterator it = m_EntityData.find(ent);
     670             : 
     671             :             // Ignore if we're not already tracking this entity
     672           2 :             if (it == m_EntityData.end())
     673           2 :                 break;
     674             : 
     675           0 :             if (it->second.HasFlag<FlagMasks::InWorld>())
     676             :             {
     677           0 :                 m_Subdivision.Remove(ent, CFixedVector2D(it->second.x, it->second.z), it->second.size);
     678           0 :                 RemoveFromRegion(PosToLosRegionsHelper(it->second.x, it->second.z), ent);
     679             :             }
     680             : 
     681             :             // This will be called after Ownership's OnDestroy, so ownership will be set
     682             :             // to -1 already and we don't have to do a LosRemove here
     683           0 :             ENSURE(it->second.owner == -1);
     684             : 
     685           0 :             m_EntityData.erase(it);
     686             : 
     687           0 :             break;
     688             :         }
     689           0 :         case MT_VisionRangeChanged:
     690             :         {
     691           0 :             const CMessageVisionRangeChanged& msgData = static_cast<const CMessageVisionRangeChanged&> (msg);
     692           0 :             entity_id_t ent = msgData.entity;
     693             : 
     694           0 :             EntityMap<EntityData>::iterator it = m_EntityData.find(ent);
     695             : 
     696             :             // Ignore if we're not already tracking this entity
     697           0 :             if (it == m_EntityData.end())
     698           0 :                 break;
     699             : 
     700           0 :             CmpPtr<ICmpVision> cmpVision(GetSimContext(), ent);
     701           0 :             if (!cmpVision)
     702           0 :                 break;
     703             : 
     704           0 :             entity_pos_t oldRange = it->second.visionRange;
     705           0 :             entity_pos_t newRange = msgData.newRange;
     706             : 
     707             :             // If the range changed and the entity's in-world, we need to manually adjust it
     708             :             //  but if it's not in-world, we only need to set the new vision range
     709             : 
     710           0 :             it->second.visionRange = newRange;
     711             : 
     712           0 :             if (it->second.HasFlag<FlagMasks::InWorld>())
     713             :             {
     714           0 :                 CFixedVector2D pos(it->second.x, it->second.z);
     715           0 :                 if (it->second.HasFlag<FlagMasks::SharedVision>())
     716             :                 {
     717           0 :                     SharingLosRemove(it->second.visionSharing, oldRange, pos);
     718           0 :                     SharingLosAdd(it->second.visionSharing, newRange, pos);
     719             :                 }
     720             :                 else
     721             :                 {
     722           0 :                     LosRemove(it->second.owner, oldRange, pos);
     723           0 :                     LosAdd(it->second.owner, newRange, pos);
     724             :                 }
     725             :             }
     726             : 
     727           0 :             break;
     728             :         }
     729           0 :         case MT_VisionSharingChanged:
     730             :         {
     731           0 :             const CMessageVisionSharingChanged& msgData = static_cast<const CMessageVisionSharingChanged&> (msg);
     732           0 :             entity_id_t ent = msgData.entity;
     733             : 
     734           0 :             EntityMap<EntityData>::iterator it = m_EntityData.find(ent);
     735             : 
     736             :             // Ignore if we're not already tracking this entity
     737           0 :             if (it == m_EntityData.end())
     738           0 :                 break;
     739             : 
     740           0 :             ENSURE(msgData.player > 0 && msgData.player < MAX_LOS_PLAYER_ID+1);
     741           0 :             u16 visionChanged = CalcVisionSharingMask(msgData.player);
     742             : 
     743           0 :             if (!it->second.HasFlag<FlagMasks::SharedVision>())
     744             :             {
     745             :                 // Activation of the Vision Sharing
     746           0 :                 ENSURE(it->second.owner == (i8)msgData.player);
     747           0 :                 it->second.visionSharing = visionChanged;
     748           0 :                 it->second.SetFlag<FlagMasks::SharedVision>(true);
     749           0 :                 break;
     750             :             }
     751             : 
     752           0 :             if (it->second.HasFlag<FlagMasks::InWorld>())
     753             :             {
     754           0 :                 entity_pos_t range = it->second.visionRange;
     755           0 :                 CFixedVector2D pos(it->second.x, it->second.z);
     756           0 :                 if (msgData.add)
     757           0 :                     LosAdd(msgData.player, range, pos);
     758             :                 else
     759           0 :                     LosRemove(msgData.player, range, pos);
     760             :             }
     761             : 
     762           0 :             if (msgData.add)
     763           0 :                 it->second.visionSharing |= visionChanged;
     764             :             else
     765           0 :                 it->second.visionSharing &= ~visionChanged;
     766           0 :             break;
     767             :         }
     768           0 :         case MT_Update:
     769             :         {
     770           0 :             m_DebugOverlayDirty = true;
     771           0 :             ExecuteActiveQueries();
     772           0 :             UpdateVisibilityData();
     773           0 :             break;
     774             :         }
     775           0 :         case MT_RenderSubmit:
     776             :         {
     777           0 :             const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
     778           0 :             RenderSubmit(msgData.collector);
     779           0 :             break;
     780             :         }
     781             :         }
     782        1060 :     }
     783             : 
     784           2 :     void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1) override
     785             :     {
     786             :         // Don't support rectangular looking maps.
     787           2 :         ENSURE(x1-x0 == z1-z0);
     788           2 :         m_WorldX0 = x0;
     789           2 :         m_WorldZ0 = z0;
     790           2 :         m_WorldX1 = x1;
     791           2 :         m_WorldZ1 = z1;
     792           2 :         m_LosVerticesPerSide = ((x1 - x0) / LOS_TILE_SIZE).ToInt_RoundToZero() + 1;
     793             : 
     794           2 :         ResetDerivedData();
     795           2 :     }
     796             : 
     797        1037 :     void Verify() override
     798             :     {
     799             :         // Ignore if map not initialised yet
     800        1037 :         if (m_WorldX1.IsZero())
     801           0 :             return;
     802             : 
     803             :         // Check that calling ResetDerivedData (i.e. recomputing all the state from scratch)
     804             :         // does not affect the incrementally-computed state
     805             : 
     806        2074 :         std::array<Grid<u16>, MAX_LOS_PLAYER_ID> oldPlayerCounts = m_LosPlayerCounts;
     807        2074 :         Grid<u32> oldStateRevealed = m_LosStateRevealed;
     808        2074 :         FastSpatialSubdivision oldSubdivision = m_Subdivision;
     809        2074 :         Grid<std::set<entity_id_t> > oldLosRegions = m_LosRegions;
     810             : 
     811        1037 :         m_Deserializing = true;
     812        1037 :         ResetDerivedData();
     813        1037 :         m_Deserializing = false;
     814             : 
     815        1037 :         if (oldPlayerCounts != m_LosPlayerCounts)
     816             :         {
     817           0 :             for (size_t id = 0; id < m_LosPlayerCounts.size(); ++id)
     818             :             {
     819           0 :                 debug_printf("player %zu\n", id);
     820           0 :                 for (size_t i = 0; i < oldPlayerCounts[id].width(); ++i)
     821             :                 {
     822           0 :                     for (size_t j = 0; j < oldPlayerCounts[id].height(); ++j)
     823           0 :                         debug_printf("%i ", oldPlayerCounts[id].get(i,j));
     824           0 :                     debug_printf("\n");
     825             :                 }
     826             :             }
     827           0 :             for (size_t id = 0; id < m_LosPlayerCounts.size(); ++id)
     828             :             {
     829           0 :                 debug_printf("player %zu\n", id);
     830           0 :                 for (size_t i = 0; i < m_LosPlayerCounts[id].width(); ++i)
     831             :                 {
     832           0 :                     for (size_t j = 0; j < m_LosPlayerCounts[id].height(); ++j)
     833           0 :                         debug_printf("%i ", m_LosPlayerCounts[id].get(i,j));
     834           0 :                     debug_printf("\n");
     835             :                 }
     836             :             }
     837           0 :             debug_warn(L"inconsistent player counts");
     838             :         }
     839        1037 :         if (oldStateRevealed != m_LosStateRevealed)
     840           0 :             debug_warn(L"inconsistent revealed");
     841        1037 :         if (oldSubdivision != m_Subdivision)
     842           0 :             debug_warn(L"inconsistent subdivs");
     843        1037 :         if (oldLosRegions != m_LosRegions)
     844           0 :             debug_warn(L"inconsistent los regions");
     845             :     }
     846             : 
     847           0 :     FastSpatialSubdivision* GetSubdivision() override
     848             :     {
     849           0 :         return &m_Subdivision;
     850             :     }
     851             : 
     852             :     // Reinitialise subdivisions and LOS data, based on entity data
     853        1039 :     void ResetDerivedData()
     854             :     {
     855        1039 :         ENSURE(m_WorldX0.IsZero() && m_WorldZ0.IsZero()); // don't bother implementing non-zero offsets yet
     856        1039 :         ResetSubdivisions(m_WorldX1, m_WorldZ1);
     857             : 
     858        1039 :         m_LosRegionsPerSide = m_LosVerticesPerSide / LOS_REGION_RATIO;
     859             : 
     860       17663 :         for (size_t player_id = 0; player_id < m_LosPlayerCounts.size(); ++player_id)
     861       16624 :             m_LosPlayerCounts[player_id].clear();
     862             : 
     863        1039 :         m_ExploredVertices.clear();
     864        1039 :         m_ExploredVertices.resize(MAX_LOS_PLAYER_ID+1, 0);
     865             : 
     866        1039 :         if (m_Deserializing)
     867             :         {
     868             :             // recalc current exploration stats.
     869      134810 :             for (i32 j = 0; j < m_LosVerticesPerSide; j++)
     870    17390490 :                 for (i32 i = 0; i < m_LosVerticesPerSide; i++)
     871    17256717 :                     if (!LosIsOffWorld(i, j))
     872   266709141 :                         for (u8 k = 1; k < MAX_LOS_PLAYER_ID+1; ++k)
     873   251020368 :                             m_ExploredVertices.at(k) += ((m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*(k-1)))) > 0);
     874             :         } else
     875           2 :             m_LosState.resize(m_LosVerticesPerSide, m_LosVerticesPerSide);
     876             : 
     877        1039 :         m_LosStateRevealed.resize(m_LosVerticesPerSide, m_LosVerticesPerSide);
     878             : 
     879        1039 :         if (!m_Deserializing)
     880             :         {
     881           2 :             m_DirtyVisibility.resize(m_LosRegionsPerSide, m_LosRegionsPerSide);
     882             :         }
     883        1039 :         ENSURE(m_DirtyVisibility.width() == m_LosRegionsPerSide);
     884        1039 :         ENSURE(m_DirtyVisibility.height() == m_LosRegionsPerSide);
     885             : 
     886        1039 :         m_LosRegions.resize(m_LosRegionsPerSide, m_LosRegionsPerSide);
     887             : 
     888        2074 :         for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
     889        1035 :             if (it->second.HasFlag<FlagMasks::InWorld>())
     890             :             {
     891        1033 :                 if (it->second.HasFlag<FlagMasks::SharedVision>())
     892           0 :                     SharingLosAdd(it->second.visionSharing, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z));
     893             :                 else
     894        1033 :                     LosAdd(it->second.owner, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z));
     895        1033 :                 AddToRegion(PosToLosRegionsHelper(it->second.x, it->second.z), it->first);
     896             : 
     897        1033 :                 if (it->second.HasFlag<FlagMasks::RevealShore>())
     898           0 :                     RevealShore(it->second.owner, true);
     899             :             }
     900             : 
     901        1039 :         m_TotalInworldVertices = 0;
     902      135070 :         for (i32 j = 0; j < m_LosVerticesPerSide; ++j)
     903    17424030 :             for (i32 i = 0; i < m_LosVerticesPerSide; ++i)
     904             :             {
     905    17289999 :                 if (LosIsOffWorld(i,j))
     906     1570968 :                     m_LosStateRevealed.get(i, j) = 0;
     907             :                 else
     908             :                 {
     909    15719031 :                     m_LosStateRevealed.get(i, j) = 0xFFFFFFFFu;
     910    15719031 :                     m_TotalInworldVertices++;
     911             :                 }
     912             :             }
     913        1039 :     }
     914             : 
     915        1045 :     void ResetSubdivisions(entity_pos_t x1, entity_pos_t z1)
     916             :     {
     917        1045 :         m_Subdivision.Reset(x1, z1);
     918             : 
     919        2080 :         for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
     920        1035 :             if (it->second.HasFlag<FlagMasks::InWorld>())
     921        1033 :                 m_Subdivision.Add(it->first, CFixedVector2D(it->second.x, it->second.z), it->second.size);
     922        1045 :     }
     923             : 
     924           0 :     tag_t CreateActiveQuery(entity_id_t source,
     925             :         entity_pos_t minRange, entity_pos_t maxRange,
     926             :         const std::vector<int>& owners, int requiredInterface, u8 flags, bool accountForSize) override
     927             :     {
     928           0 :         tag_t id = m_QueryNext++;
     929           0 :         m_Queries[id] = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flags, accountForSize);
     930             : 
     931           0 :         return id;
     932             :     }
     933             : 
     934           0 :     tag_t CreateActiveParabolicQuery(entity_id_t source,
     935             :         entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t yOrigin,
     936             :         const std::vector<int>& owners, int requiredInterface, u8 flags) override
     937             :     {
     938           0 :         tag_t id = m_QueryNext++;
     939           0 :         m_Queries[id] = ConstructParabolicQuery(source, minRange, maxRange, yOrigin, owners, requiredInterface, flags, true);
     940             : 
     941           0 :         return id;
     942             :     }
     943             : 
     944           0 :     void DestroyActiveQuery(tag_t tag) override
     945             :     {
     946           0 :         if (m_Queries.find(tag) == m_Queries.end())
     947             :         {
     948           0 :             LOGERROR("CCmpRangeManager: DestroyActiveQuery called with invalid tag %u", tag);
     949           0 :             return;
     950             :         }
     951             : 
     952           0 :         m_Queries.erase(tag);
     953             :     }
     954             : 
     955           0 :     void EnableActiveQuery(tag_t tag) override
     956             :     {
     957           0 :         std::map<tag_t, Query>::iterator it = m_Queries.find(tag);
     958           0 :         if (it == m_Queries.end())
     959             :         {
     960           0 :             LOGERROR("CCmpRangeManager: EnableActiveQuery called with invalid tag %u", tag);
     961           0 :             return;
     962             :         }
     963             : 
     964           0 :         Query& q = it->second;
     965           0 :         q.enabled = true;
     966             :     }
     967             : 
     968           0 :     void DisableActiveQuery(tag_t tag) override
     969             :     {
     970           0 :         std::map<tag_t, Query>::iterator it = m_Queries.find(tag);
     971           0 :         if (it == m_Queries.end())
     972             :         {
     973           0 :             LOGERROR("CCmpRangeManager: DisableActiveQuery called with invalid tag %u", tag);
     974           0 :             return;
     975             :         }
     976             : 
     977           0 :         Query& q = it->second;
     978           0 :         q.enabled = false;
     979             :     }
     980             : 
     981           0 :     bool IsActiveQueryEnabled(tag_t tag) const override
     982             :     {
     983           0 :         std::map<tag_t, Query>::const_iterator it = m_Queries.find(tag);
     984           0 :         if (it == m_Queries.end())
     985             :         {
     986           0 :             LOGERROR("CCmpRangeManager: IsActiveQueryEnabled called with invalid tag %u", tag);
     987           0 :             return false;
     988             :         }
     989             : 
     990           0 :         const Query& q = it->second;
     991           0 :         return q.enabled;
     992             :     }
     993             : 
     994           0 :     std::vector<entity_id_t> ExecuteQueryAroundPos(const CFixedVector2D& pos,
     995             :         entity_pos_t minRange, entity_pos_t maxRange,
     996             :         const std::vector<int>& owners, int requiredInterface, bool accountForSize) override
     997             :     {
     998           0 :         Query q = ConstructQuery(INVALID_ENTITY, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"), accountForSize);
     999           0 :         std::vector<entity_id_t> r;
    1000           0 :         PerformQuery(q, r, pos);
    1001             : 
    1002             :         // Return the list sorted by distance from the entity
    1003           0 :         std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
    1004             : 
    1005           0 :         return r;
    1006             :     }
    1007             : 
    1008          13 :     std::vector<entity_id_t> ExecuteQuery(entity_id_t source,
    1009             :         entity_pos_t minRange, entity_pos_t maxRange,
    1010             :         const std::vector<int>& owners, int requiredInterface, bool accountForSize) override
    1011             :     {
    1012          26 :         PROFILE("ExecuteQuery");
    1013             : 
    1014          26 :         Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"), accountForSize);
    1015             : 
    1016          13 :         std::vector<entity_id_t> r;
    1017             : 
    1018          13 :         CmpPtr<ICmpPosition> cmpSourcePosition(q.source);
    1019          13 :         if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
    1020             :         {
    1021             :             // If the source doesn't have a position, then the result is just the empty list
    1022           0 :             return r;
    1023             :         }
    1024             : 
    1025          13 :         CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
    1026          13 :         PerformQuery(q, r, pos);
    1027             : 
    1028             :         // Return the list sorted by distance from the entity
    1029          13 :         std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
    1030             : 
    1031          13 :         return r;
    1032             :     }
    1033             : 
    1034           0 :     std::vector<entity_id_t> ResetActiveQuery(tag_t tag) override
    1035             :     {
    1036           0 :         PROFILE("ResetActiveQuery");
    1037             : 
    1038           0 :         std::vector<entity_id_t> r;
    1039             : 
    1040           0 :         std::map<tag_t, Query>::iterator it = m_Queries.find(tag);
    1041           0 :         if (it == m_Queries.end())
    1042             :         {
    1043           0 :             LOGERROR("CCmpRangeManager: ResetActiveQuery called with invalid tag %u", tag);
    1044           0 :             return r;
    1045             :         }
    1046             : 
    1047           0 :         Query& q = it->second;
    1048           0 :         q.enabled = true;
    1049             : 
    1050           0 :         CmpPtr<ICmpPosition> cmpSourcePosition(q.source);
    1051           0 :         if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
    1052             :         {
    1053             :             // If the source doesn't have a position, then the result is just the empty list
    1054           0 :             q.lastMatch = r;
    1055           0 :             return r;
    1056             :         }
    1057             : 
    1058           0 :         CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
    1059           0 :         PerformQuery(q, r, pos);
    1060             : 
    1061           0 :         q.lastMatch = r;
    1062             : 
    1063             :         // Return the list sorted by distance from the entity
    1064           0 :         std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
    1065             : 
    1066           0 :         return r;
    1067             :     }
    1068             : 
    1069          64 :     std::vector<entity_id_t> GetEntitiesByPlayer(player_id_t player) const override
    1070             :     {
    1071          64 :         return GetEntitiesByMask(CalcOwnerMask(player));
    1072             :     }
    1073             : 
    1074           8 :     std::vector<entity_id_t> GetNonGaiaEntities() const override
    1075             :     {
    1076           8 :         return GetEntitiesByMask(~3u); // bit 0 for owner=-1 and bit 1 for gaia
    1077             :     }
    1078             : 
    1079           0 :     std::vector<entity_id_t> GetGaiaAndNonGaiaEntities() const override
    1080             :     {
    1081           0 :         return GetEntitiesByMask(~1u); // bit 0 for owner=-1
    1082             :     }
    1083             : 
    1084          72 :     std::vector<entity_id_t> GetEntitiesByMask(u32 ownerMask) const
    1085             :     {
    1086          72 :         std::vector<entity_id_t> entities;
    1087             : 
    1088         144 :         for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
    1089             :         {
    1090             :             // Check owner and add to list if it matches
    1091          72 :             if (CalcOwnerMask(it->second.owner) & ownerMask)
    1092          15 :                 entities.push_back(it->first);
    1093             :         }
    1094             : 
    1095          72 :         return entities;
    1096             :     }
    1097             : 
    1098           0 :     void SetDebugOverlay(bool enabled) override
    1099             :     {
    1100           0 :         m_DebugOverlayEnabled = enabled;
    1101           0 :         m_DebugOverlayDirty = true;
    1102           0 :         if (!enabled)
    1103           0 :             m_DebugOverlayLines.clear();
    1104           0 :     }
    1105             : 
    1106             :     /**
    1107             :      * Update all currently-enabled active queries.
    1108             :      */
    1109           0 :     void ExecuteActiveQueries()
    1110             :     {
    1111           0 :         PROFILE3("ExecuteActiveQueries");
    1112             : 
    1113             :         // Store a queue of all messages before sending any, so we can assume
    1114             :         // no entities will move until we've finished checking all the ranges
    1115           0 :         std::vector<std::pair<entity_id_t, CMessageRangeUpdate> > messages;
    1116           0 :         std::vector<entity_id_t> results;
    1117           0 :         std::vector<entity_id_t> added;
    1118           0 :         std::vector<entity_id_t> removed;
    1119             : 
    1120           0 :         for (std::map<tag_t, Query>::iterator it = m_Queries.begin(); it != m_Queries.end(); ++it)
    1121             :         {
    1122           0 :             Query& query = it->second;
    1123             : 
    1124           0 :             if (!query.enabled)
    1125           0 :                 continue;
    1126             : 
    1127           0 :             results.clear();
    1128           0 :             CmpPtr<ICmpPosition> cmpSourcePosition(query.source);
    1129           0 :             if (cmpSourcePosition && cmpSourcePosition->IsInWorld())
    1130             :             {
    1131           0 :                 results.reserve(query.lastMatch.size());
    1132           0 :                 PerformQuery(query, results, cmpSourcePosition->GetPosition2D());
    1133             :             }
    1134             : 
    1135             :             // Compute the changes vs the last match
    1136           0 :             added.clear();
    1137           0 :             removed.clear();
    1138             :             // Return the 'added' list sorted by distance from the entity
    1139             :             // (Don't bother sorting 'removed' because they might not even have positions or exist any more)
    1140           0 :             std::set_difference(results.begin(), results.end(), query.lastMatch.begin(), query.lastMatch.end(),
    1141           0 :                 std::back_inserter(added));
    1142           0 :             std::set_difference(query.lastMatch.begin(), query.lastMatch.end(), results.begin(), results.end(),
    1143           0 :                 std::back_inserter(removed));
    1144           0 :             if (added.empty() && removed.empty())
    1145           0 :                 continue;
    1146             : 
    1147           0 :             if (cmpSourcePosition && cmpSourcePosition->IsInWorld())
    1148           0 :                 std::stable_sort(added.begin(), added.end(), EntityDistanceOrdering(m_EntityData, cmpSourcePosition->GetPosition2D()));
    1149             : 
    1150           0 :             messages.resize(messages.size() + 1);
    1151           0 :             std::pair<entity_id_t, CMessageRangeUpdate>& back = messages.back();
    1152           0 :             back.first = query.source.GetId();
    1153           0 :             back.second.tag = it->first;
    1154           0 :             back.second.added.swap(added);
    1155           0 :             back.second.removed.swap(removed);
    1156           0 :             query.lastMatch.swap(results);
    1157             :         }
    1158             : 
    1159           0 :         CComponentManager& cmpMgr = GetSimContext().GetComponentManager();
    1160           0 :         for (size_t i = 0; i < messages.size(); ++i)
    1161           0 :             cmpMgr.PostMessage(messages[i].first, messages[i].second);
    1162           0 :     }
    1163             : 
    1164             :     /**
    1165             :      * Returns whether the given entity matches the given query (ignoring maxRange)
    1166             :      */
    1167          26 :     bool TestEntityQuery(const Query& q, entity_id_t id, const EntityData& entity) const
    1168             :     {
    1169             :         // Quick filter to ignore entities with the wrong owner
    1170          26 :         if (!(CalcOwnerMask(entity.owner) & q.ownersMask))
    1171           0 :             return false;
    1172             : 
    1173             :         // Ignore entities not present in the world
    1174          26 :         if (!entity.HasFlag<FlagMasks::InWorld>())
    1175           0 :             return false;
    1176             : 
    1177             :         // Ignore entities that don't match the current flags
    1178          26 :         if (!((entity.flags & FlagMasks::AllQuery) & q.flagsMask))
    1179           0 :             return false;
    1180             : 
    1181             :         // Ignore self
    1182          26 :         if (id == q.source.GetId())
    1183          13 :             return false;
    1184             : 
    1185             :         // Ignore if it's missing the required interface
    1186          13 :         if (q.interface && !GetSimContext().GetComponentManager().QueryInterface(id, q.interface))
    1187           0 :             return false;
    1188             : 
    1189          13 :         return true;
    1190             :     }
    1191             : 
    1192             :     /**
    1193             :      * Returns a list of distinct entity IDs that match the given query, sorted by ID.
    1194             :      */
    1195          13 :     void PerformQuery(const Query& q, std::vector<entity_id_t>& r, CFixedVector2D pos)
    1196             :     {
    1197             : 
    1198             :         // Special case: range is ALWAYS_IN_RANGE means check all entities ignoring distance.
    1199          13 :         if (q.maxRange == ALWAYS_IN_RANGE)
    1200             :         {
    1201           0 :             for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
    1202             :             {
    1203           0 :                 if (!TestEntityQuery(q, it->first, it->second))
    1204           0 :                     continue;
    1205             : 
    1206           0 :                 r.push_back(it->first);
    1207             :             }
    1208             :         }
    1209             :         // Not the entire world, so check a parabolic range, or a regular range.
    1210          13 :         else if (q.parabolic)
    1211             :         {
    1212             :             // The yOrigin is part of the 3D position, as the source is really that much heigher.
    1213           0 :             CmpPtr<ICmpPosition> cmpSourcePosition(q.source);
    1214           0 :             CFixedVector3D pos3d = cmpSourcePosition->GetPosition()+
    1215           0 :                 CFixedVector3D(entity_pos_t::Zero(), q.yOrigin, entity_pos_t::Zero()) ;
    1216             :             // Get a quick list of entities that are potentially in range, with a cutoff of 2*maxRange.
    1217           0 :             m_SubdivisionResults.clear();
    1218           0 :             m_Subdivision.GetNear(m_SubdivisionResults, pos, q.maxRange * 2);
    1219             : 
    1220           0 :             for (size_t i = 0; i < m_SubdivisionResults.size(); ++i)
    1221             :             {
    1222           0 :                 EntityMap<EntityData>::const_iterator it = m_EntityData.find(m_SubdivisionResults[i]);
    1223           0 :                 ENSURE(it != m_EntityData.end());
    1224             : 
    1225           0 :                 if (!TestEntityQuery(q, it->first, it->second))
    1226           0 :                     continue;
    1227             : 
    1228           0 :                 CmpPtr<ICmpPosition> cmpSecondPosition(GetSimContext(), m_SubdivisionResults[i]);
    1229           0 :                 if (!cmpSecondPosition || !cmpSecondPosition->IsInWorld())
    1230           0 :                     continue;
    1231           0 :                 CFixedVector3D secondPosition = cmpSecondPosition->GetPosition();
    1232             : 
    1233             :                 // Doing an exact check for parabolas with obstruction sizes is not really possible.
    1234             :                 // However, we can prove that InParabolicRange(d, range + size) > InParabolicRange(d, range)
    1235             :                 // in the sense that it always returns true when the latter would, which is enough.
    1236             :                 // To do so, compute the derivative with respect to distance, and notice that
    1237             :                 // they have an intersection after which the former grows slower, and then use that to prove the above.
    1238             :                 // Note that this is only true because we do not account for vertical size here,
    1239             :                 // if we did, we would also need to artificially 'raise' the source over the target.
    1240           0 :                 entity_pos_t range = q.maxRange + (q.accountForSize ? fixed::FromInt(it->second.size) : fixed::Zero());
    1241           0 :                 if (!InParabolicRange(CFixedVector3D(it->second.x, secondPosition.Y, it->second.z) - pos3d, range))
    1242           0 :                     continue;
    1243             : 
    1244           0 :                 if (!q.minRange.IsZero())
    1245           0 :                     if ((CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange) < 0)
    1246           0 :                         continue;
    1247             : 
    1248           0 :                 r.push_back(it->first);
    1249             :             }
    1250           0 :             std::sort(r.begin(), r.end());
    1251             :         }
    1252             :         // check a regular range (i.e. not the entire world, and not parabolic)
    1253             :         else
    1254             :         {
    1255             :             // Get a quick list of entities that are potentially in range
    1256          13 :             m_SubdivisionResults.clear();
    1257          13 :             m_Subdivision.GetNear(m_SubdivisionResults, pos, q.maxRange);
    1258             : 
    1259          39 :             for (size_t i = 0; i < m_SubdivisionResults.size(); ++i)
    1260             :             {
    1261          26 :                 EntityMap<EntityData>::const_iterator it = m_EntityData.find(m_SubdivisionResults[i]);
    1262          26 :                 ENSURE(it != m_EntityData.end());
    1263             : 
    1264          26 :                 if (!TestEntityQuery(q, it->first, it->second))
    1265          31 :                     continue;
    1266             : 
    1267             :                 // Restrict based on approximate circle-circle distance.
    1268          13 :                 entity_pos_t range = q.maxRange + (q.accountForSize ? fixed::FromInt(it->second.size) : fixed::Zero());
    1269          13 :                 if ((CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(range) > 0)
    1270           1 :                     continue;
    1271             : 
    1272          12 :                 if (!q.minRange.IsZero())
    1273           8 :                     if ((CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange) < 0)
    1274           4 :                         continue;
    1275             : 
    1276           8 :                 r.push_back(it->first);
    1277             :             }
    1278          13 :             std::sort(r.begin(), r.end());
    1279             :         }
    1280          13 :     }
    1281             : 
    1282           8 :     entity_pos_t GetEffectiveParabolicRange(entity_id_t source, entity_id_t target, entity_pos_t range, entity_pos_t yOrigin) const override
    1283             :     {
    1284             :         // For non-positive ranges, just return the range.
    1285           8 :         if (range < entity_pos_t::Zero())
    1286           1 :             return range;
    1287             : 
    1288           7 :         CmpPtr<ICmpPosition> cmpSourcePosition(GetSimContext(), source);
    1289           7 :         if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
    1290           1 :             return NEVER_IN_RANGE;
    1291             : 
    1292           6 :         CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), target);
    1293           6 :         if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
    1294           1 :             return NEVER_IN_RANGE;
    1295             : 
    1296           5 :         entity_pos_t heightDifference = cmpSourcePosition->GetHeightOffset() - cmpTargetPosition->GetHeightOffset() + yOrigin;
    1297           5 :         if (heightDifference < -range / 2)
    1298           1 :             return NEVER_IN_RANGE;
    1299             : 
    1300           4 :         entity_pos_t effectiveRange;
    1301           4 :         effectiveRange.SetInternalValue(static_cast<i32>(isqrt64(SQUARE_U64_FIXED(range) + static_cast<i64>(heightDifference.GetInternalValue()) * static_cast<i64>(range.GetInternalValue()) * 2)));
    1302           4 :         return effectiveRange;
    1303             :     }
    1304             : 
    1305           0 :     entity_pos_t GetElevationAdaptedRange(const CFixedVector3D& pos1, const CFixedVector3D& rot, entity_pos_t range, entity_pos_t yOrigin, entity_pos_t angle) const override
    1306             :     {
    1307           0 :         entity_pos_t r = entity_pos_t::Zero();
    1308           0 :         CFixedVector3D pos(pos1);
    1309             : 
    1310           0 :         pos.Y += yOrigin;
    1311           0 :         entity_pos_t orientation = rot.Y;
    1312             : 
    1313           0 :         entity_pos_t maxAngle = orientation + angle/2;
    1314           0 :         entity_pos_t minAngle = orientation - angle/2;
    1315             : 
    1316           0 :         int numberOfSteps = 16;
    1317             : 
    1318           0 :         if (angle == entity_pos_t::Zero())
    1319           0 :             numberOfSteps = 1;
    1320             : 
    1321           0 :         std::vector<entity_pos_t> coords = getParabolicRangeForm(pos, range, range*2, minAngle, maxAngle, numberOfSteps);
    1322             : 
    1323           0 :         entity_pos_t part = entity_pos_t::FromInt(numberOfSteps);
    1324             : 
    1325           0 :         for (int i = 0; i < numberOfSteps; ++i)
    1326           0 :             r = r + CFixedVector2D(coords[2*i],coords[2*i+1]).Length() / part;
    1327             : 
    1328           0 :         return r;
    1329             : 
    1330             :     }
    1331             : 
    1332           0 :     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) const
    1333             :     {
    1334           0 :         std::vector<entity_pos_t> r;
    1335             : 
    1336           0 :         CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
    1337           0 :         if (!cmpTerrain)
    1338           0 :             return r;
    1339             : 
    1340             :         // angle = 0 goes in the positive Z direction
    1341           0 :         u64 precisionSquared = SQUARE_U64_FIXED(PARABOLIC_RANGE_TOLERANCE);
    1342             : 
    1343           0 :         CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity());
    1344           0 :         entity_pos_t waterLevel = cmpWaterManager ? cmpWaterManager->GetWaterLevel(pos.X, pos.Z) : entity_pos_t::Zero();
    1345           0 :         entity_pos_t thisHeight = pos.Y > waterLevel ? pos.Y : waterLevel;
    1346             : 
    1347           0 :         for (int i = 0; i < numberOfSteps; ++i)
    1348             :         {
    1349           0 :             entity_pos_t angle = minAngle + (maxAngle - minAngle) / numberOfSteps * i;
    1350           0 :             entity_pos_t sin;
    1351           0 :             entity_pos_t cos;
    1352           0 :             entity_pos_t minDistance = entity_pos_t::Zero();
    1353           0 :             entity_pos_t maxDistance = cutoff;
    1354           0 :             sincos_approx(angle, sin, cos);
    1355             : 
    1356           0 :             CFixedVector2D minVector = CFixedVector2D(entity_pos_t::Zero(), entity_pos_t::Zero());
    1357           0 :             CFixedVector2D maxVector = CFixedVector2D(sin, cos).Multiply(cutoff);
    1358           0 :             entity_pos_t targetHeight = cmpTerrain->GetGroundLevel(pos.X+maxVector.X, pos.Z+maxVector.Y);
    1359             :             // use water level to display range on water
    1360           0 :             targetHeight = targetHeight > waterLevel ? targetHeight : waterLevel;
    1361             : 
    1362           0 :             if (InParabolicRange(CFixedVector3D(maxVector.X, targetHeight-thisHeight, maxVector.Y), maxRange))
    1363             :             {
    1364           0 :                 r.push_back(maxVector.X);
    1365           0 :                 r.push_back(maxVector.Y);
    1366           0 :                 continue;
    1367             :             }
    1368             : 
    1369             :             // Loop until vectors come close enough
    1370           0 :             while ((maxVector - minVector).CompareLengthSquared(precisionSquared) > 0)
    1371             :             {
    1372             :                 // difference still bigger than precision, bisect to get smaller difference
    1373           0 :                 entity_pos_t newDistance = (minDistance+maxDistance)/entity_pos_t::FromInt(2);
    1374             : 
    1375           0 :                 CFixedVector2D newVector = CFixedVector2D(sin, cos).Multiply(newDistance);
    1376             : 
    1377             :                 // get the height of the ground
    1378           0 :                 targetHeight = cmpTerrain->GetGroundLevel(pos.X+newVector.X, pos.Z+newVector.Y);
    1379           0 :                 targetHeight = targetHeight > waterLevel ? targetHeight : waterLevel;
    1380             : 
    1381           0 :                 if (InParabolicRange(CFixedVector3D(newVector.X, targetHeight-thisHeight, newVector.Y), maxRange))
    1382             :                 {
    1383             :                     // new vector is in parabolic range, so this is a new minVector
    1384           0 :                     minVector = newVector;
    1385           0 :                     minDistance = newDistance;
    1386             :                 }
    1387             :                 else
    1388             :                 {
    1389             :                     // new vector is out parabolic range, so this is a new maxVector
    1390           0 :                     maxVector = newVector;
    1391           0 :                     maxDistance = newDistance;
    1392             :                 }
    1393             : 
    1394             :             }
    1395           0 :             r.push_back(maxVector.X);
    1396           0 :             r.push_back(maxVector.Y);
    1397             : 
    1398             :         }
    1399           0 :         r.push_back(r[0]);
    1400           0 :         r.push_back(r[1]);
    1401             : 
    1402           0 :         return r;
    1403             :     }
    1404             : 
    1405          13 :     Query ConstructQuery(entity_id_t source,
    1406             :         entity_pos_t minRange, entity_pos_t maxRange,
    1407             :         const std::vector<int>& owners, int requiredInterface, u8 flagsMask, bool accountForSize) const
    1408             :     {
    1409             :         // Min range must be non-negative.
    1410          13 :         if (minRange < entity_pos_t::Zero())
    1411           0 :             LOGWARNING("CCmpRangeManager: Invalid min range %f in query for entity %u", minRange.ToDouble(), source);
    1412             : 
    1413             :         // Max range must be non-negative, or else ALWAYS_IN_RANGE.
    1414             :         // TODO add NEVER_IN_RANGE.
    1415          13 :         if (maxRange < entity_pos_t::Zero() && maxRange != ALWAYS_IN_RANGE)
    1416           0 :             LOGWARNING("CCmpRangeManager: Invalid max range %f in query for entity %u", maxRange.ToDouble(), source);
    1417             : 
    1418          13 :         Query q;
    1419          13 :         q.enabled = false;
    1420          13 :         q.parabolic = false;
    1421          13 :         q.source = GetSimContext().GetComponentManager().LookupEntityHandle(source);
    1422          13 :         q.minRange = minRange;
    1423          13 :         q.maxRange = maxRange;
    1424          13 :         q.yOrigin = entity_pos_t::Zero();
    1425          13 :         q.accountForSize = accountForSize;
    1426             : 
    1427          13 :         if (q.accountForSize && q.source.GetId() != INVALID_ENTITY && q.maxRange != ALWAYS_IN_RANGE)
    1428             :         {
    1429          13 :             u32 size = 0;
    1430          13 :             if (ENTITY_IS_LOCAL(q.source.GetId()))
    1431             :             {
    1432           0 :                 CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), q.source.GetId());
    1433           0 :                 if (cmpObstruction)
    1434           0 :                     size = cmpObstruction->GetSize().ToInt_RoundToInfinity();
    1435             :             }
    1436             :             else
    1437             :             {
    1438          13 :                 EntityMap<EntityData>::const_iterator it = m_EntityData.find(q.source.GetId());
    1439          13 :                 if (it != m_EntityData.end())
    1440          13 :                     size = it->second.size;
    1441             :             }
    1442             :             // Adjust the range query based on the querier's obstruction radius.
    1443             :             // The smallest side of the obstruction isn't known here, so we can't safely adjust the min-range, only the max.
    1444             :             // 'size' is the diagonal size rounded up so this will cover all possible rotations of the querier.
    1445          13 :             q.maxRange += fixed::FromInt(size);
    1446             :         }
    1447             : 
    1448          13 :         q.ownersMask = 0;
    1449          26 :         for (size_t i = 0; i < owners.size(); ++i)
    1450          13 :             q.ownersMask |= CalcOwnerMask(owners[i]);
    1451             : 
    1452          13 :         if (q.ownersMask == 0)
    1453           0 :             LOGWARNING("CCmpRangeManager: No owners in query for entity %u", source);
    1454             : 
    1455          13 :         q.interface = requiredInterface;
    1456          13 :         q.flagsMask = flagsMask;
    1457             : 
    1458          13 :         return q;
    1459             :     }
    1460             : 
    1461           0 :     Query ConstructParabolicQuery(entity_id_t source,
    1462             :         entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t yOrigin,
    1463             :         const std::vector<int>& owners, int requiredInterface, u8 flagsMask, bool accountForSize) const
    1464             :     {
    1465           0 :         Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flagsMask, accountForSize);
    1466           0 :         q.parabolic = true;
    1467           0 :         q.yOrigin = yOrigin;
    1468           0 :         return q;
    1469             :     }
    1470             : 
    1471           0 :     void RenderSubmit(SceneCollector& collector)
    1472             :     {
    1473           0 :         if (!m_DebugOverlayEnabled)
    1474           0 :             return;
    1475           0 :         static CColor disabledRingColor(1, 0, 0, 1);    // red
    1476           0 :         static CColor enabledRingColor(0, 1, 0, 1); // green
    1477           0 :         static CColor subdivColor(0, 0, 1, 1);          // blue
    1478           0 :         static CColor rayColor(1, 1, 0, 0.2f);
    1479             : 
    1480           0 :         if (m_DebugOverlayDirty)
    1481             :         {
    1482           0 :             m_DebugOverlayLines.clear();
    1483             : 
    1484           0 :             for (std::map<tag_t, Query>::iterator it = m_Queries.begin(); it != m_Queries.end(); ++it)
    1485             :             {
    1486           0 :                 Query& q = it->second;
    1487             : 
    1488           0 :                 CmpPtr<ICmpPosition> cmpSourcePosition(q.source);
    1489           0 :                 if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
    1490           0 :                     continue;
    1491           0 :                 CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
    1492             : 
    1493             :                 // Draw the max range circle
    1494           0 :                 if (!q.parabolic)
    1495             :                 {
    1496           0 :                     m_DebugOverlayLines.push_back(SOverlayLine());
    1497           0 :                     m_DebugOverlayLines.back().m_Color = (q.enabled ? enabledRingColor : disabledRingColor);
    1498           0 :                     SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToFloat(), q.maxRange.ToFloat(), m_DebugOverlayLines.back(), true);
    1499             :                 }
    1500             :                 else
    1501             :                 {
    1502             :                     // yOrigin is part of the 3D position. As if the unit is really that much higher.
    1503           0 :                     CFixedVector3D pos3D = cmpSourcePosition->GetPosition();
    1504           0 :                     pos3D.Y += q.yOrigin;
    1505             : 
    1506           0 :                     std::vector<entity_pos_t> coords;
    1507             : 
    1508             :                     // Get the outline from cache if possible
    1509           0 :                     if (ParabolicRangesOutlines.find(q.source.GetId()) != ParabolicRangesOutlines.end())
    1510             :                     {
    1511           0 :                         EntityParabolicRangeOutline e = ParabolicRangesOutlines[q.source.GetId()];
    1512           0 :                         if (e.position == pos3D && e.range == q.maxRange)
    1513             :                         {
    1514             :                             // outline is cached correctly, use it
    1515           0 :                             coords = e.outline;
    1516             :                         }
    1517             :                         else
    1518             :                         {
    1519             :                             // outline was cached, but important parameters changed
    1520             :                             // (position, elevation, range)
    1521             :                             // update it
    1522           0 :                             coords = getParabolicRangeForm(pos3D,q.maxRange,q.maxRange*2, entity_pos_t::Zero(), entity_pos_t::FromFloat(2.0f*3.14f),70);
    1523           0 :                             e.outline = coords;
    1524           0 :                             e.range = q.maxRange;
    1525           0 :                             e.position = pos3D;
    1526           0 :                             ParabolicRangesOutlines[q.source.GetId()] = e;
    1527             :                         }
    1528             :                     }
    1529             :                     else
    1530             :                     {
    1531             :                         // outline wasn't cached (first time you enable the range overlay
    1532             :                         // or you created a new entiy)
    1533             :                         // cache a new outline
    1534           0 :                         coords = getParabolicRangeForm(pos3D,q.maxRange,q.maxRange*2, entity_pos_t::Zero(), entity_pos_t::FromFloat(2.0f*3.14f),70);
    1535           0 :                         EntityParabolicRangeOutline e;
    1536           0 :                         e.source = q.source.GetId();
    1537           0 :                         e.range = q.maxRange;
    1538           0 :                         e.position = pos3D;
    1539           0 :                         e.outline = coords;
    1540           0 :                         ParabolicRangesOutlines[q.source.GetId()] = e;
    1541             :                     }
    1542             : 
    1543           0 :                     CColor thiscolor = q.enabled ? enabledRingColor : disabledRingColor;
    1544             : 
    1545             :                     // draw the outline (piece by piece)
    1546           0 :                     for (size_t i = 3; i < coords.size(); i += 2)
    1547             :                     {
    1548           0 :                         std::vector<float> c;
    1549           0 :                         c.push_back((coords[i - 3] + pos3D.X).ToFloat());
    1550           0 :                         c.push_back((coords[i - 2] + pos3D.Z).ToFloat());
    1551           0 :                         c.push_back((coords[i - 1] + pos3D.X).ToFloat());
    1552           0 :                         c.push_back((coords[i] + pos3D.Z).ToFloat());
    1553           0 :                         m_DebugOverlayLines.push_back(SOverlayLine());
    1554           0 :                         m_DebugOverlayLines.back().m_Color = thiscolor;
    1555           0 :                         SimRender::ConstructLineOnGround(GetSimContext(), c, m_DebugOverlayLines.back(), true);
    1556             :                     }
    1557             :                 }
    1558             : 
    1559             :                 // Draw the min range circle
    1560           0 :                 if (!q.minRange.IsZero())
    1561           0 :                     SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToFloat(), q.minRange.ToFloat(), m_DebugOverlayLines.back(), true);
    1562             : 
    1563             :                 // Draw a ray from the source to each matched entity
    1564           0 :                 for (size_t i = 0; i < q.lastMatch.size(); ++i)
    1565             :                 {
    1566           0 :                     CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), q.lastMatch[i]);
    1567           0 :                     if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
    1568           0 :                         continue;
    1569           0 :                     CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
    1570             : 
    1571           0 :                     std::vector<float> coords;
    1572           0 :                     coords.push_back(pos.X.ToFloat());
    1573           0 :                     coords.push_back(pos.Y.ToFloat());
    1574           0 :                     coords.push_back(targetPos.X.ToFloat());
    1575           0 :                     coords.push_back(targetPos.Y.ToFloat());
    1576             : 
    1577           0 :                     m_DebugOverlayLines.push_back(SOverlayLine());
    1578           0 :                     m_DebugOverlayLines.back().m_Color = rayColor;
    1579           0 :                     SimRender::ConstructLineOnGround(GetSimContext(), coords, m_DebugOverlayLines.back(), true);
    1580             :                 }
    1581             :             }
    1582             : 
    1583             :             // render subdivision grid
    1584           0 :             float divSize = m_Subdivision.GetDivisionSize();
    1585           0 :             int size = m_Subdivision.GetWidth();
    1586           0 :             for (int x = 0; x < size; ++x)
    1587             :             {
    1588           0 :                 for (int y = 0; y < size; ++y)
    1589             :                 {
    1590           0 :                     m_DebugOverlayLines.push_back(SOverlayLine());
    1591           0 :                     m_DebugOverlayLines.back().m_Color = subdivColor;
    1592             : 
    1593           0 :                     float xpos = x*divSize + divSize/2;
    1594           0 :                     float zpos = y*divSize + divSize/2;
    1595           0 :                     SimRender::ConstructSquareOnGround(GetSimContext(), xpos, zpos, divSize, divSize, 0.0f,
    1596           0 :                         m_DebugOverlayLines.back(), false, 1.0f);
    1597             :                 }
    1598             :             }
    1599             : 
    1600           0 :             m_DebugOverlayDirty = false;
    1601             :         }
    1602             : 
    1603           0 :         for (size_t i = 0; i < m_DebugOverlayLines.size(); ++i)
    1604           0 :             collector.Submit(&m_DebugOverlayLines[i]);
    1605             :     }
    1606             : 
    1607          13 :     u8 GetEntityFlagMask(const std::string& identifier) const override
    1608             :     {
    1609          13 :         if (identifier == "normal")
    1610          13 :             return FlagMasks::Normal;
    1611           0 :         if (identifier == "injured")
    1612           0 :             return FlagMasks::Injured;
    1613             : 
    1614           0 :         LOGWARNING("CCmpRangeManager: Invalid flag identifier %s", identifier.c_str());
    1615           0 :         return FlagMasks::None;
    1616             :     }
    1617             : 
    1618           0 :     void SetEntityFlag(entity_id_t ent, const std::string& identifier, bool value) override
    1619             :     {
    1620           0 :         EntityMap<EntityData>::iterator it = m_EntityData.find(ent);
    1621             : 
    1622             :         // We don't have this entity
    1623           0 :         if (it == m_EntityData.end())
    1624           0 :             return;
    1625             : 
    1626           0 :         u8 flag = GetEntityFlagMask(identifier);
    1627             : 
    1628           0 :         if (flag == FlagMasks::None)
    1629           0 :             LOGWARNING("CCmpRangeManager: Invalid flag identifier %s for entity %u", identifier.c_str(), ent);
    1630             :         else
    1631           0 :             it->second.SetFlag(flag, value);
    1632             :     }
    1633             : 
    1634             :     // ****************************************************************
    1635             : 
    1636             :     // LOS implementation:
    1637             : 
    1638           0 :     CLosQuerier GetLosQuerier(player_id_t player) const override
    1639             :     {
    1640           0 :         if (GetLosRevealAll(player))
    1641           0 :             return CLosQuerier(0xFFFFFFFFu, m_LosStateRevealed, m_LosVerticesPerSide);
    1642             :         else
    1643           0 :             return CLosQuerier(GetSharedLosMask(player), m_LosState, m_LosVerticesPerSide);
    1644             :     }
    1645             : 
    1646           0 :     void ActivateScriptedVisibility(entity_id_t ent, bool status) override
    1647             :     {
    1648           0 :         EntityMap<EntityData>::iterator it = m_EntityData.find(ent);
    1649           0 :         if (it != m_EntityData.end())
    1650           0 :             it->second.SetFlag<FlagMasks::ScriptedVisibility>(status);
    1651           0 :     }
    1652             : 
    1653           0 :     LosVisibility ComputeLosVisibility(CEntityHandle ent, player_id_t player) const
    1654             :     {
    1655             :         // Entities not with positions in the world are never visible
    1656           0 :         if (ent.GetId() == INVALID_ENTITY)
    1657           0 :             return LosVisibility::HIDDEN;
    1658           0 :         CmpPtr<ICmpPosition> cmpPosition(ent);
    1659           0 :         if (!cmpPosition || !cmpPosition->IsInWorld())
    1660           0 :             return LosVisibility::HIDDEN;
    1661             : 
    1662             :         // Mirage entities, whatever the situation, are visible for one specific player
    1663           0 :         CmpPtr<ICmpMirage> cmpMirage(ent);
    1664           0 :         if (cmpMirage && cmpMirage->GetPlayer() != player)
    1665           0 :             return LosVisibility::HIDDEN;
    1666             : 
    1667           0 :         CFixedVector2D pos = cmpPosition->GetPosition2D();
    1668           0 :         int i = (pos.X / LOS_TILE_SIZE).ToInt_RoundToNearest();
    1669           0 :         int j = (pos.Y / LOS_TILE_SIZE).ToInt_RoundToNearest();
    1670             : 
    1671             :         // Reveal flag makes all positioned entities visible and all mirages useless
    1672           0 :         if (GetLosRevealAll(player))
    1673             :         {
    1674           0 :             if (LosIsOffWorld(i, j) || cmpMirage)
    1675           0 :                 return LosVisibility::HIDDEN;
    1676           0 :             return LosVisibility::VISIBLE;
    1677             :         }
    1678             : 
    1679             :         // Get visible regions
    1680           0 :         CLosQuerier los(GetSharedLosMask(player), m_LosState, m_LosVerticesPerSide);
    1681             : 
    1682           0 :         CmpPtr<ICmpVisibility> cmpVisibility(ent);
    1683             : 
    1684             :         // Possibly ask the scripted Visibility component
    1685           0 :         EntityMap<EntityData>::const_iterator it = m_EntityData.find(ent.GetId());
    1686           0 :         if (it != m_EntityData.end())
    1687             :         {
    1688           0 :             if (it->second.HasFlag<FlagMasks::ScriptedVisibility>() && cmpVisibility)
    1689           0 :                 return cmpVisibility->GetVisibility(player, los.IsVisible(i, j), los.IsExplored(i, j));
    1690             :         }
    1691             :         else
    1692             :         {
    1693           0 :             if (cmpVisibility && cmpVisibility->IsActivated())
    1694           0 :                 return cmpVisibility->GetVisibility(player, los.IsVisible(i, j), los.IsExplored(i, j));
    1695             :         }
    1696             : 
    1697             :         // Else, default behavior
    1698             : 
    1699           0 :         if (los.IsVisible(i, j))
    1700             :         {
    1701           0 :             if (cmpMirage)
    1702           0 :                 return LosVisibility::HIDDEN;
    1703             : 
    1704           0 :             return LosVisibility::VISIBLE;
    1705             :         }
    1706             : 
    1707           0 :         if (!los.IsExplored(i, j))
    1708           0 :             return LosVisibility::HIDDEN;
    1709             : 
    1710             :         // Invisible if the 'retain in fog' flag is not set, and in a non-visible explored region
    1711             :         // Try using the 'retainInFog' flag in m_EntityData to save a script call
    1712           0 :         if (it != m_EntityData.end())
    1713             :         {
    1714           0 :             if (!it->second.HasFlag<FlagMasks::RetainInFog>())
    1715           0 :                 return LosVisibility::HIDDEN;
    1716             :         }
    1717             :         else
    1718             :         {
    1719           0 :             if (!(cmpVisibility && cmpVisibility->GetRetainInFog()))
    1720           0 :                 return LosVisibility::HIDDEN;
    1721             :         }
    1722             : 
    1723           0 :         if (cmpMirage)
    1724           0 :             return LosVisibility::FOGGED;
    1725             : 
    1726           0 :         CmpPtr<ICmpOwnership> cmpOwnership(ent);
    1727           0 :         if (!cmpOwnership)
    1728           0 :             return LosVisibility::FOGGED;
    1729             : 
    1730           0 :         if (cmpOwnership->GetOwner() == player)
    1731             :         {
    1732           0 :             CmpPtr<ICmpFogging> cmpFogging(ent);
    1733           0 :             if (!(cmpFogging && cmpFogging->IsMiraged(player)))
    1734           0 :                 return LosVisibility::FOGGED;
    1735             : 
    1736           0 :             return LosVisibility::HIDDEN;
    1737             :         }
    1738             : 
    1739             :         // Fogged entities are hidden in two cases:
    1740             :         // - They were not scouted
    1741             :         // - A mirage replaces them
    1742           0 :         CmpPtr<ICmpFogging> cmpFogging(ent);
    1743           0 :         if (cmpFogging && cmpFogging->IsActivated() &&
    1744           0 :             (!cmpFogging->WasSeen(player) || cmpFogging->IsMiraged(player)))
    1745           0 :             return LosVisibility::HIDDEN;
    1746             : 
    1747           0 :         return LosVisibility::FOGGED;
    1748             :     }
    1749             : 
    1750           0 :     LosVisibility ComputeLosVisibility(entity_id_t ent, player_id_t player) const
    1751             :     {
    1752           0 :         CEntityHandle handle = GetSimContext().GetComponentManager().LookupEntityHandle(ent);
    1753           0 :         return ComputeLosVisibility(handle, player);
    1754             :     }
    1755             : 
    1756           0 :     LosVisibility GetLosVisibility(CEntityHandle ent, player_id_t player) const override
    1757             :     {
    1758           0 :         entity_id_t entId = ent.GetId();
    1759             : 
    1760             :         // Entities not with positions in the world are never visible
    1761           0 :         if (entId == INVALID_ENTITY)
    1762           0 :             return LosVisibility::HIDDEN;
    1763             : 
    1764           0 :         CmpPtr<ICmpPosition> cmpPosition(ent);
    1765           0 :         if (!cmpPosition || !cmpPosition->IsInWorld())
    1766           0 :             return LosVisibility::HIDDEN;
    1767             : 
    1768             :         // Gaia and observers do not have a visibility cache
    1769           0 :         if (player <= 0)
    1770           0 :             return ComputeLosVisibility(ent, player);
    1771             : 
    1772           0 :         CFixedVector2D pos = cmpPosition->GetPosition2D();
    1773             : 
    1774           0 :         if (IsVisibilityDirty(m_DirtyVisibility[PosToLosRegionsHelper(pos.X, pos.Y)], player))
    1775           0 :             return ComputeLosVisibility(ent, player);
    1776             : 
    1777           0 :         if (std::find(m_ModifiedEntities.begin(), m_ModifiedEntities.end(), entId) != m_ModifiedEntities.end())
    1778           0 :             return ComputeLosVisibility(ent, player);
    1779             : 
    1780           0 :         EntityMap<EntityData>::const_iterator it = m_EntityData.find(entId);
    1781           0 :         if (it == m_EntityData.end())
    1782           0 :             return ComputeLosVisibility(ent, player);
    1783             : 
    1784           0 :         return static_cast<LosVisibility>(GetPlayerVisibility(it->second.visibilities, player));
    1785             :     }
    1786             : 
    1787           0 :     LosVisibility GetLosVisibility(entity_id_t ent, player_id_t player) const override
    1788             :     {
    1789           0 :         CEntityHandle handle = GetSimContext().GetComponentManager().LookupEntityHandle(ent);
    1790           0 :         return GetLosVisibility(handle, player);
    1791             :     }
    1792             : 
    1793           0 :     LosVisibility GetLosVisibilityPosition(entity_pos_t x, entity_pos_t z, player_id_t player) const override
    1794             :     {
    1795           0 :         int i = (x / LOS_TILE_SIZE).ToInt_RoundToNearest();
    1796           0 :         int j = (z / LOS_TILE_SIZE).ToInt_RoundToNearest();
    1797             : 
    1798             :         // Reveal flag makes all positioned entities visible and all mirages useless
    1799           0 :         if (GetLosRevealAll(player))
    1800             :         {
    1801           0 :             if (LosIsOffWorld(i, j))
    1802           0 :                 return LosVisibility::HIDDEN;
    1803             :             else
    1804           0 :                 return LosVisibility::VISIBLE;
    1805             :         }
    1806             : 
    1807             :         // Get visible regions
    1808           0 :         CLosQuerier los(GetSharedLosMask(player), m_LosState, m_LosVerticesPerSide);
    1809             : 
    1810           0 :         if (los.IsVisible(i,j))
    1811           0 :             return LosVisibility::VISIBLE;
    1812           0 :         if (los.IsExplored(i,j))
    1813           0 :             return LosVisibility::FOGGED;
    1814           0 :         return LosVisibility::HIDDEN;
    1815             :     }
    1816             : 
    1817           0 :     size_t GetVerticesPerSide() const override
    1818             :     {
    1819           0 :         return m_LosVerticesPerSide;
    1820             :     }
    1821             : 
    1822     6085928 :     LosRegion LosVertexToLosRegionsHelper(u16 x, u16 z) const
    1823             :     {
    1824    12171856 :         return LosRegion {
    1825    12171856 :             Clamp(x/LOS_REGION_RATIO, 0, m_LosRegionsPerSide - 1),
    1826    12171856 :             Clamp(z/LOS_REGION_RATIO, 0, m_LosRegionsPerSide - 1)
    1827    12171856 :         };
    1828             :     }
    1829             : 
    1830        3106 :     LosRegion PosToLosRegionsHelper(entity_pos_t x, entity_pos_t z) const
    1831             :     {
    1832        6212 :         u16 i = Clamp(
    1833        6212 :             (x/(LOS_TILE_SIZE*LOS_REGION_RATIO)).ToInt_RoundToZero(),
    1834             :             0,
    1835        6212 :             m_LosRegionsPerSide - 1);
    1836        6212 :         u16 j = Clamp(
    1837        6212 :             (z/(LOS_TILE_SIZE*LOS_REGION_RATIO)).ToInt_RoundToZero(),
    1838             :             0,
    1839        6212 :             m_LosRegionsPerSide - 1);
    1840        3106 :         return std::make_pair(i, j);
    1841             :     }
    1842             : 
    1843        2061 :     void AddToRegion(LosRegion region, entity_id_t ent)
    1844             :     {
    1845        2061 :         m_LosRegions[region].insert(ent);
    1846        2061 :     }
    1847             : 
    1848        1025 :     void RemoveFromRegion(LosRegion region, entity_id_t ent)
    1849             :     {
    1850        1025 :         std::set<entity_id_t>::const_iterator regionIt = m_LosRegions[region].find(ent);
    1851        1025 :         if (regionIt != m_LosRegions[region].end())
    1852        1025 :             m_LosRegions[region].erase(regionIt);
    1853        1025 :     }
    1854             : 
    1855           0 :     void UpdateVisibilityData()
    1856             :     {
    1857           0 :         PROFILE("UpdateVisibilityData");
    1858             : 
    1859           0 :         for (u16 i = 0; i < m_LosRegionsPerSide; ++i)
    1860           0 :             for (u16 j = 0; j < m_LosRegionsPerSide; ++j)
    1861             :             {
    1862           0 :                 LosRegion pos{i, j};
    1863           0 :                 for (player_id_t player = 1; player < MAX_LOS_PLAYER_ID + 1; ++player)
    1864           0 :                     if (IsVisibilityDirty(m_DirtyVisibility[pos], player) || m_GlobalPlayerVisibilityUpdate[player-1] == 1 || m_GlobalVisibilityUpdate)
    1865           0 :                         for (const entity_id_t& ent : m_LosRegions[pos])
    1866           0 :                             UpdateVisibility(ent, player);
    1867             : 
    1868           0 :                 m_DirtyVisibility[pos] = 0;
    1869             :             }
    1870             : 
    1871           0 :         std::fill(m_GlobalPlayerVisibilityUpdate.begin(), m_GlobalPlayerVisibilityUpdate.end(), false);
    1872           0 :         m_GlobalVisibilityUpdate = false;
    1873             : 
    1874             :         // Calling UpdateVisibility can modify m_ModifiedEntities, so be careful:
    1875             :         // infinite loops could be triggered by feedback between entities and their mirages.
    1876           0 :         std::map<entity_id_t, u8> attempts;
    1877           0 :         while (!m_ModifiedEntities.empty())
    1878             :         {
    1879           0 :             entity_id_t ent = m_ModifiedEntities.back();
    1880           0 :             m_ModifiedEntities.pop_back();
    1881             : 
    1882           0 :             ++attempts[ent];
    1883           0 :             ENSURE(attempts[ent] < 100 && "Infinite loop in UpdateVisibilityData");
    1884             : 
    1885           0 :             UpdateVisibility(ent);
    1886             :         }
    1887           0 :     }
    1888             : 
    1889        1038 :     void RequestVisibilityUpdate(entity_id_t ent) override
    1890             :     {
    1891        1038 :         if (std::find(m_ModifiedEntities.begin(), m_ModifiedEntities.end(), ent) == m_ModifiedEntities.end())
    1892           3 :             m_ModifiedEntities.push_back(ent);
    1893        1038 :     }
    1894             : 
    1895           0 :     void UpdateVisibility(entity_id_t ent, player_id_t player)
    1896             :     {
    1897           0 :         EntityMap<EntityData>::iterator itEnts = m_EntityData.find(ent);
    1898           0 :         if (itEnts == m_EntityData.end())
    1899           0 :             return;
    1900             : 
    1901           0 :         LosVisibility oldVis = GetPlayerVisibility(itEnts->second.visibilities, player);
    1902           0 :         LosVisibility newVis = ComputeLosVisibility(itEnts->first, player);
    1903             : 
    1904           0 :         if (oldVis == newVis)
    1905           0 :             return;
    1906             : 
    1907           0 :         itEnts->second.visibilities = (itEnts->second.visibilities & ~(0x3 << 2 * (player - 1))) | ((u8)newVis << 2 * (player - 1));
    1908             : 
    1909           0 :         CMessageVisibilityChanged msg(player, ent, static_cast<int>(oldVis), static_cast<int>(newVis));
    1910           0 :         GetSimContext().GetComponentManager().PostMessage(ent, msg);
    1911             :     }
    1912             : 
    1913           0 :     void UpdateVisibility(entity_id_t ent)
    1914             :     {
    1915           0 :         for (player_id_t player = 1; player < MAX_LOS_PLAYER_ID + 1; ++player)
    1916           0 :             UpdateVisibility(ent, player);
    1917           0 :     }
    1918             : 
    1919           0 :     void SetLosRevealAll(player_id_t player, bool enabled) override
    1920             :     {
    1921           0 :         if (player == -1)
    1922           0 :             m_LosRevealAll[MAX_LOS_PLAYER_ID+1] = enabled;
    1923             :         else
    1924             :         {
    1925           0 :             ENSURE(player >= 0 && player <= MAX_LOS_PLAYER_ID);
    1926           0 :             m_LosRevealAll[player] = enabled;
    1927             :         }
    1928             : 
    1929             :         // On next update, update the visibility of every entity in the world
    1930           0 :         m_GlobalVisibilityUpdate = true;
    1931           0 :     }
    1932             : 
    1933           0 :     bool GetLosRevealAll(player_id_t player) const override
    1934             :     {
    1935             :         // Special player value can force reveal-all for every player
    1936           0 :         if (m_LosRevealAll[MAX_LOS_PLAYER_ID+1] || player == -1)
    1937           0 :             return true;
    1938           0 :         ENSURE(player >= 0 && player <= MAX_LOS_PLAYER_ID+1);
    1939             :         // Otherwise check the player-specific flag
    1940           0 :         if (m_LosRevealAll[player])
    1941           0 :             return true;
    1942             : 
    1943           0 :         return false;
    1944             :     }
    1945             : 
    1946           0 :     void SetLosCircular(bool enabled) override
    1947             :     {
    1948           0 :         m_LosCircular = enabled;
    1949             : 
    1950           0 :         ResetDerivedData();
    1951           0 :     }
    1952             : 
    1953           0 :     bool GetLosCircular() const override
    1954             :     {
    1955           0 :         return m_LosCircular;
    1956             :     }
    1957             : 
    1958           0 :     void SetSharedLos(player_id_t player, const std::vector<player_id_t>& players) override
    1959             :     {
    1960           0 :         m_SharedLosMasks[player] = CalcSharedLosMask(players);
    1961             : 
    1962             :         // Units belonging to any of 'players' can now trigger visibility updates for 'player'.
    1963             :         // If shared LOS partners have been removed, we disable visibility updates from them
    1964             :         // in order to improve performance. That also allows us to properly determine whether
    1965             :         // 'player' needs a global visibility update for this turn.
    1966           0 :         bool modified = false;
    1967             : 
    1968           0 :         for (player_id_t p = 1; p < MAX_LOS_PLAYER_ID+1; ++p)
    1969             :         {
    1970           0 :             bool inList = std::find(players.begin(), players.end(), p) != players.end();
    1971             : 
    1972           0 :             if (SetPlayerSharedDirtyVisibilityBit(m_SharedDirtyVisibilityMasks[p], player, inList))
    1973           0 :                 modified = true;
    1974             :         }
    1975             : 
    1976           0 :         if (modified && (size_t)player <= m_GlobalPlayerVisibilityUpdate.size())
    1977           0 :             m_GlobalPlayerVisibilityUpdate[player-1] = 1;
    1978           0 :     }
    1979             : 
    1980           0 :     u32 GetSharedLosMask(player_id_t player) const override
    1981             :     {
    1982           0 :         return m_SharedLosMasks[player];
    1983             :     }
    1984             : 
    1985           0 :     void ExploreMap(player_id_t p) override
    1986             :     {
    1987           0 :         for (i32 j = 0; j < m_LosVerticesPerSide; ++j)
    1988           0 :             for (i32 i = 0; i < m_LosVerticesPerSide; ++i)
    1989             :             {
    1990           0 :                 if (LosIsOffWorld(i,j))
    1991           0 :                     continue;
    1992           0 :                 u32 &explored = m_ExploredVertices.at(p);
    1993           0 :                 explored += !(m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*(p-1))));
    1994           0 :                 m_LosState.get(i, j) |= ((u32)LosState::EXPLORED << (2*(p-1)));
    1995             :             }
    1996             : 
    1997           0 :         SeeExploredEntities(p);
    1998           0 :     }
    1999             : 
    2000           0 :     void ExploreTerritories() override
    2001             :     {
    2002           0 :         PROFILE3("ExploreTerritories");
    2003             : 
    2004           0 :         CmpPtr<ICmpTerritoryManager> cmpTerritoryManager(GetSystemEntity());
    2005           0 :         const Grid<u8>& grid = cmpTerritoryManager->GetTerritoryGrid();
    2006             : 
    2007             :         // Territory data is stored per territory-tile (typically a multiple of terrain-tiles).
    2008             :         // LOS data is stored per los vertex (in reality tiles too, but it's the center that matters).
    2009             :         // This scales from LOS coordinates to Territory coordinates.
    2010           0 :         auto scale = [](i32 coord, i32 max) -> i32 {
    2011           0 :             return std::min(max, (coord * LOS_TILE_SIZE + LOS_TILE_SIZE / 2) / (ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE * Pathfinding::NAVCELL_SIZE_INT));
    2012           0 :         };
    2013             : 
    2014             :         // For each territory-tile, if it is owned by a valid player then update the LOS
    2015             :         // for every vertex inside/around that tile, to mark them as explored.
    2016           0 :         for (i32 j = 0; j < m_LosVerticesPerSide; ++j)
    2017           0 :             for (i32 i = 0; i < m_LosVerticesPerSide; ++i)
    2018             :             {
    2019             :                 // TODO: This fetches data redundantly if the los grid is smaller than the territory grid
    2020             :                 // (but it's unlikely to matter much).
    2021           0 :                 u8 p = grid.get(scale(i, grid.width() - 1), scale(j, grid.height() - 1)) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK;
    2022           0 :                 if (p > 0 && p <= MAX_LOS_PLAYER_ID)
    2023             :                 {
    2024           0 :                     u32& explored = m_ExploredVertices.at(p);
    2025             : 
    2026           0 :                     if (LosIsOffWorld(i, j))
    2027           0 :                         continue;
    2028             : 
    2029           0 :                     u32& losState = m_LosState.get(i, j);
    2030           0 :                     if (!(losState & ((u32)LosState::EXPLORED << (2*(p-1)))))
    2031             :                     {
    2032           0 :                         ++explored;
    2033           0 :                         losState |= ((u32)LosState::EXPLORED << (2*(p-1)));
    2034             :                     }
    2035             :                 }
    2036             :             }
    2037             : 
    2038           0 :         for (player_id_t p = 1; p < MAX_LOS_PLAYER_ID+1; ++p)
    2039           0 :             SeeExploredEntities(p);
    2040           0 :     }
    2041             : 
    2042             :     /**
    2043             :      * Force any entity in explored territory to appear for player p.
    2044             :      * This is useful for miraging entities inside the territory borders at the beginning of a game,
    2045             :      * or if the "Explore Map" option has been set.
    2046             :      */
    2047           0 :     void SeeExploredEntities(player_id_t p) const
    2048             :     {
    2049             :         // Warning: Code related to fogging (like ForceMiraging) shouldn't be
    2050             :         // invoked while iterating through m_EntityData.
    2051             :         // Otherwise, by deleting mirage entities and so on, that code will
    2052             :         // change the indexes in the map, leading to segfaults.
    2053             :         // So we just remember what entities to mirage and do that later.
    2054           0 :         std::vector<entity_id_t> miragableEntities;
    2055             : 
    2056           0 :         for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
    2057             :         {
    2058           0 :             CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), it->first);
    2059           0 :             if (!cmpPosition || !cmpPosition->IsInWorld())
    2060           0 :                 continue;
    2061             : 
    2062           0 :             CFixedVector2D pos = cmpPosition->GetPosition2D();
    2063           0 :             int i = (pos.X / LOS_TILE_SIZE).ToInt_RoundToNearest();
    2064           0 :             int j = (pos.Y / LOS_TILE_SIZE).ToInt_RoundToNearest();
    2065             : 
    2066           0 :             CLosQuerier los(GetSharedLosMask(p), m_LosState, m_LosVerticesPerSide);
    2067           0 :             if (!los.IsExplored(i,j) || los.IsVisible(i,j))
    2068           0 :                 continue;
    2069             : 
    2070           0 :             CmpPtr<ICmpFogging> cmpFogging(GetSimContext(), it->first);
    2071           0 :             if (cmpFogging)
    2072           0 :                 miragableEntities.push_back(it->first);
    2073             :         }
    2074             : 
    2075           0 :         for (std::vector<entity_id_t>::iterator it = miragableEntities.begin(); it != miragableEntities.end(); ++it)
    2076             :         {
    2077           0 :             CmpPtr<ICmpFogging> cmpFogging(GetSimContext(), *it);
    2078           0 :             ENSURE(cmpFogging && "Impossible to retrieve Fogging component, previously achieved");
    2079           0 :             cmpFogging->ForceMiraging(p);
    2080             :         }
    2081           0 :     }
    2082             : 
    2083           0 :     void RevealShore(player_id_t p, bool enable) override
    2084             :     {
    2085           0 :         if (p <= 0 || p > MAX_LOS_PLAYER_ID)
    2086           0 :             return;
    2087             : 
    2088             :         // Maximum distance to the shore
    2089           0 :         const u16 maxdist = 10;
    2090             : 
    2091           0 :         CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
    2092           0 :         const Grid<u16>& shoreGrid = cmpPathfinder->ComputeShoreGrid(true);
    2093           0 :         ENSURE(shoreGrid.m_W == m_LosVerticesPerSide-1 && shoreGrid.m_H == m_LosVerticesPerSide-1);
    2094             : 
    2095           0 :         Grid<u16>& counts = m_LosPlayerCounts.at(p);
    2096           0 :         ENSURE(!counts.blank());
    2097             : 
    2098           0 :         for (u16 j = 0; j < shoreGrid.m_H; ++j)
    2099           0 :             for (u16 i = 0; i < shoreGrid.m_W; ++i)
    2100             :             {
    2101           0 :                 u16 shoredist = shoreGrid.get(i, j);
    2102           0 :                 if (shoredist > maxdist)
    2103           0 :                     continue;
    2104             : 
    2105             :                 // Maybe we could be more clever and don't add dummy strips of one tile
    2106           0 :                 if (enable)
    2107           0 :                     LosAddStripHelper(p, i, i, j, counts);
    2108             :                 else
    2109           0 :                     LosRemoveStripHelper(p, i, i, j, counts);
    2110             :             }
    2111             :     }
    2112             : 
    2113             :     /**
    2114             :      * Returns whether the given vertex is outside the normal bounds of the world
    2115             :      * (i.e. outside the range of a circular map)
    2116             :      */
    2117    36092680 :     inline bool LosIsOffWorld(ssize_t i, ssize_t j) const
    2118             :     {
    2119    36092680 :         if (m_LosCircular)
    2120             :         {
    2121             :             // With a circular map, vertex is off-world if hypot(i - size/2, j - size/2) >= size/2:
    2122             : 
    2123           0 :             ssize_t dist2 = (i - m_LosVerticesPerSide/2)*(i - m_LosVerticesPerSide/2)
    2124           0 :                     + (j - m_LosVerticesPerSide/2)*(j - m_LosVerticesPerSide/2);
    2125             : 
    2126           0 :             ssize_t r = m_LosVerticesPerSide / 2 - MAP_EDGE_TILES + 1;
    2127             :                 // subtract a bit from the radius to ensure nice
    2128             :                 // SoD blurring around the edges of the map
    2129             : 
    2130           0 :             return (dist2 >= r*r);
    2131             :         }
    2132             :         else
    2133             :         {
    2134             :             // With a square map, the outermost edge of the map should be off-world,
    2135             :             // so the SoD texture blends out nicely
    2136    69751280 :             return i < MAP_EDGE_TILES || j < MAP_EDGE_TILES ||
    2137   104248018 :                 i >= m_LosVerticesPerSide - MAP_EDGE_TILES ||
    2138    69771987 :                 j >= m_LosVerticesPerSide - MAP_EDGE_TILES;
    2139             :         }
    2140             :     }
    2141             : 
    2142             :     /**
    2143             :      * Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
    2144             :      */
    2145       64500 :     inline void LosAddStripHelper(u8 owner, i32 i0, i32 i1, i32 j, Grid<u16>& counts)
    2146             :     {
    2147       64500 :         if (i1 < i0)
    2148        1408 :             return;
    2149             : 
    2150       63092 :         u32 &explored = m_ExploredVertices.at(owner);
    2151     1609336 :         for (i32 i = i0; i <= i1; ++i)
    2152             :         {
    2153             :             // Increasing from zero to non-zero - move from unexplored/explored to visible+explored
    2154     1546244 :             if (counts.get(i, j) == 0)
    2155             :             {
    2156     1545964 :                 if (!LosIsOffWorld(i, j))
    2157             :                 {
    2158     1490077 :                     explored += !(m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*(owner-1))));
    2159     1490077 :                     m_LosState.get(i, j) |= (((int)LosState::VISIBLE | (u32)LosState::EXPLORED) << (2*(owner-1)));
    2160             :                 }
    2161             : 
    2162     1545964 :                 MarkVisibilityDirtyAroundTile(owner, i, j);
    2163             :             }
    2164             : 
    2165     1546244 :             ENSURE(counts.get(i, j) < std::numeric_limits<u16>::max());
    2166     1546244 :             counts.get(i, j) = (u16)(counts.get(i, j) + 1); // ignore overflow; the player should never have 64K units
    2167             :         }
    2168             :     }
    2169             : 
    2170             :     /**
    2171             :      * Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
    2172             :      */
    2173       32906 :     inline void LosRemoveStripHelper(u8 owner, i32 i0, i32 i1, i32 j, Grid<u16>& counts)
    2174             :     {
    2175       32906 :         if (i1 < i0)
    2176        1451 :             return;
    2177             : 
    2178      791616 :         for (i32 i = i0; i <= i1; ++i)
    2179             :         {
    2180      760161 :             ASSERT(counts.get(i, j) > 0);
    2181      760161 :             counts.get(i, j) = (u16)(counts.get(i, j) - 1);
    2182             : 
    2183             :             // Decreasing from non-zero to zero - move from visible+explored to explored
    2184      760161 :             if (counts.get(i, j) == 0)
    2185             :             {
    2186             :                 // (If LosIsOffWorld then this is a no-op, so don't bother doing the check)
    2187      760161 :                 m_LosState.get(i, j) &= ~((int)LosState::VISIBLE << (2*(owner-1)));
    2188             : 
    2189      760161 :                 MarkVisibilityDirtyAroundTile(owner, i, j);
    2190             :             }
    2191             :         }
    2192             :     }
    2193             : 
    2194     2306125 :     inline void MarkVisibilityDirtyAroundTile(u8 owner, i32 i, i32 j)
    2195             :     {
    2196             :         // If we're still in the deserializing process, we must not modify m_DirtyVisibility
    2197     2306125 :         if (m_Deserializing)
    2198      784643 :             return;
    2199             : 
    2200             :         // Mark the LoS regions around the updated vertex
    2201             :         // 1: left-up, 2: right-up, 3: left-down, 4: right-down
    2202     1521482 :         LosRegion n1 = LosVertexToLosRegionsHelper(i-1, j-1);
    2203     1521482 :         LosRegion n2 = LosVertexToLosRegionsHelper(i-1, j);
    2204     1521482 :         LosRegion n3 = LosVertexToLosRegionsHelper(i, j-1);
    2205     1521482 :         LosRegion n4 = LosVertexToLosRegionsHelper(i, j);
    2206             : 
    2207     1521482 :         u16 sharedDirtyVisibilityMask = m_SharedDirtyVisibilityMasks[owner];
    2208             : 
    2209     1521482 :         if (j > 0 && i > 0)
    2210     1521482 :             m_DirtyVisibility[n1] |= sharedDirtyVisibilityMask;
    2211     1521482 :         if (n2 != n1 && j > 0 && i < m_LosVerticesPerSide)
    2212      184151 :             m_DirtyVisibility[n2] |= sharedDirtyVisibilityMask;
    2213     1521482 :         if (n3 != n1 && j < m_LosVerticesPerSide && i > 0)
    2214      184542 :             m_DirtyVisibility[n3] |= sharedDirtyVisibilityMask;
    2215     1521482 :         if (n4 != n1 && j < m_LosVerticesPerSide && i < m_LosVerticesPerSide)
    2216      346321 :             m_DirtyVisibility[n4] |= sharedDirtyVisibilityMask;
    2217             :     }
    2218             : 
    2219             :     /**
    2220             :      * Update the LOS state of tiles within a given circular range,
    2221             :      * either adding or removing visibility depending on the template parameter.
    2222             :      * Assumes owner is in the valid range.
    2223             :      */
    2224             :     template<bool adding>
    2225        3002 :     void LosUpdateHelper(u8 owner, entity_pos_t visionRange, CFixedVector2D pos)
    2226             :     {
    2227        3002 :         if (m_LosVerticesPerSide == 0) // do nothing if not initialised yet
    2228           0 :             return;
    2229             : 
    2230        6004 :         PROFILE("LosUpdateHelper");
    2231             : 
    2232        3002 :         Grid<u16>& counts = m_LosPlayerCounts.at(owner);
    2233             : 
    2234             :         // Lazy initialisation of counts:
    2235        3002 :         if (counts.blank())
    2236        1041 :             counts.resize(m_LosVerticesPerSide, m_LosVerticesPerSide);
    2237             : 
    2238             :         // Compute the circular region as a series of strips.
    2239             :         // Rather than quantise pos to vertexes, we do more precise sub-tile computations
    2240             :         // to get smoother behaviour as a unit moves rather than jumping a whole tile
    2241             :         // at once.
    2242             :         // To avoid the cost of sqrt when computing the outline of the circle,
    2243             :         // we loop from the bottom to the top and estimate the width of the current
    2244             :         // strip based on the previous strip, then adjust each end of the strip
    2245             :         // inwards or outwards until it's the widest that still falls within the circle.
    2246             : 
    2247             :         // Compute top/bottom coordinates, and clamp to exclude the 1-tile border around the map
    2248             :         // (so that we never render the sharp edge of the map)
    2249        3002 :         i32 j0 = ((pos.Y - visionRange)/LOS_TILE_SIZE).ToInt_RoundToInfinity();
    2250        3002 :         i32 j1 = ((pos.Y + visionRange)/LOS_TILE_SIZE).ToInt_RoundToNegInfinity();
    2251        3002 :         i32 j0clamp = std::max(j0, 1);
    2252        3002 :         i32 j1clamp = std::min(j1, m_LosVerticesPerSide-2);
    2253             : 
    2254             :         // Translate world coordinates into fractional tile-space coordinates
    2255        3002 :         entity_pos_t x = pos.X / LOS_TILE_SIZE;
    2256        3002 :         entity_pos_t y = pos.Y / LOS_TILE_SIZE;
    2257        3002 :         entity_pos_t r = visionRange / LOS_TILE_SIZE;
    2258        3002 :         entity_pos_t r2 = r.Square();
    2259             : 
    2260             :         // Compute the integers on either side of x
    2261        3002 :         i32 xfloor = (x - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
    2262        3002 :         i32 xceil = (x + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
    2263             : 
    2264             :         // Initialise the strip (i0, i1) to a rough guess
    2265        3002 :         i32 i0 = xfloor;
    2266        3002 :         i32 i1 = xceil;
    2267             : 
    2268       94638 :         for (i32 j = j0clamp; j <= j1clamp; ++j)
    2269             :         {
    2270             :             // Adjust i0 and i1 to be the outermost values that don't exceed
    2271             :             // the circle's radius (i.e. require dy^2 + dx^2 <= r^2).
    2272             :             // When moving the points inwards, clamp them to xceil+1 or xfloor-1
    2273             :             // so they don't accidentally shoot off in the wrong direction forever.
    2274             : 
    2275       91636 :             entity_pos_t dy = entity_pos_t::FromInt(j) - y;
    2276       91636 :             entity_pos_t dy2 = dy.Square();
    2277      184556 :             while (dy2 + (entity_pos_t::FromInt(i0-1) - x).Square() <= r2)
    2278       46460 :                 --i0;
    2279      159206 :             while (i0 < xceil && dy2 + (entity_pos_t::FromInt(i0) - x).Square() > r2)
    2280       33785 :                 ++i0;
    2281      184860 :             while (dy2 + (entity_pos_t::FromInt(i1+1) - x).Square() <= r2)
    2282       46612 :                 ++i1;
    2283      159334 :             while (i1 > xfloor && dy2 + (entity_pos_t::FromInt(i1) - x).Square() > r2)
    2284       33849 :                 --i1;
    2285             : 
    2286             : #if DEBUG_RANGE_MANAGER_BOUNDS
    2287             :             if (i0 <= i1)
    2288             :             {
    2289             :                 ENSURE(dy2 + (entity_pos_t::FromInt(i0) - x).Square() <= r2);
    2290             :                 ENSURE(dy2 + (entity_pos_t::FromInt(i1) - x).Square() <= r2);
    2291             :             }
    2292             :             ENSURE(dy2 + (entity_pos_t::FromInt(i0 - 1) - x).Square() > r2);
    2293             :             ENSURE(dy2 + (entity_pos_t::FromInt(i1 + 1) - x).Square() > r2);
    2294             : #endif
    2295             : 
    2296             :             // Clamp the strip to exclude the 1-tile border,
    2297             :             // then add or remove the strip as requested
    2298       91636 :             i32 i0clamp = std::max(i0, 1);
    2299       91636 :             i32 i1clamp = std::min(i1, m_LosVerticesPerSide-2);
    2300             :             if (adding)
    2301       61628 :                 LosAddStripHelper(owner, i0clamp, i1clamp, j, counts);
    2302             :             else
    2303       30008 :                 LosRemoveStripHelper(owner, i0clamp, i1clamp, j, counts);
    2304             :         }
    2305             :     }
    2306             : 
    2307             :     /**
    2308             :      * Update the LOS state of tiles within a given circular range,
    2309             :      * by removing visibility around the 'from' position
    2310             :      * and then adding visibility around the 'to' position.
    2311             :      */
    2312          59 :     void LosUpdateHelperIncremental(u8 owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
    2313             :     {
    2314          59 :         if (m_LosVerticesPerSide == 0) // do nothing if not initialised yet
    2315           0 :             return;
    2316             : 
    2317         118 :         PROFILE("LosUpdateHelperIncremental");
    2318             : 
    2319          59 :         Grid<u16>& counts = m_LosPlayerCounts.at(owner);
    2320             : 
    2321             :         // Lazy initialisation of counts:
    2322          59 :         if (counts.blank())
    2323           0 :             counts.resize(m_LosVerticesPerSide, m_LosVerticesPerSide);
    2324             : 
    2325             :         // See comments in LosUpdateHelper.
    2326             :         // This does exactly the same, except computing the strips for
    2327             :         // both circles simultaneously.
    2328             :         // (The idea is that the circles will be heavily overlapping,
    2329             :         // so we can compute the difference between the removed/added strips
    2330             :         // and only have to touch tiles that have a net change.)
    2331             : 
    2332          59 :         i32 j0_from = ((from.Y - visionRange)/LOS_TILE_SIZE).ToInt_RoundToInfinity();
    2333          59 :         i32 j1_from = ((from.Y + visionRange)/LOS_TILE_SIZE).ToInt_RoundToNegInfinity();
    2334          59 :         i32 j0_to = ((to.Y - visionRange)/LOS_TILE_SIZE).ToInt_RoundToInfinity();
    2335          59 :         i32 j1_to = ((to.Y + visionRange)/LOS_TILE_SIZE).ToInt_RoundToNegInfinity();
    2336          59 :         i32 j0clamp = std::max(std::min(j0_from, j0_to), 1);
    2337          59 :         i32 j1clamp = std::min(std::max(j1_from, j1_to), m_LosVerticesPerSide-2);
    2338             : 
    2339          59 :         entity_pos_t x_from = from.X / LOS_TILE_SIZE;
    2340          59 :         entity_pos_t y_from = from.Y / LOS_TILE_SIZE;
    2341          59 :         entity_pos_t x_to = to.X / LOS_TILE_SIZE;
    2342          59 :         entity_pos_t y_to = to.Y / LOS_TILE_SIZE;
    2343          59 :         entity_pos_t r = visionRange / LOS_TILE_SIZE;
    2344          59 :         entity_pos_t r2 = r.Square();
    2345             : 
    2346          59 :         i32 xfloor_from = (x_from - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
    2347          59 :         i32 xceil_from = (x_from + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
    2348          59 :         i32 xfloor_to = (x_to - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
    2349          59 :         i32 xceil_to = (x_to + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
    2350             : 
    2351          59 :         i32 i0_from = xfloor_from;
    2352          59 :         i32 i1_from = xceil_from;
    2353          59 :         i32 i0_to = xfloor_to;
    2354          59 :         i32 i1_to = xceil_to;
    2355             : 
    2356        2085 :         for (i32 j = j0clamp; j <= j1clamp; ++j)
    2357             :         {
    2358        2026 :             entity_pos_t dy_from = entity_pos_t::FromInt(j) - y_from;
    2359        2026 :             entity_pos_t dy2_from = dy_from.Square();
    2360        3874 :             while (dy2_from + (entity_pos_t::FromInt(i0_from-1) - x_from).Square() <= r2)
    2361         924 :                 --i0_from;
    2362        3564 :             while (i0_from < xceil_from && dy2_from + (entity_pos_t::FromInt(i0_from) - x_from).Square() > r2)
    2363         769 :                 ++i0_from;
    2364        3908 :             while (dy2_from + (entity_pos_t::FromInt(i1_from+1) - x_from).Square() <= r2)
    2365         941 :                 ++i1_from;
    2366        3576 :             while (i1_from > xfloor_from && dy2_from + (entity_pos_t::FromInt(i1_from) - x_from).Square() > r2)
    2367         775 :                 --i1_from;
    2368             : 
    2369        2026 :             entity_pos_t dy_to = entity_pos_t::FromInt(j) - y_to;
    2370        2026 :             entity_pos_t dy2_to = dy_to.Square();
    2371        3886 :             while (dy2_to + (entity_pos_t::FromInt(i0_to-1) - x_to).Square() <= r2)
    2372         930 :                 --i0_to;
    2373        3528 :             while (i0_to < xceil_to && dy2_to + (entity_pos_t::FromInt(i0_to) - x_to).Square() > r2)
    2374         751 :                 ++i0_to;
    2375        3896 :             while (dy2_to + (entity_pos_t::FromInt(i1_to+1) - x_to).Square() <= r2)
    2376         935 :                 ++i1_to;
    2377        3530 :             while (i1_to > xfloor_to && dy2_to + (entity_pos_t::FromInt(i1_to) - x_to).Square() > r2)
    2378         752 :                 --i1_to;
    2379             : 
    2380             : #if DEBUG_RANGE_MANAGER_BOUNDS
    2381             :             if (i0_from <= i1_from)
    2382             :             {
    2383             :                 ENSURE(dy2_from + (entity_pos_t::FromInt(i0_from) - x_from).Square() <= r2);
    2384             :                 ENSURE(dy2_from + (entity_pos_t::FromInt(i1_from) - x_from).Square() <= r2);
    2385             :             }
    2386             :             ENSURE(dy2_from + (entity_pos_t::FromInt(i0_from - 1) - x_from).Square() > r2);
    2387             :             ENSURE(dy2_from + (entity_pos_t::FromInt(i1_from + 1) - x_from).Square() > r2);
    2388             :             if (i0_to <= i1_to)
    2389             :             {
    2390             :                 ENSURE(dy2_to + (entity_pos_t::FromInt(i0_to) - x_to).Square() <= r2);
    2391             :                 ENSURE(dy2_to + (entity_pos_t::FromInt(i1_to) - x_to).Square() <= r2);
    2392             :             }
    2393             :             ENSURE(dy2_to + (entity_pos_t::FromInt(i0_to - 1) - x_to).Square() > r2);
    2394             :             ENSURE(dy2_to + (entity_pos_t::FromInt(i1_to + 1) - x_to).Square() > r2);
    2395             : #endif
    2396             : 
    2397             :             // Check whether this strip moved at all
    2398        2026 :             if (!(i0_to == i0_from && i1_to == i1_from))
    2399             :             {
    2400        1846 :                 i32 i0clamp_from = std::max(i0_from, 1);
    2401        1846 :                 i32 i1clamp_from = std::min(i1_from, m_LosVerticesPerSide-2);
    2402        1846 :                 i32 i0clamp_to = std::max(i0_to, 1);
    2403        1846 :                 i32 i1clamp_to = std::min(i1_to, m_LosVerticesPerSide-2);
    2404             : 
    2405             :                 // Check whether one strip is negative width,
    2406             :                 // and we can just add/remove the entire other strip
    2407        1846 :                 if (i1clamp_from < i0clamp_from)
    2408             :                 {
    2409         256 :                     LosAddStripHelper(owner, i0clamp_to, i1clamp_to, j, counts);
    2410             :                 }
    2411        1590 :                 else if (i1clamp_to < i0clamp_to)
    2412             :                 {
    2413         282 :                     LosRemoveStripHelper(owner, i0clamp_from, i1clamp_from, j, counts);
    2414             :                 }
    2415             :                 else
    2416             :                 {
    2417             :                     // There are four possible regions of overlap between the two strips
    2418             :                     // (remove before add, remove after add, add before remove, add after remove).
    2419             :                     // Process each of the regions as its own strip.
    2420             :                     // (If this produces negative-width strips then they'll just get ignored
    2421             :                     // which is fine.)
    2422             :                     // (If the strips don't actually overlap (which is very rare with normal unit
    2423             :                     // movement speeds), the region between them will be both added and removed,
    2424             :                     // so we have to do the add first to avoid overflowing to -1 and triggering
    2425             :                     // assertion failures.)
    2426        1308 :                     LosAddStripHelper(owner, i0clamp_to, i0clamp_from-1, j, counts);
    2427        1308 :                     LosAddStripHelper(owner, i1clamp_from+1, i1clamp_to, j, counts);
    2428        1308 :                     LosRemoveStripHelper(owner, i0clamp_from, i0clamp_to-1, j, counts);
    2429        1308 :                     LosRemoveStripHelper(owner, i1clamp_to+1, i1clamp_from, j, counts);
    2430             :                 }
    2431             :             }
    2432             :         }
    2433             :     }
    2434             : 
    2435        1044 :     void LosAdd(player_id_t owner, entity_pos_t visionRange, CFixedVector2D pos)
    2436             :     {
    2437        1044 :         if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
    2438           1 :             return;
    2439             : 
    2440        1043 :         LosUpdateHelper<true>((u8)owner, visionRange, pos);
    2441             :     }
    2442             : 
    2443           0 :     void SharingLosAdd(u16 visionSharing, entity_pos_t visionRange, CFixedVector2D pos)
    2444             :     {
    2445           0 :         if (visionRange.IsZero())
    2446           0 :             return;
    2447             : 
    2448           0 :         for (player_id_t i = 1; i < MAX_LOS_PLAYER_ID+1; ++i)
    2449           0 :             if (HasVisionSharing(visionSharing, i))
    2450           0 :                 LosAdd(i, visionRange, pos);
    2451             :     }
    2452             : 
    2453           8 :     void LosRemove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D pos)
    2454             :     {
    2455           8 :         if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
    2456           1 :             return;
    2457             : 
    2458           7 :         LosUpdateHelper<false>((u8)owner, visionRange, pos);
    2459             :     }
    2460             : 
    2461           0 :     void SharingLosRemove(u16 visionSharing, entity_pos_t visionRange, CFixedVector2D pos)
    2462             :     {
    2463           0 :         if (visionRange.IsZero())
    2464           0 :             return;
    2465             : 
    2466           0 :         for (player_id_t i = 1; i < MAX_LOS_PLAYER_ID+1; ++i)
    2467           0 :             if (HasVisionSharing(visionSharing, i))
    2468           0 :                 LosRemove(i, visionRange, pos);
    2469             :     }
    2470             : 
    2471        1035 :     void LosMove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
    2472             :     {
    2473        1035 :         if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
    2474           0 :             return;
    2475             : 
    2476        1035 :         if ((from - to).CompareLength(visionRange) > 0)
    2477             :         {
    2478             :             // If it's a very large move, then simply remove and add to the new position
    2479         976 :             LosUpdateHelper<false>((u8)owner, visionRange, from);
    2480         976 :             LosUpdateHelper<true>((u8)owner, visionRange, to);
    2481             :         }
    2482             :         else
    2483             :             // Otherwise use the version optimised for mostly-overlapping circles
    2484          59 :             LosUpdateHelperIncremental((u8)owner, visionRange, from, to);
    2485             :     }
    2486             : 
    2487           0 :     void SharingLosMove(u16 visionSharing, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
    2488             :     {
    2489           0 :         if (visionRange.IsZero())
    2490           0 :             return;
    2491             : 
    2492           0 :         for (player_id_t i = 1; i < MAX_LOS_PLAYER_ID+1; ++i)
    2493           0 :             if (HasVisionSharing(visionSharing, i))
    2494           0 :                 LosMove(i, visionRange, from, to);
    2495             :     }
    2496             : 
    2497           0 :     u8 GetPercentMapExplored(player_id_t player) const override
    2498             :     {
    2499           0 :         return m_ExploredVertices.at((u8)player) * 100 / m_TotalInworldVertices;
    2500             :     }
    2501             : 
    2502           0 :     u8 GetUnionPercentMapExplored(const std::vector<player_id_t>& players) const override
    2503             :     {
    2504           0 :         u32 exploredVertices = 0;
    2505           0 :         std::vector<player_id_t>::const_iterator playerIt;
    2506             : 
    2507           0 :         for (i32 j = 0; j < m_LosVerticesPerSide; j++)
    2508           0 :             for (i32 i = 0; i < m_LosVerticesPerSide; i++)
    2509             :             {
    2510           0 :                 if (LosIsOffWorld(i, j))
    2511           0 :                     continue;
    2512             : 
    2513           0 :                 for (playerIt = players.begin(); playerIt != players.end(); ++playerIt)
    2514           0 :                     if (m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*((*playerIt)-1))))
    2515             :                     {
    2516           0 :                         exploredVertices += 1;
    2517           0 :                         break;
    2518             :                     }
    2519             :             }
    2520             : 
    2521           0 :         return exploredVertices * 100 / m_TotalInworldVertices;
    2522             :     }
    2523             : };
    2524             : 
    2525         119 : REGISTER_COMPONENT_TYPE(RangeManager)

Generated by: LCOV version 1.13