2019-01-05 14:38:00 -08:00
|
|
|
/* Copyright (C) 2018 Wildfire Games.
|
2010-07-29 13:39:23 -07:00
|
|
|
* This file is part of 0 A.D.
|
|
|
|
|
*
|
|
|
|
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* 0 A.D. is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "precompiled.h"
|
|
|
|
|
|
|
|
|
|
#include "simulation2/system/Component.h"
|
|
|
|
|
#include "ICmpRangeManager.h"
|
|
|
|
|
|
2013-08-03 12:20:20 -07:00
|
|
|
#include "ICmpTerrain.h"
|
2013-09-15 07:03:53 -07:00
|
|
|
#include "simulation2/system/EntityMap.h"
|
2010-07-29 13:39:23 -07:00
|
|
|
#include "simulation2/MessageTypes.h"
|
2014-08-04 15:49:19 -07:00
|
|
|
#include "simulation2/components/ICmpFogging.h"
|
|
|
|
|
#include "simulation2/components/ICmpMirage.h"
|
|
|
|
|
#include "simulation2/components/ICmpOwnership.h"
|
2011-08-01 14:25:12 -07:00
|
|
|
#include "simulation2/components/ICmpPosition.h"
|
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
|
|
|
#include "simulation2/components/ICmpObstructionManager.h"
|
2011-08-01 14:25:12 -07:00
|
|
|
#include "simulation2/components/ICmpTerritoryManager.h"
|
2014-11-04 12:53:25 -08:00
|
|
|
#include "simulation2/components/ICmpVisibility.h"
|
2011-08-01 14:25:12 -07:00
|
|
|
#include "simulation2/components/ICmpVision.h"
|
2013-08-03 12:20:20 -07:00
|
|
|
#include "simulation2/components/ICmpWaterManager.h"
|
2010-07-29 13:39:23 -07:00
|
|
|
#include "simulation2/helpers/Render.h"
|
2016-01-12 16:20:22 -08:00
|
|
|
#include "simulation2/helpers/Spatial.h"
|
2010-07-29 13:39:23 -07:00
|
|
|
|
|
|
|
|
#include "graphics/Overlay.h"
|
2010-09-19 11:08:56 -07:00
|
|
|
#include "graphics/Terrain.h"
|
|
|
|
|
#include "lib/timer.h"
|
2010-07-29 13:39:23 -07:00
|
|
|
#include "ps/CLogger.h"
|
|
|
|
|
#include "ps/Profile.h"
|
|
|
|
|
#include "renderer/Scene.h"
|
2013-09-15 07:03:53 -07:00
|
|
|
|
2014-07-10 13:51:39 -07:00
|
|
|
#define LOS_TILES_RATIO 8
|
2011-10-28 06:15:33 -07:00
|
|
|
#define DEBUG_RANGE_MANAGER_BOUNDS 0
|
|
|
|
|
|
2010-07-29 13:39:23 -07:00
|
|
|
/**
|
|
|
|
|
* Representation of a range query.
|
|
|
|
|
*/
|
|
|
|
|
struct Query
|
|
|
|
|
{
|
|
|
|
|
bool enabled;
|
2013-08-03 12:20:20 -07:00
|
|
|
bool parabolic;
|
2013-09-11 13:41:53 -07:00
|
|
|
CEntityHandle source; // TODO: this could crash if an entity is destroyed while a Query is still referencing it
|
2010-11-16 12:43:15 -08:00
|
|
|
entity_pos_t minRange;
|
2010-07-29 13:39:23 -07:00
|
|
|
entity_pos_t maxRange;
|
2013-08-03 12:20:20 -07:00
|
|
|
entity_pos_t elevationBonus;
|
2010-07-29 13:39:23 -07:00
|
|
|
u32 ownersMask;
|
2010-10-23 12:59:40 -07:00
|
|
|
i32 interface;
|
2010-07-29 13:39:23 -07:00
|
|
|
std::vector<entity_id_t> lastMatch;
|
2012-04-17 13:22:13 -07:00
|
|
|
u8 flagsMask;
|
2010-07-29 13:39:23 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert an owner ID (-1 = unowned, 0 = gaia, 1..30 = players)
|
2010-09-23 05:13:13 -07:00
|
|
|
* into a 32-bit mask for quick set-membership tests.
|
2010-07-29 13:39:23 -07:00
|
|
|
*/
|
2013-09-15 07:03:53 -07:00
|
|
|
static inline u32 CalcOwnerMask(player_id_t owner)
|
2010-07-29 13:39:23 -07:00
|
|
|
{
|
2010-09-23 05:13:13 -07:00
|
|
|
if (owner >= -1 && owner < 31)
|
2010-07-29 13:39:23 -07:00
|
|
|
return 1 << (1+owner);
|
|
|
|
|
else
|
|
|
|
|
return 0; // owner was invalid
|
|
|
|
|
}
|
|
|
|
|
|
2012-06-09 16:04:27 -07:00
|
|
|
/**
|
|
|
|
|
* Returns LOS mask for given player.
|
|
|
|
|
*/
|
2013-09-15 07:03:53 -07:00
|
|
|
static inline u32 CalcPlayerLosMask(player_id_t player)
|
2012-06-09 16:04:27 -07:00
|
|
|
{
|
|
|
|
|
if (player > 0 && player <= 16)
|
|
|
|
|
return ICmpRangeManager::LOS_MASK << (2*(player-1));
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2012-06-07 21:41:39 -07:00
|
|
|
/**
|
|
|
|
|
* Returns shared LOS mask for given list of players.
|
|
|
|
|
*/
|
|
|
|
|
static u32 CalcSharedLosMask(std::vector<player_id_t> players)
|
|
|
|
|
{
|
|
|
|
|
u32 playerMask = 0;
|
|
|
|
|
for (size_t i = 0; i < players.size(); i++)
|
2012-06-09 16:04:27 -07:00
|
|
|
playerMask |= CalcPlayerLosMask(players[i]);
|
2012-06-07 21:41:39 -07:00
|
|
|
|
|
|
|
|
return playerMask;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-20 08:29:50 -08:00
|
|
|
/**
|
2016-11-24 06:57:09 -08:00
|
|
|
* Add/remove a player to/from mask, which is a 1-bit mask representing a list of players.
|
2015-07-11 09:45:09 -07:00
|
|
|
* Returns true if the mask is modified.
|
2015-02-12 15:22:29 -08:00
|
|
|
*/
|
2016-11-24 06:57:09 -08:00
|
|
|
static bool SetPlayerSharedDirtyVisibilityBit(u16& mask, player_id_t player, bool enable)
|
2015-02-12 15:22:29 -08:00
|
|
|
{
|
2016-11-24 06:57:09 -08:00
|
|
|
if (player <= 0 || player > 16)
|
|
|
|
|
return false;
|
|
|
|
|
|
2015-07-11 09:45:09 -07:00
|
|
|
u16 oldMask = mask;
|
2016-11-24 06:57:09 -08:00
|
|
|
|
|
|
|
|
if (enable)
|
2015-07-11 09:45:09 -07:00
|
|
|
mask |= (0x1 << (player - 1));
|
2016-11-24 06:57:09 -08:00
|
|
|
else
|
|
|
|
|
mask &= ~(0x1 << (player - 1));
|
|
|
|
|
|
2015-07-11 09:45:09 -07:00
|
|
|
return oldMask != mask;
|
2015-02-12 15:22:29 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Computes the 2-bit visibility for one player, given the total 32-bit visibilities
|
|
|
|
|
*/
|
2015-01-20 08:29:50 -08:00
|
|
|
static inline u8 GetPlayerVisibility(u32 visibilities, player_id_t player)
|
|
|
|
|
{
|
|
|
|
|
if (player > 0 && player <= 16)
|
|
|
|
|
return (visibilities >> (2 *(player-1))) & 0x3;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-12 15:22:29 -08:00
|
|
|
/**
|
|
|
|
|
* Test whether the visibility is dirty for a given LoS tile and a given player
|
|
|
|
|
*/
|
|
|
|
|
static inline bool IsVisibilityDirty(u16 dirty, player_id_t player)
|
|
|
|
|
{
|
|
|
|
|
if (player > 0 && player <= 16)
|
|
|
|
|
return (dirty >> (player - 1)) & 0x1;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
/**
|
|
|
|
|
* Test whether a player share this vision
|
|
|
|
|
*/
|
|
|
|
|
static inline bool HasVisionSharing(u16 visionSharing, player_id_t player)
|
|
|
|
|
{
|
|
|
|
|
return visionSharing & 1 << (player-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Computes the shared vision mask for the player
|
|
|
|
|
*/
|
|
|
|
|
static inline u16 CalcVisionSharingMask(player_id_t player)
|
|
|
|
|
{
|
|
|
|
|
return 1 << (player-1);
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-03 12:20:20 -07:00
|
|
|
/**
|
|
|
|
|
* Checks whether v is in a parabolic range of (0,0,0)
|
|
|
|
|
* The highest point of the paraboloid is (0,range/2,0)
|
|
|
|
|
* and the circle of distance 'range' around (0,0,0) on height y=0 is part of the paraboloid
|
2014-01-06 20:05:10 -08:00
|
|
|
*
|
2013-08-03 12:20:20 -07:00
|
|
|
* Avoids sqrting and overflowing.
|
|
|
|
|
*/
|
2014-01-06 20:05:10 -08:00
|
|
|
static bool InParabolicRange(CFixedVector3D v, fixed range)
|
2013-08-03 12:20:20 -07:00
|
|
|
{
|
2013-09-20 13:17:54 -07:00
|
|
|
i32 x = v.X.GetInternalValue(); // abs(x) <= 2^31
|
|
|
|
|
i32 z = v.Z.GetInternalValue();
|
|
|
|
|
u64 xx = (u64)FIXED_MUL_I64_I32_I32(x, x); // xx <= 2^62
|
|
|
|
|
u64 zz = (u64)FIXED_MUL_I64_I32_I32(z, z);
|
2013-08-03 12:20:20 -07:00
|
|
|
i64 d2 = (xx + zz) >> 1; // d2 <= 2^62 (no overflow)
|
2014-01-06 20:05:10 -08:00
|
|
|
|
2013-09-20 13:17:54 -07:00
|
|
|
i32 y = v.Y.GetInternalValue();
|
|
|
|
|
i32 c = range.GetInternalValue();
|
|
|
|
|
i32 c_2 = c >> 1;
|
2013-08-03 12:20:20 -07:00
|
|
|
|
2013-09-20 13:17:54 -07:00
|
|
|
i64 c2 = FIXED_MUL_I64_I32_I32(c_2 - y, c);
|
2013-08-03 12:20:20 -07:00
|
|
|
|
|
|
|
|
if (d2 <= c2)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct EntityParabolicRangeOutline
|
|
|
|
|
{
|
|
|
|
|
entity_id_t source;
|
|
|
|
|
CFixedVector3D position;
|
|
|
|
|
entity_pos_t range;
|
|
|
|
|
std::vector<entity_pos_t> outline;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static std::map<entity_id_t, EntityParabolicRangeOutline> ParabolicRangesOutlines;
|
|
|
|
|
|
2010-07-29 13:39:23 -07:00
|
|
|
/**
|
|
|
|
|
* Representation of an entity, with the data needed for queries.
|
|
|
|
|
*/
|
2017-02-24 11:15:54 -08:00
|
|
|
enum FlagMasks
|
|
|
|
|
{
|
|
|
|
|
// flags used for queries
|
|
|
|
|
None = 0x00,
|
|
|
|
|
Normal = 0x01,
|
|
|
|
|
Injured = 0x02,
|
|
|
|
|
AllQuery = Normal | Injured,
|
|
|
|
|
|
|
|
|
|
// 0x04 reserved for future use
|
|
|
|
|
|
|
|
|
|
// general flags
|
|
|
|
|
InWorld = 0x08,
|
|
|
|
|
RetainInFog = 0x10,
|
|
|
|
|
RevealShore = 0x20,
|
|
|
|
|
ScriptedVisibility = 0x40,
|
|
|
|
|
SharedVision = 0x80
|
|
|
|
|
};
|
|
|
|
|
|
2010-07-29 13:39:23 -07:00
|
|
|
struct EntityData
|
|
|
|
|
{
|
2016-10-28 08:34:24 -07:00
|
|
|
EntityData() :
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
visibilities(0), size(0), visionSharing(0),
|
2017-02-24 11:15:54 -08:00
|
|
|
owner(-1), flags(FlagMasks::Normal)
|
2016-10-28 08:34:24 -07:00
|
|
|
{ }
|
2010-07-29 13:39:23 -07:00
|
|
|
entity_pos_t x, z;
|
2010-09-23 05:13:13 -07:00
|
|
|
entity_pos_t visionRange;
|
2014-07-10 13:51:39 -07:00
|
|
|
u32 visibilities; // 2-bit visibility, per player
|
2015-04-14 14:33:43 -07:00
|
|
|
u32 size;
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
u16 visionSharing; // 1-bit per player
|
2010-09-23 05:13:13 -07:00
|
|
|
i8 owner;
|
2017-02-24 11:15:54 -08:00
|
|
|
u8 flags; // See the FlagMasks enum
|
|
|
|
|
|
|
|
|
|
template<int mask>
|
|
|
|
|
inline bool HasFlag() const { return (flags & mask) != 0; }
|
|
|
|
|
|
|
|
|
|
template<int mask>
|
|
|
|
|
inline void SetFlag(bool val) { flags = val ? (flags | mask) : (flags & ~mask); }
|
|
|
|
|
|
|
|
|
|
inline void SetFlag(u8 mask, bool val) { flags = val ? (flags | mask) : (flags & ~mask); }
|
2010-07-29 13:39:23 -07:00
|
|
|
};
|
|
|
|
|
|
2017-02-24 11:15:54 -08:00
|
|
|
cassert(sizeof(EntityData) == 24);
|
2010-07-29 13:39:23 -07:00
|
|
|
|
2010-10-23 12:59:40 -07:00
|
|
|
/**
|
|
|
|
|
* Serialization helper template for Query
|
|
|
|
|
*/
|
|
|
|
|
struct SerializeQuery
|
|
|
|
|
{
|
|
|
|
|
template<typename S>
|
2013-09-11 13:41:53 -07:00
|
|
|
void Common(S& serialize, const char* UNUSED(name), Query& value)
|
2010-10-23 12:59:40 -07:00
|
|
|
{
|
|
|
|
|
serialize.Bool("enabled", value.enabled);
|
2013-08-03 12:20:20 -07:00
|
|
|
serialize.Bool("parabolic",value.parabolic);
|
2010-11-16 12:43:15 -08:00
|
|
|
serialize.NumberFixed_Unbounded("min range", value.minRange);
|
2010-10-23 12:59:40 -07:00
|
|
|
serialize.NumberFixed_Unbounded("max range", value.maxRange);
|
2013-08-03 12:20:20 -07:00
|
|
|
serialize.NumberFixed_Unbounded("elevation bonus", value.elevationBonus);
|
2010-10-23 12:59:40 -07:00
|
|
|
serialize.NumberU32_Unbounded("owners mask", value.ownersMask);
|
|
|
|
|
serialize.NumberI32_Unbounded("interface", value.interface);
|
|
|
|
|
SerializeVector<SerializeU32_Unbounded>()(serialize, "last match", value.lastMatch);
|
2012-04-17 13:44:18 -07:00
|
|
|
serialize.NumberU8_Unbounded("flagsMask", value.flagsMask);
|
2010-10-23 12:59:40 -07:00
|
|
|
}
|
2013-09-11 13:41:53 -07:00
|
|
|
|
|
|
|
|
void operator()(ISerializer& serialize, const char* name, Query& value, const CSimContext& UNUSED(context))
|
|
|
|
|
{
|
|
|
|
|
Common(serialize, name, value);
|
|
|
|
|
|
|
|
|
|
uint32_t id = value.source.GetId();
|
|
|
|
|
serialize.NumberU32_Unbounded("source", id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void operator()(IDeserializer& deserialize, const char* name, Query& value, const CSimContext& context)
|
|
|
|
|
{
|
|
|
|
|
Common(deserialize, name, value);
|
|
|
|
|
|
|
|
|
|
uint32_t id;
|
|
|
|
|
deserialize.NumberU32_Unbounded("source", id);
|
|
|
|
|
value.source = context.GetComponentManager().LookupEntityHandle(id, true);
|
|
|
|
|
// the referenced entity might not have been deserialized yet,
|
|
|
|
|
// so tell LookupEntityHandle to allocate the handle if necessary
|
|
|
|
|
}
|
2010-10-23 12:59:40 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Serialization helper template for EntityData
|
|
|
|
|
*/
|
|
|
|
|
struct SerializeEntityData
|
|
|
|
|
{
|
|
|
|
|
template<typename S>
|
|
|
|
|
void operator()(S& serialize, const char* UNUSED(name), EntityData& value)
|
|
|
|
|
{
|
|
|
|
|
serialize.NumberFixed_Unbounded("x", value.x);
|
|
|
|
|
serialize.NumberFixed_Unbounded("z", value.z);
|
|
|
|
|
serialize.NumberFixed_Unbounded("vision", value.visionRange);
|
2014-07-10 13:51:39 -07:00
|
|
|
serialize.NumberU32_Unbounded("visibilities", value.visibilities);
|
2015-04-14 14:33:43 -07:00
|
|
|
serialize.NumberU32_Unbounded("size", value.size);
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
serialize.NumberU16_Unbounded("vision sharing", value.visionSharing);
|
2010-10-23 12:59:40 -07:00
|
|
|
serialize.NumberI8_Unbounded("owner", value.owner);
|
2012-04-17 13:44:18 -07:00
|
|
|
serialize.NumberU8_Unbounded("flags", value.flags);
|
2010-10-23 12:59:40 -07:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2010-07-29 13:39:23 -07:00
|
|
|
/**
|
|
|
|
|
* Functor for sorting entities by distance from a source point.
|
2010-07-31 14:22:39 -07:00
|
|
|
* It must only be passed entities that are in 'entities'
|
2010-07-29 13:39:23 -07:00
|
|
|
* and are currently in the world.
|
|
|
|
|
*/
|
|
|
|
|
struct EntityDistanceOrdering
|
|
|
|
|
{
|
2013-09-15 07:03:53 -07:00
|
|
|
EntityDistanceOrdering(const EntityMap<EntityData>& entities, const CFixedVector2D& source) :
|
2010-07-31 14:22:39 -07:00
|
|
|
m_EntityData(entities), m_Source(source)
|
2010-07-29 13:39:23 -07:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
bool operator()(entity_id_t a, entity_id_t b) const
|
2010-07-29 13:39:23 -07:00
|
|
|
{
|
2010-07-31 14:22:39 -07:00
|
|
|
const EntityData& da = m_EntityData.find(a)->second;
|
|
|
|
|
const EntityData& db = m_EntityData.find(b)->second;
|
|
|
|
|
CFixedVector2D vecA = CFixedVector2D(da.x, da.z) - m_Source;
|
|
|
|
|
CFixedVector2D vecB = CFixedVector2D(db.x, db.z) - m_Source;
|
2010-07-29 13:39:23 -07:00
|
|
|
return (vecA.CompareLength(vecB) < 0);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-15 07:03:53 -07:00
|
|
|
const EntityMap<EntityData>& m_EntityData;
|
2010-07-31 14:22:39 -07:00
|
|
|
CFixedVector2D m_Source;
|
2010-07-29 14:04:07 -07:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
EntityDistanceOrdering& operator=(const EntityDistanceOrdering&);
|
2010-07-29 13:39:23 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
2010-09-23 05:13:13 -07:00
|
|
|
* Range manager implementation.
|
2010-07-29 13:39:23 -07:00
|
|
|
* Maintains a list of all entities (and their positions and owners), which is used for
|
|
|
|
|
* queries.
|
|
|
|
|
*
|
2010-09-23 05:13:13 -07:00
|
|
|
* LOS implementation is based on the model described in GPG2.
|
|
|
|
|
* (TODO: would be nice to make it cleverer, so e.g. mountains and walls
|
|
|
|
|
* can block vision)
|
2010-07-29 13:39:23 -07:00
|
|
|
*/
|
|
|
|
|
class CCmpRangeManager : public ICmpRangeManager
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
static void ClassInit(CComponentManager& componentManager)
|
|
|
|
|
{
|
|
|
|
|
componentManager.SubscribeGloballyToMessageType(MT_Create);
|
|
|
|
|
componentManager.SubscribeGloballyToMessageType(MT_PositionChanged);
|
|
|
|
|
componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged);
|
|
|
|
|
componentManager.SubscribeGloballyToMessageType(MT_Destroy);
|
2012-09-24 15:27:32 -07:00
|
|
|
componentManager.SubscribeGloballyToMessageType(MT_VisionRangeChanged);
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
componentManager.SubscribeGloballyToMessageType(MT_VisionSharingChanged);
|
2010-07-29 13:39:23 -07:00
|
|
|
|
2016-10-28 08:34:24 -07:00
|
|
|
componentManager.SubscribeToMessageType(MT_Deserialized);
|
2010-07-29 13:39:23 -07:00
|
|
|
componentManager.SubscribeToMessageType(MT_Update);
|
|
|
|
|
componentManager.SubscribeToMessageType(MT_RenderSubmit); // for debug overlays
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DEFAULT_COMPONENT_ALLOCATOR(RangeManager)
|
|
|
|
|
|
|
|
|
|
bool m_DebugOverlayEnabled;
|
|
|
|
|
bool m_DebugOverlayDirty;
|
|
|
|
|
std::vector<SOverlayLine> m_DebugOverlayLines;
|
|
|
|
|
|
2015-11-11 04:15:57 -08:00
|
|
|
// Deserialization flag. A lot of different functions are called by Deserialize()
|
|
|
|
|
// and we don't want to pass isDeserializing bool arguments to all of them...
|
|
|
|
|
bool m_Deserializing;
|
|
|
|
|
|
2010-10-23 12:59:40 -07:00
|
|
|
// World bounds (entities are expected to be within this range)
|
|
|
|
|
entity_pos_t m_WorldX0;
|
|
|
|
|
entity_pos_t m_WorldZ0;
|
|
|
|
|
entity_pos_t m_WorldX1;
|
|
|
|
|
entity_pos_t m_WorldZ1;
|
2010-09-19 11:08:56 -07:00
|
|
|
|
2010-09-23 05:13:13 -07:00
|
|
|
// Range query state:
|
2010-07-29 13:39:23 -07:00
|
|
|
tag_t m_QueryNext; // next allocated id
|
|
|
|
|
std::map<tag_t, Query> m_Queries;
|
2013-09-15 07:03:53 -07:00
|
|
|
EntityMap<EntityData> m_EntityData;
|
|
|
|
|
|
2015-04-14 14:33:43 -07:00
|
|
|
FastSpatialSubdivision m_Subdivision; // spatial index of m_EntityData
|
2016-06-08 10:06:58 -07:00
|
|
|
std::vector<entity_id_t> m_SubdivisionResults;
|
2010-07-29 13:39:23 -07:00
|
|
|
|
2010-09-23 05:13:13 -07:00
|
|
|
// LOS state:
|
2013-12-31 08:38:11 -08:00
|
|
|
static const player_id_t MAX_LOS_PLAYER_ID = 16;
|
2010-09-23 05:13:13 -07:00
|
|
|
|
2013-12-31 08:38:11 -08:00
|
|
|
std::vector<bool> m_LosRevealAll;
|
2010-10-25 14:59:52 -07:00
|
|
|
bool m_LosCircular;
|
2010-10-23 12:59:40 -07:00
|
|
|
i32 m_TerrainVerticesPerSide;
|
2016-10-28 09:11:59 -07:00
|
|
|
|
2015-11-11 04:15:57 -08:00
|
|
|
// Cache for visibility tracking
|
2014-07-10 13:51:39 -07:00
|
|
|
i32 m_LosTilesPerSide;
|
2014-09-20 01:26:45 -07:00
|
|
|
bool m_GlobalVisibilityUpdate;
|
2015-02-20 06:35:19 -08:00
|
|
|
std::vector<u8> m_GlobalPlayerVisibilityUpdate;
|
2015-02-12 15:22:29 -08:00
|
|
|
std::vector<u16> m_DirtyVisibility;
|
2014-07-10 13:51:39 -07:00
|
|
|
std::vector<std::set<entity_id_t> > m_LosTiles;
|
|
|
|
|
// List of entities that must be updated, regardless of the status of their tile
|
|
|
|
|
std::vector<entity_id_t> m_ModifiedEntities;
|
2010-09-23 05:13:13 -07:00
|
|
|
|
|
|
|
|
// Counts of units seeing vertex, per vertex, per player (starting with player 0).
|
|
|
|
|
// Use u16 to avoid overflows when we have very large (but not infeasibly large) numbers
|
|
|
|
|
// of units in a very small area.
|
|
|
|
|
// (Note we use vertexes, not tiles, to better match the renderer.)
|
|
|
|
|
// Lazily constructed when it's needed, to save memory in smaller games.
|
|
|
|
|
std::vector<std::vector<u16> > m_LosPlayerCounts;
|
|
|
|
|
|
|
|
|
|
// 2-bit ELosState per player, starting with player 1 (not 0!) up to player MAX_LOS_PLAYER_ID (inclusive)
|
|
|
|
|
std::vector<u32> m_LosState;
|
|
|
|
|
|
2010-10-25 14:59:52 -07:00
|
|
|
// Special static visibility data for the "reveal whole map" mode
|
|
|
|
|
// (TODO: this is usually a waste of memory)
|
|
|
|
|
std::vector<u32> m_LosStateRevealed;
|
|
|
|
|
|
2012-06-07 21:41:39 -07:00
|
|
|
// Shared LOS masks, one per player.
|
2013-12-31 08:38:11 -08:00
|
|
|
std::vector<u32> m_SharedLosMasks;
|
2015-02-12 15:22:29 -08:00
|
|
|
// Shared dirty visibility masks, one per player.
|
|
|
|
|
std::vector<u16> m_SharedDirtyVisibilityMasks;
|
2012-06-07 21:41:39 -07:00
|
|
|
|
2013-07-19 17:50:40 -07:00
|
|
|
// Cache explored vertices per player (not serialized)
|
|
|
|
|
u32 m_TotalInworldVertices;
|
|
|
|
|
std::vector<u32> m_ExploredVertices;
|
|
|
|
|
|
2010-07-29 13:39:23 -07:00
|
|
|
static std::string GetSchema()
|
|
|
|
|
{
|
|
|
|
|
return "<a:component type='system'/><empty/>";
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-16 06:08:38 -08:00
|
|
|
virtual void Init(const CParamNode& UNUSED(paramNode))
|
2010-07-29 13:39:23 -07:00
|
|
|
{
|
|
|
|
|
m_QueryNext = 1;
|
|
|
|
|
|
|
|
|
|
m_DebugOverlayEnabled = false;
|
|
|
|
|
m_DebugOverlayDirty = true;
|
2010-09-19 11:08:56 -07:00
|
|
|
|
2015-11-11 04:15:57 -08:00
|
|
|
m_Deserializing = false;
|
2010-10-23 12:59:40 -07:00
|
|
|
m_WorldX0 = m_WorldZ0 = m_WorldX1 = m_WorldZ1 = entity_pos_t::Zero();
|
|
|
|
|
|
2010-09-19 11:08:56 -07:00
|
|
|
// Initialise with bogus values (these will get replaced when
|
|
|
|
|
// SetBounds is called)
|
2013-09-15 07:03:53 -07:00
|
|
|
ResetSubdivisions(entity_pos_t::FromInt(1024), entity_pos_t::FromInt(1024));
|
2010-09-23 05:13:13 -07:00
|
|
|
|
2016-06-08 10:06:58 -07:00
|
|
|
m_SubdivisionResults.reserve(4096);
|
|
|
|
|
|
2011-06-28 16:24:42 -07:00
|
|
|
// The whole map should be visible to Gaia by default, else e.g. animals
|
|
|
|
|
// will get confused when trying to run from enemies
|
2013-12-31 13:30:48 -08:00
|
|
|
m_LosRevealAll.resize(MAX_LOS_PLAYER_ID+2,false);
|
2014-06-11 11:20:02 -07:00
|
|
|
m_LosRevealAll[0] = true;
|
2013-12-31 13:30:48 -08:00
|
|
|
m_SharedLosMasks.resize(MAX_LOS_PLAYER_ID+2,0);
|
2015-02-12 15:22:29 -08:00
|
|
|
m_SharedDirtyVisibilityMasks.resize(MAX_LOS_PLAYER_ID + 2, 0);
|
2014-01-06 20:05:10 -08:00
|
|
|
|
2015-11-11 04:15:57 -08:00
|
|
|
m_GlobalVisibilityUpdate = true;
|
|
|
|
|
m_GlobalPlayerVisibilityUpdate.resize(MAX_LOS_PLAYER_ID);
|
|
|
|
|
|
2010-10-25 14:59:52 -07:00
|
|
|
m_LosCircular = false;
|
2010-09-23 05:13:13 -07:00
|
|
|
m_TerrainVerticesPerSide = 0;
|
2010-07-29 13:39:23 -07:00
|
|
|
}
|
|
|
|
|
|
2011-01-16 06:08:38 -08:00
|
|
|
virtual void Deinit()
|
2010-07-29 13:39:23 -07:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-23 12:59:40 -07:00
|
|
|
template<typename S>
|
|
|
|
|
void SerializeCommon(S& serialize)
|
|
|
|
|
{
|
|
|
|
|
serialize.NumberFixed_Unbounded("world x0", m_WorldX0);
|
|
|
|
|
serialize.NumberFixed_Unbounded("world z0", m_WorldZ0);
|
|
|
|
|
serialize.NumberFixed_Unbounded("world x1", m_WorldX1);
|
|
|
|
|
serialize.NumberFixed_Unbounded("world z1", m_WorldZ1);
|
|
|
|
|
|
|
|
|
|
serialize.NumberU32_Unbounded("query next", m_QueryNext);
|
2013-09-11 13:41:53 -07:00
|
|
|
SerializeMap<SerializeU32_Unbounded, SerializeQuery>()(serialize, "queries", m_Queries, GetSimContext());
|
2013-09-15 07:03:53 -07:00
|
|
|
SerializeEntityMap<SerializeEntityData>()(serialize, "entity data", m_EntityData);
|
2010-10-23 12:59:40 -07:00
|
|
|
|
2013-12-31 08:38:11 -08:00
|
|
|
SerializeVector<SerializeBool>()(serialize, "los reveal all", m_LosRevealAll);
|
2010-10-25 14:59:52 -07:00
|
|
|
serialize.Bool("los circular", m_LosCircular);
|
2010-10-23 12:59:40 -07:00
|
|
|
serialize.NumberI32_Unbounded("terrain verts per side", m_TerrainVerticesPerSide);
|
|
|
|
|
|
2015-11-11 04:15:57 -08:00
|
|
|
serialize.Bool("global visibility update", m_GlobalVisibilityUpdate);
|
|
|
|
|
SerializeVector<SerializeU8_Unbounded>()(serialize, "global player visibility update", m_GlobalPlayerVisibilityUpdate);
|
2016-05-02 02:26:07 -07:00
|
|
|
SerializeRepetitiveVector<SerializeU16_Unbounded>()(serialize, "dirty visibility", m_DirtyVisibility);
|
2014-07-10 13:51:39 -07:00
|
|
|
SerializeVector<SerializeU32_Unbounded>()(serialize, "modified entities", m_ModifiedEntities);
|
|
|
|
|
|
|
|
|
|
// We don't serialize m_Subdivision, m_LosPlayerCounts or m_LosTiles
|
2011-08-01 14:25:12 -07:00
|
|
|
// since they can be recomputed from the entity data when deserializing;
|
|
|
|
|
// m_LosState must be serialized since it depends on the history of exploration
|
|
|
|
|
|
2016-05-02 02:26:07 -07:00
|
|
|
SerializeRepetitiveVector<SerializeU32_Unbounded>()(serialize, "los state", m_LosState);
|
2013-12-31 08:38:11 -08:00
|
|
|
SerializeVector<SerializeU32_Unbounded>()(serialize, "shared los masks", m_SharedLosMasks);
|
2015-02-12 15:22:29 -08:00
|
|
|
SerializeVector<SerializeU16_Unbounded>()(serialize, "shared dirty visibility masks", m_SharedDirtyVisibilityMasks);
|
2010-10-23 12:59:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual void Serialize(ISerializer& serialize)
|
2010-07-29 13:39:23 -07:00
|
|
|
{
|
2010-10-23 12:59:40 -07:00
|
|
|
SerializeCommon(serialize);
|
2010-07-29 13:39:23 -07:00
|
|
|
}
|
|
|
|
|
|
2011-01-16 06:08:38 -08:00
|
|
|
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
|
2010-07-29 13:39:23 -07:00
|
|
|
{
|
2011-01-16 06:08:38 -08:00
|
|
|
Init(paramNode);
|
2010-10-23 12:59:40 -07:00
|
|
|
|
|
|
|
|
SerializeCommon(deserialize);
|
2010-07-29 13:39:23 -07:00
|
|
|
}
|
|
|
|
|
|
2011-01-16 06:08:38 -08:00
|
|
|
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
|
2010-07-29 13:39:23 -07:00
|
|
|
{
|
|
|
|
|
switch (msg.GetType())
|
|
|
|
|
{
|
2016-10-28 08:34:24 -07:00
|
|
|
case MT_Deserialized:
|
|
|
|
|
{
|
|
|
|
|
// Reinitialize subdivisions and LOS data after all
|
|
|
|
|
// other components have been deserialized.
|
|
|
|
|
m_Deserializing = true;
|
|
|
|
|
ResetDerivedData();
|
|
|
|
|
m_Deserializing = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2010-07-29 13:39:23 -07:00
|
|
|
case MT_Create:
|
|
|
|
|
{
|
|
|
|
|
const CMessageCreate& msgData = static_cast<const CMessageCreate&> (msg);
|
|
|
|
|
entity_id_t ent = msgData.entity;
|
|
|
|
|
|
|
|
|
|
// Ignore local entities - we shouldn't let them influence anything
|
|
|
|
|
if (ENTITY_IS_LOCAL(ent))
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// Ignore non-positional entities
|
|
|
|
|
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), ent);
|
2012-02-07 18:46:15 -08:00
|
|
|
if (!cmpPosition)
|
2010-07-29 13:39:23 -07:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// The newly-created entity will have owner -1 and position out-of-world
|
|
|
|
|
// (any initialisation of those values will happen later), so we can just
|
|
|
|
|
// use the default-constructed EntityData here
|
|
|
|
|
EntityData entdata;
|
|
|
|
|
|
2010-09-23 05:13:13 -07:00
|
|
|
// Store the LOS data, if any
|
|
|
|
|
CmpPtr<ICmpVision> cmpVision(GetSimContext(), ent);
|
2012-02-07 18:46:15 -08:00
|
|
|
if (cmpVision)
|
2016-10-28 08:34:24 -07:00
|
|
|
{
|
2010-09-23 05:13:13 -07:00
|
|
|
entdata.visionRange = cmpVision->GetRange();
|
2017-02-24 11:15:54 -08:00
|
|
|
entdata.SetFlag<FlagMasks::RevealShore>(cmpVision->GetRevealShore());
|
2016-10-28 08:34:24 -07:00
|
|
|
}
|
2014-12-05 10:33:59 -08:00
|
|
|
CmpPtr<ICmpVisibility> cmpVisibility(GetSimContext(), ent);
|
|
|
|
|
if (cmpVisibility)
|
2017-02-24 11:15:54 -08:00
|
|
|
entdata.SetFlag<FlagMasks::RetainInFog>(cmpVisibility->GetRetainInFog());
|
2010-09-23 05:13:13 -07:00
|
|
|
|
2015-04-14 14:33:43 -07:00
|
|
|
// Store the size
|
|
|
|
|
CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), ent);
|
|
|
|
|
if (cmpObstruction)
|
|
|
|
|
entdata.size = cmpObstruction->GetSize().ToInt_RoundToInfinity();
|
|
|
|
|
|
2010-07-29 13:39:23 -07:00
|
|
|
// Remember this entity
|
2019-01-05 14:38:00 -08:00
|
|
|
m_EntityData.insert(ent, entdata);
|
2010-07-29 13:39:23 -07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case MT_PositionChanged:
|
|
|
|
|
{
|
|
|
|
|
const CMessagePositionChanged& msgData = static_cast<const CMessagePositionChanged&> (msg);
|
|
|
|
|
entity_id_t ent = msgData.entity;
|
|
|
|
|
|
2013-09-15 07:03:53 -07:00
|
|
|
EntityMap<EntityData>::iterator it = m_EntityData.find(ent);
|
2010-07-29 13:39:23 -07:00
|
|
|
|
|
|
|
|
// Ignore if we're not already tracking this entity
|
|
|
|
|
if (it == m_EntityData.end())
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (msgData.inWorld)
|
|
|
|
|
{
|
2017-02-24 11:15:54 -08:00
|
|
|
if (it->second.HasFlag<FlagMasks::InWorld>())
|
2010-09-23 05:13:13 -07:00
|
|
|
{
|
|
|
|
|
CFixedVector2D from(it->second.x, it->second.z);
|
|
|
|
|
CFixedVector2D to(msgData.x, msgData.z);
|
2015-04-14 14:33:43 -07:00
|
|
|
m_Subdivision.Move(ent, from, to, it->second.size);
|
2017-02-24 11:15:54 -08:00
|
|
|
if (it->second.HasFlag<FlagMasks::SharedVision>())
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
SharingLosMove(it->second.visionSharing, it->second.visionRange, from, to);
|
|
|
|
|
else
|
|
|
|
|
LosMove(it->second.owner, it->second.visionRange, from, to);
|
2014-07-10 13:51:39 -07:00
|
|
|
i32 oldLosTile = PosToLosTilesHelper(it->second.x, it->second.z);
|
|
|
|
|
i32 newLosTile = PosToLosTilesHelper(msgData.x, msgData.z);
|
|
|
|
|
if (oldLosTile != newLosTile)
|
|
|
|
|
{
|
|
|
|
|
RemoveFromTile(oldLosTile, ent);
|
|
|
|
|
AddToTile(newLosTile, ent);
|
|
|
|
|
}
|
2010-09-23 05:13:13 -07:00
|
|
|
}
|
2010-09-19 11:08:56 -07:00
|
|
|
else
|
2010-09-23 05:13:13 -07:00
|
|
|
{
|
|
|
|
|
CFixedVector2D to(msgData.x, msgData.z);
|
2015-04-14 14:33:43 -07:00
|
|
|
m_Subdivision.Add(ent, to, it->second.size);
|
2017-02-24 11:15:54 -08:00
|
|
|
if (it->second.HasFlag<FlagMasks::SharedVision>())
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
SharingLosAdd(it->second.visionSharing, it->second.visionRange, to);
|
|
|
|
|
else
|
|
|
|
|
LosAdd(it->second.owner, it->second.visionRange, to);
|
2014-07-10 13:51:39 -07:00
|
|
|
AddToTile(PosToLosTilesHelper(msgData.x, msgData.z), ent);
|
2010-09-23 05:13:13 -07:00
|
|
|
}
|
2010-09-19 11:08:56 -07:00
|
|
|
|
2017-02-24 11:15:54 -08:00
|
|
|
it->second.SetFlag<FlagMasks::InWorld>(true);
|
2010-07-29 13:39:23 -07:00
|
|
|
it->second.x = msgData.x;
|
|
|
|
|
it->second.z = msgData.z;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2017-02-24 11:15:54 -08:00
|
|
|
if (it->second.HasFlag<FlagMasks::InWorld>())
|
2010-09-23 05:13:13 -07:00
|
|
|
{
|
|
|
|
|
CFixedVector2D from(it->second.x, it->second.z);
|
2015-04-14 14:33:43 -07:00
|
|
|
m_Subdivision.Remove(ent, from, it->second.size);
|
2017-02-24 11:15:54 -08:00
|
|
|
if (it->second.HasFlag<FlagMasks::SharedVision>())
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
SharingLosRemove(it->second.visionSharing, it->second.visionRange, from);
|
|
|
|
|
else
|
|
|
|
|
LosRemove(it->second.owner, it->second.visionRange, from);
|
2014-07-10 13:51:39 -07:00
|
|
|
RemoveFromTile(PosToLosTilesHelper(it->second.x, it->second.z), ent);
|
2010-09-23 05:13:13 -07:00
|
|
|
}
|
2010-09-19 11:08:56 -07:00
|
|
|
|
2017-02-24 11:15:54 -08:00
|
|
|
it->second.SetFlag<FlagMasks::InWorld>(false);
|
2010-07-29 13:39:23 -07:00
|
|
|
it->second.x = entity_pos_t::Zero();
|
|
|
|
|
it->second.z = entity_pos_t::Zero();
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-12 15:24:48 -08:00
|
|
|
RequestVisibilityUpdate(ent);
|
2014-07-10 13:51:39 -07:00
|
|
|
|
2010-07-29 13:39:23 -07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case MT_OwnershipChanged:
|
|
|
|
|
{
|
|
|
|
|
const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg);
|
|
|
|
|
entity_id_t ent = msgData.entity;
|
|
|
|
|
|
2013-09-15 07:03:53 -07:00
|
|
|
EntityMap<EntityData>::iterator it = m_EntityData.find(ent);
|
2010-07-29 13:39:23 -07:00
|
|
|
|
|
|
|
|
// Ignore if we're not already tracking this entity
|
|
|
|
|
if (it == m_EntityData.end())
|
|
|
|
|
break;
|
|
|
|
|
|
2017-02-24 11:15:54 -08:00
|
|
|
if (it->second.HasFlag<FlagMasks::InWorld>())
|
2010-09-23 05:13:13 -07:00
|
|
|
{
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
// Entity vision is taken into account in VisionSharingChanged
|
|
|
|
|
// when sharing component activated
|
2017-02-24 11:15:54 -08:00
|
|
|
if (!it->second.HasFlag<FlagMasks::SharedVision>())
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
{
|
|
|
|
|
CFixedVector2D pos(it->second.x, it->second.z);
|
|
|
|
|
LosRemove(it->second.owner, it->second.visionRange, pos);
|
|
|
|
|
LosAdd(msgData.to, it->second.visionRange, pos);
|
|
|
|
|
}
|
2016-10-28 08:34:24 -07:00
|
|
|
|
2017-02-24 11:15:54 -08:00
|
|
|
if (it->second.HasFlag<FlagMasks::RevealShore>())
|
2016-10-28 08:34:24 -07:00
|
|
|
{
|
|
|
|
|
RevealShore(it->second.owner, false);
|
|
|
|
|
RevealShore(msgData.to, true);
|
|
|
|
|
}
|
2010-09-23 05:13:13 -07:00
|
|
|
}
|
|
|
|
|
|
2011-08-16 04:18:32 -07:00
|
|
|
ENSURE(-128 <= msgData.to && msgData.to <= 127);
|
|
|
|
|
it->second.owner = (i8)msgData.to;
|
2010-07-29 13:39:23 -07:00
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case MT_Destroy:
|
|
|
|
|
{
|
|
|
|
|
const CMessageDestroy& msgData = static_cast<const CMessageDestroy&> (msg);
|
|
|
|
|
entity_id_t ent = msgData.entity;
|
|
|
|
|
|
2013-09-15 07:03:53 -07:00
|
|
|
EntityMap<EntityData>::iterator it = m_EntityData.find(ent);
|
2010-09-19 11:08:56 -07:00
|
|
|
|
|
|
|
|
// Ignore if we're not already tracking this entity
|
|
|
|
|
if (it == m_EntityData.end())
|
|
|
|
|
break;
|
|
|
|
|
|
2017-02-24 11:15:54 -08:00
|
|
|
if (it->second.HasFlag<FlagMasks::InWorld>())
|
2014-07-10 13:51:39 -07:00
|
|
|
{
|
2015-04-14 14:33:43 -07:00
|
|
|
m_Subdivision.Remove(ent, CFixedVector2D(it->second.x, it->second.z), it->second.size);
|
2014-07-10 13:51:39 -07:00
|
|
|
RemoveFromTile(PosToLosTilesHelper(it->second.x, it->second.z), ent);
|
|
|
|
|
}
|
2010-09-19 11:08:56 -07:00
|
|
|
|
2011-10-28 06:15:33 -07:00
|
|
|
// This will be called after Ownership's OnDestroy, so ownership will be set
|
|
|
|
|
// to -1 already and we don't have to do a LosRemove here
|
|
|
|
|
ENSURE(it->second.owner == -1);
|
|
|
|
|
|
2010-09-19 11:08:56 -07:00
|
|
|
m_EntityData.erase(it);
|
2010-07-29 13:39:23 -07:00
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
2012-09-24 15:27:32 -07:00
|
|
|
case MT_VisionRangeChanged:
|
|
|
|
|
{
|
|
|
|
|
const CMessageVisionRangeChanged& msgData = static_cast<const CMessageVisionRangeChanged&> (msg);
|
|
|
|
|
entity_id_t ent = msgData.entity;
|
|
|
|
|
|
2013-09-15 07:03:53 -07:00
|
|
|
EntityMap<EntityData>::iterator it = m_EntityData.find(ent);
|
2012-09-24 15:27:32 -07:00
|
|
|
|
|
|
|
|
// Ignore if we're not already tracking this entity
|
|
|
|
|
if (it == m_EntityData.end())
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
CmpPtr<ICmpVision> cmpVision(GetSimContext(), ent);
|
|
|
|
|
if (!cmpVision)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
entity_pos_t oldRange = it->second.visionRange;
|
|
|
|
|
entity_pos_t newRange = msgData.newRange;
|
|
|
|
|
|
|
|
|
|
// If the range changed and the entity's in-world, we need to manually adjust it
|
|
|
|
|
// but if it's not in-world, we only need to set the new vision range
|
|
|
|
|
|
|
|
|
|
it->second.visionRange = newRange;
|
|
|
|
|
|
2017-02-24 11:15:54 -08:00
|
|
|
if (it->second.HasFlag<FlagMasks::InWorld>())
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
{
|
|
|
|
|
CFixedVector2D pos(it->second.x, it->second.z);
|
2017-02-24 11:15:54 -08:00
|
|
|
if (it->second.HasFlag<FlagMasks::SharedVision>())
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
{
|
|
|
|
|
SharingLosRemove(it->second.visionSharing, oldRange, pos);
|
|
|
|
|
SharingLosAdd(it->second.visionSharing, newRange, pos);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
LosRemove(it->second.owner, oldRange, pos);
|
|
|
|
|
LosAdd(it->second.owner, newRange, pos);
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-09-24 15:27:32 -07:00
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
case MT_VisionSharingChanged:
|
|
|
|
|
{
|
|
|
|
|
const CMessageVisionSharingChanged& msgData = static_cast<const CMessageVisionSharingChanged&> (msg);
|
|
|
|
|
entity_id_t ent = msgData.entity;
|
|
|
|
|
|
|
|
|
|
EntityMap<EntityData>::iterator it = m_EntityData.find(ent);
|
|
|
|
|
|
|
|
|
|
// Ignore if we're not already tracking this entity
|
|
|
|
|
if (it == m_EntityData.end())
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
ENSURE(msgData.player > 0 && msgData.player < MAX_LOS_PLAYER_ID+1);
|
|
|
|
|
u16 visionChanged = CalcVisionSharingMask(msgData.player);
|
|
|
|
|
|
2017-02-24 11:15:54 -08:00
|
|
|
if (!it->second.HasFlag<FlagMasks::SharedVision>())
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
{
|
|
|
|
|
// Activation of the Vision Sharing
|
|
|
|
|
ENSURE(it->second.owner == (i8)msgData.player);
|
|
|
|
|
it->second.visionSharing = visionChanged;
|
2017-02-24 11:15:54 -08:00
|
|
|
it->second.SetFlag<FlagMasks::SharedVision>(true);
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-24 11:15:54 -08:00
|
|
|
if (it->second.HasFlag<FlagMasks::InWorld>())
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
{
|
|
|
|
|
entity_pos_t range = it->second.visionRange;
|
|
|
|
|
CFixedVector2D pos(it->second.x, it->second.z);
|
|
|
|
|
if (msgData.add)
|
|
|
|
|
LosAdd(msgData.player, range, pos);
|
|
|
|
|
else
|
|
|
|
|
LosRemove(msgData.player, range, pos);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (msgData.add)
|
|
|
|
|
it->second.visionSharing |= visionChanged;
|
|
|
|
|
else
|
|
|
|
|
it->second.visionSharing &= ~visionChanged;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2010-07-29 13:39:23 -07:00
|
|
|
case MT_Update:
|
|
|
|
|
{
|
|
|
|
|
m_DebugOverlayDirty = true;
|
|
|
|
|
ExecuteActiveQueries();
|
2014-07-26 09:02:18 -07:00
|
|
|
UpdateVisibilityData();
|
2010-07-29 13:39:23 -07:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case MT_RenderSubmit:
|
|
|
|
|
{
|
|
|
|
|
const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
|
|
|
|
|
RenderSubmit(msgData.collector);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-23 05:13:13 -07:00
|
|
|
virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, ssize_t vertices)
|
2010-09-19 11:08:56 -07:00
|
|
|
{
|
2010-10-23 12:59:40 -07:00
|
|
|
m_WorldX0 = x0;
|
|
|
|
|
m_WorldZ0 = z0;
|
|
|
|
|
m_WorldX1 = x1;
|
|
|
|
|
m_WorldZ1 = z1;
|
2011-08-16 04:18:32 -07:00
|
|
|
m_TerrainVerticesPerSide = (i32)vertices;
|
2010-10-23 12:59:40 -07:00
|
|
|
|
2015-11-11 04:15:57 -08:00
|
|
|
ResetDerivedData();
|
2010-10-25 14:59:52 -07:00
|
|
|
}
|
|
|
|
|
|
2011-10-28 06:15:33 -07:00
|
|
|
virtual void Verify()
|
|
|
|
|
{
|
|
|
|
|
// Ignore if map not initialised yet
|
|
|
|
|
if (m_WorldX1.IsZero())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Check that calling ResetDerivedData (i.e. recomputing all the state from scratch)
|
|
|
|
|
// does not affect the incrementally-computed state
|
|
|
|
|
|
|
|
|
|
std::vector<std::vector<u16> > oldPlayerCounts = m_LosPlayerCounts;
|
|
|
|
|
std::vector<u32> oldStateRevealed = m_LosStateRevealed;
|
2015-04-14 14:33:43 -07:00
|
|
|
FastSpatialSubdivision oldSubdivision = m_Subdivision;
|
2014-07-17 12:08:06 -07:00
|
|
|
std::vector<std::set<entity_id_t> > oldLosTiles = m_LosTiles;
|
2011-10-28 06:15:33 -07:00
|
|
|
|
2015-11-11 04:15:57 -08:00
|
|
|
m_Deserializing = true;
|
|
|
|
|
ResetDerivedData();
|
|
|
|
|
m_Deserializing = false;
|
2013-07-19 17:50:40 -07:00
|
|
|
|
2011-10-28 06:15:33 -07:00
|
|
|
if (oldPlayerCounts != m_LosPlayerCounts)
|
|
|
|
|
{
|
|
|
|
|
for (size_t i = 0; i < oldPlayerCounts.size(); ++i)
|
|
|
|
|
{
|
2015-02-13 17:45:13 -08:00
|
|
|
debug_printf("%d: ", (int)i);
|
2011-10-28 06:15:33 -07:00
|
|
|
for (size_t j = 0; j < oldPlayerCounts[i].size(); ++j)
|
2015-02-13 17:45:13 -08:00
|
|
|
debug_printf("%d ", oldPlayerCounts[i][j]);
|
|
|
|
|
debug_printf("\n");
|
2011-10-28 06:15:33 -07:00
|
|
|
}
|
|
|
|
|
for (size_t i = 0; i < m_LosPlayerCounts.size(); ++i)
|
|
|
|
|
{
|
2015-02-13 17:45:13 -08:00
|
|
|
debug_printf("%d: ", (int)i);
|
2011-10-28 06:15:33 -07:00
|
|
|
for (size_t j = 0; j < m_LosPlayerCounts[i].size(); ++j)
|
2015-02-13 17:45:13 -08:00
|
|
|
debug_printf("%d ", m_LosPlayerCounts[i][j]);
|
|
|
|
|
debug_printf("\n");
|
2011-10-28 06:15:33 -07:00
|
|
|
}
|
|
|
|
|
debug_warn(L"inconsistent player counts");
|
|
|
|
|
}
|
|
|
|
|
if (oldStateRevealed != m_LosStateRevealed)
|
|
|
|
|
debug_warn(L"inconsistent revealed");
|
|
|
|
|
if (oldSubdivision != m_Subdivision)
|
|
|
|
|
debug_warn(L"inconsistent subdivs");
|
2014-07-17 12:08:06 -07:00
|
|
|
if (oldLosTiles != m_LosTiles)
|
|
|
|
|
debug_warn(L"inconsistent los tiles");
|
2011-10-28 06:15:33 -07:00
|
|
|
}
|
|
|
|
|
|
2015-04-14 14:33:43 -07:00
|
|
|
FastSpatialSubdivision* GetSubdivision()
|
2014-01-06 20:05:10 -08:00
|
|
|
{
|
2015-04-14 14:33:43 -07:00
|
|
|
return &m_Subdivision;
|
2014-01-06 20:05:10 -08:00
|
|
|
}
|
|
|
|
|
|
2010-10-25 14:59:52 -07:00
|
|
|
// Reinitialise subdivisions and LOS data, based on entity data
|
2015-11-11 04:15:57 -08:00
|
|
|
void ResetDerivedData()
|
2010-10-25 14:59:52 -07:00
|
|
|
{
|
2011-04-30 06:01:45 -07:00
|
|
|
ENSURE(m_WorldX0.IsZero() && m_WorldZ0.IsZero()); // don't bother implementing non-zero offsets yet
|
2010-10-25 14:59:52 -07:00
|
|
|
ResetSubdivisions(m_WorldX1, m_WorldZ1);
|
2010-09-23 05:13:13 -07:00
|
|
|
|
2014-07-17 12:08:06 -07:00
|
|
|
m_LosTilesPerSide = (m_TerrainVerticesPerSide - 1)/LOS_TILES_RATIO;
|
|
|
|
|
|
2010-09-23 05:13:13 -07:00
|
|
|
m_LosPlayerCounts.clear();
|
|
|
|
|
m_LosPlayerCounts.resize(MAX_LOS_PLAYER_ID+1);
|
2013-07-19 17:50:40 -07:00
|
|
|
m_ExploredVertices.clear();
|
|
|
|
|
m_ExploredVertices.resize(MAX_LOS_PLAYER_ID+1, 0);
|
2015-11-11 04:15:57 -08:00
|
|
|
if (m_Deserializing)
|
2013-07-19 17:50:40 -07:00
|
|
|
{
|
|
|
|
|
// recalc current exploration stats.
|
|
|
|
|
for (i32 j = 0; j < m_TerrainVerticesPerSide; j++)
|
|
|
|
|
for (i32 i = 0; i < m_TerrainVerticesPerSide; i++)
|
|
|
|
|
if (!LosIsOffWorld(i, j))
|
|
|
|
|
for (u8 k = 1; k < MAX_LOS_PLAYER_ID+1; ++k)
|
|
|
|
|
m_ExploredVertices.at(k) += ((m_LosState[j*m_TerrainVerticesPerSide + i] & (LOS_EXPLORED << (2*(k-1)))) > 0);
|
|
|
|
|
}
|
|
|
|
|
else
|
2011-08-01 14:25:12 -07:00
|
|
|
{
|
|
|
|
|
m_LosState.clear();
|
|
|
|
|
m_LosState.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
|
|
|
|
|
}
|
2010-10-25 14:59:52 -07:00
|
|
|
m_LosStateRevealed.clear();
|
|
|
|
|
m_LosStateRevealed.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
|
2015-11-11 04:15:57 -08:00
|
|
|
|
|
|
|
|
if (!m_Deserializing)
|
|
|
|
|
{
|
|
|
|
|
m_DirtyVisibility.clear();
|
|
|
|
|
m_DirtyVisibility.resize(m_LosTilesPerSide*m_LosTilesPerSide);
|
|
|
|
|
}
|
2015-11-20 12:42:45 -08:00
|
|
|
ENSURE(m_DirtyVisibility.size() == (size_t)(m_LosTilesPerSide*m_LosTilesPerSide));
|
2015-11-11 04:15:57 -08:00
|
|
|
|
2014-07-10 13:51:39 -07:00
|
|
|
m_LosTiles.clear();
|
|
|
|
|
m_LosTiles.resize(m_LosTilesPerSide*m_LosTilesPerSide);
|
2010-09-23 05:13:13 -07:00
|
|
|
|
2013-09-15 07:03:53 -07:00
|
|
|
for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
|
2017-02-24 11:15:54 -08:00
|
|
|
if (it->second.HasFlag<FlagMasks::InWorld>())
|
2014-07-10 13:51:39 -07:00
|
|
|
{
|
2017-02-24 11:15:54 -08:00
|
|
|
if (it->second.HasFlag<FlagMasks::SharedVision>())
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
SharingLosAdd(it->second.visionSharing, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z));
|
|
|
|
|
else
|
|
|
|
|
LosAdd(it->second.owner, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z));
|
2014-07-10 13:51:39 -07:00
|
|
|
AddToTile(PosToLosTilesHelper(it->second.x, it->second.z), it->first);
|
2016-10-28 08:34:24 -07:00
|
|
|
|
2017-02-24 11:15:54 -08:00
|
|
|
if (it->second.HasFlag<FlagMasks::RevealShore>())
|
2016-10-28 08:34:24 -07:00
|
|
|
RevealShore(it->second.owner, true);
|
2014-07-10 13:51:39 -07:00
|
|
|
}
|
2010-10-25 14:59:52 -07:00
|
|
|
|
2013-07-19 17:50:40 -07:00
|
|
|
m_TotalInworldVertices = 0;
|
2010-10-25 14:59:52 -07:00
|
|
|
for (ssize_t j = 0; j < m_TerrainVerticesPerSide; ++j)
|
|
|
|
|
for (ssize_t i = 0; i < m_TerrainVerticesPerSide; ++i)
|
2013-07-19 17:50:40 -07:00
|
|
|
{
|
|
|
|
|
if (LosIsOffWorld(i,j))
|
|
|
|
|
m_LosStateRevealed[i + j*m_TerrainVerticesPerSide] = 0;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
m_LosStateRevealed[i + j*m_TerrainVerticesPerSide] = 0xFFFFFFFFu;
|
|
|
|
|
m_TotalInworldVertices++;
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-09-19 11:08:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ResetSubdivisions(entity_pos_t x1, entity_pos_t z1)
|
|
|
|
|
{
|
2015-04-14 14:33:43 -07:00
|
|
|
m_Subdivision.Reset(x1, z1);
|
2010-09-19 11:08:56 -07:00
|
|
|
|
2013-09-15 07:03:53 -07:00
|
|
|
for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
|
2017-02-24 11:15:54 -08:00
|
|
|
if (it->second.HasFlag<FlagMasks::InWorld>())
|
2015-04-14 14:33:43 -07:00
|
|
|
m_Subdivision.Add(it->first, CFixedVector2D(it->second.x, it->second.z), it->second.size);
|
2010-09-19 11:08:56 -07:00
|
|
|
}
|
2010-07-29 13:39:23 -07:00
|
|
|
|
2010-11-16 12:43:15 -08:00
|
|
|
virtual tag_t CreateActiveQuery(entity_id_t source,
|
|
|
|
|
entity_pos_t minRange, entity_pos_t maxRange,
|
2016-01-23 07:17:56 -08:00
|
|
|
const std::vector<int>& owners, int requiredInterface, u8 flags)
|
2010-07-29 13:39:23 -07:00
|
|
|
{
|
2011-08-16 04:18:32 -07:00
|
|
|
tag_t id = m_QueryNext++;
|
2012-04-17 13:22:13 -07:00
|
|
|
m_Queries[id] = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flags);
|
2010-07-29 13:39:23 -07:00
|
|
|
|
2011-08-16 04:18:32 -07:00
|
|
|
return id;
|
2010-07-29 13:39:23 -07:00
|
|
|
}
|
|
|
|
|
|
2013-08-03 12:20:20 -07:00
|
|
|
virtual tag_t CreateActiveParabolicQuery(entity_id_t source,
|
|
|
|
|
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus,
|
2016-01-23 07:17:56 -08:00
|
|
|
const std::vector<int>& owners, int requiredInterface, u8 flags)
|
2013-08-03 12:20:20 -07:00
|
|
|
{
|
|
|
|
|
tag_t id = m_QueryNext++;
|
|
|
|
|
m_Queries[id] = ConstructParabolicQuery(source, minRange, maxRange, elevationBonus, owners, requiredInterface, flags);
|
|
|
|
|
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
|
2010-07-29 13:39:23 -07:00
|
|
|
virtual void DestroyActiveQuery(tag_t tag)
|
|
|
|
|
{
|
|
|
|
|
if (m_Queries.find(tag) == m_Queries.end())
|
|
|
|
|
{
|
2015-01-22 12:31:30 -08:00
|
|
|
LOGERROR("CCmpRangeManager: DestroyActiveQuery called with invalid tag %u", tag);
|
2010-07-29 13:39:23 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_Queries.erase(tag);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual void EnableActiveQuery(tag_t tag)
|
|
|
|
|
{
|
|
|
|
|
std::map<tag_t, Query>::iterator it = m_Queries.find(tag);
|
|
|
|
|
if (it == m_Queries.end())
|
|
|
|
|
{
|
2015-01-22 12:31:30 -08:00
|
|
|
LOGERROR("CCmpRangeManager: EnableActiveQuery called with invalid tag %u", tag);
|
2010-07-29 13:39:23 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Query& q = it->second;
|
|
|
|
|
q.enabled = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual void DisableActiveQuery(tag_t tag)
|
|
|
|
|
{
|
|
|
|
|
std::map<tag_t, Query>::iterator it = m_Queries.find(tag);
|
|
|
|
|
if (it == m_Queries.end())
|
|
|
|
|
{
|
2015-01-22 12:31:30 -08:00
|
|
|
LOGERROR("CCmpRangeManager: DisableActiveQuery called with invalid tag %u", tag);
|
2010-07-29 13:39:23 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Query& q = it->second;
|
|
|
|
|
q.enabled = false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
virtual bool IsActiveQueryEnabled(tag_t tag) const
|
2015-09-27 05:23:40 -07:00
|
|
|
{
|
|
|
|
|
std::map<tag_t, Query>::const_iterator it = m_Queries.find(tag);
|
|
|
|
|
if (it == m_Queries.end())
|
|
|
|
|
{
|
|
|
|
|
LOGERROR("CCmpRangeManager: IsActiveQueryEnabled called with invalid tag %u", tag);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Query& q = it->second;
|
|
|
|
|
return q.enabled;
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-23 07:17:56 -08:00
|
|
|
virtual std::vector<entity_id_t> ExecuteQueryAroundPos(const CFixedVector2D& pos,
|
2013-12-04 07:30:01 -08:00
|
|
|
entity_pos_t minRange, entity_pos_t maxRange,
|
2016-01-23 07:17:56 -08:00
|
|
|
const std::vector<int>& owners, int requiredInterface)
|
2013-12-04 07:30:01 -08:00
|
|
|
{
|
|
|
|
|
Query q = ConstructQuery(INVALID_ENTITY, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"));
|
|
|
|
|
std::vector<entity_id_t> r;
|
|
|
|
|
PerformQuery(q, r, pos);
|
|
|
|
|
|
|
|
|
|
// Return the list sorted by distance from the entity
|
|
|
|
|
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
|
|
|
|
|
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
2010-11-16 12:43:15 -08:00
|
|
|
virtual std::vector<entity_id_t> ExecuteQuery(entity_id_t source,
|
|
|
|
|
entity_pos_t minRange, entity_pos_t maxRange,
|
2016-01-23 07:17:56 -08:00
|
|
|
const std::vector<int>& owners, int requiredInterface)
|
2010-07-29 13:39:23 -07:00
|
|
|
{
|
|
|
|
|
PROFILE("ExecuteQuery");
|
|
|
|
|
|
2012-04-17 13:22:13 -07:00
|
|
|
Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"));
|
2010-07-29 13:39:23 -07:00
|
|
|
|
|
|
|
|
std::vector<entity_id_t> r;
|
|
|
|
|
|
2013-09-11 13:41:53 -07:00
|
|
|
CmpPtr<ICmpPosition> cmpSourcePosition(q.source);
|
2012-02-07 18:46:15 -08:00
|
|
|
if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
|
2010-07-29 13:39:23 -07:00
|
|
|
{
|
|
|
|
|
// If the source doesn't have a position, then the result is just the empty list
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-04 07:30:01 -08:00
|
|
|
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
|
|
|
|
|
PerformQuery(q, r, pos);
|
2010-07-29 13:39:23 -07:00
|
|
|
|
|
|
|
|
// Return the list sorted by distance from the entity
|
2010-07-31 14:22:39 -07:00
|
|
|
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
|
2010-07-29 13:39:23 -07:00
|
|
|
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual std::vector<entity_id_t> ResetActiveQuery(tag_t tag)
|
|
|
|
|
{
|
|
|
|
|
PROFILE("ResetActiveQuery");
|
|
|
|
|
|
|
|
|
|
std::vector<entity_id_t> r;
|
|
|
|
|
|
|
|
|
|
std::map<tag_t, Query>::iterator it = m_Queries.find(tag);
|
|
|
|
|
if (it == m_Queries.end())
|
|
|
|
|
{
|
2015-01-22 12:31:30 -08:00
|
|
|
LOGERROR("CCmpRangeManager: ResetActiveQuery called with invalid tag %u", tag);
|
2010-07-29 13:39:23 -07:00
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Query& q = it->second;
|
|
|
|
|
q.enabled = true;
|
|
|
|
|
|
2013-09-11 13:41:53 -07:00
|
|
|
CmpPtr<ICmpPosition> cmpSourcePosition(q.source);
|
2012-02-07 18:46:15 -08:00
|
|
|
if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
|
2010-07-29 13:39:23 -07:00
|
|
|
{
|
|
|
|
|
// If the source doesn't have a position, then the result is just the empty list
|
|
|
|
|
q.lastMatch = r;
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-04 07:30:01 -08:00
|
|
|
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
|
|
|
|
|
PerformQuery(q, r, pos);
|
2010-07-29 13:39:23 -07:00
|
|
|
|
|
|
|
|
q.lastMatch = r;
|
|
|
|
|
|
|
|
|
|
// Return the list sorted by distance from the entity
|
2010-07-31 14:22:39 -07:00
|
|
|
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
|
2010-07-29 13:39:23 -07:00
|
|
|
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
virtual std::vector<entity_id_t> GetEntitiesByPlayer(player_id_t player) const
|
2010-10-01 13:51:21 -07:00
|
|
|
{
|
2016-01-10 08:47:57 -08:00
|
|
|
return GetEntitiesByMask(CalcOwnerMask(player));
|
|
|
|
|
}
|
2010-10-01 13:51:21 -07:00
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
virtual std::vector<entity_id_t> GetNonGaiaEntities() const
|
2016-01-10 08:47:57 -08:00
|
|
|
{
|
2017-02-26 02:26:35 -08:00
|
|
|
return GetEntitiesByMask(~3); // bit 0 for owner=-1 and bit 1 for gaia
|
2016-01-10 08:47:57 -08:00
|
|
|
}
|
|
|
|
|
|
2017-12-26 14:03:25 -08:00
|
|
|
virtual std::vector<entity_id_t> GetGaiaAndNonGaiaEntities() const
|
|
|
|
|
{
|
|
|
|
|
return GetEntitiesByMask(~1); // bit 0 for owner=-1
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
std::vector<entity_id_t> GetEntitiesByMask(u32 ownerMask) const
|
2016-01-10 08:47:57 -08:00
|
|
|
{
|
|
|
|
|
std::vector<entity_id_t> entities;
|
2010-10-01 13:51:21 -07:00
|
|
|
|
2013-09-15 07:03:53 -07:00
|
|
|
for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
|
2010-10-01 13:51:21 -07:00
|
|
|
{
|
|
|
|
|
// Check owner and add to list if it matches
|
|
|
|
|
if (CalcOwnerMask(it->second.owner) & ownerMask)
|
|
|
|
|
entities.push_back(it->first);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return entities;
|
|
|
|
|
}
|
|
|
|
|
|
2010-07-29 13:39:23 -07:00
|
|
|
virtual void SetDebugOverlay(bool enabled)
|
|
|
|
|
{
|
|
|
|
|
m_DebugOverlayEnabled = enabled;
|
|
|
|
|
m_DebugOverlayDirty = true;
|
|
|
|
|
if (!enabled)
|
|
|
|
|
m_DebugOverlayLines.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update all currently-enabled active queries.
|
|
|
|
|
*/
|
|
|
|
|
void ExecuteActiveQueries()
|
|
|
|
|
{
|
2011-11-03 18:35:50 -07:00
|
|
|
PROFILE3("ExecuteActiveQueries");
|
2010-07-29 13:39:23 -07:00
|
|
|
|
|
|
|
|
// Store a queue of all messages before sending any, so we can assume
|
|
|
|
|
// no entities will move until we've finished checking all the ranges
|
|
|
|
|
std::vector<std::pair<entity_id_t, CMessageRangeUpdate> > messages;
|
2013-09-15 07:03:53 -07:00
|
|
|
std::vector<entity_id_t> results;
|
|
|
|
|
std::vector<entity_id_t> added;
|
|
|
|
|
std::vector<entity_id_t> removed;
|
2010-07-29 13:39:23 -07:00
|
|
|
|
|
|
|
|
for (std::map<tag_t, Query>::iterator it = m_Queries.begin(); it != m_Queries.end(); ++it)
|
|
|
|
|
{
|
2013-09-15 07:03:53 -07:00
|
|
|
Query& query = it->second;
|
2010-07-29 13:39:23 -07:00
|
|
|
|
2013-09-15 07:03:53 -07:00
|
|
|
if (!query.enabled)
|
2010-07-29 13:39:23 -07:00
|
|
|
continue;
|
|
|
|
|
|
2013-09-15 07:03:53 -07:00
|
|
|
results.clear();
|
2018-01-12 13:01:17 -08:00
|
|
|
CmpPtr<ICmpPosition> cmpSourcePosition(query.source);
|
|
|
|
|
if (cmpSourcePosition && cmpSourcePosition->IsInWorld())
|
|
|
|
|
{
|
|
|
|
|
results.reserve(query.lastMatch.size());
|
|
|
|
|
PerformQuery(query, results, cmpSourcePosition->GetPosition2D());
|
|
|
|
|
}
|
2010-07-29 13:39:23 -07:00
|
|
|
|
|
|
|
|
// Compute the changes vs the last match
|
2013-09-15 07:03:53 -07:00
|
|
|
added.clear();
|
|
|
|
|
removed.clear();
|
2010-07-29 13:39:23 -07:00
|
|
|
// Return the 'added' list sorted by distance from the entity
|
|
|
|
|
// (Don't bother sorting 'removed' because they might not even have positions or exist any more)
|
2014-01-06 20:05:10 -08:00
|
|
|
std::set_difference(results.begin(), results.end(), query.lastMatch.begin(), query.lastMatch.end(),
|
2013-09-15 07:03:53 -07:00
|
|
|
std::back_inserter(added));
|
2014-01-06 20:05:10 -08:00
|
|
|
std::set_difference(query.lastMatch.begin(), query.lastMatch.end(), results.begin(), results.end(),
|
2013-09-15 07:03:53 -07:00
|
|
|
std::back_inserter(removed));
|
|
|
|
|
if (added.empty() && removed.empty())
|
|
|
|
|
continue;
|
2010-07-29 13:39:23 -07:00
|
|
|
|
2018-01-12 13:01:17 -08:00
|
|
|
if (cmpSourcePosition && cmpSourcePosition->IsInWorld())
|
|
|
|
|
std::stable_sort(added.begin(), added.end(), EntityDistanceOrdering(m_EntityData, cmpSourcePosition->GetPosition2D()));
|
2010-07-29 13:39:23 -07:00
|
|
|
|
2013-09-15 07:03:53 -07:00
|
|
|
messages.resize(messages.size() + 1);
|
|
|
|
|
std::pair<entity_id_t, CMessageRangeUpdate>& back = messages.back();
|
|
|
|
|
back.first = query.source.GetId();
|
|
|
|
|
back.second.tag = it->first;
|
|
|
|
|
back.second.added.swap(added);
|
|
|
|
|
back.second.removed.swap(removed);
|
2018-01-12 13:01:17 -08:00
|
|
|
query.lastMatch.swap(results);
|
2010-07-29 13:39:23 -07:00
|
|
|
}
|
|
|
|
|
|
2013-09-15 07:03:53 -07:00
|
|
|
CComponentManager& cmpMgr = GetSimContext().GetComponentManager();
|
2010-07-29 13:39:23 -07:00
|
|
|
for (size_t i = 0; i < messages.size(); ++i)
|
2013-09-15 07:03:53 -07:00
|
|
|
cmpMgr.PostMessage(messages[i].first, messages[i].second);
|
2010-07-29 13:39:23 -07:00
|
|
|
}
|
|
|
|
|
|
2010-11-13 11:15:29 -08:00
|
|
|
/**
|
|
|
|
|
* Returns whether the given entity matches the given query (ignoring maxRange)
|
|
|
|
|
*/
|
2017-01-19 18:25:19 -08:00
|
|
|
bool TestEntityQuery(const Query& q, entity_id_t id, const EntityData& entity) const
|
2010-11-13 11:15:29 -08:00
|
|
|
{
|
|
|
|
|
// Quick filter to ignore entities with the wrong owner
|
|
|
|
|
if (!(CalcOwnerMask(entity.owner) & q.ownersMask))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// Ignore entities not present in the world
|
2017-02-24 11:15:54 -08:00
|
|
|
if (!entity.HasFlag<FlagMasks::InWorld>())
|
2010-11-13 11:15:29 -08:00
|
|
|
return false;
|
|
|
|
|
|
2012-04-17 13:22:13 -07:00
|
|
|
// Ignore entities that don't match the current flags
|
2017-02-24 11:15:54 -08:00
|
|
|
if (!((entity.flags & FlagMasks::AllQuery) & q.flagsMask))
|
2012-04-17 13:22:13 -07:00
|
|
|
return false;
|
|
|
|
|
|
2010-11-13 11:15:29 -08:00
|
|
|
// Ignore self
|
2013-09-11 13:41:53 -07:00
|
|
|
if (id == q.source.GetId())
|
2010-11-13 11:15:29 -08:00
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// Ignore if it's missing the required interface
|
|
|
|
|
if (q.interface && !GetSimContext().GetComponentManager().QueryInterface(id, q.interface))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2010-07-29 13:39:23 -07:00
|
|
|
/**
|
|
|
|
|
* Returns a list of distinct entity IDs that match the given query, sorted by ID.
|
|
|
|
|
*/
|
2013-12-04 07:30:01 -08:00
|
|
|
void PerformQuery(const Query& q, std::vector<entity_id_t>& r, CFixedVector2D pos)
|
2010-07-29 13:39:23 -07:00
|
|
|
{
|
|
|
|
|
|
2010-11-13 11:15:29 -08:00
|
|
|
// Special case: range -1.0 means check all entities ignoring distance
|
|
|
|
|
if (q.maxRange == entity_pos_t::FromInt(-1))
|
2010-07-29 13:39:23 -07:00
|
|
|
{
|
2013-09-15 07:03:53 -07:00
|
|
|
for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
|
2010-11-13 11:15:29 -08:00
|
|
|
{
|
|
|
|
|
if (!TestEntityQuery(q, it->first, it->second))
|
|
|
|
|
continue;
|
2010-09-19 11:08:56 -07:00
|
|
|
|
2010-11-13 11:15:29 -08:00
|
|
|
r.push_back(it->first);
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-08-03 12:20:20 -07:00
|
|
|
// Not the entire world, so check a parabolic range, or a regular range
|
2014-01-06 20:05:10 -08:00
|
|
|
else if (q.parabolic)
|
2013-08-03 12:20:20 -07:00
|
|
|
{
|
|
|
|
|
// elevationBonus is part of the 3D position, as the source is really that much heigher
|
2013-12-04 07:30:01 -08:00
|
|
|
CmpPtr<ICmpPosition> cmpSourcePosition(q.source);
|
2013-08-03 12:20:20 -07:00
|
|
|
CFixedVector3D pos3d = cmpSourcePosition->GetPosition()+
|
|
|
|
|
CFixedVector3D(entity_pos_t::Zero(), q.elevationBonus, entity_pos_t::Zero()) ;
|
|
|
|
|
// Get a quick list of entities that are potentially in range, with a cutoff of 2*maxRange
|
2016-06-08 10:06:58 -07:00
|
|
|
m_SubdivisionResults.clear();
|
|
|
|
|
m_Subdivision.GetNear(m_SubdivisionResults, pos, q.maxRange * 2);
|
2013-08-03 12:20:20 -07:00
|
|
|
|
2016-06-08 10:06:58 -07:00
|
|
|
for (size_t i = 0; i < m_SubdivisionResults.size(); ++i)
|
2013-08-03 12:20:20 -07:00
|
|
|
{
|
2016-06-08 10:06:58 -07:00
|
|
|
EntityMap<EntityData>::const_iterator it = m_EntityData.find(m_SubdivisionResults[i]);
|
2013-08-03 12:20:20 -07:00
|
|
|
ENSURE(it != m_EntityData.end());
|
|
|
|
|
|
|
|
|
|
if (!TestEntityQuery(q, it->first, it->second))
|
|
|
|
|
continue;
|
2014-01-06 20:05:10 -08:00
|
|
|
|
2016-06-08 10:06:58 -07:00
|
|
|
CmpPtr<ICmpPosition> cmpSecondPosition(GetSimContext(), m_SubdivisionResults[i]);
|
2013-08-03 12:20:20 -07:00
|
|
|
if (!cmpSecondPosition || !cmpSecondPosition->IsInWorld())
|
|
|
|
|
continue;
|
|
|
|
|
CFixedVector3D secondPosition = cmpSecondPosition->GetPosition();
|
|
|
|
|
|
|
|
|
|
// Restrict based on precise distance
|
|
|
|
|
if (!InParabolicRange(
|
2014-01-06 20:05:10 -08:00
|
|
|
CFixedVector3D(it->second.x, secondPosition.Y, it->second.z)
|
|
|
|
|
- pos3d,
|
2013-08-03 12:20:20 -07:00
|
|
|
q.maxRange))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (!q.minRange.IsZero())
|
|
|
|
|
{
|
|
|
|
|
int distVsMin = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange);
|
|
|
|
|
if (distVsMin < 0)
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r.push_back(it->first);
|
|
|
|
|
}
|
2015-12-30 05:44:51 -08:00
|
|
|
std::sort(r.begin(), r.end());
|
2013-08-03 12:20:20 -07:00
|
|
|
}
|
|
|
|
|
// check a regular range (i.e. not the entire world, and not parabolic)
|
2014-01-06 20:05:10 -08:00
|
|
|
else
|
2010-11-13 11:15:29 -08:00
|
|
|
{
|
|
|
|
|
// Get a quick list of entities that are potentially in range
|
2016-06-08 10:06:58 -07:00
|
|
|
m_SubdivisionResults.clear();
|
|
|
|
|
m_Subdivision.GetNear(m_SubdivisionResults, pos, q.maxRange);
|
2014-01-06 20:05:10 -08:00
|
|
|
|
2016-06-08 10:06:58 -07:00
|
|
|
for (size_t i = 0; i < m_SubdivisionResults.size(); ++i)
|
2010-11-13 11:15:29 -08:00
|
|
|
{
|
2016-06-08 10:06:58 -07:00
|
|
|
EntityMap<EntityData>::const_iterator it = m_EntityData.find(m_SubdivisionResults[i]);
|
2011-04-30 06:01:45 -07:00
|
|
|
ENSURE(it != m_EntityData.end());
|
2010-07-29 13:39:23 -07:00
|
|
|
|
2010-11-13 11:15:29 -08:00
|
|
|
if (!TestEntityQuery(q, it->first, it->second))
|
|
|
|
|
continue;
|
2010-07-29 13:39:23 -07:00
|
|
|
|
2010-11-13 11:15:29 -08:00
|
|
|
// Restrict based on precise distance
|
|
|
|
|
int distVsMax = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.maxRange);
|
|
|
|
|
if (distVsMax > 0)
|
|
|
|
|
continue;
|
2010-07-29 13:39:23 -07:00
|
|
|
|
2010-11-16 12:43:15 -08:00
|
|
|
if (!q.minRange.IsZero())
|
|
|
|
|
{
|
|
|
|
|
int distVsMin = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange);
|
|
|
|
|
if (distVsMin < 0)
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2010-11-13 11:15:29 -08:00
|
|
|
r.push_back(it->first);
|
|
|
|
|
}
|
2015-12-30 05:44:51 -08:00
|
|
|
std::sort(r.begin(), r.end());
|
2010-07-29 13:39:23 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
virtual entity_pos_t GetElevationAdaptedRange(const CFixedVector3D& pos1, const CFixedVector3D& rot, entity_pos_t range, entity_pos_t elevationBonus, entity_pos_t angle) const
|
2013-08-03 12:20:20 -07:00
|
|
|
{
|
2017-03-24 17:11:08 -07:00
|
|
|
entity_pos_t r = entity_pos_t::Zero();
|
2016-01-23 07:17:56 -08:00
|
|
|
CFixedVector3D pos(pos1);
|
2014-01-06 20:05:10 -08:00
|
|
|
|
2013-08-03 12:20:20 -07:00
|
|
|
pos.Y += elevationBonus;
|
|
|
|
|
entity_pos_t orientation = rot.Y;
|
|
|
|
|
|
|
|
|
|
entity_pos_t maxAngle = orientation + angle/2;
|
|
|
|
|
entity_pos_t minAngle = orientation - angle/2;
|
|
|
|
|
|
|
|
|
|
int numberOfSteps = 16;
|
|
|
|
|
|
|
|
|
|
if (angle == entity_pos_t::Zero())
|
|
|
|
|
numberOfSteps = 1;
|
|
|
|
|
|
|
|
|
|
std::vector<entity_pos_t> coords = getParabolicRangeForm(pos, range, range*2, minAngle, maxAngle, numberOfSteps);
|
|
|
|
|
|
2017-03-24 17:11:08 -07:00
|
|
|
entity_pos_t part = entity_pos_t::FromInt(numberOfSteps);
|
2013-08-03 12:20:20 -07:00
|
|
|
|
2017-03-24 17:11:08 -07:00
|
|
|
for (int i = 0; i < numberOfSteps; ++i)
|
2013-08-03 12:20:20 -07:00
|
|
|
r = r + CFixedVector2D(coords[2*i],coords[2*i+1]).Length() / part;
|
2014-01-06 20:05:10 -08:00
|
|
|
|
2013-08-03 12:20:20 -07:00
|
|
|
return r;
|
2014-01-06 20:05:10 -08:00
|
|
|
|
2013-08-03 12:20:20 -07:00
|
|
|
}
|
|
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
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
|
2013-08-03 12:20:20 -07:00
|
|
|
{
|
2017-01-19 18:25:19 -08:00
|
|
|
std::vector<entity_pos_t> r;
|
2014-01-06 20:05:10 -08:00
|
|
|
|
2017-03-24 17:11:08 -07:00
|
|
|
CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
|
|
|
|
|
if (!cmpTerrain)
|
|
|
|
|
return r;
|
|
|
|
|
|
2013-08-03 12:20:20 -07:00
|
|
|
// angle = 0 goes in the positive Z direction
|
|
|
|
|
entity_pos_t precision = entity_pos_t::FromInt((int)TERRAIN_TILE_SIZE)/8;
|
|
|
|
|
|
2013-09-11 13:41:53 -07:00
|
|
|
CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity());
|
2017-03-24 17:11:08 -07:00
|
|
|
entity_pos_t waterLevel = cmpWaterManager ? cmpWaterManager->GetWaterLevel(pos.X, pos.Z) : entity_pos_t::Zero();
|
2013-08-03 12:20:20 -07:00
|
|
|
entity_pos_t thisHeight = pos.Y > waterLevel ? pos.Y : waterLevel;
|
|
|
|
|
|
2017-03-24 17:11:08 -07:00
|
|
|
for (int i = 0; i < numberOfSteps; ++i)
|
2013-08-03 12:20:20 -07:00
|
|
|
{
|
2017-03-24 17:11:08 -07:00
|
|
|
entity_pos_t angle = minAngle + (maxAngle - minAngle) / numberOfSteps * i;
|
|
|
|
|
entity_pos_t sin;
|
|
|
|
|
entity_pos_t cos;
|
|
|
|
|
entity_pos_t minDistance = entity_pos_t::Zero();
|
|
|
|
|
entity_pos_t maxDistance = cutoff;
|
|
|
|
|
sincos_approx(angle, sin, cos);
|
|
|
|
|
|
|
|
|
|
CFixedVector2D minVector = CFixedVector2D(entity_pos_t::Zero(), entity_pos_t::Zero());
|
|
|
|
|
CFixedVector2D maxVector = CFixedVector2D(sin, cos).Multiply(cutoff);
|
|
|
|
|
entity_pos_t targetHeight = cmpTerrain->GetGroundLevel(pos.X+maxVector.X, pos.Z+maxVector.Y);
|
|
|
|
|
// use water level to display range on water
|
|
|
|
|
targetHeight = targetHeight > waterLevel ? targetHeight : waterLevel;
|
|
|
|
|
|
|
|
|
|
if (InParabolicRange(CFixedVector3D(maxVector.X, targetHeight-thisHeight, maxVector.Y), maxRange))
|
|
|
|
|
{
|
|
|
|
|
r.push_back(maxVector.X);
|
|
|
|
|
r.push_back(maxVector.Y);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Loop until vectors come close enough
|
|
|
|
|
while ((maxVector - minVector).CompareLength(precision) > 0)
|
2013-08-03 12:20:20 -07:00
|
|
|
{
|
2017-03-24 17:11:08 -07:00
|
|
|
// difference still bigger than precision, bisect to get smaller difference
|
|
|
|
|
entity_pos_t newDistance = (minDistance+maxDistance)/entity_pos_t::FromInt(2);
|
|
|
|
|
|
|
|
|
|
CFixedVector2D newVector = CFixedVector2D(sin, cos).Multiply(newDistance);
|
|
|
|
|
|
|
|
|
|
// get the height of the ground
|
|
|
|
|
targetHeight = cmpTerrain->GetGroundLevel(pos.X+newVector.X, pos.Z+newVector.Y);
|
2013-08-03 12:20:20 -07:00
|
|
|
targetHeight = targetHeight > waterLevel ? targetHeight : waterLevel;
|
|
|
|
|
|
2017-03-24 17:11:08 -07:00
|
|
|
if (InParabolicRange(CFixedVector3D(newVector.X, targetHeight-thisHeight, newVector.Y), maxRange))
|
2013-08-03 12:20:20 -07:00
|
|
|
{
|
2017-03-24 17:11:08 -07:00
|
|
|
// new vector is in parabolic range, so this is a new minVector
|
|
|
|
|
minVector = newVector;
|
|
|
|
|
minDistance = newDistance;
|
2013-08-03 12:20:20 -07:00
|
|
|
}
|
2017-03-24 17:11:08 -07:00
|
|
|
else
|
2013-08-03 12:20:20 -07:00
|
|
|
{
|
2017-03-24 17:11:08 -07:00
|
|
|
// new vector is out parabolic range, so this is a new maxVector
|
|
|
|
|
maxVector = newVector;
|
|
|
|
|
maxDistance = newDistance;
|
2013-08-03 12:20:20 -07:00
|
|
|
}
|
2014-01-06 20:05:10 -08:00
|
|
|
|
2013-08-03 12:20:20 -07:00
|
|
|
}
|
2017-03-24 17:11:08 -07:00
|
|
|
r.push_back(maxVector.X);
|
|
|
|
|
r.push_back(maxVector.Y);
|
2013-08-03 12:20:20 -07:00
|
|
|
|
|
|
|
|
}
|
2017-03-24 17:11:08 -07:00
|
|
|
r.push_back(r[0]);
|
|
|
|
|
r.push_back(r[1]);
|
|
|
|
|
|
2013-08-03 12:20:20 -07:00
|
|
|
return r;
|
|
|
|
|
}
|
2014-01-06 20:05:10 -08:00
|
|
|
|
2010-11-16 12:43:15 -08:00
|
|
|
Query ConstructQuery(entity_id_t source,
|
|
|
|
|
entity_pos_t minRange, entity_pos_t maxRange,
|
2017-01-19 18:25:19 -08:00
|
|
|
const std::vector<int>& owners, int requiredInterface, u8 flagsMask) const
|
2010-07-29 13:39:23 -07:00
|
|
|
{
|
2010-11-16 12:43:15 -08:00
|
|
|
// Min range must be non-negative
|
|
|
|
|
if (minRange < entity_pos_t::Zero())
|
2015-01-22 12:31:30 -08:00
|
|
|
LOGWARNING("CCmpRangeManager: Invalid min range %f in query for entity %u", minRange.ToDouble(), source);
|
2010-11-16 12:43:15 -08:00
|
|
|
|
|
|
|
|
// Max range must be non-negative, or else -1
|
2010-11-13 11:15:29 -08:00
|
|
|
if (maxRange < entity_pos_t::Zero() && maxRange != entity_pos_t::FromInt(-1))
|
2015-01-22 12:31:30 -08:00
|
|
|
LOGWARNING("CCmpRangeManager: Invalid max range %f in query for entity %u", maxRange.ToDouble(), source);
|
2010-11-13 11:15:29 -08:00
|
|
|
|
2010-07-29 13:39:23 -07:00
|
|
|
Query q;
|
|
|
|
|
q.enabled = false;
|
2013-08-03 12:20:20 -07:00
|
|
|
q.parabolic = false;
|
2013-09-11 13:41:53 -07:00
|
|
|
q.source = GetSimContext().GetComponentManager().LookupEntityHandle(source);
|
2010-11-16 12:43:15 -08:00
|
|
|
q.minRange = minRange;
|
2010-07-29 13:39:23 -07:00
|
|
|
q.maxRange = maxRange;
|
2013-08-03 12:20:20 -07:00
|
|
|
q.elevationBonus = entity_pos_t::Zero();
|
2010-07-29 13:39:23 -07:00
|
|
|
|
|
|
|
|
q.ownersMask = 0;
|
|
|
|
|
for (size_t i = 0; i < owners.size(); ++i)
|
|
|
|
|
q.ownersMask |= CalcOwnerMask(owners[i]);
|
|
|
|
|
|
2015-08-29 15:49:49 -07:00
|
|
|
if (q.ownersMask == 0)
|
|
|
|
|
LOGWARNING("CCmpRangeManager: No owners in query for entity %u", source);
|
|
|
|
|
|
2010-07-29 13:39:23 -07:00
|
|
|
q.interface = requiredInterface;
|
2012-04-17 13:22:13 -07:00
|
|
|
q.flagsMask = flagsMask;
|
2010-07-29 13:39:23 -07:00
|
|
|
|
|
|
|
|
return q;
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-03 12:20:20 -07:00
|
|
|
Query ConstructParabolicQuery(entity_id_t source,
|
|
|
|
|
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus,
|
2017-01-19 18:25:19 -08:00
|
|
|
const std::vector<int>& owners, int requiredInterface, u8 flagsMask) const
|
2013-08-03 12:20:20 -07:00
|
|
|
{
|
|
|
|
|
Query q = ConstructQuery(source,minRange,maxRange,owners,requiredInterface,flagsMask);
|
|
|
|
|
q.parabolic = true;
|
|
|
|
|
q.elevationBonus = elevationBonus;
|
|
|
|
|
return q;
|
|
|
|
|
}
|
|
|
|
|
|
2010-07-29 13:39:23 -07:00
|
|
|
void RenderSubmit(SceneCollector& collector)
|
|
|
|
|
{
|
|
|
|
|
if (!m_DebugOverlayEnabled)
|
|
|
|
|
return;
|
2015-03-15 16:59:48 -07:00
|
|
|
static CColor disabledRingColor(1, 0, 0, 1); // red
|
|
|
|
|
static CColor enabledRingColor(0, 1, 0, 1); // green
|
|
|
|
|
static CColor subdivColor(0, 0, 1, 1); // blue
|
|
|
|
|
static CColor rayColor(1, 1, 0, 0.2f);
|
2010-07-29 13:39:23 -07:00
|
|
|
|
|
|
|
|
if (m_DebugOverlayDirty)
|
|
|
|
|
{
|
|
|
|
|
m_DebugOverlayLines.clear();
|
|
|
|
|
|
|
|
|
|
for (std::map<tag_t, Query>::iterator it = m_Queries.begin(); it != m_Queries.end(); ++it)
|
|
|
|
|
{
|
|
|
|
|
Query& q = it->second;
|
|
|
|
|
|
2013-09-11 13:41:53 -07:00
|
|
|
CmpPtr<ICmpPosition> cmpSourcePosition(q.source);
|
2012-02-07 18:46:15 -08:00
|
|
|
if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
|
2010-07-29 13:39:23 -07:00
|
|
|
continue;
|
|
|
|
|
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
|
|
|
|
|
|
2010-11-16 12:43:15 -08:00
|
|
|
// Draw the max range circle
|
2013-08-03 12:20:20 -07:00
|
|
|
if (!q.parabolic)
|
|
|
|
|
{
|
|
|
|
|
m_DebugOverlayLines.push_back(SOverlayLine());
|
2015-03-15 16:59:48 -07:00
|
|
|
m_DebugOverlayLines.back().m_Color = (q.enabled ? enabledRingColor : disabledRingColor);
|
2013-08-03 12:20:20 -07:00
|
|
|
SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToFloat(), q.maxRange.ToFloat(), m_DebugOverlayLines.back(), true);
|
|
|
|
|
}
|
2014-01-06 20:05:10 -08:00
|
|
|
else
|
|
|
|
|
{
|
2013-08-03 12:20:20 -07:00
|
|
|
// elevation bonus is part of the 3D position. As if the unit is really that much higher
|
2014-01-06 20:05:10 -08:00
|
|
|
CFixedVector3D pos = cmpSourcePosition->GetPosition();
|
2013-08-03 12:20:20 -07:00
|
|
|
pos.Y += q.elevationBonus;
|
|
|
|
|
|
|
|
|
|
std::vector<entity_pos_t> coords;
|
2014-01-06 20:05:10 -08:00
|
|
|
|
2013-08-03 12:20:20 -07:00
|
|
|
// Get the outline from cache if possible
|
2013-09-11 13:41:53 -07:00
|
|
|
if (ParabolicRangesOutlines.find(q.source.GetId()) != ParabolicRangesOutlines.end())
|
2013-08-03 12:20:20 -07:00
|
|
|
{
|
2013-09-11 13:41:53 -07:00
|
|
|
EntityParabolicRangeOutline e = ParabolicRangesOutlines[q.source.GetId()];
|
2014-01-06 20:05:10 -08:00
|
|
|
if (e.position == pos && e.range == q.maxRange)
|
2013-08-03 12:20:20 -07:00
|
|
|
{
|
|
|
|
|
// outline is cached correctly, use it
|
2014-01-06 20:05:10 -08:00
|
|
|
coords = e.outline;
|
2013-08-03 12:20:20 -07:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2014-01-06 20:05:10 -08:00
|
|
|
// outline was cached, but important parameters changed
|
2013-08-03 12:20:20 -07:00
|
|
|
// (position, elevation, range)
|
|
|
|
|
// update it
|
|
|
|
|
coords = getParabolicRangeForm(pos,q.maxRange,q.maxRange*2, entity_pos_t::Zero(), entity_pos_t::FromFloat(2.0f*3.14f),70);
|
|
|
|
|
e.outline = coords;
|
|
|
|
|
e.range = q.maxRange;
|
|
|
|
|
e.position = pos;
|
2013-09-11 13:41:53 -07:00
|
|
|
ParabolicRangesOutlines[q.source.GetId()] = e;
|
2013-08-03 12:20:20 -07:00
|
|
|
}
|
|
|
|
|
}
|
2014-01-06 20:05:10 -08:00
|
|
|
else
|
2013-08-03 12:20:20 -07:00
|
|
|
{
|
2014-01-06 20:05:10 -08:00
|
|
|
// outline wasn't cached (first time you enable the range overlay
|
2013-08-03 12:20:20 -07:00
|
|
|
// or you created a new entiy)
|
|
|
|
|
// cache a new outline
|
|
|
|
|
coords = getParabolicRangeForm(pos,q.maxRange,q.maxRange*2, entity_pos_t::Zero(), entity_pos_t::FromFloat(2.0f*3.14f),70);
|
|
|
|
|
EntityParabolicRangeOutline e;
|
2013-09-11 13:41:53 -07:00
|
|
|
e.source = q.source.GetId();
|
2013-08-03 12:20:20 -07:00
|
|
|
e.range = q.maxRange;
|
|
|
|
|
e.position = pos;
|
|
|
|
|
e.outline = coords;
|
2013-09-11 13:41:53 -07:00
|
|
|
ParabolicRangesOutlines[q.source.GetId()] = e;
|
2013-08-03 12:20:20 -07:00
|
|
|
}
|
2014-01-06 20:05:10 -08:00
|
|
|
|
2015-03-15 16:59:48 -07:00
|
|
|
CColor thiscolor = q.enabled ? enabledRingColor : disabledRingColor;
|
2014-01-06 20:05:10 -08:00
|
|
|
|
|
|
|
|
// draw the outline (piece by piece)
|
2013-08-03 12:20:20 -07:00
|
|
|
for (size_t i = 3; i < coords.size(); i += 2)
|
|
|
|
|
{
|
|
|
|
|
std::vector<float> c;
|
|
|
|
|
c.push_back((coords[i-3]+pos.X).ToFloat());
|
|
|
|
|
c.push_back((coords[i-2]+pos.Z).ToFloat());
|
|
|
|
|
c.push_back((coords[i-1]+pos.X).ToFloat());
|
|
|
|
|
c.push_back((coords[i]+pos.Z).ToFloat());
|
|
|
|
|
m_DebugOverlayLines.push_back(SOverlayLine());
|
|
|
|
|
m_DebugOverlayLines.back().m_Color = thiscolor;
|
|
|
|
|
SimRender::ConstructLineOnGround(GetSimContext(), c, m_DebugOverlayLines.back(), true);
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-07-29 13:39:23 -07:00
|
|
|
|
2010-11-16 12:43:15 -08:00
|
|
|
// Draw the min range circle
|
|
|
|
|
if (!q.minRange.IsZero())
|
2011-08-16 04:18:32 -07:00
|
|
|
SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToFloat(), q.minRange.ToFloat(), m_DebugOverlayLines.back(), true);
|
2010-11-16 12:43:15 -08:00
|
|
|
|
2010-07-29 13:39:23 -07:00
|
|
|
// Draw a ray from the source to each matched entity
|
|
|
|
|
for (size_t i = 0; i < q.lastMatch.size(); ++i)
|
|
|
|
|
{
|
|
|
|
|
CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), q.lastMatch[i]);
|
2012-02-07 18:46:15 -08:00
|
|
|
if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
|
2010-07-29 13:39:23 -07:00
|
|
|
continue;
|
|
|
|
|
CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
|
|
|
|
|
|
|
|
|
|
std::vector<float> coords;
|
|
|
|
|
coords.push_back(pos.X.ToFloat());
|
|
|
|
|
coords.push_back(pos.Y.ToFloat());
|
|
|
|
|
coords.push_back(targetPos.X.ToFloat());
|
|
|
|
|
coords.push_back(targetPos.Y.ToFloat());
|
|
|
|
|
|
|
|
|
|
m_DebugOverlayLines.push_back(SOverlayLine());
|
2015-03-15 16:59:48 -07:00
|
|
|
m_DebugOverlayLines.back().m_Color = rayColor;
|
2010-07-29 13:39:23 -07:00
|
|
|
SimRender::ConstructLineOnGround(GetSimContext(), coords, m_DebugOverlayLines.back(), true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-15 07:03:53 -07:00
|
|
|
// render subdivision grid
|
2015-04-14 14:33:43 -07:00
|
|
|
float divSize = m_Subdivision.GetDivisionSize();
|
|
|
|
|
int size = m_Subdivision.GetWidth();
|
|
|
|
|
for (int x = 0; x < size; ++x)
|
2013-09-15 07:03:53 -07:00
|
|
|
{
|
2015-04-14 14:33:43 -07:00
|
|
|
for (int y = 0; y < size; ++y)
|
2013-09-15 07:03:53 -07:00
|
|
|
{
|
|
|
|
|
m_DebugOverlayLines.push_back(SOverlayLine());
|
2015-03-15 16:59:48 -07:00
|
|
|
m_DebugOverlayLines.back().m_Color = subdivColor;
|
2014-01-06 20:05:10 -08:00
|
|
|
|
2013-09-15 07:03:53 -07:00
|
|
|
float xpos = x*divSize + divSize/2;
|
|
|
|
|
float zpos = y*divSize + divSize/2;
|
|
|
|
|
SimRender::ConstructSquareOnGround(GetSimContext(), xpos, zpos, divSize, divSize, 0.0f,
|
|
|
|
|
m_DebugOverlayLines.back(), false, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-07-29 13:39:23 -07:00
|
|
|
m_DebugOverlayDirty = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < m_DebugOverlayLines.size(); ++i)
|
|
|
|
|
collector.Submit(&m_DebugOverlayLines[i]);
|
|
|
|
|
}
|
2013-07-19 17:50:40 -07:00
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
virtual u8 GetEntityFlagMask(const std::string& identifier) const
|
2012-04-17 13:22:13 -07:00
|
|
|
{
|
2012-04-17 13:44:18 -07:00
|
|
|
if (identifier == "normal")
|
2017-02-24 11:15:54 -08:00
|
|
|
return FlagMasks::Normal;
|
2012-04-17 13:44:18 -07:00
|
|
|
if (identifier == "injured")
|
2017-02-24 11:15:54 -08:00
|
|
|
return FlagMasks::Injured;
|
2012-04-17 13:22:13 -07:00
|
|
|
|
2015-01-22 12:36:24 -08:00
|
|
|
LOGWARNING("CCmpRangeManager: Invalid flag identifier %s", identifier.c_str());
|
2017-02-24 11:15:54 -08:00
|
|
|
return FlagMasks::None;
|
2012-04-17 13:22:13 -07:00
|
|
|
}
|
2013-07-19 17:50:40 -07:00
|
|
|
|
2016-01-23 07:17:56 -08:00
|
|
|
virtual void SetEntityFlag(entity_id_t ent, const std::string& identifier, bool value)
|
2012-04-17 13:22:13 -07:00
|
|
|
{
|
2013-09-15 07:03:53 -07:00
|
|
|
EntityMap<EntityData>::iterator it = m_EntityData.find(ent);
|
2012-04-17 13:22:13 -07:00
|
|
|
|
|
|
|
|
// We don't have this entity
|
|
|
|
|
if (it == m_EntityData.end())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
u8 flag = GetEntityFlagMask(identifier);
|
|
|
|
|
|
2017-02-24 11:15:54 -08:00
|
|
|
if (flag == FlagMasks::None)
|
2015-01-22 12:36:24 -08:00
|
|
|
LOGWARNING("CCmpRangeManager: Invalid flag identifier %s for entity %u", identifier.c_str(), ent);
|
2012-04-17 13:22:13 -07:00
|
|
|
else
|
2017-02-24 11:15:54 -08:00
|
|
|
it->second.SetFlag(flag, value);
|
2012-04-17 13:22:13 -07:00
|
|
|
}
|
2010-09-23 05:13:13 -07:00
|
|
|
|
2011-08-01 14:25:12 -07:00
|
|
|
// ****************************************************************
|
2010-09-23 05:13:13 -07:00
|
|
|
|
|
|
|
|
// LOS implementation:
|
|
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
virtual CLosQuerier GetLosQuerier(player_id_t player) const
|
2010-09-23 05:13:13 -07:00
|
|
|
{
|
2011-06-28 16:24:42 -07:00
|
|
|
if (GetLosRevealAll(player))
|
2012-06-14 15:47:57 -07:00
|
|
|
return CLosQuerier(0xFFFFFFFFu, m_LosStateRevealed, m_TerrainVerticesPerSide);
|
2010-10-25 14:59:52 -07:00
|
|
|
else
|
2012-06-07 21:41:39 -07:00
|
|
|
return CLosQuerier(GetSharedLosMask(player), m_LosState, m_TerrainVerticesPerSide);
|
2010-09-23 05:13:13 -07:00
|
|
|
}
|
|
|
|
|
|
2015-02-14 09:13:50 -08:00
|
|
|
virtual void ActivateScriptedVisibility(entity_id_t ent, bool status)
|
|
|
|
|
{
|
|
|
|
|
EntityMap<EntityData>::iterator it = m_EntityData.find(ent);
|
|
|
|
|
if (it != m_EntityData.end())
|
2017-02-24 11:15:54 -08:00
|
|
|
it->second.SetFlag<FlagMasks::ScriptedVisibility>(status);
|
2015-02-14 09:13:50 -08:00
|
|
|
}
|
|
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
ELosVisibility ComputeLosVisibility(CEntityHandle ent, player_id_t player) const
|
2010-09-23 05:13:13 -07:00
|
|
|
{
|
2010-10-04 10:34:33 -07:00
|
|
|
// Entities not with positions in the world are never visible
|
2013-09-11 13:41:53 -07:00
|
|
|
if (ent.GetId() == INVALID_ENTITY)
|
|
|
|
|
return VIS_HIDDEN;
|
|
|
|
|
CmpPtr<ICmpPosition> cmpPosition(ent);
|
2012-02-07 18:46:15 -08:00
|
|
|
if (!cmpPosition || !cmpPosition->IsInWorld())
|
2010-09-23 05:13:13 -07:00
|
|
|
return VIS_HIDDEN;
|
|
|
|
|
|
2014-09-06 12:59:49 -07:00
|
|
|
// Mirage entities, whatever the situation, are visible for one specific player
|
|
|
|
|
CmpPtr<ICmpMirage> cmpMirage(ent);
|
|
|
|
|
if (cmpMirage && cmpMirage->GetPlayer() != player)
|
|
|
|
|
return VIS_HIDDEN;
|
|
|
|
|
|
2014-09-07 03:28:18 -07:00
|
|
|
CFixedVector2D pos = cmpPosition->GetPosition2D();
|
|
|
|
|
int i = (pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
|
|
|
|
|
int j = (pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
|
|
|
|
|
|
2014-11-14 12:29:35 -08:00
|
|
|
// Reveal flag makes all positioned entities visible and all mirages useless
|
2011-06-28 16:24:42 -07:00
|
|
|
if (GetLosRevealAll(player))
|
2010-10-25 14:59:52 -07:00
|
|
|
{
|
2014-11-14 12:29:35 -08:00
|
|
|
if (LosIsOffWorld(i, j) || cmpMirage)
|
2010-10-25 14:59:52 -07:00
|
|
|
return VIS_HIDDEN;
|
|
|
|
|
else
|
|
|
|
|
return VIS_VISIBLE;
|
|
|
|
|
}
|
2010-09-23 05:13:13 -07:00
|
|
|
|
2015-01-30 07:28:06 -08:00
|
|
|
// Get visible regions
|
2012-06-07 21:41:39 -07:00
|
|
|
CLosQuerier los(GetSharedLosMask(player), m_LosState, m_TerrainVerticesPerSide);
|
2010-09-23 05:13:13 -07:00
|
|
|
|
2015-01-30 07:28:06 -08:00
|
|
|
CmpPtr<ICmpVisibility> cmpVisibility(ent);
|
2015-02-14 09:13:50 -08:00
|
|
|
|
|
|
|
|
// Possibly ask the scripted Visibility component
|
2017-01-19 18:25:19 -08:00
|
|
|
EntityMap<EntityData>::const_iterator it = m_EntityData.find(ent.GetId());
|
2015-02-14 09:13:50 -08:00
|
|
|
if (it != m_EntityData.end())
|
|
|
|
|
{
|
2017-02-24 11:15:54 -08:00
|
|
|
if (it->second.HasFlag<FlagMasks::ScriptedVisibility>() && cmpVisibility)
|
2015-02-14 09:13:50 -08:00
|
|
|
return cmpVisibility->GetVisibility(player, los.IsVisible(i, j), los.IsExplored(i, j));
|
|
|
|
|
}
|
2015-02-16 06:38:13 -08:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (cmpVisibility && cmpVisibility->IsActivated())
|
|
|
|
|
return cmpVisibility->GetVisibility(player, los.IsVisible(i, j), los.IsExplored(i, j));
|
|
|
|
|
}
|
2015-01-30 07:28:06 -08:00
|
|
|
|
|
|
|
|
// Else, default behavior
|
|
|
|
|
|
|
|
|
|
if (los.IsVisible(i, j))
|
2015-02-01 11:04:21 -08:00
|
|
|
{
|
|
|
|
|
if (cmpMirage)
|
|
|
|
|
return VIS_HIDDEN;
|
|
|
|
|
|
2015-01-30 07:28:06 -08:00
|
|
|
return VIS_VISIBLE;
|
2015-02-01 11:04:21 -08:00
|
|
|
}
|
2015-01-30 07:28:06 -08:00
|
|
|
|
2014-11-04 12:53:25 -08:00
|
|
|
if (!los.IsExplored(i, j))
|
2014-08-04 15:49:19 -07:00
|
|
|
return VIS_HIDDEN;
|
|
|
|
|
|
2015-01-30 07:28:06 -08:00
|
|
|
// Invisible if the 'retain in fog' flag is not set, and in a non-visible explored region
|
2015-02-14 09:13:50 -08:00
|
|
|
// Try using the 'retainInFog' flag in m_EntityData to save a script call
|
|
|
|
|
if (it != m_EntityData.end())
|
|
|
|
|
{
|
2017-02-24 11:15:54 -08:00
|
|
|
if (!it->second.HasFlag<FlagMasks::RetainInFog>())
|
2015-02-14 09:13:50 -08:00
|
|
|
return VIS_HIDDEN;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (!(cmpVisibility && cmpVisibility->GetRetainInFog()))
|
|
|
|
|
return VIS_HIDDEN;
|
|
|
|
|
}
|
2014-08-04 15:49:19 -07:00
|
|
|
|
2015-02-01 11:04:21 -08:00
|
|
|
if (cmpMirage)
|
2015-01-30 07:28:06 -08:00
|
|
|
return VIS_FOGGED;
|
|
|
|
|
|
|
|
|
|
CmpPtr<ICmpOwnership> cmpOwnership(ent);
|
|
|
|
|
if (!cmpOwnership)
|
2015-02-01 11:04:21 -08:00
|
|
|
return VIS_FOGGED;
|
2015-01-30 07:28:06 -08:00
|
|
|
|
|
|
|
|
if (cmpOwnership->GetOwner() == player)
|
|
|
|
|
{
|
|
|
|
|
CmpPtr<ICmpFogging> cmpFogging(ent);
|
2015-02-01 11:04:21 -08:00
|
|
|
if (!(cmpFogging && cmpFogging->IsMiraged(player)))
|
2015-01-30 07:28:06 -08:00
|
|
|
return VIS_FOGGED;
|
|
|
|
|
|
|
|
|
|
return VIS_HIDDEN;
|
|
|
|
|
}
|
2015-02-01 11:04:21 -08:00
|
|
|
|
2015-02-07 07:48:32 -08:00
|
|
|
// Fogged entities are hidden in two cases:
|
|
|
|
|
// - They were not scouted
|
|
|
|
|
// - A mirage replaces them
|
2015-01-30 07:28:06 -08:00
|
|
|
CmpPtr<ICmpFogging> cmpFogging(ent);
|
2015-02-07 07:48:32 -08:00
|
|
|
if (cmpFogging && cmpFogging->IsActivated() &&
|
|
|
|
|
(!cmpFogging->WasSeen(player) || cmpFogging->IsMiraged(player)))
|
|
|
|
|
return VIS_HIDDEN;
|
2015-01-30 07:28:06 -08:00
|
|
|
|
2015-02-07 07:48:32 -08:00
|
|
|
return VIS_FOGGED;
|
2010-09-23 05:13:13 -07:00
|
|
|
}
|
|
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
ELosVisibility ComputeLosVisibility(entity_id_t ent, player_id_t player) const
|
2015-01-20 08:29:50 -08:00
|
|
|
{
|
|
|
|
|
CEntityHandle handle = GetSimContext().GetComponentManager().LookupEntityHandle(ent);
|
2015-01-30 07:28:06 -08:00
|
|
|
return ComputeLosVisibility(handle, player);
|
2015-01-20 08:29:50 -08:00
|
|
|
}
|
|
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
virtual ELosVisibility GetLosVisibility(CEntityHandle ent, player_id_t player) const
|
2015-01-20 08:29:50 -08:00
|
|
|
{
|
|
|
|
|
entity_id_t entId = ent.GetId();
|
2016-10-28 09:11:59 -07:00
|
|
|
|
2015-01-20 08:29:50 -08:00
|
|
|
// Entities not with positions in the world are never visible
|
|
|
|
|
if (entId == INVALID_ENTITY)
|
|
|
|
|
return VIS_HIDDEN;
|
2017-02-24 11:15:54 -08:00
|
|
|
|
2015-01-20 08:29:50 -08:00
|
|
|
CmpPtr<ICmpPosition> cmpPosition(ent);
|
|
|
|
|
if (!cmpPosition || !cmpPosition->IsInWorld())
|
|
|
|
|
return VIS_HIDDEN;
|
|
|
|
|
|
2015-02-04 01:20:00 -08:00
|
|
|
// Gaia and observers do not have a visibility cache
|
|
|
|
|
if (player <= 0)
|
2015-01-30 10:08:16 -08:00
|
|
|
return ComputeLosVisibility(ent, player);
|
|
|
|
|
|
2017-02-24 11:15:54 -08:00
|
|
|
CFixedVector2D pos = cmpPosition->GetPosition2D();
|
|
|
|
|
i32 n = PosToLosTilesHelper(pos.X, pos.Y);
|
|
|
|
|
|
2015-02-12 15:22:29 -08:00
|
|
|
if (IsVisibilityDirty(m_DirtyVisibility[n], player))
|
2015-01-30 07:28:06 -08:00
|
|
|
return ComputeLosVisibility(ent, player);
|
2015-01-20 08:29:50 -08:00
|
|
|
|
|
|
|
|
if (std::find(m_ModifiedEntities.begin(), m_ModifiedEntities.end(), entId) != m_ModifiedEntities.end())
|
2015-01-30 07:28:06 -08:00
|
|
|
return ComputeLosVisibility(ent, player);
|
2015-01-20 08:29:50 -08:00
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
EntityMap<EntityData>::const_iterator it = m_EntityData.find(entId);
|
2015-01-20 08:29:50 -08:00
|
|
|
if (it == m_EntityData.end())
|
2015-01-30 07:28:06 -08:00
|
|
|
return ComputeLosVisibility(ent, player);
|
2015-01-20 08:29:50 -08:00
|
|
|
|
|
|
|
|
return static_cast<ELosVisibility>(GetPlayerVisibility(it->second.visibilities, player));
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
virtual ELosVisibility GetLosVisibility(entity_id_t ent, player_id_t player) const
|
2013-09-11 13:41:53 -07:00
|
|
|
{
|
|
|
|
|
CEntityHandle handle = GetSimContext().GetComponentManager().LookupEntityHandle(ent);
|
2015-01-30 07:28:06 -08:00
|
|
|
return GetLosVisibility(handle, player);
|
2013-09-11 13:41:53 -07:00
|
|
|
}
|
|
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
i32 PosToLosTilesHelper(entity_pos_t x, entity_pos_t z) const
|
2014-07-10 13:51:39 -07:00
|
|
|
{
|
|
|
|
|
i32 i = Clamp(
|
|
|
|
|
(x/(entity_pos_t::FromInt(TERRAIN_TILE_SIZE * LOS_TILES_RATIO))).ToInt_RoundToZero(),
|
|
|
|
|
0,
|
|
|
|
|
m_LosTilesPerSide - 1);
|
|
|
|
|
i32 j = Clamp(
|
|
|
|
|
(z/(entity_pos_t::FromInt(TERRAIN_TILE_SIZE * LOS_TILES_RATIO))).ToInt_RoundToZero(),
|
|
|
|
|
0,
|
|
|
|
|
m_LosTilesPerSide - 1);
|
|
|
|
|
return j*m_LosTilesPerSide + i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AddToTile(i32 tile, entity_id_t ent)
|
|
|
|
|
{
|
|
|
|
|
m_LosTiles[tile].insert(ent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RemoveFromTile(i32 tile, entity_id_t ent)
|
|
|
|
|
{
|
2017-03-24 17:11:08 -07:00
|
|
|
std::set<entity_id_t>::const_iterator tileIt = m_LosTiles[tile].find(ent);
|
|
|
|
|
if (tileIt != m_LosTiles[tile].end())
|
|
|
|
|
m_LosTiles[tile].erase(tileIt);
|
2014-07-10 13:51:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void UpdateVisibilityData()
|
|
|
|
|
{
|
|
|
|
|
PROFILE("UpdateVisibilityData");
|
2015-07-15 09:46:59 -07:00
|
|
|
|
2015-02-12 15:22:29 -08:00
|
|
|
for (i32 n = 0; n < m_LosTilesPerSide * m_LosTilesPerSide; ++n)
|
2014-07-10 13:51:39 -07:00
|
|
|
{
|
2015-02-12 15:22:29 -08:00
|
|
|
for (player_id_t player = 1; player < MAX_LOS_PLAYER_ID + 1; ++player)
|
2015-02-20 06:35:19 -08:00
|
|
|
if (IsVisibilityDirty(m_DirtyVisibility[n], player) || m_GlobalPlayerVisibilityUpdate[player-1] == 1 || m_GlobalVisibilityUpdate)
|
2015-08-30 10:42:10 -07:00
|
|
|
for (const entity_id_t& ent : m_LosTiles[n])
|
2015-02-12 15:22:29 -08:00
|
|
|
UpdateVisibility(ent, player);
|
2016-10-28 09:11:59 -07:00
|
|
|
|
2015-02-12 15:22:29 -08:00
|
|
|
m_DirtyVisibility[n] = 0;
|
2014-07-10 13:51:39 -07:00
|
|
|
}
|
|
|
|
|
|
2015-02-20 06:35:19 -08:00
|
|
|
std::fill(m_GlobalPlayerVisibilityUpdate.begin(), m_GlobalPlayerVisibilityUpdate.end(), 0);
|
2015-07-15 09:46:59 -07:00
|
|
|
m_GlobalVisibilityUpdate = false;
|
2015-02-20 06:35:19 -08:00
|
|
|
|
2015-08-30 10:42:10 -07:00
|
|
|
// Calling UpdateVisibility can modify m_ModifiedEntities, so be careful:
|
|
|
|
|
// infinite loops could be triggered by feedback between entities and their mirages.
|
|
|
|
|
std::map<entity_id_t, u8> attempts;
|
2015-07-15 09:46:59 -07:00
|
|
|
while (!m_ModifiedEntities.empty())
|
2014-07-10 13:51:39 -07:00
|
|
|
{
|
2015-07-15 09:46:59 -07:00
|
|
|
entity_id_t ent = m_ModifiedEntities.back();
|
|
|
|
|
m_ModifiedEntities.pop_back();
|
2015-08-30 10:42:10 -07:00
|
|
|
|
|
|
|
|
++attempts[ent];
|
|
|
|
|
ENSURE(attempts[ent] < 100 && "Infinite loop in UpdateVisibilityData");
|
|
|
|
|
|
2015-07-15 09:46:59 -07:00
|
|
|
UpdateVisibility(ent);
|
|
|
|
|
}
|
2014-07-10 13:51:39 -07:00
|
|
|
}
|
|
|
|
|
|
2015-02-07 07:48:32 -08:00
|
|
|
virtual void RequestVisibilityUpdate(entity_id_t ent)
|
|
|
|
|
{
|
|
|
|
|
if (std::find(m_ModifiedEntities.begin(), m_ModifiedEntities.end(), ent) == m_ModifiedEntities.end())
|
|
|
|
|
m_ModifiedEntities.push_back(ent);
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-12 15:22:29 -08:00
|
|
|
void UpdateVisibility(entity_id_t ent, player_id_t player)
|
2014-07-10 13:51:39 -07:00
|
|
|
{
|
|
|
|
|
EntityMap<EntityData>::iterator itEnts = m_EntityData.find(ent);
|
|
|
|
|
if (itEnts == m_EntityData.end())
|
|
|
|
|
return;
|
2014-08-04 15:49:19 -07:00
|
|
|
|
2015-02-12 15:22:29 -08:00
|
|
|
u8 oldVis = GetPlayerVisibility(itEnts->second.visibilities, player);
|
|
|
|
|
u8 newVis = ComputeLosVisibility(itEnts->first, player);
|
|
|
|
|
|
|
|
|
|
if (oldVis == newVis)
|
|
|
|
|
return;
|
2015-08-30 10:42:10 -07:00
|
|
|
|
2015-02-12 15:22:29 -08:00
|
|
|
itEnts->second.visibilities = (itEnts->second.visibilities & ~(0x3 << 2 * (player - 1))) | (newVis << 2 * (player - 1));
|
2014-08-04 15:49:19 -07:00
|
|
|
|
2015-02-12 15:22:29 -08:00
|
|
|
CMessageVisibilityChanged msg(player, ent, oldVis, newVis);
|
|
|
|
|
GetSimContext().GetComponentManager().PostMessage(ent, msg);
|
|
|
|
|
}
|
2014-09-20 01:26:45 -07:00
|
|
|
|
2015-02-12 15:22:29 -08:00
|
|
|
void UpdateVisibility(entity_id_t ent)
|
2015-07-15 09:46:59 -07:00
|
|
|
{
|
2015-02-12 15:22:29 -08:00
|
|
|
for (player_id_t player = 1; player < MAX_LOS_PLAYER_ID + 1; ++player)
|
|
|
|
|
UpdateVisibility(ent, player);
|
2014-07-10 13:51:39 -07:00
|
|
|
}
|
2013-09-11 13:41:53 -07:00
|
|
|
|
2011-06-28 16:24:42 -07:00
|
|
|
virtual void SetLosRevealAll(player_id_t player, bool enabled)
|
2010-09-23 05:13:13 -07:00
|
|
|
{
|
2013-12-31 13:30:48 -08:00
|
|
|
if (player == -1)
|
|
|
|
|
m_LosRevealAll[MAX_LOS_PLAYER_ID+1] = enabled;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ENSURE(player >= 0 && player <= MAX_LOS_PLAYER_ID);
|
|
|
|
|
m_LosRevealAll[player] = enabled;
|
|
|
|
|
}
|
2014-09-20 01:26:45 -07:00
|
|
|
|
|
|
|
|
// On next update, update the visibility of every entity in the world
|
|
|
|
|
m_GlobalVisibilityUpdate = true;
|
2010-09-23 05:13:13 -07:00
|
|
|
}
|
|
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
virtual bool GetLosRevealAll(player_id_t player) const
|
2010-09-23 05:13:13 -07:00
|
|
|
{
|
2011-06-28 16:24:42 -07:00
|
|
|
// Special player value can force reveal-all for every player
|
2014-03-16 16:29:27 -07:00
|
|
|
if (m_LosRevealAll[MAX_LOS_PLAYER_ID+1] || player == -1)
|
2011-06-28 16:24:42 -07:00
|
|
|
return true;
|
2013-12-31 13:45:11 -08:00
|
|
|
ENSURE(player >= 0 && player <= MAX_LOS_PLAYER_ID+1);
|
2011-06-28 16:24:42 -07:00
|
|
|
// Otherwise check the player-specific flag
|
2013-12-31 08:38:11 -08:00
|
|
|
if (m_LosRevealAll[player])
|
2011-06-28 16:24:42 -07:00
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
return false;
|
2010-09-23 05:13:13 -07:00
|
|
|
}
|
|
|
|
|
|
2010-10-25 14:59:52 -07:00
|
|
|
virtual void SetLosCircular(bool enabled)
|
|
|
|
|
{
|
|
|
|
|
m_LosCircular = enabled;
|
|
|
|
|
|
2015-11-11 04:15:57 -08:00
|
|
|
ResetDerivedData();
|
2010-10-25 14:59:52 -07:00
|
|
|
}
|
|
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
virtual bool GetLosCircular() const
|
2010-10-30 11:25:34 -07:00
|
|
|
{
|
|
|
|
|
return m_LosCircular;
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-23 07:17:56 -08:00
|
|
|
virtual void SetSharedLos(player_id_t player, const std::vector<player_id_t>& players)
|
2012-06-07 21:41:39 -07:00
|
|
|
{
|
|
|
|
|
m_SharedLosMasks[player] = CalcSharedLosMask(players);
|
2015-02-20 06:35:19 -08:00
|
|
|
|
2016-11-24 06:57:09 -08:00
|
|
|
// Units belonging to any of 'players' can now trigger visibility updates for 'player'.
|
|
|
|
|
// If shared LOS partners have been removed, we disable visibility updates from them
|
|
|
|
|
// in order to improve performance. That also allows us to properly determine whether
|
|
|
|
|
// 'player' needs a global visibility update for this turn.
|
2015-07-11 09:45:09 -07:00
|
|
|
bool modified = false;
|
|
|
|
|
|
2016-11-24 06:57:09 -08:00
|
|
|
for (player_id_t p = 1; p < MAX_LOS_PLAYER_ID+1; ++p)
|
|
|
|
|
{
|
|
|
|
|
bool inList = std::find(players.begin(), players.end(), p) != players.end();
|
|
|
|
|
|
|
|
|
|
if (SetPlayerSharedDirtyVisibilityBit(m_SharedDirtyVisibilityMasks[p], player, inList))
|
2015-07-11 09:45:09 -07:00
|
|
|
modified = true;
|
2016-11-24 06:57:09 -08:00
|
|
|
}
|
2015-02-20 06:35:19 -08:00
|
|
|
|
2015-07-11 09:45:09 -07:00
|
|
|
if (modified && (size_t)player <= m_GlobalPlayerVisibilityUpdate.size())
|
2015-02-20 06:35:19 -08:00
|
|
|
m_GlobalPlayerVisibilityUpdate[player-1] = 1;
|
2012-06-07 21:41:39 -07:00
|
|
|
}
|
|
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
virtual u32 GetSharedLosMask(player_id_t player) const
|
2012-06-07 21:41:39 -07:00
|
|
|
{
|
|
|
|
|
return m_SharedLosMasks[player];
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-14 12:52:49 -07:00
|
|
|
void ExploreAllTiles(player_id_t p)
|
|
|
|
|
{
|
|
|
|
|
for (u16 j = 0; j < m_TerrainVerticesPerSide; ++j)
|
|
|
|
|
for (u16 i = 0; i < m_TerrainVerticesPerSide; ++i)
|
|
|
|
|
{
|
|
|
|
|
if (LosIsOffWorld(i,j))
|
|
|
|
|
continue;
|
|
|
|
|
u32 &explored = m_ExploredVertices.at(p);
|
|
|
|
|
explored += !(m_LosState[i + j*m_TerrainVerticesPerSide] & (LOS_EXPLORED << (2*(p-1))));
|
|
|
|
|
m_LosState[i + j*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
|
|
|
|
|
}
|
2014-08-26 03:01:04 -07:00
|
|
|
|
|
|
|
|
SeeExploredEntities(p);
|
2014-04-14 12:52:49 -07:00
|
|
|
}
|
|
|
|
|
|
2014-08-26 03:01:04 -07:00
|
|
|
virtual void ExploreTerritories()
|
2011-08-01 14:25:12 -07:00
|
|
|
{
|
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
|
|
|
PROFILE3("ExploreTerritories");
|
2011-08-01 14:25:12 -07:00
|
|
|
|
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
|
|
|
CmpPtr<ICmpTerritoryManager> cmpTerritoryManager(GetSystemEntity());
|
2011-08-01 14:25:12 -07:00
|
|
|
const Grid<u8>& grid = cmpTerritoryManager->GetTerritoryGrid();
|
|
|
|
|
|
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
|
|
|
// Territory data is stored per territory-tile (typically a multiple of terrain-tiles).
|
|
|
|
|
// LOS data is stored per terrain-tile vertex.
|
|
|
|
|
|
|
|
|
|
// For each territory-tile, if it is owned by a valid player then update the LOS
|
|
|
|
|
// for every vertex inside/around that tile, to mark them as explored.
|
|
|
|
|
|
|
|
|
|
// Currently this code doesn't support territory-tiles smaller than terrain-tiles
|
|
|
|
|
// (it will get scale==0 and break), or a non-integer multiple, so check that first
|
|
|
|
|
cassert(ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE >= Pathfinding::NAVCELLS_PER_TILE);
|
|
|
|
|
cassert(ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE % Pathfinding::NAVCELLS_PER_TILE == 0);
|
|
|
|
|
|
|
|
|
|
int scale = ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE / Pathfinding::NAVCELLS_PER_TILE;
|
|
|
|
|
|
|
|
|
|
ENSURE(grid.m_W*scale == m_TerrainVerticesPerSide-1 && grid.m_H*scale == m_TerrainVerticesPerSide-1);
|
2011-08-01 14:25:12 -07:00
|
|
|
|
2011-08-16 04:18:32 -07:00
|
|
|
for (u16 j = 0; j < grid.m_H; ++j)
|
|
|
|
|
for (u16 i = 0; i < grid.m_W; ++i)
|
2011-08-01 14:25:12 -07:00
|
|
|
{
|
2011-08-18 13:28:53 -07:00
|
|
|
u8 p = grid.get(i, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK;
|
2011-08-01 14:25:12 -07:00
|
|
|
if (p > 0 && p <= MAX_LOS_PLAYER_ID)
|
|
|
|
|
{
|
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
|
|
|
u32& explored = m_ExploredVertices.at(p);
|
2017-06-16 12:39:30 -07:00
|
|
|
for (int tj = j * scale; tj <= (j+1) * scale; ++tj)
|
|
|
|
|
for (int ti = i * scale; ti <= (i+1) * scale; ++ti)
|
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
|
|
|
{
|
2017-06-16 12:39:30 -07:00
|
|
|
if (LosIsOffWorld(ti, tj))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
u32& losState = m_LosState[ti + tj * m_TerrainVerticesPerSide];
|
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
|
|
|
if (!(losState & (LOS_EXPLORED << (2*(p-1)))))
|
|
|
|
|
{
|
2017-06-16 12:39:30 -07:00
|
|
|
++explored;
|
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
|
|
|
losState |= (LOS_EXPLORED << (2*(p-1)));
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-08-01 14:25:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
2014-08-26 03:01:04 -07:00
|
|
|
|
|
|
|
|
for (player_id_t p = 1; p < MAX_LOS_PLAYER_ID+1; ++p)
|
|
|
|
|
SeeExploredEntities(p);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Force any entity in explored territory to appear for player p.
|
|
|
|
|
* This is useful for miraging entities inside the territory borders at the beginning of a game,
|
|
|
|
|
* or if the "Explore Map" option has been set.
|
|
|
|
|
*/
|
2017-01-19 18:25:19 -08:00
|
|
|
void SeeExploredEntities(player_id_t p) const
|
2014-08-26 03:01:04 -07:00
|
|
|
{
|
2014-08-29 02:01:08 -07:00
|
|
|
// Warning: Code related to fogging (like ForceMiraging) shouldn't be
|
|
|
|
|
// invoked while iterating through m_EntityData.
|
2016-11-23 05:02:58 -08:00
|
|
|
// Otherwise, by deleting mirage entities and so on, that code will
|
|
|
|
|
// change the indexes in the map, leading to segfaults.
|
2014-08-29 02:01:08 -07:00
|
|
|
// So we just remember what entities to mirage and do that later.
|
|
|
|
|
std::vector<entity_id_t> miragableEntities;
|
2016-10-28 09:11:59 -07:00
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
|
2014-08-26 03:01:04 -07:00
|
|
|
{
|
|
|
|
|
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), it->first);
|
|
|
|
|
if (!cmpPosition || !cmpPosition->IsInWorld())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
CFixedVector2D pos = cmpPosition->GetPosition2D();
|
|
|
|
|
int i = (pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
|
|
|
|
|
int j = (pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
|
|
|
|
|
|
|
|
|
|
CLosQuerier los(GetSharedLosMask(p), m_LosState, m_TerrainVerticesPerSide);
|
|
|
|
|
if (!los.IsExplored(i,j) || los.IsVisible(i,j))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
CmpPtr<ICmpFogging> cmpFogging(GetSimContext(), it->first);
|
|
|
|
|
if (cmpFogging)
|
2014-08-29 02:01:08 -07:00
|
|
|
miragableEntities.push_back(it->first);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (std::vector<entity_id_t>::iterator it = miragableEntities.begin(); it != miragableEntities.end(); ++it)
|
|
|
|
|
{
|
|
|
|
|
CmpPtr<ICmpFogging> cmpFogging(GetSimContext(), *it);
|
|
|
|
|
ENSURE(cmpFogging && "Impossible to retrieve Fogging component, previously achieved");
|
|
|
|
|
cmpFogging->ForceMiraging(p);
|
2014-08-26 03:01:04 -07:00
|
|
|
}
|
2011-08-01 14:25:12 -07:00
|
|
|
}
|
|
|
|
|
|
2015-05-06 11:47:02 -07:00
|
|
|
virtual void RevealShore(player_id_t p, bool enable)
|
|
|
|
|
{
|
|
|
|
|
if (p <= 0 || p > MAX_LOS_PLAYER_ID)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Maximum distance to the shore
|
|
|
|
|
const u16 maxdist = 10;
|
|
|
|
|
|
|
|
|
|
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
|
|
|
|
|
const Grid<u16>& shoreGrid = cmpPathfinder->ComputeShoreGrid(true);
|
|
|
|
|
ENSURE(shoreGrid.m_W == m_TerrainVerticesPerSide-1 && shoreGrid.m_H == m_TerrainVerticesPerSide-1);
|
|
|
|
|
|
|
|
|
|
std::vector<u16>& counts = m_LosPlayerCounts.at(p);
|
|
|
|
|
ENSURE(!counts.empty());
|
|
|
|
|
u16* countsData = &counts[0];
|
|
|
|
|
|
|
|
|
|
for (u16 j = 0; j < shoreGrid.m_H; ++j)
|
|
|
|
|
for (u16 i = 0; i < shoreGrid.m_W; ++i)
|
|
|
|
|
{
|
|
|
|
|
u16 shoredist = shoreGrid.get(i, j);
|
|
|
|
|
if (shoredist > maxdist)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Maybe we could be more clever and don't add dummy strips of one tile
|
|
|
|
|
if (enable)
|
|
|
|
|
LosAddStripHelper(p, i, i, j, countsData);
|
|
|
|
|
else
|
|
|
|
|
LosRemoveStripHelper(p, i, i, j, countsData);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-25 14:59:52 -07:00
|
|
|
/**
|
|
|
|
|
* Returns whether the given vertex is outside the normal bounds of the world
|
|
|
|
|
* (i.e. outside the range of a circular map)
|
|
|
|
|
*/
|
2017-01-19 18:25:19 -08:00
|
|
|
inline bool LosIsOffWorld(ssize_t i, ssize_t j) const
|
2010-10-25 14:59:52 -07:00
|
|
|
{
|
New long-range pathfinder.
Based on Philip's work located at
http://git.wildfiregames.com/gitweb/?p=0ad.git;a=shortlog;h=refs/heads/projects/philip/pathfinder
Includes code by wraitii, sanderd17 and kanetaka.
An updated version of docs/pathfinder.pdf describing the changes in
detail will be committed ASAP.
Running update-workspaces is needed after this change.
Fixes #1756.
Fixes #930, #1259, #2908, #2960, #3097
Refs #1200, #1914, #1942, #2568, #2132, #2563
This was SVN commit r16751.
2015-06-12 11:58:24 -07:00
|
|
|
// WARNING: CCmpPathfinder::UpdateGrid needs to be kept in sync with this
|
2011-02-02 17:12:24 -08:00
|
|
|
const ssize_t edgeSize = 3; // number of vertexes around the edge that will be off-world
|
|
|
|
|
|
2010-10-25 14:59:52 -07:00
|
|
|
if (m_LosCircular)
|
|
|
|
|
{
|
2010-10-30 11:25:34 -07:00
|
|
|
// With a circular map, vertex is off-world if hypot(i - size/2, j - size/2) >= size/2:
|
2010-10-25 14:59:52 -07:00
|
|
|
|
|
|
|
|
ssize_t dist2 = (i - m_TerrainVerticesPerSide/2)*(i - m_TerrainVerticesPerSide/2)
|
|
|
|
|
+ (j - m_TerrainVerticesPerSide/2)*(j - m_TerrainVerticesPerSide/2);
|
|
|
|
|
|
2011-02-02 17:12:24 -08:00
|
|
|
ssize_t r = m_TerrainVerticesPerSide/2 - edgeSize + 1;
|
|
|
|
|
// subtract a bit from the radius to ensure nice
|
|
|
|
|
// SoD blurring around the edges of the map
|
2010-10-25 14:59:52 -07:00
|
|
|
|
2011-02-02 17:12:24 -08:00
|
|
|
return (dist2 >= r*r);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// With a square map, the outermost edge of the map should be off-world,
|
|
|
|
|
// so the SoD texture blends out nicely
|
2010-10-25 14:59:52 -07:00
|
|
|
|
2011-02-02 17:12:24 -08:00
|
|
|
return (i < edgeSize || j < edgeSize || i >= m_TerrainVerticesPerSide-edgeSize || j >= m_TerrainVerticesPerSide-edgeSize);
|
|
|
|
|
}
|
2010-10-25 14:59:52 -07:00
|
|
|
}
|
|
|
|
|
|
2010-09-23 05:13:13 -07:00
|
|
|
/**
|
|
|
|
|
* Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
|
|
|
|
|
*/
|
2010-11-14 16:36:15 -08:00
|
|
|
inline void LosAddStripHelper(u8 owner, i32 i0, i32 i1, i32 j, u16* counts)
|
2010-09-23 05:13:13 -07:00
|
|
|
{
|
2010-11-14 16:36:15 -08:00
|
|
|
if (i1 < i0)
|
|
|
|
|
return;
|
2010-09-23 05:13:13 -07:00
|
|
|
|
2010-11-14 16:36:15 -08:00
|
|
|
i32 idx0 = j*m_TerrainVerticesPerSide + i0;
|
|
|
|
|
i32 idx1 = j*m_TerrainVerticesPerSide + i1;
|
2013-07-19 17:50:40 -07:00
|
|
|
u32 &explored = m_ExploredVertices.at(owner);
|
2010-11-14 16:36:15 -08:00
|
|
|
for (i32 idx = idx0; idx <= idx1; ++idx)
|
|
|
|
|
{
|
2010-09-23 05:13:13 -07:00
|
|
|
// Increasing from zero to non-zero - move from unexplored/explored to visible+explored
|
2010-11-14 16:36:15 -08:00
|
|
|
if (counts[idx] == 0)
|
2010-09-23 05:13:13 -07:00
|
|
|
{
|
2010-11-14 16:36:15 -08:00
|
|
|
i32 i = i0 + idx - idx0;
|
2010-10-25 14:59:52 -07:00
|
|
|
if (!LosIsOffWorld(i, j))
|
2013-07-19 17:50:40 -07:00
|
|
|
{
|
|
|
|
|
explored += !(m_LosState[idx] & (LOS_EXPLORED << (2*(owner-1))));
|
2010-10-25 14:59:52 -07:00
|
|
|
m_LosState[idx] |= ((LOS_VISIBLE | LOS_EXPLORED) << (2*(owner-1)));
|
2013-07-19 17:50:40 -07:00
|
|
|
}
|
2014-09-21 13:29:54 -07:00
|
|
|
|
2015-05-06 11:47:02 -07:00
|
|
|
MarkVisibilityDirtyAroundTile(owner, i, j);
|
2010-09-23 05:13:13 -07:00
|
|
|
}
|
|
|
|
|
|
2011-11-14 14:22:38 -08:00
|
|
|
ASSERT(counts[idx] < 65535);
|
2011-08-16 04:18:32 -07:00
|
|
|
counts[idx] = (u16)(counts[idx] + 1); // ignore overflow; the player should never have 64K units
|
2010-11-14 16:36:15 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
|
|
|
|
|
*/
|
|
|
|
|
inline void LosRemoveStripHelper(u8 owner, i32 i0, i32 i1, i32 j, u16* counts)
|
|
|
|
|
{
|
|
|
|
|
if (i1 < i0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
i32 idx0 = j*m_TerrainVerticesPerSide + i0;
|
|
|
|
|
i32 idx1 = j*m_TerrainVerticesPerSide + i1;
|
|
|
|
|
for (i32 idx = idx0; idx <= idx1; ++idx)
|
|
|
|
|
{
|
2011-11-14 14:22:38 -08:00
|
|
|
ASSERT(counts[idx] > 0);
|
2011-08-16 04:18:32 -07:00
|
|
|
counts[idx] = (u16)(counts[idx] - 1);
|
2010-09-23 05:13:13 -07:00
|
|
|
|
|
|
|
|
// Decreasing from non-zero to zero - move from visible+explored to explored
|
2010-11-14 16:36:15 -08:00
|
|
|
if (counts[idx] == 0)
|
2010-09-23 05:13:13 -07:00
|
|
|
{
|
2010-10-25 14:59:52 -07:00
|
|
|
// (If LosIsOffWorld then this is a no-op, so don't bother doing the check)
|
2010-09-23 05:13:13 -07:00
|
|
|
m_LosState[idx] &= ~(LOS_VISIBLE << (2*(owner-1)));
|
2014-07-10 13:51:39 -07:00
|
|
|
|
|
|
|
|
i32 i = i0 + idx - idx0;
|
2015-05-06 11:47:02 -07:00
|
|
|
MarkVisibilityDirtyAroundTile(owner, i, j);
|
2010-09-23 05:13:13 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-06 11:47:02 -07:00
|
|
|
inline void MarkVisibilityDirtyAroundTile(u8 owner, i32 i, i32 j)
|
|
|
|
|
{
|
2015-11-11 04:15:57 -08:00
|
|
|
// If we're still in the deserializing process, we must not modify m_DirtyVisibility
|
|
|
|
|
if (m_Deserializing)
|
|
|
|
|
return;
|
|
|
|
|
|
2015-05-06 11:47:02 -07:00
|
|
|
// Mark the LoS tiles around the updated vertex
|
|
|
|
|
// 1: left-up, 2: right-up, 3: left-down, 4: right-down
|
|
|
|
|
int n1 = ((j-1)/LOS_TILES_RATIO)*m_LosTilesPerSide + (i-1)/LOS_TILES_RATIO;
|
|
|
|
|
int n2 = ((j-1)/LOS_TILES_RATIO)*m_LosTilesPerSide + i/LOS_TILES_RATIO;
|
|
|
|
|
int n3 = (j/LOS_TILES_RATIO)*m_LosTilesPerSide + (i-1)/LOS_TILES_RATIO;
|
|
|
|
|
int n4 = (j/LOS_TILES_RATIO)*m_LosTilesPerSide + i/LOS_TILES_RATIO;
|
|
|
|
|
|
|
|
|
|
u16 sharedDirtyVisibilityMask = m_SharedDirtyVisibilityMasks[owner];
|
|
|
|
|
|
|
|
|
|
if (j > 0 && i > 0)
|
|
|
|
|
m_DirtyVisibility[n1] |= sharedDirtyVisibilityMask;
|
|
|
|
|
if (n2 != n1 && j > 0 && i < m_TerrainVerticesPerSide)
|
|
|
|
|
m_DirtyVisibility[n2] |= sharedDirtyVisibilityMask;
|
|
|
|
|
if (n3 != n1 && j < m_TerrainVerticesPerSide && i > 0)
|
|
|
|
|
m_DirtyVisibility[n3] |= sharedDirtyVisibilityMask;
|
|
|
|
|
if (n4 != n1 && j < m_TerrainVerticesPerSide && i < m_TerrainVerticesPerSide)
|
|
|
|
|
m_DirtyVisibility[n4] |= sharedDirtyVisibilityMask;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-23 05:13:13 -07:00
|
|
|
/**
|
2010-11-14 16:36:15 -08:00
|
|
|
* Update the LOS state of tiles within a given circular range,
|
|
|
|
|
* either adding or removing visibility depending on the template parameter.
|
2010-09-23 05:13:13 -07:00
|
|
|
* Assumes owner is in the valid range.
|
|
|
|
|
*/
|
2010-11-14 16:36:15 -08:00
|
|
|
template<bool adding>
|
|
|
|
|
void LosUpdateHelper(u8 owner, entity_pos_t visionRange, CFixedVector2D pos)
|
2010-09-23 05:13:13 -07:00
|
|
|
{
|
|
|
|
|
if (m_TerrainVerticesPerSide == 0) // do nothing if not initialised yet
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
PROFILE("LosUpdateHelper");
|
|
|
|
|
|
|
|
|
|
std::vector<u16>& counts = m_LosPlayerCounts.at(owner);
|
|
|
|
|
|
|
|
|
|
// Lazy initialisation of counts:
|
|
|
|
|
if (counts.empty())
|
|
|
|
|
counts.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
|
|
|
|
|
|
2010-11-14 16:48:34 -08:00
|
|
|
u16* countsData = &counts[0];
|
|
|
|
|
|
2010-09-23 05:13:13 -07:00
|
|
|
// Compute the circular region as a series of strips.
|
|
|
|
|
// Rather than quantise pos to vertexes, we do more precise sub-tile computations
|
|
|
|
|
// to get smoother behaviour as a unit moves rather than jumping a whole tile
|
|
|
|
|
// at once.
|
2010-11-14 16:36:15 -08:00
|
|
|
// To avoid the cost of sqrt when computing the outline of the circle,
|
|
|
|
|
// we loop from the bottom to the top and estimate the width of the current
|
|
|
|
|
// strip based on the previous strip, then adjust each end of the strip
|
|
|
|
|
// inwards or outwards until it's the widest that still falls within the circle.
|
2010-09-23 05:13:13 -07:00
|
|
|
|
|
|
|
|
// Compute top/bottom coordinates, and clamp to exclude the 1-tile border around the map
|
|
|
|
|
// (so that we never render the sharp edge of the map)
|
2012-01-12 04:51:10 -08:00
|
|
|
i32 j0 = ((pos.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity();
|
|
|
|
|
i32 j1 = ((pos.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity();
|
2010-10-23 12:59:40 -07:00
|
|
|
i32 j0clamp = std::max(j0, 1);
|
|
|
|
|
i32 j1clamp = std::min(j1, m_TerrainVerticesPerSide-2);
|
2010-09-23 05:13:13 -07:00
|
|
|
|
2010-11-14 16:36:15 -08:00
|
|
|
// Translate world coordinates into fractional tile-space coordinates
|
2012-01-12 04:51:10 -08:00
|
|
|
entity_pos_t x = pos.X / (int)TERRAIN_TILE_SIZE;
|
|
|
|
|
entity_pos_t y = pos.Y / (int)TERRAIN_TILE_SIZE;
|
|
|
|
|
entity_pos_t r = visionRange / (int)TERRAIN_TILE_SIZE;
|
2010-11-14 16:36:15 -08:00
|
|
|
entity_pos_t r2 = r.Square();
|
|
|
|
|
|
|
|
|
|
// Compute the integers on either side of x
|
2011-10-28 06:15:33 -07:00
|
|
|
i32 xfloor = (x - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
|
|
|
|
|
i32 xceil = (x + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
|
2010-11-14 16:36:15 -08:00
|
|
|
|
|
|
|
|
// Initialise the strip (i0, i1) to a rough guess
|
|
|
|
|
i32 i0 = xfloor;
|
|
|
|
|
i32 i1 = xceil;
|
2010-09-23 05:13:13 -07:00
|
|
|
|
2010-10-23 12:59:40 -07:00
|
|
|
for (i32 j = j0clamp; j <= j1clamp; ++j)
|
2010-09-23 05:13:13 -07:00
|
|
|
{
|
2010-11-14 16:36:15 -08:00
|
|
|
// Adjust i0 and i1 to be the outermost values that don't exceed
|
|
|
|
|
// the circle's radius (i.e. require dy^2 + dx^2 <= r^2).
|
|
|
|
|
// When moving the points inwards, clamp them to xceil+1 or xfloor-1
|
|
|
|
|
// so they don't accidentally shoot off in the wrong direction forever.
|
|
|
|
|
|
|
|
|
|
entity_pos_t dy = entity_pos_t::FromInt(j) - y;
|
|
|
|
|
entity_pos_t dy2 = dy.Square();
|
|
|
|
|
while (dy2 + (entity_pos_t::FromInt(i0-1) - x).Square() <= r2)
|
|
|
|
|
--i0;
|
2011-10-28 06:15:33 -07:00
|
|
|
while (i0 < xceil && dy2 + (entity_pos_t::FromInt(i0) - x).Square() > r2)
|
2010-11-14 16:36:15 -08:00
|
|
|
++i0;
|
|
|
|
|
while (dy2 + (entity_pos_t::FromInt(i1+1) - x).Square() <= r2)
|
|
|
|
|
++i1;
|
2011-10-28 06:15:33 -07:00
|
|
|
while (i1 > xfloor && dy2 + (entity_pos_t::FromInt(i1) - x).Square() > r2)
|
2010-11-14 16:36:15 -08:00
|
|
|
--i1;
|
|
|
|
|
|
2011-10-28 06:15:33 -07:00
|
|
|
#if DEBUG_RANGE_MANAGER_BOUNDS
|
|
|
|
|
if (i0 <= i1)
|
|
|
|
|
{
|
|
|
|
|
ENSURE(dy2 + (entity_pos_t::FromInt(i0) - x).Square() <= r2);
|
|
|
|
|
ENSURE(dy2 + (entity_pos_t::FromInt(i1) - x).Square() <= r2);
|
|
|
|
|
}
|
|
|
|
|
ENSURE(dy2 + (entity_pos_t::FromInt(i0 - 1) - x).Square() > r2);
|
|
|
|
|
ENSURE(dy2 + (entity_pos_t::FromInt(i1 + 1) - x).Square() > r2);
|
|
|
|
|
#endif
|
|
|
|
|
|
2010-11-14 16:36:15 -08:00
|
|
|
// Clamp the strip to exclude the 1-tile border,
|
|
|
|
|
// then add or remove the strip as requested
|
2010-10-23 12:59:40 -07:00
|
|
|
i32 i0clamp = std::max(i0, 1);
|
|
|
|
|
i32 i1clamp = std::min(i1, m_TerrainVerticesPerSide-2);
|
2010-11-14 16:36:15 -08:00
|
|
|
if (adding)
|
2010-11-14 16:48:34 -08:00
|
|
|
LosAddStripHelper(owner, i0clamp, i1clamp, j, countsData);
|
2010-11-14 16:36:15 -08:00
|
|
|
else
|
2010-11-14 16:48:34 -08:00
|
|
|
LosRemoveStripHelper(owner, i0clamp, i1clamp, j, countsData);
|
2010-11-14 16:36:15 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update the LOS state of tiles within a given circular range,
|
|
|
|
|
* by removing visibility around the 'from' position
|
|
|
|
|
* and then adding visibility around the 'to' position.
|
|
|
|
|
*/
|
|
|
|
|
void LosUpdateHelperIncremental(u8 owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
|
|
|
|
|
{
|
|
|
|
|
if (m_TerrainVerticesPerSide == 0) // do nothing if not initialised yet
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
PROFILE("LosUpdateHelperIncremental");
|
|
|
|
|
|
|
|
|
|
std::vector<u16>& counts = m_LosPlayerCounts.at(owner);
|
|
|
|
|
|
|
|
|
|
// Lazy initialisation of counts:
|
|
|
|
|
if (counts.empty())
|
|
|
|
|
counts.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
|
|
|
|
|
|
2010-11-14 16:48:34 -08:00
|
|
|
u16* countsData = &counts[0];
|
|
|
|
|
|
2010-11-14 16:36:15 -08:00
|
|
|
// See comments in LosUpdateHelper.
|
|
|
|
|
// This does exactly the same, except computing the strips for
|
|
|
|
|
// both circles simultaneously.
|
|
|
|
|
// (The idea is that the circles will be heavily overlapping,
|
|
|
|
|
// so we can compute the difference between the removed/added strips
|
|
|
|
|
// and only have to touch tiles that have a net change.)
|
|
|
|
|
|
2012-01-12 04:51:10 -08:00
|
|
|
i32 j0_from = ((from.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity();
|
|
|
|
|
i32 j1_from = ((from.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity();
|
|
|
|
|
i32 j0_to = ((to.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity();
|
|
|
|
|
i32 j1_to = ((to.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity();
|
2010-11-14 16:36:15 -08:00
|
|
|
i32 j0clamp = std::max(std::min(j0_from, j0_to), 1);
|
|
|
|
|
i32 j1clamp = std::min(std::max(j1_from, j1_to), m_TerrainVerticesPerSide-2);
|
|
|
|
|
|
2012-01-12 04:51:10 -08:00
|
|
|
entity_pos_t x_from = from.X / (int)TERRAIN_TILE_SIZE;
|
|
|
|
|
entity_pos_t y_from = from.Y / (int)TERRAIN_TILE_SIZE;
|
|
|
|
|
entity_pos_t x_to = to.X / (int)TERRAIN_TILE_SIZE;
|
|
|
|
|
entity_pos_t y_to = to.Y / (int)TERRAIN_TILE_SIZE;
|
|
|
|
|
entity_pos_t r = visionRange / (int)TERRAIN_TILE_SIZE;
|
2010-11-14 16:36:15 -08:00
|
|
|
entity_pos_t r2 = r.Square();
|
|
|
|
|
|
2011-10-28 06:15:33 -07:00
|
|
|
i32 xfloor_from = (x_from - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
|
|
|
|
|
i32 xceil_from = (x_from + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
|
|
|
|
|
i32 xfloor_to = (x_to - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
|
|
|
|
|
i32 xceil_to = (x_to + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
|
2010-11-14 16:36:15 -08:00
|
|
|
|
|
|
|
|
i32 i0_from = xfloor_from;
|
|
|
|
|
i32 i1_from = xceil_from;
|
|
|
|
|
i32 i0_to = xfloor_to;
|
|
|
|
|
i32 i1_to = xceil_to;
|
|
|
|
|
|
|
|
|
|
for (i32 j = j0clamp; j <= j1clamp; ++j)
|
|
|
|
|
{
|
|
|
|
|
entity_pos_t dy_from = entity_pos_t::FromInt(j) - y_from;
|
|
|
|
|
entity_pos_t dy2_from = dy_from.Square();
|
|
|
|
|
while (dy2_from + (entity_pos_t::FromInt(i0_from-1) - x_from).Square() <= r2)
|
|
|
|
|
--i0_from;
|
2011-10-28 06:15:33 -07:00
|
|
|
while (i0_from < xceil_from && dy2_from + (entity_pos_t::FromInt(i0_from) - x_from).Square() > r2)
|
2010-11-14 16:36:15 -08:00
|
|
|
++i0_from;
|
|
|
|
|
while (dy2_from + (entity_pos_t::FromInt(i1_from+1) - x_from).Square() <= r2)
|
|
|
|
|
++i1_from;
|
2011-10-28 06:15:33 -07:00
|
|
|
while (i1_from > xfloor_from && dy2_from + (entity_pos_t::FromInt(i1_from) - x_from).Square() > r2)
|
2010-11-14 16:36:15 -08:00
|
|
|
--i1_from;
|
|
|
|
|
|
|
|
|
|
entity_pos_t dy_to = entity_pos_t::FromInt(j) - y_to;
|
|
|
|
|
entity_pos_t dy2_to = dy_to.Square();
|
|
|
|
|
while (dy2_to + (entity_pos_t::FromInt(i0_to-1) - x_to).Square() <= r2)
|
|
|
|
|
--i0_to;
|
2011-10-28 06:15:33 -07:00
|
|
|
while (i0_to < xceil_to && dy2_to + (entity_pos_t::FromInt(i0_to) - x_to).Square() > r2)
|
2010-11-14 16:36:15 -08:00
|
|
|
++i0_to;
|
|
|
|
|
while (dy2_to + (entity_pos_t::FromInt(i1_to+1) - x_to).Square() <= r2)
|
|
|
|
|
++i1_to;
|
2011-10-28 06:15:33 -07:00
|
|
|
while (i1_to > xfloor_to && dy2_to + (entity_pos_t::FromInt(i1_to) - x_to).Square() > r2)
|
2010-11-14 16:36:15 -08:00
|
|
|
--i1_to;
|
|
|
|
|
|
2011-10-28 06:15:33 -07:00
|
|
|
#if DEBUG_RANGE_MANAGER_BOUNDS
|
|
|
|
|
if (i0_from <= i1_from)
|
|
|
|
|
{
|
|
|
|
|
ENSURE(dy2_from + (entity_pos_t::FromInt(i0_from) - x_from).Square() <= r2);
|
|
|
|
|
ENSURE(dy2_from + (entity_pos_t::FromInt(i1_from) - x_from).Square() <= r2);
|
|
|
|
|
}
|
|
|
|
|
ENSURE(dy2_from + (entity_pos_t::FromInt(i0_from - 1) - x_from).Square() > r2);
|
|
|
|
|
ENSURE(dy2_from + (entity_pos_t::FromInt(i1_from + 1) - x_from).Square() > r2);
|
|
|
|
|
if (i0_to <= i1_to)
|
|
|
|
|
{
|
|
|
|
|
ENSURE(dy2_to + (entity_pos_t::FromInt(i0_to) - x_to).Square() <= r2);
|
|
|
|
|
ENSURE(dy2_to + (entity_pos_t::FromInt(i1_to) - x_to).Square() <= r2);
|
|
|
|
|
}
|
|
|
|
|
ENSURE(dy2_to + (entity_pos_t::FromInt(i0_to - 1) - x_to).Square() > r2);
|
|
|
|
|
ENSURE(dy2_to + (entity_pos_t::FromInt(i1_to + 1) - x_to).Square() > r2);
|
|
|
|
|
#endif
|
|
|
|
|
|
2010-11-14 16:36:15 -08:00
|
|
|
// Check whether this strip moved at all
|
|
|
|
|
if (!(i0_to == i0_from && i1_to == i1_from))
|
|
|
|
|
{
|
|
|
|
|
i32 i0clamp_from = std::max(i0_from, 1);
|
|
|
|
|
i32 i1clamp_from = std::min(i1_from, m_TerrainVerticesPerSide-2);
|
|
|
|
|
i32 i0clamp_to = std::max(i0_to, 1);
|
|
|
|
|
i32 i1clamp_to = std::min(i1_to, m_TerrainVerticesPerSide-2);
|
|
|
|
|
|
|
|
|
|
// Check whether one strip is negative width,
|
|
|
|
|
// and we can just add/remove the entire other strip
|
|
|
|
|
if (i1clamp_from < i0clamp_from)
|
|
|
|
|
{
|
2010-11-14 16:48:34 -08:00
|
|
|
LosAddStripHelper(owner, i0clamp_to, i1clamp_to, j, countsData);
|
2010-11-14 16:36:15 -08:00
|
|
|
}
|
|
|
|
|
else if (i1clamp_to < i0clamp_to)
|
|
|
|
|
{
|
2010-11-14 16:48:34 -08:00
|
|
|
LosRemoveStripHelper(owner, i0clamp_from, i1clamp_from, j, countsData);
|
2010-11-14 16:36:15 -08:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// There are four possible regions of overlap between the two strips
|
|
|
|
|
// (remove before add, remove after add, add before remove, add after remove).
|
|
|
|
|
// Process each of the regions as its own strip.
|
|
|
|
|
// (If this produces negative-width strips then they'll just get ignored
|
|
|
|
|
// which is fine.)
|
2011-11-14 14:22:38 -08:00
|
|
|
// (If the strips don't actually overlap (which is very rare with normal unit
|
|
|
|
|
// movement speeds), the region between them will be both added and removed,
|
|
|
|
|
// so we have to do the add first to avoid overflowing to -1 and triggering
|
|
|
|
|
// assertion failures.)
|
2010-11-14 16:48:34 -08:00
|
|
|
LosAddStripHelper(owner, i0clamp_to, i0clamp_from-1, j, countsData);
|
|
|
|
|
LosAddStripHelper(owner, i1clamp_from+1, i1clamp_to, j, countsData);
|
2011-11-14 14:22:38 -08:00
|
|
|
LosRemoveStripHelper(owner, i0clamp_from, i0clamp_to-1, j, countsData);
|
|
|
|
|
LosRemoveStripHelper(owner, i1clamp_to+1, i1clamp_from, j, countsData);
|
2010-11-14 16:36:15 -08:00
|
|
|
}
|
|
|
|
|
}
|
2010-09-23 05:13:13 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-16 04:18:32 -07:00
|
|
|
void LosAdd(player_id_t owner, entity_pos_t visionRange, CFixedVector2D pos)
|
2010-09-23 05:13:13 -07:00
|
|
|
{
|
|
|
|
|
if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
|
|
|
|
|
return;
|
|
|
|
|
|
2011-08-16 04:18:32 -07:00
|
|
|
LosUpdateHelper<true>((u8)owner, visionRange, pos);
|
2010-09-23 05:13:13 -07:00
|
|
|
}
|
|
|
|
|
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
void SharingLosAdd(u16 visionSharing, entity_pos_t visionRange, CFixedVector2D pos)
|
|
|
|
|
{
|
|
|
|
|
if (visionRange.IsZero())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (player_id_t i = 1; i < MAX_LOS_PLAYER_ID+1; ++i)
|
|
|
|
|
if (HasVisionSharing(visionSharing, i))
|
|
|
|
|
LosAdd(i, visionRange, pos);
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-16 04:18:32 -07:00
|
|
|
void LosRemove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D pos)
|
2010-09-23 05:13:13 -07:00
|
|
|
{
|
|
|
|
|
if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
|
|
|
|
|
return;
|
|
|
|
|
|
2011-08-16 04:18:32 -07:00
|
|
|
LosUpdateHelper<false>((u8)owner, visionRange, pos);
|
2010-09-23 05:13:13 -07:00
|
|
|
}
|
|
|
|
|
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
void SharingLosRemove(u16 visionSharing, entity_pos_t visionRange, CFixedVector2D pos)
|
|
|
|
|
{
|
|
|
|
|
if (visionRange.IsZero())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (player_id_t i = 1; i < MAX_LOS_PLAYER_ID+1; ++i)
|
|
|
|
|
if (HasVisionSharing(visionSharing, i))
|
|
|
|
|
LosRemove(i, visionRange, pos);
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-16 04:18:32 -07:00
|
|
|
void LosMove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
|
2010-09-23 05:13:13 -07:00
|
|
|
{
|
|
|
|
|
if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
|
|
|
|
|
return;
|
|
|
|
|
|
2010-11-14 16:36:15 -08:00
|
|
|
if ((from - to).CompareLength(visionRange) > 0)
|
|
|
|
|
{
|
|
|
|
|
// If it's a very large move, then simply remove and add to the new position
|
2011-08-16 04:18:32 -07:00
|
|
|
LosUpdateHelper<false>((u8)owner, visionRange, from);
|
|
|
|
|
LosUpdateHelper<true>((u8)owner, visionRange, to);
|
2010-11-14 16:36:15 -08:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
// Otherwise use the version optimised for mostly-overlapping circles
|
2011-08-16 04:18:32 -07:00
|
|
|
LosUpdateHelperIncremental((u8)owner, visionRange, from, to);
|
2010-09-23 05:13:13 -07:00
|
|
|
}
|
2010-11-21 11:42:26 -08:00
|
|
|
|
Implement shared vision at the entity level
Summary:
To fix tickets like #3335, having a shared vision at the entity level is
needed. This patch implements that in CCmpRangeManager, interfaced with
a new JS component VisionSharing to manage the shared entities visions.
As an example of use case (in addition to garrisoning in allied
structure without the sharedLos tech), we can think of bribing enemy
units: there is a part about it in the patch, but this one is very wip
and not intended to be committed but rather for test purposes of the
feature.
So when garrisoning a unit in another player's building, the vision of
the garrisonHolder is shared (part intended for review). In addition,
for tests of the feature, when clicking on the new bribe icon in the
diplomacy window, a random unit of the chosen player is bribed and share
its vision during 15 s.
Test Plan: Garrison a unit in an allied structure without the sharedLos
tech, or test the wip bribe feature from the diplomacy window.
Reviewers: Itms
Reviewed By: Itms
Subscribers: Stan, leper, O11 Templates, wraitii, elexis, fatherbushido,
Itms, Vulcan, O1 C++ Simulation
Differential Revision: https://code.wildfiregames.com/D60
This was SVN commit r19175.
2017-01-26 13:10:46 -08:00
|
|
|
void SharingLosMove(u16 visionSharing, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
|
|
|
|
|
{
|
|
|
|
|
if (visionRange.IsZero())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (player_id_t i = 1; i < MAX_LOS_PLAYER_ID+1; ++i)
|
|
|
|
|
if (HasVisionSharing(visionSharing, i))
|
|
|
|
|
LosMove(i, visionRange, from, to);
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
virtual u8 GetPercentMapExplored(player_id_t player) const
|
2010-11-21 11:42:26 -08:00
|
|
|
{
|
2013-07-19 17:50:40 -07:00
|
|
|
return m_ExploredVertices.at((u8)player) * 100 / m_TotalInworldVertices;
|
2010-11-21 11:42:26 -08:00
|
|
|
}
|
2014-12-26 11:13:40 -08:00
|
|
|
|
2017-01-19 18:25:19 -08:00
|
|
|
virtual u8 GetUnionPercentMapExplored(const std::vector<player_id_t>& players) const
|
2014-12-26 11:13:40 -08:00
|
|
|
{
|
|
|
|
|
u32 exploredVertices = 0;
|
2016-01-23 07:17:56 -08:00
|
|
|
std::vector<player_id_t>::const_iterator playerIt;
|
2014-12-26 11:13:40 -08:00
|
|
|
|
|
|
|
|
for (i32 j = 0; j < m_TerrainVerticesPerSide; j++)
|
|
|
|
|
for (i32 i = 0; i < m_TerrainVerticesPerSide; i++)
|
|
|
|
|
{
|
|
|
|
|
if (LosIsOffWorld(i, j))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
for (playerIt = players.begin(); playerIt != players.end(); ++playerIt)
|
|
|
|
|
if (m_LosState[j*m_TerrainVerticesPerSide + i] & (LOS_EXPLORED << (2*((*playerIt)-1))))
|
|
|
|
|
{
|
|
|
|
|
exploredVertices += 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return exploredVertices * 100 / m_TotalInworldVertices;
|
|
|
|
|
}
|
2010-07-29 13:39:23 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
REGISTER_COMPONENT_TYPE(RangeManager)
|