1
0
forked from 0ad/0ad

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:
mimo 2017-01-26 21:10:46 +00:00
parent ff743bc928
commit c2d0327af9
7 changed files with 272 additions and 26 deletions

View File

@ -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);

View File

@ -0,0 +1 @@
Engine.RegisterInterface("VisionSharing");

View File

@ -140,6 +140,7 @@
<Vision>
<Range>40</Range>
</Vision>
<VisionSharing/>
<VisualActor>
<ConstructionPreview/>
<SilhouetteDisplay>false</SilhouetteDisplay>

View File

@ -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
*/

View File

@ -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)

View File

@ -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;

View File

@ -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