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.
This commit is contained in:
parent
ff743bc928
commit
c2d0327af9
@ -0,0 +1,92 @@
|
||||
function VisionSharing() {}
|
||||
|
||||
VisionSharing.prototype.Schema =
|
||||
"<empty/>";
|
||||
|
||||
VisionSharing.prototype.Init = function()
|
||||
{
|
||||
this.activated = false;
|
||||
this.shared = new Set();
|
||||
};
|
||||
|
||||
/**
|
||||
* As entities have not necessarily the VisionSharing component, it has to be activated
|
||||
* before use so that the rangeManager can register it
|
||||
*/
|
||||
VisionSharing.prototype.Activate = function()
|
||||
{
|
||||
if (this.activated)
|
||||
return;
|
||||
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
|
||||
if (!cmpOwnership || cmpOwnership.GetOwner() <= 0)
|
||||
return;
|
||||
this.shared.add(cmpOwnership.GetOwner());
|
||||
Engine.PostMessage(this.entity, MT_VisionSharingChanged,
|
||||
{ "entity": this.entity, "player": cmpOwnership.GetOwner(), "add": true });
|
||||
this.activated = true;
|
||||
};
|
||||
|
||||
VisionSharing.prototype.CheckVisionSharings = function()
|
||||
{
|
||||
let shared = new Set();
|
||||
|
||||
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
|
||||
let owner = cmpOwnership ? cmpOwnership.GetOwner() : -1;
|
||||
if (owner >= 0)
|
||||
{
|
||||
// The owner has vision
|
||||
if (owner > 0)
|
||||
shared.add(owner);
|
||||
|
||||
// Vision sharing due to garrisoned units
|
||||
let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
|
||||
if (cmpGarrisonHolder)
|
||||
{
|
||||
for (let ent of cmpGarrisonHolder.GetEntities())
|
||||
{
|
||||
let cmpEntOwnership = Engine.QueryInterface(ent, IID_Ownership);
|
||||
if (!cmpEntOwnership)
|
||||
continue;
|
||||
let entOwner = cmpEntOwnership.GetOwner();
|
||||
if (entOwner > 0 && entOwner != owner)
|
||||
{
|
||||
shared.add(entOwner);
|
||||
// if shared by another player than the owner and not yet activated, do it
|
||||
this.Activate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.activated)
|
||||
return;
|
||||
|
||||
// compare with previous vision sharing, and update if needed
|
||||
for (let player of shared)
|
||||
if (!this.shared.has(player))
|
||||
Engine.PostMessage(this.entity, MT_VisionSharingChanged,
|
||||
{ "entity": this.entity, "player": player, "add": true });
|
||||
for (let player of this.shared)
|
||||
if (!shared.has(player))
|
||||
Engine.PostMessage(this.entity, MT_VisionSharingChanged,
|
||||
{ "entity": this.entity, "player": player, "add": false });
|
||||
this.shared = shared;
|
||||
};
|
||||
|
||||
VisionSharing.prototype.OnDiplomacyChanged = function(msg)
|
||||
{
|
||||
this.CheckVisionSharings();
|
||||
};
|
||||
|
||||
VisionSharing.prototype.OnGarrisonedUnitsChanged = function(msg)
|
||||
{
|
||||
this.CheckVisionSharings();
|
||||
};
|
||||
|
||||
VisionSharing.prototype.OnOwnershipChanged = function(msg)
|
||||
{
|
||||
if (this.activated)
|
||||
this.CheckVisionSharings();
|
||||
};
|
||||
|
||||
Engine.RegisterComponentType(IID_VisionSharing, "VisionSharing", VisionSharing);
|
@ -0,0 +1 @@
|
||||
Engine.RegisterInterface("VisionSharing");
|
@ -140,6 +140,7 @@
|
||||
<Vision>
|
||||
<Range>40</Range>
|
||||
</Vision>
|
||||
<VisionSharing/>
|
||||
<VisualActor>
|
||||
<ConstructionPreview/>
|
||||
<SilhouetteDisplay>false</SilhouetteDisplay>
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015 Wildfire Games.
|
||||
/* Copyright (C) 2017 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -478,9 +478,9 @@ public:
|
||||
DEFAULT_MESSAGE_IMPL(ValueModification)
|
||||
|
||||
CMessageValueModification(const std::vector<entity_id_t>& entities, std::wstring component, const std::vector<std::wstring>& valueNames) :
|
||||
entities(entities),
|
||||
component(component),
|
||||
valueNames(valueNames)
|
||||
entities(entities),
|
||||
component(component),
|
||||
valueNames(valueNames)
|
||||
{
|
||||
}
|
||||
|
||||
@ -498,9 +498,9 @@ public:
|
||||
DEFAULT_MESSAGE_IMPL(TemplateModification)
|
||||
|
||||
CMessageTemplateModification(player_id_t player, std::wstring component, const std::vector<std::wstring>& valueNames) :
|
||||
player(player),
|
||||
component(component),
|
||||
valueNames(valueNames)
|
||||
player(player),
|
||||
component(component),
|
||||
valueNames(valueNames)
|
||||
{
|
||||
}
|
||||
|
||||
@ -518,7 +518,7 @@ public:
|
||||
DEFAULT_MESSAGE_IMPL(VisionRangeChanged)
|
||||
|
||||
CMessageVisionRangeChanged(entity_id_t entity, entity_pos_t oldRange, entity_pos_t newRange) :
|
||||
entity(entity), oldRange(oldRange), newRange(newRange)
|
||||
entity(entity), oldRange(oldRange), newRange(newRange)
|
||||
{
|
||||
}
|
||||
|
||||
@ -527,6 +527,24 @@ public:
|
||||
entity_pos_t newRange;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sent by CCmpVision when an entity's vision sharing changes.
|
||||
*/
|
||||
class CMessageVisionSharingChanged : public CMessage
|
||||
{
|
||||
public:
|
||||
DEFAULT_MESSAGE_IMPL(VisionSharingChanged)
|
||||
|
||||
CMessageVisionSharingChanged(entity_id_t entity, player_id_t player, bool add) :
|
||||
entity(entity), player(player), add(add)
|
||||
{
|
||||
}
|
||||
|
||||
entity_id_t entity;
|
||||
player_id_t player;
|
||||
bool add;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sent when an entity pings the minimap
|
||||
*/
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2014 Wildfire Games.
|
||||
/* Copyright (C) 2017 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -56,6 +56,7 @@ MESSAGE(PathResult)
|
||||
MESSAGE(ValueModification)
|
||||
MESSAGE(TemplateModification)
|
||||
MESSAGE(VisionRangeChanged)
|
||||
MESSAGE(VisionSharingChanged)
|
||||
MESSAGE(MinimapPing)
|
||||
MESSAGE(CinemaPathEnded)
|
||||
MESSAGE(CinemaQueueEnded)
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016 Wildfire Games.
|
||||
/* Copyright (C) 2017 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -135,6 +135,22 @@ static inline bool IsVisibilityDirty(u16 dirty, player_id_t player)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether v is in a parabolic range of (0,0,0)
|
||||
* The highest point of the paraboloid is (0,range/2,0)
|
||||
@ -178,23 +194,25 @@ static std::map<entity_id_t, EntityParabolicRangeOutline> ParabolicRangesOutline
|
||||
struct EntityData
|
||||
{
|
||||
EntityData() :
|
||||
visibilities(0), size(0),
|
||||
visibilities(0), size(0), visionSharing(0),
|
||||
owner(-1), retainInFog(0), inWorld(0), revealShore(0),
|
||||
flags(1), scriptedVisibility(0)
|
||||
flags(1), scriptedVisibility(0), sharedVision(0)
|
||||
{ }
|
||||
entity_pos_t x, z;
|
||||
entity_pos_t visionRange;
|
||||
u32 visibilities; // 2-bit visibility, per player
|
||||
u32 size;
|
||||
u16 visionSharing; // 1-bit per player
|
||||
i8 owner;
|
||||
u8 retainInFog; // boolean
|
||||
u8 inWorld; // boolean
|
||||
u8 revealShore; // boolean
|
||||
u8 flags; // See GetEntityFlagMask
|
||||
u8 scriptedVisibility; // boolean, see ComputeLosVisibility
|
||||
u8 sharedVision; // boolean for shared vision
|
||||
};
|
||||
|
||||
cassert(sizeof(EntityData) == 28);
|
||||
cassert(sizeof(EntityData) == 32);
|
||||
|
||||
/**
|
||||
* Serialization helper template for Query
|
||||
@ -248,12 +266,14 @@ struct SerializeEntityData
|
||||
serialize.NumberFixed_Unbounded("vision", value.visionRange);
|
||||
serialize.NumberU32_Unbounded("visibilities", value.visibilities);
|
||||
serialize.NumberU32_Unbounded("size", value.size);
|
||||
serialize.NumberU16_Unbounded("vision sharing", value.visionSharing);
|
||||
serialize.NumberI8_Unbounded("owner", value.owner);
|
||||
serialize.NumberU8("retain in fog", value.retainInFog, 0, 1);
|
||||
serialize.NumberU8("in world", value.inWorld, 0, 1);
|
||||
serialize.NumberU8("reveal shore", value.revealShore, 0, 1);
|
||||
serialize.NumberU8_Unbounded("flags", value.flags);
|
||||
serialize.NumberU8("scripted visibility", value.scriptedVisibility, 0, 1);
|
||||
serialize.NumberU8("shared vision", value.sharedVision, 0, 1);
|
||||
}
|
||||
};
|
||||
|
||||
@ -304,6 +324,7 @@ public:
|
||||
componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged);
|
||||
componentManager.SubscribeGloballyToMessageType(MT_Destroy);
|
||||
componentManager.SubscribeGloballyToMessageType(MT_VisionRangeChanged);
|
||||
componentManager.SubscribeGloballyToMessageType(MT_VisionSharingChanged);
|
||||
|
||||
componentManager.SubscribeToMessageType(MT_Deserialized);
|
||||
componentManager.SubscribeToMessageType(MT_Update);
|
||||
@ -524,7 +545,10 @@ public:
|
||||
CFixedVector2D from(it->second.x, it->second.z);
|
||||
CFixedVector2D to(msgData.x, msgData.z);
|
||||
m_Subdivision.Move(ent, from, to, it->second.size);
|
||||
LosMove(it->second.owner, it->second.visionRange, from, to);
|
||||
if (it->second.sharedVision)
|
||||
SharingLosMove(it->second.visionSharing, it->second.visionRange, from, to);
|
||||
else
|
||||
LosMove(it->second.owner, it->second.visionRange, from, to);
|
||||
i32 oldLosTile = PosToLosTilesHelper(it->second.x, it->second.z);
|
||||
i32 newLosTile = PosToLosTilesHelper(msgData.x, msgData.z);
|
||||
if (oldLosTile != newLosTile)
|
||||
@ -537,7 +561,10 @@ public:
|
||||
{
|
||||
CFixedVector2D to(msgData.x, msgData.z);
|
||||
m_Subdivision.Add(ent, to, it->second.size);
|
||||
LosAdd(it->second.owner, it->second.visionRange, to);
|
||||
if (it->second.sharedVision)
|
||||
SharingLosAdd(it->second.visionSharing, it->second.visionRange, to);
|
||||
else
|
||||
LosAdd(it->second.owner, it->second.visionRange, to);
|
||||
AddToTile(PosToLosTilesHelper(msgData.x, msgData.z), ent);
|
||||
}
|
||||
|
||||
@ -551,7 +578,10 @@ public:
|
||||
{
|
||||
CFixedVector2D from(it->second.x, it->second.z);
|
||||
m_Subdivision.Remove(ent, from, it->second.size);
|
||||
LosRemove(it->second.owner, it->second.visionRange, from);
|
||||
if (it->second.sharedVision)
|
||||
SharingLosRemove(it->second.visionSharing, it->second.visionRange, from);
|
||||
else
|
||||
LosRemove(it->second.owner, it->second.visionRange, from);
|
||||
RemoveFromTile(PosToLosTilesHelper(it->second.x, it->second.z), ent);
|
||||
}
|
||||
|
||||
@ -577,9 +607,14 @@ public:
|
||||
|
||||
if (it->second.inWorld)
|
||||
{
|
||||
CFixedVector2D pos(it->second.x, it->second.z);
|
||||
LosRemove(it->second.owner, it->second.visionRange, pos);
|
||||
LosAdd(msgData.to, it->second.visionRange, pos);
|
||||
// Entity vision is taken into account in VisionSharingChanged
|
||||
// when sharing component activated
|
||||
if (!it->second.sharedVision)
|
||||
{
|
||||
CFixedVector2D pos(it->second.x, it->second.z);
|
||||
LosRemove(it->second.owner, it->second.visionRange, pos);
|
||||
LosAdd(msgData.to, it->second.visionRange, pos);
|
||||
}
|
||||
|
||||
if (it->second.revealShore)
|
||||
{
|
||||
@ -638,17 +673,65 @@ public:
|
||||
|
||||
// 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
|
||||
CFixedVector2D pos(it->second.x, it->second.z);
|
||||
if (it->second.inWorld)
|
||||
LosRemove(it->second.owner, oldRange, pos);
|
||||
|
||||
it->second.visionRange = newRange;
|
||||
|
||||
if (it->second.inWorld)
|
||||
LosAdd(it->second.owner, newRange, pos);
|
||||
{
|
||||
CFixedVector2D pos(it->second.x, it->second.z);
|
||||
if (it->second.sharedVision)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
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);
|
||||
|
||||
if (!it->second.sharedVision)
|
||||
{
|
||||
// Activation of the Vision Sharing
|
||||
ENSURE(it->second.owner == (i8)msgData.player);
|
||||
it->second.visionSharing = visionChanged;
|
||||
it->second.sharedVision = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (it->second.inWorld)
|
||||
{
|
||||
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;
|
||||
}
|
||||
case MT_Update:
|
||||
{
|
||||
m_DebugOverlayDirty = true;
|
||||
@ -767,7 +850,10 @@ public:
|
||||
for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
|
||||
if (it->second.inWorld)
|
||||
{
|
||||
LosAdd(it->second.owner, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z));
|
||||
if (it->second.sharedVision)
|
||||
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));
|
||||
AddToTile(PosToLosTilesHelper(it->second.x, it->second.z), it->first);
|
||||
|
||||
if (it->second.revealShore)
|
||||
@ -2260,6 +2346,16 @@ public:
|
||||
LosUpdateHelper<true>((u8)owner, visionRange, pos);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void LosRemove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D pos)
|
||||
{
|
||||
if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
|
||||
@ -2268,6 +2364,16 @@ public:
|
||||
LosUpdateHelper<false>((u8)owner, visionRange, pos);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void LosMove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
|
||||
{
|
||||
if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
|
||||
@ -2276,7 +2382,6 @@ public:
|
||||
if ((from - to).CompareLength(visionRange) > 0)
|
||||
{
|
||||
// If it's a very large move, then simply remove and add to the new position
|
||||
|
||||
LosUpdateHelper<false>((u8)owner, visionRange, from);
|
||||
LosUpdateHelper<true>((u8)owner, visionRange, to);
|
||||
}
|
||||
@ -2285,6 +2390,16 @@ public:
|
||||
LosUpdateHelperIncremental((u8)owner, visionRange, from, to);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
virtual u8 GetPercentMapExplored(player_id_t player) const
|
||||
{
|
||||
return m_ExploredVertices.at((u8)player) * 100 / m_TotalInworldVertices;
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015 Wildfire Games.
|
||||
/* Copyright (C) 2017 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -457,6 +457,24 @@ CMessage* CMessageVisionRangeChanged::FromJSVal(ScriptInterface& scriptInterface
|
||||
return new CMessageVisionRangeChanged(entity, oldRange, newRange);
|
||||
}
|
||||
|
||||
JS::Value CMessageVisionSharingChanged::ToJSVal(ScriptInterface& scriptInterface) const
|
||||
{
|
||||
TOJSVAL_SETUP();
|
||||
SET_MSG_PROPERTY(entity);
|
||||
SET_MSG_PROPERTY(player);
|
||||
SET_MSG_PROPERTY(add);
|
||||
return JS::ObjectValue(*obj);
|
||||
}
|
||||
|
||||
CMessage* CMessageVisionSharingChanged::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
|
||||
{
|
||||
FROMJSVAL_SETUP();
|
||||
GET_MSG_PROPERTY(entity_id_t, entity);
|
||||
GET_MSG_PROPERTY(player_id_t, player);
|
||||
GET_MSG_PROPERTY(bool, add);
|
||||
return new CMessageVisionSharingChanged(entity, player, add);
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
|
||||
JS::Value CMessageMinimapPing::ToJSVal(ScriptInterface& scriptInterface) const
|
||||
|
Loading…
Reference in New Issue
Block a user