Add a VisibilityChanged message sent by the range manager whenever an entity changes visibility for any player.

This will be necessary for hiding buildings/trees/etc in fog-of-war, and
may be useful for AIs and for UnitAI.

Refs #599.

This was SVN commit r15508.
This commit is contained in:
Nicolas Auvray 2014-07-10 20:51:39 +00:00
parent 9d335f2e22
commit 2174eaaeee
6 changed files with 174 additions and 12 deletions

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -361,6 +361,25 @@ public:
int32_t i0, j0, i1, j1; // inclusive lower bound, exclusive upper bound, in tiles
};
/**
* Sent, at most once per turn, when the visibility of an entity changed
*/
class CMessageVisibilityChanged : public CMessage
{
public:
DEFAULT_MESSAGE_IMPL(VisibilityChanged)
CMessageVisibilityChanged(player_id_t player, entity_id_t ent, int oldVisibility, int newVisibility) :
player(player), ent(ent), oldVisibility(oldVisibility), newVisibility(newVisibility)
{
}
player_id_t player;
entity_id_t ent;
int oldVisibility;
int newVisibility;
};
/**
* Sent when ObstructionManager's view of the shape of the world has changed
* (changing the TILE_OUTOFBOUNDS tiles returned by Rasterise).

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -48,6 +48,7 @@ MESSAGE(TerritoryPositionChanged)
MESSAGE(MotionChanged)
MESSAGE(RangeUpdate)
MESSAGE(TerrainChanged)
MESSAGE(VisibilityChanged)
MESSAGE(WaterChanged)
MESSAGE(ObstructionMapShapeChanged)
MESSAGE(TerritoriesChanged)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -39,7 +39,7 @@
#include "renderer/Scene.h"
#include "lib/ps_stl.h"
#define LOS_TILES_RATIO 8
#define DEBUG_RANGE_MANAGER_BOUNDS 0
/**
@ -138,13 +138,14 @@ struct EntityData
EntityData() : retainInFog(0), owner(-1), inWorld(0), flags(1) { }
entity_pos_t x, z;
entity_pos_t visionRange;
u32 visibilities; // 2-bit visibility, per player
u8 retainInFog; // boolean
i8 owner;
u8 inWorld; // boolean
u8 flags; // See GetEntityFlagMask
};
cassert(sizeof(EntityData) == 16);
cassert(sizeof(EntityData) == 20);
/**
* Serialization helper template for Query
@ -197,6 +198,7 @@ struct SerializeEntityData
serialize.NumberFixed_Unbounded("z", value.z);
serialize.NumberFixed_Unbounded("vision", value.visionRange);
serialize.NumberU8("retain in fog", value.retainInFog, 0, 1);
serialize.NumberU32_Unbounded("visibilities", value.visibilities);
serialize.NumberI8_Unbounded("owner", value.owner);
serialize.NumberU8("in world", value.inWorld, 0, 1);
serialize.NumberU8_Unbounded("flags", value.flags);
@ -283,6 +285,13 @@ public:
bool m_LosCircular;
i32 m_TerrainVerticesPerSide;
size_t m_TerritoriesDirtyID;
// Cache for visibility tracking (not serialized)
i32 m_LosTilesPerSide;
bool* m_DirtyVisibility;
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;
// 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
@ -332,11 +341,14 @@ public:
m_LosCircular = false;
m_TerrainVerticesPerSide = 0;
m_DirtyVisibility = NULL;
m_TerritoriesDirtyID = 0;
}
virtual void Deinit()
{
delete[] m_DirtyVisibility;
}
template<typename S>
@ -355,7 +367,9 @@ public:
serialize.Bool("los circular", m_LosCircular);
serialize.NumberI32_Unbounded("terrain verts per side", m_TerrainVerticesPerSide);
// We don't serialize m_Subdivision or m_LosPlayerCounts
SerializeVector<SerializeU32_Unbounded>()(serialize, "modified entities", m_ModifiedEntities);
// We don't serialize m_Subdivision, m_LosPlayerCounts or m_LosTiles
// since they can be recomputed from the entity data when deserializing;
// m_LosState must be serialized since it depends on the history of exploration
@ -432,12 +446,20 @@ public:
CFixedVector2D to(msgData.x, msgData.z);
m_Subdivision.Move(ent, from, to);
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)
{
RemoveFromTile(oldLosTile, ent);
AddToTile(newLosTile, ent);
}
}
else
{
CFixedVector2D to(msgData.x, msgData.z);
m_Subdivision.Add(ent, to);
LosAdd(it->second.owner, it->second.visionRange, to);
AddToTile(PosToLosTilesHelper(msgData.x, msgData.z), ent);
}
it->second.inWorld = 1;
@ -451,6 +473,7 @@ public:
CFixedVector2D from(it->second.x, it->second.z);
m_Subdivision.Remove(ent, from);
LosRemove(it->second.owner, it->second.visionRange, from);
RemoveFromTile(PosToLosTilesHelper(it->second.x, it->second.z), ent);
}
it->second.inWorld = 0;
@ -458,6 +481,8 @@ public:
it->second.z = entity_pos_t::Zero();
}
m_ModifiedEntities.push_back(ent);
break;
}
case MT_OwnershipChanged:
@ -495,7 +520,10 @@ public:
break;
if (it->second.inWorld)
{
m_Subdivision.Remove(ent, CFixedVector2D(it->second.x, it->second.z));
RemoveFromTile(PosToLosTilesHelper(it->second.x, it->second.z), ent);
}
// 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
@ -539,6 +567,7 @@ public:
case MT_Update:
{
m_DebugOverlayDirty = true;
UpdateVisibilityData();
UpdateTerritoriesLos();
ExecuteActiveQueries();
break;
@ -559,6 +588,8 @@ public:
m_WorldX1 = x1;
m_WorldZ1 = z1;
m_TerrainVerticesPerSide = (i32)vertices;
m_LosTilesPerSide = (m_TerrainVerticesPerSide - 1)/LOS_TILES_RATIO;
ResetDerivedData(false);
}
@ -639,11 +670,19 @@ public:
}
m_LosStateRevealed.clear();
m_LosStateRevealed.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
delete[] m_DirtyVisibility;
m_DirtyVisibility = new bool[m_LosTilesPerSide*m_LosTilesPerSide]();
m_LosTiles.clear();
m_LosTiles.resize(m_LosTilesPerSide*m_LosTilesPerSide);
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));
AddToTile(PosToLosTilesHelper(it->second.x, it->second.z), it->first);
}
}
m_TotalInworldVertices = 0;
@ -1384,6 +1423,82 @@ public:
return GetLosVisibility(handle, player, forceRetainInFog);
}
i32 PosToLosTilesHelper(entity_pos_t x, entity_pos_t z)
{
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)
{
for (std::set<entity_id_t>::iterator tileIt = m_LosTiles[tile].begin();
tileIt != m_LosTiles[tile].end();
++tileIt)
{
if (*tileIt == ent)
{
m_LosTiles[tile].erase(tileIt);
return;
}
}
}
void UpdateVisibilityData()
{
PROFILE("UpdateVisibilityData");
for (i32 n = 0; n < m_LosTilesPerSide*m_LosTilesPerSide; ++n)
{
if (m_DirtyVisibility[n])
{
for (std::set<entity_id_t>::iterator it = m_LosTiles[n].begin();
it != m_LosTiles[n].end();
++it)
{
UpdateVisibility(*it);
}
m_DirtyVisibility[n] = false;
}
}
for (std::vector<entity_id_t>::iterator it = m_ModifiedEntities.begin(); it != m_ModifiedEntities.end(); ++it)
{
UpdateVisibility(*it);
}
m_ModifiedEntities.clear();
}
void UpdateVisibility(entity_id_t ent)
{
EntityMap<EntityData>::iterator itEnts = m_EntityData.find(ent);
if (itEnts == m_EntityData.end())
return;
for (player_id_t player = 1; player <= MAX_LOS_PLAYER_ID; ++player)
{
u8 oldVis = (itEnts->second.visibilities >> (2*player)) & 0x3;
u8 newVis = GetLosVisibility(itEnts->first, player, false);
if (oldVis != newVis)
{
CMessageVisibilityChanged msg(player, ent, oldVis, newVis);
GetSimContext().GetComponentManager().PostMessage(ent, msg);
itEnts->second.visibilities = (itEnts->second.visibilities & ~(0x3 << 2*player)) | (newVis << 2*player);
}
}
}
virtual void SetLosRevealAll(player_id_t player, bool enabled)
{
@ -1532,6 +1647,7 @@ public:
explored += !(m_LosState[idx] & (LOS_EXPLORED << (2*(owner-1))));
m_LosState[idx] |= ((LOS_VISIBLE | LOS_EXPLORED) << (2*(owner-1)));
}
m_DirtyVisibility[(j/LOS_TILES_RATIO)*m_LosTilesPerSide + i/LOS_TILES_RATIO] = true;
}
ASSERT(counts[idx] < 65535);
@ -1559,6 +1675,9 @@ public:
{
// (If LosIsOffWorld then this is a no-op, so don't bother doing the check)
m_LosState[idx] &= ~(LOS_VISIBLE << (2*(owner-1)));
i32 i = i0 + idx - idx0;
m_DirtyVisibility[(j/LOS_TILES_RATIO)*m_LosTilesPerSide + i/LOS_TILES_RATIO] = true;
}
}
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -216,9 +216,9 @@ public:
enum ELosVisibility
{
VIS_HIDDEN,
VIS_FOGGED,
VIS_VISIBLE
VIS_HIDDEN = 0,
VIS_FOGGED = 1,
VIS_VISIBLE = 2
};
/**
@ -326,6 +326,7 @@ public:
virtual ELosVisibility GetLosVisibility(CEntityHandle ent, player_id_t player, bool forceRetainInFog = false) = 0;
virtual ELosVisibility GetLosVisibility(entity_id_t ent, player_id_t player, bool forceRetainInFog = false) = 0;
/**
* GetLosVisibility wrapped for script calls.
* Returns "hidden", "fogged" or "visible".

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -310,6 +310,28 @@ CMessage* CMessageTerrainChanged::FromJSVal(ScriptInterface& scriptInterface, js
////////////////////////////////
jsval CMessageVisibilityChanged::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(player);
SET_MSG_PROPERTY(ent);
SET_MSG_PROPERTY(oldVisibility);
SET_MSG_PROPERTY(newVisibility);
return OBJECT_TO_JSVAL(obj);
}
CMessage* CMessageVisibilityChanged::FromJSVal(ScriptInterface& scriptInterface, jsval val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(player_id_t, player);
GET_MSG_PROPERTY(entity_id_t, ent);
GET_MSG_PROPERTY(int, oldVisibility);
GET_MSG_PROPERTY(int, newVisibility);
return new CMessageVisibilityChanged(player, ent, oldVisibility, newVisibility);
}
////////////////////////////////
jsval CMessageWaterChanged::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();